engineless flutter framework
diff --git a/bin/generate.dart b/bin/generate.dart
new file mode 100644
index 0000000..adc958a
--- /dev/null
+++ b/bin/generate.dart
@@ -0,0 +1,17 @@
+// Copyright 2016 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+void main() {
+
+}
diff --git a/lib/animation.dart b/lib/animation.dart
new file mode 100644
index 0000000..88bec71
--- /dev/null
+++ b/lib/animation.dart
@@ -0,0 +1,169 @@
+// Copyright 2014 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.
+
+/// The Flutter animation system.
+///
+/// To use, import `package:flutter/animation.dart`.
+///
+/// This library provides basic building blocks for implementing animations in
+/// Flutter. Other layers of the framework use these building blocks to provide
+/// advanced animation support for applications. For example, the widget library
+/// includes [ImplicitlyAnimatedWidget]s and [AnimatedWidget]s that make it easy
+/// to animate certain properties of a [Widget]. If those animated widgets are
+/// not sufficient for a given use case, the basic building blocks provided by
+/// this library can be used to implement custom animated effects.
+///
+/// This library depends only on core Dart libraries and the `physics.dart`
+/// library.
+///
+///
+/// ### Foundations: the Animation class
+///
+/// Flutter represents an animation as a value that changes over a given
+/// duration, and that value may be of any type. For example, it could be a
+/// [double] indicating the current opacity of a [Widget] as it fades out. Or,
+/// it could be the current background [Color] of a widget that transitions
+/// smoothly from one color to another. The current value of an animation is
+/// represented by an [Animation] object, which is the central class of the
+/// animation library. In addition to the current animation value, the
+/// [Animation] object also stores the current [AnimationStatus]. The status
+/// indicates whether the animation is currently conceptually running from the
+/// beginning to the end or the other way around. It may also indicate that the
+/// animation is currently stopped at the beginning or the end.
+///
+/// Other objects can register listeners on an [Animation] to be informed
+/// whenever the animation value and/or the animation status changes. A [Widget]
+/// may register such a *value* listener via [Animation.addListener] to rebuild
+/// itself with the current animation value whenever that value changes. For
+/// example, a widget might listen to an animation to update its opacity to the
+/// animation's value every time that value changes. Likewise, registering a
+/// *status* listener via [Animation.addStatusListener] may be useful to trigger
+/// another action when the current animation has ended.
+///
+/// As an example, the following video shows the changes over time in the
+/// current animation status and animation value for the opacity animation of a
+/// widget. This [Animation] is driven by an [AnimationController] (see next
+/// section). Before the animation triggers, the animation status is "dismissed"
+/// and the value is 0.0. As the value runs from 0.0 to 1.0 to fade in the
+/// widget, the status changes to "forward". When the widget is fully faded in
+/// at an animation value of 1.0 the status is "completed". When the animation
+/// triggers again to fade the widget back out, the animation status changes to
+/// "reverse" and the animation value runs back to 0.0. At that point the widget
+/// is fully faded out and the animation status switches back to "dismissed"
+/// until the animation is triggered again.
+///
+/// {@animation 420 100 https://flutter.github.io/assets-for-api-docs/assets/animation/animation_status_value.mp4}
+///
+/// Although you can't instantiate [Animation] directly (it is an abstract
+/// class), you can create one using an [AnimationController].
+///
+///
+/// ### Powering animations: AnimationController
+///
+/// An [AnimationController] is a special kind of [Animation] that advances its
+/// animation value whenever the device running the application is ready to
+/// display a new frame (typically, this rate is around 60 values per second).
+/// An [AnimationController] can be used wherever an [Animation] is expected. As
+/// the name implies, an [AnimationController] also provides control over its
+/// [Animation]: It implements methods to stop the animation at any time and to
+/// run it forward as well as in the reverse direction.
+///
+/// By default, an [AnimationController] increases its animation value linearly
+/// over the given duration from 0.0 to 1.0 when run in the forward direction.
+/// For many use cases you might want the value to be of a different type,
+/// change the range of the animation values, or change how the animation moves
+/// between values. This is achieved by wrapping the animation: Wrapping it in
+/// an [Animatable] (see below) changes the range of animation values to a
+/// different range or type (for example to animate [Color]s or [Rect]s).
+/// Furthermore, a [Curve] can be applied to the animation by wrapping it in a
+/// [CurvedAnimation]. Instead of linearly increasing the animation value, a
+/// curved animation changes its value according to the provided curve. The
+/// framework ships with many built-in curves (see [Curves]). As an example,
+/// [Curves.easeOutCubic] increases the animation value quickly at the beginning
+/// of the animation and then slows down until the target value is reached:
+///
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_cubic.mp4}
+///
+///
+/// ### Animating different types: Animatable
+///
+/// An `Animatable<T>` is an object that takes an `Animation<double>` as input
+/// and produces a value of type `T`. Objects of these types can be used to
+/// translate the animation value range of an [AnimationController] (or any
+/// other [Animation] of type [double]) to a different range. That new range
+/// doesn't even have to be of type double anymore. With the help of an
+/// [Animatable] like a [Tween] or a [TweenSequence] (see sections below) an
+/// [AnimationController] can be used to smoothly transition [Color]s, [Rect]s,
+/// [Size]s and many more types from one value to another over a given duration.
+///
+///
+/// ### Interpolating values: Tweens
+///
+/// A [Tween] is applied to an [Animation] of type [double] to change the
+/// range and type of the animation value. For example, to transition the
+/// background of a [Widget] smoothly between two [Color]s, a [ColorTween] can
+/// be used. Each [Tween] specifies a start and an end value. As the animation
+/// value of the [Animation] powering the [Tween] progresses from 0.0 to 1.0 it
+/// produces interpolated values between its start and end value. The values
+/// produced by the [Tween] usually move closer and closer to its end value as
+/// the animation value of the powering [Animation] approaches 1.0.
+///
+/// The following video shows example values produced by an [IntTween], a
+/// `Tween<double>`, and a [ColorTween] as the animation value runs from 0.0 to
+/// 1.0 and back to 0.0:
+///
+/// {@animation 530 150 https://flutter.github.io/assets-for-api-docs/assets/animation/tweens.mp4}
+///
+/// An [Animation] or [AnimationController] can power multiple [Tween]s. For
+/// example, to animate the size and the color of a widget in parallel, create
+/// one [AnimationController] that powers a [SizeTween] and a [ColorTween].
+///
+/// The framework ships with many [Tween] subclasses ([IntTween], [SizeTween],
+/// [RectTween], etc.) to animate common properties.
+///
+///
+/// ### Staggered animations: TweenSequences
+///
+/// A [TweenSequence] can help animate a given property smoothly in stages. Each
+/// [Tween] in the sequence is responsible for a different stage and has an
+/// associated weight. When the animation runs, the stages execute one after
+/// another. For example, let's say you want to animate the background of a
+/// widget from yellow to green and then, after a short pause, to red. For this
+/// you can specify three tweens within a tween sequence: One [ColorTween]
+/// animating from yellow to green, one [ConstantTween] that just holds the color
+/// green, and another [ColorTween] animating from green to red. For each
+/// tween you need to pick a weight indicating the ratio of time spent on that
+/// tween compared to all other tweens. If we assign a weight of 2 to both of
+/// the [ColorTween]s and a weight of 1 to the [ConstantTween] the transition
+/// described by the [ColorTween]s would take twice as long as the
+/// [ConstantTween]. A [TweenSequence] is driven by an [Animation] just like a
+/// regular [Tween]: As the powering [Animation] runs from 0.0 to 1.0 the
+/// [TweenSequence] runs through all of its stages.
+///
+/// The following video shows the animation described in the previous paragraph:
+///
+/// {@animation 646 250 https://flutter.github.io/assets-for-api-docs/assets/animation/tween_sequence.mp4}
+///
+///
+/// See also:
+///
+///  * [Introduction to animations](https://flutter.dev/docs/development/ui/animations)
+///    on flutter.dev.
+///  * [Animations tutorial](https://flutter.dev/docs/development/ui/animations/tutorial)
+///    on flutter.dev.
+///  * [Sample app](https://github.com/flutter/samples/tree/master/animations),
+///    which showcases Flutter's animation features.
+///  * [ImplicitlyAnimatedWidget] and its subclasses, which are [Widget]s that
+///    implicitly animate changes to their properties.
+///  * [AnimatedWidget] and its subclasses, which are [Widget]s that take an
+///    explicit [Animation] to animate their properties.
+library animation;
+
+export 'src/animation/animation.dart';
+export 'src/animation/animation_controller.dart';
+export 'src/animation/animations.dart';
+export 'src/animation/curves.dart';
+export 'src/animation/listener_helpers.dart';
+export 'src/animation/tween.dart';
+export 'src/animation/tween_sequence.dart';
diff --git a/lib/cupertino.dart b/lib/cupertino.dart
new file mode 100644
index 0000000..9c0ef65
--- /dev/null
+++ b/lib/cupertino.dart
@@ -0,0 +1,53 @@
+// Copyright 2014 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.
+
+/// Flutter widgets implementing the current iOS design language.
+///
+/// To use, import `package:flutter/cupertino.dart`.
+///
+/// This library is designed for apps that run on iOS. For apps that may also
+/// run on other operating systems, we encourage use of other widgets, for
+/// example the [Material
+/// Design](https://flutter.dev/docs/development/ui/widgets/material) set.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=3PdUaidHc-E}
+library cupertino;
+
+export 'src/cupertino/action_sheet.dart';
+export 'src/cupertino/activity_indicator.dart';
+export 'src/cupertino/app.dart';
+export 'src/cupertino/bottom_tab_bar.dart';
+export 'src/cupertino/button.dart';
+export 'src/cupertino/colors.dart';
+export 'src/cupertino/constants.dart';
+export 'src/cupertino/context_menu.dart';
+export 'src/cupertino/context_menu_action.dart';
+export 'src/cupertino/date_picker.dart';
+export 'src/cupertino/dialog.dart';
+export 'src/cupertino/form_row.dart';
+export 'src/cupertino/form_section.dart';
+export 'src/cupertino/icon_theme_data.dart';
+export 'src/cupertino/icons.dart';
+export 'src/cupertino/interface_level.dart';
+export 'src/cupertino/localizations.dart';
+export 'src/cupertino/nav_bar.dart';
+export 'src/cupertino/page_scaffold.dart';
+export 'src/cupertino/picker.dart';
+export 'src/cupertino/refresh.dart';
+export 'src/cupertino/route.dart';
+export 'src/cupertino/scrollbar.dart';
+export 'src/cupertino/search_field.dart';
+export 'src/cupertino/segmented_control.dart';
+export 'src/cupertino/slider.dart';
+export 'src/cupertino/sliding_segmented_control.dart';
+export 'src/cupertino/switch.dart';
+export 'src/cupertino/tab_scaffold.dart';
+export 'src/cupertino/tab_view.dart';
+export 'src/cupertino/text_field.dart';
+export 'src/cupertino/text_form_field_row.dart';
+export 'src/cupertino/text_selection.dart';
+export 'src/cupertino/text_theme.dart';
+export 'src/cupertino/theme.dart';
+export 'src/cupertino/thumb_painter.dart';
+export 'widgets.dart';
diff --git a/lib/fix_data.yaml b/lib/fix_data.yaml
new file mode 100644
index 0000000..cf74e86
--- /dev/null
+++ b/lib/fix_data.yaml
@@ -0,0 +1,36 @@
+# Copyright 2014 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.
+
+# TODO(Piinks): Add link to public user guide when available.
+
+# Please add new fixes to the top of the file, separated by one blank line
+# from other fixes. In a comment, include a link to the PR where the change
+# requiring the fix was made.
+
+version: 1
+transforms:
+
+  # Changes made in https://github.com/flutter/flutter/pull/41859
+  - title: 'Remove brightness'
+    date: 2020-12-10
+    element:
+      uris: [ 'cupertino.dart' ]
+      constructor: ''
+      inClass: 'CupertinoTextThemeData'
+    changes:
+      - kind: 'removeParameter'
+        name: 'brightness'
+
+  # Changes made in https://github.com/flutter/flutter/pull/41859
+  - title: 'Remove brightness'
+    date: 2020-12-10
+    element:
+      uris: [ 'cupertino.dart' ]
+      method: 'copyWith'
+      inClass: 'CupertinoTextThemeData'
+    changes:
+      - kind: 'removeParameter'
+        name: 'brightness'
+
+# Before adding a new fix: read instructions at the top of this file.
diff --git a/lib/foundation.dart b/lib/foundation.dart
new file mode 100644
index 0000000..82a1c16
--- /dev/null
+++ b/lib/foundation.dart
@@ -0,0 +1,59 @@
+// Copyright 2014 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.
+
+/// Core Flutter framework primitives.
+///
+/// The features defined in this library are the lowest-level utility
+/// classes and functions used by all the other layers of the Flutter
+/// framework.
+library foundation;
+
+export 'package:meta/meta.dart' show
+  factory,
+  immutable,
+  mustCallSuper,
+  nonVirtual,
+  optionalTypeArgs,
+  protected,
+  required,
+  visibleForTesting;
+
+// Examples can assume:
+// // @dart = 2.9
+// String _name;
+// bool _first;
+// bool _lights;
+// bool _visible;
+// bool inherit;
+// int columns;
+// int rows;
+// class Cat { }
+// double _volume;
+// dynamic _calculation;
+// dynamic _last;
+// dynamic _selection;
+
+export 'src/foundation/annotations.dart';
+export 'src/foundation/assertions.dart';
+export 'src/foundation/basic_types.dart';
+export 'src/foundation/binding.dart';
+export 'src/foundation/bitfield.dart';
+export 'src/foundation/change_notifier.dart';
+export 'src/foundation/collections.dart';
+export 'src/foundation/consolidate_response.dart';
+export 'src/foundation/constants.dart';
+export 'src/foundation/debug.dart';
+export 'src/foundation/diagnostics.dart';
+export 'src/foundation/isolates.dart';
+export 'src/foundation/key.dart';
+export 'src/foundation/licenses.dart';
+export 'src/foundation/node.dart';
+export 'src/foundation/object.dart';
+export 'src/foundation/observer_list.dart';
+export 'src/foundation/platform.dart';
+export 'src/foundation/print.dart';
+export 'src/foundation/serialization.dart';
+export 'src/foundation/stack_frame.dart';
+export 'src/foundation/synchronous_future.dart';
+export 'src/foundation/unicode.dart';
diff --git a/lib/gestures.dart b/lib/gestures.dart
new file mode 100644
index 0000000..7bc4302
--- /dev/null
+++ b/lib/gestures.dart
@@ -0,0 +1,33 @@
+// Copyright 2014 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.
+
+/// The Flutter gesture recognizers.
+///
+/// To use, import `package:flutter/gestures.dart`.
+library gestures;
+
+export 'src/gestures/arena.dart';
+export 'src/gestures/binding.dart';
+export 'src/gestures/constants.dart';
+export 'src/gestures/converter.dart';
+export 'src/gestures/debug.dart';
+export 'src/gestures/drag.dart';
+export 'src/gestures/drag_details.dart';
+export 'src/gestures/eager.dart';
+export 'src/gestures/events.dart';
+export 'src/gestures/force_press.dart';
+export 'src/gestures/hit_test.dart';
+export 'src/gestures/long_press.dart';
+export 'src/gestures/lsq_solver.dart';
+export 'src/gestures/monodrag.dart';
+export 'src/gestures/multidrag.dart';
+export 'src/gestures/multitap.dart';
+export 'src/gestures/pointer_router.dart';
+export 'src/gestures/pointer_signal_resolver.dart';
+export 'src/gestures/recognizer.dart';
+export 'src/gestures/resampler.dart';
+export 'src/gestures/scale.dart';
+export 'src/gestures/tap.dart';
+export 'src/gestures/team.dart';
+export 'src/gestures/velocity_tracker.dart';
diff --git a/lib/material.dart b/lib/material.dart
new file mode 100644
index 0000000..37b1aa5
--- /dev/null
+++ b/lib/material.dart
@@ -0,0 +1,154 @@
+// Copyright 2014 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.
+
+/// Flutter widgets implementing Material Design.
+///
+/// To use, import `package:flutter/material.dart`.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=DL0Ix1lnC4w}
+///
+/// See also:
+///
+///  * [flutter.dev/widgets](https://flutter.dev/widgets/)
+///    for a catalog of commonly-used Flutter widgets.
+///  * [material.io/design](https://material.io/design/)
+///    for an introduction to Material Design.
+library material;
+
+export 'src/material/about.dart';
+export 'src/material/animated_icons.dart';
+export 'src/material/app.dart';
+export 'src/material/app_bar.dart';
+export 'src/material/app_bar_theme.dart';
+export 'src/material/arc.dart';
+export 'src/material/back_button.dart';
+export 'src/material/banner.dart';
+export 'src/material/banner_theme.dart';
+export 'src/material/bottom_app_bar.dart';
+export 'src/material/bottom_app_bar_theme.dart';
+export 'src/material/bottom_navigation_bar.dart';
+export 'src/material/bottom_navigation_bar_theme.dart';
+export 'src/material/bottom_sheet.dart';
+export 'src/material/bottom_sheet_theme.dart';
+export 'src/material/button.dart';
+export 'src/material/button_bar.dart';
+export 'src/material/button_bar_theme.dart';
+export 'src/material/button_style.dart';
+export 'src/material/button_style_button.dart';
+export 'src/material/button_theme.dart';
+export 'src/material/calendar_date_picker.dart';
+export 'src/material/card.dart';
+export 'src/material/card_theme.dart';
+export 'src/material/checkbox.dart';
+export 'src/material/checkbox_list_tile.dart';
+export 'src/material/checkbox_theme.dart';
+export 'src/material/chip.dart';
+export 'src/material/chip_theme.dart';
+export 'src/material/circle_avatar.dart';
+export 'src/material/color_scheme.dart';
+export 'src/material/colors.dart';
+export 'src/material/constants.dart';
+export 'src/material/curves.dart';
+export 'src/material/data_table.dart';
+export 'src/material/data_table_source.dart';
+export 'src/material/data_table_theme.dart';
+export 'src/material/date.dart';
+export 'src/material/date_picker.dart';
+export 'src/material/date_picker_deprecated.dart';
+export 'src/material/debug.dart';
+export 'src/material/dialog.dart';
+export 'src/material/dialog_theme.dart';
+export 'src/material/divider.dart';
+export 'src/material/divider_theme.dart';
+export 'src/material/drawer.dart';
+export 'src/material/drawer_header.dart';
+export 'src/material/dropdown.dart';
+export 'src/material/elevated_button.dart';
+export 'src/material/elevated_button_theme.dart';
+export 'src/material/elevation_overlay.dart';
+export 'src/material/expand_icon.dart';
+export 'src/material/expansion_panel.dart';
+export 'src/material/expansion_tile.dart';
+export 'src/material/feedback.dart';
+export 'src/material/flat_button.dart';
+export 'src/material/flexible_space_bar.dart';
+export 'src/material/floating_action_button.dart';
+export 'src/material/floating_action_button_location.dart';
+export 'src/material/floating_action_button_theme.dart';
+export 'src/material/flutter_logo.dart';
+export 'src/material/grid_tile.dart';
+export 'src/material/grid_tile_bar.dart';
+export 'src/material/icon_button.dart';
+export 'src/material/icons.dart';
+export 'src/material/ink_decoration.dart';
+export 'src/material/ink_highlight.dart';
+export 'src/material/ink_ripple.dart';
+export 'src/material/ink_splash.dart';
+export 'src/material/ink_well.dart';
+export 'src/material/input_border.dart';
+export 'src/material/input_date_picker_form_field.dart';
+export 'src/material/input_decorator.dart';
+export 'src/material/list_tile.dart';
+export 'src/material/material.dart';
+export 'src/material/material_button.dart';
+export 'src/material/material_localizations.dart';
+export 'src/material/material_state.dart';
+export 'src/material/mergeable_material.dart';
+export 'src/material/navigation_rail.dart';
+export 'src/material/navigation_rail_theme.dart';
+export 'src/material/outline_button.dart';
+export 'src/material/outlined_button.dart';
+export 'src/material/outlined_button_theme.dart';
+export 'src/material/page.dart';
+export 'src/material/page_transitions_theme.dart';
+export 'src/material/paginated_data_table.dart';
+export 'src/material/popup_menu.dart';
+export 'src/material/popup_menu_theme.dart';
+export 'src/material/progress_indicator.dart';
+export 'src/material/radio.dart';
+export 'src/material/radio_list_tile.dart';
+export 'src/material/radio_theme.dart';
+export 'src/material/raised_button.dart';
+export 'src/material/range_slider.dart';
+export 'src/material/refresh_indicator.dart';
+export 'src/material/reorderable_list.dart';
+export 'src/material/scaffold.dart';
+export 'src/material/scrollbar.dart';
+export 'src/material/search.dart';
+export 'src/material/selectable_text.dart';
+export 'src/material/shadows.dart';
+export 'src/material/slider.dart';
+export 'src/material/slider_theme.dart';
+export 'src/material/snack_bar.dart';
+export 'src/material/snack_bar_theme.dart';
+export 'src/material/stepper.dart';
+export 'src/material/switch.dart';
+export 'src/material/switch_list_tile.dart';
+export 'src/material/switch_theme.dart';
+export 'src/material/tab_bar_theme.dart';
+export 'src/material/tab_controller.dart';
+export 'src/material/tab_indicator.dart';
+export 'src/material/tabs.dart';
+export 'src/material/text_button.dart';
+export 'src/material/text_button_theme.dart';
+export 'src/material/text_field.dart';
+export 'src/material/text_form_field.dart';
+export 'src/material/text_selection.dart';
+export 'src/material/text_selection_theme.dart';
+export 'src/material/text_selection_toolbar.dart';
+export 'src/material/text_selection_toolbar_text_button.dart';
+export 'src/material/text_theme.dart';
+export 'src/material/theme.dart';
+export 'src/material/theme_data.dart';
+export 'src/material/time.dart';
+export 'src/material/time_picker.dart';
+export 'src/material/time_picker_theme.dart';
+export 'src/material/toggle_buttons.dart';
+export 'src/material/toggle_buttons_theme.dart';
+export 'src/material/toggleable.dart';
+export 'src/material/tooltip.dart';
+export 'src/material/tooltip_theme.dart';
+export 'src/material/typography.dart';
+export 'src/material/user_accounts_drawer_header.dart';
+export 'widgets.dart';
diff --git a/lib/painting.dart b/lib/painting.dart
new file mode 100644
index 0000000..b73acac
--- /dev/null
+++ b/lib/painting.dart
@@ -0,0 +1,61 @@
+// Copyright 2014 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.
+
+/// The Flutter painting library.
+///
+/// To use, import `package:flutter/painting.dart`.
+///
+/// This library includes a variety of classes that wrap the Flutter
+/// engine's painting API for more specialized purposes, such as painting scaled
+/// images, interpolating between shadows, painting borders around boxes, etc.
+///
+/// In particular:
+///
+///  * Use the [TextPainter] class for painting text.
+///  * Use [Decoration] (and more concretely [BoxDecoration]) for
+///    painting boxes.
+library painting;
+
+export 'package:flute/ui.dart' show Shadow, PlaceholderAlignment, TextHeightBehavior;
+
+export 'src/painting/alignment.dart';
+export 'src/painting/basic_types.dart';
+export 'src/painting/beveled_rectangle_border.dart';
+export 'src/painting/binding.dart';
+export 'src/painting/border_radius.dart';
+export 'src/painting/borders.dart';
+export 'src/painting/box_border.dart';
+export 'src/painting/box_decoration.dart';
+export 'src/painting/box_fit.dart';
+export 'src/painting/box_shadow.dart';
+export 'src/painting/circle_border.dart';
+export 'src/painting/clip.dart';
+export 'src/painting/colors.dart';
+export 'src/painting/continuous_rectangle_border.dart';
+export 'src/painting/debug.dart';
+export 'src/painting/decoration.dart';
+export 'src/painting/decoration_image.dart';
+export 'src/painting/edge_insets.dart';
+export 'src/painting/flutter_logo.dart';
+export 'src/painting/fractional_offset.dart';
+export 'src/painting/geometry.dart';
+export 'src/painting/gradient.dart';
+export 'src/painting/image_cache.dart';
+export 'src/painting/image_decoder.dart';
+export 'src/painting/image_provider.dart';
+export 'src/painting/image_resolution.dart';
+export 'src/painting/image_stream.dart';
+export 'src/painting/inline_span.dart';
+export 'src/painting/matrix_utils.dart';
+export 'src/painting/notched_shapes.dart';
+export 'src/painting/paint_utilities.dart';
+export 'src/painting/placeholder_span.dart';
+export 'src/painting/rounded_rectangle_border.dart';
+export 'src/painting/shader_warm_up.dart';
+export 'src/painting/shape_decoration.dart';
+export 'src/painting/stadium_border.dart';
+export 'src/painting/strut_style.dart';
+export 'src/painting/text_painter.dart';
+export 'src/painting/text_span.dart';
+export 'src/painting/text_style.dart';
diff --git a/lib/physics.dart b/lib/physics.dart
new file mode 100644
index 0000000..2475523
--- /dev/null
+++ b/lib/physics.dart
@@ -0,0 +1,17 @@
+// Copyright 2014 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.
+
+/// Simple one-dimensional physics simulations, such as springs, friction, and
+/// gravity, for use in user interface animations.
+///
+/// To use, import `package:flutter/physics.dart`.
+library physics;
+
+export 'src/physics/clamped_simulation.dart';
+export 'src/physics/friction_simulation.dart';
+export 'src/physics/gravity_simulation.dart';
+export 'src/physics/simulation.dart';
+export 'src/physics/spring_simulation.dart';
+export 'src/physics/tolerance.dart';
+export 'src/physics/utils.dart';
diff --git a/lib/rendering.dart b/lib/rendering.dart
new file mode 100644
index 0000000..3f08fbf
--- /dev/null
+++ b/lib/rendering.dart
@@ -0,0 +1,75 @@
+// Copyright 2014 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.
+
+/// The Flutter rendering tree.
+///
+/// To use, import `package:flutter/rendering.dart`.
+///
+/// The [RenderObject] hierarchy is used by the Flutter Widgets library to
+/// implement its layout and painting back-end. Generally, while you may use
+/// custom [RenderBox] classes for specific effects in your applications, most
+/// of the time your only interaction with the [RenderObject] hierarchy will be
+/// in debugging layout issues.
+///
+/// If you are developing your own library or application directly on top of the
+/// rendering library, then you will want to have a binding (see [BindingBase]).
+/// You can use [RenderingFlutterBinding], or you can create your own binding.
+/// If you create your own binding, it needs to import at least
+/// [ServicesBinding], [GestureBinding], [SchedulerBinding], [PaintingBinding],
+/// and [RendererBinding]. The rendering library does not automatically create a
+/// binding, but relies on one being initialized with those features.
+library rendering;
+
+export 'package:flute/foundation.dart' show
+  VoidCallback,
+  ValueChanged,
+  ValueGetter,
+  ValueSetter,
+  DiagnosticLevel;
+export 'package:flute/semantics.dart';
+export 'package:vector_math/vector_math_64.dart' show Matrix4;
+
+export 'src/rendering/animated_size.dart';
+export 'src/rendering/binding.dart';
+export 'src/rendering/box.dart';
+export 'src/rendering/custom_layout.dart';
+export 'src/rendering/custom_paint.dart';
+export 'src/rendering/debug.dart';
+export 'src/rendering/debug_overflow_indicator.dart';
+export 'src/rendering/editable.dart';
+export 'src/rendering/error.dart';
+export 'src/rendering/flex.dart';
+export 'src/rendering/flow.dart';
+export 'src/rendering/image.dart';
+export 'src/rendering/layer.dart';
+export 'src/rendering/layout_helper.dart';
+export 'src/rendering/list_body.dart';
+export 'src/rendering/list_wheel_viewport.dart';
+export 'src/rendering/mouse_cursor.dart';
+export 'src/rendering/mouse_tracking.dart';
+export 'src/rendering/object.dart';
+export 'src/rendering/paragraph.dart';
+export 'src/rendering/performance_overlay.dart';
+export 'src/rendering/platform_view.dart';
+export 'src/rendering/proxy_box.dart';
+export 'src/rendering/proxy_sliver.dart';
+export 'src/rendering/rotated_box.dart';
+export 'src/rendering/shifted_box.dart';
+export 'src/rendering/sliver.dart';
+export 'src/rendering/sliver_fill.dart';
+export 'src/rendering/sliver_fixed_extent_list.dart';
+export 'src/rendering/sliver_grid.dart';
+export 'src/rendering/sliver_list.dart';
+export 'src/rendering/sliver_multi_box_adaptor.dart';
+export 'src/rendering/sliver_padding.dart';
+export 'src/rendering/sliver_persistent_header.dart';
+export 'src/rendering/stack.dart';
+export 'src/rendering/table.dart';
+export 'src/rendering/table_border.dart';
+export 'src/rendering/texture.dart';
+export 'src/rendering/tweens.dart';
+export 'src/rendering/view.dart';
+export 'src/rendering/viewport.dart';
+export 'src/rendering/viewport_offset.dart';
+export 'src/rendering/wrap.dart';
diff --git a/lib/scheduler.dart b/lib/scheduler.dart
new file mode 100644
index 0000000..f1cbdc2
--- /dev/null
+++ b/lib/scheduler.dart
@@ -0,0 +1,19 @@
+// Copyright 2014 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.
+
+/// The Flutter Scheduler library.
+///
+/// To use, import `package:flutter/scheduler.dart`.
+///
+/// This library is responsible for scheduler frame callbacks, and tasks at
+/// given priorities.
+///
+/// The library makes sure that tasks are only run when appropriate.
+/// For example, an idle-task is only executed when no animation is running.
+library scheduler;
+
+export 'src/scheduler/binding.dart';
+export 'src/scheduler/debug.dart';
+export 'src/scheduler/priority.dart';
+export 'src/scheduler/ticker.dart';
diff --git a/lib/semantics.dart b/lib/semantics.dart
new file mode 100644
index 0000000..f838d2a
--- /dev/null
+++ b/lib/semantics.dart
@@ -0,0 +1,19 @@
+// Copyright 2014 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.
+
+/// The Flutter semantics package.
+///
+/// To use, import `package:flutter/semantics.dart`.
+///
+/// The [SemanticsEvent] classes define the protocol for sending semantic events
+/// to the platform.
+///
+/// The [SemanticsNode] hierarchy represents the semantic structure of the UI
+/// and is used by the platform-specific accessibility services.
+library semantics;
+
+export 'src/semantics/binding.dart';
+export 'src/semantics/debug.dart';
+export 'src/semantics/semantics.dart';
+export 'src/semantics/semantics_service.dart';
diff --git a/lib/services.dart b/lib/services.dart
new file mode 100644
index 0000000..2d9715f
--- /dev/null
+++ b/lib/services.dart
@@ -0,0 +1,42 @@
+// Copyright 2014 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.
+
+/// Platform services exposed to Flutter apps.
+///
+/// To use, import `package:flutter/services.dart`.
+///
+/// This library depends only on core Dart libraries and the `foundation`
+/// library.
+library services;
+
+export 'src/services/asset_bundle.dart';
+export 'src/services/autofill.dart';
+export 'src/services/binary_messenger.dart';
+export 'src/services/binding.dart';
+export 'src/services/clipboard.dart';
+export 'src/services/font_loader.dart';
+export 'src/services/haptic_feedback.dart';
+export 'src/services/keyboard_key.dart';
+export 'src/services/keyboard_maps.dart';
+export 'src/services/message_codec.dart';
+export 'src/services/message_codecs.dart';
+export 'src/services/platform_channel.dart';
+export 'src/services/platform_messages.dart';
+export 'src/services/platform_views.dart';
+export 'src/services/raw_keyboard.dart';
+export 'src/services/raw_keyboard_android.dart';
+export 'src/services/raw_keyboard_fuchsia.dart';
+export 'src/services/raw_keyboard_ios.dart';
+export 'src/services/raw_keyboard_linux.dart';
+export 'src/services/raw_keyboard_macos.dart';
+export 'src/services/raw_keyboard_web.dart';
+export 'src/services/raw_keyboard_windows.dart';
+export 'src/services/restoration.dart';
+export 'src/services/system_channels.dart';
+export 'src/services/system_chrome.dart';
+export 'src/services/system_navigator.dart';
+export 'src/services/system_sound.dart';
+export 'src/services/text_editing.dart';
+export 'src/services/text_formatter.dart';
+export 'src/services/text_input.dart';
diff --git a/lib/src/animation/animation.dart b/lib/src/animation/animation.dart
new file mode 100644
index 0000000..07732af
--- /dev/null
+++ b/lib/src/animation/animation.dart
@@ -0,0 +1,205 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+import 'tween.dart';
+
+// Examples can assume:
+// late AnimationController _controller;
+
+/// The status of an animation.
+enum AnimationStatus {
+  /// The animation is stopped at the beginning.
+  dismissed,
+
+  /// The animation is running from beginning to end.
+  forward,
+
+  /// The animation is running backwards, from end to beginning.
+  reverse,
+
+  /// The animation is stopped at the end.
+  completed,
+}
+
+/// Signature for listeners attached using [Animation.addStatusListener].
+typedef AnimationStatusListener = void Function(AnimationStatus status);
+
+/// An animation with a value of type `T`.
+///
+/// An animation consists of a value (of type `T`) together with a status. The
+/// status indicates whether the animation is conceptually running from
+/// beginning to end or from the end back to the beginning, although the actual
+/// value of the animation might not change monotonically (e.g., if the
+/// animation uses a curve that bounces).
+///
+/// Animations also let other objects listen for changes to either their value
+/// or their status. These callbacks are called during the "animation" phase of
+/// the pipeline, just prior to rebuilding widgets.
+///
+/// To create a new animation that you can run forward and backward, consider
+/// using [AnimationController].
+///
+/// See also:
+///
+///  * [Tween], which can be used to create [Animation] subclasses that
+///    convert `Animation<double>`s into other kinds of `Animation`s.
+abstract class Animation<T> extends Listenable implements ValueListenable<T> {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const Animation();
+
+  // keep these next five dartdocs in sync with the dartdocs in AnimationWithParentMixin<T>
+
+  /// Calls the listener every time the value of the animation changes.
+  ///
+  /// Listeners can be removed with [removeListener].
+  @override
+  void addListener(VoidCallback listener);
+
+  /// Stop calling the listener every time the value of the animation changes.
+  ///
+  /// If `listener` is not currently registered as a listener, this method does
+  /// nothing.
+  ///
+  /// Listeners can be added with [addListener].
+  @override
+  void removeListener(VoidCallback listener);
+
+  /// Calls listener every time the status of the animation changes.
+  ///
+  /// Listeners can be removed with [removeStatusListener].
+  void addStatusListener(AnimationStatusListener listener);
+
+  /// Stops calling the listener every time the status of the animation changes.
+  ///
+  /// If `listener` is not currently registered as a status listener, this
+  /// method does nothing.
+  ///
+  /// Listeners can be added with [addStatusListener].
+  void removeStatusListener(AnimationStatusListener listener);
+
+  /// The current status of this animation.
+  AnimationStatus get status;
+
+  /// The current value of the animation.
+  @override
+  T get value;
+
+  /// Whether this animation is stopped at the beginning.
+  bool get isDismissed => status == AnimationStatus.dismissed;
+
+  /// Whether this animation is stopped at the end.
+  bool get isCompleted => status == AnimationStatus.completed;
+
+  /// Chains a [Tween] (or [CurveTween]) to this [Animation].
+  ///
+  /// This method is only valid for `Animation<double>` instances (i.e. when `T`
+  /// is `double`). This means, for instance, that it can be called on
+  /// [AnimationController] objects, as well as [CurvedAnimation]s,
+  /// [ProxyAnimation]s, [ReverseAnimation]s, [TrainHoppingAnimation]s, etc.
+  ///
+  /// It returns an [Animation] specialized to the same type, `U`, as the
+  /// argument to the method (`child`), whose value is derived by applying the
+  /// given [Tween] to the value of this [Animation].
+  ///
+  /// {@tool snippet}
+  ///
+  /// Given an [AnimationController] `_controller`, the following code creates
+  /// an `Animation<Alignment>` that swings from top left to top right as the
+  /// controller goes from 0.0 to 1.0:
+  ///
+  /// ```dart
+  /// Animation<Alignment> _alignment1 = _controller.drive(
+  ///   AlignmentTween(
+  ///     begin: Alignment.topLeft,
+  ///     end: Alignment.topRight,
+  ///   ),
+  /// );
+  /// ```
+  /// {@end-tool}
+  /// {@tool snippet}
+  ///
+  /// The `_alignment.value` could then be used in a widget's build method, for
+  /// instance, to position a child using an [Align] widget such that the
+  /// position of the child shifts over time from the top left to the top right.
+  ///
+  /// It is common to ease this kind of curve, e.g. making the transition slower
+  /// at the start and faster at the end. The following snippet shows one way to
+  /// chain the alignment tween in the previous example to an easing curve (in
+  /// this case, [Curves.easeIn]). In this example, the tween is created
+  /// elsewhere as a variable that can be reused, since none of its arguments
+  /// vary.
+  ///
+  /// ```dart
+  /// final Animatable<Alignment> _tween = AlignmentTween(begin: Alignment.topLeft, end: Alignment.topRight)
+  ///   .chain(CurveTween(curve: Curves.easeIn));
+  /// // ...
+  /// Animation<Alignment> _alignment2 = _controller.drive(_tween);
+  /// ```
+  /// {@end-tool}
+  /// {@tool snippet}
+  ///
+  /// The following code is exactly equivalent, and is typically clearer when
+  /// the tweens are created inline, as might be preferred when the tweens have
+  /// values that depend on other variables:
+  ///
+  /// ```dart
+  /// Animation<Alignment> _alignment3 = _controller
+  ///   .drive(CurveTween(curve: Curves.easeIn))
+  ///   .drive(AlignmentTween(
+  ///     begin: Alignment.topLeft,
+  ///     end: Alignment.topRight,
+  ///   ));
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [Animatable.animate], which does the same thing.
+  ///  * [AnimationController], which is usually used to drive animations.
+  ///  * [CurvedAnimation], an alternative to [CurveTween] for applying easing
+  ///    curves, which supports distinct curves in the forward direction and the
+  ///    reverse direction.
+  @optionalTypeArgs
+  Animation<U> drive<U>(Animatable<U> child) {
+    assert(this is Animation<double>);
+    return child.animate(this as Animation<double>);
+  }
+
+  @override
+  String toString() {
+    return '${describeIdentity(this)}(${toStringDetails()})';
+  }
+
+  /// Provides a string describing the status of this object, but not including
+  /// information about the object itself.
+  ///
+  /// This function is used by [Animation.toString] so that [Animation]
+  /// subclasses can provide additional details while ensuring all [Animation]
+  /// subclasses have a consistent [toString] style.
+  ///
+  /// The result of this function includes an icon describing the status of this
+  /// [Animation] object:
+  ///
+  /// * "&#x25B6;": [AnimationStatus.forward] ([value] increasing)
+  /// * "&#x25C0;": [AnimationStatus.reverse] ([value] decreasing)
+  /// * "&#x23ED;": [AnimationStatus.completed] ([value] == 1.0)
+  /// * "&#x23EE;": [AnimationStatus.dismissed] ([value] == 0.0)
+  String toStringDetails() {
+    assert(status != null);
+    switch (status) {
+      case AnimationStatus.forward:
+        return '\u25B6'; // >
+      case AnimationStatus.reverse:
+        return '\u25C0'; // <
+      case AnimationStatus.completed:
+        return '\u23ED'; // >>|
+      case AnimationStatus.dismissed:
+        return '\u23EE'; // |<<
+    }
+  }
+}
diff --git a/lib/src/animation/animation_controller.dart b/lib/src/animation/animation_controller.dart
new file mode 100644
index 0000000..2e38629
--- /dev/null
+++ b/lib/src/animation/animation_controller.dart
@@ -0,0 +1,910 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' as ui show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/physics.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/semantics.dart';
+
+import 'animation.dart';
+import 'curves.dart';
+import 'listener_helpers.dart';
+
+export 'package:flute/scheduler.dart' show TickerFuture, TickerCanceled;
+
+// Examples can assume:
+// late AnimationController _controller, fadeAnimationController, sizeAnimationController;
+// late bool dismissed;
+// void setState(VoidCallback fn) { }
+
+/// The direction in which an animation is running.
+enum _AnimationDirection {
+  /// The animation is running from beginning to end.
+  forward,
+
+  /// The animation is running backwards, from end to beginning.
+  reverse,
+}
+
+final SpringDescription _kFlingSpringDescription = SpringDescription.withDampingRatio(
+  mass: 1.0,
+  stiffness: 500.0,
+  ratio: 1.0,
+);
+
+const Tolerance _kFlingTolerance = Tolerance(
+  velocity: double.infinity,
+  distance: 0.01,
+);
+
+/// Configures how an [AnimationController] behaves when animations are
+/// disabled.
+///
+/// When [AccessibilityFeatures.disableAnimations] is true, the device is asking
+/// Flutter to reduce or disable animations as much as possible. To honor this,
+/// we reduce the duration and the corresponding number of frames for
+/// animations. This enum is used to allow certain [AnimationController]s to opt
+/// out of this behavior.
+///
+/// For example, the [AnimationController] which controls the physics simulation
+/// for a scrollable list will have [AnimationBehavior.preserve], so that when
+/// a user attempts to scroll it does not jump to the end/beginning too quickly.
+enum AnimationBehavior {
+  /// The [AnimationController] will reduce its duration when
+  /// [AccessibilityFeatures.disableAnimations] is true.
+  normal,
+
+  /// The [AnimationController] will preserve its behavior.
+  ///
+  /// This is the default for repeating animations in order to prevent them from
+  /// flashing rapidly on the screen if the widget does not take the
+  /// [AccessibilityFeatures.disableAnimations] flag into account.
+  preserve,
+}
+
+/// A controller for an animation.
+///
+/// This class lets you perform tasks such as:
+///
+/// * Play an animation [forward] or in [reverse], or [stop] an animation.
+/// * Set the animation to a specific [value].
+/// * Define the [upperBound] and [lowerBound] values of an animation.
+/// * Create a [fling] animation effect using a physics simulation.
+///
+/// By default, an [AnimationController] linearly produces values that range
+/// from 0.0 to 1.0, during a given duration. The animation controller generates
+/// a new value whenever the device running your app is ready to display a new
+/// frame (typically, this rate is around 60 values per second).
+///
+/// ## Ticker providers
+///
+/// An [AnimationController] needs a [TickerProvider], which is configured using
+/// the `vsync` argument on the constructor.
+///
+/// The [TickerProvider] interface describes a factory for [Ticker] objects. A
+/// [Ticker] is an object that knows how to register itself with the
+/// [SchedulerBinding] and fires a callback every frame. The
+/// [AnimationController] class uses a [Ticker] to step through the animation
+/// that it controls.
+///
+/// If an [AnimationController] is being created from a [State], then the State
+/// can use the [TickerProviderStateMixin] and [SingleTickerProviderStateMixin]
+/// classes to implement the [TickerProvider] interface. The
+/// [TickerProviderStateMixin] class always works for this purpose; the
+/// [SingleTickerProviderStateMixin] is slightly more efficient in the case of
+/// the class only ever needing one [Ticker] (e.g. if the class creates only a
+/// single [AnimationController] during its entire lifetime).
+///
+/// The widget test framework [WidgetTester] object can be used as a ticker
+/// provider in the context of tests. In other contexts, you will have to either
+/// pass a [TickerProvider] from a higher level (e.g. indirectly from a [State]
+/// that mixes in [TickerProviderStateMixin]), or create a custom
+/// [TickerProvider] subclass.
+///
+/// ## Life cycle
+///
+/// An [AnimationController] should be [dispose]d when it is no longer needed.
+/// This reduces the likelihood of leaks. When used with a [StatefulWidget], it
+/// is common for an [AnimationController] to be created in the
+/// [State.initState] method and then disposed in the [State.dispose] method.
+///
+/// ## Using [Future]s with [AnimationController]
+///
+/// The methods that start animations return a [TickerFuture] object which
+/// completes when the animation completes successfully, and never throws an
+/// error; if the animation is canceled, the future never completes. This object
+/// also has a [TickerFuture.orCancel] property which returns a future that
+/// completes when the animation completes successfully, and completes with an
+/// error when the animation is aborted.
+///
+/// This can be used to write code such as the `fadeOutAndUpdateState` method
+/// below.
+///
+/// {@tool snippet}
+///
+/// Here is a stateful `Foo` widget. Its [State] uses the
+/// [SingleTickerProviderStateMixin] to implement the necessary
+/// [TickerProvider], creating its controller in the [State.initState] method
+/// and disposing of it in the [State.dispose] method. The duration of the
+/// controller is configured from a property in the `Foo` widget; as that
+/// changes, the [State.didUpdateWidget] method is used to update the
+/// controller.
+///
+/// ```dart
+/// class Foo extends StatefulWidget {
+///   Foo({ Key? key, required this.duration }) : super(key: key);
+///
+///   final Duration duration;
+///
+///   @override
+///   _FooState createState() => _FooState();
+/// }
+///
+/// class _FooState extends State<Foo> with SingleTickerProviderStateMixin {
+///   late AnimationController _controller;
+///
+///   @override
+///   void initState() {
+///     super.initState();
+///     _controller = AnimationController(
+///       vsync: this, // the SingleTickerProviderStateMixin
+///       duration: widget.duration,
+///     );
+///   }
+///
+///   @override
+///   void didUpdateWidget(Foo oldWidget) {
+///     super.didUpdateWidget(oldWidget);
+///     _controller.duration = widget.duration;
+///   }
+///
+///   @override
+///   void dispose() {
+///     _controller.dispose();
+///     super.dispose();
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Container(); // ...
+///   }
+/// }
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// The following method (for a [State] subclass) drives two animation
+/// controllers using Dart's asynchronous syntax for awaiting [Future] objects:
+///
+/// ```dart
+/// Future<void> fadeOutAndUpdateState() async {
+///   try {
+///     await fadeAnimationController.forward().orCancel;
+///     await sizeAnimationController.forward().orCancel;
+///     setState(() {
+///       dismissed = true;
+///     });
+///   } on TickerCanceled {
+///     // the animation got canceled, probably because we were disposed
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// The assumption in the code above is that the animation controllers are being
+/// disposed in the [State] subclass' override of the [State.dispose] method.
+/// Since disposing the controller cancels the animation (raising a
+/// [TickerCanceled] exception), the code here can skip verifying whether
+/// [State.mounted] is still true at each step. (Again, this assumes that the
+/// controllers are created in [State.initState] and disposed in
+/// [State.dispose], as described in the previous section.)
+///
+/// See also:
+///
+///  * [Tween], the base class for converting an [AnimationController] to a
+///    range of values of other types.
+class AnimationController extends Animation<double>
+  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
+  /// Creates an animation controller.
+  ///
+  /// * `value` is the initial value of the animation. If defaults to the lower
+  ///   bound.
+  ///
+  /// * [duration] is the length of time this animation should last.
+  ///
+  /// * [debugLabel] is a string to help identify this animation during
+  ///   debugging (used by [toString]).
+  ///
+  /// * [lowerBound] is the smallest value this animation can obtain and the
+  ///   value at which this animation is deemed to be dismissed. It cannot be
+  ///   null.
+  ///
+  /// * [upperBound] is the largest value this animation can obtain and the
+  ///   value at which this animation is deemed to be completed. It cannot be
+  ///   null.
+  ///
+  /// * `vsync` is the [TickerProvider] for the current context. It can be
+  ///   changed by calling [resync]. It is required and must not be null. See
+  ///   [TickerProvider] for advice on obtaining a ticker provider.
+  AnimationController({
+    double? value,
+    this.duration,
+    this.reverseDuration,
+    this.debugLabel,
+    this.lowerBound = 0.0,
+    this.upperBound = 1.0,
+    this.animationBehavior = AnimationBehavior.normal,
+    required TickerProvider vsync,
+  }) : assert(lowerBound != null),
+       assert(upperBound != null),
+       assert(upperBound >= lowerBound),
+       assert(vsync != null),
+       _direction = _AnimationDirection.forward {
+    _ticker = vsync.createTicker(_tick);
+    _internalSetValue(value ?? lowerBound);
+  }
+
+  /// Creates an animation controller with no upper or lower bound for its
+  /// value.
+  ///
+  /// * [value] is the initial value of the animation.
+  ///
+  /// * [duration] is the length of time this animation should last.
+  ///
+  /// * [debugLabel] is a string to help identify this animation during
+  ///   debugging (used by [toString]).
+  ///
+  /// * `vsync` is the [TickerProvider] for the current context. It can be
+  ///   changed by calling [resync]. It is required and must not be null. See
+  ///   [TickerProvider] for advice on obtaining a ticker provider.
+  ///
+  /// This constructor is most useful for animations that will be driven using a
+  /// physics simulation, especially when the physics simulation has no
+  /// pre-determined bounds.
+  AnimationController.unbounded({
+    double value = 0.0,
+    this.duration,
+    this.reverseDuration,
+    this.debugLabel,
+    required TickerProvider vsync,
+    this.animationBehavior = AnimationBehavior.preserve,
+  }) : assert(value != null),
+       assert(vsync != null),
+       lowerBound = double.negativeInfinity,
+       upperBound = double.infinity,
+       _direction = _AnimationDirection.forward {
+    _ticker = vsync.createTicker(_tick);
+    _internalSetValue(value);
+  }
+
+  /// The value at which this animation is deemed to be dismissed.
+  final double lowerBound;
+
+  /// The value at which this animation is deemed to be completed.
+  final double upperBound;
+
+  /// A label that is used in the [toString] output. Intended to aid with
+  /// identifying animation controller instances in debug output.
+  final String? debugLabel;
+
+  /// The behavior of the controller when [AccessibilityFeatures.disableAnimations]
+  /// is true.
+  ///
+  /// Defaults to [AnimationBehavior.normal] for the [new AnimationController]
+  /// constructor, and [AnimationBehavior.preserve] for the
+  /// [new AnimationController.unbounded] constructor.
+  final AnimationBehavior animationBehavior;
+
+  /// Returns an [Animation<double>] for this animation controller, so that a
+  /// pointer to this object can be passed around without allowing users of that
+  /// pointer to mutate the [AnimationController] state.
+  Animation<double> get view => this;
+
+  /// The length of time this animation should last.
+  ///
+  /// If [reverseDuration] is specified, then [duration] is only used when going
+  /// [forward]. Otherwise, it specifies the duration going in both directions.
+  Duration? duration;
+
+  /// The length of time this animation should last when going in [reverse].
+  ///
+  /// The value of [duration] us used if [reverseDuration] is not specified or
+  /// set to null.
+  Duration? reverseDuration;
+
+  Ticker? _ticker;
+
+  /// Recreates the [Ticker] with the new [TickerProvider].
+  void resync(TickerProvider vsync) {
+    final Ticker oldTicker = _ticker!;
+    _ticker = vsync.createTicker(_tick);
+    _ticker!.absorbTicker(oldTicker);
+  }
+
+  Simulation? _simulation;
+
+  /// The current value of the animation.
+  ///
+  /// Setting this value notifies all the listeners that the value
+  /// changed.
+  ///
+  /// Setting this value also stops the controller if it is currently
+  /// running; if this happens, it also notifies all the status
+  /// listeners.
+  @override
+  double get value => _value;
+  late double _value;
+  /// Stops the animation controller and sets the current value of the
+  /// animation.
+  ///
+  /// The new value is clamped to the range set by [lowerBound] and
+  /// [upperBound].
+  ///
+  /// Value listeners are notified even if this does not change the value.
+  /// Status listeners are notified if the animation was previously playing.
+  ///
+  /// The most recently returned [TickerFuture], if any, is marked as having been
+  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
+  /// derivative future completes with a [TickerCanceled] error.
+  ///
+  /// See also:
+  ///
+  ///  * [reset], which is equivalent to setting [value] to [lowerBound].
+  ///  * [stop], which aborts the animation without changing its value or status
+  ///    and without dispatching any notifications other than completing or
+  ///    canceling the [TickerFuture].
+  ///  * [forward], [reverse], [animateTo], [animateWith], [fling], and [repeat],
+  ///    which start the animation controller.
+  set value(double newValue) {
+    assert(newValue != null);
+    stop();
+    _internalSetValue(newValue);
+    notifyListeners();
+    _checkStatusChanged();
+  }
+
+  /// Sets the controller's value to [lowerBound], stopping the animation (if
+  /// in progress), and resetting to its beginning point, or dismissed state.
+  ///
+  /// The most recently returned [TickerFuture], if any, is marked as having been
+  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
+  /// derivative future completes with a [TickerCanceled] error.
+  ///
+  /// See also:
+  ///
+  ///  * [value], which can be explicitly set to a specific value as desired.
+  ///  * [forward], which starts the animation in the forward direction.
+  ///  * [stop], which aborts the animation without changing its value or status
+  ///    and without dispatching any notifications other than completing or
+  ///    canceling the [TickerFuture].
+  void reset() {
+    value = lowerBound;
+  }
+
+  /// The rate of change of [value] per second.
+  ///
+  /// If [isAnimating] is false, then [value] is not changing and the rate of
+  /// change is zero.
+  double get velocity {
+    if (!isAnimating)
+      return 0.0;
+    return _simulation!.dx(lastElapsedDuration!.inMicroseconds.toDouble() / Duration.microsecondsPerSecond);
+  }
+
+  void _internalSetValue(double newValue) {
+    _value = newValue.clamp(lowerBound, upperBound);
+    if (_value == lowerBound) {
+      _status = AnimationStatus.dismissed;
+    } else if (_value == upperBound) {
+      _status = AnimationStatus.completed;
+    } else {
+      _status = (_direction == _AnimationDirection.forward) ?
+        AnimationStatus.forward :
+        AnimationStatus.reverse;
+    }
+  }
+
+  /// The amount of time that has passed between the time the animation started
+  /// and the most recent tick of the animation.
+  ///
+  /// If the controller is not animating, the last elapsed duration is null.
+  Duration? get lastElapsedDuration => _lastElapsedDuration;
+  Duration? _lastElapsedDuration;
+
+  /// Whether this animation is currently animating in either the forward or reverse direction.
+  ///
+  /// This is separate from whether it is actively ticking. An animation
+  /// controller's ticker might get muted, in which case the animation
+  /// controller's callbacks will no longer fire even though time is continuing
+  /// to pass. See [Ticker.muted] and [TickerMode].
+  bool get isAnimating => _ticker != null && _ticker!.isActive;
+
+  _AnimationDirection _direction;
+
+  @override
+  AnimationStatus get status => _status;
+  late AnimationStatus _status;
+
+  /// Starts running this animation forwards (towards the end).
+  ///
+  /// Returns a [TickerFuture] that completes when the animation is complete.
+  ///
+  /// The most recently returned [TickerFuture], if any, is marked as having been
+  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
+  /// derivative future completes with a [TickerCanceled] error.
+  ///
+  /// During the animation, [status] is reported as [AnimationStatus.forward],
+  /// which switches to [AnimationStatus.completed] when [upperBound] is
+  /// reached at the end of the animation.
+  TickerFuture forward({ double? from }) {
+    assert(() {
+      if (duration == null) {
+        throw FlutterError(
+          'AnimationController.forward() called with no default duration.\n'
+          'The "duration" property should be set, either in the constructor or later, before '
+          'calling the forward() function.'
+        );
+      }
+      return true;
+    }());
+    assert(
+      _ticker != null,
+      'AnimationController.forward() called after AnimationController.dispose()\n'
+      'AnimationController methods should not be used after calling dispose.'
+    );
+    _direction = _AnimationDirection.forward;
+    if (from != null)
+      value = from;
+    return _animateToInternal(upperBound);
+  }
+
+  /// Starts running this animation in reverse (towards the beginning).
+  ///
+  /// Returns a [TickerFuture] that completes when the animation is dismissed.
+  ///
+  /// The most recently returned [TickerFuture], if any, is marked as having been
+  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
+  /// derivative future completes with a [TickerCanceled] error.
+  ///
+  /// During the animation, [status] is reported as [AnimationStatus.reverse],
+  /// which switches to [AnimationStatus.dismissed] when [lowerBound] is
+  /// reached at the end of the animation.
+  TickerFuture reverse({ double? from }) {
+    assert(() {
+      if (duration == null && reverseDuration == null) {
+        throw FlutterError(
+          'AnimationController.reverse() called with no default duration or reverseDuration.\n'
+          'The "duration" or "reverseDuration" property should be set, either in the constructor or later, before '
+          'calling the reverse() function.'
+        );
+      }
+      return true;
+    }());
+    assert(
+      _ticker != null,
+      'AnimationController.reverse() called after AnimationController.dispose()\n'
+      'AnimationController methods should not be used after calling dispose.'
+    );
+    _direction = _AnimationDirection.reverse;
+    if (from != null)
+      value = from;
+    return _animateToInternal(lowerBound);
+  }
+
+  /// Drives the animation from its current value to target.
+  ///
+  /// Returns a [TickerFuture] that completes when the animation is complete.
+  ///
+  /// The most recently returned [TickerFuture], if any, is marked as having been
+  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
+  /// derivative future completes with a [TickerCanceled] error.
+  ///
+  /// During the animation, [status] is reported as [AnimationStatus.forward]
+  /// regardless of whether `target` > [value] or not. At the end of the
+  /// animation, when `target` is reached, [status] is reported as
+  /// [AnimationStatus.completed].
+  TickerFuture animateTo(double target, { Duration? duration, Curve curve = Curves.linear }) {
+    assert(() {
+      if (this.duration == null && duration == null) {
+        throw FlutterError(
+          'AnimationController.animateTo() called with no explicit duration and no default duration.\n'
+          'Either the "duration" argument to the animateTo() method should be provided, or the '
+          '"duration" property should be set, either in the constructor or later, before '
+          'calling the animateTo() function.'
+        );
+      }
+      return true;
+    }());
+    assert(
+      _ticker != null,
+      'AnimationController.animateTo() called after AnimationController.dispose()\n'
+      'AnimationController methods should not be used after calling dispose.'
+    );
+    _direction = _AnimationDirection.forward;
+    return _animateToInternal(target, duration: duration, curve: curve);
+  }
+
+  /// Drives the animation from its current value to target.
+  ///
+  /// Returns a [TickerFuture] that completes when the animation is complete.
+  ///
+  /// The most recently returned [TickerFuture], if any, is marked as having been
+  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
+  /// derivative future completes with a [TickerCanceled] error.
+  ///
+  /// During the animation, [status] is reported as [AnimationStatus.reverse]
+  /// regardless of whether `target` < [value] or not. At the end of the
+  /// animation, when `target` is reached, [status] is reported as
+  /// [AnimationStatus.dismissed].
+  TickerFuture animateBack(double target, { Duration? duration, Curve curve = Curves.linear }) {
+    assert(() {
+      if (this.duration == null && reverseDuration == null && duration == null) {
+        throw FlutterError(
+          'AnimationController.animateBack() called with no explicit duration and no default duration or reverseDuration.\n'
+          'Either the "duration" argument to the animateBack() method should be provided, or the '
+          '"duration" or "reverseDuration" property should be set, either in the constructor or later, before '
+          'calling the animateBack() function.'
+        );
+      }
+      return true;
+    }());
+    assert(
+      _ticker != null,
+      'AnimationController.animateBack() called after AnimationController.dispose()\n'
+      'AnimationController methods should not be used after calling dispose.'
+    );
+    _direction = _AnimationDirection.reverse;
+    return _animateToInternal(target, duration: duration, curve: curve);
+  }
+
+  TickerFuture _animateToInternal(double target, { Duration? duration, Curve curve = Curves.linear }) {
+    double scale = 1.0;
+    if (SemanticsBinding.instance!.disableAnimations) {
+      switch (animationBehavior) {
+        case AnimationBehavior.normal:
+          // Since the framework cannot handle zero duration animations, we run it at 5% of the normal
+          // duration to limit most animations to a single frame.
+          // TODO(jonahwilliams): determine a better process for setting duration.
+          scale = 0.05;
+          break;
+        case AnimationBehavior.preserve:
+          break;
+      }
+    }
+    Duration? simulationDuration = duration;
+    if (simulationDuration == null) {
+      assert(!(this.duration == null && _direction == _AnimationDirection.forward));
+      assert(!(this.duration == null && _direction == _AnimationDirection.reverse && reverseDuration == null));
+      final double range = upperBound - lowerBound;
+      final double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
+      final Duration directionDuration =
+        (_direction == _AnimationDirection.reverse && reverseDuration != null)
+        ? reverseDuration!
+        : this.duration!;
+      simulationDuration = directionDuration * remainingFraction;
+    } else if (target == value) {
+      // Already at target, don't animate.
+      simulationDuration = Duration.zero;
+    }
+    stop();
+    if (simulationDuration == Duration.zero) {
+      if (value != target) {
+        _value = target.clamp(lowerBound, upperBound);
+        notifyListeners();
+      }
+      _status = (_direction == _AnimationDirection.forward) ?
+        AnimationStatus.completed :
+        AnimationStatus.dismissed;
+      _checkStatusChanged();
+      return TickerFuture.complete();
+    }
+    assert(simulationDuration > Duration.zero);
+    assert(!isAnimating);
+    return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
+  }
+
+  /// Starts running this animation in the forward direction, and
+  /// restarts the animation when it completes.
+  ///
+  /// Defaults to repeating between the [lowerBound] and [upperBound] of the
+  /// [AnimationController] when no explicit value is set for [min] and [max].
+  ///
+  /// With [reverse] set to true, instead of always starting over at [min]
+  /// the starting value will alternate between [min] and [max] values on each
+  /// repeat. The [status] will be reported as [AnimationStatus.reverse] when
+  /// the animation runs from [max] to [min].
+  ///
+  /// Each run of the animation will have a duration of `period`. If `period` is not
+  /// provided, [duration] will be used instead, which has to be set before [repeat] is
+  /// called either in the constructor or later by using the [duration] setter.
+  ///
+  /// Returns a [TickerFuture] that never completes. The [TickerFuture.orCancel] future
+  /// completes with an error when the animation is stopped (e.g. with [stop]).
+  ///
+  /// The most recently returned [TickerFuture], if any, is marked as having been
+  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
+  /// derivative future completes with a [TickerCanceled] error.
+  TickerFuture repeat({ double? min, double? max, bool reverse = false, Duration? period }) {
+    min ??= lowerBound;
+    max ??= upperBound;
+    period ??= duration;
+    assert(() {
+      if (period == null) {
+        throw FlutterError(
+          'AnimationController.repeat() called without an explicit period and with no default Duration.\n'
+          'Either the "period" argument to the repeat() method should be provided, or the '
+          '"duration" property should be set, either in the constructor or later, before '
+          'calling the repeat() function.'
+        );
+      }
+      return true;
+    }());
+    assert(max >= min);
+    assert(max <= upperBound && min >= lowerBound);
+    assert(reverse != null);
+    stop();
+    return _startSimulation(_RepeatingSimulation(_value, min, max, reverse, period!, _directionSetter));
+  }
+
+  void _directionSetter(_AnimationDirection direction) {
+    _direction = direction;
+    _status = (_direction == _AnimationDirection.forward) ?
+      AnimationStatus.forward :
+      AnimationStatus.reverse;
+    _checkStatusChanged();
+  }
+
+  /// Drives the animation with a spring (within [lowerBound] and [upperBound])
+  /// and initial velocity.
+  ///
+  /// If velocity is positive, the animation will complete, otherwise it will
+  /// dismiss.
+  ///
+  /// The [springDescription] parameter can be used to specify a custom [SpringType.criticallyDamped]
+  /// or [SpringType.overDamped] spring to drive the animation with. Defaults to null, which uses a
+  /// [SpringType.criticallyDamped] spring. See [SpringDescription.withDampingRatio] for how
+  /// to create a suitable [SpringDescription].
+  ///
+  /// The resulting spring simulation cannot be of type [SpringType.underDamped],
+  /// as this can lead to unexpected look of the produced animation.
+  ///
+  /// Returns a [TickerFuture] that completes when the animation is complete.
+  ///
+  /// The most recently returned [TickerFuture], if any, is marked as having been
+  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
+  /// derivative future completes with a [TickerCanceled] error.
+  TickerFuture fling({ double velocity = 1.0, SpringDescription? springDescription, AnimationBehavior? animationBehavior }) {
+    springDescription ??= _kFlingSpringDescription;
+    _direction = velocity < 0.0 ? _AnimationDirection.reverse : _AnimationDirection.forward;
+    final double target = velocity < 0.0 ? lowerBound - _kFlingTolerance.distance
+                                         : upperBound + _kFlingTolerance.distance;
+    double scale = 1.0;
+    final AnimationBehavior behavior = animationBehavior ?? this.animationBehavior;
+    if (SemanticsBinding.instance!.disableAnimations) {
+      switch (behavior) {
+        case AnimationBehavior.normal:
+          // TODO(jonahwilliams): determine a better process for setting velocity.
+          // the value below was arbitrarily chosen because it worked for the drawer widget.
+          scale = 200.0;
+          break;
+        case AnimationBehavior.preserve:
+          break;
+      }
+    }
+    final SpringSimulation simulation = SpringSimulation(springDescription, value, target, velocity * scale)
+      ..tolerance = _kFlingTolerance;
+    assert(
+      simulation.type != SpringType.underDamped,
+      'The resulting spring simulation is of type SpringType.underDamped.\n'
+      'This can lead to unexpected look of the animation, please adjust the springDescription parameter'
+    );
+    stop();
+    return _startSimulation(simulation);
+  }
+
+  /// Drives the animation according to the given simulation.
+  ///
+  /// The values from the simulation are clamped to the [lowerBound] and
+  /// [upperBound]. To avoid this, consider creating the [AnimationController]
+  /// using the [new AnimationController.unbounded] constructor.
+  ///
+  /// Returns a [TickerFuture] that completes when the animation is complete.
+  ///
+  /// The most recently returned [TickerFuture], if any, is marked as having been
+  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
+  /// derivative future completes with a [TickerCanceled] error.
+  ///
+  /// The [status] is always [AnimationStatus.forward] for the entire duration
+  /// of the simulation.
+  TickerFuture animateWith(Simulation simulation) {
+    assert(
+      _ticker != null,
+      'AnimationController.animateWith() called after AnimationController.dispose()\n'
+      'AnimationController methods should not be used after calling dispose.'
+    );
+    stop();
+    _direction = _AnimationDirection.forward;
+    return _startSimulation(simulation);
+  }
+
+  TickerFuture _startSimulation(Simulation simulation) {
+    assert(simulation != null);
+    assert(!isAnimating);
+    _simulation = simulation;
+    _lastElapsedDuration = Duration.zero;
+    _value = simulation.x(0.0).clamp(lowerBound, upperBound);
+    final TickerFuture result = _ticker!.start();
+    _status = (_direction == _AnimationDirection.forward) ?
+      AnimationStatus.forward :
+      AnimationStatus.reverse;
+    _checkStatusChanged();
+    return result;
+  }
+
+  /// Stops running this animation.
+  ///
+  /// This does not trigger any notifications. The animation stops in its
+  /// current state.
+  ///
+  /// By default, the most recently returned [TickerFuture] is marked as having
+  /// been canceled, meaning the future never completes and its
+  /// [TickerFuture.orCancel] derivative future completes with a [TickerCanceled]
+  /// error. By passing the `canceled` argument with the value false, this is
+  /// reversed, and the futures complete successfully.
+  ///
+  /// See also:
+  ///
+  ///  * [reset], which stops the animation and resets it to the [lowerBound],
+  ///    and which does send notifications.
+  ///  * [forward], [reverse], [animateTo], [animateWith], [fling], and [repeat],
+  ///    which restart the animation controller.
+  void stop({ bool canceled = true }) {
+    assert(
+      _ticker != null,
+      'AnimationController.stop() called after AnimationController.dispose()\n'
+      'AnimationController methods should not be used after calling dispose.'
+    );
+    _simulation = null;
+    _lastElapsedDuration = null;
+    _ticker!.stop(canceled: canceled);
+  }
+
+  /// Release the resources used by this object. The object is no longer usable
+  /// after this method is called.
+  ///
+  /// The most recently returned [TickerFuture], if any, is marked as having been
+  /// canceled, meaning the future never completes and its [TickerFuture.orCancel]
+  /// derivative future completes with a [TickerCanceled] error.
+  @override
+  void dispose() {
+    assert(() {
+      if (_ticker == null) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('AnimationController.dispose() called more than once.'),
+          ErrorDescription('A given $runtimeType cannot be disposed more than once.\n'),
+          DiagnosticsProperty<AnimationController>(
+            'The following $runtimeType object was disposed multiple times',
+            this,
+            style: DiagnosticsTreeStyle.errorProperty,
+          ),
+        ]);
+      }
+      return true;
+    }());
+    _ticker!.dispose();
+    _ticker = null;
+    super.dispose();
+  }
+
+  AnimationStatus _lastReportedStatus = AnimationStatus.dismissed;
+  void _checkStatusChanged() {
+    final AnimationStatus newStatus = status;
+    if (_lastReportedStatus != newStatus) {
+      _lastReportedStatus = newStatus;
+      notifyStatusListeners(newStatus);
+    }
+  }
+
+  void _tick(Duration elapsed) {
+    _lastElapsedDuration = elapsed;
+    final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
+    assert(elapsedInSeconds >= 0.0);
+    _value = _simulation!.x(elapsedInSeconds).clamp(lowerBound, upperBound);
+    if (_simulation!.isDone(elapsedInSeconds)) {
+      _status = (_direction == _AnimationDirection.forward) ?
+        AnimationStatus.completed :
+        AnimationStatus.dismissed;
+      stop(canceled: false);
+    }
+    notifyListeners();
+    _checkStatusChanged();
+  }
+
+  @override
+  String toStringDetails() {
+    final String paused = isAnimating ? '' : '; paused';
+    final String ticker = _ticker == null ? '; DISPOSED' : (_ticker!.muted ? '; silenced' : '');
+    final String label = debugLabel == null ? '' : '; for $debugLabel';
+    final String more = '${super.toStringDetails()} ${value.toStringAsFixed(3)}';
+    return '$more$paused$ticker$label';
+  }
+}
+
+class _InterpolationSimulation extends Simulation {
+  _InterpolationSimulation(this._begin, this._end, Duration duration, this._curve, double scale)
+    : assert(_begin != null),
+      assert(_end != null),
+      assert(duration != null && duration.inMicroseconds > 0),
+      _durationInSeconds = (duration.inMicroseconds * scale) / Duration.microsecondsPerSecond;
+
+  final double _durationInSeconds;
+  final double _begin;
+  final double _end;
+  final Curve _curve;
+
+  @override
+  double x(double timeInSeconds) {
+    final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0);
+    if (t == 0.0)
+      return _begin;
+    else if (t == 1.0)
+      return _end;
+    else
+      return _begin + (_end - _begin) * _curve.transform(t);
+  }
+
+  @override
+  double dx(double timeInSeconds) {
+    final double epsilon = tolerance.time;
+    return (x(timeInSeconds + epsilon) - x(timeInSeconds - epsilon)) / (2 * epsilon);
+  }
+
+  @override
+  bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds;
+}
+
+typedef _DirectionSetter = void Function(_AnimationDirection direction);
+
+class _RepeatingSimulation extends Simulation {
+  _RepeatingSimulation(double initialValue, this.min, this.max, this.reverse, Duration period, this.directionSetter)
+      : _periodInSeconds = period.inMicroseconds / Duration.microsecondsPerSecond,
+        _initialT = (max == min) ? 0.0 : (initialValue / (max - min)) * (period.inMicroseconds / Duration.microsecondsPerSecond) {
+    assert(_periodInSeconds > 0.0);
+    assert(_initialT >= 0.0);
+  }
+
+  final double min;
+  final double max;
+  final bool reverse;
+  final _DirectionSetter directionSetter;
+
+  final double _periodInSeconds;
+  final double _initialT;
+
+  @override
+  double x(double timeInSeconds) {
+    assert(timeInSeconds >= 0.0);
+
+    final double totalTimeInSeconds = timeInSeconds + _initialT;
+    final double t = (totalTimeInSeconds / _periodInSeconds) % 1.0;
+    final bool _isPlayingReverse = (totalTimeInSeconds ~/ _periodInSeconds).isOdd;
+
+    if (reverse && _isPlayingReverse) {
+      directionSetter(_AnimationDirection.reverse);
+      return ui.lerpDouble(max, min, t)!;
+    } else {
+      directionSetter(_AnimationDirection.forward);
+      return ui.lerpDouble(min, max, t)!;
+    }
+  }
+
+  @override
+  double dx(double timeInSeconds) => (max - min) / _periodInSeconds;
+
+  @override
+  bool isDone(double timeInSeconds) => false;
+}
diff --git a/lib/src/animation/animations.dart b/lib/src/animation/animations.dart
new file mode 100644
index 0000000..2c5539f
--- /dev/null
+++ b/lib/src/animation/animations.dart
@@ -0,0 +1,725 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+
+import 'animation.dart';
+import 'curves.dart';
+import 'listener_helpers.dart';
+
+// Examples can assume:
+// late AnimationController controller;
+
+class _AlwaysCompleteAnimation extends Animation<double> {
+  const _AlwaysCompleteAnimation();
+
+  @override
+  void addListener(VoidCallback listener) { }
+
+  @override
+  void removeListener(VoidCallback listener) { }
+
+  @override
+  void addStatusListener(AnimationStatusListener listener) { }
+
+  @override
+  void removeStatusListener(AnimationStatusListener listener) { }
+
+  @override
+  AnimationStatus get status => AnimationStatus.completed;
+
+  @override
+  double get value => 1.0;
+
+  @override
+  String toString() => 'kAlwaysCompleteAnimation';
+}
+
+/// An animation that is always complete.
+///
+/// Using this constant involves less overhead than building an
+/// [AnimationController] with an initial value of 1.0. This is useful when an
+/// API expects an animation but you don't actually want to animate anything.
+const Animation<double> kAlwaysCompleteAnimation = _AlwaysCompleteAnimation();
+
+class _AlwaysDismissedAnimation extends Animation<double> {
+  const _AlwaysDismissedAnimation();
+
+  @override
+  void addListener(VoidCallback listener) { }
+
+  @override
+  void removeListener(VoidCallback listener) { }
+
+  @override
+  void addStatusListener(AnimationStatusListener listener) { }
+
+  @override
+  void removeStatusListener(AnimationStatusListener listener) { }
+
+  @override
+  AnimationStatus get status => AnimationStatus.dismissed;
+
+  @override
+  double get value => 0.0;
+
+  @override
+  String toString() => 'kAlwaysDismissedAnimation';
+}
+
+/// An animation that is always dismissed.
+///
+/// Using this constant involves less overhead than building an
+/// [AnimationController] with an initial value of 0.0. This is useful when an
+/// API expects an animation but you don't actually want to animate anything.
+const Animation<double> kAlwaysDismissedAnimation = _AlwaysDismissedAnimation();
+
+/// An animation that is always stopped at a given value.
+///
+/// The [status] is always [AnimationStatus.forward].
+class AlwaysStoppedAnimation<T> extends Animation<T> {
+  /// Creates an [AlwaysStoppedAnimation] with the given value.
+  ///
+  /// Since the [value] and [status] of an [AlwaysStoppedAnimation] can never
+  /// change, the listeners can never be called. It is therefore safe to reuse
+  /// an [AlwaysStoppedAnimation] instance in multiple places. If the [value] to
+  /// be used is known at compile time, the constructor should be called as a
+  /// `const` constructor.
+  const AlwaysStoppedAnimation(this.value);
+
+  @override
+  final T value;
+
+  @override
+  void addListener(VoidCallback listener) { }
+
+  @override
+  void removeListener(VoidCallback listener) { }
+
+  @override
+  void addStatusListener(AnimationStatusListener listener) { }
+
+  @override
+  void removeStatusListener(AnimationStatusListener listener) { }
+
+  @override
+  AnimationStatus get status => AnimationStatus.forward;
+
+  @override
+  String toStringDetails() {
+    return '${super.toStringDetails()} $value; paused';
+  }
+}
+
+/// Implements most of the [Animation] interface by deferring its behavior to a
+/// given [parent] Animation.
+///
+/// To implement an [Animation] that is driven by a parent, it is only necessary
+/// to mix in this class, implement [parent], and implement `T get value`.
+///
+/// To define a mapping from values in the range 0..1, consider subclassing
+/// [Tween] instead.
+mixin AnimationWithParentMixin<T> {
+  /// The animation whose value this animation will proxy.
+  ///
+  /// This animation must remain the same for the lifetime of this object. If
+  /// you wish to proxy a different animation at different times, consider using
+  /// [ProxyAnimation].
+  Animation<T> get parent;
+
+  // keep these next five dartdocs in sync with the dartdocs in Animation<T>
+
+  /// Calls the listener every time the value of the animation changes.
+  ///
+  /// Listeners can be removed with [removeListener].
+  void addListener(VoidCallback listener) => parent.addListener(listener);
+
+  /// Stop calling the listener every time the value of the animation changes.
+  ///
+  /// Listeners can be added with [addListener].
+  void removeListener(VoidCallback listener) => parent.removeListener(listener);
+
+  /// Calls listener every time the status of the animation changes.
+  ///
+  /// Listeners can be removed with [removeStatusListener].
+  void addStatusListener(AnimationStatusListener listener) => parent.addStatusListener(listener);
+
+  /// Stops calling the listener every time the status of the animation changes.
+  ///
+  /// Listeners can be added with [addStatusListener].
+  void removeStatusListener(AnimationStatusListener listener) => parent.removeStatusListener(listener);
+
+  /// The current status of this animation.
+  AnimationStatus get status => parent.status;
+}
+
+/// An animation that is a proxy for another animation.
+///
+/// A proxy animation is useful because the parent animation can be mutated. For
+/// example, one object can create a proxy animation, hand the proxy to another
+/// object, and then later change the animation from which the proxy receives
+/// its value.
+class ProxyAnimation extends Animation<double>
+  with AnimationLazyListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
+
+  /// Creates a proxy animation.
+  ///
+  /// If the animation argument is omitted, the proxy animation will have the
+  /// status [AnimationStatus.dismissed] and a value of 0.0.
+  ProxyAnimation([Animation<double>? animation]) {
+    _parent = animation;
+    if (_parent == null) {
+      _status = AnimationStatus.dismissed;
+      _value = 0.0;
+    }
+  }
+
+  AnimationStatus? _status;
+  double? _value;
+
+  /// The animation whose value this animation will proxy.
+  ///
+  /// This value is mutable. When mutated, the listeners on the proxy animation
+  /// will be transparently updated to be listening to the new parent animation.
+  Animation<double>? get parent => _parent;
+  Animation<double>? _parent;
+  set parent(Animation<double>? value) {
+    if (value == _parent)
+      return;
+    if (_parent != null) {
+      _status = _parent!.status;
+      _value = _parent!.value;
+      if (isListening)
+        didStopListening();
+    }
+    _parent = value;
+    if (_parent != null) {
+      if (isListening)
+        didStartListening();
+      if (_value != _parent!.value)
+        notifyListeners();
+      if (_status != _parent!.status)
+        notifyStatusListeners(_parent!.status);
+      _status = null;
+      _value = null;
+    }
+  }
+
+  @override
+  void didStartListening() {
+    if (_parent != null) {
+      _parent!.addListener(notifyListeners);
+      _parent!.addStatusListener(notifyStatusListeners);
+    }
+  }
+
+  @override
+  void didStopListening() {
+    if (_parent != null) {
+      _parent!.removeListener(notifyListeners);
+      _parent!.removeStatusListener(notifyStatusListeners);
+    }
+  }
+
+  @override
+  AnimationStatus get status => _parent != null ? _parent!.status : _status!;
+
+  @override
+  double get value => _parent != null ? _parent!.value : _value!;
+
+  @override
+  String toString() {
+    if (parent == null)
+      return '${objectRuntimeType(this, 'ProxyAnimation')}(null; ${super.toStringDetails()} ${value.toStringAsFixed(3)})';
+    return '$parent\u27A9${objectRuntimeType(this, 'ProxyAnimation')}';
+  }
+}
+
+/// An animation that is the reverse of another animation.
+///
+/// If the parent animation is running forward from 0.0 to 1.0, this animation
+/// is running in reverse from 1.0 to 0.0.
+///
+/// Using a [ReverseAnimation] is different from simply using a [Tween] with a
+/// begin of 1.0 and an end of 0.0 because the tween does not change the status
+/// or direction of the animation.
+///
+/// See also:
+///
+///  * [Curve.flipped] and [FlippedCurve], which provide a similar effect but on
+///    [Curve]s.
+///  * [CurvedAnimation], which can take separate curves for when the animation
+///    is going forward than for when it is going in reverse.
+class ReverseAnimation extends Animation<double>
+  with AnimationLazyListenerMixin, AnimationLocalStatusListenersMixin {
+
+  /// Creates a reverse animation.
+  ///
+  /// The parent argument must not be null.
+  ReverseAnimation(this.parent)
+    : assert(parent != null);
+
+  /// The animation whose value and direction this animation is reversing.
+  final Animation<double> parent;
+
+  @override
+  void addListener(VoidCallback listener) {
+    didRegisterListener();
+    parent.addListener(listener);
+  }
+
+  @override
+  void removeListener(VoidCallback listener) {
+    parent.removeListener(listener);
+    didUnregisterListener();
+  }
+
+  @override
+  void didStartListening() {
+    parent.addStatusListener(_statusChangeHandler);
+  }
+
+  @override
+  void didStopListening() {
+    parent.removeStatusListener(_statusChangeHandler);
+  }
+
+  void _statusChangeHandler(AnimationStatus status) {
+    notifyStatusListeners(_reverseStatus(status));
+  }
+
+  @override
+  AnimationStatus get status => _reverseStatus(parent.status);
+
+  @override
+  double get value => 1.0 - parent.value;
+
+  AnimationStatus _reverseStatus(AnimationStatus status) {
+    assert(status != null);
+    switch (status) {
+      case AnimationStatus.forward: return AnimationStatus.reverse;
+      case AnimationStatus.reverse: return AnimationStatus.forward;
+      case AnimationStatus.completed: return AnimationStatus.dismissed;
+      case AnimationStatus.dismissed: return AnimationStatus.completed;
+    }
+  }
+
+  @override
+  String toString() {
+    return '$parent\u27AA${objectRuntimeType(this, 'ReverseAnimation')}';
+  }
+}
+
+/// An animation that applies a curve to another animation.
+///
+/// [CurvedAnimation] is useful when you want to apply a non-linear [Curve] to
+/// an animation object, especially if you want different curves when the
+/// animation is going forward vs when it is going backward.
+///
+/// Depending on the given curve, the output of the [CurvedAnimation] could have
+/// a wider range than its input. For example, elastic curves such as
+/// [Curves.elasticIn] will significantly overshoot or undershoot the default
+/// range of 0.0 to 1.0.
+///
+/// If you want to apply a [Curve] to a [Tween], consider using [CurveTween].
+///
+/// {@tool snippet}
+///
+/// The following code snippet shows how you can apply a curve to a linear
+/// animation produced by an [AnimationController] `controller`.
+///
+/// ```dart
+/// final Animation<double> animation = CurvedAnimation(
+///   parent: controller,
+///   curve: Curves.ease,
+/// );
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// This second code snippet shows how to apply a different curve in the forward
+/// direction than in the reverse direction. This can't be done using a
+/// [CurveTween] (since [Tween]s are not aware of the animation direction when
+/// they are applied).
+///
+/// ```dart
+/// final Animation<double> animation = CurvedAnimation(
+///   parent: controller,
+///   curve: Curves.easeIn,
+///   reverseCurve: Curves.easeOut,
+/// );
+/// ```
+/// {@end-tool}
+///
+/// By default, the [reverseCurve] matches the forward [curve].
+///
+/// See also:
+///
+///  * [CurveTween], for an alternative way of expressing the first sample
+///    above.
+///  * [AnimationController], for examples of creating and disposing of an
+///    [AnimationController].
+///  * [Curve.flipped] and [FlippedCurve], which provide the reverse of a
+///    [Curve].
+class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {
+  /// Creates a curved animation.
+  ///
+  /// The parent and curve arguments must not be null.
+  CurvedAnimation({
+    required this.parent,
+    required this.curve,
+    this.reverseCurve,
+  }) : assert(parent != null),
+       assert(curve != null) {
+    _updateCurveDirection(parent.status);
+    parent.addStatusListener(_updateCurveDirection);
+  }
+
+  /// The animation to which this animation applies a curve.
+  @override
+  final Animation<double> parent;
+
+  /// The curve to use in the forward direction.
+  Curve curve;
+
+  /// The curve to use in the reverse direction.
+  ///
+  /// If the parent animation changes direction without first reaching the
+  /// [AnimationStatus.completed] or [AnimationStatus.dismissed] status, the
+  /// [CurvedAnimation] stays on the same curve (albeit in the opposite
+  /// direction) to avoid visual discontinuities.
+  ///
+  /// If you use a non-null [reverseCurve], you might want to hold this object
+  /// in a [State] object rather than recreating it each time your widget builds
+  /// in order to take advantage of the state in this object that avoids visual
+  /// discontinuities.
+  ///
+  /// If this field is null, uses [curve] in both directions.
+  Curve? reverseCurve;
+
+  /// The direction used to select the current curve.
+  ///
+  /// The curve direction is only reset when we hit the beginning or the end of
+  /// the timeline to avoid discontinuities in the value of any variables this
+  /// animation is used to animate.
+  AnimationStatus? _curveDirection;
+
+  void _updateCurveDirection(AnimationStatus status) {
+    switch (status) {
+      case AnimationStatus.dismissed:
+      case AnimationStatus.completed:
+        _curveDirection = null;
+        break;
+      case AnimationStatus.forward:
+        _curveDirection ??= AnimationStatus.forward;
+        break;
+      case AnimationStatus.reverse:
+        _curveDirection ??= AnimationStatus.reverse;
+        break;
+    }
+  }
+
+  bool get _useForwardCurve {
+    return reverseCurve == null || (_curveDirection ?? parent.status) != AnimationStatus.reverse;
+  }
+
+  @override
+  double get value {
+    final Curve? activeCurve = _useForwardCurve ? curve : reverseCurve;
+
+    final double t = parent.value;
+    if (activeCurve == null)
+      return t;
+    if (t == 0.0 || t == 1.0) {
+      assert(() {
+        final double transformedValue = activeCurve.transform(t);
+        final double roundedTransformedValue = transformedValue.round().toDouble();
+        if (roundedTransformedValue != t) {
+          throw FlutterError(
+            'Invalid curve endpoint at $t.\n'
+            'Curves must map 0.0 to near zero and 1.0 to near one but '
+            '${activeCurve.runtimeType} mapped $t to $transformedValue, which '
+            'is near $roundedTransformedValue.'
+          );
+        }
+        return true;
+      }());
+      return t;
+    }
+    return activeCurve.transform(t);
+  }
+
+  @override
+  String toString() {
+    if (reverseCurve == null)
+      return '$parent\u27A9$curve';
+    if (_useForwardCurve)
+      return '$parent\u27A9$curve\u2092\u2099/$reverseCurve';
+    return '$parent\u27A9$curve/$reverseCurve\u2092\u2099';
+  }
+}
+
+enum _TrainHoppingMode { minimize, maximize }
+
+/// This animation starts by proxying one animation, but when the value of that
+/// animation crosses the value of the second (either because the second is
+/// going in the opposite direction, or because the one overtakes the other),
+/// the animation hops over to proxying the second animation.
+///
+/// When the [TrainHoppingAnimation] starts proxying the second animation
+/// instead of the first, the [onSwitchedTrain] callback is called.
+///
+/// If the two animations start at the same value, then the
+/// [TrainHoppingAnimation] immediately hops to the second animation, and the
+/// [onSwitchedTrain] callback is not called. If only one animation is provided
+/// (i.e. if the second is null), then the [TrainHoppingAnimation] just proxies
+/// the first animation.
+///
+/// Since this object must track the two animations even when it has no
+/// listeners of its own, instead of shutting down when all its listeners are
+/// removed, it exposes a [dispose()] method. Call this method to shut this
+/// object down.
+class TrainHoppingAnimation extends Animation<double>
+  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
+
+  /// Creates a train-hopping animation.
+  ///
+  /// The current train argument must not be null but the next train argument
+  /// can be null. If the next train is null, then this object will just proxy
+  /// the first animation and never hop.
+  TrainHoppingAnimation(
+    Animation<double> this._currentTrain,
+    this._nextTrain, {
+    this.onSwitchedTrain,
+  }) : assert(_currentTrain != null) {
+    if (_nextTrain != null) {
+      if (_currentTrain!.value == _nextTrain!.value) {
+        _currentTrain = _nextTrain;
+        _nextTrain = null;
+      } else if (_currentTrain!.value > _nextTrain!.value) {
+        _mode = _TrainHoppingMode.maximize;
+      } else {
+        assert(_currentTrain!.value < _nextTrain!.value);
+        _mode = _TrainHoppingMode.minimize;
+      }
+    }
+    _currentTrain!.addStatusListener(_statusChangeHandler);
+    _currentTrain!.addListener(_valueChangeHandler);
+    _nextTrain?.addListener(_valueChangeHandler);
+    assert(_mode != null || _nextTrain == null);
+  }
+
+  /// The animation that is currently driving this animation.
+  ///
+  /// The identity of this object will change from the first animation to the
+  /// second animation when [onSwitchedTrain] is called.
+  Animation<double>? get currentTrain => _currentTrain;
+  Animation<double>? _currentTrain;
+  Animation<double>? _nextTrain;
+  _TrainHoppingMode? _mode;
+
+  /// Called when this animation switches to be driven by the second animation.
+  ///
+  /// This is not called if the two animations provided to the constructor have
+  /// the same value at the time of the call to the constructor. In that case,
+  /// the second animation is used from the start, and the first is ignored.
+  VoidCallback? onSwitchedTrain;
+
+  AnimationStatus? _lastStatus;
+  void _statusChangeHandler(AnimationStatus status) {
+    assert(_currentTrain != null);
+    if (status != _lastStatus) {
+      notifyListeners();
+      _lastStatus = status;
+    }
+    assert(_lastStatus != null);
+  }
+
+  @override
+  AnimationStatus get status => _currentTrain!.status;
+
+  double? _lastValue;
+  void _valueChangeHandler() {
+    assert(_currentTrain != null);
+    bool hop = false;
+    if (_nextTrain != null) {
+      assert(_mode != null);
+      switch (_mode!) {
+        case _TrainHoppingMode.minimize:
+          hop = _nextTrain!.value <= _currentTrain!.value;
+          break;
+        case _TrainHoppingMode.maximize:
+          hop = _nextTrain!.value >= _currentTrain!.value;
+          break;
+      }
+      if (hop) {
+        _currentTrain!
+          ..removeStatusListener(_statusChangeHandler)
+          ..removeListener(_valueChangeHandler);
+        _currentTrain = _nextTrain;
+        _nextTrain = null;
+        _currentTrain!.addStatusListener(_statusChangeHandler);
+        _statusChangeHandler(_currentTrain!.status);
+      }
+    }
+    final double newValue = value;
+    if (newValue != _lastValue) {
+      notifyListeners();
+      _lastValue = newValue;
+    }
+    assert(_lastValue != null);
+    if (hop && onSwitchedTrain != null)
+      onSwitchedTrain!();
+  }
+
+  @override
+  double get value => _currentTrain!.value;
+
+  /// Frees all the resources used by this performance.
+  /// After this is called, this object is no longer usable.
+  @override
+  void dispose() {
+    assert(_currentTrain != null);
+    _currentTrain!.removeStatusListener(_statusChangeHandler);
+    _currentTrain!.removeListener(_valueChangeHandler);
+    _currentTrain = null;
+    _nextTrain?.removeListener(_valueChangeHandler);
+    _nextTrain = null;
+    super.dispose();
+  }
+
+  @override
+  String toString() {
+    if (_nextTrain != null)
+      return '$currentTrain\u27A9${objectRuntimeType(this, 'TrainHoppingAnimation')}(next: $_nextTrain)';
+    return '$currentTrain\u27A9${objectRuntimeType(this, 'TrainHoppingAnimation')}(no next)';
+  }
+}
+
+/// An interface for combining multiple Animations. Subclasses need only
+/// implement the `value` getter to control how the child animations are
+/// combined. Can be chained to combine more than 2 animations.
+///
+/// For example, to create an animation that is the sum of two others, subclass
+/// this class and define `T get value = first.value + second.value;`
+///
+/// By default, the [status] of a [CompoundAnimation] is the status of the
+/// [next] animation if [next] is moving, and the status of the [first]
+/// animation otherwise.
+abstract class CompoundAnimation<T> extends Animation<T>
+  with AnimationLazyListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
+  /// Creates a CompoundAnimation. Both arguments must be non-null. Either can
+  /// be a CompoundAnimation itself to combine multiple animations.
+  CompoundAnimation({
+    required this.first,
+    required this.next,
+  }) : assert(first != null),
+       assert(next != null);
+
+  /// The first sub-animation. Its status takes precedence if neither are
+  /// animating.
+  final Animation<T> first;
+
+  /// The second sub-animation.
+  final Animation<T> next;
+
+  @override
+  void didStartListening() {
+    first.addListener(_maybeNotifyListeners);
+    first.addStatusListener(_maybeNotifyStatusListeners);
+    next.addListener(_maybeNotifyListeners);
+    next.addStatusListener(_maybeNotifyStatusListeners);
+  }
+
+  @override
+  void didStopListening() {
+    first.removeListener(_maybeNotifyListeners);
+    first.removeStatusListener(_maybeNotifyStatusListeners);
+    next.removeListener(_maybeNotifyListeners);
+    next.removeStatusListener(_maybeNotifyStatusListeners);
+  }
+
+  /// Gets the status of this animation based on the [first] and [next] status.
+  ///
+  /// The default is that if the [next] animation is moving, use its status.
+  /// Otherwise, default to [first].
+  @override
+  AnimationStatus get status {
+    if (next.status == AnimationStatus.forward || next.status == AnimationStatus.reverse)
+      return next.status;
+    return first.status;
+  }
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'CompoundAnimation')}($first, $next)';
+  }
+
+  AnimationStatus? _lastStatus;
+  void _maybeNotifyStatusListeners(AnimationStatus _) {
+    if (status != _lastStatus) {
+      _lastStatus = status;
+      notifyStatusListeners(status);
+    }
+  }
+
+  T? _lastValue;
+  void _maybeNotifyListeners() {
+    if (value != _lastValue) {
+      _lastValue = value;
+      notifyListeners();
+    }
+  }
+}
+
+/// An animation of [double]s that tracks the mean of two other animations.
+///
+/// The [status] of this animation is the status of the `right` animation if it is
+/// moving, and the `left` animation otherwise.
+///
+/// The [value] of this animation is the [double] that represents the mean value
+/// of the values of the `left` and `right` animations.
+class AnimationMean extends CompoundAnimation<double> {
+  /// Creates an animation that tracks the mean of two other animations.
+  AnimationMean({
+    required Animation<double> left,
+    required Animation<double> right,
+  }) : super(first: left, next: right);
+
+  @override
+  double get value => (first.value + next.value) / 2.0;
+}
+
+/// An animation that tracks the maximum of two other animations.
+///
+/// The [value] of this animation is the maximum of the values of
+/// [first] and [next].
+class AnimationMax<T extends num> extends CompoundAnimation<T> {
+  /// Creates an [AnimationMax].
+  ///
+  /// Both arguments must be non-null. Either can be an [AnimationMax] itself
+  /// to combine multiple animations.
+  AnimationMax(Animation<T> first, Animation<T> next) : super(first: first, next: next);
+
+  @override
+  T get value => math.max(first.value, next.value);
+}
+
+/// An animation that tracks the minimum of two other animations.
+///
+/// The [value] of this animation is the maximum of the values of
+/// [first] and [next].
+class AnimationMin<T extends num> extends CompoundAnimation<T> {
+  /// Creates an [AnimationMin].
+  ///
+  /// Both arguments must be non-null. Either can be an [AnimationMin] itself
+  /// to combine multiple animations.
+  AnimationMin(Animation<T> first, Animation<T> next) : super(first: first, next: next);
+
+  @override
+  T get value => math.min(first.value, next.value);
+}
diff --git a/lib/src/animation/curves.dart b/lib/src/animation/curves.dart
new file mode 100644
index 0000000..8e3faac
--- /dev/null
+++ b/lib/src/animation/curves.dart
@@ -0,0 +1,1702 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart';
+
+import 'package:flute/foundation.dart';
+
+/// An abstract class providing an interface for evaluating a parametric curve.
+///
+/// A parametric curve transforms a parameter (hence the name) `t` along a curve
+/// to the value of the curve at that value of `t`. The curve can be of
+/// arbitrary dimension, but is typically a 1D, 2D, or 3D curve.
+///
+/// See also:
+///
+///  * [Curve], a 1D animation easing curve that starts at 0.0 and ends at 1.0.
+///  * [Curve2D], a parametric curve that transforms the parameter to a 2D point.
+abstract class ParametricCurve<T> {
+  /// Abstract const constructor to enable subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const ParametricCurve();
+
+  /// Returns the value of the curve at point `t`.
+  ///
+  /// This method asserts that t is between 0 and 1 before delegating to
+  /// [transformInternal].
+  ///
+  /// It is recommended that subclasses override [transformInternal] instead of
+  /// this function, as the above case is already handled in the default
+  /// implementation of [transform], which delegates the remaining logic to
+  /// [transformInternal].
+  T transform(double t) {
+    assert(t != null);
+    assert(t >= 0.0 && t <= 1.0, 'parametric value $t is outside of [0, 1] range.');
+    return transformInternal(t);
+  }
+
+  /// Returns the value of the curve at point `t`.
+  ///
+  /// The given parametric value `t` will be between 0.0 and 1.0, inclusive.
+  @protected
+  T transformInternal(double t) {
+    throw UnimplementedError();
+  }
+
+  @override
+  String toString() => objectRuntimeType(this, 'ParametricCurve');
+}
+
+/// An parametric animation easing curve, i.e. a mapping of the unit interval to
+/// the unit interval.
+///
+/// Easing curves are used to adjust the rate of change of an animation over
+/// time, allowing them to speed up and slow down, rather than moving at a
+/// constant rate.
+///
+/// A [Curve] must map t=0.0 to 0.0 and t=1.0 to 1.0.
+///
+/// See also:
+///
+///  * [Curves], a collection of common animation easing curves.
+///  * [CurveTween], which can be used to apply a [Curve] to an [Animation].
+///  * [Canvas.drawArc], which draws an arc, and has nothing to do with easing
+///    curves.
+///  * [Animatable], for a more flexible interface that maps fractions to
+///    arbitrary values.
+@immutable
+abstract class Curve extends ParametricCurve<double> {
+  /// Abstract const constructor to enable subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const Curve();
+
+  /// Returns the value of the curve at point `t`.
+  ///
+  /// This function must ensure the following:
+  /// - The value of `t` must be between 0.0 and 1.0
+  /// - Values of `t`=0.0 and `t`=1.0 must be mapped to 0.0 and 1.0,
+  /// respectively.
+  ///
+  /// It is recommended that subclasses override [transformInternal] instead of
+  /// this function, as the above cases are already handled in the default
+  /// implementation of [transform], which delegates the remaining logic to
+  /// [transformInternal].
+  @override
+  double transform(double t) {
+    if (t == 0.0 || t == 1.0) {
+      return t;
+    }
+    return super.transform(t);
+  }
+
+  /// Returns a new curve that is the reversed inversion of this one.
+  ///
+  /// This is often useful with [CurvedAnimation.reverseCurve].
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped.mp4}
+  ///
+  /// See also:
+  ///
+  ///  * [FlippedCurve], the class that is used to implement this getter.
+  ///  * [ReverseAnimation], which reverses an [Animation] rather than a [Curve].
+  ///  * [CurvedAnimation], which can take a separate curve and reverse curve.
+  Curve get flipped => FlippedCurve(this);
+}
+
+/// The identity map over the unit interval.
+///
+/// See [Curves.linear] for an instance of this class.
+class _Linear extends Curve {
+  const _Linear._();
+
+  @override
+  double transformInternal(double t) => t;
+}
+
+/// A sawtooth curve that repeats a given number of times over the unit interval.
+///
+/// The curve rises linearly from 0.0 to 1.0 and then falls discontinuously back
+/// to 0.0 each iteration.
+///
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_sawtooth.mp4}
+class SawTooth extends Curve {
+  /// Creates a sawtooth curve.
+  ///
+  /// The [count] argument must not be null.
+  const SawTooth(this.count) : assert(count != null);
+
+  /// The number of repetitions of the sawtooth pattern in the unit interval.
+  final int count;
+
+  @override
+  double transformInternal(double t) {
+    t *= count;
+    return t - t.truncateToDouble();
+  }
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'SawTooth')}($count)';
+  }
+}
+
+/// A curve that is 0.0 until [begin], then curved (according to [curve]) from
+/// 0.0 at [begin] to 1.0 at [end], then remains 1.0 past [end].
+///
+/// An [Interval] can be used to delay an animation. For example, a six second
+/// animation that uses an [Interval] with its [begin] set to 0.5 and its [end]
+/// set to 1.0 will essentially become a three-second animation that starts
+/// three seconds later.
+///
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_interval.mp4}
+class Interval extends Curve {
+  /// Creates an interval curve.
+  ///
+  /// The arguments must not be null.
+  const Interval(this.begin, this.end, { this.curve = Curves.linear })
+    : assert(begin != null),
+      assert(end != null),
+      assert(curve != null);
+
+  /// The largest value for which this interval is 0.0.
+  ///
+  /// From t=0.0 to t=`begin`, the interval's value is 0.0.
+  final double begin;
+
+  /// The smallest value for which this interval is 1.0.
+  ///
+  /// From t=`end` to t=1.0, the interval's value is 1.0.
+  final double end;
+
+  /// The curve to apply between [begin] and [end].
+  final Curve curve;
+
+  @override
+  double transformInternal(double t) {
+    assert(begin >= 0.0);
+    assert(begin <= 1.0);
+    assert(end >= 0.0);
+    assert(end <= 1.0);
+    assert(end >= begin);
+    t = ((t - begin) / (end - begin)).clamp(0.0, 1.0);
+    if (t == 0.0 || t == 1.0)
+      return t;
+    return curve.transform(t);
+  }
+
+  @override
+  String toString() {
+    if (curve is! _Linear)
+      return '${objectRuntimeType(this, 'Interval')}($begin\u22EF$end)\u27A9$curve';
+    return '${objectRuntimeType(this, 'Interval')}($begin\u22EF$end)';
+  }
+}
+
+/// A curve that is 0.0 until it hits the threshold, then it jumps to 1.0.
+///
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_threshold.mp4}
+class Threshold extends Curve {
+  /// Creates a threshold curve.
+  ///
+  /// The [threshold] argument must not be null.
+  const Threshold(this.threshold) : assert(threshold != null);
+
+  /// The value before which the curve is 0.0 and after which the curve is 1.0.
+  ///
+  /// When t is exactly [threshold], the curve has the value 1.0.
+  final double threshold;
+
+  @override
+  double transformInternal(double t) {
+    assert(threshold >= 0.0);
+    assert(threshold <= 1.0);
+    return t < threshold ? 0.0 : 1.0;
+  }
+}
+
+/// A cubic polynomial mapping of the unit interval.
+///
+/// The [Curves] class contains some commonly used cubic curves:
+///
+///  * [Curves.ease]
+///  * [Curves.easeIn]
+///  * [Curves.easeOut]
+///  * [Curves.easeInOut]
+///
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4}
+///
+/// The [Cubic] class implements third-order Bézier curves.
+///
+/// See also:
+///
+///  * [Curves], where many more predefined curves are available.
+///  * [CatmullRomCurve], a curve which passes through specific values.
+class Cubic extends Curve {
+  /// Creates a cubic curve.
+  ///
+  /// Rather than creating a new instance, consider using one of the common
+  /// cubic curves in [Curves].
+  ///
+  /// The [a], [b], [c], and [d] arguments must not be null.
+  const Cubic(this.a, this.b, this.c, this.d)
+    : assert(a != null),
+      assert(b != null),
+      assert(c != null),
+      assert(d != null);
+
+  /// The x coordinate of the first control point.
+  ///
+  /// The line through the point (0, 0) and the first control point is tangent
+  /// to the curve at the point (0, 0).
+  final double a;
+
+  /// The y coordinate of the first control point.
+  ///
+  /// The line through the point (0, 0) and the first control point is tangent
+  /// to the curve at the point (0, 0).
+  final double b;
+
+  /// The x coordinate of the second control point.
+  ///
+  /// The line through the point (1, 1) and the second control point is tangent
+  /// to the curve at the point (1, 1).
+  final double c;
+
+  /// The y coordinate of the second control point.
+  ///
+  /// The line through the point (1, 1) and the second control point is tangent
+  /// to the curve at the point (1, 1).
+  final double d;
+
+  static const double _cubicErrorBound = 0.001;
+
+  double _evaluateCubic(double a, double b, double m) {
+    return 3 * a * (1 - m) * (1 - m) * m +
+           3 * b * (1 - m) *           m * m +
+                                       m * m * m;
+  }
+
+  @override
+  double transformInternal(double t) {
+    double start = 0.0;
+    double end = 1.0;
+    while (true) {
+      final double midpoint = (start + end) / 2;
+      final double estimate = _evaluateCubic(a, c, midpoint);
+      if ((t - estimate).abs() < _cubicErrorBound)
+        return _evaluateCubic(b, d, midpoint);
+      if (estimate < t)
+        start = midpoint;
+      else
+        end = midpoint;
+    }
+  }
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'Cubic')}(${a.toStringAsFixed(2)}, ${b.toStringAsFixed(2)}, ${c.toStringAsFixed(2)}, ${d.toStringAsFixed(2)})';
+  }
+}
+
+/// Abstract class that defines an API for evaluating 2D parametric curves.
+///
+/// [Curve2D] differs from [Curve] in that the values interpolated are [Offset]
+/// values instead of [double] values, hence the "2D" in the name. They both
+/// take a single double `t` that has a range of 0.0 to 1.0, inclusive, as input
+/// to the [transform] function . Unlike [Curve], [Curve2D] is not required to
+/// map `t=0.0` and `t=1.0` to specific output values.
+///
+/// The interpolated `t` value given to [transform] represents the progression
+/// along the curve, but it doesn't necessarily progress at a constant velocity, so
+/// incrementing `t` by, say, 0.1 might move along the curve by quite a lot at one
+/// part of the curve, or hardly at all in another part of the curve, depending
+/// on the definition of the curve.
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+/// This example shows how to use a [Curve2D] to modify the position of a widget
+/// so that it can follow an arbitrary path.
+///
+/// ```dart preamble
+/// // This is the path that the child will follow. It's a CatmullRomSpline so
+/// // that the coordinates can be specified that it must pass through. If the
+/// // tension is set to 1.0, it will linearly interpolate between those points,
+/// // instead of interpolating smoothly.
+/// final CatmullRomSpline path = CatmullRomSpline(
+///   const <Offset>[
+///     Offset(0.05, 0.75),
+///     Offset(0.18, 0.23),
+///     Offset(0.32, 0.04),
+///     Offset(0.73, 0.5),
+///     Offset(0.42, 0.74),
+///     Offset(0.73, 0.01),
+///     Offset(0.93, 0.93),
+///     Offset(0.05, 0.75),
+///   ],
+///   startHandle: Offset(0.93, 0.93),
+///   endHandle: Offset(0.18, 0.23),
+///   tension: 0.0,
+/// );
+///
+/// class FollowCurve2D extends StatefulWidget {
+///   const FollowCurve2D({
+///     Key key,
+///     @required this.path,
+///     this.curve = Curves.easeInOut,
+///     @required this.child,
+///     this.duration = const Duration(seconds: 1),
+///   })  : assert(path != null),
+///         assert(curve != null),
+///         assert(child != null),
+///         assert(duration != null),
+///         super(key: key);
+///
+///   final Curve2D path;
+///   final Curve curve;
+///   final Duration duration;
+///   final Widget child;
+///
+///   @override
+///   _FollowCurve2DState createState() => _FollowCurve2DState();
+/// }
+///
+/// class _FollowCurve2DState extends State<FollowCurve2D> with TickerProviderStateMixin {
+///   // The animation controller for this animation.
+///   AnimationController controller;
+///   // The animation that will be used to apply the widget's animation curve.
+///   Animation<double> animation;
+///
+///   @override
+///   void initState() {
+///     super.initState();
+///     controller = AnimationController(duration: widget.duration, vsync: this);
+///     animation = CurvedAnimation(parent: controller, curve: widget.curve);
+///     // Have the controller repeat indefinitely.  If you want it to "bounce" back
+///     // and forth, set the reverse parameter to true.
+///     controller.repeat(reverse: false);
+///     controller.addListener(() => setState(() {}));
+///   }
+///
+///   @override
+///   void dispose() {
+///     super.dispose();
+///     // Always have to dispose of animation controllers when done.
+///     controller.dispose();
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     // Scale the path values to match the -1.0 to 1.0 domain of the Alignment widget.
+///     final Offset position = widget.path.transform(animation.value) * 2.0 - Offset(1.0, 1.0);
+///     return Align(
+///       alignment: Alignment(position.dx, position.dy),
+///       child: widget.child,
+///     );
+///   }
+/// }
+/// ```
+///
+/// ```dart
+///   Widget build(BuildContext context) {
+///     return Container(
+///       color: Colors.white,
+///       alignment: Alignment.center,
+///       child: FollowCurve2D(
+///         path: path,
+///         curve: Curves.easeInOut,
+///         duration: const Duration(seconds: 3),
+///         child: CircleAvatar(
+///           backgroundColor: Colors.yellow,
+///           child: DefaultTextStyle(
+///             style: Theme.of(context).textTheme.headline6,
+///             child: Text("B"), // Buzz, buzz!
+///           ),
+///         ),
+///       ),
+///     );
+///   }
+/// ```
+/// {@end-tool}
+///
+abstract class Curve2D extends ParametricCurve<Offset> {
+  /// Abstract const constructor to enable subclasses to provide const
+  /// constructors so that they can be used in const expressions.
+  const Curve2D();
+
+  /// Generates a list of samples with a recursive subdivision until a tolerance
+  /// of `tolerance` is reached.
+  ///
+  /// Samples are generated in order.
+  ///
+  /// Samples can be used to render a curve efficiently, since the samples
+  /// constitute line segments which vary in size with the curvature of the
+  /// curve. They can also be used to quickly approximate the value of the curve
+  /// by searching for the desired range in X and linearly interpolating between
+  /// samples to obtain an approximation of Y at the desired X value. The
+  /// implementation of [CatmullRomCurve] uses samples for this purpose
+  /// internally.
+  ///
+  /// The tolerance is computed as the area of a triangle formed by a new point
+  /// and the preceding and following point.
+  ///
+  /// See also:
+  ///
+  ///  * Luiz Henrique de Figueire's Graphics Gem on [the algorithm](http://ariel.chronotext.org/dd/defigueiredo93adaptive.pdf).
+  Iterable<Curve2DSample> generateSamples({
+    double start = 0.0,
+    double end = 1.0,
+    double tolerance = 1e-10,
+  }) {
+    // The sampling  algorithm is:
+    // 1. Evaluate the area of the triangle (a proxy for the "flatness" of the
+    //    curve) formed by two points and a test point.
+    // 2. If the area of the triangle is small enough (below tolerance), then
+    //    the two points form the final segment.
+    // 3. If the area is still too large, divide the interval into two parts
+    //    using a random subdivision point to avoid aliasing.
+    // 4. Recursively sample the two parts.
+    //
+    // This algorithm concentrates samples in areas of high curvature.
+    assert(tolerance != null);
+    assert(start != null);
+    assert(end != null);
+    assert(end > start);
+    // We want to pick a random seed that will keep the result stable if
+    // evaluated again, so we use the first non-generated control point.
+    final math.Random rand = math.Random(samplingSeed);
+    bool isFlat(Offset p, Offset q, Offset r) {
+      // Calculates the area of the triangle given by the three points.
+      final Offset pr = p - r;
+      final Offset qr = q - r;
+      final double z = pr.dx * qr.dy - qr.dx * pr.dy;
+      return (z * z) < tolerance;
+    }
+
+    final Curve2DSample first = Curve2DSample(start, transform(start));
+    final Curve2DSample last = Curve2DSample(end, transform(end));
+    final List<Curve2DSample> samples = <Curve2DSample>[first];
+    void sample(Curve2DSample p, Curve2DSample q, {bool forceSubdivide = false}) {
+      // Pick a random point somewhat near the center, which avoids aliasing
+      // problems with periodic curves.
+      final double t = p.t + (0.45 + 0.1 * rand.nextDouble()) * (q.t - p.t);
+      final Curve2DSample r = Curve2DSample(t, transform(t));
+
+      if (!forceSubdivide && isFlat(p.value, q.value, r.value)) {
+        samples.add(q);
+      } else {
+        sample(p, r);
+        sample(r, q);
+      }
+    }
+    // If the curve starts and ends on the same point, then we force it to
+    // subdivide at least once, because otherwise it will terminate immediately.
+    sample(
+      first,
+      last,
+      forceSubdivide: (first.value.dx - last.value.dx).abs() < tolerance && (first.value.dy - last.value.dy).abs() < tolerance,
+    );
+    return samples;
+  }
+
+  /// Returns a seed value used by [generateSamples] to seed a random number
+  /// generator to avoid sample aliasing.
+  ///
+  /// Subclasses should override this and provide a custom seed.
+  ///
+  /// The value returned should be the same each time it is called, unless the
+  /// curve definition changes.
+  @protected
+  int get samplingSeed => 0;
+
+  /// Returns the parameter `t` that corresponds to the given x value of the spline.
+  ///
+  /// This will only work properly for curves which are single-valued in x
+  /// (where every value of `x` maps to only one value in 'y', i.e. the curve
+  /// does not loop or curve back over itself). For curves that are not
+  /// single-valued, it will return the parameter for only one of the values at
+  /// the given `x` location.
+  double findInverse(double x) {
+    assert(x != null);
+    double start = 0.0;
+    double end = 1.0;
+    late double mid;
+    double offsetToOrigin(double pos) => x - transform(pos).dx;
+    // Use a binary search to find the inverse point within 1e-6, or 100
+    // subdivisions, whichever comes first.
+    const double errorLimit = 1e-6;
+    int count = 100;
+    final double startValue = offsetToOrigin(start);
+    while ((end - start) / 2.0 > errorLimit && count > 0) {
+      mid = (end + start) / 2.0;
+      final double value = offsetToOrigin(mid);
+      if (value.sign == startValue.sign) {
+        start = mid;
+      } else {
+        end = mid;
+      }
+      count--;
+    }
+    return mid;
+  }
+}
+
+/// A class that holds a sample of a 2D parametric curve, containing the [value]
+/// (the X, Y coordinates) of the curve at the parametric value [t].
+///
+/// See also:
+///
+///  * [Curve2D.generateSamples], which generates samples of this type.
+///  * [Curve2D], a parametric curve that maps a double parameter to a 2D location.
+class Curve2DSample {
+  /// A const constructor for the sample so that subclasses can be const.
+  ///
+  /// All arguments must not be null.
+  const Curve2DSample(this.t, this.value) : assert(t != null), assert(value != null);
+
+  /// The parametric location of this sample point along the curve.
+  final double t;
+
+  /// The value (the X, Y coordinates) of the curve at parametric value [t].
+  final Offset value;
+
+  @override
+  String toString() {
+    return '[(${value.dx.toStringAsFixed(2)}, ${value.dy.toStringAsFixed(2)}), ${t.toStringAsFixed(2)}]';
+  }
+}
+
+/// A 2D spline that passes smoothly through the given control points using a
+/// centripetal Catmull-Rom spline.
+///
+/// When the curve is evaluated with [transform], the output values will move
+/// smoothly from one control point to the next, passing through the control
+/// points.
+///
+/// {@template flutter.animation.CatmullRomSpline}
+/// Unlike most cubic splines, Catmull-Rom splines have the advantage that their
+/// curves pass through the control points given to them. They are cubic
+/// polynomial representations, and, in fact, Catmull-Rom splines can be
+/// converted mathematically into cubic splines. This class implements a
+/// "centripetal" Catmull-Rom spline. The term centripetal implies that it won't
+/// form loops or self-intersections within a single segment.
+/// {@endtemplate}
+///
+/// See also:
+///  * [Centripetal Catmull–Rom splines](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline)
+///    on Wikipedia.
+///  * [Parameterization and Applications of Catmull-Rom Curves](http://faculty.cs.tamu.edu/schaefer/research/cr_cad.pdf),
+///    a paper on using Catmull-Rom splines.
+///  * [CatmullRomCurve], an animation curve that uses a [CatmullRomSpline] as its
+///    internal representation.
+class CatmullRomSpline extends Curve2D {
+  /// Constructs a centripetal Catmull-Rom spline curve.
+  ///
+  /// The `controlPoints` argument is a list of four or more points that
+  /// describe the points that the curve must pass through.
+  ///
+  /// The optional `tension` argument controls how tightly the curve approaches
+  /// the given `controlPoints`. It must be in the range 0.0 to 1.0, inclusive. It
+  /// defaults to 0.0, which provides the smoothest curve. A value of 1.0
+  /// produces a linear interpolation between points.
+  ///
+  /// The optional `endHandle` and `startHandle` points are the beginning and
+  /// ending handle positions. If not specified, they are created automatically
+  /// by extending the line formed by the first and/or last line segment in the
+  /// `controlPoints`, respectively. The spline will not go through these handle
+  /// points, but they will affect the slope of the line at the beginning and
+  /// end of the spline. The spline will attempt to match the slope of the line
+  /// formed by the start or end handle and the neighboring first or last
+  /// control point. The default is chosen so that the slope of the line at the
+  /// ends matches that of the first or last line segment in the control points.
+  ///
+  /// The `tension` and `controlPoints` arguments must not be null, and the
+  /// `controlPoints` list must contain at least four control points to
+  /// interpolate.
+  ///
+  /// The internal curve data structures are lazily computed the first time
+  /// [transform] is called.  If you would rather pre-compute the structures,
+  /// use [CatmullRomSpline.precompute] instead.
+  CatmullRomSpline(
+      List<Offset> controlPoints, {
+        double tension = 0.0,
+        Offset? startHandle,
+        Offset? endHandle,
+      }) : assert(controlPoints != null),
+           assert(tension != null),
+           assert(tension <= 1.0, 'tension $tension must not be greater than 1.0.'),
+           assert(tension >= 0.0, 'tension $tension must not be negative.'),
+           assert(controlPoints.length > 3, 'There must be at least four control points to create a CatmullRomSpline.'),
+           _controlPoints = controlPoints,
+           _startHandle = startHandle,
+           _endHandle = endHandle,
+           _tension = tension,
+           _cubicSegments = <List<Offset>>[];
+
+  /// Constructs a centripetal Catmull-Rom spline curve.
+  ///
+  /// The same as [new CatmullRomSpline], except that the internal data
+  /// structures are precomputed instead of being computed lazily.
+  CatmullRomSpline.precompute(
+      List<Offset> controlPoints, {
+        double tension = 0.0,
+        Offset? startHandle,
+        Offset? endHandle,
+      }) : assert(controlPoints != null),
+           assert(tension != null),
+           assert(tension <= 1.0, 'tension $tension must not be greater than 1.0.'),
+           assert(tension >= 0.0, 'tension $tension must not be negative.'),
+           assert(controlPoints.length > 3, 'There must be at least four control points to create a CatmullRomSpline.'),
+           _controlPoints = null,
+           _startHandle = null,
+           _endHandle = null,
+           _tension = null,
+           _cubicSegments = _computeSegments(controlPoints, tension, startHandle: startHandle, endHandle: endHandle);
+
+
+  static List<List<Offset>> _computeSegments(
+      List<Offset> controlPoints,
+      double tension, {
+      Offset? startHandle,
+      Offset? endHandle,
+    }) {
+    // If not specified, select the first and last control points (which are
+    // handles: they are not intersected by the resulting curve) so that they
+    // extend the first and last segments, respectively.
+    startHandle ??= controlPoints[0] * 2.0 - controlPoints[1];
+    endHandle ??= controlPoints.last * 2.0 - controlPoints[controlPoints.length - 2];
+    final List<Offset> allPoints = <Offset>[
+      startHandle,
+      ...controlPoints,
+      endHandle,
+    ];
+
+    // An alpha of 0.5 is what makes it a centripetal Catmull-Rom spline. A
+    // value of 0.0 would make it a uniform Catmull-Rom spline, and a value of
+    // 1.0 would make it a chordal Catmull-Rom spline. Non-centripetal values
+    // for alpha can give self-intersecting behavior or looping within a
+    // segment.
+    const double alpha = 0.5;
+    final double reverseTension = 1.0 - tension;
+    final List<List<Offset>> result = <List<Offset>>[];
+    for (int i = 0; i < allPoints.length - 3; ++i) {
+      final List<Offset> curve = <Offset>[allPoints[i], allPoints[i + 1], allPoints[i + 2], allPoints[i + 3]];
+      final Offset diffCurve10 = curve[1] - curve[0];
+      final Offset diffCurve21 = curve[2] - curve[1];
+      final Offset diffCurve32 = curve[3] - curve[2];
+      final double t01 = math.pow(diffCurve10.distance, alpha).toDouble();
+      final double t12 = math.pow(diffCurve21.distance, alpha).toDouble();
+      final double t23 = math.pow(diffCurve32.distance, alpha).toDouble();
+
+      final Offset m1 = (diffCurve21 + (diffCurve10 / t01 - (curve[2] - curve[0]) / (t01 + t12)) * t12) * reverseTension;
+      final Offset m2 = (diffCurve21 + (diffCurve32 / t23 - (curve[3] - curve[1]) / (t12 + t23)) * t12) * reverseTension;
+      final Offset sumM12 = m1 + m2;
+
+      final List<Offset> segment = <Offset>[
+        diffCurve21 * -2.0 + sumM12,
+        diffCurve21 * 3.0 - m1 - sumM12,
+        m1,
+        curve[1],
+      ];
+      result.add(segment);
+    }
+    return result;
+  }
+
+  // The list of control point lists for each cubic segment of the spline.
+  final List<List<Offset>> _cubicSegments;
+
+  // This is non-empty only if the _cubicSegments are being computed lazily.
+  final List<Offset>? _controlPoints;
+  final Offset? _startHandle;
+  final Offset? _endHandle;
+  final double? _tension;
+
+  void _initializeIfNeeded() {
+    if (_cubicSegments.isNotEmpty) {
+      return;
+    }
+    _cubicSegments.addAll(
+      _computeSegments(_controlPoints!, _tension!, startHandle: _startHandle, endHandle: _endHandle),
+    );
+  }
+
+  @override
+  @protected
+  int get samplingSeed {
+    _initializeIfNeeded();
+    final Offset seedPoint = _cubicSegments[0][1];
+    return ((seedPoint.dx + seedPoint.dy) * 10000).round();
+  }
+
+  @override
+  Offset transformInternal(double t) {
+    _initializeIfNeeded();
+    final double length = _cubicSegments.length.toDouble();
+    final double position;
+    final double localT;
+    final int index;
+    if (t < 1.0) {
+      position = t * length;
+      localT = position % 1.0;
+      index = position.floor();
+    } else {
+      position = length;
+      localT = 1.0;
+      index = _cubicSegments.length - 1;
+    }
+    final List<Offset> cubicControlPoints = _cubicSegments[index];
+    final double localT2 = localT * localT;
+    return cubicControlPoints[0] * localT2 * localT
+         + cubicControlPoints[1] * localT2
+         + cubicControlPoints[2] * localT
+         + cubicControlPoints[3];
+  }
+}
+
+/// An animation easing curve that passes smoothly through the given control
+/// points using a centripetal Catmull-Rom spline.
+///
+/// When this curve is evaluated with [transform], the values will interpolate
+/// smoothly from one control point to the next, passing through (0.0, 0.0), the
+/// given points, and then (1.0, 1.0).
+///
+/// {@macro flutter.animation.CatmullRomSpline}
+///
+/// This class uses a centripetal Catmull-Rom curve (a [CatmullRomSpline]) as
+/// its internal representation. The term centripetal implies that it won't form
+/// loops or self-intersections within a single segment, and corresponds to a
+/// Catmull-Rom α (alpha) value of 0.5.
+///
+/// See also:
+///
+///  * [CatmullRomSpline], the 2D spline that this curve uses to generate its values.
+///  * A Wikipedia article on [centripetal Catmull-Rom splines](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
+///  * [new CatmullRomCurve] for a description of the constraints put on the
+///    input control points.
+///  * This [paper on using Catmull-Rom splines](http://faculty.cs.tamu.edu/schaefer/research/cr_cad.pdf).
+class CatmullRomCurve extends Curve {
+  /// Constructs a centripetal [CatmullRomCurve].
+  ///
+  /// It takes a list of two or more points that describe the points that the
+  /// curve must pass through. See [controlPoints] for a description of the
+  /// restrictions placed on control points. In addition to the given
+  /// [controlPoints], the curve will begin with an implicit control point at
+  /// (0.0, 0.0) and end with an implicit control point at (1.0, 1.0), so that
+  /// the curve begins and ends at those points.
+  ///
+  /// The optional [tension] argument controls how tightly the curve approaches
+  /// the given `controlPoints`. It must be in the range 0.0 to 1.0, inclusive. It
+  /// defaults to 0.0, which provides the smoothest curve. A value of 1.0
+  /// is equivalent to a linear interpolation between points.
+  ///
+  /// The internal curve data structures are lazily computed the first time
+  /// [transform] is called.  If you would rather pre-compute the curve, use
+  /// [CatmullRomCurve.precompute] instead.
+  ///
+  /// All of the arguments must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * This [paper on using Catmull-Rom splines](http://faculty.cs.tamu.edu/schaefer/research/cr_cad.pdf).
+  CatmullRomCurve(this.controlPoints, {this.tension = 0.0})
+      : assert(tension != null),
+        assert(() {
+          return validateControlPoints(
+            controlPoints,
+            tension: tension,
+            reasons: _debugAssertReasons..clear(),
+          );
+        }(), 'control points $controlPoints could not be validated:\n  ${_debugAssertReasons.join('\n  ')}'),
+        // Pre-compute samples so that we don't have to evaluate the spline's inverse
+        // all the time in transformInternal.
+        _precomputedSamples = <Curve2DSample>[];
+
+  /// Constructs a centripetal [CatmullRomCurve].
+  ///
+  /// Same as [new CatmullRomCurve], but it precomputes the internal curve data
+  /// structures for a more predictable computation load.
+  CatmullRomCurve.precompute(this.controlPoints, {this.tension = 0.0})
+      : assert(tension != null),
+        assert(() {
+          return validateControlPoints(
+            controlPoints,
+            tension: tension,
+            reasons: _debugAssertReasons..clear(),
+          );
+        }(), 'control points $controlPoints could not be validated:\n  ${_debugAssertReasons.join('\n  ')}'),
+        // Pre-compute samples so that we don't have to evaluate the spline's inverse
+        // all the time in transformInternal.
+        _precomputedSamples = _computeSamples(controlPoints, tension);
+
+  static List<Curve2DSample> _computeSamples(List<Offset> controlPoints, double tension) {
+    return CatmullRomSpline.precompute(
+      // Force the first and last control points for the spline to be (0, 0)
+      // and (1, 1), respectively.
+      <Offset>[Offset.zero, ...controlPoints, const Offset(1.0, 1.0)],
+      tension: tension,
+    ).generateSamples(start: 0.0, end: 1.0, tolerance: 1e-12).toList();
+  }
+
+  /// A static accumulator for assertion failures. Not used in release mode.
+  static final List<String> _debugAssertReasons = <String>[];
+
+  // The precomputed approximation curve, so that evaluation of the curve is
+  // efficient.
+  //
+  // If the curve is constructed lazily, then this will be empty, and will be filled
+  // the first time transform is called.
+  final List<Curve2DSample> _precomputedSamples;
+
+  /// The control points used to create this curve.
+  ///
+  /// The `dx` value of each [Offset] in [controlPoints] represents the
+  /// animation value at which the curve should pass through the `dy` value of
+  /// the same control point.
+  ///
+  /// The [controlPoints] list must meet the following criteria:
+  ///
+  ///  * The list must contain at least two points.
+  ///  * The X value of each point must be greater than 0.0 and less then 1.0.
+  ///  * The X values of each point must be greater than the
+  ///    previous point's X value (i.e. monotonically increasing). The Y values
+  ///    are not constrained.
+  ///  * The resulting spline must be single-valued in X. That is, for each X
+  ///    value, there must be exactly one Y value. This means that the control
+  ///    points must not generated a spline that loops or overlaps itself.
+  ///
+  /// The static function [validateControlPoints] can be used to check that
+  /// these conditions are met, and will return true if they are. In debug mode,
+  /// it will also optionally return a list of reasons in text form. In debug
+  /// mode, the constructor will assert that these conditions are met and print
+  /// the reasons if the assert fires.
+  ///
+  /// When the curve is evaluated with [transform], the values will interpolate
+  /// smoothly from one control point to the next, passing through (0.0, 0.0), the
+  /// given control points, and (1.0, 1.0).
+  final List<Offset> controlPoints;
+
+  /// The "tension" of the curve.
+  ///
+  /// The `tension` attribute controls how tightly the curve approaches the
+  /// given [controlPoints]. It must be in the range 0.0 to 1.0, inclusive. It
+  /// is optional, and defaults to 0.0, which provides the smoothest curve. A
+  /// value of 1.0 is equivalent to a linear interpolation between control
+  /// points.
+  final double tension;
+
+  /// Validates that a given set of control points for a [CatmullRomCurve] is
+  /// well-formed and will not produce a spline that self-intersects.
+  ///
+  /// This method is also used in debug mode to validate a curve to make sure
+  /// that it won't violate the contract for the [new CatmullRomCurve]
+  /// constructor.
+  ///
+  /// If in debug mode, and `reasons` is non-null, this function will fill in
+  /// `reasons` with descriptions of the problems encountered. The `reasons`
+  /// argument is ignored in release mode.
+  ///
+  /// In release mode, this function can be used to decide if a proposed
+  /// modification to the curve will result in a valid curve.
+  static bool validateControlPoints(
+      List<Offset>? controlPoints, {
+      double tension = 0.0,
+      List<String>? reasons,
+    }) {
+    assert(tension != null);
+    if (controlPoints == null) {
+      assert(() {
+        reasons?.add('Supplied control points cannot be null');
+        return true;
+      }());
+      return false;
+    }
+
+    if (controlPoints.length < 2) {
+      assert(() {
+        reasons?.add('There must be at least two points supplied to create a valid curve.');
+        return true;
+      }());
+      return false;
+    }
+
+    controlPoints = <Offset>[Offset.zero, ...controlPoints, const Offset(1.0, 1.0)];
+    final Offset startHandle = controlPoints[0] * 2.0 - controlPoints[1];
+    final Offset endHandle = controlPoints.last * 2.0 - controlPoints[controlPoints.length - 2];
+    controlPoints = <Offset>[startHandle, ...controlPoints, endHandle];
+    double lastX = -double.infinity;
+    for (int i = 0; i < controlPoints.length; ++i) {
+      if (i > 1 &&
+          i < controlPoints.length - 2 &&
+          (controlPoints[i].dx <= 0.0 || controlPoints[i].dx >= 1.0)) {
+        assert(() {
+          reasons?.add('Control points must have X values between 0.0 and 1.0, exclusive. '
+              'Point $i has an x value (${controlPoints![i].dx}) which is outside the range.');
+          return true;
+        }());
+        return false;
+      }
+      if (controlPoints[i].dx <= lastX) {
+        assert(() {
+          reasons?.add('Each X coordinate must be greater than the preceding X coordinate '
+              '(i.e. must be monotonically increasing in X). Point $i has an x value of '
+              '${controlPoints![i].dx}, which is not greater than $lastX');
+          return true;
+        }());
+        return false;
+      }
+      lastX = controlPoints[i].dx;
+    }
+
+    bool success = true;
+
+    // An empirical test to make sure things are single-valued in X.
+    lastX = -double.infinity;
+    const double tolerance = 1e-3;
+    final CatmullRomSpline testSpline = CatmullRomSpline(controlPoints, tension: tension);
+    final double start = testSpline.findInverse(0.0);
+    final double end = testSpline.findInverse(1.0);
+    final Iterable<Curve2DSample> samplePoints = testSpline.generateSamples(start: start, end: end);
+    /// If the first and last points in the samples aren't at (0,0) or (1,1)
+    /// respectively, then the curve is multi-valued at the ends.
+    if (samplePoints.first.value.dy.abs() > tolerance || (1.0 - samplePoints.last.value.dy).abs() > tolerance) {
+      bool bail = true;
+      success = false;
+      assert(() {
+        reasons?.add('The curve has more than one Y value at X = ${samplePoints.first.value.dx}. '
+            'Try moving some control points further away from this value of X, or increasing '
+            'the tension.');
+        // No need to keep going if we're not giving reasons.
+        bail = reasons == null;
+        return true;
+      }());
+      if (bail) {
+        // If we're not in debug mode, then we want to bail immediately
+        // instead of checking everything else.
+        return false;
+      }
+    }
+    for (final Curve2DSample sample in samplePoints) {
+      final Offset point = sample.value;
+      final double t = sample.t;
+      final double x = point.dx;
+      if (t >= start && t <= end && (x < -1e-3 || x > 1.0 + 1e-3)) {
+        bool bail = true;
+        success = false;
+        assert(() {
+          reasons?.add('The resulting curve has an X value ($x) which is outside '
+              'the range [0.0, 1.0], inclusive.');
+          // No need to keep going if we're not giving reasons.
+          bail = reasons == null;
+          return true;
+        }());
+        if (bail) {
+          // If we're not in debug mode, then we want to bail immediately
+          // instead of checking all the segments.
+          return false;
+        }
+      }
+      if (x < lastX) {
+        bool bail = true;
+        success = false;
+        assert(() {
+          reasons?.add('The curve has more than one Y value at x = $x. Try moving '
+            'some control points further apart in X, or increasing the tension.');
+          // No need to keep going if we're not giving reasons.
+          bail = reasons == null;
+          return true;
+        }());
+        if (bail) {
+          // If we're not in debug mode, then we want to bail immediately
+          // instead of checking all the segments.
+          return false;
+        }
+      }
+      lastX = x;
+    }
+    return success;
+  }
+
+  @override
+  double transformInternal(double t) {
+    // Linearly interpolate between the two closest samples generated when the
+    // curve was created.
+    if (_precomputedSamples.isEmpty) {
+      // Compute the samples now if we were constructed lazily.
+      _precomputedSamples.addAll(_computeSamples(controlPoints, tension));
+    }
+    int start = 0;
+    int end = _precomputedSamples.length - 1;
+    int mid;
+    Offset value;
+    Offset startValue = _precomputedSamples[start].value;
+    Offset endValue = _precomputedSamples[end].value;
+    // Use a binary search to find the index of the sample point that is just
+    // before t.
+    while (end - start > 1) {
+      mid = (end + start) ~/ 2;
+      value = _precomputedSamples[mid].value;
+      if (t >= value.dx) {
+        start = mid;
+        startValue = value;
+      } else {
+        end = mid;
+        endValue = value;
+      }
+    }
+
+    // Now interpolate between the found sample and the next one.
+    final double t2 = (t - startValue.dx) / (endValue.dx - startValue.dx);
+    return lerpDouble(startValue.dy, endValue.dy, t2)!;
+  }
+}
+
+/// A curve that is the reversed inversion of its given curve.
+///
+/// This curve evaluates the given curve in reverse (i.e., from 1.0 to 0.0 as t
+/// increases from 0.0 to 1.0) and returns the inverse of the given curve's
+/// value (i.e., 1.0 minus the given curve's value).
+///
+/// This is the class used to implement the [flipped] getter on curves.
+///
+/// This is often useful with [CurvedAnimation.reverseCurve].
+///
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped.mp4}
+///
+/// See also:
+///
+///  * [Curve.flipped], which provides the [FlippedCurve] of a [Curve].
+///  * [ReverseAnimation], which reverses an [Animation] rather than a [Curve].
+///  * [CurvedAnimation], which can take a separate curve and reverse curve.
+class FlippedCurve extends Curve {
+  /// Creates a flipped curve.
+  ///
+  /// The [curve] argument must not be null.
+  const FlippedCurve(this.curve) : assert(curve != null);
+
+  /// The curve that is being flipped.
+  final Curve curve;
+
+  @override
+  double transformInternal(double t) => 1.0 - curve.transform(1.0 - t);
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'FlippedCurve')}($curve)';
+  }
+}
+
+/// A curve where the rate of change starts out quickly and then decelerates; an
+/// upside-down `f(t) = t²` parabola.
+///
+/// This is equivalent to the Android `DecelerateInterpolator` class with a unit
+/// factor (the default factor).
+///
+/// See [Curves.decelerate] for an instance of this class.
+class _DecelerateCurve extends Curve {
+  const _DecelerateCurve._();
+
+  @override
+  double transformInternal(double t) {
+    // Intended to match the behavior of:
+    // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/animation/DecelerateInterpolator.java
+    // ...as of December 2016.
+    t = 1.0 - t;
+    return 1.0 - t * t;
+  }
+}
+
+// BOUNCE CURVES
+
+double _bounce(double t) {
+  if (t < 1.0 / 2.75) {
+    return 7.5625 * t * t;
+  } else if (t < 2 / 2.75) {
+    t -= 1.5 / 2.75;
+    return 7.5625 * t * t + 0.75;
+  } else if (t < 2.5 / 2.75) {
+    t -= 2.25 / 2.75;
+    return 7.5625 * t * t + 0.9375;
+  }
+  t -= 2.625 / 2.75;
+  return 7.5625 * t * t + 0.984375;
+}
+
+/// An oscillating curve that grows in magnitude.
+///
+/// See [Curves.bounceIn] for an instance of this class.
+class _BounceInCurve extends Curve {
+  const _BounceInCurve._();
+
+  @override
+  double transformInternal(double t) {
+    return 1.0 - _bounce(1.0 - t);
+  }
+}
+
+/// An oscillating curve that shrink in magnitude.
+///
+/// See [Curves.bounceOut] for an instance of this class.
+class _BounceOutCurve extends Curve {
+  const _BounceOutCurve._();
+
+  @override
+  double transformInternal(double t) {
+    return _bounce(t);
+  }
+}
+
+/// An oscillating curve that first grows and then shrink in magnitude.
+///
+/// See [Curves.bounceInOut] for an instance of this class.
+class _BounceInOutCurve extends Curve {
+  const _BounceInOutCurve._();
+
+  @override
+  double transformInternal(double t) {
+    if (t < 0.5)
+      return (1.0 - _bounce(1.0 - t * 2.0)) * 0.5;
+    else
+      return _bounce(t * 2.0 - 1.0) * 0.5 + 0.5;
+  }
+}
+
+
+// ELASTIC CURVES
+
+/// An oscillating curve that grows in magnitude while overshooting its bounds.
+///
+/// An instance of this class using the default period of 0.4 is available as
+/// [Curves.elasticIn].
+///
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4}
+class ElasticInCurve extends Curve {
+  /// Creates an elastic-in curve.
+  ///
+  /// Rather than creating a new instance, consider using [Curves.elasticIn].
+  const ElasticInCurve([this.period = 0.4]);
+
+  /// The duration of the oscillation.
+  final double period;
+
+  @override
+  double transformInternal(double t) {
+    final double s = period / 4.0;
+    t = t - 1.0;
+    return -math.pow(2.0, 10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period);
+  }
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'ElasticInCurve')}($period)';
+  }
+}
+
+/// An oscillating curve that shrinks in magnitude while overshooting its bounds.
+///
+/// An instance of this class using the default period of 0.4 is available as
+/// [Curves.elasticOut].
+///
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4}
+class ElasticOutCurve extends Curve {
+  /// Creates an elastic-out curve.
+  ///
+  /// Rather than creating a new instance, consider using [Curves.elasticOut].
+  const ElasticOutCurve([this.period = 0.4]);
+
+  /// The duration of the oscillation.
+  final double period;
+
+  @override
+  double transformInternal(double t) {
+    final double s = period / 4.0;
+    return math.pow(2.0, -10 * t) * math.sin((t - s) * (math.pi * 2.0) / period) + 1.0;
+  }
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'ElasticOutCurve')}($period)';
+  }
+}
+
+/// An oscillating curve that grows and then shrinks in magnitude while
+/// overshooting its bounds.
+///
+/// An instance of this class using the default period of 0.4 is available as
+/// [Curves.elasticInOut].
+///
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4}
+class ElasticInOutCurve extends Curve {
+  /// Creates an elastic-in-out curve.
+  ///
+  /// Rather than creating a new instance, consider using [Curves.elasticInOut].
+  const ElasticInOutCurve([this.period = 0.4]);
+
+  /// The duration of the oscillation.
+  final double period;
+
+  @override
+  double transformInternal(double t) {
+    final double s = period / 4.0;
+    t = 2.0 * t - 1.0;
+    if (t < 0.0)
+      return -0.5 * math.pow(2.0, 10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period);
+    else
+      return math.pow(2.0, -10.0 * t) * math.sin((t - s) * (math.pi * 2.0) / period) * 0.5 + 1.0;
+  }
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'ElasticInOutCurve')}($period)';
+  }
+}
+
+
+// PREDEFINED CURVES
+
+/// A collection of common animation curves.
+///
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_decelerate.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_sine.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quad.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_cubic.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quart.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quint.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_expo.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_circ.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_back.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_sine.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quad.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quart.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quint.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_expo.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_circ.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_back.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_sine.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quad.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_cubic.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quart.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quint.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_expo.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_circ.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_back.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_slow_middle.mp4}
+/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.mp4}
+///
+/// See also:
+///
+///  * [Curve], the interface implemented by the constants available from the
+///    [Curves] class.
+class Curves {
+  // This class is not meant to be instatiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  Curves._();
+
+  /// A linear animation curve.
+  ///
+  /// This is the identity map over the unit interval: its [Curve.transform]
+  /// method returns its input unmodified. This is useful as a default curve for
+  /// cases where a [Curve] is required but no actual curve is desired.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.mp4}
+  static const Curve linear = _Linear._();
+
+  /// A curve where the rate of change starts out quickly and then decelerates; an
+  /// upside-down `f(t) = t²` parabola.
+  ///
+  /// This is equivalent to the Android `DecelerateInterpolator` class with a unit
+  /// factor (the default factor).
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_decelerate.mp4}
+  static const Curve decelerate = _DecelerateCurve._();
+
+  /// A curve that is very steep and linear at the beginning, but quickly flattens out
+  /// and very slowly eases in.
+  ///
+  /// By default is the curve used to animate pages on iOS back to their original
+  /// position if a swipe gesture is ended midway through a swipe.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_linear_to_slow_ease_in.mp4}
+  static const Cubic fastLinearToSlowEaseIn = Cubic(0.18, 1.0, 0.04, 1.0);
+
+  /// A cubic animation curve that speeds up quickly and ends slowly.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4}
+  static const Cubic ease = Cubic(0.25, 0.1, 0.25, 1.0);
+
+  /// A cubic animation curve that starts slowly and ends quickly.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4}
+  static const Cubic easeIn = Cubic(0.42, 0.0, 1.0, 1.0);
+
+  /// A cubic animation curve that starts starts slowly and ends linearly.
+  ///
+  /// The symmetric animation to [linearToEaseOut].
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_to_linear.mp4}
+  static const Cubic easeInToLinear = Cubic(0.67, 0.03, 0.65, 0.09);
+
+  /// A cubic animation curve that starts slowly and ends quickly. This is
+  /// similar to [Curves.easeIn], but with sinusoidal easing for a slightly less
+  /// abrupt beginning and end. Nonetheless, the result is quite gentle and is
+  /// hard to distinguish from [Curves.linear] at a glance.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_sine.mp4}
+  static const Cubic easeInSine = Cubic(0.47, 0.0, 0.745, 0.715);
+
+  /// A cubic animation curve that starts slowly and ends quickly. Based on a
+  /// quadratic equation where `f(t) = t²`, this is effectively the inverse of
+  /// [Curves.decelerate].
+  ///
+  /// Compared to [Curves.easeInSine], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quad.mp4}
+  static const Cubic easeInQuad = Cubic(0.55, 0.085, 0.68, 0.53);
+
+  /// A cubic animation curve that starts slowly and ends quickly. This curve is
+  /// based on a cubic equation where `f(t) = t³`. The result is a safe sweet
+  /// spot when choosing a curve for widgets animating off the viewport.
+  ///
+  /// Compared to [Curves.easeInQuad], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_cubic.mp4}
+  static const Cubic easeInCubic = Cubic(0.55, 0.055, 0.675, 0.19);
+
+  /// A cubic animation curve that starts slowly and ends quickly. This curve is
+  /// based on a quartic equation where `f(t) = t⁴`.
+  ///
+  /// Animations using this curve or steeper curves will benefit from a longer
+  /// duration to avoid motion feeling unnatural.
+  ///
+  /// Compared to [Curves.easeInCubic], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quart.mp4}
+  static const Cubic easeInQuart = Cubic(0.895, 0.03, 0.685, 0.22);
+
+  /// A cubic animation curve that starts slowly and ends quickly. This curve is
+  /// based on a quintic equation where `f(t) = t⁵`.
+  ///
+  /// Compared to [Curves.easeInQuart], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quint.mp4}
+  static const Cubic easeInQuint = Cubic(0.755, 0.05, 0.855, 0.06);
+
+  /// A cubic animation curve that starts slowly and ends quickly. This curve is
+  /// based on an exponential equation where `f(t) = 2¹⁰⁽ᵗ⁻¹⁾`.
+  ///
+  /// Using this curve can give your animations extra flare, but a longer
+  /// duration may need to be used to compensate for the steepness of the curve.
+  ///
+  /// Compared to [Curves.easeInQuint], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_expo.mp4}
+  static const Cubic easeInExpo = Cubic(0.95, 0.05, 0.795, 0.035);
+
+  /// A cubic animation curve that starts slowly and ends quickly. This curve is
+  /// effectively the bottom-right quarter of a circle.
+  ///
+  /// Like [Curves.easeInExpo], this curve is fairly dramatic and will reduce
+  /// the clarity of an animation if not given a longer duration.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_circ.mp4}
+  static const Cubic easeInCirc = Cubic(0.6, 0.04, 0.98, 0.335);
+
+  /// A cubic animation curve that starts slowly and ends quickly. This curve
+  /// is similar to [Curves.elasticIn] in that it overshoots its bounds before
+  /// reaching its end. Instead of repeated swinging motions before ascending,
+  /// though, this curve overshoots once, then continues to ascend.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_back.mp4}
+  static const Cubic easeInBack = Cubic(0.6, -0.28, 0.735, 0.045);
+
+  /// A cubic animation curve that starts quickly and ends slowly.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4}
+  static const Cubic easeOut = Cubic(0.0, 0.0, 0.58, 1.0);
+
+  /// A cubic animation curve that starts linearly and ends slowly.
+  ///
+  /// A symmetric animation to [easeInToLinear].
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear_to_ease_out.mp4}
+  static const Cubic linearToEaseOut = Cubic(0.35, 0.91, 0.33, 0.97);
+
+  /// A cubic animation curve that starts quickly and ends slowly. This is
+  /// similar to [Curves.easeOut], but with sinusoidal easing for a slightly
+  /// less abrupt beginning and end. Nonetheless, the result is quite gentle and
+  /// is hard to distinguish from [Curves.linear] at a glance.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_sine.mp4}
+  static const Cubic easeOutSine = Cubic(0.39, 0.575, 0.565, 1.0);
+
+  /// A cubic animation curve that starts quickly and ends slowly. This is
+  /// effectively the same as [Curves.decelerate], only simulated using a cubic
+  /// bezier function.
+  ///
+  /// Compared to [Curves.easeOutSine], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quad.mp4}
+  static const Cubic easeOutQuad = Cubic(0.25, 0.46, 0.45, 0.94);
+
+  /// A cubic animation curve that starts quickly and ends slowly. This curve is
+  /// a flipped version of [Curves.easeInCubic].
+  ///
+  /// The result is a safe sweet spot when choosing a curve for animating a
+  /// widget's position entering or already inside the viewport.
+  ///
+  /// Compared to [Curves.easeOutQuad], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_cubic.mp4}
+  static const Cubic easeOutCubic = Cubic(0.215, 0.61, 0.355, 1.0);
+
+  /// A cubic animation curve that starts quickly and ends slowly. This curve is
+  /// a flipped version of [Curves.easeInQuart].
+  ///
+  /// Animations using this curve or steeper curves will benefit from a longer
+  /// duration to avoid motion feeling unnatural.
+  ///
+  /// Compared to [Curves.easeOutCubic], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quart.mp4}
+  static const Cubic easeOutQuart = Cubic(0.165, 0.84, 0.44, 1.0);
+
+  /// A cubic animation curve that starts quickly and ends slowly. This curve is
+  /// a flipped version of [Curves.easeInQuint].
+  ///
+  /// Compared to [Curves.easeOutQuart], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quint.mp4}
+  static const Cubic easeOutQuint = Cubic(0.23, 1.0, 0.32, 1.0);
+
+  /// A cubic animation curve that starts quickly and ends slowly. This curve is
+  /// a flipped version of [Curves.easeInExpo]. Using this curve can give your
+  /// animations extra flare, but a longer duration may need to be used to
+  /// compensate for the steepness of the curve.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_expo.mp4}
+  static const Cubic easeOutExpo = Cubic(0.19, 1.0, 0.22, 1.0);
+
+  /// A cubic animation curve that starts quickly and ends slowly. This curve is
+  /// effectively the top-left quarter of a circle.
+  ///
+  /// Like [Curves.easeOutExpo], this curve is fairly dramatic and will reduce
+  /// the clarity of an animation if not given a longer duration.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_circ.mp4}
+  static const Cubic easeOutCirc = Cubic(0.075, 0.82, 0.165, 1.0);
+
+  /// A cubic animation curve that starts quickly and ends slowly. This curve is
+  /// similar to [Curves.elasticOut] in that it overshoots its bounds before
+  /// reaching its end. Instead of repeated swinging motions after ascending,
+  /// though, this curve only overshoots once.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_back.mp4}
+  static const Cubic easeOutBack = Cubic(0.175, 0.885, 0.32, 1.275);
+
+  /// A cubic animation curve that starts slowly, speeds up, and then ends
+  /// slowly.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4}
+  static const Cubic easeInOut = Cubic(0.42, 0.0, 0.58, 1.0);
+
+  /// A cubic animation curve that starts slowly, speeds up, and then ends
+  /// slowly. This is similar to [Curves.easeInOut], but with sinusoidal easing
+  /// for a slightly less abrupt beginning and end.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_sine.mp4}
+  static const Cubic easeInOutSine = Cubic(0.445, 0.05, 0.55, 0.95);
+
+  /// A cubic animation curve that starts slowly, speeds up, and then ends
+  /// slowly. This curve can be imagined as [Curves.easeInQuad] as the first
+  /// half, and [Curves.easeOutQuad] as the second.
+  ///
+  /// Compared to [Curves.easeInOutSine], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quad.mp4}
+  static const Cubic easeInOutQuad = Cubic(0.455, 0.03, 0.515, 0.955);
+
+  /// A cubic animation curve that starts slowly, speeds up, and then ends
+  /// slowly. This curve can be imagined as [Curves.easeInCubic] as the first
+  /// half, and [Curves.easeOutCubic] as the second.
+  ///
+  /// The result is a safe sweet spot when choosing a curve for a widget whose
+  /// initial and final positions are both within the viewport.
+  ///
+  /// Compared to [Curves.easeInOutQuad], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic.mp4}
+  static const Cubic easeInOutCubic = Cubic(0.645, 0.045, 0.355, 1.0);
+
+  /// A cubic animation curve that starts slowly, speeds up, and then ends
+  /// slowly. This curve can be imagined as [Curves.easeInQuart] as the first
+  /// half, and [Curves.easeOutQuart] as the second.
+  ///
+  /// Animations using this curve or steeper curves will benefit from a longer
+  /// duration to avoid motion feeling unnatural.
+  ///
+  /// Compared to [Curves.easeInOutCubic], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quart.mp4}
+  static const Cubic easeInOutQuart = Cubic(0.77, 0.0, 0.175, 1.0);
+
+  /// A cubic animation curve that starts slowly, speeds up, and then ends
+  /// slowly. This curve can be imagined as [Curves.easeInQuint] as the first
+  /// half, and [Curves.easeOutQuint] as the second.
+  ///
+  /// Compared to [Curves.easeInOutQuart], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quint.mp4}
+  static const Cubic easeInOutQuint = Cubic(0.86, 0.0, 0.07, 1.0);
+
+  /// A cubic animation curve that starts slowly, speeds up, and then ends
+  /// slowly.
+  ///
+  /// Since this curve is arrived at with an exponential function, the midpoint
+  /// is exceptionally steep. Extra consideration should be taken when designing
+  /// an animation using this.
+  ///
+  /// Compared to [Curves.easeInOutQuint], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_expo.mp4}
+  static const Cubic easeInOutExpo = Cubic(1.0, 0.0, 0.0, 1.0);
+
+  /// A cubic animation curve that starts slowly, speeds up, and then ends
+  /// slowly. This curve can be imagined as [Curves.easeInCirc] as the first
+  /// half, and [Curves.easeOutCirc] as the second.
+  ///
+  /// Like [Curves.easeInOutExpo], this curve is fairly dramatic and will reduce
+  /// the clarity of an animation if not given a longer duration.
+  ///
+  /// Compared to [Curves.easeInOutExpo], this curve is slightly steeper.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_circ.mp4}
+  static const Cubic easeInOutCirc = Cubic(0.785, 0.135, 0.15, 0.86);
+
+  /// A cubic animation curve that starts slowly, speeds up, and then ends
+  /// slowly. This curve can be imagined as [Curves.easeInBack] as the first
+  /// half, and [Curves.easeOutBack] as the second.
+  ///
+  /// Since two curves are used as a basis for this curve, the resulting
+  /// animation will overshoot its bounds twice before reaching its end - first
+  /// by exceeding its lower bound, then exceeding its upper bound and finally
+  /// descending to its final position.
+  ///
+  /// Derived from Robert Penner’s easing functions.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_back.mp4}
+  static const Cubic easeInOutBack = Cubic(0.68, -0.55, 0.265, 1.55);
+
+  /// A curve that starts quickly and eases into its final position.
+  ///
+  /// Over the course of the animation, the object spends more time near its
+  /// final destination. As a result, the user isn’t left waiting for the
+  /// animation to finish, and the negative effects of motion are minimized.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4}
+  ///
+  /// See also:
+  ///
+  ///  * [standardEasing], the name for this curve in the Material specification.
+  static const Cubic fastOutSlowIn = Cubic(0.4, 0.0, 0.2, 1.0);
+
+  /// A cubic animation curve that starts quickly, slows down, and then ends
+  /// quickly.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_slow_middle.mp4}
+  static const Cubic slowMiddle = Cubic(0.15, 0.85, 0.85, 0.15);
+
+  /// An oscillating curve that grows in magnitude.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
+  static const Curve bounceIn = _BounceInCurve._();
+
+  /// An oscillating curve that first grows and then shrink in magnitude.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.mp4}
+  static const Curve bounceOut = _BounceOutCurve._();
+
+  /// An oscillating curve that first grows and then shrink in magnitude.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.mp4}
+  static const Curve bounceInOut = _BounceInOutCurve._();
+
+  /// An oscillating curve that grows in magnitude while overshooting its bounds.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4}
+  static const ElasticInCurve elasticIn = ElasticInCurve();
+
+  /// An oscillating curve that shrinks in magnitude while overshooting its bounds.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4}
+  static const ElasticOutCurve elasticOut = ElasticOutCurve();
+
+  /// An oscillating curve that grows and then shrinks in magnitude while overshooting its bounds.
+  ///
+  /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4}
+  static const ElasticInOutCurve elasticInOut = ElasticInOutCurve();
+}
diff --git a/lib/src/animation/listener_helpers.dart b/lib/src/animation/listener_helpers.dart
new file mode 100644
index 0000000..686100b
--- /dev/null
+++ b/lib/src/animation/listener_helpers.dart
@@ -0,0 +1,222 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+import 'animation.dart';
+
+/// A mixin that helps listen to another object only when this object has registered listeners.
+///
+/// This mixin provides implementations of [didRegisterListener] and [didUnregisterListener],
+/// and therefore can be used in conjunction with mixins that require these methods,
+/// [AnimationLocalListenersMixin] and [AnimationLocalStatusListenersMixin].
+mixin AnimationLazyListenerMixin {
+  int _listenerCounter = 0;
+
+  /// Calls [didStartListening] every time a registration of a listener causes
+  /// an empty list of listeners to become non-empty.
+  ///
+  /// See also:
+  ///
+  ///  * [didUnregisterListener], which may cause the listener list to
+  ///    become empty again, and in turn cause this method to call
+  ///    [didStartListening] again.
+  void didRegisterListener() {
+    assert(_listenerCounter >= 0);
+    if (_listenerCounter == 0)
+      didStartListening();
+    _listenerCounter += 1;
+  }
+
+  /// Calls [didStopListening] when an only remaining listener is unregistered,
+  /// thus making the list empty.
+  ///
+  /// See also:
+  ///
+  ///  * [didRegisterListener], which causes the listener list to become non-empty.
+  void didUnregisterListener() {
+    assert(_listenerCounter >= 1);
+    _listenerCounter -= 1;
+    if (_listenerCounter == 0)
+      didStopListening();
+  }
+
+  /// Called when the number of listeners changes from zero to one.
+  @protected
+  void didStartListening();
+
+  /// Called when the number of listeners changes from one to zero.
+  @protected
+  void didStopListening();
+
+  /// Whether there are any listeners.
+  bool get isListening => _listenerCounter > 0;
+}
+
+/// A mixin that replaces the [didRegisterListener]/[didUnregisterListener] contract
+/// with a dispose contract.
+///
+/// This mixin provides implementations of [didRegisterListener] and [didUnregisterListener],
+/// and therefore can be used in conjunction with mixins that require these methods,
+/// [AnimationLocalListenersMixin] and [AnimationLocalStatusListenersMixin].
+mixin AnimationEagerListenerMixin {
+  /// This implementation ignores listener registrations.
+  void didRegisterListener() { }
+
+  /// This implementation ignores listener registrations.
+  void didUnregisterListener() { }
+
+  /// Release the resources used by this object. The object is no longer usable
+  /// after this method is called.
+  @mustCallSuper
+  void dispose() { }
+}
+
+/// A mixin that implements the [addListener]/[removeListener] protocol and notifies
+/// all the registered listeners when [notifyListeners] is called.
+///
+/// This mixin requires that the mixing class provide methods [didRegisterListener]
+/// and [didUnregisterListener]. Implementations of these methods can be obtained
+/// by mixing in another mixin from this library, such as [AnimationLazyListenerMixin].
+mixin AnimationLocalListenersMixin {
+  final ObserverList<VoidCallback> _listeners = ObserverList<VoidCallback>();
+
+  /// Called immediately before a listener is added via [addListener].
+  ///
+  /// At the time this method is called the registered listener is not yet
+  /// notified by [notifyListeners].
+  void didRegisterListener();
+
+  /// Called immediately after a listener is removed via [removeListener].
+  ///
+  /// At the time this method is called the removed listener is no longer
+  /// notified by [notifyListeners].
+  void didUnregisterListener();
+
+  /// Calls the listener every time the value of the animation changes.
+  ///
+  /// Listeners can be removed with [removeListener].
+  void addListener(VoidCallback listener) {
+    didRegisterListener();
+    _listeners.add(listener);
+  }
+
+  /// Stop calling the listener every time the value of the animation changes.
+  ///
+  /// Listeners can be added with [addListener].
+  void removeListener(VoidCallback listener) {
+    final bool removed = _listeners.remove(listener);
+    if (removed) {
+      didUnregisterListener();
+    }
+  }
+
+  /// Calls all the listeners.
+  ///
+  /// If listeners are added or removed during this function, the modifications
+  /// will not change which listeners are called during this iteration.
+  void notifyListeners() {
+    final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
+    for (final VoidCallback listener in localListeners) {
+      InformationCollector? collector;
+      assert(() {
+        collector = () sync* {
+          yield DiagnosticsProperty<AnimationLocalListenersMixin>(
+            'The $runtimeType notifying listeners was',
+            this,
+            style: DiagnosticsTreeStyle.errorProperty,
+          );
+        };
+        return true;
+      }());
+      try {
+        if (_listeners.contains(listener))
+          listener();
+      } catch (exception, stack) {
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          library: 'animation library',
+          context: ErrorDescription('while notifying listeners for $runtimeType'),
+          informationCollector: collector,
+        ));
+      }
+    }
+  }
+}
+
+/// A mixin that implements the addStatusListener/removeStatusListener protocol
+/// and notifies all the registered listeners when notifyStatusListeners is
+/// called.
+///
+/// This mixin requires that the mixing class provide methods [didRegisterListener]
+/// and [didUnregisterListener]. Implementations of these methods can be obtained
+/// by mixing in another mixin from this library, such as [AnimationLazyListenerMixin].
+mixin AnimationLocalStatusListenersMixin {
+  final ObserverList<AnimationStatusListener> _statusListeners = ObserverList<AnimationStatusListener>();
+
+  /// Called immediately before a status listener is added via [addStatusListener].
+  ///
+  /// At the time this method is called the registered listener is not yet
+  /// notified by [notifyStatusListeners].
+  void didRegisterListener();
+
+  /// Called immediately after a status listener is removed via [removeStatusListener].
+  ///
+  /// At the time this method is called the removed listener is no longer
+  /// notified by [notifyStatusListeners].
+  void didUnregisterListener();
+
+  /// Calls listener every time the status of the animation changes.
+  ///
+  /// Listeners can be removed with [removeStatusListener].
+  void addStatusListener(AnimationStatusListener listener) {
+    didRegisterListener();
+    _statusListeners.add(listener);
+  }
+
+  /// Stops calling the listener every time the status of the animation changes.
+  ///
+  /// Listeners can be added with [addStatusListener].
+  void removeStatusListener(AnimationStatusListener listener) {
+    final bool removed = _statusListeners.remove(listener);
+    if (removed) {
+      didUnregisterListener();
+    }
+  }
+
+  /// Calls all the status listeners.
+  ///
+  /// If listeners are added or removed during this function, the modifications
+  /// will not change which listeners are called during this iteration.
+  void notifyStatusListeners(AnimationStatus status) {
+    final List<AnimationStatusListener> localListeners = List<AnimationStatusListener>.from(_statusListeners);
+    for (final AnimationStatusListener listener in localListeners) {
+      try {
+        if (_statusListeners.contains(listener))
+          listener(status);
+      } catch (exception, stack) {
+        InformationCollector? collector;
+        assert(() {
+          collector = () sync* {
+            yield DiagnosticsProperty<AnimationLocalStatusListenersMixin>(
+              'The $runtimeType notifying status listeners was',
+              this,
+              style: DiagnosticsTreeStyle.errorProperty,
+            );
+          };
+          return true;
+        }());
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          library: 'animation library',
+          context: ErrorDescription('while notifying status listeners for $runtimeType'),
+          informationCollector: collector
+        ));
+      }
+    }
+  }
+}
diff --git a/lib/src/animation/tween.dart b/lib/src/animation/tween.dart
new file mode 100644
index 0000000..714a581
--- /dev/null
+++ b/lib/src/animation/tween.dart
@@ -0,0 +1,488 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show Color, Size, Rect;
+
+import 'package:flute/foundation.dart';
+
+import 'animation.dart';
+import 'animations.dart';
+import 'curves.dart';
+
+// Examples can assume:
+// late Animation<Offset> _animation;
+// late AnimationController _controller;
+
+/// An object that can produce a value of type `T` given an [Animation<double>]
+/// as input.
+///
+/// Typically, the values of the input animation are nominally in the range 0.0
+/// to 1.0. In principle, however, any value could be provided.
+///
+/// The main subclass of [Animatable] is [Tween].
+abstract class Animatable<T> {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const Animatable();
+
+  /// Returns the value of the object at point `t`.
+  ///
+  /// The value of `t` is nominally a fraction in the range 0.0 to 1.0, though
+  /// in practice it may extend outside this range.
+  ///
+  /// See also:
+  ///
+  ///  * [evaluate], which is a shorthand for applying [transform] to the value
+  ///    of an [Animation].
+  ///  * [Curve.transform], a similar method for easing curves.
+  T transform(double t);
+
+  /// The current value of this object for the given [Animation].
+  ///
+  /// This function is implemented by deferring to [transform]. Subclasses that
+  /// want to provide custom behavior should override [transform], not
+  /// [evaluate].
+  ///
+  /// See also:
+  ///
+  ///  * [transform], which is similar but takes a `t` value directly instead of
+  ///    an [Animation].
+  ///  * [animate], which creates an [Animation] out of this object, continually
+  ///    applying [evaluate].
+  T evaluate(Animation<double> animation) => transform(animation.value);
+
+  /// Returns a new [Animation] that is driven by the given animation but that
+  /// takes on values determined by this object.
+  ///
+  /// Essentially this returns an [Animation] that automatically applies the
+  /// [evaluate] method to the parent's value.
+  ///
+  /// See also:
+  ///
+  ///  * [AnimationController.drive], which does the same thing from the
+  ///    opposite starting point.
+  Animation<T> animate(Animation<double> parent) {
+    return _AnimatedEvaluation<T>(parent, this);
+  }
+
+  /// Returns a new [Animatable] whose value is determined by first evaluating
+  /// the given parent and then evaluating this object.
+  ///
+  /// This allows [Tween]s to be chained before obtaining an [Animation].
+  Animatable<T> chain(Animatable<double> parent) {
+    return _ChainedEvaluation<T>(parent, this);
+  }
+}
+
+class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {
+  _AnimatedEvaluation(this.parent, this._evaluatable);
+
+  @override
+  final Animation<double> parent;
+
+  final Animatable<T> _evaluatable;
+
+  @override
+  T get value => _evaluatable.evaluate(parent);
+
+  @override
+  String toString() {
+    return '$parent\u27A9$_evaluatable\u27A9$value';
+  }
+
+  @override
+  String toStringDetails() {
+    return '${super.toStringDetails()} $_evaluatable';
+  }
+}
+
+class _ChainedEvaluation<T> extends Animatable<T> {
+  _ChainedEvaluation(this._parent, this._evaluatable);
+
+  final Animatable<double> _parent;
+  final Animatable<T> _evaluatable;
+
+  @override
+  T transform(double t) {
+    return _evaluatable.transform(_parent.transform(t));
+  }
+
+  @override
+  String toString() {
+    return '$_parent\u27A9$_evaluatable';
+  }
+}
+
+/// A linear interpolation between a beginning and ending value.
+///
+/// [Tween] is useful if you want to interpolate across a range.
+///
+/// To use a [Tween] object with an animation, call the [Tween] object's
+/// [animate] method and pass it the [Animation] object that you want to
+/// modify.
+///
+/// You can chain [Tween] objects together using the [chain] method, so that a
+/// single [Animation] object is configured by multiple [Tween] objects called
+/// in succession. This is different than calling the [animate] method twice,
+/// which results in two separate [Animation] objects, each configured with a
+/// single [Tween].
+///
+/// {@tool snippet}
+///
+/// Suppose `_controller` is an [AnimationController], and we want to create an
+/// [Animation<Offset>] that is controlled by that controller, and save it in
+/// `_animation`. Here are two possible ways of expressing this:
+///
+/// ```dart
+/// _animation = _controller.drive(
+///   Tween<Offset>(
+///     begin: const Offset(100.0, 50.0),
+///     end: const Offset(200.0, 300.0),
+///   ),
+/// );
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// ```dart
+/// _animation = Tween<Offset>(
+///   begin: const Offset(100.0, 50.0),
+///   end: const Offset(200.0, 300.0),
+/// ).animate(_controller);
+/// ```
+/// {@end-tool}
+///
+/// In both cases, the `_animation` variable holds an object that, over the
+/// lifetime of the `_controller`'s animation, returns a value
+/// (`_animation.value`) that depicts a point along the line between the two
+/// offsets above. If we used a [MaterialPointArcTween] instead of a
+/// [Tween<Offset>] in the code above, the points would follow a pleasing curve
+/// instead of a straight line, with no other changes necessary.
+///
+/// ## Performance optimizations
+///
+/// Tweens are mutable; specifically, their [begin] and [end] values can be
+/// changed at runtime. An object created with [Animation.drive] using a [Tween]
+/// will immediately honor changes to that underlying [Tween] (though the
+/// listeners will only be triggered if the [Animation] is actively animating).
+/// This can be used to change an animation on the fly without having to
+/// recreate all the objects in the chain from the [AnimationController] to the
+/// final [Tween].
+///
+/// If a [Tween]'s values are never changed, however, a further optimization can
+/// be applied: the object can be stored in a `static final` variable, so that
+/// the exact same instance is used whenever the [Tween] is needed. This is
+/// preferable to creating an identical [Tween] afresh each time a [State.build]
+/// method is called, for example.
+///
+/// ## Types with special considerations
+///
+/// Classes with [lerp] static methods typically have corresponding dedicated
+/// [Tween] subclasses that call that method. For example, [ColorTween] uses
+/// [Color.lerp] to implement the [ColorTween.lerp] method.
+///
+/// Types that define `+` and `-` operators to combine values (`T + T → T` and
+/// `T - T → T`) and an `*` operator to scale by multiplying with a double (`T *
+/// double → T`) can be directly used with `Tween<T>`.
+///
+/// This does not extend to any type with `+`, `-`, and `*` operators. In
+/// particular, [int] does not satisfy this precise contract (`int * double`
+/// actually returns [num], not [int]). There are therefore two specific classes
+/// that can be used to interpolate integers:
+///
+///  * [IntTween], which is an approximation of a linear interpolation (using
+///    [double.round]).
+///  * [StepTween], which uses [double.floor] to ensure that the result is
+///    never greater than it would be using if a `Tween<double>`.
+///
+/// The relevant operators on [Size] also don't fulfill this contract, so
+/// [SizeTween] uses [Size.lerp].
+///
+/// In addition, some of the types that _do_ have suitable `+`, `-`, and `*`
+/// operators still have dedicated [Tween] subclasses that perform the
+/// interpolation in a more specialized manner. One such class is
+/// [MaterialPointArcTween], which is mentioned above. The [AlignmentTween], and
+/// [AlignmentGeometryTween], and [FractionalOffsetTween] are another group of
+/// [Tween]s that use dedicated `lerp` methods instead of merely relying on the
+/// operators (in particular, this allows them to handle null values in a more
+/// useful manner).
+///
+/// ## Nullability
+///
+/// The [begin] and [end] fields are nullable; a [Tween] does not have to
+/// have non-null values specified when it is created.
+///
+/// If `T` is nullable, then [lerp] and [transform] may return null.
+/// This is typically seen in the case where [begin] is null and `t`
+/// is 0.0, or [end] is null and `t` is 1.0, or both are null (at any
+/// `t` value).
+///
+/// If `T` is not nullable, then [begin] and [end] must both be set to
+/// non-null values before using [lerp] or [transform], otherwise they
+/// will throw.
+class Tween<T extends dynamic> extends Animatable<T> {
+  /// Creates a tween.
+  ///
+  /// The [begin] and [end] properties must be non-null before the tween is
+  /// first used, but the arguments can be null if the values are going to be
+  /// filled in later.
+  Tween({
+    this.begin,
+    this.end,
+  });
+
+  /// The value this variable has at the beginning of the animation.
+  ///
+  /// See the constructor for details about whether this property may be null
+  /// (it varies from subclass to subclass).
+  T? begin;
+
+  /// The value this variable has at the end of the animation.
+  ///
+  /// See the constructor for details about whether this property may be null
+  /// (it varies from subclass to subclass).
+  T? end;
+
+  /// Returns the value this variable has at the given animation clock value.
+  ///
+  /// The default implementation of this method uses the [+], [-], and [*]
+  /// operators on `T`. The [begin] and [end] properties must therefore be
+  /// non-null by the time this method is called.
+  ///
+  /// In general, however, it is possible for this to return null, especially
+  /// when `t`=0.0 and [begin] is null, or `t`=1.0 and [end] is null.
+  @protected
+  T lerp(double t) {
+    assert(begin != null);
+    assert(end != null);
+    return begin + (end - begin) * t as T;
+  }
+
+  /// Returns the interpolated value for the current value of the given animation.
+  ///
+  /// This method returns `begin` and `end` when the animation values are 0.0 or
+  /// 1.0, respectively.
+  ///
+  /// This function is implemented by deferring to [lerp]. Subclasses that want
+  /// to provide custom behavior should override [lerp], not [transform] (nor
+  /// [evaluate]).
+  ///
+  /// See the constructor for details about whether the [begin] and [end]
+  /// properties may be null when this is called. It varies from subclass to
+  /// subclass.
+  @override
+  T transform(double t) {
+    if (t == 0.0)
+      return begin as T;
+    if (t == 1.0)
+      return end as T;
+    return lerp(t);
+  }
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'Animatable')}($begin \u2192 $end)';
+}
+
+/// A [Tween] that evaluates its [parent] in reverse.
+class ReverseTween<T> extends Tween<T> {
+  /// Construct a [Tween] that evaluates its [parent] in reverse.
+  ReverseTween(this.parent)
+    : assert(parent != null),
+      super(begin: parent.end, end: parent.begin);
+
+  /// This tween's value is the same as the parent's value evaluated in reverse.
+  ///
+  /// This tween's [begin] is the parent's [end] and its [end] is the parent's
+  /// [begin]. The [lerp] method returns `parent.lerp(1.0 - t)` and its
+  /// [evaluate] method is similar.
+  final Tween<T> parent;
+
+  @override
+  T lerp(double t) => parent.lerp(1.0 - t);
+}
+
+/// An interpolation between two colors.
+///
+/// This class specializes the interpolation of [Tween<Color>] to use
+/// [Color.lerp].
+///
+/// The values can be null, representing no color (which is distinct to
+/// transparent black, as represented by [Colors.transparent]).
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+class ColorTween extends Tween<Color?> {
+  /// Creates a [Color] tween.
+  ///
+  /// The [begin] and [end] properties may be null; the null value
+  /// is treated as transparent.
+  ///
+  /// We recommend that you do not pass [Colors.transparent] as [begin]
+  /// or [end] if you want the effect of fading in or out of transparent.
+  /// Instead prefer null. [Colors.transparent] refers to black transparent and
+  /// thus will fade out of or into black which is likely unwanted.
+  ColorTween({ Color? begin, Color? end }) : super(begin: begin, end: end);
+
+  /// Returns the value this variable has at the given animation clock value.
+  @override
+  Color? lerp(double t) => Color.lerp(begin, end, t);
+}
+
+/// An interpolation between two sizes.
+///
+/// This class specializes the interpolation of [Tween<Size>] to use
+/// [Size.lerp].
+///
+/// The values can be null, representing [Size.zero].
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+class SizeTween extends Tween<Size?> {
+  /// Creates a [Size] tween.
+  ///
+  /// The [begin] and [end] properties may be null; the null value
+  /// is treated as an empty size.
+  SizeTween({ Size? begin, Size? end }) : super(begin: begin, end: end);
+
+  /// Returns the value this variable has at the given animation clock value.
+  @override
+  Size? lerp(double t) => Size.lerp(begin, end, t);
+}
+
+/// An interpolation between two rectangles.
+///
+/// This class specializes the interpolation of [Tween<Rect>] to use
+/// [Rect.lerp].
+///
+/// The values can be null, representing a zero-sized rectangle at the
+/// origin ([Rect.zero]).
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+class RectTween extends Tween<Rect?> {
+  /// Creates a [Rect] tween.
+  ///
+  /// The [begin] and [end] properties may be null; the null value
+  /// is treated as an empty rect at the top left corner.
+  RectTween({ Rect? begin, Rect? end }) : super(begin: begin, end: end);
+
+  /// Returns the value this variable has at the given animation clock value.
+  @override
+  Rect? lerp(double t) => Rect.lerp(begin, end, t);
+}
+
+/// An interpolation between two integers that rounds.
+///
+/// This class specializes the interpolation of [Tween<int>] to be
+/// appropriate for integers by interpolating between the given begin
+/// and end values and then rounding the result to the nearest
+/// integer.
+///
+/// This is the closest approximation to a linear tween that is possible with an
+/// integer. Compare to [StepTween] and [Tween<double>].
+///
+/// The [begin] and [end] values must be set to non-null values before
+/// calling [lerp] or [transform].
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+class IntTween extends Tween<int> {
+  /// Creates an int tween.
+  ///
+  /// The [begin] and [end] properties must be non-null before the tween is
+  /// first used, but the arguments can be null if the values are going to be
+  /// filled in later.
+  IntTween({ int? begin, int? end }) : super(begin: begin, end: end);
+
+  // The inherited lerp() function doesn't work with ints because it multiplies
+  // the begin and end types by a double, and int * double returns a double.
+  @override
+  int lerp(double t) => (begin! + (end! - begin!) * t).round();
+}
+
+/// An interpolation between two integers that floors.
+///
+/// This class specializes the interpolation of [Tween<int>] to be
+/// appropriate for integers by interpolating between the given begin
+/// and end values and then using [double.floor] to return the current
+/// integer component, dropping the fractional component.
+///
+/// This results in a value that is never greater than the equivalent
+/// value from a linear double interpolation. Compare to [IntTween].
+///
+/// The [begin] and [end] values must be set to non-null values before
+/// calling [lerp] or [transform].
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+class StepTween extends Tween<int> {
+  /// Creates an [int] tween that floors.
+  ///
+  /// The [begin] and [end] properties must be non-null before the tween is
+  /// first used, but the arguments can be null if the values are going to be
+  /// filled in later.
+  StepTween({ int? begin, int? end }) : super(begin: begin, end: end);
+
+  // The inherited lerp() function doesn't work with ints because it multiplies
+  // the begin and end types by a double, and int * double returns a double.
+  @override
+  int lerp(double t) => (begin! + (end! - begin!) * t).floor();
+}
+
+/// A tween with a constant value.
+class ConstantTween<T> extends Tween<T> {
+  /// Create a tween whose [begin] and [end] values equal [value].
+  ConstantTween(T value) : super(begin: value, end: value);
+
+  /// This tween doesn't interpolate, it always returns the same value.
+  @override
+  T lerp(double t) => begin as T;
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'ConstantTween')}(value: $begin)';
+}
+
+/// Transforms the value of the given animation by the given curve.
+///
+/// This class differs from [CurvedAnimation] in that [CurvedAnimation] applies
+/// a curve to an existing [Animation] object whereas [CurveTween] can be
+/// chained with another [Tween] prior to receiving the underlying [Animation].
+/// ([CurvedAnimation] also has the additional ability of having different
+/// curves when the animation is going forward vs when it is going backward,
+/// which can be useful in some scenarios.)
+///
+/// {@tool snippet}
+///
+/// The following code snippet shows how you can apply a curve to a linear
+/// animation produced by an [AnimationController] `controller`:
+///
+/// ```dart
+/// final Animation<double> animation = _controller.drive(
+///   CurveTween(curve: Curves.ease),
+/// );
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [CurvedAnimation], for an alternative way of expressing the sample above.
+///  * [AnimationController], for examples of creating and disposing of an
+///    [AnimationController].
+class CurveTween extends Animatable<double> {
+  /// Creates a curve tween.
+  ///
+  /// The [curve] argument must not be null.
+  CurveTween({ required this.curve })
+    : assert(curve != null);
+
+  /// The curve to use when transforming the value of the animation.
+  Curve curve;
+
+  @override
+  double transform(double t) {
+    if (t == 0.0 || t == 1.0) {
+      assert(curve.transform(t).round() == t);
+      return t;
+    }
+    return curve.transform(t);
+  }
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'CurveTween')}(curve: $curve)';
+}
diff --git a/lib/src/animation/tween_sequence.dart b/lib/src/animation/tween_sequence.dart
new file mode 100644
index 0000000..6a6095a
--- /dev/null
+++ b/lib/src/animation/tween_sequence.dart
@@ -0,0 +1,169 @@
+// Copyright 2014 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.
+
+
+import 'animation.dart';
+import 'tween.dart';
+
+// Examples can assume:
+// late AnimationController myAnimationController;
+
+/// Enables creating an [Animation] whose value is defined by a sequence of
+/// [Tween]s.
+///
+/// Each [TweenSequenceItem] has a weight that defines its percentage of the
+/// animation's duration. Each tween defines the animation's value during the
+/// interval indicated by its weight.
+///
+/// {@tool snippet}
+/// This example defines an animation that uses an easing curve to interpolate
+/// between 5.0 and 10.0 during the first 40% of the animation, remains at 10.0
+/// for the next 20%, and then returns to 5.0 for the final 40%.
+///
+/// ```dart
+/// final Animation<double> animation = TweenSequence(
+///   <TweenSequenceItem<double>>[
+///     TweenSequenceItem<double>(
+///       tween: Tween<double>(begin: 5.0, end: 10.0)
+///         .chain(CurveTween(curve: Curves.ease)),
+///       weight: 40.0,
+///     ),
+///     TweenSequenceItem<double>(
+///       tween: ConstantTween<double>(10.0),
+///       weight: 20.0,
+///     ),
+///     TweenSequenceItem<double>(
+///       tween: Tween<double>(begin: 10.0, end: 5.0)
+///         .chain(CurveTween(curve: Curves.ease)),
+///       weight: 40.0,
+///     ),
+///   ],
+/// ).animate(myAnimationController);
+/// ```
+/// {@end-tool}
+class TweenSequence<T> extends Animatable<T> {
+  /// Construct a TweenSequence.
+  ///
+  /// The [items] parameter must be a list of one or more [TweenSequenceItem]s.
+  ///
+  /// There's a small cost associated with building a `TweenSequence` so it's
+  /// best to reuse one, rather than rebuilding it on every frame, when that's
+  /// possible.
+  TweenSequence(List<TweenSequenceItem<T>> items)
+      : assert(items != null),
+        assert(items.isNotEmpty) {
+    _items.addAll(items);
+
+    double totalWeight = 0.0;
+    for (final TweenSequenceItem<T> item in _items)
+      totalWeight += item.weight;
+    assert(totalWeight > 0.0);
+
+    double start = 0.0;
+    for (int i = 0; i < _items.length; i += 1) {
+      final double end = i == _items.length - 1 ? 1.0 : start + _items[i].weight / totalWeight;
+      _intervals.add(_Interval(start, end));
+      start = end;
+    }
+  }
+
+  final List<TweenSequenceItem<T>> _items = <TweenSequenceItem<T>>[];
+  final List<_Interval> _intervals = <_Interval>[];
+
+  T _evaluateAt(double t, int index) {
+    final TweenSequenceItem<T> element = _items[index];
+    final double tInterval = _intervals[index].value(t);
+    return element.tween.transform(tInterval);
+  }
+
+  @override
+  T transform(double t) {
+    assert(t >= 0.0 && t <= 1.0);
+    if (t == 1.0)
+      return _evaluateAt(t, _items.length - 1);
+    for (int index = 0; index < _items.length; index++) {
+      if (_intervals[index].contains(t))
+        return _evaluateAt(t, index);
+    }
+    // Should be unreachable.
+    throw StateError('TweenSequence.evaluate() could not find an interval for $t');
+  }
+
+  @override
+  String toString() => 'TweenSequence(${_items.length} items)';
+}
+
+/// Enables creating a flipped [Animation] whose value is defined by a sequence
+/// of [Tween]s.
+///
+/// This creates a [TweenSequence] that evaluates to a result that flips the
+/// tween both horizontally and vertically.
+///
+/// This tween sequence assumes that the evaluated result has to be a double
+/// between 0.0 and 1.0.
+class FlippedTweenSequence extends TweenSequence<double> {
+  /// Creates a flipped [TweenSequence].
+  ///
+  /// The [items] parameter must be a list of one or more [TweenSequenceItem]s.
+  ///
+  /// There's a small cost associated with building a `TweenSequence` so it's
+  /// best to reuse one, rather than rebuilding it on every frame, when that's
+  /// possible.
+  FlippedTweenSequence(List<TweenSequenceItem<double>> items)
+    : assert(items != null),
+      super(items);
+
+  @override
+  double transform(double t) => 1 - super.transform(1 - t);
+}
+
+/// A simple holder for one element of a [TweenSequence].
+class TweenSequenceItem<T> {
+  /// Construct a TweenSequenceItem.
+  ///
+  /// The [tween] must not be null and [weight] must be greater than 0.0.
+  const TweenSequenceItem({
+    required this.tween,
+    required this.weight,
+  }) : assert(tween != null),
+       assert(weight != null),
+       assert(weight > 0.0);
+
+  /// Defines the value of the [TweenSequence] for the interval within the
+  /// animation's duration indicated by [weight] and this item's position
+  /// in the list of items.
+  ///
+  /// {@tool snippet}
+  ///
+  /// The value of this item can be "curved" by chaining it to a [CurveTween].
+  /// For example to create a tween that eases from 0.0 to 10.0:
+  ///
+  /// ```dart
+  /// Tween<double>(begin: 0.0, end: 10.0)
+  ///   .chain(CurveTween(curve: Curves.ease))
+  /// ```
+  /// {@end-tool}
+  final Animatable<T> tween;
+
+  /// An arbitrary value that indicates the relative percentage of a
+  /// [TweenSequence] animation's duration when [tween] will be used.
+  ///
+  /// The percentage for an individual item is the item's weight divided by the
+  /// sum of all of the items' weights.
+  final double weight;
+}
+
+class _Interval {
+  const _Interval(this.start, this.end) : assert(end > start);
+
+  final double start;
+  final double end;
+
+  bool contains(double t) => t >= start && t < end;
+
+  double value(double t) => (t - start) / (end - start);
+
+  @override
+  String toString() => '<$start, $end>';
+}
diff --git a/lib/src/cupertino/action_sheet.dart b/lib/src/cupertino/action_sheet.dart
new file mode 100644
index 0000000..badd855
--- /dev/null
+++ b/lib/src/cupertino/action_sheet.dart
@@ -0,0 +1,1431 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show ImageFilter;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'interface_level.dart';
+import 'scrollbar.dart';
+import 'theme.dart';
+
+const TextStyle _kActionSheetActionStyle = TextStyle(
+  fontFamily: '.SF UI Text',
+  inherit: false,
+  fontSize: 20.0,
+  fontWeight: FontWeight.w400,
+  textBaseline: TextBaseline.alphabetic,
+);
+
+const TextStyle _kActionSheetContentStyle = TextStyle(
+  fontFamily: '.SF UI Text',
+  inherit: false,
+  fontSize: 13.0,
+  fontWeight: FontWeight.w400,
+  color: _kContentTextColor,
+  textBaseline: TextBaseline.alphabetic,
+);
+
+// Translucent, very light gray that is painted on top of the blurred backdrop
+// as the action sheet's background color.
+// TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/39272. Use
+// System Materials once we have them.
+// Extracted from https://developer.apple.com/design/resources/.
+const Color _kBackgroundColor = CupertinoDynamicColor.withBrightness(
+  color: Color(0xC7F9F9F9),
+  darkColor: Color(0xC7252525),
+);
+
+// Translucent, light gray that is painted on top of the blurred backdrop as
+// the background color of a pressed button.
+// Eye-balled from iOS 13 beta simulator.
+const Color _kPressedColor = CupertinoDynamicColor.withBrightness(
+  color: Color(0xFFE1E1E1),
+  darkColor: Color(0xFF2E2E2E),
+);
+
+const Color _kCancelPressedColor = CupertinoDynamicColor.withBrightness(
+  color: Color(0xFFECECEC),
+  darkColor: Color(0xFF49494B),
+);
+
+// The gray color used for text that appears in the title area.
+// Extracted from https://developer.apple.com/design/resources/.
+const Color _kContentTextColor = Color(0xFF8F8F8F);
+
+// Translucent gray that is painted on top of the blurred backdrop in the gap
+// areas between the content section and actions section, as well as between
+// buttons.
+// Eye-balled from iOS 13 beta simulator.
+const Color _kButtonDividerColor = _kContentTextColor;
+
+const double _kBlurAmount = 20.0;
+const double _kEdgeHorizontalPadding = 8.0;
+const double _kCancelButtonPadding = 8.0;
+const double _kEdgeVerticalPadding = 10.0;
+const double _kContentHorizontalPadding = 40.0;
+const double _kContentVerticalPadding = 14.0;
+const double _kButtonHeight = 56.0;
+const double _kCornerRadius = 14.0;
+const double _kDividerThickness = 1.0;
+
+/// An iOS-style action sheet.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=U-ao8p4A82k}
+///
+/// An action sheet is a specific style of alert that presents the user
+/// with a set of two or more choices related to the current context.
+/// An action sheet can have a title, an additional message, and a list
+/// of actions. The title is displayed above the message and the actions
+/// are displayed below this content.
+///
+/// This action sheet styles its title and message to match standard iOS action
+/// sheet title and message text style.
+///
+/// To display action buttons that look like standard iOS action sheet buttons,
+/// provide [CupertinoActionSheetAction]s for the [actions] given to this action
+/// sheet.
+///
+/// To include a iOS-style cancel button separate from the other buttons,
+/// provide an [CupertinoActionSheetAction] for the [cancelButton] given to this
+/// action sheet.
+///
+/// An action sheet is typically passed as the child widget to
+/// [showCupertinoModalPopup], which displays the action sheet by sliding it up
+/// from the bottom of the screen.
+///
+/// {@tool snippet}
+/// This sample shows how to use a [CupertinoActionSheet].
+///	The [CupertinoActionSheet] shows an alert with a set of two choices
+/// when [CupertinoButton] is pressed.
+///
+/// ```dart
+/// class MyStatefulWidget extends StatefulWidget {
+///   @override
+///   _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
+/// }
+///
+/// class _MyStatefulWidgetState extends State<MyStatefulWidget> {
+///   @override
+///   Widget build(BuildContext context) {
+///     return CupertinoPageScaffold(
+///       child: Center(
+///         child: CupertinoButton(
+///           onPressed: () {
+///             showCupertinoModalPopup(
+///               context: context,
+///               builder: (BuildContext context) => CupertinoActionSheet(
+///                 title: const Text('Title'),
+///                 message: const Text('Message'),
+///                 actions: [
+///                   CupertinoActionSheetAction(
+///                     child: const Text('Action One'),
+///                     onPressed: () {
+///                       Navigator.pop(context);
+///                     },
+///                   ),
+///                   CupertinoActionSheetAction(
+///                     child: const Text('Action Two'),
+///                     onPressed: () {
+///                       Navigator.pop(context);
+///                     },
+///                   )
+///                 ],
+///               ),
+///             );
+///           },
+///           child: Text('CupertinoActionSheet'),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [CupertinoActionSheetAction], which is an iOS-style action sheet button.
+///  * <https://developer.apple.com/design/human-interface-guidelines/ios/views/action-sheets/>
+class CupertinoActionSheet extends StatelessWidget {
+  /// Creates an iOS-style action sheet.
+  ///
+  /// An action sheet must have a non-null value for at least one of the
+  /// following arguments: [actions], [title], [message], or [cancelButton].
+  ///
+  /// Generally, action sheets are used to give the user a choice between
+  /// two or more choices for the current context.
+  const CupertinoActionSheet({
+    Key? key,
+    this.title,
+    this.message,
+    this.actions,
+    this.messageScrollController,
+    this.actionScrollController,
+    this.cancelButton,
+  }) : assert(actions != null || title != null || message != null || cancelButton != null,
+          'An action sheet must have a non-null value for at least one of the following arguments: '
+          'actions, title, message, or cancelButton'),
+       super(key: key);
+
+  /// An optional title of the action sheet. When the [message] is non-null,
+  /// the font of the [title] is bold.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? title;
+
+  /// An optional descriptive message that provides more details about the
+  /// reason for the alert.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? message;
+
+  /// The set of actions that are displayed for the user to select.
+  ///
+  /// Typically this is a list of [CupertinoActionSheetAction] widgets.
+  final List<Widget>? actions;
+
+  /// A scroll controller that can be used to control the scrolling of the
+  /// [message] in the action sheet.
+  ///
+  /// This attribute is typically not needed, as alert messages should be
+  /// short.
+  final ScrollController? messageScrollController;
+
+  /// A scroll controller that can be used to control the scrolling of the
+  /// [actions] in the action sheet.
+  ///
+  /// This attribute is typically not needed.
+  final ScrollController? actionScrollController;
+
+  /// The optional cancel button that is grouped separately from the other
+  /// actions.
+  ///
+  /// Typically this is an [CupertinoActionSheetAction] widget.
+  final Widget? cancelButton;
+
+  Widget _buildContent(BuildContext context) {
+    final List<Widget> content = <Widget>[];
+    if (title != null || message != null) {
+      final Widget titleSection = _CupertinoAlertContentSection(
+        title: title,
+        message: message,
+        scrollController: messageScrollController,
+      );
+      content.add(Flexible(child: titleSection));
+    }
+
+    return Container(
+      color: CupertinoDynamicColor.resolve(_kBackgroundColor, context),
+      child: Column(
+        mainAxisSize: MainAxisSize.min,
+        crossAxisAlignment: CrossAxisAlignment.stretch,
+        children: content,
+      ),
+    );
+  }
+
+  Widget _buildActions() {
+    if (actions == null || actions!.isEmpty) {
+      return Container(
+        height: 0.0,
+      );
+    }
+    return Container(
+      child: _CupertinoAlertActionSection(
+        children: actions!,
+        scrollController: actionScrollController,
+        hasCancelButton: cancelButton != null,
+      ),
+    );
+  }
+
+  Widget _buildCancelButton() {
+    final double cancelPadding = (actions != null || message != null || title != null)
+        ? _kCancelButtonPadding : 0.0;
+    return Padding(
+      padding: EdgeInsets.only(top: cancelPadding),
+      child: _CupertinoActionSheetCancelButton(
+        child: cancelButton,
+      ),
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+
+    final List<Widget> children = <Widget>[
+      Flexible(child: ClipRRect(
+          borderRadius: BorderRadius.circular(12.0),
+          child: BackdropFilter(
+            filter: ImageFilter.blur(sigmaX: _kBlurAmount, sigmaY: _kBlurAmount),
+            child: _CupertinoAlertRenderWidget(
+              contentSection: Builder(builder: _buildContent),
+              actionsSection: _buildActions(),
+            ),
+          ),
+        ),
+      ),
+      if (cancelButton != null) _buildCancelButton(),
+    ];
+
+    final Orientation orientation = MediaQuery.of(context).orientation;
+    final double actionSheetWidth;
+    if (orientation == Orientation.portrait) {
+      actionSheetWidth = MediaQuery.of(context).size.width - (_kEdgeHorizontalPadding * 2);
+    } else {
+      actionSheetWidth = MediaQuery.of(context).size.height - (_kEdgeHorizontalPadding * 2);
+    }
+
+    return SafeArea(
+      child: Semantics(
+        namesRoute: true,
+        scopesRoute: true,
+        explicitChildNodes: true,
+        label: 'Alert',
+        child: CupertinoUserInterfaceLevel(
+          data: CupertinoUserInterfaceLevelData.elevated,
+          child: Container(
+            width: actionSheetWidth,
+            margin: const EdgeInsets.symmetric(
+              horizontal: _kEdgeHorizontalPadding,
+              vertical: _kEdgeVerticalPadding,
+            ),
+            child: Column(
+              children: children,
+              mainAxisSize: MainAxisSize.min,
+              crossAxisAlignment: CrossAxisAlignment.stretch,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+/// A button typically used in a [CupertinoActionSheet].
+///
+/// See also:
+///
+///  * [CupertinoActionSheet], an alert that presents the user with a set of two or
+///    more choices related to the current context.
+class CupertinoActionSheetAction extends StatelessWidget {
+  /// Creates an action for an iOS-style action sheet.
+  ///
+  /// The [child] and [onPressed] arguments must not be null.
+  const CupertinoActionSheetAction({
+    Key? key,
+    required this.onPressed,
+    this.isDefaultAction = false,
+    this.isDestructiveAction = false,
+    required this.child,
+  }) : assert(child != null),
+       assert(onPressed != null),
+       super(key: key);
+
+  /// The callback that is called when the button is tapped.
+  ///
+  /// This attribute must not be null.
+  final VoidCallback onPressed;
+
+  /// Whether this action is the default choice in the action sheet.
+  ///
+  /// Default buttons have bold text.
+  final bool isDefaultAction;
+
+  /// Whether this action might change or delete data.
+  ///
+  /// Destructive buttons have red text.
+  final bool isDestructiveAction;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// Typically a [Text] widget.
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    TextStyle style = _kActionSheetActionStyle.copyWith(
+      color: isDestructiveAction
+        ? CupertinoDynamicColor.resolve(CupertinoColors.systemRed, context)
+        : CupertinoTheme.of(context).primaryColor,
+    );
+
+    if (isDefaultAction) {
+      style = style.copyWith(fontWeight: FontWeight.w600);
+    }
+
+    return GestureDetector(
+      onTap: onPressed,
+      behavior: HitTestBehavior.opaque,
+      child: ConstrainedBox(
+        constraints: const BoxConstraints(
+          minHeight: _kButtonHeight,
+        ),
+        child: Semantics(
+          button: true,
+          child: Container(
+            alignment: Alignment.center,
+            padding: const EdgeInsets.symmetric(
+              vertical: 16.0,
+              horizontal: 10.0,
+            ),
+            child: DefaultTextStyle(
+              style: style,
+              child: child,
+              textAlign: TextAlign.center,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+class _CupertinoActionSheetCancelButton extends StatefulWidget {
+  const _CupertinoActionSheetCancelButton({
+    Key? key,
+    this.child,
+  }) : super(key: key);
+
+  final Widget? child;
+
+  @override
+  _CupertinoActionSheetCancelButtonState createState() => _CupertinoActionSheetCancelButtonState();
+}
+
+class _CupertinoActionSheetCancelButtonState extends State<_CupertinoActionSheetCancelButton> {
+  bool isBeingPressed = false;
+
+  void _onTapDown(TapDownDetails event) {
+    setState(() { isBeingPressed = true; });
+  }
+
+  void _onTapUp(TapUpDetails event) {
+    setState(() { isBeingPressed = false; });
+  }
+
+  void _onTapCancel() {
+    setState(() { isBeingPressed = false; });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final Color backgroundColor = isBeingPressed
+      ? _kCancelPressedColor
+      : CupertinoColors.secondarySystemGroupedBackground;
+    return GestureDetector(
+      excludeFromSemantics: true,
+      onTapDown: _onTapDown,
+      onTapUp: _onTapUp,
+      onTapCancel: _onTapCancel,
+      child: Container(
+        decoration: BoxDecoration(
+          color: CupertinoDynamicColor.resolve(backgroundColor, context),
+          borderRadius: BorderRadius.circular(_kCornerRadius),
+        ),
+        child: widget.child,
+      ),
+    );
+  }
+}
+
+class _CupertinoAlertRenderWidget extends RenderObjectWidget {
+  const _CupertinoAlertRenderWidget({
+    Key? key,
+    required this.contentSection,
+    required this.actionsSection,
+  }) : super(key: key);
+
+  final Widget contentSection;
+  final Widget actionsSection;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    return _RenderCupertinoAlert(
+      dividerThickness: _kDividerThickness / MediaQuery.of(context).devicePixelRatio,
+      dividerColor: CupertinoDynamicColor.resolve(_kButtonDividerColor, context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderCupertinoAlert renderObject) {
+    super.updateRenderObject(context, renderObject);
+    renderObject.dividerColor = CupertinoDynamicColor.resolve(_kButtonDividerColor, context);
+  }
+
+  @override
+  RenderObjectElement createElement() {
+    return _CupertinoAlertRenderElement(this);
+  }
+}
+
+class _CupertinoAlertRenderElement extends RenderObjectElement {
+  _CupertinoAlertRenderElement(_CupertinoAlertRenderWidget widget) : super(widget);
+
+  Element? _contentElement;
+  Element? _actionsElement;
+
+  @override
+  _CupertinoAlertRenderWidget get widget => super.widget as _CupertinoAlertRenderWidget;
+
+  @override
+  _RenderCupertinoAlert get renderObject => super.renderObject as _RenderCupertinoAlert;
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    if (_contentElement != null) {
+      visitor(_contentElement!);
+    }
+    if (_actionsElement != null) {
+      visitor(_actionsElement!);
+    }
+  }
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    _contentElement = updateChild(_contentElement,
+        widget.contentSection, _AlertSections.contentSection);
+    _actionsElement = updateChild(_actionsElement,
+        widget.actionsSection, _AlertSections.actionsSection);
+  }
+
+  @override
+  void insertRenderObjectChild(RenderObject child, _AlertSections slot) {
+    _placeChildInSlot(child, slot);
+  }
+
+  @override
+  void moveRenderObjectChild(RenderObject child, _AlertSections oldSlot, _AlertSections newSlot) {
+    _placeChildInSlot(child, newSlot);
+  }
+
+  @override
+  void update(RenderObjectWidget newWidget) {
+    super.update(newWidget);
+    _contentElement = updateChild(_contentElement,
+        widget.contentSection, _AlertSections.contentSection);
+    _actionsElement = updateChild(_actionsElement,
+        widget.actionsSection, _AlertSections.actionsSection);
+  }
+
+  @override
+  void forgetChild(Element child) {
+    assert(child == _contentElement || child == _actionsElement);
+    if (_contentElement == child) {
+      _contentElement = null;
+    } else if (_actionsElement == child) {
+      _actionsElement = null;
+    }
+    super.forgetChild(child);
+  }
+
+  @override
+  void removeRenderObjectChild(RenderObject child, _AlertSections slot) {
+    assert(child == renderObject.contentSection || child == renderObject.actionsSection);
+    if (renderObject.contentSection == child) {
+      renderObject.contentSection = null;
+    } else if (renderObject.actionsSection == child) {
+      renderObject.actionsSection = null;
+    }
+  }
+
+  void _placeChildInSlot(RenderObject child, _AlertSections slot) {
+    assert(slot != null);
+    switch (slot) {
+      case _AlertSections.contentSection:
+        renderObject.contentSection = child as RenderBox;
+        break;
+      case _AlertSections.actionsSection:
+        renderObject.actionsSection = child as RenderBox;
+        break;
+    }
+  }
+}
+
+// An iOS-style layout policy for sizing an alert's content section and action
+// button section.
+//
+// The policy is as follows:
+//
+// If all content and buttons fit on the screen:
+// The content section and action button section are sized intrinsically.
+//
+// If all content and buttons do not fit on the screen:
+// A minimum height for the action button section is calculated. The action
+// button section will not be rendered shorter than this minimum.  See
+// _RenderCupertinoAlertActions for the minimum height calculation.
+//
+// With the minimum action button section calculated, the content section can
+// take up as much of the remaining space as it needs.
+//
+// After the content section is laid out, the action button section is allowed
+// to take up any remaining space that was not consumed by the content section.
+class _RenderCupertinoAlert extends RenderBox {
+  _RenderCupertinoAlert({
+    RenderBox? contentSection,
+    RenderBox? actionsSection,
+    double dividerThickness = 0.0,
+    required Color dividerColor,
+  }) : assert(dividerColor != null),
+       _contentSection = contentSection,
+       _actionsSection = actionsSection,
+       _dividerThickness = dividerThickness,
+       _dividerPaint = Paint()
+        ..color = dividerColor
+        ..style = PaintingStyle.fill;
+
+  RenderBox? get contentSection => _contentSection;
+  RenderBox? _contentSection;
+  set contentSection(RenderBox? newContentSection) {
+    if (newContentSection != _contentSection) {
+      if (null != _contentSection) {
+        dropChild(_contentSection!);
+      }
+      _contentSection = newContentSection;
+      if (null != _contentSection) {
+        adoptChild(_contentSection!);
+      }
+    }
+  }
+
+  RenderBox? get actionsSection => _actionsSection;
+  RenderBox? _actionsSection;
+  set actionsSection(RenderBox? newActionsSection) {
+    if (newActionsSection != _actionsSection) {
+      if (null != _actionsSection) {
+        dropChild(_actionsSection!);
+      }
+      _actionsSection = newActionsSection;
+      if (null != _actionsSection) {
+        adoptChild(_actionsSection!);
+      }
+    }
+  }
+
+  Color get dividerColor => _dividerPaint.color;
+  set dividerColor(Color value) {
+    if (value == _dividerPaint.color)
+      return;
+    _dividerPaint.color = value;
+    markNeedsPaint();
+  }
+
+  final double _dividerThickness;
+
+  final Paint _dividerPaint;
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    if (null != contentSection) {
+      contentSection!.attach(owner);
+    }
+    if (null != actionsSection) {
+      actionsSection!.attach(owner);
+    }
+  }
+
+  @override
+  void detach() {
+    super.detach();
+    if (null != contentSection) {
+      contentSection!.detach();
+    }
+    if (null != actionsSection) {
+      actionsSection!.detach();
+    }
+  }
+
+  @override
+  void redepthChildren() {
+    if (null != contentSection) {
+      redepthChild(contentSection!);
+    }
+    if (null != actionsSection) {
+      redepthChild(actionsSection!);
+    }
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! MultiChildLayoutParentData) {
+      child.parentData = MultiChildLayoutParentData();
+    }
+  }
+
+  @override
+  void visitChildren(RenderObjectVisitor visitor) {
+    if (contentSection != null) {
+      visitor(contentSection!);
+    }
+    if (actionsSection != null) {
+      visitor(actionsSection!);
+    }
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    final List<DiagnosticsNode> value = <DiagnosticsNode>[];
+    if (contentSection != null) {
+      value.add(contentSection!.toDiagnosticsNode(name: 'content'));
+    }
+    if (actionsSection != null) {
+      value.add(actionsSection!.toDiagnosticsNode(name: 'actions'));
+    }
+    return value;
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    return constraints.minWidth;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    return constraints.maxWidth;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    final double contentHeight = contentSection!.getMinIntrinsicHeight(width);
+    final double actionsHeight = actionsSection!.getMinIntrinsicHeight(width);
+    final bool hasDivider = contentHeight > 0.0 && actionsHeight > 0.0;
+    double height = contentHeight + (hasDivider ? _dividerThickness : 0.0) + actionsHeight;
+
+    if (actionsHeight > 0 || contentHeight > 0)
+      height -= 2 * _kEdgeVerticalPadding;
+    if (height.isFinite)
+      return height;
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    final double contentHeight = contentSection!.getMaxIntrinsicHeight(width);
+    final double actionsHeight = actionsSection!.getMaxIntrinsicHeight(width);
+    final bool hasDivider = contentHeight > 0.0 && actionsHeight > 0.0;
+    double height = contentHeight + (hasDivider ? _dividerThickness : 0.0) + actionsHeight;
+
+    if (actionsHeight > 0 || contentHeight > 0)
+      height -= 2 * _kEdgeVerticalPadding;
+    if (height.isFinite)
+      return height;
+    return 0.0;
+  }
+
+  double _computeDividerThickness(BoxConstraints constraints) {
+    final bool hasDivider = contentSection!.getMaxIntrinsicHeight(constraints.maxWidth) > 0.0
+        && actionsSection!.getMaxIntrinsicHeight(constraints.maxWidth) > 0.0;
+    return hasDivider ? _dividerThickness : 0.0;
+  }
+
+  _AlertSizes _computeSizes({required BoxConstraints constraints, required ChildLayouter layoutChild, required double dividerThickness}) {
+    final double minActionsHeight = actionsSection!.getMinIntrinsicHeight(constraints.maxWidth);
+
+    final Size contentSize = layoutChild(
+      contentSection!,
+      constraints.deflate(EdgeInsets.only(bottom: minActionsHeight + dividerThickness)),
+    );
+
+    final Size actionsSize = layoutChild(
+      actionsSection!,
+      constraints.deflate(EdgeInsets.only(top: contentSize.height + dividerThickness)),
+    );
+
+    final double actionSheetHeight = contentSize.height + dividerThickness + actionsSize.height;
+    return _AlertSizes(
+      size: Size(constraints.maxWidth, actionSheetHeight),
+      contentHeight: contentSize.height,
+    );
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _computeSizes(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.dryLayoutChild,
+      dividerThickness: _computeDividerThickness(constraints),
+    ).size;
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    final double dividerThickness = _computeDividerThickness(constraints);
+    final _AlertSizes alertSizes = _computeSizes(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.layoutChild,
+      dividerThickness: dividerThickness,
+    );
+
+    size = alertSizes.size;
+
+    // Set the position of the actions box to sit at the bottom of the alert.
+    // The content box defaults to the top left, which is where we want it.
+    assert(actionsSection!.parentData is MultiChildLayoutParentData);
+    final MultiChildLayoutParentData actionParentData = actionsSection!.parentData! as MultiChildLayoutParentData;
+    actionParentData.offset = Offset(0.0, alertSizes.contentHeight + dividerThickness);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final MultiChildLayoutParentData contentParentData = contentSection!.parentData! as MultiChildLayoutParentData;
+    contentSection!.paint(context, offset + contentParentData.offset);
+
+    final bool hasDivider = contentSection!.size.height > 0.0 && actionsSection!.size.height > 0.0;
+    if (hasDivider) {
+      _paintDividerBetweenContentAndActions(context.canvas, offset);
+    }
+
+    final MultiChildLayoutParentData actionsParentData = actionsSection!.parentData! as MultiChildLayoutParentData;
+    actionsSection!.paint(context, offset + actionsParentData.offset);
+  }
+
+  void _paintDividerBetweenContentAndActions(Canvas canvas, Offset offset) {
+    canvas.drawRect(
+      Rect.fromLTWH(
+        offset.dx,
+        offset.dy + contentSection!.size.height,
+        size.width,
+        _dividerThickness,
+      ),
+      _dividerPaint,
+    );
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    final MultiChildLayoutParentData contentSectionParentData = contentSection!.parentData! as MultiChildLayoutParentData;
+    final MultiChildLayoutParentData actionsSectionParentData = actionsSection!.parentData! as MultiChildLayoutParentData;
+    return result.addWithPaintOffset(
+             offset: contentSectionParentData.offset,
+             position: position,
+             hitTest: (BoxHitTestResult result, Offset transformed) {
+               assert(transformed == position - contentSectionParentData.offset);
+               return contentSection!.hitTest(result, position: transformed);
+             },
+           )
+        || result.addWithPaintOffset(
+             offset: actionsSectionParentData.offset,
+             position: position,
+             hitTest: (BoxHitTestResult result, Offset transformed) {
+               assert(transformed == position - actionsSectionParentData.offset);
+               return actionsSection!.hitTest(result, position: transformed);
+             },
+           );
+  }
+}
+
+class _AlertSizes {
+  const _AlertSizes({required this.size, required this.contentHeight});
+
+  final Size size;
+  final double contentHeight;
+}
+
+// Visual components of an alert that need to be explicitly sized and
+// laid out at runtime.
+enum _AlertSections {
+  contentSection,
+  actionsSection,
+}
+
+// The "content section" of a CupertinoActionSheet.
+//
+// If title is missing, then only content is added.  If content is
+// missing, then only a title is added. If both are missing, then it returns
+// a SingleChildScrollView with a zero-sized Container.
+class _CupertinoAlertContentSection extends StatelessWidget {
+  const _CupertinoAlertContentSection({
+    Key? key,
+    this.title,
+    this.message,
+    this.scrollController,
+  }) : super(key: key);
+
+  // An optional title of the action sheet. When the message is non-null,
+  // the font of the title is bold.
+  //
+  // Typically a Text widget.
+  final Widget? title;
+
+  // An optional descriptive message that provides more details about the
+  // reason for the alert.
+  //
+  // Typically a Text widget.
+  final Widget? message;
+
+  // A scroll controller that can be used to control the scrolling of the
+  // content in the action sheet.
+  //
+  // Defaults to null, and is typically not needed, since most alert contents
+  // are short.
+  final ScrollController? scrollController;
+
+  @override
+  Widget build(BuildContext context) {
+    final List<Widget> titleContentGroup = <Widget>[];
+
+    if (title != null) {
+      titleContentGroup.add(Padding(
+        padding: const EdgeInsets.only(
+          left: _kContentHorizontalPadding,
+          right: _kContentHorizontalPadding,
+          bottom: _kContentVerticalPadding,
+          top: _kContentVerticalPadding,
+        ),
+        child: DefaultTextStyle(
+          style: message == null ? _kActionSheetContentStyle
+              : _kActionSheetContentStyle.copyWith(fontWeight: FontWeight.w600),
+          textAlign: TextAlign.center,
+          child: title!,
+        ),
+      ));
+    }
+
+    if (message != null) {
+      titleContentGroup.add(
+        Padding(
+          padding: EdgeInsets.only(
+            left: _kContentHorizontalPadding,
+            right: _kContentHorizontalPadding,
+            bottom: title == null ? _kContentVerticalPadding : 22.0,
+            top: title == null ? _kContentVerticalPadding : 0.0,
+          ),
+          child: DefaultTextStyle(
+            style: title == null ? _kActionSheetContentStyle.copyWith(fontWeight: FontWeight.w600)
+                : _kActionSheetContentStyle,
+            textAlign: TextAlign.center,
+            child: message!,
+          ),
+        ),
+      );
+    }
+
+    if (titleContentGroup.isEmpty) {
+      return SingleChildScrollView(
+        controller: scrollController,
+        child: const SizedBox(
+          width: 0.0,
+          height: 0.0,
+        ),
+      );
+    }
+
+    // Add padding between the widgets if necessary.
+    if (titleContentGroup.length > 1) {
+      titleContentGroup.insert(1, const Padding(padding: EdgeInsets.only(top: 8.0)));
+    }
+
+    return CupertinoScrollbar(
+      child: SingleChildScrollView(
+        controller: scrollController,
+        child: Column(
+          mainAxisSize: MainAxisSize.max,
+          crossAxisAlignment: CrossAxisAlignment.stretch,
+          children: titleContentGroup,
+        ),
+      ),
+    );
+  }
+}
+
+// The "actions section" of a CupertinoActionSheet.
+//
+// See _RenderCupertinoAlertActions for details about action button sizing
+// and layout.
+class _CupertinoAlertActionSection extends StatefulWidget {
+  const _CupertinoAlertActionSection({
+    Key? key,
+    required this.children,
+    this.scrollController,
+    this.hasCancelButton,
+  }) : assert(children != null),
+       super(key: key);
+
+  final List<Widget> children;
+
+  // A scroll controller that can be used to control the scrolling of the
+  // actions in the action sheet.
+  //
+  // Defaults to null, and is typically not needed, since most alerts
+  // don't have many actions.
+  final ScrollController? scrollController;
+
+  final bool? hasCancelButton;
+
+  @override
+  _CupertinoAlertActionSectionState createState() => _CupertinoAlertActionSectionState();
+}
+
+class _CupertinoAlertActionSectionState extends State<_CupertinoAlertActionSection> {
+  @override
+  Widget build(BuildContext context) {
+    final double devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
+
+    final List<Widget> interactiveButtons = <Widget>[];
+    for (int i = 0; i < widget.children.length; i += 1) {
+      interactiveButtons.add(
+        _PressableActionButton(
+          child: widget.children[i],
+        ),
+      );
+    }
+
+    return CupertinoScrollbar(
+      child: SingleChildScrollView(
+        controller: widget.scrollController,
+        child: _CupertinoAlertActionsRenderWidget(
+          actionButtons: interactiveButtons,
+          dividerThickness: _kDividerThickness / devicePixelRatio,
+          hasCancelButton: widget.hasCancelButton ?? false,
+        ),
+      ),
+    );
+  }
+}
+
+// A button that updates its render state when pressed.
+//
+// The pressed state is forwarded to an _ActionButtonParentDataWidget. The
+// corresponding _ActionButtonParentData is then interpreted and rendered
+// appropriately by _RenderCupertinoAlertActions.
+class _PressableActionButton extends StatefulWidget {
+  const _PressableActionButton({
+    required this.child,
+  });
+
+  final Widget child;
+
+  @override
+  _PressableActionButtonState createState() => _PressableActionButtonState();
+}
+
+class _PressableActionButtonState extends State<_PressableActionButton> {
+  bool _isPressed = false;
+
+  @override
+  Widget build(BuildContext context) {
+    return _ActionButtonParentDataWidget(
+      isPressed: _isPressed,
+      // TODO(mattcarroll): Button press dynamics need overhaul for iOS:
+      //  https://github.com/flutter/flutter/issues/19786
+      child: GestureDetector(
+        excludeFromSemantics: true,
+        behavior: HitTestBehavior.opaque,
+        onTapDown: (TapDownDetails details) => setState(() => _isPressed = true),
+        onTapUp: (TapUpDetails details) => setState(() => _isPressed = false),
+        // TODO(mattcarroll): Cancel is currently triggered when user moves past
+        //  slop instead of off button: https://github.com/flutter/flutter/issues/19783
+        onTapCancel: () => setState(() => _isPressed = false),
+        child: widget.child,
+      ),
+    );
+  }
+}
+
+// ParentDataWidget that updates _ActionButtonParentData for an action button.
+//
+// Each action button requires knowledge of whether or not it is pressed so that
+// the alert can correctly render the button. The pressed state is held within
+// _ActionButtonParentData. _ActionButtonParentDataWidget is responsible for
+// updating the pressed state of an _ActionButtonParentData based on the
+// incoming isPressed property.
+class _ActionButtonParentDataWidget extends ParentDataWidget<_ActionButtonParentData> {
+  const _ActionButtonParentDataWidget({
+    Key? key,
+    required this.isPressed,
+    required Widget child,
+  }) : super(key: key, child: child);
+
+  final bool isPressed;
+
+  @override
+  void applyParentData(RenderObject renderObject) {
+    assert(renderObject.parentData is _ActionButtonParentData);
+    final _ActionButtonParentData parentData = renderObject.parentData! as _ActionButtonParentData;
+    if (parentData.isPressed != isPressed) {
+      parentData.isPressed = isPressed;
+
+      // Force a repaint.
+      final AbstractNode? targetParent = renderObject.parent;
+      if (targetParent is RenderObject)
+        targetParent.markNeedsPaint();
+    }
+  }
+
+  @override
+  Type get debugTypicalAncestorWidgetClass => _CupertinoAlertActionsRenderWidget;
+}
+
+// ParentData applied to individual action buttons that report whether or not
+// that button is currently pressed by the user.
+class _ActionButtonParentData extends MultiChildLayoutParentData {
+  _ActionButtonParentData({
+    this.isPressed = false,
+  });
+
+  bool isPressed;
+}
+
+// An iOS-style alert action button layout.
+//
+// See _RenderCupertinoAlertActions for specific layout policy details.
+class _CupertinoAlertActionsRenderWidget extends MultiChildRenderObjectWidget {
+  _CupertinoAlertActionsRenderWidget({
+    Key? key,
+    required List<Widget> actionButtons,
+    double dividerThickness = 0.0,
+    bool hasCancelButton = false,
+  }) : _dividerThickness = dividerThickness,
+       _hasCancelButton = hasCancelButton,
+       super(key: key, children: actionButtons);
+
+  final double _dividerThickness;
+  final bool _hasCancelButton;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _RenderCupertinoAlertActions(
+      dividerThickness: _dividerThickness,
+      dividerColor: CupertinoDynamicColor.resolve(_kButtonDividerColor, context),
+      hasCancelButton: _hasCancelButton,
+      backgroundColor: CupertinoDynamicColor.resolve(_kBackgroundColor, context),
+      pressedColor: CupertinoDynamicColor.resolve(_kPressedColor, context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderCupertinoAlertActions renderObject) {
+    renderObject
+      ..dividerThickness = _dividerThickness
+      ..dividerColor = CupertinoDynamicColor.resolve(_kButtonDividerColor, context)
+      ..hasCancelButton = _hasCancelButton
+      ..backgroundColor = CupertinoDynamicColor.resolve(_kBackgroundColor, context)
+      ..pressedColor = CupertinoDynamicColor.resolve(_kPressedColor, context);
+  }
+}
+
+// An iOS-style layout policy for sizing and positioning an action sheet's
+// buttons.
+//
+// The policy is as follows:
+//
+// Action sheet buttons are always stacked vertically. In the case where the
+// content section and the action section combined can not fit on the screen
+// without scrolling, the height of the action section is determined as
+// follows.
+//
+// If the user has included a separate cancel button, the height of the action
+// section can be up to the height of 3 action buttons (i.e., the user can
+// include 1, 2, or 3 action buttons and they will appear without needing to
+// be scrolled). If 4+ action buttons are provided, the height of the action
+// section shrinks to 1.5 buttons tall, and is scrollable.
+//
+// If the user has not included a separate cancel button, the height of the
+// action section is at most 1.5 buttons tall.
+class _RenderCupertinoAlertActions extends RenderBox
+    with ContainerRenderObjectMixin<RenderBox, MultiChildLayoutParentData>,
+        RenderBoxContainerDefaultsMixin<RenderBox, MultiChildLayoutParentData> {
+  _RenderCupertinoAlertActions({
+    List<RenderBox>? children,
+    double dividerThickness = 0.0,
+    required Color dividerColor,
+    bool hasCancelButton = false,
+    required Color backgroundColor,
+    required Color pressedColor,
+  }) : _dividerThickness = dividerThickness,
+       _hasCancelButton = hasCancelButton,
+       _buttonBackgroundPaint = Paint()
+          ..style = PaintingStyle.fill
+          ..color = backgroundColor,
+       _pressedButtonBackgroundPaint = Paint()
+          ..style = PaintingStyle.fill
+          ..color = pressedColor,
+       _dividerPaint = Paint()
+          ..color = dividerColor
+          ..style = PaintingStyle.fill {
+    addAll(children);
+  }
+
+  // The thickness of the divider between buttons.
+  double get dividerThickness => _dividerThickness;
+  double _dividerThickness;
+  set dividerThickness(double newValue) {
+    if (newValue == _dividerThickness) {
+      return;
+    }
+
+    _dividerThickness = newValue;
+    markNeedsLayout();
+  }
+
+  Color get backgroundColor => _buttonBackgroundPaint.color;
+  set backgroundColor(Color newValue) {
+    if (newValue == _buttonBackgroundPaint.color) {
+      return;
+    }
+
+    _buttonBackgroundPaint.color = newValue;
+    markNeedsPaint();
+  }
+
+  Color get pressedColor => _pressedButtonBackgroundPaint.color;
+  set pressedColor(Color newValue) {
+    if (newValue == _pressedButtonBackgroundPaint.color) {
+      return;
+    }
+
+    _pressedButtonBackgroundPaint.color = newValue;
+    markNeedsPaint();
+  }
+
+  Color get dividerColor => _dividerPaint.color;
+  set dividerColor(Color value) {
+    if (value == _dividerPaint.color) {
+      return;
+    }
+    _dividerPaint.color = value;
+    markNeedsPaint();
+  }
+
+  bool _hasCancelButton;
+  bool get hasCancelButton => _hasCancelButton;
+  set hasCancelButton(bool newValue) {
+    if (newValue == _hasCancelButton) {
+      return;
+    }
+
+    _hasCancelButton = newValue;
+    markNeedsLayout();
+  }
+
+  final Paint _buttonBackgroundPaint;
+  final Paint _pressedButtonBackgroundPaint;
+
+  final Paint _dividerPaint;
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! _ActionButtonParentData)
+      child.parentData = _ActionButtonParentData();
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    return constraints.minWidth;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    return constraints.maxWidth;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    if (childCount == 0)
+      return 0.0;
+    if (childCount == 1)
+      return firstChild!.computeMaxIntrinsicHeight(width) + dividerThickness;
+    if (hasCancelButton && childCount < 4)
+      return _computeMinIntrinsicHeightWithCancel(width);
+    return _computeMinIntrinsicHeightWithoutCancel(width);
+  }
+
+  // The minimum height for more than 2-3 buttons when a cancel button is
+  // included is the full height of button stack.
+  double _computeMinIntrinsicHeightWithCancel(double width) {
+    assert(childCount == 2 || childCount == 3);
+    if (childCount == 2) {
+      return firstChild!.getMinIntrinsicHeight(width)
+        + childAfter(firstChild!)!.getMinIntrinsicHeight(width)
+        + dividerThickness;
+    }
+    return firstChild!.getMinIntrinsicHeight(width)
+      + childAfter(firstChild!)!.getMinIntrinsicHeight(width)
+      + childAfter(childAfter(firstChild!)!)!.getMinIntrinsicHeight(width)
+      + (dividerThickness * 2);
+  }
+
+  // The minimum height for more than 2 buttons when no cancel button or 4+
+  // buttons when a cancel button is included is the height of the 1st button
+  // + 50% the height of the 2nd button + 2 dividers.
+  double _computeMinIntrinsicHeightWithoutCancel(double width) {
+    assert(childCount >= 2);
+    return firstChild!.getMinIntrinsicHeight(width)
+      + dividerThickness
+      + (0.5 * childAfter(firstChild!)!.getMinIntrinsicHeight(width));
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    if (childCount == 0)
+      return 0.0;
+    if (childCount == 1)
+      return firstChild!.computeMaxIntrinsicHeight(width) + dividerThickness;
+    return _computeMaxIntrinsicHeightStacked(width);
+  }
+
+  // Max height of a stack of buttons is the sum of all button heights + a
+  // divider for each button.
+  double _computeMaxIntrinsicHeightStacked(double width) {
+    assert(childCount >= 2);
+
+    final double allDividersHeight = (childCount - 1) * dividerThickness;
+    double heightAccumulation = allDividersHeight;
+    RenderBox? button = firstChild;
+    while (button != null) {
+      heightAccumulation += button.getMaxIntrinsicHeight(width);
+      button = childAfter(button);
+    }
+    return heightAccumulation;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _performLayout(constraints, dry: true);
+  }
+
+  @override
+  void performLayout() {
+    size = _performLayout(constraints, dry: false);
+  }
+
+  Size _performLayout(BoxConstraints constraints, {bool dry = false}) {
+    final BoxConstraints perButtonConstraints = constraints.copyWith(
+      minHeight: 0.0,
+      maxHeight: double.infinity,
+    );
+
+    RenderBox? child = firstChild;
+    int index = 0;
+    double verticalOffset = 0.0;
+    while (child != null) {
+      final Size childSize;
+      if (!dry) {
+        child.layout(
+          perButtonConstraints,
+          parentUsesSize: true,
+        );
+        childSize = child.size;
+        assert(child.parentData is MultiChildLayoutParentData);
+        final MultiChildLayoutParentData parentData = child.parentData! as MultiChildLayoutParentData;
+        parentData.offset = Offset(0.0, verticalOffset);
+      } else {
+        childSize = child.getDryLayout(constraints);
+      }
+
+      verticalOffset += childSize.height;
+      if (index < childCount - 1) {
+        // Add a gap for the next divider.
+        verticalOffset += dividerThickness;
+      }
+
+      index += 1;
+      child = childAfter(child);
+    }
+
+    return constraints.constrain(
+      Size(constraints.maxWidth, verticalOffset)
+    );
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final Canvas canvas = context.canvas;
+    _drawButtonBackgroundsAndDividersStacked(canvas, offset);
+    _drawButtons(context, offset);
+  }
+
+  void _drawButtonBackgroundsAndDividersStacked(Canvas canvas, Offset offset) {
+    final Offset dividerOffset = Offset(0.0, dividerThickness);
+
+    final Path backgroundFillPath = Path()
+      ..fillType = PathFillType.evenOdd
+      ..addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height));
+
+    final Path pressedBackgroundFillPath = Path();
+
+    final Path dividersPath = Path();
+
+    Offset accumulatingOffset = offset;
+
+    RenderBox? child = firstChild;
+    RenderBox? prevChild;
+    while (child != null) {
+      assert(child.parentData is _ActionButtonParentData);
+      final _ActionButtonParentData currentButtonParentData = child.parentData! as _ActionButtonParentData;
+      final bool isButtonPressed = currentButtonParentData.isPressed;
+
+      bool isPrevButtonPressed = false;
+      if (prevChild != null) {
+        assert(prevChild.parentData is _ActionButtonParentData);
+        final _ActionButtonParentData previousButtonParentData = prevChild.parentData! as _ActionButtonParentData;
+        isPrevButtonPressed = previousButtonParentData.isPressed;
+      }
+
+      final bool isDividerPresent = child != firstChild;
+      final bool isDividerPainted = isDividerPresent && !(isButtonPressed || isPrevButtonPressed);
+      final Rect dividerRect = Rect.fromLTWH(
+        accumulatingOffset.dx,
+        accumulatingOffset.dy,
+        size.width,
+        _dividerThickness,
+      );
+
+      final Rect buttonBackgroundRect = Rect.fromLTWH(
+        accumulatingOffset.dx,
+        accumulatingOffset.dy + (isDividerPresent ? dividerThickness : 0.0),
+        size.width,
+        child.size.height,
+      );
+
+      // If this button is pressed, then we don't want a white background to be
+      // painted, so we erase this button from the background path.
+      if (isButtonPressed) {
+        backgroundFillPath.addRect(buttonBackgroundRect);
+        pressedBackgroundFillPath.addRect(buttonBackgroundRect);
+      }
+
+      // If this divider is needed, then we erase the divider area from the
+      // background path, and on top of that we paint a translucent gray to
+      // darken the divider area.
+      if (isDividerPainted) {
+        backgroundFillPath.addRect(dividerRect);
+        dividersPath.addRect(dividerRect);
+      }
+
+      accumulatingOffset += (isDividerPresent ? dividerOffset : Offset.zero)
+          + Offset(0.0, child.size.height);
+
+      prevChild = child;
+      child = childAfter(child);
+    }
+
+    canvas.drawPath(backgroundFillPath, _buttonBackgroundPaint);
+    canvas.drawPath(pressedBackgroundFillPath, _pressedButtonBackgroundPaint);
+    canvas.drawPath(dividersPath, _dividerPaint);
+  }
+
+  void _drawButtons(PaintingContext context, Offset offset) {
+    RenderBox? child = firstChild;
+    while (child != null) {
+      final MultiChildLayoutParentData childParentData = child.parentData! as MultiChildLayoutParentData;
+      context.paintChild(child, childParentData.offset + offset);
+      child = childAfter(child);
+    }
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    return defaultHitTestChildren(result, position: position);
+  }
+}
diff --git a/lib/src/cupertino/activity_indicator.dart b/lib/src/cupertino/activity_indicator.dart
new file mode 100644
index 0000000..cafd28a
--- /dev/null
+++ b/lib/src/cupertino/activity_indicator.dart
@@ -0,0 +1,199 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+
+const double _kDefaultIndicatorRadius = 10.0;
+
+// Extracted from iOS 13.2 Beta.
+const Color _kActiveTickColor = CupertinoDynamicColor.withBrightness(
+  color: Color(0xFF3C3C44),
+  darkColor: Color(0xFFEBEBF5),
+);
+
+/// An iOS-style activity indicator that spins clockwise.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=AENVH-ZqKDQ}
+///
+/// See also:
+///
+///  * <https://developer.apple.com/ios/human-interface-guidelines/controls/progress-indicators/#activity-indicators>
+class CupertinoActivityIndicator extends StatefulWidget {
+  /// Creates an iOS-style activity indicator that spins clockwise.
+  const CupertinoActivityIndicator({
+    Key? key,
+    this.animating = true,
+    this.radius = _kDefaultIndicatorRadius,
+  })  : assert(animating != null),
+        assert(radius != null),
+        assert(radius > 0.0),
+        progress = 1.0,
+        super(key: key);
+
+  /// Creates a non-animated iOS-style activity indicator that displays
+  /// a partial count of ticks based on the value of [progress].
+  ///
+  /// When provided, the value of [progress] must be between 0.0 (zero ticks
+  /// will be shown) and 1.0 (all ticks will be shown) inclusive. Defaults
+  /// to 1.0.
+  const CupertinoActivityIndicator.partiallyRevealed({
+    Key? key,
+    this.radius = _kDefaultIndicatorRadius,
+    this.progress = 1.0,
+  })  : assert(radius != null),
+        assert(radius > 0.0),
+        assert(progress != null),
+        assert(progress >= 0.0),
+        assert(progress <= 1.0),
+        animating = false,
+        super(key: key);
+
+  /// Whether the activity indicator is running its animation.
+  ///
+  /// Defaults to true.
+  final bool animating;
+
+  /// Radius of the spinner widget.
+  ///
+  /// Defaults to 10px. Must be positive and cannot be null.
+  final double radius;
+
+  /// Determines the percentage of spinner ticks that will be shown. Typical usage would
+  /// display all ticks, however, this allows for more fine-grained control such as
+  /// during pull-to-refresh when the drag-down action shows one tick at a time as
+  /// the user continues to drag down.
+  ///
+  /// Defaults to 1.0. Must be between 0.0 and 1.0 inclusive, and cannot be null.
+  final double progress;
+
+  @override
+  _CupertinoActivityIndicatorState createState() =>
+      _CupertinoActivityIndicatorState();
+}
+
+class _CupertinoActivityIndicatorState extends State<CupertinoActivityIndicator>
+    with SingleTickerProviderStateMixin {
+  late AnimationController _controller;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(
+      duration: const Duration(seconds: 1),
+      vsync: this,
+    );
+
+    if (widget.animating) {
+      _controller.repeat();
+    }
+  }
+
+  @override
+  void didUpdateWidget(CupertinoActivityIndicator oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.animating != oldWidget.animating) {
+      if (widget.animating)
+        _controller.repeat();
+      else
+        _controller.stop();
+    }
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      height: widget.radius * 2,
+      width: widget.radius * 2,
+      child: CustomPaint(
+        painter: _CupertinoActivityIndicatorPainter(
+          position: _controller,
+          activeColor:
+              CupertinoDynamicColor.resolve(_kActiveTickColor, context),
+          radius: widget.radius,
+          progress: widget.progress,
+        ),
+      ),
+    );
+  }
+}
+
+const double _kTwoPI = math.pi * 2.0;
+
+/// Alpha values extracted from the native component (for both dark and light mode) to
+/// draw the spinning ticks.
+const List<int> _kAlphaValues = <int>[
+  47,
+  47,
+  47,
+  47,
+  72,
+  97,
+  122,
+  147,
+];
+
+/// The alpha value that is used to draw the partially revealed ticks.
+const int _partiallyRevealedAlpha = 147;
+
+class _CupertinoActivityIndicatorPainter extends CustomPainter {
+  _CupertinoActivityIndicatorPainter({
+    required this.position,
+    required this.activeColor,
+    required this.radius,
+    required this.progress,
+  })  : tickFundamentalRRect = RRect.fromLTRBXY(
+          -radius / _kDefaultIndicatorRadius,
+          -radius / 3.0,
+          radius / _kDefaultIndicatorRadius,
+          -radius,
+          radius / _kDefaultIndicatorRadius,
+          radius / _kDefaultIndicatorRadius,
+        ),
+        super(repaint: position);
+
+  final Animation<double> position;
+  final Color activeColor;
+  final double radius;
+  final double progress;
+
+  final RRect tickFundamentalRRect;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final Paint paint = Paint();
+    final int tickCount = _kAlphaValues.length;
+
+    canvas.save();
+    canvas.translate(size.width / 2.0, size.height / 2.0);
+
+    final int activeTick = (tickCount * position.value).floor();
+
+    for (int i = 0; i < tickCount * progress; ++i) {
+      final int t = (i - activeTick) % tickCount;
+      paint.color = activeColor
+          .withAlpha(progress < 1 ? _partiallyRevealedAlpha : _kAlphaValues[t]);
+      canvas.drawRRect(tickFundamentalRRect, paint);
+      canvas.rotate(_kTwoPI / tickCount);
+    }
+
+    canvas.restore();
+  }
+
+  @override
+  bool shouldRepaint(_CupertinoActivityIndicatorPainter oldPainter) {
+    return oldPainter.position != position ||
+        oldPainter.activeColor != activeColor ||
+        oldPainter.progress != progress;
+  }
+}
diff --git a/lib/src/cupertino/app.dart b/lib/src/cupertino/app.dart
new file mode 100644
index 0000000..e86a321
--- /dev/null
+++ b/lib/src/cupertino/app.dart
@@ -0,0 +1,469 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'button.dart';
+import 'colors.dart';
+import 'icons.dart';
+import 'interface_level.dart';
+import 'localizations.dart';
+import 'route.dart';
+import 'theme.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+
+/// An application that uses Cupertino design.
+///
+/// A convenience widget that wraps a number of widgets that are commonly
+/// required for an iOS-design targeting application. It builds upon a
+/// [WidgetsApp] by iOS specific defaulting such as fonts and scrolling
+/// physics.
+///
+/// The [CupertinoApp] configures the top-level [Navigator] to search for routes
+/// in the following order:
+///
+///  1. For the `/` route, the [home] property, if non-null, is used.
+///
+///  2. Otherwise, the [routes] table is used, if it has an entry for the route.
+///
+///  3. Otherwise, [onGenerateRoute] is called, if provided. It should return a
+///     non-null value for any _valid_ route not handled by [home] and [routes].
+///
+///  4. Finally if all else fails [onUnknownRoute] is called.
+///
+/// If [home], [routes], [onGenerateRoute], and [onUnknownRoute] are all null,
+/// and [builder] is not null, then no [Navigator] is created.
+///
+/// This widget also configures the observer of the top-level [Navigator] (if
+/// any) to perform [Hero] animations.
+///
+/// Use this widget with caution on Android since it may produce behaviors
+/// Android users are not expecting such as:
+///
+///  * Pages will be dismissible via a back swipe.
+///  * Scrolling past extremities will trigger iOS-style spring overscrolls.
+///  * The San Francisco font family is unavailable on Android and can result
+///    in undefined font behavior.
+///
+/// See also:
+///
+///  * [CupertinoPageScaffold], which provides a standard page layout default
+///    with nav bars.
+///  * [Navigator], which is used to manage the app's stack of pages.
+///  * [CupertinoPageRoute], which defines an app page that transitions in an
+///    iOS-specific way.
+///  * [WidgetsApp], which defines the basic app elements but does not depend
+///    on the Cupertino library.
+class CupertinoApp extends StatefulWidget {
+  /// Creates a CupertinoApp.
+  ///
+  /// At least one of [home], [routes], [onGenerateRoute], or [builder] must be
+  /// non-null. If only [routes] is given, it must include an entry for the
+  /// [Navigator.defaultRouteName] (`/`), since that is the route used when the
+  /// application is launched with an intent that specifies an otherwise
+  /// unsupported route.
+  ///
+  /// This class creates an instance of [WidgetsApp].
+  ///
+  /// The boolean arguments, [routes], and [navigatorObservers], must not be null.
+  const CupertinoApp({
+    Key? key,
+    this.navigatorKey,
+    this.home,
+    this.theme,
+    Map<String, Widget Function(BuildContext)> this.routes = const <String, WidgetBuilder>{},
+    this.initialRoute,
+    this.onGenerateRoute,
+    this.onGenerateInitialRoutes,
+    this.onUnknownRoute,
+    List<NavigatorObserver> this.navigatorObservers = const <NavigatorObserver>[],
+    this.builder,
+    this.title = '',
+    this.onGenerateTitle,
+    this.color,
+    this.locale,
+    this.localizationsDelegates,
+    this.localeListResolutionCallback,
+    this.localeResolutionCallback,
+    this.supportedLocales = const <Locale>[Locale('en', 'US')],
+    this.showPerformanceOverlay = false,
+    this.checkerboardRasterCacheImages = false,
+    this.checkerboardOffscreenLayers = false,
+    this.showSemanticsDebugger = false,
+    this.debugShowCheckedModeBanner = true,
+    this.shortcuts,
+    this.actions,
+    this.restorationScopeId,
+  }) : assert(routes != null),
+       assert(navigatorObservers != null),
+       assert(title != null),
+       assert(showPerformanceOverlay != null),
+       assert(checkerboardRasterCacheImages != null),
+       assert(checkerboardOffscreenLayers != null),
+       assert(showSemanticsDebugger != null),
+       assert(debugShowCheckedModeBanner != null),
+       routeInformationProvider = null,
+       routeInformationParser = null,
+       routerDelegate = null,
+       backButtonDispatcher = null,
+       super(key: key);
+
+  /// Creates a [CupertinoApp] that uses the [Router] instead of a [Navigator].
+  const CupertinoApp.router({
+    Key? key,
+    this.routeInformationProvider,
+    required RouteInformationParser<Object> this.routeInformationParser,
+    required RouterDelegate<Object> this.routerDelegate,
+    this.backButtonDispatcher,
+    this.theme,
+    this.builder,
+    this.title = '',
+    this.onGenerateTitle,
+    this.color,
+    this.locale,
+    this.localizationsDelegates,
+    this.localeListResolutionCallback,
+    this.localeResolutionCallback,
+    this.supportedLocales = const <Locale>[Locale('en', 'US')],
+    this.showPerformanceOverlay = false,
+    this.checkerboardRasterCacheImages = false,
+    this.checkerboardOffscreenLayers = false,
+    this.showSemanticsDebugger = false,
+    this.debugShowCheckedModeBanner = true,
+    this.shortcuts,
+    this.actions,
+    this.restorationScopeId,
+  }) : assert(title != null),
+       assert(showPerformanceOverlay != null),
+       assert(checkerboardRasterCacheImages != null),
+       assert(checkerboardOffscreenLayers != null),
+       assert(showSemanticsDebugger != null),
+       assert(debugShowCheckedModeBanner != null),
+       navigatorObservers = null,
+       navigatorKey = null,
+       onGenerateRoute = null,
+       home = null,
+       onGenerateInitialRoutes = null,
+       onUnknownRoute = null,
+       routes = null,
+       initialRoute = null,
+       super(key: key);
+
+  /// {@macro flutter.widgets.widgetsApp.navigatorKey}
+  final GlobalKey<NavigatorState>? navigatorKey;
+
+  /// {@macro flutter.widgets.widgetsApp.home}
+  final Widget? home;
+
+  /// The top-level [CupertinoTheme] styling.
+  ///
+  /// A null [theme] or unspecified [theme] attributes will default to iOS
+  /// system values.
+  final CupertinoThemeData? theme;
+
+  /// The application's top-level routing table.
+  ///
+  /// When a named route is pushed with [Navigator.pushNamed], the route name is
+  /// looked up in this map. If the name is present, the associated
+  /// [WidgetBuilder] is used to construct a [CupertinoPageRoute] that performs
+  /// an appropriate transition, including [Hero] animations, to the new route.
+  ///
+  /// {@macro flutter.widgets.widgetsApp.routes}
+  final Map<String, WidgetBuilder>? routes;
+
+  /// {@macro flutter.widgets.widgetsApp.initialRoute}
+  final String? initialRoute;
+
+  /// {@macro flutter.widgets.widgetsApp.onGenerateRoute}
+  final RouteFactory? onGenerateRoute;
+
+  /// {@macro flutter.widgets.widgetsApp.onGenerateInitialRoutes}
+  final InitialRouteListFactory? onGenerateInitialRoutes;
+
+  /// {@macro flutter.widgets.widgetsApp.onUnknownRoute}
+  final RouteFactory? onUnknownRoute;
+
+  /// {@macro flutter.widgets.widgetsApp.navigatorObservers}
+  final List<NavigatorObserver>? navigatorObservers;
+
+  /// {@macro flutter.widgets.widgetsApp.routeInformationProvider}
+  final RouteInformationProvider? routeInformationProvider;
+
+  /// {@macro flutter.widgets.widgetsApp.routeInformationParser}
+  final RouteInformationParser<Object>? routeInformationParser;
+
+  /// {@macro flutter.widgets.widgetsApp.routerDelegate}
+  final RouterDelegate<Object>? routerDelegate;
+
+  /// {@macro flutter.widgets.widgetsApp.backButtonDispatcher}
+  final BackButtonDispatcher? backButtonDispatcher;
+
+  /// {@macro flutter.widgets.widgetsApp.builder}
+  final TransitionBuilder? builder;
+
+  /// {@macro flutter.widgets.widgetsApp.title}
+  ///
+  /// This value is passed unmodified to [WidgetsApp.title].
+  final String title;
+
+  /// {@macro flutter.widgets.widgetsApp.onGenerateTitle}
+  ///
+  /// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
+  final GenerateAppTitle? onGenerateTitle;
+
+  /// {@macro flutter.widgets.widgetsApp.color}
+  final Color? color;
+
+  /// {@macro flutter.widgets.widgetsApp.locale}
+  final Locale? locale;
+
+  /// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
+  final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
+
+  /// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
+  ///
+  /// This callback is passed along to the [WidgetsApp] built by this widget.
+  final LocaleListResolutionCallback? localeListResolutionCallback;
+
+  /// {@macro flutter.widgets.LocaleResolutionCallback}
+  ///
+  /// This callback is passed along to the [WidgetsApp] built by this widget.
+  final LocaleResolutionCallback? localeResolutionCallback;
+
+  /// {@macro flutter.widgets.widgetsApp.supportedLocales}
+  ///
+  /// It is passed along unmodified to the [WidgetsApp] built by this widget.
+  final Iterable<Locale> supportedLocales;
+
+  /// Turns on a performance overlay.
+  ///
+  /// See also:
+  ///
+  ///  * <https://flutter.dev/debugging/#performanceoverlay>
+  final bool showPerformanceOverlay;
+
+  /// Turns on checkerboarding of raster cache images.
+  final bool checkerboardRasterCacheImages;
+
+  /// Turns on checkerboarding of layers rendered to offscreen bitmaps.
+  final bool checkerboardOffscreenLayers;
+
+  /// Turns on an overlay that shows the accessibility information
+  /// reported by the framework.
+  final bool showSemanticsDebugger;
+
+  /// {@macro flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
+  final bool debugShowCheckedModeBanner;
+
+  /// {@macro flutter.widgets.widgetsApp.shortcuts}
+  /// {@tool snippet}
+  /// This example shows how to add a single shortcut for
+  /// [LogicalKeyboardKey.select] to the default shortcuts without needing to
+  /// add your own [Shortcuts] widget.
+  ///
+  /// Alternatively, you could insert a [Shortcuts] widget with just the mapping
+  /// you want to add between the [WidgetsApp] and its child and get the same
+  /// effect.
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return WidgetsApp(
+  ///     shortcuts: <LogicalKeySet, Intent>{
+  ///       ... WidgetsApp.defaultShortcuts,
+  ///       LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
+  ///     },
+  ///     color: const Color(0xFFFF0000),
+  ///     builder: (BuildContext context, Widget child) {
+  ///       return const Placeholder();
+  ///     },
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  /// {@macro flutter.widgets.widgetsApp.shortcuts.seeAlso}
+  final Map<LogicalKeySet, Intent>? shortcuts;
+
+  /// {@macro flutter.widgets.widgetsApp.actions}
+  /// {@tool snippet}
+  /// This example shows how to add a single action handling an
+  /// [ActivateAction] to the default actions without needing to
+  /// add your own [Actions] widget.
+  ///
+  /// Alternatively, you could insert a [Actions] widget with just the mapping
+  /// you want to add between the [WidgetsApp] and its child and get the same
+  /// effect.
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return WidgetsApp(
+  ///     actions: <Type, Action<Intent>>{
+  ///       ... WidgetsApp.defaultActions,
+  ///       ActivateAction: CallbackAction(
+  ///         onInvoke: (Intent intent) {
+  ///           // Do something here...
+  ///           return null;
+  ///         },
+  ///       ),
+  ///     },
+  ///     color: const Color(0xFFFF0000),
+  ///     builder: (BuildContext context, Widget child) {
+  ///       return const Placeholder();
+  ///     },
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  /// {@macro flutter.widgets.widgetsApp.actions.seeAlso}
+  final Map<Type, Action<Intent>>? actions;
+
+  /// {@macro flutter.widgets.widgetsApp.restorationScopeId}
+  final String? restorationScopeId;
+
+  @override
+  _CupertinoAppState createState() => _CupertinoAppState();
+
+  /// The [HeroController] used for Cupertino page transitions.
+  ///
+  /// Used by [CupertinoTabView] and [CupertinoApp].
+  static HeroController createCupertinoHeroController() =>
+      HeroController(); // Linear tweening.
+}
+
+class _AlwaysCupertinoScrollBehavior extends ScrollBehavior {
+  @override
+  Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
+    // Never build any overscroll glow indicators.
+    return child;
+  }
+
+  @override
+  ScrollPhysics getScrollPhysics(BuildContext context) {
+    return const BouncingScrollPhysics();
+  }
+}
+
+class _CupertinoAppState extends State<CupertinoApp> {
+  late HeroController _heroController;
+  bool get _usesRouter => widget.routerDelegate != null;
+
+  @override
+  void initState() {
+    super.initState();
+    _heroController = CupertinoApp.createCupertinoHeroController();
+  }
+
+  // Combine the default localization for Cupertino with the ones contributed
+  // by the localizationsDelegates parameter, if any. Only the first delegate
+  // of a particular LocalizationsDelegate.type is loaded so the
+  // localizationsDelegate parameter can be used to override
+  // _CupertinoLocalizationsDelegate.
+  Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
+    if (widget.localizationsDelegates != null)
+      yield* widget.localizationsDelegates!;
+    yield DefaultCupertinoLocalizations.delegate;
+  }
+
+  Widget _inspectorSelectButtonBuilder(BuildContext context, VoidCallback onPressed) {
+    return CupertinoButton.filled(
+      child: const Icon(
+        CupertinoIcons.search,
+        size: 28.0,
+        color: CupertinoColors.white,
+      ),
+      padding: EdgeInsets.zero,
+      onPressed: onPressed,
+    );
+  }
+
+  WidgetsApp _buildWidgetApp(BuildContext context) {
+    final CupertinoThemeData effectiveThemeData = CupertinoTheme.of(context);
+    final Color color = CupertinoDynamicColor.resolve(widget.color ?? effectiveThemeData.primaryColor, context);
+
+    if (_usesRouter) {
+      return WidgetsApp.router(
+        key: GlobalObjectKey(this),
+        routeInformationProvider: widget.routeInformationProvider,
+        routeInformationParser: widget.routeInformationParser!,
+        routerDelegate: widget.routerDelegate!,
+        backButtonDispatcher: widget.backButtonDispatcher,
+        builder: widget.builder,
+        title: widget.title,
+        onGenerateTitle: widget.onGenerateTitle,
+        textStyle: effectiveThemeData.textTheme.textStyle,
+        color: color,
+        locale: widget.locale,
+        localizationsDelegates: _localizationsDelegates,
+        localeResolutionCallback: widget.localeResolutionCallback,
+        localeListResolutionCallback: widget.localeListResolutionCallback,
+        supportedLocales: widget.supportedLocales,
+        showPerformanceOverlay: widget.showPerformanceOverlay,
+        checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
+        checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
+        showSemanticsDebugger: widget.showSemanticsDebugger,
+        debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
+        inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
+        shortcuts: widget.shortcuts,
+        actions: widget.actions,
+        restorationScopeId: widget.restorationScopeId,
+      );
+    }
+    return WidgetsApp(
+      key: GlobalObjectKey(this),
+      navigatorKey: widget.navigatorKey,
+      navigatorObservers: widget.navigatorObservers!,
+      pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
+        return CupertinoPageRoute<T>(settings: settings, builder: builder);
+      },
+      home: widget.home,
+      routes: widget.routes!,
+      initialRoute: widget.initialRoute,
+      onGenerateRoute: widget.onGenerateRoute,
+      onGenerateInitialRoutes: widget.onGenerateInitialRoutes,
+      onUnknownRoute: widget.onUnknownRoute,
+      builder: widget.builder,
+      title: widget.title,
+      onGenerateTitle: widget.onGenerateTitle,
+      textStyle: effectiveThemeData.textTheme.textStyle,
+      color: color,
+      locale: widget.locale,
+      localizationsDelegates: _localizationsDelegates,
+      localeResolutionCallback: widget.localeResolutionCallback,
+      localeListResolutionCallback: widget.localeListResolutionCallback,
+      supportedLocales: widget.supportedLocales,
+      showPerformanceOverlay: widget.showPerformanceOverlay,
+      checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
+      checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
+      showSemanticsDebugger: widget.showSemanticsDebugger,
+      debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
+      inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
+      shortcuts: widget.shortcuts,
+      actions: widget.actions,
+      restorationScopeId: widget.restorationScopeId,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final CupertinoThemeData effectiveThemeData = widget.theme ?? const CupertinoThemeData();
+
+    return ScrollConfiguration(
+      behavior: _AlwaysCupertinoScrollBehavior(),
+      child: CupertinoUserInterfaceLevel(
+        data: CupertinoUserInterfaceLevelData.base,
+        child: CupertinoTheme(
+          data: effectiveThemeData,
+          child: HeroControllerScope(
+            controller: _heroController,
+            child: Builder(
+              builder: _buildWidgetApp,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/cupertino/bottom_tab_bar.dart b/lib/src/cupertino/bottom_tab_bar.dart
new file mode 100644
index 0000000..284307c
--- /dev/null
+++ b/lib/src/cupertino/bottom_tab_bar.dart
@@ -0,0 +1,300 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show ImageFilter;
+
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'localizations.dart';
+import 'theme.dart';
+
+// Standard iOS 10 tab bar height.
+const double _kTabBarHeight = 50.0;
+
+const Color _kDefaultTabBarBorderColor = CupertinoDynamicColor.withBrightness(
+  color: Color(0x4C000000),
+  darkColor: Color(0x29000000),
+);
+const Color _kDefaultTabBarInactiveColor = CupertinoColors.inactiveGray;
+
+/// An iOS-styled bottom navigation tab bar.
+///
+/// Displays multiple tabs using [BottomNavigationBarItem] with one tab being
+/// active, the first tab by default.
+///
+/// This [StatelessWidget] doesn't store the active tab itself. You must
+/// listen to the [onTap] callbacks and call `setState` with a new [currentIndex]
+/// for the new selection to reflect. This can also be done automatically
+/// by wrapping this with a [CupertinoTabScaffold].
+///
+/// Tab changes typically trigger a switch between [Navigator]s, each with its
+/// own navigation stack, per standard iOS design. This can be done by using
+/// [CupertinoTabView]s inside each tab builder in [CupertinoTabScaffold].
+///
+/// If the given [backgroundColor]'s opacity is not 1.0 (which is the case by
+/// default), it will produce a blurring effect to the content behind it.
+///
+/// When used as [CupertinoTabScaffold.tabBar], by default `CupertinoTabBar` has
+/// its text scale factor set to 1.0 and does not respond to text scale factor
+/// changes from the operating system, to match the native iOS behavior. To override
+/// this behavior, wrap each of the `navigationBar`'s components inside a [MediaQuery]
+/// with the desired [MediaQueryData.textScaleFactor] value. The text scale factor
+/// value from the operating system can be retrieved in many ways, such as querying
+/// [MediaQuery.textScaleFactorOf] against [CupertinoApp]'s [BuildContext].
+///
+/// See also:
+///
+///  * [CupertinoTabScaffold], which hosts the [CupertinoTabBar] at the bottom.
+///  * [BottomNavigationBarItem], an item in a [CupertinoTabBar].
+class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
+  /// Creates a tab bar in the iOS style.
+  const CupertinoTabBar({
+    Key? key,
+    required this.items,
+    this.onTap,
+    this.currentIndex = 0,
+    this.backgroundColor,
+    this.activeColor,
+    this.inactiveColor = _kDefaultTabBarInactiveColor,
+    this.iconSize = 30.0,
+    this.border = const Border(
+      top: BorderSide(
+        color: _kDefaultTabBarBorderColor,
+        width: 0.0, // One physical pixel.
+        style: BorderStyle.solid,
+      ),
+    ),
+  }) : assert(items != null),
+       assert(
+         items.length >= 2,
+         "Tabs need at least 2 items to conform to Apple's HIG",
+       ),
+       assert(currentIndex != null),
+       assert(0 <= currentIndex && currentIndex < items.length),
+       assert(iconSize != null),
+       assert(inactiveColor != null),
+       super(key: key);
+
+  /// The interactive items laid out within the bottom navigation bar.
+  ///
+  /// Must not be null.
+  final List<BottomNavigationBarItem> items;
+
+  /// The callback that is called when a item is tapped.
+  ///
+  /// The widget creating the bottom navigation bar needs to keep track of the
+  /// current index and call `setState` to rebuild it with the newly provided
+  /// index.
+  final ValueChanged<int>? onTap;
+
+  /// The index into [items] of the current active item.
+  ///
+  /// Must not be null and must inclusively be between 0 and the number of tabs
+  /// minus 1.
+  final int currentIndex;
+
+  /// The background color of the tab bar. If it contains transparency, the
+  /// tab bar will automatically produce a blurring effect to the content
+  /// behind it.
+  ///
+  /// Defaults to [CupertinoTheme]'s `barBackgroundColor` when null.
+  final Color? backgroundColor;
+
+  /// The foreground color of the icon and title for the [BottomNavigationBarItem]
+  /// of the selected tab.
+  ///
+  /// Defaults to [CupertinoTheme]'s `primaryColor` if null.
+  final Color? activeColor;
+
+  /// The foreground color of the icon and title for the [BottomNavigationBarItem]s
+  /// in the unselected state.
+  ///
+  /// Defaults to a [CupertinoDynamicColor] that matches the disabled foreground
+  /// color of the native `UITabBar` component. Cannot be null.
+  final Color inactiveColor;
+
+  /// The size of all of the [BottomNavigationBarItem] icons.
+  ///
+  /// This value is used to configure the [IconTheme] for the navigation bar.
+  /// When a [BottomNavigationBarItem.icon] widget is not an [Icon] the widget
+  /// should configure itself to match the icon theme's size and color.
+  ///
+  /// Must not be null.
+  final double iconSize;
+
+  /// The border of the [CupertinoTabBar].
+  ///
+  /// The default value is a one physical pixel top border with grey color.
+  final Border? border;
+
+  @override
+  Size get preferredSize => const Size.fromHeight(_kTabBarHeight);
+
+  /// Indicates whether the tab bar is fully opaque or can have contents behind
+  /// it show through it.
+  bool opaque(BuildContext context) {
+    final Color backgroundColor =
+        this.backgroundColor ?? CupertinoTheme.of(context).barBackgroundColor;
+    return CupertinoDynamicColor.resolve(backgroundColor, context).alpha == 0xFF;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    final double bottomPadding = MediaQuery.of(context).padding.bottom;
+
+    final Color backgroundColor = CupertinoDynamicColor.resolve(
+      this.backgroundColor ?? CupertinoTheme.of(context).barBackgroundColor,
+      context,
+    );
+
+    BorderSide resolveBorderSide(BorderSide side) {
+      return side == BorderSide.none
+        ? side
+        : side.copyWith(color: CupertinoDynamicColor.resolve(side.color, context));
+    }
+
+    // Return the border as is when it's a subclass.
+    final Border? resolvedBorder = border == null || border.runtimeType != Border
+      ? border
+      : Border(
+        top: resolveBorderSide(border!.top),
+        left: resolveBorderSide(border!.left),
+        bottom: resolveBorderSide(border!.bottom),
+        right: resolveBorderSide(border!.right),
+      );
+
+    final Color inactive = CupertinoDynamicColor.resolve(inactiveColor, context);
+    Widget result = DecoratedBox(
+      decoration: BoxDecoration(
+        border: resolvedBorder,
+        color: backgroundColor,
+      ),
+      child: SizedBox(
+        height: _kTabBarHeight + bottomPadding,
+        child: IconTheme.merge( // Default with the inactive state.
+          data: IconThemeData(color: inactive, size: iconSize),
+          child: DefaultTextStyle( // Default with the inactive state.
+            style: CupertinoTheme.of(context).textTheme.tabLabelTextStyle.copyWith(color: inactive),
+            child: Padding(
+              padding: EdgeInsets.only(bottom: bottomPadding),
+              child: Semantics(
+                explicitChildNodes: true,
+                child: Row(
+                  // Align bottom since we want the labels to be aligned.
+                  crossAxisAlignment: CrossAxisAlignment.end,
+                  children: _buildTabItems(context),
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    if (!opaque(context)) {
+      // For non-opaque backgrounds, apply a blur effect.
+      result = ClipRect(
+        child: BackdropFilter(
+          filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
+          child: result,
+        ),
+      );
+    }
+
+    return result;
+  }
+
+  List<Widget> _buildTabItems(BuildContext context) {
+    final List<Widget> result = <Widget>[];
+    final CupertinoLocalizations localizations = CupertinoLocalizations.of(context);
+
+    for (int index = 0; index < items.length; index += 1) {
+      final bool active = index == currentIndex;
+      result.add(
+        _wrapActiveItem(
+          context,
+          Expanded(
+            child: Semantics(
+              selected: active,
+              hint: localizations.tabSemanticsLabel(
+                tabIndex: index + 1,
+                tabCount: items.length,
+              ),
+              child: GestureDetector(
+                behavior: HitTestBehavior.opaque,
+                onTap: onTap == null ? null : () { onTap!(index); },
+                child: Padding(
+                  padding: const EdgeInsets.only(bottom: 4.0),
+                  child: Column(
+                    mainAxisAlignment: MainAxisAlignment.end,
+                    children: _buildSingleTabItem(items[index], active),
+                  ),
+                ),
+              ),
+            ),
+          ),
+          active: active,
+        ),
+      );
+    }
+
+    return result;
+  }
+
+  List<Widget> _buildSingleTabItem(BottomNavigationBarItem item, bool active) {
+    return <Widget>[
+      Expanded(
+        child: Center(child: active ? item.activeIcon : item.icon),
+      ),
+      if (item.title != null) item.title!,
+      if (item.label != null) Text(item.label!),
+    ];
+  }
+
+  /// Change the active tab item's icon and title colors to active.
+  Widget _wrapActiveItem(BuildContext context, Widget item, { required bool active }) {
+    if (!active)
+      return item;
+
+    final Color activeColor = CupertinoDynamicColor.resolve(
+      this.activeColor ?? CupertinoTheme.of(context).primaryColor,
+      context,
+    );
+    return IconTheme.merge(
+      data: IconThemeData(color: activeColor),
+      child: DefaultTextStyle.merge(
+        style: TextStyle(color: activeColor),
+        child: item,
+      ),
+    );
+  }
+
+  /// Create a clone of the current [CupertinoTabBar] but with provided
+  /// parameters overridden.
+  CupertinoTabBar copyWith({
+    Key? key,
+    List<BottomNavigationBarItem>? items,
+    Color? backgroundColor,
+    Color? activeColor,
+    Color? inactiveColor,
+    double? iconSize,
+    Border? border,
+    int? currentIndex,
+    ValueChanged<int>? onTap,
+  }) {
+    return CupertinoTabBar(
+      key: key ?? this.key,
+      items: items ?? this.items,
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      activeColor: activeColor ?? this.activeColor,
+      inactiveColor: inactiveColor ?? this.inactiveColor,
+      iconSize: iconSize ?? this.iconSize,
+      border: border ?? this.border,
+      currentIndex: currentIndex ?? this.currentIndex,
+      onTap: onTap ?? this.onTap,
+    );
+  }
+}
diff --git a/lib/src/cupertino/button.dart b/lib/src/cupertino/button.dart
new file mode 100644
index 0000000..144e4e7
--- /dev/null
+++ b/lib/src/cupertino/button.dart
@@ -0,0 +1,273 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'constants.dart';
+import 'theme.dart';
+
+// Measured against iOS 12 in Xcode.
+const EdgeInsets _kButtonPadding = EdgeInsets.all(16.0);
+const EdgeInsets _kBackgroundButtonPadding = EdgeInsets.symmetric(
+  vertical: 14.0,
+  horizontal: 64.0,
+);
+
+/// An iOS-style button.
+///
+/// Takes in a text or an icon that fades out and in on touch. May optionally have a
+/// background.
+///
+/// The [padding] defaults to 16.0 pixels. When using a [CupertinoButton] within
+/// a fixed height parent, like a [CupertinoNavigationBar], a smaller, or even
+/// [EdgeInsets.zero], should be used to prevent clipping larger [child]
+/// widgets.
+///
+/// See also:
+///
+///  * <https://developer.apple.com/ios/human-interface-guidelines/controls/buttons/>
+class CupertinoButton extends StatefulWidget {
+  /// Creates an iOS-style button.
+  const CupertinoButton({
+    Key? key,
+    required this.child,
+    this.padding,
+    this.color,
+    this.disabledColor = CupertinoColors.quaternarySystemFill,
+    this.minSize = kMinInteractiveDimensionCupertino,
+    this.pressedOpacity = 0.4,
+    this.borderRadius = const BorderRadius.all(Radius.circular(8.0)),
+    required this.onPressed,
+  }) : assert(pressedOpacity == null || (pressedOpacity >= 0.0 && pressedOpacity <= 1.0)),
+       assert(disabledColor != null),
+       _filled = false,
+       super(key: key);
+
+  /// Creates an iOS-style button with a filled background.
+  ///
+  /// The background color is derived from the [CupertinoTheme]'s `primaryColor`.
+  ///
+  /// To specify a custom background color, use the [color] argument of the
+  /// default constructor.
+  const CupertinoButton.filled({
+    Key? key,
+    required this.child,
+    this.padding,
+    this.disabledColor = CupertinoColors.quaternarySystemFill,
+    this.minSize = kMinInteractiveDimensionCupertino,
+    this.pressedOpacity = 0.4,
+    this.borderRadius = const BorderRadius.all(Radius.circular(8.0)),
+    required this.onPressed,
+  }) : assert(pressedOpacity == null || (pressedOpacity >= 0.0 && pressedOpacity <= 1.0)),
+       assert(disabledColor != null),
+       color = null,
+       _filled = true,
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// Typically a [Text] widget.
+  final Widget child;
+
+  /// The amount of space to surround the child inside the bounds of the button.
+  ///
+  /// Defaults to 16.0 pixels.
+  final EdgeInsetsGeometry? padding;
+
+  /// The color of the button's background.
+  ///
+  /// Defaults to null which produces a button with no background or border.
+  ///
+  /// Defaults to the [CupertinoTheme]'s `primaryColor` when the
+  /// [CupertinoButton.filled] constructor is used.
+  final Color? color;
+
+  /// The color of the button's background when the button is disabled.
+  ///
+  /// Ignored if the [CupertinoButton] doesn't also have a [color].
+  ///
+  /// Defaults to [CupertinoColors.quaternarySystemFill] when [color] is
+  /// specified. Must not be null.
+  final Color disabledColor;
+
+  /// The callback that is called when the button is tapped or otherwise activated.
+  ///
+  /// If this is set to null, the button will be disabled.
+  final VoidCallback? onPressed;
+
+  /// Minimum size of the button.
+  ///
+  /// Defaults to kMinInteractiveDimensionCupertino which the iOS Human
+  /// Interface Guidelines recommends as the minimum tappable area.
+  final double? minSize;
+
+  /// The opacity that the button will fade to when it is pressed.
+  /// The button will have an opacity of 1.0 when it is not pressed.
+  ///
+  /// This defaults to 0.4. If null, opacity will not change on pressed if using
+  /// your own custom effects is desired.
+  final double? pressedOpacity;
+
+  /// The radius of the button's corners when it has a background color.
+  ///
+  /// Defaults to round corners of 8 logical pixels.
+  final BorderRadius? borderRadius;
+
+  final bool _filled;
+
+  /// Whether the button is enabled or disabled. Buttons are disabled by default. To
+  /// enable a button, set its [onPressed] property to a non-null value.
+  bool get enabled => onPressed != null;
+
+  @override
+  _CupertinoButtonState createState() => _CupertinoButtonState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('enabled', value: enabled, ifFalse: 'disabled'));
+  }
+}
+
+class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProviderStateMixin {
+  // Eyeballed values. Feel free to tweak.
+  static const Duration kFadeOutDuration = Duration(milliseconds: 10);
+  static const Duration kFadeInDuration = Duration(milliseconds: 100);
+  final Tween<double> _opacityTween = Tween<double>(begin: 1.0);
+
+  late AnimationController _animationController;
+  late Animation<double> _opacityAnimation;
+
+  @override
+  void initState() {
+    super.initState();
+    _animationController = AnimationController(
+      duration: const Duration(milliseconds: 200),
+      value: 0.0,
+      vsync: this,
+    );
+    _opacityAnimation = _animationController
+      .drive(CurveTween(curve: Curves.decelerate))
+      .drive(_opacityTween);
+    _setTween();
+  }
+
+  @override
+  void didUpdateWidget(CupertinoButton old) {
+    super.didUpdateWidget(old);
+    _setTween();
+  }
+
+  void _setTween() {
+    _opacityTween.end = widget.pressedOpacity ?? 1.0;
+  }
+
+  @override
+  void dispose() {
+    _animationController.dispose();
+    super.dispose();
+  }
+
+  bool _buttonHeldDown = false;
+
+  void _handleTapDown(TapDownDetails event) {
+    if (!_buttonHeldDown) {
+      _buttonHeldDown = true;
+      _animate();
+    }
+  }
+
+  void _handleTapUp(TapUpDetails event) {
+    if (_buttonHeldDown) {
+      _buttonHeldDown = false;
+      _animate();
+    }
+  }
+
+  void _handleTapCancel() {
+    if (_buttonHeldDown) {
+      _buttonHeldDown = false;
+      _animate();
+    }
+  }
+
+  void _animate() {
+    if (_animationController.isAnimating)
+      return;
+    final bool wasHeldDown = _buttonHeldDown;
+    final TickerFuture ticker = _buttonHeldDown
+        ? _animationController.animateTo(1.0, duration: kFadeOutDuration)
+        : _animationController.animateTo(0.0, duration: kFadeInDuration);
+    ticker.then<void>((void value) {
+      if (mounted && wasHeldDown != _buttonHeldDown)
+        _animate();
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final bool enabled = widget.enabled;
+    final CupertinoThemeData themeData = CupertinoTheme.of(context);
+    final Color primaryColor = themeData.primaryColor;
+    final Color? backgroundColor = widget.color == null
+      ? (widget._filled ? primaryColor : null)
+      : CupertinoDynamicColor.maybeResolve(widget.color, context);
+
+    final Color foregroundColor = backgroundColor != null
+      ? themeData.primaryContrastingColor
+      : enabled
+        ? primaryColor
+        : CupertinoDynamicColor.resolve(CupertinoColors.placeholderText, context);
+
+    final TextStyle textStyle = themeData.textTheme.textStyle.copyWith(color: foregroundColor);
+
+    return GestureDetector(
+      behavior: HitTestBehavior.opaque,
+      onTapDown: enabled ? _handleTapDown : null,
+      onTapUp: enabled ? _handleTapUp : null,
+      onTapCancel: enabled ? _handleTapCancel : null,
+      onTap: widget.onPressed,
+      child: Semantics(
+        button: true,
+        child: ConstrainedBox(
+          constraints: widget.minSize == null
+            ? const BoxConstraints()
+            : BoxConstraints(
+                minWidth: widget.minSize!,
+                minHeight: widget.minSize!,
+              ),
+          child: FadeTransition(
+            opacity: _opacityAnimation,
+            child: DecoratedBox(
+              decoration: BoxDecoration(
+                borderRadius: widget.borderRadius,
+                color: backgroundColor != null && !enabled
+                  ? CupertinoDynamicColor.resolve(widget.disabledColor, context)
+                  : backgroundColor,
+              ),
+              child: Padding(
+                padding: widget.padding ?? (backgroundColor != null
+                  ? _kBackgroundButtonPadding
+                  : _kButtonPadding),
+                child: Center(
+                  widthFactor: 1.0,
+                  heightFactor: 1.0,
+                  child: DefaultTextStyle(
+                    style: textStyle,
+                    child: IconTheme(
+                      data: IconThemeData(color: foregroundColor),
+                      child: widget.child,
+                    ),
+                  ),
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/cupertino/colors.dart b/lib/src/cupertino/colors.dart
new file mode 100644
index 0000000..f78876a
--- /dev/null
+++ b/lib/src/cupertino/colors.dart
@@ -0,0 +1,1138 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show Color, Brightness;
+
+import '../../foundation.dart';
+import '../widgets/basic.dart';
+import '../widgets/framework.dart';
+import '../widgets/media_query.dart';
+import 'interface_level.dart';
+import 'theme.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// Widget child;
+// BuildContext context;
+
+/// A palette of [Color] constants that describe colors commonly used when
+/// matching the iOS platform aesthetics.
+class CupertinoColors {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  CupertinoColors._();
+
+  /// iOS 13's default blue color. Used to indicate active elements such as
+  /// buttons, selected tabs and your own chat bubbles.
+  ///
+  /// This is SystemBlue in the iOS palette.
+  static const CupertinoDynamicColor activeBlue = systemBlue;
+
+  /// iOS 13's default green color. Used to indicate active accents such as
+  /// the switch in its on state and some accent buttons such as the call button
+  /// and Apple Map's 'Go' button.
+  ///
+  /// This is SystemGreen in the iOS palette.
+  static const CupertinoDynamicColor activeGreen = systemGreen;
+
+  /// iOS 13's orange color.
+  ///
+  /// This is SystemOrange in the iOS palette.
+  static const CupertinoDynamicColor activeOrange = systemOrange;
+
+  /// Opaque white color. Used for backgrounds and fonts against dark backgrounds.
+  ///
+  /// This is SystemWhiteColor in the iOS palette.
+  ///
+  /// See also:
+  ///
+  ///  * [material.Colors.white], the same color, in the material design palette.
+  ///  * [black], opaque black in the [CupertinoColors] palette.
+  static const Color white = Color(0xFFFFFFFF);
+
+  /// Opaque black color. Used for texts against light backgrounds.
+  ///
+  /// This is SystemBlackColor in the iOS palette.
+  ///
+  /// See also:
+  ///
+  ///  * [material.Colors.black], the same color, in the material design palette.
+  ///  * [white], opaque white in the [CupertinoColors] palette.
+  static const Color black = Color(0xFF000000);
+
+  /// Used in iOS 10 for light background fills such as the chat bubble background.
+  ///
+  /// This is SystemLightGrayColor in the iOS palette.
+  static const Color lightBackgroundGray = Color(0xFFE5E5EA);
+
+  /// Used in iOS 12 for very light background fills in tables between cell groups.
+  ///
+  /// This is SystemExtraLightGrayColor in the iOS palette.
+  static const Color extraLightBackgroundGray = Color(0xFFEFEFF4);
+
+  /// Used in iOS 12 for very dark background fills in tables between cell groups
+  /// in dark mode.
+  // Value derived from screenshot from the dark themed Apple Watch app.
+  static const Color darkBackgroundGray = Color(0xFF171717);
+
+  /// Used in iOS 13 for unselected selectables such as tab bar items in their
+  /// inactive state or de-emphasized subtitles and details text.
+  ///
+  /// Not the same grey as disabled buttons etc.
+  ///
+  /// This is the disabled color in the iOS palette.
+  static const CupertinoDynamicColor inactiveGray = CupertinoDynamicColor.withBrightness(
+    debugLabel: 'inactiveGray',
+    color: Color(0xFF999999),
+    darkColor: Color(0xFF757575),
+  );
+
+  /// Used for iOS 13 for destructive actions such as the delete actions in
+  /// table view cells and dialogs.
+  ///
+  /// Not the same red as the camera shutter or springboard icon notifications
+  /// or the foreground red theme in various native apps such as HealthKit.
+  ///
+  /// This is SystemRed in the iOS palette.
+  static const Color destructiveRed = systemRed;
+
+  /// A blue color that can adapt to the given [BuildContext].
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemBlue](https://developer.apple.com/documentation/uikit/uicolor/3173141-systemblue),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemBlue = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemBlue',
+    color: Color.fromARGB(255, 0, 122, 255),
+    darkColor: Color.fromARGB(255, 10, 132, 255),
+    highContrastColor: Color.fromARGB(255, 0, 64, 221),
+    darkHighContrastColor: Color.fromARGB(255, 64, 156, 255),
+  );
+
+  /// A green color that can adapt to the given [BuildContext].
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemGreen](https://developer.apple.com/documentation/uikit/uicolor/3173144-systemgreen),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemGreen = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemGreen',
+    color: Color.fromARGB(255, 52, 199, 89),
+    darkColor: Color.fromARGB(255, 48, 209, 88),
+    highContrastColor: Color.fromARGB(255, 36, 138, 61),
+    darkHighContrastColor: Color.fromARGB(255, 48, 219, 91),
+  );
+
+  /// An indigo color that can adapt to the given [BuildContext].
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemIndigo](https://developer.apple.com/documentation/uikit/uicolor/3173146-systemindigo),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemIndigo = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemIndigo',
+    color: Color.fromARGB(255, 88, 86, 214),
+    darkColor: Color.fromARGB(255, 94, 92, 230),
+    highContrastColor: Color.fromARGB(255, 54, 52, 163),
+    darkHighContrastColor: Color.fromARGB(255, 125, 122, 255),
+  );
+
+  /// An orange color that can adapt to the given [BuildContext].
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemOrange](https://developer.apple.com/documentation/uikit/uicolor/3173147-systemorange),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemOrange = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemOrange',
+    color: Color.fromARGB(255, 255, 149, 0),
+    darkColor: Color.fromARGB(255, 255, 159, 10),
+    highContrastColor: Color.fromARGB(255, 201, 52, 0),
+    darkHighContrastColor: Color.fromARGB(255, 255, 179, 64),
+  );
+
+  /// A pink color that can adapt to the given [BuildContext].
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemPink](https://developer.apple.com/documentation/uikit/uicolor/3173148-systempink),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemPink = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemPink',
+    color: Color.fromARGB(255, 255, 45, 85),
+    darkColor: Color.fromARGB(255, 255, 55, 95),
+    highContrastColor: Color.fromARGB(255, 211, 15, 69),
+    darkHighContrastColor: Color.fromARGB(255, 255, 100, 130),
+  );
+
+  /// A purple color that can adapt to the given [BuildContext].
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemPurple](https://developer.apple.com/documentation/uikit/uicolor/3173149-systempurple),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemPurple = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemPurple',
+    color: Color.fromARGB(255, 175, 82, 222),
+    darkColor: Color.fromARGB(255, 191, 90, 242),
+    highContrastColor: Color.fromARGB(255, 137, 68, 171),
+    darkHighContrastColor: Color.fromARGB(255, 218, 143, 255),
+  );
+
+  /// A red color that can adapt to the given [BuildContext].
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemRed](https://developer.apple.com/documentation/uikit/uicolor/3173150-systemred),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemRed = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemRed',
+    color: Color.fromARGB(255, 255, 59, 48),
+    darkColor: Color.fromARGB(255, 255, 69, 58),
+    highContrastColor: Color.fromARGB(255, 215, 0, 21),
+    darkHighContrastColor: Color.fromARGB(255, 255, 105, 97),
+  );
+
+  /// A teal color that can adapt to the given [BuildContext].
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemTeal](https://developer.apple.com/documentation/uikit/uicolor/3173151-systemteal),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemTeal = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemTeal',
+    color: Color.fromARGB(255, 90, 200, 250),
+    darkColor: Color.fromARGB(255, 100, 210, 255),
+    highContrastColor: Color.fromARGB(255, 0, 113, 164),
+    darkHighContrastColor: Color.fromARGB(255, 112, 215, 255),
+  );
+
+  /// A yellow color that can adapt to the given [BuildContext].
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemYellow](https://developer.apple.com/documentation/uikit/uicolor/3173152-systemyellow),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemYellow = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemYellow',
+    color: Color.fromARGB(255, 255, 204, 0),
+    darkColor: Color.fromARGB(255, 255, 214, 10),
+    highContrastColor: Color.fromARGB(255, 160, 90, 0),
+    darkHighContrastColor: Color.fromARGB(255, 255, 212, 38),
+  );
+
+  /// The base grey color.
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemGray](https://developer.apple.com/documentation/uikit/uicolor/3173143-systemgray),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemGrey = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemGrey',
+    color: Color.fromARGB(255, 142, 142, 147),
+    darkColor: Color.fromARGB(255, 142, 142, 147),
+    highContrastColor: Color.fromARGB(255, 108, 108, 112),
+    darkHighContrastColor: Color.fromARGB(255, 174, 174, 178),
+  );
+
+  /// A second-level shade of grey.
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemGray2](https://developer.apple.com/documentation/uikit/uicolor/3255071-systemgray2),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemGrey2 = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemGrey2',
+    color: Color.fromARGB(255, 174, 174, 178),
+    darkColor: Color.fromARGB(255, 99, 99, 102),
+    highContrastColor: Color.fromARGB(255, 142, 142, 147),
+    darkHighContrastColor: Color.fromARGB(255, 124, 124, 128),
+  );
+
+  /// A third-level shade of grey.
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemGray3](https://developer.apple.com/documentation/uikit/uicolor/3255072-systemgray3),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemGrey3 = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemGrey3',
+    color: Color.fromARGB(255, 199, 199, 204),
+    darkColor: Color.fromARGB(255, 72, 72, 74),
+    highContrastColor: Color.fromARGB(255, 174, 174, 178),
+    darkHighContrastColor: Color.fromARGB(255, 84, 84, 86),
+  );
+
+  /// A fourth-level shade of grey.
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemGray4](https://developer.apple.com/documentation/uikit/uicolor/3255073-systemgray4),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemGrey4 = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemGrey4',
+    color: Color.fromARGB(255, 209, 209, 214),
+    darkColor: Color.fromARGB(255, 58, 58, 60),
+    highContrastColor: Color.fromARGB(255, 188, 188, 192),
+    darkHighContrastColor: Color.fromARGB(255, 68, 68, 70),
+  );
+
+  /// A fifth-level shade of grey.
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemGray5](https://developer.apple.com/documentation/uikit/uicolor/3255074-systemgray5),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemGrey5 = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemGrey5',
+    color: Color.fromARGB(255, 229, 229, 234),
+    darkColor: Color.fromARGB(255, 44, 44, 46),
+    highContrastColor: Color.fromARGB(255, 216, 216, 220),
+    darkHighContrastColor: Color.fromARGB(255, 54, 54, 56),
+  );
+
+  /// A sixth-level shade of grey.
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemGray6](https://developer.apple.com/documentation/uikit/uicolor/3255075-systemgray6),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemGrey6 = CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemGrey6',
+    color: Color.fromARGB(255, 242, 242, 247),
+    darkColor: Color.fromARGB(255, 28, 28, 30),
+    highContrastColor: Color.fromARGB(255, 235, 235, 240),
+    darkHighContrastColor: Color.fromARGB(255, 36, 36, 38),
+  );
+
+  /// The color for text labels containing primary content, equivalent to
+  /// [UIColor.label](https://developer.apple.com/documentation/uikit/uicolor/3173131-label).
+  static const CupertinoDynamicColor label = CupertinoDynamicColor(
+    debugLabel: 'label',
+    color: Color.fromARGB(255, 0, 0, 0),
+    darkColor: Color.fromARGB(255, 255, 255, 255),
+    highContrastColor: Color.fromARGB(255, 0, 0, 0),
+    darkHighContrastColor: Color.fromARGB(255, 255, 255, 255),
+    elevatedColor: Color.fromARGB(255, 0, 0, 0),
+    darkElevatedColor: Color.fromARGB(255, 255, 255, 255),
+    highContrastElevatedColor: Color.fromARGB(255, 0, 0, 0),
+    darkHighContrastElevatedColor: Color.fromARGB(255, 255, 255, 255),
+  );
+
+  /// The color for text labels containing secondary content, equivalent to
+  /// [UIColor.secondaryLabel](https://developer.apple.com/documentation/uikit/uicolor/3173136-secondarylabel).
+  static const CupertinoDynamicColor secondaryLabel = CupertinoDynamicColor(
+    debugLabel: 'secondaryLabel',
+    color: Color.fromARGB(153, 60, 60, 67),
+    darkColor: Color.fromARGB(153, 235, 235, 245),
+    highContrastColor: Color.fromARGB(173, 60, 60, 67),
+    darkHighContrastColor: Color.fromARGB(173, 235, 235, 245),
+    elevatedColor: Color.fromARGB(153, 60, 60, 67),
+    darkElevatedColor: Color.fromARGB(153, 235, 235, 245),
+    highContrastElevatedColor: Color.fromARGB(173, 60, 60, 67),
+    darkHighContrastElevatedColor: Color.fromARGB(173, 235, 235, 245),
+);
+
+  /// The color for text labels containing tertiary content, equivalent to
+  /// [UIColor.tertiaryLabel](https://developer.apple.com/documentation/uikit/uicolor/3173153-tertiarylabel).
+  static const CupertinoDynamicColor tertiaryLabel = CupertinoDynamicColor(
+    debugLabel: 'tertiaryLabel',
+    color: Color.fromARGB(76, 60, 60, 67),
+    darkColor: Color.fromARGB(76, 235, 235, 245),
+    highContrastColor: Color.fromARGB(96, 60, 60, 67),
+    darkHighContrastColor: Color.fromARGB(96, 235, 235, 245),
+    elevatedColor: Color.fromARGB(76, 60, 60, 67),
+    darkElevatedColor: Color.fromARGB(76, 235, 235, 245),
+    highContrastElevatedColor: Color.fromARGB(96, 60, 60, 67),
+    darkHighContrastElevatedColor: Color.fromARGB(96, 235, 235, 245),
+  );
+
+  /// The color for text labels containing quaternary content, equivalent to
+  /// [UIColor.quaternaryLabel](https://developer.apple.com/documentation/uikit/uicolor/3173135-quaternarylabel).
+  static const CupertinoDynamicColor quaternaryLabel = CupertinoDynamicColor(
+    debugLabel: 'quaternaryLabel',
+    color: Color.fromARGB(45, 60, 60, 67),
+    darkColor: Color.fromARGB(40, 235, 235, 245),
+    highContrastColor: Color.fromARGB(66, 60, 60, 67),
+    darkHighContrastColor: Color.fromARGB(61, 235, 235, 245),
+    elevatedColor: Color.fromARGB(45, 60, 60, 67),
+    darkElevatedColor: Color.fromARGB(40, 235, 235, 245),
+    highContrastElevatedColor: Color.fromARGB(66, 60, 60, 67),
+    darkHighContrastElevatedColor: Color.fromARGB(61, 235, 235, 245),
+  );
+
+  /// An overlay fill color for thin and small shapes, equivalent to
+  /// [UIColor.systemFill](https://developer.apple.com/documentation/uikit/uicolor/3255070-systemfill).
+  static const CupertinoDynamicColor systemFill = CupertinoDynamicColor(
+    debugLabel: 'systemFill',
+    color: Color.fromARGB(51, 120, 120, 128),
+    darkColor: Color.fromARGB(91, 120, 120, 128),
+    highContrastColor: Color.fromARGB(71, 120, 120, 128),
+    darkHighContrastColor: Color.fromARGB(112, 120, 120, 128),
+    elevatedColor: Color.fromARGB(51, 120, 120, 128),
+    darkElevatedColor: Color.fromARGB(91, 120, 120, 128),
+    highContrastElevatedColor: Color.fromARGB(71, 120, 120, 128),
+    darkHighContrastElevatedColor: Color.fromARGB(112, 120, 120, 128),
+  );
+
+  /// An overlay fill color for medium-size shapes, equivalent to
+  /// [UIColor.secondarySystemFill](https://developer.apple.com/documentation/uikit/uicolor/3255069-secondarysystemfill).
+  static const CupertinoDynamicColor secondarySystemFill = CupertinoDynamicColor(
+    debugLabel: 'secondarySystemFill',
+    color: Color.fromARGB(40, 120, 120, 128),
+    darkColor: Color.fromARGB(81, 120, 120, 128),
+    highContrastColor: Color.fromARGB(61, 120, 120, 128),
+    darkHighContrastColor: Color.fromARGB(102, 120, 120, 128),
+    elevatedColor: Color.fromARGB(40, 120, 120, 128),
+    darkElevatedColor: Color.fromARGB(81, 120, 120, 128),
+    highContrastElevatedColor: Color.fromARGB(61, 120, 120, 128),
+    darkHighContrastElevatedColor: Color.fromARGB(102, 120, 120, 128),
+  );
+
+  /// An overlay fill color for large shapes, equivalent to
+  /// [UIColor.tertiarySystemFill](https://developer.apple.com/documentation/uikit/uicolor/3255076-tertiarysystemfill).
+  static const CupertinoDynamicColor tertiarySystemFill = CupertinoDynamicColor(
+    debugLabel: 'tertiarySystemFill',
+    color: Color.fromARGB(30, 118, 118, 128),
+    darkColor: Color.fromARGB(61, 118, 118, 128),
+    highContrastColor: Color.fromARGB(51, 118, 118, 128),
+    darkHighContrastColor: Color.fromARGB(81, 118, 118, 128),
+    elevatedColor: Color.fromARGB(30, 118, 118, 128),
+    darkElevatedColor: Color.fromARGB(61, 118, 118, 128),
+    highContrastElevatedColor: Color.fromARGB(51, 118, 118, 128),
+    darkHighContrastElevatedColor: Color.fromARGB(81, 118, 118, 128),
+  );
+
+  /// An overlay fill color for large areas containing complex content, equivalent
+  /// to [UIColor.quaternarySystemFill](https://developer.apple.com/documentation/uikit/uicolor/3255068-quaternarysystemfill).
+  static const CupertinoDynamicColor quaternarySystemFill = CupertinoDynamicColor(
+    debugLabel: 'quaternarySystemFill',
+    color: Color.fromARGB(20, 116, 116, 128),
+    darkColor: Color.fromARGB(45, 118, 118, 128),
+    highContrastColor: Color.fromARGB(40, 116, 116, 128),
+    darkHighContrastColor: Color.fromARGB(66, 118, 118, 128),
+    elevatedColor: Color.fromARGB(20, 116, 116, 128),
+    darkElevatedColor: Color.fromARGB(45, 118, 118, 128),
+    highContrastElevatedColor: Color.fromARGB(40, 116, 116, 128),
+    darkHighContrastElevatedColor: Color.fromARGB(66, 118, 118, 128),
+  );
+
+  /// The color for placeholder text in controls or text views, equivalent to
+  /// [UIColor.placeholderText](https://developer.apple.com/documentation/uikit/uicolor/3173134-placeholdertext).
+  static const CupertinoDynamicColor placeholderText = CupertinoDynamicColor(
+    debugLabel: 'placeholderText',
+    color: Color.fromARGB(76, 60, 60, 67),
+    darkColor: Color.fromARGB(76, 235, 235, 245),
+    highContrastColor: Color.fromARGB(96, 60, 60, 67),
+    darkHighContrastColor: Color.fromARGB(96, 235, 235, 245),
+    elevatedColor: Color.fromARGB(76, 60, 60, 67),
+    darkElevatedColor: Color.fromARGB(76, 235, 235, 245),
+    highContrastElevatedColor: Color.fromARGB(96, 60, 60, 67),
+    darkHighContrastElevatedColor: Color.fromARGB(96, 235, 235, 245),
+  );
+
+  /// The color for the main background of your interface, equivalent to
+  /// [UIColor.systemBackground](https://developer.apple.com/documentation/uikit/uicolor/3173140-systembackground).
+  ///
+  /// Typically used for designs that have a white primary background in a light environment.
+  static const CupertinoDynamicColor systemBackground = CupertinoDynamicColor(
+    debugLabel: 'systemBackground',
+    color: Color.fromARGB(255, 255, 255, 255),
+    darkColor: Color.fromARGB(255, 0, 0, 0),
+    highContrastColor: Color.fromARGB(255, 255, 255, 255),
+    darkHighContrastColor: Color.fromARGB(255, 0, 0, 0),
+    elevatedColor: Color.fromARGB(255, 255, 255, 255),
+    darkElevatedColor: Color.fromARGB(255, 28, 28, 30),
+    highContrastElevatedColor: Color.fromARGB(255, 255, 255, 255),
+    darkHighContrastElevatedColor: Color.fromARGB(255, 36, 36, 38),
+  );
+
+  /// The color for content layered on top of the main background, equivalent to
+  /// [UIColor.secondarySystemBackground](https://developer.apple.com/documentation/uikit/uicolor/3173137-secondarysystembackground).
+  ///
+  /// Typically used for designs that have a white primary background in a light environment.
+  static const CupertinoDynamicColor secondarySystemBackground = CupertinoDynamicColor(
+    debugLabel: 'secondarySystemBackground',
+    color: Color.fromARGB(255, 242, 242, 247),
+    darkColor: Color.fromARGB(255, 28, 28, 30),
+    highContrastColor: Color.fromARGB(255, 235, 235, 240),
+    darkHighContrastColor: Color.fromARGB(255, 36, 36, 38),
+    elevatedColor: Color.fromARGB(255, 242, 242, 247),
+    darkElevatedColor: Color.fromARGB(255, 44, 44, 46),
+    highContrastElevatedColor: Color.fromARGB(255, 235, 235, 240),
+    darkHighContrastElevatedColor: Color.fromARGB(255, 54, 54, 56),
+  );
+
+  /// The color for content layered on top of secondary backgrounds, equivalent
+  /// to [UIColor.tertiarySystemBackground](https://developer.apple.com/documentation/uikit/uicolor/3173154-tertiarysystembackground).
+  ///
+  /// Typically used for designs that have a white primary background in a light environment.
+  static const CupertinoDynamicColor tertiarySystemBackground = CupertinoDynamicColor(
+    debugLabel: 'tertiarySystemBackground',
+    color: Color.fromARGB(255, 255, 255, 255),
+    darkColor: Color.fromARGB(255, 44, 44, 46),
+    highContrastColor: Color.fromARGB(255, 255, 255, 255),
+    darkHighContrastColor: Color.fromARGB(255, 54, 54, 56),
+    elevatedColor: Color.fromARGB(255, 255, 255, 255),
+    darkElevatedColor: Color.fromARGB(255, 58, 58, 60),
+    highContrastElevatedColor: Color.fromARGB(255, 255, 255, 255),
+    darkHighContrastElevatedColor: Color.fromARGB(255, 68, 68, 70),
+  );
+
+  /// The color for the main background of your grouped interface, equivalent to
+  /// [UIColor.systemGroupedBackground](https://developer.apple.com/documentation/uikit/uicolor/3173145-systemgroupedbackground).
+  ///
+  /// Typically used for grouped content, including table views and platter-based designs.
+  static const CupertinoDynamicColor systemGroupedBackground = CupertinoDynamicColor(
+    debugLabel: 'systemGroupedBackground',
+    color: Color.fromARGB(255, 242, 242, 247),
+    darkColor: Color.fromARGB(255, 0, 0, 0),
+    highContrastColor: Color.fromARGB(255, 235, 235, 240),
+    darkHighContrastColor: Color.fromARGB(255, 0, 0, 0),
+    elevatedColor: Color.fromARGB(255, 242, 242, 247),
+    darkElevatedColor: Color.fromARGB(255, 28, 28, 30),
+    highContrastElevatedColor: Color.fromARGB(255, 235, 235, 240),
+    darkHighContrastElevatedColor: Color.fromARGB(255, 36, 36, 38),
+  );
+
+  /// The color for content layered on top of the main background of your grouped interface,
+  /// equivalent to [UIColor.secondarySystemGroupedBackground](https://developer.apple.com/documentation/uikit/uicolor/3173138-secondarysystemgroupedbackground).
+  ///
+  /// Typically used for grouped content, including table views and platter-based designs.
+  static const CupertinoDynamicColor secondarySystemGroupedBackground = CupertinoDynamicColor(
+    debugLabel: 'secondarySystemGroupedBackground',
+    color: Color.fromARGB(255, 255, 255, 255),
+    darkColor: Color.fromARGB(255, 28, 28, 30),
+    highContrastColor: Color.fromARGB(255, 255, 255, 255),
+    darkHighContrastColor: Color.fromARGB(255, 36, 36, 38),
+    elevatedColor: Color.fromARGB(255, 255, 255, 255),
+    darkElevatedColor: Color.fromARGB(255, 44, 44, 46),
+    highContrastElevatedColor: Color.fromARGB(255, 255, 255, 255),
+    darkHighContrastElevatedColor: Color.fromARGB(255, 54, 54, 56),
+  );
+
+  /// The color for content layered on top of secondary backgrounds of your grouped interface,
+  /// equivalent to [UIColor.tertiarySystemGroupedBackground](https://developer.apple.com/documentation/uikit/uicolor/3173155-tertiarysystemgroupedbackground).
+  ///
+  /// Typically used for grouped content, including table views and platter-based designs.
+  static const CupertinoDynamicColor tertiarySystemGroupedBackground = CupertinoDynamicColor(
+    debugLabel: 'tertiarySystemGroupedBackground',
+    color: Color.fromARGB(255, 242, 242, 247),
+    darkColor: Color.fromARGB(255, 44, 44, 46),
+    highContrastColor: Color.fromARGB(255, 235, 235, 240),
+    darkHighContrastColor: Color.fromARGB(255, 54, 54, 56),
+    elevatedColor: Color.fromARGB(255, 242, 242, 247),
+    darkElevatedColor: Color.fromARGB(255, 58, 58, 60),
+    highContrastElevatedColor: Color.fromARGB(255, 235, 235, 240),
+    darkHighContrastElevatedColor: Color.fromARGB(255, 68, 68, 70),
+  );
+
+  /// The color for thin borders or divider lines that allows some underlying content to be visible,
+  /// equivalent to [UIColor.separator](https://developer.apple.com/documentation/uikit/uicolor/3173139-separator).
+  static const CupertinoDynamicColor separator = CupertinoDynamicColor(
+    debugLabel: 'separator',
+    color: Color.fromARGB(73, 60, 60, 67),
+    darkColor: Color.fromARGB(153, 84, 84, 88),
+    highContrastColor: Color.fromARGB(94, 60, 60, 67),
+    darkHighContrastColor: Color.fromARGB(173, 84, 84, 88),
+    elevatedColor: Color.fromARGB(73, 60, 60, 67),
+    darkElevatedColor: Color.fromARGB(153, 84, 84, 88),
+    highContrastElevatedColor: Color.fromARGB(94, 60, 60, 67),
+    darkHighContrastElevatedColor: Color.fromARGB(173, 84, 84, 88),
+  );
+
+  /// The color for borders or divider lines that hide any underlying content,
+  /// equivalent to [UIColor.opaqueSeparator](https://developer.apple.com/documentation/uikit/uicolor/3173133-opaqueseparator).
+  static const CupertinoDynamicColor opaqueSeparator = CupertinoDynamicColor(
+    debugLabel: 'opaqueSeparator',
+    color: Color.fromARGB(255, 198, 198, 200),
+    darkColor: Color.fromARGB(255, 56, 56, 58),
+    highContrastColor: Color.fromARGB(255, 198, 198, 200),
+    darkHighContrastColor: Color.fromARGB(255, 56, 56, 58),
+    elevatedColor: Color.fromARGB(255, 198, 198, 200),
+    darkElevatedColor: Color.fromARGB(255, 56, 56, 58),
+    highContrastElevatedColor: Color.fromARGB(255, 198, 198, 200),
+    darkHighContrastElevatedColor: Color.fromARGB(255, 56, 56, 58),
+  );
+
+  /// The color for links, equivalent to
+  /// [UIColor.link](https://developer.apple.com/documentation/uikit/uicolor/3173132-link).
+  static const CupertinoDynamicColor link = CupertinoDynamicColor(
+    debugLabel: 'link',
+    color: Color.fromARGB(255, 0, 122, 255),
+    darkColor: Color.fromARGB(255, 9, 132, 255),
+    highContrastColor: Color.fromARGB(255, 0, 122, 255),
+    darkHighContrastColor: Color.fromARGB(255, 9, 132, 255),
+    elevatedColor: Color.fromARGB(255, 0, 122, 255),
+    darkElevatedColor: Color.fromARGB(255, 9, 132, 255),
+    highContrastElevatedColor: Color.fromARGB(255, 0, 122, 255),
+    darkHighContrastElevatedColor: Color.fromARGB(255, 9, 132, 255),
+  );
+}
+
+/// A [Color] subclass that represents a family of colors, and the correct effective
+/// color in the color family.
+///
+/// When used as a regular color, [CupertinoDynamicColor] is equivalent to the
+/// effective color (i.e. [CupertinoDynamicColor.value] will come from the effective
+/// color), which is determined by the [BuildContext] it is last resolved against.
+/// If it has never been resolved, the light, normal contrast, base elevation variant
+/// [CupertinoDynamicColor.color] will be the default effective color.
+///
+/// Sometimes manually resolving a [CupertinoDynamicColor] is not necessary, because
+/// the Cupertino Library provides built-in support for it.
+///
+/// ### Using [CupertinoDynamicColor] in a Cupertino widget
+///
+/// When a Cupertino widget is provided with a [CupertinoDynamicColor], either
+/// directly in its constructor, or from an [InheritedWidget] it depends on (for example,
+/// [DefaultTextStyle]), the widget will automatically resolve the color using
+/// [CupertinoDynamicColor.resolve] against its own [BuildContext], on a best-effort
+/// basis.
+///
+/// {@tool snippet}
+/// By default a [CupertinoButton] has no background color. The following sample
+/// code shows how to build a [CupertinoButton] that appears white in light mode,
+/// and changes automatically to black in dark mode.
+///
+/// ```dart
+/// CupertinoButton(
+///   child: child,
+///   // CupertinoDynamicColor works out of box in a CupertinoButton.
+///   color: CupertinoDynamicColor.withBrightness(
+///     color: CupertinoColors.white,
+///     darkColor: CupertinoColors.black,
+///   ),
+///   onPressed: () { },
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ### Using a [CupertinoDynamicColor] from a [CupertinoTheme]
+///
+/// When referring to a [CupertinoTheme] color, generally the color will already
+/// have adapted to the ambient [BuildContext], because [CupertinoTheme.of]
+/// implicitly resolves all the colors used in the retrieved [CupertinoThemeData],
+/// before returning it.
+///
+/// {@tool snippet}
+/// The following code sample creates a [Container] with the `primaryColor` of the
+/// current theme. If `primaryColor` is a [CupertinoDynamicColor], the container
+/// will be adaptive, thanks to [CupertinoTheme.of]: it will switch to `primaryColor`'s
+/// dark variant once dark mode is turned on, and turns to primaryColor`'s high
+/// contrast variant when [MediaQueryData.highContrast] is requested in the ambient
+/// [MediaQuery], etc.
+///
+/// ```dart
+/// Container(
+///   // Container is not a Cupertino widget, but CupertinoTheme.of implicitly
+///   // resolves colors used in the retrieved CupertinoThemeData.
+///   color: CupertinoTheme.of(context).primaryColor,
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ### Manually Resolving a [CupertinoDynamicColor]
+///
+/// When used to configure a non-Cupertino widget, or wrapped in an object opaque
+/// to the receiving Cupertino component, a [CupertinoDynamicColor] may need to be
+/// manually resolved using [CupertinoDynamicColor.resolve], before it can used
+/// to paint. For example, to use a custom [Border] in a [CupertinoNavigationBar],
+/// the colors used in the [Border] have to be resolved manually before being passed
+/// to [CupertinoNavigationBar]'s constructor.
+///
+/// {@tool snippet}
+///
+/// The following code samples demonstrate two cases where you have to manually
+/// resolve a [CupertinoDynamicColor].
+///
+/// ```dart
+/// CupertinoNavigationBar(
+///   // CupertinoNavigationBar does not know how to resolve colors used in
+///   // a Border class.
+///   border: Border(
+///     bottom: BorderSide(
+///       color: CupertinoDynamicColor.resolve(CupertinoColors.systemBlue, context),
+///     ),
+///   ),
+/// )
+/// ```
+///
+/// ```dart
+/// Container(
+///   // Container is not a Cupertino widget.
+///   color: CupertinoDynamicColor.resolve(CupertinoColors.systemBlue, context),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [CupertinoUserInterfaceLevel], an [InheritedWidget] that may affect color
+///    resolution of a [CupertinoDynamicColor].
+///  * [CupertinoTheme.of], a static method that retrieves the ambient [CupertinoThemeData],
+///    and then resolves [CupertinoDynamicColor]s used in the retrieved data.
+@immutable
+class CupertinoDynamicColor extends Color with Diagnosticable {
+  /// Creates an adaptive [Color] that changes its effective color based on the
+  /// [BuildContext] given. The default effective color is [color].
+  ///
+  /// All the colors must not be null.
+  const CupertinoDynamicColor({
+    String? debugLabel,
+    required Color color,
+    required Color darkColor,
+    required Color highContrastColor,
+    required Color darkHighContrastColor,
+    required Color elevatedColor,
+    required Color darkElevatedColor,
+    required Color highContrastElevatedColor,
+    required Color darkHighContrastElevatedColor,
+  }) : this._(
+         color,
+         color,
+         darkColor,
+         highContrastColor,
+         darkHighContrastColor,
+         elevatedColor,
+         darkElevatedColor,
+         highContrastElevatedColor,
+         darkHighContrastElevatedColor,
+         null,
+         debugLabel,
+       );
+
+  /// Creates an adaptive [Color] that changes its effective color based on the
+  /// given [BuildContext]'s brightness (from [MediaQueryData.platformBrightness]
+  /// or [CupertinoThemeData.brightness]) and accessibility contrast setting
+  /// ([MediaQueryData.highContrast]). The default effective color is [color].
+  ///
+  /// All the colors must not be null.
+  const CupertinoDynamicColor.withBrightnessAndContrast({
+    String? debugLabel,
+    required Color color,
+    required Color darkColor,
+    required Color highContrastColor,
+    required Color darkHighContrastColor,
+  }) : this(
+    debugLabel: debugLabel,
+    color: color,
+    darkColor: darkColor,
+    highContrastColor: highContrastColor,
+    darkHighContrastColor: darkHighContrastColor,
+    elevatedColor: color,
+    darkElevatedColor: darkColor,
+    highContrastElevatedColor: highContrastColor,
+    darkHighContrastElevatedColor: darkHighContrastColor,
+  );
+
+  /// Creates an adaptive [Color] that changes its effective color based on the given
+  /// [BuildContext]'s brightness (from [MediaQueryData.platformBrightness] or
+  /// [CupertinoThemeData.brightness]). The default effective color is [color].
+  ///
+  /// All the colors must not be null.
+  const CupertinoDynamicColor.withBrightness({
+    String? debugLabel,
+    required Color color,
+    required Color darkColor,
+  }) : this(
+    debugLabel: debugLabel,
+    color: color,
+    darkColor: darkColor,
+    highContrastColor: color,
+    darkHighContrastColor: darkColor,
+    elevatedColor: color,
+    darkElevatedColor: darkColor,
+    highContrastElevatedColor: color,
+    darkHighContrastElevatedColor: darkColor,
+  );
+
+  const CupertinoDynamicColor._(
+    this._effectiveColor,
+    this.color,
+    this.darkColor,
+    this.highContrastColor,
+    this.darkHighContrastColor,
+    this.elevatedColor,
+    this.darkElevatedColor,
+    this.highContrastElevatedColor,
+    this.darkHighContrastElevatedColor,
+    this._debugResolveContext,
+    this._debugLabel,
+  ) : assert(color != null),
+      assert(darkColor != null),
+      assert(highContrastColor != null),
+      assert(darkHighContrastColor != null),
+      assert(elevatedColor != null),
+      assert(darkElevatedColor != null),
+      assert(highContrastElevatedColor != null),
+      assert(darkHighContrastElevatedColor != null),
+      assert(_effectiveColor != null),
+      // The super constructor has to be called with a dummy value in order to mark
+      // this constructor const.
+      // The field `value` is overridden in the class implementation.
+      super(0);
+
+  /// The current effective color.
+  ///
+  /// Must not be null. Defaults to [color] if this [CupertinoDynamicColor] has
+  /// never been resolved.
+  final Color _effectiveColor;
+
+  @override
+  int get value => _effectiveColor.value;
+
+  final String? _debugLabel;
+
+  final Element? _debugResolveContext;
+
+  /// The color to use when the [BuildContext] implies a combination of light mode,
+  /// normal contrast, and base interface elevation.
+  ///
+  /// In other words, this color will be the effective color of the [CupertinoDynamicColor]
+  /// after it is resolved against a [BuildContext] that:
+  /// - has a [CupertinoTheme] whose [CupertinoThemeData.brightness] is [Brightness.light],
+  /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [Brightness.light].
+  /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `false`.
+  /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.base].
+  final Color color;
+
+  /// The color to use when the [BuildContext] implies a combination of dark mode,
+  /// normal contrast, and base interface elevation.
+  ///
+  /// In other words, this color will be the effective color of the [CupertinoDynamicColor]
+  /// after it is resolved against a [BuildContext] that:
+  /// - has a [CupertinoTheme] whose [CupertinoThemeData.brightness] is [Brightness.dark],
+  /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [Brightness.dark].
+  /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `false`.
+  /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.base].
+  final Color darkColor;
+
+  /// The color to use when the [BuildContext] implies a combination of light mode,
+  /// high contrast, and base interface elevation.
+  ///
+  /// In other words, this color will be the effective color of the [CupertinoDynamicColor]
+  /// after it is resolved against a [BuildContext] that:
+  /// - has a [CupertinoTheme] whose [CupertinoThemeData.brightness] is [Brightness.light],
+  /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [Brightness.light].
+  /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `true`.
+  /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.base].
+  final Color highContrastColor;
+
+  /// The color to use when the [BuildContext] implies a combination of dark mode,
+  /// high contrast, and base interface elevation.
+  ///
+  /// In other words, this color will be the effective color of the [CupertinoDynamicColor]
+  /// after it is resolved against a [BuildContext] that:
+  /// - has a [CupertinoTheme] whose [CupertinoThemeData.brightness] is [Brightness.dark],
+  /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [Brightness.dark].
+  /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `true`.
+  /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.base].
+  final Color darkHighContrastColor;
+
+  /// The color to use when the [BuildContext] implies a combination of light mode,
+  /// normal contrast, and elevated interface elevation.
+  ///
+  /// In other words, this color will be the effective color of the [CupertinoDynamicColor]
+  /// after it is resolved against a [BuildContext] that:
+  /// - has a [CupertinoTheme] whose [CupertinoThemeData.brightness] is [Brightness.light],
+  /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [Brightness.light].
+  /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `false`.
+  /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.elevated].
+  final Color elevatedColor;
+
+  /// The color to use when the [BuildContext] implies a combination of dark mode,
+  /// normal contrast, and elevated interface elevation.
+  ///
+  /// In other words, this color will be the effective color of the [CupertinoDynamicColor]
+  /// after it is resolved against a [BuildContext] that:
+  /// - has a [CupertinoTheme] whose [CupertinoThemeData.brightness] is [Brightness.dark],
+  /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [Brightness.dark].
+  /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `false`.
+  /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.elevated].
+  final Color darkElevatedColor;
+
+  /// The color to use when the [BuildContext] implies a combination of light mode,
+  /// high contrast, and elevated interface elevation.
+  ///
+  /// In other words, this color will be the effective color of the [CupertinoDynamicColor]
+  /// after it is resolved against a [BuildContext] that:
+  /// - has a [CupertinoTheme] whose [CupertinoThemeData.brightness] is [Brightness.light],
+  /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [Brightness.light].
+  /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `true`.
+  /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.elevated].
+  final Color highContrastElevatedColor;
+
+  /// The color to use when the [BuildContext] implies a combination of dark mode,
+  /// high contrast, and elevated interface elevation.
+  ///
+  /// In other words, this color will be the effective color of the [CupertinoDynamicColor]
+  /// after it is resolved against a [BuildContext] that:
+  /// - has a [CupertinoTheme] whose [CupertinoThemeData.brightness] is [Brightness.dark],
+  /// or a [MediaQuery] whose [MediaQueryData.platformBrightness] is [Brightness.dark].
+  /// - has a [MediaQuery] whose [MediaQueryData.highContrast] is `true`.
+  /// - has a [CupertinoUserInterfaceLevel] that indicates [CupertinoUserInterfaceLevelData.elevated].
+  final Color darkHighContrastElevatedColor;
+
+  /// Resolves the given [Color] by calling [resolveFrom].
+  ///
+  /// If the given color is already a concrete [Color], it will be returned as is.
+  /// If the given color is a [CupertinoDynamicColor], but the given [BuildContext]
+  /// lacks the dependencies required to the color resolution, the default trait
+  /// value will be used ([Brightness.light] platform brightness, normal contrast,
+  /// [CupertinoUserInterfaceLevelData.base] elevation level).
+  ///
+  /// See also:
+  ///
+  ///  * [maybeResolve], which is similar to this function, but will allow a
+  ///    null `resolvable` color.
+  static Color resolve(Color resolvable, BuildContext context) {
+    assert(context != null);
+    return (resolvable is CupertinoDynamicColor)
+      ? resolvable.resolveFrom(context)
+      : resolvable;
+  }
+
+  /// Resolves the given [Color] by calling [resolveFrom].
+  ///
+  /// If the given color is already a concrete [Color], it will be returned as is.
+  /// If the given color is null, returns null.
+  /// If the given color is a [CupertinoDynamicColor], but the given [BuildContext]
+  /// lacks the dependencies required to the color resolution, the default trait
+  /// value will be used ([Brightness.light] platform brightness, normal contrast,
+  /// [CupertinoUserInterfaceLevelData.base] elevation level).
+  ///
+  /// See also:
+  ///
+  ///  * [resolve], which is similar to this function, but returns a
+  ///    non-nullable value, and does not allow a null `resolvable` color.
+  static Color? maybeResolve(Color? resolvable, BuildContext context) {
+    if (resolvable == null)
+      return null;
+    assert(context != null);
+    return (resolvable is CupertinoDynamicColor)
+      ? resolvable.resolveFrom(context)
+      : resolvable;
+  }
+
+  bool get _isPlatformBrightnessDependent {
+    return color != darkColor
+        || elevatedColor != darkElevatedColor
+        || highContrastColor != darkHighContrastColor
+        || highContrastElevatedColor != darkHighContrastElevatedColor;
+  }
+
+  bool get _isHighContrastDependent {
+    return color != highContrastColor
+        || darkColor != darkHighContrastColor
+        || elevatedColor != highContrastElevatedColor
+        || darkElevatedColor != darkHighContrastElevatedColor;
+  }
+
+  bool get _isInterfaceElevationDependent {
+    return color != elevatedColor
+        || darkColor != darkElevatedColor
+        || highContrastColor != highContrastElevatedColor
+        || darkHighContrastColor != darkHighContrastElevatedColor;
+  }
+
+  /// Resolves this [CupertinoDynamicColor] using the provided [BuildContext].
+  ///
+  /// Calling this method will create a new [CupertinoDynamicColor] that is almost
+  /// identical to this [CupertinoDynamicColor], except the effective color is
+  /// changed to adapt to the given [BuildContext].
+  ///
+  /// For example, if the given [BuildContext] indicates the widgets in the
+  /// subtree should be displayed in dark mode (the surrounding
+  /// [CupertinoTheme]'s [CupertinoThemeData.brightness] or [MediaQuery]'s
+  /// [MediaQueryData.platformBrightness] is [Brightness.dark]), with a high
+  /// accessibility contrast (the surrounding [MediaQuery]'s
+  /// [MediaQueryData.highContrast] is `true`), and an elevated interface
+  /// elevation (the surrounding [CupertinoUserInterfaceLevel]'s `data` is
+  /// [CupertinoUserInterfaceLevelData.elevated]), the resolved
+  /// [CupertinoDynamicColor] will be the same as this [CupertinoDynamicColor],
+  /// except its effective color will be the `darkHighContrastElevatedColor`
+  /// variant from the original [CupertinoDynamicColor].
+  ///
+  /// Calling this function may create dependencies on the closest instance of some
+  /// [InheritedWidget]s that enclose the given [BuildContext]. E.g., if [darkColor]
+  /// is different from [color], this method will call [CupertinoTheme.of], and
+  /// then [MediaQuery.of] if brightness wasn't specified in the theme data retrieved
+  /// from the previous [CupertinoTheme.of] call, in an effort to determine the
+  /// brightness value.
+  ///
+  /// If any of the required dependencies are missing from the given context, the
+  /// default value of that trait will be used ([Brightness.light] platform
+  /// brightness, normal contrast, [CupertinoUserInterfaceLevelData.base] elevation
+  /// level).
+  CupertinoDynamicColor resolveFrom(BuildContext context) {
+    Brightness brightness = Brightness.light;
+    if (_isPlatformBrightnessDependent) {
+      brightness =  CupertinoTheme.maybeBrightnessOf(context) ?? Brightness.light;
+    }
+    bool isHighContrastEnabled = false;
+    if (_isHighContrastDependent) {
+      isHighContrastEnabled = MediaQuery.maybeOf(context)?.highContrast ?? false;
+    }
+
+    final CupertinoUserInterfaceLevelData level = _isInterfaceElevationDependent
+      ? CupertinoUserInterfaceLevel.maybeOf(context) ?? CupertinoUserInterfaceLevelData.base
+      : CupertinoUserInterfaceLevelData.base;
+
+    final Color resolved;
+    switch (brightness) {
+      case Brightness.light:
+        switch (level) {
+          case CupertinoUserInterfaceLevelData.base:
+            resolved = isHighContrastEnabled ? highContrastColor : color;
+            break;
+          case CupertinoUserInterfaceLevelData.elevated:
+            resolved = isHighContrastEnabled ? highContrastElevatedColor : elevatedColor;
+            break;
+        }
+        break;
+      case Brightness.dark:
+        switch (level) {
+          case CupertinoUserInterfaceLevelData.base:
+            resolved = isHighContrastEnabled ? darkHighContrastColor : darkColor;
+            break;
+          case CupertinoUserInterfaceLevelData.elevated:
+            resolved = isHighContrastEnabled ? darkHighContrastElevatedColor : darkElevatedColor;
+            break;
+        }
+    }
+
+    Element? _debugContext;
+    assert(() {
+      _debugContext = context as Element;
+      return true;
+    }());
+    return CupertinoDynamicColor._(
+      resolved,
+      color,
+      darkColor,
+      highContrastColor,
+      darkHighContrastColor,
+      elevatedColor,
+      darkElevatedColor,
+      highContrastElevatedColor,
+      darkHighContrastElevatedColor,
+      _debugContext,
+      _debugLabel,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is CupertinoDynamicColor
+        && other.value == value
+        && other.color == color
+        && other.darkColor == darkColor
+        && other.highContrastColor == highContrastColor
+        && other.darkHighContrastColor == darkHighContrastColor
+        && other.elevatedColor == elevatedColor
+        && other.darkElevatedColor == darkElevatedColor
+        && other.highContrastElevatedColor == highContrastElevatedColor
+        && other.darkHighContrastElevatedColor == darkHighContrastElevatedColor;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      value,
+      color,
+      darkColor,
+      highContrastColor,
+      elevatedColor,
+      darkElevatedColor,
+      darkHighContrastColor,
+      darkHighContrastElevatedColor,
+      highContrastElevatedColor,
+    );
+  }
+
+  @override
+  String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
+    String toString(String name, Color color) {
+      final String marker = color == _effectiveColor ? '*' : '';
+      return '$marker$name = $color$marker';
+    }
+
+    final List<String> xs = <String>[toString('color', color),
+      if (_isPlatformBrightnessDependent) toString('darkColor', darkColor),
+      if (_isHighContrastDependent) toString('highContrastColor', highContrastColor),
+      if (_isPlatformBrightnessDependent && _isHighContrastDependent) toString('darkHighContrastColor', darkHighContrastColor),
+      if (_isInterfaceElevationDependent) toString('elevatedColor', elevatedColor),
+      if (_isPlatformBrightnessDependent && _isInterfaceElevationDependent) toString('darkElevatedColor', darkElevatedColor),
+      if (_isHighContrastDependent && _isInterfaceElevationDependent) toString('highContrastElevatedColor', highContrastElevatedColor),
+      if (_isPlatformBrightnessDependent && _isHighContrastDependent && _isInterfaceElevationDependent) toString('darkHighContrastElevatedColor', darkHighContrastElevatedColor),
+    ];
+
+    return '${_debugLabel ?? objectRuntimeType(this, 'CupertinoDynamicColor')}(${xs.join(', ')}, resolved by: ${_debugResolveContext?.widget ?? "UNRESOLVED"})';
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    if (_debugLabel != null)
+      properties.add(MessageProperty('debugLabel', _debugLabel!));
+    properties.add(createCupertinoColorProperty('color', color));
+    if (_isPlatformBrightnessDependent)
+      properties.add(createCupertinoColorProperty('darkColor', darkColor));
+    if (_isHighContrastDependent)
+      properties.add(createCupertinoColorProperty('highContrastColor', highContrastColor));
+    if (_isPlatformBrightnessDependent && _isHighContrastDependent)
+      properties.add(createCupertinoColorProperty('darkHighContrastColor', darkHighContrastColor));
+    if (_isInterfaceElevationDependent)
+      properties.add(createCupertinoColorProperty('elevatedColor', elevatedColor));
+    if (_isPlatformBrightnessDependent && _isInterfaceElevationDependent)
+      properties.add(createCupertinoColorProperty('darkElevatedColor', darkElevatedColor));
+    if (_isHighContrastDependent && _isInterfaceElevationDependent)
+      properties.add(createCupertinoColorProperty('highContrastElevatedColor', highContrastElevatedColor));
+    if (_isPlatformBrightnessDependent && _isHighContrastDependent && _isInterfaceElevationDependent)
+      properties.add(createCupertinoColorProperty('darkHighContrastElevatedColor', darkHighContrastElevatedColor));
+
+    if (_debugResolveContext != null)
+      properties.add(DiagnosticsProperty<Element>('last resolved', _debugResolveContext));
+  }
+}
+
+/// Creates a diagnostics property for [CupertinoDynamicColor].
+///
+/// The [showName], [style], and [level] arguments must not be null.
+DiagnosticsProperty<Color> createCupertinoColorProperty(
+  String name,
+  Color? value, {
+  bool showName = true,
+  Object? defaultValue = kNoDefaultValue,
+  DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
+  DiagnosticLevel level = DiagnosticLevel.info,
+}) {
+  if (value is CupertinoDynamicColor) {
+    return DiagnosticsProperty<CupertinoDynamicColor>(
+      name,
+      value,
+      description: value._debugLabel,
+      showName: showName,
+      defaultValue: defaultValue,
+      style: style,
+      level: level,
+    );
+  } else {
+    return ColorProperty(
+      name,
+      value,
+      showName: showName,
+      defaultValue: defaultValue,
+      style: style,
+      level: level,
+    );
+  }
+}
diff --git a/lib/src/cupertino/constants.dart b/lib/src/cupertino/constants.dart
new file mode 100644
index 0000000..2831c50
--- /dev/null
+++ b/lib/src/cupertino/constants.dart
@@ -0,0 +1,17 @@
+// Copyright 2014 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.
+
+/// The minimum dimension of any interactive region according to the iOS Human
+/// Interface Guidelines.
+///
+/// This is used to avoid small regions that are hard for the user to interact
+/// with. It applies to both dimensions of a region, so a square of size
+/// kMinInteractiveDimension x kMinInteractiveDimension is the smallest
+/// acceptable region that should respond to gestures.
+///
+/// See also:
+///
+///  * [kMinInteractiveDimension]
+///  * <https://developer.apple.com/ios/human-interface-guidelines/visual-design/adaptivity-and-layout/>
+const double kMinInteractiveDimensionCupertino = 44.0;
diff --git a/lib/src/cupertino/context_menu.dart b/lib/src/cupertino/context_menu.dart
new file mode 100644
index 0000000..7ae7cc8
--- /dev/null
+++ b/lib/src/cupertino/context_menu.dart
@@ -0,0 +1,1290 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' as ui;
+import 'package:flute/gestures.dart' show kMinFlingVelocity, kLongPressTimeout;
+import 'package:flute/foundation.dart' show kIsWeb;
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+// The scale of the child at the time that the CupertinoContextMenu opens.
+// This value was eyeballed from a physical device running iOS 13.1.2.
+const double _kOpenScale = 1.1;
+
+typedef _DismissCallback = void Function(
+  BuildContext context,
+  double scale,
+  double opacity,
+);
+
+/// A function that produces the preview when the CupertinoContextMenu is open.
+///
+/// Called every time the animation value changes.
+typedef ContextMenuPreviewBuilder = Widget Function(
+  BuildContext context,
+  Animation<double> animation,
+  Widget child,
+);
+
+// A function that proxies to ContextMenuPreviewBuilder without the child.
+typedef _ContextMenuPreviewBuilderChildless = Widget Function(
+  BuildContext context,
+  Animation<double> animation,
+);
+
+// Given a GlobalKey, return the Rect of the corresponding RenderBox's
+// paintBounds in global coordinates.
+Rect _getRect(GlobalKey globalKey) {
+  assert(globalKey.currentContext != null);
+  final RenderBox renderBoxContainer = globalKey.currentContext!.findRenderObject()! as RenderBox;
+  final Offset containerOffset = renderBoxContainer.localToGlobal(
+    renderBoxContainer.paintBounds.topLeft,
+  );
+  return containerOffset & renderBoxContainer.paintBounds.size;
+}
+
+// The context menu arranges itself slightly differently based on the location
+// on the screen of [CupertinoContextMenu.child] before the
+// [CupertinoContextMenu] opens.
+enum _ContextMenuLocation {
+  center,
+  left,
+  right,
+}
+
+/// A full-screen modal route that opens when the [child] is long-pressed.
+///
+/// When open, the [CupertinoContextMenu] shows the child, or the widget returned
+/// by [previewBuilder] if given, in a large full-screen [Overlay] with a list
+/// of buttons specified by [actions]. The child/preview is placed in an
+/// [Expanded] widget so that it will grow to fill the Overlay if its size is
+/// unconstrained.
+///
+/// When closed, the CupertinoContextMenu simply displays the child as if the
+/// CupertinoContextMenu were not there. Sizing and positioning is unaffected.
+/// The menu can be closed like other [PopupRoute]s, such as by tapping the
+/// background or by calling `Navigator.pop(context)`. Unlike PopupRoute, it can
+/// also be closed by swiping downwards.
+///
+/// The [previewBuilder] parameter is most commonly used to display a slight
+/// variation of [child]. See [previewBuilder] for an example of rounding the
+/// child's corners and allowing its aspect ratio to expand, similar to the
+/// Photos app on iOS.
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+///
+/// This sample shows a very simple CupertinoContextMenu for an empty red
+/// 100x100 Container. Simply long press on it to open.
+///
+/// ```dart imports
+/// import 'package:flute/cupertino.dart';
+/// ```
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: Center(
+///       child: Container(
+///         width: 100,
+///         height: 100,
+///         child: CupertinoContextMenu(
+///           child: Container(
+///             color: Colors.red,
+///           ),
+///           actions: <Widget>[
+///             CupertinoContextMenuAction(
+///               child: const Text('Action one'),
+///               onPressed: () {
+///                 Navigator.pop(context);
+///               },
+///             ),
+///             CupertinoContextMenuAction(
+///               child: const Text('Action two'),
+///               onPressed: () {
+///                 Navigator.pop(context);
+///               },
+///             ),
+///           ],
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Apple's HIG for Context Menus](https://developer.apple.com/design/human-interface-guidelines/ios/controls/context-menus/)
+class CupertinoContextMenu extends StatefulWidget {
+  /// Create a context menu.
+  ///
+  /// [actions] is required and cannot be null or empty.
+  ///
+  /// [child] is required and cannot be null.
+  CupertinoContextMenu({
+    Key? key,
+    required this.actions,
+    required this.child,
+    this.previewBuilder,
+  }) : assert(actions != null && actions.isNotEmpty),
+       assert(child != null),
+       super(key: key);
+
+  /// The widget that can be "opened" with the [CupertinoContextMenu].
+  ///
+  /// When the [CupertinoContextMenu] is long-pressed, the menu will open and
+  /// this widget (or the widget returned by [previewBuilder], if provided) will
+  /// be moved to the new route and placed inside of an [Expanded] widget. This
+  /// allows the child to resize to fit in its place in the new route, if it
+  /// doesn't size itself.
+  ///
+  /// When the [CupertinoContextMenu] is "closed", this widget acts like a
+  /// [Container], i.e. it does not constrain its child's size or affect its
+  /// position.
+  ///
+  /// This parameter cannot be null.
+  final Widget child;
+
+  /// The actions that are shown in the menu.
+  ///
+  /// These actions are typically [CupertinoContextMenuAction]s.
+  ///
+  /// This parameter cannot be null or empty.
+  final List<Widget> actions;
+
+  /// A function that returns an alternative widget to show when the
+  /// [CupertinoContextMenu] is open.
+  ///
+  /// If not specified, [child] will be shown.
+  ///
+  /// The preview is often used to show a slight variation of the [child]. For
+  /// example, the child could be given rounded corners in the preview but have
+  /// sharp corners when in the page.
+  ///
+  /// In addition to the current [BuildContext], the function is also called
+  /// with an [Animation] and the [child]. The animation goes from 0 to 1 when
+  /// the CupertinoContextMenu opens, and from 1 to 0 when it closes, and it can
+  /// be used to animate the preview in sync with this opening and closing. The
+  /// child parameter provides access to the child displayed when the
+  /// CupertinoContextMenu is closed.
+  ///
+  /// {@tool snippet}
+  ///
+  /// Below is an example of using `previewBuilder` to show an image tile that's
+  /// similar to each tile in the iOS iPhoto app's context menu. Several of
+  /// these could be used in a GridView for a similar effect.
+  ///
+  /// When opened, the child animates to show its full aspect ratio and has
+  /// rounded corners. The larger size of the open CupertinoContextMenu allows
+  /// the FittedBox to fit the entire image, even when it has a very tall or
+  /// wide aspect ratio compared to the square of a GridView, so this animates
+  /// into view as the CupertinoContextMenu is opened. The preview is swapped in
+  /// right when the open animation begins, which includes the rounded corners.
+  ///
+  /// ```dart
+  /// CupertinoContextMenu(
+  ///   child: FittedBox(
+  ///     fit: BoxFit.cover,
+  ///     child: Image.asset('assets/photo.jpg'),
+  ///   ),
+  ///   // The FittedBox in the preview here allows the image to animate its
+  ///   // aspect ratio when the CupertinoContextMenu is animating its preview
+  ///   // widget open and closed.
+  ///   previewBuilder: (BuildContext context, Animation<double> animation, Widget child) {
+  ///     return FittedBox(
+  ///       fit: BoxFit.cover,
+  ///       // This ClipRRect rounds the corners of the image when the
+  ///       // CupertinoContextMenu is open, even though it's not rounded when
+  ///       // it's closed. It uses the given animation to animate the corners
+  ///       // in sync with the opening animation.
+  ///       child: ClipRRect(
+  ///         borderRadius: BorderRadius.circular(64.0 * animation.value),
+  ///         child: Image.asset('assets/photo.jpg'),
+  ///       ),
+  ///     );
+  ///   },
+  ///   actions: <Widget>[
+  ///     CupertinoContextMenuAction(
+  ///       child: const Text('Action one'),
+  ///       onPressed: () {},
+  ///     ),
+  ///   ],
+  /// ),
+  /// ```
+  ///
+  /// {@end-tool}
+  final ContextMenuPreviewBuilder? previewBuilder;
+
+  @override
+  _CupertinoContextMenuState createState() => _CupertinoContextMenuState();
+}
+
+class _CupertinoContextMenuState extends State<CupertinoContextMenu> with TickerProviderStateMixin {
+  final GlobalKey _childGlobalKey = GlobalKey();
+  bool _childHidden = false;
+  // Animates the child while it's opening.
+  late AnimationController _openController;
+  Rect? _decoyChildEndRect;
+  OverlayEntry? _lastOverlayEntry;
+  _ContextMenuRoute<void>? _route;
+
+  @override
+  void initState() {
+    super.initState();
+    _openController = AnimationController(
+      duration: kLongPressTimeout,
+      vsync: this,
+    );
+    _openController.addStatusListener(_onDecoyAnimationStatusChange);
+  }
+
+  // Determine the _ContextMenuLocation based on the location of the original
+  // child in the screen.
+  //
+  // The location of the original child is used to determine how to horizontally
+  // align the content of the open CupertinoContextMenu. For example, if the
+  // child is near the center of the screen, it will also appear in the center
+  // of the screen when the menu is open, and the actions will be centered below
+  // it.
+  _ContextMenuLocation get _contextMenuLocation {
+    final Rect childRect = _getRect(_childGlobalKey);
+    final double screenWidth = MediaQuery.of(context).size.width;
+
+    final double center = screenWidth / 2;
+    final bool centerDividesChild = childRect.left < center
+      && childRect.right > center;
+    final double distanceFromCenter = (center - childRect.center.dx).abs();
+    if (centerDividesChild && distanceFromCenter <= childRect.width / 4) {
+      return _ContextMenuLocation.center;
+    }
+
+    if (childRect.center.dx > center) {
+      return _ContextMenuLocation.right;
+    }
+
+    return _ContextMenuLocation.left;
+  }
+
+  // Push the new route and open the CupertinoContextMenu overlay.
+  void _openContextMenu() {
+    setState(() {
+      _childHidden = true;
+    });
+
+    _route = _ContextMenuRoute<void>(
+      actions: widget.actions,
+      barrierLabel: 'Dismiss',
+      filter: ui.ImageFilter.blur(
+        sigmaX: 5.0,
+        sigmaY: 5.0,
+      ),
+      contextMenuLocation: _contextMenuLocation,
+      previousChildRect: _decoyChildEndRect!,
+      builder: (BuildContext context, Animation<double> animation) {
+        if (widget.previewBuilder == null) {
+          return widget.child;
+        }
+        return widget.previewBuilder!(context, animation, widget.child);
+      },
+    );
+    Navigator.of(context, rootNavigator: true).push<void>(_route!);
+    _route!.animation!.addStatusListener(_routeAnimationStatusListener);
+  }
+
+  void _onDecoyAnimationStatusChange(AnimationStatus animationStatus) {
+    switch (animationStatus) {
+      case AnimationStatus.dismissed:
+        if (_route == null) {
+          setState(() {
+            _childHidden = false;
+          });
+        }
+        _lastOverlayEntry?.remove();
+        _lastOverlayEntry = null;
+        break;
+
+      case AnimationStatus.completed:
+        setState(() {
+          _childHidden = true;
+        });
+        _openContextMenu();
+        // Keep the decoy on the screen for one extra frame. We have to do this
+        // because _ContextMenuRoute renders its first frame offscreen.
+        // Otherwise there would be a visible flash when nothing is rendered for
+        // one frame.
+        SchedulerBinding.instance!.addPostFrameCallback((Duration _) {
+          _lastOverlayEntry?.remove();
+          _lastOverlayEntry = null;
+          _openController.reset();
+        });
+        break;
+
+      default:
+        return;
+    }
+  }
+
+  // Watch for when _ContextMenuRoute is closed and return to the state where
+  // the CupertinoContextMenu just behaves as a Container.
+  void _routeAnimationStatusListener(AnimationStatus status) {
+    if (status != AnimationStatus.dismissed) {
+      return;
+    }
+    setState(() {
+      _childHidden = false;
+    });
+    _route!.animation!.removeStatusListener(_routeAnimationStatusListener);
+    _route = null;
+  }
+
+  void _onTap() {
+    if (_openController.isAnimating && _openController.value < 0.5) {
+      _openController.reverse();
+    }
+  }
+
+  void _onTapCancel() {
+    if (_openController.isAnimating && _openController.value < 0.5) {
+      _openController.reverse();
+    }
+  }
+
+  void _onTapUp(TapUpDetails details) {
+    if (_openController.isAnimating && _openController.value < 0.5) {
+      _openController.reverse();
+    }
+  }
+
+  void _onTapDown(TapDownDetails details) {
+    setState(() {
+      _childHidden = true;
+    });
+
+    final Rect childRect = _getRect(_childGlobalKey);
+    _decoyChildEndRect = Rect.fromCenter(
+      center: childRect.center,
+      width: childRect.width * _kOpenScale,
+      height: childRect.height * _kOpenScale,
+    );
+
+    // Create a decoy child in an overlay directly on top of the original child.
+    // TODO(justinmc): There is a known inconsistency with native here, due to
+    // doing the bounce animation using a decoy in the top level Overlay. The
+    // decoy will pop on top of the AppBar if the child is partially behind it,
+    // such as a top item in a partially scrolled view. However, if we don't use
+    // an overlay, then the decoy will appear behind its neighboring widget when
+    // it expands. This may be solvable by adding a widget to Scaffold that's
+    // underneath the AppBar.
+    _lastOverlayEntry = OverlayEntry(
+      opaque: false,
+      builder: (BuildContext context) {
+        return _DecoyChild(
+          beginRect: childRect,
+          child: widget.child,
+          controller: _openController,
+          endRect: _decoyChildEndRect,
+        );
+      },
+    );
+    Overlay.of(context)!.insert(_lastOverlayEntry!);
+    _openController.forward();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return GestureDetector(
+      onTapCancel: _onTapCancel,
+      onTapDown: _onTapDown,
+      onTapUp: _onTapUp,
+      onTap: _onTap,
+      child: TickerMode(
+        enabled: !_childHidden,
+        child: Opacity(
+          key: _childGlobalKey,
+          opacity: _childHidden ? 0.0 : 1.0,
+          child: widget.child,
+        ),
+      ),
+    );
+  }
+
+  @override
+  void dispose() {
+    _openController.dispose();
+    super.dispose();
+  }
+}
+
+// A floating copy of the CupertinoContextMenu's child.
+//
+// When the child is pressed, but before the CupertinoContextMenu opens, it does
+// a "bounce" animation where it shrinks and then grows. This is implemented
+// by hiding the original child and placing _DecoyChild on top of it in an
+// Overlay. The use of an Overlay allows the _DecoyChild to appear on top of
+// siblings of the original child.
+class _DecoyChild extends StatefulWidget {
+  const _DecoyChild({
+    Key? key,
+    this.beginRect,
+    required this.controller,
+    this.endRect,
+    this.child,
+  }) : super(key: key);
+
+  final Rect? beginRect;
+  final AnimationController controller;
+  final Rect? endRect;
+  final Widget? child;
+
+  @override
+  _DecoyChildState createState() => _DecoyChildState();
+}
+
+class _DecoyChildState extends State<_DecoyChild> with TickerProviderStateMixin {
+  // TODO(justinmc): Dark mode support.
+  // See https://github.com/flutter/flutter/issues/43211.
+  static const Color _lightModeMaskColor = Color(0xFF888888);
+  static const Color _masklessColor = Color(0xFFFFFFFF);
+
+  final GlobalKey _childGlobalKey = GlobalKey();
+  late Animation<Color> _mask;
+  late Animation<Rect?> _rect;
+
+  @override
+  void initState() {
+    super.initState();
+    // Change the color of the child during the initial part of the decoy bounce
+    // animation. The interval was eyeballed from a physical iOS 13.1.2 device.
+    _mask = _OnOffAnimation<Color>(
+      controller: widget.controller,
+      onValue: _lightModeMaskColor,
+      offValue: _masklessColor,
+      intervalOn: 0.0,
+      intervalOff: 0.5,
+    );
+
+    final Rect midRect =  widget.beginRect!.deflate(
+      widget.beginRect!.width * (_kOpenScale - 1.0) / 2,
+    );
+    _rect = TweenSequence<Rect?>(<TweenSequenceItem<Rect?>>[
+      TweenSequenceItem<Rect?>(
+        tween: RectTween(
+          begin: widget.beginRect,
+          end: midRect,
+        ).chain(CurveTween(curve: Curves.easeInOutCubic)),
+        weight: 1.0,
+      ),
+      TweenSequenceItem<Rect?>(
+        tween: RectTween(
+          begin: midRect,
+          end: widget.endRect,
+        ).chain(CurveTween(curve: Curves.easeOutCubic)),
+        weight: 1.0,
+      ),
+    ]).animate(widget.controller);
+    _rect.addListener(_rectListener);
+  }
+
+  // Listen to the _rect animation and vibrate when it reaches the halfway point
+  // and switches from animating down to up.
+  void _rectListener() {
+    if (widget.controller.value < 0.5) {
+      return;
+    }
+    HapticFeedback.selectionClick();
+    _rect.removeListener(_rectListener);
+  }
+
+  @override
+  void dispose() {
+    _rect.removeListener(_rectListener);
+    super.dispose();
+  }
+
+  Widget _buildAnimation(BuildContext context, Widget? child) {
+    final Color color = widget.controller.status == AnimationStatus.reverse
+      ? _masklessColor
+      : _mask.value;
+    return Positioned.fromRect(
+      rect: _rect.value!,
+      // TODO(justinmc): When ShaderMask is supported on web, remove this
+      // conditional and use ShaderMask everywhere.
+      // https://github.com/flutter/flutter/issues/52967.
+      child: kIsWeb
+          ? Container(key: _childGlobalKey, child: widget.child)
+          : ShaderMask(
+            key: _childGlobalKey,
+            shaderCallback: (Rect bounds) {
+              return LinearGradient(
+                begin: Alignment.topLeft,
+                end: Alignment.bottomRight,
+                colors: <Color>[color, color],
+              ).createShader(bounds);
+            },
+            child: widget.child,
+          ),
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Stack(
+      children: <Widget>[
+        AnimatedBuilder(
+          builder: _buildAnimation,
+          animation: widget.controller,
+        ),
+      ],
+    );
+  }
+}
+
+// The open CupertinoContextMenu modal.
+class _ContextMenuRoute<T> extends PopupRoute<T> {
+  // Build a _ContextMenuRoute.
+  _ContextMenuRoute({
+    required List<Widget> actions,
+    required _ContextMenuLocation contextMenuLocation,
+    this.barrierLabel,
+    _ContextMenuPreviewBuilderChildless? builder,
+    ui.ImageFilter? filter,
+    required Rect previousChildRect,
+    RouteSettings? settings,
+  }) : assert(actions != null && actions.isNotEmpty),
+       assert(contextMenuLocation != null),
+       _actions = actions,
+       _builder = builder,
+       _contextMenuLocation = contextMenuLocation,
+       _previousChildRect = previousChildRect,
+       super(
+         filter: filter,
+         settings: settings,
+       );
+
+  // Barrier color for a Cupertino modal barrier.
+  static const Color _kModalBarrierColor = Color(0x6604040F);
+  // The duration of the transition used when a modal popup is shown. Eyeballed
+  // from a physical device running iOS 13.1.2.
+  static const Duration _kModalPopupTransitionDuration =
+    Duration(milliseconds: 335);
+
+  final List<Widget> _actions;
+  final _ContextMenuPreviewBuilderChildless? _builder;
+  final GlobalKey _childGlobalKey = GlobalKey();
+  final _ContextMenuLocation _contextMenuLocation;
+  bool _externalOffstage = false;
+  bool _internalOffstage = false;
+  Orientation? _lastOrientation;
+  // The Rect of the child at the moment that the CupertinoContextMenu opens.
+  final Rect _previousChildRect;
+  double? _scale = 1.0;
+  final GlobalKey _sheetGlobalKey = GlobalKey();
+
+  static final CurveTween _curve = CurveTween(
+    curve: Curves.easeOutBack,
+  );
+  static final CurveTween _curveReverse = CurveTween(
+    curve: Curves.easeInBack,
+  );
+  static final RectTween _rectTween = RectTween();
+  static final Animatable<Rect?> _rectAnimatable = _rectTween.chain(_curve);
+  static final RectTween _rectTweenReverse = RectTween();
+  static final Animatable<Rect?> _rectAnimatableReverse = _rectTweenReverse
+    .chain(
+      _curveReverse,
+    );
+  static final RectTween _sheetRectTween = RectTween();
+  final Animatable<Rect?> _sheetRectAnimatable = _sheetRectTween.chain(
+    _curve,
+  );
+  final Animatable<Rect?> _sheetRectAnimatableReverse = _sheetRectTween.chain(
+    _curveReverse,
+  );
+  static final Tween<double> _sheetScaleTween = Tween<double>();
+  static final Animatable<double> _sheetScaleAnimatable = _sheetScaleTween
+    .chain(
+      _curve,
+    );
+  static final Animatable<double> _sheetScaleAnimatableReverse =
+    _sheetScaleTween.chain(
+      _curveReverse,
+    );
+  final Tween<double> _opacityTween = Tween<double>(begin: 0.0, end: 1.0);
+  late Animation<double> _sheetOpacity;
+
+  @override
+  final String? barrierLabel;
+
+  @override
+  Color get barrierColor => _kModalBarrierColor;
+
+  @override
+  bool get barrierDismissible => true;
+
+  @override
+  bool get semanticsDismissible => false;
+
+  @override
+  Duration get transitionDuration => _kModalPopupTransitionDuration;
+
+  // Getting the RenderBox doesn't include the scale from the Transform.scale,
+  // so it's manually accounted for here.
+  static Rect _getScaledRect(GlobalKey globalKey, double scale) {
+    final Rect childRect = _getRect(globalKey);
+    final Size sizeScaled = childRect.size * scale;
+    final Offset offsetScaled = Offset(
+      childRect.left + (childRect.size.width - sizeScaled.width) / 2,
+      childRect.top + (childRect.size.height - sizeScaled.height) / 2,
+    );
+    return offsetScaled & sizeScaled;
+  }
+
+  // Get the alignment for the _ContextMenuSheet's Transform.scale based on the
+  // contextMenuLocation.
+  static AlignmentDirectional getSheetAlignment(_ContextMenuLocation contextMenuLocation) {
+    switch (contextMenuLocation) {
+      case _ContextMenuLocation.center:
+        return AlignmentDirectional.topCenter;
+      case _ContextMenuLocation.right:
+        return AlignmentDirectional.topEnd;
+      case _ContextMenuLocation.left:
+        return AlignmentDirectional.topStart;
+    }
+  }
+
+  // The place to start the sheetRect animation from.
+  static Rect _getSheetRectBegin(Orientation? orientation, _ContextMenuLocation contextMenuLocation, Rect childRect, Rect sheetRect) {
+    switch (contextMenuLocation) {
+      case _ContextMenuLocation.center:
+        final Offset target = orientation == Orientation.portrait
+          ? childRect.bottomCenter
+          : childRect.topCenter;
+        final Offset centered = target - Offset(sheetRect.width / 2, 0.0);
+        return centered & sheetRect.size;
+      case _ContextMenuLocation.right:
+        final Offset target = orientation == Orientation.portrait
+          ? childRect.bottomRight
+          : childRect.topRight;
+        return (target - Offset(sheetRect.width, 0.0)) & sheetRect.size;
+      case _ContextMenuLocation.left:
+        final Offset target = orientation == Orientation.portrait
+          ? childRect.bottomLeft
+          : childRect.topLeft;
+        return target & sheetRect.size;
+    }
+  }
+
+  void _onDismiss(BuildContext context, double scale, double opacity) {
+    _scale = scale;
+    _opacityTween.end = opacity;
+    _sheetOpacity = _opacityTween.animate(CurvedAnimation(
+      parent: animation!,
+      curve: const Interval(0.9, 1.0),
+    ));
+    Navigator.of(context).pop();
+  }
+
+  // Take measurements on the child and _ContextMenuSheet and update the
+  // animation tweens to match.
+  void _updateTweenRects() {
+    final Rect childRect = _scale == null
+      ? _getRect(_childGlobalKey)
+      : _getScaledRect(_childGlobalKey, _scale!);
+    _rectTween.begin = _previousChildRect;
+    _rectTween.end = childRect;
+
+    // When opening, the transition happens from the end of the child's bounce
+    // animation to the final state. When closing, it goes from the final state
+    // to the original position before the bounce.
+    final Rect childRectOriginal = Rect.fromCenter(
+      center: _previousChildRect.center,
+      width: _previousChildRect.width / _kOpenScale,
+      height: _previousChildRect.height / _kOpenScale,
+    );
+
+    final Rect sheetRect = _getRect(_sheetGlobalKey);
+    final Rect sheetRectBegin = _getSheetRectBegin(
+      _lastOrientation,
+      _contextMenuLocation,
+      childRectOriginal,
+      sheetRect,
+    );
+    _sheetRectTween.begin = sheetRectBegin;
+    _sheetRectTween.end = sheetRect;
+    _sheetScaleTween.begin = 0.0;
+    _sheetScaleTween.end = _scale;
+
+    _rectTweenReverse.begin = childRectOriginal;
+    _rectTweenReverse.end = childRect;
+  }
+
+  void _setOffstageInternally() {
+    super.offstage = _externalOffstage || _internalOffstage;
+    // It's necessary to call changedInternalState to get the backdrop to
+    // update.
+    changedInternalState();
+  }
+
+  @override
+  bool didPop(T? result) {
+    _updateTweenRects();
+    return super.didPop(result);
+  }
+
+  @override
+  set offstage(bool value) {
+    _externalOffstage = value;
+    _setOffstageInternally();
+  }
+
+  @override
+  TickerFuture didPush() {
+    _internalOffstage = true;
+    _setOffstageInternally();
+
+    // Render one frame offstage in the final position so that we can take
+    // measurements of its layout and then animate to them.
+    SchedulerBinding.instance!.addPostFrameCallback((Duration _) {
+      _updateTweenRects();
+      _internalOffstage = false;
+      _setOffstageInternally();
+    });
+    return super.didPush();
+  }
+
+  @override
+  Animation<double> createAnimation() {
+    final Animation<double> animation = super.createAnimation();
+    _sheetOpacity = _opacityTween.animate(CurvedAnimation(
+      parent: animation,
+      curve: Curves.linear,
+    ));
+    return animation;
+  }
+
+  @override
+  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
+    // This is usually used to build the "page", which is then passed to
+    // buildTransitions as child, the idea being that buildTransitions will
+    // animate the entire page into the scene. In the case of _ContextMenuRoute,
+    // two individual pieces of the page are animated into the scene in
+    // buildTransitions, and a Container is returned here.
+    return Container();
+  }
+
+  @override
+  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
+    return OrientationBuilder(
+      builder: (BuildContext context, Orientation orientation) {
+        _lastOrientation = orientation;
+
+        // While the animation is running, render everything in a Stack so that
+        // they're movable.
+        if (!animation.isCompleted) {
+          final bool reverse = animation.status == AnimationStatus.reverse;
+          final Rect rect = reverse
+            ? _rectAnimatableReverse.evaluate(animation)!
+            : _rectAnimatable.evaluate(animation)!;
+          final Rect sheetRect = reverse
+            ? _sheetRectAnimatableReverse.evaluate(animation)!
+            : _sheetRectAnimatable.evaluate(animation)!;
+          final double sheetScale = reverse
+            ? _sheetScaleAnimatableReverse.evaluate(animation)
+            : _sheetScaleAnimatable.evaluate(animation);
+          return Stack(
+            children: <Widget>[
+              Positioned.fromRect(
+                rect: sheetRect,
+                child: Opacity(
+                  opacity: _sheetOpacity.value,
+                  child: Transform.scale(
+                    alignment: getSheetAlignment(_contextMenuLocation),
+                    scale: sheetScale,
+                    child: _ContextMenuSheet(
+                      key: _sheetGlobalKey,
+                      actions: _actions,
+                      contextMenuLocation: _contextMenuLocation,
+                      orientation: orientation,
+                    ),
+                  ),
+                ),
+              ),
+              Positioned.fromRect(
+                key: _childGlobalKey,
+                rect: rect,
+                child: _builder!(context, animation),
+              ),
+            ],
+          );
+        }
+
+        // When the animation is done, just render everything in a static layout
+        // in the final position.
+        return _ContextMenuRouteStatic(
+          actions: _actions,
+          child: _builder!(context, animation),
+          childGlobalKey: _childGlobalKey,
+          contextMenuLocation: _contextMenuLocation,
+          onDismiss: _onDismiss,
+          orientation: orientation,
+          sheetGlobalKey: _sheetGlobalKey,
+        );
+      },
+    );
+  }
+}
+
+// The final state of the _ContextMenuRoute after animating in and before
+// animating out.
+class _ContextMenuRouteStatic extends StatefulWidget {
+  const _ContextMenuRouteStatic({
+    Key? key,
+    this.actions,
+    required this.child,
+    this.childGlobalKey,
+    required this.contextMenuLocation,
+    this.onDismiss,
+    required this.orientation,
+    this.sheetGlobalKey,
+  }) : assert(contextMenuLocation != null),
+       assert(orientation != null),
+       super(key: key);
+
+  final List<Widget>? actions;
+  final Widget child;
+  final GlobalKey? childGlobalKey;
+  final _ContextMenuLocation contextMenuLocation;
+  final _DismissCallback? onDismiss;
+  final Orientation orientation;
+  final GlobalKey? sheetGlobalKey;
+
+  @override
+  _ContextMenuRouteStaticState createState() => _ContextMenuRouteStaticState();
+}
+
+class _ContextMenuRouteStaticState extends State<_ContextMenuRouteStatic> with TickerProviderStateMixin {
+  // The child is scaled down as it is dragged down until it hits this minimum
+  // value.
+  static const double _kMinScale = 0.8;
+  // The CupertinoContextMenuSheet disappears at this scale.
+  static const double _kSheetScaleThreshold = 0.9;
+  static const double _kPadding = 20.0;
+  static const double _kDamping = 400.0;
+  static const Duration _kMoveControllerDuration = Duration(milliseconds: 600);
+
+  late Offset _dragOffset;
+  double _lastScale = 1.0;
+  late AnimationController _moveController;
+  late AnimationController _sheetController;
+  late Animation<Offset> _moveAnimation;
+  late Animation<double> _sheetScaleAnimation;
+  late Animation<double> _sheetOpacityAnimation;
+
+  // The scale of the child changes as a function of the distance it is dragged.
+  static double _getScale(Orientation orientation, double maxDragDistance, double dy) {
+    final double dyDirectional = dy <= 0.0 ? dy : -dy;
+    return math.max(
+      _kMinScale,
+      (maxDragDistance + dyDirectional) / maxDragDistance,
+    );
+  }
+
+  void _onPanStart(DragStartDetails details) {
+    _moveController.value = 1.0;
+    _setDragOffset(Offset.zero);
+  }
+
+  void _onPanUpdate(DragUpdateDetails details) {
+    _setDragOffset(_dragOffset + details.delta);
+  }
+
+  void _onPanEnd(DragEndDetails details) {
+    // If flung, animate a bit before handling the potential dismiss.
+    if (details.velocity.pixelsPerSecond.dy.abs() >= kMinFlingVelocity) {
+      final bool flingIsAway = details.velocity.pixelsPerSecond.dy > 0;
+      final double finalPosition = flingIsAway
+        ? _moveAnimation.value.dy + 100.0
+        : 0.0;
+
+      if (flingIsAway && _sheetController.status != AnimationStatus.forward) {
+        _sheetController.forward();
+      } else if (!flingIsAway && _sheetController.status != AnimationStatus.reverse) {
+        _sheetController.reverse();
+      }
+
+      _moveAnimation = Tween<Offset>(
+        begin: Offset(0.0, _moveAnimation.value.dy),
+        end: Offset(0.0, finalPosition),
+      ).animate(_moveController);
+      _moveController.reset();
+      _moveController.duration = const Duration(
+        milliseconds: 64,
+      );
+      _moveController.forward();
+      _moveController.addStatusListener(_flingStatusListener);
+      return;
+    }
+
+    // Dismiss if the drag is enough to scale down all the way.
+    if (_lastScale == _kMinScale) {
+      widget.onDismiss!(context, _lastScale, _sheetOpacityAnimation.value);
+      return;
+    }
+
+    // Otherwise animate back home.
+    _moveController.addListener(_moveListener);
+    _moveController.reverse();
+  }
+
+  void _moveListener() {
+    // When the scale passes the threshold, animate the sheet back in.
+    if (_lastScale > _kSheetScaleThreshold) {
+      _moveController.removeListener(_moveListener);
+      if (_sheetController.status != AnimationStatus.dismissed) {
+        _sheetController.reverse();
+      }
+    }
+  }
+
+  void _flingStatusListener(AnimationStatus status) {
+    if (status != AnimationStatus.completed) {
+      return;
+    }
+
+    // Reset the duration back to its original value.
+    _moveController.duration = _kMoveControllerDuration;
+
+    _moveController.removeStatusListener(_flingStatusListener);
+    // If it was a fling back to the start, it has reset itself, and it should
+    // not be dismissed.
+    if (_moveAnimation.value.dy == 0.0) {
+      return;
+    }
+    widget.onDismiss!(context, _lastScale, _sheetOpacityAnimation.value);
+  }
+
+  Alignment _getChildAlignment(Orientation orientation, _ContextMenuLocation contextMenuLocation) {
+    switch (contextMenuLocation) {
+      case _ContextMenuLocation.center:
+        return orientation == Orientation.portrait
+          ? Alignment.bottomCenter
+          : Alignment.topRight;
+      case _ContextMenuLocation.right:
+        return orientation == Orientation.portrait
+          ? Alignment.bottomCenter
+          : Alignment.topLeft;
+      case _ContextMenuLocation.left:
+        return orientation == Orientation.portrait
+          ? Alignment.bottomCenter
+          : Alignment.topRight;
+    }
+  }
+
+  void _setDragOffset(Offset dragOffset) {
+    // Allow horizontal and negative vertical movement, but damp it.
+    final double endX = _kPadding * dragOffset.dx / _kDamping;
+    final double endY = dragOffset.dy >= 0.0
+      ? dragOffset.dy
+      : _kPadding * dragOffset.dy / _kDamping;
+    setState(() {
+      _dragOffset = dragOffset;
+      _moveAnimation = Tween<Offset>(
+        begin: Offset.zero,
+        end: Offset(
+          endX.clamp(-_kPadding, _kPadding),
+          endY,
+        ),
+      ).animate(
+        CurvedAnimation(
+          parent: _moveController,
+          curve: Curves.elasticIn,
+        ),
+      );
+
+      // Fade the _ContextMenuSheet out or in, if needed.
+      if (_lastScale <= _kSheetScaleThreshold
+          && _sheetController.status != AnimationStatus.forward
+          && _sheetScaleAnimation.value != 0.0) {
+        _sheetController.forward();
+      } else if (_lastScale > _kSheetScaleThreshold
+          && _sheetController.status != AnimationStatus.reverse
+          && _sheetScaleAnimation.value != 1.0) {
+        _sheetController.reverse();
+      }
+    });
+  }
+
+  // The order and alignment of the _ContextMenuSheet and the child depend on
+  // both the orientation of the screen as well as the position on the screen of
+  // the original child.
+  List<Widget> _getChildren(Orientation orientation, _ContextMenuLocation contextMenuLocation) {
+    final Expanded child = Expanded(
+      child: Align(
+        alignment: _getChildAlignment(
+          widget.orientation,
+          widget.contextMenuLocation,
+        ),
+        child: AnimatedBuilder(
+          animation: _moveController,
+          builder: _buildChildAnimation,
+          child: widget.child,
+        ),
+      ),
+    );
+    const SizedBox spacer = SizedBox(
+      width: _kPadding,
+      height: _kPadding,
+    );
+    final Expanded sheet = Expanded(
+      child: AnimatedBuilder(
+        animation: _sheetController,
+        builder: _buildSheetAnimation,
+        child: _ContextMenuSheet(
+          key: widget.sheetGlobalKey,
+          actions: widget.actions!,
+          contextMenuLocation: widget.contextMenuLocation,
+          orientation: widget.orientation,
+        ),
+      ),
+    );
+
+    switch (contextMenuLocation) {
+      case _ContextMenuLocation.center:
+        return <Widget>[child, spacer, sheet];
+      case _ContextMenuLocation.right:
+        return orientation == Orientation.portrait
+          ? <Widget>[child, spacer, sheet]
+          : <Widget>[sheet, spacer, child];
+      case _ContextMenuLocation.left:
+        return <Widget>[child, spacer, sheet];
+    }
+  }
+
+  // Build the animation for the _ContextMenuSheet.
+  Widget _buildSheetAnimation(BuildContext context, Widget? child) {
+    return Transform.scale(
+      alignment: _ContextMenuRoute.getSheetAlignment(widget.contextMenuLocation),
+      scale: _sheetScaleAnimation.value,
+      child: Opacity(
+        opacity: _sheetOpacityAnimation.value,
+        child: child,
+      ),
+    );
+  }
+
+  // Build the animation for the child.
+  Widget _buildChildAnimation(BuildContext context, Widget? child) {
+    _lastScale = _getScale(
+      widget.orientation,
+      MediaQuery.of(context).size.height,
+      _moveAnimation.value.dy,
+    );
+    return Transform.scale(
+      key: widget.childGlobalKey,
+      scale: _lastScale,
+      child: child,
+    );
+  }
+
+  // Build the animation for the overall draggable dismissible content.
+  Widget _buildAnimation(BuildContext context, Widget? child) {
+    return Transform.translate(
+      offset: _moveAnimation.value,
+      child: child,
+    );
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _moveController = AnimationController(
+      duration: _kMoveControllerDuration,
+      value: 1.0,
+      vsync: this,
+    );
+    _sheetController = AnimationController(
+      duration: const Duration(milliseconds: 100),
+      reverseDuration: const Duration(milliseconds: 300),
+      vsync: this,
+    );
+    _sheetScaleAnimation = Tween<double>(
+      begin: 1.0,
+      end: 0.0,
+    ).animate(
+      CurvedAnimation(
+        parent: _sheetController,
+        curve: Curves.linear,
+        reverseCurve: Curves.easeInBack,
+      ),
+    );
+    _sheetOpacityAnimation = Tween<double>(
+      begin: 1.0,
+      end: 0.0,
+    ).animate(_sheetController);
+    _setDragOffset(Offset.zero);
+  }
+
+  @override
+  void dispose() {
+    _moveController.dispose();
+    _sheetController.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final List<Widget> children = _getChildren(
+      widget.orientation,
+      widget.contextMenuLocation,
+    );
+
+    return SafeArea(
+      child: Padding(
+        padding: const EdgeInsets.all(_kPadding),
+        child: Align(
+          alignment: Alignment.topLeft,
+          child: GestureDetector(
+            onPanEnd: _onPanEnd,
+            onPanStart: _onPanStart,
+            onPanUpdate: _onPanUpdate,
+            child: AnimatedBuilder(
+              animation: _moveController,
+              builder: _buildAnimation,
+              child: widget.orientation == Orientation.portrait
+                ? Column(
+                  crossAxisAlignment: CrossAxisAlignment.start,
+                  children: children,
+                )
+                : Row(
+                  crossAxisAlignment: CrossAxisAlignment.start,
+                  children: children,
+                ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+// The menu that displays when CupertinoContextMenu is open. It consists of a
+// list of actions that are typically CupertinoContextMenuActions.
+class _ContextMenuSheet extends StatelessWidget {
+  _ContextMenuSheet({
+    Key? key,
+    required this.actions,
+    required _ContextMenuLocation contextMenuLocation,
+    required Orientation orientation,
+  }) : assert(actions != null && actions.isNotEmpty),
+       assert(contextMenuLocation != null),
+       assert(orientation != null),
+       _contextMenuLocation = contextMenuLocation,
+       _orientation = orientation,
+       super(key: key);
+
+  final List<Widget> actions;
+  final _ContextMenuLocation _contextMenuLocation;
+  final Orientation _orientation;
+
+  // Get the children, whose order depends on orientation and
+  // contextMenuLocation.
+  List<Widget> get children {
+    final Flexible menu = Flexible(
+      fit: FlexFit.tight,
+      flex: 2,
+      child: IntrinsicHeight(
+        child: ClipRRect(
+          borderRadius: BorderRadius.circular(13.0),
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.stretch,
+            children: actions,
+          ),
+        ),
+      ),
+    );
+
+    switch (_contextMenuLocation) {
+      case _ContextMenuLocation.center:
+        return _orientation == Orientation.portrait
+          ? <Widget>[
+            const Spacer(
+              flex: 1,
+            ),
+            menu,
+            const Spacer(
+              flex: 1,
+            ),
+          ]
+        : <Widget>[
+            menu,
+            const Spacer(
+              flex: 1,
+            ),
+          ];
+      case _ContextMenuLocation.right:
+        return <Widget>[
+          const Spacer(
+            flex: 1,
+          ),
+          menu,
+        ];
+      case _ContextMenuLocation.left:
+        return <Widget>[
+          menu,
+          const Spacer(
+            flex: 1,
+          ),
+        ];
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Row(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: children,
+    );
+  }
+}
+
+// An animation that switches between two colors.
+//
+// The transition is immediate, so there are no intermediate values or
+// interpolation. The color switches from offColor to onColor and back to
+// offColor at the times given by intervalOn and intervalOff.
+class _OnOffAnimation<T> extends CompoundAnimation<T> {
+  _OnOffAnimation({
+    required AnimationController controller,
+    required T onValue,
+    required T offValue,
+    required double intervalOn,
+    required double intervalOff,
+  }) : _offValue = offValue,
+       assert(intervalOn >= 0.0 && intervalOn <= 1.0),
+       assert(intervalOff >= 0.0 && intervalOff <= 1.0),
+       assert(intervalOn <= intervalOff),
+       super(
+        first: Tween<T>(begin: offValue, end: onValue).animate(
+          CurvedAnimation(
+            parent: controller,
+            curve: Interval(intervalOn, intervalOn),
+          ),
+        ),
+        next: Tween<T>(begin: onValue, end: offValue).animate(
+          CurvedAnimation(
+            parent: controller,
+            curve: Interval(intervalOff, intervalOff),
+          ),
+        ),
+       );
+
+  final T _offValue;
+
+  @override
+  T get value => next.value == _offValue ? next.value : first.value;
+}
diff --git a/lib/src/cupertino/context_menu_action.dart b/lib/src/cupertino/context_menu_action.dart
new file mode 100644
index 0000000..51abfdd
--- /dev/null
+++ b/lib/src/cupertino/context_menu_action.dart
@@ -0,0 +1,148 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+import 'colors.dart';
+
+/// A button in a _ContextMenuSheet.
+///
+/// A typical use case is to pass a [Text] as the [child] here, but be sure to
+/// use [TextOverflow.ellipsis] for the [Text.overflow] field if the text may be
+/// long, as without it the text will wrap to the next line.
+class CupertinoContextMenuAction extends StatefulWidget {
+  /// Construct a CupertinoContextMenuAction.
+  const CupertinoContextMenuAction({
+    Key? key,
+    required this.child,
+    this.isDefaultAction = false,
+    this.isDestructiveAction = false,
+    this.onPressed,
+    this.trailingIcon,
+  }) : assert(child != null),
+       assert(isDefaultAction != null),
+       assert(isDestructiveAction != null),
+       super(key: key);
+
+  /// The widget that will be placed inside the action.
+  final Widget child;
+
+  /// Indicates whether this action should receive the style of an emphasized,
+  /// default action.
+  final bool isDefaultAction;
+
+  /// Indicates whether this action should receive the style of a destructive
+  /// action.
+  final bool isDestructiveAction;
+
+  /// Called when the action is pressed.
+  final VoidCallback? onPressed;
+
+  /// An optional icon to display to the right of the child.
+  ///
+  /// Will be colored in the same way as the [TextStyle] used for [child] (for
+  /// example, if using [isDestructiveAction]).
+  final IconData? trailingIcon;
+
+  @override
+  _CupertinoContextMenuActionState createState() => _CupertinoContextMenuActionState();
+}
+
+class _CupertinoContextMenuActionState extends State<CupertinoContextMenuAction> {
+  static const Color _kBackgroundColor = Color(0xFFEEEEEE);
+  static const Color _kBackgroundColorPressed = Color(0xFFDDDDDD);
+  static const double _kButtonHeight = 56.0;
+  static const TextStyle _kActionSheetActionStyle = TextStyle(
+    fontFamily: '.SF UI Text',
+    inherit: false,
+    fontSize: 20.0,
+    fontWeight: FontWeight.w400,
+    color: CupertinoColors.black,
+    textBaseline: TextBaseline.alphabetic,
+  );
+
+  final GlobalKey _globalKey = GlobalKey();
+  bool _isPressed = false;
+
+  void onTapDown(TapDownDetails details) {
+    setState(() {
+      _isPressed = true;
+    });
+  }
+
+  void onTapUp(TapUpDetails details) {
+    setState(() {
+      _isPressed = false;
+    });
+  }
+
+  void onTapCancel() {
+    setState(() {
+      _isPressed = false;
+    });
+  }
+
+  TextStyle get _textStyle {
+    if (widget.isDefaultAction) {
+      return _kActionSheetActionStyle.copyWith(
+        fontWeight: FontWeight.w600,
+      );
+    }
+    if (widget.isDestructiveAction) {
+      return _kActionSheetActionStyle.copyWith(
+        color: CupertinoColors.destructiveRed,
+      );
+    }
+    return _kActionSheetActionStyle;
+  }
+
+
+  @override
+  Widget build(BuildContext context) {
+    return GestureDetector(
+      key: _globalKey,
+      onTapDown: onTapDown,
+      onTapUp: onTapUp,
+      onTapCancel: onTapCancel,
+      onTap: widget.onPressed,
+      behavior: HitTestBehavior.opaque,
+      child: ConstrainedBox(
+        constraints: const BoxConstraints(
+          minHeight: _kButtonHeight,
+        ),
+        child: Semantics(
+          button: true,
+          child: Container(
+            decoration: BoxDecoration(
+              color: _isPressed ? _kBackgroundColorPressed : _kBackgroundColor,
+              border: const Border(
+                bottom: BorderSide(width: 1.0, color: _kBackgroundColorPressed),
+              ),
+            ),
+            padding: const EdgeInsets.symmetric(
+              vertical: 16.0,
+              horizontal: 10.0,
+            ),
+            child: DefaultTextStyle(
+              style: _textStyle,
+              child: Row(
+                mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                children: <Widget>[
+                  Flexible(
+                    child: widget.child,
+                  ),
+                  if (widget.trailingIcon != null)
+                    Icon(
+                      widget.trailingIcon,
+                      color: _textStyle.color,
+                    ),
+                ],
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/cupertino/date_picker.dart b/lib/src/cupertino/date_picker.dart
new file mode 100644
index 0000000..fc14d12
--- /dev/null
+++ b/lib/src/cupertino/date_picker.dart
@@ -0,0 +1,2105 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' hide TextStyle;
+
+import 'package:flute/scheduler.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'localizations.dart';
+import 'picker.dart';
+import 'theme.dart';
+
+// Values derived from https://developer.apple.com/design/resources/ and on iOS
+// simulators with "Debug View Hierarchy".
+const double _kItemExtent = 32.0;
+// From the picker's intrinsic content size constraint.
+const double _kPickerWidth = 320.0;
+const double _kPickerHeight = 216.0;
+const bool _kUseMagnifier = true;
+const double _kMagnification = 2.35/2.1;
+const double _kDatePickerPadSize = 12.0;
+// The density of a date picker is different from a generic picker.
+// Eyeballed from iOS.
+const double _kSqueeze = 1.25;
+
+const TextStyle _kDefaultPickerTextStyle = TextStyle(
+  letterSpacing: -0.83,
+);
+
+// The item height is 32 and the magnifier height is 34, from
+// iOS simulators with "Debug View Hierarchy".
+// And the magnified fontSize by [_kTimerPickerMagnification] conforms to the
+// iOS 14 native style by eyeball test.
+const double _kTimerPickerMagnification = 34 / 32;
+// Minimum horizontal padding between [CupertinoTimerPicker]
+//
+// It shouldn't actually be hard-coded for direct use, and the perfect solution
+// should be to calculate the values that match the magnified values by
+// offAxisFraction and _kSqueeze.
+// Such calculations are complex, so we'll hard-code them for now.
+const double _kTimerPickerMinHorizontalPadding = 30;
+// Half of the horizontal padding value between the timer picker's columns.
+const double _kTimerPickerHalfColumnPadding = 4;
+// The horizontal padding between the timer picker's number label and its
+// corresponding unit label.
+const double _kTimerPickerLabelPadSize = 6;
+const double _kTimerPickerLabelFontSize = 17.0;
+
+// The width of each column of the countdown time picker.
+const double _kTimerPickerColumnIntrinsicWidth = 106;
+
+TextStyle _themeTextStyle(BuildContext context, { bool isValid = true }) {
+  final TextStyle style = CupertinoTheme.of(context).textTheme.dateTimePickerTextStyle;
+  return isValid ? style : style.copyWith(color: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context));
+}
+
+void _animateColumnControllerToItem(FixedExtentScrollController controller, int targetItem) {
+  controller.animateToItem(
+    targetItem,
+    curve: Curves.easeInOut,
+    duration: const Duration(milliseconds: 200),
+  );
+}
+
+const Widget _leftSelectionOverlay = CupertinoPickerDefaultSelectionOverlay(capRightEdge: false);
+const Widget _centerSelectionOverlay = CupertinoPickerDefaultSelectionOverlay(capLeftEdge: false, capRightEdge: false,);
+const Widget _rightSelectionOverlay = CupertinoPickerDefaultSelectionOverlay(capLeftEdge: false);
+
+// Lays out the date picker based on how much space each single column needs.
+//
+// Each column is a child of this delegate, indexed from 0 to number of columns - 1.
+// Each column will be padded horizontally by 12.0 both left and right.
+//
+// The picker will be placed in the center, and the leftmost and rightmost
+// column will be extended equally to the remaining width.
+class _DatePickerLayoutDelegate extends MultiChildLayoutDelegate {
+  _DatePickerLayoutDelegate({
+    required this.columnWidths,
+    required this.textDirectionFactor,
+  }) : assert(columnWidths != null),
+       assert(textDirectionFactor != null);
+
+  // The list containing widths of all columns.
+  final List<double> columnWidths;
+
+  // textDirectionFactor is 1 if text is written left to right, and -1 if right to left.
+  final int textDirectionFactor;
+
+  @override
+  void performLayout(Size size) {
+    double remainingWidth = size.width;
+
+    for (int i = 0; i < columnWidths.length; i++)
+      remainingWidth -= columnWidths[i] + _kDatePickerPadSize * 2;
+
+    double currentHorizontalOffset = 0.0;
+
+    for (int i = 0; i < columnWidths.length; i++) {
+      final int index = textDirectionFactor == 1 ? i : columnWidths.length - i - 1;
+
+      double childWidth = columnWidths[index] + _kDatePickerPadSize * 2;
+      if (index == 0 || index == columnWidths.length - 1)
+        childWidth += remainingWidth / 2;
+
+      // We can't actually assert here because it would break things badly for
+      // semantics, which will expect that we laid things out here.
+      assert(() {
+        if (childWidth < 0) {
+          FlutterError.reportError(
+            FlutterErrorDetails(
+              exception: FlutterError(
+                'Insufficient horizontal space to render the '
+                'CupertinoDatePicker because the parent is too narrow at '
+                '${size.width}px.\n'
+                'An additional ${-remainingWidth}px is needed to avoid '
+                'overlapping columns.',
+              ),
+            ),
+          );
+        }
+        return true;
+      }());
+      layoutChild(index, BoxConstraints.tight(Size(math.max(0.0, childWidth), size.height)));
+      positionChild(index, Offset(currentHorizontalOffset, 0.0));
+
+      currentHorizontalOffset += childWidth;
+    }
+  }
+
+  @override
+  bool shouldRelayout(_DatePickerLayoutDelegate oldDelegate) {
+    return columnWidths != oldDelegate.columnWidths
+      || textDirectionFactor != oldDelegate.textDirectionFactor;
+  }
+}
+
+/// Different display modes of [CupertinoDatePicker].
+///
+/// See also:
+///
+///  * [CupertinoDatePicker], the class that implements different display modes
+///    of the iOS-style date picker.
+///  * [CupertinoPicker], the class that implements a content agnostic spinner UI.
+enum CupertinoDatePickerMode {
+  /// Mode that shows the date in hour, minute, and (optional) an AM/PM designation.
+  /// The AM/PM designation is shown only if [CupertinoDatePicker] does not use 24h format.
+  /// Column order is subject to internationalization.
+  ///
+  /// Example: ` 4 | 14 | PM `.
+  time,
+  /// Mode that shows the date in month, day of month, and year.
+  /// Name of month is spelled in full.
+  /// Column order is subject to internationalization.
+  ///
+  /// Example: ` July | 13 | 2012 `.
+  date,
+  /// Mode that shows the date as day of the week, month, day of month and
+  /// the time in hour, minute, and (optional) an AM/PM designation.
+  /// The AM/PM designation is shown only if [CupertinoDatePicker] does not use 24h format.
+  /// Column order is subject to internationalization.
+  ///
+  /// Example: ` Fri Jul 13 | 4 | 14 | PM `
+  dateAndTime,
+}
+
+// Different types of column in CupertinoDatePicker.
+enum _PickerColumnType {
+  // Day of month column in date mode.
+  dayOfMonth,
+  // Month column in date mode.
+  month,
+  // Year column in date mode.
+  year,
+  // Medium date column in dateAndTime mode.
+  date,
+  // Hour column in time and dateAndTime mode.
+  hour,
+  // minute column in time and dateAndTime mode.
+  minute,
+  // AM/PM column in time and dateAndTime mode.
+  dayPeriod,
+}
+
+/// A date picker widget in iOS style.
+///
+/// There are several modes of the date picker listed in [CupertinoDatePickerMode].
+///
+/// The class will display its children as consecutive columns. Its children
+/// order is based on internationalization.
+///
+/// Example of the picker in date mode:
+///
+///  * US-English: `| July | 13 | 2012 |`
+///  * Vietnamese: `| 13 | Tháng 7 | 2012 |`
+///
+/// Can be used with [showCupertinoModalPopup] to display the picker modally at
+/// the bottom of the screen.
+///
+/// Sizes itself to its parent and may not render correctly if not given the
+/// full screen width. Content texts are shown with
+/// [CupertinoTextThemeData.dateTimePickerTextStyle].
+///
+/// See also:
+///
+///  * [CupertinoTimerPicker], the class that implements the iOS-style timer picker.
+///  * [CupertinoPicker], the class that implements a content agnostic spinner UI.
+class CupertinoDatePicker extends StatefulWidget {
+  /// Constructs an iOS style date picker.
+  ///
+  /// [mode] is one of the mode listed in [CupertinoDatePickerMode] and defaults
+  /// to [CupertinoDatePickerMode.dateAndTime].
+  ///
+  /// [onDateTimeChanged] is the callback called when the selected date or time
+  /// changes and must not be null. When in [CupertinoDatePickerMode.time] mode,
+  /// the year, month and day will be the same as [initialDateTime]. When in
+  /// [CupertinoDatePickerMode.date] mode, this callback will always report the
+  /// start time of the currently selected day.
+  ///
+  /// [initialDateTime] is the initial date time of the picker. Defaults to the
+  /// present date and time and must not be null. The present must conform to
+  /// the intervals set in [minimumDate], [maximumDate], [minimumYear], and
+  /// [maximumYear].
+  ///
+  /// [minimumDate] is the minimum selectable [DateTime] of the picker. When set
+  /// to null, the picker does not limit the minimum [DateTime] the user can pick.
+  /// In [CupertinoDatePickerMode.time] mode, [minimumDate] should typically be
+  /// on the same date as [initialDateTime], as the picker will not limit the
+  /// minimum time the user can pick if it's set to a date earlier than that.
+  ///
+  /// [maximumDate] is the maximum selectable [DateTime] of the picker. When set
+  /// to null, the picker does not limit the maximum [DateTime] the user can pick.
+  /// In [CupertinoDatePickerMode.time] mode, [maximumDate] should typically be
+  /// on the same date as [initialDateTime], as the picker will not limit the
+  /// maximum time the user can pick if it's set to a date later than that.
+  ///
+  /// [minimumYear] is the minimum year that the picker can be scrolled to in
+  /// [CupertinoDatePickerMode.date] mode. Defaults to 1 and must not be null.
+  ///
+  /// [maximumYear] is the maximum year that the picker can be scrolled to in
+  /// [CupertinoDatePickerMode.date] mode. Null if there's no limit.
+  ///
+  /// [minuteInterval] is the granularity of the minute spinner. Must be a
+  /// positive integer factor of 60.
+  ///
+  /// [use24hFormat] decides whether 24 hour format is used. Defaults to false.
+  CupertinoDatePicker({
+    Key? key,
+    this.mode = CupertinoDatePickerMode.dateAndTime,
+    required this.onDateTimeChanged,
+    DateTime? initialDateTime,
+    this.minimumDate,
+    this.maximumDate,
+    this.minimumYear = 1,
+    this.maximumYear,
+    this.minuteInterval = 1,
+    this.use24hFormat = false,
+    this.backgroundColor,
+  }) : initialDateTime = initialDateTime ?? DateTime.now(),
+       assert(mode != null),
+       assert(onDateTimeChanged != null),
+       assert(minimumYear != null),
+       assert(
+         minuteInterval > 0 && 60 % minuteInterval == 0,
+         'minute interval is not a positive integer factor of 60',
+       ),
+       super(key: key) {
+    assert(this.initialDateTime != null);
+    assert(
+      mode != CupertinoDatePickerMode.dateAndTime || minimumDate == null || !this.initialDateTime.isBefore(minimumDate!),
+      'initial date is before minimum date',
+    );
+    assert(
+      mode != CupertinoDatePickerMode.dateAndTime || maximumDate == null || !this.initialDateTime.isAfter(maximumDate!),
+      'initial date is after maximum date',
+    );
+    assert(
+      mode != CupertinoDatePickerMode.date || (minimumYear >= 1 && this.initialDateTime.year >= minimumYear),
+      'initial year is not greater than minimum year, or minimum year is not positive',
+    );
+    assert(
+      mode != CupertinoDatePickerMode.date || maximumYear == null || this.initialDateTime.year <= maximumYear!,
+      'initial year is not smaller than maximum year',
+    );
+    assert(
+      mode != CupertinoDatePickerMode.date || minimumDate == null || !minimumDate!.isAfter(this.initialDateTime),
+      'initial date ${this.initialDateTime} is not greater than or equal to minimumDate $minimumDate',
+    );
+    assert(
+      mode != CupertinoDatePickerMode.date || maximumDate == null || !maximumDate!.isBefore(this.initialDateTime),
+      'initial date ${this.initialDateTime} is not less than or equal to maximumDate $maximumDate',
+    );
+    assert(
+      this.initialDateTime.minute % minuteInterval == 0,
+      'initial minute is not divisible by minute interval',
+    );
+  }
+
+  /// The mode of the date picker as one of [CupertinoDatePickerMode].
+  /// Defaults to [CupertinoDatePickerMode.dateAndTime]. Cannot be null and
+  /// value cannot change after initial build.
+  final CupertinoDatePickerMode mode;
+
+  /// The initial date and/or time of the picker. Defaults to the present date
+  /// and time and must not be null. The present must conform to the intervals
+  /// set in [minimumDate], [maximumDate], [minimumYear], and [maximumYear].
+  ///
+  /// Changing this value after the initial build will not affect the currently
+  /// selected date time.
+  final DateTime initialDateTime;
+
+  /// The minimum selectable date that the picker can settle on.
+  ///
+  /// When non-null, the user can still scroll the picker to [DateTime]s earlier
+  /// than [minimumDate], but the [onDateTimeChanged] will not be called on
+  /// these [DateTime]s. Once let go, the picker will scroll back to [minimumDate].
+  ///
+  /// In [CupertinoDatePickerMode.time] mode, a time becomes unselectable if the
+  /// [DateTime] produced by combining that particular time and the date part of
+  /// [initialDateTime] is earlier than [minimumDate]. So typically [minimumDate]
+  /// needs to be set to a [DateTime] that is on the same date as [initialDateTime].
+  ///
+  /// Defaults to null. When set to null, the picker does not impose a limit on
+  /// the earliest [DateTime] the user can select.
+  final DateTime? minimumDate;
+
+  /// The maximum selectable date that the picker can settle on.
+  ///
+  /// When non-null, the user can still scroll the picker to [DateTime]s later
+  /// than [maximumDate], but the [onDateTimeChanged] will not be called on
+  /// these [DateTime]s. Once let go, the picker will scroll back to [maximumDate].
+  ///
+  /// In [CupertinoDatePickerMode.time] mode, a time becomes unselectable if the
+  /// [DateTime] produced by combining that particular time and the date part of
+  /// [initialDateTime] is later than [maximumDate]. So typically [maximumDate]
+  /// needs to be set to a [DateTime] that is on the same date as [initialDateTime].
+  ///
+  /// Defaults to null. When set to null, the picker does not impose a limit on
+  /// the latest [DateTime] the user can select.
+  final DateTime? maximumDate;
+
+  /// Minimum year that the picker can be scrolled to in
+  /// [CupertinoDatePickerMode.date] mode. Defaults to 1 and must not be null.
+  final int minimumYear;
+
+  /// Maximum year that the picker can be scrolled to in
+  /// [CupertinoDatePickerMode.date] mode. Null if there's no limit.
+  final int? maximumYear;
+
+  /// The granularity of the minutes spinner, if it is shown in the current mode.
+  /// Must be an integer factor of 60.
+  final int minuteInterval;
+
+  /// Whether to use 24 hour format. Defaults to false.
+  final bool use24hFormat;
+
+  /// Callback called when the selected date and/or time changes. If the new
+  /// selected [DateTime] is not valid, or is not in the [minimumDate] through
+  /// [maximumDate] range, this callback will not be called.
+  ///
+  /// Must not be null.
+  final ValueChanged<DateTime> onDateTimeChanged;
+
+  /// Background color of date picker.
+  ///
+  /// Defaults to null, which disables background painting entirely.
+  final Color? backgroundColor;
+
+  @override
+  State<StatefulWidget> createState() { // ignore: no_logic_in_create_state, https://github.com/flutter/flutter/issues/70499
+    // The `time` mode and `dateAndTime` mode of the picker share the time
+    // columns, so they are placed together to one state.
+    // The `date` mode has different children and is implemented in a different
+    // state.
+    switch (mode) {
+      case CupertinoDatePickerMode.time:
+      case CupertinoDatePickerMode.dateAndTime:
+        return _CupertinoDatePickerDateTimeState();
+      case CupertinoDatePickerMode.date:
+        return _CupertinoDatePickerDateState();
+    }
+  }
+
+  // Estimate the minimum width that each column needs to layout its content.
+  static double _getColumnWidth(
+    _PickerColumnType columnType,
+    CupertinoLocalizations localizations,
+    BuildContext context,
+  ) {
+    String longestText = '';
+
+    switch (columnType) {
+      case _PickerColumnType.date:
+        // Measuring the length of all possible date is impossible, so here
+        // just some dates are measured.
+        for (int i = 1; i <= 12; i++) {
+          // An arbitrary date.
+          final String date =
+              localizations.datePickerMediumDate(DateTime(2018, i, 25));
+          if (longestText.length < date.length)
+            longestText = date;
+        }
+        break;
+      case _PickerColumnType.hour:
+        for (int i = 0; i < 24; i++) {
+          final String hour = localizations.datePickerHour(i);
+          if (longestText.length < hour.length)
+            longestText = hour;
+        }
+        break;
+      case _PickerColumnType.minute:
+        for (int i = 0; i < 60; i++) {
+          final String minute = localizations.datePickerMinute(i);
+          if (longestText.length < minute.length)
+            longestText = minute;
+        }
+        break;
+      case _PickerColumnType.dayPeriod:
+        longestText =
+          localizations.anteMeridiemAbbreviation.length > localizations.postMeridiemAbbreviation.length
+            ? localizations.anteMeridiemAbbreviation
+            : localizations.postMeridiemAbbreviation;
+        break;
+      case _PickerColumnType.dayOfMonth:
+        for (int i = 1; i <=31; i++) {
+          final String dayOfMonth = localizations.datePickerDayOfMonth(i);
+          if (longestText.length < dayOfMonth.length)
+            longestText = dayOfMonth;
+        }
+        break;
+      case _PickerColumnType.month:
+        for (int i = 1; i <=12; i++) {
+          final String month = localizations.datePickerMonth(i);
+          if (longestText.length < month.length)
+            longestText = month;
+        }
+        break;
+      case _PickerColumnType.year:
+        longestText = localizations.datePickerYear(2018);
+        break;
+    }
+
+    assert(longestText != '', 'column type is not appropriate');
+
+    final TextPainter painter = TextPainter(
+      text: TextSpan(
+        style: _themeTextStyle(context),
+        text: longestText,
+      ),
+      textDirection: Directionality.of(context),
+    );
+
+    // This operation is expensive and should be avoided. It is called here only
+    // because there's no other way to get the information we want without
+    // laying out the text.
+    painter.layout();
+
+    return painter.maxIntrinsicWidth;
+  }
+}
+
+typedef _ColumnBuilder = Widget Function(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay);
+
+class _CupertinoDatePickerDateTimeState extends State<CupertinoDatePicker> {
+  // Fraction of the farthest column's vanishing point vs its width. Eyeballed
+  // vs iOS.
+  static const double _kMaximumOffAxisFraction = 0.45;
+
+  late int textDirectionFactor;
+  late CupertinoLocalizations localizations;
+
+  // Alignment based on text direction. The variable name is self descriptive,
+  // however, when text direction is rtl, alignment is reversed.
+  late Alignment alignCenterLeft;
+  late Alignment alignCenterRight;
+
+  // Read this out when the state is initially created. Changes in initialDateTime
+  // in the widget after first build is ignored.
+  late DateTime initialDateTime;
+
+  // The difference in days between the initial date and the currently selected date.
+  // 0 if the current mode does not involve a date.
+  int get selectedDayFromInitial {
+    switch (widget.mode) {
+      case CupertinoDatePickerMode.dateAndTime:
+        return dateController.hasClients ? dateController.selectedItem : 0;
+      case CupertinoDatePickerMode.time:
+        return 0;
+      case CupertinoDatePickerMode.date:
+        break;
+    }
+    assert(
+      false,
+      '$runtimeType is only meant for dateAndTime mode or time mode',
+    );
+    return 0;
+  }
+  // The controller of the date column.
+  late FixedExtentScrollController dateController;
+
+  // The current selection of the hour picker. Values range from 0 to 23.
+  int get selectedHour => _selectedHour(selectedAmPm, _selectedHourIndex);
+  int get _selectedHourIndex => hourController.hasClients ? hourController.selectedItem % 24 : initialDateTime.hour;
+  // Calculates the selected hour given the selected indices of the hour picker
+  // and the meridiem picker.
+  int _selectedHour(int selectedAmPm, int selectedHour) {
+    return _isHourRegionFlipped(selectedAmPm) ? (selectedHour + 12) % 24 : selectedHour;
+  }
+  // The controller of the hour column.
+  late FixedExtentScrollController hourController;
+
+  // The current selection of the minute picker. Values range from 0 to 59.
+  int get selectedMinute {
+    return minuteController.hasClients
+      ? minuteController.selectedItem * widget.minuteInterval % 60
+      : initialDateTime.minute;
+  }
+  // The controller of the minute column.
+  late FixedExtentScrollController minuteController;
+
+  // Whether the current meridiem selection is AM or PM.
+  //
+  // We can't use the selectedItem of meridiemController as the source of truth
+  // because the meridiem picker can be scrolled **animatedly** by the hour picker
+  // (e.g. if you scroll from 12 to 1 in 12h format), but the meridiem change
+  // should take effect immediately, **before** the animation finishes.
+  late int selectedAmPm;
+  // Whether the physical-region-to-meridiem mapping is flipped.
+  bool get isHourRegionFlipped => _isHourRegionFlipped(selectedAmPm);
+  bool _isHourRegionFlipped(int selectedAmPm) => selectedAmPm != meridiemRegion;
+  // The index of the 12-hour region the hour picker is currently in.
+  //
+  // Used to determine whether the meridiemController should start animating.
+  // Valid values are 0 and 1.
+  //
+  // The AM/PM correspondence of the two regions flips when the meridiem picker
+  // scrolls. This variable is to keep track of the selected "physical"
+  // (meridiem picker invariant) region of the hour picker. The "physical" region
+  // of an item of index `i` is `i ~/ 12`.
+  late int meridiemRegion;
+  // The current selection of the AM/PM picker.
+  //
+  // - 0 means AM
+  // - 1 means PM
+  late FixedExtentScrollController meridiemController;
+
+  bool isDatePickerScrolling = false;
+  bool isHourPickerScrolling = false;
+  bool isMinutePickerScrolling = false;
+  bool isMeridiemPickerScrolling = false;
+
+  bool get isScrolling {
+    return isDatePickerScrolling
+        || isHourPickerScrolling
+        || isMinutePickerScrolling
+        || isMeridiemPickerScrolling;
+  }
+
+  // The estimated width of columns.
+  final Map<int, double> estimatedColumnWidths = <int, double>{};
+
+  @override
+  void initState() {
+    super.initState();
+    initialDateTime = widget.initialDateTime;
+
+    // Initially each of the "physical" regions is mapped to the meridiem region
+    // with the same number, e.g., the first 12 items are mapped to the first 12
+    // hours of a day. Such mapping is flipped when the meridiem picker is scrolled
+    // by the user, the first 12 items are mapped to the last 12 hours of a day.
+    selectedAmPm = initialDateTime.hour ~/ 12;
+    meridiemRegion = selectedAmPm;
+
+    meridiemController = FixedExtentScrollController(initialItem: selectedAmPm);
+    hourController = FixedExtentScrollController(initialItem: initialDateTime.hour);
+    minuteController = FixedExtentScrollController(initialItem: initialDateTime.minute ~/ widget.minuteInterval);
+    dateController = FixedExtentScrollController(initialItem: 0);
+
+    PaintingBinding.instance!.systemFonts.addListener(_handleSystemFontsChange);
+  }
+
+  void _handleSystemFontsChange () {
+    setState(() {
+      // System fonts change might cause the text layout width to change.
+      // Clears cached width to ensure that they get recalculated with the
+      // new system fonts.
+      estimatedColumnWidths.clear();
+    });
+  }
+
+  @override
+  void dispose() {
+    dateController.dispose();
+    hourController.dispose();
+    minuteController.dispose();
+    meridiemController.dispose();
+
+    PaintingBinding.instance!.systemFonts.removeListener(_handleSystemFontsChange);
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(CupertinoDatePicker oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    assert(
+      oldWidget.mode == widget.mode,
+      "The $runtimeType's mode cannot change once it's built.",
+    );
+
+    if (!widget.use24hFormat && oldWidget.use24hFormat) {
+      // Thanks to the physical and meridiem region mapping, the only thing we
+      // need to update is the meridiem controller, if it's not previously attached.
+      meridiemController.dispose();
+      meridiemController = FixedExtentScrollController(initialItem: selectedAmPm);
+    }
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+
+    textDirectionFactor = Directionality.of(context) == TextDirection.ltr ? 1 : -1;
+    localizations = CupertinoLocalizations.of(context);
+
+    alignCenterLeft = textDirectionFactor == 1 ? Alignment.centerLeft : Alignment.centerRight;
+    alignCenterRight = textDirectionFactor == 1 ? Alignment.centerRight : Alignment.centerLeft;
+
+    estimatedColumnWidths.clear();
+  }
+
+  // Lazily calculate the column width of the column being displayed only.
+  double _getEstimatedColumnWidth(_PickerColumnType columnType) {
+    if (estimatedColumnWidths[columnType.index] == null) {
+      estimatedColumnWidths[columnType.index] =
+          CupertinoDatePicker._getColumnWidth(columnType, localizations, context);
+    }
+
+    return estimatedColumnWidths[columnType.index]!;
+  }
+
+  // Gets the current date time of the picker.
+  DateTime get selectedDateTime {
+    return DateTime(
+      initialDateTime.year,
+      initialDateTime.month,
+      initialDateTime.day + selectedDayFromInitial,
+      selectedHour,
+      selectedMinute,
+    );
+  }
+
+  // Only reports datetime change when the date time is valid.
+  void _onSelectedItemChange(int index) {
+    final DateTime selected = selectedDateTime;
+
+    final bool isDateInvalid = widget.minimumDate?.isAfter(selected) == true
+                            || widget.maximumDate?.isBefore(selected) == true;
+
+    if (isDateInvalid)
+      return;
+
+    widget.onDateTimeChanged(selected);
+  }
+
+  // Builds the date column. The date is displayed in medium date format (e.g. Fri Aug 31).
+  Widget _buildMediumDatePicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) {
+    return NotificationListener<ScrollNotification>(
+      onNotification: (ScrollNotification notification) {
+        if (notification is ScrollStartNotification) {
+          isDatePickerScrolling = true;
+        } else if (notification is ScrollEndNotification) {
+          isDatePickerScrolling = false;
+          _pickerDidStopScrolling();
+        }
+
+        return false;
+      },
+      child: CupertinoPicker.builder(
+        scrollController: dateController,
+        offAxisFraction: offAxisFraction,
+        itemExtent: _kItemExtent,
+        useMagnifier: _kUseMagnifier,
+        magnification: _kMagnification,
+        backgroundColor: widget.backgroundColor,
+        squeeze: _kSqueeze,
+        onSelectedItemChanged: (int index) {
+          _onSelectedItemChange(index);
+        },
+        itemBuilder: (BuildContext context, int index) {
+          final DateTime rangeStart = DateTime(
+            initialDateTime.year,
+            initialDateTime.month,
+            initialDateTime.day + index,
+          );
+
+          // Exclusive.
+          final DateTime rangeEnd = DateTime(
+            initialDateTime.year,
+            initialDateTime.month,
+            initialDateTime.day + index + 1,
+          );
+
+          final DateTime now = DateTime.now();
+
+          if (widget.minimumDate?.isAfter(rangeEnd) == true)
+            return null;
+          if (widget.maximumDate?.isAfter(rangeStart) == false)
+            return null;
+
+          final String dateText = rangeStart == DateTime(now.year, now.month, now.day)
+            ? localizations.todayLabel
+            : localizations.datePickerMediumDate(rangeStart);
+
+          return itemPositioningBuilder(
+            context,
+            Text(dateText, style: _themeTextStyle(context)),
+          );
+        },
+      ),
+    );
+  }
+
+  // With the meridiem picker set to `meridiemIndex`, and the hour picker set to
+  // `hourIndex`, is it possible to change the value of the minute picker, so
+  // that the resulting date stays in the valid range.
+  bool _isValidHour(int meridiemIndex, int hourIndex) {
+    final DateTime rangeStart = DateTime(
+      initialDateTime.year,
+      initialDateTime.month,
+      initialDateTime.day + selectedDayFromInitial,
+      _selectedHour(meridiemIndex, hourIndex),
+      0,
+    );
+
+    // The end value of the range is exclusive, i.e. [rangeStart, rangeEnd).
+    final DateTime rangeEnd = rangeStart.add(const Duration(hours: 1));
+
+    return (widget.minimumDate?.isBefore(rangeEnd) ?? true)
+        && !(widget.maximumDate?.isBefore(rangeStart) ?? false);
+  }
+
+  Widget _buildHourPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) {
+    return NotificationListener<ScrollNotification>(
+      onNotification: (ScrollNotification notification) {
+        if (notification is ScrollStartNotification) {
+          isHourPickerScrolling = true;
+        } else if (notification is ScrollEndNotification) {
+          isHourPickerScrolling = false;
+          _pickerDidStopScrolling();
+        }
+
+        return false;
+      },
+      child: CupertinoPicker(
+        scrollController: hourController,
+        offAxisFraction: offAxisFraction,
+        itemExtent: _kItemExtent,
+        useMagnifier: _kUseMagnifier,
+        magnification: _kMagnification,
+        backgroundColor: widget.backgroundColor,
+        squeeze: _kSqueeze,
+        onSelectedItemChanged: (int index) {
+          final bool regionChanged = meridiemRegion != index ~/ 12;
+          final bool debugIsFlipped = isHourRegionFlipped;
+
+          if (regionChanged) {
+            meridiemRegion = index ~/ 12;
+            selectedAmPm = 1 - selectedAmPm;
+          }
+
+          if (!widget.use24hFormat && regionChanged) {
+            // Scroll the meridiem column to adjust AM/PM.
+            //
+            // _onSelectedItemChanged will be called when the animation finishes.
+            //
+            // Animation values obtained by comparing with iOS version.
+            meridiemController.animateToItem(
+              selectedAmPm,
+              duration: const Duration(milliseconds: 300),
+              curve: Curves.easeOut,
+            );
+          } else {
+            _onSelectedItemChange(index);
+          }
+
+          assert(debugIsFlipped == isHourRegionFlipped);
+        },
+        children: List<Widget>.generate(24, (int index) {
+          final int hour = isHourRegionFlipped ? (index + 12) % 24 : index;
+          final int displayHour = widget.use24hFormat ? hour : (hour + 11) % 12 + 1;
+
+          return itemPositioningBuilder(
+            context,
+            Text(
+              localizations.datePickerHour(displayHour),
+              semanticsLabel: localizations.datePickerHourSemanticsLabel(displayHour),
+              style: _themeTextStyle(context, isValid: _isValidHour(selectedAmPm, index)),
+            ),
+          );
+        }),
+        looping: true,
+      )
+    );
+  }
+
+  Widget _buildMinutePicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) {
+    return NotificationListener<ScrollNotification>(
+      onNotification: (ScrollNotification notification) {
+        if (notification is ScrollStartNotification) {
+          isMinutePickerScrolling = true;
+        } else if (notification is ScrollEndNotification) {
+          isMinutePickerScrolling = false;
+          _pickerDidStopScrolling();
+        }
+
+        return false;
+      },
+      child: CupertinoPicker(
+        scrollController: minuteController,
+        offAxisFraction: offAxisFraction,
+        itemExtent: _kItemExtent,
+        useMagnifier: _kUseMagnifier,
+        magnification: _kMagnification,
+        backgroundColor: widget.backgroundColor,
+        squeeze: _kSqueeze,
+        onSelectedItemChanged: _onSelectedItemChange,
+        children: List<Widget>.generate(60 ~/ widget.minuteInterval, (int index) {
+          final int minute = index * widget.minuteInterval;
+
+          final DateTime date = DateTime(
+            initialDateTime.year,
+            initialDateTime.month,
+            initialDateTime.day + selectedDayFromInitial,
+            selectedHour,
+            minute,
+          );
+
+          final bool isInvalidMinute = (widget.minimumDate?.isAfter(date) ?? false)
+                                    || (widget.maximumDate?.isBefore(date) ?? false);
+
+          return itemPositioningBuilder(
+            context,
+            Text(
+              localizations.datePickerMinute(minute),
+              semanticsLabel: localizations.datePickerMinuteSemanticsLabel(minute),
+              style: _themeTextStyle(context, isValid: !isInvalidMinute),
+            ),
+          );
+        }),
+        looping: true,
+        selectionOverlay: selectionOverlay,
+      ),
+    );
+  }
+
+  Widget _buildAmPmPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) {
+    return NotificationListener<ScrollNotification>(
+      onNotification: (ScrollNotification notification) {
+        if (notification is ScrollStartNotification) {
+          isMeridiemPickerScrolling = true;
+        } else if (notification is ScrollEndNotification) {
+          isMeridiemPickerScrolling = false;
+          _pickerDidStopScrolling();
+        }
+
+        return false;
+      },
+      child: CupertinoPicker(
+        scrollController: meridiemController,
+        offAxisFraction: offAxisFraction,
+        itemExtent: _kItemExtent,
+        useMagnifier: _kUseMagnifier,
+        magnification: _kMagnification,
+        backgroundColor: widget.backgroundColor,
+        squeeze: _kSqueeze,
+        onSelectedItemChanged: (int index) {
+          selectedAmPm = index;
+          assert(selectedAmPm == 0 || selectedAmPm == 1);
+          _onSelectedItemChange(index);
+        },
+        children: List<Widget>.generate(2, (int index) {
+          return itemPositioningBuilder(
+            context,
+            Text(
+              index == 0
+                ? localizations.anteMeridiemAbbreviation
+                : localizations.postMeridiemAbbreviation,
+              style: _themeTextStyle(context, isValid: _isValidHour(index, _selectedHourIndex)),
+            ),
+          );
+        }),
+        selectionOverlay: selectionOverlay,
+      ),
+    );
+  }
+
+  // One or more pickers have just stopped scrolling.
+  void _pickerDidStopScrolling() {
+    // Call setState to update the greyed out date/hour/minute/meridiem.
+    setState(() { });
+
+    if (isScrolling)
+      return;
+
+    // Whenever scrolling lands on an invalid entry, the picker
+    // automatically scrolls to a valid one.
+    final DateTime selectedDate = selectedDateTime;
+
+    final bool minCheck = widget.minimumDate?.isAfter(selectedDate) ?? false;
+    final bool maxCheck = widget.maximumDate?.isBefore(selectedDate) ?? false;
+
+    if (minCheck || maxCheck) {
+      // We have minCheck === !maxCheck.
+      final DateTime targetDate = minCheck ? widget.minimumDate! : widget.maximumDate!;
+      _scrollToDate(targetDate, selectedDate);
+    }
+  }
+
+  void _scrollToDate(DateTime newDate, DateTime fromDate) {
+    assert(newDate != null);
+    SchedulerBinding.instance!.addPostFrameCallback((Duration timestamp) {
+      if (fromDate.year != newDate.year || fromDate.month != newDate.month || fromDate.day != newDate.day) {
+        _animateColumnControllerToItem(dateController, selectedDayFromInitial);
+      }
+
+      if (fromDate.hour != newDate.hour) {
+        final bool needsMeridiemChange = !widget.use24hFormat
+                                      && fromDate.hour ~/ 12 != newDate.hour ~/ 12;
+        // In AM/PM mode, the pickers should not scroll all the way to the other hour region.
+        if (needsMeridiemChange) {
+          _animateColumnControllerToItem(meridiemController, 1 - meridiemController.selectedItem);
+
+          // Keep the target item index in the current 12-h region.
+          final int newItem = (hourController.selectedItem ~/ 12) * 12
+                            + (hourController.selectedItem + newDate.hour - fromDate.hour) % 12;
+          _animateColumnControllerToItem(hourController, newItem);
+        } else {
+          _animateColumnControllerToItem(
+            hourController,
+            hourController.selectedItem + newDate.hour - fromDate.hour,
+          );
+        }
+      }
+
+      if (fromDate.minute != newDate.minute) {
+        _animateColumnControllerToItem(minuteController, newDate.minute);
+      }
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // Widths of the columns in this picker, ordered from left to right.
+    final List<double> columnWidths = <double>[
+      _getEstimatedColumnWidth(_PickerColumnType.hour),
+      _getEstimatedColumnWidth(_PickerColumnType.minute),
+    ];
+
+    // Swap the hours and minutes if RTL to ensure they are in the correct position.
+    final List<_ColumnBuilder> pickerBuilders = Directionality.of(context) == TextDirection.rtl
+      ? <_ColumnBuilder>[_buildMinutePicker, _buildHourPicker]
+      : <_ColumnBuilder>[_buildHourPicker, _buildMinutePicker];
+
+    // Adds am/pm column if the picker is not using 24h format.
+    if (!widget.use24hFormat) {
+      if (localizations.datePickerDateTimeOrder == DatePickerDateTimeOrder.date_time_dayPeriod
+        || localizations.datePickerDateTimeOrder == DatePickerDateTimeOrder.time_dayPeriod_date) {
+        pickerBuilders.add(_buildAmPmPicker);
+        columnWidths.add(_getEstimatedColumnWidth(_PickerColumnType.dayPeriod));
+      } else {
+        pickerBuilders.insert(0, _buildAmPmPicker);
+        columnWidths.insert(0, _getEstimatedColumnWidth(_PickerColumnType.dayPeriod));
+      }
+    }
+
+    // Adds medium date column if the picker's mode is date and time.
+    if (widget.mode == CupertinoDatePickerMode.dateAndTime) {
+      if (localizations.datePickerDateTimeOrder == DatePickerDateTimeOrder.time_dayPeriod_date
+          || localizations.datePickerDateTimeOrder == DatePickerDateTimeOrder.dayPeriod_time_date) {
+        pickerBuilders.add(_buildMediumDatePicker);
+        columnWidths.add(_getEstimatedColumnWidth(_PickerColumnType.date));
+      } else {
+        pickerBuilders.insert(0, _buildMediumDatePicker);
+        columnWidths.insert(0, _getEstimatedColumnWidth(_PickerColumnType.date));
+      }
+    }
+
+    final List<Widget> pickers = <Widget>[];
+
+    for (int i = 0; i < columnWidths.length; i++) {
+      double offAxisFraction = 0.0;
+      Widget selectionOverlay = _centerSelectionOverlay;
+      if (i == 0) {
+        offAxisFraction = -_kMaximumOffAxisFraction * textDirectionFactor;
+        selectionOverlay = _leftSelectionOverlay;
+      } else if (i >= 2 || columnWidths.length == 2)
+        offAxisFraction = _kMaximumOffAxisFraction * textDirectionFactor;
+
+      EdgeInsets padding = const EdgeInsets.only(right: _kDatePickerPadSize);
+      if (i == columnWidths.length - 1) {
+        padding = padding.flipped;
+        selectionOverlay = _rightSelectionOverlay;
+      }
+      if (textDirectionFactor == -1)
+        padding = padding.flipped;
+
+      pickers.add(LayoutId(
+        id: i,
+        child: pickerBuilders[i](
+          offAxisFraction,
+          (BuildContext context, Widget? child) {
+            return Container(
+              alignment: i == columnWidths.length - 1
+                ? alignCenterLeft
+                : alignCenterRight,
+              padding: padding,
+              child: Container(
+                alignment: i == columnWidths.length - 1 ? alignCenterLeft : alignCenterRight,
+                width: i == 0 || i == columnWidths.length - 1
+                  ? null
+                  : columnWidths[i] + _kDatePickerPadSize,
+                child: child,
+              ),
+            );
+          },
+          selectionOverlay,
+        ),
+      ));
+    }
+
+    return MediaQuery(
+      data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
+      child: DefaultTextStyle.merge(
+        style: _kDefaultPickerTextStyle,
+        child: CustomMultiChildLayout(
+          delegate: _DatePickerLayoutDelegate(
+            columnWidths: columnWidths,
+            textDirectionFactor: textDirectionFactor,
+          ),
+          children: pickers,
+        ),
+      ),
+    );
+  }
+}
+
+class _CupertinoDatePickerDateState extends State<CupertinoDatePicker> {
+  late int textDirectionFactor;
+  late CupertinoLocalizations localizations;
+
+  // Alignment based on text direction. The variable name is self descriptive,
+  // however, when text direction is rtl, alignment is reversed.
+  late Alignment alignCenterLeft;
+  late Alignment alignCenterRight;
+
+  // The currently selected values of the picker.
+  late int selectedDay;
+  late int selectedMonth;
+  late int selectedYear;
+
+  // The controller of the day picker. There are cases where the selected value
+  // of the picker is invalid (e.g. February 30th 2018), and this dayController
+  // is responsible for jumping to a valid value.
+  late FixedExtentScrollController dayController;
+  late FixedExtentScrollController monthController;
+  late FixedExtentScrollController yearController;
+
+  bool isDayPickerScrolling = false;
+  bool isMonthPickerScrolling = false;
+  bool isYearPickerScrolling = false;
+
+  bool get isScrolling => isDayPickerScrolling || isMonthPickerScrolling || isYearPickerScrolling;
+
+  // Estimated width of columns.
+  Map<int, double> estimatedColumnWidths = <int, double>{};
+
+  @override
+  void initState() {
+    super.initState();
+    selectedDay = widget.initialDateTime.day;
+    selectedMonth = widget.initialDateTime.month;
+    selectedYear = widget.initialDateTime.year;
+
+    dayController = FixedExtentScrollController(initialItem: selectedDay - 1);
+    monthController = FixedExtentScrollController(initialItem: selectedMonth - 1);
+    yearController = FixedExtentScrollController(initialItem: selectedYear);
+
+    PaintingBinding.instance!.systemFonts.addListener(_handleSystemFontsChange);
+  }
+
+  void _handleSystemFontsChange() {
+    setState(() {
+      // System fonts change might cause the text layout width to change.
+      _refreshEstimatedColumnWidths();
+    });
+  }
+
+  @override
+  void dispose() {
+    dayController.dispose();
+    monthController.dispose();
+    yearController.dispose();
+
+    PaintingBinding.instance!.systemFonts.removeListener(_handleSystemFontsChange);
+    super.dispose();
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+
+    textDirectionFactor = Directionality.of(context) == TextDirection.ltr ? 1 : -1;
+    localizations = CupertinoLocalizations.of(context);
+
+    alignCenterLeft = textDirectionFactor == 1 ? Alignment.centerLeft : Alignment.centerRight;
+    alignCenterRight = textDirectionFactor == 1 ? Alignment.centerRight : Alignment.centerLeft;
+
+    _refreshEstimatedColumnWidths();
+  }
+
+  void _refreshEstimatedColumnWidths() {
+    estimatedColumnWidths[_PickerColumnType.dayOfMonth.index] = CupertinoDatePicker._getColumnWidth(_PickerColumnType.dayOfMonth, localizations, context);
+    estimatedColumnWidths[_PickerColumnType.month.index] = CupertinoDatePicker._getColumnWidth(_PickerColumnType.month, localizations, context);
+    estimatedColumnWidths[_PickerColumnType.year.index] = CupertinoDatePicker._getColumnWidth(_PickerColumnType.year, localizations, context);
+  }
+
+  // The DateTime of the last day of a given month in a given year.
+  // Let `DateTime` handle the year/month overflow.
+  DateTime _lastDayInMonth(int year, int month) => DateTime(year, month + 1, 0);
+
+  Widget _buildDayPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) {
+    final int daysInCurrentMonth = _lastDayInMonth(selectedYear, selectedMonth).day;
+    return NotificationListener<ScrollNotification>(
+      onNotification: (ScrollNotification notification) {
+        if (notification is ScrollStartNotification) {
+          isDayPickerScrolling = true;
+        } else if (notification is ScrollEndNotification) {
+          isDayPickerScrolling = false;
+          _pickerDidStopScrolling();
+        }
+
+        return false;
+      },
+      child: CupertinoPicker(
+        scrollController: dayController,
+        offAxisFraction: offAxisFraction,
+        itemExtent: _kItemExtent,
+        useMagnifier: _kUseMagnifier,
+        magnification: _kMagnification,
+        backgroundColor: widget.backgroundColor,
+        squeeze: _kSqueeze,
+        onSelectedItemChanged: (int index) {
+          selectedDay = index + 1;
+          if (_isCurrentDateValid)
+            widget.onDateTimeChanged(DateTime(selectedYear, selectedMonth, selectedDay));
+        },
+        children: List<Widget>.generate(31, (int index) {
+          final int day = index + 1;
+          return itemPositioningBuilder(
+            context,
+            Text(
+              localizations.datePickerDayOfMonth(day),
+              style: _themeTextStyle(context, isValid: day <= daysInCurrentMonth),
+            ),
+          );
+        }),
+        looping: true,
+        selectionOverlay: selectionOverlay,
+      ),
+    );
+  }
+
+  Widget _buildMonthPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) {
+    return NotificationListener<ScrollNotification>(
+      onNotification: (ScrollNotification notification) {
+        if (notification is ScrollStartNotification) {
+          isMonthPickerScrolling = true;
+        } else if (notification is ScrollEndNotification) {
+          isMonthPickerScrolling = false;
+          _pickerDidStopScrolling();
+        }
+
+        return false;
+      },
+      child: CupertinoPicker(
+        scrollController: monthController,
+        offAxisFraction: offAxisFraction,
+        itemExtent: _kItemExtent,
+        useMagnifier: _kUseMagnifier,
+        magnification: _kMagnification,
+        backgroundColor: widget.backgroundColor,
+        squeeze: _kSqueeze,
+        onSelectedItemChanged: (int index) {
+          selectedMonth = index + 1;
+          if (_isCurrentDateValid)
+            widget.onDateTimeChanged(DateTime(selectedYear, selectedMonth, selectedDay));
+        },
+        children: List<Widget>.generate(12, (int index) {
+          final int month = index + 1;
+          final bool isInvalidMonth = (widget.minimumDate?.year == selectedYear && widget.minimumDate!.month > month)
+                                   || (widget.maximumDate?.year == selectedYear && widget.maximumDate!.month < month);
+
+          return itemPositioningBuilder(
+            context,
+            Text(
+              localizations.datePickerMonth(month),
+              style: _themeTextStyle(context, isValid: !isInvalidMonth),
+            ),
+          );
+        }),
+        looping: true,
+        selectionOverlay: selectionOverlay,
+      ),
+    );
+  }
+
+  Widget _buildYearPicker(double offAxisFraction, TransitionBuilder itemPositioningBuilder, Widget selectionOverlay) {
+    return NotificationListener<ScrollNotification>(
+      onNotification: (ScrollNotification notification) {
+        if (notification is ScrollStartNotification) {
+          isYearPickerScrolling = true;
+        } else if (notification is ScrollEndNotification) {
+          isYearPickerScrolling = false;
+          _pickerDidStopScrolling();
+        }
+
+        return false;
+      },
+      child: CupertinoPicker.builder(
+        scrollController: yearController,
+        itemExtent: _kItemExtent,
+        offAxisFraction: offAxisFraction,
+        useMagnifier: _kUseMagnifier,
+        magnification: _kMagnification,
+        backgroundColor: widget.backgroundColor,
+        onSelectedItemChanged: (int index) {
+          selectedYear = index;
+          if (_isCurrentDateValid)
+            widget.onDateTimeChanged(DateTime(selectedYear, selectedMonth, selectedDay));
+        },
+        itemBuilder: (BuildContext context, int year) {
+          if (year < widget.minimumYear)
+            return null;
+
+          if (widget.maximumYear != null && year > widget.maximumYear!)
+            return null;
+
+          final bool isValidYear = (widget.minimumDate == null || widget.minimumDate!.year <= year)
+                                && (widget.maximumDate == null || widget.maximumDate!.year >= year);
+
+          return itemPositioningBuilder(
+            context,
+            Text(
+              localizations.datePickerYear(year),
+              style: _themeTextStyle(context, isValid: isValidYear),
+            ),
+          );
+        },
+        selectionOverlay: selectionOverlay,
+      ),
+    );
+  }
+
+  bool get _isCurrentDateValid {
+    // The current date selection represents a range [minSelectedData, maxSelectDate].
+    final DateTime minSelectedDate = DateTime(selectedYear, selectedMonth, selectedDay);
+    final DateTime maxSelectedDate = DateTime(selectedYear, selectedMonth, selectedDay + 1);
+
+    final bool minCheck = widget.minimumDate?.isBefore(maxSelectedDate) ?? true;
+    final bool maxCheck = widget.maximumDate?.isBefore(minSelectedDate) ?? false;
+
+    return minCheck && !maxCheck && minSelectedDate.day == selectedDay;
+  }
+
+  // One or more pickers have just stopped scrolling.
+  void _pickerDidStopScrolling() {
+    // Call setState to update the greyed out days/months/years, as the currently
+    // selected year/month may have changed.
+    setState(() { });
+
+    if (isScrolling) {
+      return;
+    }
+
+    // Whenever scrolling lands on an invalid entry, the picker
+    // automatically scrolls to a valid one.
+    final DateTime minSelectDate = DateTime(selectedYear, selectedMonth, selectedDay);
+    final DateTime maxSelectDate = DateTime(selectedYear, selectedMonth, selectedDay + 1);
+
+    final bool minCheck = widget.minimumDate?.isBefore(maxSelectDate) ?? true;
+    final bool maxCheck = widget.maximumDate?.isBefore(minSelectDate) ?? false;
+
+    if (!minCheck || maxCheck) {
+      // We have minCheck === !maxCheck.
+      final DateTime targetDate = minCheck ? widget.maximumDate! : widget.minimumDate!;
+      _scrollToDate(targetDate);
+      return;
+    }
+
+    // Some months have less days (e.g. February). Go to the last day of that month
+    // if the selectedDay exceeds the maximum.
+    if (minSelectDate.day != selectedDay) {
+      final DateTime lastDay = _lastDayInMonth(selectedYear, selectedMonth);
+      _scrollToDate(lastDay);
+    }
+  }
+
+  void _scrollToDate(DateTime newDate) {
+    assert(newDate != null);
+    SchedulerBinding.instance!.addPostFrameCallback((Duration timestamp) {
+      if (selectedYear != newDate.year) {
+        _animateColumnControllerToItem(yearController, newDate.year);
+      }
+
+      if (selectedMonth != newDate.month) {
+        _animateColumnControllerToItem(monthController, newDate.month - 1);
+      }
+
+      if (selectedDay != newDate.day) {
+        _animateColumnControllerToItem(dayController, newDate.day - 1);
+      }
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    List<_ColumnBuilder> pickerBuilders = <_ColumnBuilder>[];
+    List<double> columnWidths = <double>[];
+
+    switch (localizations.datePickerDateOrder) {
+      case DatePickerDateOrder.mdy:
+        pickerBuilders = <_ColumnBuilder>[_buildMonthPicker, _buildDayPicker, _buildYearPicker];
+        columnWidths = <double>[
+          estimatedColumnWidths[_PickerColumnType.month.index]!,
+          estimatedColumnWidths[_PickerColumnType.dayOfMonth.index]!,
+          estimatedColumnWidths[_PickerColumnType.year.index]!,
+        ];
+        break;
+      case DatePickerDateOrder.dmy:
+        pickerBuilders = <_ColumnBuilder>[_buildDayPicker, _buildMonthPicker, _buildYearPicker];
+        columnWidths = <double>[
+          estimatedColumnWidths[_PickerColumnType.dayOfMonth.index]!,
+          estimatedColumnWidths[_PickerColumnType.month.index]!,
+          estimatedColumnWidths[_PickerColumnType.year.index]!,
+        ];
+        break;
+      case DatePickerDateOrder.ymd:
+        pickerBuilders = <_ColumnBuilder>[_buildYearPicker, _buildMonthPicker, _buildDayPicker];
+        columnWidths = <double>[
+          estimatedColumnWidths[_PickerColumnType.year.index]!,
+          estimatedColumnWidths[_PickerColumnType.month.index]!,
+          estimatedColumnWidths[_PickerColumnType.dayOfMonth.index]!,
+        ];
+        break;
+      case DatePickerDateOrder.ydm:
+        pickerBuilders = <_ColumnBuilder>[_buildYearPicker, _buildDayPicker, _buildMonthPicker];
+        columnWidths = <double>[
+          estimatedColumnWidths[_PickerColumnType.year.index]!,
+          estimatedColumnWidths[_PickerColumnType.dayOfMonth.index]!,
+          estimatedColumnWidths[_PickerColumnType.month.index]!,
+        ];
+        break;
+    }
+
+    final List<Widget> pickers = <Widget>[];
+
+    for (int i = 0; i < columnWidths.length; i++) {
+      final double offAxisFraction = (i - 1) * 0.3 * textDirectionFactor;
+
+      EdgeInsets padding = const EdgeInsets.only(right: _kDatePickerPadSize);
+      if (textDirectionFactor == -1)
+        padding = const EdgeInsets.only(left: _kDatePickerPadSize);
+
+      Widget selectionOverlay = _centerSelectionOverlay;
+      if (i == 0)
+        selectionOverlay = _leftSelectionOverlay;
+      else if (i == columnWidths.length - 1)
+        selectionOverlay = _rightSelectionOverlay;
+
+      pickers.add(LayoutId(
+        id: i,
+        child: pickerBuilders[i](
+          offAxisFraction,
+          (BuildContext context, Widget? child) {
+            return Container(
+              alignment: i == columnWidths.length - 1
+                  ? alignCenterLeft
+                  : alignCenterRight,
+              padding: i == 0 ? null : padding,
+              child: Container(
+                alignment: i == 0 ? alignCenterLeft : alignCenterRight,
+                width: columnWidths[i] + _kDatePickerPadSize,
+                child: child,
+              ),
+            );
+          },
+          selectionOverlay,
+        ),
+      ));
+    }
+
+    return MediaQuery(
+      data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
+      child: DefaultTextStyle.merge(
+        style: _kDefaultPickerTextStyle,
+        child: CustomMultiChildLayout(
+          delegate: _DatePickerLayoutDelegate(
+            columnWidths: columnWidths,
+            textDirectionFactor: textDirectionFactor,
+          ),
+          children: pickers,
+        ),
+      ),
+    );
+  }
+}
+
+
+// The iOS date picker and timer picker has their width fixed to 320.0 in all
+// modes. The only exception is the hms mode (which doesn't have a native counterpart),
+// with a fixed width of 330.0 px.
+//
+// For date pickers, if the maximum width given to the picker is greater than
+// 320.0, the leftmost and rightmost column will be extended equally so that the
+// widths match, and the picker is in the center.
+//
+// For timer pickers, if the maximum width given to the picker is greater than
+// its intrinsic width, it will keep its intrinsic size and position itself in the
+// parent using its alignment parameter.
+//
+// If the maximum width given to the picker is smaller than 320.0, the picker's
+// layout will be broken.
+
+
+/// Different modes of [CupertinoTimerPicker].
+///
+/// See also:
+///
+///  * [CupertinoTimerPicker], the class that implements the iOS-style timer picker.
+///  * [CupertinoPicker], the class that implements a content agnostic spinner UI.
+enum CupertinoTimerPickerMode {
+  /// Mode that shows the timer duration in hour and minute.
+  ///
+  /// Examples: 16 hours | 14 min.
+  hm,
+  /// Mode that shows the timer duration in minute and second.
+  ///
+  /// Examples: 14 min | 43 sec.
+  ms,
+  /// Mode that shows the timer duration in hour, minute, and second.
+  ///
+  /// Examples: 16 hours | 14 min | 43 sec.
+  hms,
+}
+
+/// A countdown timer picker in iOS style.
+///
+/// This picker shows a countdown duration with hour, minute and second spinners.
+/// The duration is bound between 0 and 23 hours 59 minutes 59 seconds.
+///
+/// There are several modes of the timer picker listed in [CupertinoTimerPickerMode].
+///
+/// The picker has a fixed size of 320 x 216, in logical pixels, with the exception
+/// of [CupertinoTimerPickerMode.hms], which is 330 x 216. If the parent widget
+/// provides more space than it needs, the picker will position itself according
+/// to its [alignment] property.
+///
+/// See also:
+///
+///  * [CupertinoDatePicker], the class that implements different display modes
+///    of the iOS-style date picker.
+///  * [CupertinoPicker], the class that implements a content agnostic spinner UI.
+class CupertinoTimerPicker extends StatefulWidget {
+  /// Constructs an iOS style countdown timer picker.
+  ///
+  /// [mode] is one of the modes listed in [CupertinoTimerPickerMode] and
+  /// defaults to [CupertinoTimerPickerMode.hms].
+  ///
+  /// [onTimerDurationChanged] is the callback called when the selected duration
+  /// changes and must not be null.
+  ///
+  /// [initialTimerDuration] defaults to 0 second and is limited from 0 second
+  /// to 23 hours 59 minutes 59 seconds.
+  ///
+  /// [minuteInterval] is the granularity of the minute spinner. Must be a
+  /// positive integer factor of 60.
+  ///
+  /// [secondInterval] is the granularity of the second spinner. Must be a
+  /// positive integer factor of 60.
+  CupertinoTimerPicker({
+    Key? key,
+    this.mode = CupertinoTimerPickerMode.hms,
+    this.initialTimerDuration = Duration.zero,
+    this.minuteInterval = 1,
+    this.secondInterval = 1,
+    this.alignment = Alignment.center,
+    this.backgroundColor,
+    required this.onTimerDurationChanged,
+  }) : assert(mode != null),
+       assert(onTimerDurationChanged != null),
+       assert(initialTimerDuration >= Duration.zero),
+       assert(initialTimerDuration < const Duration(days: 1)),
+       assert(minuteInterval > 0 && 60 % minuteInterval == 0),
+       assert(secondInterval > 0 && 60 % secondInterval == 0),
+       assert(initialTimerDuration.inMinutes % minuteInterval == 0),
+       assert(initialTimerDuration.inSeconds % secondInterval == 0),
+       assert(alignment != null),
+       super(key: key);
+
+  /// The mode of the timer picker.
+  final CupertinoTimerPickerMode mode;
+
+  /// The initial duration of the countdown timer.
+  final Duration initialTimerDuration;
+
+  /// The granularity of the minute spinner. Must be a positive integer factor
+  /// of 60.
+  final int minuteInterval;
+
+  /// The granularity of the second spinner. Must be a positive integer factor
+  /// of 60.
+  final int secondInterval;
+
+  /// Callback called when the timer duration changes.
+  final ValueChanged<Duration> onTimerDurationChanged;
+
+  /// Defines how the timer picker should be positioned within its parent.
+  ///
+  /// This property must not be null. It defaults to [Alignment.center].
+  final AlignmentGeometry alignment;
+
+  /// Background color of timer picker.
+  ///
+  /// Defaults to null, which disables background painting entirely.
+  final Color? backgroundColor;
+
+  @override
+  State<StatefulWidget> createState() => _CupertinoTimerPickerState();
+}
+
+class _CupertinoTimerPickerState extends State<CupertinoTimerPicker> {
+  late TextDirection textDirection;
+  late CupertinoLocalizations localizations;
+  int get textDirectionFactor {
+    switch (textDirection) {
+      case TextDirection.ltr:
+        return 1;
+      case TextDirection.rtl:
+        return -1;
+    }
+  }
+
+  // The currently selected values of the picker.
+  int? selectedHour;
+  late int selectedMinute;
+  int? selectedSecond;
+
+  // On iOS the selected values won't be reported until the scrolling fully stops.
+  // The values below are the latest selected values when the picker comes to a full stop.
+  int? lastSelectedHour;
+  int? lastSelectedMinute;
+  int? lastSelectedSecond;
+
+  final TextPainter textPainter = TextPainter();
+  final List<String> numbers = List<String>.generate(10, (int i) => '${9 - i}');
+  late double numberLabelWidth;
+  late double numberLabelHeight;
+  late double numberLabelBaseline;
+
+  late double hourLabelWidth;
+  late double minuteLabelWidth;
+  late double secondLabelWidth;
+
+  late double totalWidth;
+  late double pickerColumnWidth;
+
+  @override
+  void initState() {
+    super.initState();
+
+    selectedMinute = widget.initialTimerDuration.inMinutes % 60;
+
+    if (widget.mode != CupertinoTimerPickerMode.ms)
+      selectedHour = widget.initialTimerDuration.inHours;
+
+    if (widget.mode != CupertinoTimerPickerMode.hm)
+      selectedSecond = widget.initialTimerDuration.inSeconds % 60;
+
+    PaintingBinding.instance!.systemFonts.addListener(_handleSystemFontsChange);
+  }
+
+  void _handleSystemFontsChange() {
+    setState(() {
+      // System fonts change might cause the text layout width to change.
+      textPainter.markNeedsLayout();
+      _measureLabelMetrics();
+    });
+  }
+
+  @override
+  void dispose() {
+    PaintingBinding.instance!.systemFonts.removeListener(_handleSystemFontsChange);
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(CupertinoTimerPicker oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    assert(
+      oldWidget.mode == widget.mode,
+      "The CupertinoTimerPicker's mode cannot change once it's built",
+    );
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+
+    textDirection = Directionality.of(context);
+    localizations = CupertinoLocalizations.of(context);
+
+    _measureLabelMetrics();
+  }
+
+  void _measureLabelMetrics() {
+    textPainter.textDirection = textDirection;
+    final TextStyle textStyle = _textStyleFrom(context, _kTimerPickerMagnification);
+
+    double maxWidth = double.negativeInfinity;
+    String? widestNumber;
+
+    // Assumes that:
+    // - 2-digit numbers are always wider than 1-digit numbers.
+    // - There's at least one number in 1-9 that's wider than or equal to 0.
+    // - The widest 2-digit number is composed of 2 same 1-digit numbers
+    //   that has the biggest width.
+    // - If two different 1-digit numbers are of the same width, their corresponding
+    //   2 digit numbers are of the same width.
+    for (final String input in numbers) {
+      textPainter.text = TextSpan(
+        text: input,
+        style: textStyle,
+      );
+      textPainter.layout();
+
+      if (textPainter.maxIntrinsicWidth > maxWidth) {
+        maxWidth = textPainter.maxIntrinsicWidth;
+        widestNumber = input;
+      }
+    }
+
+    textPainter.text = TextSpan(
+      text: '$widestNumber$widestNumber',
+      style: textStyle,
+    );
+
+    textPainter.layout();
+    numberLabelWidth = textPainter.maxIntrinsicWidth;
+    numberLabelHeight = textPainter.height;
+    numberLabelBaseline = textPainter.computeDistanceToActualBaseline(TextBaseline.alphabetic);
+
+    minuteLabelWidth =
+        _measureLabelsMaxWidth(localizations.timerPickerMinuteLabels, textStyle);
+
+    if (widget.mode != CupertinoTimerPickerMode.ms)
+      hourLabelWidth =
+          _measureLabelsMaxWidth(localizations.timerPickerHourLabels, textStyle);
+
+    if (widget.mode != CupertinoTimerPickerMode.hm)
+      secondLabelWidth =
+          _measureLabelsMaxWidth(localizations.timerPickerSecondLabels, textStyle);
+  }
+
+  // Measures all possible time text labels and return maximum width.
+  double _measureLabelsMaxWidth(List<String?> labels, TextStyle style) {
+    double maxWidth = double.negativeInfinity;
+    for (int i = 0; i < labels.length; i++) {
+      final String? label = labels[i];
+      if(label == null) {
+        continue;
+      }
+
+      textPainter.text = TextSpan(text: label, style: style);
+      textPainter.layout();
+      textPainter.maxIntrinsicWidth;
+      if (textPainter.maxIntrinsicWidth > maxWidth)
+        maxWidth = textPainter.maxIntrinsicWidth;
+    }
+
+    return maxWidth;
+  }
+
+  // Builds a text label with scale factor 1.0 and font weight semi-bold.
+  // `pickerPadding ` is the additional padding the corresponding picker has to apply
+  // around the `Text`, in order to extend its separators towards the closest
+  // horizontal edge of the encompassing widget.
+  Widget _buildLabel(String text, EdgeInsetsDirectional pickerPadding) {
+    final EdgeInsetsDirectional padding = EdgeInsetsDirectional.only(
+      start: numberLabelWidth
+           + _kTimerPickerLabelPadSize
+           + pickerPadding.start,
+    );
+
+    return IgnorePointer(
+      child: Container(
+        alignment: AlignmentDirectional.centerStart.resolve(textDirection),
+        padding: padding.resolve(textDirection),
+        child: SizedBox(
+          height: numberLabelHeight,
+          child: Baseline(
+            baseline: numberLabelBaseline,
+            baselineType: TextBaseline.alphabetic,
+            child: Text(
+              text,
+              style: const TextStyle(
+                fontSize: _kTimerPickerLabelFontSize,
+                fontWeight: FontWeight.w600,
+              ),
+              maxLines: 1,
+              softWrap: false,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+
+  // The picker has to be wider than its content, since the separators
+  // are part of the picker.
+  Widget _buildPickerNumberLabel(String text, EdgeInsetsDirectional padding) {
+    return Container(
+      width: _kTimerPickerColumnIntrinsicWidth + padding.horizontal,
+      padding: padding.resolve(textDirection),
+      alignment: AlignmentDirectional.centerStart.resolve(textDirection),
+      child: Container(
+        width: numberLabelWidth,
+        alignment: AlignmentDirectional.centerEnd.resolve(textDirection),
+        child: Text(text, softWrap: false, maxLines: 1, overflow: TextOverflow.visible),
+      ),
+    );
+  }
+
+  Widget _buildHourPicker(EdgeInsetsDirectional additionalPadding, Widget selectionOverlay) {
+    return CupertinoPicker(
+      scrollController: FixedExtentScrollController(initialItem: selectedHour!),
+      magnification: _kMagnification,
+      offAxisFraction: _calculateOffAxisFraction(additionalPadding.start, 0),
+      itemExtent: _kItemExtent,
+      backgroundColor: widget.backgroundColor,
+      squeeze: _kSqueeze,
+      onSelectedItemChanged: (int index) {
+        setState(() {
+          selectedHour = index;
+          widget.onTimerDurationChanged(
+              Duration(
+                  hours: selectedHour!,
+                  minutes: selectedMinute,
+                  seconds: selectedSecond ?? 0));
+        });
+      },
+      children: List<Widget>.generate(24, (int index) {
+        final String label = localizations.timerPickerHourLabel(index) ?? '';
+        final String semanticsLabel = textDirectionFactor == 1
+            ? localizations.timerPickerHour(index) + label
+            : label + localizations.timerPickerHour(index);
+
+        return Semantics(
+          label: semanticsLabel,
+          excludeSemantics: true,
+          child: _buildPickerNumberLabel(localizations.timerPickerHour(index), additionalPadding),
+        );
+      }),
+      selectionOverlay: selectionOverlay,
+    );
+  }
+
+  Widget _buildHourColumn(EdgeInsetsDirectional additionalPadding, Widget selectionOverlay) {
+    additionalPadding = EdgeInsetsDirectional.only(
+      start: math.max(additionalPadding.start, 0),
+      end: math.max(additionalPadding.end, 0),
+    );
+
+    return Stack(
+      children: <Widget>[
+        NotificationListener<ScrollEndNotification>(
+          onNotification: (ScrollEndNotification notification) {
+            setState(() { lastSelectedHour = selectedHour; });
+            return false;
+          },
+          child: _buildHourPicker(additionalPadding, selectionOverlay),
+        ),
+        _buildLabel(
+          localizations.timerPickerHourLabel(lastSelectedHour ?? selectedHour!) ?? '',
+          additionalPadding,
+        ),
+      ],
+    );
+  }
+
+  Widget _buildMinutePicker(EdgeInsetsDirectional additionalPadding, Widget selectionOverlay) {
+    return CupertinoPicker(
+      scrollController: FixedExtentScrollController(
+        initialItem: selectedMinute ~/ widget.minuteInterval,
+      ),
+      magnification: _kMagnification,
+      offAxisFraction: _calculateOffAxisFraction(
+          additionalPadding.start,
+          widget.mode == CupertinoTimerPickerMode.ms ? 0 : 1
+      ),
+      itemExtent: _kItemExtent,
+      backgroundColor: widget.backgroundColor,
+      squeeze: _kSqueeze,
+      looping: true,
+      onSelectedItemChanged: (int index) {
+        setState(() {
+          selectedMinute = index * widget.minuteInterval;
+          widget.onTimerDurationChanged(
+              Duration(
+                  hours: selectedHour ?? 0,
+                  minutes: selectedMinute,
+                  seconds: selectedSecond ?? 0));
+        });
+      },
+      children: List<Widget>.generate(60 ~/ widget.minuteInterval, (int index) {
+        final int minute = index * widget.minuteInterval;
+        final String label = localizations.timerPickerMinuteLabel(minute) ?? '';
+        final String semanticsLabel = textDirectionFactor == 1
+            ? localizations.timerPickerMinute(minute) + label
+            : label + localizations.timerPickerMinute(minute);
+
+        return Semantics(
+          label: semanticsLabel,
+          excludeSemantics: true,
+          child: _buildPickerNumberLabel(localizations.timerPickerMinute(minute), additionalPadding),
+        );
+      }),
+      selectionOverlay: selectionOverlay,
+    );
+  }
+
+  Widget _buildMinuteColumn(EdgeInsetsDirectional additionalPadding, Widget selectionOverlay) {
+    additionalPadding = EdgeInsetsDirectional.only(
+      start: math.max(additionalPadding.start, 0),
+      end: math.max(additionalPadding.end, 0),
+    );
+
+    return Stack(
+      children: <Widget>[
+        NotificationListener<ScrollEndNotification>(
+          onNotification: (ScrollEndNotification notification) {
+            setState(() { lastSelectedMinute = selectedMinute; });
+            return false;
+          },
+          child: _buildMinutePicker(additionalPadding, selectionOverlay),
+        ),
+        _buildLabel(
+          localizations.timerPickerMinuteLabel(lastSelectedMinute ?? selectedMinute) ?? '',
+          additionalPadding,
+        ),
+      ],
+    );
+  }
+
+  Widget _buildSecondPicker(EdgeInsetsDirectional additionalPadding, Widget selectionOverlay) {
+    return CupertinoPicker(
+      scrollController: FixedExtentScrollController(
+        initialItem: selectedSecond! ~/ widget.secondInterval,
+      ),
+      magnification: _kMagnification,
+      offAxisFraction: _calculateOffAxisFraction(
+          additionalPadding.start,
+          widget.mode == CupertinoTimerPickerMode.ms ? 1 : 2
+      ),
+      itemExtent: _kItemExtent,
+      backgroundColor: widget.backgroundColor,
+      squeeze: _kSqueeze,
+      looping: true,
+      onSelectedItemChanged: (int index) {
+        setState(() {
+          selectedSecond = index * widget.secondInterval;
+          widget.onTimerDurationChanged(
+              Duration(
+                  hours: selectedHour ?? 0,
+                  minutes: selectedMinute,
+                  seconds: selectedSecond!));
+        });
+      },
+      children: List<Widget>.generate(60 ~/ widget.secondInterval, (int index) {
+        final int second = index * widget.secondInterval;
+        final String label = localizations.timerPickerSecondLabel(second) ?? '';
+        final String semanticsLabel = textDirectionFactor == 1
+            ? localizations.timerPickerSecond(second) + label
+            : label + localizations.timerPickerSecond(second);
+
+        return Semantics(
+          label: semanticsLabel,
+          excludeSemantics: true,
+          child: _buildPickerNumberLabel(localizations.timerPickerSecond(second), additionalPadding),
+        );
+      }),
+      selectionOverlay: selectionOverlay,
+    );
+  }
+
+  Widget _buildSecondColumn(EdgeInsetsDirectional additionalPadding, Widget selectionOverlay) {
+    additionalPadding = EdgeInsetsDirectional.only(
+      start: math.max(additionalPadding.start, 0),
+      end: math.max(additionalPadding.end, 0),
+    );
+
+    return Stack(
+      children: <Widget>[
+        NotificationListener<ScrollEndNotification>(
+          onNotification: (ScrollEndNotification notification) {
+            setState(() { lastSelectedSecond = selectedSecond; });
+            return false;
+          },
+          child: _buildSecondPicker(additionalPadding, selectionOverlay),
+        ),
+        _buildLabel(
+          localizations.timerPickerSecondLabel(lastSelectedSecond ?? selectedSecond!) ?? '',
+          additionalPadding,
+        ),
+      ],
+    );
+  }
+
+  // Returns [CupertinoTextThemeData.pickerTextStyle] and magnifies the fontSize
+  // by [magnification].
+  TextStyle _textStyleFrom(BuildContext context, [double magnification = 1.0]) {
+    final TextStyle textStyle = CupertinoTheme.of(context).textTheme.pickerTextStyle;
+    return textStyle.copyWith(
+      fontSize: textStyle.fontSize! * magnification
+    );
+  }
+
+  // Calculate the number label center point by padding start and position to
+  // get a reasonable offAxisFraction.
+  double _calculateOffAxisFraction(double paddingStart, int position) {
+    final double centerPoint = paddingStart + (numberLabelWidth / 2);
+
+    // Compute the offAxisFraction needed to be straight within the pickerColumn.
+    final double pickerColumnOffAxisFraction =
+        0.5 - centerPoint / pickerColumnWidth;
+    // Position is to calculate the reasonable offAxisFraction in the picker.
+    final double timerPickerOffAxisFraction =
+        0.5 - (centerPoint + pickerColumnWidth * position) / totalWidth;
+    return (pickerColumnOffAxisFraction - timerPickerOffAxisFraction) * textDirectionFactor;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return LayoutBuilder(
+      builder: (BuildContext context, BoxConstraints constraints) {
+        // The timer picker can be divided into columns corresponding to hour,
+        // minute, and second. Each column consists of a scrollable and a fixed
+        // label on top of it.
+        List<Widget> columns;
+
+        if (widget.mode == CupertinoTimerPickerMode.hms){
+          // Pad the widget to make it as wide as `_kPickerWidth`.
+          pickerColumnWidth =
+              _kTimerPickerColumnIntrinsicWidth + (_kTimerPickerHalfColumnPadding * 2);
+          totalWidth = pickerColumnWidth * 3;
+        } else {
+          // The default totalWidth for 2-column modes.
+          totalWidth = _kPickerWidth;
+          pickerColumnWidth = totalWidth / 2;
+        }
+
+        if (constraints.maxWidth < totalWidth) {
+          totalWidth = constraints.maxWidth;
+          pickerColumnWidth =
+              totalWidth / (widget.mode == CupertinoTimerPickerMode.hms ? 3 : 2);
+        }
+
+        final double baseLabelContentWidth = numberLabelWidth + _kTimerPickerLabelPadSize;
+        final double minuteLabelContentWidth = baseLabelContentWidth + minuteLabelWidth;
+
+        switch (widget.mode) {
+          case CupertinoTimerPickerMode.hm:
+          // Pad the widget to make it as wide as `_kPickerWidth`.
+            final double hourLabelContentWidth = baseLabelContentWidth + hourLabelWidth;
+            double hourColumnStartPadding =
+                pickerColumnWidth - hourLabelContentWidth - _kTimerPickerHalfColumnPadding;
+            if (hourColumnStartPadding < _kTimerPickerMinHorizontalPadding)
+              hourColumnStartPadding = _kTimerPickerMinHorizontalPadding;
+
+            double minuteColumnEndPadding =
+                pickerColumnWidth - minuteLabelContentWidth - _kTimerPickerHalfColumnPadding;
+            if (minuteColumnEndPadding < _kTimerPickerMinHorizontalPadding)
+              minuteColumnEndPadding = _kTimerPickerMinHorizontalPadding;
+
+            columns = <Widget>[
+              _buildHourColumn(
+                  EdgeInsetsDirectional.only(
+                      start: hourColumnStartPadding,
+                      end: pickerColumnWidth - hourColumnStartPadding - hourLabelContentWidth
+                  ),
+                  _leftSelectionOverlay
+              ),
+              _buildMinuteColumn(
+                  EdgeInsetsDirectional.only(
+                      start: pickerColumnWidth - minuteColumnEndPadding - minuteLabelContentWidth,
+                      end: minuteColumnEndPadding
+                  ),
+                  _rightSelectionOverlay
+              ),
+            ];
+            break;
+          case CupertinoTimerPickerMode.ms:
+            final double secondLabelContentWidth = baseLabelContentWidth + secondLabelWidth;
+            double secondColumnEndPadding =
+                pickerColumnWidth - secondLabelContentWidth - _kTimerPickerHalfColumnPadding;
+            if (secondColumnEndPadding < _kTimerPickerMinHorizontalPadding)
+              secondColumnEndPadding = _kTimerPickerMinHorizontalPadding;
+
+            double minuteColumnStartPadding =
+                pickerColumnWidth - minuteLabelContentWidth - _kTimerPickerHalfColumnPadding;
+            if (minuteColumnStartPadding < _kTimerPickerMinHorizontalPadding)
+              minuteColumnStartPadding = _kTimerPickerMinHorizontalPadding;
+
+            columns = <Widget>[
+              _buildMinuteColumn(
+                  EdgeInsetsDirectional.only(
+                      start: minuteColumnStartPadding,
+                      end: pickerColumnWidth - minuteColumnStartPadding - minuteLabelContentWidth
+                  ),
+                  _leftSelectionOverlay
+              ),
+              _buildSecondColumn(
+                  EdgeInsetsDirectional.only(
+                      start: pickerColumnWidth - secondColumnEndPadding - minuteLabelContentWidth,
+                      end: secondColumnEndPadding
+                  ),
+                  _rightSelectionOverlay
+              ),
+            ];
+            break;
+          case CupertinoTimerPickerMode.hms:
+            final double hourColumnEndPadding =
+                pickerColumnWidth - baseLabelContentWidth - hourLabelWidth - _kTimerPickerMinHorizontalPadding;
+            final double minuteColumnPadding =
+                (pickerColumnWidth - minuteLabelContentWidth) / 2;
+            final double secondColumnStartPadding =
+                pickerColumnWidth - baseLabelContentWidth - secondLabelWidth - _kTimerPickerMinHorizontalPadding;
+
+            columns = <Widget>[
+              _buildHourColumn(
+                  EdgeInsetsDirectional.only(
+                      start: _kTimerPickerMinHorizontalPadding,
+                      end: math.max(hourColumnEndPadding, 0)
+                  ),
+                  _leftSelectionOverlay
+              ),
+              _buildMinuteColumn(
+                  EdgeInsetsDirectional.only(
+                      start: minuteColumnPadding,
+                      end: minuteColumnPadding
+                  ),
+                  _centerSelectionOverlay
+              ),
+              _buildSecondColumn(
+                  EdgeInsetsDirectional.only(
+                      start: math.max(secondColumnStartPadding, 0),
+                      end: _kTimerPickerMinHorizontalPadding
+                  ),
+                  _rightSelectionOverlay
+              ),
+            ];
+            break;
+        }
+        final CupertinoThemeData themeData = CupertinoTheme.of(context);
+        return MediaQuery(
+          // The native iOS picker's text scaling is fixed, so we will also fix it
+          // as well in our picker.
+          data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
+          child: CupertinoTheme(
+            data: themeData.copyWith(
+              textTheme: themeData.textTheme.copyWith(
+                pickerTextStyle: _textStyleFrom(context, _kTimerPickerMagnification),
+              ),
+            ),
+            child: Align(
+              alignment: widget.alignment,
+              child: Container(
+                color: CupertinoDynamicColor.maybeResolve(widget.backgroundColor, context),
+                width: totalWidth,
+                height: _kPickerHeight,
+                child: DefaultTextStyle(
+                  style: _textStyleFrom(context),
+                  child: Row(children: columns.map((Widget child) => Expanded(child: child)).toList(growable: false)),
+                ),
+              ),
+            ),
+          ),
+        );
+      },
+    );
+  }
+}
diff --git a/lib/src/cupertino/debug.dart b/lib/src/cupertino/debug.dart
new file mode 100644
index 0000000..ac83642
--- /dev/null
+++ b/lib/src/cupertino/debug.dart
@@ -0,0 +1,46 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'localizations.dart';
+
+/// Asserts that the given context has a [Localizations] ancestor that contains
+/// a [CupertinoLocalizations] delegate.
+///
+/// To call this function, use the following pattern, typically in the
+/// relevant Widget's build method:
+///
+/// ```dart
+/// assert(debugCheckHasCupertinoLocalizations(context));
+/// ```
+///
+/// Does nothing if asserts are disabled. Always returns true.
+bool debugCheckHasCupertinoLocalizations(BuildContext context) {
+  assert(() {
+    if (Localizations.of<CupertinoLocalizations>(context, CupertinoLocalizations) == null) {
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('No CupertinoLocalizations found.'),
+        ErrorDescription(
+          '${context.widget.runtimeType} widgets require CupertinoLocalizations '
+          'to be provided by a Localizations widget ancestor.'
+        ),
+        ErrorDescription(
+          'The cupertino library uses Localizations to generate messages, '
+          'labels, and abbreviations.'
+        ),
+        ErrorHint(
+          'To introduce a CupertinoLocalizations, either use a '
+          'CupertinoApp at the root of your application to include them '
+          'automatically, or add a Localization widget with a '
+          'CupertinoLocalizations delegate.'
+        ),
+        ...context.describeMissingAncestor(expectedAncestorType: CupertinoLocalizations)
+      ]);
+    }
+    return true;
+  }());
+  return true;
+}
diff --git a/lib/src/cupertino/dialog.dart b/lib/src/cupertino/dialog.dart
new file mode 100644
index 0000000..35ad624
--- /dev/null
+++ b/lib/src/cupertino/dialog.dart
@@ -0,0 +1,1817 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show ImageFilter;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'interface_level.dart';
+import 'localizations.dart';
+import 'scrollbar.dart';
+
+// TODO(abarth): These constants probably belong somewhere more general.
+
+// Used XD to flutter plugin(https://github.com/AdobeXD/xd-to-flutter-plugin/)
+// to derive values of TextStyle(height and letterSpacing) from
+// Adobe XD template for iOS 13, which can be found in
+// Apple Design Resources(https://developer.apple.com/design/resources/).
+// However the values are not exactly the same as native, so eyeballing is needed.
+const TextStyle _kCupertinoDialogTitleStyle = TextStyle(
+  fontFamily: '.SF UI Display',
+  inherit: false,
+  fontSize: 17.0,
+  fontWeight: FontWeight.w600,
+  height: 1.3,
+  letterSpacing: -0.5,
+  textBaseline: TextBaseline.alphabetic,
+);
+
+const TextStyle _kCupertinoDialogContentStyle = TextStyle(
+  fontFamily: '.SF UI Text',
+  inherit: false,
+  fontSize: 13.0,
+  fontWeight: FontWeight.w400,
+  height: 1.35,
+  letterSpacing: -0.2,
+  textBaseline: TextBaseline.alphabetic,
+);
+
+const TextStyle _kCupertinoDialogActionStyle = TextStyle(
+  fontFamily: '.SF UI Text',
+  inherit: false,
+  fontSize: 16.8,
+  fontWeight: FontWeight.w400,
+  textBaseline: TextBaseline.alphabetic,
+);
+
+// iOS dialogs have a normal display width and another display width that is
+// used when the device is in accessibility mode. Each of these widths are
+// listed below.
+const double _kCupertinoDialogWidth = 270.0;
+const double _kAccessibilityCupertinoDialogWidth = 310.0;
+
+const double _kBlurAmount = 20.0;
+const double _kEdgePadding = 20.0;
+const double _kMinButtonHeight = 45.0;
+const double _kMinButtonFontSize = 10.0;
+const double _kDialogCornerRadius = 14.0;
+const double _kDividerThickness = 1.0;
+
+// A translucent color that is painted on top of the blurred backdrop as the
+// dialog's background color
+// Extracted from https://developer.apple.com/design/resources/.
+const Color _kDialogColor = CupertinoDynamicColor.withBrightness(
+  color: Color(0xCCF2F2F2),
+  darkColor: Color(0xBF1E1E1E),
+);
+
+// Translucent light gray that is painted on top of the blurred backdrop as the
+// background color of a pressed button.
+// Eyeballed from iOS 13 beta simulator.
+const Color _kDialogPressedColor = CupertinoDynamicColor.withBrightness(
+  color: Color(0xFFE1E1E1),
+  darkColor: Color(0xFF2E2E2E),
+);
+
+// The alert dialog layout policy changes depending on whether the user is using
+// a "regular" font size vs a "large" font size. This is a spectrum. There are
+// many "regular" font sizes and many "large" font sizes. But depending on which
+// policy is currently being used, a dialog is laid out differently.
+//
+// Empirically, the jump from one policy to the other occurs at the following text
+// scale factors:
+// Largest regular scale factor:  1.3529411764705883
+// Smallest large scale factor:   1.6470588235294117
+//
+// The following constant represents a division in text scale factor beyond which
+// we want to change how the dialog is laid out.
+const double _kMaxRegularTextScaleFactor = 1.4;
+
+// Accessibility mode on iOS is determined by the text scale factor that the
+// user has selected.
+bool _isInAccessibilityMode(BuildContext context) {
+  final MediaQueryData? data = MediaQuery.maybeOf(context);
+  return data != null && data.textScaleFactor > _kMaxRegularTextScaleFactor;
+}
+
+/// An iOS-style alert dialog.
+///
+/// An alert dialog informs the user about situations that require
+/// acknowledgement. An alert dialog has an optional title, optional content,
+/// and an optional list of actions. The title is displayed above the content
+/// and the actions are displayed below the content.
+///
+/// This dialog styles its title and content (typically a message) to match the
+/// standard iOS title and message dialog text style. These default styles can
+/// be overridden by explicitly defining [TextStyle]s for [Text] widgets that
+/// are part of the title or content.
+///
+/// To display action buttons that look like standard iOS dialog buttons,
+/// provide [CupertinoDialogAction]s for the [actions] given to this dialog.
+///
+/// Typically passed as the child widget to [showDialog], which displays the
+/// dialog.
+///
+/// See also:
+///
+///  * [CupertinoPopupSurface], which is a generic iOS-style popup surface that
+///    holds arbitrary content to create custom popups.
+///  * [CupertinoDialogAction], which is an iOS-style dialog button.
+///  * [AlertDialog], a Material Design alert dialog.
+///  * <https://developer.apple.com/ios/human-interface-guidelines/views/alerts/>
+class CupertinoAlertDialog extends StatelessWidget {
+  /// Creates an iOS-style alert dialog.
+  ///
+  /// The [actions] must not be null.
+  const CupertinoAlertDialog({
+    Key? key,
+    this.title,
+    this.content,
+    this.actions = const <Widget>[],
+    this.scrollController,
+    this.actionScrollController,
+    this.insetAnimationDuration = const Duration(milliseconds: 100),
+    this.insetAnimationCurve = Curves.decelerate,
+  }) : assert(actions != null),
+       super(key: key);
+
+  /// The (optional) title of the dialog is displayed in a large font at the top
+  /// of the dialog.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? title;
+
+  /// The (optional) content of the dialog is displayed in the center of the
+  /// dialog in a lighter font.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? content;
+
+  /// The (optional) set of actions that are displayed at the bottom of the
+  /// dialog.
+  ///
+  /// Typically this is a list of [CupertinoDialogAction] widgets.
+  final List<Widget> actions;
+
+  /// A scroll controller that can be used to control the scrolling of the
+  /// [content] in the dialog.
+  ///
+  /// Defaults to null, and is typically not needed, since most alert messages
+  /// are short.
+  ///
+  /// See also:
+  ///
+  ///  * [actionScrollController], which can be used for controlling the actions
+  ///    section when there are many actions.
+  final ScrollController? scrollController;
+
+  /// A scroll controller that can be used to control the scrolling of the
+  /// actions in the dialog.
+  ///
+  /// Defaults to null, and is typically not needed.
+  ///
+  /// See also:
+  ///
+  ///  * [scrollController], which can be used for controlling the [content]
+  ///    section when it is long.
+  final ScrollController? actionScrollController;
+
+  /// {@macro flutter.material.dialog.insetAnimationDuration}
+  final Duration insetAnimationDuration;
+
+  /// {@macro flutter.material.dialog.insetAnimationCurve}
+  final Curve insetAnimationCurve;
+
+  Widget _buildContent(BuildContext context) {
+    final List<Widget> children = <Widget>[
+      if (title != null || content != null)
+        Flexible(
+          flex: 3,
+          child: _CupertinoAlertContentSection(
+            title: title,
+            content: content,
+            scrollController: scrollController,
+          ),
+        ),
+    ];
+
+    return Container(
+      color: CupertinoDynamicColor.resolve(_kDialogColor, context),
+      child: Column(
+        mainAxisSize: MainAxisSize.min,
+        crossAxisAlignment: CrossAxisAlignment.stretch,
+        children: children,
+      ),
+    );
+  }
+
+  Widget _buildActions() {
+    Widget actionSection = Container(
+      height: 0.0,
+    );
+    if (actions.isNotEmpty) {
+      actionSection = _CupertinoAlertActionSection(
+        children: actions,
+        scrollController: actionScrollController,
+      );
+    }
+
+    return actionSection;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final CupertinoLocalizations localizations = CupertinoLocalizations.of(context);
+    final bool isInAccessibilityMode = _isInAccessibilityMode(context);
+    final double textScaleFactor = MediaQuery.of(context).textScaleFactor;
+    return CupertinoUserInterfaceLevel(
+      data: CupertinoUserInterfaceLevelData.elevated,
+      child: MediaQuery(
+        data: MediaQuery.of(context).copyWith(
+          // iOS does not shrink dialog content below a 1.0 scale factor
+          textScaleFactor: math.max(textScaleFactor, 1.0),
+        ),
+        child: LayoutBuilder(
+          builder: (BuildContext context, BoxConstraints constraints) {
+            return AnimatedPadding(
+              padding: MediaQuery.of(context).viewInsets +
+                  const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0),
+              duration: insetAnimationDuration,
+              curve: insetAnimationCurve,
+              child: MediaQuery.removeViewInsets(
+                removeLeft: true,
+                removeTop: true,
+                removeRight: true,
+                removeBottom: true,
+                context: context,
+                child: Center(
+                  child: Container(
+                    margin: const EdgeInsets.symmetric(vertical: _kEdgePadding),
+                    width: isInAccessibilityMode
+                      ? _kAccessibilityCupertinoDialogWidth
+                      : _kCupertinoDialogWidth,
+                    child: CupertinoPopupSurface(
+                      isSurfacePainted: false,
+                      child: Semantics(
+                        namesRoute: true,
+                        scopesRoute: true,
+                        explicitChildNodes: true,
+                        label: localizations.alertDialogLabel,
+                        child: _CupertinoDialogRenderWidget(
+                          contentSection: _buildContent(context),
+                          actionsSection: _buildActions(),
+                        ),
+                      ),
+                    ),
+                  ),
+                ),
+              ),
+            );
+          },
+        ),
+      ),
+    );
+  }
+}
+
+/// An iOS-style dialog.
+///
+/// This dialog widget does not have any opinion about the contents of the
+/// dialog. Rather than using this widget directly, consider using
+/// [CupertinoAlertDialog], which implement a specific kind of dialog.
+///
+/// Push with `Navigator.of(..., rootNavigator: true)` when using with
+/// [CupertinoTabScaffold] to ensure that the dialog appears above the tabs.
+///
+/// See also:
+///
+///  * [CupertinoAlertDialog], which is a dialog with title, contents, and
+///    actions.
+///  * <https://developer.apple.com/ios/human-interface-guidelines/views/alerts/>
+@Deprecated(
+  'Use CupertinoAlertDialog for alert dialogs. Use CupertinoPopupSurface for custom popups. '
+  'This feature was deprecated after v0.2.3.'
+)
+class CupertinoDialog extends StatelessWidget {
+  /// Creates an iOS-style dialog.
+  const CupertinoDialog({
+    Key? key,
+    this.child,
+  }) : super(key: key);
+
+  /// The widget below this widget in the tree.
+  final Widget? child;
+
+  @override
+  Widget build(BuildContext context) {
+    return Center(
+      child: SizedBox(
+        width: _kCupertinoDialogWidth,
+        child: CupertinoPopupSurface(
+          child: child,
+        ),
+      ),
+    );
+  }
+}
+
+/// Rounded rectangle surface that looks like an iOS popup surface, e.g., alert dialog
+/// and action sheet.
+///
+/// A [CupertinoPopupSurface] can be configured to paint or not paint a white
+/// color on top of its blurred area. Typical usage should paint white on top
+/// of the blur. However, the white paint can be disabled for the purpose of
+/// rendering divider gaps for a more complicated layout, e.g., [CupertinoAlertDialog].
+/// Additionally, the white paint can be disabled to render a blurred rounded
+/// rectangle without any color (similar to iOS's volume control popup).
+///
+/// See also:
+///
+///  * [CupertinoAlertDialog], which is a dialog with a title, content, and
+///    actions.
+///  * <https://developer.apple.com/ios/human-interface-guidelines/views/alerts/>
+class CupertinoPopupSurface extends StatelessWidget {
+  /// Creates an iOS-style rounded rectangle popup surface.
+  const CupertinoPopupSurface({
+    Key? key,
+    this.isSurfacePainted = true,
+    this.child,
+  }) : super(key: key);
+
+  /// Whether or not to paint a translucent white on top of this surface's
+  /// blurred background. [isSurfacePainted] should be true for a typical popup
+  /// that contains content without any dividers. A popup that requires dividers
+  /// should set [isSurfacePainted] to false and then paint its own surface area.
+  ///
+  /// Some popups, like iOS's volume control popup, choose to render a blurred
+  /// area without any white paint covering it. To achieve this effect,
+  /// [isSurfacePainted] should be set to false.
+  final bool isSurfacePainted;
+
+  /// The widget below this widget in the tree.
+  final Widget? child;
+
+  @override
+  Widget build(BuildContext context) {
+    return ClipRRect(
+      borderRadius: BorderRadius.circular(_kDialogCornerRadius),
+      child: BackdropFilter(
+        filter: ImageFilter.blur(sigmaX: _kBlurAmount, sigmaY: _kBlurAmount),
+        child: Container(
+          color: isSurfacePainted ? CupertinoDynamicColor.resolve(_kDialogColor, context) : null,
+          child: child,
+        ),
+      ),
+    );
+  }
+}
+
+// iOS style layout policy widget for sizing an alert dialog's content section and
+// action button section.
+//
+// See [_RenderCupertinoDialog] for specific layout policy details.
+class _CupertinoDialogRenderWidget extends RenderObjectWidget {
+  const _CupertinoDialogRenderWidget({
+    Key? key,
+    required this.contentSection,
+    required this.actionsSection,
+  }) : super(key: key);
+
+  final Widget contentSection;
+  final Widget actionsSection;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _RenderCupertinoDialog(
+      dividerThickness: _kDividerThickness / MediaQuery.of(context).devicePixelRatio,
+      isInAccessibilityMode: _isInAccessibilityMode(context),
+      dividerColor: CupertinoDynamicColor.resolve(CupertinoColors.separator, context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderCupertinoDialog renderObject) {
+    renderObject
+      ..isInAccessibilityMode = _isInAccessibilityMode(context)
+      ..dividerColor = CupertinoDynamicColor.resolve(CupertinoColors.separator, context);
+  }
+
+  @override
+  RenderObjectElement createElement() {
+    return _CupertinoDialogRenderElement(this);
+  }
+}
+
+class _CupertinoDialogRenderElement extends RenderObjectElement {
+  _CupertinoDialogRenderElement(_CupertinoDialogRenderWidget widget) : super(widget);
+
+  Element? _contentElement;
+  Element? _actionsElement;
+
+  @override
+  _CupertinoDialogRenderWidget get widget => super.widget as _CupertinoDialogRenderWidget;
+
+  @override
+  _RenderCupertinoDialog get renderObject => super.renderObject as _RenderCupertinoDialog;
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    if (_contentElement != null) {
+      visitor(_contentElement!);
+    }
+    if (_actionsElement != null) {
+      visitor(_actionsElement!);
+    }
+  }
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    _contentElement = updateChild(_contentElement, widget.contentSection, _AlertDialogSections.contentSection);
+    _actionsElement = updateChild(_actionsElement, widget.actionsSection, _AlertDialogSections.actionsSection);
+  }
+
+  @override
+  void insertRenderObjectChild(RenderObject child, _AlertDialogSections slot) {
+    assert(slot != null);
+    switch (slot) {
+      case _AlertDialogSections.contentSection:
+        renderObject.contentSection = child as RenderBox;
+        break;
+      case _AlertDialogSections.actionsSection:
+        renderObject.actionsSection = child as RenderBox;
+        break;
+    }
+  }
+
+  @override
+  void moveRenderObjectChild(RenderObject child, _AlertDialogSections oldSlot, _AlertDialogSections newSlot) {
+    assert(false);
+  }
+
+  @override
+  void update(RenderObjectWidget newWidget) {
+    super.update(newWidget);
+    _contentElement = updateChild(_contentElement, widget.contentSection, _AlertDialogSections.contentSection);
+    _actionsElement = updateChild(_actionsElement, widget.actionsSection, _AlertDialogSections.actionsSection);
+  }
+
+  @override
+  void forgetChild(Element child) {
+    assert(child == _contentElement || child == _actionsElement);
+    if (_contentElement == child) {
+      _contentElement = null;
+    } else {
+      assert(_actionsElement == child);
+      _actionsElement = null;
+    }
+    super.forgetChild(child);
+  }
+
+  @override
+  void removeRenderObjectChild(RenderObject child, _AlertDialogSections slot) {
+    assert(child == renderObject.contentSection || child == renderObject.actionsSection);
+    if (renderObject.contentSection == child) {
+      renderObject.contentSection = null;
+    } else {
+      assert(renderObject.actionsSection == child);
+      renderObject.actionsSection = null;
+    }
+  }
+}
+
+// iOS style layout policy for sizing an alert dialog's content section and action
+// button section.
+//
+// The policy is as follows:
+//
+// If all content and buttons fit on screen:
+// The content section and action button section are sized intrinsically and centered
+// vertically on screen.
+//
+// If all content and buttons do not fit on screen, and iOS is NOT in accessibility mode:
+// A minimum height for the action button section is calculated. The action
+// button section will not be rendered shorter than this minimum.  See
+// [_RenderCupertinoDialogActions] for the minimum height calculation.
+//
+// With the minimum action button section calculated, the content section can
+// take up as much space as is available, up to the point that it hits the
+// minimum button height at the bottom.
+//
+// After the content section is laid out, the action button section is allowed
+// to take up any remaining space that was not consumed by the content section.
+//
+// If all content and buttons do not fit on screen, and iOS IS in accessibility mode:
+// The button section is given up to 50% of the available height. Then the content
+// section is given whatever height remains.
+class _RenderCupertinoDialog extends RenderBox {
+  _RenderCupertinoDialog({
+    RenderBox? contentSection,
+    RenderBox? actionsSection,
+    double dividerThickness = 0.0,
+    bool isInAccessibilityMode = false,
+    required Color dividerColor,
+  }) : _contentSection = contentSection,
+       _actionsSection = actionsSection,
+       _dividerThickness = dividerThickness,
+       _isInAccessibilityMode = isInAccessibilityMode,
+       _dividerPaint = Paint()
+        ..color = dividerColor
+        ..style = PaintingStyle.fill;
+
+
+  RenderBox? get contentSection => _contentSection;
+  RenderBox? _contentSection;
+  set contentSection(RenderBox? newContentSection) {
+    if (newContentSection != _contentSection) {
+      if (_contentSection != null) {
+        dropChild(_contentSection!);
+      }
+      _contentSection = newContentSection;
+      if (_contentSection != null) {
+        adoptChild(_contentSection!);
+      }
+    }
+  }
+
+  RenderBox? get actionsSection => _actionsSection;
+  RenderBox? _actionsSection;
+  set actionsSection(RenderBox? newActionsSection) {
+    if (newActionsSection != _actionsSection) {
+      if (null != _actionsSection) {
+        dropChild(_actionsSection!);
+      }
+      _actionsSection = newActionsSection;
+      if (null != _actionsSection) {
+        adoptChild(_actionsSection!);
+      }
+    }
+  }
+
+  bool get isInAccessibilityMode => _isInAccessibilityMode;
+  bool _isInAccessibilityMode;
+  set isInAccessibilityMode(bool newValue) {
+    if (newValue != _isInAccessibilityMode) {
+      _isInAccessibilityMode = newValue;
+      markNeedsLayout();
+    }
+  }
+
+  double get _dialogWidth => isInAccessibilityMode
+      ? _kAccessibilityCupertinoDialogWidth
+      : _kCupertinoDialogWidth;
+
+  final double _dividerThickness;
+  final Paint _dividerPaint;
+
+  Color get dividerColor => _dividerPaint.color;
+  set dividerColor(Color newValue) {
+    if (dividerColor == newValue) {
+      return;
+    }
+
+    _dividerPaint.color = newValue;
+    markNeedsPaint();
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    if (null != contentSection) {
+      contentSection!.attach(owner);
+    }
+    if (null != actionsSection) {
+      actionsSection!.attach(owner);
+    }
+  }
+
+  @override
+  void detach() {
+    super.detach();
+    if (null != contentSection) {
+      contentSection!.detach();
+    }
+    if (null != actionsSection) {
+      actionsSection!.detach();
+    }
+  }
+
+  @override
+  void redepthChildren() {
+    if (null != contentSection) {
+      redepthChild(contentSection!);
+    }
+    if (null != actionsSection) {
+      redepthChild(actionsSection!);
+    }
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! BoxParentData) {
+      child.parentData = BoxParentData();
+    }
+  }
+
+  @override
+  void visitChildren(RenderObjectVisitor visitor) {
+    if (contentSection != null) {
+      visitor(contentSection!);
+    }
+    if (actionsSection != null) {
+      visitor(actionsSection!);
+    }
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() => <DiagnosticsNode>[
+    if (contentSection != null) contentSection!.toDiagnosticsNode(name: 'content'),
+    if (actionsSection != null) actionsSection!.toDiagnosticsNode(name: 'actions'),
+  ];
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    return _dialogWidth;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    return _dialogWidth;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    final double contentHeight = contentSection!.getMinIntrinsicHeight(width);
+    final double actionsHeight = actionsSection!.getMinIntrinsicHeight(width);
+    final bool hasDivider = contentHeight > 0.0 && actionsHeight > 0.0;
+    final double height = contentHeight + (hasDivider ? _dividerThickness : 0.0) + actionsHeight;
+
+    if (height.isFinite)
+      return height;
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    final double contentHeight = contentSection!.getMaxIntrinsicHeight(width);
+    final double actionsHeight = actionsSection!.getMaxIntrinsicHeight(width);
+    final bool hasDivider = contentHeight > 0.0 && actionsHeight > 0.0;
+    final double height = contentHeight + (hasDivider ? _dividerThickness : 0.0) + actionsHeight;
+
+    if (height.isFinite)
+      return height;
+    return 0.0;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _performLayout(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.dryLayoutChild,
+    ).size;
+  }
+
+  @override
+  void performLayout() {
+    final _DialogSizes dialogSizes = _performLayout(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.layoutChild,
+    );
+    size = dialogSizes.size;
+
+    // Set the position of the actions box to sit at the bottom of the dialog.
+    // The content box defaults to the top left, which is where we want it.
+    assert(actionsSection!.parentData is BoxParentData);
+    final BoxParentData actionParentData = actionsSection!.parentData! as BoxParentData;
+    actionParentData.offset = Offset(0.0, dialogSizes.actionSectionYOffset);
+  }
+
+  _DialogSizes _performLayout({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
+    return isInAccessibilityMode
+        ? performAccessibilityLayout(
+          constraints: constraints,
+          layoutChild: layoutChild,
+        ) : performRegularLayout(
+          constraints: constraints,
+          layoutChild: layoutChild,
+        );
+  }
+
+  // When not in accessibility mode, an alert dialog might reduce the space
+  // for buttons to just over 1 button's height to make room for the content
+  // section.
+  _DialogSizes performRegularLayout({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
+    final bool hasDivider = contentSection!.getMaxIntrinsicHeight(_dialogWidth) > 0.0
+        && actionsSection!.getMaxIntrinsicHeight(_dialogWidth) > 0.0;
+    final double dividerThickness = hasDivider ? _dividerThickness : 0.0;
+
+    final double minActionsHeight = actionsSection!.getMinIntrinsicHeight(_dialogWidth);
+
+    final Size contentSize = layoutChild(
+      contentSection!,
+      constraints.deflate(EdgeInsets.only(bottom: minActionsHeight + dividerThickness)),
+    );
+
+    final Size actionsSize = layoutChild(
+      actionsSection!,
+      constraints.deflate(EdgeInsets.only(top: contentSize.height + dividerThickness)),
+    );
+
+    final double dialogHeight = contentSize.height + dividerThickness + actionsSize.height;
+
+    return _DialogSizes(
+      size: constraints.constrain(Size(_dialogWidth, dialogHeight)),
+      actionSectionYOffset: contentSize.height + dividerThickness,
+    );
+  }
+
+  // When in accessibility mode, an alert dialog will allow buttons to take
+  // up to 50% of the dialog height, even if the content exceeds available space.
+  _DialogSizes performAccessibilityLayout({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
+    final bool hasDivider = contentSection!.getMaxIntrinsicHeight(_dialogWidth) > 0.0
+        && actionsSection!.getMaxIntrinsicHeight(_dialogWidth) > 0.0;
+    final double dividerThickness = hasDivider ? _dividerThickness : 0.0;
+
+    final double maxContentHeight = contentSection!.getMaxIntrinsicHeight(_dialogWidth);
+    final double maxActionsHeight = actionsSection!.getMaxIntrinsicHeight(_dialogWidth);
+
+    final Size contentSize;
+    final Size actionsSize;
+    if (maxContentHeight + dividerThickness + maxActionsHeight > constraints.maxHeight) {
+      // There isn't enough room for everything. Following iOS's accessibility dialog
+      // layout policy, first we allow the actions to take up to 50% of the dialog
+      // height. Second we fill the rest of the available space with the content
+      // section.
+
+      actionsSize = layoutChild(
+        actionsSection!,
+        constraints.deflate(EdgeInsets.only(top: constraints.maxHeight / 2.0)),
+      );
+
+      contentSize = layoutChild(
+        contentSection!,
+        constraints.deflate(EdgeInsets.only(bottom: actionsSize.height + dividerThickness)),
+      );
+    } else {
+      // Everything fits. Give content and actions all the space they want.
+
+      contentSize = layoutChild(
+        contentSection!,
+        constraints,
+      );
+
+      actionsSize = layoutChild(
+        actionsSection!,
+        constraints.deflate(EdgeInsets.only(top: contentSize.height)),
+      );
+    }
+
+    // Calculate overall dialog height.
+    final double dialogHeight = contentSize.height + dividerThickness + actionsSize.height;
+
+    return _DialogSizes(
+      size: constraints.constrain(Size(_dialogWidth, dialogHeight)),
+      actionSectionYOffset: contentSize.height + dividerThickness,
+    );
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final BoxParentData contentParentData = contentSection!.parentData! as BoxParentData;
+    contentSection!.paint(context, offset + contentParentData.offset);
+
+    final bool hasDivider = contentSection!.size.height > 0.0 && actionsSection!.size.height > 0.0;
+    if (hasDivider) {
+      _paintDividerBetweenContentAndActions(context.canvas, offset);
+    }
+
+    final BoxParentData actionsParentData = actionsSection!.parentData! as BoxParentData;
+    actionsSection!.paint(context, offset + actionsParentData.offset);
+  }
+
+  void _paintDividerBetweenContentAndActions(Canvas canvas, Offset offset) {
+    canvas.drawRect(
+      Rect.fromLTWH(
+        offset.dx,
+        offset.dy + contentSection!.size.height,
+        size.width,
+        _dividerThickness,
+      ),
+      _dividerPaint,
+    );
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    final BoxParentData contentSectionParentData = contentSection!.parentData! as BoxParentData;
+    final BoxParentData actionsSectionParentData = actionsSection!.parentData! as BoxParentData;
+    return result.addWithPaintOffset(
+             offset: contentSectionParentData.offset,
+             position: position,
+             hitTest: (BoxHitTestResult result, Offset transformed) {
+               assert(transformed == position - contentSectionParentData.offset);
+               return contentSection!.hitTest(result, position: transformed);
+             },
+           )
+        || result.addWithPaintOffset(
+             offset: actionsSectionParentData.offset,
+             position: position,
+             hitTest: (BoxHitTestResult result, Offset transformed) {
+               assert(transformed == position - actionsSectionParentData.offset);
+               return actionsSection!.hitTest(result, position: transformed);
+             },
+           );
+  }
+}
+
+class _DialogSizes {
+  const _DialogSizes({required this.size, required this.actionSectionYOffset});
+
+  final Size size;
+  final double actionSectionYOffset;
+}
+
+// Visual components of an alert dialog that need to be explicitly sized and
+// laid out at runtime.
+enum _AlertDialogSections {
+  contentSection,
+  actionsSection,
+}
+
+// The "content section" of a CupertinoAlertDialog.
+//
+// If title is missing, then only content is added.  If content is
+// missing, then only title is added. If both are missing, then it returns
+// a SingleChildScrollView with a zero-sized Container.
+class _CupertinoAlertContentSection extends StatelessWidget {
+  const _CupertinoAlertContentSection({
+    Key? key,
+    this.title,
+    this.content,
+    this.scrollController,
+  }) : super(key: key);
+
+  // The (optional) title of the dialog is displayed in a large font at the top
+  // of the dialog.
+  //
+  // Typically a Text widget.
+  final Widget? title;
+
+  // The (optional) content of the dialog is displayed in the center of the
+  // dialog in a lighter font.
+  //
+  // Typically a Text widget.
+  final Widget? content;
+
+  // A scroll controller that can be used to control the scrolling of the
+  // content in the dialog.
+  //
+  // Defaults to null, and is typically not needed, since most alert contents
+  // are short.
+  final ScrollController? scrollController;
+
+  @override
+  Widget build(BuildContext context) {
+    if (title == null && content == null) {
+      return SingleChildScrollView(
+        controller: scrollController,
+        child: const SizedBox(width: 0.0, height: 0.0),
+      );
+    }
+
+    final double textScaleFactor = MediaQuery.of(context).textScaleFactor;
+    final List<Widget> titleContentGroup = <Widget>[
+      if (title != null)
+        Padding(
+          padding: EdgeInsets.only(
+            left: _kEdgePadding,
+            right: _kEdgePadding,
+            bottom: content == null ? _kEdgePadding : 1.0,
+            top: _kEdgePadding * textScaleFactor,
+          ),
+          child: DefaultTextStyle(
+            style: _kCupertinoDialogTitleStyle.copyWith(
+              color: CupertinoDynamicColor.resolve(CupertinoColors.label, context),
+            ),
+            textAlign: TextAlign.center,
+            child: title!,
+          ),
+        ),
+      if (content != null)
+        Padding(
+          padding: EdgeInsets.only(
+            left: _kEdgePadding,
+            right: _kEdgePadding,
+            bottom: _kEdgePadding * textScaleFactor,
+            top: title == null ? _kEdgePadding : 1.0,
+          ),
+          child: DefaultTextStyle(
+            style: _kCupertinoDialogContentStyle.copyWith(
+              color: CupertinoDynamicColor.resolve(CupertinoColors.label, context),
+            ),
+            textAlign: TextAlign.center,
+            child: content!,
+          ),
+        ),
+    ];
+
+    return CupertinoScrollbar(
+      child: SingleChildScrollView(
+        controller: scrollController,
+        child: Column(
+          mainAxisSize: MainAxisSize.max,
+          crossAxisAlignment: CrossAxisAlignment.stretch,
+          children: titleContentGroup,
+        ),
+      ),
+    );
+  }
+}
+
+// The "actions section" of a [CupertinoAlertDialog].
+//
+// See [_RenderCupertinoDialogActions] for details about action button sizing
+// and layout.
+class _CupertinoAlertActionSection extends StatefulWidget {
+  const _CupertinoAlertActionSection({
+    Key? key,
+    required this.children,
+    this.scrollController,
+  }) : assert(children != null),
+       super(key: key);
+
+  final List<Widget> children;
+
+  // A scroll controller that can be used to control the scrolling of the
+  // actions in the dialog.
+  //
+  // Defaults to null, and is typically not needed, since most alert dialogs
+  // don't have many actions.
+  final ScrollController? scrollController;
+
+  @override
+  _CupertinoAlertActionSectionState createState() => _CupertinoAlertActionSectionState();
+}
+
+class _CupertinoAlertActionSectionState extends State<_CupertinoAlertActionSection> {
+  @override
+  Widget build(BuildContext context) {
+    final double devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
+
+    final List<Widget> interactiveButtons = <Widget>[];
+    for (int i = 0; i < widget.children.length; i += 1) {
+      interactiveButtons.add(
+        _PressableActionButton(
+          child: widget.children[i],
+        ),
+      );
+    }
+
+    return CupertinoScrollbar(
+      child: SingleChildScrollView(
+        controller: widget.scrollController,
+        child: _CupertinoDialogActionsRenderWidget(
+          actionButtons: interactiveButtons,
+          dividerThickness: _kDividerThickness / devicePixelRatio,
+        ),
+      ),
+    );
+  }
+}
+
+// Button that updates its render state when pressed.
+//
+// The pressed state is forwarded to an _ActionButtonParentDataWidget. The
+// corresponding _ActionButtonParentData is then interpreted and rendered
+// appropriately by _RenderCupertinoDialogActions.
+class _PressableActionButton extends StatefulWidget {
+  const _PressableActionButton({
+    required this.child,
+  });
+
+  final Widget child;
+
+  @override
+  _PressableActionButtonState createState() => _PressableActionButtonState();
+}
+
+class _PressableActionButtonState extends State<_PressableActionButton> {
+  bool _isPressed = false;
+
+  @override
+  Widget build(BuildContext context) {
+    return _ActionButtonParentDataWidget(
+      isPressed: _isPressed,
+      child: MergeSemantics(
+        // TODO(mattcarroll): Button press dynamics need overhaul for iOS:
+        // https://github.com/flutter/flutter/issues/19786
+        child: GestureDetector(
+          excludeFromSemantics: true,
+          behavior: HitTestBehavior.opaque,
+          onTapDown: (TapDownDetails details) => setState(() {
+            _isPressed = true;
+          }),
+          onTapUp: (TapUpDetails details) => setState(() {
+            _isPressed = false;
+          }),
+          // TODO(mattcarroll): Cancel is currently triggered when user moves
+          //  past slop instead of off button: https://github.com/flutter/flutter/issues/19783
+          onTapCancel: () => setState(() => _isPressed = false),
+          child: widget.child,
+        ),
+      ),
+    );
+  }
+}
+
+// ParentDataWidget that updates _ActionButtonParentData for an action button.
+//
+// Each action button requires knowledge of whether or not it is pressed so that
+// the dialog can correctly render the button. The pressed state is held within
+// _ActionButtonParentData. _ActionButtonParentDataWidget is responsible for
+// updating the pressed state of an _ActionButtonParentData based on the
+// incoming [isPressed] property.
+class _ActionButtonParentDataWidget extends ParentDataWidget<_ActionButtonParentData> {
+  const _ActionButtonParentDataWidget({
+    Key? key,
+    required this.isPressed,
+    required Widget child,
+  }) : super(key: key, child: child);
+
+  final bool isPressed;
+
+  @override
+  void applyParentData(RenderObject renderObject) {
+    assert(renderObject.parentData is _ActionButtonParentData);
+    final _ActionButtonParentData parentData = renderObject.parentData! as _ActionButtonParentData;
+    if (parentData.isPressed != isPressed) {
+      parentData.isPressed = isPressed;
+
+      // Force a repaint.
+      final AbstractNode? targetParent = renderObject.parent;
+      if (targetParent is RenderObject)
+        targetParent.markNeedsPaint();
+    }
+  }
+
+  @override
+  Type get debugTypicalAncestorWidgetClass => _CupertinoDialogActionsRenderWidget;
+}
+
+// ParentData applied to individual action buttons that report whether or not
+// that button is currently pressed by the user.
+class _ActionButtonParentData extends MultiChildLayoutParentData {
+  _ActionButtonParentData({
+    this.isPressed = false,
+  });
+
+  bool isPressed;
+}
+
+/// A button typically used in a [CupertinoAlertDialog].
+///
+/// See also:
+///
+///  * [CupertinoAlertDialog], a dialog that informs the user about situations
+///    that require acknowledgement.
+class CupertinoDialogAction extends StatelessWidget {
+  /// Creates an action for an iOS-style dialog.
+  const CupertinoDialogAction({
+    Key? key,
+    this.onPressed,
+    this.isDefaultAction = false,
+    this.isDestructiveAction = false,
+    this.textStyle,
+    required this.child,
+  }) : assert(child != null),
+       assert(isDefaultAction != null),
+       assert(isDestructiveAction != null),
+       super(key: key);
+
+  /// The callback that is called when the button is tapped or otherwise
+  /// activated.
+  ///
+  /// If this is set to null, the button will be disabled.
+  final VoidCallback? onPressed;
+
+  /// Set to true if button is the default choice in the dialog.
+  ///
+  /// Default buttons have bold text. Similar to
+  /// [UIAlertController.preferredAction](https://developer.apple.com/documentation/uikit/uialertcontroller/1620102-preferredaction),
+  /// but more than one action can have this attribute set to true in the same
+  /// [CupertinoAlertDialog].
+  ///
+  /// This parameters defaults to false and cannot be null.
+  final bool isDefaultAction;
+
+  /// Whether this action destroys an object.
+  ///
+  /// For example, an action that deletes an email is destructive.
+  ///
+  /// Defaults to false and cannot be null.
+  final bool isDestructiveAction;
+
+  /// [TextStyle] to apply to any text that appears in this button.
+  ///
+  /// Dialog actions have a built-in text resizing policy for long text. To
+  /// ensure that this resizing policy always works as expected, [textStyle]
+  /// must be used if a text size is desired other than that specified in
+  /// [_kCupertinoDialogActionStyle].
+  final TextStyle? textStyle;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// Typically a [Text] widget.
+  final Widget child;
+
+  /// Whether the button is enabled or disabled. Buttons are disabled by
+  /// default. To enable a button, set its [onPressed] property to a non-null
+  /// value.
+  bool get enabled => onPressed != null;
+
+  double _calculatePadding(BuildContext context) {
+    return 8.0 * MediaQuery.textScaleFactorOf(context);
+  }
+
+  // Dialog action content shrinks to fit, up to a certain point, and if it still
+  // cannot fit at the minimum size, the text content is ellipsized.
+  //
+  // This policy only applies when the device is not in accessibility mode.
+  Widget _buildContentWithRegularSizingPolicy({
+    required BuildContext context,
+    required TextStyle textStyle,
+    required Widget content,
+  }) {
+    final bool isInAccessibilityMode = _isInAccessibilityMode(context);
+    final double dialogWidth = isInAccessibilityMode
+        ? _kAccessibilityCupertinoDialogWidth
+        : _kCupertinoDialogWidth;
+    final double textScaleFactor = MediaQuery.textScaleFactorOf(context);
+    // The fontSizeRatio is the ratio of the current text size (including any
+    // iOS scale factor) vs the minimum text size that we allow in action
+    // buttons. This ratio information is used to automatically scale down action
+    // button text to fit the available space.
+    final double fontSizeRatio = (textScaleFactor * textStyle.fontSize!) / _kMinButtonFontSize;
+    final double padding = _calculatePadding(context);
+
+    return IntrinsicHeight(
+      child: SizedBox(
+        width: double.infinity,
+        child: FittedBox(
+          fit: BoxFit.scaleDown,
+          child: ConstrainedBox(
+            constraints: BoxConstraints(
+              maxWidth: fontSizeRatio * (dialogWidth - (2 * padding)),
+            ),
+            child: Semantics(
+              button: true,
+              onTap: onPressed,
+              child: DefaultTextStyle(
+                style: textStyle,
+                textAlign: TextAlign.center,
+                overflow: TextOverflow.ellipsis,
+                maxLines: 1,
+                child: content,
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+
+  // Dialog action content is permitted to be as large as it wants when in
+  // accessibility mode. If text is used as the content, the text wraps instead
+  // of ellipsizing.
+  Widget _buildContentWithAccessibilitySizingPolicy({
+    required TextStyle textStyle,
+    required Widget content,
+  }) {
+    return DefaultTextStyle(
+      style: textStyle,
+      textAlign: TextAlign.center,
+      child: content,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    TextStyle style = _kCupertinoDialogActionStyle.copyWith(
+      color: CupertinoDynamicColor.resolve(
+        isDestructiveAction ?  CupertinoColors.systemRed : CupertinoColors.systemBlue,
+        context,
+      ),
+    );
+    style = style.merge(textStyle);
+
+    if (isDefaultAction) {
+      style = style.copyWith(fontWeight: FontWeight.w600);
+    }
+
+    if (!enabled) {
+      style = style.copyWith(color: style.color!.withOpacity(0.5));
+    }
+
+    // Apply a sizing policy to the action button's content based on whether or
+    // not the device is in accessibility mode.
+    // TODO(mattcarroll): The following logic is not entirely correct. It is also
+    // the case that if content text does not contain a space, it should also
+    // wrap instead of ellipsizing. We are consciously not implementing that
+    // now due to complexity.
+    final Widget sizedContent = _isInAccessibilityMode(context)
+      ? _buildContentWithAccessibilitySizingPolicy(
+          textStyle: style,
+          content: child,
+        )
+      : _buildContentWithRegularSizingPolicy(
+          context: context,
+          textStyle: style,
+          content: child,
+        );
+
+    return GestureDetector(
+      excludeFromSemantics: true,
+      onTap: onPressed,
+      behavior: HitTestBehavior.opaque,
+      child: ConstrainedBox(
+        constraints: const BoxConstraints(
+          minHeight: _kMinButtonHeight,
+        ),
+        child: Container(
+          alignment: Alignment.center,
+          padding: EdgeInsets.all(_calculatePadding(context)),
+          child: sizedContent,
+        ),
+      ),
+    );
+  }
+}
+
+// iOS style dialog action button layout.
+//
+// [_CupertinoDialogActionsRenderWidget] does not provide any scrolling
+// behavior for its buttons. It only handles the sizing and layout of buttons.
+// Scrolling behavior can be composed on top of this widget, if desired.
+//
+// See [_RenderCupertinoDialogActions] for specific layout policy details.
+class _CupertinoDialogActionsRenderWidget extends MultiChildRenderObjectWidget {
+  _CupertinoDialogActionsRenderWidget({
+    Key? key,
+    required List<Widget> actionButtons,
+    double dividerThickness = 0.0,
+  }) : _dividerThickness = dividerThickness,
+       super(key: key, children: actionButtons);
+
+  final double _dividerThickness;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _RenderCupertinoDialogActions(
+      dialogWidth: _isInAccessibilityMode(context)
+        ? _kAccessibilityCupertinoDialogWidth
+        : _kCupertinoDialogWidth,
+      dividerThickness: _dividerThickness,
+      dialogColor: CupertinoDynamicColor.resolve(_kDialogColor, context),
+      dialogPressedColor: CupertinoDynamicColor.resolve(_kDialogPressedColor, context),
+      dividerColor: CupertinoDynamicColor.resolve(CupertinoColors.separator, context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderCupertinoDialogActions renderObject) {
+    renderObject
+      ..dialogWidth = _isInAccessibilityMode(context)
+        ? _kAccessibilityCupertinoDialogWidth
+        : _kCupertinoDialogWidth
+      ..dividerThickness = _dividerThickness
+      ..dialogColor = CupertinoDynamicColor.resolve(_kDialogColor, context)
+      ..dialogPressedColor = CupertinoDynamicColor.resolve(_kDialogPressedColor, context)
+      ..dividerColor = CupertinoDynamicColor.resolve(CupertinoColors.separator, context);
+  }
+}
+
+// iOS style layout policy for sizing and positioning an alert dialog's action
+// buttons.
+//
+// The policy is as follows:
+//
+// If a single action button is provided, or if 2 action buttons are provided
+// that can fit side-by-side, then action buttons are sized and laid out in a
+// single horizontal row. The row is exactly as wide as the dialog, and the row
+// is as tall as the tallest action button. A horizontal divider is drawn above
+// the button row. If 2 action buttons are provided, a vertical divider is
+// drawn between them. The thickness of the divider is set by [dividerThickness].
+//
+// If 2 action buttons are provided but they cannot fit side-by-side, then the
+// 2 buttons are stacked vertically. A horizontal divider is drawn above each
+// button. The thickness of the divider is set by [dividerThickness]. The minimum
+// height of this [RenderBox] in the case of 2 stacked buttons is as tall as
+// the 2 buttons stacked. This is different than the 3+ button case where the
+// minimum height is only 1.5 buttons tall. See the 3+ button explanation for
+// more info.
+//
+// If 3+ action buttons are provided then they are all stacked vertically. A
+// horizontal divider is drawn above each button. The thickness of the divider
+// is set by [dividerThickness]. The minimum height of this [RenderBox] in the case
+// of 3+ stacked buttons is as tall as the 1st button + 50% the height of the
+// 2nd button. In other words, the minimum height is 1.5 buttons tall. This
+// minimum height of 1.5 buttons is expected to work in tandem with a surrounding
+// [ScrollView] to match the iOS dialog behavior.
+//
+// Each button is expected to have an _ActionButtonParentData which reports
+// whether or not that button is currently pressed. If a button is pressed,
+// then the dividers above and below that pressed button are not drawn - instead
+// they are filled with the standard white dialog background color. The one
+// exception is the very 1st divider which is always rendered. This policy comes
+// from observation of native iOS dialogs.
+class _RenderCupertinoDialogActions extends RenderBox
+    with ContainerRenderObjectMixin<RenderBox, MultiChildLayoutParentData>,
+        RenderBoxContainerDefaultsMixin<RenderBox, MultiChildLayoutParentData> {
+  _RenderCupertinoDialogActions({
+    List<RenderBox>? children,
+    required double dialogWidth,
+    double dividerThickness = 0.0,
+    required Color dialogColor,
+    required Color dialogPressedColor,
+    required Color dividerColor,
+  }) : _dialogWidth = dialogWidth,
+       _buttonBackgroundPaint = Paint()
+        ..color = dialogColor
+        ..style = PaintingStyle.fill,
+        _pressedButtonBackgroundPaint = Paint()
+          ..color = dialogPressedColor
+          ..style = PaintingStyle.fill,
+        _dividerPaint = Paint()
+          ..color = dividerColor
+          ..style = PaintingStyle.fill,
+       _dividerThickness = dividerThickness {
+    addAll(children);
+  }
+
+  double get dialogWidth => _dialogWidth;
+  double _dialogWidth;
+  set dialogWidth(double newWidth) {
+    if (newWidth != _dialogWidth) {
+      _dialogWidth = newWidth;
+      markNeedsLayout();
+    }
+  }
+
+  // The thickness of the divider between buttons.
+  double get dividerThickness => _dividerThickness;
+  double _dividerThickness;
+  set dividerThickness(double newValue) {
+    if (newValue != _dividerThickness) {
+      _dividerThickness = newValue;
+      markNeedsLayout();
+    }
+  }
+
+  final Paint _buttonBackgroundPaint;
+  set dialogColor(Color value) {
+    if (value == _buttonBackgroundPaint.color)
+      return;
+
+    _buttonBackgroundPaint.color = value;
+    markNeedsPaint();
+  }
+
+  final Paint _pressedButtonBackgroundPaint;
+  set dialogPressedColor(Color value) {
+    if (value == _pressedButtonBackgroundPaint.color)
+      return;
+
+    _pressedButtonBackgroundPaint.color = value;
+    markNeedsPaint();
+  }
+
+  final Paint _dividerPaint;
+  set dividerColor(Color value) {
+    if (value == _dividerPaint.color)
+      return;
+
+    _dividerPaint.color = value;
+    markNeedsPaint();
+  }
+
+  Iterable<RenderBox> get _pressedButtons sync* {
+    RenderBox? currentChild = firstChild;
+    while (currentChild != null) {
+      assert(currentChild.parentData is _ActionButtonParentData);
+      final _ActionButtonParentData parentData = currentChild.parentData! as _ActionButtonParentData;
+      if (parentData.isPressed) {
+        yield currentChild;
+      }
+      currentChild = childAfter(currentChild);
+    }
+  }
+
+  bool get _isButtonPressed {
+    RenderBox? currentChild = firstChild;
+    while (currentChild != null) {
+      assert(currentChild.parentData is _ActionButtonParentData);
+      final _ActionButtonParentData parentData = currentChild.parentData! as _ActionButtonParentData;
+      if (parentData.isPressed) {
+        return true;
+      }
+      currentChild = childAfter(currentChild);
+    }
+    return false;
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! _ActionButtonParentData)
+      child.parentData = _ActionButtonParentData();
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    return dialogWidth;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    return dialogWidth;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    final double minHeight;
+    if (childCount == 0) {
+      minHeight = 0.0;
+    } else if (childCount == 1) {
+      // If only 1 button, display the button across the entire dialog.
+      minHeight = _computeMinIntrinsicHeightSideBySide(width);
+    } else {
+      if (childCount == 2 && _isSingleButtonRow(width)) {
+        // The first 2 buttons fit side-by-side. Display them horizontally.
+        minHeight = _computeMinIntrinsicHeightSideBySide(width);
+      } else {
+        // 3+ buttons are always stacked. The minimum height when stacked is
+        // 1.5 buttons tall.
+        minHeight = _computeMinIntrinsicHeightStacked(width);
+      }
+    }
+    return minHeight;
+  }
+
+  // The minimum height for a single row of buttons is the larger of the buttons'
+  // min intrinsic heights.
+  double _computeMinIntrinsicHeightSideBySide(double width) {
+    assert(childCount >= 1 && childCount <= 2);
+
+    final double minHeight;
+    if (childCount == 1) {
+      minHeight = firstChild!.getMinIntrinsicHeight(width);
+    } else {
+      final double perButtonWidth = (width - dividerThickness) / 2.0;
+      minHeight = math.max(
+        firstChild!.getMinIntrinsicHeight(perButtonWidth),
+        lastChild!.getMinIntrinsicHeight(perButtonWidth),
+      );
+    }
+    return minHeight;
+  }
+
+  // The minimum height for 2+ stacked buttons is the height of the 1st button
+  // + 50% the height of the 2nd button + the divider between the two.
+  double _computeMinIntrinsicHeightStacked(double width) {
+    assert(childCount >= 2);
+
+    return firstChild!.getMinIntrinsicHeight(width)
+      + dividerThickness
+      + (0.5 * childAfter(firstChild!)!.getMinIntrinsicHeight(width));
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    final double maxHeight;
+    if (childCount == 0) {
+      // No buttons. Zero height.
+      maxHeight = 0.0;
+    } else if (childCount == 1) {
+      // One button. Our max intrinsic height is equal to the button's.
+      maxHeight = firstChild!.getMaxIntrinsicHeight(width);
+    } else if (childCount == 2) {
+      // Two buttons...
+      if (_isSingleButtonRow(width)) {
+        // The 2 buttons fit side by side so our max intrinsic height is equal
+        // to the taller of the 2 buttons.
+        final double perButtonWidth = (width - dividerThickness) / 2.0;
+        maxHeight = math.max(
+          firstChild!.getMaxIntrinsicHeight(perButtonWidth),
+          lastChild!.getMaxIntrinsicHeight(perButtonWidth),
+        );
+      } else {
+        // The 2 buttons do not fit side by side. Measure total height as a
+        // vertical stack.
+        maxHeight = _computeMaxIntrinsicHeightStacked(width);
+      }
+    } else {
+      // Three+ buttons. Stack the buttons vertically with dividers and measure
+      // the overall height.
+      maxHeight = _computeMaxIntrinsicHeightStacked(width);
+    }
+    return maxHeight;
+  }
+
+  // Max height of a stack of buttons is the sum of all button heights + a
+  // divider for each button.
+  double _computeMaxIntrinsicHeightStacked(double width) {
+    assert(childCount >= 2);
+
+    final double allDividersHeight = (childCount - 1) * dividerThickness;
+    double heightAccumulation = allDividersHeight;
+    RenderBox? button = firstChild;
+    while (button != null) {
+      heightAccumulation += button.getMaxIntrinsicHeight(width);
+      button = childAfter(button);
+    }
+    return heightAccumulation;
+  }
+
+  bool _isSingleButtonRow(double width) {
+    final bool isSingleButtonRow;
+    if (childCount == 1) {
+      isSingleButtonRow = true;
+    } else if (childCount == 2) {
+      // There are 2 buttons. If they can fit side-by-side then that's what
+      // we want to do. Otherwise, stack them vertically.
+      final double sideBySideWidth = firstChild!.getMaxIntrinsicWidth(double.infinity)
+          + dividerThickness
+          + lastChild!.getMaxIntrinsicWidth(double.infinity);
+      isSingleButtonRow = sideBySideWidth <= width;
+    } else {
+      isSingleButtonRow = false;
+    }
+    return isSingleButtonRow;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _computeLayout(constraints: constraints, dry: true);
+  }
+
+  @override
+  void performLayout() {
+    size = _computeLayout(constraints: constraints, dry: false);
+  }
+
+  Size _computeLayout({required BoxConstraints constraints, bool dry = false}) {
+    final ChildLayouter layoutChild = dry
+        ? ChildLayoutHelper.dryLayoutChild
+        : ChildLayoutHelper.layoutChild;
+
+    if (_isSingleButtonRow(dialogWidth)) {
+      if (childCount == 1) {
+        // We have 1 button. Our size is the width of the dialog and the height
+        // of the single button.
+        final Size childSize = layoutChild(
+          firstChild!,
+          constraints,
+        );
+
+        return constraints.constrain(
+          Size(dialogWidth, childSize.height)
+        );
+      } else {
+        // Each button gets half the available width, minus a single divider.
+        final BoxConstraints perButtonConstraints = BoxConstraints(
+          minWidth: (constraints.minWidth - dividerThickness) / 2.0,
+          maxWidth: (constraints.maxWidth - dividerThickness) / 2.0,
+          minHeight: 0.0,
+          maxHeight: double.infinity,
+        );
+
+        // Layout the 2 buttons.
+        final Size firstChildSize = layoutChild(
+          firstChild!,
+          perButtonConstraints,
+        );
+        final Size lastChildSize = layoutChild(
+          lastChild!,
+          perButtonConstraints,
+        );
+
+        if (!dry) {
+          // The 2nd button needs to be offset to the right.
+          assert(lastChild!.parentData is MultiChildLayoutParentData);
+          final MultiChildLayoutParentData secondButtonParentData = lastChild!.parentData! as MultiChildLayoutParentData;
+          secondButtonParentData.offset = Offset(firstChildSize.width + dividerThickness, 0.0);
+        }
+
+        // Calculate our size based on the button sizes.
+        return constraints.constrain(
+          Size(
+            dialogWidth,
+            math.max(
+              firstChildSize.height,
+              lastChildSize.height,
+            ),
+          ),
+        );
+      }
+    } else {
+      // We need to stack buttons vertically, plus dividers above each button (except the 1st).
+      final BoxConstraints perButtonConstraints = constraints.copyWith(
+        minHeight: 0.0,
+        maxHeight: double.infinity,
+      );
+
+      RenderBox? child = firstChild;
+      int index = 0;
+      double verticalOffset = 0.0;
+      while (child != null) {
+        final Size childSize = layoutChild(
+          child,
+          perButtonConstraints,
+        );
+
+        if (!dry) {
+          assert(child.parentData is MultiChildLayoutParentData);
+          final MultiChildLayoutParentData parentData = child.parentData! as MultiChildLayoutParentData;
+          parentData.offset = Offset(0.0, verticalOffset);
+        }
+        verticalOffset += childSize.height;
+        if (index < childCount - 1) {
+          // Add a gap for the next divider.
+          verticalOffset += dividerThickness;
+        }
+
+        index += 1;
+        child = childAfter(child);
+      }
+
+      // Our height is the accumulated height of all buttons and dividers.
+      return constraints.constrain(
+        Size(dialogWidth, verticalOffset)
+      );
+    }
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final Canvas canvas = context.canvas;
+
+    if (_isSingleButtonRow(size.width)) {
+      _drawButtonBackgroundsAndDividersSingleRow(canvas, offset);
+    } else {
+      _drawButtonBackgroundsAndDividersStacked(canvas, offset);
+    }
+
+    _drawButtons(context, offset);
+  }
+
+  void _drawButtonBackgroundsAndDividersSingleRow(Canvas canvas, Offset offset) {
+    // The vertical divider sits between the left button and right button (if
+    // the dialog has 2 buttons).  The vertical divider is hidden if either the
+    // left or right button is pressed.
+    final Rect verticalDivider = childCount == 2 && !_isButtonPressed
+      ? Rect.fromLTWH(
+          offset.dx + firstChild!.size.width,
+          offset.dy,
+          dividerThickness,
+          math.max(
+            firstChild!.size.height,
+            lastChild!.size.height,
+          ),
+        )
+      : Rect.zero;
+
+    final List<Rect> pressedButtonRects = _pressedButtons.map<Rect>((RenderBox pressedButton) {
+      final MultiChildLayoutParentData buttonParentData = pressedButton.parentData! as MultiChildLayoutParentData;
+
+      return Rect.fromLTWH(
+        offset.dx + buttonParentData.offset.dx,
+        offset.dy + buttonParentData.offset.dy,
+        pressedButton.size.width,
+        pressedButton.size.height,
+      );
+    }).toList();
+
+    // Create the button backgrounds path and paint it.
+    final Path backgroundFillPath = Path()
+      ..fillType = PathFillType.evenOdd
+      ..addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height))
+      ..addRect(verticalDivider);
+
+    for (int i = 0; i < pressedButtonRects.length; i += 1) {
+      backgroundFillPath.addRect(pressedButtonRects[i]);
+    }
+
+    canvas.drawPath(
+      backgroundFillPath,
+      _buttonBackgroundPaint,
+    );
+
+    // Create the pressed buttons background path and paint it.
+    final Path pressedBackgroundFillPath = Path();
+    for (int i = 0; i < pressedButtonRects.length; i += 1) {
+      pressedBackgroundFillPath.addRect(pressedButtonRects[i]);
+    }
+
+    canvas.drawPath(
+      pressedBackgroundFillPath,
+      _pressedButtonBackgroundPaint,
+    );
+
+    // Create the dividers path and paint it.
+    final Path dividersPath = Path()
+      ..addRect(verticalDivider);
+
+    canvas.drawPath(
+      dividersPath,
+      _dividerPaint,
+    );
+  }
+
+  void _drawButtonBackgroundsAndDividersStacked(Canvas canvas, Offset offset) {
+    final Offset dividerOffset = Offset(0.0, dividerThickness);
+
+    final Path backgroundFillPath = Path()
+      ..fillType = PathFillType.evenOdd
+      ..addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height));
+
+    final Path pressedBackgroundFillPath = Path();
+
+    final Path dividersPath = Path();
+
+    Offset accumulatingOffset = offset;
+
+    RenderBox? child = firstChild;
+    RenderBox? prevChild;
+    while (child != null) {
+      assert(child.parentData is _ActionButtonParentData);
+      final _ActionButtonParentData currentButtonParentData = child.parentData! as _ActionButtonParentData;
+      final bool isButtonPressed = currentButtonParentData.isPressed;
+
+      bool isPrevButtonPressed = false;
+      if (prevChild != null) {
+        assert(prevChild.parentData is _ActionButtonParentData);
+        final _ActionButtonParentData previousButtonParentData = prevChild.parentData! as _ActionButtonParentData;
+        isPrevButtonPressed = previousButtonParentData.isPressed;
+      }
+
+      final bool isDividerPresent = child != firstChild;
+      final bool isDividerPainted = isDividerPresent && !(isButtonPressed || isPrevButtonPressed);
+      final Rect dividerRect = Rect.fromLTWH(
+        accumulatingOffset.dx,
+        accumulatingOffset.dy,
+        size.width,
+        dividerThickness,
+      );
+
+      final Rect buttonBackgroundRect = Rect.fromLTWH(
+        accumulatingOffset.dx,
+        accumulatingOffset.dy + (isDividerPresent ? dividerThickness : 0.0),
+        size.width,
+        child.size.height,
+      );
+
+      // If this button is pressed, then we don't want a white background to be
+      // painted, so we erase this button from the background path.
+      if (isButtonPressed) {
+        backgroundFillPath.addRect(buttonBackgroundRect);
+        pressedBackgroundFillPath.addRect(buttonBackgroundRect);
+      }
+
+      // If this divider is needed, then we erase the divider area from the
+      // background path, and on top of that we paint a translucent gray to
+      // darken the divider area.
+      if (isDividerPainted) {
+        backgroundFillPath.addRect(dividerRect);
+        dividersPath.addRect(dividerRect);
+      }
+
+      accumulatingOffset += (isDividerPresent ? dividerOffset : Offset.zero)
+          + Offset(0.0, child.size.height);
+
+      prevChild = child;
+      child = childAfter(child);
+    }
+
+    canvas.drawPath(backgroundFillPath, _buttonBackgroundPaint);
+    canvas.drawPath(pressedBackgroundFillPath, _pressedButtonBackgroundPaint);
+    canvas.drawPath(dividersPath, _dividerPaint);
+  }
+
+  void _drawButtons(PaintingContext context, Offset offset) {
+    RenderBox? child = firstChild;
+    while (child != null) {
+      final MultiChildLayoutParentData childParentData = child.parentData! as MultiChildLayoutParentData;
+      context.paintChild(child, childParentData.offset + offset);
+      child = childAfter(child);
+    }
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    return defaultHitTestChildren(result, position: position);
+  }
+}
diff --git a/lib/src/cupertino/form_row.dart b/lib/src/cupertino/form_row.dart
new file mode 100644
index 0000000..cc9fb5d
--- /dev/null
+++ b/lib/src/cupertino/form_row.dart
@@ -0,0 +1,200 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'theme.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+
+// Content padding determined via SwiftUI's `Form` view in the iOS 14.2 SDK.
+const EdgeInsetsGeometry _kDefaultPadding =
+    EdgeInsetsDirectional.fromSTEB(20.0, 6.0, 6.0, 6.0);
+
+/// An iOS-style form row.
+///
+/// Creates an iOS-style split form row with a standard prefix and child widget.
+/// Also provides a space for error and helper widgets that appear underneath.
+///
+/// The [child] parameter is required. This widget is displayed at the end of
+/// the row.
+///
+/// The [prefix] parameter is optional and is displayed at the start of the
+/// row. Standard iOS guidelines encourage passing a [Text] widget to [prefix]
+/// to detail the nature of the row's [child] widget.
+///
+/// The [padding] parameter is used to pad the contents of the row. It defaults
+/// to the standard iOS padding. If no edge insets are intended, explicitly pass
+/// [EdgeInsets.zero] to [padding].
+///
+/// The [helper] and [error] parameters are both optional widgets targeted at
+/// displaying more information about the row. Both widgets are placed
+/// underneath the [prefix] and [child], and will expand the row's height to
+/// accomodate for their presence. When a [Text] is given to [error], it will
+/// be shown in [CupertinoColors.destructiveRed] coloring and
+/// medium-weighted font.
+///
+/// {@tool snippet}
+///
+/// Creates a [CupertinoFormSection] containing a [CupertinoFormRow] with the
+/// [prefix], [child], [helper] and [error] widgets.
+///
+/// ```dart
+/// class FlutterDemo extends StatefulWidget {
+///   FlutterDemo({Key key}) : super(key: key);
+///
+///   @override
+///   _FlutterDemoState createState() => _FlutterDemoState();
+/// }
+///
+/// class _FlutterDemoState extends State<FlutterDemo> {
+///   bool toggleValue = false;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return CupertinoPageScaffold(
+///       child: Center(
+///         child: CupertinoFormSection(
+///           header: Text('SECTION 1'),
+///           children: <Widget>[
+///             CupertinoFormRow(
+///               child: CupertinoSwitch(
+///                 value: this.toggleValue,
+///                 onChanged: (value) {
+///                   setState(() {
+///                     this.toggleValue = value;
+///                   });
+///                 },
+///               ),
+///               prefix: Text('Toggle'),
+///               helper: Text('Use your instincts'),
+///               error: toggleValue ? Text('Cannot be true') : null,
+///             ),
+///           ],
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+class CupertinoFormRow extends StatelessWidget {
+  /// Creates an iOS-style split form row with a standard prefix and child widget.
+  /// Also provides a space for error and helper widgets that appear underneath.
+  ///
+  /// The [child] parameter is required. This widget is displayed at the end of
+  /// the row.
+  ///
+  /// The [prefix] parameter is optional and is displayed at the start of the
+  /// row. Standard iOS guidelines encourage passing a [Text] widget to [prefix]
+  /// to detail the nature of the row's [child] widget.
+  ///
+  /// The [padding] parameter is used to pad the contents of the row. It defaults
+  /// to the standard iOS padding. If no edge insets are intended, explicitly
+  /// pass [EdgeInsets.zero] to [padding].
+  ///
+  /// The [helper] and [error] parameters are both optional widgets targeted at
+  /// displaying more information about the row. Both widgets are placed
+  /// underneath the [prefix] and [child], and will expand the row's height to
+  /// accomodate for their presence. When a [Text] is given to [error], it will
+  /// be shown in [CupertinoColors.destructiveRed] coloring and
+  /// medium-weighted font.
+  const CupertinoFormRow({
+    Key? key,
+    required this.child,
+    this.prefix,
+    this.padding,
+    this.helper,
+    this.error,
+  }) : super(key: key);
+
+  /// A widget that is displayed at the start of the row.
+  ///
+  /// The [prefix] parameter is displayed at the start of the row. Standard iOS
+  /// guidelines encourage passing a [Text] widget to [prefix] to detail the
+  /// nature of the row's [child] widget. If null, the [child] widget will take
+  /// up all horizontal space in the row.
+  final Widget? prefix;
+
+  /// Content padding for the row.
+  ///
+  /// Defaults to the standard iOS padding for form rows. If no edge insets are
+  /// intended, explicitly pass [EdgeInsets.zero] to [padding].
+  final EdgeInsetsGeometry? padding;
+
+  /// A widget that is displayed underneath the [prefix] and [child] widgets.
+  ///
+  /// The [helper] appears in primary label coloring, and is meant to inform the
+  /// user about interaction with the child widget. The row becomes taller in
+  /// order to display the [helper] widget underneath [prefix] and [child]. If
+  /// null, the row is shorter.
+  final Widget? helper;
+
+  /// A widget that is displayed underneath the [prefix] and [child] widgets.
+  ///
+  /// The [error] widget is primarily used to inform users of input errors. When
+  /// a [Text] is given to [error], it will be shown in
+  /// [CupertinoColors.destructiveRed] coloring and medium-weighted font. The
+  /// row becomes taller in order to display the [helper] widget underneath
+  /// [prefix] and [child]. If null, the row is shorter.
+  final Widget? error;
+
+  /// Child widget.
+  ///
+  /// The [child] widget is primarily used for input. It end-aligned and
+  /// horizontally flexible, taking up the entire space trailing past the
+  /// [prefix] widget.
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    final TextStyle textStyle = CupertinoTheme.of(context).textTheme.textStyle;
+
+    return Padding(
+      padding: padding ?? _kDefaultPadding,
+      child: Column(
+        children: <Widget>[
+          Row(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+            children: <Widget>[
+              if (prefix != null)
+                DefaultTextStyle(
+                  style: textStyle,
+                  child: prefix!,
+                ),
+              Flexible(
+                child: Align(
+                  alignment: AlignmentDirectional.centerEnd,
+                  child: child,
+                ),
+              ),
+            ],
+          ),
+          if (helper != null)
+            Align(
+              alignment: AlignmentDirectional.centerStart,
+              child: DefaultTextStyle(
+                style: textStyle,
+                child: helper!,
+              ),
+            ),
+          if (error != null)
+            Align(
+              alignment: AlignmentDirectional.centerStart,
+              child: DefaultTextStyle(
+                style: const TextStyle(
+                  color: CupertinoColors.destructiveRed,
+                  fontWeight: FontWeight.w500,
+                ),
+                child: error!,
+              ),
+            ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/src/cupertino/form_section.dart b/lib/src/cupertino/form_section.dart
new file mode 100644
index 0000000..d25fb00
--- /dev/null
+++ b/lib/src/cupertino/form_section.dart
@@ -0,0 +1,323 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+
+// Standard header margin, determined from SwiftUI's Forms in iOS 14.2 SDK.
+const EdgeInsetsDirectional _kDefaultHeaderMargin =
+    EdgeInsetsDirectional.fromSTEB(20.0, 16.0, 20.0, 10.0);
+
+// Standard footer margin, determined from SwiftUI's Forms in iOS 14.2 SDK.
+const EdgeInsetsDirectional _kDefaultFooterMargin =
+    EdgeInsetsDirectional.fromSTEB(20.0, 0.0, 20.0, 10.0);
+
+// Used for iOS "Inset Grouped" margin, determined from SwiftUI's Forms in
+// iOS 14.2 SDK.
+const EdgeInsetsDirectional _kDefaultInsetGroupedRowsMargin =
+    EdgeInsetsDirectional.fromSTEB(20.0, 0.0, 20.0, 10.0);
+
+// Used for iOS "Inset Grouped" border radius, estimated from SwiftUI's Forms in
+// iOS 14.2 SDK.
+// TODO(edrisian): This should be a rounded rectangle once that shape is added.
+const BorderRadius _kDefaultInsetGroupedBorderRadius =
+    BorderRadius.all(Radius.circular(10.0));
+
+// Used to differentiate the edge-to-edge section with the centered section.
+enum _CupertinoFormSectionType { base, insetGrouped }
+
+/// An iOS-style form section.
+///
+/// The base constructor for [CupertinoFormSection] constructs an
+/// edge-to-edge style section which includes an iOS-style header, rows,
+/// the dividers between rows, and borders on top and bottom of the rows.
+///
+/// The [CupertinoFormSection.insetGrouped] constructor creates a round-edged and
+/// padded section that is commonly seen in notched-displays like iPhone X and
+/// beyond. Creates an iOS-style header, rows, and the dividers
+/// between rows. Does not create borders on top and bottom of the rows.
+///
+/// The [header] parameter sets the form section header. The section header lies
+/// above the [children] rows, with margins that match the iOS style.
+///
+/// The [footer] parameter sets the form section footer. The section footer
+/// lies below the [children] rows.
+///
+/// The [children] parameter is required and sets the list of rows shown in
+/// the section. The [children] parameter takes a list, as opposed to a more
+/// efficient builder function that lazy builds, because forms are intended to
+/// be short in row count. It is recommended that only [CupertinoFormRow] and
+/// [CupertinoTextFormFieldRow] widgets be included in the [children] list in
+/// order to retain the iOS look.
+///
+/// The [margin] parameter sets the spacing around the content area of the
+/// section encapsulating [children].
+///
+/// The [decoration] parameter sets the decoration around [children].
+/// If null, defaults to [CupertinoColors.secondarySystemGroupedBackground].
+/// If null, defaults to 10.0 circular radius when constructing with
+/// [CupertinoFormSection.insetGrouped]. Defaults to zero radius for the
+/// standard [CupertinoFormSection] constructor.
+///
+/// The [backgroundColor] parameter sets the background color behind the section.
+/// If null, defaults to [CupertinoColors.systemGroupedBackground].
+///
+/// {@macro flutter.material.Material.clipBehavior}
+class CupertinoFormSection extends StatelessWidget {
+  /// Creates a section that mimicks standard iOS forms.
+  ///
+  /// The base constructor for [CupertinoFormSection] constructs an
+  /// edge-to-edge style section which includes an iOS-style header,
+  /// rows, the dividers between rows, and borders on top and bottom of the rows.
+  ///
+  /// The [header] parameter sets the form section header. The section header
+  /// lies above the [children] rows, with margins that match the iOS style.
+  ///
+  /// The [footer] parameter sets the form section footer. The section footer
+  /// lies below the [children] rows.
+  ///
+  /// The [children] parameter is required and sets the list of rows shown in
+  /// the section. The [children] parameter takes a list, as opposed to a more
+  /// efficient builder function that lazy builds, because forms are intended to
+  /// be short in row count. It is recommended that only [CupertinoFormRow] and
+  /// [CupertinoTextFormFieldRow] widgets be included in the [children] list in
+  /// order to retain the iOS look.
+  ///
+  /// The [margin] parameter sets the spacing around the content area of the
+  /// section encapsulating [children], and defaults to zero padding.
+  ///
+  /// The [decoration] parameter sets the decoration around [children].
+  /// If null, defaults to [CupertinoColors.secondarySystemGroupedBackground].
+  /// If null, defaults to 10.0 circular radius when constructing with
+  /// [CupertinoFormSection.insetGrouped]. Defaults to zero radius for the
+  /// standard [CupertinoFormSection] constructor.
+  ///
+  /// The [backgroundColor] parameter sets the background color behind the
+  /// section. If null, defaults to [CupertinoColors.systemGroupedBackground].
+  ///
+  /// {@macro flutter.material.Material.clipBehavior}
+  const CupertinoFormSection({
+    Key? key,
+    required this.children,
+    this.header,
+    this.footer,
+    this.margin = EdgeInsets.zero,
+    this.backgroundColor = CupertinoColors.systemGroupedBackground,
+    this.decoration,
+    this.clipBehavior = Clip.none,
+  })  : _type = _CupertinoFormSectionType.base,
+        assert(children.length > 0),
+        super(key: key);
+
+  /// Creates a section that mimicks standard "Inset Grouped" iOS forms.
+  ///
+  /// The [CupertinoFormSection.insetGrouped] constructor creates a round-edged and
+  /// padded section that is commonly seen in notched-displays like iPhone X and
+  /// beyond. Creates an iOS-style header, rows, and the dividers
+  /// between rows. Does not create borders on top and bottom of the rows.
+  ///
+  /// The [header] parameter sets the form section header. The section header
+  /// lies above the [children] rows, with margins that match the iOS style.
+  ///
+  /// The [footer] parameter sets the form section footer. The section footer
+  /// lies below the [children] rows.
+  ///
+  /// The [children] parameter is required and sets the list of rows shown in
+  /// the section. The [children] parameter takes a list, as opposed to a more
+  /// efficient builder function that lazy builds, because forms are intended to
+  /// be short in row count. It is recommended that only [CupertinoFormRow] and
+  /// [CupertinoTextFormFieldRow] widgets be included in the [children] list in
+  /// order to retain the iOS look.
+  ///
+  /// The [margin] parameter sets the spacing around the content area of the
+  /// section encapsulating [children], and defaults to the standard
+  /// notched-style iOS form padding.
+  ///
+  /// The [decoration] parameter sets the decoration around [children].
+  /// If null, defaults to [CupertinoColors.secondarySystemGroupedBackground].
+  /// If null, defaults to 10.0 circular radius when constructing with
+  /// [CupertinoFormSection.insetGrouped]. Defaults to zero radius for the
+  /// standard [CupertinoFormSection] constructor.
+  ///
+  /// The [backgroundColor] parameter sets the background color behind the
+  /// section. If null, defaults to [CupertinoColors.systemGroupedBackground].
+  ///
+  /// {@macro flutter.material.Material.clipBehavior}
+  const CupertinoFormSection.insetGrouped({
+    Key? key,
+    required this.children,
+    this.header,
+    this.footer,
+    this.margin = _kDefaultInsetGroupedRowsMargin,
+    this.backgroundColor = CupertinoColors.systemGroupedBackground,
+    this.decoration,
+    this.clipBehavior = Clip.none,
+  })  : _type = _CupertinoFormSectionType.insetGrouped,
+        assert(children.length > 0),
+        super(key: key);
+
+  final _CupertinoFormSectionType _type;
+
+  /// Sets the form section header. The section header lies above the
+  /// [children] rows.
+  final Widget? header;
+
+  /// Sets the form section footer. The section footer lies below the
+  /// [children] rows.
+  final Widget? footer;
+
+  /// Margin around the content area of the section encapsulating [children].
+  ///
+  /// Defaults to zero padding if constructed with standard
+  /// [CupertinoFormSection] constructor. Defaults to the standard notched-style
+  /// iOS margin when constructing with [CupertinoFormSection.insetGrouped].
+  final EdgeInsetsGeometry margin;
+
+  /// The list of rows in the section.
+  ///
+  /// This takes a list, as opposed to a more efficient builder function that
+  /// lazy builds, because forms are intended to be short in row count. It is
+  /// recommended that only [CupertinoFormRow] and [CupertinoTextFormFieldRow]
+  /// widgets be included in the [children] list in order to retain the iOS look.
+  final List<Widget> children;
+
+  /// Sets the decoration around [children].
+  ///
+  /// If null, background color defaults to
+  /// [CupertinoColors.secondarySystemGroupedBackground].
+  ///
+  /// If null, border radius defaults to 10.0 circular radius when constructing
+  /// with [CupertinoFormSection.insetGrouped]. Defaults to zero radius for the
+  /// standard [CupertinoFormSection] constructor.
+  final BoxDecoration? decoration;
+
+  /// Sets the background color behind the section.
+  ///
+  /// Defaults to [CupertinoColors.systemGroupedBackground].
+  final Color backgroundColor;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  final Clip clipBehavior;
+
+  @override
+  Widget build(BuildContext context) {
+    final Color dividerColor = CupertinoColors.separator.resolveFrom(context);
+    final double dividerHeight = 1.0 / MediaQuery.of(context).devicePixelRatio;
+
+    // Long divider is used for wrapping the top and bottom of rows.
+    // Only used in _CupertinoFormSectionType.base mode
+    final Widget longDivider = Container(
+      color: dividerColor,
+      height: dividerHeight,
+    );
+
+    // Short divider is used between rows.
+    // The value of the starting inset (15.0) is determined using SwiftUI's Form
+    // seperators in the iOS 14.2 SDK.
+    final Widget shortDivider = Container(
+      margin: const EdgeInsetsDirectional.only(start: 15.0),
+      color: dividerColor,
+      height: dividerHeight,
+    );
+
+    // We construct childrenWithDividers as follows:
+    // Insert a short divider between all rows.
+    // If it is a `_CupertinoFormSectionType.base` type, add a long divider
+    // to the top and bottom of the rows.
+    assert(children.isNotEmpty);
+
+    final List<Widget> childrenWithDividers = <Widget>[];
+
+    if (_type == _CupertinoFormSectionType.base) {
+      childrenWithDividers.add(longDivider);
+    }
+
+    children.sublist(0, children.length - 1).forEach((Widget widget) {
+      childrenWithDividers.add(widget);
+      childrenWithDividers.add(shortDivider);
+    });
+
+    childrenWithDividers.add(children.last);
+    if (_type == _CupertinoFormSectionType.base) {
+      childrenWithDividers.add(longDivider);
+    }
+
+    final BorderRadius childrenGroupBorderRadius;
+    switch (_type) {
+      case _CupertinoFormSectionType.insetGrouped:
+        childrenGroupBorderRadius = _kDefaultInsetGroupedBorderRadius;
+        break;
+      case _CupertinoFormSectionType.base:
+        childrenGroupBorderRadius = BorderRadius.zero;
+        break;
+    }
+
+    // Refactored the decorate children group in one place to avoid repeating it
+    // twice down bellow in the returned widget.
+    final DecoratedBox decoratedChildrenGroup = DecoratedBox(
+      decoration: decoration ??
+          BoxDecoration(
+            color: CupertinoDynamicColor.resolve(
+                decoration?.color ??
+                    CupertinoColors.secondarySystemGroupedBackground,
+                context),
+            borderRadius: childrenGroupBorderRadius,
+          ),
+      child: Column(
+        children: childrenWithDividers,
+      ),
+    );
+
+    return DecoratedBox(
+      decoration: BoxDecoration(
+        color: CupertinoDynamicColor.resolve(backgroundColor, context),
+      ),
+      child: Column(
+        children: <Widget>[
+          if (header != null)
+            Align(
+              alignment: AlignmentDirectional.centerStart,
+              child: DefaultTextStyle(
+                style: TextStyle(
+                  fontSize: 13.0,
+                  color: CupertinoColors.secondaryLabel.resolveFrom(context),
+                ),
+                child: Padding(
+                  padding: _kDefaultHeaderMargin,
+                  child: header!,
+                ),
+              ),
+            ),
+          Padding(
+            padding: margin,
+            child: clipBehavior == Clip.none
+                ? decoratedChildrenGroup
+                : ClipRRect(
+                    borderRadius: childrenGroupBorderRadius,
+                    clipBehavior: clipBehavior,
+                    child: decoratedChildrenGroup),
+          ),
+          if (footer != null)
+            Align(
+              alignment: AlignmentDirectional.centerStart,
+              child: DefaultTextStyle(
+                style: TextStyle(
+                  fontSize: 13.0,
+                  color: CupertinoColors.secondaryLabel.resolveFrom(context),
+                ),
+                child: Padding(
+                  padding: _kDefaultFooterMargin,
+                  child: footer!,
+                ),
+              ),
+            ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/src/cupertino/icon_theme_data.dart b/lib/src/cupertino/icon_theme_data.dart
new file mode 100644
index 0000000..a72cf6d
--- /dev/null
+++ b/lib/src/cupertino/icon_theme_data.dart
@@ -0,0 +1,45 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+import 'colors.dart';
+
+/// An [IconThemeData] subclass that automatically resolves its [color] when retrieved
+/// using [IconTheme.of].
+class CupertinoIconThemeData extends IconThemeData with Diagnosticable {
+  /// Creates a [CupertinoIconThemeData].
+  ///
+  /// The opacity applies to both explicit and default icon colors. The value
+  /// is clamped between 0.0 and 1.0.
+  const CupertinoIconThemeData({
+    Color? color,
+    double? opacity,
+    double? size,
+  }) : super(color: color, opacity: opacity, size: size);
+
+  /// Called by [IconTheme.of] to resolve [color] against the given [BuildContext].
+  @override
+  IconThemeData resolve(BuildContext context) {
+    final Color? resolvedColor = CupertinoDynamicColor.maybeResolve(color, context);
+    return resolvedColor == color ? this : copyWith(color: resolvedColor);
+  }
+
+  /// Creates a copy of this icon theme but with the given fields replaced with
+  /// the new values.
+  @override
+  CupertinoIconThemeData copyWith({ Color? color, double? opacity, double? size }) {
+    return CupertinoIconThemeData(
+      color: color ?? this.color,
+      opacity: opacity ?? this.opacity,
+      size: size ?? this.size,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(createCupertinoColorProperty('color', color, defaultValue: null));
+  }
+}
diff --git a/lib/src/cupertino/icons.dart b/lib/src/cupertino/icons.dart
new file mode 100644
index 0000000..f8820a4
--- /dev/null
+++ b/lib/src/cupertino/icons.dart
@@ -0,0 +1,3554 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+/// Identifiers for the supported Cupertino icons.
+///
+/// Use with the [Icon] class to show specific icons.
+///
+/// Icons are identified by their name as listed below.
+///
+/// To use this class, make sure you add a dependency on `cupertino_icons` in your
+/// project's `pubspec.yaml` file. This ensures that the CupertinoIcons font is
+/// included in your application. This font is used to display the icons. For example:
+///
+/// ```yaml
+/// name: my_awesome_application
+///
+/// dependencies:
+///   cupertino_icons: ^1.0.0
+/// ```
+///
+/// [![icon gallery preview](https://raw.githubusercontent.com/flutter/cupertino_icons/master/gallery_preview_1.0.0.png)](https://flutter.github.io/cupertino_icons)
+///
+/// For versions 1.0.0 and above (available only on Flutter SDK versions 1.22+), see the [Cupertino Icons Gallery](https://flutter.github.io/cupertino_icons).
+///
+/// For versions 0.1.3 and below, see this [glyph map](https://raw.githubusercontent.com/flutter/cupertino_icons/master/map.png).
+///
+/// See also:
+///
+///  * [Icon], used to show these icons.
+class CupertinoIcons {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  CupertinoIcons._();
+
+  /// The icon font used for Cupertino icons.
+  static const String iconFont = 'CupertinoIcons';
+
+  /// The dependent package providing the Cupertino icons font.
+  static const String iconFontPackage = 'cupertino_icons';
+
+  // ===========================================================================
+  // BEGIN LEGACY PRE SF SYMBOLS NAMES
+  // We need to leave them as-is with the same codepoints for backward
+  // compatibility with cupertino_icons <0.1.3.
+
+  /// <i class='cupertino-icons md-36'>chevron_left</i> &#x2014; Cupertino icon for a thin left chevron.
+  /// This is the same icon as [chevron_left] in cupertino_icons 1.0.0+.
+  static const IconData left_chevron = IconData(0xf3d2, fontFamily: iconFont, fontPackage: iconFontPackage, matchTextDirection: true);
+
+  /// <i class='cupertino-icons md-36'>chevron_right</i> &#x2014; Cupertino icon for a thin right chevron.
+  /// This is the same icon as [chevron_right] in cupertino_icons 1.0.0+.
+  static const IconData right_chevron = IconData(0xf3d3, fontFamily: iconFont, fontPackage: iconFontPackage, matchTextDirection: true);
+
+  /// <i class='cupertino-icons md-36'>square_arrow_up</i> &#x2014; Cupertino icon for an iOS style share icon with an arrow pointing up from a box. This icon is not filled in.
+  /// This is the same icon as [square_arrow_up] and [share_up] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [share_solid], which is similar, but filled in.
+  ///  * [share_up], for another (pre-iOS 7) version of this icon.
+  static const IconData share = IconData(0xf4ca, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>square_arrow_up_fill</i> &#x2014; Cupertino icon for an iOS style share icon with an arrow pointing up from a box. This icon is filled in.
+  /// This is the same icon as [square_arrow_up_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [share], which is similar, but not filled in.
+  ///  * [share_up], for another (pre-iOS 7) version of this icon.
+  static const IconData share_solid = IconData(0xf4cb, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>book</i> &#x2014; Cupertino icon for a book silhouette spread open. This icon is not filled in.
+  /// See also:
+  ///
+  ///  * [book_solid], which is similar, but filled in.
+  static const IconData book = IconData(0xf3e7, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>book_fill</i> &#x2014; Cupertino icon for a book silhouette spread open. This icon is filled in.
+  /// This is the same icon as [book_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [book], which is similar, but not filled in.
+  static const IconData book_solid = IconData(0xf3e8, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>bookmark</i> &#x2014; Cupertino icon for a book silhouette spread open containing a bookmark in the upper right. This icon is not filled in.
+  ///
+  /// See also:
+  ///
+  ///  * [bookmark_solid], which is similar, but filled in.
+  static const IconData bookmark = IconData(0xf3e9, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>bookmark_fill</i> &#x2014; Cupertino icon for a book silhouette spread open containing a bookmark in the upper right. This icon is filled in.
+  /// This is the same icon as [bookmark_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [bookmark], which is similar, but not filled in.
+  static const IconData bookmark_solid = IconData(0xf3ea, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>info_circle</i> &#x2014; Cupertino icon for a letter 'i' in a circle.
+  /// This is the same icon as [info_circle] in cupertino_icons 1.0.0+.
+  static const IconData info = IconData(0xf44c, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>arrowshape_turn_up_left</i> &#x2014; Cupertino icon for a curved up and left pointing arrow.
+  /// This is the same icon as [arrowshape_turn_up_left] in cupertino_icons 1.0.0+.
+  ///
+  /// For another version of this icon, see [reply_thick_solid].
+  static const IconData reply = IconData(0xf4c6, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>chat_bubble</i> &#x2014; Cupertino icon for a chat bubble.
+  /// This is the same icon as [chat_bubble] in cupertino_icons 1.0.0+.
+  static const IconData conversation_bubble = IconData(0xf3fb, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>person_crop_circle</i> &#x2014; Cupertino icon for a person's silhouette in a circle.
+  /// This is the same icon as [person_crop_circle] in cupertino_icons 1.0.0+.
+  static const IconData profile_circled = IconData(0xf419, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>plus_circle</i> &#x2014; Cupertino icon for a '+' sign in a circle.
+  /// This is the same icon as [plus_circle] in cupertino_icons 1.0.0+.
+  static const IconData plus_circled = IconData(0xf48a, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>minus_circle</i> &#x2014; Cupertino icon for a '-' sign in a circle.
+  /// This is the same icon as [minus_circle] in cupertino_icons 1.0.0+.
+  static const IconData minus_circled = IconData(0xf463, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>flag</i> &#x2014; Cupertino icon for a right facing flag and pole outline.
+  static const IconData flag = IconData(0xf42c, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>search</i> &#x2014; Cupertino icon for a magnifier loop outline.
+  static const IconData search = IconData(0xf4a5, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>checkmark</i> &#x2014; Cupertino icon for a checkmark.
+  /// This is the same icon as [checkmark] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [check_mark_circled], which consists of this check mark and a circle surrounding it.
+  static const IconData check_mark = IconData(0xf3fd, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>checkmark_circle</i> &#x2014; Cupertino icon for a checkmark in a circle. The circle is not filled in.
+  /// This is the same icon as [checkmark_circle] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [check_mark_circled_solid], which is similar, but filled in.
+  ///  * [check_mark], which is the check mark without a circle.
+  static const IconData check_mark_circled = IconData(0xf3fe, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>checkmark_circle_fill</i> &#x2014; Cupertino icon for a checkmark in a circle. The circle is filled in.
+  /// This is the same icon as [checkmark_circle_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [check_mark_circled], which is similar, but not filled in.
+  static const IconData check_mark_circled_solid = IconData(0xf3ff, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>circle</i> &#x2014; Cupertino icon for an empty circle (a ring).  An un-selected radio button.
+  ///
+  /// See also:
+  ///
+  ///  * [circle_filled], which is similar but filled in.
+  static const IconData circle = IconData(0xf401, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>circle_fill</i> &#x2014; Cupertino icon for a filled circle.  The circle is surrounded by a ring.  A selected radio button.
+  /// This is the same icon as [circle_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [circle], which is similar but not filled in.
+  static const IconData circle_filled = IconData(0xf400, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>chevron_back</i> &#x2014; Cupertino icon for a thicker left chevron used in iOS for the navigation bar back button.
+  /// This is the same icon as [chevron_back] in cupertino_icons 1.0.0+.
+  static const IconData back = IconData(0xf3cf, fontFamily: iconFont, fontPackage: iconFontPackage, matchTextDirection: true);
+
+  /// <i class='cupertino-icons md-36'>chevron_forward</i> &#x2014; Cupertino icon for a thicker right chevron that's the reverse of [back].
+  /// This is the same icon as [chevron_forward] in cupertino_icons 1.0.0+.
+  static const IconData forward = IconData(0xf3d1, fontFamily: iconFont, fontPackage: iconFontPackage, matchTextDirection: true);
+
+  /// <i class='cupertino-icons md-36'>house</i> &#x2014; Cupertino icon for an outline of a simple front-facing house.
+  /// This is the same icon as [house] in cupertino_icons 1.0.0+.
+  static const IconData home = IconData(0xf447, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>cart</i> &#x2014; Cupertino icon for a right-facing shopping cart outline.
+  /// This is the same icon as [cart] in cupertino_icons 1.0.0+.
+  static const IconData shopping_cart = IconData(0xf3f7, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>ellipsis</i> &#x2014; Cupertino icon for three solid dots.
+  static const IconData ellipsis = IconData(0xf46a, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>phone</i> &#x2014; Cupertino icon for a phone handset outline.
+  static const IconData phone = IconData(0xf4b8, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>phone_fill</i> &#x2014; Cupertino icon for a phone handset.
+  /// This is the same icon as [phone_fill] in cupertino_icons 1.0.0+.
+  static const IconData phone_solid = IconData(0xf4b9, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>arrow_down</i> &#x2014; Cupertino icon for a solid down arrow.
+  /// This is the same icon as [arrow_down] in cupertino_icons 1.0.0+.
+  static const IconData down_arrow = IconData(0xf35d, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>arrow_up</i> &#x2014; Cupertino icon for a solid up arrow.
+  /// This is the same icon as [arrow_up] in cupertino_icons 1.0.0+.
+  static const IconData up_arrow = IconData(0xf366, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>battery_100</i> &#x2014; Cupertino icon for a charging battery.
+  /// This is the same icon as [battery_100], [battery_full] and [battery_75_percent] in cupertino_icons 1.0.0+.
+  static const IconData battery_charging = IconData(0xf111, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>battery_0</i> &#x2014; Cupertino icon for an empty battery.
+  /// This is the same icon as [battery_0] in cupertino_icons 1.0.0+.
+  static const IconData battery_empty = IconData(0xf112, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>battery_100</i> &#x2014; Cupertino icon for a full battery.
+  /// This is the same icon as [battery_100], [battery_charging] and [battery_75_percent] in cupertino_icons 1.0.0+.
+  static const IconData battery_full = IconData(0xf113, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>battery_100</i> &#x2014; Cupertino icon for a 75% charged battery.
+  /// This is the same icon as [battery_100], [battery_charging] and [battery_full] in cupertino_icons 1.0.0+.
+  static const IconData battery_75_percent = IconData(0xf114, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>battery_25</i> &#x2014; Cupertino icon for a 25% charged battery.
+  /// This is the same icon as [battery_25] in cupertino_icons 1.0.0+.
+  static const IconData battery_25_percent = IconData(0xf115, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>bluetooth</i> &#x2014; Cupertino icon for the Bluetooth logo.
+  /// This icon is available in cupertino_icons 1.0.0+ for backward
+  /// compatibility but not part of Apple icons' aesthetics.
+  static const IconData bluetooth = IconData(0xf116, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>arrow_counterclockwise</i> &#x2014; Cupertino icon for a restart arrow, pointing downwards.
+  /// This is the same icon as [arrow_counterclockwise] in cupertino_icons 1.0.0+.
+  static const IconData restart = IconData(0xf21c, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>arrowshape_turn_up_left_2</i> &#x2014; Cupertino icon for two curved up and left pointing arrows.
+  /// This is the same icon as [arrowshape_turn_up_left_2] in cupertino_icons 1.0.0+.
+  static const IconData reply_all = IconData(0xf21d, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>arrowshape_turn_up_left_2_fill</i> &#x2014; Cupertino icon for a curved up and left pointing arrow.
+  /// This is the same icon as [arrowshape_turn_up_left_2_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// For another version of this icon, see [reply].
+  static const IconData reply_thick_solid = IconData(0xf21e, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>square_arrow_up</i> &#x2014; Cupertino icon for an iOS style share icon with an arrow pointing upwards to the right from a box.
+  /// This is the same icon as [square_arrow_up] and [share_up] in cupertino_icons 1.0.0+.
+  ///
+  /// For another version of this icon (introduced in iOS 7), see [share].
+  static const IconData share_up = IconData(0xf220, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>shuffle_medium</i> &#x2014; Cupertino icon for two thin right-facing intertwined arrows.
+  /// This is the same icon as [shuffle_medium] and [shuffle_thick] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [shuffle_medium], with slightly thicker arrows.
+  ///  * [shuffle_thick], with thicker, bold arrows.
+  static const IconData shuffle = IconData(0xf4a9, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>shuffle</i> &#x2014; Cupertino icon for an two medium thickness right-facing intertwined arrows.
+  /// This is the same icon as [shuffle] and [shuffle_thick] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [shuffle], with thin arrows.
+  ///  * [shuffle_thick], with thicker, bold arrows.
+  static const IconData shuffle_medium = IconData(0xf4a8, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>shuffle_medium</i> &#x2014; Cupertino icon for two thick right-facing intertwined arrows.
+  /// This is the same icon as [shuffle_medium] and [shuffle] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [shuffle], with thin arrows.
+  ///  * [shuffle_medium], with slightly thinner arrows.
+  static const IconData shuffle_thick = IconData(0xf221, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>camera</i> &#x2014; Cupertino icon for a camera for still photographs. This icon is filled in.
+  /// This is the same icon as [camera] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [photo_camera], which is similar, but not filled in.
+  ///  * [video_camera_solid], for the moving picture equivalent.
+  static const IconData photo_camera = IconData(0xf3f5, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>camera_fill</i> &#x2014; Cupertino icon for a camera for still photographs. This icon is not filled in.
+  /// This is the same icon as [camera_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [photo_camera_solid], which is similar, but filled in.
+  ///  * [video_camera], for the moving picture equivalent.
+  static const IconData photo_camera_solid = IconData(0xf3f6, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>videocam</i> &#x2014; Cupertino icon for a camera for moving pictures. This icon is not filled in.
+  /// This is the same icon as [videocam] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [video_camera_solid], which is similar, but filled in.
+  ///  * [photo_camera], for the still photograph equivalent.
+  static const IconData video_camera = IconData(0xf4cc, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>videocam_fill</i> &#x2014; Cupertino icon for a camera for moving pictures. This icon is filled in.
+  /// This is the same icon as [videocam_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [video_camera], which is similar, but not filled in.
+  ///  * [photo_camera_solid], for the still photograph equivalent.
+  static const IconData video_camera_solid = IconData(0xf4cd, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>camera_rotate</i> &#x2014; Cupertino icon for a camera containing two circular arrows pointing at each other, which indicate switching. This icon is not filled in.
+  /// This is the same icon as [camera_rotate] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [switch_camera_solid], which is similar, but filled in.
+  static const IconData switch_camera = IconData(0xf49e, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>camera_rotate_fill</i> &#x2014; Cupertino icon for a camera containing two circular arrows pointing at each other, which indicate switching. This icon is filled in.
+  /// This is the same icon as [camera_rotate_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [switch_camera], which is similar, but not filled in.
+  static const IconData switch_camera_solid = IconData(0xf49f, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>rectangle_stack</i> &#x2014; Cupertino icon for a collection of folders, which store collections of files, i.e. an album. This icon is not filled in.
+  /// This is the same icon as [rectangle_stack] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [collections_solid], which is similar, but filled in.
+  static const IconData collections = IconData(0xf3c9, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>rectangle_stack_fill</i> &#x2014; Cupertino icon for a collection of folders, which store collections of files, i.e. an album. This icon is filled in.
+  /// This is the same icon as [rectangle_stack_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [collections], which is similar, but not filled in.
+  static const IconData collections_solid = IconData(0xf3ca, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>folder_open</i> &#x2014; Cupertino icon for a single folder, which stores multiple files. This icon is not filled in.
+  /// This is the same icon as [folder_open] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [folder_solid], which is similar, but filled in.
+  ///  * [folder_open], which is the pre-iOS 7 version of this icon.
+  static const IconData folder = IconData(0xf434, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>folder_fill</i> &#x2014; Cupertino icon for a single folder, which stores multiple files. This icon is filled in.
+  /// This is the same icon as [folder_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [folder], which is similar, but not filled in.
+  ///  * [folder_open], which is the pre-iOS 7 version of this icon and not filled in.
+  static const IconData folder_solid = IconData(0xf435, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>folder</i> &#x2014; Cupertino icon for a single folder that indicates being opened. A folder like this typically stores multiple files.
+  /// This is the same icon as [folder] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [folder], which is the equivalent of this icon for iOS versions later than or equal to iOS 7.
+  static const IconData folder_open = IconData(0xf38a, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>trash</i> &#x2014; Cupertino icon for a trash bin for removing items. This icon is not filled in.
+  /// This is the same icon as [trash] and [delete_simple] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [delete_solid], which is similar, but filled in.
+  static const IconData delete = IconData(0xf4c4, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>trash_fill</i> &#x2014; Cupertino icon for a trash bin for removing items. This icon is filled in.
+  /// This is the same icon as [trash_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [delete], which is similar, but not filled in.
+  static const IconData delete_solid = IconData(0xf4c5, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>trash</i> &#x2014; Cupertino icon for a trash bin with minimal detail for removing items.
+  /// This is the same icon as [trash] and [delete] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [delete], which is the iOS 7 equivalent of this icon with richer detail.
+  static const IconData delete_simple = IconData(0xf37f, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>pen</i> &#x2014; Cupertino icon for a simple pen.
+  ///
+  /// See also:
+  ///
+  ///  * [pencil], which is similar, but has less detail and looks like a pencil.
+  static const IconData pen = IconData(0xf2bf, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>pencil</i> &#x2014; Cupertino icon for a simple pencil.
+  ///
+  /// See also:
+  ///
+  ///  * [pen], which is similar, but has more detail and looks like a pen.
+  static const IconData pencil = IconData(0xf37e, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>square_pencil</i> &#x2014; Cupertino icon for a box for writing and a pen on top (that indicates the writing). This icon is not filled in.
+  /// This is the same icon as [square_pencil] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [create_solid], which is similar, but filled in.
+  ///  * [pencil], which is just a pencil.
+  ///  * [pen], which is just a pen.
+  static const IconData create = IconData(0xf417, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>square_pencil_fill</i> &#x2014; Cupertino icon for a box for writing and a pen on top (that indicates the writing). This icon is filled in.
+  /// This is the same icon as [square_pencil_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [create], which is similar, but not filled in.
+  ///  * [pencil], which is just a pencil.
+  ///  * [pen], which is just a pen.
+  static const IconData create_solid = IconData(0xf417, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>arrow_clockwise</i> &#x2014; Cupertino icon for an arrow on a circular path with its end pointing at its start.
+  /// This is the same icon as [arrow_clockwise], [refresh_thin] and [refresh_thick] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [refresh_circled], which is this icon put in a circle.
+  ///  * [refresh_thin], which is an arrow of the same concept, but thinner and with a smaller gap in between its end and start.
+  ///  * [refresh_thick], which is similar, but rotated 45 degrees clockwise and thicker.
+  ///  * [refresh_bold], which is similar, but rotated 90 degrees clockwise and much thicker.
+  static const IconData refresh = IconData(0xf49a, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>arrow_clockwise_circle</i> &#x2014; Cupertino icon for an arrow on a circular path with its end pointing at its start surrounded by a circle. This is icon is not filled in.
+  /// This is the same icon as [arrow_clockwise_circle] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [refresh_circled_solid], which is similar, but filled in.
+  ///  * [refresh], which is the arrow of this icon without a circle.
+  static const IconData refresh_circled = IconData(0xf49b, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>arrow_clockwise_circle_fill</i> &#x2014; Cupertino icon for an arrow on a circular path with its end pointing at its start surrounded by a circle. This is icon is filled in.
+  /// This is the same icon as [arrow_clockwise_circle_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [refresh_circled], which is similar, but not filled in.
+  ///  * [refresh], which is the arrow of this icon filled in without a circle.
+  static const IconData refresh_circled_solid = IconData(0xf49c, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>arrow_clockwise</i> &#x2014; Cupertino icon for an arrow on a circular path with its end pointing at its start.
+  /// This is the same icon as [arrow_clockwise], [refresh] and [refresh_thick] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [refresh], which is an arrow of the same concept, but thicker and with a larger gap in between its end and start.
+  static const IconData refresh_thin = IconData(0xf49d, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>arrow_clockwise</i> &#x2014; Cupertino icon for an arrow on a circular path with its end pointing at its start.
+  /// This is the same icon as [arrow_clockwise], [refresh_thin] and [refresh] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [refresh], which is similar, but rotated 45 degrees anti-clockwise and thinner.
+  ///  * [refresh_bold], which is similar, but rotated 45 degrees clockwise and thicker.
+  static const IconData refresh_thick = IconData(0xf3a8, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>arrow_counterclockwise</i> &#x2014; Cupertino icon for an arrow on a circular path with its end pointing at its start.
+  /// This is the same icon as [arrow_counterclockwise] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [refresh_thick], which is similar, but rotated 45 degrees anti-clockwise and thinner.
+  ///  * [refresh], which is similar, but rotated 90 degrees anti-clockwise and much thinner.
+  static const IconData refresh_bold = IconData(0xf21c, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>xmark</i> &#x2014; Cupertino icon for a cross of two diagonal lines from edge to edge crossing in an angle of 90 degrees, which is used for dismissal.
+  /// This is the same icon as [xmark] and [clear] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [clear_circled], which uses this cross as a blank space in a filled out circled.
+  ///  * [clear], which uses a thinner cross and is the iOS 7 equivalent of this icon.
+  static const IconData clear_thick = IconData(0xf2d7, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>xmark_circle_fill</i> &#x2014; Cupertino icon for a cross of two diagonal lines from edge to edge crossing in an angle of 90 degrees, which is used for dismissal, used as a blank space in a circle.
+  /// This is the same icon as [xmark_circle_fill] and [clear_circled_solid] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [clear], which is equivalent to the cross of this icon without a circle.
+  ///  * [clear_circled_solid], which is similar, but uses a thinner cross.
+  static const IconData clear_thick_circled = IconData(0xf36e, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>xmark</i> &#x2014; Cupertino icon for a cross of two diagonal lines from edge to edge crossing in an angle of 90 degrees, which is used for dismissal.
+  /// This is the same icon as [xmark] and [clear_thick] in cupertino_icons 1.0.0+.
+  ///
+  ///
+  /// See also:
+  ///
+  ///  * [clear_circled], which consists of this cross and a circle surrounding it.
+  ///  * [clear], which uses a thicker cross and is the pre-iOS 7 equivalent of this icon.
+  static const IconData clear = IconData(0xf404, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>xmark_circle</i> &#x2014; Cupertino icon for a cross of two diagonal lines from edge to edge crossing in an angle of 90 degrees, which is used for dismissal, surrounded by circle. This icon is not filled in.
+  /// This is the same icon as [xmark_circle] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [clear_circled_solid], which is similar, but filled in.
+  ///  * [clear], which is the standalone cross of this icon.
+  static const IconData clear_circled = IconData(0xf405, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>xmark_circle_fill</i> &#x2014; Cupertino icon for a cross of two diagonal lines from edge to edge crossing in an angle of 90 degrees, which is used for dismissal, used as a blank space in a circle. This icon is filled in.
+  /// This is the same icon as [xmark_circle_fill] and [clear_thick_circled] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [clear_circled], which is similar, but not filled in.
+  static const IconData clear_circled_solid = IconData(0xf406, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>plus</i> &#x2014; Cupertino icon for an two straight lines, one horizontal and one vertical, meeting in the middle, which is the equivalent of a plus sign.
+  /// This is the same icon as [plus] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [plus_circled], which is the pre-iOS 7 version of this icon with a thicker cross.
+  ///  * [add_circled], which consists of the plus and a circle around it.
+  static const IconData add = IconData(0xf489, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>plus_circle</i> &#x2014; Cupertino icon for an two straight lines, one horizontal and one vertical, meeting in the middle, which is the equivalent of a plus sign, surrounded by a circle. This icon is not filled in.
+  /// This is the same icon as [plus_circle] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [plus_circled], which is the pre-iOS 7 version of this icon with a thicker cross and a filled in circle.
+  ///  * [add_circled_solid], which is similar, but filled in.
+  static const IconData add_circled = IconData(0xf48a, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>plus_circle_fill</i> &#x2014; Cupertino icon for an two straight lines, one horizontal and one vertical, meeting in the middle, which is the equivalent of a plus sign, surrounded by a circle. This icon is not filled in.
+  /// This is the same icon as [plus_circle_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [plus_circled], which is the pre-iOS 7 version of this icon with a thicker cross.
+  ///  * [add_circled], which is similar, but not filled in.
+  static const IconData add_circled_solid = IconData(0xf48b, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>gear_alt</i> &#x2014; Cupertino icon for a gear with eight cogs. This icon is not filled in.
+  /// This is the same icon as [gear_alt] and [gear_big] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [gear_solid], which is similar, but filled in.
+  ///  * [gear_big], which is the pre-iOS 7 version of this icon and appears bigger because of fewer and bigger cogs.
+  ///  * [settings], which is another cogwheel with a different design.
+  static const IconData gear = IconData(0xf43c, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>gear_alt_fill</i> &#x2014; Cupertino icon for a gear with eight cogs. This icon is filled in.
+  /// This is the same icon as [gear_alt_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [gear], which is similar, but not filled in.
+  ///  * [settings_solid], which is another cogwheel with a different design.
+  static const IconData gear_solid = IconData(0xf43d, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>gear_alt</i> &#x2014; Cupertino icon for a gear with six cogs.
+  /// This is the same icon as [gear_alt] and [gear] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [gear], which is the iOS 7 version of this icon and appears smaller because of more and larger cogs.
+  ///  * [settings_solid], which is another cogwheel with a different design.
+  static const IconData gear_big = IconData(0xf2f7, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>settings</i> &#x2014; Cupertino icon for a cogwheel with many cogs and decoration in the middle. This icon is not filled in.
+  ///
+  /// See also:
+  ///
+  ///  * [settings_solid], which is similar, but filled in.
+  ///  * [gear], which is another cogwheel with a different design.
+  static const IconData settings = IconData(0xf411, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>settings_solid</i> &#x2014; Cupertino icon for a cogwheel with many cogs and decoration in the middle. This icon is filled in.
+  ///
+  /// See also:
+  ///
+  ///  * [settings], which is similar, but not filled in.
+  ///  * [gear_solid], which is another cogwheel with a different design.
+  static const IconData settings_solid = IconData(0xf412, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>music_note</i> &#x2014; Cupertino icon for a symbol representing a solid single musical note.
+  ///
+  /// See also:
+  ///
+  ///  * [double_music_note], which is similar, but with 2 connected notes.
+  static const IconData music_note = IconData(0xf46b, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>music_note_2</i> &#x2014; Cupertino icon for a symbol representing 2 connected musical notes.
+  /// This is the same icon as [music_note_2] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [music_note], which is similar, but with a single note.
+  static const IconData double_music_note = IconData(0xf46c, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>play</i> &#x2014; Cupertino icon for a triangle facing to the right. This icon is not filled in.
+  /// This is the same icon as [play] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [play_arrow_solid], which is similar, but filled in.
+  static const IconData play_arrow = IconData(0xf487, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>play_fill</i> &#x2014; Cupertino icon for a triangle facing to the right. This icon is filled in.
+  /// This is the same icon as [play_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [play_arrow], which is similar, but not filled in.
+  static const IconData play_arrow_solid = IconData(0xf488, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>pause</i> &#x2014; Cupertino icon for an two vertical rectangles. This icon is not filled in.
+  ///
+  /// See also:
+  ///
+  ///  * [pause_solid], which is similar, but filled in.
+  static const IconData pause = IconData(0xf477, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>pause_fill</i> &#x2014; Cupertino icon for an two vertical rectangles. This icon is filled in.
+  /// This is the same icon as [pause_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [pause], which is similar, but not filled in.
+  static const IconData pause_solid = IconData(0xf478, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>infinite</i> &#x2014; Cupertino icon for the infinity symbol.
+  /// This is the same icon as [infinite] and [loop_thick] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [loop_thick], which is similar, but thicker.
+  static const IconData loop = IconData(0xf449, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>infinite</i> &#x2014; Cupertino icon for the infinity symbol.
+  /// This is the same icon as [infinite] and [loop] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [loop], which is similar, but thinner.
+  static const IconData loop_thick = IconData(0xf44a, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>speaker_1_fill</i> &#x2014; Cupertino icon for a speaker with a single small sound wave.
+  /// This is the same icon as [speaker_1_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [volume_mute], which is similar, but has no sound waves.
+  ///  * [volume_off], which is similar, but with an additional larger sound wave and a diagonal line crossing the whole icon.
+  ///  * [volume_up], which has an additional larger sound wave next to the small one.
+  static const IconData volume_down = IconData(0xf3b7, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>speaker_fill</i> &#x2014; Cupertino icon for a speaker symbol.
+  /// This is the same icon as [speaker_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [volume_down], which is similar, but adds a small sound wave.
+  ///  * [volume_off], which is similar, but adds a small and a large sound wave and a diagonal line crossing the whole icon.
+  ///  * [volume_up], which is similar, but has a small and a large sound wave.
+  static const IconData volume_mute = IconData(0xf3b8, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>speaker_slash_fill</i> &#x2014; Cupertino icon for a speaker with a small and a large sound wave and a diagonal line crossing the whole icon.
+  /// This is the same icon as [speaker_slash_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [volume_down], which is similar, but not crossed out and only has the small wave.
+  ///  * [volume_mute], which is similar, but not crossed out.
+  ///  * [volume_up], which is the version of this icon that is not crossed out.
+  static const IconData volume_off = IconData(0xf3b9, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>speaker_3_fill</i> &#x2014; Cupertino icon for a speaker with a small and a large sound wave.
+  /// This is the same icon as [speaker_3_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [volume_down], which is similar, but only has the small sound wave.
+  ///  * [volume_mute], which is similar, but has no sound waves.
+  ///  * [volume_off], which is the crossed out version of this icon.
+  static const IconData volume_up = IconData(0xf3ba, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>arrow_up_left_arrow_down_right</i> &#x2014; Cupertino icon for all four corners of a square facing inwards.
+  /// This is the same icon as [arrow_up_left_arrow_down_right] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [fullscreen_exit], which is similar, but has the corners facing outwards.
+  static const IconData fullscreen = IconData(0xf386, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>arrow_down_right_arrow_up_left</i> &#x2014; Cupertino icon for all four corners of a square facing outwards.
+  /// This is the same icon as [arrow_down_right_arrow_up_left] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [fullscreen], which is similar, but has the corners facing inwards.
+  static const IconData fullscreen_exit = IconData(0xf37d, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>mic_slash</i> &#x2014; Cupertino icon for a filled in microphone with a diagonal line crossing it.
+  /// This is the same icon as [mic_slash] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [mic], which is similar, but not filled in and without a diagonal line.
+  ///  * [mic_solid], which is similar, but without a diagonal line.
+  static const IconData mic_off = IconData(0xf45f, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>mic</i> &#x2014; Cupertino icon for a microphone.
+  ///
+  /// See also:
+  ///
+  ///  * [mic_solid], which is similar, but filled in.
+  ///  * [mic_off], which is similar, but filled in and with a diagonal line crossing the icon.
+  static const IconData mic = IconData(0xf460, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>mic_fill</i> &#x2014; Cupertino icon for a filled in microphone.
+  /// This is the same icon as [mic_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [mic], which is similar, but not filled in.
+  ///  * [mic_off], which is similar, but with a diagonal line crossing the icon.
+  static const IconData mic_solid = IconData(0xf461, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>time</i> &#x2014; Cupertino icon for a circle with a dotted clock face inside with hands showing 10:30.
+  /// This is the same icon as [time] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [clock_solid], which is similar, but filled in.
+  ///  * [time], which is similar, but without dots on the clock face.
+  ///  * [time_solid], which is similar, but filled in and without dots on the clock face.
+  static const IconData clock = IconData(0xf4be, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>clock_fill</i> &#x2014; Cupertino icon for a filled in circle with a dotted clock face inside with hands showing 10:30.
+  /// This is the same icon as [clock_fill] and [time_solid] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [clock], which is similar, but not filled in.
+  ///  * [time], which is similar, but not filled in and without dots on the clock face.
+  ///  * [time_solid], which is similar, but without dots on the clock face.
+  static const IconData clock_solid = IconData(0xf4bf, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>clock</i> &#x2014; Cupertino icon for a circle with with a 90 degree angle shape in the center, resembling a clock with hands showing 09:00.
+  /// This is the same icon as [clock] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [time_solid], which is similar, but filled in.
+  ///  * [clock], which is similar, but with dots on the clock face.
+  ///  * [clock_solid], which is similar, but filled in and with dots on the clock face.
+  static const IconData time = IconData(0xf402, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>clock_fill</i> &#x2014; Cupertino icon for a filled in circle with with a 90 degree angle shape in the center, resembling a clock with hands showing 09:00.
+  /// This is the same icon as [clock_fill] and [clock_solid] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [time], which is similar, but not filled in.
+  ///  * [clock], which is similar, but not filled in and with dots on the clock face.
+  ///  * [clock_solid], which is similar, but with dots on the clock face.
+  static const IconData time_solid = IconData(0xf403, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>lock</i> &#x2014; Cupertino icon for an unlocked padlock.
+  /// This is the same icon as [lock] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [padlock_solid], which is similar, but filled in.
+  static const IconData padlock = IconData(0xf4c8, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>lock_fill</i> &#x2014; Cupertino icon for an unlocked padlock.
+  /// This is the same icon as [lock_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [padlock], which is similar, but not filled in.
+  static const IconData padlock_solid = IconData(0xf4c9, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>eye</i> &#x2014; Cupertino icon for an open eye.
+  ///
+  /// See also:
+  ///
+  ///  * [eye_solid], which is similar, but filled in.
+  static const IconData eye = IconData(0xf424, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>eye_fill</i> &#x2014; Cupertino icon for an open eye.
+  /// This is the same icon as [eye_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [eye], which is similar, but not filled in.
+  static const IconData eye_solid = IconData(0xf425, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>person</i> &#x2014; Cupertino icon for a single person. This icon is not filled in.
+  ///
+  /// See also:
+  ///
+  ///  * [person_solid], which is similar, but filled in.
+  ///  * [person_add], which has an additional plus sign next to the person.
+  ///  * [group], which consists of three people.
+  static const IconData person = IconData(0xf47d, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>person_fill</i> &#x2014; Cupertino icon for a single person. This icon is filled in.
+  /// This is the same icon as [person_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [person], which is similar, but not filled in.
+  ///  * [person_add_solid], which has an additional plus sign next to the person.
+  ///  * [group_solid], which consists of three people.
+  static const IconData person_solid = IconData(0xf47e, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>person_badge_plus</i> &#x2014; Cupertino icon for a single person with a plus sign next to it. This icon is not filled in.
+  /// This is the same icon as [person_badge_plus] in cupertino_icons 1.0.0+.x
+  ///
+  /// See also:
+  ///
+  ///  * [person_add_solid], which is similar, but filled in.
+  ///  * [person], which is just the person.
+  ///  * [group], which consists of three people.
+  static const IconData person_add = IconData(0xf47f, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>person_badge_plus_fill</i> &#x2014; Cupertino icon for a single person with a plus sign next to it. This icon is filled in.
+  /// This is the same icon as [person_badge_plus_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [person_add], which is similar, but not filled in.
+  ///  * [person_solid], which is just the person.
+  ///  * [group_solid], which consists of three people.
+  static const IconData person_add_solid = IconData(0xf480, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>person_3</i> &#x2014; Cupertino icon for a group of three people. This icon is not filled in.
+  /// This is the same icon as [person_3] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [group_solid], which is similar, but filled in.
+  ///  * [person], which is just a single person.
+  static const IconData group = IconData(0xf47b, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>person_3_fill</i> &#x2014; Cupertino icon for a group of three people. This icon is filled in.
+  /// This is the same icon as [person_3_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [group], which is similar, but not filled in.
+  ///  * [person_solid], which is just a single person.
+  static const IconData group_solid = IconData(0xf47c, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>envelope</i> &#x2014; Cupertino icon for the outline of a closed mail envelope.
+  /// This is the same icon as [envelope] in cupertino_icons 1.0.0+.
+  static const IconData mail = IconData(0xf422, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>envelope_fill</i> &#x2014; Cupertino icon for a closed mail envelope. This icon is filled in.
+  /// This is the same icon as [envelope_fill] in cupertino_icons 1.0.0+.
+  static const IconData mail_solid = IconData(0xf423, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>location</i> &#x2014; Cupertino icon for a location pin.
+  static const IconData location = IconData(0xf455, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>placemark_fill</i> &#x2014; Cupertino icon for a location pin. This icon is filled in.
+  /// This is the same icon as [placemark_fill] in cupertino_icons 1.0.0+.
+  static const IconData location_solid = IconData(0xf456, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>tags</i> &#x2014; Cupertino icon for the outline of a sticker tag.
+  /// This is the same icon as [tags] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [tags], similar but with 2 overlapping tags.
+  static const IconData tag = IconData(0xf48c, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>tag_fill</i> &#x2014; Cupertino icon for a sticker tag. This icon is filled in.
+  /// This is the same icon as [tag_fill] and [tags_solid] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [tags_solid], similar but with 2 overlapping tags.
+  static const IconData tag_solid = IconData(0xf48d, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>tag</i> &#x2014; Cupertino icon for outlines of 2 overlapping sticker tags.
+  /// This is the same icon as [tag] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [tag], similar but with only one tag.
+  static const IconData tags = IconData(0xf48e, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>tag_fill</i> &#x2014; Cupertino icon for 2 overlapping sticker tags. This icon is filled in.
+  /// This is the same icon as [tag_fill] and [tag_solid] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [tag_solid], similar but with only one tag.
+  static const IconData tags_solid = IconData(0xf48f, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>bus</i> &#x2014; Cupertino icon for a filled in bus.
+  /// This icon is available in cupertino_icons 1.0.0+ for backward
+  /// compatibility but not part of Apple icons' aesthetics.
+  static const IconData bus = IconData(0xf36d, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>car_fill</i> &#x2014; Cupertino icon for a filled in car.
+  /// This is the same icon as [car_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [car_detailed], similar, but a more detailed and realistic representation.
+  static const IconData car = IconData(0xf36f, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>car_detailed</i> &#x2014; Cupertino icon for a filled in detailed, realistic car.
+  ///
+  /// See also:
+  ///
+  ///  * [car], similar, but a more simple representation.
+  /// This icon is available in cupertino_icons 1.0.0+ for backward
+  /// compatibility but not part of Apple icons' aesthetics.
+  static const IconData car_detailed = IconData(0xf2c1, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>train_style_one</i> &#x2014; Cupertino icon for a filled in train with a window divided in half and two headlights.
+  /// This icon is available in cupertino_icons 1.0.0+ for backward
+  /// compatibility but not part of Apple icons' aesthetics.
+  ///
+  /// See also:
+  ///
+  ///  * [train_style_two], similar, but with a full, undivided window and a single, centered headlight.
+  static const IconData train_style_one = IconData(0xf3af, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>train_style_two</i> &#x2014; Cupertino icon for a filled in train with a window and a single, centered headlight.
+  /// This icon is available in cupertino_icons 1.0.0+ for backward
+  /// compatibility but not part of Apple icons' aesthetics.
+  ///
+  /// See also:
+  ///
+  ///  * [train_style_one], similar, but with a with a window divided in half and two headlights.
+  static const IconData train_style_two = IconData(0xf3b4, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>paw</i> &#x2014; Cupertino icon for an outlined paw.
+  ///
+  /// See also:
+  ///
+  ///  * [paw_solid], similar, but filled in.
+  static const IconData paw = IconData(0xf479, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>paw</i> &#x2014; Cupertino icon for a filled in paw.
+  /// This is the same icon as [paw] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [paw], similar, but not filled in.
+  static const IconData paw_solid = IconData(0xf47a, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>gamecontroller</i> &#x2014; Cupertino icon for an outlined game controller.
+  /// This is the same icon as [gamecontroller] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [game_controller_solid], similar, but filled in.
+  static const IconData game_controller = IconData(0xf43a, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>gamecontroller_fill</i> &#x2014; Cupertino icon for a filled in game controller.
+  /// This is the same icon as [gamecontroller_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [game_controller], similar, but not filled in.
+  static const IconData game_controller_solid = IconData(0xf43b, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>lab_flask</i> &#x2014; Cupertino icon for an outlined lab flask.
+  /// This icon is available in cupertino_icons 1.0.0+ for backward
+  /// compatibility but not part of Apple icons' aesthetics.
+  ///
+  /// See also:
+  ///
+  ///  * [lab_flask_solid], similar, but filled in.
+  static const IconData lab_flask = IconData(0xf430, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>lab_flask_solid</i> &#x2014; Cupertino icon for a filled in lab flask.
+  /// This icon is available in cupertino_icons 1.0.0+ for backward
+  /// compatibility but not part of Apple icons' aesthetics.
+  ///
+  /// See also:
+  ///
+  ///  * [lab_flask], similar, but not filled in.
+  static const IconData lab_flask_solid = IconData(0xf431, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>heart</i> &#x2014; Cupertino icon for an outlined heart shape. Can be used to indicate like or favorite states.
+  ///
+  /// See also:
+  ///
+  ///  * [heart_solid], same shape, but filled in.
+  static const IconData heart = IconData(0xf442, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>heart_solid</i> &#x2014; Cupertino icon for a filled heart shape. Can be used to indicate like or favorite states.
+  /// This is the same icon as [heart_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [heart], same shape, but not filled in.
+  static const IconData heart_solid = IconData(0xf443, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>bell</i> &#x2014; Cupertino icon for an outlined bell. Can be used to represent notifications.
+  ///
+  /// See also:
+  ///
+  ///  * [bell_solid], same shape, but filled in.
+  static const IconData bell = IconData(0xf3e1, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>bell_fill</i> &#x2014; Cupertino icon for a filled bell. Can be used represent notifications.
+  /// This is the same icon as [bell_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [bell], same shape, but not filled in.
+  static const IconData bell_solid = IconData(0xf3e2, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>news</i> &#x2014; Cupertino icon for an outlined folded newspaper icon.
+  /// This icon is available in cupertino_icons 1.0.0+ for backward
+  /// compatibility but not part of Apple icons' aesthetics.
+  ///
+  /// See also:
+  ///
+  ///  * [news_solid], same shape, but filled in.
+  static const IconData news = IconData(0xf471, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>news_solid</i> &#x2014; Cupertino icon for a filled folded newspaper icon.
+  /// This icon is available in cupertino_icons 1.0.0+ for backward
+  /// compatibility but not part of Apple icons' aesthetics.
+  ///
+  /// See also:
+  ///
+  ///  * [news], same shape, but not filled in.
+  static const IconData news_solid = IconData(0xf472, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>sun_max</i> &#x2014; Cupertino icon for an outlined brightness icon.
+  /// This is the same icon as [sun_max] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [brightness_solid], same shape, but filled in.
+  static const IconData brightness = IconData(0xf4B6, fontFamily: iconFont, fontPackage: iconFontPackage);
+
+  /// <i class='cupertino-icons md-36'>sun_max_fill</i> &#x2014; Cupertino icon for a filled in brightness icon.
+  /// This is the same icon as [sun_max_fill] in cupertino_icons 1.0.0+.
+  ///
+  /// See also:
+  ///
+  ///  * [brightness], same shape, but not filled in.
+  static const IconData brightness_solid = IconData(0xf4B7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  // END LEGACY PRE SF SYMBOLS NAMES
+  // ===========================================================================
+
+  // ===========================================================================
+  // BEGIN GENERATED SF SYMBOLS NAMES
+  /// <i class='cupertino-icons md-36'>airplane</i> &#x2014; Cupertino icon named "airplane". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData airplane = IconData(0xf4d4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>alarm</i> &#x2014; Cupertino icon named "alarm". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData alarm = IconData(0xf4d5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>alarm_fill</i> &#x2014; Cupertino icon named "alarm_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData alarm_fill = IconData(0xf4d6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>alt</i> &#x2014; Cupertino icon named "alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData alt = IconData(0xf4d7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>ant</i> &#x2014; Cupertino icon named "ant". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData ant = IconData(0xf4d8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>ant_circle</i> &#x2014; Cupertino icon named "ant_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData ant_circle = IconData(0xf4d9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>ant_circle_fill</i> &#x2014; Cupertino icon named "ant_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData ant_circle_fill = IconData(0xf4da, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>ant_fill</i> &#x2014; Cupertino icon named "ant_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData ant_fill = IconData(0xf4db, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>antenna_radiowaves_left_right</i> &#x2014; Cupertino icon named "antenna_radiowaves_left_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData antenna_radiowaves_left_right = IconData(0xf4dc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>app</i> &#x2014; Cupertino icon named "app". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData app = IconData(0xf4dd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>app_badge</i> &#x2014; Cupertino icon named "app_badge". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData app_badge = IconData(0xf4de, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>app_badge_fill</i> &#x2014; Cupertino icon named "app_badge_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData app_badge_fill = IconData(0xf4df, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>app_fill</i> &#x2014; Cupertino icon named "app_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData app_fill = IconData(0xf4e0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>archivebox</i> &#x2014; Cupertino icon named "archivebox". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData archivebox = IconData(0xf4e1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>archivebox_fill</i> &#x2014; Cupertino icon named "archivebox_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData archivebox_fill = IconData(0xf4e2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_2_circlepath</i> &#x2014; Cupertino icon named "arrow_2_circlepath". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_2_circlepath = IconData(0xf4e3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_2_circlepath_circle</i> &#x2014; Cupertino icon named "arrow_2_circlepath_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_2_circlepath_circle = IconData(0xf4e4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_2_circlepath_circle_fill</i> &#x2014; Cupertino icon named "arrow_2_circlepath_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_2_circlepath_circle_fill = IconData(0xf4e5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_2_squarepath</i> &#x2014; Cupertino icon named "arrow_2_squarepath". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_2_squarepath = IconData(0xf4e6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_3_trianglepath</i> &#x2014; Cupertino icon named "arrow_3_trianglepath". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_3_trianglepath = IconData(0xf4e7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_branch</i> &#x2014; Cupertino icon named "arrow_branch". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_branch = IconData(0xf4e8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_clockwise</i> &#x2014; Cupertino icon named "arrow_clockwise". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [refresh] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [refresh_thin] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [refresh_thick] which is available in cupertino_icons 0.1.3.
+  static const IconData arrow_clockwise = IconData(0xf49a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_clockwise_circle</i> &#x2014; Cupertino icon named "arrow_clockwise_circle". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [refresh_circled] which is available in cupertino_icons 0.1.3.
+  static const IconData arrow_clockwise_circle = IconData(0xf49b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_clockwise_circle_fill</i> &#x2014; Cupertino icon named "arrow_clockwise_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [refresh_circled_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData arrow_clockwise_circle_fill = IconData(0xf49c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_counterclockwise</i> &#x2014; Cupertino icon named "arrow_counterclockwise". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [restart] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [refresh_bold] which is available in cupertino_icons 0.1.3.
+  static const IconData arrow_counterclockwise = IconData(0xf21c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_counterclockwise_circle</i> &#x2014; Cupertino icon named "arrow_counterclockwise_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_counterclockwise_circle = IconData(0xf4e9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_counterclockwise_circle_fill</i> &#x2014; Cupertino icon named "arrow_counterclockwise_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_counterclockwise_circle_fill = IconData(0xf4ea, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down</i> &#x2014; Cupertino icon named "arrow_down". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [down_arrow] which is available in cupertino_icons 0.1.3.
+  static const IconData arrow_down = IconData(0xf35d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_circle</i> &#x2014; Cupertino icon named "arrow_down_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_circle = IconData(0xf4eb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_circle_fill</i> &#x2014; Cupertino icon named "arrow_down_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_circle_fill = IconData(0xf4ec, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_doc</i> &#x2014; Cupertino icon named "arrow_down_doc". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_doc = IconData(0xf4ed, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_doc_fill</i> &#x2014; Cupertino icon named "arrow_down_doc_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_doc_fill = IconData(0xf4ee, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_left</i> &#x2014; Cupertino icon named "arrow_down_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_left = IconData(0xf4ef, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_left_circle</i> &#x2014; Cupertino icon named "arrow_down_left_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_left_circle = IconData(0xf4f0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_left_circle_fill</i> &#x2014; Cupertino icon named "arrow_down_left_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_left_circle_fill = IconData(0xf4f1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_left_square</i> &#x2014; Cupertino icon named "arrow_down_left_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_left_square = IconData(0xf4f2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_left_square_fill</i> &#x2014; Cupertino icon named "arrow_down_left_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_left_square_fill = IconData(0xf4f3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_right</i> &#x2014; Cupertino icon named "arrow_down_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_right = IconData(0xf4f4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_right_arrow_up_left</i> &#x2014; Cupertino icon named "arrow_down_right_arrow_up_left". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [fullscreen_exit] which is available in cupertino_icons 0.1.3.
+  static const IconData arrow_down_right_arrow_up_left = IconData(0xf37d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_right_circle</i> &#x2014; Cupertino icon named "arrow_down_right_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_right_circle = IconData(0xf4f5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_right_circle_fill</i> &#x2014; Cupertino icon named "arrow_down_right_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_right_circle_fill = IconData(0xf4f6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_right_square</i> &#x2014; Cupertino icon named "arrow_down_right_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_right_square = IconData(0xf4f7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_right_square_fill</i> &#x2014; Cupertino icon named "arrow_down_right_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_right_square_fill = IconData(0xf4f8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_square</i> &#x2014; Cupertino icon named "arrow_down_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_square = IconData(0xf4f9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_square_fill</i> &#x2014; Cupertino icon named "arrow_down_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_square_fill = IconData(0xf4fa, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_to_line</i> &#x2014; Cupertino icon named "arrow_down_to_line". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_to_line = IconData(0xf4fb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_down_to_line_alt</i> &#x2014; Cupertino icon named "arrow_down_to_line_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_down_to_line_alt = IconData(0xf4fc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_left</i> &#x2014; Cupertino icon named "arrow_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_left = IconData(0xf4fd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_left_circle</i> &#x2014; Cupertino icon named "arrow_left_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_left_circle = IconData(0xf4fe, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_left_circle_fill</i> &#x2014; Cupertino icon named "arrow_left_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_left_circle_fill = IconData(0xf4ff, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_left_right</i> &#x2014; Cupertino icon named "arrow_left_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_left_right = IconData(0xf500, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_left_right_circle</i> &#x2014; Cupertino icon named "arrow_left_right_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_left_right_circle = IconData(0xf501, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_left_right_circle_fill</i> &#x2014; Cupertino icon named "arrow_left_right_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_left_right_circle_fill = IconData(0xf502, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_left_right_square</i> &#x2014; Cupertino icon named "arrow_left_right_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_left_right_square = IconData(0xf503, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_left_right_square_fill</i> &#x2014; Cupertino icon named "arrow_left_right_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_left_right_square_fill = IconData(0xf504, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_left_square</i> &#x2014; Cupertino icon named "arrow_left_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_left_square = IconData(0xf505, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_left_square_fill</i> &#x2014; Cupertino icon named "arrow_left_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_left_square_fill = IconData(0xf506, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_left_to_line</i> &#x2014; Cupertino icon named "arrow_left_to_line". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_left_to_line = IconData(0xf507, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_left_to_line_alt</i> &#x2014; Cupertino icon named "arrow_left_to_line_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_left_to_line_alt = IconData(0xf508, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_merge</i> &#x2014; Cupertino icon named "arrow_merge". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_merge = IconData(0xf509, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_right</i> &#x2014; Cupertino icon named "arrow_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_right = IconData(0xf50a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_right_arrow_left</i> &#x2014; Cupertino icon named "arrow_right_arrow_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_right_arrow_left = IconData(0xf50b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_right_arrow_left_circle</i> &#x2014; Cupertino icon named "arrow_right_arrow_left_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_right_arrow_left_circle = IconData(0xf50c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_right_arrow_left_circle_fill</i> &#x2014; Cupertino icon named "arrow_right_arrow_left_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_right_arrow_left_circle_fill = IconData(0xf50d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_right_arrow_left_square</i> &#x2014; Cupertino icon named "arrow_right_arrow_left_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_right_arrow_left_square = IconData(0xf50e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_right_arrow_left_square_fill</i> &#x2014; Cupertino icon named "arrow_right_arrow_left_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_right_arrow_left_square_fill = IconData(0xf50f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_right_circle</i> &#x2014; Cupertino icon named "arrow_right_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_right_circle = IconData(0xf510, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_right_circle_fill</i> &#x2014; Cupertino icon named "arrow_right_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_right_circle_fill = IconData(0xf511, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_right_square</i> &#x2014; Cupertino icon named "arrow_right_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_right_square = IconData(0xf512, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_right_square_fill</i> &#x2014; Cupertino icon named "arrow_right_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_right_square_fill = IconData(0xf513, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_right_to_line</i> &#x2014; Cupertino icon named "arrow_right_to_line". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_right_to_line = IconData(0xf514, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_right_to_line_alt</i> &#x2014; Cupertino icon named "arrow_right_to_line_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_right_to_line_alt = IconData(0xf515, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_swap</i> &#x2014; Cupertino icon named "arrow_swap". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_swap = IconData(0xf516, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_turn_down_left</i> &#x2014; Cupertino icon named "arrow_turn_down_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_turn_down_left = IconData(0xf517, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_turn_down_right</i> &#x2014; Cupertino icon named "arrow_turn_down_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_turn_down_right = IconData(0xf518, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_turn_left_down</i> &#x2014; Cupertino icon named "arrow_turn_left_down". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_turn_left_down = IconData(0xf519, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_turn_left_up</i> &#x2014; Cupertino icon named "arrow_turn_left_up". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_turn_left_up = IconData(0xf51a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_turn_right_down</i> &#x2014; Cupertino icon named "arrow_turn_right_down". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_turn_right_down = IconData(0xf51b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_turn_right_up</i> &#x2014; Cupertino icon named "arrow_turn_right_up". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_turn_right_up = IconData(0xf51c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_turn_up_left</i> &#x2014; Cupertino icon named "arrow_turn_up_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_turn_up_left = IconData(0xf51d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_turn_up_right</i> &#x2014; Cupertino icon named "arrow_turn_up_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_turn_up_right = IconData(0xf51e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up</i> &#x2014; Cupertino icon named "arrow_up". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [up_arrow] which is available in cupertino_icons 0.1.3.
+  static const IconData arrow_up = IconData(0xf366, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_arrow_down</i> &#x2014; Cupertino icon named "arrow_up_arrow_down". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_arrow_down = IconData(0xf51f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_arrow_down_circle</i> &#x2014; Cupertino icon named "arrow_up_arrow_down_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_arrow_down_circle = IconData(0xf520, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_arrow_down_circle_fill</i> &#x2014; Cupertino icon named "arrow_up_arrow_down_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_arrow_down_circle_fill = IconData(0xf521, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_arrow_down_square</i> &#x2014; Cupertino icon named "arrow_up_arrow_down_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_arrow_down_square = IconData(0xf522, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_arrow_down_square_fill</i> &#x2014; Cupertino icon named "arrow_up_arrow_down_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_arrow_down_square_fill = IconData(0xf523, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_bin</i> &#x2014; Cupertino icon named "arrow_up_bin". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_bin = IconData(0xf524, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_bin_fill</i> &#x2014; Cupertino icon named "arrow_up_bin_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_bin_fill = IconData(0xf525, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_circle</i> &#x2014; Cupertino icon named "arrow_up_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_circle = IconData(0xf526, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_circle_fill</i> &#x2014; Cupertino icon named "arrow_up_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_circle_fill = IconData(0xf527, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_doc</i> &#x2014; Cupertino icon named "arrow_up_doc". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_doc = IconData(0xf528, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_doc_fill</i> &#x2014; Cupertino icon named "arrow_up_doc_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_doc_fill = IconData(0xf529, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_down</i> &#x2014; Cupertino icon named "arrow_up_down". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_down = IconData(0xf52a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_down_circle</i> &#x2014; Cupertino icon named "arrow_up_down_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_down_circle = IconData(0xf52b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_down_circle_fill</i> &#x2014; Cupertino icon named "arrow_up_down_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_down_circle_fill = IconData(0xf52c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_down_square</i> &#x2014; Cupertino icon named "arrow_up_down_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_down_square = IconData(0xf52d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_down_square_fill</i> &#x2014; Cupertino icon named "arrow_up_down_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_down_square_fill = IconData(0xf52e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_left</i> &#x2014; Cupertino icon named "arrow_up_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_left = IconData(0xf52f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_left_arrow_down_right</i> &#x2014; Cupertino icon named "arrow_up_left_arrow_down_right". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [fullscreen] which is available in cupertino_icons 0.1.3.
+  static const IconData arrow_up_left_arrow_down_right = IconData(0xf386, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_left_circle</i> &#x2014; Cupertino icon named "arrow_up_left_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_left_circle = IconData(0xf530, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_left_circle_fill</i> &#x2014; Cupertino icon named "arrow_up_left_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_left_circle_fill = IconData(0xf531, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_left_square</i> &#x2014; Cupertino icon named "arrow_up_left_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_left_square = IconData(0xf532, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_left_square_fill</i> &#x2014; Cupertino icon named "arrow_up_left_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_left_square_fill = IconData(0xf533, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_right</i> &#x2014; Cupertino icon named "arrow_up_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_right = IconData(0xf534, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_right_circle</i> &#x2014; Cupertino icon named "arrow_up_right_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_right_circle = IconData(0xf535, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_right_circle_fill</i> &#x2014; Cupertino icon named "arrow_up_right_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_right_circle_fill = IconData(0xf536, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_right_diamond</i> &#x2014; Cupertino icon named "arrow_up_right_diamond". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_right_diamond = IconData(0xf537, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_right_diamond_fill</i> &#x2014; Cupertino icon named "arrow_up_right_diamond_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_right_diamond_fill = IconData(0xf538, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_right_square</i> &#x2014; Cupertino icon named "arrow_up_right_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_right_square = IconData(0xf539, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_right_square_fill</i> &#x2014; Cupertino icon named "arrow_up_right_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_right_square_fill = IconData(0xf53a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_square</i> &#x2014; Cupertino icon named "arrow_up_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_square = IconData(0xf53b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_square_fill</i> &#x2014; Cupertino icon named "arrow_up_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_square_fill = IconData(0xf53c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_to_line</i> &#x2014; Cupertino icon named "arrow_up_to_line". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_to_line = IconData(0xf53d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_up_to_line_alt</i> &#x2014; Cupertino icon named "arrow_up_to_line_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_up_to_line_alt = IconData(0xf53e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_down</i> &#x2014; Cupertino icon named "arrow_uturn_down". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_down = IconData(0xf53f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_down_circle</i> &#x2014; Cupertino icon named "arrow_uturn_down_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_down_circle = IconData(0xf540, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_down_circle_fill</i> &#x2014; Cupertino icon named "arrow_uturn_down_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_down_circle_fill = IconData(0xf541, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_down_square</i> &#x2014; Cupertino icon named "arrow_uturn_down_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_down_square = IconData(0xf542, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_down_square_fill</i> &#x2014; Cupertino icon named "arrow_uturn_down_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_down_square_fill = IconData(0xf543, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_left</i> &#x2014; Cupertino icon named "arrow_uturn_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_left = IconData(0xf544, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_left_circle</i> &#x2014; Cupertino icon named "arrow_uturn_left_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_left_circle = IconData(0xf545, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_left_circle_fill</i> &#x2014; Cupertino icon named "arrow_uturn_left_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_left_circle_fill = IconData(0xf546, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_left_square</i> &#x2014; Cupertino icon named "arrow_uturn_left_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_left_square = IconData(0xf547, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_left_square_fill</i> &#x2014; Cupertino icon named "arrow_uturn_left_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_left_square_fill = IconData(0xf548, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_right</i> &#x2014; Cupertino icon named "arrow_uturn_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_right = IconData(0xf549, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_right_circle</i> &#x2014; Cupertino icon named "arrow_uturn_right_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_right_circle = IconData(0xf54a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_right_circle_fill</i> &#x2014; Cupertino icon named "arrow_uturn_right_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_right_circle_fill = IconData(0xf54b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_right_square</i> &#x2014; Cupertino icon named "arrow_uturn_right_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_right_square = IconData(0xf54c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_right_square_fill</i> &#x2014; Cupertino icon named "arrow_uturn_right_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_right_square_fill = IconData(0xf54d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_up</i> &#x2014; Cupertino icon named "arrow_uturn_up". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_up = IconData(0xf54e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_up_circle</i> &#x2014; Cupertino icon named "arrow_uturn_up_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_up_circle = IconData(0xf54f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_up_circle_fill</i> &#x2014; Cupertino icon named "arrow_uturn_up_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_up_circle_fill = IconData(0xf550, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_up_square</i> &#x2014; Cupertino icon named "arrow_uturn_up_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_up_square = IconData(0xf551, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrow_uturn_up_square_fill</i> &#x2014; Cupertino icon named "arrow_uturn_up_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrow_uturn_up_square_fill = IconData(0xf552, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowshape_turn_up_left</i> &#x2014; Cupertino icon named "arrowshape_turn_up_left". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [reply] which is available in cupertino_icons 0.1.3.
+  static const IconData arrowshape_turn_up_left = IconData(0xf4c6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowshape_turn_up_left_2</i> &#x2014; Cupertino icon named "arrowshape_turn_up_left_2". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [reply_all] which is available in cupertino_icons 0.1.3.
+  static const IconData arrowshape_turn_up_left_2 = IconData(0xf21d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowshape_turn_up_left_2_fill</i> &#x2014; Cupertino icon named "arrowshape_turn_up_left_2_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [reply_thick_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData arrowshape_turn_up_left_2_fill = IconData(0xf21e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowshape_turn_up_left_circle</i> &#x2014; Cupertino icon named "arrowshape_turn_up_left_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowshape_turn_up_left_circle = IconData(0xf553, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowshape_turn_up_left_circle_fill</i> &#x2014; Cupertino icon named "arrowshape_turn_up_left_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowshape_turn_up_left_circle_fill = IconData(0xf554, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowshape_turn_up_left_fill</i> &#x2014; Cupertino icon named "arrowshape_turn_up_left_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowshape_turn_up_left_fill = IconData(0xf555, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowshape_turn_up_right</i> &#x2014; Cupertino icon named "arrowshape_turn_up_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowshape_turn_up_right = IconData(0xf556, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowshape_turn_up_right_circle</i> &#x2014; Cupertino icon named "arrowshape_turn_up_right_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowshape_turn_up_right_circle = IconData(0xf557, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowshape_turn_up_right_circle_fill</i> &#x2014; Cupertino icon named "arrowshape_turn_up_right_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowshape_turn_up_right_circle_fill = IconData(0xf558, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowshape_turn_up_right_fill</i> &#x2014; Cupertino icon named "arrowshape_turn_up_right_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowshape_turn_up_right_fill = IconData(0xf559, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_down</i> &#x2014; Cupertino icon named "arrowtriangle_down". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_down = IconData(0xf55a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_down_circle</i> &#x2014; Cupertino icon named "arrowtriangle_down_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_down_circle = IconData(0xf55b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_down_circle_fill</i> &#x2014; Cupertino icon named "arrowtriangle_down_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_down_circle_fill = IconData(0xf55c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_down_fill</i> &#x2014; Cupertino icon named "arrowtriangle_down_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_down_fill = IconData(0xf55d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_down_square</i> &#x2014; Cupertino icon named "arrowtriangle_down_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_down_square = IconData(0xf55e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_down_square_fill</i> &#x2014; Cupertino icon named "arrowtriangle_down_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_down_square_fill = IconData(0xf55f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_left</i> &#x2014; Cupertino icon named "arrowtriangle_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_left = IconData(0xf560, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_left_circle</i> &#x2014; Cupertino icon named "arrowtriangle_left_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_left_circle = IconData(0xf561, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_left_circle_fill</i> &#x2014; Cupertino icon named "arrowtriangle_left_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_left_circle_fill = IconData(0xf562, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_left_fill</i> &#x2014; Cupertino icon named "arrowtriangle_left_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_left_fill = IconData(0xf563, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_left_square</i> &#x2014; Cupertino icon named "arrowtriangle_left_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_left_square = IconData(0xf564, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_left_square_fill</i> &#x2014; Cupertino icon named "arrowtriangle_left_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_left_square_fill = IconData(0xf565, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_right</i> &#x2014; Cupertino icon named "arrowtriangle_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_right = IconData(0xf566, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_right_circle</i> &#x2014; Cupertino icon named "arrowtriangle_right_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_right_circle = IconData(0xf567, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_right_circle_fill</i> &#x2014; Cupertino icon named "arrowtriangle_right_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_right_circle_fill = IconData(0xf568, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_right_fill</i> &#x2014; Cupertino icon named "arrowtriangle_right_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_right_fill = IconData(0xf569, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_right_square</i> &#x2014; Cupertino icon named "arrowtriangle_right_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_right_square = IconData(0xf56a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_right_square_fill</i> &#x2014; Cupertino icon named "arrowtriangle_right_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_right_square_fill = IconData(0xf56b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_up</i> &#x2014; Cupertino icon named "arrowtriangle_up". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_up = IconData(0xf56c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_up_circle</i> &#x2014; Cupertino icon named "arrowtriangle_up_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_up_circle = IconData(0xf56d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_up_circle_fill</i> &#x2014; Cupertino icon named "arrowtriangle_up_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_up_circle_fill = IconData(0xf56e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_up_fill</i> &#x2014; Cupertino icon named "arrowtriangle_up_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_up_fill = IconData(0xf56f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_up_square</i> &#x2014; Cupertino icon named "arrowtriangle_up_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_up_square = IconData(0xf570, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>arrowtriangle_up_square_fill</i> &#x2014; Cupertino icon named "arrowtriangle_up_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData arrowtriangle_up_square_fill = IconData(0xf571, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>asterisk_circle</i> &#x2014; Cupertino icon named "asterisk_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData asterisk_circle = IconData(0xf572, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>asterisk_circle_fill</i> &#x2014; Cupertino icon named "asterisk_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData asterisk_circle_fill = IconData(0xf573, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>at</i> &#x2014; Cupertino icon named "at". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData at = IconData(0xf574, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>at_badge_minus</i> &#x2014; Cupertino icon named "at_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData at_badge_minus = IconData(0xf575, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>at_badge_plus</i> &#x2014; Cupertino icon named "at_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData at_badge_plus = IconData(0xf576, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>at_circle</i> &#x2014; Cupertino icon named "at_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData at_circle = IconData(0xf8af, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>at_circle_fill</i> &#x2014; Cupertino icon named "at_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData at_circle_fill = IconData(0xf8b0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>backward</i> &#x2014; Cupertino icon named "backward". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData backward = IconData(0xf577, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>backward_end</i> &#x2014; Cupertino icon named "backward_end". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData backward_end = IconData(0xf578, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>backward_end_alt</i> &#x2014; Cupertino icon named "backward_end_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData backward_end_alt = IconData(0xf579, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>backward_end_alt_fill</i> &#x2014; Cupertino icon named "backward_end_alt_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData backward_end_alt_fill = IconData(0xf57a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>backward_end_fill</i> &#x2014; Cupertino icon named "backward_end_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData backward_end_fill = IconData(0xf57b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>backward_fill</i> &#x2014; Cupertino icon named "backward_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData backward_fill = IconData(0xf57c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>badge_plus_radiowaves_right</i> &#x2014; Cupertino icon named "badge_plus_radiowaves_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData badge_plus_radiowaves_right = IconData(0xf57d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bag</i> &#x2014; Cupertino icon named "bag". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bag = IconData(0xf57e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bag_badge_minus</i> &#x2014; Cupertino icon named "bag_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bag_badge_minus = IconData(0xf57f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bag_badge_plus</i> &#x2014; Cupertino icon named "bag_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bag_badge_plus = IconData(0xf580, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bag_fill</i> &#x2014; Cupertino icon named "bag_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bag_fill = IconData(0xf581, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bag_fill_badge_minus</i> &#x2014; Cupertino icon named "bag_fill_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bag_fill_badge_minus = IconData(0xf582, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bag_fill_badge_plus</i> &#x2014; Cupertino icon named "bag_fill_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bag_fill_badge_plus = IconData(0xf583, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bandage</i> &#x2014; Cupertino icon named "bandage". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bandage = IconData(0xf584, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bandage_fill</i> &#x2014; Cupertino icon named "bandage_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bandage_fill = IconData(0xf585, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>barcode</i> &#x2014; Cupertino icon named "barcode". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData barcode = IconData(0xf586, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>barcode_viewfinder</i> &#x2014; Cupertino icon named "barcode_viewfinder". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData barcode_viewfinder = IconData(0xf587, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bars</i> &#x2014; Cupertino icon named "bars". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bars = IconData(0xf8b1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>battery_0</i> &#x2014; Cupertino icon named "battery_0". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [battery_empty] which is available in cupertino_icons 0.1.3.
+  static const IconData battery_0 = IconData(0xf112, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>battery_100</i> &#x2014; Cupertino icon named "battery_100". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [battery_charging] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [battery_full] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [battery_75_percent] which is available in cupertino_icons 0.1.3.
+  static const IconData battery_100 = IconData(0xf113, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>battery_25</i> &#x2014; Cupertino icon named "battery_25". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [battery_25_percent] which is available in cupertino_icons 0.1.3.
+  static const IconData battery_25 = IconData(0xf115, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bed_double</i> &#x2014; Cupertino icon named "bed_double". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bed_double = IconData(0xf588, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bed_double_fill</i> &#x2014; Cupertino icon named "bed_double_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bed_double_fill = IconData(0xf589, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bell_circle</i> &#x2014; Cupertino icon named "bell_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bell_circle = IconData(0xf58a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bell_circle_fill</i> &#x2014; Cupertino icon named "bell_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bell_circle_fill = IconData(0xf58b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bell_fill</i> &#x2014; Cupertino icon named "bell_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [bell_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData bell_fill = IconData(0xf3e2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bell_slash</i> &#x2014; Cupertino icon named "bell_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bell_slash = IconData(0xf58c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bell_slash_fill</i> &#x2014; Cupertino icon named "bell_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bell_slash_fill = IconData(0xf58d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bin_xmark</i> &#x2014; Cupertino icon named "bin_xmark". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bin_xmark = IconData(0xf58e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bin_xmark_fill</i> &#x2014; Cupertino icon named "bin_xmark_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bin_xmark_fill = IconData(0xf58f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bitcoin</i> &#x2014; Cupertino icon named "bitcoin". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bitcoin = IconData(0xf8b2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bitcoin_circle</i> &#x2014; Cupertino icon named "bitcoin_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bitcoin_circle = IconData(0xf8b3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bitcoin_circle_fill</i> &#x2014; Cupertino icon named "bitcoin_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bitcoin_circle_fill = IconData(0xf8b4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bold</i> &#x2014; Cupertino icon named "bold". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bold = IconData(0xf590, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bold_italic_underline</i> &#x2014; Cupertino icon named "bold_italic_underline". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bold_italic_underline = IconData(0xf591, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bold_underline</i> &#x2014; Cupertino icon named "bold_underline". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bold_underline = IconData(0xf592, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bolt</i> &#x2014; Cupertino icon named "bolt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bolt = IconData(0xf593, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bolt_badge_a</i> &#x2014; Cupertino icon named "bolt_badge_a". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bolt_badge_a = IconData(0xf594, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bolt_badge_a_fill</i> &#x2014; Cupertino icon named "bolt_badge_a_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bolt_badge_a_fill = IconData(0xf595, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bolt_circle</i> &#x2014; Cupertino icon named "bolt_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bolt_circle = IconData(0xf596, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bolt_circle_fill</i> &#x2014; Cupertino icon named "bolt_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bolt_circle_fill = IconData(0xf597, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bolt_fill</i> &#x2014; Cupertino icon named "bolt_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bolt_fill = IconData(0xf598, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bolt_horizontal</i> &#x2014; Cupertino icon named "bolt_horizontal". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bolt_horizontal = IconData(0xf599, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bolt_horizontal_circle</i> &#x2014; Cupertino icon named "bolt_horizontal_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bolt_horizontal_circle = IconData(0xf59a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bolt_horizontal_circle_fill</i> &#x2014; Cupertino icon named "bolt_horizontal_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bolt_horizontal_circle_fill = IconData(0xf59b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bolt_horizontal_fill</i> &#x2014; Cupertino icon named "bolt_horizontal_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bolt_horizontal_fill = IconData(0xf59c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bolt_slash</i> &#x2014; Cupertino icon named "bolt_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bolt_slash = IconData(0xf59d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bolt_slash_fill</i> &#x2014; Cupertino icon named "bolt_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bolt_slash_fill = IconData(0xf59e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>book_circle</i> &#x2014; Cupertino icon named "book_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData book_circle = IconData(0xf59f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>book_circle_fill</i> &#x2014; Cupertino icon named "book_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData book_circle_fill = IconData(0xf5a0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>book_fill</i> &#x2014; Cupertino icon named "book_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [book_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData book_fill = IconData(0xf3e8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bookmark_fill</i> &#x2014; Cupertino icon named "bookmark_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [bookmark_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData bookmark_fill = IconData(0xf3ea, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>briefcase</i> &#x2014; Cupertino icon named "briefcase". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData briefcase = IconData(0xf5a1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>briefcase_fill</i> &#x2014; Cupertino icon named "briefcase_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData briefcase_fill = IconData(0xf5a2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bubble_left</i> &#x2014; Cupertino icon named "bubble_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bubble_left = IconData(0xf5a3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bubble_left_bubble_right</i> &#x2014; Cupertino icon named "bubble_left_bubble_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bubble_left_bubble_right = IconData(0xf5a4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bubble_left_bubble_right_fill</i> &#x2014; Cupertino icon named "bubble_left_bubble_right_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bubble_left_bubble_right_fill = IconData(0xf5a5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bubble_left_fill</i> &#x2014; Cupertino icon named "bubble_left_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bubble_left_fill = IconData(0xf5a6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bubble_middle_bottom</i> &#x2014; Cupertino icon named "bubble_middle_bottom". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bubble_middle_bottom = IconData(0xf5a7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bubble_middle_bottom_fill</i> &#x2014; Cupertino icon named "bubble_middle_bottom_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bubble_middle_bottom_fill = IconData(0xf5a8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bubble_middle_top</i> &#x2014; Cupertino icon named "bubble_middle_top". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bubble_middle_top = IconData(0xf5a9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bubble_middle_top_fill</i> &#x2014; Cupertino icon named "bubble_middle_top_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bubble_middle_top_fill = IconData(0xf5aa, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bubble_right</i> &#x2014; Cupertino icon named "bubble_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bubble_right = IconData(0xf5ab, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>bubble_right_fill</i> &#x2014; Cupertino icon named "bubble_right_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData bubble_right_fill = IconData(0xf5ac, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>building_2_fill</i> &#x2014; Cupertino icon named "building_2_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData building_2_fill = IconData(0xf8b5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>burn</i> &#x2014; Cupertino icon named "burn". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData burn = IconData(0xf5ad, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>burst</i> &#x2014; Cupertino icon named "burst". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData burst = IconData(0xf5ae, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>burst_fill</i> &#x2014; Cupertino icon named "burst_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData burst_fill = IconData(0xf5af, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>calendar</i> &#x2014; Cupertino icon named "calendar". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData calendar = IconData(0xf5b0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>calendar_badge_minus</i> &#x2014; Cupertino icon named "calendar_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData calendar_badge_minus = IconData(0xf5b1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>calendar_badge_plus</i> &#x2014; Cupertino icon named "calendar_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData calendar_badge_plus = IconData(0xf5b2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>calendar_circle</i> &#x2014; Cupertino icon named "calendar_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData calendar_circle = IconData(0xf5b3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>calendar_circle_fill</i> &#x2014; Cupertino icon named "calendar_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData calendar_circle_fill = IconData(0xf5b4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>calendar_today</i> &#x2014; Cupertino icon named "calendar_today". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData calendar_today = IconData(0xf8b6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>camera</i> &#x2014; Cupertino icon named "camera". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [photo_camera] which is available in cupertino_icons 0.1.3.
+  static const IconData camera = IconData(0xf3f5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>camera_circle</i> &#x2014; Cupertino icon named "camera_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData camera_circle = IconData(0xf5b5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>camera_circle_fill</i> &#x2014; Cupertino icon named "camera_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData camera_circle_fill = IconData(0xf5b6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>camera_fill</i> &#x2014; Cupertino icon named "camera_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [photo_camera_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData camera_fill = IconData(0xf3f6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>camera_on_rectangle</i> &#x2014; Cupertino icon named "camera_on_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData camera_on_rectangle = IconData(0xf5b7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>camera_on_rectangle_fill</i> &#x2014; Cupertino icon named "camera_on_rectangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData camera_on_rectangle_fill = IconData(0xf5b8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>camera_rotate</i> &#x2014; Cupertino icon named "camera_rotate". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [switch_camera] which is available in cupertino_icons 0.1.3.
+  static const IconData camera_rotate = IconData(0xf49e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>camera_rotate_fill</i> &#x2014; Cupertino icon named "camera_rotate_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [switch_camera_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData camera_rotate_fill = IconData(0xf49f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>camera_viewfinder</i> &#x2014; Cupertino icon named "camera_viewfinder". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData camera_viewfinder = IconData(0xf5b9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>capslock</i> &#x2014; Cupertino icon named "capslock". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData capslock = IconData(0xf5ba, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>capslock_fill</i> &#x2014; Cupertino icon named "capslock_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData capslock_fill = IconData(0xf5bb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>capsule</i> &#x2014; Cupertino icon named "capsule". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData capsule = IconData(0xf5bc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>capsule_fill</i> &#x2014; Cupertino icon named "capsule_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData capsule_fill = IconData(0xf5bd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>captions_bubble</i> &#x2014; Cupertino icon named "captions_bubble". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData captions_bubble = IconData(0xf5be, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>captions_bubble_fill</i> &#x2014; Cupertino icon named "captions_bubble_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData captions_bubble_fill = IconData(0xf5bf, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>car_fill</i> &#x2014; Cupertino icon named "car_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [car] which is available in cupertino_icons 0.1.3.
+  static const IconData car_fill = IconData(0xf36f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cart</i> &#x2014; Cupertino icon named "cart". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [shopping_cart] which is available in cupertino_icons 0.1.3.
+  static const IconData cart = IconData(0xf3f7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cart_badge_minus</i> &#x2014; Cupertino icon named "cart_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cart_badge_minus = IconData(0xf5c0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cart_badge_plus</i> &#x2014; Cupertino icon named "cart_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cart_badge_plus = IconData(0xf5c1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cart_fill</i> &#x2014; Cupertino icon named "cart_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cart_fill = IconData(0xf5c2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cart_fill_badge_minus</i> &#x2014; Cupertino icon named "cart_fill_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cart_fill_badge_minus = IconData(0xf5c3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cart_fill_badge_plus</i> &#x2014; Cupertino icon named "cart_fill_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cart_fill_badge_plus = IconData(0xf5c4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chart_bar</i> &#x2014; Cupertino icon named "chart_bar". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chart_bar = IconData(0xf5c5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chart_bar_alt_fill</i> &#x2014; Cupertino icon named "chart_bar_alt_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chart_bar_alt_fill = IconData(0xf8b7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chart_bar_circle</i> &#x2014; Cupertino icon named "chart_bar_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chart_bar_circle = IconData(0xf8b8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chart_bar_circle_fill</i> &#x2014; Cupertino icon named "chart_bar_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chart_bar_circle_fill = IconData(0xf8b9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chart_bar_fill</i> &#x2014; Cupertino icon named "chart_bar_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chart_bar_fill = IconData(0xf5c6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chart_bar_square</i> &#x2014; Cupertino icon named "chart_bar_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chart_bar_square = IconData(0xf8ba, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chart_bar_square_fill</i> &#x2014; Cupertino icon named "chart_bar_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chart_bar_square_fill = IconData(0xf8bb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chart_pie</i> &#x2014; Cupertino icon named "chart_pie". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chart_pie = IconData(0xf5c7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chart_pie_fill</i> &#x2014; Cupertino icon named "chart_pie_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chart_pie_fill = IconData(0xf5c8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chat_bubble</i> &#x2014; Cupertino icon named "chat_bubble". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [conversation_bubble] which is available in cupertino_icons 0.1.3.
+  static const IconData chat_bubble = IconData(0xf3fb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chat_bubble_2</i> &#x2014; Cupertino icon named "chat_bubble_2". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chat_bubble_2 = IconData(0xf8bc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chat_bubble_2_fill</i> &#x2014; Cupertino icon named "chat_bubble_2_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chat_bubble_2_fill = IconData(0xf8bd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chat_bubble_fill</i> &#x2014; Cupertino icon named "chat_bubble_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chat_bubble_fill = IconData(0xf8be, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chat_bubble_text</i> &#x2014; Cupertino icon named "chat_bubble_text". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chat_bubble_text = IconData(0xf8bf, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chat_bubble_text_fill</i> &#x2014; Cupertino icon named "chat_bubble_text_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chat_bubble_text_fill = IconData(0xf8c0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>checkmark</i> &#x2014; Cupertino icon named "checkmark". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [check_mark] which is available in cupertino_icons 0.1.3.
+  static const IconData checkmark = IconData(0xf3fd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>checkmark_alt</i> &#x2014; Cupertino icon named "checkmark_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData checkmark_alt = IconData(0xf8c1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>checkmark_alt_circle</i> &#x2014; Cupertino icon named "checkmark_alt_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData checkmark_alt_circle = IconData(0xf8c2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>checkmark_alt_circle_fill</i> &#x2014; Cupertino icon named "checkmark_alt_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData checkmark_alt_circle_fill = IconData(0xf8c3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>checkmark_circle</i> &#x2014; Cupertino icon named "checkmark_circle". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [check_mark_circled] which is available in cupertino_icons 0.1.3.
+  static const IconData checkmark_circle = IconData(0xf3fe, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>checkmark_circle_fill</i> &#x2014; Cupertino icon named "checkmark_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [check_mark_circled_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData checkmark_circle_fill = IconData(0xf3ff, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>checkmark_rectangle</i> &#x2014; Cupertino icon named "checkmark_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData checkmark_rectangle = IconData(0xf5c9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>checkmark_rectangle_fill</i> &#x2014; Cupertino icon named "checkmark_rectangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData checkmark_rectangle_fill = IconData(0xf5ca, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>checkmark_seal</i> &#x2014; Cupertino icon named "checkmark_seal". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData checkmark_seal = IconData(0xf5cb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>checkmark_seal_fill</i> &#x2014; Cupertino icon named "checkmark_seal_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData checkmark_seal_fill = IconData(0xf5cc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>checkmark_shield</i> &#x2014; Cupertino icon named "checkmark_shield". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData checkmark_shield = IconData(0xf5cd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>checkmark_shield_fill</i> &#x2014; Cupertino icon named "checkmark_shield_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData checkmark_shield_fill = IconData(0xf5ce, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>checkmark_square</i> &#x2014; Cupertino icon named "checkmark_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData checkmark_square = IconData(0xf5cf, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>checkmark_square_fill</i> &#x2014; Cupertino icon named "checkmark_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData checkmark_square_fill = IconData(0xf5d0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_back</i> &#x2014; Cupertino icon named "chevron_back". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [back] which is available in cupertino_icons 0.1.3.
+  static const IconData chevron_back = IconData(0xf3cf, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_compact_down</i> &#x2014; Cupertino icon named "chevron_compact_down". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_compact_down = IconData(0xf5d1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_compact_left</i> &#x2014; Cupertino icon named "chevron_compact_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_compact_left = IconData(0xf5d2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_compact_right</i> &#x2014; Cupertino icon named "chevron_compact_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_compact_right = IconData(0xf5d3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_compact_up</i> &#x2014; Cupertino icon named "chevron_compact_up". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_compact_up = IconData(0xf5d4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_down</i> &#x2014; Cupertino icon named "chevron_down". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_down = IconData(0xf5d5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_down_circle</i> &#x2014; Cupertino icon named "chevron_down_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_down_circle = IconData(0xf5d6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_down_circle_fill</i> &#x2014; Cupertino icon named "chevron_down_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_down_circle_fill = IconData(0xf5d7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_down_square</i> &#x2014; Cupertino icon named "chevron_down_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_down_square = IconData(0xf5d8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_down_square_fill</i> &#x2014; Cupertino icon named "chevron_down_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_down_square_fill = IconData(0xf5d9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_forward</i> &#x2014; Cupertino icon named "chevron_forward". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [forward] which is available in cupertino_icons 0.1.3.
+  static const IconData chevron_forward = IconData(0xf3d1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_left</i> &#x2014; Cupertino icon named "chevron_left". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [left_chevron] which is available in cupertino_icons 0.1.3.
+  static const IconData chevron_left = IconData(0xf3d2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_left_2</i> &#x2014; Cupertino icon named "chevron_left_2". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_left_2 = IconData(0xf5da, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_left_circle</i> &#x2014; Cupertino icon named "chevron_left_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_left_circle = IconData(0xf5db, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_left_circle_fill</i> &#x2014; Cupertino icon named "chevron_left_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_left_circle_fill = IconData(0xf5dc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_left_slash_chevron_right</i> &#x2014; Cupertino icon named "chevron_left_slash_chevron_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_left_slash_chevron_right = IconData(0xf5dd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_left_square</i> &#x2014; Cupertino icon named "chevron_left_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_left_square = IconData(0xf5de, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_left_square_fill</i> &#x2014; Cupertino icon named "chevron_left_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_left_square_fill = IconData(0xf5df, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_right</i> &#x2014; Cupertino icon named "chevron_right". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [right_chevron] which is available in cupertino_icons 0.1.3.
+  static const IconData chevron_right = IconData(0xf3d3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_right_2</i> &#x2014; Cupertino icon named "chevron_right_2". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_right_2 = IconData(0xf5e0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_right_circle</i> &#x2014; Cupertino icon named "chevron_right_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_right_circle = IconData(0xf5e1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_right_circle_fill</i> &#x2014; Cupertino icon named "chevron_right_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_right_circle_fill = IconData(0xf5e2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_right_square</i> &#x2014; Cupertino icon named "chevron_right_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_right_square = IconData(0xf5e3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_right_square_fill</i> &#x2014; Cupertino icon named "chevron_right_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_right_square_fill = IconData(0xf5e4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_up</i> &#x2014; Cupertino icon named "chevron_up". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_up = IconData(0xf5e5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_up_chevron_down</i> &#x2014; Cupertino icon named "chevron_up_chevron_down". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_up_chevron_down = IconData(0xf5e6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_up_circle</i> &#x2014; Cupertino icon named "chevron_up_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_up_circle = IconData(0xf5e7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_up_circle_fill</i> &#x2014; Cupertino icon named "chevron_up_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_up_circle_fill = IconData(0xf5e8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_up_square</i> &#x2014; Cupertino icon named "chevron_up_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_up_square = IconData(0xf5e9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>chevron_up_square_fill</i> &#x2014; Cupertino icon named "chevron_up_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData chevron_up_square_fill = IconData(0xf5ea, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>circle_bottomthird_split</i> &#x2014; Cupertino icon named "circle_bottomthird_split". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData circle_bottomthird_split = IconData(0xf5eb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>circle_fill</i> &#x2014; Cupertino icon named "circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [circle_filled] which is available in cupertino_icons 0.1.3.
+  static const IconData circle_fill = IconData(0xf400, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>circle_grid_3x3</i> &#x2014; Cupertino icon named "circle_grid_3x3". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData circle_grid_3x3 = IconData(0xf5ec, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>circle_grid_3x3_fill</i> &#x2014; Cupertino icon named "circle_grid_3x3_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData circle_grid_3x3_fill = IconData(0xf5ed, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>circle_grid_hex</i> &#x2014; Cupertino icon named "circle_grid_hex". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData circle_grid_hex = IconData(0xf5ee, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>circle_grid_hex_fill</i> &#x2014; Cupertino icon named "circle_grid_hex_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData circle_grid_hex_fill = IconData(0xf5ef, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>circle_lefthalf_fill</i> &#x2014; Cupertino icon named "circle_lefthalf_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData circle_lefthalf_fill = IconData(0xf5f0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>circle_righthalf_fill</i> &#x2014; Cupertino icon named "circle_righthalf_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData circle_righthalf_fill = IconData(0xf5f1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>clear_fill</i> &#x2014; Cupertino icon named "clear_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData clear_fill = IconData(0xf5f3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>clock_fill</i> &#x2014; Cupertino icon named "clock_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [clock_solid] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [time_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData clock_fill = IconData(0xf403, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud</i> &#x2014; Cupertino icon named "cloud". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud = IconData(0xf5f4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_bolt</i> &#x2014; Cupertino icon named "cloud_bolt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_bolt = IconData(0xf5f5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_bolt_fill</i> &#x2014; Cupertino icon named "cloud_bolt_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_bolt_fill = IconData(0xf5f6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_bolt_rain</i> &#x2014; Cupertino icon named "cloud_bolt_rain". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_bolt_rain = IconData(0xf5f7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_bolt_rain_fill</i> &#x2014; Cupertino icon named "cloud_bolt_rain_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_bolt_rain_fill = IconData(0xf5f8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_download</i> &#x2014; Cupertino icon named "cloud_download". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_download = IconData(0xf8c4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_download_fill</i> &#x2014; Cupertino icon named "cloud_download_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_download_fill = IconData(0xf8c5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_drizzle</i> &#x2014; Cupertino icon named "cloud_drizzle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_drizzle = IconData(0xf5f9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_drizzle_fill</i> &#x2014; Cupertino icon named "cloud_drizzle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_drizzle_fill = IconData(0xf5fa, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_fill</i> &#x2014; Cupertino icon named "cloud_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_fill = IconData(0xf5fb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_fog</i> &#x2014; Cupertino icon named "cloud_fog". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_fog = IconData(0xf5fc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_fog_fill</i> &#x2014; Cupertino icon named "cloud_fog_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_fog_fill = IconData(0xf5fd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_hail</i> &#x2014; Cupertino icon named "cloud_hail". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_hail = IconData(0xf5fe, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_hail_fill</i> &#x2014; Cupertino icon named "cloud_hail_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_hail_fill = IconData(0xf5ff, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_heavyrain</i> &#x2014; Cupertino icon named "cloud_heavyrain". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_heavyrain = IconData(0xf600, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_heavyrain_fill</i> &#x2014; Cupertino icon named "cloud_heavyrain_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_heavyrain_fill = IconData(0xf601, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_moon</i> &#x2014; Cupertino icon named "cloud_moon". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_moon = IconData(0xf602, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_moon_bolt</i> &#x2014; Cupertino icon named "cloud_moon_bolt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_moon_bolt = IconData(0xf603, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_moon_bolt_fill</i> &#x2014; Cupertino icon named "cloud_moon_bolt_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_moon_bolt_fill = IconData(0xf604, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_moon_fill</i> &#x2014; Cupertino icon named "cloud_moon_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_moon_fill = IconData(0xf605, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_moon_rain</i> &#x2014; Cupertino icon named "cloud_moon_rain". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_moon_rain = IconData(0xf606, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_moon_rain_fill</i> &#x2014; Cupertino icon named "cloud_moon_rain_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_moon_rain_fill = IconData(0xf607, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_rain</i> &#x2014; Cupertino icon named "cloud_rain". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_rain = IconData(0xf608, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_rain_fill</i> &#x2014; Cupertino icon named "cloud_rain_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_rain_fill = IconData(0xf609, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_sleet</i> &#x2014; Cupertino icon named "cloud_sleet". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_sleet = IconData(0xf60a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_sleet_fill</i> &#x2014; Cupertino icon named "cloud_sleet_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_sleet_fill = IconData(0xf60b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_snow</i> &#x2014; Cupertino icon named "cloud_snow". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_snow = IconData(0xf60c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_snow_fill</i> &#x2014; Cupertino icon named "cloud_snow_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_snow_fill = IconData(0xf60d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_sun</i> &#x2014; Cupertino icon named "cloud_sun". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_sun = IconData(0xf60e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_sun_bolt</i> &#x2014; Cupertino icon named "cloud_sun_bolt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_sun_bolt = IconData(0xf60f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_sun_bolt_fill</i> &#x2014; Cupertino icon named "cloud_sun_bolt_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_sun_bolt_fill = IconData(0xf610, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_sun_fill</i> &#x2014; Cupertino icon named "cloud_sun_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_sun_fill = IconData(0xf611, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_sun_rain</i> &#x2014; Cupertino icon named "cloud_sun_rain". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_sun_rain = IconData(0xf612, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_sun_rain_fill</i> &#x2014; Cupertino icon named "cloud_sun_rain_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_sun_rain_fill = IconData(0xf613, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_upload</i> &#x2014; Cupertino icon named "cloud_upload". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_upload = IconData(0xf8c6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cloud_upload_fill</i> &#x2014; Cupertino icon named "cloud_upload_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cloud_upload_fill = IconData(0xf8c7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>color_filter</i> &#x2014; Cupertino icon named "color_filter". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData color_filter = IconData(0xf8c8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>color_filter_fill</i> &#x2014; Cupertino icon named "color_filter_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData color_filter_fill = IconData(0xf8c9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>command</i> &#x2014; Cupertino icon named "command". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData command = IconData(0xf614, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>compass</i> &#x2014; Cupertino icon named "compass". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData compass = IconData(0xf8ca, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>compass_fill</i> &#x2014; Cupertino icon named "compass_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData compass_fill = IconData(0xf8cb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>control</i> &#x2014; Cupertino icon named "control". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData control = IconData(0xf615, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>creditcard</i> &#x2014; Cupertino icon named "creditcard". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData creditcard = IconData(0xf616, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>creditcard_fill</i> &#x2014; Cupertino icon named "creditcard_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData creditcard_fill = IconData(0xf617, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>crop</i> &#x2014; Cupertino icon named "crop". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData crop = IconData(0xf618, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>crop_rotate</i> &#x2014; Cupertino icon named "crop_rotate". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData crop_rotate = IconData(0xf619, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cube</i> &#x2014; Cupertino icon named "cube". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cube = IconData(0xf61a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cube_box</i> &#x2014; Cupertino icon named "cube_box". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cube_box = IconData(0xf61b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cube_box_fill</i> &#x2014; Cupertino icon named "cube_box_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cube_box_fill = IconData(0xf61c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cube_fill</i> &#x2014; Cupertino icon named "cube_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cube_fill = IconData(0xf61d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>cursor_rays</i> &#x2014; Cupertino icon named "cursor_rays". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData cursor_rays = IconData(0xf61e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>decrease_indent</i> &#x2014; Cupertino icon named "decrease_indent". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData decrease_indent = IconData(0xf61f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>decrease_quotelevel</i> &#x2014; Cupertino icon named "decrease_quotelevel". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData decrease_quotelevel = IconData(0xf620, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>delete_left</i> &#x2014; Cupertino icon named "delete_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData delete_left = IconData(0xf621, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>delete_left_fill</i> &#x2014; Cupertino icon named "delete_left_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData delete_left_fill = IconData(0xf622, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>delete_right</i> &#x2014; Cupertino icon named "delete_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData delete_right = IconData(0xf623, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>delete_right_fill</i> &#x2014; Cupertino icon named "delete_right_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData delete_right_fill = IconData(0xf624, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>desktopcomputer</i> &#x2014; Cupertino icon named "desktopcomputer". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData desktopcomputer = IconData(0xf625, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>device_desktop</i> &#x2014; Cupertino icon named "device_desktop". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData device_desktop = IconData(0xf8cc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>device_laptop</i> &#x2014; Cupertino icon named "device_laptop". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData device_laptop = IconData(0xf8cd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>device_phone_landscape</i> &#x2014; Cupertino icon named "device_phone_landscape". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData device_phone_landscape = IconData(0xf8ce, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>device_phone_portrait</i> &#x2014; Cupertino icon named "device_phone_portrait". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData device_phone_portrait = IconData(0xf8cf, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>dial</i> &#x2014; Cupertino icon named "dial". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData dial = IconData(0xf626, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>dial_fill</i> &#x2014; Cupertino icon named "dial_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData dial_fill = IconData(0xf627, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>divide</i> &#x2014; Cupertino icon named "divide". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData divide = IconData(0xf628, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>divide_circle</i> &#x2014; Cupertino icon named "divide_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData divide_circle = IconData(0xf629, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>divide_circle_fill</i> &#x2014; Cupertino icon named "divide_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData divide_circle_fill = IconData(0xf62a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>divide_square</i> &#x2014; Cupertino icon named "divide_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData divide_square = IconData(0xf62b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>divide_square_fill</i> &#x2014; Cupertino icon named "divide_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData divide_square_fill = IconData(0xf62c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc</i> &#x2014; Cupertino icon named "doc". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc = IconData(0xf62d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_append</i> &#x2014; Cupertino icon named "doc_append". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_append = IconData(0xf62e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_chart</i> &#x2014; Cupertino icon named "doc_chart". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_chart = IconData(0xf8d0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_chart_fill</i> &#x2014; Cupertino icon named "doc_chart_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_chart_fill = IconData(0xf8d1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_checkmark</i> &#x2014; Cupertino icon named "doc_checkmark". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_checkmark = IconData(0xf8d2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_checkmark_fill</i> &#x2014; Cupertino icon named "doc_checkmark_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_checkmark_fill = IconData(0xf8d3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_circle</i> &#x2014; Cupertino icon named "doc_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_circle = IconData(0xf62f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_circle_fill</i> &#x2014; Cupertino icon named "doc_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_circle_fill = IconData(0xf630, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_fill</i> &#x2014; Cupertino icon named "doc_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_fill = IconData(0xf631, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_on_clipboard</i> &#x2014; Cupertino icon named "doc_on_clipboard". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_on_clipboard = IconData(0xf632, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_on_clipboard_fill</i> &#x2014; Cupertino icon named "doc_on_clipboard_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_on_clipboard_fill = IconData(0xf633, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_on_doc</i> &#x2014; Cupertino icon named "doc_on_doc". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_on_doc = IconData(0xf634, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_on_doc_fill</i> &#x2014; Cupertino icon named "doc_on_doc_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_on_doc_fill = IconData(0xf635, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_person</i> &#x2014; Cupertino icon named "doc_person". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_person = IconData(0xf8d4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_person_fill</i> &#x2014; Cupertino icon named "doc_person_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_person_fill = IconData(0xf8d5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_plaintext</i> &#x2014; Cupertino icon named "doc_plaintext". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_plaintext = IconData(0xf636, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_richtext</i> &#x2014; Cupertino icon named "doc_richtext". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_richtext = IconData(0xf637, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_text</i> &#x2014; Cupertino icon named "doc_text". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_text = IconData(0xf638, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_text_fill</i> &#x2014; Cupertino icon named "doc_text_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_text_fill = IconData(0xf639, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_text_search</i> &#x2014; Cupertino icon named "doc_text_search". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_text_search = IconData(0xf63a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>doc_text_viewfinder</i> &#x2014; Cupertino icon named "doc_text_viewfinder". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData doc_text_viewfinder = IconData(0xf63b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>dot_radiowaves_left_right</i> &#x2014; Cupertino icon named "dot_radiowaves_left_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData dot_radiowaves_left_right = IconData(0xf63c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>dot_radiowaves_right</i> &#x2014; Cupertino icon named "dot_radiowaves_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData dot_radiowaves_right = IconData(0xf63d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>dot_square</i> &#x2014; Cupertino icon named "dot_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData dot_square = IconData(0xf63e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>dot_square_fill</i> &#x2014; Cupertino icon named "dot_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData dot_square_fill = IconData(0xf63f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>download_circle</i> &#x2014; Cupertino icon named "download_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData download_circle = IconData(0xf8d6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>download_circle_fill</i> &#x2014; Cupertino icon named "download_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData download_circle_fill = IconData(0xf8d7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>drop</i> &#x2014; Cupertino icon named "drop". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData drop = IconData(0xf8d8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>drop_fill</i> &#x2014; Cupertino icon named "drop_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData drop_fill = IconData(0xf8d9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>drop_triangle</i> &#x2014; Cupertino icon named "drop_triangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData drop_triangle = IconData(0xf640, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>drop_triangle_fill</i> &#x2014; Cupertino icon named "drop_triangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData drop_triangle_fill = IconData(0xf641, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>ear</i> &#x2014; Cupertino icon named "ear". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData ear = IconData(0xf642, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>eject</i> &#x2014; Cupertino icon named "eject". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData eject = IconData(0xf643, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>eject_fill</i> &#x2014; Cupertino icon named "eject_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData eject_fill = IconData(0xf644, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>ellipses_bubble</i> &#x2014; Cupertino icon named "ellipses_bubble". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData ellipses_bubble = IconData(0xf645, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>ellipses_bubble_fill</i> &#x2014; Cupertino icon named "ellipses_bubble_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData ellipses_bubble_fill = IconData(0xf646, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>ellipsis_circle</i> &#x2014; Cupertino icon named "ellipsis_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData ellipsis_circle = IconData(0xf647, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>ellipsis_circle_fill</i> &#x2014; Cupertino icon named "ellipsis_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData ellipsis_circle_fill = IconData(0xf648, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>ellipsis_vertical</i> &#x2014; Cupertino icon named "ellipsis_vertical". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData ellipsis_vertical = IconData(0xf8da, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>ellipsis_vertical_circle</i> &#x2014; Cupertino icon named "ellipsis_vertical_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData ellipsis_vertical_circle = IconData(0xf8db, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>ellipsis_vertical_circle_fill</i> &#x2014; Cupertino icon named "ellipsis_vertical_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData ellipsis_vertical_circle_fill = IconData(0xf8dc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>envelope</i> &#x2014; Cupertino icon named "envelope". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [mail] which is available in cupertino_icons 0.1.3.
+  static const IconData envelope = IconData(0xf422, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>envelope_badge</i> &#x2014; Cupertino icon named "envelope_badge". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData envelope_badge = IconData(0xf649, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>envelope_badge_fill</i> &#x2014; Cupertino icon named "envelope_badge_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData envelope_badge_fill = IconData(0xf64a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>envelope_circle</i> &#x2014; Cupertino icon named "envelope_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData envelope_circle = IconData(0xf64b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>envelope_circle_fill</i> &#x2014; Cupertino icon named "envelope_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData envelope_circle_fill = IconData(0xf64c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>envelope_fill</i> &#x2014; Cupertino icon named "envelope_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [mail_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData envelope_fill = IconData(0xf423, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>envelope_open</i> &#x2014; Cupertino icon named "envelope_open". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData envelope_open = IconData(0xf64d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>envelope_open_fill</i> &#x2014; Cupertino icon named "envelope_open_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData envelope_open_fill = IconData(0xf64e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>equal</i> &#x2014; Cupertino icon named "equal". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData equal = IconData(0xf64f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>equal_circle</i> &#x2014; Cupertino icon named "equal_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData equal_circle = IconData(0xf650, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>equal_circle_fill</i> &#x2014; Cupertino icon named "equal_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData equal_circle_fill = IconData(0xf651, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>equal_square</i> &#x2014; Cupertino icon named "equal_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData equal_square = IconData(0xf652, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>equal_square_fill</i> &#x2014; Cupertino icon named "equal_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData equal_square_fill = IconData(0xf653, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>escape</i> &#x2014; Cupertino icon named "escape". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData escape = IconData(0xf654, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>exclamationmark</i> &#x2014; Cupertino icon named "exclamationmark". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData exclamationmark = IconData(0xf655, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>exclamationmark_bubble</i> &#x2014; Cupertino icon named "exclamationmark_bubble". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData exclamationmark_bubble = IconData(0xf656, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>exclamationmark_bubble_fill</i> &#x2014; Cupertino icon named "exclamationmark_bubble_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData exclamationmark_bubble_fill = IconData(0xf657, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>exclamationmark_circle</i> &#x2014; Cupertino icon named "exclamationmark_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData exclamationmark_circle = IconData(0xf658, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>exclamationmark_circle_fill</i> &#x2014; Cupertino icon named "exclamationmark_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData exclamationmark_circle_fill = IconData(0xf659, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>exclamationmark_octagon</i> &#x2014; Cupertino icon named "exclamationmark_octagon". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData exclamationmark_octagon = IconData(0xf65a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>exclamationmark_octagon_fill</i> &#x2014; Cupertino icon named "exclamationmark_octagon_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData exclamationmark_octagon_fill = IconData(0xf65b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>exclamationmark_shield</i> &#x2014; Cupertino icon named "exclamationmark_shield". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData exclamationmark_shield = IconData(0xf65c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>exclamationmark_shield_fill</i> &#x2014; Cupertino icon named "exclamationmark_shield_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData exclamationmark_shield_fill = IconData(0xf65d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>exclamationmark_square</i> &#x2014; Cupertino icon named "exclamationmark_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData exclamationmark_square = IconData(0xf65e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>exclamationmark_square_fill</i> &#x2014; Cupertino icon named "exclamationmark_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData exclamationmark_square_fill = IconData(0xf65f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>exclamationmark_triangle</i> &#x2014; Cupertino icon named "exclamationmark_triangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData exclamationmark_triangle = IconData(0xf660, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>exclamationmark_triangle_fill</i> &#x2014; Cupertino icon named "exclamationmark_triangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData exclamationmark_triangle_fill = IconData(0xf661, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>eye_fill</i> &#x2014; Cupertino icon named "eye_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [eye_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData eye_fill = IconData(0xf425, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>eye_slash</i> &#x2014; Cupertino icon named "eye_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData eye_slash = IconData(0xf662, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>eye_slash_fill</i> &#x2014; Cupertino icon named "eye_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData eye_slash_fill = IconData(0xf663, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>eyedropper</i> &#x2014; Cupertino icon named "eyedropper". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData eyedropper = IconData(0xf664, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>eyedropper_full</i> &#x2014; Cupertino icon named "eyedropper_full". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData eyedropper_full = IconData(0xf665, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>eyedropper_halffull</i> &#x2014; Cupertino icon named "eyedropper_halffull". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData eyedropper_halffull = IconData(0xf666, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>eyeglasses</i> &#x2014; Cupertino icon named "eyeglasses". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData eyeglasses = IconData(0xf667, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>f_cursive</i> &#x2014; Cupertino icon named "f_cursive". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData f_cursive = IconData(0xf668, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>f_cursive_circle</i> &#x2014; Cupertino icon named "f_cursive_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData f_cursive_circle = IconData(0xf669, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>f_cursive_circle_fill</i> &#x2014; Cupertino icon named "f_cursive_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData f_cursive_circle_fill = IconData(0xf66a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>film</i> &#x2014; Cupertino icon named "film". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData film = IconData(0xf66b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>film_fill</i> &#x2014; Cupertino icon named "film_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData film_fill = IconData(0xf66c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>flag_circle</i> &#x2014; Cupertino icon named "flag_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData flag_circle = IconData(0xf66d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>flag_circle_fill</i> &#x2014; Cupertino icon named "flag_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData flag_circle_fill = IconData(0xf66e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>flag_fill</i> &#x2014; Cupertino icon named "flag_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData flag_fill = IconData(0xf66f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>flag_slash</i> &#x2014; Cupertino icon named "flag_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData flag_slash = IconData(0xf670, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>flag_slash_fill</i> &#x2014; Cupertino icon named "flag_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData flag_slash_fill = IconData(0xf671, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>flame</i> &#x2014; Cupertino icon named "flame". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData flame = IconData(0xf672, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>flame_fill</i> &#x2014; Cupertino icon named "flame_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData flame_fill = IconData(0xf673, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>floppy_disk</i> &#x2014; Cupertino icon named "floppy_disk". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData floppy_disk = IconData(0xf8dd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>flowchart</i> &#x2014; Cupertino icon named "flowchart". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData flowchart = IconData(0xf674, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>flowchart_fill</i> &#x2014; Cupertino icon named "flowchart_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData flowchart_fill = IconData(0xf675, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>folder_badge_minus</i> &#x2014; Cupertino icon named "folder_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData folder_badge_minus = IconData(0xf676, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>folder_badge_person_crop</i> &#x2014; Cupertino icon named "folder_badge_person_crop". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData folder_badge_person_crop = IconData(0xf677, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>folder_badge_plus</i> &#x2014; Cupertino icon named "folder_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData folder_badge_plus = IconData(0xf678, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>folder_circle</i> &#x2014; Cupertino icon named "folder_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData folder_circle = IconData(0xf679, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>folder_circle_fill</i> &#x2014; Cupertino icon named "folder_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData folder_circle_fill = IconData(0xf67a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>folder_fill</i> &#x2014; Cupertino icon named "folder_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [folder_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData folder_fill = IconData(0xf435, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>folder_fill_badge_minus</i> &#x2014; Cupertino icon named "folder_fill_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData folder_fill_badge_minus = IconData(0xf67b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>folder_fill_badge_person_crop</i> &#x2014; Cupertino icon named "folder_fill_badge_person_crop". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData folder_fill_badge_person_crop = IconData(0xf67c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>folder_fill_badge_plus</i> &#x2014; Cupertino icon named "folder_fill_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData folder_fill_badge_plus = IconData(0xf67d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>forward_end</i> &#x2014; Cupertino icon named "forward_end". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData forward_end = IconData(0xf67f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>forward_end_alt</i> &#x2014; Cupertino icon named "forward_end_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData forward_end_alt = IconData(0xf680, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>forward_end_alt_fill</i> &#x2014; Cupertino icon named "forward_end_alt_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData forward_end_alt_fill = IconData(0xf681, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>forward_end_fill</i> &#x2014; Cupertino icon named "forward_end_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData forward_end_fill = IconData(0xf682, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>forward_fill</i> &#x2014; Cupertino icon named "forward_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData forward_fill = IconData(0xf683, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>function</i> &#x2014; Cupertino icon named "function". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData function = IconData(0xf684, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>fx</i> &#x2014; Cupertino icon named "fx". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData fx = IconData(0xf685, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gamecontroller</i> &#x2014; Cupertino icon named "gamecontroller". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [game_controller] which is available in cupertino_icons 0.1.3.
+  static const IconData gamecontroller = IconData(0xf43a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gamecontroller_alt_fill</i> &#x2014; Cupertino icon named "gamecontroller_alt_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gamecontroller_alt_fill = IconData(0xf8de, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gamecontroller_fill</i> &#x2014; Cupertino icon named "gamecontroller_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [game_controller_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData gamecontroller_fill = IconData(0xf43b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gauge</i> &#x2014; Cupertino icon named "gauge". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gauge = IconData(0xf686, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gauge_badge_minus</i> &#x2014; Cupertino icon named "gauge_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gauge_badge_minus = IconData(0xf687, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gauge_badge_plus</i> &#x2014; Cupertino icon named "gauge_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gauge_badge_plus = IconData(0xf688, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gear_alt</i> &#x2014; Cupertino icon named "gear_alt". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [gear] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [gear_big] which is available in cupertino_icons 0.1.3.
+  static const IconData gear_alt = IconData(0xf43c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gear_alt_fill</i> &#x2014; Cupertino icon named "gear_alt_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [gear_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData gear_alt_fill = IconData(0xf43d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gift</i> &#x2014; Cupertino icon named "gift". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gift = IconData(0xf689, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gift_alt</i> &#x2014; Cupertino icon named "gift_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gift_alt = IconData(0xf68a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gift_alt_fill</i> &#x2014; Cupertino icon named "gift_alt_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gift_alt_fill = IconData(0xf68b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gift_fill</i> &#x2014; Cupertino icon named "gift_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gift_fill = IconData(0xf68c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>globe</i> &#x2014; Cupertino icon named "globe". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData globe = IconData(0xf68d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gobackward</i> &#x2014; Cupertino icon named "gobackward". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gobackward = IconData(0xf68e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gobackward_10</i> &#x2014; Cupertino icon named "gobackward_10". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gobackward_10 = IconData(0xf68f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gobackward_15</i> &#x2014; Cupertino icon named "gobackward_15". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gobackward_15 = IconData(0xf690, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gobackward_30</i> &#x2014; Cupertino icon named "gobackward_30". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gobackward_30 = IconData(0xf691, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gobackward_45</i> &#x2014; Cupertino icon named "gobackward_45". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gobackward_45 = IconData(0xf692, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gobackward_60</i> &#x2014; Cupertino icon named "gobackward_60". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gobackward_60 = IconData(0xf693, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gobackward_75</i> &#x2014; Cupertino icon named "gobackward_75". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gobackward_75 = IconData(0xf694, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gobackward_90</i> &#x2014; Cupertino icon named "gobackward_90". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gobackward_90 = IconData(0xf695, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>gobackward_minus</i> &#x2014; Cupertino icon named "gobackward_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData gobackward_minus = IconData(0xf696, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>goforward</i> &#x2014; Cupertino icon named "goforward". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData goforward = IconData(0xf697, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>goforward_10</i> &#x2014; Cupertino icon named "goforward_10". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData goforward_10 = IconData(0xf698, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>goforward_15</i> &#x2014; Cupertino icon named "goforward_15". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData goforward_15 = IconData(0xf699, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>goforward_30</i> &#x2014; Cupertino icon named "goforward_30". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData goforward_30 = IconData(0xf69a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>goforward_45</i> &#x2014; Cupertino icon named "goforward_45". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData goforward_45 = IconData(0xf69b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>goforward_60</i> &#x2014; Cupertino icon named "goforward_60". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData goforward_60 = IconData(0xf69c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>goforward_75</i> &#x2014; Cupertino icon named "goforward_75". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData goforward_75 = IconData(0xf69d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>goforward_90</i> &#x2014; Cupertino icon named "goforward_90". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData goforward_90 = IconData(0xf69e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>goforward_plus</i> &#x2014; Cupertino icon named "goforward_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData goforward_plus = IconData(0xf69f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>graph_circle</i> &#x2014; Cupertino icon named "graph_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData graph_circle = IconData(0xf8df, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>graph_circle_fill</i> &#x2014; Cupertino icon named "graph_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData graph_circle_fill = IconData(0xf8e0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>graph_square</i> &#x2014; Cupertino icon named "graph_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData graph_square = IconData(0xf8e1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>graph_square_fill</i> &#x2014; Cupertino icon named "graph_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData graph_square_fill = IconData(0xf8e2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>greaterthan</i> &#x2014; Cupertino icon named "greaterthan". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData greaterthan = IconData(0xf6a0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>greaterthan_circle</i> &#x2014; Cupertino icon named "greaterthan_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData greaterthan_circle = IconData(0xf6a1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>greaterthan_circle_fill</i> &#x2014; Cupertino icon named "greaterthan_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData greaterthan_circle_fill = IconData(0xf6a2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>greaterthan_square</i> &#x2014; Cupertino icon named "greaterthan_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData greaterthan_square = IconData(0xf6a3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>greaterthan_square_fill</i> &#x2014; Cupertino icon named "greaterthan_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData greaterthan_square_fill = IconData(0xf6a4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>grid</i> &#x2014; Cupertino icon named "grid". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData grid = IconData(0xf6a5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>grid_circle</i> &#x2014; Cupertino icon named "grid_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData grid_circle = IconData(0xf6a6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>grid_circle_fill</i> &#x2014; Cupertino icon named "grid_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData grid_circle_fill = IconData(0xf6a7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>guitars</i> &#x2014; Cupertino icon named "guitars". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData guitars = IconData(0xf6a8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hammer</i> &#x2014; Cupertino icon named "hammer". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hammer = IconData(0xf6a9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hammer_fill</i> &#x2014; Cupertino icon named "hammer_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hammer_fill = IconData(0xf6aa, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hand_draw</i> &#x2014; Cupertino icon named "hand_draw". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hand_draw = IconData(0xf6ab, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hand_draw_fill</i> &#x2014; Cupertino icon named "hand_draw_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hand_draw_fill = IconData(0xf6ac, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hand_point_left</i> &#x2014; Cupertino icon named "hand_point_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hand_point_left = IconData(0xf6ad, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hand_point_left_fill</i> &#x2014; Cupertino icon named "hand_point_left_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hand_point_left_fill = IconData(0xf6ae, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hand_point_right</i> &#x2014; Cupertino icon named "hand_point_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hand_point_right = IconData(0xf6af, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hand_point_right_fill</i> &#x2014; Cupertino icon named "hand_point_right_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hand_point_right_fill = IconData(0xf6b0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hand_raised</i> &#x2014; Cupertino icon named "hand_raised". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hand_raised = IconData(0xf6b1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hand_raised_fill</i> &#x2014; Cupertino icon named "hand_raised_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hand_raised_fill = IconData(0xf6b2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hand_raised_slash</i> &#x2014; Cupertino icon named "hand_raised_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hand_raised_slash = IconData(0xf6b3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hand_raised_slash_fill</i> &#x2014; Cupertino icon named "hand_raised_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hand_raised_slash_fill = IconData(0xf6b4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hand_thumbsdown</i> &#x2014; Cupertino icon named "hand_thumbsdown". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hand_thumbsdown = IconData(0xf6b5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hand_thumbsdown_fill</i> &#x2014; Cupertino icon named "hand_thumbsdown_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hand_thumbsdown_fill = IconData(0xf6b6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hand_thumbsup</i> &#x2014; Cupertino icon named "hand_thumbsup". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hand_thumbsup = IconData(0xf6b7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hand_thumbsup_fill</i> &#x2014; Cupertino icon named "hand_thumbsup_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hand_thumbsup_fill = IconData(0xf6b8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hare</i> &#x2014; Cupertino icon named "hare". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hare = IconData(0xf6b9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hare_fill</i> &#x2014; Cupertino icon named "hare_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hare_fill = IconData(0xf6ba, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>headphones</i> &#x2014; Cupertino icon named "headphones". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData headphones = IconData(0xf6bb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>heart_circle</i> &#x2014; Cupertino icon named "heart_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData heart_circle = IconData(0xf6bc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>heart_circle_fill</i> &#x2014; Cupertino icon named "heart_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData heart_circle_fill = IconData(0xf6bd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>heart_fill</i> &#x2014; Cupertino icon named "heart_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [heart_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData heart_fill = IconData(0xf443, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>heart_slash</i> &#x2014; Cupertino icon named "heart_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData heart_slash = IconData(0xf6be, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>heart_slash_circle</i> &#x2014; Cupertino icon named "heart_slash_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData heart_slash_circle = IconData(0xf6bf, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>heart_slash_circle_fill</i> &#x2014; Cupertino icon named "heart_slash_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData heart_slash_circle_fill = IconData(0xf6c0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>heart_slash_fill</i> &#x2014; Cupertino icon named "heart_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData heart_slash_fill = IconData(0xf6c1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>helm</i> &#x2014; Cupertino icon named "helm". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData helm = IconData(0xf6c2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hexagon</i> &#x2014; Cupertino icon named "hexagon". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hexagon = IconData(0xf6c3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hexagon_fill</i> &#x2014; Cupertino icon named "hexagon_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hexagon_fill = IconData(0xf6c4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hifispeaker</i> &#x2014; Cupertino icon named "hifispeaker". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hifispeaker = IconData(0xf6c5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hifispeaker_fill</i> &#x2014; Cupertino icon named "hifispeaker_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hifispeaker_fill = IconData(0xf6c6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hourglass</i> &#x2014; Cupertino icon named "hourglass". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hourglass = IconData(0xf6c7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hourglass_bottomhalf_fill</i> &#x2014; Cupertino icon named "hourglass_bottomhalf_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hourglass_bottomhalf_fill = IconData(0xf6c8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hourglass_tophalf_fill</i> &#x2014; Cupertino icon named "hourglass_tophalf_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hourglass_tophalf_fill = IconData(0xf6c9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>house</i> &#x2014; Cupertino icon named "house". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [home] which is available in cupertino_icons 0.1.3.
+  static const IconData house = IconData(0xf447, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>house_alt</i> &#x2014; Cupertino icon named "house_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData house_alt = IconData(0xf8e3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>house_alt_fill</i> &#x2014; Cupertino icon named "house_alt_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData house_alt_fill = IconData(0xf8e4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>house_fill</i> &#x2014; Cupertino icon named "house_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData house_fill = IconData(0xf6ca, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>hurricane</i> &#x2014; Cupertino icon named "hurricane". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData hurricane = IconData(0xf6cb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>increase_indent</i> &#x2014; Cupertino icon named "increase_indent". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData increase_indent = IconData(0xf6cc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>increase_quotelevel</i> &#x2014; Cupertino icon named "increase_quotelevel". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData increase_quotelevel = IconData(0xf6cd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>infinite</i> &#x2014; Cupertino icon named "infinite". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [loop] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [loop_thick] which is available in cupertino_icons 0.1.3.
+  static const IconData infinite = IconData(0xf449, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>info_circle</i> &#x2014; Cupertino icon named "info_circle". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [info] which is available in cupertino_icons 0.1.3.
+  static const IconData info_circle = IconData(0xf44c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>info_circle_fill</i> &#x2014; Cupertino icon named "info_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData info_circle_fill = IconData(0xf6cf, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>italic</i> &#x2014; Cupertino icon named "italic". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData italic = IconData(0xf6d0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>keyboard</i> &#x2014; Cupertino icon named "keyboard". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData keyboard = IconData(0xf6d1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>keyboard_chevron_compact_down</i> &#x2014; Cupertino icon named "keyboard_chevron_compact_down". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData keyboard_chevron_compact_down = IconData(0xf6d2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>largecircle_fill_circle</i> &#x2014; Cupertino icon named "largecircle_fill_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData largecircle_fill_circle = IconData(0xf6d3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lasso</i> &#x2014; Cupertino icon named "lasso". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lasso = IconData(0xf6d4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>layers</i> &#x2014; Cupertino icon named "layers". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData layers = IconData(0xf8e5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>layers_alt</i> &#x2014; Cupertino icon named "layers_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData layers_alt = IconData(0xf8e6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>layers_alt_fill</i> &#x2014; Cupertino icon named "layers_alt_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData layers_alt_fill = IconData(0xf8e7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>layers_fill</i> &#x2014; Cupertino icon named "layers_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData layers_fill = IconData(0xf8e8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>leaf_arrow_circlepath</i> &#x2014; Cupertino icon named "leaf_arrow_circlepath". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData leaf_arrow_circlepath = IconData(0xf6d5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lessthan</i> &#x2014; Cupertino icon named "lessthan". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lessthan = IconData(0xf6d6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lessthan_circle</i> &#x2014; Cupertino icon named "lessthan_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lessthan_circle = IconData(0xf6d7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lessthan_circle_fill</i> &#x2014; Cupertino icon named "lessthan_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lessthan_circle_fill = IconData(0xf6d8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lessthan_square</i> &#x2014; Cupertino icon named "lessthan_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lessthan_square = IconData(0xf6d9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lessthan_square_fill</i> &#x2014; Cupertino icon named "lessthan_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lessthan_square_fill = IconData(0xf6da, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>light_max</i> &#x2014; Cupertino icon named "light_max". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData light_max = IconData(0xf6db, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>light_min</i> &#x2014; Cupertino icon named "light_min". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData light_min = IconData(0xf6dc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lightbulb</i> &#x2014; Cupertino icon named "lightbulb". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lightbulb = IconData(0xf6dd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lightbulb_fill</i> &#x2014; Cupertino icon named "lightbulb_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lightbulb_fill = IconData(0xf6de, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lightbulb_slash</i> &#x2014; Cupertino icon named "lightbulb_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lightbulb_slash = IconData(0xf6df, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lightbulb_slash_fill</i> &#x2014; Cupertino icon named "lightbulb_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lightbulb_slash_fill = IconData(0xf6e0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>line_horizontal_3</i> &#x2014; Cupertino icon named "line_horizontal_3". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData line_horizontal_3 = IconData(0xf6e1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>line_horizontal_3_decrease</i> &#x2014; Cupertino icon named "line_horizontal_3_decrease". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData line_horizontal_3_decrease = IconData(0xf6e2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>line_horizontal_3_decrease_circle</i> &#x2014; Cupertino icon named "line_horizontal_3_decrease_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData line_horizontal_3_decrease_circle = IconData(0xf6e3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>line_horizontal_3_decrease_circle_fill</i> &#x2014; Cupertino icon named "line_horizontal_3_decrease_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData line_horizontal_3_decrease_circle_fill = IconData(0xf6e4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>link</i> &#x2014; Cupertino icon named "link". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData link = IconData(0xf6e5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>link_circle</i> &#x2014; Cupertino icon named "link_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData link_circle = IconData(0xf6e6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>link_circle_fill</i> &#x2014; Cupertino icon named "link_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData link_circle_fill = IconData(0xf6e7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>list_bullet</i> &#x2014; Cupertino icon named "list_bullet". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData list_bullet = IconData(0xf6e8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>list_bullet_below_rectangle</i> &#x2014; Cupertino icon named "list_bullet_below_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData list_bullet_below_rectangle = IconData(0xf6e9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>list_bullet_indent</i> &#x2014; Cupertino icon named "list_bullet_indent". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData list_bullet_indent = IconData(0xf6ea, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>list_dash</i> &#x2014; Cupertino icon named "list_dash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData list_dash = IconData(0xf6eb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>list_number</i> &#x2014; Cupertino icon named "list_number". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData list_number = IconData(0xf6ec, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>list_number_rtl</i> &#x2014; Cupertino icon named "list_number_rtl". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData list_number_rtl = IconData(0xf6ed, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>location_circle</i> &#x2014; Cupertino icon named "location_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData location_circle = IconData(0xf6ef, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>location_circle_fill</i> &#x2014; Cupertino icon named "location_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData location_circle_fill = IconData(0xf6f0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>location_fill</i> &#x2014; Cupertino icon named "location_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData location_fill = IconData(0xf6f1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>location_north</i> &#x2014; Cupertino icon named "location_north". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData location_north = IconData(0xf6f2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>location_north_fill</i> &#x2014; Cupertino icon named "location_north_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData location_north_fill = IconData(0xf6f3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>location_north_line</i> &#x2014; Cupertino icon named "location_north_line". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData location_north_line = IconData(0xf6f4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>location_north_line_fill</i> &#x2014; Cupertino icon named "location_north_line_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData location_north_line_fill = IconData(0xf6f5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>location_slash</i> &#x2014; Cupertino icon named "location_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData location_slash = IconData(0xf6f6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>location_slash_fill</i> &#x2014; Cupertino icon named "location_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData location_slash_fill = IconData(0xf6f7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lock</i> &#x2014; Cupertino icon named "lock". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [padlock] which is available in cupertino_icons 0.1.3.
+  static const IconData lock = IconData(0xf4c8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lock_circle</i> &#x2014; Cupertino icon named "lock_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lock_circle = IconData(0xf6f8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lock_circle_fill</i> &#x2014; Cupertino icon named "lock_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lock_circle_fill = IconData(0xf6f9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lock_fill</i> &#x2014; Cupertino icon named "lock_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [padlock_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData lock_fill = IconData(0xf4c9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lock_open</i> &#x2014; Cupertino icon named "lock_open". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lock_open = IconData(0xf6fa, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lock_open_fill</i> &#x2014; Cupertino icon named "lock_open_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lock_open_fill = IconData(0xf6fb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lock_rotation</i> &#x2014; Cupertino icon named "lock_rotation". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lock_rotation = IconData(0xf6fc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lock_rotation_open</i> &#x2014; Cupertino icon named "lock_rotation_open". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lock_rotation_open = IconData(0xf6fd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lock_shield</i> &#x2014; Cupertino icon named "lock_shield". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lock_shield = IconData(0xf6fe, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lock_shield_fill</i> &#x2014; Cupertino icon named "lock_shield_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lock_shield_fill = IconData(0xf6ff, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lock_slash</i> &#x2014; Cupertino icon named "lock_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lock_slash = IconData(0xf700, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>lock_slash_fill</i> &#x2014; Cupertino icon named "lock_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData lock_slash_fill = IconData(0xf701, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>macwindow</i> &#x2014; Cupertino icon named "macwindow". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData macwindow = IconData(0xf702, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>map</i> &#x2014; Cupertino icon named "map". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData map = IconData(0xf703, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>map_fill</i> &#x2014; Cupertino icon named "map_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData map_fill = IconData(0xf704, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>map_pin</i> &#x2014; Cupertino icon named "map_pin". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData map_pin = IconData(0xf705, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>map_pin_ellipse</i> &#x2014; Cupertino icon named "map_pin_ellipse". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData map_pin_ellipse = IconData(0xf706, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>map_pin_slash</i> &#x2014; Cupertino icon named "map_pin_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData map_pin_slash = IconData(0xf707, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>memories</i> &#x2014; Cupertino icon named "memories". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData memories = IconData(0xf708, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>memories_badge_minus</i> &#x2014; Cupertino icon named "memories_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData memories_badge_minus = IconData(0xf709, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>memories_badge_plus</i> &#x2014; Cupertino icon named "memories_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData memories_badge_plus = IconData(0xf70a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>metronome</i> &#x2014; Cupertino icon named "metronome". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData metronome = IconData(0xf70b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>mic_circle</i> &#x2014; Cupertino icon named "mic_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData mic_circle = IconData(0xf70c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>mic_circle_fill</i> &#x2014; Cupertino icon named "mic_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData mic_circle_fill = IconData(0xf70d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>mic_fill</i> &#x2014; Cupertino icon named "mic_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [mic_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData mic_fill = IconData(0xf461, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>mic_slash</i> &#x2014; Cupertino icon named "mic_slash". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [mic_off] which is available in cupertino_icons 0.1.3.
+  static const IconData mic_slash = IconData(0xf45f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>mic_slash_fill</i> &#x2014; Cupertino icon named "mic_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData mic_slash_fill = IconData(0xf70e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>minus</i> &#x2014; Cupertino icon named "minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData minus = IconData(0xf70f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>minus_circle</i> &#x2014; Cupertino icon named "minus_circle". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [minus_circled] which is available in cupertino_icons 0.1.3.
+  static const IconData minus_circle = IconData(0xf463, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>minus_circle_fill</i> &#x2014; Cupertino icon named "minus_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData minus_circle_fill = IconData(0xf710, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>minus_rectangle</i> &#x2014; Cupertino icon named "minus_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData minus_rectangle = IconData(0xf711, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>minus_rectangle_fill</i> &#x2014; Cupertino icon named "minus_rectangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData minus_rectangle_fill = IconData(0xf712, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>minus_slash_plus</i> &#x2014; Cupertino icon named "minus_slash_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData minus_slash_plus = IconData(0xf713, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>minus_square</i> &#x2014; Cupertino icon named "minus_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData minus_square = IconData(0xf714, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>minus_square_fill</i> &#x2014; Cupertino icon named "minus_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData minus_square_fill = IconData(0xf715, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_dollar</i> &#x2014; Cupertino icon named "money_dollar". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_dollar = IconData(0xf8e9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_dollar_circle</i> &#x2014; Cupertino icon named "money_dollar_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_dollar_circle = IconData(0xf8ea, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_dollar_circle_fill</i> &#x2014; Cupertino icon named "money_dollar_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_dollar_circle_fill = IconData(0xf8eb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_euro</i> &#x2014; Cupertino icon named "money_euro". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_euro = IconData(0xf8ec, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_euro_circle</i> &#x2014; Cupertino icon named "money_euro_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_euro_circle = IconData(0xf8ed, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_euro_circle_fill</i> &#x2014; Cupertino icon named "money_euro_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_euro_circle_fill = IconData(0xf8ee, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_pound</i> &#x2014; Cupertino icon named "money_pound". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_pound = IconData(0xf8ef, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_pound_circle</i> &#x2014; Cupertino icon named "money_pound_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_pound_circle = IconData(0xf8f0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_pound_circle_fill</i> &#x2014; Cupertino icon named "money_pound_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_pound_circle_fill = IconData(0xf8f1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_rubl</i> &#x2014; Cupertino icon named "money_rubl". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_rubl = IconData(0xf8f2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_rubl_circle</i> &#x2014; Cupertino icon named "money_rubl_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_rubl_circle = IconData(0xf8f3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_rubl_circle_fill</i> &#x2014; Cupertino icon named "money_rubl_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_rubl_circle_fill = IconData(0xf8f4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_yen</i> &#x2014; Cupertino icon named "money_yen". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_yen = IconData(0xf8f5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_yen_circle</i> &#x2014; Cupertino icon named "money_yen_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_yen_circle = IconData(0xf8f6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>money_yen_circle_fill</i> &#x2014; Cupertino icon named "money_yen_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData money_yen_circle_fill = IconData(0xf8f7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>moon</i> &#x2014; Cupertino icon named "moon". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData moon = IconData(0xf716, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>moon_circle</i> &#x2014; Cupertino icon named "moon_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData moon_circle = IconData(0xf717, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>moon_circle_fill</i> &#x2014; Cupertino icon named "moon_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData moon_circle_fill = IconData(0xf718, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>moon_fill</i> &#x2014; Cupertino icon named "moon_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData moon_fill = IconData(0xf719, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>moon_stars</i> &#x2014; Cupertino icon named "moon_stars". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData moon_stars = IconData(0xf71a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>moon_stars_fill</i> &#x2014; Cupertino icon named "moon_stars_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData moon_stars_fill = IconData(0xf71b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>moon_zzz</i> &#x2014; Cupertino icon named "moon_zzz". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData moon_zzz = IconData(0xf71c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>moon_zzz_fill</i> &#x2014; Cupertino icon named "moon_zzz_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData moon_zzz_fill = IconData(0xf71d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>move</i> &#x2014; Cupertino icon named "move". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData move = IconData(0xf8f8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>multiply</i> &#x2014; Cupertino icon named "multiply". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData multiply = IconData(0xf71e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>multiply_circle</i> &#x2014; Cupertino icon named "multiply_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData multiply_circle = IconData(0xf71f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>multiply_circle_fill</i> &#x2014; Cupertino icon named "multiply_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData multiply_circle_fill = IconData(0xf720, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>multiply_square</i> &#x2014; Cupertino icon named "multiply_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData multiply_square = IconData(0xf721, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>multiply_square_fill</i> &#x2014; Cupertino icon named "multiply_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData multiply_square_fill = IconData(0xf722, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>music_albums</i> &#x2014; Cupertino icon named "music_albums". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData music_albums = IconData(0xf8f9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>music_albums_fill</i> &#x2014; Cupertino icon named "music_albums_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData music_albums_fill = IconData(0xf8fa, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>music_house</i> &#x2014; Cupertino icon named "music_house". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData music_house = IconData(0xf723, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>music_house_fill</i> &#x2014; Cupertino icon named "music_house_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData music_house_fill = IconData(0xf724, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>music_mic</i> &#x2014; Cupertino icon named "music_mic". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData music_mic = IconData(0xf725, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>music_note_2</i> &#x2014; Cupertino icon named "music_note_2". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [double_music_note] which is available in cupertino_icons 0.1.3.
+  static const IconData music_note_2 = IconData(0xf46c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>music_note_list</i> &#x2014; Cupertino icon named "music_note_list". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData music_note_list = IconData(0xf726, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>nosign</i> &#x2014; Cupertino icon named "nosign". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData nosign = IconData(0xf727, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>number</i> &#x2014; Cupertino icon named "number". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData number = IconData(0xf728, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>number_circle</i> &#x2014; Cupertino icon named "number_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData number_circle = IconData(0xf729, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>number_circle_fill</i> &#x2014; Cupertino icon named "number_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData number_circle_fill = IconData(0xf72a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>number_square</i> &#x2014; Cupertino icon named "number_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData number_square = IconData(0xf72b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>number_square_fill</i> &#x2014; Cupertino icon named "number_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData number_square_fill = IconData(0xf72c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>option</i> &#x2014; Cupertino icon named "option". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData option = IconData(0xf72d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>paintbrush</i> &#x2014; Cupertino icon named "paintbrush". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData paintbrush = IconData(0xf72e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>paintbrush_fill</i> &#x2014; Cupertino icon named "paintbrush_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData paintbrush_fill = IconData(0xf72f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pano</i> &#x2014; Cupertino icon named "pano". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pano = IconData(0xf730, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pano_fill</i> &#x2014; Cupertino icon named "pano_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pano_fill = IconData(0xf731, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>paperclip</i> &#x2014; Cupertino icon named "paperclip". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData paperclip = IconData(0xf732, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>paperplane</i> &#x2014; Cupertino icon named "paperplane". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData paperplane = IconData(0xf733, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>paperplane_fill</i> &#x2014; Cupertino icon named "paperplane_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData paperplane_fill = IconData(0xf734, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>paragraph</i> &#x2014; Cupertino icon named "paragraph". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData paragraph = IconData(0xf735, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pause_circle</i> &#x2014; Cupertino icon named "pause_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pause_circle = IconData(0xf736, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pause_circle_fill</i> &#x2014; Cupertino icon named "pause_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pause_circle_fill = IconData(0xf737, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pause_fill</i> &#x2014; Cupertino icon named "pause_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [pause_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData pause_fill = IconData(0xf478, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pause_rectangle</i> &#x2014; Cupertino icon named "pause_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pause_rectangle = IconData(0xf738, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pause_rectangle_fill</i> &#x2014; Cupertino icon named "pause_rectangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pause_rectangle_fill = IconData(0xf739, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pencil_circle</i> &#x2014; Cupertino icon named "pencil_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pencil_circle = IconData(0xf73a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pencil_circle_fill</i> &#x2014; Cupertino icon named "pencil_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pencil_circle_fill = IconData(0xf73b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pencil_ellipsis_rectangle</i> &#x2014; Cupertino icon named "pencil_ellipsis_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pencil_ellipsis_rectangle = IconData(0xf73c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pencil_outline</i> &#x2014; Cupertino icon named "pencil_outline". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pencil_outline = IconData(0xf73d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pencil_slash</i> &#x2014; Cupertino icon named "pencil_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pencil_slash = IconData(0xf73e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>percent</i> &#x2014; Cupertino icon named "percent". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData percent = IconData(0xf73f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_2</i> &#x2014; Cupertino icon named "person_2". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_2 = IconData(0xf740, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_2_alt</i> &#x2014; Cupertino icon named "person_2_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_2_alt = IconData(0xf8fb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_2_fill</i> &#x2014; Cupertino icon named "person_2_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_2_fill = IconData(0xf741, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_2_square_stack</i> &#x2014; Cupertino icon named "person_2_square_stack". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_2_square_stack = IconData(0xf742, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_2_square_stack_fill</i> &#x2014; Cupertino icon named "person_2_square_stack_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_2_square_stack_fill = IconData(0xf743, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_3</i> &#x2014; Cupertino icon named "person_3". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [group] which is available in cupertino_icons 0.1.3.
+  static const IconData person_3 = IconData(0xf47b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_3_fill</i> &#x2014; Cupertino icon named "person_3_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [group_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData person_3_fill = IconData(0xf47c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_alt</i> &#x2014; Cupertino icon named "person_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_alt = IconData(0xf8fc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_alt_circle</i> &#x2014; Cupertino icon named "person_alt_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_alt_circle = IconData(0xf8fd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_alt_circle_fill</i> &#x2014; Cupertino icon named "person_alt_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_alt_circle_fill = IconData(0xf8fe, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_badge_minus</i> &#x2014; Cupertino icon named "person_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_badge_minus = IconData(0xf744, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_badge_minus_fill</i> &#x2014; Cupertino icon named "person_badge_minus_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_badge_minus_fill = IconData(0xf745, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_badge_plus</i> &#x2014; Cupertino icon named "person_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [person_add] which is available in cupertino_icons 0.1.3.
+  static const IconData person_badge_plus = IconData(0xf47f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_badge_plus_fill</i> &#x2014; Cupertino icon named "person_badge_plus_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [person_add_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData person_badge_plus_fill = IconData(0xf480, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_circle</i> &#x2014; Cupertino icon named "person_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_circle = IconData(0xf746, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_circle_fill</i> &#x2014; Cupertino icon named "person_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_circle_fill = IconData(0xf747, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_circle</i> &#x2014; Cupertino icon named "person_crop_circle". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [profile_circled] which is available in cupertino_icons 0.1.3.
+  static const IconData person_crop_circle = IconData(0xf419, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_circle_badge_checkmark</i> &#x2014; Cupertino icon named "person_crop_circle_badge_checkmark". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_circle_badge_checkmark = IconData(0xf748, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_circle_badge_exclam</i> &#x2014; Cupertino icon named "person_crop_circle_badge_exclam". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_circle_badge_exclam = IconData(0xf749, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_circle_badge_minus</i> &#x2014; Cupertino icon named "person_crop_circle_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_circle_badge_minus = IconData(0xf74a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_circle_badge_plus</i> &#x2014; Cupertino icon named "person_crop_circle_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_circle_badge_plus = IconData(0xf74b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_circle_badge_xmark</i> &#x2014; Cupertino icon named "person_crop_circle_badge_xmark". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_circle_badge_xmark = IconData(0xf74c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_circle_fill</i> &#x2014; Cupertino icon named "person_crop_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_circle_fill = IconData(0xf74d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_circle_fill_badge_checkmark</i> &#x2014; Cupertino icon named "person_crop_circle_fill_badge_checkmark". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_circle_fill_badge_checkmark = IconData(0xf74e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_circle_fill_badge_exclam</i> &#x2014; Cupertino icon named "person_crop_circle_fill_badge_exclam". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_circle_fill_badge_exclam = IconData(0xf74f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_circle_fill_badge_minus</i> &#x2014; Cupertino icon named "person_crop_circle_fill_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_circle_fill_badge_minus = IconData(0xf750, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_circle_fill_badge_plus</i> &#x2014; Cupertino icon named "person_crop_circle_fill_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_circle_fill_badge_plus = IconData(0xf751, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_circle_fill_badge_xmark</i> &#x2014; Cupertino icon named "person_crop_circle_fill_badge_xmark". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_circle_fill_badge_xmark = IconData(0xf752, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_rectangle</i> &#x2014; Cupertino icon named "person_crop_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_rectangle = IconData(0xf753, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_rectangle_fill</i> &#x2014; Cupertino icon named "person_crop_rectangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_rectangle_fill = IconData(0xf754, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_square</i> &#x2014; Cupertino icon named "person_crop_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_square = IconData(0xf755, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_crop_square_fill</i> &#x2014; Cupertino icon named "person_crop_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData person_crop_square_fill = IconData(0xf756, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>person_fill</i> &#x2014; Cupertino icon named "person_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [person_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData person_fill = IconData(0xf47e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>personalhotspot</i> &#x2014; Cupertino icon named "personalhotspot". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData personalhotspot = IconData(0xf757, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>perspective</i> &#x2014; Cupertino icon named "perspective". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData perspective = IconData(0xf758, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_arrow_down_left</i> &#x2014; Cupertino icon named "phone_arrow_down_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData phone_arrow_down_left = IconData(0xf759, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_arrow_right</i> &#x2014; Cupertino icon named "phone_arrow_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData phone_arrow_right = IconData(0xf75a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_arrow_up_right</i> &#x2014; Cupertino icon named "phone_arrow_up_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData phone_arrow_up_right = IconData(0xf75b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_badge_plus</i> &#x2014; Cupertino icon named "phone_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData phone_badge_plus = IconData(0xf75c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_circle</i> &#x2014; Cupertino icon named "phone_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData phone_circle = IconData(0xf75d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_circle_fill</i> &#x2014; Cupertino icon named "phone_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData phone_circle_fill = IconData(0xf75e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_down</i> &#x2014; Cupertino icon named "phone_down". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData phone_down = IconData(0xf75f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_down_circle</i> &#x2014; Cupertino icon named "phone_down_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData phone_down_circle = IconData(0xf760, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_down_circle_fill</i> &#x2014; Cupertino icon named "phone_down_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData phone_down_circle_fill = IconData(0xf761, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_down_fill</i> &#x2014; Cupertino icon named "phone_down_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData phone_down_fill = IconData(0xf762, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_fill</i> &#x2014; Cupertino icon named "phone_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [phone_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData phone_fill = IconData(0xf4b9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_fill_arrow_down_left</i> &#x2014; Cupertino icon named "phone_fill_arrow_down_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData phone_fill_arrow_down_left = IconData(0xf763, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_fill_arrow_right</i> &#x2014; Cupertino icon named "phone_fill_arrow_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData phone_fill_arrow_right = IconData(0xf764, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_fill_arrow_up_right</i> &#x2014; Cupertino icon named "phone_fill_arrow_up_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData phone_fill_arrow_up_right = IconData(0xf765, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>phone_fill_badge_plus</i> &#x2014; Cupertino icon named "phone_fill_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData phone_fill_badge_plus = IconData(0xf766, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>photo</i> &#x2014; Cupertino icon named "photo". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData photo = IconData(0xf767, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>photo_fill</i> &#x2014; Cupertino icon named "photo_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData photo_fill = IconData(0xf768, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>photo_fill_on_rectangle_fill</i> &#x2014; Cupertino icon named "photo_fill_on_rectangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData photo_fill_on_rectangle_fill = IconData(0xf769, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>photo_on_rectangle</i> &#x2014; Cupertino icon named "photo_on_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData photo_on_rectangle = IconData(0xf76a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>piano</i> &#x2014; Cupertino icon named "piano". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData piano = IconData(0xf8ff, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pin</i> &#x2014; Cupertino icon named "pin". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pin = IconData(0xf76b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pin_fill</i> &#x2014; Cupertino icon named "pin_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pin_fill = IconData(0xf76c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pin_slash</i> &#x2014; Cupertino icon named "pin_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pin_slash = IconData(0xf76d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>pin_slash_fill</i> &#x2014; Cupertino icon named "pin_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData pin_slash_fill = IconData(0xf76e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>placemark</i> &#x2014; Cupertino icon named "placemark". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [location] which is available in cupertino_icons 0.1.3.
+  static const IconData placemark = IconData(0xf455, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>placemark_fill</i> &#x2014; Cupertino icon named "placemark_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [location_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData placemark_fill = IconData(0xf456, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>play</i> &#x2014; Cupertino icon named "play". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [play_arrow] which is available in cupertino_icons 0.1.3.
+  static const IconData play = IconData(0xf487, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>play_circle</i> &#x2014; Cupertino icon named "play_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData play_circle = IconData(0xf76f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>play_circle_fill</i> &#x2014; Cupertino icon named "play_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData play_circle_fill = IconData(0xf770, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>play_fill</i> &#x2014; Cupertino icon named "play_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [play_arrow_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData play_fill = IconData(0xf488, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>play_rectangle</i> &#x2014; Cupertino icon named "play_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData play_rectangle = IconData(0xf771, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>play_rectangle_fill</i> &#x2014; Cupertino icon named "play_rectangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData play_rectangle_fill = IconData(0xf772, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>playpause</i> &#x2014; Cupertino icon named "playpause". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData playpause = IconData(0xf773, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>playpause_fill</i> &#x2014; Cupertino icon named "playpause_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData playpause_fill = IconData(0xf774, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus</i> &#x2014; Cupertino icon named "plus". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [add] which is available in cupertino_icons 0.1.3.
+  static const IconData plus = IconData(0xf489, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_app</i> &#x2014; Cupertino icon named "plus_app". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plus_app = IconData(0xf775, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_app_fill</i> &#x2014; Cupertino icon named "plus_app_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plus_app_fill = IconData(0xf776, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_bubble</i> &#x2014; Cupertino icon named "plus_bubble". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plus_bubble = IconData(0xf777, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_bubble_fill</i> &#x2014; Cupertino icon named "plus_bubble_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plus_bubble_fill = IconData(0xf778, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_circle</i> &#x2014; Cupertino icon named "plus_circle". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [plus_circled] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [add_circled] which is available in cupertino_icons 0.1.3.
+  static const IconData plus_circle = IconData(0xf48a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_circle_fill</i> &#x2014; Cupertino icon named "plus_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [add_circled_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData plus_circle_fill = IconData(0xf48b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_rectangle</i> &#x2014; Cupertino icon named "plus_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plus_rectangle = IconData(0xf779, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_rectangle_fill</i> &#x2014; Cupertino icon named "plus_rectangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plus_rectangle_fill = IconData(0xf77a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_rectangle_fill_on_rectangle_fill</i> &#x2014; Cupertino icon named "plus_rectangle_fill_on_rectangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plus_rectangle_fill_on_rectangle_fill = IconData(0xf77b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_rectangle_on_rectangle</i> &#x2014; Cupertino icon named "plus_rectangle_on_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plus_rectangle_on_rectangle = IconData(0xf77c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_slash_minus</i> &#x2014; Cupertino icon named "plus_slash_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plus_slash_minus = IconData(0xf77d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_square</i> &#x2014; Cupertino icon named "plus_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plus_square = IconData(0xf77e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_square_fill</i> &#x2014; Cupertino icon named "plus_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plus_square_fill = IconData(0xf77f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_square_fill_on_square_fill</i> &#x2014; Cupertino icon named "plus_square_fill_on_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plus_square_fill_on_square_fill = IconData(0xf780, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plus_square_on_square</i> &#x2014; Cupertino icon named "plus_square_on_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plus_square_on_square = IconData(0xf781, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plusminus</i> &#x2014; Cupertino icon named "plusminus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plusminus = IconData(0xf782, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plusminus_circle</i> &#x2014; Cupertino icon named "plusminus_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plusminus_circle = IconData(0xf783, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>plusminus_circle_fill</i> &#x2014; Cupertino icon named "plusminus_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData plusminus_circle_fill = IconData(0xf784, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>power</i> &#x2014; Cupertino icon named "power". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData power = IconData(0xf785, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>printer</i> &#x2014; Cupertino icon named "printer". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData printer = IconData(0xf786, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>printer_fill</i> &#x2014; Cupertino icon named "printer_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData printer_fill = IconData(0xf787, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>projective</i> &#x2014; Cupertino icon named "projective". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData projective = IconData(0xf788, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>purchased</i> &#x2014; Cupertino icon named "purchased". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData purchased = IconData(0xf789, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>purchased_circle</i> &#x2014; Cupertino icon named "purchased_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData purchased_circle = IconData(0xf78a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>purchased_circle_fill</i> &#x2014; Cupertino icon named "purchased_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData purchased_circle_fill = IconData(0xf78b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>qrcode</i> &#x2014; Cupertino icon named "qrcode". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData qrcode = IconData(0xf78c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>qrcode_viewfinder</i> &#x2014; Cupertino icon named "qrcode_viewfinder". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData qrcode_viewfinder = IconData(0xf78d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>question</i> &#x2014; Cupertino icon named "question". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData question = IconData(0xf78e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>question_circle</i> &#x2014; Cupertino icon named "question_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData question_circle = IconData(0xf78f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>question_circle_fill</i> &#x2014; Cupertino icon named "question_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData question_circle_fill = IconData(0xf790, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>question_diamond</i> &#x2014; Cupertino icon named "question_diamond". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData question_diamond = IconData(0xf791, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>question_diamond_fill</i> &#x2014; Cupertino icon named "question_diamond_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData question_diamond_fill = IconData(0xf792, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>question_square</i> &#x2014; Cupertino icon named "question_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData question_square = IconData(0xf793, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>question_square_fill</i> &#x2014; Cupertino icon named "question_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData question_square_fill = IconData(0xf794, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>quote_bubble</i> &#x2014; Cupertino icon named "quote_bubble". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData quote_bubble = IconData(0xf795, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>quote_bubble_fill</i> &#x2014; Cupertino icon named "quote_bubble_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData quote_bubble_fill = IconData(0xf796, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>radiowaves_left</i> &#x2014; Cupertino icon named "radiowaves_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData radiowaves_left = IconData(0xf797, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>radiowaves_right</i> &#x2014; Cupertino icon named "radiowaves_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData radiowaves_right = IconData(0xf798, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rays</i> &#x2014; Cupertino icon named "rays". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rays = IconData(0xf799, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>recordingtape</i> &#x2014; Cupertino icon named "recordingtape". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData recordingtape = IconData(0xf79a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle</i> &#x2014; Cupertino icon named "rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle = IconData(0xf79b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_3_offgrid</i> &#x2014; Cupertino icon named "rectangle_3_offgrid". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_3_offgrid = IconData(0xf79c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_3_offgrid_fill</i> &#x2014; Cupertino icon named "rectangle_3_offgrid_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_3_offgrid_fill = IconData(0xf79d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_arrow_up_right_arrow_down_left</i> &#x2014; Cupertino icon named "rectangle_arrow_up_right_arrow_down_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_arrow_up_right_arrow_down_left = IconData(0xf79e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_arrow_up_right_arrow_down_left_slash</i> &#x2014; Cupertino icon named "rectangle_arrow_up_right_arrow_down_left_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_arrow_up_right_arrow_down_left_slash = IconData(0xf79f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_badge_checkmark</i> &#x2014; Cupertino icon named "rectangle_badge_checkmark". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_badge_checkmark = IconData(0xf7a0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_badge_xmark</i> &#x2014; Cupertino icon named "rectangle_badge_xmark". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_badge_xmark = IconData(0xf7a1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_compress_vertical</i> &#x2014; Cupertino icon named "rectangle_compress_vertical". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_compress_vertical = IconData(0xf7a2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_dock</i> &#x2014; Cupertino icon named "rectangle_dock". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_dock = IconData(0xf7a3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_expand_vertical</i> &#x2014; Cupertino icon named "rectangle_expand_vertical". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_expand_vertical = IconData(0xf7a4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_fill</i> &#x2014; Cupertino icon named "rectangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_fill = IconData(0xf7a5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_fill_badge_checkmark</i> &#x2014; Cupertino icon named "rectangle_fill_badge_checkmark". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_fill_badge_checkmark = IconData(0xf7a6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_fill_badge_xmark</i> &#x2014; Cupertino icon named "rectangle_fill_badge_xmark". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_fill_badge_xmark = IconData(0xf7a7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_fill_on_rectangle_angled_fill</i> &#x2014; Cupertino icon named "rectangle_fill_on_rectangle_angled_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_fill_on_rectangle_angled_fill = IconData(0xf7a8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_fill_on_rectangle_fill</i> &#x2014; Cupertino icon named "rectangle_fill_on_rectangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_fill_on_rectangle_fill = IconData(0xf7a9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_grid_1x2</i> &#x2014; Cupertino icon named "rectangle_grid_1x2". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_grid_1x2 = IconData(0xf7aa, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_grid_1x2_fill</i> &#x2014; Cupertino icon named "rectangle_grid_1x2_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_grid_1x2_fill = IconData(0xf7ab, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_grid_2x2</i> &#x2014; Cupertino icon named "rectangle_grid_2x2". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_grid_2x2 = IconData(0xf7ac, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_grid_2x2_fill</i> &#x2014; Cupertino icon named "rectangle_grid_2x2_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_grid_2x2_fill = IconData(0xf7ad, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_grid_3x2</i> &#x2014; Cupertino icon named "rectangle_grid_3x2". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_grid_3x2 = IconData(0xf7ae, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_grid_3x2_fill</i> &#x2014; Cupertino icon named "rectangle_grid_3x2_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_grid_3x2_fill = IconData(0xf7af, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_on_rectangle</i> &#x2014; Cupertino icon named "rectangle_on_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_on_rectangle = IconData(0xf7b0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_on_rectangle_angled</i> &#x2014; Cupertino icon named "rectangle_on_rectangle_angled". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_on_rectangle_angled = IconData(0xf7b1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_paperclip</i> &#x2014; Cupertino icon named "rectangle_paperclip". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_paperclip = IconData(0xf7b2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_split_3x1</i> &#x2014; Cupertino icon named "rectangle_split_3x1". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_split_3x1 = IconData(0xf7b3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_split_3x1_fill</i> &#x2014; Cupertino icon named "rectangle_split_3x1_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_split_3x1_fill = IconData(0xf7b4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_split_3x3</i> &#x2014; Cupertino icon named "rectangle_split_3x3". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_split_3x3 = IconData(0xf7b5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_split_3x3_fill</i> &#x2014; Cupertino icon named "rectangle_split_3x3_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_split_3x3_fill = IconData(0xf7b6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_stack</i> &#x2014; Cupertino icon named "rectangle_stack". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [collections] which is available in cupertino_icons 0.1.3.
+  static const IconData rectangle_stack = IconData(0xf3c9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_stack_badge_minus</i> &#x2014; Cupertino icon named "rectangle_stack_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_stack_badge_minus = IconData(0xf7b7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_stack_badge_person_crop</i> &#x2014; Cupertino icon named "rectangle_stack_badge_person_crop". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_stack_badge_person_crop = IconData(0xf7b8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_stack_badge_plus</i> &#x2014; Cupertino icon named "rectangle_stack_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_stack_badge_plus = IconData(0xf7b9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_stack_fill</i> &#x2014; Cupertino icon named "rectangle_stack_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [collections_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData rectangle_stack_fill = IconData(0xf3ca, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_stack_fill_badge_minus</i> &#x2014; Cupertino icon named "rectangle_stack_fill_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_stack_fill_badge_minus = IconData(0xf7ba, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_stack_fill_badge_person_crop</i> &#x2014; Cupertino icon named "rectangle_stack_fill_badge_person_crop". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_stack_fill_badge_person_crop = IconData(0xf7bb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_stack_fill_badge_plus</i> &#x2014; Cupertino icon named "rectangle_stack_fill_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_stack_fill_badge_plus = IconData(0xf7bc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_stack_person_crop</i> &#x2014; Cupertino icon named "rectangle_stack_person_crop". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_stack_person_crop = IconData(0xf7bd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rectangle_stack_person_crop_fill</i> &#x2014; Cupertino icon named "rectangle_stack_person_crop_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rectangle_stack_person_crop_fill = IconData(0xf7be, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>repeat</i> &#x2014; Cupertino icon named "repeat". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData repeat = IconData(0xf7bf, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>repeat_1</i> &#x2014; Cupertino icon named "repeat_1". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData repeat_1 = IconData(0xf7c0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>resize</i> &#x2014; Cupertino icon named "resize". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData resize = IconData(0xf900, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>resize_h</i> &#x2014; Cupertino icon named "resize_h". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData resize_h = IconData(0xf901, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>resize_v</i> &#x2014; Cupertino icon named "resize_v". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData resize_v = IconData(0xf902, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>return_icon</i> &#x2014; Cupertino icon named "return_icon". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData return_icon = IconData(0xf7c1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rhombus</i> &#x2014; Cupertino icon named "rhombus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rhombus = IconData(0xf7c2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rhombus_fill</i> &#x2014; Cupertino icon named "rhombus_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rhombus_fill = IconData(0xf7c3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rocket</i> &#x2014; Cupertino icon named "rocket". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rocket = IconData(0xf903, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rocket_fill</i> &#x2014; Cupertino icon named "rocket_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rocket_fill = IconData(0xf904, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rosette</i> &#x2014; Cupertino icon named "rosette". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rosette = IconData(0xf7c4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rotate_left</i> &#x2014; Cupertino icon named "rotate_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rotate_left = IconData(0xf7c5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rotate_left_fill</i> &#x2014; Cupertino icon named "rotate_left_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rotate_left_fill = IconData(0xf7c6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rotate_right</i> &#x2014; Cupertino icon named "rotate_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rotate_right = IconData(0xf7c7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>rotate_right_fill</i> &#x2014; Cupertino icon named "rotate_right_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData rotate_right_fill = IconData(0xf7c8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>scissors</i> &#x2014; Cupertino icon named "scissors". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData scissors = IconData(0xf7c9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>scissors_alt</i> &#x2014; Cupertino icon named "scissors_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData scissors_alt = IconData(0xf905, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>scope</i> &#x2014; Cupertino icon named "scope". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData scope = IconData(0xf7ca, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>scribble</i> &#x2014; Cupertino icon named "scribble". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData scribble = IconData(0xf7cb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>search_circle</i> &#x2014; Cupertino icon named "search_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData search_circle = IconData(0xf7cc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>search_circle_fill</i> &#x2014; Cupertino icon named "search_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData search_circle_fill = IconData(0xf7cd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>selection_pin_in_out</i> &#x2014; Cupertino icon named "selection_pin_in_out". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData selection_pin_in_out = IconData(0xf7ce, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>shield</i> &#x2014; Cupertino icon named "shield". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData shield = IconData(0xf7cf, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>shield_fill</i> &#x2014; Cupertino icon named "shield_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData shield_fill = IconData(0xf7d0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>shield_lefthalf_fill</i> &#x2014; Cupertino icon named "shield_lefthalf_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData shield_lefthalf_fill = IconData(0xf7d1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>shield_slash</i> &#x2014; Cupertino icon named "shield_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData shield_slash = IconData(0xf7d2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>shield_slash_fill</i> &#x2014; Cupertino icon named "shield_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData shield_slash_fill = IconData(0xf7d3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>shift</i> &#x2014; Cupertino icon named "shift". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData shift = IconData(0xf7d4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>shift_fill</i> &#x2014; Cupertino icon named "shift_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData shift_fill = IconData(0xf7d5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sidebar_left</i> &#x2014; Cupertino icon named "sidebar_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sidebar_left = IconData(0xf7d6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sidebar_right</i> &#x2014; Cupertino icon named "sidebar_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sidebar_right = IconData(0xf7d7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>signature</i> &#x2014; Cupertino icon named "signature". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData signature = IconData(0xf7d8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>skew</i> &#x2014; Cupertino icon named "skew". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData skew = IconData(0xf7d9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>slash_circle</i> &#x2014; Cupertino icon named "slash_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData slash_circle = IconData(0xf7da, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>slash_circle_fill</i> &#x2014; Cupertino icon named "slash_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData slash_circle_fill = IconData(0xf7db, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>slider_horizontal_3</i> &#x2014; Cupertino icon named "slider_horizontal_3". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData slider_horizontal_3 = IconData(0xf7dc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>slider_horizontal_below_rectangle</i> &#x2014; Cupertino icon named "slider_horizontal_below_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData slider_horizontal_below_rectangle = IconData(0xf7dd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>slowmo</i> &#x2014; Cupertino icon named "slowmo". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData slowmo = IconData(0xf7de, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>smallcircle_circle</i> &#x2014; Cupertino icon named "smallcircle_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData smallcircle_circle = IconData(0xf7df, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>smallcircle_circle_fill</i> &#x2014; Cupertino icon named "smallcircle_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData smallcircle_circle_fill = IconData(0xf7e0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>smallcircle_fill_circle</i> &#x2014; Cupertino icon named "smallcircle_fill_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData smallcircle_fill_circle = IconData(0xf7e1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>smallcircle_fill_circle_fill</i> &#x2014; Cupertino icon named "smallcircle_fill_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData smallcircle_fill_circle_fill = IconData(0xf7e2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>smiley</i> &#x2014; Cupertino icon named "smiley". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData smiley = IconData(0xf7e3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>smiley_fill</i> &#x2014; Cupertino icon named "smiley_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData smiley_fill = IconData(0xf7e4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>smoke</i> &#x2014; Cupertino icon named "smoke". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData smoke = IconData(0xf7e5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>smoke_fill</i> &#x2014; Cupertino icon named "smoke_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData smoke_fill = IconData(0xf7e6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>snow</i> &#x2014; Cupertino icon named "snow". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData snow = IconData(0xf7e7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sort_down</i> &#x2014; Cupertino icon named "sort_down". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sort_down = IconData(0xf906, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sort_down_circle</i> &#x2014; Cupertino icon named "sort_down_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sort_down_circle = IconData(0xf907, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sort_down_circle_fill</i> &#x2014; Cupertino icon named "sort_down_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sort_down_circle_fill = IconData(0xf908, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sort_up</i> &#x2014; Cupertino icon named "sort_up". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sort_up = IconData(0xf909, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sort_up_circle</i> &#x2014; Cupertino icon named "sort_up_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sort_up_circle = IconData(0xf90a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sort_up_circle_fill</i> &#x2014; Cupertino icon named "sort_up_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sort_up_circle_fill = IconData(0xf90b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sparkles</i> &#x2014; Cupertino icon named "sparkles". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sparkles = IconData(0xf7e8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker</i> &#x2014; Cupertino icon named "speaker". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData speaker = IconData(0xf7e9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_1</i> &#x2014; Cupertino icon named "speaker_1". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData speaker_1 = IconData(0xf7ea, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_1_fill</i> &#x2014; Cupertino icon named "speaker_1_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [volume_down] which is available in cupertino_icons 0.1.3.
+  static const IconData speaker_1_fill = IconData(0xf3b7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_2</i> &#x2014; Cupertino icon named "speaker_2". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData speaker_2 = IconData(0xf7eb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_2_fill</i> &#x2014; Cupertino icon named "speaker_2_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData speaker_2_fill = IconData(0xf7ec, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_3</i> &#x2014; Cupertino icon named "speaker_3". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData speaker_3 = IconData(0xf7ed, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_3_fill</i> &#x2014; Cupertino icon named "speaker_3_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [volume_up] which is available in cupertino_icons 0.1.3.
+  static const IconData speaker_3_fill = IconData(0xf3ba, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_fill</i> &#x2014; Cupertino icon named "speaker_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [volume_mute] which is available in cupertino_icons 0.1.3.
+  static const IconData speaker_fill = IconData(0xf3b8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_slash</i> &#x2014; Cupertino icon named "speaker_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData speaker_slash = IconData(0xf7ee, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_slash_fill</i> &#x2014; Cupertino icon named "speaker_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [volume_off] which is available in cupertino_icons 0.1.3.
+  static const IconData speaker_slash_fill = IconData(0xf3b9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_slash_fill_rtl</i> &#x2014; Cupertino icon named "speaker_slash_fill_rtl". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData speaker_slash_fill_rtl = IconData(0xf7ef, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_slash_rtl</i> &#x2014; Cupertino icon named "speaker_slash_rtl". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData speaker_slash_rtl = IconData(0xf7f0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_zzz</i> &#x2014; Cupertino icon named "speaker_zzz". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData speaker_zzz = IconData(0xf7f1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_zzz_fill</i> &#x2014; Cupertino icon named "speaker_zzz_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData speaker_zzz_fill = IconData(0xf7f2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_zzz_fill_rtl</i> &#x2014; Cupertino icon named "speaker_zzz_fill_rtl". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData speaker_zzz_fill_rtl = IconData(0xf7f3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speaker_zzz_rtl</i> &#x2014; Cupertino icon named "speaker_zzz_rtl". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData speaker_zzz_rtl = IconData(0xf7f4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>speedometer</i> &#x2014; Cupertino icon named "speedometer". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData speedometer = IconData(0xf7f5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sportscourt</i> &#x2014; Cupertino icon named "sportscourt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sportscourt = IconData(0xf7f6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sportscourt_fill</i> &#x2014; Cupertino icon named "sportscourt_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sportscourt_fill = IconData(0xf7f7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square</i> &#x2014; Cupertino icon named "square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square = IconData(0xf7f8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_arrow_down</i> &#x2014; Cupertino icon named "square_arrow_down". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_arrow_down = IconData(0xf7f9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_arrow_down_fill</i> &#x2014; Cupertino icon named "square_arrow_down_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_arrow_down_fill = IconData(0xf7fa, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_arrow_down_on_square</i> &#x2014; Cupertino icon named "square_arrow_down_on_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_arrow_down_on_square = IconData(0xf7fb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_arrow_down_on_square_fill</i> &#x2014; Cupertino icon named "square_arrow_down_on_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_arrow_down_on_square_fill = IconData(0xf7fc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_arrow_left</i> &#x2014; Cupertino icon named "square_arrow_left". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_arrow_left = IconData(0xf90c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_arrow_left_fill</i> &#x2014; Cupertino icon named "square_arrow_left_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_arrow_left_fill = IconData(0xf90d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_arrow_right</i> &#x2014; Cupertino icon named "square_arrow_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_arrow_right = IconData(0xf90e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_arrow_right_fill</i> &#x2014; Cupertino icon named "square_arrow_right_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_arrow_right_fill = IconData(0xf90f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_arrow_up</i> &#x2014; Cupertino icon named "square_arrow_up". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [share] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [share_up] which is available in cupertino_icons 0.1.3.
+  static const IconData square_arrow_up = IconData(0xf4ca, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_arrow_up_fill</i> &#x2014; Cupertino icon named "square_arrow_up_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [share_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData square_arrow_up_fill = IconData(0xf4cb, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_arrow_up_on_square</i> &#x2014; Cupertino icon named "square_arrow_up_on_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_arrow_up_on_square = IconData(0xf7fd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_arrow_up_on_square_fill</i> &#x2014; Cupertino icon named "square_arrow_up_on_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_arrow_up_on_square_fill = IconData(0xf7fe, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_favorites</i> &#x2014; Cupertino icon named "square_favorites". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_favorites = IconData(0xf910, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_favorites_alt</i> &#x2014; Cupertino icon named "square_favorites_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_favorites_alt = IconData(0xf911, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_favorites_alt_fill</i> &#x2014; Cupertino icon named "square_favorites_alt_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_favorites_alt_fill = IconData(0xf912, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_favorites_fill</i> &#x2014; Cupertino icon named "square_favorites_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_favorites_fill = IconData(0xf913, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_fill</i> &#x2014; Cupertino icon named "square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_fill = IconData(0xf7ff, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_fill_line_vertical_square</i> &#x2014; Cupertino icon named "square_fill_line_vertical_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_fill_line_vertical_square = IconData(0xf800, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_fill_line_vertical_square_fill</i> &#x2014; Cupertino icon named "square_fill_line_vertical_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_fill_line_vertical_square_fill = IconData(0xf801, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_fill_on_circle_fill</i> &#x2014; Cupertino icon named "square_fill_on_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_fill_on_circle_fill = IconData(0xf802, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_fill_on_square_fill</i> &#x2014; Cupertino icon named "square_fill_on_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_fill_on_square_fill = IconData(0xf803, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_grid_2x2</i> &#x2014; Cupertino icon named "square_grid_2x2". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_grid_2x2 = IconData(0xf804, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_grid_2x2_fill</i> &#x2014; Cupertino icon named "square_grid_2x2_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_grid_2x2_fill = IconData(0xf805, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_grid_3x2</i> &#x2014; Cupertino icon named "square_grid_3x2". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_grid_3x2 = IconData(0xf806, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_grid_3x2_fill</i> &#x2014; Cupertino icon named "square_grid_3x2_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_grid_3x2_fill = IconData(0xf807, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_grid_4x3_fill</i> &#x2014; Cupertino icon named "square_grid_4x3_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_grid_4x3_fill = IconData(0xf808, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_lefthalf_fill</i> &#x2014; Cupertino icon named "square_lefthalf_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_lefthalf_fill = IconData(0xf809, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_line_vertical_square</i> &#x2014; Cupertino icon named "square_line_vertical_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_line_vertical_square = IconData(0xf80a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_line_vertical_square_fill</i> &#x2014; Cupertino icon named "square_line_vertical_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_line_vertical_square_fill = IconData(0xf80b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_list</i> &#x2014; Cupertino icon named "square_list". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_list = IconData(0xf914, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_list_fill</i> &#x2014; Cupertino icon named "square_list_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_list_fill = IconData(0xf915, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_on_circle</i> &#x2014; Cupertino icon named "square_on_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_on_circle = IconData(0xf80c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_on_square</i> &#x2014; Cupertino icon named "square_on_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_on_square = IconData(0xf80d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_pencil</i> &#x2014; Cupertino icon named "square_pencil". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [create] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [create_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData square_pencil = IconData(0xf417, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_pencil_fill</i> &#x2014; Cupertino icon named "square_pencil_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [create] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [create_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData square_pencil_fill = IconData(0xf417, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_righthalf_fill</i> &#x2014; Cupertino icon named "square_righthalf_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_righthalf_fill = IconData(0xf80e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_split_1x2</i> &#x2014; Cupertino icon named "square_split_1x2". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_split_1x2 = IconData(0xf80f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_split_1x2_fill</i> &#x2014; Cupertino icon named "square_split_1x2_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_split_1x2_fill = IconData(0xf810, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_split_2x1</i> &#x2014; Cupertino icon named "square_split_2x1". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_split_2x1 = IconData(0xf811, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_split_2x1_fill</i> &#x2014; Cupertino icon named "square_split_2x1_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_split_2x1_fill = IconData(0xf812, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_split_2x2</i> &#x2014; Cupertino icon named "square_split_2x2". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_split_2x2 = IconData(0xf813, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_split_2x2_fill</i> &#x2014; Cupertino icon named "square_split_2x2_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_split_2x2_fill = IconData(0xf814, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_stack</i> &#x2014; Cupertino icon named "square_stack". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_stack = IconData(0xf815, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_stack_3d_down_dottedline</i> &#x2014; Cupertino icon named "square_stack_3d_down_dottedline". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_stack_3d_down_dottedline = IconData(0xf816, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_stack_3d_down_right</i> &#x2014; Cupertino icon named "square_stack_3d_down_right". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_stack_3d_down_right = IconData(0xf817, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_stack_3d_down_right_fill</i> &#x2014; Cupertino icon named "square_stack_3d_down_right_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_stack_3d_down_right_fill = IconData(0xf818, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_stack_3d_up</i> &#x2014; Cupertino icon named "square_stack_3d_up". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_stack_3d_up = IconData(0xf819, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_stack_3d_up_fill</i> &#x2014; Cupertino icon named "square_stack_3d_up_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_stack_3d_up_fill = IconData(0xf81a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_stack_3d_up_slash</i> &#x2014; Cupertino icon named "square_stack_3d_up_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_stack_3d_up_slash = IconData(0xf81b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_stack_3d_up_slash_fill</i> &#x2014; Cupertino icon named "square_stack_3d_up_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_stack_3d_up_slash_fill = IconData(0xf81c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>square_stack_fill</i> &#x2014; Cupertino icon named "square_stack_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData square_stack_fill = IconData(0xf81d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>squares_below_rectangle</i> &#x2014; Cupertino icon named "squares_below_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData squares_below_rectangle = IconData(0xf81e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>star</i> &#x2014; Cupertino icon named "star". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData star = IconData(0xf81f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>star_circle</i> &#x2014; Cupertino icon named "star_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData star_circle = IconData(0xf820, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>star_circle_fill</i> &#x2014; Cupertino icon named "star_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData star_circle_fill = IconData(0xf821, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>star_fill</i> &#x2014; Cupertino icon named "star_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData star_fill = IconData(0xf822, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>star_lefthalf_fill</i> &#x2014; Cupertino icon named "star_lefthalf_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData star_lefthalf_fill = IconData(0xf823, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>star_slash</i> &#x2014; Cupertino icon named "star_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData star_slash = IconData(0xf824, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>star_slash_fill</i> &#x2014; Cupertino icon named "star_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData star_slash_fill = IconData(0xf825, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>staroflife</i> &#x2014; Cupertino icon named "staroflife". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData staroflife = IconData(0xf826, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>staroflife_fill</i> &#x2014; Cupertino icon named "staroflife_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData staroflife_fill = IconData(0xf827, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>stop</i> &#x2014; Cupertino icon named "stop". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData stop = IconData(0xf828, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>stop_circle</i> &#x2014; Cupertino icon named "stop_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData stop_circle = IconData(0xf829, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>stop_circle_fill</i> &#x2014; Cupertino icon named "stop_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData stop_circle_fill = IconData(0xf82a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>stop_fill</i> &#x2014; Cupertino icon named "stop_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData stop_fill = IconData(0xf82b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>stopwatch</i> &#x2014; Cupertino icon named "stopwatch". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData stopwatch = IconData(0xf82c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>stopwatch_fill</i> &#x2014; Cupertino icon named "stopwatch_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData stopwatch_fill = IconData(0xf82d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>strikethrough</i> &#x2014; Cupertino icon named "strikethrough". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData strikethrough = IconData(0xf82e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>suit_club</i> &#x2014; Cupertino icon named "suit_club". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData suit_club = IconData(0xf82f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>suit_club_fill</i> &#x2014; Cupertino icon named "suit_club_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData suit_club_fill = IconData(0xf830, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>suit_diamond</i> &#x2014; Cupertino icon named "suit_diamond". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData suit_diamond = IconData(0xf831, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>suit_diamond_fill</i> &#x2014; Cupertino icon named "suit_diamond_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData suit_diamond_fill = IconData(0xf832, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>suit_heart</i> &#x2014; Cupertino icon named "suit_heart". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData suit_heart = IconData(0xf833, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>suit_heart_fill</i> &#x2014; Cupertino icon named "suit_heart_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData suit_heart_fill = IconData(0xf834, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>suit_spade</i> &#x2014; Cupertino icon named "suit_spade". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData suit_spade = IconData(0xf835, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>suit_spade_fill</i> &#x2014; Cupertino icon named "suit_spade_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData suit_spade_fill = IconData(0xf836, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sum</i> &#x2014; Cupertino icon named "sum". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sum = IconData(0xf837, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sun_dust</i> &#x2014; Cupertino icon named "sun_dust". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sun_dust = IconData(0xf838, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sun_dust_fill</i> &#x2014; Cupertino icon named "sun_dust_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sun_dust_fill = IconData(0xf839, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sun_haze</i> &#x2014; Cupertino icon named "sun_haze". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sun_haze = IconData(0xf83a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sun_haze_fill</i> &#x2014; Cupertino icon named "sun_haze_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sun_haze_fill = IconData(0xf83b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sun_max</i> &#x2014; Cupertino icon named "sun_max". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [brightness] which is available in cupertino_icons 0.1.3.
+  static const IconData sun_max = IconData(0xf4b6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sun_max_fill</i> &#x2014; Cupertino icon named "sun_max_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [brightness_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData sun_max_fill = IconData(0xf4b7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sun_min</i> &#x2014; Cupertino icon named "sun_min". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sun_min = IconData(0xf83c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sun_min_fill</i> &#x2014; Cupertino icon named "sun_min_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sun_min_fill = IconData(0xf83d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sunrise</i> &#x2014; Cupertino icon named "sunrise". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sunrise = IconData(0xf83e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sunrise_fill</i> &#x2014; Cupertino icon named "sunrise_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sunrise_fill = IconData(0xf83f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sunset</i> &#x2014; Cupertino icon named "sunset". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sunset = IconData(0xf840, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>sunset_fill</i> &#x2014; Cupertino icon named "sunset_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData sunset_fill = IconData(0xf841, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>t_bubble</i> &#x2014; Cupertino icon named "t_bubble". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData t_bubble = IconData(0xf842, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>t_bubble_fill</i> &#x2014; Cupertino icon named "t_bubble_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData t_bubble_fill = IconData(0xf843, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>table</i> &#x2014; Cupertino icon named "table". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData table = IconData(0xf844, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>table_badge_more</i> &#x2014; Cupertino icon named "table_badge_more". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData table_badge_more = IconData(0xf845, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>table_badge_more_fill</i> &#x2014; Cupertino icon named "table_badge_more_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData table_badge_more_fill = IconData(0xf846, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>table_fill</i> &#x2014; Cupertino icon named "table_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData table_fill = IconData(0xf847, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tag_circle</i> &#x2014; Cupertino icon named "tag_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tag_circle = IconData(0xf848, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tag_circle_fill</i> &#x2014; Cupertino icon named "tag_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tag_circle_fill = IconData(0xf849, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tag_fill</i> &#x2014; Cupertino icon named "tag_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [tag_solid] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [tags_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData tag_fill = IconData(0xf48d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_aligncenter</i> &#x2014; Cupertino icon named "text_aligncenter". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_aligncenter = IconData(0xf84a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_alignleft</i> &#x2014; Cupertino icon named "text_alignleft". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_alignleft = IconData(0xf84b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_alignright</i> &#x2014; Cupertino icon named "text_alignright". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_alignright = IconData(0xf84c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_append</i> &#x2014; Cupertino icon named "text_append". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_append = IconData(0xf84d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_badge_checkmark</i> &#x2014; Cupertino icon named "text_badge_checkmark". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_badge_checkmark = IconData(0xf84e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_badge_minus</i> &#x2014; Cupertino icon named "text_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_badge_minus = IconData(0xf84f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_badge_plus</i> &#x2014; Cupertino icon named "text_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_badge_plus = IconData(0xf850, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_badge_star</i> &#x2014; Cupertino icon named "text_badge_star". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_badge_star = IconData(0xf851, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_badge_xmark</i> &#x2014; Cupertino icon named "text_badge_xmark". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_badge_xmark = IconData(0xf852, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_bubble</i> &#x2014; Cupertino icon named "text_bubble". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_bubble = IconData(0xf853, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_bubble_fill</i> &#x2014; Cupertino icon named "text_bubble_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_bubble_fill = IconData(0xf854, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_cursor</i> &#x2014; Cupertino icon named "text_cursor". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_cursor = IconData(0xf855, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_insert</i> &#x2014; Cupertino icon named "text_insert". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_insert = IconData(0xf856, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_justify</i> &#x2014; Cupertino icon named "text_justify". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_justify = IconData(0xf857, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_justifyleft</i> &#x2014; Cupertino icon named "text_justifyleft". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_justifyleft = IconData(0xf858, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_justifyright</i> &#x2014; Cupertino icon named "text_justifyright". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_justifyright = IconData(0xf859, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>text_quote</i> &#x2014; Cupertino icon named "text_quote". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData text_quote = IconData(0xf85a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>textbox</i> &#x2014; Cupertino icon named "textbox". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData textbox = IconData(0xf85b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>textformat</i> &#x2014; Cupertino icon named "textformat". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData textformat = IconData(0xf85c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>textformat_123</i> &#x2014; Cupertino icon named "textformat_123". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData textformat_123 = IconData(0xf85d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>textformat_abc</i> &#x2014; Cupertino icon named "textformat_abc". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData textformat_abc = IconData(0xf85e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>textformat_abc_dottedunderline</i> &#x2014; Cupertino icon named "textformat_abc_dottedunderline". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData textformat_abc_dottedunderline = IconData(0xf85f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>textformat_alt</i> &#x2014; Cupertino icon named "textformat_alt". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData textformat_alt = IconData(0xf860, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>textformat_size</i> &#x2014; Cupertino icon named "textformat_size". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData textformat_size = IconData(0xf861, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>textformat_subscript</i> &#x2014; Cupertino icon named "textformat_subscript". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData textformat_subscript = IconData(0xf862, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>textformat_superscript</i> &#x2014; Cupertino icon named "textformat_superscript". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData textformat_superscript = IconData(0xf863, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>thermometer</i> &#x2014; Cupertino icon named "thermometer". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData thermometer = IconData(0xf864, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>thermometer_snowflake</i> &#x2014; Cupertino icon named "thermometer_snowflake". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData thermometer_snowflake = IconData(0xf865, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>thermometer_sun</i> &#x2014; Cupertino icon named "thermometer_sun". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData thermometer_sun = IconData(0xf866, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>ticket</i> &#x2014; Cupertino icon named "ticket". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData ticket = IconData(0xf916, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>ticket_fill</i> &#x2014; Cupertino icon named "ticket_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData ticket_fill = IconData(0xf917, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tickets</i> &#x2014; Cupertino icon named "tickets". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tickets = IconData(0xf918, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tickets_fill</i> &#x2014; Cupertino icon named "tickets_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tickets_fill = IconData(0xf919, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>timelapse</i> &#x2014; Cupertino icon named "timelapse". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData timelapse = IconData(0xf867, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>timer</i> &#x2014; Cupertino icon named "timer". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData timer = IconData(0xf868, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>timer_fill</i> &#x2014; Cupertino icon named "timer_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData timer_fill = IconData(0xf91a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>today</i> &#x2014; Cupertino icon named "today". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData today = IconData(0xf91b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>today_fill</i> &#x2014; Cupertino icon named "today_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData today_fill = IconData(0xf91c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tornado</i> &#x2014; Cupertino icon named "tornado". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tornado = IconData(0xf869, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tortoise</i> &#x2014; Cupertino icon named "tortoise". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tortoise = IconData(0xf86a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tortoise_fill</i> &#x2014; Cupertino icon named "tortoise_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tortoise_fill = IconData(0xf86b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tram_fill</i> &#x2014; Cupertino icon named "tram_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tram_fill = IconData(0xf86c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>trash</i> &#x2014; Cupertino icon named "trash". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [delete] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [delete_simple] which is available in cupertino_icons 0.1.3.
+  static const IconData trash = IconData(0xf4c4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>trash_circle</i> &#x2014; Cupertino icon named "trash_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData trash_circle = IconData(0xf86d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>trash_circle_fill</i> &#x2014; Cupertino icon named "trash_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData trash_circle_fill = IconData(0xf86e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>trash_fill</i> &#x2014; Cupertino icon named "trash_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [delete_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData trash_fill = IconData(0xf4c5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>trash_slash</i> &#x2014; Cupertino icon named "trash_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData trash_slash = IconData(0xf86f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>trash_slash_fill</i> &#x2014; Cupertino icon named "trash_slash_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData trash_slash_fill = IconData(0xf870, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tray</i> &#x2014; Cupertino icon named "tray". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tray = IconData(0xf871, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tray_2</i> &#x2014; Cupertino icon named "tray_2". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tray_2 = IconData(0xf872, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tray_2_fill</i> &#x2014; Cupertino icon named "tray_2_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tray_2_fill = IconData(0xf873, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tray_arrow_down</i> &#x2014; Cupertino icon named "tray_arrow_down". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tray_arrow_down = IconData(0xf874, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tray_arrow_down_fill</i> &#x2014; Cupertino icon named "tray_arrow_down_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tray_arrow_down_fill = IconData(0xf875, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tray_arrow_up</i> &#x2014; Cupertino icon named "tray_arrow_up". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tray_arrow_up = IconData(0xf876, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tray_arrow_up_fill</i> &#x2014; Cupertino icon named "tray_arrow_up_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tray_arrow_up_fill = IconData(0xf877, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tray_fill</i> &#x2014; Cupertino icon named "tray_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tray_fill = IconData(0xf878, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tray_full</i> &#x2014; Cupertino icon named "tray_full". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tray_full = IconData(0xf879, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tray_full_fill</i> &#x2014; Cupertino icon named "tray_full_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tray_full_fill = IconData(0xf87a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tree</i> &#x2014; Cupertino icon named "tree". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tree = IconData(0xf91d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>triangle</i> &#x2014; Cupertino icon named "triangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData triangle = IconData(0xf87b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>triangle_fill</i> &#x2014; Cupertino icon named "triangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData triangle_fill = IconData(0xf87c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>triangle_lefthalf_fill</i> &#x2014; Cupertino icon named "triangle_lefthalf_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData triangle_lefthalf_fill = IconData(0xf87d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>triangle_righthalf_fill</i> &#x2014; Cupertino icon named "triangle_righthalf_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData triangle_righthalf_fill = IconData(0xf87e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tropicalstorm</i> &#x2014; Cupertino icon named "tropicalstorm". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tropicalstorm = IconData(0xf87f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tuningfork</i> &#x2014; Cupertino icon named "tuningfork". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tuningfork = IconData(0xf880, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tv</i> &#x2014; Cupertino icon named "tv". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tv = IconData(0xf881, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tv_circle</i> &#x2014; Cupertino icon named "tv_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tv_circle = IconData(0xf882, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tv_circle_fill</i> &#x2014; Cupertino icon named "tv_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tv_circle_fill = IconData(0xf883, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tv_fill</i> &#x2014; Cupertino icon named "tv_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tv_fill = IconData(0xf884, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tv_music_note</i> &#x2014; Cupertino icon named "tv_music_note". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tv_music_note = IconData(0xf885, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>tv_music_note_fill</i> &#x2014; Cupertino icon named "tv_music_note_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData tv_music_note_fill = IconData(0xf886, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>uiwindow_split_2x1</i> &#x2014; Cupertino icon named "uiwindow_split_2x1". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData uiwindow_split_2x1 = IconData(0xf887, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>umbrella</i> &#x2014; Cupertino icon named "umbrella". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData umbrella = IconData(0xf888, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>umbrella_fill</i> &#x2014; Cupertino icon named "umbrella_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData umbrella_fill = IconData(0xf889, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>underline</i> &#x2014; Cupertino icon named "underline". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData underline = IconData(0xf88a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>upload_circle</i> &#x2014; Cupertino icon named "upload_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData upload_circle = IconData(0xf91e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>upload_circle_fill</i> &#x2014; Cupertino icon named "upload_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData upload_circle_fill = IconData(0xf91f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>videocam</i> &#x2014; Cupertino icon named "videocam". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [video_camera] which is available in cupertino_icons 0.1.3.
+  static const IconData videocam = IconData(0xf4cc, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>videocam_circle</i> &#x2014; Cupertino icon named "videocam_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData videocam_circle = IconData(0xf920, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>videocam_circle_fill</i> &#x2014; Cupertino icon named "videocam_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData videocam_circle_fill = IconData(0xf921, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>videocam_fill</i> &#x2014; Cupertino icon named "videocam_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [video_camera_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData videocam_fill = IconData(0xf4cd, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>view_2d</i> &#x2014; Cupertino icon named "view_2d". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData view_2d = IconData(0xf88b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>view_3d</i> &#x2014; Cupertino icon named "view_3d". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData view_3d = IconData(0xf88c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>viewfinder</i> &#x2014; Cupertino icon named "viewfinder". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData viewfinder = IconData(0xf88d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>viewfinder_circle</i> &#x2014; Cupertino icon named "viewfinder_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData viewfinder_circle = IconData(0xf88e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>viewfinder_circle_fill</i> &#x2014; Cupertino icon named "viewfinder_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData viewfinder_circle_fill = IconData(0xf88f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>wand_rays</i> &#x2014; Cupertino icon named "wand_rays". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData wand_rays = IconData(0xf890, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>wand_rays_inverse</i> &#x2014; Cupertino icon named "wand_rays_inverse". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData wand_rays_inverse = IconData(0xf891, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>wand_stars</i> &#x2014; Cupertino icon named "wand_stars". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData wand_stars = IconData(0xf892, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>wand_stars_inverse</i> &#x2014; Cupertino icon named "wand_stars_inverse". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData wand_stars_inverse = IconData(0xf893, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>waveform</i> &#x2014; Cupertino icon named "waveform". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData waveform = IconData(0xf894, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>waveform_circle</i> &#x2014; Cupertino icon named "waveform_circle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData waveform_circle = IconData(0xf895, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>waveform_circle_fill</i> &#x2014; Cupertino icon named "waveform_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData waveform_circle_fill = IconData(0xf896, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>waveform_path</i> &#x2014; Cupertino icon named "waveform_path". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData waveform_path = IconData(0xf897, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>waveform_path_badge_minus</i> &#x2014; Cupertino icon named "waveform_path_badge_minus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData waveform_path_badge_minus = IconData(0xf898, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>waveform_path_badge_plus</i> &#x2014; Cupertino icon named "waveform_path_badge_plus". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData waveform_path_badge_plus = IconData(0xf899, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>waveform_path_ecg</i> &#x2014; Cupertino icon named "waveform_path_ecg". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData waveform_path_ecg = IconData(0xf89a, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>wifi</i> &#x2014; Cupertino icon named "wifi". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData wifi = IconData(0xf89b, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>wifi_exclamationmark</i> &#x2014; Cupertino icon named "wifi_exclamationmark". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData wifi_exclamationmark = IconData(0xf89c, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>wifi_slash</i> &#x2014; Cupertino icon named "wifi_slash". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData wifi_slash = IconData(0xf89d, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>wind</i> &#x2014; Cupertino icon named "wind". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData wind = IconData(0xf89e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>wind_snow</i> &#x2014; Cupertino icon named "wind_snow". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData wind_snow = IconData(0xf89f, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>wrench</i> &#x2014; Cupertino icon named "wrench". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData wrench = IconData(0xf8a0, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>wrench_fill</i> &#x2014; Cupertino icon named "wrench_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData wrench_fill = IconData(0xf8a1, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>xmark</i> &#x2014; Cupertino icon named "xmark". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [clear_thick] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [clear] which is available in cupertino_icons 0.1.3.
+  static const IconData xmark = IconData(0xf404, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>xmark_circle</i> &#x2014; Cupertino icon named "xmark_circle". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [clear_circled] which is available in cupertino_icons 0.1.3.
+  static const IconData xmark_circle = IconData(0xf405, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>xmark_circle_fill</i> &#x2014; Cupertino icon named "xmark_circle_fill". Available on cupertino_icons package 1.0.0+ only.
+  /// This is the same icon as [clear_thick_circled] which is available in cupertino_icons 0.1.3.
+  /// This is the same icon as [clear_circled_solid] which is available in cupertino_icons 0.1.3.
+  static const IconData xmark_circle_fill = IconData(0xf36e, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>xmark_octagon</i> &#x2014; Cupertino icon named "xmark_octagon". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData xmark_octagon = IconData(0xf8a2, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>xmark_octagon_fill</i> &#x2014; Cupertino icon named "xmark_octagon_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData xmark_octagon_fill = IconData(0xf8a3, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>xmark_rectangle</i> &#x2014; Cupertino icon named "xmark_rectangle". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData xmark_rectangle = IconData(0xf8a4, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>xmark_rectangle_fill</i> &#x2014; Cupertino icon named "xmark_rectangle_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData xmark_rectangle_fill = IconData(0xf8a5, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>xmark_seal</i> &#x2014; Cupertino icon named "xmark_seal". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData xmark_seal = IconData(0xf8a6, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>xmark_seal_fill</i> &#x2014; Cupertino icon named "xmark_seal_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData xmark_seal_fill = IconData(0xf8a7, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>xmark_shield</i> &#x2014; Cupertino icon named "xmark_shield". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData xmark_shield = IconData(0xf8a8, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>xmark_shield_fill</i> &#x2014; Cupertino icon named "xmark_shield_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData xmark_shield_fill = IconData(0xf8a9, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>xmark_square</i> &#x2014; Cupertino icon named "xmark_square". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData xmark_square = IconData(0xf8aa, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>xmark_square_fill</i> &#x2014; Cupertino icon named "xmark_square_fill". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData xmark_square_fill = IconData(0xf8ab, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>zoom_in</i> &#x2014; Cupertino icon named "zoom_in". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData zoom_in = IconData(0xf8ac, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>zoom_out</i> &#x2014; Cupertino icon named "zoom_out". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData zoom_out = IconData(0xf8ad, fontFamily: iconFont, fontPackage: iconFontPackage);
+  /// <i class='cupertino-icons md-36'>zzz</i> &#x2014; Cupertino icon named "zzz". Available on cupertino_icons package 1.0.0+ only.
+  static const IconData zzz = IconData(0xf8ae, fontFamily: iconFont, fontPackage: iconFontPackage);
+  // END GENERATED SF SYMBOLS NAMES
+  // ===========================================================================
+}
diff --git a/lib/src/cupertino/interface_level.dart b/lib/src/cupertino/interface_level.dart
new file mode 100644
index 0000000..bbdba91
--- /dev/null
+++ b/lib/src/cupertino/interface_level.dart
@@ -0,0 +1,107 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import '../widgets/framework.dart';
+
+/// Indicates the visual level for a piece of content. Equivalent to `UIUserInterfaceLevel`
+/// from `UIKit`.
+///
+/// See also:
+///
+///  * `UIUserInterfaceLevel`, the UIKit equivalent: https://developer.apple.com/documentation/uikit/uiuserinterfacelevel.
+enum CupertinoUserInterfaceLevelData {
+  /// The level for your window's main content.
+  base,
+
+  /// The level for content visually above [base].
+  elevated,
+}
+
+/// Establishes a subtree in which [CupertinoUserInterfaceLevel.of] resolves to
+/// the given data.
+///
+/// Querying the current elevation status using [CupertinoUserInterfaceLevel.of]
+/// will cause your widget to rebuild automatically whenever the [CupertinoUserInterfaceLevelData]
+/// changes.
+///
+/// If no [CupertinoUserInterfaceLevel] is in scope then the [CupertinoUserInterfaceLevel.of]
+/// method will throw an exception, unless the `nullOk` argument is set to true,
+/// in which case it returns null.
+///
+/// See also:
+///
+///  * [CupertinoUserInterfaceLevelData], specifies the visual level for the content
+///    in the subtree [CupertinoUserInterfaceLevel] established.
+class CupertinoUserInterfaceLevel extends InheritedWidget {
+  /// Creates a [CupertinoUserInterfaceLevel] to change descendant Cupertino widget's
+  /// visual level.
+  const CupertinoUserInterfaceLevel({
+    Key? key,
+    required CupertinoUserInterfaceLevelData data,
+    required Widget child,
+  }) : assert(data != null),
+      _data = data,
+      super(key: key, child: child);
+
+  final CupertinoUserInterfaceLevelData _data;
+
+  @override
+  bool updateShouldNotify(CupertinoUserInterfaceLevel oldWidget) => oldWidget._data != _data;
+
+  /// The data from the closest instance of this class that encloses the given
+  /// context.
+  ///
+  /// You can use this function to query the user interface elevation level within
+  /// the given [BuildContext]. When that information changes, your widget will
+  /// be scheduled to be rebuilt, keeping your widget up-to-date.
+  ///
+  /// See also:
+  ///
+  ///  * [maybeOf], which is similar, but will return null if no
+  ///    [CupertinoUserInterfaceLevel] encloses the given context.
+  static CupertinoUserInterfaceLevelData of(BuildContext context) {
+    assert(context != null);
+    final CupertinoUserInterfaceLevel? query = context.dependOnInheritedWidgetOfExactType<CupertinoUserInterfaceLevel>();
+    if (query != null)
+      return query._data;
+    throw FlutterError(
+      'CupertinoUserInterfaceLevel.of() called with a context that does not contain a CupertinoUserInterfaceLevel.\n'
+      'No CupertinoUserInterfaceLevel ancestor could be found starting from the context that was passed '
+      'to CupertinoUserInterfaceLevel.of(). This can happen because you do not have a WidgetsApp or '
+      'MaterialApp widget (those widgets introduce a CupertinoUserInterfaceLevel), or it can happen '
+      'if the context you use comes from a widget above those widgets.\n'
+      'The context used was:\n'
+      '  $context'
+    );
+  }
+
+  /// The data from the closest instance of this class that encloses the given
+  /// context, if there is one.
+  ///
+  /// Returns null if no [CupertinoUserInterfaceLevel] encloses the given context.
+  ///
+  /// You can use this function to query the user interface elevation level within
+  /// the given [BuildContext]. When that information changes, your widget will
+  /// be scheduled to be rebuilt, keeping your widget up-to-date.
+  ///
+  /// See also:
+  ///
+  ///  * [of], which is similar, but will throw an exception if no
+  ///    [CupertinoUserInterfaceLevel] encloses the given context.
+  static CupertinoUserInterfaceLevelData? maybeOf(BuildContext context) {
+    assert(context != null);
+    final CupertinoUserInterfaceLevel? query = context.dependOnInheritedWidgetOfExactType<CupertinoUserInterfaceLevel>();
+    if (query != null)
+      return query._data;
+    return null;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<CupertinoUserInterfaceLevelData>('user interface level', _data));
+  }
+}
diff --git a/lib/src/cupertino/localizations.dart b/lib/src/cupertino/localizations.dart
new file mode 100644
index 0000000..6af3c0a
--- /dev/null
+++ b/lib/src/cupertino/localizations.dart
@@ -0,0 +1,449 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'debug.dart';
+
+/// Determines the order of the columns inside [CupertinoDatePicker] in
+/// time and date time mode.
+enum DatePickerDateTimeOrder {
+  /// Order of the columns, from left to right: date, hour, minute, am/pm.
+  ///
+  /// Example: Fri Aug 31 | 02 | 08 | PM.
+  date_time_dayPeriod,
+  /// Order of the columns, from left to right: date, am/pm, hour, minute.
+  ///
+  /// Example: Fri Aug 31 | PM | 02 | 08.
+  date_dayPeriod_time,
+  /// Order of the columns, from left to right: hour, minute, am/pm, date.
+  ///
+  /// Example: 02 | 08 | PM | Fri Aug 31.
+  time_dayPeriod_date,
+  /// Order of the columns, from left to right: am/pm, hour, minute, date.
+  ///
+  /// Example: PM | 02 | 08 | Fri Aug 31.
+  dayPeriod_time_date,
+}
+
+/// Determines the order of the columns inside [CupertinoDatePicker] in date mode.
+enum DatePickerDateOrder {
+  /// Order of the columns, from left to right: day, month, year.
+  ///
+  /// Example: 12 | March | 1996.
+  dmy,
+  /// Order of the columns, from left to right: month, day, year.
+  ///
+  /// Example: March | 12 | 1996.
+  mdy,
+  /// Order of the columns, from left to right: year, month, day.
+  ///
+  /// Example: 1996 | March | 12.
+  ymd,
+  /// Order of the columns, from left to right: year, day, month.
+  ///
+  /// Example: 1996 | 12 | March.
+  ydm,
+}
+
+/// Defines the localized resource values used by the Cupertino widgets.
+///
+/// See also:
+///
+///  * [DefaultCupertinoLocalizations], the default, English-only, implementation
+///    of this interface.
+// TODO(xster): Supply non-english strings.
+abstract class CupertinoLocalizations {
+  /// Year that is shown in [CupertinoDatePicker] spinner corresponding to the
+  /// given year index.
+  ///
+  /// Examples: datePickerYear(1) in:
+  ///
+  ///  - US English: 2018
+  ///  - Korean: 2018년
+  // The global version uses date symbols data from the intl package.
+  String datePickerYear(int yearIndex);
+
+  /// Month that is shown in [CupertinoDatePicker] spinner corresponding to
+  /// the given month index.
+  ///
+  /// Examples: datePickerMonth(1) in:
+  ///
+  ///  - US English: January
+  ///  - Korean: 1월
+  // The global version uses date symbols data from the intl package.
+  String datePickerMonth(int monthIndex);
+
+  /// Day of month that is shown in [CupertinoDatePicker] spinner corresponding
+  /// to the given day index.
+  ///
+  /// Examples: datePickerDayOfMonth(1) in:
+  ///
+  ///  - US English: 1
+  ///  - Korean: 1일
+  // The global version uses date symbols data from the intl package.
+  String datePickerDayOfMonth(int dayIndex);
+
+  /// The medium-width date format that is shown in [CupertinoDatePicker]
+  /// spinner. Abbreviates month and days of week.
+  ///
+  /// Examples:
+  ///
+  /// - US English: Wed Sep 27
+  /// - Russian: ср сент. 27
+  // The global version is based on intl package's DateFormat.MMMEd.
+  String datePickerMediumDate(DateTime date);
+
+  /// Hour that is shown in [CupertinoDatePicker] spinner corresponding
+  /// to the given hour value.
+  ///
+  /// Examples: datePickerHour(1) in:
+  ///
+  ///  - US English: 1
+  ///  - Arabic: ٠١
+  // The global version uses date symbols data from the intl package.
+  String datePickerHour(int hour);
+
+  /// Semantics label for the given hour value in [CupertinoDatePicker].
+  // The global version uses the translated string from the arb file.
+  String? datePickerHourSemanticsLabel(int hour);
+
+  /// Minute that is shown in [CupertinoDatePicker] spinner corresponding
+  /// to the given minute value.
+  ///
+  /// Examples: datePickerMinute(1) in:
+  ///
+  ///  - US English: 01
+  ///  - Arabic: ٠١
+  // The global version uses date symbols data from the intl package.
+  String datePickerMinute(int minute);
+
+  /// Semantics label for the given minute value in [CupertinoDatePicker].
+  // The global version uses the translated string from the arb file.
+  String? datePickerMinuteSemanticsLabel(int minute);
+
+  /// The order of the date elements that will be shown in [CupertinoDatePicker].
+  // The global version uses the translated string from the arb file.
+  DatePickerDateOrder get datePickerDateOrder;
+
+  /// The order of the time elements that will be shown in [CupertinoDatePicker].
+  // The global version uses the translated string from the arb file.
+  DatePickerDateTimeOrder get datePickerDateTimeOrder;
+
+  /// The abbreviation for ante meridiem (before noon) shown in the time picker.
+  // The global version uses the translated string from the arb file.
+  String get anteMeridiemAbbreviation;
+
+  /// The abbreviation for post meridiem (after noon) shown in the time picker.
+  // The global version uses the translated string from the arb file.
+  String get postMeridiemAbbreviation;
+
+  /// Label shown in date pickers when the date is today.
+  // The global version uses the translated string from the arb file.
+  String get todayLabel;
+
+  /// The term used by the system to announce dialog alerts.
+  // The global version uses the translated string from the arb file.
+  String get alertDialogLabel;
+
+  /// The accessibility label used on a tab in a [CupertinoTabBar].
+  ///
+  /// This message describes the index of the selected tab and how many tabs
+  /// there are, e.g. 'tab, 1 of 2' in United States English.
+  ///
+  /// `tabIndex` and `tabCount` must be greater than or equal to one.
+  String tabSemanticsLabel({required int tabIndex, required int tabCount});
+
+  /// Hour that is shown in [CupertinoTimerPicker] corresponding to
+  /// the given hour value.
+  ///
+  /// Examples: timerPickerHour(1) in:
+  ///
+  ///  - US English: 1
+  ///  - Arabic: ١
+  // The global version uses date symbols data from the intl package.
+  String timerPickerHour(int hour);
+
+  /// Minute that is shown in [CupertinoTimerPicker] corresponding to
+  /// the given minute value.
+  ///
+  /// Examples: timerPickerMinute(1) in:
+  ///
+  ///  - US English: 1
+  ///  - Arabic: ١
+  // The global version uses date symbols data from the intl package.
+  String timerPickerMinute(int minute);
+
+  /// Second that is shown in [CupertinoTimerPicker] corresponding to
+  /// the given second value.
+  ///
+  /// Examples: timerPickerSecond(1) in:
+  ///
+  ///  - US English: 1
+  ///  - Arabic: ١
+  // The global version uses date symbols data from the intl package.
+  String timerPickerSecond(int second);
+
+  /// Label that appears next to the hour picker in
+  /// [CupertinoTimerPicker] when selected hour value is `hour`.
+  /// This function will deal with pluralization based on the `hour` parameter.
+  // The global version uses the translated string from the arb file.
+  String? timerPickerHourLabel(int hour);
+
+  /// All possible hour labels that appears next to the hour picker in
+  /// [CupertinoTimerPicker]
+  List<String> get timerPickerHourLabels;
+
+  /// Label that appears next to the minute picker in
+  /// [CupertinoTimerPicker] when selected minute value is `minute`.
+  /// This function will deal with pluralization based on the `minute` parameter.
+  // The global version uses the translated string from the arb file.
+  String? timerPickerMinuteLabel(int minute);
+
+  /// All possible minute labels that appears next to the minute picker in
+  /// [CupertinoTimerPicker]
+  List<String> get timerPickerMinuteLabels;
+
+  /// Label that appears next to the minute picker in
+  /// [CupertinoTimerPicker] when selected minute value is `second`.
+  /// This function will deal with pluralization based on the `second` parameter.
+  // The global version uses the translated string from the arb file.
+  String? timerPickerSecondLabel(int second);
+
+  /// All possible second labels that appears next to the second picker in
+  /// [CupertinoTimerPicker]
+  List<String> get timerPickerSecondLabels;
+
+  /// The term used for cutting.
+  // The global version uses the translated string from the arb file.
+  String get cutButtonLabel;
+
+  /// The term used for copying.
+  // The global version uses the translated string from the arb file.
+  String get copyButtonLabel;
+
+  /// The term used for pasting.
+  // The global version uses the translated string from the arb file.
+  String get pasteButtonLabel;
+
+  /// The term used for selecting everything.
+  // The global version uses the translated string from the arb file.
+  String get selectAllButtonLabel;
+
+  /// The default placeholder used in [CupertinoSearchTextField].
+  // The global version uses the translated string from the arb file.
+  String get searchTextFieldPlaceholderLabel;
+
+  /// Label read out by accessibility tools (VoiceOver) for a modal
+  /// barrier to indicate that a tap dismisses the barrier.
+  ///
+  /// A modal barrier can for example be found behind an alert or popup to block
+  /// user interaction with elements behind it.
+  String get modalBarrierDismissLabel;
+
+  /// The `CupertinoLocalizations` from the closest [Localizations] instance
+  /// that encloses the given context.
+  ///
+  /// If no [CupertinoLocalizations] are available in the given `context`, this
+  /// method throws an exception.
+  ///
+  /// This method is just a convenient shorthand for:
+  /// `Localizations.of<CupertinoLocalizations>(context, CupertinoLocalizations)!`.
+  ///
+  /// References to the localized resources defined by this class are typically
+  /// written in terms of this method. For example:
+  ///
+  /// ```dart
+  /// CupertinoLocalizations.of(context).anteMeridiemAbbreviation;
+  /// ```
+  static CupertinoLocalizations of(BuildContext context) {
+    assert(debugCheckHasCupertinoLocalizations(context));
+    return Localizations.of<CupertinoLocalizations>(context, CupertinoLocalizations)!;
+  }
+}
+
+class _CupertinoLocalizationsDelegate extends LocalizationsDelegate<CupertinoLocalizations> {
+  const _CupertinoLocalizationsDelegate();
+
+  @override
+  bool isSupported(Locale locale) => locale.languageCode == 'en';
+
+  @override
+  Future<CupertinoLocalizations> load(Locale locale) => DefaultCupertinoLocalizations.load(locale);
+
+  @override
+  bool shouldReload(_CupertinoLocalizationsDelegate old) => false;
+
+  @override
+  String toString() => 'DefaultCupertinoLocalizations.delegate(en_US)';
+}
+
+/// US English strings for the Cupertino widgets.
+class DefaultCupertinoLocalizations implements CupertinoLocalizations {
+  /// Constructs an object that defines the cupertino widgets' localized strings
+  /// for US English (only).
+  ///
+  /// [LocalizationsDelegate] implementations typically call the static [load]
+  /// function, rather than constructing this class directly.
+  const DefaultCupertinoLocalizations();
+
+  static const List<String> _shortWeekdays = <String>[
+    'Mon',
+    'Tue',
+    'Wed',
+    'Thu',
+    'Fri',
+    'Sat',
+    'Sun',
+  ];
+
+  static const List<String> _shortMonths = <String>[
+    'Jan',
+    'Feb',
+    'Mar',
+    'Apr',
+    'May',
+    'Jun',
+    'Jul',
+    'Aug',
+    'Sep',
+    'Oct',
+    'Nov',
+    'Dec',
+  ];
+
+  static const List<String> _months = <String>[
+    'January',
+    'February',
+    'March',
+    'April',
+    'May',
+    'June',
+    'July',
+    'August',
+    'September',
+    'October',
+    'November',
+    'December',
+  ];
+
+
+
+  @override
+  String datePickerYear(int yearIndex) => yearIndex.toString();
+
+  @override
+  String datePickerMonth(int monthIndex) => _months[monthIndex - 1];
+
+  @override
+  String datePickerDayOfMonth(int dayIndex) => dayIndex.toString();
+
+  @override
+  String datePickerHour(int hour) => hour.toString();
+
+  @override
+  String datePickerHourSemanticsLabel(int hour) => hour.toString() + " o'clock";
+
+  @override
+  String datePickerMinute(int minute) => minute.toString().padLeft(2, '0');
+
+  @override
+  String datePickerMinuteSemanticsLabel(int minute) {
+    if (minute == 1)
+      return '1 minute';
+    return minute.toString() + ' minutes';
+  }
+
+  @override
+  String datePickerMediumDate(DateTime date) {
+    return '${_shortWeekdays[date.weekday - DateTime.monday]} '
+      '${_shortMonths[date.month - DateTime.january]} '
+      '${date.day.toString().padRight(2)}';
+  }
+
+  @override
+  DatePickerDateOrder get datePickerDateOrder => DatePickerDateOrder.mdy;
+
+  @override
+  DatePickerDateTimeOrder get datePickerDateTimeOrder => DatePickerDateTimeOrder.date_time_dayPeriod;
+
+  @override
+  String get anteMeridiemAbbreviation => 'AM';
+
+  @override
+  String get postMeridiemAbbreviation => 'PM';
+
+  @override
+  String get todayLabel => 'Today';
+
+  @override
+  String get alertDialogLabel => 'Alert';
+
+  @override
+  String tabSemanticsLabel({required int tabIndex, required int tabCount}) {
+    assert(tabIndex >= 1);
+    assert(tabCount >= 1);
+    return 'Tab $tabIndex of $tabCount';
+  }
+
+  @override
+  String timerPickerHour(int hour) => hour.toString();
+
+  @override
+  String timerPickerMinute(int minute) => minute.toString();
+
+  @override
+  String timerPickerSecond(int second) => second.toString();
+
+  @override
+  String timerPickerHourLabel(int hour) => hour == 1 ? 'hour' : 'hours';
+
+  @override
+  List<String> get timerPickerHourLabels => const <String>['hour', 'hours'];
+
+  @override
+  String timerPickerMinuteLabel(int minute) => 'min.';
+
+  @override
+  List<String> get timerPickerMinuteLabels => const <String>['min.'];
+
+  @override
+  String timerPickerSecondLabel(int second) => 'sec.';
+
+  @override
+  List<String> get timerPickerSecondLabels => const <String>['sec.'];
+
+  @override
+  String get cutButtonLabel => 'Cut';
+
+  @override
+  String get copyButtonLabel => 'Copy';
+
+  @override
+  String get pasteButtonLabel => 'Paste';
+
+  @override
+  String get selectAllButtonLabel => 'Select All';
+
+  @override
+  String get searchTextFieldPlaceholderLabel => 'Search';
+
+  @override
+  String get modalBarrierDismissLabel => 'Dismiss';
+
+  /// Creates an object that provides US English resource values for the
+  /// cupertino library widgets.
+  ///
+  /// The [locale] parameter is ignored.
+  ///
+  /// This method is typically used to create a [LocalizationsDelegate].
+  static Future<CupertinoLocalizations> load(Locale locale) {
+    return SynchronousFuture<CupertinoLocalizations>(const DefaultCupertinoLocalizations());
+  }
+
+  /// A [LocalizationsDelegate] that uses [DefaultCupertinoLocalizations.load]
+  /// to create an instance of this class.
+  static const LocalizationsDelegate<CupertinoLocalizations> delegate = _CupertinoLocalizationsDelegate();
+}
diff --git a/lib/src/cupertino/nav_bar.dart b/lib/src/cupertino/nav_bar.dart
new file mode 100644
index 0000000..681cb7c
--- /dev/null
+++ b/lib/src/cupertino/nav_bar.dart
@@ -0,0 +1,2341 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show ImageFilter;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'button.dart';
+import 'colors.dart';
+import 'constants.dart';
+import 'icons.dart';
+import 'page_scaffold.dart';
+import 'route.dart';
+import 'theme.dart';
+
+/// Standard iOS navigation bar height without the status bar.
+///
+/// This height is constant and independent of accessibility as it is in iOS.
+const double _kNavBarPersistentHeight = kMinInteractiveDimensionCupertino;
+
+/// Size increase from expanding the navigation bar into an iOS-11-style large title
+/// form in a [CustomScrollView].
+const double _kNavBarLargeTitleHeightExtension = 52.0;
+
+/// Number of logical pixels scrolled down before the title text is transferred
+/// from the normal navigation bar to a big title below the navigation bar.
+const double _kNavBarShowLargeTitleThreshold = 10.0;
+
+const double _kNavBarEdgePadding = 16.0;
+
+const double _kNavBarBackButtonTapWidth = 50.0;
+
+/// Title text transfer fade.
+const Duration _kNavBarTitleFadeDuration = Duration(milliseconds: 150);
+
+const Color _kDefaultNavBarBorderColor = Color(0x4D000000);
+
+const Border _kDefaultNavBarBorder = Border(
+  bottom: BorderSide(
+    color: _kDefaultNavBarBorderColor,
+    width: 0.0, // One physical pixel.
+    style: BorderStyle.solid,
+  ),
+);
+
+// There's a single tag for all instances of navigation bars because they can
+// all transition between each other (per Navigator) via Hero transitions.
+const _HeroTag _defaultHeroTag = _HeroTag(null);
+
+@immutable
+class _HeroTag {
+  const _HeroTag(this.navigator);
+
+  final NavigatorState? navigator;
+
+  // Let the Hero tag be described in tree dumps.
+  @override
+  String toString() => 'Default Hero tag for Cupertino navigation bars with navigator $navigator';
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is _HeroTag
+        && other.navigator == navigator;
+  }
+
+  @override
+  int get hashCode {
+    return identityHashCode(navigator);
+  }
+}
+
+/// Returns `child` wrapped with background and a bottom border if background color
+/// is opaque. Otherwise, also blur with [BackdropFilter].
+///
+/// When `updateSystemUiOverlay` is true, the nav bar will update the OS
+/// status bar's color theme based on the background color of the nav bar.
+Widget _wrapWithBackground({
+  Border? border,
+  required Color backgroundColor,
+  Brightness? brightness,
+  required Widget child,
+  bool updateSystemUiOverlay = true,
+}) {
+  Widget result = child;
+  if (updateSystemUiOverlay) {
+    final bool isDark = backgroundColor.computeLuminance() < 0.179;
+    final Brightness newBrightness = brightness ?? (isDark ? Brightness.dark : Brightness.light);
+    final SystemUiOverlayStyle overlayStyle;
+    switch (newBrightness) {
+      case Brightness.dark:
+        overlayStyle = SystemUiOverlayStyle.light;
+        break;
+      case Brightness.light:
+        overlayStyle = SystemUiOverlayStyle.dark;
+        break;
+    }
+    result = AnnotatedRegion<SystemUiOverlayStyle>(
+      value: overlayStyle,
+      sized: true,
+      child: result,
+    );
+  }
+  final DecoratedBox childWithBackground = DecoratedBox(
+    decoration: BoxDecoration(
+      border: border,
+      color: backgroundColor,
+    ),
+    child: result,
+  );
+
+  if (backgroundColor.alpha == 0xFF)
+    return childWithBackground;
+
+  return ClipRect(
+    child: BackdropFilter(
+      filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
+      child: childWithBackground,
+    ),
+  );
+}
+
+// This exists to support backward compatibility with arguments like
+// `actionsForegroundColor`. CupertinoThemes can be used to support these
+// scenarios now. To support `actionsForegroundColor`, the nav bar rewraps
+// its children with a CupertinoTheme.
+Widget _wrapActiveColor(Color? color, BuildContext context, Widget child) {
+  if (color == null) {
+    return child;
+  }
+
+  return CupertinoTheme(
+    data: CupertinoTheme.of(context).copyWith(primaryColor: color),
+    child: child,
+  );
+}
+
+// Whether the current route supports nav bar hero transitions from or to.
+bool _isTransitionable(BuildContext context) {
+  final ModalRoute<dynamic>? route = ModalRoute.of(context);
+
+  // Fullscreen dialogs never transitions their nav bar with other push-style
+  // pages' nav bars or with other fullscreen dialog pages on the way in or on
+  // the way out.
+  return route is PageRoute && !route.fullscreenDialog;
+}
+
+/// An iOS-styled navigation bar.
+///
+/// The navigation bar is a toolbar that minimally consists of a widget, normally
+/// a page title, in the [middle] of the toolbar.
+///
+/// It also supports a [leading] and [trailing] widget before and after the
+/// [middle] widget while keeping the [middle] widget centered.
+///
+/// The [leading] widget will automatically be a back chevron icon button (or a
+/// close button in case of a fullscreen dialog) to pop the current route if none
+/// is provided and [automaticallyImplyLeading] is true (true by default).
+///
+/// The [middle] widget will automatically be a title text from the current
+/// [CupertinoPageRoute] if none is provided and [automaticallyImplyMiddle] is
+/// true (true by default).
+///
+/// It should be placed at top of the screen and automatically accounts for
+/// the OS's status bar.
+///
+/// If the given [backgroundColor]'s opacity is not 1.0 (which is the case by
+/// default), it will produce a blurring effect to the content behind it.
+///
+/// When [transitionBetweenRoutes] is true, this navigation bar will transition
+/// on top of the routes instead of inside them if the route being transitioned
+/// to also has a [CupertinoNavigationBar] or a [CupertinoSliverNavigationBar]
+/// with [transitionBetweenRoutes] set to true. If [transitionBetweenRoutes] is
+/// true, none of the [Widget] parameters can contain a key in its subtree since
+/// that widget will exist in multiple places in the tree simultaneously.
+///
+/// By default, only one [CupertinoNavigationBar] or [CupertinoSliverNavigationBar]
+/// should be present in each [PageRoute] to support the default transitions.
+/// Use [transitionBetweenRoutes] or [heroTag] to customize the transition
+/// behavior for multiple navigation bars per route.
+///
+/// When used in a [CupertinoPageScaffold], [CupertinoPageScaffold.navigationBar]
+/// has its text scale factor set to 1.0 and does not respond to text scale factor
+/// changes from the operating system, to match the native iOS behavior. To override
+/// this behavior, wrap each of the `navigationBar`'s components inside a [MediaQuery]
+/// with the desired [MediaQueryData.textScaleFactor] value. The text scale factor
+/// value from the operating system can be retrieved in many ways, such as querying
+/// [MediaQuery.textScaleFactorOf] against [CupertinoApp]'s [BuildContext].
+///
+/// See also:
+///
+///  * [CupertinoPageScaffold], a page layout helper typically hosting the
+///    [CupertinoNavigationBar].
+///  * [CupertinoSliverNavigationBar] for a navigation bar to be placed in a
+///    scrolling list and that supports iOS-11-style large titles.
+class CupertinoNavigationBar extends StatefulWidget implements ObstructingPreferredSizeWidget {
+  /// Creates a navigation bar in the iOS style.
+  const CupertinoNavigationBar({
+    Key? key,
+    this.leading,
+    this.automaticallyImplyLeading = true,
+    this.automaticallyImplyMiddle = true,
+    this.previousPageTitle,
+    this.middle,
+    this.trailing,
+    this.border = _kDefaultNavBarBorder,
+    this.backgroundColor,
+    this.brightness,
+    this.padding,
+    this.actionsForegroundColor,
+    this.transitionBetweenRoutes = true,
+    this.heroTag = _defaultHeroTag,
+  }) : assert(automaticallyImplyLeading != null),
+       assert(automaticallyImplyMiddle != null),
+       assert(transitionBetweenRoutes != null),
+       assert(
+         heroTag != null,
+         'heroTag cannot be null. Use transitionBetweenRoutes = false to '
+         'disable Hero transition on this navigation bar.'
+       ),
+       assert(
+         !transitionBetweenRoutes || identical(heroTag, _defaultHeroTag),
+         'Cannot specify a heroTag override if this navigation bar does not '
+         'transition due to transitionBetweenRoutes = false.'
+       ),
+       super(key: key);
+
+  /// {@template flutter.cupertino.CupertinoNavigationBar.leading}
+  /// Widget to place at the start of the navigation bar. Normally a back button
+  /// for a normal page or a cancel button for full page dialogs.
+  ///
+  /// If null and [automaticallyImplyLeading] is true, an appropriate button
+  /// will be automatically created.
+  /// {@endtemplate}
+  final Widget? leading;
+
+  /// {@template flutter.cupertino.CupertinoNavigationBar.automaticallyImplyLeading}
+  /// Controls whether we should try to imply the leading widget if null.
+  ///
+  /// If true and [leading] is null, automatically try to deduce what the [leading]
+  /// widget should be. If [leading] widget is not null, this parameter has no effect.
+  ///
+  /// Specifically this navigation bar will:
+  ///
+  /// 1. Show a 'Close' button if the current route is a `fullscreenDialog`.
+  /// 2. Show a back chevron with [previousPageTitle] if [previousPageTitle] is
+  ///    not null.
+  /// 3. Show a back chevron with the previous route's `title` if the current
+  ///    route is a [CupertinoPageRoute] and the previous route is also a
+  ///    [CupertinoPageRoute].
+  ///
+  /// This value cannot be null.
+  /// {@endtemplate}
+  final bool automaticallyImplyLeading;
+
+  /// Controls whether we should try to imply the middle widget if null.
+  ///
+  /// If true and [middle] is null, automatically fill in a [Text] widget with
+  /// the current route's `title` if the route is a [CupertinoPageRoute].
+  /// If [middle] widget is not null, this parameter has no effect.
+  ///
+  /// This value cannot be null.
+  final bool automaticallyImplyMiddle;
+
+  /// {@template flutter.cupertino.CupertinoNavigationBar.previousPageTitle}
+  /// Manually specify the previous route's title when automatically implying
+  /// the leading back button.
+  ///
+  /// Overrides the text shown with the back chevron instead of automatically
+  /// showing the previous [CupertinoPageRoute]'s `title` when
+  /// [automaticallyImplyLeading] is true.
+  ///
+  /// Has no effect when [leading] is not null or if [automaticallyImplyLeading]
+  /// is false.
+  /// {@endtemplate}
+  final String? previousPageTitle;
+
+  /// Widget to place in the middle of the navigation bar. Normally a title or
+  /// a segmented control.
+  ///
+  /// If null and [automaticallyImplyMiddle] is true, an appropriate [Text]
+  /// title will be created if the current route is a [CupertinoPageRoute] and
+  /// has a `title`.
+  final Widget? middle;
+
+  /// {@template flutter.cupertino.CupertinoNavigationBar.trailing}
+  /// Widget to place at the end of the navigation bar. Normally additional actions
+  /// taken on the page such as a search or edit function.
+  /// {@endtemplate}
+  final Widget? trailing;
+
+  // TODO(xster): https://github.com/flutter/flutter/issues/10469 implement
+  // support for double row navigation bars.
+
+  /// {@template flutter.cupertino.CupertinoNavigationBar.backgroundColor}
+  /// The background color of the navigation bar. If it contains transparency, the
+  /// tab bar will automatically produce a blurring effect to the content
+  /// behind it.
+  ///
+  /// Defaults to [CupertinoTheme]'s `barBackgroundColor` if null.
+  /// {@endtemplate}
+  final Color? backgroundColor;
+
+  /// {@template flutter.cupertino.CupertinoNavigationBar.brightness}
+  /// The brightness of the specified [backgroundColor].
+  ///
+  /// Setting this value changes the style of the system status bar. Typically
+  /// used to increase the contrast ratio of the system status bar over
+  /// [backgroundColor].
+  ///
+  /// If set to null, the value of the property will be inferred from the relative
+  /// luminance of [backgroundColor].
+  /// {@endtemplate}
+  final Brightness? brightness;
+
+  /// {@template flutter.cupertino.CupertinoNavigationBar.padding}
+  /// Padding for the contents of the navigation bar.
+  ///
+  /// If null, the navigation bar will adopt the following defaults:
+  ///
+  ///  * Vertically, contents will be sized to the same height as the navigation
+  ///    bar itself minus the status bar.
+  ///  * Horizontally, padding will be 16 pixels according to iOS specifications
+  ///    unless the leading widget is an automatically inserted back button, in
+  ///    which case the padding will be 0.
+  ///
+  /// Vertical padding won't change the height of the nav bar.
+  /// {@endtemplate}
+  final EdgeInsetsDirectional? padding;
+
+  /// {@template flutter.cupertino.CupertinoNavigationBar.border}
+  /// The border of the navigation bar. By default renders a single pixel bottom border side.
+  ///
+  /// If a border is null, the navigation bar will not display a border.
+  /// {@endtemplate}
+  final Border? border;
+
+  /// {@template flutter.cupertino.CupertinoNavigationBar.actionsForegroundColor}
+  /// Default color used for text and icons of the [leading] and [trailing]
+  /// widgets in the navigation bar.
+  ///
+  /// Defaults to the `primaryColor` of the [CupertinoTheme] when null.
+  /// {@endtemplate}
+  ///
+  /// The default color for text in the [middle] slot is always black, as per
+  /// iOS standard design.
+  @Deprecated(
+    'Use CupertinoTheme and primaryColor to propagate color. '
+    'This feature was deprecated after v1.1.2.'
+  )
+  final Color? actionsForegroundColor;
+
+  /// {@template flutter.cupertino.CupertinoNavigationBar.transitionBetweenRoutes}
+  /// Whether to transition between navigation bars.
+  ///
+  /// When [transitionBetweenRoutes] is true, this navigation bar will transition
+  /// on top of the routes instead of inside it if the route being transitioned
+  /// to also has a [CupertinoNavigationBar] or a [CupertinoSliverNavigationBar]
+  /// with [transitionBetweenRoutes] set to true.
+  ///
+  /// This transition will also occur on edge back swipe gestures like on iOS
+  /// but only if the previous page below has `maintainState` set to true on the
+  /// [PageRoute].
+  ///
+  /// When set to true, only one navigation bar can be present per route unless
+  /// [heroTag] is also set.
+  ///
+  /// This value defaults to true and cannot be null.
+  /// {@endtemplate}
+  final bool transitionBetweenRoutes;
+
+  /// {@template flutter.cupertino.CupertinoNavigationBar.heroTag}
+  /// Tag for the navigation bar's Hero widget if [transitionBetweenRoutes] is true.
+  ///
+  /// Defaults to a common tag between all [CupertinoNavigationBar] and
+  /// [CupertinoSliverNavigationBar] instances of the same [Navigator]. With the
+  /// default tag, all navigation bars of the same navigator can transition
+  /// between each other as long as there's only one navigation bar per route.
+  ///
+  /// This [heroTag] can be overridden to manually handle having multiple
+  /// navigation bars per route or to transition between multiple
+  /// [Navigator]s.
+  ///
+  /// Cannot be null. To disable Hero transitions for this navigation bar,
+  /// set [transitionBetweenRoutes] to false.
+  /// {@endtemplate}
+  final Object heroTag;
+
+  /// True if the navigation bar's background color has no transparency.
+  @override
+  bool shouldFullyObstruct(BuildContext context) {
+    final Color backgroundColor = CupertinoDynamicColor.maybeResolve(this.backgroundColor, context)
+                               ?? CupertinoTheme.of(context).barBackgroundColor;
+    return backgroundColor.alpha == 0xFF;
+  }
+
+  @override
+  Size get preferredSize {
+    return const Size.fromHeight(_kNavBarPersistentHeight);
+  }
+
+  @override
+  _CupertinoNavigationBarState createState() {
+    return _CupertinoNavigationBarState();
+  }
+}
+
+// A state class exists for the nav bar so that the keys of its sub-components
+// don't change when rebuilding the nav bar, causing the sub-components to
+// lose their own states.
+class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
+  late _NavigationBarStaticComponentsKeys keys;
+
+  @override
+  void initState() {
+    super.initState();
+    keys = _NavigationBarStaticComponentsKeys();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final Color backgroundColor =
+      CupertinoDynamicColor.maybeResolve(widget.backgroundColor, context) ?? CupertinoTheme.of(context).barBackgroundColor;
+
+    final _NavigationBarStaticComponents components = _NavigationBarStaticComponents(
+      keys: keys,
+      route: ModalRoute.of(context),
+      userLeading: widget.leading,
+      automaticallyImplyLeading: widget.automaticallyImplyLeading,
+      automaticallyImplyTitle: widget.automaticallyImplyMiddle,
+      previousPageTitle: widget.previousPageTitle,
+      userMiddle: widget.middle,
+      userTrailing: widget.trailing,
+      padding: widget.padding,
+      userLargeTitle: null,
+      large: false,
+    );
+
+    final Widget navBar = _wrapWithBackground(
+      border: widget.border,
+      backgroundColor: backgroundColor,
+      brightness: widget.brightness,
+      child: DefaultTextStyle(
+        style: CupertinoTheme.of(context).textTheme.textStyle,
+        child: _PersistentNavigationBar(
+          components: components,
+          padding: widget.padding,
+        ),
+      ),
+    );
+
+    final Color? actionsForegroundColor = CupertinoDynamicColor.maybeResolve(
+      widget.actionsForegroundColor,
+      context,
+    );
+    if (!widget.transitionBetweenRoutes || !_isTransitionable(context)) {
+      // Lint ignore to maintain backward compatibility.
+      return _wrapActiveColor(actionsForegroundColor, context, navBar);
+    }
+
+    return _wrapActiveColor(
+      // Lint ignore to maintain backward compatibility.
+      actionsForegroundColor,
+      context,
+      Builder(
+        // Get the context that might have a possibly changed CupertinoTheme.
+        builder: (BuildContext context) {
+          return Hero(
+            tag: widget.heroTag == _defaultHeroTag
+                ? _HeroTag(Navigator.of(context))
+                : widget.heroTag,
+            createRectTween: _linearTranslateWithLargestRectSizeTween,
+            placeholderBuilder: _navBarHeroLaunchPadBuilder,
+            flightShuttleBuilder: _navBarHeroFlightShuttleBuilder,
+            transitionOnUserGestures: true,
+            child: _TransitionableNavigationBar(
+              componentsKeys: keys,
+              backgroundColor: backgroundColor,
+              backButtonTextStyle: CupertinoTheme.of(context).textTheme.navActionTextStyle,
+              titleTextStyle: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
+              largeTitleTextStyle: null,
+              border: widget.border,
+              hasUserMiddle: widget.middle != null,
+              largeExpanded: false,
+              child: navBar,
+            ),
+          );
+        },
+      ),
+    );
+  }
+}
+
+/// An iOS-styled navigation bar with iOS-11-style large titles using slivers.
+///
+/// The [CupertinoSliverNavigationBar] must be placed in a sliver group such
+/// as the [CustomScrollView].
+///
+/// This navigation bar consists of two sections, a pinned static section on top
+/// and a sliding section containing iOS-11-style large title below it.
+///
+/// It should be placed at top of the screen and automatically accounts for
+/// the iOS status bar.
+///
+/// Minimally, a [largeTitle] widget will appear in the middle of the app bar
+/// when the sliver is collapsed and transfer to the area below in larger font
+/// when the sliver is expanded.
+///
+/// For advanced uses, an optional [middle] widget can be supplied to show a
+/// different widget in the middle of the navigation bar when the sliver is collapsed.
+///
+/// Like [CupertinoNavigationBar], it also supports a [leading] and [trailing]
+/// widget on the static section on top that remains while scrolling.
+///
+/// The [leading] widget will automatically be a back chevron icon button (or a
+/// close button in case of a fullscreen dialog) to pop the current route if none
+/// is provided and [automaticallyImplyLeading] is true (true by default).
+///
+/// The [largeTitle] widget will automatically be a title text from the current
+/// [CupertinoPageRoute] if none is provided and [automaticallyImplyTitle] is
+/// true (true by default).
+///
+/// When [transitionBetweenRoutes] is true, this navigation bar will transition
+/// on top of the routes instead of inside them if the route being transitioned
+/// to also has a [CupertinoNavigationBar] or a [CupertinoSliverNavigationBar]
+/// with [transitionBetweenRoutes] set to true. If [transitionBetweenRoutes] is
+/// true, none of the [Widget] parameters can contain any [GlobalKey]s in their
+/// subtrees since those widgets will exist in multiple places in the tree
+/// simultaneously.
+///
+/// By default, only one [CupertinoNavigationBar] or [CupertinoSliverNavigationBar]
+/// should be present in each [PageRoute] to support the default transitions.
+/// Use [transitionBetweenRoutes] or [heroTag] to customize the transition
+/// behavior for multiple navigation bars per route.
+///
+/// `CupertinoSliverNavigationBar` has its text scale factor set to 1.0 by default
+/// and does not respond to text scale factor changes from the operating system,
+/// to match the native iOS behavior. To override this behavior, wrap each of the
+/// `CupertinoSliverNavigationBar`'s components inside a [MediaQuery] with the
+/// desired [MediaQueryData.textScaleFactor] value. The text scale factor value
+/// from the operating system can be retrieved in many ways, such as querying
+/// [MediaQuery.textScaleFactorOf] against [CupertinoApp]'s [BuildContext].
+///
+/// The [stretch] parameter determines whether the nav bar should stretch to
+/// fill the over-scroll area. The nav bar can still expand and contract as the
+/// user scrolls, but it will also stretch when the user over-scrolls if the
+/// [stretch] value is `true`. Defaults to `true`.
+///
+/// See also:
+///
+///  * [CupertinoNavigationBar], an iOS navigation bar for use on non-scrolling
+///    pages.
+class CupertinoSliverNavigationBar extends StatefulWidget {
+  /// Creates a navigation bar for scrolling lists.
+  ///
+  /// The [largeTitle] argument is required and must not be null.
+  const CupertinoSliverNavigationBar({
+    Key? key,
+    this.largeTitle,
+    this.leading,
+    this.automaticallyImplyLeading = true,
+    this.automaticallyImplyTitle = true,
+    this.previousPageTitle,
+    this.middle,
+    this.trailing,
+    this.border = _kDefaultNavBarBorder,
+    this.backgroundColor,
+    this.brightness,
+    this.padding,
+    this.actionsForegroundColor,
+    this.transitionBetweenRoutes = true,
+    this.heroTag = _defaultHeroTag,
+    this.stretch = true,
+  }) : assert(automaticallyImplyLeading != null),
+       assert(automaticallyImplyTitle != null),
+       assert(
+         automaticallyImplyTitle == true || largeTitle != null,
+         'No largeTitle has been provided but automaticallyImplyTitle is also '
+         'false. Either provide a largeTitle or set automaticallyImplyTitle to '
+         'true.'
+       ),
+       super(key: key);
+
+  /// The navigation bar's title.
+  ///
+  /// This text will appear in the top static navigation bar when collapsed and
+  /// below the navigation bar, in a larger font, when expanded.
+  ///
+  /// A suitable [DefaultTextStyle] is provided around this widget as it is
+  /// moved around, to change its font size.
+  ///
+  /// If [middle] is null, then the [largeTitle] widget will be inserted into
+  /// the tree in two places when transitioning from the collapsed state to the
+  /// expanded state. It is therefore imperative that this subtree not contain
+  /// any [GlobalKey]s, and that it not rely on maintaining state (for example,
+  /// animations will not survive the transition from one location to the other,
+  /// and may in fact be visible in two places at once during the transition).
+  ///
+  /// If null and [automaticallyImplyTitle] is true, an appropriate [Text]
+  /// title will be created if the current route is a [CupertinoPageRoute] and
+  /// has a `title`.
+  ///
+  /// This parameter must either be non-null or the route must have a title
+  /// ([CupertinoPageRoute.title]) and [automaticallyImplyTitle] must be true.
+  final Widget? largeTitle;
+
+  /// {@macro flutter.cupertino.CupertinoNavigationBar.leading}
+  ///
+  /// This widget is visible in both collapsed and expanded states.
+  final Widget? leading;
+
+  /// {@macro flutter.cupertino.CupertinoNavigationBar.automaticallyImplyLeading}
+  final bool automaticallyImplyLeading;
+
+  /// Controls whether we should try to imply the [largeTitle] widget if null.
+  ///
+  /// If true and [largeTitle] is null, automatically fill in a [Text] widget
+  /// with the current route's `title` if the route is a [CupertinoPageRoute].
+  /// If [largeTitle] widget is not null, this parameter has no effect.
+  ///
+  /// This value cannot be null.
+  final bool automaticallyImplyTitle;
+
+  /// {@macro flutter.cupertino.CupertinoNavigationBar.previousPageTitle}
+  final String? previousPageTitle;
+
+  /// A widget to place in the middle of the static navigation bar instead of
+  /// the [largeTitle].
+  ///
+  /// This widget is visible in both collapsed and expanded states. The text
+  /// supplied in [largeTitle] will no longer appear in collapsed state if a
+  /// [middle] widget is provided.
+  final Widget? middle;
+
+  /// {@macro flutter.cupertino.CupertinoNavigationBar.trailing}
+  ///
+  /// This widget is visible in both collapsed and expanded states.
+  final Widget? trailing;
+
+  /// {@macro flutter.cupertino.CupertinoNavigationBar.backgroundColor}
+  final Color? backgroundColor;
+
+  /// {@macro flutter.cupertino.CupertinoNavigationBar.brightness}
+  final Brightness? brightness;
+
+  /// {@macro flutter.cupertino.CupertinoNavigationBar.padding}
+  final EdgeInsetsDirectional? padding;
+
+  /// {@macro flutter.cupertino.CupertinoNavigationBar.border}
+  final Border? border;
+
+  /// {@macro flutter.cupertino.CupertinoNavigationBar.actionsForegroundColor}
+  ///
+  /// The default color for text in the [largeTitle] slot is always black, as per
+  /// iOS standard design.
+  @Deprecated(
+    'Use CupertinoTheme and primaryColor to propagate color. '
+    'This feature was deprecated after v1.1.2.'
+  )
+  final Color? actionsForegroundColor;
+
+  /// {@macro flutter.cupertino.CupertinoNavigationBar.transitionBetweenRoutes}
+  final bool transitionBetweenRoutes;
+
+  /// {@macro flutter.cupertino.CupertinoNavigationBar.heroTag}
+  final Object heroTag;
+
+  /// True if the navigation bar's background color has no transparency.
+  bool get opaque => backgroundColor?.alpha == 0xFF;
+
+  /// Whether the nav bar should stretch to fill the over-scroll area.
+  ///
+  /// The nav bar can still expand and contract as the user scrolls, but it will
+  /// also stretch when the user over-scrolls if the [stretch] value is `true`.
+  /// Defaults to `true`.
+  final bool stretch;
+
+  @override
+  _CupertinoSliverNavigationBarState createState() => _CupertinoSliverNavigationBarState();
+}
+
+// A state class exists for the nav bar so that the keys of its sub-components
+// don't change when rebuilding the nav bar, causing the sub-components to
+// lose their own states.
+class _CupertinoSliverNavigationBarState extends State<CupertinoSliverNavigationBar> {
+  late _NavigationBarStaticComponentsKeys keys;
+
+  @override
+  void initState() {
+    super.initState();
+    keys = _NavigationBarStaticComponentsKeys();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // Lint ignore to maintain backward compatibility.
+    final Color actionsForegroundColor = CupertinoDynamicColor.maybeResolve(widget.actionsForegroundColor, context)
+                                      ?? CupertinoTheme.of(context).primaryColor;
+
+    final _NavigationBarStaticComponents components = _NavigationBarStaticComponents(
+      keys: keys,
+      route: ModalRoute.of(context),
+      userLeading: widget.leading,
+      automaticallyImplyLeading: widget.automaticallyImplyLeading,
+      automaticallyImplyTitle: widget.automaticallyImplyTitle,
+      previousPageTitle: widget.previousPageTitle,
+      userMiddle: widget.middle,
+      userTrailing: widget.trailing,
+      userLargeTitle: widget.largeTitle,
+      padding: widget.padding,
+      large: true,
+    );
+
+    return _wrapActiveColor(
+      // Lint ignore to maintain backward compatibility.
+      actionsForegroundColor,
+      context,
+      MediaQuery(
+        data: MediaQuery.of(context).copyWith(textScaleFactor: 1),
+        child: SliverPersistentHeader(
+          pinned: true, // iOS navigation bars are always pinned.
+          delegate: _LargeTitleNavigationBarSliverDelegate(
+            keys: keys,
+            components: components,
+            userMiddle: widget.middle,
+            backgroundColor: CupertinoDynamicColor.maybeResolve(widget.backgroundColor, context) ?? CupertinoTheme.of(context).barBackgroundColor,
+            brightness: widget.brightness,
+            border: widget.border,
+            padding: widget.padding,
+            actionsForegroundColor: actionsForegroundColor,
+            transitionBetweenRoutes: widget.transitionBetweenRoutes,
+            heroTag: widget.heroTag,
+            persistentHeight: _kNavBarPersistentHeight + MediaQuery.of(context).padding.top,
+            alwaysShowMiddle: widget.middle != null,
+            stretchConfiguration: widget.stretch ? OverScrollHeaderStretchConfiguration() : null,
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+class _LargeTitleNavigationBarSliverDelegate
+    extends SliverPersistentHeaderDelegate with DiagnosticableTreeMixin {
+  _LargeTitleNavigationBarSliverDelegate({
+    required this.keys,
+    required this.components,
+    required this.userMiddle,
+    required this.backgroundColor,
+    required this.brightness,
+    required this.border,
+    required this.padding,
+    required this.actionsForegroundColor,
+    required this.transitionBetweenRoutes,
+    required this.heroTag,
+    required this.persistentHeight,
+    required this.alwaysShowMiddle,
+    required this.stretchConfiguration,
+  }) : assert(persistentHeight != null),
+       assert(alwaysShowMiddle != null),
+       assert(transitionBetweenRoutes != null);
+
+  final _NavigationBarStaticComponentsKeys keys;
+  final _NavigationBarStaticComponents components;
+  final Widget? userMiddle;
+  final Color backgroundColor;
+  final Brightness? brightness;
+  final Border? border;
+  final EdgeInsetsDirectional? padding;
+  final Color actionsForegroundColor;
+  final bool transitionBetweenRoutes;
+  final Object heroTag;
+  final double persistentHeight;
+  final bool alwaysShowMiddle;
+
+  @override
+  double get minExtent => persistentHeight;
+
+  @override
+  double get maxExtent => persistentHeight + _kNavBarLargeTitleHeightExtension;
+
+  @override
+  OverScrollHeaderStretchConfiguration? stretchConfiguration;
+
+  @override
+  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
+    final bool showLargeTitle = shrinkOffset < maxExtent - minExtent - _kNavBarShowLargeTitleThreshold;
+
+    final _PersistentNavigationBar persistentNavigationBar =
+        _PersistentNavigationBar(
+      components: components,
+      padding: padding,
+      // If a user specified middle exists, always show it. Otherwise, show
+      // title when sliver is collapsed.
+      middleVisible: alwaysShowMiddle ? null : !showLargeTitle,
+    );
+
+    final Widget navBar = _wrapWithBackground(
+      border: border,
+      backgroundColor: CupertinoDynamicColor.resolve(backgroundColor, context),
+      brightness: brightness,
+      child: DefaultTextStyle(
+        style: CupertinoTheme.of(context).textTheme.textStyle,
+        child: Stack(
+          fit: StackFit.expand,
+          children: <Widget>[
+            Positioned(
+              top: persistentHeight,
+              left: 0.0,
+              right: 0.0,
+              bottom: 0.0,
+              child: ClipRect(
+                // The large title starts at the persistent bar.
+                // It's aligned with the bottom of the sliver and expands clipped
+                // and behind the persistent bar.
+                child: OverflowBox(
+                  minHeight: 0.0,
+                  maxHeight: double.infinity,
+                  alignment: AlignmentDirectional.bottomStart,
+                  child: Padding(
+                    padding: const EdgeInsetsDirectional.only(
+                      start: _kNavBarEdgePadding,
+                      bottom: 8.0, // Bottom has a different padding.
+                    ),
+                    child: SafeArea(
+                      top: false,
+                      bottom: false,
+                      child: AnimatedOpacity(
+                        opacity: showLargeTitle ? 1.0 : 0.0,
+                        duration: _kNavBarTitleFadeDuration,
+                        child: Semantics(
+                          header: true,
+                          child: DefaultTextStyle(
+                            style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle,
+                            maxLines: 1,
+                            overflow: TextOverflow.ellipsis,
+                            child: components.largeTitle!,
+                          ),
+                        ),
+                      ),
+                    ),
+                  ),
+                ),
+              ),
+            ),
+            Positioned(
+              left: 0.0,
+              right: 0.0,
+              top: 0.0,
+              child: persistentNavigationBar,
+            ),
+          ],
+        ),
+      ),
+    );
+
+    if (!transitionBetweenRoutes || !_isTransitionable(context)) {
+      return navBar;
+    }
+
+    return Hero(
+      tag: heroTag == _defaultHeroTag
+          ? _HeroTag(Navigator.of(context))
+          : heroTag,
+      createRectTween: _linearTranslateWithLargestRectSizeTween,
+      flightShuttleBuilder: _navBarHeroFlightShuttleBuilder,
+      placeholderBuilder: _navBarHeroLaunchPadBuilder,
+      transitionOnUserGestures: true,
+      // This is all the way down here instead of being at the top level of
+      // CupertinoSliverNavigationBar like CupertinoNavigationBar because it
+      // needs to wrap the top level RenderBox rather than a RenderSliver.
+      child: _TransitionableNavigationBar(
+        componentsKeys: keys,
+        backgroundColor: CupertinoDynamicColor.resolve(backgroundColor, context),
+        backButtonTextStyle: CupertinoTheme.of(context).textTheme.navActionTextStyle,
+        titleTextStyle: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
+        largeTitleTextStyle: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle,
+        border: border,
+        hasUserMiddle: userMiddle != null,
+        largeExpanded: showLargeTitle,
+        child: navBar,
+      ),
+    );
+  }
+
+  @override
+  bool shouldRebuild(_LargeTitleNavigationBarSliverDelegate oldDelegate) {
+    return components != oldDelegate.components
+        || userMiddle != oldDelegate.userMiddle
+        || backgroundColor != oldDelegate.backgroundColor
+        || border != oldDelegate.border
+        || padding != oldDelegate.padding
+        || actionsForegroundColor != oldDelegate.actionsForegroundColor
+        || transitionBetweenRoutes != oldDelegate.transitionBetweenRoutes
+        || persistentHeight != oldDelegate.persistentHeight
+        || alwaysShowMiddle != oldDelegate.alwaysShowMiddle
+        || heroTag != oldDelegate.heroTag;
+  }
+}
+
+/// The top part of the navigation bar that's never scrolled away.
+///
+/// Consists of the entire navigation bar without background and border when used
+/// without large titles. With large titles, it's the top static half that
+/// doesn't scroll.
+class _PersistentNavigationBar extends StatelessWidget {
+  const _PersistentNavigationBar({
+    Key? key,
+    required this.components,
+    this.padding,
+    this.middleVisible,
+  }) : super(key: key);
+
+  final _NavigationBarStaticComponents components;
+
+  final EdgeInsetsDirectional? padding;
+  /// Whether the middle widget has a visible animated opacity. A null value
+  /// means the middle opacity will not be animated.
+  final bool? middleVisible;
+
+  @override
+  Widget build(BuildContext context) {
+    Widget? middle = components.middle;
+
+    if (middle != null) {
+      middle = DefaultTextStyle(
+        style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
+        child: Semantics(header: true, child: middle),
+      );
+      // When the middle's visibility can change on the fly like with large title
+      // slivers, wrap with animated opacity.
+      middle = middleVisible == null
+        ? middle
+        : AnimatedOpacity(
+          opacity: middleVisible! ? 1.0 : 0.0,
+          duration: _kNavBarTitleFadeDuration,
+          child: middle,
+        );
+    }
+
+    Widget? leading = components.leading;
+    final Widget? backChevron = components.backChevron;
+    final Widget? backLabel = components.backLabel;
+
+    if (leading == null && backChevron != null && backLabel != null) {
+      leading = CupertinoNavigationBarBackButton._assemble(
+        backChevron,
+        backLabel,
+      );
+    }
+
+    Widget paddedToolbar = NavigationToolbar(
+      leading: leading,
+      middle: middle,
+      trailing: components.trailing,
+      centerMiddle: true,
+      middleSpacing: 6.0,
+    );
+
+    if (padding != null) {
+      paddedToolbar = Padding(
+        padding: EdgeInsets.only(
+          top: padding!.top,
+          bottom: padding!.bottom,
+        ),
+        child: paddedToolbar,
+      );
+    }
+
+    return SizedBox(
+      height: _kNavBarPersistentHeight + MediaQuery.of(context).padding.top,
+      child: SafeArea(
+        bottom: false,
+        child: paddedToolbar,
+      ),
+    );
+  }
+}
+
+// A collection of keys always used when building static routes' nav bars's
+// components with _NavigationBarStaticComponents and read in
+// _NavigationBarTransition in Hero flights in order to reference the components'
+// RenderBoxes for their positions.
+//
+// These keys should never re-appear inside the Hero flights.
+@immutable
+class _NavigationBarStaticComponentsKeys {
+  _NavigationBarStaticComponentsKeys()
+    : navBarBoxKey = GlobalKey(debugLabel: 'Navigation bar render box'),
+      leadingKey = GlobalKey(debugLabel: 'Leading'),
+      backChevronKey = GlobalKey(debugLabel: 'Back chevron'),
+      backLabelKey = GlobalKey(debugLabel: 'Back label'),
+      middleKey = GlobalKey(debugLabel: 'Middle'),
+      trailingKey = GlobalKey(debugLabel: 'Trailing'),
+      largeTitleKey = GlobalKey(debugLabel: 'Large title');
+
+  final GlobalKey navBarBoxKey;
+  final GlobalKey leadingKey;
+  final GlobalKey backChevronKey;
+  final GlobalKey backLabelKey;
+  final GlobalKey middleKey;
+  final GlobalKey trailingKey;
+  final GlobalKey largeTitleKey;
+}
+
+// Based on various user Widgets and other parameters, construct KeyedSubtree
+// components that are used in common by the CupertinoNavigationBar and
+// CupertinoSliverNavigationBar. The KeyedSubtrees are inserted into static
+// routes and the KeyedSubtrees' child are reused in the Hero flights.
+@immutable
+class _NavigationBarStaticComponents {
+  _NavigationBarStaticComponents({
+    required _NavigationBarStaticComponentsKeys keys,
+    required ModalRoute<dynamic>? route,
+    required Widget? userLeading,
+    required bool automaticallyImplyLeading,
+    required bool automaticallyImplyTitle,
+    required String? previousPageTitle,
+    required Widget? userMiddle,
+    required Widget? userTrailing,
+    required Widget? userLargeTitle,
+    required EdgeInsetsDirectional? padding,
+    required bool large,
+  }) : leading = createLeading(
+         leadingKey: keys.leadingKey,
+         userLeading: userLeading,
+         route: route,
+         automaticallyImplyLeading: automaticallyImplyLeading,
+         padding: padding,
+       ),
+       backChevron = createBackChevron(
+         backChevronKey: keys.backChevronKey,
+         userLeading: userLeading,
+         route: route,
+         automaticallyImplyLeading: automaticallyImplyLeading,
+       ),
+       backLabel = createBackLabel(
+         backLabelKey: keys.backLabelKey,
+         userLeading: userLeading,
+         route: route,
+         previousPageTitle: previousPageTitle,
+         automaticallyImplyLeading: automaticallyImplyLeading,
+       ),
+       middle = createMiddle(
+         middleKey: keys.middleKey,
+         userMiddle: userMiddle,
+         userLargeTitle: userLargeTitle,
+         route: route,
+         automaticallyImplyTitle: automaticallyImplyTitle,
+         large: large,
+       ),
+       trailing = createTrailing(
+         trailingKey: keys.trailingKey,
+         userTrailing: userTrailing,
+         padding: padding,
+       ),
+       largeTitle = createLargeTitle(
+         largeTitleKey: keys.largeTitleKey,
+         userLargeTitle: userLargeTitle,
+         route: route,
+         automaticImplyTitle: automaticallyImplyTitle,
+         large: large,
+       );
+
+  static Widget? _derivedTitle({
+    required bool automaticallyImplyTitle,
+    ModalRoute<dynamic>? currentRoute,
+  }) {
+    // Auto use the CupertinoPageRoute's title if middle not provided.
+    if (automaticallyImplyTitle &&
+        currentRoute is CupertinoRouteTransitionMixin &&
+        currentRoute.title != null) {
+      return Text(currentRoute.title!);
+    }
+
+    return null;
+  }
+
+  final KeyedSubtree? leading;
+  static KeyedSubtree? createLeading({
+    required GlobalKey leadingKey,
+    required Widget? userLeading,
+    required ModalRoute<dynamic>? route,
+    required bool automaticallyImplyLeading,
+    required EdgeInsetsDirectional? padding,
+  }) {
+    Widget? leadingContent;
+
+    if (userLeading != null) {
+      leadingContent = userLeading;
+    } else if (
+      automaticallyImplyLeading &&
+      route is PageRoute &&
+      route.canPop &&
+      route.fullscreenDialog
+    ) {
+      leadingContent = CupertinoButton(
+        child: const Text('Close'),
+        padding: EdgeInsets.zero,
+        onPressed: () { route.navigator!.maybePop(); },
+      );
+    }
+
+    if (leadingContent == null) {
+      return null;
+    }
+
+    return KeyedSubtree(
+      key: leadingKey,
+      child: Padding(
+        padding: EdgeInsetsDirectional.only(
+          start: padding?.start ?? _kNavBarEdgePadding,
+        ),
+        child: IconTheme.merge(
+          data: const IconThemeData(
+            size: 32.0,
+          ),
+          child: leadingContent,
+        ),
+      ),
+    );
+  }
+
+  final KeyedSubtree? backChevron;
+  static KeyedSubtree? createBackChevron({
+    required GlobalKey backChevronKey,
+    required Widget? userLeading,
+    required ModalRoute<dynamic>? route,
+    required bool automaticallyImplyLeading,
+  }) {
+    if (
+      userLeading != null ||
+      !automaticallyImplyLeading ||
+      route == null ||
+      !route.canPop ||
+      (route is PageRoute && route.fullscreenDialog)
+    ) {
+      return null;
+    }
+
+    return KeyedSubtree(key: backChevronKey, child: const _BackChevron());
+  }
+
+  /// This widget is not decorated with a font since the font style could
+  /// animate during transitions.
+  final KeyedSubtree? backLabel;
+  static KeyedSubtree? createBackLabel({
+    required GlobalKey backLabelKey,
+    required Widget? userLeading,
+    required ModalRoute<dynamic>? route,
+    required bool automaticallyImplyLeading,
+    required String? previousPageTitle,
+  }) {
+    if (
+      userLeading != null ||
+      !automaticallyImplyLeading ||
+      route == null ||
+      !route.canPop ||
+      (route is PageRoute && route.fullscreenDialog)
+    ) {
+      return null;
+    }
+
+    return KeyedSubtree(
+      key: backLabelKey,
+      child: _BackLabel(
+        specifiedPreviousTitle: previousPageTitle,
+        route: route,
+      ),
+    );
+  }
+
+  /// This widget is not decorated with a font since the font style could
+  /// animate during transitions.
+  final KeyedSubtree? middle;
+  static KeyedSubtree? createMiddle({
+    required GlobalKey middleKey,
+    required Widget? userMiddle,
+    required Widget? userLargeTitle,
+    required bool large,
+    required bool automaticallyImplyTitle,
+    required ModalRoute<dynamic>? route,
+  }) {
+    Widget? middleContent = userMiddle;
+
+    if (large) {
+      middleContent ??= userLargeTitle;
+    }
+
+    middleContent ??= _derivedTitle(
+      automaticallyImplyTitle: automaticallyImplyTitle,
+      currentRoute: route,
+    );
+
+    if (middleContent == null) {
+      return null;
+    }
+
+    return KeyedSubtree(
+      key: middleKey,
+      child: middleContent,
+    );
+  }
+
+  final KeyedSubtree? trailing;
+  static KeyedSubtree? createTrailing({
+    required GlobalKey trailingKey,
+    required Widget? userTrailing,
+    required EdgeInsetsDirectional? padding,
+  }) {
+    if (userTrailing == null) {
+      return null;
+    }
+
+    return KeyedSubtree(
+      key: trailingKey,
+      child: Padding(
+        padding: EdgeInsetsDirectional.only(
+          end: padding?.end ?? _kNavBarEdgePadding,
+        ),
+        child: IconTheme.merge(
+          data: const IconThemeData(
+            size: 32.0,
+          ),
+          child: userTrailing,
+        ),
+      ),
+    );
+  }
+
+  /// This widget is not decorated with a font since the font style could
+  /// animate during transitions.
+  final KeyedSubtree? largeTitle;
+  static KeyedSubtree? createLargeTitle({
+    required GlobalKey largeTitleKey,
+    required Widget? userLargeTitle,
+    required bool large,
+    required bool automaticImplyTitle,
+    required ModalRoute<dynamic>? route,
+  }) {
+    if (!large) {
+      return null;
+    }
+
+    final Widget? largeTitleContent = userLargeTitle ?? _derivedTitle(
+      automaticallyImplyTitle: automaticImplyTitle,
+      currentRoute: route,
+    );
+
+    assert(
+      largeTitleContent != null,
+      'largeTitle was not provided and there was no title from the route.',
+    );
+
+    return KeyedSubtree(
+      key: largeTitleKey,
+      child: largeTitleContent!,
+    );
+  }
+}
+
+/// A nav bar back button typically used in [CupertinoNavigationBar].
+///
+/// This is automatically inserted into [CupertinoNavigationBar] and
+/// [CupertinoSliverNavigationBar]'s `leading` slot when
+/// `automaticallyImplyLeading` is true.
+///
+/// When manually inserted, the [CupertinoNavigationBarBackButton] should only
+/// be used in routes that can be popped unless a custom [onPressed] is
+/// provided.
+///
+/// Shows a back chevron and the previous route's title when available from
+/// the previous [CupertinoPageRoute.title]. If [previousPageTitle] is specified,
+/// it will be shown instead.
+class CupertinoNavigationBarBackButton extends StatelessWidget {
+  /// Construct a [CupertinoNavigationBarBackButton] that can be used to pop
+  /// the current route.
+  ///
+  /// The [color] parameter must not be null.
+  const CupertinoNavigationBarBackButton({
+    Key? key,
+    this.color,
+    this.previousPageTitle,
+    this.onPressed,
+  }) : _backChevron = null,
+       _backLabel = null,
+       super(key: key);
+
+  // Allow the back chevron and label to be separately created (and keyed)
+  // because they animate separately during page transitions.
+  const CupertinoNavigationBarBackButton._assemble(
+    this._backChevron,
+    this._backLabel,
+  ) : previousPageTitle = null,
+      color = null,
+      onPressed = null;
+
+  /// The [Color] of the back button.
+  ///
+  /// Can be used to override the color of the back button chevron and label.
+  ///
+  /// Defaults to [CupertinoTheme]'s `primaryColor` if null.
+  final Color? color;
+
+  /// An override for showing the previous route's title. If null, it will be
+  /// automatically derived from [CupertinoPageRoute.title] if the current and
+  /// previous routes are both [CupertinoPageRoute]s.
+  final String? previousPageTitle;
+
+  /// An override callback to perform instead of the default behavior which is
+  /// to pop the [Navigator].
+  ///
+  /// It can, for instance, be used to pop the platform's navigation stack
+  /// via [SystemNavigator] instead of Flutter's [Navigator] in add-to-app
+  /// situations.
+  ///
+  /// Defaults to null.
+  final VoidCallback? onPressed;
+
+  final Widget? _backChevron;
+
+  final Widget? _backLabel;
+
+  @override
+  Widget build(BuildContext context) {
+    final ModalRoute<dynamic>? currentRoute = ModalRoute.of(context);
+    if (onPressed == null) {
+      assert(
+        currentRoute?.canPop == true,
+        'CupertinoNavigationBarBackButton should only be used in routes that can be popped',
+      );
+    }
+
+    TextStyle actionTextStyle = CupertinoTheme.of(context).textTheme.navActionTextStyle;
+    if (color != null) {
+      actionTextStyle = actionTextStyle.copyWith(color: CupertinoDynamicColor.maybeResolve(color, context));
+    }
+
+    return CupertinoButton(
+      child: Semantics(
+        container: true,
+        excludeSemantics: true,
+        label: 'Back',
+        button: true,
+        child: DefaultTextStyle(
+          style: actionTextStyle,
+          child: ConstrainedBox(
+            constraints: const BoxConstraints(minWidth: _kNavBarBackButtonTapWidth),
+            child: Row(
+              mainAxisSize: MainAxisSize.min,
+              mainAxisAlignment: MainAxisAlignment.start,
+              children: <Widget>[
+                const Padding(padding: EdgeInsetsDirectional.only(start: 8.0)),
+                _backChevron ?? const _BackChevron(),
+                const Padding(padding: EdgeInsetsDirectional.only(start: 6.0)),
+                Flexible(
+                  child: _backLabel ?? _BackLabel(
+                    specifiedPreviousTitle: previousPageTitle,
+                    route: currentRoute,
+                  ),
+                ),
+              ],
+            ),
+          ),
+        ),
+      ),
+      padding: EdgeInsets.zero,
+      onPressed: () {
+        if (onPressed != null) {
+          onPressed!();
+        } else {
+          Navigator.maybePop(context);
+        }
+      },
+    );
+  }
+}
+
+
+class _BackChevron extends StatelessWidget {
+  const _BackChevron({ Key? key }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final TextDirection textDirection = Directionality.of(context);
+    final TextStyle textStyle = DefaultTextStyle.of(context).style;
+
+    // Replicate the Icon logic here to get a tightly sized icon and add
+    // custom non-square padding.
+    Widget iconWidget = Padding(
+      padding: const EdgeInsetsDirectional.only(start: 6, end: 2),
+      child: Text.rich(
+        TextSpan(
+          text: String.fromCharCode(CupertinoIcons.back.codePoint),
+          style: TextStyle(
+            inherit: false,
+            color: textStyle.color,
+            fontSize: 30.0,
+            fontFamily: CupertinoIcons.back.fontFamily,
+            package: CupertinoIcons.back.fontPackage,
+          ),
+        ),
+      ),
+    );
+    switch (textDirection) {
+      case TextDirection.rtl:
+        iconWidget = Transform(
+          transform: Matrix4.identity()..scale(-1.0, 1.0, 1.0),
+          alignment: Alignment.center,
+          transformHitTests: false,
+          child: iconWidget,
+        );
+        break;
+      case TextDirection.ltr:
+        break;
+    }
+
+    return iconWidget;
+  }
+}
+
+/// A widget that shows next to the back chevron when `automaticallyImplyLeading`
+/// is true.
+class _BackLabel extends StatelessWidget {
+  const _BackLabel({
+    Key? key,
+    required this.specifiedPreviousTitle,
+    required this.route,
+  }) : super(key: key);
+
+  final String? specifiedPreviousTitle;
+  final ModalRoute<dynamic>? route;
+
+  // `child` is never passed in into ValueListenableBuilder so it's always
+  // null here and unused.
+  Widget _buildPreviousTitleWidget(BuildContext context, String? previousTitle, Widget? child) {
+    if (previousTitle == null) {
+      return const SizedBox(height: 0.0, width: 0.0);
+    }
+
+    Text textWidget = Text(
+      previousTitle,
+      maxLines: 1,
+      overflow: TextOverflow.ellipsis,
+    );
+
+    if (previousTitle.length > 12) {
+      textWidget = const Text('Back');
+    }
+
+    return Align(
+      alignment: AlignmentDirectional.centerStart,
+      widthFactor: 1.0,
+      child: textWidget,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (specifiedPreviousTitle != null) {
+      return _buildPreviousTitleWidget(context, specifiedPreviousTitle, null);
+    } else if (route is CupertinoRouteTransitionMixin<dynamic> && !route!.isFirst) {
+      final CupertinoRouteTransitionMixin<dynamic> cupertinoRoute = route! as CupertinoRouteTransitionMixin<dynamic>;
+      // There is no timing issue because the previousTitle Listenable changes
+      // happen during route modifications before the ValueListenableBuilder
+      // is built.
+      return ValueListenableBuilder<String?>(
+        valueListenable: cupertinoRoute.previousTitle,
+        builder: _buildPreviousTitleWidget,
+      );
+    } else {
+      return const SizedBox(height: 0.0, width: 0.0);
+    }
+  }
+}
+
+/// This should always be the first child of Hero widgets.
+///
+/// This class helps each Hero transition obtain the start or end navigation
+/// bar's box size and the inner components of the navigation bar that will
+/// move around.
+///
+/// It should be wrapped around the biggest [RenderBox] of the static
+/// navigation bar in each route.
+class _TransitionableNavigationBar extends StatelessWidget {
+  _TransitionableNavigationBar({
+    required this.componentsKeys,
+    required this.backgroundColor,
+    required this.backButtonTextStyle,
+    required this.titleTextStyle,
+    required this.largeTitleTextStyle,
+    required this.border,
+    required this.hasUserMiddle,
+    required this.largeExpanded,
+    required this.child,
+  }) : assert(componentsKeys != null),
+       assert(largeExpanded != null),
+       assert(!largeExpanded || largeTitleTextStyle != null),
+       super(key: componentsKeys.navBarBoxKey);
+
+  final _NavigationBarStaticComponentsKeys componentsKeys;
+  final Color? backgroundColor;
+  final TextStyle backButtonTextStyle;
+  final TextStyle titleTextStyle;
+  final TextStyle? largeTitleTextStyle;
+  final Border? border;
+  final bool hasUserMiddle;
+  final bool largeExpanded;
+  final Widget child;
+
+  RenderBox get renderBox {
+    final RenderBox box = componentsKeys.navBarBoxKey.currentContext!.findRenderObject()! as RenderBox;
+    assert(
+      box.attached,
+      '_TransitionableNavigationBar.renderBox should be called when building '
+      'hero flight shuttles when the from and the to nav bar boxes are already '
+      'laid out and painted.',
+    );
+    return box;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(() {
+      bool inHero = false;
+      context.visitAncestorElements((Element ancestor) {
+        if (ancestor is ComponentElement) {
+          assert(
+            ancestor.widget.runtimeType != _NavigationBarTransition,
+            '_TransitionableNavigationBar should never re-appear inside '
+            '_NavigationBarTransition. Keyed _TransitionableNavigationBar should '
+            'only serve as anchor points in routes rather than appearing inside '
+            'Hero flights themselves.',
+          );
+          if (ancestor.widget.runtimeType == Hero) {
+            inHero = true;
+          }
+        }
+        return true;
+      });
+      assert(
+        inHero,
+        '_TransitionableNavigationBar should only be added as the immediate '
+        'child of Hero widgets.',
+      );
+      return true;
+    }());
+    return child;
+  }
+}
+
+/// This class represents the widget that will be in the Hero flight instead of
+/// the 2 static navigation bars by taking inner components from both.
+///
+/// The `topNavBar` parameter is the nav bar that was on top regardless of
+/// push/pop direction.
+///
+/// Similarly, the `bottomNavBar` parameter is the nav bar that was at the
+/// bottom regardless of the push/pop direction.
+///
+/// If [MediaQuery.padding] is still present in this widget's [BuildContext],
+/// that padding will become part of the transitional navigation bar as well.
+///
+/// [MediaQuery.padding] should be consistent between the from/to routes and
+/// the Hero overlay. Inconsistent [MediaQuery.padding] will produce undetermined
+/// results.
+class _NavigationBarTransition extends StatelessWidget {
+  _NavigationBarTransition({
+    required this.animation,
+    required this.topNavBar,
+    required this.bottomNavBar,
+  }) : heightTween = Tween<double>(
+         begin: bottomNavBar.renderBox.size.height,
+         end: topNavBar.renderBox.size.height,
+       ),
+       backgroundTween = ColorTween(
+         begin: bottomNavBar.backgroundColor,
+         end: topNavBar.backgroundColor,
+       ),
+       borderTween = BorderTween(
+         begin: bottomNavBar.border,
+         end: topNavBar.border,
+       );
+
+  final Animation<double> animation;
+  final _TransitionableNavigationBar topNavBar;
+  final _TransitionableNavigationBar bottomNavBar;
+
+  final Tween<double> heightTween;
+  final ColorTween backgroundTween;
+  final BorderTween borderTween;
+
+  @override
+  Widget build(BuildContext context) {
+    final _NavigationBarComponentsTransition componentsTransition = _NavigationBarComponentsTransition(
+      animation: animation,
+      bottomNavBar: bottomNavBar,
+      topNavBar: topNavBar,
+      directionality: Directionality.of(context),
+    );
+
+    final List<Widget> children = <Widget>[
+      // Draw an empty navigation bar box with changing shape behind all the
+      // moving components without any components inside it itself.
+      AnimatedBuilder(
+        animation: animation,
+        builder: (BuildContext context, Widget? child) {
+          return _wrapWithBackground(
+            // Don't update the system status bar color mid-flight.
+            updateSystemUiOverlay: false,
+            backgroundColor: backgroundTween.evaluate(animation)!,
+            border: borderTween.evaluate(animation),
+            child: SizedBox(
+              height: heightTween.evaluate(animation),
+              width: double.infinity,
+            ),
+          );
+        },
+      ),
+      // Draw all the components on top of the empty bar box.
+      if (componentsTransition.bottomBackChevron != null) componentsTransition.bottomBackChevron!,
+      if (componentsTransition.bottomBackLabel != null) componentsTransition.bottomBackLabel!,
+      if (componentsTransition.bottomLeading != null) componentsTransition.bottomLeading!,
+      if (componentsTransition.bottomMiddle != null) componentsTransition.bottomMiddle!,
+      if (componentsTransition.bottomLargeTitle != null) componentsTransition.bottomLargeTitle!,
+      if (componentsTransition.bottomTrailing != null) componentsTransition.bottomTrailing!,
+      // Draw top components on top of the bottom components.
+      if (componentsTransition.topLeading != null) componentsTransition.topLeading!,
+      if (componentsTransition.topBackChevron != null) componentsTransition.topBackChevron!,
+      if (componentsTransition.topBackLabel != null) componentsTransition.topBackLabel!,
+      if (componentsTransition.topMiddle != null) componentsTransition.topMiddle!,
+      if (componentsTransition.topLargeTitle != null) componentsTransition.topLargeTitle!,
+      if (componentsTransition.topTrailing != null) componentsTransition.topTrailing!,
+    ];
+
+
+    // The actual outer box is big enough to contain both the bottom and top
+    // navigation bars. It's not a direct Rect lerp because some components
+    // can actually be outside the linearly lerp'ed Rect in the middle of
+    // the animation, such as the topLargeTitle.
+    return SizedBox(
+      height: math.max(heightTween.begin!, heightTween.end!) + MediaQuery.of(context).padding.top,
+      width: double.infinity,
+      child: Stack(
+        children: children,
+      ),
+    );
+  }
+}
+
+/// This class helps create widgets that are in transition based on static
+/// components from the bottom and top navigation bars.
+///
+/// It animates these transitional components both in terms of position and
+/// their appearance.
+///
+/// Instead of running the transitional components through their normal static
+/// navigation bar layout logic, this creates transitional widgets that are based
+/// on these widgets' existing render objects' layout and position.
+///
+/// This is possible because this widget is only used during Hero transitions
+/// where both the from and to routes are already built and laid out.
+///
+/// The components' existing layout constraints and positions are then
+/// replicated using [Positioned] or [PositionedTransition] wrappers.
+///
+/// This class should never return [KeyedSubtree]s created by
+/// _NavigationBarStaticComponents directly. Since widgets from
+/// _NavigationBarStaticComponents are still present in the widget tree during the
+/// hero transitions, it would cause global key duplications. Instead, return
+/// only the [KeyedSubtree]s' child.
+@immutable
+class _NavigationBarComponentsTransition {
+  _NavigationBarComponentsTransition({
+    required this.animation,
+    required _TransitionableNavigationBar bottomNavBar,
+    required _TransitionableNavigationBar topNavBar,
+    required TextDirection directionality,
+  }) : bottomComponents = bottomNavBar.componentsKeys,
+       topComponents = topNavBar.componentsKeys,
+       bottomNavBarBox = bottomNavBar.renderBox,
+       topNavBarBox = topNavBar.renderBox,
+       bottomBackButtonTextStyle = bottomNavBar.backButtonTextStyle,
+       topBackButtonTextStyle = topNavBar.backButtonTextStyle,
+       bottomTitleTextStyle = bottomNavBar.titleTextStyle,
+       topTitleTextStyle = topNavBar.titleTextStyle,
+       bottomLargeTitleTextStyle = bottomNavBar.largeTitleTextStyle,
+       topLargeTitleTextStyle = topNavBar.largeTitleTextStyle,
+       bottomHasUserMiddle = bottomNavBar.hasUserMiddle,
+       topHasUserMiddle = topNavBar.hasUserMiddle,
+       bottomLargeExpanded = bottomNavBar.largeExpanded,
+       topLargeExpanded = topNavBar.largeExpanded,
+       transitionBox =
+           // paintBounds are based on offset zero so it's ok to expand the Rects.
+           bottomNavBar.renderBox.paintBounds.expandToInclude(topNavBar.renderBox.paintBounds),
+       forwardDirection = directionality == TextDirection.ltr ? 1.0 : -1.0;
+
+  static final Animatable<double> fadeOut = Tween<double>(
+    begin: 1.0,
+    end: 0.0,
+  );
+  static final Animatable<double> fadeIn = Tween<double>(
+    begin: 0.0,
+    end: 1.0,
+  );
+
+  final Animation<double> animation;
+  final _NavigationBarStaticComponentsKeys bottomComponents;
+  final _NavigationBarStaticComponentsKeys topComponents;
+
+  // These render boxes that are the ancestors of all the bottom and top
+  // components are used to determine the components' relative positions inside
+  // their respective navigation bars.
+  final RenderBox bottomNavBarBox;
+  final RenderBox topNavBarBox;
+
+  final TextStyle bottomBackButtonTextStyle;
+  final TextStyle topBackButtonTextStyle;
+  final TextStyle bottomTitleTextStyle;
+  final TextStyle topTitleTextStyle;
+  final TextStyle? bottomLargeTitleTextStyle;
+  final TextStyle? topLargeTitleTextStyle;
+
+  final bool bottomHasUserMiddle;
+  final bool topHasUserMiddle;
+  final bool bottomLargeExpanded;
+  final bool topLargeExpanded;
+
+  // This is the outer box in which all the components will be fitted. The
+  // sizing component of RelativeRects will be based on this rect's size.
+  final Rect transitionBox;
+
+  // x-axis unity number representing the direction of growth for text.
+  final double forwardDirection;
+
+  // Take a widget it its original ancestor navigation bar render box and
+  // translate it into a RelativeBox in the transition navigation bar box.
+  RelativeRect positionInTransitionBox(
+    GlobalKey key, {
+    required RenderBox from,
+  }) {
+    final RenderBox componentBox = key.currentContext!.findRenderObject()! as RenderBox;
+    assert(componentBox.attached);
+
+    return RelativeRect.fromRect(
+      componentBox.localToGlobal(Offset.zero, ancestor: from) & componentBox.size,
+      transitionBox,
+    );
+  }
+
+  // Create a Tween that moves a widget between its original position in its
+  // ancestor navigation bar to another widget's position in that widget's
+  // navigation bar.
+  //
+  // Anchor their positions based on the vertical middle of their respective
+  // render boxes' leading edge.
+  //
+  // Also produce RelativeRects with sizes that would preserve the constant
+  // BoxConstraints of the 'from' widget so that animating font sizes etc don't
+  // produce rounding error artifacts with a linearly resizing rect.
+  RelativeRectTween slideFromLeadingEdge({
+    required GlobalKey fromKey,
+    required RenderBox fromNavBarBox,
+    required GlobalKey toKey,
+    required RenderBox toNavBarBox,
+  }) {
+    final RelativeRect fromRect = positionInTransitionBox(fromKey, from: fromNavBarBox);
+
+    final RenderBox fromBox = fromKey.currentContext!.findRenderObject()! as RenderBox;
+    final RenderBox toBox = toKey.currentContext!.findRenderObject()! as RenderBox;
+
+    // We move a box with the size of the 'from' render object such that its
+    // upper left corner is at the upper left corner of the 'to' render object.
+    // With slight y axis adjustment for those render objects' height differences.
+    Rect toRect =
+        toBox.localToGlobal(
+          Offset.zero,
+          ancestor: toNavBarBox,
+        ).translate(
+          0.0,
+          - fromBox.size.height / 2 + toBox.size.height / 2,
+        ) & fromBox.size; // Keep the from render object's size.
+
+    if (forwardDirection < 0) {
+      // If RTL, move the center right to the center right instead of matching
+      // the center lefts.
+      toRect = toRect.translate(- fromBox.size.width + toBox.size.width, 0.0);
+    }
+
+    return RelativeRectTween(
+        begin: fromRect,
+        end: RelativeRect.fromRect(toRect, transitionBox),
+      );
+  }
+
+  Animation<double> fadeInFrom(double t, { Curve curve = Curves.easeIn }) {
+    return animation.drive(fadeIn.chain(
+      CurveTween(curve: Interval(t, 1.0, curve: curve)),
+    ));
+  }
+
+  Animation<double> fadeOutBy(double t, { Curve curve = Curves.easeOut }) {
+    return animation.drive(fadeOut.chain(
+      CurveTween(curve: Interval(0.0, t, curve: curve)),
+    ));
+  }
+
+  Widget? get bottomLeading {
+    final KeyedSubtree? bottomLeading = bottomComponents.leadingKey.currentWidget as KeyedSubtree?;
+
+    if (bottomLeading == null) {
+      return null;
+    }
+
+    return Positioned.fromRelativeRect(
+      rect: positionInTransitionBox(bottomComponents.leadingKey, from: bottomNavBarBox),
+      child: FadeTransition(
+        opacity: fadeOutBy(0.4),
+        child: bottomLeading.child,
+      ),
+    );
+  }
+
+  Widget? get bottomBackChevron {
+    final KeyedSubtree? bottomBackChevron = bottomComponents.backChevronKey.currentWidget as KeyedSubtree?;
+
+    if (bottomBackChevron == null) {
+      return null;
+    }
+
+    return Positioned.fromRelativeRect(
+      rect: positionInTransitionBox(bottomComponents.backChevronKey, from: bottomNavBarBox),
+      child: FadeTransition(
+        opacity: fadeOutBy(0.6),
+        child: DefaultTextStyle(
+          style: bottomBackButtonTextStyle,
+          child: bottomBackChevron.child,
+        ),
+      ),
+    );
+  }
+
+  Widget? get bottomBackLabel {
+    final KeyedSubtree? bottomBackLabel = bottomComponents.backLabelKey.currentWidget as KeyedSubtree?;
+
+    if (bottomBackLabel == null) {
+      return null;
+    }
+
+    final RelativeRect from = positionInTransitionBox(bottomComponents.backLabelKey, from: bottomNavBarBox);
+
+    // Transition away by sliding horizontally to the leading edge off of the screen.
+    final RelativeRectTween positionTween = RelativeRectTween(
+      begin: from,
+      end: from.shift(
+        Offset(
+          forwardDirection * (-bottomNavBarBox.size.width / 2.0),
+          0.0,
+        ),
+      ),
+    );
+
+    return PositionedTransition(
+      rect: animation.drive(positionTween),
+      child: FadeTransition(
+        opacity: fadeOutBy(0.2),
+        child: DefaultTextStyle(
+          style: bottomBackButtonTextStyle,
+          child: bottomBackLabel.child,
+        ),
+      ),
+    );
+  }
+
+  Widget? get bottomMiddle {
+    final KeyedSubtree? bottomMiddle = bottomComponents.middleKey.currentWidget as KeyedSubtree?;
+    final KeyedSubtree? topBackLabel = topComponents.backLabelKey.currentWidget as KeyedSubtree?;
+    final KeyedSubtree? topLeading = topComponents.leadingKey.currentWidget as KeyedSubtree?;
+
+    // The middle component is non-null when the nav bar is a large title
+    // nav bar but would be invisible when expanded, therefore don't show it here.
+    if (!bottomHasUserMiddle && bottomLargeExpanded) {
+      return null;
+    }
+
+    if (bottomMiddle != null && topBackLabel != null) {
+      // Move from current position to the top page's back label position.
+      return PositionedTransition(
+        rect: animation.drive(slideFromLeadingEdge(
+          fromKey: bottomComponents.middleKey,
+          fromNavBarBox: bottomNavBarBox,
+          toKey: topComponents.backLabelKey,
+          toNavBarBox: topNavBarBox,
+        )),
+        child: FadeTransition(
+          // A custom middle widget like a segmented control fades away faster.
+          opacity: fadeOutBy(bottomHasUserMiddle ? 0.4 : 0.7),
+          child: Align(
+            // As the text shrinks, make sure it's still anchored to the leading
+            // edge of a constantly sized outer box.
+            alignment: AlignmentDirectional.centerStart,
+            child: DefaultTextStyleTransition(
+              style: animation.drive(TextStyleTween(
+                begin: bottomTitleTextStyle,
+                end: topBackButtonTextStyle,
+              )),
+              child: bottomMiddle.child,
+            ),
+          ),
+        ),
+      );
+    }
+
+    // When the top page has a leading widget override (one of the few ways to
+    // not have a top back label), don't move the bottom middle widget and just
+    // fade.
+    if (bottomMiddle != null && topLeading != null) {
+      return Positioned.fromRelativeRect(
+        rect: positionInTransitionBox(bottomComponents.middleKey, from: bottomNavBarBox),
+        child: FadeTransition(
+          opacity: fadeOutBy(bottomHasUserMiddle ? 0.4 : 0.7),
+          // Keep the font when transitioning into a non-back label leading.
+          child: DefaultTextStyle(
+            style: bottomTitleTextStyle,
+            child: bottomMiddle.child,
+          ),
+        ),
+      );
+    }
+
+    return null;
+  }
+
+  Widget? get bottomLargeTitle {
+    final KeyedSubtree? bottomLargeTitle = bottomComponents.largeTitleKey.currentWidget as KeyedSubtree?;
+    final KeyedSubtree? topBackLabel = topComponents.backLabelKey.currentWidget as KeyedSubtree?;
+    final KeyedSubtree? topLeading = topComponents.leadingKey.currentWidget as KeyedSubtree?;
+
+    if (bottomLargeTitle == null || !bottomLargeExpanded) {
+      return null;
+    }
+
+    if (bottomLargeTitle != null && topBackLabel != null) {
+      // Move from current position to the top page's back label position.
+      return PositionedTransition(
+        rect: animation.drive(slideFromLeadingEdge(
+          fromKey: bottomComponents.largeTitleKey,
+          fromNavBarBox: bottomNavBarBox,
+          toKey: topComponents.backLabelKey,
+          toNavBarBox: topNavBarBox,
+        )),
+        child: FadeTransition(
+          opacity: fadeOutBy(0.6),
+          child: Align(
+            // As the text shrinks, make sure it's still anchored to the leading
+            // edge of a constantly sized outer box.
+            alignment: AlignmentDirectional.centerStart,
+            child: DefaultTextStyleTransition(
+              style: animation.drive(TextStyleTween(
+                begin: bottomLargeTitleTextStyle,
+                end: topBackButtonTextStyle,
+              )),
+              maxLines: 1,
+              overflow: TextOverflow.ellipsis,
+              child: bottomLargeTitle.child,
+            ),
+          ),
+        ),
+      );
+    }
+
+    if (bottomLargeTitle != null && topLeading != null) {
+      // Unlike bottom middle, the bottom large title moves when it can't
+      // transition to the top back label position.
+      final RelativeRect from = positionInTransitionBox(bottomComponents.largeTitleKey, from: bottomNavBarBox);
+
+      final RelativeRectTween positionTween = RelativeRectTween(
+        begin: from,
+        end: from.shift(
+          Offset(
+            forwardDirection * bottomNavBarBox.size.width / 4.0,
+            0.0,
+          ),
+        ),
+      );
+
+      // Just shift slightly towards the trailing edge instead of moving to the
+      // back label position.
+      return PositionedTransition(
+        rect: animation.drive(positionTween),
+        child: FadeTransition(
+          opacity: fadeOutBy(0.4),
+          // Keep the font when transitioning into a non-back-label leading.
+          child: DefaultTextStyle(
+            style: bottomLargeTitleTextStyle!,
+            child: bottomLargeTitle.child,
+          ),
+        ),
+      );
+    }
+
+    return null;
+  }
+
+  Widget? get bottomTrailing {
+    final KeyedSubtree? bottomTrailing = bottomComponents.trailingKey.currentWidget as KeyedSubtree?;
+
+    if (bottomTrailing == null) {
+      return null;
+    }
+
+    return Positioned.fromRelativeRect(
+      rect: positionInTransitionBox(bottomComponents.trailingKey, from: bottomNavBarBox),
+      child: FadeTransition(
+        opacity: fadeOutBy(0.6),
+        child: bottomTrailing.child,
+      ),
+    );
+  }
+
+  Widget? get topLeading {
+    final KeyedSubtree? topLeading = topComponents.leadingKey.currentWidget as KeyedSubtree?;
+
+    if (topLeading == null) {
+      return null;
+    }
+
+    return Positioned.fromRelativeRect(
+      rect: positionInTransitionBox(topComponents.leadingKey, from: topNavBarBox),
+      child: FadeTransition(
+        opacity: fadeInFrom(0.6),
+        child: topLeading.child,
+      ),
+    );
+  }
+
+  Widget? get topBackChevron {
+    final KeyedSubtree? topBackChevron = topComponents.backChevronKey.currentWidget as KeyedSubtree?;
+    final KeyedSubtree? bottomBackChevron = bottomComponents.backChevronKey.currentWidget as KeyedSubtree?;
+
+    if (topBackChevron == null) {
+      return null;
+    }
+
+    final RelativeRect to = positionInTransitionBox(topComponents.backChevronKey, from: topNavBarBox);
+    RelativeRect from = to;
+
+    // If it's the first page with a back chevron, shift in slightly from the
+    // right.
+    if (bottomBackChevron == null) {
+      final RenderBox topBackChevronBox = topComponents.backChevronKey.currentContext!.findRenderObject()! as RenderBox;
+      from = to.shift(
+        Offset(
+          forwardDirection * topBackChevronBox.size.width * 2.0,
+          0.0,
+        ),
+      );
+    }
+
+    final RelativeRectTween positionTween = RelativeRectTween(
+      begin: from,
+      end: to,
+    );
+
+    return PositionedTransition(
+      rect: animation.drive(positionTween),
+      child: FadeTransition(
+        opacity: fadeInFrom(bottomBackChevron == null ? 0.7 : 0.4),
+        child: DefaultTextStyle(
+          style: topBackButtonTextStyle,
+          child: topBackChevron.child,
+        ),
+      ),
+    );
+  }
+
+  Widget? get topBackLabel {
+    final KeyedSubtree? bottomMiddle = bottomComponents.middleKey.currentWidget as KeyedSubtree?;
+    final KeyedSubtree? bottomLargeTitle = bottomComponents.largeTitleKey.currentWidget as KeyedSubtree?;
+    final KeyedSubtree? topBackLabel = topComponents.backLabelKey.currentWidget as KeyedSubtree?;
+
+    if (topBackLabel == null) {
+      return null;
+    }
+
+    final RenderAnimatedOpacity? topBackLabelOpacity =
+        topComponents.backLabelKey.currentContext?.findAncestorRenderObjectOfType<RenderAnimatedOpacity>();
+
+    Animation<double>? midClickOpacity;
+    if (topBackLabelOpacity != null && topBackLabelOpacity.opacity.value < 1.0) {
+      midClickOpacity = animation.drive(Tween<double>(
+        begin: 0.0,
+        end: topBackLabelOpacity.opacity.value,
+      ));
+    }
+
+    // Pick up from an incoming transition from the large title. This is
+    // duplicated here from the bottomLargeTitle transition widget because the
+    // content text might be different. For instance, if the bottomLargeTitle
+    // text is too long, the topBackLabel will say 'Back' instead of the original
+    // text.
+    if (bottomLargeTitle != null &&
+        topBackLabel != null &&
+        bottomLargeExpanded) {
+      return PositionedTransition(
+        rect: animation.drive(slideFromLeadingEdge(
+          fromKey: bottomComponents.largeTitleKey,
+          fromNavBarBox: bottomNavBarBox,
+          toKey: topComponents.backLabelKey,
+          toNavBarBox: topNavBarBox,
+        )),
+        child: FadeTransition(
+          opacity: midClickOpacity ?? fadeInFrom(0.4),
+          child: DefaultTextStyleTransition(
+            style: animation.drive(TextStyleTween(
+              begin: bottomLargeTitleTextStyle,
+              end: topBackButtonTextStyle,
+            )),
+            maxLines: 1,
+            overflow: TextOverflow.ellipsis,
+            child: topBackLabel.child,
+          ),
+        ),
+      );
+    }
+
+    // The topBackLabel always comes from the large title first if available
+    // and expanded instead of middle.
+    if (bottomMiddle != null && topBackLabel != null) {
+      return PositionedTransition(
+        rect: animation.drive(slideFromLeadingEdge(
+          fromKey: bottomComponents.middleKey,
+          fromNavBarBox: bottomNavBarBox,
+          toKey: topComponents.backLabelKey,
+          toNavBarBox: topNavBarBox,
+        )),
+        child: FadeTransition(
+          opacity: midClickOpacity ?? fadeInFrom(0.3),
+          child: DefaultTextStyleTransition(
+            style: animation.drive(TextStyleTween(
+              begin: bottomTitleTextStyle,
+              end: topBackButtonTextStyle,
+            )),
+            child: topBackLabel.child,
+          ),
+        ),
+      );
+    }
+
+    return null;
+  }
+
+  Widget? get topMiddle {
+    final KeyedSubtree? topMiddle = topComponents.middleKey.currentWidget as KeyedSubtree?;
+
+    if (topMiddle == null) {
+      return null;
+    }
+
+    // The middle component is non-null when the nav bar is a large title
+    // nav bar but would be invisible when expanded, therefore don't show it here.
+    if (!topHasUserMiddle && topLargeExpanded) {
+      return null;
+    }
+
+    final RelativeRect to = positionInTransitionBox(topComponents.middleKey, from: topNavBarBox);
+
+    // Shift in from the trailing edge of the screen.
+    final RelativeRectTween positionTween = RelativeRectTween(
+      begin: to.shift(
+        Offset(
+          forwardDirection * topNavBarBox.size.width / 2.0,
+          0.0,
+        ),
+      ),
+      end: to,
+    );
+
+    return PositionedTransition(
+      rect: animation.drive(positionTween),
+      child: FadeTransition(
+        opacity: fadeInFrom(0.25),
+        child: DefaultTextStyle(
+          style: topTitleTextStyle,
+          child: topMiddle.child,
+        ),
+      ),
+    );
+  }
+
+  Widget? get topTrailing {
+    final KeyedSubtree? topTrailing = topComponents.trailingKey.currentWidget as KeyedSubtree?;
+
+    if (topTrailing == null) {
+      return null;
+    }
+
+    return Positioned.fromRelativeRect(
+      rect: positionInTransitionBox(topComponents.trailingKey, from: topNavBarBox),
+      child: FadeTransition(
+        opacity: fadeInFrom(0.4),
+        child: topTrailing.child,
+      ),
+    );
+  }
+
+  Widget? get topLargeTitle {
+    final KeyedSubtree? topLargeTitle = topComponents.largeTitleKey.currentWidget as KeyedSubtree?;
+
+    if (topLargeTitle == null || !topLargeExpanded) {
+      return null;
+    }
+
+    final RelativeRect to = positionInTransitionBox(topComponents.largeTitleKey, from: topNavBarBox);
+
+    // Shift in from the trailing edge of the screen.
+    final RelativeRectTween positionTween = RelativeRectTween(
+      begin: to.shift(
+        Offset(
+          forwardDirection * topNavBarBox.size.width,
+          0.0,
+        ),
+      ),
+      end: to,
+    );
+
+    return PositionedTransition(
+      rect: animation.drive(positionTween),
+      child: FadeTransition(
+        opacity: fadeInFrom(0.3),
+        child: DefaultTextStyle(
+          style: topLargeTitleTextStyle!,
+          maxLines: 1,
+          overflow: TextOverflow.ellipsis,
+          child: topLargeTitle.child,
+        ),
+      ),
+    );
+  }
+}
+
+/// Navigation bars' hero rect tween that will move between the static bars
+/// but keep a constant size that's the bigger of both navigation bars.
+CreateRectTween _linearTranslateWithLargestRectSizeTween = (Rect? begin, Rect? end) {
+  final Size largestSize = Size(
+    math.max(begin!.size.width, end!.size.width),
+    math.max(begin.size.height, end.size.height),
+  );
+  return RectTween(
+    begin: begin.topLeft & largestSize,
+    end: end.topLeft & largestSize,
+  );
+};
+
+final HeroPlaceholderBuilder _navBarHeroLaunchPadBuilder = (
+  BuildContext context,
+  Size heroSize,
+  Widget child,
+) {
+  assert(child is _TransitionableNavigationBar);
+  // Tree reshaping is fine here because the Heroes' child is always a
+  // _TransitionableNavigationBar which has a GlobalKey.
+
+  // Keeping the Hero subtree here is needed (instead of just swapping out the
+  // anchor nav bars for fixed size boxes during flights) because the nav bar
+  // and their specific component children may serve as anchor points again if
+  // another mid-transition flight diversion is triggered.
+
+  // This is ok performance-wise because static nav bars are generally cheap to
+  // build and layout but expensive to GPU render (due to clips and blurs) which
+  // we're skipping here.
+  return Visibility(
+    maintainSize: true,
+    maintainAnimation: true,
+    maintainState: true,
+    visible: false,
+    child: child,
+  );
+};
+
+/// Navigation bars' hero flight shuttle builder.
+final HeroFlightShuttleBuilder _navBarHeroFlightShuttleBuilder = (
+  BuildContext flightContext,
+  Animation<double> animation,
+  HeroFlightDirection flightDirection,
+  BuildContext fromHeroContext,
+  BuildContext toHeroContext,
+) {
+  assert(animation != null);
+  assert(flightDirection != null);
+  assert(fromHeroContext != null);
+  assert(toHeroContext != null);
+  assert(fromHeroContext.widget is Hero);
+  assert(toHeroContext.widget is Hero);
+
+  final Hero fromHeroWidget = fromHeroContext.widget as Hero;
+  final Hero toHeroWidget = toHeroContext.widget as Hero;
+
+  assert(fromHeroWidget.child is _TransitionableNavigationBar);
+  assert(toHeroWidget.child is _TransitionableNavigationBar);
+
+  final _TransitionableNavigationBar fromNavBar = fromHeroWidget.child as _TransitionableNavigationBar;
+  final _TransitionableNavigationBar toNavBar = toHeroWidget.child as _TransitionableNavigationBar;
+
+  assert(fromNavBar.componentsKeys != null);
+  assert(toNavBar.componentsKeys != null);
+
+  assert(
+    fromNavBar.componentsKeys.navBarBoxKey.currentContext!.owner != null,
+    'The from nav bar to Hero must have been mounted in the previous frame',
+  );
+  assert(
+    toNavBar.componentsKeys.navBarBoxKey.currentContext!.owner != null,
+    'The to nav bar to Hero must have been mounted in the previous frame',
+  );
+
+  switch (flightDirection) {
+    case HeroFlightDirection.push:
+      return _NavigationBarTransition(
+        animation: animation,
+        bottomNavBar: fromNavBar,
+        topNavBar: toNavBar,
+      );
+    case HeroFlightDirection.pop:
+      return _NavigationBarTransition(
+        animation: animation,
+        bottomNavBar: toNavBar,
+        topNavBar: fromNavBar,
+      );
+  }
+};
diff --git a/lib/src/cupertino/page_scaffold.dart b/lib/src/cupertino/page_scaffold.dart
new file mode 100644
index 0000000..c1e7e99
--- /dev/null
+++ b/lib/src/cupertino/page_scaffold.dart
@@ -0,0 +1,212 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'theme.dart';
+
+/// Implements a single iOS application page's layout.
+///
+/// The scaffold lays out the navigation bar on top and the content between or
+/// behind the navigation bar.
+///
+/// When tapping a status bar at the top of the CupertinoPageScaffold, an
+/// animation will complete for the current primary [ScrollView], scrolling to
+/// the beginning. This is done using the [PrimaryScrollController] that
+/// encloses the [ScrollView]. The [ScrollView.primary] flag is used to connect
+/// a [ScrollView] to the enclosing [PrimaryScrollController].
+///
+/// See also:
+///
+///  * [CupertinoTabScaffold], a similar widget for tabbed applications.
+///  * [CupertinoPageRoute], a modal page route that typically hosts a
+///    [CupertinoPageScaffold] with support for iOS-style page transitions.
+class CupertinoPageScaffold extends StatefulWidget {
+  /// Creates a layout for pages with a navigation bar at the top.
+  const CupertinoPageScaffold({
+    Key? key,
+    this.navigationBar,
+    this.backgroundColor,
+    this.resizeToAvoidBottomInset = true,
+    required this.child,
+  }) : assert(child != null),
+       assert(resizeToAvoidBottomInset != null),
+       super(key: key);
+
+  /// The [navigationBar], typically a [CupertinoNavigationBar], is drawn at the
+  /// top of the screen.
+  ///
+  /// If translucent, the main content may slide behind it.
+  /// Otherwise, the main content's top margin will be offset by its height.
+  ///
+  /// The scaffold assumes the navigation bar will account for the [MediaQuery]
+  /// top padding, also consume it if the navigation bar is opaque.
+  ///
+  /// By default `navigationBar` has its text scale factor set to 1.0 and does
+  /// not respond to text scale factor changes from the operating system, to match
+  /// the native iOS behavior. To override such behavior, wrap each of the `navigationBar`'s
+  /// components inside a [MediaQuery] with the desired [MediaQueryData.textScaleFactor]
+  /// value. The text scale factor value from the operating system can be retrieved
+  /// in many ways, such as querying [MediaQuery.textScaleFactorOf] against
+  /// [CupertinoApp]'s [BuildContext].
+  // TODO(xster): document its page transition animation when ready
+  final ObstructingPreferredSizeWidget? navigationBar;
+
+  /// Widget to show in the main content area.
+  ///
+  /// Content can slide under the [navigationBar] when they're translucent.
+  /// In that case, the child's [BuildContext]'s [MediaQuery] will have a
+  /// top padding indicating the area of obstructing overlap from the
+  /// [navigationBar].
+  final Widget child;
+
+  /// The color of the widget that underlies the entire scaffold.
+  ///
+  /// By default uses [CupertinoTheme]'s `scaffoldBackgroundColor` when null.
+  final Color? backgroundColor;
+
+  /// Whether the [child] should size itself to avoid the window's bottom inset.
+  ///
+  /// For example, if there is an onscreen keyboard displayed above the
+  /// scaffold, the body can be resized to avoid overlapping the keyboard, which
+  /// prevents widgets inside the body from being obscured by the keyboard.
+  ///
+  /// Defaults to true and cannot be null.
+  final bool resizeToAvoidBottomInset;
+
+  @override
+  _CupertinoPageScaffoldState createState() => _CupertinoPageScaffoldState();
+}
+
+class _CupertinoPageScaffoldState extends State<CupertinoPageScaffold> {
+
+  void _handleStatusBarTap() {
+    final ScrollController? _primaryScrollController = PrimaryScrollController.of(context);
+    // Only act on the scroll controller if it has any attached scroll positions.
+    if (_primaryScrollController != null && _primaryScrollController.hasClients) {
+      _primaryScrollController.animateTo(
+        0.0,
+        // Eyeballed from iOS.
+        duration: const Duration(milliseconds: 500),
+        curve: Curves.linearToEaseOut,
+      );
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    Widget paddedContent = widget.child;
+
+    final MediaQueryData existingMediaQuery = MediaQuery.of(context);
+    if (widget.navigationBar != null) {
+      // TODO(xster): Use real size after partial layout instead of preferred size.
+      // https://github.com/flutter/flutter/issues/12912
+      final double topPadding =
+          widget.navigationBar!.preferredSize.height + existingMediaQuery.padding.top;
+
+      // Propagate bottom padding and include viewInsets if appropriate
+      final double bottomPadding = widget.resizeToAvoidBottomInset
+          ? existingMediaQuery.viewInsets.bottom
+          : 0.0;
+
+      final EdgeInsets newViewInsets = widget.resizeToAvoidBottomInset
+          // The insets are consumed by the scaffolds and no longer exposed to
+          // the descendant subtree.
+          ? existingMediaQuery.viewInsets.copyWith(bottom: 0.0)
+          : existingMediaQuery.viewInsets;
+
+      final bool fullObstruction = widget.navigationBar!.shouldFullyObstruct(context);
+
+      // If navigation bar is opaquely obstructing, directly shift the main content
+      // down. If translucent, let main content draw behind navigation bar but hint the
+      // obstructed area.
+      if (fullObstruction) {
+        paddedContent = MediaQuery(
+          data: existingMediaQuery
+          // If the navigation bar is opaque, the top media query padding is fully consumed by the navigation bar.
+          .removePadding(removeTop: true)
+          .copyWith(
+            viewInsets: newViewInsets,
+          ),
+          child: Padding(
+            padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
+            child: paddedContent,
+          ),
+        );
+      } else {
+        paddedContent = MediaQuery(
+          data: existingMediaQuery.copyWith(
+            padding: existingMediaQuery.padding.copyWith(
+              top: topPadding,
+            ),
+            viewInsets: newViewInsets,
+          ),
+          child: Padding(
+            padding: EdgeInsets.only(bottom: bottomPadding),
+            child: paddedContent,
+          ),
+        );
+      }
+    } else {
+      // If there is no navigation bar, still may need to add padding in order
+      // to support resizeToAvoidBottomInset.
+      final double bottomPadding = widget.resizeToAvoidBottomInset
+          ? existingMediaQuery.viewInsets.bottom
+          : 0.0;
+      paddedContent = Padding(
+        padding: EdgeInsets.only(bottom: bottomPadding),
+        child: paddedContent,
+      );
+    }
+
+    return DecoratedBox(
+      decoration: BoxDecoration(
+        color: CupertinoDynamicColor.maybeResolve(widget.backgroundColor, context)
+            ?? CupertinoTheme.of(context).scaffoldBackgroundColor,
+      ),
+      child: Stack(
+        children: <Widget>[
+          // The main content being at the bottom is added to the stack first.
+          paddedContent,
+          if (widget.navigationBar != null)
+            Positioned(
+              top: 0.0,
+              left: 0.0,
+              right: 0.0,
+              child: MediaQuery(
+                data: existingMediaQuery.copyWith(textScaleFactor: 1),
+                child: widget.navigationBar!,
+              ),
+            ),
+          // Add a touch handler the size of the status bar on top of all contents
+          // to handle scroll to top by status bar taps.
+          Positioned(
+            top: 0.0,
+            left: 0.0,
+            right: 0.0,
+            height: existingMediaQuery.padding.top,
+            child: GestureDetector(
+              excludeFromSemantics: true,
+              onTap: _handleStatusBarTap,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+/// Widget that has a preferred size and reports whether it fully obstructs
+/// widgets behind it.
+///
+/// Used by [CupertinoPageScaffold] to either shift away fully obstructed content
+/// or provide a padding guide to partially obstructed content.
+abstract class ObstructingPreferredSizeWidget implements PreferredSizeWidget {
+  /// If true, this widget fully obstructs widgets behind it by the specified
+  /// size.
+  ///
+  /// If false, this widget partially obstructs.
+  bool shouldFullyObstruct(BuildContext context);
+}
diff --git a/lib/src/cupertino/picker.dart b/lib/src/cupertino/picker.dart
new file mode 100644
index 0000000..59ba8b5
--- /dev/null
+++ b/lib/src/cupertino/picker.dart
@@ -0,0 +1,510 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'theme.dart';
+
+// Eyeballed values comparing with a native picker to produce the right
+// curvatures and densities.
+const double _kDefaultDiameterRatio = 1.07;
+const double _kDefaultPerspective = 0.003;
+const double _kSqueeze = 1.45;
+
+// Opacity fraction value that dims the wheel above and below the "magnifier"
+// lens.
+const double _kOverAndUnderCenterOpacity = 0.447;
+
+/// An iOS-styled picker.
+///
+/// Displays its children widgets on a wheel for selection and
+/// calls back when the currently selected item changes.
+///
+/// By default, the first child in `children` will be the initially selected child.
+/// The index of a different child can be specified in [scrollController], to make
+/// that child the initially selected child.
+///
+/// Can be used with [showCupertinoModalPopup] to display the picker modally at the
+/// bottom of the screen. When calling [showCupertinoModalPopup], be sure to set
+/// `semanticsDismissible` to true to enable dismissing the modal via semantics.
+///
+/// Sizes itself to its parent. All children are sized to the same size based
+/// on [itemExtent].
+///
+/// By default, descendent texts are shown with [CupertinoTextThemeData.pickerTextStyle].
+///
+/// See also:
+///
+///  * [ListWheelScrollView], the generic widget backing this picker without
+///    the iOS design specific chrome.
+///  * <https://developer.apple.com/ios/human-interface-guidelines/controls/pickers/>
+class CupertinoPicker extends StatefulWidget {
+  /// Creates a picker from a concrete list of children.
+  ///
+  /// The [diameterRatio] and [itemExtent] arguments must not be null. The
+  /// [itemExtent] must be greater than zero.
+  ///
+  /// The [backgroundColor] defaults to null, which disables background painting entirely.
+  /// (i.e. the picker is going to have a completely transparent background), to match
+  /// the native UIPicker and UIDatePicker. Also, if it has transparency, no gradient
+  /// effect will be rendered.
+  ///
+  /// The [scrollController] argument can be used to specify a custom
+  /// [FixedExtentScrollController] for programmatically reading or changing
+  /// the current picker index or for selecting an initial index value.
+  ///
+  /// The [looping] argument decides whether the child list loops and can be
+  /// scrolled infinitely.  If set to true, scrolling past the end of the list
+  /// will loop the list back to the beginning.  If set to false, the list will
+  /// stop scrolling when you reach the end or the beginning.
+  CupertinoPicker({
+    Key? key,
+    this.diameterRatio = _kDefaultDiameterRatio,
+    this.backgroundColor,
+    this.offAxisFraction = 0.0,
+    this.useMagnifier = false,
+    this.magnification = 1.0,
+    this.scrollController,
+    this.squeeze = _kSqueeze,
+    required this.itemExtent,
+    required this.onSelectedItemChanged,
+    required List<Widget> children,
+    this.selectionOverlay = const CupertinoPickerDefaultSelectionOverlay(),
+    bool looping = false,
+  }) : assert(children != null),
+       assert(diameterRatio != null),
+       assert(diameterRatio > 0.0, RenderListWheelViewport.diameterRatioZeroMessage),
+       assert(magnification > 0),
+       assert(itemExtent != null),
+       assert(itemExtent > 0),
+       assert(squeeze != null),
+       assert(squeeze > 0),
+       childDelegate = looping
+                       ? ListWheelChildLoopingListDelegate(children: children)
+                       : ListWheelChildListDelegate(children: children),
+       super(key: key);
+
+  /// Creates a picker from an [IndexedWidgetBuilder] callback where the builder
+  /// is dynamically invoked during layout.
+  ///
+  /// A child is lazily created when it starts becoming visible in the viewport.
+  /// All of the children provided by the builder are cached and reused, so
+  /// normally the builder is only called once for each index (except when
+  /// rebuilding - the cache is cleared).
+  ///
+  /// The [itemBuilder] argument must not be null. The [childCount] argument
+  /// reflects the number of children that will be provided by the [itemBuilder].
+  /// {@macro flutter.widgets.ListWheelChildBuilderDelegate.childCount}
+  ///
+  /// The [itemExtent] argument must be non-null and positive.
+  ///
+  /// The [backgroundColor] defaults to null, which disables background painting entirely.
+  /// (i.e. the picker is going to have a completely transparent background), to match
+  /// the native UIPicker and UIDatePicker.
+  CupertinoPicker.builder({
+    Key? key,
+    this.diameterRatio = _kDefaultDiameterRatio,
+    this.backgroundColor,
+    this.offAxisFraction = 0.0,
+    this.useMagnifier = false,
+    this.magnification = 1.0,
+    this.scrollController,
+    this.squeeze = _kSqueeze,
+    required this.itemExtent,
+    required this.onSelectedItemChanged,
+    required NullableIndexedWidgetBuilder itemBuilder,
+    int? childCount,
+    this.selectionOverlay = const CupertinoPickerDefaultSelectionOverlay(),
+  }) : assert(itemBuilder != null),
+       assert(diameterRatio != null),
+       assert(diameterRatio > 0.0, RenderListWheelViewport.diameterRatioZeroMessage),
+       assert(magnification > 0),
+       assert(itemExtent != null),
+       assert(itemExtent > 0),
+       assert(squeeze != null),
+       assert(squeeze > 0),
+       childDelegate = ListWheelChildBuilderDelegate(builder: itemBuilder, childCount: childCount),
+       super(key: key);
+
+  /// Relative ratio between this picker's height and the simulated cylinder's diameter.
+  ///
+  /// Smaller values creates more pronounced curvatures in the scrollable wheel.
+  ///
+  /// For more details, see [ListWheelScrollView.diameterRatio].
+  ///
+  /// Must not be null and defaults to `1.1` to visually mimic iOS.
+  final double diameterRatio;
+
+  /// Background color behind the children.
+  ///
+  /// Defaults to null, which disables background painting entirely.
+  /// (i.e. the picker is going to have a completely transparent background), to match
+  /// the native UIPicker and UIDatePicker.
+  ///
+  /// Any alpha value less 255 (fully opaque) will cause the removal of the
+  /// wheel list edge fade gradient from rendering of the widget.
+  final Color? backgroundColor;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.offAxisFraction}
+  final double offAxisFraction;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.useMagnifier}
+  final bool useMagnifier;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.magnification}
+  final double magnification;
+
+  /// A [FixedExtentScrollController] to read and control the current item, and
+  /// to set the initial item.
+  ///
+  /// If null, an implicit one will be created internally.
+  final FixedExtentScrollController? scrollController;
+
+  /// The uniform height of all children.
+  ///
+  /// All children will be given the [BoxConstraints] to match this exact
+  /// height. Must not be null and must be positive.
+  final double itemExtent;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.squeeze}
+  ///
+  /// Defaults to `1.45` to visually mimic iOS.
+  final double squeeze;
+
+  /// An option callback when the currently centered item changes.
+  ///
+  /// Value changes when the item closest to the center changes.
+  ///
+  /// This can be called during scrolls and during ballistic flings. To get the
+  /// value only when the scrolling settles, use a [NotificationListener],
+  /// listen for [ScrollEndNotification] and read its [FixedExtentMetrics].
+  final ValueChanged<int>? onSelectedItemChanged;
+
+  /// A delegate that lazily instantiates children.
+  final ListWheelChildDelegate childDelegate;
+
+  /// A widget overlaid on the picker to highlight the currently selected entry.
+  ///
+  /// The [selectionOverlay] widget drawn above the [CupertinoPicker]'s picker
+  /// wheel.
+  /// It is vertically centered in the picker and is constrained to have the
+  /// same height as the center row.
+  ///
+  /// If unspecified, it defaults to a [CupertinoPickerDefaultSelectionOverlay]
+  /// which is a gray rounded rectangle overlay in iOS 14 style.
+  /// This property can be set to null to remove the overlay.
+  final Widget selectionOverlay;
+
+  @override
+  State<StatefulWidget> createState() => _CupertinoPickerState();
+}
+
+class _CupertinoPickerState extends State<CupertinoPicker> {
+  int? _lastHapticIndex;
+  FixedExtentScrollController? _controller;
+
+  @override
+  void initState() {
+    super.initState();
+    if (widget.scrollController == null) {
+      _controller = FixedExtentScrollController();
+    }
+  }
+
+  @override
+  void didUpdateWidget(CupertinoPicker oldWidget) {
+    if (widget.scrollController != null && oldWidget.scrollController == null) {
+      _controller = null;
+    } else if (widget.scrollController == null && oldWidget.scrollController != null) {
+      assert(_controller == null);
+      _controller = FixedExtentScrollController();
+    }
+    super.didUpdateWidget(oldWidget);
+  }
+
+  @override
+  void dispose() {
+    _controller?.dispose();
+    super.dispose();
+  }
+
+  void _handleSelectedItemChanged(int index) {
+    // Only the haptic engine hardware on iOS devices would produce the
+    // intended effects.
+    final bool hasSuitableHapticHardware;
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.iOS:
+        hasSuitableHapticHardware = true;
+        break;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.macOS:
+      case TargetPlatform.windows:
+        hasSuitableHapticHardware = false;
+        break;
+    }
+    assert(hasSuitableHapticHardware != null);
+    if (hasSuitableHapticHardware && index != _lastHapticIndex) {
+      _lastHapticIndex = index;
+      HapticFeedback.selectionClick();
+    }
+
+    if (widget.onSelectedItemChanged != null) {
+      widget.onSelectedItemChanged!(index);
+    }
+  }
+
+  /// Draws the selectionOverlay.
+  Widget _buildSelectionOverlay(Widget selectionOverlay) {
+    final double height = widget.itemExtent * widget.magnification;
+
+    return IgnorePointer(
+      child: Center(
+        child: ConstrainedBox(
+          constraints: BoxConstraints.expand(
+            height: height,
+          ),
+          child: selectionOverlay,
+        ),
+      ),
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final Color? resolvedBackgroundColor = CupertinoDynamicColor.maybeResolve(widget.backgroundColor, context);
+
+    final Widget result = DefaultTextStyle(
+      style: CupertinoTheme.of(context).textTheme.pickerTextStyle,
+      child: Stack(
+        children: <Widget>[
+          Positioned.fill(
+            child: _CupertinoPickerSemantics(
+              scrollController: widget.scrollController ?? _controller!,
+              child: ListWheelScrollView.useDelegate(
+                controller: widget.scrollController ?? _controller,
+                physics: const FixedExtentScrollPhysics(),
+                diameterRatio: widget.diameterRatio,
+                perspective: _kDefaultPerspective,
+                offAxisFraction: widget.offAxisFraction,
+                useMagnifier: widget.useMagnifier,
+                magnification: widget.magnification,
+                overAndUnderCenterOpacity: _kOverAndUnderCenterOpacity,
+                itemExtent: widget.itemExtent,
+                squeeze: widget.squeeze,
+                onSelectedItemChanged: _handleSelectedItemChanged,
+                childDelegate: widget.childDelegate,
+              ),
+            ),
+          ),
+          _buildSelectionOverlay(widget.selectionOverlay),
+        ],
+      ),
+    );
+
+    return DecoratedBox(
+      decoration: BoxDecoration(color: resolvedBackgroundColor),
+      child: result,
+    );
+  }
+}
+
+/// A default selection overlay for [CupertinoPicker]s.
+///
+/// It draws a gray rounded rectangle to match the picker visuals introduced in
+/// iOS 14.
+///
+/// This widget is typically only used in [CupertinoPicker.selectionOverlay].
+/// In an iOS 14 multi-column picker, the selection overlay is a single rounded
+/// rectangle that spans the entire multi-column picker.
+/// To achieve the same effect using [CupertinoPickerDefaultSelectionOverlay],
+/// the additional margin and corner radii on the left or the right side can be
+/// disabled by turning off [capLeftEdge] and [capRightEdge], so this selection
+/// overlay visually connects with selection overlays of adjoining
+/// [CupertinoPicker]s (i.e., other "column"s).
+///
+/// See also:
+///
+///  * [CupertinoPicker], which uses this widget as its default [CupertinoPicker.selectionOverlay].
+class CupertinoPickerDefaultSelectionOverlay extends StatelessWidget {
+
+  /// Creates an iOS 14 style selection overlay that highlights the magnified
+  /// area (or the currently selected item, depending on how you described it
+  /// elsewhere) of a [CupertinoPicker].
+  ///
+  /// The [background] argument default value is [CupertinoColors.tertiarySystemFill].
+  /// It must be non-null.
+  ///
+  /// The [capLeftEdge] and [capRightEdge] arguments decide whether to add a
+  /// default margin and use rounded corners on the left and right side of the
+  /// rectangular overlay.
+  /// Default to true and must not be null.
+  const CupertinoPickerDefaultSelectionOverlay({
+    Key? key,
+    this.background = CupertinoColors.tertiarySystemFill,
+    this.capLeftEdge = true,
+    this.capRightEdge = true,
+  }) : assert(background != null),
+       assert(capLeftEdge != null),
+       assert(capRightEdge != null),
+       super(key: key);
+
+  /// Whether to use the default use rounded corners and margin on the left side.
+  final bool capLeftEdge;
+
+  /// Whether to use the default use rounded corners and margin on the right side.
+  final bool capRightEdge;
+
+  /// The color to fill in the background of the [CupertinoPickerDefaultSelectionOverlay].
+  /// It Support for use [CupertinoDynamicColor].
+  ///
+  /// Typically this should not be set to a fully opaque color, as the currently
+  /// selected item of the underlying [CupertinoPicker] should remain visible.
+  /// Defaults to [CupertinoColors.tertiarySystemFill].
+  final Color background;
+
+  /// Default margin of the 'SelectionOverlay'.
+  static const double _defaultSelectionOverlayHorizontalMargin = 9;
+
+  /// Default radius of the 'SelectionOverlay'.
+  static const double _defaultSelectionOverlayRadius = 8;
+
+  @override
+  Widget build(BuildContext context) {
+    const Radius radius = Radius.circular(_defaultSelectionOverlayRadius);
+
+    return Container(
+      margin: EdgeInsets.only(
+        left: capLeftEdge ? _defaultSelectionOverlayHorizontalMargin : 0,
+        right: capRightEdge ? _defaultSelectionOverlayHorizontalMargin : 0,
+      ),
+      decoration: BoxDecoration(
+        borderRadius: BorderRadius.horizontal(
+          left: capLeftEdge ? radius : Radius.zero,
+          right: capRightEdge ? radius : Radius.zero,
+        ),
+        color: CupertinoDynamicColor.resolve(background, context),
+      ),
+    );
+  }
+}
+
+// Turns the scroll semantics of the ListView into a single adjustable semantics
+// node. This is done by removing all of the child semantics of the scroll
+// wheel and using the scroll indexes to look up the current, previous, and
+// next semantic label. This label is then turned into the value of a new
+// adjustable semantic node, with adjustment callbacks wired to move the
+// scroll controller.
+class _CupertinoPickerSemantics extends SingleChildRenderObjectWidget {
+  const _CupertinoPickerSemantics({
+    Key? key,
+    Widget? child,
+    required this.scrollController,
+  }) : super(key: key, child: child);
+
+  final FixedExtentScrollController scrollController;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    assert(debugCheckHasDirectionality(context));
+    return _RenderCupertinoPickerSemantics(scrollController, Directionality.of(context));
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, covariant _RenderCupertinoPickerSemantics renderObject) {
+    assert(debugCheckHasDirectionality(context));
+    renderObject
+      ..textDirection = Directionality.of(context)
+      ..controller = scrollController;
+  }
+}
+
+class _RenderCupertinoPickerSemantics extends RenderProxyBox {
+  _RenderCupertinoPickerSemantics(FixedExtentScrollController controller, this._textDirection) {
+    _updateController(null, controller);
+  }
+
+  FixedExtentScrollController get controller => _controller;
+  late FixedExtentScrollController _controller;
+  set controller(FixedExtentScrollController value) => _updateController(_controller, value);
+
+  // This method exists to allow controller to be non-null. It is only called with a null oldValue from construtor.
+  void _updateController(FixedExtentScrollController? oldValue, FixedExtentScrollController value) {
+    if (value == oldValue)
+      return;
+    if (oldValue != null)
+      oldValue.removeListener(_handleScrollUpdate);
+    else
+      _currentIndex = value.initialItem;
+    value.addListener(_handleScrollUpdate);
+    _controller = value;
+  }
+
+  TextDirection get textDirection => _textDirection;
+  TextDirection _textDirection;
+  set textDirection(TextDirection value) {
+    if (textDirection == value)
+      return;
+    _textDirection = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  int _currentIndex = 0;
+
+  void _handleIncrease() {
+    controller.jumpToItem(_currentIndex + 1);
+  }
+
+  void _handleDecrease() {
+    if (_currentIndex == 0)
+      return;
+    controller.jumpToItem(_currentIndex - 1);
+  }
+
+  void _handleScrollUpdate() {
+    if (controller.selectedItem == _currentIndex)
+      return;
+    _currentIndex = controller.selectedItem;
+    markNeedsSemanticsUpdate();
+  }
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    config.isSemanticBoundary = true;
+    config.textDirection = textDirection;
+  }
+
+  @override
+  void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, Iterable<SemanticsNode> children) {
+    if (children.isEmpty)
+      return super.assembleSemanticsNode(node, config, children);
+    final SemanticsNode scrollable = children.first;
+    final Map<int, SemanticsNode> indexedChildren = <int, SemanticsNode>{};
+    scrollable.visitChildren((SemanticsNode child) {
+      assert(child.indexInParent != null);
+      indexedChildren[child.indexInParent!] = child;
+      return true;
+    });
+    if (indexedChildren[_currentIndex] == null) {
+      return node.updateWith(config: config);
+    }
+    config.value = indexedChildren[_currentIndex]!.label;
+    final SemanticsNode? previousChild = indexedChildren[_currentIndex - 1];
+    final SemanticsNode? nextChild = indexedChildren[_currentIndex + 1];
+    if (nextChild != null) {
+      config.increasedValue = nextChild.label;
+      config.onIncrease = _handleIncrease;
+    }
+    if (previousChild != null) {
+      config.decreasedValue = previousChild.label;
+      config.onDecrease = _handleDecrease;
+    }
+    node.updateWith(config: config);
+  }
+}
diff --git a/lib/src/cupertino/refresh.dart b/lib/src/cupertino/refresh.dart
new file mode 100644
index 0000000..ab1ae5c
--- /dev/null
+++ b/lib/src/cupertino/refresh.dart
@@ -0,0 +1,622 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:math';
+
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'activity_indicator.dart';
+
+const double _kActivityIndicatorRadius = 14.0;
+const double _kActivityIndicatorMargin = 16.0;
+
+class _CupertinoSliverRefresh extends SingleChildRenderObjectWidget {
+  const _CupertinoSliverRefresh({
+    Key? key,
+    this.refreshIndicatorLayoutExtent = 0.0,
+    this.hasLayoutExtent = false,
+    Widget? child,
+  }) : assert(refreshIndicatorLayoutExtent != null),
+       assert(refreshIndicatorLayoutExtent >= 0.0),
+       assert(hasLayoutExtent != null),
+       super(key: key, child: child);
+
+  // The amount of space the indicator should occupy in the sliver in a
+  // resting state when in the refreshing mode.
+  final double refreshIndicatorLayoutExtent;
+
+  // _RenderCupertinoSliverRefresh will paint the child in the available
+  // space either way but this instructs the _RenderCupertinoSliverRefresh
+  // on whether to also occupy any layoutExtent space or not.
+  final bool hasLayoutExtent;
+
+  @override
+  _RenderCupertinoSliverRefresh createRenderObject(BuildContext context) {
+    return _RenderCupertinoSliverRefresh(
+      refreshIndicatorExtent: refreshIndicatorLayoutExtent,
+      hasLayoutExtent: hasLayoutExtent,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, covariant _RenderCupertinoSliverRefresh renderObject) {
+    renderObject
+      ..refreshIndicatorLayoutExtent = refreshIndicatorLayoutExtent
+      ..hasLayoutExtent = hasLayoutExtent;
+  }
+}
+
+// RenderSliver object that gives its child RenderBox object space to paint
+// in the overscrolled gap and may or may not hold that overscrolled gap
+// around the RenderBox depending on whether [layoutExtent] is set.
+//
+// The [layoutExtentOffsetCompensation] field keeps internal accounting to
+// prevent scroll position jumps as the [layoutExtent] is set and unset.
+class _RenderCupertinoSliverRefresh extends RenderSliver
+    with RenderObjectWithChildMixin<RenderBox> {
+  _RenderCupertinoSliverRefresh({
+    required double refreshIndicatorExtent,
+    required bool hasLayoutExtent,
+    RenderBox? child,
+  }) : assert(refreshIndicatorExtent != null),
+       assert(refreshIndicatorExtent >= 0.0),
+       assert(hasLayoutExtent != null),
+       _refreshIndicatorExtent = refreshIndicatorExtent,
+       _hasLayoutExtent = hasLayoutExtent {
+    this.child = child;
+  }
+
+  // The amount of layout space the indicator should occupy in the sliver in a
+  // resting state when in the refreshing mode.
+  double get refreshIndicatorLayoutExtent => _refreshIndicatorExtent;
+  double _refreshIndicatorExtent;
+  set refreshIndicatorLayoutExtent(double value) {
+    assert(value != null);
+    assert(value >= 0.0);
+    if (value == _refreshIndicatorExtent)
+      return;
+    _refreshIndicatorExtent = value;
+    markNeedsLayout();
+  }
+
+  // The child box will be laid out and painted in the available space either
+  // way but this determines whether to also occupy any
+  // [SliverGeometry.layoutExtent] space or not.
+  bool get hasLayoutExtent => _hasLayoutExtent;
+  bool _hasLayoutExtent;
+  set hasLayoutExtent(bool value) {
+    assert(value != null);
+    if (value == _hasLayoutExtent)
+      return;
+    _hasLayoutExtent = value;
+    markNeedsLayout();
+  }
+
+  // This keeps track of the previously applied scroll offsets to the scrollable
+  // so that when [refreshIndicatorLayoutExtent] or [hasLayoutExtent] changes,
+  // the appropriate delta can be applied to keep everything in the same place
+  // visually.
+  double layoutExtentOffsetCompensation = 0.0;
+
+  @override
+  void performLayout() {
+    final SliverConstraints constraints = this.constraints;
+    // Only pulling to refresh from the top is currently supported.
+    assert(constraints.axisDirection == AxisDirection.down);
+    assert(constraints.growthDirection == GrowthDirection.forward);
+
+    // The new layout extent this sliver should now have.
+    final double layoutExtent =
+        (_hasLayoutExtent ? 1.0 : 0.0) * _refreshIndicatorExtent;
+    // If the new layoutExtent instructive changed, the SliverGeometry's
+    // layoutExtent will take that value (on the next performLayout run). Shift
+    // the scroll offset first so it doesn't make the scroll position suddenly jump.
+    if (layoutExtent != layoutExtentOffsetCompensation) {
+      geometry = SliverGeometry(
+        scrollOffsetCorrection: layoutExtent - layoutExtentOffsetCompensation,
+      );
+      layoutExtentOffsetCompensation = layoutExtent;
+      // Return so we don't have to do temporary accounting and adjusting the
+      // child's constraints accounting for this one transient frame using a
+      // combination of existing layout extent, new layout extent change and
+      // the overlap.
+      return;
+    }
+
+    final bool active = constraints.overlap < 0.0 || layoutExtent > 0.0;
+    final double overscrolledExtent =
+        constraints.overlap < 0.0 ? constraints.overlap.abs() : 0.0;
+    // Layout the child giving it the space of the currently dragged overscroll
+    // which may or may not include a sliver layout extent space that it will
+    // keep after the user lets go during the refresh process.
+    child!.layout(
+      constraints.asBoxConstraints(
+        maxExtent: layoutExtent
+            // Plus only the overscrolled portion immediately preceding this
+            // sliver.
+            + overscrolledExtent,
+      ),
+      parentUsesSize: true,
+    );
+    if (active) {
+      geometry = SliverGeometry(
+        scrollExtent: layoutExtent,
+        paintOrigin: -overscrolledExtent - constraints.scrollOffset,
+        paintExtent: max(
+          // Check child size (which can come from overscroll) because
+          // layoutExtent may be zero. Check layoutExtent also since even
+          // with a layoutExtent, the indicator builder may decide to not
+          // build anything.
+          max(child!.size.height, layoutExtent) - constraints.scrollOffset,
+          0.0,
+        ),
+        maxPaintExtent: max(
+          max(child!.size.height, layoutExtent) - constraints.scrollOffset,
+          0.0,
+        ),
+        layoutExtent: max(layoutExtent - constraints.scrollOffset, 0.0),
+      );
+    } else {
+      // If we never started overscrolling, return no geometry.
+      geometry = SliverGeometry.zero;
+    }
+  }
+
+  @override
+  void paint(PaintingContext paintContext, Offset offset) {
+    if (constraints.overlap < 0.0 ||
+        constraints.scrollOffset + child!.size.height > 0) {
+      paintContext.paintChild(child!, offset);
+    }
+  }
+
+  // Nothing special done here because this sliver always paints its child
+  // exactly between paintOrigin and paintExtent.
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) { }
+}
+
+/// The current state of the refresh control.
+///
+/// Passed into the [RefreshControlIndicatorBuilder] builder function so
+/// users can show different UI in different modes.
+enum RefreshIndicatorMode {
+  /// Initial state, when not being overscrolled into, or after the overscroll
+  /// is canceled or after done and the sliver retracted away.
+  inactive,
+
+  /// While being overscrolled but not far enough yet to trigger the refresh.
+  drag,
+
+  /// Dragged far enough that the onRefresh callback will run and the dragged
+  /// displacement is not yet at the final refresh resting state.
+  armed,
+
+  /// While the onRefresh task is running.
+  refresh,
+
+  /// While the indicator is animating away after refreshing.
+  done,
+}
+
+/// Signature for a builder that can create a different widget to show in the
+/// refresh indicator space depending on the current state of the refresh
+/// control and the space available.
+///
+/// The `refreshTriggerPullDistance` and `refreshIndicatorExtent` parameters are
+/// the same values passed into the [CupertinoSliverRefreshControl].
+///
+/// The `pulledExtent` parameter is the currently available space either from
+/// overscrolling or as held by the sliver during refresh.
+typedef RefreshControlIndicatorBuilder = Widget Function(
+  BuildContext context,
+  RefreshIndicatorMode refreshState,
+  double pulledExtent,
+  double refreshTriggerPullDistance,
+  double refreshIndicatorExtent,
+);
+
+/// A callback function that's invoked when the [CupertinoSliverRefreshControl] is
+/// pulled a `refreshTriggerPullDistance`. Must return a [Future]. Upon
+/// completion of the [Future], the [CupertinoSliverRefreshControl] enters the
+/// [RefreshIndicatorMode.done] state and will start to go away.
+typedef RefreshCallback = Future<void> Function();
+
+/// A sliver widget implementing the iOS-style pull to refresh content control.
+///
+/// When inserted as the first sliver in a scroll view or behind other slivers
+/// that still lets the scrollable overscroll in front of this sliver (such as
+/// the [CupertinoSliverNavigationBar], this widget will:
+///
+///  * Let the user draw inside the overscrolled area via the passed in [builder].
+///  * Trigger the provided [onRefresh] function when overscrolled far enough to
+///    pass [refreshTriggerPullDistance].
+///  * Continue to hold [refreshIndicatorExtent] amount of space for the [builder]
+///    to keep drawing inside of as the [Future] returned by [onRefresh] processes.
+///  * Scroll away once the [onRefresh] [Future] completes.
+///
+/// The [builder] function will be informed of the current [RefreshIndicatorMode]
+/// when invoking it, except in the [RefreshIndicatorMode.inactive] state when
+/// no space is available and nothing needs to be built. The [builder] function
+/// will otherwise be continuously invoked as the amount of space available
+/// changes from overscroll, as the sliver scrolls away after the [onRefresh]
+/// task is done, etc.
+///
+/// Only one refresh can be triggered until the previous refresh has completed
+/// and the indicator sliver has retracted at least 90% of the way back.
+///
+/// Can only be used in downward-scrolling vertical lists that overscrolls. In
+/// other words, refreshes can't be triggered with [Scrollable]s using
+/// [ClampingScrollPhysics] which is the default on Android. To allow overscroll
+/// on Android, use an overscrolling physics such as [BouncingScrollPhysics].
+/// This can be done via:
+///
+///  * Providing a [BouncingScrollPhysics] (possibly in combination with a
+///    [AlwaysScrollableScrollPhysics]) while constructing the scrollable.
+///  * By inserting a [ScrollConfiguration] with [BouncingScrollPhysics] above
+///    the scrollable.
+///  * By using [CupertinoApp], which always uses a [ScrollConfiguration]
+///    with [BouncingScrollPhysics] regardless of platform.
+///
+/// In a typical application, this sliver should be inserted between the app bar
+/// sliver such as [CupertinoSliverNavigationBar] and your main scrollable
+/// content's sliver.
+///
+/// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+///
+/// When the user scrolls past [refreshTriggerPullDistance],
+/// this sample shows the default iOS pull to refresh indicator for 1 second and
+/// adds a new item to the top of the list view.
+///
+/// ```dart imports
+/// import 'package:flute/cupertino.dart';
+/// ```
+///
+/// ```dart
+/// List<Color> colors = [
+///   CupertinoColors.systemYellow,
+///   CupertinoColors.systemOrange,
+///   CupertinoColors.systemPink
+/// ];
+/// List<Widget> items = [
+///   Container(color: CupertinoColors.systemPink, height: 100.0),
+///   Container(color: CupertinoColors.systemOrange, height: 100.0),
+///   Container(color: CupertinoColors.systemYellow, height: 100.0),
+/// ];
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return CupertinoApp(
+///     home: CupertinoPageScaffold(
+///       child: CustomScrollView(
+///         physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
+///         slivers: <Widget>[
+///           const CupertinoSliverNavigationBar(largeTitle: Text('Scroll down')),
+///           CupertinoSliverRefreshControl(
+///             refreshTriggerPullDistance: 100.0,
+///             refreshIndicatorExtent: 60.0,
+///             onRefresh: () async {
+///               await Future.delayed(Duration(milliseconds: 1000));
+///               setState(() {
+///                 items.insert(0, Container(color: colors[items.length % 3], height: 100.0));
+///               });
+///             },
+///           ),
+///           SliverList(
+///             delegate: SliverChildBuilderDelegate(
+///               (BuildContext context, int index) => items[index],
+///               childCount: items.length,
+///             ),
+///           ),
+///         ],
+///       )
+///     )
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [CustomScrollView], a typical sliver holding scroll view this control
+///    should go into.
+///  * <https://developer.apple.com/ios/human-interface-guidelines/controls/refresh-content-controls/>
+///  * [RefreshIndicator], a Material Design version of the pull-to-refresh
+///    paradigm. This widget works differently than [RefreshIndicator] because
+///    instead of being an overlay on top of the scrollable, the
+///    [CupertinoSliverRefreshControl] is part of the scrollable and actively occupies
+///    scrollable space.
+class CupertinoSliverRefreshControl extends StatefulWidget {
+  /// Create a new refresh control for inserting into a list of slivers.
+  ///
+  /// The [refreshTriggerPullDistance] and [refreshIndicatorExtent] arguments
+  /// must not be null and must be >= 0.
+  ///
+  /// The [builder] argument may be null, in which case no indicator UI will be
+  /// shown but the [onRefresh] will still be invoked. By default, [builder]
+  /// shows a [CupertinoActivityIndicator].
+  ///
+  /// The [onRefresh] argument will be called when pulled far enough to trigger
+  /// a refresh.
+  const CupertinoSliverRefreshControl({
+    Key? key,
+    this.refreshTriggerPullDistance = _defaultRefreshTriggerPullDistance,
+    this.refreshIndicatorExtent = _defaultRefreshIndicatorExtent,
+    this.builder = buildRefreshIndicator,
+    this.onRefresh,
+  }) : assert(refreshTriggerPullDistance != null),
+       assert(refreshTriggerPullDistance > 0.0),
+       assert(refreshIndicatorExtent != null),
+       assert(refreshIndicatorExtent >= 0.0),
+       assert(
+         refreshTriggerPullDistance >= refreshIndicatorExtent,
+         'The refresh indicator cannot take more space in its final state '
+         'than the amount initially created by overscrolling.'
+       ),
+       super(key: key);
+
+  /// The amount of overscroll the scrollable must be dragged to trigger a reload.
+  ///
+  /// Must not be null, must be larger than 0.0 and larger than
+  /// [refreshIndicatorExtent]. Defaults to 100px when not specified.
+  ///
+  /// When overscrolled past this distance, [onRefresh] will be called if not
+  /// null and the [builder] will build in the [RefreshIndicatorMode.armed] state.
+  final double refreshTriggerPullDistance;
+
+  /// The amount of space the refresh indicator sliver will keep holding while
+  /// [onRefresh]'s [Future] is still running.
+  ///
+  /// Must not be null and must be positive, but can be 0.0, in which case the
+  /// sliver will start retracting back to 0.0 as soon as the refresh is started.
+  /// Defaults to 60px when not specified.
+  ///
+  /// Must be smaller than [refreshTriggerPullDistance], since the sliver
+  /// shouldn't grow further after triggering the refresh.
+  final double refreshIndicatorExtent;
+
+  /// A builder that's called as this sliver's size changes, and as the state
+  /// changes.
+  ///
+  /// Can be set to null, in which case nothing will be drawn in the overscrolled
+  /// space.
+  ///
+  /// Will not be called when the available space is zero such as before any
+  /// overscroll.
+  final RefreshControlIndicatorBuilder? builder;
+
+  /// Callback invoked when pulled by [refreshTriggerPullDistance].
+  ///
+  /// If provided, must return a [Future] which will keep the indicator in the
+  /// [RefreshIndicatorMode.refresh] state until the [Future] completes.
+  ///
+  /// Can be null, in which case a single frame of [RefreshIndicatorMode.armed]
+  /// state will be drawn before going immediately to the [RefreshIndicatorMode.done]
+  /// where the sliver will start retracting.
+  final RefreshCallback? onRefresh;
+
+  static const double _defaultRefreshTriggerPullDistance = 100.0;
+  static const double _defaultRefreshIndicatorExtent = 60.0;
+
+  /// Retrieve the current state of the CupertinoSliverRefreshControl. The same as the
+  /// state that gets passed into the [builder] function. Used for testing.
+  @visibleForTesting
+  static RefreshIndicatorMode state(BuildContext context) {
+    final _CupertinoSliverRefreshControlState state = context.findAncestorStateOfType<_CupertinoSliverRefreshControlState>()!;
+    return state.refreshState;
+  }
+
+  /// Builds a refresh indicator that reflects the standard iOS pull-to-refresh
+  /// behavior. Specifically, this entails presenting an activity indicator that
+  /// changes depending on the current refreshState. As the user initially drags
+  /// down, the indicator will gradually reveal individual ticks until the refresh
+  /// becomes armed. At this point, the animated activity indicator will begin rotating.
+  /// Once the refresh has completed, the activity indicator shrinks away as the
+  /// space allocation animates back to closed.
+  static Widget buildRefreshIndicator(
+    BuildContext context,
+    RefreshIndicatorMode refreshState,
+    double pulledExtent,
+    double refreshTriggerPullDistance,
+    double refreshIndicatorExtent,
+  ) {
+    final double percentageComplete = pulledExtent / refreshTriggerPullDistance;
+
+    // Place the indicator at the top of the sliver that opens up. Note that we're using
+    // a Stack/Positioned widget because the CupertinoActivityIndicator does some internal
+    // translations based on the current size (which grows as the user drags) that makes
+    // Padding calculations difficult. Rather than be reliant on the internal implementation
+    // of the activity indicator, the Positioned widget allows us to be explicit where the
+    // widget gets placed. Also note that the indicator should appear over the top of the
+    // dragged widget, hence the use of Overflow.visible.
+    return Center(
+      child: Stack(
+        clipBehavior: Clip.none,
+        children: <Widget>[
+          Positioned(
+            top: _kActivityIndicatorMargin,
+            left: 0.0,
+            right: 0.0,
+            child: _buildIndicatorForRefreshState(refreshState, _kActivityIndicatorRadius, percentageComplete),
+          ),
+        ],
+      ),
+    );
+  }
+
+  static Widget _buildIndicatorForRefreshState(RefreshIndicatorMode refreshState, double radius, double percentageComplete) {
+    switch (refreshState) {
+      case RefreshIndicatorMode.drag:
+        // While we're dragging, we draw individual ticks of the spinner while simultaneously
+        // easing the opacity in. Note that the opacity curve values here were derived using
+        // Xcode through inspecting a native app running on iOS 13.5.
+        const Curve opacityCurve = Interval(0.0, 0.35, curve: Curves.easeInOut);
+        return Opacity(
+          opacity: opacityCurve.transform(percentageComplete),
+          child: CupertinoActivityIndicator.partiallyRevealed(radius: radius, progress: percentageComplete),
+        );
+      case RefreshIndicatorMode.armed:
+      case RefreshIndicatorMode.refresh:
+        // Once we're armed or performing the refresh, we just show the normal spinner.
+        return CupertinoActivityIndicator(radius: radius);
+      case RefreshIndicatorMode.done:
+        // When the user lets go, the standard transition is to shrink the spinner.
+        return CupertinoActivityIndicator(radius: radius * percentageComplete);
+      case RefreshIndicatorMode.inactive:
+        // Anything else doesn't show anything.
+        return Container();
+    }
+  }
+
+  @override
+  _CupertinoSliverRefreshControlState createState() => _CupertinoSliverRefreshControlState();
+}
+
+class _CupertinoSliverRefreshControlState extends State<CupertinoSliverRefreshControl> {
+  // Reset the state from done to inactive when only this fraction of the
+  // original `refreshTriggerPullDistance` is left.
+  static const double _inactiveResetOverscrollFraction = 0.1;
+
+  late RefreshIndicatorMode refreshState;
+  // [Future] returned by the widget's `onRefresh`.
+  Future<void>? refreshTask;
+  // The amount of space available from the inner indicator box's perspective.
+  //
+  // The value is the sum of the sliver's layout extent and the overscroll
+  // (which partially gets transferred into the layout extent when the refresh
+  // triggers).
+  //
+  // The value of latestIndicatorBoxExtent doesn't change when the sliver scrolls
+  // away without retracting; it is independent from the sliver's scrollOffset.
+  double latestIndicatorBoxExtent = 0.0;
+  bool hasSliverLayoutExtent = false;
+
+  @override
+  void initState() {
+    super.initState();
+    refreshState = RefreshIndicatorMode.inactive;
+  }
+
+  // A state machine transition calculator. Multiple states can be transitioned
+  // through per single call.
+  RefreshIndicatorMode transitionNextState() {
+    RefreshIndicatorMode nextState;
+
+    void goToDone() {
+      nextState = RefreshIndicatorMode.done;
+      // Either schedule the RenderSliver to re-layout on the next frame
+      // when not currently in a frame or schedule it on the next frame.
+      if (SchedulerBinding.instance!.schedulerPhase == SchedulerPhase.idle) {
+        setState(() => hasSliverLayoutExtent = false);
+      } else {
+        SchedulerBinding.instance!.addPostFrameCallback((Duration timestamp) {
+          setState(() => hasSliverLayoutExtent = false);
+        });
+      }
+    }
+
+    switch (refreshState) {
+      case RefreshIndicatorMode.inactive:
+        if (latestIndicatorBoxExtent <= 0) {
+          return RefreshIndicatorMode.inactive;
+        } else {
+          nextState = RefreshIndicatorMode.drag;
+        }
+        continue drag;
+      drag:
+      case RefreshIndicatorMode.drag:
+        if (latestIndicatorBoxExtent == 0) {
+          return RefreshIndicatorMode.inactive;
+        } else if (latestIndicatorBoxExtent < widget.refreshTriggerPullDistance) {
+          return RefreshIndicatorMode.drag;
+        } else {
+          if (widget.onRefresh != null) {
+            HapticFeedback.mediumImpact();
+            // Call onRefresh after this frame finished since the function is
+            // user supplied and we're always here in the middle of the sliver's
+            // performLayout.
+            SchedulerBinding.instance!.addPostFrameCallback((Duration timestamp) {
+              refreshTask = widget.onRefresh!()..whenComplete(() {
+                if (mounted) {
+                  setState(() => refreshTask = null);
+                  // Trigger one more transition because by this time, BoxConstraint's
+                  // maxHeight might already be resting at 0 in which case no
+                  // calls to [transitionNextState] will occur anymore and the
+                  // state may be stuck in a non-inactive state.
+                  refreshState = transitionNextState();
+                }
+              });
+              setState(() => hasSliverLayoutExtent = true);
+            });
+          }
+          return RefreshIndicatorMode.armed;
+        }
+      case RefreshIndicatorMode.armed:
+        if (refreshState == RefreshIndicatorMode.armed && refreshTask == null) {
+          goToDone();
+          continue done;
+        }
+
+        if (latestIndicatorBoxExtent > widget.refreshIndicatorExtent) {
+          return RefreshIndicatorMode.armed;
+        } else {
+          nextState = RefreshIndicatorMode.refresh;
+        }
+        continue refresh;
+      refresh:
+      case RefreshIndicatorMode.refresh:
+        if (refreshTask != null) {
+          return RefreshIndicatorMode.refresh;
+        } else {
+          goToDone();
+        }
+        continue done;
+      done:
+      case RefreshIndicatorMode.done:
+        // Let the transition back to inactive trigger before strictly going
+        // to 0.0 since the last bit of the animation can take some time and
+        // can feel sluggish if not going all the way back to 0.0 prevented
+        // a subsequent pull-to-refresh from starting.
+        if (latestIndicatorBoxExtent >
+            widget.refreshTriggerPullDistance * _inactiveResetOverscrollFraction) {
+          return RefreshIndicatorMode.done;
+        } else {
+          nextState = RefreshIndicatorMode.inactive;
+        }
+        break;
+    }
+
+    return nextState;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _CupertinoSliverRefresh(
+      refreshIndicatorLayoutExtent: widget.refreshIndicatorExtent,
+      hasLayoutExtent: hasSliverLayoutExtent,
+      // A LayoutBuilder lets the sliver's layout changes be fed back out to
+      // its owner to trigger state changes.
+      child: LayoutBuilder(
+        builder: (BuildContext context, BoxConstraints constraints) {
+          latestIndicatorBoxExtent = constraints.maxHeight;
+          refreshState = transitionNextState();
+          if (widget.builder != null && latestIndicatorBoxExtent > 0) {
+            return widget.builder!(
+              context,
+              refreshState,
+              latestIndicatorBoxExtent,
+              widget.refreshTriggerPullDistance,
+              widget.refreshIndicatorExtent,
+            );
+          }
+          return Container();
+        },
+      ),
+    );
+  }
+}
diff --git a/lib/src/cupertino/route.dart b/lib/src/cupertino/route.dart
new file mode 100644
index 0000000..7421c86
--- /dev/null
+++ b/lib/src/cupertino/route.dart
@@ -0,0 +1,1158 @@
+// Copyright 2014 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.
+
+import 'dart:math';
+import 'package:flute/ui.dart' show lerpDouble, ImageFilter;
+
+import 'package:flute/animation.dart' show Curves;
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'interface_level.dart';
+import 'localizations.dart';
+
+const double _kBackGestureWidth = 20.0;
+const double _kMinFlingVelocity = 1.0; // Screen widths per second.
+
+// An eyeballed value for the maximum time it takes for a page to animate forward
+// if the user releases a page mid swipe.
+const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds.
+
+// The maximum time for a page to get reset to it's original position if the
+// user releases a page mid swipe.
+const int _kMaxPageBackAnimationTime = 300; // Milliseconds.
+
+// Barrier color for a Cupertino modal barrier.
+// Extracted from https://developer.apple.com/design/resources/.
+const Color _kModalBarrierColor = CupertinoDynamicColor.withBrightness(
+  color: Color(0x33000000),
+  darkColor: Color(0x7A000000),
+);
+
+// The duration of the transition used when a modal popup is shown.
+const Duration _kModalPopupTransitionDuration = Duration(milliseconds: 335);
+
+// Offset from offscreen to the right to fully on screen.
+final Animatable<Offset> _kRightMiddleTween = Tween<Offset>(
+  begin: const Offset(1.0, 0.0),
+  end: Offset.zero,
+);
+
+// Offset from fully on screen to 1/3 offscreen to the left.
+final Animatable<Offset> _kMiddleLeftTween = Tween<Offset>(
+  begin: Offset.zero,
+  end: const Offset(-1.0/3.0, 0.0),
+);
+
+// Offset from offscreen below to fully on screen.
+final Animatable<Offset> _kBottomUpTween = Tween<Offset>(
+  begin: const Offset(0.0, 1.0),
+  end: Offset.zero,
+);
+
+// Custom decoration from no shadow to page shadow mimicking iOS page
+// transitions using gradients.
+final DecorationTween _kGradientShadowTween = DecorationTween(
+  begin: _CupertinoEdgeShadowDecoration.none, // No decoration initially.
+  end: const _CupertinoEdgeShadowDecoration(
+    edgeGradient: LinearGradient(
+      // Spans 5% of the page.
+      begin: AlignmentDirectional(0.90, 0.0),
+      end: AlignmentDirectional.centerEnd,
+      // Eyeballed gradient used to mimic a drop shadow on the start side only.
+      colors: <Color>[
+        Color(0x00000000),
+        Color(0x04000000),
+        Color(0x12000000),
+        Color(0x38000000),
+      ],
+      stops: <double>[0.0, 0.3, 0.6, 1.0],
+    ),
+  ),
+);
+
+/// A mixin that replaces the entire screen with an iOS transition for a
+/// [PageRoute].
+///
+/// {@template flutter.cupertino.cupertinoRouteTransitionMixin}
+/// The page slides in from the right and exits in reverse. The page also shifts
+/// to the left in parallax when another page enters to cover it.
+///
+/// The page slides in from the bottom and exits in reverse with no parallax
+/// effect for fullscreen dialogs.
+/// {@endtemplate}
+///
+/// See also:
+///
+///  * [MaterialRouteTransitionMixin], which is a mixin that provides
+///    platform-appropriate transitions for a [PageRoute].
+///  * [CupertinoPageRoute], which is a [PageRoute] that leverages this mixin.
+mixin CupertinoRouteTransitionMixin<T> on PageRoute<T> {
+  /// Builds the primary contents of the route.
+  @protected
+  Widget buildContent(BuildContext context);
+
+  /// {@template flutter.cupertino.CupertinoRouteTransitionMixin.title}
+  /// A title string for this route.
+  ///
+  /// Used to auto-populate [CupertinoNavigationBar] and
+  /// [CupertinoSliverNavigationBar]'s `middle`/`largeTitle` widgets when
+  /// one is not manually supplied.
+  /// {@endtemplate}
+  String? get title;
+
+  ValueNotifier<String?>? _previousTitle;
+
+  /// The title string of the previous [CupertinoPageRoute].
+  ///
+  /// The [ValueListenable]'s value is readable after the route is installed
+  /// onto a [Navigator]. The [ValueListenable] will also notify its listeners
+  /// if the value changes (such as by replacing the previous route).
+  ///
+  /// The [ValueListenable] itself will be null before the route is installed.
+  /// Its content value will be null if the previous route has no title or
+  /// is not a [CupertinoPageRoute].
+  ///
+  /// See also:
+  ///
+  ///  * [ValueListenableBuilder], which can be used to listen and rebuild
+  ///    widgets based on a ValueListenable.
+  ValueListenable<String?> get previousTitle {
+    assert(
+      _previousTitle != null,
+      'Cannot read the previousTitle for a route that has not yet been installed',
+    );
+    return _previousTitle!;
+  }
+
+  @override
+  void didChangePrevious(Route<dynamic>? previousRoute) {
+    final String? previousTitleString = previousRoute is CupertinoRouteTransitionMixin
+      ? previousRoute.title
+      : null;
+    if (_previousTitle == null) {
+      _previousTitle = ValueNotifier<String?>(previousTitleString);
+    } else {
+      _previousTitle!.value = previousTitleString;
+    }
+    super.didChangePrevious(previousRoute);
+  }
+
+  @override
+  // A relatively rigorous eyeball estimation.
+  Duration get transitionDuration => const Duration(milliseconds: 400);
+
+  @override
+  Color? get barrierColor => null;
+
+  @override
+  String? get barrierLabel => null;
+
+  @override
+  bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
+    // Don't perform outgoing animation if the next route is a fullscreen dialog.
+    return nextRoute is CupertinoRouteTransitionMixin && !nextRoute.fullscreenDialog;
+  }
+
+  /// True if an iOS-style back swipe pop gesture is currently underway for [route].
+  ///
+  /// This just check the route's [NavigatorState.userGestureInProgress].
+  ///
+  /// See also:
+  ///
+  ///  * [popGestureEnabled], which returns true if a user-triggered pop gesture
+  ///    would be allowed.
+  static bool isPopGestureInProgress(PageRoute<dynamic> route) {
+    return route.navigator!.userGestureInProgress;
+  }
+
+  /// True if an iOS-style back swipe pop gesture is currently underway for this route.
+  ///
+  /// See also:
+  ///
+  ///  * [isPopGestureInProgress], which returns true if a Cupertino pop gesture
+  ///    is currently underway for specific route.
+  ///  * [popGestureEnabled], which returns true if a user-triggered pop gesture
+  ///    would be allowed.
+  bool get popGestureInProgress => isPopGestureInProgress(this);
+
+  /// Whether a pop gesture can be started by the user.
+  ///
+  /// Returns true if the user can edge-swipe to a previous route.
+  ///
+  /// Returns false once [isPopGestureInProgress] is true, but
+  /// [isPopGestureInProgress] can only become true if [popGestureEnabled] was
+  /// true first.
+  ///
+  /// This should only be used between frames, not during build.
+  bool get popGestureEnabled => _isPopGestureEnabled(this);
+
+  static bool _isPopGestureEnabled<T>(PageRoute<T> route) {
+    // If there's nothing to go back to, then obviously we don't support
+    // the back gesture.
+    if (route.isFirst)
+      return false;
+    // If the route wouldn't actually pop if we popped it, then the gesture
+    // would be really confusing (or would skip internal routes), so disallow it.
+    if (route.willHandlePopInternally)
+      return false;
+    // If attempts to dismiss this route might be vetoed such as in a page
+    // with forms, then do not allow the user to dismiss the route with a swipe.
+    if (route.hasScopedWillPopCallback)
+      return false;
+    // Fullscreen dialogs aren't dismissible by back swipe.
+    if (route.fullscreenDialog)
+      return false;
+    // If we're in an animation already, we cannot be manually swiped.
+    if (route.animation!.status != AnimationStatus.completed)
+      return false;
+    // If we're being popped into, we also cannot be swiped until the pop above
+    // it completes. This translates to our secondary animation being
+    // dismissed.
+    if (route.secondaryAnimation!.status != AnimationStatus.dismissed)
+      return false;
+    // If we're in a gesture already, we cannot start another.
+    if (isPopGestureInProgress(route))
+      return false;
+
+    // Looks like a back gesture would be welcome!
+    return true;
+  }
+
+  @override
+  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
+    final Widget child = buildContent(context);
+    final Widget result = Semantics(
+      scopesRoute: true,
+      explicitChildNodes: true,
+      child: child,
+    );
+    assert(() {
+      // `child` has a non-nullable return type, but might be null when
+      // running with weak checking, so we need to null check it anyway (and
+      // ignore the warning that the null-handling logic is dead code).
+      if (child == null) { // ignore: dead_code
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('The builder for route "${settings.name}" returned null.'),
+          ErrorDescription('Route builders must never return null.'),
+        ]);
+      }
+      return true;
+    }());
+    return result;
+  }
+
+  // Called by _CupertinoBackGestureDetector when a pop ("back") drag start
+  // gesture is detected. The returned controller handles all of the subsequent
+  // drag events.
+  static _CupertinoBackGestureController<T> _startPopGesture<T>(PageRoute<T> route) {
+    assert(_isPopGestureEnabled(route));
+
+    return _CupertinoBackGestureController<T>(
+      navigator: route.navigator!,
+      controller: route.controller!, // protected access
+    );
+  }
+
+  /// Returns a [CupertinoFullscreenDialogTransition] if [route] is a full
+  /// screen dialog, otherwise a [CupertinoPageTransition] is returned.
+  ///
+  /// Used by [CupertinoPageRoute.buildTransitions].
+  ///
+  /// This method can be applied to any [PageRoute], not just
+  /// [CupertinoPageRoute]. It's typically used to provide a Cupertino style
+  /// horizontal transition for material widgets when the target platform
+  /// is [TargetPlatform.iOS].
+  ///
+  /// See also:
+  ///
+  ///  * [CupertinoPageTransitionsBuilder], which uses this method to define a
+  ///    [PageTransitionsBuilder] for the [PageTransitionsTheme].
+  static Widget buildPageTransitions<T>(
+    PageRoute<T> route,
+    BuildContext context,
+    Animation<double> animation,
+    Animation<double> secondaryAnimation,
+    Widget child,
+  ) {
+    // Check if the route has an animation that's currently participating
+    // in a back swipe gesture.
+    //
+    // In the middle of a back gesture drag, let the transition be linear to
+    // match finger motions.
+    final bool linearTransition = isPopGestureInProgress(route);
+    if (route.fullscreenDialog) {
+      return CupertinoFullscreenDialogTransition(
+        primaryRouteAnimation: animation,
+        secondaryRouteAnimation: secondaryAnimation,
+        child: child,
+        linearTransition: linearTransition,
+      );
+    } else {
+      return CupertinoPageTransition(
+        primaryRouteAnimation: animation,
+        secondaryRouteAnimation: secondaryAnimation,
+        linearTransition: linearTransition,
+        child: _CupertinoBackGestureDetector<T>(
+          enabledCallback: () => _isPopGestureEnabled<T>(route),
+          onStartPopGesture: () => _startPopGesture<T>(route),
+          child: child,
+        ),
+      );
+    }
+  }
+
+  @override
+  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
+    return buildPageTransitions<T>(this, context, animation, secondaryAnimation, child);
+  }
+}
+
+/// A modal route that replaces the entire screen with an iOS transition.
+///
+/// {@macro flutter.cupertino.cupertinoRouteTransitionMixin}
+///
+/// By default, when a modal route is replaced by another, the previous route
+/// remains in memory. To free all the resources when this is not necessary, set
+/// [maintainState] to false.
+///
+/// The type `T` specifies the return type of the route which can be supplied as
+/// the route is popped from the stack via [Navigator.pop] when an optional
+/// `result` can be provided.
+///
+/// See also:
+///
+///  * [CupertinoRouteTransitionMixin], for a mixin that provides iOS transition
+///    for this modal route.
+///  * [MaterialPageRoute], for an adaptive [PageRoute] that uses a
+///    platform-appropriate transition.
+///  * [CupertinoPageScaffold], for applications that have one page with a fixed
+///    navigation bar on top.
+///  * [CupertinoTabScaffold], for applications that have a tab bar at the
+///    bottom with multiple pages.
+///  * [CupertinoPage], for a [Page] version of this class.
+class CupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTransitionMixin<T> {
+  /// Creates a page route for use in an iOS designed app.
+  ///
+  /// The [builder], [maintainState], and [fullscreenDialog] arguments must not
+  /// be null.
+  CupertinoPageRoute({
+    required this.builder,
+    this.title,
+    RouteSettings? settings,
+    this.maintainState = true,
+    bool fullscreenDialog = false,
+  }) : assert(builder != null),
+       assert(maintainState != null),
+       assert(fullscreenDialog != null),
+       super(settings: settings, fullscreenDialog: fullscreenDialog) {
+    assert(opaque);
+  }
+
+  /// Builds the primary contents of the route.
+  final WidgetBuilder builder;
+
+  @override
+  Widget buildContent(BuildContext context) => builder(context);
+
+  @override
+  final String? title;
+
+  @override
+  final bool maintainState;
+
+  @override
+  String get debugLabel => '${super.debugLabel}(${settings.name})';
+}
+
+// A page-based version of CupertinoPageRoute.
+//
+// This route uses the builder from the page to build its content. This ensures
+// the content is up to date after page updates.
+class _PageBasedCupertinoPageRoute<T> extends PageRoute<T> with CupertinoRouteTransitionMixin<T> {
+  _PageBasedCupertinoPageRoute({
+    required CupertinoPage<T> page,
+  }) : assert(page != null),
+       super(settings: page) {
+    assert(opaque);
+  }
+
+  CupertinoPage<T> get _page => settings as CupertinoPage<T>;
+
+  @override
+  Widget buildContent(BuildContext context) => _page.child;
+
+  @override
+  String? get title => _page.title;
+
+  @override
+  bool get maintainState => _page.maintainState;
+
+  @override
+  bool get fullscreenDialog => _page.fullscreenDialog;
+
+  @override
+  String get debugLabel => '${super.debugLabel}(${_page.name})';
+}
+
+/// A page that creates a cupertino style [PageRoute].
+///
+/// {@macro flutter.cupertino.cupertinoRouteTransitionMixin}
+///
+/// By default, when a created modal route is replaced by another, the previous
+/// route remains in memory. To free all the resources when this is not
+/// necessary, set [maintainState] to false.
+///
+/// The type `T` specifies the return type of the route which can be supplied as
+/// the route is popped from the stack via [Navigator.transitionDelegate] by
+/// providing the optional `result` argument to the
+/// [RouteTransitionRecord.markForPop] in the [TransitionDelegate.resolve].
+///
+/// See also:
+///
+///  * [CupertinoPageRoute], for a [PageRoute] version of this class.
+class CupertinoPage<T> extends Page<T> {
+  /// Creates a cupertino page.
+  const CupertinoPage({
+    required this.child,
+    this.maintainState = true,
+    this.title,
+    this.fullscreenDialog = false,
+    LocalKey? key,
+    String? name,
+    Object? arguments,
+  }) : assert(child != null),
+       assert(maintainState != null),
+       assert(fullscreenDialog != null),
+       super(key: key, name: name, arguments: arguments);
+
+  /// The content to be shown in the [Route] created by this page.
+  final Widget child;
+
+  /// {@macro flutter.cupertino.CupertinoRouteTransitionMixin.title}
+  final String? title;
+
+  /// {@macro flutter.widgets.ModalRoute.maintainState}
+  final bool maintainState;
+
+  /// {@macro flutter.widgets.PageRoute.fullscreenDialog}
+  final bool fullscreenDialog;
+
+  @override
+  Route<T> createRoute(BuildContext context) {
+    return _PageBasedCupertinoPageRoute<T>(page: this);
+  }
+}
+
+/// Provides an iOS-style page transition animation.
+///
+/// The page slides in from the right and exits in reverse. It also shifts to the left in
+/// a parallax motion when another page enters to cover it.
+class CupertinoPageTransition extends StatelessWidget {
+  /// Creates an iOS-style page transition.
+  ///
+  ///  * `primaryRouteAnimation` is a linear route animation from 0.0 to 1.0
+  ///    when this screen is being pushed.
+  ///  * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0
+  ///    when another screen is being pushed on top of this one.
+  ///  * `linearTransition` is whether to perform the transitions linearly.
+  ///    Used to precisely track back gesture drags.
+  CupertinoPageTransition({
+    Key? key,
+    required Animation<double> primaryRouteAnimation,
+    required Animation<double> secondaryRouteAnimation,
+    required this.child,
+    required bool linearTransition,
+  }) : assert(linearTransition != null),
+       _primaryPositionAnimation =
+           (linearTransition
+             ? primaryRouteAnimation
+             : CurvedAnimation(
+                 // The curves below have been rigorously derived from plots of native
+                 // iOS animation frames. Specifically, a video was taken of a page
+                 // transition animation and the distance in each frame that the page
+                 // moved was measured. A best fit bezier curve was the fitted to the
+                 // point set, which is linearToEaseIn. Conversely, easeInToLinear is the
+                 // reflection over the origin of linearToEaseIn.
+                 parent: primaryRouteAnimation,
+                 curve: Curves.linearToEaseOut,
+                 reverseCurve: Curves.easeInToLinear,
+               )
+           ).drive(_kRightMiddleTween),
+       _secondaryPositionAnimation =
+           (linearTransition
+             ? secondaryRouteAnimation
+             : CurvedAnimation(
+                 parent: secondaryRouteAnimation,
+                 curve: Curves.linearToEaseOut,
+                 reverseCurve: Curves.easeInToLinear,
+               )
+           ).drive(_kMiddleLeftTween),
+       _primaryShadowAnimation =
+           (linearTransition
+             ? primaryRouteAnimation
+             : CurvedAnimation(
+                 parent: primaryRouteAnimation,
+                 curve: Curves.linearToEaseOut,
+               )
+           ).drive(_kGradientShadowTween),
+       super(key: key);
+
+  // When this page is coming in to cover another page.
+  final Animation<Offset> _primaryPositionAnimation;
+  // When this page is becoming covered by another page.
+  final Animation<Offset> _secondaryPositionAnimation;
+  final Animation<Decoration> _primaryShadowAnimation;
+
+  /// The widget below this widget in the tree.
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasDirectionality(context));
+    final TextDirection textDirection = Directionality.of(context);
+    return SlideTransition(
+      position: _secondaryPositionAnimation,
+      textDirection: textDirection,
+      transformHitTests: false,
+      child: SlideTransition(
+        position: _primaryPositionAnimation,
+        textDirection: textDirection,
+        child: DecoratedBoxTransition(
+          decoration: _primaryShadowAnimation,
+          child: child,
+        ),
+      ),
+    );
+  }
+}
+
+/// An iOS-style transition used for summoning fullscreen dialogs.
+///
+/// For example, used when creating a new calendar event by bringing in the next
+/// screen from the bottom.
+class CupertinoFullscreenDialogTransition extends StatelessWidget {
+  /// Creates an iOS-style transition used for summoning fullscreen dialogs.
+  ///
+  ///  * `primaryRouteAnimation` is a linear route animation from 0.0 to 1.0
+  ///    when this screen is being pushed.
+  ///  * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0
+  ///    when another screen is being pushed on top of this one.
+  ///  * `linearTransition` is whether to perform the secondary transition linearly.
+  ///    Used to precisely track back gesture drags.
+  CupertinoFullscreenDialogTransition({
+    Key? key,
+    required Animation<double> primaryRouteAnimation,
+    required Animation<double> secondaryRouteAnimation,
+    required this.child,
+    required bool linearTransition,
+  }) : _positionAnimation = CurvedAnimation(
+         parent: primaryRouteAnimation,
+         curve: Curves.linearToEaseOut,
+         // The curve must be flipped so that the reverse animation doesn't play
+         // an ease-in curve, which iOS does not use.
+         reverseCurve: Curves.linearToEaseOut.flipped,
+       ).drive(_kBottomUpTween),
+       _secondaryPositionAnimation =
+           (linearTransition
+             ? secondaryRouteAnimation
+             : CurvedAnimation(
+                 parent: secondaryRouteAnimation,
+                 curve: Curves.linearToEaseOut,
+                 reverseCurve: Curves.easeInToLinear,
+               )
+           ).drive(_kMiddleLeftTween),
+       super(key: key);
+
+  final Animation<Offset> _positionAnimation;
+  // When this page is becoming covered by another page.
+  final Animation<Offset> _secondaryPositionAnimation;
+
+  /// The widget below this widget in the tree.
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasDirectionality(context));
+    final TextDirection textDirection = Directionality.of(context);
+    return SlideTransition(
+      position: _secondaryPositionAnimation,
+      textDirection: textDirection,
+      transformHitTests: false,
+      child: SlideTransition(
+        position: _positionAnimation,
+        child: child,
+      ),
+    );
+  }
+}
+
+/// This is the widget side of [_CupertinoBackGestureController].
+///
+/// This widget provides a gesture recognizer which, when it determines the
+/// route can be closed with a back gesture, creates the controller and
+/// feeds it the input from the gesture recognizer.
+///
+/// The gesture data is converted from absolute coordinates to logical
+/// coordinates by this widget.
+///
+/// The type `T` specifies the return type of the route with which this gesture
+/// detector is associated.
+class _CupertinoBackGestureDetector<T> extends StatefulWidget {
+  const _CupertinoBackGestureDetector({
+    Key? key,
+    required this.enabledCallback,
+    required this.onStartPopGesture,
+    required this.child,
+  }) : assert(enabledCallback != null),
+       assert(onStartPopGesture != null),
+       assert(child != null),
+       super(key: key);
+
+  final Widget child;
+
+  final ValueGetter<bool> enabledCallback;
+
+  final ValueGetter<_CupertinoBackGestureController<T>> onStartPopGesture;
+
+  @override
+  _CupertinoBackGestureDetectorState<T> createState() => _CupertinoBackGestureDetectorState<T>();
+}
+
+class _CupertinoBackGestureDetectorState<T> extends State<_CupertinoBackGestureDetector<T>> {
+  _CupertinoBackGestureController<T>? _backGestureController;
+
+  late HorizontalDragGestureRecognizer _recognizer;
+
+  @override
+  void initState() {
+    super.initState();
+    _recognizer = HorizontalDragGestureRecognizer(debugOwner: this)
+      ..onStart = _handleDragStart
+      ..onUpdate = _handleDragUpdate
+      ..onEnd = _handleDragEnd
+      ..onCancel = _handleDragCancel;
+  }
+
+  @override
+  void dispose() {
+    _recognizer.dispose();
+    super.dispose();
+  }
+
+  void _handleDragStart(DragStartDetails details) {
+    assert(mounted);
+    assert(_backGestureController == null);
+    _backGestureController = widget.onStartPopGesture();
+  }
+
+  void _handleDragUpdate(DragUpdateDetails details) {
+    assert(mounted);
+    assert(_backGestureController != null);
+    _backGestureController!.dragUpdate(_convertToLogical(details.primaryDelta! / context.size!.width));
+  }
+
+  void _handleDragEnd(DragEndDetails details) {
+    assert(mounted);
+    assert(_backGestureController != null);
+    _backGestureController!.dragEnd(_convertToLogical(details.velocity.pixelsPerSecond.dx / context.size!.width));
+    _backGestureController = null;
+  }
+
+  void _handleDragCancel() {
+    assert(mounted);
+    // This can be called even if start is not called, paired with the "down" event
+    // that we don't consider here.
+    _backGestureController?.dragEnd(0.0);
+    _backGestureController = null;
+  }
+
+  void _handlePointerDown(PointerDownEvent event) {
+    if (widget.enabledCallback())
+      _recognizer.addPointer(event);
+  }
+
+  double _convertToLogical(double value) {
+    switch (Directionality.of(context)) {
+      case TextDirection.rtl:
+        return -value;
+      case TextDirection.ltr:
+        return value;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasDirectionality(context));
+    // For devices with notches, the drag area needs to be larger on the side
+    // that has the notch.
+    double dragAreaWidth = Directionality.of(context) == TextDirection.ltr ?
+                           MediaQuery.of(context).padding.left :
+                           MediaQuery.of(context).padding.right;
+    dragAreaWidth = max(dragAreaWidth, _kBackGestureWidth);
+    return Stack(
+      fit: StackFit.passthrough,
+      children: <Widget>[
+        widget.child,
+        PositionedDirectional(
+          start: 0.0,
+          width: dragAreaWidth,
+          top: 0.0,
+          bottom: 0.0,
+          child: Listener(
+            onPointerDown: _handlePointerDown,
+            behavior: HitTestBehavior.translucent,
+          ),
+        ),
+      ],
+    );
+  }
+}
+
+/// A controller for an iOS-style back gesture.
+///
+/// This is created by a [CupertinoPageRoute] in response from a gesture caught
+/// by a [_CupertinoBackGestureDetector] widget, which then also feeds it input
+/// from the gesture. It controls the animation controller owned by the route,
+/// based on the input provided by the gesture detector.
+///
+/// This class works entirely in logical coordinates (0.0 is new page dismissed,
+/// 1.0 is new page on top).
+///
+/// The type `T` specifies the return type of the route with which this gesture
+/// detector controller is associated.
+class _CupertinoBackGestureController<T> {
+  /// Creates a controller for an iOS-style back gesture.
+  ///
+  /// The [navigator] and [controller] arguments must not be null.
+  _CupertinoBackGestureController({
+    required this.navigator,
+    required this.controller,
+  }) : assert(navigator != null),
+       assert(controller != null) {
+    navigator.didStartUserGesture();
+  }
+
+  final AnimationController controller;
+  final NavigatorState navigator;
+
+  /// The drag gesture has changed by [fractionalDelta]. The total range of the
+  /// drag should be 0.0 to 1.0.
+  void dragUpdate(double delta) {
+    controller.value -= delta;
+  }
+
+  /// The drag gesture has ended with a horizontal motion of
+  /// [fractionalVelocity] as a fraction of screen width per second.
+  void dragEnd(double velocity) {
+    // Fling in the appropriate direction.
+    // AnimationController.fling is guaranteed to
+    // take at least one frame.
+    //
+    // This curve has been determined through rigorously eyeballing native iOS
+    // animations.
+    const Curve animationCurve = Curves.fastLinearToSlowEaseIn;
+    final bool animateForward;
+
+    // If the user releases the page before mid screen with sufficient velocity,
+    // or after mid screen, we should animate the page out. Otherwise, the page
+    // should be animated back in.
+    if (velocity.abs() >= _kMinFlingVelocity)
+      animateForward = velocity <= 0;
+    else
+      animateForward = controller.value > 0.5;
+
+    if (animateForward) {
+      // The closer the panel is to dismissing, the shorter the animation is.
+      // We want to cap the animation time, but we want to use a linear curve
+      // to determine it.
+      final int droppedPageForwardAnimationTime = min(
+        lerpDouble(_kMaxDroppedSwipePageForwardAnimationTime, 0, controller.value)!.floor(),
+        _kMaxPageBackAnimationTime,
+      );
+      controller.animateTo(1.0, duration: Duration(milliseconds: droppedPageForwardAnimationTime), curve: animationCurve);
+    } else {
+      // This route is destined to pop at this point. Reuse navigator's pop.
+      navigator.pop();
+
+      // The popping may have finished inline if already at the target destination.
+      if (controller.isAnimating) {
+        // Otherwise, use a custom popping animation duration and curve.
+        final int droppedPageBackAnimationTime = lerpDouble(0, _kMaxDroppedSwipePageForwardAnimationTime, controller.value)!.floor();
+        controller.animateBack(0.0, duration: Duration(milliseconds: droppedPageBackAnimationTime), curve: animationCurve);
+      }
+    }
+
+    if (controller.isAnimating) {
+      // Keep the userGestureInProgress in true state so we don't change the
+      // curve of the page transition mid-flight since CupertinoPageTransition
+      // depends on userGestureInProgress.
+      late AnimationStatusListener animationStatusCallback;
+      animationStatusCallback = (AnimationStatus status) {
+        navigator.didStopUserGesture();
+        controller.removeStatusListener(animationStatusCallback);
+      };
+      controller.addStatusListener(animationStatusCallback);
+    } else {
+      navigator.didStopUserGesture();
+    }
+  }
+}
+
+// A custom [Decoration] used to paint an extra shadow on the start edge of the
+// box it's decorating. It's like a [BoxDecoration] with only a gradient except
+// it paints on the start side of the box instead of behind the box.
+//
+// The [edgeGradient] will be given a [TextDirection] when its shader is
+// created, and so can be direction-sensitive; in this file we set it to a
+// gradient that uses an AlignmentDirectional to position the gradient on the
+// end edge of the gradient's box (which will be the edge adjacent to the start
+// edge of the actual box we're supposed to paint in).
+class _CupertinoEdgeShadowDecoration extends Decoration {
+  const _CupertinoEdgeShadowDecoration({ this.edgeGradient });
+
+  // An edge shadow decoration where the shadow is null. This is used
+  // for interpolating from no shadow.
+  static const _CupertinoEdgeShadowDecoration none =
+      _CupertinoEdgeShadowDecoration();
+
+  // A gradient to draw to the left of the box being decorated.
+  // Alignments are relative to the original box translated one box
+  // width to the left.
+  final LinearGradient? edgeGradient;
+
+  // Linearly interpolate between two edge shadow decorations decorations.
+  //
+  // The `t` argument represents position on the timeline, with 0.0 meaning
+  // that the interpolation has not started, returning `a` (or something
+  // equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  // returning `b` (or something equivalent to `b`), and values in between
+  // meaning that the interpolation is at the relevant point on the timeline
+  // between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+  // 1.0, so negative values and values greater than 1.0 are valid (and can
+  // easily be generated by curves such as [Curves.elasticInOut]).
+  //
+  // Values for `t` are usually obtained from an [Animation<double>], such as
+  // an [AnimationController].
+  //
+  // See also:
+  //
+  //  * [Decoration.lerp].
+  static _CupertinoEdgeShadowDecoration? lerp(
+    _CupertinoEdgeShadowDecoration? a,
+    _CupertinoEdgeShadowDecoration? b,
+    double t,
+  ) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    return _CupertinoEdgeShadowDecoration(
+      edgeGradient: LinearGradient.lerp(a?.edgeGradient, b?.edgeGradient, t),
+    );
+  }
+
+  @override
+  _CupertinoEdgeShadowDecoration lerpFrom(Decoration? a, double t) {
+    if (a is _CupertinoEdgeShadowDecoration)
+      return _CupertinoEdgeShadowDecoration.lerp(a, this, t)!;
+    return _CupertinoEdgeShadowDecoration.lerp(null, this, t)!;
+  }
+
+  @override
+  _CupertinoEdgeShadowDecoration lerpTo(Decoration? b, double t) {
+    if (b is _CupertinoEdgeShadowDecoration)
+      return _CupertinoEdgeShadowDecoration.lerp(this, b, t)!;
+    return _CupertinoEdgeShadowDecoration.lerp(this, null, t)!;
+  }
+
+  @override
+  _CupertinoEdgeShadowPainter createBoxPainter([ VoidCallback? onChanged ]) {
+    return _CupertinoEdgeShadowPainter(this, onChanged);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _CupertinoEdgeShadowDecoration
+        && other.edgeGradient == edgeGradient;
+  }
+
+  @override
+  int get hashCode => edgeGradient.hashCode;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<LinearGradient>('edgeGradient', edgeGradient));
+  }
+}
+
+/// A [BoxPainter] used to draw the page transition shadow using gradients.
+class _CupertinoEdgeShadowPainter extends BoxPainter {
+  _CupertinoEdgeShadowPainter(
+    this._decoration,
+    VoidCallback? onChange,
+  ) : assert(_decoration != null),
+      super(onChange);
+
+  final _CupertinoEdgeShadowDecoration _decoration;
+
+  @override
+  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
+    final LinearGradient? gradient = _decoration.edgeGradient;
+    if (gradient == null)
+      return;
+    // The drawable space for the gradient is a rect with the same size as
+    // its parent box one box width on the start side of the box.
+    final TextDirection? textDirection = configuration.textDirection;
+    assert(textDirection != null);
+    final double deltaX;
+    switch (textDirection!) {
+      case TextDirection.rtl:
+        deltaX = configuration.size!.width;
+        break;
+      case TextDirection.ltr:
+        deltaX = -configuration.size!.width;
+        break;
+    }
+    final Rect rect = (offset & configuration.size!).translate(deltaX, 0.0);
+    final Paint paint = Paint()
+      ..shader = gradient.createShader(rect, textDirection: textDirection);
+
+    canvas.drawRect(rect, paint);
+  }
+}
+
+class _CupertinoModalPopupRoute<T> extends PopupRoute<T> {
+  _CupertinoModalPopupRoute({
+    required this.barrierColor,
+    required this.barrierLabel,
+    required this.builder,
+    bool? barrierDismissible,
+    bool? semanticsDismissible,
+    required ImageFilter? filter,
+    RouteSettings? settings,
+  }) : super(
+         filter: filter,
+         settings: settings,
+       ) {
+    _barrierDismissible = barrierDismissible;
+    _semanticsDismissible = semanticsDismissible;
+  }
+
+  final WidgetBuilder builder;
+
+  bool? _barrierDismissible;
+
+  bool? _semanticsDismissible;
+
+  @override
+  final String barrierLabel;
+
+  @override
+  final Color? barrierColor;
+
+  @override
+  bool get barrierDismissible => _barrierDismissible ?? true;
+
+  @override
+  bool get semanticsDismissible => _semanticsDismissible ?? false;
+
+  @override
+  Duration get transitionDuration => _kModalPopupTransitionDuration;
+
+  Animation<double>? _animation;
+
+  late Tween<Offset> _offsetTween;
+
+  @override
+  Animation<double> createAnimation() {
+    assert(_animation == null);
+    _animation = CurvedAnimation(
+      parent: super.createAnimation(),
+
+      // These curves were initially measured from native iOS horizontal page
+      // route animations and seemed to be a good match here as well.
+      curve: Curves.linearToEaseOut,
+      reverseCurve: Curves.linearToEaseOut.flipped,
+    );
+    _offsetTween = Tween<Offset>(
+      begin: const Offset(0.0, 1.0),
+      end: const Offset(0.0, 0.0),
+    );
+    return _animation!;
+  }
+
+  @override
+  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
+    return CupertinoUserInterfaceLevel(
+      data: CupertinoUserInterfaceLevelData.elevated,
+      child: Builder(builder: builder),
+    );
+  }
+
+  @override
+  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
+    return Align(
+      alignment: Alignment.bottomCenter,
+      child: FractionalTranslation(
+        translation: _offsetTween.evaluate(_animation!),
+        child: child,
+      ),
+    );
+  }
+}
+
+/// Shows a modal iOS-style popup that slides up from the bottom of the screen.
+///
+/// Such a popup is an alternative to a menu or a dialog and prevents the user
+/// from interacting with the rest of the app.
+///
+/// The `context` argument is used to look up the [Navigator] for the popup.
+/// It is only used when the method is called. Its corresponding widget can be
+/// safely removed from the tree before the popup is closed.
+///
+/// The `barrierColor` argument determines the [Color] of the barrier underneath
+/// the popup. When unspecified, the barrier color defaults to a light opacity
+/// black scrim based on iOS's dialog screens.
+///
+/// The `barrierDismissible` argument determines whether clicking outside the
+/// popup results in dismissal. It is `true` by default.
+///
+/// The `useRootNavigator` argument is used to determine whether to push the
+/// popup to the [Navigator] furthest from or nearest to the given `context`. It
+/// is `false` by default.
+///
+/// The `semanticsDismissible` argument is used to determine whether the
+/// semantics of the modal barrier are included in the semantics tree.
+///
+/// The `routeSettings` argument is used to provide [RouteSettings] to the
+/// created Route.
+///
+/// The `builder` argument typically builds a [CupertinoActionSheet] widget.
+/// Content below the widget is dimmed with a [ModalBarrier]. The widget built
+/// by the `builder` does not share a context with the location that
+/// `showCupertinoModalPopup` is originally called from. Use a
+/// [StatefulBuilder] or a custom [StatefulWidget] if the widget needs to
+/// update dynamically.
+///
+/// Returns a `Future` that resolves to the value that was passed to
+/// [Navigator.pop] when the popup was closed.
+///
+/// See also:
+///
+///  * [CupertinoActionSheet], which is the widget usually returned by the
+///    `builder` argument to [showCupertinoModalPopup].
+///  * <https://developer.apple.com/design/human-interface-guidelines/ios/views/action-sheets/>
+Future<T?> showCupertinoModalPopup<T>({
+  required BuildContext context,
+  required WidgetBuilder builder,
+  ImageFilter? filter,
+  Color barrierColor = _kModalBarrierColor,
+  bool barrierDismissible = true,
+  bool useRootNavigator = true,
+  bool? semanticsDismissible,
+  RouteSettings? routeSettings,
+}) {
+  assert(useRootNavigator != null);
+  return Navigator.of(context, rootNavigator: useRootNavigator).push(
+    _CupertinoModalPopupRoute<T>(
+      barrierColor: CupertinoDynamicColor.resolve(barrierColor, context),
+      barrierDismissible: barrierDismissible,
+      barrierLabel: 'Dismiss',
+      builder: builder,
+      filter: filter,
+      semanticsDismissible: semanticsDismissible,
+      settings: routeSettings,
+    ),
+  );
+}
+
+// The curve and initial scale values were mostly eyeballed from iOS, however
+// they reuse the same animation curve that was modeled after native page
+// transitions.
+final Animatable<double> _dialogScaleTween = Tween<double>(begin: 1.3, end: 1.0)
+  .chain(CurveTween(curve: Curves.linearToEaseOut));
+
+Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
+  final CurvedAnimation fadeAnimation = CurvedAnimation(
+    parent: animation,
+    curve: Curves.easeInOut,
+  );
+  if (animation.status == AnimationStatus.reverse) {
+    return FadeTransition(
+      opacity: fadeAnimation,
+      child: child,
+    );
+  }
+  return FadeTransition(
+    opacity: fadeAnimation,
+    child: ScaleTransition(
+      child: child,
+      scale: animation.drive(_dialogScaleTween),
+    ),
+  );
+}
+
+/// Displays an iOS-style dialog above the current contents of the app, with
+/// iOS-style entrance and exit animations, modal barrier color, and modal
+/// barrier behavior (by default, the dialog is not dismissible with a tap on
+/// the barrier).
+///
+/// This function takes a `builder` which typically builds a [CupertinoAlertDialog]
+/// widget. Content below the dialog is dimmed with a [ModalBarrier]. The widget
+/// returned by the `builder` does not share a context with the location that
+/// `showCupertinoDialog` is originally called from. Use a [StatefulBuilder] or
+/// a custom [StatefulWidget] if the dialog needs to update dynamically.
+///
+/// The `context` argument is used to look up the [Navigator] for the dialog.
+/// It is only used when the method is called. Its corresponding widget can
+/// be safely removed from the tree before the dialog is closed.
+///
+/// The `useRootNavigator` argument is used to determine whether to push the
+/// dialog to the [Navigator] furthest from or nearest to the given `context`.
+/// By default, `useRootNavigator` is `true` and the dialog route created by
+/// this method is pushed to the root navigator.
+///
+/// If the application has multiple [Navigator] objects, it may be necessary to
+/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
+/// dialog rather than just `Navigator.pop(context, result)`.
+///
+/// Returns a [Future] that resolves to the value (if any) that was passed to
+/// [Navigator.pop] when the dialog was closed.
+///
+/// See also:
+///
+///  * [CupertinoAlertDialog], an iOS-style alert dialog.
+///  * [showDialog], which displays a Material-style dialog.
+///  * [showGeneralDialog], which allows for customization of the dialog popup.
+///  * <https://developer.apple.com/ios/human-interface-guidelines/views/alerts/>
+Future<T?> showCupertinoDialog<T>({
+  required BuildContext context,
+  required WidgetBuilder builder,
+  bool useRootNavigator = true,
+  bool barrierDismissible = false,
+  RouteSettings? routeSettings,
+}) {
+  assert(builder != null);
+  assert(useRootNavigator != null);
+  return showGeneralDialog(
+    context: context,
+    barrierDismissible: barrierDismissible,
+    barrierLabel: CupertinoLocalizations.of(context).modalBarrierDismissLabel,
+    barrierColor: CupertinoDynamicColor.resolve(_kModalBarrierColor, context),
+    // This transition duration was eyeballed comparing with iOS
+    transitionDuration: const Duration(milliseconds: 250),
+    pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
+      return builder(context);
+    },
+    transitionBuilder: _buildCupertinoDialogTransitions,
+    useRootNavigator: useRootNavigator,
+    routeSettings: routeSettings,
+  );
+}
diff --git a/lib/src/cupertino/scrollbar.dart b/lib/src/cupertino/scrollbar.dart
new file mode 100644
index 0000000..4915c3b
--- /dev/null
+++ b/lib/src/cupertino/scrollbar.dart
@@ -0,0 +1,212 @@
+// Copyright 2014 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.
+
+import 'package:flute/gestures.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+
+// All values eyeballed.
+const double _kScrollbarMinLength = 36.0;
+const double _kScrollbarMinOverscrollLength = 8.0;
+const Duration _kScrollbarTimeToFade = Duration(milliseconds: 1200);
+const Duration _kScrollbarFadeDuration = Duration(milliseconds: 250);
+const Duration _kScrollbarResizeDuration = Duration(milliseconds: 100);
+
+// Extracted from iOS 13.1 beta using Debug View Hierarchy.
+const Color _kScrollbarColor = CupertinoDynamicColor.withBrightness(
+  color: Color(0x59000000),
+  darkColor: Color(0x80FFFFFF),
+);
+
+// This is the amount of space from the top of a vertical scrollbar to the
+// top edge of the scrollable, measured when the vertical scrollbar overscrolls
+// to the top.
+// TODO(LongCatIsLooong): fix https://github.com/flutter/flutter/issues/32175
+const double _kScrollbarMainAxisMargin = 3.0;
+const double _kScrollbarCrossAxisMargin = 3.0;
+
+/// An iOS style scrollbar.
+///
+/// To add a scrollbar to a [ScrollView], simply wrap the scroll view widget in
+/// a [CupertinoScrollbar] widget.
+///
+/// {@macro flutter.widgets.Scrollbar}
+///
+/// When dragging a [CupertinoScrollbar] thumb, the thickness and radius will
+/// animate from [thickness] and [radius] to [thicknessWhileDragging] and
+/// [radiusWhileDragging], respectively.
+///
+// TODO(Piinks): Add code sample
+///
+/// See also:
+///
+///  * [ListView], which displays a linear, scrollable list of children.
+///  * [GridView], which displays a 2 dimensional, scrollable array of children.
+///  * [Scrollbar], a Material Design scrollbar.
+///  * [RawScrollbar], a basic scrollbar that fades in and out, extended
+///    by this class to add more animations and behaviors.
+class CupertinoScrollbar extends RawScrollbar {
+  /// Creates an iOS style scrollbar that wraps the given [child].
+  ///
+  /// The [child] should be a source of [ScrollNotification] notifications,
+  /// typically a [Scrollable] widget.
+  const CupertinoScrollbar({
+    Key? key,
+    required Widget child,
+    ScrollController? controller,
+    bool isAlwaysShown = false,
+    double thickness = defaultThickness,
+    this.thicknessWhileDragging = defaultThicknessWhileDragging,
+    Radius radius = defaultRadius,
+    this.radiusWhileDragging = defaultRadiusWhileDragging,
+  }) : assert(thickness != null),
+       assert(thickness < double.infinity),
+       assert(thicknessWhileDragging != null),
+       assert(thicknessWhileDragging < double.infinity),
+       assert(radius != null),
+       assert(radiusWhileDragging != null),
+       super(
+         key: key,
+         child: child,
+         controller: controller,
+         isAlwaysShown: isAlwaysShown,
+         thickness: thickness,
+         radius: radius,
+         fadeDuration: _kScrollbarFadeDuration,
+         timeToFade: _kScrollbarTimeToFade,
+         pressDuration: const Duration(milliseconds: 100),
+       );
+
+  /// Default value for [thickness] if it's not specified in [CupertinoScrollbar].
+  static const double defaultThickness = 3;
+
+  /// Default value for [thicknessWhileDragging] if it's not specified in
+  /// [CupertinoScrollbar].
+  static const double defaultThicknessWhileDragging = 8.0;
+
+  /// Default value for [radius] if it's not specified in [CupertinoScrollbar].
+  static const Radius defaultRadius = Radius.circular(1.5);
+
+  /// Default value for [radiusWhileDragging] if it's not specified in
+  /// [CupertinoScrollbar].
+  static const Radius defaultRadiusWhileDragging = Radius.circular(4.0);
+
+  /// The thickness of the scrollbar when it's being dragged by the user.
+  ///
+  /// When the user starts dragging the scrollbar, the thickness will animate
+  /// from [thickness] to this value, then animate back when the user stops
+  /// dragging the scrollbar.
+  final double thicknessWhileDragging;
+
+  /// The radius of the scrollbar edges when the scrollbar is being dragged by
+  /// the user.
+  ///
+  /// When the user starts dragging the scrollbar, the radius will animate
+  /// from [radius] to this value, then animate back when the user stops
+  /// dragging the scrollbar.
+  final Radius radiusWhileDragging;
+
+  @override
+  _CupertinoScrollbarState createState() => _CupertinoScrollbarState();
+}
+
+class _CupertinoScrollbarState extends RawScrollbarState<CupertinoScrollbar> {
+  late AnimationController _thicknessAnimationController;
+
+  double get _thickness {
+    return widget.thickness! + _thicknessAnimationController.value * (widget.thicknessWhileDragging - widget.thickness!);
+  }
+
+  Radius get _radius {
+    return Radius.lerp(widget.radius, widget.radiusWhileDragging, _thicknessAnimationController.value)!;
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _thicknessAnimationController = AnimationController(
+      vsync: this,
+      duration: _kScrollbarResizeDuration,
+    );
+    _thicknessAnimationController.addListener(() {
+      updateScrollbarPainter();
+    });
+  }
+
+  @override
+  void updateScrollbarPainter() {
+    scrollbarPainter
+      ..color = CupertinoDynamicColor.resolve(_kScrollbarColor, context)
+      ..textDirection = Directionality.of(context)
+      ..thickness = _thickness
+      ..mainAxisMargin = _kScrollbarMainAxisMargin
+      ..crossAxisMargin = _kScrollbarCrossAxisMargin
+      ..radius = _radius
+      ..padding = MediaQuery.of(context).padding
+      ..minLength = _kScrollbarMinLength
+      ..minOverscrollLength = _kScrollbarMinOverscrollLength;
+  }
+
+  double _pressStartAxisPosition = 0.0;
+
+  // Long press event callbacks handle the gesture where the user long presses
+  // on the scrollbar thumb and then drags the scrollbar without releasing.
+
+  @override
+  void handleThumbPressStart(Offset localPosition) {
+    super.handleThumbPressStart(localPosition);
+    final Axis direction = getScrollbarDirection()!;
+    switch (direction) {
+      case Axis.vertical:
+        _pressStartAxisPosition = localPosition.dy;
+        break;
+      case Axis.horizontal:
+        _pressStartAxisPosition = localPosition.dx;
+        break;
+    }
+  }
+
+  @override
+  void handleThumbPress() {
+    if (getScrollbarDirection() == null) {
+      return;
+    }
+    super.handleThumbPress();
+    _thicknessAnimationController.forward().then<void>(
+          (_) => HapticFeedback.mediumImpact(),
+    );
+  }
+
+  @override
+  void handleThumbPressEnd(Offset localPosition, Velocity velocity) {
+    final Axis? direction = getScrollbarDirection();
+    if (direction == null) {
+      return;
+    }
+    _thicknessAnimationController.reverse();
+    super.handleThumbPressEnd(localPosition, velocity);
+    switch(direction) {
+      case Axis.vertical:
+        if (velocity.pixelsPerSecond.dy.abs() < 10 &&
+          (localPosition.dy - _pressStartAxisPosition).abs() > 0) {
+          HapticFeedback.mediumImpact();
+        }
+        break;
+      case Axis.horizontal:
+        if (velocity.pixelsPerSecond.dx.abs() < 10 &&
+          (localPosition.dx - _pressStartAxisPosition).abs() > 0) {
+          HapticFeedback.mediumImpact();
+        }
+        break;
+    }
+  }
+
+  @override
+  void dispose() {
+    _thicknessAnimationController.dispose();
+    super.dispose();
+  }
+}
diff --git a/lib/src/cupertino/search_field.dart b/lib/src/cupertino/search_field.dart
new file mode 100644
index 0000000..ec7de65
--- /dev/null
+++ b/lib/src/cupertino/search_field.dart
@@ -0,0 +1,403 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'button.dart';
+import 'colors.dart';
+import 'icons.dart';
+import 'localizations.dart';
+import 'text_field.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+
+/// A [CupertinoTextField] that mimics the look and behavior of UIKit's
+/// `UISearchTextField`.
+///
+/// This control defaults to showing the basic parts of a `UISearchTextField`,
+/// like the 'Search' placeholder, prefix-ed Search icon, and suffix-ed
+/// X-Mark icon.
+///
+/// To control the text that is displayed in the text field, use the
+/// [controller]. For example, to set the initial value of the text field, use
+/// a [controller] that already contains some text such as:
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// class MyPrefilledSearch extends StatefulWidget {
+///   @override
+///   _MyPrefilledSearchState createState() => _MyPrefilledSearchState();
+/// }
+///
+/// class _MyPrefilledSearchState extends State<MyPrefilledSearch> {
+///   TextEditingController _textController;
+///
+///   @override
+///   void initState() {
+///     super.initState();
+///     _textController = TextEditingController(text: 'initial text');
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return CupertinoSearchTextField(controller: _textController);
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// It is recommended to pass a [ValueChanged<String>] to both [onChanged] and
+/// [onSubmitted] parameters in order to be notified once the value of the
+/// field changes or is submitted by the keyboard:
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// class MyPrefilledSearch extends StatefulWidget {
+///   @override
+///   _MyPrefilledSearchState createState() => _MyPrefilledSearchState();
+/// }
+///
+/// class _MyPrefilledSearchState extends State<MyPrefilledSearch> {
+///   @override
+///   Widget build(BuildContext context) {
+///     return CupertinoSearchTextField(
+///       onChanged: (value) {
+///         print("The text has changed to: " + value);
+///       },
+///       onSubmitted: (value) {
+///         print("Submitted text: " + value);
+///       },
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+class CupertinoSearchTextField extends StatefulWidget {
+  /// Creates a [CupertinoTextField] that mimics the look and behavior of
+  /// UIKit's `UISearchTextField`.
+  ///
+  /// Similar to [CupertinoTextField], to provide a prefilled text entry, pass
+  /// in a [TextEditingController] with an initial value to the [controller]
+  /// parameter.
+  ///
+  /// The [onChanged] parameter takes a [ValueChanged<String>] which is invoked
+  /// upon a change in the text field's value.
+  ///
+  /// The [onSubmitted] parameter takes a [ValueChanged<String>] which is
+  /// invoked when the keyboard submits.
+  ///
+  /// To provide a hint placeholder text that appears when the text entry is
+  /// empty, pass a [String] to the [placeholder] parameter. This defaults to
+  /// 'Search'.
+  // TODO(DanielEdrisian): Localize the 'Search' placeholder.
+  ///
+  /// The [style] and [placeholderStyle] properties allow changing the style of
+  /// the text and placeholder of the textfield. [placeholderStyle] defaults
+  /// to the gray [CupertinoColors.secondaryLabel] iOS color.
+  ///
+  /// To set the text field's background color and border radius, pass a
+  /// [BoxDecoration] to the [decoration] parameter. This defaults to the
+  /// default translucent tertiarySystemFill iOS color and 9 px corner radius.
+  // TODO(DanielEdrisian): Must make border radius continuous, see
+  // https://github.com/flutter/flutter/issues/13914.
+  ///
+  /// The [itemColor] and [itemSize] properties allow changing the icon color
+  /// and icon size of the search icon (prefix) and X-Mark (suffix).
+  /// They default to [CupertinoColors.secondaryLabel] and [20.0].
+  ///
+  /// The [padding], [prefixInsets], and [suffixInsets] let you set the padding
+  /// insets for text, the search icon (prefix), and the X-Mark icon (suffix).
+  /// They default to values that replicate the `UISearchTextField` look. These
+  /// default fields were determined using the comparison tool in
+  /// https://github.com/flutter/platform_tests/.
+  ///
+  /// To customize the suffix icon, pass an [Icon] to [suffixIcon]. This
+  /// defaults to the X-Mark.
+  ///
+  /// To dictate when the X-Mark (suffix) should be visible, a.k.a. only on when
+  /// editing, not editing, on always, or on never, pass a
+  /// [OverlayVisibilityMode] to [suffixMode]. This defaults to only on when
+  /// editing.
+  ///
+  /// To customize the X-Mark (suffix) action, pass a [VoidCallback] to
+  /// [onSuffixTap]. This defaults to clearing the text.
+  const CupertinoSearchTextField({
+    Key? key,
+    this.controller,
+    this.onChanged,
+    this.onSubmitted,
+    this.style,
+    this.placeholder,
+    this.placeholderStyle,
+    this.decoration,
+    this.backgroundColor,
+    this.borderRadius,
+    this.padding = const EdgeInsetsDirectional.fromSTEB(3.8, 8, 5, 8),
+    this.itemColor = CupertinoColors.secondaryLabel,
+    this.itemSize = 20.0,
+    this.prefixInsets = const EdgeInsetsDirectional.fromSTEB(6, 0, 0, 4),
+    this.suffixInsets = const EdgeInsetsDirectional.fromSTEB(0, 0, 5, 2),
+    this.suffixIcon = const Icon(CupertinoIcons.xmark_circle_fill),
+    this.suffixMode = OverlayVisibilityMode.editing,
+    this.onSuffixTap,
+    this.restorationId,
+    this.focusNode,
+  })  : assert(padding != null),
+        assert(itemColor != null),
+        assert(itemSize != null),
+        assert(prefixInsets != null),
+        assert(suffixInsets != null),
+        assert(suffixIcon != null),
+        assert(suffixMode != null),
+        assert(
+          !((decoration != null) && (backgroundColor != null)),
+          'Cannot provide both a background color and a decoration\n'
+          'To provide both, use "decoration: BoxDecoration(color: '
+          'backgroundColor)"',
+        ),
+        assert(
+          !((decoration != null) && (borderRadius != null)),
+          'Cannot provide both a border radius and a decoration\n'
+          'To provide both, use "decoration: BoxDecoration(borderRadius: '
+          'borderRadius)"',
+        ),
+        super(key: key);
+
+  /// Controls the text being edited.
+  ///
+  /// Similar to [CupertinoTextField], to provide a prefilled text entry, pass
+  /// in a [TextEditingController] with an initial value to the [controller]
+  /// parameter. Defaults to creating its own [TextEditingController].
+  final TextEditingController? controller;
+
+  /// Invoked upon user input.
+  final ValueChanged<String>? onChanged;
+
+  /// Invoked upon keyboard submission.
+  final ValueChanged<String>? onSubmitted;
+
+  /// Allows changing the style of the text.
+  ///
+  /// Defaults to the gray [CupertinoColors.secondaryLabel] iOS color.
+  final TextStyle? style;
+
+  /// A hint placeholder text that appears when the text entry is empty.
+  ///
+  /// Defaults to 'Search' localized in each supported language.
+  final String? placeholder;
+
+  /// Sets the style of the placeholder of the textfield.
+  ///
+  /// Defaults to the gray [CupertinoColors.secondaryLabel] iOS color.
+  final TextStyle? placeholderStyle;
+
+  /// Sets the decoration for the text field.
+  ///
+  /// This property is automatically set using the [backgroundColor] and
+  /// [borderRadius] properties, which both have default values. Therefore,
+  /// [decoration] has a default value upon building the widget. It is designed
+  /// to mimic the look of a `UISearchTextField`.
+  final BoxDecoration? decoration;
+
+  /// Set the [decoration] property's background color.
+  ///
+  /// Can't be set along with the [decoration]. Defaults to the translucent
+  /// [CupertinoColors.tertiarySystemFill] iOS color.
+  final Color? backgroundColor;
+
+  /// Sets the [decoration] property's border radius.
+  ///
+  /// Can't be set along with the [decoration]. Defaults to 9 px circular
+  /// corner radius.
+  // TODO(DanielEdrisian): Must make border radius continuous, see
+  // https://github.com/flutter/flutter/issues/13914.
+  final BorderRadius? borderRadius;
+
+  /// Sets the padding insets for the text and placeholder.
+  ///
+  /// Cannot be null. Defaults to padding that replicates the
+  /// `UISearchTextField` look. The inset values were determined using the
+  /// comparison tool in https://github.com/flutter/platform_tests/.
+  final EdgeInsetsGeometry padding;
+
+  /// Sets the color for the suffix and prefix icons.
+  ///
+  /// Cannot be null. Defaults to [CupertinoColors.secondaryLabel].
+  final Color itemColor;
+
+  /// Sets the base icon size for the suffix and prefix icons.
+  ///
+  /// Cannot be null. The size of the icon is scaled using the accessibility
+  /// font scale settings. Defaults to [20.0].
+  final double itemSize;
+
+  /// Sets the padding insets for the suffix.
+  ///
+  /// Cannot be null. Defaults to padding that replicates the
+  /// `UISearchTextField` suffix look. The inset values were determined using
+  /// the comparison tool in https://github.com/flutter/platform_tests/.
+  final EdgeInsetsGeometry prefixInsets;
+
+  /// Sets the padding insets for the prefix.
+  ///
+  /// Cannot be null. Defaults to padding that replicates the
+  /// `UISearchTextField` prefix look. The inset values were determined using
+  /// the comparison tool in https://github.com/flutter/platform_tests/.
+  final EdgeInsetsGeometry suffixInsets;
+
+  /// Sets the suffix widget's icon.
+  ///
+  /// Cannot be null. Defaults to the X-Mark [CupertinoIcons.xmark_circle_fill].
+  /// "To change the functionality of the suffix icon, provide a custom
+  /// onSuffixTap callback and specify an intuitive suffixIcon.
+  final Icon suffixIcon;
+
+  /// Dictates when the X-Mark (suffix) should be visible.
+  ///
+  /// Cannot be null. Defaults to only on when editing.
+  final OverlayVisibilityMode suffixMode;
+
+  /// Sets the X-Mark (suffix) action.
+  ///
+  /// Defaults to clearing the text. The suffix action is customizable
+  /// so that users can override it with other functionality, that isn't
+  /// necessarily clearing text.
+  final VoidCallback? onSuffixTap;
+
+  /// {@macro flutter.material.textfield.restorationId}
+  final String? restorationId;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  @override
+  State<StatefulWidget> createState() => _CupertinoSearchTextFieldState();
+}
+
+class _CupertinoSearchTextFieldState extends State<CupertinoSearchTextField>
+    with RestorationMixin {
+  /// Default value for the border radius. Radius value was determined using the
+  /// comparison tool in https://github.com/flutter/platform_tests/.
+  final BorderRadius _kDefaultBorderRadius =
+      const BorderRadius.all(Radius.circular(9.0));
+
+  RestorableTextEditingController? _controller;
+
+  TextEditingController get _effectiveController =>
+      widget.controller ?? _controller!.value;
+
+  @override
+  void initState() {
+    super.initState();
+    if (widget.controller == null) {
+      _createLocalController();
+    }
+  }
+
+  @override
+  void didUpdateWidget(CupertinoSearchTextField oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.controller == null && oldWidget.controller != null) {
+      _createLocalController(oldWidget.controller!.value);
+    } else if (widget.controller != null && oldWidget.controller == null) {
+      unregisterFromRestoration(_controller!);
+      _controller!.dispose();
+      _controller = null;
+    }
+  }
+
+  @override
+  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
+    if (_controller != null) {
+      _registerController();
+    }
+  }
+
+  void _registerController() {
+    assert(_controller != null);
+    registerForRestoration(_controller!, 'controller');
+  }
+
+  void _createLocalController([TextEditingValue? value]) {
+    assert(_controller == null);
+    _controller = value == null
+        ? RestorableTextEditingController()
+        : RestorableTextEditingController.fromValue(value);
+    if (!restorePending) {
+      _registerController();
+    }
+  }
+
+  @override
+  String? get restorationId => widget.restorationId;
+
+  void _defaultOnSuffixTap() {
+    final bool textChanged = _effectiveController.text.isNotEmpty;
+    _effectiveController.clear();
+    if (widget.onChanged != null && textChanged)
+      widget.onChanged!(_effectiveController.text);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final String placeholder = widget.placeholder ??
+        CupertinoLocalizations.of(context).searchTextFieldPlaceholderLabel;
+
+    final TextStyle placeholderStyle = widget.placeholderStyle ??
+        const TextStyle(color: CupertinoColors.systemGrey);
+
+    // The icon size will be scaled by a factor of the accessibility text scale,
+    // to follow the behavior of `UISearchTextField`.
+    final double scaledIconSize =
+        MediaQuery.textScaleFactorOf(context) * widget.itemSize;
+
+    // If decoration was not provided, create a decoration with the provided
+    // background color and border radius.
+    final BoxDecoration decoration = widget.decoration ??
+        BoxDecoration(
+          color: widget.backgroundColor ?? CupertinoColors.tertiarySystemFill,
+          borderRadius: widget.borderRadius ?? _kDefaultBorderRadius,
+        );
+
+    final IconThemeData iconThemeData = IconThemeData(
+        color: CupertinoDynamicColor.resolve(widget.itemColor, context),
+        size: scaledIconSize);
+
+    final Widget prefix = Padding(
+      child: IconTheme(
+          child: const Icon(CupertinoIcons.search), data: iconThemeData),
+      padding: widget.prefixInsets,
+    );
+
+    final Widget suffix = Padding(
+      child: CupertinoButton(
+        child: IconTheme(child: widget.suffixIcon, data: iconThemeData),
+        onPressed: widget.onSuffixTap ?? _defaultOnSuffixTap,
+        minSize: 0,
+        padding: EdgeInsets.zero,
+      ),
+      padding: widget.suffixInsets,
+    );
+
+    return CupertinoTextField(
+      controller: _effectiveController,
+      decoration: decoration,
+      style: widget.style,
+      prefix: prefix,
+      suffix: suffix,
+      suffixMode: widget.suffixMode,
+      placeholder: placeholder,
+      placeholderStyle: placeholderStyle,
+      padding: widget.padding,
+      onChanged: widget.onChanged,
+      onSubmitted: widget.onSubmitted,
+      focusNode: widget.focusNode,
+    );
+  }
+}
diff --git a/lib/src/cupertino/segmented_control.dart b/lib/src/cupertino/segmented_control.dart
new file mode 100644
index 0000000..2183d83
--- /dev/null
+++ b/lib/src/cupertino/segmented_control.dart
@@ -0,0 +1,750 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'theme.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+
+// Minimum padding from edges of the segmented control to edges of
+// encompassing widget.
+const EdgeInsetsGeometry _kHorizontalItemPadding = EdgeInsets.symmetric(horizontal: 16.0);
+
+// Minimum height of the segmented control.
+const double _kMinSegmentedControlHeight = 28.0;
+
+// The duration of the fade animation used to transition when a new widget
+// is selected.
+const Duration _kFadeDuration = Duration(milliseconds: 165);
+
+/// An iOS-style segmented control.
+///
+/// Displays the widgets provided in the [Map] of [children] in a
+/// horizontal list. Used to select between a number of mutually exclusive
+/// options. When one option in the segmented control is selected, the other
+/// options in the segmented control cease to be selected.
+///
+/// A segmented control can feature any [Widget] as one of the values in its
+/// [Map] of [children]. The type T is the type of the keys used
+/// to identify each widget and determine which widget is selected. As
+/// required by the [Map] class, keys must be of consistent types
+/// and must be comparable. The ordering of the keys will determine the order
+/// of the widgets in the segmented control.
+///
+/// When the state of the segmented control changes, the widget calls the
+/// [onValueChanged] callback. The map key associated with the newly selected
+/// widget is returned in the [onValueChanged] callback. Typically, widgets
+/// that use a segmented control will listen for the [onValueChanged] callback
+/// and rebuild the segmented control with a new [groupValue] to update which
+/// option is currently selected.
+///
+/// The [children] will be displayed in the order of the keys in the [Map].
+/// The height of the segmented control is determined by the height of the
+/// tallest widget provided as a value in the [Map] of [children].
+/// The width of each child in the segmented control will be equal to the width
+/// of widest child, unless the combined width of the children is wider than
+/// the available horizontal space. In this case, the available horizontal space
+/// is divided by the number of provided [children] to determine the width of
+/// each widget. The selection area for each of the widgets in the [Map] of
+/// [children] will then be expanded to fill the calculated space, so each
+/// widget will appear to have the same dimensions.
+///
+/// A segmented control may optionally be created with custom colors. The
+/// [unselectedColor], [selectedColor], [borderColor], and [pressedColor]
+/// arguments can be used to override the segmented control's colors from
+/// [CupertinoTheme] defaults.
+///
+/// See also:
+///
+///  * [CupertinoSegmentedControl], a segmented control widget in the style used
+///    up until iOS 13.
+///  * <https://developer.apple.com/design/human-interface-guidelines/ios/controls/segmented-controls/>
+class CupertinoSegmentedControl<T extends Object> extends StatefulWidget {
+  /// Creates an iOS-style segmented control bar.
+  ///
+  /// The [children] and [onValueChanged] arguments must not be null. The
+  /// [children] argument must be an ordered [Map] such as a [LinkedHashMap].
+  /// Further, the length of the [children] list must be greater than one.
+  ///
+  /// Each widget value in the map of [children] must have an associated key
+  /// that uniquely identifies this widget. This key is what will be returned
+  /// in the [onValueChanged] callback when a new value from the [children] map
+  /// is selected.
+  ///
+  /// The [groupValue] is the currently selected value for the segmented control.
+  /// If no [groupValue] is provided, or the [groupValue] is null, no widget will
+  /// appear as selected. The [groupValue] must be either null or one of the keys
+  /// in the [children] map.
+  CupertinoSegmentedControl({
+    Key? key,
+    required this.children,
+    required this.onValueChanged,
+    this.groupValue,
+    this.unselectedColor,
+    this.selectedColor,
+    this.borderColor,
+    this.pressedColor,
+    this.padding,
+  }) : assert(children != null),
+       assert(children.length >= 2),
+       assert(onValueChanged != null),
+       assert(
+         groupValue == null || children.keys.any((T child) => child == groupValue),
+         'The groupValue must be either null or one of the keys in the children map.',
+       ),
+       super(key: key);
+
+  /// The identifying keys and corresponding widget values in the
+  /// segmented control.
+  ///
+  /// The map must have more than one entry.
+  /// This attribute must be an ordered [Map] such as a [LinkedHashMap].
+  final Map<T, Widget> children;
+
+  /// The identifier of the widget that is currently selected.
+  ///
+  /// This must be one of the keys in the [Map] of [children].
+  /// If this attribute is null, no widget will be initially selected.
+  final T? groupValue;
+
+  /// The callback that is called when a new option is tapped.
+  ///
+  /// This attribute must not be null.
+  ///
+  /// The segmented control passes the newly selected widget's associated key
+  /// to the callback but does not actually change state until the parent
+  /// widget rebuilds the segmented control with the new [groupValue].
+  ///
+  /// The callback provided to [onValueChanged] should update the state of
+  /// the parent [StatefulWidget] using the [State.setState] method, so that
+  /// the parent gets rebuilt; for example:
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// class SegmentedControlExample extends StatefulWidget {
+  ///   @override
+  ///   State createState() => SegmentedControlExampleState();
+  /// }
+  ///
+  /// class SegmentedControlExampleState extends State<SegmentedControlExample> {
+  ///   final Map<int, Widget> children = const {
+  ///     0: Text('Child 1'),
+  ///     1: Text('Child 2'),
+  ///   };
+  ///
+  ///   int currentValue;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Container(
+  ///       child: CupertinoSegmentedControl<int>(
+  ///         children: children,
+  ///         onValueChanged: (int newValue) {
+  ///           setState(() {
+  ///             currentValue = newValue;
+  ///           });
+  ///         },
+  ///         groupValue: currentValue,
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  final ValueChanged<T> onValueChanged;
+
+  /// The color used to fill the backgrounds of unselected widgets and as the
+  /// text color of the selected widget.
+  ///
+  /// Defaults to [CupertinoTheme]'s `primaryContrastingColor` if null.
+  final Color? unselectedColor;
+
+  /// The color used to fill the background of the selected widget and as the text
+  /// color of unselected widgets.
+  ///
+  /// Defaults to [CupertinoTheme]'s `primaryColor` if null.
+  final Color? selectedColor;
+
+  /// The color used as the border around each widget.
+  ///
+  /// Defaults to [CupertinoTheme]'s `primaryColor` if null.
+  final Color? borderColor;
+
+  /// The color used to fill the background of the widget the user is
+  /// temporarily interacting with through a long press or drag.
+  ///
+  /// Defaults to the selectedColor at 20% opacity if null.
+  final Color? pressedColor;
+
+  /// The CupertinoSegmentedControl will be placed inside this padding.
+  ///
+  /// Defaults to EdgeInsets.symmetric(horizontal: 16.0)
+  final EdgeInsetsGeometry? padding;
+
+  @override
+  _SegmentedControlState<T> createState() => _SegmentedControlState<T>();
+}
+
+class _SegmentedControlState<T extends Object> extends State<CupertinoSegmentedControl<T>>
+    with TickerProviderStateMixin<CupertinoSegmentedControl<T>> {
+  T? _pressedKey;
+
+  final List<AnimationController> _selectionControllers = <AnimationController>[];
+  final List<ColorTween> _childTweens = <ColorTween>[];
+
+  late ColorTween _forwardBackgroundColorTween;
+  late ColorTween _reverseBackgroundColorTween;
+  late ColorTween _textColorTween;
+
+  Color? _selectedColor;
+  Color? _unselectedColor;
+  Color? _borderColor;
+  Color? _pressedColor;
+
+  AnimationController createAnimationController() {
+    return AnimationController(
+      duration: _kFadeDuration,
+      vsync: this,
+    )..addListener(() {
+      setState(() {
+        // State of background/text colors has changed
+      });
+    });
+  }
+
+  bool _updateColors() {
+    assert(mounted, 'This should only be called after didUpdateDependencies');
+    bool changed = false;
+    final Color selectedColor = widget.selectedColor ?? CupertinoTheme.of(context).primaryColor;
+    if (_selectedColor != selectedColor) {
+      changed = true;
+      _selectedColor = selectedColor;
+    }
+    final Color unselectedColor = widget.unselectedColor ?? CupertinoTheme.of(context).primaryContrastingColor;
+    if (_unselectedColor != unselectedColor) {
+      changed = true;
+      _unselectedColor = unselectedColor;
+    }
+    final Color borderColor = widget.borderColor ?? CupertinoTheme.of(context).primaryColor;
+    if (_borderColor != borderColor) {
+      changed = true;
+      _borderColor = borderColor;
+    }
+    final Color pressedColor = widget.pressedColor ?? CupertinoTheme.of(context).primaryColor.withOpacity(0.2);
+    if (_pressedColor != pressedColor) {
+      changed = true;
+      _pressedColor = pressedColor;
+    }
+
+    _forwardBackgroundColorTween = ColorTween(
+      begin: _pressedColor,
+      end: _selectedColor,
+    );
+    _reverseBackgroundColorTween = ColorTween(
+      begin: _unselectedColor,
+      end: _selectedColor,
+    );
+    _textColorTween = ColorTween(
+      begin: _selectedColor,
+      end: _unselectedColor,
+    );
+    return changed;
+  }
+
+  void _updateAnimationControllers() {
+    assert(mounted, 'This should only be called after didUpdateDependencies');
+    for (final AnimationController controller in _selectionControllers) {
+      controller.dispose();
+    }
+    _selectionControllers.clear();
+    _childTweens.clear();
+
+    for (final T key in widget.children.keys) {
+      final AnimationController animationController = createAnimationController();
+      if (widget.groupValue == key) {
+        _childTweens.add(_reverseBackgroundColorTween);
+        animationController.value = 1.0;
+      } else {
+        _childTweens.add(_forwardBackgroundColorTween);
+      }
+      _selectionControllers.add(animationController);
+    }
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+
+    if (_updateColors()) {
+      _updateAnimationControllers();
+    }
+  }
+
+  @override
+  void didUpdateWidget(CupertinoSegmentedControl<T> oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    if (_updateColors() || oldWidget.children.length != widget.children.length) {
+      _updateAnimationControllers();
+    }
+
+    if (oldWidget.groupValue != widget.groupValue) {
+      int index = 0;
+      for (final T key in widget.children.keys) {
+        if (widget.groupValue == key) {
+          _childTweens[index] = _forwardBackgroundColorTween;
+          _selectionControllers[index].forward();
+        } else {
+          _childTweens[index] = _reverseBackgroundColorTween;
+          _selectionControllers[index].reverse();
+        }
+        index += 1;
+      }
+    }
+  }
+
+  @override
+  void dispose() {
+    for (final AnimationController animationController in _selectionControllers) {
+      animationController.dispose();
+    }
+    super.dispose();
+  }
+
+
+  void _onTapDown(T currentKey) {
+    if (_pressedKey == null && currentKey != widget.groupValue) {
+      setState(() {
+        _pressedKey = currentKey;
+      });
+    }
+  }
+
+  void _onTapCancel() {
+    setState(() {
+      _pressedKey = null;
+    });
+  }
+
+  void _onTap(T currentKey) {
+    if (currentKey != _pressedKey)
+      return;
+    if (currentKey != widget.groupValue) {
+      widget.onValueChanged(currentKey);
+    }
+    _pressedKey = null;
+  }
+
+  Color? getTextColor(int index, T currentKey) {
+    if (_selectionControllers[index].isAnimating)
+      return _textColorTween.evaluate(_selectionControllers[index]);
+    if (widget.groupValue == currentKey)
+      return _unselectedColor;
+    return _selectedColor;
+  }
+
+  Color? getBackgroundColor(int index, T currentKey) {
+    if (_selectionControllers[index].isAnimating)
+      return _childTweens[index].evaluate(_selectionControllers[index]);
+    if (widget.groupValue == currentKey)
+      return _selectedColor;
+    if (_pressedKey == currentKey)
+      return _pressedColor;
+    return _unselectedColor;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final List<Widget> _gestureChildren = <Widget>[];
+    final List<Color> _backgroundColors = <Color>[];
+    int index = 0;
+    int? selectedIndex;
+    int? pressedIndex;
+    for (final T currentKey in widget.children.keys) {
+      selectedIndex = (widget.groupValue == currentKey) ? index : selectedIndex;
+      pressedIndex = (_pressedKey == currentKey) ? index : pressedIndex;
+
+      final TextStyle textStyle = DefaultTextStyle.of(context).style.copyWith(
+        color: getTextColor(index, currentKey),
+      );
+      final IconThemeData iconTheme = IconThemeData(
+        color: getTextColor(index, currentKey),
+      );
+
+      Widget child = Center(
+        child: widget.children[currentKey],
+      );
+
+      child = GestureDetector(
+        behavior: HitTestBehavior.opaque,
+        onTapDown: (TapDownDetails event) {
+          _onTapDown(currentKey);
+        },
+        onTapCancel: _onTapCancel,
+        onTap: () {
+          _onTap(currentKey);
+        },
+        child: IconTheme(
+          data: iconTheme,
+          child: DefaultTextStyle(
+            style: textStyle,
+            child: Semantics(
+              button: true,
+              inMutuallyExclusiveGroup: true,
+              selected: widget.groupValue == currentKey,
+              child: child,
+            ),
+          ),
+        ),
+      );
+
+      _backgroundColors.add(getBackgroundColor(index, currentKey)!);
+      _gestureChildren.add(child);
+      index += 1;
+    }
+
+    final Widget box = _SegmentedControlRenderWidget<T>(
+      children: _gestureChildren,
+      selectedIndex: selectedIndex,
+      pressedIndex: pressedIndex,
+      backgroundColors: _backgroundColors,
+      borderColor: _borderColor!,
+    );
+
+    return Padding(
+      padding: widget.padding ?? _kHorizontalItemPadding,
+      child: UnconstrainedBox(
+        constrainedAxis: Axis.horizontal,
+        child: box,
+      ),
+    );
+  }
+}
+
+class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget {
+  _SegmentedControlRenderWidget({
+    Key? key,
+    List<Widget> children = const <Widget>[],
+    required this.selectedIndex,
+    required this.pressedIndex,
+    required this.backgroundColors,
+    required this.borderColor,
+  }) : super(
+          key: key,
+          children: children,
+        );
+
+  final int? selectedIndex;
+  final int? pressedIndex;
+  final List<Color> backgroundColors;
+  final Color borderColor;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _RenderSegmentedControl<T>(
+      textDirection: Directionality.of(context),
+      selectedIndex: selectedIndex,
+      pressedIndex: pressedIndex,
+      backgroundColors: backgroundColors,
+      borderColor: borderColor,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderSegmentedControl<T> renderObject) {
+    renderObject
+      ..textDirection = Directionality.of(context)
+      ..selectedIndex = selectedIndex
+      ..pressedIndex = pressedIndex
+      ..backgroundColors = backgroundColors
+      ..borderColor = borderColor;
+  }
+}
+
+class _SegmentedControlContainerBoxParentData extends ContainerBoxParentData<RenderBox> {
+  RRect? surroundingRect;
+}
+
+typedef _NextChild = RenderBox? Function(RenderBox child);
+
+class _RenderSegmentedControl<T> extends RenderBox
+    with ContainerRenderObjectMixin<RenderBox, ContainerBoxParentData<RenderBox>>,
+        RenderBoxContainerDefaultsMixin<RenderBox, ContainerBoxParentData<RenderBox>> {
+  _RenderSegmentedControl({
+    required int? selectedIndex,
+    required int? pressedIndex,
+    required TextDirection textDirection,
+    required List<Color> backgroundColors,
+    required Color borderColor,
+  }) : assert(textDirection != null),
+       _textDirection = textDirection,
+       _selectedIndex = selectedIndex,
+       _pressedIndex = pressedIndex,
+       _backgroundColors = backgroundColors,
+       _borderColor = borderColor;
+
+  int? get selectedIndex => _selectedIndex;
+  int? _selectedIndex;
+  set selectedIndex(int? value) {
+    if (_selectedIndex == value) {
+      return;
+    }
+    _selectedIndex = value;
+    markNeedsPaint();
+  }
+
+  int? get pressedIndex => _pressedIndex;
+  int? _pressedIndex;
+  set pressedIndex(int? value) {
+    if (_pressedIndex == value) {
+      return;
+    }
+    _pressedIndex = value;
+    markNeedsPaint();
+  }
+
+  TextDirection get textDirection => _textDirection;
+  TextDirection _textDirection;
+  set textDirection(TextDirection value) {
+    if (_textDirection == value) {
+      return;
+    }
+    _textDirection = value;
+    markNeedsLayout();
+  }
+
+  List<Color> get backgroundColors => _backgroundColors;
+  List<Color> _backgroundColors;
+  set backgroundColors(List<Color> value) {
+    if (_backgroundColors == value) {
+      return;
+    }
+    _backgroundColors = value;
+    markNeedsPaint();
+  }
+
+  Color get borderColor => _borderColor;
+  Color _borderColor;
+  set borderColor(Color value) {
+    if (_borderColor == value) {
+      return;
+    }
+    _borderColor = value;
+    markNeedsPaint();
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    RenderBox? child = firstChild;
+    double minWidth = 0.0;
+    while (child != null) {
+      final _SegmentedControlContainerBoxParentData childParentData = child.parentData! as _SegmentedControlContainerBoxParentData;
+      final double childWidth = child.getMinIntrinsicWidth(height);
+      minWidth = math.max(minWidth, childWidth);
+      child = childParentData.nextSibling;
+    }
+    return minWidth * childCount;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    RenderBox? child = firstChild;
+    double maxWidth = 0.0;
+    while (child != null) {
+      final _SegmentedControlContainerBoxParentData childParentData = child.parentData! as _SegmentedControlContainerBoxParentData;
+      final double childWidth = child.getMaxIntrinsicWidth(height);
+      maxWidth = math.max(maxWidth, childWidth);
+      child = childParentData.nextSibling;
+    }
+    return maxWidth * childCount;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    RenderBox? child = firstChild;
+    double minHeight = 0.0;
+    while (child != null) {
+      final _SegmentedControlContainerBoxParentData childParentData = child.parentData! as _SegmentedControlContainerBoxParentData;
+      final double childHeight = child.getMinIntrinsicHeight(width);
+      minHeight = math.max(minHeight, childHeight);
+      child = childParentData.nextSibling;
+    }
+    return minHeight;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    RenderBox? child = firstChild;
+    double maxHeight = 0.0;
+    while (child != null) {
+      final _SegmentedControlContainerBoxParentData childParentData = child.parentData! as _SegmentedControlContainerBoxParentData;
+      final double childHeight = child.getMaxIntrinsicHeight(width);
+      maxHeight = math.max(maxHeight, childHeight);
+      child = childParentData.nextSibling;
+    }
+    return maxHeight;
+  }
+
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    return defaultComputeDistanceToHighestActualBaseline(baseline);
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! _SegmentedControlContainerBoxParentData) {
+      child.parentData = _SegmentedControlContainerBoxParentData();
+    }
+  }
+
+  void _layoutRects(_NextChild nextChild, RenderBox? leftChild, RenderBox? rightChild) {
+    RenderBox? child = leftChild;
+    double start = 0.0;
+    while (child != null) {
+      final _SegmentedControlContainerBoxParentData childParentData = child.parentData! as _SegmentedControlContainerBoxParentData;
+      final Offset childOffset = Offset(start, 0.0);
+      childParentData.offset = childOffset;
+      final Rect childRect = Rect.fromLTWH(start, 0.0, child.size.width, child.size.height);
+      final RRect rChildRect;
+      if (child == leftChild) {
+        rChildRect = RRect.fromRectAndCorners(childRect, topLeft: const Radius.circular(3.0),
+            bottomLeft: const Radius.circular(3.0));
+      } else if (child == rightChild) {
+        rChildRect = RRect.fromRectAndCorners(childRect, topRight: const Radius.circular(3.0),
+            bottomRight: const Radius.circular(3.0));
+      } else {
+        rChildRect = RRect.fromRectAndCorners(childRect);
+      }
+      childParentData.surroundingRect = rChildRect;
+      start += child.size.width;
+      child = nextChild(child);
+    }
+  }
+
+  Size _calculateChildSize(BoxConstraints constraints) {
+    double maxHeight = _kMinSegmentedControlHeight;
+    double childWidth = constraints.minWidth / childCount;
+    RenderBox? child = firstChild;
+    while (child != null) {
+      childWidth = math.max(childWidth, child.getMaxIntrinsicWidth(double.infinity));
+      child = childAfter(child);
+    }
+    childWidth = math.min(childWidth, constraints.maxWidth / childCount);
+    child = firstChild;
+    while (child != null) {
+      final double boxHeight = child.getMaxIntrinsicHeight(childWidth);
+      maxHeight = math.max(maxHeight, boxHeight);
+      child = childAfter(child);
+    }
+    return Size(childWidth, maxHeight);
+  }
+
+  Size _computeOverallSizeFromChildSize(Size childSize) {
+    return constraints.constrain(Size(childSize.width * childCount, childSize.height));
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    final Size childSize = _calculateChildSize(constraints);
+    return _computeOverallSizeFromChildSize(childSize);
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    final Size childSize = _calculateChildSize(constraints);
+
+    final BoxConstraints childConstraints = BoxConstraints.tightFor(
+      width: childSize.width,
+      height: childSize.height,
+    );
+
+    RenderBox? child = firstChild;
+    while (child != null) {
+      child.layout(childConstraints, parentUsesSize: true);
+      child = childAfter(child);
+    }
+
+    switch (textDirection) {
+      case TextDirection.rtl:
+        _layoutRects(
+          childBefore,
+          lastChild,
+          firstChild,
+        );
+        break;
+      case TextDirection.ltr:
+        _layoutRects(
+          childAfter,
+          firstChild,
+          lastChild,
+        );
+        break;
+    }
+
+    size = _computeOverallSizeFromChildSize(childSize);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    RenderBox? child = firstChild;
+    int index = 0;
+    while (child != null) {
+      _paintChild(context, offset, child, index);
+      child = childAfter(child);
+      index += 1;
+    }
+  }
+
+  void _paintChild(PaintingContext context, Offset offset, RenderBox child, int childIndex) {
+    assert(child != null);
+
+    final _SegmentedControlContainerBoxParentData childParentData = child.parentData! as _SegmentedControlContainerBoxParentData;
+
+    context.canvas.drawRRect(
+      childParentData.surroundingRect!.shift(offset),
+      Paint()
+        ..color = backgroundColors[childIndex]
+        ..style = PaintingStyle.fill,
+    );
+    context.canvas.drawRRect(
+      childParentData.surroundingRect!.shift(offset),
+      Paint()
+        ..color = borderColor
+        ..strokeWidth = 1.0
+        ..style = PaintingStyle.stroke,
+    );
+
+    context.paintChild(child, childParentData.offset + offset);
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    assert(position != null);
+    RenderBox? child = lastChild;
+    while (child != null) {
+      final _SegmentedControlContainerBoxParentData childParentData = child.parentData! as _SegmentedControlContainerBoxParentData;
+      if (childParentData.surroundingRect!.contains(position)) {
+        return result.addWithPaintOffset(
+          offset: childParentData.offset,
+          position: position,
+          hitTest: (BoxHitTestResult result, Offset localOffset) {
+            assert(localOffset == position - childParentData.offset);
+            return child!.hitTest(result, position: localOffset);
+          },
+        );
+      }
+      child = childParentData.previousSibling;
+    }
+    return false;
+  }
+}
diff --git a/lib/src/cupertino/slider.dart b/lib/src/cupertino/slider.dart
new file mode 100644
index 0000000..cdf3685
--- /dev/null
+++ b/lib/src/cupertino/slider.dart
@@ -0,0 +1,574 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'theme.dart';
+import 'thumb_painter.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// int _cupertinoSliderValue = 1;
+// void setState(VoidCallback fn) { }
+
+/// An iOS-style slider.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=ufb4gIPDmEs}
+///
+/// Used to select from a range of values.
+///
+/// A slider can be used to select from either a continuous or a discrete set of
+/// values. The default is use a continuous range of values from [min] to [max].
+/// To use discrete values, use a non-null value for [divisions], which
+/// indicates the number of discrete intervals. For example, if [min] is 0.0 and
+/// [max] is 50.0 and [divisions] is 5, then the slider can take on the values
+/// discrete values 0.0, 10.0, 20.0, 30.0, 40.0, and 50.0.
+///
+/// The slider itself does not maintain any state. Instead, when the state of
+/// the slider changes, the widget calls the [onChanged] callback. Most widgets
+/// that use a slider will listen for the [onChanged] callback and rebuild the
+/// slider with a new [value] to update the visual appearance of the slider.
+///
+/// See also:
+///
+///  * <https://developer.apple.com/ios/human-interface-guidelines/controls/sliders/>
+class CupertinoSlider extends StatefulWidget {
+  /// Creates an iOS-style slider.
+  ///
+  /// The slider itself does not maintain any state. Instead, when the state of
+  /// the slider changes, the widget calls the [onChanged] callback. Most widgets
+  /// that use a slider will listen for the [onChanged] callback and rebuild the
+  /// slider with a new [value] to update the visual appearance of the slider.
+  ///
+  /// * [value] determines currently selected value for this slider.
+  /// * [onChanged] is called when the user selects a new value for the slider.
+  /// * [onChangeStart] is called when the user starts to select a new value for
+  ///   the slider.
+  /// * [onChangeEnd] is called when the user is done selecting a new value for
+  ///   the slider.
+  const CupertinoSlider({
+    Key? key,
+    required this.value,
+    required this.onChanged,
+    this.onChangeStart,
+    this.onChangeEnd,
+    this.min = 0.0,
+    this.max = 1.0,
+    this.divisions,
+    this.activeColor,
+    this.thumbColor = CupertinoColors.white,
+  }) : assert(value != null),
+       assert(min != null),
+       assert(max != null),
+       assert(value >= min && value <= max),
+       assert(divisions == null || divisions > 0),
+       assert(thumbColor != null),
+       super(key: key);
+
+  /// The currently selected value for this slider.
+  ///
+  /// The slider's thumb is drawn at a position that corresponds to this value.
+  final double value;
+
+  /// Called when the user selects a new value for the slider.
+  ///
+  /// The slider passes the new value to the callback but does not actually
+  /// change state until the parent widget rebuilds the slider with the new
+  /// value.
+  ///
+  /// If null, the slider will be displayed as disabled.
+  ///
+  /// The callback provided to onChanged should update the state of the parent
+  /// [StatefulWidget] using the [State.setState] method, so that the parent
+  /// gets rebuilt; for example:
+  ///
+  /// ```dart
+  /// CupertinoSlider(
+  ///   value: _cupertinoSliderValue.toDouble(),
+  ///   min: 1.0,
+  ///   max: 10.0,
+  ///   divisions: 10,
+  ///   onChanged: (double newValue) {
+  ///     setState(() {
+  ///       _cupertinoSliderValue = newValue.round();
+  ///     });
+  ///   },
+  /// )
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * [onChangeStart] for a callback that is called when the user starts
+  ///    changing the value.
+  ///  * [onChangeEnd] for a callback that is called when the user stops
+  ///    changing the value.
+  final ValueChanged<double>? onChanged;
+
+  /// Called when the user starts selecting a new value for the slider.
+  ///
+  /// This callback shouldn't be used to update the slider [value] (use
+  /// [onChanged] for that), but rather to be notified when the user has started
+  /// selecting a new value by starting a drag.
+  ///
+  /// The value passed will be the last [value] that the slider had before the
+  /// change began.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// CupertinoSlider(
+  ///   value: _cupertinoSliderValue.toDouble(),
+  ///   min: 1.0,
+  ///   max: 10.0,
+  ///   divisions: 10,
+  ///   onChanged: (double newValue) {
+  ///     setState(() {
+  ///       _cupertinoSliderValue = newValue.round();
+  ///     });
+  ///   },
+  ///   onChangeStart: (double startValue) {
+  ///     print('Started change at $startValue');
+  ///   },
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [onChangeEnd] for a callback that is called when the value change is
+  ///    complete.
+  final ValueChanged<double>? onChangeStart;
+
+  /// Called when the user is done selecting a new value for the slider.
+  ///
+  /// This callback shouldn't be used to update the slider [value] (use
+  /// [onChanged] for that), but rather to know when the user has completed
+  /// selecting a new [value] by ending a drag.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// CupertinoSlider(
+  ///   value: _cupertinoSliderValue.toDouble(),
+  ///   min: 1.0,
+  ///   max: 10.0,
+  ///   divisions: 10,
+  ///   onChanged: (double newValue) {
+  ///     setState(() {
+  ///       _cupertinoSliderValue = newValue.round();
+  ///     });
+  ///   },
+  ///   onChangeEnd: (double newValue) {
+  ///     print('Ended change on $newValue');
+  ///   },
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [onChangeStart] for a callback that is called when a value change
+  ///    begins.
+  final ValueChanged<double>? onChangeEnd;
+
+  /// The minimum value the user can select.
+  ///
+  /// Defaults to 0.0.
+  final double min;
+
+  /// The maximum value the user can select.
+  ///
+  /// Defaults to 1.0.
+  final double max;
+
+  /// The number of discrete divisions.
+  ///
+  /// If null, the slider is continuous.
+  final int? divisions;
+
+  /// The color to use for the portion of the slider that has been selected.
+  ///
+  /// Defaults to the [CupertinoTheme]'s primary color if null.
+  final Color? activeColor;
+
+  /// The color to use for the thumb of the slider.
+  ///
+  /// Thumb color must not be null.
+  ///
+  /// Defaults to [CupertinoColors.white].
+  final Color thumbColor;
+
+  @override
+  _CupertinoSliderState createState() => _CupertinoSliderState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('value', value));
+    properties.add(DoubleProperty('min', min));
+    properties.add(DoubleProperty('max', max));
+  }
+}
+
+class _CupertinoSliderState extends State<CupertinoSlider> with TickerProviderStateMixin {
+  void _handleChanged(double value) {
+    assert(widget.onChanged != null);
+    final double lerpValue = lerpDouble(widget.min, widget.max, value)!;
+    if (lerpValue != widget.value) {
+      widget.onChanged!(lerpValue);
+    }
+  }
+
+  void _handleDragStart(double value) {
+    assert(widget.onChangeStart != null);
+    widget.onChangeStart!(lerpDouble(widget.min, widget.max, value)!);
+  }
+
+  void _handleDragEnd(double value) {
+    assert(widget.onChangeEnd != null);
+    widget.onChangeEnd!(lerpDouble(widget.min, widget.max, value)!);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _CupertinoSliderRenderObjectWidget(
+      value: (widget.value - widget.min) / (widget.max - widget.min),
+      divisions: widget.divisions,
+      activeColor: CupertinoDynamicColor.resolve(
+        widget.activeColor ?? CupertinoTheme.of(context).primaryColor,
+        context,
+      ),
+      thumbColor: widget.thumbColor,
+      onChanged: widget.onChanged != null ? _handleChanged : null,
+      onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
+      onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
+      vsync: this,
+    );
+  }
+}
+
+class _CupertinoSliderRenderObjectWidget extends LeafRenderObjectWidget {
+  const _CupertinoSliderRenderObjectWidget({
+    Key? key,
+    required this.value,
+    this.divisions,
+    required this.activeColor,
+    required this.thumbColor,
+    this.onChanged,
+    this.onChangeStart,
+    this.onChangeEnd,
+    required this.vsync,
+  }) : super(key: key);
+
+  final double value;
+  final int? divisions;
+  final Color activeColor;
+  final Color thumbColor;
+  final ValueChanged<double>? onChanged;
+  final ValueChanged<double>? onChangeStart;
+  final ValueChanged<double>? onChangeEnd;
+  final TickerProvider vsync;
+
+  @override
+  _RenderCupertinoSlider createRenderObject(BuildContext context) {
+    assert(debugCheckHasDirectionality(context));
+    return _RenderCupertinoSlider(
+      value: value,
+      divisions: divisions,
+      activeColor: activeColor,
+      thumbColor: CupertinoDynamicColor.resolve(thumbColor, context),
+      trackColor: CupertinoDynamicColor.resolve(CupertinoColors.systemFill, context),
+      onChanged: onChanged,
+      onChangeStart: onChangeStart,
+      onChangeEnd: onChangeEnd,
+      vsync: vsync,
+      textDirection: Directionality.of(context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderCupertinoSlider renderObject) {
+    assert(debugCheckHasDirectionality(context));
+    renderObject
+      ..value = value
+      ..divisions = divisions
+      ..activeColor = activeColor
+      ..thumbColor = CupertinoDynamicColor.resolve(thumbColor, context)
+      ..trackColor = CupertinoDynamicColor.resolve(CupertinoColors.systemFill, context)
+      ..onChanged = onChanged
+      ..onChangeStart = onChangeStart
+      ..onChangeEnd = onChangeEnd
+      ..textDirection = Directionality.of(context);
+    // Ticker provider cannot change since there's a 1:1 relationship between
+    // the _SliderRenderObjectWidget object and the _SliderState object.
+  }
+}
+
+const double _kPadding = 8.0;
+const double _kSliderHeight = 2.0 * (CupertinoThumbPainter.radius + _kPadding);
+const double _kSliderWidth = 176.0; // Matches Material Design slider.
+const Duration _kDiscreteTransitionDuration = Duration(milliseconds: 500);
+
+const double _kAdjustmentUnit = 0.1; // Matches iOS implementation of material slider.
+
+class _RenderCupertinoSlider extends RenderConstrainedBox {
+  _RenderCupertinoSlider({
+    required double value,
+    int? divisions,
+    required Color activeColor,
+    required Color thumbColor,
+    required Color trackColor,
+    ValueChanged<double>? onChanged,
+    this.onChangeStart,
+    this.onChangeEnd,
+    required TickerProvider vsync,
+    required TextDirection textDirection,
+  }) : assert(value != null && value >= 0.0 && value <= 1.0),
+       assert(textDirection != null),
+       _value = value,
+       _divisions = divisions,
+       _activeColor = activeColor,
+       _thumbColor = thumbColor,
+       _trackColor = trackColor,
+       _onChanged = onChanged,
+       _textDirection = textDirection,
+       super(additionalConstraints: const BoxConstraints.tightFor(width: _kSliderWidth, height: _kSliderHeight)) {
+    _drag = HorizontalDragGestureRecognizer()
+      ..onStart = _handleDragStart
+      ..onUpdate = _handleDragUpdate
+      ..onEnd = _handleDragEnd;
+    _position = AnimationController(
+      value: value,
+      duration: _kDiscreteTransitionDuration,
+      vsync: vsync,
+    )..addListener(markNeedsPaint);
+  }
+
+  double get value => _value;
+  double _value;
+  set value(double newValue) {
+    assert(newValue != null && newValue >= 0.0 && newValue <= 1.0);
+    if (newValue == _value)
+      return;
+    _value = newValue;
+    if (divisions != null)
+      _position.animateTo(newValue, curve: Curves.fastOutSlowIn);
+    else
+      _position.value = newValue;
+    markNeedsSemanticsUpdate();
+  }
+
+  int? get divisions => _divisions;
+  int? _divisions;
+  set divisions(int? value) {
+    if (value == _divisions)
+      return;
+    _divisions = value;
+    markNeedsPaint();
+  }
+
+  Color get activeColor => _activeColor;
+  Color _activeColor;
+  set activeColor(Color value) {
+    if (value == _activeColor)
+      return;
+    _activeColor = value;
+    markNeedsPaint();
+  }
+
+  Color get thumbColor => _thumbColor;
+  Color _thumbColor;
+  set thumbColor(Color value) {
+    if (value == _thumbColor)
+      return;
+    _thumbColor = value;
+    markNeedsPaint();
+  }
+
+  Color get trackColor => _trackColor;
+  Color _trackColor;
+  set trackColor(Color value) {
+    if (value == _trackColor)
+      return;
+    _trackColor = value;
+    markNeedsPaint();
+  }
+
+  ValueChanged<double>? get onChanged => _onChanged;
+  ValueChanged<double>? _onChanged;
+  set onChanged(ValueChanged<double>? value) {
+    if (value == _onChanged)
+      return;
+    final bool wasInteractive = isInteractive;
+    _onChanged = value;
+    if (wasInteractive != isInteractive)
+      markNeedsSemanticsUpdate();
+  }
+
+  ValueChanged<double>? onChangeStart;
+  ValueChanged<double>? onChangeEnd;
+
+  TextDirection get textDirection => _textDirection;
+  TextDirection _textDirection;
+  set textDirection(TextDirection value) {
+    assert(value != null);
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    markNeedsPaint();
+  }
+
+  late AnimationController _position;
+
+  late HorizontalDragGestureRecognizer _drag;
+  double _currentDragValue = 0.0;
+
+  double get _discretizedCurrentDragValue {
+    double dragValue = _currentDragValue.clamp(0.0, 1.0);
+    if (divisions != null)
+      dragValue = (dragValue * divisions!).round() / divisions!;
+    return dragValue;
+  }
+
+  double get _trackLeft => _kPadding;
+  double get _trackRight => size.width - _kPadding;
+  double get _thumbCenter {
+    final double visualPosition;
+    switch (textDirection) {
+      case TextDirection.rtl:
+        visualPosition = 1.0 - _value;
+        break;
+      case TextDirection.ltr:
+        visualPosition = _value;
+        break;
+    }
+    return lerpDouble(_trackLeft + CupertinoThumbPainter.radius, _trackRight - CupertinoThumbPainter.radius, visualPosition)!;
+  }
+
+  bool get isInteractive => onChanged != null;
+
+  void _handleDragStart(DragStartDetails details) => _startInteraction(details.globalPosition);
+
+  void _handleDragUpdate(DragUpdateDetails details) {
+    if (isInteractive) {
+      final double extent = math.max(_kPadding, size.width - 2.0 * (_kPadding + CupertinoThumbPainter.radius));
+      final double valueDelta = details.primaryDelta! / extent;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          _currentDragValue -= valueDelta;
+          break;
+        case TextDirection.ltr:
+          _currentDragValue += valueDelta;
+          break;
+      }
+      onChanged!(_discretizedCurrentDragValue);
+    }
+  }
+
+  void _handleDragEnd(DragEndDetails details) => _endInteraction();
+
+  void _startInteraction(Offset globalPosition) {
+    if (isInteractive) {
+      if (onChangeStart != null) {
+        onChangeStart!(_discretizedCurrentDragValue);
+      }
+      _currentDragValue = _value;
+      onChanged!(_discretizedCurrentDragValue);
+    }
+  }
+
+  void _endInteraction() {
+    if (onChangeEnd != null) {
+      onChangeEnd!(_discretizedCurrentDragValue);
+    }
+    _currentDragValue = 0.0;
+  }
+
+  @override
+  bool hitTestSelf(Offset position) {
+    return (position.dx - _thumbCenter).abs() < CupertinoThumbPainter.radius + _kPadding;
+  }
+
+  @override
+  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
+    assert(debugHandleEvent(event, entry));
+    if (event is PointerDownEvent && isInteractive)
+      _drag.addPointer(event);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final double visualPosition;
+    final Color leftColor;
+    final Color rightColor;
+    switch (textDirection) {
+      case TextDirection.rtl:
+        visualPosition = 1.0 - _position.value;
+        leftColor = _activeColor;
+        rightColor = trackColor;
+        break;
+      case TextDirection.ltr:
+        visualPosition = _position.value;
+        leftColor = trackColor;
+        rightColor = _activeColor;
+        break;
+    }
+
+    final double trackCenter = offset.dy + size.height / 2.0;
+    final double trackLeft = offset.dx + _trackLeft;
+    final double trackTop = trackCenter - 1.0;
+    final double trackBottom = trackCenter + 1.0;
+    final double trackRight = offset.dx + _trackRight;
+    final double trackActive = offset.dx + _thumbCenter;
+
+    final Canvas canvas = context.canvas;
+
+    if (visualPosition > 0.0) {
+      final Paint paint = Paint()..color = rightColor;
+      canvas.drawRRect(RRect.fromLTRBXY(trackLeft, trackTop, trackActive, trackBottom, 1.0, 1.0), paint);
+    }
+
+    if (visualPosition < 1.0) {
+      final Paint paint = Paint()..color = leftColor;
+      canvas.drawRRect(RRect.fromLTRBXY(trackActive, trackTop, trackRight, trackBottom, 1.0, 1.0), paint);
+    }
+
+    final Offset thumbCenter = Offset(trackActive, trackCenter);
+    CupertinoThumbPainter(color: thumbColor).paint(canvas, Rect.fromCircle(center: thumbCenter, radius: CupertinoThumbPainter.radius));
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+
+    config.isSemanticBoundary = isInteractive;
+    config.isSlider = true;
+    if (isInteractive) {
+      config.textDirection = textDirection;
+      config.onIncrease = _increaseAction;
+      config.onDecrease = _decreaseAction;
+      config.value = '${(value * 100).round()}%';
+      config.increasedValue = '${((value + _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%';
+      config.decreasedValue = '${((value - _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%';
+    }
+  }
+
+  double get _semanticActionUnit => divisions != null ? 1.0 / divisions! : _kAdjustmentUnit;
+
+  void _increaseAction() {
+    if (isInteractive)
+      onChanged!((value + _semanticActionUnit).clamp(0.0, 1.0));
+  }
+
+  void _decreaseAction() {
+    if (isInteractive)
+      onChanged!((value - _semanticActionUnit).clamp(0.0, 1.0));
+  }
+}
diff --git a/lib/src/cupertino/sliding_segmented_control.dart b/lib/src/cupertino/sliding_segmented_control.dart
new file mode 100644
index 0000000..f8d2b38
--- /dev/null
+++ b/lib/src/cupertino/sliding_segmented_control.dart
@@ -0,0 +1,1056 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/physics.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+
+// Extracted from https://developer.apple.com/design/resources/.
+
+// Minimum padding from edges of the segmented control to edges of
+// encompassing widget.
+const EdgeInsetsGeometry _kHorizontalItemPadding = EdgeInsets.symmetric(vertical: 2, horizontal: 3);
+
+// The corner radius of the thumb.
+const Radius _kThumbRadius = Radius.circular(6.93);
+// The amount of space by which to expand the thumb from the size of the currently
+// selected child.
+const EdgeInsets _kThumbInsets = EdgeInsets.symmetric(horizontal: 1);
+
+// Minimum height of the segmented control.
+const double _kMinSegmentedControlHeight = 28.0;
+
+const Color _kSeparatorColor = Color(0x4D8E8E93);
+
+const CupertinoDynamicColor _kThumbColor = CupertinoDynamicColor.withBrightness(
+  color: Color(0xFFFFFFFF),
+  darkColor: Color(0xFF636366),
+);
+
+// The amount of space by which to inset each separator.
+const EdgeInsets _kSeparatorInset = EdgeInsets.symmetric(vertical: 6);
+const double _kSeparatorWidth = 1;
+const Radius _kSeparatorRadius = Radius.circular(_kSeparatorWidth/2);
+
+// The minimum scale factor of the thumb, when being pressed on for a sufficient
+// amount of time.
+const double _kMinThumbScale = 0.95;
+
+// The minimum horizontal distance between the edges of the separator and the
+// closest child.
+const double _kSegmentMinPadding = 9.25;
+
+// The threshold value used in hasDraggedTooFar, for checking against the square
+// L2 distance from the location of the current drag pointer, to the closest
+// vertex of the CupertinoSlidingSegmentedControl's Rect.
+//
+// Both the mechanism and the value are speculated.
+const double _kTouchYDistanceThreshold = 50.0 * 50.0;
+
+// The corner radius of the segmented control.
+//
+// Inspected from iOS 13.2 simulator.
+const double _kCornerRadius = 8;
+
+// The spring animation used when the thumb changes its rect.
+final SpringSimulation _kThumbSpringAnimationSimulation = SpringSimulation(
+  const SpringDescription(mass: 1, stiffness: 503.551, damping: 44.8799),
+  0,
+  1,
+  0, // Every time a new spring animation starts the previous animation stops.
+);
+
+const Duration _kSpringAnimationDuration = Duration(milliseconds: 412);
+
+const Duration _kOpacityAnimationDuration = Duration(milliseconds: 470);
+
+const Duration _kHighlightAnimationDuration = Duration(milliseconds: 200);
+
+class _FontWeightTween extends Tween<FontWeight> {
+  _FontWeightTween({ required FontWeight begin, required FontWeight end }) : super(begin: begin, end: end);
+
+  @override
+  FontWeight lerp(double t) => FontWeight.lerp(begin, end, t)!;
+}
+
+/// An iOS 13 style segmented control.
+///
+/// Displays the widgets provided in the [Map] of [children] in a horizontal list.
+/// Used to select between a number of mutually exclusive options. When one option
+/// in the segmented control is selected, the other options in the segmented
+/// control cease to be selected.
+///
+/// A segmented control can feature any [Widget] as one of the values in its
+/// [Map] of [children]. The type T is the type of the [Map] keys used to identify
+/// each widget and determine which widget is selected. As required by the [Map]
+/// class, keys must be of consistent types and must be comparable. The [children]
+/// argument must be an ordered [Map] such as a [LinkedHashMap], the ordering of
+/// the keys will determine the order of the widgets in the segmented control.
+///
+/// When the state of the segmented control changes, the widget calls the
+/// [onValueChanged] callback. The map key associated with the newly selected
+/// widget is returned in the [onValueChanged] callback. Typically, widgets
+/// that use a segmented control will listen for the [onValueChanged] callback
+/// and rebuild the segmented control with a new [groupValue] to update which
+/// option is currently selected.
+///
+/// The [children] will be displayed in the order of the keys in the [Map].
+/// The height of the segmented control is determined by the height of the
+/// tallest widget provided as a value in the [Map] of [children].
+/// The width of each child in the segmented control will be equal to the width
+/// of widest child, unless the combined width of the children is wider than
+/// the available horizontal space. In this case, the available horizontal space
+/// is divided by the number of provided [children] to determine the width of
+/// each widget. The selection area for each of the widgets in the [Map] of
+/// [children] will then be expanded to fill the calculated space, so each
+/// widget will appear to have the same dimensions.
+///
+/// A segmented control may optionally be created with custom colors. The
+/// [thumbColor], [backgroundColor] arguments can be used to override the segmented
+/// control's colors from its defaults.
+///
+/// See also:
+///
+///  * [CupertinoSlidingSegmentedControl], a segmented control widget in the
+///    style introduced in iOS 13.
+///  * <https://developer.apple.com/design/human-interface-guidelines/ios/controls/segmented-controls/>
+class CupertinoSlidingSegmentedControl<T> extends StatefulWidget {
+  /// Creates an iOS-style segmented control bar.
+  ///
+  /// The [children] and [onValueChanged] arguments must not be null. The
+  /// [children] argument must be an ordered [Map] such as a [LinkedHashMap].
+  /// Further, the length of the [children] list must be greater than one.
+  ///
+  /// Each widget value in the map of [children] must have an associated key
+  /// that uniquely identifies this widget. This key is what will be returned
+  /// in the [onValueChanged] callback when a new value from the [children] map
+  /// is selected.
+  ///
+  /// The [groupValue] is the currently selected value for the segmented control.
+  /// If no [groupValue] is provided, or the [groupValue] is null, no widget will
+  /// appear as selected. The [groupValue] must be either null or one of the keys
+  /// in the [children] map.
+  CupertinoSlidingSegmentedControl({
+    Key? key,
+    required this.children,
+    required this.onValueChanged,
+    this.groupValue,
+    this.thumbColor = _kThumbColor,
+    this.padding = _kHorizontalItemPadding,
+    this.backgroundColor = CupertinoColors.tertiarySystemFill,
+  }) : assert(children != null),
+       assert(children.length >= 2),
+       assert(padding != null),
+       assert(onValueChanged != null),
+       assert(
+         groupValue == null || children.keys.contains(groupValue),
+         'The groupValue must be either null or one of the keys in the children map.',
+       ),
+       super(key: key);
+
+  /// The identifying keys and corresponding widget values in the
+  /// segmented control.
+  ///
+  /// The map must have more than one entry.
+  /// This attribute must be an ordered [Map] such as a [LinkedHashMap].
+  final Map<T, Widget> children;
+
+  /// The identifier of the widget that is currently selected.
+  ///
+  /// This must be one of the keys in the [Map] of [children].
+  /// If this attribute is null, no widget will be initially selected.
+  final T? groupValue;
+
+  /// The callback that is called when a new option is tapped.
+  ///
+  /// This attribute must not be null.
+  ///
+  /// The segmented control passes the newly selected widget's associated key
+  /// to the callback but does not actually change state until the parent
+  /// widget rebuilds the segmented control with the new [groupValue].
+  ///
+  /// The callback provided to [onValueChanged] should update the state of
+  /// the parent [StatefulWidget] using the [State.setState] method, so that
+  /// the parent gets rebuilt; for example:
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// class SegmentedControlExample extends StatefulWidget {
+  ///   @override
+  ///   State createState() => SegmentedControlExampleState();
+  /// }
+  ///
+  /// class SegmentedControlExampleState extends State<SegmentedControlExample> {
+  ///   final Map<int, Widget> children = const {
+  ///     0: Text('Child 1'),
+  ///     1: Text('Child 2'),
+  ///   };
+  ///
+  ///   int currentValue;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Container(
+  ///       child: CupertinoSlidingSegmentedControl<int>(
+  ///         children: children,
+  ///         onValueChanged: (int newValue) {
+  ///           setState(() {
+  ///             currentValue = newValue;
+  ///           });
+  ///         },
+  ///         groupValue: currentValue,
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  final ValueChanged<T?> onValueChanged;
+
+  /// The color used to paint the rounded rect behind the [children] and the separators.
+  ///
+  /// The default value is [CupertinoColors.tertiarySystemFill]. The background
+  /// will not be painted if null is specified.
+  final Color backgroundColor;
+
+  /// The color used to paint the interior of the thumb that appears behind the
+  /// currently selected item.
+  ///
+  /// The default value is a [CupertinoDynamicColor] that appears white in light
+  /// mode and becomes a gray color in dark mode.
+  final Color thumbColor;
+
+  /// The amount of space by which to inset the [children].
+  ///
+  /// Must not be null. Defaults to EdgeInsets.symmetric(vertical: 2, horizontal: 3).
+  final EdgeInsetsGeometry padding;
+
+  @override
+  _SegmentedControlState<T> createState() => _SegmentedControlState<T>();
+}
+
+class _SegmentedControlState<T> extends State<CupertinoSlidingSegmentedControl<T>>
+    with TickerProviderStateMixin<CupertinoSlidingSegmentedControl<T>> {
+
+  final Map<T, AnimationController> _highlightControllers = <T, AnimationController>{};
+  final Tween<FontWeight> _highlightTween = _FontWeightTween(begin: FontWeight.normal, end: FontWeight.w500);
+
+  final Map<T, AnimationController> _pressControllers = <T, AnimationController>{};
+  final Tween<double> _pressTween = Tween<double>(begin: 1, end: 0.2);
+
+  late List<T> keys;
+
+  late AnimationController thumbController;
+  late AnimationController separatorOpacityController;
+  late AnimationController thumbScaleController;
+
+  final TapGestureRecognizer tap = TapGestureRecognizer();
+  final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer();
+  final LongPressGestureRecognizer longPress = LongPressGestureRecognizer();
+
+  AnimationController _createHighlightAnimationController({ bool isCompleted = false }) {
+    return AnimationController(
+      duration: _kHighlightAnimationDuration,
+      value: isCompleted ? 1 : 0,
+      vsync: this,
+    );
+  }
+
+  AnimationController _createFadeoutAnimationController() {
+    return AnimationController(
+      duration: _kOpacityAnimationDuration,
+      vsync: this,
+    );
+  }
+
+  @override
+  void initState() {
+    super.initState();
+
+    final GestureArenaTeam team = GestureArenaTeam();
+    // If the long press or horizontal drag recognizer gets accepted, we know for
+    // sure the gesture is meant for the segmented control. Hand everything to
+    // the drag gesture recognizer.
+    longPress.team = team;
+    drag.team = team;
+    team.captain = drag;
+
+    _highlighted = widget.groupValue;
+
+    thumbController = AnimationController(
+      duration: _kSpringAnimationDuration,
+      value: 0,
+      vsync: this,
+    );
+
+    thumbScaleController = AnimationController(
+      duration: _kSpringAnimationDuration,
+      value: 1,
+      vsync: this,
+    );
+
+    separatorOpacityController = AnimationController(
+      duration: _kSpringAnimationDuration,
+      value: 0,
+      vsync: this,
+    );
+
+    for (final T currentKey in widget.children.keys) {
+      _highlightControllers[currentKey] = _createHighlightAnimationController(
+        isCompleted: currentKey == widget.groupValue,  // Highlight the current selection.
+      );
+      _pressControllers[currentKey] = _createFadeoutAnimationController();
+    }
+  }
+
+  @override
+  void didUpdateWidget(CupertinoSlidingSegmentedControl<T> oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    // Update animation controllers.
+    for (final T oldKey in oldWidget.children.keys) {
+      if (!widget.children.containsKey(oldKey)) {
+        _highlightControllers[oldKey]!.dispose();
+        _pressControllers[oldKey]!.dispose();
+
+        _highlightControllers.remove(oldKey);
+        _pressControllers.remove(oldKey);
+      }
+    }
+
+    for (final T newKey in widget.children.keys) {
+      if (!_highlightControllers.keys.contains(newKey)) {
+        _highlightControllers[newKey] = _createHighlightAnimationController();
+        _pressControllers[newKey] = _createFadeoutAnimationController();
+      }
+    }
+
+    highlighted = widget.groupValue;
+  }
+
+  @override
+  void dispose() {
+    for (final AnimationController animationController in _highlightControllers.values) {
+      animationController.dispose();
+    }
+
+    for (final AnimationController animationController in _pressControllers.values) {
+      animationController.dispose();
+    }
+
+    thumbScaleController.dispose();
+    thumbController.dispose();
+    separatorOpacityController.dispose();
+
+    drag.dispose();
+    tap.dispose();
+    longPress.dispose();
+
+    super.dispose();
+  }
+
+  // Play highlight animation for the child located at _highlightControllers[at].
+  void _animateHighlightController({ T? at, required bool forward }) {
+    if (at == null)
+      return;
+    final AnimationController? controller = _highlightControllers[at];
+    assert(!forward || controller != null);
+    controller?.animateTo(forward ? 1 : 0, duration: _kHighlightAnimationDuration, curve: Curves.ease);
+  }
+
+  T? _highlighted;
+  set highlighted(T? newValue) {
+    if (_highlighted == newValue)
+      return;
+    _animateHighlightController(at: newValue, forward: true);
+    _animateHighlightController(at: _highlighted, forward: false);
+    _highlighted = newValue;
+  }
+
+  T? _pressed;
+  set pressed(T? newValue) {
+    if (_pressed == newValue)
+      return;
+
+    if (_pressed != null) {
+      _pressControllers[_pressed]?.animateTo(0, duration: _kOpacityAnimationDuration, curve: Curves.ease);
+    }
+    if (newValue != _highlighted && newValue != null) {
+      _pressControllers[newValue]!.animateTo(1, duration: _kOpacityAnimationDuration, curve: Curves.ease);
+    }
+    _pressed = newValue;
+  }
+
+  void didChangeSelectedViaGesture() {
+    widget.onValueChanged(_highlighted);
+  }
+
+  T? indexToKey(int? index) => index == null ? null : keys[index];
+
+  @override
+  Widget build(BuildContext context) {
+    debugCheckHasDirectionality(context);
+
+    switch (Directionality.of(context)) {
+      case TextDirection.ltr:
+        keys = widget.children.keys.toList(growable: false);
+        break;
+      case TextDirection.rtl:
+        keys = widget.children.keys.toList().reversed.toList(growable: false);
+        break;
+    }
+
+    return AnimatedBuilder(
+      animation: Listenable.merge(<Listenable>[
+        ..._highlightControllers.values,
+        ..._pressControllers.values,
+      ]),
+      builder: (BuildContext context, Widget? child) {
+        final List<Widget> children = <Widget>[];
+        for (final T currentKey in keys) {
+          final TextStyle textStyle = DefaultTextStyle.of(context).style.copyWith(
+            fontWeight: _highlightTween.evaluate(_highlightControllers[currentKey]!),
+          );
+
+          final Widget child = DefaultTextStyle(
+            style: textStyle,
+            child: Semantics(
+              button: true,
+              onTap: () { widget.onValueChanged(currentKey); },
+              inMutuallyExclusiveGroup: true,
+              selected: widget.groupValue == currentKey,
+              child: Opacity(
+                opacity: _pressTween.evaluate(_pressControllers[currentKey]!),
+                // Expand the hitTest area to be as large as the Opacity widget.
+                child: MetaData(
+                  behavior: HitTestBehavior.opaque,
+                  child: Center(child: widget.children[currentKey]),
+                ),
+              ),
+            ),
+          );
+
+          children.add(child);
+        }
+
+        final int? selectedIndex = widget.groupValue == null ? null : keys.indexOf(widget.groupValue as T);
+
+        final Widget box = _SegmentedControlRenderWidget<T>(
+          children: children,
+          selectedIndex: selectedIndex,
+          thumbColor: CupertinoDynamicColor.resolve(widget.thumbColor, context),
+          state: this,
+        );
+
+        return UnconstrainedBox(
+          constrainedAxis: Axis.horizontal,
+          child: Container(
+            padding: widget.padding.resolve(Directionality.of(context)),
+            decoration: BoxDecoration(
+              borderRadius: const BorderRadius.all(Radius.circular(_kCornerRadius)),
+              color: CupertinoDynamicColor.resolve(widget.backgroundColor, context),
+            ),
+            child: box,
+          ),
+        );
+      },
+    );
+  }
+}
+
+class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget {
+  _SegmentedControlRenderWidget({
+    Key? key,
+    List<Widget> children = const <Widget>[],
+    required this.selectedIndex,
+    required this.thumbColor,
+    required this.state,
+  }) : super(key: key, children: children);
+
+  final int? selectedIndex;
+  final Color? thumbColor;
+  final _SegmentedControlState<T> state;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _RenderSegmentedControl<T>(
+      selectedIndex: selectedIndex,
+      thumbColor: CupertinoDynamicColor.maybeResolve(thumbColor, context),
+      state: state,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderSegmentedControl<T> renderObject) {
+    renderObject
+      ..thumbColor = CupertinoDynamicColor.maybeResolve(thumbColor, context)
+      ..guardedSetHighlightedIndex(selectedIndex);
+  }
+}
+
+class _ChildAnimationManifest {
+  _ChildAnimationManifest({
+    this.opacity = 1,
+    required this.separatorOpacity,
+  }) : assert(separatorOpacity != null),
+       assert(opacity != null),
+       separatorTween = Tween<double>(begin: separatorOpacity, end: separatorOpacity),
+       opacityTween = Tween<double>(begin: opacity, end: opacity);
+
+  double opacity;
+  Tween<double> opacityTween;
+  double separatorOpacity;
+  Tween<double> separatorTween;
+}
+
+class _SegmentedControlContainerBoxParentData extends ContainerBoxParentData<RenderBox> { }
+
+// The behavior of a UISegmentedControl as observed on iOS 13.1:
+//
+// 1. Tap up inside events will set the current selected index to the index of the
+//    segment at the tap up location instantaneously (there might be animation but
+//    the index change seems to happen before animation finishes), unless the tap
+//    down event from the same touch event didn't happen within the segmented
+//    control, in which case the touch event will be ignored entirely (will be
+//    referring to these touch events as invalid touch events below).
+//
+// 2. A valid tap up event will also trigger the sliding CASpringAnimation (even
+//    when it lands on the current segment), starting from the current `frame`
+//    of the thumb. The previous sliding animation, if still playing, will be
+//    removed and its velocity reset to 0. The sliding animation has a fixed
+//    duration, regardless of the distance or transform.
+//
+// 3. When the sliding animation plays two other animations take place. In one animation
+//    the content of the current segment gradually becomes "highlighted", turning the
+//    font weight to semibold (CABasicAnimation, timingFunction = default, duration = 0.2).
+//    The other is the separator fadein/fadeout animation.
+//
+// 4. A tap down event on the segment pointed to by the current selected
+//    index will trigger a CABasicAnimation that shrinks the thumb to 95% of its
+//    original size, even if the sliding animation is still playing. The
+///   corresponding tap up event inverts the process (eyeballed).
+//
+// 5. A tap down event on other segments will trigger a CABasicAnimation
+//    (timingFunction = default, duration = 0.47.) that fades out the content,
+//    eventually reducing the alpha of that segment to 20% unless interrupted by
+//    a tap up event or the pointer moves out of the region (either outside of the
+//    segmented control's vicinity or to a different segment). The reverse animation
+//    has the same duration and timing function.
+class _RenderSegmentedControl<T> extends RenderBox
+    with ContainerRenderObjectMixin<RenderBox, ContainerBoxParentData<RenderBox>>,
+        RenderBoxContainerDefaultsMixin<RenderBox, ContainerBoxParentData<RenderBox>> {
+  _RenderSegmentedControl({
+    required int? selectedIndex,
+    required Color? thumbColor,
+    required this.state,
+  }) : _highlightedIndex = selectedIndex,
+       _thumbColor = thumbColor,
+       assert(state != null) {
+         state.drag
+          ..onDown = _onDown
+          ..onUpdate = _onUpdate
+          ..onEnd = _onEnd
+          ..onCancel = _onCancel;
+
+         state.tap.onTapUp = _onTapUp;
+         // Empty callback to enable the long press recognizer.
+         state.longPress.onLongPress = () { };
+       }
+
+  final _SegmentedControlState<T> state;
+
+  Map<RenderBox, _ChildAnimationManifest>? _childAnimations = <RenderBox, _ChildAnimationManifest>{};
+
+  // The current **Unscaled** Thumb Rect.
+  Rect? currentThumbRect;
+
+  Tween<Rect?>? _currentThumbTween;
+
+  Tween<double> _thumbScaleTween = Tween<double>(begin: _kMinThumbScale, end: 1);
+  double currentThumbScale = 1;
+
+  // The current position of the active drag pointer.
+  Offset? _localDragOffset;
+  // Whether the current drag gesture started on a selected segment.
+  bool? _startedOnSelectedSegment;
+
+  @override
+  void insert(RenderBox child, { RenderBox? after }) {
+    super.insert(child, after: after);
+    if (_childAnimations == null)
+      return;
+
+    assert(_childAnimations![child] == null);
+    _childAnimations![child] = _ChildAnimationManifest(separatorOpacity: 1);
+  }
+
+  @override
+  void remove(RenderBox child) {
+    super.remove(child);
+    _childAnimations?.remove(child);
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    state.thumbController.addListener(markNeedsPaint);
+    state.thumbScaleController.addListener(markNeedsPaint);
+    state.separatorOpacityController.addListener(markNeedsPaint);
+  }
+
+  @override
+  void detach() {
+    state.thumbController.removeListener(markNeedsPaint);
+    state.thumbScaleController.removeListener(markNeedsPaint);
+    state.separatorOpacityController.removeListener(markNeedsPaint);
+    super.detach();
+  }
+
+  // Indicates whether selectedIndex has changed and animations need to be updated.
+  // when true some animation tweens will be updated in paint phase.
+  bool _needsThumbAnimationUpdate = false;
+
+  int? get highlightedIndex => _highlightedIndex;
+  int? _highlightedIndex;
+  set highlightedIndex(int? value) {
+    if (_highlightedIndex == value) {
+      return;
+    }
+
+    _needsThumbAnimationUpdate = true;
+    _highlightedIndex = value;
+
+    state.thumbController.animateWith(_kThumbSpringAnimationSimulation);
+
+    state.separatorOpacityController.reset();
+    state.separatorOpacityController.animateTo(
+      1,
+      duration: _kSpringAnimationDuration,
+      curve: Curves.ease,
+    );
+
+    state.highlighted = state.indexToKey(value);
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  void guardedSetHighlightedIndex(int? value) {
+    // Ignore set highlightedIndex when the user is dragging the thumb around.
+    if (_startedOnSelectedSegment == true)
+      return;
+    highlightedIndex = value;
+  }
+
+  int? get pressedIndex => _pressedIndex;
+  int? _pressedIndex;
+  set pressedIndex(int? value) {
+    if (_pressedIndex == value) {
+      return;
+    }
+
+    assert(value == null || (value >= 0 && value < childCount));
+
+    _pressedIndex = value;
+    state.pressed = state.indexToKey(value);
+  }
+
+  Color? get thumbColor => _thumbColor;
+  Color? _thumbColor;
+  set thumbColor(Color? value) {
+    if (_thumbColor == value) {
+      return;
+    }
+    _thumbColor = value;
+    markNeedsPaint();
+  }
+
+  double get totalSeparatorWidth => (_kSeparatorInset.horizontal + _kSeparatorWidth) * (childCount - 1);
+
+  @override
+  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
+    assert(debugHandleEvent(event, entry));
+    if (event is PointerDownEvent) {
+      state.tap.addPointer(event);
+      state.longPress.addPointer(event);
+      state.drag.addPointer(event);
+    }
+  }
+
+  int? indexFromLocation(Offset location) {
+    return childCount == 0
+      ? null
+      // This assumes all children have the same width.
+      : (location.dx / (size.width / childCount))
+          .floor()
+          .clamp(0, childCount - 1);
+  }
+
+  void _onTapUp(TapUpDetails details) {
+    highlightedIndex = indexFromLocation(details.localPosition);
+    state.didChangeSelectedViaGesture();
+  }
+
+  void _onDown(DragDownDetails details) {
+    assert(size.contains(details.localPosition));
+    _localDragOffset = details.localPosition;
+    final int? index = indexFromLocation(_localDragOffset!);
+    _startedOnSelectedSegment = index == highlightedIndex;
+    pressedIndex = index;
+
+    if (_startedOnSelectedSegment!) {
+      _playThumbScaleAnimation(isExpanding: false);
+    }
+  }
+
+  void _onUpdate(DragUpdateDetails details) {
+    _localDragOffset = details.localPosition;
+    final int? newIndex = indexFromLocation(_localDragOffset!);
+
+    if (_startedOnSelectedSegment!) {
+      highlightedIndex = newIndex;
+      pressedIndex = newIndex;
+    } else {
+      pressedIndex = _hasDraggedTooFar(details) ? null : newIndex;
+    }
+  }
+
+  void _onEnd(DragEndDetails details) {
+    if (_startedOnSelectedSegment!) {
+      _playThumbScaleAnimation(isExpanding: true);
+      state.didChangeSelectedViaGesture();
+    }
+
+    if (pressedIndex != null) {
+      highlightedIndex = pressedIndex;
+      state.didChangeSelectedViaGesture();
+    }
+    pressedIndex = null;
+    _localDragOffset = null;
+    _startedOnSelectedSegment = null;
+  }
+
+  void _onCancel() {
+    if (_startedOnSelectedSegment!) {
+      _playThumbScaleAnimation(isExpanding: true);
+    }
+
+    _localDragOffset = null;
+    pressedIndex = null;
+    _startedOnSelectedSegment = null;
+  }
+
+  void _playThumbScaleAnimation({ required bool isExpanding }) {
+    assert(isExpanding != null);
+    _thumbScaleTween = Tween<double>(begin: currentThumbScale, end: isExpanding ? 1 : _kMinThumbScale);
+    state.thumbScaleController.animateWith(_kThumbSpringAnimationSimulation);
+  }
+
+  bool _hasDraggedTooFar(DragUpdateDetails details) {
+    final Offset offCenter = details.localPosition - Offset(size.width/2, size.height/2);
+    return math.pow(math.max(0, offCenter.dx.abs() - size.width/2), 2) + math.pow(math.max(0, offCenter.dy.abs() - size.height/2), 2) > _kTouchYDistanceThreshold;
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    RenderBox? child = firstChild;
+    double maxMinChildWidth = 0;
+    while (child != null) {
+      final _SegmentedControlContainerBoxParentData childParentData =
+        child.parentData! as _SegmentedControlContainerBoxParentData;
+      final double childWidth = child.getMinIntrinsicWidth(height);
+      maxMinChildWidth = math.max(maxMinChildWidth, childWidth);
+      child = childParentData.nextSibling;
+    }
+    return (maxMinChildWidth + 2 * _kSegmentMinPadding) * childCount + totalSeparatorWidth;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    RenderBox? child = firstChild;
+    double maxMaxChildWidth = 0;
+    while (child != null) {
+      final _SegmentedControlContainerBoxParentData childParentData =
+        child.parentData! as _SegmentedControlContainerBoxParentData;
+      final double childWidth = child.getMaxIntrinsicWidth(height);
+      maxMaxChildWidth = math.max(maxMaxChildWidth, childWidth);
+      child = childParentData.nextSibling;
+    }
+    return (maxMaxChildWidth + 2 * _kSegmentMinPadding) * childCount + totalSeparatorWidth;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    RenderBox? child = firstChild;
+    double maxMinChildHeight = 0;
+    while (child != null) {
+      final _SegmentedControlContainerBoxParentData childParentData =
+        child.parentData! as _SegmentedControlContainerBoxParentData;
+      final double childHeight = child.getMinIntrinsicHeight(width);
+      maxMinChildHeight = math.max(maxMinChildHeight, childHeight);
+      child = childParentData.nextSibling;
+    }
+    return maxMinChildHeight;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    RenderBox? child = firstChild;
+    double maxMaxChildHeight = 0;
+    while (child != null) {
+      final _SegmentedControlContainerBoxParentData childParentData =
+        child.parentData! as _SegmentedControlContainerBoxParentData;
+      final double childHeight = child.getMaxIntrinsicHeight(width);
+      maxMaxChildHeight = math.max(maxMaxChildHeight, childHeight);
+      child = childParentData.nextSibling;
+    }
+    return maxMaxChildHeight;
+  }
+
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    return defaultComputeDistanceToHighestActualBaseline(baseline);
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! _SegmentedControlContainerBoxParentData) {
+      child.parentData = _SegmentedControlContainerBoxParentData();
+    }
+  }
+
+  Size _calculateChildSize(BoxConstraints constraints) {
+    double childWidth = (constraints.minWidth - totalSeparatorWidth) / childCount;
+    double maxHeight = _kMinSegmentedControlHeight;
+    RenderBox? child = firstChild;
+    while (child != null) {
+      childWidth = math.max(childWidth, child.getMaxIntrinsicWidth(double.infinity) + 2 * _kSegmentMinPadding);
+      child = childAfter(child);
+    }
+    childWidth = math.min(
+      childWidth,
+      (constraints.maxWidth - totalSeparatorWidth) / childCount,
+    );
+    child = firstChild;
+    while (child != null) {
+      final double boxHeight = child.getMaxIntrinsicHeight(childWidth);
+      maxHeight = math.max(maxHeight, boxHeight);
+      child = childAfter(child);
+    }
+    return Size(childWidth, maxHeight);
+  }
+
+  Size _computeOverallSizeFromChildSize(Size childSize) {
+    return constraints.constrain(Size(childSize.width * childCount + totalSeparatorWidth, childSize.height));
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    final Size childSize = _calculateChildSize(constraints);
+    return _computeOverallSizeFromChildSize(childSize);
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    final Size childSize = _calculateChildSize(constraints);
+
+    final BoxConstraints childConstraints = BoxConstraints.tightFor(
+      width: childSize.width,
+      height: childSize.height,
+    );
+
+    // Layout children.
+    RenderBox? child = firstChild;
+    while (child != null) {
+      child.layout(childConstraints, parentUsesSize: true);
+      child = childAfter(child);
+    }
+
+    double start = 0;
+    child = firstChild;
+
+    while (child != null) {
+      final _SegmentedControlContainerBoxParentData childParentData =
+        child.parentData! as _SegmentedControlContainerBoxParentData;
+      final Offset childOffset = Offset(start, 0);
+      childParentData.offset = childOffset;
+      start += child.size.width + _kSeparatorWidth + _kSeparatorInset.horizontal;
+      child = childAfter(child);
+    }
+
+    size = _computeOverallSizeFromChildSize(childSize);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final List<RenderBox> children = getChildrenAsList();
+
+    // Paint thumb if highlightedIndex is not null.
+    if (highlightedIndex != null) {
+      if (_childAnimations == null) {
+        _childAnimations = <RenderBox, _ChildAnimationManifest> { };
+        for (int i = 0; i < childCount - 1; i += 1) {
+          // The separator associated with the last child will not be painted (unless
+          // a new trailing segment is added), and its opacity will always be 1.
+          final bool shouldFadeOut = i == highlightedIndex || i == highlightedIndex! - 1;
+          final RenderBox child = children[i];
+          _childAnimations![child] = _ChildAnimationManifest(separatorOpacity: shouldFadeOut ? 0 : 1);
+        }
+      }
+
+      final RenderBox selectedChild = children[highlightedIndex!];
+
+      final _SegmentedControlContainerBoxParentData childParentData =
+        selectedChild.parentData! as _SegmentedControlContainerBoxParentData;
+      final Rect unscaledThumbTargetRect = _kThumbInsets.inflateRect(childParentData.offset & selectedChild.size);
+
+      // Update related Tweens before animation update phase.
+      if (_needsThumbAnimationUpdate) {
+        // Needs to ensure _currentThumbRect is valid.
+        _currentThumbTween = RectTween(begin: currentThumbRect ?? unscaledThumbTargetRect, end: unscaledThumbTargetRect);
+
+        for (int i = 0; i < childCount - 1; i += 1) {
+          // The separator associated with the last child will not be painted (unless
+          // a new segment is appended to the child list), and its opacity will always be 1.
+          final bool shouldFadeOut = i == highlightedIndex || i == highlightedIndex! - 1;
+          final RenderBox child = children[i];
+          final _ChildAnimationManifest manifest = _childAnimations![child]!;
+          assert(manifest != null);
+          manifest.separatorTween = Tween<double>(
+            begin: manifest.separatorOpacity,
+            end: shouldFadeOut ? 0 : 1,
+          );
+        }
+
+        _needsThumbAnimationUpdate = false;
+      } else if (_currentThumbTween != null && unscaledThumbTargetRect != _currentThumbTween!.begin) {
+        _currentThumbTween = RectTween(begin: _currentThumbTween!.begin, end: unscaledThumbTargetRect);
+      }
+
+      for (int index = 0; index < childCount - 1; index += 1) {
+        _paintSeparator(context, offset, children[index]);
+      }
+
+      currentThumbRect = _currentThumbTween?.evaluate(state.thumbController)
+                        ?? unscaledThumbTargetRect;
+
+      currentThumbScale = _thumbScaleTween.evaluate(state.thumbScaleController);
+
+      final Rect thumbRect = Rect.fromCenter(
+        center: currentThumbRect!.center,
+        width: currentThumbRect!.width * currentThumbScale,
+        height: currentThumbRect!.height * currentThumbScale,
+      );
+
+      _paintThumb(context, offset, thumbRect);
+    } else {
+      // Reset all animations when there's no thumb.
+      currentThumbRect = null;
+      _childAnimations = null;
+
+      for (int index = 0; index < childCount - 1; index += 1) {
+        _paintSeparator(context, offset, children[index]);
+      }
+    }
+
+    for (int index = 0; index < children.length; index++) {
+      _paintChild(context, offset, children[index], index);
+    }
+  }
+
+  // Paint the separator to the right of the given child.
+  void _paintSeparator(PaintingContext context, Offset offset, RenderBox child) {
+    assert(child != null);
+    final _SegmentedControlContainerBoxParentData childParentData =
+      child.parentData! as _SegmentedControlContainerBoxParentData;
+
+    final Paint paint = Paint();
+
+    final _ChildAnimationManifest? manifest = _childAnimations == null ? null : _childAnimations![child];
+    final double opacity = manifest?.separatorTween.evaluate(state.separatorOpacityController) ?? 1;
+    manifest?.separatorOpacity = opacity;
+    paint.color = _kSeparatorColor.withOpacity(_kSeparatorColor.opacity * opacity);
+
+    final Rect childRect = (childParentData.offset + offset) & child.size;
+    final Rect separatorRect = _kSeparatorInset.deflateRect(
+      childRect.topRight & Size(_kSeparatorInset.horizontal + _kSeparatorWidth, child.size.height),
+    );
+
+    context.canvas.drawRRect(
+      RRect.fromRectAndRadius(separatorRect, _kSeparatorRadius),
+      paint,
+    );
+  }
+
+  void _paintChild(PaintingContext context, Offset offset, RenderBox child, int childIndex) {
+    assert(child != null);
+    final _SegmentedControlContainerBoxParentData childParentData =
+      child.parentData! as _SegmentedControlContainerBoxParentData;
+    context.paintChild(child, childParentData.offset + offset);
+  }
+
+  void _paintThumb(PaintingContext context, Offset offset, Rect thumbRect) {
+    // Colors extracted from https://developer.apple.com/design/resources/.
+    const List<BoxShadow> thumbShadow = <BoxShadow> [
+      BoxShadow(
+        color: Color(0x1F000000),
+        offset: Offset(0, 3),
+        blurRadius: 8,
+      ),
+      BoxShadow(
+        color: Color(0x0A000000),
+        offset: Offset(0, 3),
+        blurRadius: 1,
+      ),
+    ];
+
+    final RRect thumbRRect = RRect.fromRectAndRadius(thumbRect.shift(offset), _kThumbRadius);
+
+    for (final BoxShadow shadow in thumbShadow) {
+      context.canvas.drawRRect(thumbRRect.shift(shadow.offset), shadow.toPaint());
+    }
+
+    context.canvas.drawRRect(
+      thumbRRect.inflate(0.5),
+      Paint()..color = const Color(0x0A000000),
+    );
+
+    context.canvas.drawRRect(
+      thumbRRect,
+      Paint()..color = thumbColor!,
+    );
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    assert(position != null);
+    RenderBox? child = lastChild;
+    while (child != null) {
+      final _SegmentedControlContainerBoxParentData childParentData =
+        child.parentData! as _SegmentedControlContainerBoxParentData;
+      if ((childParentData.offset & child.size).contains(position)) {
+        return result.addWithPaintOffset(
+          offset: childParentData.offset,
+          position: position,
+          hitTest: (BoxHitTestResult result, Offset localOffset) {
+            assert(localOffset == position - childParentData.offset);
+            return child!.hitTest(result, position: localOffset);
+          },
+        );
+      }
+      child = childParentData.previousSibling;
+    }
+    return false;
+  }
+}
diff --git a/lib/src/cupertino/switch.dart b/lib/src/cupertino/switch.dart
new file mode 100644
index 0000000..a9d990d
--- /dev/null
+++ b/lib/src/cupertino/switch.dart
@@ -0,0 +1,541 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+import 'package:flute/services.dart';
+
+import 'colors.dart';
+import 'thumb_painter.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// bool _lights;
+// void setState(VoidCallback fn) { }
+
+/// An iOS-style switch.
+///
+/// Used to toggle the on/off state of a single setting.
+///
+/// The switch itself does not maintain any state. Instead, when the state of
+/// the switch changes, the widget calls the [onChanged] callback. Most widgets
+/// that use a switch will listen for the [onChanged] callback and rebuild the
+/// switch with a new [value] to update the visual appearance of the switch.
+///
+/// {@tool snippet}
+///
+/// This sample shows how to use a [CupertinoSwitch] in a [ListTile]. The
+/// [MergeSemantics] is used to turn the entire [ListTile] into a single item
+/// for accessibility tools.
+///
+/// ```dart
+/// MergeSemantics(
+///   child: ListTile(
+///     title: Text('Lights'),
+///     trailing: CupertinoSwitch(
+///       value: _lights,
+///       onChanged: (bool value) { setState(() { _lights = value; }); },
+///     ),
+///     onTap: () { setState(() { _lights = !_lights; }); },
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Switch], the material design equivalent.
+///  * <https://developer.apple.com/ios/human-interface-guidelines/controls/switches/>
+class CupertinoSwitch extends StatefulWidget {
+  /// Creates an iOS-style switch.
+  ///
+  /// The [value] parameter must not be null.
+  /// The [dragStartBehavior] parameter defaults to [DragStartBehavior.start] and must not be null.
+  const CupertinoSwitch({
+    Key? key,
+    required this.value,
+    required this.onChanged,
+    this.activeColor,
+    this.trackColor,
+    this.dragStartBehavior = DragStartBehavior.start,
+  }) : assert(value != null),
+       assert(dragStartBehavior != null),
+       super(key: key);
+
+  /// Whether this switch is on or off.
+  ///
+  /// Must not be null.
+  final bool value;
+
+  /// Called when the user toggles with switch on or off.
+  ///
+  /// The switch passes the new value to the callback but does not actually
+  /// change state until the parent widget rebuilds the switch with the new
+  /// value.
+  ///
+  /// If null, the switch will be displayed as disabled, which has a reduced opacity.
+  ///
+  /// The callback provided to onChanged should update the state of the parent
+  /// [StatefulWidget] using the [State.setState] method, so that the parent
+  /// gets rebuilt; for example:
+  ///
+  /// ```dart
+  /// CupertinoSwitch(
+  ///   value: _giveVerse,
+  ///   onChanged: (bool newValue) {
+  ///     setState(() {
+  ///       _giveVerse = newValue;
+  ///     });
+  ///   },
+  /// )
+  /// ```
+  final ValueChanged<bool>? onChanged;
+
+  /// The color to use when this switch is on.
+  ///
+  /// Defaults to [CupertinoColors.systemGreen] when null and ignores
+  /// the [CupertinoTheme] in accordance to native iOS behavior.
+  final Color? activeColor;
+
+  /// The color to use for the background when the switch is off.
+  ///
+  /// Defaults to [CupertinoColors.secondarySystemFill] when null.
+  final Color? trackColor;
+
+  /// {@template flutter.cupertino.CupertinoSwitch.dragStartBehavior}
+  /// Determines the way that drag start behavior is handled.
+  ///
+  /// If set to [DragStartBehavior.start], the drag behavior used to move the
+  /// switch from on to off will begin upon the detection of a drag gesture. If
+  /// set to [DragStartBehavior.down] it will begin when a down event is first
+  /// detected.
+  ///
+  /// In general, setting this to [DragStartBehavior.start] will make drag
+  /// animation smoother and setting it to [DragStartBehavior.down] will make
+  /// drag behavior feel slightly more reactive.
+  ///
+  /// By default, the drag start behavior is [DragStartBehavior.start].
+  ///
+  /// See also:
+  ///
+  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for
+  ///    the different behaviors.
+  ///
+  /// {@endtemplate}
+  final DragStartBehavior dragStartBehavior;
+
+  @override
+  _CupertinoSwitchState createState() => _CupertinoSwitchState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('value', value: value, ifTrue: 'on', ifFalse: 'off', showName: true));
+    properties.add(ObjectFlagProperty<ValueChanged<bool>>('onChanged', onChanged, ifNull: 'disabled'));
+  }
+}
+
+class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderStateMixin {
+  late TapGestureRecognizer _tap;
+  late HorizontalDragGestureRecognizer _drag;
+
+  late AnimationController _positionController;
+  late CurvedAnimation position;
+
+  late AnimationController _reactionController;
+  late Animation<double> _reaction;
+
+  bool get isInteractive => widget.onChanged != null;
+
+  // A non-null boolean value that changes to true at the end of a drag if the
+  // switch must be animated to the position indicated by the widget's value.
+  bool needsPositionAnimation = false;
+
+  @override
+  void initState() {
+    super.initState();
+
+    _tap = TapGestureRecognizer()
+      ..onTapDown = _handleTapDown
+      ..onTapUp = _handleTapUp
+      ..onTap = _handleTap
+      ..onTapCancel = _handleTapCancel;
+    _drag = HorizontalDragGestureRecognizer()
+      ..onStart = _handleDragStart
+      ..onUpdate = _handleDragUpdate
+      ..onEnd = _handleDragEnd
+      ..dragStartBehavior = widget.dragStartBehavior;
+
+    _positionController = AnimationController(
+      duration: _kToggleDuration,
+      value: widget.value ? 1.0 : 0.0,
+      vsync: this,
+    );
+    position = CurvedAnimation(
+      parent: _positionController,
+      curve: Curves.linear,
+    );
+    _reactionController = AnimationController(
+      duration: _kReactionDuration,
+      vsync: this,
+    );
+    _reaction = CurvedAnimation(
+      parent: _reactionController,
+      curve: Curves.ease,
+    );
+  }
+
+  @override
+  void didUpdateWidget(CupertinoSwitch oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    _drag.dragStartBehavior = widget.dragStartBehavior;
+
+    if (needsPositionAnimation || oldWidget.value != widget.value)
+      _resumePositionAnimation(isLinear: needsPositionAnimation);
+  }
+
+  // `isLinear` must be true if the position animation is trying to move the
+  // thumb to the closest end after the most recent drag animation, so the curve
+  // does not change when the controller's value is not 0 or 1.
+  //
+  // It can be set to false when it's an implicit animation triggered by
+  // widget.value changes.
+  void _resumePositionAnimation({ bool isLinear = true }) {
+    needsPositionAnimation = false;
+    position
+      ..curve = isLinear ? Curves.linear : Curves.ease
+      ..reverseCurve = isLinear ? Curves.linear : Curves.ease.flipped;
+    if (widget.value)
+      _positionController.forward();
+    else
+      _positionController.reverse();
+  }
+
+  void _handleTapDown(TapDownDetails details) {
+    if (isInteractive)
+      needsPositionAnimation = false;
+      _reactionController.forward();
+  }
+
+  void _handleTap() {
+    if (isInteractive) {
+      widget.onChanged!(!widget.value);
+      _emitVibration();
+    }
+  }
+
+  void _handleTapUp(TapUpDetails details) {
+    if (isInteractive) {
+      needsPositionAnimation = false;
+      _reactionController.reverse();
+    }
+  }
+
+  void _handleTapCancel() {
+    if (isInteractive)
+      _reactionController.reverse();
+  }
+
+  void _handleDragStart(DragStartDetails details) {
+    if (isInteractive) {
+      needsPositionAnimation = false;
+      _reactionController.forward();
+      _emitVibration();
+    }
+  }
+
+  void _handleDragUpdate(DragUpdateDetails details) {
+    if (isInteractive) {
+      position
+        ..curve = Curves.linear
+        ..reverseCurve = Curves.linear;
+      final double delta = details.primaryDelta! / _kTrackInnerLength;
+      switch (Directionality.of(context)) {
+        case TextDirection.rtl:
+          _positionController.value -= delta;
+          break;
+        case TextDirection.ltr:
+          _positionController.value += delta;
+          break;
+      }
+    }
+  }
+
+  void _handleDragEnd(DragEndDetails details) {
+    // Deferring the animation to the next build phase.
+    setState(() { needsPositionAnimation = true; });
+    // Call onChanged when the user's intent to change value is clear.
+    if (position.value >= 0.5 != widget.value)
+      widget.onChanged!(!widget.value);
+    _reactionController.reverse();
+  }
+
+  void _emitVibration() {
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.iOS:
+        HapticFeedback.lightImpact();
+        break;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.macOS:
+      case TargetPlatform.windows:
+        break;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (needsPositionAnimation)
+      _resumePositionAnimation();
+    return Opacity(
+      opacity: widget.onChanged == null ? _kCupertinoSwitchDisabledOpacity : 1.0,
+      child: _CupertinoSwitchRenderObjectWidget(
+        value: widget.value,
+        activeColor: CupertinoDynamicColor.resolve(
+          widget.activeColor ?? CupertinoColors.systemGreen,
+          context,
+        ),
+        trackColor: CupertinoDynamicColor.resolve(widget.trackColor ?? CupertinoColors.secondarySystemFill, context),
+        onChanged: widget.onChanged,
+        textDirection: Directionality.of(context),
+        state: this,
+      ),
+    );
+  }
+
+  @override
+  void dispose() {
+    _tap.dispose();
+    _drag.dispose();
+
+    _positionController.dispose();
+    _reactionController.dispose();
+    super.dispose();
+  }
+}
+
+class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
+  const _CupertinoSwitchRenderObjectWidget({
+    Key? key,
+    required this.value,
+    required this.activeColor,
+    required this.trackColor,
+    required this.onChanged,
+    required this.textDirection,
+    required this.state,
+  }) : super(key: key);
+
+  final bool value;
+  final Color activeColor;
+  final Color trackColor;
+  final ValueChanged<bool>? onChanged;
+  final _CupertinoSwitchState state;
+  final TextDirection textDirection;
+
+  @override
+  _RenderCupertinoSwitch createRenderObject(BuildContext context) {
+    return _RenderCupertinoSwitch(
+      value: value,
+      activeColor: activeColor,
+      trackColor: trackColor,
+      onChanged: onChanged,
+      textDirection: textDirection,
+      state: state,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderCupertinoSwitch renderObject) {
+    renderObject
+      ..value = value
+      ..activeColor = activeColor
+      ..trackColor = trackColor
+      ..onChanged = onChanged
+      ..textDirection = textDirection;
+  }
+}
+
+const double _kTrackWidth = 51.0;
+const double _kTrackHeight = 31.0;
+const double _kTrackRadius = _kTrackHeight / 2.0;
+const double _kTrackInnerStart = _kTrackHeight / 2.0;
+const double _kTrackInnerEnd = _kTrackWidth - _kTrackInnerStart;
+const double _kTrackInnerLength = _kTrackInnerEnd - _kTrackInnerStart;
+const double _kSwitchWidth = 59.0;
+const double _kSwitchHeight = 39.0;
+// Opacity of a disabled switch, as eye-balled from iOS Simulator on Mac.
+const double _kCupertinoSwitchDisabledOpacity = 0.5;
+
+const Duration _kReactionDuration = Duration(milliseconds: 300);
+const Duration _kToggleDuration = Duration(milliseconds: 200);
+
+class _RenderCupertinoSwitch extends RenderConstrainedBox {
+  _RenderCupertinoSwitch({
+    required bool value,
+    required Color activeColor,
+    required Color trackColor,
+    ValueChanged<bool>? onChanged,
+    required TextDirection textDirection,
+    required _CupertinoSwitchState state,
+  }) : assert(value != null),
+       assert(activeColor != null),
+       assert(state != null),
+       _value = value,
+       _activeColor = activeColor,
+       _trackColor = trackColor,
+       _onChanged = onChanged,
+       _textDirection = textDirection,
+       _state = state,
+       super(additionalConstraints: const BoxConstraints.tightFor(width: _kSwitchWidth, height: _kSwitchHeight)) {
+         state.position.addListener(markNeedsPaint);
+         state._reaction.addListener(markNeedsPaint);
+  }
+
+  final _CupertinoSwitchState _state;
+
+  bool get value => _value;
+  bool _value;
+  set value(bool value) {
+    assert(value != null);
+    if (value == _value)
+      return;
+    _value = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  Color get activeColor => _activeColor;
+  Color _activeColor;
+  set activeColor(Color value) {
+    assert(value != null);
+    if (value == _activeColor)
+      return;
+    _activeColor = value;
+    markNeedsPaint();
+  }
+
+  Color get trackColor => _trackColor;
+  Color _trackColor;
+  set trackColor(Color value) {
+    assert(value != null);
+    if (value == _trackColor)
+      return;
+    _trackColor = value;
+    markNeedsPaint();
+  }
+
+  ValueChanged<bool>? get onChanged => _onChanged;
+  ValueChanged<bool>? _onChanged;
+  set onChanged(ValueChanged<bool>? value) {
+    if (value == _onChanged)
+      return;
+    final bool wasInteractive = isInteractive;
+    _onChanged = value;
+    if (wasInteractive != isInteractive) {
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  TextDirection get textDirection => _textDirection;
+  TextDirection _textDirection;
+  set textDirection(TextDirection value) {
+    assert(value != null);
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    markNeedsPaint();
+  }
+
+  bool get isInteractive => onChanged != null;
+
+  @override
+  bool hitTestSelf(Offset position) => true;
+
+  @override
+  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
+    assert(debugHandleEvent(event, entry));
+    if (event is PointerDownEvent && isInteractive) {
+      _state._drag.addPointer(event);
+      _state._tap.addPointer(event);
+    }
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+
+    if (isInteractive)
+      config.onTap = _state._handleTap;
+
+    config.isEnabled = isInteractive;
+    config.isToggled = _value;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final Canvas canvas = context.canvas;
+
+    final double currentValue = _state.position.value;
+    final double currentReactionValue = _state._reaction.value;
+
+    final double visualPosition;
+    switch (textDirection) {
+      case TextDirection.rtl:
+        visualPosition = 1.0 - currentValue;
+        break;
+      case TextDirection.ltr:
+        visualPosition = currentValue;
+        break;
+    }
+
+    final Paint paint = Paint()
+      ..color = Color.lerp(trackColor, activeColor, currentValue)!;
+
+    final Rect trackRect = Rect.fromLTWH(
+        offset.dx + (size.width - _kTrackWidth) / 2.0,
+        offset.dy + (size.height - _kTrackHeight) / 2.0,
+        _kTrackWidth,
+        _kTrackHeight,
+    );
+    final RRect trackRRect = RRect.fromRectAndRadius(trackRect, const Radius.circular(_kTrackRadius));
+    canvas.drawRRect(trackRRect, paint);
+
+    final double currentThumbExtension = CupertinoThumbPainter.extension * currentReactionValue;
+    final double thumbLeft = lerpDouble(
+      trackRect.left + _kTrackInnerStart - CupertinoThumbPainter.radius,
+      trackRect.left + _kTrackInnerEnd - CupertinoThumbPainter.radius - currentThumbExtension,
+      visualPosition,
+    )!;
+    final double thumbRight = lerpDouble(
+      trackRect.left + _kTrackInnerStart + CupertinoThumbPainter.radius + currentThumbExtension,
+      trackRect.left + _kTrackInnerEnd + CupertinoThumbPainter.radius,
+      visualPosition,
+    )!;
+    final double thumbCenterY = offset.dy + size.height / 2.0;
+    final Rect thumbBounds = Rect.fromLTRB(
+      thumbLeft,
+      thumbCenterY - CupertinoThumbPainter.radius,
+      thumbRight,
+      thumbCenterY + CupertinoThumbPainter.radius,
+    );
+
+    _clipRRectLayer = context.pushClipRRect(needsCompositing, Offset.zero, thumbBounds, trackRRect, (PaintingContext innerContext, Offset offset) {
+      const CupertinoThumbPainter.switchThumb().paint(innerContext.canvas, thumbBounds);
+    }, oldLayer: _clipRRectLayer);
+  }
+
+  ClipRRectLayer? _clipRRectLayer;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(FlagProperty('value', value: value, ifTrue: 'checked', ifFalse: 'unchecked', showName: true));
+    description.add(FlagProperty('isInteractive', value: isInteractive, ifTrue: 'enabled', ifFalse: 'disabled', showName: true, defaultValue: true));
+  }
+}
diff --git a/lib/src/cupertino/tab_scaffold.dart b/lib/src/cupertino/tab_scaffold.dart
new file mode 100644
index 0000000..d3d058e
--- /dev/null
+++ b/lib/src/cupertino/tab_scaffold.dart
@@ -0,0 +1,634 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+import 'bottom_tab_bar.dart';
+import 'colors.dart';
+import 'theme.dart';
+
+/// Coordinates tab selection between a [CupertinoTabBar] and a [CupertinoTabScaffold].
+///
+/// The [index] property is the index of the selected tab. Changing its value
+/// updates the actively displayed tab of the [CupertinoTabScaffold] the
+/// [CupertinoTabController] controls, as well as the currently selected tab item of
+/// its [CupertinoTabBar].
+///
+/// {@tool snippet}
+///
+/// [CupertinoTabController] can be used to switch tabs:
+///
+/// ```dart
+/// class MyCupertinoTabScaffoldPage extends StatefulWidget {
+///   @override
+///   _CupertinoTabScaffoldPageState createState() => _CupertinoTabScaffoldPageState();
+/// }
+///
+/// class _CupertinoTabScaffoldPageState extends State<MyCupertinoTabScaffoldPage> {
+///   final CupertinoTabController _controller = CupertinoTabController();
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return CupertinoTabScaffold(
+///       tabBar: CupertinoTabBar(
+///         items: <BottomNavigationBarItem> [
+///           // ...
+///         ],
+///       ),
+///       controller: _controller,
+///       tabBuilder: (BuildContext context, int index) {
+///         return Center(
+///           child: CupertinoButton(
+///             child: const Text('Go to first tab'),
+///             onPressed: () => _controller.index = 0,
+///           )
+///         );
+///       }
+///     );
+///   }
+///
+///   @override
+///   void dispose() {
+///     _controller.dispose();
+///     super.dispose();
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [CupertinoTabScaffold], a tabbed application root layout that can be
+///    controlled by a [CupertinoTabController].
+///  * [RestorableCupertinoTabController], which is a restorable version
+///    of this controller.
+class CupertinoTabController extends ChangeNotifier {
+  /// Creates a [CupertinoTabController] to control the tab index of [CupertinoTabScaffold]
+  /// and [CupertinoTabBar].
+  ///
+  /// The [initialIndex] must not be null and defaults to 0. The value must be
+  /// greater than or equal to 0, and less than the total number of tabs.
+  CupertinoTabController({ int initialIndex = 0 })
+    : _index = initialIndex,
+      assert(initialIndex != null),
+      assert(initialIndex >= 0);
+
+  bool _isDisposed = false;
+
+  /// The index of the currently selected tab.
+  ///
+  /// Changing the value of [index] updates the actively displayed tab of the
+  /// [CupertinoTabScaffold] controlled by this [CupertinoTabController], as well
+  /// as the currently selected tab item of its [CupertinoTabScaffold.tabBar].
+  ///
+  /// The value must be greater than or equal to 0, and less than the total
+  /// number of tabs.
+  int get index => _index;
+  int _index;
+  set index(int value) {
+    assert(value != null);
+    assert(value >= 0);
+    if (_index == value) {
+      return;
+    }
+    _index = value;
+    notifyListeners();
+  }
+
+  @mustCallSuper
+  @override
+  void dispose() {
+    super.dispose();
+    _isDisposed = true;
+  }
+}
+
+/// Implements a tabbed iOS application's root layout and behavior structure.
+///
+/// The scaffold lays out the tab bar at the bottom and the content between or
+/// behind the tab bar.
+///
+/// A [tabBar] and a [tabBuilder] are required. The [CupertinoTabScaffold]
+/// will automatically listen to the provided [CupertinoTabBar]'s tap callbacks
+/// to change the active tab.
+///
+/// A [controller] can be used to provide an initially selected tab index and manage
+/// subsequent tab changes. If a controller is not specified, the scaffold will
+/// create its own [CupertinoTabController] and manage it internally. Otherwise
+/// it's up to the owner of [controller] to call `dispose` on it after finish
+/// using it.
+///
+/// Tabs' contents are built with the provided [tabBuilder] at the active
+/// tab index. The [tabBuilder] must be able to build the same number of
+/// pages as there are [tabBar] items. Inactive tabs will be moved [Offstage]
+/// and their animations disabled.
+///
+/// Adding/removing tabs, or changing the order of tabs is supported but not
+/// recommended. Doing so is against the iOS human interface guidelines, and
+/// [CupertinoTabScaffold] may lose some tabs' state in the process.
+///
+/// Use [CupertinoTabView] as the root widget of each tab to support tabs with
+/// parallel navigation state and history. Since each [CupertinoTabView] contains
+/// a [Navigator], rebuilding the [CupertinoTabView] with a different
+/// [WidgetBuilder] instance in [CupertinoTabView.builder] will not recreate
+/// the [CupertinoTabView]'s navigation stack or update its UI. To update the
+/// contents of the [CupertinoTabView] after it's built, trigger a rebuild
+/// (via [State.setState], for instance) from its descendant rather than from
+/// its ancestor.
+///
+/// {@tool snippet}
+///
+/// A sample code implementing a typical iOS information architecture with tabs.
+///
+/// ```dart
+/// CupertinoTabScaffold(
+///   tabBar: CupertinoTabBar(
+///     items: <BottomNavigationBarItem> [
+///       // ...
+///     ],
+///   ),
+///   tabBuilder: (BuildContext context, int index) {
+///     return CupertinoTabView(
+///       builder: (BuildContext context) {
+///         return CupertinoPageScaffold(
+///           navigationBar: CupertinoNavigationBar(
+///             middle: Text('Page 1 of tab $index'),
+///           ),
+///           child: Center(
+///             child: CupertinoButton(
+///               child: const Text('Next page'),
+///               onPressed: () {
+///                 Navigator.of(context).push(
+///                   CupertinoPageRoute<void>(
+///                     builder: (BuildContext context) {
+///                       return CupertinoPageScaffold(
+///                         navigationBar: CupertinoNavigationBar(
+///                           middle: Text('Page 2 of tab $index'),
+///                         ),
+///                         child: Center(
+///                           child: CupertinoButton(
+///                             child: const Text('Back'),
+///                             onPressed: () { Navigator.of(context).pop(); },
+///                           ),
+///                         ),
+///                       );
+///                     },
+///                   ),
+///                 );
+///               },
+///             ),
+///           ),
+///         );
+///       },
+///     );
+///   },
+/// )
+/// ```
+/// {@end-tool}
+///
+/// To push a route above all tabs instead of inside the currently selected one
+/// (such as when showing a dialog on top of this scaffold), use
+/// `Navigator.of(rootNavigator: true)` from inside the [BuildContext] of a
+/// [CupertinoTabView].
+///
+/// See also:
+///
+///  * [CupertinoTabBar], the bottom tab bar inserted in the scaffold.
+///  * [CupertinoTabController], the selection state of this widget.
+///  * [CupertinoTabView], the typical root content of each tab that holds its own
+///    [Navigator] stack.
+///  * [CupertinoPageRoute], a route hosting modal pages with iOS style transitions.
+///  * [CupertinoPageScaffold], typical contents of an iOS modal page implementing
+///    layout with a navigation bar on top.
+///  * [iOS human interface guidelines](https://developer.apple.com/design/human-interface-guidelines/ios/bars/tab-bars/).
+class CupertinoTabScaffold extends StatefulWidget {
+  /// Creates a layout for applications with a tab bar at the bottom.
+  ///
+  /// The [tabBar] and [tabBuilder] arguments must not be null.
+  CupertinoTabScaffold({
+    Key? key,
+    required this.tabBar,
+    required this.tabBuilder,
+    this.controller,
+    this.backgroundColor,
+    this.resizeToAvoidBottomInset = true,
+    this.restorationId,
+  }) : assert(tabBar != null),
+       assert(tabBuilder != null),
+       assert(
+         controller == null || controller.index < tabBar.items.length,
+         "The CupertinoTabController's current index ${controller.index} is "
+         'out of bounds for the tab bar with ${tabBar.items.length} tabs'
+       ),
+       super(key: key);
+
+  /// The [tabBar] is a [CupertinoTabBar] drawn at the bottom of the screen
+  /// that lets the user switch between different tabs in the main content area
+  /// when present.
+  ///
+  /// The [CupertinoTabBar.currentIndex] is only used to initialize a
+  /// [CupertinoTabController] when no [controller] is provided. Subsequently
+  /// providing a different [CupertinoTabBar.currentIndex] does not affect the
+  /// scaffold or the tab bar's active tab index. To programmatically change
+  /// the active tab index, use a [CupertinoTabController].
+  ///
+  /// If [CupertinoTabBar.onTap] is provided, it will still be called.
+  /// [CupertinoTabScaffold] automatically also listen to the
+  /// [CupertinoTabBar]'s `onTap` to change the [controller]'s `index`
+  /// and change the actively displayed tab in [CupertinoTabScaffold]'s own
+  /// main content area.
+  ///
+  /// If translucent, the main content may slide behind it.
+  /// Otherwise, the main content's bottom margin will be offset by its height.
+  ///
+  /// By default `tabBar` has its text scale factor set to 1.0 and does not
+  /// respond to text scale factor changes from the operating system, to match
+  /// the native iOS behavior. To override this behavior, wrap each of the `tabBar`'s
+  /// items inside a [MediaQuery] with the desired [MediaQueryData.textScaleFactor]
+  /// value. The text scale factor value from the operating system can be retrieved
+  /// int many ways, such as querying [MediaQuery.textScaleFactorOf] against
+  /// [CupertinoApp]'s [BuildContext].
+  ///
+  /// Must not be null.
+  final CupertinoTabBar tabBar;
+
+  /// Controls the currently selected tab index of the [tabBar], as well as the
+  /// active tab index of the [tabBuilder]. Providing a different [controller]
+  /// will also update the scaffold's current active index to the new controller's
+  /// index value.
+  ///
+  /// Defaults to null.
+  final CupertinoTabController? controller;
+
+  /// An [IndexedWidgetBuilder] that's called when tabs become active.
+  ///
+  /// The widgets built by [IndexedWidgetBuilder] are typically a
+  /// [CupertinoTabView] in order to achieve the parallel hierarchical
+  /// information architecture seen on iOS apps with tab bars.
+  ///
+  /// When the tab becomes inactive, its content is cached in the widget tree
+  /// [Offstage] and its animations disabled.
+  ///
+  /// Content can slide under the [tabBar] when they're translucent.
+  /// In that case, the child's [BuildContext]'s [MediaQuery] will have a
+  /// bottom padding indicating the area of obstructing overlap from the
+  /// [tabBar].
+  ///
+  /// Must not be null.
+  final IndexedWidgetBuilder tabBuilder;
+
+  /// The color of the widget that underlies the entire scaffold.
+  ///
+  /// By default uses [CupertinoTheme]'s `scaffoldBackgroundColor` when null.
+  final Color? backgroundColor;
+
+  /// Whether the body should size itself to avoid the window's bottom inset.
+  ///
+  /// For example, if there is an onscreen keyboard displayed above the
+  /// scaffold, the body can be resized to avoid overlapping the keyboard, which
+  /// prevents widgets inside the body from being obscured by the keyboard.
+  ///
+  /// Defaults to true and cannot be null.
+  final bool resizeToAvoidBottomInset;
+
+  /// Restoration ID to save and restore the state of the [CupertinoTabScaffold].
+  ///
+  /// This property only has an effect when no [controller] has been provided:
+  /// If it is non-null (and no [controller] has been provided), the scaffold
+  /// will persist and restore the currently selected tab index. If a
+  /// [controller] has been provided, it is the responsibility of the owner of
+  /// that controller to persist and restore it, e.g. by using a
+  /// [RestorableCupertinoTabController].
+  ///
+  /// The state of this widget is persisted in a [RestorationBucket] claimed
+  /// from the surrounding [RestorationScope] using the provided restoration ID.
+  ///
+  /// See also:
+  ///
+  ///  * [RestorationManager], which explains how state restoration works in
+  ///    Flutter.
+  final String? restorationId;
+
+  @override
+  _CupertinoTabScaffoldState createState() => _CupertinoTabScaffoldState();
+}
+
+class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> with RestorationMixin {
+  RestorableCupertinoTabController? _internalController;
+  CupertinoTabController get _controller =>  widget.controller ?? _internalController!.value;
+
+  @override
+  String? get restorationId => widget.restorationId;
+
+  @override
+  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
+    _restoreInternalController();
+  }
+
+  void _restoreInternalController() {
+    if (_internalController != null) {
+      registerForRestoration(_internalController!, 'controller');
+      _internalController!.value.addListener(_onCurrentIndexChange);
+    }
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _updateTabController();
+  }
+
+  void _updateTabController([CupertinoTabController? oldWidgetController]) {
+    if (widget.controller == null && _internalController == null) {
+      // No widget-provided controller: create an internal controller.
+      _internalController = RestorableCupertinoTabController(initialIndex: widget.tabBar.currentIndex);
+      if (!restorePending) {
+        _restoreInternalController(); // Also adds the listener to the controller.
+      }
+    }
+    if (widget.controller != null && _internalController != null) {
+      // Use the widget-provided controller.
+      unregisterFromRestoration(_internalController!);
+      _internalController!.dispose();
+      _internalController = null;
+    }
+    if (oldWidgetController != widget.controller) {
+      // The widget-provided controller has changed: move listeners.
+      if (oldWidgetController?._isDisposed == false) {
+        oldWidgetController!.removeListener(_onCurrentIndexChange);
+      }
+      widget.controller?.addListener(_onCurrentIndexChange);
+    }
+  }
+
+  void _onCurrentIndexChange() {
+    assert(
+      _controller.index >= 0 && _controller.index < widget.tabBar.items.length,
+      "The $runtimeType's current index ${_controller.index} is "
+      'out of bounds for the tab bar with ${widget.tabBar.items.length} tabs'
+    );
+
+    // The value of `_controller.index` has already been updated at this point.
+    // Calling `setState` to rebuild using `_controller.index`.
+    setState(() {});
+  }
+
+  @override
+  void didUpdateWidget(CupertinoTabScaffold oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.controller != oldWidget.controller) {
+      _updateTabController(oldWidget.controller);
+    } else if (_controller.index >= widget.tabBar.items.length) {
+      // If a new [tabBar] with less than (_controller.index + 1) items is provided,
+      // clamp the current index.
+      _controller.index = widget.tabBar.items.length - 1;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final MediaQueryData existingMediaQuery = MediaQuery.of(context);
+    MediaQueryData newMediaQuery = MediaQuery.of(context);
+
+    Widget content = _TabSwitchingView(
+      currentTabIndex: _controller.index,
+      tabCount: widget.tabBar.items.length,
+      tabBuilder: widget.tabBuilder,
+    );
+    EdgeInsets contentPadding = EdgeInsets.zero;
+
+    if (widget.resizeToAvoidBottomInset) {
+      // Remove the view inset and add it back as a padding in the inner content.
+      newMediaQuery = newMediaQuery.removeViewInsets(removeBottom: true);
+      contentPadding = EdgeInsets.only(bottom: existingMediaQuery.viewInsets.bottom);
+    }
+
+    if (widget.tabBar != null &&
+        // Only pad the content with the height of the tab bar if the tab
+        // isn't already entirely obstructed by a keyboard or other view insets.
+        // Don't double pad.
+        (!widget.resizeToAvoidBottomInset ||
+            widget.tabBar.preferredSize.height > existingMediaQuery.viewInsets.bottom)) {
+      // TODO(xster): Use real size after partial layout instead of preferred size.
+      // https://github.com/flutter/flutter/issues/12912
+      final double bottomPadding =
+          widget.tabBar.preferredSize.height + existingMediaQuery.padding.bottom;
+
+      // If tab bar opaque, directly stop the main content higher. If
+      // translucent, let main content draw behind the tab bar but hint the
+      // obstructed area.
+      if (widget.tabBar.opaque(context)) {
+        contentPadding = EdgeInsets.only(bottom: bottomPadding);
+        newMediaQuery = newMediaQuery.removePadding(removeBottom: true);
+      } else {
+        newMediaQuery = newMediaQuery.copyWith(
+          padding: newMediaQuery.padding.copyWith(
+            bottom: bottomPadding,
+          ),
+        );
+      }
+    }
+
+    content = MediaQuery(
+      data: newMediaQuery,
+      child: Padding(
+        padding: contentPadding,
+        child: content,
+      ),
+    );
+
+    return DecoratedBox(
+      decoration: BoxDecoration(
+        color: CupertinoDynamicColor.maybeResolve(widget.backgroundColor, context)
+            ?? CupertinoTheme.of(context).scaffoldBackgroundColor,
+      ),
+      child: Stack(
+        children: <Widget>[
+          // The main content being at the bottom is added to the stack first.
+          content,
+          MediaQuery(
+            data: existingMediaQuery.copyWith(textScaleFactor: 1),
+            child: Align(
+              alignment: Alignment.bottomCenter,
+              // Override the tab bar's currentIndex to the current tab and hook in
+              // our own listener to update the [_controller.currentIndex] on top of a possibly user
+              // provided callback.
+              child: widget.tabBar.copyWith(
+                currentIndex: _controller.index,
+                onTap: (int newIndex) {
+                  _controller.index = newIndex;
+                  // Chain the user's original callback.
+                  widget.tabBar.onTap?.call(newIndex);
+                },
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  @override
+  void dispose() {
+    if (widget.controller?._isDisposed == false) {
+      _controller.removeListener(_onCurrentIndexChange);
+    }
+    _internalController?.dispose();
+    super.dispose();
+  }
+}
+
+/// A widget laying out multiple tabs with only one active tab being built
+/// at a time and on stage. Off stage tabs' animations are stopped.
+class _TabSwitchingView extends StatefulWidget {
+  const _TabSwitchingView({
+    required this.currentTabIndex,
+    required this.tabCount,
+    required this.tabBuilder,
+  }) : assert(currentTabIndex != null),
+       assert(tabCount != null && tabCount > 0),
+       assert(tabBuilder != null);
+
+  final int currentTabIndex;
+  final int tabCount;
+  final IndexedWidgetBuilder tabBuilder;
+
+  @override
+  _TabSwitchingViewState createState() => _TabSwitchingViewState();
+}
+
+class _TabSwitchingViewState extends State<_TabSwitchingView> {
+  final List<bool> shouldBuildTab = <bool>[];
+  final List<FocusScopeNode> tabFocusNodes = <FocusScopeNode>[];
+
+  // When focus nodes are no longer needed, we need to dispose of them, but we
+  // can't be sure that nothing else is listening to them until this widget is
+  // disposed of, so when they are no longer needed, we move them to this list,
+  // and dispose of them when we dispose of this widget.
+  final List<FocusScopeNode> discardedNodes = <FocusScopeNode>[];
+
+  @override
+  void initState() {
+    super.initState();
+    shouldBuildTab.addAll(List<bool>.filled(widget.tabCount, false));
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _focusActiveTab();
+  }
+
+  @override
+  void didUpdateWidget(_TabSwitchingView oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    // Only partially invalidate the tabs cache to avoid breaking the current
+    // behavior. We assume that the only possible change is either:
+    // - new tabs are appended to the tab list, or
+    // - some trailing tabs are removed.
+    // If the above assumption is not true, some tabs may lose their state.
+    final int lengthDiff = widget.tabCount - shouldBuildTab.length;
+    if (lengthDiff > 0) {
+      shouldBuildTab.addAll(List<bool>.filled(lengthDiff, false));
+    } else if (lengthDiff < 0) {
+      shouldBuildTab.removeRange(widget.tabCount, shouldBuildTab.length);
+    }
+    _focusActiveTab();
+  }
+
+  // Will focus the active tab if the FocusScope above it has focus already.  If
+  // not, then it will just mark it as the preferred focus for that scope.
+  void _focusActiveTab() {
+    if (tabFocusNodes.length != widget.tabCount) {
+      if (tabFocusNodes.length > widget.tabCount) {
+        discardedNodes.addAll(tabFocusNodes.sublist(widget.tabCount));
+        tabFocusNodes.removeRange(widget.tabCount, tabFocusNodes.length);
+      } else {
+        tabFocusNodes.addAll(
+          List<FocusScopeNode>.generate(
+            widget.tabCount - tabFocusNodes.length,
+              (int index) => FocusScopeNode(debugLabel: '$CupertinoTabScaffold Tab ${index + tabFocusNodes.length}'),
+          ),
+        );
+      }
+    }
+    FocusScope.of(context).setFirstFocus(tabFocusNodes[widget.currentTabIndex]);
+  }
+
+  @override
+  void dispose() {
+    for (final FocusScopeNode focusScopeNode in tabFocusNodes) {
+      focusScopeNode.dispose();
+    }
+    for (final FocusScopeNode focusScopeNode in discardedNodes) {
+      focusScopeNode.dispose();
+    }
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Stack(
+      fit: StackFit.expand,
+      children: List<Widget>.generate(widget.tabCount, (int index) {
+        final bool active = index == widget.currentTabIndex;
+        shouldBuildTab[index] = active || shouldBuildTab[index];
+
+        return HeroMode(
+          enabled: active,
+          child: Offstage(
+            offstage: !active,
+            child: TickerMode(
+              enabled: active,
+              child: FocusScope(
+                node: tabFocusNodes[index],
+                child: Builder(builder: (BuildContext context) {
+                  return shouldBuildTab[index] ? widget.tabBuilder(context, index) : Container();
+                }),
+              ),
+            ),
+          ),
+        );
+      }),
+    );
+  }
+}
+
+/// A [RestorableProperty] that knows how to store and restore a
+/// [CupertinoTabController].
+///
+/// The [CupertinoTabController] is accessible via the [value] getter. During
+/// state restoration, the property will restore [CupertinoTabController.index]
+/// to the value it had when the restoration data it is getting restored from
+/// was collected.
+class RestorableCupertinoTabController extends RestorableChangeNotifier<CupertinoTabController> {
+  /// Creates a [RestorableCupertinoTabController] to control the tab index of
+  /// [CupertinoTabScaffold] and [CupertinoTabBar].
+  ///
+  /// The `initialIndex` must not be null and defaults to 0. The value must be
+  /// greater than or equal to 0, and less than the total number of tabs.
+  RestorableCupertinoTabController({ int initialIndex = 0 })
+    : assert(initialIndex != null),
+      assert(initialIndex >= 0),
+      _initialIndex = initialIndex;
+
+  final int _initialIndex;
+
+  @override
+  CupertinoTabController createDefaultValue() {
+    return CupertinoTabController(initialIndex: _initialIndex);
+  }
+
+  @override
+  CupertinoTabController fromPrimitives(Object? data) {
+    assert(data != null);
+    return CupertinoTabController(initialIndex: data! as int);
+  }
+
+  @override
+  Object? toPrimitives() {
+    return value.index;
+  }
+}
diff --git a/lib/src/cupertino/tab_view.dart b/lib/src/cupertino/tab_view.dart
new file mode 100644
index 0000000..e61297e
--- /dev/null
+++ b/lib/src/cupertino/tab_view.dart
@@ -0,0 +1,231 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'app.dart' show CupertinoApp;
+import 'route.dart';
+
+/// A single tab view with its own [Navigator] state and history.
+///
+/// A typical tab view is used as the content of each tab in a
+/// [CupertinoTabScaffold] where multiple tabs with parallel navigation states
+/// and history can co-exist.
+///
+/// [CupertinoTabView] configures the top-level [Navigator] to search for routes
+/// in the following order:
+///
+///  1. For the `/` route, the [builder] property, if non-null, is used.
+///
+///  2. Otherwise, the [routes] table is used, if it has an entry for the route,
+///     including `/` if [builder] is not specified.
+///
+///  3. Otherwise, [onGenerateRoute] is called, if provided. It should return a
+///     non-null value for any _valid_ route not handled by [builder] and [routes].
+///
+///  4. Finally if all else fails [onUnknownRoute] is called.
+///
+/// These navigation properties are not shared with any sibling [CupertinoTabView]
+/// nor any ancestor or descendant [Navigator] instances.
+///
+/// To push a route above this [CupertinoTabView] instead of inside it (such
+/// as when showing a dialog on top of all tabs), use
+/// `Navigator.of(rootNavigator: true)`.
+///
+/// See also:
+///
+///  * [CupertinoTabScaffold], a typical host that supports switching between tabs.
+///  * [CupertinoPageRoute], a typical modal page route pushed onto the
+///    [CupertinoTabView]'s [Navigator].
+class CupertinoTabView extends StatefulWidget {
+  /// Creates the content area for a tab in a [CupertinoTabScaffold].
+  const CupertinoTabView({
+    Key? key,
+    this.builder,
+    this.navigatorKey,
+    this.defaultTitle,
+    this.routes,
+    this.onGenerateRoute,
+    this.onUnknownRoute,
+    this.navigatorObservers = const <NavigatorObserver>[],
+    this.restorationScopeId,
+  }) : assert(navigatorObservers != null),
+       super(key: key);
+
+  /// The widget builder for the default route of the tab view
+  /// ([Navigator.defaultRouteName], which is `/`).
+  ///
+  /// If a [builder] is specified, then [routes] must not include an entry for `/`,
+  /// as [builder] takes its place.
+  ///
+  /// Rebuilding a [CupertinoTabView] with a different [builder] will not clear
+  /// its current navigation stack or update its descendant. Instead, trigger a
+  /// rebuild from a descendant in its subtree. This can be done via methods such
+  /// as:
+  ///
+  ///  * Calling [State.setState] on a descendant [StatefulWidget]'s [State]
+  ///  * Modifying an [InheritedWidget] that a descendant registered itself
+  ///    as a dependent to.
+  final WidgetBuilder? builder;
+
+  /// A key to use when building this widget's [Navigator].
+  ///
+  /// If a [navigatorKey] is specified, the [Navigator] can be directly
+  /// manipulated without first obtaining it from a [BuildContext] via
+  /// [Navigator.of]: from the [navigatorKey], use the [GlobalKey.currentState]
+  /// getter.
+  ///
+  /// If this is changed, a new [Navigator] will be created, losing all the
+  /// tab's state in the process; in that case, the [navigatorObservers]
+  /// must also be changed, since the previous observers will be attached to the
+  /// previous navigator.
+  final GlobalKey<NavigatorState>? navigatorKey;
+
+  /// The title of the default route.
+  final String? defaultTitle;
+
+  /// This tab view's routing table.
+  ///
+  /// When a named route is pushed with [Navigator.pushNamed] inside this tab view,
+  /// the route name is looked up in this map. If the name is present,
+  /// the associated [WidgetBuilder] is used to construct a [CupertinoPageRoute]
+  /// that performs an appropriate transition to the new route.
+  ///
+  /// If the tab view only has one page, then you can specify it using [builder] instead.
+  ///
+  /// If [builder] is specified, then it implies an entry in this table for the
+  /// [Navigator.defaultRouteName] route (`/`), and it is an error to
+  /// redundantly provide such a route in the [routes] table.
+  ///
+  /// If a route is requested that is not specified in this table (or by
+  /// [builder]), then the [onGenerateRoute] callback is called to build the page
+  /// instead.
+  ///
+  /// This routing table is not shared with any routing tables of ancestor or
+  /// descendant [Navigator]s.
+  final Map<String, WidgetBuilder>? routes;
+
+  /// The route generator callback used when the tab view is navigated to a named route.
+  ///
+  /// This is used if [routes] does not contain the requested route.
+  final RouteFactory? onGenerateRoute;
+
+  /// Called when [onGenerateRoute] also fails to generate a route.
+  ///
+  /// This callback is typically used for error handling. For example, this
+  /// callback might always generate a "not found" page that describes the route
+  /// that wasn't found.
+  ///
+  /// The default implementation pushes a route that displays an ugly error
+  /// message.
+  final RouteFactory? onUnknownRoute;
+
+  /// The list of observers for the [Navigator] created in this tab view.
+  ///
+  /// This list of observers is not shared with ancestor or descendant [Navigator]s.
+  final List<NavigatorObserver> navigatorObservers;
+
+  /// Restoration ID to save and restore the state of the [Navigator] built by
+  /// this [CupertinoTabView].
+  ///
+  /// {@macro flutter.widgets.navigator.restorationScopeId}
+  final String? restorationScopeId;
+
+  @override
+  _CupertinoTabViewState createState() {
+    return _CupertinoTabViewState();
+  }
+}
+
+class _CupertinoTabViewState extends State<CupertinoTabView> {
+  late HeroController _heroController;
+  late List<NavigatorObserver> _navigatorObservers;
+
+  @override
+  void initState() {
+    super.initState();
+    _heroController = CupertinoApp.createCupertinoHeroController();
+    _updateObservers();
+  }
+
+  @override
+  void didUpdateWidget(CupertinoTabView oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.navigatorKey != oldWidget.navigatorKey
+        || widget.navigatorObservers != oldWidget.navigatorObservers) {
+      _updateObservers();
+    }
+  }
+
+  void _updateObservers() {
+    _navigatorObservers =
+        List<NavigatorObserver>.from(widget.navigatorObservers)
+          ..add(_heroController);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Navigator(
+      key: widget.navigatorKey,
+      onGenerateRoute: _onGenerateRoute,
+      onUnknownRoute: _onUnknownRoute,
+      observers: _navigatorObservers,
+      restorationScopeId: widget.restorationScopeId,
+    );
+  }
+
+  Route<dynamic>? _onGenerateRoute(RouteSettings settings) {
+    final String? name = settings.name;
+    WidgetBuilder? routeBuilder;
+    String? title;
+    if (name == Navigator.defaultRouteName && widget.builder != null) {
+      routeBuilder = widget.builder;
+      title = widget.defaultTitle;
+    } else if (widget.routes != null) {
+      routeBuilder = widget.routes![name];
+    }
+    if (routeBuilder != null) {
+      return CupertinoPageRoute<dynamic>(
+        builder: routeBuilder,
+        title: title,
+        settings: settings,
+      );
+    }
+    if (widget.onGenerateRoute != null)
+      return widget.onGenerateRoute!(settings);
+    return null;
+  }
+
+  Route<dynamic>? _onUnknownRoute(RouteSettings settings) {
+    assert(() {
+      if (widget.onUnknownRoute == null) {
+        throw FlutterError(
+          'Could not find a generator for route $settings in the $runtimeType.\n'
+          'Generators for routes are searched for in the following order:\n'
+          ' 1. For the "/" route, the "builder" property, if non-null, is used.\n'
+          ' 2. Otherwise, the "routes" table is used, if it has an entry for '
+          'the route.\n'
+          ' 3. Otherwise, onGenerateRoute is called. It should return a '
+          'non-null value for any valid route not handled by "builder" and "routes".\n'
+          ' 4. Finally if all else fails onUnknownRoute is called.\n'
+          'Unfortunately, onUnknownRoute was not set.'
+        );
+      }
+      return true;
+    }());
+    final Route<dynamic>? result = widget.onUnknownRoute!(settings);
+    assert(() {
+      if (result == null) {
+        throw FlutterError(
+          'The onUnknownRoute callback returned null.\n'
+          'When the $runtimeType requested the route $settings from its '
+          'onUnknownRoute callback, the callback returned null. Such callbacks '
+          'must never return null.'
+        );
+      }
+      return true;
+    }());
+    return result;
+  }
+}
diff --git a/lib/src/cupertino/text_field.dart b/lib/src/cupertino/text_field.dart
new file mode 100644
index 0000000..720ccea
--- /dev/null
+++ b/lib/src/cupertino/text_field.dart
@@ -0,0 +1,1215 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui show BoxHeightStyle, BoxWidthStyle;
+
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'icons.dart';
+import 'text_selection.dart';
+import 'theme.dart';
+
+export 'package:flute/services.dart' show TextInputType, TextInputAction, TextCapitalization, SmartQuotesType, SmartDashesType;
+
+// Examples can assume:
+// // @dart = 2.9
+
+const TextStyle _kDefaultPlaceholderStyle = TextStyle(
+  fontWeight: FontWeight.w400,
+  color: CupertinoColors.placeholderText,
+);
+
+// Value inspected from Xcode 11 & iOS 13.0 Simulator.
+const BorderSide _kDefaultRoundedBorderSide = BorderSide(
+  color: CupertinoDynamicColor.withBrightness(
+    color: Color(0x33000000),
+    darkColor: Color(0x33FFFFFF),
+  ),
+  style: BorderStyle.solid,
+  width: 0.0,
+);
+const Border _kDefaultRoundedBorder = Border(
+  top: _kDefaultRoundedBorderSide,
+  bottom: _kDefaultRoundedBorderSide,
+  left: _kDefaultRoundedBorderSide,
+  right: _kDefaultRoundedBorderSide,
+);
+
+const BoxDecoration _kDefaultRoundedBorderDecoration = BoxDecoration(
+  color: CupertinoDynamicColor.withBrightness(
+    color: CupertinoColors.white,
+    darkColor: CupertinoColors.black,
+  ),
+  border: _kDefaultRoundedBorder,
+  borderRadius: BorderRadius.all(Radius.circular(5.0)),
+);
+
+const Color _kDisabledBackground = CupertinoDynamicColor.withBrightness(
+  color: Color(0xFFFAFAFA),
+  darkColor: Color(0xFF050505),
+);
+
+// Value inspected from Xcode 12 & iOS 14.0 Simulator.
+// Note it may not be consistent with https://developer.apple.com/design/resources/.
+const CupertinoDynamicColor _kClearButtonColor = CupertinoDynamicColor.withBrightness(
+  color: Color(0x33000000),
+  darkColor: Color(0x33FFFFFF),
+);
+
+// An eyeballed value that moves the cursor slightly left of where it is
+// rendered for text on Android so it's positioning more accurately matches the
+// native iOS text cursor positioning.
+//
+// This value is in device pixels, not logical pixels as is typically used
+// throughout the codebase.
+const int _iOSHorizontalCursorOffsetPixels = -2;
+
+/// Visibility of text field overlays based on the state of the current text entry.
+///
+/// Used to toggle the visibility behavior of the optional decorating widgets
+/// surrounding the [EditableText] such as the clear text button.
+enum OverlayVisibilityMode {
+  /// Overlay will never appear regardless of the text entry state.
+  never,
+
+  /// Overlay will only appear when the current text entry is not empty.
+  ///
+  /// This includes prefilled text that the user did not type in manually. But
+  /// does not include text in placeholders.
+  editing,
+
+  /// Overlay will only appear when the current text entry is empty.
+  ///
+  /// This also includes not having prefilled text that the user did not type
+  /// in manually. Texts in placeholders are ignored.
+  notEditing,
+
+  /// Always show the overlay regardless of the text entry state.
+  always,
+}
+
+class _CupertinoTextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDetectorBuilder {
+  _CupertinoTextFieldSelectionGestureDetectorBuilder({
+    required _CupertinoTextFieldState state,
+  }) : _state = state,
+       super(delegate: state);
+
+  final _CupertinoTextFieldState _state;
+
+  @override
+  void onSingleTapUp(TapUpDetails details) {
+    // Because TextSelectionGestureDetector listens to taps that happen on
+    // widgets in front of it, tapping the clear button will also trigger
+    // this handler. If the clear button widget recognizes the up event,
+    // then do not handle it.
+    if (_state._clearGlobalKey.currentContext != null) {
+      final RenderBox renderBox = _state._clearGlobalKey.currentContext!.findRenderObject()! as RenderBox;
+      final Offset localOffset = renderBox.globalToLocal(details.globalPosition);
+      if (renderBox.hitTest(BoxHitTestResult(), position: localOffset)) {
+        return;
+      }
+    }
+    super.onSingleTapUp(details);
+    _state._requestKeyboard();
+    if (_state.widget.onTap != null)
+      _state.widget.onTap!();
+  }
+
+  @override
+  void onDragSelectionEnd(DragEndDetails details) {
+    _state._requestKeyboard();
+  }
+}
+
+/// An iOS-style text field.
+///
+/// A text field lets the user enter text, either with a hardware keyboard or with
+/// an onscreen keyboard.
+///
+/// This widget corresponds to both a `UITextField` and an editable `UITextView`
+/// on iOS.
+///
+/// The text field calls the [onChanged] callback whenever the user changes the
+/// text in the field. If the user indicates that they are done typing in the
+/// field (e.g., by pressing a button on the soft keyboard), the text field
+/// calls the [onSubmitted] callback.
+///
+/// {@macro flutter.widgets.EditableText.onChanged}
+///
+/// To control the text that is displayed in the text field, use the
+/// [controller]. For example, to set the initial value of the text field, use
+/// a [controller] that already contains some text such as:
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// class MyPrefilledText extends StatefulWidget {
+///   @override
+///   _MyPrefilledTextState createState() => _MyPrefilledTextState();
+/// }
+///
+/// class _MyPrefilledTextState extends State<MyPrefilledText> {
+///   TextEditingController _textController;
+///
+///   @override
+///   void initState() {
+///     super.initState();
+///     _textController = TextEditingController(text: 'initial text');
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return CupertinoTextField(controller: _textController);
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// The [controller] can also control the selection and composing region (and to
+/// observe changes to the text, selection, and composing region).
+///
+/// The text field has an overridable [decoration] that, by default, draws a
+/// rounded rectangle border around the text field. If you set the [decoration]
+/// property to null, the decoration will be removed entirely.
+///
+/// Remember to call [TextEditingController.dispose] when it is no longer
+/// needed. This will ensure we discard any resources used by the object.
+///
+/// See also:
+///
+///  * <https://developer.apple.com/documentation/uikit/uitextfield>
+///  * [TextField], an alternative text field widget that follows the Material
+///    Design UI conventions.
+///  * [EditableText], which is the raw text editing control at the heart of a
+///    [TextField].
+///  * Learn how to use a [TextEditingController] in one of our [cookbook recipes](https://flutter.dev/docs/cookbook/forms/text-field-changes#2-use-a-texteditingcontroller).
+class CupertinoTextField extends StatefulWidget {
+  /// Creates an iOS-style text field.
+  ///
+  /// To provide a prefilled text entry, pass in a [TextEditingController] with
+  /// an initial value to the [controller] parameter.
+  ///
+  /// To provide a hint placeholder text that appears when the text entry is
+  /// empty, pass a [String] to the [placeholder] parameter.
+  ///
+  /// The [maxLines] property can be set to null to remove the restriction on
+  /// the number of lines. In this mode, the intrinsic height of the widget will
+  /// grow as the number of lines of text grows. By default, it is `1`, meaning
+  /// this is a single-line text field and will scroll horizontally when
+  /// overflown. [maxLines] must not be zero.
+  ///
+  /// The text cursor is not shown if [showCursor] is false or if [showCursor]
+  /// is null (the default) and [readOnly] is true.
+  ///
+  /// If specified, the [maxLength] property must be greater than zero.
+  ///
+  /// The [selectionHeightStyle] and [selectionWidthStyle] properties allow
+  /// changing the shape of the selection highlighting. These properties default
+  /// to [ui.BoxHeightStyle.tight] and [ui.BoxWidthStyle.tight] respectively and
+  /// must not be null.
+  ///
+  /// The [autocorrect], [autofocus], [clearButtonMode], [dragStartBehavior],
+  /// [expands], [maxLengthEnforced], [obscureText], [prefixMode], [readOnly],
+  /// [scrollPadding], [suffixMode], [textAlign], [selectionHeightStyle],
+  /// [selectionWidthStyle], and [enableSuggestions] properties must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [minLines], which is the minimum number of lines to occupy when the
+  ///    content spans fewer lines.
+  ///  * [expands], to allow the widget to size itself to its parent's height.
+  ///  * [maxLength], which discusses the precise meaning of "number of
+  ///    characters" and how it may differ from the intuitive meaning.
+  const CupertinoTextField({
+    Key? key,
+    this.controller,
+    this.focusNode,
+    this.decoration = _kDefaultRoundedBorderDecoration,
+    this.padding = const EdgeInsets.all(6.0),
+    this.placeholder,
+    this.placeholderStyle = const TextStyle(
+      fontWeight: FontWeight.w400,
+      color: CupertinoColors.placeholderText,
+    ),
+    this.prefix,
+    this.prefixMode = OverlayVisibilityMode.always,
+    this.suffix,
+    this.suffixMode = OverlayVisibilityMode.always,
+    this.clearButtonMode = OverlayVisibilityMode.never,
+    TextInputType? keyboardType,
+    this.textInputAction,
+    this.textCapitalization = TextCapitalization.none,
+    this.style,
+    this.strutStyle,
+    this.textAlign = TextAlign.start,
+    this.textAlignVertical,
+    this.readOnly = false,
+    ToolbarOptions? toolbarOptions,
+    this.showCursor,
+    this.autofocus = false,
+    this.obscuringCharacter = '•',
+    this.obscureText = false,
+    this.autocorrect = true,
+    SmartDashesType? smartDashesType,
+    SmartQuotesType? smartQuotesType,
+    this.enableSuggestions = true,
+    this.maxLines = 1,
+    this.minLines,
+    this.expands = false,
+    this.maxLength,
+    @Deprecated(
+      'Use maxLengthEnforcement parameter which provides more specific '
+      'behavior related to the maxLength limit. '
+      'This feature was deprecated after v1.25.0-5.0.pre.'
+    )
+    this.maxLengthEnforced = true,
+    this.maxLengthEnforcement,
+    this.onChanged,
+    this.onEditingComplete,
+    this.onSubmitted,
+    this.inputFormatters,
+    this.enabled,
+    this.cursorWidth = 2.0,
+    this.cursorHeight,
+    this.cursorRadius = const Radius.circular(2.0),
+    this.cursorColor,
+    this.selectionHeightStyle = ui.BoxHeightStyle.tight,
+    this.selectionWidthStyle = ui.BoxWidthStyle.tight,
+    this.keyboardAppearance,
+    this.scrollPadding = const EdgeInsets.all(20.0),
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.enableInteractiveSelection = true,
+    this.selectionControls,
+    this.onTap,
+    this.scrollController,
+    this.scrollPhysics,
+    this.autofillHints,
+    this.restorationId,
+  }) : assert(textAlign != null),
+       assert(readOnly != null),
+       assert(autofocus != null),
+       // TODO(a14n): uncomment when issue is fixed, https://github.com/dart-lang/sdk/issues/43407
+       assert(obscuringCharacter != null/* && obscuringCharacter.length == 1*/),
+       assert(obscureText != null),
+       assert(autocorrect != null),
+       smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
+       smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
+       assert(enableSuggestions != null),
+       assert(maxLengthEnforced != null),
+       assert(
+         maxLengthEnforced || maxLengthEnforcement == null,
+         'maxLengthEnforced is deprecated, use only maxLengthEnforcement',
+       ),
+       assert(scrollPadding != null),
+       assert(dragStartBehavior != null),
+       assert(selectionHeightStyle != null),
+       assert(selectionWidthStyle != null),
+       assert(maxLines == null || maxLines > 0),
+       assert(minLines == null || minLines > 0),
+       assert(
+         (maxLines == null) || (minLines == null) || (maxLines >= minLines),
+         "minLines can't be greater than maxLines",
+       ),
+       assert(expands != null),
+       assert(
+         !expands || (maxLines == null && minLines == null),
+         'minLines and maxLines must be null when expands is true.',
+       ),
+       assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
+       assert(maxLength == null || maxLength > 0),
+       assert(clearButtonMode != null),
+       assert(prefixMode != null),
+       assert(suffixMode != null),
+       // Assert the following instead of setting it directly to avoid surprising the user by silently changing the value they set.
+       assert(!identical(textInputAction, TextInputAction.newline) ||
+         maxLines == 1 ||
+         !identical(keyboardType, TextInputType.text),
+         'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.'),
+       keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
+       toolbarOptions = toolbarOptions ?? (obscureText ?
+         const ToolbarOptions(
+           selectAll: true,
+           paste: true,
+         ) :
+         const ToolbarOptions(
+           copy: true,
+           cut: true,
+           selectAll: true,
+           paste: true,
+         )),
+       super(key: key);
+
+  /// Creates a borderless iOS-style text field.
+  ///
+  /// To provide a prefilled text entry, pass in a [TextEditingController] with
+  /// an initial value to the [controller] parameter.
+  ///
+  /// To provide a hint placeholder text that appears when the text entry is
+  /// empty, pass a [String] to the [placeholder] parameter.
+  ///
+  /// The [maxLines] property can be set to null to remove the restriction on
+  /// the number of lines. In this mode, the intrinsic height of the widget will
+  /// grow as the number of lines of text grows. By default, it is `1`, meaning
+  /// this is a single-line text field and will scroll horizontally when
+  /// overflown. [maxLines] must not be zero.
+  ///
+  /// The text cursor is not shown if [showCursor] is false or if [showCursor]
+  /// is null (the default) and [readOnly] is true.
+  ///
+  /// If specified, the [maxLength] property must be greater than zero.
+  ///
+  /// The [selectionHeightStyle] and [selectionWidthStyle] properties allow
+  /// changing the shape of the selection highlighting. These properties default
+  /// to [ui.BoxHeightStyle.tight] and [ui.BoxWidthStyle.tight] respectively and
+  /// must not be null.
+  ///
+  /// The [autocorrect], [autofocus], [clearButtonMode], [dragStartBehavior],
+  /// [expands], [maxLengthEnforced], [obscureText], [prefixMode], [readOnly],
+  /// [scrollPadding], [suffixMode], [textAlign], [selectionHeightStyle],
+  /// [selectionWidthStyle], and [enableSuggestions] properties must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [minLines], which is the minimum number of lines to occupy when the
+  ///    content spans fewer lines.
+  ///  * [expands], to allow the widget to size itself to its parent's height.
+  ///  * [maxLength], which discusses the precise meaning of "number of
+  ///    characters" and how it may differ from the intuitive meaning.
+  const CupertinoTextField.borderless({
+    Key? key,
+    this.controller,
+    this.focusNode,
+    this.decoration,
+    this.padding = const EdgeInsets.all(6.0),
+    this.placeholder,
+    this.placeholderStyle = _kDefaultPlaceholderStyle,
+    this.prefix,
+    this.prefixMode = OverlayVisibilityMode.always,
+    this.suffix,
+    this.suffixMode = OverlayVisibilityMode.always,
+    this.clearButtonMode = OverlayVisibilityMode.never,
+    TextInputType? keyboardType,
+    this.textInputAction,
+    this.textCapitalization = TextCapitalization.none,
+    this.style,
+    this.strutStyle,
+    this.textAlign = TextAlign.start,
+    this.textAlignVertical,
+    this.readOnly = false,
+    ToolbarOptions? toolbarOptions,
+    this.showCursor,
+    this.autofocus = false,
+    this.obscuringCharacter = '•',
+    this.obscureText = false,
+    this.autocorrect = true,
+    SmartDashesType? smartDashesType,
+    SmartQuotesType? smartQuotesType,
+    this.enableSuggestions = true,
+    this.maxLines = 1,
+    this.minLines,
+    this.expands = false,
+    this.maxLength,
+    @Deprecated(
+      'Use maxLengthEnforcement parameter which provides more specific '
+      'behavior related to the maxLength limit. '
+      'This feature was deprecated after v1.25.0-5.0.pre.'
+    )
+    this.maxLengthEnforced = true,
+    this.maxLengthEnforcement,
+    this.onChanged,
+    this.onEditingComplete,
+    this.onSubmitted,
+    this.inputFormatters,
+    this.enabled,
+    this.cursorWidth = 2.0,
+    this.cursorHeight,
+    this.cursorRadius = const Radius.circular(2.0),
+    this.cursorColor,
+    this.selectionHeightStyle = ui.BoxHeightStyle.tight,
+    this.selectionWidthStyle = ui.BoxWidthStyle.tight,
+    this.keyboardAppearance,
+    this.scrollPadding = const EdgeInsets.all(20.0),
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.enableInteractiveSelection = true,
+    this.selectionControls,
+    this.onTap,
+    this.scrollController,
+    this.scrollPhysics,
+    this.autofillHints,
+    this.restorationId,
+  }) : assert(textAlign != null),
+       assert(readOnly != null),
+       assert(autofocus != null),
+       // TODO(a14n): uncomment when issue is fixed, https://github.com/dart-lang/sdk/issues/43407
+       assert(obscuringCharacter != null/* && obscuringCharacter.length == 1*/),
+       assert(obscureText != null),
+       assert(autocorrect != null),
+       smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
+       smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
+       assert(enableSuggestions != null),
+       assert(maxLengthEnforced != null),
+       assert(
+         maxLengthEnforced || maxLengthEnforcement == null,
+         'maxLengthEnforced is deprecated, use only maxLengthEnforcement',
+       ),
+       assert(scrollPadding != null),
+       assert(dragStartBehavior != null),
+       assert(selectionHeightStyle != null),
+       assert(selectionWidthStyle != null),
+       assert(maxLines == null || maxLines > 0),
+       assert(minLines == null || minLines > 0),
+       assert(
+         (maxLines == null) || (minLines == null) || (maxLines >= minLines),
+         "minLines can't be greater than maxLines",
+       ),
+       assert(expands != null),
+       assert(
+         !expands || (maxLines == null && minLines == null),
+         'minLines and maxLines must be null when expands is true.',
+       ),
+       assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
+       assert(maxLength == null || maxLength > 0),
+       assert(clearButtonMode != null),
+       assert(prefixMode != null),
+       assert(suffixMode != null),
+       // Assert the following instead of setting it directly to avoid surprising the user by silently changing the value they set.
+       assert(!identical(textInputAction, TextInputAction.newline) ||
+         maxLines == 1 ||
+         !identical(keyboardType, TextInputType.text),
+         'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.'),
+       keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
+       toolbarOptions = toolbarOptions ?? (obscureText ?
+         const ToolbarOptions(
+           selectAll: true,
+           paste: true,
+         ) :
+         const ToolbarOptions(
+           copy: true,
+           cut: true,
+           selectAll: true,
+           paste: true,
+         )),
+       super(key: key);
+
+  /// Controls the text being edited.
+  ///
+  /// If null, this widget will create its own [TextEditingController].
+  final TextEditingController? controller;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// Controls the [BoxDecoration] of the box behind the text input.
+  ///
+  /// Defaults to having a rounded rectangle grey border and can be null to have
+  /// no box decoration.
+  final BoxDecoration? decoration;
+
+  /// Padding around the text entry area between the [prefix] and [suffix]
+  /// or the clear button when [clearButtonMode] is not never.
+  ///
+  /// Defaults to a padding of 6 pixels on all sides and can be null.
+  final EdgeInsetsGeometry padding;
+
+  /// A lighter colored placeholder hint that appears on the first line of the
+  /// text field when the text entry is empty.
+  ///
+  /// Defaults to having no placeholder text.
+  ///
+  /// The text style of the placeholder text matches that of the text field's
+  /// main text entry except a lighter font weight and a grey font color.
+  final String? placeholder;
+
+  /// The style to use for the placeholder text.
+  ///
+  /// The [placeholderStyle] is merged with the [style] [TextStyle] when applied
+  /// to the [placeholder] text. To avoid merging with [style], specify
+  /// [TextStyle.inherit] as false.
+  ///
+  /// Defaults to the [style] property with w300 font weight and grey color.
+  ///
+  /// If specifically set to null, placeholder's style will be the same as [style].
+  final TextStyle? placeholderStyle;
+
+  /// An optional [Widget] to display before the text.
+  final Widget? prefix;
+
+  /// Controls the visibility of the [prefix] widget based on the state of
+  /// text entry when the [prefix] argument is not null.
+  ///
+  /// Defaults to [OverlayVisibilityMode.always] and cannot be null.
+  ///
+  /// Has no effect when [prefix] is null.
+  final OverlayVisibilityMode prefixMode;
+
+  /// An optional [Widget] to display after the text.
+  final Widget? suffix;
+
+  /// Controls the visibility of the [suffix] widget based on the state of
+  /// text entry when the [suffix] argument is not null.
+  ///
+  /// Defaults to [OverlayVisibilityMode.always] and cannot be null.
+  ///
+  /// Has no effect when [suffix] is null.
+  final OverlayVisibilityMode suffixMode;
+
+  /// Show an iOS-style clear button to clear the current text entry.
+  ///
+  /// Can be made to appear depending on various text states of the
+  /// [TextEditingController].
+  ///
+  /// Will only appear if no [suffix] widget is appearing.
+  ///
+  /// Defaults to never appearing and cannot be null.
+  final OverlayVisibilityMode clearButtonMode;
+
+  /// {@macro flutter.widgets.editableText.keyboardType}
+  final TextInputType keyboardType;
+
+  /// The type of action button to use for the keyboard.
+  ///
+  /// Defaults to [TextInputAction.newline] if [keyboardType] is
+  /// [TextInputType.multiline] and [TextInputAction.done] otherwise.
+  final TextInputAction? textInputAction;
+
+  /// {@macro flutter.widgets.editableText.textCapitalization}
+  final TextCapitalization textCapitalization;
+
+  /// The style to use for the text being edited.
+  ///
+  /// Also serves as a base for the [placeholder] text's style.
+  ///
+  /// Defaults to the standard iOS font style from [CupertinoTheme] if null.
+  final TextStyle? style;
+
+  /// {@macro flutter.widgets.editableText.strutStyle}
+  final StrutStyle? strutStyle;
+
+  /// {@macro flutter.widgets.editableText.textAlign}
+  final TextAlign textAlign;
+
+  /// Configuration of toolbar options.
+  ///
+  /// If not set, select all and paste will default to be enabled. Copy and cut
+  /// will be disabled if [obscureText] is true. If [readOnly] is true,
+  /// paste and cut will be disabled regardless.
+  final ToolbarOptions toolbarOptions;
+
+  /// {@macro flutter.material.InputDecorator.textAlignVertical}
+  final TextAlignVertical? textAlignVertical;
+
+  /// {@macro flutter.widgets.editableText.readOnly}
+  final bool readOnly;
+
+  /// {@macro flutter.widgets.editableText.showCursor}
+  final bool? showCursor;
+
+  /// {@macro flutter.widgets.editableText.autofocus}
+  final bool autofocus;
+
+  /// {@macro flutter.widgets.editableText.obscuringCharacter}
+  final String obscuringCharacter;
+
+  /// {@macro flutter.widgets.editableText.obscureText}
+  final bool obscureText;
+
+  /// {@macro flutter.widgets.editableText.autocorrect}
+  final bool autocorrect;
+
+  /// {@macro flutter.services.TextInputConfiguration.smartDashesType}
+  final SmartDashesType smartDashesType;
+
+  /// {@macro flutter.services.TextInputConfiguration.smartQuotesType}
+  final SmartQuotesType smartQuotesType;
+
+  /// {@macro flutter.services.TextInputConfiguration.enableSuggestions}
+  final bool enableSuggestions;
+
+  /// {@macro flutter.widgets.editableText.maxLines}
+  final int? maxLines;
+
+  /// {@macro flutter.widgets.editableText.minLines}
+  final int? minLines;
+
+  /// {@macro flutter.widgets.editableText.expands}
+  final bool expands;
+
+  /// The maximum number of characters (Unicode scalar values) to allow in the
+  /// text field.
+  ///
+  /// After [maxLength] characters have been input, additional input
+  /// is ignored, unless [maxLengthEnforcement] is set to
+  /// [MaxLengthEnforcement.none].
+  ///
+  /// The TextField enforces the length with a
+  /// [LengthLimitingTextInputFormatter], which is evaluated after the supplied
+  /// [inputFormatters], if any.
+  ///
+  /// This value must be either null or greater than zero. If set to null
+  /// (the default), there is no limit to the number of characters allowed.
+  ///
+  /// Whitespace characters (e.g. newline, space, tab) are included in the
+  /// character count.
+  ///
+  /// {@macro flutter.services.lengthLimitingTextInputFormatter.maxLength}
+  final int? maxLength;
+
+  /// If [maxLength] is set, [maxLengthEnforced] indicates whether or not to
+  /// enforce the limit.
+  ///
+  /// If true, prevents the field from allowing more than [maxLength]
+  /// characters.
+  @Deprecated(
+    'Use maxLengthEnforcement parameter which provides more specific '
+    'behavior related to the maxLength limit. '
+    'This feature was deprecated after v1.25.0-5.0.pre.'
+  )
+  final bool maxLengthEnforced;
+
+  /// Determines how the [maxLength] limit should be enforced.
+  ///
+  /// If [MaxLengthEnforcement.none] is set, additional input beyond [maxLength]
+  /// will not be enforced by the limit.
+  ///
+  /// {@macro flutter.services.textFormatter.effectiveMaxLengthEnforcement}
+  ///
+  /// {@macro flutter.services.textFormatter.maxLengthEnforcement}
+  final MaxLengthEnforcement? maxLengthEnforcement;
+
+  /// {@macro flutter.widgets.editableText.onChanged}
+  final ValueChanged<String>? onChanged;
+
+  /// {@macro flutter.widgets.editableText.onEditingComplete}
+  final VoidCallback? onEditingComplete;
+
+  /// {@macro flutter.widgets.editableText.onSubmitted}
+  ///
+  /// See also:
+  ///
+  ///  * [TextInputAction.next] and [TextInputAction.previous], which
+  ///    automatically shift the focus to the next/previous focusable item when
+  ///    the user is done editing.
+  final ValueChanged<String>? onSubmitted;
+
+  /// {@macro flutter.widgets.editableText.inputFormatters}
+  final List<TextInputFormatter>? inputFormatters;
+
+  /// Disables the text field when false.
+  ///
+  /// Text fields in disabled states have a light grey background and don't
+  /// respond to touch events including the [prefix], [suffix] and the clear
+  /// button.
+  final bool? enabled;
+
+  /// {@macro flutter.widgets.editableText.cursorWidth}
+  final double cursorWidth;
+
+  /// {@macro flutter.widgets.editableText.cursorHeight}
+  final double? cursorHeight;
+
+  /// {@macro flutter.widgets.editableText.cursorRadius}
+  final Radius cursorRadius;
+
+  /// The color to use when painting the cursor.
+  ///
+  /// Defaults to the [CupertinoThemeData.primaryColor] of the ambient theme,
+  /// which itself defaults to [CupertinoColors.activeBlue] in the light theme
+  /// and [CupertinoColors.activeOrange] in the dark theme.
+  final Color? cursorColor;
+
+  /// Controls how tall the selection highlight boxes are computed to be.
+  ///
+  /// See [ui.BoxHeightStyle] for details on available styles.
+  final ui.BoxHeightStyle selectionHeightStyle;
+
+  /// Controls how wide the selection highlight boxes are computed to be.
+  ///
+  /// See [ui.BoxWidthStyle] for details on available styles.
+  final ui.BoxWidthStyle selectionWidthStyle;
+
+  /// The appearance of the keyboard.
+  ///
+  /// This setting is only honored on iOS devices.
+  ///
+  /// If null, defaults to [Brightness.light].
+  final Brightness? keyboardAppearance;
+
+  /// {@macro flutter.widgets.editableText.scrollPadding}
+  final EdgeInsets scrollPadding;
+
+  /// {@macro flutter.widgets.editableText.enableInteractiveSelection}
+  final bool enableInteractiveSelection;
+
+  /// {@macro flutter.widgets.editableText.selectionControls}
+  final TextSelectionControls? selectionControls;
+
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  /// {@macro flutter.widgets.editableText.scrollController}
+  final ScrollController? scrollController;
+
+  /// {@macro flutter.widgets.editableText.scrollPhysics}
+  final ScrollPhysics? scrollPhysics;
+
+  /// {@macro flutter.widgets.editableText.selectionEnabled}
+  bool get selectionEnabled => enableInteractiveSelection;
+
+  /// {@macro flutter.material.textfield.onTap}
+  final GestureTapCallback? onTap;
+
+  /// {@macro flutter.widgets.editableText.autofillHints}
+  /// {@macro flutter.services.AutofillConfiguration.autofillHints}
+  final Iterable<String>? autofillHints;
+
+  /// {@macro flutter.material.textfield.restorationId}
+  final String? restorationId;
+
+  @override
+  _CupertinoTextFieldState createState() => _CupertinoTextFieldState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<TextEditingController>('controller', controller, defaultValue: null));
+    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
+    properties.add(DiagnosticsProperty<BoxDecoration>('decoration', decoration));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
+    properties.add(StringProperty('placeholder', placeholder));
+    properties.add(DiagnosticsProperty<TextStyle>('placeholderStyle', placeholderStyle));
+    properties.add(DiagnosticsProperty<OverlayVisibilityMode>('prefix', prefix == null ? null : prefixMode));
+    properties.add(DiagnosticsProperty<OverlayVisibilityMode>('suffix', suffix == null ? null : suffixMode));
+    properties.add(DiagnosticsProperty<OverlayVisibilityMode>('clearButtonMode', clearButtonMode));
+    properties.add(DiagnosticsProperty<TextInputType>('keyboardType', keyboardType, defaultValue: TextInputType.text));
+    properties.add(DiagnosticsProperty<TextStyle>('style', style, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false));
+    properties.add(DiagnosticsProperty<String>('obscuringCharacter', obscuringCharacter, defaultValue: '•'));
+    properties.add(DiagnosticsProperty<bool>('obscureText', obscureText, defaultValue: false));
+    properties.add(DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: true));
+    properties.add(EnumProperty<SmartDashesType>('smartDashesType', smartDashesType, defaultValue: obscureText ? SmartDashesType.disabled : SmartDashesType.enabled));
+    properties.add(EnumProperty<SmartQuotesType>('smartQuotesType', smartQuotesType, defaultValue: obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled));
+    properties.add(DiagnosticsProperty<bool>('enableSuggestions', enableSuggestions, defaultValue: true));
+    properties.add(IntProperty('maxLines', maxLines, defaultValue: 1));
+    properties.add(IntProperty('minLines', minLines, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('expands', expands, defaultValue: false));
+    properties.add(IntProperty('maxLength', maxLength, defaultValue: null));
+    properties.add(FlagProperty('maxLengthEnforced', value: maxLengthEnforced, ifTrue: 'max length enforced'));
+    properties.add(EnumProperty<MaxLengthEnforcement>('maxLengthEnforcement', maxLengthEnforcement, defaultValue: null));
+    properties.add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0));
+    properties.add(DoubleProperty('cursorHeight', cursorHeight, defaultValue: null));
+    properties.add(DiagnosticsProperty<Radius>('cursorRadius', cursorRadius, defaultValue: null));
+    properties.add(createCupertinoColorProperty('cursorColor', cursorColor, defaultValue: null));
+    properties.add(FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'));
+    properties.add(DiagnosticsProperty<TextSelectionControls>('selectionControls', selectionControls, defaultValue: null));
+    properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, defaultValue: null));
+    properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
+    properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: TextAlign.start));
+    properties.add(DiagnosticsProperty<TextAlignVertical>('textAlignVertical', textAlignVertical, defaultValue: null));
+  }
+}
+
+class _CupertinoTextFieldState extends State<CupertinoTextField> with RestorationMixin, AutomaticKeepAliveClientMixin<CupertinoTextField> implements TextSelectionGestureDetectorBuilderDelegate {
+  final GlobalKey _clearGlobalKey = GlobalKey();
+
+  RestorableTextEditingController? _controller;
+  TextEditingController get _effectiveController => widget.controller ?? _controller!.value;
+
+  FocusNode? _focusNode;
+  FocusNode get _effectiveFocusNode => widget.focusNode ?? (_focusNode ??= FocusNode());
+
+  MaxLengthEnforcement get _effectiveMaxLengthEnforcement => widget.maxLengthEnforcement
+    ?? LengthLimitingTextInputFormatter.getDefaultMaxLengthEnforcement();
+
+  bool _showSelectionHandles = false;
+
+  late _CupertinoTextFieldSelectionGestureDetectorBuilder _selectionGestureDetectorBuilder;
+
+  // API for TextSelectionGestureDetectorBuilderDelegate.
+  @override
+  bool get forcePressEnabled => true;
+
+  @override
+  final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>();
+
+  @override
+  bool get selectionEnabled => widget.selectionEnabled;
+  // End of API for TextSelectionGestureDetectorBuilderDelegate.
+
+  @override
+  void initState() {
+    super.initState();
+    _selectionGestureDetectorBuilder = _CupertinoTextFieldSelectionGestureDetectorBuilder(state: this);
+    if (widget.controller == null) {
+      _createLocalController();
+    }
+    _effectiveFocusNode.canRequestFocus = widget.enabled ?? true;
+  }
+
+  @override
+  void didUpdateWidget(CupertinoTextField oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.controller == null && oldWidget.controller != null) {
+      _createLocalController(oldWidget.controller!.value);
+    } else if (widget.controller != null && oldWidget.controller == null) {
+      unregisterFromRestoration(_controller!);
+      _controller!.dispose();
+      _controller = null;
+    }
+    _effectiveFocusNode.canRequestFocus = widget.enabled ?? true;
+  }
+
+  @override
+  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
+    if (_controller != null) {
+      _registerController();
+    }
+  }
+
+  void _registerController() {
+    assert(_controller != null);
+    registerForRestoration(_controller!, 'controller');
+    _controller!.value.addListener(updateKeepAlive);
+  }
+
+  void _createLocalController([TextEditingValue? value]) {
+    assert(_controller == null);
+    _controller = value == null
+        ? RestorableTextEditingController()
+        : RestorableTextEditingController.fromValue(value);
+    if (!restorePending) {
+      _registerController();
+    }
+  }
+
+  @override
+  String? get restorationId => widget.restorationId;
+
+  @override
+  void dispose() {
+    _focusNode?.dispose();
+    _controller?.dispose();
+    super.dispose();
+  }
+
+  EditableTextState get _editableText => editableTextKey.currentState!;
+
+  void _requestKeyboard() {
+    _editableText.requestKeyboard();
+  }
+
+  bool _shouldShowSelectionHandles(SelectionChangedCause? cause) {
+    // When the text field is activated by something that doesn't trigger the
+    // selection overlay, we shouldn't show the handles either.
+    if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar)
+      return false;
+
+    // On iOS, we don't show handles when the selection is collapsed.
+    if (_effectiveController.selection.isCollapsed)
+      return false;
+
+    if (cause == SelectionChangedCause.keyboard)
+      return false;
+
+    if (_effectiveController.text.isNotEmpty)
+      return true;
+
+    return false;
+  }
+
+  void _handleSelectionChanged(TextSelection selection, SelectionChangedCause? cause) {
+    if (cause == SelectionChangedCause.longPress) {
+      _editableText.bringIntoView(selection.base);
+    }
+    final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause);
+    if (willShowSelectionHandles != _showSelectionHandles) {
+      setState(() {
+        _showSelectionHandles = willShowSelectionHandles;
+      });
+    }
+  }
+
+  @override
+  bool get wantKeepAlive => _controller?.value.text.isNotEmpty == true;
+
+  bool _shouldShowAttachment({
+    required OverlayVisibilityMode attachment,
+    required bool hasText,
+  }) {
+    switch (attachment) {
+      case OverlayVisibilityMode.never:
+        return false;
+      case OverlayVisibilityMode.always:
+        return true;
+      case OverlayVisibilityMode.editing:
+        return hasText;
+      case OverlayVisibilityMode.notEditing:
+        return !hasText;
+    }
+  }
+
+  bool _showPrefixWidget(TextEditingValue text) {
+    return widget.prefix != null && _shouldShowAttachment(
+      attachment: widget.prefixMode,
+      hasText: text.text.isNotEmpty,
+    );
+  }
+
+  bool _showSuffixWidget(TextEditingValue text) {
+    return widget.suffix != null && _shouldShowAttachment(
+      attachment: widget.suffixMode,
+      hasText: text.text.isNotEmpty,
+    );
+  }
+
+  bool _showClearButton(TextEditingValue text) {
+    return _shouldShowAttachment(
+      attachment: widget.clearButtonMode,
+      hasText: text.text.isNotEmpty,
+    );
+  }
+
+  // True if any surrounding decoration widgets will be shown.
+  bool get _hasDecoration {
+    return widget.placeholder != null ||
+      widget.clearButtonMode != OverlayVisibilityMode.never ||
+      widget.prefix != null ||
+      widget.suffix != null;
+  }
+
+  // Provide default behavior if widget.textAlignVertical is not set.
+  // CupertinoTextField has top alignment by default, unless it has decoration
+  // like a prefix or suffix, in which case it's aligned to the center.
+  TextAlignVertical get _textAlignVertical {
+    if (widget.textAlignVertical != null) {
+      return widget.textAlignVertical!;
+    }
+    return _hasDecoration ? TextAlignVertical.center : TextAlignVertical.top;
+  }
+
+  Widget _addTextDependentAttachments(Widget editableText, TextStyle textStyle, TextStyle placeholderStyle) {
+    assert(editableText != null);
+    assert(textStyle != null);
+    assert(placeholderStyle != null);
+    // If there are no surrounding widgets, just return the core editable text
+    // part.
+    if (!_hasDecoration) {
+      return editableText;
+    }
+
+    // Otherwise, listen to the current state of the text entry.
+    return ValueListenableBuilder<TextEditingValue>(
+      valueListenable: _effectiveController,
+      child: editableText,
+      builder: (BuildContext context, TextEditingValue? text, Widget? child) {
+        return Row(children: <Widget>[
+          // Insert a prefix at the front if the prefix visibility mode matches
+          // the current text state.
+          if (_showPrefixWidget(text!)) widget.prefix!,
+          // In the middle part, stack the placeholder on top of the main EditableText
+          // if needed.
+          Expanded(
+            child: Stack(
+              children: <Widget>[
+                if (widget.placeholder != null && text.text.isEmpty)
+                  SizedBox(
+                    width: double.infinity,
+                    child: Padding(
+                      padding: widget.padding,
+                      child: Text(
+                        widget.placeholder!,
+                        maxLines: widget.maxLines,
+                        overflow: TextOverflow.ellipsis,
+                        style: placeholderStyle,
+                        textAlign: widget.textAlign,
+                      ),
+                    ),
+                  ),
+                child!,
+              ],
+            ),
+          ),
+          // First add the explicit suffix if the suffix visibility mode matches.
+          if (_showSuffixWidget(text))
+            widget.suffix!
+          // Otherwise, try to show a clear button if its visibility mode matches.
+          else if (_showClearButton(text))
+            GestureDetector(
+              key: _clearGlobalKey,
+              onTap: widget.enabled ?? true ? () {
+                // Special handle onChanged for ClearButton
+                // Also call onChanged when the clear button is tapped.
+                final bool textChanged = _effectiveController.text.isNotEmpty;
+                _effectiveController.clear();
+                if (widget.onChanged != null && textChanged)
+                  widget.onChanged!(_effectiveController.text);
+              } : null,
+              child: Padding(
+                padding: const EdgeInsets.symmetric(horizontal: 6.0),
+                child: Icon(
+                  CupertinoIcons.clear_thick_circled,
+                  size: 18.0,
+                  color: CupertinoDynamicColor.resolve(_kClearButtonColor, context),
+                ),
+              ),
+            ),
+        ]);
+      },
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    super.build(context); // See AutomaticKeepAliveClientMixin.
+    assert(debugCheckHasDirectionality(context));
+    final TextEditingController controller = _effectiveController;
+    final TextSelectionControls textSelectionControls = widget.selectionControls ?? cupertinoTextSelectionControls;
+    final bool enabled = widget.enabled ?? true;
+    final Offset cursorOffset = Offset(_iOSHorizontalCursorOffsetPixels / MediaQuery.of(context).devicePixelRatio, 0);
+    final List<TextInputFormatter> formatters = <TextInputFormatter>[
+      ...?widget.inputFormatters,
+      if (widget.maxLength != null && widget.maxLengthEnforced)
+        LengthLimitingTextInputFormatter(
+          widget.maxLength,
+          maxLengthEnforcement: _effectiveMaxLengthEnforcement,
+        ),
+    ];
+    final CupertinoThemeData themeData = CupertinoTheme.of(context);
+
+    final TextStyle? resolvedStyle = widget.style?.copyWith(
+      color: CupertinoDynamicColor.maybeResolve(widget.style?.color, context),
+      backgroundColor: CupertinoDynamicColor.maybeResolve(widget.style?.backgroundColor, context),
+    );
+
+    final TextStyle textStyle = themeData.textTheme.textStyle.merge(resolvedStyle);
+
+    final TextStyle? resolvedPlaceholderStyle = widget.placeholderStyle?.copyWith(
+      color: CupertinoDynamicColor.maybeResolve(widget.placeholderStyle?.color, context),
+      backgroundColor: CupertinoDynamicColor.maybeResolve(widget.placeholderStyle?.backgroundColor, context),
+    );
+
+    final TextStyle placeholderStyle = textStyle.merge(resolvedPlaceholderStyle);
+
+    final Brightness keyboardAppearance = widget.keyboardAppearance ?? CupertinoTheme.brightnessOf(context);
+    final Color cursorColor = CupertinoDynamicColor.maybeResolve(widget.cursorColor, context) ?? themeData.primaryColor;
+    final Color disabledColor = CupertinoDynamicColor.resolve(_kDisabledBackground, context);
+
+    final Color? decorationColor = CupertinoDynamicColor.maybeResolve(widget.decoration?.color, context);
+
+    final BoxBorder? border = widget.decoration?.border;
+    Border? resolvedBorder = border as Border?;
+    if (border is Border) {
+      BorderSide resolveBorderSide(BorderSide side) {
+        return side == BorderSide.none
+          ? side
+          : side.copyWith(color: CupertinoDynamicColor.resolve(side.color, context));
+      }
+      resolvedBorder = border == null || border.runtimeType != Border
+        ? border
+        : Border(
+          top: resolveBorderSide(border.top),
+          left: resolveBorderSide(border.left),
+          bottom: resolveBorderSide(border.bottom),
+          right: resolveBorderSide(border.right),
+        );
+    }
+
+    final BoxDecoration? effectiveDecoration = widget.decoration?.copyWith(
+      border: resolvedBorder,
+      color: enabled ? decorationColor : (decorationColor ?? disabledColor),
+    );
+
+    final Color selectionColor = CupertinoTheme.of(context).primaryColor.withOpacity(0.2);
+
+    final Widget paddedEditable = Padding(
+      padding: widget.padding,
+      child: RepaintBoundary(
+        child: UnmanagedRestorationScope(
+          bucket: bucket,
+          child: EditableText(
+            key: editableTextKey,
+            controller: controller,
+            readOnly: widget.readOnly,
+            toolbarOptions: widget.toolbarOptions,
+            showCursor: widget.showCursor,
+            showSelectionHandles: _showSelectionHandles,
+            focusNode: _effectiveFocusNode,
+            keyboardType: widget.keyboardType,
+            textInputAction: widget.textInputAction,
+            textCapitalization: widget.textCapitalization,
+            style: textStyle,
+            strutStyle: widget.strutStyle,
+            textAlign: widget.textAlign,
+            autofocus: widget.autofocus,
+            obscuringCharacter: widget.obscuringCharacter,
+            obscureText: widget.obscureText,
+            autocorrect: widget.autocorrect,
+            smartDashesType: widget.smartDashesType,
+            smartQuotesType: widget.smartQuotesType,
+            enableSuggestions: widget.enableSuggestions,
+            maxLines: widget.maxLines,
+            minLines: widget.minLines,
+            expands: widget.expands,
+            selectionColor: selectionColor,
+            selectionControls: widget.selectionEnabled
+              ? textSelectionControls : null,
+            onChanged: widget.onChanged,
+            onSelectionChanged: _handleSelectionChanged,
+            onEditingComplete: widget.onEditingComplete,
+            onSubmitted: widget.onSubmitted,
+            inputFormatters: formatters,
+            rendererIgnoresPointer: true,
+            cursorWidth: widget.cursorWidth,
+            cursorHeight: widget.cursorHeight,
+            cursorRadius: widget.cursorRadius,
+            cursorColor: cursorColor,
+            cursorOpacityAnimates: true,
+            cursorOffset: cursorOffset,
+            paintCursorAboveText: true,
+            autocorrectionTextRectColor: selectionColor,
+            backgroundCursorColor: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context),
+            selectionHeightStyle: widget.selectionHeightStyle,
+            selectionWidthStyle: widget.selectionWidthStyle,
+            scrollPadding: widget.scrollPadding,
+            keyboardAppearance: keyboardAppearance,
+            dragStartBehavior: widget.dragStartBehavior,
+            scrollController: widget.scrollController,
+            scrollPhysics: widget.scrollPhysics,
+            enableInteractiveSelection: widget.enableInteractiveSelection,
+            autofillHints: widget.autofillHints,
+            restorationId: 'editable',
+          ),
+        ),
+      ),
+    );
+
+    return Semantics(
+      enabled: enabled,
+      onTap: !enabled ? null : () {
+        if (!controller.selection.isValid) {
+          controller.selection = TextSelection.collapsed(offset: controller.text.length);
+        }
+        _requestKeyboard();
+      },
+      child: IgnorePointer(
+        ignoring: !enabled,
+        child: Container(
+          decoration: effectiveDecoration,
+          child: _selectionGestureDetectorBuilder.buildGestureDetector(
+            behavior: HitTestBehavior.translucent,
+            child: Align(
+              alignment: Alignment(-1.0, _textAlignVertical.y),
+              widthFactor: 1.0,
+              heightFactor: 1.0,
+              child: _addTextDependentAttachments(paddedEditable, textStyle, placeholderStyle),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/cupertino/text_form_field_row.dart b/lib/src/cupertino/text_form_field_row.dart
new file mode 100644
index 0000000..7e25486
--- /dev/null
+++ b/lib/src/cupertino/text_form_field_row.dart
@@ -0,0 +1,389 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'form_row.dart';
+import 'text_field.dart';
+import 'theme.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+
+/// Creates a [CupertinoFormRow] containing a [FormField] that wraps
+/// a [CupertinoTextField].
+///
+/// A [Form] ancestor is not required. The [Form] simply makes it easier to
+/// save, reset, or validate multiple fields at once. To use without a [Form],
+/// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to
+/// save or reset the form field.
+///
+/// When a [controller] is specified, its [TextEditingController.text]
+/// defines the [initialValue]. If this [FormField] is part of a scrolling
+/// container that lazily constructs its children, like a [ListView] or a
+/// [CustomScrollView], then a [controller] should be specified.
+/// The controller's lifetime should be managed by a stateful widget ancestor
+/// of the scrolling container.
+///
+/// The [prefix] parameter is displayed at the start of the row. Standard iOS
+/// guidelines encourage passing a [Text] widget to [prefix] to detail the
+/// nature of the input.
+///
+/// The [padding] parameter is used to pad the contents of the row. It is
+/// directly passed to [CupertinoFormRow]. If the [padding]
+/// parameter is null, [CupertinoFormRow] constructs its own default
+/// padding (which is the standard form row padding in iOS.) If no edge
+/// insets are intended, explicitly pass [EdgeInsets.zero] to [padding].
+///
+/// If a [controller] is not specified, [initialValue] can be used to give
+/// the automatically generated controller an initial value.
+///
+/// Consider calling [TextEditingController.dispose] of the [controller], if one
+/// is specified, when it is no longer needed. This will ensure we discard any
+/// resources used by the object.
+///
+/// For documentation about the various parameters, see the
+/// [CupertinoTextField] class and [new CupertinoTextField.borderless],
+/// the constructor.
+///
+/// {@tool snippet}
+///
+/// Creates a [CupertinoTextFormFieldRow] with a leading text and validator
+/// function.
+///
+/// If the user enters valid text, the CupertinoTextField appears normally
+/// without any warnings to the user.
+///
+/// If the user enters invalid text, the error message returned from the
+/// validator function is displayed in dark red underneath the input.
+///
+/// ```dart
+/// CupertinoTextFormFieldRow(
+///   prefix: Text('Username'),
+///   onSaved: (String value) {
+///     // This optional block of code can be used to run
+///     // code when the user saves the form.
+///   },
+///   validator: (String value) {
+///     return value.contains('@') ? 'Do not use the @ char.' : null;
+///   },
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+/// This example shows how to move the focus to the next field when the user
+/// presses the SPACE key.
+///
+/// ```dart imports
+/// import 'package:flute/cupertino.dart';
+/// ```
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return CupertinoPageScaffold(
+///     child: Center(
+///       child: Form(
+///         autovalidateMode: AutovalidateMode.always,
+///         onChanged: () {
+///           Form.of(primaryFocus.context).save();
+///         },
+///         child: CupertinoFormSection.insetGrouped(
+///           header: Text('SECTION 1'),
+///           children: List<Widget>.generate(5, (int index) {
+///             return CupertinoTextFormFieldRow(
+///               prefix: Text('Enter text'),
+///               placeholder: 'Enter text',
+///               validator: (value) {
+///                 if (value.isEmpty) {
+///                   return 'Please enter a value';
+///                 }
+///                 return null;
+///               },
+///             );
+///          }),
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+class CupertinoTextFormFieldRow extends FormField<String> {
+  /// Creates a [CupertinoFormRow] containing a [FormField] that wraps
+  /// a [CupertinoTextField].
+  ///
+  /// When a [controller] is specified, [initialValue] must be null (the
+  /// default). If [controller] is null, then a [TextEditingController]
+  /// will be constructed automatically and its `text` will be initialized
+  /// to [initialValue] or the empty string.
+  ///
+  /// The [prefix] parameter is displayed at the start of the row. Standard iOS
+  /// guidelines encourage passing a [Text] widget to [prefix] to detail the
+  /// nature of the input.
+  ///
+  /// The [padding] parameter is used to pad the contents of the row. It is
+  /// directly passed to [CupertinoFormRow]. If the [padding]
+  /// parameter is null, [CupertinoFormRow] constructs its own default
+  /// padding (which is the standard form row padding in iOS.) If no edge
+  /// insets are intended, explicitly pass [EdgeInsets.zero] to [padding].
+  ///
+  /// For documentation about the various parameters, see the
+  /// [CupertinoTextField] class and [new CupertinoTextField.borderless],
+  /// the constructor.
+  CupertinoTextFormFieldRow({
+    Key? key,
+    this.prefix,
+    this.padding,
+    this.controller,
+    String? initialValue,
+    FocusNode? focusNode,
+    BoxDecoration? decoration,
+    TextInputType? keyboardType,
+    TextCapitalization textCapitalization = TextCapitalization.none,
+    TextInputAction? textInputAction,
+    TextStyle? style,
+    StrutStyle? strutStyle,
+    TextAlign textAlign = TextAlign.start,
+    TextAlignVertical? textAlignVertical,
+    bool autofocus = false,
+    bool readOnly = false,
+    ToolbarOptions? toolbarOptions,
+    bool? showCursor,
+    String obscuringCharacter = '•',
+    bool obscureText = false,
+    bool autocorrect = true,
+    SmartDashesType? smartDashesType,
+    SmartQuotesType? smartQuotesType,
+    bool enableSuggestions = true,
+    int? maxLines = 1,
+    int? minLines,
+    bool expands = false,
+    int? maxLength,
+    ValueChanged<String>? onChanged,
+    GestureTapCallback? onTap,
+    VoidCallback? onEditingComplete,
+    ValueChanged<String>? onFieldSubmitted,
+    FormFieldSetter<String>? onSaved,
+    FormFieldValidator<String>? validator,
+    List<TextInputFormatter>? inputFormatters,
+    bool? enabled,
+    double cursorWidth = 2.0,
+    double? cursorHeight,
+    Color? cursorColor,
+    Brightness? keyboardAppearance,
+    EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
+    bool enableInteractiveSelection = true,
+    TextSelectionControls? selectionControls,
+    ScrollPhysics? scrollPhysics,
+    Iterable<String>? autofillHints,
+    AutovalidateMode autovalidateMode = AutovalidateMode.disabled,
+    String? placeholder,
+    TextStyle? placeholderStyle = const TextStyle(
+      fontWeight: FontWeight.w400,
+      color: CupertinoColors.placeholderText,
+    ),
+  })  : assert(initialValue == null || controller == null),
+        assert(textAlign != null),
+        assert(autofocus != null),
+        assert(readOnly != null),
+        assert(obscuringCharacter != null && obscuringCharacter.length == 1),
+        assert(obscureText != null),
+        assert(autocorrect != null),
+        assert(enableSuggestions != null),
+        assert(scrollPadding != null),
+        assert(maxLines == null || maxLines > 0),
+        assert(minLines == null || minLines > 0),
+        assert(
+          (maxLines == null) || (minLines == null) || (maxLines >= minLines),
+          "minLines can't be greater than maxLines",
+        ),
+        assert(expands != null),
+        assert(
+          !expands || (maxLines == null && minLines == null),
+          'minLines and maxLines must be null when expands is true.',
+        ),
+        assert(!obscureText || maxLines == 1,
+            'Obscured fields cannot be multiline.'),
+        assert(maxLength == null || maxLength > 0),
+        assert(enableInteractiveSelection != null),
+        super(
+          key: key,
+          initialValue: controller?.text ?? initialValue ?? '',
+          onSaved: onSaved,
+          validator: validator,
+          autovalidateMode: autovalidateMode,
+          builder: (FormFieldState<String> field) {
+            final _CupertinoTextFormFieldRowState state =
+                field as _CupertinoTextFormFieldRowState;
+
+            void onChangedHandler(String value) {
+              field.didChange(value);
+              if (onChanged != null) {
+                onChanged(value);
+              }
+            }
+
+            return CupertinoFormRow(
+              prefix: prefix,
+              padding: padding,
+              error: (field.errorText == null) ? null : Text(field.errorText!),
+              child: CupertinoTextField.borderless(
+                controller: state._effectiveController,
+                focusNode: focusNode,
+                keyboardType: keyboardType,
+                decoration: decoration,
+                textInputAction: textInputAction,
+                style: style,
+                strutStyle: strutStyle,
+                textAlign: textAlign,
+                textAlignVertical: textAlignVertical,
+                textCapitalization: textCapitalization,
+                autofocus: autofocus,
+                toolbarOptions: toolbarOptions,
+                readOnly: readOnly,
+                showCursor: showCursor,
+                obscuringCharacter: obscuringCharacter,
+                obscureText: obscureText,
+                autocorrect: autocorrect,
+                smartDashesType: smartDashesType,
+                smartQuotesType: smartQuotesType,
+                enableSuggestions: enableSuggestions,
+                maxLines: maxLines,
+                minLines: minLines,
+                expands: expands,
+                maxLength: maxLength,
+                onChanged: onChangedHandler,
+                onTap: onTap,
+                onEditingComplete: onEditingComplete,
+                onSubmitted: onFieldSubmitted,
+                inputFormatters: inputFormatters,
+                enabled: enabled,
+                cursorWidth: cursorWidth,
+                cursorHeight: cursorHeight,
+                cursorColor: cursorColor,
+                scrollPadding: scrollPadding,
+                scrollPhysics: scrollPhysics,
+                keyboardAppearance: keyboardAppearance,
+                enableInteractiveSelection: enableInteractiveSelection,
+                selectionControls: selectionControls,
+                autofillHints: autofillHints,
+                placeholder: placeholder,
+                placeholderStyle: placeholderStyle,
+              ),
+            );
+          },
+        );
+
+  /// A widget that is displayed at the start of the row.
+  ///
+  /// The [prefix] widget is displayed at the start of the row. Standard iOS
+  /// guidelines encourage passing a [Text] widget to [prefix] to detail the
+  /// nature of the input.
+  final Widget? prefix;
+
+  /// Content padding for the row.
+  ///
+  /// The [padding] widget is passed to [CupertinoFormRow]. If the [padding]
+  /// parameter is null, [CupertinoFormRow] constructs its own default
+  /// padding, which is the standard form row padding in iOS.
+  ///
+  /// If no edge insets are intended, explicitly pass [EdgeInsets.zero] to
+  /// [padding].
+  final EdgeInsetsGeometry? padding;
+
+  /// Controls the text being edited.
+  ///
+  /// If null, this widget will create its own [TextEditingController] and
+  /// initialize its [TextEditingController.text] with [initialValue].
+  final TextEditingController? controller;
+
+  @override
+  _CupertinoTextFormFieldRowState createState() =>
+      _CupertinoTextFormFieldRowState();
+}
+
+class _CupertinoTextFormFieldRowState extends FormFieldState<String> {
+  TextEditingController? _controller;
+
+  TextEditingController? get _effectiveController =>
+      widget.controller ?? _controller;
+
+  @override
+  CupertinoTextFormFieldRow get widget =>
+      super.widget as CupertinoTextFormFieldRow;
+
+  @override
+  void initState() {
+    super.initState();
+    if (widget.controller == null) {
+      _controller = TextEditingController(text: widget.initialValue);
+    } else {
+      widget.controller!.addListener(_handleControllerChanged);
+    }
+  }
+
+  @override
+  void didUpdateWidget(CupertinoTextFormFieldRow oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.controller != oldWidget.controller) {
+      oldWidget.controller?.removeListener(_handleControllerChanged);
+      widget.controller?.addListener(_handleControllerChanged);
+
+      if (oldWidget.controller != null && widget.controller == null) {
+        _controller =
+            TextEditingController.fromValue(oldWidget.controller!.value);
+      }
+
+      if (widget.controller != null) {
+        setValue(widget.controller!.text);
+        if (oldWidget.controller == null) {
+          _controller = null;
+        }
+      }
+    }
+  }
+
+  @override
+  void dispose() {
+    widget.controller?.removeListener(_handleControllerChanged);
+    super.dispose();
+  }
+
+  @override
+  void didChange(String? value) {
+    super.didChange(value);
+
+    if (value != null && _effectiveController!.text != value) {
+      _effectiveController!.text = value;
+    }
+  }
+
+  @override
+  void reset() {
+    super.reset();
+
+    if (widget.initialValue != null) {
+      setState(() {
+        _effectiveController!.text = widget.initialValue!;
+      });
+    }
+  }
+
+  void _handleControllerChanged() {
+    // Suppress changes that originated from within this class.
+    //
+    // In the case where a controller has been passed in to this widget, we
+    // register this change listener. In these cases, we'll also receive change
+    // notifications for changes originating from within this class -- for
+    // example, the reset() method. In such cases, the FormField value will
+    // already have been set.
+    if (_effectiveController!.text != value) {
+      didChange(_effectiveController!.text);
+    }
+  }
+}
diff --git a/lib/src/cupertino/text_selection.dart b/lib/src/cupertino/text_selection.dart
new file mode 100644
index 0000000..d5f78c1
--- /dev/null
+++ b/lib/src/cupertino/text_selection.dart
@@ -0,0 +1,1263 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+import 'dart:math' as math;
+import 'package:flute/ui.dart' as ui;
+
+import 'package:flute/widgets.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+
+import 'button.dart';
+import 'colors.dart';
+import 'localizations.dart';
+import 'theme.dart';
+
+// Read off from the output on iOS 12. This color does not vary with the
+// application's theme color.
+const double _kSelectionHandleOverlap = 1.5;
+// Extracted from https://developer.apple.com/design/resources/.
+const double _kSelectionHandleRadius = 6;
+
+// Minimal padding from all edges of the selection toolbar to all edges of the
+// screen.
+const double _kToolbarScreenPadding = 8.0;
+// Minimal padding from tip of the selection toolbar arrow to horizontal edges of the
+// screen. Eyeballed value.
+const double _kArrowScreenPadding = 26.0;
+
+// Vertical distance between the tip of the arrow and the line of text the arrow
+// is pointing to. The value used here is eyeballed.
+const double _kToolbarContentDistance = 8.0;
+// Values derived from https://developer.apple.com/design/resources/.
+// 92% Opacity ~= 0xEB
+
+// Values extracted from https://developer.apple.com/design/resources/.
+// The height of the toolbar, including the arrow.
+const double _kToolbarHeight = 43.0;
+const Size _kToolbarArrowSize = Size(14.0, 7.0);
+const Radius _kToolbarBorderRadius = Radius.circular(8);
+// Colors extracted from https://developer.apple.com/design/resources/.
+// TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/41507.
+const Color _kToolbarBackgroundColor = Color(0xEB202020);
+const Color _kToolbarDividerColor = Color(0xFF808080);
+
+const TextStyle _kToolbarButtonFontStyle = TextStyle(
+  inherit: false,
+  fontSize: 14.0,
+  letterSpacing: -0.15,
+  fontWeight: FontWeight.w400,
+  color: CupertinoColors.white,
+);
+
+const TextStyle _kToolbarButtonDisabledFontStyle = TextStyle(
+  inherit: false,
+  fontSize: 14.0,
+  letterSpacing: -0.15,
+  fontWeight: FontWeight.w400,
+  color: CupertinoColors.inactiveGray,
+);
+
+// Eyeballed value.
+const EdgeInsets _kToolbarButtonPadding = EdgeInsets.symmetric(vertical: 10.0, horizontal: 18.0);
+
+// Generates the child that's passed into CupertinoTextSelectionToolbar.
+class _CupertinoTextSelectionToolbarWrapper extends StatefulWidget {
+  const _CupertinoTextSelectionToolbarWrapper({
+    Key? key,
+    required this.arrowTipX,
+    required this.barTopY,
+    this.clipboardStatus,
+    this.handleCut,
+    this.handleCopy,
+    this.handlePaste,
+    this.handleSelectAll,
+    required this.isArrowPointingDown,
+  }) : super(key: key);
+
+  final double arrowTipX;
+  final double barTopY;
+  final ClipboardStatusNotifier? clipboardStatus;
+  final VoidCallback? handleCut;
+  final VoidCallback? handleCopy;
+  final VoidCallback? handlePaste;
+  final VoidCallback? handleSelectAll;
+  final bool isArrowPointingDown;
+
+  @override
+  _CupertinoTextSelectionToolbarWrapperState createState() => _CupertinoTextSelectionToolbarWrapperState();
+}
+
+class _CupertinoTextSelectionToolbarWrapperState extends State<_CupertinoTextSelectionToolbarWrapper> {
+  late ClipboardStatusNotifier _clipboardStatus;
+
+  void _onChangedClipboardStatus() {
+    setState(() {
+      // Inform the widget that the value of clipboardStatus has changed.
+    });
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _clipboardStatus = widget.clipboardStatus ?? ClipboardStatusNotifier();
+    _clipboardStatus.addListener(_onChangedClipboardStatus);
+    _clipboardStatus.update();
+  }
+
+  @override
+  void didUpdateWidget(_CupertinoTextSelectionToolbarWrapper oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.clipboardStatus == null && widget.clipboardStatus != null) {
+      _clipboardStatus.removeListener(_onChangedClipboardStatus);
+      _clipboardStatus.dispose();
+      _clipboardStatus = widget.clipboardStatus!;
+    } else if (oldWidget.clipboardStatus != null) {
+      if (widget.clipboardStatus == null) {
+        _clipboardStatus = ClipboardStatusNotifier();
+        _clipboardStatus.addListener(_onChangedClipboardStatus);
+        oldWidget.clipboardStatus!.removeListener(_onChangedClipboardStatus);
+      } else if (widget.clipboardStatus != oldWidget.clipboardStatus) {
+        _clipboardStatus = widget.clipboardStatus!;
+        _clipboardStatus.addListener(_onChangedClipboardStatus);
+        oldWidget.clipboardStatus!.removeListener(_onChangedClipboardStatus);
+      }
+    }
+    if (widget.handlePaste != null) {
+      _clipboardStatus.update();
+    }
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    // When used in an Overlay, this can be disposed after its creator has
+    // already disposed _clipboardStatus.
+    if (!_clipboardStatus.disposed) {
+      _clipboardStatus.removeListener(_onChangedClipboardStatus);
+      if (widget.clipboardStatus == null) {
+        _clipboardStatus.dispose();
+      }
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // Don't render the menu until the state of the clipboard is known.
+    if (widget.handlePaste != null
+        && _clipboardStatus.value == ClipboardStatus.unknown) {
+      return const SizedBox(width: 0.0, height: 0.0);
+    }
+
+    final List<Widget> items = <Widget>[];
+    final CupertinoLocalizations localizations = CupertinoLocalizations.of(context);
+    final EdgeInsets arrowPadding = widget.isArrowPointingDown
+      ? EdgeInsets.only(bottom: _kToolbarArrowSize.height)
+      : EdgeInsets.only(top: _kToolbarArrowSize.height);
+    final Widget onePhysicalPixelVerticalDivider =
+        SizedBox(width: 1.0 / MediaQuery.of(context).devicePixelRatio);
+
+    void addToolbarButton(
+      String text,
+      VoidCallback onPressed,
+    ) {
+      if (items.isNotEmpty) {
+        items.add(onePhysicalPixelVerticalDivider);
+      }
+
+      items.add(CupertinoButton(
+        child: Text(
+          text,
+          overflow: TextOverflow.ellipsis,
+          style: _kToolbarButtonFontStyle,
+        ),
+        borderRadius: null,
+        color: _kToolbarBackgroundColor,
+        minSize: _kToolbarHeight,
+        onPressed: onPressed,
+        padding: _kToolbarButtonPadding.add(arrowPadding),
+        pressedOpacity: 0.7,
+      ));
+    }
+
+    if (widget.handleCut != null) {
+      addToolbarButton(localizations.cutButtonLabel, widget.handleCut!);
+    }
+    if (widget.handleCopy != null) {
+      addToolbarButton(localizations.copyButtonLabel, widget.handleCopy!);
+    }
+    if (widget.handlePaste != null
+        && _clipboardStatus.value == ClipboardStatus.pasteable) {
+      addToolbarButton(localizations.pasteButtonLabel, widget.handlePaste!);
+    }
+    if (widget.handleSelectAll != null) {
+      addToolbarButton(localizations.selectAllButtonLabel, widget.handleSelectAll!);
+    }
+
+    return CupertinoTextSelectionToolbar._(
+      barTopY: widget.barTopY,
+      arrowTipX: widget.arrowTipX,
+      isArrowPointingDown: widget.isArrowPointingDown,
+      child: items.isEmpty ? null : _CupertinoTextSelectionToolbarContent(
+        isArrowPointingDown: widget.isArrowPointingDown,
+        children: items,
+      ),
+    );
+  }
+}
+
+/// An iOS-style toolbar that appears in response to text selection.
+///
+/// Typically displays buttons for text manipulation, e.g. copying and pasting text.
+///
+/// See also:
+///
+///  * [TextSelectionControls.buildToolbar], where [CupertinoTextSelectionToolbar]
+///    will be used to build an iOS-style toolbar.
+@visibleForTesting
+class CupertinoTextSelectionToolbar extends SingleChildRenderObjectWidget {
+  const CupertinoTextSelectionToolbar._({
+    Key? key,
+    required double barTopY,
+    required double arrowTipX,
+    required bool isArrowPointingDown,
+    Widget? child,
+  }) : _barTopY = barTopY,
+       _arrowTipX = arrowTipX,
+       _isArrowPointingDown = isArrowPointingDown,
+       super(key: key, child: child);
+
+  // The y-coordinate of toolbar's top edge, in global coordinate system.
+  final double _barTopY;
+
+  // The y-coordinate of the tip of the arrow, in global coordinate system.
+  final double _arrowTipX;
+
+  // Whether the arrow should point down and be attached to the bottom
+  // of the toolbar, or point up and be attached to the top of the toolbar.
+  final bool _isArrowPointingDown;
+
+  @override
+  _ToolbarRenderBox createRenderObject(BuildContext context) => _ToolbarRenderBox(_barTopY, _arrowTipX, _isArrowPointingDown, null);
+
+  @override
+  void updateRenderObject(BuildContext context, _ToolbarRenderBox renderObject) {
+    renderObject
+      ..barTopY = _barTopY
+      ..arrowTipX = _arrowTipX
+      ..isArrowPointingDown = _isArrowPointingDown;
+  }
+}
+
+class _ToolbarParentData extends BoxParentData {
+  // The x offset from the tip of the arrow to the center of the toolbar.
+  // Positive if the tip of the arrow has a larger x-coordinate than the
+  // center of the toolbar.
+  double? arrowXOffsetFromCenter;
+  @override
+  String toString() => 'offset=$offset, arrowXOffsetFromCenter=$arrowXOffsetFromCenter';
+}
+
+class _ToolbarRenderBox extends RenderShiftedBox {
+  _ToolbarRenderBox(
+    this._barTopY,
+    this._arrowTipX,
+    this._isArrowPointingDown,
+    RenderBox? child,
+  ) : super(child);
+
+
+  @override
+  bool get isRepaintBoundary => true;
+
+  double _barTopY;
+  set barTopY(double value) {
+    if (_barTopY == value) {
+      return;
+    }
+    _barTopY = value;
+    markNeedsLayout();
+    markNeedsSemanticsUpdate();
+  }
+
+  double _arrowTipX;
+  set arrowTipX(double value) {
+    if (_arrowTipX == value) {
+      return;
+    }
+    _arrowTipX = value;
+    markNeedsLayout();
+    markNeedsSemanticsUpdate();
+  }
+
+  bool _isArrowPointingDown;
+  set isArrowPointingDown(bool value) {
+    if (_isArrowPointingDown == value) {
+      return;
+    }
+    _isArrowPointingDown = value;
+    markNeedsLayout();
+    markNeedsSemanticsUpdate();
+  }
+
+  final BoxConstraints heightConstraint = const BoxConstraints.tightFor(height: _kToolbarHeight);
+
+  @override
+  void setupParentData(RenderObject child) {
+    if (child.parentData is! _ToolbarParentData) {
+      child.parentData = _ToolbarParentData();
+    }
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return constraints.biggest;
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    size = constraints.biggest;
+
+    if (child == null) {
+      return;
+    }
+    final BoxConstraints enforcedConstraint = constraints
+      .deflate(const EdgeInsets.symmetric(horizontal: _kToolbarScreenPadding))
+      .loosen();
+
+    child!.layout(heightConstraint.enforce(enforcedConstraint), parentUsesSize: true,);
+    final _ToolbarParentData childParentData = child!.parentData! as _ToolbarParentData;
+
+    // The local x-coordinate of the center of the toolbar.
+    final double lowerBound = child!.size.width/2 + _kToolbarScreenPadding;
+    final double upperBound = size.width - child!.size.width/2 - _kToolbarScreenPadding;
+    final double adjustedCenterX = _arrowTipX.clamp(lowerBound, upperBound);
+
+    childParentData.offset = Offset(adjustedCenterX - child!.size.width / 2, _barTopY);
+    childParentData.arrowXOffsetFromCenter = _arrowTipX - adjustedCenterX;
+  }
+
+  // The path is described in the toolbar's coordinate system.
+  Path _clipPath() {
+    final _ToolbarParentData childParentData = child!.parentData! as _ToolbarParentData;
+    final Path rrect = Path()
+      ..addRRect(
+        RRect.fromRectAndRadius(
+          Offset(0, _isArrowPointingDown ? 0 : _kToolbarArrowSize.height)
+            & Size(child!.size.width, child!.size.height - _kToolbarArrowSize.height),
+          _kToolbarBorderRadius,
+        ),
+      );
+
+    final double arrowTipX = child!.size.width / 2 + childParentData.arrowXOffsetFromCenter!;
+
+    final double arrowBottomY = _isArrowPointingDown
+      ? child!.size.height - _kToolbarArrowSize.height
+      : _kToolbarArrowSize.height;
+
+    final double arrowTipY = _isArrowPointingDown ? child!.size.height : 0;
+
+    final Path arrow = Path()
+      ..moveTo(arrowTipX, arrowTipY)
+      ..lineTo(arrowTipX - _kToolbarArrowSize.width / 2, arrowBottomY)
+      ..lineTo(arrowTipX + _kToolbarArrowSize.width / 2, arrowBottomY)
+      ..close();
+
+    return Path.combine(PathOperation.union, rrect, arrow);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child == null) {
+      return;
+    }
+
+    final _ToolbarParentData childParentData = child!.parentData! as _ToolbarParentData;
+    _clipPathLayer = context.pushClipPath(
+      needsCompositing,
+      offset + childParentData.offset,
+      Offset.zero & child!.size,
+      _clipPath(),
+      (PaintingContext innerContext, Offset innerOffset) => innerContext.paintChild(child!, innerOffset),
+      oldLayer: _clipPathLayer
+    );
+  }
+
+  ClipPathLayer? _clipPathLayer;
+  Paint? _debugPaint;
+
+  @override
+  void debugPaintSize(PaintingContext context, Offset offset) {
+    assert(() {
+      if (child == null) {
+        return true;
+      }
+
+      _debugPaint ??= Paint()
+        ..shader = ui.Gradient.linear(
+          const Offset(0.0, 0.0),
+          const Offset(10.0, 10.0),
+          const <Color>[Color(0x00000000), Color(0xFFFF00FF), Color(0xFFFF00FF), Color(0x00000000)],
+          const <double>[0.25, 0.25, 0.75, 0.75],
+          TileMode.repeated,
+        )
+        ..strokeWidth = 2.0
+        ..style = PaintingStyle.stroke;
+
+      final _ToolbarParentData childParentData = child!.parentData! as _ToolbarParentData;
+      context.canvas.drawPath(_clipPath().shift(offset + childParentData.offset), _debugPaint!);
+      return true;
+    }());
+  }
+}
+
+/// Draws a single text selection handle with a bar and a ball.
+class _TextSelectionHandlePainter extends CustomPainter {
+  const _TextSelectionHandlePainter(this.color);
+
+  final Color color;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    const double halfStrokeWidth = 1.0;
+    final Paint paint = Paint()..color = color;
+    final Rect circle = Rect.fromCircle(
+      center: const Offset(_kSelectionHandleRadius, _kSelectionHandleRadius),
+      radius: _kSelectionHandleRadius,
+    );
+    final Rect line = Rect.fromPoints(
+      const Offset(
+        _kSelectionHandleRadius - halfStrokeWidth,
+        2 * _kSelectionHandleRadius - _kSelectionHandleOverlap,
+      ),
+      Offset(_kSelectionHandleRadius + halfStrokeWidth, size.height),
+    );
+    final Path path = Path()
+      ..addOval(circle)
+    // Draw line so it slightly overlaps the circle.
+      ..addRect(line);
+    canvas.drawPath(path, paint);
+  }
+
+  @override
+  bool shouldRepaint(_TextSelectionHandlePainter oldPainter) => color != oldPainter.color;
+}
+
+class _CupertinoTextSelectionControls extends TextSelectionControls {
+  /// Returns the size of the Cupertino handle.
+  @override
+  Size getHandleSize(double textLineHeight) {
+    return Size(
+      _kSelectionHandleRadius * 2,
+      textLineHeight + _kSelectionHandleRadius * 2 - _kSelectionHandleOverlap,
+    );
+  }
+
+  /// Builder for iOS-style copy/paste text selection toolbar.
+  @override
+  Widget buildToolbar(
+    BuildContext context,
+    Rect globalEditableRegion,
+    double textLineHeight,
+    Offset position,
+    List<TextSelectionPoint> endpoints,
+    TextSelectionDelegate delegate,
+    ClipboardStatusNotifier clipboardStatus,
+  ) {
+    assert(debugCheckHasMediaQuery(context));
+    final MediaQueryData mediaQuery = MediaQuery.of(context);
+
+    // The toolbar should appear below the TextField when there is not enough
+    // space above the TextField to show it, assuming there's always enough space
+    // at the bottom in this case.
+    final double toolbarHeightNeeded = mediaQuery.padding.top
+      + _kToolbarScreenPadding
+      + _kToolbarHeight
+      + _kToolbarContentDistance;
+    final double availableHeight = globalEditableRegion.top + endpoints.first.point.dy - textLineHeight;
+    final bool isArrowPointingDown = toolbarHeightNeeded <= availableHeight;
+
+    final double arrowTipX = (position.dx + globalEditableRegion.left).clamp(
+      _kArrowScreenPadding + mediaQuery.padding.left,
+      mediaQuery.size.width - mediaQuery.padding.right - _kArrowScreenPadding,
+    );
+
+    // The y-coordinate has to be calculated instead of directly quoting position.dy,
+    // since the caller (TextSelectionOverlay._buildToolbar) does not know whether
+    // the toolbar is going to be facing up or down.
+    final double localBarTopY = isArrowPointingDown
+      ? endpoints.first.point.dy - textLineHeight - _kToolbarContentDistance - _kToolbarHeight
+      : endpoints.last.point.dy + _kToolbarContentDistance;
+
+    return _CupertinoTextSelectionToolbarWrapper(
+      arrowTipX: arrowTipX,
+      barTopY: localBarTopY + globalEditableRegion.top,
+      clipboardStatus: clipboardStatus,
+      handleCut: canCut(delegate) ? () => handleCut(delegate) : null,
+      handleCopy: canCopy(delegate) ? () => handleCopy(delegate, clipboardStatus) : null,
+      handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null,
+      handleSelectAll: canSelectAll(delegate) ? () => handleSelectAll(delegate) : null,
+      isArrowPointingDown: isArrowPointingDown,
+    );
+  }
+
+  /// Builder for iOS text selection edges.
+  @override
+  Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textLineHeight) {
+    // We want a size that's a vertical line the height of the text plus a 18.0
+    // padding in every direction that will constitute the selection drag area.
+    final Size desiredSize = getHandleSize(textLineHeight);
+
+    final Widget handle = SizedBox.fromSize(
+      size: desiredSize,
+      child: CustomPaint(
+        painter: _TextSelectionHandlePainter(CupertinoTheme.of(context).primaryColor),
+      ),
+    );
+
+    // [buildHandle]'s widget is positioned at the selection cursor's bottom
+    // baseline. We transform the handle such that the SizedBox is superimposed
+    // on top of the text selection endpoints.
+    switch (type) {
+      case TextSelectionHandleType.left:
+        return handle;
+      case TextSelectionHandleType.right:
+        // Right handle is a vertical mirror of the left.
+        return Transform(
+          transform: Matrix4.identity()
+            ..translate(desiredSize.width / 2, desiredSize.height / 2)
+            ..rotateZ(math.pi)
+            ..translate(-desiredSize.width / 2, -desiredSize.height / 2),
+          child: handle,
+        );
+      // iOS doesn't draw anything for collapsed selections.
+      case TextSelectionHandleType.collapsed:
+        return const SizedBox();
+    }
+  }
+
+  /// Gets anchor for cupertino-style text selection handles.
+  ///
+  /// See [TextSelectionControls.getHandleAnchor].
+  @override
+  Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight) {
+    final Size handleSize = getHandleSize(textLineHeight);
+    switch (type) {
+      // The circle is at the top for the left handle, and the anchor point is
+      // all the way at the bottom of the line.
+      case TextSelectionHandleType.left:
+        return Offset(
+          handleSize.width / 2,
+          handleSize.height,
+        );
+      // The right handle is vertically flipped, and the anchor point is near
+      // the top of the circle to give slight overlap.
+      case TextSelectionHandleType.right:
+        return Offset(
+          handleSize.width / 2,
+          handleSize.height - 2 * _kSelectionHandleRadius + _kSelectionHandleOverlap,
+        );
+      // A collapsed handle anchors itself so that it's centered.
+      case TextSelectionHandleType.collapsed:
+        return Offset(
+          handleSize.width / 2,
+          textLineHeight + (handleSize.height - textLineHeight) / 2,
+        );
+    }
+  }
+}
+
+// Renders the content of the selection menu and maintains the page state.
+class _CupertinoTextSelectionToolbarContent extends StatefulWidget {
+  const _CupertinoTextSelectionToolbarContent({
+    Key? key,
+    required this.children,
+    required this.isArrowPointingDown,
+  }) : assert(children != null),
+       // This ignore is used because .isNotEmpty isn't compatible with const.
+       assert(children.length > 0), // ignore: prefer_is_empty
+       super(key: key);
+
+  final List<Widget> children;
+  final bool isArrowPointingDown;
+
+  @override
+  _CupertinoTextSelectionToolbarContentState createState() => _CupertinoTextSelectionToolbarContentState();
+}
+
+class _CupertinoTextSelectionToolbarContentState extends State<_CupertinoTextSelectionToolbarContent> with TickerProviderStateMixin {
+  // Controls the fading of the buttons within the menu during page transitions.
+  late AnimationController _controller;
+  int _page = 0;
+  int? _nextPage;
+
+  void _handleNextPage() {
+    _controller.reverse();
+    _controller.addStatusListener(_statusListener);
+    _nextPage = _page + 1;
+  }
+
+  void _handlePreviousPage() {
+    _controller.reverse();
+    _controller.addStatusListener(_statusListener);
+    _nextPage = _page - 1;
+  }
+
+  void _statusListener(AnimationStatus status) {
+    if (status != AnimationStatus.dismissed) {
+      return;
+    }
+
+    setState(() {
+      _page = _nextPage!;
+      _nextPage = null;
+    });
+    _controller.forward();
+    _controller.removeStatusListener(_statusListener);
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(
+      value: 1.0,
+      vsync: this,
+      // This was eyeballed on a physical iOS device running iOS 13.
+      duration: const Duration(milliseconds: 150),
+    );
+  }
+
+  @override
+  void didUpdateWidget(_CupertinoTextSelectionToolbarContent oldWidget) {
+    // If the children are changing, the current page should be reset.
+    if (widget.children != oldWidget.children) {
+      _page = 0;
+      _nextPage = null;
+      _controller.forward();
+      _controller.removeStatusListener(_statusListener);
+    }
+    super.didUpdateWidget(oldWidget);
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final EdgeInsets arrowPadding = widget.isArrowPointingDown
+      ? EdgeInsets.only(bottom: _kToolbarArrowSize.height)
+      : EdgeInsets.only(top: _kToolbarArrowSize.height);
+
+    return DecoratedBox(
+      decoration: const BoxDecoration(color: _kToolbarDividerColor),
+      child: FadeTransition(
+        opacity: _controller,
+        child: _CupertinoTextSelectionToolbarItems(
+          page: _page,
+          backButton: CupertinoButton(
+            borderRadius: null,
+            color: _kToolbarBackgroundColor,
+            minSize: _kToolbarHeight,
+            onPressed: _handlePreviousPage,
+            padding: arrowPadding,
+            pressedOpacity: 0.7,
+            child: const Text('◀', style: _kToolbarButtonFontStyle),
+          ),
+          dividerWidth: 1.0 / MediaQuery.of(context).devicePixelRatio,
+          nextButton: CupertinoButton(
+            borderRadius: null,
+            color: _kToolbarBackgroundColor,
+            minSize: _kToolbarHeight,
+            onPressed: _handleNextPage,
+            padding: arrowPadding,
+            pressedOpacity: 0.7,
+            child: const Text('▶', style: _kToolbarButtonFontStyle),
+          ),
+          nextButtonDisabled: CupertinoButton(
+            borderRadius: null,
+            color: _kToolbarBackgroundColor,
+            disabledColor: _kToolbarBackgroundColor,
+            minSize: _kToolbarHeight,
+            onPressed: null,
+            padding: arrowPadding,
+            pressedOpacity: 1.0,
+            child: const Text('▶', style: _kToolbarButtonDisabledFontStyle),
+          ),
+          children: widget.children,
+        ),
+      ),
+    );
+  }
+}
+
+// The custom RenderObjectWidget that, together with
+// _CupertinoTextSelectionToolbarItemsRenderBox and
+// _CupertinoTextSelectionToolbarItemsElement, paginates the menu items.
+class _CupertinoTextSelectionToolbarItems extends RenderObjectWidget {
+  _CupertinoTextSelectionToolbarItems({
+    Key? key,
+    required this.page,
+    required this.children,
+    required this.backButton,
+    required this.dividerWidth,
+    required this.nextButton,
+    required this.nextButtonDisabled,
+  }) : assert(children != null),
+       assert(children.isNotEmpty),
+       assert(backButton != null),
+       assert(dividerWidth != null),
+       assert(nextButton != null),
+       assert(nextButtonDisabled != null),
+       assert(page != null),
+       super(key: key);
+
+  final Widget backButton;
+  final List<Widget> children;
+  final double dividerWidth;
+  final Widget nextButton;
+  final Widget nextButtonDisabled;
+  final int page;
+
+  @override
+  _CupertinoTextSelectionToolbarItemsRenderBox createRenderObject(BuildContext context) {
+    return _CupertinoTextSelectionToolbarItemsRenderBox(
+      dividerWidth: dividerWidth,
+      page: page,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _CupertinoTextSelectionToolbarItemsRenderBox renderObject) {
+    renderObject
+      ..page = page
+      ..dividerWidth = dividerWidth;
+  }
+
+  @override
+  _CupertinoTextSelectionToolbarItemsElement createElement() => _CupertinoTextSelectionToolbarItemsElement(this);
+}
+
+// The custom RenderObjectElement that helps paginate the menu items.
+class _CupertinoTextSelectionToolbarItemsElement extends RenderObjectElement {
+  _CupertinoTextSelectionToolbarItemsElement(
+    _CupertinoTextSelectionToolbarItems widget,
+  ) : super(widget);
+
+  late List<Element> _children;
+  final Map<_CupertinoTextSelectionToolbarItemsSlot, Element> slotToChild = <_CupertinoTextSelectionToolbarItemsSlot, Element>{};
+
+  // We keep a set of forgotten children to avoid O(n^2) work walking _children
+  // repeatedly to remove children.
+  final Set<Element> _forgottenChildren = HashSet<Element>();
+
+  @override
+  _CupertinoTextSelectionToolbarItems get widget => super.widget as _CupertinoTextSelectionToolbarItems;
+
+  @override
+  _CupertinoTextSelectionToolbarItemsRenderBox get renderObject => super.renderObject as _CupertinoTextSelectionToolbarItemsRenderBox;
+
+  void _updateRenderObject(RenderBox? child, _CupertinoTextSelectionToolbarItemsSlot slot) {
+    switch (slot) {
+      case _CupertinoTextSelectionToolbarItemsSlot.backButton:
+        renderObject.backButton = child;
+        break;
+      case _CupertinoTextSelectionToolbarItemsSlot.nextButton:
+        renderObject.nextButton = child;
+        break;
+      case _CupertinoTextSelectionToolbarItemsSlot.nextButtonDisabled:
+        renderObject.nextButtonDisabled = child;
+        break;
+    }
+  }
+
+  @override
+  void insertRenderObjectChild(RenderObject child, dynamic slot) {
+    if (slot is _CupertinoTextSelectionToolbarItemsSlot) {
+      assert(child is RenderBox);
+      _updateRenderObject(child as RenderBox, slot);
+      assert(renderObject.slottedChildren.containsKey(slot));
+      return;
+    }
+    if (slot is IndexedSlot) {
+      assert(renderObject.debugValidateChild(child));
+      renderObject.insert(child as RenderBox, after: slot.value?.renderObject as RenderBox?);
+      return;
+    }
+    assert(false, 'slot must be _CupertinoTextSelectionToolbarItemsSlot or IndexedSlot');
+  }
+
+  // This is not reachable for children that don't have an IndexedSlot.
+  @override
+  void moveRenderObjectChild(RenderObject child, IndexedSlot<Element> oldSlot, IndexedSlot<Element> newSlot) {
+    assert(child.parent == renderObject);
+    renderObject.move(child as RenderBox, after: newSlot.value.renderObject as RenderBox?);
+  }
+
+  static bool _shouldPaint(Element child) {
+    return (child.renderObject!.parentData! as ToolbarItemsParentData).shouldPaint;
+  }
+
+  @override
+  void removeRenderObjectChild(RenderObject child, dynamic slot) {
+    // Check if the child is in a slot.
+    if (slot is _CupertinoTextSelectionToolbarItemsSlot) {
+      assert(child is RenderBox);
+      assert(renderObject.slottedChildren.containsKey(slot));
+      _updateRenderObject(null, slot);
+      assert(!renderObject.slottedChildren.containsKey(slot));
+      return;
+    }
+
+    // Otherwise look for it in the list of children.
+    assert(slot is IndexedSlot);
+    assert(child.parent == renderObject);
+    renderObject.remove(child as RenderBox);
+  }
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    slotToChild.values.forEach(visitor);
+    for (final Element child in _children) {
+      if (!_forgottenChildren.contains(child))
+        visitor(child);
+    }
+  }
+
+  @override
+  void forgetChild(Element child) {
+    assert(slotToChild.containsValue(child) || _children.contains(child));
+    assert(!_forgottenChildren.contains(child));
+    // Handle forgetting a child in children or in a slot.
+    if (slotToChild.containsKey(child.slot)) {
+      final _CupertinoTextSelectionToolbarItemsSlot slot = child.slot as _CupertinoTextSelectionToolbarItemsSlot;
+      slotToChild.remove(slot);
+    } else {
+      _forgottenChildren.add(child);
+    }
+    super.forgetChild(child);
+  }
+
+  // Mount or update slotted child.
+  void _mountChild(Widget widget, _CupertinoTextSelectionToolbarItemsSlot slot) {
+    final Element? oldChild = slotToChild[slot];
+    final Element? newChild = updateChild(oldChild, widget, slot);
+    if (oldChild != null) {
+      slotToChild.remove(slot);
+    }
+    if (newChild != null) {
+      slotToChild[slot] = newChild;
+    }
+  }
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    // Mount slotted children.
+    _mountChild(widget.backButton, _CupertinoTextSelectionToolbarItemsSlot.backButton);
+    _mountChild(widget.nextButton, _CupertinoTextSelectionToolbarItemsSlot.nextButton);
+    _mountChild(widget.nextButtonDisabled, _CupertinoTextSelectionToolbarItemsSlot.nextButtonDisabled);
+
+    // Mount list children.
+    _children = List<Element>.filled(widget.children.length, _NullElement.instance);
+    Element? previousChild;
+    for (int i = 0; i < _children.length; i += 1) {
+      final Element newChild = inflateWidget(widget.children[i], IndexedSlot<Element?>(i, previousChild));
+      _children[i] = newChild;
+      previousChild = newChild;
+    }
+  }
+
+  @override
+  void debugVisitOnstageChildren(ElementVisitor visitor) {
+    // Visit slot children.
+    for (final Element child in slotToChild.values) {
+      if (_shouldPaint(child) && !_forgottenChildren.contains(child)) {
+        visitor(child);
+      }
+    }
+    // Visit list children.
+    _children
+        .where((Element child) => !_forgottenChildren.contains(child) && _shouldPaint(child))
+        .forEach(visitor);
+  }
+
+  @override
+  void update(_CupertinoTextSelectionToolbarItems newWidget) {
+    super.update(newWidget);
+    assert(widget == newWidget);
+
+    // Update slotted children.
+    _mountChild(widget.backButton, _CupertinoTextSelectionToolbarItemsSlot.backButton);
+    _mountChild(widget.nextButton, _CupertinoTextSelectionToolbarItemsSlot.nextButton);
+    _mountChild(widget.nextButtonDisabled, _CupertinoTextSelectionToolbarItemsSlot.nextButtonDisabled);
+
+    // Update list children.
+    _children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
+    _forgottenChildren.clear();
+  }
+}
+
+// The custom RenderBox that helps paginate the menu items.
+class _CupertinoTextSelectionToolbarItemsRenderBox extends RenderBox with ContainerRenderObjectMixin<RenderBox, ToolbarItemsParentData>, RenderBoxContainerDefaultsMixin<RenderBox, ToolbarItemsParentData> {
+  _CupertinoTextSelectionToolbarItemsRenderBox({
+    required double dividerWidth,
+    required int page,
+  }) : assert(dividerWidth != null),
+       assert(page != null),
+       _dividerWidth = dividerWidth,
+       _page = page,
+       super();
+
+  final Map<_CupertinoTextSelectionToolbarItemsSlot, RenderBox> slottedChildren = <_CupertinoTextSelectionToolbarItemsSlot, RenderBox>{};
+
+  RenderBox? _updateChild(RenderBox? oldChild, RenderBox? newChild, _CupertinoTextSelectionToolbarItemsSlot slot) {
+    if (oldChild != null) {
+      dropChild(oldChild);
+      slottedChildren.remove(slot);
+    }
+    if (newChild != null) {
+      slottedChildren[slot] = newChild;
+      adoptChild(newChild);
+    }
+    return newChild;
+  }
+
+  bool _isSlottedChild(RenderBox child) {
+    return child == _backButton || child == _nextButton || child == _nextButtonDisabled;
+  }
+
+  int _page;
+  int get page => _page;
+  set page(int value) {
+    if (value == _page) {
+      return;
+    }
+    _page = value;
+    markNeedsLayout();
+  }
+
+  double _dividerWidth;
+  double get dividerWidth => _dividerWidth;
+  set dividerWidth(double value) {
+    if (value == _dividerWidth) {
+      return;
+    }
+    _dividerWidth = value;
+    markNeedsLayout();
+  }
+
+  RenderBox? _backButton;
+  RenderBox? get backButton => _backButton;
+  set backButton(RenderBox? value) {
+    _backButton = _updateChild(_backButton, value, _CupertinoTextSelectionToolbarItemsSlot.backButton);
+  }
+
+  RenderBox? _nextButton;
+  RenderBox? get nextButton => _nextButton;
+  set nextButton(RenderBox? value) {
+    _nextButton = _updateChild(_nextButton, value, _CupertinoTextSelectionToolbarItemsSlot.nextButton);
+  }
+
+  RenderBox? _nextButtonDisabled;
+  RenderBox? get nextButtonDisabled => _nextButtonDisabled;
+  set nextButtonDisabled(RenderBox? value) {
+    _nextButtonDisabled = _updateChild(_nextButtonDisabled, value, _CupertinoTextSelectionToolbarItemsSlot.nextButtonDisabled);
+  }
+
+  @override
+  void performLayout() {
+    if (firstChild == null) {
+      size = constraints.smallest;
+      return;
+    }
+
+    // Layout slotted children.
+    _backButton!.layout(constraints.loosen(), parentUsesSize: true);
+    _nextButton!.layout(constraints.loosen(), parentUsesSize: true);
+    _nextButtonDisabled!.layout(constraints.loosen(), parentUsesSize: true);
+
+    final double subsequentPageButtonsWidth =
+        _backButton!.size.width + _nextButton!.size.width;
+    double currentButtonPosition = 0.0;
+    late double toolbarWidth; // The width of the whole widget.
+    late double firstPageWidth;
+    int currentPage = 0;
+    int i = -1;
+    visitChildren((RenderObject renderObjectChild) {
+      i++;
+      final RenderBox child = renderObjectChild as RenderBox;
+      final ToolbarItemsParentData childParentData =
+          child.parentData! as ToolbarItemsParentData;
+      childParentData.shouldPaint = false;
+
+      // Skip slotted children and children on pages after the visible page.
+      if (_isSlottedChild(child) || currentPage > _page) {
+        return;
+      }
+
+      double paginationButtonsWidth = 0.0;
+      if (currentPage == 0) {
+        // If this is the last child, it's ok to fit without a forward button.
+        paginationButtonsWidth =
+            i == childCount - 1 ? 0.0 : _nextButton!.size.width;
+      } else {
+        paginationButtonsWidth = subsequentPageButtonsWidth;
+      }
+
+      // The width of the menu is set by the first page.
+      child.layout(
+        BoxConstraints.loose(Size(
+          (currentPage == 0 ? constraints.maxWidth : firstPageWidth) - paginationButtonsWidth,
+          constraints.maxHeight,
+        )),
+        parentUsesSize: true,
+      );
+
+      // If this child causes the current page to overflow, move to the next
+      // page and relayout the child.
+      final double currentWidth =
+          currentButtonPosition + paginationButtonsWidth + child.size.width;
+      if (currentWidth > constraints.maxWidth) {
+        currentPage++;
+        currentButtonPosition = _backButton!.size.width + dividerWidth;
+        paginationButtonsWidth = _backButton!.size.width + _nextButton!.size.width;
+        child.layout(
+          BoxConstraints.loose(Size(
+            firstPageWidth - paginationButtonsWidth,
+            constraints.maxHeight,
+          )),
+          parentUsesSize: true,
+        );
+      }
+      childParentData.offset = Offset(currentButtonPosition, 0.0);
+      currentButtonPosition += child.size.width + dividerWidth;
+      childParentData.shouldPaint = currentPage == page;
+
+      if (currentPage == 0) {
+        firstPageWidth = currentButtonPosition + _nextButton!.size.width;
+      }
+      if (currentPage == page) {
+        toolbarWidth = currentButtonPosition;
+      }
+    });
+
+    // It shouldn't be possible to navigate beyond the last page.
+    assert(page <= currentPage);
+
+    // Position page nav buttons.
+    if (currentPage > 0) {
+      final ToolbarItemsParentData nextButtonParentData =
+          _nextButton!.parentData! as ToolbarItemsParentData;
+      final ToolbarItemsParentData nextButtonDisabledParentData =
+          _nextButtonDisabled!.parentData! as ToolbarItemsParentData;
+      final ToolbarItemsParentData backButtonParentData =
+          _backButton!.parentData! as ToolbarItemsParentData;
+      // The forward button always shows if there is more than one page, even on
+      // the last page (it's just disabled).
+      if (page == currentPage) {
+        nextButtonDisabledParentData.offset = Offset(toolbarWidth, 0.0);
+        nextButtonDisabledParentData.shouldPaint = true;
+        toolbarWidth += nextButtonDisabled!.size.width;
+      } else {
+        nextButtonParentData.offset = Offset(toolbarWidth, 0.0);
+        nextButtonParentData.shouldPaint = true;
+        toolbarWidth += nextButton!.size.width;
+      }
+      if (page > 0) {
+        backButtonParentData.offset = Offset.zero;
+        backButtonParentData.shouldPaint = true;
+        // No need to add the width of the back button to toolbarWidth here. It's
+        // already been taken care of when laying out the children to
+        // accommodate the back button.
+      }
+    } else {
+      // No divider for the next button when there's only one page.
+      toolbarWidth -= dividerWidth;
+    }
+
+    size = constraints.constrain(Size(toolbarWidth, _kToolbarHeight));
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    visitChildren((RenderObject renderObjectChild) {
+      final RenderBox child = renderObjectChild as RenderBox;
+      final ToolbarItemsParentData childParentData = child.parentData! as ToolbarItemsParentData;
+
+      if (childParentData.shouldPaint) {
+        final Offset childOffset = childParentData.offset + offset;
+        context.paintChild(child, childOffset);
+      }
+    });
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! ToolbarItemsParentData) {
+      child.parentData = ToolbarItemsParentData();
+    }
+  }
+
+  // Returns true iff the single child is hit by the given position.
+  static bool hitTestChild(RenderBox? child, BoxHitTestResult result, { required Offset position }) {
+    if (child == null) {
+      return false;
+    }
+    final ToolbarItemsParentData childParentData =
+        child.parentData! as ToolbarItemsParentData;
+    return result.addWithPaintOffset(
+      offset: childParentData.offset,
+      position: position,
+      hitTest: (BoxHitTestResult result, Offset transformed) {
+        assert(transformed == position - childParentData.offset);
+        return child.hitTest(result, position: transformed);
+      },
+    );
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    // Hit test list children.
+    // The x, y parameters have the top left of the node's box as the origin.
+    RenderBox? child = lastChild;
+    while (child != null) {
+      final ToolbarItemsParentData childParentData = child.parentData! as ToolbarItemsParentData;
+
+      // Don't hit test children that aren't shown.
+      if (!childParentData.shouldPaint) {
+        child = childParentData.previousSibling;
+        continue;
+      }
+
+      if (hitTestChild(child, result, position: position)) {
+        return true;
+      }
+      child = childParentData.previousSibling;
+    }
+
+    // Hit test slot children.
+    if (hitTestChild(backButton, result, position: position)) {
+      return true;
+    }
+    if (hitTestChild(nextButton, result, position: position)) {
+      return true;
+    }
+    if (hitTestChild(nextButtonDisabled, result, position: position)) {
+      return true;
+    }
+
+    return false;
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    // Attach list children.
+    super.attach(owner);
+
+    // Attach slot children.
+    for (final RenderBox child in slottedChildren.values) {
+      child.attach(owner);
+    }
+  }
+
+  @override
+  void detach() {
+    // Detach list children.
+    super.detach();
+
+    // Detach slot children.
+    for (final RenderBox child in slottedChildren.values) {
+      child.detach();
+    }
+  }
+
+  @override
+  void redepthChildren() {
+    visitChildren((RenderObject renderObjectChild) {
+      final RenderBox child = renderObjectChild as RenderBox;
+      redepthChild(child);
+    });
+  }
+
+  @override
+  void visitChildren(RenderObjectVisitor visitor) {
+    // Visit the slotted children.
+    if (_backButton != null) {
+      visitor(_backButton!);
+    }
+    if (_nextButton != null) {
+      visitor(_nextButton!);
+    }
+    if (_nextButtonDisabled != null) {
+      visitor(_nextButtonDisabled!);
+    }
+    // Visit the list children.
+    super.visitChildren(visitor);
+  }
+
+  // Visit only the children that should be painted.
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    visitChildren((RenderObject renderObjectChild) {
+      final RenderBox child = renderObjectChild as RenderBox;
+      final ToolbarItemsParentData childParentData = child.parentData! as ToolbarItemsParentData;
+      if (childParentData.shouldPaint) {
+        visitor(renderObjectChild);
+      }
+    });
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    final List<DiagnosticsNode> value = <DiagnosticsNode>[];
+    visitChildren((RenderObject renderObjectChild) {
+      final RenderBox child = renderObjectChild as RenderBox;
+      if (child == backButton) {
+        value.add(child.toDiagnosticsNode(name: 'back button'));
+      } else if (child == nextButton) {
+        value.add(child.toDiagnosticsNode(name: 'next button'));
+      } else if (child == nextButtonDisabled) {
+        value.add(child.toDiagnosticsNode(name: 'next button disabled'));
+
+      // List children.
+      } else {
+        value.add(child.toDiagnosticsNode(name: 'menu item'));
+      }
+    });
+    return value;
+  }
+}
+
+// The slots that can be occupied by widgets in
+// _CupertinoTextSelectionToolbarItems, excluding the list of children.
+enum _CupertinoTextSelectionToolbarItemsSlot {
+  backButton,
+  nextButton,
+  nextButtonDisabled,
+}
+
+/// Text selection controls that follows iOS design conventions.
+final TextSelectionControls cupertinoTextSelectionControls = _CupertinoTextSelectionControls();
+
+class _NullElement extends Element {
+  _NullElement() : super(_NullWidget());
+
+  static _NullElement instance = _NullElement();
+
+  @override
+  bool get debugDoingBuild => throw UnimplementedError();
+
+  @override
+  void performRebuild() { }
+}
+
+class _NullWidget extends Widget {
+  @override
+  Element createElement() => throw UnimplementedError();
+}
diff --git a/lib/src/cupertino/text_theme.dart b/lib/src/cupertino/text_theme.dart
new file mode 100644
index 0000000..16e39f7
--- /dev/null
+++ b/lib/src/cupertino/text_theme.dart
@@ -0,0 +1,300 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/services.dart' show Brightness;
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+
+// Please update _TextThemeDefaultsBuilder accordingly after changing the default
+// color here, as their implementation depends on the default value of the color
+// field.
+//
+// Values derived from https://developer.apple.com/design/resources/.
+const TextStyle _kDefaultTextStyle = TextStyle(
+  inherit: false,
+  fontFamily: '.SF Pro Text',
+  fontSize: 17.0,
+  letterSpacing: -0.41,
+  color: CupertinoColors.label,
+  decoration: TextDecoration.none,
+);
+
+// Please update _TextThemeDefaultsBuilder accordingly after changing the default
+// color here, as their implementation depends on the default value of the color
+// field.
+//
+// Values derived from https://developer.apple.com/design/resources/.
+const TextStyle _kDefaultActionTextStyle = TextStyle(
+  inherit: false,
+  fontFamily: '.SF Pro Text',
+  fontSize: 17.0,
+  letterSpacing: -0.41,
+  color: CupertinoColors.activeBlue,
+  decoration: TextDecoration.none,
+);
+
+// Please update _TextThemeDefaultsBuilder accordingly after changing the default
+// color here, as their implementation depends on the default value of the color
+// field.
+//
+// Values derived from https://developer.apple.com/design/resources/.
+const TextStyle _kDefaultTabLabelTextStyle = TextStyle(
+  inherit: false,
+  fontFamily: '.SF Pro Text',
+  fontSize: 10.0,
+  letterSpacing: -0.24,
+  color: CupertinoColors.inactiveGray,
+);
+
+const TextStyle _kDefaultMiddleTitleTextStyle = TextStyle(
+  inherit: false,
+  fontFamily: '.SF Pro Text',
+  fontSize: 17.0,
+  fontWeight: FontWeight.w600,
+  letterSpacing: -0.41,
+  color: CupertinoColors.label,
+);
+
+const TextStyle _kDefaultLargeTitleTextStyle = TextStyle(
+  inherit: false,
+  fontFamily: '.SF Pro Display',
+  fontSize: 34.0,
+  fontWeight: FontWeight.w700,
+  letterSpacing: 0.41,
+  color: CupertinoColors.label,
+);
+
+// Please update _TextThemeDefaultsBuilder accordingly after changing the default
+// color here, as their implementation depends on the default value of the color
+// field.
+//
+// Inspected on iOS 13 simulator with "Debug View Hierarchy".
+// Value extracted from off-center labels. Centered labels have a font size of 25pt.
+//
+// The letterSpacing sourced from iOS 14 simulator screenshots for comparison.
+// See also:
+//
+// * https://github.com/flutter/flutter/pull/65501#discussion_r486557093
+const TextStyle _kDefaultPickerTextStyle = TextStyle(
+  inherit: false,
+  fontFamily: '.SF Pro Display',
+  fontSize: 21.0,
+  fontWeight: FontWeight.w400,
+  letterSpacing: -0.6,
+  color: CupertinoColors.label,
+);
+
+// Please update _TextThemeDefaultsBuilder accordingly after changing the default
+// color here, as their implementation depends on the default value of the color
+// field.
+//
+// Inspected on iOS 13 simulator with "Debug View Hierarchy".
+// Value extracted from off-center labels. Centered labels have a font size of 25pt.
+const TextStyle _kDefaultDateTimePickerTextStyle = TextStyle(
+  inherit: false,
+  fontFamily: '.SF Pro Display',
+  fontSize: 21,
+  fontWeight: FontWeight.normal,
+  color: CupertinoColors.label,
+);
+
+TextStyle? _resolveTextStyle(TextStyle? style, BuildContext context) {
+  // This does not resolve the shadow color, foreground, background, etc.
+  return style?.copyWith(
+    color: CupertinoDynamicColor.maybeResolve(style.color, context),
+    backgroundColor: CupertinoDynamicColor.maybeResolve(style.backgroundColor, context),
+    decorationColor: CupertinoDynamicColor.maybeResolve(style.decorationColor, context),
+  );
+}
+
+/// Cupertino typography theme in a [CupertinoThemeData].
+@immutable
+class CupertinoTextThemeData with Diagnosticable {
+  /// Create a [CupertinoTextThemeData].
+  ///
+  /// The [primaryColor] is used to derive TextStyle defaults of other attributes
+  /// such as [navActionTextStyle] and [actionTextStyle], it must not be null when
+  /// either [navActionTextStyle] or [actionTextStyle] is null. Defaults to
+  /// [CupertinoColors.systemBlue].
+  ///
+  /// Other [TextStyle] parameters default to default iOS text styles when
+  /// unspecified.
+  const CupertinoTextThemeData({
+    Color primaryColor = CupertinoColors.systemBlue,
+    TextStyle? textStyle,
+    TextStyle? actionTextStyle,
+    TextStyle? tabLabelTextStyle,
+    TextStyle? navTitleTextStyle,
+    TextStyle? navLargeTitleTextStyle,
+    TextStyle? navActionTextStyle,
+    TextStyle? pickerTextStyle,
+    TextStyle? dateTimePickerTextStyle,
+  }) : this._raw(
+         const _TextThemeDefaultsBuilder(CupertinoColors.label, CupertinoColors.inactiveGray),
+         primaryColor,
+         textStyle,
+         actionTextStyle,
+         tabLabelTextStyle,
+         navTitleTextStyle,
+         navLargeTitleTextStyle,
+         navActionTextStyle,
+         pickerTextStyle,
+         dateTimePickerTextStyle,
+       );
+
+  const CupertinoTextThemeData._raw(
+    this._defaults,
+    this._primaryColor,
+    this._textStyle,
+    this._actionTextStyle,
+    this._tabLabelTextStyle,
+    this._navTitleTextStyle,
+    this._navLargeTitleTextStyle,
+    this._navActionTextStyle,
+    this._pickerTextStyle,
+    this._dateTimePickerTextStyle,
+  ) : assert((_navActionTextStyle != null && _actionTextStyle != null) || _primaryColor != null);
+
+  final _TextThemeDefaultsBuilder _defaults;
+  final Color? _primaryColor;
+
+  final TextStyle? _textStyle;
+  /// The [TextStyle] of general text content for Cupertino widgets.
+  TextStyle get textStyle => _textStyle ?? _defaults.textStyle;
+
+  final TextStyle? _actionTextStyle;
+  /// The [TextStyle] of interactive text content such as text in a button without background.
+  TextStyle get actionTextStyle {
+    return _actionTextStyle ?? _defaults.actionTextStyle(primaryColor: _primaryColor);
+  }
+
+  final TextStyle? _tabLabelTextStyle;
+  /// The [TextStyle] of unselected tabs.
+  TextStyle get tabLabelTextStyle => _tabLabelTextStyle ?? _defaults.tabLabelTextStyle;
+
+  final TextStyle? _navTitleTextStyle;
+  /// The [TextStyle] of titles in standard navigation bars.
+  TextStyle get navTitleTextStyle => _navTitleTextStyle ?? _defaults.navTitleTextStyle;
+
+  final TextStyle? _navLargeTitleTextStyle;
+  /// The [TextStyle] of large titles in sliver navigation bars.
+  TextStyle get navLargeTitleTextStyle => _navLargeTitleTextStyle ?? _defaults.navLargeTitleTextStyle;
+
+  final TextStyle? _navActionTextStyle;
+  /// The [TextStyle] of interactive text content in navigation bars.
+  TextStyle get navActionTextStyle {
+    return _navActionTextStyle ?? _defaults.navActionTextStyle(primaryColor: _primaryColor);
+  }
+
+  final TextStyle? _pickerTextStyle;
+  /// The [TextStyle] of pickers.
+  TextStyle get pickerTextStyle => _pickerTextStyle ?? _defaults.pickerTextStyle;
+
+  final TextStyle? _dateTimePickerTextStyle;
+  /// The [TextStyle] of date time pickers.
+  TextStyle get dateTimePickerTextStyle => _dateTimePickerTextStyle ?? _defaults.dateTimePickerTextStyle;
+
+  /// Returns a copy of the current [CupertinoTextThemeData] with all the colors
+  /// resolved against the given [BuildContext].
+  ///
+  /// If any of the [InheritedWidget]s required to resolve this
+  /// [CupertinoTextThemeData] is not found in [context], any unresolved
+  /// [CupertinoDynamicColor]s will use the default trait value
+  /// ([Brightness.light] platform brightness, normal contrast,
+  /// [CupertinoUserInterfaceLevelData.base] elevation level).
+  CupertinoTextThemeData resolveFrom(BuildContext context) {
+    return CupertinoTextThemeData._raw(
+      _defaults.resolveFrom(context),
+      CupertinoDynamicColor.maybeResolve(_primaryColor, context),
+      _resolveTextStyle(_textStyle, context),
+      _resolveTextStyle(_actionTextStyle, context),
+      _resolveTextStyle(_tabLabelTextStyle, context),
+      _resolveTextStyle(_navTitleTextStyle, context),
+      _resolveTextStyle(_navLargeTitleTextStyle, context),
+      _resolveTextStyle(_navActionTextStyle, context),
+      _resolveTextStyle(_pickerTextStyle, context),
+      _resolveTextStyle(_dateTimePickerTextStyle, context),
+    );
+  }
+
+  /// Returns a copy of the current [CupertinoTextThemeData] instance with
+  /// specified overrides.
+  CupertinoTextThemeData copyWith({
+    Color? primaryColor,
+    TextStyle? textStyle,
+    TextStyle? actionTextStyle,
+    TextStyle? tabLabelTextStyle,
+    TextStyle? navTitleTextStyle,
+    TextStyle? navLargeTitleTextStyle,
+    TextStyle? navActionTextStyle,
+    TextStyle? pickerTextStyle,
+    TextStyle? dateTimePickerTextStyle,
+  }) {
+    return CupertinoTextThemeData._raw(
+      _defaults,
+      primaryColor ?? _primaryColor,
+      textStyle ?? _textStyle,
+      actionTextStyle ?? _actionTextStyle,
+      tabLabelTextStyle ?? _tabLabelTextStyle,
+      navTitleTextStyle ?? _navTitleTextStyle,
+      navLargeTitleTextStyle ?? _navLargeTitleTextStyle,
+      navActionTextStyle ?? _navActionTextStyle,
+      pickerTextStyle ?? _pickerTextStyle,
+      dateTimePickerTextStyle ?? _dateTimePickerTextStyle,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    const CupertinoTextThemeData defaultData = CupertinoTextThemeData();
+    properties.add(DiagnosticsProperty<TextStyle>('textStyle', textStyle, defaultValue: defaultData.textStyle));
+    properties.add(DiagnosticsProperty<TextStyle>('actionTextStyle', actionTextStyle, defaultValue: defaultData.actionTextStyle));
+    properties.add(DiagnosticsProperty<TextStyle>('tabLabelTextStyle', tabLabelTextStyle, defaultValue: defaultData.tabLabelTextStyle));
+    properties.add(DiagnosticsProperty<TextStyle>('navTitleTextStyle', navTitleTextStyle, defaultValue: defaultData.navTitleTextStyle));
+    properties.add(DiagnosticsProperty<TextStyle>('navLargeTitleTextStyle', navLargeTitleTextStyle, defaultValue: defaultData.navLargeTitleTextStyle));
+    properties.add(DiagnosticsProperty<TextStyle>('navActionTextStyle', navActionTextStyle, defaultValue: defaultData.navActionTextStyle));
+    properties.add(DiagnosticsProperty<TextStyle>('pickerTextStyle', pickerTextStyle, defaultValue: defaultData.pickerTextStyle));
+    properties.add(DiagnosticsProperty<TextStyle>('dateTimePickerTextStyle', dateTimePickerTextStyle, defaultValue: defaultData.dateTimePickerTextStyle));
+  }
+}
+
+
+@immutable
+class _TextThemeDefaultsBuilder {
+  const _TextThemeDefaultsBuilder(
+    this.labelColor,
+    this.inactiveGrayColor,
+  ) : assert(labelColor != null),
+      assert(inactiveGrayColor != null);
+
+  final Color labelColor;
+  final Color inactiveGrayColor;
+
+  static TextStyle _applyLabelColor(TextStyle original, Color color) {
+    return original.color == color
+      ?  original
+      :  original.copyWith(color: color);
+  }
+
+  TextStyle get textStyle => _applyLabelColor(_kDefaultTextStyle, labelColor);
+  TextStyle get tabLabelTextStyle => _applyLabelColor(_kDefaultTabLabelTextStyle, inactiveGrayColor);
+  TextStyle get navTitleTextStyle => _applyLabelColor(_kDefaultMiddleTitleTextStyle, labelColor);
+  TextStyle get navLargeTitleTextStyle => _applyLabelColor(_kDefaultLargeTitleTextStyle, labelColor);
+  TextStyle get pickerTextStyle => _applyLabelColor(_kDefaultPickerTextStyle, labelColor);
+  TextStyle get dateTimePickerTextStyle => _applyLabelColor(_kDefaultDateTimePickerTextStyle, labelColor);
+
+  TextStyle actionTextStyle({ Color? primaryColor }) => _kDefaultActionTextStyle.copyWith(color: primaryColor);
+  TextStyle navActionTextStyle({ Color? primaryColor }) => actionTextStyle(primaryColor: primaryColor);
+
+  _TextThemeDefaultsBuilder resolveFrom(BuildContext context) {
+    final Color resolvedLabelColor = CupertinoDynamicColor.resolve(labelColor, context);
+    final Color resolvedInactiveGray = CupertinoDynamicColor.resolve(inactiveGrayColor, context);
+    return resolvedLabelColor == labelColor && resolvedInactiveGray == CupertinoColors.inactiveGray
+      ? this
+      : _TextThemeDefaultsBuilder(resolvedLabelColor, resolvedInactiveGray);
+  }
+}
diff --git a/lib/src/cupertino/theme.dart b/lib/src/cupertino/theme.dart
new file mode 100644
index 0000000..39a2e99
--- /dev/null
+++ b/lib/src/cupertino/theme.dart
@@ -0,0 +1,539 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'icon_theme_data.dart';
+import 'text_theme.dart';
+
+export 'package:flute/services.dart' show Brightness;
+
+// Values derived from https://developer.apple.com/design/resources/.
+const _CupertinoThemeDefaults _kDefaultTheme = _CupertinoThemeDefaults(
+  null,
+  CupertinoColors.systemBlue,
+  CupertinoColors.systemBackground,
+  CupertinoDynamicColor.withBrightness(
+    color: Color(0xF0F9F9F9),
+    darkColor: Color(0xF01D1D1D),
+    // Values extracted from navigation bar. For toolbar or tabbar the dark color is 0xF0161616.
+  ),
+  CupertinoColors.systemBackground,
+  _CupertinoTextThemeDefaults(CupertinoColors.label, CupertinoColors.inactiveGray),
+);
+
+/// Applies a visual styling theme to descendant Cupertino widgets.
+///
+/// Affects the color and text styles of Cupertino widgets whose styling
+/// are not overridden when constructing the respective widgets instances.
+///
+/// Descendant widgets can retrieve the current [CupertinoThemeData] by calling
+/// [CupertinoTheme.of]. An [InheritedWidget] dependency is created when
+/// an ancestor [CupertinoThemeData] is retrieved via [CupertinoTheme.of].
+///
+/// The [CupertinoTheme] widget implies an [IconTheme] widget, whose
+/// [IconTheme.data] has the same color as [CupertinoThemeData.primaryColor]
+///
+/// See also:
+///
+///  * [CupertinoThemeData], specifies the theme's visual styling.
+///  * [CupertinoApp], which will automatically add a [CupertinoTheme] based on the
+///    value of [CupertinoApp.theme].
+///  * [Theme], a Material theme which will automatically add a [CupertinoTheme]
+///    with a [CupertinoThemeData] derived from the Material [ThemeData].
+class CupertinoTheme extends StatelessWidget {
+  /// Creates a [CupertinoTheme] to change descendant Cupertino widgets' styling.
+  ///
+  /// The [data] and [child] parameters must not be null.
+  const CupertinoTheme({
+    Key? key,
+    required this.data,
+    required this.child,
+  }) : assert(child != null),
+       assert(data != null),
+       super(key: key);
+
+  /// The [CupertinoThemeData] styling for this theme.
+  final CupertinoThemeData data;
+
+  /// Retrieves the [CupertinoThemeData] from the closest ancestor [CupertinoTheme]
+  /// widget, or a default [CupertinoThemeData] if no [CupertinoTheme] ancestor
+  /// exists.
+  ///
+  /// Resolves all the colors defined in that [CupertinoThemeData] against the
+  /// given [BuildContext] on a best-effort basis.
+  static CupertinoThemeData of(BuildContext context) {
+    final _InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedCupertinoTheme>();
+    return (inheritedTheme?.theme.data ?? const CupertinoThemeData()).resolveFrom(context);
+  }
+
+  /// Retrieves the [Brightness] to use for descendant Cupertino widgets, based
+  /// on the value of [CupertinoThemeData.brightness] in the given [context].
+  ///
+  /// If no [CupertinoTheme] can be found in the given [context], or its `brightness`
+  /// is null, it will fall back to [MediaQueryData.platformBrightness].
+  ///
+  /// Throws an exception if no valid [CupertinoTheme] or [MediaQuery] widgets
+  /// exist in the ancestry tree.
+  ///
+  /// See also:
+  ///
+  /// * [maybeBrightnessOf], which returns null if no valid [CupertinoTheme] or
+  ///   [MediaQuery] exists, instead of throwing.
+  /// * [CupertinoThemeData.brightness], the property takes precedence over
+  ///   [MediaQueryData.platformBrightness] for descendant Cupertino widgets.
+  static Brightness brightnessOf(BuildContext context) {
+    final _InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedCupertinoTheme>();
+    return inheritedTheme?.theme.data.brightness ?? MediaQuery.of(context).platformBrightness;
+  }
+
+  /// Retrieves the [Brightness] to use for descendant Cupertino widgets, based
+  /// on the value of [CupertinoThemeData.brightness] in the given [context].
+  ///
+  /// If no [CupertinoTheme] can be found in the given [context], it will fall
+  /// back to [MediaQueryData.platformBrightness].
+  ///
+  /// Returns null if no valid [CupertinoTheme] or [MediaQuery] widgets exist in
+  /// the ancestry tree.
+  ///
+  /// See also:
+  ///
+  /// * [CupertinoThemeData.brightness], the property takes precedence over
+  ///   [MediaQueryData.platformBrightness] for descendant Cupertino widgets.
+  /// * [brightnessOf], which throws if no valid [CupertinoTheme] or
+  ///   [MediaQuery] exists, instead of returning null.
+  static Brightness? maybeBrightnessOf(BuildContext context) {
+    final _InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedCupertinoTheme>();
+    return inheritedTheme?.theme.data.brightness ?? MediaQuery.maybeOf(context)?.platformBrightness;
+  }
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    return _InheritedCupertinoTheme(
+      theme: this,
+      child: IconTheme(
+        data: CupertinoIconThemeData(color: data.primaryColor),
+        child: child,
+      ),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    data.debugFillProperties(properties);
+  }
+}
+
+class _InheritedCupertinoTheme extends InheritedWidget {
+  const _InheritedCupertinoTheme({
+    Key? key,
+    required this.theme,
+    required Widget child,
+  }) : assert(theme != null),
+       super(key: key, child: child);
+
+  final CupertinoTheme theme;
+
+  @override
+  bool updateShouldNotify(_InheritedCupertinoTheme old) => theme.data != old.theme.data;
+}
+
+/// Styling specifications for a [CupertinoTheme].
+///
+/// All constructor parameters can be null, in which case a
+/// [CupertinoColors.activeBlue] based default iOS theme styling is used.
+///
+/// Parameters can also be partially specified, in which case some parameters
+/// will cascade down to other dependent parameters to create a cohesive
+/// visual effect. For instance, if a [primaryColor] is specified, it would
+/// cascade down to affect some fonts in [textTheme] if [textTheme] is not
+/// specified.
+///
+/// See also:
+///
+///  * [CupertinoTheme], in which this [CupertinoThemeData] is inserted.
+///  * [ThemeData], a Material equivalent that also configures Cupertino
+///    styling via a [CupertinoThemeData] subclass [MaterialBasedCupertinoThemeData].
+@immutable
+class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable {
+  /// Creates a [CupertinoTheme] styling specification.
+  ///
+  /// Unspecified parameters default to a reasonable iOS default style.
+  const CupertinoThemeData({
+    Brightness? brightness,
+    Color? primaryColor,
+    Color? primaryContrastingColor,
+    CupertinoTextThemeData? textTheme,
+    Color? barBackgroundColor,
+    Color? scaffoldBackgroundColor,
+  }) : this.raw(
+        brightness,
+        primaryColor,
+        primaryContrastingColor,
+        textTheme,
+        barBackgroundColor,
+        scaffoldBackgroundColor,
+      );
+
+  /// Same as the default constructor but with positional arguments to avoid
+  /// forgetting any and to specify all arguments.
+  ///
+  /// Used by subclasses to get the superclass's defaulting behaviors.
+  @protected
+  const CupertinoThemeData.raw(
+    Brightness? brightness,
+    Color? primaryColor,
+    Color? primaryContrastingColor,
+    CupertinoTextThemeData? textTheme,
+    Color? barBackgroundColor,
+    Color? scaffoldBackgroundColor,
+  ) : this._rawWithDefaults(
+    brightness,
+    primaryColor,
+    primaryContrastingColor,
+    textTheme,
+    barBackgroundColor,
+    scaffoldBackgroundColor,
+    _kDefaultTheme,
+  );
+
+  const CupertinoThemeData._rawWithDefaults(
+    Brightness? brightness,
+    Color? primaryColor,
+    Color? primaryContrastingColor,
+    CupertinoTextThemeData? textTheme,
+    Color? barBackgroundColor,
+    Color? scaffoldBackgroundColor,
+    this._defaults,
+  ) : super(
+    brightness: brightness,
+    primaryColor: primaryColor,
+    primaryContrastingColor: primaryContrastingColor,
+    textTheme: textTheme,
+    barBackgroundColor: barBackgroundColor,
+    scaffoldBackgroundColor: scaffoldBackgroundColor,
+  );
+
+  final _CupertinoThemeDefaults _defaults;
+
+  @override
+  Color get primaryColor => super.primaryColor ?? _defaults.primaryColor;
+
+  @override
+  Color get primaryContrastingColor => super.primaryContrastingColor ?? _defaults.primaryContrastingColor;
+
+  @override
+  CupertinoTextThemeData get textTheme {
+    return super.textTheme ?? _defaults.textThemeDefaults.createDefaults(primaryColor: primaryColor);
+  }
+
+  @override
+  Color get barBackgroundColor => super.barBackgroundColor ?? _defaults.barBackgroundColor;
+
+  @override
+  Color get scaffoldBackgroundColor => super.scaffoldBackgroundColor ?? _defaults.scaffoldBackgroundColor;
+
+  @override
+  NoDefaultCupertinoThemeData noDefault() {
+    return NoDefaultCupertinoThemeData(
+      brightness: super.brightness,
+      primaryColor: super.primaryColor,
+      primaryContrastingColor: super.primaryContrastingColor,
+      textTheme: super.textTheme,
+      barBackgroundColor: super.barBackgroundColor,
+      scaffoldBackgroundColor: super.scaffoldBackgroundColor,
+    );
+  }
+
+  @override
+  CupertinoThemeData resolveFrom(BuildContext context) {
+    Color? convertColor(Color? color) => CupertinoDynamicColor.maybeResolve(color, context);
+
+    return CupertinoThemeData._rawWithDefaults(
+      brightness,
+      convertColor(super.primaryColor),
+      convertColor(super.primaryContrastingColor),
+      super.textTheme?.resolveFrom(context),
+      convertColor(super.barBackgroundColor),
+      convertColor(super.scaffoldBackgroundColor),
+      _defaults.resolveFrom(context, super.textTheme == null),
+    );
+  }
+
+  @override
+  CupertinoThemeData copyWith({
+    Brightness? brightness,
+    Color? primaryColor,
+    Color? primaryContrastingColor,
+    CupertinoTextThemeData? textTheme,
+    Color? barBackgroundColor,
+    Color? scaffoldBackgroundColor,
+  }) {
+    return CupertinoThemeData._rawWithDefaults(
+      brightness ?? super.brightness,
+      primaryColor ?? super.primaryColor,
+      primaryContrastingColor ?? super.primaryContrastingColor,
+      textTheme ?? super.textTheme,
+      barBackgroundColor ?? super.barBackgroundColor,
+      scaffoldBackgroundColor ?? super.scaffoldBackgroundColor,
+      _defaults,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    const CupertinoThemeData defaultData = CupertinoThemeData();
+    properties.add(EnumProperty<Brightness>('brightness', brightness, defaultValue: null));
+    properties.add(createCupertinoColorProperty('primaryColor', primaryColor, defaultValue: defaultData.primaryColor));
+    properties.add(createCupertinoColorProperty('primaryContrastingColor', primaryContrastingColor, defaultValue: defaultData.primaryContrastingColor));
+    properties.add(createCupertinoColorProperty('barBackgroundColor', barBackgroundColor, defaultValue: defaultData.barBackgroundColor));
+    properties.add(createCupertinoColorProperty('scaffoldBackgroundColor', scaffoldBackgroundColor, defaultValue: defaultData.scaffoldBackgroundColor));
+    textTheme.debugFillProperties(properties);
+  }
+}
+
+/// Styling specifications for a cupertino theme without default values for
+/// unspecified properties.
+///
+/// Unlike [CupertinoThemeData] instances of this class do not return default
+/// values for properties that have been left unspecified in the constructor.
+/// Instead, unspecified properties will return null. This is used by
+/// Material's [ThemeData.cupertinoOverrideTheme].
+///
+/// See also:
+///
+///  * [CupertinoThemeData], which uses reasonable default values for
+///    unspecified theme properties.
+class NoDefaultCupertinoThemeData {
+  /// Creates a [NoDefaultCupertinoThemeData] styling specification.
+  ///
+  /// Unspecified properties default to null.
+  const NoDefaultCupertinoThemeData({
+    this.brightness,
+    this.primaryColor,
+    this.primaryContrastingColor,
+    this.textTheme,
+    this.barBackgroundColor,
+    this.scaffoldBackgroundColor,
+  });
+
+  /// The brightness override for Cupertino descendants.
+  ///
+  /// Defaults to null. If a non-null [Brightness] is specified, the value will
+  /// take precedence over the ambient [MediaQueryData.platformBrightness], when
+  /// determining the brightness of descendant Cupertino widgets.
+  ///
+  /// If coming from a Material [Theme] and unspecified, [brightness] will be
+  /// derived from the Material [ThemeData]'s `brightness`.
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialBasedCupertinoThemeData], a [CupertinoThemeData] that defers
+  ///    [brightness] to its Material [Theme] parent if it's unspecified.
+  ///
+  ///  * [CupertinoTheme.brightnessOf], a method used to retrieve the overall
+  ///    [Brightness] from a [BuildContext], for Cupertino widgets.
+  final Brightness? brightness;
+
+  /// A color used on interactive elements of the theme.
+  ///
+  /// This color is generally used on text and icons in buttons and tappable
+  /// elements. Defaults to [CupertinoColors.activeBlue].
+  ///
+  /// If coming from a Material [Theme] and unspecified, [primaryColor] will be
+  /// derived from the Material [ThemeData]'s `colorScheme.primary`. However, in
+  /// iOS styling, the [primaryColor] is more sparsely used than in Material
+  /// Design where the [primaryColor] can appear on non-interactive surfaces like
+  /// the [AppBar] background, [TextField] borders etc.
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialBasedCupertinoThemeData], a [CupertinoThemeData] that defers
+  ///    [primaryColor] to its Material [Theme] parent if it's unspecified.
+  final Color? primaryColor;
+
+  /// A color that must be easy to see when rendered on a [primaryColor] background.
+  ///
+  /// For example, this color is used for a [CupertinoButton]'s text and icons
+  /// when the button's background is [primaryColor].
+  ///
+  /// If coming from a Material [Theme] and unspecified, [primaryContrastingColor]
+  /// will be derived from the Material [ThemeData]'s `colorScheme.onPrimary`.
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialBasedCupertinoThemeData], a [CupertinoThemeData] that defers
+  ///    [primaryContrastingColor] to its Material [Theme] parent if it's unspecified.
+  final Color? primaryContrastingColor;
+
+  /// Text styles used by Cupertino widgets.
+  ///
+  /// Derived from [primaryColor] if unspecified.
+  final CupertinoTextThemeData? textTheme;
+
+  /// Background color of the top nav bar and bottom tab bar.
+  ///
+  /// Defaults to a light gray in light mode, or a dark translucent gray color in
+  /// dark mode.
+  final Color? barBackgroundColor;
+
+  /// Background color of the scaffold.
+  ///
+  /// Defaults to [CupertinoColors.systemBackground].
+  final Color? scaffoldBackgroundColor;
+
+  /// Returns an instance of the theme data whose property getters only return
+  /// the construction time specifications with no derived values.
+  ///
+  /// Used in Material themes to let unspecified properties fallback to Material
+  /// theme properties instead of iOS defaults.
+  NoDefaultCupertinoThemeData noDefault() => this;
+
+  /// Returns a new theme data with all its colors resolved against the
+  /// given [BuildContext].
+  ///
+  /// Called by [CupertinoTheme.of] to resolve colors defined in the retrieved
+  /// [CupertinoThemeData].
+  @protected
+  NoDefaultCupertinoThemeData resolveFrom(BuildContext context) {
+    Color? convertColor(Color? color) => CupertinoDynamicColor.maybeResolve(color, context);
+
+    return NoDefaultCupertinoThemeData(
+      brightness: brightness,
+      primaryColor: convertColor(primaryColor),
+      primaryContrastingColor: convertColor(primaryContrastingColor),
+      textTheme: textTheme?.resolveFrom(context),
+      barBackgroundColor: convertColor(barBackgroundColor),
+      scaffoldBackgroundColor: convertColor(scaffoldBackgroundColor),
+    );
+  }
+
+  /// Creates a copy of the theme data with specified attributes overridden.
+  ///
+  /// Only the current instance's specified attributes are copied instead of
+  /// derived values. For instance, if the current [textTheme] is implied from
+  /// the current [primaryColor] because it was not specified, copying with a
+  /// different [primaryColor] will also change the copy's implied [textTheme].
+  NoDefaultCupertinoThemeData copyWith({
+    Brightness? brightness,
+    Color? primaryColor,
+    Color? primaryContrastingColor,
+    CupertinoTextThemeData? textTheme,
+    Color? barBackgroundColor ,
+    Color? scaffoldBackgroundColor,
+  }) {
+    return NoDefaultCupertinoThemeData(
+      brightness: brightness ?? this.brightness,
+      primaryColor: primaryColor ?? this.primaryColor,
+      primaryContrastingColor: primaryContrastingColor ?? this.primaryContrastingColor,
+      textTheme: textTheme ?? this.textTheme,
+      barBackgroundColor: barBackgroundColor ?? this.barBackgroundColor,
+      scaffoldBackgroundColor: scaffoldBackgroundColor ?? this.scaffoldBackgroundColor,
+    );
+  }
+}
+
+@immutable
+class _CupertinoThemeDefaults {
+  const _CupertinoThemeDefaults(
+    this.brightness,
+    this.primaryColor,
+    this.primaryContrastingColor,
+    this.barBackgroundColor,
+    this.scaffoldBackgroundColor,
+    this.textThemeDefaults,
+  );
+
+  final Brightness? brightness;
+  final Color primaryColor;
+  final Color primaryContrastingColor;
+  final Color barBackgroundColor;
+  final Color scaffoldBackgroundColor;
+  final _CupertinoTextThemeDefaults textThemeDefaults;
+
+  _CupertinoThemeDefaults resolveFrom(BuildContext context, bool resolveTextTheme) {
+    Color convertColor(Color color) => CupertinoDynamicColor.resolve(color, context);
+
+    return _CupertinoThemeDefaults(
+      brightness,
+      convertColor(primaryColor),
+      convertColor(primaryContrastingColor),
+      convertColor(barBackgroundColor),
+      convertColor(scaffoldBackgroundColor),
+      resolveTextTheme ? textThemeDefaults.resolveFrom(context) : textThemeDefaults,
+    );
+  }
+}
+
+@immutable
+class _CupertinoTextThemeDefaults {
+  const _CupertinoTextThemeDefaults(
+    this.labelColor,
+    this.inactiveGray,
+  );
+
+  final Color labelColor;
+  final Color inactiveGray;
+
+  _CupertinoTextThemeDefaults resolveFrom(BuildContext context) {
+    return _CupertinoTextThemeDefaults(
+      CupertinoDynamicColor.resolve(labelColor, context),
+      CupertinoDynamicColor.resolve(inactiveGray, context),
+    );
+  }
+
+  CupertinoTextThemeData createDefaults({ required Color primaryColor }) {
+    assert(primaryColor != null);
+    return _DefaultCupertinoTextThemeData(
+      primaryColor: primaryColor,
+      labelColor: labelColor,
+      inactiveGray: inactiveGray,
+    );
+  }
+}
+
+// CupertinoTextThemeData with no text styles explicitly specified.
+// The implementation of this class may need to be updated when any of the default
+// text styles changes.
+class _DefaultCupertinoTextThemeData extends CupertinoTextThemeData {
+  const _DefaultCupertinoTextThemeData({
+    required this.labelColor,
+    required this.inactiveGray,
+    required Color primaryColor,
+  }) : assert(labelColor != null),
+       assert(inactiveGray != null),
+       assert(primaryColor != null),
+       super(primaryColor: primaryColor);
+
+  final Color labelColor;
+  final Color inactiveGray;
+
+  @override
+  TextStyle get textStyle => super.textStyle.copyWith(color: labelColor);
+
+  @override
+  TextStyle get tabLabelTextStyle => super.tabLabelTextStyle.copyWith(color: inactiveGray);
+
+  @override
+  TextStyle get navTitleTextStyle => super.navTitleTextStyle.copyWith(color: labelColor);
+
+  @override
+  TextStyle get navLargeTitleTextStyle => super.navLargeTitleTextStyle.copyWith(color: labelColor);
+
+  @override
+  TextStyle get pickerTextStyle => super.pickerTextStyle.copyWith(color: labelColor);
+
+  @override
+  TextStyle get dateTimePickerTextStyle => super.dateTimePickerTextStyle.copyWith(color: labelColor);
+}
diff --git a/lib/src/cupertino/thumb_painter.dart b/lib/src/cupertino/thumb_painter.dart
new file mode 100644
index 0000000..a788b1c
--- /dev/null
+++ b/lib/src/cupertino/thumb_painter.dart
@@ -0,0 +1,91 @@
+// Copyright 2014 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.
+
+import 'package:flute/painting.dart';
+
+import 'colors.dart';
+
+const Color _kThumbBorderColor = Color(0x0A000000);
+
+const List<BoxShadow> _kSwitchBoxShadows = <BoxShadow> [
+  BoxShadow(
+    color: Color(0x26000000),
+    offset: Offset(0, 3),
+    blurRadius: 8.0,
+  ),
+  BoxShadow(
+    color: Color(0x0F000000),
+    offset: Offset(0, 3),
+    blurRadius: 1.0,
+  ),
+];
+
+const List<BoxShadow> _kSliderBoxShadows = <BoxShadow> [
+  BoxShadow(
+    color: Color(0x26000000),
+    offset: Offset(0, 3),
+    blurRadius: 8.0,
+  ),
+  BoxShadow(
+    color: Color(0x29000000),
+    offset: Offset(0, 1),
+    blurRadius: 1.0,
+  ),
+  BoxShadow(
+    color: Color(0x1A000000),
+    offset: Offset(0, 3),
+    blurRadius: 1.0,
+  ),
+];
+
+/// Paints an iOS-style slider thumb or switch thumb.
+///
+/// Used by [CupertinoSwitch] and [CupertinoSlider].
+class CupertinoThumbPainter {
+  /// Creates an object that paints an iOS-style slider thumb.
+  const CupertinoThumbPainter({
+    this.color = CupertinoColors.white,
+    this.shadows = _kSliderBoxShadows,
+  }) : assert(shadows != null);
+
+  /// Creates an object that paints an iOS-style switch thumb.
+  const CupertinoThumbPainter.switchThumb({
+    Color color = CupertinoColors.white,
+    List<BoxShadow> shadows = _kSwitchBoxShadows,
+  }) : this(color: color, shadows: shadows);
+
+  /// The color of the interior of the thumb.
+  final Color color;
+
+  /// The list of [BoxShadow] to paint below the thumb.
+  ///
+  /// Must not be null.
+  final List<BoxShadow> shadows;
+
+  /// Half the default diameter of the thumb.
+  static const double radius = 14.0;
+
+  /// The default amount the thumb should be extended horizontally when pressed.
+  static const double extension = 7.0;
+
+  /// Paints the thumb onto the given canvas in the given rectangle.
+  ///
+  /// Consider using [radius] and [extension] when deciding how large a
+  /// rectangle to use for the thumb.
+  void paint(Canvas canvas, Rect rect) {
+    final RRect rrect = RRect.fromRectAndRadius(
+      rect,
+      Radius.circular(rect.shortestSide / 2.0),
+    );
+
+    for (final BoxShadow shadow in shadows)
+      canvas.drawRRect(rrect.shift(shadow.offset), shadow.toPaint());
+
+    canvas.drawRRect(
+      rrect.inflate(0.5),
+      Paint()..color = _kThumbBorderColor,
+    );
+    canvas.drawRRect(rrect, Paint()..color = color);
+  }
+}
diff --git a/lib/src/foundation/README.md b/lib/src/foundation/README.md
new file mode 100644
index 0000000..c37e4f0
--- /dev/null
+++ b/lib/src/foundation/README.md
@@ -0,0 +1,13 @@
+The rule for packages in this directory is that they can depend on
+nothing but core Dart packages. They can't depend on `dart:ui`, they
+can't depend on any `package:`, and they can't depend on anything
+outside this directory.
+
+Currently they do depend on dart:ui, but only for `VoidCallback` and
+`hashValues` (and maybe one day `hashList` and `lerpDouble`), which
+are all intended to be moved out of `dart:ui` and into `dart:core`.
+
+See also:
+
+ * https://github.com/dart-lang/sdk/issues/27791 (`VoidCallback`)
+ * https://github.com/dart-lang/sdk/issues/25217 (`hashValues`, `hashList`, and `lerpDouble`)
diff --git a/lib/src/foundation/_bitfield_io.dart b/lib/src/foundation/_bitfield_io.dart
new file mode 100644
index 0000000..a4f3bac
--- /dev/null
+++ b/lib/src/foundation/_bitfield_io.dart
@@ -0,0 +1,50 @@
+// Copyright 2014 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.
+
+import 'bitfield.dart' as bitfield;
+
+/// The dart:io implementation of [bitfield.kMaxUnsignedSMI].
+const int kMaxUnsignedSMI = 0x3FFFFFFFFFFFFFFF;
+
+/// The dart:io implementation of [bitfield.Bitfield].
+class BitField<T extends dynamic> implements bitfield.BitField<T> {
+  /// The dart:io implementation of [bitfield.Bitfield()].
+  BitField(this._length)
+    : assert(_length <= _smiBits),
+      _bits = _allZeros;
+
+  /// The dart:io implementation of [bitfield.Bitfield.filled].
+  BitField.filled(this._length, bool value)
+    : assert(_length <= _smiBits),
+      _bits = value ? _allOnes : _allZeros;
+
+  final int _length;
+  int _bits;
+
+  static const int _smiBits = 62; // see https://www.dartlang.org/articles/numeric-computation/#smis-and-mints
+  static const int _allZeros = 0;
+  static const int _allOnes = kMaxUnsignedSMI; // 2^(_kSMIBits+1)-1
+
+  @override
+  bool operator [](T index) {
+    final int _index = index.index as int;
+    assert(_index < _length);
+    return (_bits & 1 << _index) > 0;
+  }
+
+  @override
+  void operator []=(T index, bool value) {
+    final int _index = index.index as int;
+    assert(_index < _length);
+    if (value)
+      _bits = _bits | (1 << _index);
+    else
+      _bits = _bits & ~(1 << _index);
+  }
+
+  @override
+  void reset([ bool value = false ]) {
+    _bits = value ? _allOnes : _allZeros;
+  }
+}
diff --git a/lib/src/foundation/_bitfield_web.dart b/lib/src/foundation/_bitfield_web.dart
new file mode 100644
index 0000000..e1f74cc
--- /dev/null
+++ b/lib/src/foundation/_bitfield_web.dart
@@ -0,0 +1,42 @@
+// Copyright 2014 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.
+
+import 'bitfield.dart' as bitfield;
+
+/// The dart:html implementation of [bitfield.kMaxUnsignedSMI].
+///
+/// This value is used as an optimization to coerce some numbers to be within
+/// the SMI range and avoid heap allocations. Because number encoding is
+/// VM-specific, there's no guarantee that this optimization will be effective
+/// on all JavaScript engines. The value picked here should be correct, but it
+/// does not have to guarantee efficiency.
+const int kMaxUnsignedSMI = -1;
+
+/// The dart:html implementation of [bitfield.Bitfield].
+class BitField<T extends dynamic> implements bitfield.BitField<T> {
+  /// The dart:html implementation of [bitfield.Bitfield].
+  // Can remove when we have metaclasses.
+  // ignore: avoid_unused_constructor_parameters
+  BitField(int length);
+
+  /// The dart:html implementation of [bitfield.Bitfield.filled].
+  // Can remove when we have metaclasses.
+  // ignore: avoid_unused_constructor_parameters
+  BitField.filled(int length, bool value);
+
+  @override
+  bool operator [](T index) {
+    throw UnsupportedError('Not supported when compiling to JavaScript');
+  }
+
+  @override
+  void operator []=(T index, bool value) {
+    throw UnsupportedError('Not supported when compiling to JavaScript');
+  }
+
+  @override
+  void reset([ bool value = false ]) {
+    throw UnsupportedError('Not supported when compiling to JavaScript');
+  }
+}
diff --git a/lib/src/foundation/_isolates_io.dart b/lib/src/foundation/_isolates_io.dart
new file mode 100644
index 0000000..1de7de1
--- /dev/null
+++ b/lib/src/foundation/_isolates_io.dart
@@ -0,0 +1,98 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:developer';
+import 'dart:isolate';
+import 'package:meta/meta.dart';
+
+import 'constants.dart';
+import 'isolates.dart' as isolates;
+
+/// The dart:io implementation of [isolate.compute].
+Future<R> compute<Q, R>(isolates.ComputeCallback<Q, R> callback, Q message, { String? debugLabel }) async {
+  debugLabel ??= kReleaseMode ? 'compute' : callback.toString();
+  final Flow flow = Flow.begin();
+  Timeline.startSync('$debugLabel: start', flow: flow);
+  final ReceivePort resultPort = ReceivePort();
+  final ReceivePort exitPort = ReceivePort();
+  final ReceivePort errorPort = ReceivePort();
+  Timeline.finishSync();
+  final Isolate isolate = await Isolate.spawn<_IsolateConfiguration<Q, FutureOr<R>>>(
+    _spawn,
+    _IsolateConfiguration<Q, FutureOr<R>>(
+      callback,
+      message,
+      resultPort.sendPort,
+      debugLabel,
+      flow.id,
+    ),
+    errorsAreFatal: true,
+    onExit: exitPort.sendPort,
+    onError: errorPort.sendPort,
+  );
+  final Completer<R> result = Completer<R>();
+  errorPort.listen((dynamic errorData) {
+    assert(errorData is List<dynamic>);
+    assert(errorData.length == 2);
+    final Exception exception = Exception(errorData[0]);
+    final StackTrace stack = StackTrace.fromString(errorData[1] as String);
+    if (result.isCompleted) {
+      Zone.current.handleUncaughtError(exception, stack);
+    } else {
+      result.completeError(exception, stack);
+    }
+  });
+  exitPort.listen((dynamic exitData) {
+    if (!result.isCompleted) {
+      result.completeError(Exception('Isolate exited without result or error.'));
+    }
+  });
+  resultPort.listen((dynamic resultData) {
+    assert(resultData == null || resultData is R);
+    if (!result.isCompleted)
+      result.complete(resultData as R);
+  });
+  await result.future;
+  Timeline.startSync('$debugLabel: end', flow: Flow.end(flow.id));
+  resultPort.close();
+  errorPort.close();
+  isolate.kill();
+  Timeline.finishSync();
+  return result.future;
+}
+
+@immutable
+class _IsolateConfiguration<Q, R> {
+  const _IsolateConfiguration(
+    this.callback,
+    this.message,
+    this.resultPort,
+    this.debugLabel,
+    this.flowId,
+  );
+  final isolates.ComputeCallback<Q, R> callback;
+  final Q message;
+  final SendPort resultPort;
+  final String debugLabel;
+  final int flowId;
+
+  FutureOr<R> apply() => callback(message);
+}
+
+Future<void> _spawn<Q, R>(_IsolateConfiguration<Q, FutureOr<R>> configuration) async {
+  final R result = await Timeline.timeSync(
+    configuration.debugLabel,
+    () async {
+      final FutureOr<R> applicationResult = await configuration.apply();
+      return await applicationResult;
+    },
+    flow: Flow.step(configuration.flowId),
+  );
+  Timeline.timeSync(
+    '${configuration.debugLabel}: returning result',
+    () { configuration.resultPort.send(result); },
+    flow: Flow.step(configuration.flowId),
+  );
+}
diff --git a/lib/src/foundation/_isolates_web.dart b/lib/src/foundation/_isolates_web.dart
new file mode 100644
index 0000000..e325678
--- /dev/null
+++ b/lib/src/foundation/_isolates_web.dart
@@ -0,0 +1,14 @@
+// Copyright 2014 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.
+
+import 'isolates.dart' as isolates;
+
+/// The dart:html implementation of [isolate.compute].
+Future<R> compute<Q, R>(isolates.ComputeCallback<Q, R> callback, Q message, { String? debugLabel }) async {
+  // To avoid blocking the UI immediately for an expensive function call, we
+  // pump a single frame to allow the framework to complete the current set
+  // of work.
+  await null;
+  return callback(message);
+}
diff --git a/lib/src/foundation/_platform_io.dart b/lib/src/foundation/_platform_io.dart
new file mode 100644
index 0000000..e5e4777
--- /dev/null
+++ b/lib/src/foundation/_platform_io.dart
@@ -0,0 +1,40 @@
+// Copyright 2014 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.
+
+import 'dart:io';
+import 'assertions.dart';
+import 'platform.dart' as platform;
+
+/// The dart:io implementation of [platform.defaultTargetPlatform].
+platform.TargetPlatform get defaultTargetPlatform {
+  platform.TargetPlatform? result;
+  if (Platform.isAndroid) {
+    result = platform.TargetPlatform.android;
+  } else if (Platform.isIOS) {
+    result = platform.TargetPlatform.iOS;
+  } else if (Platform.isFuchsia) {
+    result = platform.TargetPlatform.fuchsia;
+  } else if (Platform.isLinux) {
+    result = platform.TargetPlatform.linux;
+  } else if (Platform.isMacOS) {
+    result = platform.TargetPlatform.macOS;
+  } else if (Platform.isWindows) {
+    result = platform.TargetPlatform.windows;
+  }
+  assert(() {
+    if (Platform.environment.containsKey('FLUTTER_TEST'))
+      result = platform.TargetPlatform.android;
+    return true;
+  }());
+  if (platform.debugDefaultTargetPlatformOverride != null)
+    result = platform.debugDefaultTargetPlatformOverride;
+  if (result == null) {
+    throw FlutterError(
+      'Unknown platform.\n'
+      '${Platform.operatingSystem} was not recognized as a target platform. '
+      'Consider updating the list of TargetPlatforms to include this platform.'
+    );
+  }
+  return result!;
+}
diff --git a/lib/src/foundation/_platform_web.dart b/lib/src/foundation/_platform_web.dart
new file mode 100644
index 0000000..412fa0c
--- /dev/null
+++ b/lib/src/foundation/_platform_web.dart
@@ -0,0 +1,44 @@
+// Copyright 2014 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.
+
+import 'dart:html' as html;
+import 'platform.dart' as platform;
+
+/// The dart:html implementation of [platform.defaultTargetPlatform].
+platform.TargetPlatform get defaultTargetPlatform {
+  // To get a better guess at the targetPlatform we need to be able to reference
+  // the window, but that won't be available until we fix the platforms
+  // configuration for Flutter.
+  platform.TargetPlatform result = _browserPlatform();
+  if (platform.debugDefaultTargetPlatformOverride != null)
+    result = platform.debugDefaultTargetPlatformOverride!;
+  return result;
+}
+
+platform.TargetPlatform _browserPlatform() {
+  final String navigatorPlatform = html.window.navigator.platform?.toLowerCase() ?? '';
+  if (navigatorPlatform.startsWith('mac')) {
+    return platform.TargetPlatform.macOS;
+  }
+  if (navigatorPlatform.startsWith('win')) {
+    return platform.TargetPlatform.windows;
+  }
+  if (navigatorPlatform.contains('iphone') ||
+      navigatorPlatform.contains('ipad') ||
+      navigatorPlatform.contains('ipod')) {
+    return platform.TargetPlatform.iOS;
+  }
+  if (navigatorPlatform.contains('android')) {
+    return platform.TargetPlatform.android;
+  }
+  // Since some phones can report a window.navigator.platform as Linux, fall
+  // back to use CSS to disambiguate Android vs Linux desktop. If the CSS
+  // indicates that a device has a "fine pointer" (mouse) as the primary
+  // pointing device, then we'll assume desktop linux, and otherwise we'll
+  // assume Android.
+  if (html.window.matchMedia('only screen and (pointer: fine)').matches) {
+    return platform.TargetPlatform.linux;
+  }
+  return platform.TargetPlatform.android;
+}
diff --git a/lib/src/foundation/annotations.dart b/lib/src/foundation/annotations.dart
new file mode 100644
index 0000000..b57bfac
--- /dev/null
+++ b/lib/src/foundation/annotations.dart
@@ -0,0 +1,118 @@
+// Copyright 2014 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.
+
+// Examples can assume:
+// class Cat { }
+
+/// A category with which to annotate a class, for documentation
+/// purposes.
+///
+/// A category is usually represented as a section and a subsection, each
+/// of which is a string. The engineering team that owns the library to which
+/// the class belongs defines the categories used for classes in that library.
+/// For example, the Flutter engineering team has defined categories like
+/// "Basic/Buttons" and "Material Design/Buttons" for Flutter widgets.
+///
+/// A class can have multiple categories.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// /// A copper coffee pot, as desired by Ben Turpin.
+/// /// ...documentation...
+/// @Category(<String>['Pots', 'Coffee'])
+/// @Category(<String>['Copper', 'Cookware'])
+/// @DocumentationIcon('https://example.com/images/coffee.png')
+/// @Summary('A proper cup of coffee is made in a proper copper coffee pot.')
+/// class CopperCoffeePot {
+///   // ...code...
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [DocumentationIcon], which is used to give the URL to an image that
+///    represents the class.
+///  * [Summary], which is used to provide a one-line description of a
+///    class that overrides the inline documentations' own description.
+class Category {
+  /// Create an annotation to provide a categorization of a class.
+  const Category(this.sections) : assert(sections != null);
+
+  /// The strings the correspond to the section and subsection of the
+  /// category represented by this object.
+  ///
+  /// By convention, this list usually has two items. The allowed values
+  /// are defined by the team that owns the library to which the annotated
+  /// class belongs.
+  final List<String> sections;
+}
+
+/// A class annotation to provide a URL to an image that represents the class.
+///
+/// Each class should only have one [DocumentationIcon].
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// /// Utility class for beginning a dream-sharing sequence.
+/// /// ...documentation...
+/// @Category(<String>['Military Technology', 'Experimental'])
+/// @DocumentationIcon('https://docs.example.org/icons/top.png')
+/// class DreamSharing {
+///   // ...code...
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Category], to help place the class in an index.
+///  * [Summary], which is used to provide a one-line description of a
+///    class that overrides the inline documentations' own description.
+class DocumentationIcon {
+  /// Create an annotation to provide a URL to an image describing a class.
+  const DocumentationIcon(this.url) : assert(url != null);
+
+  /// The URL to an image that represents the annotated class.
+  final String url;
+}
+
+/// An annotation that provides a short description of a class for use
+/// in an index.
+///
+/// Usually the first paragraph of the documentation for a class can be used
+/// for this purpose, but on occasion the first paragraph is either too short
+/// or too long for use in isolation, without the remainder of the documentation.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// /// A famous cat.
+/// ///
+/// /// Instances of this class can hunt small animals.
+/// /// This cat has three legs.
+/// @Category(<String>['Animals', 'Cats'])
+/// @Category(<String>['Cute', 'Pets'])
+/// @DocumentationIcon('https://www.examples.net/docs/images/icons/pillar.jpeg')
+/// @Summary('A famous three-legged cat.')
+/// class Pillar extends Cat {
+///   // ...code...
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Category], to help place the class in an index.
+///  * [DocumentationIcon], which is used to give the URL to an image that
+///    represents the class.
+class Summary {
+  /// Create an annotation to provide a short description of a class.
+  const Summary(this.text) : assert(text != null);
+
+  /// The text of the summary of the annotated class.
+  final String text;
+}
diff --git a/lib/src/foundation/assertions.dart b/lib/src/foundation/assertions.dart
new file mode 100644
index 0000000..1f77490
--- /dev/null
+++ b/lib/src/foundation/assertions.dart
@@ -0,0 +1,1219 @@
+// Copyright 2014 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.
+
+import 'package:meta/meta.dart';
+
+import 'basic_types.dart';
+import 'constants.dart';
+import 'diagnostics.dart';
+import 'print.dart';
+import 'stack_frame.dart';
+
+// Examples can assume:
+// late String runtimeType;
+// late bool draconisAlive;
+// late bool draconisAmulet;
+// late Diagnosticable draconis;
+
+/// Signature for [FlutterError.onError] handler.
+typedef FlutterExceptionHandler = void Function(FlutterErrorDetails details);
+
+/// Signature for [DiagnosticPropertiesBuilder] transformer.
+typedef DiagnosticPropertiesTransformer = Iterable<DiagnosticsNode> Function(Iterable<DiagnosticsNode> properties);
+
+/// Signature for [FlutterErrorDetails.informationCollector] callback
+/// and other callbacks that collect information describing an error.
+typedef InformationCollector = Iterable<DiagnosticsNode> Function();
+
+/// Signature for a function that demangles [StackTrace] objects into a format
+/// that can be parsed by [StackFrame].
+///
+/// See also:
+///
+///   * [FlutterError.demangleStackTrace], which shows an example implementation.
+typedef StackTraceDemangler = StackTrace Function(StackTrace details);
+
+/// Partial information from a stack frame for stack filtering purposes.
+///
+/// See also:
+///
+///  * [RepetitiveStackFrameFilter], which uses this class to compare against [StackFrame]s.
+@immutable
+class PartialStackFrame {
+  /// Creates a new [PartialStackFrame] instance. All arguments are required and
+  /// must not be null.
+  const PartialStackFrame({
+    required this.package,
+    required this.className,
+    required this.method,
+  }) : assert(className != null),
+       assert(method != null),
+       assert(package != null);
+
+  /// An `<asynchronous suspension>` line in a stack trace.
+  static const PartialStackFrame asynchronousSuspension = PartialStackFrame(
+    package: '',
+    className: '',
+    method: 'asynchronous suspension',
+  );
+
+  /// The package to match, e.g. `package:flutter/src/foundation/assertions.dart`,
+  /// or `dart:ui/window.dart`.
+  final Pattern package;
+
+  /// The class name for the method.
+  ///
+  /// On web, this is ignored, since class names are not available.
+  ///
+  /// On all platforms, top level methods should use the empty string.
+  final String className;
+
+  /// The method name for this frame line.
+  ///
+  /// On web, private methods are wrapped with `[]`.
+  final String method;
+
+  /// Tests whether the [StackFrame] matches the information in this
+  /// [PartialStackFrame].
+  bool matches(StackFrame stackFrame) {
+    final String stackFramePackage = '${stackFrame.packageScheme}:${stackFrame.package}/${stackFrame.packagePath}';
+    // Ideally this wouldn't be necessary.
+    // TODO(dnfield): https://github.com/dart-lang/sdk/issues/40117
+    if (kIsWeb) {
+      return package.allMatches(stackFramePackage).isNotEmpty
+          && stackFrame.method == (method.startsWith('_') ? '[$method]' : method);
+    }
+    return package.allMatches(stackFramePackage).isNotEmpty
+        && stackFrame.method == method
+        && stackFrame.className == className;
+  }
+}
+
+/// A class that filters stack frames for additional filtering on
+/// [FlutterError.defaultStackFilter].
+abstract class StackFilter {
+  /// A const constructor to allow subclasses to be const.
+  const StackFilter();
+
+  /// Filters the list of [StackFrame]s by updating corresponding indices in
+  /// `reasons`.
+  ///
+  /// To elide a frame or number of frames, set the string.
+  void filter(List<StackFrame> stackFrames, List<String?> reasons);
+}
+
+
+/// A [StackFilter] that filters based on repeating lists of
+/// [PartialStackFrame]s.
+///
+/// See also:
+///
+///   * [FlutterError.addDefaultStackFilter], a method to register additional
+///     stack filters for [FlutterError.defaultStackFilter].
+///   * [StackFrame], a class that can help with parsing stack frames.
+///   * [PartialStackFrame], a class that helps match partial method information
+///     to a stack frame.
+class RepetitiveStackFrameFilter extends StackFilter {
+  /// Creates a new RepetitiveStackFrameFilter. All parameters are required and must not be
+  /// null.
+  const RepetitiveStackFrameFilter({
+    required this.frames,
+    required this.replacement,
+  }) : assert(frames != null),
+       assert(replacement != null);
+
+  /// The shape of this repetitive stack pattern.
+  final List<PartialStackFrame> frames;
+
+  /// The number of frames in this pattern.
+  int get numFrames => frames.length;
+
+  /// The string to replace the frames with.
+  ///
+  /// If the same replacement string is used multiple times in a row, the
+  /// [FlutterError.defaultStackFilter] will simply update a counter after this
+  /// line rather than repeating it.
+  final String replacement;
+
+  List<String> get _replacements => List<String>.filled(numFrames, replacement);
+
+  @override
+  void filter(List<StackFrame> stackFrames, List<String?> reasons) {
+    for (int index = 0; index < stackFrames.length - numFrames; index += 1) {
+      if (_matchesFrames(stackFrames.skip(index).take(numFrames).toList())) {
+        reasons.setRange(index, index + numFrames, _replacements);
+        index += numFrames - 1;
+      }
+    }
+  }
+
+  bool _matchesFrames(List<StackFrame> stackFrames) {
+    if (stackFrames.length < numFrames) {
+      return false;
+    }
+    for (int index = 0; index < stackFrames.length; index++) {
+      if (!frames[index].matches(stackFrames[index])) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
+
+abstract class _ErrorDiagnostic extends DiagnosticsProperty<List<Object>> {
+  /// This constructor provides a reliable hook for a kernel transformer to find
+  /// error messages that need to be rewritten to include object references for
+  /// interactive display of errors.
+  _ErrorDiagnostic(
+    String message, {
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.flat,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(message != null),
+       super(
+         null,
+         <Object>[message],
+         showName: false,
+         showSeparator: false,
+         defaultValue: null,
+         style: style,
+         level: level,
+       );
+
+  /// In debug builds, a kernel transformer rewrites calls to the default
+  /// constructors for [ErrorSummary], [ErrorDescription], and [ErrorHint] to use
+  /// this constructor.
+  //
+  // ```dart
+  // _ErrorDiagnostic('Element $element must be $color')
+  // ```
+  // Desugars to:
+  // ```dart
+  // _ErrorDiagnostic.fromParts(<Object>['Element ', element, ' must be ', color])
+  // ```
+  //
+  // Slightly more complex case:
+  // ```dart
+  // _ErrorDiagnostic('Element ${element.runtimeType} must be $color')
+  // ```
+  // Desugars to:
+  //```dart
+  // _ErrorDiagnostic.fromParts(<Object>[
+  //   'Element ',
+  //   DiagnosticsProperty(null, element, description: element.runtimeType?.toString()),
+  //   ' must be ',
+  //   color,
+  // ])
+  // ```
+  _ErrorDiagnostic._fromParts(
+    List<Object> messageParts, {
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.flat,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(messageParts != null),
+       super(
+         null,
+         messageParts,
+         showName: false,
+         showSeparator: false,
+         defaultValue: null,
+         style: style,
+         level: level,
+       );
+
+
+  @override
+  List<Object> get value => super.value!;
+
+  @override
+  String valueToString({ TextTreeConfiguration? parentConfiguration }) {
+    return value.join('');
+  }
+}
+
+/// An explanation of the problem and its cause, any information that may help
+/// track down the problem, background information, etc.
+///
+/// Use [ErrorDescription] for any part of an error message where neither
+/// [ErrorSummary] or [ErrorHint] is appropriate.
+///
+/// In debug builds, values interpolated into the `message` are
+/// expanded and placed into [value], which is of type [List<Object>].
+/// This allows IDEs to examine values interpolated into error messages.
+///
+/// See also:
+///
+///  * [ErrorSummary], which provides a short (one line) description of the
+///    problem that was detected.
+///  * [ErrorHint], which provides specific, non-obvious advice that may be
+///    applicable.
+///  * [FlutterError], which is the most common place to use an
+///    [ErrorDescription].
+class ErrorDescription extends _ErrorDiagnostic {
+  /// A lint enforces that this constructor can only be called with a string
+  /// literal to match the limitations of the Dart Kernel transformer that
+  /// optionally extracts out objects referenced using string interpolation in
+  /// the message passed in.
+  ///
+  /// The message will display with the same text regardless of whether the
+  /// kernel transformer is used. The kernel transformer is required so that
+  /// debugging tools can provide interactive displays of objects described by
+  /// the error.
+  ErrorDescription(String message) : super(message, level: DiagnosticLevel.info);
+
+  /// Calls to the default constructor may be rewritten to use this constructor
+  /// in debug mode using a kernel transformer.
+  // ignore: unused_element
+  ErrorDescription._fromParts(List<Object> messageParts) : super._fromParts(messageParts, level: DiagnosticLevel.info);
+}
+
+/// A short (one line) description of the problem that was detected.
+///
+/// Error summaries from the same source location should have little variance,
+/// so that they can be recognized as related. For example, they shouldn't
+/// include hash codes.
+///
+/// A [FlutterError] must start with an [ErrorSummary] and may not contain
+/// multiple summaries.
+///
+/// In debug builds, values interpolated into the `message` are
+/// expanded and placed into [value], which is of type [List<Object>].
+/// This allows IDEs to examine values interpolated into error messages.
+///
+/// See also:
+///
+///  * [ErrorDescription], which provides an explanation of the problem and its
+///    cause, any information that may help track down the problem, background
+///    information, etc.
+///  * [ErrorHint], which provides specific, non-obvious advice that may be
+///    applicable.
+///  * [FlutterError], which is the most common place to use an [ErrorSummary].
+class ErrorSummary extends _ErrorDiagnostic {
+  /// A lint enforces that this constructor can only be called with a string
+  /// literal to match the limitations of the Dart Kernel transformer that
+  /// optionally extracts out objects referenced using string interpolation in
+  /// the message passed in.
+  ///
+  /// The message will display with the same text regardless of whether the
+  /// kernel transformer is used. The kernel transformer is required so that
+  /// debugging tools can provide interactive displays of objects described by
+  /// the error.
+  ErrorSummary(String message) : super(message, level: DiagnosticLevel.summary);
+
+  /// Calls to the default constructor may be rewritten to use this constructor
+  /// in debug mode using a kernel transformer.
+  // ignore: unused_element
+  ErrorSummary._fromParts(List<Object> messageParts) : super._fromParts(messageParts, level: DiagnosticLevel.summary);
+}
+
+/// An [ErrorHint] provides specific, non-obvious advice that may be applicable.
+///
+/// If your message provides obvious advice that is always applicable, it is an
+/// [ErrorDescription] not a hint.
+///
+/// In debug builds, values interpolated into the `message` are
+/// expanded and placed into [value], which is of type [List<Object>].
+/// This allows IDEs to examine values interpolated into error messages.
+///
+/// See also:
+///
+///  * [ErrorSummary], which provides a short (one line) description of the
+///    problem that was detected.
+///  * [ErrorDescription], which provides an explanation of the problem and its
+///    cause, any information that may help track down the problem, background
+///    information, etc.
+///  * [FlutterError], which is the most common place to use an [ErrorHint].
+class ErrorHint extends _ErrorDiagnostic {
+  /// A lint enforces that this constructor can only be called with a string
+  /// literal to match the limitations of the Dart Kernel transformer that
+  /// optionally extracts out objects referenced using string interpolation in
+  /// the message passed in.
+  ///
+  /// The message will display with the same text regardless of whether the
+  /// kernel transformer is used. The kernel transformer is required so that
+  /// debugging tools can provide interactive displays of objects described by
+  /// the error.
+  ErrorHint(String message) : super(message, level:DiagnosticLevel.hint);
+
+  /// Calls to the default constructor may be rewritten to use this constructor
+  /// in debug mode using a kernel transformer.
+  // ignore: unused_element
+  ErrorHint._fromParts(List<Object> messageParts) : super._fromParts(messageParts, level:DiagnosticLevel.hint);
+}
+
+/// An [ErrorSpacer] creates an empty [DiagnosticsNode], that can be used to
+/// tune the spacing between other [DiagnosticsNode] objects.
+class ErrorSpacer extends DiagnosticsProperty<void> {
+  /// Creates an empty space to insert into a list of [DiagnosticsNode] objects
+  /// typically within a [FlutterError] object.
+  ErrorSpacer() : super(
+    '',
+    null,
+    description: '',
+    showName: false,
+  );
+}
+
+/// Class for information provided to [FlutterExceptionHandler] callbacks.
+///
+/// {@tool snippet}
+/// This is an example of using [FlutterErrorDetails] when calling
+/// [FlutterError.reportError].
+///
+/// ```dart
+/// void main() {
+///   try {
+///     // Try to do something!
+///   } catch (error) {
+///     // Catch & report error.
+///     FlutterError.reportError(FlutterErrorDetails(
+///       exception: error,
+///       library: 'Flutter test framework',
+///       context: ErrorSummary('while running async test code'),
+///     ));
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///   * [FlutterError.onError], which is called whenever the Flutter framework
+///     catches an error.
+class FlutterErrorDetails with Diagnosticable {
+  /// Creates a [FlutterErrorDetails] object with the given arguments setting
+  /// the object's properties.
+  ///
+  /// The framework calls this constructor when catching an exception that will
+  /// subsequently be reported using [FlutterError.onError].
+  ///
+  /// The [exception] must not be null; other arguments can be left to
+  /// their default values. (`throw null` results in a
+  /// [NullThrownError] exception.)
+  const FlutterErrorDetails({
+    required this.exception,
+    this.stack,
+    this.library = 'Flutter framework',
+    this.context,
+    this.stackFilter,
+    this.informationCollector,
+    this.silent = false,
+  }) : assert(exception != null);
+
+  /// Creates a copy of the error details but with the given fields replaced
+  /// with new values.
+  FlutterErrorDetails copyWith({
+    DiagnosticsNode? context,
+    Object? exception,
+    InformationCollector? informationCollector,
+    String? library,
+    bool? silent,
+    StackTrace? stack,
+    IterableFilter<String>? stackFilter,
+  }) {
+    return FlutterErrorDetails(
+      context: context ?? this.context,
+      exception: exception ?? this.exception,
+      informationCollector: informationCollector ?? this.informationCollector,
+      library: library ?? this.library,
+      silent: silent ?? this.silent,
+      stack: stack ?? this.stack,
+      stackFilter: stackFilter ?? this.stackFilter,
+    );
+  }
+
+  /// Transformers to transform [DiagnosticsNode] in [DiagnosticPropertiesBuilder]
+  /// into a more descriptive form.
+  ///
+  /// There are layers that attach certain [DiagnosticsNode] into
+  /// [FlutterErrorDetails] that require knowledge from other layers to parse.
+  /// To correctly interpret those [DiagnosticsNode], register transformers in
+  /// the layers that possess the knowledge.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBinding.initInstances], which registers its transformer.
+  static final List<DiagnosticPropertiesTransformer> propertiesTransformers =
+    <DiagnosticPropertiesTransformer>[];
+
+  /// The exception. Often this will be an [AssertionError], maybe specifically
+  /// a [FlutterError]. However, this could be any value at all.
+  final Object exception;
+
+  /// The stack trace from where the [exception] was thrown (as opposed to where
+  /// it was caught).
+  ///
+  /// StackTrace objects are opaque except for their [toString] function.
+  ///
+  /// If this field is not null, then the [stackFilter] callback, if any, will
+  /// be called with the result of calling [toString] on this object and
+  /// splitting that result on line breaks. If there's no [stackFilter]
+  /// callback, then [FlutterError.defaultStackFilter] is used instead. That
+  /// function expects the stack to be in the format used by
+  /// [StackTrace.toString].
+  final StackTrace? stack;
+
+  /// A human-readable brief name describing the library that caught the error
+  /// message. This is used by the default error handler in the header dumped to
+  /// the console.
+  final String? library;
+
+  /// A [DiagnosticsNode] that provides a human-readable description of where
+  /// the error was caught (as opposed to where it was thrown).
+  ///
+  /// The node, e.g. an [ErrorDescription], should be in a form that will make
+  /// sense in English when following the word "thrown", as in "thrown while
+  /// obtaining the image from the network" (for the context "while obtaining
+  /// the image from the network").
+  ///
+  /// {@tool snippet}
+  /// This is an example of using and [ErrorDescription] as the
+  /// [FlutterErrorDetails.context] when calling [FlutterError.reportError].
+  ///
+  /// ```dart
+  /// void maybeDoSomething() {
+  ///   try {
+  ///     // Try to do something!
+  ///   } catch (error) {
+  ///     // Catch & report error.
+  ///     FlutterError.reportError(FlutterErrorDetails(
+  ///       exception: error,
+  ///       library: 'Flutter test framework',
+  ///       context: ErrorDescription('while dispatching notifications for $runtimeType'),
+  ///     ));
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [ErrorDescription], which provides an explanation of the problem and
+  ///    its cause, any information that may help track down the problem,
+  ///    background information, etc.
+  ///  * [ErrorSummary], which provides a short (one line) description of the
+  ///    problem that was detected.
+  ///  * [ErrorHint], which provides specific, non-obvious advice that may be
+  ///    applicable.
+  ///  * [FlutterError], which is the most common place to use
+  ///    [FlutterErrorDetails].
+  final DiagnosticsNode? context;
+
+  /// A callback which filters the [stack] trace. Receives an iterable of
+  /// strings representing the frames encoded in the way that
+  /// [StackTrace.toString()] provides. Should return an iterable of lines to
+  /// output for the stack.
+  ///
+  /// If this is not provided, then [FlutterError.dumpErrorToConsole] will use
+  /// [FlutterError.defaultStackFilter] instead.
+  ///
+  /// If the [FlutterError.defaultStackFilter] behavior is desired, then the
+  /// callback should manually call that function. That function expects the
+  /// incoming list to be in the [StackTrace.toString()] format. The output of
+  /// that function, however, does not always follow this format.
+  ///
+  /// This won't be called if [stack] is null.
+  final IterableFilter<String>? stackFilter;
+
+  /// A callback which, when called with a [StringBuffer] will write to that buffer
+  /// information that could help with debugging the problem.
+  ///
+  /// Information collector callbacks can be expensive, so the generated information
+  /// should be cached, rather than the callback being called multiple times.
+  ///
+  /// The text written to the information argument may contain newlines but should
+  /// not end with a newline.
+  final InformationCollector? informationCollector;
+
+  /// Whether this error should be ignored by the default error reporting
+  /// behavior in release mode.
+  ///
+  /// If this is false, the default, then the default error handler will always
+  /// dump this error to the console.
+  ///
+  /// If this is true, then the default error handler would only dump this error
+  /// to the console in checked mode. In release mode, the error is ignored.
+  ///
+  /// This is used by certain exception handlers that catch errors that could be
+  /// triggered by environmental conditions (as opposed to logic errors). For
+  /// example, the HTTP library sets this flag so as to not report every 404
+  /// error to the console on end-user devices, while still allowing a custom
+  /// error handler to see the errors even in release builds.
+  final bool silent;
+
+  /// Converts the [exception] to a string.
+  ///
+  /// This applies some additional logic to make [AssertionError] exceptions
+  /// prettier, to handle exceptions that stringify to empty strings, to handle
+  /// objects that don't inherit from [Exception] or [Error], and so forth.
+  String exceptionAsString() {
+    String? longMessage;
+    if (exception is AssertionError) {
+      // Regular _AssertionErrors thrown by assert() put the message last, after
+      // some code snippets. This leads to ugly messages. To avoid this, we move
+      // the assertion message up to before the code snippets, separated by a
+      // newline, if we recognize that format is being used.
+      final Object? message = (exception as AssertionError).message;
+      final String fullMessage = exception.toString();
+      if (message is String && message != fullMessage) {
+        if (fullMessage.length > message.length) {
+          final int position = fullMessage.lastIndexOf(message);
+          if (position == fullMessage.length - message.length &&
+              position > 2 &&
+              fullMessage.substring(position - 2, position) == ': ') {
+            // Add a linebreak so that the filename at the start of the
+            // assertion message is always on its own line.
+            String body = fullMessage.substring(0, position - 2);
+            final int splitPoint = body.indexOf(' Failed assertion:');
+            if (splitPoint >= 0) {
+              body = '${body.substring(0, splitPoint)}\n${body.substring(splitPoint + 1)}';
+            }
+            longMessage = '${message.trimRight()}\n$body';
+          }
+        }
+      }
+      longMessage ??= fullMessage;
+    } else if (exception is String) {
+      longMessage = exception as String;
+    } else if (exception is Error || exception is Exception) {
+      longMessage = exception.toString();
+    } else {
+      longMessage = '  ${exception.toString()}';
+    }
+    longMessage = longMessage.trimRight();
+    if (longMessage.isEmpty)
+      longMessage = '  <no message available>';
+    return longMessage;
+  }
+
+  Diagnosticable? _exceptionToDiagnosticable() {
+    final Object exception = this.exception;
+    if (exception is FlutterError) {
+      return exception;
+    }
+    if (exception is AssertionError && exception.message is FlutterError) {
+      return exception.message! as FlutterError;
+    }
+    return null;
+  }
+
+  /// Returns a short (one line) description of the problem that was detected.
+  ///
+  /// If the exception contains an [ErrorSummary] that summary is used,
+  /// otherwise the summary is inferred from the string representation of the
+  /// exception.
+  ///
+  /// In release mode, this always returns a [DiagnosticsNode.message] with a
+  /// formatted version of the exception.
+  DiagnosticsNode get summary {
+    String formatException() => exceptionAsString().split('\n')[0].trimLeft();
+    if (kReleaseMode) {
+      return DiagnosticsNode.message(formatException());
+    }
+    final Diagnosticable? diagnosticable = _exceptionToDiagnosticable();
+    DiagnosticsNode? summary;
+    if (diagnosticable != null) {
+      final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
+      debugFillProperties(builder);
+      summary = builder.properties.cast<DiagnosticsNode?>().firstWhere((DiagnosticsNode? node) => node!.level == DiagnosticLevel.summary, orElse: () => null);
+    }
+    return summary ?? ErrorSummary(formatException());
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    final DiagnosticsNode verb = ErrorDescription('thrown${ context != null ? ErrorDescription(" $context") : ""}');
+    final Diagnosticable? diagnosticable = _exceptionToDiagnosticable();
+    if (exception is NullThrownError) {
+      properties.add(ErrorDescription('The null value was $verb.'));
+    } else if (exception is num) {
+      properties.add(ErrorDescription('The number $exception was $verb.'));
+    } else {
+      final DiagnosticsNode errorName;
+      if (exception is AssertionError) {
+        errorName = ErrorDescription('assertion');
+      } else if (exception is String) {
+        errorName = ErrorDescription('message');
+      } else if (exception is Error || exception is Exception) {
+        errorName = ErrorDescription('${exception.runtimeType}');
+      } else {
+        errorName = ErrorDescription('${exception.runtimeType} object');
+      }
+      properties.add(ErrorDescription('The following $errorName was $verb:'));
+      if (diagnosticable != null) {
+        diagnosticable.debugFillProperties(properties);
+      } else {
+        // Many exception classes put their type at the head of their message.
+        // This is redundant with the way we display exceptions, so attempt to
+        // strip out that header when we see it.
+        final String prefix = '${exception.runtimeType}: ';
+        String message = exceptionAsString();
+        if (message.startsWith(prefix))
+          message = message.substring(prefix.length);
+        properties.add(ErrorSummary(message));
+      }
+    }
+
+    if (stack != null) {
+      if (exception is AssertionError && diagnosticable == null) {
+        // After popping off any dart: stack frames, are there at least two more
+        // stack frames coming from package flutter?
+        //
+        // If not: Error is in user code (user violated assertion in framework).
+        // If so:  Error is in Framework. We either need an assertion higher up
+        //         in the stack, or we've violated our own assertions.
+        final List<StackFrame> stackFrames = StackFrame.fromStackTrace(FlutterError.demangleStackTrace(stack!))
+                                                       .skipWhile((StackFrame frame) => frame.packageScheme == 'dart')
+                                                       .toList();
+        final bool ourFault =  stackFrames.length >= 2
+                            && stackFrames[0].package == 'flutter'
+                            && stackFrames[1].package == 'flutter';
+        if (ourFault) {
+          properties.add(ErrorSpacer());
+          properties.add(ErrorHint(
+            'Either the assertion indicates an error in the framework itself, or we should '
+            'provide substantially more information in this error message to help you determine '
+            'and fix the underlying cause.\n'
+            'In either case, please report this assertion by filing a bug on GitHub:\n'
+            '  https://github.com/flutter/flutter/issues/new?template=2_bug.md'
+          ));
+        }
+      }
+      properties.add(ErrorSpacer());
+      properties.add(DiagnosticsStackTrace('When the exception was thrown, this was the stack', stack, stackFilter: stackFilter));
+    }
+    if (informationCollector != null) {
+      properties.add(ErrorSpacer());
+      informationCollector!().forEach(properties.add);
+    }
+  }
+
+  @override
+  String toStringShort() {
+    return library != null ? 'Exception caught by $library' : 'Exception caught';
+  }
+
+  @override
+  String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
+    return toDiagnosticsNode(style: DiagnosticsTreeStyle.error).toStringDeep(minLevel: minLevel);
+  }
+
+  @override
+  DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
+    return _FlutterErrorDetailsNode(
+      name: name,
+      value: this,
+      style: style,
+    );
+  }
+}
+
+/// Error class used to report Flutter-specific assertion failures and
+/// contract violations.
+///
+/// See also:
+///
+///  * <https://flutter.dev/docs/testing/errors>, more information about error
+///    handling in Flutter.
+class FlutterError extends Error with DiagnosticableTreeMixin implements AssertionError {
+  /// Create an error message from a string.
+  ///
+  /// The message may have newlines in it. The first line should be a terse
+  /// description of the error, e.g. "Incorrect GlobalKey usage" or "setState()
+  /// or markNeedsBuild() called during build". Subsequent lines should contain
+  /// substantial additional information, ideally sufficient to develop a
+  /// correct solution to the problem.
+  ///
+  /// In some cases, when a [FlutterError] is reported to the user, only the first
+  /// line is included. For example, Flutter will typically only fully report
+  /// the first exception at runtime, displaying only the first line of
+  /// subsequent errors.
+  ///
+  /// All sentences in the error should be correctly punctuated (i.e.,
+  /// do end the error message with a period).
+  ///
+  /// This constructor defers to the [new FlutterError.fromParts] constructor.
+  /// The first line is wrapped in an implied [ErrorSummary], and subsequent
+  /// lines are wrapped in implied [ErrorDescription]s. Consider using the
+  /// [new FlutterError.fromParts] constructor to provide more detail, e.g.
+  /// using [ErrorHint]s or other [DiagnosticsNode]s.
+  factory FlutterError(String message) {
+    final List<String> lines = message.split('\n');
+    return FlutterError.fromParts(<DiagnosticsNode>[
+      ErrorSummary(lines.first),
+      ...lines.skip(1).map<DiagnosticsNode>((String line) => ErrorDescription(line)),
+    ]);
+  }
+
+  /// Create an error message from a list of [DiagnosticsNode]s.
+  ///
+  /// By convention, there should be exactly one [ErrorSummary] in the list,
+  /// and it should be the first entry.
+  ///
+  /// Other entries are typically [ErrorDescription]s (for material that is
+  /// always applicable for this error) and [ErrorHint]s (for material that may
+  /// be sometimes useful, but may not always apply). Other [DiagnosticsNode]
+  /// subclasses, such as [DiagnosticsStackTrace], may
+  /// also be used.
+  ///
+  /// When using an [ErrorSummary], [ErrorDescription]s, and [ErrorHint]s, in
+  /// debug builds, values interpolated into the `message` arguments of those
+  /// classes' constructors are expanded and placed into the
+  /// [DiagnosticsProperty.value] property of those objects (which is of type
+  /// [List<Object>]). This allows IDEs to examine values interpolated into
+  /// error messages.
+  ///
+  /// Alternatively, to include a specific [Diagnosticable] object into the
+  /// error message and have the object describe itself in detail (see
+  /// [DiagnosticsNode.toStringDeep]), consider calling
+  /// [Diagnosticable.toDiagnosticsNode] on that object and using that as one of
+  /// the values passed to this constructor.
+  ///
+  /// {@tool snippet}
+  /// In this example, an error is thrown in debug mode if certain conditions
+  /// are not met. The error message includes a description of an object that
+  /// implements the [Diagnosticable] interface, `draconis`.
+  ///
+  /// ```dart
+  /// void controlDraconis() {
+  ///   assert(() {
+  ///     if (!draconisAlive || !draconisAmulet) {
+  ///       throw FlutterError.fromParts(<DiagnosticsNode>[
+  ///         ErrorSummary('Cannot control Draconis in current state.'),
+  ///         ErrorDescription('Draconis can only be controlled while alive and while the amulet is wielded.'),
+  ///         if (!draconisAlive)
+  ///           ErrorHint('Draconis is currently not alive.'),
+  ///         if (!draconisAmulet)
+  ///           ErrorHint('The Amulet of Draconis is currently not wielded.'),
+  ///         draconis.toDiagnosticsNode(name: 'Draconis'),
+  ///       ]);
+  ///     }
+  ///     return true;
+  ///   }());
+  ///   // ...
+  /// }
+  /// ```
+  /// {@end-tool}
+  FlutterError.fromParts(this.diagnostics) : assert(diagnostics.isNotEmpty, FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Empty FlutterError')])) {
+    assert(
+      diagnostics.first.level == DiagnosticLevel.summary,
+      FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('FlutterError is missing a summary.'),
+        ErrorDescription(
+          'All FlutterError objects should start with a short (one line) '
+          'summary description of the problem that was detected.'
+        ),
+        DiagnosticsProperty<FlutterError>('Malformed', this, expandableValue: true, showSeparator: false, style: DiagnosticsTreeStyle.whitespace),
+        ErrorDescription(
+          '\nThis error should still help you solve your problem, '
+          'however please also report this malformed error in the '
+          'framework by filing a bug on GitHub:\n'
+          '  https://github.com/flutter/flutter/issues/new?template=2_bug.md'
+        ),
+      ],
+    ));
+    assert(() {
+      final Iterable<DiagnosticsNode> summaries = diagnostics.where((DiagnosticsNode node) => node.level == DiagnosticLevel.summary);
+      if (summaries.length > 1) {
+        final List<DiagnosticsNode> message = <DiagnosticsNode>[
+          ErrorSummary('FlutterError contained multiple error summaries.'),
+          ErrorDescription(
+            'All FlutterError objects should have only a single short '
+            '(one line) summary description of the problem that was '
+            'detected.'
+          ),
+          DiagnosticsProperty<FlutterError>('Malformed', this, expandableValue: true, showSeparator: false, style: DiagnosticsTreeStyle.whitespace),
+          ErrorDescription('\nThe malformed error has ${summaries.length} summaries.'),
+        ];
+        int i = 1;
+        for (final DiagnosticsNode summary in summaries) {
+          message.add(DiagnosticsProperty<DiagnosticsNode>('Summary $i', summary, expandableValue : true));
+          i += 1;
+        }
+        message.add(ErrorDescription(
+          '\nThis error should still help you solve your problem, '
+          'however please also report this malformed error in the '
+          'framework by filing a bug on GitHub:\n'
+          '  https://github.com/flutter/flutter/issues/new?template=2_bug.md'
+        ));
+        throw FlutterError.fromParts(message);
+      }
+      return true;
+    }());
+  }
+
+  /// The information associated with this error, in structured form.
+  ///
+  /// The first node is typically an [ErrorSummary] giving a short description
+  /// of the problem, suitable for an index of errors, a log, etc.
+  ///
+  /// Subsequent nodes should give information specific to this error. Typically
+  /// these will be [ErrorDescription]s or [ErrorHint]s, but they could be other
+  /// objects also. For instance, an error relating to a timer could include a
+  /// stack trace of when the timer was scheduled using the
+  /// [DiagnosticsStackTrace] class.
+  final List<DiagnosticsNode> diagnostics;
+
+  /// The message associated with this error.
+  ///
+  /// This is generated by serializing the [diagnostics].
+  @override
+  String get message => toString();
+
+  /// Called whenever the Flutter framework catches an error.
+  ///
+  /// The default behavior is to call [presentError].
+  ///
+  /// You can set this to your own function to override this default behavior.
+  /// For example, you could report all errors to your server.
+  ///
+  /// If the error handler throws an exception, it will not be caught by the
+  /// Flutter framework.
+  ///
+  /// Set this to null to silently catch and ignore errors. This is not
+  /// recommended.
+  ///
+  /// Do not call [onError] directly, instead, call [reportError], which
+  /// forwards to [onError] if it is not null.
+  static FlutterExceptionHandler? onError = (FlutterErrorDetails details) => presentError(details);
+
+  /// Called by the Flutter framework before attempting to parse a [StackTrace].
+  ///
+  /// Some [StackTrace] implementations have a different toString format from
+  /// what the framework expects, like ones from package:stack_trace. To make
+  /// sure we can still parse and filter mangled [StackTrace]s, the framework
+  /// first calls this function to demangle them.
+  ///
+  /// This should be set in any environment that could propagate a non-standard
+  /// stack trace to the framework. Otherwise, the default behavior is to assume
+  /// all stack traces are in a standard format.
+  ///
+  /// The following example demangles package:stack_trace traces by converting
+  /// them into vm traces, which the framework is able to parse:
+  ///
+  /// ```dart
+  /// FlutterError.demangleStackTrace = (StackTrace stackTrace) {
+  ///   if (stack is stack_trace.Trace)
+  ///     return stack.vmTrace;
+  ///   if (stack is stack_trace.Chain)
+  ///     return stack.toTrace().vmTrace;
+  ///   return stack;
+  /// };
+  /// ```
+  static StackTraceDemangler demangleStackTrace = (StackTrace stackTrace) => stackTrace;
+
+  /// Called whenever the Flutter framework wants to present an error to the
+  /// users.
+  ///
+  /// The default behavior is to call [dumpErrorToConsole].
+  ///
+  /// Plugins can override how an error is to be presented to the user. For
+  /// example, the structured errors service extension sets its own method when
+  /// the extension is enabled. If you want to change how Flutter responds to an
+  /// error, use [onError] instead.
+  static FlutterExceptionHandler presentError = dumpErrorToConsole;
+
+  static int _errorCount = 0;
+
+  /// Resets the count of errors used by [dumpErrorToConsole] to decide whether
+  /// to show a complete error message or an abbreviated one.
+  ///
+  /// After this is called, the next error message will be shown in full.
+  static void resetErrorCount() {
+    _errorCount = 0;
+  }
+
+  /// The width to which [dumpErrorToConsole] will wrap lines.
+  ///
+  /// This can be used to ensure strings will not exceed the length at which
+  /// they will wrap, e.g. when placing ASCII art diagrams in messages.
+  static const int wrapWidth = 100;
+
+  /// Prints the given exception details to the console.
+  ///
+  /// The first time this is called, it dumps a very verbose message to the
+  /// console using [debugPrint].
+  ///
+  /// Subsequent calls only dump the first line of the exception, unless
+  /// `forceReport` is set to true (in which case it dumps the verbose message).
+  ///
+  /// Call [resetErrorCount] to cause this method to go back to acting as if it
+  /// had not been called before (so the next message is verbose again).
+  ///
+  /// The default behavior for the [onError] handler is to call this function.
+  static void dumpErrorToConsole(FlutterErrorDetails details, { bool forceReport = false }) {
+    assert(details != null);
+    assert(details.exception != null);
+    bool isInDebugMode = false;
+    assert(() {
+      // In checked mode, we ignore the "silent" flag.
+      isInDebugMode = true;
+      return true;
+    }());
+    final bool reportError = isInDebugMode || details.silent != true; // could be null
+    if (!reportError && !forceReport)
+      return;
+    if (_errorCount == 0 || forceReport) {
+      // Diagnostics is only available in debug mode. In profile and release modes fallback to plain print.
+      if (isInDebugMode) {
+        debugPrint(
+          TextTreeRenderer(
+            wrapWidth: wrapWidth,
+            wrapWidthProperties: wrapWidth,
+            maxDescendentsTruncatableNode: 5,
+          ).render(details.toDiagnosticsNode(style: DiagnosticsTreeStyle.error)).trimRight(),
+        );
+      } else {
+        debugPrintStack(
+          stackTrace: details.stack,
+          label: details.exception.toString(),
+          maxFrames: 100,
+        );
+      }
+    } else {
+      debugPrint('Another exception was thrown: ${details.summary}');
+    }
+    _errorCount += 1;
+  }
+
+  static final List<StackFilter> _stackFilters = <StackFilter>[];
+
+  /// Adds a stack filtering function to [defaultStackFilter].
+  ///
+  /// For example, the framework adds common patterns of element building to
+  /// elide tree-walking patterns in the stacktrace.
+  ///
+  /// Added filters are checked in order of addition. The first matching filter
+  /// wins, and subsequent filters will not be checked.
+  static void addDefaultStackFilter(StackFilter filter) {
+    _stackFilters.add(filter);
+  }
+
+  /// Converts a stack to a string that is more readable by omitting stack
+  /// frames that correspond to Dart internals.
+  ///
+  /// This is the default filter used by [dumpErrorToConsole] if the
+  /// [FlutterErrorDetails] object has no [FlutterErrorDetails.stackFilter]
+  /// callback.
+  ///
+  /// This function expects its input to be in the format used by
+  /// [StackTrace.toString()]. The output of this function is similar to that
+  /// format but the frame numbers will not be consecutive (frames are elided)
+  /// and the final line may be prose rather than a stack frame.
+  static Iterable<String> defaultStackFilter(Iterable<String> frames) {
+    final Map<String, int> removedPackagesAndClasses = <String, int>{
+      'dart:async-patch': 0,
+      'dart:async': 0,
+      'package:stack_trace': 0,
+      'class _AssertionError': 0,
+      'class _FakeAsync': 0,
+      'class _FrameCallbackEntry': 0,
+      'class _Timer': 0,
+      'class _RawReceivePortImpl': 0,
+    };
+    int skipped = 0;
+
+    final List<StackFrame> parsedFrames = StackFrame.fromStackString(frames.join('\n'));
+
+    for (int index = 0; index < parsedFrames.length; index += 1) {
+      final StackFrame frame = parsedFrames[index];
+      final String className = 'class ${frame.className}';
+      final String package = '${frame.packageScheme}:${frame.package}';
+      if (removedPackagesAndClasses.containsKey(className)) {
+        skipped += 1;
+        removedPackagesAndClasses.update(className, (int value) => value + 1);
+        parsedFrames.removeAt(index);
+        index -= 1;
+      } else if (removedPackagesAndClasses.containsKey(package)) {
+        skipped += 1;
+        removedPackagesAndClasses.update(package, (int value) => value + 1);
+        parsedFrames.removeAt(index);
+        index -= 1;
+      }
+    }
+    final List<String?> reasons = List<String?>.filled(parsedFrames.length, null, growable: false);
+    for (final StackFilter filter in _stackFilters) {
+      filter.filter(parsedFrames, reasons);
+    }
+
+    final List<String> result = <String>[];
+
+    // Collapse duplicated reasons.
+    for (int index = 0; index < parsedFrames.length; index += 1) {
+      final int start = index;
+      while (index < reasons.length - 1 && reasons[index] != null && reasons[index + 1] == reasons[index]) {
+        index++;
+      }
+      String suffix = '';
+      if (reasons[index] != null) {
+        if (index != start) {
+          suffix = ' (${index - start + 2} frames)';
+        } else {
+          suffix = ' (1 frame)';
+        }
+      }
+      final String resultLine = '${reasons[index] ?? parsedFrames[index].source}$suffix';
+      result.add(resultLine);
+    }
+
+    // Only include packages we actually elided from.
+    final List<String> where = <String>[
+      for (MapEntry<String, int> entry in removedPackagesAndClasses.entries)
+        if (entry.value > 0)
+          entry.key
+    ]..sort();
+    if (skipped == 1) {
+      result.add('(elided one frame from ${where.single})');
+    } else if (skipped > 1) {
+      if (where.length > 1)
+        where[where.length - 1] = 'and ${where.last}';
+      if (where.length > 2) {
+        result.add('(elided $skipped frames from ${where.join(", ")})');
+      } else {
+        result.add('(elided $skipped frames from ${where.join(" ")})');
+      }
+    }
+    return result;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    diagnostics.forEach(properties.add);
+  }
+
+  @override
+  String toStringShort() => 'FlutterError';
+
+  @override
+  String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
+    if (kReleaseMode) {
+      final Iterable<_ErrorDiagnostic> errors = diagnostics.whereType<_ErrorDiagnostic>();
+      return errors.isNotEmpty ? errors.first.valueToString() : toStringShort();
+    }
+    // Avoid wrapping lines.
+    final TextTreeRenderer renderer = TextTreeRenderer(wrapWidth: 4000000000);
+    return diagnostics.map((DiagnosticsNode node) => renderer.render(node).trimRight()).join('\n');
+  }
+
+  /// Calls [onError] with the given details, unless it is null.
+  static void reportError(FlutterErrorDetails details) {
+    assert(details != null);
+    assert(details.exception != null);
+    if (onError != null) {
+      onError!(details);
+    }
+  }
+}
+
+/// Dump the stack to the console using [debugPrint] and
+/// [FlutterError.defaultStackFilter].
+///
+/// If the `stackTrace` parameter is null, the [StackTrace.current] is used to
+/// obtain the stack.
+///
+/// The `maxFrames` argument can be given to limit the stack to the given number
+/// of lines before filtering is applied. By default, all stack lines are
+/// included.
+///
+/// The `label` argument, if present, will be printed before the stack.
+void debugPrintStack({StackTrace? stackTrace, String? label, int? maxFrames}) {
+  if (label != null)
+    debugPrint(label);
+  if (stackTrace == null) {
+    stackTrace = StackTrace.current;
+  } else {
+    stackTrace = FlutterError.demangleStackTrace(stackTrace);
+  }
+  Iterable<String> lines = stackTrace.toString().trimRight().split('\n');
+  if (kIsWeb && lines.isNotEmpty) {
+    // Remove extra call to StackTrace.current for web platform.
+    // TODO(ferhat): remove when https://github.com/flutter/flutter/issues/37635
+    // is addressed.
+    lines = lines.skipWhile((String line) {
+      return line.contains('StackTrace.current') ||
+             line.contains('dart-sdk/lib/_internal') ||
+             line.contains('dart:sdk_internal');
+    });
+  }
+  if (maxFrames != null)
+    lines = lines.take(maxFrames);
+  debugPrint(FlutterError.defaultStackFilter(lines).join('\n'));
+}
+
+/// Diagnostic with a [StackTrace] [value] suitable for displaying stack traces
+/// as part of a [FlutterError] object.
+class DiagnosticsStackTrace extends DiagnosticsBlock {
+  /// Creates a diagnostic for a stack trace.
+  ///
+  /// [name] describes a name the stacktrace is given, e.g.
+  /// `When the exception was thrown, this was the stack`.
+  /// [stackFilter] provides an optional filter to use to filter which frames
+  /// are included. If no filter is specified, [FlutterError.defaultStackFilter]
+  /// is used.
+  /// [showSeparator] indicates whether to include a ':' after the [name].
+  DiagnosticsStackTrace(
+    String name,
+    StackTrace? stack, {
+    IterableFilter<String>? stackFilter,
+    bool showSeparator = true,
+  }) : super(
+    name: name,
+    value: stack,
+    properties: _applyStackFilter(stack, stackFilter),
+    style: DiagnosticsTreeStyle.flat,
+    showSeparator: showSeparator,
+    allowTruncate: true,
+  );
+
+  /// Creates a diagnostic describing a single frame from a StackTrace.
+  DiagnosticsStackTrace.singleFrame(
+    String name, {
+    required String frame,
+    bool showSeparator = true,
+  }) : super(
+    name: name,
+    properties: <DiagnosticsNode>[_createStackFrame(frame)],
+    style: DiagnosticsTreeStyle.whitespace,
+    showSeparator: showSeparator,
+  );
+
+  static List<DiagnosticsNode> _applyStackFilter(
+    StackTrace? stack,
+    IterableFilter<String>? stackFilter,
+  ) {
+    if (stack == null)
+      return <DiagnosticsNode>[];
+    final IterableFilter<String> filter = stackFilter ?? FlutterError.defaultStackFilter;
+    final Iterable<String> frames = filter('${FlutterError.demangleStackTrace(stack)}'.trimRight().split('\n'));
+    return frames.map<DiagnosticsNode>(_createStackFrame).toList();
+  }
+
+  static DiagnosticsNode _createStackFrame(String frame) {
+    return DiagnosticsNode.message(frame, allowWrap: false);
+  }
+}
+
+class _FlutterErrorDetailsNode extends DiagnosticableNode<FlutterErrorDetails> {
+  _FlutterErrorDetailsNode({
+    String? name,
+    required FlutterErrorDetails value,
+    required DiagnosticsTreeStyle? style,
+  }) : super(
+    name: name,
+    value: value,
+    style: style,
+  );
+
+  @override
+  DiagnosticPropertiesBuilder? get builder {
+    final DiagnosticPropertiesBuilder? builder = super.builder;
+    if (builder == null){
+      return null;
+    }
+    Iterable<DiagnosticsNode> properties = builder.properties;
+    for (final DiagnosticPropertiesTransformer transformer in FlutterErrorDetails.propertiesTransformers) {
+      properties = transformer(properties);
+    }
+    return DiagnosticPropertiesBuilder.fromProperties(properties.toList());
+  }
+}
diff --git a/lib/src/foundation/basic_types.dart b/lib/src/foundation/basic_types.dart
new file mode 100644
index 0000000..b190362
--- /dev/null
+++ b/lib/src/foundation/basic_types.dart
@@ -0,0 +1,246 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+
+// COMMON SIGNATURES
+
+export 'package:flute/ui.dart' show VoidCallback;
+
+/// Signature for callbacks that report that an underlying value has changed.
+///
+/// See also:
+///
+///  * [ValueSetter], for callbacks that report that a value has been set.
+typedef ValueChanged<T> = void Function(T value);
+
+/// Signature for callbacks that report that a value has been set.
+///
+/// This is the same signature as [ValueChanged], but is used when the
+/// callback is called even if the underlying value has not changed.
+/// For example, service extensions use this callback because they
+/// call the callback whenever the extension is called with a
+/// value, regardless of whether the given value is new or not.
+///
+/// See also:
+///
+///  * [ValueGetter], the getter equivalent of this signature.
+///  * [AsyncValueSetter], an asynchronous version of this signature.
+typedef ValueSetter<T> = void Function(T value);
+
+/// Signature for callbacks that are to report a value on demand.
+///
+/// See also:
+///
+///  * [ValueSetter], the setter equivalent of this signature.
+///  * [AsyncValueGetter], an asynchronous version of this signature.
+typedef ValueGetter<T> = T Function();
+
+/// Signature for callbacks that filter an iterable.
+typedef IterableFilter<T> = Iterable<T> Function(Iterable<T> input);
+
+/// Signature of callbacks that have no arguments and return no data, but that
+/// return a [Future] to indicate when their work is complete.
+///
+/// See also:
+///
+///  * [VoidCallback], a synchronous version of this signature.
+///  * [AsyncValueGetter], a signature for asynchronous getters.
+///  * [AsyncValueSetter], a signature for asynchronous setters.
+typedef AsyncCallback = Future<void> Function();
+
+/// Signature for callbacks that report that a value has been set and return a
+/// [Future] that completes when the value has been saved.
+///
+/// See also:
+///
+///  * [ValueSetter], a synchronous version of this signature.
+///  * [AsyncValueGetter], the getter equivalent of this signature.
+typedef AsyncValueSetter<T> = Future<void> Function(T value);
+
+/// Signature for callbacks that are to asynchronously report a value on demand.
+///
+/// See also:
+///
+///  * [ValueGetter], a synchronous version of this signature.
+///  * [AsyncValueSetter], the setter equivalent of this signature.
+typedef AsyncValueGetter<T> = Future<T> Function();
+
+// LAZY CACHING ITERATOR
+
+/// A lazy caching version of [Iterable].
+///
+/// This iterable is efficient in the following ways:
+///
+///  * It will not walk the given iterator more than you ask for.
+///
+///  * If you use it twice (e.g. you check [isNotEmpty], then
+///    use [single]), it will only walk the given iterator
+///    once. This caching will even work efficiently if you are
+///    running two side-by-side iterators on the same iterable.
+///
+///  * [toList] uses its EfficientLength variant to create its
+///    list quickly.
+///
+/// It is inefficient in the following ways:
+///
+///  * The first iteration through has caching overhead.
+///
+///  * It requires more memory than a non-caching iterator.
+///
+///  * The [length] and [toList] properties immediately pre-cache the
+///    entire list. Using these fields therefore loses the laziness of
+///    the iterable. However, it still gets cached.
+///
+/// The caching behavior is propagated to the iterators that are
+/// created by [map], [where], [expand], [take], [takeWhile], [skip],
+/// and [skipWhile], and is used by the built-in methods that use an
+/// iterator like [isNotEmpty] and [single].
+///
+/// Because a CachingIterable only walks the underlying data once, it
+/// cannot be used multiple times with the underlying data changing
+/// between each use. You must create a new iterable each time. This
+/// also applies to any iterables derived from this one, e.g. as
+/// returned by `where`.
+class CachingIterable<E> extends IterableBase<E> {
+  /// Creates a CachingIterable using the given [Iterator] as the
+  /// source of data. The iterator must be non-null and must not throw
+  /// exceptions.
+  ///
+  /// Since the argument is an [Iterator], not an [Iterable], it is
+  /// guaranteed that the underlying data set will only be walked
+  /// once. If you have an [Iterable], you can pass its [iterator]
+  /// field as the argument to this constructor.
+  ///
+  /// You can use a `sync*` function with this as follows:
+  ///
+  /// ```dart
+  /// Iterable<int> range(int start, int end) sync* {
+  ///   for (int index = start; index <= end; index += 1)
+  ///     yield index;
+  ///  }
+  ///
+  /// Iterable<int> i = CachingIterable<int>(range(1, 5).iterator);
+  /// print(i.length); // walks the list
+  /// print(i.length); // efficient
+  /// ```
+  CachingIterable(this._prefillIterator);
+
+  final Iterator<E> _prefillIterator;
+  final List<E> _results = <E>[];
+
+  @override
+  Iterator<E> get iterator {
+    return _LazyListIterator<E>(this);
+  }
+
+  @override
+  Iterable<T> map<T>(T f(E e)) {
+    return CachingIterable<T>(super.map<T>(f).iterator);
+  }
+
+  @override
+  Iterable<E> where(bool test(E element)) {
+    return CachingIterable<E>(super.where(test).iterator);
+  }
+
+  @override
+  Iterable<T> expand<T>(Iterable<T> f(E element)) {
+    return CachingIterable<T>(super.expand<T>(f).iterator);
+  }
+
+  @override
+  Iterable<E> take(int count) {
+    return CachingIterable<E>(super.take(count).iterator);
+  }
+
+  @override
+  Iterable<E> takeWhile(bool test(E value)) {
+    return CachingIterable<E>(super.takeWhile(test).iterator);
+  }
+
+  @override
+  Iterable<E> skip(int count) {
+    return CachingIterable<E>(super.skip(count).iterator);
+  }
+
+  @override
+  Iterable<E> skipWhile(bool test(E value)) {
+    return CachingIterable<E>(super.skipWhile(test).iterator);
+  }
+
+  @override
+  int get length {
+    _precacheEntireList();
+    return _results.length;
+  }
+
+  @override
+  List<E> toList({ bool growable = true }) {
+    _precacheEntireList();
+    return List<E>.from(_results, growable: growable);
+  }
+
+  void _precacheEntireList() {
+    while (_fillNext()) { }
+  }
+
+  bool _fillNext() {
+    if (!_prefillIterator.moveNext())
+      return false;
+    _results.add(_prefillIterator.current);
+    return true;
+  }
+}
+
+class _LazyListIterator<E> implements Iterator<E> {
+  _LazyListIterator(this._owner) : _index = -1;
+
+  final CachingIterable<E> _owner;
+  int _index;
+
+  @override
+  E get current {
+    assert(_index >= 0); // called "current" before "moveNext()"
+    if (_index < 0 || _index == _owner._results.length)
+      throw StateError('current can not be call after moveNext has returned false');
+    return _owner._results[_index];
+  }
+
+  @override
+  bool moveNext() {
+    if (_index >= _owner._results.length)
+      return false;
+    _index += 1;
+    if (_index == _owner._results.length)
+      return _owner._fillNext();
+    return true;
+  }
+}
+
+/// A factory interface that also reports the type of the created objects.
+class Factory<T> {
+  /// Creates a new factory.
+  ///
+  /// The `constructor` parameter must not be null.
+  const Factory(this.constructor) : assert(constructor != null);
+
+  /// Creates a new object of type T.
+  final ValueGetter<T> constructor;
+
+  /// The type of the objects created by this factory.
+  Type get type => T;
+
+  @override
+  String toString() {
+    return 'Factory(type: $type)';
+  }
+}
+
+/// Linearly interpolate between two `Duration`s.
+Duration lerpDuration(Duration a, Duration b, double t) {
+  return Duration(
+    microseconds: (a.inMicroseconds + (b.inMicroseconds - a.inMicroseconds) * t).round(),
+  );
+}
diff --git a/lib/src/foundation/binding.dart b/lib/src/foundation/binding.dart
new file mode 100644
index 0000000..fafe17a
--- /dev/null
+++ b/lib/src/foundation/binding.dart
@@ -0,0 +1,614 @@
+// Copyright 2014 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.
+
+import 'dart:convert' show json;
+import 'dart:developer' as developer;
+import 'dart:io' show exit;
+import 'package:flute/ui.dart' as ui show SingletonFlutterWindow, Brightness, PlatformDispatcher, window;
+// Before adding any more dart:ui imports, please read the README.
+
+import 'package:meta/meta.dart';
+
+import 'assertions.dart';
+import 'basic_types.dart';
+import 'constants.dart';
+import 'debug.dart';
+import 'object.dart';
+import 'platform.dart';
+import 'print.dart';
+
+/// Signature for service extensions.
+///
+/// The returned map must not contain the keys "type" or "method", as
+/// they will be replaced before the value is sent to the client. The
+/// "type" key will be set to the string `_extensionType` to indicate
+/// that this is a return value from a service extension, and the
+/// "method" key will be set to the full name of the method.
+typedef ServiceExtensionCallback = Future<Map<String, dynamic>> Function(Map<String, String> parameters);
+
+/// Base class for mixins that provide singleton services (also known as
+/// "bindings").
+///
+/// To use this class in an `on` clause of a mixin, inherit from it and implement
+/// [initInstances()]. The mixin is guaranteed to only be constructed once in
+/// the lifetime of the app (more precisely, it will assert if constructed twice
+/// in checked mode).
+///
+/// The top-most layer used to write the application will have a concrete class
+/// that inherits from [BindingBase] and uses all the various [BindingBase]
+/// mixins (such as [ServicesBinding]). For example, the Widgets library in
+/// Flutter introduces a binding called [WidgetsFlutterBinding]. The relevant
+/// library defines how to create the binding. It could be implied (for example,
+/// [WidgetsFlutterBinding] is automatically started from [runApp]), or the
+/// application might be required to explicitly call the constructor.
+abstract class BindingBase {
+  /// Default abstract constructor for bindings.
+  ///
+  /// First calls [initInstances] to have bindings initialize their
+  /// instance pointers and other state, then calls
+  /// [initServiceExtensions] to have bindings initialize their
+  /// observatory service extensions, if any.
+  BindingBase() {
+    developer.Timeline.startSync('Framework initialization');
+
+    assert(!_debugInitialized);
+    initInstances();
+    assert(_debugInitialized);
+
+    assert(!_debugServiceExtensionsRegistered);
+    initServiceExtensions();
+    assert(_debugServiceExtensionsRegistered);
+
+    developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
+
+    developer.Timeline.finishSync();
+  }
+
+  static bool _debugInitialized = false;
+  static bool _debugServiceExtensionsRegistered = false;
+
+  /// The main window to which this binding is bound.
+  ///
+  /// A number of additional bindings are defined as extensions of
+  /// [BindingBase], e.g., [ServicesBinding], [RendererBinding], and
+  /// [WidgetsBinding]. Each of these bindings define behaviors that interact
+  /// with a [ui.SingletonFlutterWindow].
+  ///
+  /// Each of these other bindings could individually access a
+  /// [ui.SingletonFlutterWindow] statically, but that would preclude the
+  /// ability to test its behaviors with a fake window for verification
+  /// purposes.  Therefore, [BindingBase] exposes this
+  /// [ui.SingletonFlutterWindow] for use by other bindings.  A subclass of
+  /// [BindingBase], such as [TestWidgetsFlutterBinding], can override this
+  /// accessor to return a different [ui.SingletonFlutterWindow] implementation,
+  /// such as a [TestWindow].
+  ///
+  /// The `window` is a singleton meant for use by applications that only have a
+  /// single main window. In addition to the properties of [ui.FlutterWindow],
+  /// `window` provides access to platform-specific properties and callbacks
+  /// available on the [platformDispatcher].
+  ///
+  /// For applications designed for more than one main window, prefer using the
+  /// [platformDispatcher] to access available views via
+  /// [ui.PlatformDispatcher.views].
+  ///
+  /// However, multiple window support is not yet implemented, so currently this
+  /// provides access to the one and only window.
+  // TODO(gspencergoog): remove the preceding note once multi-window support is
+  // active.
+  ui.SingletonFlutterWindow get window => ui.window;
+
+  /// The [ui.PlatformDispatcher] to which this binding is bound.
+  ///
+  /// A number of additional bindings are defined as extensions of
+  /// [BindingBase], e.g., [ServicesBinding], [RendererBinding], and
+  /// [WidgetsBinding]. Each of these bindings define behaviors that interact
+  /// with a [ui.PlatformDispatcher], e.g., [ServicesBinding] registers a
+  /// [ui.PlatformDispatcher.onPlatformMessage] handler, and [RendererBinding]
+  /// registers [ui.PlatformDispatcher.onMetricsChanged],
+  /// [ui.PlatformDispatcher.onTextScaleFactorChanged],
+  /// [ui.PlatformDispatcher.onSemanticsEnabledChanged], and
+  /// [ui.PlatformDispatcher.onSemanticsAction] handlers.
+  ///
+  /// Each of these other bindings could individually access a
+  /// [ui.PlatformDispatcher] statically, but that would preclude the ability to
+  /// test these behaviors with a fake platform dispatcher for verification
+  /// purposes. Therefore, [BindingBase] exposes this [ui.PlatformDispatcher]
+  /// for use by other bindings. A subclass of [BindingBase], such as
+  /// [TestWidgetsFlutterBinding], can override this accessor to return a
+  /// different [ui.PlatformDispatcher] implementation.
+  ui.PlatformDispatcher get platformDispatcher => ui.PlatformDispatcher.instance;
+
+  /// The initialization method. Subclasses override this method to hook into
+  /// the platform and otherwise configure their services. Subclasses must call
+  /// "super.initInstances()".
+  ///
+  /// By convention, if the service is to be provided as a singleton, it should
+  /// be exposed as `MixinClassName.instance`, a static getter that returns
+  /// `MixinClassName._instance`, a static field that is set by
+  /// `initInstances()`.
+  @protected
+  @mustCallSuper
+  void initInstances() {
+    assert(!_debugInitialized);
+    assert(() {
+      _debugInitialized = true;
+      return true;
+    }());
+  }
+
+  /// Called when the binding is initialized, to register service
+  /// extensions.
+  ///
+  /// Bindings that want to expose service extensions should overload
+  /// this method to register them using calls to
+  /// [registerSignalServiceExtension],
+  /// [registerBoolServiceExtension],
+  /// [registerNumericServiceExtension], and
+  /// [registerServiceExtension] (in increasing order of complexity).
+  ///
+  /// Implementations of this method must call their superclass
+  /// implementation.
+  ///
+  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
+  ///
+  /// See also:
+  ///
+  ///  * <https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#rpcs-requests-and-responses>
+  @protected
+  @mustCallSuper
+  void initServiceExtensions() {
+    assert(!_debugServiceExtensionsRegistered);
+
+    assert(() {
+      registerSignalServiceExtension(
+        name: 'reassemble',
+        callback: reassembleApplication,
+      );
+      return true;
+    }());
+
+    if (!kReleaseMode && !kIsWeb) {
+      registerSignalServiceExtension(
+        name: 'exit',
+        callback: _exitApplication,
+      );
+    }
+
+    assert(() {
+      const String platformOverrideExtensionName = 'platformOverride';
+      registerServiceExtension(
+        name: platformOverrideExtensionName,
+        callback: (Map<String, String> parameters) async {
+          if (parameters.containsKey('value')) {
+            switch (parameters['value']) {
+              case 'android':
+                debugDefaultTargetPlatformOverride = TargetPlatform.android;
+                break;
+              case 'fuchsia':
+                debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
+                break;
+              case 'iOS':
+                debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
+                break;
+              case 'linux':
+                debugDefaultTargetPlatformOverride = TargetPlatform.linux;
+                break;
+              case 'macOS':
+                debugDefaultTargetPlatformOverride = TargetPlatform.macOS;
+                break;
+              case 'windows':
+                debugDefaultTargetPlatformOverride = TargetPlatform.windows;
+                break;
+              case 'default':
+              default:
+                debugDefaultTargetPlatformOverride = null;
+            }
+            _postExtensionStateChangedEvent(
+              platformOverrideExtensionName,
+              defaultTargetPlatform.toString().substring('$TargetPlatform.'.length),
+            );
+            await reassembleApplication();
+          }
+          return <String, dynamic>{
+            'value': defaultTargetPlatform
+                     .toString()
+                     .substring('$TargetPlatform.'.length),
+          };
+        },
+      );
+
+      const String brightnessOverrideExtensionName = 'brightnessOverride';
+      registerServiceExtension(
+        name: brightnessOverrideExtensionName,
+        callback: (Map<String, String> parameters) async {
+          if (parameters.containsKey('value')) {
+            switch (parameters['value']) {
+              case 'Brightness.light':
+                debugBrightnessOverride = ui.Brightness.light;
+                break;
+              case 'Brightness.dark':
+                debugBrightnessOverride = ui.Brightness.dark;
+                break;
+              default:
+                debugBrightnessOverride = null;
+            }
+            _postExtensionStateChangedEvent(
+              brightnessOverrideExtensionName,
+              (debugBrightnessOverride ?? window.platformBrightness).toString(),
+            );
+            await reassembleApplication();
+          }
+          return <String, dynamic>{
+            'value': (debugBrightnessOverride ?? window.platformBrightness).toString(),
+          };
+        },
+      );
+      return true;
+    }());
+    assert(() {
+      _debugServiceExtensionsRegistered = true;
+      return true;
+    }());
+  }
+
+  /// Whether [lockEvents] is currently locking events.
+  ///
+  /// Binding subclasses that fire events should check this first, and if it is
+  /// set, queue events instead of firing them.
+  ///
+  /// Events should be flushed when [unlocked] is called.
+  @protected
+  bool get locked => _lockCount > 0;
+  int _lockCount = 0;
+
+  /// Locks the dispatching of asynchronous events and callbacks until the
+  /// callback's future completes.
+  ///
+  /// This causes input lag and should therefore be avoided when possible. It is
+  /// primarily intended for use during non-user-interactive time such as to
+  /// allow [reassembleApplication] to block input while it walks the tree
+  /// (which it partially does asynchronously).
+  ///
+  /// The [Future] returned by the `callback` argument is returned by [lockEvents].
+  @protected
+  Future<void> lockEvents(Future<void> callback()) {
+    developer.Timeline.startSync('Lock events');
+
+    assert(callback != null);
+    _lockCount += 1;
+    final Future<void> future = callback();
+    assert(future != null, 'The lockEvents() callback returned null; it should return a Future<void> that completes when the lock is to expire.');
+    future.whenComplete(() {
+      _lockCount -= 1;
+      if (!locked) {
+        developer.Timeline.finishSync();
+        unlocked();
+      }
+    });
+    return future;
+  }
+
+  /// Called by [lockEvents] when events get unlocked.
+  ///
+  /// This should flush any events that were queued while [locked] was true.
+  @protected
+  @mustCallSuper
+  void unlocked() {
+    assert(!locked);
+  }
+
+  /// Cause the entire application to redraw, e.g. after a hot reload.
+  ///
+  /// This is used by development tools when the application code has changed,
+  /// to cause the application to pick up any changed code. It can be triggered
+  /// manually by sending the `ext.flutter.reassemble` service extension signal.
+  ///
+  /// This method is very computationally expensive and should not be used in
+  /// production code. There is never a valid reason to cause the entire
+  /// application to repaint in production. All aspects of the Flutter framework
+  /// know how to redraw when necessary. It is only necessary in development
+  /// when the code is literally changed on the fly (e.g. in hot reload) or when
+  /// debug flags are being toggled.
+  ///
+  /// While this method runs, events are locked (e.g. pointer events are not
+  /// dispatched).
+  ///
+  /// Subclasses (binding classes) should override [performReassemble] to react
+  /// to this method being called. This method itself should not be overridden.
+  Future<void> reassembleApplication() {
+    return lockEvents(performReassemble);
+  }
+
+  /// This method is called by [reassembleApplication] to actually cause the
+  /// application to reassemble, e.g. after a hot reload.
+  ///
+  /// Bindings are expected to use this method to re-register anything that uses
+  /// closures, so that they do not keep pointing to old code, and to flush any
+  /// caches of previously computed values, in case the new code would compute
+  /// them differently. For example, the rendering layer triggers the entire
+  /// application to repaint when this is called.
+  ///
+  /// Do not call this method directly. Instead, use [reassembleApplication].
+  @mustCallSuper
+  @protected
+  Future<void> performReassemble() {
+    FlutterError.resetErrorCount();
+    return Future<void>.value();
+  }
+
+  /// Registers a service extension method with the given name (full
+  /// name "ext.flutter.name"), which takes no arguments and returns
+  /// no value.
+  ///
+  /// Calls the `callback` callback when the service extension is called.
+  ///
+  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
+  @protected
+  void registerSignalServiceExtension({
+    required String name,
+    required AsyncCallback callback,
+  }) {
+    assert(name != null);
+    assert(callback != null);
+    registerServiceExtension(
+      name: name,
+      callback: (Map<String, String> parameters) async {
+        await callback();
+        return <String, dynamic>{};
+      },
+    );
+  }
+
+  /// Registers a service extension method with the given name (full
+  /// name "ext.flutter.name"), which takes a single argument
+  /// "enabled" which can have the value "true" or the value "false"
+  /// or can be omitted to read the current value. (Any value other
+  /// than "true" is considered equivalent to "false". Other arguments
+  /// are ignored.)
+  ///
+  /// Calls the `getter` callback to obtain the value when
+  /// responding to the service extension method being called.
+  ///
+  /// Calls the `setter` callback with the new value when the
+  /// service extension method is called with a new value.
+  ///
+  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
+  @protected
+  void registerBoolServiceExtension({
+    required String name,
+    required AsyncValueGetter<bool> getter,
+    required AsyncValueSetter<bool> setter,
+  }) {
+    assert(name != null);
+    assert(getter != null);
+    assert(setter != null);
+    registerServiceExtension(
+      name: name,
+      callback: (Map<String, String> parameters) async {
+        if (parameters.containsKey('enabled')) {
+          await setter(parameters['enabled'] == 'true');
+          _postExtensionStateChangedEvent(name, await getter() ? 'true' : 'false');
+        }
+        return <String, dynamic>{'enabled': await getter() ? 'true' : 'false'};
+      },
+    );
+  }
+
+  /// Registers a service extension method with the given name (full
+  /// name "ext.flutter.name"), which takes a single argument with the
+  /// same name as the method which, if present, must have a value
+  /// that can be parsed by [double.parse], and can be omitted to read
+  /// the current value. (Other arguments are ignored.)
+  ///
+  /// Calls the `getter` callback to obtain the value when
+  /// responding to the service extension method being called.
+  ///
+  /// Calls the `setter` callback with the new value when the
+  /// service extension method is called with a new value.
+  ///
+  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
+  @protected
+  void registerNumericServiceExtension({
+    required String name,
+    required AsyncValueGetter<double> getter,
+    required AsyncValueSetter<double> setter,
+  }) {
+    assert(name != null);
+    assert(getter != null);
+    assert(setter != null);
+    registerServiceExtension(
+      name: name,
+      callback: (Map<String, String> parameters) async {
+        if (parameters.containsKey(name)) {
+          await setter(double.parse(parameters[name]!));
+          _postExtensionStateChangedEvent(name, (await getter()).toString());
+        }
+        return <String, dynamic>{name: (await getter()).toString()};
+      },
+    );
+  }
+
+  /// Sends an event when a service extension's state is changed.
+  ///
+  /// Clients should listen for this event to stay aware of the current service
+  /// extension state. Any service extension that manages a state should call
+  /// this method on state change.
+  ///
+  /// `value` reflects the newly updated service extension value.
+  ///
+  /// This will be called automatically for service extensions registered via
+  /// [registerBoolServiceExtension], [registerNumericServiceExtension], or
+  /// [registerStringServiceExtension].
+  void _postExtensionStateChangedEvent(String name, dynamic value) {
+    postEvent(
+      'Flutter.ServiceExtensionStateChanged',
+      <String, dynamic>{
+        'extension': 'ext.flutter.$name',
+        'value': value,
+      },
+    );
+  }
+
+  /// All events dispatched by a [BindingBase] use this method instead of
+  /// calling [developer.postEvent] directly so that tests for [BindingBase]
+  /// can track which events were dispatched by overriding this method.
+  @protected
+  void postEvent(String eventKind, Map<String, dynamic> eventData) {
+    developer.postEvent(eventKind, eventData);
+  }
+
+  /// Registers a service extension method with the given name (full name
+  /// "ext.flutter.name"), which optionally takes a single argument with the
+  /// name "value". If the argument is omitted, the value is to be read,
+  /// otherwise it is to be set. Returns the current value.
+  ///
+  /// Calls the `getter` callback to obtain the value when
+  /// responding to the service extension method being called.
+  ///
+  /// Calls the `setter` callback with the new value when the
+  /// service extension method is called with a new value.
+  ///
+  /// {@macro flutter.foundation.BindingBase.registerServiceExtension}
+  @protected
+  void registerStringServiceExtension({
+    required String name,
+    required AsyncValueGetter<String> getter,
+    required AsyncValueSetter<String> setter,
+  }) {
+    assert(name != null);
+    assert(getter != null);
+    assert(setter != null);
+    registerServiceExtension(
+      name: name,
+      callback: (Map<String, String> parameters) async {
+        if (parameters.containsKey('value')) {
+          await setter(parameters['value']!);
+          _postExtensionStateChangedEvent(name, await getter());
+        }
+        return <String, dynamic>{'value': await getter()};
+      },
+    );
+  }
+
+  /// Registers a service extension method with the given name (full name
+  /// "ext.flutter.name").
+  ///
+  /// The given callback is called when the extension method is called. The
+  /// callback must return a [Future] that either eventually completes to a
+  /// return value in the form of a name/value map where the values can all be
+  /// converted to JSON using `json.encode()` (see [JsonEncoder]), or fails. In
+  /// case of failure, the failure is reported to the remote caller and is
+  /// dumped to the logs.
+  ///
+  /// The returned map will be mutated.
+  ///
+  /// {@template flutter.foundation.BindingBase.registerServiceExtension}
+  /// A registered service extension can only be activated if the vm-service
+  /// is included in the build, which only happens in debug and profile mode.
+  /// Although a service extension cannot be used in release mode its code may
+  /// still be included in the Dart snapshot and blow up binary size if it is
+  /// not wrapped in a guard that allows the tree shaker to remove it (see
+  /// sample code below).
+  ///
+  /// {@tool snippet}
+  /// The following code registers a service extension that is only included in
+  /// debug builds.
+  ///
+  /// ```dart
+  /// void myRegistrationFunction() {
+  ///   assert(() {
+  ///     // Register your service extension here.
+  ///     return true;
+  ///   }());
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@tool snippet}
+  /// A service extension registered with the following code snippet is
+  /// available in debug and profile mode.
+  ///
+  /// ```dart
+  /// void myRegistrationFunction() {
+  ///   // kReleaseMode is defined in the 'flutter/foundation.dart' package.
+  ///   if (!kReleaseMode) {
+  ///     // Register your service extension here.
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// Both guards ensure that Dart's tree shaker can remove the code for the
+  /// service extension in release builds.
+  /// {@endtemplate}
+  @protected
+  void registerServiceExtension({
+    required String name,
+    required ServiceExtensionCallback callback,
+  }) {
+    assert(name != null);
+    assert(callback != null);
+    final String methodName = 'ext.flutter.$name';
+    developer.registerExtension(methodName, (String method, Map<String, String> parameters) async {
+      assert(method == methodName);
+      assert(() {
+        if (debugInstrumentationEnabled)
+          debugPrint('service extension method received: $method($parameters)');
+        return true;
+      }());
+
+      // VM service extensions are handled as "out of band" messages by the VM,
+      // which means they are handled at various times, generally ASAP.
+      // Notably, this includes being handled in the middle of microtask loops.
+      // While this makes sense for some service extensions (e.g. "dump current
+      // stack trace", which explicitly doesn't want to wait for a loop to
+      // complete), Flutter extensions need not be handled with such high
+      // priority. Further, handling them with such high priority exposes us to
+      // the possibility that they're handled in the middle of a frame, which
+      // breaks many assertions. As such, we ensure they we run the callbacks
+      // on the outer event loop here.
+      await debugInstrumentAction<void>('Wait for outer event loop', () {
+        return Future<void>.delayed(Duration.zero);
+      });
+
+      Object? caughtException;
+      StackTrace? caughtStack;
+      late Map<String, dynamic> result;
+      try {
+        result = await callback(parameters);
+      } catch (exception, stack) {
+        caughtException = exception;
+        caughtStack = stack;
+      }
+      if (caughtException == null) {
+        result['type'] = '_extensionType';
+        result['method'] = method;
+        return developer.ServiceExtensionResponse.result(json.encode(result));
+      } else {
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: caughtException,
+          stack: caughtStack,
+          context: ErrorDescription('during a service extension callback for "$method"'),
+        ));
+        return developer.ServiceExtensionResponse.error(
+          developer.ServiceExtensionResponse.extensionError,
+          json.encode(<String, String>{
+            'exception': caughtException.toString(),
+            'stack': caughtStack.toString(),
+            'method': method,
+          }),
+        );
+      }
+    });
+  }
+
+  @override
+  String toString() => '<${objectRuntimeType(this, 'BindingBase')}>';
+}
+
+/// Terminate the Flutter application.
+Future<void> _exitApplication() async {
+  exit(0);
+}
diff --git a/lib/src/foundation/bitfield.dart b/lib/src/foundation/bitfield.dart
new file mode 100644
index 0000000..e0b07a8
--- /dev/null
+++ b/lib/src/foundation/bitfield.dart
@@ -0,0 +1,48 @@
+// Copyright 2014 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.
+
+import '_bitfield_io.dart'
+  if (dart.library.html) '_bitfield_web.dart' as _bitfield;
+
+/// The largest SMI value.
+///
+/// See <https://www.dartlang.org/articles/numeric-computation/#smis-and-mints>
+///
+/// When compiling to JavaScript, this value is not supported since it is
+/// larger than the maximum safe 32bit integer.
+const int kMaxUnsignedSMI = _bitfield.kMaxUnsignedSMI;
+
+/// A BitField over an enum (or other class whose values implement "index").
+/// Only the first 62 values of the enum can be used as indices.
+///
+/// When compiling to JavaScript, this class is not supported.
+abstract class BitField<T extends dynamic> {
+  /// Creates a bit field of all zeros.
+  ///
+  /// The given length must be at most 62.
+  factory BitField(int length) = _bitfield.BitField<T>;
+
+  /// Creates a bit field filled with a particular value.
+  ///
+  /// If the value argument is true, the bits are filled with ones. Otherwise,
+  /// the bits are filled with zeros.
+  ///
+  /// The given length must be at most 62.
+  factory BitField.filled(int length, bool value) = _bitfield.BitField<T>.filled;
+
+  /// Returns whether the bit with the given index is set to one.
+  bool operator [](T index);
+
+  /// Sets the bit with the given index to the given value.
+  ///
+  /// If value is true, the bit with the given index is set to one. Otherwise,
+  /// the bit is set to zero.
+  void operator []=(T index, bool value);
+
+  /// Sets all the bits to the given value.
+  ///
+  /// If the value is true, the bits are all set to one. Otherwise, the bits are
+  /// all set to zero. Defaults to setting all the bits to zero.
+  void reset([ bool value = false ]);
+}
diff --git a/lib/src/foundation/change_notifier.dart b/lib/src/foundation/change_notifier.dart
new file mode 100644
index 0000000..f44cacb
--- /dev/null
+++ b/lib/src/foundation/change_notifier.dart
@@ -0,0 +1,314 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+
+import 'package:meta/meta.dart';
+
+import 'assertions.dart';
+import 'basic_types.dart';
+import 'diagnostics.dart';
+
+/// An object that maintains a list of listeners.
+///
+/// The listeners are typically used to notify clients that the object has been
+/// updated.
+///
+/// There are two variants of this interface:
+///
+///  * [ValueListenable], an interface that augments the [Listenable] interface
+///    with the concept of a _current value_.
+///
+///  * [Animation], an interface that augments the [ValueListenable] interface
+///    to add the concept of direction (forward or reverse).
+///
+/// Many classes in the Flutter API use or implement these interfaces. The
+/// following subclasses are especially relevant:
+///
+///  * [ChangeNotifier], which can be subclassed or mixed in to create objects
+///    that implement the [Listenable] interface.
+///
+///  * [ValueNotifier], which implements the [ValueListenable] interface with
+///    a mutable value that triggers the notifications when modified.
+///
+/// The terms "notify clients", "send notifications", "trigger notifications",
+/// and "fire notifications" are used interchangeably.
+///
+/// See also:
+///
+///  * [AnimatedBuilder], a widget that uses a builder callback to rebuild
+///    whenever a given [Listenable] triggers its notifications. This widget is
+///    commonly used with [Animation] subclasses, hence its name, but is by no
+///    means limited to animations, as it can be used with any [Listenable]. It
+///    is a subclass of [AnimatedWidget], which can be used to create widgets
+///    that are driven from a [Listenable].
+///  * [ValueListenableBuilder], a widget that uses a builder callback to
+///    rebuild whenever a [ValueListenable] object triggers its notifications,
+///    providing the builder with the value of the object.
+///  * [InheritedNotifier], an abstract superclass for widgets that use a
+///    [Listenable]'s notifications to trigger rebuilds in descendant widgets
+///    that declare a dependency on them, using the [InheritedWidget] mechanism.
+///  * [new Listenable.merge], which creates a [Listenable] that triggers
+///    notifications whenever any of a list of other [Listenable]s trigger their
+///    notifications.
+abstract class Listenable {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const Listenable();
+
+  /// Return a [Listenable] that triggers when any of the given [Listenable]s
+  /// themselves trigger.
+  ///
+  /// The list must not be changed after this method has been called. Doing so
+  /// will lead to memory leaks or exceptions.
+  ///
+  /// The list may contain nulls; they are ignored.
+  factory Listenable.merge(List<Listenable?> listenables) = _MergingListenable;
+
+  /// Register a closure to be called when the object notifies its listeners.
+  void addListener(VoidCallback listener);
+
+  /// Remove a previously registered closure from the list of closures that the
+  /// object notifies.
+  void removeListener(VoidCallback listener);
+}
+
+/// An interface for subclasses of [Listenable] that expose a [value].
+///
+/// This interface is implemented by [ValueNotifier<T>] and [Animation<T>], and
+/// allows other APIs to accept either of those implementations interchangeably.
+///
+/// See also:
+///
+///  * [ValueListenableBuilder], a widget that uses a builder callback to
+///    rebuild whenever a [ValueListenable] object triggers its notifications,
+///    providing the builder with the value of the object.
+abstract class ValueListenable<T> extends Listenable {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const ValueListenable();
+
+  /// The current value of the object. When the value changes, the callbacks
+  /// registered with [addListener] will be invoked.
+  T get value;
+}
+
+class _ListenerEntry extends LinkedListEntry<_ListenerEntry> {
+  _ListenerEntry(this.listener);
+  final VoidCallback listener;
+}
+
+/// A class that can be extended or mixed in that provides a change notification
+/// API using [VoidCallback] for notifications.
+///
+/// It is O(1) for adding listeners and O(N) for removing listeners and dispatching
+/// notifications (where N is the number of listeners).
+///
+/// See also:
+///
+///  * [ValueNotifier], which is a [ChangeNotifier] that wraps a single value.
+class ChangeNotifier implements Listenable {
+  LinkedList<_ListenerEntry>? _listeners = LinkedList<_ListenerEntry>();
+
+  bool _debugAssertNotDisposed() {
+    assert(() {
+      if (_listeners == null) {
+        throw FlutterError(
+          'A $runtimeType was used after being disposed.\n'
+          'Once you have called dispose() on a $runtimeType, it can no longer be used.'
+        );
+      }
+      return true;
+    }());
+    return true;
+  }
+
+  /// Whether any listeners are currently registered.
+  ///
+  /// Clients should not depend on this value for their behavior, because having
+  /// one listener's logic change when another listener happens to start or stop
+  /// listening will lead to extremely hard-to-track bugs. Subclasses might use
+  /// this information to determine whether to do any work when there are no
+  /// listeners, however; for example, resuming a [Stream] when a listener is
+  /// added and pausing it when a listener is removed.
+  ///
+  /// Typically this is used by overriding [addListener], checking if
+  /// [hasListeners] is false before calling `super.addListener()`, and if so,
+  /// starting whatever work is needed to determine when to call
+  /// [notifyListeners]; and similarly, by overriding [removeListener], checking
+  /// if [hasListeners] is false after calling `super.removeListener()`, and if
+  /// so, stopping that same work.
+  @protected
+  bool get hasListeners {
+    assert(_debugAssertNotDisposed());
+    return _listeners!.isNotEmpty;
+  }
+
+  /// Register a closure to be called when the object changes.
+  ///
+  /// If the given closure is already registered, an additional instance is
+  /// added, and must be removed the same number of times it is added before it
+  /// will stop being called.
+  ///
+  /// This method must not be called after [dispose] has been called.
+  ///
+  /// {@template flutter.foundation.ChangeNotifier.addListener}
+  /// If a listener is added twice, and is removed once during an iteration
+  /// (e.g. in response to a notification), it will still be called again. If,
+  /// on the other hand, it is removed as many times as it was registered, then
+  /// it will no longer be called. This odd behavior is the result of the
+  /// [ChangeNotifier] not being able to determine which listener is being
+  /// removed, since they are identical, therefore it will conservatively still
+  /// call all the listeners when it knows that any are still registered.
+  ///
+  /// This surprising behavior can be unexpectedly observed when registering a
+  /// listener on two separate objects which are both forwarding all
+  /// registrations to a common upstream object.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [removeListener], which removes a previously registered closure from
+  ///    the list of closures that are notified when the object changes.
+  @override
+  void addListener(VoidCallback listener) {
+    assert(_debugAssertNotDisposed());
+    _listeners!.add(_ListenerEntry(listener));
+  }
+
+  /// Remove a previously registered closure from the list of closures that are
+  /// notified when the object changes.
+  ///
+  /// If the given listener is not registered, the call is ignored.
+  ///
+  /// This method must not be called after [dispose] has been called.
+  ///
+  /// {@macro flutter.foundation.ChangeNotifier.addListener}
+  ///
+  /// See also:
+  ///
+  ///  * [addListener], which registers a closure to be called when the object
+  ///    changes.
+  @override
+  void removeListener(VoidCallback listener) {
+    assert(_debugAssertNotDisposed());
+    for (final _ListenerEntry entry in _listeners!) {
+      if (entry.listener == listener) {
+        entry.unlink();
+        return;
+      }
+    }
+  }
+
+  /// Discards any resources used by the object. After this is called, the
+  /// object is not in a usable state and should be discarded (calls to
+  /// [addListener] and [removeListener] will throw after the object is
+  /// disposed).
+  ///
+  /// This method should only be called by the object's owner.
+  @mustCallSuper
+  void dispose() {
+    assert(_debugAssertNotDisposed());
+    _listeners = null;
+  }
+
+  /// Call all the registered listeners.
+  ///
+  /// Call this method whenever the object changes, to notify any clients the
+  /// object may have changed. Listeners that are added during this iteration
+  /// will not be visited. Listeners that are removed during this iteration will
+  /// not be visited after they are removed.
+  ///
+  /// Exceptions thrown by listeners will be caught and reported using
+  /// [FlutterError.reportError].
+  ///
+  /// This method must not be called after [dispose] has been called.
+  ///
+  /// Surprising behavior can result when reentrantly removing a listener (e.g.
+  /// in response to a notification) that has been registered multiple times.
+  /// See the discussion at [removeListener].
+  @protected
+  @visibleForTesting
+  void notifyListeners() {
+    assert(_debugAssertNotDisposed());
+    if (_listeners!.isEmpty)
+      return;
+
+    final List<_ListenerEntry> localListeners = List<_ListenerEntry>.from(_listeners!);
+
+    for (final _ListenerEntry entry in localListeners) {
+      try {
+        if (entry.list != null)
+          entry.listener();
+      } catch (exception, stack) {
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          library: 'foundation library',
+          context: ErrorDescription('while dispatching notifications for $runtimeType'),
+          informationCollector: () sync* {
+            yield DiagnosticsProperty<ChangeNotifier>(
+              'The $runtimeType sending notification was',
+              this,
+              style: DiagnosticsTreeStyle.errorProperty,
+            );
+          },
+        ));
+      }
+    }
+  }
+}
+
+class _MergingListenable extends Listenable {
+  _MergingListenable(this._children);
+
+  final List<Listenable?> _children;
+
+  @override
+  void addListener(VoidCallback listener) {
+    for (final Listenable? child in _children) {
+      child?.addListener(listener);
+    }
+  }
+
+  @override
+  void removeListener(VoidCallback listener) {
+    for (final Listenable? child in _children) {
+      child?.removeListener(listener);
+    }
+  }
+
+  @override
+  String toString() {
+    return 'Listenable.merge([${_children.join(", ")}])';
+  }
+}
+
+/// A [ChangeNotifier] that holds a single value.
+///
+/// When [value] is replaced with something that is not equal to the old
+/// value as evaluated by the equality operator ==, this class notifies its
+/// listeners.
+class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
+  /// Creates a [ChangeNotifier] that wraps this value.
+  ValueNotifier(this._value);
+
+  /// The current value stored in this notifier.
+  ///
+  /// When the value is replaced with something that is not equal to the old
+  /// value as evaluated by the equality operator ==, this class notifies its
+  /// listeners.
+  @override
+  T get value => _value;
+  T _value;
+  set value(T newValue) {
+    if (_value == newValue)
+      return;
+    _value = newValue;
+    notifyListeners();
+  }
+
+  @override
+  String toString() => '${describeIdentity(this)}($value)';
+}
diff --git a/lib/src/foundation/collections.dart b/lib/src/foundation/collections.dart
new file mode 100644
index 0000000..d62b156
--- /dev/null
+++ b/lib/src/foundation/collections.dart
@@ -0,0 +1,355 @@
+// Copyright 2014 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.
+
+// TODO(ianh): These should be on the Set and List classes themselves.
+
+/// Compares two sets for deep equality.
+///
+/// Returns true if the sets are both null, or if they are both non-null, have
+/// the same length, and contain the same members. Returns false otherwise.
+/// Order is not compared.
+///
+/// The term "deep" above refers to the first level of equality: if the elements
+/// are maps, lists, sets, or other collections/composite objects, then the
+/// values of those elements are not compared element by element unless their
+/// equality operators ([Object.==]) do so.
+///
+/// See also:
+///
+///  * [listEquals], which does something similar for lists.
+///  * [mapEquals], which does something similar for maps.
+bool setEquals<T>(Set<T>? a, Set<T>? b) {
+  if (a == null)
+    return b == null;
+  if (b == null || a.length != b.length)
+    return false;
+  if (identical(a, b))
+    return true;
+  for (final T value in a) {
+    if (!b.contains(value))
+      return false;
+  }
+  return true;
+}
+
+/// Compares two lists for deep equality.
+///
+/// Returns true if the lists are both null, or if they are both non-null, have
+/// the same length, and contain the same members in the same order. Returns
+/// false otherwise.
+///
+/// The term "deep" above refers to the first level of equality: if the elements
+/// are maps, lists, sets, or other collections/composite objects, then the
+/// values of those elements are not compared element by element unless their
+/// equality operators ([Object.==]) do so.
+///
+/// See also:
+///
+///  * [setEquals], which does something similar for sets.
+///  * [mapEquals], which does something similar for maps.
+bool listEquals<T>(List<T>? a, List<T>? b) {
+  if (a == null)
+    return b == null;
+  if (b == null || a.length != b.length)
+    return false;
+  if (identical(a, b))
+    return true;
+  for (int index = 0; index < a.length; index += 1) {
+    if (a[index] != b[index])
+      return false;
+  }
+  return true;
+}
+
+/// Compares two maps for deep equality.
+///
+/// Returns true if the maps are both null, or if they are both non-null, have
+/// the same length, and contain the same keys associated with the same values.
+/// Returns false otherwise.
+///
+/// The term "deep" above refers to the first level of equality: if the elements
+/// are maps, lists, sets, or other collections/composite objects, then the
+/// values of those elements are not compared element by element unless their
+/// equality operators ([Object.==]) do so.
+///
+/// See also:
+///
+///  * [setEquals], which does something similar for sets.
+///  * [listEquals], which does something similar for lists.
+bool mapEquals<T, U>(Map<T, U>? a, Map<T, U>? b) {
+  if (a == null)
+    return b == null;
+  if (b == null || a.length != b.length)
+    return false;
+  if (identical(a, b))
+    return true;
+  for (final T key in a.keys) {
+    if (!b.containsKey(key) || b[key] != a[key]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+
+/// Returns the position of `value` in the `sortedList`, if it exists.
+///
+/// Returns `-1` if the `value` is not in the list. Requires the list items
+/// to implement [Comparable] and the `sortedList` to already be ordered.
+int binarySearch<T extends Comparable<Object>>(List<T> sortedList, T value) {
+  int min = 0;
+  int max = sortedList.length;
+  while (min < max) {
+    final int mid = min + ((max - min) >> 1);
+    final T element = sortedList[mid];
+    final int comp = element.compareTo(value);
+    if (comp == 0) {
+      return mid;
+    }
+    if (comp < 0) {
+      min = mid + 1;
+    } else {
+      max = mid;
+    }
+  }
+  return -1;
+}
+
+/// Limit below which merge sort defaults to insertion sort.
+const int _kMergeSortLimit = 32;
+
+/// Sorts a list between `start` (inclusive) and `end` (exclusive) using the
+/// merge sort algorithm.
+///
+/// If `compare` is omitted, this defaults to calling [Comparable.compareTo] on
+/// the objects. If any object is not [Comparable], this throws a [TypeError]
+/// (The stack trace may call it `_CastError` or `_TypeError`, but to catch it,
+/// use [TypeError]).
+///
+/// Merge-sorting works by splitting the job into two parts, sorting each
+/// recursively, and then merging the two sorted parts.
+///
+/// This takes on the order of `n * log(n)` comparisons and moves to sort `n`
+/// elements, but requires extra space of about the same size as the list being
+/// sorted.
+///
+/// This merge sort is stable: Equal elements end up in the same order as they
+/// started in.
+///
+/// For small lists (less than 32 elements), `mergeSort` automatically uses an
+/// insertion sort instead, as that is more efficient for small lists. The
+/// insertion sort is also stable.
+void mergeSort<T>(
+  List<T> list, {
+  int start = 0,
+  int? end,
+  int Function(T, T)? compare,
+}) {
+  end ??= list.length;
+  compare ??= _defaultCompare<T>();
+
+  final int length = end - start;
+  if (length < 2) {
+    return;
+  }
+  if (length < _kMergeSortLimit) {
+    _insertionSort<T>(list, compare: compare, start: start, end: end);
+    return;
+  }
+  // Special case the first split instead of directly calling _mergeSort,
+  // because the _mergeSort requires its target to be different from its source,
+  // and it requires extra space of the same size as the list to sort. This
+  // split allows us to have only half as much extra space, and it ends up in
+  // the original place.
+  final int middle = start + ((end - start) >> 1);
+  final int firstLength = middle - start;
+  final int secondLength = end - middle;
+  // secondLength is always the same as firstLength, or one greater.
+  final List<T> scratchSpace = List<T>.filled(secondLength, list[start]);
+  _mergeSort<T>(list, compare, middle, end, scratchSpace, 0);
+  final int firstTarget = end - firstLength;
+  _mergeSort<T>(list, compare, start, middle, list, firstTarget);
+  _merge<T>(compare, list, firstTarget, end, scratchSpace, 0, secondLength, list, start);
+}
+
+/// Returns a [Comparator] that asserts that its first argument is comparable.
+Comparator<T> _defaultCompare<T>() {
+  // If we specify Comparable<T> here, it fails if the type is an int, because
+  // int isn't a subtype of comparable. Leaving out the type implicitly converts
+  // it to a num, which is a comparable.
+  return (T value1, T value2) => (value1 as Comparable<dynamic>).compareTo(value2);
+}
+
+/// Sort a list between `start` (inclusive) and `end` (exclusive) using
+/// insertion sort.
+///
+/// If `compare` is omitted, this defaults to calling [Comparable.compareTo] on
+/// the objects. If any object is not [Comparable], this throws a [TypeError]
+/// (The stack trace may call it `_CastError` or `_TypeError`, but to catch it,
+/// use [TypeError]).
+///
+/// Insertion sort is a simple sorting algorithm. For `n` elements it does on
+/// the order of `n * log(n)` comparisons but up to `n` squared moves. The
+/// sorting is performed in-place, without using extra memory.
+///
+/// For short lists the many moves have less impact than the simple algorithm,
+/// and it is often the favored sorting algorithm for short lists.
+///
+/// This insertion sort is stable: Equal elements end up in the same order as
+/// they started in.
+void _insertionSort<T>(
+  List<T> list, {
+  int Function(T, T)? compare,
+  int start = 0,
+  int? end,
+}) {
+  // If the same method could have both positional and named optional
+  // parameters, this should be (list, [start, end], {compare}).
+  compare ??= _defaultCompare<T>();
+  end ??= list.length;
+
+  for (int pos = start + 1; pos < end; pos++) {
+    int min = start;
+    int max = pos;
+    final T element = list[pos];
+    while (min < max) {
+      final int mid = min + ((max - min) >> 1);
+      final int comparison = compare(element, list[mid]);
+      if (comparison < 0) {
+        max = mid;
+      } else {
+        min = mid + 1;
+      }
+    }
+    list.setRange(min + 1, pos + 1, list, min);
+    list[min] = element;
+  }
+}
+
+/// Performs an insertion sort into a potentially different list than the one
+/// containing the original values.
+///
+/// It will work in-place as well.
+void _movingInsertionSort<T>(
+  List<T> list,
+  int Function(T, T) compare,
+  int start,
+  int end,
+  List<T?> target,
+  int targetOffset,
+) {
+  final int length = end - start;
+  if (length == 0) {
+    return;
+  }
+  target[targetOffset] = list[start];
+  for (int i = 1; i < length; i++) {
+    final T element = list[start + i];
+    int min = targetOffset;
+    int max = targetOffset + i;
+    while (min < max) {
+      final int mid = min + ((max - min) >> 1);
+      if (compare(element, target[mid] as T) < 0) {
+        max = mid;
+      } else {
+        min = mid + 1;
+      }
+    }
+    target.setRange(min + 1, targetOffset + i + 1, target, min);
+    target[min] = element;
+  }
+}
+
+/// Sorts `list` from `start` to `end` into `target` at `targetOffset`.
+///
+/// The `target` list must be able to contain the range from `start` to `end`
+/// after `targetOffset`.
+///
+/// Allows target to be the same list as `list`, as long as it's not overlapping
+/// the `start..end` range.
+void _mergeSort<T>(
+    List<T> list,
+    int Function(T, T) compare,
+    int start,
+    int end,
+    List<T> target,
+    int targetOffset,
+    ) {
+  final int length = end - start;
+  if (length < _kMergeSortLimit) {
+    _movingInsertionSort<T>(list, compare, start, end, target, targetOffset);
+    return;
+  }
+  final int middle = start + (length >> 1);
+  final int firstLength = middle - start;
+  final int secondLength = end - middle;
+  // Here secondLength >= firstLength (differs by at most one).
+  final int targetMiddle = targetOffset + firstLength;
+  // Sort the second half into the end of the target area.
+  _mergeSort<T>(list, compare, middle, end, target, targetMiddle);
+  // Sort the first half into the end of the source area.
+  _mergeSort<T>(list, compare, start, middle, list, middle);
+  // Merge the two parts into the target area.
+  _merge<T>(
+    compare,
+    list,
+    middle,
+    middle + firstLength,
+    target,
+    targetMiddle,
+    targetMiddle + secondLength,
+    target,
+    targetOffset,
+  );
+}
+
+/// Merges two lists into a target list.
+///
+/// One of the input lists may be positioned at the end of the target list.
+///
+/// For equal object, elements from `firstList` are always preferred. This
+/// allows the merge to be stable if the first list contains elements that
+/// started out earlier than the ones in `secondList`.
+void _merge<T>(
+  int Function(T, T) compare,
+  List<T> firstList,
+  int firstStart,
+  int firstEnd,
+  List<T> secondList,
+  int secondStart,
+  int secondEnd,
+  List<T> target,
+  int targetOffset,
+) {
+  // No empty lists reaches here.
+  assert(firstStart < firstEnd);
+  assert(secondStart < secondEnd);
+  int cursor1 = firstStart;
+  int cursor2 = secondStart;
+  T firstElement = firstList[cursor1++];
+  T secondElement = secondList[cursor2++];
+  while (true) {
+    if (compare(firstElement, secondElement) <= 0) {
+      target[targetOffset++] = firstElement;
+      if (cursor1 == firstEnd) {
+        // Flushing second list after loop.
+        break;
+      }
+      firstElement = firstList[cursor1++];
+    } else {
+      target[targetOffset++] = secondElement;
+      if (cursor2 != secondEnd) {
+        secondElement = secondList[cursor2++];
+        continue;
+      }
+      // Second list empties first. Flushing first list here.
+      target[targetOffset++] = firstElement;
+      target.setRange(targetOffset, targetOffset + (firstEnd - cursor1), firstList, cursor1);
+      return;
+    }
+  }
+  // First list empties first. Reached by break above.
+  target[targetOffset++] = secondElement;
+  target.setRange(targetOffset, targetOffset + (secondEnd - cursor2), secondList, cursor2);
+}
diff --git a/lib/src/foundation/consolidate_response.dart b/lib/src/foundation/consolidate_response.dart
new file mode 100644
index 0000000..be1e032
--- /dev/null
+++ b/lib/src/foundation/consolidate_response.dart
@@ -0,0 +1,129 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:typed_data';
+
+/// Signature for getting notified when chunks of bytes are received while
+/// consolidating the bytes of an [HttpClientResponse] into a [Uint8List].
+///
+/// The `cumulative` parameter will contain the total number of bytes received
+/// thus far. If the response has been gzipped, this number will be the number
+/// of compressed bytes that have been received _across the wire_.
+///
+/// The `total` parameter will contain the _expected_ total number of bytes to
+/// be received across the wire (extracted from the value of the
+/// `Content-Length` HTTP response header), or null if the size of the response
+/// body is not known in advance (this is common for HTTP chunked transfer
+/// encoding, which itself is common when a large amount of data is being
+/// returned to the client and the total size of the response may not be known
+/// until the request has been fully processed).
+///
+/// This is used in [consolidateHttpClientResponseBytes].
+typedef BytesReceivedCallback = void Function(int cumulative, int? total);
+
+/// Efficiently converts the response body of an [HttpClientResponse] into a
+/// [Uint8List].
+///
+/// The future returned will forward any error emitted by `response`.
+///
+/// The `onBytesReceived` callback, if specified, will be invoked for every
+/// chunk of bytes that is received while consolidating the response bytes.
+/// If the callback throws an error, processing of the response will halt, and
+/// the returned future will complete with the error that was thrown by the
+/// callback. For more information on how to interpret the parameters to the
+/// callback, see the documentation on [BytesReceivedCallback].
+///
+/// If the `response` is gzipped and the `autoUncompress` parameter is true,
+/// this will automatically un-compress the bytes in the returned list if it
+/// hasn't already been done via [HttpClient.autoUncompress]. To get compressed
+/// bytes from this method (assuming the response is sending compressed bytes),
+/// set both [HttpClient.autoUncompress] to false and the `autoUncompress`
+/// parameter to false.
+Future<Uint8List> consolidateHttpClientResponseBytes(
+  HttpClientResponse response, {
+  bool autoUncompress = true,
+  BytesReceivedCallback? onBytesReceived,
+}) {
+  assert(autoUncompress != null);
+  final Completer<Uint8List> completer = Completer<Uint8List>.sync();
+
+  final _OutputBuffer output = _OutputBuffer();
+  ByteConversionSink sink = output;
+  int? expectedContentLength = response.contentLength;
+  if (expectedContentLength == -1)
+    expectedContentLength = null;
+  switch (response.compressionState) {
+    case HttpClientResponseCompressionState.compressed:
+      if (autoUncompress) {
+        // We need to un-compress the bytes as they come in.
+        sink = gzip.decoder.startChunkedConversion(output);
+      }
+      break;
+    case HttpClientResponseCompressionState.decompressed:
+      // response.contentLength will not match our bytes stream, so we declare
+      // that we don't know the expected content length.
+      expectedContentLength = null;
+      break;
+    case HttpClientResponseCompressionState.notCompressed:
+      // Fall-through.
+      break;
+  }
+
+  int bytesReceived = 0;
+  late final StreamSubscription<List<int>> subscription;
+  subscription = response.listen((List<int> chunk) {
+    sink.add(chunk);
+    if (onBytesReceived != null) {
+      bytesReceived += chunk.length;
+      try {
+        onBytesReceived(bytesReceived, expectedContentLength);
+      } catch (error, stackTrace) {
+        completer.completeError(error, stackTrace);
+        subscription.cancel();
+        return;
+      }
+    }
+  }, onDone: () {
+    sink.close();
+    completer.complete(output.bytes);
+  }, onError: completer.completeError, cancelOnError: true);
+
+  return completer.future;
+}
+
+class _OutputBuffer extends ByteConversionSinkBase {
+  List<List<int>>? _chunks = <List<int>>[];
+  int _contentLength = 0;
+  Uint8List? _bytes;
+
+  @override
+  void add(List<int> chunk) {
+    assert(_bytes == null);
+    _chunks!.add(chunk);
+    _contentLength += chunk.length;
+  }
+
+  @override
+  void close() {
+    if (_bytes != null) {
+      // We've already been closed; this is a no-op
+      return;
+    }
+    _bytes = Uint8List(_contentLength);
+    int offset = 0;
+    for (final List<int> chunk in _chunks!) {
+      _bytes!.setRange(offset, offset + chunk.length, chunk);
+      offset += chunk.length;
+    }
+    _chunks = null;
+  }
+
+  Uint8List get bytes {
+    assert(_bytes != null);
+    return _bytes!;
+  }
+}
diff --git a/lib/src/foundation/constants.dart b/lib/src/foundation/constants.dart
new file mode 100644
index 0000000..5accce0
--- /dev/null
+++ b/lib/src/foundation/constants.dart
@@ -0,0 +1,49 @@
+// Copyright 2014 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.
+
+/// A constant that is true if the application was compiled in release mode.
+///
+/// More specifically, this is a constant that is true if the application was
+/// compiled in Dart with the '-Ddart.vm.product=true' flag.
+///
+/// Since this is a const value, it can be used to indicate to the compiler that
+/// a particular block of code will not be executed in release mode, and hence
+/// can be removed.
+const bool kReleaseMode = bool.fromEnvironment('dart.vm.product', defaultValue: false);
+
+/// A constant that is true if the application was compiled in profile mode.
+///
+/// More specifically, this is a constant that is true if the application was
+/// compiled in Dart with the '-Ddart.vm.profile=true' flag.
+///
+/// Since this is a const value, it can be used to indicate to the compiler that
+/// a particular block of code will not be executed in profile mode, an hence
+/// can be removed.
+const bool kProfileMode = bool.fromEnvironment('dart.vm.profile', defaultValue: false);
+
+/// A constant that is true if the application was compiled in debug mode.
+///
+/// More specifically, this is a constant that is true if the application was
+/// not compiled with '-Ddart.vm.product=true' and '-Ddart.vm.profile=true'.
+///
+/// Since this is a const value, it can be used to indicate to the compiler that
+/// a particular block of code will not be executed in debug mode, and hence
+/// can be removed.
+const bool kDebugMode = !kReleaseMode && !kProfileMode;
+
+/// The epsilon of tolerable double precision error.
+///
+/// This is used in various places in the framework to allow for floating point
+/// precision loss in calculations. Differences below this threshold are safe to
+/// disregard.
+const double precisionErrorTolerance = 1e-10;
+
+/// A constant that is true if the application was compiled to run on the web.
+///
+/// This implementation takes advantage of the fact that JavaScript does not
+/// support integers. In this environment, Dart's doubles and ints are
+/// backed by the same kind of object. Thus a double `0.0` is identical
+/// to an integer `0`. This is not true for Dart code running in AOT or on the
+/// VM.
+const bool kIsWeb = identical(0, 0.0);
diff --git a/lib/src/foundation/debug.dart b/lib/src/foundation/debug.dart
new file mode 100644
index 0000000..719bc3c
--- /dev/null
+++ b/lib/src/foundation/debug.dart
@@ -0,0 +1,111 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui show Brightness;
+
+import 'assertions.dart';
+import 'platform.dart';
+import 'print.dart';
+
+/// Returns true if none of the foundation library debug variables have been
+/// changed.
+///
+/// This function is used by the test framework to ensure that debug variables
+/// haven't been inadvertently changed.
+///
+/// The `debugPrintOverride` argument can be specified to indicate the expected
+/// value of the [debugPrint] variable. This is useful for test frameworks that
+/// override [debugPrint] themselves and want to check that their own custom
+/// value wasn't overridden by a test.
+///
+/// See [the foundation library](foundation/foundation-library.html)
+/// for a complete list.
+bool debugAssertAllFoundationVarsUnset(String reason, { DebugPrintCallback debugPrintOverride = debugPrintThrottled }) {
+  assert(() {
+    if (debugPrint != debugPrintOverride ||
+        debugDefaultTargetPlatformOverride != null ||
+        debugDoublePrecision != null ||
+        debugBrightnessOverride != null)
+      throw FlutterError(reason);
+    return true;
+  }());
+  return true;
+}
+
+/// Boolean value indicating whether [debugInstrumentAction] will instrument
+/// actions in debug builds.
+bool debugInstrumentationEnabled = false;
+
+/// Runs the specified [action], timing how long the action takes in debug
+/// builds when [debugInstrumentationEnabled] is true.
+///
+/// The instrumentation will be printed to the logs using [debugPrint]. In
+/// non-debug builds, or when [debugInstrumentationEnabled] is false, this will
+/// run [action] without any instrumentation.
+///
+/// Returns the result of running [action].
+///
+/// See also:
+///
+///  * [Timeline], which is used to record synchronous tracing events for
+///    visualization in Chrome's tracing format. This method does not
+///    implicitly add any timeline events.
+Future<T> debugInstrumentAction<T>(String description, Future<T> action()) async {
+  bool instrument = false;
+  assert(() {
+    instrument = debugInstrumentationEnabled;
+    return true;
+  }());
+  if (instrument) {
+    final Stopwatch stopwatch = Stopwatch()..start();
+    try {
+      return await action();
+    } finally {
+      stopwatch.stop();
+      debugPrint('Action "$description" took ${stopwatch.elapsed}');
+    }
+  } else {
+    return action();
+  }
+}
+
+/// Argument passed to [Timeline] events in order to cause those events to be
+/// shown in the developer-centric version of the Observatory Timeline.
+///
+/// Generally these indicate landmark events such as the build phase or layout.
+///
+/// See also:
+///
+///  * [Timeline.startSync], which typically takes this value as its `arguments`
+///    argument.
+const Map<String, String> timelineArgumentsIndicatingLandmarkEvent = <String, String>{
+  'mode': 'basic',
+};
+
+/// Configure [debugFormatDouble] using [num.toStringAsPrecision].
+///
+/// Defaults to null, which uses the default logic of [debugFormatDouble].
+int? debugDoublePrecision;
+
+/// Formats a double to have standard formatting.
+///
+/// This behavior can be overridden by [debugDoublePrecision].
+String debugFormatDouble(double? value) {
+  if (value == null) {
+    return 'null';
+  }
+  if (debugDoublePrecision != null) {
+    return value.toStringAsPrecision(debugDoublePrecision!);
+  }
+  return value.toStringAsFixed(1);
+}
+
+/// A setting that can be used to override the platform [Brightness] exposed
+/// from [BindingBase.platformDispatcher].
+///
+/// See also:
+///
+///  * [WidgetsApp], which uses the [debugBrightnessOverride] setting in debug mode
+///    to construct a [MediaQueryData].
+ui.Brightness? debugBrightnessOverride;
diff --git a/lib/src/foundation/diagnostics.dart b/lib/src/foundation/diagnostics.dart
new file mode 100644
index 0000000..209eaa3
--- /dev/null
+++ b/lib/src/foundation/diagnostics.dart
@@ -0,0 +1,3726 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:meta/meta.dart';
+
+import 'assertions.dart';
+import 'constants.dart';
+import 'debug.dart';
+import 'object.dart';
+
+// Examples can assume:
+// late int rows, columns;
+// late String _name;
+// late bool inherit;
+
+/// The various priority levels used to filter which diagnostics are shown and
+/// omitted.
+///
+/// Trees of Flutter diagnostics can be very large so filtering the diagnostics
+/// shown matters. Typically filtering to only show diagnostics with at least
+/// level [debug] is appropriate.
+///
+/// In release mode, this level may not have any effect, as diagnostics in
+/// release mode are compacted or truncated to reduce binary size.
+enum DiagnosticLevel {
+  /// Diagnostics that should not be shown.
+  ///
+  /// If a user chooses to display [hidden] diagnostics, they should not expect
+  /// the diagnostics to be formatted consistently with other diagnostics and
+  /// they should expect them to sometimes be misleading. For example,
+  /// [FlagProperty] and [ObjectFlagProperty] have uglier formatting when the
+  /// property `value` does does not match a value with a custom flag
+  /// description. An example of a misleading diagnostic is a diagnostic for
+  /// a property that has no effect because some other property of the object is
+  /// set in a way that causes the hidden property to have no effect.
+  hidden,
+
+  /// A diagnostic that is likely to be low value but where the diagnostic
+  /// display is just as high quality as a diagnostic with a higher level.
+  ///
+  /// Use this level for diagnostic properties that match their default value
+  /// and other cases where showing a diagnostic would not add much value such
+  /// as an [IterableProperty] where the value is empty.
+  fine,
+
+  /// Diagnostics that should only be shown when performing fine grained
+  /// debugging of an object.
+  ///
+  /// Unlike a [fine] diagnostic, these diagnostics provide important
+  /// information about the object that is likely to be needed to debug. Used by
+  /// properties that are important but where the property value is too verbose
+  /// (e.g. 300+ characters long) to show with a higher diagnostic level.
+  debug,
+
+  /// Interesting diagnostics that should be typically shown.
+  info,
+
+  /// Very important diagnostics that indicate problematic property values.
+  ///
+  /// For example, use if you would write the property description
+  /// message in ALL CAPS.
+  warning,
+
+  /// Diagnostics that provide a hint about best practices.
+  ///
+  /// For example, a diagnostic describing best practices for fixing an error.
+  hint,
+
+  /// Diagnostics that summarize other diagnostics present.
+  ///
+  /// For example, use this level for a short one or two line summary
+  /// describing other diagnostics present.
+  summary,
+
+  /// Diagnostics that indicate errors or unexpected conditions.
+  ///
+  /// For example, use for property values where computing the value throws an
+  /// exception.
+  error,
+
+  /// Special level indicating that no diagnostics should be shown.
+  ///
+  /// Do not specify this level for diagnostics. This level is only used to
+  /// filter which diagnostics are shown.
+  off,
+}
+
+/// Styles for displaying a node in a [DiagnosticsNode] tree.
+///
+/// In release mode, these styles may be ignored, as diagnostics are compacted
+/// or truncated to save on binary size.
+///
+/// See also:
+///
+///  * [DiagnosticsNode.toStringDeep], which dumps text art trees for these
+///    styles.
+enum DiagnosticsTreeStyle {
+  /// A style that does not display the tree, for release mode.
+  none,
+
+  /// Sparse style for displaying trees.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderObject], which uses this style.
+  sparse,
+
+  /// Connects a node to its parent with a dashed line.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderSliverMultiBoxAdaptor], which uses this style to distinguish
+  ///    offstage children from onstage children.
+  offstage,
+
+  /// Slightly more compact version of the [sparse] style.
+  ///
+  /// See also:
+  ///
+  ///  * [Element], which uses this style.
+  dense,
+
+  /// Style that enables transitioning from nodes of one style to children of
+  /// another.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderParagraph], which uses this style to display a [TextSpan] child
+  ///    in a way that is compatible with the [DiagnosticsTreeStyle.sparse]
+  ///    style of the [RenderObject] tree.
+  transition,
+
+  /// Style for displaying content describing an error.
+  ///
+  /// See also:
+  ///
+  ///  * [FlutterError], which uses this style for the root node in a tree
+  ///    describing an error.
+  error,
+
+  /// Render the tree just using whitespace without connecting parents to
+  /// children using lines.
+  ///
+  /// See also:
+  ///
+  ///  * [SliverGeometry], which uses this style.
+  whitespace,
+
+  /// Render the tree without indenting children at all.
+  ///
+  /// See also:
+  ///
+  ///  * [DiagnosticsStackTrace], which uses this style.
+  flat,
+
+  /// Render the tree on a single line without showing children.
+  singleLine,
+
+  /// Render the tree using a style appropriate for properties that are part
+  /// of an error message.
+  ///
+  /// The name is placed on one line with the value and properties placed on
+  /// the following line.
+  ///
+  /// See also:
+  ///
+  ///  * [singleLine], which displays the same information but keeps the
+  ///    property and value on the same line.
+  errorProperty,
+
+  /// Render only the immediate properties of a node instead of the full tree.
+  ///
+  /// See also:
+  ///
+  ///  * [DebugOverflowIndicatorMixin], which uses this style to display just
+  ///    the immediate children of a node.
+  shallow,
+
+  /// Render only the children of a node truncating before the tree becomes too
+  /// large.
+  truncateChildren,
+}
+
+/// Configuration specifying how a particular [DiagnosticsTreeStyle] should be
+/// rendered as text art.
+///
+/// In release mode, these configurations may be ignored, as diagnostics are
+/// compacted or truncated to save on binary size.
+///
+/// See also:
+///
+///  * [sparseTextConfiguration], which is a typical style.
+///  * [transitionTextConfiguration], which is an example of a complex tree style.
+///  * [DiagnosticsNode.toStringDeep], for code using [TextTreeConfiguration]
+///    to render text art for arbitrary trees of [DiagnosticsNode] objects.
+class TextTreeConfiguration {
+  /// Create a configuration object describing how to render a tree as text.
+  ///
+  /// All of the arguments must not be null.
+  TextTreeConfiguration({
+    required this.prefixLineOne,
+    required this.prefixOtherLines,
+    required this.prefixLastChildLineOne,
+    required this.prefixOtherLinesRootNode,
+    required this.linkCharacter,
+    required this.propertyPrefixIfChildren,
+    required this.propertyPrefixNoChildren,
+    this.lineBreak = '\n',
+    this.lineBreakProperties = true,
+    this.afterName = ':',
+    this.afterDescriptionIfBody = '',
+    this.afterDescription = '',
+    this.beforeProperties = '',
+    this.afterProperties = '',
+    this.mandatoryAfterProperties = '',
+    this.propertySeparator = '',
+    this.bodyIndent = '',
+    this.footer = '',
+    this.showChildren = true,
+    this.addBlankLineIfNoChildren = true,
+    this.isNameOnOwnLine = false,
+    this.isBlankLineBetweenPropertiesAndChildren = true,
+    this.beforeName = '',
+    this.suffixLineOne = '',
+    this.mandatoryFooter = '',
+  }) : assert(prefixLineOne != null),
+       assert(prefixOtherLines != null),
+       assert(prefixLastChildLineOne != null),
+       assert(prefixOtherLinesRootNode != null),
+       assert(linkCharacter != null),
+       assert(propertyPrefixIfChildren != null),
+       assert(propertyPrefixNoChildren != null),
+       assert(lineBreak != null),
+       assert(lineBreakProperties != null),
+       assert(afterName != null),
+       assert(afterDescriptionIfBody != null),
+       assert(afterDescription != null),
+       assert(beforeProperties != null),
+       assert(afterProperties != null),
+       assert(propertySeparator != null),
+       assert(bodyIndent != null),
+       assert(footer != null),
+       assert(showChildren != null),
+       assert(addBlankLineIfNoChildren != null),
+       assert(isNameOnOwnLine != null),
+       assert(isBlankLineBetweenPropertiesAndChildren != null),
+       childLinkSpace = ' ' * linkCharacter.length;
+
+  /// Prefix to add to the first line to display a child with this style.
+  final String prefixLineOne;
+
+  /// Suffix to add to end of the first line to make its length match the footer.
+  final String suffixLineOne;
+
+  /// Prefix to add to other lines to display a child with this style.
+  ///
+  /// [prefixOtherLines] should typically be one character shorter than
+  /// [prefixLineOne] is.
+  final String prefixOtherLines;
+
+  /// Prefix to add to the first line to display the last child of a node with
+  /// this style.
+  final String prefixLastChildLineOne;
+
+  /// Additional prefix to add to other lines of a node if this is the root node
+  /// of the tree.
+  final String prefixOtherLinesRootNode;
+
+  /// Prefix to add before each property if the node as children.
+  ///
+  /// Plays a similar role to [linkCharacter] except that some configurations
+  /// intentionally use a different line style than the [linkCharacter].
+  final String propertyPrefixIfChildren;
+
+  /// Prefix to add before each property if the node does not have children.
+  ///
+  /// This string is typically a whitespace string the same length as
+  /// [propertyPrefixIfChildren] but can have a different length.
+  final String propertyPrefixNoChildren;
+
+  /// Character to use to draw line linking parent to child.
+  ///
+  /// The first child does not require a line but all subsequent children do
+  /// with the line drawn immediately before the left edge of the previous
+  /// sibling.
+  final String linkCharacter;
+
+  /// Whitespace to draw instead of the childLink character if this node is the
+  /// last child of its parent so no link line is required.
+  final String childLinkSpace;
+
+  /// Character(s) to use to separate lines.
+  ///
+  /// Typically leave set at the default value of '\n' unless this style needs
+  /// to treat lines differently as is the case for
+  /// [singleLineTextConfiguration].
+  final String lineBreak;
+
+  /// Whether to place line breaks between properties or to leave all
+  /// properties on one line.
+  final bool lineBreakProperties;
+
+
+  /// Text added immediately before the name of the node.
+  ///
+  /// See [errorTextConfiguration] for an example of using this to achieve a
+  /// custom line art style.
+  final String beforeName;
+
+  /// Text added immediately after the name of the node.
+  ///
+  /// See [transitionTextConfiguration] for an example of using a value other
+  /// than ':' to achieve a custom line art style.
+  final String afterName;
+
+  /// Text to add immediately after the description line of a node with
+  /// properties and/or children if the node has a body.
+  final String afterDescriptionIfBody;
+
+  /// Text to add immediately after the description line of a node with
+  /// properties and/or children.
+  final String afterDescription;
+
+  /// Optional string to add before the properties of a node.
+  ///
+  /// Only displayed if the node has properties.
+  /// See [singleLineTextConfiguration] for an example of using this field
+  /// to enclose the property list with parenthesis.
+  final String beforeProperties;
+
+  /// Optional string to add after the properties of a node.
+  ///
+  /// See documentation for [beforeProperties].
+  final String afterProperties;
+
+  /// Mandatory string to add after the properties of a node regardless of
+  /// whether the node has any properties.
+  final String mandatoryAfterProperties;
+
+  /// Property separator to add between properties.
+  ///
+  /// See [singleLineTextConfiguration] for an example of using this field
+  /// to render properties as a comma separated list.
+  final String propertySeparator;
+
+  /// Prefix to add to all lines of the body of the tree node.
+  ///
+  /// The body is all content in the node other than the name and description.
+  final String bodyIndent;
+
+  /// Whether the children of a node should be shown.
+  ///
+  /// See [singleLineTextConfiguration] for an example of using this field to
+  /// hide all children of a node.
+  final bool showChildren;
+
+  /// Whether to add a blank line at the end of the output for a node if it has
+  /// no children.
+  ///
+  /// See [denseTextConfiguration] for an example of setting this to false.
+  final bool addBlankLineIfNoChildren;
+
+  /// Whether the name should be displayed on the same line as the description.
+  final bool isNameOnOwnLine;
+
+  /// Footer to add as its own line at the end of a non-root node.
+  ///
+  /// See [transitionTextConfiguration] for an example of using footer to draw a box
+  /// around the node. [footer] is indented the same amount as [prefixOtherLines].
+  final String footer;
+
+  /// Footer to add even for root nodes.
+  final String mandatoryFooter;
+
+  /// Add a blank line between properties and children if both are present.
+  final bool isBlankLineBetweenPropertiesAndChildren;
+}
+
+/// Default text tree configuration.
+///
+/// Example:
+/// ```
+/// <root_name>: <root_description>
+///  │ <property1>
+///  │ <property2>
+///  │ ...
+///  │ <propertyN>
+///  ├─<child_name>: <child_description>
+///  │ │ <property1>
+///  │ │ <property2>
+///  │ │ ...
+///  │ │ <propertyN>
+///  │ │
+///  │ └─<child_name>: <child_description>
+///  │     <property1>
+///  │     <property2>
+///  │     ...
+///  │     <propertyN>
+///  │
+///  └─<child_name>: <child_description>'
+///    <property1>
+///    <property2>
+///    ...
+///    <propertyN>
+/// ```
+///
+/// See also:
+///
+///  * [DiagnosticsTreeStyle.sparse], uses this style for ASCII art display.
+final TextTreeConfiguration sparseTextConfiguration = TextTreeConfiguration(
+  prefixLineOne:            '├─',
+  prefixOtherLines:         ' ',
+  prefixLastChildLineOne:   '└─',
+  linkCharacter:            '│',
+  propertyPrefixIfChildren: '│ ',
+  propertyPrefixNoChildren: '  ',
+  prefixOtherLinesRootNode: ' ',
+);
+
+/// Identical to [sparseTextConfiguration] except that the lines connecting
+/// parent to children are dashed.
+///
+/// Example:
+/// ```
+/// <root_name>: <root_description>
+///  │ <property1>
+///  │ <property2>
+///  │ ...
+///  │ <propertyN>
+///  ├─<normal_child_name>: <child_description>
+///  ╎ │ <property1>
+///  ╎ │ <property2>
+///  ╎ │ ...
+///  ╎ │ <propertyN>
+///  ╎ │
+///  ╎ └─<child_name>: <child_description>
+///  ╎     <property1>
+///  ╎     <property2>
+///  ╎     ...
+///  ╎     <propertyN>
+///  ╎
+///  ╎╌<dashed_child_name>: <child_description>
+///  ╎ │ <property1>
+///  ╎ │ <property2>
+///  ╎ │ ...
+///  ╎ │ <propertyN>
+///  ╎ │
+///  ╎ └─<child_name>: <child_description>
+///  ╎     <property1>
+///  ╎     <property2>
+///  ╎     ...
+///  ╎     <propertyN>
+///  ╎
+///  └╌<dashed_child_name>: <child_description>'
+///    <property1>
+///    <property2>
+///    ...
+///    <propertyN>
+/// ```
+///
+/// See also:
+///
+///  * [DiagnosticsTreeStyle.offstage], uses this style for ASCII art display.
+final TextTreeConfiguration dashedTextConfiguration = TextTreeConfiguration(
+  prefixLineOne:            '╎╌',
+  prefixLastChildLineOne:   '└╌',
+  prefixOtherLines:         ' ',
+  linkCharacter:            '╎',
+  // Intentionally not set as a dashed line as that would make the properties
+  // look like they were disabled.
+  propertyPrefixIfChildren: '│ ',
+  propertyPrefixNoChildren: '  ',
+  prefixOtherLinesRootNode: ' ',
+);
+
+/// Dense text tree configuration that minimizes horizontal whitespace.
+///
+/// Example:
+/// ```
+/// <root_name>: <root_description>(<property1>; <property2> <propertyN>)
+/// ├<child_name>: <child_description>(<property1>, <property2>, <propertyN>)
+/// └<child_name>: <child_description>(<property1>, <property2>, <propertyN>)
+/// ```
+///
+/// See also:
+///
+///  * [DiagnosticsTreeStyle.dense], uses this style for ASCII art display.
+final TextTreeConfiguration denseTextConfiguration = TextTreeConfiguration(
+  propertySeparator: ', ',
+  beforeProperties: '(',
+  afterProperties: ')',
+  lineBreakProperties: false,
+  prefixLineOne:            '├',
+  prefixOtherLines:         '',
+  prefixLastChildLineOne:   '└',
+  linkCharacter:            '│',
+  propertyPrefixIfChildren: '│',
+  propertyPrefixNoChildren: ' ',
+  prefixOtherLinesRootNode: '',
+  addBlankLineIfNoChildren: false,
+  isBlankLineBetweenPropertiesAndChildren: false,
+);
+
+/// Configuration that draws a box around a leaf node.
+///
+/// Used by leaf nodes such as [TextSpan] to draw a clear border around the
+/// contents of a node.
+///
+/// Example:
+/// ```
+///  <parent_node>
+///  ╞═╦══ <name> ═══
+///  │ ║  <description>:
+///  │ ║    <body>
+///  │ ║    ...
+///  │ ╚═══════════
+///  ╘═╦══ <name> ═══
+///    ║  <description>:
+///    ║    <body>
+///    ║    ...
+///    ╚═══════════
+/// ```
+///
+/// See also:
+///
+///  * [DiagnosticsTreeStyle.transition], uses this style for ASCII art display.
+final TextTreeConfiguration transitionTextConfiguration = TextTreeConfiguration(
+  prefixLineOne:           '╞═╦══ ',
+  prefixLastChildLineOne:  '╘═╦══ ',
+  prefixOtherLines:         ' ║ ',
+  footer:                   ' ╚═══════════',
+  linkCharacter:            '│',
+  // Subtree boundaries are clear due to the border around the node so omit the
+  // property prefix.
+  propertyPrefixIfChildren: '',
+  propertyPrefixNoChildren: '',
+  prefixOtherLinesRootNode: '',
+  afterName:                ' ═══',
+  // Add a colon after the description if the node has a body to make the
+  // connection between the description and the body clearer.
+  afterDescriptionIfBody: ':',
+  // Members are indented an extra two spaces to disambiguate as the description
+  // is placed within the box instead of along side the name as is the case for
+  // other styles.
+  bodyIndent: '  ',
+  isNameOnOwnLine: true,
+  // No need to add a blank line as the footer makes the boundary of this
+  // subtree unambiguous.
+  addBlankLineIfNoChildren: false,
+  isBlankLineBetweenPropertiesAndChildren: false,
+);
+
+/// Configuration that draws a box around a node ignoring the connection to the
+/// parents.
+///
+/// If nested in a tree, this node is best displayed in the property box rather
+/// than as a traditional child.
+///
+/// Used to draw a decorative box around detailed descriptions of an exception.
+///
+/// Example:
+/// ```
+/// ══╡ <name>: <description> ╞═════════════════════════════════════
+/// <body>
+/// ...
+/// ├─<normal_child_name>: <child_description>
+/// ╎ │ <property1>
+/// ╎ │ <property2>
+/// ╎ │ ...
+/// ╎ │ <propertyN>
+/// ╎ │
+/// ╎ └─<child_name>: <child_description>
+/// ╎     <property1>
+/// ╎     <property2>
+/// ╎     ...
+/// ╎     <propertyN>
+/// ╎
+/// ╎╌<dashed_child_name>: <child_description>
+/// ╎ │ <property1>
+/// ╎ │ <property2>
+/// ╎ │ ...
+/// ╎ │ <propertyN>
+/// ╎ │
+/// ╎ └─<child_name>: <child_description>
+/// ╎     <property1>
+/// ╎     <property2>
+/// ╎     ...
+/// ╎     <propertyN>
+/// ╎
+/// └╌<dashed_child_name>: <child_description>'
+/// ════════════════════════════════════════════════════════════════
+/// ```
+///
+/// See also:
+///
+///  * [DiagnosticsTreeStyle.error], uses this style for ASCII art display.
+final TextTreeConfiguration errorTextConfiguration = TextTreeConfiguration(
+  prefixLineOne:           '╞═╦',
+  prefixLastChildLineOne:  '╘═╦',
+  prefixOtherLines:         ' ║ ',
+  footer:                   ' ╚═══════════',
+  linkCharacter:            '│',
+  // Subtree boundaries are clear due to the border around the node so omit the
+  // property prefix.
+  propertyPrefixIfChildren: '',
+  propertyPrefixNoChildren: '',
+  prefixOtherLinesRootNode: '',
+  beforeName:               '══╡ ',
+  suffixLineOne:            ' ╞══',
+  mandatoryFooter:          '═════',
+  // No need to add a blank line as the footer makes the boundary of this
+  // subtree unambiguous.
+  addBlankLineIfNoChildren: false,
+  isBlankLineBetweenPropertiesAndChildren: false,
+);
+
+/// Whitespace only configuration where children are consistently indented
+/// two spaces.
+///
+/// Use this style for displaying properties with structured values or for
+/// displaying children within a [transitionTextConfiguration] as using a style that
+/// draws line art would be visually distracting for those cases.
+///
+/// Example:
+/// ```
+/// <parent_node>
+///   <name>: <description>:
+///     <properties>
+///     <children>
+///   <name>: <description>:
+///     <properties>
+///     <children>
+/// ```
+///
+/// See also:
+///
+///  * [DiagnosticsTreeStyle.whitespace], uses this style for ASCII art display.
+final TextTreeConfiguration whitespaceTextConfiguration = TextTreeConfiguration(
+  prefixLineOne: '',
+  prefixLastChildLineOne: '',
+  prefixOtherLines: ' ',
+  prefixOtherLinesRootNode: '  ',
+  bodyIndent: '',
+  propertyPrefixIfChildren: '',
+  propertyPrefixNoChildren: '',
+  linkCharacter: ' ',
+  addBlankLineIfNoChildren: false,
+  // Add a colon after the description and before the properties to link the
+  // properties to the description line.
+  afterDescriptionIfBody: ':',
+  isBlankLineBetweenPropertiesAndChildren: false,
+);
+
+/// Whitespace only configuration where children are not indented.
+///
+/// Use this style when indentation is not needed to disambiguate parents from
+/// children as in the case of a [DiagnosticsStackTrace].
+///
+/// Example:
+/// ```
+/// <parent_node>
+/// <name>: <description>:
+/// <properties>
+/// <children>
+/// <name>: <description>:
+/// <properties>
+/// <children>
+/// ```
+///
+/// See also:
+///
+///  * [DiagnosticsTreeStyle.flat], uses this style for ASCII art display.
+final TextTreeConfiguration flatTextConfiguration = TextTreeConfiguration(
+  prefixLineOne: '',
+  prefixLastChildLineOne: '',
+  prefixOtherLines: '',
+  prefixOtherLinesRootNode: '',
+  bodyIndent: '',
+  propertyPrefixIfChildren: '',
+  propertyPrefixNoChildren: '',
+  linkCharacter: '',
+  addBlankLineIfNoChildren: false,
+  // Add a colon after the description and before the properties to link the
+  // properties to the description line.
+  afterDescriptionIfBody: ':',
+  isBlankLineBetweenPropertiesAndChildren: false,
+);
+/// Render a node as a single line omitting children.
+///
+/// Example:
+/// `<name>: <description>(<property1>, <property2>, ..., <propertyN>)`
+///
+/// See also:
+///
+///  * [DiagnosticsTreeStyle.singleLine], uses this style for ASCII art display.
+final TextTreeConfiguration singleLineTextConfiguration = TextTreeConfiguration(
+  propertySeparator: ', ',
+  beforeProperties: '(',
+  afterProperties: ')',
+  prefixLineOne: '',
+  prefixOtherLines: '',
+  prefixLastChildLineOne: '',
+  lineBreak: '',
+  lineBreakProperties: false,
+  addBlankLineIfNoChildren: false,
+  showChildren: false,
+  propertyPrefixIfChildren: '  ',
+  propertyPrefixNoChildren: '  ',
+  linkCharacter: '',
+  prefixOtherLinesRootNode: '',
+);
+
+/// Render the name on a line followed by the body and properties on the next
+/// line omitting the children.
+///
+/// Example:
+/// ```
+/// <name>:
+///   <description>(<property1>, <property2>, ..., <propertyN>)
+/// ```
+///
+/// See also:
+///
+///  * [DiagnosticsTreeStyle.errorProperty], uses this style for ASCII art
+///    display.
+final TextTreeConfiguration errorPropertyTextConfiguration = TextTreeConfiguration(
+  propertySeparator: ', ',
+  beforeProperties: '(',
+  afterProperties: ')',
+  prefixLineOne: '',
+  prefixOtherLines: '',
+  prefixLastChildLineOne: '',
+  lineBreak: '\n',
+  lineBreakProperties: false,
+  addBlankLineIfNoChildren: false,
+  showChildren: false,
+  propertyPrefixIfChildren: '  ',
+  propertyPrefixNoChildren: '  ',
+  linkCharacter: '',
+  prefixOtherLinesRootNode: '',
+  afterName: ':',
+  isNameOnOwnLine: true,
+);
+
+/// Render a node on multiple lines omitting children.
+///
+/// Example:
+/// `<name>: <description>
+///   <property1>
+///   <property2>
+///   <propertyN>`
+///
+/// See also:
+///
+///  * [DiagnosticsTreeStyle.shallow]
+final TextTreeConfiguration shallowTextConfiguration = TextTreeConfiguration(
+  prefixLineOne: '',
+  prefixLastChildLineOne: '',
+  prefixOtherLines: ' ',
+  prefixOtherLinesRootNode: '  ',
+  bodyIndent: '',
+  propertyPrefixIfChildren: '',
+  propertyPrefixNoChildren: '',
+  linkCharacter: ' ',
+  addBlankLineIfNoChildren: false,
+  // Add a colon after the description and before the properties to link the
+  // properties to the description line.
+  afterDescriptionIfBody: ':',
+  isBlankLineBetweenPropertiesAndChildren: false,
+  showChildren: false,
+);
+
+enum _WordWrapParseMode { inSpace, inWord, atBreak }
+
+/// Builder that builds a String with specified prefixes for the first and
+/// subsequent lines.
+///
+/// Allows for the incremental building of strings using `write*()` methods.
+/// The strings are concatenated into a single string with the first line
+/// prefixed by [prefixLineOne] and subsequent lines prefixed by
+/// [prefixOtherLines].
+class _PrefixedStringBuilder {
+  _PrefixedStringBuilder({
+    required this.prefixLineOne,
+    required String? prefixOtherLines,
+    this.wrapWidth}) :
+      _prefixOtherLines = prefixOtherLines;
+
+  /// Prefix to add to the first line.
+  final String prefixLineOne;
+
+  /// Prefix to add to subsequent lines.
+  ///
+  /// The prefix can be modified while the string is being built in which case
+  /// subsequent lines will be added with the modified prefix.
+  String? get prefixOtherLines => _nextPrefixOtherLines ?? _prefixOtherLines;
+  String? _prefixOtherLines;
+  set prefixOtherLines(String? prefix) {
+    _prefixOtherLines = prefix;
+    _nextPrefixOtherLines = null;
+  }
+
+  String? _nextPrefixOtherLines;
+  void incrementPrefixOtherLines(String suffix, {required bool updateCurrentLine}) {
+    if (_currentLine.isEmpty || updateCurrentLine) {
+      _prefixOtherLines = prefixOtherLines! + suffix;
+      _nextPrefixOtherLines = null;
+    } else {
+      _nextPrefixOtherLines = prefixOtherLines! + suffix;
+    }
+  }
+
+  final int? wrapWidth;
+
+  /// Buffer containing lines that have already been completely laid out.
+  final StringBuffer _buffer = StringBuffer();
+  /// Buffer containing the current line that has not yet been wrapped.
+  final StringBuffer _currentLine = StringBuffer();
+  /// List of pairs of integers indicating the start and end of each block of
+  /// text within _currentLine that can be wrapped.
+  final List<int> _wrappableRanges = <int>[];
+
+  /// Whether the string being built already has more than 1 line.
+  bool get requiresMultipleLines => _numLines > 1 || (_numLines == 1 && _currentLine.isNotEmpty) ||
+      (_currentLine.length + _getCurrentPrefix(true)!.length > wrapWidth!);
+
+  bool get isCurrentLineEmpty => _currentLine.isEmpty;
+
+  int _numLines = 0;
+
+  void _finalizeLine(bool addTrailingLineBreak) {
+    final bool firstLine = _buffer.isEmpty;
+    final String text = _currentLine.toString();
+    _currentLine.clear();
+
+    if (_wrappableRanges.isEmpty) {
+      // Fast path. There were no wrappable spans of text.
+      _writeLine(
+        text,
+        includeLineBreak: addTrailingLineBreak,
+        firstLine: firstLine,
+      );
+      return;
+    }
+    final Iterable<String> lines = _wordWrapLine(
+      text,
+      _wrappableRanges,
+      wrapWidth!,
+      startOffset: firstLine ? prefixLineOne.length : _prefixOtherLines!.length,
+      otherLineOffset: firstLine ? _prefixOtherLines!.length : _prefixOtherLines!.length,
+    );
+    int i = 0;
+    final int length = lines.length;
+    for (final String line in lines) {
+      i++;
+      _writeLine(
+        line,
+        includeLineBreak: addTrailingLineBreak || i < length,
+        firstLine: firstLine,
+      );
+    }
+    _wrappableRanges.clear();
+  }
+
+  /// Wraps the given string at the given width.
+  ///
+  /// Wrapping occurs at space characters (U+0020).
+  ///
+  /// This is not suitable for use with arbitrary Unicode text. For example, it
+  /// doesn't implement UAX #14, can't handle ideographic text, doesn't hyphenate,
+  /// and so forth. It is only intended for formatting error messages.
+  ///
+  /// This method wraps a sequence of text where only some spans of text can be
+  /// used as wrap boundaries.
+  static Iterable<String> _wordWrapLine(String message, List<int> wrapRanges, int width, { int startOffset = 0, int otherLineOffset = 0}) sync* {
+    if (message.length + startOffset < width) {
+      // Nothing to do. The line doesn't wrap.
+      yield message;
+      return;
+    }
+    int startForLengthCalculations = -startOffset;
+    bool addPrefix = false;
+    int index = 0;
+    _WordWrapParseMode mode = _WordWrapParseMode.inSpace;
+    late int lastWordStart;
+    int? lastWordEnd;
+    int start = 0;
+
+    int currentChunk = 0;
+
+    // This helper is called with increasing indexes.
+    bool noWrap(int index) {
+      while (true) {
+        if (currentChunk >= wrapRanges.length)
+          return true;
+
+        if (index < wrapRanges[currentChunk + 1])
+          break; // Found nearest chunk.
+        currentChunk+= 2;
+      }
+      return index < wrapRanges[currentChunk];
+    }
+    while (true) {
+      switch (mode) {
+        case _WordWrapParseMode.inSpace: // at start of break point (or start of line); can't break until next break
+          while ((index < message.length) && (message[index] == ' '))
+            index += 1;
+          lastWordStart = index;
+          mode = _WordWrapParseMode.inWord;
+          break;
+        case _WordWrapParseMode.inWord: // looking for a good break point. Treat all text
+          while ((index < message.length) && (message[index] != ' ' || noWrap(index)))
+            index += 1;
+          mode = _WordWrapParseMode.atBreak;
+          break;
+        case _WordWrapParseMode.atBreak: // at start of break point
+          if ((index - startForLengthCalculations > width) || (index == message.length)) {
+            // we are over the width line, so break
+            if ((index - startForLengthCalculations <= width) || (lastWordEnd == null)) {
+              // we should use this point, because either it doesn't actually go over the
+              // end (last line), or it does, but there was no earlier break point
+              lastWordEnd = index;
+            }
+            final String line = message.substring(start, lastWordEnd);
+            yield line;
+            addPrefix = true;
+            if (lastWordEnd >= message.length)
+              return;
+            // just yielded a line
+            if (lastWordEnd == index) {
+              // we broke at current position
+              // eat all the wrappable spaces, then set our start point
+              // Even if some of the spaces are not wrappable that is ok.
+              while ((index < message.length) && (message[index] == ' '))
+                index += 1;
+              start = index;
+              mode = _WordWrapParseMode.inWord;
+            } else {
+              // we broke at the previous break point, and we're at the start of a new one
+              assert(lastWordStart > lastWordEnd);
+              start = lastWordStart;
+              mode = _WordWrapParseMode.atBreak;
+            }
+            startForLengthCalculations = start - otherLineOffset;
+            assert(addPrefix);
+            lastWordEnd = null;
+          } else {
+            // save this break point, we're not yet over the line width
+            lastWordEnd = index;
+            // skip to the end of this break point
+            mode = _WordWrapParseMode.inSpace;
+          }
+          break;
+      }
+    }
+  }
+
+  /// Write text ensuring the specified prefixes for the first and subsequent
+  /// lines.
+  ///
+  /// If [allowWrap] is true, the text may be wrapped to stay within the
+  /// allow `wrapWidth`.
+  void write(String s, {bool allowWrap = false}) {
+    if (s.isEmpty)
+      return;
+
+    final List<String> lines = s.split('\n');
+    for (int i = 0; i < lines.length; i += 1) {
+      if (i > 0) {
+        _finalizeLine(true);
+        _updatePrefix();
+      }
+      final String line = lines[i];
+      if (line.isNotEmpty) {
+        if (allowWrap && wrapWidth != null) {
+          final int wrapStart = _currentLine.length;
+          final int wrapEnd = wrapStart + line.length;
+          if (_wrappableRanges.isNotEmpty && _wrappableRanges.last == wrapStart) {
+            // Extend last range.
+            _wrappableRanges.last = wrapEnd;
+          } else {
+            _wrappableRanges..add(wrapStart)..add(wrapEnd);
+          }
+        }
+        _currentLine.write(line);
+      }
+    }
+  }
+  void _updatePrefix() {
+    if (_nextPrefixOtherLines != null) {
+      _prefixOtherLines = _nextPrefixOtherLines;
+      _nextPrefixOtherLines = null;
+    }
+  }
+
+  void _writeLine(
+    String line, {
+    required bool includeLineBreak,
+    required bool firstLine,
+  }) {
+    line = '${_getCurrentPrefix(firstLine)}$line';
+    _buffer.write(line.trimRight());
+    if (includeLineBreak)
+      _buffer.write('\n');
+    _numLines++;
+  }
+
+  String? _getCurrentPrefix(bool firstLine) {
+    return _buffer.isEmpty ? prefixLineOne : (firstLine ? _prefixOtherLines : _prefixOtherLines);
+  }
+
+  /// Write lines assuming the lines obey the specified prefixes. Ensures that
+  /// a newline is added if one is not present.
+  void writeRawLines(String lines) {
+    if (lines.isEmpty)
+      return;
+
+    if (_currentLine.isNotEmpty) {
+      _finalizeLine(true);
+    }
+    assert (_currentLine.isEmpty);
+
+    _buffer.write(lines);
+    if (!lines.endsWith('\n'))
+      _buffer.write('\n');
+    _numLines++;
+    _updatePrefix();
+  }
+
+  /// Finishes the current line with a stretched version of text.
+  void writeStretched(String text, int targetLineLength) {
+    write(text);
+    final int currentLineLength = _currentLine.length + _getCurrentPrefix(_buffer.isEmpty)!.length;
+    assert (_currentLine.length > 0);
+    final int targetLength = targetLineLength - currentLineLength;
+    if (targetLength > 0) {
+      assert(text.isNotEmpty);
+      final String lastChar = text[text.length - 1];
+      assert(lastChar != '\n');
+      _currentLine.write(lastChar * targetLength);
+    }
+    // Mark the entire line as not wrappable.
+    _wrappableRanges.clear();
+  }
+
+  String build() {
+    if (_currentLine.isNotEmpty)
+      _finalizeLine(false);
+
+    return _buffer.toString();
+  }
+}
+
+class _NoDefaultValue {
+  const _NoDefaultValue();
+}
+
+/// Marker object indicating that a [DiagnosticsNode] has no default value.
+const _NoDefaultValue kNoDefaultValue = _NoDefaultValue();
+
+bool _isSingleLine(DiagnosticsTreeStyle? style) {
+  return style == DiagnosticsTreeStyle.singleLine;
+}
+
+/// Renderer that creates ASCII art representations of trees of
+/// [DiagnosticsNode] objects.
+///
+/// See also:
+///
+///  * [DiagnosticsNode.toStringDeep], which uses a [TextTreeRenderer] to return a
+///    string representation of this node and its descendants.
+class TextTreeRenderer {
+  /// Creates a [TextTreeRenderer] object with the given arguments specifying
+  /// how the tree is rendered.
+  ///
+  /// Lines are wrapped to at the maximum of [wrapWidth] and the current indent
+  /// plus [wrapWidthProperties] characters. This ensures that wrapping does not
+  /// become too excessive when displaying very deep trees and that wrapping
+  /// only occurs at the overall [wrapWidth] when the tree is not very indented.
+  /// If [maxDescendentsTruncatableNode] is specified, [DiagnosticsNode] objects
+  /// with `allowTruncate` set to `true` are truncated after including
+  /// [maxDescendentsTruncatableNode] descendants of the node to be truncated.
+  TextTreeRenderer({
+    DiagnosticLevel minLevel = DiagnosticLevel.debug,
+    int wrapWidth = 100,
+    int wrapWidthProperties = 65,
+    int maxDescendentsTruncatableNode = -1,
+  }) : assert(minLevel != null),
+       _minLevel = minLevel,
+       _wrapWidth = wrapWidth,
+       _wrapWidthProperties = wrapWidthProperties,
+       _maxDescendentsTruncatableNode = maxDescendentsTruncatableNode;
+
+  final int _wrapWidth;
+  final int _wrapWidthProperties;
+  final DiagnosticLevel _minLevel;
+  final int _maxDescendentsTruncatableNode;
+
+  /// Text configuration to use to connect this node to a `child`.
+  ///
+  /// The singleLine styles are special cased because the connection from the
+  /// parent to the child should be consistent with the parent's style as the
+  /// single line style does not provide any meaningful style for how children
+  /// should be connected to their parents.
+  TextTreeConfiguration? _childTextConfiguration(
+    DiagnosticsNode child,
+    TextTreeConfiguration textStyle,
+  ) {
+    final DiagnosticsTreeStyle? childStyle = child.style;
+    return (_isSingleLine(childStyle) || childStyle == DiagnosticsTreeStyle.errorProperty) ? textStyle : child.textTreeConfiguration;
+  }
+
+  /// Renders a [node] to a String.
+  String render(
+    DiagnosticsNode node, {
+    String prefixLineOne = '',
+    String? prefixOtherLines,
+    TextTreeConfiguration? parentConfiguration,
+  }) {
+    if (kReleaseMode) {
+      return '';
+    } else {
+      return _debugRender(
+          node,
+          prefixLineOne: prefixLineOne,
+          prefixOtherLines: prefixOtherLines,
+          parentConfiguration: parentConfiguration);
+    }
+  }
+
+  String _debugRender(
+    DiagnosticsNode node, {
+    String prefixLineOne = '',
+    String? prefixOtherLines,
+    TextTreeConfiguration? parentConfiguration,
+  }) {
+    final bool isSingleLine = _isSingleLine(node.style) && parentConfiguration?.lineBreakProperties != true;
+    prefixOtherLines ??= prefixLineOne;
+    if (node.linePrefix != null) {
+      prefixLineOne += node.linePrefix!;
+      prefixOtherLines += node.linePrefix!;
+    }
+
+    final TextTreeConfiguration config = node.textTreeConfiguration!;
+    if (prefixOtherLines.isEmpty)
+      prefixOtherLines += config.prefixOtherLinesRootNode;
+
+    if (node.style == DiagnosticsTreeStyle.truncateChildren) {
+      // This style is different enough that it isn't worthwhile to reuse the
+      // existing logic.
+      final List<String> descendants = <String>[];
+      const int maxDepth = 5;
+      int depth = 0;
+      const int maxLines = 25;
+      int lines = 0;
+      void visitor(DiagnosticsNode node) {
+        for (final DiagnosticsNode child in node.getChildren()) {
+          if (lines < maxLines) {
+            depth += 1;
+            descendants.add('$prefixOtherLines${"  " * depth}$child');
+            if (depth < maxDepth)
+              visitor(child);
+            depth -= 1;
+          } else if (lines == maxLines) {
+            descendants.add('$prefixOtherLines  ...(descendants list truncated after $lines lines)');
+          }
+          lines += 1;
+        }
+      }
+      visitor(node);
+      final StringBuffer information = StringBuffer(prefixLineOne);
+      if (lines > 1) {
+        information.writeln('This ${node.name} had the following descendants (showing up to depth $maxDepth):');
+      } else if (descendants.length == 1) {
+        information.writeln('This ${node.name} had the following child:');
+      } else {
+        information.writeln('This ${node.name} has no descendants.');
+      }
+      information.writeAll(descendants, '\n');
+      return information.toString();
+    }
+    final _PrefixedStringBuilder builder = _PrefixedStringBuilder(
+      prefixLineOne: prefixLineOne,
+      prefixOtherLines: prefixOtherLines,
+      wrapWidth: math.max(_wrapWidth, prefixOtherLines.length + _wrapWidthProperties),
+    );
+
+    List<DiagnosticsNode> children = node.getChildren();
+
+    String? description = node.toDescription(parentConfiguration: parentConfiguration);
+    if (config.beforeName.isNotEmpty) {
+      builder.write(config.beforeName);
+    }
+    final bool wrapName = !isSingleLine && node.allowNameWrap;
+    final bool wrapDescription = !isSingleLine && node.allowWrap;
+    final bool uppercaseTitle = node.style == DiagnosticsTreeStyle.error;
+    String? name = node.name;
+    if (uppercaseTitle) {
+      name = name?.toUpperCase();
+    }
+    if (description == null || description.isEmpty) {
+      if (node.showName && name != null)
+        builder.write(name, allowWrap: wrapName);
+    } else {
+      bool includeName = false;
+      if (name != null && name.isNotEmpty && node.showName) {
+        includeName = true;
+        builder.write(name, allowWrap: wrapName);
+        if (node.showSeparator)
+          builder.write(config.afterName, allowWrap: wrapName);
+
+        builder.write(
+          config.isNameOnOwnLine || description.contains('\n') ? '\n' : ' ',
+          allowWrap: wrapName,
+        );
+      }
+      if (!isSingleLine && builder.requiresMultipleLines && !builder.isCurrentLineEmpty) {
+        // Make sure there is a break between the current line and next one if
+        // there is not one already.
+        builder.write('\n');
+      }
+      if (includeName) {
+        builder.incrementPrefixOtherLines(
+          children.isEmpty ? config.propertyPrefixNoChildren : config.propertyPrefixIfChildren,
+          updateCurrentLine: true,
+        );
+      }
+
+      if (uppercaseTitle) {
+        description = description.toUpperCase();
+      }
+      builder.write(description.trimRight(), allowWrap: wrapDescription);
+
+      if (!includeName) {
+        builder.incrementPrefixOtherLines(
+          children.isEmpty ? config.propertyPrefixNoChildren : config.propertyPrefixIfChildren,
+          updateCurrentLine: false,
+        );
+      }
+    }
+    if (config.suffixLineOne.isNotEmpty) {
+      builder.writeStretched(config.suffixLineOne, builder.wrapWidth!);
+    }
+
+    final Iterable<DiagnosticsNode> propertiesIterable = node.getProperties().where(
+            (DiagnosticsNode n) => !n.isFiltered(_minLevel)
+    );
+    List<DiagnosticsNode> properties;
+    if (_maxDescendentsTruncatableNode >= 0 && node.allowTruncate) {
+      if (propertiesIterable.length < _maxDescendentsTruncatableNode) {
+        properties =
+            propertiesIterable.take(_maxDescendentsTruncatableNode).toList();
+        properties.add(DiagnosticsNode.message('...'));
+      } else {
+        properties = propertiesIterable.toList();
+      }
+      if (_maxDescendentsTruncatableNode < children.length) {
+        children = children.take(_maxDescendentsTruncatableNode).toList();
+        children.add(DiagnosticsNode.message('...'));
+      }
+    } else {
+      properties = propertiesIterable.toList();
+    }
+
+    // If the node does not show a separator and there is no description then
+    // we should not place a separator between the name and the value.
+    // Essentially in this case the properties are treated a bit like a value.
+    if ((properties.isNotEmpty || children.isNotEmpty || node.emptyBodyDescription != null) &&
+        (node.showSeparator || description?.isNotEmpty == true)) {
+      builder.write(config.afterDescriptionIfBody);
+    }
+
+    if (config.lineBreakProperties)
+      builder.write(config.lineBreak);
+
+    if (properties.isNotEmpty)
+      builder.write(config.beforeProperties);
+
+    builder.incrementPrefixOtherLines(config.bodyIndent, updateCurrentLine: false);
+
+    if (node.emptyBodyDescription != null &&
+        properties.isEmpty &&
+        children.isEmpty &&
+        prefixLineOne.isNotEmpty) {
+      builder.write(node.emptyBodyDescription!);
+      if (config.lineBreakProperties)
+        builder.write(config.lineBreak);
+    }
+
+    for (int i = 0; i < properties.length; ++i) {
+      final DiagnosticsNode property = properties[i];
+      if (i > 0)
+        builder.write(config.propertySeparator);
+
+      final TextTreeConfiguration propertyStyle = property.textTreeConfiguration!;
+      if (_isSingleLine(property.style)) {
+        // We have to treat single line properties slightly differently to deal
+        // with cases where a single line properties output may not have single
+        // linebreak.
+        final String propertyRender = render(property,
+          prefixLineOne: propertyStyle.prefixLineOne,
+          prefixOtherLines: '${propertyStyle.childLinkSpace}${propertyStyle.prefixOtherLines}',
+          parentConfiguration: config,
+        );
+        final List<String> propertyLines = propertyRender.split('\n');
+        if (propertyLines.length == 1 && !config.lineBreakProperties) {
+          builder.write(propertyLines.first);
+        } else {
+          builder.write(propertyRender, allowWrap: false);
+          if (!propertyRender.endsWith('\n'))
+            builder.write('\n');
+        }
+      } else {
+        final String propertyRender = render(property,
+          prefixLineOne: '${builder.prefixOtherLines}${propertyStyle.prefixLineOne}',
+          prefixOtherLines: '${builder.prefixOtherLines}${propertyStyle.childLinkSpace}${propertyStyle.prefixOtherLines}',
+          parentConfiguration: config,
+        );
+        builder.writeRawLines(propertyRender);
+      }
+    }
+    if (properties.isNotEmpty)
+      builder.write(config.afterProperties);
+
+    builder.write(config.mandatoryAfterProperties);
+
+    if (!config.lineBreakProperties)
+      builder.write(config.lineBreak);
+
+    final String prefixChildren = config.bodyIndent;
+    final String prefixChildrenRaw = '$prefixOtherLines$prefixChildren';
+    if (children.isEmpty &&
+        config.addBlankLineIfNoChildren &&
+        builder.requiresMultipleLines &&
+        builder.prefixOtherLines!.trimRight().isNotEmpty
+    ) {
+      builder.write(config.lineBreak);
+    }
+
+    if (children.isNotEmpty && config.showChildren) {
+      if (config.isBlankLineBetweenPropertiesAndChildren &&
+          properties.isNotEmpty &&
+          children.first.textTreeConfiguration!.isBlankLineBetweenPropertiesAndChildren) {
+        builder.write(config.lineBreak);
+      }
+
+      builder.prefixOtherLines = prefixOtherLines;
+
+      for (int i = 0; i < children.length; i++) {
+        final DiagnosticsNode child = children[i];
+        assert(child != null);
+        final TextTreeConfiguration childConfig = _childTextConfiguration(child, config)!;
+        if (i == children.length - 1) {
+          final String lastChildPrefixLineOne = '$prefixChildrenRaw${childConfig.prefixLastChildLineOne}';
+          final String childPrefixOtherLines = '$prefixChildrenRaw${childConfig.childLinkSpace}${childConfig.prefixOtherLines}';
+          builder.writeRawLines(render(
+            child,
+            prefixLineOne: lastChildPrefixLineOne,
+            prefixOtherLines: childPrefixOtherLines,
+            parentConfiguration: config,
+          ));
+          if (childConfig.footer.isNotEmpty) {
+            builder.prefixOtherLines = prefixChildrenRaw;
+            builder.write('${childConfig.childLinkSpace}${childConfig.footer}');
+            if (childConfig.mandatoryFooter.isNotEmpty) {
+              builder.writeStretched(
+                childConfig.mandatoryFooter,
+                math.max(builder.wrapWidth!, _wrapWidthProperties + childPrefixOtherLines.length),
+              );
+            }
+            builder.write(config.lineBreak);
+          }
+        } else {
+          final TextTreeConfiguration nextChildStyle = _childTextConfiguration(children[i + 1], config)!;
+          final String childPrefixLineOne = '$prefixChildrenRaw${childConfig.prefixLineOne}';
+          final String childPrefixOtherLines ='$prefixChildrenRaw${nextChildStyle.linkCharacter}${childConfig.prefixOtherLines}';
+          builder.writeRawLines(render(
+            child,
+            prefixLineOne: childPrefixLineOne,
+            prefixOtherLines: childPrefixOtherLines,
+            parentConfiguration: config,
+          ));
+          if (childConfig.footer.isNotEmpty) {
+            builder.prefixOtherLines = prefixChildrenRaw;
+            builder.write('${childConfig.linkCharacter}${childConfig.footer}');
+            if (childConfig.mandatoryFooter.isNotEmpty) {
+              builder.writeStretched(
+                childConfig.mandatoryFooter,
+                math.max(builder.wrapWidth!, _wrapWidthProperties + childPrefixOtherLines.length),
+              );
+            }
+            builder.write(config.lineBreak);
+          }
+        }
+      }
+    }
+    if (parentConfiguration == null && config.mandatoryFooter.isNotEmpty) {
+      builder.writeStretched(config.mandatoryFooter, builder.wrapWidth!);
+      builder.write(config.lineBreak);
+    }
+    return builder.build();
+  }
+}
+
+/// Defines diagnostics data for a [value].
+///
+/// For debug and profile modes, [DiagnosticsNode] provides a high quality
+/// multiline string dump via [toStringDeep]. The core members are the [name],
+/// [toDescription], [getProperties], [value], and [getChildren]. All other
+/// members exist typically to provide hints for how [toStringDeep] and
+/// debugging tools should format output.
+///
+/// In release mode, far less information is retained and some information may
+/// not print at all.
+abstract class DiagnosticsNode {
+  /// Initializes the object.
+  ///
+  /// The [style], [showName], and [showSeparator] arguments must not
+  /// be null.
+  DiagnosticsNode({
+    required this.name,
+    this.style,
+    this.showName = true,
+    this.showSeparator = true,
+    this.linePrefix,
+  }) : assert(showName != null),
+       assert(showSeparator != null),
+       // A name ending with ':' indicates that the user forgot that the ':' will
+       // be automatically added for them when generating descriptions of the
+       // property.
+       assert(
+         name == null || !name.endsWith(':'),
+         'Names of diagnostic nodes must not end with colons.\n'
+         'name:\n'
+         '  "$name"'
+       );
+
+  /// Diagnostics containing just a string `message` and not a concrete name or
+  /// value.
+  ///
+  /// The [style] and [level] arguments must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [MessageProperty], which is better suited to messages that are to be
+  ///    formatted like a property with a separate name and message.
+  factory DiagnosticsNode.message(
+    String message, {
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
+    DiagnosticLevel level = DiagnosticLevel.info,
+    bool allowWrap = true,
+  }) {
+    assert(style != null);
+    assert(level != null);
+    return DiagnosticsProperty<void>(
+      '',
+      null,
+      description: message,
+      style: style,
+      showName: false,
+      allowWrap: allowWrap,
+      level: level,
+    );
+  }
+
+  /// Label describing the [DiagnosticsNode], typically shown before a separator
+  /// (see [showSeparator]).
+  ///
+  /// The name will be omitted if the [showName] property is false.
+  final String? name;
+
+  /// Returns a description with a short summary of the node itself not
+  /// including children or properties.
+  ///
+  /// `parentConfiguration` specifies how the parent is rendered as text art.
+  /// For example, if the parent does not line break between properties, the
+  /// description of a property should also be a single line if possible.
+  String? toDescription({ TextTreeConfiguration? parentConfiguration });
+
+  /// Whether to show a separator between [name] and description.
+  ///
+  /// If false, name and description should be shown with no separation.
+  /// `:` is typically used as a separator when displaying as text.
+  final bool showSeparator;
+
+  /// Whether the diagnostic should be filtered due to its [level] being lower
+  /// than `minLevel`.
+  ///
+  /// If `minLevel` is [DiagnosticLevel.hidden] no diagnostics will be filtered.
+  /// If `minLevel` is [DiagnosticLevel.off] all diagnostics will be filtered.
+  bool isFiltered(DiagnosticLevel minLevel) => kReleaseMode || level.index < minLevel.index;
+
+  /// Priority level of the diagnostic used to control which diagnostics should
+  /// be shown and filtered.
+  ///
+  /// Typically this only makes sense to set to a different value than
+  /// [DiagnosticLevel.info] for diagnostics representing properties. Some
+  /// subclasses have a `level` argument to their constructor which influences
+  /// the value returned here but other factors also influence it. For example,
+  /// whether an exception is thrown computing a property value
+  /// [DiagnosticLevel.error] is returned.
+  DiagnosticLevel get level => kReleaseMode ? DiagnosticLevel.hidden : DiagnosticLevel.info;
+
+  /// Whether the name of the property should be shown when showing the default
+  /// view of the tree.
+  ///
+  /// This could be set to false (hiding the name) if the value's description
+  /// will make the name self-evident.
+  final bool showName;
+
+  /// Prefix to include at the start of each line.
+  final String? linePrefix;
+
+  /// Description to show if the node has no displayed properties or children.
+  String? get emptyBodyDescription => null;
+
+  /// The actual object this is diagnostics data for.
+  Object? get value;
+
+  /// Hint for how the node should be displayed.
+  final DiagnosticsTreeStyle? style;
+
+  /// Whether to wrap text on onto multiple lines or not.
+  bool get allowWrap => false;
+
+  /// Whether to wrap the name onto multiple lines or not.
+  bool get allowNameWrap => false;
+
+  /// Whether to allow truncation when displaying the node and its children.
+  bool get allowTruncate => false;
+
+  /// Properties of this [DiagnosticsNode].
+  ///
+  /// Properties and children are kept distinct even though they are both
+  /// [List<DiagnosticsNode>] because they should be grouped differently.
+  List<DiagnosticsNode> getProperties();
+
+  /// Children of this [DiagnosticsNode].
+  ///
+  /// See also:
+  ///
+  ///  * [getProperties], which returns the properties of the [DiagnosticsNode]
+  ///    object.
+  List<DiagnosticsNode> getChildren();
+
+  String get _separator => showSeparator ? ':' : '';
+
+  /// Serialize the node to a JSON map according to the configuration provided
+  /// in the [DiagnosticsSerializationDelegate].
+  ///
+  /// Subclasses should override if they have additional properties that are
+  /// useful for the GUI tools that consume this JSON.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetInspectorService], which forms the bridge between JSON returned
+  ///    by this method and interactive tree views in the Flutter IntelliJ
+  ///    plugin.
+  @mustCallSuper
+  Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
+    Map<String, Object?> result = <String, Object?>{};
+    assert(() {
+      final bool hasChildren = getChildren().isNotEmpty;
+      result = <String, Object?>{
+        'description': toDescription(),
+        'type': runtimeType.toString(),
+        if (name != null)
+          'name': name,
+        if (!showSeparator)
+          'showSeparator': showSeparator,
+        if (level != DiagnosticLevel.info)
+          'level': describeEnum(level),
+        if (showName == false)
+          'showName': showName,
+        if (emptyBodyDescription != null)
+          'emptyBodyDescription': emptyBodyDescription,
+        if (style != DiagnosticsTreeStyle.sparse)
+          'style': describeEnum(style!),
+        if (allowTruncate)
+          'allowTruncate': allowTruncate,
+        if (hasChildren)
+          'hasChildren': hasChildren,
+        if (linePrefix?.isNotEmpty == true)
+          'linePrefix': linePrefix,
+        if (!allowWrap)
+          'allowWrap': allowWrap,
+        if (allowNameWrap)
+          'allowNameWrap': allowNameWrap,
+        ...delegate.additionalNodeProperties(this),
+        if (delegate.includeProperties)
+          'properties': toJsonList(
+            delegate.filterProperties(getProperties(), this),
+            this,
+            delegate,
+          ),
+        if (delegate.subtreeDepth > 0)
+          'children': toJsonList(
+            delegate.filterChildren(getChildren(), this),
+            this,
+            delegate,
+          ),
+      };
+      return true;
+    }());
+    return result;
+  }
+
+  /// Serializes a [List] of [DiagnosticsNode]s to a JSON list according to
+  /// the configuration provided by the [DiagnosticsSerializationDelegate].
+  ///
+  /// The provided `nodes` may be properties or children of the `parent`
+  /// [DiagnosticsNode].
+  static List<Map<String, Object?>> toJsonList(
+    List<DiagnosticsNode>? nodes,
+    DiagnosticsNode? parent,
+    DiagnosticsSerializationDelegate delegate,
+  ) {
+    bool truncated = false;
+    if (nodes == null)
+      return const <Map<String, Object?>>[];
+    final int originalNodeCount = nodes.length;
+    nodes = delegate.truncateNodesList(nodes, parent);
+    if (nodes.length != originalNodeCount) {
+      nodes.add(DiagnosticsNode.message('...'));
+      truncated = true;
+    }
+    final List<Map<String, Object?>> json = nodes.map<Map<String, Object?>>((DiagnosticsNode node) {
+      return node.toJsonMap(delegate.delegateForNode(node));
+    }).toList();
+    if (truncated)
+      json.last['truncated'] = true;
+    return json;
+  }
+
+  /// Returns a string representation of this diagnostic that is compatible with
+  /// the style of the parent if the node is not the root.
+  ///
+  /// `parentConfiguration` specifies how the parent is rendered as text art.
+  /// For example, if the parent places all properties on one line, the
+  /// [toString] for each property should avoid line breaks if possible.
+  ///
+  /// `minLevel` specifies the minimum [DiagnosticLevel] for properties included
+  /// in the output.
+  ///
+  /// In release mode, far less information is retained and some information may
+  /// not print at all.
+  @override
+  String toString({
+    TextTreeConfiguration? parentConfiguration,
+    DiagnosticLevel minLevel = DiagnosticLevel.info,
+  }) {
+    String result = super.toString();
+    assert(style != null);
+    assert(minLevel != null);
+    assert(() {
+      if (_isSingleLine(style)) {
+        result = toStringDeep(
+            parentConfiguration: parentConfiguration, minLevel: minLevel);
+      } else {
+        final String description = toDescription(parentConfiguration: parentConfiguration)!;
+
+        if (name == null || name!.isEmpty || !showName) {
+          result = description;
+        } else {
+          result = description.contains('\n') ? '$name$_separator\n$description'
+              : '$name$_separator $description';
+        }
+      }
+      return true;
+    }());
+    return result;
+  }
+
+  /// Returns a configuration specifying how this object should be rendered
+  /// as text art.
+  @protected
+  TextTreeConfiguration? get textTreeConfiguration {
+    assert(style != null);
+    switch (style!) {
+      case DiagnosticsTreeStyle.none:
+        return null;
+      case DiagnosticsTreeStyle.dense:
+        return denseTextConfiguration;
+      case DiagnosticsTreeStyle.sparse:
+        return sparseTextConfiguration;
+      case DiagnosticsTreeStyle.offstage:
+        return dashedTextConfiguration;
+      case DiagnosticsTreeStyle.whitespace:
+        return whitespaceTextConfiguration;
+      case DiagnosticsTreeStyle.transition:
+        return transitionTextConfiguration;
+      case DiagnosticsTreeStyle.singleLine:
+        return singleLineTextConfiguration;
+      case DiagnosticsTreeStyle.errorProperty:
+        return errorPropertyTextConfiguration;
+      case DiagnosticsTreeStyle.shallow:
+        return shallowTextConfiguration;
+      case DiagnosticsTreeStyle.error:
+        return errorTextConfiguration;
+      case DiagnosticsTreeStyle.truncateChildren:
+        // Truncate children doesn't really need its own text style as the
+        // rendering is quite custom.
+        return whitespaceTextConfiguration;
+      case DiagnosticsTreeStyle.flat:
+        return flatTextConfiguration;
+    }
+  }
+
+  /// Returns a string representation of this node and its descendants.
+  ///
+  /// `prefixLineOne` will be added to the front of the first line of the
+  /// output. `prefixOtherLines` will be added to the front of each other line.
+  /// If `prefixOtherLines` is null, the `prefixLineOne` is used for every line.
+  /// By default, there is no prefix.
+  ///
+  /// `minLevel` specifies the minimum [DiagnosticLevel] for properties included
+  /// in the output.
+  ///
+  /// The [toStringDeep] method takes other arguments, but those are intended
+  /// for internal use when recursing to the descendants, and so can be ignored.
+  ///
+  /// In release mode, far less information is retained and some information may
+  /// not print at all.
+  ///
+  /// See also:
+  ///
+  ///  * [toString], for a brief description of the [value] but not its
+  ///    children.
+  String toStringDeep({
+    String prefixLineOne = '',
+    String? prefixOtherLines,
+    TextTreeConfiguration? parentConfiguration,
+    DiagnosticLevel minLevel = DiagnosticLevel.debug,
+  }) {
+    String result = '';
+    assert(() {
+      result = TextTreeRenderer(
+        minLevel: minLevel,
+        wrapWidth: 65,
+        wrapWidthProperties: 65,
+      ).render(
+        this,
+        prefixLineOne: prefixLineOne,
+        prefixOtherLines: prefixOtherLines,
+        parentConfiguration: parentConfiguration,
+      );
+      return true;
+    }());
+    return result;
+  }
+}
+
+/// Debugging message displayed like a property.
+///
+/// {@tool snippet}
+///
+/// The following two properties are better expressed using this
+/// [MessageProperty] class, rather than [StringProperty], as the intent is to
+/// show a message with property style display rather than to describe the value
+/// of an actual property of the object:
+///
+/// ```dart
+/// var table = MessageProperty('table size', '$columns\u00D7$rows');
+/// var usefulness = MessageProperty('usefulness ratio', 'no metrics collected yet (never painted)');
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// On the other hand, [StringProperty] is better suited when the property has a
+/// concrete value that is a string:
+///
+/// ```dart
+/// var name = StringProperty('name', _name);
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [DiagnosticsNode.message], which serves the same role for messages
+///    without a clear property name.
+///  * [StringProperty], which is a better fit for properties with string values.
+class MessageProperty extends DiagnosticsProperty<void> {
+  /// Create a diagnostics property that displays a message.
+  ///
+  /// Messages have no concrete [value] (so [value] will return null). The
+  /// message is stored as the description.
+  ///
+  /// The [name], `message`, and [level] arguments must not be null.
+  MessageProperty(
+    String name,
+    String message, {
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(name != null),
+       assert(message != null),
+       assert(style != null),
+       assert(level != null),
+       super(name, null, description: message, style: style, level: level);
+}
+
+/// Property which encloses its string [value] in quotes.
+///
+/// See also:
+///
+///  * [MessageProperty], which is a better fit for showing a message
+///    instead of describing a property with a string value.
+class StringProperty extends DiagnosticsProperty<String> {
+  /// Create a diagnostics property for strings.
+  ///
+  /// The [showName], [quoted], [style], and [level] arguments must not be null.
+  StringProperty(
+    String name,
+    String? value, {
+    String? description,
+    String? tooltip,
+    bool showName = true,
+    Object? defaultValue = kNoDefaultValue,
+    this.quoted = true,
+    String? ifEmpty,
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(showName != null),
+       assert(quoted != null),
+       assert(style != null),
+       assert(level != null),
+       super(
+    name,
+    value,
+    description: description,
+    defaultValue: defaultValue,
+    tooltip: tooltip,
+    showName: showName,
+    ifEmpty: ifEmpty,
+    style: style,
+    level: level,
+  );
+
+  /// Whether the value is enclosed in double quotes.
+  final bool quoted;
+
+  @override
+  Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
+    final Map<String, Object?> json = super.toJsonMap(delegate);
+    json['quoted'] = quoted;
+    return json;
+  }
+
+  @override
+  String valueToString({ TextTreeConfiguration? parentConfiguration }) {
+    String? text = _description ?? value;
+    if (parentConfiguration != null &&
+        !parentConfiguration.lineBreakProperties &&
+        text != null) {
+      // Escape linebreaks in multiline strings to avoid confusing output when
+      // the parent of this node is trying to display all properties on the same
+      // line.
+      text = text.replaceAll('\n', r'\n');
+    }
+
+    if (quoted && text != null) {
+      // An empty value would not appear empty after being surrounded with
+      // quotes so we have to handle this case separately.
+      if (ifEmpty != null && text.isEmpty)
+        return ifEmpty!;
+      return '"$text"';
+    }
+    return text.toString();
+  }
+}
+
+abstract class _NumProperty<T extends num> extends DiagnosticsProperty<T> {
+  _NumProperty(
+    String name,
+    T? value, {
+    String? ifNull,
+    this.unit,
+    bool showName = true,
+    Object? defaultValue = kNoDefaultValue,
+    String? tooltip,
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : super(
+    name,
+    value,
+    ifNull: ifNull,
+    showName: showName,
+    defaultValue: defaultValue,
+    tooltip: tooltip,
+    level: level,
+    style: style,
+  );
+
+  _NumProperty.lazy(
+    String name,
+    ComputePropertyValueCallback<T?> computeValue, {
+    String? ifNull,
+    this.unit,
+    bool showName = true,
+    Object? defaultValue = kNoDefaultValue,
+    String? tooltip,
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : super.lazy(
+    name,
+    computeValue,
+    ifNull: ifNull,
+    showName: showName,
+    defaultValue: defaultValue,
+    tooltip: tooltip,
+    style: style,
+    level: level,
+  );
+
+  @override
+  Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
+    final Map<String, Object?> json = super.toJsonMap(delegate);
+    if (unit != null)
+      json['unit'] = unit;
+
+    json['numberToString'] = numberToString();
+    return json;
+  }
+
+  /// Optional unit the [value] is measured in.
+  ///
+  /// Unit must be acceptable to display immediately after a number with no
+  /// spaces. For example: 'physical pixels per logical pixel' should be a
+  /// [tooltip] not a [unit].
+  final String? unit;
+
+  /// String describing just the numeric [value] without a unit suffix.
+  String numberToString();
+
+  @override
+  String valueToString({ TextTreeConfiguration? parentConfiguration }) {
+    if (value == null)
+      return value.toString();
+
+    return unit != null ? '${numberToString()}$unit' : numberToString();
+  }
+}
+/// Property describing a [double] [value] with an optional [unit] of measurement.
+///
+/// Numeric formatting is optimized for debug message readability.
+class DoubleProperty extends _NumProperty<double> {
+  /// If specified, [unit] describes the unit for the [value] (e.g. px).
+  ///
+  /// The [showName], [style], and [level] arguments must not be null.
+  DoubleProperty(
+    String name,
+    double? value, {
+    String? ifNull,
+    String? unit,
+    String? tooltip,
+    Object? defaultValue = kNoDefaultValue,
+    bool showName = true,
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(showName != null),
+       assert(style != null),
+       assert(level != null),
+       super(
+    name,
+    value,
+    ifNull: ifNull,
+    unit: unit,
+    tooltip: tooltip,
+    defaultValue: defaultValue,
+    showName: showName,
+    style :style,
+    level: level,
+  );
+
+  /// Property with a [value] that is computed only when needed.
+  ///
+  /// Use if computing the property [value] may throw an exception or is
+  /// expensive.
+  ///
+  /// The [showName] and [level] arguments must not be null.
+  DoubleProperty.lazy(
+    String name,
+    ComputePropertyValueCallback<double?> computeValue, {
+    String? ifNull,
+    bool showName = true,
+    String? unit,
+    String? tooltip,
+    Object? defaultValue = kNoDefaultValue,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(showName != null),
+       assert(level != null),
+       super.lazy(
+    name,
+    computeValue,
+    showName: showName,
+    ifNull: ifNull,
+    unit: unit,
+    tooltip: tooltip,
+    defaultValue: defaultValue,
+    level: level,
+  );
+
+  @override
+  String numberToString() => debugFormatDouble(value);
+}
+
+/// An int valued property with an optional unit the value is measured in.
+///
+/// Examples of units include 'px' and 'ms'.
+class IntProperty extends _NumProperty<int> {
+  /// Create a diagnostics property for integers.
+  ///
+  /// The [showName], [style], and [level] arguments must not be null.
+  IntProperty(
+    String name,
+    int? value, {
+    String? ifNull,
+    bool showName = true,
+    String? unit,
+    Object? defaultValue = kNoDefaultValue,
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(showName != null),
+       assert(level != null),
+       assert(style != null),
+       super(
+    name,
+    value,
+    ifNull: ifNull,
+    showName: showName,
+    unit: unit,
+    defaultValue: defaultValue,
+    level: level,
+  );
+
+  @override
+  String numberToString() => value.toString();
+}
+
+/// Property which clamps a [double] to between 0 and 1 and formats it as a
+/// percentage.
+class PercentProperty extends DoubleProperty {
+  /// Create a diagnostics property for doubles that represent percentages or
+  /// fractions.
+  ///
+  /// Setting [showName] to false is often reasonable for [PercentProperty]
+  /// objects, as the fact that the property is shown as a percentage tends to
+  /// be sufficient to disambiguate its meaning.
+  ///
+  /// The [showName] and [level] arguments must not be null.
+  PercentProperty(
+    String name,
+    double? fraction, {
+    String? ifNull,
+    bool showName = true,
+    String? tooltip,
+    String? unit,
+    DiagnosticLevel level  = DiagnosticLevel.info,
+  }) : assert(showName != null),
+       assert(level != null),
+       super(
+    name,
+    fraction,
+    ifNull: ifNull,
+    showName: showName,
+    tooltip: tooltip,
+    unit: unit,
+    level: level,
+  );
+
+  @override
+  String valueToString({ TextTreeConfiguration? parentConfiguration }) {
+    if (value == null)
+      return value.toString();
+    return unit != null ? '${numberToString()} $unit' : numberToString();
+  }
+
+  @override
+  String numberToString() {
+    final double? v = value;
+    if (v == null)
+      return value.toString();
+    return '${(v.clamp(0.0, 1.0) * 100.0).toStringAsFixed(1)}%';
+  }
+}
+
+/// Property where the description is either [ifTrue] or [ifFalse] depending on
+/// whether [value] is true or false.
+///
+/// Using [FlagProperty] instead of [DiagnosticsProperty<bool>] can make
+/// diagnostics display more polished. For example, given a property named
+/// `visible` that is typically true, the following code will return 'hidden'
+/// when `visible` is false and nothing when visible is true, in contrast to
+/// `visible: true` or `visible: false`.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// FlagProperty(
+///   'visible',
+///   value: true,
+///   ifFalse: 'hidden',
+/// )
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// [FlagProperty] should also be used instead of [DiagnosticsProperty<bool>]
+/// if showing the bool value would not clearly indicate the meaning of the
+/// property value.
+///
+/// ```dart
+/// FlagProperty(
+///   'inherit',
+///   value: inherit,
+///   ifTrue: '<all styles inherited>',
+///   ifFalse: '<no style specified>',
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ObjectFlagProperty], which provides similar behavior describing whether
+///    a [value] is null.
+class FlagProperty extends DiagnosticsProperty<bool> {
+  /// Constructs a FlagProperty with the given descriptions with the specified descriptions.
+  ///
+  /// [showName] defaults to false as typically [ifTrue] and [ifFalse] should
+  /// be descriptions that make the property name redundant.
+  ///
+  /// The [showName] and [level] arguments must not be null.
+  FlagProperty(
+    String name, {
+    required bool? value,
+    this.ifTrue,
+    this.ifFalse,
+    bool showName = false,
+    Object? defaultValue,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(showName != null),
+       assert(level != null),
+       assert(ifTrue != null || ifFalse != null),
+       super(
+         name,
+         value,
+         showName: showName,
+         defaultValue: defaultValue,
+         level: level,
+       );
+
+  @override
+  Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
+    final Map<String, Object?> json = super.toJsonMap(delegate);
+    if (ifTrue != null)
+      json['ifTrue'] = ifTrue;
+    if (ifFalse != null)
+      json['ifFalse'] = ifFalse;
+
+    return json;
+  }
+
+  /// Description to use if the property [value] is true.
+  ///
+  /// If not specified and [value] equals true the property's priority [level]
+  /// will be [DiagnosticLevel.hidden].
+  final String? ifTrue;
+
+  /// Description to use if the property value is false.
+  ///
+  /// If not specified and [value] equals false, the property's priority [level]
+  /// will be [DiagnosticLevel.hidden].
+  final String? ifFalse;
+
+  @override
+  String valueToString({ TextTreeConfiguration? parentConfiguration }) {
+    if (value == true) {
+      if (ifTrue != null)
+        return ifTrue!;
+    } else if (value == false) {
+      if (ifFalse != null)
+        return ifFalse!;
+    }
+    return super.valueToString(parentConfiguration: parentConfiguration);
+  }
+
+  @override
+  bool get showName {
+    if (value == null || (value == true && ifTrue == null) || (value == false && ifFalse == null)) {
+      // We are missing a description for the flag value so we need to show the
+      // flag name. The property will have DiagnosticLevel.hidden for this case
+      // so users will not see this the property in this case unless they are
+      // displaying hidden properties.
+      return true;
+    }
+    return super.showName;
+  }
+
+  @override
+  DiagnosticLevel get level {
+    if (value == true) {
+      if (ifTrue == null)
+        return DiagnosticLevel.hidden;
+    }
+    if (value == false) {
+      if (ifFalse == null)
+        return DiagnosticLevel.hidden;
+    }
+    return super.level;
+  }
+}
+
+/// Property with an `Iterable<T>` [value] that can be displayed with
+/// different [DiagnosticsTreeStyle] for custom rendering.
+///
+/// If [style] is [DiagnosticsTreeStyle.singleLine], the iterable is described
+/// as a comma separated list, otherwise the iterable is described as a line
+/// break separated list.
+class IterableProperty<T> extends DiagnosticsProperty<Iterable<T>> {
+  /// Create a diagnostics property for iterables (e.g. lists).
+  ///
+  /// The [ifEmpty] argument is used to indicate how an iterable [value] with 0
+  /// elements is displayed. If [ifEmpty] equals null that indicates that an
+  /// empty iterable [value] is not interesting to display similar to how
+  /// [defaultValue] is used to indicate that a specific concrete value is not
+  /// interesting to display.
+  ///
+  /// The [style], [showName], [showSeparator], and [level] arguments must not be null.
+  IterableProperty(
+    String name,
+    Iterable<T>? value, {
+    Object? defaultValue = kNoDefaultValue,
+    String? ifNull,
+    String? ifEmpty = '[]',
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
+    bool showName = true,
+    bool showSeparator = true,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(style != null),
+       assert(showName != null),
+       assert(showSeparator != null),
+       assert(level != null),
+       super(
+    name,
+    value,
+    defaultValue: defaultValue,
+    ifNull: ifNull,
+    ifEmpty: ifEmpty,
+    style: style,
+    showName: showName,
+    showSeparator: showSeparator,
+    level: level,
+  );
+
+  @override
+  String valueToString({TextTreeConfiguration? parentConfiguration}) {
+    if (value == null)
+      return value.toString();
+
+    if (value!.isEmpty)
+      return ifEmpty ?? '[]';
+
+    final Iterable<String> formattedValues = value!.map((T v) {
+      if (T == double && v is double) {
+        return debugFormatDouble(v);
+      } else {
+        return v.toString();
+      }
+    });
+
+    if (parentConfiguration != null && !parentConfiguration.lineBreakProperties) {
+      // Always display the value as a single line and enclose the iterable
+      // value in brackets to avoid ambiguity.
+      return '[${formattedValues.join(', ')}]';
+    }
+
+    return formattedValues.join(_isSingleLine(style) ? ', ' : '\n');
+  }
+
+  /// Priority level of the diagnostic used to control which diagnostics should
+  /// be shown and filtered.
+  ///
+  /// If [ifEmpty] is null and the [value] is an empty [Iterable] then level
+  /// [DiagnosticLevel.fine] is returned in a similar way to how an
+  /// [ObjectFlagProperty] handles when [ifNull] is null and the [value] is
+  /// null.
+  @override
+  DiagnosticLevel get level {
+    if (ifEmpty == null && value != null && value!.isEmpty && super.level != DiagnosticLevel.hidden)
+      return DiagnosticLevel.fine;
+    return super.level;
+  }
+
+  @override
+  Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
+    final Map<String, Object?> json = super.toJsonMap(delegate);
+    if (value != null) {
+      json['values'] = value!.map<String>((T value) => value.toString()).toList();
+    }
+    return json;
+  }
+}
+
+/// An property than displays enum values tersely.
+///
+/// The enum value is displayed with the class name stripped. For example:
+/// [HitTestBehavior.deferToChild] is shown as `deferToChild`.
+///
+/// See also:
+///
+///  * [DiagnosticsProperty] which documents named parameters common to all
+///    [DiagnosticsProperty].
+class EnumProperty<T> extends DiagnosticsProperty<T> {
+  /// Create a diagnostics property that displays an enum.
+  ///
+  /// The [level] argument must also not be null.
+  EnumProperty(
+    String name,
+    T? value, {
+    Object? defaultValue = kNoDefaultValue,
+    DiagnosticLevel level  = DiagnosticLevel.info,
+  }) : assert(level != null),
+       super (
+    name,
+    value,
+    defaultValue: defaultValue,
+    level: level,
+  );
+
+  @override
+  String valueToString({ TextTreeConfiguration? parentConfiguration }) {
+    if (value == null)
+      return value.toString();
+    return describeEnum(value!);
+  }
+}
+
+/// A property where the important diagnostic information is primarily whether
+/// the [value] is present (non-null) or absent (null), rather than the actual
+/// value of the property itself.
+///
+/// The [ifPresent] and [ifNull] strings describe the property [value] when it
+/// is non-null and null respectively. If one of [ifPresent] or [ifNull] is
+/// omitted, that is taken to mean that [level] should be
+/// [DiagnosticLevel.hidden] when [value] is non-null or null respectively.
+///
+/// This kind of diagnostics property is typically used for opaque
+/// values, like closures, where presenting the actual object is of dubious
+/// value but where reporting the presence or absence of the value is much more
+/// useful.
+///
+/// See also:
+///
+///
+///  * [FlagsSummary], which provides similar functionality but accepts multiple
+///    flags under the same name, and is preferred if there are multiple such
+///    values that can fit into a same category (such as "listeners").
+///  * [FlagProperty], which provides similar functionality describing whether
+///    a [value] is true or false.
+class ObjectFlagProperty<T> extends DiagnosticsProperty<T> {
+  /// Create a diagnostics property for values that can be present (non-null) or
+  /// absent (null), but for which the exact value's [Object.toString]
+  /// representation is not very transparent (e.g. a callback).
+  ///
+  /// The [showName] and [level] arguments must not be null. Additionally, at
+  /// least one of [ifPresent] and [ifNull] must not be null.
+  ObjectFlagProperty(
+    String name,
+    T? value, {
+    this.ifPresent,
+    String? ifNull,
+    bool showName = false,
+    DiagnosticLevel level  = DiagnosticLevel.info,
+  }) : assert(ifPresent != null || ifNull != null),
+       assert(showName != null),
+       assert(level != null),
+       super(
+    name,
+    value,
+    showName: showName,
+    ifNull: ifNull,
+    level: level,
+  );
+
+  /// Shorthand constructor to describe whether the property has a value.
+  ///
+  /// Only use if prefixing the property name with the word 'has' is a good
+  /// flag name.
+  ///
+  /// The [name] and [level] arguments must not be null.
+  ObjectFlagProperty.has(
+    String name,
+    T? value, {
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(name != null),
+       assert(level != null),
+       ifPresent = 'has $name',
+       super(
+    name,
+    value,
+    showName: false,
+    level: level,
+  );
+
+  /// Description to use if the property [value] is not null.
+  ///
+  /// If the property [value] is not null and [ifPresent] is null, the
+  /// [level] for the property is [DiagnosticLevel.hidden] and the description
+  /// from superclass is used.
+  final String? ifPresent;
+
+  @override
+  String valueToString({ TextTreeConfiguration? parentConfiguration }) {
+    if (value != null) {
+      if (ifPresent != null)
+        return ifPresent!;
+    } else {
+      if (ifNull != null)
+        return ifNull!;
+    }
+    return super.valueToString(parentConfiguration: parentConfiguration);
+  }
+
+  @override
+  bool get showName {
+    if ((value != null && ifPresent == null) || (value == null && ifNull == null)) {
+      // We are missing a description for the flag value so we need to show the
+      // flag name. The property will have DiagnosticLevel.hidden for this case
+      // so users will not see this the property in this case unless they are
+      // displaying hidden properties.
+      return true;
+    }
+    return super.showName;
+  }
+
+  @override
+  DiagnosticLevel get level {
+    if (value != null) {
+      if (ifPresent == null)
+        return DiagnosticLevel.hidden;
+    } else {
+      if (ifNull == null)
+        return DiagnosticLevel.hidden;
+    }
+
+    return super.level;
+  }
+
+  @override
+  Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
+    final Map<String, Object?> json = super.toJsonMap(delegate);
+    if (ifPresent != null)
+      json['ifPresent'] = ifPresent;
+    return json;
+  }
+}
+
+/// A summary of multiple properties, indicating whether each of them is present
+/// (non-null) or absent (null).
+///
+/// Each entry of [value] is described by its key. The eventual description will
+/// be a list of keys of non-null entries.
+///
+/// The [ifEmpty] describes the entire collection of [value] when it contains no
+/// non-null entries. If [ifEmpty] is omitted, [level] will be
+/// [DiagnosticLevel.hidden] when [value] contains no non-null entries.
+///
+/// This kind of diagnostics property is typically used for opaque
+/// values, like closures, where presenting the actual object is of dubious
+/// value but where reporting the presence or absence of the value is much more
+/// useful.
+///
+/// See also:
+///
+///  * [ObjectFlagProperty], which provides similar functionality but accepts
+///    only one flag, and is preferred if there is only one entry.
+///  * [IterableProperty], which provides similar functionality describing
+///    the values a collection of objects.
+class FlagsSummary<T> extends DiagnosticsProperty<Map<String, T?>> {
+  /// Create a summary for multiple properties, indicating whether each of them
+  /// is present (non-null) or absent (null).
+  ///
+  /// The [value], [showName], [showSeparator] and [level] arguments must not be
+  /// null.
+  FlagsSummary(
+    String name,
+    Map<String, T?> value, {
+    String? ifEmpty,
+    bool showName = true,
+    bool showSeparator = true,
+    DiagnosticLevel level  = DiagnosticLevel.info,
+  }) : assert(value != null),
+       assert(showName != null),
+       assert(showSeparator != null),
+       assert(level != null),
+       super(
+         name,
+         value,
+         ifEmpty: ifEmpty,
+         showName: showName,
+         showSeparator: showSeparator,
+         level: level,
+       );
+
+  @override
+  Map<String, T?> get value => super.value!;
+
+  @override
+  String valueToString({TextTreeConfiguration? parentConfiguration}) {
+    assert(value != null);
+    if (!_hasNonNullEntry() && ifEmpty != null)
+      return ifEmpty!;
+
+    final Iterable<String> formattedValues = _formattedValues();
+    if (parentConfiguration != null && !parentConfiguration.lineBreakProperties) {
+      // Always display the value as a single line and enclose the iterable
+      // value in brackets to avoid ambiguity.
+      return '[${formattedValues.join(', ')}]';
+    }
+
+    return formattedValues.join(_isSingleLine(style) ? ', ' : '\n');
+  }
+
+  /// Priority level of the diagnostic used to control which diagnostics should
+  /// be shown and filtered.
+  ///
+  /// If [ifEmpty] is null and the [value] contains no non-null entries, then
+  /// level [DiagnosticLevel.hidden] is returned.
+  @override
+  DiagnosticLevel get level {
+    if (!_hasNonNullEntry() && ifEmpty == null)
+      return DiagnosticLevel.hidden;
+    return super.level;
+  }
+
+  @override
+  Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
+    final Map<String, Object?> json = super.toJsonMap(delegate);
+    if (value.isNotEmpty)
+      json['values'] = _formattedValues().toList();
+    return json;
+  }
+
+  bool _hasNonNullEntry() => value.values.any((T? o) => o != null);
+
+  // An iterable of each entry's description in [value].
+  //
+  // For a non-null value, its description is its key.
+  //
+  // For a null value, it is omitted unless `includeEmtpy` is true and
+  // [ifEntryNull] contains a corresponding description.
+  Iterable<String> _formattedValues() sync* {
+    for (final MapEntry<String, T?> entry in value.entries) {
+      if (entry.value != null) {
+        yield entry.key;
+      }
+    }
+  }
+}
+
+/// Signature for computing the value of a property.
+///
+/// May throw exception if accessing the property would throw an exception
+/// and callers must handle that case gracefully. For example, accessing a
+/// property may trigger an assert that layout constraints were violated.
+typedef ComputePropertyValueCallback<T> = T? Function();
+
+/// Property with a [value] of type [T].
+///
+/// If the default `value.toString()` does not provide an adequate description
+/// of the value, specify `description` defining a custom description.
+///
+/// The [showSeparator] property indicates whether a separator should be placed
+/// between the property [name] and its [value].
+class DiagnosticsProperty<T> extends DiagnosticsNode {
+  /// Create a diagnostics property.
+  ///
+  /// The [showName], [showSeparator], [style], [missingIfNull], and [level]
+  /// arguments must not be null.
+  ///
+  /// The [level] argument is just a suggestion and can be overridden if
+  /// something else about the property causes it to have a lower or higher
+  /// level. For example, if the property value is null and [missingIfNull] is
+  /// true, [level] is raised to [DiagnosticLevel.warning].
+  DiagnosticsProperty(
+    String? name,
+    T? value, {
+    String? description,
+    String? ifNull,
+    this.ifEmpty,
+    bool showName = true,
+    bool showSeparator = true,
+    this.defaultValue = kNoDefaultValue,
+    this.tooltip,
+    this.missingIfNull = false,
+    String? linePrefix,
+    this.expandableValue = false,
+    this.allowWrap = true,
+    this.allowNameWrap = true,
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(showName != null),
+       assert(showSeparator != null),
+       assert(style != null),
+       assert(level != null),
+       _description = description,
+       _valueComputed = true,
+       _value = value,
+       _computeValue = null,
+       ifNull = ifNull ?? (missingIfNull ? 'MISSING' : null),
+       _defaultLevel = level,
+       super(
+         name: name,
+         showName: showName,
+         showSeparator: showSeparator,
+         style: style,
+         linePrefix: linePrefix,
+      );
+
+  /// Property with a [value] that is computed only when needed.
+  ///
+  /// Use if computing the property [value] may throw an exception or is
+  /// expensive.
+  ///
+  /// The [showName], [showSeparator], [style], [missingIfNull], and [level]
+  /// arguments must not be null.
+  ///
+  /// The [level] argument is just a suggestion and can be overridden if
+  /// if something else about the property causes it to have a lower or higher
+  /// level. For example, if calling `computeValue` throws an exception, [level]
+  /// will always return [DiagnosticLevel.error].
+  DiagnosticsProperty.lazy(
+    String? name,
+    ComputePropertyValueCallback<T> computeValue, {
+    String? description,
+    String? ifNull,
+    this.ifEmpty,
+    bool showName = true,
+    bool showSeparator = true,
+    this.defaultValue = kNoDefaultValue,
+    this.tooltip,
+    this.missingIfNull = false,
+    this.expandableValue = false,
+    this.allowWrap = true,
+    this.allowNameWrap = true,
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(showName != null),
+       assert(showSeparator != null),
+       assert(defaultValue == kNoDefaultValue || defaultValue is T?),
+       assert(missingIfNull != null),
+       assert(style != null),
+       assert(level != null),
+       _description = description,
+       _valueComputed = false,
+       _value = null,
+       _computeValue = computeValue,
+       _defaultLevel = level,
+       ifNull = ifNull ?? (missingIfNull ? 'MISSING' : null),
+       super(
+         name: name,
+         showName: showName,
+         showSeparator: showSeparator,
+         style: style,
+       );
+
+  final String? _description;
+
+  /// Whether to expose properties and children of the value as properties and
+  /// children.
+  final bool expandableValue;
+
+  @override
+  final bool allowWrap;
+
+  @override
+  final bool allowNameWrap;
+
+  @override
+  Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
+    final T? v = value;
+    List<Map<String, Object?>>? properties;
+    if (delegate.expandPropertyValues && delegate.includeProperties && v is Diagnosticable && getProperties().isEmpty) {
+      // Exclude children for expanded nodes to avoid cycles.
+      delegate = delegate.copyWith(subtreeDepth: 0, includeProperties: false);
+      properties = DiagnosticsNode.toJsonList(
+        delegate.filterProperties(v.toDiagnosticsNode().getProperties(), this),
+        this,
+        delegate,
+      );
+    }
+    final Map<String, Object?> json = super.toJsonMap(delegate);
+    if (properties != null) {
+      json['properties'] = properties;
+    }
+    if (defaultValue != kNoDefaultValue)
+      json['defaultValue'] = defaultValue.toString();
+    if (ifEmpty != null)
+      json['ifEmpty'] = ifEmpty;
+    if (ifNull != null)
+      json['ifNull'] = ifNull;
+    if (tooltip != null)
+      json['tooltip'] = tooltip;
+    json['missingIfNull'] = missingIfNull;
+    if (exception != null)
+      json['exception'] = exception.toString();
+    json['propertyType'] = propertyType.toString();
+    json['defaultLevel'] = describeEnum(_defaultLevel);
+    if (value is Diagnosticable || value is DiagnosticsNode)
+      json['isDiagnosticableValue'] = true;
+    if (v is num)
+      // Workaround for https://github.com/flutter/flutter/issues/39937#issuecomment-529558033.
+      // JSON.stringify replaces infinity and NaN with null.
+      json['value'] = v.isFinite ? v :  v.toString();
+    if (value is String || value is bool || value == null)
+      json['value'] = value;
+    return json;
+  }
+
+  /// Returns a string representation of the property value.
+  ///
+  /// Subclasses should override this method instead of [toDescription] to
+  /// customize how property values are converted to strings.
+  ///
+  /// Overriding this method ensures that behavior controlling how property
+  /// values are decorated to generate a nice [toDescription] are consistent
+  /// across all implementations. Debugging tools may also choose to use
+  /// [valueToString] directly instead of [toDescription].
+  ///
+  /// `parentConfiguration` specifies how the parent is rendered as text art.
+  /// For example, if the parent places all properties on one line, the value
+  /// of the property should be displayed without line breaks if possible.
+  String valueToString({ TextTreeConfiguration? parentConfiguration }) {
+    final T? v = value;
+    // DiagnosticableTree values are shown using the shorter toStringShort()
+    // instead of the longer toString() because the toString() for a
+    // DiagnosticableTree value is likely too large to be useful.
+    return v is DiagnosticableTree ? v.toStringShort() : v.toString();
+  }
+
+  @override
+  String toDescription({ TextTreeConfiguration? parentConfiguration }) {
+    if (_description != null)
+      return _addTooltip(_description!);
+
+    if (exception != null)
+      return 'EXCEPTION (${exception.runtimeType})';
+
+    if (ifNull != null && value == null)
+      return _addTooltip(ifNull!);
+
+    String result = valueToString(parentConfiguration: parentConfiguration);
+    if (result.isEmpty && ifEmpty != null)
+      result = ifEmpty!;
+    return _addTooltip(result);
+  }
+
+  /// If a [tooltip] is specified, add the tooltip it to the end of `text`
+  /// enclosing it parenthesis to disambiguate the tooltip from the rest of
+  /// the text.
+  ///
+  /// `text` must not be null.
+  String _addTooltip(String text) {
+    assert(text != null);
+    return tooltip == null ? text : '$text ($tooltip)';
+  }
+
+  /// Description if the property [value] is null.
+  final String? ifNull;
+
+  /// Description if the property description would otherwise be empty.
+  final String? ifEmpty;
+
+  /// Optional tooltip typically describing the property.
+  ///
+  /// Example tooltip: 'physical pixels per logical pixel'
+  ///
+  /// If present, the tooltip is added in parenthesis after the raw value when
+  /// generating the string description.
+  final String? tooltip;
+
+  /// Whether a [value] of null causes the property to have [level]
+  /// [DiagnosticLevel.warning] warning that the property is missing a [value].
+  final bool missingIfNull;
+
+  /// The type of the property [value].
+  ///
+  /// This is determined from the type argument `T` used to instantiate the
+  /// [DiagnosticsProperty] class. This means that the type is available even if
+  /// [value] is null, but it also means that the [propertyType] is only as
+  /// accurate as the type provided when invoking the constructor.
+  ///
+  /// Generally, this is only useful for diagnostic tools that should display
+  /// null values in a manner consistent with the property type. For example, a
+  /// tool might display a null [Color] value as an empty rectangle instead of
+  /// the word "null".
+  Type get propertyType => T;
+
+  /// Returns the value of the property either from cache or by invoking a
+  /// [ComputePropertyValueCallback].
+  ///
+  /// If an exception is thrown invoking the [ComputePropertyValueCallback],
+  /// [value] returns null and the exception thrown can be found via the
+  /// [exception] property.
+  ///
+  /// See also:
+  ///
+  ///  * [valueToString], which converts the property value to a string.
+  @override
+  T? get value {
+    _maybeCacheValue();
+    return _value;
+  }
+
+  T? _value;
+
+  bool _valueComputed;
+
+  Object? _exception;
+
+  /// Exception thrown if accessing the property [value] threw an exception.
+  ///
+  /// Returns null if computing the property value did not throw an exception.
+  Object? get exception {
+    _maybeCacheValue();
+    return _exception;
+  }
+
+  void _maybeCacheValue() {
+    if (_valueComputed)
+      return;
+
+    _valueComputed = true;
+    assert(_computeValue != null);
+    try {
+      _value = _computeValue!();
+    } catch (exception) {
+      _exception = exception;
+      _value = null;
+    }
+  }
+
+  /// If the [value] of the property equals [defaultValue] the priority [level]
+  /// of the property is downgraded to [DiagnosticLevel.fine] as the property
+  /// value is uninteresting.
+  ///
+  /// The [defaultValue] is [kNoDefaultValue] by default. Otherwise it must be of
+  /// type `T?`.
+  final Object? defaultValue;
+
+  final DiagnosticLevel _defaultLevel;
+
+  /// Priority level of the diagnostic used to control which diagnostics should
+  /// be shown and filtered.
+  ///
+  /// The property level defaults to the value specified by the `level`
+  /// constructor argument. The level is raised to [DiagnosticLevel.error] if
+  /// an [exception] was thrown getting the property [value]. The level is
+  /// raised to [DiagnosticLevel.warning] if the property [value] is null and
+  /// the property is not allowed to be null due to [missingIfNull]. The
+  /// priority level is lowered to [DiagnosticLevel.fine] if the property
+  /// [value] equals [defaultValue].
+  @override
+  DiagnosticLevel get level {
+    if (_defaultLevel == DiagnosticLevel.hidden)
+      return _defaultLevel;
+
+    if (exception != null)
+      return DiagnosticLevel.error;
+
+    if (value == null && missingIfNull)
+      return DiagnosticLevel.warning;
+
+    // Use a low level when the value matches the default value.
+    if (defaultValue != kNoDefaultValue && value == defaultValue)
+      return DiagnosticLevel.fine;
+
+    return _defaultLevel;
+  }
+
+  final ComputePropertyValueCallback<T>? _computeValue;
+
+  @override
+  List<DiagnosticsNode> getProperties() {
+    if (expandableValue) {
+      final T? object = value;
+      if (object is DiagnosticsNode) {
+        return object.getProperties();
+      }
+      if (object is Diagnosticable) {
+        return object.toDiagnosticsNode(style: style).getProperties();
+      }
+    }
+    return const <DiagnosticsNode>[];
+  }
+
+  @override
+  List<DiagnosticsNode> getChildren() {
+    if (expandableValue) {
+      final T? object = value;
+      if (object is DiagnosticsNode) {
+        return object.getChildren();
+      }
+      if (object is Diagnosticable) {
+        return object.toDiagnosticsNode(style: style).getChildren();
+      }
+    }
+    return const <DiagnosticsNode>[];
+  }
+}
+
+/// [DiagnosticsNode] that lazily calls the associated [Diagnosticable] [value]
+/// to implement [getChildren] and [getProperties].
+class DiagnosticableNode<T extends Diagnosticable> extends DiagnosticsNode {
+  /// Create a diagnostics describing a [Diagnosticable] value.
+  ///
+  /// The [value] argument must not be null.
+  DiagnosticableNode({
+    String? name,
+    required this.value,
+    required DiagnosticsTreeStyle? style,
+  }) : assert(value != null),
+       super(
+         name: name,
+         style: style,
+       );
+
+  @override
+  final T value;
+
+  DiagnosticPropertiesBuilder? _cachedBuilder;
+
+  /// Retrieve the [DiagnosticPropertiesBuilder] of current node.
+  ///
+  /// It will cache the result to prevent duplicate operation.
+  DiagnosticPropertiesBuilder? get builder {
+    if (kReleaseMode) {
+      return null;
+    } else {
+      assert(() {
+        if (_cachedBuilder == null) {
+          _cachedBuilder = DiagnosticPropertiesBuilder();
+          value.debugFillProperties(_cachedBuilder!);
+        }
+        return true;
+      }());
+      return _cachedBuilder;
+    }
+  }
+
+  @override
+  DiagnosticsTreeStyle get style {
+    return kReleaseMode ? DiagnosticsTreeStyle.none : super.style ?? builder!.defaultDiagnosticsTreeStyle;
+  }
+
+  @override
+  String? get emptyBodyDescription => (kReleaseMode || kProfileMode) ? '' : builder!.emptyBodyDescription;
+
+  @override
+  List<DiagnosticsNode> getProperties() => (kReleaseMode || kProfileMode) ? const <DiagnosticsNode>[] : builder!.properties;
+
+  @override
+  List<DiagnosticsNode> getChildren() {
+    return const<DiagnosticsNode>[];
+  }
+
+  @override
+  String toDescription({ TextTreeConfiguration? parentConfiguration }) {
+    String result = '';
+    assert(() {
+      result = value.toStringShort();
+      return true;
+    }());
+    return result;
+  }
+}
+
+/// [DiagnosticsNode] for an instance of [DiagnosticableTree].
+class DiagnosticableTreeNode extends DiagnosticableNode<DiagnosticableTree> {
+  /// Creates a [DiagnosticableTreeNode].
+  DiagnosticableTreeNode({
+    String? name,
+    required DiagnosticableTree value,
+    required DiagnosticsTreeStyle? style,
+  }) : super(
+         name: name,
+         value: value,
+         style: style,
+       );
+
+  @override
+  List<DiagnosticsNode> getChildren() => value.debugDescribeChildren();
+}
+
+/// Returns a 5 character long hexadecimal string generated from
+/// [Object.hashCode]'s 20 least-significant bits.
+String shortHash(Object? object) {
+  return object.hashCode.toUnsigned(20).toRadixString(16).padLeft(5, '0');
+}
+
+/// Returns a summary of the runtime type and hash code of `object`.
+///
+/// See also:
+///
+///  * [Object.hashCode], a value used when placing an object in a [Map] or
+///    other similar data structure, and which is also used in debug output to
+///    distinguish instances of the same class (hash collisions are
+///    possible, but rare enough that its use in debug output is useful).
+///  * [Object.runtimeType], the [Type] of an object.
+String describeIdentity(Object? object) => '${objectRuntimeType(object, '<optimized out>')}#${shortHash(object)}';
+
+// This method exists as a workaround for https://github.com/dart-lang/sdk/issues/30021
+/// Returns a short description of an enum value.
+///
+/// Strips off the enum class name from the `enumEntry.toString()`.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// enum Day {
+///   monday, tuesday, wednesday, thursday, friday, saturday, sunday
+/// }
+///
+/// void validateDescribeEnum() {
+///   assert(Day.monday.toString() == 'Day.monday');
+///   assert(describeEnum(Day.monday) == 'monday');
+/// }
+/// ```
+/// {@end-tool}
+String describeEnum(Object enumEntry) {
+  final String description = enumEntry.toString();
+  final int indexOfDot = description.indexOf('.');
+  assert(
+    indexOfDot != -1 && indexOfDot < description.length - 1,
+    'The provided object "$enumEntry" is not an enum.',
+  );
+  return description.substring(indexOfDot + 1);
+}
+
+/// Builder to accumulate properties and configuration used to assemble a
+/// [DiagnosticsNode] from a [Diagnosticable] object.
+class DiagnosticPropertiesBuilder {
+  /// Creates a [DiagnosticPropertiesBuilder] with [properties] initialize to
+  /// an empty array.
+  DiagnosticPropertiesBuilder() : properties = <DiagnosticsNode>[];
+
+  /// Creates a [DiagnosticPropertiesBuilder] with a given [properties].
+  DiagnosticPropertiesBuilder.fromProperties(this.properties);
+
+  /// Add a property to the list of properties.
+  void add(DiagnosticsNode property) {
+    assert(() {
+      properties.add(property);
+      return true;
+    }());
+  }
+
+  /// List of properties accumulated so far.
+  final List<DiagnosticsNode> properties;
+
+  /// Default style to use for the [DiagnosticsNode] if no style is specified.
+  DiagnosticsTreeStyle defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.sparse;
+
+  /// Description to show if the node has no displayed properties or children.
+  String? emptyBodyDescription;
+}
+
+// Examples can assume:
+// class ExampleSuperclass with Diagnosticable { late String message; late double stepWidth; late double scale; late double paintExtent; late double hitTestExtent; late double paintExtend; late double maxWidth; late bool primary; late double progress; late int maxLines; late Duration duration; late int depth; late dynamic boxShadow; late dynamic style; late bool hasSize; late Matrix4 transform; Map<Listenable, VoidCallback>? handles; late Color color; late bool obscureText; late ImageRepeat repeat; late Size size; late Widget widget; late bool isCurrent; late bool keepAlive; late TextAlign textAlign; }
+
+/// A mixin class for providing string and [DiagnosticsNode] debug
+/// representations describing the properties of an object.
+///
+/// The string debug representation is generated from the intermediate
+/// [DiagnosticsNode] representation. The [DiagnosticsNode] representation is
+/// also used by debugging tools displaying interactive trees of objects and
+/// properties.
+///
+/// See also:
+///
+///  * [debugFillProperties], which lists best practices for specifying the
+///    properties of a [DiagnosticsNode]. The most common use case is to
+///    override [debugFillProperties] defining custom properties for a subclass
+///    of [DiagnosticableTreeMixin] using the existing [DiagnosticsProperty]
+///    subclasses.
+///  * [DiagnosticableTree], which extends this class to also describe the
+///    children of a tree structured object.
+///  * [DiagnosticableTree.debugDescribeChildren], which lists best practices
+///    for describing the children of a [DiagnosticsNode]. Typically the base
+///    class already describes the children of a node properly or a node has
+///    no children.
+///  * [DiagnosticsProperty], which should be used to create leaf diagnostic
+///    nodes without properties or children. There are many
+///    [DiagnosticsProperty] subclasses to handle common use cases.
+mixin Diagnosticable {
+  /// A brief description of this object, usually just the [runtimeType] and the
+  /// [hashCode].
+  ///
+  /// See also:
+  ///
+  ///  * [toString], for a detailed description of the object.
+  String toStringShort() => describeIdentity(this);
+
+  @override
+  String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
+    String? fullString;
+    assert(() {
+      fullString = toDiagnosticsNode(style: DiagnosticsTreeStyle.singleLine).toString(minLevel: minLevel);
+      return true;
+    }());
+    return fullString ?? toStringShort();
+  }
+
+  /// Returns a debug representation of the object that is used by debugging
+  /// tools and by [DiagnosticsNode.toStringDeep].
+  ///
+  /// Leave [name] as null if there is not a meaningful description of the
+  /// relationship between the this node and its parent.
+  ///
+  /// Typically the [style] argument is only specified to indicate an atypical
+  /// relationship between the parent and the node. For example, pass
+  /// [DiagnosticsTreeStyle.offstage] to indicate that a node is offstage.
+  DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
+    return DiagnosticableNode<Diagnosticable>(
+      name: name,
+      value: this,
+      style: style,
+    );
+  }
+
+  /// Add additional properties associated with the node.
+  ///
+  /// Use the most specific [DiagnosticsProperty] existing subclass to describe
+  /// each property instead of the [DiagnosticsProperty] base class. There are
+  /// only a small number of [DiagnosticsProperty] subclasses each covering a
+  /// common use case. Consider what values a property is relevant for users
+  /// debugging as users debugging large trees are overloaded with information.
+  /// Common named parameters in [DiagnosticsNode] subclasses help filter when
+  /// and how properties are displayed.
+  ///
+  /// `defaultValue`, `showName`, `showSeparator`, and `level` keep string
+  /// representations of diagnostics terse and hide properties when they are not
+  /// very useful.
+  ///
+  ///  * Use `defaultValue` any time the default value of a property is
+  ///    uninteresting. For example, specify a default value of null any time
+  ///    a property being null does not indicate an error.
+  ///  * Avoid specifying the `level` parameter unless the result you want
+  ///    cannot be achieved by using the `defaultValue` parameter or using
+  ///    the [ObjectFlagProperty] class to conditionally display the property
+  ///    as a flag.
+  ///  * Specify `showName` and `showSeparator` in rare cases where the string
+  ///    output would look clumsy if they were not set.
+  ///    ```dart
+  ///    DiagnosticsProperty<Object>('child(3, 4)', null, ifNull: 'is null', showSeparator: false).toString()
+  ///    ```
+  ///    Shows using `showSeparator` to get output `child(3, 4) is null` which
+  ///    is more polished than `child(3, 4): is null`.
+  ///    ```dart
+  ///    DiagnosticsProperty<IconData>('icon', icon, ifNull: '<empty>', showName: false)).toString()
+  ///    ```
+  ///    Shows using `showName` to omit the property name as in this context the
+  ///    property name does not add useful information.
+  ///
+  /// `ifNull`, `ifEmpty`, `unit`, and `tooltip` make property
+  /// descriptions clearer. The examples in the code sample below illustrate
+  /// good uses of all of these parameters.
+  ///
+  /// ## DiagnosticsProperty subclasses for primitive types
+  ///
+  ///  * [StringProperty], which supports automatically enclosing a [String]
+  ///    value in quotes.
+  ///  * [DoubleProperty], which supports specifying a unit of measurement for
+  ///    a [double] value.
+  ///  * [PercentProperty], which clamps a [double] to between 0 and 1 and
+  ///    formats it as a percentage.
+  ///  * [IntProperty], which supports specifying a unit of measurement for an
+  ///    [int] value.
+  ///  * [FlagProperty], which formats a [bool] value as one or more flags.
+  ///    Depending on the use case it is better to format a bool as
+  ///    `DiagnosticsProperty<bool>` instead of using [FlagProperty] as the
+  ///    output is more verbose but unambiguous.
+  ///
+  /// ## Other important [DiagnosticsProperty] variants
+  ///
+  ///  * [EnumProperty], which provides terse descriptions of enum values
+  ///    working around limitations of the `toString` implementation for Dart
+  ///    enum types.
+  ///  * [IterableProperty], which handles iterable values with display
+  ///    customizable depending on the [DiagnosticsTreeStyle] used.
+  ///  * [ObjectFlagProperty], which provides terse descriptions of whether a
+  ///    property value is present or not. For example, whether an `onClick`
+  ///    callback is specified or an animation is in progress.
+  ///  * [ColorProperty], which must be used if the property value is
+  ///    a [Color] or one of its subclasses.
+  ///  * [IconDataProperty], which must be used if the property value
+  ///    is of type [IconData].
+  ///
+  /// If none of these subclasses apply, use the [DiagnosticsProperty]
+  /// constructor or in rare cases create your own [DiagnosticsProperty]
+  /// subclass as in the case for [TransformProperty] which handles [Matrix4]
+  /// that represent transforms. Generally any property value with a good
+  /// `toString` method implementation works fine using [DiagnosticsProperty]
+  /// directly.
+  ///
+  /// {@tool snippet}
+  ///
+  /// This example shows best practices for implementing [debugFillProperties]
+  /// illustrating use of all common [DiagnosticsProperty] subclasses and all
+  /// common [DiagnosticsProperty] parameters.
+  ///
+  /// ```dart
+  /// class ExampleObject extends ExampleSuperclass {
+  ///
+  ///   // ...various members and properties...
+  ///
+  ///   @override
+  ///   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+  ///     // Always add properties from the base class first.
+  ///     super.debugFillProperties(properties);
+  ///
+  ///     // Omit the property name 'message' when displaying this String property
+  ///     // as it would just add visual noise.
+  ///     properties.add(StringProperty('message', message, showName: false));
+  ///
+  ///     properties.add(DoubleProperty('stepWidth', stepWidth));
+  ///
+  ///     // A scale of 1.0 does nothing so should be hidden.
+  ///     properties.add(DoubleProperty('scale', scale, defaultValue: 1.0));
+  ///
+  ///     // If the hitTestExtent matches the paintExtent, it is just set to its
+  ///     // default value so is not relevant.
+  ///     properties.add(DoubleProperty('hitTestExtent', hitTestExtent, defaultValue: paintExtent));
+  ///
+  ///     // maxWidth of double.infinity indicates the width is unconstrained and
+  ///     // so maxWidth has no impact.,
+  ///     properties.add(DoubleProperty('maxWidth', maxWidth, defaultValue: double.infinity));
+  ///
+  ///     // Progress is a value between 0 and 1 or null. Showing it as a
+  ///     // percentage makes the meaning clear enough that the name can be
+  ///     // hidden.
+  ///     properties.add(PercentProperty(
+  ///       'progress',
+  ///       progress,
+  ///       showName: false,
+  ///       ifNull: '<indeterminate>',
+  ///     ));
+  ///
+  ///     // Most text fields have maxLines set to 1.
+  ///     properties.add(IntProperty('maxLines', maxLines, defaultValue: 1));
+  ///
+  ///     // Specify the unit as otherwise it would be unclear that time is in
+  ///     // milliseconds.
+  ///     properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms'));
+  ///
+  ///     // Tooltip is used instead of unit for this case as a unit should be a
+  ///     // terse description appropriate to display directly after a number
+  ///     // without a space.
+  ///     properties.add(DoubleProperty(
+  ///       'device pixel ratio',
+  ///       ui.window.devicePixelRatio,
+  ///       tooltip: 'physical pixels per logical pixel',
+  ///     ));
+  ///
+  ///     // Displaying the depth value would be distracting. Instead only display
+  ///     // if the depth value is missing.
+  ///     properties.add(ObjectFlagProperty<int>('depth', depth, ifNull: 'no depth'));
+  ///
+  ///     // bool flag that is only shown when the value is true.
+  ///     properties.add(FlagProperty('using primary controller', value: primary));
+  ///
+  ///     properties.add(FlagProperty(
+  ///       'isCurrent',
+  ///       value: isCurrent,
+  ///       ifTrue: 'active',
+  ///       ifFalse: 'inactive',
+  ///       showName: false,
+  ///     ));
+  ///
+  ///     properties.add(DiagnosticsProperty<bool>('keepAlive', keepAlive));
+  ///
+  ///     // FlagProperty could have also been used in this case.
+  ///     // This option results in the text "obscureText: true" instead
+  ///     // of "obscureText" which is a bit more verbose but a bit clearer.
+  ///     properties.add(DiagnosticsProperty<bool>('obscureText', obscureText, defaultValue: false));
+  ///
+  ///     properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
+  ///     properties.add(EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
+  ///
+  ///     // Warn users when the widget is missing but do not show the value.
+  ///     properties.add(ObjectFlagProperty<Widget>('widget', widget, ifNull: 'no widget'));
+  ///
+  ///     properties.add(IterableProperty<BoxShadow>(
+  ///       'boxShadow',
+  ///       boxShadow,
+  ///       defaultValue: null,
+  ///       style: style,
+  ///     ));
+  ///
+  ///     // Getting the value of size throws an exception unless hasSize is true.
+  ///     properties.add(DiagnosticsProperty<Size>.lazy(
+  ///       'size',
+  ///       () => size,
+  ///       description: '${ hasSize ? size : "MISSING" }',
+  ///     ));
+  ///
+  ///     // If the `toString` method for the property value does not provide a
+  ///     // good terse description, write a DiagnosticsProperty subclass as in
+  ///     // the case of TransformProperty which displays a nice debugging view
+  ///     // of a Matrix4 that represents a transform.
+  ///     properties.add(TransformProperty('transform', transform));
+  ///
+  ///     // If the value class has a good `toString` method, use
+  ///     // DiagnosticsProperty<YourValueType>. Specifying the value type ensures
+  ///     // that debugging tools always know the type of the field and so can
+  ///     // provide the right UI affordances. For example, in this case even
+  ///     // if color is null, a debugging tool still knows the value is a Color
+  ///     // and can display relevant color related UI.
+  ///     properties.add(DiagnosticsProperty<Color>('color', color));
+  ///
+  ///     // Use a custom description to generate a more terse summary than the
+  ///     // `toString` method on the map class.
+  ///     properties.add(DiagnosticsProperty<Map<Listenable, VoidCallback>>(
+  ///       'handles',
+  ///       handles,
+  ///       description: handles != null
+  ///         ? '${handles!.length} active client${ handles!.length == 1 ? "" : "s" }'
+  ///         : null,
+  ///       ifNull: 'no notifications ever received',
+  ///       showName: false,
+  ///     ));
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// Used by [toDiagnosticsNode] and [toString].
+  @protected
+  @mustCallSuper
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) { }
+}
+
+/// A base class for providing string and [DiagnosticsNode] debug
+/// representations describing the properties and children of an object.
+///
+/// The string debug representation is generated from the intermediate
+/// [DiagnosticsNode] representation. The [DiagnosticsNode] representation is
+/// also used by debugging tools displaying interactive trees of objects and
+/// properties.
+///
+/// See also:
+///
+///  * [DiagnosticableTreeMixin], a mixin that implements this class.
+///  * [Diagnosticable], which should be used instead of this class to
+///    provide diagnostics for objects without children.
+abstract class DiagnosticableTree with Diagnosticable {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const DiagnosticableTree();
+
+  /// Returns a one-line detailed description of the object.
+  ///
+  /// This description is often somewhat long. This includes the same
+  /// information given by [toStringDeep], but does not recurse to any children.
+  ///
+  /// `joiner` specifies the string which is place between each part obtained
+  /// from [debugFillProperties]. Passing a string such as `'\n '` will result
+  /// in a multiline string that indents the properties of the object below its
+  /// name (as per [toString]).
+  ///
+  /// `minLevel` specifies the minimum [DiagnosticLevel] for properties included
+  /// in the output.
+  ///
+  /// See also:
+  ///
+  ///  * [toString], for a brief description of the object.
+  ///  * [toStringDeep], for a description of the subtree rooted at this object.
+  String toStringShallow({
+    String joiner = ', ',
+    DiagnosticLevel minLevel = DiagnosticLevel.debug,
+  }) {
+    String? shallowString;
+    assert(() {
+      final StringBuffer result = StringBuffer();
+      result.write(toString());
+      result.write(joiner);
+      final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
+      debugFillProperties(builder);
+      result.write(
+        builder.properties.where((DiagnosticsNode n) => !n.isFiltered(minLevel))
+            .join(joiner),
+      );
+      shallowString = result.toString();
+      return true;
+    }());
+    return shallowString ?? toString();
+  }
+
+  /// Returns a string representation of this node and its descendants.
+  ///
+  /// `prefixLineOne` will be added to the front of the first line of the
+  /// output. `prefixOtherLines` will be added to the front of each other line.
+  /// If `prefixOtherLines` is null, the `prefixLineOne` is used for every line.
+  /// By default, there is no prefix.
+  ///
+  /// `minLevel` specifies the minimum [DiagnosticLevel] for properties included
+  /// in the output.
+  ///
+  /// The [toStringDeep] method takes other arguments, but those are intended
+  /// for internal use when recursing to the descendants, and so can be ignored.
+  ///
+  /// See also:
+  ///
+  ///  * [toString], for a brief description of the object but not its children.
+  ///  * [toStringShallow], for a detailed description of the object but not its
+  ///    children.
+  String toStringDeep({
+    String prefixLineOne = '',
+    String? prefixOtherLines,
+    DiagnosticLevel minLevel = DiagnosticLevel.debug,
+  }) {
+    return toDiagnosticsNode().toStringDeep(prefixLineOne: prefixLineOne, prefixOtherLines: prefixOtherLines, minLevel: minLevel);
+  }
+
+  @override
+  String toStringShort() => describeIdentity(this);
+
+  @override
+  DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
+    return DiagnosticableTreeNode(
+      name: name,
+      value: this,
+      style: style,
+    );
+  }
+
+  /// Returns a list of [DiagnosticsNode] objects describing this node's
+  /// children.
+  ///
+  /// Children that are offstage should be added with `style` set to
+  /// [DiagnosticsTreeStyle.offstage] to indicate that they are offstage.
+  ///
+  /// The list must not contain any null entries. If there are explicit null
+  /// children to report, consider [new DiagnosticsNode.message] or
+  /// [DiagnosticsProperty<Object>] as possible [DiagnosticsNode] objects to
+  /// provide.
+  ///
+  /// Used by [toStringDeep], [toDiagnosticsNode] and [toStringShallow].
+  ///
+  /// See also:
+  ///
+  ///  * [RenderTable.debugDescribeChildren], which provides high quality custom
+  ///    descriptions for its child nodes.
+  @protected
+  List<DiagnosticsNode> debugDescribeChildren() => const <DiagnosticsNode>[];
+}
+
+/// A mixin that helps dump string and [DiagnosticsNode] representations of trees.
+///
+/// This mixin is identical to class [DiagnosticableTree].
+mixin DiagnosticableTreeMixin implements DiagnosticableTree {
+  @override
+  String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
+    return toDiagnosticsNode(style: DiagnosticsTreeStyle.singleLine).toString(minLevel: minLevel);
+  }
+
+  @override
+  String toStringShallow({
+    String joiner = ', ',
+    DiagnosticLevel minLevel = DiagnosticLevel.debug,
+  }) {
+    String? shallowString;
+    assert(() {
+      final StringBuffer result = StringBuffer();
+      result.write(toStringShort());
+      result.write(joiner);
+      final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
+      debugFillProperties(builder);
+      result.write(
+        builder.properties.where((DiagnosticsNode n) => !n.isFiltered(minLevel))
+            .join(joiner),
+      );
+      shallowString = result.toString();
+      return true;
+    }());
+    return shallowString ?? toString();
+  }
+
+  @override
+  String toStringDeep({
+    String prefixLineOne = '',
+    String? prefixOtherLines,
+    DiagnosticLevel minLevel = DiagnosticLevel.debug,
+  }) {
+    return toDiagnosticsNode().toStringDeep(prefixLineOne: prefixLineOne, prefixOtherLines: prefixOtherLines, minLevel: minLevel);
+  }
+
+  @override
+  String toStringShort() => describeIdentity(this);
+
+  @override
+  DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
+    return DiagnosticableTreeNode(
+      name: name,
+      value: this,
+      style: style,
+    );
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() => const <DiagnosticsNode>[];
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) { }
+}
+
+
+/// [DiagnosticsNode] that exists mainly to provide a container for other
+/// diagnostics that typically lacks a meaningful value of its own.
+///
+/// This class is typically used for displaying complex nested error messages.
+class DiagnosticsBlock extends DiagnosticsNode {
+  /// Creates a diagnostic with properties specified by [properties] and
+  /// children specified by [children].
+  DiagnosticsBlock({
+    String? name,
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.whitespace,
+    bool showName = true,
+    bool showSeparator = true,
+    String? linePrefix,
+    this.value,
+    String? description,
+    this.level = DiagnosticLevel.info,
+    this.allowTruncate = false,
+    List<DiagnosticsNode> children = const<DiagnosticsNode>[],
+    List<DiagnosticsNode> properties = const <DiagnosticsNode>[],
+  }) : _description = description,
+       _children = children,
+       _properties = properties,
+    super(
+    name: name,
+    style: style,
+    showName: showName && name != null,
+    showSeparator: showSeparator,
+    linePrefix: linePrefix,
+  );
+
+  final List<DiagnosticsNode> _children;
+  final List<DiagnosticsNode> _properties;
+
+  @override
+  final DiagnosticLevel level;
+  final String? _description;
+  @override
+  final Object? value;
+
+  @override
+  final bool allowTruncate;
+
+  @override
+  List<DiagnosticsNode> getChildren() => _children;
+
+  @override
+  List<DiagnosticsNode> getProperties() => _properties;
+
+  @override
+  String? toDescription({TextTreeConfiguration? parentConfiguration}) => _description;
+}
+
+/// A delegate that configures how a hierarchy of [DiagnosticsNode]s should be
+/// serialized.
+///
+/// Implement this class in a subclass to fully configure how [DiagnosticsNode]s
+/// get serialized.
+abstract class DiagnosticsSerializationDelegate {
+  /// Creates a simple [DiagnosticsSerializationDelegate] that controls the
+  /// [subtreeDepth] and whether to [includeProperties].
+  ///
+  /// For additional configuration options, extend
+  /// [DiagnosticsSerializationDelegate] and provide custom implementations
+  /// for the methods of this class.
+  const factory DiagnosticsSerializationDelegate({
+    int subtreeDepth,
+    bool includeProperties,
+  }) = _DefaultDiagnosticsSerializationDelegate;
+
+  /// Returns a serializable map of additional information that will be included
+  /// in the serialization of the given [DiagnosticsNode].
+  ///
+  /// This method is called for every [DiagnosticsNode] that's included in
+  /// the serialization.
+  Map<String, Object?> additionalNodeProperties(DiagnosticsNode node);
+
+  /// Filters the list of [DiagnosticsNode]s that will be included as children
+  /// for the given `owner` node.
+  ///
+  /// The callback may return a subset of the children in the provided list
+  /// or replace the entire list with new child nodes.
+  ///
+  /// See also:
+  ///
+  ///  * [subtreeDepth], which controls how many levels of children will be
+  ///    included in the serialization.
+  List<DiagnosticsNode> filterChildren(List<DiagnosticsNode> nodes, DiagnosticsNode owner);
+
+  /// Filters the list of [DiagnosticsNode]s that will be included as properties
+  /// for the given `owner` node.
+  ///
+  /// The callback may return a subset of the properties in the provided list
+  /// or replace the entire list with new property nodes.
+  ///
+  /// By default, `nodes` is returned as-is.
+  ///
+  /// See also:
+  ///
+  ///  * [includeProperties], which controls whether properties will be included
+  ///    at all.
+  List<DiagnosticsNode> filterProperties(List<DiagnosticsNode> nodes, DiagnosticsNode owner);
+
+  /// Truncates the given list of [DiagnosticsNode] that will be added to the
+  /// serialization as children or properties of the `owner` node.
+  ///
+  /// The method must return a subset of the provided nodes and may
+  /// not replace any nodes. While [filterProperties] and [filterChildren]
+  /// completely hide a node from the serialization, truncating a node will
+  /// leave a hint in the serialization that there were additional nodes in the
+  /// result that are not included in the current serialization.
+  ///
+  /// By default, `nodes` is returned as-is.
+  List<DiagnosticsNode> truncateNodesList(List<DiagnosticsNode> nodes, DiagnosticsNode? owner);
+
+  /// Returns the [DiagnosticsSerializationDelegate] to be used
+  /// for adding the provided [DiagnosticsNode] to the serialization.
+  ///
+  /// By default, this will return a copy of this delegate, which has the
+  /// [subtreeDepth] reduced by one.
+  ///
+  /// This is called for nodes that will be added to the serialization as
+  /// property or child of another node. It may return the same delegate if no
+  /// changes to it are necessary.
+  DiagnosticsSerializationDelegate delegateForNode(DiagnosticsNode node);
+
+  /// Controls how many levels of children will be included in the serialized
+  /// hierarchy of [DiagnosticsNode]s.
+  ///
+  /// Defaults to zero.
+  ///
+  /// See also:
+  ///
+  ///  * [filterChildren], which provides a way to filter the children that
+  ///    will be included.
+  int get subtreeDepth;
+
+  /// Whether to include the properties of a [DiagnosticsNode] in the
+  /// serialization.
+  ///
+  /// Defaults to false.
+  ///
+  /// See also:
+  ///
+  ///  * [filterProperties], which provides a way to filter the properties that
+  ///    will be included.
+  bool get includeProperties;
+
+  /// Whether properties that have a [Diagnosticable] as value should be
+  /// expanded.
+  bool get expandPropertyValues;
+
+  /// Creates a copy of this [DiagnosticsSerializationDelegate] with the
+  /// provided values.
+  DiagnosticsSerializationDelegate copyWith({
+    int subtreeDepth,
+    bool includeProperties,
+  });
+}
+
+class _DefaultDiagnosticsSerializationDelegate implements DiagnosticsSerializationDelegate {
+  const _DefaultDiagnosticsSerializationDelegate({
+    this.includeProperties = false,
+    this.subtreeDepth = 0,
+  });
+
+  @override
+  Map<String, Object?> additionalNodeProperties(DiagnosticsNode node) {
+    return const <String, Object?>{};
+  }
+
+  @override
+  DiagnosticsSerializationDelegate delegateForNode(DiagnosticsNode node) {
+    return subtreeDepth > 0 ? copyWith(subtreeDepth: subtreeDepth - 1) : this;
+  }
+
+  @override
+  bool get expandPropertyValues => false;
+
+  @override
+  List<DiagnosticsNode> filterChildren(List<DiagnosticsNode> nodes, DiagnosticsNode owner) {
+    return nodes;
+  }
+
+  @override
+  List<DiagnosticsNode> filterProperties(List<DiagnosticsNode> nodes, DiagnosticsNode owner) {
+    return nodes;
+  }
+
+  @override
+  final bool includeProperties;
+
+  @override
+  final int subtreeDepth;
+
+  @override
+  List<DiagnosticsNode> truncateNodesList(List<DiagnosticsNode> nodes, DiagnosticsNode? owner) {
+    return nodes;
+  }
+
+  @override
+  DiagnosticsSerializationDelegate copyWith({int? subtreeDepth, bool? includeProperties}) {
+    return _DefaultDiagnosticsSerializationDelegate(
+      subtreeDepth: subtreeDepth ?? this.subtreeDepth,
+      includeProperties: includeProperties ?? this.includeProperties,
+    );
+  }
+}
diff --git a/lib/src/foundation/isolates.dart b/lib/src/foundation/isolates.dart
new file mode 100644
index 0000000..619b517
--- /dev/null
+++ b/lib/src/foundation/isolates.dart
@@ -0,0 +1,49 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+
+import '_isolates_io.dart'
+  if (dart.library.html) '_isolates_web.dart' as _isolates;
+
+/// Signature for the callback passed to [compute].
+///
+/// {@macro flutter.foundation.compute.types}
+///
+/// Instances of [ComputeCallback] must be top-level functions or static methods
+/// of classes, not closures or instance methods of objects.
+///
+/// {@macro flutter.foundation.compute.limitations}
+typedef ComputeCallback<Q, R> = FutureOr<R> Function(Q message);
+
+// The signature of [compute].
+typedef _ComputeImpl = Future<R> Function<Q, R>(ComputeCallback<Q, R> callback, Q message, { String? debugLabel });
+
+/// Spawn an isolate, run `callback` on that isolate, passing it `message`, and
+/// (eventually) return the value returned by `callback`.
+///
+/// This is useful for operations that take longer than a few milliseconds, and
+/// which would therefore risk skipping frames. For tasks that will only take a
+/// few milliseconds, consider [SchedulerBinding.scheduleTask] instead.
+///
+/// {@template flutter.foundation.compute.types}
+/// `Q` is the type of the message that kicks off the computation.
+///
+/// `R` is the type of the value returned.
+/// {@endtemplate}
+///
+/// The `callback` argument must be a top-level function, not a closure or an
+/// instance or static method of a class.
+///
+/// {@template flutter.foundation.compute.limitations}
+/// There are limitations on the values that can be sent and received to and
+/// from isolates. These limitations constrain the values of `Q` and `R` that
+/// are possible. See the discussion at [SendPort.send].
+/// {@endtemplate}
+///
+/// The `debugLabel` argument can be specified to provide a name to add to the
+/// [Timeline]. This is useful when profiling an application.
+// Remove when https://github.com/dart-lang/sdk/issues/37149 is fixed.
+// ignore: prefer_const_declarations
+final _ComputeImpl compute = _isolates.compute;
diff --git a/lib/src/foundation/key.dart b/lib/src/foundation/key.dart
new file mode 100644
index 0000000..88ac3c0
--- /dev/null
+++ b/lib/src/foundation/key.dart
@@ -0,0 +1,96 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show hashValues;
+
+import 'package:meta/meta.dart';
+
+/// A [Key] is an identifier for [Widget]s, [Element]s and [SemanticsNode]s.
+///
+/// A new widget will only be used to update an existing element if its key is
+/// the same as the key of the current widget associated with the element.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=kn0EOS-ZiIc}
+///
+/// Keys must be unique amongst the [Element]s with the same parent.
+///
+/// Subclasses of [Key] should either subclass [LocalKey] or [GlobalKey].
+///
+/// See also:
+///
+///  * [Widget.key], which discusses how widgets use keys.
+@immutable
+abstract class Key {
+  /// Construct a [ValueKey<String>] with the given [String].
+  ///
+  /// This is the simplest way to create keys.
+  const factory Key(String value) = ValueKey<String>;
+
+  /// Default constructor, used by subclasses.
+  ///
+  /// Useful so that subclasses can call us, because the [new Key] factory
+  /// constructor shadows the implicit constructor.
+  @protected
+  const Key.empty();
+}
+
+/// A key that is not a [GlobalKey].
+///
+/// Keys must be unique amongst the [Element]s with the same parent. By
+/// contrast, [GlobalKey]s must be unique across the entire app.
+///
+/// See also:
+///
+///  * [Widget.key], which discusses how widgets use keys.
+abstract class LocalKey extends Key {
+  /// Default constructor, used by subclasses.
+  const LocalKey() : super.empty();
+}
+
+/// A key that uses a value of a particular type to identify itself.
+///
+/// A [ValueKey<T>] is equal to another [ValueKey<T>] if, and only if, their
+/// values are [operator==].
+///
+/// This class can be subclassed to create value keys that will not be equal to
+/// other value keys that happen to use the same value. If the subclass is
+/// private, this results in a value key type that cannot collide with keys from
+/// other sources, which could be useful, for example, if the keys are being
+/// used as fallbacks in the same scope as keys supplied from another widget.
+///
+/// See also:
+///
+///  * [Widget.key], which discusses how widgets use keys.
+class ValueKey<T> extends LocalKey {
+  /// Creates a key that delegates its [operator==] to the given value.
+  const ValueKey(this.value);
+
+  /// The value to which this key delegates its [operator==]
+  final T value;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ValueKey<T>
+        && other.value == value;
+  }
+
+  @override
+  int get hashCode => hashValues(runtimeType, value);
+
+  @override
+  String toString() {
+    final String valueString = T == String ? "<'$value'>" : '<$value>';
+    // The crazy on the next line is a workaround for
+    // https://github.com/dart-lang/sdk/issues/33297
+    if (runtimeType == _TypeLiteral<ValueKey<T>>().type)
+      return '[$valueString]';
+    return '[$T $valueString]';
+  }
+}
+
+class _TypeLiteral<T> {
+  Type get type => T;
+}
diff --git a/lib/src/foundation/licenses.dart b/lib/src/foundation/licenses.dart
new file mode 100644
index 0000000..0dc7938
--- /dev/null
+++ b/lib/src/foundation/licenses.dart
@@ -0,0 +1,324 @@
+// Copyright 2014 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.
+
+import 'package:meta/meta.dart' show visibleForTesting;
+
+/// Signature for callbacks passed to [LicenseRegistry.addLicense].
+typedef LicenseEntryCollector = Stream<LicenseEntry> Function();
+
+/// A string that represents one paragraph in a [LicenseEntry].
+///
+/// See [LicenseEntry.paragraphs].
+class LicenseParagraph {
+  /// Creates a string for a license entry paragraph.
+  const LicenseParagraph(this.text, this.indent);
+
+  /// The text of the paragraph. Should not have any leading or trailing whitespace.
+  final String text;
+
+  /// How many steps of indentation the paragraph has.
+  ///
+  /// * 0 means the paragraph is not indented.
+  /// * 1 means the paragraph is indented one unit of indentation.
+  /// * 2 means the paragraph is indented two units of indentation.
+  ///
+  /// ...and so forth.
+  ///
+  /// In addition, the special value [centeredIndent] can be used to indicate
+  /// that rather than being indented, the paragraph is centered.
+  final int indent; // can be set to centeredIndent
+
+  /// A constant that represents "centered" alignment for [indent].
+  static const int centeredIndent = -1;
+}
+
+/// A license that covers part of the application's software or assets, to show
+/// in an interface such as the [LicensePage].
+///
+/// For optimal performance, [LicenseEntry] objects should only be created on
+/// demand in [LicenseEntryCollector] callbacks passed to
+/// [LicenseRegistry.addLicense].
+abstract class LicenseEntry {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const LicenseEntry();
+
+  /// The names of the packages that this license entry applies to.
+  Iterable<String> get packages;
+
+  /// The paragraphs of the license, each as a [LicenseParagraph] consisting of
+  /// a string and some formatting information. Paragraphs can include newline
+  /// characters, but this is discouraged as it results in ugliness.
+  Iterable<LicenseParagraph> get paragraphs;
+}
+
+enum _LicenseEntryWithLineBreaksParserState {
+  beforeParagraph,
+  inParagraph,
+}
+
+/// Variant of [LicenseEntry] for licenses that separate paragraphs with blank
+/// lines and that hard-wrap text within paragraphs. Lines that begin with one
+/// or more space characters are also assumed to introduce new paragraphs,
+/// unless they start with the same number of spaces as the previous line, in
+/// which case it's assumed they are a continuation of an indented paragraph.
+///
+/// {@tool snippet}
+///
+/// For example, the BSD license in this format could be encoded as follows:
+///
+/// ```dart
+/// void initMyLibrary() {
+///   LicenseRegistry.addLicense(() async* {
+///     yield LicenseEntryWithLineBreaks(<String>['my_library'], '''
+/// Copyright 2016 The Sample Authors. All rights reserved.
+///
+/// Redistribution and use in source and binary forms, with or without
+/// modification, are permitted provided that the following conditions are
+/// met:
+///
+///    * Redistributions of source code must retain the above copyright
+/// notice, this list of conditions and the following disclaimer.
+///    * Redistributions in binary form must reproduce the above
+/// copyright notice, this list of conditions and the following disclaimer
+/// in the documentation and/or other materials provided with the
+/// distribution.
+///    * Neither the name of Example Inc. nor the names of its
+/// contributors may be used to endorse or promote products derived from
+/// this software without specific prior written permission.
+///
+/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+/// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+/// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+/// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+/// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+/// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+/// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+/// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+/// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+/// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+/// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''');
+///   });
+/// }
+/// ```
+/// {@end-tool}
+///
+/// This would result in a license with six [paragraphs], the third, fourth, and
+/// fifth being indented one level.
+///
+/// ## Performance considerations
+///
+/// Computing the paragraphs is relatively expensive. Doing the work for one
+/// license per frame is reasonable; doing more at the same time is ill-advised.
+/// Consider doing all the work at once using [compute] to move the work to
+/// another thread, or spreading the work across multiple frames using
+/// [SchedulerBinding.scheduleTask].
+class LicenseEntryWithLineBreaks extends LicenseEntry {
+  /// Create a license entry for a license whose text is hard-wrapped within
+  /// paragraphs and has paragraph breaks denoted by blank lines or with
+  /// indented text.
+  const LicenseEntryWithLineBreaks(this.packages, this.text);
+
+  @override
+  final List<String> packages;
+
+  /// The text of the license.
+  ///
+  /// The text will be split into paragraphs according to the following
+  /// conventions:
+  ///
+  /// * Lines starting with a different number of space characters than the
+  ///   previous line start a new paragraph, with those spaces removed.
+  /// * Blank lines start a new paragraph.
+  /// * Other line breaks are replaced by a single space character.
+  /// * Leading spaces on a line are removed.
+  ///
+  /// For each paragraph, the algorithm attempts (using some rough heuristics)
+  /// to identify how indented the paragraph is, or whether it is centered.
+  final String text;
+
+  @override
+  Iterable<LicenseParagraph> get paragraphs sync* {
+    int lineStart = 0;
+    int currentPosition = 0;
+    int lastLineIndent = 0;
+    int currentLineIndent = 0;
+    int? currentParagraphIndentation;
+    _LicenseEntryWithLineBreaksParserState state = _LicenseEntryWithLineBreaksParserState.beforeParagraph;
+    final List<String> lines = <String>[];
+
+    void addLine() {
+      assert(lineStart < currentPosition);
+      lines.add(text.substring(lineStart, currentPosition));
+    }
+
+    LicenseParagraph getParagraph() {
+      assert(lines.isNotEmpty);
+      assert(currentParagraphIndentation != null);
+      final LicenseParagraph result = LicenseParagraph(lines.join(' '), currentParagraphIndentation!);
+      assert(result.text.trimLeft() == result.text);
+      assert(result.text.isNotEmpty);
+      lines.clear();
+      return result;
+    }
+
+    while (currentPosition < text.length) {
+      switch (state) {
+        case _LicenseEntryWithLineBreaksParserState.beforeParagraph:
+          assert(lineStart == currentPosition);
+          switch (text[currentPosition]) {
+            case ' ':
+              lineStart = currentPosition + 1;
+              currentLineIndent += 1;
+              state = _LicenseEntryWithLineBreaksParserState.beforeParagraph;
+              break;
+            case '\t':
+              lineStart = currentPosition + 1;
+              currentLineIndent += 8;
+              state = _LicenseEntryWithLineBreaksParserState.beforeParagraph;
+              break;
+            case '\r':
+            case '\n':
+            case '\f':
+              if (lines.isNotEmpty) {
+                yield getParagraph();
+              }
+              if (text[currentPosition] == '\r' && currentPosition < text.length - 1
+                  && text[currentPosition + 1] == '\n') {
+                currentPosition += 1;
+              }
+              lastLineIndent = 0;
+              currentLineIndent = 0;
+              currentParagraphIndentation = null;
+              lineStart = currentPosition + 1;
+              state = _LicenseEntryWithLineBreaksParserState.beforeParagraph;
+              break;
+            case '[':
+              // This is a bit of a hack for the LGPL 2.1, which does something like this:
+              //
+              //   [this is a
+              //    single paragraph]
+              //
+              // ...near the top.
+              currentLineIndent += 1;
+              continue startParagraph;
+            startParagraph:
+            default:
+              if (lines.isNotEmpty && currentLineIndent > lastLineIndent) {
+                yield getParagraph();
+                currentParagraphIndentation = null;
+              }
+              // The following is a wild heuristic for guessing the indentation level.
+              // It happens to work for common variants of the BSD and LGPL licenses.
+              if (currentParagraphIndentation == null) {
+                if (currentLineIndent > 10)
+                  currentParagraphIndentation = LicenseParagraph.centeredIndent;
+                else
+                  currentParagraphIndentation = currentLineIndent ~/ 3;
+              }
+              state = _LicenseEntryWithLineBreaksParserState.inParagraph;
+          }
+          break;
+        case _LicenseEntryWithLineBreaksParserState.inParagraph:
+          switch (text[currentPosition]) {
+            case '\n':
+              addLine();
+              lastLineIndent = currentLineIndent;
+              currentLineIndent = 0;
+              lineStart = currentPosition + 1;
+              state = _LicenseEntryWithLineBreaksParserState.beforeParagraph;
+              break;
+            case '\f':
+              addLine();
+              yield getParagraph();
+              lastLineIndent = 0;
+              currentLineIndent = 0;
+              currentParagraphIndentation = null;
+              lineStart = currentPosition + 1;
+              state = _LicenseEntryWithLineBreaksParserState.beforeParagraph;
+              break;
+            default:
+              state = _LicenseEntryWithLineBreaksParserState.inParagraph;
+          }
+          break;
+      }
+      currentPosition += 1;
+    }
+    switch (state) {
+      case _LicenseEntryWithLineBreaksParserState.beforeParagraph:
+        if (lines.isNotEmpty) {
+          yield getParagraph();
+        }
+        break;
+      case _LicenseEntryWithLineBreaksParserState.inParagraph:
+        addLine();
+        yield getParagraph();
+        break;
+    }
+  }
+}
+
+
+/// A registry for packages to add licenses to, so that they can be displayed
+/// together in an interface such as the [LicensePage].
+///
+/// Packages can register their licenses using [addLicense]. User interfaces
+/// that wish to show all the licenses can obtain them by calling [licenses].
+///
+/// The flutter tool will automatically collect the contents of all the LICENSE
+/// files found at the root of each package into a single LICENSE file in the
+/// default asset bundle. Each license in that file is separated from the next
+/// by a line of eighty hyphens (`-`), and begins with a list of package names
+/// that the license applies to, one to a line, separated from the next by a
+/// blank line. The `services` package registers a license collector that splits
+/// that file and adds each entry to the registry.
+///
+/// The LICENSE files in each package can either consist of a single license, or
+/// can be in the format described above. In the latter case, each component
+/// license and list of package names is merged independently.
+///
+/// See also:
+///
+///  * [showAboutDialog], which shows a Material-style dialog with information
+///    about the application, including a button that shows a [LicensePage] that
+///    uses this API to select licenses to show.
+///  * [AboutListTile], which is a widget that can be added to a [Drawer]. When
+///    tapped it calls [showAboutDialog].
+class LicenseRegistry {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  LicenseRegistry._();
+
+  static List<LicenseEntryCollector>? _collectors;
+
+  /// Adds licenses to the registry.
+  ///
+  /// To avoid actually manipulating the licenses unless strictly necessary,
+  /// licenses are added by adding a closure that returns a list of
+  /// [LicenseEntry] objects. The closure is only called if [licenses] is itself
+  /// called; in normal operation, if the user does not request to see the
+  /// licenses, the closure will not be called.
+  static void addLicense(LicenseEntryCollector collector) {
+    _collectors ??= <LicenseEntryCollector>[];
+    _collectors!.add(collector);
+  }
+
+  /// Returns the licenses that have been registered.
+  ///
+  /// Generating the list of licenses is expensive.
+  static Stream<LicenseEntry> get licenses async* {
+    if (_collectors == null)
+      return;
+    for (final LicenseEntryCollector collector in _collectors!)
+      yield* collector();
+  }
+
+  /// Resets the internal state of [LicenseRegistry]. Intended for use in
+  /// testing.
+  @visibleForTesting
+  static void reset() {
+    _collectors = null;
+  }
+}
diff --git a/lib/src/foundation/node.dart b/lib/src/foundation/node.dart
new file mode 100644
index 0000000..f1cdce0
--- /dev/null
+++ b/lib/src/foundation/node.dart
@@ -0,0 +1,149 @@
+// Copyright 2014 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.
+
+import 'package:meta/meta.dart';
+
+// This file gets mutated by //dev/devicelab/bin/tasks/flutter_test_performance.dart
+// during device lab performance tests. When editing this file, check to make sure
+// that it didn't break that test.
+
+/// An abstract node in a tree.
+///
+/// AbstractNode has as notion of depth, attachment, and parent, but does not
+/// have a model for children.
+///
+/// When a subclass is changing the parent of a child, it should call either
+/// `parent.adoptChild(child)` or `parent.dropChild(child)` as appropriate.
+/// Subclasses can expose an API for manipulating the tree if desired (e.g. a
+/// setter for a `child` property, or an `add()` method to manipulate a list).
+///
+/// The current parent node is exposed by the [parent] property.
+///
+/// The current attachment state is exposed by [attached]. The root of any tree
+/// that is to be considered attached should be manually attached by calling
+/// [attach]. Other than that, the [attach] and [detach] methods should not be
+/// called directly; attachment is managed automatically by the aforementioned
+/// [adoptChild] and [dropChild] methods.
+///
+/// Subclasses that have children must override [attach] and [detach] as
+/// described in the documentation for those methods.
+///
+/// Nodes always have a [depth] greater than their ancestors'. There's no
+/// guarantee regarding depth between siblings. The depth of a node is used to
+/// ensure that nodes are processed in depth order. The [depth] of a child can
+/// be more than one greater than the [depth] of the parent, because the [depth]
+/// values are never decreased: all that matters is that it's greater than the
+/// parent. Consider a tree with a root node A, a child B, and a grandchild C.
+/// Initially, A will have [depth] 0, B [depth] 1, and C [depth] 2. If C is
+/// moved to be a child of A, sibling of B, then the numbers won't change. C's
+/// [depth] will still be 2. The [depth] is automatically maintained by the
+/// [adoptChild] and [dropChild] methods.
+class AbstractNode {
+  /// The depth of this node in the tree.
+  ///
+  /// The depth of nodes in a tree monotonically increases as you traverse down
+  /// the tree.
+  int get depth => _depth;
+  int _depth = 0;
+
+  /// Adjust the [depth] of the given [child] to be greater than this node's own
+  /// [depth].
+  ///
+  /// Only call this method from overrides of [redepthChildren].
+  @protected
+  void redepthChild(AbstractNode child) {
+    assert(child.owner == owner);
+    if (child._depth <= _depth) {
+      child._depth = _depth + 1;
+      child.redepthChildren();
+    }
+  }
+
+  /// Adjust the [depth] of this node's children, if any.
+  ///
+  /// Override this method in subclasses with child nodes to call [redepthChild]
+  /// for each child. Do not call this method directly.
+  void redepthChildren() { }
+
+  /// The owner for this node (null if unattached).
+  ///
+  /// The entire subtree that this node belongs to will have the same owner.
+  Object? get owner => _owner;
+  Object? _owner;
+
+  /// Whether this node is in a tree whose root is attached to something.
+  ///
+  /// This becomes true during the call to [attach].
+  ///
+  /// This becomes false during the call to [detach].
+  bool get attached => _owner != null;
+
+  /// Mark this node as attached to the given owner.
+  ///
+  /// Typically called only from the [parent]'s [attach] method, and by the
+  /// [owner] to mark the root of a tree as attached.
+  ///
+  /// Subclasses with children should override this method to first call their
+  /// inherited [attach] method, and then [attach] all their children to the
+  /// same [owner].
+  @mustCallSuper
+  void attach(covariant Object owner) {
+    assert(owner != null);
+    assert(_owner == null);
+    _owner = owner;
+  }
+
+  /// Mark this node as detached.
+  ///
+  /// Typically called only from the [parent]'s [detach], and by the [owner] to
+  /// mark the root of a tree as detached.
+  ///
+  /// Subclasses with children should override this method to first call their
+  /// inherited [detach] method, and then [detach] all their children.
+  @mustCallSuper
+  void detach() {
+    assert(_owner != null);
+    _owner = null;
+    assert(parent == null || attached == parent!.attached);
+  }
+
+  /// The parent of this node in the tree.
+  AbstractNode? get parent => _parent;
+  AbstractNode? _parent;
+
+  /// Mark the given node as being a child of this node.
+  ///
+  /// Subclasses should call this function when they acquire a new child.
+  @protected
+  @mustCallSuper
+  void adoptChild(covariant AbstractNode child) {
+    assert(child != null);
+    assert(child._parent == null);
+    assert(() {
+      AbstractNode node = this;
+      while (node.parent != null)
+        node = node.parent!;
+      assert(node != child); // indicates we are about to create a cycle
+      return true;
+    }());
+    child._parent = this;
+    if (attached)
+      child.attach(_owner!);
+    redepthChild(child);
+  }
+
+  /// Disconnect the given node from this node.
+  ///
+  /// Subclasses should call this function when they lose a child.
+  @protected
+  @mustCallSuper
+  void dropChild(covariant AbstractNode child) {
+    assert(child != null);
+    assert(child._parent == this);
+    assert(child.attached == attached);
+    child._parent = null;
+    if (attached)
+      child.detach();
+  }
+}
diff --git a/lib/src/foundation/object.dart b/lib/src/foundation/object.dart
new file mode 100644
index 0000000..a95d800
--- /dev/null
+++ b/lib/src/foundation/object.dart
@@ -0,0 +1,18 @@
+// Copyright 2014 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.
+
+/// Framework code should use this method in favor of calling `toString` on
+/// [Object.runtimeType].
+///
+/// Calling `toString` on a runtime type is a non-trivial operation that can
+/// negatively impact performance. If asserts are enabled, this method will
+/// return `object.runtimeType.toString()`; otherwise, it will return the
+/// [optimizedValue], which must be a simple constant string.
+String objectRuntimeType(Object? object, String optimizedValue) {
+  assert(() {
+    optimizedValue = object.runtimeType.toString();
+    return true;
+  }());
+  return optimizedValue;
+}
diff --git a/lib/src/foundation/observer_list.dart b/lib/src/foundation/observer_list.dart
new file mode 100644
index 0000000..46aadd2
--- /dev/null
+++ b/lib/src/foundation/observer_list.dart
@@ -0,0 +1,127 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+
+/// A list optimized for the observer pattern when there are small numbers of
+/// observers.
+///
+/// Consider using an [ObserverList] instead of a [List] when the number of
+/// [contains] calls dominates the number of [add] and [remove] calls.
+///
+/// This class will include in the [iterator] each added item in the order it
+/// was added, as many times as it was added.
+///
+/// If there will be a large number of observers, consider using
+/// [HashedObserverList] instead. It has slightly different iteration semantics,
+/// but serves a similar purpose, while being more efficient for large numbers
+/// of observers.
+///
+/// See also:
+///
+///  * [HashedObserverList] for a list that is optimized for larger numbers of
+///    observers.
+// TODO(ianh): Use DelegatingIterable, possibly moving it from the collection
+// package to foundation, or to dart:collection.
+class ObserverList<T> extends Iterable<T> {
+  final List<T> _list = <T>[];
+  bool _isDirty = false;
+  late final HashSet<T> _set = HashSet<T>();
+
+  /// Adds an item to the end of this list.
+  ///
+  /// This operation has constant time complexity.
+  void add(T item) {
+    _isDirty = true;
+    _list.add(item);
+  }
+
+  /// Removes an item from the list.
+  ///
+  /// This is O(N) in the number of items in the list.
+  ///
+  /// Returns whether the item was present in the list.
+  bool remove(T item) {
+    _isDirty = true;
+    _set.clear(); // Clear the set so that we don't leak items.
+    return _list.remove(item);
+  }
+
+  @override
+  bool contains(Object? element) {
+    if (_list.length < 3)
+      return _list.contains(element);
+
+    if (_isDirty) {
+      _set.addAll(_list);
+      _isDirty = false;
+    }
+
+    return _set.contains(element);
+  }
+
+  @override
+  Iterator<T> get iterator => _list.iterator;
+
+  @override
+  bool get isEmpty => _list.isEmpty;
+
+  @override
+  bool get isNotEmpty => _list.isNotEmpty;
+}
+
+/// A list optimized for the observer pattern, but for larger numbers of observers.
+///
+/// For small numbers of observers (e.g. less than 10), use [ObserverList] instead.
+///
+/// The iteration semantics of the this class are slightly different from
+/// [ObserverList]. This class will only return an item once in the [iterator],
+/// no matter how many times it was added, although it does require that an item
+/// be removed as many times as it was added for it to stop appearing in the
+/// [iterator]. It will return them in the order the first instance of an item
+/// was originally added.
+///
+/// See also:
+///
+///  * [ObserverList] for a list that is fast for small numbers of observers.
+class HashedObserverList<T> extends Iterable<T> {
+  final LinkedHashMap<T, int> _map = LinkedHashMap<T, int>();
+
+  /// Adds an item to the end of this list.
+  ///
+  /// This has constant time complexity.
+  void add(T item) {
+    _map[item] = (_map[item] ?? 0) + 1;
+  }
+
+  /// Removes an item from the list.
+  ///
+  /// This operation has constant time complexity.
+  ///
+  /// Returns whether the item was present in the list.
+  bool remove(T item) {
+    final int? value = _map[item];
+    if (value == null) {
+      return false;
+    }
+    if (value == 1) {
+      _map.remove(item);
+    } else {
+      _map[item] = value - 1;
+    }
+    return true;
+  }
+
+  @override
+  bool contains(Object? element) => _map.containsKey(element);
+
+  @override
+  Iterator<T> get iterator => _map.keys.iterator;
+
+  @override
+  bool get isEmpty => _map.isEmpty;
+
+  @override
+  bool get isNotEmpty => _map.isNotEmpty;
+}
diff --git a/lib/src/foundation/platform.dart b/lib/src/foundation/platform.dart
new file mode 100644
index 0000000..d98e0c8
--- /dev/null
+++ b/lib/src/foundation/platform.dart
@@ -0,0 +1,83 @@
+// Copyright 2014 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.
+
+import '_platform_io.dart'
+  if (dart.library.html) '_platform_web.dart' as _platform;
+
+/// The [TargetPlatform] that matches the platform on which the framework is
+/// currently executing.
+///
+/// This is the default value of [ThemeData.platform] (hence the name). Widgets
+/// from the material library should use [Theme.of] to determine the current
+/// platform for styling purposes, rather than using [defaultTargetPlatform].
+/// Widgets and render objects at lower layers that try to emulate the
+/// underlying platform can depend on [defaultTargetPlatform] directly. The
+/// [dart:io.Platform] object should only be used directly when it's critical to
+/// actually know the current platform, without any overrides possible (for
+/// example, when a system API is about to be called).
+///
+/// In a test environment, the platform returned is [TargetPlatform.android]
+/// regardless of the host platform. (Android was chosen because the tests were
+/// originally written assuming Android-like behavior, and we added platform
+/// adaptations for iOS later). Tests can check iOS behavior by using the
+/// platform override APIs (such as [ThemeData.platform] in the material
+/// library) or by setting [debugDefaultTargetPlatformOverride].
+///
+/// Tests can also create specific platform tests by and adding a `variant:`
+/// argument to the test and using a [TargetPlatformVariant].
+//
+// When adding support for a new platform (e.g. Windows Phone, Rasberry Pi),
+// first create a new value on the [TargetPlatform] enum, then add a rule for
+// selecting that platform here.
+//
+// It would be incorrect to make a platform that isn't supported by
+// [TargetPlatform] default to the behavior of another platform, because doing
+// that would mean we'd be stuck with that platform forever emulating the other,
+// and we'd never be able to introduce dedicated behavior for that platform
+// (since doing so would be a big breaking change).
+TargetPlatform get defaultTargetPlatform => _platform.defaultTargetPlatform;
+
+/// The platform that user interaction should adapt to target.
+///
+/// The [defaultTargetPlatform] getter returns the current platform.
+enum TargetPlatform {
+  /// Android: <https://www.android.com/>
+  android,
+
+  /// Fuchsia: <https://fuchsia.dev/fuchsia-src/concepts>
+  fuchsia,
+
+  /// iOS: <https://www.apple.com/ios/>
+  iOS,
+
+  /// Linux: <https://www.linux.org>
+  linux,
+
+  /// macOS: <https://www.apple.com/macos>
+  macOS,
+
+  /// Windows: <https://www.windows.com>
+  windows,
+}
+
+/// Override the [defaultTargetPlatform].
+///
+/// Setting this to null returns the [defaultTargetPlatform] to its original
+/// value (based on the actual current platform).
+///
+/// Generally speaking this override is only useful for tests. To change the
+/// platform that widgets resemble, consider using the platform override APIs
+/// (such as [ThemeData.platform] in the material library) instead.
+///
+/// Setting [debugDefaultTargetPlatformOverride] (as opposed to, say,
+/// [ThemeData.platform]) will cause unexpected and undesirable effects. For
+/// example, setting this to [TargetPlatform.iOS] when the application is
+/// running on Android will cause the TalkBack accessibility tool on Android to
+/// be confused because it would be receiving data intended for iOS VoiceOver.
+/// Similarly, setting it to [TargetPlatform.android] while on iOS will cause
+/// certainly widgets to work assuming the presence of a system-wide back
+/// button, which will make those widgets unusable since iOS has no such button.
+///
+/// In general, therefore, this property should not be used in release builds.
+TargetPlatform? debugDefaultTargetPlatformOverride;
diff --git a/lib/src/foundation/print.dart b/lib/src/foundation/print.dart
new file mode 100644
index 0000000..1e869eb
--- /dev/null
+++ b/lib/src/foundation/print.dart
@@ -0,0 +1,177 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:collection';
+
+/// Signature for [debugPrint] implementations.
+typedef DebugPrintCallback = void Function(String? message, { int? wrapWidth });
+
+/// Prints a message to the console, which you can access using the "flutter"
+/// tool's "logs" command ("flutter logs").
+///
+/// If a wrapWidth is provided, each line of the message is word-wrapped to that
+/// width. (Lines may be separated by newline characters, as in '\n'.)
+///
+/// By default, this function very crudely attempts to throttle the rate at
+/// which messages are sent to avoid data loss on Android. This means that
+/// interleaving calls to this function (directly or indirectly via, e.g.,
+/// [debugDumpRenderTree] or [debugDumpApp]) and to the Dart [print] method can
+/// result in out-of-order messages in the logs.
+///
+/// The implementation of this function can be replaced by setting the
+/// [debugPrint] variable to a new implementation that matches the
+/// [DebugPrintCallback] signature. For example, flutter_test does this.
+///
+/// The default value is [debugPrintThrottled]. For a version that acts
+/// identically but does not throttle, use [debugPrintSynchronously].
+DebugPrintCallback debugPrint = debugPrintThrottled;
+
+/// Alternative implementation of [debugPrint] that does not throttle.
+/// Used by tests.
+void debugPrintSynchronously(String? message, { int? wrapWidth }) {
+  if (message != null && wrapWidth != null) {
+    print(message.split('\n').expand<String>((String line) => debugWordWrap(line, wrapWidth)).join('\n'));
+  } else {
+    print(message);
+  }
+}
+
+/// Implementation of [debugPrint] that throttles messages. This avoids dropping
+/// messages on platforms that rate-limit their logging (for example, Android).
+void debugPrintThrottled(String? message, { int? wrapWidth }) {
+  final List<String> messageLines = message?.split('\n') ?? <String>['null'];
+  if (wrapWidth != null) {
+    _debugPrintBuffer.addAll(messageLines.expand<String>((String line) => debugWordWrap(line, wrapWidth)));
+  } else {
+    _debugPrintBuffer.addAll(messageLines);
+  }
+  if (!_debugPrintScheduled)
+    _debugPrintTask();
+}
+int _debugPrintedCharacters = 0;
+const int _kDebugPrintCapacity = 12 * 1024;
+const Duration _kDebugPrintPauseTime = Duration(seconds: 1);
+final Queue<String> _debugPrintBuffer = Queue<String>();
+final Stopwatch _debugPrintStopwatch = Stopwatch();
+Completer<void>? _debugPrintCompleter;
+bool _debugPrintScheduled = false;
+void _debugPrintTask() {
+  _debugPrintScheduled = false;
+  if (_debugPrintStopwatch.elapsed > _kDebugPrintPauseTime) {
+    _debugPrintStopwatch.stop();
+    _debugPrintStopwatch.reset();
+    _debugPrintedCharacters = 0;
+  }
+  while (_debugPrintedCharacters < _kDebugPrintCapacity && _debugPrintBuffer.isNotEmpty) {
+    final String line = _debugPrintBuffer.removeFirst();
+    _debugPrintedCharacters += line.length; // TODO(ianh): Use the UTF-8 byte length instead
+    print(line);
+  }
+  if (_debugPrintBuffer.isNotEmpty) {
+    _debugPrintScheduled = true;
+    _debugPrintedCharacters = 0;
+    Timer(_kDebugPrintPauseTime, _debugPrintTask);
+    _debugPrintCompleter ??= Completer<void>();
+  } else {
+    _debugPrintStopwatch.start();
+    _debugPrintCompleter?.complete();
+    _debugPrintCompleter = null;
+  }
+}
+
+/// A Future that resolves when there is no longer any buffered content being
+/// printed by [debugPrintThrottled] (which is the default implementation for
+/// [debugPrint], which is used to report errors to the console).
+Future<void> get debugPrintDone => _debugPrintCompleter?.future ?? Future<void>.value();
+
+final RegExp _indentPattern = RegExp('^ *(?:[-+*] |[0-9]+[.):] )?');
+enum _WordWrapParseMode { inSpace, inWord, atBreak }
+
+/// Wraps the given string at the given width.
+///
+/// Wrapping occurs at space characters (U+0020). Lines that start with an
+/// octothorpe ("#", U+0023) are not wrapped (so for example, Dart stack traces
+/// won't be wrapped).
+///
+/// Subsequent lines attempt to duplicate the indentation of the first line, for
+/// example if the first line starts with multiple spaces. In addition, if a
+/// `wrapIndent` argument is provided, each line after the first is prefixed by
+/// that string.
+///
+/// This is not suitable for use with arbitrary Unicode text. For example, it
+/// doesn't implement UAX #14, can't handle ideographic text, doesn't hyphenate,
+/// and so forth. It is only intended for formatting error messages.
+///
+/// The default [debugPrint] implementation uses this for its line wrapping.
+Iterable<String> debugWordWrap(String message, int width, { String wrapIndent = '' }) sync* {
+  if (message.length < width || message.trimLeft()[0] == '#') {
+    yield message;
+    return;
+  }
+  final Match prefixMatch = _indentPattern.matchAsPrefix(message)!;
+  final String prefix = wrapIndent + ' ' * prefixMatch.group(0)!.length;
+  int start = 0;
+  int startForLengthCalculations = 0;
+  bool addPrefix = false;
+  int index = prefix.length;
+  _WordWrapParseMode mode = _WordWrapParseMode.inSpace;
+  late int lastWordStart;
+  int? lastWordEnd;
+  while (true) {
+    switch (mode) {
+      case _WordWrapParseMode.inSpace: // at start of break point (or start of line); can't break until next break
+        while ((index < message.length) && (message[index] == ' '))
+          index += 1;
+        lastWordStart = index;
+        mode = _WordWrapParseMode.inWord;
+        break;
+      case _WordWrapParseMode.inWord: // looking for a good break point
+        while ((index < message.length) && (message[index] != ' '))
+          index += 1;
+        mode = _WordWrapParseMode.atBreak;
+        break;
+      case _WordWrapParseMode.atBreak: // at start of break point
+        if ((index - startForLengthCalculations > width) || (index == message.length)) {
+          // we are over the width line, so break
+          if ((index - startForLengthCalculations <= width) || (lastWordEnd == null)) {
+            // we should use this point, because either it doesn't actually go over the
+            // end (last line), or it does, but there was no earlier break point
+            lastWordEnd = index;
+          }
+          if (addPrefix) {
+            yield prefix + message.substring(start, lastWordEnd);
+          } else {
+            yield message.substring(start, lastWordEnd);
+            addPrefix = true;
+          }
+          if (lastWordEnd >= message.length)
+            return;
+          // just yielded a line
+          if (lastWordEnd == index) {
+            // we broke at current position
+            // eat all the spaces, then set our start point
+            while ((index < message.length) && (message[index] == ' '))
+              index += 1;
+            start = index;
+            mode = _WordWrapParseMode.inWord;
+          } else {
+            // we broke at the previous break point, and we're at the start of a new one
+            assert(lastWordStart > lastWordEnd);
+            start = lastWordStart;
+            mode = _WordWrapParseMode.atBreak;
+          }
+          startForLengthCalculations = start - prefix.length;
+          assert(addPrefix);
+          lastWordEnd = null;
+        } else {
+          // save this break point, we're not yet over the line width
+          lastWordEnd = index;
+          // skip to the end of this break point
+          mode = _WordWrapParseMode.inSpace;
+        }
+        break;
+    }
+  }
+}
diff --git a/lib/src/foundation/serialization.dart b/lib/src/foundation/serialization.dart
new file mode 100644
index 0000000..1873582
--- /dev/null
+++ b/lib/src/foundation/serialization.dart
@@ -0,0 +1,196 @@
+// Copyright 2014 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.
+
+import 'dart:typed_data';
+
+import 'package:typed_data/typed_buffers.dart' show Uint8Buffer;
+
+/// Write-only buffer for incrementally building a [ByteData] instance.
+///
+/// A WriteBuffer instance can be used only once. Attempts to reuse will result
+/// in [NoSuchMethodError]s being thrown.
+///
+/// The byte order used is [Endian.host] throughout.
+class WriteBuffer {
+  /// Creates an interface for incrementally building a [ByteData] instance.
+  WriteBuffer()
+    : _buffer = Uint8Buffer(),
+      _eightBytes = ByteData(8) {
+    _eightBytesAsList = _eightBytes.buffer.asUint8List();
+  }
+
+  Uint8Buffer? _buffer;
+  final ByteData _eightBytes;
+  late Uint8List _eightBytesAsList;
+
+  /// Write a Uint8 into the buffer.
+  void putUint8(int byte) {
+    _buffer!.add(byte);
+  }
+
+  /// Write a Uint16 into the buffer.
+  void putUint16(int value, {Endian? endian}) {
+    _eightBytes.setUint16(0, value, endian ?? Endian.host);
+    _buffer!.addAll(_eightBytesAsList, 0, 2);
+  }
+
+  /// Write a Uint32 into the buffer.
+  void putUint32(int value, {Endian? endian}) {
+    _eightBytes.setUint32(0, value, endian ?? Endian.host);
+    _buffer!.addAll(_eightBytesAsList, 0, 4);
+  }
+
+  /// Write an Int32 into the buffer.
+  void putInt32(int value, {Endian? endian}) {
+    _eightBytes.setInt32(0, value, endian ?? Endian.host);
+    _buffer!.addAll(_eightBytesAsList, 0, 4);
+  }
+
+  /// Write an Int64 into the buffer.
+  void putInt64(int value, {Endian? endian}) {
+    _eightBytes.setInt64(0, value, endian ?? Endian.host);
+    _buffer!.addAll(_eightBytesAsList, 0, 8);
+  }
+
+  /// Write an Float64 into the buffer.
+  void putFloat64(double value, {Endian? endian}) {
+    _alignTo(8);
+    _eightBytes.setFloat64(0, value, endian ?? Endian.host);
+    _buffer!.addAll(_eightBytesAsList);
+  }
+
+  /// Write all the values from a [Uint8List] into the buffer.
+  void putUint8List(Uint8List list) {
+    _buffer!.addAll(list);
+  }
+
+  /// Write all the values from an [Int32List] into the buffer.
+  void putInt32List(Int32List list) {
+    _alignTo(4);
+    _buffer!.addAll(list.buffer.asUint8List(list.offsetInBytes, 4 * list.length));
+  }
+
+  /// Write all the values from an [Int64List] into the buffer.
+  void putInt64List(Int64List list) {
+    _alignTo(8);
+    _buffer!.addAll(list.buffer.asUint8List(list.offsetInBytes, 8 * list.length));
+  }
+
+  /// Write all the values from a [Float64List] into the buffer.
+  void putFloat64List(Float64List list) {
+    _alignTo(8);
+    _buffer!.addAll(list.buffer.asUint8List(list.offsetInBytes, 8 * list.length));
+  }
+
+  void _alignTo(int alignment) {
+    final int mod = _buffer!.length % alignment;
+    if (mod != 0) {
+      for (int i = 0; i < alignment - mod; i++)
+        _buffer!.add(0);
+    }
+  }
+
+  /// Finalize and return the written [ByteData].
+  ByteData done() {
+    final ByteData result = _buffer!.buffer.asByteData(0, _buffer!.lengthInBytes);
+    _buffer = null;
+    return result;
+  }
+}
+
+/// Read-only buffer for reading sequentially from a [ByteData] instance.
+///
+/// The byte order used is [Endian.host] throughout.
+class ReadBuffer {
+  /// Creates a [ReadBuffer] for reading from the specified [data].
+  ReadBuffer(this.data)
+    : assert(data != null);
+
+  /// The underlying data being read.
+  final ByteData data;
+
+  /// The position to read next.
+  int _position = 0;
+
+  /// Whether the buffer has data remaining to read.
+  bool get hasRemaining => _position < data.lengthInBytes;
+
+  /// Reads a Uint8 from the buffer.
+  int getUint8() {
+    return data.getUint8(_position++);
+  }
+
+  /// Reads a Uint16 from the buffer.
+  int getUint16({Endian? endian}) {
+    final int value = data.getUint16(_position, endian ?? Endian.host);
+    _position += 2;
+    return value;
+  }
+
+  /// Reads a Uint32 from the buffer.
+  int getUint32({Endian? endian}) {
+    final int value = data.getUint32(_position, endian ?? Endian.host);
+    _position += 4;
+    return value;
+  }
+
+  /// Reads an Int32 from the buffer.
+  int getInt32({Endian? endian}) {
+    final int value = data.getInt32(_position, endian ?? Endian.host);
+    _position += 4;
+    return value;
+  }
+
+  /// Reads an Int64 from the buffer.
+  int getInt64({Endian? endian}) {
+    final int value = data.getInt64(_position, endian ?? Endian.host);
+    _position += 8;
+    return value;
+  }
+
+  /// Reads a Float64 from the buffer.
+  double getFloat64({Endian? endian}) {
+    _alignTo(8);
+    final double value = data.getFloat64(_position, endian ?? Endian.host);
+    _position += 8;
+    return value;
+  }
+
+  /// Reads the given number of Uint8s from the buffer.
+  Uint8List getUint8List(int length) {
+    final Uint8List list = data.buffer.asUint8List(data.offsetInBytes + _position, length);
+    _position += length;
+    return list;
+  }
+
+  /// Reads the given number of Int32s from the buffer.
+  Int32List getInt32List(int length) {
+    _alignTo(4);
+    final Int32List list = data.buffer.asInt32List(data.offsetInBytes + _position, length);
+    _position += 4 * length;
+    return list;
+  }
+
+  /// Reads the given number of Int64s from the buffer.
+  Int64List getInt64List(int length) {
+    _alignTo(8);
+    final Int64List list = data.buffer.asInt64List(data.offsetInBytes + _position, length);
+    _position += 8 * length;
+    return list;
+  }
+
+  /// Reads the given number of Float64s from the buffer.
+  Float64List getFloat64List(int length) {
+    _alignTo(8);
+    final Float64List list = data.buffer.asFloat64List(data.offsetInBytes + _position, length);
+    _position += 8 * length;
+    return list;
+  }
+
+  void _alignTo(int alignment) {
+    final int mod = _position % alignment;
+    if (mod != 0)
+      _position += alignment - mod;
+  }
+}
diff --git a/lib/src/foundation/stack_frame.dart b/lib/src/foundation/stack_frame.dart
new file mode 100644
index 0000000..d961a17
--- /dev/null
+++ b/lib/src/foundation/stack_frame.dart
@@ -0,0 +1,317 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show hashValues;
+
+import 'package:meta/meta.dart';
+
+import 'constants.dart';
+import 'object.dart';
+
+/// A object representation of a frame from a stack trace.
+///
+/// {@tool snippet}
+///
+/// This example creates a traversable list of parsed [StackFrame] objects from
+/// the current [StackTrace].
+///
+/// ```dart
+/// final List<StackFrame> currentFrames = StackFrame.fromStackTrace(StackTrace.current);
+/// ```
+/// {@end-tool}
+@immutable
+class StackFrame {
+  /// Creates a new StackFrame instance.
+  ///
+  /// All parameters must not be null. The [className] may be the empty string
+  /// if there is no class (e.g. for a top level library method).
+  const StackFrame({
+    required this.number,
+    required this.column,
+    required this.line,
+    required this.packageScheme,
+    required this.package,
+    required this.packagePath,
+    this.className = '',
+    required this.method,
+    this.isConstructor = false,
+    required this.source,
+  })  : assert(number != null),
+        assert(column != null),
+        assert(line != null),
+        assert(method != null),
+        assert(packageScheme != null),
+        assert(package != null),
+        assert(packagePath != null),
+        assert(className != null),
+        assert(isConstructor != null),
+        assert(source != null);
+
+  /// A stack frame representing an asynchronous suspension.
+  static const StackFrame asynchronousSuspension = StackFrame(
+    number: -1,
+    column: -1,
+    line: -1,
+    method: 'asynchronous suspension',
+    packageScheme: '',
+    package: '',
+    packagePath: '',
+    source: '<asynchronous suspension>',
+  );
+
+  /// A stack frame representing a Dart elided stack overflow frame.
+  static const StackFrame stackOverFlowElision = StackFrame(
+    number: -1,
+    column: -1,
+    line: -1,
+    method: '...',
+    packageScheme: '',
+    package: '',
+    packagePath: '',
+    source: '...',
+  );
+
+  /// Parses a list of [StackFrame]s from a [StackTrace] object.
+  ///
+  /// This is normally useful with [StackTrace.current].
+  static List<StackFrame> fromStackTrace(StackTrace stack) {
+    assert(stack != null);
+    return fromStackString(stack.toString());
+  }
+
+  /// Parses a list of [StackFrame]s from the [StackTrace.toString] method.
+  static List<StackFrame> fromStackString(String stack) {
+    assert(stack != null);
+    return stack
+        .trim()
+        .split('\n')
+        .where((String line) => line.isNotEmpty)
+        .map(fromStackTraceLine)
+        // On the Web in non-debug builds the stack trace includes the exception
+        // message that precedes the stack trace itself. fromStackTraceLine will
+        // return null in that case. We will skip it here.
+        .whereType<StackFrame>()
+        .toList();
+  }
+
+  static StackFrame? _parseWebFrame(String line) {
+    if (kDebugMode) {
+      return _parseWebDebugFrame(line);
+    } else {
+      return _parseWebNonDebugFrame(line);
+    }
+  }
+
+  static StackFrame _parseWebDebugFrame(String line) {
+    // This RegExp is only partially correct for flutter run/test differences.
+    // https://github.com/flutter/flutter/issues/52685
+    final bool hasPackage = line.startsWith('package');
+    final RegExp parser = hasPackage
+        ? RegExp(r'^(package.+) (\d+):(\d+)\s+(.+)$')
+        : RegExp(r'^(.+) (\d+):(\d+)\s+(.+)$');
+    Match? match = parser.firstMatch(line);
+    assert(match != null, 'Expected $line to match $parser.');
+    match = match!;
+
+    String package = '<unknown>';
+    String packageScheme = '<unknown>';
+    String packagePath = '<unknown>';
+    if (hasPackage) {
+      packageScheme = 'package';
+      final Uri packageUri = Uri.parse(match.group(1)!);
+      package = packageUri.pathSegments[0];
+      packagePath = packageUri.path.replaceFirst(packageUri.pathSegments[0] + '/', '');
+    }
+
+    return StackFrame(
+      number: -1,
+      packageScheme: packageScheme,
+      package: package,
+      packagePath: packagePath,
+      line: int.parse(match.group(2)!),
+      column: int.parse(match.group(3)!),
+      className: '<unknown>',
+      method: match.group(4)!,
+      source: line,
+    );
+  }
+
+  // Non-debug builds do not point to dart code but compiled JavaScript, so
+  // line numbers are meaningless. We only attempt to parse the class and
+  // method name, which is more or less readable in profile builds, and
+  // minified in release builds.
+  static final RegExp _webNonDebugFramePattern = RegExp(r'^\s*at ([^\s]+).*$');
+
+  // Parses `line` as a stack frame in profile and release Web builds. If not
+  // recognized as a stack frame, returns null.
+  static StackFrame? _parseWebNonDebugFrame(String line) {
+    final Match? match = _webNonDebugFramePattern.firstMatch(line);
+    if (match == null) {
+      // On the Web in non-debug builds the stack trace includes the exception
+      // message that precedes the stack trace itself. Example:
+      //
+      // TypeError: Cannot read property 'hello$0' of null
+      //    at _GalleryAppState.build$1 (http://localhost:8080/main.dart.js:149790:13)
+      //    at StatefulElement.build$0 (http://localhost:8080/main.dart.js:129138:37)
+      //    at StatefulElement.performRebuild$0 (http://localhost:8080/main.dart.js:129032:23)
+      //
+      // Instead of crashing when a line is not recognized as a stack frame, we
+      // return null. The caller, such as fromStackString, can then just skip
+      // this frame.
+      return null;
+    }
+
+    final List<String> classAndMethod = match.group(1)!.split('.');
+    final String className = classAndMethod.length > 1 ? classAndMethod.first : '<unknown>';
+    final String method = classAndMethod.length > 1
+      ? classAndMethod.skip(1).join('.')
+      : classAndMethod.single;
+
+    return StackFrame(
+      number: -1,
+      packageScheme: '<unknown>',
+      package: '<unknown>',
+      packagePath: '<unknown>',
+      line: -1,
+      column: -1,
+      className: className,
+      method: method,
+      source: line,
+    );
+  }
+
+  /// Parses a single [StackFrame] from a single line of a [StackTrace].
+  static StackFrame? fromStackTraceLine(String line) {
+    assert(line != null);
+    if (line == '<asynchronous suspension>') {
+      return asynchronousSuspension;
+    } else if (line == '...') {
+      return stackOverFlowElision;
+    }
+
+    assert(
+      line != '===== asynchronous gap ===========================',
+      'Got a stack frame from package:stack_trace, where a vm or web frame was expected. '
+      'This can happen if FlutterError.demangleStackTrace was not set in an environment '
+      'that propagates non-standard stack traces to the framework, such as during tests.'
+    );
+
+    // Web frames.
+    if (!line.startsWith('#')) {
+      return _parseWebFrame(line);
+    }
+
+    final RegExp parser = RegExp(r'^#(\d+) +(.+) \((.+?):?(\d+){0,1}:?(\d+){0,1}\)$');
+    Match? match = parser.firstMatch(line);
+    assert(match != null, 'Expected $line to match $parser.');
+    match = match!;
+
+    bool isConstructor = false;
+    String className = '';
+    String method = match.group(2)!.replaceAll('.<anonymous closure>', '');
+    if (method.startsWith('new')) {
+      final List<String> methodParts = method.split(' ');
+      // Sometimes a web frame will only read "new" and have no class name.
+      className = methodParts.length > 1 ? method.split(' ')[1] : '<unknown>';
+      method = '';
+      if (className.contains('.')) {
+        final List<String> parts  = className.split('.');
+        className = parts[0];
+        method = parts[1];
+      }
+      isConstructor = true;
+    } else if (method.contains('.')) {
+      final List<String> parts = method.split('.');
+      className = parts[0];
+      method = parts[1];
+    }
+
+    final Uri packageUri = Uri.parse(match.group(3)!);
+    String package = '<unknown>';
+    String packagePath = packageUri.path;
+    if (packageUri.scheme == 'dart' || packageUri.scheme == 'package') {
+      package = packageUri.pathSegments[0];
+      packagePath = packageUri.path.replaceFirst(packageUri.pathSegments[0] + '/', '');
+    }
+
+    return StackFrame(
+      number: int.parse(match.group(1)!),
+      className: className,
+      method: method,
+      packageScheme: packageUri.scheme,
+      package: package,
+      packagePath: packagePath,
+      line: match.group(4) == null ? -1 : int.parse(match.group(4)!),
+      column: match.group(5) == null ? -1 : int.parse(match.group(5)!),
+      isConstructor: isConstructor,
+      source: line,
+    );
+  }
+
+  /// The original source of this stack frame.
+  final String source;
+
+  /// The zero-indexed frame number.
+  ///
+  /// This value may be -1 to indicate an unknown frame number.
+  final int number;
+
+  /// The scheme of the package for this frame, e.g. "dart" for
+  /// dart:core/errors_patch.dart or "package" for
+  /// package:flutter/src/widgets/text.dart.
+  ///
+  /// The path property refers to the source file.
+  final String packageScheme;
+
+  /// The package for this frame, e.g. "core" for
+  /// dart:core/errors_patch.dart or "flutter" for
+  /// package:flutter/src/widgets/text.dart.
+  final String package;
+
+  /// The path of the file for this frame, e.g. "errors_patch.dart" for
+  /// dart:core/errors_patch.dart or "src/widgets/text.dart" for
+  /// package:flutter/src/widgets/text.dart.
+  final String packagePath;
+
+  /// The source line number.
+  final int line;
+
+  /// The source column number.
+  final int column;
+
+  /// The class name, if any, for this frame.
+  ///
+  /// This may be null for top level methods in a library or anonymous closure
+  /// methods.
+  final String className;
+
+  /// The method name for this frame.
+  ///
+  /// This will be an empty string if the stack frame is from the default
+  /// constructor.
+  final String method;
+
+  /// Whether or not this was thrown from a constructor.
+  final bool isConstructor;
+
+  @override
+  int get hashCode => hashValues(number, package, line, column, className, method, source);
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is StackFrame
+        && other.number == number
+        && other.package == package
+        && other.line == line
+        && other.column == column
+        && other.className == className
+        && other.method == method
+        && other.source == source;
+  }
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'StackFrame')}(#$number, $packageScheme:$package/$packagePath:$line:$column, className: $className, method: $method)';
+}
diff --git a/lib/src/foundation/synchronous_future.dart b/lib/src/foundation/synchronous_future.dart
new file mode 100644
index 0000000..9182fbd
--- /dev/null
+++ b/lib/src/foundation/synchronous_future.dart
@@ -0,0 +1,63 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+
+/// A [Future] whose [then] implementation calls the callback immediately.
+///
+/// This is similar to [new Future.value], except that the value is available in
+/// the same event-loop iteration.
+///
+/// ⚠ This class is useful in cases where you want to expose a single API, where
+/// you normally want to have everything execute synchronously, but where on
+/// rare occasions you want the ability to switch to an asynchronous model. **In
+/// general use of this class should be avoided as it is very difficult to debug
+/// such bimodal behavior.**
+class SynchronousFuture<T> implements Future<T> {
+  /// Creates a synchronous future.
+  ///
+  /// See also:
+  ///
+  ///  * [new Future.value] for information about creating a regular
+  ///    [Future] that completes with a value.
+  SynchronousFuture(this._value);
+
+  final T _value;
+
+  @override
+  Stream<T> asStream() {
+    final StreamController<T> controller = StreamController<T>();
+    controller.add(_value);
+    controller.close();
+    return controller.stream;
+  }
+
+  @override
+  Future<T> catchError(Function onError, { bool test(Object error)? }) => Completer<T>().future;
+
+  @override
+  Future<R> then<R>(FutureOr<R> onValue(T value), { Function? onError }) {
+    final dynamic result = onValue(_value);
+    if (result is Future<R>)
+      return result;
+    return SynchronousFuture<R>(result as R);
+  }
+
+  @override
+  Future<T> timeout(Duration timeLimit, { FutureOr<T> onTimeout()? }) {
+    return Future<T>.value(_value).timeout(timeLimit, onTimeout: onTimeout);
+  }
+
+  @override
+  Future<T> whenComplete(FutureOr<dynamic> action()) {
+    try {
+      final FutureOr<dynamic> result = action();
+      if (result is Future)
+        return result.then<T>((dynamic value) => _value);
+      return this;
+    } catch (e, stack) {
+      return Future<T>.error(e, stack);
+    }
+  }
+}
diff --git a/lib/src/foundation/unicode.dart b/lib/src/foundation/unicode.dart
new file mode 100644
index 0000000..5b78fb7
--- /dev/null
+++ b/lib/src/foundation/unicode.dart
@@ -0,0 +1,101 @@
+// Copyright 2014 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.
+
+/// Constants for useful Unicode characters.
+///
+/// Currently, these characters are all related to bidirectional text.
+///
+/// See also:
+///
+///  * <http://unicode.org/reports/tr9/>, which describes the Unicode
+///    bidirectional text algorithm.
+class Unicode {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  Unicode._();
+  /// `U+202A LEFT-TO-RIGHT EMBEDDING`
+  ///
+  /// Treat the following text as embedded left-to-right.
+  ///
+  /// Use [PDF] to end the embedding.
+  static const String LRE = '\u202A';
+
+  /// `U+202B RIGHT-TO-LEFT EMBEDDING`
+  ///
+  /// Treat the following text as embedded right-to-left.
+  ///
+  /// Use [PDF] to end the embedding.
+  static const String RLE = '\u202B';
+
+  /// `U+202C POP DIRECTIONAL FORMATTING`
+  ///
+  /// End the scope of the last [LRE], [RLE], [RLO], or [LRO].
+  static const String PDF = '\u202C';
+
+  /// `U+202A LEFT-TO-RIGHT OVERRIDE`
+  ///
+  /// Force following characters to be treated as strong left-to-right characters.
+  ///
+  /// For example, this causes Hebrew text to render backwards.
+  ///
+  /// Use [PDF] to end the override.
+  static const String LRO = '\u202D';
+
+  /// `U+202B RIGHT-TO-LEFT OVERRIDE`
+  ///
+  /// Force following characters to be treated as strong right-to-left characters.
+  ///
+  /// For example, this causes English text to render backwards.
+  ///
+  /// Use [PDF] to end the override.
+  static const String RLO = '\u202E';
+
+  /// `U+2066 LEFT-TO-RIGHT ISOLATE`
+  ///
+  /// Treat the following text as isolated and left-to-right.
+  ///
+  /// Use [PDI] to end the isolated scope.
+  static const String LRI = '\u2066';
+
+  /// `U+2067 RIGHT-TO-LEFT ISOLATE`
+  ///
+  /// Treat the following text as isolated and right-to-left.
+  ///
+  /// Use [PDI] to end the isolated scope.
+  static const String RLI = '\u2067';
+
+  /// `U+2068 FIRST STRONG ISOLATE`
+  ///
+  /// Treat the following text as isolated and in the direction of its first
+  /// strong directional character that is not inside a nested isolate.
+  ///
+  /// This essentially "auto-detects" the directionality of the text. It is not
+  /// 100% reliable. For example, Arabic text that starts with an English quote
+  /// will be detected as LTR, not RTL, which will lead to the text being in a
+  /// weird order.
+  ///
+  /// Use [PDI] to end the isolated scope.
+  static const String FSI = '\u2068';
+
+  /// `U+2069 POP DIRECTIONAL ISOLATE`
+  ///
+  /// End the scope of the last [LRI], [RLI], or [FSI].
+  static const String PDI = '\u2069';
+
+  /// `U+200E LEFT-TO-RIGHT MARK`
+  ///
+  /// Left-to-right zero-width character.
+  static const String LRM = '\u200E';
+
+  /// `U+200F RIGHT-TO-LEFT MARK`
+  ///
+  /// Right-to-left zero-width non-Arabic character.
+  static const String RLM = '\u200F';
+
+  /// `U+061C ARABIC LETTER MARK`
+  ///
+  /// Right-to-left zero-width Arabic character.
+  static const String ALM = '\u061C';
+}
diff --git a/lib/src/gestures/arena.dart b/lib/src/gestures/arena.dart
new file mode 100644
index 0000000..49a1f65
--- /dev/null
+++ b/lib/src/gestures/arena.dart
@@ -0,0 +1,278 @@
+// Copyright 2014 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.
+
+
+import 'dart:async';
+
+import 'package:flute/foundation.dart';
+
+import 'debug.dart';
+
+/// Whether the gesture was accepted or rejected.
+enum GestureDisposition {
+  /// This gesture was accepted as the interpretation of the user's input.
+  accepted,
+
+  /// This gesture was rejected as the interpretation of the user's input.
+  rejected,
+}
+
+/// Represents an object participating in an arena.
+///
+/// Receives callbacks from the GestureArena to notify the object when it wins
+/// or loses a gesture negotiation. Exactly one of [acceptGesture] or
+/// [rejectGesture] will be called for each arena this member was added to,
+/// regardless of what caused the arena to be resolved. For example, if a
+/// member resolves the arena itself, that member still receives an
+/// [acceptGesture] callback.
+abstract class GestureArenaMember {
+  /// Called when this member wins the arena for the given pointer id.
+  void acceptGesture(int pointer);
+
+  /// Called when this member loses the arena for the given pointer id.
+  void rejectGesture(int pointer);
+}
+
+/// An interface to pass information to an arena.
+///
+/// A given [GestureArenaMember] can have multiple entries in multiple arenas
+/// with different pointer ids.
+class GestureArenaEntry {
+  GestureArenaEntry._(this._arena, this._pointer, this._member);
+
+  final GestureArenaManager _arena;
+  final int _pointer;
+  final GestureArenaMember _member;
+
+  /// Call this member to claim victory (with accepted) or admit defeat (with rejected).
+  ///
+  /// It's fine to attempt to resolve a gesture recognizer for an arena that is
+  /// already resolved.
+  void resolve(GestureDisposition disposition) {
+    _arena._resolve(_pointer, _member, disposition);
+  }
+}
+
+class _GestureArena {
+  final List<GestureArenaMember> members = <GestureArenaMember>[];
+  bool isOpen = true;
+  bool isHeld = false;
+  bool hasPendingSweep = false;
+
+  /// If a member attempts to win while the arena is still open, it becomes the
+  /// "eager winner". We look for an eager winner when closing the arena to new
+  /// participants, and if there is one, we resolve the arena in its favor at
+  /// that time.
+  GestureArenaMember? eagerWinner;
+
+  void add(GestureArenaMember member) {
+    assert(isOpen);
+    members.add(member);
+  }
+
+  @override
+  String toString() {
+    final StringBuffer buffer = StringBuffer();
+    if (members.isEmpty) {
+      buffer.write('<empty>');
+    } else {
+      buffer.write(members.map<String>((GestureArenaMember member) {
+        if (member == eagerWinner)
+          return '$member (eager winner)';
+        return '$member';
+      }).join(', '));
+    }
+    if (isOpen)
+      buffer.write(' [open]');
+    if (isHeld)
+      buffer.write(' [held]');
+    if (hasPendingSweep)
+      buffer.write(' [hasPendingSweep]');
+    return buffer.toString();
+  }
+}
+
+/// The first member to accept or the last member to not reject wins.
+///
+/// See <https://flutter.dev/gestures/#gesture-disambiguation> for more
+/// information about the role this class plays in the gesture system.
+///
+/// To debug problems with gestures, consider using
+/// [debugPrintGestureArenaDiagnostics].
+class GestureArenaManager {
+  final Map<int, _GestureArena> _arenas = <int, _GestureArena>{};
+
+  /// Adds a new member (e.g., gesture recognizer) to the arena.
+  GestureArenaEntry add(int pointer, GestureArenaMember member) {
+    final _GestureArena state = _arenas.putIfAbsent(pointer, () {
+      assert(_debugLogDiagnostic(pointer, '★ Opening new gesture arena.'));
+      return _GestureArena();
+    });
+    state.add(member);
+    assert(_debugLogDiagnostic(pointer, 'Adding: $member'));
+    return GestureArenaEntry._(this, pointer, member);
+  }
+
+  /// Prevents new members from entering the arena.
+  ///
+  /// Called after the framework has finished dispatching the pointer down event.
+  void close(int pointer) {
+    final _GestureArena? state = _arenas[pointer];
+    if (state == null)
+      return; // This arena either never existed or has been resolved.
+    state.isOpen = false;
+    assert(_debugLogDiagnostic(pointer, 'Closing', state));
+    _tryToResolveArena(pointer, state);
+  }
+
+  /// Forces resolution of the arena, giving the win to the first member.
+  ///
+  /// Sweep is typically after all the other processing for a [PointerUpEvent]
+  /// have taken place. It ensures that multiple passive gestures do not cause a
+  /// stalemate that prevents the user from interacting with the app.
+  ///
+  /// Recognizers that wish to delay resolving an arena past [PointerUpEvent]
+  /// should call [hold] to delay sweep until [release] is called.
+  ///
+  /// See also:
+  ///
+  ///  * [hold]
+  ///  * [release]
+  void sweep(int pointer) {
+    final _GestureArena? state = _arenas[pointer];
+    if (state == null)
+      return; // This arena either never existed or has been resolved.
+    assert(!state.isOpen);
+    if (state.isHeld) {
+      state.hasPendingSweep = true;
+      assert(_debugLogDiagnostic(pointer, 'Delaying sweep', state));
+      return; // This arena is being held for a long-lived member.
+    }
+    assert(_debugLogDiagnostic(pointer, 'Sweeping', state));
+    _arenas.remove(pointer);
+    if (state.members.isNotEmpty) {
+      // First member wins.
+      assert(_debugLogDiagnostic(pointer, 'Winner: ${state.members.first}'));
+      state.members.first.acceptGesture(pointer);
+      // Give all the other members the bad news.
+      for (int i = 1; i < state.members.length; i++)
+        state.members[i].rejectGesture(pointer);
+    }
+  }
+
+  /// Prevents the arena from being swept.
+  ///
+  /// Typically, a winner is chosen in an arena after all the other
+  /// [PointerUpEvent] processing by [sweep]. If a recognizer wishes to delay
+  /// resolving an arena past [PointerUpEvent], the recognizer can [hold] the
+  /// arena open using this function. To release such a hold and let the arena
+  /// resolve, call [release].
+  ///
+  /// See also:
+  ///
+  ///  * [sweep]
+  ///  * [release]
+  void hold(int pointer) {
+    final _GestureArena? state = _arenas[pointer];
+    if (state == null)
+      return; // This arena either never existed or has been resolved.
+    state.isHeld = true;
+    assert(_debugLogDiagnostic(pointer, 'Holding', state));
+  }
+
+  /// Releases a hold, allowing the arena to be swept.
+  ///
+  /// If a sweep was attempted on a held arena, the sweep will be done
+  /// on release.
+  ///
+  /// See also:
+  ///
+  ///  * [sweep]
+  ///  * [hold]
+  void release(int pointer) {
+    final _GestureArena? state = _arenas[pointer];
+    if (state == null)
+      return; // This arena either never existed or has been resolved.
+    state.isHeld = false;
+    assert(_debugLogDiagnostic(pointer, 'Releasing', state));
+    if (state.hasPendingSweep)
+      sweep(pointer);
+  }
+
+  /// Reject or accept a gesture recognizer.
+  ///
+  /// This is called by calling [GestureArenaEntry.resolve] on the object returned from [add].
+  void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) {
+    final _GestureArena? state = _arenas[pointer];
+    if (state == null)
+      return; // This arena has already resolved.
+    assert(_debugLogDiagnostic(pointer, '${ disposition == GestureDisposition.accepted ? "Accepting" : "Rejecting" }: $member'));
+    assert(state.members.contains(member));
+    if (disposition == GestureDisposition.rejected) {
+      state.members.remove(member);
+      member.rejectGesture(pointer);
+      if (!state.isOpen)
+        _tryToResolveArena(pointer, state);
+    } else {
+      assert(disposition == GestureDisposition.accepted);
+      if (state.isOpen) {
+        state.eagerWinner ??= member;
+      } else {
+        assert(_debugLogDiagnostic(pointer, 'Self-declared winner: $member'));
+        _resolveInFavorOf(pointer, state, member);
+      }
+    }
+  }
+
+  void _tryToResolveArena(int pointer, _GestureArena state) {
+    assert(_arenas[pointer] == state);
+    assert(!state.isOpen);
+    if (state.members.length == 1) {
+      scheduleMicrotask(() => _resolveByDefault(pointer, state));
+    } else if (state.members.isEmpty) {
+      _arenas.remove(pointer);
+      assert(_debugLogDiagnostic(pointer, 'Arena empty.'));
+    } else if (state.eagerWinner != null) {
+      assert(_debugLogDiagnostic(pointer, 'Eager winner: ${state.eagerWinner}'));
+      _resolveInFavorOf(pointer, state, state.eagerWinner!);
+    }
+  }
+
+  void _resolveByDefault(int pointer, _GestureArena state) {
+    if (!_arenas.containsKey(pointer))
+      return; // Already resolved earlier.
+    assert(_arenas[pointer] == state);
+    assert(!state.isOpen);
+    final List<GestureArenaMember> members = state.members;
+    assert(members.length == 1);
+    _arenas.remove(pointer);
+    assert(_debugLogDiagnostic(pointer, 'Default winner: ${state.members.first}'));
+    state.members.first.acceptGesture(pointer);
+  }
+
+  void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
+    assert(state == _arenas[pointer]);
+    assert(state != null);
+    assert(state.eagerWinner == null || state.eagerWinner == member);
+    assert(!state.isOpen);
+    _arenas.remove(pointer);
+    for (final GestureArenaMember rejectedMember in state.members) {
+      if (rejectedMember != member)
+        rejectedMember.rejectGesture(pointer);
+    }
+    member.acceptGesture(pointer);
+  }
+
+  bool _debugLogDiagnostic(int pointer, String message, [ _GestureArena? state ]) {
+    assert(() {
+      if (debugPrintGestureArenaDiagnostics) {
+        final int? count = state != null ? state.members.length : null;
+        final String s = count != 1 ? 's' : '';
+        debugPrint('Gesture arena ${pointer.toString().padRight(4)} ❙ $message${ count != null ? " with $count member$s." : ""}');
+      }
+      return true;
+    }());
+    return true;
+  }
+}
diff --git a/lib/src/gestures/binding.dart b/lib/src/gestures/binding.dart
new file mode 100644
index 0000000..f5fd3b0
--- /dev/null
+++ b/lib/src/gestures/binding.dart
@@ -0,0 +1,476 @@
+// Copyright 2014 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.
+
+
+import 'dart:async';
+import 'dart:collection';
+import 'package:flute/ui.dart' as ui show PointerDataPacket;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+
+import 'arena.dart';
+import 'converter.dart';
+import 'debug.dart';
+import 'events.dart';
+import 'hit_test.dart';
+import 'pointer_router.dart';
+import 'pointer_signal_resolver.dart';
+import 'resampler.dart';
+
+typedef _HandleSampleTimeChangedCallback = void Function();
+
+// Class that handles resampling of touch events for multiple pointer
+// devices.
+//
+// SchedulerBinding's `currentSystemFrameTimeStamp` is used to determine
+// sample time.
+class _Resampler {
+  _Resampler(this._handlePointerEvent, this._handleSampleTimeChanged);
+
+  // Resamplers used to filter incoming pointer events.
+  final Map<int, PointerEventResampler> _resamplers = <int, PointerEventResampler>{};
+
+  // Flag to track if a frame callback has been scheduled.
+  bool _frameCallbackScheduled = false;
+
+  // Current frame time for resampling.
+  Duration _frameTime = Duration.zero;
+
+  // Last sample time and time stamp of last event.
+  //
+  // Only used for debugPrint of resampling margin.
+  Duration _lastSampleTime = Duration.zero;
+  Duration _lastEventTime = Duration.zero;
+
+  // Callback used to handle pointer events.
+  final HandleEventCallback _handlePointerEvent;
+
+  // Callback used to handle sample time changes.
+  final _HandleSampleTimeChangedCallback _handleSampleTimeChanged;
+
+  // Add `event` for resampling or dispatch it directly if
+  // not a touch event.
+  void addOrDispatch(PointerEvent event) {
+    final SchedulerBinding? scheduler = SchedulerBinding.instance;
+    assert(scheduler != null);
+      // Add touch event to resampler or dispatch pointer event directly.
+    if (event.kind == PointerDeviceKind.touch) {
+      // Save last event time for debugPrint of resampling margin.
+      _lastEventTime = event.timeStamp;
+
+      final PointerEventResampler resampler = _resamplers.putIfAbsent(
+        event.device,
+        () => PointerEventResampler(),
+      );
+      resampler.addEvent(event);
+    } else {
+      _handlePointerEvent(event);
+    }
+  }
+
+  // Sample and dispatch events.
+  //
+  // `samplingOffset` is relative to the current frame time, which
+  // can be in the past when we're not actively resampling.
+  // `samplingInterval` is used to determine the approximate next
+  // time for resampling.
+  // `currentSystemFrameTimeStamp` is used to determine the current
+  // frame time.
+  void sample(Duration samplingOffset, Duration samplingInterval) {
+    final SchedulerBinding? scheduler = SchedulerBinding.instance;
+    assert(scheduler != null);
+
+    // Determine sample time by adding the offset to the current
+    // frame time. This is expected to be in the past and not
+    // result in any dispatched events unless we're actively
+    // resampling events.
+    final Duration sampleTime = _frameTime + samplingOffset;
+
+    // Determine next sample time by adding the sampling interval
+    // to the current sample time.
+    final Duration nextSampleTime = sampleTime + samplingInterval;
+
+    // Iterate over active resamplers and sample pointer events for
+    // current sample time.
+    for (final PointerEventResampler resampler in _resamplers.values) {
+      resampler.sample(sampleTime, nextSampleTime, _handlePointerEvent);
+    }
+
+    // Remove inactive resamplers.
+    _resamplers.removeWhere((int key, PointerEventResampler resampler) {
+      return !resampler.hasPendingEvents && !resampler.isDown;
+    });
+
+    // Save last sample time for debugPrint of resampling margin.
+    _lastSampleTime = sampleTime;
+
+    // Schedule a frame callback if another call to `sample` is needed.
+    if (!_frameCallbackScheduled && _resamplers.isNotEmpty) {
+      _frameCallbackScheduled = true;
+      scheduler?.scheduleFrameCallback((_) {
+        _frameCallbackScheduled = false;
+        // We use `currentSystemFrameTimeStamp` here as it's critical that
+        // sample time is in the same clock as the event time stamps, and
+        // never adjusted or scaled like `currentFrameTimeStamp`.
+        _frameTime = scheduler.currentSystemFrameTimeStamp;
+        assert(() {
+          if (debugPrintResamplingMargin) {
+            final Duration resamplingMargin = _lastEventTime - _lastSampleTime;
+              debugPrint('$resamplingMargin');
+          }
+          return true;
+        }());
+        _handleSampleTimeChanged();
+      });
+    }
+  }
+
+  // Stop all resampling and dispatched any queued events.
+  void stop() {
+    for (final PointerEventResampler resampler in _resamplers.values) {
+      resampler.stop(_handlePointerEvent);
+    }
+    _resamplers.clear();
+  }
+}
+
+// The default sampling offset.
+//
+// Sampling offset is relative to presentation time. If we produce frames
+// 16.667 ms before presentation and input rate is ~60hz, worst case latency
+// is 33.334 ms. This however assumes zero latency from the input driver.
+// 4.666 ms margin is added for this.
+const Duration _defaultSamplingOffset = Duration(milliseconds: -38);
+
+// The sampling interval.
+//
+// Sampling interval is used to determine the approximate time for subsequent
+// sampling. This is used to decide if early processing of up and removed events
+// is appropriate. 16667 us for 60hz sampling interval.
+const Duration _samplingInterval = Duration(microseconds: 16667);
+
+/// A binding for the gesture subsystem.
+///
+/// ## Lifecycle of pointer events and the gesture arena
+///
+/// ### [PointerDownEvent]
+///
+/// When a [PointerDownEvent] is received by the [GestureBinding] (from
+/// [dart:ui.PlatformDispatcher.onPointerDataPacket], as interpreted by the
+/// [PointerEventConverter]), a [hitTest] is performed to determine which
+/// [HitTestTarget] nodes are affected. (Other bindings are expected to
+/// implement [hitTest] to defer to [HitTestable] objects. For example, the
+/// rendering layer defers to the [RenderView] and the rest of the render object
+/// hierarchy.)
+///
+/// The affected nodes then are given the event to handle ([dispatchEvent] calls
+/// [HitTestTarget.handleEvent] for each affected node). If any have relevant
+/// [GestureRecognizer]s, they provide the event to them using
+/// [GestureRecognizer.addPointer]. This typically causes the recognizer to
+/// register with the [PointerRouter] to receive notifications regarding the
+/// pointer in question.
+///
+/// Once the hit test and dispatching logic is complete, the event is then
+/// passed to the aforementioned [PointerRouter], which passes it to any objects
+/// that have registered interest in that event.
+///
+/// Finally, the [gestureArena] is closed for the given pointer
+/// ([GestureArenaManager.close]), which begins the process of selecting a
+/// gesture to win that pointer.
+///
+/// ### Other events
+///
+/// A pointer that is [PointerEvent.down] may send further events, such as
+/// [PointerMoveEvent], [PointerUpEvent], or [PointerCancelEvent]. These are
+/// sent to the same [HitTestTarget] nodes as were found when the
+/// [PointerDownEvent] was received (even if they have since been disposed; it is
+/// the responsibility of those objects to be aware of that possibility).
+///
+/// Then, the events are routed to any still-registered entrants in the
+/// [PointerRouter]'s table for that pointer.
+///
+/// When a [PointerUpEvent] is received, the [GestureArenaManager.sweep] method
+/// is invoked to force the gesture arena logic to terminate if necessary.
+mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
+  @override
+  void initInstances() {
+    super.initInstances();
+    _instance = this;
+    window.onPointerDataPacket = _handlePointerDataPacket;
+  }
+
+  @override
+  void unlocked() {
+    super.unlocked();
+    _flushPointerEventQueue();
+  }
+
+  /// The singleton instance of this object.
+  static GestureBinding? get instance => _instance;
+  static GestureBinding? _instance;
+
+  final Queue<PointerEvent> _pendingPointerEvents = Queue<PointerEvent>();
+
+  void _handlePointerDataPacket(ui.PointerDataPacket packet) {
+    // We convert pointer data to logical pixels so that e.g. the touch slop can be
+    // defined in a device-independent manner.
+    _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
+    if (!locked)
+      _flushPointerEventQueue();
+  }
+
+  /// Dispatch a [PointerCancelEvent] for the given pointer soon.
+  ///
+  /// The pointer event will be dispatched before the next pointer event and
+  /// before the end of the microtask but not within this function call.
+  void cancelPointer(int pointer) {
+    if (_pendingPointerEvents.isEmpty && !locked)
+      scheduleMicrotask(_flushPointerEventQueue);
+    _pendingPointerEvents.addFirst(PointerCancelEvent(pointer: pointer));
+  }
+
+  void _flushPointerEventQueue() {
+    assert(!locked);
+
+    while (_pendingPointerEvents.isNotEmpty)
+      handlePointerEvent(_pendingPointerEvents.removeFirst());
+  }
+
+  /// A router that routes all pointer events received from the engine.
+  final PointerRouter pointerRouter = PointerRouter();
+
+  /// The gesture arenas used for disambiguating the meaning of sequences of
+  /// pointer events.
+  final GestureArenaManager gestureArena = GestureArenaManager();
+
+  /// The resolver used for determining which widget handles a
+  /// [PointerSignalEvent].
+  final PointerSignalResolver pointerSignalResolver = PointerSignalResolver();
+
+  /// State for all pointers which are currently down.
+  ///
+  /// The state of hovering pointers is not tracked because that would require
+  /// hit-testing on every frame.
+  final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
+
+  /// Dispatch an event to the targets found by a hit test on its position.
+  ///
+  /// This method sends the given event to [dispatchEvent] based on event types:
+  ///
+  ///  * [PointerDownEvent]s and [PointerSignalEvent]s are dispatched to the
+  ///    result of a new [hitTest].
+  ///  * [PointerUpEvent]s and [PointerMoveEvent]s are dispatched to the result of hit test of the
+  ///    preceding [PointerDownEvent]s.
+  ///  * [PointerHoverEvent]s, [PointerAddedEvent]s, and [PointerRemovedEvent]s
+  ///    are dispatched without a hit test result.
+  void handlePointerEvent(PointerEvent event) {
+    assert(!locked);
+
+    if (resamplingEnabled) {
+      _resampler.addOrDispatch(event);
+      _resampler.sample(samplingOffset, _samplingInterval);
+      return;
+    }
+
+    // Stop resampler if resampling is not enabled. This is a no-op if
+    // resampling was never enabled.
+    _resampler.stop();
+    _handlePointerEventImmediately(event);
+  }
+
+  void _handlePointerEventImmediately(PointerEvent event) {
+    HitTestResult? hitTestResult;
+    if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent) {
+      assert(!_hitTests.containsKey(event.pointer));
+      hitTestResult = HitTestResult();
+      hitTest(hitTestResult, event.position);
+      if (event is PointerDownEvent) {
+        _hitTests[event.pointer] = hitTestResult;
+      }
+      assert(() {
+        if (debugPrintHitTestResults)
+          debugPrint('$event: $hitTestResult');
+        return true;
+      }());
+    } else if (event is PointerUpEvent || event is PointerCancelEvent) {
+      hitTestResult = _hitTests.remove(event.pointer);
+    } else if (event.down) {
+      // Because events that occur with the pointer down (like
+      // [PointerMoveEvent]s) should be dispatched to the same place that their
+      // initial PointerDownEvent was, we want to re-use the path we found when
+      // the pointer went down, rather than do hit detection each time we get
+      // such an event.
+      hitTestResult = _hitTests[event.pointer];
+    }
+    assert(() {
+      if (debugPrintMouseHoverEvents && event is PointerHoverEvent)
+        debugPrint('$event');
+      return true;
+    }());
+    if (hitTestResult != null ||
+        event is PointerAddedEvent ||
+        event is PointerRemovedEvent) {
+      assert(event.position != null);
+      dispatchEvent(event, hitTestResult);
+    }
+  }
+
+  /// Determine which [HitTestTarget] objects are located at a given position.
+  @override // from HitTestable
+  void hitTest(HitTestResult result, Offset position) {
+    result.add(HitTestEntry(this));
+  }
+
+  /// Dispatch an event to [pointerRouter] and the path of a hit test result.
+  ///
+  /// The `event` is routed to [pointerRouter]. If the `hitTestResult` is not
+  /// null, the event is also sent to every [HitTestTarget] in the entries of the
+  /// given [HitTestResult]. Any exceptions from the handlers are caught.
+  ///
+  /// The `hitTestResult` argument may only be null for [PointerAddedEvent]s or
+  /// [PointerRemovedEvent]s.
+  @override // from HitTestDispatcher
+  void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
+    assert(!locked);
+    // No hit test information implies that this is a [PointerHoverEvent],
+    // [PointerAddedEvent], or [PointerRemovedEvent]. These events are specially
+    // routed here; other events will be routed through the `handleEvent` below.
+    if (hitTestResult == null) {
+      assert(event is PointerAddedEvent || event is PointerRemovedEvent);
+      try {
+        pointerRouter.route(event);
+      } catch (exception, stack) {
+        FlutterError.reportError(FlutterErrorDetailsForPointerEventDispatcher(
+          exception: exception,
+          stack: stack,
+          library: 'gesture library',
+          context: ErrorDescription('while dispatching a non-hit-tested pointer event'),
+          event: event,
+          hitTestEntry: null,
+          informationCollector: () sync* {
+            yield DiagnosticsProperty<PointerEvent>('Event', event, style: DiagnosticsTreeStyle.errorProperty);
+          },
+        ));
+      }
+      return;
+    }
+    for (final HitTestEntry entry in hitTestResult.path) {
+      try {
+        entry.target.handleEvent(event.transformed(entry.transform), entry);
+      } catch (exception, stack) {
+        FlutterError.reportError(FlutterErrorDetailsForPointerEventDispatcher(
+          exception: exception,
+          stack: stack,
+          library: 'gesture library',
+          context: ErrorDescription('while dispatching a pointer event'),
+          event: event,
+          hitTestEntry: entry,
+          informationCollector: () sync* {
+            yield DiagnosticsProperty<PointerEvent>('Event', event, style: DiagnosticsTreeStyle.errorProperty);
+            yield DiagnosticsProperty<HitTestTarget>('Target', entry.target, style: DiagnosticsTreeStyle.errorProperty);
+          },
+        ));
+      }
+    }
+  }
+
+  @override // from HitTestTarget
+  void handleEvent(PointerEvent event, HitTestEntry entry) {
+    pointerRouter.route(event);
+    if (event is PointerDownEvent) {
+      gestureArena.close(event.pointer);
+    } else if (event is PointerUpEvent) {
+      gestureArena.sweep(event.pointer);
+    } else if (event is PointerSignalEvent) {
+      pointerSignalResolver.resolve(event);
+    }
+  }
+
+  /// Reset states of [GestureBinding].
+  ///
+  /// This clears the hit test records.
+  ///
+  /// This is typically called between tests.
+  @protected
+  void resetGestureBinding() {
+    _hitTests.clear();
+  }
+
+  void _handleSampleTimeChanged() {
+    if (!locked) {
+      if (resamplingEnabled) {
+        _resampler.sample(samplingOffset, _samplingInterval);
+      }
+      else {
+        _resampler.stop();
+      }
+    }
+  }
+
+  // Resampler used to filter incoming pointer events when resampling
+  // is enabled.
+  late final _Resampler _resampler = _Resampler(
+    _handlePointerEventImmediately,
+    _handleSampleTimeChanged,
+  );
+
+  /// Enable pointer event resampling for touch devices by setting
+  /// this to true.
+  ///
+  /// Resampling results in smoother touch event processing at the
+  /// cost of some added latency. Devices with low frequency sensors
+  /// or when the frequency is not a multiple of the display frequency
+  /// (e.g., 120Hz input and 90Hz display) benefit from this.
+  ///
+  /// This is typically set during application initialization but
+  /// can be adjusted dynamically in case the application only
+  /// wants resampling for some period of time.
+  bool resamplingEnabled = false;
+
+  /// Offset relative to current frame time that should be used for
+  /// resampling. The [samplingOffset] is expected to be negative.
+  /// Non-negative [samplingOffset] is allowed but will effectively
+  /// disable resampling.
+  Duration samplingOffset = _defaultSamplingOffset;
+}
+
+/// Variant of [FlutterErrorDetails] with extra fields for the gesture
+/// library's binding's pointer event dispatcher ([GestureBinding.dispatchEvent]).
+class FlutterErrorDetailsForPointerEventDispatcher extends FlutterErrorDetails {
+  /// Creates a [FlutterErrorDetailsForPointerEventDispatcher] object with the given
+  /// arguments setting the object's properties.
+  ///
+  /// The gesture library calls this constructor when catching an exception
+  /// that will subsequently be reported using [FlutterError.onError].
+  const FlutterErrorDetailsForPointerEventDispatcher({
+    required Object exception,
+    StackTrace? stack,
+    String? library,
+    DiagnosticsNode? context,
+    this.event,
+    this.hitTestEntry,
+    InformationCollector? informationCollector,
+    bool silent = false,
+  }) : super(
+    exception: exception,
+    stack: stack,
+    library: library,
+    context: context,
+    informationCollector: informationCollector,
+    silent: silent,
+  );
+
+  /// The pointer event that was being routed when the exception was raised.
+  final PointerEvent? event;
+
+  /// The hit test result entry for the object whose handleEvent method threw
+  /// the exception. May be null if no hit test entry is associated with the
+  /// event (e.g. [PointerHoverEvent]s, [PointerAddedEvent]s, and
+  /// [PointerRemovedEvent]s).
+  ///
+  /// The target object itself is given by the [HitTestEntry.target] property of
+  /// the hitTestEntry object.
+  final HitTestEntry? hitTestEntry;
+}
diff --git a/lib/src/gestures/constants.dart b/lib/src/gestures/constants.dart
new file mode 100644
index 0000000..6d83ad3
--- /dev/null
+++ b/lib/src/gestures/constants.dart
@@ -0,0 +1,105 @@
+// Copyright 2014 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.
+
+// Modeled after Android's ViewConfiguration:
+// https://github.com/android/platform_frameworks_base/blob/master/core/java/android/view/ViewConfiguration.java
+
+/// The time that must elapse before a tap gesture sends onTapDown, if there's
+/// any doubt that the gesture is a tap.
+const Duration kPressTimeout = Duration(milliseconds: 100);
+
+/// Maximum length of time between a tap down and a tap up for the gesture to be
+/// considered a tap. (Currently not honored by the TapGestureRecognizer.)
+// TODO(ianh): Remove this, or implement a hover-tap gesture recognizer which
+// uses this.
+const Duration kHoverTapTimeout = Duration(milliseconds: 150);
+
+/// Maximum distance between the down and up pointers for a tap. (Currently not
+/// honored by the [TapGestureRecognizer]; [PrimaryPointerGestureRecognizer],
+/// which TapGestureRecognizer inherits from, uses [kTouchSlop].)
+// TODO(ianh): Remove this or implement it correctly.
+const double kHoverTapSlop = 20.0; // Logical pixels
+
+/// The time before a long press gesture attempts to win.
+const Duration kLongPressTimeout = Duration(milliseconds: 500);
+
+/// The maximum time from the start of the first tap to the start of the second
+/// tap in a double-tap gesture.
+// In Android, this is actually the time from the first's up event
+// to the second's down event, according to the ViewConfiguration docs.
+const Duration kDoubleTapTimeout = Duration(milliseconds: 300);
+
+/// The minimum time from the end of the first tap to the start of the second
+/// tap in a double-tap gesture.
+const Duration kDoubleTapMinTime = Duration(milliseconds: 40);
+
+/// The maximum distance that the first touch in a double-tap gesture can travel
+/// before deciding that it is not part of a double-tap gesture.
+/// DoubleTapGestureRecognizer also restricts the second touch to this distance.
+const double kDoubleTapTouchSlop = kTouchSlop; // Logical pixels
+
+/// Distance between the initial position of the first touch and the start
+/// position of a potential second touch for the second touch to be considered
+/// the second touch of a double-tap gesture.
+const double kDoubleTapSlop = 100.0; // Logical pixels
+
+/// The time for which zoom controls (e.g. in a map interface) are to be
+/// displayed on the screen, from the moment they were last requested.
+const Duration kZoomControlsTimeout = Duration(milliseconds: 3000);
+
+/// The distance a touch has to travel for the framework to be confident that
+/// the gesture is a scroll gesture, or, inversely, the maximum distance that a
+/// touch can travel before the framework becomes confident that it is not a
+/// tap.
+///
+/// A total delta less than or equal to [kTouchSlop] is not considered to be a
+/// drag, whereas if the delta is greater than [kTouchSlop] it is considered to
+/// be a drag.
+// This value was empirically derived. We started at 8.0 and increased it to
+// 18.0 after getting complaints that it was too difficult to hit targets.
+const double kTouchSlop = 18.0; // Logical pixels
+
+/// The distance a touch has to travel for the framework to be confident that
+/// the gesture is a paging gesture. (Currently not used, because paging uses a
+/// regular drag gesture, which uses kTouchSlop.)
+// TODO(ianh): Create variants of HorizontalDragGestureRecognizer et al for
+// paging, which use this constant.
+const double kPagingTouchSlop = kTouchSlop * 2.0; // Logical pixels
+
+/// The distance a touch has to travel for the framework to be confident that
+/// the gesture is a panning gesture.
+const double kPanSlop = kTouchSlop * 2.0; // Logical pixels
+
+/// The distance a touch has to travel for the framework to be confident that
+/// the gesture is a scale gesture.
+const double kScaleSlop = kTouchSlop; // Logical pixels
+
+/// The margin around a dialog, popup menu, or other window-like widget inside
+/// which we do not consider a tap to dismiss the widget. (Not currently used.)
+// TODO(ianh): Make ModalBarrier support this.
+const double kWindowTouchSlop = 16.0; // Logical pixels
+
+/// The minimum velocity for a touch to consider that touch to trigger a fling
+/// gesture.
+// TODO(ianh): Make sure nobody has their own version of this.
+const double kMinFlingVelocity = 50.0; // Logical pixels / second
+// const Velocity kMinFlingVelocity = const Velocity(pixelsPerSecond: 50.0);
+
+/// Drag gesture fling velocities are clipped to this value.
+// TODO(ianh): Make sure nobody has their own version of this.
+const double kMaxFlingVelocity = 8000.0; // Logical pixels / second
+
+/// The maximum time from the start of the first tap to the start of the second
+/// tap in a jump-tap gesture.
+// TODO(ianh): Implement jump-tap gestures.
+const Duration kJumpTapTimeout = Duration(milliseconds: 500);
+
+/// Like [kTouchSlop], but for more precise pointers like mice and trackpads.
+const double kPrecisePointerHitSlop = 1.0; // Logical pixels;
+
+/// Like [kPanSlop], but for more precise pointers like mice and trackpads.
+const double kPrecisePointerPanSlop = kPrecisePointerHitSlop * 2.0; // Logical pixels
+
+/// Like [kScaleSlop], but for more precise pointers like mice and trackpads.
+const double kPrecisePointerScaleSlop = kPrecisePointerHitSlop; // Logical pixels
diff --git a/lib/src/gestures/converter.dart b/lib/src/gestures/converter.dart
new file mode 100644
index 0000000..d6c1f18
--- /dev/null
+++ b/lib/src/gestures/converter.dart
@@ -0,0 +1,242 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' as ui show PointerData, PointerChange, PointerSignalKind;
+
+import 'events.dart';
+
+// Add `kPrimaryButton` to [buttons] when a pointer of certain devices is down.
+//
+// TODO(tongmu): This patch is supposed to be done by embedders. Patching it
+// in framework is a workaround before [PointerEventConverter] is moved to embedders.
+// https://github.com/flutter/flutter/issues/30454
+int _synthesiseDownButtons(int buttons, PointerDeviceKind kind) {
+  switch (kind) {
+    case PointerDeviceKind.mouse:
+      return buttons;
+    case PointerDeviceKind.touch:
+    case PointerDeviceKind.stylus:
+    case PointerDeviceKind.invertedStylus:
+      return buttons | kPrimaryButton;
+    case PointerDeviceKind.unknown:
+      // We have no information about the device but we know we never want
+      // buttons to be 0 when the pointer is down.
+      return buttons == 0 ? kPrimaryButton : buttons;
+  }
+}
+
+/// Converts from engine pointer data to framework pointer events.
+///
+/// This takes [PointerDataPacket] objects, as received from the engine via
+/// [dart:ui.PlatformDispatcher.onPointerDataPacket], and converts them to
+/// [PointerEvent] objects.
+class PointerEventConverter {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  PointerEventConverter._();
+
+  /// Expand the given packet of pointer data into a sequence of framework
+  /// pointer events.
+  ///
+  /// The `devicePixelRatio` argument (usually given the value from
+  /// [dart:ui.FlutterView.devicePixelRatio]) is used to convert the incoming data
+  /// from physical coordinates to logical pixels. See the discussion at
+  /// [PointerEvent] for more details on the [PointerEvent] coordinate space.
+  static Iterable<PointerEvent> expand(Iterable<ui.PointerData> data, double devicePixelRatio) sync* {
+    for (final ui.PointerData datum in data) {
+      final Offset position = Offset(datum.physicalX, datum.physicalY) / devicePixelRatio;
+      assert(position != null);
+      final Offset delta = Offset(datum.physicalDeltaX, datum.physicalDeltaY) / devicePixelRatio;
+      final double radiusMinor = _toLogicalPixels(datum.radiusMinor, devicePixelRatio);
+      final double radiusMajor = _toLogicalPixels(datum.radiusMajor, devicePixelRatio);
+      final double radiusMin = _toLogicalPixels(datum.radiusMin, devicePixelRatio);
+      final double radiusMax = _toLogicalPixels(datum.radiusMax, devicePixelRatio);
+      final Duration timeStamp = datum.timeStamp;
+      final PointerDeviceKind kind = datum.kind;
+      assert(datum.change != null);
+      if (datum.signalKind == null || datum.signalKind == ui.PointerSignalKind.none) {
+        switch (datum.change) {
+          case ui.PointerChange.add:
+            yield PointerAddedEvent(
+              timeStamp: timeStamp,
+              kind: kind,
+              device: datum.device,
+              position: position,
+              obscured: datum.obscured,
+              pressureMin: datum.pressureMin,
+              pressureMax: datum.pressureMax,
+              distance: datum.distance,
+              distanceMax: datum.distanceMax,
+              radiusMin: radiusMin,
+              radiusMax: radiusMax,
+              orientation: datum.orientation,
+              tilt: datum.tilt,
+              embedderId: datum.embedderId,
+            );
+            break;
+          case ui.PointerChange.hover:
+            yield PointerHoverEvent(
+              timeStamp: timeStamp,
+              kind: kind,
+              device: datum.device,
+              position: position,
+              delta: delta,
+              buttons: datum.buttons,
+              obscured: datum.obscured,
+              pressureMin: datum.pressureMin,
+              pressureMax: datum.pressureMax,
+              distance: datum.distance,
+              distanceMax: datum.distanceMax,
+              size: datum.size,
+              radiusMajor: radiusMajor,
+              radiusMinor: radiusMinor,
+              radiusMin: radiusMin,
+              radiusMax: radiusMax,
+              orientation: datum.orientation,
+              tilt: datum.tilt,
+              synthesized: datum.synthesized,
+              embedderId: datum.embedderId,
+            );
+            break;
+          case ui.PointerChange.down:
+            yield PointerDownEvent(
+              timeStamp: timeStamp,
+              pointer: datum.pointerIdentifier,
+              kind: kind,
+              device: datum.device,
+              position: position,
+              buttons: _synthesiseDownButtons(datum.buttons, kind),
+              obscured: datum.obscured,
+              pressure: datum.pressure,
+              pressureMin: datum.pressureMin,
+              pressureMax: datum.pressureMax,
+              distanceMax: datum.distanceMax,
+              size: datum.size,
+              radiusMajor: radiusMajor,
+              radiusMinor: radiusMinor,
+              radiusMin: radiusMin,
+              radiusMax: radiusMax,
+              orientation: datum.orientation,
+              tilt: datum.tilt,
+              embedderId: datum.embedderId,
+            );
+            break;
+          case ui.PointerChange.move:
+            yield PointerMoveEvent(
+              timeStamp: timeStamp,
+              pointer: datum.pointerIdentifier,
+              kind: kind,
+              device: datum.device,
+              position: position,
+              delta: delta,
+              buttons: _synthesiseDownButtons(datum.buttons, kind),
+              obscured: datum.obscured,
+              pressure: datum.pressure,
+              pressureMin: datum.pressureMin,
+              pressureMax: datum.pressureMax,
+              distanceMax: datum.distanceMax,
+              size: datum.size,
+              radiusMajor: radiusMajor,
+              radiusMinor: radiusMinor,
+              radiusMin: radiusMin,
+              radiusMax: radiusMax,
+              orientation: datum.orientation,
+              tilt: datum.tilt,
+              platformData: datum.platformData,
+              synthesized: datum.synthesized,
+              embedderId: datum.embedderId,
+            );
+            break;
+          case ui.PointerChange.up:
+            yield PointerUpEvent(
+              timeStamp: timeStamp,
+              pointer: datum.pointerIdentifier,
+              kind: kind,
+              device: datum.device,
+              position: position,
+              buttons: datum.buttons,
+              obscured: datum.obscured,
+              pressure: datum.pressure,
+              pressureMin: datum.pressureMin,
+              pressureMax: datum.pressureMax,
+              distance: datum.distance,
+              distanceMax: datum.distanceMax,
+              size: datum.size,
+              radiusMajor: radiusMajor,
+              radiusMinor: radiusMinor,
+              radiusMin: radiusMin,
+              radiusMax: radiusMax,
+              orientation: datum.orientation,
+              tilt: datum.tilt,
+              embedderId: datum.embedderId,
+            );
+            break;
+          case ui.PointerChange.cancel:
+            yield PointerCancelEvent(
+              timeStamp: timeStamp,
+              pointer: datum.pointerIdentifier,
+              kind: kind,
+              device: datum.device,
+              position: position,
+              buttons: datum.buttons,
+              obscured: datum.obscured,
+              pressureMin: datum.pressureMin,
+              pressureMax: datum.pressureMax,
+              distance: datum.distance,
+              distanceMax: datum.distanceMax,
+              size: datum.size,
+              radiusMajor: radiusMajor,
+              radiusMinor: radiusMinor,
+              radiusMin: radiusMin,
+              radiusMax: radiusMax,
+              orientation: datum.orientation,
+              tilt: datum.tilt,
+              embedderId: datum.embedderId,
+            );
+            break;
+          case ui.PointerChange.remove:
+            yield PointerRemovedEvent(
+              timeStamp: timeStamp,
+              kind: kind,
+              device: datum.device,
+              position: position,
+              obscured: datum.obscured,
+              pressureMin: datum.pressureMin,
+              pressureMax: datum.pressureMax,
+              distanceMax: datum.distanceMax,
+              radiusMin: radiusMin,
+              radiusMax: radiusMax,
+              embedderId: datum.embedderId,
+            );
+            break;
+        }
+      } else {
+        switch (datum.signalKind!) {
+          case ui.PointerSignalKind.scroll:
+            final Offset scrollDelta =
+                Offset(datum.scrollDeltaX, datum.scrollDeltaY) / devicePixelRatio;
+            yield PointerScrollEvent(
+              timeStamp: timeStamp,
+              kind: kind,
+              device: datum.device,
+              position: position,
+              scrollDelta: scrollDelta,
+              embedderId: datum.embedderId,
+            );
+            break;
+          case ui.PointerSignalKind.none:
+            assert(false); // This branch should already have 'none' filtered out.
+            break;
+          case ui.PointerSignalKind.unknown:
+            // Ignore unknown signals.
+            break;
+        }
+      }
+    }
+  }
+
+  static double _toLogicalPixels(double physicalPixels, double devicePixelRatio) => physicalPixels / devicePixelRatio;
+}
diff --git a/lib/src/gestures/debug.dart b/lib/src/gestures/debug.dart
new file mode 100644
index 0000000..c6ef4d9
--- /dev/null
+++ b/lib/src/gestures/debug.dart
@@ -0,0 +1,84 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+// Any changes to this file should be reflected in the debugAssertAllGesturesVarsUnset()
+// function below.
+
+/// Whether to print the results of each hit test to the console.
+///
+/// When this is set, in debug mode, any time a hit test is triggered by the
+/// [GestureBinding] the results are dumped to the console.
+///
+/// This has no effect in release builds.
+bool debugPrintHitTestResults = false;
+
+/// Whether to print the details of each mouse hover event to the console.
+///
+/// When this is set, in debug mode, any time a mouse hover event is triggered
+/// by the [GestureBinding], the results are dumped to the console.
+///
+/// This has no effect in release builds, and only applies to mouse hover
+/// events.
+bool debugPrintMouseHoverEvents = false;
+
+/// Prints information about gesture recognizers and gesture arenas.
+///
+/// This flag only has an effect in debug mode.
+///
+/// See also:
+///
+///  * [GestureArenaManager], the class that manages gesture arenas.
+///  * [debugPrintRecognizerCallbacksTrace], for debugging issues with
+///    gesture recognizers.
+bool debugPrintGestureArenaDiagnostics = false;
+
+/// Logs a message every time a gesture recognizer callback is invoked.
+///
+/// This flag only has an effect in debug mode.
+///
+/// This is specifically used by [GestureRecognizer.invokeCallback]. Gesture
+/// recognizers that do not use this method to invoke callbacks may not honor
+/// the [debugPrintRecognizerCallbacksTrace] flag.
+///
+/// See also:
+///
+///  * [debugPrintGestureArenaDiagnostics], for debugging issues with gesture
+///    arenas.
+bool debugPrintRecognizerCallbacksTrace = false;
+
+/// Whether to print the resampling margin to the console.
+///
+/// When this is set, in debug mode, any time resampling is triggered by the
+/// [GestureBinding] the resampling margin is dumped to the console. The
+/// resampling margin is the delta between the time of the last received
+/// touch event and the current sample time. Positive value indicates that
+/// resampling is effective and the resampling offset can potentially be
+/// reduced for improved latency. Negative value indicates that resampling
+/// is failing and resampling offset needs to be increased for smooth
+/// touch event processing.
+///
+/// This has no effect in release builds.
+bool debugPrintResamplingMargin = false;
+
+/// Returns true if none of the gestures library debug variables have been changed.
+///
+/// This function is used by the test framework to ensure that debug variables
+/// haven't been inadvertently changed.
+///
+/// See [the gestures library](gestures/gestures-library.html) for a complete
+/// list.
+bool debugAssertAllGesturesVarsUnset(String reason) {
+  assert(() {
+    if (debugPrintHitTestResults ||
+        debugPrintGestureArenaDiagnostics ||
+        debugPrintRecognizerCallbacksTrace ||
+        debugPrintResamplingMargin)
+      throw FlutterError(reason);
+    return true;
+  }());
+  return true;
+}
diff --git a/lib/src/gestures/drag.dart b/lib/src/gestures/drag.dart
new file mode 100644
index 0000000..757b9c1
--- /dev/null
+++ b/lib/src/gestures/drag.dart
@@ -0,0 +1,30 @@
+// Copyright 2014 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.
+
+
+import 'drag_details.dart';
+
+/// Interface for objects that receive updates about drags.
+///
+/// This interface is used in various ways. For example,
+/// [MultiDragGestureRecognizer] uses it to update its clients when it
+/// recognizes a gesture. Similarly, the scrolling infrastructure in the widgets
+/// library uses it to notify the [DragScrollActivity] when the user drags the
+/// scrollable.
+abstract class Drag {
+  /// The pointer has moved.
+  void update(DragUpdateDetails details) { }
+
+  /// The pointer is no longer in contact with the screen.
+  ///
+  /// The velocity at which the pointer was moving when it stopped contacting
+  /// the screen is available in the `details`.
+  void end(DragEndDetails details) { }
+
+  /// The input from the pointer is no longer directed towards this receiver.
+  ///
+  /// For example, the user might have been interrupted by a system-modal dialog
+  /// in the middle of the drag.
+  void cancel() { }
+}
diff --git a/lib/src/gestures/drag_details.dart b/lib/src/gestures/drag_details.dart
new file mode 100644
index 0000000..63809fb
--- /dev/null
+++ b/lib/src/gestures/drag_details.dart
@@ -0,0 +1,243 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' show Offset, PointerDeviceKind;
+
+import 'package:flute/foundation.dart';
+
+import 'velocity_tracker.dart';
+
+/// Details object for callbacks that use [GestureDragDownCallback].
+///
+/// See also:
+///
+///  * [DragGestureRecognizer.onDown], which uses [GestureDragDownCallback].
+///  * [DragStartDetails], the details for [GestureDragStartCallback].
+///  * [DragUpdateDetails], the details for [GestureDragUpdateCallback].
+///  * [DragEndDetails], the details for [GestureDragEndCallback].
+class DragDownDetails {
+  /// Creates details for a [GestureDragDownCallback].
+  ///
+  /// The [globalPosition] argument must not be null.
+  DragDownDetails({
+    this.globalPosition = Offset.zero,
+    Offset? localPosition,
+  }) : assert(globalPosition != null),
+       localPosition = localPosition ?? globalPosition;
+
+  /// The global position at which the pointer contacted the screen.
+  ///
+  /// Defaults to the origin if not specified in the constructor.
+  ///
+  /// See also:
+  ///
+  ///  * [localPosition], which is the [globalPosition] transformed to the
+  ///    coordinate space of the event receiver.
+  final Offset globalPosition;
+
+  /// The local position in the coordinate system of the event receiver at
+  /// which the pointer contacted the screen.
+  ///
+  /// Defaults to [globalPosition] if not specified in the constructor.
+  final Offset localPosition;
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'DragDownDetails')}($globalPosition)';
+}
+
+/// Signature for when a pointer has contacted the screen and might begin to
+/// move.
+///
+/// The `details` object provides the position of the touch.
+///
+/// See [DragGestureRecognizer.onDown].
+typedef GestureDragDownCallback = void Function(DragDownDetails details);
+
+/// Details object for callbacks that use [GestureDragStartCallback].
+///
+/// See also:
+///
+///  * [DragGestureRecognizer.onStart], which uses [GestureDragStartCallback].
+///  * [DragDownDetails], the details for [GestureDragDownCallback].
+///  * [DragUpdateDetails], the details for [GestureDragUpdateCallback].
+///  * [DragEndDetails], the details for [GestureDragEndCallback].
+class DragStartDetails {
+  /// Creates details for a [GestureDragStartCallback].
+  ///
+  /// The [globalPosition] argument must not be null.
+  DragStartDetails({
+    this.sourceTimeStamp,
+    this.globalPosition = Offset.zero,
+    Offset? localPosition,
+    this.kind,
+  }) : assert(globalPosition != null),
+       localPosition = localPosition ?? globalPosition;
+
+  /// Recorded timestamp of the source pointer event that triggered the drag
+  /// event.
+  ///
+  /// Could be null if triggered from proxied events such as accessibility.
+  final Duration? sourceTimeStamp;
+
+  /// The global position at which the pointer contacted the screen.
+  ///
+  /// Defaults to the origin if not specified in the constructor.
+  ///
+  /// See also:
+  ///
+  ///  * [localPosition], which is the [globalPosition] transformed to the
+  ///    coordinate space of the event receiver.
+  final Offset globalPosition;
+
+  /// The local position in the coordinate system of the event receiver at
+  /// which the pointer contacted the screen.
+  ///
+  /// Defaults to [globalPosition] if not specified in the constructor.
+  final Offset localPosition;
+
+  /// The kind of the device that initiated the event.
+  final PointerDeviceKind? kind;
+
+  // TODO(ianh): Expose the current position, so that you can have a no-jump
+  // drag even when disambiguating (though of course it would lag the finger
+  // instead).
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'DragStartDetails')}($globalPosition)';
+}
+
+/// Signature for when a pointer has contacted the screen and has begun to move.
+///
+/// The `details` object provides the position of the touch when it first
+/// touched the surface.
+///
+/// See [DragGestureRecognizer.onStart].
+typedef GestureDragStartCallback = void Function(DragStartDetails details);
+
+/// Details object for callbacks that use [GestureDragUpdateCallback].
+///
+/// See also:
+///
+///  * [DragGestureRecognizer.onUpdate], which uses [GestureDragUpdateCallback].
+///  * [DragDownDetails], the details for [GestureDragDownCallback].
+///  * [DragStartDetails], the details for [GestureDragStartCallback].
+///  * [DragEndDetails], the details for [GestureDragEndCallback].
+class DragUpdateDetails {
+  /// Creates details for a [DragUpdateDetails].
+  ///
+  /// The [delta] argument must not be null.
+  ///
+  /// If [primaryDelta] is non-null, then its value must match one of the
+  /// coordinates of [delta] and the other coordinate must be zero.
+  ///
+  /// The [globalPosition] argument must be provided and must not be null.
+  DragUpdateDetails({
+    this.sourceTimeStamp,
+    this.delta = Offset.zero,
+    this.primaryDelta,
+    required this.globalPosition,
+    Offset? localPosition,
+  }) : assert(delta != null),
+       assert(primaryDelta == null
+           || (primaryDelta == delta.dx && delta.dy == 0.0)
+           || (primaryDelta == delta.dy && delta.dx == 0.0)),
+       localPosition = localPosition ?? globalPosition;
+
+  /// Recorded timestamp of the source pointer event that triggered the drag
+  /// event.
+  ///
+  /// Could be null if triggered from proxied events such as accessibility.
+  final Duration? sourceTimeStamp;
+
+  /// The amount the pointer has moved in the coordinate space of the event
+  /// receiver since the previous update.
+  ///
+  /// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g.,
+  /// a horizontal or vertical drag), then this offset contains only the delta
+  /// in that direction (i.e., the coordinate in the other direction is zero).
+  ///
+  /// Defaults to zero if not specified in the constructor.
+  final Offset delta;
+
+  /// The amount the pointer has moved along the primary axis in the coordinate
+  /// space of the event receiver since the previous
+  /// update.
+  ///
+  /// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g.,
+  /// a horizontal or vertical drag), then this value contains the component of
+  /// [delta] along the primary axis (e.g., horizontal or vertical,
+  /// respectively). Otherwise, if the [GestureDragUpdateCallback] is for a
+  /// two-dimensional drag (e.g., a pan), then this value is null.
+  ///
+  /// Defaults to null if not specified in the constructor.
+  final double? primaryDelta;
+
+  /// The pointer's global position when it triggered this update.
+  ///
+  /// See also:
+  ///
+  ///  * [localPosition], which is the [globalPosition] transformed to the
+  ///    coordinate space of the event receiver.
+  final Offset globalPosition;
+
+  /// The local position in the coordinate system of the event receiver at
+  /// which the pointer contacted the screen.
+  ///
+  /// Defaults to [globalPosition] if not specified in the constructor.
+  final Offset localPosition;
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'DragUpdateDetails')}($delta)';
+}
+
+/// Signature for when a pointer that is in contact with the screen and moving
+/// has moved again.
+///
+/// The `details` object provides the position of the touch and the distance it
+/// has traveled since the last update.
+///
+/// See [DragGestureRecognizer.onUpdate].
+typedef GestureDragUpdateCallback = void Function(DragUpdateDetails details);
+
+/// Details object for callbacks that use [GestureDragEndCallback].
+///
+/// See also:
+///
+///  * [DragGestureRecognizer.onEnd], which uses [GestureDragEndCallback].
+///  * [DragDownDetails], the details for [GestureDragDownCallback].
+///  * [DragStartDetails], the details for [GestureDragStartCallback].
+///  * [DragUpdateDetails], the details for [GestureDragUpdateCallback].
+class DragEndDetails {
+  /// Creates details for a [GestureDragEndCallback].
+  ///
+  /// The [velocity] argument must not be null.
+  DragEndDetails({
+    this.velocity = Velocity.zero,
+    this.primaryVelocity,
+  }) : assert(velocity != null),
+       assert(primaryVelocity == null
+           || primaryVelocity == velocity.pixelsPerSecond.dx
+           || primaryVelocity == velocity.pixelsPerSecond.dy);
+
+  /// The velocity the pointer was moving when it stopped contacting the screen.
+  ///
+  /// Defaults to zero if not specified in the constructor.
+  final Velocity velocity;
+
+  /// The velocity the pointer was moving along the primary axis when it stopped
+  /// contacting the screen, in logical pixels per second.
+  ///
+  /// If the [GestureDragEndCallback] is for a one-dimensional drag (e.g., a
+  /// horizontal or vertical drag), then this value contains the component of
+  /// [velocity] along the primary axis (e.g., horizontal or vertical,
+  /// respectively). Otherwise, if the [GestureDragEndCallback] is for a
+  /// two-dimensional drag (e.g., a pan), then this value is null.
+  ///
+  /// Defaults to null if not specified in the constructor.
+  final double? primaryVelocity;
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'DragEndDetails')}($velocity)';
+}
diff --git a/lib/src/gestures/eager.dart b/lib/src/gestures/eager.dart
new file mode 100644
index 0000000..5440f9b
--- /dev/null
+++ b/lib/src/gestures/eager.dart
@@ -0,0 +1,37 @@
+// Copyright 2014 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.
+
+
+import 'arena.dart';
+import 'events.dart';
+import 'recognizer.dart';
+
+/// A gesture recognizer that eagerly claims victory in all gesture arenas.
+///
+/// This is typically passed in [AndroidView.gestureRecognizers] in order to immediately dispatch
+/// all touch events inside the view bounds to the embedded Android view.
+/// See [AndroidView.gestureRecognizers] for more details.
+class EagerGestureRecognizer extends OneSequenceGestureRecognizer {
+  /// Create an eager gesture recognizer.
+  ///
+  /// {@macro flutter.gestures.GestureRecognizer.kind}
+  EagerGestureRecognizer({ PointerDeviceKind? kind }) : super(kind: kind);
+
+  @override
+  void addAllowedPointer(PointerDownEvent event) {
+    // We call startTrackingPointer as this is where OneSequenceGestureRecognizer joins the arena.
+    startTrackingPointer(event.pointer, event.transform);
+    resolve(GestureDisposition.accepted);
+    stopTrackingPointer(event.pointer);
+  }
+
+  @override
+  String get debugDescription => 'eager';
+
+  @override
+  void didStopTrackingLastPointer(int pointer) { }
+
+  @override
+  void handleEvent(PointerEvent event) { }
+}
diff --git a/lib/src/gestures/events.dart b/lib/src/gestures/events.dart
new file mode 100644
index 0000000..d3cac85
--- /dev/null
+++ b/lib/src/gestures/events.dart
@@ -0,0 +1,2166 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' show Offset, PointerDeviceKind;
+
+import 'package:flute/foundation.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'constants.dart';
+
+export 'package:flute/ui.dart' show Offset, PointerDeviceKind;
+
+/// The bit of [PointerEvent.buttons] that corresponds to a cross-device
+/// behavior of "primary operation".
+///
+/// More specifically, it includes:
+///
+///  * [kTouchContact]: The pointer contacts the touch screen.
+///  * [kStylusContact]: The stylus contacts the screen.
+///  * [kPrimaryMouseButton]: The primary mouse button.
+///
+/// See also:
+///
+///  * [kSecondaryButton], which describes a cross-device behavior of
+///    "secondary operation".
+///  * [kTertiaryButton], which describes a cross-device behavior of
+///    "tertiary operation".
+const int kPrimaryButton = 0x01;
+
+/// The bit of [PointerEvent.buttons] that corresponds to a cross-device
+/// behavior of "secondary operation".
+///
+/// It is equivalent to:
+///
+///  * [kPrimaryStylusButton]: The stylus contacts the screen.
+///  * [kSecondaryMouseButton]: The secondary mouse button.
+///
+/// See also:
+///
+///  * [kPrimaryButton], which describes a cross-device behavior of
+///    "primary operation".
+///  * [kTertiaryButton], which describes a cross-device behavior of
+///    "tertiary operation".
+const int kSecondaryButton = 0x02;
+
+/// The bit of [PointerEvent.buttons] that corresponds to the primary mouse button.
+///
+/// The primary mouse button is typically the left button on the top of the
+/// mouse but can be reconfigured to be a different physical button.
+///
+/// See also:
+///
+///  * [kPrimaryButton], which has the same value but describes its cross-device
+///    concept.
+const int kPrimaryMouseButton = kPrimaryButton;
+
+/// The bit of [PointerEvent.buttons] that corresponds to the secondary mouse button.
+///
+/// The secondary mouse button is typically the right button on the top of the
+/// mouse but can be reconfigured to be a different physical button.
+///
+/// See also:
+///
+///  * [kSecondaryButton], which has the same value but describes its cross-device
+///    concept.
+const int kSecondaryMouseButton = kSecondaryButton;
+
+/// The bit of [PointerEvent.buttons] that corresponds to when a stylus
+/// contacting the screen.
+///
+/// See also:
+///
+///  * [kPrimaryButton], which has the same value but describes its cross-device
+///    concept.
+const int kStylusContact = kPrimaryButton;
+
+/// The bit of [PointerEvent.buttons] that corresponds to the primary stylus button.
+///
+/// The primary stylus button is typically the top of the stylus and near the
+/// tip but can be reconfigured to be a different physical button.
+///
+/// See also:
+///
+///  * [kSecondaryButton], which has the same value but describes its cross-device
+///    concept.
+const int kPrimaryStylusButton = kSecondaryButton;
+
+/// The bit of [PointerEvent.buttons] that corresponds to a cross-device
+/// behavior of "tertiary operation".
+///
+/// It is equivalent to:
+///
+///  * [kMiddleMouseButton]: The tertiary mouseButton.
+///  * [kSecondaryStylusButton]: The secondary button on a stylus. This is considered
+///    a tertiary button as the primary button of a stylus already corresponds to a
+///    "secondary operation" (where stylus contact is the primary operation).
+///
+/// See also:
+///
+///  * [kPrimaryButton], which describes a cross-device behavior of
+///    "primary operation".
+///  * [kSecondaryButton], which describes a cross-device behavior of
+///    "secondary operation".
+const int kTertiaryButton = 0x04;
+
+/// The bit of [PointerEvent.buttons] that corresponds to the middle mouse button.
+///
+/// The middle mouse button is typically between the left and right buttons on
+/// the top of the mouse but can be reconfigured to be a different physical
+/// button.
+///
+/// See also:
+///
+///  * [kTertiaryButton], which has the same value but describes its cross-device
+///    concept.
+const int kMiddleMouseButton = kTertiaryButton;
+
+/// The bit of [PointerEvent.buttons] that corresponds to the secondary stylus button.
+///
+/// The secondary stylus button is typically on the end of the stylus farthest
+/// from the tip but can be reconfigured to be a different physical button.
+///
+/// See also:
+///
+///  * [kTertiaryButton], which has the same value but describes its cross-device
+///    concept.
+const int kSecondaryStylusButton = kTertiaryButton;
+
+/// The bit of [PointerEvent.buttons] that corresponds to the back mouse button.
+///
+/// The back mouse button is typically on the left side of the mouse but can be
+/// reconfigured to be a different physical button.
+const int kBackMouseButton = 0x08;
+
+/// The bit of [PointerEvent.buttons] that corresponds to the forward mouse button.
+///
+/// The forward mouse button is typically on the right side of the mouse but can
+/// be reconfigured to be a different physical button.
+const int kForwardMouseButton = 0x10;
+
+/// The bit of [PointerEvent.buttons] that corresponds to the pointer contacting
+/// a touch screen.
+///
+/// See also:
+///
+///  * [kPrimaryButton], which has the same value but describes its cross-device
+///    concept.
+const int kTouchContact = kPrimaryButton;
+
+/// The bit of [PointerEvent.buttons] that corresponds to the nth mouse button.
+///
+/// The `number` argument can be at most 62 in Flutter for mobile and desktop,
+/// and at most 32 on Flutter for web.
+///
+/// See [kPrimaryMouseButton], [kSecondaryMouseButton], [kMiddleMouseButton],
+/// [kBackMouseButton], and [kForwardMouseButton] for semantic names for some
+/// mouse buttons.
+int nthMouseButton(int number) => (kPrimaryMouseButton << (number - 1)) & kMaxUnsignedSMI;
+
+/// The bit of [PointerEvent.buttons] that corresponds to the nth stylus button.
+///
+/// The `number` argument can be at most 62 in Flutter for mobile and desktop,
+/// and at most 32 on Flutter for web.
+///
+/// See [kPrimaryStylusButton] and [kSecondaryStylusButton] for semantic names
+/// for some stylus buttons.
+int nthStylusButton(int number) => (kPrimaryStylusButton << (number - 1)) & kMaxUnsignedSMI;
+
+/// Returns the button of `buttons` with the smallest integer.
+///
+/// The `buttons` parameter is a bit field where each set bit represents a button.
+/// This function returns the set bit closest to the least significant bit.
+///
+/// It returns zero when `buttons` is zero.
+///
+/// Example:
+///
+/// ```dart
+///   assert(rightmostButton(0x1) == 0x1);
+///   assert(rightmostButton(0x11) == 0x1);
+///   assert(rightmostButton(0) == 0);
+/// ```
+///
+/// See also:
+///
+///  * [isSingleButton], which checks if a `buttons` contains exactly one button.
+int smallestButton(int buttons) => buttons & (-buttons);
+
+/// Returns whether `buttons` contains one and only one button.
+///
+/// The `buttons` parameter is a bit field where each set bit represents a button.
+/// This function returns whether there is only one set bit in the given integer.
+///
+/// It returns false when `buttons` is zero.
+///
+/// Example:
+///
+/// ```dart
+///   assert(isSingleButton(0x1) == true);
+///   assert(isSingleButton(0x11) == false);
+///   assert(isSingleButton(0) == false);
+/// ```
+///
+/// See also:
+///
+///  * [smallestButton], which returns the button in a `buttons` bit field with
+///    the smallest integer button.
+bool isSingleButton(int buttons) => buttons != 0 && (smallestButton(buttons) == buttons);
+
+/// Base class for touch, stylus, or mouse events.
+///
+/// Pointer events operate in the coordinate space of the screen, scaled to
+/// logical pixels. Logical pixels approximate a grid with about 38 pixels per
+/// centimeter, or 96 pixels per inch.
+///
+/// This allows gestures to be recognized independent of the precise hardware
+/// characteristics of the device. In particular, features such as touch slop
+/// (see [kTouchSlop]) can be defined in terms of roughly physical lengths so
+/// that the user can shift their finger by the same distance on a high-density
+/// display as on a low-resolution device.
+///
+/// For similar reasons, pointer events are not affected by any transforms in
+/// the rendering layer. This means that deltas may need to be scaled before
+/// being applied to movement within the rendering. For example, if a scrolling
+/// list is shown scaled by 2x, the pointer deltas will have to be scaled by the
+/// inverse amount if the list is to appear to scroll with the user's finger.
+///
+/// See also:
+///
+///  * [dart:ui.FlutterView.devicePixelRatio], which defines the device's
+///    current resolution.
+///  * [Listener], a widget that calls callbacks in response to common pointer
+///    events.
+@immutable
+abstract class PointerEvent with Diagnosticable {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const PointerEvent({
+    this.embedderId = 0,
+    this.timeStamp = Duration.zero,
+    this.pointer = 0,
+    this.kind = PointerDeviceKind.touch,
+    this.device = 0,
+    this.position = Offset.zero,
+    this.delta = Offset.zero,
+    this.buttons = 0,
+    this.down = false,
+    this.obscured = false,
+    this.pressure = 1.0,
+    this.pressureMin = 1.0,
+    this.pressureMax = 1.0,
+    this.distance = 0.0,
+    this.distanceMax = 0.0,
+    this.size = 0.0,
+    this.radiusMajor = 0.0,
+    this.radiusMinor = 0.0,
+    this.radiusMin = 0.0,
+    this.radiusMax = 0.0,
+    this.orientation = 0.0,
+    this.tilt = 0.0,
+    this.platformData = 0,
+    this.synthesized = false,
+    this.transform,
+    this.original,
+  });
+
+  /// Unique identifier that ties the [PointerEvent] to the embedder event that created it.
+  ///
+  /// No two pointer events can have the same [embedderId] on platforms that set it.
+  /// This is different from [pointer] identifier - used for hit-testing,
+  /// whereas [embedderId] is used to identify the platform event.
+  ///
+  /// On Android this is ID of the underlying [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent).
+  final int embedderId;
+
+  /// Time of event dispatch, relative to an arbitrary timeline.
+  final Duration timeStamp;
+
+  /// Unique identifier for the pointer, not reused. Changes for each new
+  /// pointer down event.
+  final int pointer;
+
+  /// The kind of input device for which the event was generated.
+  final PointerDeviceKind kind;
+
+  /// Unique identifier for the pointing device, reused across interactions.
+  final int device;
+
+  /// Coordinate of the position of the pointer, in logical pixels in the global
+  /// coordinate space.
+  ///
+  /// See also:
+  ///
+  ///  * [localPosition], which is the [position] transformed into the local
+  ///    coordinate system of the event receiver.
+  final Offset position;
+
+  /// The [position] transformed into the event receiver's local coordinate
+  /// system according to [transform].
+  ///
+  /// If this event has not been transformed, [position] is returned as-is.
+  /// See also:
+  ///
+  ///  * [position], which is the position in the global coordinate system of
+  ///    the screen.
+  Offset get localPosition => position;
+
+  /// Distance in logical pixels that the pointer moved since the last
+  /// [PointerMoveEvent] or [PointerHoverEvent].
+  ///
+  /// This value is always 0.0 for down, up, and cancel events.
+  ///
+  /// See also:
+  ///
+  ///  * [localDelta], which is the [delta] transformed into the local
+  ///    coordinate space of the event receiver.
+  final Offset delta;
+
+  /// The [delta] transformed into the event receiver's local coordinate
+  /// system according to [transform].
+  ///
+  /// If this event has not been transformed, [delta] is returned as-is.
+  ///
+  /// See also:
+  ///
+  ///  * [delta], which is the distance the pointer moved in the global
+  ///    coordinate system of the screen.
+  Offset get localDelta => delta;
+
+  /// Bit field using the *Button constants such as [kPrimaryMouseButton],
+  /// [kSecondaryStylusButton], etc.
+  ///
+  /// For example, if this has the value 6 and the
+  /// [kind] is [PointerDeviceKind.invertedStylus], then this indicates an
+  /// upside-down stylus with both its primary and secondary buttons pressed.
+  final int buttons;
+
+  /// Set if the pointer is currently down.
+  ///
+  /// For touch and stylus pointers, this means the object (finger, pen) is in
+  /// contact with the input surface. For mice, it means a button is pressed.
+  final bool down;
+
+  /// Set if an application from a different security domain is in any way
+  /// obscuring this application's window.
+  ///
+  /// This is not currently implemented.
+  final bool obscured;
+
+  /// The pressure of the touch.
+  ///
+  /// This value is a number ranging from 0.0, indicating a touch with no
+  /// discernible pressure, to 1.0, indicating a touch with "normal" pressure,
+  /// and possibly beyond, indicating a stronger touch. For devices that do not
+  /// detect pressure (e.g. mice), returns 1.0.
+  final double pressure;
+
+  /// The minimum value that [pressure] can return for this pointer.
+  ///
+  /// For devices that do not detect pressure (e.g. mice), returns 1.0.
+  /// This will always be a number less than or equal to 1.0.
+  final double pressureMin;
+
+  /// The maximum value that [pressure] can return for this pointer.
+  ///
+  /// For devices that do not detect pressure (e.g. mice), returns 1.0.
+  /// This will always be a greater than or equal to 1.0.
+  final double pressureMax;
+
+  /// The distance of the detected object from the input surface.
+  ///
+  /// For instance, this value could be the distance of a stylus or finger
+  /// from a touch screen, in arbitrary units on an arbitrary (not necessarily
+  /// linear) scale. If the pointer is down, this is 0.0 by definition.
+  final double distance;
+
+  /// The minimum value that [distance] can return for this pointer.
+  ///
+  /// This value is always 0.0.
+  double get distanceMin => 0.0;
+
+  /// The maximum value that [distance] can return for this pointer.
+  ///
+  /// If this input device cannot detect "hover touch" input events,
+  /// then this will be 0.0.
+  final double distanceMax;
+
+  /// The area of the screen being pressed.
+  ///
+  /// This value is scaled to a range between 0 and 1. It can be used to
+  /// determine fat touch events. This value is only set on Android and is
+  /// a device specific approximation within the range of detectable values.
+  /// So, for example, the value of 0.1 could mean a touch with the tip of
+  /// the finger, 0.2 a touch with full finger, and 0.3 the full palm.
+  ///
+  /// Because this value uses device-specific range and is uncalibrated,
+  /// it is of limited use and is primarily retained in order to be able
+  /// to reconstruct original pointer events for [AndroidView].
+  final double size;
+
+  /// The radius of the contact ellipse along the major axis, in logical pixels.
+  final double radiusMajor;
+
+  /// The radius of the contact ellipse along the minor axis, in logical pixels.
+  final double radiusMinor;
+
+  /// The minimum value that could be reported for [radiusMajor] and [radiusMinor]
+  /// for this pointer, in logical pixels.
+  final double radiusMin;
+
+  /// The maximum value that could be reported for [radiusMajor] and [radiusMinor]
+  /// for this pointer, in logical pixels.
+  final double radiusMax;
+
+  /// The orientation angle of the detected object, in radians.
+  ///
+  /// For [PointerDeviceKind.touch] events:
+  ///
+  /// The angle of the contact ellipse, in radians in the range:
+  ///
+  ///     -pi/2 < orientation <= pi/2
+  ///
+  /// ...giving the angle of the major axis of the ellipse with the y-axis
+  /// (negative angles indicating an orientation along the top-left /
+  /// bottom-right diagonal, positive angles indicating an orientation along the
+  /// top-right / bottom-left diagonal, and zero indicating an orientation
+  /// parallel with the y-axis).
+  ///
+  /// For [PointerDeviceKind.stylus] and [PointerDeviceKind.invertedStylus] events:
+  ///
+  /// The angle of the stylus, in radians in the range:
+  ///
+  ///     -pi < orientation <= pi
+  ///
+  /// ...giving the angle of the axis of the stylus projected onto the input
+  /// surface, relative to the positive y-axis of that surface (thus 0.0
+  /// indicates the stylus, if projected onto that surface, would go from the
+  /// contact point vertically up in the positive y-axis direction, pi would
+  /// indicate that the stylus would go down in the negative y-axis direction;
+  /// pi/4 would indicate that the stylus goes up and to the right, -pi/2 would
+  /// indicate that the stylus goes to the left, etc).
+  final double orientation;
+
+  /// The tilt angle of the detected object, in radians.
+  ///
+  /// For [PointerDeviceKind.stylus] and [PointerDeviceKind.invertedStylus] events:
+  ///
+  /// The angle of the stylus, in radians in the range:
+  ///
+  ///     0 <= tilt <= pi/2
+  ///
+  /// ...giving the angle of the axis of the stylus, relative to the axis
+  /// perpendicular to the input surface (thus 0.0 indicates the stylus is
+  /// orthogonal to the plane of the input surface, while pi/2 indicates that
+  /// the stylus is flat on that surface).
+  final double tilt;
+
+  /// Opaque platform-specific data associated with the event.
+  final int platformData;
+
+  /// Set if the event was synthesized by Flutter.
+  ///
+  /// We occasionally synthesize PointerEvents that aren't exact translations
+  /// of [PointerData] from the engine to cover small cross-OS discrepancies
+  /// in pointer behaviors.
+  ///
+  /// For instance, on end events, Android always drops any location changes
+  /// that happened between its reporting intervals when emitting the end events.
+  ///
+  /// On iOS, minor incorrect location changes from the previous move events
+  /// can be reported on end events. We synthesize a [PointerEvent] to cover
+  /// the difference between the 2 events in that case.
+  final bool synthesized;
+
+  /// The transformation used to transform this event from the global coordinate
+  /// space into the coordinate space of the event receiver.
+  ///
+  /// This value affects what is returned by [localPosition] and [localDelta].
+  /// If this value is null, it is treated as the identity transformation.
+  ///
+  /// Unlike a paint transform, this transform usually does not contain any
+  /// "perspective" components, meaning that the third row and the third column
+  /// of the matrix should be equal to "0, 0, 1, 0". This ensures that
+  /// [localPosition] describes the point in the local coordinate system of the
+  /// event receiver at which the user is actually touching the screen.
+  ///
+  /// See also:
+  ///
+  ///  * [transformed], which transforms this event into a different coordinate
+  ///    space.
+  final Matrix4? transform;
+
+  /// The original un-transformed [PointerEvent] before any [transform]s were
+  /// applied.
+  ///
+  /// If [transform] is null or the identity transformation this may be null.
+  ///
+  /// When multiple event receivers in different coordinate spaces receive an
+  /// event, they all receive the event transformed to their local coordinate
+  /// space. The [original] property can be used to determine if all those
+  /// transformed events actually originated from the same pointer interaction.
+  final PointerEvent? original;
+
+  /// Transforms the event from the global coordinate space into the coordinate
+  /// space of an event receiver.
+  ///
+  /// The coordinate space of the event receiver is described by `transform`. A
+  /// null value for `transform` is treated as the identity transformation.
+  ///
+  /// The resulting event will store the base event as [original], delegates
+  /// most properties to [original], except for [localPosition] and [localDelta],
+  /// which are calculated based on [transform] on first use and cached.
+  ///
+  /// The method may return the same object instance if for example the
+  /// transformation has no effect on the event. Otherwise, the resulting event
+  /// will be a subclass of, but not exactly, the original event class (e.g.
+  /// [PointerDownEvent.transformed] may return a subclass of [PointerDownEvent]).
+  ///
+  /// Transforms are not commutative, and are based on [original] events.
+  /// If this method is called on a transformed event, the provided `transform`
+  /// will override (instead of multiplied onto) the existing [transform] and
+  /// used to calculate the new [localPosition] and [localDelta].
+  PointerEvent transformed(Matrix4? transform);
+
+  /// Creates a copy of event with the specified properties replaced.
+  ///
+  /// Calling this method on a transformed event will return a new transformed
+  /// event based on the current [transform] and the provided properties.
+  PointerEvent copyWith({
+    Duration? timeStamp,
+    int? pointer,
+    PointerDeviceKind? kind,
+    int? device,
+    Offset? position,
+    Offset? delta,
+    int? buttons,
+    bool? obscured,
+    double? pressure,
+    double? pressureMin,
+    double? pressureMax,
+    double? distance,
+    double? distanceMax,
+    double? size,
+    double? radiusMajor,
+    double? radiusMinor,
+    double? radiusMin,
+    double? radiusMax,
+    double? orientation,
+    double? tilt,
+    bool? synthesized,
+    int? embedderId,
+  });
+
+  /// Returns the transformation of `position` into the coordinate system
+  /// described by `transform`.
+  ///
+  /// The z-value of `position` is assumed to be 0.0. If `transform` is null,
+  /// `position` is returned as-is.
+  static Offset transformPosition(Matrix4? transform, Offset position) {
+    if (transform == null) {
+      return position;
+    }
+    final Vector3 position3 = Vector3(position.dx, position.dy, 0.0);
+    final Vector3 transformed3 = transform.perspectiveTransform(position3);
+    return Offset(transformed3.x, transformed3.y);
+  }
+
+  /// Transforms `untransformedDelta` into the coordinate system described by
+  /// `transform`.
+  ///
+  /// It uses the provided `untransformedEndPosition` and
+  /// `transformedEndPosition` of the provided delta to increase accuracy.
+  ///
+  /// If `transform` is null, `untransformedDelta` is returned.
+  static Offset transformDeltaViaPositions({
+    required Offset untransformedEndPosition,
+    Offset? transformedEndPosition,
+    required Offset untransformedDelta,
+    required Matrix4? transform,
+  }) {
+    if (transform == null) {
+      return untransformedDelta;
+    }
+    // We could transform the delta directly with the transformation matrix.
+    // While that is mathematically equivalent, in practice we are seeing a
+    // greater precision error with that approach. Instead, we are transforming
+    // start and end point of the delta separately and calculate the delta in
+    // the new space for greater accuracy.
+    transformedEndPosition ??= transformPosition(transform, untransformedEndPosition);
+    final Offset transformedStartPosition = transformPosition(transform, untransformedEndPosition - untransformedDelta);
+    return transformedEndPosition - transformedStartPosition;
+  }
+
+  /// Removes the "perspective" component from `transform`.
+  ///
+  /// When applying the resulting transform matrix to a point with a
+  /// z-coordinate of zero (which is generally assumed for all points
+  /// represented by an [Offset]), the other coordinates will get transformed as
+  /// before, but the new z-coordinate is going to be zero again. This is
+  /// achieved by setting the third column and third row of the matrix to
+  /// "0, 0, 1, 0".
+  static Matrix4 removePerspectiveTransform(Matrix4 transform) {
+    final Vector4 vector = Vector4(0, 0, 1, 0);
+    return transform.clone()
+      ..setColumn(2, vector)
+      ..setRow(2, vector);
+  }
+}
+
+// A mixin that adds implementation for [debugFillProperties] and [toStringFull]
+// to [PointerEvent].
+mixin _PointerEventDescription on PointerEvent {
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Offset>('position', position));
+    properties.add(DiagnosticsProperty<Offset>('localPosition', localPosition, defaultValue: position, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<Offset>('delta', delta, defaultValue: Offset.zero, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<Offset>('localDelta', localDelta, defaultValue: delta, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<Duration>('timeStamp', timeStamp, defaultValue: Duration.zero, level: DiagnosticLevel.debug));
+    properties.add(IntProperty('pointer', pointer, level: DiagnosticLevel.debug));
+    properties.add(EnumProperty<PointerDeviceKind>('kind', kind, level: DiagnosticLevel.debug));
+    properties.add(IntProperty('device', device, defaultValue: 0, level: DiagnosticLevel.debug));
+    properties.add(IntProperty('buttons', buttons, defaultValue: 0, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<bool>('down', down, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('pressure', pressure, defaultValue: 1.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('pressureMin', pressureMin, defaultValue: 1.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('pressureMax', pressureMax, defaultValue: 1.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('distance', distance, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('distanceMin', distanceMin, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('distanceMax', distanceMax, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('size', size, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('radiusMajor', radiusMajor, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('radiusMinor', radiusMinor, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('radiusMin', radiusMin, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('radiusMax', radiusMax, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('orientation', orientation, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(DoubleProperty('tilt', tilt, defaultValue: 0.0, level: DiagnosticLevel.debug));
+    properties.add(IntProperty('platformData', platformData, defaultValue: 0, level: DiagnosticLevel.debug));
+    properties.add(FlagProperty('obscured', value: obscured, ifTrue: 'obscured', level: DiagnosticLevel.debug));
+    properties.add(FlagProperty('synthesized', value: synthesized, ifTrue: 'synthesized', level: DiagnosticLevel.debug));
+    properties.add(IntProperty('embedderId', embedderId, defaultValue: 0, level: DiagnosticLevel.debug));
+  }
+
+  /// Returns a complete textual description of this event.
+  String toStringFull() {
+    return toString(minLevel: DiagnosticLevel.fine);
+  }
+}
+
+abstract class _AbstractPointerEvent implements PointerEvent {}
+
+// The base class for transformed pointer event classes.
+//
+// A _TransformedPointerEvent stores an [original] event and the [transform]
+// matrix. It defers all field getters to the original event, except for
+// [localPosition] and [localDelta], which are calculated when first used.
+abstract class _TransformedPointerEvent extends _AbstractPointerEvent with Diagnosticable, _PointerEventDescription {
+
+  @override
+  PointerEvent get original;
+
+  @override
+  Matrix4 get transform;
+
+  @override
+  int get embedderId => original.embedderId;
+
+  @override
+  Duration get timeStamp => original.timeStamp;
+
+  @override
+  int get pointer => original.pointer;
+
+  @override
+  PointerDeviceKind get kind => original.kind;
+
+  @override
+  int get device => original.device;
+
+  @override
+  Offset get position => original.position;
+
+  @override
+  Offset get delta => original.delta;
+
+  @override
+  int get buttons => original.buttons;
+
+  @override
+  bool get down => original.down;
+
+  @override
+  bool get obscured => original.obscured;
+
+  @override
+  double get pressure => original.pressure;
+
+  @override
+  double get pressureMin => original.pressureMin;
+
+  @override
+  double get pressureMax => original.pressureMax;
+
+  @override
+  double get distance => original.distance;
+
+  @override
+  double get distanceMin => 0.0;
+
+  @override
+  double get distanceMax => original.distanceMax;
+
+  @override
+  double get size => original.size;
+
+  @override
+  double get radiusMajor => original.radiusMajor;
+
+  @override
+  double get radiusMinor => original.radiusMinor;
+
+  @override
+  double get radiusMin => original.radiusMin;
+
+  @override
+  double get radiusMax => original.radiusMax;
+
+  @override
+  double get orientation => original.orientation;
+
+  @override
+  double get tilt => original.tilt;
+
+  @override
+  int get platformData => original.platformData;
+
+  @override
+  bool get synthesized => original.synthesized;
+
+  @override
+  late final Offset localPosition = PointerEvent.transformPosition(transform, position);
+
+  @override
+  late final Offset localDelta = PointerEvent.transformDeltaViaPositions(
+    transform: transform,
+    untransformedDelta: delta,
+    untransformedEndPosition: position,
+    transformedEndPosition: localPosition,
+  );
+}
+
+mixin _CopyPointerAddedEvent on PointerEvent {
+  @override
+  PointerAddedEvent copyWith({
+    Duration? timeStamp,
+    int? pointer,
+    PointerDeviceKind? kind,
+    int? device,
+    Offset? position,
+    Offset? delta,
+    int? buttons,
+    bool? obscured,
+    double? pressure,
+    double? pressureMin,
+    double? pressureMax,
+    double? distance,
+    double? distanceMax,
+    double? size,
+    double? radiusMajor,
+    double? radiusMinor,
+    double? radiusMin,
+    double? radiusMax,
+    double? orientation,
+    double? tilt,
+    bool? synthesized,
+    int? embedderId,
+  }) {
+    return PointerAddedEvent(
+      timeStamp: timeStamp ?? this.timeStamp,
+      kind: kind ?? this.kind,
+      device: device ?? this.device,
+      position: position ?? this.position,
+      obscured: obscured ?? this.obscured,
+      pressureMin: pressureMin ?? this.pressureMin,
+      pressureMax: pressureMax ?? this.pressureMax,
+      distance: distance ?? this.distance,
+      distanceMax: distanceMax ?? this.distanceMax,
+      radiusMin: radiusMin ?? this.radiusMin,
+      radiusMax: radiusMax ?? this.radiusMax,
+      orientation: orientation ?? this.orientation,
+      tilt: tilt ?? this.tilt,
+      embedderId: embedderId ?? this.embedderId,
+    ).transformed(transform);
+  }
+}
+
+/// The device has started tracking the pointer.
+///
+/// For example, the pointer might be hovering above the device, having not yet
+/// made contact with the surface of the device.
+class PointerAddedEvent extends PointerEvent with _PointerEventDescription, _CopyPointerAddedEvent {
+  /// Creates a pointer added event.
+  ///
+  /// All of the arguments must be non-null.
+  const PointerAddedEvent({
+    Duration timeStamp = Duration.zero,
+    int pointer = 0,
+    PointerDeviceKind kind = PointerDeviceKind.touch,
+    int device = 0,
+    Offset position = Offset.zero,
+    bool obscured = false,
+    double pressureMin = 1.0,
+    double pressureMax = 1.0,
+    double distance = 0.0,
+    double distanceMax = 0.0,
+    double radiusMin = 0.0,
+    double radiusMax = 0.0,
+    double orientation = 0.0,
+    double tilt = 0.0,
+    int embedderId = 0,
+  }) : super(
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+         obscured: obscured,
+         pressure: 0.0,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: distance,
+         distanceMax: distanceMax,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+         embedderId: embedderId,
+       );
+
+  @override
+  PointerAddedEvent transformed(Matrix4? transform) {
+    if (transform == null || transform == this.transform) {
+      return this;
+    }
+    return _TransformedPointerAddedEvent(original as PointerAddedEvent? ?? this, transform);
+  }
+}
+
+class _TransformedPointerAddedEvent extends _TransformedPointerEvent with _CopyPointerAddedEvent implements PointerAddedEvent {
+  _TransformedPointerAddedEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
+
+  @override
+  final PointerAddedEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerAddedEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+mixin _CopyPointerRemovedEvent on PointerEvent {
+  @override
+  PointerRemovedEvent copyWith({
+    Duration? timeStamp,
+    int? pointer,
+    PointerDeviceKind? kind,
+    int? device,
+    Offset? position,
+    Offset? delta,
+    int? buttons,
+    bool? obscured,
+    double? pressure,
+    double? pressureMin,
+    double? pressureMax,
+    double? distance,
+    double? distanceMax,
+    double? size,
+    double? radiusMajor,
+    double? radiusMinor,
+    double? radiusMin,
+    double? radiusMax,
+    double? orientation,
+    double? tilt,
+    bool? synthesized,
+    int? embedderId,
+  }) {
+    return PointerRemovedEvent(
+      timeStamp: timeStamp ?? this.timeStamp,
+      kind: kind ?? this.kind,
+      device: device ?? this.device,
+      position: position ?? this.position,
+      obscured: obscured ?? this.obscured,
+      pressureMin: pressureMin ?? this.pressureMin,
+      pressureMax: pressureMax ?? this.pressureMax,
+      distanceMax: distanceMax ?? this.distanceMax,
+      radiusMin: radiusMin ?? this.radiusMin,
+      radiusMax: radiusMax ?? this.radiusMax,
+      embedderId: embedderId ?? this.embedderId,
+    ).transformed(transform);
+  }
+}
+
+/// The device is no longer tracking the pointer.
+///
+/// For example, the pointer might have drifted out of the device's hover
+/// detection range or might have been disconnected from the system entirely.
+class PointerRemovedEvent extends PointerEvent with _PointerEventDescription, _CopyPointerRemovedEvent {
+  /// Creates a pointer removed event.
+  ///
+  /// All of the arguments must be non-null.
+  const PointerRemovedEvent({
+    Duration timeStamp = Duration.zero,
+    int pointer = 0,
+    PointerDeviceKind kind = PointerDeviceKind.touch,
+    int device = 0,
+    Offset position = Offset.zero,
+    bool obscured = false,
+    double pressureMin = 1.0,
+    double pressureMax = 1.0,
+    double distanceMax = 0.0,
+    double radiusMin = 0.0,
+    double radiusMax = 0.0,
+    PointerRemovedEvent? original,
+    int embedderId = 0,
+  }) : super(
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+         obscured: obscured,
+         pressure: 0.0,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distanceMax: distanceMax,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         original: original,
+         embedderId: embedderId,
+       );
+
+  @override
+  PointerRemovedEvent transformed(Matrix4? transform) {
+    if (transform == null || transform == this.transform) {
+      return this;
+    }
+    return _TransformedPointerRemovedEvent(original as PointerRemovedEvent? ?? this, transform);
+  }
+}
+
+class _TransformedPointerRemovedEvent extends _TransformedPointerEvent with _CopyPointerRemovedEvent implements PointerRemovedEvent {
+  _TransformedPointerRemovedEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
+
+  @override
+  final PointerRemovedEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerRemovedEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+mixin _CopyPointerHoverEvent on PointerEvent {
+  @override
+  PointerHoverEvent copyWith({
+    Duration? timeStamp,
+    int? pointer,
+    PointerDeviceKind? kind,
+    int? device,
+    Offset? position,
+    Offset? delta,
+    int? buttons,
+    bool? obscured,
+    double? pressure,
+    double? pressureMin,
+    double? pressureMax,
+    double? distance,
+    double? distanceMax,
+    double? size,
+    double? radiusMajor,
+    double? radiusMinor,
+    double? radiusMin,
+    double? radiusMax,
+    double? orientation,
+    double? tilt,
+    bool? synthesized,
+    int? embedderId,
+  }) {
+    return PointerHoverEvent(
+      timeStamp: timeStamp ?? this.timeStamp,
+      kind: kind ?? this.kind,
+      device: device ?? this.device,
+      position: position ?? this.position,
+      delta: delta ?? this.delta,
+      buttons: buttons ?? this.buttons,
+      obscured: obscured ?? this.obscured,
+      pressureMin: pressureMin ?? this.pressureMin,
+      pressureMax: pressureMax ?? this.pressureMax,
+      distance: distance ?? this.distance,
+      distanceMax: distanceMax ?? this.distanceMax,
+      size: size ?? this.size,
+      radiusMajor: radiusMajor ?? this.radiusMajor,
+      radiusMinor: radiusMinor ?? this.radiusMinor,
+      radiusMin: radiusMin ?? this.radiusMin,
+      radiusMax: radiusMax ?? this.radiusMax,
+      orientation: orientation ?? this.orientation,
+      tilt: tilt ?? this.tilt,
+      synthesized: synthesized ?? this.synthesized,
+      embedderId: embedderId ?? this.embedderId,
+    ).transformed(transform);
+  }
+}
+
+/// The pointer has moved with respect to the device while the pointer is not
+/// in contact with the device.
+///
+/// See also:
+///
+///  * [PointerEnterEvent], which reports when the pointer has entered an
+///    object.
+///  * [PointerExitEvent], which reports when the pointer has left an object.
+///  * [PointerMoveEvent], which reports movement while the pointer is in
+///    contact with the device.
+///  * [Listener.onPointerHover], which allows callers to be notified of these
+///    events in a widget tree.
+class PointerHoverEvent extends PointerEvent with _PointerEventDescription, _CopyPointerHoverEvent {
+  /// Creates a pointer hover event.
+  ///
+  /// All of the arguments must be non-null.
+  const PointerHoverEvent({
+    Duration timeStamp = Duration.zero,
+    PointerDeviceKind kind = PointerDeviceKind.touch,
+    int pointer = 0,
+    int device = 0,
+    Offset position = Offset.zero,
+    Offset delta = Offset.zero,
+    int buttons = 0,
+    bool obscured = false,
+    double pressureMin = 1.0,
+    double pressureMax = 1.0,
+    double distance = 0.0,
+    double distanceMax = 0.0,
+    double size = 0.0,
+    double radiusMajor = 0.0,
+    double radiusMinor = 0.0,
+    double radiusMin = 0.0,
+    double radiusMax = 0.0,
+    double orientation = 0.0,
+    double tilt = 0.0,
+    bool synthesized = false,
+    int embedderId = 0,
+  }) : super(
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+         delta: delta,
+         buttons: buttons,
+         down: false,
+         obscured: obscured,
+         pressure: 0.0,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: distance,
+         distanceMax: distanceMax,
+         size: size,
+         radiusMajor: radiusMajor,
+         radiusMinor: radiusMinor,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+         synthesized: synthesized,
+         embedderId: embedderId,
+       );
+
+  @override
+  PointerHoverEvent transformed(Matrix4? transform) {
+    if (transform == null || transform == this.transform) {
+      return this;
+    }
+    return _TransformedPointerHoverEvent(original as PointerHoverEvent? ?? this, transform);
+  }
+}
+
+class _TransformedPointerHoverEvent extends _TransformedPointerEvent with _CopyPointerHoverEvent implements PointerHoverEvent {
+  _TransformedPointerHoverEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
+
+  @override
+  final PointerHoverEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerHoverEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+mixin _CopyPointerEnterEvent on PointerEvent {
+  @override
+  PointerEnterEvent copyWith({
+    Duration? timeStamp,
+    int? pointer,
+    PointerDeviceKind? kind,
+    int? device,
+    Offset? position,
+    Offset? delta,
+    int? buttons,
+    bool? obscured,
+    double? pressure,
+    double? pressureMin,
+    double? pressureMax,
+    double? distance,
+    double? distanceMax,
+    double? size,
+    double? radiusMajor,
+    double? radiusMinor,
+    double? radiusMin,
+    double? radiusMax,
+    double? orientation,
+    double? tilt,
+    bool? synthesized,
+    int? embedderId,
+  }) {
+    return PointerEnterEvent(
+      timeStamp: timeStamp ?? this.timeStamp,
+      kind: kind ?? this.kind,
+      device: device ?? this.device,
+      position: position ?? this.position,
+      delta: delta ?? this.delta,
+      buttons: buttons ?? this.buttons,
+      obscured: obscured ?? this.obscured,
+      pressureMin: pressureMin ?? this.pressureMin,
+      pressureMax: pressureMax ?? this.pressureMax,
+      distance: distance ?? this.distance,
+      distanceMax: distanceMax ?? this.distanceMax,
+      size: size ?? this.size,
+      radiusMajor: radiusMajor ?? this.radiusMajor,
+      radiusMinor: radiusMinor ?? this.radiusMinor,
+      radiusMin: radiusMin ?? this.radiusMin,
+      radiusMax: radiusMax ?? this.radiusMax,
+      orientation: orientation ?? this.orientation,
+      tilt: tilt ?? this.tilt,
+      synthesized: synthesized ?? this.synthesized,
+      embedderId: embedderId ?? this.embedderId,
+    ).transformed(transform);
+  }
+}
+
+/// The pointer has moved with respect to the device while the pointer is or is
+/// not in contact with the device, and it has entered a target object.
+///
+/// See also:
+///
+///  * [PointerHoverEvent], which reports when the pointer has moved while
+///    within an object.
+///  * [PointerExitEvent], which reports when the pointer has left an object.
+///  * [PointerMoveEvent], which reports movement while the pointer is in
+///    contact with the device.
+///  * [MouseRegion.onEnter], which allows callers to be notified of these
+///    events in a widget tree.
+class PointerEnterEvent extends PointerEvent with _PointerEventDescription, _CopyPointerEnterEvent {
+  /// Creates a pointer enter event.
+  ///
+  /// All of the arguments must be non-null.
+  const PointerEnterEvent({
+    Duration timeStamp = Duration.zero,
+    int pointer = 0,
+    PointerDeviceKind kind = PointerDeviceKind.touch,
+    int device = 0,
+    Offset position = Offset.zero,
+    Offset delta = Offset.zero,
+    int buttons = 0,
+    bool obscured = false,
+    double pressureMin = 1.0,
+    double pressureMax = 1.0,
+    double distance = 0.0,
+    double distanceMax = 0.0,
+    double size = 0.0,
+    double radiusMajor = 0.0,
+    double radiusMinor = 0.0,
+    double radiusMin = 0.0,
+    double radiusMax = 0.0,
+    double orientation = 0.0,
+    double tilt = 0.0,
+    bool down = false,
+    bool synthesized = false,
+    int embedderId = 0,
+  }) : super(
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+         delta: delta,
+         buttons: buttons,
+         down: down,
+         obscured: obscured,
+         pressure: 0.0,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: distance,
+         distanceMax: distanceMax,
+         size: size,
+         radiusMajor: radiusMajor,
+         radiusMinor: radiusMinor,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+         synthesized: synthesized,
+         embedderId: embedderId,
+       );
+
+  /// Creates an enter event from a [PointerHoverEvent].
+  ///
+  /// Deprecated. Please use [PointerEnterEvent.fromMouseEvent] instead.
+  @Deprecated(
+    'Use PointerEnterEvent.fromMouseEvent instead. '
+    'This feature was deprecated after v1.4.3.'
+  )
+  factory PointerEnterEvent.fromHoverEvent(PointerHoverEvent event) => PointerEnterEvent.fromMouseEvent(event);
+
+  /// Creates an enter event from a [PointerEvent].
+  ///
+  /// This is used by the [MouseTracker] to synthesize enter events.
+  factory PointerEnterEvent.fromMouseEvent(PointerEvent event) => PointerEnterEvent(
+    timeStamp: event.timeStamp,
+    pointer: event.pointer,
+    kind: event.kind,
+    device: event.device,
+    position: event.position,
+    delta: event.delta,
+    buttons: event.buttons,
+    obscured: event.obscured,
+    pressureMin: event.pressureMin,
+    pressureMax: event.pressureMax,
+    distance: event.distance,
+    distanceMax: event.distanceMax,
+    size: event.size,
+    radiusMajor: event.radiusMajor,
+    radiusMinor: event.radiusMinor,
+    radiusMin: event.radiusMin,
+    radiusMax: event.radiusMax,
+    orientation: event.orientation,
+    tilt: event.tilt,
+    down: event.down,
+    synthesized: event.synthesized,
+  ).transformed(event.transform);
+
+  @override
+  PointerEnterEvent transformed(Matrix4? transform) {
+    if (transform == null || transform == this.transform) {
+      return this;
+    }
+    return _TransformedPointerEnterEvent(original as PointerEnterEvent? ?? this, transform);
+  }
+}
+
+class _TransformedPointerEnterEvent extends _TransformedPointerEvent with _CopyPointerEnterEvent implements PointerEnterEvent {
+  _TransformedPointerEnterEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
+
+  @override
+  final PointerEnterEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerEnterEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+mixin _CopyPointerExitEvent on PointerEvent {
+  @override
+  PointerExitEvent copyWith({
+    Duration? timeStamp,
+    int? pointer,
+    PointerDeviceKind? kind,
+    int? device,
+    Offset? position,
+    Offset? delta,
+    int? buttons,
+    bool? obscured,
+    double? pressure,
+    double? pressureMin,
+    double? pressureMax,
+    double? distance,
+    double? distanceMax,
+    double? size,
+    double? radiusMajor,
+    double? radiusMinor,
+    double? radiusMin,
+    double? radiusMax,
+    double? orientation,
+    double? tilt,
+    bool? synthesized,
+    int? embedderId,
+  }) {
+    return PointerExitEvent(
+      timeStamp: timeStamp ?? this.timeStamp,
+      kind: kind ?? this.kind,
+      device: device ?? this.device,
+      position: position ?? this.position,
+      delta: delta ?? this.delta,
+      buttons: buttons ?? this.buttons,
+      obscured: obscured ?? this.obscured,
+      pressureMin: pressureMin ?? this.pressureMin,
+      pressureMax: pressureMax ?? this.pressureMax,
+      distance: distance ?? this.distance,
+      distanceMax: distanceMax ?? this.distanceMax,
+      size: size ?? this.size,
+      radiusMajor: radiusMajor ?? this.radiusMajor,
+      radiusMinor: radiusMinor ?? this.radiusMinor,
+      radiusMin: radiusMin ?? this.radiusMin,
+      radiusMax: radiusMax ?? this.radiusMax,
+      orientation: orientation ?? this.orientation,
+      tilt: tilt ?? this.tilt,
+      synthesized: synthesized ?? this.synthesized,
+      embedderId: embedderId ?? this.embedderId,
+    ).transformed(transform);
+  }
+}
+
+/// The pointer has moved with respect to the device while the pointer is or is
+/// not in contact with the device, and entered a target object.
+///
+/// See also:
+///
+///  * [PointerHoverEvent], which reports when the pointer has moved while
+///    within an object.
+///  * [PointerEnterEvent], which reports when the pointer has entered an object.
+///  * [PointerMoveEvent], which reports movement while the pointer is in
+///    contact with the device.
+///  * [MouseRegion.onExit], which allows callers to be notified of these
+///    events in a widget tree.
+class PointerExitEvent extends PointerEvent with _PointerEventDescription, _CopyPointerExitEvent {
+  /// Creates a pointer exit event.
+  ///
+  /// All of the arguments must be non-null.
+  const PointerExitEvent({
+    Duration timeStamp = Duration.zero,
+    PointerDeviceKind kind = PointerDeviceKind.touch,
+    int pointer = 0,
+    int device = 0,
+    Offset position = Offset.zero,
+    Offset delta = Offset.zero,
+    int buttons = 0,
+    bool obscured = false,
+    double pressureMin = 1.0,
+    double pressureMax = 1.0,
+    double distance = 0.0,
+    double distanceMax = 0.0,
+    double size = 0.0,
+    double radiusMajor = 0.0,
+    double radiusMinor = 0.0,
+    double radiusMin = 0.0,
+    double radiusMax = 0.0,
+    double orientation = 0.0,
+    double tilt = 0.0,
+    bool down = false,
+    bool synthesized = false,
+    int embedderId = 0,
+  }) : super(
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+         delta: delta,
+         buttons: buttons,
+         down: down,
+         obscured: obscured,
+         pressure: 0.0,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: distance,
+         distanceMax: distanceMax,
+         size: size,
+         radiusMajor: radiusMajor,
+         radiusMinor: radiusMinor,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+         synthesized: synthesized,
+         embedderId: embedderId,
+       );
+
+  /// Creates an enter event from a [PointerHoverEvent].
+  ///
+  /// Deprecated. Please use [PointerExitEvent.fromMouseEvent] instead.
+  @Deprecated(
+    'Use PointerExitEvent.fromMouseEvent instead. '
+    'This feature was deprecated after v1.4.3.'
+  )
+  factory PointerExitEvent.fromHoverEvent(PointerHoverEvent event) => PointerExitEvent.fromMouseEvent(event);
+
+  /// Creates an exit event from a [PointerEvent].
+  ///
+  /// This is used by the [MouseTracker] to synthesize exit events.
+  factory PointerExitEvent.fromMouseEvent(PointerEvent event) => PointerExitEvent(
+    timeStamp: event.timeStamp,
+    pointer: event.pointer,
+    kind: event.kind,
+    device: event.device,
+    position: event.position,
+    delta: event.delta,
+    buttons: event.buttons,
+    obscured: event.obscured,
+    pressureMin: event.pressureMin,
+    pressureMax: event.pressureMax,
+    distance: event.distance,
+    distanceMax: event.distanceMax,
+    size: event.size,
+    radiusMajor: event.radiusMajor,
+    radiusMinor: event.radiusMinor,
+    radiusMin: event.radiusMin,
+    radiusMax: event.radiusMax,
+    orientation: event.orientation,
+    tilt: event.tilt,
+    down: event.down,
+    synthesized: event.synthesized,
+  ).transformed(event.transform);
+
+  @override
+  PointerExitEvent transformed(Matrix4? transform) {
+    if (transform == null || transform == this.transform) {
+      return this;
+    }
+    return _TransformedPointerExitEvent(original as PointerExitEvent? ?? this, transform);
+  }
+
+}
+
+class _TransformedPointerExitEvent extends _TransformedPointerEvent with _CopyPointerExitEvent implements PointerExitEvent {
+  _TransformedPointerExitEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
+
+  @override
+  final PointerExitEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerExitEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+mixin _CopyPointerDownEvent on PointerEvent {
+  @override
+  PointerDownEvent copyWith({
+    Duration? timeStamp,
+    int? pointer,
+    PointerDeviceKind? kind,
+    int? device,
+    Offset? position,
+    Offset? delta,
+    int? buttons,
+    bool? obscured,
+    double? pressure,
+    double? pressureMin,
+    double? pressureMax,
+    double? distance,
+    double? distanceMax,
+    double? size,
+    double? radiusMajor,
+    double? radiusMinor,
+    double? radiusMin,
+    double? radiusMax,
+    double? orientation,
+    double? tilt,
+    bool? synthesized,
+    int? embedderId,
+  }) {
+    return PointerDownEvent(
+      timeStamp: timeStamp ?? this.timeStamp,
+      pointer: pointer ?? this.pointer,
+      kind: kind ?? this.kind,
+      device: device ?? this.device,
+      position: position ?? this.position,
+      buttons: buttons ?? this.buttons,
+      obscured: obscured ?? this.obscured,
+      pressure: pressure ?? this.pressure,
+      pressureMin: pressureMin ?? this.pressureMin,
+      pressureMax: pressureMax ?? this.pressureMax,
+      distanceMax: distanceMax ?? this.distanceMax,
+      size: size ?? this.size,
+      radiusMajor: radiusMajor ?? this.radiusMajor,
+      radiusMinor: radiusMinor ?? this.radiusMinor,
+      radiusMin: radiusMin ?? this.radiusMin,
+      radiusMax: radiusMax ?? this.radiusMax,
+      orientation: orientation ?? this.orientation,
+      tilt: tilt ?? this.tilt,
+      embedderId: embedderId ?? this.embedderId,
+    ).transformed(transform);
+  }
+}
+
+/// The pointer has made contact with the device.
+///
+/// See also:
+///
+///  * [Listener.onPointerDown], which allows callers to be notified of these
+///    events in a widget tree.
+class PointerDownEvent extends PointerEvent with _PointerEventDescription, _CopyPointerDownEvent {
+  /// Creates a pointer down event.
+  ///
+  /// All of the arguments must be non-null.
+  const PointerDownEvent({
+    Duration timeStamp = Duration.zero,
+    int pointer = 0,
+    PointerDeviceKind kind = PointerDeviceKind.touch,
+    int device = 0,
+    Offset position = Offset.zero,
+    int buttons = kPrimaryButton,
+    bool obscured = false,
+    double pressure = 1.0,
+    double pressureMin = 1.0,
+    double pressureMax = 1.0,
+    double distanceMax = 0.0,
+    double size = 0.0,
+    double radiusMajor = 0.0,
+    double radiusMinor = 0.0,
+    double radiusMin = 0.0,
+    double radiusMax = 0.0,
+    double orientation = 0.0,
+    double tilt = 0.0,
+    int embedderId = 0,
+  }) : super(
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+         buttons: buttons,
+         down: true,
+         obscured: obscured,
+         pressure: pressure,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: 0.0,
+         distanceMax: distanceMax,
+         size: size,
+         radiusMajor: radiusMajor,
+         radiusMinor: radiusMinor,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+         embedderId: embedderId,
+       );
+
+  @override
+  PointerDownEvent transformed(Matrix4? transform) {
+    if (transform == null || transform == this.transform) {
+      return this;
+    }
+    return _TransformedPointerDownEvent(original as PointerDownEvent? ?? this, transform);
+  }
+}
+
+class _TransformedPointerDownEvent extends _TransformedPointerEvent with _CopyPointerDownEvent implements PointerDownEvent {
+  _TransformedPointerDownEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
+
+  @override
+  final PointerDownEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerDownEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+mixin _CopyPointerMoveEvent on PointerEvent {
+  @override
+  PointerMoveEvent copyWith({
+    Duration? timeStamp,
+    int? pointer,
+    PointerDeviceKind? kind,
+    int? device,
+    Offset? position,
+    Offset? delta,
+    int? buttons,
+    bool? obscured,
+    double? pressure,
+    double? pressureMin,
+    double? pressureMax,
+    double? distance,
+    double? distanceMax,
+    double? size,
+    double? radiusMajor,
+    double? radiusMinor,
+    double? radiusMin,
+    double? radiusMax,
+    double? orientation,
+    double? tilt,
+    bool? synthesized,
+    int? embedderId,
+  }) {
+    return PointerMoveEvent(
+      timeStamp: timeStamp ?? this.timeStamp,
+      pointer: pointer ?? this.pointer,
+      kind: kind ?? this.kind,
+      device: device ?? this.device,
+      position: position ?? this.position,
+      delta: delta ?? this.delta,
+      buttons: buttons ?? this.buttons,
+      obscured: obscured ?? this.obscured,
+      pressure: pressure ?? this.pressure,
+      pressureMin: pressureMin ?? this.pressureMin,
+      pressureMax: pressureMax ?? this.pressureMax,
+      distanceMax: distanceMax ?? this.distanceMax,
+      size: size ?? this.size,
+      radiusMajor: radiusMajor ?? this.radiusMajor,
+      radiusMinor: radiusMinor ?? this.radiusMinor,
+      radiusMin: radiusMin ?? this.radiusMin,
+      radiusMax: radiusMax ?? this.radiusMax,
+      orientation: orientation ?? this.orientation,
+      tilt: tilt ?? this.tilt,
+      synthesized: synthesized ?? this.synthesized,
+      embedderId: embedderId ?? this.embedderId,
+    ).transformed(transform);
+  }
+}
+
+/// The pointer has moved with respect to the device while the pointer is in
+/// contact with the device.
+///
+/// See also:
+///
+///  * [PointerHoverEvent], which reports movement while the pointer is not in
+///    contact with the device.
+///  * [Listener.onPointerMove], which allows callers to be notified of these
+///    events in a widget tree.
+class PointerMoveEvent extends PointerEvent with _PointerEventDescription, _CopyPointerMoveEvent {
+  /// Creates a pointer move event.
+  ///
+  /// All of the arguments must be non-null.
+  const PointerMoveEvent({
+    Duration timeStamp = Duration.zero,
+    int pointer = 0,
+    PointerDeviceKind kind = PointerDeviceKind.touch,
+    int device = 0,
+    Offset position = Offset.zero,
+    Offset delta = Offset.zero,
+    int buttons = kPrimaryButton,
+    bool obscured = false,
+    double pressure = 1.0,
+    double pressureMin = 1.0,
+    double pressureMax = 1.0,
+    double distanceMax = 0.0,
+    double size = 0.0,
+    double radiusMajor = 0.0,
+    double radiusMinor = 0.0,
+    double radiusMin = 0.0,
+    double radiusMax = 0.0,
+    double orientation = 0.0,
+    double tilt = 0.0,
+    int platformData = 0,
+    bool synthesized = false,
+    int embedderId = 0,
+  }) : super(
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+         delta: delta,
+         buttons: buttons,
+         down: true,
+         obscured: obscured,
+         pressure: pressure,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: 0.0,
+         distanceMax: distanceMax,
+         size: size,
+         radiusMajor: radiusMajor,
+         radiusMinor: radiusMinor,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+         platformData: platformData,
+         synthesized: synthesized,
+         embedderId: embedderId,
+       );
+
+  @override
+  PointerMoveEvent transformed(Matrix4? transform) {
+    if (transform == null || transform == this.transform) {
+      return this;
+    }
+
+    return _TransformedPointerMoveEvent(original as PointerMoveEvent? ?? this, transform);
+  }
+}
+
+class _TransformedPointerMoveEvent extends _TransformedPointerEvent with _CopyPointerMoveEvent implements PointerMoveEvent {
+  _TransformedPointerMoveEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
+
+  @override
+  final PointerMoveEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerMoveEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+mixin _CopyPointerUpEvent on PointerEvent {
+  @override
+  PointerUpEvent copyWith({
+    Duration? timeStamp,
+    int? pointer,
+    PointerDeviceKind? kind,
+    int? device,
+    Offset? position,
+    Offset? localPosition,
+    Offset? delta,
+    int? buttons,
+    bool? obscured,
+    double? pressure,
+    double? pressureMin,
+    double? pressureMax,
+    double? distance,
+    double? distanceMax,
+    double? size,
+    double? radiusMajor,
+    double? radiusMinor,
+    double? radiusMin,
+    double? radiusMax,
+    double? orientation,
+    double? tilt,
+    bool? synthesized,
+    int? embedderId,
+  }) {
+    return PointerUpEvent(
+      timeStamp: timeStamp ?? this.timeStamp,
+      pointer: pointer ?? this.pointer,
+      kind: kind ?? this.kind,
+      device: device ?? this.device,
+      position: position ?? this.position,
+      buttons: buttons ?? this.buttons,
+      obscured: obscured ?? this.obscured,
+      pressure: pressure ?? this.pressure,
+      pressureMin: pressureMin ?? this.pressureMin,
+      pressureMax: pressureMax ?? this.pressureMax,
+      distance: distance ?? this.distance,
+      distanceMax: distanceMax ?? this.distanceMax,
+      size: size ?? this.size,
+      radiusMajor: radiusMajor ?? this.radiusMajor,
+      radiusMinor: radiusMinor ?? this.radiusMinor,
+      radiusMin: radiusMin ?? this.radiusMin,
+      radiusMax: radiusMax ?? this.radiusMax,
+      orientation: orientation ?? this.orientation,
+      tilt: tilt ?? this.tilt,
+      embedderId: embedderId ?? this.embedderId,
+    ).transformed(transform);
+  }
+}
+
+/// The pointer has stopped making contact with the device.
+///
+/// See also:
+///
+///  * [Listener.onPointerUp], which allows callers to be notified of these
+///    events in a widget tree.
+class PointerUpEvent extends PointerEvent with _PointerEventDescription, _CopyPointerUpEvent {
+  /// Creates a pointer up event.
+  ///
+  /// All of the arguments must be non-null.
+  const PointerUpEvent({
+    Duration timeStamp = Duration.zero,
+    int pointer = 0,
+    PointerDeviceKind kind = PointerDeviceKind.touch,
+    int device = 0,
+    Offset position = Offset.zero,
+    int buttons = 0,
+    bool obscured = false,
+    // Allow pressure customization here because PointerUpEvent can contain
+    // non-zero pressure. See https://github.com/flutter/flutter/issues/31340
+    double pressure = 0.0,
+    double pressureMin = 1.0,
+    double pressureMax = 1.0,
+    double distance = 0.0,
+    double distanceMax = 0.0,
+    double size = 0.0,
+    double radiusMajor = 0.0,
+    double radiusMinor = 0.0,
+    double radiusMin = 0.0,
+    double radiusMax = 0.0,
+    double orientation = 0.0,
+    double tilt = 0.0,
+    int embedderId = 0,
+  }) : super(
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+         buttons: buttons,
+         down: false,
+         obscured: obscured,
+         pressure: pressure,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: distance,
+         distanceMax: distanceMax,
+         size: size,
+         radiusMajor: radiusMajor,
+         radiusMinor: radiusMinor,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+         embedderId: embedderId,
+       );
+
+  @override
+  PointerUpEvent transformed(Matrix4? transform) {
+    if (transform == null || transform == this.transform) {
+      return this;
+    }
+    return _TransformedPointerUpEvent(original as PointerUpEvent? ?? this, transform);
+  }
+}
+
+class _TransformedPointerUpEvent extends _TransformedPointerEvent with _CopyPointerUpEvent implements PointerUpEvent {
+  _TransformedPointerUpEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
+
+  @override
+  final PointerUpEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerUpEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
+
+/// An event that corresponds to a discrete pointer signal.
+///
+/// Pointer signals are events that originate from the pointer but don't change
+/// the state of the pointer itself, and are discrete rather than needing to be
+/// interpreted in the context of a series of events.
+///
+/// See also:
+///
+///  * [Listener.onPointerSignal], which allows callers to be notified of these
+///    events in a widget tree.
+abstract class PointerSignalEvent extends PointerEvent {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const PointerSignalEvent({
+    Duration timeStamp = Duration.zero,
+    int pointer = 0,
+    PointerDeviceKind kind = PointerDeviceKind.mouse,
+    int device = 0,
+    Offset position = Offset.zero,
+    int embedderId = 0,
+  }) : super(
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+         embedderId: embedderId,
+       );
+}
+
+mixin _CopyPointerScrollEvent on PointerEvent {
+  /// The amount to scroll, in logical pixels.
+  Offset get scrollDelta;
+
+  @override
+  PointerScrollEvent copyWith({
+    Duration? timeStamp,
+    int? pointer,
+    PointerDeviceKind? kind,
+    int? device,
+    Offset? position,
+    Offset? delta,
+    int? buttons,
+    bool? obscured,
+    double? pressure,
+    double? pressureMin,
+    double? pressureMax,
+    double? distance,
+    double? distanceMax,
+    double? size,
+    double? radiusMajor,
+    double? radiusMinor,
+    double? radiusMin,
+    double? radiusMax,
+    double? orientation,
+    double? tilt,
+    bool? synthesized,
+    int? embedderId,
+  }) {
+    return PointerScrollEvent(
+      timeStamp: timeStamp ?? this.timeStamp,
+      kind: kind ?? this.kind,
+      device: device ?? this.device,
+      position: position ?? this.position,
+      scrollDelta: scrollDelta,
+      embedderId: embedderId ?? this.embedderId,
+    ).transformed(transform);
+  }
+}
+
+/// The pointer issued a scroll event.
+///
+/// Scrolling the scroll wheel on a mouse is an example of an event that
+/// would create a [PointerScrollEvent].
+///
+/// See also:
+///
+///  * [Listener.onPointerSignal], which allows callers to be notified of these
+///    events in a widget tree.
+class PointerScrollEvent extends PointerSignalEvent with _PointerEventDescription, _CopyPointerScrollEvent {
+  /// Creates a pointer scroll event.
+  ///
+  /// All of the arguments must be non-null.
+  const PointerScrollEvent({
+    Duration timeStamp = Duration.zero,
+    PointerDeviceKind kind = PointerDeviceKind.mouse,
+    int device = 0,
+    Offset position = Offset.zero,
+    this.scrollDelta = Offset.zero,
+    int embedderId = 0,
+  }) : assert(timeStamp != null),
+       assert(kind != null),
+       assert(device != null),
+       assert(position != null),
+       assert(scrollDelta != null),
+       super(
+         timeStamp: timeStamp,
+         kind: kind,
+         device: device,
+         position: position,
+         embedderId: embedderId,
+       );
+
+  @override
+  final Offset scrollDelta;
+
+  @override
+  PointerScrollEvent transformed(Matrix4? transform) {
+    if (transform == null || transform == this.transform) {
+      return this;
+    }
+    return _TransformedPointerScrollEvent(original as PointerScrollEvent? ?? this, transform);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Offset>('scrollDelta', scrollDelta));
+  }
+}
+
+class _TransformedPointerScrollEvent extends _TransformedPointerEvent with _CopyPointerScrollEvent implements PointerScrollEvent {
+  _TransformedPointerScrollEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
+
+  @override
+  final PointerScrollEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  Offset get scrollDelta => original.scrollDelta;
+
+  @override
+  PointerScrollEvent transformed(Matrix4? transform) => original.transformed(transform);
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Offset>('scrollDelta', scrollDelta));
+  }
+}
+
+mixin _CopyPointerCancelEvent on PointerEvent {
+  @override
+  PointerCancelEvent copyWith({
+    Duration? timeStamp,
+    int? pointer,
+    PointerDeviceKind? kind,
+    int? device,
+    Offset? position,
+    Offset? delta,
+    int? buttons,
+    bool? obscured,
+    double? pressure,
+    double? pressureMin,
+    double? pressureMax,
+    double? distance,
+    double? distanceMax,
+    double? size,
+    double? radiusMajor,
+    double? radiusMinor,
+    double? radiusMin,
+    double? radiusMax,
+    double? orientation,
+    double? tilt,
+    bool? synthesized,
+    int? embedderId,
+  }) {
+    return PointerCancelEvent(
+      timeStamp: timeStamp ?? this.timeStamp,
+      pointer: pointer ?? this.pointer,
+      kind: kind ?? this.kind,
+      device: device ?? this.device,
+      position: position ?? this.position,
+      buttons: buttons ?? this.buttons,
+      obscured: obscured ?? this.obscured,
+      pressureMin: pressureMin ?? this.pressureMin,
+      pressureMax: pressureMax ?? this.pressureMax,
+      distance: distance ?? this.distance,
+      distanceMax: distanceMax ?? this.distanceMax,
+      size: size ?? this.size,
+      radiusMajor: radiusMajor ?? this.radiusMajor,
+      radiusMinor: radiusMinor ?? this.radiusMinor,
+      radiusMin: radiusMin ?? this.radiusMin,
+      radiusMax: radiusMax ?? this.radiusMax,
+      orientation: orientation ?? this.orientation,
+      tilt: tilt ?? this.tilt,
+      embedderId: embedderId ?? this.embedderId,
+    ).transformed(transform);
+  }
+}
+
+/// The input from the pointer is no longer directed towards this receiver.
+///
+/// See also:
+///
+///  * [Listener.onPointerCancel], which allows callers to be notified of these
+///    events in a widget tree.
+class PointerCancelEvent extends PointerEvent with _PointerEventDescription, _CopyPointerCancelEvent {
+  /// Creates a pointer cancel event.
+  ///
+  /// All of the arguments must be non-null.
+  const PointerCancelEvent({
+    Duration timeStamp = Duration.zero,
+    int pointer = 0,
+    PointerDeviceKind kind = PointerDeviceKind.touch,
+    int device = 0,
+    Offset position = Offset.zero,
+    int buttons = 0,
+    bool obscured = false,
+    double pressureMin = 1.0,
+    double pressureMax = 1.0,
+    double distance = 0.0,
+    double distanceMax = 0.0,
+    double size = 0.0,
+    double radiusMajor = 0.0,
+    double radiusMinor = 0.0,
+    double radiusMin = 0.0,
+    double radiusMax = 0.0,
+    double orientation = 0.0,
+    double tilt = 0.0,
+    int embedderId = 0,
+  }) : super(
+         timeStamp: timeStamp,
+         pointer: pointer,
+         kind: kind,
+         device: device,
+         position: position,
+         buttons: buttons,
+         down: false,
+         obscured: obscured,
+         pressure: 0.0,
+         pressureMin: pressureMin,
+         pressureMax: pressureMax,
+         distance: distance,
+         distanceMax: distanceMax,
+         size: size,
+         radiusMajor: radiusMajor,
+         radiusMinor: radiusMinor,
+         radiusMin: radiusMin,
+         radiusMax: radiusMax,
+         orientation: orientation,
+         tilt: tilt,
+         embedderId: embedderId,
+       );
+
+  @override
+  PointerCancelEvent transformed(Matrix4? transform) {
+    if (transform == null || transform == this.transform) {
+      return this;
+    }
+    return _TransformedPointerCancelEvent(original as PointerCancelEvent? ?? this, transform);
+  }
+}
+
+/// Determine the appropriate hit slop pixels based on the [kind] of pointer.
+double computeHitSlop(PointerDeviceKind kind) {
+  switch (kind) {
+    case PointerDeviceKind.mouse:
+      return kPrecisePointerHitSlop;
+    case PointerDeviceKind.stylus:
+    case PointerDeviceKind.invertedStylus:
+    case PointerDeviceKind.unknown:
+    case PointerDeviceKind.touch:
+      return kTouchSlop;
+  }
+}
+
+/// Determine the appropriate pan slop pixels based on the [kind] of pointer.
+double computePanSlop(PointerDeviceKind kind) {
+  switch (kind) {
+    case PointerDeviceKind.mouse:
+      return kPrecisePointerPanSlop;
+    case PointerDeviceKind.stylus:
+    case PointerDeviceKind.invertedStylus:
+    case PointerDeviceKind.unknown:
+    case PointerDeviceKind.touch:
+      return kPanSlop;
+  }
+}
+
+/// Determine the appropriate scale slop pixels based on the [kind] of pointer.
+double computeScaleSlop(PointerDeviceKind kind) {
+  switch (kind) {
+    case PointerDeviceKind.mouse:
+      return kPrecisePointerScaleSlop;
+    case PointerDeviceKind.stylus:
+    case PointerDeviceKind.invertedStylus:
+    case PointerDeviceKind.unknown:
+    case PointerDeviceKind.touch:
+      return kScaleSlop;
+  }
+}
+
+class _TransformedPointerCancelEvent extends _TransformedPointerEvent with _CopyPointerCancelEvent implements PointerCancelEvent {
+  _TransformedPointerCancelEvent(this.original, this.transform)
+    : assert(original != null), assert(transform != null);
+
+  @override
+  final PointerCancelEvent original;
+
+  @override
+  final Matrix4 transform;
+
+  @override
+  PointerCancelEvent transformed(Matrix4? transform) => original.transformed(transform);
+}
diff --git a/lib/src/gestures/force_press.dart b/lib/src/gestures/force_press.dart
new file mode 100644
index 0000000..9438396
--- /dev/null
+++ b/lib/src/gestures/force_press.dart
@@ -0,0 +1,348 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show Offset;
+
+import 'package:flute/foundation.dart';
+
+import 'arena.dart';
+import 'events.dart';
+import 'recognizer.dart';
+
+enum _ForceState {
+  // No pointer has touched down and the detector is ready for a pointer down to occur.
+  ready,
+
+  // A pointer has touched down, but a force press gesture has not yet been detected.
+  possible,
+
+  // A pointer is down and a force press gesture has been detected. However, if
+  // the ForcePressGestureRecognizer is the only recognizer in the arena, thus
+  // accepted as soon as the gesture state is possible, the gesture will not
+  // yet have started.
+  accepted,
+
+  // A pointer is down and the gesture has started, ie. the pressure of the pointer
+  // has just become greater than the ForcePressGestureRecognizer.startPressure.
+  started,
+
+  // A pointer is down and the pressure of the pointer has just become greater
+  // than the ForcePressGestureRecognizer.peakPressure. Even after a pointer
+  // crosses this threshold, onUpdate callbacks will still be sent.
+  peaked,
+}
+
+/// Details object for callbacks that use [GestureForcePressStartCallback],
+/// [GestureForcePressPeakCallback], [GestureForcePressEndCallback] or
+/// [GestureForcePressUpdateCallback].
+///
+/// See also:
+///
+///  * [ForcePressGestureRecognizer.onStart], [ForcePressGestureRecognizer.onPeak],
+///    [ForcePressGestureRecognizer.onEnd], and [ForcePressGestureRecognizer.onUpdate]
+///    which use [ForcePressDetails].
+class ForcePressDetails {
+  /// Creates details for a [GestureForcePressStartCallback],
+  /// [GestureForcePressPeakCallback] or [GestureForcePressEndCallback].
+  ///
+  /// The [globalPosition] argument must not be null.
+  ForcePressDetails({
+    required this.globalPosition,
+    Offset? localPosition,
+    required this.pressure,
+  }) : assert(globalPosition != null),
+       assert(pressure != null),
+       localPosition = localPosition ?? globalPosition;
+
+  /// The global position at which the function was called.
+  final Offset globalPosition;
+
+  /// The local position at which the function was called.
+  final Offset localPosition;
+
+  /// The pressure of the pointer on the screen.
+  final double pressure;
+}
+
+/// Signature used by a [ForcePressGestureRecognizer] for when a pointer has
+/// pressed with at least [ForcePressGestureRecognizer.startPressure].
+typedef GestureForcePressStartCallback = void Function(ForcePressDetails details);
+
+/// Signature used by [ForcePressGestureRecognizer] for when a pointer that has
+/// pressed with at least [ForcePressGestureRecognizer.peakPressure].
+typedef GestureForcePressPeakCallback = void Function(ForcePressDetails details);
+
+/// Signature used by [ForcePressGestureRecognizer] during the frames
+/// after the triggering of a [ForcePressGestureRecognizer.onStart] callback.
+typedef GestureForcePressUpdateCallback = void Function(ForcePressDetails details);
+
+/// Signature for when the pointer that previously triggered a
+/// [ForcePressGestureRecognizer.onStart] callback is no longer in contact
+/// with the screen.
+typedef GestureForcePressEndCallback = void Function(ForcePressDetails details);
+
+/// Signature used by [ForcePressGestureRecognizer] for interpolating the raw
+/// device pressure to a value in the range [0, 1] given the device's pressure
+/// min and pressure max.
+typedef GestureForceInterpolation = double Function(double pressureMin, double pressureMax, double pressure);
+
+/// Recognizes a force press on devices that have force sensors.
+///
+/// Only the force from a single pointer is used to invoke events. A tap
+/// recognizer will win against this recognizer on pointer up as long as the
+/// pointer has not pressed with a force greater than
+/// [ForcePressGestureRecognizer.startPressure]. A long press recognizer will
+/// win when the press down time exceeds the threshold time as long as the
+/// pointer's pressure was never greater than
+/// [ForcePressGestureRecognizer.startPressure] in that duration.
+///
+/// As of November, 2018 iPhone devices of generation 6S and higher have
+/// force touch functionality, with the exception of the iPhone XR. In addition,
+/// a small handful of Android devices have this functionality as well.
+///
+/// Devices with faux screen pressure sensors like the Pixel 2 and 3 will not
+/// send any force press related callbacks.
+///
+/// Reported pressure will always be in the range 0.0 to 1.0, where 1.0 is
+/// maximum pressure and 0.0 is minimum pressure. If using a custom
+/// [interpolation] callback, the pressure reported will correspond to that
+/// custom curve.
+class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer {
+  /// Creates a force press gesture recognizer.
+  ///
+  /// The [startPressure] defaults to 0.4, and [peakPressure] defaults to 0.85
+  /// where a value of 0.0 is no pressure and a value of 1.0 is maximum pressure.
+  ///
+  /// The [startPressure], [peakPressure] and [interpolation] arguments must not
+  /// be null. The [peakPressure] argument must be greater than [startPressure].
+  /// The [interpolation] callback must always return a value in the range 0.0
+  /// to 1.0 for values of `pressure` that are between `pressureMin` and
+  /// `pressureMax`.
+  ///
+  /// {@macro flutter.gestures.GestureRecognizer.kind}
+  ForcePressGestureRecognizer({
+    this.startPressure = 0.4,
+    this.peakPressure = 0.85,
+    this.interpolation = _inverseLerp,
+    Object? debugOwner,
+    PointerDeviceKind? kind,
+  }) : assert(startPressure != null),
+       assert(peakPressure != null),
+       assert(interpolation != null),
+       assert(peakPressure > startPressure),
+       super(debugOwner: debugOwner, kind: kind);
+
+  /// A pointer is in contact with the screen and has just pressed with a force
+  /// exceeding the [startPressure]. Consequently, if there were other gesture
+  /// detectors, only the force press gesture will be detected and all others
+  /// will be rejected.
+  ///
+  /// The position of the pointer is provided in the callback's `details`
+  /// argument, which is a [ForcePressDetails] object.
+  GestureForcePressStartCallback? onStart;
+
+  /// A pointer is in contact with the screen and is either moving on the plane
+  /// of the screen, pressing the screen with varying forces or both
+  /// simultaneously.
+  ///
+  /// This callback will be invoked for every pointer event after the invocation
+  /// of [onStart] and/or [onPeak] and before the invocation of [onEnd], no
+  /// matter what the pressure is during this time period. The position and
+  /// pressure of the pointer is provided in the callback's `details` argument,
+  /// which is a [ForcePressDetails] object.
+  GestureForcePressUpdateCallback? onUpdate;
+
+  /// A pointer is in contact with the screen and has just pressed with a force
+  /// exceeding the [peakPressure]. This is an arbitrary second level action
+  /// threshold and isn't necessarily the maximum possible device pressure
+  /// (which is 1.0).
+  ///
+  /// The position of the pointer is provided in the callback's `details`
+  /// argument, which is a [ForcePressDetails] object.
+  GestureForcePressPeakCallback? onPeak;
+
+  /// A pointer is no longer in contact with the screen.
+  ///
+  /// The position of the pointer is provided in the callback's `details`
+  /// argument, which is a [ForcePressDetails] object.
+  GestureForcePressEndCallback? onEnd;
+
+  /// The pressure of the press required to initiate a force press.
+  ///
+  /// A value of 0.0 is no pressure, and 1.0 is maximum pressure.
+  final double startPressure;
+
+  /// The pressure of the press required to peak a force press.
+  ///
+  /// A value of 0.0 is no pressure, and 1.0 is maximum pressure. This value
+  /// must be greater than [startPressure].
+  final double peakPressure;
+
+  /// The function used to convert the raw device pressure values into a value
+  /// in the range 0.0 to 1.0.
+  ///
+  /// The function takes in the device's minimum, maximum and raw touch pressure
+  /// and returns a value in the range 0.0 to 1.0 denoting the interpolated
+  /// touch pressure.
+  ///
+  /// This function must always return values in the range 0.0 to 1.0 given a
+  /// pressure that is between the minimum and maximum pressures. It may return
+  /// `double.NaN` for values that it does not want to support.
+  ///
+  /// By default, the function is a linear interpolation; however, changing the
+  /// function could be useful to accommodate variations in the way different
+  /// devices respond to pressure, or to change how animations from pressure
+  /// feedback are rendered.
+  ///
+  /// For example, an ease-in curve can be used to determine the interpolated
+  /// value:
+  ///
+  /// ```dart
+  /// static double interpolateWithEasing(double min, double max, double t) {
+  ///    final double lerp = (t - min) / (max - min);
+  ///    return Curves.easeIn.transform(lerp);
+  /// }
+  /// ```
+  final GestureForceInterpolation interpolation;
+
+  late OffsetPair _lastPosition;
+  late double _lastPressure;
+  _ForceState _state = _ForceState.ready;
+
+  @override
+  void addAllowedPointer(PointerEvent event) {
+    // If the device has a maximum pressure of less than or equal to 1, it
+    // doesn't have touch pressure sensing capabilities. Do not participate
+    // in the gesture arena.
+    if (event is! PointerUpEvent && event.pressureMax <= 1.0) {
+      resolve(GestureDisposition.rejected);
+    } else {
+      startTrackingPointer(event.pointer, event.transform);
+      if (_state == _ForceState.ready) {
+        _state = _ForceState.possible;
+        _lastPosition = OffsetPair.fromEventPosition(event);
+      }
+    }
+  }
+
+  @override
+  void handleEvent(PointerEvent event) {
+    assert(_state != _ForceState.ready);
+    // A static pointer with changes in pressure creates PointerMoveEvent events.
+    if (event is PointerMoveEvent || event is PointerDownEvent) {
+      if (event.pressure > event.pressureMax || event.pressure < event.pressureMin) {
+        debugPrint(
+          'The reported device pressure ' + event.pressure.toString() +
+          ' is outside of the device pressure range where: ' +
+          event.pressureMin.toString() + ' <= pressure <= ' + event.pressureMax.toString(),
+        );
+      }
+
+      final double pressure = interpolation(event.pressureMin, event.pressureMax, event.pressure);
+      assert(
+        (pressure >= 0.0 && pressure <= 1.0) || // Interpolated pressure must be between 1.0 and 0.0...
+        pressure.isNaN // and interpolation may return NaN for values it doesn't want to support...
+      );
+
+      _lastPosition = OffsetPair.fromEventPosition(event);
+      _lastPressure = pressure;
+
+      if (_state == _ForceState.possible) {
+        if (pressure > startPressure) {
+          _state = _ForceState.started;
+          resolve(GestureDisposition.accepted);
+        } else if (event.delta.distanceSquared > computeHitSlop(event.kind)) {
+          resolve(GestureDisposition.rejected);
+        }
+      }
+      // In case this is the only gesture detector we still don't want to start
+      // the gesture until the pressure is greater than the startPressure.
+      if (pressure > startPressure && _state == _ForceState.accepted) {
+        _state = _ForceState.started;
+        if (onStart != null) {
+          invokeCallback<void>('onStart', () => onStart!(ForcePressDetails(
+            pressure: pressure,
+            globalPosition: _lastPosition.global,
+            localPosition: _lastPosition.local,
+          )));
+        }
+      }
+      if (onPeak != null && pressure > peakPressure &&
+         (_state == _ForceState.started)) {
+        _state = _ForceState.peaked;
+        if (onPeak != null) {
+          invokeCallback<void>('onPeak', () => onPeak!(ForcePressDetails(
+            pressure: pressure,
+            globalPosition: event.position,
+            localPosition: event.localPosition,
+          )));
+        }
+      }
+      if (onUpdate != null &&  !pressure.isNaN &&
+         (_state == _ForceState.started || _state == _ForceState.peaked)) {
+        if (onUpdate != null) {
+          invokeCallback<void>('onUpdate', () => onUpdate!(ForcePressDetails(
+            pressure: pressure,
+            globalPosition: event.position,
+            localPosition: event.localPosition,
+          )));
+        }
+      }
+    }
+    stopTrackingIfPointerNoLongerDown(event);
+  }
+
+  @override
+  void acceptGesture(int pointer) {
+    if (_state == _ForceState.possible)
+      _state = _ForceState.accepted;
+
+    if (onStart != null && _state == _ForceState.started) {
+      invokeCallback<void>('onStart', () => onStart!(ForcePressDetails(
+        pressure: _lastPressure,
+        globalPosition: _lastPosition.global,
+        localPosition: _lastPosition.local,
+      )));
+    }
+  }
+
+  @override
+  void didStopTrackingLastPointer(int pointer) {
+    final bool wasAccepted = _state == _ForceState.started || _state == _ForceState.peaked;
+    if (_state == _ForceState.possible) {
+      resolve(GestureDisposition.rejected);
+      return;
+    }
+    if (wasAccepted && onEnd != null) {
+      if (onEnd != null) {
+        invokeCallback<void>('onEnd', () => onEnd!(ForcePressDetails(
+          pressure: 0.0,
+          globalPosition: _lastPosition.global,
+          localPosition: _lastPosition.local,
+        )));
+      }
+    }
+    _state = _ForceState.ready;
+  }
+
+  @override
+  void rejectGesture(int pointer) {
+    stopTrackingPointer(pointer);
+    didStopTrackingLastPointer(pointer);
+  }
+
+  static double _inverseLerp(double min, double max, double t) {
+    assert(min <= max);
+    double value = (t - min) / (max - min);
+
+    // If the device incorrectly reports a pressure outside of pressureMin
+    // and pressureMax, we still want this recognizer to respond normally.
+    if (!value.isNaN)
+      value = value.clamp(0.0, 1.0);
+    return value;
+  }
+
+  @override
+  String get debugDescription => 'force press';
+}
diff --git a/lib/src/gestures/hit_test.dart b/lib/src/gestures/hit_test.dart
new file mode 100644
index 0000000..038da8b
--- /dev/null
+++ b/lib/src/gestures/hit_test.dart
@@ -0,0 +1,289 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'events.dart';
+
+/// An object that can hit-test pointers.
+abstract class HitTestable {
+  // This class is intended to be used as an interface, and should not be
+  // extended directly; this constructor prevents instantiation and extension.
+  HitTestable._();
+
+  /// Check whether the given position hits this object.
+  ///
+  /// If this given position hits this object, consider adding a [HitTestEntry]
+  /// to the given hit test result.
+  void hitTest(HitTestResult result, Offset position);
+}
+
+/// An object that can dispatch events.
+abstract class HitTestDispatcher {
+  // This class is intended to be used as an interface, and should not be
+  // extended directly; this constructor prevents instantiation and extension.
+  HitTestDispatcher._();
+
+  /// Override this method to dispatch events.
+  void dispatchEvent(PointerEvent event, HitTestResult result);
+}
+
+/// An object that can handle events.
+abstract class HitTestTarget {
+  // This class is intended to be used as an interface, and should not be
+  // extended directly; this constructor prevents instantiation and extension.
+  HitTestTarget._();
+
+  /// Override this method to receive events.
+  void handleEvent(PointerEvent event, HitTestEntry entry);
+}
+
+/// Data collected during a hit test about a specific [HitTestTarget].
+///
+/// Subclass this object to pass additional information from the hit test phase
+/// to the event propagation phase.
+class HitTestEntry {
+  /// Creates a hit test entry.
+  HitTestEntry(this.target);
+
+  /// The [HitTestTarget] encountered during the hit test.
+  final HitTestTarget target;
+
+  @override
+  String toString() => '${describeIdentity(this)}($target)';
+
+  /// Returns a matrix describing how [PointerEvent]s delivered to this
+  /// [HitTestEntry] should be transformed from the global coordinate space of
+  /// the screen to the local coordinate space of [target].
+  ///
+  /// See also:
+  ///
+  ///  * [BoxHitTestResult.addWithPaintTransform], which is used during hit testing
+  ///    to build up this transform.
+  Matrix4? get transform => _transform;
+  Matrix4? _transform;
+}
+
+// A type of data that can be applied to a matrix by left-multiplication.
+@immutable
+abstract class _TransformPart {
+  const _TransformPart();
+
+  // Apply this transform part to `rhs` from the left.
+  //
+  // This should work as if this transform part is first converted to a matrix
+  // and then left-multiplied to `rhs`.
+  //
+  // For example, if this transform part is a vector `v1`, whose corresponding
+  // matrix is `m1 = Matrix4.translation(v1)`, then the result of
+  // `_VectorTransformPart(v1).multiply(rhs)` should equal to `m1 * rhs`.
+  Matrix4 multiply(Matrix4 rhs);
+}
+
+class _MatrixTransformPart extends _TransformPart {
+  const _MatrixTransformPart(this.matrix);
+
+  final Matrix4 matrix;
+
+  @override
+  Matrix4 multiply(Matrix4 rhs) {
+    return matrix * rhs as Matrix4;
+  }
+}
+
+class _OffsetTransformPart extends _TransformPart {
+  const _OffsetTransformPart(this.offset);
+
+  final Offset offset;
+
+  @override
+  Matrix4 multiply(Matrix4 rhs) {
+    return rhs.clone()..leftTranslate(offset.dx, offset.dy);
+  }
+}
+
+/// The result of performing a hit test.
+class HitTestResult {
+  /// Creates an empty hit test result.
+  HitTestResult()
+     : _path = <HitTestEntry>[],
+       _transforms = <Matrix4>[Matrix4.identity()],
+       _localTransforms = <_TransformPart>[];
+
+  /// Wraps `result` (usually a subtype of [HitTestResult]) to create a
+  /// generic [HitTestResult].
+  ///
+  /// The [HitTestEntry]s added to the returned [HitTestResult] are also
+  /// added to the wrapped `result` (both share the same underlying data
+  /// structure to store [HitTestEntry]s).
+  HitTestResult.wrap(HitTestResult result)
+     : _path = result._path,
+       _transforms = result._transforms,
+       _localTransforms = result._localTransforms;
+
+  /// An unmodifiable list of [HitTestEntry] objects recorded during the hit test.
+  ///
+  /// The first entry in the path is the most specific, typically the one at
+  /// the leaf of tree being hit tested. Event propagation starts with the most
+  /// specific (i.e., first) entry and proceeds in order through the path.
+  Iterable<HitTestEntry> get path => _path;
+  final List<HitTestEntry> _path;
+
+  // A stack of transform parts.
+  //
+  // The transform part stack leading from global to the current object is stored
+  // in 2 parts:
+  //
+  //  * `_transforms` are globalized matrices, meaning they have been multiplied
+  //    by the ancestors and are thus relative to the global coordinate space.
+  //  * `_localTransforms` are local transform parts, which are relative to the
+  //    parent's coordinate space.
+  //
+  // When new transform parts are added they're appended to `_localTransforms`,
+  // and are converted to global ones and moved to `_transforms` only when used.
+  final List<Matrix4> _transforms;
+  final List<_TransformPart> _localTransforms;
+
+  // Globalize all transform parts in `_localTransforms` and move them to
+  // _transforms.
+  void _globalizeTransforms() {
+    if (_localTransforms.isEmpty) {
+      return;
+    }
+    Matrix4 last = _transforms.last;
+    for (final _TransformPart part in _localTransforms) {
+      last = part.multiply(last);
+      _transforms.add(last);
+    }
+    _localTransforms.clear();
+  }
+
+  Matrix4 get _lastTransform {
+    _globalizeTransforms();
+    assert(_localTransforms.isEmpty);
+    return _transforms.last;
+  }
+
+  /// Add a [HitTestEntry] to the path.
+  ///
+  /// The new entry is added at the end of the path, which means entries should
+  /// be added in order from most specific to least specific, typically during an
+  /// upward walk of the tree being hit tested.
+  void add(HitTestEntry entry) {
+    assert(entry._transform == null);
+    entry._transform = _lastTransform;
+    _path.add(entry);
+  }
+
+  /// Pushes a new transform matrix that is to be applied to all future
+  /// [HitTestEntry]s added via [add] until it is removed via [popTransform].
+  ///
+  /// This method is only to be used by subclasses, which must provide
+  /// coordinate space specific public wrappers around this function for their
+  /// users (see [BoxHitTestResult.addWithPaintTransform] for such an example).
+  ///
+  /// The provided `transform` matrix should describe how to transform
+  /// [PointerEvent]s from the coordinate space of the method caller to the
+  /// coordinate space of its children. In most cases `transform` is derived
+  /// from running the inverted result of [RenderObject.applyPaintTransform]
+  /// through [PointerEvent.removePerspectiveTransform] to remove
+  /// the perspective component.
+  ///
+  /// If the provided `transform` is a translation matrix, it is much faster
+  /// to use [pushOffset] with the translation offset instead.
+  ///
+  /// [HitTestable]s need to call this method indirectly through a convenience
+  /// method defined on a subclass before hit testing a child that does not
+  /// have the same origin as the parent. After hit testing the child,
+  /// [popTransform] has to be called to remove the child-specific `transform`.
+  ///
+  /// See also:
+  ///
+  ///  * [pushOffset], which is similar to [pushTransform] but is limited to
+  ///    translations, and is faster in such cases.
+  ///  * [BoxHitTestResult.addWithPaintTransform], which is a public wrapper
+  ///    around this function for hit testing on [RenderBox]s.
+  @protected
+  void pushTransform(Matrix4 transform) {
+    assert(transform != null);
+    assert(
+      _debugVectorMoreOrLessEquals(transform.getRow(2), Vector4(0, 0, 1, 0)) &&
+      _debugVectorMoreOrLessEquals(transform.getColumn(2), Vector4(0, 0, 1, 0)),
+      'The third row and third column of a transform matrix for pointer '
+      'events must be Vector4(0, 0, 1, 0) to ensure that a transformed '
+      'point is directly under the pointing device. Did you forget to run the paint '
+      'matrix through PointerEvent.removePerspectiveTransform? '
+      'The provided matrix is:\n$transform'
+    );
+    _localTransforms.add(_MatrixTransformPart(transform));
+  }
+
+  /// Pushes a new translation offset that is to be applied to all future
+  /// [HitTestEntry]s added via [add] until it is removed via [popTransform].
+  ///
+  /// This method is only to be used by subclasses, which must provide
+  /// coordinate space specific public wrappers around this function for their
+  /// users (see [BoxHitTestResult.addWithPaintOffset] for such an example).
+  ///
+  /// The provided `offset` should describe how to transform [PointerEvent]s from
+  /// the coordinate space of the method caller to the coordinate space of its
+  /// children. Usually `offset` is the inverse of the offset of the child
+  /// relative to the parent.
+  ///
+  /// [HitTestable]s need to call this method indirectly through a convenience
+  /// method defined on a subclass before hit testing a child that does not
+  /// have the same origin as the parent. After hit testing the child,
+  /// [popTransform] has to be called to remove the child-specific `transform`.
+  ///
+  /// See also:
+  ///
+  ///  * [pushTransform], which is similar to [pushOffset] but allows general
+  ///    transform besides translation.
+  ///  * [BoxHitTestResult.addWithPaintOffset], which is a public wrapper
+  ///    around this function for hit testing on [RenderBox]s.
+  ///  * [SliverHitTestResult.addWithAxisOffset], which is a public wrapper
+  ///    around this function for hit testing on [RenderSliver]s.
+  @protected
+  void pushOffset(Offset offset) {
+    assert(offset != null);
+    _localTransforms.add(_OffsetTransformPart(offset));
+  }
+
+  /// Removes the last transform added via [pushTransform] or [pushOffset].
+  ///
+  /// This method is only to be used by subclasses, which must provide
+  /// coordinate space specific public wrappers around this function for their
+  /// users (see [BoxHitTestResult.addWithPaintTransform] for such an example).
+  ///
+  /// This method must be called after hit testing is done on a child that
+  /// required a call to [pushTransform] or [pushOffset].
+  ///
+  /// See also:
+  ///
+  ///  * [pushTransform] and [pushOffset], which describes the use case of this
+  ///    function pair in more details.
+  @protected
+  void popTransform() {
+    if (_localTransforms.isNotEmpty)
+      _localTransforms.removeLast();
+    else
+      _transforms.removeLast();
+    assert(_transforms.isNotEmpty);
+  }
+
+  bool _debugVectorMoreOrLessEquals(Vector4 a, Vector4 b, { double epsilon = precisionErrorTolerance }) {
+    bool result = true;
+    assert(() {
+      final Vector4 difference = a - b;
+      result = difference.storage.every((double component) => component.abs() < epsilon);
+      return true;
+    }());
+    return result;
+  }
+
+  @override
+  String toString() => 'HitTestResult(${_path.isEmpty ? "<empty path>" : _path.join(", ")})';
+}
diff --git a/lib/src/gestures/long_press.dart b/lib/src/gestures/long_press.dart
new file mode 100644
index 0000000..639c778
--- /dev/null
+++ b/lib/src/gestures/long_press.dart
@@ -0,0 +1,555 @@
+// Copyright 2014 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.
+
+
+import 'arena.dart';
+import 'constants.dart';
+import 'events.dart';
+import 'recognizer.dart';
+import 'velocity_tracker.dart';
+
+/// Callback signature for [LongPressGestureRecognizer.onLongPress].
+///
+/// Called when a pointer has remained in contact with the screen at the
+/// same location for a long period of time.
+typedef GestureLongPressCallback = void Function();
+
+/// Callback signature for [LongPressGestureRecognizer.onLongPressUp].
+///
+/// Called when a pointer stops contacting the screen after a long press
+/// gesture was detected.
+typedef GestureLongPressUpCallback = void Function();
+
+/// Callback signature for [LongPressGestureRecognizer.onLongPressStart].
+///
+/// Called when a pointer has remained in contact with the screen at the
+/// same location for a long period of time. Also reports the long press down
+/// position.
+typedef GestureLongPressStartCallback = void Function(LongPressStartDetails details);
+
+/// Callback signature for [LongPressGestureRecognizer.onLongPressMoveUpdate].
+///
+/// Called when a pointer is moving after being held in contact at the same
+/// location for a long period of time. Reports the new position and its offset
+/// from the original down position.
+typedef GestureLongPressMoveUpdateCallback = void Function(LongPressMoveUpdateDetails details);
+
+/// Callback signature for [LongPressGestureRecognizer.onLongPressEnd].
+///
+/// Called when a pointer stops contacting the screen after a long press
+/// gesture was detected. Also reports the position where the pointer stopped
+/// contacting the screen.
+typedef GestureLongPressEndCallback = void Function(LongPressEndDetails details);
+
+/// Details for callbacks that use [GestureLongPressStartCallback].
+///
+/// See also:
+///
+///  * [LongPressGestureRecognizer.onLongPressStart], which uses [GestureLongPressStartCallback].
+///  * [LongPressMoveUpdateDetails], the details for [GestureLongPressMoveUpdateCallback]
+///  * [LongPressEndDetails], the details for [GestureLongPressEndCallback].
+class LongPressStartDetails {
+  /// Creates the details for a [GestureLongPressStartCallback].
+  ///
+  /// The [globalPosition] argument must not be null.
+  const LongPressStartDetails({
+    this.globalPosition = Offset.zero,
+    Offset? localPosition,
+  }) : assert(globalPosition != null),
+       localPosition = localPosition ?? globalPosition;
+
+  /// The global position at which the pointer contacted the screen.
+  final Offset globalPosition;
+
+  /// The local position at which the pointer contacted the screen.
+  final Offset localPosition;
+}
+
+/// Details for callbacks that use [GestureLongPressMoveUpdateCallback].
+///
+/// See also:
+///
+///  * [LongPressGestureRecognizer.onLongPressMoveUpdate], which uses [GestureLongPressMoveUpdateCallback].
+///  * [LongPressEndDetails], the details for [GestureLongPressEndCallback]
+///  * [LongPressStartDetails], the details for [GestureLongPressStartCallback].
+class LongPressMoveUpdateDetails {
+  /// Creates the details for a [GestureLongPressMoveUpdateCallback].
+  ///
+  /// The [globalPosition] and [offsetFromOrigin] arguments must not be null.
+  const LongPressMoveUpdateDetails({
+    this.globalPosition = Offset.zero,
+    Offset? localPosition,
+    this.offsetFromOrigin = Offset.zero,
+    Offset? localOffsetFromOrigin,
+  }) : assert(globalPosition != null),
+       assert(offsetFromOrigin != null),
+       localPosition = localPosition ?? globalPosition,
+       localOffsetFromOrigin = localOffsetFromOrigin ?? offsetFromOrigin;
+
+  /// The global position of the pointer when it triggered this update.
+  final Offset globalPosition;
+
+  /// The local position of the pointer when it triggered this update.
+  final Offset localPosition;
+
+  /// A delta offset from the point where the long press drag initially contacted
+  /// the screen to the point where the pointer is currently located (the
+  /// present [globalPosition]) when this callback is triggered.
+  final Offset offsetFromOrigin;
+
+  /// A local delta offset from the point where the long press drag initially contacted
+  /// the screen to the point where the pointer is currently located (the
+  /// present [localPosition]) when this callback is triggered.
+  final Offset localOffsetFromOrigin;
+}
+
+/// Details for callbacks that use [GestureLongPressEndCallback].
+///
+/// See also:
+///
+///  * [LongPressGestureRecognizer.onLongPressEnd], which uses [GestureLongPressEndCallback].
+///  * [LongPressMoveUpdateDetails], the details for [GestureLongPressMoveUpdateCallback].
+///  * [LongPressStartDetails], the details for [GestureLongPressStartCallback].
+class LongPressEndDetails {
+  /// Creates the details for a [GestureLongPressEndCallback].
+  ///
+  /// The [globalPosition] argument must not be null.
+  const LongPressEndDetails({
+    this.globalPosition = Offset.zero,
+    Offset? localPosition,
+    this.velocity = Velocity.zero,
+  }) : assert(globalPosition != null),
+       localPosition = localPosition ?? globalPosition;
+
+  /// The global position at which the pointer lifted from the screen.
+  final Offset globalPosition;
+
+  /// The local position at which the pointer contacted the screen.
+  final Offset localPosition;
+
+  /// The pointer's velocity when it stopped contacting the screen.
+  ///
+  /// Defaults to zero if not specified in the constructor.
+  final Velocity velocity;
+}
+
+/// Recognizes when the user has pressed down at the same location for a long
+/// period of time.
+///
+/// The gesture must not deviate in position from its touch down point for 500ms
+/// until it's recognized. Once the gesture is accepted, the finger can be
+/// moved, triggering [onLongPressMoveUpdate] callbacks, unless the
+/// [postAcceptSlopTolerance] constructor argument is specified.
+///
+/// [LongPressGestureRecognizer] may compete on pointer events of
+/// [kPrimaryButton], [kSecondaryButton], and/or [kTertiaryButton] if at least
+/// one corresponding callback is non-null. If it has no callbacks, it is a no-op.
+class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
+  /// Creates a long-press gesture recognizer.
+  ///
+  /// Consider assigning the [onLongPressStart] callback after creating this
+  /// object.
+  ///
+  /// The [postAcceptSlopTolerance] argument can be used to specify a maximum
+  /// allowed distance for the gesture to deviate from the starting point once
+  /// the long press has triggered. If the gesture deviates past that point,
+  /// subsequent callbacks ([onLongPressMoveUpdate], [onLongPressUp],
+  /// [onLongPressEnd]) will stop. Defaults to null, which means the gesture
+  /// can be moved without limit once the long press is accepted.
+  ///
+  /// The [duration] argument can be used to overwrite the default duration
+  /// after which the long press will be recognized.
+  LongPressGestureRecognizer({
+    Duration? duration,
+    double? postAcceptSlopTolerance,
+    PointerDeviceKind? kind,
+    Object? debugOwner,
+  }) : super(
+          deadline: duration ?? kLongPressTimeout,
+          postAcceptSlopTolerance: postAcceptSlopTolerance,
+          kind: kind,
+          debugOwner: debugOwner,
+        );
+
+  bool _longPressAccepted = false;
+  OffsetPair? _longPressOrigin;
+  // The buttons sent by `PointerDownEvent`. If a `PointerMoveEvent` comes with a
+  // different set of buttons, the gesture is canceled.
+  int? _initialButtons;
+
+  /// Called when a long press gesture by a primary button has been recognized.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [onLongPressStart], which has the same timing but has data for the
+  ///    press location.
+  GestureLongPressCallback? onLongPress;
+
+  /// Called when a long press gesture by a primary button has been recognized.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [onLongPress], which has the same timing but without details.
+  ///  * [LongPressStartDetails], which is passed as an argument to this callback.
+  GestureLongPressStartCallback? onLongPressStart;
+
+  /// Called when moving after the long press by a primary button is recognized.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [LongPressMoveUpdateDetails], which is passed as an argument to this
+  ///    callback.
+  GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate;
+
+  /// Called when the pointer stops contacting the screen after a long-press
+  /// by a primary button.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [onLongPressEnd], which has the same timing but has data for the up
+  ///    gesture location.
+  GestureLongPressUpCallback? onLongPressUp;
+
+  /// Called when the pointer stops contacting the screen after a long-press
+  /// by a primary button.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [onLongPressUp], which has the same timing, but without details.
+  ///  * [LongPressEndDetails], which is passed as an argument to this
+  ///    callback.
+  GestureLongPressEndCallback? onLongPressEnd;
+
+  /// Called when a long press gesture by a secondary button has been
+  /// recognized.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [onSecondaryLongPressStart], which has the same timing but has data for
+  ///    the press location.
+  GestureLongPressCallback? onSecondaryLongPress;
+
+  /// Called when a long press gesture by a secondary button has been recognized.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [onSecondaryLongPress], which has the same timing but without details.
+  ///  * [LongPressStartDetails], which is passed as an argument to this
+  ///    callback.
+  GestureLongPressStartCallback? onSecondaryLongPressStart;
+
+  /// Called when moving after the long press by a secondary button is
+  /// recognized.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [LongPressMoveUpdateDetails], which is passed as an argument to this
+  ///    callback.
+  GestureLongPressMoveUpdateCallback? onSecondaryLongPressMoveUpdate;
+
+  /// Called when the pointer stops contacting the screen after a long-press by
+  /// a secondary button.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [onSecondaryLongPressEnd], which has the same timing but has data for
+  ///    the up gesture location.
+  GestureLongPressUpCallback? onSecondaryLongPressUp;
+
+  /// Called when the pointer stops contacting the screen after a long-press by
+  /// a secondary button.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [onSecondaryLongPressUp], which has the same timing, but without
+  ///    details.
+  ///  * [LongPressEndDetails], which is passed as an argument to this callback.
+  GestureLongPressEndCallback? onSecondaryLongPressEnd;
+
+  /// Called when a long press gesture by a tertiary button has been
+  /// recognized.
+  ///
+  /// See also:
+  ///
+  ///  * [kTertiaryButton], the button this callback responds to.
+  ///  * [onTertiaryLongPressStart], which has the same timing but has data for
+  ///    the press location.
+  GestureLongPressCallback? onTertiaryLongPress;
+
+  /// Called when a long press gesture by a tertiary button has been recognized.
+  ///
+  /// See also:
+  ///
+  ///  * [kTertiaryButton], the button this callback responds to.
+  ///  * [onTertiaryLongPress], which has the same timing but without details.
+  ///  * [LongPressStartDetails], which is passed as an argument to this
+  ///    callback.
+  GestureLongPressStartCallback? onTertiaryLongPressStart;
+
+  /// Called when moving after the long press by a tertiary button is
+  /// recognized.
+  ///
+  /// See also:
+  ///
+  ///  * [kTertiaryButton], the button this callback responds to.
+  ///  * [LongPressMoveUpdateDetails], which is passed as an argument to this
+  ///    callback.
+  GestureLongPressMoveUpdateCallback? onTertiaryLongPressMoveUpdate;
+
+  /// Called when the pointer stops contacting the screen after a long-press by
+  /// a tertiary button.
+  ///
+  /// See also:
+  ///
+  ///  * [kTertiaryButton], the button this callback responds to.
+  ///  * [onTertiaryLongPressEnd], which has the same timing but has data for
+  ///    the up gesture location.
+  GestureLongPressUpCallback? onTertiaryLongPressUp;
+
+  /// Called when the pointer stops contacting the screen after a long-press by
+  /// a tertiary button.
+  ///
+  /// See also:
+  ///
+  ///  * [kTertiaryButton], the button this callback responds to.
+  ///  * [onTertiaryLongPressUp], which has the same timing, but without
+  ///    details.
+  ///  * [LongPressEndDetails], which is passed as an argument to this callback.
+  GestureLongPressEndCallback? onTertiaryLongPressEnd;
+
+  VelocityTracker? _velocityTracker;
+
+  @override
+  bool isPointerAllowed(PointerDownEvent event) {
+    switch (event.buttons) {
+      case kPrimaryButton:
+        if (onLongPressStart == null &&
+            onLongPress == null &&
+            onLongPressMoveUpdate == null &&
+            onLongPressEnd == null &&
+            onLongPressUp == null)
+          return false;
+        break;
+      case kSecondaryButton:
+        if (onSecondaryLongPressStart == null &&
+            onSecondaryLongPress == null &&
+            onSecondaryLongPressMoveUpdate == null &&
+            onSecondaryLongPressEnd == null &&
+            onSecondaryLongPressUp == null)
+          return false;
+        break;
+      case kTertiaryButton:
+        if (onTertiaryLongPressStart == null &&
+            onTertiaryLongPress == null &&
+            onTertiaryLongPressMoveUpdate == null &&
+            onTertiaryLongPressEnd == null &&
+            onTertiaryLongPressUp == null)
+          return false;
+        break;
+      default:
+        return false;
+    }
+    return super.isPointerAllowed(event);
+  }
+
+  @override
+  void didExceedDeadline() {
+    // Exceeding the deadline puts the gesture in the accepted state.
+    resolve(GestureDisposition.accepted);
+    _longPressAccepted = true;
+    super.acceptGesture(primaryPointer!);
+    _checkLongPressStart();
+  }
+
+  @override
+  void handlePrimaryPointer(PointerEvent event) {
+    if (!event.synthesized) {
+      if (event is PointerDownEvent) {
+        _velocityTracker = VelocityTracker.withKind(event.kind);
+        _velocityTracker!.addPosition(event.timeStamp, event.localPosition);
+      }
+      if (event is PointerMoveEvent) {
+        assert(_velocityTracker != null);
+        _velocityTracker!.addPosition(event.timeStamp, event.localPosition);
+      }
+    }
+
+    if (event is PointerUpEvent) {
+      if (_longPressAccepted == true) {
+        _checkLongPressEnd(event);
+      } else {
+        // Pointer is lifted before timeout.
+        resolve(GestureDisposition.rejected);
+      }
+      _reset();
+    } else if (event is PointerCancelEvent) {
+      _reset();
+    } else if (event is PointerDownEvent) {
+      // The first touch.
+      _longPressOrigin = OffsetPair.fromEventPosition(event);
+      _initialButtons = event.buttons;
+    } else if (event is PointerMoveEvent) {
+      if (event.buttons != _initialButtons) {
+        resolve(GestureDisposition.rejected);
+        stopTrackingPointer(primaryPointer!);
+      } else if (_longPressAccepted) {
+        _checkLongPressMoveUpdate(event);
+      }
+    }
+  }
+
+  void _checkLongPressStart() {
+    switch (_initialButtons) {
+      case kPrimaryButton:
+        if (onLongPressStart != null) {
+          final LongPressStartDetails details = LongPressStartDetails(
+            globalPosition: _longPressOrigin!.global,
+            localPosition: _longPressOrigin!.local,
+          );
+          invokeCallback<void>('onLongPressStart', () => onLongPressStart!(details));
+        }
+        if (onLongPress != null) {
+          invokeCallback<void>('onLongPress', onLongPress!);
+        }
+        break;
+      case kSecondaryButton:
+        if (onSecondaryLongPressStart != null) {
+          final LongPressStartDetails details = LongPressStartDetails(
+            globalPosition: _longPressOrigin!.global,
+            localPosition: _longPressOrigin!.local,
+          );
+          invokeCallback<void>(
+              'onSecondaryLongPressStart', () => onSecondaryLongPressStart!(details));
+        }
+        if (onSecondaryLongPress != null) {
+          invokeCallback<void>('onSecondaryLongPress', onSecondaryLongPress!);
+        }
+        break;
+      case kTertiaryButton:
+        if (onTertiaryLongPressStart != null) {
+          final LongPressStartDetails details = LongPressStartDetails(
+            globalPosition: _longPressOrigin!.global,
+            localPosition: _longPressOrigin!.local,
+          );
+          invokeCallback<void>(
+              'onTertiaryLongPressStart', () => onTertiaryLongPressStart!(details));
+        }
+        if (onTertiaryLongPress != null) {
+          invokeCallback<void>('onTertiaryLongPress', onTertiaryLongPress!);
+        }
+        break;
+      default:
+        assert(false, 'Unhandled button $_initialButtons');
+    }
+  }
+
+  void _checkLongPressMoveUpdate(PointerEvent event) {
+    final LongPressMoveUpdateDetails details = LongPressMoveUpdateDetails(
+      globalPosition: event.position,
+      localPosition: event.localPosition,
+      offsetFromOrigin: event.position - _longPressOrigin!.global,
+      localOffsetFromOrigin: event.localPosition - _longPressOrigin!.local,
+    );
+    switch (_initialButtons) {
+      case kPrimaryButton:
+        if (onLongPressMoveUpdate != null) {
+          invokeCallback<void>('onLongPressMoveUpdate',
+            () => onLongPressMoveUpdate!(details));
+        }
+        break;
+      case kSecondaryButton:
+        if (onSecondaryLongPressMoveUpdate != null) {
+          invokeCallback<void>('onSecondaryLongPressMoveUpdate',
+            () => onSecondaryLongPressMoveUpdate!(details));
+        }
+        break;
+      case kTertiaryButton:
+        if (onTertiaryLongPressMoveUpdate != null) {
+          invokeCallback<void>('onTertiaryLongPressMoveUpdate',
+                  () => onTertiaryLongPressMoveUpdate!(details));
+        }
+        break;
+      default:
+        assert(false, 'Unhandled button $_initialButtons');
+    }
+  }
+
+  void _checkLongPressEnd(PointerEvent event) {
+    final VelocityEstimate? estimate = _velocityTracker!.getVelocityEstimate();
+    final Velocity velocity = estimate == null
+        ? Velocity.zero
+        : Velocity(pixelsPerSecond: estimate.pixelsPerSecond);
+    final LongPressEndDetails details = LongPressEndDetails(
+      globalPosition: event.position,
+      localPosition: event.localPosition,
+      velocity: velocity,
+    );
+
+    _velocityTracker = null;
+    switch (_initialButtons) {
+      case kPrimaryButton:
+        if (onLongPressEnd != null) {
+          invokeCallback<void>('onLongPressEnd', () => onLongPressEnd!(details));
+        }
+        if (onLongPressUp != null) {
+          invokeCallback<void>('onLongPressUp', onLongPressUp!);
+        }
+        break;
+      case kSecondaryButton:
+        if (onSecondaryLongPressEnd != null) {
+          invokeCallback<void>('onSecondaryLongPressEnd', () => onSecondaryLongPressEnd!(details));
+        }
+        if (onSecondaryLongPressUp != null) {
+          invokeCallback<void>('onSecondaryLongPressUp', onSecondaryLongPressUp!);
+        }
+        break;
+      case kTertiaryButton:
+        if (onTertiaryLongPressEnd != null) {
+          invokeCallback<void>('onTertiaryLongPressEnd', () => onTertiaryLongPressEnd!(details));
+        }
+        if (onTertiaryLongPressUp != null) {
+          invokeCallback<void>('onTertiaryLongPressUp', onTertiaryLongPressUp!);
+        }
+        break;
+      default:
+        assert(false, 'Unhandled button $_initialButtons');
+    }
+  }
+
+  void _reset() {
+    _longPressAccepted = false;
+    _longPressOrigin = null;
+    _initialButtons = null;
+    _velocityTracker = null;
+  }
+
+  @override
+  void resolve(GestureDisposition disposition) {
+    if (_longPressAccepted && disposition == GestureDisposition.rejected) {
+      // This can happen if the gesture has been canceled. For example when
+      // the buttons have changed.
+      _reset();
+    }
+    super.resolve(disposition);
+  }
+
+  @override
+  void acceptGesture(int pointer) {
+    // Winning the arena isn't important here since it may happen from a sweep.
+    // Explicitly exceeding the deadline puts the gesture in accepted state.
+  }
+
+  @override
+  String get debugDescription => 'long press';
+}
diff --git a/lib/src/gestures/lsq_solver.dart b/lib/src/gestures/lsq_solver.dart
new file mode 100644
index 0000000..4513171
--- /dev/null
+++ b/lib/src/gestures/lsq_solver.dart
@@ -0,0 +1,190 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+import 'dart:typed_data';
+
+import 'package:flute/foundation.dart';
+
+// TODO(abarth): Consider using vector_math.
+class _Vector {
+  _Vector(int size)
+    : _offset = 0,
+      _length = size,
+      _elements = Float64List(size);
+
+  _Vector.fromVOL(List<double> values, int offset, int length)
+    : _offset = offset,
+      _length = length,
+      _elements = values;
+
+  final int _offset;
+
+  final int _length;
+
+  final List<double> _elements;
+
+  double operator [](int i) => _elements[i + _offset];
+  void operator []=(int i, double value) {
+    _elements[i + _offset] = value;
+  }
+
+  double operator *(_Vector a) {
+    double result = 0.0;
+    for (int i = 0; i < _length; i += 1)
+      result += this[i] * a[i];
+    return result;
+  }
+
+  double norm() => math.sqrt(this * this);
+}
+
+// TODO(abarth): Consider using vector_math.
+class _Matrix {
+  _Matrix(int rows, int cols)
+    : _columns = cols,
+      _elements = Float64List(rows * cols);
+
+  final int _columns;
+  final List<double> _elements;
+
+  double get(int row, int col) => _elements[row * _columns + col];
+  void set(int row, int col, double value) {
+    _elements[row * _columns + col] = value;
+  }
+
+  _Vector getRow(int row) => _Vector.fromVOL(
+    _elements,
+    row * _columns,
+    _columns,
+  );
+}
+
+/// An nth degree polynomial fit to a dataset.
+class PolynomialFit {
+  /// Creates a polynomial fit of the given degree.
+  ///
+  /// There are n + 1 coefficients in a fit of degree n.
+  PolynomialFit(int degree) : coefficients = Float64List(degree + 1);
+
+  /// The polynomial coefficients of the fit.
+  final List<double> coefficients;
+
+  /// An indicator of the quality of the fit.
+  ///
+  /// Larger values indicate greater quality.
+  late double confidence;
+}
+
+/// Uses the least-squares algorithm to fit a polynomial to a set of data.
+class LeastSquaresSolver {
+  /// Creates a least-squares solver.
+  ///
+  /// The [x], [y], and [w] arguments must not be null.
+  LeastSquaresSolver(this.x, this.y, this.w)
+    : assert(x.length == y.length),
+      assert(y.length == w.length);
+
+  /// The x-coordinates of each data point.
+  final List<double> x;
+
+  /// The y-coordinates of each data point.
+  final List<double> y;
+
+  /// The weight to use for each data point.
+  final List<double> w;
+
+  /// Fits a polynomial of the given degree to the data points.
+  ///
+  /// When there is not enough data to fit a curve null is returned.
+  PolynomialFit? solve(int degree) {
+    if (degree > x.length) // Not enough data to fit a curve.
+      return null;
+
+    final PolynomialFit result = PolynomialFit(degree);
+
+    // Shorthands for the purpose of notation equivalence to original C++ code.
+    final int m = x.length;
+    final int n = degree + 1;
+
+    // Expand the X vector to a matrix A, pre-multiplied by the weights.
+    final _Matrix a = _Matrix(n, m);
+    for (int h = 0; h < m; h += 1) {
+      a.set(0, h, w[h]);
+      for (int i = 1; i < n; i += 1)
+        a.set(i, h, a.get(i - 1, h) * x[h]);
+    }
+
+    // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
+
+    // Orthonormal basis, column-major ordVectorer.
+    final _Matrix q = _Matrix(n, m);
+    // Upper triangular matrix, row-major order.
+    final _Matrix r = _Matrix(n, n);
+    for (int j = 0; j < n; j += 1) {
+      for (int h = 0; h < m; h += 1)
+        q.set(j, h, a.get(j, h));
+      for (int i = 0; i < j; i += 1) {
+        final double dot = q.getRow(j) * q.getRow(i);
+        for (int h = 0; h < m; h += 1)
+          q.set(j, h, q.get(j, h) - dot * q.get(i, h));
+      }
+
+      final double norm = q.getRow(j).norm();
+      if (norm < precisionErrorTolerance) {
+        // Vectors are linearly dependent or zero so no solution.
+        return null;
+      }
+
+      final double inverseNorm = 1.0 / norm;
+      for (int h = 0; h < m; h += 1)
+        q.set(j, h, q.get(j, h) * inverseNorm);
+      for (int i = 0; i < n; i += 1)
+        r.set(j, i, i < j ? 0.0 : q.getRow(j) * a.getRow(i));
+    }
+
+    // Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
+    // We just work from bottom-right to top-left calculating B's coefficients.
+    final _Vector wy = _Vector(m);
+    for (int h = 0; h < m; h += 1)
+      wy[h] = y[h] * w[h];
+    for (int i = n - 1; i >= 0; i -= 1) {
+      result.coefficients[i] = q.getRow(i) * wy;
+      for (int j = n - 1; j > i; j -= 1)
+        result.coefficients[i] -= r.get(i, j) * result.coefficients[j];
+      result.coefficients[i] /= r.get(i, i);
+    }
+
+    // Calculate the coefficient of determination (confidence) as:
+    //   1 - (sumSquaredError / sumSquaredTotal)
+    // ...where sumSquaredError is the residual sum of squares (variance of the
+    // error), and sumSquaredTotal is the total sum of squares (variance of the
+    // data) where each has been weighted.
+    double yMean = 0.0;
+    for (int h = 0; h < m; h += 1)
+      yMean += y[h];
+    yMean /= m;
+
+    double sumSquaredError = 0.0;
+    double sumSquaredTotal = 0.0;
+    for (int h = 0; h < m; h += 1) {
+      double term = 1.0;
+      double err = y[h] - result.coefficients[0];
+      for (int i = 1; i < n; i += 1) {
+        term *= x[h];
+        err -= term * result.coefficients[i];
+      }
+      sumSquaredError += w[h] * w[h] * err * err;
+      final double v = y[h] - yMean;
+      sumSquaredTotal += w[h] * w[h] * v * v;
+    }
+
+    result.confidence = sumSquaredTotal <= precisionErrorTolerance ? 1.0 :
+                          1.0 - (sumSquaredError / sumSquaredTotal);
+
+    return result;
+  }
+
+}
diff --git a/lib/src/gestures/monodrag.dart b/lib/src/gestures/monodrag.dart
new file mode 100644
index 0000000..65c1e7f
--- /dev/null
+++ b/lib/src/gestures/monodrag.dart
@@ -0,0 +1,604 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'arena.dart';
+import 'constants.dart';
+import 'drag_details.dart';
+import 'events.dart';
+import 'recognizer.dart';
+import 'velocity_tracker.dart';
+
+enum _DragState {
+  ready,
+  possible,
+  accepted,
+}
+
+/// Signature for when a pointer that was previously in contact with the screen
+/// and moving is no longer in contact with the screen.
+///
+/// The velocity at which the pointer was moving when it stopped contacting
+/// the screen is available in the `details`.
+///
+/// Used by [DragGestureRecognizer.onEnd].
+typedef GestureDragEndCallback = void Function(DragEndDetails details);
+
+/// Signature for when the pointer that previously triggered a
+/// [GestureDragDownCallback] did not complete.
+///
+/// Used by [DragGestureRecognizer.onCancel].
+typedef GestureDragCancelCallback = void Function();
+
+/// Signature for a function that builds a [VelocityTracker].
+///
+/// Used by [DragGestureRecognizer.velocityTrackerBuilder].
+typedef GestureVelocityTrackerBuilder = VelocityTracker Function(PointerEvent event);
+
+/// Recognizes movement.
+///
+/// In contrast to [MultiDragGestureRecognizer], [DragGestureRecognizer]
+/// recognizes a single gesture sequence for all the pointers it watches, which
+/// means that the recognizer has at most one drag sequence active at any given
+/// time regardless of how many pointers are in contact with the screen.
+///
+/// [DragGestureRecognizer] is not intended to be used directly. Instead,
+/// consider using one of its subclasses to recognize specific types for drag
+/// gestures.
+///
+/// [DragGestureRecognizer] competes on pointer events of [kPrimaryButton]
+/// only when it has at least one non-null callback. If it has no callbacks, it
+/// is a no-op.
+///
+/// See also:
+///
+///  * [HorizontalDragGestureRecognizer], for left and right drags.
+///  * [VerticalDragGestureRecognizer], for up and down drags.
+///  * [PanGestureRecognizer], for drags that are not locked to a single axis.
+abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
+  /// Initialize the object.
+  ///
+  /// [dragStartBehavior] must not be null.
+  ///
+  /// {@macro flutter.gestures.GestureRecognizer.kind}
+  DragGestureRecognizer({
+    Object? debugOwner,
+    PointerDeviceKind? kind,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.velocityTrackerBuilder = _defaultBuilder,
+  }) : assert(dragStartBehavior != null),
+       super(debugOwner: debugOwner, kind: kind);
+
+  static VelocityTracker _defaultBuilder(PointerEvent event) => VelocityTracker.withKind(event.kind);
+  /// Configure the behavior of offsets sent to [onStart].
+  ///
+  /// If set to [DragStartBehavior.start], the [onStart] callback will be called
+  /// at the time and position when this gesture recognizer wins the arena. If
+  /// [DragStartBehavior.down], [onStart] will be called at the time and
+  /// position when a down event was first detected.
+  ///
+  /// For more information about the gesture arena:
+  /// https://flutter.dev/docs/development/ui/advanced/gestures#gesture-disambiguation
+  ///
+  /// By default, the drag start behavior is [DragStartBehavior.start].
+  ///
+  /// ## Example:
+  ///
+  /// A finger presses down on the screen with offset (500.0, 500.0), and then
+  /// moves to position (510.0, 500.0) before winning the arena. With
+  /// [dragStartBehavior] set to [DragStartBehavior.down], the [onStart]
+  /// callback will be called at the time corresponding to the touch's position
+  /// at (500.0, 500.0). If it is instead set to [DragStartBehavior.start],
+  /// [onStart] will be called at the time corresponding to the touch's position
+  /// at (510.0, 500.0).
+  DragStartBehavior dragStartBehavior;
+
+  /// A pointer has contacted the screen with a primary button and might begin
+  /// to move.
+  ///
+  /// The position of the pointer is provided in the callback's `details`
+  /// argument, which is a [DragDownDetails] object.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [DragDownDetails], which is passed as an argument to this callback.
+  GestureDragDownCallback? onDown;
+
+  /// A pointer has contacted the screen with a primary button and has begun to
+  /// move.
+  ///
+  /// The position of the pointer is provided in the callback's `details`
+  /// argument, which is a [DragStartDetails] object.
+  ///
+  /// Depending on the value of [dragStartBehavior], this function will be
+  /// called on the initial touch down, if set to [DragStartBehavior.down] or
+  /// when the drag gesture is first detected, if set to
+  /// [DragStartBehavior.start].
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [DragStartDetails], which is passed as an argument to this callback.
+  GestureDragStartCallback? onStart;
+
+  /// A pointer that is in contact with the screen with a primary button and
+  /// moving has moved again.
+  ///
+  /// The distance traveled by the pointer since the last update is provided in
+  /// the callback's `details` argument, which is a [DragUpdateDetails] object.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [DragUpdateDetails], which is passed as an argument to this callback.
+  GestureDragUpdateCallback? onUpdate;
+
+  /// A pointer that was previously in contact with the screen with a primary
+  /// button and moving is no longer in contact with the screen and was moving
+  /// at a specific velocity when it stopped contacting the screen.
+  ///
+  /// The velocity is provided in the callback's `details` argument, which is a
+  /// [DragEndDetails] object.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [DragEndDetails], which is passed as an argument to this callback.
+  GestureDragEndCallback? onEnd;
+
+  /// The pointer that previously triggered [onDown] did not complete.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  GestureDragCancelCallback? onCancel;
+
+  /// The minimum distance an input pointer drag must have moved to
+  /// to be considered a fling gesture.
+  ///
+  /// This value is typically compared with the distance traveled along the
+  /// scrolling axis. If null then [kTouchSlop] is used.
+  double? minFlingDistance;
+
+  /// The minimum velocity for an input pointer drag to be considered fling.
+  ///
+  /// This value is typically compared with the magnitude of fling gesture's
+  /// velocity along the scrolling axis. If null then [kMinFlingVelocity]
+  /// is used.
+  double? minFlingVelocity;
+
+  /// Fling velocity magnitudes will be clamped to this value.
+  ///
+  /// If null then [kMaxFlingVelocity] is used.
+  double? maxFlingVelocity;
+
+  /// Determines the type of velocity estimation method to use for a potential
+  /// drag gesture, when a new pointer is added.
+  ///
+  /// To estimate the velocity of a gesture, [DragGestureRecognizer] calls
+  /// [velocityTrackerBuilder] when it starts to track a new pointer in
+  /// [addAllowedPointer], and add subsequent updates on the pointer to the
+  /// resulting velocity tracker, until the gesture recognizer stops tracking
+  /// the pointer. This allows you to specify a different velocity estimation
+  /// strategy for each allowed pointer added, by changing the type of velocity
+  /// tracker this [GestureVelocityTrackerBuilder] returns.
+  ///
+  /// If left unspecified the default [velocityTrackerBuilder] creates a new
+  /// [VelocityTracker] for every pointer added.
+  ///
+  /// See also:
+  ///
+  ///  * [VelocityTracker], a velocity tracker that uses least squares estimation
+  ///    on the 20 most recent pointer data samples. It's a well-rounded velocity
+  ///    tracker and is used by default.
+  ///  * [IOSScrollViewFlingVelocityTracker], a specialized velocity tracker for
+  ///    determining the initial fling velocity for a [Scrollable] on iOS, to
+  ///    match the native behavior on that platform.
+  GestureVelocityTrackerBuilder velocityTrackerBuilder;
+
+  _DragState _state = _DragState.ready;
+  late OffsetPair _initialPosition;
+  late OffsetPair _pendingDragOffset;
+  Duration? _lastPendingEventTimestamp;
+  // The buttons sent by `PointerDownEvent`. If a `PointerMoveEvent` comes with a
+  // different set of buttons, the gesture is canceled.
+  int? _initialButtons;
+  Matrix4? _lastTransform;
+
+  /// Distance moved in the global coordinate space of the screen in drag direction.
+  ///
+  /// If drag is only allowed along a defined axis, this value may be negative to
+  /// differentiate the direction of the drag.
+  late double _globalDistanceMoved;
+
+  /// Determines if a gesture is a fling or not based on velocity.
+  ///
+  /// A fling calls its gesture end callback with a velocity, allowing the
+  /// provider of the callback to respond by carrying the gesture forward with
+  /// inertia, for example.
+  bool isFlingGesture(VelocityEstimate estimate, PointerDeviceKind kind);
+
+  Offset _getDeltaForDetails(Offset delta);
+  double? _getPrimaryValueFromOffset(Offset value);
+  bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind);
+
+  final Map<int, VelocityTracker> _velocityTrackers = <int, VelocityTracker>{};
+
+  @override
+  bool isPointerAllowed(PointerEvent event) {
+    if (_initialButtons == null) {
+      switch (event.buttons) {
+        case kPrimaryButton:
+          if (onDown == null &&
+              onStart == null &&
+              onUpdate == null &&
+              onEnd == null &&
+              onCancel == null)
+            return false;
+          break;
+        default:
+          return false;
+      }
+    } else {
+      // There can be multiple drags simultaneously. Their effects are combined.
+      if (event.buttons != _initialButtons) {
+        return false;
+      }
+    }
+    return super.isPointerAllowed(event as PointerDownEvent);
+  }
+
+  @override
+  void addAllowedPointer(PointerEvent event) {
+    startTrackingPointer(event.pointer, event.transform);
+    _velocityTrackers[event.pointer] = velocityTrackerBuilder(event);
+    if (_state == _DragState.ready) {
+      _state = _DragState.possible;
+      _initialPosition = OffsetPair(global: event.position, local: event.localPosition);
+      _initialButtons = event.buttons;
+      _pendingDragOffset = OffsetPair.zero;
+      _globalDistanceMoved = 0.0;
+      _lastPendingEventTimestamp = event.timeStamp;
+      _lastTransform = event.transform;
+      _checkDown();
+    } else if (_state == _DragState.accepted) {
+      resolve(GestureDisposition.accepted);
+    }
+  }
+
+  @override
+  void handleEvent(PointerEvent event) {
+    assert(_state != _DragState.ready);
+    if (!event.synthesized
+        && (event is PointerDownEvent || event is PointerMoveEvent)) {
+      final VelocityTracker tracker = _velocityTrackers[event.pointer]!;
+      assert(tracker != null);
+      tracker.addPosition(event.timeStamp, event.localPosition);
+    }
+
+    if (event is PointerMoveEvent) {
+      if (event.buttons != _initialButtons) {
+        _giveUpPointer(event.pointer);
+        return;
+      }
+      if (_state == _DragState.accepted) {
+        _checkUpdate(
+          sourceTimeStamp: event.timeStamp,
+          delta: _getDeltaForDetails(event.localDelta),
+          primaryDelta: _getPrimaryValueFromOffset(event.localDelta),
+          globalPosition: event.position,
+          localPosition: event.localPosition,
+        );
+      } else {
+        _pendingDragOffset += OffsetPair(local: event.localDelta, global: event.delta);
+        _lastPendingEventTimestamp = event.timeStamp;
+        _lastTransform = event.transform;
+        final Offset movedLocally = _getDeltaForDetails(event.localDelta);
+        final Matrix4? localToGlobalTransform = event.transform == null ? null : Matrix4.tryInvert(event.transform!);
+        _globalDistanceMoved += PointerEvent.transformDeltaViaPositions(
+          transform: localToGlobalTransform,
+          untransformedDelta: movedLocally,
+          untransformedEndPosition: event.localPosition,
+        ).distance * (_getPrimaryValueFromOffset(movedLocally) ?? 1).sign;
+        if (_hasSufficientGlobalDistanceToAccept(event.kind))
+          resolve(GestureDisposition.accepted);
+      }
+    }
+    if (event is PointerUpEvent || event is PointerCancelEvent) {
+      _giveUpPointer(
+        event.pointer,
+        reject: event is PointerCancelEvent || _state ==_DragState.possible,
+      );
+    }
+  }
+
+  @override
+  void acceptGesture(int pointer) {
+    if (_state != _DragState.accepted) {
+      _state = _DragState.accepted;
+      final OffsetPair delta = _pendingDragOffset;
+      final Duration timestamp = _lastPendingEventTimestamp!;
+      final Matrix4? transform = _lastTransform;
+      final Offset localUpdateDelta;
+      switch (dragStartBehavior) {
+        case DragStartBehavior.start:
+          _initialPosition = _initialPosition + delta;
+          localUpdateDelta = Offset.zero;
+          break;
+        case DragStartBehavior.down:
+          localUpdateDelta = _getDeltaForDetails(delta.local);
+          break;
+      }
+      _pendingDragOffset = OffsetPair.zero;
+      _lastPendingEventTimestamp = null;
+      _lastTransform = null;
+      _checkStart(timestamp, pointer);
+      if (localUpdateDelta != Offset.zero && onUpdate != null) {
+        final Matrix4? localToGlobal = transform != null ? Matrix4.tryInvert(transform) : null;
+        final Offset correctedLocalPosition = _initialPosition.local + localUpdateDelta;
+        final Offset globalUpdateDelta = PointerEvent.transformDeltaViaPositions(
+          untransformedEndPosition: correctedLocalPosition,
+          untransformedDelta: localUpdateDelta,
+          transform: localToGlobal,
+        );
+        final OffsetPair updateDelta = OffsetPair(local: localUpdateDelta, global: globalUpdateDelta);
+        final OffsetPair correctedPosition = _initialPosition + updateDelta; // Only adds delta for down behaviour
+        _checkUpdate(
+          sourceTimeStamp: timestamp,
+          delta: localUpdateDelta,
+          primaryDelta: _getPrimaryValueFromOffset(localUpdateDelta),
+          globalPosition: correctedPosition.global,
+          localPosition: correctedPosition.local,
+        );
+      }
+    }
+  }
+
+  @override
+  void rejectGesture(int pointer) {
+    _giveUpPointer(pointer);
+  }
+
+  @override
+  void didStopTrackingLastPointer(int pointer) {
+    assert(_state != _DragState.ready);
+    switch(_state) {
+      case _DragState.ready:
+        break;
+
+      case _DragState.possible:
+        resolve(GestureDisposition.rejected);
+        _checkCancel();
+        break;
+
+      case _DragState.accepted:
+        _checkEnd(pointer);
+        break;
+    }
+    _velocityTrackers.clear();
+    _initialButtons = null;
+    _state = _DragState.ready;
+  }
+
+  void _giveUpPointer(int pointer, {bool reject = true}) {
+    stopTrackingPointer(pointer);
+    if (reject)
+      resolvePointer(pointer, GestureDisposition.rejected);
+  }
+
+  void _checkDown() {
+    assert(_initialButtons == kPrimaryButton);
+    final DragDownDetails details = DragDownDetails(
+      globalPosition: _initialPosition.global,
+      localPosition: _initialPosition.local,
+    );
+    if (onDown != null)
+      invokeCallback<void>('onDown', () => onDown!(details));
+  }
+
+  void _checkStart(Duration timestamp, int pointer) {
+    assert(_initialButtons == kPrimaryButton);
+    final DragStartDetails details = DragStartDetails(
+      sourceTimeStamp: timestamp,
+      globalPosition: _initialPosition.global,
+      localPosition: _initialPosition.local,
+      kind: getKindForPointer(pointer),
+    );
+    if (onStart != null)
+      invokeCallback<void>('onStart', () => onStart!(details));
+  }
+
+  void _checkUpdate({
+    Duration? sourceTimeStamp,
+    required Offset delta,
+    double? primaryDelta,
+    required Offset globalPosition,
+    Offset? localPosition,
+  }) {
+    assert(_initialButtons == kPrimaryButton);
+    final DragUpdateDetails details = DragUpdateDetails(
+      sourceTimeStamp: sourceTimeStamp,
+      delta: delta,
+      primaryDelta: primaryDelta,
+      globalPosition: globalPosition,
+      localPosition: localPosition,
+    );
+    if (onUpdate != null)
+      invokeCallback<void>('onUpdate', () => onUpdate!(details));
+  }
+
+  void _checkEnd(int pointer) {
+    assert(_initialButtons == kPrimaryButton);
+    if (onEnd == null)
+      return;
+
+    final VelocityTracker tracker = _velocityTrackers[pointer]!;
+    assert(tracker != null);
+
+    final DragEndDetails details;
+    final String Function() debugReport;
+
+    final VelocityEstimate? estimate = tracker.getVelocityEstimate();
+    if (estimate != null && isFlingGesture(estimate, tracker.kind)) {
+      final Velocity velocity = Velocity(pixelsPerSecond: estimate.pixelsPerSecond)
+        .clampMagnitude(minFlingVelocity ?? kMinFlingVelocity, maxFlingVelocity ?? kMaxFlingVelocity);
+      details = DragEndDetails(
+        velocity: velocity,
+        primaryVelocity: _getPrimaryValueFromOffset(velocity.pixelsPerSecond),
+      );
+      debugReport = () {
+        return '$estimate; fling at $velocity.';
+      };
+    } else {
+      details = DragEndDetails(
+        velocity: Velocity.zero,
+        primaryVelocity: 0.0,
+      );
+      debugReport = () {
+        if (estimate == null)
+          return 'Could not estimate velocity.';
+        return '$estimate; judged to not be a fling.';
+      };
+    }
+    invokeCallback<void>('onEnd', () => onEnd!(details), debugReport: debugReport);
+  }
+
+  void _checkCancel() {
+    assert(_initialButtons == kPrimaryButton);
+    if (onCancel != null)
+      invokeCallback<void>('onCancel', onCancel!);
+  }
+
+  @override
+  void dispose() {
+    _velocityTrackers.clear();
+    super.dispose();
+  }
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<DragStartBehavior>('start behavior', dragStartBehavior));
+  }
+}
+
+/// Recognizes movement in the vertical direction.
+///
+/// Used for vertical scrolling.
+///
+/// See also:
+///
+///  * [HorizontalDragGestureRecognizer], for a similar recognizer but for
+///    horizontal movement.
+///  * [MultiDragGestureRecognizer], for a family of gesture recognizers that
+///    track each touch point independently.
+class VerticalDragGestureRecognizer extends DragGestureRecognizer {
+  /// Create a gesture recognizer for interactions in the vertical axis.
+  ///
+  /// {@macro flutter.gestures.GestureRecognizer.kind}
+  VerticalDragGestureRecognizer({
+    Object? debugOwner,
+    PointerDeviceKind? kind,
+  }) : super(debugOwner: debugOwner, kind: kind);
+
+  @override
+  bool isFlingGesture(VelocityEstimate estimate, PointerDeviceKind kind) {
+    final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
+    final double minDistance = minFlingDistance ?? computeHitSlop(kind);
+    return estimate.pixelsPerSecond.dy.abs() > minVelocity && estimate.offset.dy.abs() > minDistance;
+  }
+
+  @override
+  bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind) {
+    return _globalDistanceMoved.abs() > computeHitSlop(pointerDeviceKind);
+  }
+
+  @override
+  Offset _getDeltaForDetails(Offset delta) => Offset(0.0, delta.dy);
+
+  @override
+  double _getPrimaryValueFromOffset(Offset value) => value.dy;
+
+  @override
+  String get debugDescription => 'vertical drag';
+}
+
+/// Recognizes movement in the horizontal direction.
+///
+/// Used for horizontal scrolling.
+///
+/// See also:
+///
+///  * [VerticalDragGestureRecognizer], for a similar recognizer but for
+///    vertical movement.
+///  * [MultiDragGestureRecognizer], for a family of gesture recognizers that
+///    track each touch point independently.
+class HorizontalDragGestureRecognizer extends DragGestureRecognizer {
+  /// Create a gesture recognizer for interactions in the horizontal axis.
+  ///
+  /// {@macro flutter.gestures.GestureRecognizer.kind}
+  HorizontalDragGestureRecognizer({
+    Object? debugOwner,
+    PointerDeviceKind? kind,
+  }) : super(debugOwner: debugOwner, kind: kind);
+
+  @override
+  bool isFlingGesture(VelocityEstimate estimate, PointerDeviceKind kind) {
+    final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
+    final double minDistance = minFlingDistance ?? computeHitSlop(kind);
+    return estimate.pixelsPerSecond.dx.abs() > minVelocity && estimate.offset.dx.abs() > minDistance;
+  }
+
+  @override
+  bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind) {
+    return _globalDistanceMoved.abs() > computeHitSlop(pointerDeviceKind);
+  }
+
+  @override
+  Offset _getDeltaForDetails(Offset delta) => Offset(delta.dx, 0.0);
+
+  @override
+  double _getPrimaryValueFromOffset(Offset value) => value.dx;
+
+  @override
+  String get debugDescription => 'horizontal drag';
+}
+
+/// Recognizes movement both horizontally and vertically.
+///
+/// See also:
+///
+///  * [ImmediateMultiDragGestureRecognizer], for a similar recognizer that
+///    tracks each touch point independently.
+///  * [DelayedMultiDragGestureRecognizer], for a similar recognizer that
+///    tracks each touch point independently, but that doesn't start until
+///    some time has passed.
+class PanGestureRecognizer extends DragGestureRecognizer {
+  /// Create a gesture recognizer for tracking movement on a plane.
+  PanGestureRecognizer({ Object? debugOwner }) : super(debugOwner: debugOwner);
+
+  @override
+  bool isFlingGesture(VelocityEstimate estimate, PointerDeviceKind kind) {
+    final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
+    final double minDistance = minFlingDistance ?? computeHitSlop(kind);
+    return estimate.pixelsPerSecond.distanceSquared > minVelocity * minVelocity
+        && estimate.offset.distanceSquared > minDistance * minDistance;
+  }
+
+  @override
+  bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind) {
+    return _globalDistanceMoved.abs() > computePanSlop(pointerDeviceKind);
+  }
+
+  @override
+  Offset _getDeltaForDetails(Offset delta) => delta;
+
+  @override
+  double? _getPrimaryValueFromOffset(Offset value) => null;
+
+  @override
+  String get debugDescription => 'pan';
+}
diff --git a/lib/src/gestures/multidrag.dart b/lib/src/gestures/multidrag.dart
new file mode 100644
index 0000000..6ffb763
--- /dev/null
+++ b/lib/src/gestures/multidrag.dart
@@ -0,0 +1,570 @@
+// Copyright 2014 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.
+
+
+import 'dart:async';
+import 'package:flute/ui.dart' show Offset;
+
+import 'package:flute/foundation.dart';
+
+import 'arena.dart';
+import 'binding.dart';
+import 'constants.dart';
+import 'drag.dart';
+import 'drag_details.dart';
+import 'events.dart';
+import 'recognizer.dart';
+import 'velocity_tracker.dart';
+
+/// Signature for when [MultiDragGestureRecognizer] recognizes the start of a drag gesture.
+typedef GestureMultiDragStartCallback = Drag? Function(Offset position);
+
+/// Per-pointer state for a [MultiDragGestureRecognizer].
+///
+/// A [MultiDragGestureRecognizer] tracks each pointer separately. The state for
+/// each pointer is a subclass of [MultiDragPointerState].
+abstract class MultiDragPointerState {
+  /// Creates per-pointer state for a [MultiDragGestureRecognizer].
+  ///
+  /// The [initialPosition] argument must not be null.
+  MultiDragPointerState(this.initialPosition, this.kind)
+    : assert(initialPosition != null),
+      _velocityTracker = VelocityTracker.withKind(kind);
+
+  /// The global coordinates of the pointer when the pointer contacted the screen.
+  final Offset initialPosition;
+
+  final VelocityTracker _velocityTracker;
+
+  /// The kind of pointer performing the multi-drag gesture.
+  ///
+  /// Used by subclasses to determine the appropriate hit slop, for example.
+  final PointerDeviceKind kind;
+
+  Drag? _client;
+
+  /// The offset of the pointer from the last position that was reported to the client.
+  ///
+  /// After the pointer contacts the screen, the pointer might move some
+  /// distance before this movement will be recognized as a drag. This field
+  /// accumulates that movement so that we can report it to the client after
+  /// the drag starts.
+  Offset? get pendingDelta => _pendingDelta;
+  Offset? _pendingDelta = Offset.zero;
+
+  Duration? _lastPendingEventTimestamp;
+
+  GestureArenaEntry? _arenaEntry;
+  void _setArenaEntry(GestureArenaEntry entry) {
+    assert(_arenaEntry == null);
+    assert(pendingDelta != null);
+    assert(_client == null);
+    _arenaEntry = entry;
+  }
+
+  /// Resolve this pointer's entry in the [GestureArenaManager] with the given disposition.
+  @protected
+  @mustCallSuper
+  void resolve(GestureDisposition disposition) {
+    _arenaEntry!.resolve(disposition);
+  }
+
+  void _move(PointerMoveEvent event) {
+    assert(_arenaEntry != null);
+    if (!event.synthesized)
+      _velocityTracker.addPosition(event.timeStamp, event.position);
+    if (_client != null) {
+      assert(pendingDelta == null);
+      // Call client last to avoid reentrancy.
+      _client!.update(DragUpdateDetails(
+        sourceTimeStamp: event.timeStamp,
+        delta: event.delta,
+        globalPosition: event.position,
+      ));
+    } else {
+      assert(pendingDelta != null);
+      _pendingDelta = _pendingDelta! + event.delta;
+      _lastPendingEventTimestamp = event.timeStamp;
+      checkForResolutionAfterMove();
+    }
+  }
+
+  /// Override this to call resolve() if the drag should be accepted or rejected.
+  /// This is called when a pointer movement is received, but only if the gesture
+  /// has not yet been resolved.
+  @protected
+  void checkForResolutionAfterMove() { }
+
+  /// Called when the gesture was accepted.
+  ///
+  /// Either immediately or at some future point before the gesture is disposed,
+  /// call starter(), passing it initialPosition, to start the drag.
+  @protected
+  void accepted(GestureMultiDragStartCallback starter);
+
+  /// Called when the gesture was rejected.
+  ///
+  /// The [dispose] method will be called immediately following this.
+  @protected
+  @mustCallSuper
+  void rejected() {
+    assert(_arenaEntry != null);
+    assert(_client == null);
+    assert(pendingDelta != null);
+    _pendingDelta = null;
+    _lastPendingEventTimestamp = null;
+    _arenaEntry = null;
+  }
+
+  void _startDrag(Drag client) {
+    assert(_arenaEntry != null);
+    assert(_client == null);
+    assert(client != null);
+    assert(pendingDelta != null);
+    _client = client;
+    final DragUpdateDetails details = DragUpdateDetails(
+      sourceTimeStamp: _lastPendingEventTimestamp,
+      delta: pendingDelta!,
+      globalPosition: initialPosition,
+    );
+    _pendingDelta = null;
+    _lastPendingEventTimestamp = null;
+    // Call client last to avoid reentrancy.
+    _client!.update(details);
+  }
+
+  void _up() {
+    assert(_arenaEntry != null);
+    if (_client != null) {
+      assert(pendingDelta == null);
+      final DragEndDetails details = DragEndDetails(velocity: _velocityTracker.getVelocity());
+      final Drag client = _client!;
+      _client = null;
+      // Call client last to avoid reentrancy.
+      client.end(details);
+    } else {
+      assert(pendingDelta != null);
+      _pendingDelta = null;
+      _lastPendingEventTimestamp = null;
+    }
+  }
+
+  void _cancel() {
+    assert(_arenaEntry != null);
+    if (_client != null) {
+      assert(pendingDelta == null);
+      final Drag client = _client!;
+      _client = null;
+      // Call client last to avoid reentrancy.
+      client.cancel();
+    } else {
+      assert(pendingDelta != null);
+      _pendingDelta = null;
+      _lastPendingEventTimestamp = null;
+    }
+  }
+
+  /// Releases any resources used by the object.
+  @protected
+  @mustCallSuper
+  void dispose() {
+    _arenaEntry?.resolve(GestureDisposition.rejected);
+    _arenaEntry = null;
+    assert(() {
+      _pendingDelta = null;
+      return true;
+    }());
+  }
+}
+
+/// Recognizes movement on a per-pointer basis.
+///
+/// In contrast to [DragGestureRecognizer], [MultiDragGestureRecognizer] watches
+/// each pointer separately, which means multiple drags can be recognized
+/// concurrently if multiple pointers are in contact with the screen.
+///
+/// [MultiDragGestureRecognizer] is not intended to be used directly. Instead,
+/// consider using one of its subclasses to recognize specific types for drag
+/// gestures.
+///
+/// See also:
+///
+///  * [ImmediateMultiDragGestureRecognizer], the most straight-forward variant
+///    of multi-pointer drag gesture recognizer.
+///  * [HorizontalMultiDragGestureRecognizer], which only recognizes drags that
+///    start horizontally.
+///  * [VerticalMultiDragGestureRecognizer], which only recognizes drags that
+///    start vertically.
+///  * [DelayedMultiDragGestureRecognizer], which only recognizes drags that
+///    start after a long-press gesture.
+abstract class MultiDragGestureRecognizer<T extends MultiDragPointerState> extends GestureRecognizer {
+  /// Initialize the object.
+  MultiDragGestureRecognizer({
+    required Object? debugOwner,
+    PointerDeviceKind? kind,
+  }) : super(debugOwner: debugOwner, kind: kind);
+
+  /// Called when this class recognizes the start of a drag gesture.
+  ///
+  /// The remaining notifications for this drag gesture are delivered to the
+  /// [Drag] object returned by this callback.
+  GestureMultiDragStartCallback? onStart;
+
+  Map<int, T>? _pointers = <int, T>{};
+
+  @override
+  void addAllowedPointer(PointerDownEvent event) {
+    assert(_pointers != null);
+    assert(event.pointer != null);
+    assert(event.position != null);
+    assert(!_pointers!.containsKey(event.pointer));
+    final T state = createNewPointerState(event);
+    _pointers![event.pointer] = state;
+    GestureBinding.instance!.pointerRouter.addRoute(event.pointer, _handleEvent);
+    state._setArenaEntry(GestureBinding.instance!.gestureArena.add(event.pointer, this));
+  }
+
+  /// Subclasses should override this method to create per-pointer state
+  /// objects to track the pointer associated with the given event.
+  @protected
+  @factory
+  T createNewPointerState(PointerDownEvent event);
+
+  void _handleEvent(PointerEvent event) {
+    assert(_pointers != null);
+    assert(event.pointer != null);
+    assert(event.timeStamp != null);
+    assert(event.position != null);
+    assert(_pointers!.containsKey(event.pointer));
+    final T state = _pointers![event.pointer]!;
+    if (event is PointerMoveEvent) {
+      state._move(event);
+      // We might be disposed here.
+    } else if (event is PointerUpEvent) {
+      assert(event.delta == Offset.zero);
+      state._up();
+      // We might be disposed here.
+      _removeState(event.pointer);
+    } else if (event is PointerCancelEvent) {
+      assert(event.delta == Offset.zero);
+      state._cancel();
+      // We might be disposed here.
+      _removeState(event.pointer);
+    } else if (event is! PointerDownEvent) {
+      // we get the PointerDownEvent that resulted in our addPointer getting called since we
+      // add ourselves to the pointer router then (before the pointer router has heard of
+      // the event).
+      assert(false);
+    }
+  }
+
+  @override
+  void acceptGesture(int pointer) {
+    assert(_pointers != null);
+    final T? state = _pointers![pointer];
+    if (state == null)
+      return; // We might already have canceled this drag if the up comes before the accept.
+    state.accepted((Offset initialPosition) => _startDrag(initialPosition, pointer));
+  }
+
+  Drag? _startDrag(Offset initialPosition, int pointer) {
+    assert(_pointers != null);
+    final T state = _pointers![pointer]!;
+    assert(state != null);
+    assert(state._pendingDelta != null);
+    Drag? drag;
+    if (onStart != null)
+      drag = invokeCallback<Drag?>('onStart', () => onStart!(initialPosition));
+    if (drag != null) {
+      state._startDrag(drag);
+    } else {
+      _removeState(pointer);
+    }
+    return drag;
+  }
+
+  @override
+  void rejectGesture(int pointer) {
+    assert(_pointers != null);
+    if (_pointers!.containsKey(pointer)) {
+      final T state = _pointers![pointer]!;
+      assert(state != null);
+      state.rejected();
+      _removeState(pointer);
+    } // else we already preemptively forgot about it (e.g. we got an up event)
+  }
+
+  void _removeState(int pointer) {
+    if (_pointers == null) {
+      // We've already been disposed. It's harmless to skip removing the state
+      // for the given pointer because dispose() has already removed it.
+      return;
+    }
+    assert(_pointers!.containsKey(pointer));
+    GestureBinding.instance!.pointerRouter.removeRoute(pointer, _handleEvent);
+    _pointers!.remove(pointer)!.dispose();
+  }
+
+  @override
+  void dispose() {
+    _pointers!.keys.toList().forEach(_removeState);
+    assert(_pointers!.isEmpty);
+    _pointers = null;
+    super.dispose();
+  }
+}
+
+class _ImmediatePointerState extends MultiDragPointerState {
+  _ImmediatePointerState(Offset initialPosition, PointerDeviceKind kind) : super(initialPosition, kind);
+
+  @override
+  void checkForResolutionAfterMove() {
+    assert(pendingDelta != null);
+    if (pendingDelta!.distance > computeHitSlop(kind))
+      resolve(GestureDisposition.accepted);
+  }
+
+  @override
+  void accepted(GestureMultiDragStartCallback starter) {
+    starter(initialPosition);
+  }
+}
+
+/// Recognizes movement both horizontally and vertically on a per-pointer basis.
+///
+/// In contrast to [PanGestureRecognizer], [ImmediateMultiDragGestureRecognizer]
+/// watches each pointer separately, which means multiple drags can be
+/// recognized concurrently if multiple pointers are in contact with the screen.
+///
+/// See also:
+///
+///  * [PanGestureRecognizer], which recognizes only one drag gesture at a time,
+///    regardless of how many fingers are involved.
+///  * [HorizontalMultiDragGestureRecognizer], which only recognizes drags that
+///    start horizontally.
+///  * [VerticalMultiDragGestureRecognizer], which only recognizes drags that
+///    start vertically.
+///  * [DelayedMultiDragGestureRecognizer], which only recognizes drags that
+///    start after a long-press gesture.
+class ImmediateMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_ImmediatePointerState> {
+  /// Create a gesture recognizer for tracking multiple pointers at once.
+  ImmediateMultiDragGestureRecognizer({
+    Object? debugOwner,
+    PointerDeviceKind? kind,
+  }) : super(debugOwner: debugOwner, kind: kind);
+
+  @override
+  _ImmediatePointerState createNewPointerState(PointerDownEvent event) {
+    return _ImmediatePointerState(event.position, event.kind);
+  }
+
+  @override
+  String get debugDescription => 'multidrag';
+}
+
+
+class _HorizontalPointerState extends MultiDragPointerState {
+  _HorizontalPointerState(Offset initialPosition, PointerDeviceKind kind) : super(initialPosition, kind);
+
+  @override
+  void checkForResolutionAfterMove() {
+    assert(pendingDelta != null);
+    if (pendingDelta!.dx.abs() > computeHitSlop(kind))
+      resolve(GestureDisposition.accepted);
+  }
+
+  @override
+  void accepted(GestureMultiDragStartCallback starter) {
+    starter(initialPosition);
+  }
+}
+
+/// Recognizes movement in the horizontal direction on a per-pointer basis.
+///
+/// In contrast to [HorizontalDragGestureRecognizer],
+/// [HorizontalMultiDragGestureRecognizer] watches each pointer separately,
+/// which means multiple drags can be recognized concurrently if multiple
+/// pointers are in contact with the screen.
+///
+/// See also:
+///
+///  * [HorizontalDragGestureRecognizer], a gesture recognizer that just
+///    looks at horizontal movement.
+///  * [ImmediateMultiDragGestureRecognizer], a similar recognizer, but without
+///    the limitation that the drag must start horizontally.
+///  * [VerticalMultiDragGestureRecognizer], which only recognizes drags that
+///    start vertically.
+class HorizontalMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_HorizontalPointerState> {
+  /// Create a gesture recognizer for tracking multiple pointers at once
+  /// but only if they first move horizontally.
+  HorizontalMultiDragGestureRecognizer({
+    Object? debugOwner,
+    PointerDeviceKind? kind,
+  }) : super(debugOwner: debugOwner, kind: kind);
+
+  @override
+  _HorizontalPointerState createNewPointerState(PointerDownEvent event) {
+    return _HorizontalPointerState(event.position, event.kind);
+  }
+
+  @override
+  String get debugDescription => 'horizontal multidrag';
+}
+
+
+class _VerticalPointerState extends MultiDragPointerState {
+  _VerticalPointerState(Offset initialPosition, PointerDeviceKind kind) : super(initialPosition, kind);
+
+  @override
+  void checkForResolutionAfterMove() {
+    assert(pendingDelta != null);
+    if (pendingDelta!.dy.abs() > computeHitSlop(kind))
+      resolve(GestureDisposition.accepted);
+  }
+
+  @override
+  void accepted(GestureMultiDragStartCallback starter) {
+    starter(initialPosition);
+  }
+}
+
+/// Recognizes movement in the vertical direction on a per-pointer basis.
+///
+/// In contrast to [VerticalDragGestureRecognizer],
+/// [VerticalMultiDragGestureRecognizer] watches each pointer separately,
+/// which means multiple drags can be recognized concurrently if multiple
+/// pointers are in contact with the screen.
+///
+/// See also:
+///
+///  * [VerticalDragGestureRecognizer], a gesture recognizer that just
+///    looks at vertical movement.
+///  * [ImmediateMultiDragGestureRecognizer], a similar recognizer, but without
+///    the limitation that the drag must start vertically.
+///  * [HorizontalMultiDragGestureRecognizer], which only recognizes drags that
+///    start horizontally.
+class VerticalMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_VerticalPointerState> {
+  /// Create a gesture recognizer for tracking multiple pointers at once
+  /// but only if they first move vertically.
+  VerticalMultiDragGestureRecognizer({
+    Object? debugOwner,
+    PointerDeviceKind? kind,
+  }) : super(debugOwner: debugOwner, kind: kind);
+
+  @override
+  _VerticalPointerState createNewPointerState(PointerDownEvent event) {
+    return _VerticalPointerState(event.position, event.kind);
+  }
+
+  @override
+  String get debugDescription => 'vertical multidrag';
+}
+
+class _DelayedPointerState extends MultiDragPointerState {
+  _DelayedPointerState(Offset initialPosition, Duration delay, PointerDeviceKind kind)
+      : assert(delay != null),
+        super(initialPosition, kind) {
+    _timer = Timer(delay, _delayPassed);
+  }
+
+  Timer? _timer;
+  GestureMultiDragStartCallback? _starter;
+
+  void _delayPassed() {
+    assert(_timer != null);
+    assert(pendingDelta != null);
+    assert(pendingDelta!.distance <= computeHitSlop(kind));
+    _timer = null;
+    if (_starter != null) {
+      _starter!(initialPosition);
+      _starter = null;
+    } else {
+      resolve(GestureDisposition.accepted);
+    }
+    assert(_starter == null);
+  }
+
+  void _ensureTimerStopped() {
+    _timer?.cancel();
+    _timer = null;
+  }
+
+  @override
+  void accepted(GestureMultiDragStartCallback starter) {
+    assert(_starter == null);
+    if (_timer == null)
+      starter(initialPosition);
+    else
+      _starter = starter;
+  }
+
+  @override
+  void checkForResolutionAfterMove() {
+    if (_timer == null) {
+      // If we've been accepted by the gesture arena but the pointer moves too
+      // much before the timer fires, we end up a state where the timer is
+      // stopped but we keep getting calls to this function because we never
+      // actually started the drag. In this case, _starter will be non-null
+      // because we're essentially waiting forever to start the drag.
+      assert(_starter != null);
+      return;
+    }
+    assert(pendingDelta != null);
+    if (pendingDelta!.distance > computeHitSlop(kind)) {
+      resolve(GestureDisposition.rejected);
+      _ensureTimerStopped();
+    }
+  }
+
+  @override
+  void dispose() {
+    _ensureTimerStopped();
+    super.dispose();
+  }
+}
+
+/// Recognizes movement both horizontally and vertically on a per-pointer basis
+/// after a delay.
+///
+/// In contrast to [ImmediateMultiDragGestureRecognizer],
+/// [DelayedMultiDragGestureRecognizer] waits for a [delay] before recognizing
+/// the drag. If the pointer moves more than [kTouchSlop] before the delay
+/// expires, the gesture is not recognized.
+///
+/// In contrast to [PanGestureRecognizer], [DelayedMultiDragGestureRecognizer]
+/// watches each pointer separately, which means multiple drags can be
+/// recognized concurrently if multiple pointers are in contact with the screen.
+///
+/// See also:
+///
+///  * [ImmediateMultiDragGestureRecognizer], a similar recognizer but without
+///    the delay.
+///  * [PanGestureRecognizer], which recognizes only one drag gesture at a time,
+///    regardless of how many fingers are involved.
+class DelayedMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_DelayedPointerState> {
+  /// Creates a drag recognizer that works on a per-pointer basis after a delay.
+  ///
+  /// In order for a drag to be recognized by this recognizer, the pointer must
+  /// remain in the same place for [delay] (up to [kTouchSlop]). The [delay]
+  /// defaults to [kLongPressTimeout] to match [LongPressGestureRecognizer] but
+  /// can be changed for specific behaviors.
+  DelayedMultiDragGestureRecognizer({
+    this.delay = kLongPressTimeout,
+    Object? debugOwner,
+    PointerDeviceKind? kind,
+  }) : assert(delay != null),
+       super(debugOwner: debugOwner, kind: kind);
+
+  /// The amount of time the pointer must remain in the same place for the drag
+  /// to be recognized.
+  final Duration delay;
+
+  @override
+  _DelayedPointerState createNewPointerState(PointerDownEvent event) {
+    return _DelayedPointerState(event.position, delay, event.kind);
+  }
+
+  @override
+  String get debugDescription => 'long multidrag';
+}
diff --git a/lib/src/gestures/multitap.dart b/lib/src/gestures/multitap.dart
new file mode 100644
index 0000000..196861b
--- /dev/null
+++ b/lib/src/gestures/multitap.dart
@@ -0,0 +1,570 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'package:flute/ui.dart' show Offset;
+import 'package:vector_math/vector_math_64.dart';
+
+import 'arena.dart';
+import 'binding.dart';
+import 'constants.dart';
+import 'events.dart';
+import 'pointer_router.dart';
+import 'recognizer.dart';
+import 'tap.dart';
+
+/// Signature for callback when the user has tapped the screen at the same
+/// location twice in quick succession.
+///
+/// See also:
+///
+///  * [GestureDetector.onDoubleTap], which matches this signature.
+typedef GestureDoubleTapCallback = void Function();
+
+/// Signature used by [MultiTapGestureRecognizer] for when a pointer that might
+/// cause a tap has contacted the screen at a particular location.
+typedef GestureMultiTapDownCallback = void Function(int pointer, TapDownDetails details);
+
+/// Signature used by [MultiTapGestureRecognizer] for when a pointer that will
+/// trigger a tap has stopped contacting the screen at a particular location.
+typedef GestureMultiTapUpCallback = void Function(int pointer, TapUpDetails details);
+
+/// Signature used by [MultiTapGestureRecognizer] for when a tap has occurred.
+typedef GestureMultiTapCallback = void Function(int pointer);
+
+/// Signature for when the pointer that previously triggered a
+/// [GestureMultiTapDownCallback] will not end up causing a tap.
+typedef GestureMultiTapCancelCallback = void Function(int pointer);
+
+/// CountdownZoned tracks whether the specified duration has elapsed since
+/// creation, honoring [Zone].
+class _CountdownZoned {
+  _CountdownZoned({ required Duration duration })
+       : assert(duration != null) {
+    Timer(duration, _onTimeout);
+  }
+
+  bool _timeout = false;
+
+  bool get timeout => _timeout;
+
+  void _onTimeout() {
+    _timeout = true;
+  }
+}
+
+/// TapTracker helps track individual tap sequences as part of a
+/// larger gesture.
+class _TapTracker {
+  _TapTracker({
+    required PointerDownEvent event,
+    required this.entry,
+    required Duration doubleTapMinTime,
+  }) : assert(doubleTapMinTime != null),
+       assert(event != null),
+       assert(event.buttons != null),
+       pointer = event.pointer,
+       _initialGlobalPosition = event.position,
+       initialButtons = event.buttons,
+       _doubleTapMinTimeCountdown = _CountdownZoned(duration: doubleTapMinTime);
+
+  final int pointer;
+  final GestureArenaEntry entry;
+  final Offset _initialGlobalPosition;
+  final int initialButtons;
+  final _CountdownZoned _doubleTapMinTimeCountdown;
+
+  bool _isTrackingPointer = false;
+
+  void startTrackingPointer(PointerRoute route, Matrix4? transform) {
+    if (!_isTrackingPointer) {
+      _isTrackingPointer = true;
+      GestureBinding.instance!.pointerRouter.addRoute(pointer, route, transform);
+    }
+  }
+
+  void stopTrackingPointer(PointerRoute route) {
+    if (_isTrackingPointer) {
+      _isTrackingPointer = false;
+      GestureBinding.instance!.pointerRouter.removeRoute(pointer, route);
+    }
+  }
+
+  bool isWithinGlobalTolerance(PointerEvent event, double tolerance) {
+    final Offset offset = event.position - _initialGlobalPosition;
+    return offset.distance <= tolerance;
+  }
+
+  bool hasElapsedMinTime() {
+    return _doubleTapMinTimeCountdown.timeout;
+  }
+
+  bool hasSameButton(PointerDownEvent event) {
+    return event.buttons == initialButtons;
+  }
+}
+
+/// Recognizes when the user has tapped the screen at the same location twice in
+/// quick succession.
+///
+/// [DoubleTapGestureRecognizer] competes on pointer events of [kPrimaryButton]
+/// only when it has a non-null callback. If it has no callbacks, it is a no-op.
+///
+class DoubleTapGestureRecognizer extends GestureRecognizer {
+  /// Create a gesture recognizer for double taps.
+  ///
+  /// {@macro flutter.gestures.GestureRecognizer.kind}
+  DoubleTapGestureRecognizer({
+    Object? debugOwner,
+    PointerDeviceKind? kind,
+  }) : super(debugOwner: debugOwner, kind: kind);
+
+  // Implementation notes:
+  //
+  // The double tap recognizer can be in one of four states. There's no
+  // explicit enum for the states, because they are already captured by
+  // the state of existing fields. Specifically:
+  //
+  // 1. Waiting on first tap: In this state, the _trackers list is empty, and
+  //    _firstTap is null.
+  // 2. First tap in progress: In this state, the _trackers list contains all
+  //    the states for taps that have begun but not completed. This list can
+  //    have more than one entry if two pointers begin to tap.
+  // 3. Waiting on second tap: In this state, one of the in-progress taps has
+  //    completed successfully. The _trackers list is again empty, and
+  //    _firstTap records the successful tap.
+  // 4. Second tap in progress: Much like the "first tap in progress" state, but
+  //    _firstTap is non-null. If a tap completes successfully while in this
+  //    state, the callback is called and the state is reset.
+  //
+  // There are various other scenarios that cause the state to reset:
+  //
+  // - All in-progress taps are rejected (by time, distance, pointercancel, etc)
+  // - The long timer between taps expires
+  // - The gesture arena decides we have been rejected wholesale
+
+  /// A pointer has contacted the screen with a primary button at the same
+  /// location twice in quick succession, which might be the start of a double
+  /// tap.
+  ///
+  /// This triggers immediately after the down event of the second tap.
+  ///
+  /// If this recognizer doesn't win the arena, [onDoubleTapCancel] is called
+  /// next. Otherwise, [onDoubleTap] is called next.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [TapDownDetails], which is passed as an argument to this callback.
+  ///  * [GestureDetector.onDoubleTapDown], which exposes this callback.
+  GestureTapDownCallback? onDoubleTapDown;
+
+  /// Called when the user has tapped the screen with a primary button at the
+  /// same location twice in quick succession.
+  ///
+  /// This triggers when the pointer stops contacting the device after the
+  /// second tap.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [GestureDetector.onDoubleTap], which exposes this callback.
+  GestureDoubleTapCallback? onDoubleTap;
+
+  /// A pointer that previously triggered [onDoubleTapDown] will not end up
+  /// causing a double tap.
+  ///
+  /// This triggers once the gesture loses the arena if [onDoubleTapDown] has
+  /// previously been triggered.
+  ///
+  /// If this recognizer wins the arena, [onDoubleTap] is called instead.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [GestureDetector.onDoubleTapCancel], which exposes this callback.
+  GestureTapCancelCallback? onDoubleTapCancel;
+
+  Timer? _doubleTapTimer;
+  _TapTracker? _firstTap;
+  final Map<int, _TapTracker> _trackers = <int, _TapTracker>{};
+
+  @override
+  bool isPointerAllowed(PointerDownEvent event) {
+    if (_firstTap == null) {
+      switch (event.buttons) {
+        case kPrimaryButton:
+          if (onDoubleTapDown == null &&
+              onDoubleTap == null &&
+              onDoubleTapCancel == null)
+            return false;
+          break;
+        default:
+          return false;
+      }
+    }
+    return super.isPointerAllowed(event);
+  }
+
+  @override
+  void addAllowedPointer(PointerDownEvent event) {
+    if (_firstTap != null) {
+      if (!_firstTap!.isWithinGlobalTolerance(event, kDoubleTapSlop)) {
+        // Ignore out-of-bounds second taps.
+        return;
+      } else if (!_firstTap!.hasElapsedMinTime() || !_firstTap!.hasSameButton(event)) {
+        // Restart when the second tap is too close to the first (touch screens
+        // often detect touches intermittently), or when buttons mismatch.
+        _reset();
+        return _trackTap(event);
+      } else if (onDoubleTapDown != null) {
+        final TapDownDetails details = TapDownDetails(
+          globalPosition: event.position,
+          localPosition: event.localPosition,
+          kind: getKindForPointer(event.pointer),
+        );
+        invokeCallback<void>('onDoubleTapDown', () => onDoubleTapDown!(details));
+      }
+    }
+    _trackTap(event);
+  }
+
+  void _trackTap(PointerDownEvent event) {
+    _stopDoubleTapTimer();
+    final _TapTracker tracker = _TapTracker(
+      event: event,
+      entry: GestureBinding.instance!.gestureArena.add(event.pointer, this),
+      doubleTapMinTime: kDoubleTapMinTime,
+    );
+    _trackers[event.pointer] = tracker;
+    tracker.startTrackingPointer(_handleEvent, event.transform);
+  }
+
+  void _handleEvent(PointerEvent event) {
+    final _TapTracker tracker = _trackers[event.pointer]!;
+    if (event is PointerUpEvent) {
+      if (_firstTap == null)
+        _registerFirstTap(tracker);
+      else
+        _registerSecondTap(tracker);
+    } else if (event is PointerMoveEvent) {
+      if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop))
+        _reject(tracker);
+    } else if (event is PointerCancelEvent) {
+      _reject(tracker);
+    }
+  }
+
+  @override
+  void acceptGesture(int pointer) { }
+
+  @override
+  void rejectGesture(int pointer) {
+    _TapTracker? tracker = _trackers[pointer];
+    // If tracker isn't in the list, check if this is the first tap tracker
+    if (tracker == null &&
+        _firstTap != null &&
+        _firstTap!.pointer == pointer)
+      tracker = _firstTap;
+    // If tracker is still null, we rejected ourselves already
+    if (tracker != null)
+      _reject(tracker);
+  }
+
+  void _reject(_TapTracker tracker) {
+    _trackers.remove(tracker.pointer);
+    tracker.entry.resolve(GestureDisposition.rejected);
+    _freezeTracker(tracker);
+    if (_firstTap != null) {
+      if (tracker == _firstTap) {
+        _reset();
+      } else {
+        _checkCancel();
+        if (_trackers.isEmpty)
+          _reset();
+      }
+    }
+  }
+
+  @override
+  void dispose() {
+    _reset();
+    super.dispose();
+  }
+
+  void _reset() {
+    _stopDoubleTapTimer();
+    if (_firstTap != null) {
+      if (_trackers.isNotEmpty)
+        _checkCancel();
+      // Note, order is important below in order for the resolve -> reject logic
+      // to work properly.
+      final _TapTracker tracker = _firstTap!;
+      _firstTap = null;
+      _reject(tracker);
+      GestureBinding.instance!.gestureArena.release(tracker.pointer);
+    }
+    _clearTrackers();
+  }
+
+  void _registerFirstTap(_TapTracker tracker) {
+    _startDoubleTapTimer();
+    GestureBinding.instance!.gestureArena.hold(tracker.pointer);
+    // Note, order is important below in order for the clear -> reject logic to
+    // work properly.
+    _freezeTracker(tracker);
+    _trackers.remove(tracker.pointer);
+    _clearTrackers();
+    _firstTap = tracker;
+  }
+
+  void _registerSecondTap(_TapTracker tracker) {
+    _firstTap!.entry.resolve(GestureDisposition.accepted);
+    tracker.entry.resolve(GestureDisposition.accepted);
+    _freezeTracker(tracker);
+    _trackers.remove(tracker.pointer);
+    _checkUp(tracker.initialButtons);
+    _reset();
+  }
+
+  void _clearTrackers() {
+    _trackers.values.toList().forEach(_reject);
+    assert(_trackers.isEmpty);
+  }
+
+  void _freezeTracker(_TapTracker tracker) {
+    tracker.stopTrackingPointer(_handleEvent);
+  }
+
+  void _startDoubleTapTimer() {
+    _doubleTapTimer ??= Timer(kDoubleTapTimeout, _reset);
+  }
+
+  void _stopDoubleTapTimer() {
+    if (_doubleTapTimer != null) {
+      _doubleTapTimer!.cancel();
+      _doubleTapTimer = null;
+    }
+  }
+
+  void _checkUp(int buttons) {
+    assert(buttons == kPrimaryButton);
+    if (onDoubleTap != null)
+      invokeCallback<void>('onDoubleTap', onDoubleTap!);
+  }
+
+  void _checkCancel() {
+    if (onDoubleTapCancel != null)
+      invokeCallback<void>('onDoubleTapCancel', onDoubleTapCancel!);
+  }
+
+  @override
+  String get debugDescription => 'double tap';
+}
+
+/// TapGesture represents a full gesture resulting from a single tap sequence,
+/// as part of a [MultiTapGestureRecognizer]. Tap gestures are passive, meaning
+/// that they will not preempt any other arena member in play.
+class _TapGesture extends _TapTracker {
+
+  _TapGesture({
+    required this.gestureRecognizer,
+    required PointerEvent event,
+    required Duration longTapDelay,
+  }) : _lastPosition = OffsetPair.fromEventPosition(event),
+       super(
+    event: event as PointerDownEvent,
+    entry: GestureBinding.instance!.gestureArena.add(event.pointer, gestureRecognizer),
+    doubleTapMinTime: kDoubleTapMinTime,
+  ) {
+    startTrackingPointer(handleEvent, event.transform);
+    if (longTapDelay > Duration.zero) {
+      _timer = Timer(longTapDelay, () {
+        _timer = null;
+        gestureRecognizer._dispatchLongTap(event.pointer, _lastPosition);
+      });
+    }
+  }
+
+  final MultiTapGestureRecognizer gestureRecognizer;
+
+  bool _wonArena = false;
+  Timer? _timer;
+
+  OffsetPair _lastPosition;
+  OffsetPair? _finalPosition;
+
+  void handleEvent(PointerEvent event) {
+    assert(event.pointer == pointer);
+    if (event is PointerMoveEvent) {
+      if (!isWithinGlobalTolerance(event, computeHitSlop(event.kind)))
+        cancel();
+      else
+        _lastPosition = OffsetPair.fromEventPosition(event);
+    } else if (event is PointerCancelEvent) {
+      cancel();
+    } else if (event is PointerUpEvent) {
+      stopTrackingPointer(handleEvent);
+      _finalPosition = OffsetPair.fromEventPosition(event);
+      _check();
+    }
+  }
+
+  @override
+  void stopTrackingPointer(PointerRoute route) {
+    _timer?.cancel();
+    _timer = null;
+    super.stopTrackingPointer(route);
+  }
+
+  void accept() {
+    _wonArena = true;
+    _check();
+  }
+
+  void reject() {
+    stopTrackingPointer(handleEvent);
+    gestureRecognizer._dispatchCancel(pointer);
+  }
+
+  void cancel() {
+    // If we won the arena already, then entry is resolved, so resolving
+    // again is a no-op. But we still need to clean up our own state.
+    if (_wonArena)
+      reject();
+    else
+      entry.resolve(GestureDisposition.rejected); // eventually calls reject()
+  }
+
+  void _check() {
+    if (_wonArena && _finalPosition != null)
+      gestureRecognizer._dispatchTap(pointer, _finalPosition!);
+  }
+}
+
+/// Recognizes taps on a per-pointer basis.
+///
+/// [MultiTapGestureRecognizer] considers each sequence of pointer events that
+/// could constitute a tap independently of other pointers: For example, down-1,
+/// down-2, up-1, up-2 produces two taps, on up-1 and up-2.
+///
+/// See also:
+///
+///  * [TapGestureRecognizer]
+class MultiTapGestureRecognizer extends GestureRecognizer {
+  /// Creates a multi-tap gesture recognizer.
+  ///
+  /// The [longTapDelay] defaults to [Duration.zero], which means
+  /// [onLongTapDown] is called immediately after [onTapDown].
+  MultiTapGestureRecognizer({
+    this.longTapDelay = Duration.zero,
+    Object? debugOwner,
+    PointerDeviceKind? kind,
+  }) : super(debugOwner: debugOwner, kind: kind);
+
+  /// A pointer that might cause a tap has contacted the screen at a particular
+  /// location.
+  GestureMultiTapDownCallback? onTapDown;
+
+  /// A pointer that will trigger a tap has stopped contacting the screen at a
+  /// particular location.
+  GestureMultiTapUpCallback? onTapUp;
+
+  /// A tap has occurred.
+  GestureMultiTapCallback? onTap;
+
+  /// The pointer that previously triggered [onTapDown] will not end up causing
+  /// a tap.
+  GestureMultiTapCancelCallback? onTapCancel;
+
+  /// The amount of time between [onTapDown] and [onLongTapDown].
+  Duration longTapDelay;
+
+  /// A pointer that might cause a tap is still in contact with the screen at a
+  /// particular location after [longTapDelay].
+  GestureMultiTapDownCallback? onLongTapDown;
+
+  final Map<int, _TapGesture> _gestureMap = <int, _TapGesture>{};
+
+  @override
+  void addAllowedPointer(PointerEvent event) {
+    assert(!_gestureMap.containsKey(event.pointer));
+    _gestureMap[event.pointer] = _TapGesture(
+      gestureRecognizer: this,
+      event: event,
+      longTapDelay: longTapDelay,
+    );
+    if (onTapDown != null)
+      invokeCallback<void>('onTapDown', () {
+        onTapDown!(event.pointer, TapDownDetails(
+          globalPosition: event.position,
+          localPosition: event.localPosition,
+          kind: event.kind,
+        ));
+      });
+  }
+
+  @override
+  void acceptGesture(int pointer) {
+    assert(_gestureMap.containsKey(pointer));
+    _gestureMap[pointer]!.accept();
+  }
+
+  @override
+  void rejectGesture(int pointer) {
+    assert(_gestureMap.containsKey(pointer));
+    _gestureMap[pointer]!.reject();
+    assert(!_gestureMap.containsKey(pointer));
+  }
+
+  void _dispatchCancel(int pointer) {
+    assert(_gestureMap.containsKey(pointer));
+    _gestureMap.remove(pointer);
+    if (onTapCancel != null)
+      invokeCallback<void>('onTapCancel', () => onTapCancel!(pointer));
+  }
+
+  void _dispatchTap(int pointer, OffsetPair position) {
+    assert(_gestureMap.containsKey(pointer));
+    _gestureMap.remove(pointer);
+    if (onTapUp != null)
+      invokeCallback<void>('onTapUp', () {
+        onTapUp!(pointer, TapUpDetails(
+          kind: getKindForPointer(pointer),
+          localPosition: position.local,
+          globalPosition: position.global,
+        ));
+      });
+    if (onTap != null)
+      invokeCallback<void>('onTap', () => onTap!(pointer));
+  }
+
+  void _dispatchLongTap(int pointer, OffsetPair lastPosition) {
+    assert(_gestureMap.containsKey(pointer));
+    if (onLongTapDown != null)
+      invokeCallback<void>('onLongTapDown', () {
+        onLongTapDown!(
+          pointer,
+          TapDownDetails(
+            globalPosition: lastPosition.global,
+            localPosition: lastPosition.local,
+            kind: getKindForPointer(pointer),
+          ),
+        );
+      });
+  }
+
+  @override
+  void dispose() {
+    final List<_TapGesture> localGestures = List<_TapGesture>.from(_gestureMap.values);
+    for (final _TapGesture gesture in localGestures)
+      gesture.cancel();
+    // Rejection of each gesture should cause it to be removed from our map
+    assert(_gestureMap.isEmpty);
+    super.dispose();
+  }
+
+  @override
+  String get debugDescription => 'multitap';
+}
diff --git a/lib/src/gestures/pointer_router.dart b/lib/src/gestures/pointer_router.dart
new file mode 100644
index 0000000..fb2a58b
--- /dev/null
+++ b/lib/src/gestures/pointer_router.dart
@@ -0,0 +1,142 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'events.dart';
+
+/// A callback that receives a [PointerEvent]
+typedef PointerRoute = void Function(PointerEvent event);
+
+/// A routing table for [PointerEvent] events.
+class PointerRouter {
+  final Map<int, Map<PointerRoute, Matrix4?>> _routeMap = <int, Map<PointerRoute, Matrix4?>>{};
+  final Map<PointerRoute, Matrix4?> _globalRoutes = <PointerRoute, Matrix4?>{};
+
+  /// Adds a route to the routing table.
+  ///
+  /// Whenever this object routes a [PointerEvent] corresponding to
+  /// pointer, call route.
+  ///
+  /// Routes added reentrantly within [PointerRouter.route] will take effect when
+  /// routing the next event.
+  void addRoute(int pointer, PointerRoute route, [Matrix4? transform]) {
+    final Map<PointerRoute, Matrix4?> routes = _routeMap.putIfAbsent(
+      pointer,
+      () => <PointerRoute, Matrix4?>{},
+    );
+    assert(!routes.containsKey(route));
+    routes[route] = transform;
+  }
+
+  /// Removes a route from the routing table.
+  ///
+  /// No longer call route when routing a [PointerEvent] corresponding to
+  /// pointer. Requires that this route was previously added to the router.
+  ///
+  /// Routes removed reentrantly within [PointerRouter.route] will take effect
+  /// immediately.
+  void removeRoute(int pointer, PointerRoute route) {
+    assert(_routeMap.containsKey(pointer));
+    final Map<PointerRoute, Matrix4?> routes = _routeMap[pointer]!;
+    assert(routes.containsKey(route));
+    routes.remove(route);
+    if (routes.isEmpty)
+      _routeMap.remove(pointer);
+  }
+
+  /// Adds a route to the global entry in the routing table.
+  ///
+  /// Whenever this object routes a [PointerEvent], call route.
+  ///
+  /// Routes added reentrantly within [PointerRouter.route] will take effect when
+  /// routing the next event.
+  void addGlobalRoute(PointerRoute route, [Matrix4? transform]) {
+    assert(!_globalRoutes.containsKey(route));
+    _globalRoutes[route] = transform;
+  }
+
+  /// Removes a route from the global entry in the routing table.
+  ///
+  /// No longer call route when routing a [PointerEvent]. Requires that this
+  /// route was previously added via [addGlobalRoute].
+  ///
+  /// Routes removed reentrantly within [PointerRouter.route] will take effect
+  /// immediately.
+  void removeGlobalRoute(PointerRoute route) {
+    assert(_globalRoutes.containsKey(route));
+    _globalRoutes.remove(route);
+  }
+
+  /// The number of global routes that have been registered.
+  ///
+  /// This is valid in debug builds only. In release builds, this will throw an
+  /// [UnsupportedError].
+  int get debugGlobalRouteCount {
+    int? count;
+    assert(() {
+      count = _globalRoutes.length;
+      return true;
+    }());
+    if (count != null) {
+      return count!;
+    }
+    throw UnsupportedError('debugGlobalRouteCount is not supported in release builds');
+  }
+
+  void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) {
+    try {
+      event = event.transformed(transform);
+      route(event);
+    } catch (exception, stack) {
+      InformationCollector? collector;
+      assert(() {
+        collector = () sync* {
+          yield DiagnosticsProperty<PointerRouter>('router', this, level: DiagnosticLevel.debug);
+          yield DiagnosticsProperty<PointerRoute>('route', route, level: DiagnosticLevel.debug);
+          yield DiagnosticsProperty<PointerEvent>('event', event, level: DiagnosticLevel.debug);
+        };
+        return true;
+      }());
+      FlutterError.reportError(FlutterErrorDetails(
+        exception: exception,
+        stack: stack,
+        library: 'gesture library',
+        context: ErrorDescription('while routing a pointer event'),
+        informationCollector: collector
+      ));
+    }
+  }
+
+  /// Calls the routes registered for this pointer event.
+  ///
+  /// Routes are called in the order in which they were added to the
+  /// PointerRouter object.
+  void route(PointerEvent event) {
+    final Map<PointerRoute, Matrix4?>? routes = _routeMap[event.pointer];
+    final Map<PointerRoute, Matrix4?> copiedGlobalRoutes = Map<PointerRoute, Matrix4?>.from(_globalRoutes);
+    if (routes != null) {
+      _dispatchEventToRoutes(
+        event,
+        routes,
+        Map<PointerRoute, Matrix4?>.from(routes),
+      );
+    }
+    _dispatchEventToRoutes(event, _globalRoutes, copiedGlobalRoutes);
+  }
+
+  void _dispatchEventToRoutes(
+    PointerEvent event,
+    Map<PointerRoute, Matrix4?> referenceRoutes,
+    Map<PointerRoute, Matrix4?> copiedRoutes,
+  ) {
+    copiedRoutes.forEach((PointerRoute route, Matrix4? transform) {
+      if (referenceRoutes.containsKey(route)) {
+        _dispatch(event, route, transform);
+      }
+    });
+  }
+}
diff --git a/lib/src/gestures/pointer_signal_resolver.dart b/lib/src/gestures/pointer_signal_resolver.dart
new file mode 100644
index 0000000..1bd3edd
--- /dev/null
+++ b/lib/src/gestures/pointer_signal_resolver.dart
@@ -0,0 +1,77 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+import 'events.dart';
+
+/// The callback to register with a [PointerSignalResolver] to express
+/// interest in a pointer signal event.
+typedef PointerSignalResolvedCallback = void Function(PointerSignalEvent event);
+
+bool _isSameEvent(PointerSignalEvent event1, PointerSignalEvent event2) {
+  return (event1.original ?? event1) == (event2.original ?? event2);
+}
+
+/// An resolver for pointer signal events.
+///
+/// Objects interested in a [PointerSignalEvent] should register a callback to
+/// be called if they should handle the event. The resolver's purpose is to
+/// ensure that the same pointer signal is not handled by multiple objects in
+/// a hierarchy.
+///
+/// Pointer signals are immediate, so unlike a gesture arena it always resolves
+/// at the end of event dispatch. The first callback registered will be the one
+/// that is called.
+class PointerSignalResolver {
+  PointerSignalResolvedCallback? _firstRegisteredCallback;
+
+  PointerSignalEvent? _currentEvent;
+
+  /// Registers interest in handling [event].
+  void register(PointerSignalEvent event, PointerSignalResolvedCallback callback) {
+    assert(event != null);
+    assert(callback != null);
+    assert(_currentEvent == null || _isSameEvent(_currentEvent!, event));
+    if (_firstRegisteredCallback != null) {
+      return;
+    }
+    _currentEvent = event;
+    _firstRegisteredCallback = callback;
+  }
+
+  /// Resolves the event, calling the first registered callback if there was
+  /// one.
+  ///
+  /// Called after the framework has finished dispatching the pointer signal
+  /// event.
+  void resolve(PointerSignalEvent event) {
+    if (_firstRegisteredCallback == null) {
+      assert(_currentEvent == null);
+      return;
+    }
+    assert(_isSameEvent(_currentEvent!, event));
+    try {
+      _firstRegisteredCallback!(_currentEvent!);
+    } catch (exception, stack) {
+      InformationCollector? collector;
+      assert(() {
+        collector = () sync* {
+          yield DiagnosticsProperty<PointerSignalEvent>('Event', event, style: DiagnosticsTreeStyle.errorProperty);
+        };
+        return true;
+      }());
+      FlutterError.reportError(FlutterErrorDetails(
+        exception: exception,
+        stack: stack,
+        library: 'gesture library',
+        context: ErrorDescription('while resolving a PointerSignalEvent'),
+        informationCollector: collector
+      ));
+    }
+    _firstRegisteredCallback = null;
+    _currentEvent = null;
+  }
+}
diff --git a/lib/src/gestures/recognizer.dart b/lib/src/gestures/recognizer.dart
new file mode 100644
index 0000000..15bdaa9
--- /dev/null
+++ b/lib/src/gestures/recognizer.dart
@@ -0,0 +1,605 @@
+// Copyright 2014 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.
+
+
+import 'dart:async';
+import 'dart:collection';
+import 'package:flute/ui.dart' show Offset;
+
+import 'package:vector_math/vector_math_64.dart';
+import 'package:flute/foundation.dart';
+
+import 'arena.dart';
+import 'binding.dart';
+import 'constants.dart';
+import 'debug.dart';
+import 'events.dart';
+import 'pointer_router.dart';
+import 'team.dart';
+
+export 'pointer_router.dart' show PointerRouter;
+
+/// Generic signature for callbacks passed to
+/// [GestureRecognizer.invokeCallback]. This allows the
+/// [GestureRecognizer.invokeCallback] mechanism to be generically used with
+/// anonymous functions that return objects of particular types.
+typedef RecognizerCallback<T> = T Function();
+
+/// Configuration of offset passed to [DragStartDetails].
+///
+/// The settings determines when a drag formally starts when the user
+/// initiates a drag.
+///
+/// See also:
+///
+///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
+enum DragStartBehavior {
+  /// Set the initial offset, at the position where the first down event was
+  /// detected.
+  down,
+
+  /// Set the initial position at the position where the drag start event was
+  /// detected.
+  start,
+}
+
+/// The base class that all gesture recognizers inherit from.
+///
+/// Provides a basic API that can be used by classes that work with
+/// gesture recognizers but don't care about the specific details of
+/// the gestures recognizers themselves.
+///
+/// See also:
+///
+///  * [GestureDetector], the widget that is used to detect gestures.
+///  * [debugPrintRecognizerCallbacksTrace], a flag that can be set to help
+///    debug issues with gesture recognizers.
+abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableTreeMixin {
+  /// Initializes the gesture recognizer.
+  ///
+  /// The argument is optional and is only used for debug purposes (e.g. in the
+  /// [toString] serialization).
+  ///
+  /// {@template flutter.gestures.GestureRecognizer.kind}
+  /// It's possible to limit this recognizer to a specific [PointerDeviceKind]
+  /// by providing the optional [kind] argument. If [kind] is null,
+  /// the recognizer will accept pointer events from all device kinds.
+  /// {@endtemplate}
+  GestureRecognizer({ this.debugOwner, PointerDeviceKind? kind }) : _kindFilter = kind;
+
+  /// The recognizer's owner.
+  ///
+  /// This is used in the [toString] serialization to report the object for which
+  /// this gesture recognizer was created, to aid in debugging.
+  final Object? debugOwner;
+
+  /// The kind of device that's allowed to be recognized. If null, events from
+  /// all device kinds will be tracked and recognized.
+  final PointerDeviceKind? _kindFilter;
+
+  /// Holds a mapping between pointer IDs and the kind of devices they are
+  /// coming from.
+  final Map<int, PointerDeviceKind> _pointerToKind = <int, PointerDeviceKind>{};
+
+  /// Registers a new pointer that might be relevant to this gesture
+  /// detector.
+  ///
+  /// The owner of this gesture recognizer calls addPointer() with the
+  /// PointerDownEvent of each pointer that should be considered for
+  /// this gesture.
+  ///
+  /// It's the GestureRecognizer's responsibility to then add itself
+  /// to the global pointer router (see [PointerRouter]) to receive
+  /// subsequent events for this pointer, and to add the pointer to
+  /// the global gesture arena manager (see [GestureArenaManager]) to track
+  /// that pointer.
+  ///
+  /// This method is called for each and all pointers being added. In
+  /// most cases, you want to override [addAllowedPointer] instead.
+  void addPointer(PointerDownEvent event) {
+    _pointerToKind[event.pointer] = event.kind;
+    if (isPointerAllowed(event)) {
+      addAllowedPointer(event);
+    } else {
+      handleNonAllowedPointer(event);
+    }
+  }
+
+  /// Registers a new pointer that's been checked to be allowed by this gesture
+  /// recognizer.
+  ///
+  /// Subclasses of [GestureRecognizer] are supposed to override this method
+  /// instead of [addPointer] because [addPointer] will be called for each
+  /// pointer being added while [addAllowedPointer] is only called for pointers
+  /// that are allowed by this recognizer.
+  @protected
+  void addAllowedPointer(PointerDownEvent event) { }
+
+  /// Handles a pointer being added that's not allowed by this recognizer.
+  ///
+  /// Subclasses can override this method and reject the gesture.
+  ///
+  /// See:
+  /// - [OneSequenceGestureRecognizer.handleNonAllowedPointer].
+  @protected
+  void handleNonAllowedPointer(PointerDownEvent event) { }
+
+  /// Checks whether or not a pointer is allowed to be tracked by this recognizer.
+  @protected
+  bool isPointerAllowed(PointerDownEvent event) {
+    // Currently, it only checks for device kind. But in the future we could check
+    // for other things e.g. mouse button.
+    return _kindFilter == null || _kindFilter == event.kind;
+  }
+
+  /// For a given pointer ID, returns the device kind associated with it.
+  ///
+  /// The pointer ID is expected to be a valid one i.e. an event was received
+  /// with that pointer ID.
+  @protected
+  PointerDeviceKind getKindForPointer(int pointer) {
+    assert(_pointerToKind.containsKey(pointer));
+    return _pointerToKind[pointer]!;
+  }
+
+  /// Releases any resources used by the object.
+  ///
+  /// This method is called by the owner of this gesture recognizer
+  /// when the object is no longer needed (e.g. when a gesture
+  /// recognizer is being unregistered from a [GestureDetector], the
+  /// GestureDetector widget calls this method).
+  @mustCallSuper
+  void dispose() { }
+
+  /// Returns a very short pretty description of the gesture that the
+  /// recognizer looks for, like 'tap' or 'horizontal drag'.
+  String get debugDescription;
+
+  /// Invoke a callback provided by the application, catching and logging any
+  /// exceptions.
+  ///
+  /// The `name` argument is ignored except when reporting exceptions.
+  ///
+  /// The `debugReport` argument is optional and is used when
+  /// [debugPrintRecognizerCallbacksTrace] is true. If specified, it must be a
+  /// callback that returns a string describing useful debugging information,
+  /// e.g. the arguments passed to the callback.
+  @protected
+  T? invokeCallback<T>(String name, RecognizerCallback<T> callback, { String Function()? debugReport }) {
+    assert(callback != null);
+    T? result;
+    try {
+      assert(() {
+        if (debugPrintRecognizerCallbacksTrace) {
+          final String? report = debugReport != null ? debugReport() : null;
+          // The 19 in the line below is the width of the prefix used by
+          // _debugLogDiagnostic in arena.dart.
+          final String prefix = debugPrintGestureArenaDiagnostics ? ' ' * 19 + '❙ ' : '';
+          debugPrint('$prefix$this calling $name callback.${ report?.isNotEmpty == true ? " $report" : "" }');
+        }
+        return true;
+      }());
+      result = callback();
+    } catch (exception, stack) {
+      InformationCollector? collector;
+      assert(() {
+        collector = () sync* {
+          yield StringProperty('Handler', name);
+          yield DiagnosticsProperty<GestureRecognizer>('Recognizer', this, style: DiagnosticsTreeStyle.errorProperty);
+        };
+        return true;
+      }());
+      FlutterError.reportError(FlutterErrorDetails(
+        exception: exception,
+        stack: stack,
+        library: 'gesture',
+        context: ErrorDescription('while handling a gesture'),
+        informationCollector: collector
+      ));
+    }
+    return result;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Object>('debugOwner', debugOwner, defaultValue: null));
+  }
+}
+
+/// Base class for gesture recognizers that can only recognize one
+/// gesture at a time. For example, a single [TapGestureRecognizer]
+/// can never recognize two taps happening simultaneously, even if
+/// multiple pointers are placed on the same widget.
+///
+/// This is in contrast to, for instance, [MultiTapGestureRecognizer],
+/// which manages each pointer independently and can consider multiple
+/// simultaneous touches to each result in a separate tap.
+abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
+  /// Initialize the object.
+  ///
+  /// {@macro flutter.gestures.GestureRecognizer.kind}
+  OneSequenceGestureRecognizer({
+    Object? debugOwner,
+    PointerDeviceKind? kind,
+  }) : super(debugOwner: debugOwner, kind: kind);
+
+  final Map<int, GestureArenaEntry> _entries = <int, GestureArenaEntry>{};
+  final Set<int> _trackedPointers = HashSet<int>();
+
+  @override
+  void handleNonAllowedPointer(PointerDownEvent event) {
+    resolve(GestureDisposition.rejected);
+  }
+
+  /// Called when a pointer event is routed to this recognizer.
+  @protected
+  void handleEvent(PointerEvent event);
+
+  @override
+  void acceptGesture(int pointer) { }
+
+  @override
+  void rejectGesture(int pointer) { }
+
+  /// Called when the number of pointers this recognizer is tracking changes from one to zero.
+  ///
+  /// The given pointer ID is the ID of the last pointer this recognizer was
+  /// tracking.
+  @protected
+  void didStopTrackingLastPointer(int pointer);
+
+  /// Resolves this recognizer's participation in each gesture arena with the
+  /// given disposition.
+  @protected
+  @mustCallSuper
+  void resolve(GestureDisposition disposition) {
+    final List<GestureArenaEntry> localEntries = List<GestureArenaEntry>.from(_entries.values);
+    _entries.clear();
+    for (final GestureArenaEntry entry in localEntries)
+      entry.resolve(disposition);
+  }
+
+  /// Resolves this recognizer's participation in the given gesture arena with
+  /// the given disposition.
+  @protected
+  @mustCallSuper
+  void resolvePointer(int pointer, GestureDisposition disposition) {
+    final GestureArenaEntry? entry = _entries[pointer];
+    if (entry != null) {
+      _entries.remove(pointer);
+      entry.resolve(disposition);
+    }
+  }
+
+  @override
+  void dispose() {
+    resolve(GestureDisposition.rejected);
+    for (final int pointer in _trackedPointers)
+      GestureBinding.instance!.pointerRouter.removeRoute(pointer, handleEvent);
+    _trackedPointers.clear();
+    assert(_entries.isEmpty);
+    super.dispose();
+  }
+
+  /// The team that this recognizer belongs to, if any.
+  ///
+  /// If [team] is null, this recognizer competes directly in the
+  /// [GestureArenaManager] to recognize a sequence of pointer events as a
+  /// gesture. If [team] is non-null, this recognizer competes in the arena in
+  /// a group with other recognizers on the same team.
+  ///
+  /// A recognizer can be assigned to a team only when it is not participating
+  /// in the arena. For example, a common time to assign a recognizer to a team
+  /// is shortly after creating the recognizer.
+  GestureArenaTeam? get team => _team;
+  GestureArenaTeam? _team;
+  /// The [team] can only be set once.
+  set team(GestureArenaTeam? value) {
+    assert(value != null);
+    assert(_entries.isEmpty);
+    assert(_trackedPointers.isEmpty);
+    assert(_team == null);
+    _team = value;
+  }
+
+  GestureArenaEntry _addPointerToArena(int pointer) {
+    if (_team != null)
+      return _team!.add(pointer, this);
+    return GestureBinding.instance!.gestureArena.add(pointer, this);
+  }
+
+  /// Causes events related to the given pointer ID to be routed to this recognizer.
+  ///
+  /// The pointer events are transformed according to `transform` and then delivered
+  /// to [handleEvent]. The value for the `transform` argument is usually obtained
+  /// from [PointerDownEvent.transform] to transform the events from the global
+  /// coordinate space into the coordinate space of the event receiver. It may be
+  /// null if no transformation is necessary.
+  ///
+  /// Use [stopTrackingPointer] to remove the route added by this function.
+  @protected
+  void startTrackingPointer(int pointer, [Matrix4? transform]) {
+    GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);
+    _trackedPointers.add(pointer);
+    assert(!_entries.containsValue(pointer));
+    _entries[pointer] = _addPointerToArena(pointer);
+  }
+
+  /// Stops events related to the given pointer ID from being routed to this recognizer.
+  ///
+  /// If this function reduces the number of tracked pointers to zero, it will
+  /// call [didStopTrackingLastPointer] synchronously.
+  ///
+  /// Use [startTrackingPointer] to add the routes in the first place.
+  @protected
+  void stopTrackingPointer(int pointer) {
+    if (_trackedPointers.contains(pointer)) {
+      GestureBinding.instance!.pointerRouter.removeRoute(pointer, handleEvent);
+      _trackedPointers.remove(pointer);
+      if (_trackedPointers.isEmpty)
+        didStopTrackingLastPointer(pointer);
+    }
+  }
+
+  /// Stops tracking the pointer associated with the given event if the event is
+  /// a [PointerUpEvent] or a [PointerCancelEvent] event.
+  @protected
+  void stopTrackingIfPointerNoLongerDown(PointerEvent event) {
+    if (event is PointerUpEvent || event is PointerCancelEvent)
+      stopTrackingPointer(event.pointer);
+  }
+}
+
+/// The possible states of a [PrimaryPointerGestureRecognizer].
+///
+/// The recognizer advances from [ready] to [possible] when it starts tracking a
+/// primary pointer. When the primary pointer is resolved in the gesture
+/// arena (either accepted or rejected), the recognizers advances to [defunct].
+/// Once the recognizer has stopped tracking any remaining pointers, the
+/// recognizer returns to [ready].
+enum GestureRecognizerState {
+  /// The recognizer is ready to start recognizing a gesture.
+  ready,
+
+  /// The sequence of pointer events seen thus far is consistent with the
+  /// gesture the recognizer is attempting to recognize but the gesture has not
+  /// been accepted definitively.
+  possible,
+
+  /// Further pointer events cannot cause this recognizer to recognize the
+  /// gesture until the recognizer returns to the [ready] state (typically when
+  /// all the pointers the recognizer is tracking are removed from the screen).
+  defunct,
+}
+
+/// A base class for gesture recognizers that track a single primary pointer.
+///
+/// Gestures based on this class will stop tracking the gesture if the primary
+/// pointer travels beyond [preAcceptSlopTolerance] or [postAcceptSlopTolerance]
+/// pixels from the original contact point of the gesture.
+///
+/// If the [preAcceptSlopTolerance] was breached before the gesture was accepted
+/// in the gesture arena, the gesture will be rejected.
+abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecognizer {
+  /// Initializes the [deadline] field during construction of subclasses.
+  ///
+  /// {@macro flutter.gestures.GestureRecognizer.kind}
+  PrimaryPointerGestureRecognizer({
+    this.deadline,
+    this.preAcceptSlopTolerance = kTouchSlop,
+    this.postAcceptSlopTolerance = kTouchSlop,
+    Object? debugOwner,
+    PointerDeviceKind? kind,
+  }) : assert(
+         preAcceptSlopTolerance == null || preAcceptSlopTolerance >= 0,
+         'The preAcceptSlopTolerance must be positive or null',
+       ),
+       assert(
+         postAcceptSlopTolerance == null || postAcceptSlopTolerance >= 0,
+         'The postAcceptSlopTolerance must be positive or null',
+       ),
+       super(debugOwner: debugOwner, kind: kind);
+
+  /// If non-null, the recognizer will call [didExceedDeadline] after this
+  /// amount of time has elapsed since starting to track the primary pointer.
+  ///
+  /// The [didExceedDeadline] will not be called if the primary pointer is
+  /// accepted, rejected, or all pointers are up or canceled before [deadline].
+  final Duration? deadline;
+
+  /// The maximum distance in logical pixels the gesture is allowed to drift
+  /// from the initial touch down position before the gesture is accepted.
+  ///
+  /// Drifting past the allowed slop amount causes the gesture to be rejected.
+  ///
+  /// Can be null to indicate that the gesture can drift for any distance.
+  /// Defaults to 18 logical pixels.
+  final double? preAcceptSlopTolerance;
+
+  /// The maximum distance in logical pixels the gesture is allowed to drift
+  /// after the gesture has been accepted.
+  ///
+  /// Drifting past the allowed slop amount causes the gesture to stop tracking
+  /// and signaling subsequent callbacks.
+  ///
+  /// Can be null to indicate that the gesture can drift for any distance.
+  /// Defaults to 18 logical pixels.
+  final double? postAcceptSlopTolerance;
+
+  /// The current state of the recognizer.
+  ///
+  /// See [GestureRecognizerState] for a description of the states.
+  GestureRecognizerState state = GestureRecognizerState.ready;
+
+  /// The ID of the primary pointer this recognizer is tracking.
+  int? primaryPointer;
+
+  /// The location at which the primary pointer contacted the screen.
+  OffsetPair? initialPosition;
+
+  // Whether this pointer is accepted by winning the arena or as defined by
+  // a subclass calling acceptGesture.
+  bool _gestureAccepted = false;
+  Timer? _timer;
+
+  @override
+  void addAllowedPointer(PointerDownEvent event) {
+    startTrackingPointer(event.pointer, event.transform);
+    if (state == GestureRecognizerState.ready) {
+      state = GestureRecognizerState.possible;
+      primaryPointer = event.pointer;
+      initialPosition = OffsetPair(local: event.localPosition, global: event.position);
+      if (deadline != null)
+        _timer = Timer(deadline!, () => didExceedDeadlineWithEvent(event));
+    }
+  }
+
+  @override
+  void handleEvent(PointerEvent event) {
+    assert(state != GestureRecognizerState.ready);
+    if (state == GestureRecognizerState.possible && event.pointer == primaryPointer) {
+      final bool isPreAcceptSlopPastTolerance =
+          !_gestureAccepted &&
+          preAcceptSlopTolerance != null &&
+          _getGlobalDistance(event) > preAcceptSlopTolerance!;
+      final bool isPostAcceptSlopPastTolerance =
+          _gestureAccepted &&
+          postAcceptSlopTolerance != null &&
+          _getGlobalDistance(event) > postAcceptSlopTolerance!;
+
+      if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
+        resolve(GestureDisposition.rejected);
+        stopTrackingPointer(primaryPointer!);
+      } else {
+        handlePrimaryPointer(event);
+      }
+    }
+    stopTrackingIfPointerNoLongerDown(event);
+  }
+
+  /// Override to provide behavior for the primary pointer when the gesture is still possible.
+  @protected
+  void handlePrimaryPointer(PointerEvent event);
+
+  /// Override to be notified when [deadline] is exceeded.
+  ///
+  /// You must override this method or [didExceedDeadlineWithEvent] if you
+  /// supply a [deadline].
+  @protected
+  void didExceedDeadline() {
+    assert(deadline == null);
+  }
+
+  /// Same as [didExceedDeadline] but receives the [event] that initiated the
+  /// gesture.
+  ///
+  /// You must override this method or [didExceedDeadline] if you supply a
+  /// [deadline].
+  @protected
+  void didExceedDeadlineWithEvent(PointerDownEvent event) {
+    didExceedDeadline();
+  }
+
+  @override
+  void acceptGesture(int pointer) {
+    if (pointer == primaryPointer) {
+      _stopTimer();
+      _gestureAccepted = true;
+    }
+  }
+
+  @override
+  void rejectGesture(int pointer) {
+    if (pointer == primaryPointer && state == GestureRecognizerState.possible) {
+      _stopTimer();
+      state = GestureRecognizerState.defunct;
+    }
+  }
+
+  @override
+  void didStopTrackingLastPointer(int pointer) {
+    assert(state != GestureRecognizerState.ready);
+    _stopTimer();
+    state = GestureRecognizerState.ready;
+  }
+
+  @override
+  void dispose() {
+    _stopTimer();
+    super.dispose();
+  }
+
+  void _stopTimer() {
+    if (_timer != null) {
+      _timer!.cancel();
+      _timer = null;
+    }
+  }
+
+  double _getGlobalDistance(PointerEvent event) {
+    final Offset offset = event.position - initialPosition!.global;
+    return offset.distance;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<GestureRecognizerState>('state', state));
+  }
+}
+
+/// A container for a [local] and [global] [Offset] pair.
+///
+/// Usually, the [global] [Offset] is in the coordinate space of the screen
+/// after conversion to logical pixels and the [local] offset is the same
+/// [Offset], but transformed to a local coordinate space.
+class OffsetPair {
+  /// Creates a [OffsetPair] combining a [local] and [global] [Offset].
+  const OffsetPair({
+    required this.local,
+    required this.global,
+  });
+
+  /// Creates a [OffsetPair] from [PointerEvent.localPosition] and
+  /// [PointerEvent.position].
+  factory OffsetPair.fromEventPosition(PointerEvent event) {
+    return OffsetPair(local: event.localPosition, global: event.position);
+  }
+
+  /// Creates a [OffsetPair] from [PointerEvent.localDelta] and
+  /// [PointerEvent.delta].
+  factory OffsetPair.fromEventDelta(PointerEvent event) {
+    return OffsetPair(local: event.localDelta, global: event.delta);
+  }
+
+  /// A [OffsetPair] where both [Offset]s are [Offset.zero].
+  static const OffsetPair zero = OffsetPair(local: Offset.zero, global: Offset.zero);
+
+  /// The [Offset] in the local coordinate space.
+  final Offset local;
+
+  /// The [Offset] in the global coordinate space after conversion to logical
+  /// pixels.
+  final Offset global;
+
+  /// Adds the `other.global` to [global] and `other.local` to [local].
+  OffsetPair operator+(OffsetPair other) {
+    return OffsetPair(
+      local: local + other.local,
+      global: global + other.global,
+    );
+  }
+
+  /// Subtracts the `other.global` from [global] and `other.local` from [local].
+  OffsetPair operator-(OffsetPair other) {
+    return OffsetPair(
+      local: local - other.local,
+      global: global - other.global,
+    );
+  }
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'OffsetPair')}(local: $local, global: $global)';
+}
diff --git a/lib/src/gestures/resampler.dart b/lib/src/gestures/resampler.dart
new file mode 100644
index 0000000..b074a8c
--- /dev/null
+++ b/lib/src/gestures/resampler.dart
@@ -0,0 +1,325 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+
+import 'events.dart';
+
+/// A callback used by [PointerEventResampler.sample] and
+/// [PointerEventResampler.stop] to process a resampled `event`.
+typedef HandleEventCallback = void Function(PointerEvent event);
+
+/// Class for pointer event resampling.
+///
+/// An instance of this class can be used to resample one sequence
+/// of pointer events. Multiple instances are expected to be used for
+/// multi-touch support. The sampling frequency and the sampling
+/// offset is determined by the caller.
+///
+/// This can be used to get smooth touch event processing at the cost
+/// of adding some latency. Devices with low frequency sensors or when
+/// the frequency is not a multiple of the display frequency
+/// (e.g., 120Hz input and 90Hz display) benefit from this.
+///
+/// The following pointer event types are supported:
+/// [PointerAddedEvent], [PointerHoverEvent], [PointerDownEvent],
+/// [PointerMoveEvent], [PointerCancelEvent], [PointerUpEvent],
+/// [PointerRemovedEvent].
+///
+/// Resampling is currently limited to event position and delta. All
+/// pointer event types except [PointerAddedEvent] will be resampled.
+/// [PointerHoverEvent] and [PointerMoveEvent] will only be generated
+/// when the position has changed.
+class PointerEventResampler {
+  // Events queued for processing.
+  final Queue<PointerEvent> _queuedEvents = Queue<PointerEvent>();
+
+  // Pointer state required for resampling.
+  PointerEvent? _last;
+  PointerEvent? _next;
+  Offset _position = Offset.zero;
+  bool _isTracked = false;
+  bool _isDown = false;
+  int _pointerIdentifier = 0;
+
+  PointerEvent _toHoverEvent(
+    PointerEvent event,
+    Offset position,
+    Offset delta,
+    Duration timeStamp,
+  ) {
+    return PointerHoverEvent(
+      timeStamp: timeStamp,
+      kind: event.kind,
+      device: event.device,
+      position: position,
+      delta: delta,
+      buttons: event.buttons,
+      obscured: event.obscured,
+      pressureMin: event.pressureMin,
+      pressureMax: event.pressureMax,
+      distance: event.distance,
+      distanceMax: event.distanceMax,
+      size: event.size,
+      radiusMajor: event.radiusMajor,
+      radiusMinor: event.radiusMinor,
+      radiusMin: event.radiusMin,
+      radiusMax: event.radiusMax,
+      orientation: event.orientation,
+      tilt: event.tilt,
+      synthesized: event.synthesized,
+      embedderId: event.embedderId,
+    );
+  }
+
+  PointerEvent _toMoveEvent(
+    PointerEvent event,
+    Offset position,
+    Offset delta,
+    int pointerIdentifier,
+    Duration timeStamp,
+  ) {
+    return PointerMoveEvent(
+      timeStamp: timeStamp,
+      pointer: pointerIdentifier,
+      kind: event.kind,
+      device: event.device,
+      position: position,
+      delta: delta,
+      buttons: event.buttons,
+      obscured: event.obscured,
+      pressure: event.pressure,
+      pressureMin: event.pressureMin,
+      pressureMax: event.pressureMax,
+      distanceMax: event.distanceMax,
+      size: event.size,
+      radiusMajor: event.radiusMajor,
+      radiusMinor: event.radiusMinor,
+      radiusMin: event.radiusMin,
+      radiusMax: event.radiusMax,
+      orientation: event.orientation,
+      tilt: event.tilt,
+      platformData: event.platformData,
+      synthesized: event.synthesized,
+      embedderId: event.embedderId,
+    );
+  }
+
+  PointerEvent _toMoveOrHoverEvent(
+    PointerEvent event,
+    Offset position,
+    Offset delta,
+    int pointerIdentifier,
+    Duration timeStamp,
+    bool isDown,
+  ) {
+    return isDown ? _toMoveEvent(event, position, delta, pointerIdentifier, timeStamp)
+                  : _toHoverEvent(event, position, delta, timeStamp);
+  }
+
+  Offset _positionAt(Duration sampleTime) {
+    // Use `next` position by default.
+    double x = _next?.position.dx ?? 0.0;
+    double y = _next?.position.dy ?? 0.0;
+
+    final Duration nextTimeStamp = _next?.timeStamp ?? Duration.zero;
+    final Duration lastTimeStamp = _last?.timeStamp ?? Duration.zero;
+
+    // Resample if `next` time stamp is past `sampleTime`.
+    if (nextTimeStamp > sampleTime && nextTimeStamp > lastTimeStamp) {
+      final double interval = (nextTimeStamp - lastTimeStamp).inMicroseconds.toDouble();
+      final double scalar = (sampleTime - lastTimeStamp).inMicroseconds.toDouble() / interval;
+      final double lastX = _last?.position.dx ?? 0.0;
+      final double lastY = _last?.position.dy ?? 0.0;
+      x = lastX + (x - lastX) * scalar;
+      y = lastY + (y - lastY) * scalar;
+    }
+
+    return Offset(x, y);
+  }
+
+  void _processPointerEvents(Duration sampleTime) {
+    final Iterator<PointerEvent> it = _queuedEvents.iterator;
+    while (it.moveNext()) {
+      final PointerEvent event = it.current;
+
+      // Update both `last` and `next` pointer event if time stamp is older
+      // or equal to `sampleTime`.
+      if (event.timeStamp <= sampleTime || _last == null) {
+        _last = event;
+        _next = event;
+        continue;
+      }
+
+      // Update only `next` pointer event if time stamp is more recent than
+      // `sampleTime` and next event is not already more recent.
+      final Duration nextTimeStamp = _next?.timeStamp ?? Duration.zero;
+      if (nextTimeStamp < sampleTime) {
+        _next = event;
+        break;
+      }
+    }
+  }
+
+  void _dequeueAndSampleNonHoverOrMovePointerEventsUntil(
+      Duration sampleTime,
+      Duration nextSampleTime,
+      HandleEventCallback callback,
+  ) {
+    Duration endTime = sampleTime;
+    // Scan queued events to determine end time.
+    final Iterator<PointerEvent> it = _queuedEvents.iterator;
+    while (it.moveNext()) {
+      final PointerEvent event = it.current;
+
+      // Potentially stop dispatching events if more recent than `sampleTime`.
+      if (event.timeStamp > sampleTime) {
+        // Definitely stop if more recent than `nextSampleTime`.
+        if (event.timeStamp >= nextSampleTime) {
+          break;
+        }
+
+        // Update `endTime` to allow early processing of up and removed
+        // events as this improves resampling of these events, which is
+        // important for fling animations.
+        if (event is PointerUpEvent || event is PointerRemovedEvent) {
+          endTime = event.timeStamp;
+          continue;
+        }
+
+        // Stop if event is not move or hover.
+        if (event is! PointerMoveEvent && event is! PointerHoverEvent) {
+          break;
+        }
+      }
+    }
+
+    while (_queuedEvents.isNotEmpty) {
+      final PointerEvent event = _queuedEvents.first;
+
+      // Stop dispatching events if more recent than `endTime`.
+      if (event.timeStamp > endTime) {
+        break;
+      }
+
+      final bool wasTracked = _isTracked;
+      final bool wasDown = _isDown;
+
+      // Update pointer state.
+      _isTracked = event is! PointerRemovedEvent;
+      _isDown = event.down;
+
+      // Position at `sampleTime`.
+      final Offset position = _positionAt(sampleTime);
+
+      // Initialize position if we are starting to track this pointer.
+      if (_isTracked && !wasTracked) {
+        _position = position;
+      }
+
+      // Current pointer identifier.
+      final int pointerIdentifier = event.pointer;
+
+      // Initialize pointer identifier for `move` events.
+      // Identifier is expected to be the same while `down`.
+      assert(!wasDown || _pointerIdentifier == pointerIdentifier);
+      _pointerIdentifier = pointerIdentifier;
+
+      // Skip `move` and `hover` events as they are automatically
+      // generated when the position has changed.
+      if (event is! PointerMoveEvent && event is! PointerHoverEvent) {
+        // Add synthetics `move` or `hover` event if position has changed.
+        // Note: Devices without `hover` events are expected to always have
+        // `add` and `down` events with the same position and this logic will
+        // therefor never produce `hover` events.
+        if (position != _position) {
+          final Offset delta = position - _position;
+          callback(_toMoveOrHoverEvent(event, position, delta, _pointerIdentifier, sampleTime, wasDown));
+          _position = position;
+        }
+        callback(event.copyWith(
+          position: position,
+          delta: Offset.zero,
+          pointer: pointerIdentifier,
+          timeStamp: sampleTime,
+        ));
+      }
+
+      _queuedEvents.removeFirst();
+    }
+  }
+
+  void _samplePointerPosition(
+      Duration sampleTime,
+      HandleEventCallback callback,
+  ) {
+    // Position at `sampleTime`.
+    final Offset position = _positionAt(sampleTime);
+
+    // Add `move` or `hover` events if position has changed.
+    final PointerEvent? next = _next;
+    if (position != _position && next != null) {
+      final Offset delta = position - _position;
+      callback(_toMoveOrHoverEvent(next, position, delta, _pointerIdentifier, sampleTime, _isDown));
+      _position = position;
+    }
+  }
+
+  /// Enqueue pointer `event` for resampling.
+  void addEvent(PointerEvent event) {
+    _queuedEvents.add(event);
+  }
+
+  /// Dispatch resampled pointer events for the specified `sampleTime`
+  /// by calling [callback].
+  ///
+  /// This may dispatch multiple events if position is not the only
+  /// state that has changed since last sample.
+  ///
+  /// Calling [callback] must not add or sample events.
+  ///
+  /// Positive value for `nextSampleTime` allow early processing of
+  /// up and removed events. This improves resampling of these events,
+  /// which is important for fling animations.
+  void sample(
+    Duration sampleTime,
+    Duration nextSampleTime,
+    HandleEventCallback callback,
+  ) {
+    _processPointerEvents(sampleTime);
+
+    // Dequeue and sample pointer events until `sampleTime`.
+    _dequeueAndSampleNonHoverOrMovePointerEventsUntil(sampleTime, nextSampleTime, callback);
+
+    // Dispatch resampled pointer location event if tracked.
+    if (_isTracked) {
+      _samplePointerPosition(sampleTime, callback);
+    }
+  }
+
+  /// Stop resampling.
+  ///
+  /// This will dispatch pending events by calling [callback] and reset
+  /// internal state.
+  void stop(HandleEventCallback callback) {
+    while (_queuedEvents.isNotEmpty) {
+      callback(_queuedEvents.removeFirst());
+    }
+    _pointerIdentifier = 0;
+    _isDown = false;
+    _isTracked = false;
+    _position = Offset.zero;
+    _next = null;
+    _last = null;
+  }
+
+  /// Returns `true` if a call to [sample] can dispatch more events.
+  bool get hasPendingEvents => _queuedEvents.isNotEmpty;
+
+  /// Returns `true` if pointer is currently tracked.
+  bool get isTracked => _isTracked;
+
+  /// Returns `true` if pointer is currently down.
+  bool get isDown => _isDown;
+}
diff --git a/lib/src/gestures/scale.dart b/lib/src/gestures/scale.dart
new file mode 100644
index 0000000..a4ec980
--- /dev/null
+++ b/lib/src/gestures/scale.dart
@@ -0,0 +1,498 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+
+import 'package:vector_math/vector_math_64.dart';
+
+import 'arena.dart';
+import 'constants.dart';
+import 'events.dart';
+import 'recognizer.dart';
+import 'velocity_tracker.dart';
+
+/// The possible states of a [ScaleGestureRecognizer].
+enum _ScaleState {
+  /// The recognizer is ready to start recognizing a gesture.
+  ready,
+
+  /// The sequence of pointer events seen thus far is consistent with a scale
+  /// gesture but the gesture has not been accepted definitively.
+  possible,
+
+  /// The sequence of pointer events seen thus far has been accepted
+  /// definitively as a scale gesture.
+  accepted,
+
+  /// The sequence of pointer events seen thus far has been accepted
+  /// definitively as a scale gesture and the pointers established a focal point
+  /// and initial scale.
+  started,
+}
+
+/// Details for [GestureScaleStartCallback].
+class ScaleStartDetails {
+  /// Creates details for [GestureScaleStartCallback].
+  ///
+  /// The [focalPoint] argument must not be null.
+  ScaleStartDetails({ this.focalPoint = Offset.zero, Offset? localFocalPoint, })
+    : assert(focalPoint != null), localFocalPoint = localFocalPoint ?? focalPoint;
+
+  /// The initial focal point of the pointers in contact with the screen.
+  ///
+  /// Reported in global coordinates.
+  ///
+  /// See also:
+  ///
+  ///  * [localFocalPoint], which is the same value reported in local
+  ///    coordinates.
+  final Offset focalPoint;
+
+  /// The initial focal point of the pointers in contact with the screen.
+  ///
+  /// Reported in local coordinates. Defaults to [focalPoint] if not set in the
+  /// constructor.
+  ///
+  /// See also:
+  ///
+  ///  * [focalPoint], which is the same value reported in global
+  ///    coordinates.
+  final Offset localFocalPoint;
+
+  @override
+  String toString() => 'ScaleStartDetails(focalPoint: $focalPoint, localFocalPoint: $localFocalPoint)';
+}
+
+/// Details for [GestureScaleUpdateCallback].
+class ScaleUpdateDetails {
+  /// Creates details for [GestureScaleUpdateCallback].
+  ///
+  /// The [focalPoint], [scale], [horizontalScale], [verticalScale], [rotation]
+  /// arguments must not be null. The [scale], [horizontalScale], and [verticalScale]
+  /// argument must be greater than or equal to zero.
+  ScaleUpdateDetails({
+    this.focalPoint = Offset.zero,
+    Offset? localFocalPoint,
+    this.scale = 1.0,
+    this.horizontalScale = 1.0,
+    this.verticalScale = 1.0,
+    this.rotation = 0.0,
+  }) : assert(focalPoint != null),
+       assert(scale != null && scale >= 0.0),
+       assert(horizontalScale != null && horizontalScale >= 0.0),
+       assert(verticalScale != null && verticalScale >= 0.0),
+       assert(rotation != null),
+       localFocalPoint = localFocalPoint ?? focalPoint;
+
+  /// The focal point of the pointers in contact with the screen.
+  ///
+  /// Reported in global coordinates.
+  ///
+  /// See also:
+  ///
+  ///  * [localFocalPoint], which is the same value reported in local
+  ///    coordinates.
+  final Offset focalPoint;
+
+  /// The focal point of the pointers in contact with the screen.
+  ///
+  /// Reported in local coordinates. Defaults to [focalPoint] if not set in the
+  /// constructor.
+  ///
+  /// See also:
+  ///
+  ///  * [focalPoint], which is the same value reported in global
+  ///    coordinates.
+  final Offset localFocalPoint;
+
+  /// The scale implied by the average distance between the pointers in contact
+  /// with the screen.
+  ///
+  /// This value must be greater than or equal to zero.
+  ///
+  /// See also:
+  ///
+  ///  * [horizontalScale], which is the scale along the horizontal axis.
+  ///  * [verticalScale], which is the scale along the vertical axis.
+  final double scale;
+
+  /// The scale implied by the average distance along the horizontal axis
+  /// between the pointers in contact with the screen.
+  ///
+  /// This value must be greater than or equal to zero.
+  ///
+  /// See also:
+  ///
+  ///  * [scale], which is the general scale implied by the pointers.
+  ///  * [verticalScale], which is the scale along the vertical axis.
+  final double horizontalScale;
+
+  /// The scale implied by the average distance along the vertical axis
+  /// between the pointers in contact with the screen.
+  ///
+  /// This value must be greater than or equal to zero.
+  ///
+  /// See also:
+  ///
+  ///  * [scale], which is the general scale implied by the pointers.
+  ///  * [horizontalScale], which is the scale along the horizontal axis.
+  final double verticalScale;
+
+  /// The angle implied by the first two pointers to enter in contact with
+  /// the screen.
+  ///
+  /// Expressed in radians.
+  final double rotation;
+
+  @override
+  String toString() => 'ScaleUpdateDetails(focalPoint: $focalPoint, localFocalPoint: $localFocalPoint, scale: $scale, horizontalScale: $horizontalScale, verticalScale: $verticalScale, rotation: $rotation)';
+}
+
+/// Details for [GestureScaleEndCallback].
+class ScaleEndDetails {
+  /// Creates details for [GestureScaleEndCallback].
+  ///
+  /// The [velocity] argument must not be null.
+  ScaleEndDetails({ this.velocity = Velocity.zero })
+    : assert(velocity != null);
+
+  /// The velocity of the last pointer to be lifted off of the screen.
+  final Velocity velocity;
+
+  @override
+  String toString() => 'ScaleEndDetails(velocity: $velocity)';
+}
+
+/// Signature for when the pointers in contact with the screen have established
+/// a focal point and initial scale of 1.0.
+typedef GestureScaleStartCallback = void Function(ScaleStartDetails details);
+
+/// Signature for when the pointers in contact with the screen have indicated a
+/// new focal point and/or scale.
+typedef GestureScaleUpdateCallback = void Function(ScaleUpdateDetails details);
+
+/// Signature for when the pointers are no longer in contact with the screen.
+typedef GestureScaleEndCallback = void Function(ScaleEndDetails details);
+
+bool _isFlingGesture(Velocity velocity) {
+  assert(velocity != null);
+  final double speedSquared = velocity.pixelsPerSecond.distanceSquared;
+  return speedSquared > kMinFlingVelocity * kMinFlingVelocity;
+}
+
+
+/// Defines a line between two pointers on screen.
+///
+/// [_LineBetweenPointers] is an abstraction of a line between two pointers in
+/// contact with the screen. Used to track the rotation of a scale gesture.
+class _LineBetweenPointers{
+
+  /// Creates a [_LineBetweenPointers]. None of the [pointerStartLocation], [pointerStartId]
+  /// [pointerEndLocation] and [pointerEndId] must be null. [pointerStartId] and [pointerEndId]
+  /// should be different.
+  _LineBetweenPointers({
+    this.pointerStartLocation = Offset.zero,
+    this.pointerStartId = 0,
+    this.pointerEndLocation = Offset.zero,
+    this.pointerEndId = 1,
+  }) : assert(pointerStartLocation != null && pointerEndLocation != null),
+       assert(pointerStartId != null && pointerEndId != null),
+       assert(pointerStartId != pointerEndId);
+
+  // The location and the id of the pointer that marks the start of the line.
+  final Offset pointerStartLocation;
+  final int pointerStartId;
+
+  // The location and the id of the pointer that marks the end of the line.
+  final Offset pointerEndLocation;
+  final int pointerEndId;
+
+}
+
+
+/// Recognizes a scale gesture.
+///
+/// [ScaleGestureRecognizer] tracks the pointers in contact with the screen and
+/// calculates their focal point, indicated scale, and rotation. When a focal
+/// pointer is established, the recognizer calls [onStart]. As the focal point,
+/// scale, rotation change, the recognizer calls [onUpdate]. When the pointers
+/// are no longer in contact with the screen, the recognizer calls [onEnd].
+class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
+  /// Create a gesture recognizer for interactions intended for scaling content.
+  ///
+  /// {@macro flutter.gestures.GestureRecognizer.kind}
+  ScaleGestureRecognizer({
+    Object? debugOwner,
+    PointerDeviceKind? kind,
+  }) : super(debugOwner: debugOwner, kind: kind);
+
+  /// The pointers in contact with the screen have established a focal point and
+  /// initial scale of 1.0.
+  GestureScaleStartCallback? onStart;
+
+  /// The pointers in contact with the screen have indicated a new focal point
+  /// and/or scale.
+  GestureScaleUpdateCallback? onUpdate;
+
+  /// The pointers are no longer in contact with the screen.
+  GestureScaleEndCallback? onEnd;
+
+  _ScaleState _state = _ScaleState.ready;
+
+  Matrix4? _lastTransform;
+
+  late Offset _initialFocalPoint;
+  late Offset _currentFocalPoint;
+  late double _initialSpan;
+  late double _currentSpan;
+  late double _initialHorizontalSpan;
+  late double _currentHorizontalSpan;
+  late double _initialVerticalSpan;
+  late double _currentVerticalSpan;
+  _LineBetweenPointers? _initialLine;
+  _LineBetweenPointers? _currentLine;
+  late Map<int, Offset> _pointerLocations;
+  late List<int> _pointerQueue; // A queue to sort pointers in order of entrance
+  final Map<int, VelocityTracker> _velocityTrackers = <int, VelocityTracker>{};
+
+  double get _scaleFactor => _initialSpan > 0.0 ? _currentSpan / _initialSpan : 1.0;
+
+  double get _horizontalScaleFactor => _initialHorizontalSpan > 0.0 ? _currentHorizontalSpan / _initialHorizontalSpan : 1.0;
+
+  double get _verticalScaleFactor => _initialVerticalSpan > 0.0 ? _currentVerticalSpan / _initialVerticalSpan : 1.0;
+
+  double _computeRotationFactor() {
+    if (_initialLine == null || _currentLine == null) {
+      return 0.0;
+    }
+    final double fx = _initialLine!.pointerStartLocation.dx;
+    final double fy = _initialLine!.pointerStartLocation.dy;
+    final double sx = _initialLine!.pointerEndLocation.dx;
+    final double sy = _initialLine!.pointerEndLocation.dy;
+
+    final double nfx = _currentLine!.pointerStartLocation.dx;
+    final double nfy = _currentLine!.pointerStartLocation.dy;
+    final double nsx = _currentLine!.pointerEndLocation.dx;
+    final double nsy = _currentLine!.pointerEndLocation.dy;
+
+    final double angle1 = math.atan2(fy - sy, fx - sx);
+    final double angle2 = math.atan2(nfy - nsy, nfx - nsx);
+
+    return angle2 - angle1;
+  }
+
+  @override
+  void addAllowedPointer(PointerEvent event) {
+    startTrackingPointer(event.pointer, event.transform);
+    _velocityTrackers[event.pointer] = VelocityTracker.withKind(event.kind);
+    if (_state == _ScaleState.ready) {
+      _state = _ScaleState.possible;
+      _initialSpan = 0.0;
+      _currentSpan = 0.0;
+      _initialHorizontalSpan = 0.0;
+      _currentHorizontalSpan = 0.0;
+      _initialVerticalSpan = 0.0;
+      _currentVerticalSpan = 0.0;
+      _pointerLocations = <int, Offset>{};
+      _pointerQueue = <int>[];
+    }
+  }
+
+  @override
+  void handleEvent(PointerEvent event) {
+    assert(_state != _ScaleState.ready);
+    bool didChangeConfiguration = false;
+    bool shouldStartIfAccepted = false;
+    if (event is PointerMoveEvent) {
+      final VelocityTracker tracker = _velocityTrackers[event.pointer]!;
+      if (!event.synthesized)
+        tracker.addPosition(event.timeStamp, event.position);
+      _pointerLocations[event.pointer] = event.position;
+      shouldStartIfAccepted = true;
+      _lastTransform = event.transform;
+    } else if (event is PointerDownEvent) {
+      _pointerLocations[event.pointer] = event.position;
+      _pointerQueue.add(event.pointer);
+      didChangeConfiguration = true;
+      shouldStartIfAccepted = true;
+      _lastTransform = event.transform;
+    } else if (event is PointerUpEvent || event is PointerCancelEvent) {
+      _pointerLocations.remove(event.pointer);
+      _pointerQueue.remove(event.pointer);
+      didChangeConfiguration = true;
+      _lastTransform = event.transform;
+    }
+
+    _updateLines();
+    _update();
+
+    if (!didChangeConfiguration || _reconfigure(event.pointer))
+      _advanceStateMachine(shouldStartIfAccepted, event.kind);
+    stopTrackingIfPointerNoLongerDown(event);
+  }
+
+  void _update() {
+    final int count = _pointerLocations.keys.length;
+
+    // Compute the focal point
+    Offset focalPoint = Offset.zero;
+    for (final int pointer in _pointerLocations.keys)
+      focalPoint += _pointerLocations[pointer]!;
+    _currentFocalPoint = count > 0 ? focalPoint / count.toDouble() : Offset.zero;
+
+    // Span is the average deviation from focal point. Horizontal and vertical
+    // spans are the average deviations from the focal point's horizontal and
+    // vertical coordinates, respectively.
+    double totalDeviation = 0.0;
+    double totalHorizontalDeviation = 0.0;
+    double totalVerticalDeviation = 0.0;
+    for (final int pointer in _pointerLocations.keys) {
+      totalDeviation += (_currentFocalPoint - _pointerLocations[pointer]!).distance;
+      totalHorizontalDeviation += (_currentFocalPoint.dx - _pointerLocations[pointer]!.dx).abs();
+      totalVerticalDeviation += (_currentFocalPoint.dy - _pointerLocations[pointer]!.dy).abs();
+    }
+    _currentSpan = count > 0 ? totalDeviation / count : 0.0;
+    _currentHorizontalSpan = count > 0 ? totalHorizontalDeviation / count : 0.0;
+    _currentVerticalSpan = count > 0 ? totalVerticalDeviation / count : 0.0;
+  }
+
+  /// Updates [_initialLine] and [_currentLine] accordingly to the situation of
+  /// the registered pointers.
+  void _updateLines() {
+    final int count = _pointerLocations.keys.length;
+    assert(_pointerQueue.length >= count);
+    /// In case of just one pointer registered, reconfigure [_initialLine]
+    if (count < 2) {
+      _initialLine = _currentLine;
+    } else if (_initialLine != null &&
+      _initialLine!.pointerStartId == _pointerQueue[0] &&
+      _initialLine!.pointerEndId == _pointerQueue[1]) {
+      /// Rotation updated, set the [_currentLine]
+      _currentLine = _LineBetweenPointers(
+        pointerStartId: _pointerQueue[0],
+        pointerStartLocation: _pointerLocations[_pointerQueue[0]]!,
+        pointerEndId: _pointerQueue[1],
+        pointerEndLocation: _pointerLocations[_pointerQueue[1]]!,
+      );
+    } else {
+      /// A new rotation process is on the way, set the [_initialLine]
+      _initialLine = _LineBetweenPointers(
+        pointerStartId: _pointerQueue[0],
+        pointerStartLocation: _pointerLocations[_pointerQueue[0]]!,
+        pointerEndId: _pointerQueue[1],
+        pointerEndLocation: _pointerLocations[_pointerQueue[1]]!,
+      );
+      _currentLine = null;
+    }
+  }
+
+  bool _reconfigure(int pointer) {
+    _initialFocalPoint = _currentFocalPoint;
+    _initialSpan = _currentSpan;
+    _initialLine = _currentLine;
+    _initialHorizontalSpan = _currentHorizontalSpan;
+    _initialVerticalSpan = _currentVerticalSpan;
+    if (_state == _ScaleState.started) {
+      if (onEnd != null) {
+        final VelocityTracker tracker = _velocityTrackers[pointer]!;
+
+        Velocity velocity = tracker.getVelocity();
+        if (_isFlingGesture(velocity)) {
+          final Offset pixelsPerSecond = velocity.pixelsPerSecond;
+          if (pixelsPerSecond.distanceSquared > kMaxFlingVelocity * kMaxFlingVelocity)
+            velocity = Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * kMaxFlingVelocity);
+          invokeCallback<void>('onEnd', () => onEnd!(ScaleEndDetails(velocity: velocity)));
+        } else {
+          invokeCallback<void>('onEnd', () => onEnd!(ScaleEndDetails(velocity: Velocity.zero)));
+        }
+      }
+      _state = _ScaleState.accepted;
+      return false;
+    }
+    return true;
+  }
+
+  void _advanceStateMachine(bool shouldStartIfAccepted, PointerDeviceKind pointerDeviceKind) {
+    if (_state == _ScaleState.ready)
+      _state = _ScaleState.possible;
+
+    if (_state == _ScaleState.possible) {
+      final double spanDelta = (_currentSpan - _initialSpan).abs();
+      final double focalPointDelta = (_currentFocalPoint - _initialFocalPoint).distance;
+      if (spanDelta > computeScaleSlop(pointerDeviceKind) || focalPointDelta > computePanSlop(pointerDeviceKind))
+        resolve(GestureDisposition.accepted);
+    } else if (_state.index >= _ScaleState.accepted.index) {
+      resolve(GestureDisposition.accepted);
+    }
+
+    if (_state == _ScaleState.accepted && shouldStartIfAccepted) {
+      _state = _ScaleState.started;
+      _dispatchOnStartCallbackIfNeeded();
+    }
+
+    if (_state == _ScaleState.started && onUpdate != null)
+      invokeCallback<void>('onUpdate', () {
+        onUpdate!(ScaleUpdateDetails(
+          scale: _scaleFactor,
+          horizontalScale: _horizontalScaleFactor,
+          verticalScale: _verticalScaleFactor,
+          focalPoint: _currentFocalPoint,
+          localFocalPoint: PointerEvent.transformPosition(_lastTransform, _currentFocalPoint),
+          rotation: _computeRotationFactor(),
+        ));
+      });
+  }
+
+  void _dispatchOnStartCallbackIfNeeded() {
+    assert(_state == _ScaleState.started);
+    if (onStart != null)
+      invokeCallback<void>('onStart', () {
+        onStart!(ScaleStartDetails(
+          focalPoint: _currentFocalPoint,
+          localFocalPoint: PointerEvent.transformPosition(_lastTransform, _currentFocalPoint),
+        ));
+      });
+  }
+
+  @override
+  void acceptGesture(int pointer) {
+    if (_state == _ScaleState.possible) {
+      _state = _ScaleState.started;
+      _dispatchOnStartCallbackIfNeeded();
+    }
+  }
+
+  @override
+  void rejectGesture(int pointer) {
+    stopTrackingPointer(pointer);
+  }
+
+  @override
+  void didStopTrackingLastPointer(int pointer) {
+    switch (_state) {
+      case _ScaleState.possible:
+        resolve(GestureDisposition.rejected);
+        break;
+      case _ScaleState.ready:
+        assert(false); // We should have not seen a pointer yet
+        break;
+      case _ScaleState.accepted:
+        break;
+      case _ScaleState.started:
+        assert(false); // We should be in the accepted state when user is done
+        break;
+    }
+    _state = _ScaleState.ready;
+  }
+
+  @override
+  void dispose() {
+    _velocityTrackers.clear();
+    super.dispose();
+  }
+
+  @override
+  String get debugDescription => 'scale';
+}
diff --git a/lib/src/gestures/tap.dart b/lib/src/gestures/tap.dart
new file mode 100644
index 0000000..5e02627
--- /dev/null
+++ b/lib/src/gestures/tap.dart
@@ -0,0 +1,637 @@
+// Copyright 2014 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.
+
+
+import 'package:vector_math/vector_math_64.dart' show Matrix4;
+import 'package:flute/foundation.dart';
+
+import 'arena.dart';
+import 'constants.dart';
+import 'events.dart';
+import 'recognizer.dart';
+
+/// Details for [GestureTapDownCallback], such as position.
+///
+/// See also:
+///
+///  * [GestureDetector.onTapDown], which receives this information.
+///  * [TapGestureRecognizer], which passes this information to one of its callbacks.
+class TapDownDetails {
+  /// Creates details for a [GestureTapDownCallback].
+  ///
+  /// The [globalPosition] argument must not be null.
+  TapDownDetails({
+    this.globalPosition = Offset.zero,
+    Offset? localPosition,
+    this.kind,
+  }) : assert(globalPosition != null),
+       localPosition = localPosition ?? globalPosition;
+
+  /// The global position at which the pointer contacted the screen.
+  final Offset globalPosition;
+
+  /// The kind of the device that initiated the event.
+  final PointerDeviceKind? kind;
+
+  /// The local position at which the pointer contacted the screen.
+  final Offset localPosition;
+}
+
+/// Signature for when a pointer that might cause a tap has contacted the
+/// screen.
+///
+/// The position at which the pointer contacted the screen is available in the
+/// `details`.
+///
+/// See also:
+///
+///  * [GestureDetector.onTapDown], which matches this signature.
+///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
+typedef GestureTapDownCallback = void Function(TapDownDetails details);
+
+/// Details for [GestureTapUpCallback], such as position.
+///
+/// See also:
+///
+///  * [GestureDetector.onTapUp], which receives this information.
+///  * [TapGestureRecognizer], which passes this information to one of its callbacks.
+class TapUpDetails {
+  /// The [globalPosition] argument must not be null.
+  TapUpDetails({
+    required this.kind,
+    this.globalPosition = Offset.zero,
+    Offset? localPosition,
+  }) : assert(globalPosition != null),
+       localPosition = localPosition ?? globalPosition;
+
+  /// The global position at which the pointer contacted the screen.
+  final Offset globalPosition;
+
+  /// The local position at which the pointer contacted the screen.
+  final Offset localPosition;
+
+  /// The kind of the device that initiated the event.
+  final PointerDeviceKind kind;
+}
+
+/// Signature for when a pointer that will trigger a tap has stopped contacting
+/// the screen.
+///
+/// The position at which the pointer stopped contacting the screen is available
+/// in the `details`.
+///
+/// See also:
+///
+///  * [GestureDetector.onTapUp], which matches this signature.
+///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
+typedef GestureTapUpCallback = void Function(TapUpDetails details);
+
+/// Signature for when a tap has occurred.
+///
+/// See also:
+///
+///  * [GestureDetector.onTap], which matches this signature.
+///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
+typedef GestureTapCallback = void Function();
+
+/// Signature for when the pointer that previously triggered a
+/// [GestureTapDownCallback] will not end up causing a tap.
+///
+/// See also:
+///
+///  * [GestureDetector.onTapCancel], which matches this signature.
+///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
+typedef GestureTapCancelCallback = void Function();
+
+/// A base class for gesture recognizers that recognize taps.
+///
+/// Gesture recognizers take part in gesture arenas to enable potential gestures
+/// to be disambiguated from each other. This process is managed by a
+/// [GestureArenaManager].
+///
+/// A tap is defined as a sequence of events that starts with a down, followed
+/// by optional moves, then ends with an up. All move events must contain the
+/// same `buttons` as the down event, and must not be too far from the initial
+/// position. The gesture is rejected on any violation, a cancel event, or
+/// if any other recognizers wins the arena. It is accepted only when it is the
+/// last member of the arena.
+///
+/// The [BaseTapGestureRecognizer] considers all the pointers involved in the
+/// pointer event sequence as contributing to one gesture. For this reason,
+/// extra pointer interactions during a tap sequence are not recognized as
+/// additional taps. For example, down-1, down-2, up-1, up-2 produces only one
+/// tap on up-1.
+///
+/// The [BaseTapGestureRecognizer] can not be directly used, since it does not
+/// define which buttons to accept, or what to do when a tap happens. If you
+/// want to build a custom tap recognizer, extend this class by overriding
+/// [isPointerAllowed] and the handler methods.
+///
+/// See also:
+///
+///  * [TapGestureRecognizer], a ready-to-use tap recognizer that recognizes
+///    taps of the primary button and taps of the secondary button.
+///  * [ModalBarrier], a widget that uses a custom tap recognizer that accepts
+///    any buttons.
+abstract class BaseTapGestureRecognizer extends PrimaryPointerGestureRecognizer {
+  /// Creates a tap gesture recognizer.
+  BaseTapGestureRecognizer({ Object? debugOwner })
+    : super(deadline: kPressTimeout , debugOwner: debugOwner);
+
+  bool _sentTapDown = false;
+  bool _wonArenaForPrimaryPointer = false;
+
+  PointerDownEvent? _down;
+  PointerUpEvent? _up;
+
+  /// A pointer has contacted the screen, which might be the start of a tap.
+  ///
+  /// This triggers after the down event, once a short timeout ([deadline]) has
+  /// elapsed, or once the gesture has won the arena, whichever comes first.
+  ///
+  /// The parameter `down` is the down event of the primary pointer that started
+  /// the tap sequence.
+  ///
+  /// If this recognizer doesn't win the arena, [handleTapCancel] is called next.
+  /// Otherwise, [handleTapUp] is called next.
+  @protected
+  void handleTapDown({ required PointerDownEvent down });
+
+  /// A pointer has stopped contacting the screen, which is recognized as a tap.
+  ///
+  /// This triggers on the up event if the recognizer wins the arena with it
+  /// or has previously won.
+  ///
+  /// The parameter `down` is the down event of the primary pointer that started
+  /// the tap sequence, and `up` is the up event that ended the tap sequence.
+  ///
+  /// If this recognizer doesn't win the arena, [handleTapCancel] is called
+  /// instead.
+  @protected
+  void handleTapUp({ required PointerDownEvent down, required PointerUpEvent up });
+
+  /// A pointer that previously triggered [handleTapDown] will not end up
+  /// causing a tap.
+  ///
+  /// This triggers once the gesture loses the arena if [handleTapDown] has
+  /// been previously triggered.
+  ///
+  /// The parameter `down` is the down event of the primary pointer that started
+  /// the tap sequence; `cancel` is the cancel event, which might be null;
+  /// `reason` is a short description of the cause if `cancel` is null, which
+  /// can be "forced" if other gestures won the arena, or "spontaneous"
+  /// otherwise.
+  ///
+  /// If this recognizer wins the arena, [handleTapUp] is called instead.
+  @protected
+  void handleTapCancel({ required PointerDownEvent down, PointerCancelEvent? cancel, required String reason });
+
+  @override
+  void addAllowedPointer(PointerDownEvent event) {
+    assert(event != null);
+    if (state == GestureRecognizerState.ready) {
+      // `_down` must be assigned in this method instead of `handlePrimaryPointer`,
+      // because `acceptGesture` might be called before `handlePrimaryPointer`,
+      // which relies on `_down` to call `handleTapDown`.
+      _down = event;
+    }
+    if (_down != null) {
+      // This happens when this tap gesture has been rejected while the pointer
+      // is down (i.e. due to movement), when another allowed pointer is added,
+      // in which case all pointers are simply ignored. The `_down` being null
+      // means that _reset() has been called, since it is always set at the
+      // first allowed down event and will not be cleared except for reset(),
+      super.addAllowedPointer(event);
+    }
+  }
+
+  @override
+  @protected
+  void startTrackingPointer(int pointer, [Matrix4? transform]) {
+    // The recognizer should never track any pointers when `_down` is null,
+    // because calling `_checkDown` in this state will throw exception.
+    assert(_down != null);
+    super.startTrackingPointer(pointer, transform);
+  }
+
+  @override
+  void handlePrimaryPointer(PointerEvent event) {
+    if (event is PointerUpEvent) {
+      _up = event;
+      _checkUp();
+    } else if (event is PointerCancelEvent) {
+      resolve(GestureDisposition.rejected);
+      if (_sentTapDown) {
+        _checkCancel(event, '');
+      }
+      _reset();
+    } else if (event.buttons != _down!.buttons) {
+      resolve(GestureDisposition.rejected);
+      stopTrackingPointer(primaryPointer!);
+    }
+  }
+
+  @override
+  void resolve(GestureDisposition disposition) {
+    if (_wonArenaForPrimaryPointer && disposition == GestureDisposition.rejected) {
+      // This can happen if the gesture has been canceled. For example, when
+      // the pointer has exceeded the touch slop, the buttons have been changed,
+      // or if the recognizer is disposed.
+      assert(_sentTapDown);
+      _checkCancel(null, 'spontaneous');
+      _reset();
+    }
+    super.resolve(disposition);
+  }
+
+  @override
+  void didExceedDeadline() {
+    _checkDown();
+  }
+
+  @override
+  void acceptGesture(int pointer) {
+    super.acceptGesture(pointer);
+    if (pointer == primaryPointer) {
+      _checkDown();
+      _wonArenaForPrimaryPointer = true;
+      _checkUp();
+    }
+  }
+
+  @override
+  void rejectGesture(int pointer) {
+    super.rejectGesture(pointer);
+    if (pointer == primaryPointer) {
+      // Another gesture won the arena.
+      assert(state != GestureRecognizerState.possible);
+      if (_sentTapDown)
+        _checkCancel(null, 'forced');
+      _reset();
+    }
+  }
+
+  void _checkDown() {
+    if (_sentTapDown) {
+      return;
+    }
+    handleTapDown(down: _down!);
+    _sentTapDown = true;
+  }
+
+  void _checkUp() {
+    if (!_wonArenaForPrimaryPointer || _up == null) {
+      return;
+    }
+    handleTapUp(down: _down!, up: _up!);
+    _reset();
+  }
+
+  void _checkCancel(PointerCancelEvent? event, String note) {
+    handleTapCancel(down: _down!, cancel: event, reason: note);
+  }
+
+  void _reset() {
+    _sentTapDown = false;
+    _wonArenaForPrimaryPointer = false;
+    _up = null;
+    _down = null;
+  }
+
+  @override
+  String get debugDescription => 'base tap';
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('wonArenaForPrimaryPointer', value: _wonArenaForPrimaryPointer, ifTrue: 'won arena'));
+    properties.add(DiagnosticsProperty<Offset>('finalPosition', _up?.position, defaultValue: null));
+    properties.add(DiagnosticsProperty<Offset>('finalLocalPosition', _up?.localPosition, defaultValue: _up?.position));
+    properties.add(DiagnosticsProperty<int>('button', _down?.buttons, defaultValue: null));
+    properties.add(FlagProperty('sentTapDown', value: _sentTapDown, ifTrue: 'sent tap down'));
+  }
+}
+
+/// Recognizes taps.
+///
+/// Gesture recognizers take part in gesture arenas to enable potential gestures
+/// to be disambiguated from each other. This process is managed by a
+/// [GestureArenaManager].
+///
+/// [TapGestureRecognizer] considers all the pointers involved in the pointer
+/// event sequence as contributing to one gesture. For this reason, extra
+/// pointer interactions during a tap sequence are not recognized as additional
+/// taps. For example, down-1, down-2, up-1, up-2 produces only one tap on up-1.
+///
+/// [TapGestureRecognizer] competes on pointer events of [kPrimaryButton] only
+/// when it has at least one non-null `onTap*` callback, on events of
+/// [kSecondaryButton] only when it has at least one non-null `onSecondaryTap*`
+/// callback, and on events of [kTertiaryButton] only when it has at least
+/// one non-null `onTertiaryTap*` callback. If it has no callbacks, it is a
+/// no-op.
+///
+/// See also:
+///
+///  * [GestureDetector.onTap], which uses this recognizer.
+///  * [MultiTapGestureRecognizer]
+class TapGestureRecognizer extends BaseTapGestureRecognizer {
+  /// Creates a tap gesture recognizer.
+  TapGestureRecognizer({ Object? debugOwner }) : super(debugOwner: debugOwner);
+
+  /// A pointer has contacted the screen at a particular location with a primary
+  /// button, which might be the start of a tap.
+  ///
+  /// This triggers after the down event, once a short timeout ([deadline]) has
+  /// elapsed, or once the gestures has won the arena, whichever comes first.
+  ///
+  /// If this recognizer doesn't win the arena, [onTapCancel] is called next.
+  /// Otherwise, [onTapUp] is called next.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [onSecondaryTapDown], a similar callback but for a secondary button.
+  ///  * [onTertiaryTapDown], a similar callback but for a tertiary button.
+  ///  * [TapDownDetails], which is passed as an argument to this callback.
+  ///  * [GestureDetector.onTapDown], which exposes this callback.
+  GestureTapDownCallback? onTapDown;
+
+  /// A pointer has stopped contacting the screen at a particular location,
+  /// which is recognized as a tap of a primary button.
+  ///
+  /// This triggers on the up event, if the recognizer wins the arena with it
+  /// or has previously won, immediately followed by [onTap].
+  ///
+  /// If this recognizer doesn't win the arena, [onTapCancel] is called instead.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [onSecondaryTapUp], a similar callback but for a secondary button.
+  ///  * [onTertiaryTapUp], a similar callback but for a tertiary button.
+  ///  * [TapUpDetails], which is passed as an argument to this callback.
+  ///  * [GestureDetector.onTapUp], which exposes this callback.
+  GestureTapUpCallback? onTapUp;
+
+  /// A pointer has stopped contacting the screen, which is recognized as a tap
+  /// of a primary button.
+  ///
+  /// This triggers on the up event, if the recognizer wins the arena with it
+  /// or has previously won, immediately following [onTapUp].
+  ///
+  /// If this recognizer doesn't win the arena, [onTapCancel] is called instead.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [onTapUp], which has the same timing but with details.
+  ///  * [GestureDetector.onTap], which exposes this callback.
+  GestureTapCallback? onTap;
+
+  /// A pointer that previously triggered [onTapDown] will not end up causing
+  /// a tap.
+  ///
+  /// This triggers once the gesture loses the arena if [onTapDown] has
+  /// previously been triggered.
+  ///
+  /// If this recognizer wins the arena, [onTapUp] and [onTap] are called
+  /// instead.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [onSecondaryTapCancel], a similar callback but for a secondary button.
+  ///  * [onTertiaryTapCancel], a similar callback but for a tertiary button.
+  ///  * [GestureDetector.onTapCancel], which exposes this callback.
+  GestureTapCancelCallback? onTapCancel;
+
+  /// A pointer has stopped contacting the screen, which is recognized as a tap
+  /// of a secondary button.
+  ///
+  /// This triggers on the up event, if the recognizer wins the arena with it or
+  /// has previously won, immediately following [onSecondaryTapUp].
+  ///
+  /// If this recognizer doesn't win the arena, [onSecondaryTapCancel] is called
+  /// instead.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [onSecondaryTapUp], which has the same timing but with details.
+  ///  * [GestureDetector.onSecondaryTap], which exposes this callback.
+  GestureTapCallback? onSecondaryTap;
+
+  /// A pointer has contacted the screen at a particular location with a
+  /// secondary button, which might be the start of a secondary tap.
+  ///
+  /// This triggers after the down event, once a short timeout ([deadline]) has
+  /// elapsed, or once the gestures has won the arena, whichever comes first.
+  ///
+  /// If this recognizer doesn't win the arena, [onSecondaryTapCancel] is called
+  /// next. Otherwise, [onSecondaryTapUp] is called next.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [onTapDown], a similar callback but for a primary button.
+  ///  * [onTertiaryTapDown], a similar callback but for a tertiary button.
+  ///  * [TapDownDetails], which is passed as an argument to this callback.
+  ///  * [GestureDetector.onSecondaryTapDown], which exposes this callback.
+  GestureTapDownCallback? onSecondaryTapDown;
+
+  /// A pointer has stopped contacting the screen at a particular location,
+  /// which is recognized as a tap of a secondary button.
+  ///
+  /// This triggers on the up event if the recognizer wins the arena with it
+  /// or has previously won.
+  ///
+  /// If this recognizer doesn't win the arena, [onSecondaryTapCancel] is called
+  /// instead.
+  ///
+  /// See also:
+  ///
+  ///  * [onSecondaryTap], a handler triggered right after this one that doesn't
+  ///    pass any details about the tap.
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [onTapUp], a similar callback but for a primary button.
+  ///  * [onTertiaryTapUp], a similar callback but for a tertiary button.
+  ///  * [TapUpDetails], which is passed as an argument to this callback.
+  ///  * [GestureDetector.onSecondaryTapUp], which exposes this callback.
+  GestureTapUpCallback? onSecondaryTapUp;
+
+  /// A pointer that previously triggered [onSecondaryTapDown] will not end up
+  /// causing a tap.
+  ///
+  /// This triggers once the gesture loses the arena if [onSecondaryTapDown]
+  /// has previously been triggered.
+  ///
+  /// If this recognizer wins the arena, [onSecondaryTapUp] is called instead.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [onTapCancel], a similar callback but for a primary button.
+  ///  * [onTertiaryTapCancel], a similar callback but for a tertiary button.
+  ///  * [GestureDetector.onSecondaryTapCancel], which exposes this callback.
+  GestureTapCancelCallback? onSecondaryTapCancel;
+
+  /// A pointer has contacted the screen at a particular location with a
+  /// tertiary button, which might be the start of a tertiary tap.
+  ///
+  /// This triggers after the down event, once a short timeout ([deadline]) has
+  /// elapsed, or once the gestures has won the arena, whichever comes first.
+  ///
+  /// If this recognizer doesn't win the arena, [onTertiaryTapCancel] is called
+  /// next. Otherwise, [onTertiaryTapUp] is called next.
+  ///
+  /// See also:
+  ///
+  ///  * [kTertiaryButton], the button this callback responds to.
+  ///  * [onTapDown], a similar callback but for a primary button.
+  ///  * [onSecondaryTapDown], a similar callback but for a secondary button.
+  ///  * [TapDownDetails], which is passed as an argument to this callback.
+  ///  * [GestureDetector.onTertiaryTapDown], which exposes this callback.
+  GestureTapDownCallback? onTertiaryTapDown;
+
+  /// A pointer has stopped contacting the screen at a particular location,
+  /// which is recognized as a tap of a tertiary button.
+  ///
+  /// This triggers on the up event if the recognizer wins the arena with it
+  /// or has previously won.
+  ///
+  /// If this recognizer doesn't win the arena, [onTertiaryTapCancel] is called
+  /// instead.
+  ///
+  /// See also:
+  ///
+  ///  * [kTertiaryButton], the button this callback responds to.
+  ///  * [onTapUp], a similar callback but for a primary button.
+  ///  * [onSecondaryTapUp], a similar callback but for a secondary button.
+  ///  * [TapUpDetails], which is passed as an argument to this callback.
+  ///  * [GestureDetector.onTertiaryTapUp], which exposes this callback.
+  GestureTapUpCallback? onTertiaryTapUp;
+
+  /// A pointer that previously triggered [onTertiaryTapDown] will not end up
+  /// causing a tap.
+  ///
+  /// This triggers once the gesture loses the arena if [onTertiaryTapDown]
+  /// has previously been triggered.
+  ///
+  /// If this recognizer wins the arena, [onTertiaryTapUp] is called instead.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [onTapCancel], a similar callback but for a primary button.
+  ///  * [onSecondaryTapCancel], a similar callback but for a secondary button.
+  ///  * [GestureDetector.onTertiaryTapCancel], which exposes this callback.
+  GestureTapCancelCallback? onTertiaryTapCancel;
+
+  @override
+  bool isPointerAllowed(PointerDownEvent event) {
+    switch (event.buttons) {
+      case kPrimaryButton:
+        if (onTapDown == null &&
+            onTap == null &&
+            onTapUp == null &&
+            onTapCancel == null)
+          return false;
+        break;
+      case kSecondaryButton:
+        if (onSecondaryTap == null &&
+            onSecondaryTapDown == null &&
+            onSecondaryTapUp == null &&
+            onSecondaryTapCancel == null)
+          return false;
+        break;
+      case kTertiaryButton:
+        if (onTertiaryTapDown == null &&
+            onTertiaryTapUp == null &&
+            onTertiaryTapCancel == null)
+          return false;
+        break;
+      default:
+        return false;
+    }
+    return super.isPointerAllowed(event);
+  }
+
+  @protected
+  @override
+  void handleTapDown({required PointerDownEvent down}) {
+    final TapDownDetails details = TapDownDetails(
+      globalPosition: down.position,
+      localPosition: down.localPosition,
+      kind: getKindForPointer(down.pointer),
+    );
+    switch (down.buttons) {
+      case kPrimaryButton:
+        if (onTapDown != null)
+          invokeCallback<void>('onTapDown', () => onTapDown!(details));
+        break;
+      case kSecondaryButton:
+        if (onSecondaryTapDown != null)
+          invokeCallback<void>('onSecondaryTapDown', () => onSecondaryTapDown!(details));
+        break;
+      case kTertiaryButton:
+        if (onTertiaryTapDown != null)
+          invokeCallback<void>('onTertiaryTapDown', () => onTertiaryTapDown!(details));
+        break;
+      default:
+    }
+  }
+
+  @protected
+  @override
+  void handleTapUp({ required PointerDownEvent down, required PointerUpEvent up}) {
+    final TapUpDetails details = TapUpDetails(
+      kind: up.kind,
+      globalPosition: up.position,
+      localPosition: up.localPosition,
+    );
+    switch (down.buttons) {
+      case kPrimaryButton:
+        if (onTapUp != null)
+          invokeCallback<void>('onTapUp', () => onTapUp!(details));
+        if (onTap != null)
+          invokeCallback<void>('onTap', onTap!);
+        break;
+      case kSecondaryButton:
+        if (onSecondaryTapUp != null)
+          invokeCallback<void>('onSecondaryTapUp', () => onSecondaryTapUp!(details));
+        if (onSecondaryTap != null)
+          invokeCallback<void>('onSecondaryTap', () => onSecondaryTap!());
+        break;
+      case kTertiaryButton:
+        if (onTertiaryTapUp != null)
+          invokeCallback<void>('onTertiaryTapUp', () => onTertiaryTapUp!(details));
+        break;
+      default:
+    }
+  }
+
+  @protected
+  @override
+  void handleTapCancel({ required PointerDownEvent down, PointerCancelEvent? cancel, required String reason }) {
+    final String note = reason == '' ? reason : '$reason ';
+    switch (down.buttons) {
+      case kPrimaryButton:
+        if (onTapCancel != null)
+          invokeCallback<void>('${note}onTapCancel', onTapCancel!);
+        break;
+      case kSecondaryButton:
+        if (onSecondaryTapCancel != null)
+          invokeCallback<void>('${note}onSecondaryTapCancel', onSecondaryTapCancel!);
+        break;
+      case kTertiaryButton:
+        if (onTertiaryTapCancel != null)
+          invokeCallback<void>('${note}onTertiaryTapCancel', onTertiaryTapCancel!);
+        break;
+      default:
+    }
+  }
+
+  @override
+  String get debugDescription => 'tap';
+}
diff --git a/lib/src/gestures/team.dart b/lib/src/gestures/team.dart
new file mode 100644
index 0000000..724617c
--- /dev/null
+++ b/lib/src/gestures/team.dart
@@ -0,0 +1,147 @@
+// Copyright 2014 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.
+
+
+import 'arena.dart';
+import 'binding.dart';
+
+class _CombiningGestureArenaEntry implements GestureArenaEntry {
+  _CombiningGestureArenaEntry(this._combiner, this._member);
+
+  final _CombiningGestureArenaMember _combiner;
+  final GestureArenaMember _member;
+
+  @override
+  void resolve(GestureDisposition disposition) {
+    _combiner._resolve(_member, disposition);
+  }
+}
+
+class _CombiningGestureArenaMember extends GestureArenaMember {
+  _CombiningGestureArenaMember(this._owner, this._pointer);
+
+  final GestureArenaTeam _owner;
+  final List<GestureArenaMember> _members = <GestureArenaMember>[];
+  final int _pointer;
+
+  bool _resolved = false;
+  GestureArenaMember? _winner;
+  GestureArenaEntry? _entry;
+
+  @override
+  void acceptGesture(int pointer) {
+    assert(_pointer == pointer);
+    assert(_winner != null || _members.isNotEmpty);
+    _close();
+    _winner ??= _owner.captain ?? _members[0];
+    for (final GestureArenaMember member in _members) {
+      if (member != _winner)
+        member.rejectGesture(pointer);
+    }
+    _winner!.acceptGesture(pointer);
+  }
+
+  @override
+  void rejectGesture(int pointer) {
+    assert(_pointer == pointer);
+    _close();
+    for (final GestureArenaMember member in _members)
+      member.rejectGesture(pointer);
+  }
+
+  void _close() {
+    assert(!_resolved);
+    _resolved = true;
+    final _CombiningGestureArenaMember? combiner = _owner._combiners.remove(_pointer);
+    assert(combiner == this);
+  }
+
+  GestureArenaEntry _add(int pointer, GestureArenaMember member) {
+    assert(!_resolved);
+    assert(_pointer == pointer);
+    _members.add(member);
+    _entry ??= GestureBinding.instance!.gestureArena.add(pointer, this);
+    return _CombiningGestureArenaEntry(this, member);
+  }
+
+  void _resolve(GestureArenaMember member, GestureDisposition disposition) {
+    if (_resolved)
+      return;
+    if (disposition == GestureDisposition.rejected) {
+      _members.remove(member);
+      member.rejectGesture(_pointer);
+      if (_members.isEmpty)
+        _entry!.resolve(disposition);
+    } else {
+      assert(disposition == GestureDisposition.accepted);
+      _winner ??= _owner.captain ?? member;
+      _entry!.resolve(disposition);
+    }
+  }
+}
+
+/// A group of [GestureArenaMember] objects that are competing as a unit in the
+/// [GestureArenaManager].
+///
+/// Normally, a recognizer competes directly in the [GestureArenaManager] to
+/// recognize a sequence of pointer events as a gesture. With a
+/// [GestureArenaTeam], recognizers can compete in the arena in a group with
+/// other recognizers. Arena teams may have a captain which wins the arena on
+/// behalf of its team.
+///
+/// When gesture recognizers are in a team together without a captain, then once
+/// there are no other competing gestures in the arena, the first gesture to
+/// have been added to the team automatically wins, instead of the gestures
+/// continuing to compete against each other.
+///
+/// When gesture recognizers are in a team with a captain, then once one of the
+/// team members claims victory or there are no other competing gestures in the
+/// arena, the captain wins the arena, and all other team members lose.
+///
+/// For example, [Slider] uses a team without a captain to support both a
+/// [HorizontalDragGestureRecognizer] and a [TapGestureRecognizer], but without
+/// the drag recognizer having to wait until the user has dragged outside the
+/// slop region of the tap gesture before triggering. Since they compete as a
+/// team, as soon as any other recognizers are out of the arena, the drag
+/// recognizer wins, even if the user has not actually dragged yet. On the other
+/// hand, if the tap can win outright, before the other recognizers are taken
+/// out of the arena (e.g. if the slider is in a vertical scrolling list and the
+/// user places their finger on the touch surface then lifts it, so that neither
+/// the horizontal nor vertical drag recognizers can claim victory) the tap
+/// recognizer still actually wins, despite being in the team.
+///
+/// [AndroidView] uses a team with a captain to decide which gestures are
+/// forwarded to the native view. For example if we want to forward taps and
+/// vertical scrolls to a native Android view, [TapGestureRecognizer]s and
+/// [VerticalDragGestureRecognizer] are added to a team with a captain(the captain is set to be a
+/// gesture recognizer that never explicitly claims the gesture).
+/// The captain allows [AndroidView] to know when any gestures in the team has been
+/// recognized (or all other arena members are out), once the captain wins the
+/// gesture is forwarded to the Android view.
+///
+/// To assign a gesture recognizer to a team, set
+/// [OneSequenceGestureRecognizer.team] to an instance of [GestureArenaTeam].
+class GestureArenaTeam {
+  final Map<int, _CombiningGestureArenaMember> _combiners = <int, _CombiningGestureArenaMember>{};
+
+  /// A member that wins on behalf of the entire team.
+  ///
+  /// If not null, when any one of the [GestureArenaTeam] members claims victory
+  /// the captain accepts the gesture.
+  /// If null, the member that claims a victory accepts the gesture.
+  GestureArenaMember? captain;
+
+  /// Adds a new member to the arena on behalf of this team.
+  ///
+  /// Used by [GestureRecognizer] subclasses that wish to compete in the arena
+  /// using this team.
+  ///
+  /// To assign a gesture recognizer to a team, see
+  /// [OneSequenceGestureRecognizer.team].
+  GestureArenaEntry add(int pointer, GestureArenaMember member) {
+    final _CombiningGestureArenaMember combiner = _combiners.putIfAbsent(
+        pointer, () => _CombiningGestureArenaMember(this, pointer));
+    return combiner._add(pointer, member);
+  }
+}
diff --git a/lib/src/gestures/velocity_tracker.dart b/lib/src/gestures/velocity_tracker.dart
new file mode 100644
index 0000000..1b5e4c9
--- /dev/null
+++ b/lib/src/gestures/velocity_tracker.dart
@@ -0,0 +1,376 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' show Offset;
+
+import 'package:flute/foundation.dart';
+
+import 'events.dart';
+import 'lsq_solver.dart';
+
+export 'package:flute/ui.dart' show Offset;
+
+/// A velocity in two dimensions.
+@immutable
+class Velocity {
+  /// Creates a velocity.
+  ///
+  /// The [pixelsPerSecond] argument must not be null.
+  const Velocity({
+    required this.pixelsPerSecond,
+  }) : assert(pixelsPerSecond != null);
+
+  /// A velocity that isn't moving at all.
+  static const Velocity zero = Velocity(pixelsPerSecond: Offset.zero);
+
+  /// The number of pixels per second of velocity in the x and y directions.
+  final Offset pixelsPerSecond;
+
+  /// Return the negation of a velocity.
+  Velocity operator -() => Velocity(pixelsPerSecond: -pixelsPerSecond);
+
+  /// Return the difference of two velocities.
+  Velocity operator -(Velocity other) {
+    return Velocity(
+        pixelsPerSecond: pixelsPerSecond - other.pixelsPerSecond);
+  }
+
+  /// Return the sum of two velocities.
+  Velocity operator +(Velocity other) {
+    return Velocity(
+        pixelsPerSecond: pixelsPerSecond + other.pixelsPerSecond);
+  }
+
+  /// Return a velocity whose magnitude has been clamped to [minValue]
+  /// and [maxValue].
+  ///
+  /// If the magnitude of this Velocity is less than minValue then return a new
+  /// Velocity with the same direction and with magnitude [minValue]. Similarly,
+  /// if the magnitude of this Velocity is greater than maxValue then return a
+  /// new Velocity with the same direction and magnitude [maxValue].
+  ///
+  /// If the magnitude of this Velocity is within the specified bounds then
+  /// just return this.
+  Velocity clampMagnitude(double minValue, double maxValue) {
+    assert(minValue != null && minValue >= 0.0);
+    assert(maxValue != null && maxValue >= 0.0 && maxValue >= minValue);
+    final double valueSquared = pixelsPerSecond.distanceSquared;
+    if (valueSquared > maxValue * maxValue)
+      return Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * maxValue);
+    if (valueSquared < minValue * minValue)
+      return Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * minValue);
+    return this;
+  }
+
+  @override
+  bool operator ==(Object other) {
+    return other is Velocity
+        && other.pixelsPerSecond == pixelsPerSecond;
+  }
+
+  @override
+  int get hashCode => pixelsPerSecond.hashCode;
+
+  @override
+  String toString() => 'Velocity(${pixelsPerSecond.dx.toStringAsFixed(1)}, ${pixelsPerSecond.dy.toStringAsFixed(1)})';
+}
+
+/// A two dimensional velocity estimate.
+///
+/// VelocityEstimates are computed by [VelocityTracker.getVelocityEstimate]. An
+/// estimate's [confidence] measures how well the velocity tracker's position
+/// data fit a straight line, [duration] is the time that elapsed between the
+/// first and last position sample used to compute the velocity, and [offset]
+/// is similarly the difference between the first and last positions.
+///
+/// See also:
+///
+///  * [VelocityTracker], which computes [VelocityEstimate]s.
+///  * [Velocity], which encapsulates (just) a velocity vector and provides some
+///    useful velocity operations.
+class VelocityEstimate {
+  /// Creates a dimensional velocity estimate.
+  ///
+  /// [pixelsPerSecond], [confidence], [duration], and [offset] must not be null.
+  const VelocityEstimate({
+    required this.pixelsPerSecond,
+    required this.confidence,
+    required this.duration,
+    required this.offset,
+  }) : assert(pixelsPerSecond != null),
+       assert(confidence != null),
+       assert(duration != null),
+       assert(offset != null);
+
+  /// The number of pixels per second of velocity in the x and y directions.
+  final Offset pixelsPerSecond;
+
+  /// A value between 0.0 and 1.0 that indicates how well [VelocityTracker]
+  /// was able to fit a straight line to its position data.
+  ///
+  /// The value of this property is 1.0 for a perfect fit, 0.0 for a poor fit.
+  final double confidence;
+
+  /// The time that elapsed between the first and last position sample used
+  /// to compute [pixelsPerSecond].
+  final Duration duration;
+
+  /// The difference between the first and last position sample used
+  /// to compute [pixelsPerSecond].
+  final Offset offset;
+
+  @override
+  String toString() => 'VelocityEstimate(${pixelsPerSecond.dx.toStringAsFixed(1)}, ${pixelsPerSecond.dy.toStringAsFixed(1)}; offset: $offset, duration: $duration, confidence: ${confidence.toStringAsFixed(1)})';
+}
+
+class _PointAtTime {
+  const _PointAtTime(this.point, this.time)
+    : assert(point != null),
+      assert(time != null);
+
+  final Duration time;
+  final Offset point;
+
+  @override
+  String toString() => '_PointAtTime($point at $time)';
+}
+
+/// Computes a pointer's velocity based on data from [PointerMoveEvent]s.
+///
+/// The input data is provided by calling [addPosition]. Adding data is cheap.
+///
+/// To obtain a velocity, call [getVelocity] or [getVelocityEstimate]. This will
+/// compute the velocity based on the data added so far. Only call these when
+/// you need to use the velocity, as they are comparatively expensive.
+///
+/// The quality of the velocity estimation will be better if more data points
+/// have been received.
+class VelocityTracker {
+  /// Create a new velocity tracker for a pointer [kind].
+  @Deprecated(
+    'Use VelocityTracker.withKind and provide the PointerDeviceKind associated with the gesture being tracked. '
+    'This feature was deprecated after v1.22.0-12.1.pre.'
+  )
+  VelocityTracker([this.kind = PointerDeviceKind.touch]);
+
+  /// Create a new velocity tracker for a pointer [kind].
+  VelocityTracker.withKind(this.kind);
+
+  static const int _assumePointerMoveStoppedMilliseconds = 40;
+  static const int _historySize = 20;
+  static const int _horizonMilliseconds = 100;
+  static const int _minSampleSize = 3;
+
+  /// The kind of pointer this tracker is for.
+  final PointerDeviceKind kind;
+
+  // Circular buffer; current sample at _index.
+  final List<_PointAtTime?> _samples = List<_PointAtTime?>.filled(_historySize, null, growable: false);
+  int _index = 0;
+
+  /// Adds a position as the given time to the tracker.
+  void addPosition(Duration time, Offset position) {
+    _index += 1;
+    if (_index == _historySize)
+      _index = 0;
+    _samples[_index] = _PointAtTime(position, time);
+  }
+
+  /// Returns an estimate of the velocity of the object being tracked by the
+  /// tracker given the current information available to the tracker.
+  ///
+  /// Information is added using [addPosition].
+  ///
+  /// Returns null if there is no data on which to base an estimate.
+  VelocityEstimate? getVelocityEstimate() {
+    final List<double> x = <double>[];
+    final List<double> y = <double>[];
+    final List<double> w = <double>[];
+    final List<double> time = <double>[];
+    int sampleCount = 0;
+    int index = _index;
+
+    final _PointAtTime? newestSample = _samples[index];
+    if (newestSample == null)
+      return null;
+
+    _PointAtTime previousSample = newestSample;
+    _PointAtTime oldestSample = newestSample;
+
+    // Starting with the most recent PointAtTime sample, iterate backwards while
+    // the samples represent continuous motion.
+    do {
+      final _PointAtTime? sample = _samples[index];
+      if (sample == null)
+        break;
+
+      final double age = (newestSample.time - sample.time).inMicroseconds.toDouble() / 1000;
+      final double delta = (sample.time - previousSample.time).inMicroseconds.abs().toDouble() / 1000;
+      previousSample = sample;
+      if (age > _horizonMilliseconds || delta > _assumePointerMoveStoppedMilliseconds)
+        break;
+
+      oldestSample = sample;
+      final Offset position = sample.point;
+      x.add(position.dx);
+      y.add(position.dy);
+      w.add(1.0);
+      time.add(-age);
+      index = (index == 0 ? _historySize : index) - 1;
+
+      sampleCount += 1;
+    } while (sampleCount < _historySize);
+
+    if (sampleCount >= _minSampleSize) {
+      final LeastSquaresSolver xSolver = LeastSquaresSolver(time, x, w);
+      final PolynomialFit? xFit = xSolver.solve(2);
+      if (xFit != null) {
+        final LeastSquaresSolver ySolver = LeastSquaresSolver(time, y, w);
+        final PolynomialFit? yFit = ySolver.solve(2);
+        if (yFit != null) {
+          return VelocityEstimate( // convert from pixels/ms to pixels/s
+            pixelsPerSecond: Offset(xFit.coefficients[1] * 1000, yFit.coefficients[1] * 1000),
+            confidence: xFit.confidence * yFit.confidence,
+            duration: newestSample.time - oldestSample.time,
+            offset: newestSample.point - oldestSample.point,
+          );
+        }
+      }
+    }
+
+    // We're unable to make a velocity estimate but we did have at least one
+    // valid pointer position.
+    return VelocityEstimate(
+      pixelsPerSecond: Offset.zero,
+      confidence: 1.0,
+      duration: newestSample.time - oldestSample.time,
+      offset: newestSample.point - oldestSample.point,
+    );
+  }
+
+  /// Computes the velocity of the pointer at the time of the last
+  /// provided data point.
+  ///
+  /// This can be expensive. Only call this when you need the velocity.
+  ///
+  /// Returns [Velocity.zero] if there is no data from which to compute an
+  /// estimate or if the estimated velocity is zero.
+  Velocity getVelocity() {
+    final VelocityEstimate? estimate = getVelocityEstimate();
+    if (estimate == null || estimate.pixelsPerSecond == Offset.zero)
+      return Velocity.zero;
+    return Velocity(pixelsPerSecond: estimate.pixelsPerSecond);
+  }
+}
+
+/// A [VelocityTracker] subclass that provides a close approximation of iOS
+/// scroll view's velocity estimation strategy.
+///
+/// The estimated velocity reported by this class is a close approximation of
+/// the velocity an iOS scroll view would report with the same
+/// [PointerMoveEvent]s, when the touch that initiates a fling is released.
+///
+/// This class differs from the [VelocityTracker] class in that it uses weighted
+/// average of the latest few velocity samples of the tracked pointer, instead
+/// of doing a linear regression on a relatively large amount of data points, to
+/// estimate the velocity of the tracked pointer. Adding data points and
+/// estimating the velocity are both cheap.
+///
+/// To obtain a velocity, call [getVelocity] or [getVelocityEstimate]. The
+/// estimated velocity is typically used as the initial flinging velocity of a
+/// `Scrollable`, when its drag gesture ends.
+///
+/// See also:
+///
+/// * [scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)](https://developer.apple.com/documentation/uikit/uiscrollviewdelegate/1619385-scrollviewwillenddragging),
+///   the iOS method that reports the fling velocity when the touch is released.
+class IOSScrollViewFlingVelocityTracker extends VelocityTracker {
+  /// Create a new IOSScrollViewFlingVelocityTracker.
+  IOSScrollViewFlingVelocityTracker(PointerDeviceKind kind) : super.withKind(kind);
+
+  /// The velocity estimation uses at most 4 `_PointAtTime` samples. The extra
+  /// samples are there to make the `VelocityEstimate.offset` sufficiently large
+  /// to be recognized as a fling. See
+  /// `VerticalDragGestureRecognizer.isFlingGesture`.
+  static const int _sampleSize = 20;
+
+  final List<_PointAtTime?> _touchSamples = List<_PointAtTime?>.filled(_sampleSize, null, growable: false);
+
+  @override
+  void addPosition(Duration time, Offset position) {
+    assert(() {
+      final _PointAtTime? previousPoint = _touchSamples[_index];
+      if (previousPoint == null || previousPoint.time <= time)
+        return true;
+      throw FlutterError(
+        'The position being added ($position) has a smaller timestamp ($time)'
+        'than its predecessor: $previousPoint.'
+      );
+    }());
+    _index = (_index + 1) % _sampleSize;
+    _touchSamples[_index] = _PointAtTime(position, time);
+  }
+
+  // Computes the velocity using 2 adjacent points in history. When index = 0,
+  // it uses the latest point recorded and the point recorded immediately before
+  // it. The smaller index is, the ealier in history the points used are.
+  Offset _previousVelocityAt(int index) {
+    final int endIndex = (_index + index) % _sampleSize;
+    final int startIndex = (_index + index - 1) % _sampleSize;
+    final _PointAtTime? end = _touchSamples[endIndex];
+    final _PointAtTime? start = _touchSamples[startIndex];
+
+    if (end == null || start == null) {
+      return Offset.zero;
+    }
+
+    final int dt = (end.time - start.time).inMicroseconds;
+    assert(dt >= 0);
+
+    return dt > 0
+      // Convert dt to milliseconds to preserve floating point precision.
+      ? (end.point - start.point) * 1000 / (dt.toDouble() / 1000)
+      : Offset.zero;
+  }
+
+  @override
+  VelocityEstimate getVelocityEstimate() {
+    // The velocity estimated using this expression is an aproximation of the
+    // scroll velocity of an iOS scroll view at the moment the user touch was
+    // released, not the final velocity of the iOS pan gesture recognizer
+    // installed on the scroll view would report. Typically in an iOS scroll
+    // view the velocity values are different between the two, because the
+    // scroll view usually slows down when the touch is released.
+    final Offset estimatedVelocity = _previousVelocityAt(-2) * 0.6
+                                   + _previousVelocityAt(-1) * 0.35
+                                   + _previousVelocityAt(0) * 0.05;
+
+    final _PointAtTime? newestSample = _touchSamples[_index];
+    _PointAtTime? oldestNonNullSample;
+
+    for (int i = 1; i <= _sampleSize; i += 1) {
+      oldestNonNullSample = _touchSamples[(_index + i) % _sampleSize];
+      if (oldestNonNullSample != null)
+        break;
+    }
+
+    if (oldestNonNullSample == null || newestSample == null) {
+      assert(false, 'There must be at least 1 point in _touchSamples: $_touchSamples');
+      return const VelocityEstimate(
+        pixelsPerSecond: Offset.zero,
+        confidence: 0.0,
+        duration: Duration.zero,
+        offset: Offset.zero,
+      );
+    } else {
+      return VelocityEstimate(
+        pixelsPerSecond: estimatedVelocity,
+        confidence: 1.0,
+        duration: newestSample.time - oldestNonNullSample.time,
+        offset: newestSample.point - oldestNonNullSample.point,
+      );
+    }
+  }
+}
diff --git a/lib/src/material/about.dart b/lib/src/material/about.dart
new file mode 100644
index 0000000..466dd2b
--- /dev/null
+++ b/lib/src/material/about.dart
@@ -0,0 +1,1660 @@
+// Copyright 2014 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.
+
+import 'dart:developer' show Timeline, Flow;
+import 'dart:io' show Platform;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/widgets.dart' hide Flow;
+
+import 'app_bar.dart';
+import 'back_button.dart';
+import 'button_bar.dart';
+import 'card.dart';
+import 'constants.dart';
+import 'debug.dart';
+import 'dialog.dart';
+import 'divider.dart';
+import 'floating_action_button.dart';
+import 'floating_action_button_location.dart';
+import 'ink_decoration.dart';
+import 'list_tile.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'page.dart';
+import 'page_transitions_theme.dart';
+import 'progress_indicator.dart';
+import 'scaffold.dart';
+import 'scrollbar.dart';
+import 'text_button.dart';
+import 'text_theme.dart';
+import 'theme.dart';
+
+/// A [ListTile] that shows an about box.
+///
+/// This widget is often added to an app's [Drawer]. When tapped it shows
+/// an about box dialog with [showAboutDialog].
+///
+/// The about box will include a button that shows licenses for software used by
+/// the application. The licenses shown are those returned by the
+/// [LicenseRegistry] API, which can be used to add more licenses to the list.
+///
+/// If your application does not have a [Drawer], you should provide an
+/// affordance to call [showAboutDialog] or (at least) [showLicensePage].
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+///
+/// This sample shows two ways to open [AboutDialog]. The first one
+/// uses an [AboutListTile], and the second uses the [showAboutDialog] function.
+///
+/// ```dart
+///  Widget build(BuildContext context) {
+///    final TextStyle textStyle = Theme.of(context).textTheme.bodyText2;
+///    final List<Widget> aboutBoxChildren = <Widget>[
+///      SizedBox(height: 24),
+///      RichText(
+///        text: TextSpan(
+///          children: <TextSpan>[
+///            TextSpan(
+///              style: textStyle,
+///              text: "Flutter is Google's UI toolkit for building beautiful, "
+///              'natively compiled applications for mobile, web, and desktop '
+///              'from a single codebase. Learn more about Flutter at '
+///            ),
+///            TextSpan(
+///              style: textStyle.copyWith(color: Theme.of(context).accentColor),
+///              text: 'https://flutter.dev'
+///            ),
+///            TextSpan(
+///              style: textStyle,
+///              text: '.'
+///            ),
+///          ],
+///        ),
+///      ),
+///    ];
+///
+///    return Scaffold(
+///      appBar: AppBar(
+///        title: Text('Show About Example'),
+///      ),
+///      drawer: Drawer(
+///        child: SingleChildScrollView(
+///          child: SafeArea(
+///            child: AboutListTile(
+///              icon: Icon(Icons.info),
+///              applicationIcon: FlutterLogo(),
+///              applicationName: 'Show About Example',
+///              applicationVersion: 'August 2019',
+///              applicationLegalese: '\u{a9} 2014 The Flutter Authors',
+///              aboutBoxChildren: aboutBoxChildren,
+///            ),
+///          ),
+///        ),
+///      ),
+///      body: Center(
+///        child: ElevatedButton(
+///          child: Text('Show About Example'),
+///          onPressed: () {
+///            showAboutDialog(
+///              context: context,
+///              applicationIcon: FlutterLogo(),
+///              applicationName: 'Show About Example',
+///              applicationVersion: 'August 2019',
+///              applicationLegalese: '\u{a9} 2014 The Flutter Authors',
+///              children: aboutBoxChildren,
+///            );
+///          },
+///        ),
+///      ),
+///    );
+/// }
+/// ```
+/// {@end-tool}
+class AboutListTile extends StatelessWidget {
+  /// Creates a list tile for showing an about box.
+  ///
+  /// The arguments are all optional. The application name, if omitted, will be
+  /// derived from the nearest [Title] widget. The version, icon, and legalese
+  /// values default to the empty string.
+  const AboutListTile({
+    Key? key,
+    this.icon,
+    this.child,
+    this.applicationName,
+    this.applicationVersion,
+    this.applicationIcon,
+    this.applicationLegalese,
+    this.aboutBoxChildren,
+    this.dense,
+  }) : super(key: key);
+
+  /// The icon to show for this drawer item.
+  ///
+  /// By default no icon is shown.
+  ///
+  /// This is not necessarily the same as the image shown in the dialog box
+  /// itself; which is controlled by the [applicationIcon] property.
+  final Widget? icon;
+
+  /// The label to show on this drawer item.
+  ///
+  /// Defaults to a text widget that says "About Foo" where "Foo" is the
+  /// application name specified by [applicationName].
+  final Widget? child;
+
+  /// The name of the application.
+  ///
+  /// This string is used in the default label for this drawer item (see
+  /// [child]) and as the caption of the [AboutDialog] that is shown.
+  ///
+  /// Defaults to the value of [Title.title], if a [Title] widget can be found.
+  /// Otherwise, defaults to [Platform.resolvedExecutable].
+  final String? applicationName;
+
+  /// The version of this build of the application.
+  ///
+  /// This string is shown under the application name in the [AboutDialog].
+  ///
+  /// Defaults to the empty string.
+  final String? applicationVersion;
+
+  /// The icon to show next to the application name in the [AboutDialog].
+  ///
+  /// By default no icon is shown.
+  ///
+  /// Typically this will be an [ImageIcon] widget. It should honor the
+  /// [IconTheme]'s [IconThemeData.size].
+  ///
+  /// This is not necessarily the same as the icon shown on the drawer item
+  /// itself, which is controlled by the [icon] property.
+  final Widget? applicationIcon;
+
+  /// A string to show in small print in the [AboutDialog].
+  ///
+  /// Typically this is a copyright notice.
+  ///
+  /// Defaults to the empty string.
+  final String? applicationLegalese;
+
+  /// Widgets to add to the [AboutDialog] after the name, version, and legalese.
+  ///
+  /// This could include a link to a Web site, some descriptive text, credits,
+  /// or other information to show in the about box.
+  ///
+  /// Defaults to nothing.
+  final List<Widget>? aboutBoxChildren;
+
+  /// Whether this list tile is part of a vertically dense list.
+  ///
+  /// If this property is null, then its value is based on [ListTileTheme.dense].
+  ///
+  /// Dense list tiles default to a smaller height.
+  final bool? dense;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+    return ListTile(
+      leading: icon,
+      title: child ?? Text(MaterialLocalizations.of(context).aboutListTileTitle(
+        applicationName ?? _defaultApplicationName(context),
+      )),
+      dense: dense,
+      onTap: () {
+        showAboutDialog(
+          context: context,
+          applicationName: applicationName,
+          applicationVersion: applicationVersion,
+          applicationIcon: applicationIcon,
+          applicationLegalese: applicationLegalese,
+          children: aboutBoxChildren,
+        );
+      },
+    );
+  }
+}
+
+/// Displays an [AboutDialog], which describes the application and provides a
+/// button to show licenses for software used by the application.
+///
+/// The arguments correspond to the properties on [AboutDialog].
+///
+/// If the application has a [Drawer], consider using [AboutListTile] instead
+/// of calling this directly.
+///
+/// If you do not need an about box in your application, you should at least
+/// provide an affordance to call [showLicensePage].
+///
+/// The licenses shown on the [LicensePage] are those returned by the
+/// [LicenseRegistry] API, which can be used to add more licenses to the list.
+///
+/// The [context], [useRootNavigator] and [routeSettings] arguments are passed to
+/// [showDialog], the documentation for which discusses how it is used.
+void showAboutDialog({
+  required BuildContext context,
+  String? applicationName,
+  String? applicationVersion,
+  Widget? applicationIcon,
+  String? applicationLegalese,
+  List<Widget>? children,
+  bool useRootNavigator = true,
+  RouteSettings? routeSettings,
+}) {
+  assert(context != null);
+  assert(useRootNavigator != null);
+  showDialog<void>(
+    context: context,
+    useRootNavigator: useRootNavigator,
+    builder: (BuildContext context) {
+      return AboutDialog(
+        applicationName: applicationName,
+        applicationVersion: applicationVersion,
+        applicationIcon: applicationIcon,
+        applicationLegalese: applicationLegalese,
+        children: children,
+      );
+    },
+    routeSettings: routeSettings,
+  );
+}
+
+/// Displays a [LicensePage], which shows licenses for software used by the
+/// application.
+///
+/// The application arguments correspond to the properties on [LicensePage].
+///
+/// The `context` argument is used to look up the [Navigator] for the page.
+///
+/// The `useRootNavigator` argument is used to determine whether to push the
+/// page to the [Navigator] furthest from or nearest to the given `context`. It
+/// is `false` by default.
+///
+/// If the application has a [Drawer], consider using [AboutListTile] instead
+/// of calling this directly.
+///
+/// The [AboutDialog] shown by [showAboutDialog] includes a button that calls
+/// [showLicensePage].
+///
+/// The licenses shown on the [LicensePage] are those returned by the
+/// [LicenseRegistry] API, which can be used to add more licenses to the list.
+void showLicensePage({
+  required BuildContext context,
+  String? applicationName,
+  String? applicationVersion,
+  Widget? applicationIcon,
+  String? applicationLegalese,
+  bool useRootNavigator = false,
+}) {
+  assert(context != null);
+  assert(useRootNavigator != null);
+  Navigator.of(context, rootNavigator: useRootNavigator).push(MaterialPageRoute<void>(
+    builder: (BuildContext context) => LicensePage(
+      applicationName: applicationName,
+      applicationVersion: applicationVersion,
+      applicationIcon: applicationIcon,
+      applicationLegalese: applicationLegalese,
+    ),
+  ));
+}
+
+/// The amount of vertical space to separate chunks of text.
+const double _textVerticalSeparation = 18.0;
+
+/// An about box. This is a dialog box with the application's icon, name,
+/// version number, and copyright, plus a button to show licenses for software
+/// used by the application.
+///
+/// To show an [AboutDialog], use [showAboutDialog].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=YFCSODyFxbE}
+///
+/// If the application has a [Drawer], the [AboutListTile] widget can make the
+/// process of showing an about dialog simpler.
+///
+/// The [AboutDialog] shown by [showAboutDialog] includes a button that calls
+/// [showLicensePage].
+///
+/// The licenses shown on the [LicensePage] are those returned by the
+/// [LicenseRegistry] API, which can be used to add more licenses to the list.
+class AboutDialog extends StatelessWidget {
+  /// Creates an about box.
+  ///
+  /// The arguments are all optional. The application name, if omitted, will be
+  /// derived from the nearest [Title] widget. The version, icon, and legalese
+  /// values default to the empty string.
+  const AboutDialog({
+    Key? key,
+    this.applicationName,
+    this.applicationVersion,
+    this.applicationIcon,
+    this.applicationLegalese,
+    this.children,
+  }) : super(key: key);
+
+  /// The name of the application.
+  ///
+  /// Defaults to the value of [Title.title], if a [Title] widget can be found.
+  /// Otherwise, defaults to [Platform.resolvedExecutable].
+  final String? applicationName;
+
+  /// The version of this build of the application.
+  ///
+  /// This string is shown under the application name.
+  ///
+  /// Defaults to the empty string.
+  final String? applicationVersion;
+
+  /// The icon to show next to the application name.
+  ///
+  /// By default no icon is shown.
+  ///
+  /// Typically this will be an [ImageIcon] widget. It should honor the
+  /// [IconTheme]'s [IconThemeData.size].
+  final Widget? applicationIcon;
+
+  /// A string to show in small print.
+  ///
+  /// Typically this is a copyright notice.
+  ///
+  /// Defaults to the empty string.
+  final String? applicationLegalese;
+
+  /// Widgets to add to the dialog box after the name, version, and legalese.
+  ///
+  /// This could include a link to a Web site, some descriptive text, credits,
+  /// or other information to show in the about box.
+  ///
+  /// Defaults to nothing.
+  final List<Widget>? children;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterialLocalizations(context));
+    final String name = applicationName ?? _defaultApplicationName(context);
+    final String version = applicationVersion ?? _defaultApplicationVersion(context);
+    final Widget? icon = applicationIcon ?? _defaultApplicationIcon(context);
+    return AlertDialog(
+      content: ListBody(
+        children: <Widget>[
+          Row(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: <Widget>[
+              if (icon != null) IconTheme(data: Theme.of(context).iconTheme, child: icon),
+              Expanded(
+                child: Padding(
+                  padding: const EdgeInsets.symmetric(horizontal: 24.0),
+                  child: ListBody(
+                    children: <Widget>[
+                      Text(name, style: Theme.of(context).textTheme.headline5),
+                      Text(version, style: Theme.of(context).textTheme.bodyText2),
+                      const SizedBox(height: _textVerticalSeparation),
+                      Text(applicationLegalese ?? '', style: Theme.of(context).textTheme.caption),
+                    ],
+                  ),
+                ),
+              ),
+            ],
+          ),
+          ...?children,
+        ],
+      ),
+      actions: <Widget>[
+        TextButton(
+          child: Text(MaterialLocalizations.of(context).viewLicensesButtonLabel),
+          onPressed: () {
+            showLicensePage(
+              context: context,
+              applicationName: applicationName,
+              applicationVersion: applicationVersion,
+              applicationIcon: applicationIcon,
+              applicationLegalese: applicationLegalese,
+            );
+          },
+        ),
+        TextButton(
+          child: Text(MaterialLocalizations.of(context).closeButtonLabel),
+          onPressed: () {
+            Navigator.pop(context);
+          },
+        ),
+      ],
+      scrollable: true,
+    );
+  }
+}
+
+/// A page that shows licenses for software used by the application.
+///
+/// To show a [LicensePage], use [showLicensePage].
+///
+/// The [AboutDialog] shown by [showAboutDialog] and [AboutListTile] includes
+/// a button that calls [showLicensePage].
+///
+/// The licenses shown on the [LicensePage] are those returned by the
+/// [LicenseRegistry] API, which can be used to add more licenses to the list.
+class LicensePage extends StatefulWidget {
+  /// Creates a page that shows licenses for software used by the application.
+  ///
+  /// The arguments are all optional. The application name, if omitted, will be
+  /// derived from the nearest [Title] widget. The version and legalese values
+  /// default to the empty string.
+  ///
+  /// The licenses shown on the [LicensePage] are those returned by the
+  /// [LicenseRegistry] API, which can be used to add more licenses to the list.
+  const LicensePage({
+    Key? key,
+    this.applicationName,
+    this.applicationVersion,
+    this.applicationIcon,
+    this.applicationLegalese,
+  }) : super(key: key);
+
+  /// The name of the application.
+  ///
+  /// Defaults to the value of [Title.title], if a [Title] widget can be found.
+  /// Otherwise, defaults to [Platform.resolvedExecutable].
+  final String? applicationName;
+
+  /// The version of this build of the application.
+  ///
+  /// This string is shown under the application name.
+  ///
+  /// Defaults to the empty string.
+  final String? applicationVersion;
+
+  /// The icon to show below the application name.
+  ///
+  /// By default no icon is shown.
+  ///
+  /// Typically this will be an [ImageIcon] widget. It should honor the
+  /// [IconTheme]'s [IconThemeData.size].
+  final Widget? applicationIcon;
+
+  /// A string to show in small print.
+  ///
+  /// Typically this is a copyright notice.
+  ///
+  /// Defaults to the empty string.
+  final String? applicationLegalese;
+
+  @override
+  _LicensePageState createState() => _LicensePageState();
+}
+
+class _LicensePageState extends State<LicensePage> {
+  final ValueNotifier<int?> selectedId = ValueNotifier<int?>(null);
+
+  @override
+  Widget build(BuildContext context) {
+    return _MasterDetailFlow(
+      detailPageFABlessGutterWidth: _getGutterSize(context),
+      title: Text(MaterialLocalizations.of(context).licensesPageTitle),
+      detailPageBuilder: _packageLicensePage,
+      masterViewBuilder: _packagesView,
+    );
+  }
+
+  Widget _packageLicensePage(BuildContext _, Object? args, ScrollController? scrollController) {
+    assert(args is _DetailArguments);
+    final _DetailArguments detailArguments = args! as _DetailArguments;
+    return _PackageLicensePage(
+      packageName: detailArguments.packageName,
+      licenseEntries: detailArguments.licenseEntries,
+      scrollController: scrollController,
+    );
+  }
+
+  Widget _packagesView(final BuildContext _, final bool isLateral) {
+    final Widget about = _AboutProgram(
+        name: widget.applicationName ?? _defaultApplicationName(context),
+        icon: widget.applicationIcon ?? _defaultApplicationIcon(context),
+        version: widget.applicationVersion ?? _defaultApplicationVersion(context),
+        legalese: widget.applicationLegalese,
+      );
+    return _PackagesView(
+      about: about,
+      isLateral: isLateral,
+      selectedId: selectedId,
+    );
+  }
+}
+
+class _AboutProgram extends StatelessWidget {
+  const _AboutProgram({
+    Key? key,
+    required this.name,
+    required this.version,
+    this.icon,
+    this.legalese,
+  })  : assert(name != null),
+        assert(version != null),
+        super(key: key);
+
+  final String name;
+  final String version;
+  final Widget? icon;
+  final String? legalese;
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: EdgeInsets.symmetric(
+        horizontal: _getGutterSize(context),
+        vertical: 24.0,
+      ),
+      child: Column(
+        children: <Widget>[
+          Text(
+            name,
+            style: Theme.of(context).textTheme.headline5,
+            textAlign: TextAlign.center,
+          ),
+          if (icon != null)
+            IconTheme(data: Theme.of(context).iconTheme, child: icon!),
+          Text(
+            version,
+            style: Theme.of(context).textTheme.bodyText2,
+            textAlign: TextAlign.center,
+          ),
+          const SizedBox(height: _textVerticalSeparation),
+          Text(
+            legalese ?? '',
+            style: Theme.of(context).textTheme.caption,
+            textAlign: TextAlign.center,
+          ),
+          const SizedBox(height: _textVerticalSeparation),
+          Text(
+            'Powered by Flutter',
+            style: Theme.of(context).textTheme.bodyText2,
+            textAlign: TextAlign.center,
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+class _PackagesView extends StatefulWidget {
+  const _PackagesView({
+    Key? key,
+    required this.about,
+    required this.isLateral,
+    required this.selectedId,
+  })  : assert(about != null),
+        assert(isLateral != null),
+        super(key: key);
+
+  final Widget about;
+  final bool isLateral;
+  final ValueNotifier<int?> selectedId;
+
+  @override
+  _PackagesViewState createState() => _PackagesViewState();
+}
+
+class _PackagesViewState extends State<_PackagesView> {
+  final Future<_LicenseData> licenses = LicenseRegistry.licenses
+      .fold<_LicenseData>(
+        _LicenseData(),
+        (_LicenseData prev, LicenseEntry license) => prev..addLicense(license),
+      )
+      .then((_LicenseData licenseData) => licenseData..sortPackages());
+
+  @override
+  Widget build(BuildContext context) {
+    return FutureBuilder<_LicenseData>(
+      future: licenses,
+      builder: (BuildContext context, AsyncSnapshot<_LicenseData> snapshot) {
+        return LayoutBuilder(
+          key: ValueKey<ConnectionState>(snapshot.connectionState),
+          builder: (BuildContext context, BoxConstraints constraints) {
+            switch (snapshot.connectionState) {
+              case ConnectionState.done:
+                _initDefaultDetailPage(snapshot.data!, context);
+                return ValueListenableBuilder<int?>(
+                  valueListenable: widget.selectedId,
+                  builder: (BuildContext context, int? selectedId, Widget? _) {
+                    return Center(
+                      child: Material(
+                        color: Theme.of(context).cardColor,
+                        elevation: 4.0,
+                        child: Container(
+                          constraints: BoxConstraints.loose(const Size.fromWidth(600.0)),
+                          child: _packagesList(context, selectedId, snapshot.data!, widget.isLateral),
+                        ),
+                      ),
+                    );
+                  },
+                );
+              default:
+                return Material(
+                    color: Theme.of(context).cardColor,
+                    child: Column(
+                    mainAxisAlignment: MainAxisAlignment.start,
+                    children: <Widget>[
+                      widget.about,
+                      const Center(child: CircularProgressIndicator()),
+                    ],
+                  ),
+                );
+            }
+          },
+        );
+      },
+    );
+  }
+
+  void _initDefaultDetailPage(_LicenseData data, BuildContext context) {
+    if (data.packages.isEmpty) {
+      return;
+    }
+    final String packageName = data.packages[widget.selectedId.value ?? 0];
+    final List<int> bindings = data.packageLicenseBindings[packageName]!;
+    _MasterDetailFlow.of(context)!.setInitialDetailPage(
+      _DetailArguments(
+        packageName,
+        bindings.map((int i) => data.licenses[i]).toList(growable: false),
+      ),
+    );
+  }
+
+  Widget _packagesList(
+    final BuildContext context,
+    final int? selectedId,
+    final _LicenseData data,
+    final bool drawSelection,
+  ) {
+    return ListView(
+      children: <Widget>[
+        widget.about,
+        ...data.packages
+            .asMap()
+            .entries
+            .map<Widget>((MapEntry<int, String> entry) {
+          final String packageName = entry.value;
+          final int index = entry.key;
+          final List<int> bindings = data.packageLicenseBindings[packageName]!;
+          return _PackageListTile(
+            packageName: packageName,
+            index: index,
+            isSelected: drawSelection && entry.key == (selectedId ?? 0),
+            numberLicenses: bindings.length,
+            onTap: () {
+              widget.selectedId.value = index;
+              _MasterDetailFlow.of(context)!.openDetailPage(_DetailArguments(
+                packageName,
+                bindings.map((int i) => data.licenses[i]).toList(growable: false),
+              ));
+            },
+          );
+        }),
+      ],
+    );
+  }
+}
+
+class _PackageListTile extends StatelessWidget {
+  const _PackageListTile({
+    Key? key,
+    required this.packageName,
+    this.index,
+    required this.isSelected,
+    required this.numberLicenses,
+    this.onTap,
+}) : super(key:key);
+
+  final String packageName;
+  final int? index;
+  final bool isSelected;
+  final int numberLicenses;
+  final GestureTapCallback? onTap;
+
+  @override
+  Widget build(BuildContext context) {
+    return Ink(
+      color: isSelected ? Theme.of(context).highlightColor : Theme.of(context).cardColor,
+      child: ListTile(
+        title: Text(packageName),
+        subtitle: Text(MaterialLocalizations.of(context).licensesPackageDetailText(numberLicenses)),
+        selected: isSelected,
+        onTap: onTap,
+      ),
+    );
+  }
+}
+
+/// This is a collection of licenses and the packages to which they apply.
+/// [packageLicenseBindings] records the m+:n+ relationship between the license
+/// and packages as a map of package names to license indexes.
+class _LicenseData {
+  final List<LicenseEntry> licenses = <LicenseEntry>[];
+  final Map<String, List<int>> packageLicenseBindings = <String, List<int>>{};
+  final List<String> packages = <String>[];
+
+  // Special treatment for the first package since it should be the package
+  // for delivered application.
+  String? firstPackage;
+
+  void addLicense(LicenseEntry entry) {
+    // Before the license can be added, we must first record the packages to
+    // which it belongs.
+    for (final String package in entry.packages) {
+      _addPackage(package);
+      // Bind this license to the package using the next index value. This
+      // creates a contract that this license must be inserted at this same
+      // index value.
+      packageLicenseBindings[package]!.add(licenses.length);
+    }
+    licenses.add(entry); // Completion of the contract above.
+  }
+
+  /// Add a package and initialize package license binding. This is a no-op if
+  /// the package has been seen before.
+  void _addPackage(String package) {
+    if (!packageLicenseBindings.containsKey(package)) {
+      packageLicenseBindings[package] = <int>[];
+      firstPackage ??= package;
+      packages.add(package);
+    }
+  }
+
+  /// Sort the packages using some comparison method, or by the default manner,
+  /// which is to put the application package first, followed by every other
+  /// package in case-insensitive alphabetical order.
+  void sortPackages([int Function(String a, String b)? compare]) {
+    packages.sort(compare ?? (String a, String b) {
+      // Based on how LicenseRegistry currently behaves, the first package
+      // returned is the end user application license. This should be
+      // presented first in the list. So here we make sure that first package
+      // remains at the front regardless of alphabetical sorting.
+      if (a == firstPackage) {
+        return -1;
+      }
+      if (b == firstPackage) {
+        return 1;
+      }
+      return a.toLowerCase().compareTo(b.toLowerCase());
+    });
+  }
+}
+
+@immutable
+class _DetailArguments {
+  const _DetailArguments(this.packageName, this.licenseEntries);
+
+  final String packageName;
+  final List<LicenseEntry> licenseEntries;
+
+  @override
+  bool operator ==(final dynamic other) {
+    if (other is _DetailArguments) {
+      return other.packageName == packageName;
+    }
+    return other == this;
+  }
+
+  @override
+  int get hashCode => packageName.hashCode; // Good enough.
+}
+
+class _PackageLicensePage extends StatefulWidget {
+  const _PackageLicensePage({
+    Key? key,
+    required this.packageName,
+    required this.licenseEntries,
+    required this.scrollController,
+  }) : super(key: key);
+
+  final String packageName;
+  final List<LicenseEntry> licenseEntries;
+  final ScrollController? scrollController;
+
+  @override
+  _PackageLicensePageState createState() => _PackageLicensePageState();
+}
+
+class _PackageLicensePageState extends State<_PackageLicensePage> {
+  @override
+  void initState() {
+    super.initState();
+    _initLicenses();
+  }
+
+  final List<Widget> _licenses = <Widget>[];
+  bool _loaded = false;
+
+  Future<void> _initLicenses() async {
+    int debugFlowId = -1;
+    assert(() {
+      final Flow flow = Flow.begin();
+      Timeline.timeSync('_initLicenses()', () { }, flow: flow);
+      debugFlowId = flow.id;
+      return true;
+    }());
+    for (final LicenseEntry license in widget.licenseEntries) {
+      if (!mounted) {
+        return;
+      }
+      assert(() {
+        Timeline.timeSync('_initLicenses()', () { }, flow: Flow.step(debugFlowId));
+        return true;
+      }());
+      final List<LicenseParagraph> paragraphs =
+        await SchedulerBinding.instance!.scheduleTask<List<LicenseParagraph>>(
+          license.paragraphs.toList,
+          Priority.animation,
+          debugLabel: 'License',
+        );
+      if (!mounted) {
+        return;
+      }
+      setState(() {
+        _licenses.add(const Padding(
+          padding: EdgeInsets.all(18.0),
+          child: Divider(),
+        ));
+        for (final LicenseParagraph paragraph in paragraphs) {
+          if (paragraph.indent == LicenseParagraph.centeredIndent) {
+            _licenses.add(Padding(
+              padding: const EdgeInsets.only(top: 16.0),
+              child: Text(
+                paragraph.text,
+                style: const TextStyle(fontWeight: FontWeight.bold),
+                textAlign: TextAlign.center,
+              ),
+            ));
+          } else {
+            assert(paragraph.indent >= 0);
+            _licenses.add(Padding(
+              padding: EdgeInsetsDirectional.only(top: 8.0, start: 16.0 * paragraph.indent),
+              child: Text(paragraph.text),
+            ));
+          }
+        }
+      });
+    }
+    setState(() {
+      _loaded = true;
+    });
+    assert(() {
+      Timeline.timeSync('Build scheduled', () { }, flow: Flow.end(debugFlowId));
+      return true;
+    }());
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterialLocalizations(context));
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final ThemeData theme = Theme.of(context);
+    final String title = widget.packageName;
+    final String subtitle = localizations.licensesPackageDetailText(widget.licenseEntries.length);
+    final double pad = _getGutterSize(context);
+    final EdgeInsets padding = EdgeInsets.only(left: pad, right: pad, bottom: pad);
+    final List<Widget> listWidgets = <Widget>[
+      ..._licenses,
+      if (!_loaded)
+        const Padding(
+          padding: EdgeInsets.symmetric(vertical: 24.0),
+          child: Center(
+            child: CircularProgressIndicator(),
+          ),
+        ),
+    ];
+
+    final Widget page;
+    if (widget.scrollController == null) {
+      page = Scaffold(
+        appBar: AppBar(
+          title: _PackageLicensePageTitle(
+            title,
+            subtitle,
+            theme.appBarTheme.textTheme ?? theme.primaryTextTheme,
+          ),
+        ),
+        body: Center(
+          child: Material(
+            color: theme.cardColor,
+            elevation: 4.0,
+            child: Container(
+              constraints: BoxConstraints.loose(const Size.fromWidth(600.0)),
+              child: Localizations.override(
+                locale: const Locale('en', 'US'),
+                context: context,
+                child: Scrollbar(
+                  child: ListView(padding: padding, children: listWidgets),
+                ),
+              ),
+            ),
+          ),
+        ),
+      );
+    } else {
+      page = CustomScrollView(
+        controller: widget.scrollController,
+        slivers: <Widget>[
+          SliverAppBar(
+            automaticallyImplyLeading: false,
+            pinned: true,
+            backgroundColor: theme.cardColor,
+            title: _PackageLicensePageTitle(title, subtitle, theme.textTheme),
+          ),
+          SliverPadding(
+            padding: padding,
+            sliver: SliverList(
+              delegate: SliverChildBuilderDelegate(
+                (BuildContext context, int index) => Localizations.override(
+                  locale: const Locale('en', 'US'),
+                  context: context,
+                  child: listWidgets[index],
+                ),
+                childCount: listWidgets.length,
+              ),
+            ),
+          ),
+        ],
+      );
+    }
+    return DefaultTextStyle(
+      style: theme.textTheme.caption!,
+      child: page,
+    );
+  }
+}
+
+class _PackageLicensePageTitle extends StatelessWidget {
+  const _PackageLicensePageTitle(
+    this.title,
+    this.subtitle,
+    this.theme, {
+    Key? key,
+  }) : super(key: key);
+
+  final String title;
+  final String subtitle;
+  final TextTheme theme;
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.center,
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: <Widget>[
+        Text(title, style: theme.headline6),
+        Text(subtitle, style: theme.subtitle2),
+      ],
+    );
+  }
+}
+
+String _defaultApplicationName(BuildContext context) {
+  // This doesn't handle the case of the application's title dynamically
+  // changing. In theory, we should make Title expose the current application
+  // title using an InheritedWidget, and so forth. However, in practice, if
+  // someone really wants their application title to change dynamically, they
+  // can provide an explicit applicationName to the widgets defined in this
+  // file, instead of relying on the default.
+  final Title? ancestorTitle = context.findAncestorWidgetOfExactType<Title>();
+  return ancestorTitle?.title ?? Platform.resolvedExecutable.split(Platform.pathSeparator).last;
+}
+
+String _defaultApplicationVersion(BuildContext context) {
+  // TODO(ianh): Get this from the embedder somehow.
+  return '';
+}
+
+Widget? _defaultApplicationIcon(BuildContext context) {
+  // TODO(ianh): Get this from the embedder somehow.
+  return null;
+}
+
+const int _materialGutterThreshold = 720;
+const double _wideGutterSize = 24.0;
+const double _narrowGutterSize = 12.0;
+
+double _getGutterSize(BuildContext context) =>
+    MediaQuery.of(context).size.width >= _materialGutterThreshold ? _wideGutterSize : _narrowGutterSize;
+
+/// Signature for the builder callback used by [_MasterDetailFlow].
+typedef _MasterViewBuilder = Widget Function(BuildContext context, bool isLateralUI);
+
+/// Signature for the builder callback used by [_MasterDetailFlow.detailPageBuilder].
+///
+/// scrollController is provided when the page destination is the draggable
+/// sheet in the lateral UI. Otherwise, it is null.
+typedef _DetailPageBuilder = Widget Function(BuildContext context, Object? arguments, ScrollController? scrollController);
+
+/// Signature for the builder callback used by [_MasterDetailFlow.actionBuilder].
+///
+/// Builds the actions that go in the app bars constructed for the master and
+/// lateral UI pages. actionLevel indicates the intended destination of the
+/// return actions.
+typedef _ActionBuilder = List<Widget> Function(BuildContext context, _ActionLevel actionLevel);
+
+/// Describes which type of app bar the actions are intended for.
+enum _ActionLevel {
+  /// Indicates the top app bar in the lateral UI.
+  top,
+
+  /// Indicates the master view app bar in the lateral UI.
+  view,
+
+  /// Indicates the master page app bar in the nested UI.
+  composite,
+}
+
+/// Describes which layout will be used by [_MasterDetailFlow].
+enum _LayoutMode {
+  /// Use a nested or lateral layout depending on available screen width.
+  auto,
+
+  /// Always use a lateral layout.
+  lateral,
+
+  /// Always use a nested layout.
+  nested,
+}
+
+const String _navMaster = 'master';
+const String _navDetail = 'detail';
+enum _Focus { master, detail }
+
+/// A Master Detail Flow widget. Depending on screen width it builds either a
+/// lateral or nested navigation flow between a master view and a detail page.
+/// bloc pattern.
+///
+/// If focus is on detail view, then switching to nested navigation will
+/// populate the navigation history with the master page and the detail page on
+/// top. Otherwise the focus is on the master view and just the master page
+/// is shown.
+class _MasterDetailFlow extends StatefulWidget {
+  /// Creates a master detail navigation flow which is either nested or
+  /// lateral depending on screen width.
+  const _MasterDetailFlow({
+    Key? key,
+    required this.detailPageBuilder,
+    required this.masterViewBuilder,
+    this.actionBuilder,
+    this.automaticallyImplyLeading = true,
+    this.breakpoint,
+    this.centerTitle,
+    this.detailPageFABGutterWidth,
+    this.detailPageFABlessGutterWidth,
+    this.displayMode = _LayoutMode.auto,
+    this.flexibleSpace,
+    this.floatingActionButton,
+    this.floatingActionButtonLocation,
+    this.floatingActionButtonMasterPageLocation,
+    this.leading,
+    this.masterPageBuilder,
+    this.masterViewWidth,
+    this.title,
+  })  : assert(masterViewBuilder != null),
+        assert(automaticallyImplyLeading != null),
+        assert(detailPageBuilder != null),
+        assert(displayMode != null),
+        super(key: key);
+
+  /// Builder for the master view for lateral navigation.
+  ///
+  /// If [masterPageBuilder] is not supplied the master page required for nested navigation, also
+  /// builds the master view inside a [Scaffold] with an [AppBar].
+  final _MasterViewBuilder masterViewBuilder;
+
+  /// Builder for the master page for nested navigation.
+  ///
+  /// This builder is usually a wrapper around the [masterViewBuilder] builder to provide the
+  /// extra UI required to make a page. However, this builder is optional, and the master page
+  /// can be built using the master view builder and the configuration for the lateral UI's app bar.
+  final _MasterViewBuilder? masterPageBuilder;
+
+  /// Builder for the detail page.
+  ///
+  /// If scrollController == null, the page is intended for nested navigation. The lateral detail
+  /// page is inside a [DraggableScrollableSheet] and should have a scrollable element that uses
+  /// the [ScrollController] provided. In fact, it is strongly recommended the entire lateral
+  /// page is scrollable.
+  final _DetailPageBuilder detailPageBuilder;
+
+  /// Override the width of the master view in the lateral UI.
+  final double? masterViewWidth;
+
+  /// Override the width of the floating action button gutter in the lateral UI.
+  final double? detailPageFABGutterWidth;
+
+  /// Override the width of the gutter when there is no floating action button.
+  final double? detailPageFABlessGutterWidth;
+
+  /// Add a floating action button to the lateral UI. If no [masterPageBuilder] is supplied, this
+  /// floating action button is also used on the nested master page.
+  ///
+  /// See [Scaffold.floatingActionButton].
+  final FloatingActionButton? floatingActionButton;
+
+  /// The title for the lateral UI [AppBar].
+  ///
+  /// See [AppBar.title].
+  final Widget? title;
+
+  /// A widget to display before the title for the lateral UI [AppBar].
+  ///
+  /// See [AppBar.leading].
+  final Widget? leading;
+
+  /// Override the framework from determining whether to show a leading widget or not.
+  ///
+  /// See [AppBar.automaticallyImplyLeading].
+  final bool automaticallyImplyLeading;
+
+  /// Override the framework from determining whether to display the title in the center of the
+  /// app bar or not.
+  ///
+  /// See [AppBar.centerTitle].
+  final bool? centerTitle;
+
+  /// See [AppBar.flexibleSpace].
+  final Widget? flexibleSpace;
+
+  /// Build actions for the lateral UI, and potentially the master page in the nested UI.
+  ///
+  /// If level is [_ActionLevel.top] then the actions are for
+  /// the entire lateral UI page. If level is [_ActionLevel.view] the actions
+  /// are for the master
+  /// view toolbar. Finally, if the [AppBar] for the master page for the nested UI is being built
+  /// by [_MasterDetailFlow], then [_ActionLevel.composite] indicates the
+  /// actions are for the
+  /// nested master page.
+  final _ActionBuilder? actionBuilder;
+
+  /// Determine where the floating action button will go.
+  ///
+  /// If null, [FloatingActionButtonLocation.endTop] is used.
+  ///
+  /// Also see [Scaffold.floatingActionButtonLocation].
+  final FloatingActionButtonLocation? floatingActionButtonLocation;
+
+  /// Determine where the floating action button will go on the master page.
+  ///
+  /// See [Scaffold.floatingActionButtonLocation].
+  final FloatingActionButtonLocation? floatingActionButtonMasterPageLocation;
+
+  /// Forces display mode and style.
+  final _LayoutMode displayMode;
+
+  /// Width at which layout changes from nested to lateral.
+  final double? breakpoint;
+
+  @override
+  _MasterDetailFlowState createState() => _MasterDetailFlowState();
+
+  /// The master detail flow proxy from the closest instance of this class that encloses the given
+  /// context.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// _MasterDetailFlow.of(context).openDetailPage(arguments);
+  /// ```
+  static _MasterDetailFlowProxy? of(
+      BuildContext context, {
+        bool nullOk = false,
+      }) {
+    _PageOpener? pageOpener = context.findAncestorStateOfType<_MasterDetailScaffoldState>();
+    pageOpener ??= context.findAncestorStateOfType<_MasterDetailFlowState>();
+    assert(() {
+      if (pageOpener == null && !nullOk) {
+        throw FlutterError(
+            'Master Detail operation requested with a context that does not include a Master Detail'
+                ' Flow.\nThe context used to open a detail page from the Master Detail Flow must be'
+                ' that of a widget that is a descendant of a Master Detail Flow widget.');
+      }
+      return true;
+    }());
+    return pageOpener != null ? _MasterDetailFlowProxy._(pageOpener) : null;
+  }
+}
+
+/// Interface for interacting with the [_MasterDetailFlow].
+class _MasterDetailFlowProxy implements _PageOpener {
+  _MasterDetailFlowProxy._(this._pageOpener);
+
+  final _PageOpener _pageOpener;
+
+  /// Open detail page with arguments.
+  @override
+  void openDetailPage(Object arguments) =>
+      _pageOpener.openDetailPage(arguments);
+
+  /// Set the initial page to be open for the lateral layout. This can be set at any time, but
+  /// will have no effect after any calls to openDetailPage.
+  @override
+  void setInitialDetailPage(Object arguments) =>
+      _pageOpener.setInitialDetailPage(arguments);
+}
+
+abstract class _PageOpener {
+  void openDetailPage(Object arguments);
+
+  void setInitialDetailPage(Object arguments);
+}
+
+const int _materialWideDisplayThreshold = 840;
+
+class _MasterDetailFlowState extends State<_MasterDetailFlow> implements _PageOpener {
+  /// Tracks whether focus is on the detail or master views. Determines behavior when switching
+  /// from lateral to nested navigation.
+  _Focus focus = _Focus.master;
+
+  /// Cache of arguments passed when opening a detail page. Used when rebuilding.
+  Object? _cachedDetailArguments;
+
+  /// Record of the layout that was built.
+  _LayoutMode? _builtLayout;
+
+  /// Key to access navigator in the nested layout.
+  final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
+
+  @override
+  void openDetailPage(Object arguments) {
+    _cachedDetailArguments = arguments;
+    if (_builtLayout == _LayoutMode.nested) {
+      _navigatorKey.currentState!.pushNamed(_navDetail, arguments: arguments);
+    } else {
+      focus = _Focus.detail;
+    }
+  }
+
+  @override
+  void setInitialDetailPage(Object arguments) {
+    _cachedDetailArguments = arguments;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    switch (widget.displayMode) {
+      case _LayoutMode.nested:
+        return _nestedUI(context);
+      case _LayoutMode.lateral:
+        return _lateralUI(context);
+      case _LayoutMode.auto:
+        return LayoutBuilder(
+            builder: (BuildContext context, BoxConstraints constraints) {
+              final double availableWidth = constraints.maxWidth;
+              if (availableWidth >= (widget.breakpoint ?? _materialWideDisplayThreshold)) {
+                return _lateralUI(context);
+              } else {
+                return _nestedUI(context);
+              }
+            });
+    }
+  }
+
+  Widget _nestedUI(BuildContext context) {
+    _builtLayout = _LayoutMode.nested;
+    final MaterialPageRoute<void> masterPageRoute = _masterPageRoute(context);
+
+    return WillPopScope(
+      // Push pop check into nested navigator.
+      onWillPop: () async => !(await _navigatorKey.currentState!.maybePop()),
+      child: Navigator(
+        key: _navigatorKey,
+        initialRoute: 'initial',
+        onGenerateInitialRoutes: (NavigatorState navigator, String initialRoute) {
+          switch (focus) {
+            case _Focus.master:
+              return <Route<void>>[masterPageRoute];
+            case _Focus.detail:
+              return <Route<void>>[
+                masterPageRoute,
+                _detailPageRoute(_cachedDetailArguments)
+              ];
+          }
+        },
+        onGenerateRoute: (RouteSettings settings) {
+          switch (settings.name) {
+            case _navMaster:
+              // Matching state to navigation event.
+              focus = _Focus.master;
+              return masterPageRoute;
+            case _navDetail:
+              // Matching state to navigation event.
+              focus = _Focus.detail;
+              // Cache detail page settings.
+              _cachedDetailArguments = settings.arguments;
+              return _detailPageRoute(_cachedDetailArguments);
+            default:
+              throw Exception('Unknown route ${settings.name}');
+          }
+        },
+      ),
+    );
+  }
+
+  MaterialPageRoute<void> _masterPageRoute(BuildContext context) {
+    return MaterialPageRoute<dynamic>(
+      builder: (BuildContext c) => BlockSemantics(
+        child: widget.masterPageBuilder != null
+            ? widget.masterPageBuilder!(c, false)
+            : _MasterPage(
+                leading: widget.leading ??
+                    (widget.automaticallyImplyLeading && Navigator.of(context).canPop()
+                        ? BackButton(onPressed: () => Navigator.of(context).pop())
+                        : null),
+                title: widget.title,
+                centerTitle: widget.centerTitle,
+                flexibleSpace: widget.flexibleSpace,
+                automaticallyImplyLeading: widget.automaticallyImplyLeading,
+                floatingActionButton: widget.floatingActionButton,
+                floatingActionButtonLocation: widget.floatingActionButtonMasterPageLocation,
+                masterViewBuilder: widget.masterViewBuilder,
+                actionBuilder: widget.actionBuilder,
+              ),
+      ),
+    );
+  }
+
+  MaterialPageRoute<void> _detailPageRoute(Object? arguments) {
+    return MaterialPageRoute<dynamic>(builder: (BuildContext context) {
+      return WillPopScope(
+        onWillPop: () async {
+          // No need for setState() as rebuild happens on navigation pop.
+          focus = _Focus.master;
+          Navigator.of(context).pop();
+          return false;
+        },
+        child: BlockSemantics(child: widget.detailPageBuilder(context, arguments, null)),
+      );
+    });
+  }
+
+  Widget _lateralUI(BuildContext context) {
+    _builtLayout = _LayoutMode.lateral;
+    return _MasterDetailScaffold(
+      actionBuilder: widget.actionBuilder ?? (_, __) => const<Widget>[],
+      automaticallyImplyLeading: widget.automaticallyImplyLeading,
+      centerTitle: widget.centerTitle,
+      detailPageBuilder: (BuildContext context, Object? args, ScrollController? scrollController) =>
+          widget.detailPageBuilder(context, args ?? _cachedDetailArguments, scrollController),
+      floatingActionButton: widget.floatingActionButton,
+      detailPageFABlessGutterWidth: widget.detailPageFABlessGutterWidth,
+      detailPageFABGutterWidth: widget.detailPageFABGutterWidth,
+      floatingActionButtonLocation: widget.floatingActionButtonLocation,
+      initialArguments: _cachedDetailArguments,
+      leading: widget.leading,
+      masterViewBuilder: (BuildContext context, bool isLateral) => widget.masterViewBuilder(context, isLateral),
+      masterViewWidth: widget.masterViewWidth,
+      title: widget.title,
+    );
+  }
+}
+
+class _MasterPage extends StatelessWidget {
+  const _MasterPage({
+    Key? key,
+    this.leading,
+    this.title,
+    this.actionBuilder,
+    this.centerTitle,
+    this.flexibleSpace,
+    this.floatingActionButton,
+    this.floatingActionButtonLocation,
+    this.masterViewBuilder,
+    required this.automaticallyImplyLeading,
+  }) : super(key: key);
+
+  final _MasterViewBuilder? masterViewBuilder;
+  final Widget? title;
+  final Widget? leading;
+  final bool automaticallyImplyLeading;
+  final bool? centerTitle;
+  final Widget? flexibleSpace;
+  final _ActionBuilder? actionBuilder;
+  final FloatingActionButton? floatingActionButton;
+  final FloatingActionButtonLocation? floatingActionButtonLocation;
+
+  @override
+  Widget build(BuildContext context) {
+      return Scaffold(
+        appBar: AppBar(
+          title: title,
+          leading: leading,
+          actions: actionBuilder == null
+              ? const <Widget>[]
+              : actionBuilder!(context, _ActionLevel.composite),
+          centerTitle: centerTitle,
+          flexibleSpace: flexibleSpace,
+          automaticallyImplyLeading: automaticallyImplyLeading,
+        ),
+        body: masterViewBuilder!(context, false),
+        floatingActionButton: floatingActionButton,
+        floatingActionButtonLocation: floatingActionButtonLocation,
+      );
+  }
+
+}
+
+const double _kCardElevation = 4.0;
+const double _kMasterViewWidth = 320.0;
+const double _kDetailPageFABlessGutterWidth = 40.0;
+const double _kDetailPageFABGutterWidth = 84.0;
+
+class _MasterDetailScaffold extends StatefulWidget {
+  const _MasterDetailScaffold({
+    Key? key,
+    required this.detailPageBuilder,
+    required this.masterViewBuilder,
+    this.actionBuilder,
+    this.floatingActionButton,
+    this.floatingActionButtonLocation,
+    this.initialArguments,
+    this.leading,
+    this.title,
+    required this.automaticallyImplyLeading,
+    this.centerTitle,
+    this.detailPageFABlessGutterWidth,
+    this.detailPageFABGutterWidth,
+    this.masterViewWidth,
+  })  : assert(detailPageBuilder != null),
+        assert(masterViewBuilder != null),
+        super(key: key);
+
+  final _MasterViewBuilder masterViewBuilder;
+
+  /// Builder for the detail page.
+  ///
+  /// The detail page is inside a [DraggableScrollableSheet] and should have a scrollable element
+  /// that uses the [ScrollController] provided. In fact, it is strongly recommended the entire
+  /// lateral page is scrollable.
+  final _DetailPageBuilder detailPageBuilder;
+  final _ActionBuilder? actionBuilder;
+  final FloatingActionButton? floatingActionButton;
+  final FloatingActionButtonLocation? floatingActionButtonLocation;
+  final Object? initialArguments;
+  final Widget? leading;
+  final Widget? title;
+  final bool automaticallyImplyLeading;
+  final bool? centerTitle;
+  final double? detailPageFABlessGutterWidth;
+  final double? detailPageFABGutterWidth;
+  final double? masterViewWidth;
+
+  @override
+  _MasterDetailScaffoldState createState() => _MasterDetailScaffoldState();
+}
+
+class _MasterDetailScaffoldState extends State<_MasterDetailScaffold>
+    implements _PageOpener {
+  late FloatingActionButtonLocation floatingActionButtonLocation;
+  late double detailPageFABGutterWidth;
+  late double detailPageFABlessGutterWidth;
+  late double masterViewWidth;
+
+  final ValueNotifier<Object?> _detailArguments = ValueNotifier<Object?>(null);
+
+  @override
+  void initState() {
+    super.initState();
+    detailPageFABlessGutterWidth = widget.detailPageFABlessGutterWidth ?? _kDetailPageFABlessGutterWidth;
+    detailPageFABGutterWidth = widget.detailPageFABGutterWidth ?? _kDetailPageFABGutterWidth;
+    masterViewWidth = widget.masterViewWidth ?? _kMasterViewWidth;
+    floatingActionButtonLocation = widget.floatingActionButtonLocation ?? FloatingActionButtonLocation.endTop;
+  }
+
+  @override
+  void openDetailPage(Object arguments) {
+    SchedulerBinding.instance!
+        .addPostFrameCallback((_) => _detailArguments.value = arguments);
+    _MasterDetailFlow.of(context)!.openDetailPage(arguments);
+  }
+
+  @override
+  void setInitialDetailPage(Object arguments) {
+    SchedulerBinding.instance!
+        .addPostFrameCallback((_) => _detailArguments.value = arguments);
+    _MasterDetailFlow.of(context)!.setInitialDetailPage(arguments);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Stack(
+      children: <Widget>[
+        Scaffold(
+          floatingActionButtonLocation: floatingActionButtonLocation,
+          appBar: AppBar(
+            title: widget.title,
+            actions: widget.actionBuilder!(context, _ActionLevel.top),
+            leading: widget.leading,
+            automaticallyImplyLeading: widget.automaticallyImplyLeading,
+            centerTitle: widget.centerTitle,
+            bottom: PreferredSize(
+              preferredSize: const Size.fromHeight(kToolbarHeight),
+              child: Row(
+                mainAxisAlignment: MainAxisAlignment.start,
+                children: <Widget>[
+                  ConstrainedBox(
+                    constraints:
+                    BoxConstraints.tightFor(width: masterViewWidth),
+                    child: IconTheme(
+                      data: Theme.of(context).primaryIconTheme,
+                      child: ButtonBar(
+                        children:
+                        widget.actionBuilder!(context, _ActionLevel.view),
+                      ),
+                    ),
+                  )
+                ],
+              ),
+            ),
+          ),
+          body: _masterPanel(context),
+          floatingActionButton: widget.floatingActionButton,
+        ),
+        // Detail view stacked above main scaffold and master view.
+        SafeArea(
+          child: Padding(
+            padding: EdgeInsetsDirectional.only(
+              start: masterViewWidth - _kCardElevation,
+              end: widget.floatingActionButton == null
+                  ? detailPageFABlessGutterWidth
+                  : detailPageFABGutterWidth,
+            ),
+            child: ValueListenableBuilder<Object?>(
+              valueListenable: _detailArguments,
+              builder: (BuildContext context, Object? value, Widget? child) {
+                return AnimatedSwitcher(
+                  transitionBuilder:
+                      (Widget child, Animation<double> animation) =>
+                      const FadeUpwardsPageTransitionsBuilder()
+                          .buildTransitions<void>(
+                          null, null, animation, null, child),
+                  duration: const Duration(milliseconds: 500),
+                  child: Container(
+                    key: ValueKey<Object?>(value ?? widget.initialArguments),
+                    constraints: const BoxConstraints.expand(),
+                    child: _DetailView(
+                      builder: widget.detailPageBuilder,
+                      arguments: value ?? widget.initialArguments,
+                    ),
+                  ),
+                );
+              },
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+
+  ConstrainedBox _masterPanel(BuildContext context, {bool needsScaffold = false}) {
+    return ConstrainedBox(
+      constraints: BoxConstraints(maxWidth: masterViewWidth),
+      child: needsScaffold
+          ? Scaffold(
+              appBar: AppBar(
+                title: widget.title,
+                actions: widget.actionBuilder!(context, _ActionLevel.top),
+                leading: widget.leading,
+                automaticallyImplyLeading: widget.automaticallyImplyLeading,
+                centerTitle: widget.centerTitle,
+              ),
+              body: widget.masterViewBuilder(context, true),
+            )
+          : widget.masterViewBuilder(context, true),
+    );
+  }
+}
+
+class _DetailView extends StatelessWidget {
+  const _DetailView({
+    Key? key,
+    required _DetailPageBuilder builder,
+    Object? arguments,
+  })  : assert(builder != null),
+        _builder = builder,
+        _arguments = arguments,
+        super(key: key);
+
+  final _DetailPageBuilder _builder;
+  final Object? _arguments;
+
+  @override
+  Widget build(BuildContext context) {
+    if (_arguments == null) {
+      return Container();
+    }
+    final double screenHeight = MediaQuery.of(context).size.height;
+    final double minHeight = (screenHeight - kToolbarHeight) / screenHeight;
+
+    return DraggableScrollableSheet(
+      initialChildSize: minHeight,
+      minChildSize: minHeight,
+      maxChildSize: 1,
+      expand: false,
+      builder: (BuildContext context, ScrollController controller) {
+        return MouseRegion(
+          // TODO(TonicArtos): Remove MouseRegion workaround for pointer hover events passing through DraggableScrollableSheet once https://github.com/flutter/flutter/issues/59741 is resolved.
+          child: Card(
+            color: Theme.of(context).cardColor,
+            elevation: _kCardElevation,
+            clipBehavior: Clip.antiAlias,
+            margin: const EdgeInsets.fromLTRB(
+                _kCardElevation, 0.0, _kCardElevation, 0.0),
+            shape: const RoundedRectangleBorder(
+              borderRadius: BorderRadius.vertical(
+                  top: Radius.circular(3.0), bottom: Radius.zero),
+            ),
+            child: _builder(
+              context,
+              _arguments,
+              controller,
+            ),
+          ),
+        );
+      },
+    );
+  }
+}
diff --git a/lib/src/material/animated_icons.dart b/lib/src/material/animated_icons.dart
new file mode 100644
index 0000000..a9cfe10
--- /dev/null
+++ b/lib/src/material/animated_icons.dart
@@ -0,0 +1,38 @@
+// Copyright 2014 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.
+
+/// Flutter widgets implementing Material Design animated icons.
+library material_animated_icons;
+
+import 'dart:math' as math show pi;
+import 'package:flute/ui.dart' as ui show Paint, Path, Canvas;
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/widgets.dart';
+
+// This package is split into multiple parts to enable a private API that is
+// testable.
+
+// Public API.
+part 'animated_icons/animated_icons.dart';
+
+// Provides a public interface for referring to the private icon
+// implementations.
+part 'animated_icons/animated_icons_data.dart';
+
+// Generated animated icon data files.
+part 'animated_icons/data/add_event.g.dart';
+part 'animated_icons/data/arrow_menu.g.dart';
+part 'animated_icons/data/close_menu.g.dart';
+part 'animated_icons/data/ellipsis_search.g.dart';
+part 'animated_icons/data/event_add.g.dart';
+part 'animated_icons/data/home_menu.g.dart';
+part 'animated_icons/data/list_view.g.dart';
+part 'animated_icons/data/menu_arrow.g.dart';
+part 'animated_icons/data/menu_close.g.dart';
+part 'animated_icons/data/menu_home.g.dart';
+part 'animated_icons/data/pause_play.g.dart';
+part 'animated_icons/data/play_pause.g.dart';
+part 'animated_icons/data/search_ellipsis.g.dart';
+part 'animated_icons/data/view_list.g.dart';
diff --git a/lib/src/material/animated_icons/animated_icons.dart b/lib/src/material/animated_icons/animated_icons.dart
new file mode 100644
index 0000000..bbea08d
--- /dev/null
+++ b/lib/src/material/animated_icons/animated_icons.dart
@@ -0,0 +1,305 @@
+// Copyright 2014 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.
+
+part of material_animated_icons;
+
+// The code for drawing animated icons is kept in a private API, as we are not
+// yet ready for exposing a public API for (partial) vector graphics support.
+// See: https://github.com/flutter/flutter/issues/1831 for details regarding
+// generic vector graphics support in Flutter.
+
+// Examples can assume:
+// late AnimationController controller;
+
+/// Shows an animated icon at a given animation [progress].
+///
+/// The available icons are specified in [AnimatedIcons].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=pJcbh8pbvJs}
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// AnimatedIcon(
+///   icon: AnimatedIcons.menu_arrow,
+///   progress: controller,
+///   semanticLabel: 'Show menu',
+/// )
+/// ```
+/// {@end-tool}
+///
+class AnimatedIcon extends StatelessWidget {
+
+  /// Creates an AnimatedIcon.
+  ///
+  /// The [progress] and [icon] arguments must not be null.
+  /// The [size] and [color] default to the value given by the current [IconTheme].
+  const AnimatedIcon({
+    Key? key,
+    required this.icon,
+    required this.progress,
+    this.color,
+    this.size,
+    this.semanticLabel,
+    this.textDirection,
+  }) : assert(progress != null),
+       assert(icon != null),
+       super(key: key);
+
+  /// The animation progress for the animated icon.
+  ///
+  /// The value is clamped to be between 0 and 1.
+  ///
+  /// This determines the actual frame that is displayed.
+  final Animation<double> progress;
+
+  /// The color to use when drawing the icon.
+  ///
+  /// Defaults to the current [IconTheme] color, if any.
+  ///
+  /// The given color will be adjusted by the opacity of the current
+  /// [IconTheme], if any.
+  ///
+  /// In material apps, if there is a [Theme] without any [IconTheme]s
+  /// specified, icon colors default to white if the theme is dark
+  /// and black if the theme is light.
+  ///
+  /// If no [IconTheme] and no [Theme] is specified, icons will default to black.
+  ///
+  /// See [Theme] to set the current theme and [ThemeData.brightness]
+  /// for setting the current theme's brightness.
+  final Color? color;
+
+  /// The size of the icon in logical pixels.
+  ///
+  /// Icons occupy a square with width and height equal to size.
+  ///
+  /// Defaults to the current [IconTheme] size.
+  final double? size;
+
+  /// The icon to display. Available icons are listed in [AnimatedIcons].
+  final AnimatedIconData icon;
+
+  /// Semantic label for the icon.
+  ///
+  /// Announced in accessibility modes (e.g TalkBack/VoiceOver).
+  /// This label does not show in the UI.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsProperties.label], which is set to [semanticLabel] in the
+  ///    underlying [Semantics] widget.
+  final String? semanticLabel;
+
+  /// The text direction to use for rendering the icon.
+  ///
+  /// If this is null, the ambient [Directionality] is used instead.
+  ///
+  /// If the text direction is [TextDirection.rtl], the icon will be mirrored
+  /// horizontally (e.g back arrow will point right).
+  final TextDirection? textDirection;
+
+  static final _UiPathFactory _pathFactory = () => ui.Path();
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasDirectionality(context));
+    final _AnimatedIconData iconData = icon as _AnimatedIconData;
+    final IconThemeData iconTheme = IconTheme.of(context);
+    assert(iconTheme.isConcrete);
+    final double iconSize = size ?? iconTheme.size!;
+    final TextDirection textDirection = this.textDirection ?? Directionality.of(context);
+    final double iconOpacity = iconTheme.opacity!;
+    Color iconColor = color ?? iconTheme.color!;
+    if (iconOpacity != 1.0)
+      iconColor = iconColor.withOpacity(iconColor.opacity * iconOpacity);
+    return Semantics(
+      label: semanticLabel,
+      child: CustomPaint(
+        size: Size(iconSize, iconSize),
+        painter: _AnimatedIconPainter(
+          paths: iconData.paths,
+          progress: progress,
+          color: iconColor,
+          scale: iconSize / iconData.size.width,
+          shouldMirror: textDirection == TextDirection.rtl && iconData.matchTextDirection,
+          uiPathFactory: _pathFactory,
+        ),
+      ),
+    );
+  }
+}
+
+typedef _UiPathFactory = ui.Path Function();
+
+class _AnimatedIconPainter extends CustomPainter {
+  _AnimatedIconPainter({
+    required this.paths,
+    required this.progress,
+    required this.color,
+    required this.scale,
+    required this.shouldMirror,
+    required this.uiPathFactory,
+  }) : super(repaint: progress);
+
+  // This list is assumed to be immutable, changes to the contents of the list
+  // will not trigger a redraw as shouldRepaint will keep returning false.
+  final List<_PathFrames> paths;
+  final Animation<double> progress;
+  final Color color;
+  final double scale;
+  /// If this is true the image will be mirrored horizontally.
+  final bool shouldMirror;
+  final _UiPathFactory uiPathFactory;
+
+  @override
+  void paint(ui.Canvas canvas, Size size) {
+    // The RenderCustomPaint render object performs canvas.save before invoking
+    // this and canvas.restore after, so we don't need to do it here.
+    canvas.scale(scale, scale);
+    if (shouldMirror) {
+      canvas.rotate(math.pi);
+      canvas.translate(-size.width, -size.height);
+    }
+
+    final double clampedProgress = progress.value.clamp(0.0, 1.0);
+    for (final _PathFrames path in paths)
+      path.paint(canvas, color, uiPathFactory, clampedProgress);
+  }
+
+
+  @override
+  bool shouldRepaint(_AnimatedIconPainter oldDelegate) {
+    return oldDelegate.progress.value != progress.value
+        || oldDelegate.color != color
+        // We are comparing the paths list by reference, assuming the list is
+        // treated as immutable to be more efficient.
+        || oldDelegate.paths != paths
+        || oldDelegate.scale != scale
+        || oldDelegate.uiPathFactory != uiPathFactory;
+  }
+
+  @override
+  bool? hitTest(Offset position) => null;
+
+  @override
+  bool shouldRebuildSemantics(CustomPainter oldDelegate) => false;
+
+  @override
+  SemanticsBuilderCallback? get semanticsBuilder => null;
+}
+
+class _PathFrames {
+  const _PathFrames({
+    required this.commands,
+    required this.opacities,
+  });
+
+  final List<_PathCommand> commands;
+  final List<double> opacities;
+
+  void paint(ui.Canvas canvas, Color color, _UiPathFactory uiPathFactory, double progress) {
+    final double opacity = _interpolate<double?>(opacities, progress, lerpDouble)!;
+    final ui.Paint paint = ui.Paint()
+      ..style = PaintingStyle.fill
+      ..color = color.withOpacity(color.opacity * opacity);
+    final ui.Path path = uiPathFactory();
+    for (final _PathCommand command in commands)
+      command.apply(path, progress);
+    canvas.drawPath(path, paint);
+  }
+}
+
+/// Paths are being built by a set of commands e.g moveTo, lineTo, etc...
+///
+/// _PathCommand instances represents such a command, and can apply it to
+/// a given Path.
+abstract class _PathCommand {
+  const _PathCommand();
+
+  /// Applies the path command to [path].
+  ///
+  /// For example if the object is a [_PathMoveTo] command it will invoke
+  /// [Path.moveTo] on [path].
+  void apply(ui.Path path, double progress);
+}
+
+class _PathMoveTo extends _PathCommand {
+  const _PathMoveTo(this.points);
+
+  final List<Offset> points;
+
+  @override
+  void apply(Path path, double progress) {
+    final Offset offset = _interpolate<Offset?>(points, progress, Offset.lerp)!;
+    path.moveTo(offset.dx, offset.dy);
+  }
+}
+
+class _PathCubicTo extends _PathCommand {
+  const _PathCubicTo(this.controlPoints1, this.controlPoints2, this.targetPoints);
+
+  final List<Offset> controlPoints2;
+  final List<Offset> controlPoints1;
+  final List<Offset> targetPoints;
+
+  @override
+  void apply(Path path, double progress) {
+    final Offset controlPoint1 = _interpolate<Offset?>(controlPoints1, progress, Offset.lerp)!;
+    final Offset controlPoint2 = _interpolate<Offset?>(controlPoints2, progress, Offset.lerp)!;
+    final Offset targetPoint = _interpolate<Offset?>(targetPoints, progress, Offset.lerp)!;
+    path.cubicTo(
+      controlPoint1.dx, controlPoint1.dy,
+      controlPoint2.dx, controlPoint2.dy,
+      targetPoint.dx, targetPoint.dy,
+    );
+  }
+}
+
+// ignore: unused_element
+class _PathLineTo extends _PathCommand {
+  const _PathLineTo(this.points);
+
+  final List<Offset> points;
+
+  @override
+  void apply(Path path, double progress) {
+    final Offset point = _interpolate<Offset?>(points, progress, Offset.lerp)!;
+    path.lineTo(point.dx, point.dy);
+  }
+}
+
+class _PathClose extends _PathCommand {
+  const _PathClose();
+
+  @override
+  void apply(Path path, double progress) {
+    path.close();
+  }
+}
+
+/// Interpolates a value given a set of values equally spaced in time.
+///
+/// [interpolator] is the interpolation function used to interpolate between 2
+/// points of type T.
+///
+/// This is currently done with linear interpolation between every 2 consecutive
+/// points. Linear interpolation was smooth enough with the limited set of
+/// animations we have tested, so we use it for simplicity. If we find this to
+/// not be smooth enough we can try applying spline instead.
+///
+/// [progress] is expected to be between 0.0 and 1.0.
+T _interpolate<T>(List<T> values, double progress, _Interpolator<T> interpolator) {
+  assert(progress <= 1.0);
+  assert(progress >= 0.0);
+  if (values.length == 1)
+    return values[0];
+  final double targetIdx = lerpDouble(0, values.length -1, progress)!;
+  final int lowIdx = targetIdx.floor();
+  final int highIdx = targetIdx.ceil();
+  final double t = targetIdx - lowIdx;
+  return interpolator(values[lowIdx], values[highIdx], t);
+}
+
+typedef _Interpolator<T> = T Function(T a, T b, double progress);
diff --git a/lib/src/material/animated_icons/animated_icons_data.dart b/lib/src/material/animated_icons/animated_icons_data.dart
new file mode 100644
index 0000000..f6de42b
--- /dev/null
+++ b/lib/src/material/animated_icons/animated_icons_data.dart
@@ -0,0 +1,93 @@
+// Copyright 2014 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.
+
+// This file serves as the interface between the public and private APIs for
+// animated icons.
+// The AnimatedIcons class is public and is used to specify available icons,
+// while the _AnimatedIconData interface which used to deliver the icon data is
+// kept private.
+
+part of material_animated_icons;
+
+/// Identifier for the supported material design animated icons.
+///
+/// Use with [AnimatedIcon] class to show specific animated icons.
+abstract class AnimatedIcons {
+
+  /// The material design add to event icon animation.
+  static const AnimatedIconData add_event = _$add_event;
+
+  /// The material design arrow to menu icon animation.
+  static const AnimatedIconData arrow_menu = _$arrow_menu;
+
+  /// The material design close to menu icon animation.
+  static const AnimatedIconData close_menu = _$close_menu;
+
+  /// The material design ellipsis to search icon animation.
+  static const AnimatedIconData ellipsis_search = _$ellipsis_search;
+
+  /// The material design event to add icon animation.
+  static const AnimatedIconData event_add = _$event_add;
+
+  /// The material design home to menu icon animation.
+  static const AnimatedIconData home_menu = _$home_menu;
+
+  /// The material design list to view icon animation.
+  static const AnimatedIconData list_view = _$list_view;
+
+  /// The material design menu to arrow icon animation.
+  static const AnimatedIconData menu_arrow = _$menu_arrow;
+
+  /// The material design menu to close icon animation.
+  static const AnimatedIconData menu_close = _$menu_close;
+
+  /// The material design menu to home icon animation.
+  static const AnimatedIconData menu_home = _$menu_home;
+
+  /// The material design pause to play icon animation.
+  static const AnimatedIconData pause_play = _$pause_play;
+
+  /// The material design play to pause icon animation.
+  static const AnimatedIconData play_pause = _$play_pause;
+
+  /// The material design search to ellipsis icon animation.
+  static const AnimatedIconData search_ellipsis = _$search_ellipsis;
+
+  /// The material design view to list icon animation.
+  static const AnimatedIconData view_list = _$view_list;
+}
+
+/// Vector graphics data for icons used by [AnimatedIcon].
+///
+/// Instances of this class are currently opaque because we have not committed to a specific
+/// animated vector graphics format.
+///
+/// See also:
+///
+///  * [AnimatedIcons], a class that contains constants that implement this interface.
+abstract class AnimatedIconData {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const AnimatedIconData();
+
+  /// Whether this icon should be mirrored horizontally when text direction is
+  /// right-to-left.
+  ///
+  /// See also:
+  ///
+  ///  * [TextDirection], which discusses concerns regarding reading direction
+  ///    in Flutter.
+  ///  * [Directionality], a widget which determines the ambient directionality.
+  bool get matchTextDirection;
+}
+
+class _AnimatedIconData extends AnimatedIconData {
+  const _AnimatedIconData(this.size, this.paths, {this.matchTextDirection = false});
+
+  final Size size;
+  final List<_PathFrames> paths;
+
+  @override
+  final bool matchTextDirection;
+}
diff --git a/lib/src/material/animated_icons/data/add_event.g.dart b/lib/src/material/animated_icons/data/add_event.g.dart
new file mode 100644
index 0000000..27305b8
--- /dev/null
+++ b/lib/src/material/animated_icons/data/add_event.g.dart
@@ -0,0 +1,3308 @@
+// Copyright 2014 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.
+
+// AUTOGENERATED FILE DO NOT EDIT!
+// This file was generated by vitool.
+part of material_animated_icons;
+const _AnimatedIconData _$add_event = _AnimatedIconData(
+  Size(48.0, 48.0),
+  <_PathFrames>[
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.190476190476,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(18.192636718749995, 24.891542968750002),
+            Offset(18.175135177837678, 24.768938846121223),
+            Offset(18.131762833609578, 24.290016731469834),
+            Offset(18.189398131863207, 23.129814344209493),
+            Offset(19.14691116775322, 20.688226165351878),
+            Offset(22.277081692306755, 18.381965826572245),
+            Offset(25.153850173498547, 18.208885778660576),
+            Offset(26.987657743646544, 18.810016196193768),
+            Offset(28.229748817157525, 19.528141401396265),
+            Offset(29.223774647225785, 20.19509758103034),
+            Offset(30.007739613111152, 20.830494962200966),
+            Offset(30.626964119061235, 21.41670003990219),
+            Offset(31.072971936868854, 21.941254300113602),
+            Offset(31.381936148538408, 22.388826086603757),
+            Offset(31.595631995844528, 22.75496752966676),
+            Offset(31.74362009537025, 23.04237991891895),
+            Offset(31.845995957897536, 23.25665695166987),
+            Offset(31.916063614634442, 23.404284579714346),
+            Offset(31.962512649081546, 23.491750839098724),
+            Offset(31.99086595053382, 23.525019467195833),
+            Offset(32.00558109744533, 23.528032986218715),
+            Offset(32.01015625, 23.52890625),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(18.192636718749995, 24.891542968750002),
+            Offset(18.175135177837678, 24.768938846121223),
+            Offset(18.131762833609578, 24.290016731469834),
+            Offset(18.189398131863207, 23.129814344209493),
+            Offset(19.14691116775322, 20.688226165351878),
+            Offset(22.277081692306755, 18.381965826572245),
+            Offset(25.153850173498547, 18.208885778660576),
+            Offset(26.987657743646544, 18.810016196193768),
+            Offset(28.229748817157525, 19.528141401396265),
+            Offset(29.223774647225785, 20.19509758103034),
+            Offset(30.007739613111152, 20.830494962200966),
+            Offset(30.626964119061235, 21.41670003990219),
+            Offset(31.072971936868854, 21.941254300113602),
+            Offset(31.381936148538408, 22.388826086603757),
+            Offset(31.595631995844528, 22.75496752966676),
+            Offset(31.74362009537025, 23.04237991891895),
+            Offset(31.845995957897536, 23.25665695166987),
+            Offset(31.916063614634442, 23.404284579714346),
+            Offset(31.962512649081546, 23.491750839098724),
+            Offset(31.99086595053382, 23.525019467195833),
+            Offset(32.00558109744533, 23.528032986218715),
+            Offset(32.01015625, 23.52890625),
+          ],
+          <Offset>[
+            Offset(23.992636718749996, 24.891542968750002),
+            Offset(23.97384664920713, 24.89118970218503),
+            Offset(23.901054413892187, 24.886065942257186),
+            Offset(23.728661104347875, 24.849281041083934),
+            Offset(23.386158286870817, 24.64660763547994),
+            Offset(23.116959480006564, 24.121768211366486),
+            Offset(23.169707830342585, 23.691656086157646),
+            Offset(23.322360298939, 23.45580690452118),
+            Offset(23.48231873652916, 23.34351072659664),
+            Offset(23.629082531765302, 23.319033616339574),
+            Offset(23.74394094991015, 23.337067196646014),
+            Offset(23.829253355825543, 23.374789989582887),
+            Offset(23.889084353557546, 23.411693680396755),
+            Offset(23.930803165274646, 23.442370323371723),
+            Offset(23.960074268764437, 23.466992025443258),
+            Offset(23.980512394929132, 23.486331772113996),
+            Offset(23.994442088149636, 23.50119509189468),
+            Offset(24.003391328945682, 23.512265882811025),
+            Offset(24.008386840165617, 23.520115789343254),
+            Offset(24.010119497846063, 23.52522637938814),
+            Offset(24.010150449065325, 23.528032986218715),
+            Offset(24.01015625, 23.52890625),
+          ],
+          <Offset>[
+            Offset(23.992636718749996, 24.891542968750002),
+            Offset(23.97384664920713, 24.89118970218503),
+            Offset(23.901054413892187, 24.886065942257186),
+            Offset(23.728661104347875, 24.849281041083934),
+            Offset(23.386158286870817, 24.64660763547994),
+            Offset(23.116959480006564, 24.121768211366486),
+            Offset(23.169707830342585, 23.691656086157646),
+            Offset(23.322360298939, 23.45580690452118),
+            Offset(23.48231873652916, 23.34351072659664),
+            Offset(23.629082531765302, 23.319033616339574),
+            Offset(23.74394094991015, 23.337067196646014),
+            Offset(23.829253355825543, 23.374789989582887),
+            Offset(23.889084353557546, 23.411693680396755),
+            Offset(23.930803165274646, 23.442370323371723),
+            Offset(23.960074268764437, 23.466992025443258),
+            Offset(23.980512394929132, 23.486331772113996),
+            Offset(23.994442088149636, 23.50119509189468),
+            Offset(24.003391328945682, 23.512265882811025),
+            Offset(24.008386840165617, 23.520115789343254),
+            Offset(24.010119497846063, 23.52522637938814),
+            Offset(24.010150449065325, 23.528032986218715),
+            Offset(24.01015625, 23.52890625),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(23.992636718749996, 24.891542968750002),
+            Offset(23.97384664920713, 24.89118970218503),
+            Offset(23.901054413892187, 24.886065942257186),
+            Offset(23.728661104347875, 24.849281041083934),
+            Offset(23.386158286870817, 24.64660763547994),
+            Offset(23.116959480006564, 24.121768211366486),
+            Offset(23.169707830342585, 23.691656086157646),
+            Offset(23.322360298939, 23.45580690452118),
+            Offset(23.48231873652916, 23.34351072659664),
+            Offset(23.629082531765302, 23.319033616339574),
+            Offset(23.74394094991015, 23.337067196646014),
+            Offset(23.829253355825543, 23.374789989582887),
+            Offset(23.889084353557546, 23.411693680396755),
+            Offset(23.930803165274646, 23.442370323371723),
+            Offset(23.960074268764437, 23.466992025443258),
+            Offset(23.980512394929132, 23.486331772113996),
+            Offset(23.994442088149636, 23.50119509189468),
+            Offset(24.003391328945682, 23.512265882811025),
+            Offset(24.008386840165617, 23.520115789343254),
+            Offset(24.010119497846063, 23.52522637938814),
+            Offset(24.010150449065325, 23.528032986218715),
+            Offset(24.01015625, 23.52890625),
+          ],
+          <Offset>[
+            Offset(23.992636718749996, 19.091542968750005),
+            Offset(24.096097505270937, 19.09247823081558),
+            Offset(24.497103624679536, 19.11677436197458),
+            Offset(25.448127801222316, 19.310018068599263),
+            Offset(27.344539756998877, 20.407360516362342),
+            Offset(28.85676186480081, 23.281890423666677),
+            Offset(28.652478137839655, 25.675798429313613),
+            Offset(27.968151007266407, 27.121104349228723),
+            Offset(27.29768806172953, 28.090940807225007),
+            Offset(26.753018567074538, 28.913725731800056),
+            Offset(26.250513184355196, 29.600865859847012),
+            Offset(25.78734330550624, 30.17250075281858),
+            Offset(25.359523733840703, 30.595581263708063),
+            Offset(24.984347402042612, 30.89350330663548),
+            Offset(24.672098764540934, 31.102549752523352),
+            Offset(24.424464248124174, 31.249439472555114),
+            Offset(24.238980228374444, 31.352748961642575),
+            Offset(24.11137263204236, 31.424938168499786),
+            Offset(24.036751790410147, 31.47424159825918),
+            Offset(24.010326410038374, 31.505972832075898),
+            Offset(24.010150449065325, 31.523463634598716),
+            Offset(24.01015625, 31.52890625),
+          ],
+          <Offset>[
+            Offset(23.992636718749996, 19.091542968750005),
+            Offset(24.096097505270937, 19.09247823081558),
+            Offset(24.497103624679536, 19.11677436197458),
+            Offset(25.448127801222316, 19.310018068599263),
+            Offset(27.344539756998877, 20.407360516362342),
+            Offset(28.85676186480081, 23.281890423666677),
+            Offset(28.652478137839655, 25.675798429313613),
+            Offset(27.968151007266407, 27.121104349228723),
+            Offset(27.29768806172953, 28.090940807225007),
+            Offset(26.753018567074538, 28.913725731800056),
+            Offset(26.250513184355196, 29.600865859847012),
+            Offset(25.78734330550624, 30.17250075281858),
+            Offset(25.359523733840703, 30.595581263708063),
+            Offset(24.984347402042612, 30.89350330663548),
+            Offset(24.672098764540934, 31.102549752523352),
+            Offset(24.424464248124174, 31.249439472555114),
+            Offset(24.238980228374444, 31.352748961642575),
+            Offset(24.11137263204236, 31.424938168499786),
+            Offset(24.036751790410147, 31.47424159825918),
+            Offset(24.010326410038374, 31.505972832075898),
+            Offset(24.010150449065325, 31.523463634598716),
+            Offset(24.01015625, 31.52890625),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(23.992636718749996, 19.091542968750005),
+            Offset(24.096097505270937, 19.09247823081558),
+            Offset(24.497103624679536, 19.11677436197458),
+            Offset(25.448127801222316, 19.310018068599263),
+            Offset(27.344539756998877, 20.407360516362342),
+            Offset(28.85676186480081, 23.281890423666677),
+            Offset(28.652478137839655, 25.675798429313613),
+            Offset(27.968151007266407, 27.121104349228723),
+            Offset(27.29768806172953, 28.090940807225007),
+            Offset(26.753018567074538, 28.913725731800056),
+            Offset(26.250513184355196, 29.600865859847012),
+            Offset(25.78734330550624, 30.17250075281858),
+            Offset(25.359523733840703, 30.595581263708063),
+            Offset(24.984347402042612, 30.89350330663548),
+            Offset(24.672098764540934, 31.102549752523352),
+            Offset(24.424464248124174, 31.249439472555114),
+            Offset(24.238980228374444, 31.352748961642575),
+            Offset(24.11137263204236, 31.424938168499786),
+            Offset(24.036751790410147, 31.47424159825918),
+            Offset(24.010326410038374, 31.505972832075898),
+            Offset(24.010150449065325, 31.523463634598716),
+            Offset(24.01015625, 31.52890625),
+          ],
+          <Offset>[
+            Offset(18.19263671875, 19.091542968750005),
+            Offset(18.297386033901486, 18.970227374751772),
+            Offset(18.72781204439693, 18.52072515118723),
+            Offset(19.90886482873765, 17.59055137172482),
+            Offset(23.10529263788128, 16.448979046234285),
+            Offset(28.016884077101, 17.542088038872436),
+            Offset(30.636620480995617, 20.19302812181654),
+            Offset(31.63344845197395, 22.475313640901312),
+            Offset(32.0451181423579, 24.275571482024635),
+            Offset(32.34771068253502, 25.78978969649082),
+            Offset(32.5143118475562, 27.094293625401967),
+            Offset(32.58505406874193, 28.21441080313788),
+            Offset(32.54341131715201, 29.12514188342491),
+            Offset(32.43548038530638, 29.83995906986752),
+            Offset(32.307656491621024, 30.39052525674685),
+            Offset(32.18757194856529, 30.80548761936007),
+            Offset(32.09053409812234, 31.10821082141777),
+            Offset(32.024044917731125, 31.316956865403107),
+            Offset(31.990877599326076, 31.44587664801465),
+            Offset(31.991072862726128, 31.505765919883586),
+            Offset(32.00558109744533, 31.523463634598716),
+            Offset(32.01015625, 31.52890625),
+          ],
+          <Offset>[
+            Offset(18.19263671875, 19.091542968750005),
+            Offset(18.297386033901486, 18.970227374751772),
+            Offset(18.72781204439693, 18.52072515118723),
+            Offset(19.90886482873765, 17.59055137172482),
+            Offset(23.10529263788128, 16.448979046234285),
+            Offset(28.016884077101, 17.542088038872436),
+            Offset(30.636620480995617, 20.19302812181654),
+            Offset(31.63344845197395, 22.475313640901312),
+            Offset(32.0451181423579, 24.275571482024635),
+            Offset(32.34771068253502, 25.78978969649082),
+            Offset(32.5143118475562, 27.094293625401967),
+            Offset(32.58505406874193, 28.21441080313788),
+            Offset(32.54341131715201, 29.12514188342491),
+            Offset(32.43548038530638, 29.83995906986752),
+            Offset(32.307656491621024, 30.39052525674685),
+            Offset(32.18757194856529, 30.80548761936007),
+            Offset(32.09053409812234, 31.10821082141777),
+            Offset(32.024044917731125, 31.316956865403107),
+            Offset(31.990877599326076, 31.44587664801465),
+            Offset(31.991072862726128, 31.505765919883586),
+            Offset(32.00558109744533, 31.523463634598716),
+            Offset(32.01015625, 31.52890625),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(18.19263671875, 19.091542968750005),
+            Offset(18.297386033901486, 18.970227374751772),
+            Offset(18.72781204439693, 18.52072515118723),
+            Offset(19.90886482873765, 17.59055137172482),
+            Offset(23.10529263788128, 16.448979046234285),
+            Offset(28.016884077101, 17.542088038872436),
+            Offset(30.636620480995617, 20.19302812181654),
+            Offset(31.63344845197395, 22.475313640901312),
+            Offset(32.0451181423579, 24.275571482024635),
+            Offset(32.34771068253502, 25.78978969649082),
+            Offset(32.5143118475562, 27.094293625401967),
+            Offset(32.58505406874193, 28.21441080313788),
+            Offset(32.54341131715201, 29.12514188342491),
+            Offset(32.43548038530638, 29.83995906986752),
+            Offset(32.307656491621024, 30.39052525674685),
+            Offset(32.18757194856529, 30.80548761936007),
+            Offset(32.09053409812234, 31.10821082141777),
+            Offset(32.024044917731125, 31.316956865403107),
+            Offset(31.990877599326076, 31.44587664801465),
+            Offset(31.991072862726128, 31.505765919883586),
+            Offset(32.00558109744533, 31.523463634598716),
+            Offset(32.01015625, 31.52890625),
+          ],
+          <Offset>[
+            Offset(18.192636718749995, 24.891542968750002),
+            Offset(18.175135177837678, 24.768938846121223),
+            Offset(18.131762833609578, 24.290016731469834),
+            Offset(18.189398131863207, 23.129814344209493),
+            Offset(19.14691116775322, 20.688226165351878),
+            Offset(22.277081692306755, 18.381965826572245),
+            Offset(25.153850173498547, 18.208885778660576),
+            Offset(26.987657743646544, 18.810016196193768),
+            Offset(28.229748817157525, 19.528141401396265),
+            Offset(29.223774647225785, 20.19509758103034),
+            Offset(30.007739613111152, 20.830494962200966),
+            Offset(30.626964119061235, 21.41670003990219),
+            Offset(31.072971936868854, 21.941254300113602),
+            Offset(31.381936148538408, 22.388826086603757),
+            Offset(31.595631995844528, 22.75496752966676),
+            Offset(31.74362009537025, 23.04237991891895),
+            Offset(31.845995957897536, 23.25665695166987),
+            Offset(31.916063614634442, 23.404284579714346),
+            Offset(31.962512649081546, 23.491750839098724),
+            Offset(31.99086595053382, 23.525019467195833),
+            Offset(32.00558109744533, 23.528032986218715),
+            Offset(32.01015625, 23.52890625),
+          ],
+          <Offset>[
+            Offset(18.192636718749995, 24.891542968750002),
+            Offset(18.175135177837678, 24.768938846121223),
+            Offset(18.131762833609578, 24.290016731469834),
+            Offset(18.189398131863207, 23.129814344209493),
+            Offset(19.14691116775322, 20.688226165351878),
+            Offset(22.277081692306755, 18.381965826572245),
+            Offset(25.153850173498547, 18.208885778660576),
+            Offset(26.987657743646544, 18.810016196193768),
+            Offset(28.229748817157525, 19.528141401396265),
+            Offset(29.223774647225785, 20.19509758103034),
+            Offset(30.007739613111152, 20.830494962200966),
+            Offset(30.626964119061235, 21.41670003990219),
+            Offset(31.072971936868854, 21.941254300113602),
+            Offset(31.381936148538408, 22.388826086603757),
+            Offset(31.595631995844528, 22.75496752966676),
+            Offset(31.74362009537025, 23.04237991891895),
+            Offset(31.845995957897536, 23.25665695166987),
+            Offset(31.916063614634442, 23.404284579714346),
+            Offset(31.962512649081546, 23.491750839098724),
+            Offset(31.99086595053382, 23.525019467195833),
+            Offset(32.00558109744533, 23.528032986218715),
+            Offset(32.01015625, 23.52890625),
+          ],
+        ),
+        _PathClose(
+        ),
+        _PathMoveTo(
+          <Offset>[
+            Offset(19.359999999999996, 37.6),
+            Offset(19.07437364316861, 37.499177937670424),
+            Offset(17.986934103582882, 37.051154681687954),
+            Offset(15.536736677617021, 35.61304326763691),
+            Offset(11.326880236819443, 30.7735984135526),
+            Offset(9.869566263871294, 21.377480537283333),
+            Offset(12.74113193535829, 14.964919748422403),
+            Offset(16.0704913221344, 11.713990403514046),
+            Offset(18.914329388866914, 9.893901895554396),
+            Offset(21.25283585091745, 8.565246523821894),
+            Offset(23.25484407901928, 7.610299138856277),
+            Offset(24.968395165544187, 6.916249550797959),
+            Offset(26.405174995011652, 6.49649745011436),
+            Offset(27.573815336148815, 6.274596001300022),
+            Offset(28.49870054201158, 6.167904420613963),
+            Offset(29.208394245687987, 6.121885514098405),
+            Offset(29.729906624130507, 6.1022310660712105),
+            Offset(30.08688450541718, 6.08845659062094),
+            Offset(30.299438656814726, 6.069069138689281),
+            Offset(30.384131484923635, 6.038341565542824),
+            Offset(30.396344518703998, 6.009138703240001),
+            Offset(30.4, 6.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(19.359999999999996, 37.6),
+            Offset(19.07437364316861, 37.499177937670424),
+            Offset(17.986934103582882, 37.051154681687954),
+            Offset(15.536736677617021, 35.61304326763691),
+            Offset(11.326880236819443, 30.7735984135526),
+            Offset(9.869566263871294, 21.377480537283333),
+            Offset(12.74113193535829, 14.964919748422403),
+            Offset(16.0704913221344, 11.713990403514046),
+            Offset(18.914329388866914, 9.893901895554396),
+            Offset(21.25283585091745, 8.565246523821894),
+            Offset(23.25484407901928, 7.610299138856277),
+            Offset(24.968395165544187, 6.916249550797959),
+            Offset(26.405174995011652, 6.49649745011436),
+            Offset(27.573815336148815, 6.274596001300022),
+            Offset(28.49870054201158, 6.167904420613963),
+            Offset(29.208394245687987, 6.121885514098405),
+            Offset(29.729906624130507, 6.1022310660712105),
+            Offset(30.08688450541718, 6.08845659062094),
+            Offset(30.299438656814726, 6.069069138689281),
+            Offset(30.384131484923635, 6.038341565542824),
+            Offset(30.396344518703998, 6.009138703240001),
+            Offset(30.4, 6.0),
+          ],
+          <Offset>[
+            Offset(19.359999999999996, 35.28),
+            Offset(19.123273985594132, 35.179693349122644),
+            Offset(18.225353787897824, 34.74343804957491),
+            Offset(16.224523356366795, 33.397338078643045),
+            Offset(12.910232824870667, 29.077899565905565),
+            Offset(12.165487217788991, 21.04152942220341),
+            Offset(14.934240058357119, 15.758576685684787),
+            Offset(17.928807605465366, 13.180109381397063),
+            Offset(20.440477118947065, 11.792873927805745),
+            Offset(22.502410265041142, 10.803123370006087),
+            Offset(24.2574729727973, 10.115818604136678),
+            Offset(25.751631145416468, 9.635333856092236),
+            Offset(26.993350747124914, 9.370052483438883),
+            Offset(27.995233030856, 9.255049194605526),
+            Offset(28.78351034032218, 9.222127511445999),
+            Offset(29.385974986966005, 9.227128594274852),
+            Offset(29.82772188022043, 9.24285261397037),
+            Offset(30.130077026655854, 9.253525504896444),
+            Offset(30.310784636912537, 9.250719462255653),
+            Offset(30.384214249800557, 9.230640146617926),
+            Offset(30.396344518703998, 9.207310962592),
+            Offset(30.4, 9.2),
+          ],
+          <Offset>[
+            Offset(19.359999999999996, 35.28),
+            Offset(19.123273985594132, 35.179693349122644),
+            Offset(18.225353787897824, 34.74343804957491),
+            Offset(16.224523356366795, 33.397338078643045),
+            Offset(12.910232824870667, 29.077899565905565),
+            Offset(12.165487217788991, 21.04152942220341),
+            Offset(14.934240058357119, 15.758576685684787),
+            Offset(17.928807605465366, 13.180109381397063),
+            Offset(20.440477118947065, 11.792873927805745),
+            Offset(22.502410265041142, 10.803123370006087),
+            Offset(24.2574729727973, 10.115818604136678),
+            Offset(25.751631145416468, 9.635333856092236),
+            Offset(26.993350747124914, 9.370052483438883),
+            Offset(27.995233030856, 9.255049194605526),
+            Offset(28.78351034032218, 9.222127511445999),
+            Offset(29.385974986966005, 9.227128594274852),
+            Offset(29.82772188022043, 9.24285261397037),
+            Offset(30.130077026655854, 9.253525504896444),
+            Offset(30.310784636912537, 9.250719462255653),
+            Offset(30.384214249800557, 9.230640146617926),
+            Offset(30.396344518703998, 9.207310962592),
+            Offset(30.4, 9.2),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(19.359999999999996, 35.28),
+            Offset(19.123273985594132, 35.179693349122644),
+            Offset(18.225353787897824, 34.74343804957491),
+            Offset(16.224523356366795, 33.397338078643045),
+            Offset(12.910232824870667, 29.077899565905565),
+            Offset(12.165487217788991, 21.04152942220341),
+            Offset(14.934240058357119, 15.758576685684787),
+            Offset(17.928807605465366, 13.180109381397063),
+            Offset(20.440477118947065, 11.792873927805745),
+            Offset(22.502410265041142, 10.803123370006087),
+            Offset(24.2574729727973, 10.115818604136678),
+            Offset(25.751631145416468, 9.635333856092236),
+            Offset(26.993350747124914, 9.370052483438883),
+            Offset(27.995233030856, 9.255049194605526),
+            Offset(28.78351034032218, 9.222127511445999),
+            Offset(29.385974986966005, 9.227128594274852),
+            Offset(29.82772188022043, 9.24285261397037),
+            Offset(30.130077026655854, 9.253525504896444),
+            Offset(30.310784636912537, 9.250719462255653),
+            Offset(30.384214249800557, 9.230640146617926),
+            Offset(30.396344518703998, 9.207310962592),
+            Offset(30.4, 9.2),
+          ],
+          <Offset>[
+            Offset(28.639999999999993, 35.28),
+            Offset(28.401212339785253, 35.37529471882475),
+            Offset(27.45622031634999, 35.69711678683468),
+            Offset(25.08734411234227, 36.14848479364215),
+            Offset(19.693028215458817, 35.41130991811046),
+            Offset(13.509291678108683, 30.2252132378742),
+            Offset(11.759612309307578, 24.5310091776801),
+            Offset(12.064331693933298, 20.613374514720917),
+            Offset(12.844588989941675, 17.897464848126344),
+            Offset(13.550902880304374, 15.801421026500861),
+            Offset(14.235395111675697, 14.126334179248751),
+            Offset(14.875293924239358, 12.768277775581351),
+            Offset(15.499130613826821, 11.722755491891931),
+            Offset(16.07342025763398, 10.94071997343427),
+            Offset(16.566617976994035, 10.361366704688399),
+            Offset(16.965002666260215, 9.937451559386922),
+            Offset(17.26523568862379, 9.63411363833006),
+            Offset(17.469801369553835, 9.426295589851133),
+            Offset(17.584183342647055, 9.296103382646901),
+            Offset(17.61501992550015, 9.23097120612562),
+            Offset(17.603655481296002, 9.207310962592),
+            Offset(17.6, 9.2),
+          ],
+          <Offset>[
+            Offset(28.639999999999993, 35.28),
+            Offset(28.401212339785253, 35.37529471882475),
+            Offset(27.45622031634999, 35.69711678683468),
+            Offset(25.08734411234227, 36.14848479364215),
+            Offset(19.693028215458817, 35.41130991811046),
+            Offset(13.509291678108683, 30.2252132378742),
+            Offset(11.759612309307578, 24.5310091776801),
+            Offset(12.064331693933298, 20.613374514720917),
+            Offset(12.844588989941675, 17.897464848126344),
+            Offset(13.550902880304374, 15.801421026500861),
+            Offset(14.235395111675697, 14.126334179248751),
+            Offset(14.875293924239358, 12.768277775581351),
+            Offset(15.499130613826821, 11.722755491891931),
+            Offset(16.07342025763398, 10.94071997343427),
+            Offset(16.566617976994035, 10.361366704688399),
+            Offset(16.965002666260215, 9.937451559386922),
+            Offset(17.26523568862379, 9.63411363833006),
+            Offset(17.469801369553835, 9.426295589851133),
+            Offset(17.584183342647055, 9.296103382646901),
+            Offset(17.61501992550015, 9.23097120612562),
+            Offset(17.603655481296002, 9.207310962592),
+            Offset(17.6, 9.2),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(28.639999999999993, 35.28),
+            Offset(28.401212339785253, 35.37529471882475),
+            Offset(27.45622031634999, 35.69711678683468),
+            Offset(25.08734411234227, 36.14848479364215),
+            Offset(19.693028215458817, 35.41130991811046),
+            Offset(13.509291678108683, 30.2252132378742),
+            Offset(11.759612309307578, 24.5310091776801),
+            Offset(12.064331693933298, 20.613374514720917),
+            Offset(12.844588989941675, 17.897464848126344),
+            Offset(13.550902880304374, 15.801421026500861),
+            Offset(14.235395111675697, 14.126334179248751),
+            Offset(14.875293924239358, 12.768277775581351),
+            Offset(15.499130613826821, 11.722755491891931),
+            Offset(16.07342025763398, 10.94071997343427),
+            Offset(16.566617976994035, 10.361366704688399),
+            Offset(16.965002666260215, 9.937451559386922),
+            Offset(17.26523568862379, 9.63411363833006),
+            Offset(17.469801369553835, 9.426295589851133),
+            Offset(17.584183342647055, 9.296103382646901),
+            Offset(17.61501992550015, 9.23097120612562),
+            Offset(17.603655481296002, 9.207310962592),
+            Offset(17.6, 9.2),
+          ],
+          <Offset>[
+            Offset(28.639999999999993, 37.6),
+            Offset(28.35231199735973, 37.69477930737253),
+            Offset(27.21780063203505, 38.00483341894772),
+            Offset(24.39955743359249, 38.36418998263601),
+            Offset(18.109675627407594, 37.1070087657575),
+            Offset(11.213370724190986, 30.561164352954123),
+            Offset(9.566504186308748, 23.737352240417717),
+            Offset(10.206015410602333, 19.1472555368379),
+            Offset(11.318441259861526, 15.998492815874997),
+            Offset(12.301328466180678, 13.563544180316669),
+            Offset(13.232766217897677, 11.62081471396835),
+            Offset(14.092057944367081, 10.049193470287074),
+            Offset(14.91095486171356, 8.849200458567408),
+            Offset(15.652002562926794, 7.960266780128766),
+            Offset(16.281808178683434, 7.307143613856363),
+            Offset(16.787421924982198, 6.832208479210475),
+            Offset(17.167420432533866, 6.4934920904309),
+            Offset(17.426608848315166, 6.261226675575628),
+            Offset(17.572837362549244, 6.11445305908053),
+            Offset(17.614937160623228, 6.038672625050518),
+            Offset(17.603655481296002, 6.009138703240001),
+            Offset(17.6, 6.0),
+          ],
+          <Offset>[
+            Offset(28.639999999999993, 37.6),
+            Offset(28.35231199735973, 37.69477930737253),
+            Offset(27.21780063203505, 38.00483341894772),
+            Offset(24.39955743359249, 38.36418998263601),
+            Offset(18.109675627407594, 37.1070087657575),
+            Offset(11.213370724190986, 30.561164352954123),
+            Offset(9.566504186308748, 23.737352240417717),
+            Offset(10.206015410602333, 19.1472555368379),
+            Offset(11.318441259861526, 15.998492815874997),
+            Offset(12.301328466180678, 13.563544180316669),
+            Offset(13.232766217897677, 11.62081471396835),
+            Offset(14.092057944367081, 10.049193470287074),
+            Offset(14.91095486171356, 8.849200458567408),
+            Offset(15.652002562926794, 7.960266780128766),
+            Offset(16.281808178683434, 7.307143613856363),
+            Offset(16.787421924982198, 6.832208479210475),
+            Offset(17.167420432533866, 6.4934920904309),
+            Offset(17.426608848315166, 6.261226675575628),
+            Offset(17.572837362549244, 6.11445305908053),
+            Offset(17.614937160623228, 6.038672625050518),
+            Offset(17.603655481296002, 6.009138703240001),
+            Offset(17.6, 6.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(28.639999999999993, 37.6),
+            Offset(28.35231199735973, 37.69477930737253),
+            Offset(27.21780063203505, 38.00483341894772),
+            Offset(24.39955743359249, 38.36418998263601),
+            Offset(18.109675627407594, 37.1070087657575),
+            Offset(11.213370724190986, 30.561164352954123),
+            Offset(9.566504186308748, 23.737352240417717),
+            Offset(10.206015410602333, 19.1472555368379),
+            Offset(11.318441259861526, 15.998492815874997),
+            Offset(12.301328466180678, 13.563544180316669),
+            Offset(13.232766217897677, 11.62081471396835),
+            Offset(14.092057944367081, 10.049193470287074),
+            Offset(14.91095486171356, 8.849200458567408),
+            Offset(15.652002562926794, 7.960266780128766),
+            Offset(16.281808178683434, 7.307143613856363),
+            Offset(16.787421924982198, 6.832208479210475),
+            Offset(17.167420432533866, 6.4934920904309),
+            Offset(17.426608848315166, 6.261226675575628),
+            Offset(17.572837362549244, 6.11445305908053),
+            Offset(17.614937160623228, 6.038672625050518),
+            Offset(17.603655481296002, 6.009138703240001),
+            Offset(17.6, 6.0),
+          ],
+          <Offset>[
+            Offset(30.959999999999994, 37.6),
+            Offset(30.67179658590751, 37.74367964979805),
+            Offset(29.525517264148096, 38.24325310326266),
+            Offset(26.61526262258636, 39.051976661385794),
+            Offset(19.80537447505463, 38.69036135380872),
+            Offset(11.54932183927091, 32.857085306871824),
+            Offset(8.772847249046361, 25.930460363416543),
+            Offset(8.739896432719314, 21.005571820168864),
+            Offset(9.419469227610179, 17.524640545955144),
+            Offset(10.063451619996487, 14.813118594440363),
+            Offset(10.727246752617276, 12.62344360774637),
+            Offset(11.372973639072804, 10.832429450159353),
+            Offset(12.037399828389036, 9.43737621068067),
+            Offset(12.671549369621289, 8.381684474835954),
+            Offset(13.227585087851399, 7.591953412166962),
+            Offset(13.68217884480575, 7.009789220488495),
+            Offset(14.026798884634708, 6.591307346520823),
+            Offset(14.26153993403966, 6.304419196814299),
+            Offset(14.391187038982872, 6.125799039178341),
+            Offset(14.422638579548124, 6.0387553899274415),
+            Offset(14.405483221944, 6.009138703240001),
+            Offset(14.399999999999999, 6.0),
+          ],
+          <Offset>[
+            Offset(30.959999999999994, 37.6),
+            Offset(30.67179658590751, 37.74367964979805),
+            Offset(29.525517264148096, 38.24325310326266),
+            Offset(26.61526262258636, 39.051976661385794),
+            Offset(19.80537447505463, 38.69036135380872),
+            Offset(11.54932183927091, 32.857085306871824),
+            Offset(8.772847249046361, 25.930460363416543),
+            Offset(8.739896432719314, 21.005571820168864),
+            Offset(9.419469227610179, 17.524640545955144),
+            Offset(10.063451619996487, 14.813118594440363),
+            Offset(10.727246752617276, 12.62344360774637),
+            Offset(11.372973639072804, 10.832429450159353),
+            Offset(12.037399828389036, 9.43737621068067),
+            Offset(12.671549369621289, 8.381684474835954),
+            Offset(13.227585087851399, 7.591953412166962),
+            Offset(13.68217884480575, 7.009789220488495),
+            Offset(14.026798884634708, 6.591307346520823),
+            Offset(14.26153993403966, 6.304419196814299),
+            Offset(14.391187038982872, 6.125799039178341),
+            Offset(14.422638579548124, 6.0387553899274415),
+            Offset(14.405483221944, 6.009138703240001),
+            Offset(14.399999999999999, 6.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(30.959999999999994, 37.6),
+            Offset(30.67179658590751, 37.74367964979805),
+            Offset(29.525517264148096, 38.24325310326266),
+            Offset(26.61526262258636, 39.051976661385794),
+            Offset(19.80537447505463, 38.69036135380872),
+            Offset(11.54932183927091, 32.857085306871824),
+            Offset(8.772847249046361, 25.930460363416543),
+            Offset(8.739896432719314, 21.005571820168864),
+            Offset(9.419469227610179, 17.524640545955144),
+            Offset(10.063451619996487, 14.813118594440363),
+            Offset(10.727246752617276, 12.62344360774637),
+            Offset(11.372973639072804, 10.832429450159353),
+            Offset(12.037399828389036, 9.43737621068067),
+            Offset(12.671549369621289, 8.381684474835954),
+            Offset(13.227585087851399, 7.591953412166962),
+            Offset(13.68217884480575, 7.009789220488495),
+            Offset(14.026798884634708, 6.591307346520823),
+            Offset(14.26153993403966, 6.304419196814299),
+            Offset(14.391187038982872, 6.125799039178341),
+            Offset(14.422638579548124, 6.0387553899274415),
+            Offset(14.405483221944, 6.009138703240001),
+            Offset(14.399999999999999, 6.0),
+          ],
+          <Offset>[
+            Offset(30.959999999999994, 35.28),
+            Offset(30.720696928333034, 35.424195061250266),
+            Offset(29.763936948463037, 35.93553647114962),
+            Offset(27.303049301336138, 36.83627147239193),
+            Offset(21.388727063105854, 36.99466250616168),
+            Offset(13.845242793188607, 32.521134191791894),
+            Offset(10.96595537204519, 26.72411730067893),
+            Offset(10.598212716050279, 22.47169079805188),
+            Offset(10.945616957690326, 19.423612578206495),
+            Offset(11.313026034120181, 17.050995440624554),
+            Offset(11.729875646395296, 15.128963073026771),
+            Offset(12.156209618945082, 13.55151375545363),
+            Offset(12.625575580502298, 12.310931244005193),
+            Offset(13.092967064328475, 11.362137668141457),
+            Offset(13.512394886161998, 10.646176502998998),
+            Offset(13.859759586083767, 10.115032300664941),
+            Offset(14.124614140724631, 9.731928894419983),
+            Offset(14.304732455278332, 9.469488111089804),
+            Offset(14.402533019080684, 9.307449362744713),
+            Offset(14.422721344425048, 9.231053971002543),
+            Offset(14.405483221944, 9.207310962592),
+            Offset(14.399999999999999, 9.2),
+          ],
+          <Offset>[
+            Offset(30.959999999999994, 35.28),
+            Offset(30.720696928333034, 35.424195061250266),
+            Offset(29.763936948463037, 35.93553647114962),
+            Offset(27.303049301336138, 36.83627147239193),
+            Offset(21.388727063105854, 36.99466250616168),
+            Offset(13.845242793188607, 32.521134191791894),
+            Offset(10.96595537204519, 26.72411730067893),
+            Offset(10.598212716050279, 22.47169079805188),
+            Offset(10.945616957690326, 19.423612578206495),
+            Offset(11.313026034120181, 17.050995440624554),
+            Offset(11.729875646395296, 15.128963073026771),
+            Offset(12.156209618945082, 13.55151375545363),
+            Offset(12.625575580502298, 12.310931244005193),
+            Offset(13.092967064328475, 11.362137668141457),
+            Offset(13.512394886161998, 10.646176502998998),
+            Offset(13.859759586083767, 10.115032300664941),
+            Offset(14.124614140724631, 9.731928894419983),
+            Offset(14.304732455278332, 9.469488111089804),
+            Offset(14.402533019080684, 9.307449362744713),
+            Offset(14.422721344425048, 9.231053971002543),
+            Offset(14.405483221944, 9.207310962592),
+            Offset(14.399999999999999, 9.2),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(30.959999999999994, 35.28),
+            Offset(30.720696928333034, 35.424195061250266),
+            Offset(29.763936948463037, 35.93553647114962),
+            Offset(27.303049301336138, 36.83627147239193),
+            Offset(21.388727063105854, 36.99466250616168),
+            Offset(13.845242793188607, 32.521134191791894),
+            Offset(10.96595537204519, 26.72411730067893),
+            Offset(10.598212716050279, 22.47169079805188),
+            Offset(10.945616957690326, 19.423612578206495),
+            Offset(11.313026034120181, 17.050995440624554),
+            Offset(11.729875646395296, 15.128963073026771),
+            Offset(12.156209618945082, 13.55151375545363),
+            Offset(12.625575580502298, 12.310931244005193),
+            Offset(13.092967064328475, 11.362137668141457),
+            Offset(13.512394886161998, 10.646176502998998),
+            Offset(13.859759586083767, 10.115032300664941),
+            Offset(14.124614140724631, 9.731928894419983),
+            Offset(14.304732455278332, 9.469488111089804),
+            Offset(14.402533019080684, 9.307449362744713),
+            Offset(14.422721344425048, 9.231053971002543),
+            Offset(14.405483221944, 9.207310962592),
+            Offset(14.399999999999999, 9.2),
+          ],
+          <Offset>[
+            Offset(32.11999999999999, 35.28),
+            Offset(31.880439222606924, 35.44864523246303),
+            Offset(30.917795264519558, 36.05474631330709),
+            Offset(28.41090189583307, 37.180164811766815),
+            Offset(22.236576486929373, 37.786338800187295),
+            Offset(14.013218350728568, 33.669094668750745),
+            Offset(10.569126903413999, 27.820671362178345),
+            Offset(9.86515322710877, 23.400848939717363),
+            Offset(9.996130941564655, 20.18668644324657),
+            Offset(10.194087611028085, 17.675782647686404),
+            Offset(10.477115913755096, 15.63027751991578),
+            Offset(10.796667466297944, 13.94313174538977),
+            Offset(11.188798063840036, 12.605019120061824),
+            Offset(11.602740467675723, 11.57284651549505),
+            Offset(11.98528334074598, 10.788581402154296),
+            Offset(12.307138045995544, 10.20382267130395),
+            Offset(12.55430336677505, 9.780836522464943),
+            Offset(12.72219799814058, 9.49108437170914),
+            Offset(12.811707857297497, 9.313122352793618),
+            Offset(12.826572053887496, 9.231095353441006),
+            Offset(12.806397092268, 9.207310962592),
+            Offset(12.799999999999999, 9.2),
+          ],
+          <Offset>[
+            Offset(32.11999999999999, 35.28),
+            Offset(31.880439222606924, 35.44864523246303),
+            Offset(30.917795264519558, 36.05474631330709),
+            Offset(28.41090189583307, 37.180164811766815),
+            Offset(22.236576486929373, 37.786338800187295),
+            Offset(14.013218350728568, 33.669094668750745),
+            Offset(10.569126903413999, 27.820671362178345),
+            Offset(9.86515322710877, 23.400848939717363),
+            Offset(9.996130941564655, 20.18668644324657),
+            Offset(10.194087611028085, 17.675782647686404),
+            Offset(10.477115913755096, 15.63027751991578),
+            Offset(10.796667466297944, 13.94313174538977),
+            Offset(11.188798063840036, 12.605019120061824),
+            Offset(11.602740467675723, 11.57284651549505),
+            Offset(11.98528334074598, 10.788581402154296),
+            Offset(12.307138045995544, 10.20382267130395),
+            Offset(12.55430336677505, 9.780836522464943),
+            Offset(12.72219799814058, 9.49108437170914),
+            Offset(12.811707857297497, 9.313122352793618),
+            Offset(12.826572053887496, 9.231095353441006),
+            Offset(12.806397092268, 9.207310962592),
+            Offset(12.799999999999999, 9.2),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(33.40180389404459, 35.28),
+            Offset(33.161958350959075, 35.47566275373077),
+            Offset(32.19281257718938, 36.186473589070786),
+            Offset(29.635082731741335, 37.56016810620365),
+            Offset(23.17345294642977, 38.66114376269145),
+            Offset(14.198831905693254, 34.93759484941898),
+            Offset(10.130630113449154, 29.032367281195906),
+            Offset(9.055120030995347, 24.427571805381206),
+            Offset(8.946945706382945, 21.029885625705216),
+            Offset(8.957656897307762, 18.366174608859787),
+            Offset(9.09281220375468, 16.18423166660815),
+            Offset(9.294368823728218, 14.37587093890534),
+            Offset(9.601154084759504, 12.929987210338286),
+            Offset(9.95603507578061, 11.805680499156674),
+            Offset(10.297819956647084, 10.945939293764894),
+            Offset(10.591486032148467, 10.301936328923558),
+            Offset(10.819104690129567, 9.834879615634355),
+            Offset(10.973492110538054, 9.51494831219075),
+            Offset(11.053840713230413, 9.319391025841519),
+            Offset(11.062821729674083, 9.231141081174425),
+            Offset(11.039401550947822, 9.207310962592),
+            Offset(11.031994628903998, 9.2),
+          ],
+          <Offset>[
+            Offset(34.428397521973196, 34.24180389404461),
+            Offset(34.21020673189347, 34.4593355607496),
+            Offset(33.32066325711653, 35.25927432509759),
+            Offset(30.923309576566957, 36.87298746940196),
+            Offset(24.632342654683864, 38.60295054594897),
+            Offset(15.374910124648133, 35.80319600547601),
+            Offset(10.760851302536071, 30.35797174936804),
+            Offset(9.23795982776596, 25.90595743850577),
+            Offset(8.789604345416578, 22.554988601644673),
+            Offset(8.52658499233495, 19.920553987514175),
+            Offset(8.43280146906967, 17.749107953581934),
+            Offset(8.441678273181578, 15.93923637150114),
+            Offset(8.59282153680617, 14.476164419419169),
+            Offset(8.82577643484907, 13.325904473016532),
+            Offset(9.073786534329603, 12.438726553996867),
+            Offset(9.296891581635101, 11.770106385528296),
+            Offset(9.473160444080882, 11.283585469050152),
+            Offset(9.592286389851099, 10.950423910880021),
+            Offset(9.651046490767305, 10.748194770371416),
+            Offset(9.650275412603275, 10.659725961266831),
+            Offset(9.624219110228523, 10.638487680623824),
+            Offset(9.616003417967999, 10.631994628904),
+          ],
+          <Offset>[
+            Offset(34.428397521973196, 32.96),
+            Offset(34.23722425316121, 33.17781643239746),
+            Offset(33.45239053288022, 33.984257012427776),
+            Offset(31.303312871003797, 35.648806633493706),
+            Offset(25.507147617188025, 37.666074086448575),
+            Offset(16.643410305316365, 35.61758245051132),
+            Offset(11.972547221553633, 30.79646853933288),
+            Offset(10.264682693429805, 26.71599063461919),
+            Offset(9.632803527875225, 23.60417383682638),
+            Offset(9.216976953508334, 21.156984701234496),
+            Offset(8.98675561576204, 19.133411663582347),
+            Offset(8.87441746669715, 17.441535014070865),
+            Offset(8.917789627082632, 16.063808398499702),
+            Offset(9.058610418510696, 14.972609864911643),
+            Offset(9.2311444259402, 14.126189938095761),
+            Offset(9.395005239254708, 13.48575839937537),
+            Offset(9.527203537250292, 13.018784145695633),
+            Offset(9.616150330332708, 12.699129798482545),
+            Offset(9.657315163815205, 12.5060619144385),
+            Offset(9.650321140336693, 12.423476285480243),
+            Offset(9.624219110228523, 12.405483221944),
+            Offset(9.616003417967999, 12.399999999999999),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(34.428397521973196, 32.96),
+            Offset(34.23722425316121, 33.17781643239746),
+            Offset(33.45239053288022, 33.984257012427776),
+            Offset(31.303312871003797, 35.648806633493706),
+            Offset(25.507147617188025, 37.666074086448575),
+            Offset(16.643410305316365, 35.61758245051132),
+            Offset(11.972547221553633, 30.79646853933288),
+            Offset(10.264682693429805, 26.71599063461919),
+            Offset(9.632803527875225, 23.60417383682638),
+            Offset(9.216976953508334, 21.156984701234496),
+            Offset(8.98675561576204, 19.133411663582347),
+            Offset(8.87441746669715, 17.441535014070865),
+            Offset(8.917789627082632, 16.063808398499702),
+            Offset(9.058610418510696, 14.972609864911643),
+            Offset(9.2311444259402, 14.126189938095761),
+            Offset(9.395005239254708, 13.48575839937537),
+            Offset(9.527203537250292, 13.018784145695633),
+            Offset(9.616150330332708, 12.699129798482545),
+            Offset(9.657315163815205, 12.5060619144385),
+            Offset(9.650321140336693, 12.423476285480243),
+            Offset(9.624219110228523, 12.405483221944),
+            Offset(9.616003417967999, 12.399999999999999),
+          ],
+          <Offset>[
+            Offset(34.440000000000516, 16.720000000000006),
+            Offset(34.591126550559416, 16.94166886650632),
+            Offset(35.132869371152644, 17.831432940717747),
+            Offset(36.128900514825645, 20.142309978565798),
+            Offset(36.59909603898658, 25.80410060706257),
+            Offset(32.71653709715015, 33.277406702029566),
+            Offset(27.320334950142062, 36.36303498327675),
+            Offset(23.26556451587313, 36.98811704611288),
+            Offset(20.306340749954074, 36.90461043133784),
+            Offset(17.95280607783294, 36.82837183128392),
+            Offset(15.99262759869828, 36.67706213593723),
+            Offset(14.343470999981285, 36.47904216761644),
+            Offset(13.020649047420964, 36.181635138771405),
+            Offset(11.99362883202704, 35.83788975664637),
+            Offset(11.209538636398053, 35.50717592712125),
+            Offset(10.622540896042542, 35.22334805399359),
+            Offset(10.196203867594564, 35.003624161748164),
+            Offset(9.902669253773741, 34.854828207151854),
+            Offset(9.720825374512906, 34.7776709214224),
+            Offset(9.634935591827066, 34.76956676691874),
+            Offset(9.608224832915282, 34.792689037407996),
+            Offset(9.59999999999928, 34.8),
+          ],
+          <Offset>[
+            Offset(34.440000000000516, 16.720000000000006),
+            Offset(34.591126550559416, 16.94166886650632),
+            Offset(35.132869371152644, 17.831432940717747),
+            Offset(36.128900514825645, 20.142309978565798),
+            Offset(36.59909603898658, 25.80410060706257),
+            Offset(32.71653709715015, 33.277406702029566),
+            Offset(27.320334950142062, 36.36303498327675),
+            Offset(23.26556451587313, 36.98811704611288),
+            Offset(20.306340749954074, 36.90461043133784),
+            Offset(17.95280607783294, 36.82837183128392),
+            Offset(15.99262759869828, 36.67706213593723),
+            Offset(14.343470999981285, 36.47904216761644),
+            Offset(13.020649047420964, 36.181635138771405),
+            Offset(11.99362883202704, 35.83788975664637),
+            Offset(11.209538636398053, 35.50717592712125),
+            Offset(10.622540896042542, 35.22334805399359),
+            Offset(10.196203867594564, 35.003624161748164),
+            Offset(9.902669253773741, 34.854828207151854),
+            Offset(9.720825374512906, 34.7776709214224),
+            Offset(9.634935591827066, 34.76956676691874),
+            Offset(9.608224832915282, 34.792689037407996),
+            Offset(9.59999999999928, 34.8),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(34.440000000000516, 15.438196105955406),
+            Offset(34.61814407182715, 15.660149738154173),
+            Offset(35.264596646916345, 16.556415628047922),
+            Offset(36.50890380926249, 18.918129142657538),
+            Offset(37.473901001490745, 24.867224147562176),
+            Offset(33.98503727781838, 33.091793147064884),
+            Offset(28.532030869159623, 36.80153177324159),
+            Offset(24.292287381536976, 37.798150242226306),
+            Offset(21.149539932412726, 37.95379566651955),
+            Offset(18.643198039006325, 38.06480254500424),
+            Offset(16.546581745390647, 38.061365845937644),
+            Offset(14.776210193496857, 37.98134081018617),
+            Offset(13.345617137697426, 37.76927911785194),
+            Offset(12.226462815688665, 37.48459514854148),
+            Offset(11.366896528008649, 37.19463931122015),
+            Offset(10.72065455366215, 36.93900006784067),
+            Offset(10.250246960763974, 36.73882283839365),
+            Offset(9.92653319425535, 36.60353409475438),
+            Offset(9.727094047560806, 36.53553806548948),
+            Offset(9.634981319560485, 36.53331709113216),
+            Offset(9.608224832915282, 36.55968457872818),
+            Offset(9.59999999999928, 36.568005371096),
+          ],
+          <Offset>[
+            Offset(33.401803894045116, 14.400000000000006),
+            Offset(33.602061432789306, 14.600301456800754),
+            Offset(34.33858973602437, 15.417023900053458),
+            Offset(35.825162840489824, 17.618821405258995),
+            Offset(37.42362623889116, 23.39985413386847),
+            Offset(34.862120490952606, 31.914034813700184),
+            Offset(29.868603220438434, 36.17527971655787),
+            Offset(25.77996658097369, 37.62264260632878),
+            Offset(22.682275277103862, 38.12063391596769),
+            Offset(20.203826624420508, 38.5070662245178),
+            Offset(18.116472247756285, 38.73390685413197),
+            Offset(16.343492642578116, 38.847629686554015),
+            Offset(14.894735853778217, 38.79198251025913),
+            Offset(13.748794328144617, 38.62975923890631),
+            Offset(12.861108141441791, 38.43394711125329),
+            Offset(12.18971270364993, 38.249124050511625),
+            Offset(11.699441994938162, 38.10047354672682),
+            Offset(11.362224801685391, 38.0005685406703),
+            Offset(11.155954534110002, 37.954243937938855),
+            Offset(11.063566613565678, 37.96182831085034),
+            Offset(11.039401550947105, 37.99086129676),
+            Offset(11.031994628903279, 38.0),
+          ],
+          <Offset>[
+            Offset(32.120000000000516, 14.400000000000006),
+            Offset(32.32054230443716, 14.573283935533016),
+            Offset(33.06357242335454, 15.285296624289762),
+            Offset(34.60098200458155, 17.238818110822155),
+            Offset(36.48674977939076, 22.52504917136431),
+            Offset(34.67650693598792, 30.64553463303195),
+            Offset(30.30710001040328, 34.96358379754031),
+            Offset(26.58999977708711, 36.59591974066494),
+            Offset(23.731460512285572, 37.27743473350904),
+            Offset(21.440257338140828, 37.81667426334442),
+            Offset(19.500775957756698, 38.17995270743961),
+            Offset(17.84579128514784, 38.41489049303844),
+            Offset(16.482379832858747, 38.46701441998266),
+            Offset(15.39549972003973, 38.39692525524468),
+            Offset(14.548571525540689, 38.27658921964269),
+            Offset(13.905364717497006, 38.15101039289202),
+            Offset(13.434640671583647, 38.046430453557406),
+            Offset(13.110930689287915, 37.97670460018869),
+            Offset(12.913821678177088, 37.94797526489096),
+            Offset(12.827316937779091, 37.96178258311692),
+            Offset(12.806397092267282, 37.99086129676),
+            Offset(12.79999999999928, 38.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(32.120000000000516, 14.400000000000006),
+            Offset(32.32054230443716, 14.573283935533016),
+            Offset(33.06357242335454, 15.285296624289762),
+            Offset(34.60098200458155, 17.238818110822155),
+            Offset(36.48674977939076, 22.52504917136431),
+            Offset(34.67650693598792, 30.64553463303195),
+            Offset(30.30710001040328, 34.96358379754031),
+            Offset(26.58999977708711, 36.59591974066494),
+            Offset(23.731460512285572, 37.27743473350904),
+            Offset(21.440257338140828, 37.81667426334442),
+            Offset(19.500775957756698, 38.17995270743961),
+            Offset(17.84579128514784, 38.41489049303844),
+            Offset(16.482379832858747, 38.46701441998266),
+            Offset(15.39549972003973, 38.39692525524468),
+            Offset(14.548571525540689, 38.27658921964269),
+            Offset(13.905364717497006, 38.15101039289202),
+            Offset(13.434640671583647, 38.046430453557406),
+            Offset(13.110930689287915, 37.97670460018869),
+            Offset(12.913821678177088, 37.94797526489096),
+            Offset(12.827316937779091, 37.96178258311692),
+            Offset(12.806397092267282, 37.99086129676),
+            Offset(12.79999999999928, 38.0),
+          ],
+          <Offset>[
+            Offset(15.88000000000052, 14.400000000000002),
+            Offset(16.0841501846027, 14.23098153855435),
+            Offset(16.909555998563246, 13.616358834085176),
+            Offset(19.091045681624482, 12.424311359573721),
+            Offset(24.616857845861503, 11.441581055005743),
+            Offset(32.32484913042846, 14.574087955608066),
+            Offset(35.862698571239974, 19.611826936548507),
+            Offset(36.85283262226824, 23.587705757348193),
+            Offset(37.024264738045005, 26.59440062294799),
+            Offset(37.105395261430175, 29.069653364478558),
+            Offset(37.039412214719505, 31.16155045099348),
+            Offset(36.87938142220778, 32.932238633932485),
+            Offset(36.59726506613041, 34.34978415518983),
+            Offset(36.25867207317826, 35.44700139229438),
+            Offset(35.92813316136494, 36.28292063146849),
+            Offset(35.64206627873214, 36.90794520394589),
+            Offset(35.41899150687777, 37.36172366092795),
+            Offset(35.26641308921645, 37.674356951517986),
+            Offset(35.185373943141684, 37.868553404206274),
+            Offset(35.1734070053048, 37.961203228978455),
+            Offset(35.19360290773128, 37.99086129676),
+            Offset(35.19999999999928, 38.0),
+          ],
+          <Offset>[
+            Offset(15.88000000000052, 14.400000000000002),
+            Offset(16.0841501846027, 14.23098153855435),
+            Offset(16.909555998563246, 13.616358834085176),
+            Offset(19.091045681624482, 12.424311359573721),
+            Offset(24.616857845861503, 11.441581055005743),
+            Offset(32.32484913042846, 14.574087955608066),
+            Offset(35.862698571239974, 19.611826936548507),
+            Offset(36.85283262226824, 23.587705757348193),
+            Offset(37.024264738045005, 26.59440062294799),
+            Offset(37.105395261430175, 29.069653364478558),
+            Offset(37.039412214719505, 31.16155045099348),
+            Offset(36.87938142220778, 32.932238633932485),
+            Offset(36.59726506613041, 34.34978415518983),
+            Offset(36.25867207317826, 35.44700139229438),
+            Offset(35.92813316136494, 36.28292063146849),
+            Offset(35.64206627873214, 36.90794520394589),
+            Offset(35.41899150687777, 37.36172366092795),
+            Offset(35.26641308921645, 37.674356951517986),
+            Offset(35.185373943141684, 37.868553404206274),
+            Offset(35.1734070053048, 37.961203228978455),
+            Offset(35.19360290773128, 37.99086129676),
+            Offset(35.19999999999928, 38.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(14.59819610595592, 14.400000000000002),
+            Offset(14.802631056250549, 14.203964017286612),
+            Offset(15.634538685893425, 13.484631558321478),
+            Offset(17.866864845716222, 12.044308065136882),
+            Offset(23.679981386361106, 10.56677609250158),
+            Offset(32.13923557546377, 13.305587774939834),
+            Offset(36.30119536120482, 18.400131017530946),
+            Offset(37.66286581838166, 22.560982891684347),
+            Offset(38.073449973226715, 25.751201440489343),
+            Offset(38.3418259751505, 28.379261403305176),
+            Offset(38.423715924719914, 30.60759630430111),
+            Offset(38.381680064777505, 32.49949944041691),
+            Offset(38.184909045210944, 34.02481606491337),
+            Offset(37.90537746507338, 35.21416740863276),
+            Offset(37.615596545463845, 36.1255627398579),
+            Offset(37.35771829257922, 36.80983154632628),
+            Offset(37.15419018352325, 37.30768056775854),
+            Offset(37.01511897681897, 37.650493011036374),
+            Offset(36.94324108720877, 37.862284731158375),
+            Offset(36.937157329518215, 37.96115750124504),
+            Offset(36.96059844905146, 37.99086129676),
+            Offset(36.96800537109528, 38.0),
+          ],
+          <Offset>[
+            Offset(13.560000000000521, 15.438196105955402),
+            Offset(13.74278277489713, 15.22004665632446),
+            Offset(14.49514695789896, 14.410638469213454),
+            Offset(16.56755710831768, 12.72804903390955),
+            Offset(22.212611372667403, 10.617050855101162),
+            Offset(30.96147724209907, 12.428504561805607),
+            Offset(35.67494330452109, 17.06355866625214),
+            Offset(37.48735818248413, 21.073303692247634),
+            Offset(38.24028822267485, 24.218466095798203),
+            Offset(38.78408965466406, 26.818632817890993),
+            Offset(39.09625693291425, 29.037705801935473),
+            Offset(39.24796894114535, 30.932216991335657),
+            Offset(39.20761243761814, 32.475697348832576),
+            Offset(39.05054155543821, 33.691835896176805),
+            Offset(38.854904345496976, 34.631351126424754),
+            Offset(38.66784227525018, 35.340773396338506),
+            Offset(38.51584089185641, 35.85848553358435),
+            Offset(38.41215342273489, 36.21480140360634),
+            Offset(38.36194695965814, 36.43342424460918),
+            Offset(38.3656685492364, 36.532572207239845),
+            Offset(38.39177516708328, 36.55968457872818),
+            Offset(38.39999999999928, 36.568005371096),
+          ],
+          <Offset>[
+            Offset(13.560000000000521, 16.720000000000002),
+            Offset(13.715765253629392, 16.501565784676608),
+            Offset(14.363419682135264, 15.685655781883277),
+            Offset(16.18755381388084, 13.952229869817812),
+            Offset(21.337806410163243, 11.553927314601557),
+            Offset(29.692977061430838, 12.614118116770292),
+            Offset(34.463247385503536, 16.625061876287294),
+            Offset(36.46063531682029, 20.26327049613421),
+            Offset(37.3970890402162, 23.169280860616492),
+            Offset(38.093697693490675, 25.582202104170673),
+            Offset(38.54230278622188, 27.65340209193506),
+            Offset(38.81522974762978, 29.42991834876593),
+            Offset(38.882644347341675, 30.888053369752043),
+            Offset(38.81770757177658, 32.045130504281694),
+            Offset(38.69754645388638, 32.943887742325856),
+            Offset(38.56972861763057, 33.62512138249143),
+            Offset(38.461797798687, 34.12328685693886),
+            Offset(38.38828948225328, 34.46609551600381),
+            Offset(38.35567828661024, 34.67555710054209),
+            Offset(38.36562282150298, 34.76882188302643),
+            Offset(38.39177516708328, 34.792689037407996),
+            Offset(38.39999999999928, 34.8),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(13.560000000000521, 16.720000000000002),
+            Offset(13.715765253629392, 16.501565784676608),
+            Offset(14.363419682135264, 15.685655781883277),
+            Offset(16.18755381388084, 13.952229869817812),
+            Offset(21.337806410163243, 11.553927314601557),
+            Offset(29.692977061430838, 12.614118116770292),
+            Offset(34.463247385503536, 16.625061876287294),
+            Offset(36.46063531682029, 20.26327049613421),
+            Offset(37.3970890402162, 23.169280860616492),
+            Offset(38.093697693490675, 25.582202104170673),
+            Offset(38.54230278622188, 27.65340209193506),
+            Offset(38.81522974762978, 29.42991834876593),
+            Offset(38.882644347341675, 30.888053369752043),
+            Offset(38.81770757177658, 32.045130504281694),
+            Offset(38.69754645388638, 32.943887742325856),
+            Offset(38.56972861763057, 33.62512138249143),
+            Offset(38.461797798687, 34.12328685693886),
+            Offset(38.38828948225328, 34.46609551600381),
+            Offset(38.35567828661024, 34.67555710054209),
+            Offset(38.36562282150298, 34.76882188302643),
+            Offset(38.39177516708328, 34.792689037407996),
+            Offset(38.39999999999928, 34.8),
+          ],
+          <Offset>[
+            Offset(13.56000000000052, 32.96),
+            Offset(13.373462856650727, 32.73795790451107),
+            Offset(12.694481891930677, 31.839672206674575),
+            Offset(11.373047062632402, 29.462166192774887),
+            Offset(10.254338293804675, 23.423819248130823),
+            Offset(13.621530384006954, 14.965775922329756),
+            Offset(19.111490524511733, 11.069463315450594),
+            Offset(23.452421333503544, 10.000437650953089),
+            Offset(26.714054929655155, 9.876476634857061),
+            Offset(29.346676794624816, 9.917064180881326),
+            Offset(31.523900529775755, 10.114765834972257),
+            Offset(33.33257788852383, 10.396328211705992),
+            Offset(34.76541408254884, 10.773168136480383),
+            Offset(35.86778370882628, 11.181958151143158),
+            Offset(36.70387786571219, 11.5643261065016),
+            Offset(37.32666342868444, 11.888419821256292),
+            Offset(37.777091006057546, 12.138936021644747),
+            Offset(38.085941833582574, 12.31061311607528),
+            Offset(38.27625642592556, 12.404004835577497),
+            Offset(38.365043467364515, 12.422731815500718),
+            Offset(38.39177516708328, 12.405483221944),
+            Offset(38.39999999999928, 12.399999999999999),
+          ],
+          <Offset>[
+            Offset(13.56000000000052, 32.96),
+            Offset(13.373462856650727, 32.73795790451107),
+            Offset(12.694481891930677, 31.839672206674575),
+            Offset(11.373047062632402, 29.462166192774887),
+            Offset(10.254338293804675, 23.423819248130823),
+            Offset(13.621530384006954, 14.965775922329756),
+            Offset(19.111490524511733, 11.069463315450594),
+            Offset(23.452421333503544, 10.000437650953089),
+            Offset(26.714054929655155, 9.876476634857061),
+            Offset(29.346676794624816, 9.917064180881326),
+            Offset(31.523900529775755, 10.114765834972257),
+            Offset(33.33257788852383, 10.396328211705992),
+            Offset(34.76541408254884, 10.773168136480383),
+            Offset(35.86778370882628, 11.181958151143158),
+            Offset(36.70387786571219, 11.5643261065016),
+            Offset(37.32666342868444, 11.888419821256292),
+            Offset(37.777091006057546, 12.138936021644747),
+            Offset(38.085941833582574, 12.31061311607528),
+            Offset(38.27625642592556, 12.404004835577497),
+            Offset(38.365043467364515, 12.422731815500718),
+            Offset(38.39177516708328, 12.405483221944),
+            Offset(38.39999999999928, 12.399999999999999),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(13.560000000000517, 34.2418038940446),
+            Offset(13.34644533538299, 34.01947703286322),
+            Offset(12.56275461616698, 33.114689519344395),
+            Offset(10.993043768195564, 30.686347028683148),
+            Offset(9.379533331300514, 24.360695707631216),
+            Offset(12.353030203338722, 15.151389477294442),
+            Offset(17.899794605494172, 10.630966525485752),
+            Offset(22.425698467839702, 9.190404454839666),
+            Offset(25.870855747196508, 8.827291399675353),
+            Offset(28.65628483345143, 8.680633467161003),
+            Offset(30.969946383083386, 8.730462124971844),
+            Offset(32.899838695008256, 8.894029569136269),
+            Offset(34.44044599227237, 9.18552415739985),
+            Offset(35.63494972516465, 9.535252759248047),
+            Offset(36.546519974101585, 9.876862722402706),
+            Offset(37.228549771064834, 10.172767807409217),
+            Offset(37.723047912888134, 10.403737344999264),
+            Offset(38.06207789310096, 10.561907228472755),
+            Offset(38.26998775287766, 10.646137691510413),
+            Offset(38.3649977396311, 10.658981491287307),
+            Offset(38.39177516708328, 10.638487680623824),
+            Offset(38.39999999999928, 10.631994628904),
+          ],
+          <Offset>[
+            Offset(14.598196105955916, 35.28),
+            Offset(14.362527974420836, 35.07932531421663),
+            Offset(13.488761527058955, 34.25408124733886),
+            Offset(11.676784736968232, 31.985654766081694),
+            Offset(9.429808093900096, 25.828065721324922),
+            Offset(11.475946990204495, 16.329147810659144),
+            Offset(16.56322225421536, 11.257218582169475),
+            Offset(20.938019268402986, 9.365912090737192),
+            Offset(24.338120402505368, 8.660453150227216),
+            Offset(27.09565624803725, 8.238369787647443),
+            Offset(29.40005588071775, 8.057921116777507),
+            Offset(31.332556245926995, 8.027740692768422),
+            Offset(32.89132727619159, 8.16282076499266),
+            Offset(34.112618212708696, 8.390088668883216),
+            Offset(35.05230836066845, 8.637554922369567),
+            Offset(35.75949162107706, 8.862643824738257),
+            Offset(36.273852878713946, 9.042086636666099),
+            Offset(36.626386285670925, 9.164872782556836),
+            Offset(36.84112726632846, 9.227431819061039),
+            Offset(36.936412445625905, 9.230470271569121),
+            Offset(36.96059844905146, 9.207310962592),
+            Offset(36.96800537109528, 9.2),
+          ],
+          <Offset>[
+            Offset(15.880000000000516, 35.28),
+            Offset(15.644047102772983, 35.10634283548437),
+            Offset(14.763778839728777, 34.385808523102554),
+            Offset(12.900965572876494, 32.365658060518534),
+            Offset(10.36668455340049, 26.702870683829083),
+            Offset(11.66156054516918, 17.597647991327378),
+            Offset(16.12472546425052, 12.468914501187037),
+            Offset(20.127986072289563, 10.392634956401036),
+            Offset(23.288935167323658, 9.503652332685864),
+            Offset(25.859225534316927, 8.928761748820826),
+            Offset(28.015752170717334, 8.611875263469875),
+            Offset(29.83025760335727, 8.460479886283995),
+            Offset(31.303683297111053, 8.487788855269121),
+            Offset(32.465912820813585, 8.62292265254484),
+            Offset(33.36484497656955, 8.794912813980165),
+            Offset(34.04383960722998, 8.960757482357865),
+            Offset(34.538654202068464, 9.096129729835509),
+            Offset(34.8776803980684, 9.188736723038446),
+            Offset(35.08326012226138, 9.233700492108937),
+            Offset(35.17266212141249, 9.23051599930254),
+            Offset(35.19360290773128, 9.207310962592),
+            Offset(35.19999999999928, 9.2),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(15.880000000000516, 35.28),
+            Offset(15.644047102772983, 35.10634283548437),
+            Offset(14.763778839728777, 34.385808523102554),
+            Offset(12.900965572876494, 32.365658060518534),
+            Offset(10.36668455340049, 26.702870683829083),
+            Offset(11.66156054516918, 17.597647991327378),
+            Offset(16.12472546425052, 12.468914501187037),
+            Offset(20.127986072289563, 10.392634956401036),
+            Offset(23.288935167323658, 9.503652332685864),
+            Offset(25.859225534316927, 8.928761748820826),
+            Offset(28.015752170717334, 8.611875263469875),
+            Offset(29.83025760335727, 8.460479886283995),
+            Offset(31.303683297111053, 8.487788855269121),
+            Offset(32.465912820813585, 8.62292265254484),
+            Offset(33.36484497656955, 8.794912813980165),
+            Offset(34.04383960722998, 8.960757482357865),
+            Offset(34.538654202068464, 9.096129729835509),
+            Offset(34.8776803980684, 9.188736723038446),
+            Offset(35.08326012226138, 9.233700492108937),
+            Offset(35.17266212141249, 9.23051599930254),
+            Offset(35.19360290773128, 9.207310962592),
+            Offset(35.19999999999928, 9.2),
+          ],
+          <Offset>[
+            Offset(17.040000000000518, 35.28),
+            Offset(16.803789397046874, 35.13079300669713),
+            Offset(15.9176371557853, 34.50501836526003),
+            Offset(14.008818167373427, 32.70955139989342),
+            Offset(11.21453397722401, 27.494546977854696),
+            Offset(11.829536102709142, 18.74560846828623),
+            Offset(15.727896995619327, 13.565468562686451),
+            Offset(19.394926583348056, 11.321793098066518),
+            Offset(22.339449151197982, 10.266726197725939),
+            Offset(24.74028711122483, 9.553548955882674),
+            Offset(26.762992438077134, 9.113189710358885),
+            Offset(28.47071545071013, 8.852097876220133),
+            Offset(29.86690578044879, 8.781876731325752),
+            Offset(30.975686224160835, 8.833631499898434),
+            Offset(31.83773343115353, 8.937317713135464),
+            Offset(32.49121806714176, 9.049547852996874),
+            Offset(32.968343428118885, 9.14503735788047),
+            Offset(33.295145940930645, 9.210332983657782),
+            Offset(33.49243496047819, 9.239373482157845),
+            Offset(33.57651283087494, 9.230557381741002),
+            Offset(33.59451677805528, 9.207310962592),
+            Offset(33.599999999999284, 9.2),
+          ],
+          <Offset>[
+            Offset(17.040000000000518, 35.28),
+            Offset(16.803789397046874, 35.13079300669713),
+            Offset(15.9176371557853, 34.50501836526003),
+            Offset(14.008818167373427, 32.70955139989342),
+            Offset(11.21453397722401, 27.494546977854696),
+            Offset(11.829536102709142, 18.74560846828623),
+            Offset(15.727896995619327, 13.565468562686451),
+            Offset(19.394926583348056, 11.321793098066518),
+            Offset(22.339449151197982, 10.266726197725939),
+            Offset(24.74028711122483, 9.553548955882674),
+            Offset(26.762992438077134, 9.113189710358885),
+            Offset(28.47071545071013, 8.852097876220133),
+            Offset(29.86690578044879, 8.781876731325752),
+            Offset(30.975686224160835, 8.833631499898434),
+            Offset(31.83773343115353, 8.937317713135464),
+            Offset(32.49121806714176, 9.049547852996874),
+            Offset(32.968343428118885, 9.14503735788047),
+            Offset(33.295145940930645, 9.210332983657782),
+            Offset(33.49243496047819, 9.239373482157845),
+            Offset(33.57651283087494, 9.230557381741002),
+            Offset(33.59451677805528, 9.207310962592),
+            Offset(33.599999999999284, 9.2),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(17.040000000000518, 35.28),
+            Offset(16.803789397046874, 35.13079300669713),
+            Offset(15.9176371557853, 34.50501836526003),
+            Offset(14.008818167373427, 32.70955139989342),
+            Offset(11.21453397722401, 27.494546977854696),
+            Offset(11.829536102709142, 18.74560846828623),
+            Offset(15.727896995619327, 13.565468562686451),
+            Offset(19.394926583348056, 11.321793098066518),
+            Offset(22.339449151197982, 10.266726197725939),
+            Offset(24.74028711122483, 9.553548955882674),
+            Offset(26.762992438077134, 9.113189710358885),
+            Offset(28.47071545071013, 8.852097876220133),
+            Offset(29.86690578044879, 8.781876731325752),
+            Offset(30.975686224160835, 8.833631499898434),
+            Offset(31.83773343115353, 8.937317713135464),
+            Offset(32.49121806714176, 9.049547852996874),
+            Offset(32.968343428118885, 9.14503735788047),
+            Offset(33.295145940930645, 9.210332983657782),
+            Offset(33.49243496047819, 9.239373482157845),
+            Offset(33.57651283087494, 9.230557381741002),
+            Offset(33.59451677805528, 9.207310962592),
+            Offset(33.599999999999284, 9.2),
+          ],
+          <Offset>[
+            Offset(17.040000000000518, 37.6),
+            Offset(16.75488905462135, 37.45027759524491),
+            Offset(15.67921747147036, 36.81273499737307),
+            Offset(13.321031488623651, 34.92525658888729),
+            Offset(9.631181389172786, 29.190245825501734),
+            Offset(9.533615148791444, 19.08155958336615),
+            Offset(13.534788872620497, 12.771811625424068),
+            Offset(17.53661030001709, 9.855674120183501),
+            Offset(20.813301421117835, 8.36775416547459),
+            Offset(23.490712697101138, 7.315672109698481),
+            Offset(25.760363544299118, 6.607670245078484),
+            Offset(27.687479470837854, 6.133013570925856),
+            Offset(29.27873002833553, 5.908321698001231),
+            Offset(30.554268529453648, 5.85317830659293),
+            Offset(31.55292363284293, 5.883094622303428),
+            Offset(32.31363732586374, 5.944304772820427),
+            Offset(32.87052817202896, 6.00441580998131),
+            Offset(33.25195341969197, 6.045264069382277),
+            Offset(33.481088980380385, 6.0577231585914735),
+            Offset(33.57643006599802, 6.038258800665901),
+            Offset(33.59451677805528, 6.009138703240001),
+            Offset(33.599999999999284, 6.0),
+          ],
+          <Offset>[
+            Offset(17.040000000000518, 37.6),
+            Offset(16.75488905462135, 37.45027759524491),
+            Offset(15.67921747147036, 36.81273499737307),
+            Offset(13.321031488623651, 34.92525658888729),
+            Offset(9.631181389172786, 29.190245825501734),
+            Offset(9.533615148791444, 19.08155958336615),
+            Offset(13.534788872620497, 12.771811625424068),
+            Offset(17.53661030001709, 9.855674120183501),
+            Offset(20.813301421117835, 8.36775416547459),
+            Offset(23.490712697101138, 7.315672109698481),
+            Offset(25.760363544299118, 6.607670245078484),
+            Offset(27.687479470837854, 6.133013570925856),
+            Offset(29.27873002833553, 5.908321698001231),
+            Offset(30.554268529453648, 5.85317830659293),
+            Offset(31.55292363284293, 5.883094622303428),
+            Offset(32.31363732586374, 5.944304772820427),
+            Offset(32.87052817202896, 6.00441580998131),
+            Offset(33.25195341969197, 6.045264069382277),
+            Offset(33.481088980380385, 6.0577231585914735),
+            Offset(33.57643006599802, 6.038258800665901),
+            Offset(33.59451677805528, 6.009138703240001),
+            Offset(33.599999999999284, 6.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(17.040000000000518, 37.6),
+            Offset(16.75488905462135, 37.45027759524491),
+            Offset(15.67921747147036, 36.81273499737307),
+            Offset(13.321031488623651, 34.92525658888729),
+            Offset(9.631181389172786, 29.190245825501734),
+            Offset(9.533615148791444, 19.08155958336615),
+            Offset(13.534788872620497, 12.771811625424068),
+            Offset(17.53661030001709, 9.855674120183501),
+            Offset(20.813301421117835, 8.36775416547459),
+            Offset(23.490712697101138, 7.315672109698481),
+            Offset(25.760363544299118, 6.607670245078484),
+            Offset(27.687479470837854, 6.133013570925856),
+            Offset(29.27873002833553, 5.908321698001231),
+            Offset(30.554268529453648, 5.85317830659293),
+            Offset(31.55292363284293, 5.883094622303428),
+            Offset(32.31363732586374, 5.944304772820427),
+            Offset(32.87052817202896, 6.00441580998131),
+            Offset(33.25195341969197, 6.045264069382277),
+            Offset(33.481088980380385, 6.0577231585914735),
+            Offset(33.57643006599802, 6.038258800665901),
+            Offset(33.59451677805528, 6.009138703240001),
+            Offset(33.599999999999284, 6.0),
+          ],
+          <Offset>[
+            Offset(19.360000000000518, 37.6),
+            Offset(19.07437364316913, 37.49917793767044),
+            Offset(17.9869341035834, 37.05115468168801),
+            Offset(15.536736677617519, 35.61304326763707),
+            Offset(11.326880236819823, 30.773598413552957),
+            Offset(9.869566263871368, 21.377480537283848),
+            Offset(12.741131935358112, 14.964919748422895),
+            Offset(16.070491322134075, 11.713990403514464),
+            Offset(18.914329388866488, 9.893901895554741),
+            Offset(21.252835850916945, 8.565246523822175),
+            Offset(23.254844079018717, 7.610299138856501),
+            Offset(24.968395165543576, 6.916249550798135),
+            Offset(26.405174995011006, 6.496497450114491),
+            Offset(27.573815336148144, 6.274596001300116),
+            Offset(28.498700542010894, 6.167904420614027),
+            Offset(29.208394245687288, 6.1218855140984445),
+            Offset(29.7299066241298, 6.102231066071232),
+            Offset(30.08688450541647, 6.08845659062095),
+            Offset(30.29943865681401, 6.069069138689285),
+            Offset(30.384131484922918, 6.038341565542824),
+            Offset(30.39634451870328, 6.009138703240001),
+            Offset(30.39999999999928, 6.0),
+          ],
+          <Offset>[
+            Offset(19.360000000000518, 37.6),
+            Offset(19.07437364316913, 37.49917793767044),
+            Offset(17.9869341035834, 37.05115468168801),
+            Offset(15.536736677617519, 35.61304326763707),
+            Offset(11.326880236819823, 30.773598413552957),
+            Offset(9.869566263871368, 21.377480537283848),
+            Offset(12.741131935358112, 14.964919748422895),
+            Offset(16.070491322134075, 11.713990403514464),
+            Offset(18.914329388866488, 9.893901895554741),
+            Offset(21.252835850916945, 8.565246523822175),
+            Offset(23.254844079018717, 7.610299138856501),
+            Offset(24.968395165543576, 6.916249550798135),
+            Offset(26.405174995011006, 6.496497450114491),
+            Offset(27.573815336148144, 6.274596001300116),
+            Offset(28.498700542010894, 6.167904420614027),
+            Offset(29.208394245687288, 6.1218855140984445),
+            Offset(29.7299066241298, 6.102231066071232),
+            Offset(30.08688450541647, 6.08845659062095),
+            Offset(30.29943865681401, 6.069069138689285),
+            Offset(30.384131484922918, 6.038341565542824),
+            Offset(30.39634451870328, 6.009138703240001),
+            Offset(30.39999999999928, 6.0),
+          ],
+        ),
+        _PathClose(
+        ),
+        _PathMoveTo(
+          <Offset>[
+            Offset(15.879999999999999, 16.720000000000002),
+            Offset(16.035249842176654, 16.55046612710212),
+            Offset(16.671136314247786, 15.924075466198163),
+            Offset(18.403259002874208, 14.640016548567434),
+            Offset(23.0335052578099, 13.137279902652425),
+            Offset(30.028928176510686, 14.910039070687475),
+            Offset(33.66959044824132, 18.81816999928563),
+            Offset(34.9945163389376, 22.121586779464756),
+            Offset(35.49811700796528, 24.6954285906963),
+            Offset(35.85582084730699, 26.831776518294085),
+            Offset(36.03678332094205, 28.656030985712853),
+            Offset(36.09614544233611, 30.213154328638034),
+            Offset(36.0090893140178, 31.476229121865174),
+            Offset(35.83725437847175, 32.46654819898878),
+            Offset(35.64332336305503, 33.22869754063639),
+            Offset(35.46448553745482, 33.80270212376941),
+            Offset(35.32117625078855, 34.22110211302876),
+            Offset(35.22322056797849, 34.50928803724247),
+            Offset(35.17402796304459, 34.6869030806399),
+            Offset(35.1733242404286, 34.768904647903355),
+            Offset(35.193602907732, 34.792689037407996),
+            Offset(35.2, 34.8),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(15.879999999999999, 16.720000000000002),
+            Offset(16.035249842176654, 16.55046612710212),
+            Offset(16.671136314247786, 15.924075466198163),
+            Offset(18.403259002874208, 14.640016548567434),
+            Offset(23.0335052578099, 13.137279902652425),
+            Offset(30.028928176510686, 14.910039070687475),
+            Offset(33.66959044824132, 18.81816999928563),
+            Offset(34.9945163389376, 22.121586779464756),
+            Offset(35.49811700796528, 24.6954285906963),
+            Offset(35.85582084730699, 26.831776518294085),
+            Offset(36.03678332094205, 28.656030985712853),
+            Offset(36.09614544233611, 30.213154328638034),
+            Offset(36.0090893140178, 31.476229121865174),
+            Offset(35.83725437847175, 32.46654819898878),
+            Offset(35.64332336305503, 33.22869754063639),
+            Offset(35.46448553745482, 33.80270212376941),
+            Offset(35.32117625078855, 34.22110211302876),
+            Offset(35.22322056797849, 34.50928803724247),
+            Offset(35.17402796304459, 34.6869030806399),
+            Offset(35.1733242404286, 34.768904647903355),
+            Offset(35.193602907732, 34.792689037407996),
+            Offset(35.2, 34.8),
+          ],
+          <Offset>[
+            Offset(32.12, 16.720000000000006),
+            Offset(32.27164196201112, 16.892768524080786),
+            Offset(32.82515273903908, 17.59301325640275),
+            Offset(33.91319532583128, 19.454523299815868),
+            Offset(34.90339719133917, 24.22074801901099),
+            Offset(32.380585982070144, 30.981485748111357),
+            Offset(28.113991887404627, 34.16992686027743),
+            Offset(24.731683493756478, 35.1298007627815),
+            Offset(22.20531278220585, 35.37846270125735),
+            Offset(20.190682924017636, 35.57879741715995),
+            Offset(18.498147063979243, 35.67443324215898),
+            Offset(17.062555305276174, 35.69580618774398),
+            Offset(15.894204080746132, 35.59345938665801),
+            Offset(14.974082025333214, 35.41647206193909),
+            Offset(14.263761727230776, 35.22236612881059),
+            Offset(13.727783976219687, 35.045767312715526),
+            Offset(13.33682541549443, 34.90580890565822),
+            Offset(13.067738168049956, 34.811635685913174),
+            Offset(12.902475698079991, 34.766324941324584),
+            Offset(12.827234172902884, 34.76948400204182),
+            Offset(12.806397092268, 34.792689037407996),
+            Offset(12.799999999999999, 34.8),
+          ],
+          <Offset>[
+            Offset(32.12, 16.720000000000006),
+            Offset(32.27164196201112, 16.892768524080786),
+            Offset(32.82515273903908, 17.59301325640275),
+            Offset(33.91319532583128, 19.454523299815868),
+            Offset(34.90339719133917, 24.22074801901099),
+            Offset(32.380585982070144, 30.981485748111357),
+            Offset(28.113991887404627, 34.16992686027743),
+            Offset(24.731683493756478, 35.1298007627815),
+            Offset(22.20531278220585, 35.37846270125735),
+            Offset(20.190682924017636, 35.57879741715995),
+            Offset(18.498147063979243, 35.67443324215898),
+            Offset(17.062555305276174, 35.69580618774398),
+            Offset(15.894204080746132, 35.59345938665801),
+            Offset(14.974082025333214, 35.41647206193909),
+            Offset(14.263761727230776, 35.22236612881059),
+            Offset(13.727783976219687, 35.045767312715526),
+            Offset(13.33682541549443, 34.90580890565822),
+            Offset(13.067738168049956, 34.811635685913174),
+            Offset(12.902475698079991, 34.766324941324584),
+            Offset(12.827234172902884, 34.76948400204182),
+            Offset(12.806397092268, 34.792689037407996),
+            Offset(12.799999999999999, 34.8),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(32.12, 16.720000000000006),
+            Offset(32.27164196201112, 16.892768524080786),
+            Offset(32.82515273903908, 17.59301325640275),
+            Offset(33.91319532583128, 19.454523299815868),
+            Offset(34.90339719133917, 24.22074801901099),
+            Offset(32.380585982070144, 30.981485748111357),
+            Offset(28.113991887404627, 34.16992686027743),
+            Offset(24.731683493756478, 35.1298007627815),
+            Offset(22.20531278220585, 35.37846270125735),
+            Offset(20.190682924017636, 35.57879741715995),
+            Offset(18.498147063979243, 35.67443324215898),
+            Offset(17.062555305276174, 35.69580618774398),
+            Offset(15.894204080746132, 35.59345938665801),
+            Offset(14.974082025333214, 35.41647206193909),
+            Offset(14.263761727230776, 35.22236612881059),
+            Offset(13.727783976219687, 35.045767312715526),
+            Offset(13.33682541549443, 34.90580890565822),
+            Offset(13.067738168049956, 34.811635685913174),
+            Offset(12.902475698079991, 34.766324941324584),
+            Offset(12.827234172902884, 34.76948400204182),
+            Offset(12.806397092268, 34.792689037407996),
+            Offset(12.799999999999999, 34.8),
+          ],
+          <Offset>[
+            Offset(32.12, 29.480000000000004),
+            Offset(32.00269007867074, 29.64993376109358),
+            Offset(31.51384447530691, 30.285454733024483),
+            Offset(30.130368592707512, 31.640901839282144),
+            Offset(26.194957957057433, 33.5470916810697),
+            Offset(19.753020735522814, 32.82921688105094),
+            Offset(16.051897210911072, 29.804813705334308),
+            Offset(14.51094393543618, 27.066146384424904),
+            Offset(13.811500266765027, 24.934116523874938),
+            Offset(13.31802364633732, 23.270474763146886),
+            Offset(12.983688148200141, 21.89407618311678),
+            Offset(12.754757415978641, 20.740842508625462),
+            Offset(12.65923744412319, 19.788906703373133),
+            Offset(12.656284704443689, 19.023979498758813),
+            Offset(12.697307836522478, 18.424139129234387),
+            Offset(12.751089899190589, 17.966930371745068),
+            Offset(12.798841506999857, 17.632390392212844),
+            Offset(12.83017930123726, 17.403756657397903),
+            Offset(12.840072807542027, 17.267248161709546),
+            Offset(12.826778966079804, 17.21184180612876),
+            Offset(12.806397092268, 17.202741610971998),
+            Offset(12.799999999999999, 17.2),
+          ],
+          <Offset>[
+            Offset(32.12, 29.480000000000004),
+            Offset(32.00269007867074, 29.64993376109358),
+            Offset(31.51384447530691, 30.285454733024483),
+            Offset(30.130368592707512, 31.640901839282144),
+            Offset(26.194957957057433, 33.5470916810697),
+            Offset(19.753020735522814, 32.82921688105094),
+            Offset(16.051897210911072, 29.804813705334308),
+            Offset(14.51094393543618, 27.066146384424904),
+            Offset(13.811500266765027, 24.934116523874938),
+            Offset(13.31802364633732, 23.270474763146886),
+            Offset(12.983688148200141, 21.89407618311678),
+            Offset(12.754757415978641, 20.740842508625462),
+            Offset(12.65923744412319, 19.788906703373133),
+            Offset(12.656284704443689, 19.023979498758813),
+            Offset(12.697307836522478, 18.424139129234387),
+            Offset(12.751089899190589, 17.966930371745068),
+            Offset(12.798841506999857, 17.632390392212844),
+            Offset(12.83017930123726, 17.403756657397903),
+            Offset(12.840072807542027, 17.267248161709546),
+            Offset(12.826778966079804, 17.21184180612876),
+            Offset(12.806397092268, 17.202741610971998),
+            Offset(12.799999999999999, 17.2),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(32.12, 29.480000000000004),
+            Offset(32.00269007867074, 29.64993376109358),
+            Offset(31.51384447530691, 30.285454733024483),
+            Offset(30.130368592707512, 31.640901839282144),
+            Offset(26.194957957057433, 33.5470916810697),
+            Offset(19.753020735522814, 32.82921688105094),
+            Offset(16.051897210911072, 29.804813705334308),
+            Offset(14.51094393543618, 27.066146384424904),
+            Offset(13.811500266765027, 24.934116523874938),
+            Offset(13.31802364633732, 23.270474763146886),
+            Offset(12.983688148200141, 21.89407618311678),
+            Offset(12.754757415978641, 20.740842508625462),
+            Offset(12.65923744412319, 19.788906703373133),
+            Offset(12.656284704443689, 19.023979498758813),
+            Offset(12.697307836522478, 18.424139129234387),
+            Offset(12.751089899190589, 17.966930371745068),
+            Offset(12.798841506999857, 17.632390392212844),
+            Offset(12.83017930123726, 17.403756657397903),
+            Offset(12.840072807542027, 17.267248161709546),
+            Offset(12.826778966079804, 17.21184180612876),
+            Offset(12.806397092268, 17.202741610971998),
+            Offset(12.799999999999999, 17.2),
+          ],
+          <Offset>[
+            Offset(15.879999999999997, 29.480000000000004),
+            Offset(15.766297958836272, 29.30763136411491),
+            Offset(15.359828050515612, 28.6165169428199),
+            Offset(14.620432269750436, 26.826395088033706),
+            Offset(14.325066023528167, 22.463623564711135),
+            Offset(17.401362929963348, 16.757770203627054),
+            Offset(21.607495771747768, 14.453056844342509),
+            Offset(24.7737767806173, 14.057932401108161),
+            Offset(27.10430449252446, 14.251082413313888),
+            Offset(28.983161569626667, 14.523453864281027),
+            Offset(30.522324405162944, 14.87567392667065),
+            Offset(31.78834755303858, 15.258190649519511),
+            Offset(32.77412267739486, 15.671676438580297),
+            Offset(33.51945705758222, 16.074055635808506),
+            Offset(34.076869472346736, 16.430470541060192),
+            Offset(34.487791460425726, 16.723865182798942),
+            Offset(34.78319234229397, 16.947683599583385),
+            Offset(34.98566170116579, 17.1014090087272),
+            Offset(35.11162507250663, 17.187826301024863),
+            Offset(35.17286903360552, 17.211262451990294),
+            Offset(35.193602907732, 17.202741610971998),
+            Offset(35.2, 17.2),
+          ],
+          <Offset>[
+            Offset(15.879999999999997, 29.480000000000004),
+            Offset(15.766297958836272, 29.30763136411491),
+            Offset(15.359828050515612, 28.6165169428199),
+            Offset(14.620432269750436, 26.826395088033706),
+            Offset(14.325066023528167, 22.463623564711135),
+            Offset(17.401362929963348, 16.757770203627054),
+            Offset(21.607495771747768, 14.453056844342509),
+            Offset(24.7737767806173, 14.057932401108161),
+            Offset(27.10430449252446, 14.251082413313888),
+            Offset(28.983161569626667, 14.523453864281027),
+            Offset(30.522324405162944, 14.87567392667065),
+            Offset(31.78834755303858, 15.258190649519511),
+            Offset(32.77412267739486, 15.671676438580297),
+            Offset(33.51945705758222, 16.074055635808506),
+            Offset(34.076869472346736, 16.430470541060192),
+            Offset(34.487791460425726, 16.723865182798942),
+            Offset(34.78319234229397, 16.947683599583385),
+            Offset(34.98566170116579, 17.1014090087272),
+            Offset(35.11162507250663, 17.187826301024863),
+            Offset(35.17286903360552, 17.211262451990294),
+            Offset(35.193602907732, 17.202741610971998),
+            Offset(35.2, 17.2),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(15.879999999999997, 29.480000000000004),
+            Offset(15.766297958836272, 29.30763136411491),
+            Offset(15.359828050515612, 28.6165169428199),
+            Offset(14.620432269750436, 26.826395088033706),
+            Offset(14.325066023528167, 22.463623564711135),
+            Offset(17.401362929963348, 16.757770203627054),
+            Offset(21.607495771747768, 14.453056844342509),
+            Offset(24.7737767806173, 14.057932401108161),
+            Offset(27.10430449252446, 14.251082413313888),
+            Offset(28.983161569626667, 14.523453864281027),
+            Offset(30.522324405162944, 14.87567392667065),
+            Offset(31.78834755303858, 15.258190649519511),
+            Offset(32.77412267739486, 15.671676438580297),
+            Offset(33.51945705758222, 16.074055635808506),
+            Offset(34.076869472346736, 16.430470541060192),
+            Offset(34.487791460425726, 16.723865182798942),
+            Offset(34.78319234229397, 16.947683599583385),
+            Offset(34.98566170116579, 17.1014090087272),
+            Offset(35.11162507250663, 17.187826301024863),
+            Offset(35.17286903360552, 17.211262451990294),
+            Offset(35.193602907732, 17.202741610971998),
+            Offset(35.2, 17.2),
+          ],
+          <Offset>[
+            Offset(15.879999999999999, 16.720000000000002),
+            Offset(16.035249842176654, 16.55046612710212),
+            Offset(16.671136314247786, 15.924075466198163),
+            Offset(18.403259002874208, 14.640016548567434),
+            Offset(23.0335052578099, 13.137279902652425),
+            Offset(30.028928176510686, 14.910039070687475),
+            Offset(33.66959044824132, 18.81816999928563),
+            Offset(34.9945163389376, 22.121586779464756),
+            Offset(35.49811700796528, 24.6954285906963),
+            Offset(35.85582084730699, 26.831776518294085),
+            Offset(36.03678332094205, 28.656030985712853),
+            Offset(36.09614544233611, 30.213154328638034),
+            Offset(36.0090893140178, 31.476229121865174),
+            Offset(35.83725437847175, 32.46654819898878),
+            Offset(35.64332336305503, 33.22869754063639),
+            Offset(35.46448553745482, 33.80270212376941),
+            Offset(35.32117625078855, 34.22110211302876),
+            Offset(35.22322056797849, 34.50928803724247),
+            Offset(35.17402796304459, 34.6869030806399),
+            Offset(35.1733242404286, 34.768904647903355),
+            Offset(35.193602907732, 34.792689037407996),
+            Offset(35.2, 34.8),
+          ],
+          <Offset>[
+            Offset(15.879999999999999, 16.720000000000002),
+            Offset(16.035249842176654, 16.55046612710212),
+            Offset(16.671136314247786, 15.924075466198163),
+            Offset(18.403259002874208, 14.640016548567434),
+            Offset(23.0335052578099, 13.137279902652425),
+            Offset(30.028928176510686, 14.910039070687475),
+            Offset(33.66959044824132, 18.81816999928563),
+            Offset(34.9945163389376, 22.121586779464756),
+            Offset(35.49811700796528, 24.6954285906963),
+            Offset(35.85582084730699, 26.831776518294085),
+            Offset(36.03678332094205, 28.656030985712853),
+            Offset(36.09614544233611, 30.213154328638034),
+            Offset(36.0090893140178, 31.476229121865174),
+            Offset(35.83725437847175, 32.46654819898878),
+            Offset(35.64332336305503, 33.22869754063639),
+            Offset(35.46448553745482, 33.80270212376941),
+            Offset(35.32117625078855, 34.22110211302876),
+            Offset(35.22322056797849, 34.50928803724247),
+            Offset(35.17402796304459, 34.6869030806399),
+            Offset(35.1733242404286, 34.768904647903355),
+            Offset(35.193602907732, 34.792689037407996),
+            Offset(35.2, 34.8),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(38.0, 22.0),
+            Offset(38.039045226086195, 22.295532593474903),
+            Offset(38.131410438884686, 23.449328584561677),
+            Offset(37.96355431181625, 26.240346174357384),
+            Offset(35.597624587569385, 32.092904541992695),
+            Offset(28.005894824414142, 37.5629202849435),
+            Offset(21.11658608929624, 37.84506858847438),
+            Offset(16.898690234114245, 36.22993865924664),
+            Offset(14.340278644035072, 34.329074659673786),
+            Offset(12.751502851764192, 32.57154081283703),
+            Offset(11.745130152351626, 31.05819842574568),
+            Offset(11.10059480478027, 29.797011782766905),
+            Offset(10.68542515781559, 28.766770056534085),
+            Offset(10.41788407761968, 27.940320681241946),
+            Offset(10.246172947866903, 27.291237065301104),
+            Offset(10.137024991031543, 26.79605506038706),
+            Offset(10.06904541544619, 26.434852020780554),
+            Offset(10.028594148271123, 26.190848814107472),
+            Offset(10.007221106846284, 26.049911912085946),
+            Offset(10.000051857547207, 26.00036296922149),
+            Offset(10.0, 26.0),
+            Offset(10.0, 26.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.0, 22.0),
+            Offset(38.039045226086195, 22.295532593474903),
+            Offset(38.131410438884686, 23.449328584561677),
+            Offset(37.96355431181625, 26.240346174357384),
+            Offset(35.597624587569385, 32.092904541992695),
+            Offset(28.005894824414142, 37.5629202849435),
+            Offset(21.11658608929624, 37.84506858847438),
+            Offset(16.898690234114245, 36.22993865924664),
+            Offset(14.340278644035072, 34.329074659673786),
+            Offset(12.751502851764192, 32.57154081283703),
+            Offset(11.745130152351626, 31.05819842574568),
+            Offset(11.10059480478027, 29.797011782766905),
+            Offset(10.68542515781559, 28.766770056534085),
+            Offset(10.41788407761968, 27.940320681241946),
+            Offset(10.246172947866903, 27.291237065301104),
+            Offset(10.137024991031543, 26.79605506038706),
+            Offset(10.06904541544619, 26.434852020780554),
+            Offset(10.028594148271123, 26.190848814107472),
+            Offset(10.007221106846284, 26.049911912085946),
+            Offset(10.000051857547207, 26.00036296922149),
+            Offset(10.0, 26.0),
+            Offset(10.0, 26.0),
+          ],
+          <Offset>[
+            Offset(38.0, 26.0),
+            Offset(37.95473429086978, 26.294643953040044),
+            Offset(37.72034201765203, 27.428150364066923),
+            Offset(36.77771521052353, 30.06052753469164),
+            Offset(32.86770633230866, 35.01652324483241),
+            Offset(24.048041351653396, 38.14205402438172),
+            Offset(17.355303440951563, 36.48390994993835),
+            Offset(13.758359800160616, 33.75237437842877),
+            Offset(11.83452659356501, 31.211189693616653),
+            Offset(10.801411310099256, 29.079099978817517),
+            Offset(10.259029387048772, 27.34450693137431),
+            Offset(9.993407713414326, 25.953297856794705),
+            Offset(9.883312535673422, 24.848018298461085),
+            Offset(9.857878923767148, 23.97971539572578),
+            Offset(9.874779651667918, 23.308516008091793),
+            Offset(9.908648574481903, 22.80257985546041),
+            Offset(9.94452503300979, 22.436790656274262),
+            Offset(9.974012714390186, 22.191221223059088),
+            Offset(9.992956927188366, 22.049937345519467),
+            Offset(9.999948151863302, 22.000362970565853),
+            Offset(10.0, 22.0),
+            Offset(10.0, 22.0),
+          ],
+          <Offset>[
+            Offset(38.0, 26.0),
+            Offset(37.95473429086978, 26.294643953040044),
+            Offset(37.72034201765203, 27.428150364066923),
+            Offset(36.77771521052353, 30.06052753469164),
+            Offset(32.86770633230866, 35.01652324483241),
+            Offset(24.048041351653396, 38.14205402438172),
+            Offset(17.355303440951563, 36.48390994993835),
+            Offset(13.758359800160616, 33.75237437842877),
+            Offset(11.83452659356501, 31.211189693616653),
+            Offset(10.801411310099256, 29.079099978817517),
+            Offset(10.259029387048772, 27.34450693137431),
+            Offset(9.993407713414326, 25.953297856794705),
+            Offset(9.883312535673422, 24.848018298461085),
+            Offset(9.857878923767148, 23.97971539572578),
+            Offset(9.874779651667918, 23.308516008091793),
+            Offset(9.908648574481903, 22.80257985546041),
+            Offset(9.94452503300979, 22.436790656274262),
+            Offset(9.974012714390186, 22.191221223059088),
+            Offset(9.992956927188366, 22.049937345519467),
+            Offset(9.999948151863302, 22.000362970565853),
+            Offset(10.0, 22.0),
+            Offset(10.0, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.0, 26.0),
+            Offset(37.95473429086978, 26.294643953040044),
+            Offset(37.72034201765203, 27.428150364066923),
+            Offset(36.77771521052353, 30.06052753469164),
+            Offset(32.86770633230866, 35.01652324483241),
+            Offset(24.048041351653396, 38.14205402438172),
+            Offset(17.355303440951563, 36.48390994993835),
+            Offset(13.758359800160616, 33.75237437842877),
+            Offset(11.83452659356501, 31.211189693616653),
+            Offset(10.801411310099256, 29.079099978817517),
+            Offset(10.259029387048772, 27.34450693137431),
+            Offset(9.993407713414326, 25.953297856794705),
+            Offset(9.883312535673422, 24.848018298461085),
+            Offset(9.857878923767148, 23.97971539572578),
+            Offset(9.874779651667918, 23.308516008091793),
+            Offset(9.908648574481903, 22.80257985546041),
+            Offset(9.94452503300979, 22.436790656274262),
+            Offset(9.974012714390186, 22.191221223059088),
+            Offset(9.992956927188366, 22.049937345519467),
+            Offset(9.999948151863302, 22.000362970565853),
+            Offset(10.0, 22.0),
+            Offset(10.0, 22.0),
+          ],
+          <Offset>[
+            Offset(10.0, 26.0),
+            Offset(9.960954773913805, 25.704467406525104),
+            Offset(9.86858956111531, 24.55067141543833),
+            Offset(10.036445688183749, 21.759653825642616),
+            Offset(12.402375412430613, 15.907095458007305),
+            Offset(19.994105175585858, 10.437079715056495),
+            Offset(26.88341391070376, 10.154931411525617),
+            Offset(31.101309765885755, 11.770061340753355),
+            Offset(33.659721355964926, 13.670925340326216),
+            Offset(35.24849714823581, 15.428459187162975),
+            Offset(36.254869847648365, 16.94180157425432),
+            Offset(36.89940519521972, 18.202988217233095),
+            Offset(37.31457484218441, 19.233229943465915),
+            Offset(37.58211592238032, 20.059679318758054),
+            Offset(37.7538270521331, 20.708762934698896),
+            Offset(37.86297500896845, 21.20394493961294),
+            Offset(37.93095458455382, 21.565147979219446),
+            Offset(37.971405851728875, 21.809151185892528),
+            Offset(37.99277889315371, 21.950088087914054),
+            Offset(37.999948142452794, 21.99963703077851),
+            Offset(38.0, 22.0),
+            Offset(38.0, 22.0),
+          ],
+          <Offset>[
+            Offset(10.0, 26.0),
+            Offset(9.960954773913805, 25.704467406525104),
+            Offset(9.86858956111531, 24.55067141543833),
+            Offset(10.036445688183749, 21.759653825642616),
+            Offset(12.402375412430613, 15.907095458007305),
+            Offset(19.994105175585858, 10.437079715056495),
+            Offset(26.88341391070376, 10.154931411525617),
+            Offset(31.101309765885755, 11.770061340753355),
+            Offset(33.659721355964926, 13.670925340326216),
+            Offset(35.24849714823581, 15.428459187162975),
+            Offset(36.254869847648365, 16.94180157425432),
+            Offset(36.89940519521972, 18.202988217233095),
+            Offset(37.31457484218441, 19.233229943465915),
+            Offset(37.58211592238032, 20.059679318758054),
+            Offset(37.7538270521331, 20.708762934698896),
+            Offset(37.86297500896845, 21.20394493961294),
+            Offset(37.93095458455382, 21.565147979219446),
+            Offset(37.971405851728875, 21.809151185892528),
+            Offset(37.99277889315371, 21.950088087914054),
+            Offset(37.999948142452794, 21.99963703077851),
+            Offset(38.0, 22.0),
+            Offset(38.0, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(10.0, 26.0),
+            Offset(9.960954773913805, 25.704467406525104),
+            Offset(9.86858956111531, 24.55067141543833),
+            Offset(10.036445688183749, 21.759653825642616),
+            Offset(12.402375412430613, 15.907095458007305),
+            Offset(19.994105175585858, 10.437079715056495),
+            Offset(26.88341391070376, 10.154931411525617),
+            Offset(31.101309765885755, 11.770061340753355),
+            Offset(33.659721355964926, 13.670925340326216),
+            Offset(35.24849714823581, 15.428459187162975),
+            Offset(36.254869847648365, 16.94180157425432),
+            Offset(36.89940519521972, 18.202988217233095),
+            Offset(37.31457484218441, 19.233229943465915),
+            Offset(37.58211592238032, 20.059679318758054),
+            Offset(37.7538270521331, 20.708762934698896),
+            Offset(37.86297500896845, 21.20394493961294),
+            Offset(37.93095458455382, 21.565147979219446),
+            Offset(37.971405851728875, 21.809151185892528),
+            Offset(37.99277889315371, 21.950088087914054),
+            Offset(37.999948142452794, 21.99963703077851),
+            Offset(38.0, 22.0),
+            Offset(38.0, 22.0),
+          ],
+          <Offset>[
+            Offset(10.0, 22.0),
+            Offset(10.045265709130224, 21.705356046959963),
+            Offset(10.279657982347967, 20.571849635933084),
+            Offset(11.222284789476467, 17.93947246530836),
+            Offset(15.132293667691345, 12.983476755167583),
+            Offset(23.951958648346604, 9.857945975618275),
+            Offset(30.644696559048437, 11.516090050061646),
+            Offset(34.24164019983938, 14.247625621571231),
+            Offset(36.16547340643499, 16.788810306383347),
+            Offset(37.19858868990075, 18.920900021182483),
+            Offset(37.74097061295122, 20.65549306862569),
+            Offset(38.006592286585665, 22.046702143205295),
+            Offset(38.116687464326574, 23.151981701538915),
+            Offset(38.142121076232854, 24.02028460427422),
+            Offset(38.12522034833208, 24.691483991908207),
+            Offset(38.09135142551809, 25.19742014453959),
+            Offset(38.05547496699022, 25.563209343725738),
+            Offset(38.025987285609816, 25.808778776940912),
+            Offset(38.00704307281163, 25.950062654480533),
+            Offset(38.0000518481367, 25.999637029434147),
+            Offset(38.0, 26.0),
+            Offset(38.0, 26.0),
+          ],
+          <Offset>[
+            Offset(10.0, 22.0),
+            Offset(10.045265709130224, 21.705356046959963),
+            Offset(10.279657982347967, 20.571849635933084),
+            Offset(11.222284789476467, 17.93947246530836),
+            Offset(15.132293667691345, 12.983476755167583),
+            Offset(23.951958648346604, 9.857945975618275),
+            Offset(30.644696559048437, 11.516090050061646),
+            Offset(34.24164019983938, 14.247625621571231),
+            Offset(36.16547340643499, 16.788810306383347),
+            Offset(37.19858868990075, 18.920900021182483),
+            Offset(37.74097061295122, 20.65549306862569),
+            Offset(38.006592286585665, 22.046702143205295),
+            Offset(38.116687464326574, 23.151981701538915),
+            Offset(38.142121076232854, 24.02028460427422),
+            Offset(38.12522034833208, 24.691483991908207),
+            Offset(38.09135142551809, 25.19742014453959),
+            Offset(38.05547496699022, 25.563209343725738),
+            Offset(38.025987285609816, 25.808778776940912),
+            Offset(38.00704307281163, 25.950062654480533),
+            Offset(38.0000518481367, 25.999637029434147),
+            Offset(38.0, 26.0),
+            Offset(38.0, 26.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(10.0, 22.0),
+            Offset(10.045265709130224, 21.705356046959963),
+            Offset(10.279657982347967, 20.571849635933084),
+            Offset(11.222284789476467, 17.93947246530836),
+            Offset(15.132293667691345, 12.983476755167583),
+            Offset(23.951958648346604, 9.857945975618275),
+            Offset(30.644696559048437, 11.516090050061646),
+            Offset(34.24164019983938, 14.247625621571231),
+            Offset(36.16547340643499, 16.788810306383347),
+            Offset(37.19858868990075, 18.920900021182483),
+            Offset(37.74097061295122, 20.65549306862569),
+            Offset(38.006592286585665, 22.046702143205295),
+            Offset(38.116687464326574, 23.151981701538915),
+            Offset(38.142121076232854, 24.02028460427422),
+            Offset(38.12522034833208, 24.691483991908207),
+            Offset(38.09135142551809, 25.19742014453959),
+            Offset(38.05547496699022, 25.563209343725738),
+            Offset(38.025987285609816, 25.808778776940912),
+            Offset(38.00704307281163, 25.950062654480533),
+            Offset(38.0000518481367, 25.999637029434147),
+            Offset(38.0, 26.0),
+            Offset(38.0, 26.0),
+          ],
+          <Offset>[
+            Offset(38.0, 22.0),
+            Offset(38.039045226086195, 22.295532593474903),
+            Offset(38.131410438884686, 23.449328584561677),
+            Offset(37.96355431181625, 26.240346174357384),
+            Offset(35.597624587569385, 32.092904541992695),
+            Offset(28.005894824414142, 37.5629202849435),
+            Offset(21.11658608929624, 37.84506858847438),
+            Offset(16.898690234114245, 36.22993865924664),
+            Offset(14.340278644035072, 34.329074659673786),
+            Offset(12.751502851764192, 32.57154081283703),
+            Offset(11.745130152351626, 31.05819842574568),
+            Offset(11.10059480478027, 29.797011782766905),
+            Offset(10.68542515781559, 28.766770056534085),
+            Offset(10.41788407761968, 27.940320681241946),
+            Offset(10.246172947866903, 27.291237065301104),
+            Offset(10.137024991031543, 26.79605506038706),
+            Offset(10.06904541544619, 26.434852020780554),
+            Offset(10.028594148271123, 26.190848814107472),
+            Offset(10.007221106846284, 26.049911912085946),
+            Offset(10.000051857547207, 26.00036296922149),
+            Offset(10.0, 26.0),
+            Offset(10.0, 26.0),
+          ],
+          <Offset>[
+            Offset(38.0, 22.0),
+            Offset(38.039045226086195, 22.295532593474903),
+            Offset(38.131410438884686, 23.449328584561677),
+            Offset(37.96355431181625, 26.240346174357384),
+            Offset(35.597624587569385, 32.092904541992695),
+            Offset(28.005894824414142, 37.5629202849435),
+            Offset(21.11658608929624, 37.84506858847438),
+            Offset(16.898690234114245, 36.22993865924664),
+            Offset(14.340278644035072, 34.329074659673786),
+            Offset(12.751502851764192, 32.57154081283703),
+            Offset(11.745130152351626, 31.05819842574568),
+            Offset(11.10059480478027, 29.797011782766905),
+            Offset(10.68542515781559, 28.766770056534085),
+            Offset(10.41788407761968, 27.940320681241946),
+            Offset(10.246172947866903, 27.291237065301104),
+            Offset(10.137024991031543, 26.79605506038706),
+            Offset(10.06904541544619, 26.434852020780554),
+            Offset(10.028594148271123, 26.190848814107472),
+            Offset(10.007221106846284, 26.049911912085946),
+            Offset(10.000051857547207, 26.00036296922149),
+            Offset(10.0, 26.0),
+            Offset(10.0, 26.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(26.0, 38.0),
+            Offset(25.7044674065251, 38.0390452260862),
+            Offset(24.550671415438327, 38.13141043888469),
+            Offset(21.759653825642616, 37.96355431181625),
+            Offset(15.907095458007305, 35.597624587569385),
+            Offset(10.437079715056495, 28.005894824414142),
+            Offset(10.154931411525617, 21.11658608929624),
+            Offset(11.770061340753355, 16.898690234114245),
+            Offset(13.670925340326216, 14.340278644035072),
+            Offset(15.428459187162979, 12.751502851764188),
+            Offset(16.941801574254317, 11.74513015235163),
+            Offset(18.20298821723309, 11.100594804780274),
+            Offset(19.233229943465915, 10.68542515781559),
+            Offset(20.059679318758054, 10.41788407761968),
+            Offset(20.708762934698896, 10.246172947866903),
+            Offset(21.203944939612935, 10.137024991031547),
+            Offset(21.565147979219454, 10.069045415446187),
+            Offset(21.809151185892528, 10.028594148271123),
+            Offset(21.950088087914054, 10.007221106846284),
+            Offset(21.99963703077851, 10.000051857547207),
+            Offset(22.0, 10.0),
+            Offset(22.0, 10.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(26.0, 38.0),
+            Offset(25.7044674065251, 38.0390452260862),
+            Offset(24.550671415438327, 38.13141043888469),
+            Offset(21.759653825642616, 37.96355431181625),
+            Offset(15.907095458007305, 35.597624587569385),
+            Offset(10.437079715056495, 28.005894824414142),
+            Offset(10.154931411525617, 21.11658608929624),
+            Offset(11.770061340753355, 16.898690234114245),
+            Offset(13.670925340326216, 14.340278644035072),
+            Offset(15.428459187162979, 12.751502851764188),
+            Offset(16.941801574254317, 11.74513015235163),
+            Offset(18.20298821723309, 11.100594804780274),
+            Offset(19.233229943465915, 10.68542515781559),
+            Offset(20.059679318758054, 10.41788407761968),
+            Offset(20.708762934698896, 10.246172947866903),
+            Offset(21.203944939612935, 10.137024991031547),
+            Offset(21.565147979219454, 10.069045415446187),
+            Offset(21.809151185892528, 10.028594148271123),
+            Offset(21.950088087914054, 10.007221106846284),
+            Offset(21.99963703077851, 10.000051857547207),
+            Offset(22.0, 10.0),
+            Offset(22.0, 10.0),
+          ],
+          <Offset>[
+            Offset(22.0, 38.0),
+            Offset(21.70535604695996, 37.95473429086978),
+            Offset(20.571849635933077, 37.72034201765204),
+            Offset(17.93947246530836, 36.77771521052353),
+            Offset(12.983476755167583, 32.86770633230866),
+            Offset(9.857945975618275, 24.048041351653396),
+            Offset(11.516090050061646, 17.355303440951563),
+            Offset(14.247625621571231, 13.758359800160616),
+            Offset(16.788810306383347, 11.83452659356501),
+            Offset(18.920900021182486, 10.801411310099253),
+            Offset(20.655493068625688, 10.259029387048775),
+            Offset(22.04670214320529, 9.99340771341433),
+            Offset(23.151981701538915, 9.883312535673422),
+            Offset(24.02028460427422, 9.857878923767148),
+            Offset(24.691483991908207, 9.874779651667918),
+            Offset(25.197420144539585, 9.908648574481907),
+            Offset(25.56320934372574, 9.944525033009786),
+            Offset(25.808778776940912, 9.974012714390186),
+            Offset(25.950062654480533, 9.992956927188366),
+            Offset(25.999637029434147, 9.999948151863302),
+            Offset(26.0, 10.0),
+            Offset(26.0, 10.0),
+          ],
+          <Offset>[
+            Offset(22.0, 38.0),
+            Offset(21.70535604695996, 37.95473429086978),
+            Offset(20.571849635933077, 37.72034201765204),
+            Offset(17.93947246530836, 36.77771521052353),
+            Offset(12.983476755167583, 32.86770633230866),
+            Offset(9.857945975618275, 24.048041351653396),
+            Offset(11.516090050061646, 17.355303440951563),
+            Offset(14.247625621571231, 13.758359800160616),
+            Offset(16.788810306383347, 11.83452659356501),
+            Offset(18.920900021182486, 10.801411310099253),
+            Offset(20.655493068625688, 10.259029387048775),
+            Offset(22.04670214320529, 9.99340771341433),
+            Offset(23.151981701538915, 9.883312535673422),
+            Offset(24.02028460427422, 9.857878923767148),
+            Offset(24.691483991908207, 9.874779651667918),
+            Offset(25.197420144539585, 9.908648574481907),
+            Offset(25.56320934372574, 9.944525033009786),
+            Offset(25.808778776940912, 9.974012714390186),
+            Offset(25.950062654480533, 9.992956927188366),
+            Offset(25.999637029434147, 9.999948151863302),
+            Offset(26.0, 10.0),
+            Offset(26.0, 10.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(22.0, 38.0),
+            Offset(21.70535604695996, 37.95473429086978),
+            Offset(20.571849635933077, 37.72034201765204),
+            Offset(17.93947246530836, 36.77771521052353),
+            Offset(12.983476755167583, 32.86770633230866),
+            Offset(9.857945975618275, 24.048041351653396),
+            Offset(11.516090050061646, 17.355303440951563),
+            Offset(14.247625621571231, 13.758359800160616),
+            Offset(16.788810306383347, 11.83452659356501),
+            Offset(18.920900021182486, 10.801411310099253),
+            Offset(20.655493068625688, 10.259029387048775),
+            Offset(22.04670214320529, 9.99340771341433),
+            Offset(23.151981701538915, 9.883312535673422),
+            Offset(24.02028460427422, 9.857878923767148),
+            Offset(24.691483991908207, 9.874779651667918),
+            Offset(25.197420144539585, 9.908648574481907),
+            Offset(25.56320934372574, 9.944525033009786),
+            Offset(25.808778776940912, 9.974012714390186),
+            Offset(25.950062654480533, 9.992956927188366),
+            Offset(25.999637029434147, 9.999948151863302),
+            Offset(26.0, 10.0),
+            Offset(26.0, 10.0),
+          ],
+          <Offset>[
+            Offset(22.0, 10.0),
+            Offset(22.2955325934749, 9.960954773913809),
+            Offset(23.449328584561673, 9.868589561115314),
+            Offset(26.240346174357384, 10.036445688183749),
+            Offset(32.092904541992695, 12.402375412430613),
+            Offset(37.5629202849435, 19.994105175585858),
+            Offset(37.84506858847438, 26.88341391070376),
+            Offset(36.22993865924664, 31.101309765885755),
+            Offset(34.329074659673786, 33.659721355964926),
+            Offset(32.57154081283703, 35.24849714823581),
+            Offset(31.058198425745676, 36.25486984764837),
+            Offset(29.7970117827669, 36.89940519521973),
+            Offset(28.766770056534085, 37.31457484218441),
+            Offset(27.940320681241946, 37.58211592238032),
+            Offset(27.291237065301104, 37.7538270521331),
+            Offset(26.796055060387058, 37.862975008968455),
+            Offset(26.434852020780554, 37.93095458455382),
+            Offset(26.190848814107472, 37.971405851728875),
+            Offset(26.049911912085946, 37.99277889315371),
+            Offset(26.00036296922149, 37.999948142452794),
+            Offset(26.0, 38.0),
+            Offset(26.0, 38.0),
+          ],
+          <Offset>[
+            Offset(22.0, 10.0),
+            Offset(22.2955325934749, 9.960954773913809),
+            Offset(23.449328584561673, 9.868589561115314),
+            Offset(26.240346174357384, 10.036445688183749),
+            Offset(32.092904541992695, 12.402375412430613),
+            Offset(37.5629202849435, 19.994105175585858),
+            Offset(37.84506858847438, 26.88341391070376),
+            Offset(36.22993865924664, 31.101309765885755),
+            Offset(34.329074659673786, 33.659721355964926),
+            Offset(32.57154081283703, 35.24849714823581),
+            Offset(31.058198425745676, 36.25486984764837),
+            Offset(29.7970117827669, 36.89940519521973),
+            Offset(28.766770056534085, 37.31457484218441),
+            Offset(27.940320681241946, 37.58211592238032),
+            Offset(27.291237065301104, 37.7538270521331),
+            Offset(26.796055060387058, 37.862975008968455),
+            Offset(26.434852020780554, 37.93095458455382),
+            Offset(26.190848814107472, 37.971405851728875),
+            Offset(26.049911912085946, 37.99277889315371),
+            Offset(26.00036296922149, 37.999948142452794),
+            Offset(26.0, 38.0),
+            Offset(26.0, 38.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(22.0, 10.0),
+            Offset(22.2955325934749, 9.960954773913809),
+            Offset(23.449328584561673, 9.868589561115314),
+            Offset(26.240346174357384, 10.036445688183749),
+            Offset(32.092904541992695, 12.402375412430613),
+            Offset(37.5629202849435, 19.994105175585858),
+            Offset(37.84506858847438, 26.88341391070376),
+            Offset(36.22993865924664, 31.101309765885755),
+            Offset(34.329074659673786, 33.659721355964926),
+            Offset(32.57154081283703, 35.24849714823581),
+            Offset(31.058198425745676, 36.25486984764837),
+            Offset(29.7970117827669, 36.89940519521973),
+            Offset(28.766770056534085, 37.31457484218441),
+            Offset(27.940320681241946, 37.58211592238032),
+            Offset(27.291237065301104, 37.7538270521331),
+            Offset(26.796055060387058, 37.862975008968455),
+            Offset(26.434852020780554, 37.93095458455382),
+            Offset(26.190848814107472, 37.971405851728875),
+            Offset(26.049911912085946, 37.99277889315371),
+            Offset(26.00036296922149, 37.999948142452794),
+            Offset(26.0, 38.0),
+            Offset(26.0, 38.0),
+          ],
+          <Offset>[
+            Offset(26.0, 10.0),
+            Offset(26.29464395304004, 10.045265709130227),
+            Offset(27.428150364066923, 10.27965798234797),
+            Offset(30.06052753469164, 11.222284789476467),
+            Offset(35.01652324483241, 15.132293667691345),
+            Offset(38.14205402438172, 23.951958648346604),
+            Offset(36.48390994993835, 30.644696559048437),
+            Offset(33.75237437842877, 34.24164019983938),
+            Offset(31.211189693616653, 36.16547340643499),
+            Offset(29.07909997881752, 37.198588689900745),
+            Offset(27.344506931374305, 37.74097061295122),
+            Offset(25.953297856794702, 38.00659228658567),
+            Offset(24.848018298461085, 38.116687464326574),
+            Offset(23.97971539572578, 38.142121076232854),
+            Offset(23.308516008091793, 38.12522034833208),
+            Offset(22.802579855460408, 38.09135142551809),
+            Offset(22.436790656274265, 38.05547496699022),
+            Offset(22.191221223059088, 38.025987285609816),
+            Offset(22.049937345519467, 38.00704307281163),
+            Offset(22.000362970565853, 38.0000518481367),
+            Offset(22.0, 38.0),
+            Offset(22.0, 38.0),
+          ],
+          <Offset>[
+            Offset(26.0, 10.0),
+            Offset(26.29464395304004, 10.045265709130227),
+            Offset(27.428150364066923, 10.27965798234797),
+            Offset(30.06052753469164, 11.222284789476467),
+            Offset(35.01652324483241, 15.132293667691345),
+            Offset(38.14205402438172, 23.951958648346604),
+            Offset(36.48390994993835, 30.644696559048437),
+            Offset(33.75237437842877, 34.24164019983938),
+            Offset(31.211189693616653, 36.16547340643499),
+            Offset(29.07909997881752, 37.198588689900745),
+            Offset(27.344506931374305, 37.74097061295122),
+            Offset(25.953297856794702, 38.00659228658567),
+            Offset(24.848018298461085, 38.116687464326574),
+            Offset(23.97971539572578, 38.142121076232854),
+            Offset(23.308516008091793, 38.12522034833208),
+            Offset(22.802579855460408, 38.09135142551809),
+            Offset(22.436790656274265, 38.05547496699022),
+            Offset(22.191221223059088, 38.025987285609816),
+            Offset(22.049937345519467, 38.00704307281163),
+            Offset(22.000362970565853, 38.0000518481367),
+            Offset(22.0, 38.0),
+            Offset(22.0, 38.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(26.0, 10.0),
+            Offset(26.29464395304004, 10.045265709130227),
+            Offset(27.428150364066923, 10.27965798234797),
+            Offset(30.06052753469164, 11.222284789476467),
+            Offset(35.01652324483241, 15.132293667691345),
+            Offset(38.14205402438172, 23.951958648346604),
+            Offset(36.48390994993835, 30.644696559048437),
+            Offset(33.75237437842877, 34.24164019983938),
+            Offset(31.211189693616653, 36.16547340643499),
+            Offset(29.07909997881752, 37.198588689900745),
+            Offset(27.344506931374305, 37.74097061295122),
+            Offset(25.953297856794702, 38.00659228658567),
+            Offset(24.848018298461085, 38.116687464326574),
+            Offset(23.97971539572578, 38.142121076232854),
+            Offset(23.308516008091793, 38.12522034833208),
+            Offset(22.802579855460408, 38.09135142551809),
+            Offset(22.436790656274265, 38.05547496699022),
+            Offset(22.191221223059088, 38.025987285609816),
+            Offset(22.049937345519467, 38.00704307281163),
+            Offset(22.000362970565853, 38.0000518481367),
+            Offset(22.0, 38.0),
+            Offset(22.0, 38.0),
+          ],
+          <Offset>[
+            Offset(26.0, 38.0),
+            Offset(25.7044674065251, 38.0390452260862),
+            Offset(24.550671415438327, 38.13141043888469),
+            Offset(21.759653825642616, 37.96355431181625),
+            Offset(15.907095458007305, 35.597624587569385),
+            Offset(10.437079715056495, 28.005894824414142),
+            Offset(10.154931411525617, 21.11658608929624),
+            Offset(11.770061340753355, 16.898690234114245),
+            Offset(13.670925340326216, 14.340278644035072),
+            Offset(15.428459187162979, 12.751502851764188),
+            Offset(16.941801574254317, 11.74513015235163),
+            Offset(18.20298821723309, 11.100594804780274),
+            Offset(19.233229943465915, 10.68542515781559),
+            Offset(20.059679318758054, 10.41788407761968),
+            Offset(20.708762934698896, 10.246172947866903),
+            Offset(21.203944939612935, 10.137024991031547),
+            Offset(21.565147979219454, 10.069045415446187),
+            Offset(21.809151185892528, 10.028594148271123),
+            Offset(21.950088087914054, 10.007221106846284),
+            Offset(21.99963703077851, 10.000051857547207),
+            Offset(22.0, 10.0),
+            Offset(22.0, 10.0),
+          ],
+          <Offset>[
+            Offset(26.0, 38.0),
+            Offset(25.7044674065251, 38.0390452260862),
+            Offset(24.550671415438327, 38.13141043888469),
+            Offset(21.759653825642616, 37.96355431181625),
+            Offset(15.907095458007305, 35.597624587569385),
+            Offset(10.437079715056495, 28.005894824414142),
+            Offset(10.154931411525617, 21.11658608929624),
+            Offset(11.770061340753355, 16.898690234114245),
+            Offset(13.670925340326216, 14.340278644035072),
+            Offset(15.428459187162979, 12.751502851764188),
+            Offset(16.941801574254317, 11.74513015235163),
+            Offset(18.20298821723309, 11.100594804780274),
+            Offset(19.233229943465915, 10.68542515781559),
+            Offset(20.059679318758054, 10.41788407761968),
+            Offset(20.708762934698896, 10.246172947866903),
+            Offset(21.203944939612935, 10.137024991031547),
+            Offset(21.565147979219454, 10.069045415446187),
+            Offset(21.809151185892528, 10.028594148271123),
+            Offset(21.950088087914054, 10.007221106846284),
+            Offset(21.99963703077851, 10.000051857547207),
+            Offset(22.0, 10.0),
+            Offset(22.0, 10.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        0.857142857143,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(38.0, 22.0),
+            Offset(38.039045226086195, 22.295532593474903),
+            Offset(38.131410438884686, 23.449328584561677),
+            Offset(37.96355431181625, 26.240346174357384),
+            Offset(35.597624587569385, 32.092904541992695),
+            Offset(28.005894824414142, 37.5629202849435),
+            Offset(21.11658608929624, 37.84506858847438),
+            Offset(16.898690234114245, 36.22993865924664),
+            Offset(14.340278644035072, 34.329074659673786),
+            Offset(12.751502851764192, 32.57154081283703),
+            Offset(11.745130152351626, 31.05819842574568),
+            Offset(11.10059480478027, 29.797011782766905),
+            Offset(10.68542515781559, 28.766770056534085),
+            Offset(10.41788407761968, 27.940320681241946),
+            Offset(10.246172947866903, 27.291237065301104),
+            Offset(10.137024991031543, 26.79605506038706),
+            Offset(10.06904541544619, 26.434852020780554),
+            Offset(10.028594148271123, 26.190848814107472),
+            Offset(10.007221106846284, 26.049911912085946),
+            Offset(10.000051857547207, 26.00036296922149),
+            Offset(10.0, 26.0),
+            Offset(10.0, 26.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.0, 22.0),
+            Offset(38.039045226086195, 22.295532593474903),
+            Offset(38.131410438884686, 23.449328584561677),
+            Offset(37.96355431181625, 26.240346174357384),
+            Offset(35.597624587569385, 32.092904541992695),
+            Offset(28.005894824414142, 37.5629202849435),
+            Offset(21.11658608929624, 37.84506858847438),
+            Offset(16.898690234114245, 36.22993865924664),
+            Offset(14.340278644035072, 34.329074659673786),
+            Offset(12.751502851764192, 32.57154081283703),
+            Offset(11.745130152351626, 31.05819842574568),
+            Offset(11.10059480478027, 29.797011782766905),
+            Offset(10.68542515781559, 28.766770056534085),
+            Offset(10.41788407761968, 27.940320681241946),
+            Offset(10.246172947866903, 27.291237065301104),
+            Offset(10.137024991031543, 26.79605506038706),
+            Offset(10.06904541544619, 26.434852020780554),
+            Offset(10.028594148271123, 26.190848814107472),
+            Offset(10.007221106846284, 26.049911912085946),
+            Offset(10.000051857547207, 26.00036296922149),
+            Offset(10.0, 26.0),
+            Offset(10.0, 26.0),
+          ],
+          <Offset>[
+            Offset(38.0, 26.0),
+            Offset(37.95473429086978, 26.294643953040044),
+            Offset(37.72034201765203, 27.428150364066923),
+            Offset(36.77771521052353, 30.06052753469164),
+            Offset(32.86770633230866, 35.01652324483241),
+            Offset(24.048041351653396, 38.14205402438172),
+            Offset(17.355303440951563, 36.48390994993835),
+            Offset(13.758359800160616, 33.75237437842877),
+            Offset(11.83452659356501, 31.211189693616653),
+            Offset(10.801411310099256, 29.079099978817517),
+            Offset(10.259029387048772, 27.34450693137431),
+            Offset(9.993407713414326, 25.953297856794705),
+            Offset(9.883312535673422, 24.848018298461085),
+            Offset(9.857878923767148, 23.97971539572578),
+            Offset(9.874779651667918, 23.308516008091793),
+            Offset(9.908648574481903, 22.80257985546041),
+            Offset(9.94452503300979, 22.436790656274262),
+            Offset(9.974012714390186, 22.191221223059088),
+            Offset(9.992956927188366, 22.049937345519467),
+            Offset(9.999948151863302, 22.000362970565853),
+            Offset(10.0, 22.0),
+            Offset(10.0, 22.0),
+          ],
+          <Offset>[
+            Offset(38.0, 26.0),
+            Offset(37.95473429086978, 26.294643953040044),
+            Offset(37.72034201765203, 27.428150364066923),
+            Offset(36.77771521052353, 30.06052753469164),
+            Offset(32.86770633230866, 35.01652324483241),
+            Offset(24.048041351653396, 38.14205402438172),
+            Offset(17.355303440951563, 36.48390994993835),
+            Offset(13.758359800160616, 33.75237437842877),
+            Offset(11.83452659356501, 31.211189693616653),
+            Offset(10.801411310099256, 29.079099978817517),
+            Offset(10.259029387048772, 27.34450693137431),
+            Offset(9.993407713414326, 25.953297856794705),
+            Offset(9.883312535673422, 24.848018298461085),
+            Offset(9.857878923767148, 23.97971539572578),
+            Offset(9.874779651667918, 23.308516008091793),
+            Offset(9.908648574481903, 22.80257985546041),
+            Offset(9.94452503300979, 22.436790656274262),
+            Offset(9.974012714390186, 22.191221223059088),
+            Offset(9.992956927188366, 22.049937345519467),
+            Offset(9.999948151863302, 22.000362970565853),
+            Offset(10.0, 22.0),
+            Offset(10.0, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.0, 26.0),
+            Offset(37.95473429086978, 26.294643953040044),
+            Offset(37.72034201765203, 27.428150364066923),
+            Offset(36.77771521052353, 30.06052753469164),
+            Offset(32.86770633230866, 35.01652324483241),
+            Offset(24.048041351653396, 38.14205402438172),
+            Offset(17.355303440951563, 36.48390994993835),
+            Offset(13.758359800160616, 33.75237437842877),
+            Offset(11.83452659356501, 31.211189693616653),
+            Offset(10.801411310099256, 29.079099978817517),
+            Offset(10.259029387048772, 27.34450693137431),
+            Offset(9.993407713414326, 25.953297856794705),
+            Offset(9.883312535673422, 24.848018298461085),
+            Offset(9.857878923767148, 23.97971539572578),
+            Offset(9.874779651667918, 23.308516008091793),
+            Offset(9.908648574481903, 22.80257985546041),
+            Offset(9.94452503300979, 22.436790656274262),
+            Offset(9.974012714390186, 22.191221223059088),
+            Offset(9.992956927188366, 22.049937345519467),
+            Offset(9.999948151863302, 22.000362970565853),
+            Offset(10.0, 22.0),
+            Offset(10.0, 22.0),
+          ],
+          <Offset>[
+            Offset(10.0, 26.0),
+            Offset(9.960954773913805, 25.704467406525104),
+            Offset(9.86858956111531, 24.55067141543833),
+            Offset(10.036445688183749, 21.759653825642616),
+            Offset(12.402375412430613, 15.907095458007305),
+            Offset(19.994105175585858, 10.437079715056495),
+            Offset(26.88341391070376, 10.154931411525617),
+            Offset(31.101309765885755, 11.770061340753355),
+            Offset(33.659721355964926, 13.670925340326216),
+            Offset(35.24849714823581, 15.428459187162975),
+            Offset(36.254869847648365, 16.94180157425432),
+            Offset(36.89940519521972, 18.202988217233095),
+            Offset(37.31457484218441, 19.233229943465915),
+            Offset(37.58211592238032, 20.059679318758054),
+            Offset(37.7538270521331, 20.708762934698896),
+            Offset(37.86297500896845, 21.20394493961294),
+            Offset(37.93095458455382, 21.565147979219446),
+            Offset(37.971405851728875, 21.809151185892528),
+            Offset(37.99277889315371, 21.950088087914054),
+            Offset(37.999948142452794, 21.99963703077851),
+            Offset(38.0, 22.0),
+            Offset(38.0, 22.0),
+          ],
+          <Offset>[
+            Offset(10.0, 26.0),
+            Offset(9.960954773913805, 25.704467406525104),
+            Offset(9.86858956111531, 24.55067141543833),
+            Offset(10.036445688183749, 21.759653825642616),
+            Offset(12.402375412430613, 15.907095458007305),
+            Offset(19.994105175585858, 10.437079715056495),
+            Offset(26.88341391070376, 10.154931411525617),
+            Offset(31.101309765885755, 11.770061340753355),
+            Offset(33.659721355964926, 13.670925340326216),
+            Offset(35.24849714823581, 15.428459187162975),
+            Offset(36.254869847648365, 16.94180157425432),
+            Offset(36.89940519521972, 18.202988217233095),
+            Offset(37.31457484218441, 19.233229943465915),
+            Offset(37.58211592238032, 20.059679318758054),
+            Offset(37.7538270521331, 20.708762934698896),
+            Offset(37.86297500896845, 21.20394493961294),
+            Offset(37.93095458455382, 21.565147979219446),
+            Offset(37.971405851728875, 21.809151185892528),
+            Offset(37.99277889315371, 21.950088087914054),
+            Offset(37.999948142452794, 21.99963703077851),
+            Offset(38.0, 22.0),
+            Offset(38.0, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(10.0, 26.0),
+            Offset(9.960954773913805, 25.704467406525104),
+            Offset(9.86858956111531, 24.55067141543833),
+            Offset(10.036445688183749, 21.759653825642616),
+            Offset(12.402375412430613, 15.907095458007305),
+            Offset(19.994105175585858, 10.437079715056495),
+            Offset(26.88341391070376, 10.154931411525617),
+            Offset(31.101309765885755, 11.770061340753355),
+            Offset(33.659721355964926, 13.670925340326216),
+            Offset(35.24849714823581, 15.428459187162975),
+            Offset(36.254869847648365, 16.94180157425432),
+            Offset(36.89940519521972, 18.202988217233095),
+            Offset(37.31457484218441, 19.233229943465915),
+            Offset(37.58211592238032, 20.059679318758054),
+            Offset(37.7538270521331, 20.708762934698896),
+            Offset(37.86297500896845, 21.20394493961294),
+            Offset(37.93095458455382, 21.565147979219446),
+            Offset(37.971405851728875, 21.809151185892528),
+            Offset(37.99277889315371, 21.950088087914054),
+            Offset(37.999948142452794, 21.99963703077851),
+            Offset(38.0, 22.0),
+            Offset(38.0, 22.0),
+          ],
+          <Offset>[
+            Offset(10.0, 22.0),
+            Offset(10.045265709130224, 21.705356046959963),
+            Offset(10.279657982347967, 20.571849635933084),
+            Offset(11.222284789476467, 17.93947246530836),
+            Offset(15.132293667691345, 12.983476755167583),
+            Offset(23.951958648346604, 9.857945975618275),
+            Offset(30.644696559048437, 11.516090050061646),
+            Offset(34.24164019983938, 14.247625621571231),
+            Offset(36.16547340643499, 16.788810306383347),
+            Offset(37.19858868990075, 18.920900021182483),
+            Offset(37.74097061295122, 20.65549306862569),
+            Offset(38.006592286585665, 22.046702143205295),
+            Offset(38.116687464326574, 23.151981701538915),
+            Offset(38.142121076232854, 24.02028460427422),
+            Offset(38.12522034833208, 24.691483991908207),
+            Offset(38.09135142551809, 25.19742014453959),
+            Offset(38.05547496699022, 25.563209343725738),
+            Offset(38.025987285609816, 25.808778776940912),
+            Offset(38.00704307281163, 25.950062654480533),
+            Offset(38.0000518481367, 25.999637029434147),
+            Offset(38.0, 26.0),
+            Offset(38.0, 26.0),
+          ],
+          <Offset>[
+            Offset(10.0, 22.0),
+            Offset(10.045265709130224, 21.705356046959963),
+            Offset(10.279657982347967, 20.571849635933084),
+            Offset(11.222284789476467, 17.93947246530836),
+            Offset(15.132293667691345, 12.983476755167583),
+            Offset(23.951958648346604, 9.857945975618275),
+            Offset(30.644696559048437, 11.516090050061646),
+            Offset(34.24164019983938, 14.247625621571231),
+            Offset(36.16547340643499, 16.788810306383347),
+            Offset(37.19858868990075, 18.920900021182483),
+            Offset(37.74097061295122, 20.65549306862569),
+            Offset(38.006592286585665, 22.046702143205295),
+            Offset(38.116687464326574, 23.151981701538915),
+            Offset(38.142121076232854, 24.02028460427422),
+            Offset(38.12522034833208, 24.691483991908207),
+            Offset(38.09135142551809, 25.19742014453959),
+            Offset(38.05547496699022, 25.563209343725738),
+            Offset(38.025987285609816, 25.808778776940912),
+            Offset(38.00704307281163, 25.950062654480533),
+            Offset(38.0000518481367, 25.999637029434147),
+            Offset(38.0, 26.0),
+            Offset(38.0, 26.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(10.0, 22.0),
+            Offset(10.045265709130224, 21.705356046959963),
+            Offset(10.279657982347967, 20.571849635933084),
+            Offset(11.222284789476467, 17.93947246530836),
+            Offset(15.132293667691345, 12.983476755167583),
+            Offset(23.951958648346604, 9.857945975618275),
+            Offset(30.644696559048437, 11.516090050061646),
+            Offset(34.24164019983938, 14.247625621571231),
+            Offset(36.16547340643499, 16.788810306383347),
+            Offset(37.19858868990075, 18.920900021182483),
+            Offset(37.74097061295122, 20.65549306862569),
+            Offset(38.006592286585665, 22.046702143205295),
+            Offset(38.116687464326574, 23.151981701538915),
+            Offset(38.142121076232854, 24.02028460427422),
+            Offset(38.12522034833208, 24.691483991908207),
+            Offset(38.09135142551809, 25.19742014453959),
+            Offset(38.05547496699022, 25.563209343725738),
+            Offset(38.025987285609816, 25.808778776940912),
+            Offset(38.00704307281163, 25.950062654480533),
+            Offset(38.0000518481367, 25.999637029434147),
+            Offset(38.0, 26.0),
+            Offset(38.0, 26.0),
+          ],
+          <Offset>[
+            Offset(38.0, 22.0),
+            Offset(38.039045226086195, 22.295532593474903),
+            Offset(38.131410438884686, 23.449328584561677),
+            Offset(37.96355431181625, 26.240346174357384),
+            Offset(35.597624587569385, 32.092904541992695),
+            Offset(28.005894824414142, 37.5629202849435),
+            Offset(21.11658608929624, 37.84506858847438),
+            Offset(16.898690234114245, 36.22993865924664),
+            Offset(14.340278644035072, 34.329074659673786),
+            Offset(12.751502851764192, 32.57154081283703),
+            Offset(11.745130152351626, 31.05819842574568),
+            Offset(11.10059480478027, 29.797011782766905),
+            Offset(10.68542515781559, 28.766770056534085),
+            Offset(10.41788407761968, 27.940320681241946),
+            Offset(10.246172947866903, 27.291237065301104),
+            Offset(10.137024991031543, 26.79605506038706),
+            Offset(10.06904541544619, 26.434852020780554),
+            Offset(10.028594148271123, 26.190848814107472),
+            Offset(10.007221106846284, 26.049911912085946),
+            Offset(10.000051857547207, 26.00036296922149),
+            Offset(10.0, 26.0),
+            Offset(10.0, 26.0),
+          ],
+          <Offset>[
+            Offset(38.0, 22.0),
+            Offset(38.039045226086195, 22.295532593474903),
+            Offset(38.131410438884686, 23.449328584561677),
+            Offset(37.96355431181625, 26.240346174357384),
+            Offset(35.597624587569385, 32.092904541992695),
+            Offset(28.005894824414142, 37.5629202849435),
+            Offset(21.11658608929624, 37.84506858847438),
+            Offset(16.898690234114245, 36.22993865924664),
+            Offset(14.340278644035072, 34.329074659673786),
+            Offset(12.751502851764192, 32.57154081283703),
+            Offset(11.745130152351626, 31.05819842574568),
+            Offset(11.10059480478027, 29.797011782766905),
+            Offset(10.68542515781559, 28.766770056534085),
+            Offset(10.41788407761968, 27.940320681241946),
+            Offset(10.246172947866903, 27.291237065301104),
+            Offset(10.137024991031543, 26.79605506038706),
+            Offset(10.06904541544619, 26.434852020780554),
+            Offset(10.028594148271123, 26.190848814107472),
+            Offset(10.007221106846284, 26.049911912085946),
+            Offset(10.000051857547207, 26.00036296922149),
+            Offset(10.0, 26.0),
+            Offset(10.0, 26.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        0.857142857143,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(26.0, 38.0),
+            Offset(25.7044674065251, 38.0390452260862),
+            Offset(24.550671415438327, 38.13141043888469),
+            Offset(21.759653825642616, 37.96355431181625),
+            Offset(15.907095458007305, 35.597624587569385),
+            Offset(10.437079715056495, 28.005894824414142),
+            Offset(10.154931411525617, 21.11658608929624),
+            Offset(11.770061340753355, 16.898690234114245),
+            Offset(13.670925340326216, 14.340278644035072),
+            Offset(15.428459187162979, 12.751502851764188),
+            Offset(16.941801574254317, 11.74513015235163),
+            Offset(18.20298821723309, 11.100594804780274),
+            Offset(19.233229943465915, 10.68542515781559),
+            Offset(20.059679318758054, 10.41788407761968),
+            Offset(20.708762934698896, 10.246172947866903),
+            Offset(21.203944939612935, 10.137024991031547),
+            Offset(21.565147979219454, 10.069045415446187),
+            Offset(21.809151185892528, 10.028594148271123),
+            Offset(21.950088087914054, 10.007221106846284),
+            Offset(21.99963703077851, 10.000051857547207),
+            Offset(22.0, 10.0),
+            Offset(22.0, 10.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(26.0, 38.0),
+            Offset(25.7044674065251, 38.0390452260862),
+            Offset(24.550671415438327, 38.13141043888469),
+            Offset(21.759653825642616, 37.96355431181625),
+            Offset(15.907095458007305, 35.597624587569385),
+            Offset(10.437079715056495, 28.005894824414142),
+            Offset(10.154931411525617, 21.11658608929624),
+            Offset(11.770061340753355, 16.898690234114245),
+            Offset(13.670925340326216, 14.340278644035072),
+            Offset(15.428459187162979, 12.751502851764188),
+            Offset(16.941801574254317, 11.74513015235163),
+            Offset(18.20298821723309, 11.100594804780274),
+            Offset(19.233229943465915, 10.68542515781559),
+            Offset(20.059679318758054, 10.41788407761968),
+            Offset(20.708762934698896, 10.246172947866903),
+            Offset(21.203944939612935, 10.137024991031547),
+            Offset(21.565147979219454, 10.069045415446187),
+            Offset(21.809151185892528, 10.028594148271123),
+            Offset(21.950088087914054, 10.007221106846284),
+            Offset(21.99963703077851, 10.000051857547207),
+            Offset(22.0, 10.0),
+            Offset(22.0, 10.0),
+          ],
+          <Offset>[
+            Offset(22.0, 38.0),
+            Offset(21.70535604695996, 37.95473429086978),
+            Offset(20.571849635933077, 37.72034201765204),
+            Offset(17.93947246530836, 36.77771521052353),
+            Offset(12.983476755167583, 32.86770633230866),
+            Offset(9.857945975618275, 24.048041351653396),
+            Offset(11.516090050061646, 17.355303440951563),
+            Offset(14.247625621571231, 13.758359800160616),
+            Offset(16.788810306383347, 11.83452659356501),
+            Offset(18.920900021182486, 10.801411310099253),
+            Offset(20.655493068625688, 10.259029387048775),
+            Offset(22.04670214320529, 9.99340771341433),
+            Offset(23.151981701538915, 9.883312535673422),
+            Offset(24.02028460427422, 9.857878923767148),
+            Offset(24.691483991908207, 9.874779651667918),
+            Offset(25.197420144539585, 9.908648574481907),
+            Offset(25.56320934372574, 9.944525033009786),
+            Offset(25.808778776940912, 9.974012714390186),
+            Offset(25.950062654480533, 9.992956927188366),
+            Offset(25.999637029434147, 9.999948151863302),
+            Offset(26.0, 10.0),
+            Offset(26.0, 10.0),
+          ],
+          <Offset>[
+            Offset(22.0, 38.0),
+            Offset(21.70535604695996, 37.95473429086978),
+            Offset(20.571849635933077, 37.72034201765204),
+            Offset(17.93947246530836, 36.77771521052353),
+            Offset(12.983476755167583, 32.86770633230866),
+            Offset(9.857945975618275, 24.048041351653396),
+            Offset(11.516090050061646, 17.355303440951563),
+            Offset(14.247625621571231, 13.758359800160616),
+            Offset(16.788810306383347, 11.83452659356501),
+            Offset(18.920900021182486, 10.801411310099253),
+            Offset(20.655493068625688, 10.259029387048775),
+            Offset(22.04670214320529, 9.99340771341433),
+            Offset(23.151981701538915, 9.883312535673422),
+            Offset(24.02028460427422, 9.857878923767148),
+            Offset(24.691483991908207, 9.874779651667918),
+            Offset(25.197420144539585, 9.908648574481907),
+            Offset(25.56320934372574, 9.944525033009786),
+            Offset(25.808778776940912, 9.974012714390186),
+            Offset(25.950062654480533, 9.992956927188366),
+            Offset(25.999637029434147, 9.999948151863302),
+            Offset(26.0, 10.0),
+            Offset(26.0, 10.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(22.0, 38.0),
+            Offset(21.70535604695996, 37.95473429086978),
+            Offset(20.571849635933077, 37.72034201765204),
+            Offset(17.93947246530836, 36.77771521052353),
+            Offset(12.983476755167583, 32.86770633230866),
+            Offset(9.857945975618275, 24.048041351653396),
+            Offset(11.516090050061646, 17.355303440951563),
+            Offset(14.247625621571231, 13.758359800160616),
+            Offset(16.788810306383347, 11.83452659356501),
+            Offset(18.920900021182486, 10.801411310099253),
+            Offset(20.655493068625688, 10.259029387048775),
+            Offset(22.04670214320529, 9.99340771341433),
+            Offset(23.151981701538915, 9.883312535673422),
+            Offset(24.02028460427422, 9.857878923767148),
+            Offset(24.691483991908207, 9.874779651667918),
+            Offset(25.197420144539585, 9.908648574481907),
+            Offset(25.56320934372574, 9.944525033009786),
+            Offset(25.808778776940912, 9.974012714390186),
+            Offset(25.950062654480533, 9.992956927188366),
+            Offset(25.999637029434147, 9.999948151863302),
+            Offset(26.0, 10.0),
+            Offset(26.0, 10.0),
+          ],
+          <Offset>[
+            Offset(22.0, 10.0),
+            Offset(22.2955325934749, 9.960954773913809),
+            Offset(23.449328584561673, 9.868589561115314),
+            Offset(26.240346174357384, 10.036445688183749),
+            Offset(32.092904541992695, 12.402375412430613),
+            Offset(37.5629202849435, 19.994105175585858),
+            Offset(37.84506858847438, 26.88341391070376),
+            Offset(36.22993865924664, 31.101309765885755),
+            Offset(34.329074659673786, 33.659721355964926),
+            Offset(32.57154081283703, 35.24849714823581),
+            Offset(31.058198425745676, 36.25486984764837),
+            Offset(29.7970117827669, 36.89940519521973),
+            Offset(28.766770056534085, 37.31457484218441),
+            Offset(27.940320681241946, 37.58211592238032),
+            Offset(27.291237065301104, 37.7538270521331),
+            Offset(26.796055060387058, 37.862975008968455),
+            Offset(26.434852020780554, 37.93095458455382),
+            Offset(26.190848814107472, 37.971405851728875),
+            Offset(26.049911912085946, 37.99277889315371),
+            Offset(26.00036296922149, 37.999948142452794),
+            Offset(26.0, 38.0),
+            Offset(26.0, 38.0),
+          ],
+          <Offset>[
+            Offset(22.0, 10.0),
+            Offset(22.2955325934749, 9.960954773913809),
+            Offset(23.449328584561673, 9.868589561115314),
+            Offset(26.240346174357384, 10.036445688183749),
+            Offset(32.092904541992695, 12.402375412430613),
+            Offset(37.5629202849435, 19.994105175585858),
+            Offset(37.84506858847438, 26.88341391070376),
+            Offset(36.22993865924664, 31.101309765885755),
+            Offset(34.329074659673786, 33.659721355964926),
+            Offset(32.57154081283703, 35.24849714823581),
+            Offset(31.058198425745676, 36.25486984764837),
+            Offset(29.7970117827669, 36.89940519521973),
+            Offset(28.766770056534085, 37.31457484218441),
+            Offset(27.940320681241946, 37.58211592238032),
+            Offset(27.291237065301104, 37.7538270521331),
+            Offset(26.796055060387058, 37.862975008968455),
+            Offset(26.434852020780554, 37.93095458455382),
+            Offset(26.190848814107472, 37.971405851728875),
+            Offset(26.049911912085946, 37.99277889315371),
+            Offset(26.00036296922149, 37.999948142452794),
+            Offset(26.0, 38.0),
+            Offset(26.0, 38.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(22.0, 10.0),
+            Offset(22.2955325934749, 9.960954773913809),
+            Offset(23.449328584561673, 9.868589561115314),
+            Offset(26.240346174357384, 10.036445688183749),
+            Offset(32.092904541992695, 12.402375412430613),
+            Offset(37.5629202849435, 19.994105175585858),
+            Offset(37.84506858847438, 26.88341391070376),
+            Offset(36.22993865924664, 31.101309765885755),
+            Offset(34.329074659673786, 33.659721355964926),
+            Offset(32.57154081283703, 35.24849714823581),
+            Offset(31.058198425745676, 36.25486984764837),
+            Offset(29.7970117827669, 36.89940519521973),
+            Offset(28.766770056534085, 37.31457484218441),
+            Offset(27.940320681241946, 37.58211592238032),
+            Offset(27.291237065301104, 37.7538270521331),
+            Offset(26.796055060387058, 37.862975008968455),
+            Offset(26.434852020780554, 37.93095458455382),
+            Offset(26.190848814107472, 37.971405851728875),
+            Offset(26.049911912085946, 37.99277889315371),
+            Offset(26.00036296922149, 37.999948142452794),
+            Offset(26.0, 38.0),
+            Offset(26.0, 38.0),
+          ],
+          <Offset>[
+            Offset(26.0, 10.0),
+            Offset(26.29464395304004, 10.045265709130227),
+            Offset(27.428150364066923, 10.27965798234797),
+            Offset(30.06052753469164, 11.222284789476467),
+            Offset(35.01652324483241, 15.132293667691345),
+            Offset(38.14205402438172, 23.951958648346604),
+            Offset(36.48390994993835, 30.644696559048437),
+            Offset(33.75237437842877, 34.24164019983938),
+            Offset(31.211189693616653, 36.16547340643499),
+            Offset(29.07909997881752, 37.198588689900745),
+            Offset(27.344506931374305, 37.74097061295122),
+            Offset(25.953297856794702, 38.00659228658567),
+            Offset(24.848018298461085, 38.116687464326574),
+            Offset(23.97971539572578, 38.142121076232854),
+            Offset(23.308516008091793, 38.12522034833208),
+            Offset(22.802579855460408, 38.09135142551809),
+            Offset(22.436790656274265, 38.05547496699022),
+            Offset(22.191221223059088, 38.025987285609816),
+            Offset(22.049937345519467, 38.00704307281163),
+            Offset(22.000362970565853, 38.0000518481367),
+            Offset(22.0, 38.0),
+            Offset(22.0, 38.0),
+          ],
+          <Offset>[
+            Offset(26.0, 10.0),
+            Offset(26.29464395304004, 10.045265709130227),
+            Offset(27.428150364066923, 10.27965798234797),
+            Offset(30.06052753469164, 11.222284789476467),
+            Offset(35.01652324483241, 15.132293667691345),
+            Offset(38.14205402438172, 23.951958648346604),
+            Offset(36.48390994993835, 30.644696559048437),
+            Offset(33.75237437842877, 34.24164019983938),
+            Offset(31.211189693616653, 36.16547340643499),
+            Offset(29.07909997881752, 37.198588689900745),
+            Offset(27.344506931374305, 37.74097061295122),
+            Offset(25.953297856794702, 38.00659228658567),
+            Offset(24.848018298461085, 38.116687464326574),
+            Offset(23.97971539572578, 38.142121076232854),
+            Offset(23.308516008091793, 38.12522034833208),
+            Offset(22.802579855460408, 38.09135142551809),
+            Offset(22.436790656274265, 38.05547496699022),
+            Offset(22.191221223059088, 38.025987285609816),
+            Offset(22.049937345519467, 38.00704307281163),
+            Offset(22.000362970565853, 38.0000518481367),
+            Offset(22.0, 38.0),
+            Offset(22.0, 38.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(26.0, 10.0),
+            Offset(26.29464395304004, 10.045265709130227),
+            Offset(27.428150364066923, 10.27965798234797),
+            Offset(30.06052753469164, 11.222284789476467),
+            Offset(35.01652324483241, 15.132293667691345),
+            Offset(38.14205402438172, 23.951958648346604),
+            Offset(36.48390994993835, 30.644696559048437),
+            Offset(33.75237437842877, 34.24164019983938),
+            Offset(31.211189693616653, 36.16547340643499),
+            Offset(29.07909997881752, 37.198588689900745),
+            Offset(27.344506931374305, 37.74097061295122),
+            Offset(25.953297856794702, 38.00659228658567),
+            Offset(24.848018298461085, 38.116687464326574),
+            Offset(23.97971539572578, 38.142121076232854),
+            Offset(23.308516008091793, 38.12522034833208),
+            Offset(22.802579855460408, 38.09135142551809),
+            Offset(22.436790656274265, 38.05547496699022),
+            Offset(22.191221223059088, 38.025987285609816),
+            Offset(22.049937345519467, 38.00704307281163),
+            Offset(22.000362970565853, 38.0000518481367),
+            Offset(22.0, 38.0),
+            Offset(22.0, 38.0),
+          ],
+          <Offset>[
+            Offset(26.0, 38.0),
+            Offset(25.7044674065251, 38.0390452260862),
+            Offset(24.550671415438327, 38.13141043888469),
+            Offset(21.759653825642616, 37.96355431181625),
+            Offset(15.907095458007305, 35.597624587569385),
+            Offset(10.437079715056495, 28.005894824414142),
+            Offset(10.154931411525617, 21.11658608929624),
+            Offset(11.770061340753355, 16.898690234114245),
+            Offset(13.670925340326216, 14.340278644035072),
+            Offset(15.428459187162979, 12.751502851764188),
+            Offset(16.941801574254317, 11.74513015235163),
+            Offset(18.20298821723309, 11.100594804780274),
+            Offset(19.233229943465915, 10.68542515781559),
+            Offset(20.059679318758054, 10.41788407761968),
+            Offset(20.708762934698896, 10.246172947866903),
+            Offset(21.203944939612935, 10.137024991031547),
+            Offset(21.565147979219454, 10.069045415446187),
+            Offset(21.809151185892528, 10.028594148271123),
+            Offset(21.950088087914054, 10.007221106846284),
+            Offset(21.99963703077851, 10.000051857547207),
+            Offset(22.0, 10.0),
+            Offset(22.0, 10.0),
+          ],
+          <Offset>[
+            Offset(26.0, 38.0),
+            Offset(25.7044674065251, 38.0390452260862),
+            Offset(24.550671415438327, 38.13141043888469),
+            Offset(21.759653825642616, 37.96355431181625),
+            Offset(15.907095458007305, 35.597624587569385),
+            Offset(10.437079715056495, 28.005894824414142),
+            Offset(10.154931411525617, 21.11658608929624),
+            Offset(11.770061340753355, 16.898690234114245),
+            Offset(13.670925340326216, 14.340278644035072),
+            Offset(15.428459187162979, 12.751502851764188),
+            Offset(16.941801574254317, 11.74513015235163),
+            Offset(18.20298821723309, 11.100594804780274),
+            Offset(19.233229943465915, 10.68542515781559),
+            Offset(20.059679318758054, 10.41788407761968),
+            Offset(20.708762934698896, 10.246172947866903),
+            Offset(21.203944939612935, 10.137024991031547),
+            Offset(21.565147979219454, 10.069045415446187),
+            Offset(21.809151185892528, 10.028594148271123),
+            Offset(21.950088087914054, 10.007221106846284),
+            Offset(21.99963703077851, 10.000051857547207),
+            Offset(22.0, 10.0),
+            Offset(22.0, 10.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+  ],
+);
diff --git a/lib/src/material/animated_icons/data/arrow_menu.g.dart b/lib/src/material/animated_icons/data/arrow_menu.g.dart
new file mode 100644
index 0000000..2649d8c
--- /dev/null
+++ b/lib/src/material/animated_icons/data/arrow_menu.g.dart
@@ -0,0 +1,1027 @@
+// Copyright 2014 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.
+
+// AUTOGENERATED FILE DO NOT EDIT!
+// This file was generated by vitool.
+part of material_animated_icons;
+const _AnimatedIconData _$arrow_menu = _AnimatedIconData(
+  Size(48.0, 48.0),
+  <_PathFrames>[
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(39.94921875, 22.0),
+            Offset(39.999299444085175, 22.321134814951655),
+            Offset(40.123697128128484, 23.4571221977224),
+            Offset(40.127941008338254, 25.772984385261328),
+            Offset(39.36673145070893, 29.679631356759792),
+            Offset(36.03000494036236, 35.505984938867314),
+            Offset(30.483797891740366, 39.621714579092405),
+            Offset(23.719442126984134, 41.17597729654657),
+            Offset(17.808469902722187, 40.26388356552361),
+            Offset(13.571377486425117, 38.15001206462978),
+            Offset(10.73996518894534, 35.74540806580379),
+            Offset(8.908677157504997, 33.47648684184537),
+            Offset(7.746937054898215, 31.506333856475468),
+            Offset(7.021336564573183, 29.876779670456198),
+            Offset(6.575233382277915, 28.57846974488041),
+            Offset(6.306002369153553, 27.58317306758941),
+            Offset(6.147597126684428, 26.85785338221681),
+            Offset(6.058558318339831, 26.3706514388743),
+            Offset(6.013954859968965, 26.093012124442712),
+            Offset(6.000028695609693, 26.000194701816614),
+            Offset(6.0, 26.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(39.94921875, 22.0),
+            Offset(39.999299444085175, 22.321134814951655),
+            Offset(40.123697128128484, 23.4571221977224),
+            Offset(40.127941008338254, 25.772984385261328),
+            Offset(39.36673145070893, 29.679631356759792),
+            Offset(36.03000494036236, 35.505984938867314),
+            Offset(30.483797891740366, 39.621714579092405),
+            Offset(23.719442126984134, 41.17597729654657),
+            Offset(17.808469902722187, 40.26388356552361),
+            Offset(13.571377486425117, 38.15001206462978),
+            Offset(10.73996518894534, 35.74540806580379),
+            Offset(8.908677157504997, 33.47648684184537),
+            Offset(7.746937054898215, 31.506333856475468),
+            Offset(7.021336564573183, 29.876779670456198),
+            Offset(6.575233382277915, 28.57846974488041),
+            Offset(6.306002369153553, 27.58317306758941),
+            Offset(6.147597126684428, 26.85785338221681),
+            Offset(6.058558318339831, 26.3706514388743),
+            Offset(6.013954859968965, 26.093012124442712),
+            Offset(6.000028695609693, 26.000194701816614),
+            Offset(6.0, 26.0),
+          ],
+          <Offset>[
+            Offset(12.562500000000004, 21.999999999999996),
+            Offset(12.563028263179644, 21.76974679627851),
+            Offset(12.601915221773575, 20.955868783506492),
+            Offset(12.859988895862408, 19.29992503795225),
+            Offset(13.868967368048864, 16.521093282496043),
+            Offset(17.119246052043927, 12.463158561907132),
+            Offset(22.085512547276693, 9.843640931525094),
+            Offset(27.97135871762438, 9.40112844480027),
+            Offset(33.018872763119475, 10.9710702242868),
+            Offset(36.55408111158449, 13.438130103560162),
+            Offset(38.838028231283474, 16.033718047868426),
+            Offset(40.24471329470705, 18.407336125221466),
+            Offset(41.07692592252046, 20.434907268146556),
+            Offset(41.54755362886814, 22.095337098743826),
+            Offset(41.7985009918588, 23.40952601819154),
+            Offset(41.921690170757245, 24.41237661448199),
+            Offset(41.975185653795926, 25.14085315878499),
+            Offset(41.994340767445394, 25.629108510947514),
+            Offset(41.999299914344064, 25.906972804802937),
+            Offset(41.99999903720365, 25.999805298117412),
+            Offset(42.0, 26.0),
+          ],
+          <Offset>[
+            Offset(12.562500000000004, 21.999999999999996),
+            Offset(12.563028263179644, 21.76974679627851),
+            Offset(12.601915221773575, 20.955868783506492),
+            Offset(12.859988895862408, 19.29992503795225),
+            Offset(13.868967368048864, 16.521093282496043),
+            Offset(17.119246052043927, 12.463158561907132),
+            Offset(22.085512547276693, 9.843640931525094),
+            Offset(27.97135871762438, 9.40112844480027),
+            Offset(33.018872763119475, 10.9710702242868),
+            Offset(36.55408111158449, 13.438130103560162),
+            Offset(38.838028231283474, 16.033718047868426),
+            Offset(40.24471329470705, 18.407336125221466),
+            Offset(41.07692592252046, 20.434907268146556),
+            Offset(41.54755362886814, 22.095337098743826),
+            Offset(41.7985009918588, 23.40952601819154),
+            Offset(41.921690170757245, 24.41237661448199),
+            Offset(41.975185653795926, 25.14085315878499),
+            Offset(41.994340767445394, 25.629108510947514),
+            Offset(41.999299914344064, 25.906972804802937),
+            Offset(41.99999903720365, 25.999805298117412),
+            Offset(42.0, 26.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.562500000000004, 21.999999999999996),
+            Offset(12.563028263179644, 21.76974679627851),
+            Offset(12.601915221773575, 20.955868783506492),
+            Offset(12.859988895862408, 19.29992503795225),
+            Offset(13.868967368048864, 16.521093282496043),
+            Offset(17.119246052043927, 12.463158561907132),
+            Offset(22.085512547276693, 9.843640931525094),
+            Offset(27.97135871762438, 9.40112844480027),
+            Offset(33.018872763119475, 10.9710702242868),
+            Offset(36.55408111158449, 13.438130103560162),
+            Offset(38.838028231283474, 16.033718047868426),
+            Offset(40.24471329470705, 18.407336125221466),
+            Offset(41.07692592252046, 20.434907268146556),
+            Offset(41.54755362886814, 22.095337098743826),
+            Offset(41.7985009918588, 23.40952601819154),
+            Offset(41.921690170757245, 24.41237661448199),
+            Offset(41.975185653795926, 25.14085315878499),
+            Offset(41.994340767445394, 25.629108510947514),
+            Offset(41.999299914344064, 25.906972804802937),
+            Offset(41.99999903720365, 25.999805298117412),
+            Offset(42.0, 26.0),
+          ],
+          <Offset>[
+            Offset(12.562500000000004, 25.999999999999996),
+            Offset(12.482656306166858, 25.76893925832956),
+            Offset(12.239876568700678, 24.939451092644983),
+            Offset(11.936115204605002, 23.191770024922107),
+            Offset(12.03457162794638, 20.07566671241613),
+            Offset(14.027204238592981, 15.000731698561715),
+            Offset(18.235691601462104, 10.929402730985391),
+            Offset(24.00669715972937, 8.870601601606145),
+            Offset(29.46892180503868, 9.127744915172523),
+            Offset(33.625033100689926, 10.714038007122415),
+            Offset(36.54081321903698, 12.759148887275034),
+            Offset(38.511185280577465, 14.802494849349817),
+            Offset(39.815969078409935, 16.638858290947915),
+            Offset(40.668101748647686, 18.193214036822702),
+            Offset(41.21772917473674, 19.451912583754474),
+            Offset(41.566980772751144, 20.42813499995029),
+            Offset(41.783709535999954, 21.145438675115766),
+            Offset(41.9118174384751, 21.629959864025817),
+            Offset(41.978620736948194, 21.907026258707322),
+            Offset(41.99995577009031, 21.99980529835142),
+            Offset(42.0, 22.0),
+          ],
+          <Offset>[
+            Offset(12.562500000000004, 25.999999999999996),
+            Offset(12.482656306166858, 25.76893925832956),
+            Offset(12.239876568700678, 24.939451092644983),
+            Offset(11.936115204605002, 23.191770024922107),
+            Offset(12.03457162794638, 20.07566671241613),
+            Offset(14.027204238592981, 15.000731698561715),
+            Offset(18.235691601462104, 10.929402730985391),
+            Offset(24.00669715972937, 8.870601601606145),
+            Offset(29.46892180503868, 9.127744915172523),
+            Offset(33.625033100689926, 10.714038007122415),
+            Offset(36.54081321903698, 12.759148887275034),
+            Offset(38.511185280577465, 14.802494849349817),
+            Offset(39.815969078409935, 16.638858290947915),
+            Offset(40.668101748647686, 18.193214036822702),
+            Offset(41.21772917473674, 19.451912583754474),
+            Offset(41.566980772751144, 20.42813499995029),
+            Offset(41.783709535999954, 21.145438675115766),
+            Offset(41.9118174384751, 21.629959864025817),
+            Offset(41.978620736948194, 21.907026258707322),
+            Offset(41.99995577009031, 21.99980529835142),
+            Offset(42.0, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.562500000000004, 25.999999999999996),
+            Offset(12.482656306166858, 25.76893925832956),
+            Offset(12.239876568700678, 24.939451092644983),
+            Offset(11.936115204605002, 23.191770024922107),
+            Offset(12.03457162794638, 20.07566671241613),
+            Offset(14.027204238592981, 15.000731698561715),
+            Offset(18.235691601462104, 10.929402730985391),
+            Offset(24.00669715972937, 8.870601601606145),
+            Offset(29.46892180503868, 9.127744915172523),
+            Offset(33.625033100689926, 10.714038007122415),
+            Offset(36.54081321903698, 12.759148887275034),
+            Offset(38.511185280577465, 14.802494849349817),
+            Offset(39.815969078409935, 16.638858290947915),
+            Offset(40.668101748647686, 18.193214036822702),
+            Offset(41.21772917473674, 19.451912583754474),
+            Offset(41.566980772751144, 20.42813499995029),
+            Offset(41.783709535999954, 21.145438675115766),
+            Offset(41.9118174384751, 21.629959864025817),
+            Offset(41.978620736948194, 21.907026258707322),
+            Offset(41.99995577009031, 21.99980529835142),
+            Offset(42.0, 22.0),
+          ],
+          <Offset>[
+            Offset(39.94921875, 26.0),
+            Offset(39.91892748707239, 26.320327277002704),
+            Offset(39.76165847505559, 27.44070450686089),
+            Offset(39.204067317080856, 29.66482937223119),
+            Offset(37.532335710606446, 33.23420478667988),
+            Offset(32.93796312691141, 38.04355807552189),
+            Offset(26.633976945925774, 40.7074763785527),
+            Offset(19.754780569089124, 40.645450453352446),
+            Offset(14.258518944641395, 38.42055825640933),
+            Offset(10.642329475530556, 35.42591996819203),
+            Offset(8.442750176698839, 32.47083890521039),
+            Offset(7.175149143375414, 29.871645565973722),
+            Offset(6.485980210787691, 27.710284879276827),
+            Offset(6.141884684352732, 25.97465660853507),
+            Offset(5.994461565155852, 24.620856310443347),
+            Offset(5.951292971147453, 23.598931453057713),
+            Offset(5.956121008888456, 22.862438898547587),
+            Offset(5.976034989369527, 22.3715027919526),
+            Offset(5.9932756825730955, 22.093065578347097),
+            Offset(5.999985428496359, 22.00019470205062),
+            Offset(6.0, 22.0),
+          ],
+          <Offset>[
+            Offset(39.94921875, 26.0),
+            Offset(39.91892748707239, 26.320327277002704),
+            Offset(39.76165847505559, 27.44070450686089),
+            Offset(39.204067317080856, 29.66482937223119),
+            Offset(37.532335710606446, 33.23420478667988),
+            Offset(32.93796312691141, 38.04355807552189),
+            Offset(26.633976945925774, 40.7074763785527),
+            Offset(19.754780569089124, 40.645450453352446),
+            Offset(14.258518944641395, 38.42055825640933),
+            Offset(10.642329475530556, 35.42591996819203),
+            Offset(8.442750176698839, 32.47083890521039),
+            Offset(7.175149143375414, 29.871645565973722),
+            Offset(6.485980210787691, 27.710284879276827),
+            Offset(6.141884684352732, 25.97465660853507),
+            Offset(5.994461565155852, 24.620856310443347),
+            Offset(5.951292971147453, 23.598931453057713),
+            Offset(5.956121008888456, 22.862438898547587),
+            Offset(5.976034989369527, 22.3715027919526),
+            Offset(5.9932756825730955, 22.093065578347097),
+            Offset(5.999985428496359, 22.00019470205062),
+            Offset(6.0, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(39.94921875, 26.0),
+            Offset(39.91892748707239, 26.320327277002704),
+            Offset(39.76165847505559, 27.44070450686089),
+            Offset(39.204067317080856, 29.66482937223119),
+            Offset(37.532335710606446, 33.23420478667988),
+            Offset(32.93796312691141, 38.04355807552189),
+            Offset(26.633976945925774, 40.7074763785527),
+            Offset(19.754780569089124, 40.645450453352446),
+            Offset(14.258518944641395, 38.42055825640933),
+            Offset(10.642329475530556, 35.42591996819203),
+            Offset(8.442750176698839, 32.47083890521039),
+            Offset(7.175149143375414, 29.871645565973722),
+            Offset(6.485980210787691, 27.710284879276827),
+            Offset(6.141884684352732, 25.97465660853507),
+            Offset(5.994461565155852, 24.620856310443347),
+            Offset(5.951292971147453, 23.598931453057713),
+            Offset(5.956121008888456, 22.862438898547587),
+            Offset(5.976034989369527, 22.3715027919526),
+            Offset(5.9932756825730955, 22.093065578347097),
+            Offset(5.999985428496359, 22.00019470205062),
+            Offset(6.0, 22.0),
+          ],
+          <Offset>[
+            Offset(39.94921875, 22.0),
+            Offset(39.999299444085175, 22.321134814951655),
+            Offset(40.123697128128484, 23.4571221977224),
+            Offset(40.127941008338254, 25.772984385261328),
+            Offset(39.36673145070893, 29.679631356759792),
+            Offset(36.03000494036236, 35.505984938867314),
+            Offset(30.483797891740366, 39.621714579092405),
+            Offset(23.719442126984134, 41.17597729654657),
+            Offset(17.808469902722187, 40.26388356552361),
+            Offset(13.571377486425117, 38.15001206462978),
+            Offset(10.73996518894534, 35.74540806580379),
+            Offset(8.908677157504997, 33.47648684184537),
+            Offset(7.746937054898215, 31.506333856475468),
+            Offset(7.021336564573183, 29.876779670456198),
+            Offset(6.575233382277915, 28.57846974488041),
+            Offset(6.306002369153553, 27.58317306758941),
+            Offset(6.147597126684428, 26.85785338221681),
+            Offset(6.058558318339831, 26.3706514388743),
+            Offset(6.013954859968965, 26.093012124442712),
+            Offset(6.000028695609693, 26.000194701816614),
+            Offset(6.0, 26.0),
+          ],
+          <Offset>[
+            Offset(39.94921875, 22.0),
+            Offset(39.999299444085175, 22.321134814951655),
+            Offset(40.123697128128484, 23.4571221977224),
+            Offset(40.127941008338254, 25.772984385261328),
+            Offset(39.36673145070893, 29.679631356759792),
+            Offset(36.03000494036236, 35.505984938867314),
+            Offset(30.483797891740366, 39.621714579092405),
+            Offset(23.719442126984134, 41.17597729654657),
+            Offset(17.808469902722187, 40.26388356552361),
+            Offset(13.571377486425117, 38.15001206462978),
+            Offset(10.73996518894534, 35.74540806580379),
+            Offset(8.908677157504997, 33.47648684184537),
+            Offset(7.746937054898215, 31.506333856475468),
+            Offset(7.021336564573183, 29.876779670456198),
+            Offset(6.575233382277915, 28.57846974488041),
+            Offset(6.306002369153553, 27.58317306758941),
+            Offset(6.147597126684428, 26.85785338221681),
+            Offset(6.058558318339831, 26.3706514388743),
+            Offset(6.013954859968965, 26.093012124442712),
+            Offset(6.000028695609693, 26.000194701816614),
+            Offset(6.0, 26.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(24.00120242935725, 7.98287044589657),
+            Offset(24.475307447859166, 8.003617986907148),
+            Offset(26.126659093516306, 8.177690670193112),
+            Offset(29.352785593371877, 8.997654274881697),
+            Offset(34.261760845645924, 11.73586028864448),
+            Offset(39.689530947675216, 19.233828052194387),
+            Offset(40.483245113546566, 28.57182115613015),
+            Offset(36.43819118187483, 37.011448735029944),
+            Offset(29.990575804517718, 41.869715613211284),
+            Offset(23.79684347484401, 43.53566841500789),
+            Offset(18.750267725016194, 43.37984126279926),
+            Offset(14.90588210305647, 42.37202895863352),
+            Offset(12.06653769423517, 41.068216372655385),
+            Offset(10.005013565343445, 39.76135295541911),
+            Offset(8.528791010746735, 38.595867902966916),
+            Offset(7.490777665658619, 37.63578444828991),
+            Offset(6.783719048719078, 36.90231589146764),
+            Offset(6.331695603851976, 36.39438332503497),
+            Offset(6.082250821137091, 36.09960487240796),
+            Offset(6.000171486902083, 36.000208945476956),
+            Offset(6.0, 36.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(24.00120242935725, 7.98287044589657),
+            Offset(24.475307447859166, 8.003617986907148),
+            Offset(26.126659093516306, 8.177690670193112),
+            Offset(29.352785593371877, 8.997654274881697),
+            Offset(34.261760845645924, 11.73586028864448),
+            Offset(39.689530947675216, 19.233828052194387),
+            Offset(40.483245113546566, 28.57182115613015),
+            Offset(36.43819118187483, 37.011448735029944),
+            Offset(29.990575804517718, 41.869715613211284),
+            Offset(23.79684347484401, 43.53566841500789),
+            Offset(18.750267725016194, 43.37984126279926),
+            Offset(14.90588210305647, 42.37202895863352),
+            Offset(12.06653769423517, 41.068216372655385),
+            Offset(10.005013565343445, 39.76135295541911),
+            Offset(8.528791010746735, 38.595867902966916),
+            Offset(7.490777665658619, 37.63578444828991),
+            Offset(6.783719048719078, 36.90231589146764),
+            Offset(6.331695603851976, 36.39438332503497),
+            Offset(6.082250821137091, 36.09960487240796),
+            Offset(6.000171486902083, 36.000208945476956),
+            Offset(6.0, 36.0),
+          ],
+          <Offset>[
+            Offset(8.389135783884633, 23.59493709136918),
+            Offset(8.411539419733579, 23.280020314446016),
+            Offset(8.535460203545142, 22.174793784450976),
+            Offset(9.004955277979905, 19.95721448439121),
+            Offset(10.506583157969082, 16.301391783224325),
+            Offset(15.00736732713968, 11.07994257565777),
+            Offset(21.799606746441338, 7.957602889938901),
+            Offset(29.78681308270101, 8.138822767869717),
+            Offset(36.3635225612393, 11.3666818389531),
+            Offset(40.53777507406921, 15.843550126760437),
+            Offset(42.771588169072636, 20.33326716415372),
+            Offset(43.71871364987625, 24.29568645796705),
+            Offset(43.90383296365319, 27.574365631376406),
+            Offset(43.68696452483883, 30.181901809762802),
+            Offset(43.29833346247236, 32.192150096307955),
+            Offset(42.87820947711742, 33.69183670134356),
+            Offset(42.5074211611244, 34.76136730547495),
+            Offset(42.22832709352211, 35.46839060016271),
+            Offset(42.058590816793185, 35.86711275140609),
+            Offset(42.00012355171139, 35.99972219110006),
+            Offset(42.0, 36.0),
+          ],
+          <Offset>[
+            Offset(8.389135783884633, 23.59493709136918),
+            Offset(8.411539419733579, 23.280020314446016),
+            Offset(8.535460203545142, 22.174793784450976),
+            Offset(9.004955277979905, 19.95721448439121),
+            Offset(10.506583157969082, 16.301391783224325),
+            Offset(15.00736732713968, 11.07994257565777),
+            Offset(21.799606746441338, 7.957602889938901),
+            Offset(29.78681308270101, 8.138822767869717),
+            Offset(36.3635225612393, 11.3666818389531),
+            Offset(40.53777507406921, 15.843550126760437),
+            Offset(42.771588169072636, 20.33326716415372),
+            Offset(43.71871364987625, 24.29568645796705),
+            Offset(43.90383296365319, 27.574365631376406),
+            Offset(43.68696452483883, 30.181901809762802),
+            Offset(43.29833346247236, 32.192150096307955),
+            Offset(42.87820947711742, 33.69183670134356),
+            Offset(42.5074211611244, 34.76136730547495),
+            Offset(42.22832709352211, 35.46839060016271),
+            Offset(42.058590816793185, 35.86711275140609),
+            Offset(42.00012355171139, 35.99972219110006),
+            Offset(42.0, 36.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(8.389135783884633, 23.59493709136918),
+            Offset(8.411539419733579, 23.280020314446016),
+            Offset(8.535460203545142, 22.174793784450976),
+            Offset(9.004955277979905, 19.95721448439121),
+            Offset(10.506583157969082, 16.301391783224325),
+            Offset(15.00736732713968, 11.07994257565777),
+            Offset(21.799606746441338, 7.957602889938901),
+            Offset(29.78681308270101, 8.138822767869717),
+            Offset(36.3635225612393, 11.3666818389531),
+            Offset(40.53777507406921, 15.843550126760437),
+            Offset(42.771588169072636, 20.33326716415372),
+            Offset(43.71871364987625, 24.29568645796705),
+            Offset(43.90383296365319, 27.574365631376406),
+            Offset(43.68696452483883, 30.181901809762802),
+            Offset(43.29833346247236, 32.192150096307955),
+            Offset(42.87820947711742, 33.69183670134356),
+            Offset(42.5074211611244, 34.76136730547495),
+            Offset(42.22832709352211, 35.46839060016271),
+            Offset(42.058590816793185, 35.86711275140609),
+            Offset(42.00012355171139, 35.99972219110006),
+            Offset(42.0, 36.0),
+          ],
+          <Offset>[
+            Offset(11.217562908630821, 26.423364216115367),
+            Offset(11.168037593988725, 26.178591999833156),
+            Offset(11.026001684399013, 25.304842207741935),
+            Offset(10.901761927954412, 23.478879507162752),
+            Offset(11.261530717110908, 20.229502548330856),
+            Offset(13.752640365544114, 14.878055298029915),
+            Offset(18.8358022450215, 10.643838709326973),
+            Offset(25.888907364460923, 9.03678212759111),
+            Offset(32.44806665065668, 10.54863232194106),
+            Offset(37.11467529624949, 13.774157000481484),
+            Offset(40.00232975553069, 17.44688398809633),
+            Offset(41.592943181014675, 20.907309278000795),
+            Offset(42.34289639575017, 23.891503017731534),
+            Offset(42.59272365615382, 26.334482295166104),
+            Offset(42.57381482854062, 28.258313197161566),
+            Offset(42.4351505629611, 29.71645007881405),
+            Offset(42.26812747456164, 30.76853140456803),
+            Offset(42.125177049114, 31.469720812803587),
+            Offset(42.03274190981708, 31.867196273027055),
+            Offset(42.00006946781973, 31.999722191465697),
+            Offset(42.0, 32.0),
+          ],
+          <Offset>[
+            Offset(11.217562908630821, 26.423364216115367),
+            Offset(11.168037593988725, 26.178591999833156),
+            Offset(11.026001684399013, 25.304842207741935),
+            Offset(10.901761927954412, 23.478879507162752),
+            Offset(11.261530717110908, 20.229502548330856),
+            Offset(13.752640365544114, 14.878055298029915),
+            Offset(18.8358022450215, 10.643838709326973),
+            Offset(25.888907364460923, 9.03678212759111),
+            Offset(32.44806665065668, 10.54863232194106),
+            Offset(37.11467529624949, 13.774157000481484),
+            Offset(40.00232975553069, 17.44688398809633),
+            Offset(41.592943181014675, 20.907309278000795),
+            Offset(42.34289639575017, 23.891503017731534),
+            Offset(42.59272365615382, 26.334482295166104),
+            Offset(42.57381482854062, 28.258313197161566),
+            Offset(42.4351505629611, 29.71645007881405),
+            Offset(42.26812747456164, 30.76853140456803),
+            Offset(42.125177049114, 31.469720812803587),
+            Offset(42.03274190981708, 31.867196273027055),
+            Offset(42.00006946781973, 31.999722191465697),
+            Offset(42.0, 32.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(11.217562908630821, 26.423364216115367),
+            Offset(11.168037593988725, 26.178591999833156),
+            Offset(11.026001684399013, 25.304842207741935),
+            Offset(10.901761927954412, 23.478879507162752),
+            Offset(11.261530717110908, 20.229502548330856),
+            Offset(13.752640365544114, 14.878055298029915),
+            Offset(18.8358022450215, 10.643838709326973),
+            Offset(25.888907364460923, 9.03678212759111),
+            Offset(32.44806665065668, 10.54863232194106),
+            Offset(37.11467529624949, 13.774157000481484),
+            Offset(40.00232975553069, 17.44688398809633),
+            Offset(41.592943181014675, 20.907309278000795),
+            Offset(42.34289639575017, 23.891503017731534),
+            Offset(42.59272365615382, 26.334482295166104),
+            Offset(42.57381482854062, 28.258313197161566),
+            Offset(42.4351505629611, 29.71645007881405),
+            Offset(42.26812747456164, 30.76853140456803),
+            Offset(42.125177049114, 31.469720812803587),
+            Offset(42.03274190981708, 31.867196273027055),
+            Offset(42.00006946781973, 31.999722191465697),
+            Offset(42.0, 32.0),
+          ],
+          <Offset>[
+            Offset(26.829629554103438, 10.811297570642758),
+            Offset(27.231805622114308, 10.902189672294288),
+            Offset(28.61720057437018, 11.307739093484066),
+            Offset(31.249592243346385, 12.519319297653237),
+            Offset(35.01670840478775, 15.663971053751007),
+            Offset(38.43480398607965, 23.031940774566532),
+            Offset(37.51944061212673, 31.258056975518222),
+            Offset(32.54028546363474, 37.909408094751335),
+            Offset(26.0751198939351, 41.05166609619924),
+            Offset(20.373743697024292, 41.46627528872894),
+            Offset(15.981009311474246, 40.49345808674188),
+            Offset(12.780111634194903, 38.983651778667266),
+            Offset(10.505601126332149, 37.38535375901051),
+            Offset(8.910772696658444, 35.91393344082242),
+            Offset(7.804272376814996, 34.66203100382052),
+            Offset(7.047718751502295, 33.6603978257604),
+            Offset(6.544425362156314, 32.90947999056071),
+            Offset(6.228545559443866, 32.39571353767585),
+            Offset(6.056401914160979, 32.099688394028924),
+            Offset(6.000117403010417, 32.00020894584259),
+            Offset(6.0, 32.0),
+          ],
+          <Offset>[
+            Offset(26.829629554103438, 10.811297570642758),
+            Offset(27.231805622114308, 10.902189672294288),
+            Offset(28.61720057437018, 11.307739093484066),
+            Offset(31.249592243346385, 12.519319297653237),
+            Offset(35.01670840478775, 15.663971053751007),
+            Offset(38.43480398607965, 23.031940774566532),
+            Offset(37.51944061212673, 31.258056975518222),
+            Offset(32.54028546363474, 37.909408094751335),
+            Offset(26.0751198939351, 41.05166609619924),
+            Offset(20.373743697024292, 41.46627528872894),
+            Offset(15.981009311474246, 40.49345808674188),
+            Offset(12.780111634194903, 38.983651778667266),
+            Offset(10.505601126332149, 37.38535375901051),
+            Offset(8.910772696658444, 35.91393344082242),
+            Offset(7.804272376814996, 34.66203100382052),
+            Offset(7.047718751502295, 33.6603978257604),
+            Offset(6.544425362156314, 32.90947999056071),
+            Offset(6.228545559443866, 32.39571353767585),
+            Offset(6.056401914160979, 32.099688394028924),
+            Offset(6.000117403010417, 32.00020894584259),
+            Offset(6.0, 32.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(26.829629554103438, 10.811297570642758),
+            Offset(27.231805622114308, 10.902189672294288),
+            Offset(28.61720057437018, 11.307739093484066),
+            Offset(31.249592243346385, 12.519319297653237),
+            Offset(35.01670840478775, 15.663971053751007),
+            Offset(38.43480398607965, 23.031940774566532),
+            Offset(37.51944061212673, 31.258056975518222),
+            Offset(32.54028546363474, 37.909408094751335),
+            Offset(26.0751198939351, 41.05166609619924),
+            Offset(20.373743697024292, 41.46627528872894),
+            Offset(15.981009311474246, 40.49345808674188),
+            Offset(12.780111634194903, 38.983651778667266),
+            Offset(10.505601126332149, 37.38535375901051),
+            Offset(8.910772696658444, 35.91393344082242),
+            Offset(7.804272376814996, 34.66203100382052),
+            Offset(7.047718751502295, 33.6603978257604),
+            Offset(6.544425362156314, 32.90947999056071),
+            Offset(6.228545559443866, 32.39571353767585),
+            Offset(6.056401914160979, 32.099688394028924),
+            Offset(6.000117403010417, 32.00020894584259),
+            Offset(6.0, 32.0),
+          ],
+          <Offset>[
+            Offset(24.00120242935725, 7.98287044589657),
+            Offset(24.475307447859166, 8.003617986907148),
+            Offset(26.126659093516306, 8.177690670193112),
+            Offset(29.352785593371877, 8.997654274881697),
+            Offset(34.261760845645924, 11.73586028864448),
+            Offset(39.689530947675216, 19.233828052194387),
+            Offset(40.483245113546566, 28.57182115613015),
+            Offset(36.43819118187483, 37.011448735029944),
+            Offset(29.990575804517718, 41.869715613211284),
+            Offset(23.79684347484401, 43.53566841500789),
+            Offset(18.750267725016194, 43.37984126279926),
+            Offset(14.90588210305647, 42.37202895863352),
+            Offset(12.06653769423517, 41.068216372655385),
+            Offset(10.005013565343445, 39.76135295541911),
+            Offset(8.528791010746735, 38.595867902966916),
+            Offset(7.490777665658619, 37.63578444828991),
+            Offset(6.783719048719078, 36.90231589146764),
+            Offset(6.331695603851976, 36.39438332503497),
+            Offset(6.082250821137091, 36.09960487240796),
+            Offset(6.000171486902083, 36.000208945476956),
+            Offset(6.0, 36.0),
+          ],
+          <Offset>[
+            Offset(24.00120242935725, 7.98287044589657),
+            Offset(24.475307447859166, 8.003617986907148),
+            Offset(26.126659093516306, 8.177690670193112),
+            Offset(29.352785593371877, 8.997654274881697),
+            Offset(34.261760845645924, 11.73586028864448),
+            Offset(39.689530947675216, 19.233828052194387),
+            Offset(40.483245113546566, 28.57182115613015),
+            Offset(36.43819118187483, 37.011448735029944),
+            Offset(29.990575804517718, 41.869715613211284),
+            Offset(23.79684347484401, 43.53566841500789),
+            Offset(18.750267725016194, 43.37984126279926),
+            Offset(14.90588210305647, 42.37202895863352),
+            Offset(12.06653769423517, 41.068216372655385),
+            Offset(10.005013565343445, 39.76135295541911),
+            Offset(8.528791010746735, 38.595867902966916),
+            Offset(7.490777665658619, 37.63578444828991),
+            Offset(6.783719048719078, 36.90231589146764),
+            Offset(6.331695603851976, 36.39438332503497),
+            Offset(6.082250821137091, 36.09960487240796),
+            Offset(6.000171486902083, 36.000208945476956),
+            Offset(6.0, 36.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(26.829629554103434, 37.188702429357235),
+            Offset(26.70295401712204, 37.21708146835027),
+            Offset(26.253437940457104, 37.31668297706097),
+            Offset(25.316159296509255, 37.51407939114749),
+            Offset(23.588416071597663, 37.80897683895165),
+            Offset(20.234451203252327, 37.9685842890323),
+            Offset(16.265106286835728, 37.25239920809595),
+            Offset(12.10312127594884, 35.1746313573166),
+            Offset(8.858593681675742, 32.111920483700914),
+            Offset(6.842177615689938, 28.881561047964333),
+            Offset(5.761956468124708, 25.926691322240227),
+            Offset(5.290469810431201, 23.40907221160189),
+            Offset(5.178751382636996, 21.349132840198116),
+            Offset(5.258916359304976, 19.710667345993283),
+            Offset(5.423821188616003, 18.440676377623248),
+            Offset(5.607765949538116, 17.48625665894383),
+            Offset(5.772423819137188, 16.80059820468561),
+            Offset(5.897326348355019, 16.34455713877282),
+            Offset(5.973614735709873, 16.086271507697752),
+            Offset(5.9999443342489265, 16.000180457509614),
+            Offset(6.0, 16.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(26.829629554103434, 37.188702429357235),
+            Offset(26.70295401712204, 37.21708146835027),
+            Offset(26.253437940457104, 37.31668297706097),
+            Offset(25.316159296509255, 37.51407939114749),
+            Offset(23.588416071597663, 37.80897683895165),
+            Offset(20.234451203252327, 37.9685842890323),
+            Offset(16.265106286835728, 37.25239920809595),
+            Offset(12.10312127594884, 35.1746313573166),
+            Offset(8.858593681675742, 32.111920483700914),
+            Offset(6.842177615689938, 28.881561047964333),
+            Offset(5.761956468124708, 25.926691322240227),
+            Offset(5.290469810431201, 23.40907221160189),
+            Offset(5.178751382636996, 21.349132840198116),
+            Offset(5.258916359304976, 19.710667345993283),
+            Offset(5.423821188616003, 18.440676377623248),
+            Offset(5.607765949538116, 17.48625665894383),
+            Offset(5.772423819137188, 16.80059820468561),
+            Offset(5.897326348355019, 16.34455713877282),
+            Offset(5.973614735709873, 16.086271507697752),
+            Offset(5.9999443342489265, 16.000180457509614),
+            Offset(6.0, 16.0),
+          ],
+          <Offset>[
+            Offset(11.2175487664952, 21.576649926020245),
+            Offset(11.26591566746795, 21.307620185014063),
+            Offset(11.473792301821605, 20.377637880770784),
+            Offset(12.06502589368821, 18.578567196580664),
+            Offset(13.54646415229777, 15.801892832848761),
+            Offset(17.052481673761243, 12.16993860231353),
+            Offset(21.42462731807053, 9.9137145270952),
+            Offset(26.111676903911107, 9.066591789474842),
+            Offset(30.142827978195022, 9.351628521416771),
+            Offset(33.249292834290536, 10.17924910714327),
+            Offset(35.60828563949723, 11.183386110908502),
+            Offset(37.40014550074941, 12.188459616498097),
+            Offset(38.76213503811523, 13.111795882844753),
+            Offset(39.793775377370764, 13.915564474413902),
+            Offset(40.567255045797005, 14.584805428587808),
+            Offset(41.13536943122808, 15.116774671440997),
+            Offset(41.537143495419784, 15.5155379179047),
+            Offset(41.801600251554405, 15.788922082526666),
+            Offset(41.95043550915973, 15.946775613712157),
+            Offset(41.99989640116428, 15.999888404883473),
+            Offset(42.0, 16.0),
+          ],
+          <Offset>[
+            Offset(11.2175487664952, 21.576649926020245),
+            Offset(11.26591566746795, 21.307620185014063),
+            Offset(11.473792301821605, 20.377637880770784),
+            Offset(12.06502589368821, 18.578567196580664),
+            Offset(13.54646415229777, 15.801892832848761),
+            Offset(17.052481673761243, 12.16993860231353),
+            Offset(21.42462731807053, 9.9137145270952),
+            Offset(26.111676903911107, 9.066591789474842),
+            Offset(30.142827978195022, 9.351628521416771),
+            Offset(33.249292834290536, 10.17924910714327),
+            Offset(35.60828563949723, 11.183386110908502),
+            Offset(37.40014550074941, 12.188459616498097),
+            Offset(38.76213503811523, 13.111795882844753),
+            Offset(39.793775377370764, 13.915564474413902),
+            Offset(40.567255045797005, 14.584805428587808),
+            Offset(41.13536943122808, 15.116774671440997),
+            Offset(41.537143495419784, 15.5155379179047),
+            Offset(41.801600251554405, 15.788922082526666),
+            Offset(41.95043550915973, 15.946775613712157),
+            Offset(41.99989640116428, 15.999888404883473),
+            Offset(42.0, 16.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(11.2175487664952, 21.576649926020245),
+            Offset(11.26591566746795, 21.307620185014063),
+            Offset(11.473792301821605, 20.377637880770784),
+            Offset(12.06502589368821, 18.578567196580664),
+            Offset(13.54646415229777, 15.801892832848761),
+            Offset(17.052481673761243, 12.16993860231353),
+            Offset(21.42462731807053, 9.9137145270952),
+            Offset(26.111676903911107, 9.066591789474842),
+            Offset(30.142827978195022, 9.351628521416771),
+            Offset(33.249292834290536, 10.17924910714327),
+            Offset(35.60828563949723, 11.183386110908502),
+            Offset(37.40014550074941, 12.188459616498097),
+            Offset(38.76213503811523, 13.111795882844753),
+            Offset(39.793775377370764, 13.915564474413902),
+            Offset(40.567255045797005, 14.584805428587808),
+            Offset(41.13536943122808, 15.116774671440997),
+            Offset(41.537143495419784, 15.5155379179047),
+            Offset(41.801600251554405, 15.788922082526666),
+            Offset(41.95043550915973, 15.946775613712157),
+            Offset(41.99989640116428, 15.999888404883473),
+            Offset(42.0, 16.0),
+          ],
+          <Offset>[
+            Offset(8.38912164174901, 24.405077050766437),
+            Offset(8.395184821848037, 24.09310118692493),
+            Offset(8.459782153583708, 23.007417876572518),
+            Offset(8.787794358833729, 20.871982437428983),
+            Offset(9.907414129760891, 17.4624092987335),
+            Offset(13.082563535366404, 12.659581303110395),
+            Offset(17.494014200747685, 9.171905574648836),
+            Offset(22.5870000408063, 7.175387672861925),
+            Offset(27.221252362148654, 6.6195238529014375),
+            Offset(30.937448027657968, 6.914992133489694),
+            Offset(33.83674155554175, 7.597074418012536),
+            Offset(36.08060605872299, 8.412374916582504),
+            Offset(37.809259485732085, 9.226949916571378),
+            Offset(39.13181277597566, 9.970719038360135),
+            Offset(40.13100060158848, 10.608666338979862),
+            Offset(40.8691843334897, 11.125641311887346),
+            Offset(41.393512389333864, 11.518117486505904),
+            Offset(41.739705833610245, 11.789400976065625),
+            Offset(41.934926095887384, 11.946805681562672),
+            Offset(41.99986395082928, 11.999888405015101),
+            Offset(42.0, 12.0),
+          ],
+          <Offset>[
+            Offset(8.38912164174901, 24.405077050766437),
+            Offset(8.395184821848037, 24.09310118692493),
+            Offset(8.459782153583708, 23.007417876572518),
+            Offset(8.787794358833729, 20.871982437428983),
+            Offset(9.907414129760891, 17.4624092987335),
+            Offset(13.082563535366404, 12.659581303110395),
+            Offset(17.494014200747685, 9.171905574648836),
+            Offset(22.5870000408063, 7.175387672861925),
+            Offset(27.221252362148654, 6.6195238529014375),
+            Offset(30.937448027657968, 6.914992133489694),
+            Offset(33.83674155554175, 7.597074418012536),
+            Offset(36.08060605872299, 8.412374916582504),
+            Offset(37.809259485732085, 9.226949916571378),
+            Offset(39.13181277597566, 9.970719038360135),
+            Offset(40.13100060158848, 10.608666338979862),
+            Offset(40.8691843334897, 11.125641311887346),
+            Offset(41.393512389333864, 11.518117486505904),
+            Offset(41.739705833610245, 11.789400976065625),
+            Offset(41.934926095887384, 11.946805681562672),
+            Offset(41.99986395082928, 11.999888405015101),
+            Offset(42.0, 12.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(8.38912164174901, 24.405077050766437),
+            Offset(8.395184821848037, 24.09310118692493),
+            Offset(8.459782153583708, 23.007417876572518),
+            Offset(8.787794358833729, 20.871982437428983),
+            Offset(9.907414129760891, 17.4624092987335),
+            Offset(13.082563535366404, 12.659581303110395),
+            Offset(17.494014200747685, 9.171905574648836),
+            Offset(22.5870000408063, 7.175387672861925),
+            Offset(27.221252362148654, 6.6195238529014375),
+            Offset(30.937448027657968, 6.914992133489694),
+            Offset(33.83674155554175, 7.597074418012536),
+            Offset(36.08060605872299, 8.412374916582504),
+            Offset(37.809259485732085, 9.226949916571378),
+            Offset(39.13181277597566, 9.970719038360135),
+            Offset(40.13100060158848, 10.608666338979862),
+            Offset(40.8691843334897, 11.125641311887346),
+            Offset(41.393512389333864, 11.518117486505904),
+            Offset(41.739705833610245, 11.789400976065625),
+            Offset(41.934926095887384, 11.946805681562672),
+            Offset(41.99986395082928, 11.999888405015101),
+            Offset(42.0, 12.0),
+          ],
+          <Offset>[
+            Offset(24.001202429357242, 40.01712955410343),
+            Offset(23.83222317150212, 40.00256247026114),
+            Offset(23.23942779221921, 39.946462972862705),
+            Offset(22.038927761654776, 39.80749463199581),
+            Offset(19.949366049060785, 39.46949330483639),
+            Offset(16.264533064857485, 38.458226989829164),
+            Offset(12.33449316951288, 36.51059025564959),
+            Offset(8.578444412844032, 33.28342724070369),
+            Offset(5.937018065629374, 29.37981581518558),
+            Offset(4.53033280905737, 25.617304074310752),
+            Offset(3.9904123841692254, 22.340379629344262),
+            Offset(3.9709303684047867, 19.632987511686302),
+            Offset(4.225875830253855, 17.46428687392474),
+            Offset(4.596953757909873, 15.765821909939516),
+            Offset(4.987566744407481, 14.464537288015299),
+            Offset(5.341580851799733, 13.495123299390182),
+            Offset(5.628792713051272, 12.803177773286812),
+            Offset(5.835431930410859, 12.34503603231178),
+            Offset(5.958105322437522, 12.08630157554827),
+            Offset(5.999911883913924, 12.000180457641243),
+            Offset(6.0, 12.0),
+          ],
+          <Offset>[
+            Offset(24.001202429357242, 40.01712955410343),
+            Offset(23.83222317150212, 40.00256247026114),
+            Offset(23.23942779221921, 39.946462972862705),
+            Offset(22.038927761654776, 39.80749463199581),
+            Offset(19.949366049060785, 39.46949330483639),
+            Offset(16.264533064857485, 38.458226989829164),
+            Offset(12.33449316951288, 36.51059025564959),
+            Offset(8.578444412844032, 33.28342724070369),
+            Offset(5.937018065629374, 29.37981581518558),
+            Offset(4.53033280905737, 25.617304074310752),
+            Offset(3.9904123841692254, 22.340379629344262),
+            Offset(3.9709303684047867, 19.632987511686302),
+            Offset(4.225875830253855, 17.46428687392474),
+            Offset(4.596953757909873, 15.765821909939516),
+            Offset(4.987566744407481, 14.464537288015299),
+            Offset(5.341580851799733, 13.495123299390182),
+            Offset(5.628792713051272, 12.803177773286812),
+            Offset(5.835431930410859, 12.34503603231178),
+            Offset(5.958105322437522, 12.08630157554827),
+            Offset(5.999911883913924, 12.000180457641243),
+            Offset(6.0, 12.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(24.001202429357242, 40.01712955410343),
+            Offset(23.83222317150212, 40.00256247026114),
+            Offset(23.23942779221921, 39.946462972862705),
+            Offset(22.038927761654776, 39.80749463199581),
+            Offset(19.949366049060785, 39.46949330483639),
+            Offset(16.264533064857485, 38.458226989829164),
+            Offset(12.33449316951288, 36.51059025564959),
+            Offset(8.578444412844032, 33.28342724070369),
+            Offset(5.937018065629374, 29.37981581518558),
+            Offset(4.53033280905737, 25.617304074310752),
+            Offset(3.9904123841692254, 22.340379629344262),
+            Offset(3.9709303684047867, 19.632987511686302),
+            Offset(4.225875830253855, 17.46428687392474),
+            Offset(4.596953757909873, 15.765821909939516),
+            Offset(4.987566744407481, 14.464537288015299),
+            Offset(5.341580851799733, 13.495123299390182),
+            Offset(5.628792713051272, 12.803177773286812),
+            Offset(5.835431930410859, 12.34503603231178),
+            Offset(5.958105322437522, 12.08630157554827),
+            Offset(5.999911883913924, 12.000180457641243),
+            Offset(6.0, 12.0),
+          ],
+          <Offset>[
+            Offset(26.829629554103434, 37.188702429357235),
+            Offset(26.70295401712204, 37.21708146835027),
+            Offset(26.253437940457104, 37.31668297706097),
+            Offset(25.316159296509255, 37.51407939114749),
+            Offset(23.588416071597663, 37.80897683895165),
+            Offset(20.234451203252327, 37.9685842890323),
+            Offset(16.265106286835728, 37.25239920809595),
+            Offset(12.10312127594884, 35.1746313573166),
+            Offset(8.858593681675742, 32.111920483700914),
+            Offset(6.842177615689938, 28.881561047964333),
+            Offset(5.761956468124708, 25.926691322240227),
+            Offset(5.290469810431201, 23.40907221160189),
+            Offset(5.178751382636996, 21.349132840198116),
+            Offset(5.258916359304976, 19.710667345993283),
+            Offset(5.423821188616003, 18.440676377623248),
+            Offset(5.607765949538116, 17.48625665894383),
+            Offset(5.772423819137188, 16.80059820468561),
+            Offset(5.897326348355019, 16.34455713877282),
+            Offset(5.973614735709873, 16.086271507697752),
+            Offset(5.9999443342489265, 16.000180457509614),
+            Offset(6.0, 16.0),
+          ],
+          <Offset>[
+            Offset(26.829629554103434, 37.188702429357235),
+            Offset(26.70295401712204, 37.21708146835027),
+            Offset(26.253437940457104, 37.31668297706097),
+            Offset(25.316159296509255, 37.51407939114749),
+            Offset(23.588416071597663, 37.80897683895165),
+            Offset(20.234451203252327, 37.9685842890323),
+            Offset(16.265106286835728, 37.25239920809595),
+            Offset(12.10312127594884, 35.1746313573166),
+            Offset(8.858593681675742, 32.111920483700914),
+            Offset(6.842177615689938, 28.881561047964333),
+            Offset(5.761956468124708, 25.926691322240227),
+            Offset(5.290469810431201, 23.40907221160189),
+            Offset(5.178751382636996, 21.349132840198116),
+            Offset(5.258916359304976, 19.710667345993283),
+            Offset(5.423821188616003, 18.440676377623248),
+            Offset(5.607765949538116, 17.48625665894383),
+            Offset(5.772423819137188, 16.80059820468561),
+            Offset(5.897326348355019, 16.34455713877282),
+            Offset(5.973614735709873, 16.086271507697752),
+            Offset(5.9999443342489265, 16.000180457509614),
+            Offset(6.0, 16.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+  ],
+  matchTextDirection: true,
+);
diff --git a/lib/src/material/animated_icons/data/close_menu.g.dart b/lib/src/material/animated_icons/data/close_menu.g.dart
new file mode 100644
index 0000000..a0b86c0
--- /dev/null
+++ b/lib/src/material/animated_icons/data/close_menu.g.dart
@@ -0,0 +1,1026 @@
+// Copyright 2014 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.
+
+// AUTOGENERATED FILE DO NOT EDIT!
+// This file was generated by vitool.
+part of material_animated_icons;
+const _AnimatedIconData _$close_menu = _AnimatedIconData(
+  Size(48.0, 48.0),
+  <_PathFrames>[
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(24.0, 26.0),
+            Offset(23.959814021491162, 25.999596231025475),
+            Offset(23.818980673456252, 25.99179115456858),
+            Offset(23.538063154364504, 25.945922493483316),
+            Offset(23.08280212993325, 25.77728671495204),
+            Offset(22.453979093274526, 25.26878656832729),
+            Offset(22.075089527092704, 24.54288089973015),
+            Offset(22.262051805115647, 21.908449654595582),
+            Offset(24.88919842700383, 17.94756272667581),
+            Offset(28.423231797685485, 16.307213722363326),
+            Offset(31.75801074104301, 16.114437816710378),
+            Offset(34.549940992662854, 16.70741310193702),
+            Offset(36.75456537179234, 17.655783514919893),
+            Offset(38.441715273516245, 18.694992104934485),
+            Offset(39.70097192738563, 19.67449366114299),
+            Offset(40.617793553920194, 20.51263931960471),
+            Offset(41.26084924113239, 21.170496215429807),
+            Offset(41.68247655138362, 21.634692364647904),
+            Offset(41.92023728365015, 21.90732809306482),
+            Offset(41.99983686928147, 21.999805299557877),
+            Offset(42.0, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(24.0, 26.0),
+            Offset(23.959814021491162, 25.999596231025475),
+            Offset(23.818980673456252, 25.99179115456858),
+            Offset(23.538063154364504, 25.945922493483316),
+            Offset(23.08280212993325, 25.77728671495204),
+            Offset(22.453979093274526, 25.26878656832729),
+            Offset(22.075089527092704, 24.54288089973015),
+            Offset(22.262051805115647, 21.908449654595582),
+            Offset(24.88919842700383, 17.94756272667581),
+            Offset(28.423231797685485, 16.307213722363326),
+            Offset(31.75801074104301, 16.114437816710378),
+            Offset(34.549940992662854, 16.70741310193702),
+            Offset(36.75456537179234, 17.655783514919893),
+            Offset(38.441715273516245, 18.694992104934485),
+            Offset(39.70097192738563, 19.67449366114299),
+            Offset(40.617793553920194, 20.51263931960471),
+            Offset(41.26084924113239, 21.170496215429807),
+            Offset(41.68247655138362, 21.634692364647904),
+            Offset(41.92023728365015, 21.90732809306482),
+            Offset(41.99983686928147, 21.999805299557877),
+            Offset(42.0, 22.0),
+          ],
+          <Offset>[
+            Offset(24.0, 26.0),
+            Offset(23.959814021491162, 25.999596231025475),
+            Offset(23.818980673456252, 25.99179115456858),
+            Offset(23.538063154364504, 25.945922493483316),
+            Offset(23.08280212993325, 25.77728671495204),
+            Offset(22.453979093274526, 25.26878656832729),
+            Offset(22.075089527092704, 24.54288089973015),
+            Offset(21.773286636989344, 25.561023502210293),
+            Offset(19.560850614934246, 28.209111964173573),
+            Offset(16.64772019146621, 28.96869418114919),
+            Offset(13.944774246803789, 28.61099302263078),
+            Offset(11.716530993310267, 27.687745622141946),
+            Offset(9.984477784090515, 26.54816750788367),
+            Offset(8.678832846158931, 25.4028848331679),
+            Offset(7.7182562555573995, 24.367892904410397),
+            Offset(7.027497048073013, 23.503119065863647),
+            Offset(6.547674641076508, 22.834089300900743),
+            Offset(6.235000119645385, 22.366158988430406),
+            Offset(6.059083538826545, 22.092725360840227),
+            Offset(6.000119863487498, 22.00019470067613),
+            Offset(5.9999999999999964, 22.000000000000007),
+          ],
+          <Offset>[
+            Offset(24.0, 26.0),
+            Offset(23.959814021491162, 25.999596231025475),
+            Offset(23.818980673456252, 25.99179115456858),
+            Offset(23.538063154364504, 25.945922493483316),
+            Offset(23.08280212993325, 25.77728671495204),
+            Offset(22.453979093274526, 25.26878656832729),
+            Offset(22.075089527092704, 24.54288089973015),
+            Offset(21.773286636989344, 25.561023502210293),
+            Offset(19.560850614934246, 28.209111964173573),
+            Offset(16.64772019146621, 28.96869418114919),
+            Offset(13.944774246803789, 28.61099302263078),
+            Offset(11.716530993310267, 27.687745622141946),
+            Offset(9.984477784090515, 26.54816750788367),
+            Offset(8.678832846158931, 25.4028848331679),
+            Offset(7.7182562555573995, 24.367892904410397),
+            Offset(7.027497048073013, 23.503119065863647),
+            Offset(6.547674641076508, 22.834089300900743),
+            Offset(6.235000119645385, 22.366158988430406),
+            Offset(6.059083538826545, 22.092725360840227),
+            Offset(6.000119863487498, 22.00019470067613),
+            Offset(5.9999999999999964, 22.000000000000007),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(24.0, 26.0),
+            Offset(23.959814021491162, 25.999596231025475),
+            Offset(23.818980673456252, 25.99179115456858),
+            Offset(23.538063154364504, 25.945922493483316),
+            Offset(23.08280212993325, 25.77728671495204),
+            Offset(22.453979093274526, 25.26878656832729),
+            Offset(22.075089527092704, 24.54288089973015),
+            Offset(21.773286636989344, 25.561023502210293),
+            Offset(19.560850614934246, 28.209111964173573),
+            Offset(16.64772019146621, 28.96869418114919),
+            Offset(13.944774246803789, 28.61099302263078),
+            Offset(11.716530993310267, 27.687745622141946),
+            Offset(9.984477784090515, 26.54816750788367),
+            Offset(8.678832846158931, 25.4028848331679),
+            Offset(7.7182562555573995, 24.367892904410397),
+            Offset(7.027497048073013, 23.503119065863647),
+            Offset(6.547674641076508, 22.834089300900743),
+            Offset(6.235000119645385, 22.366158988430406),
+            Offset(6.059083538826545, 22.092725360840227),
+            Offset(6.000119863487498, 22.00019470067613),
+            Offset(5.9999999999999964, 22.000000000000007),
+          ],
+          <Offset>[
+            Offset(24.0, 22.0),
+            Offset(24.040185978508838, 22.000403768974525),
+            Offset(24.181019326543748, 22.00820884543142),
+            Offset(24.461936845635496, 22.054077506516684),
+            Offset(24.91719787006675, 22.22271328504796),
+            Offset(25.546020906725474, 22.73121343167271),
+            Offset(25.924910472907296, 23.45711910026985),
+            Offset(25.737948194884353, 26.091550345404418),
+            Offset(23.110801573005386, 30.05243727330644),
+            Offset(19.576768202341754, 31.692786277607382),
+            Offset(16.24198925903886, 31.88556218323219),
+            Offset(13.450059007427265, 31.292586898019646),
+            Offset(11.245434628207665, 30.344216485080107),
+            Offset(9.558284726386193, 29.305007895087492),
+            Offset(8.29902807271331, 28.325506338842494),
+            Offset(7.38220644607981, 27.487360680395284),
+            Offset(6.7391507588675985, 26.8295037845702),
+            Offset(6.3175234486163845, 26.36530763535209),
+            Offset(6.079762716249856, 26.0926719069357),
+            Offset(6.000163130618532, 26.000194700442123),
+            Offset(5.9999999999999964, 26.000000000000007),
+          ],
+          <Offset>[
+            Offset(24.0, 22.0),
+            Offset(24.040185978508838, 22.000403768974525),
+            Offset(24.181019326543748, 22.00820884543142),
+            Offset(24.461936845635496, 22.054077506516684),
+            Offset(24.91719787006675, 22.22271328504796),
+            Offset(25.546020906725474, 22.73121343167271),
+            Offset(25.924910472907296, 23.45711910026985),
+            Offset(25.737948194884353, 26.091550345404418),
+            Offset(23.110801573005386, 30.05243727330644),
+            Offset(19.576768202341754, 31.692786277607382),
+            Offset(16.24198925903886, 31.88556218323219),
+            Offset(13.450059007427265, 31.292586898019646),
+            Offset(11.245434628207665, 30.344216485080107),
+            Offset(9.558284726386193, 29.305007895087492),
+            Offset(8.29902807271331, 28.325506338842494),
+            Offset(7.38220644607981, 27.487360680395284),
+            Offset(6.7391507588675985, 26.8295037845702),
+            Offset(6.3175234486163845, 26.36530763535209),
+            Offset(6.079762716249856, 26.0926719069357),
+            Offset(6.000163130618532, 26.000194700442123),
+            Offset(5.9999999999999964, 26.000000000000007),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(24.0, 22.0),
+            Offset(24.040185978508838, 22.000403768974525),
+            Offset(24.181019326543748, 22.00820884543142),
+            Offset(24.461936845635496, 22.054077506516684),
+            Offset(24.91719787006675, 22.22271328504796),
+            Offset(25.546020906725474, 22.73121343167271),
+            Offset(25.924910472907296, 23.45711910026985),
+            Offset(25.737948194884353, 26.091550345404418),
+            Offset(23.110801573005386, 30.05243727330644),
+            Offset(19.576768202341754, 31.692786277607382),
+            Offset(16.24198925903886, 31.88556218323219),
+            Offset(13.450059007427265, 31.292586898019646),
+            Offset(11.245434628207665, 30.344216485080107),
+            Offset(9.558284726386193, 29.305007895087492),
+            Offset(8.29902807271331, 28.325506338842494),
+            Offset(7.38220644607981, 27.487360680395284),
+            Offset(6.7391507588675985, 26.8295037845702),
+            Offset(6.3175234486163845, 26.36530763535209),
+            Offset(6.079762716249856, 26.0926719069357),
+            Offset(6.000163130618532, 26.000194700442123),
+            Offset(5.9999999999999964, 26.000000000000007),
+          ],
+          <Offset>[
+            Offset(24.0, 22.0),
+            Offset(24.040185978508838, 22.000403768974525),
+            Offset(24.181019326543748, 22.00820884543142),
+            Offset(24.461936845635496, 22.054077506516684),
+            Offset(24.91719787006675, 22.22271328504796),
+            Offset(25.546020906725474, 22.73121343167271),
+            Offset(25.924910472907296, 23.45711910026985),
+            Offset(26.226713363010656, 22.438976497789707),
+            Offset(28.439149385074973, 19.790888035808678),
+            Offset(31.352279808561033, 19.031305818821522),
+            Offset(34.05522575327808, 19.389006977311787),
+            Offset(36.28346900677985, 20.312254377814725),
+            Offset(38.015522215909485, 21.45183249211633),
+            Offset(39.32116715374351, 22.597115166854078),
+            Offset(40.281743744541544, 23.632107095575087),
+            Offset(40.972502951927, 24.496880934136346),
+            Offset(41.45232535892349, 25.165910699099264),
+            Offset(41.76499988035462, 25.633841011569586),
+            Offset(41.940916461073456, 25.907274639160292),
+            Offset(41.9998801364125, 25.99980529932387),
+            Offset(42.0, 26.0),
+          ],
+          <Offset>[
+            Offset(24.0, 22.0),
+            Offset(24.040185978508838, 22.000403768974525),
+            Offset(24.181019326543748, 22.00820884543142),
+            Offset(24.461936845635496, 22.054077506516684),
+            Offset(24.91719787006675, 22.22271328504796),
+            Offset(25.546020906725474, 22.73121343167271),
+            Offset(25.924910472907296, 23.45711910026985),
+            Offset(26.226713363010656, 22.438976497789707),
+            Offset(28.439149385074973, 19.790888035808678),
+            Offset(31.352279808561033, 19.031305818821522),
+            Offset(34.05522575327808, 19.389006977311787),
+            Offset(36.28346900677985, 20.312254377814725),
+            Offset(38.015522215909485, 21.45183249211633),
+            Offset(39.32116715374351, 22.597115166854078),
+            Offset(40.281743744541544, 23.632107095575087),
+            Offset(40.972502951927, 24.496880934136346),
+            Offset(41.45232535892349, 25.165910699099264),
+            Offset(41.76499988035462, 25.633841011569586),
+            Offset(41.940916461073456, 25.907274639160292),
+            Offset(41.9998801364125, 25.99980529932387),
+            Offset(42.0, 26.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(24.0, 22.0),
+            Offset(24.040185978508838, 22.000403768974525),
+            Offset(24.181019326543748, 22.00820884543142),
+            Offset(24.461936845635496, 22.054077506516684),
+            Offset(24.91719787006675, 22.22271328504796),
+            Offset(25.546020906725474, 22.73121343167271),
+            Offset(25.924910472907296, 23.45711910026985),
+            Offset(26.226713363010656, 22.438976497789707),
+            Offset(28.439149385074973, 19.790888035808678),
+            Offset(31.352279808561033, 19.031305818821522),
+            Offset(34.05522575327808, 19.389006977311787),
+            Offset(36.28346900677985, 20.312254377814725),
+            Offset(38.015522215909485, 21.45183249211633),
+            Offset(39.32116715374351, 22.597115166854078),
+            Offset(40.281743744541544, 23.632107095575087),
+            Offset(40.972502951927, 24.496880934136346),
+            Offset(41.45232535892349, 25.165910699099264),
+            Offset(41.76499988035462, 25.633841011569586),
+            Offset(41.940916461073456, 25.907274639160292),
+            Offset(41.9998801364125, 25.99980529932387),
+            Offset(42.0, 26.0),
+          ],
+          <Offset>[
+            Offset(24.0, 26.0),
+            Offset(23.959814021491162, 25.999596231025475),
+            Offset(23.818980673456252, 25.99179115456858),
+            Offset(23.538063154364504, 25.945922493483316),
+            Offset(23.08280212993325, 25.77728671495204),
+            Offset(22.453979093274526, 25.26878656832729),
+            Offset(22.075089527092704, 24.54288089973015),
+            Offset(22.262051805115647, 21.908449654595582),
+            Offset(24.88919842700383, 17.94756272667581),
+            Offset(28.423231797685485, 16.307213722363326),
+            Offset(31.75801074104301, 16.114437816710378),
+            Offset(34.549940992662854, 16.70741310193702),
+            Offset(36.75456537179234, 17.655783514919893),
+            Offset(38.441715273516245, 18.694992104934485),
+            Offset(39.70097192738563, 19.67449366114299),
+            Offset(40.617793553920194, 20.51263931960471),
+            Offset(41.26084924113239, 21.170496215429807),
+            Offset(41.68247655138362, 21.634692364647904),
+            Offset(41.92023728365015, 21.90732809306482),
+            Offset(41.99983686928147, 21.999805299557877),
+            Offset(42.0, 22.0),
+          ],
+          <Offset>[
+            Offset(24.0, 26.0),
+            Offset(23.959814021491162, 25.999596231025475),
+            Offset(23.818980673456252, 25.99179115456858),
+            Offset(23.538063154364504, 25.945922493483316),
+            Offset(23.08280212993325, 25.77728671495204),
+            Offset(22.453979093274526, 25.26878656832729),
+            Offset(22.075089527092704, 24.54288089973015),
+            Offset(22.262051805115647, 21.908449654595582),
+            Offset(24.88919842700383, 17.94756272667581),
+            Offset(28.423231797685485, 16.307213722363326),
+            Offset(31.75801074104301, 16.114437816710378),
+            Offset(34.549940992662854, 16.70741310193702),
+            Offset(36.75456537179234, 17.655783514919893),
+            Offset(38.441715273516245, 18.694992104934485),
+            Offset(39.70097192738563, 19.67449366114299),
+            Offset(40.617793553920194, 20.51263931960471),
+            Offset(41.26084924113239, 21.170496215429807),
+            Offset(41.68247655138362, 21.634692364647904),
+            Offset(41.92023728365015, 21.90732809306482),
+            Offset(41.99983686928147, 21.999805299557877),
+            Offset(42.0, 22.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(9.857864376269049, 12.68629150101524),
+            Offset(10.0286848752889, 12.538401058257541),
+            Offset(10.632873267418944, 12.039156238673812),
+            Offset(11.869661541953974, 11.120999908012887),
+            Offset(14.01269232753313, 9.802147761233421),
+            Offset(17.63755687713399, 8.1644208393319),
+            Offset(21.40270587697591, 7.061028351517436),
+            Offset(25.372566440386116, 6.474033586994366),
+            Offset(29.043281052671396, 6.480169426635232),
+            Offset(32.12573791953512, 6.935455570385773),
+            Offset(34.627918413389764, 7.642591727654583),
+            Offset(36.617140423267614, 8.447772996169093),
+            Offset(38.17478812826869, 9.24831138340156),
+            Offset(39.37733102863164, 9.981782438812019),
+            Offset(40.28991005711473, 10.613640055506202),
+            Offset(40.96529784286365, 11.127522886831306),
+            Offset(41.445182774971684, 11.518668960278076),
+            Offset(41.761944370115955, 11.789503608737085),
+            Offset(41.94049711880572, 11.94681212872769),
+            Offset(41.99987560677617, 11.999888404963674),
+            Offset(42.0, 12.000000000000002),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(9.857864376269049, 12.68629150101524),
+            Offset(10.0286848752889, 12.538401058257541),
+            Offset(10.632873267418944, 12.039156238673812),
+            Offset(11.869661541953974, 11.120999908012887),
+            Offset(14.01269232753313, 9.802147761233421),
+            Offset(17.63755687713399, 8.1644208393319),
+            Offset(21.40270587697591, 7.061028351517436),
+            Offset(25.372566440386116, 6.474033586994366),
+            Offset(29.043281052671396, 6.480169426635232),
+            Offset(32.12573791953512, 6.935455570385773),
+            Offset(34.627918413389764, 7.642591727654583),
+            Offset(36.617140423267614, 8.447772996169093),
+            Offset(38.17478812826869, 9.24831138340156),
+            Offset(39.37733102863164, 9.981782438812019),
+            Offset(40.28991005711473, 10.613640055506202),
+            Offset(40.96529784286365, 11.127522886831306),
+            Offset(41.445182774971684, 11.518668960278076),
+            Offset(41.761944370115955, 11.789503608737085),
+            Offset(41.94049711880572, 11.94681212872769),
+            Offset(41.99987560677617, 11.999888404963674),
+            Offset(42.0, 12.000000000000002),
+          ],
+          <Offset>[
+            Offset(35.31370849898477, 38.14213562373095),
+            Offset(35.09801389245515, 38.37497866886739),
+            Offset(34.30089322953512, 39.165247572901634),
+            Offset(32.51039870948586, 40.61608372177527),
+            Offset(28.957340520209982, 42.55359796419574),
+            Offset(22.04434118430577, 43.893684084885464),
+            Offset(14.726425304958642, 42.43654640742306),
+            Offset(8.351729390869856, 38.19612535493764),
+            Offset(4.454339035895707, 32.774349970923794),
+            Offset(2.747425156507667, 27.742058829873816),
+            Offset(2.3511131772704275, 23.586488483141284),
+            Offset(2.632378123985845, 20.323627974288197),
+            Offset(3.211174431823274, 17.82419135491087),
+            Offset(3.873722104158137, 15.939445851429909),
+            Offset(4.5046582506767905, 14.539930053688956),
+            Offset(5.04509760688121, 13.523188766482994),
+            Offset(5.468398892380936, 12.81134891500736),
+            Offset(5.766254411966681, 12.346553370240805),
+            Offset(5.940767729461317, 12.086396848425768),
+            Offset(5.999875607960821, 12.000180458137994),
+            Offset(5.9999999999999964, 12.000000000000005),
+          ],
+          <Offset>[
+            Offset(35.31370849898477, 38.14213562373095),
+            Offset(35.09801389245515, 38.37497866886739),
+            Offset(34.30089322953512, 39.165247572901634),
+            Offset(32.51039870948586, 40.61608372177527),
+            Offset(28.957340520209982, 42.55359796419574),
+            Offset(22.04434118430577, 43.893684084885464),
+            Offset(14.726425304958642, 42.43654640742306),
+            Offset(8.351729390869856, 38.19612535493764),
+            Offset(4.454339035895707, 32.774349970923794),
+            Offset(2.747425156507667, 27.742058829873816),
+            Offset(2.3511131772704275, 23.586488483141284),
+            Offset(2.632378123985845, 20.323627974288197),
+            Offset(3.211174431823274, 17.82419135491087),
+            Offset(3.873722104158137, 15.939445851429909),
+            Offset(4.5046582506767905, 14.539930053688956),
+            Offset(5.04509760688121, 13.523188766482994),
+            Offset(5.468398892380936, 12.81134891500736),
+            Offset(5.766254411966681, 12.346553370240805),
+            Offset(5.940767729461317, 12.086396848425768),
+            Offset(5.999875607960821, 12.000180458137994),
+            Offset(5.9999999999999964, 12.000000000000005),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(35.31370849898477, 38.14213562373095),
+            Offset(35.09801389245515, 38.37497866886739),
+            Offset(34.30089322953512, 39.165247572901634),
+            Offset(32.51039870948586, 40.61608372177527),
+            Offset(28.957340520209982, 42.55359796419574),
+            Offset(22.04434118430577, 43.893684084885464),
+            Offset(14.726425304958642, 42.43654640742306),
+            Offset(8.351729390869856, 38.19612535493764),
+            Offset(4.454339035895707, 32.774349970923794),
+            Offset(2.747425156507667, 27.742058829873816),
+            Offset(2.3511131772704275, 23.586488483141284),
+            Offset(2.632378123985845, 20.323627974288197),
+            Offset(3.211174431823274, 17.82419135491087),
+            Offset(3.873722104158137, 15.939445851429909),
+            Offset(4.5046582506767905, 14.539930053688956),
+            Offset(5.04509760688121, 13.523188766482994),
+            Offset(5.468398892380936, 12.81134891500736),
+            Offset(5.766254411966681, 12.346553370240805),
+            Offset(5.940767729461317, 12.086396848425768),
+            Offset(5.999875607960821, 12.000180458137994),
+            Offset(5.9999999999999964, 12.000000000000005),
+          ],
+          <Offset>[
+            Offset(38.14213562373095, 35.31370849898476),
+            Offset(37.96874473807846, 35.58949766696003),
+            Offset(37.314903377782656, 36.53546757711095),
+            Offset(35.78763024434835, 38.32266848093839),
+            Offset(32.59639054276135, 40.89308149834275),
+            Offset(26.01425932270061, 43.4040413840886),
+            Offset(18.657038422281488, 43.17835535986942),
+            Offset(11.876406253974665, 40.08732947155056),
+            Offset(7.375914651927772, 35.506454639454425),
+            Offset(5.059269963117448, 31.006315803543533),
+            Offset(4.122657261213394, 27.17280017604343),
+            Offset(3.9519175659990786, 24.099712674208394),
+            Offset(4.164049984213197, 21.709037321182585),
+            Offset(4.535684705560126, 19.884291287482522),
+            Offset(4.940912694919316, 18.516069143293173),
+            Offset(5.3112827046202895, 17.514322126036596),
+            Offset(5.612029998461967, 16.80876934640633),
+            Offset(5.828148829911537, 16.346074476701837),
+            Offset(5.9562771427611025, 16.086366780575148),
+            Offset(5.9999080583135225, 16.000180458006366),
+            Offset(5.9999999999999964, 16.000000000000007),
+          ],
+          <Offset>[
+            Offset(38.14213562373095, 35.31370849898476),
+            Offset(37.96874473807846, 35.58949766696003),
+            Offset(37.314903377782656, 36.53546757711095),
+            Offset(35.78763024434835, 38.32266848093839),
+            Offset(32.59639054276135, 40.89308149834275),
+            Offset(26.01425932270061, 43.4040413840886),
+            Offset(18.657038422281488, 43.17835535986942),
+            Offset(11.876406253974665, 40.08732947155056),
+            Offset(7.375914651927772, 35.506454639454425),
+            Offset(5.059269963117448, 31.006315803543533),
+            Offset(4.122657261213394, 27.17280017604343),
+            Offset(3.9519175659990786, 24.099712674208394),
+            Offset(4.164049984213197, 21.709037321182585),
+            Offset(4.535684705560126, 19.884291287482522),
+            Offset(4.940912694919316, 18.516069143293173),
+            Offset(5.3112827046202895, 17.514322126036596),
+            Offset(5.612029998461967, 16.80876934640633),
+            Offset(5.828148829911537, 16.346074476701837),
+            Offset(5.9562771427611025, 16.086366780575148),
+            Offset(5.9999080583135225, 16.000180458006366),
+            Offset(5.9999999999999964, 16.000000000000007),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.14213562373095, 35.31370849898476),
+            Offset(37.96874473807846, 35.58949766696003),
+            Offset(37.314903377782656, 36.53546757711095),
+            Offset(35.78763024434835, 38.32266848093839),
+            Offset(32.59639054276135, 40.89308149834275),
+            Offset(26.01425932270061, 43.4040413840886),
+            Offset(18.657038422281488, 43.17835535986942),
+            Offset(11.876406253974665, 40.08732947155056),
+            Offset(7.375914651927772, 35.506454639454425),
+            Offset(5.059269963117448, 31.006315803543533),
+            Offset(4.122657261213394, 27.17280017604343),
+            Offset(3.9519175659990786, 24.099712674208394),
+            Offset(4.164049984213197, 21.709037321182585),
+            Offset(4.535684705560126, 19.884291287482522),
+            Offset(4.940912694919316, 18.516069143293173),
+            Offset(5.3112827046202895, 17.514322126036596),
+            Offset(5.612029998461967, 16.80876934640633),
+            Offset(5.828148829911537, 16.346074476701837),
+            Offset(5.9562771427611025, 16.086366780575148),
+            Offset(5.9999080583135225, 16.000180458006366),
+            Offset(5.9999999999999964, 16.000000000000007),
+          ],
+          <Offset>[
+            Offset(12.68629150101524, 9.857864376269049),
+            Offset(12.899415720912216, 9.75292005635018),
+            Offset(13.64688341566648, 9.409376242883125),
+            Offset(15.146893076816461, 8.82758466717601),
+            Offset(17.651742350084497, 8.141631295380437),
+            Offset(21.607475015528827, 7.674778138535036),
+            Offset(25.333318994298757, 7.802837303963798),
+            Offset(28.897243303490924, 8.365237703607285),
+            Offset(31.96485666870346, 9.212274095165863),
+            Offset(34.437582726144896, 10.19971254405549),
+            Offset(36.399462497332735, 11.228903420556733),
+            Offset(37.93667986528085, 12.22385769608929),
+            Offset(39.127663680658614, 13.133157349673274),
+            Offset(40.039293630033626, 13.926627874864629),
+            Offset(40.72616450135726, 14.58977914511042),
+            Offset(41.231482940602724, 15.118656246384909),
+            Offset(41.588813881052715, 15.516089391677047),
+            Offset(41.82383878806081, 15.789024715198115),
+            Offset(41.956006532105505, 15.946782060877066),
+            Offset(41.99990805712887, 15.999888404832046),
+            Offset(42.0, 16.0),
+          ],
+          <Offset>[
+            Offset(12.68629150101524, 9.857864376269049),
+            Offset(12.899415720912216, 9.75292005635018),
+            Offset(13.64688341566648, 9.409376242883125),
+            Offset(15.146893076816461, 8.82758466717601),
+            Offset(17.651742350084497, 8.141631295380437),
+            Offset(21.607475015528827, 7.674778138535036),
+            Offset(25.333318994298757, 7.802837303963798),
+            Offset(28.897243303490924, 8.365237703607285),
+            Offset(31.96485666870346, 9.212274095165863),
+            Offset(34.437582726144896, 10.19971254405549),
+            Offset(36.399462497332735, 11.228903420556733),
+            Offset(37.93667986528085, 12.22385769608929),
+            Offset(39.127663680658614, 13.133157349673274),
+            Offset(40.039293630033626, 13.926627874864629),
+            Offset(40.72616450135726, 14.58977914511042),
+            Offset(41.231482940602724, 15.118656246384909),
+            Offset(41.588813881052715, 15.516089391677047),
+            Offset(41.82383878806081, 15.789024715198115),
+            Offset(41.956006532105505, 15.946782060877066),
+            Offset(41.99990805712887, 15.999888404832046),
+            Offset(42.0, 16.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.68629150101524, 9.857864376269049),
+            Offset(12.899415720912216, 9.75292005635018),
+            Offset(13.64688341566648, 9.409376242883125),
+            Offset(15.146893076816461, 8.82758466717601),
+            Offset(17.651742350084497, 8.141631295380437),
+            Offset(21.607475015528827, 7.674778138535036),
+            Offset(25.333318994298757, 7.802837303963798),
+            Offset(28.897243303490924, 8.365237703607285),
+            Offset(31.96485666870346, 9.212274095165863),
+            Offset(34.437582726144896, 10.19971254405549),
+            Offset(36.399462497332735, 11.228903420556733),
+            Offset(37.93667986528085, 12.22385769608929),
+            Offset(39.127663680658614, 13.133157349673274),
+            Offset(40.039293630033626, 13.926627874864629),
+            Offset(40.72616450135726, 14.58977914511042),
+            Offset(41.231482940602724, 15.118656246384909),
+            Offset(41.588813881052715, 15.516089391677047),
+            Offset(41.82383878806081, 15.789024715198115),
+            Offset(41.956006532105505, 15.946782060877066),
+            Offset(41.99990805712887, 15.999888404832046),
+            Offset(42.0, 16.0),
+          ],
+          <Offset>[
+            Offset(9.857864376269049, 12.68629150101524),
+            Offset(10.0286848752889, 12.538401058257541),
+            Offset(10.632873267418944, 12.039156238673812),
+            Offset(11.869661541953974, 11.120999908012887),
+            Offset(14.01269232753313, 9.802147761233421),
+            Offset(17.63755687713399, 8.1644208393319),
+            Offset(21.40270587697591, 7.061028351517436),
+            Offset(25.372566440386116, 6.474033586994366),
+            Offset(29.043281052671396, 6.480169426635232),
+            Offset(32.12573791953512, 6.935455570385773),
+            Offset(34.627918413389764, 7.642591727654583),
+            Offset(36.617140423267614, 8.447772996169093),
+            Offset(38.17478812826869, 9.24831138340156),
+            Offset(39.37733102863164, 9.981782438812019),
+            Offset(40.28991005711473, 10.613640055506202),
+            Offset(40.96529784286365, 11.127522886831306),
+            Offset(41.445182774971684, 11.518668960278076),
+            Offset(41.761944370115955, 11.789503608737085),
+            Offset(41.94049711880572, 11.94681212872769),
+            Offset(41.99987560677617, 11.999888404963674),
+            Offset(42.0, 12.000000000000002),
+          ],
+          <Offset>[
+            Offset(9.857864376269049, 12.68629150101524),
+            Offset(10.0286848752889, 12.538401058257541),
+            Offset(10.632873267418944, 12.039156238673812),
+            Offset(11.869661541953974, 11.120999908012887),
+            Offset(14.01269232753313, 9.802147761233421),
+            Offset(17.63755687713399, 8.1644208393319),
+            Offset(21.40270587697591, 7.061028351517436),
+            Offset(25.372566440386116, 6.474033586994366),
+            Offset(29.043281052671396, 6.480169426635232),
+            Offset(32.12573791953512, 6.935455570385773),
+            Offset(34.627918413389764, 7.642591727654583),
+            Offset(36.617140423267614, 8.447772996169093),
+            Offset(38.17478812826869, 9.24831138340156),
+            Offset(39.37733102863164, 9.981782438812019),
+            Offset(40.28991005711473, 10.613640055506202),
+            Offset(40.96529784286365, 11.127522886831306),
+            Offset(41.445182774971684, 11.518668960278076),
+            Offset(41.761944370115955, 11.789503608737085),
+            Offset(41.94049711880572, 11.94681212872769),
+            Offset(41.99987560677617, 11.999888404963674),
+            Offset(42.0, 12.000000000000002),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(12.686291501015244, 38.14213562373095),
+            Offset(12.335961696184835, 37.78957826421869),
+            Offset(11.186164512970027, 36.485148967548454),
+            Offset(9.272264829328229, 33.57462824174341),
+            Offset(7.396433901397504, 28.01370476875244),
+            Offset(8.455221168610253, 18.46855392229577),
+            Offset(14.400164412415048, 10.886305797611346),
+            Offset(23.385743674953297, 7.627722418507858),
+            Offset(31.513897019057687, 8.97811161083639),
+            Offset(37.00821523813391, 12.590468749760188),
+            Offset(40.228807248234226, 16.687449599224042),
+            Offset(41.900283080823314, 20.466101465000598),
+            Offset(42.622994421193994, 23.65567978529433),
+            Offset(42.809759514234806, 26.219169470442615),
+            Offset(42.724595353131164, 28.207893098186),
+            Offset(42.529420070562, 29.69761906859474),
+            Offset(42.319508324086705, 30.763041306693708),
+            Offset(42.1473924208978, 31.468700863761335),
+            Offset(42.038312568419684, 31.86713222464318),
+            Offset(42.00008112385512, 31.999722191105658),
+            Offset(42.0, 32.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.686291501015244, 38.14213562373095),
+            Offset(12.335961696184835, 37.78957826421869),
+            Offset(11.186164512970027, 36.485148967548454),
+            Offset(9.272264829328229, 33.57462824174341),
+            Offset(7.396433901397504, 28.01370476875244),
+            Offset(8.455221168610253, 18.46855392229577),
+            Offset(14.400164412415048, 10.886305797611346),
+            Offset(23.385743674953297, 7.627722418507858),
+            Offset(31.513897019057687, 8.97811161083639),
+            Offset(37.00821523813391, 12.590468749760188),
+            Offset(40.228807248234226, 16.687449599224042),
+            Offset(41.900283080823314, 20.466101465000598),
+            Offset(42.622994421193994, 23.65567978529433),
+            Offset(42.809759514234806, 26.219169470442615),
+            Offset(42.724595353131164, 28.207893098186),
+            Offset(42.529420070562, 29.69761906859474),
+            Offset(42.319508324086705, 30.763041306693708),
+            Offset(42.1473924208978, 31.468700863761335),
+            Offset(42.038312568419684, 31.86713222464318),
+            Offset(42.00008112385512, 31.999722191105658),
+            Offset(42.0, 32.0),
+          ],
+          <Offset>[
+            Offset(38.14213562373096, 12.686291501015244),
+            Offset(38.423106864699406, 12.98109469595424),
+            Offset(39.356600322670786, 14.070275639966862),
+            Offset(40.967250034331684, 16.50336839208347),
+            Offset(42.74943078741556, 21.21917673678452),
+            Offset(42.63823566995957, 29.761096576655866),
+            Offset(38.576286786907716, 37.560546310389874),
+            Offset(31.46737791244584, 42.70887388266861),
+            Offset(24.151451365764814, 44.21721480604141),
+            Offset(18.383677101408267, 43.39836675000767),
+            Offset(14.25135866363073, 41.61077532101089),
+            Offset(11.404888461060231, 39.59803568464825),
+            Offset(9.477230898414673, 37.70410889647938),
+            Offset(8.1829838828817, 36.06733728866804),
+            Offset(7.320063260869414, 34.72856080387442),
+            Offset(6.750940467797069, 33.68514929600791),
+            Offset(6.383985215921761, 32.91668448571468),
+            Offset(6.159364334665909, 32.39705126344063),
+            Offset(6.0390642630099585, 32.09977238767511),
+            Offset(6.000081127145819, 32.000208946289966),
+            Offset(5.9999999999999964, 32.00000000000001),
+          ],
+          <Offset>[
+            Offset(38.14213562373096, 12.686291501015244),
+            Offset(38.423106864699406, 12.98109469595424),
+            Offset(39.356600322670786, 14.070275639966862),
+            Offset(40.967250034331684, 16.50336839208347),
+            Offset(42.74943078741556, 21.21917673678452),
+            Offset(42.63823566995957, 29.761096576655866),
+            Offset(38.576286786907716, 37.560546310389874),
+            Offset(31.46737791244584, 42.70887388266861),
+            Offset(24.151451365764814, 44.21721480604141),
+            Offset(18.383677101408267, 43.39836675000767),
+            Offset(14.25135866363073, 41.61077532101089),
+            Offset(11.404888461060231, 39.59803568464825),
+            Offset(9.477230898414673, 37.70410889647938),
+            Offset(8.1829838828817, 36.06733728866804),
+            Offset(7.320063260869414, 34.72856080387442),
+            Offset(6.750940467797069, 33.68514929600791),
+            Offset(6.383985215921761, 32.91668448571468),
+            Offset(6.159364334665909, 32.39705126344063),
+            Offset(6.0390642630099585, 32.09977238767511),
+            Offset(6.000081127145819, 32.000208946289966),
+            Offset(5.9999999999999964, 32.00000000000001),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.14213562373096, 12.686291501015244),
+            Offset(38.423106864699406, 12.98109469595424),
+            Offset(39.356600322670786, 14.070275639966862),
+            Offset(40.967250034331684, 16.50336839208347),
+            Offset(42.74943078741556, 21.21917673678452),
+            Offset(42.63823566995957, 29.761096576655866),
+            Offset(38.576286786907716, 37.560546310389874),
+            Offset(31.46737791244584, 42.70887388266861),
+            Offset(24.151451365764814, 44.21721480604141),
+            Offset(18.383677101408267, 43.39836675000767),
+            Offset(14.25135866363073, 41.61077532101089),
+            Offset(11.404888461060231, 39.59803568464825),
+            Offset(9.477230898414673, 37.70410889647938),
+            Offset(8.1829838828817, 36.06733728866804),
+            Offset(7.320063260869414, 34.72856080387442),
+            Offset(6.750940467797069, 33.68514929600791),
+            Offset(6.383985215921761, 32.91668448571468),
+            Offset(6.159364334665909, 32.39705126344063),
+            Offset(6.0390642630099585, 32.09977238767511),
+            Offset(6.000081127145819, 32.000208946289966),
+            Offset(5.9999999999999964, 32.00000000000001),
+          ],
+          <Offset>[
+            Offset(35.31370849898477, 9.857864376269053),
+            Offset(35.6666086904478, 10.082523010563733),
+            Offset(36.86605884182839, 10.940227216666777),
+            Offset(39.07044338436947, 12.981703369305308),
+            Offset(41.99448322830801, 17.2910659716714),
+            Offset(43.892962631555136, 25.96298385428372),
+            Offset(41.540091288327545, 34.8743104910018),
+            Offset(35.36528363068592, 41.81091452294722),
+            Offset(28.066907276343148, 45.035264323073946),
+            Offset(21.806776879213544, 45.46775987631051),
+            Offset(17.0206170771626, 44.49715849707795),
+            Offset(13.530658929909972, 42.986412864621926),
+            Offset(11.038167466324122, 41.386971510121526),
+            Offset(9.277224751573414, 39.91475680326283),
+            Offset(8.044581894834794, 38.66239770301462),
+            Offset(7.193999381954086, 37.66053591853735),
+            Offset(6.6232789024796475, 36.90952038662189),
+            Offset(6.2625143790747195, 36.39572105079973),
+            Offset(6.064913170013508, 36.09968886605397),
+            Offset(6.000135211055188, 36.00020894592433),
+            Offset(5.9999999999999964, 36.00000000000001),
+          ],
+          <Offset>[
+            Offset(35.31370849898477, 9.857864376269053),
+            Offset(35.6666086904478, 10.082523010563733),
+            Offset(36.86605884182839, 10.940227216666777),
+            Offset(39.07044338436947, 12.981703369305308),
+            Offset(41.99448322830801, 17.2910659716714),
+            Offset(43.892962631555136, 25.96298385428372),
+            Offset(41.540091288327545, 34.8743104910018),
+            Offset(35.36528363068592, 41.81091452294722),
+            Offset(28.066907276343148, 45.035264323073946),
+            Offset(21.806776879213544, 45.46775987631051),
+            Offset(17.0206170771626, 44.49715849707795),
+            Offset(13.530658929909972, 42.986412864621926),
+            Offset(11.038167466324122, 41.386971510121526),
+            Offset(9.277224751573414, 39.91475680326283),
+            Offset(8.044581894834794, 38.66239770301462),
+            Offset(7.193999381954086, 37.66053591853735),
+            Offset(6.6232789024796475, 36.90952038662189),
+            Offset(6.2625143790747195, 36.39572105079973),
+            Offset(6.064913170013508, 36.09968886605397),
+            Offset(6.000135211055188, 36.00020894592433),
+            Offset(5.9999999999999964, 36.00000000000001),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(35.31370849898477, 9.857864376269053),
+            Offset(35.6666086904478, 10.082523010563733),
+            Offset(36.86605884182839, 10.940227216666777),
+            Offset(39.07044338436947, 12.981703369305308),
+            Offset(41.99448322830801, 17.2910659716714),
+            Offset(43.892962631555136, 25.96298385428372),
+            Offset(41.540091288327545, 34.8743104910018),
+            Offset(35.36528363068592, 41.81091452294722),
+            Offset(28.066907276343148, 45.035264323073946),
+            Offset(21.806776879213544, 45.46775987631051),
+            Offset(17.0206170771626, 44.49715849707795),
+            Offset(13.530658929909972, 42.986412864621926),
+            Offset(11.038167466324122, 41.386971510121526),
+            Offset(9.277224751573414, 39.91475680326283),
+            Offset(8.044581894834794, 38.66239770301462),
+            Offset(7.193999381954086, 37.66053591853735),
+            Offset(6.6232789024796475, 36.90952038662189),
+            Offset(6.2625143790747195, 36.39572105079973),
+            Offset(6.064913170013508, 36.09968886605397),
+            Offset(6.000135211055188, 36.00020894592433),
+            Offset(5.9999999999999964, 36.00000000000001),
+          ],
+          <Offset>[
+            Offset(9.857864376269053, 35.31370849898477),
+            Offset(9.579463521933231, 34.89100657882818),
+            Offset(8.695623032127628, 33.35510054424837),
+            Offset(7.375458179366014, 30.052963218965253),
+            Offset(6.641486342289959, 24.085594003639322),
+            Offset(9.70994813020582, 14.670441199923623),
+            Offset(17.36396891383488, 8.200069978223272),
+            Offset(27.283649393193382, 6.729763058786467),
+            Offset(35.429352929636025, 9.796161127868931),
+            Offset(40.431315015939184, 14.659861876063037),
+            Offset(42.99806566176609, 19.573832775291095),
+            Offset(44.02605354967305, 23.854478644974275),
+            Offset(44.183930989103445, 27.338542398936475),
+            Offset(43.90400038292652, 30.066588985037402),
+            Offset(43.44911398709654, 32.141729997326195),
+            Offset(42.97247898471902, 33.673005691124175),
+            Offset(42.55880201064459, 34.75587720760092),
+            Offset(42.25054246530661, 35.46737065112043),
+            Offset(42.064161475423234, 35.867048703022036),
+            Offset(42.00013520776449, 35.99972219074003),
+            Offset(42.0, 36.0),
+          ],
+          <Offset>[
+            Offset(9.857864376269053, 35.31370849898477),
+            Offset(9.579463521933231, 34.89100657882818),
+            Offset(8.695623032127628, 33.35510054424837),
+            Offset(7.375458179366014, 30.052963218965253),
+            Offset(6.641486342289959, 24.085594003639322),
+            Offset(9.70994813020582, 14.670441199923623),
+            Offset(17.36396891383488, 8.200069978223272),
+            Offset(27.283649393193382, 6.729763058786467),
+            Offset(35.429352929636025, 9.796161127868931),
+            Offset(40.431315015939184, 14.659861876063037),
+            Offset(42.99806566176609, 19.573832775291095),
+            Offset(44.02605354967305, 23.854478644974275),
+            Offset(44.183930989103445, 27.338542398936475),
+            Offset(43.90400038292652, 30.066588985037402),
+            Offset(43.44911398709654, 32.141729997326195),
+            Offset(42.97247898471902, 33.673005691124175),
+            Offset(42.55880201064459, 34.75587720760092),
+            Offset(42.25054246530661, 35.46737065112043),
+            Offset(42.064161475423234, 35.867048703022036),
+            Offset(42.00013520776449, 35.99972219074003),
+            Offset(42.0, 36.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(9.857864376269053, 35.31370849898477),
+            Offset(9.579463521933231, 34.89100657882818),
+            Offset(8.695623032127628, 33.35510054424837),
+            Offset(7.375458179366014, 30.052963218965253),
+            Offset(6.641486342289959, 24.085594003639322),
+            Offset(9.70994813020582, 14.670441199923623),
+            Offset(17.36396891383488, 8.200069978223272),
+            Offset(27.283649393193382, 6.729763058786467),
+            Offset(35.429352929636025, 9.796161127868931),
+            Offset(40.431315015939184, 14.659861876063037),
+            Offset(42.99806566176609, 19.573832775291095),
+            Offset(44.02605354967305, 23.854478644974275),
+            Offset(44.183930989103445, 27.338542398936475),
+            Offset(43.90400038292652, 30.066588985037402),
+            Offset(43.44911398709654, 32.141729997326195),
+            Offset(42.97247898471902, 33.673005691124175),
+            Offset(42.55880201064459, 34.75587720760092),
+            Offset(42.25054246530661, 35.46737065112043),
+            Offset(42.064161475423234, 35.867048703022036),
+            Offset(42.00013520776449, 35.99972219074003),
+            Offset(42.0, 36.0),
+          ],
+          <Offset>[
+            Offset(12.686291501015244, 38.14213562373095),
+            Offset(12.335961696184835, 37.78957826421869),
+            Offset(11.186164512970027, 36.485148967548454),
+            Offset(9.272264829328229, 33.57462824174341),
+            Offset(7.396433901397504, 28.01370476875244),
+            Offset(8.455221168610253, 18.46855392229577),
+            Offset(14.400164412415048, 10.886305797611346),
+            Offset(23.385743674953297, 7.627722418507858),
+            Offset(31.513897019057687, 8.97811161083639),
+            Offset(37.00821523813391, 12.590468749760188),
+            Offset(40.228807248234226, 16.687449599224042),
+            Offset(41.900283080823314, 20.466101465000598),
+            Offset(42.622994421193994, 23.65567978529433),
+            Offset(42.809759514234806, 26.219169470442615),
+            Offset(42.724595353131164, 28.207893098186),
+            Offset(42.529420070562, 29.69761906859474),
+            Offset(42.319508324086705, 30.763041306693708),
+            Offset(42.1473924208978, 31.468700863761335),
+            Offset(42.038312568419684, 31.86713222464318),
+            Offset(42.00008112385512, 31.999722191105658),
+            Offset(42.0, 32.0),
+          ],
+          <Offset>[
+            Offset(12.686291501015244, 38.14213562373095),
+            Offset(12.335961696184835, 37.78957826421869),
+            Offset(11.186164512970027, 36.485148967548454),
+            Offset(9.272264829328229, 33.57462824174341),
+            Offset(7.396433901397504, 28.01370476875244),
+            Offset(8.455221168610253, 18.46855392229577),
+            Offset(14.400164412415048, 10.886305797611346),
+            Offset(23.385743674953297, 7.627722418507858),
+            Offset(31.513897019057687, 8.97811161083639),
+            Offset(37.00821523813391, 12.590468749760188),
+            Offset(40.228807248234226, 16.687449599224042),
+            Offset(41.900283080823314, 20.466101465000598),
+            Offset(42.622994421193994, 23.65567978529433),
+            Offset(42.809759514234806, 26.219169470442615),
+            Offset(42.724595353131164, 28.207893098186),
+            Offset(42.529420070562, 29.69761906859474),
+            Offset(42.319508324086705, 30.763041306693708),
+            Offset(42.1473924208978, 31.468700863761335),
+            Offset(42.038312568419684, 31.86713222464318),
+            Offset(42.00008112385512, 31.999722191105658),
+            Offset(42.0, 32.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+  ],
+);
diff --git a/lib/src/material/animated_icons/data/ellipsis_search.g.dart b/lib/src/material/animated_icons/data/ellipsis_search.g.dart
new file mode 100644
index 0000000..ef8802d
--- /dev/null
+++ b/lib/src/material/animated_icons/data/ellipsis_search.g.dart
@@ -0,0 +1,5267 @@
+// Copyright 2014 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.
+
+// AUTOGENERATED FILE DO NOT EDIT!
+// This file was generated by vitool.
+part of material_animated_icons;
+const _AnimatedIconData _$ellipsis_search = _AnimatedIconData(
+  Size(96.0, 96.0),
+  <_PathFrames>[
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(57.62369742449878, 56.510753652468935),
+            Offset(57.50386046799878, 56.38647829016894),
+            Offset(57.38402351149878, 56.26220292786894),
+            Offset(57.26418655499878, 56.13792756556894),
+            Offset(57.144349598398776, 56.01365220316894),
+            Offset(57.02451264189878, 55.88937684086894),
+            Offset(56.90467568539878, 55.76510147856894),
+            Offset(56.78483872889878, 55.64082611626894),
+            Offset(56.66500177229878, 55.516550753968936),
+            Offset(56.54516481579878, 55.39227539166894),
+            Offset(56.42532785929878, 55.26800002926894),
+            Offset(56.30549090279878, 55.143724666968936),
+            Offset(56.18565394629878, 55.01944930466894),
+            Offset(56.06581698969878, 54.895173942368935),
+            Offset(55.94598003319878, 54.77089858006894),
+            Offset(55.89372930109525, 54.63379537519879),
+            Offset(55.99467401074788, 54.46761572737509),
+            Offset(56.09561872053984, 54.30143607948509),
+            Offset(56.19656343029247, 54.13525643166141),
+            Offset(56.297508139945094, 53.969076783837714),
+            Offset(56.398452849697726, 53.80289713591403),
+            Offset(56.49939755945036, 53.63671748809034),
+            Offset(56.297960835674125, 53.25914221256317),
+            Offset(55.78881270857159, 52.69340023656423),
+            Offset(56.006714995568615, 52.88005225271881),
+            Offset(56.182356492271566, 53.03050427381422),
+            Offset(56.32344115337003, 53.15135539844181),
+            Offset(56.43452394733299, 53.246507348148306),
+            Offset(56.52071628832435, 53.320338487990085),
+            Offset(56.5859271296418, 53.37619717205798),
+            Offset(56.63368824673307, 53.417108669232874),
+            Offset(56.66699682004896, 53.44564032188472),
+            Offset(56.68847407317482, 53.46403743305161),
+            Offset(56.70040259814858, 53.474255238779065),
+            Offset(56.70477862272339, 53.478003679517734),
+            Offset(56.70335264548873, 53.47678220759329),
+            Offset(56.69766279856795, 53.471908365221054),
+            Offset(56.689065722138324, 53.46454423118308),
+            Offset(56.678761173675056, 53.45571750075846),
+            Offset(56.667817449874256, 53.44634326178278),
+            Offset(56.657150392580974, 53.43720601142315),
+            Offset(56.64761589840118, 53.429038898603),
+            Offset(56.639944407998925, 53.42246760846087),
+            Offset(56.63479944561546, 53.418060506474255),
+            Offset(56.632765532511705, 53.416318285285435),
+            Offset(56.63275314854877, 53.416307677358),
+            Offset(56.63275314854877, 53.416307677358),
+            Offset(56.63275314854877, 53.416307677358),
+            Offset(56.63275314854877, 53.416307677358),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(59.11722254046772, 54.78101039685442),
+            Offset(58.99738558396772, 54.65673503455442),
+            Offset(58.877548627467725, 54.532459672254426),
+            Offset(58.75771167096772, 54.40818430995442),
+            Offset(58.63787471436772, 54.28390894755442),
+            Offset(58.518037757867724, 54.159633585254426),
+            Offset(58.39820080136772, 54.03535822295442),
+            Offset(58.27836384486772, 53.911082860654425),
+            Offset(58.15852688826772, 53.78680749835442),
+            Offset(58.03868993176772, 53.66253213605442),
+            Offset(57.918852975267725, 53.538256773654425),
+            Offset(57.79901601876772, 53.41398141135442),
+            Offset(57.67917906226772, 53.28970604905442),
+            Offset(57.559342105667724, 53.16543068675442),
+            Offset(57.43950514916772, 53.04115532445442),
+            Offset(57.485490128564514, 52.790279302768965),
+            Offset(57.907338162449705, 52.252441786684095),
+            Offset(58.32918619648207, 51.71460427052383),
+            Offset(58.75103423046726, 51.176766754438965),
+            Offset(59.17288226435245, 50.63892923835408),
+            Offset(59.59473029833764, 50.101091722169215),
+            Offset(60.01657833232283, 49.56325420608434),
+            Offset(60.01932238869081, 48.94920461646341),
+            Offset(59.563472601893324, 48.32173455588634),
+            Offset(59.82481548777557, 48.45807534383447),
+            Offset(60.035472549991965, 48.56797368582478),
+            Offset(60.204683589459826, 48.656249922511655),
+            Offset(60.33791164477868, 48.72575408359739),
+            Offset(60.441287132232546, 48.77968436513692),
+            Offset(60.51949828685408, 48.820486587135186),
+            Offset(60.57678097234404, 48.850370570581376),
+            Offset(60.61672988153154, 48.871211642026296),
+            Offset(60.64248880031156, 48.88464989297123),
+            Offset(60.65679537406957, 48.892113534204164),
+            Offset(60.66204379484951, 48.89485159926458),
+            Offset(60.66033353741787, 48.89395936971321),
+            Offset(60.65350937308716, 48.890399250068874),
+            Offset(60.643198399323495, 48.885020085900294),
+            Offset(60.630839555044176, 48.87857256175406),
+            Offset(60.617714110608446, 48.87172510757334),
+            Offset(60.60492048840596, 48.86505076277184),
+            Offset(60.593485215014766, 48.85908505921234),
+            Offset(60.58428435040535, 48.85428503136227),
+            Offset(60.57811369779046, 48.85106584428997),
+            Offset(60.575674307513765, 48.84979323114302),
+            Offset(60.57565945470678, 48.849785482535665),
+            Offset(60.57565945470678, 48.849785482535665),
+            Offset(60.57565945470678, 48.849785482535665),
+            Offset(60.57565945470678, 48.849785482535665),
+          ],
+          <Offset>[
+            Offset(60.01637695941208, 52.53311272229554),
+            Offset(59.896540002912076, 52.408837359995545),
+            Offset(59.77670304641208, 52.28456199769555),
+            Offset(59.65686608991208, 52.160286635395536),
+            Offset(59.537029133312075, 52.03601127299554),
+            Offset(59.41719217681208, 51.91173591069554),
+            Offset(59.29735522031208, 51.78746054839554),
+            Offset(59.177518263812075, 51.663185186095546),
+            Offset(59.05768130721208, 51.538909823795535),
+            Offset(58.937844350712076, 51.41463446149554),
+            Offset(58.81800739421208, 51.29035909909554),
+            Offset(58.69817043771208, 51.16608373679554),
+            Offset(58.57833348121208, 51.041808374495545),
+            Offset(58.45849652461208, 50.917533012195534),
+            Offset(58.33865956811208, 50.79325764989554),
+            Offset(58.44378588577579, 50.39452751777087),
+            Offset(59.058828957998486, 49.37369990758731),
+            Offset(59.673872030373076, 48.35287229731656),
+            Offset(60.28891510269577, 47.332044687133006),
+            Offset(60.90395817491846, 46.31121707694944),
+            Offset(61.51900124724116, 45.290389466665886),
+            Offset(62.13404431956385, 44.26956185648232),
+            Offset(62.259712355426515, 43.348200728562944),
+            Offset(61.83595003585357, 42.64051158499319),
+            Offset(62.12344568343471, 42.711470130505994),
+            Offset(62.35518334223874, 42.76866670842857),
+            Offset(62.54132744639319, 42.81461006443314),
+            Offset(62.68788772451427, 42.85078349611087),
+            Offset(62.80160806784497, 42.87885150418633),
+            Offset(62.88764586631069, 42.90008701536577),
+            Offset(62.95066086939573, 42.91564013069819),
+            Offset(62.9946074933042, 42.92648686364521),
+            Offset(63.02294412474014, 42.933480799617264),
+            Offset(63.038682367138975, 42.93736525073486),
+            Offset(63.04445600098332, 42.9387902763426),
+            Offset(63.042574596918385, 42.938325915587605),
+            Offset(63.03506753378623, 42.936473051777625),
+            Offset(63.02372473381152, 42.93367346647948),
+            Offset(63.010129131515164, 42.93031785336868),
+            Offset(62.99569021485936, 42.92675409672298),
+            Offset(62.981616326115095, 42.92328043483138),
+            Offset(62.969036717078595, 42.92017558518285),
+            Offset(62.958915115642, 42.91767741130702),
+            Offset(62.952126961590935, 42.916001985810205),
+            Offset(62.94944345985745, 42.91533965446192),
+            Offset(62.94942712071989, 42.915335621700216),
+            Offset(62.94942712071989, 42.915335621700216),
+            Offset(62.94942712071989, 42.915335621700216),
+            Offset(62.94942712071989, 42.915335621700216),
+          ],
+          <Offset>[
+            Offset(60.01637695941208, 50.07184435071208),
+            Offset(59.896540002912076, 49.94756898841208),
+            Offset(59.77670304641208, 49.82329362611208),
+            Offset(59.65686608991208, 49.69901826381208),
+            Offset(59.537029133312075, 49.57474290141208),
+            Offset(59.41719217681208, 49.45046753911208),
+            Offset(59.29735522031208, 49.32619217681208),
+            Offset(59.177518263812075, 49.20191681451208),
+            Offset(59.05768130721208, 49.07764145221208),
+            Offset(58.937844350712076, 48.95336608991208),
+            Offset(58.81800739421208, 48.82909072751208),
+            Offset(58.69817043771208, 48.70481536521208),
+            Offset(58.57833348121208, 48.58054000291208),
+            Offset(58.45849652461208, 48.456264640612076),
+            Offset(58.33865956811208, 48.33198927831208),
+            Offset(58.44378588577579, 47.77137070715779),
+            Offset(59.058828957998486, 46.22170752948048),
+            Offset(59.673872030373076, 44.67204435170307),
+            Offset(60.28891510269577, 43.12238117402577),
+            Offset(60.90395817491846, 41.57271799634846),
+            Offset(61.51900124724116, 40.023054818571154),
+            Offset(62.13404431956385, 38.47339164089385),
+            Offset(62.259712355426515, 37.21554892931651),
+            Offset(61.83595003585357, 36.42002629848357),
+            Offset(62.12344568343471, 36.4193965118447),
+            Offset(62.35518334223874, 36.41888886839874),
+            Offset(62.54132744639319, 36.41848110198319),
+            Offset(62.68788772451427, 36.41816004771427),
+            Offset(62.80160806784497, 36.41791093245497),
+            Offset(62.88764586631069, 36.4177224584507),
+            Offset(62.95066086939573, 36.417584418075734),
+            Offset(62.9946074933042, 36.4174881488042),
+            Offset(63.02294412474014, 36.41742607471014),
+            Offset(63.038682367138975, 36.41739159858898),
+            Offset(63.04445600098332, 36.417378950893315),
+            Offset(63.042574596918385, 36.41738307228838),
+            Offset(63.03506753378623, 36.41739951722624),
+            Offset(63.02372473381152, 36.417424364711515),
+            Offset(63.010129131515164, 36.417454147175164),
+            Offset(62.99569021485936, 36.41748577699936),
+            Offset(62.981616326115095, 36.41751660719509),
+            Offset(62.969036717078595, 36.4175441640286),
+            Offset(62.958915115642, 36.41756633636199),
+            Offset(62.952126961590935, 36.41758120646094),
+            Offset(62.94944345985745, 36.41758708492745),
+            Offset(62.94942712071989, 36.41758712071989),
+            Offset(62.94942712071989, 36.41758712071989),
+            Offset(62.94942712071989, 36.41758712071989),
+            Offset(62.94942712071989, 36.41758712071989),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(60.01637695941208, 44.60068993176772),
+            Offset(59.896540002912076, 44.47641456946772),
+            Offset(59.77670304641208, 44.352139207167724),
+            Offset(59.65686608991208, 44.22786384486772),
+            Offset(59.537029133312075, 44.10358848246772),
+            Offset(59.41719217681208, 43.979313120167724),
+            Offset(59.29735522031208, 43.85503775786772),
+            Offset(59.177518263812075, 43.73076239556772),
+            Offset(59.05768130721208, 43.60648703326772),
+            Offset(58.937844350712076, 43.48221167096772),
+            Offset(58.81800739421208, 43.35793630856772),
+            Offset(58.69817043771208, 43.23366094626772),
+            Offset(58.57833348121208, 43.10938558396772),
+            Offset(58.45849652461208, 42.98511022166772),
+            Offset(58.33865956811208, 42.86083485936772),
+            Offset(58.44378588577579, 41.94035441423051),
+            Offset(59.058828957998486, 39.2151424482157),
+            Offset(59.673872030373076, 36.48993048207206),
+            Offset(60.28891510269577, 33.76471851605726),
+            Offset(60.90395817491846, 31.039506550042443),
+            Offset(61.51900124724116, 28.314294583927637),
+            Offset(62.13404431956385, 25.58908261791283),
+            Offset(62.259712355426515, 23.5832748633608),
+            Offset(61.83595003585357, 22.59250713958332),
+            Offset(62.12344568343471, 22.432743621605564),
+            Offset(62.35518334223874, 22.303965242071957),
+            Offset(62.54132744639319, 22.20052354762982),
+            Offset(62.68788772451427, 22.11907888637867),
+            Offset(62.80160806784497, 22.05588362666254),
+            Offset(62.88764586631069, 22.00807176767408),
+            Offset(62.95066086939573, 21.973053851184037),
+            Offset(62.9946074933042, 21.948632378031544),
+            Offset(63.02294412474014, 21.932885496421555),
+            Offset(63.038682367138975, 21.92413963541956),
+            Offset(63.04445600098332, 21.920931183179505),
+            Offset(63.042574596918385, 21.92197669372786),
+            Offset(63.03506753378623, 21.926148425807156),
+            Offset(63.02372473381152, 21.93245170602349),
+            Offset(63.010129131515164, 21.940006885624168),
+            Offset(62.99569021485936, 21.94803070142844),
+            Offset(62.981616326115095, 21.955851668445952),
+            Offset(62.969036717078595, 21.962842252864764),
+            Offset(62.958915115642, 21.968466903765343),
+            Offset(62.952126961590935, 21.972239132600446),
+            Offset(62.94944345985745, 21.973730374923758),
+            Offset(62.94942712071989, 21.97373945470677),
+            Offset(62.94942712071989, 21.97373945470677),
+            Offset(62.94942712071989, 21.97373945470677),
+            Offset(62.94942712071989, 21.97373945470677),
+          ],
+          <Offset>[
+            Offset(55.58154300555442, 40.16584435071208),
+            Offset(55.46170604905442, 40.04156898841208),
+            Offset(55.341869092554425, 39.917293626112084),
+            Offset(55.22203213605442, 39.79301826381208),
+            Offset(55.10219517945442, 39.66874290141208),
+            Offset(54.982358222954424, 39.544467539112084),
+            Offset(54.86252126645442, 39.42019217681208),
+            Offset(54.74268430995442, 39.29591681451208),
+            Offset(54.62284735335442, 39.17164145221208),
+            Offset(54.50301039685442, 39.04736608991208),
+            Offset(54.383173440354426, 38.92309072751208),
+            Offset(54.263336483854424, 38.79881536521208),
+            Offset(54.14349952735442, 38.67454000291208),
+            Offset(54.023662570754425, 38.55026464061208),
+            Offset(53.90382561425442, 38.42598927831208),
+            Offset(53.717253409954964, 37.21380954643979),
+            Offset(53.3794146437701, 33.53571324376249),
+            Offset(53.04157587771383, 29.857616940933074),
+            Offset(52.70373711162896, 26.17952063825577),
+            Offset(52.36589834544409, 22.50142433557846),
+            Offset(52.028059579359216, 18.823328032801157),
+            Offset(51.69022081327434, 15.145231730123854),
+            Offset(51.209599844133415, 12.533133381006508),
+            Offset(50.627574843376344, 11.384102561113572),
+            Offset(50.786079126264475, 11.095347340254705),
+            Offset(50.91384249150479, 10.86259439455874),
+            Offset(51.01646887208166, 10.675634757573192),
+            Offset(51.09727159719739, 10.528432370914274),
+            Offset(51.159968760166926, 10.414213797064974),
+            Offset(51.207403772355185, 10.327799050590698),
+            Offset(51.24214568222138, 10.264507966755737),
+            Offset(51.266374668526296, 10.220368804304208),
+            Offset(51.281997435281234, 10.191908024680146),
+            Offset(51.29067436255417, 10.176100830038976),
+            Offset(51.29385752619458, 10.170301900803324),
+            Offset(51.29282025622321, 10.172191547658386),
+            Offset(51.288681405188875, 10.179731500666236),
+            Offset(51.282427806600296, 10.19112399561152),
+            Offset(51.27493217593407, 10.204779162835159),
+            Offset(51.26697160279334, 10.219281339139357),
+            Offset(51.25921227961184, 10.233416888275087),
+            Offset(51.25227679406234, 10.246051610978604),
+            Offset(51.24669647592227, 10.256217557081996),
+            Offset(51.242953979299976, 10.263035451330936),
+            Offset(51.24147449075302, 10.265730709997449),
+            Offset(51.241465482535666, 10.265747120719887),
+            Offset(51.241465482535666, 10.265747120719887),
+            Offset(51.241465482535666, 10.265747120719887),
+            Offset(51.241465482535666, 10.265747120719887),
+          ],
+          <Offset>[
+            Offset(50.11037695941208, 40.16584435071208),
+            Offset(49.99054000291208, 40.04156898841208),
+            Offset(49.87070304641208, 39.917293626112084),
+            Offset(49.75086608991208, 39.79301826381208),
+            Offset(49.631029133312076, 39.66874290141208),
+            Offset(49.51119217681208, 39.544467539112084),
+            Offset(49.39135522031208, 39.42019217681208),
+            Offset(49.27151826381208, 39.29591681451208),
+            Offset(49.15168130721208, 39.17164145221208),
+            Offset(49.03184435071208, 39.04736608991208),
+            Offset(48.91200739421208, 38.92309072751208),
+            Offset(48.79217043771208, 38.79881536521208),
+            Offset(48.67233348121208, 38.67454000291208),
+            Offset(48.55249652461208, 38.55026464061208),
+            Offset(48.43265956811208, 38.42598927831208),
+            Offset(47.886224725057794, 37.21380954643979),
+            Offset(46.37283467228049, 33.53571324376249),
+            Offset(44.859444619603074, 29.857616940933074),
+            Offset(43.346054566925766, 26.17952063825577),
+            Offset(41.83266451414846, 22.50142433557846),
+            Offset(40.31927446147115, 18.823328032801157),
+            Offset(38.80588440879385, 15.145231730123854),
+            Offset(37.57729680711651, 12.533133381006508),
+            Offset(36.80002629848357, 11.384102561113572),
+            Offset(36.799396511844705, 11.095347340254705),
+            Offset(36.79888886839874, 10.86259439455874),
+            Offset(36.798481101983185, 10.675634757573192),
+            Offset(36.798160047714276, 10.528432370914274),
+            Offset(36.79791093245497, 10.414213797064974),
+            Offset(36.79772245845069, 10.327799050590698),
+            Offset(36.79758441807573, 10.264507966755737),
+            Offset(36.797488148804206, 10.220368804304208),
+            Offset(36.797426074710145, 10.191908024680146),
+            Offset(36.79739159858897, 10.176100830038976),
+            Offset(36.79737895089332, 10.170301900803324),
+            Offset(36.79738307228838, 10.172191547658386),
+            Offset(36.79739951722624, 10.179731500666236),
+            Offset(36.79742436471152, 10.19112399561152),
+            Offset(36.79745414717516, 10.204779162835159),
+            Offset(36.79748577699936, 10.219281339139357),
+            Offset(36.79751660719509, 10.233416888275087),
+            Offset(36.7975441640286, 10.246051610978604),
+            Offset(36.797566336361996, 10.256217557081996),
+            Offset(36.79758120646093, 10.263035451330936),
+            Offset(36.79758708492745, 10.265730709997449),
+            Offset(36.79758712071989, 10.265747120719887),
+            Offset(36.79758712071989, 10.265747120719887),
+            Offset(36.79758712071989, 10.265747120719887),
+            Offset(36.79758712071989, 10.265747120719887),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(44.63922254046772, 40.16584435071208),
+            Offset(44.51938558396772, 40.04156898841208),
+            Offset(44.39954862746772, 39.917293626112084),
+            Offset(44.27971167096772, 39.79301826381208),
+            Offset(44.15987471436772, 39.66874290141208),
+            Offset(44.04003775786772, 39.544467539112084),
+            Offset(43.92020080136772, 39.42019217681208),
+            Offset(43.80036384486772, 39.29591681451208),
+            Offset(43.68052688826772, 39.17164145221208),
+            Offset(43.56068993176772, 39.04736608991208),
+            Offset(43.440852975267724, 38.92309072751208),
+            Offset(43.32101601876772, 38.79881536521208),
+            Offset(43.20117906226772, 38.67454000291208),
+            Offset(43.08134210566772, 38.55026464061208),
+            Offset(42.96150514916772, 38.42598927831208),
+            Offset(42.05520843213051, 37.21380954643979),
+            Offset(39.36626959101571, 33.53571324376249),
+            Offset(36.677330749972064, 29.857616940933074),
+            Offset(33.988391908957254, 26.17952063825577),
+            Offset(31.299453067842446, 22.50142433557846),
+            Offset(28.610514226827636, 18.823328032801157),
+            Offset(25.92157538581283, 15.145231730123854),
+            Offset(23.945022741160805, 12.533133381006508),
+            Offset(22.97250713958332, 11.384102561113572),
+            Offset(22.812743621605566, 11.095347340254705),
+            Offset(22.68396524207196, 10.86259439455874),
+            Offset(22.580523547629824, 10.675634757573192),
+            Offset(22.499078886378673, 10.528432370914274),
+            Offset(22.43588362666254, 10.414213797064974),
+            Offset(22.38807176767408, 10.327799050590698),
+            Offset(22.35305385118404, 10.264507966755737),
+            Offset(22.328632378031546, 10.220368804304208),
+            Offset(22.312885496421558, 10.191908024680146),
+            Offset(22.304139635419563, 10.176100830038976),
+            Offset(22.300931183179507, 10.170301900803324),
+            Offset(22.301976693727863, 10.172191547658386),
+            Offset(22.30614842580716, 10.179731500666236),
+            Offset(22.31245170602349, 10.19112399561152),
+            Offset(22.32000688562417, 10.204779162835159),
+            Offset(22.328030701428442, 10.219281339139357),
+            Offset(22.335851668445954, 10.233416888275087),
+            Offset(22.342842252864767, 10.246051610978604),
+            Offset(22.348466903765345, 10.256217557081996),
+            Offset(22.35223913260045, 10.263035451330936),
+            Offset(22.35373037492376, 10.265730709997449),
+            Offset(22.353739454706773, 10.265747120719887),
+            Offset(22.353739454706773, 10.265747120719887),
+            Offset(22.353739454706773, 10.265747120719887),
+            Offset(22.353739454706773, 10.265747120719887),
+          ],
+          <Offset>[
+            Offset(40.20437695941208, 44.60068993176772),
+            Offset(40.08454000291208, 44.47641456946772),
+            Offset(39.96470304641208, 44.352139207167724),
+            Offset(39.84486608991208, 44.22786384486772),
+            Offset(39.72502913331208, 44.10358848246772),
+            Offset(39.60519217681208, 43.979313120167724),
+            Offset(39.48535522031208, 43.85503775786772),
+            Offset(39.36551826381208, 43.73076239556772),
+            Offset(39.24568130721208, 43.60648703326772),
+            Offset(39.12584435071208, 43.48221167096772),
+            Offset(39.006007394212084, 43.35793630856772),
+            Offset(38.88617043771208, 43.23366094626772),
+            Offset(38.76633348121208, 43.10938558396772),
+            Offset(38.64649652461208, 42.98511022166772),
+            Offset(38.52665956811208, 42.86083485936772),
+            Offset(37.32866356433979, 41.94035441423051),
+            Offset(33.686840386562494, 39.2151424482157),
+            Offset(30.045017208833077, 36.48993048207207),
+            Offset(26.403194031155767, 33.764718516057265),
+            Offset(22.761370853378462, 31.03950655004245),
+            Offset(19.119547675701156, 28.314294583927644),
+            Offset(15.477724498023854, 25.589082617912837),
+            Offset(12.894881258806514, 23.583274863360806),
+            Offset(11.764102561113575, 22.592507139583326),
+            Offset(11.475347340254707, 22.43274362160557),
+            Offset(11.242594394558743, 22.303965242071964),
+            Offset(11.055634757573195, 22.200523547629828),
+            Offset(10.908432370914277, 22.119078886378677),
+            Offset(10.794213797064977, 22.055883626662546),
+            Offset(10.707799050590701, 22.008071767674085),
+            Offset(10.64450796675574, 21.973053851184044),
+            Offset(10.60036880430421, 21.94863237803155),
+            Offset(10.571908024680148, 21.932885496421562),
+            Offset(10.556100830038979, 21.924139635419568),
+            Offset(10.550301900803326, 21.920931183179516),
+            Offset(10.552191547658389, 21.921976693727867),
+            Offset(10.559731500666238, 21.926148425807163),
+            Offset(10.571123995611522, 21.932451706023496),
+            Offset(10.584779162835162, 21.94000688562418),
+            Offset(10.59928133913936, 21.948030701428447),
+            Offset(10.61341688827509, 21.95585166844596),
+            Offset(10.626051610978607, 21.96284225286477),
+            Offset(10.636217557081999, 21.96846690376535),
+            Offset(10.643035451330938, 21.972239132600453),
+            Offset(10.645730709997451, 21.973730374923765),
+            Offset(10.64574712071989, 21.973739454706777),
+            Offset(10.64574712071989, 21.973739454706777),
+            Offset(10.64574712071989, 21.973739454706777),
+            Offset(10.64574712071989, 21.973739454706777),
+          ],
+          <Offset>[
+            Offset(40.20437695941208, 50.07184435071208),
+            Offset(40.08454000291208, 49.94756898841208),
+            Offset(39.96470304641208, 49.82329362611208),
+            Offset(39.84486608991208, 49.69901826381208),
+            Offset(39.72502913331208, 49.57474290141208),
+            Offset(39.60519217681208, 49.45046753911208),
+            Offset(39.48535522031208, 49.32619217681208),
+            Offset(39.36551826381208, 49.20191681451208),
+            Offset(39.24568130721208, 49.07764145221208),
+            Offset(39.12584435071208, 48.95336608991208),
+            Offset(39.006007394212084, 48.82909072751208),
+            Offset(38.88617043771208, 48.70481536521208),
+            Offset(38.76633348121208, 48.58054000291208),
+            Offset(38.64649652461208, 48.456264640612076),
+            Offset(38.52665956811208, 48.33198927831208),
+            Offset(37.32866356433979, 47.77137070715779),
+            Offset(33.686840386562494, 46.22170752948048),
+            Offset(30.045017208833077, 44.67204435170307),
+            Offset(26.403194031155767, 43.122381174025776),
+            Offset(22.761370853378462, 41.57271799634846),
+            Offset(19.119547675701156, 40.023054818571154),
+            Offset(15.477724498023854, 38.47339164089385),
+            Offset(12.894881258806514, 37.21554892931651),
+            Offset(11.764102561113575, 36.42002629848357),
+            Offset(11.475347340254707, 36.41939651184471),
+            Offset(11.242594394558743, 36.41888886839874),
+            Offset(11.055634757573195, 36.41848110198319),
+            Offset(10.908432370914277, 36.418160047714274),
+            Offset(10.794213797064977, 36.41791093245497),
+            Offset(10.707799050590701, 36.4177224584507),
+            Offset(10.64450796675574, 36.417584418075734),
+            Offset(10.60036880430421, 36.4174881488042),
+            Offset(10.571908024680148, 36.41742607471014),
+            Offset(10.556100830038979, 36.41739159858898),
+            Offset(10.550301900803326, 36.41737895089332),
+            Offset(10.552191547658389, 36.41738307228839),
+            Offset(10.559731500666238, 36.41739951722624),
+            Offset(10.571123995611522, 36.41742436471152),
+            Offset(10.584779162835162, 36.417454147175164),
+            Offset(10.59928133913936, 36.41748577699936),
+            Offset(10.61341688827509, 36.4175166071951),
+            Offset(10.626051610978607, 36.4175441640286),
+            Offset(10.636217557081999, 36.417566336362),
+            Offset(10.643035451330938, 36.41758120646094),
+            Offset(10.645730709997451, 36.41758708492745),
+            Offset(10.64574712071989, 36.41758712071989),
+            Offset(10.64574712071989, 36.41758712071989),
+            Offset(10.64574712071989, 36.41758712071989),
+            Offset(10.64574712071989, 36.41758712071989),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(40.20437695941208, 55.54301039685442),
+            Offset(40.08454000291208, 55.41873503455442),
+            Offset(39.96470304641208, 55.294459672254426),
+            Offset(39.84486608991208, 55.17018430995442),
+            Offset(39.72502913331208, 55.04590894755442),
+            Offset(39.60519217681208, 54.921633585254426),
+            Offset(39.48535522031208, 54.79735822295442),
+            Offset(39.36551826381208, 54.673082860654425),
+            Offset(39.24568130721208, 54.54880749835442),
+            Offset(39.12584435071208, 54.424532136054424),
+            Offset(39.006007394212084, 54.300256773654425),
+            Offset(38.88617043771208, 54.17598141135442),
+            Offset(38.76633348121208, 54.051706049054424),
+            Offset(38.64649652461208, 53.92743068675442),
+            Offset(38.52665956811208, 53.80315532445442),
+            Offset(37.32866356433979, 53.60239939205497),
+            Offset(33.686840386562494, 53.2282875009701),
+            Offset(30.045017208833077, 52.854175609813836),
+            Offset(26.403194031155767, 52.480063718728964),
+            Offset(22.761370853378462, 52.105951827644084),
+            Offset(19.119547675701156, 51.73183993645922),
+            Offset(15.477724498023854, 51.357728045374344),
+            Offset(12.894881258806514, 50.847851966333415),
+            Offset(11.764102561113575, 50.24757484337634),
+            Offset(11.475347340254707, 50.40607912626448),
+            Offset(11.242594394558743, 50.53384249150479),
+            Offset(11.055634757573195, 50.636468872081664),
+            Offset(10.908432370914277, 50.717271597197396),
+            Offset(10.794213797064977, 50.77996876016692),
+            Offset(10.707799050590701, 50.82740377235519),
+            Offset(10.64450796675574, 50.86214568222138),
+            Offset(10.60036880430421, 50.8863746685263),
+            Offset(10.571908024680148, 50.90199743528123),
+            Offset(10.556100830038979, 50.91067436255417),
+            Offset(10.550301900803326, 50.913857526194576),
+            Offset(10.552191547658389, 50.91282025622321),
+            Offset(10.559731500666238, 50.90868140518888),
+            Offset(10.571123995611522, 50.9024278066003),
+            Offset(10.584779162835162, 50.894932175934066),
+            Offset(10.59928133913936, 50.886971602793345),
+            Offset(10.61341688827509, 50.87921227961184),
+            Offset(10.626051610978607, 50.872276794062344),
+            Offset(10.636217557081999, 50.866696475922275),
+            Offset(10.643035451330938, 50.862953979299974),
+            Offset(10.645730709997451, 50.861474490753025),
+            Offset(10.64574712071989, 50.861465482535664),
+            Offset(10.64574712071989, 50.861465482535664),
+            Offset(10.64574712071989, 50.861465482535664),
+            Offset(10.64574712071989, 50.861465482535664),
+          ],
+          <Offset>[
+            Offset(44.63922254046772, 59.97784435071208),
+            Offset(44.51938558396772, 59.853568988412086),
+            Offset(44.39954862746772, 59.72929362611209),
+            Offset(44.27971167096772, 59.605018263812084),
+            Offset(44.15987471436772, 59.480742901412086),
+            Offset(44.04003775786772, 59.35646753911209),
+            Offset(43.92020080136772, 59.232192176812084),
+            Offset(43.80036384486772, 59.10791681451209),
+            Offset(43.68052688826772, 58.98364145221208),
+            Offset(43.56068993176772, 58.859366089912086),
+            Offset(43.440852975267724, 58.73509072751209),
+            Offset(43.32101601876772, 58.61081536521208),
+            Offset(43.20117906226772, 58.486540002912086),
+            Offset(43.08134210566772, 58.36226464061208),
+            Offset(42.96150514916772, 58.237989278312085),
+            Offset(42.05520843213051, 58.328931867875795),
+            Offset(39.36626959101571, 58.907701815198486),
+            Offset(36.67733074997207, 59.48647176247307),
+            Offset(33.98839190895726, 60.06524170979577),
+            Offset(31.299453067842453, 60.644011657118455),
+            Offset(28.610514226827643, 61.22278160434116),
+            Offset(25.921575385812837, 61.80155155166385),
+            Offset(23.945022741160813, 61.897964477626516),
+            Offset(22.97250713958333, 61.455950035853576),
+            Offset(22.812743621605573, 61.74344568343471),
+            Offset(22.683965242071967, 61.975183342238736),
+            Offset(22.58052354762983, 62.16132744639319),
+            Offset(22.49907888637868, 62.30788772451427),
+            Offset(22.435883626662548, 62.42160806784497),
+            Offset(22.388071767674088, 62.507645866310696),
+            Offset(22.353053851184047, 62.57066086939574),
+            Offset(22.328632378031553, 62.614607493304206),
+            Offset(22.312885496421565, 62.64294412474014),
+            Offset(22.30413963541957, 62.65868236713898),
+            Offset(22.300931183179518, 62.66445600098332),
+            Offset(22.30197669372787, 62.66257459691839),
+            Offset(22.306148425807166, 62.65506753378624),
+            Offset(22.3124517060235, 62.643724733811524),
+            Offset(22.32000688562418, 62.63012913151516),
+            Offset(22.32803070142845, 62.615690214859356),
+            Offset(22.33585166844596, 62.60161632611509),
+            Offset(22.342842252864774, 62.5890367170786),
+            Offset(22.348466903765352, 62.578915115642),
+            Offset(22.352239132600456, 62.57212696159094),
+            Offset(22.353730374923767, 62.56944345985745),
+            Offset(22.35373945470678, 62.569427120719894),
+            Offset(22.35373945470678, 62.569427120719894),
+            Offset(22.35373945470678, 62.569427120719894),
+            Offset(22.35373945470678, 62.569427120719894),
+          ],
+          <Offset>[
+            Offset(50.11037695941208, 59.97784435071208),
+            Offset(49.99054000291208, 59.853568988412086),
+            Offset(49.87070304641208, 59.72929362611209),
+            Offset(49.75086608991208, 59.605018263812084),
+            Offset(49.631029133312076, 59.480742901412086),
+            Offset(49.51119217681208, 59.35646753911209),
+            Offset(49.39135522031208, 59.232192176812084),
+            Offset(49.27151826381208, 59.10791681451209),
+            Offset(49.15168130721208, 58.98364145221208),
+            Offset(49.03184435071208, 58.859366089912086),
+            Offset(48.91200739421208, 58.73509072751209),
+            Offset(48.79217043771208, 58.61081536521208),
+            Offset(48.67233348121208, 58.486540002912086),
+            Offset(48.55249652461208, 58.36226464061208),
+            Offset(48.43265956811208, 58.237989278312085),
+            Offset(47.886224725057794, 58.328931867875795),
+            Offset(46.37283467228049, 58.907701815198486),
+            Offset(44.859444619603074, 59.48647176247307),
+            Offset(43.34605456692577, 60.06524170979577),
+            Offset(41.83266451414846, 60.644011657118455),
+            Offset(40.31927446147115, 61.22278160434116),
+            Offset(38.80588440879385, 61.80155155166385),
+            Offset(37.57729680711652, 61.897964477626516),
+            Offset(36.80002629848357, 61.455950035853576),
+            Offset(36.799396511844705, 61.74344568343471),
+            Offset(36.79888886839874, 61.975183342238736),
+            Offset(36.79848110198319, 62.16132744639319),
+            Offset(36.798160047714276, 62.30788772451427),
+            Offset(36.797910932454975, 62.42160806784497),
+            Offset(36.7977224584507, 62.507645866310696),
+            Offset(36.797584418075736, 62.57066086939574),
+            Offset(36.797488148804206, 62.614607493304206),
+            Offset(36.797426074710145, 62.64294412474014),
+            Offset(36.79739159858898, 62.65868236713898),
+            Offset(36.797378950893325, 62.66445600098332),
+            Offset(36.79738307228839, 62.66257459691839),
+            Offset(36.79739951722624, 62.65506753378624),
+            Offset(36.797424364711524, 62.643724733811524),
+            Offset(36.797454147175166, 62.63012913151516),
+            Offset(36.797485776999366, 62.615690214859356),
+            Offset(36.79751660719509, 62.60161632611509),
+            Offset(36.79754416402861, 62.5890367170786),
+            Offset(36.797566336361996, 62.578915115642),
+            Offset(36.79758120646094, 62.57212696159094),
+            Offset(36.79758708492745, 62.56944345985745),
+            Offset(36.79758712071989, 62.569427120719894),
+            Offset(36.79758712071989, 62.569427120719894),
+            Offset(36.79758712071989, 62.569427120719894),
+            Offset(36.79758712071989, 62.569427120719894),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(52.571645330995544, 59.97784435071208),
+            Offset(52.45180837449554, 59.853568988412086),
+            Offset(52.33197141799555, 59.72929362611209),
+            Offset(52.212134461495545, 59.605018263812084),
+            Offset(52.09229750489554, 59.480742901412086),
+            Offset(51.972460548395546, 59.35646753911209),
+            Offset(51.852623591895544, 59.232192176812084),
+            Offset(51.73278663539554, 59.10791681451209),
+            Offset(51.612949678795545, 58.98364145221208),
+            Offset(51.49311272229554, 58.859366089912086),
+            Offset(51.37327576579555, 58.73509072751209),
+            Offset(51.253438809295545, 58.61081536521208),
+            Offset(51.13360185279554, 58.486540002912086),
+            Offset(51.013764896195546, 58.36226464061208),
+            Offset(50.893927939695544, 58.237989278312085),
+            Offset(50.509381535670876, 58.328931867875795),
+            Offset(49.524827050387316, 58.907701815198486),
+            Offset(48.54027256521657, 59.48647176247307),
+            Offset(47.55571808003301, 60.06524170979577),
+            Offset(46.57116359474944, 60.644011657118455),
+            Offset(45.586609109565885, 61.22278160434116),
+            Offset(44.602054624382326, 61.80155155166385),
+            Offset(43.70994860636296, 61.897964477626516),
+            Offset(43.0205115849932, 61.455950035853576),
+            Offset(43.091470130506, 61.74344568343471),
+            Offset(43.14866670842858, 61.975183342238736),
+            Offset(43.19461006443314, 62.16132744639319),
+            Offset(43.23078349611088, 62.30788772451427),
+            Offset(43.25885150418633, 62.42160806784497),
+            Offset(43.28008701536578, 62.507645866310696),
+            Offset(43.295640130698196, 62.57066086939574),
+            Offset(43.30648686364522, 62.614607493304206),
+            Offset(43.313480799617274, 62.64294412474014),
+            Offset(43.317365250734866, 62.65868236713898),
+            Offset(43.31879027634261, 62.66445600098332),
+            Offset(43.318325915587614, 62.66257459691839),
+            Offset(43.31647305177763, 62.65506753378624),
+            Offset(43.313673466479486, 62.643724733811524),
+            Offset(43.31031785336869, 62.63012913151516),
+            Offset(43.30675409672299, 62.615690214859356),
+            Offset(43.30328043483139, 62.60161632611509),
+            Offset(43.300175585182856, 62.5890367170786),
+            Offset(43.29767741130703, 62.578915115642),
+            Offset(43.296001985810214, 62.57212696159094),
+            Offset(43.29533965446193, 62.56944345985745),
+            Offset(43.295335621700225, 62.569427120719894),
+            Offset(43.295335621700225, 62.569427120719894),
+            Offset(43.295335621700225, 62.569427120719894),
+            Offset(43.295335621700225, 62.569427120719894),
+          ],
+          <Offset>[
+            Offset(54.81954300555442, 59.07868993176772),
+            Offset(54.69970604905442, 58.95441456946772),
+            Offset(54.579869092554425, 58.830139207167726),
+            Offset(54.46003213605442, 58.70586384486772),
+            Offset(54.34019517945442, 58.58158848246772),
+            Offset(54.220358222954424, 58.457313120167726),
+            Offset(54.10052126645442, 58.33303775786772),
+            Offset(53.98068430995442, 58.208762395567724),
+            Offset(53.86084735335442, 58.08448703326772),
+            Offset(53.74101039685442, 57.96021167096772),
+            Offset(53.621173440354426, 57.835936308567724),
+            Offset(53.50133648385442, 57.71166094626772),
+            Offset(53.38149952735442, 57.58738558396772),
+            Offset(53.261662570754424, 57.46311022166772),
+            Offset(53.14182561425442, 57.33883485936772),
+            Offset(52.905133320668966, 57.37063611066451),
+            Offset(52.4035689294841, 57.756211019649704),
+            Offset(51.90200453842384, 58.14178592858207),
+            Offset(51.40044014733896, 58.527360837567265),
+            Offset(50.89887575615409, 58.91293574655245),
+            Offset(50.397311365069214, 59.298510655437646),
+            Offset(49.895746973984345, 59.68408556442283),
+            Offset(49.31095249426342, 59.657574510890804),
+            Offset(48.70173455588635, 59.18347260189332),
+            Offset(48.83807534383448, 59.44481548777557),
+            Offset(48.94797368582479, 59.65547254999196),
+            Offset(49.036249922511665, 59.82468358945982),
+            Offset(49.1057540835974, 59.957911644778676),
+            Offset(49.159684365136926, 60.06128713223254),
+            Offset(49.20048658713519, 60.13949828685408),
+            Offset(49.230370570581385, 60.196780972344044),
+            Offset(49.2512116420263, 60.236729881531545),
+            Offset(49.264649892971235, 60.26248880031156),
+            Offset(49.27211353420417, 60.276795374069565),
+            Offset(49.27485159926458, 60.28204379484951),
+            Offset(49.27395936971321, 60.28033353741787),
+            Offset(49.27039925006888, 60.273509373087165),
+            Offset(49.265020085900304, 60.2631983993235),
+            Offset(49.25857256175407, 60.25083955504418),
+            Offset(49.25172510757334, 60.237714110608444),
+            Offset(49.24505076277185, 60.22492048840596),
+            Offset(49.23908505921234, 60.21348521501477),
+            Offset(49.234285031362276, 60.204284350405345),
+            Offset(49.23106584428998, 60.198113697790454),
+            Offset(49.22979323114302, 60.19567430751376),
+            Offset(49.22978548253567, 60.19565945470678),
+            Offset(49.22978548253567, 60.19565945470678),
+            Offset(49.22978548253567, 60.19565945470678),
+            Offset(49.22978548253567, 60.19565945470678),
+          ],
+          <Offset>[
+            Offset(56.549286261168945, 57.59279225720884),
+            Offset(56.42944930466894, 57.468516894908845),
+            Offset(56.30961234816895, 57.34424153260885),
+            Offset(56.189775391668945, 57.219966170308844),
+            Offset(56.06993843506894, 57.095690807908845),
+            Offset(55.950101478568946, 56.97141544560885),
+            Offset(55.830264522068944, 56.847140083308844),
+            Offset(55.71042756556894, 56.72286472100885),
+            Offset(55.590590608968945, 56.59858935870884),
+            Offset(55.47075365246894, 56.474313996408846),
+            Offset(55.35091669596895, 56.35003863400885),
+            Offset(55.231079739468946, 56.22576327170884),
+            Offset(55.111242782968944, 56.101487909408846),
+            Offset(54.99140582636895, 55.97721254710884),
+            Offset(54.871568869868945, 55.852937184808845),
+            Offset(54.74864939309879, 55.78700441495241),
+            Offset(54.6187428701751, 55.85331485483892),
+            Offset(54.4888363473851, 55.9196252946648),
+            Offset(54.358929824561415, 55.985935734551305),
+            Offset(54.22902330163772, 56.052246174437805),
+            Offset(54.09911677881403, 56.11855661422432),
+            Offset(53.96921025599034, 56.18486705411082),
+            Offset(53.620890090363176, 55.95521797286034),
+            Offset(53.07340023656424, 55.42808991849018),
+            Offset(53.26005225271882, 55.64621405687709),
+            Offset(53.410504273814226, 55.82203437827575),
+            Offset(53.53135539844182, 55.9632626809513),
+            Offset(53.62650734814831, 56.07445857089216),
+            Offset(53.700338487990095, 56.16073866631195),
+            Offset(53.75619717205799, 56.226015900304674),
+            Offset(53.79710866923288, 56.27382564410085),
+            Offset(53.82564032188473, 56.307168129650464),
+            Offset(53.84403743305162, 56.328667249267596),
+            Offset(53.85425523877907, 56.34060791895026),
+            Offset(53.85800367951774, 56.34498839885754),
+            Offset(53.8567822075933, 56.34356096980227),
+            Offset(53.85190836522106, 56.337865329915914),
+            Offset(53.84454423118309, 56.32925950060268),
+            Offset(53.83571750075847, 56.31894446083879),
+            Offset(53.82634326178278, 56.30798959497809),
+            Offset(53.81720601142316, 56.29731167730551),
+            Offset(53.809038898603006, 56.28776747583528),
+            Offset(53.80246760846087, 56.2800881749101),
+            Offset(53.79806050647426, 56.274937974320686),
+            Offset(53.79631828528544, 56.27290199044266),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(56.549286261168945, 57.59279225720884),
+            Offset(56.42944930466894, 57.468516894908845),
+            Offset(56.30961234816895, 57.34424153260885),
+            Offset(56.189775391668945, 57.219966170308844),
+            Offset(56.06993843506894, 57.095690807908845),
+            Offset(55.950101478568946, 56.97141544560885),
+            Offset(55.830264522068944, 56.847140083308844),
+            Offset(55.71042756556894, 56.72286472100885),
+            Offset(55.590590608968945, 56.59858935870884),
+            Offset(55.47075365246894, 56.474313996408846),
+            Offset(55.35091669596895, 56.35003863400885),
+            Offset(55.231079739468946, 56.22576327170884),
+            Offset(55.111242782968944, 56.101487909408846),
+            Offset(54.99140582636895, 55.97721254710884),
+            Offset(54.871568869868945, 55.852937184808845),
+            Offset(54.74864939309879, 55.78700441495241),
+            Offset(54.6187428701751, 55.85331485483892),
+            Offset(54.4888363473851, 55.9196252946648),
+            Offset(54.358929824561415, 55.985935734551305),
+            Offset(54.22902330163772, 56.052246174437805),
+            Offset(54.09911677881403, 56.11855661422432),
+            Offset(53.96921025599034, 56.18486705411082),
+            Offset(53.620890090363176, 55.95521797286034),
+            Offset(53.07340023656424, 55.42808991849018),
+            Offset(53.26005225271882, 55.64621405687709),
+            Offset(53.410504273814226, 55.82203437827575),
+            Offset(53.53135539844182, 55.9632626809513),
+            Offset(53.62650734814831, 56.07445857089216),
+            Offset(53.700338487990095, 56.16073866631195),
+            Offset(53.75619717205799, 56.226015900304674),
+            Offset(53.79710866923288, 56.27382564410085),
+            Offset(53.82564032188473, 56.307168129650464),
+            Offset(53.84403743305162, 56.328667249267596),
+            Offset(53.85425523877907, 56.34060791895026),
+            Offset(53.85800367951774, 56.34498839885754),
+            Offset(53.8567822075933, 56.34356096980227),
+            Offset(53.85190836522106, 56.337865329915914),
+            Offset(53.84454423118309, 56.32925950060268),
+            Offset(53.83571750075847, 56.31894446083879),
+            Offset(53.82634326178278, 56.30798959497809),
+            Offset(53.81720601142316, 56.29731167730551),
+            Offset(53.809038898603006, 56.28776747583528),
+            Offset(53.80246760846087, 56.2800881749101),
+            Offset(53.79806050647426, 56.274937974320686),
+            Offset(53.79631828528544, 56.27290199044266),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+          ],
+          <Offset>[
+            Offset(57.62369742449878, 56.51075365246894),
+            Offset(57.50386046799878, 56.386478290168945),
+            Offset(57.38402351149878, 56.26220292786895),
+            Offset(57.26418655499878, 56.137927565568944),
+            Offset(57.144349598398776, 56.013652203168945),
+            Offset(57.02451264189878, 55.88937684086895),
+            Offset(56.90467568539878, 55.765101478568944),
+            Offset(56.78483872889878, 55.64082611626895),
+            Offset(56.66500177229878, 55.51655075396894),
+            Offset(56.54516481579878, 55.392275391668946),
+            Offset(56.42532785929878, 55.26800002926895),
+            Offset(56.30549090279878, 55.14372466696894),
+            Offset(56.18565394629878, 55.019449304668946),
+            Offset(56.06581698969878, 54.89517394236894),
+            Offset(55.94598003319878, 54.770898580068945),
+            Offset(55.89372930109525, 54.63379537519879),
+            Offset(55.994674010747886, 54.46761572737509),
+            Offset(56.09561872053984, 54.3014360794851),
+            Offset(56.19656343029247, 54.13525643166142),
+            Offset(56.2975081399451, 53.969076783837714),
+            Offset(56.398452849697726, 53.80289713591403),
+            Offset(56.49939755945036, 53.63671748809034),
+            Offset(56.297960835674125, 53.25914221256317),
+            Offset(55.7888127085716, 52.69340023656424),
+            Offset(56.006714995568615, 52.88005225271882),
+            Offset(56.182356492271566, 53.03050427381422),
+            Offset(56.32344115337003, 53.151355398441815),
+            Offset(56.43452394733299, 53.246507348148306),
+            Offset(56.52071628832435, 53.32033848799009),
+            Offset(56.5859271296418, 53.376197172057985),
+            Offset(56.63368824673307, 53.417108669232874),
+            Offset(56.66699682004896, 53.445640321884724),
+            Offset(56.68847407317482, 53.464037433051615),
+            Offset(56.700402598148585, 53.474255238779065),
+            Offset(56.70477862272339, 53.47800367951774),
+            Offset(56.703352645488735, 53.476782207593295),
+            Offset(56.697662798567954, 53.471908365221054),
+            Offset(56.689065722138324, 53.46454423118309),
+            Offset(56.678761173675056, 53.45571750075847),
+            Offset(56.66781744987426, 53.446343261782786),
+            Offset(56.65715039258098, 53.43720601142316),
+            Offset(56.64761589840118, 53.429038898603004),
+            Offset(56.639944407998925, 53.42246760846087),
+            Offset(56.634799445615464, 53.418060506474255),
+            Offset(56.632765532511705, 53.416318285285435),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+          ],
+          <Offset>[
+            Offset(57.62369742449878, 56.51075365246894),
+            Offset(57.50386046799878, 56.386478290168945),
+            Offset(57.38402351149878, 56.26220292786895),
+            Offset(57.26418655499878, 56.137927565568944),
+            Offset(57.144349598398776, 56.013652203168945),
+            Offset(57.02451264189878, 55.88937684086895),
+            Offset(56.90467568539878, 55.765101478568944),
+            Offset(56.78483872889878, 55.64082611626895),
+            Offset(56.66500177229878, 55.51655075396894),
+            Offset(56.54516481579878, 55.392275391668946),
+            Offset(56.42532785929878, 55.26800002926895),
+            Offset(56.30549090279878, 55.14372466696894),
+            Offset(56.18565394629878, 55.019449304668946),
+            Offset(56.06581698969878, 54.89517394236894),
+            Offset(55.94598003319878, 54.770898580068945),
+            Offset(55.89372930109525, 54.63379537519879),
+            Offset(55.994674010747886, 54.46761572737509),
+            Offset(56.09561872053984, 54.3014360794851),
+            Offset(56.19656343029247, 54.13525643166142),
+            Offset(56.2975081399451, 53.969076783837714),
+            Offset(56.398452849697726, 53.80289713591403),
+            Offset(56.49939755945036, 53.63671748809034),
+            Offset(56.297960835674125, 53.25914221256317),
+            Offset(55.7888127085716, 52.69340023656424),
+            Offset(56.006714995568615, 52.88005225271882),
+            Offset(56.182356492271566, 53.03050427381422),
+            Offset(56.32344115337003, 53.151355398441815),
+            Offset(56.43452394733299, 53.246507348148306),
+            Offset(56.52071628832435, 53.32033848799009),
+            Offset(56.5859271296418, 53.376197172057985),
+            Offset(56.63368824673307, 53.417108669232874),
+            Offset(56.66699682004896, 53.445640321884724),
+            Offset(56.68847407317482, 53.464037433051615),
+            Offset(56.700402598148585, 53.474255238779065),
+            Offset(56.70477862272339, 53.47800367951774),
+            Offset(56.703352645488735, 53.476782207593295),
+            Offset(56.697662798567954, 53.471908365221054),
+            Offset(56.689065722138324, 53.46454423118309),
+            Offset(56.678761173675056, 53.45571750075847),
+            Offset(56.66781744987426, 53.446343261782786),
+            Offset(56.65715039258098, 53.43720601142316),
+            Offset(56.64761589840118, 53.429038898603004),
+            Offset(56.639944407998925, 53.42246760846087),
+            Offset(56.634799445615464, 53.418060506474255),
+            Offset(56.632765532511705, 53.416318285285435),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+          ],
+        ),
+        _PathClose(
+        ),
+        _PathMoveTo(
+          <Offset>[
+            Offset(50.11037695941208, 50.11515566052058),
+            Offset(49.99054000291208, 49.99088029822058),
+            Offset(49.87070304641208, 49.866604935920584),
+            Offset(49.75086608991208, 49.74232957362058),
+            Offset(49.631029133312076, 49.61805421122058),
+            Offset(49.51119217681208, 49.493778848920584),
+            Offset(49.39135522031208, 49.36950348662058),
+            Offset(49.27151826381208, 49.24522812432058),
+            Offset(49.15168130721208, 49.12095276202058),
+            Offset(49.03184435071208, 48.99667739972058),
+            Offset(48.91200739421208, 48.87240203732058),
+            Offset(48.79217043771208, 48.74812667502058),
+            Offset(48.67233348121208, 48.62385131272058),
+            Offset(48.55249652461208, 48.49957595042058),
+            Offset(48.43265956811208, 48.37530058812058),
+            Offset(47.886224725057794, 48.02143406108567),
+            Offset(46.37283467228049, 47.32255270277091),
+            Offset(44.859444619603074, 46.89224005129487),
+            Offset(43.346054566925766, 46.73049610683733),
+            Offset(41.83266451414846, 46.83732086930855),
+            Offset(40.31927446147115, 47.21271433859731),
+            Offset(38.80588440879385, 47.856676514909296),
+            Offset(37.57729680711651, 48.70078707684129),
+            Offset(36.80002629848357, 49.649293275456884),
+            Offset(36.799396511844705, 51.39862444641657),
+            Offset(36.79888886839874, 52.36567493589737),
+            Offset(36.798481101983185, 52.561543433342045),
+            Offset(36.798160047714276, 52.73365619281698),
+            Offset(36.79791093245497, 52.88590941777883),
+            Offset(36.79772245845069, 53.021274808915365),
+            Offset(36.79758441807573, 53.14247036493895),
+            Offset(36.797488148804206, 53.25181934443066),
+            Offset(36.797426074710145, 53.351374286002354),
+            Offset(36.79739159858897, 53.44294120991781),
+            Offset(36.79737895089332, 53.52811757525859),
+            Offset(36.79738307228838, 53.60832148263455),
+            Offset(36.79739951722624, 53.68481556964314),
+            Offset(36.79742436471152, 53.7587295593099),
+            Offset(36.79745414717516, 53.83107808312845),
+            Offset(36.79748577699936, 53.90277982470875),
+            Offset(36.79751660719509, 53.97463627018816),
+            Offset(36.7975441640286, 54.047410713622526),
+            Offset(36.797566336361996, 54.121768596383546),
+            Offset(36.79758120646093, 54.19832738700239),
+            Offset(36.79758708492745, 54.27764432912612),
+            Offset(36.79758712071989, 54.35877252649494),
+            Offset(36.79758712071989, 54.439911871108684),
+            Offset(36.79758712071989, 54.52105121572243),
+            Offset(36.79758712071989, 54.522707120719886),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(50.08645981463666, 50.11515566052058),
+            Offset(49.96662285813666, 49.99088029822058),
+            Offset(49.846785901636665, 49.866604935920584),
+            Offset(49.72694894513666, 49.74232957362058),
+            Offset(49.60711198853666, 49.61805421122058),
+            Offset(49.48727503203666, 49.493778848920584),
+            Offset(49.36743807553666, 49.36950348662058),
+            Offset(49.24760111903666, 49.24522812432058),
+            Offset(49.12776416243666, 49.12095276202058),
+            Offset(49.00792720593666, 48.99667739972058),
+            Offset(48.888090249436665, 48.87240203732058),
+            Offset(48.76825329293666, 48.74812667502058),
+            Offset(48.64841633643666, 48.62385131272058),
+            Offset(48.528579379836664, 48.49957595042058),
+            Offset(48.40874242333666, 48.37530058812058),
+            Offset(47.748134816992604, 48.02143406108567),
+            Offset(45.764925398174555, 47.32255270277091),
+            Offset(43.63340675157196, 46.89224005129487),
+            Offset(41.35357887709607, 46.73049610683733),
+            Offset(38.92544177464233, 46.83732086930855),
+            Offset(36.34899544441074, 47.21271433859731),
+            Offset(33.624239886301304, 47.856676514909296),
+            Offset(31.234909988934966, 48.70078707684129),
+            Offset(29.494550483270046, 49.649293275456884),
+            Offset(28.52755585328232, 51.39862444641657),
+            Offset(27.99274184675299, 52.36567493589737),
+            Offset(27.883944986437346, 52.561543433342045),
+            Offset(27.788401120897287, 52.73365619281698),
+            Offset(27.703935759159105, 52.88590941777883),
+            Offset(27.628890333495022, 53.021274808915365),
+            Offset(27.561748066907615, 53.14247036493895),
+            Offset(27.50121256374279, 53.25181934443066),
+            Offset(27.446138611583994, 53.351374286002354),
+            Offset(27.39551864926441, 53.44294120991781),
+            Offset(27.348461571724304, 53.52811757525859),
+            Offset(27.30417642143305, 53.60832148263455),
+            Offset(27.261959043201006, 53.68481556964314),
+            Offset(27.22117949399494, 53.7587295593099),
+            Offset(27.181272090318803, 53.83107808312845),
+            Offset(27.141724722671132, 53.90277982470875),
+            Offset(27.102090684257846, 53.97463627018816),
+            Offset(27.061944627502523, 54.047410713622526),
+            Offset(27.020915803873248, 54.121768596383546),
+            Offset(26.978660256499385, 54.19832738700239),
+            Offset(26.934867640331532, 54.27764432912612),
+            Offset(26.890065739866895, 54.35877252649494),
+            Offset(26.845257628088735, 54.439911871108684),
+            Offset(26.80044951631057, 54.52105121572243),
+            Offset(26.79953506506211, 54.522707120719886),
+          ],
+          <Offset>[
+            Offset(50.06706564959764, 50.095761495481554),
+            Offset(49.947228693097635, 49.97148613318156),
+            Offset(49.82739173659764, 49.84721077088156),
+            Offset(49.70755478009764, 49.722935408581556),
+            Offset(49.587717823497634, 49.59866004618156),
+            Offset(49.46788086699764, 49.47438468388156),
+            Offset(49.34804391049764, 49.350109321581556),
+            Offset(49.228206953997635, 49.22583395928156),
+            Offset(49.10836999739764, 49.101558596981555),
+            Offset(48.988533040897636, 48.97728323468156),
+            Offset(48.86869608439764, 48.85300787228156),
+            Offset(48.74885912789764, 48.728732509981555),
+            Offset(48.62902217139764, 48.60445714768156),
+            Offset(48.50918521479764, 48.48018178538155),
+            Offset(48.38934825829764, 48.35590642308156),
+            Offset(47.63616175837963, 47.909461002472696),
+            Offset(45.27199148434817, 46.82961878894062),
+            Offset(42.63925301353386, 45.89808631326019),
+            Offset(39.73794634587201, 45.11486357561327),
+            Offset(36.56807148124825, 44.47995057591447),
+            Offset(33.12962841986258, 43.993347314049146),
+            Offset(29.42261716161499, 43.65505379022298),
+            Offset(26.09208026716689, 43.55795735507321),
+            Offset(23.57078423835929, 43.725527030546125),
+            Offset(21.82019681522399, 44.69126540835824),
+            Offset(20.852132118393076, 45.225065207537455),
+            Offset(20.655446961021294, 45.333045407926),
+            Offset(20.48269090511957, 45.42794597703926),
+            Offset(20.329938213814554, 45.511911872434275),
+            Offset(20.194194600945227, 45.58657907636557),
+            Offset(20.07272166098446, 45.653443959015796),
+            Offset(19.9631788172331, 45.71378559792097),
+            Offset(19.86349838492615, 45.76873405934452),
+            Offset(19.771861154060407, 45.81928371469362),
+            Offset(19.686658130182877, 45.86631413371717),
+            Offset(19.606461097044544, 45.91060615824605),
+            Offset(19.529998528318746, 45.95285505476088),
+            Offset(19.456132860826976, 45.993682926121764),
+            Offset(19.38384252924341, 46.033648522053056),
+            Offset(19.312202675685725, 46.07325777772334),
+            Offset(19.240406520613522, 46.11295210654384),
+            Offset(19.167685822801204, 46.1531519089212),
+            Offset(19.093370918660103, 46.19422371115027),
+            Offset(19.016840503908618, 46.236507634411616),
+            Offset(18.93753395574349, 46.28031064453808),
+            Offset(18.856404467546696, 46.325111254174736),
+            Offset(18.775263760542785, 46.369918003542615),
+            Offset(18.69412305351876, 46.41472475293062),
+            Offset(18.692467120719886, 46.41563917637766),
+          ],
+          <Offset>[
+            Offset(50.06706564959764, 50.07184435070614),
+            Offset(49.947228693097635, 49.94756898840614),
+            Offset(49.82739173659764, 49.82329362610614),
+            Offset(49.70755478009764, 49.69901826380614),
+            Offset(49.587717823497634, 49.57474290140614),
+            Offset(49.46788086699764, 49.45046753910614),
+            Offset(49.34804391049764, 49.32619217680614),
+            Offset(49.228206953997635, 49.20191681450614),
+            Offset(49.10836999739764, 49.07764145220614),
+            Offset(48.988533040897636, 48.95336608990614),
+            Offset(48.86869608439764, 48.82909072750614),
+            Offset(48.74885912789764, 48.70481536520614),
+            Offset(48.62902217139764, 48.58054000290614),
+            Offset(48.50918521479764, 48.456264640606136),
+            Offset(48.38934825829764, 48.33198927830614),
+            Offset(47.63616175837963, 47.7713707071586),
+            Offset(45.27199148434817, 46.22170752947073),
+            Offset(42.63925301353386, 44.672044351691675),
+            Offset(39.73794634587201, 43.12238117401274),
+            Offset(36.56807148124825, 41.572717996339655),
+            Offset(33.12962841986258, 40.02305481856121),
+            Offset(29.42261716161499, 38.47339164088308),
+            Offset(26.09208026716689, 37.21554892929752),
+            Offset(23.57078423835929, 36.42002629846431),
+            Offset(21.82019681522399, 36.41939651182523),
+            Offset(20.852132118393076, 36.41888886837908),
+            Offset(20.655446961021294, 36.41848110196339),
+            Offset(20.48269090511957, 36.41816004771427),
+            Offset(20.329938213814554, 36.41791093245497),
+            Offset(20.194194600945227, 36.4177224584507),
+            Offset(20.07272166098446, 36.41758441807573),
+            Offset(19.9631788172331, 36.4174881488042),
+            Offset(19.86349838492615, 36.417426074710136),
+            Offset(19.771861154060407, 36.41739159858898),
+            Offset(19.686658130182877, 36.417378950893315),
+            Offset(19.606461097044544, 36.41738307228839),
+            Offset(19.529998528318746, 36.41739951720605),
+            Offset(19.456132860826976, 36.41742436469134),
+            Offset(19.38384252924341, 36.417454147155),
+            Offset(19.312202675685725, 36.417485776979206),
+            Offset(19.240406520613522, 36.417516607174946),
+            Offset(19.167685822801204, 36.41754416400846),
+            Offset(19.093370918660103, 36.41756633636199),
+            Offset(19.016840503908618, 36.41758120646094),
+            Offset(18.93753395574349, 36.41758708492745),
+            Offset(18.856404467546696, 36.41758712071989),
+            Offset(18.775263760542785, 36.41758712071989),
+            Offset(18.69412305351876, 36.41758712071989),
+            Offset(18.692467120719886, 36.41758712071989),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(50.06706564959764, 50.04792720593072),
+            Offset(49.947228693097635, 49.92365184363072),
+            Offset(49.82739173659764, 49.799376481330725),
+            Offset(49.70755478009764, 49.675101119030714),
+            Offset(49.587717823497634, 49.550825756630715),
+            Offset(49.46788086699764, 49.42655039433072),
+            Offset(49.34804391049764, 49.30227503203072),
+            Offset(49.228206953997635, 49.177999669730724),
+            Offset(49.10836999739764, 49.05372430743071),
+            Offset(48.988533040897636, 48.929448945130716),
+            Offset(48.86869608439764, 48.80517358273072),
+            Offset(48.74885912789764, 48.68089822043072),
+            Offset(48.62902217139764, 48.55662285813072),
+            Offset(48.50918521479764, 48.43234749583071),
+            Offset(48.38934825829764, 48.308072133530715),
+            Offset(47.63616175837963, 47.63328079909341),
+            Offset(45.27199148434817, 45.61379825536479),
+            Offset(42.63925301353386, 43.44600648366056),
+            Offset(39.73794634587201, 41.12990548418304),
+            Offset(36.56807148124825, 38.66549525683352),
+            Offset(33.12962841986258, 36.05277580150079),
+            Offset(29.42261716161499, 33.29174711839053),
+            Offset(26.09208026716689, 30.873162111115967),
+            Offset(23.57078423835929, 29.11455048325078),
+            Offset(21.82019681522399, 28.147555853262837),
+            Offset(20.852132118393076, 27.612742525999973),
+            Offset(20.655446961021294, 27.50394701174588),
+            Offset(20.48269090511957, 27.408404506536804),
+            Offset(20.329938213814554, 27.323940514395186),
+            Offset(20.194194600945227, 27.2488964636637),
+            Offset(20.07272166098446, 27.181755574389616),
+            Offset(19.9631788172331, 27.121221448636877),
+            Offset(19.86349838492615, 27.066148872338097),
+            Offset(19.771861154060407, 27.015530283259928),
+            Offset(19.686658130182877, 26.968474575656916),
+            Offset(19.606461097044544, 26.924190791705026),
+            Offset(19.529998528318746, 26.88197477619479),
+            Offset(19.456132860826976, 26.841196586461674),
+            Offset(19.38384252924341, 26.80129053948502),
+            Offset(19.312202675685725, 26.761744526458134),
+            Offset(19.240406520613522, 26.72211184147367),
+            Offset(19.167685822801204, 26.68196713796564),
+            Offset(19.093370918660103, 26.64093966853734),
+            Offset(19.016840503908618, 26.598685477488807),
+            Offset(18.93753395574349, 26.5548942211387),
+            Offset(18.856404467546696, 26.5100936830677),
+            Offset(18.775263760542785, 26.465286933679703),
+            Offset(18.69412305351876, 26.42048018431182),
+            Offset(18.692467120719886, 26.419565760864774),
+          ],
+          <Offset>[
+            Offset(50.08645981463666, 50.02853304089169),
+            Offset(49.96662285813666, 49.90425767859169),
+            Offset(49.846785901636665, 49.779982316291694),
+            Offset(49.72694894513666, 49.6557069539917),
+            Offset(49.60711198853666, 49.5314315915917),
+            Offset(49.48727503203666, 49.4071562292917),
+            Offset(49.36743807553666, 49.28288086699169),
+            Offset(49.24760111903666, 49.15860550469169),
+            Offset(49.12776416243666, 49.034330142391696),
+            Offset(49.00792720593666, 48.9100547800917),
+            Offset(48.888090249436665, 48.7857794176917),
+            Offset(48.76825329293666, 48.66150405539169),
+            Offset(48.64841633643666, 48.53722869309169),
+            Offset(48.528579379836664, 48.412953330791694),
+            Offset(48.40874242333666, 48.2886779684917),
+            Offset(47.748134816992604, 47.521307740480445),
+            Offset(45.76492539817846, 45.1208643415384),
+            Offset(43.63340675156854, 42.45185274562246),
+            Offset(41.35357887709607, 39.51427295295898),
+            Offset(38.92544177464233, 36.30812496343945),
+            Offset(36.34899544441074, 32.83340877695263),
+            Offset(33.624239886301304, 29.090124393704222),
+            Offset(31.234909988934966, 25.730332389347893),
+            Offset(29.494550483270046, 23.19078423834003),
+            Offset(28.52755585328232, 21.440196815204512),
+            Offset(27.99274184675299, 20.472132118373413),
+            Offset(27.883944986437346, 20.275446961001492),
+            Offset(27.788401120897287, 20.102690905119566),
+            Offset(27.703935759159105, 19.94993821381455),
+            Offset(27.628890333495022, 19.814194600945225),
+            Offset(27.56174806690762, 19.692721660984457),
+            Offset(27.50121256374279, 19.583178817233097),
+            Offset(27.446138611583994, 19.483498384926143),
+            Offset(27.395518649284597, 19.391861154060404),
+            Offset(27.348461571724304, 19.306658130182875),
+            Offset(27.30417642143305, 19.22646109704454),
+            Offset(27.261959043201006, 19.14999852829856),
+            Offset(27.22117949401511, 19.076132860806798),
+            Offset(27.1812720903188, 19.003842529223242),
+            Offset(27.141724722671132, 18.93220267566557),
+            Offset(27.102090684257846, 18.860406520593376),
+            Offset(27.061944627502523, 18.787685822781068),
+            Offset(27.020915803893374, 18.7133709186601),
+            Offset(26.978660256499385, 18.636840503908616),
+            Offset(26.934867640331532, 18.557533955743487),
+            Offset(26.890065739866895, 18.476404467546697),
+            Offset(26.84525762810885, 18.395263760542782),
+            Offset(26.80044951631057, 18.314123053518756),
+            Offset(26.79953506506211, 18.312467120719884),
+          ],
+          <Offset>[
+            Offset(50.11037695941208, 50.02853304089169),
+            Offset(49.99054000291208, 49.90425767859169),
+            Offset(49.87070304641208, 49.779982316291694),
+            Offset(49.75086608991208, 49.6557069539917),
+            Offset(49.631029133312076, 49.5314315915917),
+            Offset(49.51119217681208, 49.4071562292917),
+            Offset(49.39135522031208, 49.28288086699169),
+            Offset(49.27151826381208, 49.15860550469169),
+            Offset(49.15168130721208, 49.034330142391696),
+            Offset(49.03184435071208, 48.9100547800917),
+            Offset(48.91200739421208, 48.7857794176917),
+            Offset(48.79217043771208, 48.66150405539169),
+            Offset(48.67233348121208, 48.53722869309169),
+            Offset(48.55249652461208, 48.412953330791694),
+            Offset(48.43265956811208, 48.2886779684917),
+            Offset(47.886224725057794, 47.521307740480445),
+            Offset(46.37283467228049, 45.1208643415384),
+            Offset(44.859444619603074, 42.45185274562246),
+            Offset(43.346054566925766, 39.51427295295898),
+            Offset(41.83266451414846, 36.30812496343945),
+            Offset(40.31927446147115, 32.83340877695263),
+            Offset(38.80588440879385, 29.090124393704222),
+            Offset(37.57729680711652, 25.730332389347893),
+            Offset(36.80002629848357, 23.19078423834003),
+            Offset(36.799396511844705, 21.440196815204512),
+            Offset(36.79888886839874, 20.472132118373413),
+            Offset(36.798481101983185, 20.275446961001492),
+            Offset(36.798160047714276, 20.102690905119566),
+            Offset(36.79791093245497, 19.94993821381455),
+            Offset(36.79772245845069, 19.814194600945225),
+            Offset(36.79758441807573, 19.692721660984457),
+            Offset(36.797488148804206, 19.583178817233097),
+            Offset(36.797426074710145, 19.483498384926143),
+            Offset(36.79739159858897, 19.391861154060404),
+            Offset(36.79737895089332, 19.306658130182875),
+            Offset(36.79738307228838, 19.22646109704454),
+            Offset(36.79739951722624, 19.14999852829856),
+            Offset(36.79742436471152, 19.076132860806798),
+            Offset(36.79745414717516, 19.003842529223242),
+            Offset(36.79748577699936, 18.93220267566557),
+            Offset(36.79751660719509, 18.860406520593376),
+            Offset(36.7975441640286, 18.787685822781068),
+            Offset(36.797566336361996, 18.7133709186601),
+            Offset(36.79758120646093, 18.636840503908616),
+            Offset(36.79758708492745, 18.557533955743487),
+            Offset(36.79758712071989, 18.476404467546697),
+            Offset(36.79758712071989, 18.395263760542782),
+            Offset(36.79758712071989, 18.314123053518756),
+            Offset(36.79758712071989, 18.312467120719884),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(50.1342941041875, 50.02853304089169),
+            Offset(50.014457147687494, 49.90425767859169),
+            Offset(49.8946201911875, 49.779982316291694),
+            Offset(49.7747832346875, 49.6557069539917),
+            Offset(49.65494627808749, 49.5314315915917),
+            Offset(49.5351093215875, 49.4071562292917),
+            Offset(49.415272365087496, 49.28288086699169),
+            Offset(49.295435408587494, 49.15860550469169),
+            Offset(49.1755984519875, 49.034330142391696),
+            Offset(49.055761495487495, 48.9100547800917),
+            Offset(48.9359245389875, 48.7857794176917),
+            Offset(48.8160875824875, 48.66150405539169),
+            Offset(48.696250625987496, 48.53722869309169),
+            Offset(48.5764136693875, 48.412953330791694),
+            Offset(48.4565767128875, 48.2886779684917),
+            Offset(48.02431502037189, 47.521307740480445),
+            Offset(46.98074593174941, 45.1208643415384),
+            Offset(46.08548658116816, 42.45185274562246),
+            Offset(45.338536968526306, 39.51427295295898),
+            Offset(44.73989709372328, 36.30812496343945),
+            Offset(44.28956695695909, 32.83340877695263),
+            Offset(43.9875465581158, 29.090124393704222),
+            Offset(43.91970523287322, 25.730332389347893),
+            Offset(44.10552703056539, 23.19078423834003),
+            Offset(45.07126540837772, 21.440196815204512),
+            Offset(45.60506520753746, 20.472132118373413),
+            Offset(45.7130454079458, 20.275446961001492),
+            Offset(45.80794597703926, 20.102690905119566),
+            Offset(45.89191187243428, 19.94993821381455),
+            Offset(45.96657907636557, 19.814194600945225),
+            Offset(46.0334439590158, 19.692721660984457),
+            Offset(46.09378559792097, 19.583178817233097),
+            Offset(46.14873405934452, 19.483498384926143),
+            Offset(46.19928371471381, 19.391861154060404),
+            Offset(46.24631413371717, 19.306658130182875),
+            Offset(46.29060615824605, 19.22646109704454),
+            Offset(46.332855054781064, 19.14999852829856),
+            Offset(46.37368292614194, 19.076132860806798),
+            Offset(46.41364852205306, 19.003842529223242),
+            Offset(46.453257777743495, 18.93220267566557),
+            Offset(46.492952106563976, 18.860406520593376),
+            Offset(46.533151908941335, 18.787685822781068),
+            Offset(46.574223711150275, 18.7133709186601),
+            Offset(46.61650763441162, 18.636840503908616),
+            Offset(46.66031064453808, 18.557533955743487),
+            Offset(46.70511125417474, 18.476404467546697),
+            Offset(46.74991800356274, 18.395263760542782),
+            Offset(46.79472475293062, 18.314123053518756),
+            Offset(46.79563917637766, 18.312467120719884),
+          ],
+          <Offset>[
+            Offset(50.15368826922652, 50.04792720593072),
+            Offset(50.033851312726526, 49.92365184363072),
+            Offset(49.91401435622653, 49.799376481330725),
+            Offset(49.79417739972652, 49.675101119030714),
+            Offset(49.674340443126525, 49.550825756630715),
+            Offset(49.55450348662653, 49.42655039433072),
+            Offset(49.43466653012652, 49.30227503203072),
+            Offset(49.314829573626525, 49.177999669730724),
+            Offset(49.19499261702653, 49.05372430743071),
+            Offset(49.07515566052652, 48.929448945130716),
+            Offset(48.955318704026524, 48.80517358273072),
+            Offset(48.83548174752653, 48.68089822043072),
+            Offset(48.71564479102652, 48.55662285813072),
+            Offset(48.59580783442652, 48.43234749583071),
+            Offset(48.47597087792653, 48.308072133530715),
+            Offset(48.136288078984855, 47.63328079909341),
+            Offset(47.473679845580676, 45.61379825536869),
+            Offset(47.07964031920627, 43.44600648365714),
+            Offset(46.95416949975036, 41.12990548418304),
+            Offset(47.097267387117355, 38.66549525683352),
+            Offset(47.508933981507255, 36.05277580150079),
+            Offset(48.18916928282006, 33.29174711839053),
+            Offset(49.06253495466028, 30.873162111115967),
+            Offset(50.02929327547615, 29.114550483250788),
+            Offset(51.77862444643605, 28.14755585326284),
+            Offset(52.74567493591703, 27.612742526019634),
+            Offset(52.94154343336185, 27.50394701174588),
+            Offset(53.11365619281698, 27.408404506536808),
+            Offset(53.26590941777883, 27.323940514375185),
+            Offset(53.40127480891536, 27.24889646368377),
+            Offset(53.522470364938954, 27.18175557438962),
+            Offset(53.63181934443066, 27.121221448636877),
+            Offset(53.731374286002364, 27.066148872338097),
+            Offset(53.822941209917815, 27.015530283280118),
+            Offset(53.9081175752586, 26.968474575656916),
+            Offset(53.988321482634554, 26.924190791684836),
+            Offset(54.064815569663324, 26.88197477619479),
+            Offset(54.13872955933007, 26.841196586461674),
+            Offset(54.211078083148614, 26.801290539485016),
+            Offset(54.282779824728905, 26.761744526458138),
+            Offset(54.3546362702083, 26.72211184147367),
+            Offset(54.42741071364266, 26.681967137945506),
+            Offset(54.50176859638355, 26.640939668537342),
+            Offset(54.57832738700239, 26.598685477488804),
+            Offset(54.65764432912613, 26.5548942211387),
+            Offset(54.73877252649494, 26.510093683047582),
+            Offset(54.81991187110869, 26.46528693369982),
+            Offset(54.90105121572243, 26.42048018431182),
+            Offset(54.90270712071989, 26.419565760864774),
+          ],
+          <Offset>[
+            Offset(50.15368826922652, 50.07184435070614),
+            Offset(50.033851312726526, 49.94756898840614),
+            Offset(49.91401435622653, 49.82329362610614),
+            Offset(49.79417739972652, 49.69901826380614),
+            Offset(49.674340443126525, 49.57474290140614),
+            Offset(49.55450348662653, 49.45046753910614),
+            Offset(49.43466653012652, 49.32619217680614),
+            Offset(49.314829573626525, 49.20191681450614),
+            Offset(49.19499261702653, 49.07764145220614),
+            Offset(49.07515566052652, 48.95336608990614),
+            Offset(48.955318704026524, 48.82909072750614),
+            Offset(48.83548174752653, 48.70481536520614),
+            Offset(48.71564479102652, 48.58054000290614),
+            Offset(48.59580783442652, 48.456264640606136),
+            Offset(48.47597087792653, 48.33198927830614),
+            Offset(48.136288078984855, 47.7713707071586),
+            Offset(47.473679845580676, 46.22170752947072),
+            Offset(47.07964031920627, 44.672044351691675),
+            Offset(46.95416949975036, 43.12238117401274),
+            Offset(47.097267387117355, 41.572717996339655),
+            Offset(47.508933981507255, 40.02305481856121),
+            Offset(48.18916928282006, 38.47339164088308),
+            Offset(49.06253495466028, 37.21554892929752),
+            Offset(50.02929327547615, 36.42002629846431),
+            Offset(51.77862444643605, 36.41939651182523),
+            Offset(52.74567493591703, 36.41888886837908),
+            Offset(52.94154343336185, 36.41848110196339),
+            Offset(53.11365619281698, 36.41816004771427),
+            Offset(53.26590941777883, 36.41791093245497),
+            Offset(53.40127480891536, 36.4177224584507),
+            Offset(53.522470364938954, 36.417584418075734),
+            Offset(53.63181934443066, 36.4174881488042),
+            Offset(53.731374286002364, 36.417426074710136),
+            Offset(53.822941209917815, 36.41739159858898),
+            Offset(53.9081175752586, 36.417378950893315),
+            Offset(53.988321482634554, 36.41738307228838),
+            Offset(54.064815569663324, 36.41739951720605),
+            Offset(54.13872955933007, 36.41742436469134),
+            Offset(54.211078083148614, 36.417454147155),
+            Offset(54.282779824728905, 36.417485776979206),
+            Offset(54.3546362702083, 36.417516607174946),
+            Offset(54.42741071364266, 36.41754416400846),
+            Offset(54.50176859638355, 36.41756633636199),
+            Offset(54.57832738700239, 36.41758120646094),
+            Offset(54.65764432912613, 36.41758708492745),
+            Offset(54.73877252649494, 36.41758712071989),
+            Offset(54.81991187110869, 36.41758712071989),
+            Offset(54.90105121572243, 36.41758712071989),
+            Offset(54.90270712071989, 36.41758712071989),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(50.15368826922652, 50.095761495481554),
+            Offset(50.033851312726526, 49.97148613318156),
+            Offset(49.91401435622653, 49.84721077088156),
+            Offset(49.79417739972652, 49.722935408581556),
+            Offset(49.674340443126525, 49.59866004618156),
+            Offset(49.55450348662653, 49.47438468388156),
+            Offset(49.43466653012652, 49.350109321581556),
+            Offset(49.314829573626525, 49.22583395928156),
+            Offset(49.19499261702653, 49.101558596981555),
+            Offset(49.07515566052652, 48.97728323468156),
+            Offset(48.955318704026524, 48.85300787228156),
+            Offset(48.83548174752653, 48.728732509981555),
+            Offset(48.71564479102652, 48.60445714768156),
+            Offset(48.59580783442652, 48.48018178538155),
+            Offset(48.47597087792653, 48.35590642308156),
+            Offset(48.136288078984855, 47.909461002472696),
+            Offset(47.473679845580676, 46.82961878893964),
+            Offset(47.07964031920627, 45.89808631325676),
+            Offset(46.95416949975036, 45.11486357561327),
+            Offset(47.097267387117355, 44.47995057591447),
+            Offset(47.508933981507255, 43.993347314049146),
+            Offset(48.18916928282006, 43.65505379020504),
+            Offset(49.06253495466028, 43.55795735505423),
+            Offset(50.02929327547615, 43.725527030546125),
+            Offset(51.77862444643605, 44.69126540835824),
+            Offset(52.74567493591703, 45.225065207517794),
+            Offset(52.94154343336185, 45.333045407926),
+            Offset(53.11365619281698, 45.42794597703926),
+            Offset(53.26590941777883, 45.511911872434275),
+            Offset(53.40127480891536, 45.58657907636557),
+            Offset(53.522470364938954, 45.653443959015796),
+            Offset(53.63181934443066, 45.71378559792097),
+            Offset(53.731374286002364, 45.76873405934451),
+            Offset(53.822941209917815, 45.819283714713805),
+            Offset(53.9081175752586, 45.86631413371717),
+            Offset(53.988321482634554, 45.91060615824605),
+            Offset(54.064815569663324, 45.95285505476088),
+            Offset(54.13872955933007, 45.993682926121764),
+            Offset(54.211078083148614, 46.03364852203289),
+            Offset(54.282779824728905, 46.07325777772334),
+            Offset(54.3546362702083, 46.11295210654383),
+            Offset(54.42741071364266, 46.1531519089212),
+            Offset(54.50176859638355, 46.19422371115027),
+            Offset(54.57832738700239, 46.236507634411616),
+            Offset(54.65764432912613, 46.280310644538076),
+            Offset(54.73877252649494, 46.32511125417474),
+            Offset(54.81991187110869, 46.36991800356274),
+            Offset(54.90105121572243, 46.41472475293062),
+            Offset(54.90270712071989, 46.41563917637766),
+          ],
+          <Offset>[
+            Offset(50.1342941041875, 50.11515566052058),
+            Offset(50.014457147687494, 49.99088029822058),
+            Offset(49.8946201911875, 49.866604935920584),
+            Offset(49.7747832346875, 49.74232957362058),
+            Offset(49.65494627808749, 49.61805421122058),
+            Offset(49.5351093215875, 49.493778848920584),
+            Offset(49.415272365087496, 49.36950348662058),
+            Offset(49.295435408587494, 49.24522812432058),
+            Offset(49.1755984519875, 49.12095276202058),
+            Offset(49.055761495487495, 48.99667739972058),
+            Offset(48.9359245389875, 48.87240203732058),
+            Offset(48.8160875824875, 48.74812667502058),
+            Offset(48.696250625987496, 48.62385131272058),
+            Offset(48.5764136693875, 48.49957595042058),
+            Offset(48.4565767128875, 48.37530058812058),
+            Offset(48.02431502037189, 48.02143406108567),
+            Offset(46.980745931750384, 47.32255270277091),
+            Offset(46.08548658117158, 46.89224005129487),
+            Offset(45.3385369685263, 46.73049610683733),
+            Offset(44.73989709372328, 46.83732086930855),
+            Offset(44.28956695695909, 47.21271433859731),
+            Offset(43.98754655813375, 47.856676514909296),
+            Offset(43.919705232892206, 48.70078707684129),
+            Offset(44.10552703056539, 49.649293275456884),
+            Offset(45.07126540837772, 51.39862444641657),
+            Offset(45.60506520755712, 52.36567493589737),
+            Offset(45.7130454079458, 52.56154343334205),
+            Offset(45.80794597703926, 52.73365619281698),
+            Offset(45.89191187243428, 52.88590941777883),
+            Offset(45.96657907636557, 53.021274808915365),
+            Offset(46.0334439590158, 53.14247036493895),
+            Offset(46.09378559792097, 53.25181934443066),
+            Offset(46.14873405934452, 53.351374286002354),
+            Offset(46.19928371469362, 53.44294120991781),
+            Offset(46.24631413371717, 53.5281175752586),
+            Offset(46.29060615824605, 53.60832148263455),
+            Offset(46.332855054781064, 53.68481556964314),
+            Offset(46.373682926141946, 53.7587295593099),
+            Offset(46.413648522073224, 53.83107808312845),
+            Offset(46.453257777743495, 53.90277982470875),
+            Offset(46.49295210656398, 53.97463627018816),
+            Offset(46.533151908941335, 54.04741071362252),
+            Offset(46.574223711150275, 54.121768596383546),
+            Offset(46.61650763441162, 54.19832738700239),
+            Offset(46.660310644538086, 54.27764432912612),
+            Offset(46.70511125417474, 54.35877252649494),
+            Offset(46.74991800354262, 54.439911871108684),
+            Offset(46.79472475293062, 54.52105121572244),
+            Offset(46.79563917637766, 54.522707120719886),
+          ],
+          <Offset>[
+            Offset(50.11037695941208, 50.11515566052058),
+            Offset(49.99054000291208, 49.99088029822058),
+            Offset(49.87070304641208, 49.866604935920584),
+            Offset(49.75086608991208, 49.74232957362058),
+            Offset(49.631029133312076, 49.61805421122058),
+            Offset(49.51119217681208, 49.493778848920584),
+            Offset(49.39135522031208, 49.36950348662058),
+            Offset(49.27151826381208, 49.24522812432058),
+            Offset(49.15168130721208, 49.12095276202058),
+            Offset(49.03184435071208, 48.99667739972058),
+            Offset(48.91200739421208, 48.87240203732058),
+            Offset(48.79217043771208, 48.74812667502058),
+            Offset(48.67233348121208, 48.62385131272058),
+            Offset(48.55249652461208, 48.49957595042058),
+            Offset(48.43265956811208, 48.37530058812058),
+            Offset(47.886224725057794, 48.02143406108567),
+            Offset(46.37283467228049, 47.32255270277091),
+            Offset(44.859444619603074, 46.89224005129487),
+            Offset(43.346054566925766, 46.73049610683733),
+            Offset(41.83266451414846, 46.83732086930855),
+            Offset(40.31927446147115, 47.21271433859731),
+            Offset(38.80588440879385, 47.856676514909296),
+            Offset(37.57729680711652, 48.70078707684129),
+            Offset(36.80002629848357, 49.649293275456884),
+            Offset(36.799396511844705, 51.39862444641657),
+            Offset(36.79888886839874, 52.36567493589737),
+            Offset(36.798481101983185, 52.56154343334205),
+            Offset(36.798160047714276, 52.73365619281698),
+            Offset(36.79791093245497, 52.88590941777883),
+            Offset(36.79772245845069, 53.021274808915365),
+            Offset(36.79758441807573, 53.14247036493895),
+            Offset(36.797488148804206, 53.25181934443066),
+            Offset(36.797426074710145, 53.351374286002354),
+            Offset(36.79739159858897, 53.44294120991781),
+            Offset(36.79737895089332, 53.5281175752586),
+            Offset(36.79738307228838, 53.60832148263455),
+            Offset(36.79739951722624, 53.68481556964314),
+            Offset(36.79742436471152, 53.7587295593099),
+            Offset(36.79745414717516, 53.83107808312845),
+            Offset(36.79748577699936, 53.90277982470875),
+            Offset(36.79751660719509, 53.97463627018816),
+            Offset(36.7975441640286, 54.04741071362252),
+            Offset(36.797566336361996, 54.121768596383546),
+            Offset(36.79758120646093, 54.19832738700239),
+            Offset(36.79758708492745, 54.27764432912612),
+            Offset(36.79758712071989, 54.35877252649494),
+            Offset(36.79758712071989, 54.439911871108684),
+            Offset(36.79758712071989, 54.52105121572244),
+            Offset(36.79758712071989, 54.522707120719886),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+          ],
+          <Offset>[
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+          ],
+          <Offset>[
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+          ],
+          <Offset>[
+            Offset(56.04837809389354, 52.900684331474906),
+            Offset(56.030300891829654, 52.8847340126718),
+            Offset(56.01222368976779, 52.86878369386669),
+            Offset(55.9941464877039, 52.85283337506358),
+            Offset(55.976069285642026, 52.83688305626048),
+            Offset(55.95799208358016, 52.820932737455365),
+            Offset(55.93991488151627, 52.80498241865226),
+            Offset(55.921837679454406, 52.78903209984916),
+            Offset(55.90376047739254, 52.77308178104404),
+            Offset(55.88568327532865, 52.757131462240935),
+            Offset(55.86760607326678, 52.741181143437835),
+            Offset(55.84952887120491, 52.72523082463272),
+            Offset(55.831451669141025, 52.70928050582962),
+            Offset(55.81337446707916, 52.69333018702651),
+            Offset(55.79529726501527, 52.6773798682214),
+            Offset(55.78900433275297, 52.672562732506236),
+            Offset(55.80942241203834, 52.69298081179161),
+            Offset(55.829840491323715, 52.713398891074974),
+            Offset(55.850258570607075, 52.73381697036035),
+            Offset(55.87067664989245, 52.75423504964572),
+            Offset(55.89109472917782, 52.77465312892909),
+            Offset(55.91151280846119, 52.795071208214466),
+            Offset(55.93193088774657, 52.81548928749984),
+            Offset(55.952348967031945, 52.8359073667832),
+            Offset(55.972767046315305, 52.856325446068574),
+            Offset(55.997486569314425, 52.87878031963378),
+            Offset(56.02633547827809, 52.9031905157102),
+            Offset(56.05518438724176, 52.9276007117846),
+            Offset(56.084033296203415, 52.952010907861016),
+            Offset(56.11288220516708, 52.97642110393542),
+            Offset(56.14173111413075, 53.00083130001184),
+            Offset(56.170580023094416, 53.025241496086245),
+            Offset(56.19942893205808, 53.04965169216266),
+            Offset(56.22827784102175, 53.07406188823707),
+            Offset(56.25712674998542, 53.098472084313485),
+            Offset(56.285975658949084, 53.122882280387884),
+            Offset(56.31482456791073, 53.1472924764643),
+            Offset(56.3436734768744, 53.17170267253871),
+            Offset(56.37252238583807, 53.196112868615124),
+            Offset(56.401371294801734, 53.22052306468953),
+            Offset(56.4302202037654, 53.24493326076595),
+            Offset(56.45906911272907, 53.26934345684035),
+            Offset(56.487918021692735, 53.29375365291677),
+            Offset(56.51676693065439, 53.318163848991176),
+            Offset(56.54561583961806, 53.342574045067586),
+            Offset(56.574464748581725, 53.36698424114199),
+            Offset(56.60331365754539, 53.39139443721841),
+            Offset(56.63216256650906, 53.415804633292815),
+            Offset(56.632751319752806, 53.41630280056001),
+          ],
+          <Offset>[
+            Offset(56.04837809389354, 52.900684331474906),
+            Offset(56.030300891829654, 52.8847340126718),
+            Offset(56.01222368976779, 52.86878369386669),
+            Offset(55.9941464877039, 52.85283337506358),
+            Offset(55.976069285642026, 52.83688305626048),
+            Offset(55.95799208358016, 52.820932737455365),
+            Offset(55.93991488151627, 52.80498241865226),
+            Offset(55.921837679454406, 52.78903209984916),
+            Offset(55.90376047739254, 52.77308178104404),
+            Offset(55.88568327532865, 52.757131462240935),
+            Offset(55.86760607326678, 52.741181143437835),
+            Offset(55.84952887120491, 52.72523082463272),
+            Offset(55.831451669141025, 52.70928050582962),
+            Offset(55.81337446707916, 52.69333018702651),
+            Offset(55.79529726501527, 52.6773798682214),
+            Offset(55.78900433275297, 52.672562732506236),
+            Offset(55.80942241203834, 52.69298081179161),
+            Offset(55.829840491323715, 52.713398891074974),
+            Offset(55.850258570607075, 52.73381697036035),
+            Offset(55.87067664989245, 52.75423504964572),
+            Offset(55.89109472917782, 52.77465312892909),
+            Offset(55.91151280846119, 52.795071208214466),
+            Offset(55.93193088774657, 52.81548928749984),
+            Offset(55.952348967031945, 52.8359073667832),
+            Offset(55.972767046315305, 52.856325446068574),
+            Offset(55.997486569314425, 52.87878031963378),
+            Offset(56.02633547827809, 52.9031905157102),
+            Offset(56.05518438724176, 52.9276007117846),
+            Offset(56.084033296203415, 52.952010907861016),
+            Offset(56.11288220516708, 52.97642110393542),
+            Offset(56.14173111413075, 53.00083130001184),
+            Offset(56.170580023094416, 53.025241496086245),
+            Offset(56.19942893205808, 53.04965169216266),
+            Offset(56.22827784102175, 53.07406188823707),
+            Offset(56.25712674998542, 53.098472084313485),
+            Offset(56.285975658949084, 53.122882280387884),
+            Offset(56.31482456791073, 53.1472924764643),
+            Offset(56.3436734768744, 53.17170267253871),
+            Offset(56.37252238583807, 53.196112868615124),
+            Offset(56.401371294801734, 53.22052306468953),
+            Offset(56.4302202037654, 53.24493326076595),
+            Offset(56.45906911272907, 53.26934345684035),
+            Offset(56.487918021692735, 53.29375365291677),
+            Offset(56.51676693065439, 53.318163848991176),
+            Offset(56.54561583961806, 53.342574045067586),
+            Offset(56.574464748581725, 53.36698424114199),
+            Offset(56.60331365754539, 53.39139443721841),
+            Offset(56.63216256650906, 53.415804633292815),
+            Offset(56.632751319752806, 53.41630280056001),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(56.04837809389354, 52.9006440978749),
+            Offset(56.030300891829654, 52.8846937790718),
+            Offset(56.01222368976779, 52.868743460266685),
+            Offset(55.9941464877039, 52.85279314146358),
+            Offset(55.976069285642026, 52.83684282266048),
+            Offset(55.95799208358016, 52.82089250385536),
+            Offset(55.93991488151627, 52.80494218505226),
+            Offset(55.921837679454406, 52.788991866249155),
+            Offset(55.90376047739254, 52.773041547444045),
+            Offset(55.88568327532865, 52.75709122864094),
+            Offset(55.86760607326678, 52.74114090983784),
+            Offset(55.84952887120491, 52.72519059103272),
+            Offset(55.831451669141025, 52.709240272229614),
+            Offset(55.81337446707916, 52.693289953426515),
+            Offset(55.79529726501527, 52.6773396346214),
+            Offset(55.78900433275297, 52.67252249890623),
+            Offset(55.80942241203834, 52.69294057819161),
+            Offset(55.829840491323715, 52.71335865747497),
+            Offset(55.850258570607075, 52.73377673676035),
+            Offset(55.87067664989245, 52.754194816045725),
+            Offset(55.89109472917782, 52.774612895329085),
+            Offset(55.91151280846119, 52.79503097461446),
+            Offset(55.93193088774657, 52.81544905389984),
+            Offset(55.952348967031945, 52.8358671331832),
+            Offset(55.972767046315305, 52.85628521246858),
+            Offset(55.997486569314425, 52.87874008603378),
+            Offset(56.02633547827809, 52.9031502821102),
+            Offset(56.05518438724176, 52.9275604781846),
+            Offset(56.084033296203415, 52.95197067426102),
+            Offset(56.11288220516708, 52.976380870335426),
+            Offset(56.14173111413075, 53.00079106641184),
+            Offset(56.170580023094416, 53.02520126248624),
+            Offset(56.19942893205808, 53.049611458562666),
+            Offset(56.22827784102175, 53.074021654637065),
+            Offset(56.25712674998542, 53.09843185071348),
+            Offset(56.285975658949084, 53.12284204678789),
+            Offset(56.31482456791073, 53.147252242864305),
+            Offset(56.3436734768744, 53.17166243893871),
+            Offset(56.37252238583807, 53.19607263501513),
+            Offset(56.401371294801734, 53.22048283108953),
+            Offset(56.4302202037654, 53.24489302716594),
+            Offset(56.45906911272907, 53.26930322324035),
+            Offset(56.487918021692735, 53.29371341931677),
+            Offset(56.51676693065439, 53.31812361539117),
+            Offset(56.54561583961806, 53.34253381146759),
+            Offset(56.574464748581725, 53.366944007541996),
+            Offset(56.60331365754539, 53.39135420361841),
+            Offset(56.63216256650906, 53.41576439969282),
+            Offset(56.632751319752806, 53.41626256696001),
+          ],
+          <Offset>[
+            Offset(53.21193262270276, 55.75726624798824),
+            Offset(53.193855420638876, 55.741315929185134),
+            Offset(53.17577821857701, 55.725365610380024),
+            Offset(53.15770101651312, 55.70941529157692),
+            Offset(53.139623814451255, 55.69346497277382),
+            Offset(53.12154661238938, 55.6775146539687),
+            Offset(53.1034694103255, 55.6615643351656),
+            Offset(53.08539220826363, 55.645614016362494),
+            Offset(53.06731500620175, 55.629663697557376),
+            Offset(53.049237804137874, 55.61371337875428),
+            Offset(53.031160602076, 55.59776305995117),
+            Offset(53.01308340001413, 55.58181274114605),
+            Offset(52.99500619795025, 55.56586242234295),
+            Offset(52.97692899588838, 55.549912103539846),
+            Offset(52.95885179382449, 55.533961784734736),
+            Offset(52.95255886156219, 55.52914464901957),
+            Offset(52.97297694084756, 55.54956272830495),
+            Offset(52.99339502013294, 55.56998080758831),
+            Offset(53.013813099416296, 55.590398886873686),
+            Offset(53.034231178701674, 55.610816966159064),
+            Offset(53.05464925798705, 55.63123504544242),
+            Offset(53.07506733727041, 55.6516531247278),
+            Offset(53.09548541655579, 55.67207120401317),
+            Offset(53.11590349584117, 55.69248928329654),
+            Offset(53.136321575124526, 55.712907362581916),
+            Offset(53.16104109812365, 55.73536223614712),
+            Offset(53.189890007087314, 55.759772432223535),
+            Offset(53.21873891605098, 55.78418262829794),
+            Offset(53.24758782501264, 55.80859282437436),
+            Offset(53.276436733976304, 55.83300302044876),
+            Offset(53.30528564293997, 55.857413216525174),
+            Offset(53.33413455190364, 55.88182341259958),
+            Offset(53.362983460867305, 55.906233608676),
+            Offset(53.39183236983097, 55.9306438047504),
+            Offset(53.42068127879464, 55.95505400082682),
+            Offset(53.449530187758306, 55.979464196901226),
+            Offset(53.47837909671996, 56.00387439297764),
+            Offset(53.50722800568363, 56.02828458905205),
+            Offset(53.53607691464729, 56.052694785128466),
+            Offset(53.56492582361096, 56.077104981202865),
+            Offset(53.59377473257462, 56.10151517727928),
+            Offset(53.6226236415383, 56.12592537335369),
+            Offset(53.65147255050196, 56.150335569430105),
+            Offset(53.68032145946361, 56.17474576550451),
+            Offset(53.70917036842728, 56.19915596158093),
+            Offset(53.73801927739095, 56.22356615765533),
+            Offset(53.766868186354614, 56.247976353731744),
+            Offset(53.79571709531828, 56.27238654980615),
+            Offset(53.79630584856203, 56.272884717073346),
+          ],
+          <Offset>[
+            Offset(53.21190192690009, 55.75726624798824),
+            Offset(53.19382472483621, 55.741315929185134),
+            Offset(53.17574752277434, 55.725365610380024),
+            Offset(53.15767032071045, 55.70941529157692),
+            Offset(53.139593118648584, 55.69346497277382),
+            Offset(53.121515916586716, 55.6775146539687),
+            Offset(53.10343871452283, 55.6615643351656),
+            Offset(53.08536151246096, 55.645614016362494),
+            Offset(53.06728431039909, 55.629663697557376),
+            Offset(53.04920710833521, 55.61371337875428),
+            Offset(53.031129906273335, 55.59776305995117),
+            Offset(53.01305270421147, 55.58181274114605),
+            Offset(52.99497550214758, 55.56586242234295),
+            Offset(52.97689830008571, 55.549912103539846),
+            Offset(52.95882109802183, 55.533961784734736),
+            Offset(52.95252816575952, 55.52914464901957),
+            Offset(52.972946245044895, 55.54956272830495),
+            Offset(52.993364324330265, 55.56998080758831),
+            Offset(53.01378240361363, 55.590398886873686),
+            Offset(53.03420048289901, 55.610816966159064),
+            Offset(53.05461856218438, 55.63123504544242),
+            Offset(53.07503664146775, 55.6516531247278),
+            Offset(53.095454720753125, 55.67207120401317),
+            Offset(53.115872800038495, 55.69248928329654),
+            Offset(53.13629087932186, 55.712907362581916),
+            Offset(53.161010402320976, 55.73536223614712),
+            Offset(53.18985931128464, 55.759772432223535),
+            Offset(53.21870822024831, 55.78418262829794),
+            Offset(53.247557129209966, 55.80859282437436),
+            Offset(53.27640603817363, 55.83300302044876),
+            Offset(53.3052549471373, 55.857413216525174),
+            Offset(53.33410385610097, 55.88182341259958),
+            Offset(53.362952765064634, 55.906233608676),
+            Offset(53.3918016740283, 55.9306438047504),
+            Offset(53.42065058299197, 55.95505400082682),
+            Offset(53.449499491955635, 55.979464196901226),
+            Offset(53.47834840091729, 56.00387439297764),
+            Offset(53.50719730988096, 56.02828458905205),
+            Offset(53.536046218844625, 56.052694785128466),
+            Offset(53.56489512780829, 56.077104981202865),
+            Offset(53.59374403677196, 56.10151517727928),
+            Offset(53.622592945735626, 56.12592537335369),
+            Offset(53.65144185469929, 56.150335569430105),
+            Offset(53.68029076366095, 56.17474576550451),
+            Offset(53.709139672624616, 56.19915596158093),
+            Offset(53.73798858158828, 56.22356615765533),
+            Offset(53.76683749055195, 56.247976353731744),
+            Offset(53.79568639951562, 56.27238654980615),
+            Offset(53.796275152759364, 56.272884717073346),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(53.21190192690009, 55.75722601438824),
+            Offset(53.19382472483621, 55.74127569558514),
+            Offset(53.17574752277434, 55.72532537678002),
+            Offset(53.15767032071045, 55.70937505797692),
+            Offset(53.139593118648584, 55.693424739173814),
+            Offset(53.121515916586716, 55.677474420368696),
+            Offset(53.10343871452283, 55.6615241015656),
+            Offset(53.08536151246096, 55.6455737827625),
+            Offset(53.06728431039909, 55.62962346395738),
+            Offset(53.04920710833521, 55.61367314515427),
+            Offset(53.031129906273335, 55.59772282635117),
+            Offset(53.01305270421147, 55.581772507546056),
+            Offset(52.99497550214758, 55.565822188742956),
+            Offset(52.97689830008571, 55.54987186993985),
+            Offset(52.95882109802183, 55.53392155113473),
+            Offset(52.95252816575952, 55.52910441541957),
+            Offset(52.972946245044895, 55.549522494704945),
+            Offset(52.993364324330265, 55.56994057398831),
+            Offset(53.01378240361363, 55.59035865327368),
+            Offset(53.03420048289901, 55.61077673255906),
+            Offset(53.05461856218438, 55.63119481184242),
+            Offset(53.07503664146775, 55.6516128911278),
+            Offset(53.095454720753125, 55.672030970413175),
+            Offset(53.115872800038495, 55.692449049696535),
+            Offset(53.13629087932186, 55.71286712898191),
+            Offset(53.161010402320976, 55.73532200254712),
+            Offset(53.18985931128464, 55.75973219862353),
+            Offset(53.21870822024831, 55.78414239469794),
+            Offset(53.247557129209966, 55.808552590774354),
+            Offset(53.27640603817363, 55.83296278684876),
+            Offset(53.3052549471373, 55.85737298292518),
+            Offset(53.33410385610097, 55.881783178999584),
+            Offset(53.362952765064634, 55.906193375076),
+            Offset(53.3918016740283, 55.9306035711504),
+            Offset(53.42065058299197, 55.955013767226816),
+            Offset(53.449499491955635, 55.97942396330122),
+            Offset(53.47834840091729, 56.00383415937764),
+            Offset(53.50719730988096, 56.028244355452046),
+            Offset(53.536046218844625, 56.05265455152846),
+            Offset(53.56489512780829, 56.07706474760286),
+            Offset(53.59374403677196, 56.101474943679285),
+            Offset(53.622592945735626, 56.125885139753684),
+            Offset(53.65144185469929, 56.15029533583011),
+            Offset(53.68029076366095, 56.17470553190451),
+            Offset(53.709139672624616, 56.199115727980924),
+            Offset(53.73798858158828, 56.22352592405533),
+            Offset(53.76683749055195, 56.24793612013175),
+            Offset(53.79568639951562, 56.27234631620615),
+            Offset(53.796275152759364, 56.27284448347334),
+          ],
+          <Offset>[
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856235894),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856235894),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+          ],
+          <Offset>[
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856235894),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856235894),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+          ],
+          <Offset>[
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243940005),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+          ],
+          <Offset>[
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243940005),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243940005),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+          ],
+          <Offset>[
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.53659283720165),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.5785472078895, 61.10082674001624),
+            Offset(57.412981276788415, 62.9439908225431),
+            Offset(59.24741534567123, 64.7871549050921),
+            Offset(61.081849414574165, 66.63031898762097),
+            Offset(62.91628348345698, 68.47348307014985),
+            Offset(64.75071755235992, 70.31664715269883),
+            Offset(66.58515162124274, 72.15981123522772),
+            Offset(68.41958569014567, 74.00297531775661),
+            Offset(70.2540197590285, 75.84613940028547),
+            Offset(72.08845382793142, 77.68930348283448),
+            Offset(73.03297916605787, 78.63832439892204),
+            Offset(73.12319212266141, 78.72896787526297),
+            Offset(73.21340507924484, 78.81961135158377),
+            Offset(73.30361803584839, 78.9102548279046),
+            Offset(73.39383099245194, 79.0008983042254),
+            Offset(73.48404394903537, 79.09154178054621),
+            Offset(73.5742569056389, 79.18218525686703),
+            Offset(73.66446986222233, 79.27282873318785),
+            Offset(73.75468281882588, 79.36347220952878),
+            Offset(73.84489577542942, 79.45411568584959),
+            Offset(73.93510873201285, 79.5447591621704),
+            Offset(74.0253216886164, 79.6354026384912),
+            Offset(74.11553464521995, 79.72604611481202),
+            Offset(74.20574760180338, 79.81668959113284),
+            Offset(74.29596055840692, 79.90733306745365),
+            Offset(74.38617351501047, 79.99797654379458),
+            Offset(74.4763864715939, 80.0886200201154),
+            Offset(74.56659942819743, 80.1792634964362),
+            Offset(74.65681238478088, 80.26990697275701),
+            Offset(74.74702534138441, 80.36055044907783),
+            Offset(74.83723829798797, 80.45119392539866),
+            Offset(74.9274512545714, 80.54183740171945),
+            Offset(75.01766421117493, 80.63248087806039),
+            Offset(75.0195052919199, 80.63433074491833),
+          ],
+          <Offset>[
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.53659283720165),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.5785472078895, 61.10082674001624),
+            Offset(57.412981276788415, 62.9439908225431),
+            Offset(59.24741534567123, 64.7871549050921),
+            Offset(61.081849414574165, 66.63031898762097),
+            Offset(62.91628348345698, 68.47348307014985),
+            Offset(64.75071755235992, 70.31664715269883),
+            Offset(66.58515162124274, 72.15981123522772),
+            Offset(68.41958569014567, 74.00297531775661),
+            Offset(70.2540197590285, 75.84613940028547),
+            Offset(72.08845382793142, 77.68930348283448),
+            Offset(73.03297916605787, 78.63832439892204),
+            Offset(73.12319212266141, 78.72896787526297),
+            Offset(73.21340507924484, 78.81961135158377),
+            Offset(73.30361803584839, 78.9102548279046),
+            Offset(73.39383099245194, 79.0008983042254),
+            Offset(73.48404394903537, 79.09154178054621),
+            Offset(73.5742569056389, 79.18218525686703),
+            Offset(73.66446986222233, 79.27282873318785),
+            Offset(73.75468281882588, 79.36347220952878),
+            Offset(73.84489577542942, 79.45411568584959),
+            Offset(73.93510873201285, 79.5447591621704),
+            Offset(74.0253216886164, 79.6354026384912),
+            Offset(74.11553464521995, 79.72604611481202),
+            Offset(74.20574760180338, 79.81668959113284),
+            Offset(74.29596055840692, 79.90733306745365),
+            Offset(74.38617351501047, 79.99797654379458),
+            Offset(74.4763864715939, 80.0886200201154),
+            Offset(74.56659942819743, 80.1792634964362),
+            Offset(74.65681238478088, 80.26990697275701),
+            Offset(74.74702534138441, 80.36055044907783),
+            Offset(74.83723829798797, 80.45119392539866),
+            Offset(74.9274512545714, 80.54183740171945),
+            Offset(75.01766421117493, 80.63248087806039),
+            Offset(75.0195052919199, 80.63433074491833),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.53659283720165),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.5785472078895, 61.10082674001624),
+            Offset(57.412981276788415, 62.9439908225431),
+            Offset(59.24741534567123, 64.7871549050921),
+            Offset(61.081849414574165, 66.63031898762097),
+            Offset(62.91628348345698, 68.47348307014985),
+            Offset(64.75071755235992, 70.31664715269883),
+            Offset(66.58515162124274, 72.15981123522772),
+            Offset(68.41958569014567, 74.00297531775661),
+            Offset(70.2540197590285, 75.84613940028547),
+            Offset(72.08845382793142, 77.68930348283448),
+            Offset(73.03297916605787, 78.63832439892204),
+            Offset(73.12319212266141, 78.72896787526297),
+            Offset(73.21340507924484, 78.81961135158377),
+            Offset(73.30361803584839, 78.9102548279046),
+            Offset(73.39383099245194, 79.0008983042254),
+            Offset(73.48404394903537, 79.09154178054621),
+            Offset(73.5742569056389, 79.18218525686703),
+            Offset(73.66446986222233, 79.27282873318785),
+            Offset(73.75468281882588, 79.36347220952878),
+            Offset(73.84489577542942, 79.45411568584959),
+            Offset(73.93510873201285, 79.5447591621704),
+            Offset(74.0253216886164, 79.6354026384912),
+            Offset(74.11553464521995, 79.72604611481202),
+            Offset(74.20574760180338, 79.81668959113284),
+            Offset(74.29596055840692, 79.90733306745365),
+            Offset(74.38617351501047, 79.99797654379458),
+            Offset(74.4763864715939, 80.0886200201154),
+            Offset(74.56659942819743, 80.1792634964362),
+            Offset(74.65681238478088, 80.26990697275701),
+            Offset(74.74702534138441, 80.36055044907783),
+            Offset(74.83723829798797, 80.45119392539866),
+            Offset(74.9274512545714, 80.54183740171945),
+            Offset(75.01766421117493, 80.63248087806039),
+            Offset(75.0195052919199, 80.63433074491833),
+          ],
+          <Offset>[
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.54176433622131),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.57337474962035, 55.105999198285396),
+            Offset(63.407805684985675, 56.94916641434585),
+            Offset(65.24223662033491, 58.79233363042842),
+            Offset(67.07666755572437, 60.63550084647077),
+            Offset(68.9110984910736, 62.47866806253323),
+            Offset(70.74552942644296, 64.32183527861581),
+            Offset(72.57996036179219, 66.16500249467828),
+            Offset(74.41439129716153, 68.00816971074073),
+            Offset(76.24882223253088, 69.85133692678309),
+            Offset(78.08325316790022, 71.69450414286567),
+            Offset(79.02777766633133, 72.64352589864858),
+            Offset(79.11799198534516, 72.73416801257923),
+            Offset(79.20820630433886, 72.82481012648977),
+            Offset(79.29842062333256, 72.91545224042041),
+            Offset(79.38863494234639, 73.00609435433094),
+            Offset(79.47884926134012, 73.09673646824147),
+            Offset(79.56906358035394, 73.18737858215201),
+            Offset(79.65927789932752, 73.27802069608266),
+            Offset(79.74949221834135, 73.36866281001332),
+            Offset(79.83970653735517, 73.45930492392384),
+            Offset(79.92992085632878, 73.5499470378545),
+            Offset(80.0201351753426, 73.64058915176503),
+            Offset(80.11034949435643, 73.73123126567556),
+            Offset(80.20056381333, 73.82187337960622),
+            Offset(80.29077813234383, 73.91251549351674),
+            Offset(80.38099245135766, 74.00315760744739),
+            Offset(80.47120677033126, 74.09379972137803),
+            Offset(80.56142108934507, 74.18444183528857),
+            Offset(80.65163540833879, 74.2750839491991),
+            Offset(80.74184972735262, 74.36572606310963),
+            Offset(80.83206404634632, 74.45636817704029),
+            Offset(80.92227836534002, 74.54701029095082),
+            Offset(81.01249268435384, 74.63765240488146),
+            Offset(81.01433379290023, 74.639502243938),
+          ],
+          <Offset>[
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.54176433622131),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.57337474962035, 55.105999198285396),
+            Offset(63.407805684985675, 56.94916641434585),
+            Offset(65.24223662033491, 58.79233363042842),
+            Offset(67.07666755572437, 60.63550084647077),
+            Offset(68.9110984910736, 62.47866806253323),
+            Offset(70.74552942644296, 64.32183527861581),
+            Offset(72.57996036179219, 66.16500249467828),
+            Offset(74.41439129716153, 68.00816971074073),
+            Offset(76.24882223253088, 69.85133692678309),
+            Offset(78.08325316790022, 71.69450414286567),
+            Offset(79.02777766633133, 72.64352589864858),
+            Offset(79.11799198534516, 72.73416801257923),
+            Offset(79.20820630433886, 72.82481012648977),
+            Offset(79.29842062333256, 72.91545224042041),
+            Offset(79.38863494234639, 73.00609435433094),
+            Offset(79.47884926134012, 73.09673646824147),
+            Offset(79.56906358035394, 73.18737858215201),
+            Offset(79.65927789932752, 73.27802069608266),
+            Offset(79.74949221834135, 73.36866281001332),
+            Offset(79.83970653735517, 73.45930492392384),
+            Offset(79.92992085632878, 73.5499470378545),
+            Offset(80.0201351753426, 73.64058915176503),
+            Offset(80.11034949435643, 73.73123126567556),
+            Offset(80.20056381333, 73.82187337960622),
+            Offset(80.29077813234383, 73.91251549351674),
+            Offset(80.38099245135766, 74.00315760744739),
+            Offset(80.47120677033126, 74.09379972137803),
+            Offset(80.56142108934507, 74.18444183528857),
+            Offset(80.65163540833879, 74.2750839491991),
+            Offset(80.74184972735262, 74.36572606310963),
+            Offset(80.83206404634632, 74.45636817704029),
+            Offset(80.92227836534002, 74.54701029095082),
+            Offset(81.01249268435384, 74.63765240488146),
+            Offset(81.01433379290023, 74.639502243938),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.54176433622131),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.57337474962035, 55.105999198285396),
+            Offset(63.407805684985675, 56.94916641434585),
+            Offset(65.24223662033491, 58.79233363042842),
+            Offset(67.07666755572437, 60.63550084647077),
+            Offset(68.9110984910736, 62.47866806253323),
+            Offset(70.74552942644296, 64.32183527861581),
+            Offset(72.57996036179219, 66.16500249467828),
+            Offset(74.41439129716153, 68.00816971074073),
+            Offset(76.24882223253088, 69.85133692678309),
+            Offset(78.08325316790022, 71.69450414286567),
+            Offset(79.02777766633133, 72.64352589864858),
+            Offset(79.11799198534516, 72.73416801257923),
+            Offset(79.20820630433886, 72.82481012648977),
+            Offset(79.29842062333256, 72.91545224042041),
+            Offset(79.38863494234639, 73.00609435433094),
+            Offset(79.47884926134012, 73.09673646824147),
+            Offset(79.56906358035394, 73.18737858215201),
+            Offset(79.65927789932752, 73.27802069608266),
+            Offset(79.74949221834135, 73.36866281001332),
+            Offset(79.83970653735517, 73.45930492392384),
+            Offset(79.92992085632878, 73.5499470378545),
+            Offset(80.0201351753426, 73.64058915176503),
+            Offset(80.11034949435643, 73.73123126567556),
+            Offset(80.20056381333, 73.82187337960622),
+            Offset(80.29077813234383, 73.91251549351674),
+            Offset(80.38099245135766, 74.00315760744739),
+            Offset(80.47120677033126, 74.09379972137803),
+            Offset(80.56142108934507, 74.18444183528857),
+            Offset(80.65163540833879, 74.2750839491991),
+            Offset(80.74184972735262, 74.36572606310963),
+            Offset(80.83206404634632, 74.45636817704029),
+            Offset(80.92227836534002, 74.54701029095082),
+            Offset(81.01249268435384, 74.63765240488146),
+            Offset(81.01433379290023, 74.639502243938),
+          ],
+          <Offset>[
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.522702243936166),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.937745291917885, 54.52270224394),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.93774529189977, 54.52270224395811),
+            Offset(60.93774529194001, 54.52270224393799),
+            Offset(60.937745291919896, 54.522702243937985),
+            Offset(60.937745291919896, 54.52270224395811),
+            Offset(60.93774529189977, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224391788),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224395811),
+            Offset(60.93774529191989, 54.522702243937985),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.93774529194001, 54.52270224391787),
+            Offset(60.93774529189977, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.93774529194001, 54.52270224393799),
+            Offset(60.93774529189978, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.93774529194002, 54.52270224393799),
+            Offset(60.93774529189978, 54.52270224393799),
+            Offset(60.937745291919896, 54.522702243938),
+            Offset(60.93774529194001, 54.52270224393799),
+            Offset(60.93774529189978, 54.52270224395811),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224391788),
+            Offset(60.937745291919896, 54.522702243938),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+          ],
+          <Offset>[
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.522702243936166),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.937745291917885, 54.52270224394),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.93774529189977, 54.52270224395811),
+            Offset(60.93774529194001, 54.52270224393799),
+            Offset(60.937745291919896, 54.522702243937985),
+            Offset(60.937745291919896, 54.522702243937985),
+            Offset(60.93774529189977, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224391788),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224395811),
+            Offset(60.93774529191989, 54.522702243937985),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.93774529194001, 54.52270224391787),
+            Offset(60.93774529189977, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.93774529194001, 54.52270224393799),
+            Offset(60.93774529189978, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.93774529194002, 54.52270224393799),
+            Offset(60.93774529189978, 54.52270224393799),
+            Offset(60.937745291919896, 54.522702243938),
+            Offset(60.93774529194001, 54.52270224393799),
+            Offset(60.93774529189978, 54.52270224395811),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224391788),
+            Offset(60.937745291919896, 54.522702243938),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.920754690484827),
+            Offset(48.0, 32.45354569710999),
+            Offset(48.0, 33.98010729644093),
+            Offset(48.0, 37.048746193280394),
+            Offset(48.0, 43.18831617722788),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.920754690484827),
+            Offset(52.445826306158004, 32.45354569710999),
+            Offset(52.445826306158004, 33.98010729644093),
+            Offset(52.445826306158004, 37.048746193280394),
+            Offset(52.445826306158004, 43.18831617722788),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+          ],
+          <Offset>[
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.319830300840163),
+            Offset(56.04672, 28.852621307465323),
+            Offset(56.04672, 30.379182906796263),
+            Offset(56.04672, 33.44782180363573),
+            Offset(56.04672, 39.587391787583215),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+          ],
+          <Offset>[
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.874034690484827),
+            Offset(56.04672, 24.406825697109987),
+            Offset(56.04672, 25.933387296440927),
+            Offset(56.04672, 29.002026193280393),
+            Offset(56.04672, 35.14159617722788),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.428208384326826),
+            Offset(56.04672, 19.960999390951986),
+            Offset(56.04672, 21.487560990282926),
+            Offset(56.04672, 24.556199887122393),
+            Offset(56.04672, 30.69576987106988),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+          ],
+          <Offset>[
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.827314690484826),
+            Offset(52.445826306158004, 16.360105697109987),
+            Offset(52.445826306158004, 17.886667296440926),
+            Offset(52.445826306158004, 20.955306193280393),
+            Offset(52.445826306158004, 27.09487617722788),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+          ],
+          <Offset>[
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.827314690484826),
+            Offset(48.0, 16.360105697109987),
+            Offset(48.0, 17.886667296440926),
+            Offset(48.0, 20.955306193280393),
+            Offset(48.0, 27.09487617722788),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.827314690484826),
+            Offset(43.554173693841996, 16.360105697109987),
+            Offset(43.554173693841996, 17.886667296440926),
+            Offset(43.554173693841996, 20.955306193280393),
+            Offset(43.554173693841996, 27.09487617722788),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+          ],
+          <Offset>[
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.428208384326826),
+            Offset(39.95328, 19.960999390951986),
+            Offset(39.95328, 21.487560990282926),
+            Offset(39.95328, 24.556199887122393),
+            Offset(39.95328, 30.69576987106988),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+          ],
+          <Offset>[
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.874034690484827),
+            Offset(39.95328, 24.406825697109987),
+            Offset(39.95328, 25.933387296440927),
+            Offset(39.95328, 29.002026193280393),
+            Offset(39.95328, 35.14159617722788),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.319830300840163),
+            Offset(39.95328, 28.852621307465323),
+            Offset(39.95328, 30.379182906796263),
+            Offset(39.95328, 33.44782180363573),
+            Offset(39.95328, 39.587391787583215),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+          ],
+          <Offset>[
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.920754690484827),
+            Offset(43.554173693841996, 32.45354569710999),
+            Offset(43.554173693841996, 33.98010729644093),
+            Offset(43.554173693841996, 37.048746193280394),
+            Offset(43.554173693841996, 43.18831617722788),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+          ],
+          <Offset>[
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.920754690484827),
+            Offset(48.0, 32.45354569710999),
+            Offset(48.0, 33.98010729644093),
+            Offset(48.0, 37.048746193280394),
+            Offset(48.0, 43.18831617722788),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+          ],
+          <Offset>[
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+          ],
+          <Offset>[
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+          ],
+          <Offset>[
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+          ],
+          <Offset>[
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+          ],
+          <Offset>[
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+          ],
+          <Offset>[
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+          ],
+          <Offset>[
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+          ],
+          <Offset>[
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.07926469426482),
+            Offset(48.0, 63.5472012869485),
+            Offset(48.0, 62.02272441694872),
+            Offset(48.0, 58.958276167797024),
+            Offset(48.0, 52.827090609197995),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.07926469426482),
+            Offset(43.554173693841996, 63.5472012869485),
+            Offset(43.554173693841996, 62.02272441694872),
+            Offset(43.554173693841996, 58.958276167797024),
+            Offset(43.554173693841996, 52.827090609197995),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+          ],
+          <Offset>[
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.68018908390948),
+            Offset(39.95328, 67.14812567659317),
+            Offset(39.95328, 65.62364880659338),
+            Offset(39.95328, 62.559200557441685),
+            Offset(39.95328, 56.428014998842656),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+          ],
+          <Offset>[
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.12598469426482),
+            Offset(39.95328, 71.5939212869485),
+            Offset(39.95328, 70.06944441694873),
+            Offset(39.95328, 67.00499616779703),
+            Offset(39.95328, 60.873810609197996),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.57181100042281),
+            Offset(39.95328, 76.0397475931065),
+            Offset(39.95328, 74.51527072310672),
+            Offset(39.95328, 71.45082247395503),
+            Offset(39.95328, 65.319636915356),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+          ],
+          <Offset>[
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.17270469426481),
+            Offset(43.554173693841996, 79.6406412869485),
+            Offset(43.554173693841996, 78.11616441694872),
+            Offset(43.554173693841996, 75.05171616779703),
+            Offset(43.554173693841996, 68.920530609198),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+          ],
+          <Offset>[
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.17270469426481),
+            Offset(48.0, 79.6406412869485),
+            Offset(48.0, 78.11616441694872),
+            Offset(48.0, 75.05171616779703),
+            Offset(48.0, 68.920530609198),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.17270469426481),
+            Offset(52.445826306158004, 79.6406412869485),
+            Offset(52.445826306158004, 78.11616441694872),
+            Offset(52.445826306158004, 75.05171616779703),
+            Offset(52.445826306158004, 68.920530609198),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+          ],
+          <Offset>[
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.57181100042281),
+            Offset(56.04672, 76.0397475931065),
+            Offset(56.04672, 74.51527072310672),
+            Offset(56.04672, 71.45082247395503),
+            Offset(56.04672, 65.319636915356),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+          ],
+          <Offset>[
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.12598469426482),
+            Offset(56.04672, 71.5939212869485),
+            Offset(56.04672, 70.06944441694873),
+            Offset(56.04672, 67.00499616779703),
+            Offset(56.04672, 60.873810609197996),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.68018908390948),
+            Offset(56.04672, 67.14812567659317),
+            Offset(56.04672, 65.62364880659338),
+            Offset(56.04672, 62.559200557441685),
+            Offset(56.04672, 56.428014998842656),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+          ],
+          <Offset>[
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.07926469426482),
+            Offset(52.445826306158004, 63.5472012869485),
+            Offset(52.445826306158004, 62.02272441694872),
+            Offset(52.445826306158004, 58.958276167797024),
+            Offset(52.445826306158004, 52.827090609197995),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+          ],
+          <Offset>[
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.07926469426482),
+            Offset(48.0, 63.5472012869485),
+            Offset(48.0, 62.02272441694872),
+            Offset(48.0, 58.958276167797024),
+            Offset(48.0, 52.827090609197995),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+  ],
+);
diff --git a/lib/src/material/animated_icons/data/event_add.g.dart b/lib/src/material/animated_icons/data/event_add.g.dart
new file mode 100644
index 0000000..8a012f2
--- /dev/null
+++ b/lib/src/material/animated_icons/data/event_add.g.dart
@@ -0,0 +1,3308 @@
+// Copyright 2014 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.
+
+// AUTOGENERATED FILE DO NOT EDIT!
+// This file was generated by vitool.
+part of material_animated_icons;
+const _AnimatedIconData _$event_add = _AnimatedIconData(
+  Size(48.0, 48.0),
+  <_PathFrames>[
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.761904761905,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(32.01015625, 23.52890625),
+            Offset(32.01015329719101, 23.53062658538017),
+            Offset(31.908309754160946, 24.86045986263573),
+            Offset(29.93347206506402, 28.868314694169428),
+            Offset(26.259363778077685, 30.74028679651635),
+            Offset(23.62410382175968, 30.58943972916481),
+            Offset(21.925014841592773, 29.92755775441251),
+            Offset(20.79646278990295, 29.175637008874478),
+            Offset(20.023435215871057, 28.454097084853878),
+            Offset(19.478915461565702, 27.800711501947685),
+            Offset(19.08488578667808, 27.224748870271775),
+            Offset(18.794354809526837, 26.724256833788246),
+            Offset(18.590826530804044, 26.293721762918267),
+            Offset(18.45085412812233, 25.930620946266075),
+            Offset(18.35542242952162, 25.63066280891873),
+            Offset(18.291196017351787, 25.38919913375952),
+            Offset(18.248665479323506, 25.201028049969374),
+            Offset(18.221259589085008, 25.06135592936411),
+            Offset(18.204520556081953, 24.965782149690927),
+            Offset(18.19555633054043, 24.91035753064884),
+            Offset(18.192643234510292, 24.891585410299637),
+            Offset(18.192636718750002, 24.891542968750002),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(32.01015625, 23.52890625),
+            Offset(32.01015329719101, 23.53062658538017),
+            Offset(31.908309754160946, 24.86045986263573),
+            Offset(29.93347206506402, 28.868314694169428),
+            Offset(26.259363778077685, 30.74028679651635),
+            Offset(23.62410382175968, 30.58943972916481),
+            Offset(21.925014841592773, 29.92755775441251),
+            Offset(20.79646278990295, 29.175637008874478),
+            Offset(20.023435215871057, 28.454097084853878),
+            Offset(19.478915461565702, 27.800711501947685),
+            Offset(19.08488578667808, 27.224748870271775),
+            Offset(18.794354809526837, 26.724256833788246),
+            Offset(18.590826530804044, 26.293721762918267),
+            Offset(18.45085412812233, 25.930620946266075),
+            Offset(18.35542242952162, 25.63066280891873),
+            Offset(18.291196017351787, 25.38919913375952),
+            Offset(18.248665479323506, 25.201028049969374),
+            Offset(18.221259589085008, 25.06135592936411),
+            Offset(18.204520556081953, 24.965782149690927),
+            Offset(18.19555633054043, 24.91035753064884),
+            Offset(18.192643234510292, 24.891585410299637),
+            Offset(18.192636718750002, 24.891542968750002),
+          ],
+          <Offset>[
+            Offset(24.01015625, 23.52890625),
+            Offset(24.010258478063488, 23.528888402424656),
+            Offset(24.091555200930664, 23.52418084688126),
+            Offset(24.377115816859927, 23.6152417705347),
+            Offset(24.633459690010874, 23.859538100350317),
+            Offset(24.736069637313253, 24.1352514457875),
+            Offset(24.72116824601048, 24.371795772576675),
+            Offset(24.648015807099657, 24.549452838288758),
+            Offset(24.55323690381452, 24.674789163937373),
+            Offset(24.455385525488182, 24.759322859315567),
+            Offset(24.363483168967523, 24.813428460885813),
+            Offset(24.28176718778124, 24.845878439805734),
+            Offset(24.21233699329442, 24.865919213504945),
+            Offset(24.154550207153598, 24.87807591728),
+            Offset(24.107302586424357, 24.885092784958154),
+            Offset(24.06957428023968, 24.88885460147735),
+            Offset(24.040355338731057, 24.890659603706283),
+            Offset(24.01876825740043, 24.89137581050446),
+            Offset(24.004045682792036, 24.891564195943896),
+            Offset(23.995525876462455, 24.89156214884712),
+            Offset(23.9926432343554, 24.891543022538777),
+            Offset(23.99263671875, 24.89154296875),
+          ],
+          <Offset>[
+            Offset(24.01015625, 23.52890625),
+            Offset(24.010258478063488, 23.528888402424656),
+            Offset(24.091555200930664, 23.52418084688126),
+            Offset(24.377115816859927, 23.6152417705347),
+            Offset(24.633459690010874, 23.859538100350317),
+            Offset(24.736069637313253, 24.1352514457875),
+            Offset(24.72116824601048, 24.371795772576675),
+            Offset(24.648015807099657, 24.549452838288758),
+            Offset(24.55323690381452, 24.674789163937373),
+            Offset(24.455385525488182, 24.759322859315567),
+            Offset(24.363483168967523, 24.813428460885813),
+            Offset(24.28176718778124, 24.845878439805734),
+            Offset(24.21233699329442, 24.865919213504945),
+            Offset(24.154550207153598, 24.87807591728),
+            Offset(24.107302586424357, 24.885092784958154),
+            Offset(24.06957428023968, 24.88885460147735),
+            Offset(24.040355338731057, 24.890659603706283),
+            Offset(24.01876825740043, 24.89137581050446),
+            Offset(24.004045682792036, 24.891564195943896),
+            Offset(23.995525876462455, 24.89156214884712),
+            Offset(23.9926432343554, 24.891543022538777),
+            Offset(23.99263671875, 24.89154296875),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(24.01015625, 23.52890625),
+            Offset(24.010258478063488, 23.528888402424656),
+            Offset(24.091555200930664, 23.52418084688126),
+            Offset(24.377115816859927, 23.6152417705347),
+            Offset(24.633459690010874, 23.859538100350317),
+            Offset(24.736069637313253, 24.1352514457875),
+            Offset(24.72116824601048, 24.371795772576675),
+            Offset(24.648015807099657, 24.549452838288758),
+            Offset(24.55323690381452, 24.674789163937373),
+            Offset(24.455385525488182, 24.759322859315567),
+            Offset(24.363483168967523, 24.813428460885813),
+            Offset(24.28176718778124, 24.845878439805734),
+            Offset(24.21233699329442, 24.865919213504945),
+            Offset(24.154550207153598, 24.87807591728),
+            Offset(24.107302586424357, 24.885092784958154),
+            Offset(24.06957428023968, 24.88885460147735),
+            Offset(24.040355338731057, 24.890659603706283),
+            Offset(24.01876825740043, 24.89137581050446),
+            Offset(24.004045682792036, 24.891564195943896),
+            Offset(23.995525876462455, 24.89156214884712),
+            Offset(23.9926432343554, 24.891543022538777),
+            Offset(23.99263671875, 24.89154296875),
+          ],
+          <Offset>[
+            Offset(24.01015625, 31.52890625),
+            Offset(24.008520295107978, 31.528783221552175),
+            Offset(22.755276185176193, 31.340935400111544),
+            Offset(19.124042893225194, 29.17159801873879),
+            Offset(17.752710993844843, 25.48544218841713),
+            Offset(18.281881353935944, 23.023285630233925),
+            Offset(19.165406264174642, 21.57564236815897),
+            Offset(20.021831636513937, 20.69789982109205),
+            Offset(20.773928982898013, 20.14498747599391),
+            Offset(21.41399688285607, 19.782852795393087),
+            Offset(21.95216275958156, 19.534831078596365),
+            Offset(22.40338879379873, 19.358466061551326),
+            Offset(22.7845344438811, 19.244408751014564),
+            Offset(23.10200517816752, 19.174379838248726),
+            Offset(23.36173256246378, 19.133212628055418),
+            Offset(23.569229747957515, 19.110476338589457),
+            Offset(23.729986892467963, 19.098969744298735),
+            Offset(23.84878813854078, 19.09386714218904),
+            Offset(23.929827729045, 19.092039069233813),
+            Offset(23.976730494660732, 19.091592602925097),
+            Offset(23.99260084659454, 19.09154302269367),
+            Offset(23.99263671875, 19.09154296875),
+          ],
+          <Offset>[
+            Offset(24.01015625, 31.52890625),
+            Offset(24.008520295107978, 31.528783221552175),
+            Offset(22.755276185176193, 31.340935400111544),
+            Offset(19.124042893225194, 29.17159801873879),
+            Offset(17.752710993844843, 25.48544218841713),
+            Offset(18.281881353935944, 23.023285630233925),
+            Offset(19.165406264174642, 21.57564236815897),
+            Offset(20.021831636513937, 20.69789982109205),
+            Offset(20.773928982898013, 20.14498747599391),
+            Offset(21.41399688285607, 19.782852795393087),
+            Offset(21.95216275958156, 19.534831078596365),
+            Offset(22.40338879379873, 19.358466061551326),
+            Offset(22.7845344438811, 19.244408751014564),
+            Offset(23.10200517816752, 19.174379838248726),
+            Offset(23.36173256246378, 19.133212628055418),
+            Offset(23.569229747957515, 19.110476338589457),
+            Offset(23.729986892467963, 19.098969744298735),
+            Offset(23.84878813854078, 19.09386714218904),
+            Offset(23.929827729045, 19.092039069233813),
+            Offset(23.976730494660732, 19.091592602925097),
+            Offset(23.99260084659454, 19.09154302269367),
+            Offset(23.99263671875, 19.09154296875),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(24.01015625, 31.52890625),
+            Offset(24.008520295107978, 31.528783221552175),
+            Offset(22.755276185176193, 31.340935400111544),
+            Offset(19.124042893225194, 29.17159801873879),
+            Offset(17.752710993844843, 25.48544218841713),
+            Offset(18.281881353935944, 23.023285630233925),
+            Offset(19.165406264174642, 21.57564236815897),
+            Offset(20.021831636513937, 20.69789982109205),
+            Offset(20.773928982898013, 20.14498747599391),
+            Offset(21.41399688285607, 19.782852795393087),
+            Offset(21.95216275958156, 19.534831078596365),
+            Offset(22.40338879379873, 19.358466061551326),
+            Offset(22.7845344438811, 19.244408751014564),
+            Offset(23.10200517816752, 19.174379838248726),
+            Offset(23.36173256246378, 19.133212628055418),
+            Offset(23.569229747957515, 19.110476338589457),
+            Offset(23.729986892467963, 19.098969744298735),
+            Offset(23.84878813854078, 19.09386714218904),
+            Offset(23.929827729045, 19.092039069233813),
+            Offset(23.976730494660732, 19.091592602925097),
+            Offset(23.99260084659454, 19.09154302269367),
+            Offset(23.99263671875, 19.09154296875),
+          ],
+          <Offset>[
+            Offset(32.01015625, 31.52890625),
+            Offset(32.0084151142355, 31.530521404507688),
+            Offset(30.572030738406475, 32.67721441586602),
+            Offset(24.68039914142929, 34.424670942373524),
+            Offset(19.378615081911654, 32.36619088458316),
+            Offset(17.16991553838237, 29.477473913611234),
+            Offset(16.369252859756937, 27.131404349994806),
+            Offset(16.17027861931723, 25.32408399167777),
+            Offset(16.244127294954552, 23.924295396910416),
+            Offset(16.437526818933584, 22.824241438025204),
+            Offset(16.673565377292114, 21.94615148798233),
+            Offset(16.915976415544325, 21.236844455533838),
+            Offset(17.16302398139072, 20.67221130042789),
+            Offset(17.39830909913625, 20.226924867234807),
+            Offset(17.609852405561046, 19.878782652015996),
+            Offset(17.79085148506962, 19.61082087087162),
+            Offset(17.938297033060415, 19.409338190561826),
+            Offset(18.05127947022536, 19.26384726104869),
+            Offset(18.13030260233492, 19.166257022980844),
+            Offset(18.17676094873871, 19.11038798472682),
+            Offset(18.192600846749432, 19.091585410454528),
+            Offset(18.19263671875, 19.09154296875),
+          ],
+          <Offset>[
+            Offset(32.01015625, 31.52890625),
+            Offset(32.0084151142355, 31.530521404507688),
+            Offset(30.572030738406475, 32.67721441586602),
+            Offset(24.68039914142929, 34.424670942373524),
+            Offset(19.378615081911654, 32.36619088458316),
+            Offset(17.16991553838237, 29.477473913611234),
+            Offset(16.369252859756937, 27.131404349994806),
+            Offset(16.17027861931723, 25.32408399167777),
+            Offset(16.244127294954552, 23.924295396910416),
+            Offset(16.437526818933584, 22.824241438025204),
+            Offset(16.673565377292114, 21.94615148798233),
+            Offset(16.915976415544325, 21.236844455533838),
+            Offset(17.16302398139072, 20.67221130042789),
+            Offset(17.39830909913625, 20.226924867234807),
+            Offset(17.609852405561046, 19.878782652015996),
+            Offset(17.79085148506962, 19.61082087087162),
+            Offset(17.938297033060415, 19.409338190561826),
+            Offset(18.05127947022536, 19.26384726104869),
+            Offset(18.13030260233492, 19.166257022980844),
+            Offset(18.17676094873871, 19.11038798472682),
+            Offset(18.192600846749432, 19.091585410454528),
+            Offset(18.19263671875, 19.09154296875),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(32.01015625, 31.52890625),
+            Offset(32.0084151142355, 31.530521404507688),
+            Offset(30.572030738406475, 32.67721441586602),
+            Offset(24.68039914142929, 34.424670942373524),
+            Offset(19.378615081911654, 32.36619088458316),
+            Offset(17.16991553838237, 29.477473913611234),
+            Offset(16.369252859756937, 27.131404349994806),
+            Offset(16.17027861931723, 25.32408399167777),
+            Offset(16.244127294954552, 23.924295396910416),
+            Offset(16.437526818933584, 22.824241438025204),
+            Offset(16.673565377292114, 21.94615148798233),
+            Offset(16.915976415544325, 21.236844455533838),
+            Offset(17.16302398139072, 20.67221130042789),
+            Offset(17.39830909913625, 20.226924867234807),
+            Offset(17.609852405561046, 19.878782652015996),
+            Offset(17.79085148506962, 19.61082087087162),
+            Offset(17.938297033060415, 19.409338190561826),
+            Offset(18.05127947022536, 19.26384726104869),
+            Offset(18.13030260233492, 19.166257022980844),
+            Offset(18.17676094873871, 19.11038798472682),
+            Offset(18.192600846749432, 19.091585410454528),
+            Offset(18.19263671875, 19.09154296875),
+          ],
+          <Offset>[
+            Offset(32.01015625, 23.52890625),
+            Offset(32.01015329719101, 23.53062658538017),
+            Offset(31.908309754160946, 24.86045986263573),
+            Offset(29.93347206506402, 28.868314694169428),
+            Offset(26.259363778077685, 30.74028679651635),
+            Offset(23.62410382175968, 30.58943972916481),
+            Offset(21.925014841592773, 29.92755775441251),
+            Offset(20.79646278990295, 29.175637008874478),
+            Offset(20.023435215871057, 28.454097084853878),
+            Offset(19.478915461565702, 27.800711501947685),
+            Offset(19.08488578667808, 27.224748870271775),
+            Offset(18.794354809526837, 26.724256833788246),
+            Offset(18.590826530804044, 26.293721762918267),
+            Offset(18.45085412812233, 25.930620946266075),
+            Offset(18.35542242952162, 25.63066280891873),
+            Offset(18.291196017351787, 25.38919913375952),
+            Offset(18.248665479323506, 25.201028049969374),
+            Offset(18.221259589085008, 25.06135592936411),
+            Offset(18.204520556081953, 24.965782149690927),
+            Offset(18.19555633054043, 24.91035753064884),
+            Offset(18.192643234510292, 24.891585410299637),
+            Offset(18.192636718750002, 24.891542968750002),
+          ],
+          <Offset>[
+            Offset(32.01015625, 23.52890625),
+            Offset(32.01015329719101, 23.53062658538017),
+            Offset(31.908309754160946, 24.86045986263573),
+            Offset(29.93347206506402, 28.868314694169428),
+            Offset(26.259363778077685, 30.74028679651635),
+            Offset(23.62410382175968, 30.58943972916481),
+            Offset(21.925014841592773, 29.92755775441251),
+            Offset(20.79646278990295, 29.175637008874478),
+            Offset(20.023435215871057, 28.454097084853878),
+            Offset(19.478915461565702, 27.800711501947685),
+            Offset(19.08488578667808, 27.224748870271775),
+            Offset(18.794354809526837, 26.724256833788246),
+            Offset(18.590826530804044, 26.293721762918267),
+            Offset(18.45085412812233, 25.930620946266075),
+            Offset(18.35542242952162, 25.63066280891873),
+            Offset(18.291196017351787, 25.38919913375952),
+            Offset(18.248665479323506, 25.201028049969374),
+            Offset(18.221259589085008, 25.06135592936411),
+            Offset(18.204520556081953, 24.965782149690927),
+            Offset(18.19555633054043, 24.91035753064884),
+            Offset(18.192643234510292, 24.891585410299637),
+            Offset(18.192636718750002, 24.891542968750002),
+          ],
+        ),
+        _PathClose(
+        ),
+        _PathMoveTo(
+          <Offset>[
+            Offset(30.4, 6.0),
+            Offset(30.403826772654973, 6.001600955318109),
+            Offset(33.26297392821128, 7.464112893661335),
+            Offset(40.3252246978808, 15.636425198410986),
+            Offset(41.008618677528034, 25.792861690388108),
+            Offset(37.989766327618334, 31.726851345525642),
+            Offset(34.66109919246733, 34.936041005423874),
+            Offset(31.70815663793754, 36.68371605880273),
+            Offset(29.216038057712375, 37.618746191043186),
+            Offset(27.14455430692518, 38.09258228629963),
+            Offset(25.430782781823723, 38.305428372486986),
+            Offset(24.014543572941673, 38.36973863661014),
+            Offset(22.850742435528595, 38.323714848128795),
+            Offset(21.905079756466474, 38.2162199326415),
+            Offset(21.146729034109512, 38.08362328363005),
+            Offset(20.550519051592694, 37.94957638018356),
+            Offset(20.09440860715983, 37.82880891010404),
+            Offset(19.760567137156492, 37.73014235182733),
+            Offset(19.53440820396981, 37.65826086700748),
+            Offset(19.40409629300435, 37.614964895879226),
+            Offset(19.360099392114897, 37.60003390984549),
+            Offset(19.360000000000003, 37.6),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(30.4, 6.0),
+            Offset(30.403826772654973, 6.001600955318109),
+            Offset(33.26297392821128, 7.464112893661335),
+            Offset(40.3252246978808, 15.636425198410986),
+            Offset(41.008618677528034, 25.792861690388108),
+            Offset(37.989766327618334, 31.726851345525642),
+            Offset(34.66109919246733, 34.936041005423874),
+            Offset(31.70815663793754, 36.68371605880273),
+            Offset(29.216038057712375, 37.618746191043186),
+            Offset(27.14455430692518, 38.09258228629963),
+            Offset(25.430782781823723, 38.305428372486986),
+            Offset(24.014543572941673, 38.36973863661014),
+            Offset(22.850742435528595, 38.323714848128795),
+            Offset(21.905079756466474, 38.2162199326415),
+            Offset(21.146729034109512, 38.08362328363005),
+            Offset(20.550519051592694, 37.94957638018356),
+            Offset(20.09440860715983, 37.82880891010404),
+            Offset(19.760567137156492, 37.73014235182733),
+            Offset(19.53440820396981, 37.65826086700748),
+            Offset(19.40409629300435, 37.614964895879226),
+            Offset(19.360099392114897, 37.60003390984549),
+            Offset(19.360000000000003, 37.6),
+          ],
+          <Offset>[
+            Offset(30.4, 9.2),
+            Offset(30.40313149947277, 9.201558882969117),
+            Offset(32.7284623219095, 10.590814714953448),
+            Offset(38.22399552842691, 17.858967697692623),
+            Offset(38.25631919906162, 26.44322332561483),
+            Offset(35.40809101426741, 31.282065019304213),
+            Offset(32.438794399733, 33.81757964365679),
+            Offset(29.857682969703255, 35.14309485192405),
+            Offset(27.704314889345774, 35.8068255158658),
+            Offset(25.92799884987233, 36.101994260730635),
+            Offset(24.466254618069335, 36.19398941957121),
+            Offset(23.263192215348667, 36.174773685308374),
+            Offset(22.279621415763266, 36.07511066313264),
+            Offset(21.484061744872044, 35.93474150102899),
+            Offset(20.84850102452528, 35.782871220868955),
+            Offset(20.35038123867983, 35.6382250750284),
+            Offset(19.970261228654596, 35.512132966341014),
+            Offset(19.692575089612635, 35.41113888450116),
+            Offset(19.504721022470996, 35.338450816323444),
+            Offset(19.39657814028366, 35.29497707751041),
+            Offset(19.360082437010554, 35.28003390990745),
+            Offset(19.36, 35.28),
+          ],
+          <Offset>[
+            Offset(30.4, 9.2),
+            Offset(30.40313149947277, 9.201558882969117),
+            Offset(32.7284623219095, 10.590814714953448),
+            Offset(38.22399552842691, 17.858967697692623),
+            Offset(38.25631919906162, 26.44322332561483),
+            Offset(35.40809101426741, 31.282065019304213),
+            Offset(32.438794399733, 33.81757964365679),
+            Offset(29.857682969703255, 35.14309485192405),
+            Offset(27.704314889345774, 35.8068255158658),
+            Offset(25.92799884987233, 36.101994260730635),
+            Offset(24.466254618069335, 36.19398941957121),
+            Offset(23.263192215348667, 36.174773685308374),
+            Offset(22.279621415763266, 36.07511066313264),
+            Offset(21.484061744872044, 35.93474150102899),
+            Offset(20.84850102452528, 35.782871220868955),
+            Offset(20.35038123867983, 35.6382250750284),
+            Offset(19.970261228654596, 35.512132966341014),
+            Offset(19.692575089612635, 35.41113888450116),
+            Offset(19.504721022470996, 35.338450816323444),
+            Offset(19.39657814028366, 35.29497707751041),
+            Offset(19.360082437010554, 35.28003390990745),
+            Offset(19.36, 35.28),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(30.4, 9.2),
+            Offset(30.40313149947277, 9.201558882969117),
+            Offset(32.7284623219095, 10.590814714953448),
+            Offset(38.22399552842691, 17.858967697692623),
+            Offset(38.25631919906162, 26.44322332561483),
+            Offset(35.40809101426741, 31.282065019304213),
+            Offset(32.438794399733, 33.81757964365679),
+            Offset(29.857682969703255, 35.14309485192405),
+            Offset(27.704314889345774, 35.8068255158658),
+            Offset(25.92799884987233, 36.101994260730635),
+            Offset(24.466254618069335, 36.19398941957121),
+            Offset(23.263192215348667, 36.174773685308374),
+            Offset(22.279621415763266, 36.07511066313264),
+            Offset(21.484061744872044, 35.93474150102899),
+            Offset(20.84850102452528, 35.782871220868955),
+            Offset(20.35038123867983, 35.6382250750284),
+            Offset(19.970261228654596, 35.512132966341014),
+            Offset(19.692575089612635, 35.41113888450116),
+            Offset(19.504721022470996, 35.338450816323444),
+            Offset(19.39657814028366, 35.29497707751041),
+            Offset(19.360082437010554, 35.28003390990745),
+            Offset(19.36, 35.28),
+          ],
+          <Offset>[
+            Offset(17.6, 9.2),
+            Offset(17.60329978886874, 9.1987777902403),
+            Offset(20.22165503674104, 8.452768289746293),
+            Offset(29.333825531300356, 9.454051019877056),
+            Offset(35.65487265815472, 15.434025411749177),
+            Offset(37.18723631915313, 20.955363765900515),
+            Offset(36.912639846801326, 24.928360472719458),
+            Offset(36.02016779721799, 27.741200178986897),
+            Offset(34.95199759005531, 29.759932842399394),
+            Offset(33.8903509521483, 31.235772432519248),
+            Offset(32.91201042973245, 32.335876764553674),
+            Offset(32.04305202055572, 33.16936825493635),
+            Offset(31.274038155747874, 33.790626584071326),
+            Offset(30.609975471322077, 34.250669454651266),
+            Offset(30.051509275569657, 34.589959182532034),
+            Offset(29.595786459300463, 34.83767382337693),
+            Offset(29.236965003706672, 35.01554345232007),
+            Offset(28.96858895891731, 35.13917069432573),
+            Offset(28.78396122520713, 35.21970209032819),
+            Offset(28.6765294137589, 35.264904466627655),
+            Offset(28.640082436762725, 35.279966089490074),
+            Offset(28.64, 35.28),
+          ],
+          <Offset>[
+            Offset(17.6, 9.2),
+            Offset(17.60329978886874, 9.1987777902403),
+            Offset(20.22165503674104, 8.452768289746293),
+            Offset(29.333825531300356, 9.454051019877056),
+            Offset(35.65487265815472, 15.434025411749177),
+            Offset(37.18723631915313, 20.955363765900515),
+            Offset(36.912639846801326, 24.928360472719458),
+            Offset(36.02016779721799, 27.741200178986897),
+            Offset(34.95199759005531, 29.759932842399394),
+            Offset(33.8903509521483, 31.235772432519248),
+            Offset(32.91201042973245, 32.335876764553674),
+            Offset(32.04305202055572, 33.16936825493635),
+            Offset(31.274038155747874, 33.790626584071326),
+            Offset(30.609975471322077, 34.250669454651266),
+            Offset(30.051509275569657, 34.589959182532034),
+            Offset(29.595786459300463, 34.83767382337693),
+            Offset(29.236965003706672, 35.01554345232007),
+            Offset(28.96858895891731, 35.13917069432573),
+            Offset(28.78396122520713, 35.21970209032819),
+            Offset(28.6765294137589, 35.264904466627655),
+            Offset(28.640082436762725, 35.279966089490074),
+            Offset(28.64, 35.28),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(17.6, 9.2),
+            Offset(17.60329978886874, 9.1987777902403),
+            Offset(20.22165503674104, 8.452768289746293),
+            Offset(29.333825531300356, 9.454051019877056),
+            Offset(35.65487265815472, 15.434025411749177),
+            Offset(37.18723631915313, 20.955363765900515),
+            Offset(36.912639846801326, 24.928360472719458),
+            Offset(36.02016779721799, 27.741200178986897),
+            Offset(34.95199759005531, 29.759932842399394),
+            Offset(33.8903509521483, 31.235772432519248),
+            Offset(32.91201042973245, 32.335876764553674),
+            Offset(32.04305202055572, 33.16936825493635),
+            Offset(31.274038155747874, 33.790626584071326),
+            Offset(30.609975471322077, 34.250669454651266),
+            Offset(30.051509275569657, 34.589959182532034),
+            Offset(29.595786459300463, 34.83767382337693),
+            Offset(29.236965003706672, 35.01554345232007),
+            Offset(28.96858895891731, 35.13917069432573),
+            Offset(28.78396122520713, 35.21970209032819),
+            Offset(28.6765294137589, 35.264904466627655),
+            Offset(28.640082436762725, 35.279966089490074),
+            Offset(28.64, 35.28),
+          ],
+          <Offset>[
+            Offset(17.6, 6.0),
+            Offset(17.60399506205094, 5.99881986258929),
+            Offset(20.75616664304283, 5.326066468454183),
+            Offset(31.43505470075425, 7.231508520595419),
+            Offset(38.40717213662114, 14.783663776522452),
+            Offset(39.76891163250406, 21.400150092121944),
+            Offset(39.13494463953566, 26.04682183448654),
+            Offset(37.87064146545227, 29.28182138586558),
+            Offset(36.463720758421914, 31.571853517576777),
+            Offset(35.106906409201144, 33.22636045808824),
+            Offset(33.876538593486835, 34.44731571746945),
+            Offset(32.79440337814872, 35.364333206238115),
+            Offset(31.845159175513203, 36.03923076906747),
+            Offset(31.030993482916507, 36.53214788626377),
+            Offset(30.349737285153886, 36.890711245293126),
+            Offset(29.795924272213327, 37.14902512853209),
+            Offset(29.36111238221191, 37.33221939608309),
+            Offset(29.03658100646117, 37.4581741616519),
+            Offset(28.81364840670594, 37.539512141012224),
+            Offset(28.684047566479585, 37.58489228499647),
+            Offset(28.64009939186707, 37.59996608942812),
+            Offset(28.64, 37.6),
+          ],
+          <Offset>[
+            Offset(17.6, 6.0),
+            Offset(17.60399506205094, 5.99881986258929),
+            Offset(20.75616664304283, 5.326066468454183),
+            Offset(31.43505470075425, 7.231508520595419),
+            Offset(38.40717213662114, 14.783663776522452),
+            Offset(39.76891163250406, 21.400150092121944),
+            Offset(39.13494463953566, 26.04682183448654),
+            Offset(37.87064146545227, 29.28182138586558),
+            Offset(36.463720758421914, 31.571853517576777),
+            Offset(35.106906409201144, 33.22636045808824),
+            Offset(33.876538593486835, 34.44731571746945),
+            Offset(32.79440337814872, 35.364333206238115),
+            Offset(31.845159175513203, 36.03923076906747),
+            Offset(31.030993482916507, 36.53214788626377),
+            Offset(30.349737285153886, 36.890711245293126),
+            Offset(29.795924272213327, 37.14902512853209),
+            Offset(29.36111238221191, 37.33221939608309),
+            Offset(29.03658100646117, 37.4581741616519),
+            Offset(28.81364840670594, 37.539512141012224),
+            Offset(28.684047566479585, 37.58489228499647),
+            Offset(28.64009939186707, 37.59996608942812),
+            Offset(28.64, 37.6),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(17.6, 6.0),
+            Offset(17.60399506205094, 5.99881986258929),
+            Offset(20.75616664304283, 5.326066468454183),
+            Offset(31.43505470075425, 7.231508520595419),
+            Offset(38.40717213662114, 14.783663776522452),
+            Offset(39.76891163250406, 21.400150092121944),
+            Offset(39.13494463953566, 26.04682183448654),
+            Offset(37.87064146545227, 29.28182138586558),
+            Offset(36.463720758421914, 31.571853517576777),
+            Offset(35.106906409201144, 33.22636045808824),
+            Offset(33.876538593486835, 34.44731571746945),
+            Offset(32.79440337814872, 35.364333206238115),
+            Offset(31.845159175513203, 36.03923076906747),
+            Offset(31.030993482916507, 36.53214788626377),
+            Offset(30.349737285153886, 36.890711245293126),
+            Offset(29.795924272213327, 37.14902512853209),
+            Offset(29.36111238221191, 37.33221939608309),
+            Offset(29.03658100646117, 37.4581741616519),
+            Offset(28.81364840670594, 37.539512141012224),
+            Offset(28.684047566479585, 37.58489228499647),
+            Offset(28.64009939186707, 37.59996608942812),
+            Offset(28.64, 37.6),
+          ],
+          <Offset>[
+            Offset(14.399999999999999, 6.0),
+            Offset(14.404037134399934, 5.998124589407087),
+            Offset(17.629464821750716, 4.791554862152392),
+            Offset(29.212512201472613, 5.130279351141528),
+            Offset(37.75681050139441, 12.03136429805604),
+            Offset(40.21369795872548, 18.81847477877102),
+            Offset(40.25340600130274, 23.824517041752205),
+            Offset(39.411262672330956, 27.43134771763129),
+            Offset(38.2756414335993, 30.060130349210176),
+            Offset(37.09749443477014, 32.009805001035396),
+            Offset(35.98797754640261, 33.48278755371506),
+            Offset(34.989368329450485, 34.61298184864511),
+            Offset(34.09376336050936, 35.468109749302144),
+            Offset(33.31247191452901, 36.111129874669345),
+            Offset(32.650489347914984, 36.5924832357089),
+            Offset(32.10727557736848, 36.948887315619224),
+            Offset(31.67778832597493, 37.20807201757785),
+            Offset(31.35558447378734, 37.390182114108036),
+            Offset(31.133458457389974, 37.50982495951341),
+            Offset(31.004035384848397, 37.57737413227578),
+            Offset(30.960099391805116, 37.59994913432378),
+            Offset(30.96, 37.6),
+          ],
+          <Offset>[
+            Offset(14.399999999999999, 6.0),
+            Offset(14.404037134399934, 5.998124589407087),
+            Offset(17.629464821750716, 4.791554862152392),
+            Offset(29.212512201472613, 5.130279351141528),
+            Offset(37.75681050139441, 12.03136429805604),
+            Offset(40.21369795872548, 18.81847477877102),
+            Offset(40.25340600130274, 23.824517041752205),
+            Offset(39.411262672330956, 27.43134771763129),
+            Offset(38.2756414335993, 30.060130349210176),
+            Offset(37.09749443477014, 32.009805001035396),
+            Offset(35.98797754640261, 33.48278755371506),
+            Offset(34.989368329450485, 34.61298184864511),
+            Offset(34.09376336050936, 35.468109749302144),
+            Offset(33.31247191452901, 36.111129874669345),
+            Offset(32.650489347914984, 36.5924832357089),
+            Offset(32.10727557736848, 36.948887315619224),
+            Offset(31.67778832597493, 37.20807201757785),
+            Offset(31.35558447378734, 37.390182114108036),
+            Offset(31.133458457389974, 37.50982495951341),
+            Offset(31.004035384848397, 37.57737413227578),
+            Offset(30.960099391805116, 37.59994913432378),
+            Offset(30.96, 37.6),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(14.399999999999999, 6.0),
+            Offset(14.404037134399934, 5.998124589407087),
+            Offset(17.629464821750716, 4.791554862152392),
+            Offset(29.212512201472613, 5.130279351141528),
+            Offset(37.75681050139441, 12.03136429805604),
+            Offset(40.21369795872548, 18.81847477877102),
+            Offset(40.25340600130274, 23.824517041752205),
+            Offset(39.411262672330956, 27.43134771763129),
+            Offset(38.2756414335993, 30.060130349210176),
+            Offset(37.09749443477014, 32.009805001035396),
+            Offset(35.98797754640261, 33.48278755371506),
+            Offset(34.989368329450485, 34.61298184864511),
+            Offset(34.09376336050936, 35.468109749302144),
+            Offset(33.31247191452901, 36.111129874669345),
+            Offset(32.650489347914984, 36.5924832357089),
+            Offset(32.10727557736848, 36.948887315619224),
+            Offset(31.67778832597493, 37.20807201757785),
+            Offset(31.35558447378734, 37.390182114108036),
+            Offset(31.133458457389974, 37.50982495951341),
+            Offset(31.004035384848397, 37.57737413227578),
+            Offset(30.960099391805116, 37.59994913432378),
+            Offset(30.96, 37.6),
+          ],
+          <Offset>[
+            Offset(14.399999999999999, 9.2),
+            Offset(14.40334186121773, 9.198082517058094),
+            Offset(17.09495321544893, 7.918256683444506),
+            Offset(27.11128303201872, 7.3528218504231635),
+            Offset(35.004511022928, 12.681725933282765),
+            Offset(37.63202264537456, 18.37368845254959),
+            Offset(38.03110120856841, 22.706055679985123),
+            Offset(37.56078900409667, 25.890726510752607),
+            Offset(36.7639182652327, 28.248209674032793),
+            Offset(35.88093897771729, 30.019216975466403),
+            Offset(35.023449382648224, 31.371348600799287),
+            Offset(34.238016971857476, 32.41801689734335),
+            Offset(33.52264234074403, 33.21950556430599),
+            Offset(32.891453902934586, 33.82965144305683),
+            Offset(32.35226133833075, 34.2917311729478),
+            Offset(31.90713776445562, 34.63753601046407),
+            Offset(31.55364094746969, 34.89139607381483),
+            Offset(31.287592426243478, 35.07117864678187),
+            Offset(31.10377127589116, 35.19001490882938),
+            Offset(30.996517232127708, 35.25738631390697),
+            Offset(30.96008243670077, 35.27994913438573),
+            Offset(30.96, 35.28),
+          ],
+          <Offset>[
+            Offset(14.399999999999999, 9.2),
+            Offset(14.40334186121773, 9.198082517058094),
+            Offset(17.09495321544893, 7.918256683444506),
+            Offset(27.11128303201872, 7.3528218504231635),
+            Offset(35.004511022928, 12.681725933282765),
+            Offset(37.63202264537456, 18.37368845254959),
+            Offset(38.03110120856841, 22.706055679985123),
+            Offset(37.56078900409667, 25.890726510752607),
+            Offset(36.7639182652327, 28.248209674032793),
+            Offset(35.88093897771729, 30.019216975466403),
+            Offset(35.023449382648224, 31.371348600799287),
+            Offset(34.238016971857476, 32.41801689734335),
+            Offset(33.52264234074403, 33.21950556430599),
+            Offset(32.891453902934586, 33.82965144305683),
+            Offset(32.35226133833075, 34.2917311729478),
+            Offset(31.90713776445562, 34.63753601046407),
+            Offset(31.55364094746969, 34.89139607381483),
+            Offset(31.287592426243478, 35.07117864678187),
+            Offset(31.10377127589116, 35.19001490882938),
+            Offset(30.996517232127708, 35.25738631390697),
+            Offset(30.96008243670077, 35.27994913438573),
+            Offset(30.96, 35.28),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(14.399999999999999, 9.2),
+            Offset(14.40334186121773, 9.198082517058094),
+            Offset(17.09495321544893, 7.918256683444506),
+            Offset(27.11128303201872, 7.3528218504231635),
+            Offset(35.004511022928, 12.681725933282765),
+            Offset(37.63202264537456, 18.37368845254959),
+            Offset(38.03110120856841, 22.706055679985123),
+            Offset(37.56078900409667, 25.890726510752607),
+            Offset(36.7639182652327, 28.248209674032793),
+            Offset(35.88093897771729, 30.019216975466403),
+            Offset(35.023449382648224, 31.371348600799287),
+            Offset(34.238016971857476, 32.41801689734335),
+            Offset(33.52264234074403, 33.21950556430599),
+            Offset(32.891453902934586, 33.82965144305683),
+            Offset(32.35226133833075, 34.2917311729478),
+            Offset(31.90713776445562, 34.63753601046407),
+            Offset(31.55364094746969, 34.89139607381483),
+            Offset(31.287592426243478, 35.07117864678187),
+            Offset(31.10377127589116, 35.19001490882938),
+            Offset(30.996517232127708, 35.25738631390697),
+            Offset(30.96008243670077, 35.27994913438573),
+            Offset(30.96, 35.28),
+          ],
+          <Offset>[
+            Offset(12.799999999999999, 9.2),
+            Offset(12.803362897392224, 9.197734880466992),
+            Offset(15.531602304802872, 7.651000880293612),
+            Offset(26.0000117823779, 6.302207265696218),
+            Offset(34.67933020531464, 11.305576194049557),
+            Offset(37.85441580848527, 17.08285079587413),
+            Offset(38.590331889451946, 21.594903283617956),
+            Offset(38.33109960753601, 24.965489676635464),
+            Offset(37.669878602821385, 27.49234808984949),
+            Offset(36.87623299050179, 29.41093924693998),
+            Offset(36.07916885910612, 30.889084518922097),
+            Offset(35.33549944750836, 32.042341218546845),
+            Offset(34.646944433242105, 32.93394505442333),
+            Offset(34.032193118740835, 33.61914243725962),
+            Offset(33.502637369711294, 34.14261716815569),
+            Offset(33.0628134170332, 34.53746710400763),
+            Offset(32.7119789193512, 34.82932238456221),
+            Offset(32.44709415990656, 35.03718262300993),
+            Offset(32.263676301233176, 35.175171318079975),
+            Offset(32.15651114131211, 35.253627237546624),
+            Offset(32.12008243666979, 35.27994065683356),
+            Offset(32.120000000000005, 35.28),
+          ],
+          <Offset>[
+            Offset(12.799999999999999, 9.2),
+            Offset(12.803362897392224, 9.197734880466992),
+            Offset(15.531602304802872, 7.651000880293612),
+            Offset(26.0000117823779, 6.302207265696218),
+            Offset(34.67933020531464, 11.305576194049557),
+            Offset(37.85441580848527, 17.08285079587413),
+            Offset(38.590331889451946, 21.594903283617956),
+            Offset(38.33109960753601, 24.965489676635464),
+            Offset(37.669878602821385, 27.49234808984949),
+            Offset(36.87623299050179, 29.41093924693998),
+            Offset(36.07916885910612, 30.889084518922097),
+            Offset(35.33549944750836, 32.042341218546845),
+            Offset(34.646944433242105, 32.93394505442333),
+            Offset(34.032193118740835, 33.61914243725962),
+            Offset(33.502637369711294, 34.14261716815569),
+            Offset(33.0628134170332, 34.53746710400763),
+            Offset(32.7119789193512, 34.82932238456221),
+            Offset(32.44709415990656, 35.03718262300993),
+            Offset(32.263676301233176, 35.175171318079975),
+            Offset(32.15651114131211, 35.253627237546624),
+            Offset(32.12008243666979, 35.27994065683356),
+            Offset(32.120000000000005, 35.28),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(11.031994628903998, 9.2),
+            Offset(11.035380771339659, 9.197350740866831),
+            Offset(13.80409430047159, 7.355682320651514),
+            Offset(24.772053321059442, 5.141274622728069),
+            Offset(34.320004310241, 9.784926112551638),
+            Offset(38.10016100028201, 15.656470851989635),
+            Offset(39.20828366912931, 20.36707615556587),
+            Offset(39.18229541021911, 23.94309986897611),
+            Offset(38.670967817106856, 26.657118501948734),
+            Offset(37.976036215765966, 28.738790314969485),
+            Offset(37.24574242457374, 30.35618108951862),
+            Offset(36.54822126727992, 31.627218332357877),
+            Offset(35.88930201966152, 32.61839973239491),
+            Offset(35.29271378159415, 33.38652927918864),
+            Offset(34.773806746124365, 33.97784569229438),
+            Offset(34.33983889265947, 34.42689062644846),
+            Offset(33.99194626674555, 34.76073074956073),
+            Offset(33.72834746797622, 34.99961690261951),
+            Offset(33.545375247961886, 35.15876910047291),
+            Offset(33.438308304985036, 35.24947344554947),
+            Offset(33.40188633068016, 35.27993128910995),
+            Offset(33.401803894044605, 35.28),
+          ],
+          <Offset>[
+            Offset(9.616003417967999, 10.631994628904),
+            Offset(9.61909704372049, 10.629018885991774),
+            Offset(12.181344285634644, 8.518356219904028),
+            Offset(22.84828784303791, 5.2060705244071706),
+            Offset(32.80057167595657, 8.858076892900925),
+            Offset(37.14168235852622, 14.315045482191389),
+            Offset(38.70872208508259, 18.883202806434653),
+            Offset(39.03593220224475, 22.434844949065443),
+            Offset(38.79624415881739, 25.177353691127),
+            Offset(38.31245942468365, 27.309683066288713),
+            Offset(37.74842362764679, 28.984514638765333),
+            Offset(37.18325875817459, 30.31250428874126),
+            Offset(36.6287274978085, 31.35943365120302),
+            Offset(36.11385686779049, 32.17927219665962),
+            Offset(35.65842668096787, 32.816297930813164),
+            Offset(35.27304416151989, 33.30400436440065),
+            Offset(34.96151326541533, 33.66908727918451),
+            Offset(34.72407380578028, 33.931780449070615),
+            Offset(34.55859985994813, 34.10752150024265),
+            Offset(34.46153218185176, 34.2079560289239),
+            Offset(34.42847237120061, 34.241727680595176),
+            Offset(34.4283975219732, 34.241803894044594),
+          ],
+          <Offset>[
+            Offset(9.616003417967999, 12.399999999999999),
+            Offset(9.61871290412033, 12.397001012044338),
+            Offset(11.886025725992546, 10.245864224235309),
+            Offset(21.68735520006976, 6.434028985725625),
+            Offset(31.279921594458653, 9.217402787974557),
+            Offset(35.71530241464173, 14.069300290394656),
+            Offset(37.4808949570305, 18.265251026757294),
+            Offset(38.01354239458539, 21.58364914638235),
+            Offset(37.96101457091663, 24.176264476841528),
+            Offset(37.64031049271316, 26.209879841024538),
+            Offset(37.21552019824331, 27.817941073297703),
+            Offset(36.76813587198562, 29.0997824689697),
+            Offset(36.313182175780085, 30.117076064783603),
+            Offset(35.88124370971952, 30.918751533806304),
+            Offset(35.49365520510656, 31.545128554400094),
+            Offset(35.16246768396071, 32.02697888877437),
+            Offset(34.892921630413845, 32.38911993179016),
+            Offset(34.68650808538985, 32.65052714100095),
+            Offset(34.542197642341065, 32.825822553513945),
+            Offset(34.4573783898546, 32.92615886525098),
+            Offset(34.428463003477, 32.9599237865848),
+            Offset(34.428397521973196, 32.96),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(9.616003417967999, 12.399999999999999),
+            Offset(9.61871290412033, 12.397001012044338),
+            Offset(11.886025725992546, 10.245864224235309),
+            Offset(21.68735520006976, 6.434028985725625),
+            Offset(31.279921594458653, 9.217402787974557),
+            Offset(35.71530241464173, 14.069300290394656),
+            Offset(37.4808949570305, 18.265251026757294),
+            Offset(38.01354239458539, 21.58364914638235),
+            Offset(37.96101457091663, 24.176264476841528),
+            Offset(37.64031049271316, 26.209879841024538),
+            Offset(37.21552019824331, 27.817941073297703),
+            Offset(36.76813587198562, 29.0997824689697),
+            Offset(36.313182175780085, 30.117076064783603),
+            Offset(35.88124370971952, 30.918751533806304),
+            Offset(35.49365520510656, 31.545128554400094),
+            Offset(35.16246768396071, 32.02697888877437),
+            Offset(34.892921630413845, 32.38911993179016),
+            Offset(34.68650808538985, 32.65052714100095),
+            Offset(34.542197642341065, 32.825822553513945),
+            Offset(34.4573783898546, 32.92615886525098),
+            Offset(34.428463003477, 32.9599237865848),
+            Offset(34.428397521973196, 32.96),
+          ],
+          <Offset>[
+            Offset(9.59999999999928, 34.8),
+            Offset(9.59784278428286, 34.79670302849285),
+            Offset(8.12880763309575, 32.13010384432861),
+            Offset(6.967635927464624, 21.981318090494952),
+            Offset(12.01057274235646, 13.75616979739632),
+            Offset(17.64579962789941, 10.942884872751186),
+            Offset(21.93035490934461, 10.42490759674647),
+            Offset(25.067931468540735, 10.790046353371297),
+            Offset(27.388013931066364, 11.485259520063472),
+            Offset(29.134377359648454, 12.26967958533492),
+            Offset(30.47438250198729, 13.033044731841269),
+            Offset(31.519653538066578, 13.731270250539573),
+            Offset(32.32658046011612, 14.373990554688655),
+            Offset(32.945527457598416, 14.946296972765026),
+            Offset(33.41756535579906, 15.438372656482638),
+            Offset(33.77306221888595, 15.846518849853458),
+            Offset(34.035475835072845, 16.171767455952793),
+            Offset(34.22216124688238, 16.41716283685671),
+            Offset(34.34598889992722, 16.58700373110889),
+            Offset(34.416353737915934, 16.68620653787546),
+            Offset(34.43994679577361, 16.719923702224868),
+            Offset(34.440000000000516, 16.72),
+          ],
+          <Offset>[
+            Offset(9.59999999999928, 34.8),
+            Offset(9.59784278428286, 34.79670302849285),
+            Offset(8.12880763309575, 32.13010384432861),
+            Offset(6.967635927464624, 21.981318090494952),
+            Offset(12.01057274235646, 13.75616979739632),
+            Offset(17.64579962789941, 10.942884872751186),
+            Offset(21.93035490934461, 10.42490759674647),
+            Offset(25.067931468540735, 10.790046353371297),
+            Offset(27.388013931066364, 11.485259520063472),
+            Offset(29.134377359648454, 12.26967958533492),
+            Offset(30.47438250198729, 13.033044731841269),
+            Offset(31.519653538066578, 13.731270250539573),
+            Offset(32.32658046011612, 14.373990554688655),
+            Offset(32.945527457598416, 14.946296972765026),
+            Offset(33.41756535579906, 15.438372656482638),
+            Offset(33.77306221888595, 15.846518849853458),
+            Offset(34.035475835072845, 16.171767455952793),
+            Offset(34.22216124688238, 16.41716283685671),
+            Offset(34.34598889992722, 16.58700373110889),
+            Offset(34.416353737915934, 16.68620653787546),
+            Offset(34.43994679577361, 16.719923702224868),
+            Offset(34.440000000000516, 16.72),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(9.59999999999928, 36.568005371096),
+            Offset(9.5974586446827, 36.564685154545415),
+            Offset(7.833489073453652, 33.857611848659886),
+            Offset(5.8067032844964785, 23.20927655181341),
+            Offset(10.489922660858541, 14.115495692469953),
+            Offset(16.219419684014916, 10.697139680954454),
+            Offset(20.70252778129252, 9.806955817069111),
+            Offset(24.045541660881376, 9.938850550688198),
+            Offset(26.55278434316561, 10.484170305778001),
+            Offset(28.46222842767796, 11.169876360070745),
+            Offset(29.941479072583817, 11.866471166373643),
+            Offset(31.10453065187761, 12.518548430768014),
+            Offset(32.011035138087706, 13.131632968269235),
+            Offset(32.71291429952744, 13.685776309911715),
+            Offset(33.252793879937755, 14.16720328006957),
+            Offset(33.662485741326776, 14.569493374227186),
+            Offset(33.96688420007136, 14.891800108558444),
+            Offset(34.184595526491954, 15.135909528787048),
+            Offset(34.32958668232016, 15.305304784380185),
+            Offset(34.41219994591878, 15.404409374202539),
+            Offset(34.43993742805, 15.438119808214498),
+            Offset(34.440000000000516, 15.438196105955399),
+          ],
+          <Offset>[
+            Offset(11.031994628903279, 38.0),
+            Offset(11.029123312699099, 37.9969720897259),
+            Offset(8.99348984375479, 35.49599871228041),
+            Offset(5.860990795973912, 25.144157116262335),
+            Offset(9.549309004043138, 15.638180829591539),
+            Offset(14.86508318012379, 11.653393915996185),
+            Offset(19.207540534520554, 10.300923899661631),
+            Offset(22.52803239611086, 10.077509007067547),
+            Offset(25.06545930180785, 10.349832425351934),
+            Offset(27.02703710229079, 10.82349808484828),
+            Offset(28.564988950784752, 11.353230513276403),
+            Offset(29.786059048943372, 11.872533770641846),
+            Offset(30.74921284177406, 12.380962067429417),
+            Offset(31.503551677244786, 12.853223394675975),
+            Offset(32.089754659866806, 13.271077127444466),
+            Offset(32.538598576444194, 13.624728880051993),
+            Offset(32.87461986019893, 13.910647255693526),
+            Offset(33.11641904008201, 14.128585696683976),
+            Offset(33.27819061447309, 14.280478644316606),
+            Offset(33.37064493049935, 14.369583080230184),
+            Offset(33.40173373474159, 14.399931289667558),
+            Offset(33.401803894045116, 14.399999999999999),
+          ],
+          <Offset>[
+            Offset(12.79999999999928, 38.0),
+            Offset(12.797105438751665, 37.997356229326066),
+            Offset(10.720997848086075, 35.79131727192251),
+            Offset(7.08894925729237, 26.305089759230484),
+            Offset(9.90863489911677, 17.15883091108946),
+            Offset(14.619337988327057, 13.07977385988068),
+            Offset(18.589588754843195, 11.528751027713723),
+            Offset(21.676836593427762, 11.099898814726902),
+            Offset(24.064370087522377, 11.18506201325269),
+            Offset(25.927233877026616, 11.495647016818776),
+            Offset(27.398415385317126, 11.886133942679876),
+            Offset(28.573337229171813, 12.287656656830816),
+            Offset(29.50685525535464, 12.696507389457834),
+            Offset(30.243031014391473, 13.085836552746947),
+            Offset(30.818585283453736, 13.435848603305775),
+            Offset(31.26157310081792, 13.735305357611166),
+            Offset(31.594652512804586, 13.97923889069501),
+            Offset(31.83516573201235, 14.166151417074401),
+            Offset(31.99649166774438, 14.29688086192367),
+            Offset(32.08884776682643, 14.37373687222734),
+            Offset(32.119929840731224, 14.399940657391166),
+            Offset(32.120000000000516, 14.399999999999999),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.79999999999928, 38.0),
+            Offset(12.797105438751665, 37.997356229326066),
+            Offset(10.720997848086075, 35.79131727192251),
+            Offset(7.08894925729237, 26.305089759230484),
+            Offset(9.90863489911677, 17.15883091108946),
+            Offset(14.619337988327057, 13.07977385988068),
+            Offset(18.589588754843195, 11.528751027713723),
+            Offset(21.676836593427762, 11.099898814726902),
+            Offset(24.064370087522377, 11.18506201325269),
+            Offset(25.927233877026616, 11.495647016818776),
+            Offset(27.398415385317126, 11.886133942679876),
+            Offset(28.573337229171813, 12.287656656830816),
+            Offset(29.50685525535464, 12.696507389457834),
+            Offset(30.243031014391473, 13.085836552746947),
+            Offset(30.818585283453736, 13.435848603305775),
+            Offset(31.26157310081792, 13.735305357611166),
+            Offset(31.594652512804586, 13.97923889069501),
+            Offset(31.83516573201235, 14.166151417074401),
+            Offset(31.99649166774438, 14.29688086192367),
+            Offset(32.08884776682643, 14.37373687222734),
+            Offset(32.119929840731224, 14.399940657391166),
+            Offset(32.120000000000516, 14.399999999999999),
+          ],
+          <Offset>[
+            Offset(35.19999999999928, 38.0),
+            Offset(35.19681093230872, 38.0022231416015),
+            Offset(32.60791059713087, 39.532898516035026),
+            Offset(22.646746752263834, 41.013693945407724),
+            Offset(14.461166345703841, 36.42492726035435),
+            Offset(11.505833704777048, 31.15150105333715),
+            Offset(10.760359222473623, 27.08488457685406),
+            Offset(10.892488145276982, 24.053214492366923),
+            Offset(11.380925361280685, 21.767124191818905),
+            Offset(11.99311769804367, 20.0115352161887),
+            Offset(12.618342714906678, 18.63783108896057),
+            Offset(13.208582570059477, 17.547116159981847),
+            Offset(13.766625960381576, 16.694354527815143),
+            Offset(14.272681993103916, 16.032962633907964),
+            Offset(14.713320844126082, 15.523444670395389),
+            Offset(15.082113964731814, 15.13627004800123),
+            Offset(15.377920906463453, 14.84827054023167),
+            Offset(15.602141460729166, 14.642095749881417),
+            Offset(15.75782131295615, 14.504691132415362),
+            Offset(15.84893303824477, 14.426363941272161),
+            Offset(15.879929841164916, 14.400059343121573),
+            Offset(15.88000000000052, 14.400000000000002),
+          ],
+          <Offset>[
+            Offset(35.19999999999928, 38.0),
+            Offset(35.19681093230872, 38.0022231416015),
+            Offset(32.60791059713087, 39.532898516035026),
+            Offset(22.646746752263834, 41.013693945407724),
+            Offset(14.461166345703841, 36.42492726035435),
+            Offset(11.505833704777048, 31.15150105333715),
+            Offset(10.760359222473623, 27.08488457685406),
+            Offset(10.892488145276982, 24.053214492366923),
+            Offset(11.380925361280685, 21.767124191818905),
+            Offset(11.99311769804367, 20.0115352161887),
+            Offset(12.618342714906678, 18.63783108896057),
+            Offset(13.208582570059477, 17.547116159981847),
+            Offset(13.766625960381576, 16.694354527815143),
+            Offset(14.272681993103916, 16.032962633907964),
+            Offset(14.713320844126082, 15.523444670395389),
+            Offset(15.082113964731814, 15.13627004800123),
+            Offset(15.377920906463453, 14.84827054023167),
+            Offset(15.602141460729166, 14.642095749881417),
+            Offset(15.75782131295615, 14.504691132415362),
+            Offset(15.84893303824477, 14.426363941272161),
+            Offset(15.879929841164916, 14.400059343121573),
+            Offset(15.88000000000052, 14.400000000000002),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(36.96800537109528, 38.0),
+            Offset(36.96479305836129, 38.00260728120166),
+            Offset(34.33541860146215, 39.828217075677124),
+            Offset(23.874705213582292, 42.17462658837587),
+            Offset(14.820492240777476, 37.94557734185227),
+            Offset(11.260088512980314, 32.57788099722164),
+            Offset(10.142407442796266, 28.31271170490615),
+            Offset(10.041292342593884, 25.075604300026278),
+            Offset(10.379836146995213, 22.60235377971966),
+            Offset(10.893314472779496, 20.683684148159198),
+            Offset(11.45176914943905, 19.170734518364043),
+            Offset(11.99586075028792, 17.962239046170815),
+            Offset(12.524268373962157, 17.009899849843556),
+            Offset(13.012161330250605, 16.26557579197894),
+            Offset(13.442151467713014, 15.688216146256698),
+            Offset(13.805088489105541, 15.246846525560404),
+            Offset(14.097953559069104, 14.916862175233152),
+            Offset(14.320888152659505, 14.679661470271842),
+            Offset(14.476122366227445, 14.521093350022426),
+            Offset(14.567135874571848, 14.430517733269317),
+            Offset(14.598125947154546, 14.400068710845181),
+            Offset(14.59819610595592, 14.400000000000002),
+          ],
+          <Offset>[
+            Offset(38.39999999999928, 36.568005371096),
+            Offset(38.39707999354177, 36.57094261318526),
+            Offset(35.97380546508267, 38.668216305375985),
+            Offset(25.809585778031217, 42.120339076898446),
+            Offset(16.34317737789906, 38.88619099866767),
+            Offset(12.216342748022047, 33.93221750111277),
+            Offset(10.636375525388786, 29.80769895167812),
+            Offset(10.17995079897323, 26.593113564796795),
+            Offset(10.245498266569147, 24.08967882107742),
+            Offset(10.54693619755703, 22.118875473546364),
+            Offset(10.938528496341814, 20.547224640163105),
+            Offset(11.34984609016175, 19.280710649105057),
+            Offset(11.773597473122338, 18.271722146157202),
+            Offset(12.179608415014865, 17.474938414261594),
+            Offset(12.546025315087908, 16.851255366327646),
+            Offset(12.860323994930349, 16.370733690442982),
+            Offset(13.116800706204186, 16.009126515105578),
+            Offset(13.313564320556432, 15.747837956681783),
+            Offset(13.451296226163867, 15.572489417869503),
+            Offset(13.532309580599492, 15.472072748688737),
+            Offset(13.559937428607608, 15.43827240415359),
+            Offset(13.560000000000521, 15.438196105955402),
+          ],
+          <Offset>[
+            Offset(38.39999999999928, 34.8),
+            Offset(38.39746413314193, 34.802960487132694),
+            Offset(36.26912402472477, 36.9407083010447),
+            Offset(26.970518420999362, 40.89238061557998),
+            Offset(17.86382745939698, 38.52686510359404),
+            Offset(13.642722691906542, 34.1779626929095),
+            Offset(11.864202653440877, 30.425650731355475),
+            Offset(11.202340606632587, 27.444309367479892),
+            Offset(11.080727854469902, 25.090768035362892),
+            Offset(11.219085129527524, 23.218678698810542),
+            Offset(11.471431925745287, 21.713798205630734),
+            Offset(11.764968976350719, 20.493432468876616),
+            Offset(12.089142795150753, 19.514079732576622),
+            Offset(12.412221573085839, 18.735459077114903),
+            Offset(12.710796790949217, 18.122424742740712),
+            Offset(12.970900472489523, 17.647759166069257),
+            Offset(13.18539234120567, 17.289093862499925),
+            Offset(13.351130040946858, 17.029091264751443),
+            Offset(13.46769844377093, 16.85418836459821),
+            Offset(13.536463372596648, 16.753869912361658),
+            Offset(13.559946796331216, 16.72007629816396),
+            Offset(13.560000000000521, 16.720000000000002),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.39999999999928, 34.8),
+            Offset(38.39746413314193, 34.802960487132694),
+            Offset(36.26912402472477, 36.9407083010447),
+            Offset(26.970518420999362, 40.89238061557998),
+            Offset(17.86382745939698, 38.52686510359404),
+            Offset(13.642722691906542, 34.1779626929095),
+            Offset(11.864202653440877, 30.425650731355475),
+            Offset(11.202340606632587, 27.444309367479892),
+            Offset(11.080727854469902, 25.090768035362892),
+            Offset(11.219085129527524, 23.218678698810542),
+            Offset(11.471431925745287, 21.713798205630734),
+            Offset(11.764968976350719, 20.493432468876616),
+            Offset(12.089142795150753, 19.514079732576622),
+            Offset(12.412221573085839, 18.735459077114903),
+            Offset(12.710796790949217, 18.122424742740712),
+            Offset(12.970900472489523, 17.647759166069257),
+            Offset(13.18539234120567, 17.289093862499925),
+            Offset(13.351130040946858, 17.029091264751443),
+            Offset(13.46769844377093, 16.85418836459821),
+            Offset(13.536463372596648, 16.753869912361658),
+            Offset(13.559946796331216, 16.72007629816396),
+            Offset(13.560000000000521, 16.720000000000002),
+          ],
+          <Offset>[
+            Offset(38.39999999999928, 12.399999999999999),
+            Offset(38.40233104541737, 12.403254993575636),
+            Offset(40.010705268837285, 15.05379555199991),
+            Offset(41.67912260717661, 25.33458312060852),
+            Offset(37.12992380866187, 33.974333657006966),
+            Offset(31.71444988536301, 37.29146697645951),
+            Offset(27.420336202581215, 38.25488026372505),
+            Offset(24.155656284272606, 38.22865781563067),
+            Offset(21.662790033036117, 37.77421276160458),
+            Offset(19.734973328897453, 37.15279487779348),
+            Offset(18.223129072025984, 36.49387087604118),
+            Offset(17.02442847950175, 35.858187127988955),
+            Offset(16.086989933508065, 35.25430902754969),
+            Offset(15.359347654246855, 34.705808098402464),
+            Offset(14.798392858038833, 34.227689182068374),
+            Offset(14.371865162879587, 33.82721830215536),
+            Offset(14.05442399074233, 33.50582546884106),
+            Offset(13.827074373753874, 33.26211553603463),
+            Offset(13.675508714262623, 33.09285871938644),
+            Offset(13.589090441641469, 32.993784640943325),
+            Offset(13.56006548206162, 32.96007629773027),
+            Offset(13.560000000000523, 32.96),
+          ],
+          <Offset>[
+            Offset(38.39999999999928, 12.399999999999999),
+            Offset(38.40233104541737, 12.403254993575636),
+            Offset(40.010705268837285, 15.05379555199991),
+            Offset(41.67912260717661, 25.33458312060852),
+            Offset(37.12992380866187, 33.974333657006966),
+            Offset(31.71444988536301, 37.29146697645951),
+            Offset(27.420336202581215, 38.25488026372505),
+            Offset(24.155656284272606, 38.22865781563067),
+            Offset(21.662790033036117, 37.77421276160458),
+            Offset(19.734973328897453, 37.15279487779348),
+            Offset(18.223129072025984, 36.49387087604118),
+            Offset(17.02442847950175, 35.858187127988955),
+            Offset(16.086989933508065, 35.25430902754969),
+            Offset(15.359347654246855, 34.705808098402464),
+            Offset(14.798392858038833, 34.227689182068374),
+            Offset(14.371865162879587, 33.82721830215536),
+            Offset(14.05442399074233, 33.50582546884106),
+            Offset(13.827074373753874, 33.26211553603463),
+            Offset(13.675508714262623, 33.09285871938644),
+            Offset(13.589090441641469, 32.993784640943325),
+            Offset(13.56006548206162, 32.96007629773027),
+            Offset(13.560000000000523, 32.96),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.39999999999928, 10.631994628904),
+            Offset(38.40271518501753, 10.635272867523073),
+            Offset(40.30602382847938, 13.32628754766863),
+            Offset(42.84005525014476, 24.106624659290063),
+            Offset(38.65057389015979, 33.61500776193334),
+            Offset(33.14082982924751, 37.53721216825625),
+            Offset(28.648163330633302, 38.872832043402404),
+            Offset(25.17804609193196, 39.07985361831377),
+            Offset(22.498019620936873, 38.77530197589005),
+            Offset(20.407122260867947, 38.252598103057665),
+            Offset(18.756032501429452, 37.66044444150881),
+            Offset(17.439551365690722, 37.07090894776051),
+            Offset(16.402535255536478, 36.496666613969104),
+            Offset(15.591960812317827, 35.966328761255774),
+            Offset(14.96316433390014, 35.49885855848144),
+            Offset(14.48244164043876, 35.10424377778163),
+            Offset(14.123015625743813, 34.78579281623541),
+            Offset(13.864640094144299, 34.543368844104286),
+            Offset(13.691910931869685, 34.374557666115145),
+            Offset(13.593244233638625, 34.275581804616245),
+            Offset(13.560074849785229, 34.241880191740634),
+            Offset(13.560000000000525, 34.2418038940446),
+          ],
+          <Offset>[
+            Offset(36.96800537109528, 9.2),
+            Offset(36.97105051700113, 9.202985932342585),
+            Offset(39.146023058178244, 11.687900684048108),
+            Offset(42.78576773866732, 22.171744094841138),
+            Offset(39.5911875469752, 32.09232262481175),
+            Offset(34.49516633313863, 36.58095793321451),
+            Offset(30.143150577405272, 38.37886396080988),
+            Offset(26.695555356702478, 38.941195161934424),
+            Offset(23.985344662294633, 38.90963985631612),
+            Offset(21.842313586255116, 38.59897637828013),
+            Offset(20.132522623228514, 38.17368509460604),
+            Offset(18.75802296862496, 37.71692360788668),
+            Offset(17.664357551850124, 37.247337514808926),
+            Offset(16.801323434600484, 36.79888167649151),
+            Offset(16.126203553971088, 36.394984711106545),
+            Offset(15.606328805321338, 36.04900827195683),
+            Offset(15.215279965616238, 35.76694566910032),
+            Offset(14.93281658055424, 35.55069267620736),
+            Offset(14.743306999716763, 35.39938380617872),
+            Offset(14.634799249058046, 35.3104080985886),
+            Offset(14.598278543093638, 35.28006871028757),
+            Offset(14.598196105955923, 35.28),
+          ],
+          <Offset>[
+            Offset(35.19999999999928, 9.2),
+            Offset(35.20306839094856, 9.202601792742424),
+            Offset(37.41851505384696, 11.39258212440601),
+            Offset(41.557809277348866, 21.01081145187299),
+            Offset(39.23186165190157, 30.57167254331383),
+            Offset(34.74091152493536, 35.15457798933002),
+            Offset(30.761102357082628, 37.151036832757796),
+            Offset(27.54675115938558, 37.918805354275065),
+            Offset(24.986433876580104, 38.07441026841536),
+            Offset(22.94211681151929, 37.92682744630963),
+            Offset(21.299096188696144, 37.640781665202574),
+            Offset(19.970744788396516, 37.30180072169771),
+            Offset(18.906715138269544, 36.931792192780506),
+            Offset(18.061844097453793, 36.56626851842054),
+            Offset(17.397372930384158, 36.23021323524523),
+            Offset(16.88335428094761, 35.93843179439765),
+            Offset(16.495247313010587, 35.69835403409884),
+            Offset(16.214069888623904, 35.51312695581694),
+            Offset(16.025005946445468, 35.38298158857166),
+            Offset(15.916596412730968, 35.30625430659144),
+            Offset(15.880082437104008, 35.280059342563966),
+            Offset(15.880000000000523, 35.28),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(35.19999999999928, 9.2),
+            Offset(35.20306839094856, 9.202601792742424),
+            Offset(37.41851505384696, 11.39258212440601),
+            Offset(41.557809277348866, 21.01081145187299),
+            Offset(39.23186165190157, 30.57167254331383),
+            Offset(34.74091152493536, 35.15457798933002),
+            Offset(30.761102357082628, 37.151036832757796),
+            Offset(27.54675115938558, 37.918805354275065),
+            Offset(24.986433876580104, 38.07441026841536),
+            Offset(22.94211681151929, 37.92682744630963),
+            Offset(21.299096188696144, 37.640781665202574),
+            Offset(19.970744788396516, 37.30180072169771),
+            Offset(18.906715138269544, 36.931792192780506),
+            Offset(18.061844097453793, 36.56626851842054),
+            Offset(17.397372930384158, 36.23021323524523),
+            Offset(16.88335428094761, 35.93843179439765),
+            Offset(16.495247313010587, 35.69835403409884),
+            Offset(16.214069888623904, 35.51312695581694),
+            Offset(16.025005946445468, 35.38298158857166),
+            Offset(15.916596412730968, 35.30625430659144),
+            Offset(15.880082437104008, 35.280059342563966),
+            Offset(15.880000000000523, 35.28),
+          ],
+          <Offset>[
+            Offset(33.599999999999284, 9.2),
+            Offset(33.60308942712306, 9.202254156151323),
+            Offset(35.85516414320091, 11.125326321255116),
+            Offset(40.446538027708044, 19.960196867146045),
+            Offset(38.9066808342882, 29.195522804080625),
+            Offset(34.96330468804608, 33.86374033265456),
+            Offset(31.320333037966172, 36.03988443639063),
+            Offset(28.31706176282492, 36.99356852015792),
+            Offset(25.892394214168796, 37.31854868423206),
+            Offset(23.93741082430379, 37.31854971778321),
+            Offset(22.35481566515403, 37.15851758332538),
+            Offset(21.068227264047398, 36.926125042901205),
+            Offset(20.03101723076762, 36.64623168289785),
+            Offset(19.20258331326005, 36.35575951262332),
+            Offset(18.547748961764704, 36.08109923045312),
+            Offset(18.039029933525192, 35.83836288794122),
+            Offset(17.653585284892095, 35.63628034484623),
+            Offset(17.37357162228699, 35.47913093204501),
+            Offset(17.184910971787485, 35.36813799782225),
+            Offset(17.076590321915372, 35.3024952302311),
+            Offset(17.040082437073032, 35.280050865011795),
+            Offset(17.04000000000052, 35.28),
+          ],
+          <Offset>[
+            Offset(33.599999999999284, 9.2),
+            Offset(33.60308942712306, 9.202254156151323),
+            Offset(35.85516414320091, 11.125326321255116),
+            Offset(40.446538027708044, 19.960196867146045),
+            Offset(38.9066808342882, 29.195522804080625),
+            Offset(34.96330468804608, 33.86374033265456),
+            Offset(31.320333037966172, 36.03988443639063),
+            Offset(28.31706176282492, 36.99356852015792),
+            Offset(25.892394214168796, 37.31854868423206),
+            Offset(23.93741082430379, 37.31854971778321),
+            Offset(22.35481566515403, 37.15851758332538),
+            Offset(21.068227264047398, 36.926125042901205),
+            Offset(20.03101723076762, 36.64623168289785),
+            Offset(19.20258331326005, 36.35575951262332),
+            Offset(18.547748961764704, 36.08109923045312),
+            Offset(18.039029933525192, 35.83836288794122),
+            Offset(17.653585284892095, 35.63628034484623),
+            Offset(17.37357162228699, 35.47913093204501),
+            Offset(17.184910971787485, 35.36813799782225),
+            Offset(17.076590321915372, 35.3024952302311),
+            Offset(17.040082437073032, 35.280050865011795),
+            Offset(17.04000000000052, 35.28),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(33.599999999999284, 9.2),
+            Offset(33.60308942712306, 9.202254156151323),
+            Offset(35.85516414320091, 11.125326321255116),
+            Offset(40.446538027708044, 19.960196867146045),
+            Offset(38.9066808342882, 29.195522804080625),
+            Offset(34.96330468804608, 33.86374033265456),
+            Offset(31.320333037966172, 36.03988443639063),
+            Offset(28.31706176282492, 36.99356852015792),
+            Offset(25.892394214168796, 37.31854868423206),
+            Offset(23.93741082430379, 37.31854971778321),
+            Offset(22.35481566515403, 37.15851758332538),
+            Offset(21.068227264047398, 36.926125042901205),
+            Offset(20.03101723076762, 36.64623168289785),
+            Offset(19.20258331326005, 36.35575951262332),
+            Offset(18.547748961764704, 36.08109923045312),
+            Offset(18.039029933525192, 35.83836288794122),
+            Offset(17.653585284892095, 35.63628034484623),
+            Offset(17.37357162228699, 35.47913093204501),
+            Offset(17.184910971787485, 35.36813799782225),
+            Offset(17.076590321915372, 35.3024952302311),
+            Offset(17.040082437073032, 35.280050865011795),
+            Offset(17.04000000000052, 35.28),
+          ],
+          <Offset>[
+            Offset(33.599999999999284, 6.0),
+            Offset(33.60378470030526, 6.002296228500315),
+            Offset(36.38967574950269, 7.998624499963004),
+            Offset(42.54776719716194, 17.73765436786441),
+            Offset(41.658980312754615, 28.5451611688539),
+            Offset(37.54498000139701, 34.308526658875984),
+            Offset(33.54263783070051, 37.15834579815771),
+            Offset(30.167535431059207, 38.534189727036605),
+            Offset(27.404117382535397, 39.130469359409446),
+            Offset(25.153966281356634, 39.3091377433522),
+            Offset(23.319343828908416, 39.26995653624116),
+            Offset(21.819578621640403, 39.12108999420297),
+            Offset(20.60213825053295, 38.894835867893995),
+            Offset(19.62360132485448, 38.637237944235835),
+            Offset(18.845976971348936, 38.38185129321421),
+            Offset(18.239167746438056, 38.14971419309638),
+            Offset(17.777732663397334, 37.95295628860924),
+            Offset(17.441563669830845, 37.79813439937118),
+            Offset(17.214598153286296, 37.687948048506286),
+            Offset(17.08410847463606, 37.62248304859991),
+            Offset(17.040099392177375, 37.600050864949836),
+            Offset(17.040000000000525, 37.6),
+          ],
+          <Offset>[
+            Offset(33.599999999999284, 6.0),
+            Offset(33.60378470030526, 6.002296228500315),
+            Offset(36.38967574950269, 7.998624499963004),
+            Offset(42.54776719716194, 17.73765436786441),
+            Offset(41.658980312754615, 28.5451611688539),
+            Offset(37.54498000139701, 34.308526658875984),
+            Offset(33.54263783070051, 37.15834579815771),
+            Offset(30.167535431059207, 38.534189727036605),
+            Offset(27.404117382535397, 39.130469359409446),
+            Offset(25.153966281356634, 39.3091377433522),
+            Offset(23.319343828908416, 39.26995653624116),
+            Offset(21.819578621640403, 39.12108999420297),
+            Offset(20.60213825053295, 38.894835867893995),
+            Offset(19.62360132485448, 38.637237944235835),
+            Offset(18.845976971348936, 38.38185129321421),
+            Offset(18.239167746438056, 38.14971419309638),
+            Offset(17.777732663397334, 37.95295628860924),
+            Offset(17.441563669830845, 37.79813439937118),
+            Offset(17.214598153286296, 37.687948048506286),
+            Offset(17.08410847463606, 37.62248304859991),
+            Offset(17.040099392177375, 37.600050864949836),
+            Offset(17.040000000000525, 37.6),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(33.599999999999284, 6.0),
+            Offset(33.60378470030526, 6.002296228500315),
+            Offset(36.38967574950269, 7.998624499963004),
+            Offset(42.54776719716194, 17.73765436786441),
+            Offset(41.658980312754615, 28.5451611688539),
+            Offset(37.54498000139701, 34.308526658875984),
+            Offset(33.54263783070051, 37.15834579815771),
+            Offset(30.167535431059207, 38.534189727036605),
+            Offset(27.404117382535397, 39.130469359409446),
+            Offset(25.153966281356634, 39.3091377433522),
+            Offset(23.319343828908416, 39.26995653624116),
+            Offset(21.819578621640403, 39.12108999420297),
+            Offset(20.60213825053295, 38.894835867893995),
+            Offset(19.62360132485448, 38.637237944235835),
+            Offset(18.845976971348936, 38.38185129321421),
+            Offset(18.239167746438056, 38.14971419309638),
+            Offset(17.777732663397334, 37.95295628860924),
+            Offset(17.441563669830845, 37.79813439937118),
+            Offset(17.214598153286296, 37.687948048506286),
+            Offset(17.08410847463606, 37.62248304859991),
+            Offset(17.040099392177375, 37.600050864949836),
+            Offset(17.040000000000525, 37.6),
+          ],
+          <Offset>[
+            Offset(30.39999999999928, 6.0),
+            Offset(30.403826772654256, 6.001600955318109),
+            Offset(33.262973928210585, 7.464112893661214),
+            Offset(40.3252246978803, 15.636425198410514),
+            Offset(41.00861867752789, 25.79286169038749),
+            Offset(37.98976632761843, 31.726851345525063),
+            Offset(34.66109919246759, 34.93604100542338),
+            Offset(31.70815663793789, 36.68371605880232),
+            Offset(29.216038057712783, 37.618746191042845),
+            Offset(27.144554306925627, 38.092582286299354),
+            Offset(25.430782781824195, 38.30542837248677),
+            Offset(24.014543572942166, 38.369738636609966),
+            Offset(22.850742435529103, 38.32371484812867),
+            Offset(21.90507975646699, 38.2162199326414),
+            Offset(21.146729034110027, 38.08362328362998),
+            Offset(20.550519051593216, 37.949576380183515),
+            Offset(20.094408607160354, 37.82880891010401),
+            Offset(19.760567137157015, 37.73014235182732),
+            Offset(19.53440820397033, 37.65826086700747),
+            Offset(19.40409629300487, 37.61496489587922),
+            Offset(19.360099392115416, 37.60003390984549),
+            Offset(19.360000000000525, 37.6),
+          ],
+          <Offset>[
+            Offset(30.39999999999928, 6.0),
+            Offset(30.403826772654256, 6.001600955318109),
+            Offset(33.262973928210585, 7.464112893661214),
+            Offset(40.3252246978803, 15.636425198410514),
+            Offset(41.00861867752789, 25.79286169038749),
+            Offset(37.98976632761843, 31.726851345525063),
+            Offset(34.66109919246759, 34.93604100542338),
+            Offset(31.70815663793789, 36.68371605880232),
+            Offset(29.216038057712783, 37.618746191042845),
+            Offset(27.144554306925627, 38.092582286299354),
+            Offset(25.430782781824195, 38.30542837248677),
+            Offset(24.014543572942166, 38.369738636609966),
+            Offset(22.850742435529103, 38.32371484812867),
+            Offset(21.90507975646699, 38.2162199326414),
+            Offset(21.146729034110027, 38.08362328362998),
+            Offset(20.550519051593216, 37.949576380183515),
+            Offset(20.094408607160354, 37.82880891010401),
+            Offset(19.760567137157015, 37.73014235182732),
+            Offset(19.53440820397033, 37.65826086700747),
+            Offset(19.40409629300487, 37.61496489587922),
+            Offset(19.360099392115416, 37.60003390984549),
+            Offset(19.360000000000525, 37.6),
+          ],
+        ),
+        _PathClose(
+        ),
+        _PathMoveTo(
+          <Offset>[
+            Offset(35.2, 34.8),
+            Offset(35.19750620549164, 34.80226521395049),
+            Offset(33.142422203433355, 36.40619669474303),
+            Offset(24.747975921718226, 38.79115144612656),
+            Offset(17.213465824170402, 35.77456562512825),
+            Offset(14.087509018127873, 31.596287379559158),
+            Offset(12.982664015207707, 28.20334593862164),
+            Offset(12.742961813510924, 25.59383569924602),
+            Offset(12.89264852964688, 23.57904486699663),
+            Offset(13.20967315509607, 22.002123241757968),
+            Offset(13.58287087866059, 20.749270041876564),
+            Offset(13.959933927651988, 19.74208111128378),
+            Offset(14.3377469801464, 18.94295871281142),
+            Offset(14.693700004697835, 18.31444106552057),
+            Offset(15.011548853709796, 17.824196733156548),
+            Offset(15.282251777644161, 17.447621353156435),
+            Offset(15.50206828496817, 17.16494648399472),
+            Offset(15.670133508272507, 16.961099217207604),
+            Offset(15.78750849445444, 16.8245011830994),
+            Offset(15.856451190964936, 16.746351759640973),
+            Offset(15.879946796268738, 16.720059343059617),
+            Offset(15.879999999999999, 16.720000000000002),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(35.2, 34.8),
+            Offset(35.19750620549164, 34.80226521395049),
+            Offset(33.142422203433355, 36.40619669474303),
+            Offset(24.747975921718226, 38.79115144612656),
+            Offset(17.213465824170402, 35.77456562512825),
+            Offset(14.087509018127873, 31.596287379559158),
+            Offset(12.982664015207707, 28.20334593862164),
+            Offset(12.742961813510924, 25.59383569924602),
+            Offset(12.89264852964688, 23.57904486699663),
+            Offset(13.20967315509607, 22.002123241757968),
+            Offset(13.58287087866059, 20.749270041876564),
+            Offset(13.959933927651988, 19.74208111128378),
+            Offset(14.3377469801464, 18.94295871281142),
+            Offset(14.693700004697835, 18.31444106552057),
+            Offset(15.011548853709796, 17.824196733156548),
+            Offset(15.282251777644161, 17.447621353156435),
+            Offset(15.50206828496817, 17.16494648399472),
+            Offset(15.670133508272507, 16.961099217207604),
+            Offset(15.78750849445444, 16.8245011830994),
+            Offset(15.856451190964936, 16.746351759640973),
+            Offset(15.879946796268738, 16.720059343059617),
+            Offset(15.879999999999999, 16.720000000000002),
+          ],
+          <Offset>[
+            Offset(12.799999999999999, 34.8),
+            Offset(12.797800711934586, 34.79739830167506),
+            Offset(11.255509454388564, 32.66461545063052),
+            Offset(9.190178426746762, 24.08254725994932),
+            Offset(12.660934377583331, 16.50846927586335),
+            Offset(17.201013301677882, 13.52456018610269),
+            Offset(20.811893547577277, 12.647212389481304),
+            Offset(23.527310261661704, 12.640520021606001),
+            Offset(25.576093255888573, 12.996982688430414),
+            Offset(27.143789334079017, 13.48623504238804),
+            Offset(28.362943549071037, 13.997572895595871),
+            Offset(29.324688586764324, 14.482621608132748),
+            Offset(30.077976275119465, 14.945111574454113),
+            Offset(30.664049025985392, 15.367314984359552),
+            Offset(31.116813293037453, 15.736600666066936),
+            Offset(31.46171091373027, 16.046656662766367),
+            Offset(31.718799891309303, 16.29591483445806),
+            Offset(31.903157779555688, 16.485154884400586),
+            Offset(32.02617884924267, 16.61669091260771),
+            Offset(32.0963659195466, 16.69372469059615),
+            Offset(32.11994679583504, 16.71994065732921),
+            Offset(32.12, 16.72),
+          ],
+          <Offset>[
+            Offset(12.799999999999999, 34.8),
+            Offset(12.797800711934586, 34.79739830167506),
+            Offset(11.255509454388564, 32.66461545063052),
+            Offset(9.190178426746762, 24.08254725994932),
+            Offset(12.660934377583331, 16.50846927586335),
+            Offset(17.201013301677882, 13.52456018610269),
+            Offset(20.811893547577277, 12.647212389481304),
+            Offset(23.527310261661704, 12.640520021606001),
+            Offset(25.576093255888573, 12.996982688430414),
+            Offset(27.143789334079017, 13.48623504238804),
+            Offset(28.362943549071037, 13.997572895595871),
+            Offset(29.324688586764324, 14.482621608132748),
+            Offset(30.077976275119465, 14.945111574454113),
+            Offset(30.664049025985392, 15.367314984359552),
+            Offset(31.116813293037453, 15.736600666066936),
+            Offset(31.46171091373027, 16.046656662766367),
+            Offset(31.718799891309303, 16.29591483445806),
+            Offset(31.903157779555688, 16.485154884400586),
+            Offset(32.02617884924267, 16.61669091260771),
+            Offset(32.0963659195466, 16.69372469059615),
+            Offset(32.11994679583504, 16.71994065732921),
+            Offset(32.12, 16.72),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.799999999999999, 34.8),
+            Offset(12.797800711934586, 34.79739830167506),
+            Offset(11.255509454388564, 32.66461545063052),
+            Offset(9.190178426746762, 24.08254725994932),
+            Offset(12.660934377583331, 16.50846927586335),
+            Offset(17.201013301677882, 13.52456018610269),
+            Offset(20.811893547577277, 12.647212389481304),
+            Offset(23.527310261661704, 12.640520021606001),
+            Offset(25.576093255888573, 12.996982688430414),
+            Offset(27.143789334079017, 13.48623504238804),
+            Offset(28.362943549071037, 13.997572895595871),
+            Offset(29.324688586764324, 14.482621608132748),
+            Offset(30.077976275119465, 14.945111574454113),
+            Offset(30.664049025985392, 15.367314984359552),
+            Offset(31.116813293037453, 15.736600666066936),
+            Offset(31.46171091373027, 16.046656662766367),
+            Offset(31.718799891309303, 16.29591483445806),
+            Offset(31.903157779555688, 16.485154884400586),
+            Offset(32.02617884924267, 16.61669091260771),
+            Offset(32.0963659195466, 16.69372469059615),
+            Offset(32.11994679583504, 16.71994065732921),
+            Offset(32.12, 16.72),
+          ],
+          <Offset>[
+            Offset(12.799999999999999, 17.2),
+            Offset(12.801624714436713, 17.19762969959451),
+            Offset(14.195323289048401, 15.467755433523894),
+            Offset(20.74693885874317, 11.858563513900311),
+            Offset(27.798581509148605, 12.931480282116368),
+            Offset(31.400227525107965, 15.970884980320555),
+            Offset(33.03456990761612, 18.79874987920025),
+            Offset(33.70491543695029, 21.113936659438757),
+            Offset(33.89057068190488, 22.962546401906028),
+            Offset(33.83484434786968, 24.434469183017498),
+            Offset(33.667848449720154, 25.61048713663265),
+            Offset(33.45712105352585, 26.55492884029244),
+            Offset(33.219141883828776, 27.31243459193295),
+            Offset(32.97964808975476, 27.915446358228348),
+            Offset(32.75706734575072, 28.390737011252952),
+            Offset(32.562468884751034, 28.759088841119738),
+            Offset(32.40161047308811, 29.037632525154663),
+            Offset(32.277114041046914, 29.239673954694513),
+            Offset(32.189458347486145, 29.375646191369892),
+            Offset(32.13771575951039, 29.453657691624603),
+            Offset(32.12004004890893, 29.47994065698845),
+            Offset(32.12, 29.479999999999997),
+          ],
+          <Offset>[
+            Offset(12.799999999999999, 17.2),
+            Offset(12.801624714436713, 17.19762969959451),
+            Offset(14.195323289048401, 15.467755433523894),
+            Offset(20.74693885874317, 11.858563513900311),
+            Offset(27.798581509148605, 12.931480282116368),
+            Offset(31.400227525107965, 15.970884980320555),
+            Offset(33.03456990761612, 18.79874987920025),
+            Offset(33.70491543695029, 21.113936659438757),
+            Offset(33.89057068190488, 22.962546401906028),
+            Offset(33.83484434786968, 24.434469183017498),
+            Offset(33.667848449720154, 25.61048713663265),
+            Offset(33.45712105352585, 26.55492884029244),
+            Offset(33.219141883828776, 27.31243459193295),
+            Offset(32.97964808975476, 27.915446358228348),
+            Offset(32.75706734575072, 28.390737011252952),
+            Offset(32.562468884751034, 28.759088841119738),
+            Offset(32.40161047308811, 29.037632525154663),
+            Offset(32.277114041046914, 29.239673954694513),
+            Offset(32.189458347486145, 29.375646191369892),
+            Offset(32.13771575951039, 29.453657691624603),
+            Offset(32.12004004890893, 29.47994065698845),
+            Offset(32.12, 29.479999999999997),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.799999999999999, 17.2),
+            Offset(12.801624714436713, 17.19762969959451),
+            Offset(14.195323289048401, 15.467755433523894),
+            Offset(20.74693885874317, 11.858563513900311),
+            Offset(27.798581509148605, 12.931480282116368),
+            Offset(31.400227525107965, 15.970884980320555),
+            Offset(33.03456990761612, 18.79874987920025),
+            Offset(33.70491543695029, 21.113936659438757),
+            Offset(33.89057068190488, 22.962546401906028),
+            Offset(33.83484434786968, 24.434469183017498),
+            Offset(33.667848449720154, 25.61048713663265),
+            Offset(33.45712105352585, 26.55492884029244),
+            Offset(33.219141883828776, 27.31243459193295),
+            Offset(32.97964808975476, 27.915446358228348),
+            Offset(32.75706734575072, 28.390737011252952),
+            Offset(32.562468884751034, 28.759088841119738),
+            Offset(32.40161047308811, 29.037632525154663),
+            Offset(32.277114041046914, 29.239673954694513),
+            Offset(32.189458347486145, 29.375646191369892),
+            Offset(32.13771575951039, 29.453657691624603),
+            Offset(32.12004004890893, 29.47994065698845),
+            Offset(32.12, 29.479999999999997),
+          ],
+          <Offset>[
+            Offset(35.2, 17.2),
+            Offset(35.20133020799377, 17.202496611869943),
+            Offset(36.08223603809319, 19.20933667763641),
+            Offset(36.30473635371463, 26.567167700077555),
+            Offset(32.35111295573567, 32.19757663138127),
+            Offset(28.286723241557954, 34.04261217377702),
+            Offset(25.205340375246543, 34.35488342834059),
+            Offset(22.92056698879951, 34.06725233707878),
+            Offset(21.20712595566319, 33.54460858047224),
+            Offset(19.90072816888673, 32.95035738238742),
+            Offset(18.887775779309706, 32.36218428291335),
+            Offset(18.092366394413514, 31.814388343443472),
+            Offset(17.478912588855714, 31.310281730290257),
+            Offset(17.009299068467204, 30.862572439389364),
+            Offset(16.651802906423065, 30.478333078342565),
+            Offset(16.383009748664925, 30.160053531509803),
+            Offset(16.184878866746974, 29.906664174691322),
+            Offset(16.044089769763733, 29.71561828750153),
+            Offset(15.950787992697913, 29.583456461861584),
+            Offset(15.897801030928724, 29.506284760669423),
+            Offset(15.880040049342629, 29.480059342718857),
+            Offset(15.88, 29.48),
+          ],
+          <Offset>[
+            Offset(35.2, 17.2),
+            Offset(35.20133020799377, 17.202496611869943),
+            Offset(36.08223603809319, 19.20933667763641),
+            Offset(36.30473635371463, 26.567167700077555),
+            Offset(32.35111295573567, 32.19757663138127),
+            Offset(28.286723241557954, 34.04261217377702),
+            Offset(25.205340375246543, 34.35488342834059),
+            Offset(22.92056698879951, 34.06725233707878),
+            Offset(21.20712595566319, 33.54460858047224),
+            Offset(19.90072816888673, 32.95035738238742),
+            Offset(18.887775779309706, 32.36218428291335),
+            Offset(18.092366394413514, 31.814388343443472),
+            Offset(17.478912588855714, 31.310281730290257),
+            Offset(17.009299068467204, 30.862572439389364),
+            Offset(16.651802906423065, 30.478333078342565),
+            Offset(16.383009748664925, 30.160053531509803),
+            Offset(16.184878866746974, 29.906664174691322),
+            Offset(16.044089769763733, 29.71561828750153),
+            Offset(15.950787992697913, 29.583456461861584),
+            Offset(15.897801030928724, 29.506284760669423),
+            Offset(15.880040049342629, 29.480059342718857),
+            Offset(15.88, 29.48),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(35.2, 17.2),
+            Offset(35.20133020799377, 17.202496611869943),
+            Offset(36.08223603809319, 19.20933667763641),
+            Offset(36.30473635371463, 26.567167700077555),
+            Offset(32.35111295573567, 32.19757663138127),
+            Offset(28.286723241557954, 34.04261217377702),
+            Offset(25.205340375246543, 34.35488342834059),
+            Offset(22.92056698879951, 34.06725233707878),
+            Offset(21.20712595566319, 33.54460858047224),
+            Offset(19.90072816888673, 32.95035738238742),
+            Offset(18.887775779309706, 32.36218428291335),
+            Offset(18.092366394413514, 31.814388343443472),
+            Offset(17.478912588855714, 31.310281730290257),
+            Offset(17.009299068467204, 30.862572439389364),
+            Offset(16.651802906423065, 30.478333078342565),
+            Offset(16.383009748664925, 30.160053531509803),
+            Offset(16.184878866746974, 29.906664174691322),
+            Offset(16.044089769763733, 29.71561828750153),
+            Offset(15.950787992697913, 29.583456461861584),
+            Offset(15.897801030928724, 29.506284760669423),
+            Offset(15.880040049342629, 29.480059342718857),
+            Offset(15.88, 29.48),
+          ],
+          <Offset>[
+            Offset(35.2, 34.8),
+            Offset(35.19750620549164, 34.80226521395049),
+            Offset(33.142422203433355, 36.40619669474303),
+            Offset(24.747975921718226, 38.79115144612656),
+            Offset(17.213465824170402, 35.77456562512825),
+            Offset(14.087509018127873, 31.596287379559158),
+            Offset(12.982664015207707, 28.20334593862164),
+            Offset(12.742961813510924, 25.59383569924602),
+            Offset(12.89264852964688, 23.57904486699663),
+            Offset(13.20967315509607, 22.002123241757968),
+            Offset(13.58287087866059, 20.749270041876564),
+            Offset(13.959933927651988, 19.74208111128378),
+            Offset(14.3377469801464, 18.94295871281142),
+            Offset(14.693700004697835, 18.31444106552057),
+            Offset(15.011548853709796, 17.824196733156548),
+            Offset(15.282251777644161, 17.447621353156435),
+            Offset(15.50206828496817, 17.16494648399472),
+            Offset(15.670133508272507, 16.961099217207604),
+            Offset(15.78750849445444, 16.8245011830994),
+            Offset(15.856451190964936, 16.746351759640973),
+            Offset(15.879946796268738, 16.720059343059617),
+            Offset(15.879999999999999, 16.720000000000002),
+          ],
+          <Offset>[
+            Offset(35.2, 34.8),
+            Offset(35.19750620549164, 34.80226521395049),
+            Offset(33.142422203433355, 36.40619669474303),
+            Offset(24.747975921718226, 38.79115144612656),
+            Offset(17.213465824170402, 35.77456562512825),
+            Offset(14.087509018127873, 31.596287379559158),
+            Offset(12.982664015207707, 28.20334593862164),
+            Offset(12.742961813510924, 25.59383569924602),
+            Offset(12.89264852964688, 23.57904486699663),
+            Offset(13.20967315509607, 22.002123241757968),
+            Offset(13.58287087866059, 20.749270041876564),
+            Offset(13.959933927651988, 19.74208111128378),
+            Offset(14.3377469801464, 18.94295871281142),
+            Offset(14.693700004697835, 18.31444106552057),
+            Offset(15.011548853709796, 17.824196733156548),
+            Offset(15.282251777644161, 17.447621353156435),
+            Offset(15.50206828496817, 17.16494648399472),
+            Offset(15.670133508272507, 16.961099217207604),
+            Offset(15.78750849445444, 16.8245011830994),
+            Offset(15.856451190964936, 16.746351759640973),
+            Offset(15.879946796268738, 16.720059343059617),
+            Offset(15.879999999999999, 16.720000000000002),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.285714285714,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(10.0, 26.0),
+            Offset(9.999565779019244, 25.996958092697728),
+            Offset(9.863179258510144, 23.612315433654864),
+            Offset(12.452761639331678, 15.835363679752177),
+            Offset(18.834098352379797, 10.835142987213462),
+            Offset(24.406021200709937, 9.863694019137322),
+            Offset(28.507374325508763, 10.595389722571399),
+            Offset(31.42060810506175, 11.961122338395),
+            Offset(33.46861178272727, 13.495458557937988),
+            Offset(34.90276563764392, 14.992797246058515),
+            Offset(35.90322109391197, 16.36368363741724),
+            Offset(36.59776146682701, 17.573772022023455),
+            Offset(37.07681747862736, 18.615128169522876),
+            Offset(37.40459569801132, 19.492582316574545),
+            Offset(37.626755542882265, 20.216941267370075),
+            Offset(37.77527700273555, 20.799727590047226),
+            Offset(37.87291743778957, 21.253700350603722),
+            Offset(37.93537260667183, 21.590562241264497),
+            Offset(37.973261356283984, 21.821016964503688),
+            Offset(37.99344532401808, 21.954642338436525),
+            Offset(37.99998538315687, 21.999897684768573),
+            Offset(38.0, 21.999999999999993),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(10.0, 26.0),
+            Offset(9.999565779019244, 25.996958092697728),
+            Offset(9.863179258510144, 23.612315433654864),
+            Offset(12.452761639331678, 15.835363679752177),
+            Offset(18.834098352379797, 10.835142987213462),
+            Offset(24.406021200709937, 9.863694019137322),
+            Offset(28.507374325508763, 10.595389722571399),
+            Offset(31.42060810506175, 11.961122338395),
+            Offset(33.46861178272727, 13.495458557937988),
+            Offset(34.90276563764392, 14.992797246058515),
+            Offset(35.90322109391197, 16.36368363741724),
+            Offset(36.59776146682701, 17.573772022023455),
+            Offset(37.07681747862736, 18.615128169522876),
+            Offset(37.40459569801132, 19.492582316574545),
+            Offset(37.626755542882265, 20.216941267370075),
+            Offset(37.77527700273555, 20.799727590047226),
+            Offset(37.87291743778957, 21.253700350603722),
+            Offset(37.93537260667183, 21.590562241264497),
+            Offset(37.973261356283984, 21.821016964503688),
+            Offset(37.99344532401808, 21.954642338436525),
+            Offset(37.99998538315687, 21.999897684768573),
+            Offset(38.0, 21.999999999999993),
+          ],
+          <Offset>[
+            Offset(10.0, 22.0),
+            Offset(10.000434881903109, 21.996958187115208),
+            Offset(10.537203766746376, 19.66951300869151),
+            Offset(15.2007493434278, 12.928722391574958),
+            Offset(22.726894381864838, 9.915284806391266),
+            Offset(28.34794602732309, 10.54283219457061),
+            Offset(32.08037023016842, 12.393638944810997),
+            Offset(34.49466952610868, 14.52044771427649),
+            Offset(36.03113891519555, 16.566851514784105),
+            Offset(36.98867178324178, 18.40585973475647),
+            Offset(37.56526083167866, 20.0020381982159),
+            Offset(37.893194841987366, 21.35819435185408),
+            Offset(38.06150889201586, 22.492031936757623),
+            Offset(38.13048882144999, 23.426165819354733),
+            Offset(38.14094176630335, 24.183755168682307),
+            Offset(38.12034219741291, 24.78481604721129),
+            Offset(38.086964642108946, 25.247969219160655),
+            Offset(38.052600274850896, 25.58884408148203),
+            Offset(38.02444615197159, 25.820689465683056),
+            Offset(38.00640765629513, 25.954621335624125),
+            Offset(38.000014616095385, 25.999897684661754),
+            Offset(38.0, 25.999999999999993),
+          ],
+          <Offset>[
+            Offset(10.0, 22.0),
+            Offset(10.000434881903109, 21.996958187115208),
+            Offset(10.537203766746376, 19.66951300869151),
+            Offset(15.2007493434278, 12.928722391574958),
+            Offset(22.726894381864838, 9.915284806391266),
+            Offset(28.34794602732309, 10.54283219457061),
+            Offset(32.08037023016842, 12.393638944810997),
+            Offset(34.49466952610868, 14.52044771427649),
+            Offset(36.03113891519555, 16.566851514784105),
+            Offset(36.98867178324178, 18.40585973475647),
+            Offset(37.56526083167866, 20.0020381982159),
+            Offset(37.893194841987366, 21.35819435185408),
+            Offset(38.06150889201586, 22.492031936757623),
+            Offset(38.13048882144999, 23.426165819354733),
+            Offset(38.14094176630335, 24.183755168682307),
+            Offset(38.12034219741291, 24.78481604721129),
+            Offset(38.086964642108946, 25.247969219160655),
+            Offset(38.052600274850896, 25.58884408148203),
+            Offset(38.02444615197159, 25.820689465683056),
+            Offset(38.00640765629513, 25.954621335624125),
+            Offset(38.000014616095385, 25.999897684661754),
+            Offset(38.0, 25.999999999999993),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(10.0, 22.0),
+            Offset(10.000434881903109, 21.996958187115208),
+            Offset(10.537203766746376, 19.66951300869151),
+            Offset(15.2007493434278, 12.928722391574958),
+            Offset(22.726894381864838, 9.915284806391266),
+            Offset(28.34794602732309, 10.54283219457061),
+            Offset(32.08037023016842, 12.393638944810997),
+            Offset(34.49466952610868, 14.52044771427649),
+            Offset(36.03113891519555, 16.566851514784105),
+            Offset(36.98867178324178, 18.40585973475647),
+            Offset(37.56526083167866, 20.0020381982159),
+            Offset(37.893194841987366, 21.35819435185408),
+            Offset(38.06150889201586, 22.492031936757623),
+            Offset(38.13048882144999, 23.426165819354733),
+            Offset(38.14094176630335, 24.183755168682307),
+            Offset(38.12034219741291, 24.78481604721129),
+            Offset(38.086964642108946, 25.247969219160655),
+            Offset(38.052600274850896, 25.58884408148203),
+            Offset(38.02444615197159, 25.820689465683056),
+            Offset(38.00640765629513, 25.954621335624125),
+            Offset(38.000014616095385, 25.999897684661754),
+            Offset(38.0, 25.999999999999993),
+          ],
+          <Offset>[
+            Offset(38.0, 22.0),
+            Offset(38.00043422098076, 22.003041907302272),
+            Offset(38.136820741489856, 24.387684566345136),
+            Offset(35.547238360668324, 32.16463632024782),
+            Offset(29.165901647620203, 37.16485701278654),
+            Offset(23.59397879929007, 38.13630598086269),
+            Offset(19.492625674491244, 37.4046102774286),
+            Offset(16.57939189493825, 36.038877661605),
+            Offset(14.531388217272726, 34.50454144206201),
+            Offset(13.097234362356085, 33.007202753941485),
+            Offset(12.096778906088034, 31.63631636258276),
+            Offset(11.402238533172989, 30.426227977976552),
+            Offset(10.923182521372642, 29.384871830477124),
+            Offset(10.595404301988683, 28.507417683425455),
+            Offset(10.373244457117737, 27.783058732629918),
+            Offset(10.224722997264447, 27.20027240995278),
+            Offset(10.12708256221043, 26.746299649396278),
+            Offset(10.064627393328172, 26.40943775873551),
+            Offset(10.026738643716017, 26.17898303549632),
+            Offset(10.006554675981915, 26.045357661563475),
+            Offset(10.000014616843135, 26.00010231523142),
+            Offset(10.0, 26.0),
+          ],
+          <Offset>[
+            Offset(38.0, 22.0),
+            Offset(38.00043422098076, 22.003041907302272),
+            Offset(38.136820741489856, 24.387684566345136),
+            Offset(35.547238360668324, 32.16463632024782),
+            Offset(29.165901647620203, 37.16485701278654),
+            Offset(23.59397879929007, 38.13630598086269),
+            Offset(19.492625674491244, 37.4046102774286),
+            Offset(16.57939189493825, 36.038877661605),
+            Offset(14.531388217272726, 34.50454144206201),
+            Offset(13.097234362356085, 33.007202753941485),
+            Offset(12.096778906088034, 31.63631636258276),
+            Offset(11.402238533172989, 30.426227977976552),
+            Offset(10.923182521372642, 29.384871830477124),
+            Offset(10.595404301988683, 28.507417683425455),
+            Offset(10.373244457117737, 27.783058732629918),
+            Offset(10.224722997264447, 27.20027240995278),
+            Offset(10.12708256221043, 26.746299649396278),
+            Offset(10.064627393328172, 26.40943775873551),
+            Offset(10.026738643716017, 26.17898303549632),
+            Offset(10.006554675981915, 26.045357661563475),
+            Offset(10.000014616843135, 26.00010231523142),
+            Offset(10.0, 26.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.0, 22.0),
+            Offset(38.00043422098076, 22.003041907302272),
+            Offset(38.136820741489856, 24.387684566345136),
+            Offset(35.547238360668324, 32.16463632024782),
+            Offset(29.165901647620203, 37.16485701278654),
+            Offset(23.59397879929007, 38.13630598086269),
+            Offset(19.492625674491244, 37.4046102774286),
+            Offset(16.57939189493825, 36.038877661605),
+            Offset(14.531388217272726, 34.50454144206201),
+            Offset(13.097234362356085, 33.007202753941485),
+            Offset(12.096778906088034, 31.63631636258276),
+            Offset(11.402238533172989, 30.426227977976552),
+            Offset(10.923182521372642, 29.384871830477124),
+            Offset(10.595404301988683, 28.507417683425455),
+            Offset(10.373244457117737, 27.783058732629918),
+            Offset(10.224722997264447, 27.20027240995278),
+            Offset(10.12708256221043, 26.746299649396278),
+            Offset(10.064627393328172, 26.40943775873551),
+            Offset(10.026738643716017, 26.17898303549632),
+            Offset(10.006554675981915, 26.045357661563475),
+            Offset(10.000014616843135, 26.00010231523142),
+            Offset(10.0, 26.0),
+          ],
+          <Offset>[
+            Offset(38.0, 26.0),
+            Offset(37.99956511809689, 26.003041812884792),
+            Offset(37.46279623325363, 28.33048699130849),
+            Offset(32.7992506565722, 35.07127760842504),
+            Offset(25.273105618135162, 38.08471519360873),
+            Offset(19.652053972676917, 37.4571678054294),
+            Offset(15.919629769831586, 35.606361055189005),
+            Offset(13.50533047389132, 33.479552285723514),
+            Offset(11.968861084804454, 31.433148485215895),
+            Offset(11.011328216758224, 29.59414026524353),
+            Offset(10.43473916832134, 27.9979618017841),
+            Offset(10.106805158012635, 26.641805648145926),
+            Offset(9.938491107984142, 25.507968063242377),
+            Offset(9.86951117855001, 24.573834180645267),
+            Offset(9.859058233696649, 23.816244831317686),
+            Offset(9.87965780258709, 23.215183952788717),
+            Offset(9.913035357891056, 22.752030780839345),
+            Offset(9.947399725149102, 22.411155918517977),
+            Offset(9.97555384802841, 22.17931053431695),
+            Offset(9.993592343704867, 22.045378664375875),
+            Offset(9.999985383904612, 22.00010231533824),
+            Offset(10.0, 22.0),
+          ],
+          <Offset>[
+            Offset(38.0, 26.0),
+            Offset(37.99956511809689, 26.003041812884792),
+            Offset(37.46279623325363, 28.33048699130849),
+            Offset(32.7992506565722, 35.07127760842504),
+            Offset(25.273105618135162, 38.08471519360873),
+            Offset(19.652053972676917, 37.4571678054294),
+            Offset(15.919629769831586, 35.606361055189005),
+            Offset(13.50533047389132, 33.479552285723514),
+            Offset(11.968861084804454, 31.433148485215895),
+            Offset(11.011328216758224, 29.59414026524353),
+            Offset(10.43473916832134, 27.9979618017841),
+            Offset(10.106805158012635, 26.641805648145926),
+            Offset(9.938491107984142, 25.507968063242377),
+            Offset(9.86951117855001, 24.573834180645267),
+            Offset(9.859058233696649, 23.816244831317686),
+            Offset(9.87965780258709, 23.215183952788717),
+            Offset(9.913035357891056, 22.752030780839345),
+            Offset(9.947399725149102, 22.411155918517977),
+            Offset(9.97555384802841, 22.17931053431695),
+            Offset(9.993592343704867, 22.045378664375875),
+            Offset(9.999985383904612, 22.00010231533824),
+            Offset(10.0, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.0, 26.0),
+            Offset(37.99956511809689, 26.003041812884792),
+            Offset(37.46279623325363, 28.33048699130849),
+            Offset(32.7992506565722, 35.07127760842504),
+            Offset(25.273105618135162, 38.08471519360873),
+            Offset(19.652053972676917, 37.4571678054294),
+            Offset(15.919629769831586, 35.606361055189005),
+            Offset(13.50533047389132, 33.479552285723514),
+            Offset(11.968861084804454, 31.433148485215895),
+            Offset(11.011328216758224, 29.59414026524353),
+            Offset(10.43473916832134, 27.9979618017841),
+            Offset(10.106805158012635, 26.641805648145926),
+            Offset(9.938491107984142, 25.507968063242377),
+            Offset(9.86951117855001, 24.573834180645267),
+            Offset(9.859058233696649, 23.816244831317686),
+            Offset(9.87965780258709, 23.215183952788717),
+            Offset(9.913035357891056, 22.752030780839345),
+            Offset(9.947399725149102, 22.411155918517977),
+            Offset(9.97555384802841, 22.17931053431695),
+            Offset(9.993592343704867, 22.045378664375875),
+            Offset(9.999985383904612, 22.00010231533824),
+            Offset(10.0, 22.0),
+          ],
+          <Offset>[
+            Offset(10.0, 26.0),
+            Offset(9.999565779019244, 25.996958092697728),
+            Offset(9.863179258510144, 23.612315433654864),
+            Offset(12.452761639331678, 15.835363679752177),
+            Offset(18.834098352379797, 10.835142987213462),
+            Offset(24.406021200709937, 9.863694019137322),
+            Offset(28.507374325508763, 10.595389722571399),
+            Offset(31.42060810506175, 11.961122338395),
+            Offset(33.46861178272727, 13.495458557937988),
+            Offset(34.90276563764392, 14.992797246058515),
+            Offset(35.90322109391197, 16.36368363741724),
+            Offset(36.59776146682701, 17.573772022023455),
+            Offset(37.07681747862736, 18.615128169522876),
+            Offset(37.40459569801132, 19.492582316574545),
+            Offset(37.626755542882265, 20.216941267370075),
+            Offset(37.77527700273555, 20.799727590047226),
+            Offset(37.87291743778957, 21.253700350603722),
+            Offset(37.93537260667183, 21.590562241264497),
+            Offset(37.973261356283984, 21.821016964503688),
+            Offset(37.99344532401808, 21.954642338436525),
+            Offset(37.99998538315687, 21.999897684768573),
+            Offset(38.0, 21.999999999999993),
+          ],
+          <Offset>[
+            Offset(10.0, 26.0),
+            Offset(9.999565779019244, 25.996958092697728),
+            Offset(9.863179258510144, 23.612315433654864),
+            Offset(12.452761639331678, 15.835363679752177),
+            Offset(18.834098352379797, 10.835142987213462),
+            Offset(24.406021200709937, 9.863694019137322),
+            Offset(28.507374325508763, 10.595389722571399),
+            Offset(31.42060810506175, 11.961122338395),
+            Offset(33.46861178272727, 13.495458557937988),
+            Offset(34.90276563764392, 14.992797246058515),
+            Offset(35.90322109391197, 16.36368363741724),
+            Offset(36.59776146682701, 17.573772022023455),
+            Offset(37.07681747862736, 18.615128169522876),
+            Offset(37.40459569801132, 19.492582316574545),
+            Offset(37.626755542882265, 20.216941267370075),
+            Offset(37.77527700273555, 20.799727590047226),
+            Offset(37.87291743778957, 21.253700350603722),
+            Offset(37.93537260667183, 21.590562241264497),
+            Offset(37.973261356283984, 21.821016964503688),
+            Offset(37.99344532401808, 21.954642338436525),
+            Offset(37.99998538315687, 21.999897684768573),
+            Offset(38.0, 21.999999999999993),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.285714285714,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(22.0, 10.0),
+            Offset(22.003041907302272, 9.999565779019244),
+            Offset(24.387684566345136, 9.863179258510144),
+            Offset(32.16463632024782, 12.452761639331678),
+            Offset(37.16485701278654, 18.834098352379797),
+            Offset(38.13630598086269, 24.406021200709937),
+            Offset(37.4046102774286, 28.50737432550876),
+            Offset(36.038877661605, 31.42060810506175),
+            Offset(34.50454144206201, 33.46861178272727),
+            Offset(33.007202753941485, 34.90276563764392),
+            Offset(31.63631636258276, 35.90322109391197),
+            Offset(30.426227977976545, 36.597761466827016),
+            Offset(29.384871830477124, 37.07681747862736),
+            Offset(28.507417683425455, 37.40459569801132),
+            Offset(27.78305873262992, 37.62675554288226),
+            Offset(27.20027240995278, 37.77527700273556),
+            Offset(26.746299649396278, 37.87291743778957),
+            Offset(26.409437758735507, 37.935372606671834),
+            Offset(26.178983035496316, 37.973261356283984),
+            Offset(26.045357661563475, 37.99344532401808),
+            Offset(26.000102315231423, 37.99998538315686),
+            Offset(26.000000000000004, 38.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(22.0, 10.0),
+            Offset(22.003041907302272, 9.999565779019244),
+            Offset(24.387684566345136, 9.863179258510144),
+            Offset(32.16463632024782, 12.452761639331678),
+            Offset(37.16485701278654, 18.834098352379797),
+            Offset(38.13630598086269, 24.406021200709937),
+            Offset(37.4046102774286, 28.50737432550876),
+            Offset(36.038877661605, 31.42060810506175),
+            Offset(34.50454144206201, 33.46861178272727),
+            Offset(33.007202753941485, 34.90276563764392),
+            Offset(31.63631636258276, 35.90322109391197),
+            Offset(30.426227977976545, 36.597761466827016),
+            Offset(29.384871830477124, 37.07681747862736),
+            Offset(28.507417683425455, 37.40459569801132),
+            Offset(27.78305873262992, 37.62675554288226),
+            Offset(27.20027240995278, 37.77527700273556),
+            Offset(26.746299649396278, 37.87291743778957),
+            Offset(26.409437758735507, 37.935372606671834),
+            Offset(26.178983035496316, 37.973261356283984),
+            Offset(26.045357661563475, 37.99344532401808),
+            Offset(26.000102315231423, 37.99998538315686),
+            Offset(26.000000000000004, 38.0),
+          ],
+          <Offset>[
+            Offset(26.0, 10.0),
+            Offset(26.003041812884792, 10.000434881903109),
+            Offset(28.33048699130849, 10.537203766746376),
+            Offset(35.07127760842504, 15.2007493434278),
+            Offset(38.08471519360873, 22.726894381864838),
+            Offset(37.4571678054294, 28.34794602732309),
+            Offset(35.606361055189005, 32.08037023016842),
+            Offset(33.479552285723514, 34.49466952610868),
+            Offset(31.433148485215895, 36.03113891519555),
+            Offset(29.59414026524353, 36.98867178324178),
+            Offset(27.9979618017841, 37.56526083167866),
+            Offset(26.641805648145922, 37.893194841987366),
+            Offset(25.507968063242377, 38.06150889201586),
+            Offset(24.573834180645267, 38.13048882144999),
+            Offset(23.81624483131769, 38.14094176630335),
+            Offset(23.215183952788713, 38.12034219741291),
+            Offset(22.752030780839345, 38.086964642108946),
+            Offset(22.411155918517974, 38.0526002748509),
+            Offset(22.179310534316947, 38.024446151971595),
+            Offset(22.045378664375875, 38.00640765629513),
+            Offset(22.000102315338243, 38.000014616095385),
+            Offset(22.000000000000004, 38.0),
+          ],
+          <Offset>[
+            Offset(26.0, 10.0),
+            Offset(26.003041812884792, 10.000434881903109),
+            Offset(28.33048699130849, 10.537203766746376),
+            Offset(35.07127760842504, 15.2007493434278),
+            Offset(38.08471519360873, 22.726894381864838),
+            Offset(37.4571678054294, 28.34794602732309),
+            Offset(35.606361055189005, 32.08037023016842),
+            Offset(33.479552285723514, 34.49466952610868),
+            Offset(31.433148485215895, 36.03113891519555),
+            Offset(29.59414026524353, 36.98867178324178),
+            Offset(27.9979618017841, 37.56526083167866),
+            Offset(26.641805648145922, 37.893194841987366),
+            Offset(25.507968063242377, 38.06150889201586),
+            Offset(24.573834180645267, 38.13048882144999),
+            Offset(23.81624483131769, 38.14094176630335),
+            Offset(23.215183952788713, 38.12034219741291),
+            Offset(22.752030780839345, 38.086964642108946),
+            Offset(22.411155918517974, 38.0526002748509),
+            Offset(22.179310534316947, 38.024446151971595),
+            Offset(22.045378664375875, 38.00640765629513),
+            Offset(22.000102315338243, 38.000014616095385),
+            Offset(22.000000000000004, 38.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(26.0, 10.0),
+            Offset(26.003041812884792, 10.000434881903109),
+            Offset(28.33048699130849, 10.537203766746376),
+            Offset(35.07127760842504, 15.2007493434278),
+            Offset(38.08471519360873, 22.726894381864838),
+            Offset(37.4571678054294, 28.34794602732309),
+            Offset(35.606361055189005, 32.08037023016842),
+            Offset(33.479552285723514, 34.49466952610868),
+            Offset(31.433148485215895, 36.03113891519555),
+            Offset(29.59414026524353, 36.98867178324178),
+            Offset(27.9979618017841, 37.56526083167866),
+            Offset(26.641805648145922, 37.893194841987366),
+            Offset(25.507968063242377, 38.06150889201586),
+            Offset(24.573834180645267, 38.13048882144999),
+            Offset(23.81624483131769, 38.14094176630335),
+            Offset(23.215183952788713, 38.12034219741291),
+            Offset(22.752030780839345, 38.086964642108946),
+            Offset(22.411155918517974, 38.0526002748509),
+            Offset(22.179310534316947, 38.024446151971595),
+            Offset(22.045378664375875, 38.00640765629513),
+            Offset(22.000102315338243, 38.000014616095385),
+            Offset(22.000000000000004, 38.0),
+          ],
+          <Offset>[
+            Offset(26.0, 38.0),
+            Offset(25.996958092697728, 38.00043422098076),
+            Offset(23.612315433654864, 38.136820741489856),
+            Offset(15.835363679752177, 35.547238360668324),
+            Offset(10.835142987213462, 29.165901647620203),
+            Offset(9.863694019137322, 23.59397879929007),
+            Offset(10.595389722571403, 19.49262567449124),
+            Offset(11.961122338395, 16.57939189493825),
+            Offset(13.495458557937988, 14.531388217272726),
+            Offset(14.992797246058515, 13.097234362356085),
+            Offset(16.36368363741724, 12.096778906088034),
+            Offset(17.573772022023455, 11.402238533172993),
+            Offset(18.615128169522876, 10.923182521372642),
+            Offset(19.492582316574545, 10.595404301988683),
+            Offset(20.21694126737008, 10.373244457117734),
+            Offset(20.79972759004722, 10.22472299726445),
+            Offset(21.253700350603722, 10.12708256221043),
+            Offset(21.590562241264493, 10.064627393328175),
+            Offset(21.821016964503684, 10.02673864371602),
+            Offset(21.954642338436525, 10.006554675981915),
+            Offset(21.999897684768577, 10.000014616843131),
+            Offset(21.999999999999996, 9.999999999999996),
+          ],
+          <Offset>[
+            Offset(26.0, 38.0),
+            Offset(25.996958092697728, 38.00043422098076),
+            Offset(23.612315433654864, 38.136820741489856),
+            Offset(15.835363679752177, 35.547238360668324),
+            Offset(10.835142987213462, 29.165901647620203),
+            Offset(9.863694019137322, 23.59397879929007),
+            Offset(10.595389722571403, 19.49262567449124),
+            Offset(11.961122338395, 16.57939189493825),
+            Offset(13.495458557937988, 14.531388217272726),
+            Offset(14.992797246058515, 13.097234362356085),
+            Offset(16.36368363741724, 12.096778906088034),
+            Offset(17.573772022023455, 11.402238533172993),
+            Offset(18.615128169522876, 10.923182521372642),
+            Offset(19.492582316574545, 10.595404301988683),
+            Offset(20.21694126737008, 10.373244457117734),
+            Offset(20.79972759004722, 10.22472299726445),
+            Offset(21.253700350603722, 10.12708256221043),
+            Offset(21.590562241264493, 10.064627393328175),
+            Offset(21.821016964503684, 10.02673864371602),
+            Offset(21.954642338436525, 10.006554675981915),
+            Offset(21.999897684768577, 10.000014616843131),
+            Offset(21.999999999999996, 9.999999999999996),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(26.0, 38.0),
+            Offset(25.996958092697728, 38.00043422098076),
+            Offset(23.612315433654864, 38.136820741489856),
+            Offset(15.835363679752177, 35.547238360668324),
+            Offset(10.835142987213462, 29.165901647620203),
+            Offset(9.863694019137322, 23.59397879929007),
+            Offset(10.595389722571403, 19.49262567449124),
+            Offset(11.961122338395, 16.57939189493825),
+            Offset(13.495458557937988, 14.531388217272726),
+            Offset(14.992797246058515, 13.097234362356085),
+            Offset(16.36368363741724, 12.096778906088034),
+            Offset(17.573772022023455, 11.402238533172993),
+            Offset(18.615128169522876, 10.923182521372642),
+            Offset(19.492582316574545, 10.595404301988683),
+            Offset(20.21694126737008, 10.373244457117734),
+            Offset(20.79972759004722, 10.22472299726445),
+            Offset(21.253700350603722, 10.12708256221043),
+            Offset(21.590562241264493, 10.064627393328175),
+            Offset(21.821016964503684, 10.02673864371602),
+            Offset(21.954642338436525, 10.006554675981915),
+            Offset(21.999897684768577, 10.000014616843131),
+            Offset(21.999999999999996, 9.999999999999996),
+          ],
+          <Offset>[
+            Offset(22.0, 38.0),
+            Offset(21.996958187115208, 37.99956511809689),
+            Offset(19.66951300869151, 37.46279623325363),
+            Offset(12.928722391574958, 32.7992506565722),
+            Offset(9.915284806391266, 25.273105618135162),
+            Offset(10.54283219457061, 19.652053972676917),
+            Offset(12.393638944811, 15.919629769831582),
+            Offset(14.52044771427649, 13.50533047389132),
+            Offset(16.566851514784105, 11.968861084804454),
+            Offset(18.40585973475647, 11.011328216758224),
+            Offset(20.0020381982159, 10.43473916832134),
+            Offset(21.358194351854078, 10.106805158012639),
+            Offset(22.492031936757623, 9.938491107984142),
+            Offset(23.426165819354733, 9.86951117855001),
+            Offset(24.18375516868231, 9.859058233696645),
+            Offset(24.784816047211287, 9.879657802587094),
+            Offset(25.247969219160655, 9.913035357891056),
+            Offset(25.588844081482026, 9.947399725149106),
+            Offset(25.820689465683053, 9.975553848028413),
+            Offset(25.954621335624125, 9.993592343704867),
+            Offset(25.999897684661757, 9.999985383904608),
+            Offset(25.999999999999996, 9.999999999999996),
+          ],
+          <Offset>[
+            Offset(22.0, 38.0),
+            Offset(21.996958187115208, 37.99956511809689),
+            Offset(19.66951300869151, 37.46279623325363),
+            Offset(12.928722391574958, 32.7992506565722),
+            Offset(9.915284806391266, 25.273105618135162),
+            Offset(10.54283219457061, 19.652053972676917),
+            Offset(12.393638944811, 15.919629769831582),
+            Offset(14.52044771427649, 13.50533047389132),
+            Offset(16.566851514784105, 11.968861084804454),
+            Offset(18.40585973475647, 11.011328216758224),
+            Offset(20.0020381982159, 10.43473916832134),
+            Offset(21.358194351854078, 10.106805158012639),
+            Offset(22.492031936757623, 9.938491107984142),
+            Offset(23.426165819354733, 9.86951117855001),
+            Offset(24.18375516868231, 9.859058233696645),
+            Offset(24.784816047211287, 9.879657802587094),
+            Offset(25.247969219160655, 9.913035357891056),
+            Offset(25.588844081482026, 9.947399725149106),
+            Offset(25.820689465683053, 9.975553848028413),
+            Offset(25.954621335624125, 9.993592343704867),
+            Offset(25.999897684661757, 9.999985383904608),
+            Offset(25.999999999999996, 9.999999999999996),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(22.0, 38.0),
+            Offset(21.996958187115208, 37.99956511809689),
+            Offset(19.66951300869151, 37.46279623325363),
+            Offset(12.928722391574958, 32.7992506565722),
+            Offset(9.915284806391266, 25.273105618135162),
+            Offset(10.54283219457061, 19.652053972676917),
+            Offset(12.393638944811, 15.919629769831582),
+            Offset(14.52044771427649, 13.50533047389132),
+            Offset(16.566851514784105, 11.968861084804454),
+            Offset(18.40585973475647, 11.011328216758224),
+            Offset(20.0020381982159, 10.43473916832134),
+            Offset(21.358194351854078, 10.106805158012639),
+            Offset(22.492031936757623, 9.938491107984142),
+            Offset(23.426165819354733, 9.86951117855001),
+            Offset(24.18375516868231, 9.859058233696645),
+            Offset(24.784816047211287, 9.879657802587094),
+            Offset(25.247969219160655, 9.913035357891056),
+            Offset(25.588844081482026, 9.947399725149106),
+            Offset(25.820689465683053, 9.975553848028413),
+            Offset(25.954621335624125, 9.993592343704867),
+            Offset(25.999897684661757, 9.999985383904608),
+            Offset(25.999999999999996, 9.999999999999996),
+          ],
+          <Offset>[
+            Offset(22.0, 10.0),
+            Offset(22.003041907302272, 9.999565779019244),
+            Offset(24.387684566345136, 9.863179258510144),
+            Offset(32.16463632024782, 12.452761639331678),
+            Offset(37.16485701278654, 18.834098352379797),
+            Offset(38.13630598086269, 24.406021200709937),
+            Offset(37.4046102774286, 28.50737432550876),
+            Offset(36.038877661605, 31.42060810506175),
+            Offset(34.50454144206201, 33.46861178272727),
+            Offset(33.007202753941485, 34.90276563764392),
+            Offset(31.63631636258276, 35.90322109391197),
+            Offset(30.426227977976545, 36.597761466827016),
+            Offset(29.384871830477124, 37.07681747862736),
+            Offset(28.507417683425455, 37.40459569801132),
+            Offset(27.78305873262992, 37.62675554288226),
+            Offset(27.20027240995278, 37.77527700273556),
+            Offset(26.746299649396278, 37.87291743778957),
+            Offset(26.409437758735507, 37.935372606671834),
+            Offset(26.178983035496316, 37.973261356283984),
+            Offset(26.045357661563475, 37.99344532401808),
+            Offset(26.000102315231423, 37.99998538315686),
+            Offset(26.000000000000004, 38.0),
+          ],
+          <Offset>[
+            Offset(22.0, 10.0),
+            Offset(22.003041907302272, 9.999565779019244),
+            Offset(24.387684566345136, 9.863179258510144),
+            Offset(32.16463632024782, 12.452761639331678),
+            Offset(37.16485701278654, 18.834098352379797),
+            Offset(38.13630598086269, 24.406021200709937),
+            Offset(37.4046102774286, 28.50737432550876),
+            Offset(36.038877661605, 31.42060810506175),
+            Offset(34.50454144206201, 33.46861178272727),
+            Offset(33.007202753941485, 34.90276563764392),
+            Offset(31.63631636258276, 35.90322109391197),
+            Offset(30.426227977976545, 36.597761466827016),
+            Offset(29.384871830477124, 37.07681747862736),
+            Offset(28.507417683425455, 37.40459569801132),
+            Offset(27.78305873262992, 37.62675554288226),
+            Offset(27.20027240995278, 37.77527700273556),
+            Offset(26.746299649396278, 37.87291743778957),
+            Offset(26.409437758735507, 37.935372606671834),
+            Offset(26.178983035496316, 37.973261356283984),
+            Offset(26.045357661563475, 37.99344532401808),
+            Offset(26.000102315231423, 37.99998538315686),
+            Offset(26.000000000000004, 38.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(10.0, 26.0),
+            Offset(9.999565779019244, 25.996958092697728),
+            Offset(9.863179258510144, 23.612315433654864),
+            Offset(12.452761639331678, 15.835363679752177),
+            Offset(18.834098352379797, 10.835142987213462),
+            Offset(24.406021200709937, 9.863694019137322),
+            Offset(28.507374325508763, 10.595389722571399),
+            Offset(31.42060810506175, 11.961122338395),
+            Offset(33.46861178272727, 13.495458557937988),
+            Offset(34.90276563764392, 14.992797246058515),
+            Offset(35.90322109391197, 16.36368363741724),
+            Offset(36.59776146682701, 17.573772022023455),
+            Offset(37.07681747862736, 18.615128169522876),
+            Offset(37.40459569801132, 19.492582316574545),
+            Offset(37.626755542882265, 20.216941267370075),
+            Offset(37.77527700273555, 20.799727590047226),
+            Offset(37.87291743778957, 21.253700350603722),
+            Offset(37.93537260667183, 21.590562241264497),
+            Offset(37.973261356283984, 21.821016964503688),
+            Offset(37.99344532401808, 21.954642338436525),
+            Offset(37.99998538315687, 21.999897684768573),
+            Offset(38.0, 21.999999999999993),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(10.0, 26.0),
+            Offset(9.999565779019244, 25.996958092697728),
+            Offset(9.863179258510144, 23.612315433654864),
+            Offset(12.452761639331678, 15.835363679752177),
+            Offset(18.834098352379797, 10.835142987213462),
+            Offset(24.406021200709937, 9.863694019137322),
+            Offset(28.507374325508763, 10.595389722571399),
+            Offset(31.42060810506175, 11.961122338395),
+            Offset(33.46861178272727, 13.495458557937988),
+            Offset(34.90276563764392, 14.992797246058515),
+            Offset(35.90322109391197, 16.36368363741724),
+            Offset(36.59776146682701, 17.573772022023455),
+            Offset(37.07681747862736, 18.615128169522876),
+            Offset(37.40459569801132, 19.492582316574545),
+            Offset(37.626755542882265, 20.216941267370075),
+            Offset(37.77527700273555, 20.799727590047226),
+            Offset(37.87291743778957, 21.253700350603722),
+            Offset(37.93537260667183, 21.590562241264497),
+            Offset(37.973261356283984, 21.821016964503688),
+            Offset(37.99344532401808, 21.954642338436525),
+            Offset(37.99998538315687, 21.999897684768573),
+            Offset(38.0, 21.999999999999993),
+          ],
+          <Offset>[
+            Offset(10.0, 22.0),
+            Offset(10.000434881903109, 21.996958187115208),
+            Offset(10.537203766746376, 19.66951300869151),
+            Offset(15.2007493434278, 12.928722391574958),
+            Offset(22.726894381864838, 9.915284806391266),
+            Offset(28.34794602732309, 10.54283219457061),
+            Offset(32.08037023016842, 12.393638944810997),
+            Offset(34.49466952610868, 14.52044771427649),
+            Offset(36.03113891519555, 16.566851514784105),
+            Offset(36.98867178324178, 18.40585973475647),
+            Offset(37.56526083167866, 20.0020381982159),
+            Offset(37.893194841987366, 21.35819435185408),
+            Offset(38.06150889201586, 22.492031936757623),
+            Offset(38.13048882144999, 23.426165819354733),
+            Offset(38.14094176630335, 24.183755168682307),
+            Offset(38.12034219741291, 24.78481604721129),
+            Offset(38.086964642108946, 25.247969219160655),
+            Offset(38.052600274850896, 25.58884408148203),
+            Offset(38.02444615197159, 25.820689465683056),
+            Offset(38.00640765629513, 25.954621335624125),
+            Offset(38.000014616095385, 25.999897684661754),
+            Offset(38.0, 25.999999999999993),
+          ],
+          <Offset>[
+            Offset(10.0, 22.0),
+            Offset(10.000434881903109, 21.996958187115208),
+            Offset(10.537203766746376, 19.66951300869151),
+            Offset(15.2007493434278, 12.928722391574958),
+            Offset(22.726894381864838, 9.915284806391266),
+            Offset(28.34794602732309, 10.54283219457061),
+            Offset(32.08037023016842, 12.393638944810997),
+            Offset(34.49466952610868, 14.52044771427649),
+            Offset(36.03113891519555, 16.566851514784105),
+            Offset(36.98867178324178, 18.40585973475647),
+            Offset(37.56526083167866, 20.0020381982159),
+            Offset(37.893194841987366, 21.35819435185408),
+            Offset(38.06150889201586, 22.492031936757623),
+            Offset(38.13048882144999, 23.426165819354733),
+            Offset(38.14094176630335, 24.183755168682307),
+            Offset(38.12034219741291, 24.78481604721129),
+            Offset(38.086964642108946, 25.247969219160655),
+            Offset(38.052600274850896, 25.58884408148203),
+            Offset(38.02444615197159, 25.820689465683056),
+            Offset(38.00640765629513, 25.954621335624125),
+            Offset(38.000014616095385, 25.999897684661754),
+            Offset(38.0, 25.999999999999993),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(10.0, 22.0),
+            Offset(10.000434881903109, 21.996958187115208),
+            Offset(10.537203766746376, 19.66951300869151),
+            Offset(15.2007493434278, 12.928722391574958),
+            Offset(22.726894381864838, 9.915284806391266),
+            Offset(28.34794602732309, 10.54283219457061),
+            Offset(32.08037023016842, 12.393638944810997),
+            Offset(34.49466952610868, 14.52044771427649),
+            Offset(36.03113891519555, 16.566851514784105),
+            Offset(36.98867178324178, 18.40585973475647),
+            Offset(37.56526083167866, 20.0020381982159),
+            Offset(37.893194841987366, 21.35819435185408),
+            Offset(38.06150889201586, 22.492031936757623),
+            Offset(38.13048882144999, 23.426165819354733),
+            Offset(38.14094176630335, 24.183755168682307),
+            Offset(38.12034219741291, 24.78481604721129),
+            Offset(38.086964642108946, 25.247969219160655),
+            Offset(38.052600274850896, 25.58884408148203),
+            Offset(38.02444615197159, 25.820689465683056),
+            Offset(38.00640765629513, 25.954621335624125),
+            Offset(38.000014616095385, 25.999897684661754),
+            Offset(38.0, 25.999999999999993),
+          ],
+          <Offset>[
+            Offset(38.0, 22.0),
+            Offset(38.00043422098076, 22.003041907302272),
+            Offset(38.136820741489856, 24.387684566345136),
+            Offset(35.547238360668324, 32.16463632024782),
+            Offset(29.165901647620203, 37.16485701278654),
+            Offset(23.59397879929007, 38.13630598086269),
+            Offset(19.492625674491244, 37.4046102774286),
+            Offset(16.57939189493825, 36.038877661605),
+            Offset(14.531388217272726, 34.50454144206201),
+            Offset(13.097234362356085, 33.007202753941485),
+            Offset(12.096778906088034, 31.63631636258276),
+            Offset(11.402238533172989, 30.426227977976552),
+            Offset(10.923182521372642, 29.384871830477124),
+            Offset(10.595404301988683, 28.507417683425455),
+            Offset(10.373244457117737, 27.783058732629918),
+            Offset(10.224722997264447, 27.20027240995278),
+            Offset(10.12708256221043, 26.746299649396278),
+            Offset(10.064627393328172, 26.40943775873551),
+            Offset(10.026738643716017, 26.17898303549632),
+            Offset(10.006554675981915, 26.045357661563475),
+            Offset(10.000014616843135, 26.00010231523142),
+            Offset(10.0, 26.0),
+          ],
+          <Offset>[
+            Offset(38.0, 22.0),
+            Offset(38.00043422098076, 22.003041907302272),
+            Offset(38.136820741489856, 24.387684566345136),
+            Offset(35.547238360668324, 32.16463632024782),
+            Offset(29.165901647620203, 37.16485701278654),
+            Offset(23.59397879929007, 38.13630598086269),
+            Offset(19.492625674491244, 37.4046102774286),
+            Offset(16.57939189493825, 36.038877661605),
+            Offset(14.531388217272726, 34.50454144206201),
+            Offset(13.097234362356085, 33.007202753941485),
+            Offset(12.096778906088034, 31.63631636258276),
+            Offset(11.402238533172989, 30.426227977976552),
+            Offset(10.923182521372642, 29.384871830477124),
+            Offset(10.595404301988683, 28.507417683425455),
+            Offset(10.373244457117737, 27.783058732629918),
+            Offset(10.224722997264447, 27.20027240995278),
+            Offset(10.12708256221043, 26.746299649396278),
+            Offset(10.064627393328172, 26.40943775873551),
+            Offset(10.026738643716017, 26.17898303549632),
+            Offset(10.006554675981915, 26.045357661563475),
+            Offset(10.000014616843135, 26.00010231523142),
+            Offset(10.0, 26.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.0, 22.0),
+            Offset(38.00043422098076, 22.003041907302272),
+            Offset(38.136820741489856, 24.387684566345136),
+            Offset(35.547238360668324, 32.16463632024782),
+            Offset(29.165901647620203, 37.16485701278654),
+            Offset(23.59397879929007, 38.13630598086269),
+            Offset(19.492625674491244, 37.4046102774286),
+            Offset(16.57939189493825, 36.038877661605),
+            Offset(14.531388217272726, 34.50454144206201),
+            Offset(13.097234362356085, 33.007202753941485),
+            Offset(12.096778906088034, 31.63631636258276),
+            Offset(11.402238533172989, 30.426227977976552),
+            Offset(10.923182521372642, 29.384871830477124),
+            Offset(10.595404301988683, 28.507417683425455),
+            Offset(10.373244457117737, 27.783058732629918),
+            Offset(10.224722997264447, 27.20027240995278),
+            Offset(10.12708256221043, 26.746299649396278),
+            Offset(10.064627393328172, 26.40943775873551),
+            Offset(10.026738643716017, 26.17898303549632),
+            Offset(10.006554675981915, 26.045357661563475),
+            Offset(10.000014616843135, 26.00010231523142),
+            Offset(10.0, 26.0),
+          ],
+          <Offset>[
+            Offset(38.0, 26.0),
+            Offset(37.99956511809689, 26.003041812884792),
+            Offset(37.46279623325363, 28.33048699130849),
+            Offset(32.7992506565722, 35.07127760842504),
+            Offset(25.273105618135162, 38.08471519360873),
+            Offset(19.652053972676917, 37.4571678054294),
+            Offset(15.919629769831586, 35.606361055189005),
+            Offset(13.50533047389132, 33.479552285723514),
+            Offset(11.968861084804454, 31.433148485215895),
+            Offset(11.011328216758224, 29.59414026524353),
+            Offset(10.43473916832134, 27.9979618017841),
+            Offset(10.106805158012635, 26.641805648145926),
+            Offset(9.938491107984142, 25.507968063242377),
+            Offset(9.86951117855001, 24.573834180645267),
+            Offset(9.859058233696649, 23.816244831317686),
+            Offset(9.87965780258709, 23.215183952788717),
+            Offset(9.913035357891056, 22.752030780839345),
+            Offset(9.947399725149102, 22.411155918517977),
+            Offset(9.97555384802841, 22.17931053431695),
+            Offset(9.993592343704867, 22.045378664375875),
+            Offset(9.999985383904612, 22.00010231533824),
+            Offset(10.0, 22.0),
+          ],
+          <Offset>[
+            Offset(38.0, 26.0),
+            Offset(37.99956511809689, 26.003041812884792),
+            Offset(37.46279623325363, 28.33048699130849),
+            Offset(32.7992506565722, 35.07127760842504),
+            Offset(25.273105618135162, 38.08471519360873),
+            Offset(19.652053972676917, 37.4571678054294),
+            Offset(15.919629769831586, 35.606361055189005),
+            Offset(13.50533047389132, 33.479552285723514),
+            Offset(11.968861084804454, 31.433148485215895),
+            Offset(11.011328216758224, 29.59414026524353),
+            Offset(10.43473916832134, 27.9979618017841),
+            Offset(10.106805158012635, 26.641805648145926),
+            Offset(9.938491107984142, 25.507968063242377),
+            Offset(9.86951117855001, 24.573834180645267),
+            Offset(9.859058233696649, 23.816244831317686),
+            Offset(9.87965780258709, 23.215183952788717),
+            Offset(9.913035357891056, 22.752030780839345),
+            Offset(9.947399725149102, 22.411155918517977),
+            Offset(9.97555384802841, 22.17931053431695),
+            Offset(9.993592343704867, 22.045378664375875),
+            Offset(9.999985383904612, 22.00010231533824),
+            Offset(10.0, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.0, 26.0),
+            Offset(37.99956511809689, 26.003041812884792),
+            Offset(37.46279623325363, 28.33048699130849),
+            Offset(32.7992506565722, 35.07127760842504),
+            Offset(25.273105618135162, 38.08471519360873),
+            Offset(19.652053972676917, 37.4571678054294),
+            Offset(15.919629769831586, 35.606361055189005),
+            Offset(13.50533047389132, 33.479552285723514),
+            Offset(11.968861084804454, 31.433148485215895),
+            Offset(11.011328216758224, 29.59414026524353),
+            Offset(10.43473916832134, 27.9979618017841),
+            Offset(10.106805158012635, 26.641805648145926),
+            Offset(9.938491107984142, 25.507968063242377),
+            Offset(9.86951117855001, 24.573834180645267),
+            Offset(9.859058233696649, 23.816244831317686),
+            Offset(9.87965780258709, 23.215183952788717),
+            Offset(9.913035357891056, 22.752030780839345),
+            Offset(9.947399725149102, 22.411155918517977),
+            Offset(9.97555384802841, 22.17931053431695),
+            Offset(9.993592343704867, 22.045378664375875),
+            Offset(9.999985383904612, 22.00010231533824),
+            Offset(10.0, 22.0),
+          ],
+          <Offset>[
+            Offset(10.0, 26.0),
+            Offset(9.999565779019244, 25.996958092697728),
+            Offset(9.863179258510144, 23.612315433654864),
+            Offset(12.452761639331678, 15.835363679752177),
+            Offset(18.834098352379797, 10.835142987213462),
+            Offset(24.406021200709937, 9.863694019137322),
+            Offset(28.507374325508763, 10.595389722571399),
+            Offset(31.42060810506175, 11.961122338395),
+            Offset(33.46861178272727, 13.495458557937988),
+            Offset(34.90276563764392, 14.992797246058515),
+            Offset(35.90322109391197, 16.36368363741724),
+            Offset(36.59776146682701, 17.573772022023455),
+            Offset(37.07681747862736, 18.615128169522876),
+            Offset(37.40459569801132, 19.492582316574545),
+            Offset(37.626755542882265, 20.216941267370075),
+            Offset(37.77527700273555, 20.799727590047226),
+            Offset(37.87291743778957, 21.253700350603722),
+            Offset(37.93537260667183, 21.590562241264497),
+            Offset(37.973261356283984, 21.821016964503688),
+            Offset(37.99344532401808, 21.954642338436525),
+            Offset(37.99998538315687, 21.999897684768573),
+            Offset(38.0, 21.999999999999993),
+          ],
+          <Offset>[
+            Offset(10.0, 26.0),
+            Offset(9.999565779019244, 25.996958092697728),
+            Offset(9.863179258510144, 23.612315433654864),
+            Offset(12.452761639331678, 15.835363679752177),
+            Offset(18.834098352379797, 10.835142987213462),
+            Offset(24.406021200709937, 9.863694019137322),
+            Offset(28.507374325508763, 10.595389722571399),
+            Offset(31.42060810506175, 11.961122338395),
+            Offset(33.46861178272727, 13.495458557937988),
+            Offset(34.90276563764392, 14.992797246058515),
+            Offset(35.90322109391197, 16.36368363741724),
+            Offset(36.59776146682701, 17.573772022023455),
+            Offset(37.07681747862736, 18.615128169522876),
+            Offset(37.40459569801132, 19.492582316574545),
+            Offset(37.626755542882265, 20.216941267370075),
+            Offset(37.77527700273555, 20.799727590047226),
+            Offset(37.87291743778957, 21.253700350603722),
+            Offset(37.93537260667183, 21.590562241264497),
+            Offset(37.973261356283984, 21.821016964503688),
+            Offset(37.99344532401808, 21.954642338436525),
+            Offset(37.99998538315687, 21.999897684768573),
+            Offset(38.0, 21.999999999999993),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(22.0, 10.0),
+            Offset(22.003041907302272, 9.999565779019244),
+            Offset(24.387684566345136, 9.863179258510144),
+            Offset(32.16463632024782, 12.452761639331678),
+            Offset(37.16485701278654, 18.834098352379797),
+            Offset(38.13630598086269, 24.406021200709937),
+            Offset(37.4046102774286, 28.50737432550876),
+            Offset(36.038877661605, 31.42060810506175),
+            Offset(34.50454144206201, 33.46861178272727),
+            Offset(33.007202753941485, 34.90276563764392),
+            Offset(31.63631636258276, 35.90322109391197),
+            Offset(30.426227977976545, 36.597761466827016),
+            Offset(29.384871830477124, 37.07681747862736),
+            Offset(28.507417683425455, 37.40459569801132),
+            Offset(27.78305873262992, 37.62675554288226),
+            Offset(27.20027240995278, 37.77527700273556),
+            Offset(26.746299649396278, 37.87291743778957),
+            Offset(26.409437758735507, 37.935372606671834),
+            Offset(26.178983035496316, 37.973261356283984),
+            Offset(26.045357661563475, 37.99344532401808),
+            Offset(26.000102315231423, 37.99998538315686),
+            Offset(26.000000000000004, 38.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(22.0, 10.0),
+            Offset(22.003041907302272, 9.999565779019244),
+            Offset(24.387684566345136, 9.863179258510144),
+            Offset(32.16463632024782, 12.452761639331678),
+            Offset(37.16485701278654, 18.834098352379797),
+            Offset(38.13630598086269, 24.406021200709937),
+            Offset(37.4046102774286, 28.50737432550876),
+            Offset(36.038877661605, 31.42060810506175),
+            Offset(34.50454144206201, 33.46861178272727),
+            Offset(33.007202753941485, 34.90276563764392),
+            Offset(31.63631636258276, 35.90322109391197),
+            Offset(30.426227977976545, 36.597761466827016),
+            Offset(29.384871830477124, 37.07681747862736),
+            Offset(28.507417683425455, 37.40459569801132),
+            Offset(27.78305873262992, 37.62675554288226),
+            Offset(27.20027240995278, 37.77527700273556),
+            Offset(26.746299649396278, 37.87291743778957),
+            Offset(26.409437758735507, 37.935372606671834),
+            Offset(26.178983035496316, 37.973261356283984),
+            Offset(26.045357661563475, 37.99344532401808),
+            Offset(26.000102315231423, 37.99998538315686),
+            Offset(26.000000000000004, 38.0),
+          ],
+          <Offset>[
+            Offset(26.0, 10.0),
+            Offset(26.003041812884792, 10.000434881903109),
+            Offset(28.33048699130849, 10.537203766746376),
+            Offset(35.07127760842504, 15.2007493434278),
+            Offset(38.08471519360873, 22.726894381864838),
+            Offset(37.4571678054294, 28.34794602732309),
+            Offset(35.606361055189005, 32.08037023016842),
+            Offset(33.479552285723514, 34.49466952610868),
+            Offset(31.433148485215895, 36.03113891519555),
+            Offset(29.59414026524353, 36.98867178324178),
+            Offset(27.9979618017841, 37.56526083167866),
+            Offset(26.641805648145922, 37.893194841987366),
+            Offset(25.507968063242377, 38.06150889201586),
+            Offset(24.573834180645267, 38.13048882144999),
+            Offset(23.81624483131769, 38.14094176630335),
+            Offset(23.215183952788713, 38.12034219741291),
+            Offset(22.752030780839345, 38.086964642108946),
+            Offset(22.411155918517974, 38.0526002748509),
+            Offset(22.179310534316947, 38.024446151971595),
+            Offset(22.045378664375875, 38.00640765629513),
+            Offset(22.000102315338243, 38.000014616095385),
+            Offset(22.000000000000004, 38.0),
+          ],
+          <Offset>[
+            Offset(26.0, 10.0),
+            Offset(26.003041812884792, 10.000434881903109),
+            Offset(28.33048699130849, 10.537203766746376),
+            Offset(35.07127760842504, 15.2007493434278),
+            Offset(38.08471519360873, 22.726894381864838),
+            Offset(37.4571678054294, 28.34794602732309),
+            Offset(35.606361055189005, 32.08037023016842),
+            Offset(33.479552285723514, 34.49466952610868),
+            Offset(31.433148485215895, 36.03113891519555),
+            Offset(29.59414026524353, 36.98867178324178),
+            Offset(27.9979618017841, 37.56526083167866),
+            Offset(26.641805648145922, 37.893194841987366),
+            Offset(25.507968063242377, 38.06150889201586),
+            Offset(24.573834180645267, 38.13048882144999),
+            Offset(23.81624483131769, 38.14094176630335),
+            Offset(23.215183952788713, 38.12034219741291),
+            Offset(22.752030780839345, 38.086964642108946),
+            Offset(22.411155918517974, 38.0526002748509),
+            Offset(22.179310534316947, 38.024446151971595),
+            Offset(22.045378664375875, 38.00640765629513),
+            Offset(22.000102315338243, 38.000014616095385),
+            Offset(22.000000000000004, 38.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(26.0, 10.0),
+            Offset(26.003041812884792, 10.000434881903109),
+            Offset(28.33048699130849, 10.537203766746376),
+            Offset(35.07127760842504, 15.2007493434278),
+            Offset(38.08471519360873, 22.726894381864838),
+            Offset(37.4571678054294, 28.34794602732309),
+            Offset(35.606361055189005, 32.08037023016842),
+            Offset(33.479552285723514, 34.49466952610868),
+            Offset(31.433148485215895, 36.03113891519555),
+            Offset(29.59414026524353, 36.98867178324178),
+            Offset(27.9979618017841, 37.56526083167866),
+            Offset(26.641805648145922, 37.893194841987366),
+            Offset(25.507968063242377, 38.06150889201586),
+            Offset(24.573834180645267, 38.13048882144999),
+            Offset(23.81624483131769, 38.14094176630335),
+            Offset(23.215183952788713, 38.12034219741291),
+            Offset(22.752030780839345, 38.086964642108946),
+            Offset(22.411155918517974, 38.0526002748509),
+            Offset(22.179310534316947, 38.024446151971595),
+            Offset(22.045378664375875, 38.00640765629513),
+            Offset(22.000102315338243, 38.000014616095385),
+            Offset(22.000000000000004, 38.0),
+          ],
+          <Offset>[
+            Offset(26.0, 38.0),
+            Offset(25.996958092697728, 38.00043422098076),
+            Offset(23.612315433654864, 38.136820741489856),
+            Offset(15.835363679752177, 35.547238360668324),
+            Offset(10.835142987213462, 29.165901647620203),
+            Offset(9.863694019137322, 23.59397879929007),
+            Offset(10.595389722571403, 19.49262567449124),
+            Offset(11.961122338395, 16.57939189493825),
+            Offset(13.495458557937988, 14.531388217272726),
+            Offset(14.992797246058515, 13.097234362356085),
+            Offset(16.36368363741724, 12.096778906088034),
+            Offset(17.573772022023455, 11.402238533172993),
+            Offset(18.615128169522876, 10.923182521372642),
+            Offset(19.492582316574545, 10.595404301988683),
+            Offset(20.21694126737008, 10.373244457117734),
+            Offset(20.79972759004722, 10.22472299726445),
+            Offset(21.253700350603722, 10.12708256221043),
+            Offset(21.590562241264493, 10.064627393328175),
+            Offset(21.821016964503684, 10.02673864371602),
+            Offset(21.954642338436525, 10.006554675981915),
+            Offset(21.999897684768577, 10.000014616843131),
+            Offset(21.999999999999996, 9.999999999999996),
+          ],
+          <Offset>[
+            Offset(26.0, 38.0),
+            Offset(25.996958092697728, 38.00043422098076),
+            Offset(23.612315433654864, 38.136820741489856),
+            Offset(15.835363679752177, 35.547238360668324),
+            Offset(10.835142987213462, 29.165901647620203),
+            Offset(9.863694019137322, 23.59397879929007),
+            Offset(10.595389722571403, 19.49262567449124),
+            Offset(11.961122338395, 16.57939189493825),
+            Offset(13.495458557937988, 14.531388217272726),
+            Offset(14.992797246058515, 13.097234362356085),
+            Offset(16.36368363741724, 12.096778906088034),
+            Offset(17.573772022023455, 11.402238533172993),
+            Offset(18.615128169522876, 10.923182521372642),
+            Offset(19.492582316574545, 10.595404301988683),
+            Offset(20.21694126737008, 10.373244457117734),
+            Offset(20.79972759004722, 10.22472299726445),
+            Offset(21.253700350603722, 10.12708256221043),
+            Offset(21.590562241264493, 10.064627393328175),
+            Offset(21.821016964503684, 10.02673864371602),
+            Offset(21.954642338436525, 10.006554675981915),
+            Offset(21.999897684768577, 10.000014616843131),
+            Offset(21.999999999999996, 9.999999999999996),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(26.0, 38.0),
+            Offset(25.996958092697728, 38.00043422098076),
+            Offset(23.612315433654864, 38.136820741489856),
+            Offset(15.835363679752177, 35.547238360668324),
+            Offset(10.835142987213462, 29.165901647620203),
+            Offset(9.863694019137322, 23.59397879929007),
+            Offset(10.595389722571403, 19.49262567449124),
+            Offset(11.961122338395, 16.57939189493825),
+            Offset(13.495458557937988, 14.531388217272726),
+            Offset(14.992797246058515, 13.097234362356085),
+            Offset(16.36368363741724, 12.096778906088034),
+            Offset(17.573772022023455, 11.402238533172993),
+            Offset(18.615128169522876, 10.923182521372642),
+            Offset(19.492582316574545, 10.595404301988683),
+            Offset(20.21694126737008, 10.373244457117734),
+            Offset(20.79972759004722, 10.22472299726445),
+            Offset(21.253700350603722, 10.12708256221043),
+            Offset(21.590562241264493, 10.064627393328175),
+            Offset(21.821016964503684, 10.02673864371602),
+            Offset(21.954642338436525, 10.006554675981915),
+            Offset(21.999897684768577, 10.000014616843131),
+            Offset(21.999999999999996, 9.999999999999996),
+          ],
+          <Offset>[
+            Offset(22.0, 38.0),
+            Offset(21.996958187115208, 37.99956511809689),
+            Offset(19.66951300869151, 37.46279623325363),
+            Offset(12.928722391574958, 32.7992506565722),
+            Offset(9.915284806391266, 25.273105618135162),
+            Offset(10.54283219457061, 19.652053972676917),
+            Offset(12.393638944811, 15.919629769831582),
+            Offset(14.52044771427649, 13.50533047389132),
+            Offset(16.566851514784105, 11.968861084804454),
+            Offset(18.40585973475647, 11.011328216758224),
+            Offset(20.0020381982159, 10.43473916832134),
+            Offset(21.358194351854078, 10.106805158012639),
+            Offset(22.492031936757623, 9.938491107984142),
+            Offset(23.426165819354733, 9.86951117855001),
+            Offset(24.18375516868231, 9.859058233696645),
+            Offset(24.784816047211287, 9.879657802587094),
+            Offset(25.247969219160655, 9.913035357891056),
+            Offset(25.588844081482026, 9.947399725149106),
+            Offset(25.820689465683053, 9.975553848028413),
+            Offset(25.954621335624125, 9.993592343704867),
+            Offset(25.999897684661757, 9.999985383904608),
+            Offset(25.999999999999996, 9.999999999999996),
+          ],
+          <Offset>[
+            Offset(22.0, 38.0),
+            Offset(21.996958187115208, 37.99956511809689),
+            Offset(19.66951300869151, 37.46279623325363),
+            Offset(12.928722391574958, 32.7992506565722),
+            Offset(9.915284806391266, 25.273105618135162),
+            Offset(10.54283219457061, 19.652053972676917),
+            Offset(12.393638944811, 15.919629769831582),
+            Offset(14.52044771427649, 13.50533047389132),
+            Offset(16.566851514784105, 11.968861084804454),
+            Offset(18.40585973475647, 11.011328216758224),
+            Offset(20.0020381982159, 10.43473916832134),
+            Offset(21.358194351854078, 10.106805158012639),
+            Offset(22.492031936757623, 9.938491107984142),
+            Offset(23.426165819354733, 9.86951117855001),
+            Offset(24.18375516868231, 9.859058233696645),
+            Offset(24.784816047211287, 9.879657802587094),
+            Offset(25.247969219160655, 9.913035357891056),
+            Offset(25.588844081482026, 9.947399725149106),
+            Offset(25.820689465683053, 9.975553848028413),
+            Offset(25.954621335624125, 9.993592343704867),
+            Offset(25.999897684661757, 9.999985383904608),
+            Offset(25.999999999999996, 9.999999999999996),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(22.0, 38.0),
+            Offset(21.996958187115208, 37.99956511809689),
+            Offset(19.66951300869151, 37.46279623325363),
+            Offset(12.928722391574958, 32.7992506565722),
+            Offset(9.915284806391266, 25.273105618135162),
+            Offset(10.54283219457061, 19.652053972676917),
+            Offset(12.393638944811, 15.919629769831582),
+            Offset(14.52044771427649, 13.50533047389132),
+            Offset(16.566851514784105, 11.968861084804454),
+            Offset(18.40585973475647, 11.011328216758224),
+            Offset(20.0020381982159, 10.43473916832134),
+            Offset(21.358194351854078, 10.106805158012639),
+            Offset(22.492031936757623, 9.938491107984142),
+            Offset(23.426165819354733, 9.86951117855001),
+            Offset(24.18375516868231, 9.859058233696645),
+            Offset(24.784816047211287, 9.879657802587094),
+            Offset(25.247969219160655, 9.913035357891056),
+            Offset(25.588844081482026, 9.947399725149106),
+            Offset(25.820689465683053, 9.975553848028413),
+            Offset(25.954621335624125, 9.993592343704867),
+            Offset(25.999897684661757, 9.999985383904608),
+            Offset(25.999999999999996, 9.999999999999996),
+          ],
+          <Offset>[
+            Offset(22.0, 10.0),
+            Offset(22.003041907302272, 9.999565779019244),
+            Offset(24.387684566345136, 9.863179258510144),
+            Offset(32.16463632024782, 12.452761639331678),
+            Offset(37.16485701278654, 18.834098352379797),
+            Offset(38.13630598086269, 24.406021200709937),
+            Offset(37.4046102774286, 28.50737432550876),
+            Offset(36.038877661605, 31.42060810506175),
+            Offset(34.50454144206201, 33.46861178272727),
+            Offset(33.007202753941485, 34.90276563764392),
+            Offset(31.63631636258276, 35.90322109391197),
+            Offset(30.426227977976545, 36.597761466827016),
+            Offset(29.384871830477124, 37.07681747862736),
+            Offset(28.507417683425455, 37.40459569801132),
+            Offset(27.78305873262992, 37.62675554288226),
+            Offset(27.20027240995278, 37.77527700273556),
+            Offset(26.746299649396278, 37.87291743778957),
+            Offset(26.409437758735507, 37.935372606671834),
+            Offset(26.178983035496316, 37.973261356283984),
+            Offset(26.045357661563475, 37.99344532401808),
+            Offset(26.000102315231423, 37.99998538315686),
+            Offset(26.000000000000004, 38.0),
+          ],
+          <Offset>[
+            Offset(22.0, 10.0),
+            Offset(22.003041907302272, 9.999565779019244),
+            Offset(24.387684566345136, 9.863179258510144),
+            Offset(32.16463632024782, 12.452761639331678),
+            Offset(37.16485701278654, 18.834098352379797),
+            Offset(38.13630598086269, 24.406021200709937),
+            Offset(37.4046102774286, 28.50737432550876),
+            Offset(36.038877661605, 31.42060810506175),
+            Offset(34.50454144206201, 33.46861178272727),
+            Offset(33.007202753941485, 34.90276563764392),
+            Offset(31.63631636258276, 35.90322109391197),
+            Offset(30.426227977976545, 36.597761466827016),
+            Offset(29.384871830477124, 37.07681747862736),
+            Offset(28.507417683425455, 37.40459569801132),
+            Offset(27.78305873262992, 37.62675554288226),
+            Offset(27.20027240995278, 37.77527700273556),
+            Offset(26.746299649396278, 37.87291743778957),
+            Offset(26.409437758735507, 37.935372606671834),
+            Offset(26.178983035496316, 37.973261356283984),
+            Offset(26.045357661563475, 37.99344532401808),
+            Offset(26.000102315231423, 37.99998538315686),
+            Offset(26.000000000000004, 38.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+  ],
+);
diff --git a/lib/src/material/animated_icons/data/home_menu.g.dart b/lib/src/material/animated_icons/data/home_menu.g.dart
new file mode 100644
index 0000000..447a370
--- /dev/null
+++ b/lib/src/material/animated_icons/data/home_menu.g.dart
@@ -0,0 +1,1702 @@
+// Copyright 2014 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.
+
+// AUTOGENERATED FILE DO NOT EDIT!
+// This file was generated by vitool.
+part of material_animated_icons;
+const _AnimatedIconData _$home_menu = _AnimatedIconData(
+  Size(48.0, 48.0),
+  <_PathFrames>[
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.634146341463,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(41.961602748986124, 36.01074618636323),
+            Offset(41.61012274691143, 36.52041429443682),
+            Offset(40.73074694162982, 37.67330625857439),
+            Offset(38.95361640675702, 39.597004636313734),
+            Offset(35.56126879040947, 42.254157368159426),
+            Offset(29.45217770721976, 44.90815523939214),
+            Offset(20.31425476555182, 45.290666483987),
+            Offset(11.333081294378362, 41.505038353879655),
+            Offset(5.639760014140567, 35.39204919789558),
+            Offset(3.1052482171005202, 29.50332128779013),
+            Offset(2.404116971035613, 24.703586146049652),
+            Offset(2.598975684223376, 21.02118219738726),
+            Offset(3.169562607312212, 18.258042555457294),
+            Offset(3.8457725409130035, 16.209986341924132),
+            Offset(4.491857018848872, 14.709706425920167),
+            Offset(5.042760982134354, 13.631765527094924),
+            Offset(5.471212748718976, 12.884189626374933),
+            Offset(5.770140143051716, 12.400456609777674),
+            Offset(5.943251827328922, 12.132774431470317),
+            Offset(5.999943741061273, 12.046959719775653),
+            Offset(5.9999999999999964, 12.046875000000004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(41.961602748986124, 36.01074618636323),
+            Offset(41.61012274691143, 36.52041429443682),
+            Offset(40.73074694162982, 37.67330625857439),
+            Offset(38.95361640675702, 39.597004636313734),
+            Offset(35.56126879040947, 42.254157368159426),
+            Offset(29.45217770721976, 44.90815523939214),
+            Offset(20.31425476555182, 45.290666483987),
+            Offset(11.333081294378362, 41.505038353879655),
+            Offset(5.639760014140567, 35.39204919789558),
+            Offset(3.1052482171005202, 29.50332128779013),
+            Offset(2.404116971035613, 24.703586146049652),
+            Offset(2.598975684223376, 21.02118219738726),
+            Offset(3.169562607312212, 18.258042555457294),
+            Offset(3.8457725409130035, 16.209986341924132),
+            Offset(4.491857018848872, 14.709706425920167),
+            Offset(5.042760982134354, 13.631765527094924),
+            Offset(5.471212748718976, 12.884189626374933),
+            Offset(5.770140143051716, 12.400456609777674),
+            Offset(5.943251827328922, 12.132774431470317),
+            Offset(5.999943741061273, 12.046959719775653),
+            Offset(5.9999999999999964, 12.046875000000004),
+          ],
+          <Offset>[
+            Offset(41.97427088111201, 32.057641484479724),
+            Offset(41.73604180244445, 32.56929525585058),
+            Offset(41.121377806968006, 33.73952883824795),
+            Offset(39.81729775857236, 35.739382079848234),
+            Offset(37.1732602008072, 38.64463095068733),
+            Offset(32.08695737071304, 41.96110020250069),
+            Offset(23.93217304852415, 43.69759882586336),
+            Offset(15.2830121061518, 41.66392148019527),
+            Offset(9.234232376942689, 37.03733509970011),
+            Offset(6.058741440847815, 32.130881951771805),
+            Offset(4.6970485662914285, 27.923782097401467),
+            Offset(4.310954600883996, 24.584372545170346),
+            Offset(4.402665831815483, 22.013925101989418),
+            Offset(4.698295162721713, 20.070090284168355),
+            Offset(5.0503340012927325, 18.62318323378621),
+            Offset(5.38119368862878, 17.57037702974403),
+            Offset(5.652345022660288, 16.833162703639687),
+            Offset(5.847293032367528, 16.35282864404965),
+            Offset(5.962087095621268, 16.085854559458284),
+            Offset(5.999962347024592, 16.000084719731866),
+            Offset(5.9999999999999964, 16.000000000000004),
+          ],
+          <Offset>[
+            Offset(41.97427088111201, 32.057641484479724),
+            Offset(41.73604180244445, 32.56929525585058),
+            Offset(41.121377806968006, 33.73952883824795),
+            Offset(39.81729775857236, 35.739382079848234),
+            Offset(37.1732602008072, 38.64463095068733),
+            Offset(32.08695737071304, 41.96110020250069),
+            Offset(23.93217304852415, 43.69759882586336),
+            Offset(15.2830121061518, 41.66392148019527),
+            Offset(9.234232376942689, 37.03733509970011),
+            Offset(6.058741440847815, 32.130881951771805),
+            Offset(4.6970485662914285, 27.923782097401467),
+            Offset(4.310954600883996, 24.584372545170346),
+            Offset(4.402665831815483, 22.013925101989418),
+            Offset(4.698295162721713, 20.070090284168355),
+            Offset(5.0503340012927325, 18.62318323378621),
+            Offset(5.38119368862878, 17.57037702974403),
+            Offset(5.652345022660288, 16.833162703639687),
+            Offset(5.847293032367528, 16.35282864404965),
+            Offset(5.962087095621268, 16.085854559458284),
+            Offset(5.999962347024592, 16.000084719731866),
+            Offset(5.9999999999999964, 16.000000000000004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(41.97427088111201, 32.057641484479724),
+            Offset(41.73604180244445, 32.56929525585058),
+            Offset(41.121377806968006, 33.73952883824795),
+            Offset(39.81729775857236, 35.739382079848234),
+            Offset(37.1732602008072, 38.64463095068733),
+            Offset(32.08695737071304, 41.96110020250069),
+            Offset(23.93217304852415, 43.69759882586336),
+            Offset(15.2830121061518, 41.66392148019527),
+            Offset(9.234232376942689, 37.03733509970011),
+            Offset(6.058741440847815, 32.130881951771805),
+            Offset(4.6970485662914285, 27.923782097401467),
+            Offset(4.310954600883996, 24.584372545170346),
+            Offset(4.402665831815483, 22.013925101989418),
+            Offset(4.698295162721713, 20.070090284168355),
+            Offset(5.0503340012927325, 18.62318323378621),
+            Offset(5.38119368862878, 17.57037702974403),
+            Offset(5.652345022660288, 16.833162703639687),
+            Offset(5.847293032367528, 16.35282864404965),
+            Offset(5.962087095621268, 16.085854559458284),
+            Offset(5.999962347024592, 16.000084719731866),
+            Offset(5.9999999999999964, 16.000000000000004),
+          ],
+          <Offset>[
+            Offset(5.9744557303626635, 31.942276360297758),
+            Offset(5.75430953010175, 31.42258575407952),
+            Offset(5.297570785497186, 30.182163171294633),
+            Offset(4.687011710760039, 27.874078385846133),
+            Offset(4.3023160669901515, 23.964677553231365),
+            Offset(5.248954188903129, 17.96690121163705),
+            Offset(9.424552952409986, 10.750232327965165),
+            Offset(16.729916149753336, 5.693010055981826),
+            Offset(24.217389364126984, 4.303484017106875),
+            Offset(29.987199029044564, 5.234248009029647),
+            Offset(34.02246940389847, 7.042697530328738),
+            Offset(36.75992915144614, 8.993860987913147),
+            Offset(38.606434160708815, 10.7844000851691),
+            Offset(39.851178494463575, 12.30640601283526),
+            Offset(40.68926904209655, 13.537290081412095),
+            Offset(41.24902334121195, 14.488365346885672),
+            Offset(41.61453462747447, 15.183641916442898),
+            Offset(41.840435984789025, 15.650218932651905),
+            Offset(41.96167845880022, 15.914327056906625),
+            Offset(41.99996234662585, 15.999915280445354),
+            Offset(42.0, 15.999999999999996),
+          ],
+          <Offset>[
+            Offset(5.9744557303626635, 31.942276360297758),
+            Offset(5.75430953010175, 31.42258575407952),
+            Offset(5.297570785497186, 30.182163171294633),
+            Offset(4.687011710760039, 27.874078385846133),
+            Offset(4.3023160669901515, 23.964677553231365),
+            Offset(5.248954188903129, 17.96690121163705),
+            Offset(9.424552952409986, 10.750232327965165),
+            Offset(16.729916149753336, 5.693010055981826),
+            Offset(24.217389364126984, 4.303484017106875),
+            Offset(29.987199029044564, 5.234248009029647),
+            Offset(34.02246940389847, 7.042697530328738),
+            Offset(36.75992915144614, 8.993860987913147),
+            Offset(38.606434160708815, 10.7844000851691),
+            Offset(39.851178494463575, 12.30640601283526),
+            Offset(40.68926904209655, 13.537290081412095),
+            Offset(41.24902334121195, 14.488365346885672),
+            Offset(41.61453462747447, 15.183641916442898),
+            Offset(41.840435984789025, 15.650218932651905),
+            Offset(41.96167845880022, 15.914327056906625),
+            Offset(41.99996234662585, 15.999915280445354),
+            Offset(42.0, 15.999999999999996),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(5.9744557303626635, 31.942276360297758),
+            Offset(5.75430953010175, 31.42258575407952),
+            Offset(5.297570785497186, 30.182163171294633),
+            Offset(4.687011710760039, 27.874078385846133),
+            Offset(4.3023160669901515, 23.964677553231365),
+            Offset(5.248954188903129, 17.96690121163705),
+            Offset(9.424552952409986, 10.750232327965165),
+            Offset(16.729916149753336, 5.693010055981826),
+            Offset(24.217389364126984, 4.303484017106875),
+            Offset(29.987199029044564, 5.234248009029647),
+            Offset(34.02246940389847, 7.042697530328738),
+            Offset(36.75992915144614, 8.993860987913147),
+            Offset(38.606434160708815, 10.7844000851691),
+            Offset(39.851178494463575, 12.30640601283526),
+            Offset(40.68926904209655, 13.537290081412095),
+            Offset(41.24902334121195, 14.488365346885672),
+            Offset(41.61453462747447, 15.183641916442898),
+            Offset(41.840435984789025, 15.650218932651905),
+            Offset(41.96167845880022, 15.914327056906625),
+            Offset(41.99996234662585, 15.999915280445354),
+            Offset(42.0, 15.999999999999996),
+          ],
+          <Offset>[
+            Offset(5.96181263407102, 35.88756860229611),
+            Offset(5.628639326457137, 35.36589625701638),
+            Offset(4.907711917916583, 34.10816632794454),
+            Offset(3.825037239086633, 31.72407718231862),
+            Offset(2.6935104103679173, 27.567070519285533),
+            Offset(2.6193815998436385, 20.90813202908801),
+            Offset(5.813784705570013, 12.34015163103323),
+            Offset(12.787791525354942, 5.534440927939556),
+            Offset(20.630020701646604, 2.66144966846797),
+            Offset(27.039542748427206, 2.6118801526843),
+            Offset(31.73406929400877, 3.8288656025961956),
+            Offset(35.05133359232832, 5.4377125182877375),
+            Offset(37.37576789909982, 7.035940231416685),
+            Offset(39.00034069997069, 8.453930734508514),
+            Offset(40.13189576910416, 9.631547417435115),
+            Offset(40.91125947405842, 10.557537661435477),
+            Offset(41.43376032245399, 11.242473133797246),
+            Offset(41.76343557153906, 11.705657910305364),
+            Offset(41.94288041435826, 11.969059340238793),
+            Offset(41.9999437774332, 12.054602780489052),
+            Offset(42.0, 12.054687499999996),
+          ],
+          <Offset>[
+            Offset(5.96181263407102, 35.88756860229611),
+            Offset(5.628639326457137, 35.36589625701638),
+            Offset(4.907711917916583, 34.10816632794454),
+            Offset(3.825037239086633, 31.72407718231862),
+            Offset(2.6935104103679173, 27.567070519285533),
+            Offset(2.6193815998436385, 20.90813202908801),
+            Offset(5.813784705570013, 12.34015163103323),
+            Offset(12.787791525354942, 5.534440927939556),
+            Offset(20.630020701646604, 2.66144966846797),
+            Offset(27.039542748427206, 2.6118801526843),
+            Offset(31.73406929400877, 3.8288656025961956),
+            Offset(35.05133359232832, 5.4377125182877375),
+            Offset(37.37576789909982, 7.035940231416685),
+            Offset(39.00034069997069, 8.453930734508514),
+            Offset(40.13189576910416, 9.631547417435115),
+            Offset(40.91125947405842, 10.557537661435477),
+            Offset(41.43376032245399, 11.242473133797246),
+            Offset(41.76343557153906, 11.705657910305364),
+            Offset(41.94288041435826, 11.969059340238793),
+            Offset(41.9999437774332, 12.054602780489052),
+            Offset(42.0, 12.054687499999996),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(5.96181263407102, 35.88756860229611),
+            Offset(5.628639326457137, 35.36589625701638),
+            Offset(4.907711917916583, 34.10816632794454),
+            Offset(3.825037239086633, 31.72407718231862),
+            Offset(2.6935104103679173, 27.567070519285533),
+            Offset(2.6193815998436385, 20.90813202908801),
+            Offset(5.813784705570013, 12.34015163103323),
+            Offset(12.787791525354942, 5.534440927939556),
+            Offset(20.630020701646604, 2.66144966846797),
+            Offset(27.039542748427206, 2.6118801526843),
+            Offset(31.73406929400877, 3.8288656025961956),
+            Offset(35.05133359232832, 5.4377125182877375),
+            Offset(37.37576789909982, 7.035940231416685),
+            Offset(39.00034069997069, 8.453930734508514),
+            Offset(40.13189576910416, 9.631547417435115),
+            Offset(40.91125947405842, 10.557537661435477),
+            Offset(41.43376032245399, 11.242473133797246),
+            Offset(41.76343557153906, 11.705657910305364),
+            Offset(41.94288041435826, 11.969059340238793),
+            Offset(41.9999437774332, 12.054602780489052),
+            Offset(42.0, 12.054687499999996),
+          ],
+          <Offset>[
+            Offset(41.961602748986124, 36.01074618636323),
+            Offset(41.61012274691143, 36.52041429443682),
+            Offset(40.73074694162982, 37.67330625857439),
+            Offset(38.95361640675702, 39.597004636313734),
+            Offset(35.56126879040947, 42.254157368159426),
+            Offset(29.45217770721976, 44.90815523939214),
+            Offset(20.31425476555182, 45.290666483987),
+            Offset(11.333081294378362, 41.505038353879655),
+            Offset(5.639760014140567, 35.39204919789558),
+            Offset(3.1052482171005202, 29.50332128779013),
+            Offset(2.404116971035613, 24.703586146049652),
+            Offset(2.598975684223376, 21.02118219738726),
+            Offset(3.169562607312212, 18.258042555457294),
+            Offset(3.8457725409130035, 16.209986341924132),
+            Offset(4.491857018848872, 14.709706425920167),
+            Offset(5.042760982134354, 13.631765527094924),
+            Offset(5.471212748718976, 12.884189626374933),
+            Offset(5.770140143051716, 12.400456609777674),
+            Offset(5.943251827328922, 12.132774431470317),
+            Offset(5.999943741061273, 12.046959719775653),
+            Offset(5.9999999999999964, 12.046875000000004),
+          ],
+          <Offset>[
+            Offset(41.961602748986124, 36.01074618636323),
+            Offset(41.61012274691143, 36.52041429443682),
+            Offset(40.73074694162982, 37.67330625857439),
+            Offset(38.95361640675702, 39.597004636313734),
+            Offset(35.56126879040947, 42.254157368159426),
+            Offset(29.45217770721976, 44.90815523939214),
+            Offset(20.31425476555182, 45.290666483987),
+            Offset(11.333081294378362, 41.505038353879655),
+            Offset(5.639760014140567, 35.39204919789558),
+            Offset(3.1052482171005202, 29.50332128779013),
+            Offset(2.404116971035613, 24.703586146049652),
+            Offset(2.598975684223376, 21.02118219738726),
+            Offset(3.169562607312212, 18.258042555457294),
+            Offset(3.8457725409130035, 16.209986341924132),
+            Offset(4.491857018848872, 14.709706425920167),
+            Offset(5.042760982134354, 13.631765527094924),
+            Offset(5.471212748718976, 12.884189626374933),
+            Offset(5.770140143051716, 12.400456609777674),
+            Offset(5.943251827328922, 12.132774431470317),
+            Offset(5.999943741061273, 12.046959719775653),
+            Offset(5.9999999999999964, 12.046875000000004),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(38.005915505011714, 22.044873132716152),
+            Offset(38.49527667048537, 22.460937675484836),
+            Offset(39.29605291379161, 23.509087727219047),
+            Offset(40.090934628235715, 25.55307837508503),
+            Offset(40.245484311889584, 29.064745081245363),
+            Offset(38.450753318138695, 34.23676198345152),
+            Offset(33.0728907045072, 39.641976680762035),
+            Offset(25.27493194621109, 42.06583927008458),
+            Offset(18.326968788774145, 41.199323151695744),
+            Offset(13.530028647165079, 38.7776757262709),
+            Offset(10.497349834922744, 36.06973233007009),
+            Offset(8.641652255677663, 33.59797658699316),
+            Offset(7.521978336487795, 31.514971860015343),
+            Offset(6.854874126980906, 29.834780098541096),
+            Offset(6.463082099174432, 28.52288741178727),
+            Offset(6.237308044978324, 27.533663044350465),
+            Offset(6.1105452413260615, 26.82265981608807),
+            Offset(6.04246239664468, 26.35092390861118),
+            Offset(6.009733624107838, 26.085741049230215),
+            Offset(6.000009413493068, 26.000084719621103),
+            Offset(6.0, 26.000000000000004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.005915505011714, 22.044873132716152),
+            Offset(38.49527667048537, 22.460937675484836),
+            Offset(39.29605291379161, 23.509087727219047),
+            Offset(40.090934628235715, 25.55307837508503),
+            Offset(40.245484311889584, 29.064745081245363),
+            Offset(38.450753318138695, 34.23676198345152),
+            Offset(33.0728907045072, 39.641976680762035),
+            Offset(25.27493194621109, 42.06583927008458),
+            Offset(18.326968788774145, 41.199323151695744),
+            Offset(13.530028647165079, 38.7776757262709),
+            Offset(10.497349834922744, 36.06973233007009),
+            Offset(8.641652255677663, 33.59797658699316),
+            Offset(7.521978336487795, 31.514971860015343),
+            Offset(6.854874126980906, 29.834780098541096),
+            Offset(6.463082099174432, 28.52288741178727),
+            Offset(6.237308044978324, 27.533663044350465),
+            Offset(6.1105452413260615, 26.82265981608807),
+            Offset(6.04246239664468, 26.35092390861118),
+            Offset(6.009733624107838, 26.085741049230215),
+            Offset(6.000009413493068, 26.000084719621103),
+            Offset(6.0, 26.000000000000004),
+          ],
+          <Offset>[
+            Offset(10.006902842119626, 21.955147406089473),
+            Offset(9.632135496378087, 21.541092072032644),
+            Offset(9.099209938092097, 20.510489270395304),
+            Offset(8.782988004431196, 18.54355650849138),
+            Offset(9.385621621161075, 15.282927792774965),
+            Offset(12.215268791957268, 10.781237663014041),
+            Offset(18.587927795299567, 6.746065530872357),
+            Offset(26.721835989812625, 6.094927845871137),
+            Offset(33.31012577595844, 8.46547206910251),
+            Offset(37.45848623536183, 11.881041783528742),
+            Offset(39.822770672529785, 15.188647762997359),
+            Offset(41.090626806239804, 18.007465029735965),
+            Offset(41.72574666538112, 20.285446843195025),
+            Offset(42.007757458722764, 22.071095827208),
+            Offset(42.102017139978244, 23.436994259413154),
+            Offset(42.10513769756149, 24.451651361492107),
+            Offset(42.07273484614025, 25.17313902889128),
+            Offset(42.03560534906618, 25.64831419721343),
+            Offset(42.00932498728679, 25.914213546678557),
+            Offset(42.000009413094325, 25.99991528033459),
+            Offset(42.0, 25.999999999999996),
+          ],
+          <Offset>[
+            Offset(10.006902842119626, 21.955147406089473),
+            Offset(9.632135496378087, 21.541092072032644),
+            Offset(9.099209938092097, 20.510489270395304),
+            Offset(8.782988004431196, 18.54355650849138),
+            Offset(9.385621621161075, 15.282927792774965),
+            Offset(12.215268791957268, 10.781237663014041),
+            Offset(18.587927795299567, 6.746065530872357),
+            Offset(26.721835989812625, 6.094927845871137),
+            Offset(33.31012577595844, 8.46547206910251),
+            Offset(37.45848623536183, 11.881041783528742),
+            Offset(39.822770672529785, 15.188647762997359),
+            Offset(41.090626806239804, 18.007465029735965),
+            Offset(41.72574666538112, 20.285446843195025),
+            Offset(42.007757458722764, 22.071095827208),
+            Offset(42.102017139978244, 23.436994259413154),
+            Offset(42.10513769756149, 24.451651361492107),
+            Offset(42.07273484614025, 25.17313902889128),
+            Offset(42.03560534906618, 25.64831419721343),
+            Offset(42.00932498728679, 25.914213546678557),
+            Offset(42.000009413094325, 25.99991528033459),
+            Offset(42.0, 25.999999999999996),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(10.006902842119626, 21.955147406089473),
+            Offset(9.632135496378087, 21.541092072032644),
+            Offset(9.099209938092097, 20.510489270395304),
+            Offset(8.782988004431196, 18.54355650849138),
+            Offset(9.385621621161075, 15.282927792774965),
+            Offset(12.215268791957268, 10.781237663014041),
+            Offset(18.587927795299567, 6.746065530872357),
+            Offset(26.721835989812625, 6.094927845871137),
+            Offset(33.31012577595844, 8.46547206910251),
+            Offset(37.45848623536183, 11.881041783528742),
+            Offset(39.822770672529785, 15.188647762997359),
+            Offset(41.090626806239804, 18.007465029735965),
+            Offset(41.72574666538112, 20.285446843195025),
+            Offset(42.007757458722764, 22.071095827208),
+            Offset(42.102017139978244, 23.436994259413154),
+            Offset(42.10513769756149, 24.451651361492107),
+            Offset(42.07273484614025, 25.17313902889128),
+            Offset(42.03560534906618, 25.64831419721343),
+            Offset(42.00932498728679, 25.914213546678557),
+            Offset(42.000009413094325, 25.99991528033459),
+            Offset(42.0, 25.999999999999996),
+          ],
+          <Offset>[
+            Offset(9.991126102962665, 26.878296385119686),
+            Offset(9.478546725862362, 26.36043822046848),
+            Offset(8.639473946578361, 25.14017735498227),
+            Offset(7.81032131475563, 22.887960789223563),
+            Offset(7.650884598609569, 19.167302777578165),
+            Offset(9.48709191219624, 13.832759399890797),
+            Offset(14.921172139995065, 8.360637610125773),
+            Offset(22.72506805378891, 5.934160729915408),
+            Offset(29.673031211225855, 6.800676848304256),
+            Offset(34.46997135283492, 9.222324273729102),
+            Offset(37.502650165077256, 11.930267669929911),
+            Offset(39.35834774432234, 14.402023413006837),
+            Offset(40.47802166351221, 16.485028139984657),
+            Offset(41.1451258730191, 18.165219901458904),
+            Offset(41.53691790082557, 19.47711258821273),
+            Offset(41.76269195502168, 20.466336955649535),
+            Offset(41.889454758673935, 21.17734018391193),
+            Offset(41.95753760335532, 21.64907609138882),
+            Offset(41.99026637589216, 21.914258950769785),
+            Offset(41.99999058650693, 21.999915280378897),
+            Offset(42.0, 21.999999999999996),
+          ],
+          <Offset>[
+            Offset(9.991126102962665, 26.878296385119686),
+            Offset(9.478546725862362, 26.36043822046848),
+            Offset(8.639473946578361, 25.14017735498227),
+            Offset(7.81032131475563, 22.887960789223563),
+            Offset(7.650884598609569, 19.167302777578165),
+            Offset(9.48709191219624, 13.832759399890797),
+            Offset(14.921172139995065, 8.360637610125773),
+            Offset(22.72506805378891, 5.934160729915408),
+            Offset(29.673031211225855, 6.800676848304256),
+            Offset(34.46997135283492, 9.222324273729102),
+            Offset(37.502650165077256, 11.930267669929911),
+            Offset(39.35834774432234, 14.402023413006837),
+            Offset(40.47802166351221, 16.485028139984657),
+            Offset(41.1451258730191, 18.165219901458904),
+            Offset(41.53691790082557, 19.47711258821273),
+            Offset(41.76269195502168, 20.466336955649535),
+            Offset(41.889454758673935, 21.17734018391193),
+            Offset(41.95753760335532, 21.64907609138882),
+            Offset(41.99026637589216, 21.914258950769785),
+            Offset(41.99999058650693, 21.999915280378897),
+            Offset(42.0, 21.999999999999996),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(9.991126102962665, 26.878296385119686),
+            Offset(9.478546725862362, 26.36043822046848),
+            Offset(8.639473946578361, 25.14017735498227),
+            Offset(7.81032131475563, 22.887960789223563),
+            Offset(7.650884598609569, 19.167302777578165),
+            Offset(9.48709191219624, 13.832759399890797),
+            Offset(14.921172139995065, 8.360637610125773),
+            Offset(22.72506805378891, 5.934160729915408),
+            Offset(29.673031211225855, 6.800676848304256),
+            Offset(34.46997135283492, 9.222324273729102),
+            Offset(37.502650165077256, 11.930267669929911),
+            Offset(39.35834774432234, 14.402023413006837),
+            Offset(40.47802166351221, 16.485028139984657),
+            Offset(41.1451258730191, 18.165219901458904),
+            Offset(41.53691790082557, 19.47711258821273),
+            Offset(41.76269195502168, 20.466336955649535),
+            Offset(41.889454758673935, 21.17734018391193),
+            Offset(41.95753760335532, 21.64907609138882),
+            Offset(41.99026637589216, 21.914258950769785),
+            Offset(41.99999058650693, 21.999915280378897),
+            Offset(42.0, 21.999999999999996),
+          ],
+          <Offset>[
+            Offset(37.99013876585475, 26.968022111746365),
+            Offset(38.34168789996964, 27.280283823920673),
+            Offset(38.83631692227788, 28.138775811806013),
+            Offset(39.118267938560145, 29.897482655817214),
+            Offset(38.51074728933807, 32.949120066048565),
+            Offset(35.722576438377665, 37.28828372032828),
+            Offset(29.406135049202696, 41.25654876001545),
+            Offset(21.278164010187375, 41.90507215412886),
+            Offset(14.689874224041562, 39.53452793089749),
+            Offset(10.541513764638172, 36.118958216471256),
+            Offset(8.177229327470219, 32.81135223700264),
+            Offset(6.909373193760196, 29.992534970264035),
+            Offset(6.274253334618873, 27.714553156804975),
+            Offset(5.992242541277232, 25.928904172792),
+            Offset(5.897982860021752, 24.563005740586846),
+            Offset(5.894862302438508, 23.548348638507893),
+            Offset(5.927265153859754, 22.82686097110872),
+            Offset(5.964394650933819, 22.35168580278657),
+            Offset(5.990675012713211, 22.085786453321443),
+            Offset(5.999990586905675, 22.00008471966541),
+            Offset(6.0, 22.000000000000004),
+          ],
+          <Offset>[
+            Offset(37.99013876585475, 26.968022111746365),
+            Offset(38.34168789996964, 27.280283823920673),
+            Offset(38.83631692227788, 28.138775811806013),
+            Offset(39.118267938560145, 29.897482655817214),
+            Offset(38.51074728933807, 32.949120066048565),
+            Offset(35.722576438377665, 37.28828372032828),
+            Offset(29.406135049202696, 41.25654876001545),
+            Offset(21.278164010187375, 41.90507215412886),
+            Offset(14.689874224041562, 39.53452793089749),
+            Offset(10.541513764638172, 36.118958216471256),
+            Offset(8.177229327470219, 32.81135223700264),
+            Offset(6.909373193760196, 29.992534970264035),
+            Offset(6.274253334618873, 27.714553156804975),
+            Offset(5.992242541277232, 25.928904172792),
+            Offset(5.897982860021752, 24.563005740586846),
+            Offset(5.894862302438508, 23.548348638507893),
+            Offset(5.927265153859754, 22.82686097110872),
+            Offset(5.964394650933819, 22.35168580278657),
+            Offset(5.990675012713211, 22.085786453321443),
+            Offset(5.999990586905675, 22.00008471966541),
+            Offset(6.0, 22.000000000000004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(37.99013876585475, 26.968022111746365),
+            Offset(38.34168789996964, 27.280283823920673),
+            Offset(38.83631692227788, 28.138775811806013),
+            Offset(39.118267938560145, 29.897482655817214),
+            Offset(38.51074728933807, 32.949120066048565),
+            Offset(35.722576438377665, 37.28828372032828),
+            Offset(29.406135049202696, 41.25654876001545),
+            Offset(21.278164010187375, 41.90507215412886),
+            Offset(14.689874224041562, 39.53452793089749),
+            Offset(10.541513764638172, 36.118958216471256),
+            Offset(8.177229327470219, 32.81135223700264),
+            Offset(6.909373193760196, 29.992534970264035),
+            Offset(6.274253334618873, 27.714553156804975),
+            Offset(5.992242541277232, 25.928904172792),
+            Offset(5.897982860021752, 24.563005740586846),
+            Offset(5.894862302438508, 23.548348638507893),
+            Offset(5.927265153859754, 22.82686097110872),
+            Offset(5.964394650933819, 22.35168580278657),
+            Offset(5.990675012713211, 22.085786453321443),
+            Offset(5.999990586905675, 22.00008471966541),
+            Offset(6.0, 22.000000000000004),
+          ],
+          <Offset>[
+            Offset(38.005915505011714, 22.044873132716152),
+            Offset(38.49527667048537, 22.460937675484836),
+            Offset(39.29605291379161, 23.509087727219047),
+            Offset(40.090934628235715, 25.55307837508503),
+            Offset(40.245484311889584, 29.064745081245363),
+            Offset(38.450753318138695, 34.23676198345152),
+            Offset(33.0728907045072, 39.641976680762035),
+            Offset(25.27493194621109, 42.06583927008458),
+            Offset(18.326968788774145, 41.199323151695744),
+            Offset(13.530028647165079, 38.7776757262709),
+            Offset(10.497349834922744, 36.06973233007009),
+            Offset(8.641652255677663, 33.59797658699316),
+            Offset(7.521978336487795, 31.514971860015343),
+            Offset(6.854874126980906, 29.834780098541096),
+            Offset(6.463082099174432, 28.52288741178727),
+            Offset(6.237308044978324, 27.533663044350465),
+            Offset(6.1105452413260615, 26.82265981608807),
+            Offset(6.04246239664468, 26.35092390861118),
+            Offset(6.009733624107838, 26.085741049230215),
+            Offset(6.000009413493068, 26.000084719621103),
+            Offset(6.0, 26.000000000000004),
+          ],
+          <Offset>[
+            Offset(38.005915505011714, 22.044873132716152),
+            Offset(38.49527667048537, 22.460937675484836),
+            Offset(39.29605291379161, 23.509087727219047),
+            Offset(40.090934628235715, 25.55307837508503),
+            Offset(40.245484311889584, 29.064745081245363),
+            Offset(38.450753318138695, 34.23676198345152),
+            Offset(33.0728907045072, 39.641976680762035),
+            Offset(25.27493194621109, 42.06583927008458),
+            Offset(18.326968788774145, 41.199323151695744),
+            Offset(13.530028647165079, 38.7776757262709),
+            Offset(10.497349834922744, 36.06973233007009),
+            Offset(8.641652255677663, 33.59797658699316),
+            Offset(7.521978336487795, 31.514971860015343),
+            Offset(6.854874126980906, 29.834780098541096),
+            Offset(6.463082099174432, 28.52288741178727),
+            Offset(6.237308044978324, 27.533663044350465),
+            Offset(6.1105452413260615, 26.82265981608807),
+            Offset(6.04246239664468, 26.35092390861118),
+            Offset(6.009733624107838, 26.085741049230215),
+            Offset(6.000009413493068, 26.000084719621103),
+            Offset(6.0, 26.000000000000004),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(20.176179763180997, 21.987735903833876),
+            Offset(20.55193618727129, 21.88909752977205),
+            Offset(21.227655124257208, 21.71486474263169),
+            Offset(22.13567600845661, 21.53308372554798),
+            Offset(23.226514419921436, 21.464181986077985),
+            Offset(24.4102783754196, 21.684041595886637),
+            Offset(25.440842849032627, 22.309300738195095),
+            Offset(26.03642252837242, 23.134721894037483),
+            Offset(26.21244881576038, 23.97183703919535),
+            Offset(26.123327546035636, 24.622256415770117),
+            Offset(25.931014482213666, 25.0802342265462),
+            Offset(25.71921080582189, 25.392852152910002),
+            Offset(25.5230666408985, 25.604989383797584),
+            Offset(25.3554718074648, 25.748833638139686),
+            Offset(25.21948374988762, 25.846234627148135),
+            Offset(25.114174554400172, 25.911632280813432),
+            Offset(25.037072414338468, 25.95453414453137),
+            Offset(24.98527999526863, 25.9811477091939),
+            Offset(24.955944954391757, 25.995467904751596),
+            Offset(24.946435804885212, 25.999995545483056),
+            Offset(24.946426391602, 26.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(20.176179763180997, 21.987735903833876),
+            Offset(20.55193618727129, 21.88909752977205),
+            Offset(21.227655124257208, 21.71486474263169),
+            Offset(22.13567600845661, 21.53308372554798),
+            Offset(23.226514419921436, 21.464181986077985),
+            Offset(24.4102783754196, 21.684041595886637),
+            Offset(25.440842849032627, 22.309300738195095),
+            Offset(26.03642252837242, 23.134721894037483),
+            Offset(26.21244881576038, 23.97183703919535),
+            Offset(26.123327546035636, 24.622256415770117),
+            Offset(25.931014482213666, 25.0802342265462),
+            Offset(25.71921080582189, 25.392852152910002),
+            Offset(25.5230666408985, 25.604989383797584),
+            Offset(25.3554718074648, 25.748833638139686),
+            Offset(25.21948374988762, 25.846234627148135),
+            Offset(25.114174554400172, 25.911632280813432),
+            Offset(25.037072414338468, 25.95453414453137),
+            Offset(24.98527999526863, 25.9811477091939),
+            Offset(24.955944954391757, 25.995467904751596),
+            Offset(24.946435804885212, 25.999995545483056),
+            Offset(24.946426391602, 26.0),
+          ],
+          <Offset>[
+            Offset(10.006902842149625, 21.95514740608957),
+            Offset(9.632135496348102, 21.541092072031688),
+            Offset(9.099209938111999, 20.510489270397283),
+            Offset(8.78298800447023, 18.543556508500117),
+            Offset(9.385621621133684, 15.282927792762731),
+            Offset(12.215268791942359, 10.781237663000711),
+            Offset(18.58792779526773, 6.746065530800053),
+            Offset(26.721835989812707, 6.094927845869137),
+            Offset(33.31012577595927, 8.465472069100691),
+            Offset(37.458486235363154, 11.881041783527248),
+            Offset(39.82277067253141, 15.1886477629962),
+            Offset(41.09062680624161, 18.007465029735098),
+            Offset(41.72574666538303, 20.2854468431944),
+            Offset(42.007757458724726, 22.07109582720757),
+            Offset(42.10201713998023, 23.43699425941287),
+            Offset(42.105137697563485, 24.451651361491937),
+            Offset(42.07273484614225, 25.173139028891192),
+            Offset(42.03560534906818, 25.64831419721339),
+            Offset(42.00932498728879, 25.91421354667855),
+            Offset(42.00000941309632, 25.99991528033459),
+            Offset(42.000000000002004, 25.999999999999996),
+          ],
+          <Offset>[
+            Offset(10.006902842149625, 21.95514740608957),
+            Offset(9.632135496348102, 21.541092072031688),
+            Offset(9.099209938111999, 20.510489270397283),
+            Offset(8.78298800447023, 18.543556508500117),
+            Offset(9.385621621133684, 15.282927792762731),
+            Offset(12.215268791942359, 10.781237663000711),
+            Offset(18.58792779526773, 6.746065530800053),
+            Offset(26.721835989812707, 6.094927845869137),
+            Offset(33.31012577595927, 8.465472069100691),
+            Offset(37.458486235363154, 11.881041783527248),
+            Offset(39.82277067253141, 15.1886477629962),
+            Offset(41.09062680624161, 18.007465029735098),
+            Offset(41.72574666538303, 20.2854468431944),
+            Offset(42.007757458724726, 22.07109582720757),
+            Offset(42.10201713998023, 23.43699425941287),
+            Offset(42.105137697563485, 24.451651361491937),
+            Offset(42.07273484614225, 25.173139028891192),
+            Offset(42.03560534906818, 25.64831419721339),
+            Offset(42.00932498728879, 25.91421354667855),
+            Offset(42.00000941309632, 25.99991528033459),
+            Offset(42.000000000002004, 25.999999999999996),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(10.006902842149625, 21.95514740608957),
+            Offset(9.632135496348102, 21.541092072031688),
+            Offset(9.099209938111999, 20.510489270397283),
+            Offset(8.78298800447023, 18.543556508500117),
+            Offset(9.385621621133684, 15.282927792762731),
+            Offset(12.215268791942359, 10.781237663000711),
+            Offset(18.58792779526773, 6.746065530800053),
+            Offset(26.721835989812707, 6.094927845869137),
+            Offset(33.31012577595927, 8.465472069100691),
+            Offset(37.458486235363154, 11.881041783527248),
+            Offset(39.82277067253141, 15.1886477629962),
+            Offset(41.09062680624161, 18.007465029735098),
+            Offset(41.72574666538303, 20.2854468431944),
+            Offset(42.007757458724726, 22.07109582720757),
+            Offset(42.10201713998023, 23.43699425941287),
+            Offset(42.105137697563485, 24.451651361491937),
+            Offset(42.07273484614225, 25.173139028891192),
+            Offset(42.03560534906818, 25.64831419721339),
+            Offset(42.00932498728879, 25.91421354667855),
+            Offset(42.00000941309632, 25.99991528033459),
+            Offset(42.000000000002004, 25.999999999999996),
+          ],
+          <Offset>[
+            Offset(9.950103066904006, 39.679580365751406),
+            Offset(9.115564488520917, 37.75018397768418),
+            Offset(7.7454464103988485, 34.143319828138715),
+            Offset(6.441070390954248, 29.003703867586243),
+            Offset(6.213866824197122, 22.385033086525855),
+            Offset(8.625212439492824, 14.796789248890494),
+            Offset(14.838843583186215, 8.396889109087798),
+            Offset(22.72506805378899, 5.934160729913408),
+            Offset(29.67303121122669, 6.800676848302437),
+            Offset(34.469971352836254, 9.222324273727608),
+            Offset(37.50265016507888, 11.930267669928751),
+            Offset(39.35834774432414, 14.40202341300597),
+            Offset(40.478021663514106, 16.48502813998403),
+            Offset(41.145125873021044, 18.165219901458475),
+            Offset(41.53691790082755, 19.47711258821245),
+            Offset(41.76269195502367, 20.46633695564936),
+            Offset(41.88945475867594, 21.177340183911838),
+            Offset(41.95753760335732, 21.649076091388782),
+            Offset(41.990266375894166, 21.914258950769778),
+            Offset(41.99999058650893, 21.999915280378897),
+            Offset(42.000000000002004, 21.999999999999996),
+          ],
+          <Offset>[
+            Offset(9.950103066904006, 39.679580365751406),
+            Offset(9.115564488520917, 37.75018397768418),
+            Offset(7.7454464103988485, 34.143319828138715),
+            Offset(6.441070390954248, 29.003703867586243),
+            Offset(6.213866824197122, 22.385033086525855),
+            Offset(8.625212439492824, 14.796789248890494),
+            Offset(14.838843583186215, 8.396889109087798),
+            Offset(22.72506805378899, 5.934160729913408),
+            Offset(29.67303121122669, 6.800676848302437),
+            Offset(34.469971352836254, 9.222324273727608),
+            Offset(37.50265016507888, 11.930267669928751),
+            Offset(39.35834774432414, 14.40202341300597),
+            Offset(40.478021663514106, 16.48502813998403),
+            Offset(41.145125873021044, 18.165219901458475),
+            Offset(41.53691790082755, 19.47711258821245),
+            Offset(41.76269195502367, 20.46633695564936),
+            Offset(41.88945475867594, 21.177340183911838),
+            Offset(41.95753760335732, 21.649076091388782),
+            Offset(41.990266375894166, 21.914258950769778),
+            Offset(41.99999058650893, 21.999915280378897),
+            Offset(42.000000000002004, 21.999999999999996),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(9.950103066904006, 39.679580365751406),
+            Offset(9.115564488520917, 37.75018397768418),
+            Offset(7.7454464103988485, 34.143319828138715),
+            Offset(6.441070390954248, 29.003703867586243),
+            Offset(6.213866824197122, 22.385033086525855),
+            Offset(8.625212439492824, 14.796789248890494),
+            Offset(14.838843583186215, 8.396889109087798),
+            Offset(22.72506805378899, 5.934160729913408),
+            Offset(29.67303121122669, 6.800676848302437),
+            Offset(34.469971352836254, 9.222324273727608),
+            Offset(37.50265016507888, 11.930267669928751),
+            Offset(39.35834774432414, 14.40202341300597),
+            Offset(40.478021663514106, 16.48502813998403),
+            Offset(41.145125873021044, 18.165219901458475),
+            Offset(41.53691790082755, 19.47711258821245),
+            Offset(41.76269195502367, 20.46633695564936),
+            Offset(41.88945475867594, 21.177340183911838),
+            Offset(41.95753760335732, 21.649076091388782),
+            Offset(41.990266375894166, 21.914258950769778),
+            Offset(41.99999058650893, 21.999915280378897),
+            Offset(42.000000000002004, 21.999999999999996),
+          ],
+          <Offset>[
+            Offset(20.119379987935375, 39.71216886349571),
+            Offset(20.035365179444106, 38.098189435424544),
+            Offset(19.873891596544055, 35.347695300373125),
+            Offset(19.793758394940628, 31.993231084634104),
+            Offset(20.054759622984875, 28.566287279841106),
+            Offset(20.820222022970064, 25.699593181776418),
+            Offset(21.69175863695111, 23.96012431648284),
+            Offset(22.0396545923487, 22.973954778081758),
+            Offset(22.575354251027797, 22.307041818397096),
+            Offset(23.13481266350873, 21.963538905970477),
+            Offset(23.61089397476114, 21.82185413347875),
+            Offset(23.986931743904425, 21.787410536180875),
+            Offset(24.275341639029573, 21.804570680587215),
+            Offset(24.49284022176112, 21.842957712390593),
+            Offset(24.65438451073494, 21.88635295594771),
+            Offset(24.771728811860356, 21.926317874970856),
+            Offset(24.853792326872156, 21.958735299552014),
+            Offset(24.90721224955777, 21.981909603369285),
+            Offset(24.93688634299713, 21.995513308842824),
+            Offset(24.946416978297822, 21.99999554552736),
+            Offset(24.946426391602, 22.0),
+          ],
+          <Offset>[
+            Offset(20.119379987935375, 39.71216886349571),
+            Offset(20.035365179444106, 38.098189435424544),
+            Offset(19.873891596544055, 35.347695300373125),
+            Offset(19.793758394940628, 31.993231084634104),
+            Offset(20.054759622984875, 28.566287279841106),
+            Offset(20.820222022970064, 25.699593181776418),
+            Offset(21.69175863695111, 23.96012431648284),
+            Offset(22.0396545923487, 22.973954778081758),
+            Offset(22.575354251027797, 22.307041818397096),
+            Offset(23.13481266350873, 21.963538905970477),
+            Offset(23.61089397476114, 21.82185413347875),
+            Offset(23.986931743904425, 21.787410536180875),
+            Offset(24.275341639029573, 21.804570680587215),
+            Offset(24.49284022176112, 21.842957712390593),
+            Offset(24.65438451073494, 21.88635295594771),
+            Offset(24.771728811860356, 21.926317874970856),
+            Offset(24.853792326872156, 21.958735299552014),
+            Offset(24.90721224955777, 21.981909603369285),
+            Offset(24.93688634299713, 21.995513308842824),
+            Offset(24.946416978297822, 21.99999554552736),
+            Offset(24.946426391602, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(20.119379987935375, 39.71216886349571),
+            Offset(20.035365179444106, 38.098189435424544),
+            Offset(19.873891596544055, 35.347695300373125),
+            Offset(19.793758394940628, 31.993231084634104),
+            Offset(20.054759622984875, 28.566287279841106),
+            Offset(20.820222022970064, 25.699593181776418),
+            Offset(21.69175863695111, 23.96012431648284),
+            Offset(22.0396545923487, 22.973954778081758),
+            Offset(22.575354251027797, 22.307041818397096),
+            Offset(23.13481266350873, 21.963538905970477),
+            Offset(23.61089397476114, 21.82185413347875),
+            Offset(23.986931743904425, 21.787410536180875),
+            Offset(24.275341639029573, 21.804570680587215),
+            Offset(24.49284022176112, 21.842957712390593),
+            Offset(24.65438451073494, 21.88635295594771),
+            Offset(24.771728811860356, 21.926317874970856),
+            Offset(24.853792326872156, 21.958735299552014),
+            Offset(24.90721224955777, 21.981909603369285),
+            Offset(24.93688634299713, 21.995513308842824),
+            Offset(24.946416978297822, 21.99999554552736),
+            Offset(24.946426391602, 22.0),
+          ],
+          <Offset>[
+            Offset(20.176179763180997, 21.987735903833876),
+            Offset(20.55193618727129, 21.88909752977205),
+            Offset(21.227655124257208, 21.71486474263169),
+            Offset(22.13567600845661, 21.53308372554798),
+            Offset(23.226514419921436, 21.464181986077985),
+            Offset(24.410278375419598, 21.684041595886633),
+            Offset(25.440842849032627, 22.309300738195095),
+            Offset(26.03642252837242, 23.134721894037487),
+            Offset(26.21244881576038, 23.971837039195353),
+            Offset(26.123327546035632, 24.622256415770117),
+            Offset(25.931014482213666, 25.0802342265462),
+            Offset(25.71921080582189, 25.392852152910002),
+            Offset(25.5230666408985, 25.604989383797584),
+            Offset(25.3554718074648, 25.748833638139686),
+            Offset(25.21948374988762, 25.846234627148135),
+            Offset(25.114174554400172, 25.911632280813432),
+            Offset(25.037072414338468, 25.95453414453137),
+            Offset(24.98527999526863, 25.9811477091939),
+            Offset(24.955944954391757, 25.995467904751596),
+            Offset(24.946435804885212, 25.999995545483056),
+            Offset(24.946426391602, 26.0),
+          ],
+          <Offset>[
+            Offset(20.176179763180997, 21.987735903833876),
+            Offset(20.55193618727129, 21.88909752977205),
+            Offset(21.227655124257208, 21.71486474263169),
+            Offset(22.13567600845661, 21.53308372554798),
+            Offset(23.226514419921436, 21.464181986077985),
+            Offset(24.410278375419598, 21.684041595886633),
+            Offset(25.440842849032627, 22.309300738195095),
+            Offset(26.03642252837242, 23.134721894037487),
+            Offset(26.21244881576038, 23.971837039195353),
+            Offset(26.123327546035632, 24.622256415770117),
+            Offset(25.931014482213666, 25.0802342265462),
+            Offset(25.71921080582189, 25.392852152910002),
+            Offset(25.5230666408985, 25.604989383797584),
+            Offset(25.3554718074648, 25.748833638139686),
+            Offset(25.21948374988762, 25.846234627148135),
+            Offset(25.114174554400172, 25.911632280813432),
+            Offset(25.037072414338468, 25.95453414453137),
+            Offset(24.98527999526863, 25.9811477091939),
+            Offset(24.955944954391757, 25.995467904751596),
+            Offset(24.946435804885212, 25.999995545483056),
+            Offset(24.946426391602, 26.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(27.83663858395034, 22.012284634971753),
+            Offset(27.57547597959216, 22.112932217745428),
+            Offset(27.1676077276265, 22.304712254982665),
+            Offset(26.73824662421029, 22.56355115802843),
+            Offset(26.404591513129226, 22.883490887942344),
+            Offset(26.255743734676358, 23.33395805057893),
+            Offset(26.21997565073384, 24.078741473347776),
+            Offset(25.9603454076513, 25.02604522191824),
+            Offset(25.424645748972203, 25.692958181602904),
+            Offset(24.865187336491275, 26.036461094029523),
+            Offset(24.38910602523886, 26.17814586652125),
+            Offset(24.013068256095575, 26.212589463819125),
+            Offset(23.724658360970427, 26.195429319412785),
+            Offset(23.50715977823888, 26.157042287609407),
+            Offset(23.34561548926506, 26.11364704405229),
+            Offset(23.228271188139644, 26.073682125029144),
+            Offset(23.14620767312784, 26.041264700447986),
+            Offset(23.092787750442234, 26.018090396630715),
+            Offset(23.063113657002866, 26.004486691157176),
+            Offset(23.053583021702178, 26.00000445447264),
+            Offset(23.053573608398, 26.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(27.83663858395034, 22.012284634971753),
+            Offset(27.57547597959216, 22.112932217745428),
+            Offset(27.1676077276265, 22.304712254982665),
+            Offset(26.73824662421029, 22.56355115802843),
+            Offset(26.404591513129226, 22.883490887942344),
+            Offset(26.255743734676358, 23.33395805057893),
+            Offset(26.21997565073384, 24.078741473347776),
+            Offset(25.9603454076513, 25.02604522191824),
+            Offset(25.424645748972203, 25.692958181602904),
+            Offset(24.865187336491275, 26.036461094029523),
+            Offset(24.38910602523886, 26.17814586652125),
+            Offset(24.013068256095575, 26.212589463819125),
+            Offset(23.724658360970427, 26.195429319412785),
+            Offset(23.50715977823888, 26.157042287609407),
+            Offset(23.34561548926506, 26.11364704405229),
+            Offset(23.228271188139644, 26.073682125029144),
+            Offset(23.14620767312784, 26.041264700447986),
+            Offset(23.092787750442234, 26.018090396630715),
+            Offset(23.063113657002866, 26.004486691157176),
+            Offset(23.053583021702178, 26.00000445447264),
+            Offset(23.053573608398, 26.0),
+          ],
+          <Offset>[
+            Offset(38.00591550498171, 22.044873132716056),
+            Offset(38.49527667051535, 22.460937675485788),
+            Offset(39.29605291377171, 23.50908772721707),
+            Offset(40.09093462819668, 25.55307837507629),
+            Offset(40.245484311916975, 29.0647450812576),
+            Offset(38.4507533181536, 34.23676198346485),
+            Offset(33.07289070449873, 39.64197668074282),
+            Offset(25.27493194621101, 42.065839270086585),
+            Offset(18.32696878877331, 41.19932315169756),
+            Offset(13.530028647163753, 38.77767572627239),
+            Offset(10.497349834921115, 36.06973233007125),
+            Offset(8.641652255675858, 33.59797658699403),
+            Offset(7.521978336485894, 31.51497186001597),
+            Offset(6.854874126978952, 29.834780098541525),
+            Offset(6.463082099172453, 28.52288741178755),
+            Offset(6.237308044976331, 27.53366304435064),
+            Offset(6.110545241324058, 26.822659816088162),
+            Offset(6.042462396642684, 26.350923908611218),
+            Offset(6.009733624105834, 26.085741049230222),
+            Offset(6.0000094134910675, 26.000084719621103),
+            Offset(5.999999999998, 26.000000000000004),
+          ],
+          <Offset>[
+            Offset(38.00591550498171, 22.044873132716056),
+            Offset(38.49527667051535, 22.460937675485788),
+            Offset(39.29605291377171, 23.50908772721707),
+            Offset(40.09093462819668, 25.55307837507629),
+            Offset(40.245484311916975, 29.0647450812576),
+            Offset(38.4507533181536, 34.23676198346485),
+            Offset(33.07289070449873, 39.64197668074282),
+            Offset(25.27493194621101, 42.065839270086585),
+            Offset(18.32696878877331, 41.19932315169756),
+            Offset(13.530028647163753, 38.77767572627239),
+            Offset(10.497349834921115, 36.06973233007125),
+            Offset(8.641652255675858, 33.59797658699403),
+            Offset(7.521978336485894, 31.51497186001597),
+            Offset(6.854874126978952, 29.834780098541525),
+            Offset(6.463082099172453, 28.52288741178755),
+            Offset(6.237308044976331, 27.53366304435064),
+            Offset(6.110545241324058, 26.822659816088162),
+            Offset(6.042462396642684, 26.350923908611218),
+            Offset(6.009733624105834, 26.085741049230222),
+            Offset(6.0000094134910675, 26.000084719621103),
+            Offset(5.999999999998, 26.000000000000004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.00591550498171, 22.044873132716056),
+            Offset(38.49527667051535, 22.460937675485788),
+            Offset(39.29605291377171, 23.50908772721707),
+            Offset(40.09093462819668, 25.55307837507629),
+            Offset(40.245484311916975, 29.0647450812576),
+            Offset(38.4507533181536, 34.23676198346485),
+            Offset(33.07289070449873, 39.64197668074282),
+            Offset(25.27493194621101, 42.065839270086585),
+            Offset(18.32696878877331, 41.19932315169756),
+            Offset(13.530028647163753, 38.77767572627239),
+            Offset(10.497349834921115, 36.06973233007125),
+            Offset(8.641652255675858, 33.59797658699403),
+            Offset(7.521978336485894, 31.51497186001597),
+            Offset(6.854874126978952, 29.834780098541525),
+            Offset(6.463082099172453, 28.52288741178755),
+            Offset(6.237308044976331, 27.53366304435064),
+            Offset(6.110545241324058, 26.822659816088162),
+            Offset(6.042462396642684, 26.350923908611218),
+            Offset(6.009733624105834, 26.085741049230222),
+            Offset(6.0000094134910675, 26.000084719621103),
+            Offset(5.999999999998, 26.000000000000004),
+          ],
+          <Offset>[
+            Offset(37.94911572973609, 39.76930609237789),
+            Offset(37.978705662688164, 38.67002958113828),
+            Offset(37.94228938605856, 37.14191828495851),
+            Offset(37.74901701468069, 36.01322573416242),
+            Offset(37.073729514980414, 36.166850375020715),
+            Offset(34.860696965704065, 38.25231356935464),
+            Offset(29.32380649241722, 41.29280025903057),
+            Offset(21.278164010187293, 41.90507215413086),
+            Offset(14.689874224040729, 39.53452793089931),
+            Offset(10.541513764636846, 36.11895821647275),
+            Offset(8.17722932746859, 32.8113522370038),
+            Offset(6.909373193758391, 29.992534970264902),
+            Offset(6.274253334616972, 27.7145531568056),
+            Offset(5.992242541275278, 25.92890417279243),
+            Offset(5.8979828600197735, 24.56300574058713),
+            Offset(5.894862302436515, 23.548348638508063),
+            Offset(5.92726515385775, 22.826860971108808),
+            Offset(5.964394650931823, 22.35168580278661),
+            Offset(5.990675012711208, 22.08578645332145),
+            Offset(5.9999905869036745, 22.00008471966541),
+            Offset(5.999999999998, 22.000000000000004),
+          ],
+          <Offset>[
+            Offset(37.94911572973609, 39.76930609237789),
+            Offset(37.978705662688164, 38.67002958113828),
+            Offset(37.94228938605856, 37.14191828495851),
+            Offset(37.74901701468069, 36.01322573416242),
+            Offset(37.073729514980414, 36.166850375020715),
+            Offset(34.860696965704065, 38.25231356935464),
+            Offset(29.32380649241722, 41.29280025903057),
+            Offset(21.278164010187293, 41.90507215413086),
+            Offset(14.689874224040729, 39.53452793089931),
+            Offset(10.541513764636846, 36.11895821647275),
+            Offset(8.17722932746859, 32.8113522370038),
+            Offset(6.909373193758391, 29.992534970264902),
+            Offset(6.274253334616972, 27.7145531568056),
+            Offset(5.992242541275278, 25.92890417279243),
+            Offset(5.8979828600197735, 24.56300574058713),
+            Offset(5.894862302436515, 23.548348638508063),
+            Offset(5.92726515385775, 22.826860971108808),
+            Offset(5.964394650931823, 22.35168580278661),
+            Offset(5.990675012711208, 22.08578645332145),
+            Offset(5.9999905869036745, 22.00008471966541),
+            Offset(5.999999999998, 22.000000000000004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(37.94911572973609, 39.76930609237789),
+            Offset(37.978705662688164, 38.67002958113828),
+            Offset(37.94228938605856, 37.14191828495851),
+            Offset(37.74901701468069, 36.01322573416242),
+            Offset(37.073729514980414, 36.166850375020715),
+            Offset(34.860696965704065, 38.25231356935464),
+            Offset(29.32380649241722, 41.29280025903057),
+            Offset(21.278164010187293, 41.90507215413086),
+            Offset(14.689874224040729, 39.53452793089931),
+            Offset(10.541513764636846, 36.11895821647275),
+            Offset(8.17722932746859, 32.8113522370038),
+            Offset(6.909373193758391, 29.992534970264902),
+            Offset(6.274253334616972, 27.7145531568056),
+            Offset(5.992242541275278, 25.92890417279243),
+            Offset(5.8979828600197735, 24.56300574058713),
+            Offset(5.894862302436515, 23.548348638508063),
+            Offset(5.92726515385775, 22.826860971108808),
+            Offset(5.964394650931823, 22.35168580278661),
+            Offset(5.990675012711208, 22.08578645332145),
+            Offset(5.9999905869036745, 22.00008471966541),
+            Offset(5.999999999998, 22.000000000000004),
+          ],
+          <Offset>[
+            Offset(27.779838808704717, 39.73671759463359),
+            Offset(27.058904971764974, 38.322024123397924),
+            Offset(25.813844199913348, 35.937542812724104),
+            Offset(24.396329010694313, 33.02369851711455),
+            Offset(23.23283671619266, 29.985596181705468),
+            Offset(22.665687382226825, 27.349509636468714),
+            Offset(22.470891438652323, 25.72956505163552),
+            Offset(21.96357747162758, 24.86527810596251),
+            Offset(21.78755118423962, 24.028162960804647),
+            Offset(21.876672453964368, 23.377743584229883),
+            Offset(22.068985517786334, 22.9197657734538),
+            Offset(22.28078919417811, 22.607147847089998),
+            Offset(22.4769333591015, 22.395010616202416),
+            Offset(22.6445281925352, 22.251166361860314),
+            Offset(22.78051625011238, 22.153765372851865),
+            Offset(22.885825445599828, 22.088367719186568),
+            Offset(22.96292758566153, 22.04546585546863),
+            Offset(23.014720004731373, 22.0188522908061),
+            Offset(23.04405504560824, 22.004532095248404),
+            Offset(23.053564195114788, 22.000004454516944),
+            Offset(23.053573608398, 22.0),
+          ],
+          <Offset>[
+            Offset(27.779838808704717, 39.73671759463359),
+            Offset(27.058904971764974, 38.322024123397924),
+            Offset(25.813844199913348, 35.937542812724104),
+            Offset(24.396329010694313, 33.02369851711455),
+            Offset(23.23283671619266, 29.985596181705468),
+            Offset(22.665687382226825, 27.349509636468714),
+            Offset(22.470891438652323, 25.72956505163552),
+            Offset(21.96357747162758, 24.86527810596251),
+            Offset(21.78755118423962, 24.028162960804647),
+            Offset(21.876672453964368, 23.377743584229883),
+            Offset(22.068985517786334, 22.9197657734538),
+            Offset(22.28078919417811, 22.607147847089998),
+            Offset(22.4769333591015, 22.395010616202416),
+            Offset(22.6445281925352, 22.251166361860314),
+            Offset(22.78051625011238, 22.153765372851865),
+            Offset(22.885825445599828, 22.088367719186568),
+            Offset(22.96292758566153, 22.04546585546863),
+            Offset(23.014720004731373, 22.0188522908061),
+            Offset(23.04405504560824, 22.004532095248404),
+            Offset(23.053564195114788, 22.000004454516944),
+            Offset(23.053573608398, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(27.779838808704717, 39.73671759463359),
+            Offset(27.058904971764974, 38.322024123397924),
+            Offset(25.813844199913348, 35.937542812724104),
+            Offset(24.396329010694313, 33.02369851711455),
+            Offset(23.23283671619266, 29.985596181705468),
+            Offset(22.665687382226825, 27.349509636468714),
+            Offset(22.470891438652323, 25.72956505163552),
+            Offset(21.96357747162758, 24.86527810596251),
+            Offset(21.78755118423962, 24.028162960804647),
+            Offset(21.876672453964368, 23.377743584229883),
+            Offset(22.068985517786334, 22.9197657734538),
+            Offset(22.28078919417811, 22.607147847089998),
+            Offset(22.4769333591015, 22.395010616202416),
+            Offset(22.6445281925352, 22.251166361860314),
+            Offset(22.78051625011238, 22.153765372851865),
+            Offset(22.885825445599828, 22.088367719186568),
+            Offset(22.96292758566153, 22.04546585546863),
+            Offset(23.014720004731373, 22.0188522908061),
+            Offset(23.04405504560824, 22.004532095248404),
+            Offset(23.053564195114788, 22.000004454516944),
+            Offset(23.053573608398, 22.0),
+          ],
+          <Offset>[
+            Offset(27.83663858395034, 22.012284634971753),
+            Offset(27.57547597959216, 22.112932217745428),
+            Offset(27.1676077276265, 22.304712254982665),
+            Offset(26.738246624210294, 22.56355115802843),
+            Offset(26.404591513129223, 22.883490887942344),
+            Offset(26.25574373467636, 23.333958050578932),
+            Offset(26.21997565073384, 24.078741473347776),
+            Offset(25.9603454076513, 25.02604522191824),
+            Offset(25.424645748972203, 25.692958181602904),
+            Offset(24.865187336491275, 26.036461094029523),
+            Offset(24.38910602523886, 26.17814586652125),
+            Offset(24.013068256095575, 26.212589463819125),
+            Offset(23.724658360970427, 26.195429319412785),
+            Offset(23.50715977823888, 26.157042287609407),
+            Offset(23.34561548926506, 26.11364704405229),
+            Offset(23.228271188139644, 26.073682125029144),
+            Offset(23.14620767312784, 26.041264700447986),
+            Offset(23.092787750442234, 26.018090396630715),
+            Offset(23.063113657002866, 26.004486691157176),
+            Offset(23.053583021702178, 26.00000445447264),
+            Offset(23.053573608398, 26.0),
+          ],
+          <Offset>[
+            Offset(27.83663858395034, 22.012284634971753),
+            Offset(27.57547597959216, 22.112932217745428),
+            Offset(27.1676077276265, 22.304712254982665),
+            Offset(26.738246624210294, 22.56355115802843),
+            Offset(26.404591513129223, 22.883490887942344),
+            Offset(26.25574373467636, 23.333958050578932),
+            Offset(26.21997565073384, 24.078741473347776),
+            Offset(25.9603454076513, 25.02604522191824),
+            Offset(25.424645748972203, 25.692958181602904),
+            Offset(24.865187336491275, 26.036461094029523),
+            Offset(24.38910602523886, 26.17814586652125),
+            Offset(24.013068256095575, 26.212589463819125),
+            Offset(23.724658360970427, 26.195429319412785),
+            Offset(23.50715977823888, 26.157042287609407),
+            Offset(23.34561548926506, 26.11364704405229),
+            Offset(23.228271188139644, 26.073682125029144),
+            Offset(23.14620767312784, 26.041264700447986),
+            Offset(23.092787750442234, 26.018090396630715),
+            Offset(23.063113657002866, 26.004486691157176),
+            Offset(23.053583021702178, 26.00000445447264),
+            Offset(23.053573608398, 26.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(23.684125337085877, 6.32904503029285),
+            Offset(24.757855588582725, 6.522328815913423),
+            Offset(27.574407323116677, 7.305175651642309),
+            Offset(34.29718691345236, 10.565460438397327),
+            Offset(42.96465562696383, 18.312204872266342),
+            Offset(45.05615687407709, 26.45166424954756),
+            Offset(42.23233332042143, 35.60390079418309),
+            Offset(35.26685178627038, 42.467757059973906),
+            Offset(27.4197052006056, 45.36131120369138),
+            Offset(21.001315853482343, 45.424469500769995),
+            Offset(16.29765110355406, 44.215682562738714),
+            Offset(12.97234991047133, 42.61158062881598),
+            Offset(10.641290841160107, 41.016018618041265),
+            Offset(9.0114530912401, 39.59946991291384),
+            Offset(7.875830197056132, 38.422591589788325),
+            Offset(7.093422401327867, 37.49694905895691),
+            Offset(6.568745459991835, 36.81215692853645),
+            Offset(6.237631760921833, 36.34901917317271),
+            Offset(6.057380152594408, 36.08562753900215),
+            Offset(6.000056479961543, 36.00008471951035),
+            Offset(6.0000000000000036, 36.00000000000001),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(23.684125337085877, 6.32904503029285),
+            Offset(24.757855588582725, 6.522328815913423),
+            Offset(27.574407323116677, 7.305175651642309),
+            Offset(34.29718691345236, 10.565460438397327),
+            Offset(42.96465562696383, 18.312204872266342),
+            Offset(45.05615687407709, 26.45166424954756),
+            Offset(42.23233332042143, 35.60390079418309),
+            Offset(35.26685178627038, 42.467757059973906),
+            Offset(27.4197052006056, 45.36131120369138),
+            Offset(21.001315853482343, 45.424469500769995),
+            Offset(16.29765110355406, 44.215682562738714),
+            Offset(12.97234991047133, 42.61158062881598),
+            Offset(10.641290841160107, 41.016018618041265),
+            Offset(9.0114530912401, 39.59946991291384),
+            Offset(7.875830197056132, 38.422591589788325),
+            Offset(7.093422401327867, 37.49694905895691),
+            Offset(6.568745459991835, 36.81215692853645),
+            Offset(6.237631760921833, 36.34901917317271),
+            Offset(6.057380152594408, 36.08562753900215),
+            Offset(6.000056479961543, 36.00008471951035),
+            Offset(6.0000000000000036, 36.00000000000001),
+          ],
+          <Offset>[
+            Offset(23.689243079011977, 6.329061430625194),
+            Offset(23.64011618905085, 6.48670734054237),
+            Offset(23.133765296901146, 6.8642122589514845),
+            Offset(19.98658162330333, 7.361465557774245),
+            Offset(15.467378257263233, 6.032096680033179),
+            Offset(19.194942220379907, 3.3307514773626146),
+            Offset(27.75047360634506, 2.715037120490525),
+            Offset(36.713755829871914, 6.4968456357604545),
+            Offset(42.40286218778989, 12.627460121098146),
+            Offset(44.92977344167909, 18.52783555802784),
+            Offset(45.6230719411611, 23.334597995665984),
+            Offset(45.42132446103347, 27.02106907155878),
+            Offset(44.84505917005344, 29.786493601220947),
+            Offset(44.16433642298196, 31.835785641580745),
+            Offset(43.51476523785995, 33.33669843741421),
+            Offset(42.96125205391104, 34.41493737609855),
+            Offset(42.530935064806016, 35.16263614133967),
+            Offset(42.23077471334334, 35.64640946177496),
+            Offset(42.056971515773355, 35.91410003645049),
+            Offset(42.0000564795628, 35.99991528022383),
+            Offset(42.0, 35.99999999999999),
+          ],
+          <Offset>[
+            Offset(23.689243079011977, 6.329061430625194),
+            Offset(23.64011618905085, 6.48670734054237),
+            Offset(23.133765296901146, 6.8642122589514845),
+            Offset(19.98658162330333, 7.361465557774245),
+            Offset(15.467378257263233, 6.032096680033179),
+            Offset(19.194942220379907, 3.3307514773626146),
+            Offset(27.75047360634506, 2.715037120490525),
+            Offset(36.713755829871914, 6.4968456357604545),
+            Offset(42.40286218778989, 12.627460121098146),
+            Offset(44.92977344167909, 18.52783555802784),
+            Offset(45.6230719411611, 23.334597995665984),
+            Offset(45.42132446103347, 27.02106907155878),
+            Offset(44.84505917005344, 29.786493601220947),
+            Offset(44.16433642298196, 31.835785641580745),
+            Offset(43.51476523785995, 33.33669843741421),
+            Offset(42.96125205391104, 34.41493737609855),
+            Offset(42.530935064806016, 35.16263614133967),
+            Offset(42.23077471334334, 35.64640946177496),
+            Offset(42.056971515773355, 35.91410003645049),
+            Offset(42.0000564795628, 35.99991528022383),
+            Offset(42.0, 35.99999999999999),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(23.689243079011977, 6.329061430625194),
+            Offset(23.64011618905085, 6.48670734054237),
+            Offset(23.133765296901146, 6.8642122589514845),
+            Offset(19.98658162330333, 7.361465557774245),
+            Offset(15.467378257263233, 6.032096680033179),
+            Offset(19.194942220379907, 3.3307514773626146),
+            Offset(27.75047360634506, 2.715037120490525),
+            Offset(36.713755829871914, 6.4968456357604545),
+            Offset(42.40286218778989, 12.627460121098146),
+            Offset(44.92977344167909, 18.52783555802784),
+            Offset(45.6230719411611, 23.334597995665984),
+            Offset(45.42132446103347, 27.02106907155878),
+            Offset(44.84505917005344, 29.786493601220947),
+            Offset(44.16433642298196, 31.835785641580745),
+            Offset(43.51476523785995, 33.33669843741421),
+            Offset(42.96125205391104, 34.41493737609855),
+            Offset(42.530935064806016, 35.16263614133967),
+            Offset(42.23077471334334, 35.64640946177496),
+            Offset(42.056971515773355, 35.91410003645049),
+            Offset(42.0000564795628, 35.99991528022383),
+            Offset(42.0, 35.99999999999999),
+          ],
+          <Offset>[
+            Offset(4.256210085244433, 23.724986123422013),
+            Offset(4.334340540250139, 22.918290544448126),
+            Offset(4.683893395049303, 20.897955670871653),
+            Offset(6.165631658910659, 16.538261784200643),
+            Offset(10.04725752347015, 10.401464969282092),
+            Offset(15.676813589860602, 6.207875011618711),
+            Offset(24.053924042171055, 4.3051387362291464),
+            Offset(32.7169878938482, 6.336078519804726),
+            Offset(38.76576762305731, 10.962664900299892),
+            Offset(41.94125855915219, 15.869118048228197),
+            Offset(43.30295143370857, 20.076217902598536),
+            Offset(43.689045399116004, 23.415627454829654),
+            Offset(43.59733416818452, 25.98607489801058),
+            Offset(43.30170483727829, 27.92990971583165),
+            Offset(42.94966599870727, 29.37681676621379),
+            Offset(42.618806311371216, 30.429622970255974),
+            Offset(42.347654977339715, 31.166837296360313),
+            Offset(42.15270696763247, 31.64717135595035),
+            Offset(42.03791290437873, 31.914145440541716),
+            Offset(42.00003765297541, 31.999915280268137),
+            Offset(42.0, 31.999999999999996),
+          ],
+          <Offset>[
+            Offset(4.256210085244433, 23.724986123422013),
+            Offset(4.334340540250139, 22.918290544448126),
+            Offset(4.683893395049303, 20.897955670871653),
+            Offset(6.165631658910659, 16.538261784200643),
+            Offset(10.04725752347015, 10.401464969282092),
+            Offset(15.676813589860602, 6.207875011618711),
+            Offset(24.053924042171055, 4.3051387362291464),
+            Offset(32.7169878938482, 6.336078519804726),
+            Offset(38.76576762305731, 10.962664900299892),
+            Offset(41.94125855915219, 15.869118048228197),
+            Offset(43.30295143370857, 20.076217902598536),
+            Offset(43.689045399116004, 23.415627454829654),
+            Offset(43.59733416818452, 25.98607489801058),
+            Offset(43.30170483727829, 27.92990971583165),
+            Offset(42.94966599870727, 29.37681676621379),
+            Offset(42.618806311371216, 30.429622970255974),
+            Offset(42.347654977339715, 31.166837296360313),
+            Offset(42.15270696763247, 31.64717135595035),
+            Offset(42.03791290437873, 31.914145440541716),
+            Offset(42.00003765297541, 31.999915280268137),
+            Offset(42.0, 31.999999999999996),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(4.256210085244433, 23.724986123422013),
+            Offset(4.334340540250139, 22.918290544448126),
+            Offset(4.683893395049303, 20.897955670871653),
+            Offset(6.165631658910659, 16.538261784200643),
+            Offset(10.04725752347015, 10.401464969282092),
+            Offset(15.676813589860602, 6.207875011618711),
+            Offset(24.053924042171055, 4.3051387362291464),
+            Offset(32.7169878938482, 6.336078519804726),
+            Offset(38.76576762305731, 10.962664900299892),
+            Offset(41.94125855915219, 15.869118048228197),
+            Offset(43.30295143370857, 20.076217902598536),
+            Offset(43.689045399116004, 23.415627454829654),
+            Offset(43.59733416818452, 25.98607489801058),
+            Offset(43.30170483727829, 27.92990971583165),
+            Offset(42.94966599870727, 29.37681676621379),
+            Offset(42.618806311371216, 30.429622970255974),
+            Offset(42.347654977339715, 31.166837296360313),
+            Offset(42.15270696763247, 31.64717135595035),
+            Offset(42.03791290437873, 31.914145440541716),
+            Offset(42.00003765297541, 31.999915280268137),
+            Offset(42.0, 31.999999999999996),
+          ],
+          <Offset>[
+            Offset(43.99436998801117, 23.85233115929924),
+            Offset(43.935955265272696, 24.180362852333243),
+            Offset(43.76617309606209, 24.778893280638325),
+            Offset(43.45759773349659, 24.887543031835328),
+            Offset(43.476142398493316, 25.33059113299948),
+            Offset(42.61623543783426, 30.292746160199744),
+            Offset(38.56421880483555, 37.258579505338574),
+            Offset(31.270083850246664, 42.30698994401817),
+            Offset(23.782610635873013, 43.696515982893125),
+            Offset(18.012800970955436, 42.76575199097035),
+            Offset(13.977530596101534, 40.95730246967126),
+            Offset(11.240070848553863, 39.00613901208685),
+            Offset(9.393565839291185, 37.2155999148309),
+            Offset(8.148821505536425, 35.693593987164746),
+            Offset(7.310730957903452, 34.462709918587905),
+            Offset(6.750976658788051, 33.51163465311433),
+            Offset(6.385465372525527, 32.8163580835571),
+            Offset(6.159564015210972, 32.3497810673481),
+            Offset(6.038321541199782, 32.08567294309337),
+            Offset(6.00003765337415, 32.00008471955465),
+            Offset(6.0000000000000036, 32.00000000000001),
+          ],
+          <Offset>[
+            Offset(43.99436998801117, 23.85233115929924),
+            Offset(43.935955265272696, 24.180362852333243),
+            Offset(43.76617309606209, 24.778893280638325),
+            Offset(43.45759773349659, 24.887543031835328),
+            Offset(43.476142398493316, 25.33059113299948),
+            Offset(42.61623543783426, 30.292746160199744),
+            Offset(38.56421880483555, 37.258579505338574),
+            Offset(31.270083850246664, 42.30698994401817),
+            Offset(23.782610635873013, 43.696515982893125),
+            Offset(18.012800970955436, 42.76575199097035),
+            Offset(13.977530596101534, 40.95730246967126),
+            Offset(11.240070848553863, 39.00613901208685),
+            Offset(9.393565839291185, 37.2155999148309),
+            Offset(8.148821505536425, 35.693593987164746),
+            Offset(7.310730957903452, 34.462709918587905),
+            Offset(6.750976658788051, 33.51163465311433),
+            Offset(6.385465372525527, 32.8163580835571),
+            Offset(6.159564015210972, 32.3497810673481),
+            Offset(6.038321541199782, 32.08567294309337),
+            Offset(6.00003765337415, 32.00008471955465),
+            Offset(6.0000000000000036, 32.00000000000001),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(43.99436998801117, 23.85233115929924),
+            Offset(43.935955265272696, 24.180362852333243),
+            Offset(43.76617309606209, 24.778893280638325),
+            Offset(43.45759773349659, 24.887543031835328),
+            Offset(43.476142398493316, 25.33059113299948),
+            Offset(42.61623543783426, 30.292746160199744),
+            Offset(38.56421880483555, 37.258579505338574),
+            Offset(31.270083850246664, 42.30698994401817),
+            Offset(23.782610635873013, 43.696515982893125),
+            Offset(18.012800970955436, 42.76575199097035),
+            Offset(13.977530596101534, 40.95730246967126),
+            Offset(11.240070848553863, 39.00613901208685),
+            Offset(9.393565839291185, 37.2155999148309),
+            Offset(8.148821505536425, 35.693593987164746),
+            Offset(7.310730957903452, 34.462709918587905),
+            Offset(6.750976658788051, 33.51163465311433),
+            Offset(6.385465372525527, 32.8163580835571),
+            Offset(6.159564015210972, 32.3497810673481),
+            Offset(6.038321541199782, 32.08567294309337),
+            Offset(6.00003765337415, 32.00008471955465),
+            Offset(6.0000000000000036, 32.00000000000001),
+          ],
+          <Offset>[
+            Offset(23.684125337090187, 6.329045030292864),
+            Offset(24.757855588602716, 6.52232881591406),
+            Offset(27.574407323047023, 7.305175651635392),
+            Offset(34.29718691345236, 10.565460438397327),
+            Offset(42.96465562689078, 18.312204872233718),
+            Offset(45.05615687408604, 26.45166424955556),
+            Offset(42.232333320435735, 35.60390079421558),
+            Offset(35.26685178627038, 42.467757059973906),
+            Offset(27.4197052006056, 45.36131120369138),
+            Offset(21.001315853482343, 45.424469500769995),
+            Offset(16.29765110355406, 44.215682562738714),
+            Offset(12.97234991047133, 42.61158062881598),
+            Offset(10.641290841160107, 41.016018618041265),
+            Offset(9.0114530912401, 39.59946991291384),
+            Offset(7.875830197056132, 38.422591589788325),
+            Offset(7.093422401327867, 37.49694905895691),
+            Offset(6.568745459991835, 36.81215692853645),
+            Offset(6.237631760921833, 36.34901917317271),
+            Offset(6.057380152594408, 36.08562753900215),
+            Offset(6.000056479961543, 36.00008471951035),
+            Offset(6.0000000000000036, 36.00000000000001),
+          ],
+          <Offset>[
+            Offset(23.684125337090187, 6.329045030292864),
+            Offset(24.757855588602716, 6.52232881591406),
+            Offset(27.574407323047023, 7.305175651635392),
+            Offset(34.29718691345236, 10.565460438397327),
+            Offset(42.96465562689078, 18.312204872233718),
+            Offset(45.05615687408604, 26.45166424955556),
+            Offset(42.232333320435735, 35.60390079421558),
+            Offset(35.26685178627038, 42.467757059973906),
+            Offset(27.4197052006056, 45.36131120369138),
+            Offset(21.001315853482343, 45.424469500769995),
+            Offset(16.29765110355406, 44.215682562738714),
+            Offset(12.97234991047133, 42.61158062881598),
+            Offset(10.641290841160107, 41.016018618041265),
+            Offset(9.0114530912401, 39.59946991291384),
+            Offset(7.875830197056132, 38.422591589788325),
+            Offset(7.093422401327867, 37.49694905895691),
+            Offset(6.568745459991835, 36.81215692853645),
+            Offset(6.237631760921833, 36.34901917317271),
+            Offset(6.057380152594408, 36.08562753900215),
+            Offset(6.000056479961543, 36.00008471951035),
+            Offset(6.0000000000000036, 36.00000000000001),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+  ],
+);
diff --git a/lib/src/material/animated_icons/data/list_view.g.dart b/lib/src/material/animated_icons/data/list_view.g.dart
new file mode 100644
index 0000000..35814a0
--- /dev/null
+++ b/lib/src/material/animated_icons/data/list_view.g.dart
@@ -0,0 +1,3901 @@
+// Copyright 2014 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.
+
+// AUTOGENERATED FILE DO NOT EDIT!
+// This file was generated by vitool.
+part of material_animated_icons;
+const _AnimatedIconData _$list_view = _AnimatedIconData(
+  Size(48.0, 48.0),
+  <_PathFrames>[
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.878048780488,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(3.3000000000000016, 35.5),
+            Offset(3.1872491552817053, 35.294662556963694),
+            Offset(2.803584610041825, 34.55708172822131),
+            Offset(2.086762034690722, 32.974965285488736),
+            Offset(1.0562543947134673, 29.85871467131613),
+            Offset(0.3277056784121384, 23.397935591386524),
+            Offset(5.068384450186188, 9.77558673709007),
+            Offset(19.142440865906075, 0.8236301535641601),
+            Offset(28.4986244695124, 0.7512929847205392),
+            Offset(34.03339705415398, 2.550735594111994),
+            Offset(37.52630261515188, 4.563458703685585),
+            Offset(39.83951862260312, 6.397453314812415),
+            Offset(41.420680465862844, 7.960676694252207),
+            Offset(42.52292959864048, 9.247336542718527),
+            Offset(43.29857028685616, 10.277566364406328),
+            Offset(43.843675623946226, 11.078369385731442),
+            Offset(44.22049532021145, 11.67638165937833),
+            Offset(44.47040673191611, 12.096116254351111),
+            Offset(44.62108022189184, 12.359078623996243),
+            Offset(44.69110902782625, 12.484010802427473),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(3.3000000000000016, 35.5),
+            Offset(3.1872491552817053, 35.294662556963694),
+            Offset(2.803584610041825, 34.55708172822131),
+            Offset(2.086762034690722, 32.974965285488736),
+            Offset(1.0562543947134673, 29.85871467131613),
+            Offset(0.3277056784121384, 23.397935591386524),
+            Offset(5.068384450186188, 9.77558673709007),
+            Offset(19.142440865906075, 0.8236301535641601),
+            Offset(28.4986244695124, 0.7512929847205392),
+            Offset(34.03339705415398, 2.550735594111994),
+            Offset(37.52630261515188, 4.563458703685585),
+            Offset(39.83951862260312, 6.397453314812415),
+            Offset(41.420680465862844, 7.960676694252207),
+            Offset(42.52292959864048, 9.247336542718527),
+            Offset(43.29857028685616, 10.277566364406328),
+            Offset(43.843675623946226, 11.078369385731442),
+            Offset(44.22049532021145, 11.67638165937833),
+            Offset(44.47040673191611, 12.096116254351111),
+            Offset(44.62108022189184, 12.359078623996243),
+            Offset(44.69110902782625, 12.484010802427473),
+          ],
+          <Offset>[
+            Offset(7.900000000000001, 35.5),
+            Offset(7.787024068249312, 35.34016805150932),
+            Offset(7.398927009112204, 34.7640315658779),
+            Offset(6.654572753845953, 33.51820117713304),
+            Offset(5.505071070263646, 31.02834289744042),
+            Offset(4.290730713340881, 25.733408257149932),
+            Offset(6.941261311200804, 13.977054607189284),
+            Offset(17.78085941259789, 5.217500423156466),
+            Offset(25.541432860040533, 4.274788093964937),
+            Offset(30.30609713874217, 5.246516620191658),
+            Offset(33.39574733179454, 6.587937167668043),
+            Offset(35.489171433369805, 7.892270806391144),
+            Offset(36.94930799941936, 9.040874947505337),
+            Offset(37.985765755731585, 10.005059620498354),
+            Offset(38.72688536686231, 10.787171105652785),
+            Offset(39.254973101552665, 11.400563676461365),
+            Offset(39.62422079973953, 11.861477668143195),
+            Offset(39.87129202785027, 12.186360028903175),
+            Offset(40.02118722421174, 12.390453931063687),
+            Offset(40.091110400688535, 12.487564720144858),
+          ],
+          <Offset>[
+            Offset(7.900000000000001, 35.5),
+            Offset(7.787024068249312, 35.34016805150932),
+            Offset(7.398927009112204, 34.7640315658779),
+            Offset(6.654572753845953, 33.51820117713304),
+            Offset(5.505071070263646, 31.02834289744042),
+            Offset(4.290730713340881, 25.733408257149932),
+            Offset(6.941261311200804, 13.977054607189284),
+            Offset(17.78085941259789, 5.217500423156466),
+            Offset(25.541432860040533, 4.274788093964937),
+            Offset(30.30609713874217, 5.246516620191658),
+            Offset(33.39574733179454, 6.587937167668043),
+            Offset(35.489171433369805, 7.892270806391144),
+            Offset(36.94930799941936, 9.040874947505337),
+            Offset(37.985765755731585, 10.005059620498354),
+            Offset(38.72688536686231, 10.787171105652785),
+            Offset(39.254973101552665, 11.400563676461365),
+            Offset(39.62422079973953, 11.861477668143195),
+            Offset(39.87129202785027, 12.186360028903175),
+            Offset(40.02118722421174, 12.390453931063687),
+            Offset(40.091110400688535, 12.487564720144858),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(7.900000000000001, 35.5),
+            Offset(7.787024068249312, 35.34016805150932),
+            Offset(7.398927009112204, 34.7640315658779),
+            Offset(6.654572753845953, 33.51820117713304),
+            Offset(5.505071070263646, 31.02834289744042),
+            Offset(4.290730713340881, 25.733408257149932),
+            Offset(6.941261311200804, 13.977054607189284),
+            Offset(17.78085941259789, 5.217500423156466),
+            Offset(25.541432860040533, 4.274788093964937),
+            Offset(30.30609713874217, 5.246516620191658),
+            Offset(33.39574733179454, 6.587937167668043),
+            Offset(35.489171433369805, 7.892270806391144),
+            Offset(36.94930799941936, 9.040874947505337),
+            Offset(37.985765755731585, 10.005059620498354),
+            Offset(38.72688536686231, 10.787171105652785),
+            Offset(39.254973101552665, 11.400563676461365),
+            Offset(39.62422079973953, 11.861477668143195),
+            Offset(39.87129202785027, 12.186360028903175),
+            Offset(40.02118722421174, 12.390453931063687),
+            Offset(40.091110400688535, 12.487564720144858),
+          ],
+          <Offset>[
+            Offset(7.900000000000001, 30.900000000000002),
+            Offset(7.832529562794939, 30.740393138541712),
+            Offset(7.605876846768791, 30.168689166807518),
+            Offset(7.19780864549025, 28.950390457977804),
+            Offset(6.674699296387938, 26.57952622189024),
+            Offset(6.626203379104288, 21.770383222221188),
+            Offset(11.14272918130002, 12.104177746174667),
+            Offset(22.174729682190197, 6.579081876464652),
+            Offset(29.06492796928493, 7.231979703436803),
+            Offset(33.00187816482183, 8.973816535603465),
+            Offset(35.420225795777, 10.718492451025382),
+            Offset(36.983988924948534, 12.242617995624466),
+            Offset(38.02950625267249, 13.51224741394882),
+            Offset(38.743488833511414, 14.542223463407256),
+            Offset(39.23649010810877, 15.358856025646629),
+            Offset(39.57716739228259, 15.989266198854928),
+            Offset(39.8093168085044, 16.457752188615107),
+            Offset(39.96153580240233, 16.785474732969014),
+            Offset(40.052562531279186, 16.990346928743786),
+            Offset(40.094664318405925, 17.087563347282572),
+          ],
+          <Offset>[
+            Offset(7.900000000000001, 30.900000000000002),
+            Offset(7.832529562794939, 30.740393138541712),
+            Offset(7.605876846768791, 30.168689166807518),
+            Offset(7.19780864549025, 28.950390457977804),
+            Offset(6.674699296387938, 26.57952622189024),
+            Offset(6.626203379104288, 21.770383222221188),
+            Offset(11.14272918130002, 12.104177746174667),
+            Offset(22.174729682190197, 6.579081876464652),
+            Offset(29.06492796928493, 7.231979703436803),
+            Offset(33.00187816482183, 8.973816535603465),
+            Offset(35.420225795777, 10.718492451025382),
+            Offset(36.983988924948534, 12.242617995624466),
+            Offset(38.02950625267249, 13.51224741394882),
+            Offset(38.743488833511414, 14.542223463407256),
+            Offset(39.23649010810877, 15.358856025646629),
+            Offset(39.57716739228259, 15.989266198854928),
+            Offset(39.8093168085044, 16.457752188615107),
+            Offset(39.96153580240233, 16.785474732969014),
+            Offset(40.052562531279186, 16.990346928743786),
+            Offset(40.094664318405925, 17.087563347282572),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(7.900000000000001, 30.900000000000002),
+            Offset(7.832529562794939, 30.740393138541712),
+            Offset(7.605876846768791, 30.168689166807518),
+            Offset(7.19780864549025, 28.950390457977804),
+            Offset(6.674699296387938, 26.57952622189024),
+            Offset(6.626203379104288, 21.770383222221188),
+            Offset(11.14272918130002, 12.104177746174667),
+            Offset(22.174729682190197, 6.579081876464652),
+            Offset(29.06492796928493, 7.231979703436803),
+            Offset(33.00187816482183, 8.973816535603465),
+            Offset(35.420225795777, 10.718492451025382),
+            Offset(36.983988924948534, 12.242617995624466),
+            Offset(38.02950625267249, 13.51224741394882),
+            Offset(38.743488833511414, 14.542223463407256),
+            Offset(39.23649010810877, 15.358856025646629),
+            Offset(39.57716739228259, 15.989266198854928),
+            Offset(39.8093168085044, 16.457752188615107),
+            Offset(39.96153580240233, 16.785474732969014),
+            Offset(40.052562531279186, 16.990346928743786),
+            Offset(40.094664318405925, 17.087563347282572),
+          ],
+          <Offset>[
+            Offset(3.3000000000000016, 30.900000000000002),
+            Offset(3.2327546498273323, 30.694887643996086),
+            Offset(3.0105344476984124, 29.96173932915093),
+            Offset(2.6299979263350193, 28.407154566333507),
+            Offset(2.2258826208377593, 25.40989799576595),
+            Offset(2.663178344175545, 19.43491055645778),
+            Offset(9.269852320285402, 7.902709876075453),
+            Offset(23.53631113549838, 2.185211606872347),
+            Offset(32.0221195787568, 3.708484594192405),
+            Offset(36.729178080233645, 6.278035509523802),
+            Offset(39.55078107913434, 8.694013987042924),
+            Offset(41.33433611418185, 10.747800504045737),
+            Offset(42.500878719115974, 12.43204916069569),
+            Offset(43.28065267642031, 13.784500385627428),
+            Offset(43.80817502810262, 14.849251284400172),
+            Offset(44.16586991467615, 15.667071908125004),
+            Offset(44.405591328976314, 16.272656179850244),
+            Offset(44.56065050646817, 16.695230958416946),
+            Offset(44.65245552895929, 16.95897162167634),
+            Offset(44.69466294554364, 17.084009429565185),
+          ],
+          <Offset>[
+            Offset(3.3000000000000016, 30.900000000000002),
+            Offset(3.2327546498273323, 30.694887643996086),
+            Offset(3.0105344476984124, 29.96173932915093),
+            Offset(2.6299979263350193, 28.407154566333507),
+            Offset(2.2258826208377593, 25.40989799576595),
+            Offset(2.663178344175545, 19.43491055645778),
+            Offset(9.269852320285402, 7.902709876075453),
+            Offset(23.53631113549838, 2.185211606872347),
+            Offset(32.0221195787568, 3.708484594192405),
+            Offset(36.729178080233645, 6.278035509523802),
+            Offset(39.55078107913434, 8.694013987042924),
+            Offset(41.33433611418185, 10.747800504045737),
+            Offset(42.500878719115974, 12.43204916069569),
+            Offset(43.28065267642031, 13.784500385627428),
+            Offset(43.80817502810262, 14.849251284400172),
+            Offset(44.16586991467615, 15.667071908125004),
+            Offset(44.405591328976314, 16.272656179850244),
+            Offset(44.56065050646817, 16.695230958416946),
+            Offset(44.65245552895929, 16.95897162167634),
+            Offset(44.69466294554364, 17.084009429565185),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(3.3000000000000016, 30.900000000000002),
+            Offset(3.2327546498273323, 30.694887643996086),
+            Offset(3.0105344476984124, 29.96173932915093),
+            Offset(2.6299979263350193, 28.407154566333507),
+            Offset(2.2258826208377593, 25.40989799576595),
+            Offset(2.663178344175545, 19.43491055645778),
+            Offset(9.269852320285402, 7.902709876075453),
+            Offset(23.53631113549838, 2.185211606872347),
+            Offset(32.0221195787568, 3.708484594192405),
+            Offset(36.729178080233645, 6.278035509523802),
+            Offset(39.55078107913434, 8.694013987042924),
+            Offset(41.33433611418185, 10.747800504045737),
+            Offset(42.500878719115974, 12.43204916069569),
+            Offset(43.28065267642031, 13.784500385627428),
+            Offset(43.80817502810262, 14.849251284400172),
+            Offset(44.16586991467615, 15.667071908125004),
+            Offset(44.405591328976314, 16.272656179850244),
+            Offset(44.56065050646817, 16.695230958416946),
+            Offset(44.65245552895929, 16.95897162167634),
+            Offset(44.69466294554364, 17.084009429565185),
+          ],
+          <Offset>[
+            Offset(3.3000000000000016, 35.5),
+            Offset(3.1872491552817053, 35.294662556963694),
+            Offset(2.803584610041825, 34.55708172822131),
+            Offset(2.086762034690722, 32.974965285488736),
+            Offset(1.0562543947134673, 29.85871467131613),
+            Offset(0.3277056784121384, 23.397935591386524),
+            Offset(5.068384450186188, 9.77558673709007),
+            Offset(19.142440865906075, 0.8236301535641601),
+            Offset(28.4986244695124, 0.7512929847205392),
+            Offset(34.03339705415398, 2.550735594111994),
+            Offset(37.52630261515188, 4.563458703685585),
+            Offset(39.83951862260312, 6.397453314812415),
+            Offset(41.420680465862844, 7.960676694252207),
+            Offset(42.52292959864048, 9.247336542718527),
+            Offset(43.29857028685616, 10.277566364406328),
+            Offset(43.843675623946226, 11.078369385731442),
+            Offset(44.22049532021145, 11.67638165937833),
+            Offset(44.47040673191611, 12.096116254351111),
+            Offset(44.62108022189184, 12.359078623996243),
+            Offset(44.69110902782625, 12.484010802427473),
+          ],
+          <Offset>[
+            Offset(3.3000000000000016, 35.5),
+            Offset(3.1872491552817053, 35.294662556963694),
+            Offset(2.803584610041825, 34.55708172822131),
+            Offset(2.086762034690722, 32.974965285488736),
+            Offset(1.0562543947134673, 29.85871467131613),
+            Offset(0.3277056784121384, 23.397935591386524),
+            Offset(5.068384450186188, 9.77558673709007),
+            Offset(19.142440865906075, 0.8236301535641601),
+            Offset(28.4986244695124, 0.7512929847205392),
+            Offset(34.03339705415398, 2.550735594111994),
+            Offset(37.52630261515188, 4.563458703685585),
+            Offset(39.83951862260312, 6.397453314812415),
+            Offset(41.420680465862844, 7.960676694252207),
+            Offset(42.52292959864048, 9.247336542718527),
+            Offset(43.29857028685616, 10.277566364406328),
+            Offset(43.843675623946226, 11.078369385731442),
+            Offset(44.22049532021145, 11.67638165937833),
+            Offset(44.47040673191611, 12.096116254351111),
+            Offset(44.62108022189184, 12.359078623996243),
+            Offset(44.69110902782625, 12.484010802427473),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.878048780488,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(3.3000000000000016, 17.1),
+            Offset(3.3692711334642134, 16.89556290509327),
+            Offset(3.631383960668174, 16.17571213193979),
+            Offset(4.25970560126791, 14.703722408867815),
+            Offset(5.734767299210637, 12.063447969115414),
+            Offset(9.669596341465768, 7.545835451671554),
+            Offset(21.874255930583047, 2.2840792930316054),
+            Offset(36.7179219442753, 6.269955966796905),
+            Offset(42.592604906489996, 12.580059422608004),
+            Offset(44.81652115847264, 17.459935255759223),
+            Offset(45.624216471081716, 21.085679837114938),
+            Offset(45.81878858891804, 23.798842071745703),
+            Offset(45.74147347887537, 25.84616656002614),
+            Offset(45.55382190975979, 27.395991914354134),
+            Offset(45.33698925184199, 28.564306044381702),
+            Offset(45.13245278686591, 29.433179475305685),
+            Offset(44.960879355270905, 30.061479741265988),
+            Offset(44.831381830124364, 30.492575070614457),
+            Offset(44.74658145016162, 30.758650614716647),
+            Offset(44.7053246986958, 30.884005310978335),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(3.3000000000000016, 17.1),
+            Offset(3.3692711334642134, 16.89556290509327),
+            Offset(3.631383960668174, 16.17571213193979),
+            Offset(4.25970560126791, 14.703722408867815),
+            Offset(5.734767299210637, 12.063447969115414),
+            Offset(9.669596341465768, 7.545835451671554),
+            Offset(21.874255930583047, 2.2840792930316054),
+            Offset(36.7179219442753, 6.269955966796905),
+            Offset(42.592604906489996, 12.580059422608004),
+            Offset(44.81652115847264, 17.459935255759223),
+            Offset(45.624216471081716, 21.085679837114938),
+            Offset(45.81878858891804, 23.798842071745703),
+            Offset(45.74147347887537, 25.84616656002614),
+            Offset(45.55382190975979, 27.395991914354134),
+            Offset(45.33698925184199, 28.564306044381702),
+            Offset(45.13245278686591, 29.433179475305685),
+            Offset(44.960879355270905, 30.061479741265988),
+            Offset(44.831381830124364, 30.492575070614457),
+            Offset(44.74658145016162, 30.758650614716647),
+            Offset(44.7053246986958, 30.884005310978335),
+          ],
+          <Offset>[
+            Offset(7.900000000000001, 17.1),
+            Offset(7.96904604643182, 16.941068399638898),
+            Offset(8.226726359738553, 16.382661969596377),
+            Offset(8.82751632042314, 15.246958300512112),
+            Offset(10.183583974760815, 13.233076195239708),
+            Offset(13.63262137639451, 9.88130811743496),
+            Offset(23.747132791597664, 6.48554716313082),
+            Offset(35.35634049096711, 10.66382623638921),
+            Offset(39.63541329701813, 16.103554531852403),
+            Offset(41.08922124306083, 20.155716281838888),
+            Offset(41.49366118772438, 23.1101583010974),
+            Offset(41.46844139968472, 25.293659563324432),
+            Offset(41.27010101243189, 26.92636481327927),
+            Offset(41.016658066850894, 28.15371499213396),
+            Offset(40.76530433184814, 29.07391078562816),
+            Offset(40.54375026447235, 29.75537376603561),
+            Offset(40.36460483479899, 30.24657575003085),
+            Offset(40.232267126058524, 30.582818845166525),
+            Offset(40.14668845248152, 30.790025921784093),
+            Offset(40.10532607155808, 30.88755922869572),
+          ],
+          <Offset>[
+            Offset(7.900000000000001, 17.1),
+            Offset(7.96904604643182, 16.941068399638898),
+            Offset(8.226726359738553, 16.382661969596377),
+            Offset(8.82751632042314, 15.246958300512112),
+            Offset(10.183583974760815, 13.233076195239708),
+            Offset(13.63262137639451, 9.88130811743496),
+            Offset(23.747132791597664, 6.48554716313082),
+            Offset(35.35634049096711, 10.66382623638921),
+            Offset(39.63541329701813, 16.103554531852403),
+            Offset(41.08922124306083, 20.155716281838888),
+            Offset(41.49366118772438, 23.1101583010974),
+            Offset(41.46844139968472, 25.293659563324432),
+            Offset(41.27010101243189, 26.92636481327927),
+            Offset(41.016658066850894, 28.15371499213396),
+            Offset(40.76530433184814, 29.07391078562816),
+            Offset(40.54375026447235, 29.75537376603561),
+            Offset(40.36460483479899, 30.24657575003085),
+            Offset(40.232267126058524, 30.582818845166525),
+            Offset(40.14668845248152, 30.790025921784093),
+            Offset(40.10532607155808, 30.88755922869572),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(7.900000000000001, 17.1),
+            Offset(7.96904604643182, 16.941068399638898),
+            Offset(8.226726359738553, 16.382661969596377),
+            Offset(8.82751632042314, 15.246958300512112),
+            Offset(10.183583974760815, 13.233076195239708),
+            Offset(13.63262137639451, 9.88130811743496),
+            Offset(23.747132791597664, 6.48554716313082),
+            Offset(35.35634049096711, 10.66382623638921),
+            Offset(39.63541329701813, 16.103554531852403),
+            Offset(41.08922124306083, 20.155716281838888),
+            Offset(41.49366118772438, 23.1101583010974),
+            Offset(41.46844139968472, 25.293659563324432),
+            Offset(41.27010101243189, 26.92636481327927),
+            Offset(41.016658066850894, 28.15371499213396),
+            Offset(40.76530433184814, 29.07391078562816),
+            Offset(40.54375026447235, 29.75537376603561),
+            Offset(40.36460483479899, 30.24657575003085),
+            Offset(40.232267126058524, 30.582818845166525),
+            Offset(40.14668845248152, 30.790025921784093),
+            Offset(40.10532607155808, 30.88755922869572),
+          ],
+          <Offset>[
+            Offset(7.900000000000001, 12.5),
+            Offset(8.014551540977447, 12.34129348667129),
+            Offset(8.43367619739514, 11.787319570526),
+            Offset(9.370752212067437, 10.679147581356883),
+            Offset(11.353212200885107, 8.78425951968953),
+            Offset(15.968094042157917, 5.918283082506218),
+            Offset(27.94860066169688, 4.6126703021162045),
+            Offset(39.75021076055942, 12.025407689697397),
+            Offset(43.158908406262526, 19.060746141324266),
+            Offset(43.78500226914049, 23.883016197250694),
+            Offset(43.51813965170684, 27.24071358445474),
+            Offset(42.96325889126345, 29.644006752557758),
+            Offset(42.35029926568502, 31.397737279722755),
+            Offset(41.77438114463072, 32.69087883504286),
+            Offset(41.2749090730946, 33.64559570562201),
+            Offset(40.865944555202276, 34.34407628842917),
+            Offset(40.549700843563855, 34.842850270502765),
+            Offset(40.32251090061059, 35.18193354923236),
+            Offset(40.17806375954897, 35.38991891946419),
+            Offset(40.10887998927547, 35.48755785583344),
+          ],
+          <Offset>[
+            Offset(7.900000000000001, 12.5),
+            Offset(8.014551540977447, 12.34129348667129),
+            Offset(8.43367619739514, 11.787319570526),
+            Offset(9.370752212067437, 10.679147581356883),
+            Offset(11.353212200885107, 8.78425951968953),
+            Offset(15.968094042157917, 5.918283082506218),
+            Offset(27.94860066169688, 4.6126703021162045),
+            Offset(39.75021076055942, 12.025407689697397),
+            Offset(43.158908406262526, 19.060746141324266),
+            Offset(43.78500226914049, 23.883016197250694),
+            Offset(43.51813965170684, 27.24071358445474),
+            Offset(42.96325889126345, 29.644006752557758),
+            Offset(42.35029926568502, 31.397737279722755),
+            Offset(41.77438114463072, 32.69087883504286),
+            Offset(41.2749090730946, 33.64559570562201),
+            Offset(40.865944555202276, 34.34407628842917),
+            Offset(40.549700843563855, 34.842850270502765),
+            Offset(40.32251090061059, 35.18193354923236),
+            Offset(40.17806375954897, 35.38991891946419),
+            Offset(40.10887998927547, 35.48755785583344),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(7.900000000000001, 12.5),
+            Offset(8.014551540977447, 12.34129348667129),
+            Offset(8.43367619739514, 11.787319570526),
+            Offset(9.370752212067437, 10.679147581356883),
+            Offset(11.353212200885107, 8.78425951968953),
+            Offset(15.968094042157917, 5.918283082506218),
+            Offset(27.94860066169688, 4.6126703021162045),
+            Offset(39.75021076055942, 12.025407689697397),
+            Offset(43.158908406262526, 19.060746141324266),
+            Offset(43.78500226914049, 23.883016197250694),
+            Offset(43.51813965170684, 27.24071358445474),
+            Offset(42.96325889126345, 29.644006752557758),
+            Offset(42.35029926568502, 31.397737279722755),
+            Offset(41.77438114463072, 32.69087883504286),
+            Offset(41.2749090730946, 33.64559570562201),
+            Offset(40.865944555202276, 34.34407628842917),
+            Offset(40.549700843563855, 34.842850270502765),
+            Offset(40.32251090061059, 35.18193354923236),
+            Offset(40.17806375954897, 35.38991891946419),
+            Offset(40.10887998927547, 35.48755785583344),
+          ],
+          <Offset>[
+            Offset(3.3000000000000016, 12.5),
+            Offset(3.4147766280098404, 12.295787992125664),
+            Offset(3.8383337983247614, 11.580369732869412),
+            Offset(4.802941492912208, 10.135911689712586),
+            Offset(6.904395525334929, 7.614631293565236),
+            Offset(12.005069007229174, 3.5828104167428108),
+            Offset(26.075723800682262, 0.4112024320169896),
+            Offset(41.1117922138676, 7.631537420105092),
+            Offset(46.11610001573439, 15.537251032079869),
+            Offset(47.512302184552304, 21.18723517117103),
+            Offset(47.64869493506418, 25.216235120472277),
+            Offset(47.31360608049677, 28.14918926097903),
+            Offset(46.8216717321285, 30.317539026469625),
+            Offset(46.31154498753962, 31.933155757263034),
+            Offset(45.84659399308845, 33.13599096437555),
+            Offset(45.45464707759584, 34.021881997699246),
+            Offset(45.14597536403577, 34.6577542617379),
+            Offset(44.92162560467643, 35.0916897746803),
+            Offset(44.77795675722907, 35.358543612396744),
+            Offset(44.70887861641319, 35.48400393811605),
+          ],
+          <Offset>[
+            Offset(3.3000000000000016, 12.5),
+            Offset(3.4147766280098404, 12.295787992125664),
+            Offset(3.8383337983247614, 11.580369732869412),
+            Offset(4.802941492912208, 10.135911689712586),
+            Offset(6.904395525334929, 7.614631293565236),
+            Offset(12.005069007229174, 3.5828104167428108),
+            Offset(26.075723800682262, 0.4112024320169896),
+            Offset(41.1117922138676, 7.631537420105092),
+            Offset(46.11610001573439, 15.537251032079869),
+            Offset(47.512302184552304, 21.18723517117103),
+            Offset(47.64869493506418, 25.216235120472277),
+            Offset(47.31360608049677, 28.14918926097903),
+            Offset(46.8216717321285, 30.317539026469625),
+            Offset(46.31154498753962, 31.933155757263034),
+            Offset(45.84659399308845, 33.13599096437555),
+            Offset(45.45464707759584, 34.021881997699246),
+            Offset(45.14597536403577, 34.6577542617379),
+            Offset(44.92162560467643, 35.0916897746803),
+            Offset(44.77795675722907, 35.358543612396744),
+            Offset(44.70887861641319, 35.48400393811605),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(3.3000000000000016, 12.5),
+            Offset(3.4147766280098404, 12.295787992125664),
+            Offset(3.8383337983247614, 11.580369732869412),
+            Offset(4.802941492912208, 10.135911689712586),
+            Offset(6.904395525334929, 7.614631293565236),
+            Offset(12.005069007229174, 3.5828104167428108),
+            Offset(26.075723800682262, 0.4112024320169896),
+            Offset(41.1117922138676, 7.631537420105092),
+            Offset(46.11610001573439, 15.537251032079869),
+            Offset(47.512302184552304, 21.18723517117103),
+            Offset(47.64869493506418, 25.216235120472277),
+            Offset(47.31360608049677, 28.14918926097903),
+            Offset(46.8216717321285, 30.317539026469625),
+            Offset(46.31154498753962, 31.933155757263034),
+            Offset(45.84659399308845, 33.13599096437555),
+            Offset(45.45464707759584, 34.021881997699246),
+            Offset(45.14597536403577, 34.6577542617379),
+            Offset(44.92162560467643, 35.0916897746803),
+            Offset(44.77795675722907, 35.358543612396744),
+            Offset(44.70887861641319, 35.48400393811605),
+          ],
+          <Offset>[
+            Offset(3.3000000000000016, 17.1),
+            Offset(3.3692711334642134, 16.89556290509327),
+            Offset(3.631383960668174, 16.17571213193979),
+            Offset(4.25970560126791, 14.703722408867815),
+            Offset(5.734767299210637, 12.063447969115414),
+            Offset(9.669596341465768, 7.545835451671554),
+            Offset(21.874255930583047, 2.2840792930316054),
+            Offset(36.7179219442753, 6.269955966796905),
+            Offset(42.592604906489996, 12.580059422608004),
+            Offset(44.81652115847264, 17.459935255759223),
+            Offset(45.624216471081716, 21.085679837114938),
+            Offset(45.81878858891804, 23.798842071745703),
+            Offset(45.74147347887537, 25.84616656002614),
+            Offset(45.55382190975979, 27.395991914354134),
+            Offset(45.33698925184199, 28.564306044381702),
+            Offset(45.13245278686591, 29.433179475305685),
+            Offset(44.960879355270905, 30.061479741265988),
+            Offset(44.831381830124364, 30.492575070614457),
+            Offset(44.74658145016162, 30.758650614716647),
+            Offset(44.7053246986958, 30.884005310978335),
+          ],
+          <Offset>[
+            Offset(3.3000000000000016, 17.1),
+            Offset(3.3692711334642134, 16.89556290509327),
+            Offset(3.631383960668174, 16.17571213193979),
+            Offset(4.25970560126791, 14.703722408867815),
+            Offset(5.734767299210637, 12.063447969115414),
+            Offset(9.669596341465768, 7.545835451671554),
+            Offset(21.874255930583047, 2.2840792930316054),
+            Offset(36.7179219442753, 6.269955966796905),
+            Offset(42.592604906489996, 12.580059422608004),
+            Offset(44.81652115847264, 17.459935255759223),
+            Offset(45.624216471081716, 21.085679837114938),
+            Offset(45.81878858891804, 23.798842071745703),
+            Offset(45.74147347887537, 25.84616656002614),
+            Offset(45.55382190975979, 27.395991914354134),
+            Offset(45.33698925184199, 28.564306044381702),
+            Offset(45.13245278686591, 29.433179475305685),
+            Offset(44.960879355270905, 30.061479741265988),
+            Offset(44.831381830124364, 30.492575070614457),
+            Offset(44.74658145016162, 30.758650614716647),
+            Offset(44.7053246986958, 30.884005310978335),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.878048780488,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(3.3000000000000016, 26.3),
+            Offset(3.2782601443729593, 26.095112731028483),
+            Offset(3.217484285354999, 25.366396930080548),
+            Offset(3.1732338179793156, 23.839343847178277),
+            Offset(3.3955108469620523, 20.961081320215772),
+            Offset(4.998651009938952, 15.471885521529039),
+            Offset(13.471320190384617, 6.029833015060841),
+            Offset(27.930181405090686, 3.5467930601805335),
+            Offset(35.545614688001194, 6.665676203664274),
+            Offset(39.42495910631331, 10.005335424935609),
+            Offset(41.57525954311679, 12.824569270400263),
+            Offset(42.82915360576058, 15.098147693279058),
+            Offset(43.58107697236911, 16.903421627139174),
+            Offset(44.03837575420013, 18.32166422853633),
+            Offset(44.31777976934908, 19.420936204394014),
+            Offset(44.48806420540607, 20.25577443051856),
+            Offset(44.59068733774117, 20.86893070032216),
+            Offset(44.650894281020236, 21.294345662482787),
+            Offset(44.68383083602673, 21.558864619356445),
+            Offset(44.698216863261024, 21.684008056702904),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(3.3000000000000016, 26.3),
+            Offset(3.2782601443729593, 26.095112731028483),
+            Offset(3.217484285354999, 25.366396930080548),
+            Offset(3.1732338179793156, 23.839343847178277),
+            Offset(3.3955108469620523, 20.961081320215772),
+            Offset(4.998651009938952, 15.471885521529039),
+            Offset(13.471320190384617, 6.029833015060841),
+            Offset(27.930181405090686, 3.5467930601805335),
+            Offset(35.545614688001194, 6.665676203664274),
+            Offset(39.42495910631331, 10.005335424935609),
+            Offset(41.57525954311679, 12.824569270400263),
+            Offset(42.82915360576058, 15.098147693279058),
+            Offset(43.58107697236911, 16.903421627139174),
+            Offset(44.03837575420013, 18.32166422853633),
+            Offset(44.31777976934908, 19.420936204394014),
+            Offset(44.48806420540607, 20.25577443051856),
+            Offset(44.59068733774117, 20.86893070032216),
+            Offset(44.650894281020236, 21.294345662482787),
+            Offset(44.68383083602673, 21.558864619356445),
+            Offset(44.698216863261024, 21.684008056702904),
+          ],
+          <Offset>[
+            Offset(7.900000000000001, 26.3),
+            Offset(7.878035057340566, 26.14061822557411),
+            Offset(7.812826684425378, 25.573346767737135),
+            Offset(7.741044537134545, 24.382579738822574),
+            Offset(7.844327522512231, 22.130709546340064),
+            Offset(8.961676044867694, 17.807358187292447),
+            Offset(15.344197051399235, 10.231300885160056),
+            Offset(26.568599951782502, 7.940663329772839),
+            Offset(32.58842307852933, 10.189171312908673),
+            Offset(35.6976591909015, 12.701116451015272),
+            Offset(37.444704259759455, 14.84904773438272),
+            Offset(38.47880641652726, 16.592965184857785),
+            Offset(39.10970450592563, 17.983619880392304),
+            Offset(39.501211911291236, 19.079387306316157),
+            Offset(39.74609484935523, 19.930540945640473),
+            Offset(39.89936168301251, 20.577968721248485),
+            Offset(39.99441281726926, 21.054026709087022),
+            Offset(40.051779576954395, 21.384589437034855),
+            Offset(40.08393783834663, 21.59023992642389),
+            Offset(40.09821823612331, 21.68756197442029),
+          ],
+          <Offset>[
+            Offset(7.900000000000001, 26.3),
+            Offset(7.878035057340566, 26.14061822557411),
+            Offset(7.812826684425378, 25.573346767737135),
+            Offset(7.741044537134545, 24.382579738822574),
+            Offset(7.844327522512231, 22.130709546340064),
+            Offset(8.961676044867694, 17.807358187292447),
+            Offset(15.344197051399235, 10.231300885160056),
+            Offset(26.568599951782502, 7.940663329772839),
+            Offset(32.58842307852933, 10.189171312908673),
+            Offset(35.6976591909015, 12.701116451015272),
+            Offset(37.444704259759455, 14.84904773438272),
+            Offset(38.47880641652726, 16.592965184857785),
+            Offset(39.10970450592563, 17.983619880392304),
+            Offset(39.501211911291236, 19.079387306316157),
+            Offset(39.74609484935523, 19.930540945640473),
+            Offset(39.89936168301251, 20.577968721248485),
+            Offset(39.99441281726926, 21.054026709087022),
+            Offset(40.051779576954395, 21.384589437034855),
+            Offset(40.08393783834663, 21.59023992642389),
+            Offset(40.09821823612331, 21.68756197442029),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(7.900000000000001, 26.3),
+            Offset(7.878035057340566, 26.14061822557411),
+            Offset(7.812826684425378, 25.573346767737135),
+            Offset(7.741044537134545, 24.382579738822574),
+            Offset(7.844327522512231, 22.130709546340064),
+            Offset(8.961676044867694, 17.807358187292447),
+            Offset(15.344197051399235, 10.231300885160056),
+            Offset(26.568599951782502, 7.940663329772839),
+            Offset(32.58842307852933, 10.189171312908673),
+            Offset(35.6976591909015, 12.701116451015272),
+            Offset(37.444704259759455, 14.84904773438272),
+            Offset(38.47880641652726, 16.592965184857785),
+            Offset(39.10970450592563, 17.983619880392304),
+            Offset(39.501211911291236, 19.079387306316157),
+            Offset(39.74609484935523, 19.930540945640473),
+            Offset(39.89936168301251, 20.577968721248485),
+            Offset(39.99441281726926, 21.054026709087022),
+            Offset(40.051779576954395, 21.384589437034855),
+            Offset(40.08393783834663, 21.59023992642389),
+            Offset(40.09821823612331, 21.68756197442029),
+          ],
+          <Offset>[
+            Offset(7.900000000000001, 21.7),
+            Offset(7.923540551886193, 21.5408433126065),
+            Offset(8.019776522081965, 20.97800436866676),
+            Offset(8.284280428778843, 19.814769019667345),
+            Offset(9.013955748636523, 17.681892870789884),
+            Offset(11.2971487106311, 13.844333152363703),
+            Offset(19.54566492149845, 8.358424024145439),
+            Offset(30.962470221374808, 9.302244783081026),
+            Offset(36.111918187773725, 13.146362922380538),
+            Offset(38.39344021698116, 16.42841636642708),
+            Offset(39.469182723741916, 18.97960301774006),
+            Offset(39.97362390810599, 20.94331237409111),
+            Offset(40.18990275917876, 22.454992346835787),
+            Offset(40.258934989071065, 23.616551149225057),
+            Offset(40.25569959060169, 24.50222586563432),
+            Offset(40.22155597374243, 25.166671243642046),
+            Offset(40.17950882603412, 25.650301229558934),
+            Offset(40.14202335150646, 25.98370414110069),
+            Offset(40.11531314541408, 26.190132924103988),
+            Offset(40.1017721538407, 26.287560601558003),
+          ],
+          <Offset>[
+            Offset(7.900000000000001, 21.7),
+            Offset(7.923540551886193, 21.5408433126065),
+            Offset(8.019776522081965, 20.97800436866676),
+            Offset(8.284280428778843, 19.814769019667345),
+            Offset(9.013955748636523, 17.681892870789884),
+            Offset(11.2971487106311, 13.844333152363703),
+            Offset(19.54566492149845, 8.358424024145439),
+            Offset(30.962470221374808, 9.302244783081026),
+            Offset(36.111918187773725, 13.146362922380538),
+            Offset(38.39344021698116, 16.42841636642708),
+            Offset(39.469182723741916, 18.97960301774006),
+            Offset(39.97362390810599, 20.94331237409111),
+            Offset(40.18990275917876, 22.454992346835787),
+            Offset(40.258934989071065, 23.616551149225057),
+            Offset(40.25569959060169, 24.50222586563432),
+            Offset(40.22155597374243, 25.166671243642046),
+            Offset(40.17950882603412, 25.650301229558934),
+            Offset(40.14202335150646, 25.98370414110069),
+            Offset(40.11531314541408, 26.190132924103988),
+            Offset(40.1017721538407, 26.287560601558003),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(7.900000000000001, 21.7),
+            Offset(7.923540551886193, 21.5408433126065),
+            Offset(8.019776522081965, 20.97800436866676),
+            Offset(8.284280428778843, 19.814769019667345),
+            Offset(9.013955748636523, 17.681892870789884),
+            Offset(11.2971487106311, 13.844333152363703),
+            Offset(19.54566492149845, 8.358424024145439),
+            Offset(30.962470221374808, 9.302244783081026),
+            Offset(36.111918187773725, 13.146362922380538),
+            Offset(38.39344021698116, 16.42841636642708),
+            Offset(39.469182723741916, 18.97960301774006),
+            Offset(39.97362390810599, 20.94331237409111),
+            Offset(40.18990275917876, 22.454992346835787),
+            Offset(40.258934989071065, 23.616551149225057),
+            Offset(40.25569959060169, 24.50222586563432),
+            Offset(40.22155597374243, 25.166671243642046),
+            Offset(40.17950882603412, 25.650301229558934),
+            Offset(40.14202335150646, 25.98370414110069),
+            Offset(40.11531314541408, 26.190132924103988),
+            Offset(40.1017721538407, 26.287560601558003),
+          ],
+          <Offset>[
+            Offset(3.3000000000000016, 21.7),
+            Offset(3.3237656389185863, 21.495337818060875),
+            Offset(3.4244341230115865, 20.771054531010172),
+            Offset(3.716469709623613, 19.271533128023048),
+            Offset(4.565139073086344, 16.512264644665592),
+            Offset(7.334123675702358, 11.508860486600296),
+            Offset(17.672788060483832, 4.156956154046225),
+            Offset(32.32405167468299, 4.90837451348872),
+            Offset(39.06910979724559, 9.62286781313614),
+            Offset(42.120740132392974, 13.732635340347416),
+            Offset(43.599738007099255, 16.9551245537576),
+            Offset(44.32397109733931, 19.44849488251238),
+            Offset(44.66127522562224, 21.374794093582658),
+            Offset(44.79609883197996, 22.85882807144523),
+            Offset(44.827384510595536, 23.99262112438786),
+            Offset(44.810258496135994, 24.84447695291212),
+            Offset(44.77578334650604, 25.465205220794072),
+            Offset(44.7411380555723, 25.893460366548624),
+            Offset(44.71520614309418, 26.158757617036542),
+            Offset(44.701770780978414, 26.284006683840616),
+          ],
+          <Offset>[
+            Offset(3.3000000000000016, 21.7),
+            Offset(3.3237656389185863, 21.495337818060875),
+            Offset(3.4244341230115865, 20.771054531010172),
+            Offset(3.716469709623613, 19.271533128023048),
+            Offset(4.565139073086344, 16.512264644665592),
+            Offset(7.334123675702358, 11.508860486600296),
+            Offset(17.672788060483832, 4.156956154046225),
+            Offset(32.32405167468299, 4.90837451348872),
+            Offset(39.06910979724559, 9.62286781313614),
+            Offset(42.120740132392974, 13.732635340347416),
+            Offset(43.599738007099255, 16.9551245537576),
+            Offset(44.32397109733931, 19.44849488251238),
+            Offset(44.66127522562224, 21.374794093582658),
+            Offset(44.79609883197996, 22.85882807144523),
+            Offset(44.827384510595536, 23.99262112438786),
+            Offset(44.810258496135994, 24.84447695291212),
+            Offset(44.77578334650604, 25.465205220794072),
+            Offset(44.7411380555723, 25.893460366548624),
+            Offset(44.71520614309418, 26.158757617036542),
+            Offset(44.701770780978414, 26.284006683840616),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(3.3000000000000016, 21.7),
+            Offset(3.3237656389185863, 21.495337818060875),
+            Offset(3.4244341230115865, 20.771054531010172),
+            Offset(3.716469709623613, 19.271533128023048),
+            Offset(4.565139073086344, 16.512264644665592),
+            Offset(7.334123675702358, 11.508860486600296),
+            Offset(17.672788060483832, 4.156956154046225),
+            Offset(32.32405167468299, 4.90837451348872),
+            Offset(39.06910979724559, 9.62286781313614),
+            Offset(42.120740132392974, 13.732635340347416),
+            Offset(43.599738007099255, 16.9551245537576),
+            Offset(44.32397109733931, 19.44849488251238),
+            Offset(44.66127522562224, 21.374794093582658),
+            Offset(44.79609883197996, 22.85882807144523),
+            Offset(44.827384510595536, 23.99262112438786),
+            Offset(44.810258496135994, 24.84447695291212),
+            Offset(44.77578334650604, 25.465205220794072),
+            Offset(44.7411380555723, 25.893460366548624),
+            Offset(44.71520614309418, 26.158757617036542),
+            Offset(44.701770780978414, 26.284006683840616),
+          ],
+          <Offset>[
+            Offset(3.3000000000000016, 26.3),
+            Offset(3.2782601443729593, 26.095112731028483),
+            Offset(3.217484285354999, 25.366396930080548),
+            Offset(3.1732338179793156, 23.839343847178277),
+            Offset(3.3955108469620523, 20.961081320215772),
+            Offset(4.998651009938952, 15.471885521529039),
+            Offset(13.471320190384617, 6.029833015060841),
+            Offset(27.930181405090686, 3.5467930601805335),
+            Offset(35.545614688001194, 6.665676203664274),
+            Offset(39.42495910631331, 10.005335424935609),
+            Offset(41.57525954311679, 12.824569270400263),
+            Offset(42.82915360576058, 15.098147693279058),
+            Offset(43.58107697236911, 16.903421627139174),
+            Offset(44.03837575420013, 18.32166422853633),
+            Offset(44.31777976934908, 19.420936204394014),
+            Offset(44.48806420540607, 20.25577443051856),
+            Offset(44.59068733774117, 20.86893070032216),
+            Offset(44.650894281020236, 21.294345662482787),
+            Offset(44.68383083602673, 21.558864619356445),
+            Offset(44.698216863261024, 21.684008056702904),
+          ],
+          <Offset>[
+            Offset(3.3000000000000016, 26.3),
+            Offset(3.2782601443729593, 26.095112731028483),
+            Offset(3.217484285354999, 25.366396930080548),
+            Offset(3.1732338179793156, 23.839343847178277),
+            Offset(3.3955108469620523, 20.961081320215772),
+            Offset(4.998651009938952, 15.471885521529039),
+            Offset(13.471320190384617, 6.029833015060841),
+            Offset(27.930181405090686, 3.5467930601805335),
+            Offset(35.545614688001194, 6.665676203664274),
+            Offset(39.42495910631331, 10.005335424935609),
+            Offset(41.57525954311679, 12.824569270400263),
+            Offset(42.82915360576058, 15.098147693279058),
+            Offset(43.58107697236911, 16.903421627139174),
+            Offset(44.03837575420013, 18.32166422853633),
+            Offset(44.31777976934908, 19.420936204394014),
+            Offset(44.48806420540607, 20.25577443051856),
+            Offset(44.59068733774117, 20.86893070032216),
+            Offset(44.650894281020236, 21.294345662482787),
+            Offset(44.68383083602673, 21.558864619356445),
+            Offset(44.698216863261024, 21.684008056702904),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.878048780488,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(12.5, 35.5),
+            Offset(12.386798981216916, 35.38567354605495),
+            Offset(11.994269408182586, 34.97098140353448),
+            Offset(11.222383473001184, 34.06143706877733),
+            Offset(9.953887745813823, 32.19797112356471),
+            Offset(8.253755748269624, 28.068880922913333),
+            Offset(8.81413817221542, 18.1785224772885),
+            Offset(16.419277959289705, 9.611370692748771),
+            Offset(22.584241250568667, 7.798283203209337),
+            Offset(26.57879722333036, 7.942297646271321),
+            Offset(29.2651920484372, 8.612415631650505),
+            Offset(31.13882424413648, 9.387088297969871),
+            Offset(32.47793553297588, 10.121073200758469),
+            Offset(33.44860191282268, 10.762782698278183),
+            Offset(34.15520044686847, 11.296775846899239),
+            Offset(34.666270579159104, 11.72275796719129),
+            Offset(35.027946279267624, 12.046573676908062),
+            Offset(35.27217732378443, 12.276603803455243),
+            Offset(35.42129422653164, 12.421829238131133),
+            Offset(35.49111177355083, 12.491118637862245),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.5, 35.5),
+            Offset(12.386798981216916, 35.38567354605495),
+            Offset(11.994269408182586, 34.97098140353448),
+            Offset(11.222383473001184, 34.06143706877733),
+            Offset(9.953887745813823, 32.19797112356471),
+            Offset(8.253755748269624, 28.068880922913333),
+            Offset(8.81413817221542, 18.1785224772885),
+            Offset(16.419277959289705, 9.611370692748771),
+            Offset(22.584241250568667, 7.798283203209337),
+            Offset(26.57879722333036, 7.942297646271321),
+            Offset(29.2651920484372, 8.612415631650505),
+            Offset(31.13882424413648, 9.387088297969871),
+            Offset(32.47793553297588, 10.121073200758469),
+            Offset(33.44860191282268, 10.762782698278183),
+            Offset(34.15520044686847, 11.296775846899239),
+            Offset(34.666270579159104, 11.72275796719129),
+            Offset(35.027946279267624, 12.046573676908062),
+            Offset(35.27217732378443, 12.276603803455243),
+            Offset(35.42129422653164, 12.421829238131133),
+            Offset(35.49111177355083, 12.491118637862245),
+          ],
+          <Offset>[
+            Offset(44.699999999999996, 35.5),
+            Offset(44.58522337199017, 35.70421200787435),
+            Offset(44.161666201675246, 36.41963026713059),
+            Offset(43.197058507087796, 37.86408831028741),
+            Offset(41.09560447466507, 40.38536870643476),
+            Offset(35.994930992770826, 44.41718958325718),
+            Offset(21.92427619931773, 47.58879756798301),
+            Offset(6.8882077861324005, 40.36846257989491),
+            Offset(1.8838999842656072, 32.46274896792012),
+            Offset(0.4876978154477065, 26.81276482882897),
+            Offset(0.3513050649358309, 22.783764879527716),
+            Offset(0.6863939195032227, 19.85081073902098),
+            Offset(1.1783282678714926, 17.682460973530382),
+            Offset(1.6884550124603805, 16.066844242736977),
+            Offset(2.1534060069115526, 14.864009035624443),
+            Offset(2.5453529224041667, 13.978118002300755),
+            Offset(2.854024635964233, 13.34224573826211),
+            Offset(3.0783743953235785, 12.9083102253197),
+            Offset(3.222043242770944, 12.641456387603249),
+            Offset(3.2911213835868267, 12.515996061883946),
+          ],
+          <Offset>[
+            Offset(44.699999999999996, 35.5),
+            Offset(44.58522337199017, 35.70421200787435),
+            Offset(44.161666201675246, 36.41963026713059),
+            Offset(43.197058507087796, 37.86408831028741),
+            Offset(41.09560447466507, 40.38536870643476),
+            Offset(35.994930992770826, 44.41718958325718),
+            Offset(21.92427619931773, 47.58879756798301),
+            Offset(6.8882077861324005, 40.36846257989491),
+            Offset(1.8838999842656072, 32.46274896792012),
+            Offset(0.4876978154477065, 26.81276482882897),
+            Offset(0.3513050649358309, 22.783764879527716),
+            Offset(0.6863939195032227, 19.85081073902098),
+            Offset(1.1783282678714926, 17.682460973530382),
+            Offset(1.6884550124603805, 16.066844242736977),
+            Offset(2.1534060069115526, 14.864009035624443),
+            Offset(2.5453529224041667, 13.978118002300755),
+            Offset(2.854024635964233, 13.34224573826211),
+            Offset(3.0783743953235785, 12.9083102253197),
+            Offset(3.222043242770944, 12.641456387603249),
+            Offset(3.2911213835868267, 12.515996061883946),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(44.699999999999996, 35.5),
+            Offset(44.58522337199017, 35.70421200787435),
+            Offset(44.161666201675246, 36.41963026713059),
+            Offset(43.197058507087796, 37.86408831028741),
+            Offset(41.09560447466507, 40.38536870643476),
+            Offset(35.994930992770826, 44.41718958325718),
+            Offset(21.92427619931773, 47.58879756798301),
+            Offset(6.8882077861324005, 40.36846257989491),
+            Offset(1.8838999842656072, 32.46274896792012),
+            Offset(0.4876978154477065, 26.81276482882897),
+            Offset(0.3513050649358309, 22.783764879527716),
+            Offset(0.6863939195032227, 19.85081073902098),
+            Offset(1.1783282678714926, 17.682460973530382),
+            Offset(1.6884550124603805, 16.066844242736977),
+            Offset(2.1534060069115526, 14.864009035624443),
+            Offset(2.5453529224041667, 13.978118002300755),
+            Offset(2.854024635964233, 13.34224573826211),
+            Offset(3.0783743953235785, 12.9083102253197),
+            Offset(3.222043242770944, 12.641456387603249),
+            Offset(3.2911213835868267, 12.515996061883946),
+          ],
+          <Offset>[
+            Offset(44.699999999999996, 30.900000000000002),
+            Offset(44.63072886653579, 31.104437094906736),
+            Offset(44.368616039331826, 31.82428786806021),
+            Offset(43.74029439873209, 33.296277591132174),
+            Offset(42.26523270078937, 35.936552030884584),
+            Offset(38.33040365853423, 40.454164548328436),
+            Offset(26.125744069416946, 45.71592070696839),
+            Offset(11.282078055724707, 41.73004403320309),
+            Offset(5.407395093510004, 35.41994057739199),
+            Offset(3.1834788415273714, 30.54006474424078),
+            Offset(2.375783528918289, 26.914320162885055),
+            Offset(2.181211411081952, 24.2011579282543),
+            Offset(2.2585265211246224, 22.153833439973866),
+            Offset(2.446178090240206, 20.604008085645876),
+            Offset(2.66301074815801, 19.43569395561829),
+            Offset(2.8675472131340918, 18.566820524694318),
+            Offset(3.0391206447290973, 17.938520258734023),
+            Offset(3.168618169875643, 17.507424929385536),
+            Offset(3.2534185498383863, 17.24134938528335),
+            Offset(3.29467530130421, 17.11599468902166),
+          ],
+          <Offset>[
+            Offset(44.699999999999996, 30.900000000000002),
+            Offset(44.63072886653579, 31.104437094906736),
+            Offset(44.368616039331826, 31.82428786806021),
+            Offset(43.74029439873209, 33.296277591132174),
+            Offset(42.26523270078937, 35.936552030884584),
+            Offset(38.33040365853423, 40.454164548328436),
+            Offset(26.125744069416946, 45.71592070696839),
+            Offset(11.282078055724707, 41.73004403320309),
+            Offset(5.407395093510004, 35.41994057739199),
+            Offset(3.1834788415273714, 30.54006474424078),
+            Offset(2.375783528918289, 26.914320162885055),
+            Offset(2.181211411081952, 24.2011579282543),
+            Offset(2.2585265211246224, 22.153833439973866),
+            Offset(2.446178090240206, 20.604008085645876),
+            Offset(2.66301074815801, 19.43569395561829),
+            Offset(2.8675472131340918, 18.566820524694318),
+            Offset(3.0391206447290973, 17.938520258734023),
+            Offset(3.168618169875643, 17.507424929385536),
+            Offset(3.2534185498383863, 17.24134938528335),
+            Offset(3.29467530130421, 17.11599468902166),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(44.699999999999996, 30.900000000000002),
+            Offset(44.63072886653579, 31.104437094906736),
+            Offset(44.368616039331826, 31.82428786806021),
+            Offset(43.74029439873209, 33.296277591132174),
+            Offset(42.26523270078937, 35.936552030884584),
+            Offset(38.33040365853423, 40.454164548328436),
+            Offset(26.125744069416946, 45.71592070696839),
+            Offset(11.282078055724707, 41.73004403320309),
+            Offset(5.407395093510004, 35.41994057739199),
+            Offset(3.1834788415273714, 30.54006474424078),
+            Offset(2.375783528918289, 26.914320162885055),
+            Offset(2.181211411081952, 24.2011579282543),
+            Offset(2.2585265211246224, 22.153833439973866),
+            Offset(2.446178090240206, 20.604008085645876),
+            Offset(2.66301074815801, 19.43569395561829),
+            Offset(2.8675472131340918, 18.566820524694318),
+            Offset(3.0391206447290973, 17.938520258734023),
+            Offset(3.168618169875643, 17.507424929385536),
+            Offset(3.2534185498383863, 17.24134938528335),
+            Offset(3.29467530130421, 17.11599468902166),
+          ],
+          <Offset>[
+            Offset(12.5, 30.900000000000002),
+            Offset(12.432304475762546, 30.785898633087346),
+            Offset(12.201219245839173, 30.3756390044641),
+            Offset(11.765619364645481, 29.493626349622097),
+            Offset(11.123515971938117, 27.749154448014536),
+            Offset(10.589228414033032, 24.10585588798459),
+            Offset(13.015606042314635, 16.305645616273882),
+            Offset(20.81314822888201, 10.972952146056956),
+            Offset(26.107736359813064, 10.755474812681204),
+            Offset(29.274578249410023, 11.66959756168313),
+            Offset(31.28967051241966, 12.742970915007843),
+            Offset(32.63364173571521, 13.737435487203195),
+            Offset(33.55813378622901, 14.592445667201952),
+            Offset(34.20632499060251, 15.299946541187085),
+            Offset(34.664805188114926, 15.868460766893085),
+            Offset(34.98846486988903, 16.311460489584853),
+            Offset(35.21304228803248, 16.642848197379976),
+            Offset(35.3624210983365, 16.87571850752108),
+            Offset(35.452669533599085, 17.021722235811232),
+            Offset(35.49466569126821, 17.09111726499996),
+          ],
+          <Offset>[
+            Offset(12.5, 30.900000000000002),
+            Offset(12.432304475762546, 30.785898633087346),
+            Offset(12.201219245839173, 30.3756390044641),
+            Offset(11.765619364645481, 29.493626349622097),
+            Offset(11.123515971938117, 27.749154448014536),
+            Offset(10.589228414033032, 24.10585588798459),
+            Offset(13.015606042314635, 16.305645616273882),
+            Offset(20.81314822888201, 10.972952146056956),
+            Offset(26.107736359813064, 10.755474812681204),
+            Offset(29.274578249410023, 11.66959756168313),
+            Offset(31.28967051241966, 12.742970915007843),
+            Offset(32.63364173571521, 13.737435487203195),
+            Offset(33.55813378622901, 14.592445667201952),
+            Offset(34.20632499060251, 15.299946541187085),
+            Offset(34.664805188114926, 15.868460766893085),
+            Offset(34.98846486988903, 16.311460489584853),
+            Offset(35.21304228803248, 16.642848197379976),
+            Offset(35.3624210983365, 16.87571850752108),
+            Offset(35.452669533599085, 17.021722235811232),
+            Offset(35.49466569126821, 17.09111726499996),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.5, 30.900000000000002),
+            Offset(12.432304475762546, 30.785898633087346),
+            Offset(12.201219245839173, 30.3756390044641),
+            Offset(11.765619364645481, 29.493626349622097),
+            Offset(11.123515971938117, 27.749154448014536),
+            Offset(10.589228414033032, 24.10585588798459),
+            Offset(13.015606042314635, 16.305645616273882),
+            Offset(20.81314822888201, 10.972952146056956),
+            Offset(26.107736359813064, 10.755474812681204),
+            Offset(29.274578249410023, 11.66959756168313),
+            Offset(31.28967051241966, 12.742970915007843),
+            Offset(32.63364173571521, 13.737435487203195),
+            Offset(33.55813378622901, 14.592445667201952),
+            Offset(34.20632499060251, 15.299946541187085),
+            Offset(34.664805188114926, 15.868460766893085),
+            Offset(34.98846486988903, 16.311460489584853),
+            Offset(35.21304228803248, 16.642848197379976),
+            Offset(35.3624210983365, 16.87571850752108),
+            Offset(35.452669533599085, 17.021722235811232),
+            Offset(35.49466569126821, 17.09111726499996),
+          ],
+          <Offset>[
+            Offset(12.5, 35.5),
+            Offset(12.386798981216916, 35.38567354605495),
+            Offset(11.994269408182586, 34.97098140353448),
+            Offset(11.222383473001184, 34.06143706877733),
+            Offset(9.953887745813823, 32.19797112356471),
+            Offset(8.253755748269624, 28.068880922913333),
+            Offset(8.81413817221542, 18.1785224772885),
+            Offset(16.419277959289705, 9.611370692748771),
+            Offset(22.584241250568667, 7.798283203209337),
+            Offset(26.57879722333036, 7.942297646271321),
+            Offset(29.2651920484372, 8.612415631650505),
+            Offset(31.13882424413648, 9.387088297969871),
+            Offset(32.47793553297588, 10.121073200758469),
+            Offset(33.44860191282268, 10.762782698278183),
+            Offset(34.15520044686847, 11.296775846899239),
+            Offset(34.666270579159104, 11.72275796719129),
+            Offset(35.027946279267624, 12.046573676908062),
+            Offset(35.27217732378443, 12.276603803455243),
+            Offset(35.42129422653164, 12.421829238131133),
+            Offset(35.49111177355083, 12.491118637862245),
+          ],
+          <Offset>[
+            Offset(12.5, 35.5),
+            Offset(12.386798981216916, 35.38567354605495),
+            Offset(11.994269408182586, 34.97098140353448),
+            Offset(11.222383473001184, 34.06143706877733),
+            Offset(9.953887745813823, 32.19797112356471),
+            Offset(8.253755748269624, 28.068880922913333),
+            Offset(8.81413817221542, 18.1785224772885),
+            Offset(16.419277959289705, 9.611370692748771),
+            Offset(22.584241250568667, 7.798283203209337),
+            Offset(26.57879722333036, 7.942297646271321),
+            Offset(29.2651920484372, 8.612415631650505),
+            Offset(31.13882424413648, 9.387088297969871),
+            Offset(32.47793553297588, 10.121073200758469),
+            Offset(33.44860191282268, 10.762782698278183),
+            Offset(34.15520044686847, 11.296775846899239),
+            Offset(34.666270579159104, 11.72275796719129),
+            Offset(35.027946279267624, 12.046573676908062),
+            Offset(35.27217732378443, 12.276603803455243),
+            Offset(35.42129422653164, 12.421829238131133),
+            Offset(35.49111177355083, 12.491118637862245),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.878048780488,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(12.5, 17.1),
+            Offset(12.568820959399424, 16.986573894184524),
+            Offset(12.822068758808932, 16.589611807252965),
+            Offset(13.395327039578369, 15.790194192156408),
+            Offset(14.632400650310995, 14.402704421364),
+            Offset(17.59564641132325, 12.216780783198363),
+            Offset(25.620009652612282, 10.687015033230036),
+            Offset(33.99475903765893, 15.057696505981518),
+            Offset(36.67822168754626, 19.6270496410968),
+            Offset(37.361921327649014, 22.851497307918553),
+            Offset(37.36310590436704, 25.13463676507986),
+            Offset(37.118094210451396, 26.788477054903165),
+            Offset(36.7987285459884, 28.0065630665324),
+            Offset(36.479494223942, 28.91143806991379),
+            Offset(36.1936194118543, 29.58351552687462),
+            Offset(35.95504774207879, 30.077568056765532),
+            Offset(35.76833031432708, 30.43167175879571),
+            Offset(35.63315242199269, 30.673062619718593),
+            Offset(35.54679545480142, 30.821401228851528),
+            Offset(35.50532744442037, 30.891113146413097),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.5, 17.1),
+            Offset(12.568820959399424, 16.986573894184524),
+            Offset(12.822068758808932, 16.589611807252965),
+            Offset(13.395327039578369, 15.790194192156408),
+            Offset(14.632400650310995, 14.402704421364),
+            Offset(17.59564641132325, 12.216780783198363),
+            Offset(25.620009652612282, 10.687015033230036),
+            Offset(33.99475903765893, 15.057696505981518),
+            Offset(36.67822168754626, 19.6270496410968),
+            Offset(37.361921327649014, 22.851497307918553),
+            Offset(37.36310590436704, 25.13463676507986),
+            Offset(37.118094210451396, 26.788477054903165),
+            Offset(36.7987285459884, 28.0065630665324),
+            Offset(36.479494223942, 28.91143806991379),
+            Offset(36.1936194118543, 29.58351552687462),
+            Offset(35.95504774207879, 30.077568056765532),
+            Offset(35.76833031432708, 30.43167175879571),
+            Offset(35.63315242199269, 30.673062619718593),
+            Offset(35.54679545480142, 30.821401228851528),
+            Offset(35.50532744442037, 30.891113146413097),
+          ],
+          <Offset>[
+            Offset(44.699999999999996, 17.1),
+            Offset(44.76724535017267, 17.305112356003914),
+            Offset(44.98946555230159, 18.038260670849073),
+            Offset(45.37000207366498, 19.592845433666486),
+            Offset(45.77411737916225, 22.590102004234048),
+            Offset(45.33682165582445, 28.565089443542213),
+            Offset(38.7301476797146, 40.09729012392454),
+            Offset(24.463688864501623, 45.81478839312766),
+            Offset(15.977880421243203, 44.29151540580759),
+            Offset(11.270821919766366, 41.7219644904762),
+            Offset(8.44921892086567, 39.30598601295708),
+            Offset(6.665663885818139, 37.25219949595427),
+            Offset(5.499121280884019, 35.56795083930431),
+            Offset(4.71934732357969, 34.21549961437258),
+            Offset(4.191824971897383, 33.150748715599825),
+            Offset(3.83413008532386, 32.332928091875),
+            Offset(3.5944086710236895, 31.727343820149756),
+            Offset(3.439349493531836, 31.30476904158305),
+            Offset(3.3475444710407203, 31.041028378323645),
+            Offset(3.3053370544563663, 30.9159905704348),
+          ],
+          <Offset>[
+            Offset(44.699999999999996, 17.1),
+            Offset(44.76724535017267, 17.305112356003914),
+            Offset(44.98946555230159, 18.038260670849073),
+            Offset(45.37000207366498, 19.592845433666486),
+            Offset(45.77411737916225, 22.590102004234048),
+            Offset(45.33682165582445, 28.565089443542213),
+            Offset(38.7301476797146, 40.09729012392454),
+            Offset(24.463688864501623, 45.81478839312766),
+            Offset(15.977880421243203, 44.29151540580759),
+            Offset(11.270821919766366, 41.7219644904762),
+            Offset(8.44921892086567, 39.30598601295708),
+            Offset(6.665663885818139, 37.25219949595427),
+            Offset(5.499121280884019, 35.56795083930431),
+            Offset(4.71934732357969, 34.21549961437258),
+            Offset(4.191824971897383, 33.150748715599825),
+            Offset(3.83413008532386, 32.332928091875),
+            Offset(3.5944086710236895, 31.727343820149756),
+            Offset(3.439349493531836, 31.30476904158305),
+            Offset(3.3475444710407203, 31.041028378323645),
+            Offset(3.3053370544563663, 30.9159905704348),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(44.699999999999996, 17.1),
+            Offset(44.76724535017267, 17.305112356003914),
+            Offset(44.98946555230159, 18.038260670849073),
+            Offset(45.37000207366498, 19.592845433666486),
+            Offset(45.77411737916225, 22.590102004234048),
+            Offset(45.33682165582445, 28.565089443542213),
+            Offset(38.7301476797146, 40.09729012392454),
+            Offset(24.463688864501623, 45.81478839312766),
+            Offset(15.977880421243203, 44.29151540580759),
+            Offset(11.270821919766366, 41.7219644904762),
+            Offset(8.44921892086567, 39.30598601295708),
+            Offset(6.665663885818139, 37.25219949595427),
+            Offset(5.499121280884019, 35.56795083930431),
+            Offset(4.71934732357969, 34.21549961437258),
+            Offset(4.191824971897383, 33.150748715599825),
+            Offset(3.83413008532386, 32.332928091875),
+            Offset(3.5944086710236895, 31.727343820149756),
+            Offset(3.439349493531836, 31.30476904158305),
+            Offset(3.3475444710407203, 31.041028378323645),
+            Offset(3.3053370544563663, 30.9159905704348),
+          ],
+          <Offset>[
+            Offset(44.699999999999996, 12.5),
+            Offset(44.8127508447183, 12.705337443036306),
+            Offset(45.196415389958176, 13.442918271778694),
+            Offset(45.91323796530928, 15.025034714511255),
+            Offset(46.94374560528654, 18.14128532868387),
+            Offset(47.67229432158786, 24.60206440861347),
+            Offset(42.93161554981381, 38.22441326290992),
+            Offset(28.85755913409393, 47.17636984643584),
+            Offset(19.5013755304876, 47.24870701527945),
+            Offset(13.96660294584603, 45.449264405888016),
+            Offset(10.473697384848128, 43.43654129631442),
+            Offset(8.160481377396868, 41.60254668518759),
+            Offset(6.579319534137149, 40.03932330574779),
+            Offset(5.477070401359516, 38.75266345728148),
+            Offset(4.70142971314384, 37.722433635593674),
+            Offset(4.156324376053785, 36.92163061426856),
+            Offset(3.7795046797885536, 36.32361834062167),
+            Offset(3.5295932680839, 35.90388374564888),
+            Offset(3.3789197781081626, 35.640921376003746),
+            Offset(3.3088909721737494, 35.515989197572516),
+          ],
+          <Offset>[
+            Offset(44.699999999999996, 12.5),
+            Offset(44.8127508447183, 12.705337443036306),
+            Offset(45.196415389958176, 13.442918271778694),
+            Offset(45.91323796530928, 15.025034714511255),
+            Offset(46.94374560528654, 18.14128532868387),
+            Offset(47.67229432158786, 24.60206440861347),
+            Offset(42.93161554981381, 38.22441326290992),
+            Offset(28.85755913409393, 47.17636984643584),
+            Offset(19.5013755304876, 47.24870701527945),
+            Offset(13.96660294584603, 45.449264405888016),
+            Offset(10.473697384848128, 43.43654129631442),
+            Offset(8.160481377396868, 41.60254668518759),
+            Offset(6.579319534137149, 40.03932330574779),
+            Offset(5.477070401359516, 38.75266345728148),
+            Offset(4.70142971314384, 37.722433635593674),
+            Offset(4.156324376053785, 36.92163061426856),
+            Offset(3.7795046797885536, 36.32361834062167),
+            Offset(3.5295932680839, 35.90388374564888),
+            Offset(3.3789197781081626, 35.640921376003746),
+            Offset(3.3088909721737494, 35.515989197572516),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(44.699999999999996, 12.5),
+            Offset(44.8127508447183, 12.705337443036306),
+            Offset(45.196415389958176, 13.442918271778694),
+            Offset(45.91323796530928, 15.025034714511255),
+            Offset(46.94374560528654, 18.14128532868387),
+            Offset(47.67229432158786, 24.60206440861347),
+            Offset(42.93161554981381, 38.22441326290992),
+            Offset(28.85755913409393, 47.17636984643584),
+            Offset(19.5013755304876, 47.24870701527945),
+            Offset(13.96660294584603, 45.449264405888016),
+            Offset(10.473697384848128, 43.43654129631442),
+            Offset(8.160481377396868, 41.60254668518759),
+            Offset(6.579319534137149, 40.03932330574779),
+            Offset(5.477070401359516, 38.75266345728148),
+            Offset(4.70142971314384, 37.722433635593674),
+            Offset(4.156324376053785, 36.92163061426856),
+            Offset(3.7795046797885536, 36.32361834062167),
+            Offset(3.5295932680839, 35.90388374564888),
+            Offset(3.3789197781081626, 35.640921376003746),
+            Offset(3.3088909721737494, 35.515989197572516),
+          ],
+          <Offset>[
+            Offset(12.5, 12.5),
+            Offset(12.614326453945054, 12.386798981216916),
+            Offset(13.02901859646552, 11.994269408182586),
+            Offset(13.938562931222666, 11.222383473001177),
+            Offset(15.802028876435289, 9.953887745813823),
+            Offset(19.931119077086663, 8.25375574826962),
+            Offset(29.821477522711497, 8.814138172215419),
+            Offset(38.388629307251236, 16.4192779592897),
+            Offset(40.20171679679066, 22.584241250568667),
+            Offset(40.057702353728686, 26.578797223330362),
+            Offset(39.3875843683495, 29.2651920484372),
+            Offset(38.612911702030125, 31.138824244136487),
+            Offset(37.87892679924153, 32.477935532975884),
+            Offset(37.23721730172182, 33.44860191282269),
+            Offset(36.703224153100756, 34.15520044686847),
+            Offset(36.277242032808715, 34.6662705791591),
+            Offset(35.953426323091946, 35.027946279267624),
+            Offset(35.723396196544755, 35.272177323784426),
+            Offset(35.57817076186886, 35.42129422653163),
+            Offset(35.50888136213775, 35.49111177355081),
+          ],
+          <Offset>[
+            Offset(12.5, 12.5),
+            Offset(12.614326453945054, 12.386798981216916),
+            Offset(13.02901859646552, 11.994269408182586),
+            Offset(13.938562931222666, 11.222383473001177),
+            Offset(15.802028876435289, 9.953887745813823),
+            Offset(19.931119077086663, 8.25375574826962),
+            Offset(29.821477522711497, 8.814138172215419),
+            Offset(38.388629307251236, 16.4192779592897),
+            Offset(40.20171679679066, 22.584241250568667),
+            Offset(40.057702353728686, 26.578797223330362),
+            Offset(39.3875843683495, 29.2651920484372),
+            Offset(38.612911702030125, 31.138824244136487),
+            Offset(37.87892679924153, 32.477935532975884),
+            Offset(37.23721730172182, 33.44860191282269),
+            Offset(36.703224153100756, 34.15520044686847),
+            Offset(36.277242032808715, 34.6662705791591),
+            Offset(35.953426323091946, 35.027946279267624),
+            Offset(35.723396196544755, 35.272177323784426),
+            Offset(35.57817076186886, 35.42129422653163),
+            Offset(35.50888136213775, 35.49111177355081),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.5, 12.5),
+            Offset(12.614326453945054, 12.386798981216916),
+            Offset(13.02901859646552, 11.994269408182586),
+            Offset(13.938562931222666, 11.222383473001177),
+            Offset(15.802028876435289, 9.953887745813823),
+            Offset(19.931119077086663, 8.25375574826962),
+            Offset(29.821477522711497, 8.814138172215419),
+            Offset(38.388629307251236, 16.4192779592897),
+            Offset(40.20171679679066, 22.584241250568667),
+            Offset(40.057702353728686, 26.578797223330362),
+            Offset(39.3875843683495, 29.2651920484372),
+            Offset(38.612911702030125, 31.138824244136487),
+            Offset(37.87892679924153, 32.477935532975884),
+            Offset(37.23721730172182, 33.44860191282269),
+            Offset(36.703224153100756, 34.15520044686847),
+            Offset(36.277242032808715, 34.6662705791591),
+            Offset(35.953426323091946, 35.027946279267624),
+            Offset(35.723396196544755, 35.272177323784426),
+            Offset(35.57817076186886, 35.42129422653163),
+            Offset(35.50888136213775, 35.49111177355081),
+          ],
+          <Offset>[
+            Offset(12.5, 17.1),
+            Offset(12.568820959399424, 16.986573894184524),
+            Offset(12.822068758808932, 16.589611807252965),
+            Offset(13.395327039578369, 15.790194192156408),
+            Offset(14.632400650310995, 14.402704421364),
+            Offset(17.59564641132325, 12.216780783198363),
+            Offset(25.620009652612282, 10.687015033230036),
+            Offset(33.99475903765893, 15.057696505981518),
+            Offset(36.67822168754626, 19.6270496410968),
+            Offset(37.361921327649014, 22.851497307918553),
+            Offset(37.36310590436704, 25.13463676507986),
+            Offset(37.118094210451396, 26.788477054903165),
+            Offset(36.7987285459884, 28.0065630665324),
+            Offset(36.479494223942, 28.91143806991379),
+            Offset(36.1936194118543, 29.58351552687462),
+            Offset(35.95504774207879, 30.077568056765532),
+            Offset(35.76833031432708, 30.43167175879571),
+            Offset(35.63315242199269, 30.673062619718593),
+            Offset(35.54679545480142, 30.821401228851528),
+            Offset(35.50532744442037, 30.891113146413097),
+          ],
+          <Offset>[
+            Offset(12.5, 17.1),
+            Offset(12.568820959399424, 16.986573894184524),
+            Offset(12.822068758808932, 16.589611807252965),
+            Offset(13.395327039578369, 15.790194192156408),
+            Offset(14.632400650310995, 14.402704421364),
+            Offset(17.59564641132325, 12.216780783198363),
+            Offset(25.620009652612282, 10.687015033230036),
+            Offset(33.99475903765893, 15.057696505981518),
+            Offset(36.67822168754626, 19.6270496410968),
+            Offset(37.361921327649014, 22.851497307918553),
+            Offset(37.36310590436704, 25.13463676507986),
+            Offset(37.118094210451396, 26.788477054903165),
+            Offset(36.7987285459884, 28.0065630665324),
+            Offset(36.479494223942, 28.91143806991379),
+            Offset(36.1936194118543, 29.58351552687462),
+            Offset(35.95504774207879, 30.077568056765532),
+            Offset(35.76833031432708, 30.43167175879571),
+            Offset(35.63315242199269, 30.673062619718593),
+            Offset(35.54679545480142, 30.821401228851528),
+            Offset(35.50532744442037, 30.891113146413097),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.878048780488,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(12.5, 26.3),
+            Offset(12.477809970308172, 26.186123720119735),
+            Offset(12.40816908349576, 25.780296605393723),
+            Offset(12.308855256289778, 24.925815630466868),
+            Offset(12.293144198062407, 23.30033777246436),
+            Offset(12.924701079796437, 20.14283085305585),
+            Offset(17.217073912413852, 14.432768755259271),
+            Offset(25.207018498474316, 12.334533599365145),
+            Offset(29.631231469057468, 13.71266642215307),
+            Offset(31.970359275489688, 15.396897477094935),
+            Offset(33.314148976402116, 16.873526198365184),
+            Offset(34.12845922729394, 18.087782676436515),
+            Offset(34.638332039482144, 19.063818133645434),
+            Offset(34.96404806838234, 19.837110384095983),
+            Offset(35.17440992936138, 20.44014568688693),
+            Offset(35.31065916061894, 20.900163011978407),
+            Offset(35.398138296797356, 21.239122717851885),
+            Offset(35.45266487288856, 21.47483321158692),
+            Offset(35.484044840666535, 21.62161523349133),
+            Offset(35.4982196089856, 21.691115892137674),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.5, 26.3),
+            Offset(12.477809970308172, 26.186123720119735),
+            Offset(12.40816908349576, 25.780296605393723),
+            Offset(12.308855256289778, 24.925815630466868),
+            Offset(12.293144198062407, 23.30033777246436),
+            Offset(12.924701079796437, 20.14283085305585),
+            Offset(17.217073912413852, 14.432768755259271),
+            Offset(25.207018498474316, 12.334533599365145),
+            Offset(29.631231469057468, 13.71266642215307),
+            Offset(31.970359275489688, 15.396897477094935),
+            Offset(33.314148976402116, 16.873526198365184),
+            Offset(34.12845922729394, 18.087782676436515),
+            Offset(34.638332039482144, 19.063818133645434),
+            Offset(34.96404806838234, 19.837110384095983),
+            Offset(35.17440992936138, 20.44014568688693),
+            Offset(35.31065916061894, 20.900163011978407),
+            Offset(35.398138296797356, 21.239122717851885),
+            Offset(35.45266487288856, 21.47483321158692),
+            Offset(35.484044840666535, 21.62161523349133),
+            Offset(35.4982196089856, 21.691115892137674),
+          ],
+          <Offset>[
+            Offset(44.699999999999996, 26.3),
+            Offset(44.67623436108142, 26.504662181939125),
+            Offset(44.57556587698842, 27.22894546898983),
+            Offset(44.28353029037639, 28.72846687197695),
+            Offset(43.43486092691366, 31.487735355334408),
+            Offset(40.665876324297635, 36.4911395133997),
+            Offset(30.32721193951616, 43.84304384595377),
+            Offset(15.675948325317012, 43.09162548651128),
+            Offset(8.930890202754409, 38.37713218686386),
+            Offset(5.879259867607036, 34.267364659652586),
+            Offset(4.400261992900747, 31.044875446242393),
+            Offset(3.676028902660681, 28.551505117487622),
+            Offset(3.3387247743777593, 26.62520590641735),
+            Offset(3.2039011680200318, 25.141171928554776),
+            Offset(3.172615489404464, 24.007378875612137),
+            Offset(3.18974150386401, 23.155523047087872),
+            Offset(3.224216653493965, 22.53479477920593),
+            Offset(3.258861944427707, 22.106539633451376),
+            Offset(3.2847938569058357, 21.841242382963447),
+            Offset(3.2982292190216, 21.715993316159373),
+          ],
+          <Offset>[
+            Offset(44.699999999999996, 26.3),
+            Offset(44.67623436108142, 26.504662181939125),
+            Offset(44.57556587698842, 27.22894546898983),
+            Offset(44.28353029037639, 28.72846687197695),
+            Offset(43.43486092691366, 31.487735355334408),
+            Offset(40.665876324297635, 36.4911395133997),
+            Offset(30.32721193951616, 43.84304384595377),
+            Offset(15.675948325317012, 43.09162548651128),
+            Offset(8.930890202754409, 38.37713218686386),
+            Offset(5.879259867607036, 34.267364659652586),
+            Offset(4.400261992900747, 31.044875446242393),
+            Offset(3.676028902660681, 28.551505117487622),
+            Offset(3.3387247743777593, 26.62520590641735),
+            Offset(3.2039011680200318, 25.141171928554776),
+            Offset(3.172615489404464, 24.007378875612137),
+            Offset(3.18974150386401, 23.155523047087872),
+            Offset(3.224216653493965, 22.53479477920593),
+            Offset(3.258861944427707, 22.106539633451376),
+            Offset(3.2847938569058357, 21.841242382963447),
+            Offset(3.2982292190216, 21.715993316159373),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(44.699999999999996, 26.3),
+            Offset(44.67623436108142, 26.504662181939125),
+            Offset(44.57556587698842, 27.22894546898983),
+            Offset(44.28353029037639, 28.72846687197695),
+            Offset(43.43486092691366, 31.487735355334408),
+            Offset(40.665876324297635, 36.4911395133997),
+            Offset(30.32721193951616, 43.84304384595377),
+            Offset(15.675948325317012, 43.09162548651128),
+            Offset(8.930890202754409, 38.37713218686386),
+            Offset(5.879259867607036, 34.267364659652586),
+            Offset(4.400261992900747, 31.044875446242393),
+            Offset(3.676028902660681, 28.551505117487622),
+            Offset(3.3387247743777593, 26.62520590641735),
+            Offset(3.2039011680200318, 25.141171928554776),
+            Offset(3.172615489404464, 24.007378875612137),
+            Offset(3.18974150386401, 23.155523047087872),
+            Offset(3.224216653493965, 22.53479477920593),
+            Offset(3.258861944427707, 22.106539633451376),
+            Offset(3.2847938569058357, 21.841242382963447),
+            Offset(3.2982292190216, 21.715993316159373),
+          ],
+          <Offset>[
+            Offset(44.699999999999996, 21.7),
+            Offset(44.72173985562705, 21.904887268971517),
+            Offset(44.782515714645, 22.633603069919452),
+            Offset(44.82676618202069, 24.16065615282172),
+            Offset(44.60448915303795, 27.03891867978423),
+            Offset(43.00134899006105, 32.528114478470954),
+            Offset(34.528679809615376, 41.970166984939155),
+            Offset(20.069818594909318, 44.45320693981947),
+            Offset(12.454385311998806, 41.334323796335724),
+            Offset(8.575040893686701, 37.99466457506439),
+            Offset(6.424740456883205, 35.17543072959973),
+            Offset(5.17084639423941, 32.90185230672094),
+            Offset(4.418923027630889, 31.096578372860833),
+            Offset(3.9616242457998574, 29.678335771463676),
+            Offset(3.6822202306509215, 28.579063795605983),
+            Offset(3.511935794593935, 27.744225569481436),
+            Offset(3.409312662258829, 27.131069299677847),
+            Offset(3.3491057189797715, 26.70565433751721),
+            Offset(3.316169163973278, 26.441135380643548),
+            Offset(3.301783136738983, 26.315991943297085),
+          ],
+          <Offset>[
+            Offset(44.699999999999996, 21.7),
+            Offset(44.72173985562705, 21.904887268971517),
+            Offset(44.782515714645, 22.633603069919452),
+            Offset(44.82676618202069, 24.16065615282172),
+            Offset(44.60448915303795, 27.03891867978423),
+            Offset(43.00134899006105, 32.528114478470954),
+            Offset(34.528679809615376, 41.970166984939155),
+            Offset(20.069818594909318, 44.45320693981947),
+            Offset(12.454385311998806, 41.334323796335724),
+            Offset(8.575040893686701, 37.99466457506439),
+            Offset(6.424740456883205, 35.17543072959973),
+            Offset(5.17084639423941, 32.90185230672094),
+            Offset(4.418923027630889, 31.096578372860833),
+            Offset(3.9616242457998574, 29.678335771463676),
+            Offset(3.6822202306509215, 28.579063795605983),
+            Offset(3.511935794593935, 27.744225569481436),
+            Offset(3.409312662258829, 27.131069299677847),
+            Offset(3.3491057189797715, 26.70565433751721),
+            Offset(3.316169163973278, 26.441135380643548),
+            Offset(3.301783136738983, 26.315991943297085),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(44.699999999999996, 21.7),
+            Offset(44.72173985562705, 21.904887268971517),
+            Offset(44.782515714645, 22.633603069919452),
+            Offset(44.82676618202069, 24.16065615282172),
+            Offset(44.60448915303795, 27.03891867978423),
+            Offset(43.00134899006105, 32.528114478470954),
+            Offset(34.528679809615376, 41.970166984939155),
+            Offset(20.069818594909318, 44.45320693981947),
+            Offset(12.454385311998806, 41.334323796335724),
+            Offset(8.575040893686701, 37.99466457506439),
+            Offset(6.424740456883205, 35.17543072959973),
+            Offset(5.17084639423941, 32.90185230672094),
+            Offset(4.418923027630889, 31.096578372860833),
+            Offset(3.9616242457998574, 29.678335771463676),
+            Offset(3.6822202306509215, 28.579063795605983),
+            Offset(3.511935794593935, 27.744225569481436),
+            Offset(3.409312662258829, 27.131069299677847),
+            Offset(3.3491057189797715, 26.70565433751721),
+            Offset(3.316169163973278, 26.441135380643548),
+            Offset(3.301783136738983, 26.315991943297085),
+          ],
+          <Offset>[
+            Offset(12.5, 21.7),
+            Offset(12.523315464853802, 21.586348807152127),
+            Offset(12.615118921152348, 21.184954206323344),
+            Offset(12.852091147934075, 20.35800491131164),
+            Offset(13.462772424186701, 18.851521096914183),
+            Offset(15.260173745559845, 16.179805818127107),
+            Offset(21.418541782513067, 12.559891894244654),
+            Offset(29.60088876806662, 13.69611505267333),
+            Offset(33.15472657830186, 16.669858031624937),
+            Offset(34.66614030156936, 19.124197392506744),
+            Offset(35.33862744038458, 21.004081481722523),
+            Offset(35.62327671887267, 22.438129865669836),
+            Offset(35.718530292735274, 23.535190600088917),
+            Offset(35.72177114616216, 24.374274227004882),
+            Offset(35.68401467060784, 25.011830606880775),
+            Offset(35.632853451348865, 25.48886553437197),
+            Offset(35.583234305562215, 25.8353972383238),
+            Offset(35.542908647440626, 26.073947915652752),
+            Offset(35.51542014773398, 26.22150823117143),
+            Offset(35.50177352670298, 26.291114519275386),
+          ],
+          <Offset>[
+            Offset(12.5, 21.7),
+            Offset(12.523315464853802, 21.586348807152127),
+            Offset(12.615118921152348, 21.184954206323344),
+            Offset(12.852091147934075, 20.35800491131164),
+            Offset(13.462772424186701, 18.851521096914183),
+            Offset(15.260173745559845, 16.179805818127107),
+            Offset(21.418541782513067, 12.559891894244654),
+            Offset(29.60088876806662, 13.69611505267333),
+            Offset(33.15472657830186, 16.669858031624937),
+            Offset(34.66614030156936, 19.124197392506744),
+            Offset(35.33862744038458, 21.004081481722523),
+            Offset(35.62327671887267, 22.438129865669836),
+            Offset(35.718530292735274, 23.535190600088917),
+            Offset(35.72177114616216, 24.374274227004882),
+            Offset(35.68401467060784, 25.011830606880775),
+            Offset(35.632853451348865, 25.48886553437197),
+            Offset(35.583234305562215, 25.8353972383238),
+            Offset(35.542908647440626, 26.073947915652752),
+            Offset(35.51542014773398, 26.22150823117143),
+            Offset(35.50177352670298, 26.291114519275386),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.5, 21.7),
+            Offset(12.523315464853802, 21.586348807152127),
+            Offset(12.615118921152348, 21.184954206323344),
+            Offset(12.852091147934075, 20.35800491131164),
+            Offset(13.462772424186701, 18.851521096914183),
+            Offset(15.260173745559845, 16.179805818127107),
+            Offset(21.418541782513067, 12.559891894244654),
+            Offset(29.60088876806662, 13.69611505267333),
+            Offset(33.15472657830186, 16.669858031624937),
+            Offset(34.66614030156936, 19.124197392506744),
+            Offset(35.33862744038458, 21.004081481722523),
+            Offset(35.62327671887267, 22.438129865669836),
+            Offset(35.718530292735274, 23.535190600088917),
+            Offset(35.72177114616216, 24.374274227004882),
+            Offset(35.68401467060784, 25.011830606880775),
+            Offset(35.632853451348865, 25.48886553437197),
+            Offset(35.583234305562215, 25.8353972383238),
+            Offset(35.542908647440626, 26.073947915652752),
+            Offset(35.51542014773398, 26.22150823117143),
+            Offset(35.50177352670298, 26.291114519275386),
+          ],
+          <Offset>[
+            Offset(12.5, 26.3),
+            Offset(12.477809970308172, 26.186123720119735),
+            Offset(12.40816908349576, 25.780296605393723),
+            Offset(12.308855256289778, 24.925815630466868),
+            Offset(12.293144198062407, 23.30033777246436),
+            Offset(12.924701079796437, 20.14283085305585),
+            Offset(17.217073912413852, 14.432768755259271),
+            Offset(25.207018498474316, 12.334533599365145),
+            Offset(29.631231469057468, 13.71266642215307),
+            Offset(31.970359275489688, 15.396897477094935),
+            Offset(33.314148976402116, 16.873526198365184),
+            Offset(34.12845922729394, 18.087782676436515),
+            Offset(34.638332039482144, 19.063818133645434),
+            Offset(34.96404806838234, 19.837110384095983),
+            Offset(35.17440992936138, 20.44014568688693),
+            Offset(35.31065916061894, 20.900163011978407),
+            Offset(35.398138296797356, 21.239122717851885),
+            Offset(35.45266487288856, 21.47483321158692),
+            Offset(35.484044840666535, 21.62161523349133),
+            Offset(35.4982196089856, 21.691115892137674),
+          ],
+          <Offset>[
+            Offset(12.5, 26.3),
+            Offset(12.477809970308172, 26.186123720119735),
+            Offset(12.40816908349576, 25.780296605393723),
+            Offset(12.308855256289778, 24.925815630466868),
+            Offset(12.293144198062407, 23.30033777246436),
+            Offset(12.924701079796437, 20.14283085305585),
+            Offset(17.217073912413852, 14.432768755259271),
+            Offset(25.207018498474316, 12.334533599365145),
+            Offset(29.631231469057468, 13.71266642215307),
+            Offset(31.970359275489688, 15.396897477094935),
+            Offset(33.314148976402116, 16.873526198365184),
+            Offset(34.12845922729394, 18.087782676436515),
+            Offset(34.638332039482144, 19.063818133645434),
+            Offset(34.96404806838234, 19.837110384095983),
+            Offset(35.17440992936138, 20.44014568688693),
+            Offset(35.31065916061894, 20.900163011978407),
+            Offset(35.398138296797356, 21.239122717851885),
+            Offset(35.45266487288856, 21.47483321158692),
+            Offset(35.484044840666535, 21.62161523349133),
+            Offset(35.4982196089856, 21.691115892137674),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.146341463415,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(18.039538499999995, 12.930571500000006),
+            Offset(18.149334465395803, 12.872149290550801),
+            Offset(18.543577170010774, 12.67362376971493),
+            Offset(19.38848933269092, 12.304132440263823),
+            Offset(21.0500251098891, 11.778829975926628),
+            Offset(24.4849959176686, 11.43667565110878),
+            Offset(31.716380432411114, 14.006619703548635),
+            Offset(36.563833612934715, 21.53883416894973),
+            Offset(36.92848913752976, 26.678594000151676),
+            Offset(36.41588677676291, 30.008169262247456),
+            Offset(35.68873044963835, 32.36483086720799),
+            Offset(34.89395296392378, 34.0653568909929),
+            Offset(34.11750536482414, 35.28323166524228),
+            Offset(33.42442890399698, 36.15784461978932),
+            Offset(32.840093239206396, 36.785183822829495),
+            Offset(32.36951533303179, 37.23048479128569),
+            Offset(32.00877148413249, 37.538392723203316),
+            Offset(31.749801205381853, 37.7397340593929),
+            Offset(31.58332657425492, 37.85607615398918),
+            Offset(31.500705947534364, 37.904137673197035),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(18.039538499999995, 12.930571500000006),
+            Offset(18.149334465395803, 12.872149290550801),
+            Offset(18.543577170010774, 12.67362376971493),
+            Offset(19.38848933269092, 12.304132440263823),
+            Offset(21.0500251098891, 11.778829975926628),
+            Offset(24.4849959176686, 11.43667565110878),
+            Offset(31.716380432411114, 14.006619703548635),
+            Offset(36.563833612934715, 21.53883416894973),
+            Offset(36.92848913752976, 26.678594000151676),
+            Offset(36.41588677676291, 30.008169262247456),
+            Offset(35.68873044963835, 32.36483086720799),
+            Offset(34.89395296392378, 34.0653568909929),
+            Offset(34.11750536482414, 35.28323166524228),
+            Offset(33.42442890399698, 36.15784461978932),
+            Offset(32.840093239206396, 36.785183822829495),
+            Offset(32.36951533303179, 37.23048479128569),
+            Offset(32.00877148413249, 37.538392723203316),
+            Offset(31.749801205381853, 37.7397340593929),
+            Offset(31.58332657425492, 37.85607615398918),
+            Offset(31.500705947534364, 37.904137673197035),
+          ],
+          <Offset>[
+            Offset(9.524593499999996, 12.930571500000006),
+            Offset(9.63480611837429, 12.787915207234757),
+            Offset(10.037253738651572, 12.290544098974763),
+            Offset(10.933129105730652, 11.298562062138357),
+            Offset(12.814931782195051, 9.613760407253604),
+            Offset(17.148837885012377, 7.11336292762574),
+            Offset(28.234762045851944, 6.196224855085145),
+            Offset(39.13042441508561, 13.256357206311968),
+            Offset(42.67716555792793, 19.829043505343183),
+            Offset(43.985492641283884, 24.533428745633444),
+            Offset(44.43019441391232, 28.080441886710624),
+            Offset(44.394288300179475, 30.80095825365075),
+            Offset(44.09477826399472, 32.872912945700186),
+            Offset(43.700712499650194, 34.44166739898953),
+            Offset(43.302847476418705, 35.61890284347735),
+            Offset(42.94671302192797, 36.48781021374379),
+            Offset(42.653748813975184, 37.109710143785925),
+            Offset(42.43168887555528, 37.5301341853464),
+            Offset(42.28113740093583, 37.78310769585204),
+            Offset(42.20064775415372, 37.89587099404257),
+          ],
+          <Offset>[
+            Offset(9.524593499999996, 12.930571500000006),
+            Offset(9.63480611837429, 12.787915207234757),
+            Offset(10.037253738651572, 12.290544098974763),
+            Offset(10.933129105730652, 11.298562062138357),
+            Offset(12.814931782195051, 9.613760407253604),
+            Offset(17.148837885012377, 7.11336292762574),
+            Offset(28.234762045851944, 6.196224855085145),
+            Offset(39.13042441508561, 13.256357206311968),
+            Offset(42.67716555792793, 19.829043505343183),
+            Offset(43.985492641283884, 24.533428745633444),
+            Offset(44.43019441391232, 28.080441886710624),
+            Offset(44.394288300179475, 30.80095825365075),
+            Offset(44.09477826399472, 32.872912945700186),
+            Offset(43.700712499650194, 34.44166739898953),
+            Offset(43.302847476418705, 35.61890284347735),
+            Offset(42.94671302192797, 36.48781021374379),
+            Offset(42.653748813975184, 37.109710143785925),
+            Offset(42.43168887555528, 37.5301341853464),
+            Offset(42.28113740093583, 37.78310769585204),
+            Offset(42.20064775415372, 37.89587099404257),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(9.524593499999996, 12.930571500000006),
+            Offset(9.63480611837429, 12.787915207234757),
+            Offset(10.037253738651572, 12.290544098974763),
+            Offset(10.933129105730652, 11.298562062138357),
+            Offset(12.814931782195051, 9.613760407253604),
+            Offset(17.148837885012377, 7.11336292762574),
+            Offset(28.234762045851944, 6.196224855085145),
+            Offset(39.13042441508561, 13.256357206311968),
+            Offset(42.67716555792793, 19.829043505343183),
+            Offset(43.985492641283884, 24.533428745633444),
+            Offset(44.43019441391232, 28.080441886710624),
+            Offset(44.394288300179475, 30.80095825365075),
+            Offset(44.09477826399472, 32.872912945700186),
+            Offset(43.700712499650194, 34.44166739898953),
+            Offset(43.302847476418705, 35.61890284347735),
+            Offset(42.94671302192797, 36.48781021374379),
+            Offset(42.653748813975184, 37.109710143785925),
+            Offset(42.43168887555528, 37.5301341853464),
+            Offset(42.28113740093583, 37.78310769585204),
+            Offset(42.20064775415372, 37.89587099404257),
+          ],
+          <Offset>[
+            Offset(9.524593499999998, 23.148505500000006),
+            Offset(9.533725218395034, 23.005349223660573),
+            Offset(9.577558133763372, 22.498132216605804),
+            Offset(9.726444651980092, 21.44499433449068),
+            Offset(10.216848299787422, 19.495872400486462),
+            Offset(11.960862616832728, 15.916752566813207),
+            Offset(18.862288227695757, 10.374166918956151),
+            Offset(29.191452059920294, 10.176448243730892),
+            Offset(34.457704964157735, 12.93063180086538),
+            Offset(37.415804021347064, 15.449901708208273),
+            Offset(39.288927637315474, 17.590685129581864),
+            Offset(40.4770099353689, 19.400555850143917),
+            Offset(41.202395800544195, 20.900185466695486),
+            Offset(41.64129983469044, 22.110127084205665),
+            Offset(41.90331030119613, 23.06359775882259),
+            Offset(42.055503528877686, 23.79517298706838),
+            Offset(42.13932971867431, 24.335737347974696),
+            Offset(42.18016902669948, 24.711868981138284),
+            Offset(42.19357525117126, 24.945734703834944),
+            Offset(42.19072773916836, 25.055940826099334),
+          ],
+          <Offset>[
+            Offset(9.524593499999998, 23.148505500000006),
+            Offset(9.533725218395034, 23.005349223660573),
+            Offset(9.577558133763372, 22.498132216605804),
+            Offset(9.726444651980092, 21.44499433449068),
+            Offset(10.216848299787422, 19.495872400486462),
+            Offset(11.960862616832728, 15.916752566813207),
+            Offset(18.862288227695757, 10.374166918956151),
+            Offset(29.191452059920294, 10.176448243730892),
+            Offset(34.457704964157735, 12.93063180086538),
+            Offset(37.415804021347064, 15.449901708208273),
+            Offset(39.288927637315474, 17.590685129581864),
+            Offset(40.4770099353689, 19.400555850143917),
+            Offset(41.202395800544195, 20.900185466695486),
+            Offset(41.64129983469044, 22.110127084205665),
+            Offset(41.90331030119613, 23.06359775882259),
+            Offset(42.055503528877686, 23.79517298706838),
+            Offset(42.13932971867431, 24.335737347974696),
+            Offset(42.18016902669948, 24.711868981138284),
+            Offset(42.19357525117126, 24.945734703834944),
+            Offset(42.19072773916836, 25.055940826099334),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(9.524593499999998, 23.148505500000006),
+            Offset(9.533725218395034, 23.005349223660573),
+            Offset(9.577558133763372, 22.498132216605804),
+            Offset(9.726444651980092, 21.44499433449068),
+            Offset(10.216848299787422, 19.495872400486462),
+            Offset(11.960862616832728, 15.916752566813207),
+            Offset(18.862288227695757, 10.374166918956151),
+            Offset(29.191452059920294, 10.176448243730892),
+            Offset(34.457704964157735, 12.93063180086538),
+            Offset(37.415804021347064, 15.449901708208273),
+            Offset(39.288927637315474, 17.590685129581864),
+            Offset(40.4770099353689, 19.400555850143917),
+            Offset(41.202395800544195, 20.900185466695486),
+            Offset(41.64129983469044, 22.110127084205665),
+            Offset(41.90331030119613, 23.06359775882259),
+            Offset(42.055503528877686, 23.79517298706838),
+            Offset(42.13932971867431, 24.335737347974696),
+            Offset(42.18016902669948, 24.711868981138284),
+            Offset(42.19357525117126, 24.945734703834944),
+            Offset(42.19072773916836, 25.055940826099334),
+          ],
+          <Offset>[
+            Offset(18.0395385, 23.148505500000006),
+            Offset(18.048253565416548, 23.089583306976618),
+            Offset(18.083881565122574, 22.88121188734597),
+            Offset(18.181804878940362, 22.450564712616142),
+            Offset(18.45194162748147, 21.660941969159484),
+            Offset(19.29702064948895, 20.240065290296247),
+            Offset(22.343906614254927, 18.18456176741964),
+            Offset(26.6248612577694, 18.458925206368654),
+            Offset(28.709028543759565, 19.78018229567387),
+            Offset(29.84619815682609, 20.924642224822286),
+            Offset(30.547463673041506, 21.875074110079233),
+            Offset(30.976674599113203, 22.664954487486067),
+            Offset(31.225122901373616, 23.310504186237587),
+            Offset(31.365016239037228, 23.82630430500546),
+            Offset(31.440556063983824, 24.229878738174733),
+            Offset(31.47830583998151, 24.53784756461028),
+            Offset(31.49435238883162, 24.76441992739209),
+            Offset(31.498281356526046, 24.921468855184788),
+            Offset(31.495764424490346, 25.018703161972084),
+            Offset(31.490785932549, 25.064207505253805),
+          ],
+          <Offset>[
+            Offset(18.0395385, 23.148505500000006),
+            Offset(18.048253565416548, 23.089583306976618),
+            Offset(18.083881565122574, 22.88121188734597),
+            Offset(18.181804878940362, 22.450564712616142),
+            Offset(18.45194162748147, 21.660941969159484),
+            Offset(19.29702064948895, 20.240065290296247),
+            Offset(22.343906614254927, 18.18456176741964),
+            Offset(26.6248612577694, 18.458925206368654),
+            Offset(28.709028543759565, 19.78018229567387),
+            Offset(29.84619815682609, 20.924642224822286),
+            Offset(30.547463673041506, 21.875074110079233),
+            Offset(30.976674599113203, 22.664954487486067),
+            Offset(31.225122901373616, 23.310504186237587),
+            Offset(31.365016239037228, 23.82630430500546),
+            Offset(31.440556063983824, 24.229878738174733),
+            Offset(31.47830583998151, 24.53784756461028),
+            Offset(31.49435238883162, 24.76441992739209),
+            Offset(31.498281356526046, 24.921468855184788),
+            Offset(31.495764424490346, 25.018703161972084),
+            Offset(31.490785932549, 25.064207505253805),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(18.0395385, 23.148505500000006),
+            Offset(18.048253565416548, 23.089583306976618),
+            Offset(18.083881565122574, 22.88121188734597),
+            Offset(18.181804878940362, 22.450564712616142),
+            Offset(18.45194162748147, 21.660941969159484),
+            Offset(19.29702064948895, 20.240065290296247),
+            Offset(22.343906614254927, 18.18456176741964),
+            Offset(26.6248612577694, 18.458925206368654),
+            Offset(28.709028543759565, 19.78018229567387),
+            Offset(29.84619815682609, 20.924642224822286),
+            Offset(30.547463673041506, 21.875074110079233),
+            Offset(30.976674599113203, 22.664954487486067),
+            Offset(31.225122901373616, 23.310504186237587),
+            Offset(31.365016239037228, 23.82630430500546),
+            Offset(31.440556063983824, 24.229878738174733),
+            Offset(31.47830583998151, 24.53784756461028),
+            Offset(31.49435238883162, 24.76441992739209),
+            Offset(31.498281356526046, 24.921468855184788),
+            Offset(31.495764424490346, 25.018703161972084),
+            Offset(31.490785932549, 25.064207505253805),
+          ],
+          <Offset>[
+            Offset(18.039538499999995, 12.930571500000006),
+            Offset(18.149334465395803, 12.872149290550801),
+            Offset(18.543577170010774, 12.67362376971493),
+            Offset(19.38848933269092, 12.304132440263823),
+            Offset(21.0500251098891, 11.778829975926628),
+            Offset(24.4849959176686, 11.43667565110878),
+            Offset(31.716380432411114, 14.006619703548635),
+            Offset(36.563833612934715, 21.53883416894973),
+            Offset(36.92848913752976, 26.678594000151676),
+            Offset(36.41588677676291, 30.008169262247456),
+            Offset(35.68873044963835, 32.36483086720799),
+            Offset(34.89395296392378, 34.0653568909929),
+            Offset(34.11750536482414, 35.28323166524228),
+            Offset(33.42442890399698, 36.15784461978932),
+            Offset(32.840093239206396, 36.785183822829495),
+            Offset(32.36951533303179, 37.23048479128569),
+            Offset(32.00877148413249, 37.538392723203316),
+            Offset(31.749801205381853, 37.7397340593929),
+            Offset(31.58332657425492, 37.85607615398918),
+            Offset(31.500705947534364, 37.904137673197035),
+          ],
+          <Offset>[
+            Offset(18.039538499999995, 12.930571500000006),
+            Offset(18.149334465395803, 12.872149290550801),
+            Offset(18.543577170010774, 12.67362376971493),
+            Offset(19.38848933269092, 12.304132440263823),
+            Offset(21.0500251098891, 11.778829975926628),
+            Offset(24.4849959176686, 11.43667565110878),
+            Offset(31.716380432411114, 14.006619703548635),
+            Offset(36.563833612934715, 21.53883416894973),
+            Offset(36.92848913752976, 26.678594000151676),
+            Offset(36.41588677676291, 30.008169262247456),
+            Offset(35.68873044963835, 32.36483086720799),
+            Offset(34.89395296392378, 34.0653568909929),
+            Offset(34.11750536482414, 35.28323166524228),
+            Offset(33.42442890399698, 36.15784461978932),
+            Offset(32.840093239206396, 36.785183822829495),
+            Offset(32.36951533303179, 37.23048479128569),
+            Offset(32.00877148413249, 37.538392723203316),
+            Offset(31.749801205381853, 37.7397340593929),
+            Offset(31.58332657425492, 37.85607615398918),
+            Offset(31.500705947534364, 37.904137673197035),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.146341463415,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(38.4754065, 24.8514945),
+            Offset(38.466274781604966, 24.99465077633943),
+            Offset(38.42244186623663, 25.501867783394207),
+            Offset(38.27355534801991, 26.555005665509313),
+            Offset(37.78315170021258, 28.504127599513545),
+            Offset(36.03913738316727, 32.08324743318679),
+            Offset(29.137711772304243, 37.62583308104385),
+            Offset(18.8085479400797, 37.8235517562691),
+            Offset(13.542295035842265, 35.06936819913462),
+            Offset(10.58419597865294, 32.55009829179173),
+            Offset(8.71107236268452, 30.409314870418136),
+            Offset(7.522990064631102, 28.599444149856083),
+            Offset(6.797604199455802, 27.099814533304514),
+            Offset(6.358700165309552, 25.889872915794328),
+            Offset(6.096689698803871, 24.93640224117742),
+            Offset(5.94449647112231, 24.204827012931613),
+            Offset(5.860670281325686, 23.6642626520253),
+            Offset(5.819830973300516, 23.288131018861716),
+            Offset(5.806424748828734, 23.05426529616505),
+            Offset(5.809272260831642, 22.944059173900662),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.4754065, 24.8514945),
+            Offset(38.466274781604966, 24.99465077633943),
+            Offset(38.42244186623663, 25.501867783394207),
+            Offset(38.27355534801991, 26.555005665509313),
+            Offset(37.78315170021258, 28.504127599513545),
+            Offset(36.03913738316727, 32.08324743318679),
+            Offset(29.137711772304243, 37.62583308104385),
+            Offset(18.8085479400797, 37.8235517562691),
+            Offset(13.542295035842265, 35.06936819913462),
+            Offset(10.58419597865294, 32.55009829179173),
+            Offset(8.71107236268452, 30.409314870418136),
+            Offset(7.522990064631102, 28.599444149856083),
+            Offset(6.797604199455802, 27.099814533304514),
+            Offset(6.358700165309552, 25.889872915794328),
+            Offset(6.096689698803871, 24.93640224117742),
+            Offset(5.94449647112231, 24.204827012931613),
+            Offset(5.860670281325686, 23.6642626520253),
+            Offset(5.819830973300516, 23.288131018861716),
+            Offset(5.806424748828734, 23.05426529616505),
+            Offset(5.809272260831642, 22.944059173900662),
+          ],
+          <Offset>[
+            Offset(29.9604615, 24.8514945),
+            Offset(29.951746434583455, 24.910416693023386),
+            Offset(29.91611843487743, 25.11878811265404),
+            Offset(29.818195121059638, 25.54943528738385),
+            Offset(29.54805837251853, 26.339058030840523),
+            Offset(28.702979350511054, 27.759934709703753),
+            Offset(25.656093385745073, 29.81543823258036),
+            Offset(21.375138742230593, 29.54107479363134),
+            Offset(19.290971456240435, 28.21981770432613),
+            Offset(18.153801843173916, 27.07535777517771),
+            Offset(17.452536326958487, 26.124925889920767),
+            Offset(17.023325400886797, 25.335045512513933),
+            Offset(16.774877098626384, 24.689495813762413),
+            Offset(16.63498376096277, 24.17369569499453),
+            Offset(16.559443936016173, 23.770121261825274),
+            Offset(16.521694160018484, 23.46215243538971),
+            Offset(16.505647611168378, 23.235580072607906),
+            Offset(16.501718643473946, 23.078531144815212),
+            Offset(16.504235575509647, 22.98129683802791),
+            Offset(16.509214067451, 22.93579249474619),
+          ],
+          <Offset>[
+            Offset(29.9604615, 24.8514945),
+            Offset(29.951746434583455, 24.910416693023386),
+            Offset(29.91611843487743, 25.11878811265404),
+            Offset(29.818195121059638, 25.54943528738385),
+            Offset(29.54805837251853, 26.339058030840523),
+            Offset(28.702979350511054, 27.759934709703753),
+            Offset(25.656093385745073, 29.81543823258036),
+            Offset(21.375138742230593, 29.54107479363134),
+            Offset(19.290971456240435, 28.21981770432613),
+            Offset(18.153801843173916, 27.07535777517771),
+            Offset(17.452536326958487, 26.124925889920767),
+            Offset(17.023325400886797, 25.335045512513933),
+            Offset(16.774877098626384, 24.689495813762413),
+            Offset(16.63498376096277, 24.17369569499453),
+            Offset(16.559443936016173, 23.770121261825274),
+            Offset(16.521694160018484, 23.46215243538971),
+            Offset(16.505647611168378, 23.235580072607906),
+            Offset(16.501718643473946, 23.078531144815212),
+            Offset(16.504235575509647, 22.98129683802791),
+            Offset(16.509214067451, 22.93579249474619),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(29.9604615, 24.8514945),
+            Offset(29.951746434583455, 24.910416693023386),
+            Offset(29.91611843487743, 25.11878811265404),
+            Offset(29.818195121059638, 25.54943528738385),
+            Offset(29.54805837251853, 26.339058030840523),
+            Offset(28.702979350511054, 27.759934709703753),
+            Offset(25.656093385745073, 29.81543823258036),
+            Offset(21.375138742230593, 29.54107479363134),
+            Offset(19.290971456240435, 28.21981770432613),
+            Offset(18.153801843173916, 27.07535777517771),
+            Offset(17.452536326958487, 26.124925889920767),
+            Offset(17.023325400886797, 25.335045512513933),
+            Offset(16.774877098626384, 24.689495813762413),
+            Offset(16.63498376096277, 24.17369569499453),
+            Offset(16.559443936016173, 23.770121261825274),
+            Offset(16.521694160018484, 23.46215243538971),
+            Offset(16.505647611168378, 23.235580072607906),
+            Offset(16.501718643473946, 23.078531144815212),
+            Offset(16.504235575509647, 22.98129683802791),
+            Offset(16.509214067451, 22.93579249474619),
+          ],
+          <Offset>[
+            Offset(29.9604615, 35.0694285),
+            Offset(29.8506655346042, 35.1278507094492),
+            Offset(29.45642282998923, 35.326376230285085),
+            Offset(28.61151066730908, 35.69586755973617),
+            Offset(26.9499748901109, 36.22117002407338),
+            Offset(23.515004082331405, 36.56332434889122),
+            Offset(16.283619567588886, 33.99338029645136),
+            Offset(11.43616638706528, 26.461165831050263),
+            Offset(11.071510862470241, 21.321405999848324),
+            Offset(11.5841132232371, 17.99183073775254),
+            Offset(12.311269550361642, 15.635169132792008),
+            Offset(13.10604703607622, 13.934643109007098),
+            Offset(13.882494635175863, 12.716768334757713),
+            Offset(14.575571096003015, 11.84215538021067),
+            Offset(15.159906760793596, 11.214816177170512),
+            Offset(15.630484666968204, 10.7695152087143),
+            Offset(15.991228515867505, 10.461607276796677),
+            Offset(16.250198794618143, 10.260265940607095),
+            Offset(16.416673425745074, 10.143923846010813),
+            Offset(16.49929405246564, 10.095862326802958),
+          ],
+          <Offset>[
+            Offset(29.9604615, 35.0694285),
+            Offset(29.8506655346042, 35.1278507094492),
+            Offset(29.45642282998923, 35.326376230285085),
+            Offset(28.61151066730908, 35.69586755973617),
+            Offset(26.9499748901109, 36.22117002407338),
+            Offset(23.515004082331405, 36.56332434889122),
+            Offset(16.283619567588886, 33.99338029645136),
+            Offset(11.43616638706528, 26.461165831050263),
+            Offset(11.071510862470241, 21.321405999848324),
+            Offset(11.5841132232371, 17.99183073775254),
+            Offset(12.311269550361642, 15.635169132792008),
+            Offset(13.10604703607622, 13.934643109007098),
+            Offset(13.882494635175863, 12.716768334757713),
+            Offset(14.575571096003015, 11.84215538021067),
+            Offset(15.159906760793596, 11.214816177170512),
+            Offset(15.630484666968204, 10.7695152087143),
+            Offset(15.991228515867505, 10.461607276796677),
+            Offset(16.250198794618143, 10.260265940607095),
+            Offset(16.416673425745074, 10.143923846010813),
+            Offset(16.49929405246564, 10.095862326802958),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(29.9604615, 35.0694285),
+            Offset(29.8506655346042, 35.1278507094492),
+            Offset(29.45642282998923, 35.326376230285085),
+            Offset(28.61151066730908, 35.69586755973617),
+            Offset(26.9499748901109, 36.22117002407338),
+            Offset(23.515004082331405, 36.56332434889122),
+            Offset(16.283619567588886, 33.99338029645136),
+            Offset(11.43616638706528, 26.461165831050263),
+            Offset(11.071510862470241, 21.321405999848324),
+            Offset(11.5841132232371, 17.99183073775254),
+            Offset(12.311269550361642, 15.635169132792008),
+            Offset(13.10604703607622, 13.934643109007098),
+            Offset(13.882494635175863, 12.716768334757713),
+            Offset(14.575571096003015, 11.84215538021067),
+            Offset(15.159906760793596, 11.214816177170512),
+            Offset(15.630484666968204, 10.7695152087143),
+            Offset(15.991228515867505, 10.461607276796677),
+            Offset(16.250198794618143, 10.260265940607095),
+            Offset(16.416673425745074, 10.143923846010813),
+            Offset(16.49929405246564, 10.095862326802958),
+          ],
+          <Offset>[
+            Offset(38.4754065, 35.0694285),
+            Offset(38.365193881625714, 35.212084792765246),
+            Offset(37.96274626134843, 35.70945590102525),
+            Offset(37.06687089426935, 36.701437937861634),
+            Offset(35.18506821780495, 38.386239592746406),
+            Offset(30.851162114987627, 40.886637072374256),
+            Offset(19.765237954148056, 41.80377514491485),
+            Offset(8.869575584914383, 34.743642793688025),
+            Offset(5.3228344420720735, 28.170956494656817),
+            Offset(4.0145073587161235, 23.466571254366553),
+            Offset(3.5698055860876767, 19.919558113289376),
+            Offset(3.6057116998205245, 17.19904174634925),
+            Offset(3.90522173600528, 15.127087054299814),
+            Offset(4.299287500349799, 13.558332601010466),
+            Offset(4.697152523581295, 12.381097156522657),
+            Offset(5.053286978072029, 11.5121897862562),
+            Offset(5.346251186024813, 10.890289856214071),
+            Offset(5.568311124444711, 10.469865814653598),
+            Offset(5.7188625990641615, 10.216892304147956),
+            Offset(5.799352245846278, 10.104129005957429),
+          ],
+          <Offset>[
+            Offset(38.4754065, 35.0694285),
+            Offset(38.365193881625714, 35.212084792765246),
+            Offset(37.96274626134843, 35.70945590102525),
+            Offset(37.06687089426935, 36.701437937861634),
+            Offset(35.18506821780495, 38.386239592746406),
+            Offset(30.851162114987627, 40.886637072374256),
+            Offset(19.765237954148056, 41.80377514491485),
+            Offset(8.869575584914383, 34.743642793688025),
+            Offset(5.3228344420720735, 28.170956494656817),
+            Offset(4.0145073587161235, 23.466571254366553),
+            Offset(3.5698055860876767, 19.919558113289376),
+            Offset(3.6057116998205245, 17.19904174634925),
+            Offset(3.90522173600528, 15.127087054299814),
+            Offset(4.299287500349799, 13.558332601010466),
+            Offset(4.697152523581295, 12.381097156522657),
+            Offset(5.053286978072029, 11.5121897862562),
+            Offset(5.346251186024813, 10.890289856214071),
+            Offset(5.568311124444711, 10.469865814653598),
+            Offset(5.7188625990641615, 10.216892304147956),
+            Offset(5.799352245846278, 10.104129005957429),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.4754065, 35.0694285),
+            Offset(38.365193881625714, 35.212084792765246),
+            Offset(37.96274626134843, 35.70945590102525),
+            Offset(37.06687089426935, 36.701437937861634),
+            Offset(35.18506821780495, 38.386239592746406),
+            Offset(30.851162114987627, 40.886637072374256),
+            Offset(19.765237954148056, 41.80377514491485),
+            Offset(8.869575584914383, 34.743642793688025),
+            Offset(5.3228344420720735, 28.170956494656817),
+            Offset(4.0145073587161235, 23.466571254366553),
+            Offset(3.5698055860876767, 19.919558113289376),
+            Offset(3.6057116998205245, 17.19904174634925),
+            Offset(3.90522173600528, 15.127087054299814),
+            Offset(4.299287500349799, 13.558332601010466),
+            Offset(4.697152523581295, 12.381097156522657),
+            Offset(5.053286978072029, 11.5121897862562),
+            Offset(5.346251186024813, 10.890289856214071),
+            Offset(5.568311124444711, 10.469865814653598),
+            Offset(5.7188625990641615, 10.216892304147956),
+            Offset(5.799352245846278, 10.104129005957429),
+          ],
+          <Offset>[
+            Offset(38.4754065, 24.8514945),
+            Offset(38.466274781604966, 24.99465077633943),
+            Offset(38.42244186623663, 25.501867783394207),
+            Offset(38.27355534801991, 26.555005665509313),
+            Offset(37.78315170021258, 28.504127599513545),
+            Offset(36.03913738316727, 32.08324743318679),
+            Offset(29.137711772304243, 37.62583308104385),
+            Offset(18.8085479400797, 37.8235517562691),
+            Offset(13.542295035842265, 35.06936819913462),
+            Offset(10.58419597865294, 32.55009829179173),
+            Offset(8.71107236268452, 30.409314870418136),
+            Offset(7.522990064631102, 28.599444149856083),
+            Offset(6.797604199455802, 27.099814533304514),
+            Offset(6.358700165309552, 25.889872915794328),
+            Offset(6.096689698803871, 24.93640224117742),
+            Offset(5.94449647112231, 24.204827012931613),
+            Offset(5.860670281325686, 23.6642626520253),
+            Offset(5.819830973300516, 23.288131018861716),
+            Offset(5.806424748828734, 23.05426529616505),
+            Offset(5.809272260831642, 22.944059173900662),
+          ],
+          <Offset>[
+            Offset(38.4754065, 24.8514945),
+            Offset(38.466274781604966, 24.99465077633943),
+            Offset(38.42244186623663, 25.501867783394207),
+            Offset(38.27355534801991, 26.555005665509313),
+            Offset(37.78315170021258, 28.504127599513545),
+            Offset(36.03913738316727, 32.08324743318679),
+            Offset(29.137711772304243, 37.62583308104385),
+            Offset(18.8085479400797, 37.8235517562691),
+            Offset(13.542295035842265, 35.06936819913462),
+            Offset(10.58419597865294, 32.55009829179173),
+            Offset(8.71107236268452, 30.409314870418136),
+            Offset(7.522990064631102, 28.599444149856083),
+            Offset(6.797604199455802, 27.099814533304514),
+            Offset(6.358700165309552, 25.889872915794328),
+            Offset(6.096689698803871, 24.93640224117742),
+            Offset(5.94449647112231, 24.204827012931613),
+            Offset(5.860670281325686, 23.6642626520253),
+            Offset(5.819830973300516, 23.288131018861716),
+            Offset(5.806424748828734, 23.05426529616505),
+            Offset(5.809272260831642, 22.944059173900662),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.146341463415,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(28.2574725, 24.8514945),
+            Offset(28.248840765179153, 24.893569876360175),
+            Offset(28.214853748605588, 25.042172178506007),
+            Offset(28.12712307566759, 25.34832121175876),
+            Offset(27.90103970697972, 25.90604411710592),
+            Offset(27.235747743979807, 26.895272165007142),
+            Offset(24.959769708433235, 28.253359262887663),
+            Offset(21.888456902660778, 27.88457940110379),
+            Offset(20.440706740320067, 26.84990760536443),
+            Offset(19.66772301607811, 25.980409671854908),
+            Offset(19.20082911981328, 25.26804809382129),
+            Offset(18.92339246813794, 24.682165785045505),
+            Offset(18.770331678460497, 24.207432069853994),
+            Offset(18.690240480093415, 23.830460250834577),
+            Offset(18.651994783458633, 23.536865065954846),
+            Offset(18.637133697797722, 23.313617519881333),
+            Offset(18.634643077136914, 23.149843556724427),
+            Offset(18.638096177508633, 23.03661117000591),
+            Offset(18.643797740845827, 22.966703146400476),
+            Offset(18.649202428774874, 22.9341391589153),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(28.2574725, 24.8514945),
+            Offset(28.248840765179153, 24.893569876360175),
+            Offset(28.214853748605588, 25.042172178506007),
+            Offset(28.12712307566759, 25.34832121175876),
+            Offset(27.90103970697972, 25.90604411710592),
+            Offset(27.235747743979807, 26.895272165007142),
+            Offset(24.959769708433235, 28.253359262887663),
+            Offset(21.888456902660778, 27.88457940110379),
+            Offset(20.440706740320067, 26.84990760536443),
+            Offset(19.66772301607811, 25.980409671854908),
+            Offset(19.20082911981328, 25.26804809382129),
+            Offset(18.92339246813794, 24.682165785045505),
+            Offset(18.770331678460497, 24.207432069853994),
+            Offset(18.690240480093415, 23.830460250834577),
+            Offset(18.651994783458633, 23.536865065954846),
+            Offset(18.637133697797722, 23.313617519881333),
+            Offset(18.634643077136914, 23.149843556724427),
+            Offset(18.638096177508633, 23.03661117000591),
+            Offset(18.643797740845827, 22.966703146400476),
+            Offset(18.649202428774874, 22.9341391589153),
+          ],
+          <Offset>[
+            Offset(19.7425275, 24.8514945),
+            Offset(19.73431241815764, 24.80933579304413),
+            Offset(19.708530317246385, 24.65909250776584),
+            Offset(19.67176284870732, 24.34275083363329),
+            Offset(19.66594637928567, 23.740974548432895),
+            Offset(19.899589711323586, 22.5719594415241),
+            Offset(21.478151321874066, 20.442964414424175),
+            Offset(24.455047704811673, 19.602102438466027),
+            Offset(26.189383160718236, 20.00035711055594),
+            Offset(27.237328880599087, 20.505669155240895),
+            Offset(27.942293084087247, 20.983659113323917),
+            Offset(28.423727804393636, 21.417767147703355),
+            Offset(28.74760457763108, 21.797113350311893),
+            Offset(28.96652407574663, 22.11428303003478),
+            Offset(29.114749020670935, 22.3705840866027),
+            Offset(29.2143313866939, 22.570942942339432),
+            Offset(29.279620406979603, 22.721160977307033),
+            Offset(29.319983847682064, 22.827011295959405),
+            Offset(29.34160856752674, 22.893734688263336),
+            Offset(29.349144235394235, 22.92587247976083),
+          ],
+          <Offset>[
+            Offset(19.7425275, 24.8514945),
+            Offset(19.73431241815764, 24.80933579304413),
+            Offset(19.708530317246385, 24.65909250776584),
+            Offset(19.67176284870732, 24.34275083363329),
+            Offset(19.66594637928567, 23.740974548432895),
+            Offset(19.899589711323586, 22.5719594415241),
+            Offset(21.478151321874066, 20.442964414424175),
+            Offset(24.455047704811673, 19.602102438466027),
+            Offset(26.189383160718236, 20.00035711055594),
+            Offset(27.237328880599087, 20.505669155240895),
+            Offset(27.942293084087247, 20.983659113323917),
+            Offset(28.423727804393636, 21.417767147703355),
+            Offset(28.74760457763108, 21.797113350311893),
+            Offset(28.96652407574663, 22.11428303003478),
+            Offset(29.114749020670935, 22.3705840866027),
+            Offset(29.2143313866939, 22.570942942339432),
+            Offset(29.279620406979603, 22.721160977307033),
+            Offset(29.319983847682064, 22.827011295959405),
+            Offset(29.34160856752674, 22.893734688263336),
+            Offset(29.349144235394235, 22.92587247976083),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(19.7425275, 24.8514945),
+            Offset(19.73431241815764, 24.80933579304413),
+            Offset(19.708530317246385, 24.65909250776584),
+            Offset(19.67176284870732, 24.34275083363329),
+            Offset(19.66594637928567, 23.740974548432895),
+            Offset(19.899589711323586, 22.5719594415241),
+            Offset(21.478151321874066, 20.442964414424175),
+            Offset(24.455047704811673, 19.602102438466027),
+            Offset(26.189383160718236, 20.00035711055594),
+            Offset(27.237328880599087, 20.505669155240895),
+            Offset(27.942293084087247, 20.983659113323917),
+            Offset(28.423727804393636, 21.417767147703355),
+            Offset(28.74760457763108, 21.797113350311893),
+            Offset(28.96652407574663, 22.11428303003478),
+            Offset(29.114749020670935, 22.3705840866027),
+            Offset(29.2143313866939, 22.570942942339432),
+            Offset(29.279620406979603, 22.721160977307033),
+            Offset(29.319983847682064, 22.827011295959405),
+            Offset(29.34160856752674, 22.893734688263336),
+            Offset(29.349144235394235, 22.92587247976083),
+          ],
+          <Offset>[
+            Offset(19.7425275, 35.0694285),
+            Offset(19.633231518178384, 35.02676980946995),
+            Offset(19.248834712358185, 34.86668062539688),
+            Offset(18.465078394956763, 34.48918310598561),
+            Offset(17.067862896878044, 33.62308654166575),
+            Offset(14.711614443143937, 31.375349080711565),
+            Offset(12.105677503717876, 24.620906478295183),
+            Offset(14.516075349646359, 16.52219347588495),
+            Offset(17.969922566948043, 13.101945406078135),
+            Offset(20.66764026066227, 11.422142117815724),
+            Offset(22.8010263074904, 10.49390235619516),
+            Offset(24.506449439583058, 10.01736474419652),
+            Offset(25.85522211418056, 9.824385871307193),
+            Offset(26.907111410786875, 9.782742715250919),
+            Offset(27.71521184544836, 9.815279001947939),
+            Offset(28.323121893643616, 9.87830571566402),
+            Offset(28.76520131167873, 9.947188181495804),
+            Offset(29.068463998826257, 10.008746091751288),
+            Offset(29.25404641776217, 10.05636169624624),
+            Offset(29.33922422040887, 10.085942311817597),
+          ],
+          <Offset>[
+            Offset(19.7425275, 35.0694285),
+            Offset(19.633231518178384, 35.02676980946995),
+            Offset(19.248834712358185, 34.86668062539688),
+            Offset(18.465078394956763, 34.48918310598561),
+            Offset(17.067862896878044, 33.62308654166575),
+            Offset(14.711614443143937, 31.375349080711565),
+            Offset(12.105677503717876, 24.620906478295183),
+            Offset(14.516075349646359, 16.52219347588495),
+            Offset(17.969922566948043, 13.101945406078135),
+            Offset(20.66764026066227, 11.422142117815724),
+            Offset(22.8010263074904, 10.49390235619516),
+            Offset(24.506449439583058, 10.01736474419652),
+            Offset(25.85522211418056, 9.824385871307193),
+            Offset(26.907111410786875, 9.782742715250919),
+            Offset(27.71521184544836, 9.815279001947939),
+            Offset(28.323121893643616, 9.87830571566402),
+            Offset(28.76520131167873, 9.947188181495804),
+            Offset(29.068463998826257, 10.008746091751288),
+            Offset(29.25404641776217, 10.05636169624624),
+            Offset(29.33922422040887, 10.085942311817597),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(19.7425275, 35.0694285),
+            Offset(19.633231518178384, 35.02676980946995),
+            Offset(19.248834712358185, 34.86668062539688),
+            Offset(18.465078394956763, 34.48918310598561),
+            Offset(17.067862896878044, 33.62308654166575),
+            Offset(14.711614443143937, 31.375349080711565),
+            Offset(12.105677503717876, 24.620906478295183),
+            Offset(14.516075349646359, 16.52219347588495),
+            Offset(17.969922566948043, 13.101945406078135),
+            Offset(20.66764026066227, 11.422142117815724),
+            Offset(22.8010263074904, 10.49390235619516),
+            Offset(24.506449439583058, 10.01736474419652),
+            Offset(25.85522211418056, 9.824385871307193),
+            Offset(26.907111410786875, 9.782742715250919),
+            Offset(27.71521184544836, 9.815279001947939),
+            Offset(28.323121893643616, 9.87830571566402),
+            Offset(28.76520131167873, 9.947188181495804),
+            Offset(29.068463998826257, 10.008746091751288),
+            Offset(29.25404641776217, 10.05636169624624),
+            Offset(29.33922422040887, 10.085942311817597),
+          ],
+          <Offset>[
+            Offset(28.2574725, 35.0694285),
+            Offset(28.147759865199898, 35.111003892785995),
+            Offset(27.755158143717388, 35.24976029613705),
+            Offset(26.920438621917032, 35.49475348411108),
+            Offset(25.302956224572092, 35.788156110338775),
+            Offset(22.04777247580016, 35.69866180419461),
+            Offset(15.58729589027705, 32.43130132675867),
+            Offset(11.949484547495462, 24.804670438522713),
+            Offset(12.221246146549875, 19.951495900886627),
+            Offset(13.098034396141294, 16.896882634429737),
+            Offset(14.059562343216436, 14.778291336692531),
+            Offset(15.006114103327363, 13.28176338153867),
+            Offset(15.877949215009977, 12.234704590849294),
+            Offset(16.63082781513366, 11.498919936050715),
+            Offset(17.252457608236057, 10.981559981300084),
+            Offset(17.74592420474744, 10.620980293205921),
+            Offset(18.12022398183604, 10.375870760913198),
+            Offset(18.386576328652826, 10.218345965797791),
+            Offset(18.556235591081254, 10.129330154383384),
+            Offset(18.63928241378951, 10.094208990972067),
+          ],
+          <Offset>[
+            Offset(28.2574725, 35.0694285),
+            Offset(28.147759865199898, 35.111003892785995),
+            Offset(27.755158143717388, 35.24976029613705),
+            Offset(26.920438621917032, 35.49475348411108),
+            Offset(25.302956224572092, 35.788156110338775),
+            Offset(22.04777247580016, 35.69866180419461),
+            Offset(15.58729589027705, 32.43130132675867),
+            Offset(11.949484547495462, 24.804670438522713),
+            Offset(12.221246146549875, 19.951495900886627),
+            Offset(13.098034396141294, 16.896882634429737),
+            Offset(14.059562343216436, 14.778291336692531),
+            Offset(15.006114103327363, 13.28176338153867),
+            Offset(15.877949215009977, 12.234704590849294),
+            Offset(16.63082781513366, 11.498919936050715),
+            Offset(17.252457608236057, 10.981559981300084),
+            Offset(17.74592420474744, 10.620980293205921),
+            Offset(18.12022398183604, 10.375870760913198),
+            Offset(18.386576328652826, 10.218345965797791),
+            Offset(18.556235591081254, 10.129330154383384),
+            Offset(18.63928241378951, 10.094208990972067),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(28.2574725, 35.0694285),
+            Offset(28.147759865199898, 35.111003892785995),
+            Offset(27.755158143717388, 35.24976029613705),
+            Offset(26.920438621917032, 35.49475348411108),
+            Offset(25.302956224572092, 35.788156110338775),
+            Offset(22.04777247580016, 35.69866180419461),
+            Offset(15.58729589027705, 32.43130132675867),
+            Offset(11.949484547495462, 24.804670438522713),
+            Offset(12.221246146549875, 19.951495900886627),
+            Offset(13.098034396141294, 16.896882634429737),
+            Offset(14.059562343216436, 14.778291336692531),
+            Offset(15.006114103327363, 13.28176338153867),
+            Offset(15.877949215009977, 12.234704590849294),
+            Offset(16.63082781513366, 11.498919936050715),
+            Offset(17.252457608236057, 10.981559981300084),
+            Offset(17.74592420474744, 10.620980293205921),
+            Offset(18.12022398183604, 10.375870760913198),
+            Offset(18.386576328652826, 10.218345965797791),
+            Offset(18.556235591081254, 10.129330154383384),
+            Offset(18.63928241378951, 10.094208990972067),
+          ],
+          <Offset>[
+            Offset(28.2574725, 24.8514945),
+            Offset(28.248840765179153, 24.893569876360175),
+            Offset(28.214853748605588, 25.042172178506007),
+            Offset(28.12712307566759, 25.34832121175876),
+            Offset(27.90103970697972, 25.90604411710592),
+            Offset(27.235747743979807, 26.895272165007142),
+            Offset(24.959769708433235, 28.253359262887663),
+            Offset(21.888456902660778, 27.88457940110379),
+            Offset(20.440706740320067, 26.84990760536443),
+            Offset(19.66772301607811, 25.980409671854908),
+            Offset(19.20082911981328, 25.26804809382129),
+            Offset(18.92339246813794, 24.682165785045505),
+            Offset(18.770331678460497, 24.207432069853994),
+            Offset(18.690240480093415, 23.830460250834577),
+            Offset(18.651994783458633, 23.536865065954846),
+            Offset(18.637133697797722, 23.313617519881333),
+            Offset(18.634643077136914, 23.149843556724427),
+            Offset(18.638096177508633, 23.03661117000591),
+            Offset(18.643797740845827, 22.966703146400476),
+            Offset(18.649202428774874, 22.9341391589153),
+          ],
+          <Offset>[
+            Offset(28.2574725, 24.8514945),
+            Offset(28.248840765179153, 24.893569876360175),
+            Offset(28.214853748605588, 25.042172178506007),
+            Offset(28.12712307566759, 25.34832121175876),
+            Offset(27.90103970697972, 25.90604411710592),
+            Offset(27.235747743979807, 26.895272165007142),
+            Offset(24.959769708433235, 28.253359262887663),
+            Offset(21.888456902660778, 27.88457940110379),
+            Offset(20.440706740320067, 26.84990760536443),
+            Offset(19.66772301607811, 25.980409671854908),
+            Offset(19.20082911981328, 25.26804809382129),
+            Offset(18.92339246813794, 24.682165785045505),
+            Offset(18.770331678460497, 24.207432069853994),
+            Offset(18.690240480093415, 23.830460250834577),
+            Offset(18.651994783458633, 23.536865065954846),
+            Offset(18.637133697797722, 23.313617519881333),
+            Offset(18.634643077136914, 23.149843556724427),
+            Offset(18.638096177508633, 23.03661117000591),
+            Offset(18.643797740845827, 22.966703146400476),
+            Offset(18.649202428774874, 22.9341391589153),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.146341463415,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(18.0395385, 24.851494500000005),
+            Offset(18.03140674875334, 24.79248897638092),
+            Offset(18.007265630974544, 24.58247657361781),
+            Offset(17.98069080331527, 24.141636758008197),
+            Offset(18.018927713746866, 23.30796063469829),
+            Offset(18.432358104792343, 21.707296896827494),
+            Offset(20.78182764456223, 18.880885444731472),
+            Offset(24.96836586524185, 17.945607045938473),
+            Offset(27.339118444797872, 18.63044701159424),
+            Offset(28.751250053503288, 19.41072105191809),
+            Offset(29.690585876942034, 20.12678131722444),
+            Offset(30.323794871644772, 20.764887420234924),
+            Offset(30.7430591574652, 21.315049606403473),
+            Offset(31.021780794877273, 21.771047585874822),
+            Offset(31.20729986811339, 22.13732789073227),
+            Offset(31.32977092447313, 22.42240802683105),
+            Offset(31.408615872948143, 22.635424461423554),
+            Offset(31.456361381716757, 22.785091321150105),
+            Offset(31.48117073286292, 22.87914099663591),
+            Offset(31.489132596718107, 22.924219143929935),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(18.0395385, 24.851494500000005),
+            Offset(18.03140674875334, 24.79248897638092),
+            Offset(18.007265630974544, 24.58247657361781),
+            Offset(17.98069080331527, 24.141636758008197),
+            Offset(18.018927713746866, 23.30796063469829),
+            Offset(18.432358104792343, 21.707296896827494),
+            Offset(20.78182764456223, 18.880885444731472),
+            Offset(24.96836586524185, 17.945607045938473),
+            Offset(27.339118444797872, 18.63044701159424),
+            Offset(28.751250053503288, 19.41072105191809),
+            Offset(29.690585876942034, 20.12678131722444),
+            Offset(30.323794871644772, 20.764887420234924),
+            Offset(30.7430591574652, 21.315049606403473),
+            Offset(31.021780794877273, 21.771047585874822),
+            Offset(31.20729986811339, 22.13732789073227),
+            Offset(31.32977092447313, 22.42240802683105),
+            Offset(31.408615872948143, 22.635424461423554),
+            Offset(31.456361381716757, 22.785091321150105),
+            Offset(31.48117073286292, 22.87914099663591),
+            Offset(31.489132596718107, 22.924219143929935),
+          ],
+          <Offset>[
+            Offset(9.5245935, 24.851494500000005),
+            Offset(9.516878401731827, 24.708254893064876),
+            Offset(9.500942199615341, 24.199396902877645),
+            Offset(9.525330576355001, 23.136066379882735),
+            Offset(9.783834386052819, 21.142891066025268),
+            Offset(11.09620007213612, 17.383984173344455),
+            Offset(17.300209258003058, 11.070490596267984),
+            Offset(27.534956667392745, 9.663130083300711),
+            Offset(33.08779486519604, 11.780896516785745),
+            Offset(36.320855918024264, 13.935980535304076),
+            Offset(38.432049841216, 15.842392336727073),
+            Offset(39.82413020790047, 17.500488782892777),
+            Offset(40.72033205663578, 18.904730886861373),
+            Offset(41.298064390530485, 20.054870365075026),
+            Offset(41.67005410532569, 20.971046911380125),
+            Offset(41.90696861336931, 21.67973344928915),
+            Offset(42.053593202790836, 22.20674188200616),
+            Offset(42.138249051890185, 22.5754914471036),
+            Offset(42.178981559543836, 22.806172538498764),
+            Offset(42.189074403337465, 22.915952464775465),
+          ],
+          <Offset>[
+            Offset(9.5245935, 24.851494500000005),
+            Offset(9.516878401731827, 24.708254893064876),
+            Offset(9.500942199615341, 24.199396902877645),
+            Offset(9.525330576355001, 23.136066379882735),
+            Offset(9.783834386052819, 21.142891066025268),
+            Offset(11.09620007213612, 17.383984173344455),
+            Offset(17.300209258003058, 11.070490596267984),
+            Offset(27.534956667392745, 9.663130083300711),
+            Offset(33.08779486519604, 11.780896516785745),
+            Offset(36.320855918024264, 13.935980535304076),
+            Offset(38.432049841216, 15.842392336727073),
+            Offset(39.82413020790047, 17.500488782892777),
+            Offset(40.72033205663578, 18.904730886861373),
+            Offset(41.298064390530485, 20.054870365075026),
+            Offset(41.67005410532569, 20.971046911380125),
+            Offset(41.90696861336931, 21.67973344928915),
+            Offset(42.053593202790836, 22.20674188200616),
+            Offset(42.138249051890185, 22.5754914471036),
+            Offset(42.178981559543836, 22.806172538498764),
+            Offset(42.189074403337465, 22.915952464775465),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(9.5245935, 24.851494500000005),
+            Offset(9.516878401731827, 24.708254893064876),
+            Offset(9.500942199615341, 24.199396902877645),
+            Offset(9.525330576355001, 23.136066379882735),
+            Offset(9.783834386052819, 21.142891066025268),
+            Offset(11.09620007213612, 17.383984173344455),
+            Offset(17.300209258003058, 11.070490596267984),
+            Offset(27.534956667392745, 9.663130083300711),
+            Offset(33.08779486519604, 11.780896516785745),
+            Offset(36.320855918024264, 13.935980535304076),
+            Offset(38.432049841216, 15.842392336727073),
+            Offset(39.82413020790047, 17.500488782892777),
+            Offset(40.72033205663578, 18.904730886861373),
+            Offset(41.298064390530485, 20.054870365075026),
+            Offset(41.67005410532569, 20.971046911380125),
+            Offset(41.90696861336931, 21.67973344928915),
+            Offset(42.053593202790836, 22.20674188200616),
+            Offset(42.138249051890185, 22.5754914471036),
+            Offset(42.178981559543836, 22.806172538498764),
+            Offset(42.189074403337465, 22.915952464775465),
+          ],
+          <Offset>[
+            Offset(9.524593500000002, 35.06942850000001),
+            Offset(9.415797501752571, 34.92568890949069),
+            Offset(9.041246594727141, 34.406985020508685),
+            Offset(8.318646122604441, 33.282498652235056),
+            Offset(7.185750903645189, 31.025003059258125),
+            Offset(5.908224803956472, 26.187373812531924),
+            Offset(7.92773543984687, 15.24843266013899),
+            Offset(17.59598431222743, 6.583221120719635),
+            Offset(24.868334271425848, 4.882484812307943),
+            Offset(29.75116729808745, 4.852453497878906),
+            Offset(33.290783064619156, 5.352635579598315),
+            Offset(35.90685184308989, 6.1000863793859414),
+            Offset(37.82794959318526, 6.932003407856674),
+            Offset(39.23865172557073, 7.723330050291165),
+            Offset(40.27051693010311, 8.415741826725363),
+            Offset(41.015759120319025, 8.987096222613737),
+            Offset(41.53917410748996, 9.43276908619493),
+            Offset(41.886729203034385, 9.757226242895484),
+            Offset(42.09141940977926, 9.968799546481671),
+            Offset(42.1791543883521, 10.076022296832232),
+          ],
+          <Offset>[
+            Offset(9.524593500000002, 35.06942850000001),
+            Offset(9.415797501752571, 34.92568890949069),
+            Offset(9.041246594727141, 34.406985020508685),
+            Offset(8.318646122604441, 33.282498652235056),
+            Offset(7.185750903645189, 31.025003059258125),
+            Offset(5.908224803956472, 26.187373812531924),
+            Offset(7.92773543984687, 15.24843266013899),
+            Offset(17.59598431222743, 6.583221120719635),
+            Offset(24.868334271425848, 4.882484812307943),
+            Offset(29.75116729808745, 4.852453497878906),
+            Offset(33.290783064619156, 5.352635579598315),
+            Offset(35.90685184308989, 6.1000863793859414),
+            Offset(37.82794959318526, 6.932003407856674),
+            Offset(39.23865172557073, 7.723330050291165),
+            Offset(40.27051693010311, 8.415741826725363),
+            Offset(41.015759120319025, 8.987096222613737),
+            Offset(41.53917410748996, 9.43276908619493),
+            Offset(41.886729203034385, 9.757226242895484),
+            Offset(42.09141940977926, 9.968799546481671),
+            Offset(42.1791543883521, 10.076022296832232),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(9.524593500000002, 35.06942850000001),
+            Offset(9.415797501752571, 34.92568890949069),
+            Offset(9.041246594727141, 34.406985020508685),
+            Offset(8.318646122604441, 33.282498652235056),
+            Offset(7.185750903645189, 31.025003059258125),
+            Offset(5.908224803956472, 26.187373812531924),
+            Offset(7.92773543984687, 15.24843266013899),
+            Offset(17.59598431222743, 6.583221120719635),
+            Offset(24.868334271425848, 4.882484812307943),
+            Offset(29.75116729808745, 4.852453497878906),
+            Offset(33.290783064619156, 5.352635579598315),
+            Offset(35.90685184308989, 6.1000863793859414),
+            Offset(37.82794959318526, 6.932003407856674),
+            Offset(39.23865172557073, 7.723330050291165),
+            Offset(40.27051693010311, 8.415741826725363),
+            Offset(41.015759120319025, 8.987096222613737),
+            Offset(41.53917410748996, 9.43276908619493),
+            Offset(41.886729203034385, 9.757226242895484),
+            Offset(42.09141940977926, 9.968799546481671),
+            Offset(42.1791543883521, 10.076022296832232),
+          ],
+          <Offset>[
+            Offset(18.0395385, 35.0694285),
+            Offset(17.930325848774086, 35.009922992806736),
+            Offset(17.547570026086344, 34.79006469124885),
+            Offset(16.77400634956471, 34.28806903036052),
+            Offset(15.420844231339236, 33.190072627931144),
+            Offset(13.244382836612694, 30.510686536014962),
+            Offset(11.409353826406043, 23.05882750860248),
+            Offset(15.029393510076535, 14.865698083357398),
+            Offset(19.119657851027682, 11.732035307116437),
+            Offset(22.181561433566472, 10.327194014492921),
+            Offset(24.549319100345187, 9.637024560095686),
+            Offset(26.406516506834194, 9.36448501672809),
+            Offset(27.85067669401468, 9.342322127398774),
+            Offset(28.962368129917518, 9.43950727109096),
+            Offset(29.807762692890815, 9.582022806077507),
+            Offset(30.438561431422848, 9.729770800155638),
+            Offset(30.89419677764727, 9.861451665612325),
+            Offset(31.20484153286095, 9.966826116941988),
+            Offset(31.39360858309835, 10.041768004618815),
+            Offset(31.479212581732742, 10.084288975986702),
+          ],
+          <Offset>[
+            Offset(18.0395385, 35.0694285),
+            Offset(17.930325848774086, 35.009922992806736),
+            Offset(17.547570026086344, 34.79006469124885),
+            Offset(16.77400634956471, 34.28806903036052),
+            Offset(15.420844231339236, 33.190072627931144),
+            Offset(13.244382836612694, 30.510686536014962),
+            Offset(11.409353826406043, 23.05882750860248),
+            Offset(15.029393510076535, 14.865698083357398),
+            Offset(19.119657851027682, 11.732035307116437),
+            Offset(22.181561433566472, 10.327194014492921),
+            Offset(24.549319100345187, 9.637024560095686),
+            Offset(26.406516506834194, 9.36448501672809),
+            Offset(27.85067669401468, 9.342322127398774),
+            Offset(28.962368129917518, 9.43950727109096),
+            Offset(29.807762692890815, 9.582022806077507),
+            Offset(30.438561431422848, 9.729770800155638),
+            Offset(30.89419677764727, 9.861451665612325),
+            Offset(31.20484153286095, 9.966826116941988),
+            Offset(31.39360858309835, 10.041768004618815),
+            Offset(31.479212581732742, 10.084288975986702),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(18.0395385, 35.0694285),
+            Offset(17.930325848774086, 35.009922992806736),
+            Offset(17.547570026086344, 34.79006469124885),
+            Offset(16.77400634956471, 34.28806903036052),
+            Offset(15.420844231339236, 33.190072627931144),
+            Offset(13.244382836612694, 30.510686536014962),
+            Offset(11.409353826406043, 23.05882750860248),
+            Offset(15.029393510076535, 14.865698083357398),
+            Offset(19.119657851027682, 11.732035307116437),
+            Offset(22.181561433566472, 10.327194014492921),
+            Offset(24.549319100345187, 9.637024560095686),
+            Offset(26.406516506834194, 9.36448501672809),
+            Offset(27.85067669401468, 9.342322127398774),
+            Offset(28.962368129917518, 9.43950727109096),
+            Offset(29.807762692890815, 9.582022806077507),
+            Offset(30.438561431422848, 9.729770800155638),
+            Offset(30.89419677764727, 9.861451665612325),
+            Offset(31.20484153286095, 9.966826116941988),
+            Offset(31.39360858309835, 10.041768004618815),
+            Offset(31.479212581732742, 10.084288975986702),
+          ],
+          <Offset>[
+            Offset(18.0395385, 24.851494500000005),
+            Offset(18.03140674875334, 24.79248897638092),
+            Offset(18.007265630974544, 24.58247657361781),
+            Offset(17.98069080331527, 24.141636758008197),
+            Offset(18.018927713746866, 23.30796063469829),
+            Offset(18.432358104792343, 21.707296896827494),
+            Offset(20.78182764456223, 18.880885444731472),
+            Offset(24.96836586524185, 17.945607045938473),
+            Offset(27.339118444797872, 18.63044701159424),
+            Offset(28.751250053503288, 19.41072105191809),
+            Offset(29.690585876942034, 20.12678131722444),
+            Offset(30.323794871644772, 20.764887420234924),
+            Offset(30.7430591574652, 21.315049606403473),
+            Offset(31.021780794877273, 21.771047585874822),
+            Offset(31.20729986811339, 22.13732789073227),
+            Offset(31.32977092447313, 22.42240802683105),
+            Offset(31.408615872948143, 22.635424461423554),
+            Offset(31.456361381716757, 22.785091321150105),
+            Offset(31.48117073286292, 22.87914099663591),
+            Offset(31.489132596718107, 22.924219143929935),
+          ],
+          <Offset>[
+            Offset(18.0395385, 24.851494500000005),
+            Offset(18.03140674875334, 24.79248897638092),
+            Offset(18.007265630974544, 24.58247657361781),
+            Offset(17.98069080331527, 24.141636758008197),
+            Offset(18.018927713746866, 23.30796063469829),
+            Offset(18.432358104792343, 21.707296896827494),
+            Offset(20.78182764456223, 18.880885444731472),
+            Offset(24.96836586524185, 17.945607045938473),
+            Offset(27.339118444797872, 18.63044701159424),
+            Offset(28.751250053503288, 19.41072105191809),
+            Offset(29.690585876942034, 20.12678131722444),
+            Offset(30.323794871644772, 20.764887420234924),
+            Offset(30.7430591574652, 21.315049606403473),
+            Offset(31.021780794877273, 21.771047585874822),
+            Offset(31.20729986811339, 22.13732789073227),
+            Offset(31.32977092447313, 22.42240802683105),
+            Offset(31.408615872948143, 22.635424461423554),
+            Offset(31.456361381716757, 22.785091321150105),
+            Offset(31.48117073286292, 22.87914099663591),
+            Offset(31.489132596718107, 22.924219143929935),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.146341463415,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(38.4754065, 12.930571500000003),
+            Offset(38.58420249824743, 13.074311090509312),
+            Offset(38.958753405272866, 13.593014979491326),
+            Offset(39.681353877395566, 14.717501347764943),
+            Offset(40.81424909635481, 16.97499694074188),
+            Offset(42.09177519604353, 21.812626187468076),
+            Offset(40.07226456015313, 32.75156733986101),
+            Offset(30.404015687772564, 41.41677887928036),
+            Offset(23.131665728574156, 43.11751518769206),
+            Offset(18.248832701912562, 43.147546502121095),
+            Offset(14.709216935380839, 42.647364420401686),
+            Offset(12.093148156910111, 41.89991362061406),
+            Offset(10.17205040681474, 41.06799659214333),
+            Offset(8.761348274429261, 40.276669949708825),
+            Offset(7.729483069896876, 39.58425817327465),
+            Offset(6.984240879680973, 39.012903777386256),
+            Offset(6.460825892510038, 38.56723091380506),
+            Offset(6.1132707969656215, 38.242773757104516),
+            Offset(5.908580590220735, 38.03120045351832),
+            Offset(5.820845611647898, 37.923977703167765),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.4754065, 12.930571500000003),
+            Offset(38.58420249824743, 13.074311090509312),
+            Offset(38.958753405272866, 13.593014979491326),
+            Offset(39.681353877395566, 14.717501347764943),
+            Offset(40.81424909635481, 16.97499694074188),
+            Offset(42.09177519604353, 21.812626187468076),
+            Offset(40.07226456015313, 32.75156733986101),
+            Offset(30.404015687772564, 41.41677887928036),
+            Offset(23.131665728574156, 43.11751518769206),
+            Offset(18.248832701912562, 43.147546502121095),
+            Offset(14.709216935380839, 42.647364420401686),
+            Offset(12.093148156910111, 41.89991362061406),
+            Offset(10.17205040681474, 41.06799659214333),
+            Offset(8.761348274429261, 40.276669949708825),
+            Offset(7.729483069896876, 39.58425817327465),
+            Offset(6.984240879680973, 39.012903777386256),
+            Offset(6.460825892510038, 38.56723091380506),
+            Offset(6.1132707969656215, 38.242773757104516),
+            Offset(5.908580590220735, 38.03120045351832),
+            Offset(5.820845611647898, 37.923977703167765),
+          ],
+          <Offset>[
+            Offset(29.9604615, 12.930571500000003),
+            Offset(30.069674151225918, 12.990077007193268),
+            Offset(30.452429973913663, 13.20993530875116),
+            Offset(31.225993650435292, 13.711930969639477),
+            Offset(32.57915576866076, 14.80992737206886),
+            Offset(34.755617163387306, 17.489313463985035),
+            Offset(36.59064617359396, 24.94117249139752),
+            Offset(32.970606489923455, 33.1343019166426),
+            Offset(28.880342148972325, 36.26796469288357),
+            Offset(25.81843856643354, 37.672805985507075),
+            Offset(23.450680899654806, 38.36297543990432),
+            Offset(21.593483493165806, 38.63551498327191),
+            Offset(20.149323305985323, 38.657677872601234),
+            Offset(19.03763187008248, 38.56049272890903),
+            Offset(18.192237307109178, 38.417977193922496),
+            Offset(17.56143856857715, 38.27022919984435),
+            Offset(17.10580322235273, 38.13854833438767),
+            Offset(16.795158467139053, 38.03317388305801),
+            Offset(16.606391416901644, 37.95823199538118),
+            Offset(16.520787418267258, 37.9157110240133),
+          ],
+          <Offset>[
+            Offset(29.9604615, 12.930571500000003),
+            Offset(30.069674151225918, 12.990077007193268),
+            Offset(30.452429973913663, 13.20993530875116),
+            Offset(31.225993650435292, 13.711930969639477),
+            Offset(32.57915576866076, 14.80992737206886),
+            Offset(34.755617163387306, 17.489313463985035),
+            Offset(36.59064617359396, 24.94117249139752),
+            Offset(32.970606489923455, 33.1343019166426),
+            Offset(28.880342148972325, 36.26796469288357),
+            Offset(25.81843856643354, 37.672805985507075),
+            Offset(23.450680899654806, 38.36297543990432),
+            Offset(21.593483493165806, 38.63551498327191),
+            Offset(20.149323305985323, 38.657677872601234),
+            Offset(19.03763187008248, 38.56049272890903),
+            Offset(18.192237307109178, 38.417977193922496),
+            Offset(17.56143856857715, 38.27022919984435),
+            Offset(17.10580322235273, 38.13854833438767),
+            Offset(16.795158467139053, 38.03317388305801),
+            Offset(16.606391416901644, 37.95823199538118),
+            Offset(16.520787418267258, 37.9157110240133),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(29.9604615, 12.930571500000003),
+            Offset(30.069674151225918, 12.990077007193268),
+            Offset(30.452429973913663, 13.20993530875116),
+            Offset(31.225993650435292, 13.711930969639477),
+            Offset(32.57915576866076, 14.80992737206886),
+            Offset(34.755617163387306, 17.489313463985035),
+            Offset(36.59064617359396, 24.94117249139752),
+            Offset(32.970606489923455, 33.1343019166426),
+            Offset(28.880342148972325, 36.26796469288357),
+            Offset(25.81843856643354, 37.672805985507075),
+            Offset(23.450680899654806, 38.36297543990432),
+            Offset(21.593483493165806, 38.63551498327191),
+            Offset(20.149323305985323, 38.657677872601234),
+            Offset(19.03763187008248, 38.56049272890903),
+            Offset(18.192237307109178, 38.417977193922496),
+            Offset(17.56143856857715, 38.27022919984435),
+            Offset(17.10580322235273, 38.13854833438767),
+            Offset(16.795158467139053, 38.03317388305801),
+            Offset(16.606391416901644, 37.95823199538118),
+            Offset(16.520787418267258, 37.9157110240133),
+          ],
+          <Offset>[
+            Offset(29.9604615, 23.148505500000002),
+            Offset(29.968593251246663, 23.207511023619084),
+            Offset(29.992734369025463, 23.4175234263822),
+            Offset(30.019309196684734, 23.858363241991796),
+            Offset(29.98107228625313, 24.692039365301717),
+            Offset(29.567641895207657, 26.2927031031725),
+            Offset(27.218172355437773, 29.119114555268528),
+            Offset(23.031634134758143, 30.054392954061523),
+            Offset(20.66088155520213, 29.369552988405765),
+            Offset(19.248749946496723, 28.58927894808191),
+            Offset(18.30941412305796, 27.87321868277556),
+            Offset(17.676205128355228, 27.235112579765072),
+            Offset(17.256940842534803, 26.684950393596534),
+            Offset(16.978219205122727, 26.228952414125168),
+            Offset(16.7927001318866, 25.862672109267738),
+            Offset(16.67022907552687, 25.57759197316894),
+            Offset(16.591384127051857, 25.364575538576442),
+            Offset(16.543638618283246, 25.21490867884989),
+            Offset(16.518829267137072, 25.12085900336409),
+            Offset(16.510867403281896, 25.075780856070065),
+          ],
+          <Offset>[
+            Offset(29.9604615, 23.148505500000002),
+            Offset(29.968593251246663, 23.207511023619084),
+            Offset(29.992734369025463, 23.4175234263822),
+            Offset(30.019309196684734, 23.858363241991796),
+            Offset(29.98107228625313, 24.692039365301717),
+            Offset(29.567641895207657, 26.2927031031725),
+            Offset(27.218172355437773, 29.119114555268528),
+            Offset(23.031634134758143, 30.054392954061523),
+            Offset(20.66088155520213, 29.369552988405765),
+            Offset(19.248749946496723, 28.58927894808191),
+            Offset(18.30941412305796, 27.87321868277556),
+            Offset(17.676205128355228, 27.235112579765072),
+            Offset(17.256940842534803, 26.684950393596534),
+            Offset(16.978219205122727, 26.228952414125168),
+            Offset(16.7927001318866, 25.862672109267738),
+            Offset(16.67022907552687, 25.57759197316894),
+            Offset(16.591384127051857, 25.364575538576442),
+            Offset(16.543638618283246, 25.21490867884989),
+            Offset(16.518829267137072, 25.12085900336409),
+            Offset(16.510867403281896, 25.075780856070065),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(29.9604615, 23.148505500000002),
+            Offset(29.968593251246663, 23.207511023619084),
+            Offset(29.992734369025463, 23.4175234263822),
+            Offset(30.019309196684734, 23.858363241991796),
+            Offset(29.98107228625313, 24.692039365301717),
+            Offset(29.567641895207657, 26.2927031031725),
+            Offset(27.218172355437773, 29.119114555268528),
+            Offset(23.031634134758143, 30.054392954061523),
+            Offset(20.66088155520213, 29.369552988405765),
+            Offset(19.248749946496723, 28.58927894808191),
+            Offset(18.30941412305796, 27.87321868277556),
+            Offset(17.676205128355228, 27.235112579765072),
+            Offset(17.256940842534803, 26.684950393596534),
+            Offset(16.978219205122727, 26.228952414125168),
+            Offset(16.7927001318866, 25.862672109267738),
+            Offset(16.67022907552687, 25.57759197316894),
+            Offset(16.591384127051857, 25.364575538576442),
+            Offset(16.543638618283246, 25.21490867884989),
+            Offset(16.518829267137072, 25.12085900336409),
+            Offset(16.510867403281896, 25.075780856070065),
+          ],
+          <Offset>[
+            Offset(38.4754065, 23.148505500000002),
+            Offset(38.48312159826818, 23.291745106935128),
+            Offset(38.49905780038466, 23.800603097122366),
+            Offset(38.474669423645004, 24.863933620117265),
+            Offset(38.216165613947176, 26.85710893397474),
+            Offset(36.90379992786388, 30.61601582665554),
+            Offset(30.699790741996942, 36.92950940373201),
+            Offset(20.465043332607248, 38.336869916699285),
+            Offset(14.912205134803964, 36.219103483214255),
+            Offset(11.679144081975746, 34.064019464695924),
+            Offset(9.567950158783995, 32.15760766327293),
+            Offset(8.175869792099533, 30.499511217107223),
+            Offset(7.2796679433642195, 29.095269113138635),
+            Offset(6.701935609469508, 27.945129634924964),
+            Offset(6.3299458946742995, 27.028953088619883),
+            Offset(6.093031386630692, 26.32026655071084),
+            Offset(5.946406797209165, 25.793258117993837),
+            Offset(5.861750948109816, 25.424508552896395),
+            Offset(5.821018440456163, 25.19382746150123),
+            Offset(5.810925596662535, 25.084047535224535),
+          ],
+          <Offset>[
+            Offset(38.4754065, 23.148505500000002),
+            Offset(38.48312159826818, 23.291745106935128),
+            Offset(38.49905780038466, 23.800603097122366),
+            Offset(38.474669423645004, 24.863933620117265),
+            Offset(38.216165613947176, 26.85710893397474),
+            Offset(36.90379992786388, 30.61601582665554),
+            Offset(30.699790741996942, 36.92950940373201),
+            Offset(20.465043332607248, 38.336869916699285),
+            Offset(14.912205134803964, 36.219103483214255),
+            Offset(11.679144081975746, 34.064019464695924),
+            Offset(9.567950158783995, 32.15760766327293),
+            Offset(8.175869792099533, 30.499511217107223),
+            Offset(7.2796679433642195, 29.095269113138635),
+            Offset(6.701935609469508, 27.945129634924964),
+            Offset(6.3299458946742995, 27.028953088619883),
+            Offset(6.093031386630692, 26.32026655071084),
+            Offset(5.946406797209165, 25.793258117993837),
+            Offset(5.861750948109816, 25.424508552896395),
+            Offset(5.821018440456163, 25.19382746150123),
+            Offset(5.810925596662535, 25.084047535224535),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(38.4754065, 23.148505500000002),
+            Offset(38.48312159826818, 23.291745106935128),
+            Offset(38.49905780038466, 23.800603097122366),
+            Offset(38.474669423645004, 24.863933620117265),
+            Offset(38.216165613947176, 26.85710893397474),
+            Offset(36.90379992786388, 30.61601582665554),
+            Offset(30.699790741996942, 36.92950940373201),
+            Offset(20.465043332607248, 38.336869916699285),
+            Offset(14.912205134803964, 36.219103483214255),
+            Offset(11.679144081975746, 34.064019464695924),
+            Offset(9.567950158783995, 32.15760766327293),
+            Offset(8.175869792099533, 30.499511217107223),
+            Offset(7.2796679433642195, 29.095269113138635),
+            Offset(6.701935609469508, 27.945129634924964),
+            Offset(6.3299458946742995, 27.028953088619883),
+            Offset(6.093031386630692, 26.32026655071084),
+            Offset(5.946406797209165, 25.793258117993837),
+            Offset(5.861750948109816, 25.424508552896395),
+            Offset(5.821018440456163, 25.19382746150123),
+            Offset(5.810925596662535, 25.084047535224535),
+          ],
+          <Offset>[
+            Offset(38.4754065, 12.930571500000003),
+            Offset(38.58420249824743, 13.074311090509312),
+            Offset(38.958753405272866, 13.593014979491326),
+            Offset(39.681353877395566, 14.717501347764943),
+            Offset(40.81424909635481, 16.97499694074188),
+            Offset(42.09177519604353, 21.812626187468076),
+            Offset(40.07226456015313, 32.75156733986101),
+            Offset(30.404015687772564, 41.41677887928036),
+            Offset(23.131665728574156, 43.11751518769206),
+            Offset(18.248832701912562, 43.147546502121095),
+            Offset(14.709216935380839, 42.647364420401686),
+            Offset(12.093148156910111, 41.89991362061406),
+            Offset(10.17205040681474, 41.06799659214333),
+            Offset(8.761348274429261, 40.276669949708825),
+            Offset(7.729483069896876, 39.58425817327465),
+            Offset(6.984240879680973, 39.012903777386256),
+            Offset(6.460825892510038, 38.56723091380506),
+            Offset(6.1132707969656215, 38.242773757104516),
+            Offset(5.908580590220735, 38.03120045351832),
+            Offset(5.820845611647898, 37.923977703167765),
+          ],
+          <Offset>[
+            Offset(38.4754065, 12.930571500000003),
+            Offset(38.58420249824743, 13.074311090509312),
+            Offset(38.958753405272866, 13.593014979491326),
+            Offset(39.681353877395566, 14.717501347764943),
+            Offset(40.81424909635481, 16.97499694074188),
+            Offset(42.09177519604353, 21.812626187468076),
+            Offset(40.07226456015313, 32.75156733986101),
+            Offset(30.404015687772564, 41.41677887928036),
+            Offset(23.131665728574156, 43.11751518769206),
+            Offset(18.248832701912562, 43.147546502121095),
+            Offset(14.709216935380839, 42.647364420401686),
+            Offset(12.093148156910111, 41.89991362061406),
+            Offset(10.17205040681474, 41.06799659214333),
+            Offset(8.761348274429261, 40.276669949708825),
+            Offset(7.729483069896876, 39.58425817327465),
+            Offset(6.984240879680973, 39.012903777386256),
+            Offset(6.460825892510038, 38.56723091380506),
+            Offset(6.1132707969656215, 38.242773757104516),
+            Offset(5.908580590220735, 38.03120045351832),
+            Offset(5.820845611647898, 37.923977703167765),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.146341463415,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(28.257472499999995, 12.930571500000006),
+            Offset(28.366768481821616, 12.973230190530057),
+            Offset(28.75116528764182, 13.133319374603126),
+            Offset(29.53492160504324, 13.510816894014384),
+            Offset(30.932137103121953, 14.376913458334252),
+            Offset(33.28838555685606, 16.624650919288428),
+            Offset(35.89432249628212, 23.37909352170482),
+            Offset(33.48392465035364, 31.477806524115046),
+            Offset(30.030077433051954, 34.89805459392186),
+            Offset(27.33235973933773, 36.577857882184276),
+            Offset(25.1989736925096, 37.50609764380484),
+            Offset(23.49355056041695, 37.98263525580348),
+            Offset(22.14477788581944, 38.17561412869281),
+            Offset(21.092888589213125, 38.217257284749074),
+            Offset(20.284788154551638, 38.18472099805207),
+            Offset(19.676878106356384, 38.12169428433597),
+            Offset(19.234798688321266, 38.052811818504196),
+            Offset(18.93153600117374, 37.99125390824871),
+            Offset(18.74595358223783, 37.94363830375375),
+            Offset(18.66077577959113, 37.914057688182396),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(28.257472499999995, 12.930571500000006),
+            Offset(28.366768481821616, 12.973230190530057),
+            Offset(28.75116528764182, 13.133319374603126),
+            Offset(29.53492160504324, 13.510816894014384),
+            Offset(30.932137103121953, 14.376913458334252),
+            Offset(33.28838555685606, 16.624650919288428),
+            Offset(35.89432249628212, 23.37909352170482),
+            Offset(33.48392465035364, 31.477806524115046),
+            Offset(30.030077433051954, 34.89805459392186),
+            Offset(27.33235973933773, 36.577857882184276),
+            Offset(25.1989736925096, 37.50609764380484),
+            Offset(23.49355056041695, 37.98263525580348),
+            Offset(22.14477788581944, 38.17561412869281),
+            Offset(21.092888589213125, 38.217257284749074),
+            Offset(20.284788154551638, 38.18472099805207),
+            Offset(19.676878106356384, 38.12169428433597),
+            Offset(19.234798688321266, 38.052811818504196),
+            Offset(18.93153600117374, 37.99125390824871),
+            Offset(18.74595358223783, 37.94363830375375),
+            Offset(18.66077577959113, 37.914057688182396),
+          ],
+          <Offset>[
+            Offset(19.742527499999994, 12.930571500000006),
+            Offset(19.852240134800102, 12.888996107214012),
+            Offset(20.244841856282616, 12.75023970386296),
+            Offset(21.07956137808297, 12.505246515888919),
+            Offset(22.697043775427904, 12.211843889661228),
+            Offset(25.95222752419984, 12.30133819580539),
+            Offset(32.41270410972295, 15.568698673241332),
+            Offset(36.05051545250454, 23.195329561477283),
+            Offset(35.77875385345012, 28.048504099113373),
+            Offset(34.901965603858706, 31.10311736557026),
+            Offset(33.940437656783566, 33.221708663307474),
+            Offset(32.993885896672644, 34.71823661846133),
+            Offset(32.12205078499002, 35.7652954091507),
+            Offset(31.369172184866343, 36.50108006394928),
+            Offset(30.74754239176394, 37.01844001869992),
+            Offset(30.25407579525256, 37.379019706794075),
+            Offset(29.879776018163955, 37.6241292390868),
+            Offset(29.61342367134717, 37.781654034202205),
+            Offset(29.44376440891874, 37.87066984561661),
+            Offset(29.36071758621049, 37.90579100902793),
+          ],
+          <Offset>[
+            Offset(19.742527499999994, 12.930571500000006),
+            Offset(19.852240134800102, 12.888996107214012),
+            Offset(20.244841856282616, 12.75023970386296),
+            Offset(21.07956137808297, 12.505246515888919),
+            Offset(22.697043775427904, 12.211843889661228),
+            Offset(25.95222752419984, 12.30133819580539),
+            Offset(32.41270410972295, 15.568698673241332),
+            Offset(36.05051545250454, 23.195329561477283),
+            Offset(35.77875385345012, 28.048504099113373),
+            Offset(34.901965603858706, 31.10311736557026),
+            Offset(33.940437656783566, 33.221708663307474),
+            Offset(32.993885896672644, 34.71823661846133),
+            Offset(32.12205078499002, 35.7652954091507),
+            Offset(31.369172184866343, 36.50108006394928),
+            Offset(30.74754239176394, 37.01844001869992),
+            Offset(30.25407579525256, 37.379019706794075),
+            Offset(29.879776018163955, 37.6241292390868),
+            Offset(29.61342367134717, 37.781654034202205),
+            Offset(29.44376440891874, 37.87066984561661),
+            Offset(29.36071758621049, 37.90579100902793),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(19.742527499999994, 12.930571500000006),
+            Offset(19.852240134800102, 12.888996107214012),
+            Offset(20.244841856282616, 12.75023970386296),
+            Offset(21.07956137808297, 12.505246515888919),
+            Offset(22.697043775427904, 12.211843889661228),
+            Offset(25.95222752419984, 12.30133819580539),
+            Offset(32.41270410972295, 15.568698673241332),
+            Offset(36.05051545250454, 23.195329561477283),
+            Offset(35.77875385345012, 28.048504099113373),
+            Offset(34.901965603858706, 31.10311736557026),
+            Offset(33.940437656783566, 33.221708663307474),
+            Offset(32.993885896672644, 34.71823661846133),
+            Offset(32.12205078499002, 35.7652954091507),
+            Offset(31.369172184866343, 36.50108006394928),
+            Offset(30.74754239176394, 37.01844001869992),
+            Offset(30.25407579525256, 37.379019706794075),
+            Offset(29.879776018163955, 37.6241292390868),
+            Offset(29.61342367134717, 37.781654034202205),
+            Offset(29.44376440891874, 37.87066984561661),
+            Offset(29.36071758621049, 37.90579100902793),
+          ],
+          <Offset>[
+            Offset(19.742527499999998, 23.148505500000006),
+            Offset(19.751159234820847, 23.10643012363983),
+            Offset(19.785146251394416, 22.957827821494),
+            Offset(19.872876924332413, 22.65167878824124),
+            Offset(20.098960293020276, 22.093955882894086),
+            Offset(20.764252256020193, 21.104727834992858),
+            Offset(23.040230291566765, 19.74664073711234),
+            Offset(26.111543097339222, 20.115420598896208),
+            Offset(27.55929325967993, 21.15009239463557),
+            Offset(28.33227698392189, 22.01959032814509),
+            Offset(28.79917088018672, 22.731951906178715),
+            Offset(29.076607531862066, 23.317834214954495),
+            Offset(29.229668321539503, 23.792567930146006),
+            Offset(29.309759519906592, 24.16953974916542),
+            Offset(29.348005216541363, 24.46313493404516),
+            Offset(29.362866302202278, 24.686382480118663),
+            Offset(29.365356922863082, 24.85015644327557),
+            Offset(29.361903822491364, 24.96338882999409),
+            Offset(29.356202259154166, 25.033296853599516),
+            Offset(29.350797571225126, 25.065860841084696),
+          ],
+          <Offset>[
+            Offset(19.742527499999998, 23.148505500000006),
+            Offset(19.751159234820847, 23.10643012363983),
+            Offset(19.785146251394416, 22.957827821494),
+            Offset(19.872876924332413, 22.65167878824124),
+            Offset(20.098960293020276, 22.093955882894086),
+            Offset(20.764252256020193, 21.104727834992858),
+            Offset(23.040230291566765, 19.74664073711234),
+            Offset(26.111543097339222, 20.115420598896208),
+            Offset(27.55929325967993, 21.15009239463557),
+            Offset(28.33227698392189, 22.01959032814509),
+            Offset(28.79917088018672, 22.731951906178715),
+            Offset(29.076607531862066, 23.317834214954495),
+            Offset(29.229668321539503, 23.792567930146006),
+            Offset(29.309759519906592, 24.16953974916542),
+            Offset(29.348005216541363, 24.46313493404516),
+            Offset(29.362866302202278, 24.686382480118663),
+            Offset(29.365356922863082, 24.85015644327557),
+            Offset(29.361903822491364, 24.96338882999409),
+            Offset(29.356202259154166, 25.033296853599516),
+            Offset(29.350797571225126, 25.065860841084696),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(19.742527499999998, 23.148505500000006),
+            Offset(19.751159234820847, 23.10643012363983),
+            Offset(19.785146251394416, 22.957827821494),
+            Offset(19.872876924332413, 22.65167878824124),
+            Offset(20.098960293020276, 22.093955882894086),
+            Offset(20.764252256020193, 21.104727834992858),
+            Offset(23.040230291566765, 19.74664073711234),
+            Offset(26.111543097339222, 20.115420598896208),
+            Offset(27.55929325967993, 21.15009239463557),
+            Offset(28.33227698392189, 22.01959032814509),
+            Offset(28.79917088018672, 22.731951906178715),
+            Offset(29.076607531862066, 23.317834214954495),
+            Offset(29.229668321539503, 23.792567930146006),
+            Offset(29.309759519906592, 24.16953974916542),
+            Offset(29.348005216541363, 24.46313493404516),
+            Offset(29.362866302202278, 24.686382480118663),
+            Offset(29.365356922863082, 24.85015644327557),
+            Offset(29.361903822491364, 24.96338882999409),
+            Offset(29.356202259154166, 25.033296853599516),
+            Offset(29.350797571225126, 25.065860841084696),
+          ],
+          <Offset>[
+            Offset(28.2574725, 23.148505500000006),
+            Offset(28.26568758184236, 23.190664206955873),
+            Offset(28.29146968275362, 23.340907492234166),
+            Offset(28.328237151292683, 23.657249166366704),
+            Offset(28.334053620714325, 24.25902545156711),
+            Offset(28.100410288676414, 25.428040558475896),
+            Offset(26.521848678125934, 27.55703558557583),
+            Offset(23.544952295188327, 28.39789756153397),
+            Offset(21.810616839281764, 27.99964288944406),
+            Offset(20.762671119400913, 27.4943308447591),
+            Offset(20.057706915912753, 27.016340886676083),
+            Offset(19.57627219560637, 26.582232852296645),
+            Offset(19.25239542236892, 26.202886649688107),
+            Offset(19.033475924253374, 25.885716969965216),
+            Offset(18.88525097932906, 25.629415913397306),
+            Offset(18.7856686133061, 25.429057057660565),
+            Offset(18.720379593020393, 25.278839022692964),
+            Offset(18.680016152317933, 25.172988704040595),
+            Offset(18.65839143247326, 25.106265311736657),
+            Offset(18.650855764605765, 25.074127520239166),
+          ],
+          <Offset>[
+            Offset(28.2574725, 23.148505500000006),
+            Offset(28.26568758184236, 23.190664206955873),
+            Offset(28.29146968275362, 23.340907492234166),
+            Offset(28.328237151292683, 23.657249166366704),
+            Offset(28.334053620714325, 24.25902545156711),
+            Offset(28.100410288676414, 25.428040558475896),
+            Offset(26.521848678125934, 27.55703558557583),
+            Offset(23.544952295188327, 28.39789756153397),
+            Offset(21.810616839281764, 27.99964288944406),
+            Offset(20.762671119400913, 27.4943308447591),
+            Offset(20.057706915912753, 27.016340886676083),
+            Offset(19.57627219560637, 26.582232852296645),
+            Offset(19.25239542236892, 26.202886649688107),
+            Offset(19.033475924253374, 25.885716969965216),
+            Offset(18.88525097932906, 25.629415913397306),
+            Offset(18.7856686133061, 25.429057057660565),
+            Offset(18.720379593020393, 25.278839022692964),
+            Offset(18.680016152317933, 25.172988704040595),
+            Offset(18.65839143247326, 25.106265311736657),
+            Offset(18.650855764605765, 25.074127520239166),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(28.2574725, 23.148505500000006),
+            Offset(28.26568758184236, 23.190664206955873),
+            Offset(28.29146968275362, 23.340907492234166),
+            Offset(28.328237151292683, 23.657249166366704),
+            Offset(28.334053620714325, 24.25902545156711),
+            Offset(28.100410288676414, 25.428040558475896),
+            Offset(26.521848678125934, 27.55703558557583),
+            Offset(23.544952295188327, 28.39789756153397),
+            Offset(21.810616839281764, 27.99964288944406),
+            Offset(20.762671119400913, 27.4943308447591),
+            Offset(20.057706915912753, 27.016340886676083),
+            Offset(19.57627219560637, 26.582232852296645),
+            Offset(19.25239542236892, 26.202886649688107),
+            Offset(19.033475924253374, 25.885716969965216),
+            Offset(18.88525097932906, 25.629415913397306),
+            Offset(18.7856686133061, 25.429057057660565),
+            Offset(18.720379593020393, 25.278839022692964),
+            Offset(18.680016152317933, 25.172988704040595),
+            Offset(18.65839143247326, 25.106265311736657),
+            Offset(18.650855764605765, 25.074127520239166),
+          ],
+          <Offset>[
+            Offset(28.257472499999995, 12.930571500000006),
+            Offset(28.366768481821616, 12.973230190530057),
+            Offset(28.75116528764182, 13.133319374603126),
+            Offset(29.53492160504324, 13.510816894014384),
+            Offset(30.932137103121953, 14.376913458334252),
+            Offset(33.28838555685606, 16.624650919288428),
+            Offset(35.89432249628212, 23.37909352170482),
+            Offset(33.48392465035364, 31.477806524115046),
+            Offset(30.030077433051954, 34.89805459392186),
+            Offset(27.33235973933773, 36.577857882184276),
+            Offset(25.1989736925096, 37.50609764380484),
+            Offset(23.49355056041695, 37.98263525580348),
+            Offset(22.14477788581944, 38.17561412869281),
+            Offset(21.092888589213125, 38.217257284749074),
+            Offset(20.284788154551638, 38.18472099805207),
+            Offset(19.676878106356384, 38.12169428433597),
+            Offset(19.234798688321266, 38.052811818504196),
+            Offset(18.93153600117374, 37.99125390824871),
+            Offset(18.74595358223783, 37.94363830375375),
+            Offset(18.66077577959113, 37.914057688182396),
+          ],
+          <Offset>[
+            Offset(28.257472499999995, 12.930571500000006),
+            Offset(28.366768481821616, 12.973230190530057),
+            Offset(28.75116528764182, 13.133319374603126),
+            Offset(29.53492160504324, 13.510816894014384),
+            Offset(30.932137103121953, 14.376913458334252),
+            Offset(33.28838555685606, 16.624650919288428),
+            Offset(35.89432249628212, 23.37909352170482),
+            Offset(33.48392465035364, 31.477806524115046),
+            Offset(30.030077433051954, 34.89805459392186),
+            Offset(27.33235973933773, 36.577857882184276),
+            Offset(25.1989736925096, 37.50609764380484),
+            Offset(23.49355056041695, 37.98263525580348),
+            Offset(22.14477788581944, 38.17561412869281),
+            Offset(21.092888589213125, 38.217257284749074),
+            Offset(20.284788154551638, 38.18472099805207),
+            Offset(19.676878106356384, 38.12169428433597),
+            Offset(19.234798688321266, 38.052811818504196),
+            Offset(18.93153600117374, 37.99125390824871),
+            Offset(18.74595358223783, 37.94363830375375),
+            Offset(18.66077577959113, 37.914057688182396),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+  ],
+  matchTextDirection: true,
+);
diff --git a/lib/src/material/animated_icons/data/menu_arrow.g.dart b/lib/src/material/animated_icons/data/menu_arrow.g.dart
new file mode 100644
index 0000000..78613e3
--- /dev/null
+++ b/lib/src/material/animated_icons/data/menu_arrow.g.dart
@@ -0,0 +1,1027 @@
+// Copyright 2014 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.
+
+// AUTOGENERATED FILE DO NOT EDIT!
+// This file was generated by vitool.
+part of material_animated_icons;
+const _AnimatedIconData _$menu_arrow = _AnimatedIconData(
+  Size(48.0, 48.0),
+  <_PathFrames>[
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(6.0, 26.0),
+            Offset(5.976562557689849, 25.638185989482512),
+            Offset(5.951781669661045, 24.367972149512962),
+            Offset(6.172793116155802, 21.823631861702058),
+            Offset(7.363587976838016, 17.665129222832853),
+            Offset(11.400806749308899, 11.800457098273661),
+            Offset(17.41878573585796, 8.03287301910486),
+            Offset(24.257523532175192, 6.996159828679087),
+            Offset(29.90338248135665, 8.291042849526),
+            Offset(33.76252909490214, 10.56619705548221),
+            Offset(36.23501636298456, 12.973675163618006),
+            Offset(37.77053540180521, 15.158665125787222),
+            Offset(38.70420448893307, 17.008159945496722),
+            Offset(39.260392038988186, 18.5104805430827),
+            Offset(39.58393261852967, 19.691668944482075),
+            Offset(39.766765502294305, 20.58840471665747),
+            Offset(39.866421084642994, 21.237322746452932),
+            Offset(39.91802804639694, 21.671102155152063),
+            Offset(39.94204075298555, 21.917555098992118),
+            Offset(39.94920417650143, 21.999827480806236),
+            Offset(39.94921875, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 26.0),
+            Offset(5.976562557689849, 25.638185989482512),
+            Offset(5.951781669661045, 24.367972149512962),
+            Offset(6.172793116155802, 21.823631861702058),
+            Offset(7.363587976838016, 17.665129222832853),
+            Offset(11.400806749308899, 11.800457098273661),
+            Offset(17.41878573585796, 8.03287301910486),
+            Offset(24.257523532175192, 6.996159828679087),
+            Offset(29.90338248135665, 8.291042849526),
+            Offset(33.76252909490214, 10.56619705548221),
+            Offset(36.23501636298456, 12.973675163618006),
+            Offset(37.77053540180521, 15.158665125787222),
+            Offset(38.70420448893307, 17.008159945496722),
+            Offset(39.260392038988186, 18.5104805430827),
+            Offset(39.58393261852967, 19.691668944482075),
+            Offset(39.766765502294305, 20.58840471665747),
+            Offset(39.866421084642994, 21.237322746452932),
+            Offset(39.91802804639694, 21.671102155152063),
+            Offset(39.94204075298555, 21.917555098992118),
+            Offset(39.94920417650143, 21.999827480806236),
+            Offset(39.94921875, 22.0),
+          ],
+          <Offset>[
+            Offset(42.0, 26.0),
+            Offset(41.91421333157091, 26.360426629492423),
+            Offset(41.55655262500356, 27.60382930516768),
+            Offset(40.57766190556539, 29.99090297157744),
+            Offset(38.19401046368096, 33.57567286235671),
+            Offset(32.70215654116029, 37.756226919427284),
+            Offset(26.22621984436523, 39.26167875408963),
+            Offset(20.102351173097617, 38.04803275423973),
+            Offset(15.903199608216863, 35.25316524725598),
+            Offset(13.57741782841064, 32.27000071222682),
+            Offset(12.442030802775209, 29.665215617986277),
+            Offset(11.981806515947115, 27.560177578292762),
+            Offset(11.879421136842055, 25.918712565594948),
+            Offset(11.95091483982305, 24.66543021784112),
+            Offset(12.092167805674123, 23.72603017548901),
+            Offset(12.245452640806768, 23.03857447590349),
+            Offset(12.379956070248545, 22.554583229506296),
+            Offset(12.480582865035407, 22.237279988168645),
+            Offset(12.541514124262473, 22.059212079933666),
+            Offset(12.562455771803593, 22.000123717314214),
+            Offset(12.562499999999996, 22.000000000000004),
+          ],
+          <Offset>[
+            Offset(42.0, 26.0),
+            Offset(41.91421333157091, 26.360426629492423),
+            Offset(41.55655262500356, 27.60382930516768),
+            Offset(40.57766190556539, 29.99090297157744),
+            Offset(38.19401046368096, 33.57567286235671),
+            Offset(32.70215654116029, 37.756226919427284),
+            Offset(26.22621984436523, 39.26167875408963),
+            Offset(20.102351173097617, 38.04803275423973),
+            Offset(15.903199608216863, 35.25316524725598),
+            Offset(13.57741782841064, 32.27000071222682),
+            Offset(12.442030802775209, 29.665215617986277),
+            Offset(11.981806515947115, 27.560177578292762),
+            Offset(11.879421136842055, 25.918712565594948),
+            Offset(11.95091483982305, 24.66543021784112),
+            Offset(12.092167805674123, 23.72603017548901),
+            Offset(12.245452640806768, 23.03857447590349),
+            Offset(12.379956070248545, 22.554583229506296),
+            Offset(12.480582865035407, 22.237279988168645),
+            Offset(12.541514124262473, 22.059212079933666),
+            Offset(12.562455771803593, 22.000123717314214),
+            Offset(12.562499999999996, 22.000000000000004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 26.0),
+            Offset(41.91421333157091, 26.360426629492423),
+            Offset(41.55655262500356, 27.60382930516768),
+            Offset(40.57766190556539, 29.99090297157744),
+            Offset(38.19401046368096, 33.57567286235671),
+            Offset(32.70215654116029, 37.756226919427284),
+            Offset(26.22621984436523, 39.26167875408963),
+            Offset(20.102351173097617, 38.04803275423973),
+            Offset(15.903199608216863, 35.25316524725598),
+            Offset(13.57741782841064, 32.27000071222682),
+            Offset(12.442030802775209, 29.665215617986277),
+            Offset(11.981806515947115, 27.560177578292762),
+            Offset(11.879421136842055, 25.918712565594948),
+            Offset(11.95091483982305, 24.66543021784112),
+            Offset(12.092167805674123, 23.72603017548901),
+            Offset(12.245452640806768, 23.03857447590349),
+            Offset(12.379956070248545, 22.554583229506296),
+            Offset(12.480582865035407, 22.237279988168645),
+            Offset(12.541514124262473, 22.059212079933666),
+            Offset(12.562455771803593, 22.000123717314214),
+            Offset(12.562499999999996, 22.000000000000004),
+          ],
+          <Offset>[
+            Offset(42.0, 22.0),
+            Offset(41.99458528858859, 22.361234167441474),
+            Offset(41.91859127809106, 23.620246996030513),
+            Offset(41.501535596836376, 26.09905798461081),
+            Offset(40.02840620381446, 30.021099432452637),
+            Offset(35.79419835461124, 35.2186537827727),
+            Offset(30.076040790179817, 38.175916954629336),
+            Offset(24.067012730992623, 38.57855959743385),
+            Offset(19.453150566288006, 37.096490556388844),
+            Offset(16.506465839286186, 34.99409280868502),
+            Offset(14.73924581501028, 32.939784778587686),
+            Offset(13.715334530064114, 31.165018854170466),
+            Offset(13.140377980959201, 29.714761542791386),
+            Offset(12.83036672005031, 28.56755327976071),
+            Offset(12.672939622830032, 27.683643609921106),
+            Offset(12.600162038813565, 27.02281609043513),
+            Offset(12.571432188039635, 26.54999771317575),
+            Offset(12.56310619400641, 26.23642863509033),
+            Offset(12.562193301685781, 26.059158626029138),
+            Offset(12.562499038934627, 26.000123717080207),
+            Offset(12.562499999999996, 26.000000000000004),
+          ],
+          <Offset>[
+            Offset(42.0, 22.0),
+            Offset(41.99458528858859, 22.361234167441474),
+            Offset(41.91859127809106, 23.620246996030513),
+            Offset(41.501535596836376, 26.09905798461081),
+            Offset(40.02840620381446, 30.021099432452637),
+            Offset(35.79419835461124, 35.2186537827727),
+            Offset(30.076040790179817, 38.175916954629336),
+            Offset(24.067012730992623, 38.57855959743385),
+            Offset(19.453150566288006, 37.096490556388844),
+            Offset(16.506465839286186, 34.99409280868502),
+            Offset(14.73924581501028, 32.939784778587686),
+            Offset(13.715334530064114, 31.165018854170466),
+            Offset(13.140377980959201, 29.714761542791386),
+            Offset(12.83036672005031, 28.56755327976071),
+            Offset(12.672939622830032, 27.683643609921106),
+            Offset(12.600162038813565, 27.02281609043513),
+            Offset(12.571432188039635, 26.54999771317575),
+            Offset(12.56310619400641, 26.23642863509033),
+            Offset(12.562193301685781, 26.059158626029138),
+            Offset(12.562499038934627, 26.000123717080207),
+            Offset(12.562499999999996, 26.000000000000004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 22.0),
+            Offset(41.99458528858859, 22.361234167441474),
+            Offset(41.91859127809106, 23.620246996030513),
+            Offset(41.501535596836376, 26.09905798461081),
+            Offset(40.02840620381446, 30.021099432452637),
+            Offset(35.79419835461124, 35.2186537827727),
+            Offset(30.076040790179817, 38.175916954629336),
+            Offset(24.067012730992623, 38.57855959743385),
+            Offset(19.453150566288006, 37.096490556388844),
+            Offset(16.506465839286186, 34.99409280868502),
+            Offset(14.73924581501028, 32.939784778587686),
+            Offset(13.715334530064114, 31.165018854170466),
+            Offset(13.140377980959201, 29.714761542791386),
+            Offset(12.83036672005031, 28.56755327976071),
+            Offset(12.672939622830032, 27.683643609921106),
+            Offset(12.600162038813565, 27.02281609043513),
+            Offset(12.571432188039635, 26.54999771317575),
+            Offset(12.56310619400641, 26.23642863509033),
+            Offset(12.562193301685781, 26.059158626029138),
+            Offset(12.562499038934627, 26.000123717080207),
+            Offset(12.562499999999996, 26.000000000000004),
+          ],
+          <Offset>[
+            Offset(6.0, 22.0),
+            Offset(6.056934514707525, 21.63899352743156),
+            Offset(6.3138203227485405, 20.384389840375796),
+            Offset(7.096666807426793, 17.931786874735423),
+            Offset(9.197983716971518, 14.110555792928775),
+            Offset(14.492848562759846, 9.262883961619078),
+            Offset(21.26860668167255, 6.947111219644562),
+            Offset(28.222185090070198, 7.526686671873211),
+            Offset(33.453333439427794, 10.134368158658866),
+            Offset(36.69157710577769, 13.290289151940406),
+            Offset(38.53223137521963, 16.248244324219414),
+            Offset(39.50406341592221, 18.763506401664923),
+            Offset(39.965161333050226, 20.80420892269316),
+            Offset(40.139843919215444, 22.41260360500229),
+            Offset(40.164704435685586, 23.649282378914172),
+            Offset(40.1214749003011, 24.572646331189105),
+            Offset(40.057897202434084, 25.232737230122385),
+            Offset(40.00055137536795, 25.670250802073745),
+            Offset(39.96271993040885, 25.917501645087587),
+            Offset(39.949247443632466, 25.99982748057223),
+            Offset(39.94921875, 26.0),
+          ],
+          <Offset>[
+            Offset(6.0, 22.0),
+            Offset(6.056934514707525, 21.63899352743156),
+            Offset(6.3138203227485405, 20.384389840375796),
+            Offset(7.096666807426793, 17.931786874735423),
+            Offset(9.197983716971518, 14.110555792928775),
+            Offset(14.492848562759846, 9.262883961619078),
+            Offset(21.26860668167255, 6.947111219644562),
+            Offset(28.222185090070198, 7.526686671873211),
+            Offset(33.453333439427794, 10.134368158658866),
+            Offset(36.69157710577769, 13.290289151940406),
+            Offset(38.53223137521963, 16.248244324219414),
+            Offset(39.50406341592221, 18.763506401664923),
+            Offset(39.965161333050226, 20.80420892269316),
+            Offset(40.139843919215444, 22.41260360500229),
+            Offset(40.164704435685586, 23.649282378914172),
+            Offset(40.1214749003011, 24.572646331189105),
+            Offset(40.057897202434084, 25.232737230122385),
+            Offset(40.00055137536795, 25.670250802073745),
+            Offset(39.96271993040885, 25.917501645087587),
+            Offset(39.949247443632466, 25.99982748057223),
+            Offset(39.94921875, 26.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 22.0),
+            Offset(6.056934514707525, 21.63899352743156),
+            Offset(6.3138203227485405, 20.384389840375796),
+            Offset(7.096666807426793, 17.931786874735423),
+            Offset(9.197983716971518, 14.110555792928775),
+            Offset(14.492848562759846, 9.262883961619078),
+            Offset(21.26860668167255, 6.947111219644562),
+            Offset(28.222185090070198, 7.526686671873211),
+            Offset(33.453333439427794, 10.134368158658866),
+            Offset(36.69157710577769, 13.290289151940406),
+            Offset(38.53223137521963, 16.248244324219414),
+            Offset(39.50406341592221, 18.763506401664923),
+            Offset(39.965161333050226, 20.80420892269316),
+            Offset(40.139843919215444, 22.41260360500229),
+            Offset(40.164704435685586, 23.649282378914172),
+            Offset(40.1214749003011, 24.572646331189105),
+            Offset(40.057897202434084, 25.232737230122385),
+            Offset(40.00055137536795, 25.670250802073745),
+            Offset(39.96271993040885, 25.917501645087587),
+            Offset(39.949247443632466, 25.99982748057223),
+            Offset(39.94921875, 26.0),
+          ],
+          <Offset>[
+            Offset(6.0, 26.0),
+            Offset(5.976562557689849, 25.638185989482512),
+            Offset(5.951781669661045, 24.367972149512962),
+            Offset(6.172793116155802, 21.823631861702058),
+            Offset(7.363587976838016, 17.665129222832853),
+            Offset(11.400806749308899, 11.800457098273661),
+            Offset(17.41878573585796, 8.03287301910486),
+            Offset(24.257523532175192, 6.996159828679087),
+            Offset(29.90338248135665, 8.291042849526),
+            Offset(33.76252909490214, 10.56619705548221),
+            Offset(36.23501636298456, 12.973675163618006),
+            Offset(37.77053540180521, 15.158665125787222),
+            Offset(38.70420448893307, 17.008159945496722),
+            Offset(39.260392038988186, 18.5104805430827),
+            Offset(39.58393261852967, 19.691668944482075),
+            Offset(39.766765502294305, 20.58840471665747),
+            Offset(39.866421084642994, 21.237322746452932),
+            Offset(39.91802804639694, 21.671102155152063),
+            Offset(39.94204075298555, 21.917555098992118),
+            Offset(39.94920417650143, 21.999827480806236),
+            Offset(39.94921875, 22.0),
+          ],
+          <Offset>[
+            Offset(6.0, 26.0),
+            Offset(5.976562557689849, 25.638185989482512),
+            Offset(5.951781669661045, 24.367972149512962),
+            Offset(6.172793116155802, 21.823631861702058),
+            Offset(7.363587976838016, 17.665129222832853),
+            Offset(11.400806749308899, 11.800457098273661),
+            Offset(17.41878573585796, 8.03287301910486),
+            Offset(24.257523532175192, 6.996159828679087),
+            Offset(29.90338248135665, 8.291042849526),
+            Offset(33.76252909490214, 10.56619705548221),
+            Offset(36.23501636298456, 12.973675163618006),
+            Offset(37.77053540180521, 15.158665125787222),
+            Offset(38.70420448893307, 17.008159945496722),
+            Offset(39.260392038988186, 18.5104805430827),
+            Offset(39.58393261852967, 19.691668944482075),
+            Offset(39.766765502294305, 20.58840471665747),
+            Offset(39.866421084642994, 21.237322746452932),
+            Offset(39.91802804639694, 21.671102155152063),
+            Offset(39.94204075298555, 21.917555098992118),
+            Offset(39.94920417650143, 21.999827480806236),
+            Offset(39.94921875, 22.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(6.0, 36.0),
+            Offset(5.8396336833594695, 35.66398057820908),
+            Offset(5.329309336374063, 34.47365089829387),
+            Offset(4.546341863759643, 32.03857491308836),
+            Offset(3.9472816617934896, 27.893335303194206),
+            Offset(4.788314785722232, 21.470485758169694),
+            Offset(7.406922551234356, 16.186721598040453),
+            Offset(10.987511722222681, 12.449414121983239),
+            Offset(14.290737577882037, 10.382465570533384),
+            Offset(16.84152025666389, 9.340052761292668),
+            Offset(18.753361861843203, 8.79207829497377),
+            Offset(20.19495897321279, 8.483469022255434),
+            Offset(21.293826339887335, 8.297708512391797),
+            Offset(22.135385178177998, 8.180000583359465),
+            Offset(22.776244370552647, 8.102975309903787),
+            Offset(23.25488929254563, 8.051973096906334),
+            Offset(23.598629725699347, 8.018606137477462),
+            Offset(23.827700643867974, 7.99783596371886),
+            Offset(23.95771797811348, 7.986559676107813),
+            Offset(24.001111438945117, 7.982878122631195),
+            Offset(24.001202429357242, 7.98287044589657),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 36.0),
+            Offset(5.8396336833594695, 35.66398057820908),
+            Offset(5.329309336374063, 34.47365089829387),
+            Offset(4.546341863759643, 32.03857491308836),
+            Offset(3.9472816617934896, 27.893335303194206),
+            Offset(4.788314785722232, 21.470485758169694),
+            Offset(7.406922551234356, 16.186721598040453),
+            Offset(10.987511722222681, 12.449414121983239),
+            Offset(14.290737577882037, 10.382465570533384),
+            Offset(16.84152025666389, 9.340052761292668),
+            Offset(18.753361861843203, 8.79207829497377),
+            Offset(20.19495897321279, 8.483469022255434),
+            Offset(21.293826339887335, 8.297708512391797),
+            Offset(22.135385178177998, 8.180000583359465),
+            Offset(22.776244370552647, 8.102975309903787),
+            Offset(23.25488929254563, 8.051973096906334),
+            Offset(23.598629725699347, 8.018606137477462),
+            Offset(23.827700643867974, 7.99783596371886),
+            Offset(23.95771797811348, 7.986559676107813),
+            Offset(24.001111438945117, 7.982878122631195),
+            Offset(24.001202429357242, 7.98287044589657),
+          ],
+          <Offset>[
+            Offset(42.0, 36.0),
+            Offset(41.7493389152824, 36.20520796529164),
+            Offset(40.85819701033384, 36.89246335931071),
+            Offset(39.01294315759756, 38.1256246432051),
+            Offset(35.758514239960064, 39.76970128020763),
+            Offset(30.180134511403956, 41.28645636464381),
+            Offset(24.56603417073137, 41.32925393403815),
+            Offset(19.271926095830622, 39.91690773672663),
+            Offset(15.201959304751512, 37.5726832793895),
+            Offset(12.456295622648877, 35.01429311055303),
+            Offset(10.686459838185314, 32.608514843335385),
+            Offset(9.579921816288039, 30.502293804851334),
+            Offset(8.90802993167501, 28.734147272525124),
+            Offset(8.513791284564158, 27.294928344333726),
+            Offset(8.292240475325507, 26.156988797411067),
+            Offset(8.174465865426919, 25.287693028463128),
+            Offset(8.11616441641861, 24.655137447505503),
+            Offset(8.089821190085125, 24.230473791307258),
+            Offset(8.079382709319852, 23.988506993748523),
+            Offset(8.076631388780909, 23.907616552409003),
+            Offset(8.076626005900048, 23.907446869353766),
+          ],
+          <Offset>[
+            Offset(42.0, 36.0),
+            Offset(41.7493389152824, 36.20520796529164),
+            Offset(40.85819701033384, 36.89246335931071),
+            Offset(39.01294315759756, 38.1256246432051),
+            Offset(35.758514239960064, 39.76970128020763),
+            Offset(30.180134511403956, 41.28645636464381),
+            Offset(24.56603417073137, 41.32925393403815),
+            Offset(19.271926095830622, 39.91690773672663),
+            Offset(15.201959304751512, 37.5726832793895),
+            Offset(12.456295622648877, 35.01429311055303),
+            Offset(10.686459838185314, 32.608514843335385),
+            Offset(9.579921816288039, 30.502293804851334),
+            Offset(8.90802993167501, 28.734147272525124),
+            Offset(8.513791284564158, 27.294928344333726),
+            Offset(8.292240475325507, 26.156988797411067),
+            Offset(8.174465865426919, 25.287693028463128),
+            Offset(8.11616441641861, 24.655137447505503),
+            Offset(8.089821190085125, 24.230473791307258),
+            Offset(8.079382709319852, 23.988506993748523),
+            Offset(8.076631388780909, 23.907616552409003),
+            Offset(8.076626005900048, 23.907446869353766),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 36.0),
+            Offset(41.7493389152824, 36.20520796529164),
+            Offset(40.85819701033384, 36.89246335931071),
+            Offset(39.01294315759756, 38.1256246432051),
+            Offset(35.758514239960064, 39.76970128020763),
+            Offset(30.180134511403956, 41.28645636464381),
+            Offset(24.56603417073137, 41.32925393403815),
+            Offset(19.271926095830622, 39.91690773672663),
+            Offset(15.201959304751512, 37.5726832793895),
+            Offset(12.456295622648877, 35.01429311055303),
+            Offset(10.686459838185314, 32.608514843335385),
+            Offset(9.579921816288039, 30.502293804851334),
+            Offset(8.90802993167501, 28.734147272525124),
+            Offset(8.513791284564158, 27.294928344333726),
+            Offset(8.292240475325507, 26.156988797411067),
+            Offset(8.174465865426919, 25.287693028463128),
+            Offset(8.11616441641861, 24.655137447505503),
+            Offset(8.089821190085125, 24.230473791307258),
+            Offset(8.079382709319852, 23.988506993748523),
+            Offset(8.076631388780909, 23.907616552409003),
+            Offset(8.076626005900048, 23.907446869353766),
+          ],
+          <Offset>[
+            Offset(42.0, 32.0),
+            Offset(41.803966700752746, 32.205577011286266),
+            Offset(41.104447603276626, 32.89996903899956),
+            Offset(39.64402995767152, 34.17517788052204),
+            Offset(37.031973302731046, 35.97545970343111),
+            Offset(32.44508133022271, 37.98012671725157),
+            Offset(27.6644042246058, 38.77327245743646),
+            Offset(22.963108117227325, 38.302914175295534),
+            Offset(19.18039906547299, 36.862333955479784),
+            Offset(16.509090720567585, 35.04434211490934),
+            Offset(14.703380298498667, 33.21759365821649),
+            Offset(13.512146444284534, 31.556733263561572),
+            Offset(12.740174664860898, 30.12862517729895),
+            Offset(12.248059307884624, 28.947244716051806),
+            Offset(11.939734974297815, 28.002595790430043),
+            Offset(11.750425410476474, 27.27521551305395),
+            Offset(11.637314290474384, 26.742992599694542),
+            Offset(11.572897732210654, 26.384358993735816),
+            Offset(11.54031155133882, 26.17955109507089),
+            Offset(11.530083003283234, 26.111009046369567),
+            Offset(11.530061897030713, 26.110865227715482),
+          ],
+          <Offset>[
+            Offset(42.0, 32.0),
+            Offset(41.803966700752746, 32.205577011286266),
+            Offset(41.104447603276626, 32.89996903899956),
+            Offset(39.64402995767152, 34.17517788052204),
+            Offset(37.031973302731046, 35.97545970343111),
+            Offset(32.44508133022271, 37.98012671725157),
+            Offset(27.6644042246058, 38.77327245743646),
+            Offset(22.963108117227325, 38.302914175295534),
+            Offset(19.18039906547299, 36.862333955479784),
+            Offset(16.509090720567585, 35.04434211490934),
+            Offset(14.703380298498667, 33.21759365821649),
+            Offset(13.512146444284534, 31.556733263561572),
+            Offset(12.740174664860898, 30.12862517729895),
+            Offset(12.248059307884624, 28.947244716051806),
+            Offset(11.939734974297815, 28.002595790430043),
+            Offset(11.750425410476474, 27.27521551305395),
+            Offset(11.637314290474384, 26.742992599694542),
+            Offset(11.572897732210654, 26.384358993735816),
+            Offset(11.54031155133882, 26.17955109507089),
+            Offset(11.530083003283234, 26.111009046369567),
+            Offset(11.530061897030713, 26.110865227715482),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 32.0),
+            Offset(41.803966700752746, 32.205577011286266),
+            Offset(41.104447603276626, 32.89996903899956),
+            Offset(39.64402995767152, 34.17517788052204),
+            Offset(37.031973302731046, 35.97545970343111),
+            Offset(32.44508133022271, 37.98012671725157),
+            Offset(27.6644042246058, 38.77327245743646),
+            Offset(22.963108117227325, 38.302914175295534),
+            Offset(19.18039906547299, 36.862333955479784),
+            Offset(16.509090720567585, 35.04434211490934),
+            Offset(14.703380298498667, 33.21759365821649),
+            Offset(13.512146444284534, 31.556733263561572),
+            Offset(12.740174664860898, 30.12862517729895),
+            Offset(12.248059307884624, 28.947244716051806),
+            Offset(11.939734974297815, 28.002595790430043),
+            Offset(11.750425410476474, 27.27521551305395),
+            Offset(11.637314290474384, 26.742992599694542),
+            Offset(11.572897732210654, 26.384358993735816),
+            Offset(11.54031155133882, 26.17955109507089),
+            Offset(11.530083003283234, 26.111009046369567),
+            Offset(11.530061897030713, 26.110865227715482),
+          ],
+          <Offset>[
+            Offset(6.0, 32.0),
+            Offset(5.899914425897517, 31.66443482499171),
+            Offset(5.601001082666045, 30.482888615847468),
+            Offset(5.242005036683729, 28.09953280239226),
+            Offset(5.346316156571252, 24.145975901906155),
+            Offset(7.249241148069178, 18.317100047682345),
+            Offset(10.710823881370487, 13.931896549234073),
+            Offset(14.817117889097364, 11.294374466111893),
+            Offset(18.288493245756, 10.248489378687303),
+            Offset(20.784419638077317, 10.013509863155594),
+            Offset(22.541938014255397, 10.075312777589325),
+            Offset(23.798109358346892, 10.220508832423288),
+            Offset(24.71461203122786, 10.370924674281323),
+            Offset(25.392890381083, 10.501349297587215),
+            Offset(25.896277759611298, 10.60605174724228),
+            Offset(26.265268043339944, 10.685909272436422),
+            Offset(26.526795349038366, 10.74364670273436),
+            Offset(26.699555102368272, 10.782158496973931),
+            Offset(26.79709065296033, 10.80399872839147),
+            Offset(26.829561509459538, 10.811282301423006),
+            Offset(26.829629554119695, 10.811297570626497),
+          ],
+          <Offset>[
+            Offset(6.0, 32.0),
+            Offset(5.899914425897517, 31.66443482499171),
+            Offset(5.601001082666045, 30.482888615847468),
+            Offset(5.242005036683729, 28.09953280239226),
+            Offset(5.346316156571252, 24.145975901906155),
+            Offset(7.249241148069178, 18.317100047682345),
+            Offset(10.710823881370487, 13.931896549234073),
+            Offset(14.817117889097364, 11.294374466111893),
+            Offset(18.288493245756, 10.248489378687303),
+            Offset(20.784419638077317, 10.013509863155594),
+            Offset(22.541938014255397, 10.075312777589325),
+            Offset(23.798109358346892, 10.220508832423288),
+            Offset(24.71461203122786, 10.370924674281323),
+            Offset(25.392890381083, 10.501349297587215),
+            Offset(25.896277759611298, 10.60605174724228),
+            Offset(26.265268043339944, 10.685909272436422),
+            Offset(26.526795349038366, 10.74364670273436),
+            Offset(26.699555102368272, 10.782158496973931),
+            Offset(26.79709065296033, 10.80399872839147),
+            Offset(26.829561509459538, 10.811282301423006),
+            Offset(26.829629554119695, 10.811297570626497),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 32.0),
+            Offset(5.899914425897517, 31.66443482499171),
+            Offset(5.601001082666045, 30.482888615847468),
+            Offset(5.242005036683729, 28.09953280239226),
+            Offset(5.346316156571252, 24.145975901906155),
+            Offset(7.249241148069178, 18.317100047682345),
+            Offset(10.710823881370487, 13.931896549234073),
+            Offset(14.817117889097364, 11.294374466111893),
+            Offset(18.288493245756, 10.248489378687303),
+            Offset(20.784419638077317, 10.013509863155594),
+            Offset(22.541938014255397, 10.075312777589325),
+            Offset(23.798109358346892, 10.220508832423288),
+            Offset(24.71461203122786, 10.370924674281323),
+            Offset(25.392890381083, 10.501349297587215),
+            Offset(25.896277759611298, 10.60605174724228),
+            Offset(26.265268043339944, 10.685909272436422),
+            Offset(26.526795349038366, 10.74364670273436),
+            Offset(26.699555102368272, 10.782158496973931),
+            Offset(26.79709065296033, 10.80399872839147),
+            Offset(26.829561509459538, 10.811282301423006),
+            Offset(26.829629554119695, 10.811297570626497),
+          ],
+          <Offset>[
+            Offset(6.0, 36.0),
+            Offset(5.839633683308566, 35.66398057820831),
+            Offset(5.329309336323984, 34.47365089829046),
+            Offset(4.546341863735712, 32.03857491308413),
+            Offset(3.947281661825336, 27.893335303206097),
+            Offset(4.788314785746671, 21.47048575818877),
+            Offset(7.406922551270995, 16.18672159809414),
+            Offset(10.98751172223972, 12.449414122039723),
+            Offset(14.290737577881032, 10.382465570503403),
+            Offset(16.841520256655304, 9.340052761342939),
+            Offset(18.753361861827802, 8.792078295019234),
+            Offset(20.194958973207576, 8.483469022266245),
+            Offset(21.293826339889407, 8.297708512388375),
+            Offset(22.13538517817335, 8.180000583365981),
+            Offset(22.776244370563283, 8.102975309890528),
+            Offset(23.25488929251534, 8.051973096940955),
+            Offset(23.598629725644848, 8.018606137536025),
+            Offset(23.82770064384222, 7.997835963745423),
+            Offset(23.957717978081078, 7.986559676140466),
+            Offset(24.001111438940168, 7.982878122636148),
+            Offset(24.001202429373503, 7.982870445880305),
+          ],
+          <Offset>[
+            Offset(6.0, 36.0),
+            Offset(5.839633683308566, 35.66398057820831),
+            Offset(5.329309336323984, 34.47365089829046),
+            Offset(4.546341863735712, 32.03857491308413),
+            Offset(3.947281661825336, 27.893335303206097),
+            Offset(4.788314785746671, 21.47048575818877),
+            Offset(7.406922551270995, 16.18672159809414),
+            Offset(10.98751172223972, 12.449414122039723),
+            Offset(14.290737577881032, 10.382465570503403),
+            Offset(16.841520256655304, 9.340052761342939),
+            Offset(18.753361861827802, 8.792078295019234),
+            Offset(20.194958973207576, 8.483469022266245),
+            Offset(21.293826339889407, 8.297708512388375),
+            Offset(22.13538517817335, 8.180000583365981),
+            Offset(22.776244370563283, 8.102975309890528),
+            Offset(23.25488929251534, 8.051973096940955),
+            Offset(23.598629725644848, 8.018606137536025),
+            Offset(23.82770064384222, 7.997835963745423),
+            Offset(23.957717978081078, 7.986559676140466),
+            Offset(24.001111438940168, 7.982878122636148),
+            Offset(24.001202429373503, 7.982870445880305),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(6.0, 16.0),
+            Offset(6.222470088677106, 15.614531066984553),
+            Offset(7.071161725316092, 14.306422712262563),
+            Offset(9.085869786142727, 11.907139949336411),
+            Offset(13.311519331212619, 8.711520321213257),
+            Offset(21.694206315186374, 6.462423500731354),
+            Offset(30.07031570748504, 8.471955170698632),
+            Offset(36.20036889900587, 14.155750775196541),
+            Offset(38.533897479983715, 20.76099122996903),
+            Offset(38.182626701431914, 26.194302454359914),
+            Offset(36.59711302702814, 30.110286603895076),
+            Offset(34.63761335058528, 32.76106836363335),
+            Offset(32.7272901891386, 34.4927008221791),
+            Offset(31.04869117038896, 35.596105690451935),
+            Offset(29.664526028757855, 36.28441549314729),
+            Offset(28.581655311555835, 36.70452225851578),
+            Offset(27.782897949107628, 36.95396775456513),
+            Offset(27.242531133855476, 37.09522522130338),
+            Offset(26.933380541033216, 37.166375518103024),
+            Offset(26.82984682779076, 37.188656481991416),
+            Offset(26.829629554103434, 37.18870242935725),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 16.0),
+            Offset(6.222470088677106, 15.614531066984553),
+            Offset(7.071161725316092, 14.306422712262563),
+            Offset(9.085869786142727, 11.907139949336411),
+            Offset(13.311519331212619, 8.711520321213257),
+            Offset(21.694206315186374, 6.462423500731354),
+            Offset(30.07031570748504, 8.471955170698632),
+            Offset(36.20036889900587, 14.155750775196541),
+            Offset(38.533897479983715, 20.76099122996903),
+            Offset(38.182626701431914, 26.194302454359914),
+            Offset(36.59711302702814, 30.110286603895076),
+            Offset(34.63761335058528, 32.76106836363335),
+            Offset(32.7272901891386, 34.4927008221791),
+            Offset(31.04869117038896, 35.596105690451935),
+            Offset(29.664526028757855, 36.28441549314729),
+            Offset(28.581655311555835, 36.70452225851578),
+            Offset(27.782897949107628, 36.95396775456513),
+            Offset(27.242531133855476, 37.09522522130338),
+            Offset(26.933380541033216, 37.166375518103024),
+            Offset(26.82984682779076, 37.188656481991416),
+            Offset(26.829629554103434, 37.18870242935725),
+          ],
+          <Offset>[
+            Offset(42.0, 16.0),
+            Offset(42.119273441095075, 16.516374018071716),
+            Offset(42.428662704565184, 18.32937541467259),
+            Offset(42.54812490043565, 21.94159775950881),
+            Offset(41.3111285319893, 27.683594454682137),
+            Offset(36.06395079582478, 35.01020271691918),
+            Offset(28.59459512599702, 38.51093769070532),
+            Offset(21.239886122259133, 38.07233071493643),
+            Offset(16.251628495692138, 35.34156866251391),
+            Offset(13.527101819238178, 32.27103394597236),
+            Offset(12.16858814546228, 29.604397296366464),
+            Offset(11.548946515009288, 27.474331231158473),
+            Offset(11.311114637013635, 25.826563435488687),
+            Offset(11.262012546535352, 24.572239162454554),
+            Offset(11.298221100690522, 23.63118177535833),
+            Offset(11.364474416879979, 22.940254245947138),
+            Offset(11.431638843687892, 22.451805922237554),
+            Offset(11.485090012547001, 22.130328573710905),
+            Offset(11.518417313485447, 21.949395273355513),
+            Offset(11.530012405933167, 21.889264075838188),
+            Offset(11.53003696527787, 21.889138124802937),
+          ],
+          <Offset>[
+            Offset(42.0, 16.0),
+            Offset(42.119273441095075, 16.516374018071716),
+            Offset(42.428662704565184, 18.32937541467259),
+            Offset(42.54812490043565, 21.94159775950881),
+            Offset(41.3111285319893, 27.683594454682137),
+            Offset(36.06395079582478, 35.01020271691918),
+            Offset(28.59459512599702, 38.51093769070532),
+            Offset(21.239886122259133, 38.07233071493643),
+            Offset(16.251628495692138, 35.34156866251391),
+            Offset(13.527101819238178, 32.27103394597236),
+            Offset(12.16858814546228, 29.604397296366464),
+            Offset(11.548946515009288, 27.474331231158473),
+            Offset(11.311114637013635, 25.826563435488687),
+            Offset(11.262012546535352, 24.572239162454554),
+            Offset(11.298221100690522, 23.63118177535833),
+            Offset(11.364474416879979, 22.940254245947138),
+            Offset(11.431638843687892, 22.451805922237554),
+            Offset(11.485090012547001, 22.130328573710905),
+            Offset(11.518417313485447, 21.949395273355513),
+            Offset(11.530012405933167, 21.889264075838188),
+            Offset(11.53003696527787, 21.889138124802937),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 16.0),
+            Offset(42.119273441095075, 16.516374018071716),
+            Offset(42.428662704565184, 18.32937541467259),
+            Offset(42.54812490043565, 21.94159775950881),
+            Offset(41.3111285319893, 27.683594454682137),
+            Offset(36.06395079582478, 35.01020271691918),
+            Offset(28.59459512599702, 38.51093769070532),
+            Offset(21.239886122259133, 38.07233071493643),
+            Offset(16.251628495692138, 35.34156866251391),
+            Offset(13.527101819238178, 32.27103394597236),
+            Offset(12.16858814546228, 29.604397296366464),
+            Offset(11.548946515009288, 27.474331231158473),
+            Offset(11.311114637013635, 25.826563435488687),
+            Offset(11.262012546535352, 24.572239162454554),
+            Offset(11.298221100690522, 23.63118177535833),
+            Offset(11.364474416879979, 22.940254245947138),
+            Offset(11.431638843687892, 22.451805922237554),
+            Offset(11.485090012547001, 22.130328573710905),
+            Offset(11.518417313485447, 21.949395273355513),
+            Offset(11.530012405933167, 21.889264075838188),
+            Offset(11.53003696527787, 21.889138124802937),
+          ],
+          <Offset>[
+            Offset(42.0, 12.0),
+            Offset(42.22538630246601, 12.517777761542249),
+            Offset(42.90619853384615, 14.357900907446863),
+            Offset(43.759884509852945, 18.128995147835514),
+            Offset(43.66585885175813, 24.44736028078141),
+            Offset(39.74861752085834, 33.43380529842439),
+            Offset(32.57188683977151, 39.07136996422343),
+            Offset(24.376857043988256, 40.600018479197814),
+            Offset(17.959269400168804, 39.004426856660785),
+            Offset(13.850567169499653, 36.311009998593796),
+            Offset(11.374155956344177, 33.58880277176081),
+            Offset(9.917496515696001, 31.204288894581083),
+            Offset(9.07498759074148, 29.236785710939074),
+            Offset(8.597571742452605, 27.666692096657314),
+            Offset(8.334783321442917, 26.44693980672826),
+            Offset(8.195874559699876, 25.52824222288586),
+            Offset(8.126295299747222, 24.866824239052814),
+            Offset(8.093843447379264, 24.426077640310794),
+            Offset(8.080338503727083, 24.17611706018137),
+            Offset(8.076619249177135, 24.092742069165425),
+            Offset(8.07661186374038, 24.09256727275783),
+          ],
+          <Offset>[
+            Offset(42.0, 12.0),
+            Offset(42.22538630246601, 12.517777761542249),
+            Offset(42.90619853384615, 14.357900907446863),
+            Offset(43.759884509852945, 18.128995147835514),
+            Offset(43.66585885175813, 24.44736028078141),
+            Offset(39.74861752085834, 33.43380529842439),
+            Offset(32.57188683977151, 39.07136996422343),
+            Offset(24.376857043988256, 40.600018479197814),
+            Offset(17.959269400168804, 39.004426856660785),
+            Offset(13.850567169499653, 36.311009998593796),
+            Offset(11.374155956344177, 33.58880277176081),
+            Offset(9.917496515696001, 31.204288894581083),
+            Offset(9.07498759074148, 29.236785710939074),
+            Offset(8.597571742452605, 27.666692096657314),
+            Offset(8.334783321442917, 26.44693980672826),
+            Offset(8.195874559699876, 25.52824222288586),
+            Offset(8.126295299747222, 24.866824239052814),
+            Offset(8.093843447379264, 24.426077640310794),
+            Offset(8.080338503727083, 24.17611706018137),
+            Offset(8.076619249177135, 24.092742069165425),
+            Offset(8.07661186374038, 24.09256727275783),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 12.0),
+            Offset(42.22538630246601, 12.517777761542249),
+            Offset(42.90619853384615, 14.357900907446863),
+            Offset(43.759884509852945, 18.128995147835514),
+            Offset(43.66585885175813, 24.44736028078141),
+            Offset(39.74861752085834, 33.43380529842439),
+            Offset(32.57188683977151, 39.07136996422343),
+            Offset(24.376857043988256, 40.600018479197814),
+            Offset(17.959269400168804, 39.004426856660785),
+            Offset(13.850567169499653, 36.311009998593796),
+            Offset(11.374155956344177, 33.58880277176081),
+            Offset(9.917496515696001, 31.204288894581083),
+            Offset(9.07498759074148, 29.236785710939074),
+            Offset(8.597571742452605, 27.666692096657314),
+            Offset(8.334783321442917, 26.44693980672826),
+            Offset(8.195874559699876, 25.52824222288586),
+            Offset(8.126295299747222, 24.866824239052814),
+            Offset(8.093843447379264, 24.426077640310794),
+            Offset(8.080338503727083, 24.17611706018137),
+            Offset(8.076619249177135, 24.092742069165425),
+            Offset(8.07661186374038, 24.09256727275783),
+          ],
+          <Offset>[
+            Offset(6.0, 12.0),
+            Offset(6.3229312318803075, 11.61579282114921),
+            Offset(7.523361420980265, 10.332065476778915),
+            Offset(10.234818160108134, 8.075701885898315),
+            Offset(15.555284551985588, 5.400098023461183),
+            Offset(25.267103519984172, 4.663978182144188),
+            Offset(34.065497532306516, 8.668225867992323),
+            Offset(39.59155761731576, 16.27703318845691),
+            Offset(40.72409454498984, 24.108085016590273),
+            Offset(39.139841854472834, 30.0780814324673),
+            Offset(36.514293313228855, 34.10942912386185),
+            Offset(33.744815583253256, 36.6601595585975),
+            Offset(31.226861893018718, 38.20062678263231),
+            Offset(29.10189988007002, 39.09038725780428),
+            Offset(27.3951953205187, 39.57837027981981),
+            Offset(26.083922435637483, 39.82883505984612),
+            Offset(25.128742795932077, 39.94653528477588),
+            Offset(24.487982707377697, 39.99564983955995),
+            Offset(24.123290412440365, 40.013021521592925),
+            Offset(24.001457946431486, 40.017121849607435),
+            Offset(24.001202429333205, 40.017129554079396),
+          ],
+          <Offset>[
+            Offset(6.0, 12.0),
+            Offset(6.3229312318803075, 11.61579282114921),
+            Offset(7.523361420980265, 10.332065476778915),
+            Offset(10.234818160108134, 8.075701885898315),
+            Offset(15.555284551985588, 5.400098023461183),
+            Offset(25.267103519984172, 4.663978182144188),
+            Offset(34.065497532306516, 8.668225867992323),
+            Offset(39.59155761731576, 16.27703318845691),
+            Offset(40.72409454498984, 24.108085016590273),
+            Offset(39.139841854472834, 30.0780814324673),
+            Offset(36.514293313228855, 34.10942912386185),
+            Offset(33.744815583253256, 36.6601595585975),
+            Offset(31.226861893018718, 38.20062678263231),
+            Offset(29.10189988007002, 39.09038725780428),
+            Offset(27.3951953205187, 39.57837027981981),
+            Offset(26.083922435637483, 39.82883505984612),
+            Offset(25.128742795932077, 39.94653528477588),
+            Offset(24.487982707377697, 39.99564983955995),
+            Offset(24.123290412440365, 40.013021521592925),
+            Offset(24.001457946431486, 40.017121849607435),
+            Offset(24.001202429333205, 40.017129554079396),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 12.0),
+            Offset(6.3229312318803075, 11.61579282114921),
+            Offset(7.523361420980265, 10.332065476778915),
+            Offset(10.234818160108134, 8.075701885898315),
+            Offset(15.555284551985588, 5.400098023461183),
+            Offset(25.267103519984172, 4.663978182144188),
+            Offset(34.065497532306516, 8.668225867992323),
+            Offset(39.59155761731576, 16.27703318845691),
+            Offset(40.72409454498984, 24.108085016590273),
+            Offset(39.139841854472834, 30.0780814324673),
+            Offset(36.514293313228855, 34.10942912386185),
+            Offset(33.744815583253256, 36.6601595585975),
+            Offset(31.226861893018718, 38.20062678263231),
+            Offset(29.10189988007002, 39.09038725780428),
+            Offset(27.3951953205187, 39.57837027981981),
+            Offset(26.083922435637483, 39.82883505984612),
+            Offset(25.128742795932077, 39.94653528477588),
+            Offset(24.487982707377697, 39.99564983955995),
+            Offset(24.123290412440365, 40.013021521592925),
+            Offset(24.001457946431486, 40.017121849607435),
+            Offset(24.001202429333205, 40.017129554079396),
+          ],
+          <Offset>[
+            Offset(6.0, 16.0),
+            Offset(6.22247008872931, 15.614531066985863),
+            Offset(7.071161725356028, 14.306422712267109),
+            Offset(9.085869786222908, 11.907139949360454),
+            Offset(13.311519331206826, 8.711520321209331),
+            Offset(21.69420631520211, 6.462423500762615),
+            Offset(30.070315707485825, 8.471955170682651),
+            Offset(36.20036889903345, 14.155750775152455),
+            Offset(38.53389748002304, 20.760991229943293),
+            Offset(38.18262670145813, 26.194302454353455),
+            Offset(36.597113027065134, 30.110286603895844),
+            Offset(34.63761335066132, 32.761068363650764),
+            Offset(32.72729018913396, 34.49270082217723),
+            Offset(31.048691170407302, 35.59610569046216),
+            Offset(29.66452602881138, 36.28441549318417),
+            Offset(28.58165531160348, 36.70452225855387),
+            Offset(27.78289794916673, 36.95396775461755),
+            Offset(27.24253113386635, 37.09522522131371),
+            Offset(26.933380541051008, 37.16637551812059),
+            Offset(26.829846827821875, 37.18865648202253),
+            Offset(26.829629554079393, 37.188702429333205),
+          ],
+          <Offset>[
+            Offset(6.0, 16.0),
+            Offset(6.22247008872931, 15.614531066985863),
+            Offset(7.071161725356028, 14.306422712267109),
+            Offset(9.085869786222908, 11.907139949360454),
+            Offset(13.311519331206826, 8.711520321209331),
+            Offset(21.69420631520211, 6.462423500762615),
+            Offset(30.070315707485825, 8.471955170682651),
+            Offset(36.20036889903345, 14.155750775152455),
+            Offset(38.53389748002304, 20.760991229943293),
+            Offset(38.18262670145813, 26.194302454353455),
+            Offset(36.597113027065134, 30.110286603895844),
+            Offset(34.63761335066132, 32.761068363650764),
+            Offset(32.72729018913396, 34.49270082217723),
+            Offset(31.048691170407302, 35.59610569046216),
+            Offset(29.66452602881138, 36.28441549318417),
+            Offset(28.58165531160348, 36.70452225855387),
+            Offset(27.78289794916673, 36.95396775461755),
+            Offset(27.24253113386635, 37.09522522131371),
+            Offset(26.933380541051008, 37.16637551812059),
+            Offset(26.829846827821875, 37.18865648202253),
+            Offset(26.829629554079393, 37.188702429333205),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+  ],
+  matchTextDirection: true,
+);
diff --git a/lib/src/material/animated_icons/data/menu_close.g.dart b/lib/src/material/animated_icons/data/menu_close.g.dart
new file mode 100644
index 0000000..43e194f
--- /dev/null
+++ b/lib/src/material/animated_icons/data/menu_close.g.dart
@@ -0,0 +1,1026 @@
+// Copyright 2014 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.
+
+// AUTOGENERATED FILE DO NOT EDIT!
+// This file was generated by vitool.
+part of material_animated_icons;
+const _AnimatedIconData _$menu_close = _AnimatedIconData(
+  Size(48.0, 48.0),
+  <_PathFrames>[
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(6.0, 26.0),
+            Offset(6.667958372815065, 25.652081003354123),
+            Offset(8.330956385969174, 24.584197933972426),
+            Offset(10.795082531480682, 22.920903618043887),
+            Offset(14.118850428921743, 21.151292868049936),
+            Offset(18.25264983114299, 20.14945205026408),
+            Offset(21.311663261847183, 21.835975547204264),
+            Offset(22.017669221052497, 23.734736578402938),
+            Offset(22.22502452096443, 23.078337345433567),
+            Offset(22.535475994562226, 22.637953951770903),
+            Offset(22.851392493882464, 22.362715419699295),
+            Offset(23.1332359929415, 22.197579362061152),
+            Offset(23.369521577941427, 22.101975511401783),
+            Offset(23.560274059886364, 22.048938469040202),
+            Offset(23.709614091422043, 22.02119328278395),
+            Offset(23.822645300996605, 22.00787919273418),
+            Offset(23.90426194110445, 22.002292758165275),
+            Offset(23.958738335514504, 22.000425676539155),
+            Offset(23.989660411288344, 22.000026726952264),
+            Offset(23.999978366434483, 22.000000000117),
+            Offset(23.999999999999996, 22.000000000000004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 26.0),
+            Offset(6.667958372815065, 25.652081003354123),
+            Offset(8.330956385969174, 24.584197933972426),
+            Offset(10.795082531480682, 22.920903618043887),
+            Offset(14.118850428921743, 21.151292868049936),
+            Offset(18.25264983114299, 20.14945205026408),
+            Offset(21.311663261847183, 21.835975547204264),
+            Offset(22.017669221052497, 23.734736578402938),
+            Offset(22.22502452096443, 23.078337345433567),
+            Offset(22.535475994562226, 22.637953951770903),
+            Offset(22.851392493882464, 22.362715419699295),
+            Offset(23.1332359929415, 22.197579362061152),
+            Offset(23.369521577941427, 22.101975511401783),
+            Offset(23.560274059886364, 22.048938469040202),
+            Offset(23.709614091422043, 22.02119328278395),
+            Offset(23.822645300996605, 22.00787919273418),
+            Offset(23.90426194110445, 22.002292758165275),
+            Offset(23.958738335514504, 22.000425676539155),
+            Offset(23.989660411288344, 22.000026726952264),
+            Offset(23.999978366434483, 22.000000000117),
+            Offset(23.999999999999996, 22.000000000000004),
+          ],
+          <Offset>[
+            Offset(42.0, 26.0),
+            Offset(41.25166967016726, 26.34711145869683),
+            Offset(39.30700496104292, 27.399384375173792),
+            Offset(36.28104377724833, 28.970941368922745),
+            Offset(32.04675383085589, 30.403280561808284),
+            Offset(26.655308355431437, 30.38812108642142),
+            Offset(22.838515792338228, 27.24978625225603),
+            Offset(22.017669221052497, 23.734736578402938),
+            Offset(22.22502452096443, 23.078337345433567),
+            Offset(22.535475994562226, 22.637953951770903),
+            Offset(22.851392493882464, 22.362715419699295),
+            Offset(23.1332359929415, 22.197579362061152),
+            Offset(23.369521577941427, 22.101975511401783),
+            Offset(23.560274059886364, 22.048938469040202),
+            Offset(23.709614091422043, 22.02119328278395),
+            Offset(23.822645300996605, 22.00787919273418),
+            Offset(23.90426194110445, 22.002292758165275),
+            Offset(23.958738335514504, 22.000425676539155),
+            Offset(23.989660411288344, 22.000026726952264),
+            Offset(23.999978366434483, 22.000000000117),
+            Offset(23.999999999999996, 22.000000000000004),
+          ],
+          <Offset>[
+            Offset(42.0, 26.0),
+            Offset(41.25166967016726, 26.34711145869683),
+            Offset(39.30700496104292, 27.399384375173792),
+            Offset(36.28104377724833, 28.970941368922745),
+            Offset(32.04675383085589, 30.403280561808284),
+            Offset(26.655308355431437, 30.38812108642142),
+            Offset(22.838515792338228, 27.24978625225603),
+            Offset(22.017669221052497, 23.734736578402938),
+            Offset(22.22502452096443, 23.078337345433567),
+            Offset(22.535475994562226, 22.637953951770903),
+            Offset(22.851392493882464, 22.362715419699295),
+            Offset(23.1332359929415, 22.197579362061152),
+            Offset(23.369521577941427, 22.101975511401783),
+            Offset(23.560274059886364, 22.048938469040202),
+            Offset(23.709614091422043, 22.02119328278395),
+            Offset(23.822645300996605, 22.00787919273418),
+            Offset(23.90426194110445, 22.002292758165275),
+            Offset(23.958738335514504, 22.000425676539155),
+            Offset(23.989660411288344, 22.000026726952264),
+            Offset(23.999978366434483, 22.000000000117),
+            Offset(23.999999999999996, 22.000000000000004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 26.0),
+            Offset(41.25166967016726, 26.34711145869683),
+            Offset(39.30700496104292, 27.399384375173792),
+            Offset(36.28104377724833, 28.970941368922745),
+            Offset(32.04675383085589, 30.403280561808284),
+            Offset(26.655308355431437, 30.38812108642142),
+            Offset(22.838515792338228, 27.24978625225603),
+            Offset(22.017669221052497, 23.734736578402938),
+            Offset(22.22502452096443, 23.078337345433567),
+            Offset(22.535475994562226, 22.637953951770903),
+            Offset(22.851392493882464, 22.362715419699295),
+            Offset(23.1332359929415, 22.197579362061152),
+            Offset(23.369521577941427, 22.101975511401783),
+            Offset(23.560274059886364, 22.048938469040202),
+            Offset(23.709614091422043, 22.02119328278395),
+            Offset(23.822645300996605, 22.00787919273418),
+            Offset(23.90426194110445, 22.002292758165275),
+            Offset(23.958738335514504, 22.000425676539155),
+            Offset(23.989660411288344, 22.000026726952264),
+            Offset(23.999978366434483, 22.000000000117),
+            Offset(23.999999999999996, 22.000000000000004),
+          ],
+          <Offset>[
+            Offset(42.0, 22.0),
+            Offset(41.332041627184935, 22.347918996645877),
+            Offset(39.669043614130416, 23.415802066036626),
+            Offset(37.20491746851932, 25.079096381956113),
+            Offset(33.88114957098939, 26.848707131904206),
+            Offset(29.747350168882384, 27.85054794976684),
+            Offset(26.688336738152817, 26.164024452795736),
+            Offset(25.982330778947503, 24.265263421597062),
+            Offset(25.77497547903557, 24.921662654566433),
+            Offset(25.464524005437774, 25.362046048229097),
+            Offset(25.148607506117536, 25.637284580300705),
+            Offset(24.8667640070585, 25.802420637938855),
+            Offset(24.630478422058573, 25.898024488598217),
+            Offset(24.43972594011363, 25.95106153095979),
+            Offset(24.290385908577957, 25.97880671721605),
+            Offset(24.177354699003402, 25.992120807265813),
+            Offset(24.09573805889554, 25.997707241834732),
+            Offset(24.041261664485504, 25.999574323460838),
+            Offset(24.010339588711656, 25.999973273047736),
+            Offset(24.000021633565517, 25.999999999883),
+            Offset(23.999999999999996, 26.000000000000004),
+          ],
+          <Offset>[
+            Offset(42.0, 22.0),
+            Offset(41.332041627184935, 22.347918996645877),
+            Offset(39.669043614130416, 23.415802066036626),
+            Offset(37.20491746851932, 25.079096381956113),
+            Offset(33.88114957098939, 26.848707131904206),
+            Offset(29.747350168882384, 27.85054794976684),
+            Offset(26.688336738152817, 26.164024452795736),
+            Offset(25.982330778947503, 24.265263421597062),
+            Offset(25.77497547903557, 24.921662654566433),
+            Offset(25.464524005437774, 25.362046048229097),
+            Offset(25.148607506117536, 25.637284580300705),
+            Offset(24.8667640070585, 25.802420637938855),
+            Offset(24.630478422058573, 25.898024488598217),
+            Offset(24.43972594011363, 25.95106153095979),
+            Offset(24.290385908577957, 25.97880671721605),
+            Offset(24.177354699003402, 25.992120807265813),
+            Offset(24.09573805889554, 25.997707241834732),
+            Offset(24.041261664485504, 25.999574323460838),
+            Offset(24.010339588711656, 25.999973273047736),
+            Offset(24.000021633565517, 25.999999999883),
+            Offset(23.999999999999996, 26.000000000000004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 22.0),
+            Offset(41.332041627184935, 22.347918996645877),
+            Offset(39.669043614130416, 23.415802066036626),
+            Offset(37.20491746851932, 25.079096381956113),
+            Offset(33.88114957098939, 26.848707131904206),
+            Offset(29.747350168882384, 27.85054794976684),
+            Offset(26.688336738152817, 26.164024452795736),
+            Offset(25.982330778947503, 24.265263421597062),
+            Offset(25.77497547903557, 24.921662654566433),
+            Offset(25.464524005437774, 25.362046048229097),
+            Offset(25.148607506117536, 25.637284580300705),
+            Offset(24.8667640070585, 25.802420637938855),
+            Offset(24.630478422058573, 25.898024488598217),
+            Offset(24.43972594011363, 25.95106153095979),
+            Offset(24.290385908577957, 25.97880671721605),
+            Offset(24.177354699003402, 25.992120807265813),
+            Offset(24.09573805889554, 25.997707241834732),
+            Offset(24.041261664485504, 25.999574323460838),
+            Offset(24.010339588711656, 25.999973273047736),
+            Offset(24.000021633565517, 25.999999999883),
+            Offset(23.999999999999996, 26.000000000000004),
+          ],
+          <Offset>[
+            Offset(6.0, 22.0),
+            Offset(6.74833032983274, 21.65288854130317),
+            Offset(8.692995039056669, 20.60061562483526),
+            Offset(11.718956222751673, 19.029058631077255),
+            Offset(15.953246169055248, 17.596719438145858),
+            Offset(21.344691644593937, 17.6118789136095),
+            Offset(25.161484207661772, 20.75021374774397),
+            Offset(25.982330778947503, 24.265263421597062),
+            Offset(25.77497547903557, 24.921662654566433),
+            Offset(25.464524005437774, 25.362046048229097),
+            Offset(25.148607506117536, 25.637284580300705),
+            Offset(24.8667640070585, 25.802420637938855),
+            Offset(24.630478422058573, 25.898024488598217),
+            Offset(24.43972594011363, 25.95106153095979),
+            Offset(24.290385908577957, 25.97880671721605),
+            Offset(24.177354699003402, 25.992120807265813),
+            Offset(24.09573805889554, 25.997707241834732),
+            Offset(24.041261664485504, 25.999574323460838),
+            Offset(24.010339588711656, 25.999973273047736),
+            Offset(24.000021633565517, 25.999999999883),
+            Offset(23.999999999999996, 26.000000000000004),
+          ],
+          <Offset>[
+            Offset(6.0, 22.0),
+            Offset(6.74833032983274, 21.65288854130317),
+            Offset(8.692995039056669, 20.60061562483526),
+            Offset(11.718956222751673, 19.029058631077255),
+            Offset(15.953246169055248, 17.596719438145858),
+            Offset(21.344691644593937, 17.6118789136095),
+            Offset(25.161484207661772, 20.75021374774397),
+            Offset(25.982330778947503, 24.265263421597062),
+            Offset(25.77497547903557, 24.921662654566433),
+            Offset(25.464524005437774, 25.362046048229097),
+            Offset(25.148607506117536, 25.637284580300705),
+            Offset(24.8667640070585, 25.802420637938855),
+            Offset(24.630478422058573, 25.898024488598217),
+            Offset(24.43972594011363, 25.95106153095979),
+            Offset(24.290385908577957, 25.97880671721605),
+            Offset(24.177354699003402, 25.992120807265813),
+            Offset(24.09573805889554, 25.997707241834732),
+            Offset(24.041261664485504, 25.999574323460838),
+            Offset(24.010339588711656, 25.999973273047736),
+            Offset(24.000021633565517, 25.999999999883),
+            Offset(23.999999999999996, 26.000000000000004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 22.0),
+            Offset(6.74833032983274, 21.65288854130317),
+            Offset(8.692995039056669, 20.60061562483526),
+            Offset(11.718956222751673, 19.029058631077255),
+            Offset(15.953246169055248, 17.596719438145858),
+            Offset(21.344691644593937, 17.6118789136095),
+            Offset(25.161484207661772, 20.75021374774397),
+            Offset(25.982330778947503, 24.265263421597062),
+            Offset(25.77497547903557, 24.921662654566433),
+            Offset(25.464524005437774, 25.362046048229097),
+            Offset(25.148607506117536, 25.637284580300705),
+            Offset(24.8667640070585, 25.802420637938855),
+            Offset(24.630478422058573, 25.898024488598217),
+            Offset(24.43972594011363, 25.95106153095979),
+            Offset(24.290385908577957, 25.97880671721605),
+            Offset(24.177354699003402, 25.992120807265813),
+            Offset(24.09573805889554, 25.997707241834732),
+            Offset(24.041261664485504, 25.999574323460838),
+            Offset(24.010339588711656, 25.999973273047736),
+            Offset(24.000021633565517, 25.999999999883),
+            Offset(23.999999999999996, 26.000000000000004),
+          ],
+          <Offset>[
+            Offset(6.0, 26.0),
+            Offset(6.667958372815065, 25.652081003354123),
+            Offset(8.330956385969174, 24.584197933972426),
+            Offset(10.795082531480682, 22.920903618043887),
+            Offset(14.118850428921743, 21.151292868049936),
+            Offset(18.25264983114299, 20.14945205026408),
+            Offset(21.311663261847183, 21.835975547204264),
+            Offset(22.017669221052497, 23.734736578402938),
+            Offset(22.22502452096443, 23.078337345433567),
+            Offset(22.535475994562226, 22.637953951770903),
+            Offset(22.851392493882464, 22.362715419699295),
+            Offset(23.1332359929415, 22.197579362061152),
+            Offset(23.369521577941427, 22.101975511401783),
+            Offset(23.560274059886364, 22.048938469040202),
+            Offset(23.709614091422043, 22.02119328278395),
+            Offset(23.822645300996605, 22.00787919273418),
+            Offset(23.90426194110445, 22.002292758165275),
+            Offset(23.958738335514504, 22.000425676539155),
+            Offset(23.989660411288344, 22.000026726952264),
+            Offset(23.999978366434483, 22.000000000117),
+            Offset(23.999999999999996, 22.000000000000004),
+          ],
+          <Offset>[
+            Offset(6.0, 26.0),
+            Offset(6.667958372815065, 25.652081003354123),
+            Offset(8.330956385969174, 24.584197933972426),
+            Offset(10.795082531480682, 22.920903618043887),
+            Offset(14.118850428921743, 21.151292868049936),
+            Offset(18.25264983114299, 20.14945205026408),
+            Offset(21.311663261847183, 21.835975547204264),
+            Offset(22.017669221052497, 23.734736578402938),
+            Offset(22.22502452096443, 23.078337345433567),
+            Offset(22.535475994562226, 22.637953951770903),
+            Offset(22.851392493882464, 22.362715419699295),
+            Offset(23.1332359929415, 22.197579362061152),
+            Offset(23.369521577941427, 22.101975511401783),
+            Offset(23.560274059886364, 22.048938469040202),
+            Offset(23.709614091422043, 22.02119328278395),
+            Offset(23.822645300996605, 22.00787919273418),
+            Offset(23.90426194110445, 22.002292758165275),
+            Offset(23.958738335514504, 22.000425676539155),
+            Offset(23.989660411288344, 22.000026726952264),
+            Offset(23.999978366434483, 22.000000000117),
+            Offset(23.999999999999996, 22.000000000000004),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(6.0, 36.0),
+            Offset(5.755802622931704, 35.48132577125743),
+            Offset(5.010307637171476, 33.62392385238556),
+            Offset(4.045724406149144, 29.753229622188503),
+            Offset(4.0861862642629525, 23.09758660034176),
+            Offset(8.564534830281378, 13.380886967716135),
+            Offset(17.231202711318005, 7.518259082609372),
+            Offset(27.314036258810987, 7.072010923819601),
+            Offset(34.88234825995056, 10.868941008448914),
+            Offset(39.083273856489825, 15.969526394266794),
+            Offset(40.919225828875916, 20.778998598927743),
+            Offset(41.3739602310385, 24.78219833097991),
+            Offset(41.11406980357167, 27.929167557007872),
+            Offset(40.542525130544135, 30.325075504900447),
+            Offset(39.89012097509991, 32.10612260851932),
+            Offset(39.28321027503917, 33.39611499843784),
+            Offset(38.78633478113526, 34.295159061960966),
+            Offset(38.427830072911185, 34.87959495005215),
+            Offset(38.21486700558917, 35.20562675712161),
+            Offset(38.14228859445484, 35.31348285156429),
+            Offset(38.14213562373095, 35.31370849898477),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 36.0),
+            Offset(5.755802622931704, 35.48132577125743),
+            Offset(5.010307637171476, 33.62392385238556),
+            Offset(4.045724406149144, 29.753229622188503),
+            Offset(4.0861862642629525, 23.09758660034176),
+            Offset(8.564534830281378, 13.380886967716135),
+            Offset(17.231202711318005, 7.518259082609372),
+            Offset(27.314036258810987, 7.072010923819601),
+            Offset(34.88234825995056, 10.868941008448914),
+            Offset(39.083273856489825, 15.969526394266794),
+            Offset(40.919225828875916, 20.778998598927743),
+            Offset(41.3739602310385, 24.78219833097991),
+            Offset(41.11406980357167, 27.929167557007872),
+            Offset(40.542525130544135, 30.325075504900447),
+            Offset(39.89012097509991, 32.10612260851932),
+            Offset(39.28321027503917, 33.39611499843784),
+            Offset(38.78633478113526, 34.295159061960966),
+            Offset(38.427830072911185, 34.87959495005215),
+            Offset(38.21486700558917, 35.20562675712161),
+            Offset(38.14228859445484, 35.31348285156429),
+            Offset(38.14213562373095, 35.31370849898477),
+          ],
+          <Offset>[
+            Offset(42.0, 36.0),
+            Offset(41.74444683546158, 36.38547605961641),
+            Offset(40.779522756565214, 37.69372111300368),
+            Offset(38.528666977308376, 40.09376498715554),
+            Offset(33.888986943996294, 43.29147358735062),
+            Offset(24.750542697847216, 45.53696181075469),
+            Offset(15.464766435530956, 43.474895505995576),
+            Offset(8.222494539070887, 37.592709388360404),
+            Offset(4.758504180127748, 30.580714593150105),
+            Offset(4.129263053465191, 24.58446277139909),
+            Offset(4.926943149181838, 20.033621174401183),
+            Offset(6.282139476517855, 16.74701842430737),
+            Offset(7.742736159475957, 14.425312891970705),
+            Offset(9.093991024465053, 12.803953891864928),
+            Offset(10.244527895379168, 11.682146233885181),
+            Offset(11.164395063408941, 10.916519114743828),
+            Offset(11.853227009710306, 10.407762682849047),
+            Offset(12.32400850869499, 10.088659111654252),
+            Offset(12.595052974338124, 9.914815599625843),
+            Offset(12.68610028619067, 9.857982919050801),
+            Offset(12.68629150101523, 9.85786437626906),
+          ],
+          <Offset>[
+            Offset(42.0, 36.0),
+            Offset(41.74444683546158, 36.38547605961641),
+            Offset(40.779522756565214, 37.69372111300368),
+            Offset(38.528666977308376, 40.09376498715554),
+            Offset(33.888986943996294, 43.29147358735062),
+            Offset(24.750542697847216, 45.53696181075469),
+            Offset(15.464766435530956, 43.474895505995576),
+            Offset(8.222494539070887, 37.592709388360404),
+            Offset(4.758504180127748, 30.580714593150105),
+            Offset(4.129263053465191, 24.58446277139909),
+            Offset(4.926943149181838, 20.033621174401183),
+            Offset(6.282139476517855, 16.74701842430737),
+            Offset(7.742736159475957, 14.425312891970705),
+            Offset(9.093991024465053, 12.803953891864928),
+            Offset(10.244527895379168, 11.682146233885181),
+            Offset(11.164395063408941, 10.916519114743828),
+            Offset(11.853227009710306, 10.407762682849047),
+            Offset(12.32400850869499, 10.088659111654252),
+            Offset(12.595052974338124, 9.914815599625843),
+            Offset(12.68610028619067, 9.857982919050801),
+            Offset(12.68629150101523, 9.85786437626906),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 36.0),
+            Offset(41.74444683546158, 36.38547605961641),
+            Offset(40.779522756565214, 37.69372111300368),
+            Offset(38.528666977308376, 40.09376498715554),
+            Offset(33.888986943996294, 43.29147358735062),
+            Offset(24.750542697847216, 45.53696181075469),
+            Offset(15.464766435530956, 43.474895505995576),
+            Offset(8.222494539070887, 37.592709388360404),
+            Offset(4.758504180127748, 30.580714593150105),
+            Offset(4.129263053465191, 24.58446277139909),
+            Offset(4.926943149181838, 20.033621174401183),
+            Offset(6.282139476517855, 16.74701842430737),
+            Offset(7.742736159475957, 14.425312891970705),
+            Offset(9.093991024465053, 12.803953891864928),
+            Offset(10.244527895379168, 11.682146233885181),
+            Offset(11.164395063408941, 10.916519114743828),
+            Offset(11.853227009710306, 10.407762682849047),
+            Offset(12.32400850869499, 10.088659111654252),
+            Offset(12.595052974338124, 9.914815599625843),
+            Offset(12.68610028619067, 9.857982919050801),
+            Offset(12.68629150101523, 9.85786437626906),
+          ],
+          <Offset>[
+            Offset(42.0, 32.0),
+            Offset(41.84490797861258, 32.38673781377975),
+            Offset(41.231722452189445, 33.71936387751549),
+            Offset(39.67761535119361, 36.262326923693394),
+            Offset(36.132752164775056, 39.98005128960247),
+            Offset(28.323439902629275, 43.73851649213626),
+            Offset(19.459948260351645, 43.67116620330525),
+            Offset(11.613683257353195, 39.71399180166486),
+            Offset(6.948701245094547, 33.927808379797085),
+            Offset(5.086478206479892, 28.468241749512934),
+            Offset(4.844123435345551, 24.032763694367194),
+            Offset(5.389341709109795, 20.646109619254112),
+            Offset(6.242307863360715, 18.133238852425784),
+            Offset(7.147199734127774, 16.29823545920705),
+            Offset(7.975197187086486, 14.976101020520819),
+            Offset(8.66666218744294, 14.040831916036076),
+            Offset(9.199071856475648, 13.400330213007376),
+            Offset(9.569460082206334, 12.989083729900493),
+            Offset(9.784962845727483, 12.76146160309818),
+            Offset(9.857711404800284, 12.68644828663571),
+            Offset(9.857864376269042, 12.686291501015248),
+          ],
+          <Offset>[
+            Offset(42.0, 32.0),
+            Offset(41.84490797861258, 32.38673781377975),
+            Offset(41.231722452189445, 33.71936387751549),
+            Offset(39.67761535119361, 36.262326923693394),
+            Offset(36.132752164775056, 39.98005128960247),
+            Offset(28.323439902629275, 43.73851649213626),
+            Offset(19.459948260351645, 43.67116620330525),
+            Offset(11.613683257353195, 39.71399180166486),
+            Offset(6.948701245094547, 33.927808379797085),
+            Offset(5.086478206479892, 28.468241749512934),
+            Offset(4.844123435345551, 24.032763694367194),
+            Offset(5.389341709109795, 20.646109619254112),
+            Offset(6.242307863360715, 18.133238852425784),
+            Offset(7.147199734127774, 16.29823545920705),
+            Offset(7.975197187086486, 14.976101020520819),
+            Offset(8.66666218744294, 14.040831916036076),
+            Offset(9.199071856475648, 13.400330213007376),
+            Offset(9.569460082206334, 12.989083729900493),
+            Offset(9.784962845727483, 12.76146160309818),
+            Offset(9.857711404800284, 12.68644828663571),
+            Offset(9.857864376269042, 12.686291501015248),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 32.0),
+            Offset(41.84490797861258, 32.38673781377975),
+            Offset(41.231722452189445, 33.71936387751549),
+            Offset(39.67761535119361, 36.262326923693394),
+            Offset(36.132752164775056, 39.98005128960247),
+            Offset(28.323439902629275, 43.73851649213626),
+            Offset(19.459948260351645, 43.67116620330525),
+            Offset(11.613683257353195, 39.71399180166486),
+            Offset(6.948701245094547, 33.927808379797085),
+            Offset(5.086478206479892, 28.468241749512934),
+            Offset(4.844123435345551, 24.032763694367194),
+            Offset(5.389341709109795, 20.646109619254112),
+            Offset(6.242307863360715, 18.133238852425784),
+            Offset(7.147199734127774, 16.29823545920705),
+            Offset(7.975197187086486, 14.976101020520819),
+            Offset(8.66666218744294, 14.040831916036076),
+            Offset(9.199071856475648, 13.400330213007376),
+            Offset(9.569460082206334, 12.989083729900493),
+            Offset(9.784962845727483, 12.76146160309818),
+            Offset(9.857711404800284, 12.68644828663571),
+            Offset(9.857864376269042, 12.686291501015248),
+          ],
+          <Offset>[
+            Offset(6.0, 32.0),
+            Offset(5.8562637660827015, 31.482587525420783),
+            Offset(5.462507332795713, 29.649566616897364),
+            Offset(5.19467278003437, 25.921791558726365),
+            Offset(6.329951485041715, 19.786164302593612),
+            Offset(12.137432035063437, 11.58244164909771),
+            Offset(21.226384536138692, 7.714529779919044),
+            Offset(30.7052249770933, 9.193293337124057),
+            Offset(37.072545324917364, 14.216034795095894),
+            Offset(40.040489009504526, 19.85330537238064),
+            Offset(40.83640611503963, 24.77814111889375),
+            Offset(40.48116246363044, 28.68128952592665),
+            Offset(39.61364150745642, 31.63709351746295),
+            Offset(38.59573384020686, 33.81935707224257),
+            Offset(37.620790266807234, 35.40007739515496),
+            Offset(36.78547739907316, 36.520427799730086),
+            Offset(36.1321796279006, 37.28772659211929),
+            Offset(35.67328164642253, 37.78001956829839),
+            Offset(35.40477687697853, 38.05227276059395),
+            Offset(35.31389971306446, 38.1419482191492),
+            Offset(35.31370849898476, 38.14213562373095),
+          ],
+          <Offset>[
+            Offset(6.0, 32.0),
+            Offset(5.8562637660827015, 31.482587525420783),
+            Offset(5.462507332795713, 29.649566616897364),
+            Offset(5.19467278003437, 25.921791558726365),
+            Offset(6.329951485041715, 19.786164302593612),
+            Offset(12.137432035063437, 11.58244164909771),
+            Offset(21.226384536138692, 7.714529779919044),
+            Offset(30.7052249770933, 9.193293337124057),
+            Offset(37.072545324917364, 14.216034795095894),
+            Offset(40.040489009504526, 19.85330537238064),
+            Offset(40.83640611503963, 24.77814111889375),
+            Offset(40.48116246363044, 28.68128952592665),
+            Offset(39.61364150745642, 31.63709351746295),
+            Offset(38.59573384020686, 33.81935707224257),
+            Offset(37.620790266807234, 35.40007739515496),
+            Offset(36.78547739907316, 36.520427799730086),
+            Offset(36.1321796279006, 37.28772659211929),
+            Offset(35.67328164642253, 37.78001956829839),
+            Offset(35.40477687697853, 38.05227276059395),
+            Offset(35.31389971306446, 38.1419482191492),
+            Offset(35.31370849898476, 38.14213562373095),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 32.0),
+            Offset(5.8562637660827015, 31.482587525420783),
+            Offset(5.462507332795713, 29.649566616897364),
+            Offset(5.19467278003437, 25.921791558726365),
+            Offset(6.329951485041715, 19.786164302593612),
+            Offset(12.137432035063437, 11.58244164909771),
+            Offset(21.226384536138692, 7.714529779919044),
+            Offset(30.7052249770933, 9.193293337124057),
+            Offset(37.072545324917364, 14.216034795095894),
+            Offset(40.040489009504526, 19.85330537238064),
+            Offset(40.83640611503963, 24.77814111889375),
+            Offset(40.48116246363044, 28.68128952592665),
+            Offset(39.61364150745642, 31.63709351746295),
+            Offset(38.59573384020686, 33.81935707224257),
+            Offset(37.620790266807234, 35.40007739515496),
+            Offset(36.78547739907316, 36.520427799730086),
+            Offset(36.1321796279006, 37.28772659211929),
+            Offset(35.67328164642253, 37.78001956829839),
+            Offset(35.40477687697853, 38.05227276059395),
+            Offset(35.31389971306446, 38.1419482191492),
+            Offset(35.31370849898476, 38.14213562373095),
+          ],
+          <Offset>[
+            Offset(6.0, 36.0),
+            Offset(5.755802622931704, 35.48132577125743),
+            Offset(5.010307637171476, 33.62392385238556),
+            Offset(4.045724406149144, 29.753229622188503),
+            Offset(4.0861862642629525, 23.09758660034176),
+            Offset(8.564534830281378, 13.380886967716135),
+            Offset(17.231202711318005, 7.518259082609372),
+            Offset(27.314036258810987, 7.072010923819601),
+            Offset(34.88234825995056, 10.868941008448914),
+            Offset(39.083273856489825, 15.969526394266794),
+            Offset(40.919225828875916, 20.778998598927743),
+            Offset(41.3739602310385, 24.78219833097991),
+            Offset(41.11406980357167, 27.929167557007872),
+            Offset(40.542525130544135, 30.325075504900447),
+            Offset(39.89012097509991, 32.10612260851932),
+            Offset(39.28321027503917, 33.39611499843784),
+            Offset(38.78633478113526, 34.295159061960966),
+            Offset(38.427830072911185, 34.87959495005215),
+            Offset(38.21486700558917, 35.20562675712161),
+            Offset(38.14228859445484, 35.31348285156429),
+            Offset(38.14213562373095, 35.31370849898477),
+          ],
+          <Offset>[
+            Offset(6.0, 36.0),
+            Offset(5.755802622931704, 35.48132577125743),
+            Offset(5.010307637171476, 33.62392385238556),
+            Offset(4.045724406149144, 29.753229622188503),
+            Offset(4.0861862642629525, 23.09758660034176),
+            Offset(8.564534830281378, 13.380886967716135),
+            Offset(17.231202711318005, 7.518259082609372),
+            Offset(27.314036258810987, 7.072010923819601),
+            Offset(34.88234825995056, 10.868941008448914),
+            Offset(39.083273856489825, 15.969526394266794),
+            Offset(40.919225828875916, 20.778998598927743),
+            Offset(41.3739602310385, 24.78219833097991),
+            Offset(41.11406980357167, 27.929167557007872),
+            Offset(40.542525130544135, 30.325075504900447),
+            Offset(39.89012097509991, 32.10612260851932),
+            Offset(39.28321027503917, 33.39611499843784),
+            Offset(38.78633478113526, 34.295159061960966),
+            Offset(38.427830072911185, 34.87959495005215),
+            Offset(38.21486700558917, 35.20562675712161),
+            Offset(38.14228859445484, 35.31348285156429),
+            Offset(38.14213562373095, 35.31370849898477),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(6.0, 16.0),
+            Offset(6.1715484384586965, 15.794477742439435),
+            Offset(6.7847088111550455, 15.101124417731686),
+            Offset(8.064809036741185, 13.831258504138926),
+            Offset(10.327896232258308, 12.039205529321242),
+            Offset(14.135313755104503, 9.942822494875724),
+            Offset(17.85576112924513, 8.665143896025008),
+            Offset(21.423658706813853, 7.951290714362276),
+            Offset(24.4827045503675, 7.678712896347676),
+            Offset(26.97423123596107, 7.701340160804744),
+            Offset(28.998592463240865, 7.903908926143316),
+            Offset(30.63345298306288, 8.203149386677556),
+            Offset(31.940891049382852, 8.538653103313674),
+            Offset(32.972454180204764, 8.86889674774221),
+            Offset(33.77116819246568, 9.167199716087978),
+            Offset(34.37258718307626, 9.41785407630459),
+            Offset(34.80589641289028, 9.612989774645834),
+            Offset(35.09487909270652, 9.750154330042164),
+            Offset(35.25887447203158, 9.830559256807962),
+            Offset(35.31359376965362, 9.857807024169409),
+            Offset(35.313708498984745, 9.85786437626905),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 16.0),
+            Offset(6.1715484384586965, 15.794477742439435),
+            Offset(6.7847088111550455, 15.101124417731686),
+            Offset(8.064809036741185, 13.831258504138926),
+            Offset(10.327896232258308, 12.039205529321242),
+            Offset(14.135313755104503, 9.942822494875724),
+            Offset(17.85576112924513, 8.665143896025008),
+            Offset(21.423658706813853, 7.951290714362276),
+            Offset(24.4827045503675, 7.678712896347676),
+            Offset(26.97423123596107, 7.701340160804744),
+            Offset(28.998592463240865, 7.903908926143316),
+            Offset(30.63345298306288, 8.203149386677556),
+            Offset(31.940891049382852, 8.538653103313674),
+            Offset(32.972454180204764, 8.86889674774221),
+            Offset(33.77116819246568, 9.167199716087978),
+            Offset(34.37258718307626, 9.41785407630459),
+            Offset(34.80589641289028, 9.612989774645834),
+            Offset(35.09487909270652, 9.750154330042164),
+            Offset(35.25887447203158, 9.830559256807962),
+            Offset(35.31359376965362, 9.857807024169409),
+            Offset(35.313708498984745, 9.85786437626905),
+          ],
+          <Offset>[
+            Offset(42.0, 16.0),
+            Offset(42.16746021740808, 16.33700442573998),
+            Offset(42.70156935314198, 17.546350134810247),
+            Offset(43.51618803296806, 20.092227060671057),
+            Offset(44.054130843957765, 24.630515982034453),
+            Offset(42.51578514966233, 32.09115975577829),
+            Offset(38.14918656898573, 38.40025586692044),
+            Offset(31.819015610164328, 42.4177462160811),
+            Offset(25.688490276712407, 43.658513907222414),
+            Offset(20.913117319647185, 43.18743459360287),
+            Offset(17.449482120110062, 42.00109429799166),
+            Offset(15.000094691649505, 40.63150285293139),
+            Offset(13.281945592346325, 39.325724325359715),
+            Offset(12.08031575221366, 38.18644357392904),
+            Offset(11.243480256299911, 37.247500217520134),
+            Offset(10.667161603617046, 36.51126283372602),
+            Offset(10.280531326105294, 35.9664803851875),
+            Offset(10.035976293649936, 35.59684445677665),
+            Offset(9.901923001772541, 35.38491333072124),
+            Offset(9.857956160571883, 35.31385765884373),
+            Offset(9.857864376269035, 35.31370849898477),
+          ],
+          <Offset>[
+            Offset(42.0, 16.0),
+            Offset(42.16746021740808, 16.33700442573998),
+            Offset(42.70156935314198, 17.546350134810247),
+            Offset(43.51618803296806, 20.092227060671057),
+            Offset(44.054130843957765, 24.630515982034453),
+            Offset(42.51578514966233, 32.09115975577829),
+            Offset(38.14918656898573, 38.40025586692044),
+            Offset(31.819015610164328, 42.4177462160811),
+            Offset(25.688490276712407, 43.658513907222414),
+            Offset(20.913117319647185, 43.18743459360287),
+            Offset(17.449482120110062, 42.00109429799166),
+            Offset(15.000094691649505, 40.63150285293139),
+            Offset(13.281945592346325, 39.325724325359715),
+            Offset(12.08031575221366, 38.18644357392904),
+            Offset(11.243480256299911, 37.247500217520134),
+            Offset(10.667161603617046, 36.51126283372602),
+            Offset(10.280531326105294, 35.9664803851875),
+            Offset(10.035976293649936, 35.59684445677665),
+            Offset(9.901923001772541, 35.38491333072124),
+            Offset(9.857956160571883, 35.31385765884373),
+            Offset(9.857864376269035, 35.31370849898477),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 16.0),
+            Offset(42.16746021740808, 16.33700442573998),
+            Offset(42.70156935314198, 17.546350134810247),
+            Offset(43.51618803296806, 20.092227060671057),
+            Offset(44.054130843957765, 24.630515982034453),
+            Offset(42.51578514966233, 32.09115975577829),
+            Offset(38.14918656898573, 38.40025586692044),
+            Offset(31.819015610164328, 42.4177462160811),
+            Offset(25.688490276712407, 43.658513907222414),
+            Offset(20.913117319647185, 43.18743459360287),
+            Offset(17.449482120110062, 42.00109429799166),
+            Offset(15.000094691649505, 40.63150285293139),
+            Offset(13.281945592346325, 39.325724325359715),
+            Offset(12.08031575221366, 38.18644357392904),
+            Offset(11.243480256299911, 37.247500217520134),
+            Offset(10.667161603617046, 36.51126283372602),
+            Offset(10.280531326105294, 35.9664803851875),
+            Offset(10.035976293649936, 35.59684445677665),
+            Offset(9.901923001772541, 35.38491333072124),
+            Offset(9.857956160571883, 35.31385765884373),
+            Offset(9.857864376269035, 35.31370849898477),
+          ],
+          <Offset>[
+            Offset(42.0, 12.0),
+            Offset(42.227740959997035, 12.33745867252338),
+            Offset(42.973261099484034, 13.555587852367255),
+            Offset(44.211851205916076, 16.153184949979185),
+            Offset(45.453165338703684, 20.883156580734514),
+            Offset(44.97671151198483, 28.937774045271865),
+            Offset(41.453087899085226, 36.14543081806037),
+            Offset(35.64862177702197, 41.26270656015326),
+            Offset(29.68624594458738, 43.52453771540631),
+            Offset(24.8560167010692, 43.86089169541553),
+            Offset(21.238058272537653, 43.28432878056175),
+            Offset(18.60324507678882, 42.368542663088434),
+            Offset(16.702731283684777, 41.398940487252666),
+            Offset(15.33782095512331, 40.50779228815028),
+            Offset(14.363513645347927, 39.750576654871885),
+            Offset(13.67754035444165, 39.145199009221486),
+            Offset(13.208696949498814, 38.691520950385836),
+            Offset(12.90783075217599, 38.38116699000516),
+            Offset(12.741295676651793, 38.202352382972244),
+            Offset(12.68640623109125, 38.14226183763059),
+            Offset(12.686291501015226, 38.14213562373095),
+          ],
+          <Offset>[
+            Offset(42.0, 12.0),
+            Offset(42.227740959997035, 12.33745867252338),
+            Offset(42.973261099484034, 13.555587852367255),
+            Offset(44.211851205916076, 16.153184949979185),
+            Offset(45.453165338703684, 20.883156580734514),
+            Offset(44.97671151198483, 28.937774045271865),
+            Offset(41.453087899085226, 36.14543081806037),
+            Offset(35.64862177702197, 41.26270656015326),
+            Offset(29.68624594458738, 43.52453771540631),
+            Offset(24.8560167010692, 43.86089169541553),
+            Offset(21.238058272537653, 43.28432878056175),
+            Offset(18.60324507678882, 42.368542663088434),
+            Offset(16.702731283684777, 41.398940487252666),
+            Offset(15.33782095512331, 40.50779228815028),
+            Offset(14.363513645347927, 39.750576654871885),
+            Offset(13.67754035444165, 39.145199009221486),
+            Offset(13.208696949498814, 38.691520950385836),
+            Offset(12.90783075217599, 38.38116699000516),
+            Offset(12.741295676651793, 38.202352382972244),
+            Offset(12.68640623109125, 38.14226183763059),
+            Offset(12.686291501015226, 38.14213562373095),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 12.0),
+            Offset(42.227740959997035, 12.33745867252338),
+            Offset(42.973261099484034, 13.555587852367255),
+            Offset(44.211851205916076, 16.153184949979185),
+            Offset(45.453165338703684, 20.883156580734514),
+            Offset(44.97671151198483, 28.937774045271865),
+            Offset(41.453087899085226, 36.14543081806037),
+            Offset(35.64862177702197, 41.26270656015326),
+            Offset(29.68624594458738, 43.52453771540631),
+            Offset(24.8560167010692, 43.86089169541553),
+            Offset(21.238058272537653, 43.28432878056175),
+            Offset(18.60324507678882, 42.368542663088434),
+            Offset(16.702731283684777, 41.398940487252666),
+            Offset(15.33782095512331, 40.50779228815028),
+            Offset(14.363513645347927, 39.750576654871885),
+            Offset(13.67754035444165, 39.145199009221486),
+            Offset(13.208696949498814, 38.691520950385836),
+            Offset(12.90783075217599, 38.38116699000516),
+            Offset(12.741295676651793, 38.202352382972244),
+            Offset(12.68640623109125, 38.14226183763059),
+            Offset(12.686291501015226, 38.14213562373095),
+          ],
+          <Offset>[
+            Offset(6.0, 12.0),
+            Offset(6.231829181047647, 11.794931989222837),
+            Offset(7.056400557497106, 11.110362135288694),
+            Offset(8.7604722096892, 9.89221639344705),
+            Offset(11.726930727004222, 8.291846128021302),
+            Offset(16.596240117427012, 6.7894367843693),
+            Offset(21.159662459344624, 6.4103188471649375),
+            Offset(25.253264873671498, 6.796251058434446),
+            Offset(28.48046021824247, 7.5447367045315765),
+            Offset(30.917130617383087, 8.374797262617399),
+            Offset(32.78716861566846, 9.187143408713407),
+            Offset(34.236603368202196, 9.940189196834599),
+            Offset(35.3616767407213, 10.611869265206622),
+            Offset(36.22995938311441, 11.190245461963444),
+            Offset(36.8912015815137, 11.67027615343973),
+            Offset(37.38296593390086, 12.051790251800059),
+            Offset(37.734062036283795, 12.338030339844167),
+            Offset(37.96673355123257, 12.534476863270674),
+            Offset(38.09824714691083, 12.647998309058966),
+            Offset(38.14204384017299, 12.686211202956269),
+            Offset(38.14213562373094, 12.68629150101524),
+          ],
+          <Offset>[
+            Offset(6.0, 12.0),
+            Offset(6.231829181047647, 11.794931989222837),
+            Offset(7.056400557497106, 11.110362135288694),
+            Offset(8.7604722096892, 9.89221639344705),
+            Offset(11.726930727004222, 8.291846128021302),
+            Offset(16.596240117427012, 6.7894367843693),
+            Offset(21.159662459344624, 6.4103188471649375),
+            Offset(25.253264873671498, 6.796251058434446),
+            Offset(28.48046021824247, 7.5447367045315765),
+            Offset(30.917130617383087, 8.374797262617399),
+            Offset(32.78716861566846, 9.187143408713407),
+            Offset(34.236603368202196, 9.940189196834599),
+            Offset(35.3616767407213, 10.611869265206622),
+            Offset(36.22995938311441, 11.190245461963444),
+            Offset(36.8912015815137, 11.67027615343973),
+            Offset(37.38296593390086, 12.051790251800059),
+            Offset(37.734062036283795, 12.338030339844167),
+            Offset(37.96673355123257, 12.534476863270674),
+            Offset(38.09824714691083, 12.647998309058966),
+            Offset(38.14204384017299, 12.686211202956269),
+            Offset(38.14213562373094, 12.68629150101524),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 12.0),
+            Offset(6.231829181047647, 11.794931989222837),
+            Offset(7.056400557497106, 11.110362135288694),
+            Offset(8.7604722096892, 9.89221639344705),
+            Offset(11.726930727004222, 8.291846128021302),
+            Offset(16.596240117427012, 6.7894367843693),
+            Offset(21.159662459344624, 6.4103188471649375),
+            Offset(25.253264873671498, 6.796251058434446),
+            Offset(28.48046021824247, 7.5447367045315765),
+            Offset(30.917130617383087, 8.374797262617399),
+            Offset(32.78716861566846, 9.187143408713407),
+            Offset(34.236603368202196, 9.940189196834599),
+            Offset(35.3616767407213, 10.611869265206622),
+            Offset(36.22995938311441, 11.190245461963444),
+            Offset(36.8912015815137, 11.67027615343973),
+            Offset(37.38296593390086, 12.051790251800059),
+            Offset(37.734062036283795, 12.338030339844167),
+            Offset(37.96673355123257, 12.534476863270674),
+            Offset(38.09824714691083, 12.647998309058966),
+            Offset(38.14204384017299, 12.686211202956269),
+            Offset(38.14213562373094, 12.68629150101524),
+          ],
+          <Offset>[
+            Offset(6.0, 16.0),
+            Offset(6.1715484384586965, 15.794477742439435),
+            Offset(6.7847088111550455, 15.101124417731686),
+            Offset(8.064809036741185, 13.831258504138926),
+            Offset(10.327896232258308, 12.039205529321242),
+            Offset(14.135313755104503, 9.942822494875724),
+            Offset(17.85576112924513, 8.665143896025008),
+            Offset(21.423658706813853, 7.951290714362276),
+            Offset(24.4827045503675, 7.678712896347676),
+            Offset(26.97423123596107, 7.701340160804744),
+            Offset(28.998592463240865, 7.903908926143316),
+            Offset(30.63345298306288, 8.203149386677556),
+            Offset(31.940891049382852, 8.538653103313674),
+            Offset(32.972454180204764, 8.86889674774221),
+            Offset(33.77116819246568, 9.167199716087978),
+            Offset(34.37258718307626, 9.41785407630459),
+            Offset(34.80589641289028, 9.612989774645834),
+            Offset(35.09487909270652, 9.750154330042164),
+            Offset(35.25887447203158, 9.830559256807962),
+            Offset(35.31359376965362, 9.857807024169409),
+            Offset(35.313708498984745, 9.85786437626905),
+          ],
+          <Offset>[
+            Offset(6.0, 16.0),
+            Offset(6.1715484384586965, 15.794477742439435),
+            Offset(6.7847088111550455, 15.101124417731686),
+            Offset(8.064809036741185, 13.831258504138926),
+            Offset(10.327896232258308, 12.039205529321242),
+            Offset(14.135313755104503, 9.942822494875724),
+            Offset(17.85576112924513, 8.665143896025008),
+            Offset(21.423658706813853, 7.951290714362276),
+            Offset(24.4827045503675, 7.678712896347676),
+            Offset(26.97423123596107, 7.701340160804744),
+            Offset(28.998592463240865, 7.903908926143316),
+            Offset(30.63345298306288, 8.203149386677556),
+            Offset(31.940891049382852, 8.538653103313674),
+            Offset(32.972454180204764, 8.86889674774221),
+            Offset(33.77116819246568, 9.167199716087978),
+            Offset(34.37258718307626, 9.41785407630459),
+            Offset(34.80589641289028, 9.612989774645834),
+            Offset(35.09487909270652, 9.750154330042164),
+            Offset(35.25887447203158, 9.830559256807962),
+            Offset(35.31359376965362, 9.857807024169409),
+            Offset(35.313708498984745, 9.85786437626905),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+  ],
+);
diff --git a/lib/src/material/animated_icons/data/menu_home.g.dart b/lib/src/material/animated_icons/data/menu_home.g.dart
new file mode 100644
index 0000000..dfdcc79
--- /dev/null
+++ b/lib/src/material/animated_icons/data/menu_home.g.dart
@@ -0,0 +1,1702 @@
+// Copyright 2014 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.
+
+// AUTOGENERATED FILE DO NOT EDIT!
+// This file was generated by vitool.
+part of material_animated_icons;
+const _AnimatedIconData _$menu_home = _AnimatedIconData(
+  Size(48.0, 48.0),
+  <_PathFrames>[
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.853658536585,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(6.0, 12.046875),
+            Offset(6.1618805351617105, 11.806612807600084),
+            Offset(6.781939829118144, 10.945667339250278),
+            Offset(8.288913455339518, 9.166289848732603),
+            Offset(11.935042610511557, 6.074766376544677),
+            Offset(23.256788788206386, 2.4054443351966768),
+            Offset(36.24002084790047, 6.193846936842682),
+            Offset(43.02277578355333, 13.752502805353782),
+            Offset(45.18366429152893, 19.742117374864932),
+            Offset(45.60696992064962, 24.126681150009468),
+            Offset(45.345679612709475, 27.35248551632729),
+            Offset(44.82758462670622, 29.752296574674826),
+            Offset(44.24395862249675, 31.553763072552943),
+            Offset(43.684466559586255, 32.91061016598502),
+            Offset(43.191063930200144, 33.928759363213324),
+            Offset(42.78156545517243, 34.68316415294171),
+            Offset(42.461367454866235, 35.22742664024267),
+            Offset(42.229341109216534, 35.60035861469174),
+            Offset(42.08077400509552, 35.830587840098524),
+            Offset(42.00921658628339, 35.93923428088923),
+            Offset(42.0, 35.953125),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 12.046875),
+            Offset(6.1618805351617105, 11.806612807600084),
+            Offset(6.781939829118144, 10.945667339250278),
+            Offset(8.288913455339518, 9.166289848732603),
+            Offset(11.935042610511557, 6.074766376544677),
+            Offset(23.256788788206386, 2.4054443351966768),
+            Offset(36.24002084790047, 6.193846936842682),
+            Offset(43.02277578355333, 13.752502805353782),
+            Offset(45.18366429152893, 19.742117374864932),
+            Offset(45.60696992064962, 24.126681150009468),
+            Offset(45.345679612709475, 27.35248551632729),
+            Offset(44.82758462670622, 29.752296574674826),
+            Offset(44.24395862249675, 31.553763072552943),
+            Offset(43.684466559586255, 32.91061016598502),
+            Offset(43.191063930200144, 33.928759363213324),
+            Offset(42.78156545517243, 34.68316415294171),
+            Offset(42.461367454866235, 35.22742664024267),
+            Offset(42.229341109216534, 35.60035861469174),
+            Offset(42.08077400509552, 35.830587840098524),
+            Offset(42.00921658628339, 35.93923428088923),
+            Offset(42.0, 35.953125),
+          ],
+          <Offset>[
+            Offset(6.0, 16.0),
+            Offset(6.108878658535886, 15.759382477932412),
+            Offset(6.534966570751962, 14.8910699138017),
+            Offset(7.618227825115078, 13.06210530265691),
+            Offset(10.424159957469506, 9.727769370570497),
+            Offset(20.040805554226083, 4.704280802712924),
+            Offset(32.287403394427514, 6.130502385964199),
+            Offset(39.5356887387035, 11.890401517221083),
+            Offset(42.39074647053927, 16.944474204118634),
+            Offset(43.43945637458355, 20.820764893773518),
+            Offset(43.696252373988955, 23.759911640985017),
+            Offset(43.59634590360605, 25.995802401597533),
+            Offset(43.34634769806003, 27.703893997641842),
+            Offset(43.050276896752194, 29.008687468496074),
+            Offset(42.76198609923806, 29.998989715943665),
+            Offset(42.5089105957515, 30.739453134971555),
+            Offset(42.30406998921387, 31.277432365193103),
+            Offset(42.152364969347445, 31.64798313410847),
+            Offset(42.05392318847743, 31.87755403057046),
+            Offset(42.00616671483272, 31.986110457391014),
+            Offset(42.0, 31.999999999999996),
+          ],
+          <Offset>[
+            Offset(6.0, 16.0),
+            Offset(6.108878658535886, 15.759382477932412),
+            Offset(6.534966570751962, 14.8910699138017),
+            Offset(7.618227825115078, 13.06210530265691),
+            Offset(10.424159957469506, 9.727769370570497),
+            Offset(20.040805554226083, 4.704280802712924),
+            Offset(32.287403394427514, 6.130502385964199),
+            Offset(39.5356887387035, 11.890401517221083),
+            Offset(42.39074647053927, 16.944474204118634),
+            Offset(43.43945637458355, 20.820764893773518),
+            Offset(43.696252373988955, 23.759911640985017),
+            Offset(43.59634590360605, 25.995802401597533),
+            Offset(43.34634769806003, 27.703893997641842),
+            Offset(43.050276896752194, 29.008687468496074),
+            Offset(42.76198609923806, 29.998989715943665),
+            Offset(42.5089105957515, 30.739453134971555),
+            Offset(42.30406998921387, 31.277432365193103),
+            Offset(42.152364969347445, 31.64798313410847),
+            Offset(42.05392318847743, 31.87755403057046),
+            Offset(42.00616671483272, 31.986110457391014),
+            Offset(42.0, 31.999999999999996),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 16.0),
+            Offset(6.108878658535886, 15.759382477932412),
+            Offset(6.534966570751962, 14.8910699138017),
+            Offset(7.618227825115078, 13.06210530265691),
+            Offset(10.424159957469506, 9.727769370570497),
+            Offset(20.040805554226083, 4.704280802712924),
+            Offset(32.287403394427514, 6.130502385964199),
+            Offset(39.5356887387035, 11.890401517221083),
+            Offset(42.39074647053927, 16.944474204118634),
+            Offset(43.43945637458355, 20.820764893773518),
+            Offset(43.696252373988955, 23.759911640985017),
+            Offset(43.59634590360605, 25.995802401597533),
+            Offset(43.34634769806003, 27.703893997641842),
+            Offset(43.050276896752194, 29.008687468496074),
+            Offset(42.76198609923806, 29.998989715943665),
+            Offset(42.5089105957515, 30.739453134971555),
+            Offset(42.30406998921387, 31.277432365193103),
+            Offset(42.152364969347445, 31.64798313410847),
+            Offset(42.05392318847743, 31.87755403057046),
+            Offset(42.00616671483272, 31.986110457391014),
+            Offset(42.0, 31.999999999999996),
+          ],
+          <Offset>[
+            Offset(42.0, 16.0),
+            Offset(42.10564277096942, 16.24205569431935),
+            Offset(42.46464060935464, 17.140186069041555),
+            Offset(43.096325871919824, 19.169851120985427),
+            Offset(43.691033073024805, 23.4869536891827),
+            Offset(40.975664135876016, 33.99133760544266),
+            Offset(31.710542346111218, 42.12588030217638),
+            Offset(22.578054873653144, 43.64632464502351),
+            Offset(16.913395223901084, 42.37879301660944),
+            Offset(13.333404776292529, 40.559702483244706),
+            Offset(10.979690283915431, 38.78078262126988),
+            Offset(9.387007663408092, 37.208347927379336),
+            Offset(8.286670430885419, 35.8781847877691),
+            Offset(7.516561896694743, 34.78407475375168),
+            Offset(6.974676742284352, 33.906481109369096),
+            Offset(6.594641088228872, 33.222444423927385),
+            Offset(6.332580622754584, 32.709896242122134),
+            Offset(6.159190632336145, 32.348983237896554),
+            Offset(6.054753634514366, 32.12207688230202),
+            Offset(6.00617742890433, 32.01388478079939),
+            Offset(6.0, 32.0),
+          ],
+          <Offset>[
+            Offset(42.0, 16.0),
+            Offset(42.10564277096942, 16.24205569431935),
+            Offset(42.46464060935464, 17.140186069041555),
+            Offset(43.096325871919824, 19.169851120985427),
+            Offset(43.691033073024805, 23.4869536891827),
+            Offset(40.975664135876016, 33.99133760544266),
+            Offset(31.710542346111218, 42.12588030217638),
+            Offset(22.578054873653144, 43.64632464502351),
+            Offset(16.913395223901084, 42.37879301660944),
+            Offset(13.333404776292529, 40.559702483244706),
+            Offset(10.979690283915431, 38.78078262126988),
+            Offset(9.387007663408092, 37.208347927379336),
+            Offset(8.286670430885419, 35.8781847877691),
+            Offset(7.516561896694743, 34.78407475375168),
+            Offset(6.974676742284352, 33.906481109369096),
+            Offset(6.594641088228872, 33.222444423927385),
+            Offset(6.332580622754584, 32.709896242122134),
+            Offset(6.159190632336145, 32.348983237896554),
+            Offset(6.054753634514366, 32.12207688230202),
+            Offset(6.00617742890433, 32.01388478079939),
+            Offset(6.0, 32.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 16.0),
+            Offset(42.10564277096942, 16.24205569431935),
+            Offset(42.46464060935464, 17.140186069041555),
+            Offset(43.096325871919824, 19.169851120985427),
+            Offset(43.691033073024805, 23.4869536891827),
+            Offset(40.975664135876016, 33.99133760544266),
+            Offset(31.710542346111218, 42.12588030217638),
+            Offset(22.578054873653144, 43.64632464502351),
+            Offset(16.913395223901084, 42.37879301660944),
+            Offset(13.333404776292529, 40.559702483244706),
+            Offset(10.979690283915431, 38.78078262126988),
+            Offset(9.387007663408092, 37.208347927379336),
+            Offset(8.286670430885419, 35.8781847877691),
+            Offset(7.516561896694743, 34.78407475375168),
+            Offset(6.974676742284352, 33.906481109369096),
+            Offset(6.594641088228872, 33.222444423927385),
+            Offset(6.332580622754584, 32.709896242122134),
+            Offset(6.159190632336145, 32.348983237896554),
+            Offset(6.054753634514366, 32.12207688230202),
+            Offset(6.00617742890433, 32.01388478079939),
+            Offset(6.0, 32.0),
+          ],
+          <Offset>[
+            Offset(42.0, 12.0546875),
+            Offset(42.15853990080349, 12.297097821754479),
+            Offset(42.711125778277406, 13.202580732779758),
+            Offset(43.765686036471884, 15.281734907088639),
+            Offset(45.19892979196993, 19.841170068662862),
+            Offset(44.185291671765455, 31.697044293000552),
+            Offset(35.65534830264055, 42.18909966619542),
+            Offset(26.058250442129758, 45.50474589108481),
+            Offset(19.700793444454007, 45.170907248283115),
+            Offset(15.496634698749768, 43.85908530814422),
+            Offset(12.625857785010018, 42.366256548241914),
+            Offset(10.615813108399369, 40.957418198928806),
+            Offset(9.18250742068886, 39.72044542577326),
+            Offset(8.14949822027419, 38.6782861415618),
+            Offset(7.402906593343346, 37.82848441346233),
+            Offset(6.866757104054109, 37.15836154660511),
+            Offset(6.489567223850322, 36.65208420437516),
+            Offset(6.236014645446598, 36.2935476997435),
+            Offset(6.081551386277482, 36.0672983720484),
+            Offset(6.009221272941055, 35.9591961066227),
+            Offset(6.0, 35.9453125),
+          ],
+          <Offset>[
+            Offset(42.0, 12.0546875),
+            Offset(42.15853990080349, 12.297097821754479),
+            Offset(42.711125778277406, 13.202580732779758),
+            Offset(43.765686036471884, 15.281734907088639),
+            Offset(45.19892979196993, 19.841170068662862),
+            Offset(44.185291671765455, 31.697044293000552),
+            Offset(35.65534830264055, 42.18909966619542),
+            Offset(26.058250442129758, 45.50474589108481),
+            Offset(19.700793444454007, 45.170907248283115),
+            Offset(15.496634698749768, 43.85908530814422),
+            Offset(12.625857785010018, 42.366256548241914),
+            Offset(10.615813108399369, 40.957418198928806),
+            Offset(9.18250742068886, 39.72044542577326),
+            Offset(8.14949822027419, 38.6782861415618),
+            Offset(7.402906593343346, 37.82848441346233),
+            Offset(6.866757104054109, 37.15836154660511),
+            Offset(6.489567223850322, 36.65208420437516),
+            Offset(6.236014645446598, 36.2935476997435),
+            Offset(6.081551386277482, 36.0672983720484),
+            Offset(6.009221272941055, 35.9591961066227),
+            Offset(6.0, 35.9453125),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 12.0546875),
+            Offset(42.15853990080349, 12.297097821754479),
+            Offset(42.711125778277406, 13.202580732779758),
+            Offset(43.765686036471884, 15.281734907088639),
+            Offset(45.19892979196993, 19.841170068662862),
+            Offset(44.185291671765455, 31.697044293000552),
+            Offset(35.65534830264055, 42.18909966619542),
+            Offset(26.058250442129758, 45.50474589108481),
+            Offset(19.700793444454007, 45.170907248283115),
+            Offset(15.496634698749768, 43.85908530814422),
+            Offset(12.625857785010018, 42.366256548241914),
+            Offset(10.615813108399369, 40.957418198928806),
+            Offset(9.18250742068886, 39.72044542577326),
+            Offset(8.14949822027419, 38.6782861415618),
+            Offset(7.402906593343346, 37.82848441346233),
+            Offset(6.866757104054109, 37.15836154660511),
+            Offset(6.489567223850322, 36.65208420437516),
+            Offset(6.236014645446598, 36.2935476997435),
+            Offset(6.081551386277482, 36.0672983720484),
+            Offset(6.009221272941055, 35.9591961066227),
+            Offset(6.0, 35.9453125),
+          ],
+          <Offset>[
+            Offset(6.0, 12.046875),
+            Offset(6.1618805351617105, 11.806612807600084),
+            Offset(6.781939829118144, 10.945667339250278),
+            Offset(8.288913455339518, 9.166289848732603),
+            Offset(11.935042610511557, 6.074766376544677),
+            Offset(23.256788788206386, 2.4054443351966768),
+            Offset(36.24002084790047, 6.193846936842682),
+            Offset(43.02277578355333, 13.752502805353782),
+            Offset(45.18366429152893, 19.742117374864932),
+            Offset(45.60696992064962, 24.126681150009468),
+            Offset(45.345679612709475, 27.35248551632729),
+            Offset(44.82758462670622, 29.752296574674826),
+            Offset(44.24395862249675, 31.553763072552943),
+            Offset(43.684466559586255, 32.91061016598502),
+            Offset(43.191063930200144, 33.928759363213324),
+            Offset(42.78156545517243, 34.68316415294171),
+            Offset(42.461367454866235, 35.22742664024267),
+            Offset(42.229341109216534, 35.60035861469174),
+            Offset(42.08077400509552, 35.830587840098524),
+            Offset(42.00921658628339, 35.93923428088923),
+            Offset(42.0, 35.953125),
+          ],
+          <Offset>[
+            Offset(6.0, 12.046875),
+            Offset(6.1618805351617105, 11.806612807600084),
+            Offset(6.781939829118144, 10.945667339250278),
+            Offset(8.288913455339518, 9.166289848732603),
+            Offset(11.935042610511557, 6.074766376544677),
+            Offset(23.256788788206386, 2.4054443351966768),
+            Offset(36.24002084790047, 6.193846936842682),
+            Offset(43.02277578355333, 13.752502805353782),
+            Offset(45.18366429152893, 19.742117374864932),
+            Offset(45.60696992064962, 24.126681150009468),
+            Offset(45.345679612709475, 27.35248551632729),
+            Offset(44.82758462670622, 29.752296574674826),
+            Offset(44.24395862249675, 31.553763072552943),
+            Offset(43.684466559586255, 32.91061016598502),
+            Offset(43.191063930200144, 33.928759363213324),
+            Offset(42.78156545517243, 34.68316415294171),
+            Offset(42.461367454866235, 35.22742664024267),
+            Offset(42.229341109216534, 35.60035861469174),
+            Offset(42.08077400509552, 35.830587840098524),
+            Offset(42.00921658628339, 35.93923428088923),
+            Offset(42.0, 35.953125),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(6.0, 26.0),
+            Offset(5.97480276509507, 25.758483620275058),
+            Offset(5.9102120831853355, 24.871534924524667),
+            Offset(5.921631764468266, 22.91713253788045),
+            Offset(6.602164313410562, 18.968567458224747),
+            Offset(11.905511997912269, 10.519519297615686),
+            Offset(22.288325077287325, 5.992865842523692),
+            Offset(30.404134439557254, 7.761342935335598),
+            Offset(33.61929906828942, 11.570908914742331),
+            Offset(35.25300243452185, 14.230458757139374),
+            Offset(36.21471762947331, 16.191242561808075),
+            Offset(36.803975496328306, 17.698645771806415),
+            Offset(37.18416968837589, 18.87242179985836),
+            Offset(37.445620479790264, 19.788403617209426),
+            Offset(37.63807337154698, 20.499019999728493),
+            Offset(37.76636524119959, 21.043465830420356),
+            Offset(37.84688070253245, 21.447001555066198),
+            Offset(37.895912975403306, 21.728985066095305),
+            Offset(37.92359388763603, 21.905378434612928),
+            Offset(37.93595283405028, 21.98924768876775),
+            Offset(37.9375, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 26.0),
+            Offset(5.97480276509507, 25.758483620275058),
+            Offset(5.9102120831853355, 24.871534924524667),
+            Offset(5.921631764468266, 22.91713253788045),
+            Offset(6.602164313410562, 18.968567458224747),
+            Offset(11.905511997912269, 10.519519297615686),
+            Offset(22.288325077287325, 5.992865842523692),
+            Offset(30.404134439557254, 7.761342935335598),
+            Offset(33.61929906828942, 11.570908914742331),
+            Offset(35.25300243452185, 14.230458757139374),
+            Offset(36.21471762947331, 16.191242561808075),
+            Offset(36.803975496328306, 17.698645771806415),
+            Offset(37.18416968837589, 18.87242179985836),
+            Offset(37.445620479790264, 19.788403617209426),
+            Offset(37.63807337154698, 20.499019999728493),
+            Offset(37.76636524119959, 21.043465830420356),
+            Offset(37.84688070253245, 21.447001555066198),
+            Offset(37.895912975403306, 21.728985066095305),
+            Offset(37.92359388763603, 21.905378434612928),
+            Offset(37.93595283405028, 21.98924768876775),
+            Offset(37.9375, 22.0),
+          ],
+          <Offset>[
+            Offset(42.0, 26.0),
+            Offset(41.9715668775286, 26.241156836662),
+            Offset(41.83988612178801, 27.12065107976452),
+            Offset(41.39972981127301, 29.024878356208966),
+            Offset(39.86903742896586, 32.72775177683695),
+            Offset(32.840370579562205, 39.80657610034542),
+            Offset(21.712188487576384, 41.94303848554115),
+            Offset(14.06742965730648, 38.354475524191464),
+            Offset(11.554665508171254, 33.598274280005),
+            Offset(10.55378227775913, 30.424424398606067),
+            Offset(10.116296706050587, 28.1735838726282),
+            Offset(9.950186111823127, 26.50031664598051),
+            Offset(9.907575779387743, 25.232058503788906),
+            Offset(9.912669821946713, 24.26340582721259),
+            Offset(9.927760918072412, 23.52461229394332),
+            Offset(9.95774683780533, 22.966059779854906),
+            Offset(9.993956644475434, 22.556166293104994),
+            Offset(10.026198124175803, 22.27177334090344),
+            Offset(10.04923690661602, 22.094713837168957),
+            Offset(10.060961130015452, 22.01075350168465),
+            Offset(10.0625, 22.0),
+          ],
+          <Offset>[
+            Offset(42.0, 26.0),
+            Offset(41.9715668775286, 26.241156836662),
+            Offset(41.83988612178801, 27.12065107976452),
+            Offset(41.39972981127301, 29.024878356208966),
+            Offset(39.86903742896586, 32.72775177683695),
+            Offset(32.840370579562205, 39.80657610034542),
+            Offset(21.712188487576384, 41.94303848554115),
+            Offset(14.06742965730648, 38.354475524191464),
+            Offset(11.554665508171254, 33.598274280005),
+            Offset(10.55378227775913, 30.424424398606067),
+            Offset(10.116296706050587, 28.1735838726282),
+            Offset(9.950186111823127, 26.50031664598051),
+            Offset(9.907575779387743, 25.232058503788906),
+            Offset(9.912669821946713, 24.26340582721259),
+            Offset(9.927760918072412, 23.52461229394332),
+            Offset(9.95774683780533, 22.966059779854906),
+            Offset(9.993956644475434, 22.556166293104994),
+            Offset(10.026198124175803, 22.27177334090344),
+            Offset(10.04923690661602, 22.094713837168957),
+            Offset(10.060961130015452, 22.01075350168465),
+            Offset(10.0625, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 26.0),
+            Offset(41.9715668775286, 26.241156836662),
+            Offset(41.83988612178801, 27.12065107976452),
+            Offset(41.39972981127301, 29.024878356208966),
+            Offset(39.86903742896586, 32.72775177683695),
+            Offset(32.840370579562205, 39.80657610034542),
+            Offset(21.712188487576384, 41.94303848554115),
+            Offset(14.06742965730648, 38.354475524191464),
+            Offset(11.554665508171254, 33.598274280005),
+            Offset(10.55378227775913, 30.424424398606067),
+            Offset(10.116296706050587, 28.1735838726282),
+            Offset(9.950186111823127, 26.50031664598051),
+            Offset(9.907575779387743, 25.232058503788906),
+            Offset(9.912669821946713, 24.26340582721259),
+            Offset(9.927760918072412, 23.52461229394332),
+            Offset(9.95774683780533, 22.966059779854906),
+            Offset(9.993956644475434, 22.556166293104994),
+            Offset(10.026198124175803, 22.27177334090344),
+            Offset(10.04923690661602, 22.094713837168957),
+            Offset(10.060961130015452, 22.01075350168465),
+            Offset(10.0625, 22.0),
+          ],
+          <Offset>[
+            Offset(42.0, 22.0),
+            Offset(42.02519723490493, 22.241516379724942),
+            Offset(42.089787916814664, 23.128465075475333),
+            Offset(42.078368235531734, 25.08286746211955),
+            Offset(41.39783568658944, 29.031432541775253),
+            Offset(36.09448800208773, 37.48048070238431),
+            Offset(25.716890915777384, 42.00721774895393),
+            Offset(17.730033699513, 40.31030272815691),
+            Offset(14.773810944920879, 36.822866202860226),
+            Offset(13.156032790251274, 34.39340640919596),
+            Offset(12.13588194777422, 32.57238911127051),
+            Offset(11.474202347993174, 31.15007140388232),
+            Offset(11.025213475416908, 30.025626279936134),
+            Offset(10.704423952068467, 29.134761499184584),
+            Offset(10.46368421682347, 28.432941181442178),
+            Offset(10.298295990283647, 27.89180160467138),
+            Offset(10.190423044262575, 27.48975598051868),
+            Offset(10.12234223563285, 27.208337182264366),
+            Offset(10.082773894961147, 27.032099939188196),
+            Offset(10.064770455621808, 26.94825203221997),
+            Offset(10.0625, 26.9375),
+          ],
+          <Offset>[
+            Offset(42.0, 22.0),
+            Offset(42.02519723490493, 22.241516379724942),
+            Offset(42.089787916814664, 23.128465075475333),
+            Offset(42.078368235531734, 25.08286746211955),
+            Offset(41.39783568658944, 29.031432541775253),
+            Offset(36.09448800208773, 37.48048070238431),
+            Offset(25.716890915777384, 42.00721774895393),
+            Offset(17.730033699513, 40.31030272815691),
+            Offset(14.773810944920879, 36.822866202860226),
+            Offset(13.156032790251274, 34.39340640919596),
+            Offset(12.13588194777422, 32.57238911127051),
+            Offset(11.474202347993174, 31.15007140388232),
+            Offset(11.025213475416908, 30.025626279936134),
+            Offset(10.704423952068467, 29.134761499184584),
+            Offset(10.46368421682347, 28.432941181442178),
+            Offset(10.298295990283647, 27.89180160467138),
+            Offset(10.190423044262575, 27.48975598051868),
+            Offset(10.12234223563285, 27.208337182264366),
+            Offset(10.082773894961147, 27.032099939188196),
+            Offset(10.064770455621808, 26.94825203221997),
+            Offset(10.0625, 26.9375),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 22.0),
+            Offset(42.02519723490493, 22.241516379724942),
+            Offset(42.089787916814664, 23.128465075475333),
+            Offset(42.078368235531734, 25.08286746211955),
+            Offset(41.39783568658944, 29.031432541775253),
+            Offset(36.09448800208773, 37.48048070238431),
+            Offset(25.716890915777384, 42.00721774895393),
+            Offset(17.730033699513, 40.31030272815691),
+            Offset(14.773810944920879, 36.822866202860226),
+            Offset(13.156032790251274, 34.39340640919596),
+            Offset(12.13588194777422, 32.57238911127051),
+            Offset(11.474202347993174, 31.15007140388232),
+            Offset(11.025213475416908, 30.025626279936134),
+            Offset(10.704423952068467, 29.134761499184584),
+            Offset(10.46368421682347, 28.432941181442178),
+            Offset(10.298295990283647, 27.89180160467138),
+            Offset(10.190423044262575, 27.48975598051868),
+            Offset(10.12234223563285, 27.208337182264366),
+            Offset(10.082773894961147, 27.032099939188196),
+            Offset(10.064770455621808, 26.94825203221997),
+            Offset(10.0625, 26.9375),
+          ],
+          <Offset>[
+            Offset(6.0, 22.0),
+            Offset(6.028433122471398, 21.758843163338),
+            Offset(6.1601138782119875, 20.87934892023548),
+            Offset(6.600270188726988, 18.975121643791034),
+            Offset(8.13096257103414, 15.272248223163048),
+            Offset(15.159629420437794, 8.193423899654581),
+            Offset(26.293027505488325, 6.057045105936464),
+            Offset(34.06673848176378, 9.717170139301047),
+            Offset(36.83844450503905, 14.79550083759756),
+            Offset(37.855252947013994, 18.199440767729264),
+            Offset(38.234302871196945, 20.590047800450385),
+            Offset(38.327991732498354, 22.348400529708222),
+            Offset(38.30180738440505, 23.665989576005586),
+            Offset(38.23737460991202, 24.659759289181423),
+            Offset(38.17399667029804, 25.40734888722735),
+            Offset(38.106914393677904, 25.96920765523683),
+            Offset(38.04334710231959, 26.380591242479884),
+            Offset(37.992057086860356, 26.66554890745623),
+            Offset(37.95713087598116, 26.842764536632167),
+            Offset(37.93976215965664, 26.92674621930307),
+            Offset(37.9375, 26.9375),
+          ],
+          <Offset>[
+            Offset(6.0, 22.0),
+            Offset(6.028433122471398, 21.758843163338),
+            Offset(6.1601138782119875, 20.87934892023548),
+            Offset(6.600270188726988, 18.975121643791034),
+            Offset(8.13096257103414, 15.272248223163048),
+            Offset(15.159629420437794, 8.193423899654581),
+            Offset(26.293027505488325, 6.057045105936464),
+            Offset(34.06673848176378, 9.717170139301047),
+            Offset(36.83844450503905, 14.79550083759756),
+            Offset(37.855252947013994, 18.199440767729264),
+            Offset(38.234302871196945, 20.590047800450385),
+            Offset(38.327991732498354, 22.348400529708222),
+            Offset(38.30180738440505, 23.665989576005586),
+            Offset(38.23737460991202, 24.659759289181423),
+            Offset(38.17399667029804, 25.40734888722735),
+            Offset(38.106914393677904, 25.96920765523683),
+            Offset(38.04334710231959, 26.380591242479884),
+            Offset(37.992057086860356, 26.66554890745623),
+            Offset(37.95713087598116, 26.842764536632167),
+            Offset(37.93976215965664, 26.92674621930307),
+            Offset(37.9375, 26.9375),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 22.0),
+            Offset(6.028433122471398, 21.758843163338),
+            Offset(6.1601138782119875, 20.87934892023548),
+            Offset(6.600270188726988, 18.975121643791034),
+            Offset(8.13096257103414, 15.272248223163048),
+            Offset(15.159629420437794, 8.193423899654581),
+            Offset(26.293027505488325, 6.057045105936464),
+            Offset(34.06673848176378, 9.717170139301047),
+            Offset(36.83844450503905, 14.79550083759756),
+            Offset(37.855252947013994, 18.199440767729264),
+            Offset(38.234302871196945, 20.590047800450385),
+            Offset(38.327991732498354, 22.348400529708222),
+            Offset(38.30180738440505, 23.665989576005586),
+            Offset(38.23737460991202, 24.659759289181423),
+            Offset(38.17399667029804, 25.40734888722735),
+            Offset(38.106914393677904, 25.96920765523683),
+            Offset(38.04334710231959, 26.380591242479884),
+            Offset(37.992057086860356, 26.66554890745623),
+            Offset(37.95713087598116, 26.842764536632167),
+            Offset(37.93976215965664, 26.92674621930307),
+            Offset(37.9375, 26.9375),
+          ],
+          <Offset>[
+            Offset(6.0, 26.0),
+            Offset(5.97480276509507, 25.758483620275058),
+            Offset(5.9102120831853355, 24.871534924524667),
+            Offset(5.921631764468266, 22.91713253788045),
+            Offset(6.602164313410562, 18.968567458224747),
+            Offset(11.905511997912269, 10.519519297615686),
+            Offset(22.288325077287325, 5.992865842523692),
+            Offset(30.404134439557254, 7.761342935335598),
+            Offset(33.61929906828942, 11.570908914742331),
+            Offset(35.25300243452185, 14.230458757139374),
+            Offset(36.21471762947331, 16.191242561808075),
+            Offset(36.803975496328306, 17.698645771806415),
+            Offset(37.18416968837589, 18.87242179985836),
+            Offset(37.445620479790264, 19.788403617209426),
+            Offset(37.63807337154698, 20.499019999728493),
+            Offset(37.76636524119959, 21.043465830420356),
+            Offset(37.84688070253245, 21.447001555066198),
+            Offset(37.895912975403306, 21.728985066095305),
+            Offset(37.92359388763603, 21.905378434612928),
+            Offset(37.93595283405028, 21.98924768876775),
+            Offset(37.9375, 22.0),
+          ],
+          <Offset>[
+            Offset(6.0, 26.0),
+            Offset(5.97480276509507, 25.758483620275058),
+            Offset(5.9102120831853355, 24.871534924524667),
+            Offset(5.921631764468266, 22.91713253788045),
+            Offset(6.602164313410562, 18.968567458224747),
+            Offset(11.905511997912269, 10.519519297615686),
+            Offset(22.288325077287325, 5.992865842523692),
+            Offset(30.404134439557254, 7.761342935335598),
+            Offset(33.61929906828942, 11.570908914742331),
+            Offset(35.25300243452185, 14.230458757139374),
+            Offset(36.21471762947331, 16.191242561808075),
+            Offset(36.803975496328306, 17.698645771806415),
+            Offset(37.18416968837589, 18.87242179985836),
+            Offset(37.445620479790264, 19.788403617209426),
+            Offset(37.63807337154698, 20.499019999728493),
+            Offset(37.76636524119959, 21.043465830420356),
+            Offset(37.84688070253245, 21.447001555066198),
+            Offset(37.895912975403306, 21.728985066095305),
+            Offset(37.92359388763603, 21.905378434612928),
+            Offset(37.93595283405028, 21.98924768876775),
+            Offset(37.9375, 22.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(24.946426391602, 26.0),
+            Offset(24.919526142652916, 26.01250952487153),
+            Offset(24.81962665114753, 26.055221415675078),
+            Offset(24.593386574407845, 26.131575775813122),
+            Offset(24.112549450730533, 26.21086569759901),
+            Offset(23.26102990202604, 26.40544929256212),
+            Offset(21.93030078929294, 28.333115001580325),
+            Offset(20.21574669997896, 26.840753449152132),
+            Offset(19.702668556230993, 25.46403358899737),
+            Offset(19.57851450097965, 24.507387236351565),
+            Offset(19.59742184955703, 23.820597237404847),
+            Offset(19.672638738964572, 23.313659191847616),
+            Offset(19.76421128572404, 22.933948220939197),
+            Offset(19.85318172952868, 22.647748525973807),
+            Offset(19.930810929304265, 22.432414178767115),
+            Offset(19.99628397445662, 22.272029228601628),
+            Offset(20.048487526419784, 22.155772744171706),
+            Offset(20.086790256569586, 22.07583407578212),
+            Offset(20.111504785414724, 22.026366303959268),
+            Offset(20.12345813528363, 22.002990192537517),
+            Offset(20.125, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(24.946426391602, 26.0),
+            Offset(24.919526142652916, 26.01250952487153),
+            Offset(24.81962665114753, 26.055221415675078),
+            Offset(24.593386574407845, 26.131575775813122),
+            Offset(24.112549450730533, 26.21086569759901),
+            Offset(23.26102990202604, 26.40544929256212),
+            Offset(21.93030078929294, 28.333115001580325),
+            Offset(20.21574669997896, 26.840753449152132),
+            Offset(19.702668556230993, 25.46403358899737),
+            Offset(19.57851450097965, 24.507387236351565),
+            Offset(19.59742184955703, 23.820597237404847),
+            Offset(19.672638738964572, 23.313659191847616),
+            Offset(19.76421128572404, 22.933948220939197),
+            Offset(19.85318172952868, 22.647748525973807),
+            Offset(19.930810929304265, 22.432414178767115),
+            Offset(19.99628397445662, 22.272029228601628),
+            Offset(20.048487526419784, 22.155772744171706),
+            Offset(20.086790256569586, 22.07583407578212),
+            Offset(20.111504785414724, 22.026366303959268),
+            Offset(20.12345813528363, 22.002990192537517),
+            Offset(20.125, 22.0),
+          ],
+          <Offset>[
+            Offset(42.000000000002004, 26.0),
+            Offset(41.971566877530606, 26.241156836662025),
+            Offset(41.83988612179001, 27.120651079764645),
+            Offset(41.39972981127498, 29.024878356209307),
+            Offset(39.86903742895662, 32.72775177683313),
+            Offset(32.84037057959128, 39.806576100386096),
+            Offset(21.712188487577507, 41.94303848547116),
+            Offset(14.067429657339456, 38.35447552412971),
+            Offset(11.554665508114638, 33.59827428006152),
+            Offset(10.553782277809306, 30.424424398573166),
+            Offset(10.11629670607785, 28.173583872615687),
+            Offset(9.950186111889643, 26.50031664595871),
+            Offset(9.907575779455914, 25.23205850377301),
+            Offset(9.912669821848008, 24.263405827228635),
+            Offset(9.927760918072412, 23.52461229394332),
+            Offset(9.95774683780533, 22.966059779854906),
+            Offset(9.993956644475434, 22.556166293104994),
+            Offset(10.026198124175803, 22.27177334090344),
+            Offset(10.04923690661602, 22.094713837168957),
+            Offset(10.060961130015452, 22.01075350168465),
+            Offset(10.0625, 22.0),
+          ],
+          <Offset>[
+            Offset(42.000000000002004, 26.0),
+            Offset(41.971566877530606, 26.241156836662025),
+            Offset(41.83988612179001, 27.120651079764645),
+            Offset(41.39972981127498, 29.024878356209307),
+            Offset(39.86903742895662, 32.72775177683313),
+            Offset(32.84037057959128, 39.806576100386096),
+            Offset(21.712188487577507, 41.94303848547116),
+            Offset(14.067429657339456, 38.35447552412971),
+            Offset(11.554665508114638, 33.59827428006152),
+            Offset(10.553782277809306, 30.424424398573166),
+            Offset(10.11629670607785, 28.173583872615687),
+            Offset(9.950186111889643, 26.50031664595871),
+            Offset(9.907575779455914, 25.23205850377301),
+            Offset(9.912669821848008, 24.263405827228635),
+            Offset(9.927760918072412, 23.52461229394332),
+            Offset(9.95774683780533, 22.966059779854906),
+            Offset(9.993956644475434, 22.556166293104994),
+            Offset(10.026198124175803, 22.27177334090344),
+            Offset(10.04923690661602, 22.094713837168957),
+            Offset(10.060961130015452, 22.01075350168465),
+            Offset(10.0625, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.000000000002004, 26.0),
+            Offset(41.971566877530606, 26.241156836662025),
+            Offset(41.83988612179001, 27.120651079764645),
+            Offset(41.39972981127498, 29.024878356209307),
+            Offset(39.86903742895662, 32.72775177683313),
+            Offset(32.84037057959128, 39.806576100386096),
+            Offset(21.712188487577507, 41.94303848547116),
+            Offset(14.067429657339456, 38.35447552412971),
+            Offset(11.554665508114638, 33.59827428006152),
+            Offset(10.553782277809306, 30.424424398573166),
+            Offset(10.11629670607785, 28.173583872615687),
+            Offset(9.950186111889643, 26.50031664595871),
+            Offset(9.907575779455914, 25.23205850377301),
+            Offset(9.912669821848008, 24.263405827228635),
+            Offset(9.927760918072412, 23.52461229394332),
+            Offset(9.95774683780533, 22.966059779854906),
+            Offset(9.993956644475434, 22.556166293104994),
+            Offset(10.026198124175803, 22.27177334090344),
+            Offset(10.04923690661602, 22.094713837168957),
+            Offset(10.060961130015452, 22.01075350168465),
+            Offset(10.0625, 22.0),
+          ],
+          <Offset>[
+            Offset(42.000000000002004, 22.0),
+            Offset(42.025197234906926, 22.241516379724967),
+            Offset(42.08978791681666, 23.128465075475457),
+            Offset(42.0783682355337, 25.08286746211989),
+            Offset(41.397835686580194, 29.03143254177143),
+            Offset(36.094488002116805, 37.48048070242499),
+            Offset(25.789219352940048, 42.00837688265331),
+            Offset(19.59049856191341, 41.30378926060276),
+            Offset(20.224936460436354, 42.28321450135585),
+            Offset(18.827987907130577, 43.04433671564179),
+            Offset(16.997529418949235, 43.161414977908365),
+            Offset(15.331601790632021, 42.918949572968955),
+            Offset(13.928660013420258, 42.478559653106245),
+            Offset(12.785040072364048, 41.93598444668365),
+            Offset(11.87472277556043, 41.35613622700879),
+            Offset(11.194931733517697, 40.86084337127677),
+            Offset(10.707701666486946, 40.479460473962305),
+            Offset(10.375481162000769, 40.20587235951845),
+            Offset(10.171073813641986, 40.03180005589708),
+            Offset(10.074800072408165, 39.948248163249666),
+            Offset(10.062500000000002, 39.9375),
+          ],
+          <Offset>[
+            Offset(42.000000000002004, 22.0),
+            Offset(42.025197234906926, 22.241516379724967),
+            Offset(42.08978791681666, 23.128465075475457),
+            Offset(42.0783682355337, 25.08286746211989),
+            Offset(41.397835686580194, 29.03143254177143),
+            Offset(36.094488002116805, 37.48048070242499),
+            Offset(25.789219352940048, 42.00837688265331),
+            Offset(19.59049856191341, 41.30378926060276),
+            Offset(20.224936460436354, 42.28321450135585),
+            Offset(18.827987907130577, 43.04433671564179),
+            Offset(16.997529418949235, 43.161414977908365),
+            Offset(15.331601790632021, 42.918949572968955),
+            Offset(13.928660013420258, 42.478559653106245),
+            Offset(12.785040072364048, 41.93598444668365),
+            Offset(11.87472277556043, 41.35613622700879),
+            Offset(11.194931733517697, 40.86084337127677),
+            Offset(10.707701666486946, 40.479460473962305),
+            Offset(10.375481162000769, 40.20587235951845),
+            Offset(10.171073813641986, 40.03180005589708),
+            Offset(10.074800072408165, 39.948248163249666),
+            Offset(10.062500000000002, 39.9375),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.000000000002004, 22.0),
+            Offset(42.025197234906926, 22.241516379724967),
+            Offset(42.08978791681666, 23.128465075475457),
+            Offset(42.0783682355337, 25.08286746211989),
+            Offset(41.397835686580194, 29.03143254177143),
+            Offset(36.094488002116805, 37.48048070242499),
+            Offset(25.789219352940048, 42.00837688265331),
+            Offset(19.59049856191341, 41.30378926060276),
+            Offset(20.224936460436354, 42.28321450135585),
+            Offset(18.827987907130577, 43.04433671564179),
+            Offset(16.997529418949235, 43.161414977908365),
+            Offset(15.331601790632021, 42.918949572968955),
+            Offset(13.928660013420258, 42.478559653106245),
+            Offset(12.785040072364048, 41.93598444668365),
+            Offset(11.87472277556043, 41.35613622700879),
+            Offset(11.194931733517697, 40.86084337127677),
+            Offset(10.707701666486946, 40.479460473962305),
+            Offset(10.375481162000769, 40.20587235951845),
+            Offset(10.171073813641986, 40.03180005589708),
+            Offset(10.074800072408165, 39.948248163249666),
+            Offset(10.062500000000002, 39.9375),
+          ],
+          <Offset>[
+            Offset(24.946426391602, 22.0),
+            Offset(24.97315650002924, 22.01286906793447),
+            Offset(25.06952844617418, 22.06303541138589),
+            Offset(25.272024998666566, 22.189564881723705),
+            Offset(25.64134770835411, 22.51454646253731),
+            Offset(26.515147324551563, 24.079353894601017),
+            Offset(26.00733165465548, 28.39845339876247),
+            Offset(25.738815604552915, 29.790067185625176),
+            Offset(28.372939508552708, 34.1489738102917),
+            Offset(27.852720130300924, 37.12729955342019),
+            Offset(26.47865456242841, 38.80842834269753),
+            Offset(25.05405441770695, 39.73229211885786),
+            Offset(23.785295519688383, 40.180449370272434),
+            Offset(22.72555198004472, 40.32032714542882),
+            Offset(21.87777278679228, 40.26393811183259),
+            Offset(21.233468870168988, 40.166812820023495),
+            Offset(20.762232548431296, 40.079066925029025),
+            Offset(20.436073294394554, 40.00993309439713),
+            Offset(20.23334169244069, 39.96345252268739),
+            Offset(20.137297077676344, 39.940484854102536),
+            Offset(20.125, 39.9375),
+          ],
+          <Offset>[
+            Offset(24.946426391602, 22.0),
+            Offset(24.97315650002924, 22.01286906793447),
+            Offset(25.06952844617418, 22.06303541138589),
+            Offset(25.272024998666566, 22.189564881723705),
+            Offset(25.64134770835411, 22.51454646253731),
+            Offset(26.515147324551563, 24.079353894601017),
+            Offset(26.00733165465548, 28.39845339876247),
+            Offset(25.738815604552915, 29.790067185625176),
+            Offset(28.372939508552708, 34.1489738102917),
+            Offset(27.852720130300924, 37.12729955342019),
+            Offset(26.47865456242841, 38.80842834269753),
+            Offset(25.05405441770695, 39.73229211885786),
+            Offset(23.785295519688383, 40.180449370272434),
+            Offset(22.72555198004472, 40.32032714542882),
+            Offset(21.87777278679228, 40.26393811183259),
+            Offset(21.233468870168988, 40.166812820023495),
+            Offset(20.762232548431296, 40.079066925029025),
+            Offset(20.436073294394554, 40.00993309439713),
+            Offset(20.23334169244069, 39.96345252268739),
+            Offset(20.137297077676344, 39.940484854102536),
+            Offset(20.125, 39.9375),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(24.946426391602, 22.0),
+            Offset(24.97315650002924, 22.01286906793447),
+            Offset(25.06952844617418, 22.06303541138589),
+            Offset(25.272024998666566, 22.189564881723705),
+            Offset(25.64134770835411, 22.51454646253731),
+            Offset(26.515147324551563, 24.079353894601017),
+            Offset(26.00733165465548, 28.39845339876247),
+            Offset(25.738815604552915, 29.790067185625176),
+            Offset(28.372939508552708, 34.1489738102917),
+            Offset(27.852720130300924, 37.12729955342019),
+            Offset(26.47865456242841, 38.80842834269753),
+            Offset(25.05405441770695, 39.73229211885786),
+            Offset(23.785295519688383, 40.180449370272434),
+            Offset(22.72555198004472, 40.32032714542882),
+            Offset(21.87777278679228, 40.26393811183259),
+            Offset(21.233468870168988, 40.166812820023495),
+            Offset(20.762232548431296, 40.079066925029025),
+            Offset(20.436073294394554, 40.00993309439713),
+            Offset(20.23334169244069, 39.96345252268739),
+            Offset(20.137297077676344, 39.940484854102536),
+            Offset(20.125, 39.9375),
+          ],
+          <Offset>[
+            Offset(24.946426391602, 26.0),
+            Offset(24.919526142652916, 26.01250952487153),
+            Offset(24.81962665114753, 26.055221415675078),
+            Offset(24.59338657440784, 26.131575775813122),
+            Offset(24.112549450730533, 26.21086569759901),
+            Offset(23.26102990202604, 26.405449292562118),
+            Offset(21.93030078929294, 28.333115001580325),
+            Offset(20.21574669997896, 26.840753449152132),
+            Offset(19.702668556230993, 25.46403358899737),
+            Offset(19.57851450097965, 24.507387236351565),
+            Offset(19.597421849557026, 23.820597237404847),
+            Offset(19.672638738964572, 23.313659191847616),
+            Offset(19.76421128572404, 22.933948220939197),
+            Offset(19.853181729528682, 22.647748525973807),
+            Offset(19.930810929304265, 22.432414178767115),
+            Offset(19.99628397445662, 22.272029228601628),
+            Offset(20.048487526419784, 22.155772744171706),
+            Offset(20.086790256569586, 22.07583407578212),
+            Offset(20.111504785414724, 22.026366303959268),
+            Offset(20.12345813528363, 22.002990192537517),
+            Offset(20.125, 22.0),
+          ],
+          <Offset>[
+            Offset(24.946426391602, 26.0),
+            Offset(24.919526142652916, 26.01250952487153),
+            Offset(24.81962665114753, 26.055221415675078),
+            Offset(24.59338657440784, 26.131575775813122),
+            Offset(24.112549450730533, 26.21086569759901),
+            Offset(23.26102990202604, 26.405449292562118),
+            Offset(21.93030078929294, 28.333115001580325),
+            Offset(20.21574669997896, 26.840753449152132),
+            Offset(19.702668556230993, 25.46403358899737),
+            Offset(19.57851450097965, 24.507387236351565),
+            Offset(19.597421849557026, 23.820597237404847),
+            Offset(19.672638738964572, 23.313659191847616),
+            Offset(19.76421128572404, 22.933948220939197),
+            Offset(19.853181729528682, 22.647748525973807),
+            Offset(19.930810929304265, 22.432414178767115),
+            Offset(19.99628397445662, 22.272029228601628),
+            Offset(20.048487526419784, 22.155772744171706),
+            Offset(20.086790256569586, 22.07583407578212),
+            Offset(20.111504785414724, 22.026366303959268),
+            Offset(20.12345813528363, 22.002990192537517),
+            Offset(20.125, 22.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(23.053573608398, 26.0),
+            Offset(23.026843499970756, 25.98713093206553),
+            Offset(22.930471553825814, 25.93696458861411),
+            Offset(22.727975001333434, 25.810435118276295),
+            Offset(22.35865229164589, 25.48545353746269),
+            Offset(21.484852675448433, 23.920646105398983),
+            Offset(22.070212775572372, 19.602789326384535),
+            Offset(24.25581739693188, 19.275065010286717),
+            Offset(25.471296020158917, 19.70514960582061),
+            Offset(26.22827021130133, 20.147495919393876),
+            Offset(26.733592485966874, 20.544229197031424),
+            Offset(27.081522869281894, 20.885303225908167),
+            Offset(27.327534182039592, 21.170532082708068),
+            Offset(27.505108572109584, 21.404060918464253),
+            Offset(27.63502336031513, 21.591218114904695),
+            Offset(27.72782810454829, 21.737496381673637),
+            Offset(27.7923498205881, 21.847395103999485),
+            Offset(27.83532084300952, 21.924924331216626),
+            Offset(27.861326008837327, 21.973725967822613),
+            Offset(27.873455828782106, 21.997010997914884),
+            Offset(27.875, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(23.053573608398, 26.0),
+            Offset(23.026843499970756, 25.98713093206553),
+            Offset(22.930471553825814, 25.93696458861411),
+            Offset(22.727975001333434, 25.810435118276295),
+            Offset(22.35865229164589, 25.48545353746269),
+            Offset(21.484852675448433, 23.920646105398983),
+            Offset(22.070212775572372, 19.602789326384535),
+            Offset(24.25581739693188, 19.275065010286717),
+            Offset(25.471296020158917, 19.70514960582061),
+            Offset(26.22827021130133, 20.147495919393876),
+            Offset(26.733592485966874, 20.544229197031424),
+            Offset(27.081522869281894, 20.885303225908167),
+            Offset(27.327534182039592, 21.170532082708068),
+            Offset(27.505108572109584, 21.404060918464253),
+            Offset(27.63502336031513, 21.591218114904695),
+            Offset(27.72782810454829, 21.737496381673637),
+            Offset(27.7923498205881, 21.847395103999485),
+            Offset(27.83532084300952, 21.924924331216626),
+            Offset(27.861326008837327, 21.973725967822613),
+            Offset(27.873455828782106, 21.997010997914884),
+            Offset(27.875, 22.0),
+          ],
+          <Offset>[
+            Offset(5.999999999998, 26.0),
+            Offset(5.974802765093067, 25.758483620275033),
+            Offset(5.910212083183335, 24.871534924524543),
+            Offset(5.921631764466294, 22.91713253788011),
+            Offset(6.602164313419802, 18.96856745822857),
+            Offset(11.905511997883192, 10.519519297575007),
+            Offset(22.288325077287805, 5.992865842493696),
+            Offset(30.404134439571386, 7.761342935309134),
+            Offset(33.61929906827527, 11.570908914756462),
+            Offset(35.253002434471675, 14.230458757172274),
+            Offset(36.21471762944605, 16.191242561820587),
+            Offset(36.80397549635682, 17.698645771797075),
+            Offset(37.18416968830772, 18.872421799874253),
+            Offset(37.44562047979026, 19.788403617209426),
+            Offset(37.63807337154698, 20.499019999728493),
+            Offset(37.76636524119958, 21.043465830420356),
+            Offset(37.84688070253245, 21.447001555066198),
+            Offset(37.895912975403306, 21.728985066095305),
+            Offset(37.92359388763603, 21.905378434612928),
+            Offset(37.93595283405028, 21.98924768876775),
+            Offset(37.9375, 22.0),
+          ],
+          <Offset>[
+            Offset(5.999999999998, 26.0),
+            Offset(5.974802765093067, 25.758483620275033),
+            Offset(5.910212083183335, 24.871534924524543),
+            Offset(5.921631764466294, 22.91713253788011),
+            Offset(6.602164313419802, 18.96856745822857),
+            Offset(11.905511997883192, 10.519519297575007),
+            Offset(22.288325077287805, 5.992865842493696),
+            Offset(30.404134439571386, 7.761342935309134),
+            Offset(33.61929906827527, 11.570908914756462),
+            Offset(35.253002434471675, 14.230458757172274),
+            Offset(36.21471762944605, 16.191242561820587),
+            Offset(36.80397549635682, 17.698645771797075),
+            Offset(37.18416968830772, 18.872421799874253),
+            Offset(37.44562047979026, 19.788403617209426),
+            Offset(37.63807337154698, 20.499019999728493),
+            Offset(37.76636524119958, 21.043465830420356),
+            Offset(37.84688070253245, 21.447001555066198),
+            Offset(37.895912975403306, 21.728985066095305),
+            Offset(37.92359388763603, 21.905378434612928),
+            Offset(37.93595283405028, 21.98924768876775),
+            Offset(37.9375, 22.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(5.999999999998, 26.0),
+            Offset(5.974802765093067, 25.758483620275033),
+            Offset(5.910212083183335, 24.871534924524543),
+            Offset(5.921631764466294, 22.91713253788011),
+            Offset(6.602164313419802, 18.96856745822857),
+            Offset(11.905511997883192, 10.519519297575007),
+            Offset(22.288325077287805, 5.992865842493696),
+            Offset(30.404134439571386, 7.761342935309134),
+            Offset(33.61929906827527, 11.570908914756462),
+            Offset(35.253002434471675, 14.230458757172274),
+            Offset(36.21471762944605, 16.191242561820587),
+            Offset(36.80397549635682, 17.698645771797075),
+            Offset(37.18416968830772, 18.872421799874253),
+            Offset(37.44562047979026, 19.788403617209426),
+            Offset(37.63807337154698, 20.499019999728493),
+            Offset(37.76636524119958, 21.043465830420356),
+            Offset(37.84688070253245, 21.447001555066198),
+            Offset(37.895912975403306, 21.728985066095305),
+            Offset(37.92359388763603, 21.905378434612928),
+            Offset(37.93595283405028, 21.98924768876775),
+            Offset(37.9375, 22.0),
+          ],
+          <Offset>[
+            Offset(5.999999999998, 22.0),
+            Offset(6.028433122469394, 21.758843163337975),
+            Offset(6.160113878209987, 20.879348920235355),
+            Offset(6.600270188725016, 18.975121643790693),
+            Offset(8.13096257104338, 15.27224822316687),
+            Offset(15.159629420408717, 8.193423899613903),
+            Offset(26.365355942650346, 6.058204239675842),
+            Offset(35.92720334414534, 10.710656671782182),
+            Offset(42.28957002059698, 20.25584913605079),
+            Offset(43.52720806379294, 26.850371074240893),
+            Offset(43.09595034231744, 31.17907366711327),
+            Offset(42.1853911750992, 34.117278698807326),
+            Offset(41.205253922272064, 36.11892294920749),
+            Offset(40.3179907303063, 37.46098223666444),
+            Offset(39.585035229035, 38.330543932793965),
+            Offset(39.00355013691195, 38.93824942184222),
+            Offset(38.56062572454396, 39.37029573592351),
+            Offset(38.24519601322827, 39.66308408471031),
+            Offset(38.045430794661996, 39.842464653341054),
+            Offset(37.949791776443, 39.926742350332766),
+            Offset(37.9375, 39.9375),
+          ],
+          <Offset>[
+            Offset(5.999999999998, 22.0),
+            Offset(6.028433122469394, 21.758843163337975),
+            Offset(6.160113878209987, 20.879348920235355),
+            Offset(6.600270188725016, 18.975121643790693),
+            Offset(8.13096257104338, 15.27224822316687),
+            Offset(15.159629420408717, 8.193423899613903),
+            Offset(26.365355942650346, 6.058204239675842),
+            Offset(35.92720334414534, 10.710656671782182),
+            Offset(42.28957002059698, 20.25584913605079),
+            Offset(43.52720806379294, 26.850371074240893),
+            Offset(43.09595034231744, 31.17907366711327),
+            Offset(42.1853911750992, 34.117278698807326),
+            Offset(41.205253922272064, 36.11892294920749),
+            Offset(40.3179907303063, 37.46098223666444),
+            Offset(39.585035229035, 38.330543932793965),
+            Offset(39.00355013691195, 38.93824942184222),
+            Offset(38.56062572454396, 39.37029573592351),
+            Offset(38.24519601322827, 39.66308408471031),
+            Offset(38.045430794661996, 39.842464653341054),
+            Offset(37.949791776443, 39.926742350332766),
+            Offset(37.9375, 39.9375),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(5.999999999998, 22.0),
+            Offset(6.028433122469394, 21.758843163337975),
+            Offset(6.160113878209987, 20.879348920235355),
+            Offset(6.600270188725016, 18.975121643790693),
+            Offset(8.13096257104338, 15.27224822316687),
+            Offset(15.159629420408717, 8.193423899613903),
+            Offset(26.365355942650346, 6.058204239675842),
+            Offset(35.92720334414534, 10.710656671782182),
+            Offset(42.28957002059698, 20.25584913605079),
+            Offset(43.52720806379294, 26.850371074240893),
+            Offset(43.09595034231744, 31.17907366711327),
+            Offset(42.1853911750992, 34.117278698807326),
+            Offset(41.205253922272064, 36.11892294920749),
+            Offset(40.3179907303063, 37.46098223666444),
+            Offset(39.585035229035, 38.330543932793965),
+            Offset(39.00355013691195, 38.93824942184222),
+            Offset(38.56062572454396, 39.37029573592351),
+            Offset(38.24519601322827, 39.66308408471031),
+            Offset(38.045430794661996, 39.842464653341054),
+            Offset(37.949791776443, 39.926742350332766),
+            Offset(37.9375, 39.9375),
+          ],
+          <Offset>[
+            Offset(23.053573608398, 22.0),
+            Offset(23.08047385734708, 21.98749047512847),
+            Offset(23.180373348852466, 21.944778584324922),
+            Offset(23.40661342559216, 21.868424224186878),
+            Offset(23.887450549269467, 21.78913430240099),
+            Offset(24.73897009797396, 21.594550707437882),
+            Offset(26.147243640934914, 19.66812772356668),
+            Offset(29.778886301505835, 22.224378746759765),
+            Offset(34.14156697248063, 28.39008982711494),
+            Offset(34.5024758406226, 32.767408236462494),
+            Offset(33.614825198838254, 35.532060302324105),
+            Offset(32.46293854802427, 37.30393615291841),
+            Offset(31.348618416003937, 38.417033232041305),
+            Offset(30.377478822625626, 39.07663953791927),
+            Offset(29.58198521780315, 39.42274204797017),
+            Offset(28.96501300026066, 39.6322799730955),
+            Offset(28.506094842599616, 39.7706892848568),
+            Offset(28.184603880834484, 39.85902334983163),
+            Offset(27.983162915863293, 39.910812186550736),
+            Offset(27.88729477117482, 39.934505659479896),
+            Offset(27.875, 39.9375),
+          ],
+          <Offset>[
+            Offset(23.053573608398, 22.0),
+            Offset(23.08047385734708, 21.98749047512847),
+            Offset(23.180373348852466, 21.944778584324922),
+            Offset(23.40661342559216, 21.868424224186878),
+            Offset(23.887450549269467, 21.78913430240099),
+            Offset(24.73897009797396, 21.594550707437882),
+            Offset(26.147243640934914, 19.66812772356668),
+            Offset(29.778886301505835, 22.224378746759765),
+            Offset(34.14156697248063, 28.39008982711494),
+            Offset(34.5024758406226, 32.767408236462494),
+            Offset(33.614825198838254, 35.532060302324105),
+            Offset(32.46293854802427, 37.30393615291841),
+            Offset(31.348618416003937, 38.417033232041305),
+            Offset(30.377478822625626, 39.07663953791927),
+            Offset(29.58198521780315, 39.42274204797017),
+            Offset(28.96501300026066, 39.6322799730955),
+            Offset(28.506094842599616, 39.7706892848568),
+            Offset(28.184603880834484, 39.85902334983163),
+            Offset(27.983162915863293, 39.910812186550736),
+            Offset(27.88729477117482, 39.934505659479896),
+            Offset(27.875, 39.9375),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(23.053573608398, 22.0),
+            Offset(23.08047385734708, 21.98749047512847),
+            Offset(23.180373348852466, 21.944778584324922),
+            Offset(23.40661342559216, 21.868424224186878),
+            Offset(23.887450549269467, 21.78913430240099),
+            Offset(24.73897009797396, 21.594550707437882),
+            Offset(26.147243640934914, 19.66812772356668),
+            Offset(29.778886301505835, 22.224378746759765),
+            Offset(34.14156697248063, 28.39008982711494),
+            Offset(34.5024758406226, 32.767408236462494),
+            Offset(33.614825198838254, 35.532060302324105),
+            Offset(32.46293854802427, 37.30393615291841),
+            Offset(31.348618416003937, 38.417033232041305),
+            Offset(30.377478822625626, 39.07663953791927),
+            Offset(29.58198521780315, 39.42274204797017),
+            Offset(28.96501300026066, 39.6322799730955),
+            Offset(28.506094842599616, 39.7706892848568),
+            Offset(28.184603880834484, 39.85902334983163),
+            Offset(27.983162915863293, 39.910812186550736),
+            Offset(27.88729477117482, 39.934505659479896),
+            Offset(27.875, 39.9375),
+          ],
+          <Offset>[
+            Offset(23.053573608398, 26.0),
+            Offset(23.026843499970756, 25.98713093206553),
+            Offset(22.930471553825818, 25.93696458861411),
+            Offset(22.727975001333434, 25.810435118276295),
+            Offset(22.35865229164589, 25.48545353746269),
+            Offset(21.484852675448437, 23.920646105398983),
+            Offset(22.070212775572372, 19.602789326384535),
+            Offset(24.25581739693188, 19.275065010286717),
+            Offset(25.471296020158917, 19.70514960582061),
+            Offset(26.22827021130133, 20.147495919393876),
+            Offset(26.733592485966874, 20.544229197031424),
+            Offset(27.081522869281894, 20.885303225908167),
+            Offset(27.327534182039592, 21.170532082708068),
+            Offset(27.505108572109584, 21.404060918464253),
+            Offset(27.63502336031513, 21.591218114904695),
+            Offset(27.72782810454829, 21.737496381673637),
+            Offset(27.7923498205881, 21.847395103999485),
+            Offset(27.83532084300952, 21.924924331216626),
+            Offset(27.861326008837327, 21.973725967822613),
+            Offset(27.873455828782106, 21.997010997914884),
+            Offset(27.875, 22.0),
+          ],
+          <Offset>[
+            Offset(23.053573608398, 26.0),
+            Offset(23.026843499970756, 25.98713093206553),
+            Offset(22.930471553825818, 25.93696458861411),
+            Offset(22.727975001333434, 25.810435118276295),
+            Offset(22.35865229164589, 25.48545353746269),
+            Offset(21.484852675448437, 23.920646105398983),
+            Offset(22.070212775572372, 19.602789326384535),
+            Offset(24.25581739693188, 19.275065010286717),
+            Offset(25.471296020158917, 19.70514960582061),
+            Offset(26.22827021130133, 20.147495919393876),
+            Offset(26.733592485966874, 20.544229197031424),
+            Offset(27.081522869281894, 20.885303225908167),
+            Offset(27.327534182039592, 21.170532082708068),
+            Offset(27.505108572109584, 21.404060918464253),
+            Offset(27.63502336031513, 21.591218114904695),
+            Offset(27.72782810454829, 21.737496381673637),
+            Offset(27.7923498205881, 21.847395103999485),
+            Offset(27.83532084300952, 21.924924331216626),
+            Offset(27.861326008837327, 21.973725967822613),
+            Offset(27.873455828782106, 21.997010997914884),
+            Offset(27.875, 22.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(6.0, 36.0),
+            Offset(5.840726871654255, 35.757584762617704),
+            Offset(5.285457595618709, 34.85199993524763),
+            Offset(4.227525983784449, 32.77342417727839),
+            Offset(3.5264717596110557, 28.827211071001663),
+            Offset(6.517886449942477, 24.591045888519158),
+            Offset(7.520599639567256, 20.429019310226224),
+            Offset(9.337441251165988, 14.915299719515282),
+            Offset(11.67955424234765, 11.532962037598704),
+            Offset(14.02683296676815, 9.419579743902961),
+            Offset(16.229456854531474, 8.105002459199046),
+            Offset(18.082743777434697, 7.3261172370029986),
+            Offset(19.565347960385694, 6.872065247520328),
+            Offset(20.738317895220952, 6.610528546128252),
+            Offset(21.653024624707548, 6.463637943249828),
+            Offset(22.351393858266118, 6.3842574556990765),
+            Offset(22.866855439990758, 6.343605379246371),
+            Offset(23.22597898795316, 6.324220552672472),
+            Offset(23.450183580341104, 6.315826581309215),
+            Offset(23.556666562733803, 6.312836771798083),
+            Offset(23.5703125, 6.312499999999998),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 36.0),
+            Offset(5.840726871654255, 35.757584762617704),
+            Offset(5.285457595618709, 34.85199993524763),
+            Offset(4.227525983784449, 32.77342417727839),
+            Offset(3.5264717596110557, 28.827211071001663),
+            Offset(6.517886449942477, 24.591045888519158),
+            Offset(7.520599639567256, 20.429019310226224),
+            Offset(9.337441251165988, 14.915299719515282),
+            Offset(11.67955424234765, 11.532962037598704),
+            Offset(14.02683296676815, 9.419579743902961),
+            Offset(16.229456854531474, 8.105002459199046),
+            Offset(18.082743777434697, 7.3261172370029986),
+            Offset(19.565347960385694, 6.872065247520328),
+            Offset(20.738317895220952, 6.610528546128252),
+            Offset(21.653024624707548, 6.463637943249828),
+            Offset(22.351393858266118, 6.3842574556990765),
+            Offset(22.866855439990758, 6.343605379246371),
+            Offset(23.22597898795316, 6.324220552672472),
+            Offset(23.450183580341104, 6.315826581309215),
+            Offset(23.556666562733803, 6.312836771798083),
+            Offset(23.5703125, 6.312499999999998),
+          ],
+          <Offset>[
+            Offset(42.0, 36.0),
+            Offset(41.837490984087786, 36.240257979004646),
+            Offset(41.215131634221386, 37.10111609048749),
+            Offset(39.70046993008684, 38.880282689423),
+            Offset(35.11680046930425, 41.89297634122701),
+            Offset(17.977223307522138, 40.62221458070813),
+            Offset(7.4051530249760695, 27.632737664041155),
+            Offset(7.974125419949719, 17.46832999953845),
+            Offset(11.043336185217218, 12.16810549276672),
+            Offset(13.987438256258187, 9.445408761353912),
+            Offset(16.335956080085097, 8.056106394810097),
+            Offset(18.194102300352007, 7.289618065369594),
+            Offset(19.679474513989774, 6.845456228021216),
+            Offset(20.85398754040343, 6.5917284573090615),
+            Offset(21.76951977235388, 6.450918244703521),
+            Offset(22.468302287652584, 6.376174801763673),
+            Offset(22.98395013193887, 6.338942410896992),
+            Offset(23.343144268998117, 6.321938651292953),
+            Offset(23.56736837706624, 6.315030608484569),
+            Offset(23.673854027857267, 6.312746360589072),
+            Offset(23.6875, 6.312499999999998),
+          ],
+          <Offset>[
+            Offset(42.0, 36.0),
+            Offset(41.837490984087786, 36.240257979004646),
+            Offset(41.215131634221386, 37.10111609048749),
+            Offset(39.70046993008684, 38.880282689423),
+            Offset(35.11680046930425, 41.89297634122701),
+            Offset(17.977223307522138, 40.62221458070813),
+            Offset(7.4051530249760695, 27.632737664041155),
+            Offset(7.974125419949719, 17.46832999953845),
+            Offset(11.043336185217218, 12.16810549276672),
+            Offset(13.987438256258187, 9.445408761353912),
+            Offset(16.335956080085097, 8.056106394810097),
+            Offset(18.194102300352007, 7.289618065369594),
+            Offset(19.679474513989774, 6.845456228021216),
+            Offset(20.85398754040343, 6.5917284573090615),
+            Offset(21.76951977235388, 6.450918244703521),
+            Offset(22.468302287652584, 6.376174801763673),
+            Offset(22.98395013193887, 6.338942410896992),
+            Offset(23.343144268998117, 6.321938651292953),
+            Offset(23.56736837706624, 6.315030608484569),
+            Offset(23.673854027857267, 6.312746360589072),
+            Offset(23.6875, 6.312499999999998),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 36.0),
+            Offset(41.837490984087786, 36.240257979004646),
+            Offset(41.215131634221386, 37.10111609048749),
+            Offset(39.70046993008684, 38.880282689423),
+            Offset(35.11680046930425, 41.89297634122701),
+            Offset(17.977223307522138, 40.62221458070813),
+            Offset(7.4051530249760695, 27.632737664041155),
+            Offset(7.974125419949719, 17.46832999953845),
+            Offset(11.043336185217218, 12.16810549276672),
+            Offset(13.987438256258187, 9.445408761353912),
+            Offset(16.335956080085097, 8.056106394810097),
+            Offset(18.194102300352007, 7.289618065369594),
+            Offset(19.679474513989774, 6.845456228021216),
+            Offset(20.85398754040343, 6.5917284573090615),
+            Offset(21.76951977235388, 6.450918244703521),
+            Offset(22.468302287652584, 6.376174801763673),
+            Offset(22.98395013193887, 6.338942410896992),
+            Offset(23.343144268998117, 6.321938651292953),
+            Offset(23.56736837706624, 6.315030608484569),
+            Offset(23.673854027857267, 6.312746360589072),
+            Offset(23.6875, 6.312499999999998),
+          ],
+          <Offset>[
+            Offset(42.0, 32.0),
+            Offset(41.891121341464114, 32.24061752206759),
+            Offset(41.46503342924804, 33.1089300861983),
+            Offset(40.382213841518244, 34.93682280811562),
+            Offset(37.807067599374584, 37.94317621909476),
+            Offset(31.28569346733829, 41.88836406262549),
+            Offset(21.918156356601166, 43.36435943014172),
+            Offset(14.025584375723557, 40.89778062244908),
+            Offset(9.769920092960579, 37.63050924159487),
+            Offset(7.367850012815694, 34.638113152492416),
+            Offset(5.973096817006795, 32.07021795746492),
+            Offset(5.173978263055725, 29.973153422615376),
+            Offset(4.723297068059789, 28.301904267150505),
+            Offset(4.475728989802221, 26.983370203369095),
+            Offset(4.346332932330473, 25.957300958770094),
+            Offset(4.284100454437475, 25.175143122894923),
+            Offset(4.258180500986249, 24.598514092031586),
+            Offset(4.250093592349071, 24.197111996156252),
+            Offset(4.24918203540361, 23.946652278620284),
+            Offset(4.2498612199243055, 23.82773735822788),
+            Offset(4.25, 23.8125),
+          ],
+          <Offset>[
+            Offset(42.0, 32.0),
+            Offset(41.891121341464114, 32.24061752206759),
+            Offset(41.46503342924804, 33.1089300861983),
+            Offset(40.382213841518244, 34.93682280811562),
+            Offset(37.807067599374584, 37.94317621909476),
+            Offset(31.28569346733829, 41.88836406262549),
+            Offset(21.918156356601166, 43.36435943014172),
+            Offset(14.025584375723557, 40.89778062244908),
+            Offset(9.769920092960579, 37.63050924159487),
+            Offset(7.367850012815694, 34.638113152492416),
+            Offset(5.973096817006795, 32.07021795746492),
+            Offset(5.173978263055725, 29.973153422615376),
+            Offset(4.723297068059789, 28.301904267150505),
+            Offset(4.475728989802221, 26.983370203369095),
+            Offset(4.346332932330473, 25.957300958770094),
+            Offset(4.284100454437475, 25.175143122894923),
+            Offset(4.258180500986249, 24.598514092031586),
+            Offset(4.250093592349071, 24.197111996156252),
+            Offset(4.24918203540361, 23.946652278620284),
+            Offset(4.2498612199243055, 23.82773735822788),
+            Offset(4.25, 23.8125),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.0, 32.0),
+            Offset(41.891121341464114, 32.24061752206759),
+            Offset(41.46503342924804, 33.1089300861983),
+            Offset(40.382213841518244, 34.93682280811562),
+            Offset(37.807067599374584, 37.94317621909476),
+            Offset(31.28569346733829, 41.88836406262549),
+            Offset(21.918156356601166, 43.36435943014172),
+            Offset(14.025584375723557, 40.89778062244908),
+            Offset(9.769920092960579, 37.63050924159487),
+            Offset(7.367850012815694, 34.638113152492416),
+            Offset(5.973096817006795, 32.07021795746492),
+            Offset(5.173978263055725, 29.973153422615376),
+            Offset(4.723297068059789, 28.301904267150505),
+            Offset(4.475728989802221, 26.983370203369095),
+            Offset(4.346332932330473, 25.957300958770094),
+            Offset(4.284100454437475, 25.175143122894923),
+            Offset(4.258180500986249, 24.598514092031586),
+            Offset(4.250093592349071, 24.197111996156252),
+            Offset(4.24918203540361, 23.946652278620284),
+            Offset(4.2498612199243055, 23.82773735822788),
+            Offset(4.25, 23.8125),
+          ],
+          <Offset>[
+            Offset(6.0, 32.0),
+            Offset(5.894357229030582, 31.757944305680645),
+            Offset(5.535359390645361, 30.859813930958445),
+            Offset(4.903580651262228, 28.82898486195736),
+            Offset(4.366121086699318, 24.111995114141347),
+            Offset(9.36700394742377, 11.224965859696677),
+            Offset(22.54292545773696, 4.379581429626963),
+            Offset(32.60235573940598, 6.109759600029818),
+            Offset(37.826493474762316, 9.62132472038408),
+            Offset(40.59568298818934, 12.852389468570193),
+            Offset(42.097634124796315, 15.484672916733718),
+            Offset(42.946789236607636, 17.592634404564635),
+            Offset(43.43502405056509, 19.276124853051655),
+            Offset(43.71087263569899, 20.606380075899363),
+            Offset(43.86148701396686, 21.64277921186285),
+            Offset(43.93943970232704, 22.43350690800619),
+            Offset(43.97670000978504, 23.016835227922453),
+            Offset(43.992556922799054, 23.423091048223576),
+            Offset(43.99826508457116, 23.676658296500023),
+            Offset(43.99984938980357, 23.797069876131136),
+            Offset(44.0, 23.812499999999996),
+          ],
+          <Offset>[
+            Offset(6.0, 32.0),
+            Offset(5.894357229030582, 31.757944305680645),
+            Offset(5.535359390645361, 30.859813930958445),
+            Offset(4.903580651262228, 28.82898486195736),
+            Offset(4.366121086699318, 24.111995114141347),
+            Offset(9.36700394742377, 11.224965859696677),
+            Offset(22.54292545773696, 4.379581429626963),
+            Offset(32.60235573940598, 6.109759600029818),
+            Offset(37.826493474762316, 9.62132472038408),
+            Offset(40.59568298818934, 12.852389468570193),
+            Offset(42.097634124796315, 15.484672916733718),
+            Offset(42.946789236607636, 17.592634404564635),
+            Offset(43.43502405056509, 19.276124853051655),
+            Offset(43.71087263569899, 20.606380075899363),
+            Offset(43.86148701396686, 21.64277921186285),
+            Offset(43.93943970232704, 22.43350690800619),
+            Offset(43.97670000978504, 23.016835227922453),
+            Offset(43.992556922799054, 23.423091048223576),
+            Offset(43.99826508457116, 23.676658296500023),
+            Offset(43.99984938980357, 23.797069876131136),
+            Offset(44.0, 23.812499999999996),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(6.0, 32.0),
+            Offset(5.894357229030582, 31.757944305680645),
+            Offset(5.535359390645361, 30.859813930958445),
+            Offset(4.903580651262228, 28.82898486195736),
+            Offset(4.366121086699318, 24.111995114141347),
+            Offset(9.36700394742377, 11.224965859696677),
+            Offset(22.54292545773696, 4.379581429626963),
+            Offset(32.60235573940598, 6.109759600029818),
+            Offset(37.826493474762316, 9.62132472038408),
+            Offset(40.59568298818934, 12.852389468570193),
+            Offset(42.097634124796315, 15.484672916733718),
+            Offset(42.946789236607636, 17.592634404564635),
+            Offset(43.43502405056509, 19.276124853051655),
+            Offset(43.71087263569899, 20.606380075899363),
+            Offset(43.86148701396686, 21.64277921186285),
+            Offset(43.93943970232704, 22.43350690800619),
+            Offset(43.97670000978504, 23.016835227922453),
+            Offset(43.992556922799054, 23.423091048223576),
+            Offset(43.99826508457116, 23.676658296500023),
+            Offset(43.99984938980357, 23.797069876131136),
+            Offset(44.0, 23.812499999999996),
+          ],
+          <Offset>[
+            Offset(6.0, 36.0),
+            Offset(5.840726871654255, 35.757584762617704),
+            Offset(5.285457595618709, 34.85199993524763),
+            Offset(4.227525983824194, 32.773424177285236),
+            Offset(3.526471759568551, 28.827211070984085),
+            Offset(6.517886449901766, 24.591045888462205),
+            Offset(7.520599639566615, 20.429019310266217),
+            Offset(9.337441251161277, 14.915299719524107),
+            Offset(11.679554242336327, 11.53296203761001),
+            Offset(14.02683296675945, 9.419579743908663),
+            Offset(16.229456854531474, 8.105002459199046),
+            Offset(18.082743777434697, 7.3261172370029986),
+            Offset(19.565347960385694, 6.872065247520328),
+            Offset(20.738317895220952, 6.610528546128252),
+            Offset(21.653024624707548, 6.463637943249828),
+            Offset(22.351393858266118, 6.3842574556990765),
+            Offset(22.866855439990758, 6.343605379246371),
+            Offset(23.22597898795316, 6.324220552672472),
+            Offset(23.450183580341104, 6.315826581309215),
+            Offset(23.556666562733803, 6.312836771798083),
+            Offset(23.5703125, 6.312499999999998),
+          ],
+          <Offset>[
+            Offset(6.0, 36.0),
+            Offset(5.840726871654255, 35.757584762617704),
+            Offset(5.285457595618709, 34.85199993524763),
+            Offset(4.227525983824194, 32.773424177285236),
+            Offset(3.526471759568551, 28.827211070984085),
+            Offset(6.517886449901766, 24.591045888462205),
+            Offset(7.520599639566615, 20.429019310266217),
+            Offset(9.337441251161277, 14.915299719524107),
+            Offset(11.679554242336327, 11.53296203761001),
+            Offset(14.02683296675945, 9.419579743908663),
+            Offset(16.229456854531474, 8.105002459199046),
+            Offset(18.082743777434697, 7.3261172370029986),
+            Offset(19.565347960385694, 6.872065247520328),
+            Offset(20.738317895220952, 6.610528546128252),
+            Offset(21.653024624707548, 6.463637943249828),
+            Offset(22.351393858266118, 6.3842574556990765),
+            Offset(22.866855439990758, 6.343605379246371),
+            Offset(23.22597898795316, 6.324220552672472),
+            Offset(23.450183580341104, 6.315826581309215),
+            Offset(23.556666562733803, 6.312836771798083),
+            Offset(23.5703125, 6.312499999999998),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+  ],
+);
diff --git a/lib/src/material/animated_icons/data/pause_play.g.dart b/lib/src/material/animated_icons/data/pause_play.g.dart
new file mode 100644
index 0000000..b9d117e
--- /dev/null
+++ b/lib/src/material/animated_icons/data/pause_play.g.dart
@@ -0,0 +1,816 @@
+// Copyright 2014 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.
+
+// AUTOGENERATED FILE DO NOT EDIT!
+// This file was generated by vitool.
+part of material_animated_icons;
+const _AnimatedIconData _$pause_play = _AnimatedIconData(
+  Size(48.0, 48.0),
+  <_PathFrames>[
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(12.0, 38.0),
+            Offset(11.702518567871357, 37.66537647364598),
+            Offset(10.658516625352865, 36.33928535710834),
+            Offset(8.824010134196683, 33.09051115695809),
+            Offset(7.183618981881198, 27.281162329416798),
+            Offset(7.4961448079806345, 21.476581828368367),
+            Offset(9.111297618374987, 17.041890063132637),
+            Offset(10.944617318451954, 14.273857051215758),
+            Offset(12.539001321903285, 12.570685504133255),
+            Offset(13.794958189315498, 11.511449771679523),
+            Offset(14.725226220617056, 10.84845238497297),
+            Offset(15.369630177419687, 10.439047154098617),
+            Offset(15.770227104208459, 10.200145460041224),
+            Offset(15.977727112935135, 10.078681424879498),
+            Offset(16.046646118566024, 10.039192889334426),
+            Offset(16.046875, 10.039062500000002),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.0, 38.0),
+            Offset(11.702518567871357, 37.66537647364598),
+            Offset(10.658516625352865, 36.33928535710834),
+            Offset(8.824010134196683, 33.09051115695809),
+            Offset(7.183618981881198, 27.281162329416798),
+            Offset(7.4961448079806345, 21.476581828368367),
+            Offset(9.111297618374987, 17.041890063132637),
+            Offset(10.944617318451954, 14.273857051215758),
+            Offset(12.539001321903285, 12.570685504133255),
+            Offset(13.794958189315498, 11.511449771679523),
+            Offset(14.725226220617056, 10.84845238497297),
+            Offset(15.369630177419687, 10.439047154098617),
+            Offset(15.770227104208459, 10.200145460041224),
+            Offset(15.977727112935135, 10.078681424879498),
+            Offset(16.046646118566024, 10.039192889334426),
+            Offset(16.046875, 10.039062500000002),
+          ],
+          <Offset>[
+            Offset(20.0, 38.0),
+            Offset(19.80052916638957, 37.82094858440343),
+            Offset(19.11164196247534, 37.113338636628065),
+            Offset(17.966685507002932, 35.41245490387412),
+            Offset(16.388368582775627, 32.64921363804367),
+            Offset(15.647464737031132, 29.978925368550197),
+            Offset(15.429304294338682, 27.901717576585412),
+            Offset(15.521061545777922, 26.541981731286498),
+            Offset(15.686957461206134, 25.650190745693187),
+            Offset(15.84005263487414, 25.05047803281999),
+            Offset(15.953666440217397, 24.63834819764938),
+            Offset(16.022300799535966, 24.35280207511846),
+            Offset(16.047681038809458, 24.15758118207667),
+            Offset(16.04697282889272, 24.039447195772148),
+            Offset(16.046875001068816, 24.00013038745822),
+            Offset(16.046875, 24.0),
+          ],
+          <Offset>[
+            Offset(20.0, 38.0),
+            Offset(19.80052916638957, 37.82094858440343),
+            Offset(19.11164196247534, 37.113338636628065),
+            Offset(17.966685507002932, 35.41245490387412),
+            Offset(16.388368582775627, 32.64921363804367),
+            Offset(15.647464737031132, 29.978925368550197),
+            Offset(15.429304294338682, 27.901717576585412),
+            Offset(15.521061545777922, 26.541981731286498),
+            Offset(15.686957461206134, 25.650190745693187),
+            Offset(15.84005263487414, 25.05047803281999),
+            Offset(15.953666440217397, 24.63834819764938),
+            Offset(16.022300799535966, 24.35280207511846),
+            Offset(16.047681038809458, 24.15758118207667),
+            Offset(16.04697282889272, 24.039447195772148),
+            Offset(16.046875001068816, 24.00013038745822),
+            Offset(16.046875, 24.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(20.0, 38.0),
+            Offset(19.80052916638957, 37.82094858440343),
+            Offset(19.11164196247534, 37.113338636628065),
+            Offset(17.966685507002932, 35.41245490387412),
+            Offset(16.388368582775627, 32.64921363804367),
+            Offset(15.647464737031132, 29.978925368550197),
+            Offset(15.429304294338682, 27.901717576585412),
+            Offset(15.521061545777922, 26.541981731286498),
+            Offset(15.686957461206134, 25.650190745693187),
+            Offset(15.84005263487414, 25.05047803281999),
+            Offset(15.953666440217397, 24.63834819764938),
+            Offset(16.022300799535966, 24.35280207511846),
+            Offset(16.047681038809458, 24.15758118207667),
+            Offset(16.04697282889272, 24.039447195772148),
+            Offset(16.046875001068816, 24.00013038745822),
+            Offset(16.046875, 24.0),
+          ],
+          <Offset>[
+            Offset(20.0, 10.0),
+            Offset(20.336398367184067, 9.927295626138141),
+            Offset(21.61961776538543, 9.72474102274872),
+            Offset(24.500250707242675, 9.686480391272799),
+            Offset(29.133385987204207, 10.794971358676317),
+            Offset(33.085276289027, 13.261042804636414),
+            Offset(35.61934940727931, 16.155597331715125),
+            Offset(36.90120687325454, 18.566431660274624),
+            Offset(37.51766973937702, 20.39600693122759),
+            Offset(37.80131897673813, 21.733189454430033),
+            Offset(37.922586409468074, 22.681298975667566),
+            Offset(37.96809580639086, 23.323362088481808),
+            Offset(37.98160482507013, 23.72156603022978),
+            Offset(37.98420298259533, 23.93063803493896),
+            Offset(37.98437499812064, 23.99977073325126),
+            Offset(37.984375, 24.0),
+          ],
+          <Offset>[
+            Offset(20.0, 10.0),
+            Offset(20.336398367184067, 9.927295626138141),
+            Offset(21.61961776538543, 9.72474102274872),
+            Offset(24.500250707242675, 9.686480391272799),
+            Offset(29.133385987204207, 10.794971358676317),
+            Offset(33.085276289027, 13.261042804636414),
+            Offset(35.61934940727931, 16.155597331715125),
+            Offset(36.90120687325454, 18.566431660274624),
+            Offset(37.51766973937702, 20.39600693122759),
+            Offset(37.80131897673813, 21.733189454430033),
+            Offset(37.922586409468074, 22.681298975667566),
+            Offset(37.96809580639086, 23.323362088481808),
+            Offset(37.98160482507013, 23.72156603022978),
+            Offset(37.98420298259533, 23.93063803493896),
+            Offset(37.98437499812064, 23.99977073325126),
+            Offset(37.984375, 24.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(20.0, 10.0),
+            Offset(20.336398367184067, 9.927295626138141),
+            Offset(21.61961776538543, 9.72474102274872),
+            Offset(24.500250707242675, 9.686480391272799),
+            Offset(29.133385987204207, 10.794971358676317),
+            Offset(33.085276289027, 13.261042804636414),
+            Offset(35.61934940727931, 16.155597331715125),
+            Offset(36.90120687325454, 18.566431660274624),
+            Offset(37.51766973937702, 20.39600693122759),
+            Offset(37.80131897673813, 21.733189454430033),
+            Offset(37.922586409468074, 22.681298975667566),
+            Offset(37.96809580639086, 23.323362088481808),
+            Offset(37.98160482507013, 23.72156603022978),
+            Offset(37.98420298259533, 23.93063803493896),
+            Offset(37.98437499812064, 23.99977073325126),
+            Offset(37.984375, 24.0),
+          ],
+          <Offset>[
+            Offset(12.0, 10.0),
+            Offset(12.471392106978678, 9.776199797090369),
+            Offset(14.305807236043629, 9.055014880021247),
+            Offset(18.610309025181536, 8.190625764405059),
+            Offset(25.30150028462865, 8.56028166562692),
+            Offset(31.058306963326036, 11.146785273031004),
+            Offset(34.676551168058815, 14.535050411928939),
+            Offset(36.494518178468795, 17.476216776016784),
+            Offset(37.358155554762675, 19.733238289785056),
+            Offset(37.747534126472296, 21.37712051621949),
+            Offset(37.90872109014562, 22.525653380696408),
+            Offset(37.966090830555, 23.280619636994825),
+            Offset(37.98158497072831, 23.720567249296572),
+            Offset(37.98420298259533, 23.93063803493896),
+            Offset(37.98437499812064, 23.99977073325126),
+            Offset(37.984375, 24.0),
+          ],
+          <Offset>[
+            Offset(12.0, 10.0),
+            Offset(12.471392106978678, 9.776199797090369),
+            Offset(14.305807236043629, 9.055014880021247),
+            Offset(18.610309025181536, 8.190625764405059),
+            Offset(25.30150028462865, 8.56028166562692),
+            Offset(31.058306963326036, 11.146785273031004),
+            Offset(34.676551168058815, 14.535050411928939),
+            Offset(36.494518178468795, 17.476216776016784),
+            Offset(37.358155554762675, 19.733238289785056),
+            Offset(37.747534126472296, 21.37712051621949),
+            Offset(37.90872109014562, 22.525653380696408),
+            Offset(37.966090830555, 23.280619636994825),
+            Offset(37.98158497072831, 23.720567249296572),
+            Offset(37.98420298259533, 23.93063803493896),
+            Offset(37.98437499812064, 23.99977073325126),
+            Offset(37.984375, 24.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(12.0, 10.0),
+            Offset(12.471392106978678, 9.776199797090369),
+            Offset(14.305807236043629, 9.055014880021247),
+            Offset(18.610309025181536, 8.190625764405059),
+            Offset(25.30150028462865, 8.56028166562692),
+            Offset(31.058306963326036, 11.146785273031004),
+            Offset(34.676551168058815, 14.535050411928939),
+            Offset(36.494518178468795, 17.476216776016784),
+            Offset(37.358155554762675, 19.733238289785056),
+            Offset(37.747534126472296, 21.37712051621949),
+            Offset(37.90872109014562, 22.525653380696408),
+            Offset(37.966090830555, 23.280619636994825),
+            Offset(37.98158497072831, 23.720567249296572),
+            Offset(37.98420298259533, 23.93063803493896),
+            Offset(37.98437499812064, 23.99977073325126),
+            Offset(37.984375, 24.0),
+          ],
+          <Offset>[
+            Offset(12.0, 38.0),
+            Offset(11.702518567871357, 37.66537647364598),
+            Offset(10.658516625352863, 36.33928535710834),
+            Offset(8.824010134196683, 33.09051115695809),
+            Offset(7.1836189818552825, 27.281162329401685),
+            Offset(7.496144808015238, 21.476581828404456),
+            Offset(9.111297618380014, 17.04189006314128),
+            Offset(10.944617318448458, 14.273857051206388),
+            Offset(12.53900132190984, 12.570685504160476),
+            Offset(13.794958189310869, 11.511449771648872),
+            Offset(14.725226220612885, 10.848452384926155),
+            Offset(15.369630177421872, 10.439047154145166),
+            Offset(15.770227104207432, 10.2001454599895),
+            Offset(15.977727112935135, 10.078681424879498),
+            Offset(16.046646118566024, 10.039192889334426),
+            Offset(16.046875, 10.039062500000002),
+          ],
+          <Offset>[
+            Offset(12.0, 38.0),
+            Offset(11.702518567871357, 37.66537647364598),
+            Offset(10.658516625352863, 36.33928535710834),
+            Offset(8.824010134196683, 33.09051115695809),
+            Offset(7.1836189818552825, 27.281162329401685),
+            Offset(7.496144808015238, 21.476581828404456),
+            Offset(9.111297618380014, 17.04189006314128),
+            Offset(10.944617318448458, 14.273857051206388),
+            Offset(12.53900132190984, 12.570685504160476),
+            Offset(13.794958189310869, 11.511449771648872),
+            Offset(14.725226220612885, 10.848452384926155),
+            Offset(15.369630177421872, 10.439047154145166),
+            Offset(15.770227104207432, 10.2001454599895),
+            Offset(15.977727112935135, 10.078681424879498),
+            Offset(16.046646118566024, 10.039192889334426),
+            Offset(16.046875, 10.039062500000002),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(28.0, 10.0),
+            Offset(28.201404627403626, 10.078391455203436),
+            Offset(28.933428294762216, 10.394467165488965),
+            Offset(30.390192389339767, 11.182335018165535),
+            Offset(32.965271689797355, 13.029661051643949),
+            Offset(35.1122456147493, 15.375300336193657),
+            Offset(36.562147646495, 17.776144251469397),
+            Offset(37.30789556803902, 19.656646544522257),
+            Offset(37.67718392397584, 21.05877557270265),
+            Offset(37.8551038269895, 22.089258392661982),
+            Offset(37.936451728823435, 22.836944570678963),
+            Offset(37.970100782170086, 23.366104539917995),
+            Offset(37.98162467944151, 23.722564811170674),
+            Offset(37.98420298259533, 23.93063803493896),
+            Offset(37.98437499812064, 23.99977073325126),
+            Offset(37.984375, 24.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(28.0, 10.0),
+            Offset(28.201404627403626, 10.078391455203436),
+            Offset(28.933428294762216, 10.394467165488965),
+            Offset(30.390192389339767, 11.182335018165535),
+            Offset(32.965271689797355, 13.029661051643949),
+            Offset(35.1122456147493, 15.375300336193657),
+            Offset(36.562147646495, 17.776144251469397),
+            Offset(37.30789556803902, 19.656646544522257),
+            Offset(37.67718392397584, 21.05877557270265),
+            Offset(37.8551038269895, 22.089258392661982),
+            Offset(37.936451728823435, 22.836944570678963),
+            Offset(37.970100782170086, 23.366104539917995),
+            Offset(37.98162467944151, 23.722564811170674),
+            Offset(37.98420298259533, 23.93063803493896),
+            Offset(37.98437499812064, 23.99977073325126),
+            Offset(37.984375, 24.0),
+          ],
+          <Offset>[
+            Offset(28.0, 38.0),
+            Offset(27.665535426609125, 37.97204441346873),
+            Offset(26.425452491852123, 37.78306477936831),
+            Offset(23.856627189100024, 36.90830953076686),
+            Offset(20.220254285368775, 34.883903331011304),
+            Offset(17.674434062753434, 32.09318290010744),
+            Offset(16.372102533554372, 29.522264496339687),
+            Offset(15.927750240562414, 27.632196615534127),
+            Offset(15.84647164580496, 26.31295938716825),
+            Offset(15.893837485125506, 25.406546971051934),
+            Offset(15.967531759572758, 24.79399379266078),
+            Offset(16.024305775315185, 24.395544526554644),
+            Offset(16.04770089318083, 24.15857996301756),
+            Offset(16.04697282889272, 24.039447195772148),
+            Offset(16.046875001068816, 24.00013038745822),
+            Offset(16.046875, 24.0),
+          ],
+          <Offset>[
+            Offset(28.0, 38.0),
+            Offset(27.665535426609125, 37.97204441346873),
+            Offset(26.425452491852123, 37.78306477936831),
+            Offset(23.856627189100024, 36.90830953076686),
+            Offset(20.220254285368775, 34.883903331011304),
+            Offset(17.674434062753434, 32.09318290010744),
+            Offset(16.372102533554372, 29.522264496339687),
+            Offset(15.927750240562414, 27.632196615534127),
+            Offset(15.84647164580496, 26.31295938716825),
+            Offset(15.893837485125506, 25.406546971051934),
+            Offset(15.967531759572758, 24.79399379266078),
+            Offset(16.024305775315185, 24.395544526554644),
+            Offset(16.04770089318083, 24.15857996301756),
+            Offset(16.04697282889272, 24.039447195772148),
+            Offset(16.046875001068816, 24.00013038745822),
+            Offset(16.046875, 24.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(28.0, 38.0),
+            Offset(27.665535426609125, 37.97204441346873),
+            Offset(26.425452491852123, 37.78306477936831),
+            Offset(23.856627189100024, 36.90830953076686),
+            Offset(20.220254285368775, 34.883903331011304),
+            Offset(17.674434062753434, 32.09318290010744),
+            Offset(16.372102533554372, 29.522264496339687),
+            Offset(15.927750240562414, 27.632196615534127),
+            Offset(15.84647164580496, 26.31295938716825),
+            Offset(15.893837485125506, 25.406546971051934),
+            Offset(15.967531759572758, 24.79399379266078),
+            Offset(16.024305775315185, 24.395544526554644),
+            Offset(16.04770089318083, 24.15857996301756),
+            Offset(16.04697282889272, 24.039447195772148),
+            Offset(16.046875001068816, 24.00013038745822),
+            Offset(16.046875, 24.0),
+          ],
+          <Offset>[
+            Offset(36.0, 38.0),
+            Offset(35.76354602512734, 38.127616524226184),
+            Offset(34.8785778289746, 38.55711805888804),
+            Offset(32.999302561906276, 39.23025327768289),
+            Offset(29.425003886263205, 40.25195463963817),
+            Offset(25.82575399180393, 40.59552644028928),
+            Offset(22.690109209518063, 40.382092009792466),
+            Offset(20.504194467888382, 39.90032129560486),
+            Offset(18.99442778510781, 39.39246462872818),
+            Offset(17.93893193068415, 38.9455752321924),
+            Offset(17.195971979173102, 38.583889605337184),
+            Offset(16.676976397431464, 38.30929944757449),
+            Offset(16.325154827781827, 38.116015685053),
+            Offset(16.11621854485031, 38.00021296666479),
+            Offset(16.047103883571605, 37.96106788558201),
+            Offset(16.046875, 37.9609375),
+          ],
+          <Offset>[
+            Offset(36.0, 38.0),
+            Offset(35.76354602512734, 38.127616524226184),
+            Offset(34.8785778289746, 38.55711805888804),
+            Offset(32.999302561906276, 39.23025327768289),
+            Offset(29.425003886263205, 40.25195463963817),
+            Offset(25.82575399180393, 40.59552644028928),
+            Offset(22.690109209518063, 40.382092009792466),
+            Offset(20.504194467888382, 39.90032129560486),
+            Offset(18.99442778510781, 39.39246462872818),
+            Offset(17.93893193068415, 38.9455752321924),
+            Offset(17.195971979173102, 38.583889605337184),
+            Offset(16.676976397431464, 38.30929944757449),
+            Offset(16.325154827781827, 38.116015685053),
+            Offset(16.11621854485031, 38.00021296666479),
+            Offset(16.047103883571605, 37.96106788558201),
+            Offset(16.046875, 37.9609375),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(36.0, 38.0),
+            Offset(35.76354602512734, 38.127616524226184),
+            Offset(34.8785778289746, 38.55711805888804),
+            Offset(32.999302561906276, 39.23025327768289),
+            Offset(29.425003886263205, 40.25195463963817),
+            Offset(25.82575399180393, 40.59552644028928),
+            Offset(22.690109209518063, 40.382092009792466),
+            Offset(20.504194467888382, 39.90032129560486),
+            Offset(18.99442778510781, 39.39246462872818),
+            Offset(17.93893193068415, 38.9455752321924),
+            Offset(17.195971979173102, 38.583889605337184),
+            Offset(16.676976397431464, 38.30929944757449),
+            Offset(16.325154827781827, 38.116015685053),
+            Offset(16.11621854485031, 38.00021296666479),
+            Offset(16.047103883571605, 37.96106788558201),
+            Offset(16.046875, 37.9609375),
+          ],
+          <Offset>[
+            Offset(36.0, 10.0),
+            Offset(36.06641088760902, 10.22948728425121),
+            Offset(36.247238824104016, 11.064193308216439),
+            Offset(36.28013407140091, 12.678189645033275),
+            Offset(36.797157392346996, 15.26435074467823),
+            Offset(37.13921494048486, 17.48955786783516),
+            Offset(37.50494588572052, 19.396691171264223),
+            Offset(37.71458426282127, 20.746861428770725),
+            Offset(37.836698108596735, 21.721544214172404),
+            Offset(37.9088886772507, 22.445327330841877),
+            Offset(37.95031704814173, 22.992590165603303),
+            Offset(37.972105758008134, 23.40884699145153),
+            Offset(37.981644533782294, 23.72356359205216),
+            Offset(37.98420298259533, 23.93063803493896),
+            Offset(37.98437499812064, 23.99977073325126),
+            Offset(37.984375, 24.0),
+          ],
+          <Offset>[
+            Offset(36.0, 10.0),
+            Offset(36.06641088760902, 10.22948728425121),
+            Offset(36.247238824104016, 11.064193308216439),
+            Offset(36.28013407140091, 12.678189645033275),
+            Offset(36.797157392346996, 15.26435074467823),
+            Offset(37.13921494048486, 17.48955786783516),
+            Offset(37.50494588572052, 19.396691171264223),
+            Offset(37.71458426282127, 20.746861428770725),
+            Offset(37.836698108596735, 21.721544214172404),
+            Offset(37.9088886772507, 22.445327330841877),
+            Offset(37.95031704814173, 22.992590165603303),
+            Offset(37.972105758008134, 23.40884699145153),
+            Offset(37.981644533782294, 23.72356359205216),
+            Offset(37.98420298259533, 23.93063803493896),
+            Offset(37.98437499812064, 23.99977073325126),
+            Offset(37.984375, 24.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(36.0, 10.0),
+            Offset(36.06641088760902, 10.22948728425121),
+            Offset(36.247238824104016, 11.064193308216439),
+            Offset(36.28013407140091, 12.678189645033275),
+            Offset(36.797157392346996, 15.26435074467823),
+            Offset(37.13921494048486, 17.48955786783516),
+            Offset(37.50494588572052, 19.396691171264223),
+            Offset(37.71458426282127, 20.746861428770725),
+            Offset(37.836698108596735, 21.721544214172404),
+            Offset(37.9088886772507, 22.445327330841877),
+            Offset(37.95031704814173, 22.992590165603303),
+            Offset(37.972105758008134, 23.40884699145153),
+            Offset(37.981644533782294, 23.72356359205216),
+            Offset(37.98420298259533, 23.93063803493896),
+            Offset(37.98437499812064, 23.99977073325126),
+            Offset(37.984375, 24.0),
+          ],
+          <Offset>[
+            Offset(28.0, 10.0),
+            Offset(28.201404627403626, 10.078391455203436),
+            Offset(28.933428294762216, 10.394467165488965),
+            Offset(30.390192389339767, 11.182335018165535),
+            Offset(32.965271689771434, 13.029661051628835),
+            Offset(35.1122456147839, 15.37530033622975),
+            Offset(36.56214764650002, 17.776144251478037),
+            Offset(37.30789556803553, 19.656646544512885),
+            Offset(37.6771839239824, 21.058775572729875),
+            Offset(37.855103826984866, 22.08925839263133),
+            Offset(37.936451728819264, 22.83694457063215),
+            Offset(37.97010078217227, 23.366104539964546),
+            Offset(37.98162467944048, 23.72256481111895),
+            Offset(37.98420298259533, 23.93063803493896),
+            Offset(37.98437499812064, 23.99977073325126),
+            Offset(37.984375, 24.0),
+          ],
+          <Offset>[
+            Offset(28.0, 10.0),
+            Offset(28.201404627403626, 10.078391455203436),
+            Offset(28.933428294762216, 10.394467165488965),
+            Offset(30.390192389339767, 11.182335018165535),
+            Offset(32.965271689771434, 13.029661051628835),
+            Offset(35.1122456147839, 15.37530033622975),
+            Offset(36.56214764650002, 17.776144251478037),
+            Offset(37.30789556803553, 19.656646544512885),
+            Offset(37.6771839239824, 21.058775572729875),
+            Offset(37.855103826984866, 22.08925839263133),
+            Offset(37.936451728819264, 22.83694457063215),
+            Offset(37.97010078217227, 23.366104539964546),
+            Offset(37.98162467944048, 23.72256481111895),
+            Offset(37.98420298259533, 23.93063803493896),
+            Offset(37.98437499812064, 23.99977073325126),
+            Offset(37.984375, 24.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.4,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(25.9803619385, 10.0808715820312),
+            Offset(26.24734975266162, 10.121477441291603),
+            Offset(27.241376275815597, 10.319449968588765),
+            Offset(29.345667299377446, 10.996623113678018),
+            Offset(32.72280163575925, 12.973817480806794),
+            Offset(35.41804029052999, 15.796810056618218),
+            Offset(36.783888674275296, 18.8537388815253),
+            Offset(36.76701249453052, 21.35111747744179),
+            Offset(36.26390281517317, 23.085253769933463),
+            Offset(35.55535514851543, 24.257370049664456),
+            Offset(34.77089367166791, 25.028704643469656),
+            Offset(33.96889356105216, 25.514915651546502),
+            Offset(33.73686027995342, 25.787198268946305),
+            Offset(33.7091203242374, 25.93222884083115),
+            Offset(33.69944957998795, 25.98020292120046),
+            Offset(33.69941711426, 25.9803619385),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(25.9803619385, 10.0808715820312),
+            Offset(26.24734975266162, 10.121477441291603),
+            Offset(27.241376275815597, 10.319449968588765),
+            Offset(29.345667299377446, 10.996623113678018),
+            Offset(32.72280163575925, 12.973817480806794),
+            Offset(35.41804029052999, 15.796810056618218),
+            Offset(36.783888674275296, 18.8537388815253),
+            Offset(36.76701249453052, 21.35111747744179),
+            Offset(36.26390281517317, 23.085253769933463),
+            Offset(35.55535514851543, 24.257370049664456),
+            Offset(34.77089367166791, 25.028704643469656),
+            Offset(33.96889356105216, 25.514915651546502),
+            Offset(33.73686027995342, 25.787198268946305),
+            Offset(33.7091203242374, 25.93222884083115),
+            Offset(33.69944957998795, 25.98020292120046),
+            Offset(33.69941711426, 25.9803619385),
+          ],
+          <Offset>[
+            Offset(21.71524047854, 10.08666992187495),
+            Offset(21.982903763348478, 10.045351931852684),
+            Offset(22.99349608666543, 9.936293880836264),
+            Offset(25.21035246628397, 9.952369351341783),
+            Offset(29.035517231913246, 10.830169092168797),
+            Offset(32.462182698221135, 12.722041677870582),
+            Offset(34.6342053830667, 15.169969772143412),
+            Offset(35.27135627354946, 17.356833870551508),
+            Offset(35.261093097185864, 18.9396949456921),
+            Offset(34.91382519935541, 20.040769389846336),
+            Offset(34.38827629668238, 20.780777793707646),
+            Offset(33.765232522395, 21.254657700831064),
+            Offset(33.64837008011561, 21.522993253407158),
+            Offset(33.68424238621404, 21.667178311445102),
+            Offset(33.695656510950016, 21.71508152285272),
+            Offset(33.69569396972875, 21.71524047854),
+          ],
+          <Offset>[
+            Offset(21.71524047854, 10.08666992187495),
+            Offset(21.982903763348478, 10.045351931852684),
+            Offset(22.99349608666543, 9.936293880836264),
+            Offset(25.21035246628397, 9.952369351341783),
+            Offset(29.035517231913246, 10.830169092168797),
+            Offset(32.462182698221135, 12.722041677870582),
+            Offset(34.6342053830667, 15.169969772143412),
+            Offset(35.27135627354946, 17.356833870551508),
+            Offset(35.261093097185864, 18.9396949456921),
+            Offset(34.91382519935541, 20.040769389846336),
+            Offset(34.38827629668238, 20.780777793707646),
+            Offset(33.765232522395, 21.254657700831064),
+            Offset(33.64837008011561, 21.522993253407158),
+            Offset(33.68424238621404, 21.667178311445102),
+            Offset(33.695656510950016, 21.71508152285272),
+            Offset(33.69569396972875, 21.71524047854),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(21.71524047854, 10.08666992187495),
+            Offset(21.982903763348478, 10.045351931852684),
+            Offset(22.99349608666543, 9.936293880836264),
+            Offset(25.21035246628397, 9.952369351341783),
+            Offset(29.035517231913246, 10.830169092168797),
+            Offset(32.462182698221135, 12.722041677870582),
+            Offset(34.6342053830667, 15.169969772143412),
+            Offset(35.27135627354946, 17.356833870551508),
+            Offset(35.261093097185864, 18.9396949456921),
+            Offset(34.91382519935541, 20.040769389846336),
+            Offset(34.38827629668238, 20.780777793707646),
+            Offset(33.765232522395, 21.254657700831064),
+            Offset(33.64837008011561, 21.522993253407158),
+            Offset(33.68424238621404, 21.667178311445102),
+            Offset(33.695656510950016, 21.71508152285272),
+            Offset(33.69569396972875, 21.71524047854),
+          ],
+          <Offset>[
+            Offset(20.38534545901, 33.58843994137495),
+            Offset(20.201841309352226, 33.51724216990988),
+            Offset(19.52604939723053, 33.21887595990057),
+            Offset(18.136339024380444, 32.40365324862053),
+            Offset(16.04712654551591, 30.461865967119433),
+            Offset(14.577060238240236, 28.0264269776442),
+            Offset(13.994487095033097, 25.634345790525074),
+            Offset(14.153634069120049, 23.79893867630368),
+            Offset(14.553527226932179, 22.528826743801503),
+            Offset(15.025384555247236, 21.662436101573526),
+            Offset(15.497710938825563, 21.080134472962335),
+            Offset(15.942008679892364, 20.700182681641447),
+            Offset(16.112739485629255, 20.479547499155572),
+            Offset(16.16612590845245, 20.362297297739467),
+            Offset(16.184204863198634, 20.323614463660565),
+            Offset(16.18426513672875, 20.323486328150004),
+          ],
+          <Offset>[
+            Offset(20.38534545901, 33.58843994137495),
+            Offset(20.201841309352226, 33.51724216990988),
+            Offset(19.52604939723053, 33.21887595990057),
+            Offset(18.136339024380444, 32.40365324862053),
+            Offset(16.04712654551591, 30.461865967119433),
+            Offset(14.577060238240236, 28.0264269776442),
+            Offset(13.994487095033097, 25.634345790525074),
+            Offset(14.153634069120049, 23.79893867630368),
+            Offset(14.553527226932179, 22.528826743801503),
+            Offset(15.025384555247236, 21.662436101573526),
+            Offset(15.497710938825563, 21.080134472962335),
+            Offset(15.942008679892364, 20.700182681641447),
+            Offset(16.112739485629255, 20.479547499155572),
+            Offset(16.16612590845245, 20.362297297739467),
+            Offset(16.184204863198634, 20.323614463660565),
+            Offset(16.18426513672875, 20.323486328150004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(20.38534545901, 33.58843994137495),
+            Offset(20.201841309352226, 33.51724216990988),
+            Offset(19.52604939723053, 33.21887595990057),
+            Offset(18.136339024380444, 32.40365324862053),
+            Offset(16.04712654551591, 30.461865967119433),
+            Offset(14.577060238240236, 28.0264269776442),
+            Offset(13.994487095033097, 25.634345790525074),
+            Offset(14.153634069120049, 23.79893867630368),
+            Offset(14.553527226932179, 22.528826743801503),
+            Offset(15.025384555247236, 21.662436101573526),
+            Offset(15.497710938825563, 21.080134472962335),
+            Offset(15.942008679892364, 20.700182681641447),
+            Offset(16.112739485629255, 20.479547499155572),
+            Offset(16.16612590845245, 20.362297297739467),
+            Offset(16.184204863198634, 20.323614463660565),
+            Offset(16.18426513672875, 20.323486328150004),
+          ],
+          <Offset>[
+            Offset(26.45292663577, 33.61682128903115),
+            Offset(26.267757982058896, 33.66216200032068),
+            Offset(25.565762818520753, 33.800433030319155),
+            Offset(24.010240085864943, 33.92471641540826),
+            Offset(21.274220812328387, 33.54307228337337),
+            Offset(18.755635225924188, 32.42595626039356),
+            Offset(17.02173119623894, 30.892872384129042),
+            Offset(16.25004855832711, 29.492905492037444),
+            Offset(15.94990552834042, 28.433591685898556),
+            Offset(15.909598267598703, 27.665285725985097),
+            Offset(16.015710329110547, 27.125598624037742),
+            Offset(16.207680408412312, 26.761973499405),
+            Offset(16.21513039026629, 26.546291998077596),
+            Offset(16.178017367555142, 26.42989412892011),
+            Offset(16.166100602771248, 26.391195938046174),
+            Offset(16.16606140137715, 26.391067504910005),
+          ],
+          <Offset>[
+            Offset(26.45292663577, 33.61682128903125),
+            Offset(26.267757982058896, 33.66216200032078),
+            Offset(25.565762818520742, 33.800433030319255),
+            Offset(24.010240085864922, 33.924716415408355),
+            Offset(21.274220812328338, 33.543072283373455),
+            Offset(18.75563522592411, 32.42595626039363),
+            Offset(17.02173119623894, 30.892872384129042),
+            Offset(16.25004855832711, 29.492905492037444),
+            Offset(15.94990552834032, 28.433591685898577),
+            Offset(15.909598267598604, 27.66528572598511),
+            Offset(16.015710329110444, 27.12559862403775),
+            Offset(16.207680408412212, 26.761973499405002),
+            Offset(16.21513039026629, 26.546291998077596),
+            Offset(16.178017367555142, 26.42989412892011),
+            Offset(16.166100602771248, 26.391195938046174),
+            Offset(16.16606140137715, 26.391067504910005),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(26.45292663577, 33.61682128903125),
+            Offset(26.267757982058896, 33.66216200032078),
+            Offset(25.565762818520742, 33.800433030319255),
+            Offset(24.010240085864922, 33.924716415408355),
+            Offset(21.274220812328338, 33.543072283373455),
+            Offset(18.75563522592411, 32.42595626039363),
+            Offset(17.02173119623894, 30.892872384129042),
+            Offset(16.25004855832711, 29.492905492037444),
+            Offset(15.94990552834032, 28.433591685898577),
+            Offset(15.909598267598604, 27.66528572598511),
+            Offset(16.015710329110444, 27.12559862403775),
+            Offset(16.207680408412212, 26.761973499405002),
+            Offset(16.21513039026629, 26.546291998077596),
+            Offset(16.178017367555142, 26.42989412892011),
+            Offset(16.166100602771248, 26.391195938046174),
+            Offset(16.16606140137715, 26.391067504910005),
+          ],
+          <Offset>[
+            Offset(25.980361938504004, 10.08087158203125),
+            Offset(26.24734975266562, 10.121477441291729),
+            Offset(27.241376275819576, 10.319449968589181),
+            Offset(29.34566729938131, 10.996623113679052),
+            Offset(32.72280163576268, 12.973817480808854),
+            Offset(35.418040290532716, 15.796810056621142),
+            Offset(36.78388867431184, 18.85373888150635),
+            Offset(36.7670124944848, 21.351117477459912),
+            Offset(36.26390281513503, 23.08525376994367),
+            Offset(35.55535514848602, 24.2573700496689),
+            Offset(34.770893671747466, 25.028704643461566),
+            Offset(33.968893561041796, 25.51491565154599),
+            Offset(33.73686027997065, 25.787198268949965),
+            Offset(33.70912032425456, 25.93222884083507),
+            Offset(33.6994495800051, 25.98020292120446),
+            Offset(33.69941711427715, 25.980361938504004),
+          ],
+          <Offset>[
+            Offset(25.980361938504004, 10.08087158203125),
+            Offset(26.24734975266562, 10.121477441291729),
+            Offset(27.241376275819576, 10.319449968589181),
+            Offset(29.34566729938131, 10.996623113679052),
+            Offset(32.72280163576268, 12.973817480808854),
+            Offset(35.418040290532716, 15.796810056621142),
+            Offset(36.78388867431184, 18.85373888150635),
+            Offset(36.7670124944848, 21.351117477459912),
+            Offset(36.26390281513503, 23.08525376994367),
+            Offset(35.55535514848602, 24.2573700496689),
+            Offset(34.770893671747466, 25.028704643461566),
+            Offset(33.968893561041796, 25.51491565154599),
+            Offset(33.73686027997065, 25.787198268949965),
+            Offset(33.70912032425456, 25.93222884083507),
+            Offset(33.6994495800051, 25.98020292120446),
+            Offset(33.69941711427715, 25.980361938504004),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+  ],
+);
diff --git a/lib/src/material/animated_icons/data/play_pause.g.dart b/lib/src/material/animated_icons/data/play_pause.g.dart
new file mode 100644
index 0000000..a850aa3
--- /dev/null
+++ b/lib/src/material/animated_icons/data/play_pause.g.dart
@@ -0,0 +1,816 @@
+// Copyright 2014 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.
+
+// AUTOGENERATED FILE DO NOT EDIT!
+// This file was generated by vitool.
+part of material_animated_icons;
+const _AnimatedIconData _$play_pause = _AnimatedIconData(
+  Size(48.0, 48.0),
+  <_PathFrames>[
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(16.046875, 10.039062500000002),
+            Offset(16.316498427194905, 9.888877552610037),
+            Offset(17.350168694919763, 9.372654593279519),
+            Offset(19.411307079826894, 8.531523285503246),
+            Offset(22.581365240485308, 7.589125591600418),
+            Offset(25.499178877190392, 6.946027752843147),
+            Offset(28.464059662259196, 6.878006546805963),
+            Offset(30.817518246129985, 7.278084288616373),
+            Offset(32.55729037951853, 7.8522502852455425),
+            Offset(33.815177617779455, 8.44633949301522),
+            Offset(34.712260860180656, 8.99474841944718),
+            Offset(35.33082450786742, 9.453096000457315),
+            Offset(35.71938467416858, 9.764269500343072),
+            Offset(35.93041292728106, 9.940652668613495),
+            Offset(35.999770475547926, 9.999803268019111),
+            Offset(36.0, 10.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(16.046875, 10.039062500000002),
+            Offset(16.316498427194905, 9.888877552610037),
+            Offset(17.350168694919763, 9.372654593279519),
+            Offset(19.411307079826894, 8.531523285503246),
+            Offset(22.581365240485308, 7.589125591600418),
+            Offset(25.499178877190392, 6.946027752843147),
+            Offset(28.464059662259196, 6.878006546805963),
+            Offset(30.817518246129985, 7.278084288616373),
+            Offset(32.55729037951853, 7.8522502852455425),
+            Offset(33.815177617779455, 8.44633949301522),
+            Offset(34.712260860180656, 8.99474841944718),
+            Offset(35.33082450786742, 9.453096000457315),
+            Offset(35.71938467416858, 9.764269500343072),
+            Offset(35.93041292728106, 9.940652668613495),
+            Offset(35.999770475547926, 9.999803268019111),
+            Offset(36.0, 10.0),
+          ],
+          <Offset>[
+            Offset(16.046875, 24.0),
+            Offset(16.048342217256838, 23.847239495401816),
+            Offset(16.077346902872737, 23.272630763824544),
+            Offset(16.048056811677085, 21.774352893256555),
+            Offset(16.312852147291277, 18.33792251536507),
+            Offset(17.783803270262858, 14.342870123090869),
+            Offset(20.317723014778526, 11.617364447163006),
+            Offset(22.6612333095366, 10.320666923510533),
+            Offset(24.489055761050455, 9.794101160418514),
+            Offset(25.820333134665205, 9.653975058221658),
+            Offset(26.739449095852216, 9.704987479092615),
+            Offset(27.339611564620206, 9.827950233030684),
+            Offset(27.720964836869285, 9.92326668993185),
+            Offset(27.930511332768496, 9.98033236260651),
+            Offset(27.999770476623045, 9.999934423927339),
+            Offset(27.999999999999996, 10.0),
+          ],
+          <Offset>[
+            Offset(16.046875, 24.0),
+            Offset(16.048342217256838, 23.847239495401816),
+            Offset(16.077346902872737, 23.272630763824544),
+            Offset(16.048056811677085, 21.774352893256555),
+            Offset(16.312852147291277, 18.33792251536507),
+            Offset(17.783803270262858, 14.342870123090869),
+            Offset(20.317723014778526, 11.617364447163006),
+            Offset(22.6612333095366, 10.320666923510533),
+            Offset(24.489055761050455, 9.794101160418514),
+            Offset(25.820333134665205, 9.653975058221658),
+            Offset(26.739449095852216, 9.704987479092615),
+            Offset(27.339611564620206, 9.827950233030684),
+            Offset(27.720964836869285, 9.92326668993185),
+            Offset(27.930511332768496, 9.98033236260651),
+            Offset(27.999770476623045, 9.999934423927339),
+            Offset(27.999999999999996, 10.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(16.046875, 24.0),
+            Offset(16.048342217256838, 23.847239495401816),
+            Offset(16.077346902872737, 23.272630763824544),
+            Offset(16.048056811677085, 21.774352893256555),
+            Offset(16.312852147291277, 18.33792251536507),
+            Offset(17.783803270262858, 14.342870123090869),
+            Offset(20.317723014778526, 11.617364447163006),
+            Offset(22.6612333095366, 10.320666923510533),
+            Offset(24.489055761050455, 9.794101160418514),
+            Offset(25.820333134665205, 9.653975058221658),
+            Offset(26.739449095852216, 9.704987479092615),
+            Offset(27.339611564620206, 9.827950233030684),
+            Offset(27.720964836869285, 9.92326668993185),
+            Offset(27.930511332768496, 9.98033236260651),
+            Offset(27.999770476623045, 9.999934423927339),
+            Offset(27.999999999999996, 10.0),
+          ],
+          <Offset>[
+            Offset(37.984375, 24.0),
+            Offset(37.98179511896882, 24.268606388242382),
+            Offset(37.92629019604922, 25.273340032354483),
+            Offset(37.60401862920776, 27.24886978355857),
+            Offset(36.59673961336577, 30.16713606026377),
+            Offset(35.26901818749416, 32.58105797429066),
+            Offset(33.66938906523204, 34.56713290494057),
+            Offset(32.196778918797094, 35.8827095523761),
+            Offset(30.969894470496282, 36.721466129987085),
+            Offset(29.989349224706995, 37.25388702486493),
+            Offset(29.223528593231507, 37.59010302049878),
+            Offset(28.651601378627003, 37.79719553439594),
+            Offset(28.27745500043001, 37.91773612047938),
+            Offset(28.069390261744058, 37.979987943400474),
+            Offset(28.000229522301836, 37.99993442016443),
+            Offset(28.0, 38.0),
+          ],
+          <Offset>[
+            Offset(37.984375, 24.0),
+            Offset(37.98179511896882, 24.268606388242382),
+            Offset(37.92629019604922, 25.273340032354483),
+            Offset(37.60401862920776, 27.24886978355857),
+            Offset(36.59673961336577, 30.16713606026377),
+            Offset(35.26901818749416, 32.58105797429066),
+            Offset(33.66938906523204, 34.56713290494057),
+            Offset(32.196778918797094, 35.8827095523761),
+            Offset(30.969894470496282, 36.721466129987085),
+            Offset(29.989349224706995, 37.25388702486493),
+            Offset(29.223528593231507, 37.59010302049878),
+            Offset(28.651601378627003, 37.79719553439594),
+            Offset(28.27745500043001, 37.91773612047938),
+            Offset(28.069390261744058, 37.979987943400474),
+            Offset(28.000229522301836, 37.99993442016443),
+            Offset(28.0, 38.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(37.984375, 24.0),
+            Offset(37.98179511896882, 24.268606388242382),
+            Offset(37.92629019604922, 25.273340032354483),
+            Offset(37.60401862920776, 27.24886978355857),
+            Offset(36.59673961336577, 30.16713606026377),
+            Offset(35.26901818749416, 32.58105797429066),
+            Offset(33.66938906523204, 34.56713290494057),
+            Offset(32.196778918797094, 35.8827095523761),
+            Offset(30.969894470496282, 36.721466129987085),
+            Offset(29.989349224706995, 37.25388702486493),
+            Offset(29.223528593231507, 37.59010302049878),
+            Offset(28.651601378627003, 37.79719553439594),
+            Offset(28.27745500043001, 37.91773612047938),
+            Offset(28.069390261744058, 37.979987943400474),
+            Offset(28.000229522301836, 37.99993442016443),
+            Offset(28.0, 38.0),
+          ],
+          <Offset>[
+            Offset(37.984375, 24.0),
+            Offset(37.98179511896882, 24.268606388242382),
+            Offset(37.92663369548548, 25.26958881281347),
+            Offset(37.702366207906195, 26.86162526614268),
+            Offset(37.62294586290445, 28.407471142252255),
+            Offset(38.43944238184115, 29.541526367903558),
+            Offset(38.93163276984633, 31.5056762828673),
+            Offset(38.80537374713073, 33.4174700441868),
+            Offset(38.35814295213548, 34.94327332096457),
+            Offset(37.78610517302408, 36.076173087300646),
+            Offset(37.186112675124534, 36.8807750697281),
+            Offset(36.64281432187422, 37.42234130182257),
+            Offset(36.275874837729305, 37.7587389308906),
+            Offset(36.06929185625662, 37.94030824940746),
+            Offset(36.00022952122672, 37.9998032642562),
+            Offset(36.0, 38.0),
+          ],
+          <Offset>[
+            Offset(37.984375, 24.0),
+            Offset(37.98179511896882, 24.268606388242382),
+            Offset(37.92663369548548, 25.26958881281347),
+            Offset(37.702366207906195, 26.86162526614268),
+            Offset(37.62294586290445, 28.407471142252255),
+            Offset(38.43944238184115, 29.541526367903558),
+            Offset(38.93163276984633, 31.5056762828673),
+            Offset(38.80537374713073, 33.4174700441868),
+            Offset(38.35814295213548, 34.94327332096457),
+            Offset(37.78610517302408, 36.076173087300646),
+            Offset(37.186112675124534, 36.8807750697281),
+            Offset(36.64281432187422, 37.42234130182257),
+            Offset(36.275874837729305, 37.7587389308906),
+            Offset(36.06929185625662, 37.94030824940746),
+            Offset(36.00022952122672, 37.9998032642562),
+            Offset(36.0, 38.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(37.984375, 24.0),
+            Offset(37.98179511896882, 24.268606388242382),
+            Offset(37.92663369548548, 25.26958881281347),
+            Offset(37.702366207906195, 26.86162526614268),
+            Offset(37.62294586290445, 28.407471142252255),
+            Offset(38.43944238184115, 29.541526367903558),
+            Offset(38.93163276984633, 31.5056762828673),
+            Offset(38.80537374713073, 33.4174700441868),
+            Offset(38.35814295213548, 34.94327332096457),
+            Offset(37.78610517302408, 36.076173087300646),
+            Offset(37.186112675124534, 36.8807750697281),
+            Offset(36.64281432187422, 37.42234130182257),
+            Offset(36.275874837729305, 37.7587389308906),
+            Offset(36.06929185625662, 37.94030824940746),
+            Offset(36.00022952122672, 37.9998032642562),
+            Offset(36.0, 38.0),
+          ],
+          <Offset>[
+            Offset(16.046875, 10.039062500000002),
+            Offset(16.316498427194905, 9.888877552610037),
+            Offset(17.35016869491465, 9.372654593335355),
+            Offset(19.411307079839695, 8.531523285452844),
+            Offset(22.58136524050546, 7.589125591565864),
+            Offset(25.499178877175954, 6.946027752856988),
+            Offset(28.464059662259196, 6.878006546805963),
+            Offset(30.817518246129985, 7.278084288616373),
+            Offset(32.55729037951755, 7.852250285245777),
+            Offset(33.81517761778539, 8.446339493014325),
+            Offset(34.71226086018563, 8.994748419446736),
+            Offset(35.33082450786742, 9.453096000457315),
+            Offset(35.71938467416858, 9.764269500343072),
+            Offset(35.93041292728106, 9.940652668613495),
+            Offset(35.999770475547926, 9.999803268019111),
+            Offset(36.0, 10.0),
+          ],
+          <Offset>[
+            Offset(16.046875, 10.039062500000002),
+            Offset(16.316498427194905, 9.888877552610037),
+            Offset(17.35016869491465, 9.372654593335355),
+            Offset(19.411307079839695, 8.531523285452844),
+            Offset(22.58136524050546, 7.589125591565864),
+            Offset(25.499178877175954, 6.946027752856988),
+            Offset(28.464059662259196, 6.878006546805963),
+            Offset(30.817518246129985, 7.278084288616373),
+            Offset(32.55729037951755, 7.852250285245777),
+            Offset(33.81517761778539, 8.446339493014325),
+            Offset(34.71226086018563, 8.994748419446736),
+            Offset(35.33082450786742, 9.453096000457315),
+            Offset(35.71938467416858, 9.764269500343072),
+            Offset(35.93041292728106, 9.940652668613495),
+            Offset(35.999770475547926, 9.999803268019111),
+            Offset(36.0, 10.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(37.984375, 24.0),
+            Offset(37.98179511896882, 24.268606388242382),
+            Offset(37.925946696573504, 25.277091251817644),
+            Offset(37.50567105053561, 27.636114300999704),
+            Offset(35.57053336387648, 31.926800978315658),
+            Offset(32.09859399311199, 35.6205895806324),
+            Offset(28.407145360613207, 37.6285895270458),
+            Offset(25.588184090469714, 38.34794906057932),
+            Offset(23.581645988882627, 38.49965893899394),
+            Offset(22.19259327642332, 38.43160096243417),
+            Offset(21.26094464377359, 38.29943245748053),
+            Offset(20.660388435379787, 38.17204976696931),
+            Offset(20.279035163130715, 38.07673331006816),
+            Offset(20.069488667231496, 38.01966763739349),
+            Offset(20.000229523376955, 38.00006557607266),
+            Offset(20.0, 38.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(37.984375, 24.0),
+            Offset(37.98179511896882, 24.268606388242382),
+            Offset(37.925946696573504, 25.277091251817644),
+            Offset(37.50567105053561, 27.636114300999704),
+            Offset(35.57053336387648, 31.926800978315658),
+            Offset(32.09859399311199, 35.6205895806324),
+            Offset(28.407145360613207, 37.6285895270458),
+            Offset(25.588184090469714, 38.34794906057932),
+            Offset(23.581645988882627, 38.49965893899394),
+            Offset(22.19259327642332, 38.43160096243417),
+            Offset(21.26094464377359, 38.29943245748053),
+            Offset(20.660388435379787, 38.17204976696931),
+            Offset(20.279035163130715, 38.07673331006816),
+            Offset(20.069488667231496, 38.01966763739349),
+            Offset(20.000229523376955, 38.00006557607266),
+            Offset(20.0, 38.0),
+          ],
+          <Offset>[
+            Offset(16.046875, 24.0),
+            Offset(16.048342217256838, 23.847239495401816),
+            Offset(16.077003403397015, 23.276381983287706),
+            Offset(15.949709233004938, 22.161597410697688),
+            Offset(15.286645897801982, 20.097587433416958),
+            Offset(14.613379075880687, 17.38240172943261),
+            Offset(15.05547931015969, 14.678821069268237),
+            Offset(16.052638481209218, 12.785906431713748),
+            Offset(17.100807279436804, 11.57229396942536),
+            Offset(18.02357718638153, 10.831688995790898),
+            Offset(18.7768651463943, 10.414316916074366),
+            Offset(19.34839862137299, 10.202804465604057),
+            Offset(19.722544999569994, 10.082263879520628),
+            Offset(19.93060973825594, 10.02001205659953),
+            Offset(19.99977047769816, 10.000065579835564),
+            Offset(19.999999999999996, 10.000000000000004),
+          ],
+          <Offset>[
+            Offset(16.046875, 24.0),
+            Offset(16.048342217256838, 23.847239495401816),
+            Offset(16.077003403397015, 23.276381983287706),
+            Offset(15.949709233004938, 22.161597410697688),
+            Offset(15.286645897801982, 20.097587433416958),
+            Offset(14.613379075880687, 17.38240172943261),
+            Offset(15.05547931015969, 14.678821069268237),
+            Offset(16.052638481209218, 12.785906431713748),
+            Offset(17.100807279436804, 11.57229396942536),
+            Offset(18.02357718638153, 10.831688995790898),
+            Offset(18.7768651463943, 10.414316916074366),
+            Offset(19.34839862137299, 10.202804465604057),
+            Offset(19.722544999569994, 10.082263879520628),
+            Offset(19.93060973825594, 10.02001205659953),
+            Offset(19.99977047769816, 10.000065579835564),
+            Offset(19.999999999999996, 10.000000000000004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(16.046875, 24.0),
+            Offset(16.048342217256838, 23.847239495401816),
+            Offset(16.077003403397015, 23.276381983287706),
+            Offset(15.949709233004938, 22.161597410697688),
+            Offset(15.286645897801982, 20.097587433416958),
+            Offset(14.613379075880687, 17.38240172943261),
+            Offset(15.05547931015969, 14.678821069268237),
+            Offset(16.052638481209218, 12.785906431713748),
+            Offset(17.100807279436804, 11.57229396942536),
+            Offset(18.02357718638153, 10.831688995790898),
+            Offset(18.7768651463943, 10.414316916074366),
+            Offset(19.34839862137299, 10.202804465604057),
+            Offset(19.722544999569994, 10.082263879520628),
+            Offset(19.93060973825594, 10.02001205659953),
+            Offset(19.99977047769816, 10.000065579835564),
+            Offset(19.999999999999996, 10.000000000000004),
+          ],
+          <Offset>[
+            Offset(16.046875, 37.9609375),
+            Offset(15.780186007318768, 37.8056014381936),
+            Offset(14.804181611349989, 37.17635815383272),
+            Offset(12.58645896485513, 35.404427018450995),
+            Offset(9.018132804607959, 30.846384357181606),
+            Offset(6.898003468953149, 24.77924409968033),
+            Offset(6.909142662679017, 19.41817896962528),
+            Offset(7.8963535446158275, 15.828489066607908),
+            Offset(9.032572660968736, 13.51414484459833),
+            Offset(10.02873270326728, 12.039324560997336),
+            Offset(10.80405338206586, 11.124555975719801),
+            Offset(11.357185678125777, 10.577658698177427),
+            Offset(11.724125162270699, 10.241261069109406),
+            Offset(11.930708143743377, 10.059691750592545),
+            Offset(11.999770478773279, 10.000196735743792),
+            Offset(11.999999999999996, 10.000000000000004),
+          ],
+          <Offset>[
+            Offset(16.046875, 37.9609375),
+            Offset(15.780186007318768, 37.8056014381936),
+            Offset(14.804181611349989, 37.17635815383272),
+            Offset(12.58645896485513, 35.404427018450995),
+            Offset(9.018132804607959, 30.846384357181606),
+            Offset(6.898003468953149, 24.77924409968033),
+            Offset(6.909142662679017, 19.41817896962528),
+            Offset(7.8963535446158275, 15.828489066607908),
+            Offset(9.032572660968736, 13.51414484459833),
+            Offset(10.02873270326728, 12.039324560997336),
+            Offset(10.80405338206586, 11.124555975719801),
+            Offset(11.357185678125777, 10.577658698177427),
+            Offset(11.724125162270699, 10.241261069109406),
+            Offset(11.930708143743377, 10.059691750592545),
+            Offset(11.999770478773279, 10.000196735743792),
+            Offset(11.999999999999996, 10.000000000000004),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(16.046875, 37.9609375),
+            Offset(15.780186007318768, 37.8056014381936),
+            Offset(14.804181611349989, 37.17635815383272),
+            Offset(12.58645896485513, 35.404427018450995),
+            Offset(9.018132804607959, 30.846384357181606),
+            Offset(6.898003468953149, 24.77924409968033),
+            Offset(6.909142662679017, 19.41817896962528),
+            Offset(7.8963535446158275, 15.828489066607908),
+            Offset(9.032572660968736, 13.51414484459833),
+            Offset(10.02873270326728, 12.039324560997336),
+            Offset(10.80405338206586, 11.124555975719801),
+            Offset(11.357185678125777, 10.577658698177427),
+            Offset(11.724125162270699, 10.241261069109406),
+            Offset(11.930708143743377, 10.059691750592545),
+            Offset(11.999770478773279, 10.000196735743792),
+            Offset(11.999999999999996, 10.000000000000004),
+          ],
+          <Offset>[
+            Offset(37.984375, 24.0),
+            Offset(37.98179511896882, 24.268606388242382),
+            Offset(37.92560319713213, 25.28084247141449),
+            Offset(37.40732347184997, 28.02335881836519),
+            Offset(34.544327114357955, 33.68646589629262),
+            Offset(28.928169798750567, 38.66012118703334),
+            Offset(23.144901655998915, 40.69004614911907),
+            Offset(18.979589262136074, 40.81318856876862),
+            Offset(16.193397507242462, 40.27785174801669),
+            Offset(14.395837328112165, 39.60931489999756),
+            Offset(13.298360561885538, 39.008760408250765),
+            Offset(12.669175492132574, 38.546903999542685),
+            Offset(12.280615325831423, 38.23573049965694),
+            Offset(12.069587072718935, 38.05934733138651),
+            Offset(12.000229524452074, 38.00019673198088),
+            Offset(12.0, 38.0),
+          ],
+          <Offset>[
+            Offset(37.984375, 24.0),
+            Offset(37.98179511896882, 24.268606388242382),
+            Offset(37.92560319713213, 25.28084247141449),
+            Offset(37.40732347184997, 28.02335881836519),
+            Offset(34.544327114357955, 33.68646589629262),
+            Offset(28.928169798750567, 38.66012118703334),
+            Offset(23.144901655998915, 40.69004614911907),
+            Offset(18.979589262136074, 40.81318856876862),
+            Offset(16.193397507242462, 40.27785174801669),
+            Offset(14.395837328112165, 39.60931489999756),
+            Offset(13.298360561885538, 39.008760408250765),
+            Offset(12.669175492132574, 38.546903999542685),
+            Offset(12.280615325831423, 38.23573049965694),
+            Offset(12.069587072718935, 38.05934733138651),
+            Offset(12.000229524452074, 38.00019673198088),
+            Offset(12.0, 38.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(37.984375, 24.0),
+            Offset(37.98179511896882, 24.268606388242382),
+            Offset(37.92560319713213, 25.28084247141449),
+            Offset(37.40732347184997, 28.02335881836519),
+            Offset(34.544327114357955, 33.68646589629262),
+            Offset(28.928169798750567, 38.66012118703334),
+            Offset(23.144901655998915, 40.69004614911907),
+            Offset(18.979589262136074, 40.81318856876862),
+            Offset(16.193397507242462, 40.27785174801669),
+            Offset(14.395837328112165, 39.60931489999756),
+            Offset(13.298360561885538, 39.008760408250765),
+            Offset(12.669175492132574, 38.546903999542685),
+            Offset(12.280615325831423, 38.23573049965694),
+            Offset(12.069587072718935, 38.05934733138651),
+            Offset(12.000229524452074, 38.00019673198088),
+            Offset(12.0, 38.0),
+          ],
+          <Offset>[
+            Offset(37.984375, 24.0),
+            Offset(37.98179511896882, 24.268606388242382),
+            Offset(37.92594669656839, 25.27709125187348),
+            Offset(37.50567105054841, 27.636114300949302),
+            Offset(35.57053336389663, 31.9268009782811),
+            Offset(32.09859399309755, 35.62058958064624),
+            Offset(28.407145360613207, 37.628589527045804),
+            Offset(25.588184090469714, 38.34794906057932),
+            Offset(23.58164598888166, 38.49965893899417),
+            Offset(22.192593276429257, 38.43160096243327),
+            Offset(21.260944643778565, 38.29943245748009),
+            Offset(20.660388435379787, 38.17204976696931),
+            Offset(20.279035163130715, 38.07673331006816),
+            Offset(20.069488667231496, 38.01966763739349),
+            Offset(20.000229523376955, 38.00006557607266),
+            Offset(20.0, 38.0),
+          ],
+          <Offset>[
+            Offset(37.984375, 24.0),
+            Offset(37.98179511896882, 24.268606388242382),
+            Offset(37.92594669656839, 25.27709125187348),
+            Offset(37.50567105054841, 27.636114300949302),
+            Offset(35.57053336389663, 31.9268009782811),
+            Offset(32.09859399309755, 35.62058958064624),
+            Offset(28.407145360613207, 37.628589527045804),
+            Offset(25.588184090469714, 38.34794906057932),
+            Offset(23.58164598888166, 38.49965893899417),
+            Offset(22.192593276429257, 38.43160096243327),
+            Offset(21.260944643778565, 38.29943245748009),
+            Offset(20.660388435379787, 38.17204976696931),
+            Offset(20.279035163130715, 38.07673331006816),
+            Offset(20.069488667231496, 38.01966763739349),
+            Offset(20.000229523376955, 38.00006557607266),
+            Offset(20.0, 38.0),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.733333333333,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(36.21875, 24.387283325200002),
+            Offset(36.858953419818775, 24.63439009154731),
+            Offset(37.42714268809582, 25.618428032998864),
+            Offset(37.46673246436919, 27.957602694496682),
+            Offset(35.51445214909996, 31.937043103050268),
+            Offset(32.888668544302234, 34.79679735028506),
+            Offset(30.100083850883422, 36.58444430738925),
+            Offset(27.884884986535624, 37.434542424473584),
+            Offset(26.23678799810123, 37.80492814052796),
+            Offset(25.03902259291319, 37.946314694750235),
+            Offset(24.185908910024594, 37.98372980970255),
+            Offset(23.59896217337824, 37.97921421880389),
+            Offset(23.221743554700737, 37.96329396736102),
+            Offset(23.013561704380457, 37.95013265178958),
+            Offset(22.94461033630511, 37.9450856638228),
+            Offset(22.9443817139, 37.945068359375),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(36.21875, 24.387283325200002),
+            Offset(36.858953419818775, 24.63439009154731),
+            Offset(37.42714268809582, 25.618428032998864),
+            Offset(37.46673246436919, 27.957602694496682),
+            Offset(35.51445214909996, 31.937043103050268),
+            Offset(32.888668544302234, 34.79679735028506),
+            Offset(30.100083850883422, 36.58444430738925),
+            Offset(27.884884986535624, 37.434542424473584),
+            Offset(26.23678799810123, 37.80492814052796),
+            Offset(25.03902259291319, 37.946314694750235),
+            Offset(24.185908910024594, 37.98372980970255),
+            Offset(23.59896217337824, 37.97921421880389),
+            Offset(23.221743554700737, 37.96329396736102),
+            Offset(23.013561704380457, 37.95013265178958),
+            Offset(22.94461033630511, 37.9450856638228),
+            Offset(22.9443817139, 37.945068359375),
+          ],
+          <Offset>[
+            Offset(36.1819000244141, 23.597152709966),
+            Offset(36.8358384608093, 23.843669618675563),
+            Offset(37.45961204802207, 24.827964901265894),
+            Offset(37.71106940406011, 26.916549745564488),
+            Offset(36.67279396166709, 30.08280087402087),
+            Offset(34.51215067847019, 33.33246277147643),
+            Offset(32.022419367141104, 35.54300484126963),
+            Offset(29.955608739426065, 36.73306317469314),
+            Offset(28.376981306736234, 37.3582262261251),
+            Offset(27.209745307333925, 37.68567529681684),
+            Offset(26.368492376458054, 37.856060664218916),
+            Offset(25.784980483216092, 37.94324273411291),
+            Offset(25.407936267815487, 37.98634651128109),
+            Offset(25.199167384595825, 38.0057906185826),
+            Offset(25.129914160588893, 38.01154763962766),
+            Offset(25.129684448280003, 38.0115661621094),
+          ],
+          <Offset>[
+            Offset(36.1819000244141, 23.597152709966),
+            Offset(36.8358384608093, 23.843669618675563),
+            Offset(37.45961204802207, 24.827964901265894),
+            Offset(37.71106940406011, 26.916549745564488),
+            Offset(36.67279396166709, 30.08280087402087),
+            Offset(34.51215067847019, 33.33246277147643),
+            Offset(32.022419367141104, 35.54300484126963),
+            Offset(29.955608739426065, 36.73306317469314),
+            Offset(28.376981306736234, 37.3582262261251),
+            Offset(27.209745307333925, 37.68567529681684),
+            Offset(26.368492376458054, 37.856060664218916),
+            Offset(25.784980483216092, 37.94324273411291),
+            Offset(25.407936267815487, 37.98634651128109),
+            Offset(25.199167384595825, 38.0057906185826),
+            Offset(25.129914160588893, 38.01154763962766),
+            Offset(25.129684448280003, 38.0115661621094),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(36.1819000244141, 23.597152709966),
+            Offset(36.8358384608093, 23.843669618675563),
+            Offset(37.45961204802207, 24.827964901265894),
+            Offset(37.71106940406011, 26.916549745564488),
+            Offset(36.67279396166709, 30.08280087402087),
+            Offset(34.51215067847019, 33.33246277147643),
+            Offset(32.022419367141104, 35.54300484126963),
+            Offset(29.955608739426065, 36.73306317469314),
+            Offset(28.376981306736234, 37.3582262261251),
+            Offset(27.209745307333925, 37.68567529681684),
+            Offset(26.368492376458054, 37.856060664218916),
+            Offset(25.784980483216092, 37.94324273411291),
+            Offset(25.407936267815487, 37.98634651128109),
+            Offset(25.199167384595825, 38.0057906185826),
+            Offset(25.129914160588893, 38.01154763962766),
+            Offset(25.129684448280003, 38.0115661621094),
+          ],
+          <Offset>[
+            Offset(16.1149902344141, 22.955383300786004),
+            Offset(15.997629933953313, 22.801455805116497),
+            Offset(15.966446205406928, 22.215379763234004),
+            Offset(16.088459709151728, 20.876736411055298),
+            Offset(16.769441289779344, 18.37084947089115),
+            Offset(18.595653610551377, 16.59990844352802),
+            Offset(20.48764499639903, 15.536450078720307),
+            Offset(21.968961727208672, 15.064497861016925),
+            Offset(23.06110116092593, 14.884804779309462),
+            Offset(23.849967628988242, 14.837805654268031),
+            Offset(24.40943781230773, 14.84572910499329),
+            Offset(24.793207208324446, 14.870972819299066),
+            Offset(25.03935354219434, 14.895712045654406),
+            Offset(25.1750322217718, 14.912227213496571),
+            Offset(25.21994388130627, 14.918147112632923),
+            Offset(25.220092773475297, 14.9181671142094),
+          ],
+          <Offset>[
+            Offset(16.170043945314102, 22.942321777349),
+            Offset(16.055083258838646, 22.789495616149246),
+            Offset(16.026762188208856, 22.207786731939372),
+            Offset(16.150920741832245, 20.879123319500057),
+            Offset(16.82882476693832, 18.390360508490243),
+            Offset(18.647384744725734, 16.634993592875272),
+            Offset(20.52967353640347, 15.58271755944683),
+            Offset(22.002563841255288, 15.117204368008782),
+            Offset(23.0881035089048, 14.941178098808251),
+            Offset(23.872012376061566, 14.896295884855345),
+            Offset(24.42787166552447, 14.90545574061985),
+            Offset(24.80911858591767, 14.931420366898372),
+            Offset(25.053627357583, 14.956567087696417),
+            Offset(25.188396770682292, 14.973288385939487),
+            Offset(25.233006406883348, 14.979273607487709),
+            Offset(25.233154296913, 14.9792938232094),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(16.225097656251602, 22.9292602539115),
+            Offset(16.112536583755883, 22.7775354271821),
+            Offset(16.087078170937534, 22.200193700637527),
+            Offset(16.213381774594694, 20.88151022796511),
+            Offset(16.888208244083728, 18.409871546081646),
+            Offset(18.699115878889145, 16.67007874221141),
+            Offset(20.571702076399895, 15.628985040159975),
+            Offset(22.03616595529626, 15.16991087498609),
+            Offset(23.115105856879826, 14.997551418291916),
+            Offset(23.894057123132363, 14.954786115427265),
+            Offset(24.446305518739628, 14.965182376230889),
+            Offset(24.825029963509966, 14.9918679144821),
+            Offset(25.067901172971148, 15.017422129722831),
+            Offset(25.201761319592507, 15.034349558366799),
+            Offset(25.24606893246022, 15.040400102326899),
+            Offset(25.2462158203505, 15.0404205321938),
+          ],
+          <Offset>[
+            Offset(16.172653198243793, 25.050704956059),
+            Offset(16.017298096111325, 24.897541931224776),
+            Offset(15.837305455486472, 24.307642370134865),
+            Offset(15.617771431142284, 23.034739327639596),
+            Offset(15.534079923477577, 20.72510957725349),
+            Offset(16.76065281331448, 18.52381863579275),
+            Offset(18.25163791556585, 16.97482787617967),
+            Offset(19.521978435885586, 16.104176237124552),
+            Offset(20.506617505527394, 15.621874388004521),
+            Offset(21.24147683283453, 15.352037236477383),
+            Offset(21.774425023577333, 15.199799658679147),
+            Offset(22.14565785051594, 15.114161535583197),
+            Offset(22.386204205776483, 15.067342323943635),
+            Offset(22.519618086537456, 15.044265557010121),
+            Offset(22.563909453457644, 15.037056623787358),
+            Offset(22.564056396523, 15.0370330810219),
+          ],
+          <Offset>[
+            Offset(16.172653198243804, 25.050704956059),
+            Offset(16.017298096111343, 24.89754193122478),
+            Offset(15.837305455486483, 24.307642370134865),
+            Offset(15.617771431142284, 23.034739327639596),
+            Offset(15.534079923477577, 20.72510957725349),
+            Offset(16.76065281331448, 18.52381863579275),
+            Offset(18.25163791556585, 16.97482787617967),
+            Offset(19.521978435885586, 16.104176237124552),
+            Offset(20.506617505527394, 15.621874388004521),
+            Offset(21.24147683283453, 15.352037236477383),
+            Offset(21.774425023577333, 15.199799658679147),
+            Offset(22.14565785051594, 15.114161535583197),
+            Offset(22.386204205776483, 15.067342323943635),
+            Offset(22.519618086537456, 15.044265557010121),
+            Offset(22.563909453457644, 15.037056623787358),
+            Offset(22.564056396523, 15.0370330810219),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(16.172653198243804, 25.050704956059),
+            Offset(16.017298096111343, 24.89754193122478),
+            Offset(15.837305455486483, 24.307642370134865),
+            Offset(15.617771431142284, 23.034739327639596),
+            Offset(15.534079923477577, 20.72510957725349),
+            Offset(16.76065281331448, 18.52381863579275),
+            Offset(18.25163791556585, 16.97482787617967),
+            Offset(19.521978435885586, 16.104176237124552),
+            Offset(20.506617505527394, 15.621874388004521),
+            Offset(21.24147683283453, 15.352037236477383),
+            Offset(21.774425023577333, 15.199799658679147),
+            Offset(22.14565785051594, 15.114161535583197),
+            Offset(22.386204205776483, 15.067342323943635),
+            Offset(22.519618086537456, 15.044265557010121),
+            Offset(22.563909453457644, 15.037056623787358),
+            Offset(22.564056396523, 15.0370330810219),
+          ],
+          <Offset>[
+            Offset(36.218750000043805, 24.387283325200002),
+            Offset(36.858953419751415, 24.634390091546017),
+            Offset(37.42714268811728, 25.61842803300083),
+            Offset(37.46673246430412, 27.95760269448635),
+            Offset(35.51445214905712, 31.937043103018333),
+            Offset(32.88866854426982, 34.79679735024258),
+            Offset(30.100083850861907, 36.584444307340334),
+            Offset(27.884884986522685, 37.434542424421736),
+            Offset(26.23678799809464, 37.80492814047493),
+            Offset(25.039022592911195, 37.94631469469684),
+            Offset(24.185908910025862, 37.983729809649134),
+            Offset(23.59896217338175, 37.97921421875057),
+            Offset(23.221743554705682, 37.96329396730781),
+            Offset(23.0135617043862, 37.95013265173645),
+            Offset(22.94461033631111, 37.9450856637697),
+            Offset(22.944381713906004, 37.9450683593219),
+          ],
+          <Offset>[
+            Offset(36.218750000043805, 24.387283325200002),
+            Offset(36.858953419751415, 24.634390091546017),
+            Offset(37.42714268811728, 25.61842803300083),
+            Offset(37.46673246430412, 27.95760269448635),
+            Offset(35.51445214905712, 31.937043103018333),
+            Offset(32.88866854426982, 34.79679735024258),
+            Offset(30.100083850861907, 36.584444307340334),
+            Offset(27.884884986522685, 37.434542424421736),
+            Offset(26.23678799809464, 37.80492814047493),
+            Offset(25.039022592911195, 37.94631469469684),
+            Offset(24.185908910025862, 37.983729809649134),
+            Offset(23.59896217338175, 37.97921421875057),
+            Offset(23.221743554705682, 37.96329396730781),
+            Offset(23.0135617043862, 37.95013265173645),
+            Offset(22.94461033631111, 37.9450856637697),
+            Offset(22.944381713906004, 37.9450683593219),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+  ],
+);
diff --git a/lib/src/material/animated_icons/data/search_ellipsis.g.dart b/lib/src/material/animated_icons/data/search_ellipsis.g.dart
new file mode 100644
index 0000000..93ae867
--- /dev/null
+++ b/lib/src/material/animated_icons/data/search_ellipsis.g.dart
@@ -0,0 +1,5267 @@
+// Copyright 2014 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.
+
+// AUTOGENERATED FILE DO NOT EDIT!
+// This file was generated by vitool.
+part of material_animated_icons;
+const _AnimatedIconData _$search_ellipsis = _AnimatedIconData(
+  Size(96.0, 96.0),
+  <_PathFrames>[
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(56.63275314854877, 53.416307677358),
+            Offset(56.63275314854877, 53.416307677358),
+            Offset(56.63275314854877, 53.416307677358),
+            Offset(56.63275314854877, 53.416307677358),
+            Offset(56.63275314854877, 53.416307677358),
+            Offset(56.63275314854877, 53.416307677358),
+            Offset(56.63275314854877, 53.416307677358),
+            Offset(56.63275314854877, 53.416307677358),
+            Offset(56.63275314854877, 53.416307677358),
+            Offset(56.63275314854877, 53.416307677358),
+            Offset(57.069606004928445, 53.86609044856987),
+            Offset(59.09460654800288, 55.971023159415935),
+            Offset(60.89239811736094, 57.88133187802052),
+            Offset(62.39435194501161, 59.538230172598404),
+            Offset(63.48463970399173, 60.8425011369294),
+            Offset(63.932061433030945, 61.59610215538352),
+            Offset(63.07663991080549, 61.23370607747795),
+            Offset(59.89718017699878, 58.88057174806894),
+            Offset(61.21880612019878, 60.239014522068935),
+            Offset(61.09896916359878, 60.11473915976894),
+            Offset(60.97913220709878, 59.99046379736894),
+            Offset(60.85929525059878, 59.866188435068935),
+            Offset(60.73945829409878, 59.74191307276894),
+            Offset(60.61962133759878, 59.61763771046894),
+            Offset(60.49978438099878, 59.49336234816894),
+            Offset(60.37994742449878, 59.36908698586894),
+            Offset(60.26011046799878, 59.24481162346894),
+            Offset(60.140273511498776, 59.12053626116894),
+            Offset(60.02043655499878, 58.99626089886894),
+            Offset(59.90059959839878, 58.871985536568936),
+            Offset(59.78076264189878, 58.74771017426894),
+            Offset(59.66092568539878, 58.62343481186894),
+            Offset(59.54108872889878, 58.499159449568936),
+            Offset(59.42125177229878, 58.37488408726894),
+            Offset(59.30141481579878, 58.250608724968934),
+            Offset(59.18157785929878, 58.12633336266894),
+            Offset(59.06174090279878, 58.00205800026894),
+            Offset(58.94190394629878, 57.877782637968934),
+            Offset(58.822066989698776, 57.75350727566894),
+            Offset(58.70223003319878, 57.62923191336894),
+            Offset(58.58239307669878, 57.504956551068936),
+            Offset(58.46255612019878, 57.38068118876894),
+            Offset(58.34271916359878, 57.25640582636894),
+            Offset(58.22288220709878, 57.132130464068936),
+            Offset(58.10304525059878, 57.00785510176894),
+            Offset(57.98320829409878, 56.883579739468935),
+            Offset(57.86337133759878, 56.75930437716894),
+            Offset(57.74353438099878, 56.63502901476894),
+            Offset(57.62369742449878, 56.510753652468935),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(60.57565945470678, 48.849785482535665),
+            Offset(60.57565945470678, 48.849785482535665),
+            Offset(60.57565945470678, 48.849785482535665),
+            Offset(60.57565945470678, 48.849785482535665),
+            Offset(60.57565945470678, 48.849785482535665),
+            Offset(60.57565945470678, 48.849785482535665),
+            Offset(60.57565945470678, 48.849785482535665),
+            Offset(60.57565945470678, 48.849785482535665),
+            Offset(60.57565945470678, 48.849785482535665),
+            Offset(60.57565945470678, 48.849785482535665),
+            Offset(61.01177894139154, 49.30041761427725),
+            Offset(63.01014446522872, 51.43619797836744),
+            Offset(64.73600505254888, 53.42981438943695),
+            Offset(66.10705023046606, 55.238326039262105),
+            Offset(66.98436034991653, 56.78925950135372),
+            Offset(67.09064385091588, 57.93795367416795),
+            Offset(65.63435166794956, 58.2714628487421),
+            Offset(61.39070529296772, 57.15082849245442),
+            Offset(62.71233123616772, 58.50927126645442),
+            Offset(62.592494279567724, 58.38499590415442),
+            Offset(62.47265732306772, 58.260720541754424),
+            Offset(62.35282036656772, 58.13644517945442),
+            Offset(62.232983410067725, 58.01216981715442),
+            Offset(62.11314645356772, 57.887894454854425),
+            Offset(61.993309496967726, 57.76361909255442),
+            Offset(61.87347254046772, 57.639343730254424),
+            Offset(61.75363558396772, 57.515068367854425),
+            Offset(61.63379862746772, 57.39079300555442),
+            Offset(61.513961670967724, 57.266517643254424),
+            Offset(61.39412471436772, 57.14224228095442),
+            Offset(61.274287757867725, 57.01796691865442),
+            Offset(61.15445080136772, 56.893691556254424),
+            Offset(61.03461384486772, 56.76941619395442),
+            Offset(60.914776888267724, 56.64514083165442),
+            Offset(60.79493993176772, 56.52086546935442),
+            Offset(60.67510297526772, 56.39659010705442),
+            Offset(60.555266018767725, 56.27231474465442),
+            Offset(60.43542906226772, 56.14803938235442),
+            Offset(60.31559210566772, 56.02376402005442),
+            Offset(60.195755149167724, 55.899488657754425),
+            Offset(60.07591819266772, 55.77521329545442),
+            Offset(59.95608123616772, 55.650937933154424),
+            Offset(59.83624427956772, 55.526662570754425),
+            Offset(59.71640732306772, 55.40238720845442),
+            Offset(59.596570366567725, 55.278111846154424),
+            Offset(59.47673341006772, 55.15383648385442),
+            Offset(59.35689645356772, 55.02956112155442),
+            Offset(59.237059496967724, 54.905285759154424),
+            Offset(59.11722254046772, 54.78101039685442),
+          ],
+          <Offset>[
+            Offset(62.94942712071989, 42.915335621700216),
+            Offset(62.94942712071989, 42.915335621700216),
+            Offset(62.94942712071989, 42.915335621700216),
+            Offset(62.94942712071989, 42.915335621700216),
+            Offset(62.94942712071989, 42.915335621700216),
+            Offset(62.94942712071989, 42.915335621700216),
+            Offset(62.94942712071989, 42.915335621700216),
+            Offset(62.94942712071989, 42.915335621700216),
+            Offset(62.94942712071989, 42.915335621700216),
+            Offset(62.94942712071989, 42.915335621700216),
+            Offset(63.38510509316773, 43.36707154474344),
+            Offset(65.36743540273916, 45.5429401518535),
+            Offset(67.04999102004787, 47.644819547939385),
+            Offset(68.34222460676555, 49.6503611948963),
+            Offset(69.09131472932955, 51.52184630725002),
+            Offset(68.99222107132888, 53.18398603334939),
+            Offset(67.17418370976294, 54.42186283224286),
+            Offset(62.28985971191208, 54.90293081789554),
+            Offset(63.61148565511208, 56.261373591895534),
+            Offset(63.49164869851208, 56.13709822959554),
+            Offset(63.37181174201208, 56.01282286719554),
+            Offset(63.251974785512076, 55.88854750489554),
+            Offset(63.13213782901208, 55.764272142595544),
+            Offset(63.01230087251208, 55.63999678029555),
+            Offset(62.89246391591208, 55.515721417995536),
+            Offset(62.77262695941208, 55.39144605569554),
+            Offset(62.65279000291208, 55.26717069329554),
+            Offset(62.532953046412075, 55.14289533099554),
+            Offset(62.41311608991208, 55.018619968695546),
+            Offset(62.293279133312076, 54.894344606395535),
+            Offset(62.17344217681208, 54.77006924409554),
+            Offset(62.05360522031208, 54.64579388169554),
+            Offset(61.93376826381208, 54.52151851939554),
+            Offset(61.81393130721208, 54.397243157095545),
+            Offset(61.69409435071208, 54.272967794795534),
+            Offset(61.574257394212076, 54.14869243249554),
+            Offset(61.45442043771208, 54.02441707009554),
+            Offset(61.33458348121208, 53.90014170779554),
+            Offset(61.214746524612075, 53.775866345495544),
+            Offset(61.09490956811208, 53.65159098319555),
+            Offset(60.97507261161208, 53.527315620895536),
+            Offset(60.855235655112075, 53.40304025859554),
+            Offset(60.73539869851208, 53.27876489619554),
+            Offset(60.615561742012076, 53.15448953389554),
+            Offset(60.49572478551208, 53.030214171595546),
+            Offset(60.37588782901208, 52.905938809295534),
+            Offset(60.25605087251208, 52.78166344699554),
+            Offset(60.13621391591208, 52.65738808459554),
+            Offset(60.01637695941208, 52.53311272229554),
+          ],
+          <Offset>[
+            Offset(62.94942712071989, 36.41758712071989),
+            Offset(62.94942712071989, 36.41758712071989),
+            Offset(62.94942712071989, 36.41758712071989),
+            Offset(62.94942712071989, 36.41758712071989),
+            Offset(62.94942712071989, 36.41758712071989),
+            Offset(62.94942712071989, 36.41758712071989),
+            Offset(62.94942712071989, 36.41758712071989),
+            Offset(62.94942712071989, 36.41758712071989),
+            Offset(62.94942712071989, 36.41758712071989),
+            Offset(62.94942712071989, 36.41758712071989),
+            Offset(63.38510509316773, 36.87053160705772),
+            Offset(65.36743540273916, 39.09029363766916),
+            Offset(67.04999102004787, 41.31071235333787),
+            Offset(68.34222460676555, 43.531986106645554),
+            Offset(69.09131472932955, 45.754449667519545),
+            Offset(68.99222107132888, 47.97877125586888),
+            Offset(67.17418370976294, 50.206858354462945),
+            Offset(62.28985971191208, 52.44166244631208),
+            Offset(63.61148565511208, 53.800105220312076),
+            Offset(63.49164869851208, 53.67582985801208),
+            Offset(63.37181174201208, 53.55155449561208),
+            Offset(63.251974785512076, 53.42727913331208),
+            Offset(63.13213782901208, 53.30300377101208),
+            Offset(63.01230087251208, 53.17872840871208),
+            Offset(62.89246391591208, 53.05445304641208),
+            Offset(62.77262695941208, 52.93017768411208),
+            Offset(62.65279000291208, 52.80590232171208),
+            Offset(62.532953046412075, 52.68162695941208),
+            Offset(62.41311608991208, 52.55735159711208),
+            Offset(62.293279133312076, 52.43307623481208),
+            Offset(62.17344217681208, 52.30880087251208),
+            Offset(62.05360522031208, 52.18452551011208),
+            Offset(61.93376826381208, 52.06025014781208),
+            Offset(61.81393130721208, 51.93597478551208),
+            Offset(61.69409435071208, 51.811699423212076),
+            Offset(61.574257394212076, 51.68742406091208),
+            Offset(61.45442043771208, 51.56314869851208),
+            Offset(61.33458348121208, 51.438873336212076),
+            Offset(61.214746524612075, 51.31459797391208),
+            Offset(61.09490956811208, 51.19032261161208),
+            Offset(60.97507261161208, 51.06604724931208),
+            Offset(60.855235655112075, 50.94177188701208),
+            Offset(60.73539869851208, 50.81749652461208),
+            Offset(60.615561742012076, 50.69322116231208),
+            Offset(60.49572478551208, 50.56894580001208),
+            Offset(60.37588782901208, 50.44467043771208),
+            Offset(60.25605087251208, 50.32039507541208),
+            Offset(60.13621391591208, 50.19611971301208),
+            Offset(60.01637695941208, 50.07184435071208),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(62.94942712071989, 21.97373945470677),
+            Offset(62.94942712071989, 21.97373945470677),
+            Offset(62.94942712071989, 21.97373945470677),
+            Offset(62.94942712071989, 21.97373945470677),
+            Offset(62.94942712071989, 21.97373945470677),
+            Offset(62.94942712071989, 21.97373945470677),
+            Offset(62.94942712071989, 21.97373945470677),
+            Offset(62.94942712071989, 21.97373945470677),
+            Offset(62.94942712071989, 21.97373945470677),
+            Offset(62.94942712071989, 21.97373945470677),
+            Offset(63.38510509316773, 22.429370456861527),
+            Offset(65.36743540273916, 24.74670319561872),
+            Offset(67.04999102004787, 27.230623010818878),
+            Offset(68.34222460676555, 29.931447747106052),
+            Offset(69.09131472932955, 32.934101437486525),
+            Offset(68.99222107132888, 36.408097305735886),
+            Offset(67.17418370976294, 40.83732326404956),
+            Offset(62.28985971191208, 46.97050802736772),
+            Offset(63.61148565511208, 48.32895080136772),
+            Offset(63.49164869851208, 48.20467543906772),
+            Offset(63.37181174201208, 48.08040007666772),
+            Offset(63.251974785512076, 47.95612471436772),
+            Offset(63.13213782901208, 47.83184935206772),
+            Offset(63.01230087251208, 47.707573989767724),
+            Offset(62.89246391591208, 47.58329862746772),
+            Offset(62.77262695941208, 47.45902326516772),
+            Offset(62.65279000291208, 47.334747902767724),
+            Offset(62.532953046412075, 47.21047254046772),
+            Offset(62.41311608991208, 47.08619717816772),
+            Offset(62.293279133312076, 46.96192181586772),
+            Offset(62.17344217681208, 46.83764645356772),
+            Offset(62.05360522031208, 46.71337109116772),
+            Offset(61.93376826381208, 46.58909572886772),
+            Offset(61.81393130721208, 46.46482036656772),
+            Offset(61.69409435071208, 46.34054500426772),
+            Offset(61.574257394212076, 46.21626964196772),
+            Offset(61.45442043771208, 46.09199427956772),
+            Offset(61.33458348121208, 45.96771891726772),
+            Offset(61.214746524612075, 45.84344355496772),
+            Offset(61.09490956811208, 45.71916819266772),
+            Offset(60.97507261161208, 45.59489283036772),
+            Offset(60.855235655112075, 45.47061746806772),
+            Offset(60.73539869851208, 45.34634210566772),
+            Offset(60.615561742012076, 45.22206674336772),
+            Offset(60.49572478551208, 45.09779138106772),
+            Offset(60.37588782901208, 44.97351601876772),
+            Offset(60.25605087251208, 44.84924065646772),
+            Offset(60.13621391591208, 44.72496529406772),
+            Offset(60.01637695941208, 44.60068993176772),
+          ],
+          <Offset>[
+            Offset(51.241465482535666, 10.265747120719887),
+            Offset(51.241465482535666, 10.265747120719887),
+            Offset(51.241465482535666, 10.265747120719887),
+            Offset(51.241465482535666, 10.265747120719887),
+            Offset(51.241465482535666, 10.265747120719887),
+            Offset(51.241465482535666, 10.265747120719887),
+            Offset(51.241465482535666, 10.265747120719887),
+            Offset(51.241465482535666, 10.265747120719887),
+            Offset(51.241465482535666, 10.265747120719887),
+            Offset(51.241465482535666, 10.265747120719887),
+            Offset(51.67932110354725, 10.723555777147723),
+            Offset(53.74074073435744, 13.11997804449917),
+            Offset(55.636886306106945, 15.817488374127876),
+            Offset(57.3178365729021, 18.90703080962556),
+            Offset(58.69933686192372, 22.542096324509547),
+            Offset(59.61321003018795, 27.02906167480888),
+            Offset(59.5793821068421, 33.242501749162955),
+            Offset(57.855025758054424, 42.53566244631208),
+            Offset(59.17665170125442, 43.89410522031208),
+            Offset(59.056814744654424, 43.76982985801208),
+            Offset(58.93697778815442, 43.64555449561208),
+            Offset(58.81714083165442, 43.52127913331208),
+            Offset(58.697303875154425, 43.39700377101208),
+            Offset(58.57746691865442, 43.272728408712084),
+            Offset(58.457629962054426, 43.14845304641208),
+            Offset(58.337793005554424, 43.02417768411208),
+            Offset(58.21795604905442, 42.899902321712084),
+            Offset(58.09811909255442, 42.77562695941208),
+            Offset(57.978282136054425, 42.65135159711208),
+            Offset(57.85844517945442, 42.52707623481208),
+            Offset(57.738608222954426, 42.40280087251208),
+            Offset(57.618771266454424, 42.27852551011208),
+            Offset(57.49893430995442, 42.15425014781208),
+            Offset(57.379097353354425, 42.02997478551208),
+            Offset(57.25926039685442, 41.90569942321208),
+            Offset(57.13942344035442, 41.78142406091208),
+            Offset(57.019586483854425, 41.65714869851208),
+            Offset(56.89974952735442, 41.53287333621208),
+            Offset(56.77991257075442, 41.40859797391208),
+            Offset(56.660075614254424, 41.28432261161208),
+            Offset(56.54023865775442, 41.16004724931208),
+            Offset(56.42040170125442, 41.03577188701208),
+            Offset(56.30056474465442, 40.91149652461208),
+            Offset(56.18072778815442, 40.78722116231208),
+            Offset(56.060890831654426, 40.66294580001208),
+            Offset(55.941053875154424, 40.53867043771208),
+            Offset(55.82121691865442, 40.41439507541208),
+            Offset(55.701379962054425, 40.29011971301208),
+            Offset(55.58154300555442, 40.16584435071208),
+          ],
+          <Offset>[
+            Offset(36.79758712071989, 10.265747120719887),
+            Offset(36.79758712071989, 10.265747120719887),
+            Offset(36.79758712071989, 10.265747120719887),
+            Offset(36.79758712071989, 10.265747120719887),
+            Offset(36.79758712071989, 10.265747120719887),
+            Offset(36.79758712071989, 10.265747120719887),
+            Offset(36.79758712071989, 10.265747120719887),
+            Offset(36.79758712071989, 10.265747120719887),
+            Offset(36.79758712071989, 10.265747120719887),
+            Offset(36.79758712071989, 10.265747120719887),
+            Offset(37.238129263257726, 10.723555777147723),
+            Offset(39.39711980956916, 13.11997804449917),
+            Offset(41.55676704083787, 15.817488374127876),
+            Offset(43.717269309745554, 18.90703080962556),
+            Offset(45.878961386319546, 22.542096324509547),
+            Offset(48.04251149026888, 27.02906167480888),
+            Offset(50.209827104462946, 33.242501749162955),
+            Offset(52.38385971191208, 42.53566244631208),
+            Offset(53.70548565511208, 43.89410522031208),
+            Offset(53.58564869851208, 43.76982985801208),
+            Offset(53.46581174201208, 43.64555449561208),
+            Offset(53.34597478551208, 43.52127913331208),
+            Offset(53.22613782901208, 43.39700377101208),
+            Offset(53.10630087251208, 43.272728408712084),
+            Offset(52.98646391591208, 43.14845304641208),
+            Offset(52.86662695941208, 43.02417768411208),
+            Offset(52.74679000291208, 42.899902321712084),
+            Offset(52.626953046412076, 42.77562695941208),
+            Offset(52.50711608991208, 42.65135159711208),
+            Offset(52.38727913331208, 42.52707623481208),
+            Offset(52.26744217681208, 42.40280087251208),
+            Offset(52.14760522031208, 42.27852551011208),
+            Offset(52.02776826381208, 42.15425014781208),
+            Offset(51.90793130721208, 42.02997478551208),
+            Offset(51.78809435071208, 41.90569942321208),
+            Offset(51.66825739421208, 41.78142406091208),
+            Offset(51.54842043771208, 41.65714869851208),
+            Offset(51.42858348121208, 41.53287333621208),
+            Offset(51.308746524612076, 41.40859797391208),
+            Offset(51.18890956811208, 41.28432261161208),
+            Offset(51.06907261161208, 41.16004724931208),
+            Offset(50.949235655112076, 41.03577188701208),
+            Offset(50.82939869851208, 40.91149652461208),
+            Offset(50.70956174201208, 40.78722116231208),
+            Offset(50.58972478551208, 40.66294580001208),
+            Offset(50.46988782901208, 40.53867043771208),
+            Offset(50.35005087251208, 40.41439507541208),
+            Offset(50.23021391591208, 40.29011971301208),
+            Offset(50.11037695941208, 40.16584435071208),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(22.353739454706773, 10.265747120719887),
+            Offset(22.353739454706773, 10.265747120719887),
+            Offset(22.353739454706773, 10.265747120719887),
+            Offset(22.353739454706773, 10.265747120719887),
+            Offset(22.353739454706773, 10.265747120719887),
+            Offset(22.353739454706773, 10.265747120719887),
+            Offset(22.353739454706773, 10.265747120719887),
+            Offset(22.353739454706773, 10.265747120719887),
+            Offset(22.353739454706773, 10.265747120719887),
+            Offset(22.353739454706773, 10.265747120719887),
+            Offset(22.79696811306153, 10.723555777147723),
+            Offset(25.05352936751872, 13.11997804449917),
+            Offset(27.476677698318877, 15.817488374127876),
+            Offset(30.11673095020605, 18.90703080962556),
+            Offset(33.058613156286526, 22.542096324509547),
+            Offset(36.47183754013589, 27.02906167480888),
+            Offset(40.84029201404956, 33.242501749162955),
+            Offset(46.91270529296772, 42.53566244631208),
+            Offset(48.23433123616772, 43.89410522031208),
+            Offset(48.11449427956772, 43.76982985801208),
+            Offset(47.99465732306772, 43.64555449561208),
+            Offset(47.87482036656772, 43.52127913331208),
+            Offset(47.75498341006772, 43.39700377101208),
+            Offset(47.63514645356772, 43.272728408712084),
+            Offset(47.515309496967724, 43.14845304641208),
+            Offset(47.39547254046772, 43.02417768411208),
+            Offset(47.27563558396772, 42.899902321712084),
+            Offset(47.15579862746772, 42.77562695941208),
+            Offset(47.03596167096772, 42.65135159711208),
+            Offset(46.91612471436772, 42.52707623481208),
+            Offset(46.796287757867724, 42.40280087251208),
+            Offset(46.67645080136772, 42.27852551011208),
+            Offset(46.55661384486772, 42.15425014781208),
+            Offset(46.43677688826772, 42.02997478551208),
+            Offset(46.31693993176772, 41.90569942321208),
+            Offset(46.19710297526772, 41.78142406091208),
+            Offset(46.07726601876772, 41.65714869851208),
+            Offset(45.95742906226772, 41.53287333621208),
+            Offset(45.83759210566772, 41.40859797391208),
+            Offset(45.71775514916772, 41.28432261161208),
+            Offset(45.59791819266772, 41.16004724931208),
+            Offset(45.47808123616772, 41.03577188701208),
+            Offset(45.35824427956772, 40.91149652461208),
+            Offset(45.23840732306772, 40.78722116231208),
+            Offset(45.118570366567724, 40.66294580001208),
+            Offset(44.99873341006772, 40.53867043771208),
+            Offset(44.87889645356772, 40.41439507541208),
+            Offset(44.75905949696772, 40.29011971301208),
+            Offset(44.63922254046772, 40.16584435071208),
+          ],
+          <Offset>[
+            Offset(10.64574712071989, 21.973739454706777),
+            Offset(10.64574712071989, 21.973739454706777),
+            Offset(10.64574712071989, 21.973739454706777),
+            Offset(10.64574712071989, 21.973739454706777),
+            Offset(10.64574712071989, 21.973739454706777),
+            Offset(10.64574712071989, 21.973739454706777),
+            Offset(10.64574712071989, 21.973739454706777),
+            Offset(10.64574712071989, 21.973739454706777),
+            Offset(10.64574712071989, 21.973739454706777),
+            Offset(10.64574712071989, 21.973739454706777),
+            Offset(11.091153433347728, 22.429370456861534),
+            Offset(13.42680421639917, 24.74670319561873),
+            Offset(16.063543061627875, 27.230623010818885),
+            Offset(19.09231401272556, 29.93144774710606),
+            Offset(22.666608043309548, 32.93410143748653),
+            Offset(27.09280190920888, 36.40809730573589),
+            Offset(33.24547049916295, 40.83732326404956),
+            Offset(42.47785971191208, 46.97050802736772),
+            Offset(43.79948565511208, 48.32895080136772),
+            Offset(43.67964869851208, 48.20467543906772),
+            Offset(43.55981174201208, 48.08040007666772),
+            Offset(43.43997478551208, 47.95612471436772),
+            Offset(43.32013782901208, 47.83184935206772),
+            Offset(43.20030087251208, 47.707573989767724),
+            Offset(43.080463915912084, 47.58329862746772),
+            Offset(42.96062695941208, 47.45902326516772),
+            Offset(42.84079000291208, 47.334747902767724),
+            Offset(42.72095304641208, 47.21047254046772),
+            Offset(42.60111608991208, 47.08619717816772),
+            Offset(42.48127913331208, 46.96192181586772),
+            Offset(42.36144217681208, 46.83764645356772),
+            Offset(42.24160522031208, 46.71337109116772),
+            Offset(42.12176826381208, 46.58909572886772),
+            Offset(42.00193130721208, 46.46482036656772),
+            Offset(41.88209435071208, 46.34054500426772),
+            Offset(41.76225739421208, 46.21626964196772),
+            Offset(41.64242043771208, 46.09199427956772),
+            Offset(41.52258348121208, 45.96771891726772),
+            Offset(41.40274652461208, 45.84344355496772),
+            Offset(41.28290956811208, 45.71916819266772),
+            Offset(41.16307261161208, 45.59489283036772),
+            Offset(41.04323565511208, 45.47061746806772),
+            Offset(40.92339869851208, 45.34634210566772),
+            Offset(40.80356174201208, 45.22206674336772),
+            Offset(40.683724785512084, 45.09779138106772),
+            Offset(40.56388782901208, 44.97351601876772),
+            Offset(40.44405087251208, 44.84924065646772),
+            Offset(40.32421391591208, 44.72496529406772),
+            Offset(40.20437695941208, 44.60068993176772),
+          ],
+          <Offset>[
+            Offset(10.64574712071989, 36.41758712071989),
+            Offset(10.64574712071989, 36.41758712071989),
+            Offset(10.64574712071989, 36.41758712071989),
+            Offset(10.64574712071989, 36.41758712071989),
+            Offset(10.64574712071989, 36.41758712071989),
+            Offset(10.64574712071989, 36.41758712071989),
+            Offset(10.64574712071989, 36.41758712071989),
+            Offset(10.64574712071989, 36.41758712071989),
+            Offset(10.64574712071989, 36.41758712071989),
+            Offset(10.64574712071989, 36.41758712071989),
+            Offset(11.091153433347728, 36.87053160705773),
+            Offset(13.42680421639917, 39.09029363766917),
+            Offset(16.063543061627875, 41.31071235333788),
+            Offset(19.09231401272556, 43.53198610664556),
+            Offset(22.666608043309548, 45.75444966751955),
+            Offset(27.09280190920888, 47.97877125586888),
+            Offset(33.24547049916295, 50.20685835446295),
+            Offset(42.47785971191208, 52.44166244631208),
+            Offset(43.79948565511208, 53.800105220312076),
+            Offset(43.67964869851208, 53.67582985801208),
+            Offset(43.55981174201208, 53.55155449561208),
+            Offset(43.43997478551208, 53.42727913331208),
+            Offset(43.32013782901208, 53.30300377101208),
+            Offset(43.20030087251208, 53.17872840871208),
+            Offset(43.080463915912084, 53.05445304641208),
+            Offset(42.96062695941208, 52.93017768411208),
+            Offset(42.84079000291208, 52.80590232171208),
+            Offset(42.72095304641208, 52.68162695941208),
+            Offset(42.60111608991208, 52.55735159711208),
+            Offset(42.48127913331208, 52.43307623481208),
+            Offset(42.36144217681208, 52.30880087251208),
+            Offset(42.24160522031208, 52.18452551011208),
+            Offset(42.12176826381208, 52.06025014781208),
+            Offset(42.00193130721208, 51.93597478551208),
+            Offset(41.88209435071208, 51.811699423212076),
+            Offset(41.76225739421208, 51.68742406091208),
+            Offset(41.64242043771208, 51.56314869851208),
+            Offset(41.52258348121208, 51.438873336212076),
+            Offset(41.40274652461208, 51.31459797391208),
+            Offset(41.28290956811208, 51.19032261161208),
+            Offset(41.16307261161208, 51.06604724931208),
+            Offset(41.04323565511208, 50.94177188701208),
+            Offset(40.92339869851208, 50.81749652461208),
+            Offset(40.80356174201208, 50.69322116231208),
+            Offset(40.683724785512084, 50.56894580001208),
+            Offset(40.56388782901208, 50.44467043771208),
+            Offset(40.44405087251208, 50.32039507541208),
+            Offset(40.32421391591208, 50.19611971301208),
+            Offset(40.20437695941208, 50.07184435071208),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(10.64574712071989, 50.861465482535664),
+            Offset(10.64574712071989, 50.861465482535664),
+            Offset(10.64574712071989, 50.861465482535664),
+            Offset(10.64574712071989, 50.861465482535664),
+            Offset(10.64574712071989, 50.861465482535664),
+            Offset(10.64574712071989, 50.861465482535664),
+            Offset(10.64574712071989, 50.861465482535664),
+            Offset(10.64574712071989, 50.861465482535664),
+            Offset(10.64574712071989, 50.861465482535664),
+            Offset(10.64574712071989, 50.861465482535664),
+            Offset(11.091153433347728, 51.311723447347255),
+            Offset(13.42680421639917, 53.433914562457446),
+            Offset(16.063543061627875, 55.39083161860695),
+            Offset(19.09231401272556, 57.13255336980211),
+            Offset(22.666608043309548, 58.574825143123725),
+            Offset(27.09280190920888, 59.54946979578796),
+            Offset(33.24547049916295, 59.5764133568421),
+            Offset(42.47785971191208, 57.91282849245442),
+            Offset(43.79948565511208, 59.27127126645442),
+            Offset(43.67964869851208, 59.14699590415442),
+            Offset(43.55981174201208, 59.022720541754424),
+            Offset(43.43997478551208, 58.89844517945442),
+            Offset(43.32013782901208, 58.77416981715442),
+            Offset(43.20030087251208, 58.649894454854426),
+            Offset(43.080463915912084, 58.52561909255442),
+            Offset(42.96062695941208, 58.401343730254425),
+            Offset(42.84079000291208, 58.277068367854426),
+            Offset(42.72095304641208, 58.15279300555442),
+            Offset(42.60111608991208, 58.028517643254425),
+            Offset(42.48127913331208, 57.90424228095442),
+            Offset(42.36144217681208, 57.779966918654424),
+            Offset(42.24160522031208, 57.655691556254425),
+            Offset(42.12176826381208, 57.53141619395442),
+            Offset(42.00193130721208, 57.407140831654424),
+            Offset(41.88209435071208, 57.28286546935442),
+            Offset(41.76225739421208, 57.15859010705442),
+            Offset(41.64242043771208, 57.034314744654424),
+            Offset(41.52258348121208, 56.91003938235442),
+            Offset(41.40274652461208, 56.78576402005442),
+            Offset(41.28290956811208, 56.661488657754425),
+            Offset(41.16307261161208, 56.53721329545442),
+            Offset(41.04323565511208, 56.412937933154424),
+            Offset(40.92339869851208, 56.288662570754425),
+            Offset(40.80356174201208, 56.16438720845442),
+            Offset(40.683724785512084, 56.040111846154424),
+            Offset(40.56388782901208, 55.91583648385442),
+            Offset(40.44405087251208, 55.79156112155442),
+            Offset(40.32421391591208, 55.667285759154424),
+            Offset(40.20437695941208, 55.54301039685442),
+          ],
+          <Offset>[
+            Offset(22.35373945470678, 62.569427120719894),
+            Offset(22.35373945470678, 62.569427120719894),
+            Offset(22.35373945470678, 62.569427120719894),
+            Offset(22.35373945470678, 62.569427120719894),
+            Offset(22.35373945470678, 62.569427120719894),
+            Offset(22.35373945470678, 62.569427120719894),
+            Offset(22.35373945470678, 62.569427120719894),
+            Offset(22.35373945470678, 62.569427120719894),
+            Offset(22.35373945470678, 62.569427120719894),
+            Offset(22.35373945470678, 62.569427120719894),
+            Offset(22.79696811306154, 63.01750743696773),
+            Offset(25.053529367518728, 65.06060923083916),
+            Offset(27.476677698318884, 66.80393633254788),
+            Offset(30.11673095020606, 68.15694140366556),
+            Offset(33.05861315628653, 68.96680301052955),
+            Offset(36.471837540135894, 68.92848083692888),
+            Offset(40.84029201404956, 67.17121495976295),
+            Offset(46.91270529296772, 62.347662446312086),
+            Offset(48.23433123616772, 63.70610522031208),
+            Offset(48.11449427956772, 63.581829858012085),
+            Offset(47.99465732306772, 63.45755449561209),
+            Offset(47.87482036656772, 63.33327913331208),
+            Offset(47.75498341006772, 63.209003771012085),
+            Offset(47.63514645356772, 63.08472840871209),
+            Offset(47.515309496967724, 62.960453046412084),
+            Offset(47.39547254046772, 62.83617768411209),
+            Offset(47.27563558396772, 62.71190232171209),
+            Offset(47.15579862746772, 62.587626959412084),
+            Offset(47.03596167096772, 62.46335159711209),
+            Offset(46.91612471436772, 62.33907623481208),
+            Offset(46.796287757867724, 62.214800872512086),
+            Offset(46.67645080136772, 62.09052551011209),
+            Offset(46.55661384486772, 61.96625014781208),
+            Offset(46.43677688826772, 61.841974785512086),
+            Offset(46.31693993176772, 61.71769942321208),
+            Offset(46.19710297526772, 61.593424060912085),
+            Offset(46.07726601876772, 61.469148698512086),
+            Offset(45.95742906226772, 61.34487333621208),
+            Offset(45.83759210566772, 61.220597973912085),
+            Offset(45.71775514916772, 61.09632261161209),
+            Offset(45.59791819266772, 60.972047249312084),
+            Offset(45.47808123616772, 60.84777188701209),
+            Offset(45.35824427956772, 60.72349652461209),
+            Offset(45.23840732306772, 60.599221162312084),
+            Offset(45.118570366567724, 60.47494580001209),
+            Offset(44.99873341006772, 60.35067043771208),
+            Offset(44.87889645356772, 60.226395075412086),
+            Offset(44.75905949696772, 60.10211971301209),
+            Offset(44.63922254046772, 59.97784435071208),
+          ],
+          <Offset>[
+            Offset(36.79758712071989, 62.569427120719894),
+            Offset(36.79758712071989, 62.569427120719894),
+            Offset(36.79758712071989, 62.569427120719894),
+            Offset(36.79758712071989, 62.569427120719894),
+            Offset(36.79758712071989, 62.569427120719894),
+            Offset(36.79758712071989, 62.569427120719894),
+            Offset(36.79758712071989, 62.569427120719894),
+            Offset(36.79758712071989, 62.569427120719894),
+            Offset(36.79758712071989, 62.569427120719894),
+            Offset(36.79758712071989, 62.569427120719894),
+            Offset(37.23812926325773, 63.01750743696773),
+            Offset(39.39711980956917, 65.06060923083916),
+            Offset(41.556767040837876, 66.80393633254788),
+            Offset(43.71726930974556, 68.15694140366556),
+            Offset(45.878961386319546, 68.96680301052955),
+            Offset(48.04251149026888, 68.92848083692888),
+            Offset(50.20982710446295, 67.17121495976295),
+            Offset(52.38385971191208, 62.347662446312086),
+            Offset(53.70548565511208, 63.70610522031208),
+            Offset(53.58564869851208, 63.581829858012085),
+            Offset(53.46581174201208, 63.45755449561209),
+            Offset(53.34597478551208, 63.33327913331208),
+            Offset(53.22613782901208, 63.209003771012085),
+            Offset(53.10630087251208, 63.08472840871209),
+            Offset(52.98646391591208, 62.960453046412084),
+            Offset(52.86662695941208, 62.83617768411209),
+            Offset(52.74679000291208, 62.71190232171209),
+            Offset(52.626953046412076, 62.587626959412084),
+            Offset(52.50711608991208, 62.46335159711209),
+            Offset(52.38727913331208, 62.33907623481208),
+            Offset(52.26744217681208, 62.214800872512086),
+            Offset(52.14760522031208, 62.09052551011209),
+            Offset(52.02776826381208, 61.96625014781208),
+            Offset(51.90793130721208, 61.841974785512086),
+            Offset(51.78809435071208, 61.71769942321208),
+            Offset(51.66825739421208, 61.593424060912085),
+            Offset(51.54842043771208, 61.469148698512086),
+            Offset(51.42858348121208, 61.34487333621208),
+            Offset(51.308746524612076, 61.220597973912085),
+            Offset(51.18890956811208, 61.09632261161209),
+            Offset(51.06907261161208, 60.972047249312084),
+            Offset(50.949235655112076, 60.84777188701209),
+            Offset(50.82939869851208, 60.72349652461209),
+            Offset(50.70956174201208, 60.599221162312084),
+            Offset(50.58972478551208, 60.47494580001209),
+            Offset(50.46988782901208, 60.35067043771208),
+            Offset(50.35005087251208, 60.226395075412086),
+            Offset(50.23021391591208, 60.10211971301209),
+            Offset(50.11037695941208, 59.97784435071208),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(43.295335621700225, 62.569427120719894),
+            Offset(43.295335621700225, 62.569427120719894),
+            Offset(43.295335621700225, 62.569427120719894),
+            Offset(43.295335621700225, 62.569427120719894),
+            Offset(43.295335621700225, 62.569427120719894),
+            Offset(43.295335621700225, 62.569427120719894),
+            Offset(43.295335621700225, 62.569427120719894),
+            Offset(43.295335621700225, 62.569427120719894),
+            Offset(43.295335621700225, 62.569427120719894),
+            Offset(43.295335621700225, 62.569427120719894),
+            Offset(43.73466920094344, 63.01750743696773),
+            Offset(45.84976632375351, 65.06060923083916),
+            Offset(47.89087423543939, 66.80393633254788),
+            Offset(49.835644397996305, 68.15694140366556),
+            Offset(51.646358026050024, 68.96680301052955),
+            Offset(53.24772626774939, 68.92848083692888),
+            Offset(54.42483158224287, 67.17121495976295),
+            Offset(54.845128083495545, 62.347662446312086),
+            Offset(56.16675402669554, 63.70610522031208),
+            Offset(56.046917070095546, 63.581829858012085),
+            Offset(55.927080113595544, 63.45755449561209),
+            Offset(55.80724315709554, 63.33327913331208),
+            Offset(55.68740620059555, 63.209003771012085),
+            Offset(55.567569244095544, 63.08472840871209),
+            Offset(55.44773228749555, 62.960453046412084),
+            Offset(55.327895330995545, 62.83617768411209),
+            Offset(55.20805837449554, 62.71190232171209),
+            Offset(55.08822141799554, 62.587626959412084),
+            Offset(54.968384461495546, 62.46335159711209),
+            Offset(54.84854750489554, 62.33907623481208),
+            Offset(54.72871054839555, 62.214800872512086),
+            Offset(54.608873591895545, 62.09052551011209),
+            Offset(54.48903663539554, 61.96625014781208),
+            Offset(54.369199678795546, 61.841974785512086),
+            Offset(54.249362722295544, 61.71769942321208),
+            Offset(54.12952576579554, 61.593424060912085),
+            Offset(54.00968880929555, 61.469148698512086),
+            Offset(53.889851852795545, 61.34487333621208),
+            Offset(53.77001489619554, 61.220597973912085),
+            Offset(53.650177939695546, 61.09632261161209),
+            Offset(53.53034098319554, 60.972047249312084),
+            Offset(53.41050402669554, 60.84777188701209),
+            Offset(53.290667070095544, 60.72349652461209),
+            Offset(53.17083011359554, 60.599221162312084),
+            Offset(53.05099315709555, 60.47494580001209),
+            Offset(52.931156200595545, 60.35067043771208),
+            Offset(52.81131924409554, 60.226395075412086),
+            Offset(52.691482287495546, 60.10211971301209),
+            Offset(52.571645330995544, 59.97784435071208),
+          ],
+          <Offset>[
+            Offset(49.22978548253567, 60.19565945470678),
+            Offset(49.22978548253567, 60.19565945470678),
+            Offset(49.22978548253567, 60.19565945470678),
+            Offset(49.22978548253567, 60.19565945470678),
+            Offset(49.22978548253567, 60.19565945470678),
+            Offset(49.22978548253567, 60.19565945470678),
+            Offset(49.22978548253567, 60.19565945470678),
+            Offset(49.22978548253567, 60.19565945470678),
+            Offset(49.22978548253567, 60.19565945470678),
+            Offset(49.22978548253567, 60.19565945470678),
+            Offset(49.668015270477255, 60.644181285191536),
+            Offset(51.743024150267445, 62.70331829332873),
+            Offset(53.67586907693695, 64.48995036504888),
+            Offset(55.423609242362105, 65.92176702736606),
+            Offset(56.91377122015372, 66.85984863111653),
+            Offset(58.00169390856796, 67.0269036165159),
+            Offset(58.274431598742105, 65.63138291794957),
+            Offset(57.093025758054424, 61.44850802736772),
+            Offset(58.41465170125442, 62.80695080136772),
+            Offset(58.294814744654424, 62.68267543906772),
+            Offset(58.17497778815442, 62.55840007666772),
+            Offset(58.05514083165442, 62.43412471436772),
+            Offset(57.935303875154425, 62.30984935206772),
+            Offset(57.81546691865442, 62.185573989767725),
+            Offset(57.695629962054426, 62.06129862746772),
+            Offset(57.575793005554424, 61.937023265167724),
+            Offset(57.45595604905442, 61.812747902767725),
+            Offset(57.33611909255442, 61.68847254046772),
+            Offset(57.216282136054424, 61.564197178167724),
+            Offset(57.09644517945442, 61.43992181586772),
+            Offset(56.976608222954425, 61.31564645356772),
+            Offset(56.85677126645442, 61.191371091167724),
+            Offset(56.73693430995442, 61.06709572886772),
+            Offset(56.617097353354424, 60.94282036656772),
+            Offset(56.49726039685442, 60.81854500426772),
+            Offset(56.37742344035442, 60.69426964196772),
+            Offset(56.257586483854425, 60.56999427956772),
+            Offset(56.13774952735442, 60.44571891726772),
+            Offset(56.01791257075442, 60.32144355496772),
+            Offset(55.898075614254424, 60.197168192667725),
+            Offset(55.77823865775442, 60.07289283036772),
+            Offset(55.65840170125442, 59.948617468067724),
+            Offset(55.53856474465442, 59.824342105667725),
+            Offset(55.41872778815442, 59.70006674336772),
+            Offset(55.298890831654425, 59.575791381067724),
+            Offset(55.17905387515442, 59.45151601876772),
+            Offset(55.05921691865442, 59.32724065646772),
+            Offset(54.939379962054424, 59.202965294067724),
+            Offset(54.81954300555442, 59.07868993176772),
+          ],
+          <Offset>[
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(54.233688104769875, 56.72214104872773),
+            Offset(56.27784933131594, 58.80777705090479),
+            Offset(58.127386565520524, 60.66597275272132),
+            Offset(59.7235133756984, 62.22802951354025),
+            Offset(60.96701285572941, 63.37800107878284),
+            Offset(61.65984238978352, 63.88445209731733),
+            Offset(61.23667482747795, 63.08673340955032),
+            Offset(58.822769013668946, 59.962610352808845),
+            Offset(60.14439495686894, 61.32105312680884),
+            Offset(60.02455800026895, 61.196777764508845),
+            Offset(59.904721043768944, 61.072502402108846),
+            Offset(59.78488408726894, 60.94822703980884),
+            Offset(59.66504713076895, 60.823951677508845),
+            Offset(59.545210174268945, 60.69967631520885),
+            Offset(59.42537321766895, 60.57540095290884),
+            Offset(59.305536261168946, 60.45112559060885),
+            Offset(59.185699304668944, 60.32685022820885),
+            Offset(59.06586234816894, 60.20257486590884),
+            Offset(58.94602539166895, 60.07829950360885),
+            Offset(58.82618843506894, 59.95402414130884),
+            Offset(58.70635147856895, 59.829748779008845),
+            Offset(58.586514522068946, 59.70547341660885),
+            Offset(58.46667756556894, 59.58119805430884),
+            Offset(58.34684060896895, 59.456922692008845),
+            Offset(58.227003652468944, 59.33264732970884),
+            Offset(58.10716669596894, 59.208371967408844),
+            Offset(57.98732973946895, 59.084096605008845),
+            Offset(57.867492782968945, 58.95982124270884),
+            Offset(57.74765582636894, 58.835545880408844),
+            Offset(57.627818869868946, 58.71127051810885),
+            Offset(57.507981913368944, 58.58699515580884),
+            Offset(57.38814495686894, 58.462719793508846),
+            Offset(57.268308000268945, 58.33844443110885),
+            Offset(57.14847104376894, 58.21416906880884),
+            Offset(57.02863408726895, 58.089893706508846),
+            Offset(56.908797130768946, 57.96561834420884),
+            Offset(56.788960174268944, 57.841342981908845),
+            Offset(56.66912321766895, 57.717067619508846),
+            Offset(56.549286261168945, 57.59279225720884),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(53.796307677358, 56.272889593871334),
+            Offset(54.233688104769875, 56.72214104872773),
+            Offset(56.27784933131594, 58.80777705090479),
+            Offset(58.127386565520524, 60.66597275272132),
+            Offset(59.7235133756984, 62.22802951354025),
+            Offset(60.96701285572941, 63.37800107878284),
+            Offset(61.65984238978352, 63.88445209731733),
+            Offset(61.23667482747795, 63.08673340955032),
+            Offset(58.822769013668946, 59.962610352808845),
+            Offset(60.14439495686894, 61.32105312680884),
+            Offset(60.02455800026895, 61.196777764508845),
+            Offset(59.904721043768944, 61.072502402108846),
+            Offset(59.78488408726894, 60.94822703980884),
+            Offset(59.66504713076895, 60.823951677508845),
+            Offset(59.545210174268945, 60.69967631520885),
+            Offset(59.42537321766895, 60.57540095290884),
+            Offset(59.305536261168946, 60.45112559060885),
+            Offset(59.185699304668944, 60.32685022820885),
+            Offset(59.06586234816894, 60.20257486590884),
+            Offset(58.94602539166895, 60.07829950360885),
+            Offset(58.82618843506894, 59.95402414130884),
+            Offset(58.70635147856895, 59.829748779008845),
+            Offset(58.586514522068946, 59.70547341660885),
+            Offset(58.46667756556894, 59.58119805430884),
+            Offset(58.34684060896895, 59.456922692008845),
+            Offset(58.227003652468944, 59.33264732970884),
+            Offset(58.10716669596894, 59.208371967408844),
+            Offset(57.98732973946895, 59.084096605008845),
+            Offset(57.867492782968945, 58.95982124270884),
+            Offset(57.74765582636894, 58.835545880408844),
+            Offset(57.627818869868946, 58.71127051810885),
+            Offset(57.507981913368944, 58.58699515580884),
+            Offset(57.38814495686894, 58.462719793508846),
+            Offset(57.268308000268945, 58.33844443110885),
+            Offset(57.14847104376894, 58.21416906880884),
+            Offset(57.02863408726895, 58.089893706508846),
+            Offset(56.908797130768946, 57.96561834420884),
+            Offset(56.788960174268944, 57.841342981908845),
+            Offset(56.66912321766895, 57.717067619508846),
+            Offset(56.549286261168945, 57.59279225720884),
+          ],
+          <Offset>[
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(57.069606004928445, 53.86609044856987),
+            Offset(59.094606548002886, 55.97102315941594),
+            Offset(60.89239811736094, 57.881331878020525),
+            Offset(62.39435194501161, 59.538230172598404),
+            Offset(63.48463970399174, 60.8425011369294),
+            Offset(63.932061433030945, 61.59610215538352),
+            Offset(63.07663991080549, 61.23370607747795),
+            Offset(59.89718017699878, 58.880571748068945),
+            Offset(61.21880612019878, 60.23901452206894),
+            Offset(61.09896916359878, 60.114739159768945),
+            Offset(60.97913220709878, 59.990463797368946),
+            Offset(60.85929525059878, 59.86618843506894),
+            Offset(60.73945829409878, 59.741913072768945),
+            Offset(60.61962133759878, 59.61763771046895),
+            Offset(60.49978438099878, 59.493362348168944),
+            Offset(60.37994742449878, 59.36908698586895),
+            Offset(60.26011046799878, 59.24481162346895),
+            Offset(60.140273511498776, 59.120536261168944),
+            Offset(60.02043655499878, 58.99626089886895),
+            Offset(59.90059959839878, 58.87198553656894),
+            Offset(59.78076264189878, 58.747710174268946),
+            Offset(59.66092568539878, 58.62343481186895),
+            Offset(59.54108872889878, 58.49915944956894),
+            Offset(59.42125177229878, 58.374884087268946),
+            Offset(59.30141481579878, 58.25060872496894),
+            Offset(59.18157785929878, 58.126333362668944),
+            Offset(59.06174090279878, 58.002058000268946),
+            Offset(58.94190394629878, 57.87778263796894),
+            Offset(58.822066989698776, 57.753507275668944),
+            Offset(58.70223003319878, 57.62923191336895),
+            Offset(58.58239307669878, 57.50495655106894),
+            Offset(58.46255612019878, 57.380681188768946),
+            Offset(58.34271916359878, 57.25640582636895),
+            Offset(58.22288220709878, 57.13213046406894),
+            Offset(58.10304525059878, 57.007855101768946),
+            Offset(57.98320829409878, 56.88357973946894),
+            Offset(57.86337133759878, 56.759304377168945),
+            Offset(57.74353438099878, 56.635029014768946),
+            Offset(57.62369742449878, 56.51075365246894),
+          ],
+          <Offset>[
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(56.63275314854878, 53.416307677358),
+            Offset(57.069606004928445, 53.86609044856987),
+            Offset(59.094606548002886, 55.97102315941594),
+            Offset(60.89239811736094, 57.881331878020525),
+            Offset(62.39435194501161, 59.538230172598404),
+            Offset(63.48463970399174, 60.8425011369294),
+            Offset(63.932061433030945, 61.59610215538352),
+            Offset(63.07663991080549, 61.23370607747795),
+            Offset(59.89718017699878, 58.880571748068945),
+            Offset(61.21880612019878, 60.23901452206894),
+            Offset(61.09896916359878, 60.114739159768945),
+            Offset(60.97913220709878, 59.990463797368946),
+            Offset(60.85929525059878, 59.86618843506894),
+            Offset(60.73945829409878, 59.741913072768945),
+            Offset(60.61962133759878, 59.61763771046895),
+            Offset(60.49978438099878, 59.493362348168944),
+            Offset(60.37994742449878, 59.36908698586895),
+            Offset(60.26011046799878, 59.24481162346895),
+            Offset(60.140273511498776, 59.120536261168944),
+            Offset(60.02043655499878, 58.99626089886895),
+            Offset(59.90059959839878, 58.87198553656894),
+            Offset(59.78076264189878, 58.747710174268946),
+            Offset(59.66092568539878, 58.62343481186895),
+            Offset(59.54108872889878, 58.49915944956894),
+            Offset(59.42125177229878, 58.374884087268946),
+            Offset(59.30141481579878, 58.25060872496894),
+            Offset(59.18157785929878, 58.126333362668944),
+            Offset(59.06174090279878, 58.002058000268946),
+            Offset(58.94190394629878, 57.87778263796894),
+            Offset(58.822066989698776, 57.753507275668944),
+            Offset(58.70223003319878, 57.62923191336895),
+            Offset(58.58239307669878, 57.50495655106894),
+            Offset(58.46255612019878, 57.380681188768946),
+            Offset(58.34271916359878, 57.25640582636895),
+            Offset(58.22288220709878, 57.13213046406894),
+            Offset(58.10304525059878, 57.007855101768946),
+            Offset(57.98320829409878, 56.88357973946894),
+            Offset(57.86337133759878, 56.759304377168945),
+            Offset(57.74353438099878, 56.635029014768946),
+            Offset(57.62369742449878, 56.51075365246894),
+          ],
+        ),
+        _PathClose(
+        ),
+        _PathMoveTo(
+          <Offset>[
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(37.238129263257726, 54.34772049679427),
+            Offset(39.39711980956916, 53.40971455122347),
+            Offset(41.55676704083787, 52.38323260637452),
+            Offset(43.717269309745554, 51.34517214659979),
+            Offset(45.878961386319546, 50.4025549847652),
+            Offset(48.04251149026888, 49.72174872023579),
+            Offset(50.209827104462946, 50.28103042236462),
+            Offset(52.38385971191208, 52.48497375612058),
+            Offset(53.70548565511208, 53.84341653012058),
+            Offset(53.58564869851208, 53.71914116782058),
+            Offset(53.46581174201208, 53.59486580542058),
+            Offset(53.34597478551208, 53.47059044312058),
+            Offset(53.22613782901208, 53.34631508082058),
+            Offset(53.10630087251208, 53.222039718520584),
+            Offset(52.98646391591208, 53.09776435622058),
+            Offset(52.86662695941208, 52.97348899392058),
+            Offset(52.74679000291208, 52.849213631520584),
+            Offset(52.626953046412076, 52.72493826922058),
+            Offset(52.50711608991208, 52.60066290692058),
+            Offset(52.38727913331208, 52.47638754462058),
+            Offset(52.26744217681208, 52.35211218232058),
+            Offset(52.14760522031208, 52.22783681992058),
+            Offset(52.02776826381208, 52.10356145762058),
+            Offset(51.90793130721208, 51.97928609532058),
+            Offset(51.78809435071208, 51.85501073302058),
+            Offset(51.66825739421208, 51.73073537072058),
+            Offset(51.54842043771208, 51.60646000832058),
+            Offset(51.42858348121208, 51.48218464602058),
+            Offset(51.308746524612076, 51.35790928372058),
+            Offset(51.18890956811208, 51.233633921420584),
+            Offset(51.06907261161208, 51.10935855912058),
+            Offset(50.949235655112076, 50.98508319682058),
+            Offset(50.82939869851208, 50.860807834420584),
+            Offset(50.70956174201208, 50.73653247212058),
+            Offset(50.58972478551208, 50.61225710982058),
+            Offset(50.46988782901208, 50.48798174752058),
+            Offset(50.35005087251208, 50.36370638522058),
+            Offset(50.23021391591208, 50.23943102282058),
+            Offset(50.11037695941208, 50.11515566052058),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(26.79953506506211, 54.522707120719886),
+            Offset(26.79953506506211, 54.522707120719886),
+            Offset(26.79953506506211, 54.522707120719886),
+            Offset(26.79953506506211, 54.522707120719886),
+            Offset(26.79953506506211, 54.522707120719886),
+            Offset(26.79953506506211, 54.522707120719886),
+            Offset(26.79953506506211, 54.522707120719886),
+            Offset(26.79953506506211, 54.522707120719886),
+            Offset(26.79953506506211, 54.522707120719886),
+            Offset(26.79953506506211, 54.522707120719886),
+            Offset(27.586834836005405, 54.34772049679427),
+            Offset(31.489615606172315, 53.40971455122347),
+            Offset(35.442274116666454, 52.38323260637452),
+            Offset(39.402653738040314, 51.34517214659979),
+            Offset(43.31217425963652, 50.4025549847652),
+            Offset(47.08000098098459, 49.72174872023579),
+            Offset(50.16886819340391, 50.28103042236462),
+            Offset(52.35994256713666, 52.48497375612058),
+            Offset(53.68156851033666, 53.84341653012058),
+            Offset(53.561731553736664, 53.71914116782058),
+            Offset(53.44189459723666, 53.59486580542058),
+            Offset(53.32205764073666, 53.47059044312058),
+            Offset(53.202220684236664, 53.34631508082058),
+            Offset(53.08238372773666, 53.222039718520584),
+            Offset(52.962546771136665, 53.09776435622058),
+            Offset(52.84270981463666, 52.97348899392058),
+            Offset(52.72287285813666, 52.849213631520584),
+            Offset(52.60303590163666, 52.72493826922058),
+            Offset(52.483198945136664, 52.60066290692058),
+            Offset(52.36336198853666, 52.47638754462058),
+            Offset(52.243525032036665, 52.35211218232058),
+            Offset(52.12368807553666, 52.22783681992058),
+            Offset(52.00385111903666, 52.10356145762058),
+            Offset(51.884014162436664, 51.97928609532058),
+            Offset(51.76417720593666, 51.85501073302058),
+            Offset(51.64434024943666, 51.73073537072058),
+            Offset(51.524503292936664, 51.60646000832058),
+            Offset(51.40466633643666, 51.48218464602058),
+            Offset(51.28482937983666, 51.35790928372058),
+            Offset(51.16499242333666, 51.233633921420584),
+            Offset(51.04515546683666, 51.10935855912058),
+            Offset(50.92531851033666, 50.98508319682058),
+            Offset(50.80548155373666, 50.860807834420584),
+            Offset(50.68564459723666, 50.73653247212058),
+            Offset(50.565807640736665, 50.61225710982058),
+            Offset(50.44597068423666, 50.48798174752058),
+            Offset(50.32613372773666, 50.36370638522058),
+            Offset(50.206296771136664, 50.23943102282058),
+            Offset(50.08645981463666, 50.11515566052058),
+          ],
+          <Offset>[
+            Offset(18.692467120719886, 46.41563917637766),
+            Offset(18.692467120719886, 46.41563917637766),
+            Offset(18.692467120719886, 46.41563917637766),
+            Offset(18.692467120719886, 46.41563917637766),
+            Offset(18.692467120719886, 46.41563917637766),
+            Offset(18.692467120719886, 46.41563917637766),
+            Offset(18.692467120719886, 46.41563917637766),
+            Offset(18.692467120719886, 46.41563917637766),
+            Offset(18.692467120719886, 46.41563917637766),
+            Offset(18.692467120719886, 46.41563917637766),
+            Offset(19.76094037350107, 46.52182603428993),
+            Offset(25.077698896014848, 46.99779784106601),
+            Offset(30.48424678780122, 47.42520527750929),
+            Offset(35.90408326978374, 47.84660167834321),
+            Offset(41.23085606905603, 48.32123679418471),
+            Offset(46.299534025885855, 48.94128176514349),
+            Offset(50.13565503655109, 50.247817265511806),
+            Offset(52.34054840209764, 52.46557959108156),
+            Offset(53.662174345297636, 53.824022365081554),
+            Offset(53.54233738869764, 53.69974700278156),
+            Offset(53.42250043219764, 53.57547164038156),
+            Offset(53.302663475697635, 53.451196278081554),
+            Offset(53.18282651919764, 53.32692091578156),
+            Offset(53.06298956269764, 53.20264555348156),
+            Offset(52.94315260609764, 53.078370191181556),
+            Offset(52.82331564959764, 52.95409482888156),
+            Offset(52.70347869309764, 52.82981946648156),
+            Offset(52.583641736597635, 52.705544104181556),
+            Offset(52.46380478009764, 52.58126874188156),
+            Offset(52.343967823497636, 52.456993379581554),
+            Offset(52.22413086699764, 52.33271801728156),
+            Offset(52.10429391049764, 52.20844265488156),
+            Offset(51.984456953997636, 52.084167292581554),
+            Offset(51.86461999739764, 51.95989193028156),
+            Offset(51.74478304089764, 51.83561656798155),
+            Offset(51.624946084397635, 51.711341205681556),
+            Offset(51.50510912789764, 51.58706584328156),
+            Offset(51.38527217139764, 51.46279048098155),
+            Offset(51.265435214797634, 51.338515118681556),
+            Offset(51.14559825829764, 51.21423975638156),
+            Offset(51.02576130179764, 51.089964394081555),
+            Offset(50.905924345297635, 50.96568903178156),
+            Offset(50.78608738869764, 50.84141366938156),
+            Offset(50.666250432197636, 50.717138307081555),
+            Offset(50.54641347569764, 50.59286294478156),
+            Offset(50.42657651919764, 50.468587582481554),
+            Offset(50.306739562697636, 50.34431222018156),
+            Offset(50.18690260609764, 50.22003685778156),
+            Offset(50.06706564959764, 50.095761495481554),
+          ],
+          <Offset>[
+            Offset(18.692467120719886, 36.41758712071989),
+            Offset(18.692467120719886, 36.41758712071989),
+            Offset(18.692467120719886, 36.41758712071989),
+            Offset(18.692467120719886, 36.41758712071989),
+            Offset(18.692467120719886, 36.41758712071989),
+            Offset(18.692467120719886, 36.41758712071989),
+            Offset(18.692467120719886, 36.41758712071989),
+            Offset(18.692467120719886, 36.41758712071989),
+            Offset(18.692467120719886, 36.41758712071989),
+            Offset(18.692467120719886, 36.41758712071989),
+            Offset(19.76094037350107, 36.87053160703761),
+            Offset(25.077698896014848, 39.09029363766916),
+            Offset(30.48424678780122, 41.31071235333787),
+            Offset(35.90408326978374, 43.53198610663798),
+            Offset(41.23085606905603, 45.75444966750169),
+            Offset(46.299534025885855, 47.97877125585276),
+            Offset(50.13565503655109, 50.20685835445277),
+            Offset(52.34054840209764, 52.44166244630614),
+            Offset(53.662174345297636, 53.800105220306136),
+            Offset(53.54233738869764, 53.67582985800614),
+            Offset(53.42250043219764, 53.55155449560614),
+            Offset(53.302663475697635, 53.427279133306136),
+            Offset(53.18282651919764, 53.30300377100614),
+            Offset(53.06298956269764, 53.17872840870614),
+            Offset(52.94315260609764, 53.05445304640614),
+            Offset(52.82331564959764, 52.93017768410614),
+            Offset(52.70347869309764, 52.80590232170614),
+            Offset(52.583641736597635, 52.68162695940614),
+            Offset(52.46380478009764, 52.55735159710614),
+            Offset(52.343967823497636, 52.43307623480614),
+            Offset(52.22413086699764, 52.30880087250614),
+            Offset(52.10429391049764, 52.18452551010614),
+            Offset(51.984456953997636, 52.06025014780614),
+            Offset(51.86461999739764, 51.93597478550614),
+            Offset(51.74478304089764, 51.811699423206136),
+            Offset(51.624946084397635, 51.68742406090614),
+            Offset(51.50510912789764, 51.56314869850614),
+            Offset(51.38527217139764, 51.438873336206136),
+            Offset(51.265435214797634, 51.31459797390614),
+            Offset(51.14559825829764, 51.19032261160614),
+            Offset(51.02576130179764, 51.06604724930614),
+            Offset(50.905924345297635, 50.94177188700614),
+            Offset(50.78608738869764, 50.81749652460614),
+            Offset(50.666250432197636, 50.69322116230614),
+            Offset(50.54641347569764, 50.56894580000614),
+            Offset(50.42657651919764, 50.44467043770614),
+            Offset(50.306739562697636, 50.32039507540614),
+            Offset(50.18690260609764, 50.19611971300614),
+            Offset(50.06706564959764, 50.07184435070614),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(18.692467120719886, 26.419565760864774),
+            Offset(18.692467120719886, 26.419565760864774),
+            Offset(18.692467120719886, 26.419565760864774),
+            Offset(18.692467120719886, 26.419565760864774),
+            Offset(18.692467120719886, 26.419565760864774),
+            Offset(18.692467120719886, 26.419565760864774),
+            Offset(18.692467120719886, 26.419565760864774),
+            Offset(18.692467120719886, 26.419565760864774),
+            Offset(18.692467120719886, 26.419565760864774),
+            Offset(18.692467120719886, 26.419565760864774),
+            Offset(19.76094037350107, 27.219266804248566),
+            Offset(25.077698896014848, 31.18281367228795),
+            Offset(30.48424678780122, 35.1962381308779),
+            Offset(35.90408326978374, 39.21738368206378),
+            Offset(41.23085606905603, 43.187670298244306),
+            Offset(46.299534025885855, 47.016263564148105),
+            Offset(50.13565503655109, 50.165899443393734),
+            Offset(52.34054840209764, 52.417745301530715),
+            Offset(53.662174345297636, 53.77618807553071),
+            Offset(53.54233738869764, 53.651912713230715),
+            Offset(53.42250043219764, 53.527637350830716),
+            Offset(53.302663475697635, 53.40336198853072),
+            Offset(53.18282651919764, 53.27908662623072),
+            Offset(53.06298956269764, 53.154811263930725),
+            Offset(52.94315260609764, 53.030535901630714),
+            Offset(52.82331564959764, 52.90626053933072),
+            Offset(52.70347869309764, 52.78198517693072),
+            Offset(52.583641736597635, 52.65770981463072),
+            Offset(52.46380478009764, 52.533434452330724),
+            Offset(52.343967823497636, 52.40915909003071),
+            Offset(52.22413086699764, 52.284883727730715),
+            Offset(52.10429391049764, 52.16060836533072),
+            Offset(51.984456953997636, 52.03633300303072),
+            Offset(51.86461999739764, 51.91205764073072),
+            Offset(51.74478304089764, 51.78778227843071),
+            Offset(51.624946084397635, 51.663506916130714),
+            Offset(51.50510912789764, 51.539231553730716),
+            Offset(51.38527217139764, 51.41495619143072),
+            Offset(51.265435214797634, 51.29068082913072),
+            Offset(51.14559825829764, 51.166405466830724),
+            Offset(51.02576130179764, 51.04213010453071),
+            Offset(50.905924345297635, 50.917854742230716),
+            Offset(50.78608738869764, 50.79357937983072),
+            Offset(50.666250432197636, 50.66930401753072),
+            Offset(50.54641347569764, 50.54502865523072),
+            Offset(50.42657651919764, 50.42075329293071),
+            Offset(50.306739562697636, 50.296477930630715),
+            Offset(50.18690260609764, 50.172202568230716),
+            Offset(50.06706564959764, 50.04792720593072),
+          ],
+          <Offset>[
+            Offset(26.79953506506211, 18.312467120719884),
+            Offset(26.79953506506211, 18.312467120719884),
+            Offset(26.79953506506211, 18.312467120719884),
+            Offset(26.79953506506211, 18.312467120719884),
+            Offset(26.79953506506211, 18.312467120719884),
+            Offset(26.79953506506211, 18.312467120719884),
+            Offset(26.79953506506211, 18.312467120719884),
+            Offset(26.79953506506211, 18.312467120719884),
+            Offset(26.79953506506211, 18.312467120719884),
+            Offset(26.79953506506211, 18.312467120719884),
+            Offset(27.58683483600541, 19.393342717280955),
+            Offset(31.489615606172315, 24.77087272411485),
+            Offset(35.442274116666454, 30.23819210030122),
+            Offset(39.40265373804032, 35.71880006667617),
+            Offset(43.31217425963652, 41.106344350238174),
+            Offset(47.08000098097815, 46.23579379146974),
+            Offset(50.16886819340391, 50.13268628654091),
+            Offset(52.35994256713666, 52.3983511364917),
+            Offset(53.68156851033666, 53.756793910491695),
+            Offset(53.561731553736664, 53.6325185481917),
+            Offset(53.44189459723666, 53.5082431857917),
+            Offset(53.32205764073666, 53.38396782349169),
+            Offset(53.202220684236664, 53.25969246119169),
+            Offset(53.08238372773666, 53.13541709889169),
+            Offset(52.962546771136665, 53.011141736591696),
+            Offset(52.84270981463666, 52.8868663742917),
+            Offset(52.72287285813666, 52.7625910118917),
+            Offset(52.60303590163666, 52.63831564959169),
+            Offset(52.483198945136664, 52.51404028729169),
+            Offset(52.36336198853666, 52.389764924991695),
+            Offset(52.243525032036665, 52.2654895626917),
+            Offset(52.12368807553666, 52.1412142002917),
+            Offset(52.00385111903666, 52.01693883799169),
+            Offset(51.884014162436664, 51.89266347569169),
+            Offset(51.76417720593666, 51.768388113391694),
+            Offset(51.64434024943666, 51.6441127510917),
+            Offset(51.524503292936664, 51.5198373886917),
+            Offset(51.40466633643666, 51.39556202639169),
+            Offset(51.28482937983666, 51.27128666409169),
+            Offset(51.16499242333666, 51.14701130179169),
+            Offset(51.04515546683666, 51.022735939491696),
+            Offset(50.92531851033666, 50.8984605771917),
+            Offset(50.80548155373666, 50.7741852147917),
+            Offset(50.68564459723666, 50.64990985249169),
+            Offset(50.565807640736665, 50.52563449019169),
+            Offset(50.44597068423666, 50.401359127891695),
+            Offset(50.32613372773666, 50.2770837655917),
+            Offset(50.206296771136664, 50.1528084031917),
+            Offset(50.08645981463666, 50.02853304089169),
+          ],
+          <Offset>[
+            Offset(36.79758712071989, 18.312467120719884),
+            Offset(36.79758712071989, 18.312467120719884),
+            Offset(36.79758712071989, 18.312467120719884),
+            Offset(36.79758712071989, 18.312467120719884),
+            Offset(36.79758712071989, 18.312467120719884),
+            Offset(36.79758712071989, 18.312467120719884),
+            Offset(36.79758712071989, 18.312467120719884),
+            Offset(36.79758712071989, 18.312467120719884),
+            Offset(36.79758712071989, 18.312467120719884),
+            Offset(36.79758712071989, 18.312467120719884),
+            Offset(37.238129263257726, 19.393342717280955),
+            Offset(39.39711980956916, 24.77087272411485),
+            Offset(41.55676704083787, 30.23819210030122),
+            Offset(43.717269309745554, 35.71880006667617),
+            Offset(45.878961386319546, 41.106344350238174),
+            Offset(48.04251149026888, 46.23579379146974),
+            Offset(50.209827104462946, 50.13268628654091),
+            Offset(52.38385971191208, 52.3983511364917),
+            Offset(53.70548565511208, 53.756793910491695),
+            Offset(53.58564869851208, 53.6325185481917),
+            Offset(53.46581174201208, 53.5082431857917),
+            Offset(53.34597478551208, 53.38396782349169),
+            Offset(53.22613782901208, 53.25969246119169),
+            Offset(53.10630087251208, 53.13541709889169),
+            Offset(52.98646391591208, 53.011141736591696),
+            Offset(52.86662695941208, 52.8868663742917),
+            Offset(52.74679000291208, 52.7625910118917),
+            Offset(52.626953046412076, 52.63831564959169),
+            Offset(52.50711608991208, 52.51404028729169),
+            Offset(52.38727913331208, 52.389764924991695),
+            Offset(52.26744217681208, 52.2654895626917),
+            Offset(52.14760522031208, 52.1412142002917),
+            Offset(52.02776826381208, 52.01693883799169),
+            Offset(51.90793130721208, 51.89266347569169),
+            Offset(51.78809435071208, 51.768388113391694),
+            Offset(51.66825739421208, 51.6441127510917),
+            Offset(51.54842043771208, 51.5198373886917),
+            Offset(51.42858348121208, 51.39556202639169),
+            Offset(51.308746524612076, 51.27128666409169),
+            Offset(51.18890956811208, 51.14701130179169),
+            Offset(51.06907261161208, 51.022735939491696),
+            Offset(50.949235655112076, 50.8984605771917),
+            Offset(50.82939869851208, 50.7741852147917),
+            Offset(50.70956174201208, 50.64990985249169),
+            Offset(50.58972478551208, 50.52563449019169),
+            Offset(50.46988782901208, 50.401359127891695),
+            Offset(50.35005087251208, 50.2770837655917),
+            Offset(50.23021391591208, 50.1528084031917),
+            Offset(50.11037695941208, 50.02853304089169),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(46.79563917637766, 18.312467120719884),
+            Offset(46.79563917637766, 18.312467120719884),
+            Offset(46.79563917637766, 18.312467120719884),
+            Offset(46.79563917637766, 18.312467120719884),
+            Offset(46.79563917637766, 18.312467120719884),
+            Offset(46.79563917637766, 18.312467120719884),
+            Offset(46.79563917637766, 18.312467120719884),
+            Offset(46.79563917637766, 18.312467120719884),
+            Offset(46.79563917637766, 18.312467120719884),
+            Offset(46.79563917637766, 18.312467120719884),
+            Offset(46.88942369051005, 19.393342717280955),
+            Offset(47.30462401296601, 24.77087272411485),
+            Offset(47.67125996500929, 30.23819210030122),
+            Offset(48.031884881450786, 35.71880006667617),
+            Offset(48.44574851300257, 41.106344350238174),
+            Offset(49.005021999553165, 46.23579379146974),
+            Offset(50.25078601552198, 50.13268628654091),
+            Offset(52.4077768566875, 52.3983511364917),
+            Offset(53.729402799887495, 53.756793910491695),
+            Offset(53.6095658432875, 53.6325185481917),
+            Offset(53.489728886787496, 53.5082431857917),
+            Offset(53.369891930287494, 53.38396782349169),
+            Offset(53.2500549737875, 53.25969246119169),
+            Offset(53.1302180172875, 53.13541709889169),
+            Offset(53.0103810606875, 53.011141736591696),
+            Offset(52.8905441041875, 52.8868663742917),
+            Offset(52.770707147687496, 52.7625910118917),
+            Offset(52.650870191187494, 52.63831564959169),
+            Offset(52.5310332346875, 52.51404028729169),
+            Offset(52.411196278087495, 52.389764924991695),
+            Offset(52.2913593215875, 52.2654895626917),
+            Offset(52.1715223650875, 52.1412142002917),
+            Offset(52.051685408587495, 52.01693883799169),
+            Offset(51.9318484519875, 51.89266347569169),
+            Offset(51.812011495487496, 51.768388113391694),
+            Offset(51.692174538987494, 51.6441127510917),
+            Offset(51.5723375824875, 51.5198373886917),
+            Offset(51.4525006259875, 51.39556202639169),
+            Offset(51.33266366938749, 51.27128666409169),
+            Offset(51.2128267128875, 51.14701130179169),
+            Offset(51.092989756387496, 51.022735939491696),
+            Offset(50.973152799887494, 50.8984605771917),
+            Offset(50.8533158432875, 50.7741852147917),
+            Offset(50.733478886787495, 50.64990985249169),
+            Offset(50.6136419302875, 50.52563449019169),
+            Offset(50.4938049737875, 50.401359127891695),
+            Offset(50.373968017287496, 50.2770837655917),
+            Offset(50.2541310606875, 50.1528084031917),
+            Offset(50.1342941041875, 50.02853304089169),
+          ],
+          <Offset>[
+            Offset(54.90270712071989, 26.419565760864774),
+            Offset(54.90270712071989, 26.419565760864774),
+            Offset(54.90270712071989, 26.419565760864774),
+            Offset(54.90270712071989, 26.419565760864774),
+            Offset(54.90270712071989, 26.419565760864774),
+            Offset(54.90270712071989, 26.419565760864774),
+            Offset(54.90270712071989, 26.419565760864774),
+            Offset(54.90270712071989, 26.419565760864774),
+            Offset(54.90270712071989, 26.419565760864774),
+            Offset(54.90270712071989, 26.419565760864774),
+            Offset(54.71531815301439, 27.21926680424857),
+            Offset(53.71654072312347, 31.18281367228795),
+            Offset(52.62928729387452, 35.1962381308779),
+            Offset(51.53045534970737, 39.21738368206378),
+            Offset(50.52706670358306, 43.187670298244306),
+            Offset(49.7854889546519, 47.01626356414165),
+            Offset(50.283999172374806, 50.165899443393734),
+            Offset(52.42717102172652, 52.417745301530715),
+            Offset(53.74879696492653, 53.77618807553071),
+            Offset(53.62896000832653, 53.651912713230715),
+            Offset(53.50912305182652, 53.527637350830716),
+            Offset(53.389286095326526, 53.40336198853072),
+            Offset(53.26944913882653, 53.27908662623072),
+            Offset(53.14961218232652, 53.154811263930725),
+            Offset(53.029775225726524, 53.030535901630714),
+            Offset(52.90993826922653, 52.90626053933072),
+            Offset(52.79010131272652, 52.78198517693072),
+            Offset(52.670264356226525, 52.65770981463072),
+            Offset(52.55042739972653, 52.533434452330724),
+            Offset(52.43059044312652, 52.40915909003071),
+            Offset(52.310753486626524, 52.284883727730715),
+            Offset(52.19091653012653, 52.16060836533072),
+            Offset(52.07107957362652, 52.03633300303072),
+            Offset(51.95124261702652, 51.91205764073072),
+            Offset(51.83140566052653, 51.78778227843071),
+            Offset(51.71156870402652, 51.663506916130714),
+            Offset(51.591731747526524, 51.539231553730716),
+            Offset(51.47189479102653, 51.41495619143072),
+            Offset(51.35205783442652, 51.29068082913072),
+            Offset(51.23222087792652, 51.166405466830724),
+            Offset(51.11238392142653, 51.04213010453071),
+            Offset(50.99254696492652, 50.917854742230716),
+            Offset(50.87271000832652, 50.79357937983072),
+            Offset(50.752873051826526, 50.66930401753072),
+            Offset(50.63303609532653, 50.54502865523072),
+            Offset(50.51319913882652, 50.42075329293071),
+            Offset(50.39336218232653, 50.296477930630715),
+            Offset(50.27352522572653, 50.172202568230716),
+            Offset(50.15368826922652, 50.04792720593072),
+          ],
+          <Offset>[
+            Offset(54.90270712071989, 36.41758712071989),
+            Offset(54.90270712071989, 36.41758712071989),
+            Offset(54.90270712071989, 36.41758712071989),
+            Offset(54.90270712071989, 36.41758712071989),
+            Offset(54.90270712071989, 36.41758712071989),
+            Offset(54.90270712071989, 36.41758712071989),
+            Offset(54.90270712071989, 36.41758712071989),
+            Offset(54.90270712071989, 36.41758712071989),
+            Offset(54.90270712071989, 36.41758712071989),
+            Offset(54.90270712071989, 36.41758712071989),
+            Offset(54.71531815301439, 36.87053160703761),
+            Offset(53.71654072312347, 39.09029363766916),
+            Offset(52.62928729387452, 41.31071235333787),
+            Offset(51.53045534970737, 43.53198610663798),
+            Offset(50.52706670358306, 45.75444966750169),
+            Offset(49.7854889546519, 47.97877125585276),
+            Offset(50.283999172374806, 50.20685835445277),
+            Offset(52.42717102172652, 52.44166244630614),
+            Offset(53.74879696492653, 53.800105220306136),
+            Offset(53.62896000832653, 53.67582985800614),
+            Offset(53.50912305182652, 53.55155449560614),
+            Offset(53.389286095326526, 53.427279133306136),
+            Offset(53.26944913882653, 53.30300377100614),
+            Offset(53.14961218232652, 53.17872840870614),
+            Offset(53.029775225726524, 53.05445304640614),
+            Offset(52.90993826922653, 52.93017768410614),
+            Offset(52.79010131272652, 52.80590232170614),
+            Offset(52.670264356226525, 52.68162695940614),
+            Offset(52.55042739972653, 52.55735159710614),
+            Offset(52.43059044312652, 52.43307623480614),
+            Offset(52.310753486626524, 52.30880087250614),
+            Offset(52.19091653012653, 52.18452551010614),
+            Offset(52.07107957362652, 52.06025014780614),
+            Offset(51.95124261702652, 51.93597478550614),
+            Offset(51.83140566052653, 51.811699423206136),
+            Offset(51.71156870402652, 51.68742406090614),
+            Offset(51.591731747526524, 51.56314869850614),
+            Offset(51.47189479102653, 51.438873336206136),
+            Offset(51.35205783442652, 51.31459797390614),
+            Offset(51.23222087792652, 51.19032261160614),
+            Offset(51.11238392142653, 51.06604724930614),
+            Offset(50.99254696492652, 50.94177188700614),
+            Offset(50.87271000832652, 50.81749652460614),
+            Offset(50.752873051826526, 50.69322116230614),
+            Offset(50.63303609532653, 50.56894580000614),
+            Offset(50.51319913882652, 50.44467043770614),
+            Offset(50.39336218232653, 50.32039507540614),
+            Offset(50.27352522572653, 50.19611971300614),
+            Offset(50.15368826922652, 50.07184435070614),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(54.90270712071989, 46.41563917637766),
+            Offset(54.90270712071989, 46.41563917637766),
+            Offset(54.90270712071989, 46.41563917637766),
+            Offset(54.90270712071989, 46.41563917637766),
+            Offset(54.90270712071989, 46.41563917637766),
+            Offset(54.90270712071989, 46.41563917637766),
+            Offset(54.90270712071989, 46.41563917637766),
+            Offset(54.90270712071989, 46.41563917637766),
+            Offset(54.90270712071989, 46.41563917637766),
+            Offset(54.90270712071989, 46.41563917637766),
+            Offset(54.71531815301439, 46.52182603428994),
+            Offset(53.71654072312347, 46.99779784106601),
+            Offset(52.62928729387452, 47.42520527750929),
+            Offset(51.53045534970737, 47.84660167834321),
+            Offset(50.52706670358306, 48.32123679418471),
+            Offset(49.7854889546519, 48.94128176513705),
+            Offset(50.283999172374806, 50.247817265511806),
+            Offset(52.42717102172652, 52.46557959108156),
+            Offset(53.74879696492653, 53.824022365081554),
+            Offset(53.62896000832653, 53.69974700278156),
+            Offset(53.50912305182652, 53.57547164038156),
+            Offset(53.389286095326526, 53.451196278081554),
+            Offset(53.26944913882653, 53.32692091578156),
+            Offset(53.14961218232652, 53.20264555348156),
+            Offset(53.029775225726524, 53.078370191181556),
+            Offset(52.90993826922653, 52.95409482888156),
+            Offset(52.79010131272652, 52.82981946648156),
+            Offset(52.670264356226525, 52.705544104181556),
+            Offset(52.55042739972653, 52.58126874188156),
+            Offset(52.43059044312652, 52.456993379581554),
+            Offset(52.310753486626524, 52.33271801728156),
+            Offset(52.19091653012653, 52.20844265488156),
+            Offset(52.07107957362652, 52.084167292581554),
+            Offset(51.95124261702652, 51.95989193028156),
+            Offset(51.83140566052653, 51.83561656798155),
+            Offset(51.71156870402652, 51.711341205681556),
+            Offset(51.591731747526524, 51.58706584328156),
+            Offset(51.47189479102653, 51.46279048098155),
+            Offset(51.35205783442652, 51.338515118681556),
+            Offset(51.23222087792652, 51.21423975638156),
+            Offset(51.11238392142653, 51.089964394081555),
+            Offset(50.99254696492652, 50.96568903178156),
+            Offset(50.87271000832652, 50.84141366938156),
+            Offset(50.752873051826526, 50.717138307081555),
+            Offset(50.63303609532653, 50.59286294478156),
+            Offset(50.51319913882652, 50.468587582481554),
+            Offset(50.39336218232653, 50.34431222018156),
+            Offset(50.27352522572653, 50.22003685778156),
+            Offset(50.15368826922652, 50.095761495481554),
+          ],
+          <Offset>[
+            Offset(46.79563917637766, 54.522707120719886),
+            Offset(46.79563917637766, 54.522707120719886),
+            Offset(46.79563917637766, 54.522707120719886),
+            Offset(46.79563917637766, 54.522707120719886),
+            Offset(46.79563917637766, 54.522707120719886),
+            Offset(46.79563917637766, 54.522707120719886),
+            Offset(46.79563917637766, 54.522707120719886),
+            Offset(46.79563917637766, 54.522707120719886),
+            Offset(46.79563917637766, 54.522707120719886),
+            Offset(46.79563917637766, 54.522707120719886),
+            Offset(46.88942369051005, 54.34772049679427),
+            Offset(47.30462401296601, 53.40971455122347),
+            Offset(47.67125996500929, 52.38323260637452),
+            Offset(48.031884881450786, 51.34517214659979),
+            Offset(48.44574851300257, 50.4025549847652),
+            Offset(49.00502199955961, 49.72174872023579),
+            Offset(50.25078601552198, 50.28103042236462),
+            Offset(52.4077768566875, 52.48497375612058),
+            Offset(53.729402799887495, 53.84341653012058),
+            Offset(53.6095658432875, 53.71914116782058),
+            Offset(53.489728886787496, 53.59486580542058),
+            Offset(53.369891930287494, 53.47059044312058),
+            Offset(53.2500549737875, 53.34631508082058),
+            Offset(53.1302180172875, 53.222039718520584),
+            Offset(53.0103810606875, 53.09776435622058),
+            Offset(52.8905441041875, 52.97348899392058),
+            Offset(52.770707147687496, 52.849213631520584),
+            Offset(52.650870191187494, 52.72493826922058),
+            Offset(52.5310332346875, 52.60066290692058),
+            Offset(52.411196278087495, 52.47638754462058),
+            Offset(52.2913593215875, 52.35211218232058),
+            Offset(52.1715223650875, 52.22783681992058),
+            Offset(52.051685408587495, 52.10356145762058),
+            Offset(51.9318484519875, 51.97928609532058),
+            Offset(51.812011495487496, 51.85501073302058),
+            Offset(51.692174538987494, 51.73073537072058),
+            Offset(51.5723375824875, 51.60646000832058),
+            Offset(51.4525006259875, 51.48218464602058),
+            Offset(51.33266366938749, 51.35790928372058),
+            Offset(51.2128267128875, 51.233633921420584),
+            Offset(51.092989756387496, 51.10935855912058),
+            Offset(50.973152799887494, 50.98508319682058),
+            Offset(50.8533158432875, 50.860807834420584),
+            Offset(50.733478886787495, 50.73653247212058),
+            Offset(50.6136419302875, 50.61225710982058),
+            Offset(50.4938049737875, 50.48798174752058),
+            Offset(50.373968017287496, 50.36370638522058),
+            Offset(50.2541310606875, 50.23943102282058),
+            Offset(50.1342941041875, 50.11515566052058),
+          ],
+          <Offset>[
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(36.79758712071989, 54.522707120719886),
+            Offset(37.238129263257726, 54.34772049679427),
+            Offset(39.39711980956916, 53.40971455122347),
+            Offset(41.55676704083787, 52.38323260637452),
+            Offset(43.717269309745554, 51.34517214659979),
+            Offset(45.878961386319546, 50.4025549847652),
+            Offset(48.04251149026888, 49.72174872023579),
+            Offset(50.209827104462946, 50.28103042236462),
+            Offset(52.38385971191208, 52.48497375612058),
+            Offset(53.70548565511208, 53.84341653012058),
+            Offset(53.58564869851208, 53.71914116782058),
+            Offset(53.46581174201208, 53.59486580542058),
+            Offset(53.34597478551208, 53.47059044312058),
+            Offset(53.22613782901208, 53.34631508082058),
+            Offset(53.10630087251208, 53.222039718520584),
+            Offset(52.98646391591208, 53.09776435622058),
+            Offset(52.86662695941208, 52.97348899392058),
+            Offset(52.74679000291208, 52.849213631520584),
+            Offset(52.626953046412076, 52.72493826922058),
+            Offset(52.50711608991208, 52.60066290692058),
+            Offset(52.38727913331208, 52.47638754462058),
+            Offset(52.26744217681208, 52.35211218232058),
+            Offset(52.14760522031208, 52.22783681992058),
+            Offset(52.02776826381208, 52.10356145762058),
+            Offset(51.90793130721208, 51.97928609532058),
+            Offset(51.78809435071208, 51.85501073302058),
+            Offset(51.66825739421208, 51.73073537072058),
+            Offset(51.54842043771208, 51.60646000832058),
+            Offset(51.42858348121208, 51.48218464602058),
+            Offset(51.308746524612076, 51.35790928372058),
+            Offset(51.18890956811208, 51.233633921420584),
+            Offset(51.06907261161208, 51.10935855912058),
+            Offset(50.949235655112076, 50.98508319682058),
+            Offset(50.82939869851208, 50.860807834420584),
+            Offset(50.70956174201208, 50.73653247212058),
+            Offset(50.58972478551208, 50.61225710982058),
+            Offset(50.46988782901208, 50.48798174752058),
+            Offset(50.35005087251208, 50.36370638522058),
+            Offset(50.23021391591208, 50.23943102282058),
+            Offset(50.11037695941208, 50.11515566052058),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+            Offset(60.937745291919896, 54.52270224391989),
+          ],
+          <Offset>[
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+          ],
+          <Offset>[
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+            Offset(57.739181458913336, 54.52270224391989),
+          ],
+          <Offset>[
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.39144480852299, 53.174996289330196),
+            Offset(56.43397725114974, 53.217528731956946),
+            Offset(56.47650969377649, 53.2600611745837),
+            Offset(56.51904213640324, 53.30259361721045),
+            Offset(56.561574579029994, 53.3451260598372),
+            Offset(56.604107021656745, 53.38765850246395),
+            Offset(56.62684855989554, 53.41109453319635),
+            Offset(56.60877135783367, 53.39514421439123),
+            Offset(56.5906941557718, 53.37919389558813),
+            Offset(56.572616953707914, 53.363243576785024),
+            Offset(56.55453975164605, 53.347293257979906),
+            Offset(56.53646254958216, 53.33134293917681),
+            Offset(56.518385347520294, 53.3153926203737),
+            Offset(56.50030814545842, 53.29944230156859),
+            Offset(56.48223094339454, 53.28349198276548),
+            Offset(56.464153741332666, 53.267541663962376),
+            Offset(56.44607653927079, 53.251591345157266),
+            Offset(56.42799933720691, 53.23564102635416),
+            Offset(56.40992213514504, 53.21969070755106),
+            Offset(56.39184493308116, 53.20374038874594),
+            Offset(56.373767731019285, 53.187790069942835),
+            Offset(56.35569052895741, 53.171839751139736),
+            Offset(56.33761332689353, 53.15588943233462),
+            Offset(56.31953612483166, 53.13993911353152),
+            Offset(56.30145892276979, 53.12398879472841),
+            Offset(56.283381720705904, 53.108038475923294),
+            Offset(56.26530451864404, 53.092088157120195),
+            Offset(56.24722731658217, 53.07613783831709),
+            Offset(56.22915011451828, 53.06018751951198),
+            Offset(56.211072912456416, 53.04423720070887),
+            Offset(56.19299571039253, 53.02828688190577),
+            Offset(56.174918508330656, 53.012336563100654),
+            Offset(56.15684130626879, 52.996386244297554),
+            Offset(56.1387641042049, 52.98043592549445),
+            Offset(56.120686902143035, 52.96448560668933),
+            Offset(56.10260970008116, 52.94853528788623),
+            Offset(56.08453249801728, 52.93258496908312),
+            Offset(56.06645529595541, 52.916634650278006),
+            Offset(56.04837809389354, 52.900684331474906),
+          ],
+          <Offset>[
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.3827647181903, 53.16631619899751),
+            Offset(56.39144480852299, 53.174996289330196),
+            Offset(56.43397725114974, 53.217528731956946),
+            Offset(56.47650969377649, 53.2600611745837),
+            Offset(56.51904213640324, 53.30259361721045),
+            Offset(56.561574579029994, 53.3451260598372),
+            Offset(56.604107021656745, 53.38765850246395),
+            Offset(56.62684855989554, 53.41109453319635),
+            Offset(56.60877135783367, 53.39514421439123),
+            Offset(56.5906941557718, 53.37919389558813),
+            Offset(56.572616953707914, 53.363243576785024),
+            Offset(56.55453975164605, 53.347293257979906),
+            Offset(56.53646254958216, 53.33134293917681),
+            Offset(56.518385347520294, 53.3153926203737),
+            Offset(56.50030814545842, 53.29944230156859),
+            Offset(56.48223094339454, 53.28349198276548),
+            Offset(56.464153741332666, 53.267541663962376),
+            Offset(56.44607653927079, 53.251591345157266),
+            Offset(56.42799933720691, 53.23564102635416),
+            Offset(56.40992213514504, 53.21969070755106),
+            Offset(56.39184493308116, 53.20374038874594),
+            Offset(56.373767731019285, 53.187790069942835),
+            Offset(56.35569052895741, 53.171839751139736),
+            Offset(56.33761332689353, 53.15588943233462),
+            Offset(56.31953612483166, 53.13993911353152),
+            Offset(56.30145892276979, 53.12398879472841),
+            Offset(56.283381720705904, 53.108038475923294),
+            Offset(56.26530451864404, 53.092088157120195),
+            Offset(56.24722731658217, 53.07613783831709),
+            Offset(56.22915011451828, 53.06018751951198),
+            Offset(56.211072912456416, 53.04423720070887),
+            Offset(56.19299571039253, 53.02828688190577),
+            Offset(56.174918508330656, 53.012336563100654),
+            Offset(56.15684130626879, 52.996386244297554),
+            Offset(56.1387641042049, 52.98043592549445),
+            Offset(56.120686902143035, 52.96448560668933),
+            Offset(56.10260970008116, 52.94853528788623),
+            Offset(56.08453249801728, 52.93258496908312),
+            Offset(56.06645529595541, 52.916634650278006),
+            Offset(56.04837809389354, 52.900684331474906),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(56.3827647181903, 53.16627596539751),
+            Offset(56.3827647181903, 53.16627596539751),
+            Offset(56.3827647181903, 53.16627596539751),
+            Offset(56.3827647181903, 53.16627596539751),
+            Offset(56.3827647181903, 53.16627596539751),
+            Offset(56.3827647181903, 53.16627596539751),
+            Offset(56.3827647181903, 53.16627596539751),
+            Offset(56.3827647181903, 53.16627596539751),
+            Offset(56.3827647181903, 53.16627596539751),
+            Offset(56.3827647181903, 53.16627596539751),
+            Offset(56.39144480852299, 53.1749560557302),
+            Offset(56.43397725114974, 53.21748849835695),
+            Offset(56.47650969377649, 53.2600209409837),
+            Offset(56.51904213640324, 53.302553383610444),
+            Offset(56.561574579029994, 53.345085826237195),
+            Offset(56.604107021656745, 53.387618268863946),
+            Offset(56.62684855989554, 53.411054299596344),
+            Offset(56.60877135783367, 53.395103980791234),
+            Offset(56.5906941557718, 53.37915366198813),
+            Offset(56.572616953707914, 53.36320334318502),
+            Offset(56.55453975164605, 53.34725302437991),
+            Offset(56.53646254958216, 53.3313027055768),
+            Offset(56.518385347520294, 53.315352386773704),
+            Offset(56.50030814545842, 53.299402067968586),
+            Offset(56.48223094339454, 53.28345174916548),
+            Offset(56.464153741332666, 53.26750143036238),
+            Offset(56.44607653927079, 53.25155111155726),
+            Offset(56.42799933720691, 53.23560079275416),
+            Offset(56.40992213514504, 53.219650473951056),
+            Offset(56.39184493308116, 53.20370015514594),
+            Offset(56.373767731019285, 53.18774983634284),
+            Offset(56.35569052895741, 53.17179951753973),
+            Offset(56.33761332689353, 53.15584919873462),
+            Offset(56.31953612483166, 53.139898879931515),
+            Offset(56.30145892276979, 53.123948561128415),
+            Offset(56.283381720705904, 53.1079982423233),
+            Offset(56.26530451864404, 53.0920479235202),
+            Offset(56.24722731658217, 53.07609760471709),
+            Offset(56.22915011451828, 53.060147285911974),
+            Offset(56.211072912456416, 53.044196967108874),
+            Offset(56.19299571039253, 53.02824664830577),
+            Offset(56.174918508330656, 53.01229632950065),
+            Offset(56.15684130626879, 52.99634601069755),
+            Offset(56.1387641042049, 52.980395691894444),
+            Offset(56.120686902143035, 52.96444537308933),
+            Offset(56.10260970008116, 52.94849505428623),
+            Offset(56.08453249801728, 52.93254473548313),
+            Offset(56.06645529595541, 52.91659441667801),
+            Offset(56.04837809389354, 52.9006440978749),
+          ],
+          <Offset>[
+            Offset(53.54631924699953, 56.02289811551084),
+            Offset(53.54631924699953, 56.02289811551084),
+            Offset(53.54631924699953, 56.02289811551084),
+            Offset(53.54631924699953, 56.02289811551084),
+            Offset(53.54631924699953, 56.02289811551084),
+            Offset(53.54631924699953, 56.02289811551084),
+            Offset(53.54631924699953, 56.02289811551084),
+            Offset(53.54631924699953, 56.02289811551084),
+            Offset(53.54631924699953, 56.02289811551084),
+            Offset(53.54631924699953, 56.02289811551084),
+            Offset(53.55499933733222, 56.03157820584353),
+            Offset(53.59753177995896, 56.07411064847028),
+            Offset(53.64006422258572, 56.11664309109703),
+            Offset(53.682596665212465, 56.15917553372378),
+            Offset(53.725129107839216, 56.201707976350534),
+            Offset(53.76766155046597, 56.244240418977284),
+            Offset(53.790403088704764, 56.26767644970968),
+            Offset(53.77232588664289, 56.251726130904565),
+            Offset(53.75424868458102, 56.235775812101465),
+            Offset(53.736171482517136, 56.21982549329836),
+            Offset(53.71809428045527, 56.20387517449325),
+            Offset(53.70001707839138, 56.18792485569014),
+            Offset(53.68193987632951, 56.171974536887035),
+            Offset(53.66386267426764, 56.156024218081924),
+            Offset(53.645785472203755, 56.14007389927882),
+            Offset(53.62770827014189, 56.12412358047571),
+            Offset(53.60963106808002, 56.1081732616706),
+            Offset(53.591553866016135, 56.092222942867494),
+            Offset(53.57347666395426, 56.076272624064394),
+            Offset(53.55539946189038, 56.06032230525928),
+            Offset(53.53732225982851, 56.04437198645618),
+            Offset(53.51924505776664, 56.02842166765307),
+            Offset(53.501167855702754, 56.01247134884795),
+            Offset(53.483090653640886, 55.99652103004485),
+            Offset(53.46501345157901, 55.980570711241754),
+            Offset(53.44693624951513, 55.964620392436636),
+            Offset(53.42885904745326, 55.94867007363353),
+            Offset(53.41078184539139, 55.93271975483043),
+            Offset(53.392704643327505, 55.91676943602531),
+            Offset(53.37462744126564, 55.90081911722221),
+            Offset(53.35655023920175, 55.884868798419106),
+            Offset(53.33847303713988, 55.86891847961399),
+            Offset(53.32039583507801, 55.85296816081089),
+            Offset(53.302318633014124, 55.83701784200778),
+            Offset(53.28424143095226, 55.821067523202665),
+            Offset(53.26616422889038, 55.805117204399565),
+            Offset(53.248087026826504, 55.78916688559646),
+            Offset(53.23000982476463, 55.77321656679135),
+            Offset(53.21193262270276, 55.75726624798824),
+          ],
+          <Offset>[
+            Offset(53.54628855119686, 56.02289811551084),
+            Offset(53.54628855119686, 56.02289811551084),
+            Offset(53.54628855119686, 56.02289811551084),
+            Offset(53.54628855119686, 56.02289811551084),
+            Offset(53.54628855119686, 56.02289811551084),
+            Offset(53.54628855119686, 56.02289811551084),
+            Offset(53.54628855119686, 56.02289811551084),
+            Offset(53.54628855119686, 56.02289811551084),
+            Offset(53.54628855119686, 56.02289811551084),
+            Offset(53.54628855119686, 56.02289811551084),
+            Offset(53.55496864152955, 56.03157820584353),
+            Offset(53.5975010841563, 56.07411064847028),
+            Offset(53.64003352678305, 56.11664309109703),
+            Offset(53.6825659694098, 56.15917553372378),
+            Offset(53.72509841203655, 56.201707976350534),
+            Offset(53.7676308546633, 56.244240418977284),
+            Offset(53.79037239290209, 56.26767644970968),
+            Offset(53.772295190840225, 56.251726130904565),
+            Offset(53.75421798877835, 56.235775812101465),
+            Offset(53.73614078671447, 56.21982549329836),
+            Offset(53.7180635846526, 56.20387517449325),
+            Offset(53.69998638258872, 56.18792485569014),
+            Offset(53.681909180526844, 56.171974536887035),
+            Offset(53.66383197846497, 56.156024218081924),
+            Offset(53.64575477640109, 56.14007389927882),
+            Offset(53.62767757433922, 56.12412358047571),
+            Offset(53.60960037227735, 56.1081732616706),
+            Offset(53.59152317021346, 56.092222942867494),
+            Offset(53.573445968151596, 56.076272624064394),
+            Offset(53.55536876608771, 56.06032230525928),
+            Offset(53.53729156402584, 56.04437198645618),
+            Offset(53.51921436196397, 56.02842166765307),
+            Offset(53.50113715990009, 56.01247134884795),
+            Offset(53.483059957838215, 55.99652103004485),
+            Offset(53.46498275577635, 55.980570711241754),
+            Offset(53.44690555371246, 55.964620392436636),
+            Offset(53.428828351650594, 55.94867007363353),
+            Offset(53.41075114958872, 55.93271975483043),
+            Offset(53.392673947524834, 55.91676943602531),
+            Offset(53.374596745462966, 55.90081911722221),
+            Offset(53.35651954339908, 55.884868798419106),
+            Offset(53.33844234133721, 55.86891847961399),
+            Offset(53.32036513927534, 55.85296816081089),
+            Offset(53.30228793721146, 55.83701784200778),
+            Offset(53.284210735149586, 55.821067523202665),
+            Offset(53.26613353308772, 55.805117204399565),
+            Offset(53.24805633102383, 55.78916688559646),
+            Offset(53.229979128961965, 55.77321656679135),
+            Offset(53.21190192690009, 55.75726624798824),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(53.54628855119686, 56.022857881910845),
+            Offset(53.54628855119686, 56.022857881910845),
+            Offset(53.54628855119686, 56.022857881910845),
+            Offset(53.54628855119686, 56.022857881910845),
+            Offset(53.54628855119686, 56.022857881910845),
+            Offset(53.54628855119686, 56.022857881910845),
+            Offset(53.54628855119686, 56.022857881910845),
+            Offset(53.54628855119686, 56.022857881910845),
+            Offset(53.54628855119686, 56.022857881910845),
+            Offset(53.54628855119686, 56.022857881910845),
+            Offset(53.55496864152955, 56.031537972243534),
+            Offset(53.5975010841563, 56.074070414870285),
+            Offset(53.64003352678305, 56.116602857497035),
+            Offset(53.6825659694098, 56.159135300123786),
+            Offset(53.72509841203655, 56.20166774275054),
+            Offset(53.7676308546633, 56.24420018537729),
+            Offset(53.79037239290209, 56.26763621610968),
+            Offset(53.772295190840225, 56.25168589730457),
+            Offset(53.75421798877835, 56.23573557850146),
+            Offset(53.73614078671447, 56.21978525969836),
+            Offset(53.7180635846526, 56.203834940893245),
+            Offset(53.69998638258872, 56.18788462209014),
+            Offset(53.681909180526844, 56.17193430328704),
+            Offset(53.66383197846497, 56.15598398448192),
+            Offset(53.64575477640109, 56.14003366567882),
+            Offset(53.62767757433922, 56.124083346875715),
+            Offset(53.60960037227735, 56.1081330280706),
+            Offset(53.59152317021346, 56.0921827092675),
+            Offset(53.573445968151596, 56.0762323904644),
+            Offset(53.55536876608771, 56.06028207165928),
+            Offset(53.53729156402584, 56.04433175285617),
+            Offset(53.51921436196397, 56.028381434053074),
+            Offset(53.50113715990009, 56.012431115247956),
+            Offset(53.483059957838215, 55.99648079644485),
+            Offset(53.46498275577635, 55.98053047764175),
+            Offset(53.44690555371246, 55.96458015883663),
+            Offset(53.428828351650594, 55.94862984003353),
+            Offset(53.41075114958872, 55.932679521230426),
+            Offset(53.392673947524834, 55.916729202425316),
+            Offset(53.374596745462966, 55.90077888362221),
+            Offset(53.35651954339908, 55.8848285648191),
+            Offset(53.33844234133721, 55.86887824601399),
+            Offset(53.32036513927534, 55.852927927210885),
+            Offset(53.30228793721146, 55.83697760840778),
+            Offset(53.284210735149586, 55.82102728960267),
+            Offset(53.26613353308772, 55.80507697079956),
+            Offset(53.24805633102383, 55.78912665199646),
+            Offset(53.229979128961965, 55.773176333191344),
+            Offset(53.21190192690009, 55.75722601438824),
+          ],
+          <Offset>[
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+          ],
+          <Offset>[
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291921905, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291921905, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.379314856233876),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.37931485623388),
+            Offset(54.902705291919894, 57.379314856233876),
+          ],
+          <Offset>[
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291921905, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+          ],
+          <Offset>[
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291921905, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291921905, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+            Offset(54.902705291919894, 60.557742243937994),
+          ],
+          <Offset>[
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(74.32497336367568, 79.93649262312883),
+            Offset(70.92176691523076, 76.5170858263241),
+            Offset(67.51856046678586, 73.09767902951937),
+            Offset(64.11535401834094, 69.67827223271463),
+            Offset(60.71214756989602, 66.25886543593002),
+            Offset(57.30894112147122, 62.83945863912528),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.01698575823052, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+          ],
+          <Offset>[
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(74.32497336367568, 79.93649262312883),
+            Offset(70.92176691523076, 76.5170858263241),
+            Offset(67.51856046678586, 73.09767902951937),
+            Offset(64.11535401834094, 69.67827223271463),
+            Offset(60.71214756989602, 66.25886543593002),
+            Offset(57.30894112147122, 62.83945863912528),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.01698575823052, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(75.0195052919199, 80.63433074491833),
+            Offset(74.32497336367568, 79.93649262312883),
+            Offset(70.92176691523076, 76.5170858263241),
+            Offset(67.51856046678586, 73.09767902951937),
+            Offset(64.11535401834094, 69.67827223271463),
+            Offset(60.71214756989602, 66.25886543593002),
+            Offset(57.30894112147122, 62.83945863912528),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.01698575823052, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+            Offset(55.016985758228515, 60.536592837199635),
+          ],
+          <Offset>[
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(80.31980186465601, 73.9416641221485),
+            Offset(76.9165954162111, 70.52225732534376),
+            Offset(73.51338896776619, 67.10285052853904),
+            Offset(70.11018251932127, 63.6834437317343),
+            Offset(66.70697607087635, 60.26403693494969),
+            Offset(63.30376962245156, 56.84463013814495),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425921086, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+          ],
+          <Offset>[
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(80.31980186465601, 73.9416641221485),
+            Offset(76.9165954162111, 70.52225732534376),
+            Offset(73.51338896776619, 67.10285052853904),
+            Offset(70.11018251932127, 63.6834437317343),
+            Offset(66.70697607087635, 60.26403693494969),
+            Offset(63.30376962245156, 56.84463013814495),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425921086, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(81.01433379290023, 74.639502243938),
+            Offset(80.31980186465601, 73.9416641221485),
+            Offset(76.9165954162111, 70.52225732534376),
+            Offset(73.51338896776619, 67.10285052853904),
+            Offset(70.11018251932127, 63.6834437317343),
+            Offset(66.70697607087635, 60.26403693494969),
+            Offset(63.30376962245156, 56.84463013814495),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425921086, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+            Offset(61.01181425920885, 54.5417643362193),
+          ],
+          <Offset>[
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529194001, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224391788),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.93774529194001, 54.52270224393799),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192572, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+          ],
+          <Offset>[
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529191989, 54.52270224393799),
+            Offset(60.93774529194001, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.937745291919896, 54.52270224391788),
+            Offset(60.937745291919896, 54.52270224393799),
+            Offset(60.93774529194001, 54.52270224393799),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192572, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+            Offset(60.93774529192372, 54.52270224393415),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 54.754867779413956),
+            Offset(48.0, 51.604258802513954),
+            Offset(48.0, 48.45364982561395),
+            Offset(48.0, 45.30304084871395),
+            Offset(48.0, 42.15243187181395),
+            Offset(48.0, 39.00182289491395),
+            Offset(48.0, 35.85121391801395),
+            Offset(48.0, 34.05512107195435),
+            Offset(48.0, 33.540905901349916),
+            Offset(48.0, 33.12129231086275),
+            Offset(48.0, 32.781503706824346),
+            Offset(48.0, 32.50949551311259),
+            Offset(48.0, 32.29485026134325),
+            Offset(48.0, 32.1283960707288),
+            Offset(48.0, 32.00233286349681),
+            Offset(48.0, 31.909924001704802),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 55.9765324848),
+            Offset(52.445826306158004, 54.754867779413956),
+            Offset(52.445826306158004, 51.604258802513954),
+            Offset(52.445826306158004, 48.45364982561395),
+            Offset(52.445826306158004, 45.30304084871395),
+            Offset(52.445826306158004, 42.15243187181395),
+            Offset(52.445826306158004, 39.00182289491395),
+            Offset(52.445826306158004, 35.85121391801395),
+            Offset(52.445826306158004, 34.05512107195435),
+            Offset(52.445826306158004, 33.540905901349916),
+            Offset(52.445826306158004, 33.12129231086275),
+            Offset(52.445826306158004, 32.781503706824346),
+            Offset(52.445826306158004, 32.50949551311259),
+            Offset(52.445826306158004, 32.29485026134325),
+            Offset(52.445826306158004, 32.1283960707288),
+            Offset(52.445826306158004, 32.00233286349681),
+            Offset(52.445826306158004, 31.909924001704802),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+            Offset(52.445826306158004, 31.90656),
+          ],
+          <Offset>[
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 52.37560809515534),
+            Offset(56.04672, 51.153943389769296),
+            Offset(56.04672, 48.00333441286929),
+            Offset(56.04672, 44.85272543596929),
+            Offset(56.04672, 41.70211645906929),
+            Offset(56.04672, 38.551507482169285),
+            Offset(56.04672, 35.40089850526928),
+            Offset(56.04672, 32.25028952836928),
+            Offset(56.04672, 30.45419668230969),
+            Offset(56.04672, 29.939981511705252),
+            Offset(56.04672, 29.520367921218085),
+            Offset(56.04672, 29.18057931717968),
+            Offset(56.04672, 28.908571123467926),
+            Offset(56.04672, 28.693925871698585),
+            Offset(56.04672, 28.527471681084133),
+            Offset(56.04672, 28.401408473852147),
+            Offset(56.04672, 28.308999612060138),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+            Offset(56.04672, 28.305635610355335),
+          ],
+          <Offset>[
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 47.9298124848),
+            Offset(56.04672, 46.708147779413956),
+            Offset(56.04672, 43.55753880251395),
+            Offset(56.04672, 40.40692982561395),
+            Offset(56.04672, 37.25632084871395),
+            Offset(56.04672, 34.10571187181395),
+            Offset(56.04672, 30.95510289491395),
+            Offset(56.04672, 27.804493918013947),
+            Offset(56.04672, 26.008401071954353),
+            Offset(56.04672, 25.494185901349915),
+            Offset(56.04672, 25.07457231086275),
+            Offset(56.04672, 24.734783706824345),
+            Offset(56.04672, 24.46277551311259),
+            Offset(56.04672, 24.24813026134325),
+            Offset(56.04672, 24.081676070728797),
+            Offset(56.04672, 23.95561286349681),
+            Offset(56.04672, 23.8632040017048),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+            Offset(56.04672, 23.85984),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 43.483986178642),
+            Offset(56.04672, 42.26232147325595),
+            Offset(56.04672, 39.11171249635595),
+            Offset(56.04672, 35.961103519455946),
+            Offset(56.04672, 32.810494542555944),
+            Offset(56.04672, 29.65988556565595),
+            Offset(56.04672, 26.50927658875595),
+            Offset(56.04672, 23.358667611855946),
+            Offset(56.04672, 21.562574765796352),
+            Offset(56.04672, 21.048359595191915),
+            Offset(56.04672, 20.628746004704748),
+            Offset(56.04672, 20.288957400666344),
+            Offset(56.04672, 20.01694920695459),
+            Offset(56.04672, 19.80230395518525),
+            Offset(56.04672, 19.635849764570796),
+            Offset(56.04672, 19.50978655733881),
+            Offset(56.04672, 19.4173776955468),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+            Offset(56.04672, 19.414013693841998),
+          ],
+          <Offset>[
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 39.8830924848),
+            Offset(52.445826306158004, 38.661427779413955),
+            Offset(52.445826306158004, 35.51081880251395),
+            Offset(52.445826306158004, 32.36020982561395),
+            Offset(52.445826306158004, 29.209600848713947),
+            Offset(52.445826306158004, 26.05899187181395),
+            Offset(52.445826306158004, 22.90838289491395),
+            Offset(52.445826306158004, 19.757773918013946),
+            Offset(52.445826306158004, 17.961681071954352),
+            Offset(52.445826306158004, 17.447465901349915),
+            Offset(52.445826306158004, 17.027852310862748),
+            Offset(52.445826306158004, 16.688063706824344),
+            Offset(52.445826306158004, 16.41605551311259),
+            Offset(52.445826306158004, 16.20141026134325),
+            Offset(52.445826306158004, 16.034956070728796),
+            Offset(52.445826306158004, 15.90889286349681),
+            Offset(52.445826306158004, 15.8164840017048),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+            Offset(52.445826306158004, 15.813119999999998),
+          ],
+          <Offset>[
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 39.8830924848),
+            Offset(48.0, 38.661427779413955),
+            Offset(48.0, 35.51081880251395),
+            Offset(48.0, 32.36020982561395),
+            Offset(48.0, 29.209600848713947),
+            Offset(48.0, 26.05899187181395),
+            Offset(48.0, 22.90838289491395),
+            Offset(48.0, 19.757773918013946),
+            Offset(48.0, 17.961681071954352),
+            Offset(48.0, 17.447465901349915),
+            Offset(48.0, 17.027852310862748),
+            Offset(48.0, 16.688063706824344),
+            Offset(48.0, 16.41605551311259),
+            Offset(48.0, 16.20141026134325),
+            Offset(48.0, 16.034956070728796),
+            Offset(48.0, 15.90889286349681),
+            Offset(48.0, 15.8164840017048),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+            Offset(48.0, 15.813119999999998),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 39.8830924848),
+            Offset(43.554173693841996, 38.661427779413955),
+            Offset(43.554173693841996, 35.51081880251395),
+            Offset(43.554173693841996, 32.36020982561395),
+            Offset(43.554173693841996, 29.209600848713947),
+            Offset(43.554173693841996, 26.05899187181395),
+            Offset(43.554173693841996, 22.90838289491395),
+            Offset(43.554173693841996, 19.757773918013946),
+            Offset(43.554173693841996, 17.961681071954352),
+            Offset(43.554173693841996, 17.447465901349915),
+            Offset(43.554173693841996, 17.027852310862748),
+            Offset(43.554173693841996, 16.688063706824344),
+            Offset(43.554173693841996, 16.41605551311259),
+            Offset(43.554173693841996, 16.20141026134325),
+            Offset(43.554173693841996, 16.034956070728796),
+            Offset(43.554173693841996, 15.90889286349681),
+            Offset(43.554173693841996, 15.8164840017048),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+            Offset(43.554173693841996, 15.813119999999998),
+          ],
+          <Offset>[
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 43.483986178642),
+            Offset(39.95328, 42.26232147325595),
+            Offset(39.95328, 39.11171249635595),
+            Offset(39.95328, 35.961103519455946),
+            Offset(39.95328, 32.810494542555944),
+            Offset(39.95328, 29.65988556565595),
+            Offset(39.95328, 26.50927658875595),
+            Offset(39.95328, 23.358667611855946),
+            Offset(39.95328, 21.562574765796352),
+            Offset(39.95328, 21.048359595191915),
+            Offset(39.95328, 20.628746004704748),
+            Offset(39.95328, 20.288957400666344),
+            Offset(39.95328, 20.01694920695459),
+            Offset(39.95328, 19.80230395518525),
+            Offset(39.95328, 19.635849764570796),
+            Offset(39.95328, 19.50978655733881),
+            Offset(39.95328, 19.4173776955468),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+            Offset(39.95328, 19.414013693841998),
+          ],
+          <Offset>[
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 47.9298124848),
+            Offset(39.95328, 46.708147779413956),
+            Offset(39.95328, 43.55753880251395),
+            Offset(39.95328, 40.40692982561395),
+            Offset(39.95328, 37.25632084871395),
+            Offset(39.95328, 34.10571187181395),
+            Offset(39.95328, 30.95510289491395),
+            Offset(39.95328, 27.804493918013947),
+            Offset(39.95328, 26.008401071954353),
+            Offset(39.95328, 25.494185901349915),
+            Offset(39.95328, 25.07457231086275),
+            Offset(39.95328, 24.734783706824345),
+            Offset(39.95328, 24.46277551311259),
+            Offset(39.95328, 24.24813026134325),
+            Offset(39.95328, 24.081676070728797),
+            Offset(39.95328, 23.95561286349681),
+            Offset(39.95328, 23.8632040017048),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+            Offset(39.95328, 23.85984),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 52.37560809515534),
+            Offset(39.95328, 51.153943389769296),
+            Offset(39.95328, 48.00333441286929),
+            Offset(39.95328, 44.85272543596929),
+            Offset(39.95328, 41.70211645906929),
+            Offset(39.95328, 38.551507482169285),
+            Offset(39.95328, 35.40089850526928),
+            Offset(39.95328, 32.25028952836928),
+            Offset(39.95328, 30.45419668230969),
+            Offset(39.95328, 29.939981511705252),
+            Offset(39.95328, 29.520367921218085),
+            Offset(39.95328, 29.18057931717968),
+            Offset(39.95328, 28.908571123467926),
+            Offset(39.95328, 28.693925871698585),
+            Offset(39.95328, 28.527471681084133),
+            Offset(39.95328, 28.401408473852147),
+            Offset(39.95328, 28.308999612060138),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+            Offset(39.95328, 28.305635610355335),
+          ],
+          <Offset>[
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 55.9765324848),
+            Offset(43.554173693841996, 54.754867779413956),
+            Offset(43.554173693841996, 51.604258802513954),
+            Offset(43.554173693841996, 48.45364982561395),
+            Offset(43.554173693841996, 45.30304084871395),
+            Offset(43.554173693841996, 42.15243187181395),
+            Offset(43.554173693841996, 39.00182289491395),
+            Offset(43.554173693841996, 35.85121391801395),
+            Offset(43.554173693841996, 34.05512107195435),
+            Offset(43.554173693841996, 33.540905901349916),
+            Offset(43.554173693841996, 33.12129231086275),
+            Offset(43.554173693841996, 32.781503706824346),
+            Offset(43.554173693841996, 32.50949551311259),
+            Offset(43.554173693841996, 32.29485026134325),
+            Offset(43.554173693841996, 32.1283960707288),
+            Offset(43.554173693841996, 32.00233286349681),
+            Offset(43.554173693841996, 31.909924001704802),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+            Offset(43.554173693841996, 31.90656),
+          ],
+          <Offset>[
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 55.9765324848),
+            Offset(48.0, 54.754867779413956),
+            Offset(48.0, 51.604258802513954),
+            Offset(48.0, 48.45364982561395),
+            Offset(48.0, 45.30304084871395),
+            Offset(48.0, 42.15243187181395),
+            Offset(48.0, 39.00182289491395),
+            Offset(48.0, 35.85121391801395),
+            Offset(48.0, 34.05512107195435),
+            Offset(48.0, 33.540905901349916),
+            Offset(48.0, 33.12129231086275),
+            Offset(48.0, 32.781503706824346),
+            Offset(48.0, 32.50949551311259),
+            Offset(48.0, 32.29485026134325),
+            Offset(48.0, 32.1283960707288),
+            Offset(48.0, 32.00233286349681),
+            Offset(48.0, 31.909924001704802),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+            Offset(48.0, 31.90656),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+            Offset(43.554173693841996, 39.95328),
+          ],
+          <Offset>[
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+            Offset(39.95328, 43.554173693841996),
+          ],
+          <Offset>[
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+            Offset(39.95328, 48.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+            Offset(39.95328, 52.445826306158004),
+          ],
+          <Offset>[
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+            Offset(43.554173693841996, 56.04672),
+          ],
+          <Offset>[
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+            Offset(48.0, 56.04672),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+            Offset(52.445826306158004, 56.04672),
+          ],
+          <Offset>[
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+            Offset(56.04672, 52.445826306158004),
+          ],
+          <Offset>[
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+            Offset(56.04672, 48.0),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+            Offset(56.04672, 43.554173693841996),
+          ],
+          <Offset>[
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+            Offset(52.445826306158004, 39.95328),
+          ],
+          <Offset>[
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+            Offset(48.0, 39.95328),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 41.2763342754),
+            Offset(48.0, 44.422639514400004),
+            Offset(48.0, 47.568944753400004),
+            Offset(48.0, 50.715249992400004),
+            Offset(48.0, 53.861555231400004),
+            Offset(48.0, 57.0078604704),
+            Offset(48.0, 60.154165709400004),
+            Offset(48.0, 61.94780621836377),
+            Offset(48.0, 62.46132080048674),
+            Offset(48.0, 62.88036269183587),
+            Offset(48.0, 63.219688353387404),
+            Offset(48.0, 63.491325951788546),
+            Offset(48.0, 63.70567876161293),
+            Offset(48.0, 63.87190616809776),
+            Offset(48.0, 63.997797621511204),
+            Offset(48.0, 64.09008058170691),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 40.0563383664),
+            Offset(43.554173693841996, 41.2763342754),
+            Offset(43.554173693841996, 44.422639514400004),
+            Offset(43.554173693841996, 47.568944753400004),
+            Offset(43.554173693841996, 50.715249992400004),
+            Offset(43.554173693841996, 53.861555231400004),
+            Offset(43.554173693841996, 57.0078604704),
+            Offset(43.554173693841996, 60.154165709400004),
+            Offset(43.554173693841996, 61.94780621836377),
+            Offset(43.554173693841996, 62.46132080048674),
+            Offset(43.554173693841996, 62.88036269183587),
+            Offset(43.554173693841996, 63.219688353387404),
+            Offset(43.554173693841996, 63.491325951788546),
+            Offset(43.554173693841996, 63.70567876161293),
+            Offset(43.554173693841996, 63.87190616809776),
+            Offset(43.554173693841996, 63.997797621511204),
+            Offset(43.554173693841996, 64.09008058170691),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+            Offset(43.554173693841996, 64.09344),
+          ],
+          <Offset>[
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 43.65726275604466),
+            Offset(39.95328, 44.877258665044664),
+            Offset(39.95328, 48.023563904044664),
+            Offset(39.95328, 51.169869143044664),
+            Offset(39.95328, 54.316174382044665),
+            Offset(39.95328, 57.462479621044665),
+            Offset(39.95328, 60.608784860044665),
+            Offset(39.95328, 63.755090099044665),
+            Offset(39.95328, 65.54873060800844),
+            Offset(39.95328, 66.0622451901314),
+            Offset(39.95328, 66.48128708148053),
+            Offset(39.95328, 66.82061274303207),
+            Offset(39.95328, 67.09225034143321),
+            Offset(39.95328, 67.30660315125759),
+            Offset(39.95328, 67.47283055774243),
+            Offset(39.95328, 67.59872201115587),
+            Offset(39.95328, 67.69100497135157),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+            Offset(39.95328, 67.69436438964466),
+          ],
+          <Offset>[
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 48.1030583664),
+            Offset(39.95328, 49.3230542754),
+            Offset(39.95328, 52.4693595144),
+            Offset(39.95328, 55.6156647534),
+            Offset(39.95328, 58.761969992400005),
+            Offset(39.95328, 61.908275231400005),
+            Offset(39.95328, 65.0545804704),
+            Offset(39.95328, 68.20088570940001),
+            Offset(39.95328, 69.99452621836377),
+            Offset(39.95328, 70.50804080048674),
+            Offset(39.95328, 70.92708269183586),
+            Offset(39.95328, 71.2664083533874),
+            Offset(39.95328, 71.53804595178855),
+            Offset(39.95328, 71.75239876161294),
+            Offset(39.95328, 71.91862616809776),
+            Offset(39.95328, 72.0445176215112),
+            Offset(39.95328, 72.13680058170692),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+            Offset(39.95328, 72.14016000000001),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 52.548884672558),
+            Offset(39.95328, 53.768880581558),
+            Offset(39.95328, 56.915185820558),
+            Offset(39.95328, 60.061491059558),
+            Offset(39.95328, 63.20779629855801),
+            Offset(39.95328, 66.35410153755801),
+            Offset(39.95328, 69.500406776558),
+            Offset(39.95328, 72.64671201555801),
+            Offset(39.95328, 74.44035252452177),
+            Offset(39.95328, 74.95386710664474),
+            Offset(39.95328, 75.37290899799387),
+            Offset(39.95328, 75.7122346595454),
+            Offset(39.95328, 75.98387225794654),
+            Offset(39.95328, 76.19822506777093),
+            Offset(39.95328, 76.36445247425576),
+            Offset(39.95328, 76.4903439276692),
+            Offset(39.95328, 76.58262688786492),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+            Offset(39.95328, 76.585986306158),
+          ],
+          <Offset>[
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 56.1497783664),
+            Offset(43.554173693841996, 57.369774275400005),
+            Offset(43.554173693841996, 60.516079514400005),
+            Offset(43.554173693841996, 63.662384753400005),
+            Offset(43.554173693841996, 66.8086899924),
+            Offset(43.554173693841996, 69.9549952314),
+            Offset(43.554173693841996, 73.10130047039999),
+            Offset(43.554173693841996, 76.2476057094),
+            Offset(43.554173693841996, 78.04124621836377),
+            Offset(43.554173693841996, 78.55476080048675),
+            Offset(43.554173693841996, 78.97380269183587),
+            Offset(43.554173693841996, 79.31312835338741),
+            Offset(43.554173693841996, 79.58476595178854),
+            Offset(43.554173693841996, 79.79911876161293),
+            Offset(43.554173693841996, 79.96534616809777),
+            Offset(43.554173693841996, 80.0912376215112),
+            Offset(43.554173693841996, 80.18352058170692),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+            Offset(43.554173693841996, 80.18688),
+          ],
+          <Offset>[
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 56.1497783664),
+            Offset(48.0, 57.369774275400005),
+            Offset(48.0, 60.516079514400005),
+            Offset(48.0, 63.662384753400005),
+            Offset(48.0, 66.8086899924),
+            Offset(48.0, 69.9549952314),
+            Offset(48.0, 73.10130047039999),
+            Offset(48.0, 76.2476057094),
+            Offset(48.0, 78.04124621836377),
+            Offset(48.0, 78.55476080048675),
+            Offset(48.0, 78.97380269183587),
+            Offset(48.0, 79.31312835338741),
+            Offset(48.0, 79.58476595178854),
+            Offset(48.0, 79.79911876161293),
+            Offset(48.0, 79.96534616809777),
+            Offset(48.0, 80.0912376215112),
+            Offset(48.0, 80.18352058170692),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+            Offset(48.0, 80.18688),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 56.1497783664),
+            Offset(52.445826306158004, 57.369774275400005),
+            Offset(52.445826306158004, 60.516079514400005),
+            Offset(52.445826306158004, 63.662384753400005),
+            Offset(52.445826306158004, 66.8086899924),
+            Offset(52.445826306158004, 69.9549952314),
+            Offset(52.445826306158004, 73.10130047039999),
+            Offset(52.445826306158004, 76.2476057094),
+            Offset(52.445826306158004, 78.04124621836377),
+            Offset(52.445826306158004, 78.55476080048675),
+            Offset(52.445826306158004, 78.97380269183587),
+            Offset(52.445826306158004, 79.31312835338741),
+            Offset(52.445826306158004, 79.58476595178854),
+            Offset(52.445826306158004, 79.79911876161293),
+            Offset(52.445826306158004, 79.96534616809777),
+            Offset(52.445826306158004, 80.0912376215112),
+            Offset(52.445826306158004, 80.18352058170692),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+            Offset(52.445826306158004, 80.18688),
+          ],
+          <Offset>[
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 52.548884672558),
+            Offset(56.04672, 53.768880581558),
+            Offset(56.04672, 56.915185820558),
+            Offset(56.04672, 60.061491059558),
+            Offset(56.04672, 63.20779629855801),
+            Offset(56.04672, 66.35410153755801),
+            Offset(56.04672, 69.500406776558),
+            Offset(56.04672, 72.64671201555801),
+            Offset(56.04672, 74.44035252452177),
+            Offset(56.04672, 74.95386710664474),
+            Offset(56.04672, 75.37290899799387),
+            Offset(56.04672, 75.7122346595454),
+            Offset(56.04672, 75.98387225794654),
+            Offset(56.04672, 76.19822506777093),
+            Offset(56.04672, 76.36445247425576),
+            Offset(56.04672, 76.4903439276692),
+            Offset(56.04672, 76.58262688786492),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+            Offset(56.04672, 76.585986306158),
+          ],
+          <Offset>[
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 48.1030583664),
+            Offset(56.04672, 49.3230542754),
+            Offset(56.04672, 52.4693595144),
+            Offset(56.04672, 55.6156647534),
+            Offset(56.04672, 58.761969992400005),
+            Offset(56.04672, 61.908275231400005),
+            Offset(56.04672, 65.0545804704),
+            Offset(56.04672, 68.20088570940001),
+            Offset(56.04672, 69.99452621836377),
+            Offset(56.04672, 70.50804080048674),
+            Offset(56.04672, 70.92708269183586),
+            Offset(56.04672, 71.2664083533874),
+            Offset(56.04672, 71.53804595178855),
+            Offset(56.04672, 71.75239876161294),
+            Offset(56.04672, 71.91862616809776),
+            Offset(56.04672, 72.0445176215112),
+            Offset(56.04672, 72.13680058170692),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+            Offset(56.04672, 72.14016000000001),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 43.65726275604466),
+            Offset(56.04672, 44.877258665044664),
+            Offset(56.04672, 48.023563904044664),
+            Offset(56.04672, 51.169869143044664),
+            Offset(56.04672, 54.316174382044665),
+            Offset(56.04672, 57.462479621044665),
+            Offset(56.04672, 60.608784860044665),
+            Offset(56.04672, 63.755090099044665),
+            Offset(56.04672, 65.54873060800844),
+            Offset(56.04672, 66.0622451901314),
+            Offset(56.04672, 66.48128708148053),
+            Offset(56.04672, 66.82061274303207),
+            Offset(56.04672, 67.09225034143321),
+            Offset(56.04672, 67.30660315125759),
+            Offset(56.04672, 67.47283055774243),
+            Offset(56.04672, 67.59872201115587),
+            Offset(56.04672, 67.69100497135157),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+            Offset(56.04672, 67.69436438964466),
+          ],
+          <Offset>[
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 40.0563383664),
+            Offset(52.445826306158004, 41.2763342754),
+            Offset(52.445826306158004, 44.422639514400004),
+            Offset(52.445826306158004, 47.568944753400004),
+            Offset(52.445826306158004, 50.715249992400004),
+            Offset(52.445826306158004, 53.861555231400004),
+            Offset(52.445826306158004, 57.0078604704),
+            Offset(52.445826306158004, 60.154165709400004),
+            Offset(52.445826306158004, 61.94780621836377),
+            Offset(52.445826306158004, 62.46132080048674),
+            Offset(52.445826306158004, 62.88036269183587),
+            Offset(52.445826306158004, 63.219688353387404),
+            Offset(52.445826306158004, 63.491325951788546),
+            Offset(52.445826306158004, 63.70567876161293),
+            Offset(52.445826306158004, 63.87190616809776),
+            Offset(52.445826306158004, 63.997797621511204),
+            Offset(52.445826306158004, 64.09008058170691),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+            Offset(52.445826306158004, 64.09344),
+          ],
+          <Offset>[
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 40.0563383664),
+            Offset(48.0, 41.2763342754),
+            Offset(48.0, 44.422639514400004),
+            Offset(48.0, 47.568944753400004),
+            Offset(48.0, 50.715249992400004),
+            Offset(48.0, 53.861555231400004),
+            Offset(48.0, 57.0078604704),
+            Offset(48.0, 60.154165709400004),
+            Offset(48.0, 61.94780621836377),
+            Offset(48.0, 62.46132080048674),
+            Offset(48.0, 62.88036269183587),
+            Offset(48.0, 63.219688353387404),
+            Offset(48.0, 63.491325951788546),
+            Offset(48.0, 63.70567876161293),
+            Offset(48.0, 63.87190616809776),
+            Offset(48.0, 63.997797621511204),
+            Offset(48.0, 64.09008058170691),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+            Offset(48.0, 64.09344),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+  ],
+);
diff --git a/lib/src/material/animated_icons/data/view_list.g.dart b/lib/src/material/animated_icons/data/view_list.g.dart
new file mode 100644
index 0000000..af9606b
--- /dev/null
+++ b/lib/src/material/animated_icons/data/view_list.g.dart
@@ -0,0 +1,4237 @@
+// Copyright 2014 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.
+
+// AUTOGENERATED FILE DO NOT EDIT!
+// This file was generated by vitool.
+part of material_animated_icons;
+const _AnimatedIconData _$view_list = _AnimatedIconData(
+  Size(48.0, 48.0),
+  <_PathFrames>[
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.634146341463,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(44.69110902782625, 12.484010802427473),
+            Offset(44.91169585808795, 12.889600531986844),
+            Offset(45.905579651564764, 15.006358905929599),
+            Offset(46.93099203454209, 18.091564986244922),
+            Offset(47.57487910345822, 21.7717551172888),
+            Offset(47.611090562906426, 25.804550478715143),
+            Offset(46.90934109732998, 29.991835318680575),
+            Offset(45.41657507137544, 34.10298531188382),
+            Offset(43.12531983681261, 37.96288441331596),
+            Offset(40.11875918372392, 41.34720733654602),
+            Offset(36.52311532698831, 44.097551654541164),
+            Offset(32.469796235554185, 46.11340208398952),
+            Offset(28.106936276731332, 47.32108647595278),
+            Offset(23.593982433468472, 47.67646826990182),
+            Offset(19.205125831976975, 47.18941960707135),
+            Offset(15.060647386344348, 45.9277900128747),
+            Offset(11.36456649310902, 44.027127105327324),
+            Offset(8.242362271905536, 41.67588337906012),
+            Offset(5.7867901900563705, 39.13337333243891),
+            Offset(4.073681185543615, 36.793819543226384),
+            Offset(3.3006450057068637, 35.50116093358544),
+            Offset(3.3000000000000016, 35.5),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(44.69110902782625, 12.484010802427473),
+            Offset(44.91169585808795, 12.889600531986844),
+            Offset(45.905579651564764, 15.006358905929599),
+            Offset(46.93099203454209, 18.091564986244922),
+            Offset(47.57487910345822, 21.7717551172888),
+            Offset(47.611090562906426, 25.804550478715143),
+            Offset(46.90934109732998, 29.991835318680575),
+            Offset(45.41657507137544, 34.10298531188382),
+            Offset(43.12531983681261, 37.96288441331596),
+            Offset(40.11875918372392, 41.34720733654602),
+            Offset(36.52311532698831, 44.097551654541164),
+            Offset(32.469796235554185, 46.11340208398952),
+            Offset(28.106936276731332, 47.32108647595278),
+            Offset(23.593982433468472, 47.67646826990182),
+            Offset(19.205125831976975, 47.18941960707135),
+            Offset(15.060647386344348, 45.9277900128747),
+            Offset(11.36456649310902, 44.027127105327324),
+            Offset(8.242362271905536, 41.67588337906012),
+            Offset(5.7867901900563705, 39.13337333243891),
+            Offset(4.073681185543615, 36.793819543226384),
+            Offset(3.3006450057068637, 35.50116093358544),
+            Offset(3.3000000000000016, 35.5),
+          ],
+          <Offset>[
+            Offset(40.091110400688535, 12.487564720144858),
+            Offset(40.3125022719963, 12.803470643716631),
+            Offset(41.33730753204713, 14.467016860435445),
+            Offset(42.479650461341066, 16.931583004189925),
+            Offset(43.36138596487936, 19.926091125347472),
+            Offset(43.771900701159595, 23.27065600469687),
+            Offset(43.58434311086645, 26.813095255382933),
+            Offset(42.73290999433846, 30.366952422000203),
+            Offset(41.194877262006315, 33.787553113242254),
+            Offset(39.018140391822314, 36.880817488479344),
+            Offset(36.292544012456446, 39.503333889903196),
+            Offset(33.117698958194936, 41.55925868901449),
+            Offset(29.60963450105552, 42.973455161476714),
+            Offset(25.896557894757017, 43.69423946619505),
+            Offset(22.20703084230837, 43.70394025568405),
+            Offset(18.647310095538202, 43.04753818178802),
+            Offset(15.399557758932708, 41.81831622960108),
+            Offset(12.585723714323475, 40.16088787394561),
+            Offset(10.307276132541034, 38.28178255994396),
+            Offset(8.664359809057567, 36.50112554668383),
+            Offset(7.900644998472249, 35.500902944325),
+            Offset(7.900000000000001, 35.5),
+          ],
+          <Offset>[
+            Offset(40.091110400688535, 12.487564720144858),
+            Offset(40.3125022719963, 12.803470643716631),
+            Offset(41.33730753204713, 14.467016860435445),
+            Offset(42.479650461341066, 16.931583004189925),
+            Offset(43.36138596487936, 19.926091125347472),
+            Offset(43.771900701159595, 23.27065600469687),
+            Offset(43.58434311086645, 26.813095255382933),
+            Offset(42.73290999433846, 30.366952422000203),
+            Offset(41.194877262006315, 33.787553113242254),
+            Offset(39.018140391822314, 36.880817488479344),
+            Offset(36.292544012456446, 39.503333889903196),
+            Offset(33.117698958194936, 41.55925868901449),
+            Offset(29.60963450105552, 42.973455161476714),
+            Offset(25.896557894757017, 43.69423946619505),
+            Offset(22.20703084230837, 43.70394025568405),
+            Offset(18.647310095538202, 43.04753818178802),
+            Offset(15.399557758932708, 41.81831622960108),
+            Offset(12.585723714323475, 40.16088787394561),
+            Offset(10.307276132541034, 38.28178255994396),
+            Offset(8.664359809057567, 36.50112554668383),
+            Offset(7.900644998472249, 35.500902944325),
+            Offset(7.900000000000001, 35.5),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(40.091110400688535, 12.487564720144858),
+            Offset(40.3125022719963, 12.803470643716631),
+            Offset(41.33730753204713, 14.467016860435445),
+            Offset(42.479650461341066, 16.931583004189925),
+            Offset(43.36138596487936, 19.926091125347472),
+            Offset(43.771900701159595, 23.27065600469687),
+            Offset(43.58434311086645, 26.813095255382933),
+            Offset(42.73290999433846, 30.366952422000203),
+            Offset(41.194877262006315, 33.787553113242254),
+            Offset(39.018140391822314, 36.880817488479344),
+            Offset(36.292544012456446, 39.503333889903196),
+            Offset(33.117698958194936, 41.55925868901449),
+            Offset(29.60963450105552, 42.973455161476714),
+            Offset(25.896557894757017, 43.69423946619505),
+            Offset(22.20703084230837, 43.70394025568405),
+            Offset(18.647310095538202, 43.04753818178802),
+            Offset(15.399557758932708, 41.81831622960108),
+            Offset(12.585723714323475, 40.16088787394561),
+            Offset(10.307276132541034, 38.28178255994396),
+            Offset(8.664359809057567, 36.50112554668383),
+            Offset(7.900644998472249, 35.500902944325),
+            Offset(7.900000000000001, 35.5),
+          ],
+          <Offset>[
+            Offset(40.094664318405925, 17.087563347282572),
+            Offset(40.22637238372609, 17.40266422980828),
+            Offset(40.79796548655297, 19.035288979953084),
+            Offset(41.31966847928607, 21.38292457739095),
+            Offset(41.51572197293803, 24.13958426392634),
+            Offset(41.23800622714133, 27.109845866443706),
+            Offset(40.40560304756881, 30.13809324184646),
+            Offset(38.996877104454846, 33.05061749903718),
+            Offset(37.01954596193261, 35.717995688048546),
+            Offset(34.55175054375564, 37.98143628038095),
+            Offset(31.69832624781848, 39.73390520443506),
+            Offset(28.563555563219907, 40.91135596637374),
+            Offset(25.26200318657945, 41.47075693715253),
+            Offset(21.914329091050245, 41.3916640049065),
+            Offset(18.72155149092107, 40.702035245352654),
+            Offset(15.767058264451522, 39.46087547259416),
+            Offset(13.190746883206462, 37.78332496377739),
+            Offset(11.070728209208971, 35.817526431527675),
+            Offset(9.455685360046092, 33.761296617459294),
+            Offset(8.37166581251501, 31.91044692316988),
+            Offset(7.90038700921181, 30.900902951559615),
+            Offset(7.900000000000001, 30.900000000000002),
+          ],
+          <Offset>[
+            Offset(40.094664318405925, 17.087563347282572),
+            Offset(40.22637238372609, 17.40266422980828),
+            Offset(40.79796548655297, 19.035288979953084),
+            Offset(41.31966847928607, 21.38292457739095),
+            Offset(41.51572197293803, 24.13958426392634),
+            Offset(41.23800622714133, 27.109845866443706),
+            Offset(40.40560304756881, 30.13809324184646),
+            Offset(38.996877104454846, 33.05061749903718),
+            Offset(37.01954596193261, 35.717995688048546),
+            Offset(34.55175054375564, 37.98143628038095),
+            Offset(31.69832624781848, 39.73390520443506),
+            Offset(28.563555563219907, 40.91135596637374),
+            Offset(25.26200318657945, 41.47075693715253),
+            Offset(21.914329091050245, 41.3916640049065),
+            Offset(18.72155149092107, 40.702035245352654),
+            Offset(15.767058264451522, 39.46087547259416),
+            Offset(13.190746883206462, 37.78332496377739),
+            Offset(11.070728209208971, 35.817526431527675),
+            Offset(9.455685360046092, 33.761296617459294),
+            Offset(8.37166581251501, 31.91044692316988),
+            Offset(7.90038700921181, 30.900902951559615),
+            Offset(7.900000000000001, 30.900000000000002),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(40.094664318405925, 17.087563347282572),
+            Offset(40.22637238372609, 17.40266422980828),
+            Offset(40.79796548655297, 19.035288979953084),
+            Offset(41.31966847928607, 21.38292457739095),
+            Offset(41.51572197293803, 24.13958426392634),
+            Offset(41.23800622714133, 27.109845866443706),
+            Offset(40.40560304756881, 30.13809324184646),
+            Offset(38.996877104454846, 33.05061749903718),
+            Offset(37.01954596193261, 35.717995688048546),
+            Offset(34.55175054375564, 37.98143628038095),
+            Offset(31.69832624781848, 39.73390520443506),
+            Offset(28.563555563219907, 40.91135596637374),
+            Offset(25.26200318657945, 41.47075693715253),
+            Offset(21.914329091050245, 41.3916640049065),
+            Offset(18.72155149092107, 40.702035245352654),
+            Offset(15.767058264451522, 39.46087547259416),
+            Offset(13.190746883206462, 37.78332496377739),
+            Offset(11.070728209208971, 35.817526431527675),
+            Offset(9.455685360046092, 33.761296617459294),
+            Offset(8.37166581251501, 31.91044692316988),
+            Offset(7.90038700921181, 30.900902951559615),
+            Offset(7.900000000000001, 30.900000000000002),
+          ],
+          <Offset>[
+            Offset(44.69466294554364, 17.084009429565185),
+            Offset(44.82556596981774, 17.488794118078495),
+            Offset(45.366237606070605, 19.57463102544724),
+            Offset(45.7710100524871, 22.542906559445946),
+            Offset(45.72921511151689, 25.98524825586767),
+            Offset(45.07719608888816, 29.643740340461978),
+            Offset(43.73060103403234, 33.3168333051441),
+            Offset(41.68054218149182, 36.7866503889208),
+            Offset(38.9499885367389, 39.893326988122254),
+            Offset(35.65236933565724, 42.44782612844762),
+            Offset(31.928897562350347, 44.32812296907303),
+            Offset(27.915652840579153, 45.46549936134877),
+            Offset(23.75930496225526, 45.8183882516286),
+            Offset(19.6117536297617, 45.37389280861327),
+            Offset(15.719646480589672, 44.18751459673995),
+            Offset(12.180395555257666, 42.34112730368084),
+            Offset(9.155755617382773, 39.99213583950363),
+            Offset(6.7273667667910315, 37.33252193664218),
+            Offset(4.935199417561428, 34.61288738995424),
+            Offset(3.7809871890010585, 32.20314091971243),
+            Offset(3.3003870164464253, 30.901160940820056),
+            Offset(3.3000000000000016, 30.900000000000002),
+          ],
+          <Offset>[
+            Offset(44.69466294554364, 17.084009429565185),
+            Offset(44.82556596981774, 17.488794118078495),
+            Offset(45.366237606070605, 19.57463102544724),
+            Offset(45.7710100524871, 22.542906559445946),
+            Offset(45.72921511151689, 25.98524825586767),
+            Offset(45.07719608888816, 29.643740340461978),
+            Offset(43.73060103403234, 33.3168333051441),
+            Offset(41.68054218149182, 36.7866503889208),
+            Offset(38.9499885367389, 39.893326988122254),
+            Offset(35.65236933565724, 42.44782612844762),
+            Offset(31.928897562350347, 44.32812296907303),
+            Offset(27.915652840579153, 45.46549936134877),
+            Offset(23.75930496225526, 45.8183882516286),
+            Offset(19.6117536297617, 45.37389280861327),
+            Offset(15.719646480589672, 44.18751459673995),
+            Offset(12.180395555257666, 42.34112730368084),
+            Offset(9.155755617382773, 39.99213583950363),
+            Offset(6.7273667667910315, 37.33252193664218),
+            Offset(4.935199417561428, 34.61288738995424),
+            Offset(3.7809871890010585, 32.20314091971243),
+            Offset(3.3003870164464253, 30.901160940820056),
+            Offset(3.3000000000000016, 30.900000000000002),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(44.69466294554364, 17.084009429565185),
+            Offset(44.82556596981774, 17.488794118078495),
+            Offset(45.366237606070605, 19.57463102544724),
+            Offset(45.7710100524871, 22.542906559445946),
+            Offset(45.72921511151689, 25.98524825586767),
+            Offset(45.07719608888816, 29.643740340461978),
+            Offset(43.73060103403234, 33.3168333051441),
+            Offset(41.68054218149182, 36.7866503889208),
+            Offset(38.9499885367389, 39.893326988122254),
+            Offset(35.65236933565724, 42.44782612844762),
+            Offset(31.928897562350347, 44.32812296907303),
+            Offset(27.915652840579153, 45.46549936134877),
+            Offset(23.75930496225526, 45.8183882516286),
+            Offset(19.6117536297617, 45.37389280861327),
+            Offset(15.719646480589672, 44.18751459673995),
+            Offset(12.180395555257666, 42.34112730368084),
+            Offset(9.155755617382773, 39.99213583950363),
+            Offset(6.7273667667910315, 37.33252193664218),
+            Offset(4.935199417561428, 34.61288738995424),
+            Offset(3.7809871890010585, 32.20314091971243),
+            Offset(3.3003870164464253, 30.901160940820056),
+            Offset(3.3000000000000016, 30.900000000000002),
+          ],
+          <Offset>[
+            Offset(44.69110902782625, 12.484010802427473),
+            Offset(44.91169585808795, 12.889600531986844),
+            Offset(45.905579651564764, 15.006358905929599),
+            Offset(46.93099203454209, 18.091564986244922),
+            Offset(47.57487910345822, 21.7717551172888),
+            Offset(47.611090562906426, 25.804550478715143),
+            Offset(46.90934109732998, 29.991835318680575),
+            Offset(45.41657507137544, 34.10298531188382),
+            Offset(43.12531983681261, 37.96288441331596),
+            Offset(40.11875918372392, 41.34720733654602),
+            Offset(36.52311532698831, 44.097551654541164),
+            Offset(32.469796235554185, 46.11340208398952),
+            Offset(28.106936276731332, 47.32108647595278),
+            Offset(23.593982433468472, 47.67646826990182),
+            Offset(19.205125831976975, 47.18941960707135),
+            Offset(15.060647386344348, 45.9277900128747),
+            Offset(11.36456649310902, 44.027127105327324),
+            Offset(8.242362271905536, 41.67588337906012),
+            Offset(5.7867901900563705, 39.13337333243891),
+            Offset(4.073681185543615, 36.793819543226384),
+            Offset(3.3006450057068637, 35.50116093358544),
+            Offset(3.3000000000000016, 35.5),
+          ],
+          <Offset>[
+            Offset(44.69110902782625, 12.484010802427473),
+            Offset(44.91169585808795, 12.889600531986844),
+            Offset(45.905579651564764, 15.006358905929599),
+            Offset(46.93099203454209, 18.091564986244922),
+            Offset(47.57487910345822, 21.7717551172888),
+            Offset(47.611090562906426, 25.804550478715143),
+            Offset(46.90934109732998, 29.991835318680575),
+            Offset(45.41657507137544, 34.10298531188382),
+            Offset(43.12531983681261, 37.96288441331596),
+            Offset(40.11875918372392, 41.34720733654602),
+            Offset(36.52311532698831, 44.097551654541164),
+            Offset(32.469796235554185, 46.11340208398952),
+            Offset(28.106936276731332, 47.32108647595278),
+            Offset(23.593982433468472, 47.67646826990182),
+            Offset(19.205125831976975, 47.18941960707135),
+            Offset(15.060647386344348, 45.9277900128747),
+            Offset(11.36456649310902, 44.027127105327324),
+            Offset(8.242362271905536, 41.67588337906012),
+            Offset(5.7867901900563705, 39.13337333243891),
+            Offset(4.073681185543615, 36.793819543226384),
+            Offset(3.3006450057068637, 35.50116093358544),
+            Offset(3.3000000000000016, 35.5),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.634146341463,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(44.7053246986958, 30.884005310978335),
+            Offset(44.567176305007095, 31.28637487635344),
+            Offset(43.74821146958815, 33.27944738400016),
+            Offset(42.29106410632212, 35.896931279049014),
+            Offset(40.19222313569291, 38.62572767160428),
+            Offset(37.47551266683334, 41.16130992570247),
+            Offset(34.19438084413941, 43.291827264534675),
+            Offset(30.472443511840986, 44.83764562003173),
+            Offset(26.423994636517765, 45.684654712541146),
+            Offset(22.25319979145722, 45.749682504152446),
+            Offset(18.146244268436455, 45.01983691266863),
+            Offset(14.253222655654064, 43.5217911934265),
+            Offset(10.71641101882705, 41.31029357865603),
+            Offset(7.665067218641396, 38.46616642474764),
+            Offset(5.263208426427763, 35.18179956574576),
+            Offset(3.5396400619976247, 31.581139176099278),
+            Offset(2.529322990204036, 27.887162042032575),
+            Offset(2.1823802514475172, 24.302437609388363),
+            Offset(2.3804271000766017, 21.051429562500246),
+            Offset(2.902905199373388, 18.43110504917058),
+            Offset(3.2996130486651083, 17.1011609625239),
+            Offset(3.3000000000000016, 17.1),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(44.7053246986958, 30.884005310978335),
+            Offset(44.567176305007095, 31.28637487635344),
+            Offset(43.74821146958815, 33.27944738400016),
+            Offset(42.29106410632212, 35.896931279049014),
+            Offset(40.19222313569291, 38.62572767160428),
+            Offset(37.47551266683334, 41.16130992570247),
+            Offset(34.19438084413941, 43.291827264534675),
+            Offset(30.472443511840986, 44.83764562003173),
+            Offset(26.423994636517765, 45.684654712541146),
+            Offset(22.25319979145722, 45.749682504152446),
+            Offset(18.146244268436455, 45.01983691266863),
+            Offset(14.253222655654064, 43.5217911934265),
+            Offset(10.71641101882705, 41.31029357865603),
+            Offset(7.665067218641396, 38.46616642474764),
+            Offset(5.263208426427763, 35.18179956574576),
+            Offset(3.5396400619976247, 31.581139176099278),
+            Offset(2.529322990204036, 27.887162042032575),
+            Offset(2.1823802514475172, 24.302437609388363),
+            Offset(2.3804271000766017, 21.051429562500246),
+            Offset(2.902905199373388, 18.43110504917058),
+            Offset(3.2996130486651083, 17.1011609625239),
+            Offset(3.3000000000000016, 17.1),
+          ],
+          <Offset>[
+            Offset(40.10532607155808, 30.88755922869572),
+            Offset(39.967982718915444, 31.200244988083227),
+            Offset(39.17993935007051, 32.740105338506),
+            Offset(37.83972253312109, 34.73694929699402),
+            Offset(35.978729997114044, 36.78006367966295),
+            Offset(33.63632280508651, 38.6274154516842),
+            Offset(30.869382857675884, 40.113087201237036),
+            Offset(27.788778434804005, 41.10161273014811),
+            Offset(24.49355206171147, 41.50932341246744),
+            Offset(21.152580999555614, 41.28329265608577),
+            Offset(17.915672953904586, 40.42561914803066),
+            Offset(14.901125378294816, 38.96764779845147),
+            Offset(12.219109243151237, 36.96266226417996),
+            Offset(9.96764267992994, 34.48393762104087),
+            Offset(8.26511343675916, 31.69632021435846),
+            Offset(7.126302771191481, 28.7008873450126),
+            Offset(6.564314256027725, 25.678351166306328),
+            Offset(6.525741693865457, 22.787442104273858),
+            Offset(6.900913042561266, 20.199838790005305),
+            Offset(7.49358382288734, 18.138411052628022),
+            Offset(7.899613041430493, 17.10090297326346),
+            Offset(7.900000000000001, 17.1),
+          ],
+          <Offset>[
+            Offset(40.10532607155808, 30.88755922869572),
+            Offset(39.967982718915444, 31.200244988083227),
+            Offset(39.17993935007051, 32.740105338506),
+            Offset(37.83972253312109, 34.73694929699402),
+            Offset(35.978729997114044, 36.78006367966295),
+            Offset(33.63632280508651, 38.6274154516842),
+            Offset(30.869382857675884, 40.113087201237036),
+            Offset(27.788778434804005, 41.10161273014811),
+            Offset(24.49355206171147, 41.50932341246744),
+            Offset(21.152580999555614, 41.28329265608577),
+            Offset(17.915672953904586, 40.42561914803066),
+            Offset(14.901125378294816, 38.96764779845147),
+            Offset(12.219109243151237, 36.96266226417996),
+            Offset(9.96764267992994, 34.48393762104087),
+            Offset(8.26511343675916, 31.69632021435846),
+            Offset(7.126302771191481, 28.7008873450126),
+            Offset(6.564314256027725, 25.678351166306328),
+            Offset(6.525741693865457, 22.787442104273858),
+            Offset(6.900913042561266, 20.199838790005305),
+            Offset(7.49358382288734, 18.138411052628022),
+            Offset(7.899613041430493, 17.10090297326346),
+            Offset(7.900000000000001, 17.1),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(40.10532607155808, 30.88755922869572),
+            Offset(39.967982718915444, 31.200244988083227),
+            Offset(39.17993935007051, 32.740105338506),
+            Offset(37.83972253312109, 34.73694929699402),
+            Offset(35.978729997114044, 36.78006367966295),
+            Offset(33.63632280508651, 38.6274154516842),
+            Offset(30.869382857675884, 40.113087201237036),
+            Offset(27.788778434804005, 41.10161273014811),
+            Offset(24.49355206171147, 41.50932341246744),
+            Offset(21.152580999555614, 41.28329265608577),
+            Offset(17.915672953904586, 40.42561914803066),
+            Offset(14.901125378294816, 38.96764779845147),
+            Offset(12.219109243151237, 36.96266226417996),
+            Offset(9.96764267992994, 34.48393762104087),
+            Offset(8.26511343675916, 31.69632021435846),
+            Offset(7.126302771191481, 28.7008873450126),
+            Offset(6.564314256027725, 25.678351166306328),
+            Offset(6.525741693865457, 22.787442104273858),
+            Offset(6.900913042561266, 20.199838790005305),
+            Offset(7.49358382288734, 18.138411052628022),
+            Offset(7.899613041430493, 17.10090297326346),
+            Offset(7.900000000000001, 17.1),
+          ],
+          <Offset>[
+            Offset(40.10887998927547, 35.48755785583344),
+            Offset(39.88185283064523, 35.79943857417488),
+            Offset(38.64059730457635, 37.308377458023635),
+            Offset(36.6797405510661, 39.18829087019505),
+            Offset(34.13306600517272, 40.993556818241814),
+            Offset(31.102428331068236, 42.46660531343103),
+            Offset(27.690642794378242, 43.438085187700565),
+            Offset(24.05274554492039, 43.78527780718509),
+            Offset(20.318220761637757, 43.43976598727373),
+            Offset(16.68619115148894, 42.38391144798737),
+            Offset(13.321455189266624, 40.65619046256253),
+            Offset(10.346981983319786, 38.31974507581072),
+            Offset(7.871477928675166, 35.45996403985578),
+            Offset(5.985413876223171, 32.181362159752325),
+            Offset(4.779634085371857, 28.694415204027067),
+            Offset(4.2460509401048, 25.114224635818744),
+            Offset(4.355503380301478, 21.64335990048264),
+            Offset(5.010746188750952, 18.44408066185592),
+            Offset(6.049322270066323, 15.67935284752064),
+            Offset(7.200889826344783, 13.547732429114072),
+            Offset(7.899355052170055, 12.500902980498076),
+            Offset(7.900000000000001, 12.5),
+          ],
+          <Offset>[
+            Offset(40.10887998927547, 35.48755785583344),
+            Offset(39.88185283064523, 35.79943857417488),
+            Offset(38.64059730457635, 37.308377458023635),
+            Offset(36.6797405510661, 39.18829087019505),
+            Offset(34.13306600517272, 40.993556818241814),
+            Offset(31.102428331068236, 42.46660531343103),
+            Offset(27.690642794378242, 43.438085187700565),
+            Offset(24.05274554492039, 43.78527780718509),
+            Offset(20.318220761637757, 43.43976598727373),
+            Offset(16.68619115148894, 42.38391144798737),
+            Offset(13.321455189266624, 40.65619046256253),
+            Offset(10.346981983319786, 38.31974507581072),
+            Offset(7.871477928675166, 35.45996403985578),
+            Offset(5.985413876223171, 32.181362159752325),
+            Offset(4.779634085371857, 28.694415204027067),
+            Offset(4.2460509401048, 25.114224635818744),
+            Offset(4.355503380301478, 21.64335990048264),
+            Offset(5.010746188750952, 18.44408066185592),
+            Offset(6.049322270066323, 15.67935284752064),
+            Offset(7.200889826344783, 13.547732429114072),
+            Offset(7.899355052170055, 12.500902980498076),
+            Offset(7.900000000000001, 12.5),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(40.10887998927547, 35.48755785583344),
+            Offset(39.88185283064523, 35.79943857417488),
+            Offset(38.64059730457635, 37.308377458023635),
+            Offset(36.6797405510661, 39.18829087019505),
+            Offset(34.13306600517272, 40.993556818241814),
+            Offset(31.102428331068236, 42.46660531343103),
+            Offset(27.690642794378242, 43.438085187700565),
+            Offset(24.05274554492039, 43.78527780718509),
+            Offset(20.318220761637757, 43.43976598727373),
+            Offset(16.68619115148894, 42.38391144798737),
+            Offset(13.321455189266624, 40.65619046256253),
+            Offset(10.346981983319786, 38.31974507581072),
+            Offset(7.871477928675166, 35.45996403985578),
+            Offset(5.985413876223171, 32.181362159752325),
+            Offset(4.779634085371857, 28.694415204027067),
+            Offset(4.2460509401048, 25.114224635818744),
+            Offset(4.355503380301478, 21.64335990048264),
+            Offset(5.010746188750952, 18.44408066185592),
+            Offset(6.049322270066323, 15.67935284752064),
+            Offset(7.200889826344783, 13.547732429114072),
+            Offset(7.899355052170055, 12.500902980498076),
+            Offset(7.900000000000001, 12.5),
+          ],
+          <Offset>[
+            Offset(44.70887861641319, 35.48400393811605),
+            Offset(44.48104641673688, 35.88556846244509),
+            Offset(43.20886942409399, 37.847719503517794),
+            Offset(41.131082124267124, 40.34827285225004),
+            Offset(38.34655914375159, 42.83922081018314),
+            Offset(34.94161819281507, 45.0004997874493),
+            Offset(31.015640780841768, 46.616825250998204),
+            Offset(26.736410621957372, 47.52131069706871),
+            Offset(22.248663336444054, 47.61509728734744),
+            Offset(17.786809943390548, 46.85030129605405),
+            Offset(13.55202650379849, 45.250408227200495),
+            Offset(9.699079260679033, 42.87388847078575),
+            Offset(6.3687797043509775, 39.807595354331845),
+            Offset(3.682838414934627, 36.16359096345909),
+            Offset(1.77772907504046, 32.179894555414364),
+            Offset(0.6593882309109436, 27.99447646690542),
+            Offset(0.32051211447779027, 23.852170776208887),
+            Offset(0.6673847463330125, 19.959076166970426),
+            Offset(1.5288363275816592, 16.53094362001558),
+            Offset(2.6102112028308313, 13.840426425656629),
+            Offset(3.29935505940467, 12.501160969758514),
+            Offset(3.3000000000000016, 12.5),
+          ],
+          <Offset>[
+            Offset(44.70887861641319, 35.48400393811605),
+            Offset(44.48104641673688, 35.88556846244509),
+            Offset(43.20886942409399, 37.847719503517794),
+            Offset(41.131082124267124, 40.34827285225004),
+            Offset(38.34655914375159, 42.83922081018314),
+            Offset(34.94161819281507, 45.0004997874493),
+            Offset(31.015640780841768, 46.616825250998204),
+            Offset(26.736410621957372, 47.52131069706871),
+            Offset(22.248663336444054, 47.61509728734744),
+            Offset(17.786809943390548, 46.85030129605405),
+            Offset(13.55202650379849, 45.250408227200495),
+            Offset(9.699079260679033, 42.87388847078575),
+            Offset(6.3687797043509775, 39.807595354331845),
+            Offset(3.682838414934627, 36.16359096345909),
+            Offset(1.77772907504046, 32.179894555414364),
+            Offset(0.6593882309109436, 27.99447646690542),
+            Offset(0.32051211447779027, 23.852170776208887),
+            Offset(0.6673847463330125, 19.959076166970426),
+            Offset(1.5288363275816592, 16.53094362001558),
+            Offset(2.6102112028308313, 13.840426425656629),
+            Offset(3.29935505940467, 12.501160969758514),
+            Offset(3.3000000000000016, 12.5),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(44.70887861641319, 35.48400393811605),
+            Offset(44.48104641673688, 35.88556846244509),
+            Offset(43.20886942409399, 37.847719503517794),
+            Offset(41.131082124267124, 40.34827285225004),
+            Offset(38.34655914375159, 42.83922081018314),
+            Offset(34.94161819281507, 45.0004997874493),
+            Offset(31.015640780841768, 46.616825250998204),
+            Offset(26.736410621957372, 47.52131069706871),
+            Offset(22.248663336444054, 47.61509728734744),
+            Offset(17.786809943390548, 46.85030129605405),
+            Offset(13.55202650379849, 45.250408227200495),
+            Offset(9.699079260679033, 42.87388847078575),
+            Offset(6.3687797043509775, 39.807595354331845),
+            Offset(3.682838414934627, 36.16359096345909),
+            Offset(1.77772907504046, 32.179894555414364),
+            Offset(0.6593882309109436, 27.99447646690542),
+            Offset(0.32051211447779027, 23.852170776208887),
+            Offset(0.6673847463330125, 19.959076166970426),
+            Offset(1.5288363275816592, 16.53094362001558),
+            Offset(2.6102112028308313, 13.840426425656629),
+            Offset(3.29935505940467, 12.501160969758514),
+            Offset(3.3000000000000016, 12.5),
+          ],
+          <Offset>[
+            Offset(44.7053246986958, 30.884005310978335),
+            Offset(44.567176305007095, 31.28637487635344),
+            Offset(43.74821146958815, 33.27944738400016),
+            Offset(42.29106410632212, 35.896931279049014),
+            Offset(40.19222313569291, 38.62572767160428),
+            Offset(37.47551266683334, 41.16130992570247),
+            Offset(34.19438084413941, 43.291827264534675),
+            Offset(30.472443511840986, 44.83764562003173),
+            Offset(26.423994636517765, 45.684654712541146),
+            Offset(22.25319979145722, 45.749682504152446),
+            Offset(18.146244268436455, 45.01983691266863),
+            Offset(14.253222655654064, 43.5217911934265),
+            Offset(10.71641101882705, 41.31029357865603),
+            Offset(7.665067218641396, 38.46616642474764),
+            Offset(5.263208426427763, 35.18179956574576),
+            Offset(3.5396400619976247, 31.581139176099278),
+            Offset(2.529322990204036, 27.887162042032575),
+            Offset(2.1823802514475172, 24.302437609388363),
+            Offset(2.3804271000766017, 21.051429562500246),
+            Offset(2.902905199373388, 18.43110504917058),
+            Offset(3.2996130486651083, 17.1011609625239),
+            Offset(3.3000000000000016, 17.1),
+          ],
+          <Offset>[
+            Offset(44.7053246986958, 30.884005310978335),
+            Offset(44.567176305007095, 31.28637487635344),
+            Offset(43.74821146958815, 33.27944738400016),
+            Offset(42.29106410632212, 35.896931279049014),
+            Offset(40.19222313569291, 38.62572767160428),
+            Offset(37.47551266683334, 41.16130992570247),
+            Offset(34.19438084413941, 43.291827264534675),
+            Offset(30.472443511840986, 44.83764562003173),
+            Offset(26.423994636517765, 45.684654712541146),
+            Offset(22.25319979145722, 45.749682504152446),
+            Offset(18.146244268436455, 45.01983691266863),
+            Offset(14.253222655654064, 43.5217911934265),
+            Offset(10.71641101882705, 41.31029357865603),
+            Offset(7.665067218641396, 38.46616642474764),
+            Offset(5.263208426427763, 35.18179956574576),
+            Offset(3.5396400619976247, 31.581139176099278),
+            Offset(2.529322990204036, 27.887162042032575),
+            Offset(2.1823802514475172, 24.302437609388363),
+            Offset(2.3804271000766017, 21.051429562500246),
+            Offset(2.902905199373388, 18.43110504917058),
+            Offset(3.2996130486651083, 17.1011609625239),
+            Offset(3.3000000000000016, 17.1),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.634146341463,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(44.698216863261024, 21.684008056702904),
+            Offset(44.739436081547524, 22.08798770417014),
+            Offset(44.82689556057646, 24.14290314496488),
+            Offset(44.611028070432106, 26.99424813264697),
+            Offset(43.88355111957557, 30.19874139444654),
+            Offset(42.54330161486988, 33.482930202208806),
+            Offset(40.55186097073469, 36.64183129160762),
+            Offset(37.94450929160821, 39.470315465957775),
+            Offset(34.77465723666519, 41.823769562928554),
+            Offset(31.18597948759057, 43.548444920349226),
+            Offset(27.334679797712376, 44.5586942836049),
+            Offset(23.361509445604124, 44.81759663870801),
+            Offset(19.41167364777919, 44.315690027304406),
+            Offset(15.629524826054936, 43.07131734732473),
+            Offset(12.234167129202369, 41.18560958640855),
+            Offset(9.300143724170985, 38.754464594487),
+            Offset(6.946944741656527, 35.95714457367995),
+            Offset(5.2123712616765285, 32.989160494224244),
+            Offset(4.0836086450664855, 30.092401447469573),
+            Offset(3.4882931924585017, 27.61246229619848),
+            Offset(3.300129027185985, 26.30116094805467),
+            Offset(3.3000000000000016, 26.3),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(44.698216863261024, 21.684008056702904),
+            Offset(44.739436081547524, 22.08798770417014),
+            Offset(44.82689556057646, 24.14290314496488),
+            Offset(44.611028070432106, 26.99424813264697),
+            Offset(43.88355111957557, 30.19874139444654),
+            Offset(42.54330161486988, 33.482930202208806),
+            Offset(40.55186097073469, 36.64183129160762),
+            Offset(37.94450929160821, 39.470315465957775),
+            Offset(34.77465723666519, 41.823769562928554),
+            Offset(31.18597948759057, 43.548444920349226),
+            Offset(27.334679797712376, 44.5586942836049),
+            Offset(23.361509445604124, 44.81759663870801),
+            Offset(19.41167364777919, 44.315690027304406),
+            Offset(15.629524826054936, 43.07131734732473),
+            Offset(12.234167129202369, 41.18560958640855),
+            Offset(9.300143724170985, 38.754464594487),
+            Offset(6.946944741656527, 35.95714457367995),
+            Offset(5.2123712616765285, 32.989160494224244),
+            Offset(4.0836086450664855, 30.092401447469573),
+            Offset(3.4882931924585017, 27.61246229619848),
+            Offset(3.300129027185985, 26.30116094805467),
+            Offset(3.3000000000000016, 26.3),
+          ],
+          <Offset>[
+            Offset(40.09821823612331, 21.68756197442029),
+            Offset(40.14024249545587, 22.001857815899925),
+            Offset(40.25862344105882, 23.603561099470724),
+            Offset(40.15968649723108, 25.834266150591972),
+            Offset(39.6700579809967, 28.35307740250521),
+            Offset(38.70411175312305, 30.949035728190534),
+            Offset(37.22686298427116, 33.463091228309985),
+            Offset(35.260844214571236, 35.73428257607416),
+            Offset(32.84421466185889, 37.64843826285484),
+            Offset(30.085360695688962, 39.08205507228256),
+            Offset(27.10410848318051, 39.96447651896693),
+            Offset(24.00941216824488, 40.26345324373298),
+            Offset(20.914371872103377, 39.96805871282834),
+            Offset(17.93210028734348, 39.08908854361796),
+            Offset(15.236072139533766, 37.700130235021255),
+            Offset(12.88680643336484, 35.874212763400315),
+            Offset(10.981936007480215, 33.748333697953704),
+            Offset(9.555732704094467, 31.474164989109738),
+            Offset(8.604094587551149, 29.240810674974632),
+            Offset(8.078971815972453, 27.319768299655923),
+            Offset(7.90012901995137, 26.300902958794232),
+            Offset(7.900000000000001, 26.3),
+          ],
+          <Offset>[
+            Offset(40.09821823612331, 21.68756197442029),
+            Offset(40.14024249545587, 22.001857815899925),
+            Offset(40.25862344105882, 23.603561099470724),
+            Offset(40.15968649723108, 25.834266150591972),
+            Offset(39.6700579809967, 28.35307740250521),
+            Offset(38.70411175312305, 30.949035728190534),
+            Offset(37.22686298427116, 33.463091228309985),
+            Offset(35.260844214571236, 35.73428257607416),
+            Offset(32.84421466185889, 37.64843826285484),
+            Offset(30.085360695688962, 39.08205507228256),
+            Offset(27.10410848318051, 39.96447651896693),
+            Offset(24.00941216824488, 40.26345324373298),
+            Offset(20.914371872103377, 39.96805871282834),
+            Offset(17.93210028734348, 39.08908854361796),
+            Offset(15.236072139533766, 37.700130235021255),
+            Offset(12.88680643336484, 35.874212763400315),
+            Offset(10.981936007480215, 33.748333697953704),
+            Offset(9.555732704094467, 31.474164989109738),
+            Offset(8.604094587551149, 29.240810674974632),
+            Offset(8.078971815972453, 27.319768299655923),
+            Offset(7.90012901995137, 26.300902958794232),
+            Offset(7.900000000000001, 26.3),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(40.09821823612331, 21.68756197442029),
+            Offset(40.14024249545587, 22.001857815899925),
+            Offset(40.25862344105882, 23.603561099470724),
+            Offset(40.15968649723108, 25.834266150591972),
+            Offset(39.6700579809967, 28.35307740250521),
+            Offset(38.70411175312305, 30.949035728190534),
+            Offset(37.22686298427116, 33.463091228309985),
+            Offset(35.260844214571236, 35.73428257607416),
+            Offset(32.84421466185889, 37.64843826285484),
+            Offset(30.085360695688962, 39.08205507228256),
+            Offset(27.10410848318051, 39.96447651896693),
+            Offset(24.00941216824488, 40.26345324373298),
+            Offset(20.914371872103377, 39.96805871282834),
+            Offset(17.93210028734348, 39.08908854361796),
+            Offset(15.236072139533766, 37.700130235021255),
+            Offset(12.88680643336484, 35.874212763400315),
+            Offset(10.981936007480215, 33.748333697953704),
+            Offset(9.555732704094467, 31.474164989109738),
+            Offset(8.604094587551149, 29.240810674974632),
+            Offset(8.078971815972453, 27.319768299655923),
+            Offset(7.90012901995137, 26.300902958794232),
+            Offset(7.900000000000001, 26.3),
+          ],
+          <Offset>[
+            Offset(40.1017721538407, 26.287560601558003),
+            Offset(40.05411260718566, 26.601051401991576),
+            Offset(39.719281395564664, 28.171833218988365),
+            Offset(38.999704515176084, 30.285607723792996),
+            Offset(37.82439398905538, 32.566570541084076),
+            Offset(36.17021727910478, 34.78822558993737),
+            Offset(34.04812292097352, 36.788089214773514),
+            Offset(31.52481132468762, 38.417947653111135),
+            Offset(28.66888336178518, 39.57888083766114),
+            Offset(25.61897084762229, 40.18267386418417),
+            Offset(22.50989071854255, 40.1950478334988),
+            Offset(19.455268773269847, 39.61555052109223),
+            Offset(16.566740557627305, 38.46536048850415),
+            Offset(13.94987148363671, 36.786513082329414),
+            Offset(11.750592788146463, 34.69822522468986),
+            Offset(10.00655460227816, 32.287550054206456),
+            Offset(8.773125131753968, 29.713342432130013),
+            Offset(8.040737198979963, 27.130803546691798),
+            Offset(7.7525038150562064, 24.720324732489967),
+            Offset(7.7862778194298965, 22.729089676141975),
+            Offset(7.899871030690932, 21.700902966028845),
+            Offset(7.900000000000001, 21.7),
+          ],
+          <Offset>[
+            Offset(40.1017721538407, 26.287560601558003),
+            Offset(40.05411260718566, 26.601051401991576),
+            Offset(39.719281395564664, 28.171833218988365),
+            Offset(38.999704515176084, 30.285607723792996),
+            Offset(37.82439398905538, 32.566570541084076),
+            Offset(36.17021727910478, 34.78822558993737),
+            Offset(34.04812292097352, 36.788089214773514),
+            Offset(31.52481132468762, 38.417947653111135),
+            Offset(28.66888336178518, 39.57888083766114),
+            Offset(25.61897084762229, 40.18267386418417),
+            Offset(22.50989071854255, 40.1950478334988),
+            Offset(19.455268773269847, 39.61555052109223),
+            Offset(16.566740557627305, 38.46536048850415),
+            Offset(13.94987148363671, 36.786513082329414),
+            Offset(11.750592788146463, 34.69822522468986),
+            Offset(10.00655460227816, 32.287550054206456),
+            Offset(8.773125131753968, 29.713342432130013),
+            Offset(8.040737198979963, 27.130803546691798),
+            Offset(7.7525038150562064, 24.720324732489967),
+            Offset(7.7862778194298965, 22.729089676141975),
+            Offset(7.899871030690932, 21.700902966028845),
+            Offset(7.900000000000001, 21.7),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(40.1017721538407, 26.287560601558003),
+            Offset(40.05411260718566, 26.601051401991576),
+            Offset(39.719281395564664, 28.171833218988365),
+            Offset(38.999704515176084, 30.285607723792996),
+            Offset(37.82439398905538, 32.566570541084076),
+            Offset(36.17021727910478, 34.78822558993737),
+            Offset(34.04812292097352, 36.788089214773514),
+            Offset(31.52481132468762, 38.417947653111135),
+            Offset(28.66888336178518, 39.57888083766114),
+            Offset(25.61897084762229, 40.18267386418417),
+            Offset(22.50989071854255, 40.1950478334988),
+            Offset(19.455268773269847, 39.61555052109223),
+            Offset(16.566740557627305, 38.46536048850415),
+            Offset(13.94987148363671, 36.786513082329414),
+            Offset(11.750592788146463, 34.69822522468986),
+            Offset(10.00655460227816, 32.287550054206456),
+            Offset(8.773125131753968, 29.713342432130013),
+            Offset(8.040737198979963, 27.130803546691798),
+            Offset(7.7525038150562064, 24.720324732489967),
+            Offset(7.7862778194298965, 22.729089676141975),
+            Offset(7.899871030690932, 21.700902966028845),
+            Offset(7.900000000000001, 21.7),
+          ],
+          <Offset>[
+            Offset(44.701770780978414, 26.284006683840616),
+            Offset(44.65330619327731, 26.68718129026179),
+            Offset(44.2875535150823, 28.71117526448252),
+            Offset(43.45104608837711, 31.445589705847993),
+            Offset(42.03788712763425, 34.412234533025405),
+            Offset(40.009407140851614, 37.32212006395564),
+            Offset(37.37312090743705, 39.96682927807115),
+            Offset(34.208476401724596, 42.15398054299475),
+            Offset(30.599325936591477, 43.754212137734854),
+            Offset(26.719589639523896, 44.649063712250836),
+            Offset(22.740462033074415, 44.789265598136765),
+            Offset(18.807366050629092, 44.16969391606726),
+            Offset(15.064042333303117, 42.81299180298022),
+            Offset(11.647296022348167, 40.76874188603618),
+            Offset(8.748687777815066, 38.183704576077155),
+            Offset(6.419891893084303, 35.16780188529314),
+            Offset(4.73813386593028, 31.92215330785626),
+            Offset(3.6973757565620238, 28.6457990518063),
+            Offset(3.2320178725715425, 25.571915504984908),
+            Offset(3.195599195915945, 23.02178367268453),
+            Offset(3.2998710379255467, 21.701160955289282),
+            Offset(3.3000000000000016, 21.7),
+          ],
+          <Offset>[
+            Offset(44.701770780978414, 26.284006683840616),
+            Offset(44.65330619327731, 26.68718129026179),
+            Offset(44.2875535150823, 28.71117526448252),
+            Offset(43.45104608837711, 31.445589705847993),
+            Offset(42.03788712763425, 34.412234533025405),
+            Offset(40.009407140851614, 37.32212006395564),
+            Offset(37.37312090743705, 39.96682927807115),
+            Offset(34.208476401724596, 42.15398054299475),
+            Offset(30.599325936591477, 43.754212137734854),
+            Offset(26.719589639523896, 44.649063712250836),
+            Offset(22.740462033074415, 44.789265598136765),
+            Offset(18.807366050629092, 44.16969391606726),
+            Offset(15.064042333303117, 42.81299180298022),
+            Offset(11.647296022348167, 40.76874188603618),
+            Offset(8.748687777815066, 38.183704576077155),
+            Offset(6.419891893084303, 35.16780188529314),
+            Offset(4.73813386593028, 31.92215330785626),
+            Offset(3.6973757565620238, 28.6457990518063),
+            Offset(3.2320178725715425, 25.571915504984908),
+            Offset(3.195599195915945, 23.02178367268453),
+            Offset(3.2998710379255467, 21.701160955289282),
+            Offset(3.3000000000000016, 21.7),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(44.701770780978414, 26.284006683840616),
+            Offset(44.65330619327731, 26.68718129026179),
+            Offset(44.2875535150823, 28.71117526448252),
+            Offset(43.45104608837711, 31.445589705847993),
+            Offset(42.03788712763425, 34.412234533025405),
+            Offset(40.009407140851614, 37.32212006395564),
+            Offset(37.37312090743705, 39.96682927807115),
+            Offset(34.208476401724596, 42.15398054299475),
+            Offset(30.599325936591477, 43.754212137734854),
+            Offset(26.719589639523896, 44.649063712250836),
+            Offset(22.740462033074415, 44.789265598136765),
+            Offset(18.807366050629092, 44.16969391606726),
+            Offset(15.064042333303117, 42.81299180298022),
+            Offset(11.647296022348167, 40.76874188603618),
+            Offset(8.748687777815066, 38.183704576077155),
+            Offset(6.419891893084303, 35.16780188529314),
+            Offset(4.73813386593028, 31.92215330785626),
+            Offset(3.6973757565620238, 28.6457990518063),
+            Offset(3.2320178725715425, 25.571915504984908),
+            Offset(3.195599195915945, 23.02178367268453),
+            Offset(3.2998710379255467, 21.701160955289282),
+            Offset(3.3000000000000016, 21.7),
+          ],
+          <Offset>[
+            Offset(44.698216863261024, 21.684008056702904),
+            Offset(44.739436081547524, 22.08798770417014),
+            Offset(44.82689556057646, 24.14290314496488),
+            Offset(44.611028070432106, 26.99424813264697),
+            Offset(43.88355111957557, 30.19874139444654),
+            Offset(42.54330161486988, 33.482930202208806),
+            Offset(40.55186097073469, 36.64183129160762),
+            Offset(37.94450929160821, 39.470315465957775),
+            Offset(34.77465723666519, 41.823769562928554),
+            Offset(31.18597948759057, 43.548444920349226),
+            Offset(27.334679797712376, 44.5586942836049),
+            Offset(23.361509445604124, 44.81759663870801),
+            Offset(19.41167364777919, 44.315690027304406),
+            Offset(15.629524826054936, 43.07131734732473),
+            Offset(12.234167129202369, 41.18560958640855),
+            Offset(9.300143724170985, 38.754464594487),
+            Offset(6.946944741656527, 35.95714457367995),
+            Offset(5.2123712616765285, 32.989160494224244),
+            Offset(4.0836086450664855, 30.092401447469573),
+            Offset(3.4882931924585017, 27.61246229619848),
+            Offset(3.300129027185985, 26.30116094805467),
+            Offset(3.3000000000000016, 26.3),
+          ],
+          <Offset>[
+            Offset(44.698216863261024, 21.684008056702904),
+            Offset(44.739436081547524, 22.08798770417014),
+            Offset(44.82689556057646, 24.14290314496488),
+            Offset(44.611028070432106, 26.99424813264697),
+            Offset(43.88355111957557, 30.19874139444654),
+            Offset(42.54330161486988, 33.482930202208806),
+            Offset(40.55186097073469, 36.64183129160762),
+            Offset(37.94450929160821, 39.470315465957775),
+            Offset(34.77465723666519, 41.823769562928554),
+            Offset(31.18597948759057, 43.548444920349226),
+            Offset(27.334679797712376, 44.5586942836049),
+            Offset(23.361509445604124, 44.81759663870801),
+            Offset(19.41167364777919, 44.315690027304406),
+            Offset(15.629524826054936, 43.07131734732473),
+            Offset(12.234167129202369, 41.18560958640855),
+            Offset(9.300143724170985, 38.754464594487),
+            Offset(6.946944741656527, 35.95714457367995),
+            Offset(5.2123712616765285, 32.989160494224244),
+            Offset(4.0836086450664855, 30.092401447469573),
+            Offset(3.4882931924585017, 27.61246229619848),
+            Offset(3.300129027185985, 26.30116094805467),
+            Offset(3.3000000000000016, 26.3),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.634146341463,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(35.49111177355083, 12.491118637862245),
+            Offset(35.71330868590466, 12.717340755446422),
+            Offset(36.76903541252949, 13.927674814941295),
+            Offset(38.028308888140046, 15.771601022134929),
+            Offset(39.14789282630049, 18.080427133406147),
+            Offset(39.93271083941276, 20.736761530678603),
+            Offset(40.25934512440292, 23.63435519208529),
+            Offset(40.04924491730148, 26.630919532116586),
+            Offset(39.264434687200016, 29.612221813168535),
+            Offset(37.917521599920704, 32.41442764041267),
+            Offset(36.061972697924574, 34.909116125265236),
+            Offset(33.765601680835694, 37.005115294039456),
+            Offset(31.112332725379705, 38.625823847000646),
+            Offset(28.199133356045557, 39.71201066248828),
+            Offset(25.20893585263977, 40.21846090429675),
+            Offset(22.23397280473206, 40.167286350701346),
+            Offset(19.4345490247564, 39.60950535387484),
+            Offset(16.929085156741415, 38.64589236883111),
+            Offset(14.827762075025696, 37.430191787449026),
+            Offset(13.255038432571515, 36.20843155014127),
+            Offset(12.500644991237632, 35.50064495506456),
+            Offset(12.5, 35.5),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(35.49111177355083, 12.491118637862245),
+            Offset(35.71330868590466, 12.717340755446422),
+            Offset(36.76903541252949, 13.927674814941295),
+            Offset(38.028308888140046, 15.771601022134929),
+            Offset(39.14789282630049, 18.080427133406147),
+            Offset(39.93271083941276, 20.736761530678603),
+            Offset(40.25934512440292, 23.63435519208529),
+            Offset(40.04924491730148, 26.630919532116586),
+            Offset(39.264434687200016, 29.612221813168535),
+            Offset(37.917521599920704, 32.41442764041267),
+            Offset(36.061972697924574, 34.909116125265236),
+            Offset(33.765601680835694, 37.005115294039456),
+            Offset(31.112332725379705, 38.625823847000646),
+            Offset(28.199133356045557, 39.71201066248828),
+            Offset(25.20893585263977, 40.21846090429675),
+            Offset(22.23397280473206, 40.167286350701346),
+            Offset(19.4345490247564, 39.60950535387484),
+            Offset(16.929085156741415, 38.64589236883111),
+            Offset(14.827762075025696, 37.430191787449026),
+            Offset(13.255038432571515, 36.20843155014127),
+            Offset(12.500644991237632, 35.50064495506456),
+            Offset(12.5, 35.5),
+          ],
+          <Offset>[
+            Offset(3.2911213835868267, 12.515996061883946),
+            Offset(3.5189535832631194, 12.114431537554925),
+            Offset(4.791130575906015, 10.152280496482213),
+            Offset(6.868917875732878, 7.651727147749964),
+            Offset(9.653440856248416, 5.16077918981685),
+            Offset(13.05838180718493, 2.9995002125507035),
+            Offset(16.984359219158236, 1.3831747490018031),
+            Offset(21.26358937804263, 0.47868930293129175),
+            Offset(25.751336663555943, 0.38490271265255416),
+            Offset(30.21319005660945, 1.1496987039459512),
+            Offset(34.44797349620151, 2.7495917727994907),
+            Offset(38.30092073932097, 5.126111529214247),
+            Offset(41.63122029564902, 8.192404645668148),
+            Offset(44.317161585065364, 11.836409036540893),
+            Offset(46.222270924959545, 15.820105444585629),
+            Offset(47.34061176908905, 20.005523533094575),
+            Offset(47.67948788552222, 24.147829223791117),
+            Offset(47.33261525366699, 28.040923833029577),
+            Offset(46.47116367241834, 31.469056379984426),
+            Offset(45.389788797169174, 34.15957357434337),
+            Offset(44.700644940595325, 35.49883903024149),
+            Offset(44.699999999999996, 35.5),
+          ],
+          <Offset>[
+            Offset(3.2911213835868267, 12.515996061883946),
+            Offset(3.5189535832631194, 12.114431537554925),
+            Offset(4.791130575906015, 10.152280496482213),
+            Offset(6.868917875732878, 7.651727147749964),
+            Offset(9.653440856248416, 5.16077918981685),
+            Offset(13.05838180718493, 2.9995002125507035),
+            Offset(16.984359219158236, 1.3831747490018031),
+            Offset(21.26358937804263, 0.47868930293129175),
+            Offset(25.751336663555943, 0.38490271265255416),
+            Offset(30.21319005660945, 1.1496987039459512),
+            Offset(34.44797349620151, 2.7495917727994907),
+            Offset(38.30092073932097, 5.126111529214247),
+            Offset(41.63122029564902, 8.192404645668148),
+            Offset(44.317161585065364, 11.836409036540893),
+            Offset(46.222270924959545, 15.820105444585629),
+            Offset(47.34061176908905, 20.005523533094575),
+            Offset(47.67948788552222, 24.147829223791117),
+            Offset(47.33261525366699, 28.040923833029577),
+            Offset(46.47116367241834, 31.469056379984426),
+            Offset(45.389788797169174, 34.15957357434337),
+            Offset(44.700644940595325, 35.49883903024149),
+            Offset(44.699999999999996, 35.5),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(3.2911213835868267, 12.515996061883946),
+            Offset(3.5189535832631194, 12.114431537554925),
+            Offset(4.791130575906015, 10.152280496482213),
+            Offset(6.868917875732878, 7.651727147749964),
+            Offset(9.653440856248416, 5.16077918981685),
+            Offset(13.05838180718493, 2.9995002125507035),
+            Offset(16.984359219158236, 1.3831747490018031),
+            Offset(21.26358937804263, 0.47868930293129175),
+            Offset(25.751336663555943, 0.38490271265255416),
+            Offset(30.21319005660945, 1.1496987039459512),
+            Offset(34.44797349620151, 2.7495917727994907),
+            Offset(38.30092073932097, 5.126111529214247),
+            Offset(41.63122029564902, 8.192404645668148),
+            Offset(44.317161585065364, 11.836409036540893),
+            Offset(46.222270924959545, 15.820105444585629),
+            Offset(47.34061176908905, 20.005523533094575),
+            Offset(47.67948788552222, 24.147829223791117),
+            Offset(47.33261525366699, 28.040923833029577),
+            Offset(46.47116367241834, 31.469056379984426),
+            Offset(45.389788797169174, 34.15957357434337),
+            Offset(44.700644940595325, 35.49883903024149),
+            Offset(44.699999999999996, 35.5),
+          ],
+          <Offset>[
+            Offset(3.29467530130421, 17.11599468902166),
+            Offset(3.432823694992905, 16.713625123646572),
+            Offset(4.2517885304118614, 14.720552615999853),
+            Offset(5.708935893677882, 12.103068720950988),
+            Offset(7.807776864307087, 9.374272328395719),
+            Offset(10.524487333166658, 6.838690074297536),
+            Offset(13.805619155860596, 4.708172735465329),
+            Offset(17.52755648815902, 3.162354379968269),
+            Offset(21.57600536348223, 2.3153452874588503),
+            Offset(25.746800208542776, 2.2503174958475576),
+            Offset(29.853755731563552, 2.9801630873313556),
+            Offset(33.74677734434594, 4.478208806573495),
+            Offset(37.28358898117295, 6.68970642134396),
+            Offset(40.334932781358596, 9.533833575252348),
+            Offset(42.73679157357225, 12.81820043425423),
+            Offset(44.46035993800237, 16.41886082390072),
+            Offset(45.47067700979597, 20.11283795796743),
+            Offset(45.81761974855248, 23.697562390611637),
+            Offset(45.619572899923405, 26.94857043749976),
+            Offset(45.097094800626614, 29.568894950829417),
+            Offset(44.70038695133489, 30.898839037476105),
+            Offset(44.699999999999996, 30.900000000000002),
+          ],
+          <Offset>[
+            Offset(3.29467530130421, 17.11599468902166),
+            Offset(3.432823694992905, 16.713625123646572),
+            Offset(4.2517885304118614, 14.720552615999853),
+            Offset(5.708935893677882, 12.103068720950988),
+            Offset(7.807776864307087, 9.374272328395719),
+            Offset(10.524487333166658, 6.838690074297536),
+            Offset(13.805619155860596, 4.708172735465329),
+            Offset(17.52755648815902, 3.162354379968269),
+            Offset(21.57600536348223, 2.3153452874588503),
+            Offset(25.746800208542776, 2.2503174958475576),
+            Offset(29.853755731563552, 2.9801630873313556),
+            Offset(33.74677734434594, 4.478208806573495),
+            Offset(37.28358898117295, 6.68970642134396),
+            Offset(40.334932781358596, 9.533833575252348),
+            Offset(42.73679157357225, 12.81820043425423),
+            Offset(44.46035993800237, 16.41886082390072),
+            Offset(45.47067700979597, 20.11283795796743),
+            Offset(45.81761974855248, 23.697562390611637),
+            Offset(45.619572899923405, 26.94857043749976),
+            Offset(45.097094800626614, 29.568894950829417),
+            Offset(44.70038695133489, 30.898839037476105),
+            Offset(44.699999999999996, 30.900000000000002),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(3.29467530130421, 17.11599468902166),
+            Offset(3.432823694992905, 16.713625123646572),
+            Offset(4.2517885304118614, 14.720552615999853),
+            Offset(5.708935893677882, 12.103068720950988),
+            Offset(7.807776864307087, 9.374272328395719),
+            Offset(10.524487333166658, 6.838690074297536),
+            Offset(13.805619155860596, 4.708172735465329),
+            Offset(17.52755648815902, 3.162354379968269),
+            Offset(21.57600536348223, 2.3153452874588503),
+            Offset(25.746800208542776, 2.2503174958475576),
+            Offset(29.853755731563552, 2.9801630873313556),
+            Offset(33.74677734434594, 4.478208806573495),
+            Offset(37.28358898117295, 6.68970642134396),
+            Offset(40.334932781358596, 9.533833575252348),
+            Offset(42.73679157357225, 12.81820043425423),
+            Offset(44.46035993800237, 16.41886082390072),
+            Offset(45.47067700979597, 20.11283795796743),
+            Offset(45.81761974855248, 23.697562390611637),
+            Offset(45.619572899923405, 26.94857043749976),
+            Offset(45.097094800626614, 29.568894950829417),
+            Offset(44.70038695133489, 30.898839037476105),
+            Offset(44.699999999999996, 30.900000000000002),
+          ],
+          <Offset>[
+            Offset(35.49466569126821, 17.09111726499996),
+            Offset(35.62717879763444, 17.31653434153807),
+            Offset(36.22969336703533, 18.495946934458935),
+            Offset(36.86832690608505, 20.22294259533595),
+            Offset(37.30222883435916, 22.293920271985016),
+            Offset(37.39881636539449, 24.575951392425434),
+            Offset(37.08060506110528, 26.959353178548817),
+            Offset(36.31321202741787, 29.314584609153563),
+            Offset(35.08910338712631, 31.54266438797483),
+            Offset(33.45113175185403, 33.51504643231427),
+            Offset(31.46775493328661, 35.1396874397971),
+            Offset(29.21145828586066, 36.357212571398705),
+            Offset(26.764701410903633, 37.123125622676454),
+            Offset(24.21690455233879, 37.409435201199734),
+            Offset(21.72345650125247, 37.21655589396535),
+            Offset(19.353720973645377, 36.580623641507486),
+            Offset(17.225738149030153, 35.574514088051146),
+            Offset(15.414089651626908, 34.30253092641317),
+            Offset(13.976171302530755, 32.90970584496436),
+            Offset(12.962344436028957, 31.61775292662732),
+            Offset(12.500387001977192, 30.900644962299175),
+            Offset(12.5, 30.900000000000002),
+          ],
+          <Offset>[
+            Offset(35.49466569126821, 17.09111726499996),
+            Offset(35.62717879763444, 17.31653434153807),
+            Offset(36.22969336703533, 18.495946934458935),
+            Offset(36.86832690608505, 20.22294259533595),
+            Offset(37.30222883435916, 22.293920271985016),
+            Offset(37.39881636539449, 24.575951392425434),
+            Offset(37.08060506110528, 26.959353178548817),
+            Offset(36.31321202741787, 29.314584609153563),
+            Offset(35.08910338712631, 31.54266438797483),
+            Offset(33.45113175185403, 33.51504643231427),
+            Offset(31.46775493328661, 35.1396874397971),
+            Offset(29.21145828586066, 36.357212571398705),
+            Offset(26.764701410903633, 37.123125622676454),
+            Offset(24.21690455233879, 37.409435201199734),
+            Offset(21.72345650125247, 37.21655589396535),
+            Offset(19.353720973645377, 36.580623641507486),
+            Offset(17.225738149030153, 35.574514088051146),
+            Offset(15.414089651626908, 34.30253092641317),
+            Offset(13.976171302530755, 32.90970584496436),
+            Offset(12.962344436028957, 31.61775292662732),
+            Offset(12.500387001977192, 30.900644962299175),
+            Offset(12.5, 30.900000000000002),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(35.49466569126821, 17.09111726499996),
+            Offset(35.62717879763444, 17.31653434153807),
+            Offset(36.22969336703533, 18.495946934458935),
+            Offset(36.86832690608505, 20.22294259533595),
+            Offset(37.30222883435916, 22.293920271985016),
+            Offset(37.39881636539449, 24.575951392425434),
+            Offset(37.08060506110528, 26.959353178548817),
+            Offset(36.31321202741787, 29.314584609153563),
+            Offset(35.08910338712631, 31.54266438797483),
+            Offset(33.45113175185403, 33.51504643231427),
+            Offset(31.46775493328661, 35.1396874397971),
+            Offset(29.21145828586066, 36.357212571398705),
+            Offset(26.764701410903633, 37.123125622676454),
+            Offset(24.21690455233879, 37.409435201199734),
+            Offset(21.72345650125247, 37.21655589396535),
+            Offset(19.353720973645377, 36.580623641507486),
+            Offset(17.225738149030153, 35.574514088051146),
+            Offset(15.414089651626908, 34.30253092641317),
+            Offset(13.976171302530755, 32.90970584496436),
+            Offset(12.962344436028957, 31.61775292662732),
+            Offset(12.500387001977192, 30.900644962299175),
+            Offset(12.5, 30.900000000000002),
+          ],
+          <Offset>[
+            Offset(35.49111177355083, 12.491118637862245),
+            Offset(35.71330868590466, 12.717340755446422),
+            Offset(36.76903541252949, 13.927674814941295),
+            Offset(38.028308888140046, 15.771601022134929),
+            Offset(39.14789282630049, 18.080427133406147),
+            Offset(39.93271083941276, 20.736761530678603),
+            Offset(40.25934512440292, 23.63435519208529),
+            Offset(40.04924491730148, 26.630919532116586),
+            Offset(39.264434687200016, 29.612221813168535),
+            Offset(37.917521599920704, 32.41442764041267),
+            Offset(36.061972697924574, 34.909116125265236),
+            Offset(33.765601680835694, 37.005115294039456),
+            Offset(31.112332725379705, 38.625823847000646),
+            Offset(28.199133356045557, 39.71201066248828),
+            Offset(25.20893585263977, 40.21846090429675),
+            Offset(22.23397280473206, 40.167286350701346),
+            Offset(19.4345490247564, 39.60950535387484),
+            Offset(16.929085156741415, 38.64589236883111),
+            Offset(14.827762075025696, 37.430191787449026),
+            Offset(13.255038432571515, 36.20843155014127),
+            Offset(12.500644991237632, 35.50064495506456),
+            Offset(12.5, 35.5),
+          ],
+          <Offset>[
+            Offset(35.49111177355083, 12.491118637862245),
+            Offset(35.71330868590466, 12.717340755446422),
+            Offset(36.76903541252949, 13.927674814941295),
+            Offset(38.028308888140046, 15.771601022134929),
+            Offset(39.14789282630049, 18.080427133406147),
+            Offset(39.93271083941276, 20.736761530678603),
+            Offset(40.25934512440292, 23.63435519208529),
+            Offset(40.04924491730148, 26.630919532116586),
+            Offset(39.264434687200016, 29.612221813168535),
+            Offset(37.917521599920704, 32.41442764041267),
+            Offset(36.061972697924574, 34.909116125265236),
+            Offset(33.765601680835694, 37.005115294039456),
+            Offset(31.112332725379705, 38.625823847000646),
+            Offset(28.199133356045557, 39.71201066248828),
+            Offset(25.20893585263977, 40.21846090429675),
+            Offset(22.23397280473206, 40.167286350701346),
+            Offset(19.4345490247564, 39.60950535387484),
+            Offset(16.929085156741415, 38.64589236883111),
+            Offset(14.827762075025696, 37.430191787449026),
+            Offset(13.255038432571515, 36.20843155014127),
+            Offset(12.500644991237632, 35.50064495506456),
+            Offset(12.5, 35.5),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.634146341463,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(35.50532744442037, 30.891113146413097),
+            Offset(35.3687891328238, 31.114115099813013),
+            Offset(34.61166723055287, 32.200763293011846),
+            Offset(33.38838095992007, 33.576967314939026),
+            Offset(31.76523685853518, 34.93439968772162),
+            Offset(29.797132943339673, 36.093520977665925),
+            Offset(27.54438487121236, 36.9343471379394),
+            Offset(25.105113357767024, 37.3655798402645),
+            Offset(22.563109486905173, 37.333992112393716),
+            Offset(20.051962207654007, 36.816902808019094),
+            Offset(17.68510163937272, 35.8314013833927),
+            Offset(15.549028100935569, 34.41350440347644),
+            Offset(13.721807467475426, 32.61503094970389),
+            Offset(12.270218141218486, 30.5017088173341),
+            Offset(11.267018447090559, 28.21084086297116),
+            Offset(10.71296548038534, 25.820635513925914),
+            Offset(10.599305521851413, 23.469540290580085),
+            Offset(10.869103136283398, 21.272446599159352),
+            Offset(11.421398985045926, 19.348248017510365),
+            Offset(12.084262446401288, 17.84571705608547),
+            Offset(12.499613034195878, 17.100644984003022),
+            Offset(12.5, 17.1),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(35.50532744442037, 30.891113146413097),
+            Offset(35.3687891328238, 31.114115099813013),
+            Offset(34.61166723055287, 32.200763293011846),
+            Offset(33.38838095992007, 33.576967314939026),
+            Offset(31.76523685853518, 34.93439968772162),
+            Offset(29.797132943339673, 36.093520977665925),
+            Offset(27.54438487121236, 36.9343471379394),
+            Offset(25.105113357767024, 37.3655798402645),
+            Offset(22.563109486905173, 37.333992112393716),
+            Offset(20.051962207654007, 36.816902808019094),
+            Offset(17.68510163937272, 35.8314013833927),
+            Offset(15.549028100935569, 34.41350440347644),
+            Offset(13.721807467475426, 32.61503094970389),
+            Offset(12.270218141218486, 30.5017088173341),
+            Offset(11.267018447090559, 28.21084086297116),
+            Offset(10.71296548038534, 25.820635513925914),
+            Offset(10.599305521851413, 23.469540290580085),
+            Offset(10.869103136283398, 21.272446599159352),
+            Offset(11.421398985045926, 19.348248017510365),
+            Offset(12.084262446401288, 17.84571705608547),
+            Offset(12.499613034195878, 17.100644984003022),
+            Offset(12.5, 17.1),
+          ],
+          <Offset>[
+            Offset(3.3053370544563663, 30.9159905704348),
+            Offset(3.174434030182262, 30.511205881921516),
+            Offset(2.6337623939293966, 28.42536897455276),
+            Offset(2.2289899475128987, 25.45709344055406),
+            Offset(2.270784888483103, 22.014751744132322),
+            Offset(2.922803911111842, 18.35625965953803),
+            Offset(4.269398965967673, 14.683166694855906),
+            Offset(6.3194578185081784, 11.213349611079208),
+            Offset(9.0500114632611, 8.106673011877739),
+            Offset(12.347630664342756, 5.552173871552384),
+            Offset(16.071102437649664, 3.6718770309269573),
+            Offset(20.084347159420847, 2.534500638651229),
+            Offset(24.240695037744743, 2.181611748371399),
+            Offset(28.388246370238292, 2.6261071913867156),
+            Offset(32.28035351941033, 3.812485403260041),
+            Offset(35.81960444474234, 5.658872696319149),
+            Offset(38.844244382617234, 8.007864160496363),
+            Offset(41.27263323320897, 10.667478063357821),
+            Offset(43.06480058243858, 13.387112610045765),
+            Offset(44.21901281099895, 15.796859080287568),
+            Offset(44.69961298355358, 17.09883905917995),
+            Offset(44.699999999999996, 17.1),
+          ],
+          <Offset>[
+            Offset(3.3053370544563663, 30.9159905704348),
+            Offset(3.174434030182262, 30.511205881921516),
+            Offset(2.6337623939293966, 28.42536897455276),
+            Offset(2.2289899475128987, 25.45709344055406),
+            Offset(2.270784888483103, 22.014751744132322),
+            Offset(2.922803911111842, 18.35625965953803),
+            Offset(4.269398965967673, 14.683166694855906),
+            Offset(6.3194578185081784, 11.213349611079208),
+            Offset(9.0500114632611, 8.106673011877739),
+            Offset(12.347630664342756, 5.552173871552384),
+            Offset(16.071102437649664, 3.6718770309269573),
+            Offset(20.084347159420847, 2.534500638651229),
+            Offset(24.240695037744743, 2.181611748371399),
+            Offset(28.388246370238292, 2.6261071913867156),
+            Offset(32.28035351941033, 3.812485403260041),
+            Offset(35.81960444474234, 5.658872696319149),
+            Offset(38.844244382617234, 8.007864160496363),
+            Offset(41.27263323320897, 10.667478063357821),
+            Offset(43.06480058243858, 13.387112610045765),
+            Offset(44.21901281099895, 15.796859080287568),
+            Offset(44.69961298355358, 17.09883905917995),
+            Offset(44.699999999999996, 17.1),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(3.3053370544563663, 30.9159905704348),
+            Offset(3.174434030182262, 30.511205881921516),
+            Offset(2.6337623939293966, 28.42536897455276),
+            Offset(2.2289899475128987, 25.45709344055406),
+            Offset(2.270784888483103, 22.014751744132322),
+            Offset(2.922803911111842, 18.35625965953803),
+            Offset(4.269398965967673, 14.683166694855906),
+            Offset(6.3194578185081784, 11.213349611079208),
+            Offset(9.0500114632611, 8.106673011877739),
+            Offset(12.347630664342756, 5.552173871552384),
+            Offset(16.071102437649664, 3.6718770309269573),
+            Offset(20.084347159420847, 2.534500638651229),
+            Offset(24.240695037744743, 2.181611748371399),
+            Offset(28.388246370238292, 2.6261071913867156),
+            Offset(32.28035351941033, 3.812485403260041),
+            Offset(35.81960444474234, 5.658872696319149),
+            Offset(38.844244382617234, 8.007864160496363),
+            Offset(41.27263323320897, 10.667478063357821),
+            Offset(43.06480058243858, 13.387112610045765),
+            Offset(44.21901281099895, 15.796859080287568),
+            Offset(44.69961298355358, 17.09883905917995),
+            Offset(44.699999999999996, 17.1),
+          ],
+          <Offset>[
+            Offset(3.3088909721737494, 35.515989197572516),
+            Offset(3.0883041419120474, 35.11039946801316),
+            Offset(2.094420348435243, 32.9936410940704),
+            Offset(1.069007965457903, 29.90843501375508),
+            Offset(0.4251208965417739, 26.22824488271119),
+            Offset(0.3889094370935702, 22.19544952128486),
+            Offset(1.0906589026700306, 18.008164681319432),
+            Offset(2.5834249286245647, 13.897014688116185),
+            Offset(4.874680163187389, 10.037115586684035),
+            Offset(7.881240816276082, 6.65279266345399),
+            Offset(11.4768846730117, 3.902448345458822),
+            Offset(15.530203764445819, 1.8865979160104764),
+            Offset(19.89306372326867, 0.6789135240472106),
+            Offset(24.406017566531524, 0.32353173009817127),
+            Offset(28.794874168023036, 0.8105803929286424),
+            Offset(32.93935261365566, 2.0722099871252926),
+            Offset(36.63543350689098, 3.9728728946726743),
+            Offset(39.757637728094466, 6.324116620939881),
+            Offset(42.213209809943635, 8.866626667561102),
+            Offset(43.92631881445639, 11.206180456773618),
+            Offset(44.69935499429313, 12.498839066414565),
+            Offset(44.699999999999996, 12.5),
+          ],
+          <Offset>[
+            Offset(3.3088909721737494, 35.515989197572516),
+            Offset(3.0883041419120474, 35.11039946801316),
+            Offset(2.094420348435243, 32.9936410940704),
+            Offset(1.069007965457903, 29.90843501375508),
+            Offset(0.4251208965417739, 26.22824488271119),
+            Offset(0.3889094370935702, 22.19544952128486),
+            Offset(1.0906589026700306, 18.008164681319432),
+            Offset(2.5834249286245647, 13.897014688116185),
+            Offset(4.874680163187389, 10.037115586684035),
+            Offset(7.881240816276082, 6.65279266345399),
+            Offset(11.4768846730117, 3.902448345458822),
+            Offset(15.530203764445819, 1.8865979160104764),
+            Offset(19.89306372326867, 0.6789135240472106),
+            Offset(24.406017566531524, 0.32353173009817127),
+            Offset(28.794874168023036, 0.8105803929286424),
+            Offset(32.93935261365566, 2.0722099871252926),
+            Offset(36.63543350689098, 3.9728728946726743),
+            Offset(39.757637728094466, 6.324116620939881),
+            Offset(42.213209809943635, 8.866626667561102),
+            Offset(43.92631881445639, 11.206180456773618),
+            Offset(44.69935499429313, 12.498839066414565),
+            Offset(44.699999999999996, 12.5),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(3.3088909721737494, 35.515989197572516),
+            Offset(3.0883041419120474, 35.11039946801316),
+            Offset(2.094420348435243, 32.9936410940704),
+            Offset(1.069007965457903, 29.90843501375508),
+            Offset(0.4251208965417739, 26.22824488271119),
+            Offset(0.3889094370935702, 22.19544952128486),
+            Offset(1.0906589026700306, 18.008164681319432),
+            Offset(2.5834249286245647, 13.897014688116185),
+            Offset(4.874680163187389, 10.037115586684035),
+            Offset(7.881240816276082, 6.65279266345399),
+            Offset(11.4768846730117, 3.902448345458822),
+            Offset(15.530203764445819, 1.8865979160104764),
+            Offset(19.89306372326867, 0.6789135240472106),
+            Offset(24.406017566531524, 0.32353173009817127),
+            Offset(28.794874168023036, 0.8105803929286424),
+            Offset(32.93935261365566, 2.0722099871252926),
+            Offset(36.63543350689098, 3.9728728946726743),
+            Offset(39.757637728094466, 6.324116620939881),
+            Offset(42.213209809943635, 8.866626667561102),
+            Offset(43.92631881445639, 11.206180456773618),
+            Offset(44.69935499429313, 12.498839066414565),
+            Offset(44.699999999999996, 12.5),
+          ],
+          <Offset>[
+            Offset(35.50888136213775, 35.49111177355081),
+            Offset(35.282659244553585, 35.713308685904664),
+            Offset(34.072325185058716, 36.76903541252948),
+            Offset(32.22839897786507, 38.028308888140046),
+            Offset(29.91957286659385, 39.14789282630049),
+            Offset(27.2632384693214, 39.93271083941276),
+            Offset(24.365644807914713, 40.25934512440292),
+            Offset(21.369080467883414, 40.04924491730148),
+            Offset(18.38777818683146, 39.264434687200016),
+            Offset(15.585572359587335, 37.917521599920704),
+            Offset(13.09088387473476, 36.06197269792457),
+            Offset(10.99488470596054, 33.76560168083569),
+            Offset(9.374176152999356, 31.112332725379705),
+            Offset(8.287989337511718, 28.199133356045557),
+            Offset(7.781539095703257, 25.208935852639762),
+            Offset(7.8327136492986575, 22.233972804732062),
+            Offset(8.390494646125166, 19.434549024756393),
+            Offset(9.354107631168892, 16.929085156741415),
+            Offset(10.569808212550985, 14.8277620750257),
+            Offset(11.79156844985873, 13.255038432571517),
+            Offset(12.499355044935438, 12.500644991237637),
+            Offset(12.5, 12.5),
+          ],
+          <Offset>[
+            Offset(35.50888136213775, 35.49111177355081),
+            Offset(35.282659244553585, 35.713308685904664),
+            Offset(34.072325185058716, 36.76903541252948),
+            Offset(32.22839897786507, 38.028308888140046),
+            Offset(29.91957286659385, 39.14789282630049),
+            Offset(27.2632384693214, 39.93271083941276),
+            Offset(24.365644807914713, 40.25934512440292),
+            Offset(21.369080467883414, 40.04924491730148),
+            Offset(18.38777818683146, 39.264434687200016),
+            Offset(15.585572359587335, 37.917521599920704),
+            Offset(13.09088387473476, 36.06197269792457),
+            Offset(10.99488470596054, 33.76560168083569),
+            Offset(9.374176152999356, 31.112332725379705),
+            Offset(8.287989337511718, 28.199133356045557),
+            Offset(7.781539095703257, 25.208935852639762),
+            Offset(7.8327136492986575, 22.233972804732062),
+            Offset(8.390494646125166, 19.434549024756393),
+            Offset(9.354107631168892, 16.929085156741415),
+            Offset(10.569808212550985, 14.8277620750257),
+            Offset(11.79156844985873, 13.255038432571517),
+            Offset(12.499355044935438, 12.500644991237637),
+            Offset(12.5, 12.5),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(35.50888136213775, 35.49111177355081),
+            Offset(35.282659244553585, 35.713308685904664),
+            Offset(34.072325185058716, 36.76903541252948),
+            Offset(32.22839897786507, 38.028308888140046),
+            Offset(29.91957286659385, 39.14789282630049),
+            Offset(27.2632384693214, 39.93271083941276),
+            Offset(24.365644807914713, 40.25934512440292),
+            Offset(21.369080467883414, 40.04924491730148),
+            Offset(18.38777818683146, 39.264434687200016),
+            Offset(15.585572359587335, 37.917521599920704),
+            Offset(13.09088387473476, 36.06197269792457),
+            Offset(10.99488470596054, 33.76560168083569),
+            Offset(9.374176152999356, 31.112332725379705),
+            Offset(8.287989337511718, 28.199133356045557),
+            Offset(7.781539095703257, 25.208935852639762),
+            Offset(7.8327136492986575, 22.233972804732062),
+            Offset(8.390494646125166, 19.434549024756393),
+            Offset(9.354107631168892, 16.929085156741415),
+            Offset(10.569808212550985, 14.8277620750257),
+            Offset(11.79156844985873, 13.255038432571517),
+            Offset(12.499355044935438, 12.500644991237637),
+            Offset(12.5, 12.5),
+          ],
+          <Offset>[
+            Offset(35.50532744442037, 30.891113146413097),
+            Offset(35.3687891328238, 31.114115099813013),
+            Offset(34.61166723055287, 32.200763293011846),
+            Offset(33.38838095992007, 33.576967314939026),
+            Offset(31.76523685853518, 34.93439968772162),
+            Offset(29.797132943339673, 36.093520977665925),
+            Offset(27.54438487121236, 36.9343471379394),
+            Offset(25.105113357767024, 37.3655798402645),
+            Offset(22.563109486905173, 37.333992112393716),
+            Offset(20.051962207654007, 36.816902808019094),
+            Offset(17.68510163937272, 35.8314013833927),
+            Offset(15.549028100935569, 34.41350440347644),
+            Offset(13.721807467475426, 32.61503094970389),
+            Offset(12.270218141218486, 30.5017088173341),
+            Offset(11.267018447090559, 28.21084086297116),
+            Offset(10.71296548038534, 25.820635513925914),
+            Offset(10.599305521851413, 23.469540290580085),
+            Offset(10.869103136283398, 21.272446599159352),
+            Offset(11.421398985045926, 19.348248017510365),
+            Offset(12.084262446401288, 17.84571705608547),
+            Offset(12.499613034195878, 17.100644984003022),
+            Offset(12.5, 17.1),
+          ],
+          <Offset>[
+            Offset(35.50532744442037, 30.891113146413097),
+            Offset(35.3687891328238, 31.114115099813013),
+            Offset(34.61166723055287, 32.200763293011846),
+            Offset(33.38838095992007, 33.576967314939026),
+            Offset(31.76523685853518, 34.93439968772162),
+            Offset(29.797132943339673, 36.093520977665925),
+            Offset(27.54438487121236, 36.9343471379394),
+            Offset(25.105113357767024, 37.3655798402645),
+            Offset(22.563109486905173, 37.333992112393716),
+            Offset(20.051962207654007, 36.816902808019094),
+            Offset(17.68510163937272, 35.8314013833927),
+            Offset(15.549028100935569, 34.41350440347644),
+            Offset(13.721807467475426, 32.61503094970389),
+            Offset(12.270218141218486, 30.5017088173341),
+            Offset(11.267018447090559, 28.21084086297116),
+            Offset(10.71296548038534, 25.820635513925914),
+            Offset(10.599305521851413, 23.469540290580085),
+            Offset(10.869103136283398, 21.272446599159352),
+            Offset(11.421398985045926, 19.348248017510365),
+            Offset(12.084262446401288, 17.84571705608547),
+            Offset(12.499613034195878, 17.100644984003022),
+            Offset(12.5, 17.1),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.634146341463,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(35.4982196089856, 21.691115892137674),
+            Offset(35.54104890936423, 21.915727927629714),
+            Offset(35.69035132154117, 23.06421905397657),
+            Offset(35.70834492403006, 24.67428416853698),
+            Offset(35.45656484241783, 26.507413410563885),
+            Offset(34.86492189137621, 28.415141254172266),
+            Offset(33.901864997807635, 30.284351165012342),
+            Offset(32.57717913753425, 31.99824968619054),
+            Offset(30.913772087052596, 33.47310696278113),
+            Offset(28.984741903787356, 34.61566522421589),
+            Offset(26.873537168648646, 35.370258754328965),
+            Offset(24.65731489088563, 35.709309848757954),
+            Offset(22.41707009642756, 35.62042739835226),
+            Offset(20.23467574863202, 35.10685973991119),
+            Offset(18.23797714986516, 34.21465088363395),
+            Offset(16.473469142558695, 32.99396093231363),
+            Offset(15.016927273303907, 31.53952282222746),
+            Offset(13.899094146512402, 29.959169483995232),
+            Offset(13.124580530035814, 28.389219902479688),
+            Offset(12.669650439486402, 27.027074303113366),
+            Offset(12.500129012716759, 26.300644969533792),
+            Offset(12.5, 26.3),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(35.4982196089856, 21.691115892137674),
+            Offset(35.54104890936423, 21.915727927629714),
+            Offset(35.69035132154117, 23.06421905397657),
+            Offset(35.70834492403006, 24.67428416853698),
+            Offset(35.45656484241783, 26.507413410563885),
+            Offset(34.86492189137621, 28.415141254172266),
+            Offset(33.901864997807635, 30.284351165012342),
+            Offset(32.57717913753425, 31.99824968619054),
+            Offset(30.913772087052596, 33.47310696278113),
+            Offset(28.984741903787356, 34.61566522421589),
+            Offset(26.873537168648646, 35.370258754328965),
+            Offset(24.65731489088563, 35.709309848757954),
+            Offset(22.41707009642756, 35.62042739835226),
+            Offset(20.23467574863202, 35.10685973991119),
+            Offset(18.23797714986516, 34.21465088363395),
+            Offset(16.473469142558695, 32.99396093231363),
+            Offset(15.016927273303907, 31.53952282222746),
+            Offset(13.899094146512402, 29.959169483995232),
+            Offset(13.124580530035814, 28.389219902479688),
+            Offset(12.669650439486402, 27.027074303113366),
+            Offset(12.500129012716759, 26.300644969533792),
+            Offset(12.5, 26.3),
+          ],
+          <Offset>[
+            Offset(3.2982292190216, 21.715993316159373),
+            Offset(3.3466938067226906, 21.312818709738217),
+            Offset(3.712446484917704, 19.288824735517487),
+            Offset(4.5489539116228865, 16.554410294152014),
+            Offset(5.962112872365758, 13.587765466974588),
+            Offset(7.990592859148386, 10.677879936044366),
+            Offset(10.626879092562953, 8.033170721928855),
+            Offset(13.791523598275406, 5.846019457005246),
+            Offset(17.400674063408523, 4.245787862265146),
+            Offset(21.280410360476104, 3.350936287749171),
+            Offset(25.25953796692559, 3.2107344018632205),
+            Offset(29.192633949370908, 3.8303060839327383),
+            Offset(32.93595766669688, 5.187008197019772),
+            Offset(36.35270397765183, 7.231258113963804),
+            Offset(39.251312222184936, 9.816295423922835),
+            Offset(41.58010810691569, 12.832198114706863),
+            Offset(43.26186613406972, 16.07784669214374),
+            Offset(44.302624243437975, 19.354200948193697),
+            Offset(44.76798212742847, 22.428084495015092),
+            Offset(44.80440080408406, 24.978216327315465),
+            Offset(44.70012896207446, 26.29883904471072),
+            Offset(44.699999999999996, 26.3),
+          ],
+          <Offset>[
+            Offset(3.2982292190216, 21.715993316159373),
+            Offset(3.3466938067226906, 21.312818709738217),
+            Offset(3.712446484917704, 19.288824735517487),
+            Offset(4.5489539116228865, 16.554410294152014),
+            Offset(5.962112872365758, 13.587765466974588),
+            Offset(7.990592859148386, 10.677879936044366),
+            Offset(10.626879092562953, 8.033170721928855),
+            Offset(13.791523598275406, 5.846019457005246),
+            Offset(17.400674063408523, 4.245787862265146),
+            Offset(21.280410360476104, 3.350936287749171),
+            Offset(25.25953796692559, 3.2107344018632205),
+            Offset(29.192633949370908, 3.8303060839327383),
+            Offset(32.93595766669688, 5.187008197019772),
+            Offset(36.35270397765183, 7.231258113963804),
+            Offset(39.251312222184936, 9.816295423922835),
+            Offset(41.58010810691569, 12.832198114706863),
+            Offset(43.26186613406972, 16.07784669214374),
+            Offset(44.302624243437975, 19.354200948193697),
+            Offset(44.76798212742847, 22.428084495015092),
+            Offset(44.80440080408406, 24.978216327315465),
+            Offset(44.70012896207446, 26.29883904471072),
+            Offset(44.699999999999996, 26.3),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(3.2982292190216, 21.715993316159373),
+            Offset(3.3466938067226906, 21.312818709738217),
+            Offset(3.712446484917704, 19.288824735517487),
+            Offset(4.5489539116228865, 16.554410294152014),
+            Offset(5.962112872365758, 13.587765466974588),
+            Offset(7.990592859148386, 10.677879936044366),
+            Offset(10.626879092562953, 8.033170721928855),
+            Offset(13.791523598275406, 5.846019457005246),
+            Offset(17.400674063408523, 4.245787862265146),
+            Offset(21.280410360476104, 3.350936287749171),
+            Offset(25.25953796692559, 3.2107344018632205),
+            Offset(29.192633949370908, 3.8303060839327383),
+            Offset(32.93595766669688, 5.187008197019772),
+            Offset(36.35270397765183, 7.231258113963804),
+            Offset(39.251312222184936, 9.816295423922835),
+            Offset(41.58010810691569, 12.832198114706863),
+            Offset(43.26186613406972, 16.07784669214374),
+            Offset(44.302624243437975, 19.354200948193697),
+            Offset(44.76798212742847, 22.428084495015092),
+            Offset(44.80440080408406, 24.978216327315465),
+            Offset(44.70012896207446, 26.29883904471072),
+            Offset(44.699999999999996, 26.3),
+          ],
+          <Offset>[
+            Offset(3.301783136738983, 26.315991943297085),
+            Offset(3.260563918452476, 25.912012295829864),
+            Offset(3.1731044394235504, 23.857096855035127),
+            Offset(3.388971929567891, 21.005751867353034),
+            Offset(4.116448880424429, 17.801258605553457),
+            Offset(5.456698385130114, 14.517069797791198),
+            Offset(7.448139029265311, 11.35816870839238),
+            Offset(10.055490708391792, 8.529684534042223),
+            Offset(13.225342763334812, 6.1762304370714425),
+            Offset(16.81402051240943, 4.4515550796507775),
+            Offset(20.665320202287624, 3.4413057163950853),
+            Offset(24.63849055439588, 3.1824033612919855),
+            Offset(28.588326352220808, 3.6843099726955835),
+            Offset(32.37047517394506, 4.92868265267526),
+            Offset(35.76583287079764, 6.814390413591436),
+            Offset(38.699856275829006, 9.245535405513007),
+            Offset(41.05305525834348, 12.042855426320052),
+            Offset(42.78762873832347, 15.010839505775758),
+            Offset(43.91639135493352, 17.90759855253043),
+            Offset(44.5117068075415, 20.387537703801513),
+            Offset(44.69987097281401, 21.698839051945335),
+            Offset(44.699999999999996, 21.7),
+          ],
+          <Offset>[
+            Offset(3.301783136738983, 26.315991943297085),
+            Offset(3.260563918452476, 25.912012295829864),
+            Offset(3.1731044394235504, 23.857096855035127),
+            Offset(3.388971929567891, 21.005751867353034),
+            Offset(4.116448880424429, 17.801258605553457),
+            Offset(5.456698385130114, 14.517069797791198),
+            Offset(7.448139029265311, 11.35816870839238),
+            Offset(10.055490708391792, 8.529684534042223),
+            Offset(13.225342763334812, 6.1762304370714425),
+            Offset(16.81402051240943, 4.4515550796507775),
+            Offset(20.665320202287624, 3.4413057163950853),
+            Offset(24.63849055439588, 3.1824033612919855),
+            Offset(28.588326352220808, 3.6843099726955835),
+            Offset(32.37047517394506, 4.92868265267526),
+            Offset(35.76583287079764, 6.814390413591436),
+            Offset(38.699856275829006, 9.245535405513007),
+            Offset(41.05305525834348, 12.042855426320052),
+            Offset(42.78762873832347, 15.010839505775758),
+            Offset(43.91639135493352, 17.90759855253043),
+            Offset(44.5117068075415, 20.387537703801513),
+            Offset(44.69987097281401, 21.698839051945335),
+            Offset(44.699999999999996, 21.7),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(3.301783136738983, 26.315991943297085),
+            Offset(3.260563918452476, 25.912012295829864),
+            Offset(3.1731044394235504, 23.857096855035127),
+            Offset(3.388971929567891, 21.005751867353034),
+            Offset(4.116448880424429, 17.801258605553457),
+            Offset(5.456698385130114, 14.517069797791198),
+            Offset(7.448139029265311, 11.35816870839238),
+            Offset(10.055490708391792, 8.529684534042223),
+            Offset(13.225342763334812, 6.1762304370714425),
+            Offset(16.81402051240943, 4.4515550796507775),
+            Offset(20.665320202287624, 3.4413057163950853),
+            Offset(24.63849055439588, 3.1824033612919855),
+            Offset(28.588326352220808, 3.6843099726955835),
+            Offset(32.37047517394506, 4.92868265267526),
+            Offset(35.76583287079764, 6.814390413591436),
+            Offset(38.699856275829006, 9.245535405513007),
+            Offset(41.05305525834348, 12.042855426320052),
+            Offset(42.78762873832347, 15.010839505775758),
+            Offset(43.91639135493352, 17.90759855253043),
+            Offset(44.5117068075415, 20.387537703801513),
+            Offset(44.69987097281401, 21.698839051945335),
+            Offset(44.699999999999996, 21.7),
+          ],
+          <Offset>[
+            Offset(35.50177352670298, 26.291114519275386),
+            Offset(35.454919021094014, 26.51492151372136),
+            Offset(35.15100927604702, 27.63249117349421),
+            Offset(34.54836294197506, 29.125625741738),
+            Offset(33.610900850476504, 30.720906549142754),
+            Offset(32.331027417357944, 32.2543311159191),
+            Offset(30.723124934509997, 33.60934915147587),
+            Offset(28.84114624765064, 34.68191476322752),
+            Offset(26.738440786978884, 35.40354953758742),
+            Offset(24.518352055720683, 35.71628401611749),
+            Offset(22.279319404010682, 35.60083006886083),
+            Offset(20.1031714959106, 35.061407126117196),
+            Offset(18.06943878195149, 34.11772917402808),
+            Offset(16.252446944925254, 32.804284278622646),
+            Offset(14.75249779847786, 31.212745873302556),
+            Offset(13.593217311472014, 29.407298223119774),
+            Offset(12.80811639757766, 27.504531556403773),
+            Offset(12.384098641397896, 25.615808041577292),
+            Offset(12.272989757540874, 23.868733959995026),
+            Offset(12.376956442943843, 22.436395679599414),
+            Offset(12.499871023456318, 21.700644976768405),
+            Offset(12.5, 21.7),
+          ],
+          <Offset>[
+            Offset(35.50177352670298, 26.291114519275386),
+            Offset(35.454919021094014, 26.51492151372136),
+            Offset(35.15100927604702, 27.63249117349421),
+            Offset(34.54836294197506, 29.125625741738),
+            Offset(33.610900850476504, 30.720906549142754),
+            Offset(32.331027417357944, 32.2543311159191),
+            Offset(30.723124934509997, 33.60934915147587),
+            Offset(28.84114624765064, 34.68191476322752),
+            Offset(26.738440786978884, 35.40354953758742),
+            Offset(24.518352055720683, 35.71628401611749),
+            Offset(22.279319404010682, 35.60083006886083),
+            Offset(20.1031714959106, 35.061407126117196),
+            Offset(18.06943878195149, 34.11772917402808),
+            Offset(16.252446944925254, 32.804284278622646),
+            Offset(14.75249779847786, 31.212745873302556),
+            Offset(13.593217311472014, 29.407298223119774),
+            Offset(12.80811639757766, 27.504531556403773),
+            Offset(12.384098641397896, 25.615808041577292),
+            Offset(12.272989757540874, 23.868733959995026),
+            Offset(12.376956442943843, 22.436395679599414),
+            Offset(12.499871023456318, 21.700644976768405),
+            Offset(12.5, 21.7),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(35.50177352670298, 26.291114519275386),
+            Offset(35.454919021094014, 26.51492151372136),
+            Offset(35.15100927604702, 27.63249117349421),
+            Offset(34.54836294197506, 29.125625741738),
+            Offset(33.610900850476504, 30.720906549142754),
+            Offset(32.331027417357944, 32.2543311159191),
+            Offset(30.723124934509997, 33.60934915147587),
+            Offset(28.84114624765064, 34.68191476322752),
+            Offset(26.738440786978884, 35.40354953758742),
+            Offset(24.518352055720683, 35.71628401611749),
+            Offset(22.279319404010682, 35.60083006886083),
+            Offset(20.1031714959106, 35.061407126117196),
+            Offset(18.06943878195149, 34.11772917402808),
+            Offset(16.252446944925254, 32.804284278622646),
+            Offset(14.75249779847786, 31.212745873302556),
+            Offset(13.593217311472014, 29.407298223119774),
+            Offset(12.80811639757766, 27.504531556403773),
+            Offset(12.384098641397896, 25.615808041577292),
+            Offset(12.272989757540874, 23.868733959995026),
+            Offset(12.376956442943843, 22.436395679599414),
+            Offset(12.499871023456318, 21.700644976768405),
+            Offset(12.5, 21.7),
+          ],
+          <Offset>[
+            Offset(35.4982196089856, 21.691115892137674),
+            Offset(35.54104890936423, 21.915727927629714),
+            Offset(35.69035132154117, 23.06421905397657),
+            Offset(35.70834492403006, 24.67428416853698),
+            Offset(35.45656484241783, 26.507413410563885),
+            Offset(34.86492189137621, 28.415141254172266),
+            Offset(33.901864997807635, 30.284351165012342),
+            Offset(32.57717913753425, 31.99824968619054),
+            Offset(30.913772087052596, 33.47310696278113),
+            Offset(28.984741903787356, 34.61566522421589),
+            Offset(26.873537168648646, 35.370258754328965),
+            Offset(24.65731489088563, 35.709309848757954),
+            Offset(22.41707009642756, 35.62042739835226),
+            Offset(20.23467574863202, 35.10685973991119),
+            Offset(18.23797714986516, 34.21465088363395),
+            Offset(16.473469142558695, 32.99396093231363),
+            Offset(15.016927273303907, 31.53952282222746),
+            Offset(13.899094146512402, 29.959169483995232),
+            Offset(13.124580530035814, 28.389219902479688),
+            Offset(12.669650439486402, 27.027074303113366),
+            Offset(12.500129012716759, 26.300644969533792),
+            Offset(12.5, 26.3),
+          ],
+          <Offset>[
+            Offset(35.4982196089856, 21.691115892137674),
+            Offset(35.54104890936423, 21.915727927629714),
+            Offset(35.69035132154117, 23.06421905397657),
+            Offset(35.70834492403006, 24.67428416853698),
+            Offset(35.45656484241783, 26.507413410563885),
+            Offset(34.86492189137621, 28.415141254172266),
+            Offset(33.901864997807635, 30.284351165012342),
+            Offset(32.57717913753425, 31.99824968619054),
+            Offset(30.913772087052596, 33.47310696278113),
+            Offset(28.984741903787356, 34.61566522421589),
+            Offset(26.873537168648646, 35.370258754328965),
+            Offset(24.65731489088563, 35.709309848757954),
+            Offset(22.41707009642756, 35.62042739835226),
+            Offset(20.23467574863202, 35.10685973991119),
+            Offset(18.23797714986516, 34.21465088363395),
+            Offset(16.473469142558695, 32.99396093231363),
+            Offset(15.016927273303907, 31.53952282222746),
+            Offset(13.899094146512402, 29.959169483995232),
+            Offset(13.124580530035814, 28.389219902479688),
+            Offset(12.669650439486402, 27.027074303113366),
+            Offset(12.500129012716759, 26.300644969533792),
+            Offset(12.5, 26.3),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.390243902439,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(31.500705947534364, 37.904137673197035),
+            Offset(31.224359596665288, 38.04026654278025),
+            Offset(29.77500721437707, 38.610257636892996),
+            Offset(27.671279110116373, 39.06613773120253),
+            Offset(25.221379281133324, 39.03073015693383),
+            Offset(22.702260300479338, 38.4714008591571),
+            Offset(20.28270034295162, 37.48547892474107),
+            Offset(18.054569997652198, 36.18517574943152),
+            Offset(16.048128541632522, 34.59627819756455),
+            Offset(14.343114833822348, 32.739119933221964),
+            Offset(12.99519031795477, 30.657890586626984),
+            Offset(12.038214592605321, 28.40198375080891),
+            Offset(11.498734264325233, 26.032323169068647),
+            Offset(11.392995785033866, 23.617840044802506),
+            Offset(11.712187869419232, 21.29019602644484),
+            Offset(12.42139313017819, 19.101094337832414),
+            Offset(13.45637301051034, 17.152279319649516),
+            Offset(14.726397156564566, 15.511205096609956),
+            Offset(16.093303132855745, 14.22536480165701),
+            Offset(17.347278595340214, 13.332261414827599),
+            Offset(18.038917684662923, 12.930905807638549),
+            Offset(18.039538499999995, 12.930571500000006),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(31.500705947534364, 37.904137673197035),
+            Offset(31.224359596665288, 38.04026654278025),
+            Offset(29.77500721437707, 38.610257636892996),
+            Offset(27.671279110116373, 39.06613773120253),
+            Offset(25.221379281133324, 39.03073015693383),
+            Offset(22.702260300479338, 38.4714008591571),
+            Offset(20.28270034295162, 37.48547892474107),
+            Offset(18.054569997652198, 36.18517574943152),
+            Offset(16.048128541632522, 34.59627819756455),
+            Offset(14.343114833822348, 32.739119933221964),
+            Offset(12.99519031795477, 30.657890586626984),
+            Offset(12.038214592605321, 28.40198375080891),
+            Offset(11.498734264325233, 26.032323169068647),
+            Offset(11.392995785033866, 23.617840044802506),
+            Offset(11.712187869419232, 21.29019602644484),
+            Offset(12.42139313017819, 19.101094337832414),
+            Offset(13.45637301051034, 17.152279319649516),
+            Offset(14.726397156564566, 15.511205096609956),
+            Offset(16.093303132855745, 14.22536480165701),
+            Offset(17.347278595340214, 13.332261414827599),
+            Offset(18.038917684662923, 12.930905807638549),
+            Offset(18.039538499999995, 12.930571500000006),
+          ],
+          <Offset>[
+            Offset(42.20064775415372, 37.89587099404257),
+            Offset(41.91674410275704, 38.2405046640102),
+            Offset(40.34190628686553, 39.857813126404416),
+            Offset(37.83452426041213, 41.714593496656875),
+            Offset(34.57684474021924, 43.1287659571083),
+            Offset(30.91528934967189, 43.89206242373822),
+            Offset(27.130871371497264, 44.03241690707209),
+            Offset(23.411876177751978, 43.64328677129125),
+            Offset(19.813656815016323, 42.74069454351543),
+            Offset(16.453681942475647, 41.30395235032578),
+            Offset(13.431837558864594, 39.35824654869015),
+            Offset(10.822310505850872, 36.948639555937035),
+            Offset(8.696502202314639, 34.13978724004353),
+            Offset(7.116981614284766, 31.013066425911557),
+            Offset(6.150621971699855, 27.74766987427346),
+            Offset(5.782126079860243, 24.43272505675491),
+            Offset(5.987301553125755, 21.240953911434477),
+            Offset(6.686509374540779, 18.31557540123979),
+            Offset(7.725544616870944, 15.801723190853087),
+            Offset(8.849588162319126, 13.874059954477616),
+            Offset(9.523972698054738, 12.931383365108815),
+            Offset(9.524593499999996, 12.930571500000006),
+          ],
+          <Offset>[
+            Offset(42.20064775415372, 37.89587099404257),
+            Offset(41.91674410275704, 38.2405046640102),
+            Offset(40.34190628686553, 39.857813126404416),
+            Offset(37.83452426041213, 41.714593496656875),
+            Offset(34.57684474021924, 43.1287659571083),
+            Offset(30.91528934967189, 43.89206242373822),
+            Offset(27.130871371497264, 44.03241690707209),
+            Offset(23.411876177751978, 43.64328677129125),
+            Offset(19.813656815016323, 42.74069454351543),
+            Offset(16.453681942475647, 41.30395235032578),
+            Offset(13.431837558864594, 39.35824654869015),
+            Offset(10.822310505850872, 36.948639555937035),
+            Offset(8.696502202314639, 34.13978724004353),
+            Offset(7.116981614284766, 31.013066425911557),
+            Offset(6.150621971699855, 27.74766987427346),
+            Offset(5.782126079860243, 24.43272505675491),
+            Offset(5.987301553125755, 21.240953911434477),
+            Offset(6.686509374540779, 18.31557540123979),
+            Offset(7.725544616870944, 15.801723190853087),
+            Offset(8.849588162319126, 13.874059954477616),
+            Offset(9.523972698054738, 12.931383365108815),
+            Offset(9.524593499999996, 12.930571500000006),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.20064775415372, 37.89587099404257),
+            Offset(41.91674410275704, 38.2405046640102),
+            Offset(40.34190628686553, 39.857813126404416),
+            Offset(37.83452426041213, 41.714593496656875),
+            Offset(34.57684474021924, 43.1287659571083),
+            Offset(30.91528934967189, 43.89206242373822),
+            Offset(27.130871371497264, 44.03241690707209),
+            Offset(23.411876177751978, 43.64328677129125),
+            Offset(19.813656815016323, 42.74069454351543),
+            Offset(16.453681942475647, 41.30395235032578),
+            Offset(13.431837558864594, 39.35824654869015),
+            Offset(10.822310505850872, 36.948639555937035),
+            Offset(8.696502202314639, 34.13978724004353),
+            Offset(7.116981614284766, 31.013066425911557),
+            Offset(6.150621971699855, 27.74766987427346),
+            Offset(5.782126079860243, 24.43272505675491),
+            Offset(5.987301553125755, 21.240953911434477),
+            Offset(6.686509374540779, 18.31557540123979),
+            Offset(7.725544616870944, 15.801723190853087),
+            Offset(8.849588162319126, 13.874059954477616),
+            Offset(9.523972698054738, 12.931383365108815),
+            Offset(9.524593499999996, 12.930571500000006),
+          ],
+          <Offset>[
+            Offset(42.19072773916836, 25.055940826099334),
+            Offset(42.15702984823299, 25.409643256700093),
+            Offset(41.838972874279236, 27.177534239418264),
+            Offset(41.01267117895735, 29.518699316301966),
+            Offset(39.49448770042861, 31.902207406205207),
+            Offset(37.42008322716923, 34.036427564707154),
+            Offset(34.987196950294496, 35.814611672817314),
+            Offset(32.36160940398365, 37.21451935517152),
+            Offset(29.58695643015737, 38.22206061545487),
+            Offset(26.731480843000227, 38.77127181994182),
+            Offset(23.87226471334039, 38.83426985959836),
+            Offset(21.07829747200463, 38.407724460042374),
+            Offset(18.4254590874845, 37.50246571445624),
+            Offset(15.991253271615628, 36.144283430810475),
+            Offset(13.899590589094196, 34.421548951536714),
+            Offset(12.180082942567237, 32.39984551713645),
+            Offset(10.893711063267709, 30.203839660295976),
+            Offset(10.05175374009658, 27.963440739668336),
+            Offset(9.617174683906235, 25.84303341003485),
+            Offset(9.499746409899146, 24.07128847410292),
+            Offset(9.52454576701906, 23.149317349038636),
+            Offset(9.524593499999998, 23.148505500000006),
+          ],
+          <Offset>[
+            Offset(42.19072773916836, 25.055940826099334),
+            Offset(42.15702984823299, 25.409643256700093),
+            Offset(41.838972874279236, 27.177534239418264),
+            Offset(41.01267117895735, 29.518699316301966),
+            Offset(39.49448770042861, 31.902207406205207),
+            Offset(37.42008322716923, 34.036427564707154),
+            Offset(34.987196950294496, 35.814611672817314),
+            Offset(32.36160940398365, 37.21451935517152),
+            Offset(29.58695643015737, 38.22206061545487),
+            Offset(26.731480843000227, 38.77127181994182),
+            Offset(23.87226471334039, 38.83426985959836),
+            Offset(21.07829747200463, 38.407724460042374),
+            Offset(18.4254590874845, 37.50246571445624),
+            Offset(15.991253271615628, 36.144283430810475),
+            Offset(13.899590589094196, 34.421548951536714),
+            Offset(12.180082942567237, 32.39984551713645),
+            Offset(10.893711063267709, 30.203839660295976),
+            Offset(10.05175374009658, 27.963440739668336),
+            Offset(9.617174683906235, 25.84303341003485),
+            Offset(9.499746409899146, 24.07128847410292),
+            Offset(9.52454576701906, 23.149317349038636),
+            Offset(9.524593499999998, 23.148505500000006),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.19072773916836, 25.055940826099334),
+            Offset(42.15702984823299, 25.409643256700093),
+            Offset(41.838972874279236, 27.177534239418264),
+            Offset(41.01267117895735, 29.518699316301966),
+            Offset(39.49448770042861, 31.902207406205207),
+            Offset(37.42008322716923, 34.036427564707154),
+            Offset(34.987196950294496, 35.814611672817314),
+            Offset(32.36160940398365, 37.21451935517152),
+            Offset(29.58695643015737, 38.22206061545487),
+            Offset(26.731480843000227, 38.77127181994182),
+            Offset(23.87226471334039, 38.83426985959836),
+            Offset(21.07829747200463, 38.407724460042374),
+            Offset(18.4254590874845, 37.50246571445624),
+            Offset(15.991253271615628, 36.144283430810475),
+            Offset(13.899590589094196, 34.421548951536714),
+            Offset(12.180082942567237, 32.39984551713645),
+            Offset(10.893711063267709, 30.203839660295976),
+            Offset(10.05175374009658, 27.963440739668336),
+            Offset(9.617174683906235, 25.84303341003485),
+            Offset(9.499746409899146, 24.07128847410292),
+            Offset(9.52454576701906, 23.149317349038636),
+            Offset(9.524593499999998, 23.148505500000006),
+          ],
+          <Offset>[
+            Offset(31.490785932549, 25.064207505253805),
+            Offset(31.46464534214123, 25.209405135470142),
+            Offset(31.272073801790775, 25.92997874990684),
+            Offset(30.84942602866159, 26.87024355084762),
+            Offset(30.139022241342694, 27.80417160603073),
+            Offset(29.207054177976676, 28.615766000126037),
+            Offset(28.139025921748853, 29.26767369048629),
+            Offset(27.004303223883873, 29.75640833331179),
+            Offset(25.82142815677357, 30.077644269503992),
+            Offset(24.620913734346928, 30.206439402838008),
+            Offset(23.435617472430565, 30.133913897535194),
+            Offset(22.294201558759077, 29.861068654914245),
+            Offset(21.227691149495094, 29.395001643481358),
+            Offset(20.267267442364727, 28.749057049701424),
+            Offset(19.46115648681357, 27.964075103708094),
+            Offset(18.819349992885186, 27.06821479821395),
+            Offset(18.362782520652296, 26.115165068511015),
+            Offset(18.091641522120366, 25.1590704350385),
+            Offset(17.984933199891035, 24.26667502083877),
+            Offset(17.997436842920234, 23.529489934452904),
+            Offset(18.039490753627245, 23.14883979156837),
+            Offset(18.0395385, 23.148505500000006),
+          ],
+          <Offset>[
+            Offset(31.490785932549, 25.064207505253805),
+            Offset(31.46464534214123, 25.209405135470142),
+            Offset(31.272073801790775, 25.92997874990684),
+            Offset(30.84942602866159, 26.87024355084762),
+            Offset(30.139022241342694, 27.80417160603073),
+            Offset(29.207054177976676, 28.615766000126037),
+            Offset(28.139025921748853, 29.26767369048629),
+            Offset(27.004303223883873, 29.75640833331179),
+            Offset(25.82142815677357, 30.077644269503992),
+            Offset(24.620913734346928, 30.206439402838008),
+            Offset(23.435617472430565, 30.133913897535194),
+            Offset(22.294201558759077, 29.861068654914245),
+            Offset(21.227691149495094, 29.395001643481358),
+            Offset(20.267267442364727, 28.749057049701424),
+            Offset(19.46115648681357, 27.964075103708094),
+            Offset(18.819349992885186, 27.06821479821395),
+            Offset(18.362782520652296, 26.115165068511015),
+            Offset(18.091641522120366, 25.1590704350385),
+            Offset(17.984933199891035, 24.26667502083877),
+            Offset(17.997436842920234, 23.529489934452904),
+            Offset(18.039490753627245, 23.14883979156837),
+            Offset(18.0395385, 23.148505500000006),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(31.490785932549, 25.064207505253805),
+            Offset(31.46464534214123, 25.209405135470142),
+            Offset(31.272073801790775, 25.92997874990684),
+            Offset(30.84942602866159, 26.87024355084762),
+            Offset(30.139022241342694, 27.80417160603073),
+            Offset(29.207054177976676, 28.615766000126037),
+            Offset(28.139025921748853, 29.26767369048629),
+            Offset(27.004303223883873, 29.75640833331179),
+            Offset(25.82142815677357, 30.077644269503992),
+            Offset(24.620913734346928, 30.206439402838008),
+            Offset(23.435617472430565, 30.133913897535194),
+            Offset(22.294201558759077, 29.861068654914245),
+            Offset(21.227691149495094, 29.395001643481358),
+            Offset(20.267267442364727, 28.749057049701424),
+            Offset(19.46115648681357, 27.964075103708094),
+            Offset(18.819349992885186, 27.06821479821395),
+            Offset(18.362782520652296, 26.115165068511015),
+            Offset(18.091641522120366, 25.1590704350385),
+            Offset(17.984933199891035, 24.26667502083877),
+            Offset(17.997436842920234, 23.529489934452904),
+            Offset(18.039490753627245, 23.14883979156837),
+            Offset(18.0395385, 23.148505500000006),
+          ],
+          <Offset>[
+            Offset(31.500705947534364, 37.904137673197035),
+            Offset(31.224359596665288, 38.04026654278025),
+            Offset(29.77500721437707, 38.610257636892996),
+            Offset(27.671279110116373, 39.06613773120253),
+            Offset(25.221379281133324, 39.03073015693383),
+            Offset(22.702260300479338, 38.4714008591571),
+            Offset(20.28270034295162, 37.48547892474107),
+            Offset(18.054569997652198, 36.18517574943152),
+            Offset(16.048128541632522, 34.59627819756455),
+            Offset(14.343114833822348, 32.739119933221964),
+            Offset(12.99519031795477, 30.657890586626984),
+            Offset(12.038214592605321, 28.40198375080891),
+            Offset(11.498734264325233, 26.032323169068647),
+            Offset(11.392995785033866, 23.617840044802506),
+            Offset(11.712187869419232, 21.29019602644484),
+            Offset(12.42139313017819, 19.101094337832414),
+            Offset(13.45637301051034, 17.152279319649516),
+            Offset(14.726397156564566, 15.511205096609956),
+            Offset(16.093303132855745, 14.22536480165701),
+            Offset(17.347278595340214, 13.332261414827599),
+            Offset(18.038917684662923, 12.930905807638549),
+            Offset(18.039538499999995, 12.930571500000006),
+          ],
+          <Offset>[
+            Offset(31.500705947534364, 37.904137673197035),
+            Offset(31.224359596665288, 38.04026654278025),
+            Offset(29.77500721437707, 38.610257636892996),
+            Offset(27.671279110116373, 39.06613773120253),
+            Offset(25.221379281133324, 39.03073015693383),
+            Offset(22.702260300479338, 38.4714008591571),
+            Offset(20.28270034295162, 37.48547892474107),
+            Offset(18.054569997652198, 36.18517574943152),
+            Offset(16.048128541632522, 34.59627819756455),
+            Offset(14.343114833822348, 32.739119933221964),
+            Offset(12.99519031795477, 30.657890586626984),
+            Offset(12.038214592605321, 28.40198375080891),
+            Offset(11.498734264325233, 26.032323169068647),
+            Offset(11.392995785033866, 23.617840044802506),
+            Offset(11.712187869419232, 21.29019602644484),
+            Offset(12.42139313017819, 19.101094337832414),
+            Offset(13.45637301051034, 17.152279319649516),
+            Offset(14.726397156564566, 15.511205096609956),
+            Offset(16.093303132855745, 14.22536480165701),
+            Offset(17.347278595340214, 13.332261414827599),
+            Offset(18.038917684662923, 12.930905807638549),
+            Offset(18.039538499999995, 12.930571500000006),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.390243902439,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(5.809272260831642, 22.944059173900662),
+            Offset(5.842970151767011, 22.590356743299907),
+            Offset(6.161027125720756, 20.822465760581736),
+            Offset(6.987328821042649, 18.48130068369803),
+            Offset(8.505512299571395, 16.097792593794804),
+            Offset(10.579916772830773, 13.963572435292853),
+            Offset(13.012803049705509, 12.18538832718269),
+            Offset(15.638390596016343, 10.785480644828485),
+            Offset(18.413043569842625, 9.77793938454513),
+            Offset(21.26851915699977, 9.228728180058184),
+            Offset(24.12773528665962, 9.165730140401642),
+            Offset(26.92170252799538, 9.592275539957622),
+            Offset(29.5745409125155, 10.497534285543754),
+            Offset(32.008746728384374, 11.855716569189521),
+            Offset(34.100409410905804, 13.578451048463291),
+            Offset(35.819917057432754, 15.600154482863552),
+            Offset(37.10628893673229, 17.796160339704027),
+            Offset(37.94824625990342, 20.03655926033166),
+            Offset(38.38282531609377, 22.156966589965144),
+            Offset(38.50025359010085, 23.928711525897086),
+            Offset(38.47545423298094, 24.850682650961364),
+            Offset(38.4754065, 24.8514945),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(5.809272260831642, 22.944059173900662),
+            Offset(5.842970151767011, 22.590356743299907),
+            Offset(6.161027125720756, 20.822465760581736),
+            Offset(6.987328821042649, 18.48130068369803),
+            Offset(8.505512299571395, 16.097792593794804),
+            Offset(10.579916772830773, 13.963572435292853),
+            Offset(13.012803049705509, 12.18538832718269),
+            Offset(15.638390596016343, 10.785480644828485),
+            Offset(18.413043569842625, 9.77793938454513),
+            Offset(21.26851915699977, 9.228728180058184),
+            Offset(24.12773528665962, 9.165730140401642),
+            Offset(26.92170252799538, 9.592275539957622),
+            Offset(29.5745409125155, 10.497534285543754),
+            Offset(32.008746728384374, 11.855716569189521),
+            Offset(34.100409410905804, 13.578451048463291),
+            Offset(35.819917057432754, 15.600154482863552),
+            Offset(37.10628893673229, 17.796160339704027),
+            Offset(37.94824625990342, 20.03655926033166),
+            Offset(38.38282531609377, 22.156966589965144),
+            Offset(38.50025359010085, 23.928711525897086),
+            Offset(38.47545423298094, 24.850682650961364),
+            Offset(38.4754065, 24.8514945),
+          ],
+          <Offset>[
+            Offset(16.509214067451, 22.93579249474619),
+            Offset(16.53535465785877, 22.790594864529858),
+            Offset(16.727926198209218, 22.07002125009316),
+            Offset(17.150573971338403, 21.129756449152378),
+            Offset(17.86097775865731, 20.19582839396928),
+            Offset(18.792945822023324, 19.38423399987397),
+            Offset(19.860974078251154, 18.732326309513716),
+            Offset(20.995696776116127, 18.24359166668821),
+            Offset(22.178571843226425, 17.922355730496008),
+            Offset(23.379086265653072, 17.793560597162),
+            Offset(24.564382527569446, 17.866086102464806),
+            Offset(25.70579844124093, 18.13893134508575),
+            Offset(26.772308850504906, 18.60499835651864),
+            Offset(27.732732557635273, 19.250942950298572),
+            Offset(28.53884351318643, 20.03592489629191),
+            Offset(29.180650007114806, 20.93178520178605),
+            Offset(29.637217479347704, 21.88483493148899),
+            Offset(29.908358477879634, 22.840929564961495),
+            Offset(30.015066800108972, 23.733324979161225),
+            Offset(30.00256315707976, 24.470510065547103),
+            Offset(29.960509246372755, 24.85116020843163),
+            Offset(29.9604615, 24.8514945),
+          ],
+          <Offset>[
+            Offset(16.509214067451, 22.93579249474619),
+            Offset(16.53535465785877, 22.790594864529858),
+            Offset(16.727926198209218, 22.07002125009316),
+            Offset(17.150573971338403, 21.129756449152378),
+            Offset(17.86097775865731, 20.19582839396928),
+            Offset(18.792945822023324, 19.38423399987397),
+            Offset(19.860974078251154, 18.732326309513716),
+            Offset(20.995696776116127, 18.24359166668821),
+            Offset(22.178571843226425, 17.922355730496008),
+            Offset(23.379086265653072, 17.793560597162),
+            Offset(24.564382527569446, 17.866086102464806),
+            Offset(25.70579844124093, 18.13893134508575),
+            Offset(26.772308850504906, 18.60499835651864),
+            Offset(27.732732557635273, 19.250942950298572),
+            Offset(28.53884351318643, 20.03592489629191),
+            Offset(29.180650007114806, 20.93178520178605),
+            Offset(29.637217479347704, 21.88483493148899),
+            Offset(29.908358477879634, 22.840929564961495),
+            Offset(30.015066800108972, 23.733324979161225),
+            Offset(30.00256315707976, 24.470510065547103),
+            Offset(29.960509246372755, 24.85116020843163),
+            Offset(29.9604615, 24.8514945),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(16.509214067451, 22.93579249474619),
+            Offset(16.53535465785877, 22.790594864529858),
+            Offset(16.727926198209218, 22.07002125009316),
+            Offset(17.150573971338403, 21.129756449152378),
+            Offset(17.86097775865731, 20.19582839396928),
+            Offset(18.792945822023324, 19.38423399987397),
+            Offset(19.860974078251154, 18.732326309513716),
+            Offset(20.995696776116127, 18.24359166668821),
+            Offset(22.178571843226425, 17.922355730496008),
+            Offset(23.379086265653072, 17.793560597162),
+            Offset(24.564382527569446, 17.866086102464806),
+            Offset(25.70579844124093, 18.13893134508575),
+            Offset(26.772308850504906, 18.60499835651864),
+            Offset(27.732732557635273, 19.250942950298572),
+            Offset(28.53884351318643, 20.03592489629191),
+            Offset(29.180650007114806, 20.93178520178605),
+            Offset(29.637217479347704, 21.88483493148899),
+            Offset(29.908358477879634, 22.840929564961495),
+            Offset(30.015066800108972, 23.733324979161225),
+            Offset(30.00256315707976, 24.470510065547103),
+            Offset(29.960509246372755, 24.85116020843163),
+            Offset(29.9604615, 24.8514945),
+          ],
+          <Offset>[
+            Offset(16.49929405246564, 10.095862326802958),
+            Offset(16.775640403334712, 9.95973345721975),
+            Offset(18.224992785622923, 9.389742363107004),
+            Offset(20.328720889883623, 8.93386226879747),
+            Offset(22.778620718866684, 8.969269843066183),
+            Offset(25.297739699520662, 9.528599140842907),
+            Offset(27.717299657048386, 10.514521075258944),
+            Offset(29.9454300023478, 11.814824250568474),
+            Offset(31.951871458367474, 13.403721802435447),
+            Offset(33.656885166177645, 15.26088006677804),
+            Offset(35.00480968204524, 17.342109413373016),
+            Offset(35.961785407394686, 19.59801624919109),
+            Offset(36.501265735674764, 21.96767683093135),
+            Offset(36.60700421496613, 24.38215995519749),
+            Offset(36.28781213058077, 26.70980397355516),
+            Offset(35.578606869821805, 28.898905662167586),
+            Offset(34.54362698948966, 30.847720680350488),
+            Offset(33.273602843435434, 32.48879490339004),
+            Offset(31.906696867144262, 33.774635198342985),
+            Offset(30.65272140465978, 34.66773858517241),
+            Offset(29.961082315337077, 35.069094192361455),
+            Offset(29.9604615, 35.0694285),
+          ],
+          <Offset>[
+            Offset(16.49929405246564, 10.095862326802958),
+            Offset(16.775640403334712, 9.95973345721975),
+            Offset(18.224992785622923, 9.389742363107004),
+            Offset(20.328720889883623, 8.93386226879747),
+            Offset(22.778620718866684, 8.969269843066183),
+            Offset(25.297739699520662, 9.528599140842907),
+            Offset(27.717299657048386, 10.514521075258944),
+            Offset(29.9454300023478, 11.814824250568474),
+            Offset(31.951871458367474, 13.403721802435447),
+            Offset(33.656885166177645, 15.26088006677804),
+            Offset(35.00480968204524, 17.342109413373016),
+            Offset(35.961785407394686, 19.59801624919109),
+            Offset(36.501265735674764, 21.96767683093135),
+            Offset(36.60700421496613, 24.38215995519749),
+            Offset(36.28781213058077, 26.70980397355516),
+            Offset(35.578606869821805, 28.898905662167586),
+            Offset(34.54362698948966, 30.847720680350488),
+            Offset(33.273602843435434, 32.48879490339004),
+            Offset(31.906696867144262, 33.774635198342985),
+            Offset(30.65272140465978, 34.66773858517241),
+            Offset(29.961082315337077, 35.069094192361455),
+            Offset(29.9604615, 35.0694285),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(16.49929405246564, 10.095862326802958),
+            Offset(16.775640403334712, 9.95973345721975),
+            Offset(18.224992785622923, 9.389742363107004),
+            Offset(20.328720889883623, 8.93386226879747),
+            Offset(22.778620718866684, 8.969269843066183),
+            Offset(25.297739699520662, 9.528599140842907),
+            Offset(27.717299657048386, 10.514521075258944),
+            Offset(29.9454300023478, 11.814824250568474),
+            Offset(31.951871458367474, 13.403721802435447),
+            Offset(33.656885166177645, 15.26088006677804),
+            Offset(35.00480968204524, 17.342109413373016),
+            Offset(35.961785407394686, 19.59801624919109),
+            Offset(36.501265735674764, 21.96767683093135),
+            Offset(36.60700421496613, 24.38215995519749),
+            Offset(36.28781213058077, 26.70980397355516),
+            Offset(35.578606869821805, 28.898905662167586),
+            Offset(34.54362698948966, 30.847720680350488),
+            Offset(33.273602843435434, 32.48879490339004),
+            Offset(31.906696867144262, 33.774635198342985),
+            Offset(30.65272140465978, 34.66773858517241),
+            Offset(29.961082315337077, 35.069094192361455),
+            Offset(29.9604615, 35.0694285),
+          ],
+          <Offset>[
+            Offset(5.799352245846278, 10.104129005957429),
+            Offset(6.083255897242955, 9.759495335989797),
+            Offset(7.658093713134462, 8.14218687359558),
+            Offset(10.165475739587867, 6.285406503343122),
+            Offset(13.423155259780767, 4.871234042891707),
+            Offset(17.08471065032811, 4.107937576261792),
+            Offset(20.869128628502743, 3.9675830929279154),
+            Offset(24.588123822248015, 4.356713228708747),
+            Offset(28.186343184983674, 5.2593054564845705),
+            Offset(31.546318057524346, 6.696047649674224),
+            Offset(34.56816244113542, 8.641753451309853),
+            Offset(37.177689494149135, 11.051360444062961),
+            Offset(39.30349779768536, 13.860212759956465),
+            Offset(40.88301838571523, 16.98693357408844),
+            Offset(41.849378028300144, 20.252330125726544),
+            Offset(42.21787392013975, 23.56727494324509),
+            Offset(42.012698446874246, 26.759046088565526),
+            Offset(41.313490625459224, 29.684424598760206),
+            Offset(40.274455383129066, 32.198276809146904),
+            Offset(39.15041183768086, 34.12594004552239),
+            Offset(38.476027301945265, 35.068616634891185),
+            Offset(38.4754065, 35.0694285),
+          ],
+          <Offset>[
+            Offset(5.799352245846278, 10.104129005957429),
+            Offset(6.083255897242955, 9.759495335989797),
+            Offset(7.658093713134462, 8.14218687359558),
+            Offset(10.165475739587867, 6.285406503343122),
+            Offset(13.423155259780767, 4.871234042891707),
+            Offset(17.08471065032811, 4.107937576261792),
+            Offset(20.869128628502743, 3.9675830929279154),
+            Offset(24.588123822248015, 4.356713228708747),
+            Offset(28.186343184983674, 5.2593054564845705),
+            Offset(31.546318057524346, 6.696047649674224),
+            Offset(34.56816244113542, 8.641753451309853),
+            Offset(37.177689494149135, 11.051360444062961),
+            Offset(39.30349779768536, 13.860212759956465),
+            Offset(40.88301838571523, 16.98693357408844),
+            Offset(41.849378028300144, 20.252330125726544),
+            Offset(42.21787392013975, 23.56727494324509),
+            Offset(42.012698446874246, 26.759046088565526),
+            Offset(41.313490625459224, 29.684424598760206),
+            Offset(40.274455383129066, 32.198276809146904),
+            Offset(39.15041183768086, 34.12594004552239),
+            Offset(38.476027301945265, 35.068616634891185),
+            Offset(38.4754065, 35.0694285),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(5.799352245846278, 10.104129005957429),
+            Offset(6.083255897242955, 9.759495335989797),
+            Offset(7.658093713134462, 8.14218687359558),
+            Offset(10.165475739587867, 6.285406503343122),
+            Offset(13.423155259780767, 4.871234042891707),
+            Offset(17.08471065032811, 4.107937576261792),
+            Offset(20.869128628502743, 3.9675830929279154),
+            Offset(24.588123822248015, 4.356713228708747),
+            Offset(28.186343184983674, 5.2593054564845705),
+            Offset(31.546318057524346, 6.696047649674224),
+            Offset(34.56816244113542, 8.641753451309853),
+            Offset(37.177689494149135, 11.051360444062961),
+            Offset(39.30349779768536, 13.860212759956465),
+            Offset(40.88301838571523, 16.98693357408844),
+            Offset(41.849378028300144, 20.252330125726544),
+            Offset(42.21787392013975, 23.56727494324509),
+            Offset(42.012698446874246, 26.759046088565526),
+            Offset(41.313490625459224, 29.684424598760206),
+            Offset(40.274455383129066, 32.198276809146904),
+            Offset(39.15041183768086, 34.12594004552239),
+            Offset(38.476027301945265, 35.068616634891185),
+            Offset(38.4754065, 35.0694285),
+          ],
+          <Offset>[
+            Offset(5.809272260831642, 22.944059173900662),
+            Offset(5.842970151767011, 22.590356743299907),
+            Offset(6.161027125720756, 20.822465760581736),
+            Offset(6.987328821042649, 18.48130068369803),
+            Offset(8.505512299571395, 16.097792593794804),
+            Offset(10.579916772830773, 13.963572435292853),
+            Offset(13.012803049705509, 12.18538832718269),
+            Offset(15.638390596016343, 10.785480644828485),
+            Offset(18.413043569842625, 9.77793938454513),
+            Offset(21.26851915699977, 9.228728180058184),
+            Offset(24.12773528665962, 9.165730140401642),
+            Offset(26.92170252799538, 9.592275539957622),
+            Offset(29.5745409125155, 10.497534285543754),
+            Offset(32.008746728384374, 11.855716569189521),
+            Offset(34.100409410905804, 13.578451048463291),
+            Offset(35.819917057432754, 15.600154482863552),
+            Offset(37.10628893673229, 17.796160339704027),
+            Offset(37.94824625990342, 20.03655926033166),
+            Offset(38.38282531609377, 22.156966589965144),
+            Offset(38.50025359010085, 23.928711525897086),
+            Offset(38.47545423298094, 24.850682650961364),
+            Offset(38.4754065, 24.8514945),
+          ],
+          <Offset>[
+            Offset(5.809272260831642, 22.944059173900662),
+            Offset(5.842970151767011, 22.590356743299907),
+            Offset(6.161027125720756, 20.822465760581736),
+            Offset(6.987328821042649, 18.48130068369803),
+            Offset(8.505512299571395, 16.097792593794804),
+            Offset(10.579916772830773, 13.963572435292853),
+            Offset(13.012803049705509, 12.18538832718269),
+            Offset(15.638390596016343, 10.785480644828485),
+            Offset(18.413043569842625, 9.77793938454513),
+            Offset(21.26851915699977, 9.228728180058184),
+            Offset(24.12773528665962, 9.165730140401642),
+            Offset(26.92170252799538, 9.592275539957622),
+            Offset(29.5745409125155, 10.497534285543754),
+            Offset(32.008746728384374, 11.855716569189521),
+            Offset(34.100409410905804, 13.578451048463291),
+            Offset(35.819917057432754, 15.600154482863552),
+            Offset(37.10628893673229, 17.796160339704027),
+            Offset(37.94824625990342, 20.03655926033166),
+            Offset(38.38282531609377, 22.156966589965144),
+            Offset(38.50025359010085, 23.928711525897086),
+            Offset(38.47545423298094, 24.850682650961364),
+            Offset(38.4754065, 24.8514945),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.390243902439,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(18.649202428774874, 22.9341391589153),
+            Offset(18.67383155907712, 22.83064248877585),
+            Offset(18.841306012706912, 22.31953234799544),
+            Offset(19.183223001397558, 21.65944760224325),
+            Offset(19.732070850474493, 21.015435554004174),
+            Offset(20.435551631861834, 20.468366312790188),
+            Offset(21.23060828396028, 20.041713905979922),
+            Offset(22.06715801213608, 19.735213871060157),
+            Offset(22.931677497903188, 19.55123899968618),
+            Offset(23.801199687383725, 19.506527080582764),
+            Offset(24.651711975751407, 19.606157294877438),
+            Offset(25.46261762389004, 19.84826250611138),
+            Offset(26.211862438102788, 20.22649117071362),
+            Offset(26.877529723485452, 20.729988226520383),
+            Offset(27.426530333642553, 21.327419665857633),
+            Offset(27.852796597051224, 21.998111345570546),
+            Offset(28.14340318787079, 22.70256984984598),
+            Offset(28.300380921474876, 23.40180362588746),
+            Offset(28.341515096912012, 24.048596657000438),
+            Offset(28.30302507047554, 24.578869773477106),
+            Offset(28.25752024905112, 24.851255719925685),
+            Offset(28.2574725, 24.8514945),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(18.649202428774874, 22.9341391589153),
+            Offset(18.67383155907712, 22.83064248877585),
+            Offset(18.841306012706912, 22.31953234799544),
+            Offset(19.183223001397558, 21.65944760224325),
+            Offset(19.732070850474493, 21.015435554004174),
+            Offset(20.435551631861834, 20.468366312790188),
+            Offset(21.23060828396028, 20.041713905979922),
+            Offset(22.06715801213608, 19.735213871060157),
+            Offset(22.931677497903188, 19.55123899968618),
+            Offset(23.801199687383725, 19.506527080582764),
+            Offset(24.651711975751407, 19.606157294877438),
+            Offset(25.46261762389004, 19.84826250611138),
+            Offset(26.211862438102788, 20.22649117071362),
+            Offset(26.877529723485452, 20.729988226520383),
+            Offset(27.426530333642553, 21.327419665857633),
+            Offset(27.852796597051224, 21.998111345570546),
+            Offset(28.14340318787079, 22.70256984984598),
+            Offset(28.300380921474876, 23.40180362588746),
+            Offset(28.341515096912012, 24.048596657000438),
+            Offset(28.30302507047554, 24.578869773477106),
+            Offset(28.25752024905112, 24.851255719925685),
+            Offset(28.2574725, 24.8514945),
+          ],
+          <Offset>[
+            Offset(29.349144235394235, 22.92587247976083),
+            Offset(29.366216065168874, 23.030880610005802),
+            Offset(29.408205085195373, 23.567087837506865),
+            Offset(29.346468151693315, 24.307903367697598),
+            Offset(29.087536309560406, 25.11347135417865),
+            Offset(28.648580681054387, 25.889027877371305),
+            Offset(28.078779312505926, 26.58865188831095),
+            Offset(27.42446419223586, 27.193324892919883),
+            Offset(26.69720577128699, 27.695655345637057),
+            Offset(25.91176679603703, 28.07135949768658),
+            Offset(25.08835921666123, 28.3065132569406),
+            Offset(24.24671353713559, 28.394918311239508),
+            Offset(23.409630376092196, 28.333955241688503),
+            Offset(22.601515552736352, 28.125214607629434),
+            Offset(21.864964435923177, 27.78489351368625),
+            Offset(21.213529546733277, 27.329742064493043),
+            Offset(20.6743317304862, 26.79124444163094),
+            Offset(20.26049313945109, 26.206173930517295),
+            Offset(19.973756580927212, 25.62495504619652),
+            Offset(19.805334637454454, 25.120668313127123),
+            Offset(19.742575262442934, 24.85173327739595),
+            Offset(19.7425275, 24.8514945),
+          ],
+          <Offset>[
+            Offset(29.349144235394235, 22.92587247976083),
+            Offset(29.366216065168874, 23.030880610005802),
+            Offset(29.408205085195373, 23.567087837506865),
+            Offset(29.346468151693315, 24.307903367697598),
+            Offset(29.087536309560406, 25.11347135417865),
+            Offset(28.648580681054387, 25.889027877371305),
+            Offset(28.078779312505926, 26.58865188831095),
+            Offset(27.42446419223586, 27.193324892919883),
+            Offset(26.69720577128699, 27.695655345637057),
+            Offset(25.91176679603703, 28.07135949768658),
+            Offset(25.08835921666123, 28.3065132569406),
+            Offset(24.24671353713559, 28.394918311239508),
+            Offset(23.409630376092196, 28.333955241688503),
+            Offset(22.601515552736352, 28.125214607629434),
+            Offset(21.864964435923177, 27.78489351368625),
+            Offset(21.213529546733277, 27.329742064493043),
+            Offset(20.6743317304862, 26.79124444163094),
+            Offset(20.26049313945109, 26.206173930517295),
+            Offset(19.973756580927212, 25.62495504619652),
+            Offset(19.805334637454454, 25.120668313127123),
+            Offset(19.742575262442934, 24.85173327739595),
+            Offset(19.7425275, 24.8514945),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(29.349144235394235, 22.92587247976083),
+            Offset(29.366216065168874, 23.030880610005802),
+            Offset(29.408205085195373, 23.567087837506865),
+            Offset(29.346468151693315, 24.307903367697598),
+            Offset(29.087536309560406, 25.11347135417865),
+            Offset(28.648580681054387, 25.889027877371305),
+            Offset(28.078779312505926, 26.58865188831095),
+            Offset(27.42446419223586, 27.193324892919883),
+            Offset(26.69720577128699, 27.695655345637057),
+            Offset(25.91176679603703, 28.07135949768658),
+            Offset(25.08835921666123, 28.3065132569406),
+            Offset(24.24671353713559, 28.394918311239508),
+            Offset(23.409630376092196, 28.333955241688503),
+            Offset(22.601515552736352, 28.125214607629434),
+            Offset(21.864964435923177, 27.78489351368625),
+            Offset(21.213529546733277, 27.329742064493043),
+            Offset(20.6743317304862, 26.79124444163094),
+            Offset(20.26049313945109, 26.206173930517295),
+            Offset(19.973756580927212, 25.62495504619652),
+            Offset(19.805334637454454, 25.120668313127123),
+            Offset(19.742575262442934, 24.85173327739595),
+            Offset(19.7425275, 24.8514945),
+          ],
+          <Offset>[
+            Offset(29.33922422040887, 10.085942311817597),
+            Offset(29.60650181064482, 10.200019202695694),
+            Offset(30.90527167260908, 10.88680895052071),
+            Offset(32.52461507023853, 12.11200918734269),
+            Offset(34.005179269769776, 13.886912803275553),
+            Offset(35.153374558551725, 16.03339301834024),
+            Offset(35.93510489130316, 18.370846654056177),
+            Offset(36.37419741846753, 20.764557476800146),
+            Offset(36.47050538642804, 23.177021417576494),
+            Offset(36.1895656965616, 25.538678967302616),
+            Offset(35.52878637113702, 27.782536567848812),
+            Offset(34.50270050328935, 29.854003215344843),
+            Offset(33.13858726126206, 31.696633716101214),
+            Offset(31.475787210067217, 33.25643161252835),
+            Offset(29.613933053317517, 34.4587725909495),
+            Offset(27.611486409440268, 35.29686252487458),
+            Offset(25.580741240628157, 35.75413019049244),
+            Offset(23.62573750500689, 35.85403926894584),
+            Offset(21.865386647962502, 35.66626526537828),
+            Offset(20.455492885034474, 35.317896832752425),
+            Offset(19.743148331407255, 35.069667261325776),
+            Offset(19.7425275, 35.0694285),
+          ],
+          <Offset>[
+            Offset(29.33922422040887, 10.085942311817597),
+            Offset(29.60650181064482, 10.200019202695694),
+            Offset(30.90527167260908, 10.88680895052071),
+            Offset(32.52461507023853, 12.11200918734269),
+            Offset(34.005179269769776, 13.886912803275553),
+            Offset(35.153374558551725, 16.03339301834024),
+            Offset(35.93510489130316, 18.370846654056177),
+            Offset(36.37419741846753, 20.764557476800146),
+            Offset(36.47050538642804, 23.177021417576494),
+            Offset(36.1895656965616, 25.538678967302616),
+            Offset(35.52878637113702, 27.782536567848812),
+            Offset(34.50270050328935, 29.854003215344843),
+            Offset(33.13858726126206, 31.696633716101214),
+            Offset(31.475787210067217, 33.25643161252835),
+            Offset(29.613933053317517, 34.4587725909495),
+            Offset(27.611486409440268, 35.29686252487458),
+            Offset(25.580741240628157, 35.75413019049244),
+            Offset(23.62573750500689, 35.85403926894584),
+            Offset(21.865386647962502, 35.66626526537828),
+            Offset(20.455492885034474, 35.317896832752425),
+            Offset(19.743148331407255, 35.069667261325776),
+            Offset(19.7425275, 35.0694285),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(29.33922422040887, 10.085942311817597),
+            Offset(29.60650181064482, 10.200019202695694),
+            Offset(30.90527167260908, 10.88680895052071),
+            Offset(32.52461507023853, 12.11200918734269),
+            Offset(34.005179269769776, 13.886912803275553),
+            Offset(35.153374558551725, 16.03339301834024),
+            Offset(35.93510489130316, 18.370846654056177),
+            Offset(36.37419741846753, 20.764557476800146),
+            Offset(36.47050538642804, 23.177021417576494),
+            Offset(36.1895656965616, 25.538678967302616),
+            Offset(35.52878637113702, 27.782536567848812),
+            Offset(34.50270050328935, 29.854003215344843),
+            Offset(33.13858726126206, 31.696633716101214),
+            Offset(31.475787210067217, 33.25643161252835),
+            Offset(29.613933053317517, 34.4587725909495),
+            Offset(27.611486409440268, 35.29686252487458),
+            Offset(25.580741240628157, 35.75413019049244),
+            Offset(23.62573750500689, 35.85403926894584),
+            Offset(21.865386647962502, 35.66626526537828),
+            Offset(20.455492885034474, 35.317896832752425),
+            Offset(19.743148331407255, 35.069667261325776),
+            Offset(19.7425275, 35.0694285),
+          ],
+          <Offset>[
+            Offset(18.63928241378951, 10.094208990972067),
+            Offset(18.914117304553063, 9.999781081465741),
+            Offset(20.338372600120618, 9.639253461009286),
+            Offset(22.361369919942774, 9.463553421888342),
+            Offset(24.649713810683863, 9.788877003101078),
+            Offset(26.940345509359172, 10.612731453759126),
+            Offset(29.086933862757515, 11.823908671725148),
+            Offset(31.016891238367755, 13.306446454940419),
+            Offset(32.70497711304424, 15.03260507162562),
+            Offset(34.0789985879083, 16.9738465501988),
+            Offset(35.0921391302272, 19.08218060578565),
+            Offset(35.718604590043796, 21.307347410216714),
+            Offset(35.94081932327265, 23.58916964512633),
+            Offset(35.75180138081632, 25.8612052314193),
+            Offset(35.17549895103689, 28.001298743120884),
+            Offset(34.250753459758215, 29.965231805952083),
+            Offset(33.049812698012744, 31.665455598707478),
+            Offset(31.665625287030675, 33.049668964316005),
+            Offset(30.233145163947302, 34.0899068761822),
+            Offset(28.95318331805556, 34.776098293102415),
+            Offset(28.25809331801544, 35.06918970385551),
+            Offset(28.2574725, 35.0694285),
+          ],
+          <Offset>[
+            Offset(18.63928241378951, 10.094208990972067),
+            Offset(18.914117304553063, 9.999781081465741),
+            Offset(20.338372600120618, 9.639253461009286),
+            Offset(22.361369919942774, 9.463553421888342),
+            Offset(24.649713810683863, 9.788877003101078),
+            Offset(26.940345509359172, 10.612731453759126),
+            Offset(29.086933862757515, 11.823908671725148),
+            Offset(31.016891238367755, 13.306446454940419),
+            Offset(32.70497711304424, 15.03260507162562),
+            Offset(34.0789985879083, 16.9738465501988),
+            Offset(35.0921391302272, 19.08218060578565),
+            Offset(35.718604590043796, 21.307347410216714),
+            Offset(35.94081932327265, 23.58916964512633),
+            Offset(35.75180138081632, 25.8612052314193),
+            Offset(35.17549895103689, 28.001298743120884),
+            Offset(34.250753459758215, 29.965231805952083),
+            Offset(33.049812698012744, 31.665455598707478),
+            Offset(31.665625287030675, 33.049668964316005),
+            Offset(30.233145163947302, 34.0899068761822),
+            Offset(28.95318331805556, 34.776098293102415),
+            Offset(28.25809331801544, 35.06918970385551),
+            Offset(28.2574725, 35.0694285),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(18.63928241378951, 10.094208990972067),
+            Offset(18.914117304553063, 9.999781081465741),
+            Offset(20.338372600120618, 9.639253461009286),
+            Offset(22.361369919942774, 9.463553421888342),
+            Offset(24.649713810683863, 9.788877003101078),
+            Offset(26.940345509359172, 10.612731453759126),
+            Offset(29.086933862757515, 11.823908671725148),
+            Offset(31.016891238367755, 13.306446454940419),
+            Offset(32.70497711304424, 15.03260507162562),
+            Offset(34.0789985879083, 16.9738465501988),
+            Offset(35.0921391302272, 19.08218060578565),
+            Offset(35.718604590043796, 21.307347410216714),
+            Offset(35.94081932327265, 23.58916964512633),
+            Offset(35.75180138081632, 25.8612052314193),
+            Offset(35.17549895103689, 28.001298743120884),
+            Offset(34.250753459758215, 29.965231805952083),
+            Offset(33.049812698012744, 31.665455598707478),
+            Offset(31.665625287030675, 33.049668964316005),
+            Offset(30.233145163947302, 34.0899068761822),
+            Offset(28.95318331805556, 34.776098293102415),
+            Offset(28.25809331801544, 35.06918970385551),
+            Offset(28.2574725, 35.0694285),
+          ],
+          <Offset>[
+            Offset(18.649202428774874, 22.9341391589153),
+            Offset(18.67383155907712, 22.83064248877585),
+            Offset(18.841306012706912, 22.31953234799544),
+            Offset(19.183223001397558, 21.65944760224325),
+            Offset(19.732070850474493, 21.015435554004174),
+            Offset(20.435551631861834, 20.468366312790188),
+            Offset(21.23060828396028, 20.041713905979922),
+            Offset(22.06715801213608, 19.735213871060157),
+            Offset(22.931677497903188, 19.55123899968618),
+            Offset(23.801199687383725, 19.506527080582764),
+            Offset(24.651711975751407, 19.606157294877438),
+            Offset(25.46261762389004, 19.84826250611138),
+            Offset(26.211862438102788, 20.22649117071362),
+            Offset(26.877529723485452, 20.729988226520383),
+            Offset(27.426530333642553, 21.327419665857633),
+            Offset(27.852796597051224, 21.998111345570546),
+            Offset(28.14340318787079, 22.70256984984598),
+            Offset(28.300380921474876, 23.40180362588746),
+            Offset(28.341515096912012, 24.048596657000438),
+            Offset(28.30302507047554, 24.578869773477106),
+            Offset(28.25752024905112, 24.851255719925685),
+            Offset(28.2574725, 24.8514945),
+          ],
+          <Offset>[
+            Offset(18.649202428774874, 22.9341391589153),
+            Offset(18.67383155907712, 22.83064248877585),
+            Offset(18.841306012706912, 22.31953234799544),
+            Offset(19.183223001397558, 21.65944760224325),
+            Offset(19.732070850474493, 21.015435554004174),
+            Offset(20.435551631861834, 20.468366312790188),
+            Offset(21.23060828396028, 20.041713905979922),
+            Offset(22.06715801213608, 19.735213871060157),
+            Offset(22.931677497903188, 19.55123899968618),
+            Offset(23.801199687383725, 19.506527080582764),
+            Offset(24.651711975751407, 19.606157294877438),
+            Offset(25.46261762389004, 19.84826250611138),
+            Offset(26.211862438102788, 20.22649117071362),
+            Offset(26.877529723485452, 20.729988226520383),
+            Offset(27.426530333642553, 21.327419665857633),
+            Offset(27.852796597051224, 21.998111345570546),
+            Offset(28.14340318787079, 22.70256984984598),
+            Offset(28.300380921474876, 23.40180362588746),
+            Offset(28.341515096912012, 24.048596657000438),
+            Offset(28.30302507047554, 24.578869773477106),
+            Offset(28.25752024905112, 24.851255719925685),
+            Offset(28.2574725, 24.8514945),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.390243902439,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(31.489132596718107, 22.924219143929935),
+            Offset(31.504692966387218, 23.070928234251795),
+            Offset(31.52158489969306, 23.816598935409147),
+            Offset(31.379117181752463, 24.837594520788468),
+            Offset(30.958629401377593, 25.933078514213545),
+            Offset(30.291186490892898, 26.97316019028753),
+            Offset(29.448413518215055, 27.898039484777154),
+            Offset(28.495925428255816, 28.68494709729183),
+            Offset(27.450311425963747, 29.324538614827237),
+            Offset(26.33388021776769, 29.784325981107337),
+            Offset(25.175688664843197, 30.046584449353226),
+            Offset(24.0035327197847, 30.10424947226514),
+            Offset(22.849183963690074, 29.955448055883483),
+            Offset(21.746312718586534, 29.604259883851242),
+            Offset(20.7526512563793, 29.076388283251973),
+            Offset(19.885676136669687, 28.39606820827754),
+            Offset(19.180517439009286, 27.608979359987934),
+            Offset(18.65251558304633, 26.76704799144326),
+            Offset(18.300204877730252, 25.940226724035732),
+            Offset(18.105796550850236, 25.229028021057122),
+            Offset(18.039586265121297, 24.851828788890007),
+            Offset(18.0395385, 24.851494500000005),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(31.489132596718107, 22.924219143929935),
+            Offset(31.504692966387218, 23.070928234251795),
+            Offset(31.52158489969306, 23.816598935409147),
+            Offset(31.379117181752463, 24.837594520788468),
+            Offset(30.958629401377593, 25.933078514213545),
+            Offset(30.291186490892898, 26.97316019028753),
+            Offset(29.448413518215055, 27.898039484777154),
+            Offset(28.495925428255816, 28.68494709729183),
+            Offset(27.450311425963747, 29.324538614827237),
+            Offset(26.33388021776769, 29.784325981107337),
+            Offset(25.175688664843197, 30.046584449353226),
+            Offset(24.0035327197847, 30.10424947226514),
+            Offset(22.849183963690074, 29.955448055883483),
+            Offset(21.746312718586534, 29.604259883851242),
+            Offset(20.7526512563793, 29.076388283251973),
+            Offset(19.885676136669687, 28.39606820827754),
+            Offset(19.180517439009286, 27.608979359987934),
+            Offset(18.65251558304633, 26.76704799144326),
+            Offset(18.300204877730252, 25.940226724035732),
+            Offset(18.105796550850236, 25.229028021057122),
+            Offset(18.039586265121297, 24.851828788890007),
+            Offset(18.0395385, 24.851494500000005),
+          ],
+          <Offset>[
+            Offset(42.189074403337465, 22.915952464775465),
+            Offset(42.19707747247897, 23.271166355481746),
+            Offset(42.08848397218152, 25.06415442492057),
+            Offset(41.54236233204822, 27.486050286242815),
+            Offset(40.314094860463506, 30.03111431438802),
+            Offset(38.504215540085454, 32.39382175486865),
+            Offset(36.2965845467607, 34.44497746710818),
+            Offset(33.8532316083556, 36.143058119151554),
+            Offset(31.215839699347548, 37.46895496077811),
+            Offset(28.444447326420992, 38.34915839821115),
+            Offset(25.61233590575302, 38.74694041141639),
+            Offset(22.787628633030252, 38.650905277393264),
+            Offset(20.046951901679478, 38.062912126858365),
+            Offset(17.470298547837437, 36.99948626496029),
+            Offset(15.191085358659922, 35.53386213108059),
+            Offset(13.246409086351742, 33.72769892720004),
+            Offset(11.711445981624701, 31.697653951772896),
+            Offset(10.612627801022544, 29.571418296073094),
+            Offset(9.932446361745452, 27.516585113231805),
+            Offset(9.608106117829148, 25.77082656070714),
+            Offset(9.524641278513112, 24.852306346360272),
+            Offset(9.5245935, 24.851494500000005),
+          ],
+          <Offset>[
+            Offset(42.189074403337465, 22.915952464775465),
+            Offset(42.19707747247897, 23.271166355481746),
+            Offset(42.08848397218152, 25.06415442492057),
+            Offset(41.54236233204822, 27.486050286242815),
+            Offset(40.314094860463506, 30.03111431438802),
+            Offset(38.504215540085454, 32.39382175486865),
+            Offset(36.2965845467607, 34.44497746710818),
+            Offset(33.8532316083556, 36.143058119151554),
+            Offset(31.215839699347548, 37.46895496077811),
+            Offset(28.444447326420992, 38.34915839821115),
+            Offset(25.61233590575302, 38.74694041141639),
+            Offset(22.787628633030252, 38.650905277393264),
+            Offset(20.046951901679478, 38.062912126858365),
+            Offset(17.470298547837437, 36.99948626496029),
+            Offset(15.191085358659922, 35.53386213108059),
+            Offset(13.246409086351742, 33.72769892720004),
+            Offset(11.711445981624701, 31.697653951772896),
+            Offset(10.612627801022544, 29.571418296073094),
+            Offset(9.932446361745452, 27.516585113231805),
+            Offset(9.608106117829148, 25.77082656070714),
+            Offset(9.524641278513112, 24.852306346360272),
+            Offset(9.5245935, 24.851494500000005),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.189074403337465, 22.915952464775465),
+            Offset(42.19707747247897, 23.271166355481746),
+            Offset(42.08848397218152, 25.06415442492057),
+            Offset(41.54236233204822, 27.486050286242815),
+            Offset(40.314094860463506, 30.03111431438802),
+            Offset(38.504215540085454, 32.39382175486865),
+            Offset(36.2965845467607, 34.44497746710818),
+            Offset(33.8532316083556, 36.143058119151554),
+            Offset(31.215839699347548, 37.46895496077811),
+            Offset(28.444447326420992, 38.34915839821115),
+            Offset(25.61233590575302, 38.74694041141639),
+            Offset(22.787628633030252, 38.650905277393264),
+            Offset(20.046951901679478, 38.062912126858365),
+            Offset(17.470298547837437, 36.99948626496029),
+            Offset(15.191085358659922, 35.53386213108059),
+            Offset(13.246409086351742, 33.72769892720004),
+            Offset(11.711445981624701, 31.697653951772896),
+            Offset(10.612627801022544, 29.571418296073094),
+            Offset(9.932446361745452, 27.516585113231805),
+            Offset(9.608106117829148, 25.77082656070714),
+            Offset(9.524641278513112, 24.852306346360272),
+            Offset(9.5245935, 24.851494500000005),
+          ],
+          <Offset>[
+            Offset(42.1791543883521, 10.076022296832232),
+            Offset(42.43736321795492, 10.440304948171638),
+            Offset(43.58555055959523, 12.383875537934415),
+            Offset(44.72050925059344, 15.290156105887906),
+            Offset(45.231737820672876, 18.804555763484924),
+            Offset(45.00900941758279, 22.538186895837583),
+            Offset(44.15291012555793, 26.22717223285341),
+            Offset(42.80296483458727, 29.714290703031818),
+            Offset(40.9891393144886, 32.950321032717554),
+            Offset(38.72224622694557, 35.816477867827196),
+            Offset(36.05276306022881, 38.2229637223246),
+            Offset(33.04361559918401, 40.1099901814986),
+            Offset(29.775908786849342, 41.42559060127108),
+            Offset(26.3445702051683, 42.13070326985921),
+            Offset(22.940053976054262, 42.20774120834385),
+            Offset(19.644365949058738, 41.694819387581575),
+            Offset(16.617855491766655, 40.660539700634395),
+            Offset(13.977872166578344, 39.219283634501636),
+            Offset(11.824076428780742, 37.557895332413565),
+            Offset(10.258264365409168, 35.96805508033244),
+            Offset(9.525214347477434, 35.0702403302901),
+            Offset(9.524593500000002, 35.06942850000001),
+          ],
+          <Offset>[
+            Offset(42.1791543883521, 10.076022296832232),
+            Offset(42.43736321795492, 10.440304948171638),
+            Offset(43.58555055959523, 12.383875537934415),
+            Offset(44.72050925059344, 15.290156105887906),
+            Offset(45.231737820672876, 18.804555763484924),
+            Offset(45.00900941758279, 22.538186895837583),
+            Offset(44.15291012555793, 26.22717223285341),
+            Offset(42.80296483458727, 29.714290703031818),
+            Offset(40.9891393144886, 32.950321032717554),
+            Offset(38.72224622694557, 35.816477867827196),
+            Offset(36.05276306022881, 38.2229637223246),
+            Offset(33.04361559918401, 40.1099901814986),
+            Offset(29.775908786849342, 41.42559060127108),
+            Offset(26.3445702051683, 42.13070326985921),
+            Offset(22.940053976054262, 42.20774120834385),
+            Offset(19.644365949058738, 41.694819387581575),
+            Offset(16.617855491766655, 40.660539700634395),
+            Offset(13.977872166578344, 39.219283634501636),
+            Offset(11.824076428780742, 37.557895332413565),
+            Offset(10.258264365409168, 35.96805508033244),
+            Offset(9.525214347477434, 35.0702403302901),
+            Offset(9.524593500000002, 35.06942850000001),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(42.1791543883521, 10.076022296832232),
+            Offset(42.43736321795492, 10.440304948171638),
+            Offset(43.58555055959523, 12.383875537934415),
+            Offset(44.72050925059344, 15.290156105887906),
+            Offset(45.231737820672876, 18.804555763484924),
+            Offset(45.00900941758279, 22.538186895837583),
+            Offset(44.15291012555793, 26.22717223285341),
+            Offset(42.80296483458727, 29.714290703031818),
+            Offset(40.9891393144886, 32.950321032717554),
+            Offset(38.72224622694557, 35.816477867827196),
+            Offset(36.05276306022881, 38.2229637223246),
+            Offset(33.04361559918401, 40.1099901814986),
+            Offset(29.775908786849342, 41.42559060127108),
+            Offset(26.3445702051683, 42.13070326985921),
+            Offset(22.940053976054262, 42.20774120834385),
+            Offset(19.644365949058738, 41.694819387581575),
+            Offset(16.617855491766655, 40.660539700634395),
+            Offset(13.977872166578344, 39.219283634501636),
+            Offset(11.824076428780742, 37.557895332413565),
+            Offset(10.258264365409168, 35.96805508033244),
+            Offset(9.525214347477434, 35.0702403302901),
+            Offset(9.524593500000002, 35.06942850000001),
+          ],
+          <Offset>[
+            Offset(31.479212581732742, 10.084288975986702),
+            Offset(31.744978711863162, 10.240066826941685),
+            Offset(33.018651487106766, 11.136320048422991),
+            Offset(34.55726410029768, 12.641700340433559),
+            Offset(35.87627236158696, 14.706519963310448),
+            Offset(36.79598036839023, 17.117525331256466),
+            Offset(37.30473909701229, 19.680234250522382),
+            Offset(37.44565865448749, 22.256179681172092),
+            Offset(37.223611041104796, 24.805904686766674),
+            Offset(36.611679118292265, 27.25164545072338),
+            Offset(35.61611581931899, 29.522607760261437),
+            Offset(34.25951968593846, 31.563334376370474),
+            Offset(32.57814084885994, 33.3181265302962),
+            Offset(30.620584375917396, 34.73547688875016),
+            Offset(28.501619873773638, 35.75026736051523),
+            Offset(26.283632999376685, 36.36318866865908),
+            Offset(24.08692694915124, 36.57186510884944),
+            Offset(22.01775994860213, 36.41491332987181),
+            Offset(20.191834944765542, 35.98153694321749),
+            Offset(18.755954798430256, 35.42625654068243),
+            Offset(18.04015933408562, 35.06976277281983),
+            Offset(18.0395385, 35.0694285),
+          ],
+          <Offset>[
+            Offset(31.479212581732742, 10.084288975986702),
+            Offset(31.744978711863162, 10.240066826941685),
+            Offset(33.018651487106766, 11.136320048422991),
+            Offset(34.55726410029768, 12.641700340433559),
+            Offset(35.87627236158696, 14.706519963310448),
+            Offset(36.79598036839023, 17.117525331256466),
+            Offset(37.30473909701229, 19.680234250522382),
+            Offset(37.44565865448749, 22.256179681172092),
+            Offset(37.223611041104796, 24.805904686766674),
+            Offset(36.611679118292265, 27.25164545072338),
+            Offset(35.61611581931899, 29.522607760261437),
+            Offset(34.25951968593846, 31.563334376370474),
+            Offset(32.57814084885994, 33.3181265302962),
+            Offset(30.620584375917396, 34.73547688875016),
+            Offset(28.501619873773638, 35.75026736051523),
+            Offset(26.283632999376685, 36.36318866865908),
+            Offset(24.08692694915124, 36.57186510884944),
+            Offset(22.01775994860213, 36.41491332987181),
+            Offset(20.191834944765542, 35.98153694321749),
+            Offset(18.755954798430256, 35.42625654068243),
+            Offset(18.04015933408562, 35.06976277281983),
+            Offset(18.0395385, 35.0694285),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(31.479212581732742, 10.084288975986702),
+            Offset(31.744978711863162, 10.240066826941685),
+            Offset(33.018651487106766, 11.136320048422991),
+            Offset(34.55726410029768, 12.641700340433559),
+            Offset(35.87627236158696, 14.706519963310448),
+            Offset(36.79598036839023, 17.117525331256466),
+            Offset(37.30473909701229, 19.680234250522382),
+            Offset(37.44565865448749, 22.256179681172092),
+            Offset(37.223611041104796, 24.805904686766674),
+            Offset(36.611679118292265, 27.25164545072338),
+            Offset(35.61611581931899, 29.522607760261437),
+            Offset(34.25951968593846, 31.563334376370474),
+            Offset(32.57814084885994, 33.3181265302962),
+            Offset(30.620584375917396, 34.73547688875016),
+            Offset(28.501619873773638, 35.75026736051523),
+            Offset(26.283632999376685, 36.36318866865908),
+            Offset(24.08692694915124, 36.57186510884944),
+            Offset(22.01775994860213, 36.41491332987181),
+            Offset(20.191834944765542, 35.98153694321749),
+            Offset(18.755954798430256, 35.42625654068243),
+            Offset(18.04015933408562, 35.06976277281983),
+            Offset(18.0395385, 35.0694285),
+          ],
+          <Offset>[
+            Offset(31.489132596718107, 22.924219143929935),
+            Offset(31.504692966387218, 23.070928234251795),
+            Offset(31.52158489969306, 23.816598935409147),
+            Offset(31.379117181752463, 24.837594520788468),
+            Offset(30.958629401377593, 25.933078514213545),
+            Offset(30.291186490892898, 26.97316019028753),
+            Offset(29.448413518215055, 27.898039484777154),
+            Offset(28.495925428255816, 28.68494709729183),
+            Offset(27.450311425963747, 29.324538614827237),
+            Offset(26.33388021776769, 29.784325981107337),
+            Offset(25.175688664843197, 30.046584449353226),
+            Offset(24.0035327197847, 30.10424947226514),
+            Offset(22.849183963690074, 29.955448055883483),
+            Offset(21.746312718586534, 29.604259883851242),
+            Offset(20.7526512563793, 29.076388283251973),
+            Offset(19.885676136669687, 28.39606820827754),
+            Offset(19.180517439009286, 27.608979359987934),
+            Offset(18.65251558304633, 26.76704799144326),
+            Offset(18.300204877730252, 25.940226724035732),
+            Offset(18.105796550850236, 25.229028021057122),
+            Offset(18.039586265121297, 24.851828788890007),
+            Offset(18.0395385, 24.851494500000005),
+          ],
+          <Offset>[
+            Offset(31.489132596718107, 22.924219143929935),
+            Offset(31.504692966387218, 23.070928234251795),
+            Offset(31.52158489969306, 23.816598935409147),
+            Offset(31.379117181752463, 24.837594520788468),
+            Offset(30.958629401377593, 25.933078514213545),
+            Offset(30.291186490892898, 26.97316019028753),
+            Offset(29.448413518215055, 27.898039484777154),
+            Offset(28.495925428255816, 28.68494709729183),
+            Offset(27.450311425963747, 29.324538614827237),
+            Offset(26.33388021776769, 29.784325981107337),
+            Offset(25.175688664843197, 30.046584449353226),
+            Offset(24.0035327197847, 30.10424947226514),
+            Offset(22.849183963690074, 29.955448055883483),
+            Offset(21.746312718586534, 29.604259883851242),
+            Offset(20.7526512563793, 29.076388283251973),
+            Offset(19.885676136669687, 28.39606820827754),
+            Offset(19.180517439009286, 27.608979359987934),
+            Offset(18.65251558304633, 26.76704799144326),
+            Offset(18.300204877730252, 25.940226724035732),
+            Offset(18.105796550850236, 25.229028021057122),
+            Offset(18.039586265121297, 24.851828788890007),
+            Offset(18.0395385, 24.851494500000005),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.390243902439,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(5.820845611647898, 37.923977703167765),
+            Offset(5.562636782045075, 37.55969505182836),
+            Offset(4.414449440404765, 35.616124462065585),
+            Offset(3.279490749406561, 32.70984389411209),
+            Offset(2.7682621793271274, 29.195444236515087),
+            Offset(2.990990582417215, 25.461813104162424),
+            Offset(3.8470898744420694, 21.77282776714659),
+            Offset(5.197035165412721, 18.285709296968175),
+            Offset(7.010860685511399, 15.049678967282448),
+            Offset(9.277753773054428, 12.183522132172802),
+            Offset(11.947236939771194, 9.777036277675393),
+            Offset(14.956384400815995, 7.8900098185013965),
+            Offset(18.22409121315066, 6.574409398728922),
+            Offset(21.6554297948317, 5.869296730140784),
+            Offset(25.05994602394574, 5.792258791656161),
+            Offset(28.35563405094127, 6.305180612418426),
+            Offset(31.382144508233342, 7.339460299365608),
+            Offset(34.022127833421656, 8.780716365498357),
+            Offset(36.175923571219265, 10.44210466758643),
+            Offset(37.741735634590825, 12.031944919667563),
+            Offset(38.47478565252257, 12.929759669709906),
+            Offset(38.4754065, 12.930571500000003),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(5.820845611647898, 37.923977703167765),
+            Offset(5.562636782045075, 37.55969505182836),
+            Offset(4.414449440404765, 35.616124462065585),
+            Offset(3.279490749406561, 32.70984389411209),
+            Offset(2.7682621793271274, 29.195444236515087),
+            Offset(2.990990582417215, 25.461813104162424),
+            Offset(3.8470898744420694, 21.77282776714659),
+            Offset(5.197035165412721, 18.285709296968175),
+            Offset(7.010860685511399, 15.049678967282448),
+            Offset(9.277753773054428, 12.183522132172802),
+            Offset(11.947236939771194, 9.777036277675393),
+            Offset(14.956384400815995, 7.8900098185013965),
+            Offset(18.22409121315066, 6.574409398728922),
+            Offset(21.6554297948317, 5.869296730140784),
+            Offset(25.05994602394574, 5.792258791656161),
+            Offset(28.35563405094127, 6.305180612418426),
+            Offset(31.382144508233342, 7.339460299365608),
+            Offset(34.022127833421656, 8.780716365498357),
+            Offset(36.175923571219265, 10.44210466758643),
+            Offset(37.741735634590825, 12.031944919667563),
+            Offset(38.47478565252257, 12.929759669709906),
+            Offset(38.4754065, 12.930571500000003),
+          ],
+          <Offset>[
+            Offset(16.520787418267258, 37.9157110240133),
+            Offset(16.25502128813683, 37.759933173058315),
+            Offset(14.981348512893227, 36.863679951577005),
+            Offset(13.442735899702317, 35.35829965956644),
+            Offset(12.123727638413042, 33.29348003668956),
+            Offset(11.204019631609766, 30.88247466874354),
+            Offset(10.695260902987714, 28.319765749477618),
+            Offset(10.554341345512503, 25.7438203188279),
+            Offset(10.7763889588952, 23.194095313233326),
+            Offset(11.38832088170773, 20.74835454927662),
+            Offset(12.383884180681019, 18.477392239738556),
+            Offset(13.740480314061546, 16.436665623629526),
+            Offset(15.421859151140067, 14.681873469703806),
+            Offset(17.379415624082604, 13.264523111249835),
+            Offset(19.498380126226365, 12.249732639484778),
+            Offset(21.716367000623322, 11.636811331340922),
+            Offset(23.91307305084876, 11.42813489115057),
+            Offset(25.98224005139787, 11.585086670128192),
+            Offset(27.808165055234465, 12.018463056782506),
+            Offset(29.244045201569737, 12.57374345931758),
+            Offset(29.95984066591438, 12.930237227180172),
+            Offset(29.9604615, 12.930571500000003),
+          ],
+          <Offset>[
+            Offset(16.520787418267258, 37.9157110240133),
+            Offset(16.25502128813683, 37.759933173058315),
+            Offset(14.981348512893227, 36.863679951577005),
+            Offset(13.442735899702317, 35.35829965956644),
+            Offset(12.123727638413042, 33.29348003668956),
+            Offset(11.204019631609766, 30.88247466874354),
+            Offset(10.695260902987714, 28.319765749477618),
+            Offset(10.554341345512503, 25.7438203188279),
+            Offset(10.7763889588952, 23.194095313233326),
+            Offset(11.38832088170773, 20.74835454927662),
+            Offset(12.383884180681019, 18.477392239738556),
+            Offset(13.740480314061546, 16.436665623629526),
+            Offset(15.421859151140067, 14.681873469703806),
+            Offset(17.379415624082604, 13.264523111249835),
+            Offset(19.498380126226365, 12.249732639484778),
+            Offset(21.716367000623322, 11.636811331340922),
+            Offset(23.91307305084876, 11.42813489115057),
+            Offset(25.98224005139787, 11.585086670128192),
+            Offset(27.808165055234465, 12.018463056782506),
+            Offset(29.244045201569737, 12.57374345931758),
+            Offset(29.95984066591438, 12.930237227180172),
+            Offset(29.9604615, 12.930571500000003),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(16.520787418267258, 37.9157110240133),
+            Offset(16.25502128813683, 37.759933173058315),
+            Offset(14.981348512893227, 36.863679951577005),
+            Offset(13.442735899702317, 35.35829965956644),
+            Offset(12.123727638413042, 33.29348003668956),
+            Offset(11.204019631609766, 30.88247466874354),
+            Offset(10.695260902987714, 28.319765749477618),
+            Offset(10.554341345512503, 25.7438203188279),
+            Offset(10.7763889588952, 23.194095313233326),
+            Offset(11.38832088170773, 20.74835454927662),
+            Offset(12.383884180681019, 18.477392239738556),
+            Offset(13.740480314061546, 16.436665623629526),
+            Offset(15.421859151140067, 14.681873469703806),
+            Offset(17.379415624082604, 13.264523111249835),
+            Offset(19.498380126226365, 12.249732639484778),
+            Offset(21.716367000623322, 11.636811331340922),
+            Offset(23.91307305084876, 11.42813489115057),
+            Offset(25.98224005139787, 11.585086670128192),
+            Offset(27.808165055234465, 12.018463056782506),
+            Offset(29.244045201569737, 12.57374345931758),
+            Offset(29.95984066591438, 12.930237227180172),
+            Offset(29.9604615, 12.930571500000003),
+          ],
+          <Offset>[
+            Offset(16.510867403281896, 25.075780856070065),
+            Offset(16.495307033612775, 24.929071765748205),
+            Offset(16.478415100306933, 24.183401064590853),
+            Offset(16.620882818247537, 23.16240547921153),
+            Offset(17.041370598622414, 22.066921485786466),
+            Offset(17.708813509107102, 21.026839809712477),
+            Offset(18.55158648178495, 20.101960515222846),
+            Offset(19.504074571744177, 19.315052902708164),
+            Offset(20.54968857403625, 18.675461385172763),
+            Offset(21.666119782232308, 18.215674018892656),
+            Offset(22.824311335156814, 17.95341555064677),
+            Offset(23.996467280215303, 17.89575052773486),
+            Offset(25.15081603630993, 18.04455194411652),
+            Offset(26.253687281413466, 18.39574011614875),
+            Offset(27.247348743620705, 18.923611716748034),
+            Offset(28.114323863330313, 19.60393179172246),
+            Offset(28.81948256099071, 20.39102064001207),
+            Offset(29.34748441695367, 21.232952008556737),
+            Offset(29.699795122269755, 22.059773275964268),
+            Offset(29.894203449149757, 22.770971978942885),
+            Offset(29.960413734878703, 23.148171211109993),
+            Offset(29.9604615, 23.148505500000002),
+          ],
+          <Offset>[
+            Offset(16.510867403281896, 25.075780856070065),
+            Offset(16.495307033612775, 24.929071765748205),
+            Offset(16.478415100306933, 24.183401064590853),
+            Offset(16.620882818247537, 23.16240547921153),
+            Offset(17.041370598622414, 22.066921485786466),
+            Offset(17.708813509107102, 21.026839809712477),
+            Offset(18.55158648178495, 20.101960515222846),
+            Offset(19.504074571744177, 19.315052902708164),
+            Offset(20.54968857403625, 18.675461385172763),
+            Offset(21.666119782232308, 18.215674018892656),
+            Offset(22.824311335156814, 17.95341555064677),
+            Offset(23.996467280215303, 17.89575052773486),
+            Offset(25.15081603630993, 18.04455194411652),
+            Offset(26.253687281413466, 18.39574011614875),
+            Offset(27.247348743620705, 18.923611716748034),
+            Offset(28.114323863330313, 19.60393179172246),
+            Offset(28.81948256099071, 20.39102064001207),
+            Offset(29.34748441695367, 21.232952008556737),
+            Offset(29.699795122269755, 22.059773275964268),
+            Offset(29.894203449149757, 22.770971978942885),
+            Offset(29.960413734878703, 23.148171211109993),
+            Offset(29.9604615, 23.148505500000002),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(16.510867403281896, 25.075780856070065),
+            Offset(16.495307033612775, 24.929071765748205),
+            Offset(16.478415100306933, 24.183401064590853),
+            Offset(16.620882818247537, 23.16240547921153),
+            Offset(17.041370598622414, 22.066921485786466),
+            Offset(17.708813509107102, 21.026839809712477),
+            Offset(18.55158648178495, 20.101960515222846),
+            Offset(19.504074571744177, 19.315052902708164),
+            Offset(20.54968857403625, 18.675461385172763),
+            Offset(21.666119782232308, 18.215674018892656),
+            Offset(22.824311335156814, 17.95341555064677),
+            Offset(23.996467280215303, 17.89575052773486),
+            Offset(25.15081603630993, 18.04455194411652),
+            Offset(26.253687281413466, 18.39574011614875),
+            Offset(27.247348743620705, 18.923611716748034),
+            Offset(28.114323863330313, 19.60393179172246),
+            Offset(28.81948256099071, 20.39102064001207),
+            Offset(29.34748441695367, 21.232952008556737),
+            Offset(29.699795122269755, 22.059773275964268),
+            Offset(29.894203449149757, 22.770971978942885),
+            Offset(29.960413734878703, 23.148171211109993),
+            Offset(29.9604615, 23.148505500000002),
+          ],
+          <Offset>[
+            Offset(5.810925596662535, 25.084047535224535),
+            Offset(5.8029225275210194, 24.728833644518254),
+            Offset(5.911516027818471, 22.93584557507943),
+            Offset(6.457637667951779, 20.51394971375718),
+            Offset(7.6859051395365, 17.96888568561199),
+            Offset(9.495784459914551, 15.606178245131362),
+            Offset(11.703415453239304, 13.555022532891817),
+            Offset(14.146768391644395, 11.856941880848437),
+            Offset(16.78416030065245, 10.531045039221889),
+            Offset(19.555552673579008, 9.650841601788843),
+            Offset(22.38766409424699, 9.253059588583607),
+            Offset(25.21237136696975, 9.349094722606733),
+            Offset(27.953048098320522, 9.937087873141635),
+            Offset(30.529701452162563, 11.000513735039702),
+            Offset(32.80891464134008, 12.466137868919416),
+            Offset(34.75359091364826, 14.272301072799962),
+            Offset(36.288554018375294, 16.302346048227108),
+            Offset(37.38737219897746, 18.428581703926902),
+            Offset(38.06755363825456, 20.483414886768188),
+            Offset(38.39189388217084, 22.229173439292868),
+            Offset(38.475358721486884, 23.147693653639728),
+            Offset(38.4754065, 23.148505500000002),
+          ],
+          <Offset>[
+            Offset(5.810925596662535, 25.084047535224535),
+            Offset(5.8029225275210194, 24.728833644518254),
+            Offset(5.911516027818471, 22.93584557507943),
+            Offset(6.457637667951779, 20.51394971375718),
+            Offset(7.6859051395365, 17.96888568561199),
+            Offset(9.495784459914551, 15.606178245131362),
+            Offset(11.703415453239304, 13.555022532891817),
+            Offset(14.146768391644395, 11.856941880848437),
+            Offset(16.78416030065245, 10.531045039221889),
+            Offset(19.555552673579008, 9.650841601788843),
+            Offset(22.38766409424699, 9.253059588583607),
+            Offset(25.21237136696975, 9.349094722606733),
+            Offset(27.953048098320522, 9.937087873141635),
+            Offset(30.529701452162563, 11.000513735039702),
+            Offset(32.80891464134008, 12.466137868919416),
+            Offset(34.75359091364826, 14.272301072799962),
+            Offset(36.288554018375294, 16.302346048227108),
+            Offset(37.38737219897746, 18.428581703926902),
+            Offset(38.06755363825456, 20.483414886768188),
+            Offset(38.39189388217084, 22.229173439292868),
+            Offset(38.475358721486884, 23.147693653639728),
+            Offset(38.4754065, 23.148505500000002),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(5.810925596662535, 25.084047535224535),
+            Offset(5.8029225275210194, 24.728833644518254),
+            Offset(5.911516027818471, 22.93584557507943),
+            Offset(6.457637667951779, 20.51394971375718),
+            Offset(7.6859051395365, 17.96888568561199),
+            Offset(9.495784459914551, 15.606178245131362),
+            Offset(11.703415453239304, 13.555022532891817),
+            Offset(14.146768391644395, 11.856941880848437),
+            Offset(16.78416030065245, 10.531045039221889),
+            Offset(19.555552673579008, 9.650841601788843),
+            Offset(22.38766409424699, 9.253059588583607),
+            Offset(25.21237136696975, 9.349094722606733),
+            Offset(27.953048098320522, 9.937087873141635),
+            Offset(30.529701452162563, 11.000513735039702),
+            Offset(32.80891464134008, 12.466137868919416),
+            Offset(34.75359091364826, 14.272301072799962),
+            Offset(36.288554018375294, 16.302346048227108),
+            Offset(37.38737219897746, 18.428581703926902),
+            Offset(38.06755363825456, 20.483414886768188),
+            Offset(38.39189388217084, 22.229173439292868),
+            Offset(38.475358721486884, 23.147693653639728),
+            Offset(38.4754065, 23.148505500000002),
+          ],
+          <Offset>[
+            Offset(5.820845611647898, 37.923977703167765),
+            Offset(5.562636782045075, 37.55969505182836),
+            Offset(4.414449440404765, 35.616124462065585),
+            Offset(3.279490749406561, 32.70984389411209),
+            Offset(2.7682621793271274, 29.195444236515087),
+            Offset(2.990990582417215, 25.461813104162424),
+            Offset(3.8470898744420694, 21.77282776714659),
+            Offset(5.197035165412721, 18.285709296968175),
+            Offset(7.010860685511399, 15.049678967282448),
+            Offset(9.277753773054428, 12.183522132172802),
+            Offset(11.947236939771194, 9.777036277675393),
+            Offset(14.956384400815995, 7.8900098185013965),
+            Offset(18.22409121315066, 6.574409398728922),
+            Offset(21.6554297948317, 5.869296730140784),
+            Offset(25.05994602394574, 5.792258791656161),
+            Offset(28.35563405094127, 6.305180612418426),
+            Offset(31.382144508233342, 7.339460299365608),
+            Offset(34.022127833421656, 8.780716365498357),
+            Offset(36.175923571219265, 10.44210466758643),
+            Offset(37.741735634590825, 12.031944919667563),
+            Offset(38.47478565252257, 12.929759669709906),
+            Offset(38.4754065, 12.930571500000003),
+          ],
+          <Offset>[
+            Offset(5.820845611647898, 37.923977703167765),
+            Offset(5.562636782045075, 37.55969505182836),
+            Offset(4.414449440404765, 35.616124462065585),
+            Offset(3.279490749406561, 32.70984389411209),
+            Offset(2.7682621793271274, 29.195444236515087),
+            Offset(2.990990582417215, 25.461813104162424),
+            Offset(3.8470898744420694, 21.77282776714659),
+            Offset(5.197035165412721, 18.285709296968175),
+            Offset(7.010860685511399, 15.049678967282448),
+            Offset(9.277753773054428, 12.183522132172802),
+            Offset(11.947236939771194, 9.777036277675393),
+            Offset(14.956384400815995, 7.8900098185013965),
+            Offset(18.22409121315066, 6.574409398728922),
+            Offset(21.6554297948317, 5.869296730140784),
+            Offset(25.05994602394574, 5.792258791656161),
+            Offset(28.35563405094127, 6.305180612418426),
+            Offset(31.382144508233342, 7.339460299365608),
+            Offset(34.022127833421656, 8.780716365498357),
+            Offset(36.175923571219265, 10.44210466758643),
+            Offset(37.741735634590825, 12.031944919667563),
+            Offset(38.47478565252257, 12.929759669709906),
+            Offset(38.4754065, 12.930571500000003),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+    _PathFrames(
+      opacities: <double>[
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        1.0,
+        0.390243902439,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ],
+      commands: <_PathCommand>[
+        _PathMoveTo(
+          <Offset>[
+            Offset(18.66077577959113, 37.914057688182396),
+            Offset(18.393498189355185, 37.79998079730431),
+            Offset(17.09472832739092, 37.11319104947929),
+            Offset(15.475384929761471, 35.88799081265731),
+            Offset(13.994820730230222, 34.11308719672446),
+            Offset(12.846625441448277, 31.966606981659766),
+            Offset(12.064895108696843, 29.629153345943823),
+            Offset(11.625802581532461, 27.235442523199854),
+            Offset(11.529494613571963, 24.822978582423502),
+            Offset(11.810434303438388, 22.461321032697384),
+            Offset(12.47121362886298, 20.217463432151188),
+            Offset(13.49729949671066, 18.145996784655154),
+            Offset(14.861412738737947, 16.303366283898786),
+            Offset(16.52421278993278, 14.743568387471647),
+            Offset(18.38606694668249, 13.541227409050501),
+            Offset(20.388513590559725, 12.70313747512542),
+            Offset(22.419258759371843, 12.24586980950756),
+            Offset(24.37426249499311, 12.145960731054156),
+            Offset(26.134613352037505, 12.33373473462172),
+            Offset(27.54450711496552, 12.682103167247583),
+            Offset(28.256851668592745, 12.930332738674227),
+            Offset(28.257472499999995, 12.930571500000006),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(18.66077577959113, 37.914057688182396),
+            Offset(18.393498189355185, 37.79998079730431),
+            Offset(17.09472832739092, 37.11319104947929),
+            Offset(15.475384929761471, 35.88799081265731),
+            Offset(13.994820730230222, 34.11308719672446),
+            Offset(12.846625441448277, 31.966606981659766),
+            Offset(12.064895108696843, 29.629153345943823),
+            Offset(11.625802581532461, 27.235442523199854),
+            Offset(11.529494613571963, 24.822978582423502),
+            Offset(11.810434303438388, 22.461321032697384),
+            Offset(12.47121362886298, 20.217463432151188),
+            Offset(13.49729949671066, 18.145996784655154),
+            Offset(14.861412738737947, 16.303366283898786),
+            Offset(16.52421278993278, 14.743568387471647),
+            Offset(18.38606694668249, 13.541227409050501),
+            Offset(20.388513590559725, 12.70313747512542),
+            Offset(22.419258759371843, 12.24586980950756),
+            Offset(24.37426249499311, 12.145960731054156),
+            Offset(26.134613352037505, 12.33373473462172),
+            Offset(27.54450711496552, 12.682103167247583),
+            Offset(28.256851668592745, 12.930332738674227),
+            Offset(28.257472499999995, 12.930571500000006),
+          ],
+          <Offset>[
+            Offset(29.36071758621049, 37.90579100902793),
+            Offset(29.08588269544694, 38.00021891853426),
+            Offset(27.661627399879382, 38.36074653899071),
+            Offset(25.638630080057226, 38.53644657811165),
+            Offset(23.350286189316137, 38.211122996898936),
+            Offset(21.059654490640828, 37.38726854624088),
+            Offset(18.913066137242488, 36.17609132827485),
+            Offset(16.983108761632245, 34.69355354505958),
+            Offset(15.295022886955763, 32.967394928374375),
+            Offset(13.92100141209169, 31.0261534498012),
+            Offset(12.907860869772804, 28.91781939421435),
+            Offset(12.281395409956211, 26.692652589783282),
+            Offset(12.059180676727353, 24.41083035487367),
+            Offset(12.24819861918368, 22.1387947685807),
+            Offset(12.824501048963114, 19.99870125687912),
+            Offset(13.74924654024178, 18.034768194047917),
+            Offset(14.950187301987258, 16.334544401292522),
+            Offset(16.334374712969325, 14.950331035683991),
+            Offset(17.766854836052705, 13.910093123817797),
+            Offset(19.04681668194443, 13.2239017068976),
+            Offset(19.74190668198456, 12.930810296144493),
+            Offset(19.742527499999994, 12.930571500000006),
+          ],
+          <Offset>[
+            Offset(29.36071758621049, 37.90579100902793),
+            Offset(29.08588269544694, 38.00021891853426),
+            Offset(27.661627399879382, 38.36074653899071),
+            Offset(25.638630080057226, 38.53644657811165),
+            Offset(23.350286189316137, 38.211122996898936),
+            Offset(21.059654490640828, 37.38726854624088),
+            Offset(18.913066137242488, 36.17609132827485),
+            Offset(16.983108761632245, 34.69355354505958),
+            Offset(15.295022886955763, 32.967394928374375),
+            Offset(13.92100141209169, 31.0261534498012),
+            Offset(12.907860869772804, 28.91781939421435),
+            Offset(12.281395409956211, 26.692652589783282),
+            Offset(12.059180676727353, 24.41083035487367),
+            Offset(12.24819861918368, 22.1387947685807),
+            Offset(12.824501048963114, 19.99870125687912),
+            Offset(13.74924654024178, 18.034768194047917),
+            Offset(14.950187301987258, 16.334544401292522),
+            Offset(16.334374712969325, 14.950331035683991),
+            Offset(17.766854836052705, 13.910093123817797),
+            Offset(19.04681668194443, 13.2239017068976),
+            Offset(19.74190668198456, 12.930810296144493),
+            Offset(19.742527499999994, 12.930571500000006),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(29.36071758621049, 37.90579100902793),
+            Offset(29.08588269544694, 38.00021891853426),
+            Offset(27.661627399879382, 38.36074653899071),
+            Offset(25.638630080057226, 38.53644657811165),
+            Offset(23.350286189316137, 38.211122996898936),
+            Offset(21.059654490640828, 37.38726854624088),
+            Offset(18.913066137242488, 36.17609132827485),
+            Offset(16.983108761632245, 34.69355354505958),
+            Offset(15.295022886955763, 32.967394928374375),
+            Offset(13.92100141209169, 31.0261534498012),
+            Offset(12.907860869772804, 28.91781939421435),
+            Offset(12.281395409956211, 26.692652589783282),
+            Offset(12.059180676727353, 24.41083035487367),
+            Offset(12.24819861918368, 22.1387947685807),
+            Offset(12.824501048963114, 19.99870125687912),
+            Offset(13.74924654024178, 18.034768194047917),
+            Offset(14.950187301987258, 16.334544401292522),
+            Offset(16.334374712969325, 14.950331035683991),
+            Offset(17.766854836052705, 13.910093123817797),
+            Offset(19.04681668194443, 13.2239017068976),
+            Offset(19.74190668198456, 12.930810296144493),
+            Offset(19.742527499999994, 12.930571500000006),
+          ],
+          <Offset>[
+            Offset(29.350797571225126, 25.065860841084696),
+            Offset(29.326168440922885, 25.16935751122415),
+            Offset(29.158693987293088, 25.68046765200456),
+            Offset(28.816776998602446, 26.340552397756746),
+            Offset(28.267929149525507, 26.984564445995836),
+            Offset(27.564448368138166, 27.53163368720982),
+            Offset(26.76939171603972, 27.958286094020078),
+            Offset(25.932841987863917, 28.264786128939843),
+            Offset(25.068322502096812, 28.448761000313816),
+            Offset(24.198800312616267, 28.493472919417236),
+            Offset(23.3482880242486, 28.393842705122562),
+            Offset(22.537382376109967, 28.151737493888618),
+            Offset(21.788137561897216, 27.77350882928638),
+            Offset(21.122470276514544, 27.270011773479617),
+            Offset(20.573469666357454, 26.672580334142374),
+            Offset(20.147203402948776, 26.001888654429454),
+            Offset(19.85659681212921, 25.29743015015402),
+            Offset(19.699619078525124, 24.598196374112536),
+            Offset(19.658484903087995, 23.951403342999555),
+            Offset(19.69697492952445, 23.421130226522905),
+            Offset(19.74247975094888, 23.148744280074315),
+            Offset(19.742527499999998, 23.148505500000006),
+          ],
+          <Offset>[
+            Offset(29.350797571225126, 25.065860841084696),
+            Offset(29.326168440922885, 25.16935751122415),
+            Offset(29.158693987293088, 25.68046765200456),
+            Offset(28.816776998602446, 26.340552397756746),
+            Offset(28.267929149525507, 26.984564445995836),
+            Offset(27.564448368138166, 27.53163368720982),
+            Offset(26.76939171603972, 27.958286094020078),
+            Offset(25.932841987863917, 28.264786128939843),
+            Offset(25.068322502096812, 28.448761000313816),
+            Offset(24.198800312616267, 28.493472919417236),
+            Offset(23.3482880242486, 28.393842705122562),
+            Offset(22.537382376109967, 28.151737493888618),
+            Offset(21.788137561897216, 27.77350882928638),
+            Offset(21.122470276514544, 27.270011773479617),
+            Offset(20.573469666357454, 26.672580334142374),
+            Offset(20.147203402948776, 26.001888654429454),
+            Offset(19.85659681212921, 25.29743015015402),
+            Offset(19.699619078525124, 24.598196374112536),
+            Offset(19.658484903087995, 23.951403342999555),
+            Offset(19.69697492952445, 23.421130226522905),
+            Offset(19.74247975094888, 23.148744280074315),
+            Offset(19.742527499999998, 23.148505500000006),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(29.350797571225126, 25.065860841084696),
+            Offset(29.326168440922885, 25.16935751122415),
+            Offset(29.158693987293088, 25.68046765200456),
+            Offset(28.816776998602446, 26.340552397756746),
+            Offset(28.267929149525507, 26.984564445995836),
+            Offset(27.564448368138166, 27.53163368720982),
+            Offset(26.76939171603972, 27.958286094020078),
+            Offset(25.932841987863917, 28.264786128939843),
+            Offset(25.068322502096812, 28.448761000313816),
+            Offset(24.198800312616267, 28.493472919417236),
+            Offset(23.3482880242486, 28.393842705122562),
+            Offset(22.537382376109967, 28.151737493888618),
+            Offset(21.788137561897216, 27.77350882928638),
+            Offset(21.122470276514544, 27.270011773479617),
+            Offset(20.573469666357454, 26.672580334142374),
+            Offset(20.147203402948776, 26.001888654429454),
+            Offset(19.85659681212921, 25.29743015015402),
+            Offset(19.699619078525124, 24.598196374112536),
+            Offset(19.658484903087995, 23.951403342999555),
+            Offset(19.69697492952445, 23.421130226522905),
+            Offset(19.74247975094888, 23.148744280074315),
+            Offset(19.742527499999998, 23.148505500000006),
+          ],
+          <Offset>[
+            Offset(18.650855764605765, 25.074127520239166),
+            Offset(18.63378393483113, 24.969119389994198),
+            Offset(18.591794914804627, 24.432912162493135),
+            Offset(18.65353184830669, 23.6920966323024),
+            Offset(18.912463690439594, 22.88652864582136),
+            Offset(19.351419318945613, 22.110972122628702),
+            Offset(19.921220687494078, 21.41134811168905),
+            Offset(20.575535807764133, 20.806675107080117),
+            Offset(21.30279422871301, 20.304344654362943),
+            Offset(22.088233203962965, 19.92864050231342),
+            Offset(22.911640783338775, 19.6934867430594),
+            Offset(23.753286462864416, 19.60508168876049),
+            Offset(24.59036962390781, 19.666044758311497),
+            Offset(25.39848444726364, 19.874785392370566),
+            Offset(26.13503556407683, 20.215106486313754),
+            Offset(26.786470453266723, 20.670257935506957),
+            Offset(27.3256682695138, 21.20875555836906),
+            Offset(27.73950686054891, 21.7938260694827),
+            Offset(28.026243419072795, 22.37504495380348),
+            Offset(28.19466536254554, 22.879331686872888),
+            Offset(28.257424737557066, 23.14826672260405),
+            Offset(28.2574725, 23.148505500000006),
+          ],
+          <Offset>[
+            Offset(18.650855764605765, 25.074127520239166),
+            Offset(18.63378393483113, 24.969119389994198),
+            Offset(18.591794914804627, 24.432912162493135),
+            Offset(18.65353184830669, 23.6920966323024),
+            Offset(18.912463690439594, 22.88652864582136),
+            Offset(19.351419318945613, 22.110972122628702),
+            Offset(19.921220687494078, 21.41134811168905),
+            Offset(20.575535807764133, 20.806675107080117),
+            Offset(21.30279422871301, 20.304344654362943),
+            Offset(22.088233203962965, 19.92864050231342),
+            Offset(22.911640783338775, 19.6934867430594),
+            Offset(23.753286462864416, 19.60508168876049),
+            Offset(24.59036962390781, 19.666044758311497),
+            Offset(25.39848444726364, 19.874785392370566),
+            Offset(26.13503556407683, 20.215106486313754),
+            Offset(26.786470453266723, 20.670257935506957),
+            Offset(27.3256682695138, 21.20875555836906),
+            Offset(27.73950686054891, 21.7938260694827),
+            Offset(28.026243419072795, 22.37504495380348),
+            Offset(28.19466536254554, 22.879331686872888),
+            Offset(28.257424737557066, 23.14826672260405),
+            Offset(28.2574725, 23.148505500000006),
+          ],
+        ),
+        _PathCubicTo(
+          <Offset>[
+            Offset(18.650855764605765, 25.074127520239166),
+            Offset(18.63378393483113, 24.969119389994198),
+            Offset(18.591794914804627, 24.432912162493135),
+            Offset(18.65353184830669, 23.6920966323024),
+            Offset(18.912463690439594, 22.88652864582136),
+            Offset(19.351419318945613, 22.110972122628702),
+            Offset(19.921220687494078, 21.41134811168905),
+            Offset(20.575535807764133, 20.806675107080117),
+            Offset(21.30279422871301, 20.304344654362943),
+            Offset(22.088233203962965, 19.92864050231342),
+            Offset(22.911640783338775, 19.6934867430594),
+            Offset(23.753286462864416, 19.60508168876049),
+            Offset(24.59036962390781, 19.666044758311497),
+            Offset(25.39848444726364, 19.874785392370566),
+            Offset(26.13503556407683, 20.215106486313754),
+            Offset(26.786470453266723, 20.670257935506957),
+            Offset(27.3256682695138, 21.20875555836906),
+            Offset(27.73950686054891, 21.7938260694827),
+            Offset(28.026243419072795, 22.37504495380348),
+            Offset(28.19466536254554, 22.879331686872888),
+            Offset(28.257424737557066, 23.14826672260405),
+            Offset(28.2574725, 23.148505500000006),
+          ],
+          <Offset>[
+            Offset(18.66077577959113, 37.914057688182396),
+            Offset(18.393498189355185, 37.79998079730431),
+            Offset(17.09472832739092, 37.11319104947929),
+            Offset(15.475384929761471, 35.88799081265731),
+            Offset(13.994820730230222, 34.11308719672446),
+            Offset(12.846625441448277, 31.966606981659766),
+            Offset(12.064895108696843, 29.629153345943823),
+            Offset(11.625802581532461, 27.235442523199854),
+            Offset(11.529494613571963, 24.822978582423502),
+            Offset(11.810434303438388, 22.461321032697384),
+            Offset(12.47121362886298, 20.217463432151188),
+            Offset(13.49729949671066, 18.145996784655154),
+            Offset(14.861412738737947, 16.303366283898786),
+            Offset(16.52421278993278, 14.743568387471647),
+            Offset(18.38606694668249, 13.541227409050501),
+            Offset(20.388513590559725, 12.70313747512542),
+            Offset(22.419258759371843, 12.24586980950756),
+            Offset(24.37426249499311, 12.145960731054156),
+            Offset(26.134613352037505, 12.33373473462172),
+            Offset(27.54450711496552, 12.682103167247583),
+            Offset(28.256851668592745, 12.930332738674227),
+            Offset(28.257472499999995, 12.930571500000006),
+          ],
+          <Offset>[
+            Offset(18.66077577959113, 37.914057688182396),
+            Offset(18.393498189355185, 37.79998079730431),
+            Offset(17.09472832739092, 37.11319104947929),
+            Offset(15.475384929761471, 35.88799081265731),
+            Offset(13.994820730230222, 34.11308719672446),
+            Offset(12.846625441448277, 31.966606981659766),
+            Offset(12.064895108696843, 29.629153345943823),
+            Offset(11.625802581532461, 27.235442523199854),
+            Offset(11.529494613571963, 24.822978582423502),
+            Offset(11.810434303438388, 22.461321032697384),
+            Offset(12.47121362886298, 20.217463432151188),
+            Offset(13.49729949671066, 18.145996784655154),
+            Offset(14.861412738737947, 16.303366283898786),
+            Offset(16.52421278993278, 14.743568387471647),
+            Offset(18.38606694668249, 13.541227409050501),
+            Offset(20.388513590559725, 12.70313747512542),
+            Offset(22.419258759371843, 12.24586980950756),
+            Offset(24.37426249499311, 12.145960731054156),
+            Offset(26.134613352037505, 12.33373473462172),
+            Offset(27.54450711496552, 12.682103167247583),
+            Offset(28.256851668592745, 12.930332738674227),
+            Offset(28.257472499999995, 12.930571500000006),
+          ],
+        ),
+        _PathClose(
+        ),
+      ],
+    ),
+  ],
+  matchTextDirection: true,
+);
diff --git a/lib/src/material/app.dart b/lib/src/material/app.dart
new file mode 100644
index 0000000..b0ac8c5
--- /dev/null
+++ b/lib/src/material/app.dart
@@ -0,0 +1,862 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui;
+
+import 'package:flute/cupertino.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'arc.dart';
+import 'colors.dart';
+import 'floating_action_button.dart';
+import 'icons.dart';
+import 'material_localizations.dart';
+import 'page.dart';
+import 'scaffold.dart' show ScaffoldMessenger, ScaffoldMessengerState;
+import 'theme.dart';
+
+/// [MaterialApp] uses this [TextStyle] as its [DefaultTextStyle] to encourage
+/// developers to be intentional about their [DefaultTextStyle].
+///
+/// In Material Design, most [Text] widgets are contained in [Material] widgets,
+/// which sets a specific [DefaultTextStyle]. If you're seeing text that uses
+/// this text style, consider putting your text in a [Material] widget (or
+/// another widget that sets a [DefaultTextStyle]).
+const TextStyle _errorTextStyle = TextStyle(
+  color: Color(0xD0FF0000),
+  fontFamily: 'monospace',
+  fontSize: 48.0,
+  fontWeight: FontWeight.w900,
+  decoration: TextDecoration.underline,
+  decorationColor: Color(0xFFFFFF00),
+  decorationStyle: TextDecorationStyle.double,
+  debugLabel: 'fallback style; consider putting your text in a Material',
+);
+
+/// Describes which theme will be used by [MaterialApp].
+enum ThemeMode {
+  /// Use either the light or dark theme based on what the user has selected in
+  /// the system settings.
+  system,
+
+  /// Always use the light mode regardless of system preference.
+  light,
+
+  /// Always use the dark mode (if available) regardless of system preference.
+  dark,
+}
+
+/// An application that uses material design.
+///
+/// A convenience widget that wraps a number of widgets that are commonly
+/// required for material design applications. It builds upon a [WidgetsApp] by
+/// adding material-design specific functionality, such as [AnimatedTheme] and
+/// [GridPaper].
+///
+/// The [MaterialApp] configures the top-level [Navigator] to search for routes
+/// in the following order:
+///
+///  1. For the `/` route, the [home] property, if non-null, is used.
+///
+///  2. Otherwise, the [routes] table is used, if it has an entry for the route.
+///
+///  3. Otherwise, [onGenerateRoute] is called, if provided. It should return a
+///     non-null value for any _valid_ route not handled by [home] and [routes].
+///
+///  4. Finally if all else fails [onUnknownRoute] is called.
+///
+/// If a [Navigator] is created, at least one of these options must handle the
+/// `/` route, since it is used when an invalid [initialRoute] is specified on
+/// startup (e.g. by another application launching this one with an intent on
+/// Android; see [dart:ui.PlatformDispatcher.defaultRouteName]).
+///
+/// This widget also configures the observer of the top-level [Navigator] (if
+/// any) to perform [Hero] animations.
+///
+/// If [home], [routes], [onGenerateRoute], and [onUnknownRoute] are all null,
+/// and [builder] is not null, then no [Navigator] is created.
+///
+/// {@tool snippet}
+/// This example shows how to create a [MaterialApp] that disables the "debug"
+/// banner with a [home] route that will be displayed when the app is launched.
+///
+/// ![The MaterialApp displays a Scaffold ](https://flutter.github.io/assets-for-api-docs/assets/material/basic_material_app.png)
+///
+/// ```dart
+/// MaterialApp(
+///   home: Scaffold(
+///     appBar: AppBar(
+///       title: const Text('Home'),
+///     ),
+///   ),
+///   debugShowCheckedModeBanner: false,
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+/// This example shows how to create a [MaterialApp] that uses the [routes]
+/// `Map` to define the "home" route and an "about" route.
+///
+/// ```dart
+/// MaterialApp(
+///   routes: <String, WidgetBuilder>{
+///     '/': (BuildContext context) {
+///       return Scaffold(
+///         appBar: AppBar(
+///           title: const Text('Home Route'),
+///         ),
+///       );
+///     },
+///     '/about': (BuildContext context) {
+///       return Scaffold(
+///         appBar: AppBar(
+///           title: const Text('About Route'),
+///         ),
+///       );
+///      }
+///    },
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+/// This example shows how to create a [MaterialApp] that defines a [theme] that
+/// will be used for material widgets in the app.
+///
+/// ![The MaterialApp displays a Scaffold with a dark background and a blue / grey AppBar at the top](https://flutter.github.io/assets-for-api-docs/assets/material/theme_material_app.png)
+///
+/// ```dart
+/// MaterialApp(
+///   theme: ThemeData(
+///     brightness: Brightness.dark,
+///     primaryColor: Colors.blueGrey
+///   ),
+///   home: Scaffold(
+///     appBar: AppBar(
+///       title: const Text('MaterialApp Theme'),
+///     ),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Scaffold], which provides standard app elements like an [AppBar] and a [Drawer].
+///  * [Navigator], which is used to manage the app's stack of pages.
+///  * [MaterialPageRoute], which defines an app page that transitions in a material-specific way.
+///  * [WidgetsApp], which defines the basic app elements but does not depend on the material library.
+///  * The Flutter Internationalization Tutorial,
+///    <https://flutter.dev/tutorials/internationalization/>.
+class MaterialApp extends StatefulWidget {
+  /// Creates a MaterialApp.
+  ///
+  /// At least one of [home], [routes], [onGenerateRoute], or [builder] must be
+  /// non-null. If only [routes] is given, it must include an entry for the
+  /// [Navigator.defaultRouteName] (`/`), since that is the route used when the
+  /// application is launched with an intent that specifies an otherwise
+  /// unsupported route.
+  ///
+  /// This class creates an instance of [WidgetsApp].
+  ///
+  /// The boolean arguments, [routes], and [navigatorObservers], must not be null.
+  const MaterialApp({
+    Key? key,
+    this.navigatorKey,
+    this.scaffoldMessengerKey,
+    this.home,
+    Map<String, WidgetBuilder> this.routes = const <String, WidgetBuilder>{},
+    this.initialRoute,
+    this.onGenerateRoute,
+    this.onGenerateInitialRoutes,
+    this.onUnknownRoute,
+    List<NavigatorObserver> this.navigatorObservers = const <NavigatorObserver>[],
+    this.builder,
+    this.title = '',
+    this.onGenerateTitle,
+    this.color,
+    this.theme,
+    this.darkTheme,
+    this.highContrastTheme,
+    this.highContrastDarkTheme,
+    this.themeMode = ThemeMode.system,
+    this.locale,
+    this.localizationsDelegates,
+    this.localeListResolutionCallback,
+    this.localeResolutionCallback,
+    this.supportedLocales = const <Locale>[Locale('en', 'US')],
+    this.debugShowMaterialGrid = false,
+    this.showPerformanceOverlay = false,
+    this.checkerboardRasterCacheImages = false,
+    this.checkerboardOffscreenLayers = false,
+    this.showSemanticsDebugger = false,
+    this.debugShowCheckedModeBanner = true,
+    this.shortcuts,
+    this.actions,
+    this.restorationScopeId,
+  }) : assert(routes != null),
+       assert(navigatorObservers != null),
+       assert(title != null),
+       assert(debugShowMaterialGrid != null),
+       assert(showPerformanceOverlay != null),
+       assert(checkerboardRasterCacheImages != null),
+       assert(checkerboardOffscreenLayers != null),
+       assert(showSemanticsDebugger != null),
+       assert(debugShowCheckedModeBanner != null),
+       routeInformationProvider = null,
+       routeInformationParser = null,
+       routerDelegate = null,
+       backButtonDispatcher = null,
+       super(key: key);
+
+  /// Creates a [MaterialApp] that uses the [Router] instead of a [Navigator].
+  const MaterialApp.router({
+    Key? key,
+    this.scaffoldMessengerKey,
+    this.routeInformationProvider,
+    required RouteInformationParser<Object> this.routeInformationParser,
+    required RouterDelegate<Object> this.routerDelegate,
+    this.backButtonDispatcher,
+    this.builder,
+    this.title = '',
+    this.onGenerateTitle,
+    this.color,
+    this.theme,
+    this.darkTheme,
+    this.highContrastTheme,
+    this.highContrastDarkTheme,
+    this.themeMode = ThemeMode.system,
+    this.locale,
+    this.localizationsDelegates,
+    this.localeListResolutionCallback,
+    this.localeResolutionCallback,
+    this.supportedLocales = const <Locale>[Locale('en', 'US')],
+    this.debugShowMaterialGrid = false,
+    this.showPerformanceOverlay = false,
+    this.checkerboardRasterCacheImages = false,
+    this.checkerboardOffscreenLayers = false,
+    this.showSemanticsDebugger = false,
+    this.debugShowCheckedModeBanner = true,
+    this.shortcuts,
+    this.actions,
+    this.restorationScopeId,
+  }) : assert(routeInformationParser != null),
+       assert(routerDelegate != null),
+       assert(title != null),
+       assert(debugShowMaterialGrid != null),
+       assert(showPerformanceOverlay != null),
+       assert(checkerboardRasterCacheImages != null),
+       assert(checkerboardOffscreenLayers != null),
+       assert(showSemanticsDebugger != null),
+       assert(debugShowCheckedModeBanner != null),
+       navigatorObservers = null,
+       navigatorKey = null,
+       onGenerateRoute = null,
+       home = null,
+       onGenerateInitialRoutes = null,
+       onUnknownRoute = null,
+       routes = null,
+       initialRoute = null,
+       super(key: key);
+
+  /// {@macro flutter.widgets.widgetsApp.navigatorKey}
+  final GlobalKey<NavigatorState>? navigatorKey;
+
+  /// A key to use when building the [ScaffoldMessenger].
+  ///
+  /// If a [scaffoldMessengerKey] is specified, the [ScaffoldMessenger] can be
+  /// directly manipulated without first obtaining it from a [BuildContext] via
+  /// [ScaffoldMessenger.of]: from the [scaffoldMessengerKey], use the
+  /// [GlobalKey.currentState] getter.
+  final GlobalKey<ScaffoldMessengerState>? scaffoldMessengerKey;
+
+  /// {@macro flutter.widgets.widgetsApp.home}
+  final Widget? home;
+
+  /// The application's top-level routing table.
+  ///
+  /// When a named route is pushed with [Navigator.pushNamed], the route name is
+  /// looked up in this map. If the name is present, the associated
+  /// [WidgetBuilder] is used to construct a [MaterialPageRoute] that performs
+  /// an appropriate transition, including [Hero] animations, to the new route.
+  ///
+  /// {@macro flutter.widgets.widgetsApp.routes}
+  final Map<String, WidgetBuilder>? routes;
+
+  /// {@macro flutter.widgets.widgetsApp.initialRoute}
+  final String? initialRoute;
+
+  /// {@macro flutter.widgets.widgetsApp.onGenerateRoute}
+  final RouteFactory? onGenerateRoute;
+
+  /// {@macro flutter.widgets.widgetsApp.onGenerateInitialRoutes}
+  final InitialRouteListFactory? onGenerateInitialRoutes;
+
+  /// {@macro flutter.widgets.widgetsApp.onUnknownRoute}
+  final RouteFactory? onUnknownRoute;
+
+  /// {@macro flutter.widgets.widgetsApp.navigatorObservers}
+  final List<NavigatorObserver>? navigatorObservers;
+
+  /// {@macro flutter.widgets.widgetsApp.routeInformationProvider}
+  final RouteInformationProvider? routeInformationProvider;
+
+  /// {@macro flutter.widgets.widgetsApp.routeInformationParser}
+  final RouteInformationParser<Object>? routeInformationParser;
+
+  /// {@macro flutter.widgets.widgetsApp.routerDelegate}
+  final RouterDelegate<Object>? routerDelegate;
+
+  /// {@macro flutter.widgets.widgetsApp.backButtonDispatcher}
+  final BackButtonDispatcher? backButtonDispatcher;
+
+  /// {@macro flutter.widgets.widgetsApp.builder}
+  ///
+  /// Material specific features such as [showDialog] and [showMenu], and widgets
+  /// such as [Tooltip], [PopupMenuButton], also require a [Navigator] to properly
+  /// function.
+  final TransitionBuilder? builder;
+
+  /// {@macro flutter.widgets.widgetsApp.title}
+  ///
+  /// This value is passed unmodified to [WidgetsApp.title].
+  final String title;
+
+  /// {@macro flutter.widgets.widgetsApp.onGenerateTitle}
+  ///
+  /// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
+  final GenerateAppTitle? onGenerateTitle;
+
+  /// Default visual properties, like colors fonts and shapes, for this app's
+  /// material widgets.
+  ///
+  /// A second [darkTheme] [ThemeData] value, which is used to provide a dark
+  /// version of the user interface can also be specified. [themeMode] will
+  /// control which theme will be used if a [darkTheme] is provided.
+  ///
+  /// The default value of this property is the value of [ThemeData.light()].
+  ///
+  /// See also:
+  ///
+  ///  * [themeMode], which controls which theme to use.
+  ///  * [MediaQueryData.platformBrightness], which indicates the platform's
+  ///    desired brightness and is used to automatically toggle between [theme]
+  ///    and [darkTheme] in [MaterialApp].
+  ///  * [ThemeData.brightness], which indicates the [Brightness] of a theme's
+  ///    colors.
+  final ThemeData? theme;
+
+  /// The [ThemeData] to use when a 'dark mode' is requested by the system.
+  ///
+  /// Some host platforms allow the users to select a system-wide 'dark mode',
+  /// or the application may want to offer the user the ability to choose a
+  /// dark theme just for this application. This is theme that will be used for
+  /// such cases. [themeMode] will control which theme will be used.
+  ///
+  /// This theme should have a [ThemeData.brightness] set to [Brightness.dark].
+  ///
+  /// Uses [theme] instead when null. Defaults to the value of
+  /// [ThemeData.light()] when both [darkTheme] and [theme] are null.
+  ///
+  /// See also:
+  ///
+  ///  * [themeMode], which controls which theme to use.
+  ///  * [MediaQueryData.platformBrightness], which indicates the platform's
+  ///    desired brightness and is used to automatically toggle between [theme]
+  ///    and [darkTheme] in [MaterialApp].
+  ///  * [ThemeData.brightness], which is typically set to the value of
+  ///    [MediaQueryData.platformBrightness].
+  final ThemeData? darkTheme;
+
+  /// The [ThemeData] to use when 'high contrast' is requested by the system.
+  ///
+  /// Some host platforms (for example, iOS) allow the users to increase
+  /// contrast through an accessibility setting.
+  ///
+  /// Uses [theme] instead when null.
+  ///
+  /// See also:
+  ///
+  ///  * [MediaQueryData.highContrast], which indicates the platform's
+  ///    desire to increase contrast.
+  final ThemeData? highContrastTheme;
+
+  /// The [ThemeData] to use when a 'dark mode' and 'high contrast' is requested
+  /// by the system.
+  ///
+  /// Some host platforms (for example, iOS) allow the users to increase
+  /// contrast through an accessibility setting.
+  ///
+  /// This theme should have a [ThemeData.brightness] set to [Brightness.dark].
+  ///
+  /// Uses [darkTheme] instead when null.
+  ///
+  /// See also:
+  ///
+  ///  * [MediaQueryData.highContrast], which indicates the platform's
+  ///    desire to increase contrast.
+  final ThemeData? highContrastDarkTheme;
+
+  /// Determines which theme will be used by the application if both [theme]
+  /// and [darkTheme] are provided.
+  ///
+  /// If set to [ThemeMode.system], the choice of which theme to use will
+  /// be based on the user's system preferences. If the [MediaQuery.platformBrightnessOf]
+  /// is [Brightness.light], [theme] will be used. If it is [Brightness.dark],
+  /// [darkTheme] will be used (unless it is null, in which case [theme]
+  /// will be used.
+  ///
+  /// If set to [ThemeMode.light] the [theme] will always be used,
+  /// regardless of the user's system preference.
+  ///
+  /// If set to [ThemeMode.dark] the [darkTheme] will be used
+  /// regardless of the user's system preference. If [darkTheme] is null
+  /// then it will fallback to using [theme].
+  ///
+  /// The default value is [ThemeMode.system].
+  ///
+  /// See also:
+  ///
+  ///  * [theme], which is used when a light mode is selected.
+  ///  * [darkTheme], which is used when a dark mode is selected.
+  ///  * [ThemeData.brightness], which indicates to various parts of the
+  ///    system what kind of theme is being used.
+  final ThemeMode? themeMode;
+
+  /// {@macro flutter.widgets.widgetsApp.color}
+  final Color? color;
+
+  /// {@macro flutter.widgets.widgetsApp.locale}
+  final Locale? locale;
+
+  /// {@macro flutter.widgets.widgetsApp.localizationsDelegates}
+  ///
+  /// Internationalized apps that require translations for one of the locales
+  /// listed in [GlobalMaterialLocalizations] should specify this parameter
+  /// and list the [supportedLocales] that the application can handle.
+  ///
+  /// ```dart
+  /// import 'package:flutter_localizations/flutter_localizations.dart';
+  /// MaterialApp(
+  ///   localizationsDelegates: [
+  ///     // ... app-specific localization delegate[s] here
+  ///     GlobalMaterialLocalizations.delegate,
+  ///     GlobalWidgetsLocalizations.delegate,
+  ///   ],
+  ///   supportedLocales: [
+  ///     const Locale('en', 'US'), // English
+  ///     const Locale('he', 'IL'), // Hebrew
+  ///     // ... other locales the app supports
+  ///   ],
+  ///   // ...
+  /// )
+  /// ```
+  ///
+  /// ## Adding localizations for a new locale
+  ///
+  /// The information that follows applies to the unusual case of an app
+  /// adding translations for a language not already supported by
+  /// [GlobalMaterialLocalizations].
+  ///
+  /// Delegates that produce [WidgetsLocalizations] and [MaterialLocalizations]
+  /// are included automatically. Apps can provide their own versions of these
+  /// localizations by creating implementations of
+  /// [LocalizationsDelegate<WidgetsLocalizations>] or
+  /// [LocalizationsDelegate<MaterialLocalizations>] whose load methods return
+  /// custom versions of [WidgetsLocalizations] or [MaterialLocalizations].
+  ///
+  /// For example: to add support to [MaterialLocalizations] for a
+  /// locale it doesn't already support, say `const Locale('foo', 'BR')`,
+  /// one could just extend [DefaultMaterialLocalizations]:
+  ///
+  /// ```dart
+  /// class FooLocalizations extends DefaultMaterialLocalizations {
+  ///   FooLocalizations(Locale locale) : super(locale);
+  ///   @override
+  ///   String get okButtonLabel {
+  ///     if (locale == const Locale('foo', 'BR'))
+  ///       return 'foo';
+  ///     return super.okButtonLabel;
+  ///   }
+  /// }
+  ///
+  /// ```
+  ///
+  /// A `FooLocalizationsDelegate` is essentially just a method that constructs
+  /// a `FooLocalizations` object. We return a [SynchronousFuture] here because
+  /// no asynchronous work takes place upon "loading" the localizations object.
+  ///
+  /// ```dart
+  /// class FooLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
+  ///   const FooLocalizationsDelegate();
+  ///   @override
+  ///   Future<FooLocalizations> load(Locale locale) {
+  ///     return SynchronousFuture(FooLocalizations(locale));
+  ///   }
+  ///   @override
+  ///   bool shouldReload(FooLocalizationsDelegate old) => false;
+  /// }
+  /// ```
+  ///
+  /// Constructing a [MaterialApp] with a `FooLocalizationsDelegate` overrides
+  /// the automatically included delegate for [MaterialLocalizations] because
+  /// only the first delegate of each [LocalizationsDelegate.type] is used and
+  /// the automatically included delegates are added to the end of the app's
+  /// [localizationsDelegates] list.
+  ///
+  /// ```dart
+  /// MaterialApp(
+  ///   localizationsDelegates: [
+  ///     const FooLocalizationsDelegate(),
+  ///   ],
+  ///   // ...
+  /// )
+  /// ```
+  /// See also:
+  ///
+  ///  * [supportedLocales], which must be specified along with
+  ///    [localizationsDelegates].
+  ///  * [GlobalMaterialLocalizations], a [localizationsDelegates] value
+  ///    which provides material localizations for many languages.
+  ///  * The Flutter Internationalization Tutorial,
+  ///    <https://flutter.dev/tutorials/internationalization/>.
+  final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
+
+  /// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
+  ///
+  /// This callback is passed along to the [WidgetsApp] built by this widget.
+  final LocaleListResolutionCallback? localeListResolutionCallback;
+
+  /// {@macro flutter.widgets.LocaleResolutionCallback}
+  ///
+  /// This callback is passed along to the [WidgetsApp] built by this widget.
+  final LocaleResolutionCallback? localeResolutionCallback;
+
+  /// {@macro flutter.widgets.widgetsApp.supportedLocales}
+  ///
+  /// It is passed along unmodified to the [WidgetsApp] built by this widget.
+  ///
+  /// See also:
+  ///
+  ///  * [localizationsDelegates], which must be specified for localized
+  ///    applications.
+  ///  * [GlobalMaterialLocalizations], a [localizationsDelegates] value
+  ///    which provides material localizations for many languages.
+  ///  * The Flutter Internationalization Tutorial,
+  ///    <https://flutter.dev/tutorials/internationalization/>.
+  final Iterable<Locale> supportedLocales;
+
+  /// Turns on a performance overlay.
+  ///
+  /// See also:
+  ///
+  ///  * <https://flutter.dev/debugging/#performanceoverlay>
+  final bool showPerformanceOverlay;
+
+  /// Turns on checkerboarding of raster cache images.
+  final bool checkerboardRasterCacheImages;
+
+  /// Turns on checkerboarding of layers rendered to offscreen bitmaps.
+  final bool checkerboardOffscreenLayers;
+
+  /// Turns on an overlay that shows the accessibility information
+  /// reported by the framework.
+  final bool showSemanticsDebugger;
+
+  /// {@macro flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
+  final bool debugShowCheckedModeBanner;
+
+  /// {@macro flutter.widgets.widgetsApp.shortcuts}
+  /// {@tool snippet}
+  /// This example shows how to add a single shortcut for
+  /// [LogicalKeyboardKey.select] to the default shortcuts without needing to
+  /// add your own [Shortcuts] widget.
+  ///
+  /// Alternatively, you could insert a [Shortcuts] widget with just the mapping
+  /// you want to add between the [WidgetsApp] and its child and get the same
+  /// effect.
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return WidgetsApp(
+  ///     shortcuts: <LogicalKeySet, Intent>{
+  ///       ... WidgetsApp.defaultShortcuts,
+  ///       LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
+  ///     },
+  ///     color: const Color(0xFFFF0000),
+  ///     builder: (BuildContext context, Widget? child) {
+  ///       return const Placeholder();
+  ///     },
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  /// {@macro flutter.widgets.widgetsApp.shortcuts.seeAlso}
+  final Map<LogicalKeySet, Intent>? shortcuts;
+
+  /// {@macro flutter.widgets.widgetsApp.actions}
+  /// {@tool snippet}
+  /// This example shows how to add a single action handling an
+  /// [ActivateAction] to the default actions without needing to
+  /// add your own [Actions] widget.
+  ///
+  /// Alternatively, you could insert a [Actions] widget with just the mapping
+  /// you want to add between the [WidgetsApp] and its child and get the same
+  /// effect.
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return WidgetsApp(
+  ///     actions: <Type, Action<Intent>>{
+  ///       ... WidgetsApp.defaultActions,
+  ///       ActivateAction: CallbackAction(
+  ///         onInvoke: (Intent intent) {
+  ///           // Do something here...
+  ///           return null;
+  ///         },
+  ///       ),
+  ///     },
+  ///     color: const Color(0xFFFF0000),
+  ///     builder: (BuildContext context, Widget? child) {
+  ///       return const Placeholder();
+  ///     },
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  /// {@macro flutter.widgets.widgetsApp.actions.seeAlso}
+  final Map<Type, Action<Intent>>? actions;
+
+  /// {@macro flutter.widgets.widgetsApp.restorationScopeId}
+  final String? restorationScopeId;
+
+  /// Turns on a [GridPaper] overlay that paints a baseline grid
+  /// Material apps.
+  ///
+  /// Only available in checked mode.
+  ///
+  /// See also:
+  ///
+  ///  * <https://material.io/design/layout/spacing-methods.html>
+  final bool debugShowMaterialGrid;
+
+  @override
+  _MaterialAppState createState() => _MaterialAppState();
+
+  /// The [HeroController] used for Material page transitions.
+  ///
+  /// Used by the [MaterialApp].
+  static HeroController createMaterialHeroController() {
+    return HeroController(
+      createRectTween: (Rect? begin, Rect? end) {
+        return MaterialRectArcTween(begin: begin, end: end);
+      },
+    );
+  }
+}
+
+class _MaterialScrollBehavior extends ScrollBehavior {
+  @override
+  TargetPlatform getPlatform(BuildContext context) {
+    return Theme.of(context).platform;
+  }
+
+  @override
+  Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
+    // When modifying this function, consider modifying the implementation in
+    // the base class as well.
+    switch (getPlatform(context)) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.linux:
+      case TargetPlatform.macOS:
+      case TargetPlatform.windows:
+        return child;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+        return GlowingOverscrollIndicator(
+          child: child,
+          axisDirection: axisDirection,
+          color: Theme.of(context).accentColor,
+        );
+    }
+  }
+}
+
+class _MaterialAppState extends State<MaterialApp> {
+  late HeroController _heroController;
+
+  bool get _usesRouter => widget.routerDelegate != null;
+
+  @override
+  void initState() {
+    super.initState();
+    _heroController = MaterialApp.createMaterialHeroController();
+  }
+
+  // Combine the Localizations for Material with the ones contributed
+  // by the localizationsDelegates parameter, if any. Only the first delegate
+  // of a particular LocalizationsDelegate.type is loaded so the
+  // localizationsDelegate parameter can be used to override
+  // _MaterialLocalizationsDelegate.
+  Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
+    if (widget.localizationsDelegates != null)
+      yield* widget.localizationsDelegates!;
+    yield DefaultMaterialLocalizations.delegate;
+    yield DefaultCupertinoLocalizations.delegate;
+  }
+
+  Widget _inspectorSelectButtonBuilder(BuildContext context, VoidCallback onPressed) {
+    return FloatingActionButton(
+      child: const Icon(Icons.search),
+      onPressed: onPressed,
+      mini: true,
+    );
+  }
+
+  Widget _materialBuilder(BuildContext context, Widget? child) {
+    // Resolve which theme to use based on brightness and high contrast.
+    final ThemeMode mode = widget.themeMode ?? ThemeMode.system;
+    final Brightness platformBrightness = MediaQuery.platformBrightnessOf(context);
+    final bool useDarkTheme = mode == ThemeMode.dark
+      || (mode == ThemeMode.system && platformBrightness == ui.Brightness.dark);
+    final bool highContrast = MediaQuery.highContrastOf(context);
+    ThemeData? theme;
+
+    if (useDarkTheme && highContrast && widget.highContrastDarkTheme != null) {
+      theme = widget.highContrastDarkTheme;
+    } else if (useDarkTheme && widget.darkTheme != null) {
+      theme = widget.darkTheme;
+    } else if (highContrast && widget.highContrastTheme != null) {
+      theme = widget.highContrastTheme;
+    }
+    theme ??= widget.theme ?? ThemeData.light();
+
+    return ScaffoldMessenger(
+      key: widget.scaffoldMessengerKey,
+      child: AnimatedTheme(
+        data: theme,
+        child: widget.builder != null
+          ? Builder(
+              builder: (BuildContext context) {
+                // Why are we surrounding a builder with a builder?
+                //
+                // The widget.builder may contain code that invokes
+                // Theme.of(), which should return the theme we selected
+                // above in AnimatedTheme. However, if we invoke
+                // widget.builder() directly as the child of AnimatedTheme
+                // then there is no Context separating them, and the
+                // widget.builder() will not find the theme. Therefore, we
+                // surround widget.builder with yet another builder so that
+                // a context separates them and Theme.of() correctly
+                // resolves to the theme we passed to AnimatedTheme.
+                return widget.builder!(context, child);
+              },
+            )
+          : child!,
+      )
+    );
+  }
+
+  Widget _buildWidgetApp(BuildContext context) {
+    // The color property is always pulled from the light theme, even if dark
+    // mode is activated. This was done to simplify the technical details
+    // of switching themes and it was deemed acceptable because this color
+    // property is only used on old Android OSes to color the app bar in
+    // Android's switcher UI.
+    //
+    // blue is the primary color of the default theme.
+    final Color materialColor = widget.color ?? widget.theme?.primaryColor ?? Colors.blue;
+    if (_usesRouter) {
+      return WidgetsApp.router(
+        key: GlobalObjectKey(this),
+        routeInformationProvider: widget.routeInformationProvider,
+        routeInformationParser: widget.routeInformationParser!,
+        routerDelegate: widget.routerDelegate!,
+        backButtonDispatcher: widget.backButtonDispatcher,
+        builder: _materialBuilder,
+        title: widget.title,
+        onGenerateTitle: widget.onGenerateTitle,
+        textStyle: _errorTextStyle,
+        color: materialColor,
+        locale: widget.locale,
+        localizationsDelegates: _localizationsDelegates,
+        localeResolutionCallback: widget.localeResolutionCallback,
+        localeListResolutionCallback: widget.localeListResolutionCallback,
+        supportedLocales: widget.supportedLocales,
+        showPerformanceOverlay: widget.showPerformanceOverlay,
+        checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
+        checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
+        showSemanticsDebugger: widget.showSemanticsDebugger,
+        debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
+        inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
+        shortcuts: widget.shortcuts,
+        actions: widget.actions,
+        restorationScopeId: widget.restorationScopeId,
+      );
+    }
+
+    return WidgetsApp(
+      key: GlobalObjectKey(this),
+      navigatorKey: widget.navigatorKey,
+      navigatorObservers: widget.navigatorObservers!,
+      pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
+        return MaterialPageRoute<T>(settings: settings, builder: builder);
+      },
+      home: widget.home,
+      routes: widget.routes!,
+      initialRoute: widget.initialRoute,
+      onGenerateRoute: widget.onGenerateRoute,
+      onGenerateInitialRoutes: widget.onGenerateInitialRoutes,
+      onUnknownRoute: widget.onUnknownRoute,
+      builder: _materialBuilder,
+      title: widget.title,
+      onGenerateTitle: widget.onGenerateTitle,
+      textStyle: _errorTextStyle,
+      color: materialColor,
+      locale: widget.locale,
+      localizationsDelegates: _localizationsDelegates,
+      localeResolutionCallback: widget.localeResolutionCallback,
+      localeListResolutionCallback: widget.localeListResolutionCallback,
+      supportedLocales: widget.supportedLocales,
+      showPerformanceOverlay: widget.showPerformanceOverlay,
+      checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
+      checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
+      showSemanticsDebugger: widget.showSemanticsDebugger,
+      debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
+      inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
+      shortcuts: widget.shortcuts,
+      actions: widget.actions,
+      restorationScopeId: widget.restorationScopeId,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    Widget result = _buildWidgetApp(context);
+
+    assert(() {
+      if (widget.debugShowMaterialGrid) {
+        result = GridPaper(
+          color: const Color(0xE0F9BBE0),
+          interval: 8.0,
+          divisions: 2,
+          subdivisions: 1,
+          child: result,
+        );
+      }
+      return true;
+    }());
+
+    return ScrollConfiguration(
+      behavior: _MaterialScrollBehavior(),
+      child: HeroControllerScope(
+        controller: _heroController,
+        child: result,
+      )
+    );
+  }
+}
diff --git a/lib/src/material/app_bar.dart b/lib/src/material/app_bar.dart
new file mode 100644
index 0000000..2b479ae
--- /dev/null
+++ b/lib/src/material/app_bar.dart
@@ -0,0 +1,1869 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'app_bar_theme.dart';
+import 'back_button.dart';
+import 'color_scheme.dart';
+import 'constants.dart';
+import 'debug.dart';
+import 'flexible_space_bar.dart';
+import 'icon_button.dart';
+import 'icons.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'scaffold.dart';
+import 'tabs.dart';
+import 'text_theme.dart';
+import 'theme.dart';
+
+const double _kLeadingWidth = kToolbarHeight; // So the leading button is square.
+const double _kMaxTitleTextScaleFactor = 1.34; // TODO(perc): Add link to Material spec when available, https://github.com/flutter/flutter/issues/58769.
+
+// Bottom justify the toolbarHeight child which may overflow the top.
+class _ToolbarContainerLayout extends SingleChildLayoutDelegate {
+  const _ToolbarContainerLayout(this.toolbarHeight);
+
+  final double toolbarHeight;
+
+  @override
+  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
+    return constraints.tighten(height: toolbarHeight);
+  }
+
+  @override
+  Size getSize(BoxConstraints constraints) {
+    return Size(constraints.maxWidth, toolbarHeight);
+  }
+
+  @override
+  Offset getPositionForChild(Size size, Size childSize) {
+    return Offset(0.0, size.height - childSize.height);
+  }
+
+  @override
+  bool shouldRelayout(_ToolbarContainerLayout oldDelegate) =>
+      toolbarHeight != oldDelegate.toolbarHeight;
+}
+
+/// A material design app bar.
+///
+/// An app bar consists of a toolbar and potentially other widgets, such as a
+/// [TabBar] and a [FlexibleSpaceBar]. App bars typically expose one or more
+/// common [actions] with [IconButton]s which are optionally followed by a
+/// [PopupMenuButton] for less common operations (sometimes called the "overflow
+/// menu").
+///
+/// App bars are typically used in the [Scaffold.appBar] property, which places
+/// the app bar as a fixed-height widget at the top of the screen. For a scrollable
+/// app bar, see [SliverAppBar], which embeds an [AppBar] in a sliver for use in
+/// a [CustomScrollView].
+///
+/// The AppBar displays the toolbar widgets, [leading], [title], and [actions],
+/// above the [bottom] (if any). The [bottom] is usually used for a [TabBar]. If
+/// a [flexibleSpace] widget is specified then it is stacked behind the toolbar
+/// and the bottom widget. The following diagram shows where each of these slots
+/// appears in the toolbar when the writing language is left-to-right (e.g.
+/// English):
+///
+/// The [AppBar] insets its content based on the ambient [MediaQuery]'s padding,
+/// to avoid system UI intrusions. It's taken care of by [Scaffold] when used in
+/// the [Scaffold.appBar] property. When animating an [AppBar], unexpected
+/// [MediaQuery] changes (as is common in [Hero] animations) may cause the content
+/// to suddenly jump. Wrap the [AppBar] in a [MediaQuery] widget, and adjust its
+/// padding such that the animation is smooth.
+///
+/// ![The leading widget is in the top left, the actions are in the top right,
+/// the title is between them. The bottom is, naturally, at the bottom, and the
+/// flexibleSpace is behind all of them.](https://flutter.github.io/assets-for-api-docs/assets/material/app_bar.png)
+///
+/// If the [leading] widget is omitted, but the [AppBar] is in a [Scaffold] with
+/// a [Drawer], then a button will be inserted to open the drawer. Otherwise, if
+/// the nearest [Navigator] has any previous routes, a [BackButton] is inserted
+/// instead. This behavior can be turned off by setting the [automaticallyImplyLeading]
+/// to false. In that case a null leading widget will result in the middle/title widget
+/// stretching to start.
+///
+/// {@tool dartpad --template=stateless_widget_material}
+///
+/// This sample shows an [AppBar] with two simple actions. The first action
+/// opens a [SnackBar], while the second action navigates to a new page.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: AppBar(
+///       title: const Text('AppBar Demo'),
+///       actions: <Widget>[
+///         IconButton(
+///           icon: const Icon(Icons.add_alert),
+///           tooltip: 'Show Snackbar',
+///           onPressed: () {
+///             ScaffoldMessenger.of(context).showSnackBar(
+///               const SnackBar(content: Text('This is a snackbar'))
+///             );
+///           },
+///         ),
+///         IconButton(
+///           icon: const Icon(Icons.navigate_next),
+///           tooltip: 'Go to the next page',
+///           onPressed: () {
+///             Navigator.push(context, MaterialPageRoute(
+///               builder: (BuildContext context) {
+///                 return Scaffold(
+///                   appBar: AppBar(
+///                     title: const Text('Next page'),
+///                   ),
+///                   body: const Center(
+///                     child: Text(
+///                       'This is the next page',
+///                       style: TextStyle(fontSize: 24),
+///                     ),
+///                   ),
+///                 );
+///               },
+///             ));
+///           },
+///         ),
+///       ],
+///     ),
+///     body: const Center(
+///       child: Text(
+///         'This is the home page',
+///         style: TextStyle(fontSize: 24),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Scaffold], which displays the [AppBar] in its [Scaffold.appBar] slot.
+///  * [SliverAppBar], which uses [AppBar] to provide a flexible app bar that
+///    can be used in a [CustomScrollView].
+///  * [TabBar], which is typically placed in the [bottom] slot of the [AppBar]
+///    if the screen has multiple pages arranged in tabs.
+///  * [IconButton], which is used with [actions] to show buttons on the app bar.
+///  * [PopupMenuButton], to show a popup menu on the app bar, via [actions].
+///  * [FlexibleSpaceBar], which is used with [flexibleSpace] when the app bar
+///    can expand and collapse.
+///  * <https://material.io/design/components/app-bars-top.html>
+///  * Cookbook: [Place a floating app bar above a list](https://flutter.dev/docs/cookbook/lists/floating-app-bar)
+class AppBar extends StatefulWidget implements PreferredSizeWidget {
+  /// Creates a material design app bar.
+  ///
+  /// The arguments [primary], [toolbarOpacity], [bottomOpacity],
+  /// [backwardsCompatibility], and [automaticallyImplyLeading] must
+  /// not be null. Additionally, if [elevation] is specified, it must
+  /// be non-negative.
+  ///
+  /// Typically used in the [Scaffold.appBar] property.
+  AppBar({
+    Key? key,
+    this.leading,
+    this.automaticallyImplyLeading = true,
+    this.title,
+    this.actions,
+    this.flexibleSpace,
+    this.bottom,
+    this.elevation,
+    this.shadowColor,
+    this.shape,
+    this.backgroundColor,
+    this.foregroundColor,
+    this.brightness,
+    this.iconTheme,
+    this.actionsIconTheme,
+    this.textTheme,
+    this.primary = true,
+    this.centerTitle,
+    this.excludeHeaderSemantics = false,
+    this.titleSpacing,
+    this.toolbarOpacity = 1.0,
+    this.bottomOpacity = 1.0,
+    this.toolbarHeight,
+    this.leadingWidth,
+    this.backwardsCompatibility = true,
+    this.toolbarTextStyle,
+    this.titleTextStyle,
+    this.systemOverlayStyle,
+  }) : assert(automaticallyImplyLeading != null),
+       assert(elevation == null || elevation >= 0.0),
+       assert(primary != null),
+       assert(toolbarOpacity != null),
+       assert(bottomOpacity != null),
+       assert(backwardsCompatibility != null),
+       preferredSize = Size.fromHeight(toolbarHeight ?? kToolbarHeight + (bottom?.preferredSize.height ?? 0.0)),
+       super(key: key);
+
+  /// {@template flutter.material.appbar.leading}
+  /// A widget to display before the toolbar's [title].
+  ///
+  /// Typically the [leading] widget is an [Icon] or an [IconButton].
+  ///
+  /// Becomes the leading component of the [NavigationToolbar] built
+  /// by this widget. The [leading] widget's width and height are constrained to
+  /// be no bigger than [leadingWidth] and [toolbarHeight] respectively.
+  ///
+  /// If this is null and [automaticallyImplyLeading] is set to true, the
+  /// [AppBar] will imply an appropriate widget. For example, if the [AppBar] is
+  /// in a [Scaffold] that also has a [Drawer], the [Scaffold] will fill this
+  /// widget with an [IconButton] that opens the drawer (using [Icons.menu]). If
+  /// there's no [Drawer] and the parent [Navigator] can go back, the [AppBar]
+  /// will use a [BackButton] that calls [Navigator.maybePop].
+  /// {@endtemplate}
+  ///
+  /// {@tool snippet}
+  ///
+  /// The following code shows how the drawer button could be manually specified
+  /// instead of relying on [automaticallyImplyLeading]:
+  ///
+  /// ```dart
+  /// AppBar(
+  ///   leading: Builder(
+  ///     builder: (BuildContext context) {
+  ///       return IconButton(
+  ///         icon: const Icon(Icons.menu),
+  ///         onPressed: () { Scaffold.of(context).openDrawer(); },
+  ///         tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
+  ///       );
+  ///     },
+  ///   ),
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// The [Builder] is used in this example to ensure that the `context` refers
+  /// to that part of the subtree. That way this code snippet can be used even
+  /// inside the very code that is creating the [Scaffold] (in which case,
+  /// without the [Builder], the `context` wouldn't be able to see the
+  /// [Scaffold], since it would refer to an ancestor of that widget).
+  ///
+  /// See also:
+  ///
+  ///  * [Scaffold.appBar], in which an [AppBar] is usually placed.
+  ///  * [Scaffold.drawer], in which the [Drawer] is usually placed.
+  final Widget? leading;
+
+  /// {@template flutter.material.appbar.automaticallyImplyLeading}
+  /// Controls whether we should try to imply the leading widget if null.
+  ///
+  /// If true and [leading] is null, automatically try to deduce what the leading
+  /// widget should be. If false and [leading] is null, leading space is given to [title].
+  /// If leading widget is not null, this parameter has no effect.
+  /// {@endtemplate}
+  final bool automaticallyImplyLeading;
+
+  /// {@template flutter.material.appbar.title}
+  /// The primary widget displayed in the app bar.
+  ///
+  /// Becomes the middle component of the [NavigationToolbar] built by this widget.
+  //.
+  /// Typically a [Text] widget that contains a description of the current
+  /// contents of the app.
+  /// {@endtemplate}
+  ///
+  /// The [title]'s width is constrained to fit within the remaining space
+  /// between the toolbar's [leading] and [actions] widgets. Its height is
+  /// _not_ constrained. The [title] is vertically centered and clipped to fit
+  /// within the toolbar, whose height is [toolbarHeight]. Typically this
+  /// isn't noticeable because a simple [Text] [title] will fit within the
+  /// toolbar by default. On the other hand, it is noticeable when a
+  /// widget with an intrinsic height that is greater than [toolbarHeight]
+  /// is used as the [title]. For example, when the height of an Image used
+  /// as the [title] exceeds [toolbarHeight], it will be centered and
+  /// clipped (top and bottom), which may be undesirable. In cases like this
+  /// the height of the [title] widget can be constrained. For example:
+  ///
+  /// ```dart
+  /// MaterialApp(
+  ///   home: Scaffold(
+  ///     appBar: AppBar(
+  ///        title: SizedBox(
+  ///        height: toolbarHeight,
+  ///        child: child: Image.asset(logoAsset),
+  ///      ),
+  ///      toolbarHeight: toolbarHeight,
+  ///   ),
+  /// )
+  /// ```
+  final Widget? title;
+
+  /// {@template flutter.material.appbar.actions}
+  /// A list of Widgets to display in a row after the [title] widget.
+  ///
+  /// Typically these widgets are [IconButton]s representing common operations.
+  /// For less common operations, consider using a [PopupMenuButton] as the
+  /// last action.
+  ///
+  /// The [actions] become the trailing component of the [NavigationToolbar] built
+  /// by this widget. The height of each action is constrained to be no bigger
+  /// than the [toolbarHeight].
+  /// {@endtemplate}
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Scaffold(
+  ///   body: CustomScrollView(
+  ///     primary: true,
+  ///     slivers: <Widget>[
+  ///       SliverAppBar(
+  ///         title: Text('Hello World'),
+  ///         actions: <Widget>[
+  ///           IconButton(
+  ///             icon: Icon(Icons.shopping_cart),
+  ///             tooltip: 'Open shopping cart',
+  ///             onPressed: () {
+  ///               // handle the press
+  ///             },
+  ///           ),
+  ///         ],
+  ///       ),
+  ///       // ...rest of body...
+  ///     ],
+  ///   ),
+  /// )
+  /// ```
+  /// {@end-tool}
+  final List<Widget>? actions;
+
+  /// {@template flutter.material.appbar.flexibleSpace}
+  /// This widget is stacked behind the toolbar and the tab bar. Its height will
+  /// be the same as the app bar's overall height.
+  ///
+  /// A flexible space isn't actually flexible unless the [AppBar]'s container
+  /// changes the [AppBar]'s size. A [SliverAppBar] in a [CustomScrollView]
+  /// changes the [AppBar]'s height when scrolled.
+  ///
+  /// Typically a [FlexibleSpaceBar]. See [FlexibleSpaceBar] for details.
+  /// {@endtemplate}
+  final Widget? flexibleSpace;
+
+  /// {@template flutter.material.appbar.bottom}
+  /// This widget appears across the bottom of the app bar.
+  ///
+  /// Typically a [TabBar]. Only widgets that implement [PreferredSizeWidget] can
+  /// be used at the bottom of an app bar.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [PreferredSize], which can be used to give an arbitrary widget a preferred size.
+  final PreferredSizeWidget? bottom;
+
+  /// {@template flutter.material.appbar.elevation}
+  /// The z-coordinate at which to place this app bar relative to its parent.
+  ///
+  /// This property controls the size of the shadow below the app bar.
+  ///
+  /// The value must be non-negative.
+  ///
+  /// If this property is null, then [AppBarTheme.elevation] of
+  /// [ThemeData.appBarTheme] is used. If that is also null, the
+  /// default value is 4.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [shadowColor], which is the color of the shadow below the app bar.
+  ///  * [shape], which defines the shape of the app bar's [Material] and its
+  ///    shadow.
+  final double? elevation;
+
+  /// {@template flutter.material.appbar.shadowColor}
+  /// The of the shadow below the app bar.
+  ///
+  /// If this property is null, then [AppBarTheme.shadowColor] of
+  /// [ThemeData.appBarTheme] is used. If that is also null, the default value
+  /// is fully opaque black.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], which defines the size of the shadow below the app bar.
+  ///  * [shape], which defines the shape of the app bar and its shadow.
+  final Color? shadowColor;
+
+  /// {@template flutter.material.appbar.shape}
+  /// The shape of the app bar's material's shape as well as its shadow.
+  ///
+  /// A shadow is only displayed if the [elevation] is greater than
+  /// zero.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], which defines the size of the shadow below the app bar.
+  ///  * [shadowColor], which is the color of the shadow below the app bar.
+  final ShapeBorder? shape;
+
+  /// {@template flutter.material.appbar.backgroundColor}
+  /// The fill color to use for an app bar's [Material].
+  ///
+  /// If null, then the [AppBarTheme.backgroundColor] is used. If that value is also
+  /// null, then [AppBar] uses the overall theme's [ColorScheme.primary] if the
+  /// overall theme's brightness is [Brightness.light], and [ColorScheme.surface]
+  /// if the overall theme's [brightness] is [Brightness.dark].
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [foregroundColor], which specifies the color for icons and text within
+  ///    the app bar.
+  ///  * [Theme.of], which returns the current overall Material theme as
+  ///    a [ThemeData].
+  ///  * [ThemeData.colorScheme], the thirteen colors that most Material widget
+  ///    default colors are based on.
+  ///  * [ColorScheme.brightness], which indicates if the overall [Theme]
+  ///    is light or dark.
+  final Color? backgroundColor;
+
+  /// {@template flutter.material.appbar.foregroundColor}
+  /// The default color for [Text] and [Icon]s within the app bar.
+  ///
+  /// If null, then [AppBarTheme.foregroundColor] is used. If that
+  /// value is also null, then [AppBar] uses the overall theme's
+  /// [ColorScheme.onPrimary] if the overall theme's brightness is
+  /// [Brightness.light], and [ColorScheme.onSurface] if the overall
+  /// theme's [brightness] is [Brightness.dark].
+  ///
+  /// This color is used to configure [DefaultTextStyle] that contains
+  /// the toolbar's children, and the default [IconTheme] widgets that
+  /// are created if [iconTheme] and [actionsIconTheme] are null.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [backgroundColor], which specifies the app bar's background color.
+  ///  * [Theme.of], which returns the current overall Material theme as
+  ///    a [ThemeData].
+  ///  * [ThemeData.colorScheme], the thirteen colors that most Material widget
+  ///    default colors are based on.
+  ///  * [ColorScheme.brightness], which indicates if the overall [Theme]
+  ///    is light or dark.
+  final Color? foregroundColor;
+
+  /// {@template flutter.material.appbar.brightness}
+  /// This property is obsolete, please use [systemOverlayStyle] instead.
+  ///
+  /// Determines the brightness of the [SystemUiOverlayStyle]: for
+  /// [Brightness.dark], [SystemUiOverlayStyle.light] is used and fo
+  /// [Brightness.light], [SystemUiOverlayStyle.dark] is used.
+  ///
+  /// If this value is null then [AppBarTheme.brightness] is used
+  /// and if that's null then overall theme's brightness is used.
+  ///
+  /// The AppBar is built within a `AnnotatedRegion<SystemUiOverlayStyle>`
+  /// which causes [SystemChrome.setSystemUIOverlayStyle] to be called
+  /// automatically.  Apps should not enclose the AppBar with
+  /// their own [AnnotatedRegion].
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [Theme.of], which returns the current overall Material theme as
+  ///    a [ThemeData].
+  ///  * [ThemeData.colorScheme], the thirteen colors that most Material widget
+  ///    default colors are based on.
+  ///  * [ColorScheme.brightness], which indicates if the overall [Theme]
+  ///    is light or dark.
+  ///  * [backwardsCompatibility], which forces AppBar to use this
+  ///    obsolete property.
+  final Brightness? brightness;
+
+  /// {@template flutter.material.appbar.iconTheme}
+  /// The color, opacity, and size to use for toolbar icons.
+  ///
+  /// If this property is null, then a copy of [ThemeData.iconTheme]
+  /// is used, with the [IconThemeData.color] set to the
+  /// app bar's [foregroundColor].
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [actionsIconTheme], which defines the appearance of icons in
+  ///    in the [actions] list.
+  final IconThemeData? iconTheme;
+
+  /// {@template flutter.material.appbar.actionsIconTheme}
+  /// The color, opacity, and size to use for the icons that appear in the app
+  /// bar's [actions].
+  ///
+  /// This property should only be used when the [actions] should be
+  /// themed differently than the icon that appears in the app bar's [leading]
+  /// widget.
+  ///
+  /// If this property is null, then [AppBarTheme.actionsIconTheme] of
+  /// [ThemeData.appBarTheme] is used. If that is also null, then the value of
+  /// [iconTheme] is used.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [iconTheme], which defines the appearance of all of the toolbar icons.
+  final IconThemeData? actionsIconTheme;
+
+  /// {@template flutter.material.appbar.textTheme}
+  /// The typographic styles to use for text in the app bar. Typically this is
+  /// set along with [brightness] [backgroundColor], [iconTheme].
+  ///
+  /// If this property is null, then [AppBarTheme.textTheme] of
+  /// [ThemeData.appBarTheme] is used. If that is also null, then
+  /// [ThemeData.primaryTextTheme] is used.
+  /// {@endtemplate}
+  final TextTheme? textTheme;
+
+  /// {@template flutter.material.appbar.primary}
+  /// Whether this app bar is being displayed at the top of the screen.
+  ///
+  /// If true, the app bar's toolbar elements and [bottom] widget will be
+  /// padded on top by the height of the system status bar. The layout
+  /// of the [flexibleSpace] is not affected by the [primary] property.
+  /// {@endtemplate}
+  final bool primary;
+
+  /// {@template flutter.material.appbar.centerTitle}
+  /// Whether the title should be centered.
+  ///
+  /// If this property is null, then [AppBarTheme.centerTitle] of
+  /// [ThemeData.appBarTheme] is used. If that is also null, then value is
+  /// adapted to the current [TargetPlatform].
+  /// {@endtemplate}
+  final bool? centerTitle;
+
+  /// {@template flutter.material.appbar.excludeHeaderSemantics}
+  /// Whether the title should be wrapped with header [Semantics].
+  ///
+  /// Defaults to false.
+  /// {@endtemplate}
+  final bool excludeHeaderSemantics;
+
+  /// {@template flutter.material.appbar.titleSpacing}
+  /// The spacing around [title] content on the horizontal axis. This spacing is
+  /// applied even if there is no [leading] content or [actions]. If you want
+  /// [title] to take all the space available, set this value to 0.0.
+  ///
+  /// If this property is null, then [AppBarTheme.titleSpacing] of
+  /// [ThemeData.appBarTheme] is used. If that is also null, then the
+  /// default value is [NavigationToolbar.kMiddleSpacing].
+  /// {@endtemplate}
+  final double? titleSpacing;
+
+  /// {@template flutter.material.appbar.toolbarOpacity}
+  /// How opaque the toolbar part of the app bar is.
+  ///
+  /// A value of 1.0 is fully opaque, and a value of 0.0 is fully transparent.
+  ///
+  /// Typically, this value is not changed from its default value (1.0). It is
+  /// used by [SliverAppBar] to animate the opacity of the toolbar when the app
+  /// bar is scrolled.
+  /// {@endtemplate}
+  final double toolbarOpacity;
+
+  /// {@template flutter.material.appbar.bottomOpacity}
+  /// How opaque the bottom part of the app bar is.
+  ///
+  /// A value of 1.0 is fully opaque, and a value of 0.0 is fully transparent.
+  ///
+  /// Typically, this value is not changed from its default value (1.0). It is
+  /// used by [SliverAppBar] to animate the opacity of the toolbar when the app
+  /// bar is scrolled.
+  /// {@endtemplate}
+  final double bottomOpacity;
+
+  /// {@template flutter.material.appbar.preferredSize}
+  /// A size whose height is the sum of [toolbarHeight] and the [bottom] widget's
+  /// preferred height.
+  ///
+  /// [Scaffold] uses this size to set its app bar's height.
+  /// {@endtemplate}
+  @override
+  final Size preferredSize;
+
+  /// {@template flutter.material.appbar.toolbarHeight}
+  /// Defines the height of the toolbar component of an [AppBar].
+  ///
+  /// By default, the value of `toolbarHeight` is [kToolbarHeight].
+  /// {@endtemplate}
+  final double? toolbarHeight;
+
+  /// {@template flutter.material.appbar.leadingWidth}
+  /// Defines the width of [leading] widget.
+  ///
+  /// By default, the value of `leadingWidth` is 56.0.
+  /// {@endtemplate}
+  final double? leadingWidth;
+
+  /// {@template flutter.material.appbar.backwardsCompatibility}
+  /// If true, preserves the original defaults for the [backgroundColor],
+  /// [iconTheme], [actionsIconTheme] properties, and the original use of
+  /// the [textTheme] and [brightness] properties.
+  ///
+  /// This property is true by default.
+  ///
+  /// This is a temporary property. When setting it to false is no
+  /// longer considereed a breaking change, it will be depreacted and
+  /// its default value will be changed to false. App developers are
+  /// encouraged to opt into the new features by setting it to false
+  /// and using the [foregroundColor] and [systemOverlayStyle]
+  /// properties as needed.
+  /// {@endtemplate}
+  final bool backwardsCompatibility;
+
+  /// {@template flutter.material.appbar.toolbarTextStyle}
+  /// The default text style for the AppBar's [leading], and
+  /// [actions] widgets, but not its [title].
+  ///
+  /// If this property is null, then [AppBarTheme.toolbarTextStyle] of
+  /// [ThemeData.appBarTheme] is used. If that is also null, the default
+  /// value is a copy of the overall theme's [TextTheme.bodyText2]
+  /// [TextStyle], with color set to the app bar's [foregroundColor].
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [titleTextStyle], which overrides the default text style for the [title].
+  ///  * [DefaultTextStyle], which overrides the default text style for all of the
+  ///    the widgets in a subtree.
+  final TextStyle? toolbarTextStyle;
+
+  /// {@template flutter.material.appbar.titleTextStyle}
+  /// The default text style for the AppBar's [title] widget.
+  ///
+  /// If this property is null, then [AppBarTheme.titleTextStyle] of
+  /// [ThemeData.appBarTheme] is used. If that is also null, the default
+  /// value is a copy of the overall theme's [TextTheme.headline6]
+  /// [TextStyle], with color set to the app bar's [foregroundColor].
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [toolbarTextStyle], which is the default text style for the AppBar's
+  ///    [title], [leading], and [actions] widgets, also known as the
+  ///    AppBar's "toolbar".
+  ///  * [DefaultTextStyle], which overrides the default text style for all of the
+  ///    the widgets in a subtree.
+  final TextStyle? titleTextStyle;
+
+  /// {@template flutter.material.appbar.systemOverlayStyle}
+  /// Specifies the style to use for the system overlays that overlap the AppBar.
+  ///
+  /// If this property is null, then [SystemUiOverlayStyle.light] is used if the
+  /// overall theme is dark, [SystemUiOverlayStyle.dark] otherwise. Theme brightness
+  /// is defined by [ColorScheme.brightness] for [ThemeData.colorScheme].
+  ///
+  /// The AppBar's descendants are built within a
+  /// `AnnotatedRegion<SystemUiOverlayStyle>` widget, which causes
+  /// [SystemChrome.setSystemUIOverlayStyle] to be called
+  /// automatically.  Apps should not enclose an AppBar with their
+  /// own [AnnotatedRegion].
+  /// {@endtemplate}
+  //
+  /// See also:
+  ///  * [SystemChrome.setSystemUIOverlayStyle]
+  final SystemUiOverlayStyle? systemOverlayStyle;
+
+
+  bool _getEffectiveCenterTitle(ThemeData theme) {
+    if (centerTitle != null)
+      return centerTitle!;
+    if (theme.appBarTheme.centerTitle != null)
+      return theme.appBarTheme.centerTitle!;
+    assert(theme.platform != null);
+    switch (theme.platform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        return false;
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        return actions == null || actions!.length < 2;
+    }
+  }
+
+  @override
+  _AppBarState createState() => _AppBarState();
+}
+
+class _AppBarState extends State<AppBar> {
+  static const double _defaultElevation = 4.0;
+  static const Color _defaultShadowColor = Color(0xFF000000);
+
+  void _handleDrawerButton() {
+    Scaffold.of(context).openDrawer();
+  }
+
+  void _handleDrawerButtonEnd() {
+    Scaffold.of(context).openEndDrawer();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(!widget.primary || debugCheckHasMediaQuery(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+    final ThemeData theme = Theme.of(context);
+    final ColorScheme colorScheme = theme.colorScheme;
+    final AppBarTheme appBarTheme = AppBarTheme.of(context);
+    final ScaffoldState? scaffold = Scaffold.maybeOf(context);
+    final ModalRoute<dynamic>? parentRoute = ModalRoute.of(context);
+
+    final bool hasDrawer = scaffold?.hasDrawer ?? false;
+    final bool hasEndDrawer = scaffold?.hasEndDrawer ?? false;
+    final bool canPop = parentRoute?.canPop ?? false;
+    final bool useCloseButton = parentRoute is PageRoute<dynamic> && parentRoute.fullscreenDialog;
+
+    final double toolbarHeight = widget.toolbarHeight ?? kToolbarHeight;
+
+    final Color backgroundColor = widget.backwardsCompatibility
+      ? widget.backgroundColor
+        ?? appBarTheme.color
+        ?? theme.primaryColor
+      : widget.backgroundColor
+        ?? appBarTheme.backgroundColor
+        ?? (colorScheme.brightness == Brightness.dark ? colorScheme.surface : colorScheme.primary);
+
+    final Color foregroundColor = widget.foregroundColor
+      ?? appBarTheme.foregroundColor
+      ?? (colorScheme.brightness == Brightness.dark ? colorScheme.onSurface : colorScheme.onPrimary);
+
+    IconThemeData overallIconTheme = widget.backwardsCompatibility
+      ? widget.iconTheme
+        ?? appBarTheme.iconTheme
+        ?? theme.primaryIconTheme
+      : widget.iconTheme
+        ?? appBarTheme.iconTheme
+        ?? theme.iconTheme.copyWith(color: foregroundColor);
+
+    IconThemeData actionsIconTheme = widget.actionsIconTheme
+      ?? appBarTheme.actionsIconTheme
+      ?? overallIconTheme;
+
+    TextStyle? toolbarTextStyle = widget.backwardsCompatibility
+      ? widget.textTheme?.bodyText2
+        ?? appBarTheme.textTheme?.bodyText2
+        ?? theme.primaryTextTheme.bodyText2
+      : widget.toolbarTextStyle
+        ?? appBarTheme.toolbarTextStyle
+        ?? theme.textTheme.bodyText2?.copyWith(color: foregroundColor);
+
+    TextStyle? titleTextStyle = widget.backwardsCompatibility
+      ? widget.textTheme?.headline6
+        ?? appBarTheme.textTheme?.headline6
+        ?? theme.primaryTextTheme.headline6
+      : widget.titleTextStyle
+        ?? appBarTheme.titleTextStyle
+        ?? theme.textTheme.headline6?.copyWith(color: foregroundColor);
+
+    if (widget.toolbarOpacity != 1.0) {
+      final double opacity = const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn).transform(widget.toolbarOpacity);
+      if (titleTextStyle?.color != null)
+        titleTextStyle = titleTextStyle!.copyWith(color: titleTextStyle.color!.withOpacity(opacity));
+      if (toolbarTextStyle?.color != null)
+        toolbarTextStyle = toolbarTextStyle!.copyWith(color: toolbarTextStyle.color!.withOpacity(opacity));
+      overallIconTheme = overallIconTheme.copyWith(
+        opacity: opacity * (overallIconTheme.opacity ?? 1.0),
+      );
+      actionsIconTheme = actionsIconTheme.copyWith(
+        opacity: opacity * (actionsIconTheme.opacity ?? 1.0),
+      );
+    }
+
+    Widget? leading = widget.leading;
+    if (leading == null && widget.automaticallyImplyLeading) {
+      if (hasDrawer) {
+        leading = IconButton(
+          icon: const Icon(Icons.menu),
+          onPressed: _handleDrawerButton,
+          tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
+        );
+      } else {
+        if (!hasEndDrawer && canPop)
+          leading = useCloseButton ? const CloseButton() : const BackButton();
+      }
+    }
+    if (leading != null) {
+      leading = ConstrainedBox(
+        constraints: BoxConstraints.tightFor(width: widget.leadingWidth ?? _kLeadingWidth),
+        child: leading,
+      );
+    }
+
+    Widget? title = widget.title;
+    if (title != null) {
+      bool? namesRoute;
+      switch (theme.platform) {
+        case TargetPlatform.android:
+        case TargetPlatform.fuchsia:
+        case TargetPlatform.linux:
+        case TargetPlatform.windows:
+          namesRoute = true;
+          break;
+        case TargetPlatform.iOS:
+        case TargetPlatform.macOS:
+          break;
+      }
+
+      title = _AppBarTitleBox(child: title);
+      if (!widget.excludeHeaderSemantics) {
+        title = Semantics(
+          namesRoute: namesRoute,
+          child: title,
+          header: true,
+        );
+      }
+
+      title = DefaultTextStyle(
+        style: titleTextStyle!,
+        softWrap: false,
+        overflow: TextOverflow.ellipsis,
+        child: title,
+      );
+
+      // Set maximum text scale factor to [_kMaxTitleTextScaleFactor] for the
+      // title to keep the visual hierarchy the same even with larger font
+      // sizes. To opt out, wrap the [title] widget in a [MediaQuery] widget
+      // with [MediaQueryData.textScaleFactor] set to
+      // `MediaQuery.textScaleFactorOf(context)`.
+      final MediaQueryData mediaQueryData = MediaQuery.of(context);
+      title = MediaQuery(
+        data: mediaQueryData.copyWith(
+          textScaleFactor: math.min(
+            mediaQueryData.textScaleFactor,
+            _kMaxTitleTextScaleFactor,
+          ),
+        ),
+        child: title,
+      );
+    }
+
+    Widget? actions;
+    if (widget.actions != null && widget.actions!.isNotEmpty) {
+      actions = Row(
+        mainAxisSize: MainAxisSize.min,
+        crossAxisAlignment: CrossAxisAlignment.stretch,
+        children: widget.actions!,
+      );
+    } else if (hasEndDrawer) {
+      actions = IconButton(
+        icon: const Icon(Icons.menu),
+        onPressed: _handleDrawerButtonEnd,
+        tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
+      );
+    }
+
+    // Allow the trailing actions to have their own theme if necessary.
+    if (actions != null) {
+      actions = IconTheme.merge(
+        data: actionsIconTheme,
+        child: actions,
+      );
+    }
+
+    final Widget toolbar = NavigationToolbar(
+      leading: leading,
+      middle: title,
+      trailing: actions,
+      centerMiddle: widget._getEffectiveCenterTitle(theme),
+      middleSpacing: widget.titleSpacing ?? appBarTheme.titleSpacing ?? NavigationToolbar.kMiddleSpacing,
+    );
+
+    // If the toolbar is allocated less than toolbarHeight make it
+    // appear to scroll upwards within its shrinking container.
+    Widget appBar = ClipRect(
+      child: CustomSingleChildLayout(
+        delegate: _ToolbarContainerLayout(toolbarHeight),
+        child: IconTheme.merge(
+          data: overallIconTheme,
+          child: DefaultTextStyle(
+            style: toolbarTextStyle!,
+            child: toolbar,
+          ),
+        ),
+      ),
+    );
+    if (widget.bottom != null) {
+      appBar = Column(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: <Widget>[
+          Flexible(
+            child: ConstrainedBox(
+              constraints: BoxConstraints(maxHeight: toolbarHeight),
+              child: appBar,
+            ),
+          ),
+          if (widget.bottomOpacity == 1.0)
+            widget.bottom!
+          else
+            Opacity(
+              opacity: const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn).transform(widget.bottomOpacity),
+              child: widget.bottom,
+            ),
+        ],
+      );
+    }
+
+    // The padding applies to the toolbar and tabbar, not the flexible space.
+    if (widget.primary) {
+      appBar = SafeArea(
+        bottom: false,
+        top: true,
+        child: appBar,
+      );
+    }
+
+    appBar = Align(
+      alignment: Alignment.topCenter,
+      child: appBar,
+    );
+
+    if (widget.flexibleSpace != null) {
+      appBar = Stack(
+        fit: StackFit.passthrough,
+        children: <Widget>[
+          Semantics(
+            sortKey: const OrdinalSortKey(1.0),
+            explicitChildNodes: true,
+            child: widget.flexibleSpace,
+          ),
+          Semantics(
+            sortKey: const OrdinalSortKey(0.0),
+            explicitChildNodes: true,
+            // Creates a material widget to prevent the flexibleSpace from
+            // obscuring the ink splashes produced by appBar children.
+            child: Material(
+              type: MaterialType.transparency,
+              child: appBar,
+            ),
+          ),
+        ],
+      );
+    }
+
+    final Brightness overlayStyleBrightness = widget.brightness ?? appBarTheme.brightness ?? colorScheme.brightness;
+    final SystemUiOverlayStyle overlayStyle = widget.backwardsCompatibility
+      ? (overlayStyleBrightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark)
+      : widget.systemOverlayStyle
+        ?? appBarTheme.systemOverlayStyle
+        ?? (colorScheme.brightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark);
+
+    return Semantics(
+      container: true,
+      child: AnnotatedRegion<SystemUiOverlayStyle>(
+        value: overlayStyle,
+        child: Material(
+          color: backgroundColor,
+          elevation: widget.elevation
+            ?? appBarTheme.elevation
+            ?? _defaultElevation,
+          shadowColor: widget.shadowColor
+            ?? appBarTheme.shadowColor
+            ?? _defaultShadowColor,
+          shape: widget.shape,
+          child: Semantics(
+            explicitChildNodes: true,
+            child: appBar,
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+class _FloatingAppBar extends StatefulWidget {
+  const _FloatingAppBar({ Key? key, required this.child }) : super(key: key);
+
+  final Widget child;
+
+  @override
+  _FloatingAppBarState createState() => _FloatingAppBarState();
+}
+
+// A wrapper for the widget created by _SliverAppBarDelegate that starts and
+// stops the floating app bar's snap-into-view or snap-out-of-view animation.
+class _FloatingAppBarState extends State<_FloatingAppBar> {
+  ScrollPosition? _position;
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    if (_position != null)
+      _position!.isScrollingNotifier.removeListener(_isScrollingListener);
+    _position = Scrollable.of(context)?.position;
+    if (_position != null)
+      _position!.isScrollingNotifier.addListener(_isScrollingListener);
+  }
+
+  @override
+  void dispose() {
+    if (_position != null)
+      _position!.isScrollingNotifier.removeListener(_isScrollingListener);
+    super.dispose();
+  }
+
+  RenderSliverFloatingPersistentHeader? _headerRenderer() {
+    return context.findAncestorRenderObjectOfType<RenderSliverFloatingPersistentHeader>();
+  }
+
+  void _isScrollingListener() {
+    if (_position == null)
+      return;
+
+    // When a scroll stops, then maybe snap the appbar into view.
+    // Similarly, when a scroll starts, then maybe stop the snap animation.
+    final RenderSliverFloatingPersistentHeader? header = _headerRenderer();
+    if (_position!.isScrollingNotifier.value)
+      header?.maybeStopSnapAnimation(_position!.userScrollDirection);
+    else
+      header?.maybeStartSnapAnimation(_position!.userScrollDirection);
+  }
+
+  @override
+  Widget build(BuildContext context) => widget.child;
+}
+
+class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
+  _SliverAppBarDelegate({
+    required this.leading,
+    required this.automaticallyImplyLeading,
+    required this.title,
+    required this.actions,
+    required this.flexibleSpace,
+    required this.bottom,
+    required this.elevation,
+    required this.shadowColor,
+    required this.forceElevated,
+    required this.backgroundColor,
+    required this.foregroundColor,
+    required this.brightness,
+    required this.iconTheme,
+    required this.actionsIconTheme,
+    required this.textTheme,
+    required this.primary,
+    required this.centerTitle,
+    required this.excludeHeaderSemantics,
+    required this.titleSpacing,
+    required this.expandedHeight,
+    required this.collapsedHeight,
+    required this.topPadding,
+    required this.floating,
+    required this.pinned,
+    required this.vsync,
+    required this.snapConfiguration,
+    required this.stretchConfiguration,
+    required this.showOnScreenConfiguration,
+    required this.shape,
+    required this.toolbarHeight,
+    required this.leadingWidth,
+    required this.backwardsCompatibility,
+    required this.toolbarTextStyle,
+    required this.titleTextStyle,
+    required this.systemOverlayStyle,
+  }) : assert(primary || topPadding == 0.0),
+       assert(
+         !floating || (snapConfiguration == null && showOnScreenConfiguration == null) || vsync != null,
+         'vsync cannot be null when snapConfiguration or showOnScreenConfiguration is not null, and floating is true',
+       ),
+       _bottomHeight = bottom?.preferredSize.height ?? 0.0;
+
+  final Widget? leading;
+  final bool automaticallyImplyLeading;
+  final Widget? title;
+  final List<Widget>? actions;
+  final Widget? flexibleSpace;
+  final PreferredSizeWidget? bottom;
+  final double? elevation;
+  final Color? shadowColor;
+  final bool forceElevated;
+  final Color? backgroundColor;
+  final Color? foregroundColor;
+  final Brightness? brightness;
+  final IconThemeData? iconTheme;
+  final IconThemeData? actionsIconTheme;
+  final TextTheme? textTheme;
+  final bool primary;
+  final bool? centerTitle;
+  final bool excludeHeaderSemantics;
+  final double? titleSpacing;
+  final double? expandedHeight;
+  final double collapsedHeight;
+  final double topPadding;
+  final bool floating;
+  final bool pinned;
+  final ShapeBorder? shape;
+  final double? toolbarHeight;
+  final double? leadingWidth;
+  final bool backwardsCompatibility;
+  final TextStyle? toolbarTextStyle;
+  final TextStyle? titleTextStyle;
+  final SystemUiOverlayStyle? systemOverlayStyle;
+  final double _bottomHeight;
+
+  @override
+  double get minExtent => collapsedHeight;
+
+  @override
+  double get maxExtent => math.max(topPadding + (expandedHeight ?? (toolbarHeight ?? kToolbarHeight) + _bottomHeight), minExtent);
+
+  @override
+  final TickerProvider vsync;
+
+  @override
+  final FloatingHeaderSnapConfiguration? snapConfiguration;
+
+  @override
+  final OverScrollHeaderStretchConfiguration? stretchConfiguration;
+
+  @override
+  final PersistentHeaderShowOnScreenConfiguration? showOnScreenConfiguration;
+
+  @override
+  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
+    final double visibleMainHeight = maxExtent - shrinkOffset - topPadding;
+    final double extraToolbarHeight = math.max(minExtent - _bottomHeight - topPadding - (toolbarHeight ?? kToolbarHeight), 0.0);
+    final double visibleToolbarHeight = visibleMainHeight - _bottomHeight - extraToolbarHeight;
+
+    final bool isPinnedWithOpacityFade = pinned && floating && bottom != null && extraToolbarHeight == 0.0;
+    final double toolbarOpacity = !pinned || isPinnedWithOpacityFade
+      ? (visibleToolbarHeight / (toolbarHeight ?? kToolbarHeight)).clamp(0.0, 1.0)
+      : 1.0;
+
+    final Widget appBar = FlexibleSpaceBar.createSettings(
+      minExtent: minExtent,
+      maxExtent: maxExtent,
+      currentExtent: math.max(minExtent, maxExtent - shrinkOffset),
+      toolbarOpacity: toolbarOpacity,
+      child: AppBar(
+        leading: leading,
+        automaticallyImplyLeading: automaticallyImplyLeading,
+        title: title,
+        actions: actions,
+        flexibleSpace: (title == null && flexibleSpace != null && !excludeHeaderSemantics)
+          ? Semantics(child: flexibleSpace, header: true)
+          : flexibleSpace,
+        bottom: bottom,
+        elevation: forceElevated || overlapsContent || (pinned && shrinkOffset > maxExtent - minExtent) ? elevation ?? 4.0 : 0.0,
+        shadowColor: shadowColor,
+        backgroundColor: backgroundColor,
+        foregroundColor: foregroundColor,
+        brightness: brightness,
+        iconTheme: iconTheme,
+        actionsIconTheme: actionsIconTheme,
+        textTheme: textTheme,
+        primary: primary,
+        centerTitle: centerTitle,
+        excludeHeaderSemantics: excludeHeaderSemantics,
+        titleSpacing: titleSpacing,
+        shape: shape,
+        toolbarOpacity: toolbarOpacity,
+        bottomOpacity: pinned ? 1.0 : ((visibleMainHeight / _bottomHeight).clamp(0.0, 1.0)),
+        toolbarHeight: toolbarHeight,
+        leadingWidth: leadingWidth,
+        backwardsCompatibility: backwardsCompatibility,
+        toolbarTextStyle: toolbarTextStyle,
+        titleTextStyle: titleTextStyle,
+        systemOverlayStyle: systemOverlayStyle,
+      ),
+    );
+    return floating ? _FloatingAppBar(child: appBar) : appBar;
+  }
+
+  @override
+  bool shouldRebuild(covariant _SliverAppBarDelegate oldDelegate) {
+    return leading != oldDelegate.leading
+        || automaticallyImplyLeading != oldDelegate.automaticallyImplyLeading
+        || title != oldDelegate.title
+        || actions != oldDelegate.actions
+        || flexibleSpace != oldDelegate.flexibleSpace
+        || bottom != oldDelegate.bottom
+        || _bottomHeight != oldDelegate._bottomHeight
+        || elevation != oldDelegate.elevation
+        || shadowColor != oldDelegate.shadowColor
+        || backgroundColor != oldDelegate.backgroundColor
+        || foregroundColor != oldDelegate.foregroundColor
+        || brightness != oldDelegate.brightness
+        || iconTheme != oldDelegate.iconTheme
+        || actionsIconTheme != oldDelegate.actionsIconTheme
+        || textTheme != oldDelegate.textTheme
+        || primary != oldDelegate.primary
+        || centerTitle != oldDelegate.centerTitle
+        || titleSpacing != oldDelegate.titleSpacing
+        || expandedHeight != oldDelegate.expandedHeight
+        || topPadding != oldDelegate.topPadding
+        || pinned != oldDelegate.pinned
+        || floating != oldDelegate.floating
+        || vsync != oldDelegate.vsync
+        || snapConfiguration != oldDelegate.snapConfiguration
+        || stretchConfiguration != oldDelegate.stretchConfiguration
+        || showOnScreenConfiguration != oldDelegate.showOnScreenConfiguration
+        || forceElevated != oldDelegate.forceElevated
+        || toolbarHeight != oldDelegate.toolbarHeight
+        || leadingWidth != oldDelegate.leadingWidth
+        || backwardsCompatibility != oldDelegate.backwardsCompatibility
+        || toolbarTextStyle != oldDelegate.toolbarTextStyle
+        || titleTextStyle != oldDelegate.titleTextStyle
+        || systemOverlayStyle != oldDelegate.systemOverlayStyle;
+  }
+
+  @override
+  String toString() {
+    return '${describeIdentity(this)}(topPadding: ${topPadding.toStringAsFixed(1)}, bottomHeight: ${_bottomHeight.toStringAsFixed(1)}, ...)';
+  }
+}
+
+/// A material design app bar that integrates with a [CustomScrollView].
+///
+/// An app bar consists of a toolbar and potentially other widgets, such as a
+/// [TabBar] and a [FlexibleSpaceBar]. App bars typically expose one or more
+/// common actions with [IconButton]s which are optionally followed by a
+/// [PopupMenuButton] for less common operations.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=R9C5KMJKluE}
+///
+/// Sliver app bars are typically used as the first child of a
+/// [CustomScrollView], which lets the app bar integrate with the scroll view so
+/// that it can vary in height according to the scroll offset or float above the
+/// other content in the scroll view. For a fixed-height app bar at the top of
+/// the screen see [AppBar], which is used in the [Scaffold.appBar] slot.
+///
+/// The AppBar displays the toolbar widgets, [leading], [title], and
+/// [actions], above the [bottom] (if any). If a [flexibleSpace] widget is
+/// specified then it is stacked behind the toolbar and the bottom widget.
+///
+/// {@tool snippet}
+///
+/// This is an example that could be included in a [CustomScrollView]'s
+/// [CustomScrollView.slivers] list:
+///
+/// ```dart
+/// SliverAppBar(
+///   expandedHeight: 150.0,
+///   flexibleSpace: const FlexibleSpaceBar(
+///     title: Text('Available seats'),
+///   ),
+///   actions: <Widget>[
+///     IconButton(
+///       icon: const Icon(Icons.add_circle),
+///       tooltip: 'Add new entry',
+///       onPressed: () { /* ... */ },
+///     ),
+///   ]
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=freeform}
+///
+/// This sample shows a [SliverAppBar] and it's behaviors when using the [pinned], [snap] and [floating] parameters.
+///
+/// ```dart imports
+/// import 'package:flute/material.dart';
+/// ```
+///
+/// ```dart
+/// void main() => runApp(MyApp());
+///
+/// class MyApp extends StatefulWidget {
+///   const MyApp({Key? key}) : super(key: key);
+///
+///   @override
+///   State<StatefulWidget> createState() => _MyAppState();
+/// }
+///
+/// class _MyAppState extends State<MyApp> {
+///   bool _pinned = true;
+///   bool _snap = false;
+///   bool _floating = false;
+///
+///   // SliverAppBar is declared in Scaffold.body, in slivers of a
+///   // CustomScrollView.
+///   @override
+///   Widget build(BuildContext context) {
+///     return MaterialApp(
+///       home: Scaffold(
+///         body: CustomScrollView(
+///           slivers: <Widget>[
+///             SliverAppBar(
+///               pinned: this._pinned,
+///               snap: this._snap,
+///               floating: this._floating,
+///               expandedHeight: 160.0,
+///               flexibleSpace: FlexibleSpaceBar(
+///                 title: const Text("SliverAppBar"),
+///                 background: FlutterLogo(),
+///               ),
+///             ),
+///             SliverToBoxAdapter(
+///               child: Center(
+///                 child: Container(
+///                   height: 2000,
+///                   child: const Text("Scroll to see SliverAppBar in effect ."),
+///                 ),
+///               ),
+///             ),
+///           ],
+///         ),
+///         bottomNavigationBar: BottomAppBar(
+///           child: ButtonBar(
+///             alignment: MainAxisAlignment.spaceEvenly,
+///             children: <Widget>[
+///               Row(
+///                 children: <Widget>[
+///                   const Text('pinned'),
+///                   Switch(
+///                     onChanged: (bool val) {
+///                       setState(() {
+///                         this._pinned = val;
+///                       });
+///                     },
+///                     value: this._pinned,
+///                   ),
+///                 ],
+///               ),
+///               Row(
+///                 children: <Widget>[
+///                   const Text('snap'),
+///                   Switch(
+///                     onChanged: (bool val) {
+///                       setState(() {
+///                         this._snap = val;
+///                         //Snapping only applies when the app bar is floating.
+///                         this._floating = this._floating || val;
+///                       });
+///                     },
+///                     value: this._snap,
+///                   ),
+///                 ],
+///               ),
+///               Row(
+///                 children: <Widget>[
+///                   const Text('floating'),
+///                   Switch(
+///                     onChanged: (bool val) {
+///                       setState(() {
+///                         this._floating = val;
+///                         if (this._snap == true) {
+///                           if (this._floating != true) {
+///                             this._snap = false;
+///                           }
+///                         }
+///                       });
+///                     },
+///                     value: this._floating,
+///                   ),
+///                 ],
+///               ),
+///             ],
+///           ),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+///
+/// ```
+/// {@end-tool}
+///
+/// ## Animated Examples
+///
+/// The following animations show how app bars with different configurations
+/// behave when a user scrolls up and then down again.
+///
+/// * App bar with [floating]: false, [pinned]: false, [snap]: false:
+///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar.mp4}
+///
+/// * App bar with [floating]: true, [pinned]: false, [snap]: false:
+///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating.mp4}
+///
+/// * App bar with [floating]: true, [pinned]: false, [snap]: true:
+///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating_snap.mp4}
+///
+/// * App bar with [floating]: true, [pinned]: true, [snap]: false:
+///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_pinned_floating.mp4}
+///
+/// * App bar with [floating]: true, [pinned]: true, [snap]: true:
+///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_pinned_floating_snap.mp4}
+///
+/// * App bar with [floating]: false, [pinned]: true, [snap]: false:
+///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_pinned.mp4}
+///
+/// The property [snap] can only be set to true if [floating] is also true.
+///
+/// See also:
+///
+///  * [CustomScrollView], which integrates the [SliverAppBar] into its
+///    scrolling.
+///  * [AppBar], which is a fixed-height app bar for use in [Scaffold.appBar].
+///  * [TabBar], which is typically placed in the [bottom] slot of the [AppBar]
+///    if the screen has multiple pages arranged in tabs.
+///  * [IconButton], which is used with [actions] to show buttons on the app bar.
+///  * [PopupMenuButton], to show a popup menu on the app bar, via [actions].
+///  * [FlexibleSpaceBar], which is used with [flexibleSpace] when the app bar
+///    can expand and collapse.
+///  * <https://material.io/design/components/app-bars-top.html>
+class SliverAppBar extends StatefulWidget {
+  /// Creates a material design app bar that can be placed in a [CustomScrollView].
+  ///
+  /// The arguments [forceElevated], [primary], [floating], [pinned], [snap]
+  /// and [automaticallyImplyLeading] must not be null.
+  const SliverAppBar({
+    Key? key,
+    this.leading,
+    this.automaticallyImplyLeading = true,
+    this.title,
+    this.actions,
+    this.flexibleSpace,
+    this.bottom,
+    this.elevation,
+    this.shadowColor,
+    this.forceElevated = false,
+    this.backgroundColor,
+    this.foregroundColor,
+    this.brightness,
+    this.iconTheme,
+    this.actionsIconTheme,
+    this.textTheme,
+    this.primary = true,
+    this.centerTitle,
+    this.excludeHeaderSemantics = false,
+    this.titleSpacing,
+    this.collapsedHeight,
+    this.expandedHeight,
+    this.floating = false,
+    this.pinned = false,
+    this.snap = false,
+    this.stretch = false,
+    this.stretchTriggerOffset = 100.0,
+    this.onStretchTrigger,
+    this.shape,
+    this.toolbarHeight = kToolbarHeight,
+    this.leadingWidth,
+    this.backwardsCompatibility = true,
+    this.toolbarTextStyle,
+    this.titleTextStyle,
+    this.systemOverlayStyle,
+  }) : assert(automaticallyImplyLeading != null),
+       assert(forceElevated != null),
+       assert(primary != null),
+       assert(floating != null),
+       assert(pinned != null),
+       assert(snap != null),
+       assert(stretch != null),
+       assert(toolbarHeight != null),
+       assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'),
+       assert(stretchTriggerOffset > 0.0),
+       assert(collapsedHeight == null || collapsedHeight > toolbarHeight, 'The "collapsedHeight" argument has to be larger than [toolbarHeight].'),
+       super(key: key);
+
+  /// {@macro flutter.material.appbar.leading}
+  ///
+  /// This property is used to configure an [AppBar].
+  final Widget? leading;
+
+  /// {@macro flutter.material.appbar.automaticallyImplyLeading}
+  ///
+  /// This property is used to configure an [AppBar].
+  final bool automaticallyImplyLeading;
+
+  /// {@macro flutter.material.appbar.title}
+  ///
+  /// This property is used to configure an [AppBar].
+  final Widget? title;
+
+  /// {@macro flutter.material.appbar.actions}
+  ///
+  /// This property is used to configure an [AppBar].
+  final List<Widget>? actions;
+
+  /// {@macro flutter.material.appbar.flexibleSpace}
+  ///
+  /// This property is used to configure an [AppBar].
+  final Widget? flexibleSpace;
+
+  /// {@macro flutter.material.appbar.bottom}
+  ///
+  /// This property is used to configure an [AppBar].
+  final PreferredSizeWidget? bottom;
+
+  /// {@macro flutter.material.appbar.elevation}
+  ///
+  /// This property is used to configure an [AppBar].
+  final double? elevation;
+
+  /// {@macro flutter.material.appbar.shadowColor}
+  ///
+  /// This property is used to configure an [AppBar].
+  final Color? shadowColor;
+
+  /// Whether to show the shadow appropriate for the [elevation] even if the
+  /// content is not scrolled under the [AppBar].
+  ///
+  /// Defaults to false, meaning that the [elevation] is only applied when the
+  /// [AppBar] is being displayed over content that is scrolled under it.
+  ///
+  /// When set to true, the [elevation] is applied regardless.
+  ///
+  /// Ignored when [elevation] is zero.
+  final bool forceElevated;
+
+  /// {@macro flutter.material.appbar.backgroundColor}
+  ///
+  /// This property is used to configure an [AppBar].
+  final Color? backgroundColor;
+
+  /// {@macro flutter.material.appbar.foregroundColor}
+  ///
+  /// This property is used to configure an [AppBar].
+  final Color? foregroundColor;
+
+  /// {@macro flutter.material.appbar.brightness}
+  ///
+  /// This property is used to configure an [AppBar].
+  final Brightness? brightness;
+
+  /// {@macro flutter.material.appbar.iconTheme}
+  ///
+  /// This property is used to configure an [AppBar].
+  final IconThemeData? iconTheme;
+
+  /// {@macro flutter.material.appbar.actionsIconTheme}
+  ///
+  /// This property is used to configure an [AppBar].
+  final IconThemeData? actionsIconTheme;
+
+  /// {@macro flutter.material.appbar.textTheme}
+  ///
+  /// This property is used to configure an [AppBar].
+  final TextTheme? textTheme;
+
+  /// {@macro flutter.material.appbar.primary}
+  ///
+  /// This property is used to configure an [AppBar].
+  final bool primary;
+
+  /// {@macro flutter.material.appbar.centerTitle}
+  ///
+  /// This property is used to configure an [AppBar].
+  final bool? centerTitle;
+
+  /// {@macro flutter.material.appbar.excludeHeaderSemantics}
+  ///
+  /// This property is used to configure an [AppBar].
+  final bool excludeHeaderSemantics;
+
+  /// {@macro flutter.material.appbar.titleSpacing}
+  ///
+  /// This property is used to configure an [AppBar].
+  final double? titleSpacing;
+
+  /// Defines the height of the app bar when it is collapsed.
+  ///
+  /// By default, the collapsed height is [toolbarHeight]. If [bottom] widget is
+  /// specified, then its height from [PreferredSizeWidget.preferredSize] is
+  /// added to the height. If [primary] is true, then the [MediaQuery] top
+  /// padding, [EdgeInsets.top] of [MediaQueryData.padding], is added as well.
+  ///
+  /// If [pinned] and [floating] are true, with [bottom] set, the default
+  /// collapsed height is only the height of [PreferredSizeWidget.preferredSize]
+  /// with the [MediaQuery] top padding.
+  final double? collapsedHeight;
+
+  /// The size of the app bar when it is fully expanded.
+  ///
+  /// By default, the total height of the toolbar and the bottom widget (if
+  /// any). If a [flexibleSpace] widget is specified this height should be big
+  /// enough to accommodate whatever that widget contains.
+  ///
+  /// This does not include the status bar height (which will be automatically
+  /// included if [primary] is true).
+  final double? expandedHeight;
+
+  /// Whether the app bar should become visible as soon as the user scrolls
+  /// towards the app bar.
+  ///
+  /// Otherwise, the user will need to scroll near the top of the scroll view to
+  /// reveal the app bar.
+  ///
+  /// If [snap] is true then a scroll that exposes the app bar will trigger an
+  /// animation that slides the entire app bar into view. Similarly if a scroll
+  /// dismisses the app bar, the animation will slide it completely out of view.
+  ///
+  /// ## Animated Examples
+  ///
+  /// The following animations show how the app bar changes its scrolling
+  /// behavior based on the value of this property.
+  ///
+  /// * App bar with [floating] set to false:
+  ///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar.mp4}
+  /// * App bar with [floating] set to true:
+  ///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating.mp4}
+  ///
+  /// See also:
+  ///
+  ///  * [SliverAppBar] for more animated examples of how this property changes the
+  ///    behavior of the app bar in combination with [pinned] and [snap].
+  final bool floating;
+
+  /// Whether the app bar should remain visible at the start of the scroll view.
+  ///
+  /// The app bar can still expand and contract as the user scrolls, but it will
+  /// remain visible rather than being scrolled out of view.
+  ///
+  /// ## Animated Examples
+  ///
+  /// The following animations show how the app bar changes its scrolling
+  /// behavior based on the value of this property.
+  ///
+  /// * App bar with [pinned] set to false:
+  ///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar.mp4}
+  /// * App bar with [pinned] set to true:
+  ///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_pinned.mp4}
+  ///
+  /// See also:
+  ///
+  ///  * [SliverAppBar] for more animated examples of how this property changes the
+  ///    behavior of the app bar in combination with [floating].
+  final bool pinned;
+
+  /// {@macro flutter.material.appbar.shape}
+  ///
+  /// This property is used to configure an [AppBar].
+  final ShapeBorder? shape;
+
+  /// If [snap] and [floating] are true then the floating app bar will "snap"
+  /// into view.
+  ///
+  /// If [snap] is true then a scroll that exposes the floating app bar will
+  /// trigger an animation that slides the entire app bar into view. Similarly
+  /// if a scroll dismisses the app bar, the animation will slide the app bar
+  /// completely out of view. Additionally, setting [snap] to true will fully
+  /// expand the floating app bar when the framework tries to reveal the
+  /// contents of the app bar by calling [RenderObject.showOnScreen]. For
+  /// example, when a [TextField] in the floating app bar gains focus, if [snap]
+  /// is true, the framework will always fully expand the floating app bar, in
+  /// order to reveal the focused [TextField].
+  ///
+  /// Snapping only applies when the app bar is floating, not when the app bar
+  /// appears at the top of its scroll view.
+  ///
+  /// ## Animated Examples
+  ///
+  /// The following animations show how the app bar changes its scrolling
+  /// behavior based on the value of this property.
+  ///
+  /// * App bar with [snap] set to false:
+  ///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating.mp4}
+  /// * App bar with [snap] set to true:
+  ///   {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating_snap.mp4}
+  ///
+  /// See also:
+  ///
+  ///  * [SliverAppBar] for more animated examples of how this property changes the
+  ///    behavior of the app bar in combination with [pinned] and [floating].
+  final bool snap;
+
+  /// Whether the app bar should stretch to fill the over-scroll area.
+  ///
+  /// The app bar can still expand and contract as the user scrolls, but it will
+  /// also stretch when the user over-scrolls.
+  final bool stretch;
+
+  /// The offset of overscroll required to activate [onStretchTrigger].
+  ///
+  /// This defaults to 100.0.
+  final double stretchTriggerOffset;
+
+  /// The callback function to be executed when a user over-scrolls to the
+  /// offset specified by [stretchTriggerOffset].
+  final AsyncCallback? onStretchTrigger;
+
+  /// {@macro flutter.material.appbar.toolbarHeight}
+  ///
+  /// This property is used to configure an [AppBar].
+  final double toolbarHeight;
+
+  /// {@macro flutter.material.appbar.leadingWidth}
+  ///
+  /// This property is used to configure an [AppBar].
+  final double? leadingWidth;
+
+  /// {@macro flutter.material.appbar.backwardsCompatibility}
+  ///
+  /// This property is used to configure an [AppBar].
+  final bool backwardsCompatibility;
+
+  /// {@macro flutter.material.appbar.toolbarTextStyle}
+  ///
+  /// This property is used to configure an [AppBar].
+  final TextStyle? toolbarTextStyle;
+
+  /// {@macro flutter.material.appbar.titleTextStyle}
+  ///
+  /// This property is used to configure an [AppBar].
+  final TextStyle? titleTextStyle;
+
+  /// {@macro flutter.material.appbar.systemOverlayStyle}
+  ///
+  /// This property is used to configure an [AppBar].
+  final SystemUiOverlayStyle? systemOverlayStyle;
+
+  @override
+  _SliverAppBarState createState() => _SliverAppBarState();
+}
+
+// This class is only Stateful because it owns the TickerProvider used
+// by the floating appbar snap animation (via FloatingHeaderSnapConfiguration).
+class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMixin {
+  FloatingHeaderSnapConfiguration? _snapConfiguration;
+  OverScrollHeaderStretchConfiguration? _stretchConfiguration;
+  PersistentHeaderShowOnScreenConfiguration? _showOnScreenConfiguration;
+
+  void _updateSnapConfiguration() {
+    if (widget.snap && widget.floating) {
+      _snapConfiguration = FloatingHeaderSnapConfiguration(
+        curve: Curves.easeOut,
+        duration: const Duration(milliseconds: 200),
+      );
+    } else {
+      _snapConfiguration = null;
+    }
+
+    _showOnScreenConfiguration = widget.floating & widget.snap
+      ? const PersistentHeaderShowOnScreenConfiguration(minShowOnScreenExtent: double.infinity)
+      : null;
+  }
+
+  void _updateStretchConfiguration() {
+    if (widget.stretch) {
+      _stretchConfiguration = OverScrollHeaderStretchConfiguration(
+        stretchTriggerOffset: widget.stretchTriggerOffset,
+        onStretchTrigger: widget.onStretchTrigger,
+      );
+    } else {
+      _stretchConfiguration = null;
+    }
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _updateSnapConfiguration();
+    _updateStretchConfiguration();
+  }
+
+  @override
+  void didUpdateWidget(SliverAppBar oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.snap != oldWidget.snap || widget.floating != oldWidget.floating)
+      _updateSnapConfiguration();
+    if (widget.stretch != oldWidget.stretch)
+      _updateStretchConfiguration();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(!widget.primary || debugCheckHasMediaQuery(context));
+    final double bottomHeight = widget.bottom?.preferredSize.height ?? 0.0;
+    final double topPadding = widget.primary ? MediaQuery.of(context).padding.top : 0.0;
+    final double collapsedHeight = (widget.pinned && widget.floating && widget.bottom != null)
+      ? (widget.collapsedHeight ?? 0.0) + bottomHeight + topPadding
+      : (widget.collapsedHeight ?? widget.toolbarHeight) + bottomHeight + topPadding;
+
+    return MediaQuery.removePadding(
+      context: context,
+      removeBottom: true,
+      child: SliverPersistentHeader(
+        floating: widget.floating,
+        pinned: widget.pinned,
+        delegate: _SliverAppBarDelegate(
+          vsync: this,
+          leading: widget.leading,
+          automaticallyImplyLeading: widget.automaticallyImplyLeading,
+          title: widget.title,
+          actions: widget.actions,
+          flexibleSpace: widget.flexibleSpace,
+          bottom: widget.bottom,
+          elevation: widget.elevation,
+          shadowColor: widget.shadowColor,
+          forceElevated: widget.forceElevated,
+          backgroundColor: widget.backgroundColor,
+          foregroundColor: widget.foregroundColor,
+          brightness: widget.brightness,
+          iconTheme: widget.iconTheme,
+          actionsIconTheme: widget.actionsIconTheme,
+          textTheme: widget.textTheme,
+          primary: widget.primary,
+          centerTitle: widget.centerTitle,
+          excludeHeaderSemantics: widget.excludeHeaderSemantics,
+          titleSpacing: widget.titleSpacing,
+          expandedHeight: widget.expandedHeight,
+          collapsedHeight: collapsedHeight,
+          topPadding: topPadding,
+          floating: widget.floating,
+          pinned: widget.pinned,
+          shape: widget.shape,
+          snapConfiguration: _snapConfiguration,
+          stretchConfiguration: _stretchConfiguration,
+          showOnScreenConfiguration: _showOnScreenConfiguration,
+          toolbarHeight: widget.toolbarHeight,
+          leadingWidth: widget.leadingWidth,
+          backwardsCompatibility: widget.backwardsCompatibility,
+          toolbarTextStyle: widget.toolbarTextStyle,
+          titleTextStyle: widget.titleTextStyle,
+          systemOverlayStyle: widget.systemOverlayStyle,
+        ),
+      ),
+    );
+  }
+}
+
+// Layout the AppBar's title with unconstrained height, vertically
+// center it within its (NavigationToolbar) parent, and allow the
+// parent to constrain the title's actual height.
+class _AppBarTitleBox extends SingleChildRenderObjectWidget {
+  const _AppBarTitleBox({ Key? key, required Widget child }) : assert(child != null), super(key: key, child: child);
+
+  @override
+  _RenderAppBarTitleBox createRenderObject(BuildContext context) {
+    return _RenderAppBarTitleBox(
+      textDirection: Directionality.of(context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderAppBarTitleBox renderObject) {
+    renderObject.textDirection = Directionality.of(context);
+  }
+}
+
+class _RenderAppBarTitleBox extends RenderAligningShiftedBox {
+  _RenderAppBarTitleBox({
+    RenderBox? child,
+    TextDirection? textDirection,
+  }) : super(child: child, alignment: Alignment.center, textDirection: textDirection);
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    final BoxConstraints innerConstraints = constraints.copyWith(maxHeight: double.infinity);
+    final Size childSize = child!.getDryLayout(innerConstraints);
+    return constraints.constrain(childSize);
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints innerConstraints = constraints.copyWith(maxHeight: double.infinity);
+    child!.layout(innerConstraints, parentUsesSize: true);
+    size = constraints.constrain(child!.size);
+    alignChild();
+  }
+}
diff --git a/lib/src/material/app_bar_theme.dart b/lib/src/material/app_bar_theme.dart
new file mode 100644
index 0000000..50f4d42
--- /dev/null
+++ b/lib/src/material/app_bar_theme.dart
@@ -0,0 +1,290 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'text_theme.dart';
+import 'theme.dart';
+
+/// Overrides the default values of visual properties for descendant
+/// [AppBar] widgets.
+///
+/// Descendant widgets obtain the current [AppBarTheme] object with
+/// `AppBarTheme.of(context)`. Instances of [AppBarTheme] can be customized
+/// with [AppBarTheme.copyWith].
+///
+/// Typically an [AppBarTheme] is specified as part of the overall [Theme] with
+/// [ThemeData.appBarTheme].
+///
+/// All [AppBarTheme] properties are `null` by default. When null, the [AppBar]
+/// compute its own default values, typically based on the overall theme's
+/// [ThemeData.colorScheme], [ThemeData.textTheme], and [ThemeData.iconTheme].
+@immutable
+class AppBarTheme with Diagnosticable {
+  /// Creates a theme that can be used for [ThemeData.appBarTheme].
+  const AppBarTheme({
+    this.brightness,
+    this.color,
+    this.backgroundColor,
+    this.foregroundColor,
+    this.elevation,
+    this.shadowColor,
+    this.iconTheme,
+    this.actionsIconTheme,
+    this.textTheme,
+    this.centerTitle,
+    this.titleSpacing,
+    this.toolbarTextStyle,
+    this.titleTextStyle,
+    this.systemOverlayStyle,
+  });
+
+  /// This property is obsolete, please use [systemOverlayStyle] instead.
+  ///
+  /// Overrides the default value of the obsolete [AppBar.brightness]
+  /// property which implicitly defines [AppBar.systemOverlayStyle] in
+  /// all descendant [AppBar] widgets.
+  ///
+  /// See also:
+  ///
+  ///  * [systemOverlayStyle], which overrides the default value of
+  ///    [AppBar.systemOverlayStyle] in all descendant [AppBar] widgets.
+  ///  * [AppBar.backwardsCompatibility], which forces [AppBar] to depend
+  ///    on this obsolete property.
+  final Brightness? brightness;
+
+  /// Obsolete property that overrides the default value of
+  /// [AppBar.backgroundColor] in all descendant [AppBar] widgets.
+  ///
+  /// See also:
+  ///
+  ///  * [backgroundColor], which serves this same purpose
+  ///    as this property, but has a consistent name.
+  ///  * [AppBar.backwardsCompatibility], which forces [AppBar] to depend
+  ///    on this obsolete property.
+  final Color? color;
+
+  /// Overrides the default value of [AppBar.backgroundColor] in all
+  /// descendant [AppBar] widgets.
+  ///
+  /// See also:
+  ///
+  ///  * [foregroundColor], which overrides the default value for
+  ///    [AppBar.foregroundColor] in all descendant widgets.
+  final Color? backgroundColor;
+
+
+  /// Overrides the default value of [AppBar.foregroundColor] in all
+  /// descendant widgets.
+  ///
+  /// See also:
+  ///
+  ///  * [backgroundColor], which overrides the default value for
+  ///    [AppBar.backgroundColor] in all descendant [AppBar] widgets.
+  final Color? foregroundColor;
+
+  /// Overrides the default value of [AppBar.elevation] in all
+  /// descendant [AppBar] widgets.
+  final double? elevation;
+
+  /// Overrides the default value for [AppBar.shadowColor] in all
+  /// descendant widgets.
+  final Color? shadowColor;
+
+  /// Overrides the default value of [AppBar.iconTheme] in all
+  /// descendant [AppBar] widgets.
+  ///
+  /// See also:
+  ///
+  ///  * [actionsIconTheme], which overrides the default value for
+  ///    [AppBar.actionsIconTheme] in all descendant [AppBar] widgets.
+  ///  * [foregroundColor], which overrides the default value
+  ///    [AppBar.foregroundColor] in all descendant widgets.
+  final IconThemeData? iconTheme;
+
+  /// Overrides the default value of [AppBar.actionsIconTheme] in all
+  /// descendant widgets.
+  ///
+  /// See also:
+  ///
+  ///  * [iconTheme], which overrides the default value for
+  ///    [AppBar.iconTheme] in all descendant widgets.
+  ///  * [foregroundColor], which overrides the default value
+  ///    [AppBar.foregroundColor] in all descendant widgets.
+  final IconThemeData? actionsIconTheme;
+
+  /// Overrides the default value of the obsolete [AppBar.textTheme]
+  /// property in all descendant [AppBar] widgets.
+  ///
+  /// See also:
+  ///
+  ///  * [toolbarTextStyle], which overrides the default value for
+  ///    [AppBar.toolbarTextStyle in all descendant [AppBar] widgets.
+  ///  * [titleTextStyle], which overrides the default value for
+  ///    [AppBar.titleTextStyle in all descendant [AppBar] widgets.
+  final TextTheme? textTheme;
+
+  /// Overrides the default value for [AppBar.centerTitle].
+  /// property in all descendant widgets.
+  final bool? centerTitle;
+
+  /// Overrides the default value for the obsolete [AppBar.titleSpacing]
+  /// property in all descendant [AppBar] widgets.
+  ///
+  /// If null, [AppBar] uses default value of [NavigationToolbar.kMiddleSpacing].
+  final double? titleSpacing;
+
+  /// Overrides the default value for the obsolete [AppBar.toolbarTextStyle]
+  /// property in all descendant [AppBar] widgets.
+  ///
+  /// See also:
+  ///
+  ///  * [titleTextStyle], which overrides the default of [AppBar.titleTextStyle]
+  ///    in all descendant [AppBar] widgets.
+  final TextStyle? toolbarTextStyle;
+
+  /// Overrides the default value of [AppBar.titleTextStyle]
+  /// property in all descendant [AppBar] widgets.
+  ///
+  /// See also:
+  ///
+  ///  * [toolbarTextStyle], which overrides the default of [AppBar.toolbarTextStyle]
+  ///    in all descendant [AppBar] widgets.
+  final TextStyle? titleTextStyle;
+
+  /// Overrides the default value of [AppBar.systemOverlayStyle]
+  /// property in all descendant [AppBar] widgets.
+  final SystemUiOverlayStyle? systemOverlayStyle;
+
+  /// Creates a copy of this object with the given fields replaced with the
+  /// new values.
+  AppBarTheme copyWith({
+    IconThemeData? actionsIconTheme,
+    Brightness? brightness,
+    Color? color,
+    Color? backgroundColor,
+    Color? foregroundColor,
+    double? elevation,
+    Color? shadowColor,
+    IconThemeData? iconTheme,
+    TextTheme? textTheme,
+    bool? centerTitle,
+    double? titleSpacing,
+    TextStyle? toolbarTextStyle,
+    TextStyle? titleTextStyle,
+    SystemUiOverlayStyle? systemOverlayStyle,
+  }) {
+    return AppBarTheme(
+      brightness: brightness ?? this.brightness,
+      color: color ?? this.color,
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      foregroundColor: foregroundColor ?? this.foregroundColor,
+      elevation: elevation ?? this.elevation,
+      shadowColor: shadowColor ?? this.shadowColor,
+      iconTheme: iconTheme ?? this.iconTheme,
+      actionsIconTheme: actionsIconTheme ?? this.actionsIconTheme,
+      textTheme: textTheme ?? this.textTheme,
+      centerTitle: centerTitle ?? this.centerTitle,
+      titleSpacing: titleSpacing ?? this.titleSpacing,
+      toolbarTextStyle: toolbarTextStyle ?? this.toolbarTextStyle,
+      titleTextStyle: titleTextStyle ?? this.titleTextStyle,
+      systemOverlayStyle: systemOverlayStyle ?? this.systemOverlayStyle,
+    );
+  }
+
+  /// The [ThemeData.appBarTheme] property of the ambient [Theme].
+  static AppBarTheme of(BuildContext context) {
+    return Theme.of(context).appBarTheme;
+  }
+
+  /// Linearly interpolate between two AppBar themes.
+  ///
+  /// The argument `t` must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static AppBarTheme lerp(AppBarTheme? a, AppBarTheme? b, double t) {
+    assert(t != null);
+    return AppBarTheme(
+      brightness: t < 0.5 ? a?.brightness : b?.brightness,
+      color: Color.lerp(a?.color, b?.color, t),
+      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
+      foregroundColor: Color.lerp(a?.foregroundColor, b?.foregroundColor, t),
+      elevation: lerpDouble(a?.elevation, b?.elevation, t),
+      shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
+      iconTheme: IconThemeData.lerp(a?.iconTheme, b?.iconTheme, t),
+      actionsIconTheme: IconThemeData.lerp(a?.actionsIconTheme, b?.actionsIconTheme, t),
+      textTheme: TextTheme.lerp(a?.textTheme, b?.textTheme, t),
+      centerTitle: t < 0.5 ? a?.centerTitle : b?.centerTitle,
+      titleSpacing: lerpDouble(a?.titleSpacing, b?.titleSpacing, t),
+      toolbarTextStyle: TextStyle.lerp(a?.toolbarTextStyle, b?.toolbarTextStyle, t),
+      titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t),
+      systemOverlayStyle: t < 0.5 ? a?.systemOverlayStyle : b?.systemOverlayStyle,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      brightness,
+      color,
+      backgroundColor,
+      foregroundColor,
+      elevation,
+      shadowColor,
+      iconTheme,
+      actionsIconTheme,
+      textTheme,
+      centerTitle,
+      titleSpacing,
+      toolbarTextStyle,
+      titleTextStyle,
+      systemOverlayStyle,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is AppBarTheme
+        && other.brightness == brightness
+        && other.color == color
+        && other.backgroundColor == backgroundColor
+        && other.foregroundColor == foregroundColor
+        && other.elevation == elevation
+        && other.shadowColor == shadowColor
+        && other.iconTheme == iconTheme
+        && other.actionsIconTheme == actionsIconTheme
+        && other.textTheme == textTheme
+        && other.centerTitle == centerTitle
+        && other.titleSpacing == titleSpacing
+        && other.toolbarTextStyle == toolbarTextStyle
+        && other.titleTextStyle == titleTextStyle
+        && other.systemOverlayStyle == systemOverlayStyle;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Brightness>('brightness', brightness, defaultValue: null));
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
+    properties.add(ColorProperty('foregroundColor', foregroundColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
+    properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<IconThemeData>('iconTheme', iconTheme, defaultValue: null));
+    properties.add(DiagnosticsProperty<IconThemeData>('actionsIconTheme', actionsIconTheme, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextTheme>('textTheme', textTheme, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('centerTitle', centerTitle, defaultValue: null));
+    properties.add(DiagnosticsProperty<double>('titleSpacing', titleSpacing, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('toolbarTextStyle', toolbarTextStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null));
+  }
+}
diff --git a/lib/src/material/arc.dart b/lib/src/material/arc.dart
new file mode 100644
index 0000000..fb72efd
--- /dev/null
+++ b/lib/src/material/arc.dart
@@ -0,0 +1,418 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart';
+
+// How close the begin and end points must be to an axis to be considered
+// vertical or horizontal.
+const double _kOnAxisDelta = 2.0;
+
+/// A [Tween] that interpolates an [Offset] along a circular arc.
+///
+/// This class specializes the interpolation of [Tween<Offset>] so that instead
+/// of a straight line, the intermediate points follow the arc of a circle in a
+/// manner consistent with material design principles.
+///
+/// The arc's radius is related to the bounding box that contains the [begin]
+/// and [end] points. If the bounding box is taller than it is wide, then the
+/// center of the circle will be horizontally aligned with the end point.
+/// Otherwise the center of the circle will be aligned with the begin point.
+/// The arc's sweep is always less than or equal to 90 degrees.
+///
+/// See also:
+///
+///  * [Tween], for a discussion on how to use interpolation objects.
+///  * [MaterialRectArcTween], which extends this concept to interpolating [Rect]s.
+class MaterialPointArcTween extends Tween<Offset> {
+  /// Creates a [Tween] for animating [Offset]s along a circular arc.
+  ///
+  /// The [begin] and [end] properties must be non-null before the tween is
+  /// first used, but the arguments can be null if the values are going to be
+  /// filled in later.
+  MaterialPointArcTween({
+    Offset? begin,
+    Offset? end,
+  }) : super(begin: begin, end: end);
+
+  bool _dirty = true;
+
+  void _initialize() {
+    assert(this.begin != null);
+    assert(this.end != null);
+
+    final Offset begin = this.begin!;
+    final Offset end = this.end!;
+
+    // An explanation with a diagram can be found at https://goo.gl/vMSdRg
+    final Offset delta = end - begin;
+    final double deltaX = delta.dx.abs();
+    final double deltaY = delta.dy.abs();
+    final double distanceFromAtoB = delta.distance;
+    final Offset c = Offset(end.dx, begin.dy);
+
+    double sweepAngle() => 2.0 * math.asin(distanceFromAtoB / (2.0 * _radius!));
+
+    if (deltaX > _kOnAxisDelta && deltaY > _kOnAxisDelta) {
+      if (deltaX < deltaY) {
+        _radius = distanceFromAtoB * distanceFromAtoB / (c - begin).distance / 2.0;
+        _center = Offset(end.dx + _radius! * (begin.dx - end.dx).sign, end.dy);
+        if (begin.dx < end.dx) {
+          _beginAngle = sweepAngle() * (begin.dy - end.dy).sign;
+          _endAngle = 0.0;
+        } else {
+          _beginAngle = math.pi + sweepAngle() * (end.dy - begin.dy).sign;
+          _endAngle = math.pi;
+        }
+      } else {
+        _radius = distanceFromAtoB * distanceFromAtoB / (c - end).distance / 2.0;
+        _center = Offset(begin.dx, begin.dy + (end.dy - begin.dy).sign * _radius!);
+        if (begin.dy < end.dy) {
+          _beginAngle = -math.pi / 2.0;
+          _endAngle = _beginAngle! + sweepAngle() * (end.dx - begin.dx).sign;
+        } else {
+          _beginAngle = math.pi / 2.0;
+          _endAngle = _beginAngle! + sweepAngle() * (begin.dx - end.dx).sign;
+        }
+      }
+      assert(_beginAngle != null);
+      assert(_endAngle != null);
+    } else {
+      _beginAngle = null;
+      _endAngle = null;
+    }
+    _dirty = false;
+  }
+
+  /// The center of the circular arc, null if [begin] and [end] are horizontally or
+  /// vertically aligned, or if either is null.
+  Offset? get center {
+    if (begin == null || end == null)
+      return null;
+    if (_dirty)
+      _initialize();
+    return _center;
+  }
+  Offset? _center;
+
+  /// The radius of the circular arc, null if [begin] and [end] are horizontally or
+  /// vertically aligned, or if either is null.
+  double? get radius {
+    if (begin == null || end == null)
+      return null;
+    if (_dirty)
+      _initialize();
+    return _radius;
+  }
+  double? _radius;
+
+  /// The beginning of the arc's sweep in radians, measured from the positive x
+  /// axis. Positive angles turn clockwise.
+  ///
+  /// This will be null if [begin] and [end] are horizontally or vertically
+  /// aligned, or if either is null.
+  double? get beginAngle {
+    if (begin == null || end == null)
+      return null;
+    if (_dirty)
+      _initialize();
+    return _beginAngle;
+  }
+  double? _beginAngle;
+
+  /// The end of the arc's sweep in radians, measured from the positive x axis.
+  /// Positive angles turn clockwise.
+  ///
+  /// This will be null if [begin] and [end] are horizontally or vertically
+  /// aligned, or if either is null.
+  double? get endAngle {
+    if (begin == null || end == null)
+      return null;
+    if (_dirty)
+      _initialize();
+    return _beginAngle;
+  }
+  double? _endAngle;
+
+  @override
+  set begin(Offset? value) {
+    if (value != begin) {
+      super.begin = value;
+      _dirty = true;
+    }
+  }
+
+  @override
+  set end(Offset? value) {
+    if (value != end) {
+      super.end = value;
+      _dirty = true;
+    }
+  }
+
+  @override
+  Offset lerp(double t) {
+    if (_dirty)
+      _initialize();
+    if (t == 0.0)
+      return begin!;
+    if (t == 1.0)
+      return end!;
+    if (_beginAngle == null || _endAngle == null)
+      return Offset.lerp(begin, end, t)!;
+    final double angle = lerpDouble(_beginAngle, _endAngle, t)!;
+    final double x = math.cos(angle) * _radius!;
+    final double y = math.sin(angle) * _radius!;
+    return _center! + Offset(x, y);
+  }
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'MaterialPointArcTween')}($begin \u2192 $end; center=$center, radius=$radius, beginAngle=$beginAngle, endAngle=$endAngle)';
+  }
+}
+
+enum _CornerId {
+  topLeft,
+  topRight,
+  bottomLeft,
+  bottomRight
+}
+
+class _Diagonal {
+  const _Diagonal(this.beginId, this.endId);
+  final _CornerId beginId;
+  final _CornerId endId;
+}
+
+const List<_Diagonal> _allDiagonals = <_Diagonal>[
+  _Diagonal(_CornerId.topLeft, _CornerId.bottomRight),
+  _Diagonal(_CornerId.bottomRight, _CornerId.topLeft),
+  _Diagonal(_CornerId.topRight, _CornerId.bottomLeft),
+  _Diagonal(_CornerId.bottomLeft, _CornerId.topRight),
+];
+
+typedef _KeyFunc<T> = double Function(T input);
+
+// Select the element for which the key function returns the maximum value.
+T _maxBy<T>(Iterable<T> input, _KeyFunc<T> keyFunc) {
+  late T maxValue;
+  double? maxKey;
+  for (final T value in input) {
+    final double key = keyFunc(value);
+    if (maxKey == null || key > maxKey) {
+      maxValue = value;
+      maxKey = key;
+    }
+  }
+  return maxValue;
+}
+
+/// A [Tween] that interpolates a [Rect] by having its opposite corners follow
+/// circular arcs.
+///
+/// This class specializes the interpolation of [Tween<Rect>] so that instead of
+/// growing or shrinking linearly, opposite corners of the rectangle follow arcs
+/// in a manner consistent with material design principles.
+///
+/// Specifically, the rectangle corners whose diagonals are closest to the overall
+/// direction of the animation follow arcs defined with [MaterialPointArcTween].
+///
+/// See also:
+///
+///  * [MaterialRectCenterArcTween], which interpolates a rect along a circular
+///    arc between the begin and end [Rect]'s centers.
+///  * [Tween], for a discussion on how to use interpolation objects.
+///  * [MaterialPointArcTween], the analog for [Offset] interpolation.
+///  * [RectTween], which does a linear rectangle interpolation.
+///  * [Hero.createRectTween], which can be used to specify the tween that defines
+///    a hero's path.
+class MaterialRectArcTween extends RectTween {
+  /// Creates a [Tween] for animating [Rect]s along a circular arc.
+  ///
+  /// The [begin] and [end] properties must be non-null before the tween is
+  /// first used, but the arguments can be null if the values are going to be
+  /// filled in later.
+  MaterialRectArcTween({
+    Rect? begin,
+    Rect? end,
+  }) : super(begin: begin, end: end);
+
+  bool _dirty = true;
+
+  void _initialize() {
+    assert(begin != null);
+    assert(end != null);
+    final Offset centersVector = end!.center - begin!.center;
+    final _Diagonal diagonal = _maxBy<_Diagonal>(_allDiagonals, (_Diagonal d) => _diagonalSupport(centersVector, d));
+    _beginArc = MaterialPointArcTween(
+      begin: _cornerFor(begin!, diagonal.beginId),
+      end: _cornerFor(end!, diagonal.beginId),
+    );
+    _endArc = MaterialPointArcTween(
+      begin: _cornerFor(begin!, diagonal.endId),
+      end: _cornerFor(end!, diagonal.endId),
+    );
+    _dirty = false;
+  }
+
+  double _diagonalSupport(Offset centersVector, _Diagonal diagonal) {
+    final Offset delta = _cornerFor(begin!, diagonal.endId) - _cornerFor(begin!, diagonal.beginId);
+    final double length = delta.distance;
+    return centersVector.dx * delta.dx / length + centersVector.dy * delta.dy / length;
+  }
+
+  Offset _cornerFor(Rect rect, _CornerId id) {
+    switch (id) {
+      case _CornerId.topLeft: return rect.topLeft;
+      case _CornerId.topRight: return rect.topRight;
+      case _CornerId.bottomLeft: return rect.bottomLeft;
+      case _CornerId.bottomRight: return rect.bottomRight;
+    }
+  }
+
+  /// The path of the corresponding [begin], [end] rectangle corners that lead
+  /// the animation.
+  MaterialPointArcTween? get beginArc {
+    if (begin == null)
+      return null;
+    if (_dirty)
+      _initialize();
+    return _beginArc;
+  }
+  late MaterialPointArcTween _beginArc;
+
+  /// The path of the corresponding [begin], [end] rectangle corners that trail
+  /// the animation.
+  MaterialPointArcTween? get endArc {
+    if (end == null)
+      return null;
+    if (_dirty)
+      _initialize();
+    return _endArc;
+  }
+  late MaterialPointArcTween _endArc;
+
+  @override
+  set begin(Rect? value) {
+    if (value != begin) {
+      super.begin = value;
+      _dirty = true;
+    }
+  }
+
+  @override
+  set end(Rect? value) {
+    if (value != end) {
+      super.end = value;
+      _dirty = true;
+    }
+  }
+
+  @override
+  Rect lerp(double t) {
+    if (_dirty)
+      _initialize();
+    if (t == 0.0)
+      return begin!;
+    if (t == 1.0)
+      return end!;
+    return Rect.fromPoints(_beginArc.lerp(t), _endArc.lerp(t));
+  }
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'MaterialRectArcTween')}($begin \u2192 $end; beginArc=$beginArc, endArc=$endArc)';
+  }
+}
+
+/// A [Tween] that interpolates a [Rect] by moving it along a circular arc from
+/// [begin]'s [Rect.center] to [end]'s [Rect.center] while interpolating the
+/// rectangle's width and height.
+///
+/// The arc that defines that center of the interpolated rectangle as it morphs
+/// from [begin] to [end] is a [MaterialPointArcTween].
+///
+/// See also:
+///
+///  * [MaterialRectArcTween], A [Tween] that interpolates a [Rect] by having
+///    its opposite corners follow circular arcs.
+///  * [Tween], for a discussion on how to use interpolation objects.
+///  * [MaterialPointArcTween], the analog for [Offset] interpolation.
+///  * [RectTween], which does a linear rectangle interpolation.
+///  * [Hero.createRectTween], which can be used to specify the tween that defines
+///    a hero's path.
+class MaterialRectCenterArcTween extends RectTween {
+  /// Creates a [Tween] for animating [Rect]s along a circular arc.
+  ///
+  /// The [begin] and [end] properties must be non-null before the tween is
+  /// first used, but the arguments can be null if the values are going to be
+  /// filled in later.
+  MaterialRectCenterArcTween({
+    Rect? begin,
+    Rect? end,
+  }) : super(begin: begin, end: end);
+
+  bool _dirty = true;
+
+  void _initialize() {
+    assert(begin != null);
+    assert(end != null);
+    _centerArc = MaterialPointArcTween(
+      begin: begin!.center,
+      end: end!.center,
+    );
+    _dirty = false;
+  }
+
+  /// If [begin] and [end] are non-null, returns a tween that interpolates along
+  /// a circular arc between [begin]'s [Rect.center] and [end]'s [Rect.center].
+  MaterialPointArcTween? get centerArc {
+    if (begin == null || end == null)
+      return null;
+    if (_dirty)
+      _initialize();
+    return _centerArc;
+  }
+  late MaterialPointArcTween _centerArc;
+
+  @override
+  set begin(Rect? value) {
+    if (value != begin) {
+      super.begin = value;
+      _dirty = true;
+    }
+  }
+
+  @override
+  set end(Rect? value) {
+    if (value != end) {
+      super.end = value;
+      _dirty = true;
+    }
+  }
+
+  @override
+  Rect lerp(double t) {
+    if (_dirty)
+      _initialize();
+    if (t == 0.0)
+      return begin!;
+    if (t == 1.0)
+      return end!;
+    final Offset center = _centerArc.lerp(t);
+    final double width = lerpDouble(begin!.width, end!.width, t)!;
+    final double height = lerpDouble(begin!.height, end!.height, t)!;
+    return Rect.fromLTWH(center.dx - width / 2.0, center.dy - height / 2.0, width, height);
+  }
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'MaterialRectCenterArcTween')}($begin \u2192 $end; centerArc=$centerArc)';
+  }
+}
diff --git a/lib/src/material/back_button.dart b/lib/src/material/back_button.dart
new file mode 100644
index 0000000..25de4c7
--- /dev/null
+++ b/lib/src/material/back_button.dart
@@ -0,0 +1,165 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'debug.dart';
+import 'icon_button.dart';
+import 'icons.dart';
+import 'material_localizations.dart';
+import 'theme.dart';
+
+/// A "back" icon that's appropriate for the current [TargetPlatform].
+///
+/// The current platform is determined by querying for the ambient [Theme].
+///
+/// See also:
+///
+///  * [BackButton], an [IconButton] with a [BackButtonIcon] that calls
+///    [Navigator.maybePop] to return to the previous route.
+///  * [IconButton], which is a more general widget for creating buttons
+///    with icons.
+///  * [Icon], a material design icon.
+///  * [ThemeData.platform], which specifies the current platform.
+class BackButtonIcon extends StatelessWidget {
+  /// Creates an icon that shows the appropriate "back" image for
+  /// the current platform (as obtained from the [Theme]).
+  const BackButtonIcon({ Key? key }) : super(key: key);
+
+  /// Returns the appropriate "back" icon for the given `platform`.
+  static IconData _getIconData(TargetPlatform platform) {
+    switch (platform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        return Icons.arrow_back;
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        return Icons.arrow_back_ios;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) => Icon(_getIconData(Theme.of(context).platform));
+}
+
+/// A material design back button.
+///
+/// A [BackButton] is an [IconButton] with a "back" icon appropriate for the
+/// current [TargetPlatform]. When pressed, the back button calls
+/// [Navigator.maybePop] to return to the previous route unless a custom
+/// [onPressed] callback is provided.
+///
+/// When deciding to display a [BackButton], consider using
+/// `ModalRoute.of(context)?.canPop` to check whether the current route can be
+/// popped. If that value is false (e.g., because the current route is the
+/// initial route), the [BackButton] will not have any effect when pressed,
+/// which could frustrate the user.
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// See also:
+///
+///  * [AppBar], which automatically uses a [BackButton] in its
+///    [AppBar.leading] slot when the [Scaffold] has no [Drawer] and the
+///    current [Route] is not the [Navigator]'s first route.
+///  * [BackButtonIcon], which is useful if you need to create a back button
+///    that responds differently to being pressed.
+///  * [IconButton], which is a more general widget for creating buttons with
+///    icons.
+///  * [CloseButton], an alternative which may be more appropriate for leaf
+///    node pages in the navigation tree.
+class BackButton extends StatelessWidget {
+  /// Creates an [IconButton] with the appropriate "back" icon for the current
+  /// target platform.
+  const BackButton({ Key? key, this.color, this.onPressed }) : super(key: key);
+
+  /// The color to use for the icon.
+  ///
+  /// Defaults to the [IconThemeData.color] specified in the ambient [IconTheme],
+  /// which usually matches the ambient [Theme]'s [ThemeData.iconTheme].
+  final Color? color;
+
+  /// An override callback to perform instead of the default behavior which is
+  /// to pop the [Navigator].
+  ///
+  /// It can, for instance, be used to pop the platform's navigation stack
+  /// via [SystemNavigator] instead of Flutter's [Navigator] in add-to-app
+  /// situations.
+  ///
+  /// Defaults to null.
+  final VoidCallback? onPressed;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterialLocalizations(context));
+    return IconButton(
+      icon: const BackButtonIcon(),
+      color: color,
+      tooltip: MaterialLocalizations.of(context).backButtonTooltip,
+      onPressed: () {
+        if (onPressed != null) {
+          onPressed!();
+        } else {
+          Navigator.maybePop(context);
+        }
+      },
+    );
+  }
+}
+
+/// A material design close button.
+///
+/// A [CloseButton] is an [IconButton] with a "close" icon. When pressed, the
+/// close button calls [Navigator.maybePop] to return to the previous route.
+///
+/// Use a [CloseButton] instead of a [BackButton] on fullscreen dialogs or
+/// pages that may solicit additional actions to close.
+///
+/// See also:
+///
+///  * [AppBar], which automatically uses a [CloseButton] in its
+///    [AppBar.leading] slot when appropriate.
+///  * [BackButton], which is more appropriate for middle nodes in the
+///    navigation tree or where pages can be popped instantaneously with
+///    no user data consequence.
+///  * [IconButton], to create other material design icon buttons.
+class CloseButton extends StatelessWidget {
+  /// Creates a Material Design close button.
+  const CloseButton({ Key? key, this.color, this.onPressed }) : super(key: key);
+
+  /// The color to use for the icon.
+  ///
+  /// Defaults to the [IconThemeData.color] specified in the ambient [IconTheme],
+  /// which usually matches the ambient [Theme]'s [ThemeData.iconTheme].
+  final Color? color;
+
+  /// An override callback to perform instead of the default behavior which is
+  /// to pop the [Navigator].
+  ///
+  /// It can, for instance, be used to pop the platform's navigation stack
+  /// via [SystemNavigator] instead of Flutter's [Navigator] in add-to-app
+  /// situations.
+  ///
+  /// Defaults to null.
+  final VoidCallback? onPressed;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterialLocalizations(context));
+    return IconButton(
+      icon: const Icon(Icons.close),
+      color: color,
+      tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
+      onPressed: () {
+        if (onPressed != null) {
+          onPressed!();
+        } else {
+          Navigator.maybePop(context);
+        }
+      },
+    );
+  }
+}
diff --git a/lib/src/material/banner.dart b/lib/src/material/banner.dart
new file mode 100644
index 0000000..752399b
--- /dev/null
+++ b/lib/src/material/banner.dart
@@ -0,0 +1,188 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'banner_theme.dart';
+import 'divider.dart';
+import 'theme.dart';
+
+/// A Material Design banner.
+///
+/// A banner displays an important, succinct message, and provides actions for
+/// users to address (or dismiss the banner). A user action is required for it
+/// to be dismissed.
+///
+/// Banners should be displayed at the top of the screen, below a top app bar.
+/// They are persistent and non-modal, allowing the user to either ignore them or
+/// interact with them at any time.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return MaterialBanner(
+///     padding: const EdgeInsets.all(20),
+///     content: Text("Hey, I am a Material Banner"),
+///     leading: Icon(Icons.agriculture_outlined),
+///     backgroundColor: Colors.grey[300],
+///     actions: <Widget>[
+///       FlatButton(
+///         child: Text("OPEN"),
+///         onPressed: () {},
+///       ),
+///       FlatButton(
+///         child: Text("DISMISS"),
+///         onPressed: () {},
+///       ),
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// The [actions] will be placed beside the [content] if there is only one.
+/// Otherwise, the [actions] will be placed below the [content]. Use
+/// [forceActionsBelow] to override this behavior.
+///
+/// The [actions] and [content] must be provided. An optional leading widget
+/// (typically an [Image]) can also be provided. The [contentTextStyle] and
+/// [backgroundColor] can be provided to customize the banner.
+///
+/// This widget is unrelated to the widgets library [Banner] widget.
+class MaterialBanner extends StatelessWidget {
+  /// Creates a [MaterialBanner].
+  ///
+  /// The [actions], [content], and [forceActionsBelow] must be non-null.
+  /// The [actions.length] must be greater than 0.
+  const MaterialBanner({
+    Key? key,
+    required this.content,
+    this.contentTextStyle,
+    required this.actions,
+    this.leading,
+    this.backgroundColor,
+    this.padding,
+    this.leadingPadding,
+    this.forceActionsBelow = false,
+  }) : assert(content != null),
+       assert(actions != null),
+       assert(forceActionsBelow != null),
+       super(key: key);
+
+  /// The content of the [MaterialBanner].
+  ///
+  /// Typically a [Text] widget.
+  final Widget content;
+
+  /// Style for the text in the [content] of the [MaterialBanner].
+  ///
+  /// If `null`, [MaterialBannerThemeData.contentTextStyle] is used. If that is
+  /// also `null`, [TextTheme.bodyText2] of [ThemeData.textTheme] is used.
+  final TextStyle? contentTextStyle;
+
+  /// The set of actions that are displayed at the bottom or trailing side of
+  /// the [MaterialBanner].
+  ///
+  /// Typically this is a list of [TextButton] widgets.
+  final List<Widget> actions;
+
+  /// The (optional) leading widget of the [MaterialBanner].
+  ///
+  /// Typically an [Icon] widget.
+  final Widget? leading;
+
+  /// The color of the surface of this [MaterialBanner].
+  ///
+  /// If `null`, [MaterialBannerThemeData.backgroundColor] is used. If that is
+  /// also `null`, [ColorScheme.surface] of [ThemeData.colorScheme] is used.
+  final Color? backgroundColor;
+
+  /// The amount of space by which to inset the [content].
+  ///
+  /// If the [actions] are below the [content], this defaults to
+  /// `EdgeInsetsDirectional.only(start: 16.0, top: 24.0, end: 16.0, bottom: 4.0)`.
+  ///
+  /// If the [actions] are trailing the [content], this defaults to
+  /// `EdgeInsetsDirectional.only(start: 16.0, top: 2.0)`.
+  final EdgeInsetsGeometry? padding;
+
+  /// The amount of space by which to inset the [leading] widget.
+  ///
+  /// This defaults to `EdgeInsetsDirectional.only(end: 16.0)`.
+  final EdgeInsetsGeometry? leadingPadding;
+
+  /// An override to force the [actions] to be below the [content] regardless of
+  /// how many there are.
+  ///
+  /// If this is true, the [actions] will be placed below the [content]. If
+  /// this is false, the [actions] will be placed on the trailing side of the
+  /// [content] if [actions]'s length is 1 and below the [content] if greater
+  /// than 1.
+  final bool forceActionsBelow;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(actions.isNotEmpty);
+
+    final ThemeData theme = Theme.of(context);
+    final MaterialBannerThemeData bannerTheme = MaterialBannerTheme.of(context);
+
+    final bool isSingleRow = actions.length == 1 && !forceActionsBelow;
+    final EdgeInsetsGeometry padding = this.padding ?? bannerTheme.padding ?? (isSingleRow
+        ? const EdgeInsetsDirectional.only(start: 16.0, top: 2.0)
+        : const EdgeInsetsDirectional.only(start: 16.0, top: 24.0, end: 16.0, bottom: 4.0));
+    final EdgeInsetsGeometry leadingPadding = this.leadingPadding
+        ?? bannerTheme.leadingPadding
+        ?? const EdgeInsetsDirectional.only(end: 16.0);
+
+    final Widget buttonBar = Container(
+      alignment: AlignmentDirectional.centerEnd,
+      constraints: const BoxConstraints(minHeight: 52.0),
+      padding: const EdgeInsets.symmetric(horizontal: 8),
+      child: OverflowBar(
+        spacing: 8,
+        children: actions,
+      ),
+    );
+
+    final Color backgroundColor = this.backgroundColor
+        ?? bannerTheme.backgroundColor
+        ?? theme.colorScheme.surface;
+    final TextStyle? textStyle = contentTextStyle
+        ?? bannerTheme.contentTextStyle
+        ?? theme.textTheme.bodyText2;
+
+    return Container(
+      color: backgroundColor,
+      child: Column(
+        children: <Widget>[
+          Padding(
+            padding: padding,
+            child: Row(
+              children: <Widget>[
+                if (leading != null)
+                  Padding(
+                    padding: leadingPadding,
+                    child: leading,
+                  ),
+                Expanded(
+                  child: DefaultTextStyle(
+                    style: textStyle!,
+                    child: content,
+                  ),
+                ),
+                if (isSingleRow)
+                  buttonBar,
+              ],
+            ),
+          ),
+          if (!isSingleRow)
+            buttonBar,
+          const Divider(height: 0),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/banner_theme.dart b/lib/src/material/banner_theme.dart
new file mode 100644
index 0000000..56b3e8d
--- /dev/null
+++ b/lib/src/material/banner_theme.dart
@@ -0,0 +1,155 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'theme.dart';
+
+/// Defines the visual properties of [MaterialBanner] widgets.
+///
+/// Descendant widgets obtain the current [MaterialBannerThemeData] object using
+/// `MaterialBannerTheme.of(context)`. Instances of [MaterialBannerThemeData]
+/// can be customized with [MaterialBannerThemeData.copyWith].
+///
+/// Typically a [MaterialBannerThemeData] is specified as part of the overall
+/// [Theme] with [ThemeData.bannerTheme].
+///
+/// All [MaterialBannerThemeData] properties are `null` by default. When null,
+/// the [MaterialBanner] will provide its own defaults.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class MaterialBannerThemeData with Diagnosticable {
+
+  /// Creates a theme that can be used for [MaterialBannerTheme] or
+  /// [ThemeData.bannerTheme].
+  const MaterialBannerThemeData({
+    this.backgroundColor,
+    this.contentTextStyle,
+    this.padding,
+    this.leadingPadding,
+  });
+
+  /// The background color of a [MaterialBanner].
+  final Color? backgroundColor;
+
+  /// Used to configure the [DefaultTextStyle] for the [MaterialBanner.content]
+  /// widget.
+  final TextStyle? contentTextStyle;
+
+  /// The amount of space by which to inset [MaterialBanner.content].
+  final EdgeInsetsGeometry? padding;
+
+  /// The amount of space by which to inset [MaterialBanner.leading].
+  final EdgeInsetsGeometry? leadingPadding;
+
+  /// Creates a copy of this object with the given fields replaced with the
+  /// new values.
+  MaterialBannerThemeData copyWith({
+    Color? backgroundColor,
+    TextStyle? contentTextStyle,
+    EdgeInsetsGeometry? padding,
+    EdgeInsetsGeometry? leadingPadding,
+  }) {
+    return MaterialBannerThemeData(
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      contentTextStyle: contentTextStyle ?? this.contentTextStyle,
+      padding: padding ?? this.padding,
+      leadingPadding: leadingPadding ?? this.leadingPadding,
+    );
+  }
+
+  /// Linearly interpolate between two Banner themes.
+  ///
+  /// The argument `t` must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static MaterialBannerThemeData lerp(MaterialBannerThemeData? a, MaterialBannerThemeData? b, double t) {
+    assert(t != null);
+    return MaterialBannerThemeData(
+      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
+      contentTextStyle: TextStyle.lerp(a?.contentTextStyle, b?.contentTextStyle, t),
+      padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
+      leadingPadding: EdgeInsetsGeometry.lerp(a?.leadingPadding, b?.leadingPadding, t),
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      backgroundColor,
+      contentTextStyle,
+      padding,
+      leadingPadding,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is MaterialBannerThemeData
+        && other.backgroundColor == backgroundColor
+        && other.contentTextStyle == contentTextStyle
+        && other.padding == padding
+        && other.leadingPadding == leadingPadding;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('contentTextStyle', contentTextStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('leadingPadding', leadingPadding, defaultValue: null));
+  }
+}
+
+/// An inherited widget that defines the configuration for
+/// [MaterialBanner]s in this widget's subtree.
+///
+/// Values specified here are used for [MaterialBanner] properties that are not
+/// given an explicit non-null value.
+class MaterialBannerTheme extends InheritedTheme {
+  /// Creates a banner theme that controls the configurations for
+  /// [MaterialBanner]s in its widget subtree.
+  const MaterialBannerTheme({
+    Key? key,
+    this.data,
+    required Widget child,
+  }) : super(key: key, child: child);
+
+  /// The properties for descendant [MaterialBanner] widgets.
+  final MaterialBannerThemeData? data;
+
+  /// The closest instance of this class's [data] value that encloses the given
+  /// context.
+  ///
+  /// If there is no ancestor, it returns [ThemeData.bannerTheme]. Applications
+  /// can assume that the returned value will not be null.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// MaterialBannerThemeData theme = MaterialBannerTheme.of(context);
+  /// ```
+  static MaterialBannerThemeData of(BuildContext context) {
+    final MaterialBannerTheme? bannerTheme = context.dependOnInheritedWidgetOfExactType<MaterialBannerTheme>();
+    return bannerTheme?.data ?? Theme.of(context).bannerTheme;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return MaterialBannerTheme(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(MaterialBannerTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/bottom_app_bar.dart b/lib/src/material/bottom_app_bar.dart
new file mode 100644
index 0000000..5430974
--- /dev/null
+++ b/lib/src/material/bottom_app_bar.dart
@@ -0,0 +1,182 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'bottom_app_bar_theme.dart';
+import 'elevation_overlay.dart';
+import 'material.dart';
+import 'scaffold.dart';
+import 'theme.dart';
+
+// Examples can assume:
+// late Widget bottomAppBarContents;
+
+/// A container that is typically used with [Scaffold.bottomNavigationBar], and
+/// can have a notch along the top that makes room for an overlapping
+/// [FloatingActionButton].
+///
+/// Typically used with a [Scaffold] and a [FloatingActionButton].
+///
+/// {@tool snippet}
+/// ```dart
+/// Scaffold(
+///   bottomNavigationBar: BottomAppBar(
+///     color: Colors.white,
+///     child: bottomAppBarContents,
+///   ),
+///   floatingActionButton: FloatingActionButton(onPressed: null),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [NotchedShape] which calculates the notch for a notched [BottomAppBar].
+///  * [FloatingActionButton] which the [BottomAppBar] makes a notch for.
+///  * [AppBar] for a toolbar that is shown at the top of the screen.
+class BottomAppBar extends StatefulWidget {
+  /// Creates a bottom application bar.
+  ///
+  /// The [clipBehavior] argument defaults to [Clip.none] and must not be null.
+  /// Additionally, [elevation] must be non-negative.
+  ///
+  /// If [color], [elevation], or [shape] are null, their [BottomAppBarTheme] values will be used.
+  /// If the corresponding [BottomAppBarTheme] property is null, then the default
+  /// specified in the property's documentation will be used.
+  const BottomAppBar({
+    Key? key,
+    this.color,
+    this.elevation,
+    this.shape,
+    this.clipBehavior = Clip.none,
+    this.notchMargin = 4.0,
+    this.child,
+  }) : assert(elevation == null || elevation >= 0.0),
+       assert(notchMargin != null),
+       assert(clipBehavior != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  ///
+  /// Typically this the child will be a [Row], with the first child
+  /// being an [IconButton] with the [Icons.menu] icon.
+  final Widget? child;
+
+  /// The bottom app bar's background color.
+  ///
+  /// If this property is null then [BottomAppBarTheme.color] of
+  /// [ThemeData.bottomAppBarTheme] is used. If that's null then
+  /// [ThemeData.bottomAppBarColor] is used.
+  final Color? color;
+
+  /// The z-coordinate at which to place this bottom app bar relative to its
+  /// parent.
+  ///
+  /// This controls the size of the shadow below the bottom app bar. The
+  /// value is non-negative.
+  ///
+  /// If this property is null then [BottomAppBarTheme.elevation] of
+  /// [ThemeData.bottomAppBarTheme] is used. If that's null, the default value
+  /// is 8.
+  final double? elevation;
+
+  /// The notch that is made for the floating action button.
+  ///
+  /// If this property is null then [BottomAppBarTheme.shape] of
+  /// [ThemeData.bottomAppBarTheme] is used. If that's null then the shape will
+  /// be rectangular with no notch.
+  final NotchedShape? shape;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  final Clip clipBehavior;
+
+  /// The margin between the [FloatingActionButton] and the [BottomAppBar]'s
+  /// notch.
+  ///
+  /// Not used if [shape] is null.
+  final double notchMargin;
+
+  @override
+  State createState() => _BottomAppBarState();
+}
+
+class _BottomAppBarState extends State<BottomAppBar> {
+  late ValueListenable<ScaffoldGeometry> geometryListenable;
+  static const double _defaultElevation = 8.0;
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    geometryListenable = Scaffold.geometryOf(context);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final BottomAppBarTheme babTheme = BottomAppBarTheme.of(context);
+    final NotchedShape? notchedShape = widget.shape ?? babTheme.shape;
+    final CustomClipper<Path> clipper = notchedShape != null
+      ? _BottomAppBarClipper(
+        geometry: geometryListenable,
+        shape: notchedShape,
+        notchMargin: widget.notchMargin,
+      )
+      : const ShapeBorderClipper(shape: RoundedRectangleBorder());
+    final double elevation = widget.elevation ?? babTheme.elevation ?? _defaultElevation;
+    final Color color = widget.color ?? babTheme.color ?? Theme.of(context).bottomAppBarColor;
+    final Color effectiveColor = ElevationOverlay.applyOverlay(context, color, elevation);
+    return PhysicalShape(
+      clipper: clipper,
+      elevation: elevation,
+      color: effectiveColor,
+      clipBehavior: widget.clipBehavior,
+      child: Material(
+        type: MaterialType.transparency,
+        child: widget.child == null
+          ? null
+          : SafeArea(child: widget.child!),
+      ),
+    );
+  }
+}
+
+class _BottomAppBarClipper extends CustomClipper<Path> {
+  const _BottomAppBarClipper({
+    required this.geometry,
+    required this.shape,
+    required this.notchMargin,
+  }) : assert(geometry != null),
+       assert(shape != null),
+       assert(notchMargin != null),
+       super(reclip: geometry);
+
+  final ValueListenable<ScaffoldGeometry> geometry;
+  final NotchedShape shape;
+  final double notchMargin;
+
+  @override
+  Path getClip(Size size) {
+    // button is the floating action button's bounding rectangle in the
+    // coordinate system whose origin is at the appBar's top left corner,
+    // or null if there is no floating action button.
+    final Rect? button = geometry.value.floatingActionButtonArea?.translate(
+      0.0,
+      geometry.value.bottomNavigationBarTop! * -1.0,
+    );
+    return shape.getOuterPath(Offset.zero & size, button?.inflate(notchMargin));
+  }
+
+  @override
+  bool shouldReclip(_BottomAppBarClipper oldClipper) {
+    return oldClipper.geometry != geometry
+        || oldClipper.shape != shape
+        || oldClipper.notchMargin != notchMargin;
+  }
+}
diff --git a/lib/src/material/bottom_app_bar_theme.dart b/lib/src/material/bottom_app_bar_theme.dart
new file mode 100644
index 0000000..33f3933
--- /dev/null
+++ b/lib/src/material/bottom_app_bar_theme.dart
@@ -0,0 +1,109 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'theme.dart';
+
+/// Defines default property values for descendant [BottomAppBar] widgets.
+///
+/// Descendant widgets obtain the current [BottomAppBarTheme] object using
+/// `BottomAppBarTheme.of(context)`. Instances of [BottomAppBarTheme] can be
+/// customized with [BottomAppBarTheme.copyWith].
+///
+/// Typically a [BottomAppBarTheme] is specified as part of the overall [Theme]
+/// with [ThemeData.bottomAppBarTheme].
+///
+/// All [BottomAppBarTheme] properties are `null` by default. When null, the
+/// [BottomAppBar] constructor provides defaults.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class BottomAppBarTheme with Diagnosticable {
+  /// Creates a theme that can be used for [ThemeData.bottomAppBarTheme].
+  const BottomAppBarTheme({
+    this.color,
+    this.elevation,
+    this.shape,
+  });
+
+  /// Default value for [BottomAppBar.color].
+  ///
+  /// If null, [BottomAppBar] uses [ThemeData.bottomAppBarColor].
+  final Color? color;
+
+  /// Default value for [BottomAppBar.elevation].
+  final double? elevation;
+
+  /// Default value for [BottomAppBar.shape].
+  final NotchedShape? shape;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  BottomAppBarTheme copyWith({
+    Color? color,
+    double? elevation,
+    NotchedShape? shape,
+  }) {
+    return BottomAppBarTheme(
+      color: color ?? this.color,
+      elevation: elevation ?? this.elevation,
+      shape: shape ?? this.shape,
+    );
+  }
+
+  /// The [ThemeData.bottomAppBarTheme] property of the ambient [Theme].
+  static BottomAppBarTheme of(BuildContext context) {
+    return Theme.of(context).bottomAppBarTheme;
+  }
+
+  /// Linearly interpolate between two BAB themes.
+  ///
+  /// The argument `t` must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static BottomAppBarTheme lerp(BottomAppBarTheme? a, BottomAppBarTheme? b, double t) {
+    assert(t != null);
+    return BottomAppBarTheme(
+      color: Color.lerp(a?.color, b?.color, t),
+      elevation: lerpDouble(a?.elevation, b?.elevation, t),
+      shape: t < 0.5 ? a?.shape : b?.shape,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      color,
+      elevation,
+      shape,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is BottomAppBarTheme
+        && other.color == color
+        && other.elevation == elevation
+        && other.shape == shape;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
+    properties.add(DiagnosticsProperty<NotchedShape>('shape', shape, defaultValue: null));
+  }
+}
diff --git a/lib/src/material/bottom_navigation_bar.dart b/lib/src/material/bottom_navigation_bar.dart
new file mode 100644
index 0000000..c031529
--- /dev/null
+++ b/lib/src/material/bottom_navigation_bar.dart
@@ -0,0 +1,1075 @@
+// Copyright 2014 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.
+
+import 'dart:collection' show Queue;
+import 'dart:math' as math;
+
+import 'package:flute/widgets.dart';
+import 'package:flute/rendering.dart';
+import 'package:vector_math/vector_math_64.dart' show Vector3;
+
+import 'bottom_navigation_bar_theme.dart';
+import 'constants.dart';
+import 'debug.dart';
+import 'ink_well.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'theme.dart';
+import 'tooltip.dart';
+
+/// Defines the layout and behavior of a [BottomNavigationBar].
+///
+/// See also:
+///
+///  * [BottomNavigationBar]
+///  * [BottomNavigationBarItem]
+///  * <https://material.io/design/components/bottom-navigation.html#specs>
+enum BottomNavigationBarType {
+  /// The [BottomNavigationBar]'s [BottomNavigationBarItem]s have fixed width.
+  fixed,
+
+  /// The location and size of the [BottomNavigationBar] [BottomNavigationBarItem]s
+  /// animate and labels fade in when they are tapped.
+  shifting,
+}
+
+/// A material widget that's displayed at the bottom of an app for selecting
+/// among a small number of views, typically between three and five.
+///
+/// The bottom navigation bar consists of multiple items in the form of
+/// text labels, icons, or both, laid out on top of a piece of material. It
+/// provides quick navigation between the top-level views of an app. For larger
+/// screens, side navigation may be a better fit.
+///
+/// A bottom navigation bar is usually used in conjunction with a [Scaffold],
+/// where it is provided as the [Scaffold.bottomNavigationBar] argument.
+///
+/// The bottom navigation bar's [type] changes how its [items] are displayed.
+/// If not specified, then it's automatically set to
+/// [BottomNavigationBarType.fixed] when there are less than four items, and
+/// [BottomNavigationBarType.shifting] otherwise.
+///
+///  * [BottomNavigationBarType.fixed], the default when there are less than
+///    four [items]. The selected item is rendered with the
+///    [selectedItemColor] if it's non-null, otherwise the theme's
+///    [ThemeData.primaryColor] is used. If [backgroundColor] is null, The
+///    navigation bar's background color defaults to the [Material] background
+///    color, [ThemeData.canvasColor] (essentially opaque white).
+///  * [BottomNavigationBarType.shifting], the default when there are four
+///    or more [items]. If [selectedItemColor] is null, all items are rendered
+///    in white. The navigation bar's background color is the same as the
+///    [BottomNavigationBarItem.backgroundColor] of the selected item. In this
+///    case it's assumed that each item will have a different background color
+///    and that background color will contrast well with white.
+///
+/// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+/// This example shows a [BottomNavigationBar] as it is used within a [Scaffold]
+/// widget. The [BottomNavigationBar] has three [BottomNavigationBarItem]
+/// widgets and the [currentIndex] is set to index 0. The selected item is
+/// amber. The `_onItemTapped` function changes the selected item's index
+/// and displays a corresponding message in the center of the [Scaffold].
+///
+/// ![A scaffold with a bottom navigation bar containing three bottom navigation
+/// bar items. The first one is selected.](https://flutter.github.io/assets-for-api-docs/assets/material/bottom_navigation_bar.png)
+///
+/// ```dart
+/// int _selectedIndex = 0;
+/// static const TextStyle optionStyle = TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
+/// static const List<Widget> _widgetOptions = <Widget>[
+///   Text(
+///     'Index 0: Home',
+///     style: optionStyle,
+///   ),
+///   Text(
+///      'Index 1: Business',
+///      style: optionStyle,
+///   ),
+///   Text(
+///      'Index 2: School',
+///      style: optionStyle,
+///   ),
+/// ];
+///
+/// void _onItemTapped(int index) {
+///   setState(() {
+///     _selectedIndex = index;
+///   });
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: AppBar(
+///       title: const Text('BottomNavigationBar Sample'),
+///     ),
+///     body: Center(
+///       child: _widgetOptions.elementAt(_selectedIndex),
+///     ),
+///     bottomNavigationBar: BottomNavigationBar(
+///       items: const <BottomNavigationBarItem>[
+///         BottomNavigationBarItem(
+///           icon: Icon(Icons.home),
+///           label: 'Home',
+///         ),
+///         BottomNavigationBarItem(
+///           icon: Icon(Icons.business),
+///           label: 'Business',
+///         ),
+///         BottomNavigationBarItem(
+///           icon: Icon(Icons.school),
+///           label: 'School',
+///         ),
+///       ],
+///       currentIndex: _selectedIndex,
+///       selectedItemColor: Colors.amber[800],
+///       onTap: _onItemTapped,
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [BottomNavigationBarItem]
+///  * [Scaffold]
+///  * <https://material.io/design/components/bottom-navigation.html>
+class BottomNavigationBar extends StatefulWidget {
+  /// Creates a bottom navigation bar which is typically used as a
+  /// [Scaffold]'s [Scaffold.bottomNavigationBar] argument.
+  ///
+  /// The length of [items] must be at least two and each item's icon and label
+  /// must not be null.
+  ///
+  /// If [type] is null then [BottomNavigationBarType.fixed] is used when there
+  /// are two or three [items], [BottomNavigationBarType.shifting] otherwise.
+  ///
+  /// The [iconSize], [selectedFontSize], [unselectedFontSize], and [elevation]
+  /// arguments must be non-null and non-negative.
+  ///
+  /// If [selectedLabelStyle.color] and [unselectedLabelStyle.color] values
+  /// are non-null, they will be used instead of [selectedItemColor] and
+  /// [unselectedItemColor].
+  ///
+  /// If custom [IconThemeData]s are used, you must provide both
+  /// [selectedIconTheme] and [unselectedIconTheme], and both
+  /// [IconThemeData.color] and [IconThemeData.size] must be set.
+  ///
+  /// If both [selectedLabelStyle.fontSize] and [selectedFontSize] are set,
+  /// [selectedLabelStyle.fontSize] will be used.
+  ///
+  /// Only one of [selectedItemColor] and [fixedColor] can be specified. The
+  /// former is preferred, [fixedColor] only exists for the sake of
+  /// backwards compatibility.
+  ///
+  /// If [showSelectedLabels] is `null`, [BottomNavigationBarThemeData.showSelectedLabels]
+  /// is used. If [BottomNavigationBarThemeData.showSelectedLabels]  is null,
+  /// then [showSelectedLabels] defaults to `true`.
+  ///
+  /// If [showUnselectedLabels] is `null`, [BottomNavigationBarThemeData.showUnselectedLabels]
+  /// is used. If [BottomNavigationBarThemeData.showSelectedLabels] is null,
+  /// then [showUnselectedLabels] defaults to `true` when [type] is
+  /// [BottomNavigationBarType.fixed] and `false` when [type] is
+  /// [BottomNavigationBarType.shifting].
+  BottomNavigationBar({
+    Key? key,
+    required this.items,
+    this.onTap,
+    this.currentIndex = 0,
+    this.elevation,
+    this.type,
+    Color? fixedColor,
+    this.backgroundColor,
+    this.iconSize = 24.0,
+    Color? selectedItemColor,
+    this.unselectedItemColor,
+    this.selectedIconTheme,
+    this.unselectedIconTheme,
+    this.selectedFontSize = 14.0,
+    this.unselectedFontSize = 12.0,
+    this.selectedLabelStyle,
+    this.unselectedLabelStyle,
+    this.showSelectedLabels,
+    this.showUnselectedLabels,
+    this.mouseCursor,
+  }) : assert(items != null),
+       assert(items.length >= 2),
+       assert(
+        items.every((BottomNavigationBarItem item) => item.title != null) ||
+        items.every((BottomNavigationBarItem item) => item.label != null),
+        'Every item must have a non-null title or label',
+       ),
+       assert(0 <= currentIndex && currentIndex < items.length),
+       assert(elevation == null || elevation >= 0.0),
+       assert(iconSize != null && iconSize >= 0.0),
+       assert(
+         selectedItemColor == null || fixedColor == null,
+         'Either selectedItemColor or fixedColor can be specified, but not both'
+       ),
+       assert(selectedFontSize != null && selectedFontSize >= 0.0),
+       assert(unselectedFontSize != null && unselectedFontSize >= 0.0),
+       selectedItemColor = selectedItemColor ?? fixedColor,
+       super(key: key);
+
+  /// Defines the appearance of the button items that are arrayed within the
+  /// bottom navigation bar.
+  final List<BottomNavigationBarItem> items;
+
+  /// Called when one of the [items] is tapped.
+  ///
+  /// The stateful widget that creates the bottom navigation bar needs to keep
+  /// track of the index of the selected [BottomNavigationBarItem] and call
+  /// `setState` to rebuild the bottom navigation bar with the new [currentIndex].
+  final ValueChanged<int>? onTap;
+
+  /// The index into [items] for the current active [BottomNavigationBarItem].
+  final int currentIndex;
+
+  /// The z-coordinate of this [BottomNavigationBar].
+  ///
+  /// If null, defaults to `8.0`.
+  ///
+  /// {@macro flutter.material.material.elevation}
+  final double? elevation;
+
+  /// Defines the layout and behavior of a [BottomNavigationBar].
+  ///
+  /// See documentation for [BottomNavigationBarType] for information on the
+  /// meaning of different types.
+  final BottomNavigationBarType? type;
+
+  /// The value of [selectedItemColor].
+  ///
+  /// This getter only exists for backwards compatibility, the
+  /// [selectedItemColor] property is preferred.
+  Color? get fixedColor => selectedItemColor;
+
+  /// The color of the [BottomNavigationBar] itself.
+  ///
+  /// If [type] is [BottomNavigationBarType.shifting] and the
+  /// [items] have [BottomNavigationBarItem.backgroundColor] set, the [items]'
+  /// backgroundColor will splash and overwrite this color.
+  final Color? backgroundColor;
+
+  /// The size of all of the [BottomNavigationBarItem] icons.
+  ///
+  /// See [BottomNavigationBarItem.icon] for more information.
+  final double iconSize;
+
+  /// The color of the selected [BottomNavigationBarItem.icon] and
+  /// [BottomNavigationBarItem.title].
+  ///
+  /// If null then the [ThemeData.primaryColor] is used.
+  final Color? selectedItemColor;
+
+  /// The color of the unselected [BottomNavigationBarItem.icon] and
+  /// [BottomNavigationBarItem.title]s.
+  ///
+  /// If null then the [ThemeData.unselectedWidgetColor]'s color is used.
+  final Color? unselectedItemColor;
+
+  /// The size, opacity, and color of the icon in the currently selected
+  /// [BottomNavigationBarItem.icon].
+  ///
+  /// If this is not provided, the size will default to [iconSize], the color
+  /// will default to [selectedItemColor].
+  ///
+  /// It this field is provided, it must contain non-null [IconThemeData.size]
+  /// and [IconThemeData.color] properties. Also, if this field is supplied,
+  /// [unselectedIconTheme] must be provided.
+  final IconThemeData? selectedIconTheme;
+
+  /// The size, opacity, and color of the icon in the currently unselected
+  /// [BottomNavigationBarItem.icon]s.
+  ///
+  /// If this is not provided, the size will default to [iconSize], the color
+  /// will default to [unselectedItemColor].
+  ///
+  /// It this field is provided, it must contain non-null [IconThemeData.size]
+  /// and [IconThemeData.color] properties. Also, if this field is supplied,
+  /// [selectedIconTheme] must be provided.
+  final IconThemeData? unselectedIconTheme;
+
+  /// The [TextStyle] of the [BottomNavigationBarItem] labels when they are
+  /// selected.
+  final TextStyle? selectedLabelStyle;
+
+  /// The [TextStyle] of the [BottomNavigationBarItem] labels when they are not
+  /// selected.
+  final TextStyle? unselectedLabelStyle;
+
+  /// The font size of the [BottomNavigationBarItem] labels when they are selected.
+  ///
+  /// If [TextStyle.fontSize] of [selectedLabelStyle] is non-null, it will be
+  /// used instead of this.
+  ///
+  /// Defaults to `14.0`.
+  final double selectedFontSize;
+
+  /// The font size of the [BottomNavigationBarItem] labels when they are not
+  /// selected.
+  ///
+  /// If [TextStyle.fontSize] of [unselectedLabelStyle] is non-null, it will be
+  /// used instead of this.
+  ///
+  /// Defaults to `12.0`.
+  final double unselectedFontSize;
+
+  /// Whether the labels are shown for the unselected [BottomNavigationBarItem]s.
+  final bool? showUnselectedLabels;
+
+  /// Whether the labels are shown for the selected [BottomNavigationBarItem].
+  final bool? showSelectedLabels;
+
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// tiles.
+  ///
+  /// If this property is null, [SystemMouseCursors.click] will be used.
+  final MouseCursor? mouseCursor;
+
+  @override
+  _BottomNavigationBarState createState() => _BottomNavigationBarState();
+}
+
+// This represents a single tile in the bottom navigation bar. It is intended
+// to go into a flex container.
+class _BottomNavigationTile extends StatelessWidget {
+  const _BottomNavigationTile(
+    this.type,
+    this.item,
+    this.animation,
+    this.iconSize, {
+    this.onTap,
+    this.colorTween,
+    this.flex,
+    this.selected = false,
+    required this.selectedLabelStyle,
+    required this.unselectedLabelStyle,
+    required this.selectedIconTheme,
+    required this.unselectedIconTheme,
+    required this.showSelectedLabels,
+    required this.showUnselectedLabels,
+    this.indexLabel,
+    required this.mouseCursor,
+    }) : assert(type != null),
+         assert(item != null),
+         assert(animation != null),
+         assert(selected != null),
+         assert(selectedLabelStyle != null),
+         assert(unselectedLabelStyle != null),
+         assert(mouseCursor != null);
+
+  final BottomNavigationBarType type;
+  final BottomNavigationBarItem item;
+  final Animation<double> animation;
+  final double iconSize;
+  final VoidCallback? onTap;
+  final ColorTween? colorTween;
+  final double? flex;
+  final bool selected;
+  final IconThemeData? selectedIconTheme;
+  final IconThemeData? unselectedIconTheme;
+  final TextStyle selectedLabelStyle;
+  final TextStyle unselectedLabelStyle;
+  final String? indexLabel;
+  final bool showSelectedLabels;
+  final bool showUnselectedLabels;
+  final MouseCursor mouseCursor;
+
+  @override
+  Widget build(BuildContext context) {
+    // In order to use the flex container to grow the tile during animation, we
+    // need to divide the changes in flex allotment into smaller pieces to
+    // produce smooth animation. We do this by multiplying the flex value
+    // (which is an integer) by a large number.
+    final int size;
+
+    final double selectedFontSize = selectedLabelStyle.fontSize!;
+
+    final double selectedIconSize = selectedIconTheme?.size ?? iconSize;
+    final double unselectedIconSize = unselectedIconTheme?.size ?? iconSize;
+
+    // The amount that the selected icon is bigger than the unselected icons,
+    // (or zero if the selected icon is not bigger than the unselected icons).
+    final double selectedIconDiff = math.max(selectedIconSize - unselectedIconSize, 0);
+    // The amount that the unselected icons are bigger than the selected icon,
+    // (or zero if the unselected icons are not any bigger than the selected icon).
+    final double unselectedIconDiff = math.max(unselectedIconSize - selectedIconSize, 0);
+
+    // The effective tool tip message to be shown on the BottomNavigationBarItem.
+    final String? effectiveTooltip = item.tooltip == '' ? null : item.tooltip ?? item.label;
+
+    // Defines the padding for the animating icons + labels.
+    //
+    // The animations go from "Unselected":
+    // =======
+    // |      <-- Padding equal to the text height + 1/2 selectedIconDiff.
+    // |  ☆
+    // | text <-- Invisible text + padding equal to 1/2 selectedIconDiff.
+    // =======
+    //
+    // To "Selected":
+    //
+    // =======
+    // |      <-- Padding equal to 1/2 text height + 1/2 unselectedIconDiff.
+    // |  ☆
+    // | text
+    // |      <-- Padding equal to 1/2 text height + 1/2 unselectedIconDiff.
+    // =======
+    double bottomPadding;
+    double topPadding;
+    if (showSelectedLabels && !showUnselectedLabels) {
+      bottomPadding = Tween<double>(
+        begin: selectedIconDiff / 2.0,
+        end: selectedFontSize / 2.0 - unselectedIconDiff / 2.0,
+      ).evaluate(animation);
+      topPadding = Tween<double>(
+        begin: selectedFontSize + selectedIconDiff / 2.0,
+        end: selectedFontSize / 2.0 - unselectedIconDiff / 2.0,
+      ).evaluate(animation);
+    } else if (!showSelectedLabels && !showUnselectedLabels) {
+      bottomPadding = Tween<double>(
+        begin: selectedIconDiff / 2.0,
+        end: unselectedIconDiff / 2.0,
+      ).evaluate(animation);
+      topPadding = Tween<double>(
+        begin: selectedFontSize + selectedIconDiff / 2.0,
+        end: selectedFontSize + unselectedIconDiff / 2.0,
+      ).evaluate(animation);
+    } else {
+      bottomPadding = Tween<double>(
+        begin: selectedFontSize / 2.0 + selectedIconDiff / 2.0,
+        end: selectedFontSize / 2.0 + unselectedIconDiff / 2.0,
+      ).evaluate(animation);
+      topPadding = Tween<double>(
+        begin: selectedFontSize / 2.0 + selectedIconDiff / 2.0,
+        end: selectedFontSize / 2.0 + unselectedIconDiff / 2.0,
+      ).evaluate(animation);
+    }
+
+    switch (type) {
+      case BottomNavigationBarType.fixed:
+        size = 1;
+        break;
+      case BottomNavigationBarType.shifting:
+        size = (flex! * 1000.0).round();
+        break;
+    }
+
+    Widget result = InkResponse(
+      onTap: onTap,
+      mouseCursor: mouseCursor,
+      child: Padding(
+        padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.center,
+          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          mainAxisSize: MainAxisSize.min,
+          children: <Widget>[
+            _TileIcon(
+              colorTween: colorTween!,
+              animation: animation,
+              iconSize: iconSize,
+              selected: selected,
+              item: item,
+              selectedIconTheme: selectedIconTheme,
+              unselectedIconTheme: unselectedIconTheme,
+            ),
+            _Label(
+              colorTween: colorTween!,
+              animation: animation,
+              item: item,
+              selectedLabelStyle: selectedLabelStyle,
+              unselectedLabelStyle: unselectedLabelStyle,
+              showSelectedLabels: showSelectedLabels,
+              showUnselectedLabels: showUnselectedLabels,
+            ),
+          ],
+        ),
+      ),
+    );
+
+    if (effectiveTooltip != null) {
+      result = Tooltip(
+        message: effectiveTooltip,
+        preferBelow: false,
+        verticalOffset: selectedIconSize + selectedFontSize,
+        excludeFromSemantics: true,
+        child: result,
+      );
+    }
+
+    result = Semantics(
+      selected: selected,
+      container: true,
+      child: Stack(
+        children: <Widget>[
+          result,
+          Semantics(
+            label: indexLabel,
+          ),
+        ],
+      ),
+    );
+
+    return Expanded(
+      flex: size,
+      child: result,
+    );
+  }
+}
+
+
+class _TileIcon extends StatelessWidget {
+  const _TileIcon({
+    Key? key,
+    required this.colorTween,
+    required this.animation,
+    required this.iconSize,
+    required this.selected,
+    required this.item,
+    required this.selectedIconTheme,
+    required this.unselectedIconTheme,
+  }) : assert(selected != null),
+       assert(item != null),
+       super(key: key);
+
+  final ColorTween colorTween;
+  final Animation<double> animation;
+  final double iconSize;
+  final bool selected;
+  final BottomNavigationBarItem item;
+  final IconThemeData? selectedIconTheme;
+  final IconThemeData? unselectedIconTheme;
+
+  @override
+  Widget build(BuildContext context) {
+    final Color? iconColor = colorTween.evaluate(animation);
+    final IconThemeData defaultIconTheme = IconThemeData(
+      color: iconColor,
+      size: iconSize,
+    );
+    final IconThemeData iconThemeData = IconThemeData.lerp(
+      defaultIconTheme.merge(unselectedIconTheme),
+      defaultIconTheme.merge(selectedIconTheme),
+      animation.value,
+    );
+
+    return Align(
+      alignment: Alignment.topCenter,
+      heightFactor: 1.0,
+      child: Container(
+        child: IconTheme(
+          data: iconThemeData,
+          child: selected ? item.activeIcon : item.icon,
+        ),
+      ),
+    );
+  }
+}
+
+class _Label extends StatelessWidget {
+  const _Label({
+    Key? key,
+    required this.colorTween,
+    required this.animation,
+    required this.item,
+    required this.selectedLabelStyle,
+    required this.unselectedLabelStyle,
+    required this.showSelectedLabels,
+    required this.showUnselectedLabels,
+  }) : assert(colorTween != null),
+       assert(animation != null),
+       assert(item != null),
+       assert(selectedLabelStyle != null),
+       assert(unselectedLabelStyle != null),
+       assert(showSelectedLabels != null),
+       assert(showUnselectedLabels != null),
+       super(key: key);
+
+  final ColorTween colorTween;
+  final Animation<double> animation;
+  final BottomNavigationBarItem item;
+  final TextStyle selectedLabelStyle;
+  final TextStyle unselectedLabelStyle;
+  final bool showSelectedLabels;
+  final bool showUnselectedLabels;
+
+  @override
+  Widget build(BuildContext context) {
+    final double? selectedFontSize = selectedLabelStyle.fontSize;
+    final double? unselectedFontSize = unselectedLabelStyle.fontSize;
+
+    final TextStyle customStyle = TextStyle.lerp(
+      unselectedLabelStyle,
+      selectedLabelStyle,
+      animation.value,
+    )!;
+    Widget text = DefaultTextStyle.merge(
+      style: customStyle.copyWith(
+        fontSize: selectedFontSize,
+        color: colorTween.evaluate(animation),
+      ),
+      // The font size should grow here when active, but because of the way
+      // font rendering works, it doesn't grow smoothly if we just animate
+      // the font size, so we use a transform instead.
+      child: Transform(
+        transform: Matrix4.diagonal3(
+          Vector3.all(
+            Tween<double>(
+              begin: unselectedFontSize! / selectedFontSize!,
+              end: 1.0,
+            ).evaluate(animation),
+          ),
+        ),
+        alignment: Alignment.bottomCenter,
+        child: item.title ?? Text(item.label!),
+      ),
+    );
+
+    if (!showUnselectedLabels && !showSelectedLabels) {
+      // Never show any labels.
+      text = Opacity(
+        alwaysIncludeSemantics: true,
+        opacity: 0.0,
+        child: text,
+      );
+    } else if (!showUnselectedLabels) {
+      // Fade selected labels in.
+      text = FadeTransition(
+        alwaysIncludeSemantics: true,
+        opacity: animation,
+        child: text,
+      );
+    } else if (!showSelectedLabels) {
+      // Fade selected labels out.
+      text = FadeTransition(
+        alwaysIncludeSemantics: true,
+        opacity: Tween<double>(begin: 1.0, end: 0.0).animate(animation),
+        child: text,
+      );
+    }
+
+    text = Align(
+      alignment: Alignment.bottomCenter,
+      heightFactor: 1.0,
+      child: Container(child: text),
+    );
+
+    if (item.label != null) {
+      // Do not grow text in bottom navigation bar when we can show a tooltip
+      // instead.
+      final MediaQueryData mediaQueryData = MediaQuery.of(context);
+      text = MediaQuery(
+        data: mediaQueryData.copyWith(
+          textScaleFactor: math.min(1.0, mediaQueryData.textScaleFactor),
+        ),
+        child: text,
+      );
+    }
+
+    return text;
+  }
+}
+
+class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerProviderStateMixin {
+  List<AnimationController> _controllers = <AnimationController>[];
+  late List<CurvedAnimation> _animations;
+
+  // A queue of color splashes currently being animated.
+  final Queue<_Circle> _circles = Queue<_Circle>();
+
+  // Last splash circle's color, and the final color of the control after
+  // animation is complete.
+  Color? _backgroundColor;
+
+  static final Animatable<double> _flexTween = Tween<double>(begin: 1.0, end: 1.5);
+
+  void _resetState() {
+    for (final AnimationController controller in _controllers)
+      controller.dispose();
+    for (final _Circle circle in _circles)
+      circle.dispose();
+    _circles.clear();
+
+    _controllers = List<AnimationController>.generate(widget.items.length, (int index) {
+      return AnimationController(
+        duration: kThemeAnimationDuration,
+        vsync: this,
+      )..addListener(_rebuild);
+    });
+    _animations = List<CurvedAnimation>.generate(widget.items.length, (int index) {
+      return CurvedAnimation(
+        parent: _controllers[index],
+        curve: Curves.fastOutSlowIn,
+        reverseCurve: Curves.fastOutSlowIn.flipped,
+      );
+    });
+    _controllers[widget.currentIndex].value = 1.0;
+    _backgroundColor = widget.items[widget.currentIndex].backgroundColor;
+  }
+
+  // Computes the default value for the [type] parameter.
+  //
+  // If type is provided, it is returned. Next, if the bottom navigation bar
+  // theme provides a type, it is used. Finally, the default behavior will be
+  // [BottomNavigationBarType.fixed] for 3 or fewer items, and
+  // [BottomNavigationBarType.shifting] is used for 4+ items.
+  BottomNavigationBarType get _effectiveType {
+    return widget.type
+      ?? BottomNavigationBarTheme.of(context).type
+      ?? (widget.items.length <= 3 ? BottomNavigationBarType.fixed : BottomNavigationBarType.shifting);
+  }
+
+  // Computes the default value for the [showUnselected] parameter.
+  //
+  // Unselected labels are shown by default for [BottomNavigationBarType.fixed],
+  // and hidden by default for [BottomNavigationBarType.shifting].
+  bool get _defaultShowUnselected {
+    switch (_effectiveType) {
+      case BottomNavigationBarType.shifting:
+        return false;
+      case BottomNavigationBarType.fixed:
+        return true;
+    }
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _resetState();
+  }
+
+  void _rebuild() {
+    setState(() {
+      // Rebuilding when any of the controllers tick, i.e. when the items are
+      // animated.
+    });
+  }
+
+  @override
+  void dispose() {
+    for (final AnimationController controller in _controllers)
+      controller.dispose();
+    for (final _Circle circle in _circles)
+      circle.dispose();
+    super.dispose();
+  }
+
+  double _evaluateFlex(Animation<double> animation) => _flexTween.evaluate(animation);
+
+  void _pushCircle(int index) {
+    if (widget.items[index].backgroundColor != null) {
+      _circles.add(
+        _Circle(
+          state: this,
+          index: index,
+          color: widget.items[index].backgroundColor!,
+          vsync: this,
+        )..controller.addStatusListener(
+          (AnimationStatus status) {
+            switch (status) {
+              case AnimationStatus.completed:
+                setState(() {
+                  final _Circle circle = _circles.removeFirst();
+                  _backgroundColor = circle.color;
+                  circle.dispose();
+                });
+                break;
+              case AnimationStatus.dismissed:
+              case AnimationStatus.forward:
+              case AnimationStatus.reverse:
+                break;
+            }
+          },
+        ),
+      );
+    }
+  }
+
+  @override
+  void didUpdateWidget(BottomNavigationBar oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    // No animated segue if the length of the items list changes.
+    if (widget.items.length != oldWidget.items.length) {
+      _resetState();
+      return;
+    }
+
+    if (widget.currentIndex != oldWidget.currentIndex) {
+      switch (_effectiveType) {
+        case BottomNavigationBarType.fixed:
+          break;
+        case BottomNavigationBarType.shifting:
+          _pushCircle(widget.currentIndex);
+          break;
+      }
+      _controllers[oldWidget.currentIndex].reverse();
+      _controllers[widget.currentIndex].forward();
+    } else {
+      if (_backgroundColor != widget.items[widget.currentIndex].backgroundColor)
+        _backgroundColor = widget.items[widget.currentIndex].backgroundColor;
+    }
+  }
+
+  // If the given [TextStyle] has a non-null `fontSize`, it should be used.
+  // Otherwise, the [selectedFontSize] parameter should be used.
+  static TextStyle _effectiveTextStyle(TextStyle? textStyle, double fontSize) {
+    textStyle ??= const TextStyle();
+    // Prefer the font size on textStyle if present.
+    return textStyle.fontSize == null ? textStyle.copyWith(fontSize: fontSize) : textStyle;
+  }
+
+  List<Widget> _createTiles() {
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    assert(localizations != null);
+
+    final ThemeData themeData = Theme.of(context);
+    final BottomNavigationBarThemeData bottomTheme = BottomNavigationBarTheme.of(context);
+
+    final TextStyle effectiveSelectedLabelStyle =
+      _effectiveTextStyle(
+        widget.selectedLabelStyle ?? bottomTheme.selectedLabelStyle,
+        widget.selectedFontSize,
+      );
+    final TextStyle effectiveUnselectedLabelStyle =
+      _effectiveTextStyle(
+        widget.unselectedLabelStyle ?? bottomTheme.unselectedLabelStyle,
+        widget.unselectedFontSize,
+      );
+
+    final Color themeColor;
+    switch (themeData.brightness) {
+      case Brightness.light:
+        themeColor = themeData.primaryColor;
+        break;
+      case Brightness.dark:
+        themeColor = themeData.accentColor;
+        break;
+    }
+
+    final ColorTween colorTween;
+    switch (_effectiveType) {
+      case BottomNavigationBarType.fixed:
+        colorTween = ColorTween(
+          begin: widget.unselectedItemColor
+            ?? bottomTheme.unselectedItemColor
+            ?? themeData.unselectedWidgetColor,
+          end: widget.selectedItemColor
+            ?? bottomTheme.selectedItemColor
+            ?? widget.fixedColor
+            ?? themeColor,
+        );
+        break;
+      case BottomNavigationBarType.shifting:
+        colorTween = ColorTween(
+          begin: widget.unselectedItemColor
+            ?? bottomTheme.unselectedItemColor
+            ?? themeData.colorScheme.surface,
+          end: widget.selectedItemColor
+            ?? bottomTheme.selectedItemColor
+            ?? themeData.colorScheme.surface,
+        );
+        break;
+    }
+    final MouseCursor effectiveMouseCursor = widget.mouseCursor ?? SystemMouseCursors.click;
+
+    final List<Widget> tiles = <Widget>[];
+    for (int i = 0; i < widget.items.length; i++) {
+      tiles.add(_BottomNavigationTile(
+        _effectiveType,
+        widget.items[i],
+        _animations[i],
+        widget.iconSize,
+        selectedIconTheme: widget.selectedIconTheme ?? bottomTheme.selectedIconTheme,
+        unselectedIconTheme: widget.unselectedIconTheme ?? bottomTheme.unselectedIconTheme,
+        selectedLabelStyle: effectiveSelectedLabelStyle,
+        unselectedLabelStyle: effectiveUnselectedLabelStyle,
+        onTap: () {
+          if (widget.onTap != null)
+            widget.onTap!(i);
+        },
+        colorTween: colorTween,
+        flex: _evaluateFlex(_animations[i]),
+        selected: i == widget.currentIndex,
+        showSelectedLabels: widget.showSelectedLabels ?? bottomTheme.showSelectedLabels ?? true,
+        showUnselectedLabels: widget.showUnselectedLabels ?? bottomTheme.showUnselectedLabels ?? _defaultShowUnselected,
+        indexLabel: localizations.tabLabel(tabIndex: i + 1, tabCount: widget.items.length),
+        mouseCursor: effectiveMouseCursor,
+      ));
+    }
+    return tiles;
+  }
+
+  Widget _createContainer(List<Widget> tiles) {
+    return DefaultTextStyle.merge(
+      overflow: TextOverflow.ellipsis,
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: tiles,
+      ),
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasDirectionality(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+    assert(debugCheckHasMediaQuery(context));
+    assert(Overlay.of(context, debugRequiredFor: widget) != null);
+
+    final BottomNavigationBarThemeData bottomTheme = BottomNavigationBarTheme.of(context);
+
+    // Labels apply up to _bottomMargin padding. Remainder is media padding.
+    final double additionalBottomPadding = math.max(MediaQuery.of(context).padding.bottom - widget.selectedFontSize / 2.0, 0.0);
+    Color? backgroundColor;
+    switch (_effectiveType) {
+      case BottomNavigationBarType.fixed:
+        backgroundColor = widget.backgroundColor ?? bottomTheme.backgroundColor;
+        break;
+      case BottomNavigationBarType.shifting:
+        backgroundColor = _backgroundColor;
+        break;
+    }
+    return Semantics(
+      explicitChildNodes: true,
+      child: Material(
+        elevation: widget.elevation ?? bottomTheme.elevation ?? 8.0,
+        color: backgroundColor,
+        child: ConstrainedBox(
+          constraints: BoxConstraints(minHeight: kBottomNavigationBarHeight + additionalBottomPadding),
+          child: CustomPaint(
+            painter: _RadialPainter(
+              circles: _circles.toList(),
+              textDirection: Directionality.of(context),
+            ),
+            child: Material( // Splashes.
+              type: MaterialType.transparency,
+              child: Padding(
+                padding: EdgeInsets.only(bottom: additionalBottomPadding),
+                child: MediaQuery.removePadding(
+                  context: context,
+                  removeBottom: true,
+                  child: _createContainer(_createTiles()),
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+// Describes an animating color splash circle.
+class _Circle {
+  _Circle({
+    required this.state,
+    required this.index,
+    required this.color,
+    required TickerProvider vsync,
+  }) : assert(state != null),
+       assert(index != null),
+       assert(color != null) {
+    controller = AnimationController(
+      duration: kThemeAnimationDuration,
+      vsync: vsync,
+    );
+    animation = CurvedAnimation(
+      parent: controller,
+      curve: Curves.fastOutSlowIn,
+    );
+    controller.forward();
+  }
+
+  final _BottomNavigationBarState state;
+  final int index;
+  final Color color;
+  late AnimationController controller;
+  late CurvedAnimation animation;
+
+  double get horizontalLeadingOffset {
+    double weightSum(Iterable<Animation<double>> animations) {
+      // We're adding flex values instead of animation values to produce correct
+      // ratios.
+      return animations.map<double>(state._evaluateFlex).fold<double>(0.0, (double sum, double value) => sum + value);
+    }
+
+    final double allWeights = weightSum(state._animations);
+    // These weights sum to the start edge of the indexed item.
+    final double leadingWeights = weightSum(state._animations.sublist(0, index));
+
+    // Add half of its flex value in order to get to the center.
+    return (leadingWeights + state._evaluateFlex(state._animations[index]) / 2.0) / allWeights;
+  }
+
+  void dispose() {
+    controller.dispose();
+  }
+}
+
+// Paints the animating color splash circles.
+class _RadialPainter extends CustomPainter {
+  _RadialPainter({
+    required this.circles,
+    required this.textDirection,
+  }) : assert(circles != null),
+       assert(textDirection != null);
+
+  final List<_Circle> circles;
+  final TextDirection textDirection;
+
+  // Computes the maximum radius attainable such that at least one of the
+  // bounding rectangle's corners touches the edge of the circle. Drawing a
+  // circle larger than this radius is not needed, since there is no perceivable
+  // difference within the cropped rectangle.
+  static double _maxRadius(Offset center, Size size) {
+    final double maxX = math.max(center.dx, size.width - center.dx);
+    final double maxY = math.max(center.dy, size.height - center.dy);
+    return math.sqrt(maxX * maxX + maxY * maxY);
+  }
+
+  @override
+  bool shouldRepaint(_RadialPainter oldPainter) {
+    if (textDirection != oldPainter.textDirection)
+      return true;
+    if (circles == oldPainter.circles)
+      return false;
+    if (circles.length != oldPainter.circles.length)
+      return true;
+    for (int i = 0; i < circles.length; i += 1)
+      if (circles[i] != oldPainter.circles[i])
+        return true;
+    return false;
+  }
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    for (final _Circle circle in circles) {
+      final Paint paint = Paint()..color = circle.color;
+      final Rect rect = Rect.fromLTWH(0.0, 0.0, size.width, size.height);
+      canvas.clipRect(rect);
+      final double leftFraction;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          leftFraction = 1.0 - circle.horizontalLeadingOffset;
+          break;
+        case TextDirection.ltr:
+          leftFraction = circle.horizontalLeadingOffset;
+          break;
+      }
+      final Offset center = Offset(leftFraction * size.width, size.height / 2.0);
+      final Tween<double> radiusTween = Tween<double>(
+        begin: 0.0,
+        end: _maxRadius(center, size),
+      );
+      canvas.drawCircle(
+        center,
+        radiusTween.transform(circle.animation.value),
+        paint,
+      );
+    }
+  }
+}
diff --git a/lib/src/material/bottom_navigation_bar_theme.dart b/lib/src/material/bottom_navigation_bar_theme.dart
new file mode 100644
index 0000000..88a578d
--- /dev/null
+++ b/lib/src/material/bottom_navigation_bar_theme.dart
@@ -0,0 +1,269 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'bottom_navigation_bar.dart';
+import 'theme.dart';
+
+/// Defines default property values for descendant [BottomNavigationBar]
+/// widgets.
+///
+/// Descendant widgets obtain the current [BottomNavigationBarThemeData] object
+/// using `BottomNavigationBarTheme.of(context)`. Instances of
+/// [BottomNavigationBarThemeData] can be customized with
+/// [BottomNavigationBarThemeData.copyWith].
+///
+/// Typically a [BottomNavigationBarThemeData] is specified as part of the
+/// overall [Theme] with [ThemeData.bottomNavigationBarTheme].
+///
+/// All [BottomNavigationBarThemeData] properties are `null` by default. When
+/// null, the [BottomNavigationBar]'s build method provides defaults.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class BottomNavigationBarThemeData with Diagnosticable {
+  /// Creates a theme that can be used for [ThemeData.bottomNavigationBarTheme].
+  const BottomNavigationBarThemeData({
+    this.backgroundColor,
+    this.elevation,
+    this.selectedIconTheme,
+    this.unselectedIconTheme,
+    this.selectedItemColor,
+    this.unselectedItemColor,
+    this.selectedLabelStyle,
+    this.unselectedLabelStyle,
+    this.showSelectedLabels,
+    this.showUnselectedLabels,
+    this.type,
+  });
+
+  /// The color of the [BottomNavigationBar] itself.
+  ///
+  /// See [BottomNavigationBar.backgroundColor].
+  final Color? backgroundColor;
+
+  /// The z-coordinate of the [BottomNavigationBar].
+  ///
+  /// See [BottomNavigationBar.elevation].
+  final double? elevation;
+
+  /// The size, opacity, and color of the icon in the currently selected
+  /// [BottomNavigationBarItem.icon].
+  ///
+  /// If [BottomNavigationBar.selectedIconTheme] is non-null on the widget,
+  /// the whole [IconThemeData] from the widget will be used over this
+  /// [selectedIconTheme].
+  ///
+  /// See [BottomNavigationBar.selectedIconTheme].
+  final IconThemeData? selectedIconTheme;
+
+  /// The size, opacity, and color of the icon in the currently unselected
+  /// [BottomNavigationBarItem.icon]s.
+  ///
+  /// If [BottomNavigationBar.unselectedIconTheme] is non-null on the widget,
+  /// the whole [IconThemeData] from the widget will be used over this
+  /// [unselectedIconTheme].
+  ///
+  /// See [BottomNavigationBar.unselectedIconTheme].
+  final IconThemeData? unselectedIconTheme;
+
+  /// The color of the selected [BottomNavigationBarItem.icon] and
+  /// [BottomNavigationBarItem.title].
+  ///
+  /// See [BottomNavigationBar.selectedItemColor].
+  final Color? selectedItemColor;
+
+  /// The color of the unselected [BottomNavigationBarItem.icon] and
+  /// [BottomNavigationBarItem.title]s.
+  ///
+  /// See [BottomNavigationBar.unselectedItemColor].
+  final Color? unselectedItemColor;
+
+  /// The [TextStyle] of the [BottomNavigationBarItem] labels when they are
+  /// selected.
+  ///
+  /// See [BottomNavigationBar.selectedLabelStyle].
+  final TextStyle? selectedLabelStyle;
+
+  /// The [TextStyle] of the [BottomNavigationBarItem] labels when they are not
+  /// selected.
+  ///
+  /// See [BottomNavigationBar.unselectedLabelStyle].
+  final TextStyle? unselectedLabelStyle;
+
+  /// Whether the labels are shown for the selected [BottomNavigationBarItem].
+  ///
+  /// See [BottomNavigationBar.showSelectedLabels].
+  final bool? showSelectedLabels;
+
+  /// Whether the labels are shown for the unselected [BottomNavigationBarItem]s.
+  ///
+  /// See [BottomNavigationBar.showUnselectedLabels].
+  final bool? showUnselectedLabels;
+
+  /// Defines the layout and behavior of a [BottomNavigationBar].
+  ///
+  /// See [BottomNavigationBar.type].
+  final BottomNavigationBarType? type;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  BottomNavigationBarThemeData copyWith({
+    Color? backgroundColor,
+    double? elevation,
+    IconThemeData? selectedIconTheme,
+    IconThemeData? unselectedIconTheme,
+    Color? selectedItemColor,
+    Color? unselectedItemColor,
+    TextStyle? selectedLabelStyle,
+    TextStyle? unselectedLabelStyle,
+    bool? showSelectedLabels,
+    bool? showUnselectedLabels,
+    BottomNavigationBarType? type,
+  }) {
+    return BottomNavigationBarThemeData(
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      elevation: elevation ?? this.elevation,
+      selectedIconTheme: selectedIconTheme ?? this.selectedIconTheme,
+      unselectedIconTheme: unselectedIconTheme ?? this.unselectedIconTheme,
+      selectedItemColor: selectedItemColor ?? this.selectedItemColor,
+      unselectedItemColor: unselectedItemColor ?? this.unselectedItemColor,
+      selectedLabelStyle: selectedLabelStyle ?? this.selectedLabelStyle,
+      unselectedLabelStyle: unselectedLabelStyle ?? this.unselectedLabelStyle,
+      showSelectedLabels: showSelectedLabels ?? this.showSelectedLabels,
+      showUnselectedLabels: showUnselectedLabels ?? this.showUnselectedLabels,
+      type: type ?? this.type,
+    );
+  }
+
+  /// Linearly interpolate between two [BottomNavigationBarThemeData].
+  ///
+  /// The argument `t` must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static BottomNavigationBarThemeData lerp(BottomNavigationBarThemeData? a, BottomNavigationBarThemeData? b, double t) {
+    assert(t != null);
+    return BottomNavigationBarThemeData(
+      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
+      elevation: lerpDouble(a?.elevation, b?.elevation, t),
+      selectedIconTheme: IconThemeData.lerp(a?.selectedIconTheme, b?.selectedIconTheme, t),
+      unselectedIconTheme: IconThemeData.lerp(a?.unselectedIconTheme, b?.unselectedIconTheme, t),
+      selectedItemColor: Color.lerp(a?.selectedItemColor, b?.selectedItemColor, t),
+      unselectedItemColor: Color.lerp(a?.unselectedItemColor, b?.unselectedItemColor, t),
+      selectedLabelStyle: TextStyle.lerp(a?.selectedLabelStyle, b?.selectedLabelStyle, t),
+      unselectedLabelStyle: TextStyle.lerp(a?.unselectedLabelStyle, b?.unselectedLabelStyle, t),
+      showSelectedLabels: t < 0.5 ? a?.showSelectedLabels : b?.showSelectedLabels,
+      showUnselectedLabels: t < 0.5 ? a?.showUnselectedLabels : b?.showUnselectedLabels,
+      type: t < 0.5 ? a?.type : b?.type,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      backgroundColor,
+      elevation,
+      selectedIconTheme,
+      unselectedIconTheme,
+      selectedItemColor,
+      unselectedItemColor,
+      selectedLabelStyle,
+      unselectedLabelStyle,
+      showSelectedLabels,
+      showUnselectedLabels,
+      type,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is BottomNavigationBarThemeData
+        && other.backgroundColor == backgroundColor
+        && other.elevation == elevation
+        && other.selectedIconTheme == selectedIconTheme
+        && other.unselectedIconTheme == unselectedIconTheme
+        && other.selectedItemColor == selectedItemColor
+        && other.unselectedItemColor == unselectedItemColor
+        && other.selectedLabelStyle == selectedLabelStyle
+        && other.unselectedLabelStyle == unselectedLabelStyle
+        && other.showSelectedLabels == showSelectedLabels
+        && other.showUnselectedLabels == showUnselectedLabels
+        && other.type == type;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
+    properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
+    properties.add(DiagnosticsProperty<IconThemeData>('selectedIconTheme', selectedIconTheme, defaultValue: null));
+    properties.add(DiagnosticsProperty<IconThemeData>('unselectedIconTheme', unselectedIconTheme, defaultValue: null));
+    properties.add(ColorProperty('selectedItemColor', selectedItemColor, defaultValue: null));
+    properties.add(ColorProperty('unselectedItemColor', unselectedItemColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('selectedLabelStyle', selectedLabelStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('unselectedLabelStyle', unselectedLabelStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('showSelectedLabels', showSelectedLabels, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('showUnselectedLabels', showUnselectedLabels, defaultValue: null));
+    properties.add(DiagnosticsProperty<BottomNavigationBarType>('type', type, defaultValue: null));
+  }
+}
+
+/// Applies a bottom navigation bar theme to descendant [BottomNavigationBar]
+/// widgets.
+///
+/// Descendant widgets obtain the current theme's [BottomNavigationBarTheme]
+/// object using [BottomNavigationBarTheme.of]. When a widget uses
+/// [BottomNavigationBarTheme.of], it is automatically rebuilt if the theme
+/// later changes.
+///
+/// A bottom navigation theme can be specified as part of the overall Material
+/// theme using [ThemeData.bottomNavigationBarTheme].
+///
+/// See also:
+///
+///  * [BottomNavigationBarThemeData], which describes the actual configuration
+///    of a bottom navigation bar theme.
+class BottomNavigationBarTheme extends InheritedWidget {
+  /// Constructs a bottom navigation bar theme that configures all descendant
+  /// [BottomNavigationBar] widgets.
+  ///
+  /// The [data] must not be null.
+  const BottomNavigationBarTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null), super(key: key, child: child);
+
+  /// The properties used for all descendant [BottomNavigationBar] widgets.
+  final BottomNavigationBarThemeData data;
+
+  /// Returns the configuration [data] from the closest
+  /// [BottomNavigationBarTheme] ancestor. If there is no ancestor, it returns
+  /// [ThemeData.bottomNavigationBarTheme]. Applications can assume that the
+  /// returned value will not be null.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// BottomNavigationBarThemeData theme = BottomNavigationBarTheme.of(context);
+  /// ```
+  static BottomNavigationBarThemeData of(BuildContext context) {
+    final BottomNavigationBarTheme? bottomNavTheme = context.dependOnInheritedWidgetOfExactType<BottomNavigationBarTheme>();
+    return bottomNavTheme?.data ?? Theme.of(context).bottomNavigationBarTheme;
+  }
+
+  @override
+  bool updateShouldNotify(BottomNavigationBarTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/bottom_sheet.dart b/lib/src/material/bottom_sheet.dart
new file mode 100644
index 0000000..e7c9e16
--- /dev/null
+++ b/lib/src/material/bottom_sheet.dart
@@ -0,0 +1,747 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/widgets.dart';
+
+import 'bottom_sheet_theme.dart';
+import 'colors.dart';
+import 'curves.dart';
+import 'debug.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'scaffold.dart';
+import 'theme.dart';
+
+const Duration _bottomSheetEnterDuration = Duration(milliseconds: 250);
+const Duration _bottomSheetExitDuration = Duration(milliseconds: 200);
+const Curve _modalBottomSheetCurve = decelerateEasing;
+const double _minFlingVelocity = 700.0;
+const double _closeProgressThreshold = 0.5;
+
+/// A callback for when the user begins dragging the bottom sheet.
+///
+/// Used by [BottomSheet.onDragStart].
+typedef BottomSheetDragStartHandler = void Function(DragStartDetails details);
+
+/// A callback for when the user stops dragging the bottom sheet.
+///
+/// Used by [BottomSheet.onDragEnd].
+typedef BottomSheetDragEndHandler = void Function(
+  DragEndDetails details, {
+  required bool isClosing,
+});
+
+/// A material design bottom sheet.
+///
+/// There are two kinds of bottom sheets in material design:
+///
+///  * _Persistent_. A persistent bottom sheet shows information that
+///    supplements the primary content of the app. A persistent bottom sheet
+///    remains visible even when the user interacts with other parts of the app.
+///    Persistent bottom sheets can be created and displayed with the
+///    [ScaffoldState.showBottomSheet] function or by specifying the
+///    [Scaffold.bottomSheet] constructor parameter.
+///
+///  * _Modal_. A modal bottom sheet is an alternative to a menu or a dialog and
+///    prevents the user from interacting with the rest of the app. Modal bottom
+///    sheets can be created and displayed with the [showModalBottomSheet]
+///    function.
+///
+/// The [BottomSheet] widget itself is rarely used directly. Instead, prefer to
+/// create a persistent bottom sheet with [ScaffoldState.showBottomSheet] or
+/// [Scaffold.bottomSheet], and a modal bottom sheet with [showModalBottomSheet].
+///
+/// See also:
+///
+///  * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing
+///    non-modal "persistent" bottom sheets.
+///  * [showModalBottomSheet], which can be used to display a modal bottom
+///    sheet.
+///  * <https://material.io/design/components/sheets-bottom.html>
+class BottomSheet extends StatefulWidget {
+  /// Creates a bottom sheet.
+  ///
+  /// Typically, bottom sheets are created implicitly by
+  /// [ScaffoldState.showBottomSheet], for persistent bottom sheets, or by
+  /// [showModalBottomSheet], for modal bottom sheets.
+  const BottomSheet({
+    Key? key,
+    this.animationController,
+    this.enableDrag = true,
+    this.onDragStart,
+    this.onDragEnd,
+    this.backgroundColor,
+    this.elevation,
+    this.shape,
+    this.clipBehavior,
+    required this.onClosing,
+    required this.builder,
+  }) : assert(enableDrag != null),
+       assert(onClosing != null),
+       assert(builder != null),
+       assert(elevation == null || elevation >= 0.0),
+       super(key: key);
+
+  /// The animation controller that controls the bottom sheet's entrance and
+  /// exit animations.
+  ///
+  /// The BottomSheet widget will manipulate the position of this animation, it
+  /// is not just a passive observer.
+  final AnimationController? animationController;
+
+  /// Called when the bottom sheet begins to close.
+  ///
+  /// A bottom sheet might be prevented from closing (e.g., by user
+  /// interaction) even after this callback is called. For this reason, this
+  /// callback might be call multiple times for a given bottom sheet.
+  final VoidCallback onClosing;
+
+  /// A builder for the contents of the sheet.
+  ///
+  /// The bottom sheet will wrap the widget produced by this builder in a
+  /// [Material] widget.
+  final WidgetBuilder builder;
+
+  /// If true, the bottom sheet can be dragged up and down and dismissed by
+  /// swiping downwards.
+  ///
+  /// Default is true.
+  final bool enableDrag;
+
+  /// Called when the user begins dragging the bottom sheet vertically, if
+  /// [enableDrag] is true.
+  ///
+  /// Would typically be used to change the bottom sheet animation curve so
+  /// that it tracks the user's finger accurately.
+  final BottomSheetDragStartHandler? onDragStart;
+
+  /// Called when the user stops dragging the bottom sheet, if [enableDrag]
+  /// is true.
+  ///
+  /// Would typically be used to reset the bottom sheet animation curve, so
+  /// that it animates non-linearly. Called before [onClosing] if the bottom
+  /// sheet is closing.
+  final BottomSheetDragEndHandler? onDragEnd;
+
+  /// The bottom sheet's background color.
+  ///
+  /// Defines the bottom sheet's [Material.color].
+  ///
+  /// Defaults to null and falls back to [Material]'s default.
+  final Color? backgroundColor;
+
+  /// The z-coordinate at which to place this material relative to its parent.
+  ///
+  /// This controls the size of the shadow below the material.
+  ///
+  /// Defaults to 0. The value is non-negative.
+  final double? elevation;
+
+  /// The shape of the bottom sheet.
+  ///
+  /// Defines the bottom sheet's [Material.shape].
+  ///
+  /// Defaults to null and falls back to [Material]'s default.
+  final ShapeBorder? shape;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defines the bottom sheet's [Material.clipBehavior].
+  ///
+  /// Use this property to enable clipping of content when the bottom sheet has
+  /// a custom [shape] and the content can extend past this shape. For example,
+  /// a bottom sheet with rounded corners and an edge-to-edge [Image] at the
+  /// top.
+  ///
+  /// If this property is null then [BottomSheetThemeData.clipBehavior] of
+  /// [ThemeData.bottomSheetTheme] is used. If that's null then the behavior
+  /// will be [Clip.none].
+  final Clip? clipBehavior;
+
+  @override
+  _BottomSheetState createState() => _BottomSheetState();
+
+  /// Creates an [AnimationController] suitable for a
+  /// [BottomSheet.animationController].
+  ///
+  /// This API available as a convenience for a Material compliant bottom sheet
+  /// animation. If alternative animation durations are required, a different
+  /// animation controller could be provided.
+  static AnimationController createAnimationController(TickerProvider vsync) {
+    return AnimationController(
+      duration: _bottomSheetEnterDuration,
+      reverseDuration: _bottomSheetExitDuration,
+      debugLabel: 'BottomSheet',
+      vsync: vsync,
+    );
+  }
+}
+
+class _BottomSheetState extends State<BottomSheet> {
+
+  final GlobalKey _childKey = GlobalKey(debugLabel: 'BottomSheet child');
+
+  double get _childHeight {
+    final RenderBox renderBox = _childKey.currentContext!.findRenderObject()! as RenderBox;
+    return renderBox.size.height;
+  }
+
+  bool get _dismissUnderway => widget.animationController!.status == AnimationStatus.reverse;
+
+  void _handleDragStart(DragStartDetails details) {
+    if (widget.onDragStart != null) {
+      widget.onDragStart!(details);
+    }
+  }
+
+  void _handleDragUpdate(DragUpdateDetails details) {
+    assert(widget.enableDrag);
+    if (_dismissUnderway)
+      return;
+    widget.animationController!.value -= details.primaryDelta! / _childHeight;
+  }
+
+  void _handleDragEnd(DragEndDetails details) {
+    assert(widget.enableDrag);
+    if (_dismissUnderway)
+      return;
+    bool isClosing = false;
+    if (details.velocity.pixelsPerSecond.dy > _minFlingVelocity) {
+      final double flingVelocity = -details.velocity.pixelsPerSecond.dy / _childHeight;
+      if (widget.animationController!.value > 0.0) {
+        widget.animationController!.fling(velocity: flingVelocity);
+      }
+      if (flingVelocity < 0.0) {
+        isClosing = true;
+      }
+    } else if (widget.animationController!.value < _closeProgressThreshold) {
+      if (widget.animationController!.value > 0.0)
+        widget.animationController!.fling(velocity: -1.0);
+      isClosing = true;
+    } else {
+      widget.animationController!.forward();
+    }
+
+    if (widget.onDragEnd != null) {
+      widget.onDragEnd!(
+        details,
+        isClosing: isClosing,
+      );
+    }
+
+    if (isClosing) {
+      widget.onClosing();
+    }
+  }
+
+  bool extentChanged(DraggableScrollableNotification notification) {
+    if (notification.extent == notification.minExtent) {
+      widget.onClosing();
+    }
+    return false;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final BottomSheetThemeData bottomSheetTheme = Theme.of(context).bottomSheetTheme;
+    final Color? color = widget.backgroundColor ?? bottomSheetTheme.backgroundColor;
+    final double elevation = widget.elevation ?? bottomSheetTheme.elevation ?? 0;
+    final ShapeBorder? shape = widget.shape ?? bottomSheetTheme.shape;
+    final Clip clipBehavior = widget.clipBehavior ?? bottomSheetTheme.clipBehavior ?? Clip.none;
+
+    final Widget bottomSheet = Material(
+      key: _childKey,
+      color: color,
+      elevation: elevation,
+      shape: shape,
+      clipBehavior: clipBehavior,
+      child: NotificationListener<DraggableScrollableNotification>(
+        onNotification: extentChanged,
+        child: widget.builder(context),
+      ),
+    );
+    return !widget.enableDrag ? bottomSheet : GestureDetector(
+      onVerticalDragStart: _handleDragStart,
+      onVerticalDragUpdate: _handleDragUpdate,
+      onVerticalDragEnd: _handleDragEnd,
+      child: bottomSheet,
+      excludeFromSemantics: true,
+    );
+  }
+}
+
+// PERSISTENT BOTTOM SHEETS
+
+// See scaffold.dart
+
+
+// MODAL BOTTOM SHEETS
+class _ModalBottomSheetLayout extends SingleChildLayoutDelegate {
+  _ModalBottomSheetLayout(this.progress, this.isScrollControlled);
+
+  final double progress;
+  final bool isScrollControlled;
+
+  @override
+  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
+    return BoxConstraints(
+      minWidth: constraints.maxWidth,
+      maxWidth: constraints.maxWidth,
+      minHeight: 0.0,
+      maxHeight: isScrollControlled
+        ? constraints.maxHeight
+        : constraints.maxHeight * 9.0 / 16.0,
+    );
+  }
+
+  @override
+  Offset getPositionForChild(Size size, Size childSize) {
+    return Offset(0.0, size.height - childSize.height * progress);
+  }
+
+  @override
+  bool shouldRelayout(_ModalBottomSheetLayout oldDelegate) {
+    return progress != oldDelegate.progress;
+  }
+}
+
+class _ModalBottomSheet<T> extends StatefulWidget {
+  const _ModalBottomSheet({
+    Key? key,
+    this.route,
+    this.backgroundColor,
+    this.elevation,
+    this.shape,
+    this.clipBehavior,
+    this.isScrollControlled = false,
+    this.enableDrag = true,
+  }) : assert(isScrollControlled != null),
+       assert(enableDrag != null),
+       super(key: key);
+
+  final _ModalBottomSheetRoute<T>? route;
+  final bool isScrollControlled;
+  final Color? backgroundColor;
+  final double? elevation;
+  final ShapeBorder? shape;
+  final Clip? clipBehavior;
+  final bool enableDrag;
+
+  @override
+  _ModalBottomSheetState<T> createState() => _ModalBottomSheetState<T>();
+}
+
+class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
+  ParametricCurve<double> animationCurve = _modalBottomSheetCurve;
+
+  String _getRouteLabel(MaterialLocalizations localizations) {
+    switch (Theme.of(context).platform) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        return '';
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        return localizations.dialogLabel;
+    }
+  }
+
+  void handleDragStart(DragStartDetails details) {
+    // Allow the bottom sheet to track the user's finger accurately.
+    animationCurve = Curves.linear;
+  }
+
+  void handleDragEnd(DragEndDetails details, {bool? isClosing}) {
+    // Allow the bottom sheet to animate smoothly from its current position.
+    animationCurve = _BottomSheetSuspendedCurve(
+      widget.route!.animation!.value,
+      curve: _modalBottomSheetCurve,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+    final MediaQueryData mediaQuery = MediaQuery.of(context);
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final String routeLabel = _getRouteLabel(localizations);
+
+    return AnimatedBuilder(
+      animation: widget.route!.animation!,
+      child: BottomSheet(
+        animationController: widget.route!._animationController,
+        onClosing: () {
+          if (widget.route!.isCurrent) {
+            Navigator.pop(context);
+          }
+        },
+        builder: widget.route!.builder!,
+        backgroundColor: widget.backgroundColor,
+        elevation: widget.elevation,
+        shape: widget.shape,
+        clipBehavior: widget.clipBehavior,
+        enableDrag: widget.enableDrag,
+        onDragStart: handleDragStart,
+        onDragEnd: handleDragEnd,
+      ),
+      builder: (BuildContext context, Widget? child) {
+        // Disable the initial animation when accessible navigation is on so
+        // that the semantics are added to the tree at the correct time.
+        final double animationValue = animationCurve.transform(
+            mediaQuery.accessibleNavigation ? 1.0 : widget.route!.animation!.value
+        );
+        return Semantics(
+          scopesRoute: true,
+          namesRoute: true,
+          label: routeLabel,
+          explicitChildNodes: true,
+          child: ClipRect(
+            child: CustomSingleChildLayout(
+              delegate: _ModalBottomSheetLayout(animationValue, widget.isScrollControlled),
+              child: child,
+            ),
+          ),
+        );
+      },
+    );
+  }
+}
+
+class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
+  _ModalBottomSheetRoute({
+    this.builder,
+    required this.capturedThemes,
+    this.barrierLabel,
+    this.backgroundColor,
+    this.elevation,
+    this.shape,
+    this.clipBehavior,
+    this.modalBarrierColor,
+    this.isDismissible = true,
+    this.enableDrag = true,
+    required this.isScrollControlled,
+    RouteSettings? settings,
+  }) : assert(isScrollControlled != null),
+       assert(isDismissible != null),
+       assert(enableDrag != null),
+       super(settings: settings);
+
+  final WidgetBuilder? builder;
+  final CapturedThemes capturedThemes;
+  final bool isScrollControlled;
+  final Color? backgroundColor;
+  final double? elevation;
+  final ShapeBorder? shape;
+  final Clip? clipBehavior;
+  final Color? modalBarrierColor;
+  final bool isDismissible;
+  final bool enableDrag;
+
+  @override
+  Duration get transitionDuration => _bottomSheetEnterDuration;
+
+  @override
+  Duration get reverseTransitionDuration => _bottomSheetExitDuration;
+
+  @override
+  bool get barrierDismissible => isDismissible;
+
+  @override
+  final String? barrierLabel;
+
+  @override
+  Color get barrierColor => modalBarrierColor ?? Colors.black54;
+
+  AnimationController? _animationController;
+
+  @override
+  AnimationController createAnimationController() {
+    assert(_animationController == null);
+    _animationController = BottomSheet.createAnimationController(navigator!.overlay!);
+    return _animationController!;
+  }
+
+  @override
+  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
+    // By definition, the bottom sheet is aligned to the bottom of the page
+    // and isn't exposed to the top padding of the MediaQuery.
+    final Widget bottomSheet = MediaQuery.removePadding(
+      context: context,
+      removeTop: true,
+      child: Builder(
+        builder: (BuildContext context) {
+          final BottomSheetThemeData sheetTheme = Theme.of(context).bottomSheetTheme;
+          return _ModalBottomSheet<T>(
+            route: this,
+            backgroundColor: backgroundColor ?? sheetTheme.modalBackgroundColor ?? sheetTheme.backgroundColor,
+            elevation: elevation ?? sheetTheme.modalElevation ?? sheetTheme.elevation,
+            shape: shape,
+            clipBehavior: clipBehavior,
+            isScrollControlled: isScrollControlled,
+            enableDrag: enableDrag,
+          );
+        },
+      ),
+    );
+    return capturedThemes.wrap(bottomSheet);
+  }
+}
+
+// TODO(guidezpl): Look into making this public. A copy of this class is in
+//  scaffold.dart, for now, https://github.com/flutter/flutter/issues/51627
+/// A curve that progresses linearly until a specified [startingPoint], at which
+/// point [curve] will begin. Unlike [Interval], [curve] will not start at zero,
+/// but will use [startingPoint] as the Y position.
+///
+/// For example, if [startingPoint] is set to `0.5`, and [curve] is set to
+/// [Curves.easeOut], then the bottom-left quarter of the curve will be a
+/// straight line, and the top-right quarter will contain the entire contents of
+/// [Curves.easeOut].
+///
+/// This is useful in situations where a widget must track the user's finger
+/// (which requires a linear animation), and afterwards can be flung using a
+/// curve specified with the [curve] argument, after the finger is released. In
+/// such a case, the value of [startingPoint] would be the progress of the
+/// animation at the time when the finger was released.
+///
+/// The [startingPoint] and [curve] arguments must not be null.
+class _BottomSheetSuspendedCurve extends ParametricCurve<double> {
+  /// Creates a suspended curve.
+  const _BottomSheetSuspendedCurve(
+    this.startingPoint, {
+    this.curve = Curves.easeOutCubic,
+  }) : assert(startingPoint != null),
+       assert(curve != null);
+
+  /// The progress value at which [curve] should begin.
+  ///
+  /// This defaults to [Curves.easeOutCubic].
+  final double startingPoint;
+
+  /// The curve to use when [startingPoint] is reached.
+  final Curve curve;
+
+  @override
+  double transform(double t) {
+    assert(t >= 0.0 && t <= 1.0);
+    assert(startingPoint >= 0.0 && startingPoint <= 1.0);
+
+    if (t < startingPoint) {
+      return t;
+    }
+
+    if (t == 1.0) {
+      return t;
+    }
+
+    final double curveProgress = (t - startingPoint) / (1 - startingPoint);
+    final double transformed = curve.transform(curveProgress);
+    return lerpDouble(startingPoint, 1, transformed)!;
+  }
+
+  @override
+  String toString() {
+    return '${describeIdentity(this)}($startingPoint, $curve)';
+  }
+}
+
+/// Shows a modal material design bottom sheet.
+///
+/// A modal bottom sheet is an alternative to a menu or a dialog and prevents
+/// the user from interacting with the rest of the app.
+///
+/// A closely related widget is a persistent bottom sheet, which shows
+/// information that supplements the primary content of the app without
+/// preventing the use from interacting with the app. Persistent bottom sheets
+/// can be created and displayed with the [showBottomSheet] function or the
+/// [ScaffoldState.showBottomSheet] method.
+///
+/// The `context` argument is used to look up the [Navigator] and [Theme] for
+/// the bottom sheet. It is only used when the method is called. Its
+/// corresponding widget can be safely removed from the tree before the bottom
+/// sheet is closed.
+///
+/// The `isScrollControlled` parameter specifies whether this is a route for
+/// a bottom sheet that will utilize [DraggableScrollableSheet]. If you wish
+/// to have a bottom sheet that has a scrollable child such as a [ListView] or
+/// a [GridView] and have the bottom sheet be draggable, you should set this
+/// parameter to true.
+///
+/// The `useRootNavigator` parameter ensures that the root navigator is used to
+/// display the [BottomSheet] when set to `true`. This is useful in the case
+/// that a modal [BottomSheet] needs to be displayed above all other content
+/// but the caller is inside another [Navigator].
+///
+/// The [isDismissible] parameter specifies whether the bottom sheet will be
+/// dismissed when user taps on the scrim.
+///
+/// The [enableDrag] parameter specifies whether the bottom sheet can be
+/// dragged up and down and dismissed by swiping downwards.
+///
+/// The optional [backgroundColor], [elevation], [shape], and [clipBehavior]
+/// parameters can be passed in to customize the appearance and behavior of
+/// modal bottom sheets.
+///
+/// The optional `routeSettings` parameter sets the [RouteSettings] of the modal bottom sheet
+/// sheet. This is particularly useful in the case that a user wants to observe
+/// [PopupRoute]s within a [NavigatorObserver].
+///
+/// Returns a `Future` that resolves to the value (if any) that was passed to
+/// [Navigator.pop] when the modal bottom sheet was closed.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// This example demonstrates how to use `showModalBottomSheet` to display a
+/// bottom sheet that obscures the content behind it when a user taps a button.
+/// It also demonstrates how to close the bottom sheet using the [Navigator]
+/// when a user taps on a button inside the bottom sheet.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Center(
+///     child: ElevatedButton(
+///       child: const Text('showModalBottomSheet'),
+///       onPressed: () {
+///         showModalBottomSheet<void>(
+///           context: context,
+///           builder: (BuildContext context) {
+///             return Container(
+///               height: 200,
+///               color: Colors.amber,
+///               child: Center(
+///                 child: Column(
+///                   mainAxisAlignment: MainAxisAlignment.center,
+///                   mainAxisSize: MainAxisSize.min,
+///                   children: <Widget>[
+///                     const Text('Modal BottomSheet'),
+///                     ElevatedButton(
+///                       child: const Text('Close BottomSheet'),
+///                       onPressed: () => Navigator.pop(context),
+///                     )
+///                   ],
+///                 ),
+///               ),
+///             );
+///           },
+///         );
+///       },
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+/// See also:
+///
+///  * [BottomSheet], which becomes the parent of the widget returned by the
+///    function passed as the `builder` argument to [showModalBottomSheet].
+///  * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing
+///    non-modal bottom sheets.
+///  * [DraggableScrollableSheet], which allows you to create a bottom sheet
+///    that grows and then becomes scrollable once it reaches its maximum size.
+///  * <https://material.io/design/components/sheets-bottom.html#modal-bottom-sheet>
+Future<T?> showModalBottomSheet<T>({
+  required BuildContext context,
+  required WidgetBuilder builder,
+  Color? backgroundColor,
+  double? elevation,
+  ShapeBorder? shape,
+  Clip? clipBehavior,
+  Color? barrierColor,
+  bool isScrollControlled = false,
+  bool useRootNavigator = false,
+  bool isDismissible = true,
+  bool enableDrag = true,
+  RouteSettings? routeSettings,
+}) {
+  assert(context != null);
+  assert(builder != null);
+  assert(isScrollControlled != null);
+  assert(useRootNavigator != null);
+  assert(isDismissible != null);
+  assert(enableDrag != null);
+  assert(debugCheckHasMediaQuery(context));
+  assert(debugCheckHasMaterialLocalizations(context));
+
+  final NavigatorState navigator = Navigator.of(context, rootNavigator: useRootNavigator);
+  return navigator.push(_ModalBottomSheetRoute<T>(
+    builder: builder,
+    capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
+    isScrollControlled: isScrollControlled,
+    barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
+    backgroundColor: backgroundColor,
+    elevation: elevation,
+    shape: shape,
+    clipBehavior: clipBehavior,
+    isDismissible: isDismissible,
+    modalBarrierColor: barrierColor,
+    enableDrag: enableDrag,
+    settings: routeSettings,
+  ));
+}
+
+/// Shows a material design bottom sheet in the nearest [Scaffold] ancestor. If
+/// you wish to show a persistent bottom sheet, use [Scaffold.bottomSheet].
+///
+/// Returns a controller that can be used to close and otherwise manipulate the
+/// bottom sheet.
+///
+/// The optional [backgroundColor], [elevation], [shape], and [clipBehavior]
+/// parameters can be passed in to customize the appearance and behavior of
+/// persistent bottom sheets.
+///
+/// To rebuild the bottom sheet (e.g. if it is stateful), call
+/// [PersistentBottomSheetController.setState] on the controller returned by
+/// this method.
+///
+/// The new bottom sheet becomes a [LocalHistoryEntry] for the enclosing
+/// [ModalRoute] and a back button is added to the app bar of the [Scaffold]
+/// that closes the bottom sheet.
+///
+/// To create a persistent bottom sheet that is not a [LocalHistoryEntry] and
+/// does not add a back button to the enclosing Scaffold's app bar, use the
+/// [Scaffold.bottomSheet] constructor parameter.
+///
+/// A closely related widget is a modal bottom sheet, which is an alternative
+/// to a menu or a dialog and prevents the user from interacting with the rest
+/// of the app. Modal bottom sheets can be created and displayed with the
+/// [showModalBottomSheet] function.
+///
+/// The `context` argument is used to look up the [Scaffold] for the bottom
+/// sheet. It is only used when the method is called. Its corresponding widget
+/// can be safely removed from the tree before the bottom sheet is closed.
+///
+/// See also:
+///
+///  * [BottomSheet], which becomes the parent of the widget returned by the
+///    `builder`.
+///  * [showModalBottomSheet], which can be used to display a modal bottom
+///    sheet.
+///  * [Scaffold.of], for information about how to obtain the [BuildContext].
+///  * <https://material.io/design/components/sheets-bottom.html#standard-bottom-sheet>
+PersistentBottomSheetController<T> showBottomSheet<T>({
+  required BuildContext context,
+  required WidgetBuilder builder,
+  Color? backgroundColor,
+  double? elevation,
+  ShapeBorder? shape,
+  Clip? clipBehavior,
+}) {
+  assert(context != null);
+  assert(builder != null);
+  assert(debugCheckHasScaffold(context));
+
+  return Scaffold.of(context).showBottomSheet<T>(
+    builder,
+    backgroundColor: backgroundColor,
+    elevation: elevation,
+    shape: shape,
+    clipBehavior: clipBehavior,
+  );
+}
diff --git a/lib/src/material/bottom_sheet_theme.dart b/lib/src/material/bottom_sheet_theme.dart
new file mode 100644
index 0000000..8ec76ca
--- /dev/null
+++ b/lib/src/material/bottom_sheet_theme.dart
@@ -0,0 +1,146 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+/// Defines default property values for [BottomSheet]'s [Material].
+///
+/// Descendant widgets obtain the current [BottomSheetThemeData] object
+/// using `Theme.of(context).bottomSheetTheme`. Instances of
+/// [BottomSheetThemeData] can be customized with
+/// [BottomSheetThemeData.copyWith].
+///
+/// Typically a [BottomSheetThemeData] is specified as part of the
+/// overall [Theme] with [ThemeData.bottomSheetTheme].
+///
+/// All [BottomSheetThemeData] properties are `null` by default.
+/// When null, the [BottomSheet] will provide its own defaults.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class BottomSheetThemeData with Diagnosticable {
+  /// Creates a theme that can be used for [ThemeData.bottomSheetTheme].
+  const BottomSheetThemeData({
+    this.backgroundColor,
+    this.elevation,
+    this.modalBackgroundColor,
+    this.modalElevation,
+    this.shape,
+    this.clipBehavior,
+  });
+
+  /// Default value for [BottomSheet.backgroundColor].
+  ///
+  /// If null, [BottomSheet] defaults to [Material]'s default.
+  final Color? backgroundColor;
+
+  /// Default value for [BottomSheet.elevation].
+  ///
+  /// {@macro flutter.material.material.elevation}
+  ///
+  /// If null, [BottomSheet] defaults to 0.0.
+  final double? elevation;
+
+  /// Value for [BottomSheet.backgroundColor] when the Bottom sheet is presented
+  /// as a modal bottom sheet.
+  final Color? modalBackgroundColor;
+
+  /// Value for [BottomSheet.elevation] when the Bottom sheet is presented as a
+  /// modal bottom sheet.
+  final double? modalElevation;
+
+  /// Default value for [BottomSheet.shape].
+  ///
+  /// If null, no overriding shape is specified for [BottomSheet], so the
+  /// [BottomSheet] is rectangular.
+  final ShapeBorder? shape;
+
+  /// Default value for [BottomSheet.clipBehavior].
+  ///
+  /// If null, [BottomSheet] uses [Clip.none].
+  final Clip? clipBehavior;
+
+  /// Creates a copy of this object with the given fields replaced with the
+  /// new values.
+  BottomSheetThemeData copyWith({
+    Color? backgroundColor,
+    double? elevation,
+    Color? modalBackgroundColor,
+    double? modalElevation,
+    ShapeBorder? shape,
+    Clip? clipBehavior,
+  }) {
+    return BottomSheetThemeData(
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      elevation: elevation ?? this.elevation,
+      modalBackgroundColor: modalBackgroundColor ?? this.modalBackgroundColor,
+      modalElevation: modalElevation ?? this.modalElevation,
+      shape: shape ?? this.shape,
+      clipBehavior: clipBehavior ?? this.clipBehavior,
+    );
+  }
+
+  /// Linearly interpolate between two bottom sheet themes.
+  ///
+  /// If both arguments are null then null is returned.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static BottomSheetThemeData? lerp(BottomSheetThemeData? a, BottomSheetThemeData? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    return BottomSheetThemeData(
+      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
+      elevation: lerpDouble(a?.elevation, b?.elevation, t),
+      modalBackgroundColor: Color.lerp(a?.modalBackgroundColor, b?.modalBackgroundColor, t),
+      modalElevation: lerpDouble(a?.modalElevation, b?.modalElevation, t),
+      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
+      clipBehavior: t < 0.5 ? a?.clipBehavior : b?.clipBehavior,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      backgroundColor,
+      elevation,
+      modalBackgroundColor,
+      modalElevation,
+      shape,
+      clipBehavior,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is BottomSheetThemeData
+        && other.backgroundColor == backgroundColor
+        && other.elevation == elevation
+        && other.modalBackgroundColor == modalBackgroundColor
+        && other.modalElevation == modalElevation
+        && other.shape == shape
+        && other.clipBehavior == clipBehavior;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
+    properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
+    properties.add(ColorProperty('modalBackgroundColor', modalBackgroundColor, defaultValue: null));
+    properties.add(DoubleProperty('modalElevation', modalElevation, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
+    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: null));
+  }
+}
diff --git a/lib/src/material/button.dart b/lib/src/material/button.dart
new file mode 100644
index 0000000..ee6fc7c
--- /dev/null
+++ b/lib/src/material/button.dart
@@ -0,0 +1,591 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'button_theme.dart';
+import 'constants.dart';
+import 'ink_well.dart';
+import 'material.dart';
+import 'material_state.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// Creates a button based on [Semantics], [Material], and [InkWell]
+/// widgets.
+///
+/// ### This class is obsolete.
+///
+/// Custom button classes can be created by configuring the
+/// [ButtonStyle] of a [TextButton], [ElevatedButton] or an
+/// [OutlinedButton].
+///
+/// FlatButton, RaisedButton, and OutlineButton have been replaced by
+/// TextButton, ElevatedButton, and OutlinedButton respectively.
+/// ButtonTheme has been replaced by TextButtonTheme,
+/// ElevatedButtonTheme, and OutlinedButtonTheme. The original classes
+/// will be deprecated soon, please migrate code that uses them.
+/// There's a detailed migration guide for the new button and button
+/// theme classes in
+/// [flutter.dev/go/material-button-migration-guide](https://flutter.dev/go/material-button-migration-guide).
+///
+/// This class does not use the current [Theme] or [ButtonTheme] to
+/// compute default values for unspecified parameters. It's intended to
+/// be used for custom Material buttons that optionally incorporate defaults
+/// from the themes or from app-specific sources.
+@Category(<String>['Material', 'Button'])
+class RawMaterialButton extends StatefulWidget {
+  /// Create a button based on [Semantics], [Material], and [InkWell] widgets.
+  ///
+  /// The [shape], [elevation], [focusElevation], [hoverElevation],
+  /// [highlightElevation], [disabledElevation], [padding], [constraints],
+  /// [autofocus], and [clipBehavior] arguments must not be null. Additionally,
+  /// [elevation], [focusElevation], [hoverElevation], [highlightElevation], and
+  /// [disabledElevation] must be non-negative.
+  const RawMaterialButton({
+    Key? key,
+    required this.onPressed,
+    this.onLongPress,
+    this.onHighlightChanged,
+    this.mouseCursor,
+    this.textStyle,
+    this.fillColor,
+    this.focusColor,
+    this.hoverColor,
+    this.highlightColor,
+    this.splashColor,
+    this.elevation = 2.0,
+    this.focusElevation = 4.0,
+    this.hoverElevation = 4.0,
+    this.highlightElevation = 8.0,
+    this.disabledElevation = 0.0,
+    this.padding = EdgeInsets.zero,
+    this.visualDensity = const VisualDensity(),
+    this.constraints = const BoxConstraints(minWidth: 88.0, minHeight: 36.0),
+    this.shape = const RoundedRectangleBorder(),
+    this.animationDuration = kThemeChangeDuration,
+    this.clipBehavior = Clip.none,
+    this.focusNode,
+    this.autofocus = false,
+    MaterialTapTargetSize? materialTapTargetSize,
+    this.child,
+    this.enableFeedback = true,
+  }) : materialTapTargetSize = materialTapTargetSize ?? MaterialTapTargetSize.padded,
+       assert(shape != null),
+       assert(elevation != null && elevation >= 0.0),
+       assert(focusElevation != null && focusElevation >= 0.0),
+       assert(hoverElevation != null && hoverElevation >= 0.0),
+       assert(highlightElevation != null && highlightElevation >= 0.0),
+       assert(disabledElevation != null && disabledElevation >= 0.0),
+       assert(padding != null),
+       assert(constraints != null),
+       assert(animationDuration != null),
+       assert(clipBehavior != null),
+       assert(autofocus != null),
+       super(key: key);
+
+  /// Called when the button is tapped or otherwise activated.
+  ///
+  /// If this callback and [onLongPress] are null, then the button will be disabled.
+  ///
+  /// See also:
+  ///
+  ///  * [enabled], which is true if the button is enabled.
+  final VoidCallback? onPressed;
+
+  /// Called when the button is long-pressed.
+  ///
+  /// If this callback and [onPressed] are null, then the button will be disabled.
+  ///
+  /// See also:
+  ///
+  ///  * [enabled], which is true if the button is enabled.
+  final VoidCallback? onLongPress;
+
+  /// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
+  /// callback.
+  ///
+  /// If [onPressed] changes from null to non-null while a gesture is ongoing,
+  /// this can fire during the build phase (in which case calling
+  /// [State.setState] is not allowed).
+  final ValueChanged<bool>? onHighlightChanged;
+
+  /// {@template flutter.material.RawMaterialButton.mouseCursor}
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// button.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.pressed].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  ///
+  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
+  /// {@endtemplate}
+  final MouseCursor? mouseCursor;
+
+  /// Defines the default text style, with [Material.textStyle], for the
+  /// button's [child].
+  ///
+  /// If [TextStyle.color] is a [MaterialStateProperty<Color>], [MaterialStateProperty.resolve]
+  /// is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.pressed].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  final TextStyle? textStyle;
+
+  /// The color of the button's [Material].
+  final Color? fillColor;
+
+  /// The color for the button's [Material] when it has the input focus.
+  final Color? focusColor;
+
+  /// The color for the button's [Material] when a pointer is hovering over it.
+  final Color? hoverColor;
+
+  /// The highlight color for the button's [InkWell].
+  final Color? highlightColor;
+
+  /// The splash color for the button's [InkWell].
+  final Color? splashColor;
+
+  /// The elevation for the button's [Material] when the button
+  /// is [enabled] but not pressed.
+  ///
+  /// Defaults to 2.0. The value is always non-negative.
+  ///
+  /// See also:
+  ///
+  ///  * [highlightElevation], the default elevation.
+  ///  * [hoverElevation], the elevation when a pointer is hovering over the
+  ///    button.
+  ///  * [focusElevation], the elevation when the button is focused.
+  ///  * [disabledElevation], the elevation when the button is disabled.
+  final double elevation;
+
+  /// The elevation for the button's [Material] when the button
+  /// is [enabled] and a pointer is hovering over it.
+  ///
+  /// Defaults to 4.0. The value is always non-negative.
+  ///
+  /// If the button is [enabled], and being pressed (in the highlighted state),
+  /// then the [highlightElevation] take precedence over the [hoverElevation].
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], the default elevation.
+  ///  * [focusElevation], the elevation when the button is focused.
+  ///  * [disabledElevation], the elevation when the button is disabled.
+  ///  * [highlightElevation], the elevation when the button is pressed.
+  final double hoverElevation;
+
+  /// The elevation for the button's [Material] when the button
+  /// is [enabled] and has the input focus.
+  ///
+  /// Defaults to 4.0. The value is always non-negative.
+  ///
+  /// If the button is [enabled], and being pressed (in the highlighted state),
+  /// or a mouse cursor is hovering over the button, then the [hoverElevation]
+  /// and [highlightElevation] take precedence over the [focusElevation].
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], the default elevation.
+  ///  * [hoverElevation], the elevation when a pointer is hovering over the
+  ///    button.
+  ///  * [disabledElevation], the elevation when the button is disabled.
+  ///  * [highlightElevation], the elevation when the button is pressed.
+  final double focusElevation;
+
+  /// The elevation for the button's [Material] when the button
+  /// is [enabled] and pressed.
+  ///
+  /// Defaults to 8.0. The value is always non-negative.
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], the default elevation.
+  ///  * [hoverElevation], the elevation when a pointer is hovering over the
+  ///    button.
+  ///  * [focusElevation], the elevation when the button is focused.
+  ///  * [disabledElevation], the elevation when the button is disabled.
+  final double highlightElevation;
+
+  /// The elevation for the button's [Material] when the button
+  /// is not [enabled].
+  ///
+  /// Defaults to 0.0. The value is always non-negative.
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], the default elevation.
+  ///  * [hoverElevation], the elevation when a pointer is hovering over the
+  ///    button.
+  ///  * [focusElevation], the elevation when the button is focused.
+  ///  * [highlightElevation], the elevation when the button is pressed.
+  final double disabledElevation;
+
+  /// The internal padding for the button's [child].
+  final EdgeInsetsGeometry padding;
+
+  /// Defines how compact the button's layout will be.
+  ///
+  /// {@macro flutter.material.themedata.visualDensity}
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.visualDensity], which specifies the [visualDensity] for all widgets
+  ///    within a [Theme].
+  final VisualDensity visualDensity;
+
+  /// Defines the button's size.
+  ///
+  /// Typically used to constrain the button's minimum size.
+  final BoxConstraints constraints;
+
+  /// The shape of the button's [Material].
+  ///
+  /// The button's highlight and splash are clipped to this shape. If the
+  /// button has an elevation, then its drop shadow is defined by this shape.
+  ///
+  /// If [shape] is a [MaterialStateProperty<ShapeBorder>], [MaterialStateProperty.resolve]
+  /// is used for the following [MaterialState]s:
+  ///
+  /// * [MaterialState.pressed].
+  /// * [MaterialState.hovered].
+  /// * [MaterialState.focused].
+  /// * [MaterialState.disabled].
+  final ShapeBorder shape;
+
+  /// Defines the duration of animated changes for [shape] and [elevation].
+  ///
+  /// The default value is [kThemeChangeDuration].
+  final Duration animationDuration;
+
+  /// Typically the button's label.
+  final Widget? child;
+
+  /// Whether the button is enabled or disabled.
+  ///
+  /// Buttons are disabled by default. To enable a button, set its [onPressed]
+  /// or [onLongPress] properties to a non-null value.
+  bool get enabled => onPressed != null || onLongPress != null;
+
+  /// Configures the minimum size of the tap target.
+  ///
+  /// Defaults to [MaterialTapTargetSize.padded].
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialTapTargetSize], for a description of how this affects tap targets.
+  final MaterialTapTargetSize materialTapTargetSize;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  final Clip clipBehavior;
+
+  /// Whether detected gestures should provide acoustic and/or haptic feedback.
+  ///
+  /// For example, on Android a tap will produce a clicking sound and a
+  /// long-press will produce a short vibration, when feedback is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [Feedback] for providing platform-specific feedback to certain actions.
+  final bool enableFeedback;
+
+  @override
+  _RawMaterialButtonState createState() => _RawMaterialButtonState();
+}
+
+class _RawMaterialButtonState extends State<RawMaterialButton> {
+  final Set<MaterialState> _states = <MaterialState>{};
+
+  bool get _hovered => _states.contains(MaterialState.hovered);
+  bool get _focused => _states.contains(MaterialState.focused);
+  bool get _pressed => _states.contains(MaterialState.pressed);
+  bool get _disabled => _states.contains(MaterialState.disabled);
+
+  void _updateState(MaterialState state, bool value) {
+    value ? _states.add(state) : _states.remove(state);
+  }
+
+  void _handleHighlightChanged(bool value) {
+    if (_pressed != value) {
+      setState(() {
+        _updateState(MaterialState.pressed, value);
+        if (widget.onHighlightChanged != null) {
+          widget.onHighlightChanged!(value);
+        }
+      });
+    }
+  }
+
+  void _handleHoveredChanged(bool value) {
+    if (_hovered != value) {
+      setState(() {
+        _updateState(MaterialState.hovered, value);
+      });
+    }
+  }
+
+  void _handleFocusedChanged(bool value) {
+    if (_focused != value) {
+      setState(() {
+        _updateState(MaterialState.focused, value);
+      });
+    }
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _updateState(MaterialState.disabled, !widget.enabled);
+  }
+
+  @override
+  void didUpdateWidget(RawMaterialButton oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    _updateState(MaterialState.disabled, !widget.enabled);
+    // If the button is disabled while a press gesture is currently ongoing,
+    // InkWell makes a call to handleHighlightChanged. This causes an exception
+    // because it calls setState in the middle of a build. To preempt this, we
+    // manually update pressed to false when this situation occurs.
+    if (_disabled && _pressed) {
+      _handleHighlightChanged(false);
+    }
+  }
+
+  double get _effectiveElevation {
+    // These conditionals are in order of precedence, so be careful about
+    // reorganizing them.
+    if (_disabled) {
+      return widget.disabledElevation;
+    }
+    if (_pressed) {
+      return widget.highlightElevation;
+    }
+    if (_hovered) {
+      return widget.hoverElevation;
+    }
+    if (_focused) {
+      return widget.focusElevation;
+    }
+    return widget.elevation;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final Color? effectiveTextColor = MaterialStateProperty.resolveAs<Color?>(widget.textStyle?.color, _states);
+    final ShapeBorder? effectiveShape =  MaterialStateProperty.resolveAs<ShapeBorder?>(widget.shape, _states);
+    final Offset densityAdjustment = widget.visualDensity.baseSizeAdjustment;
+    final BoxConstraints effectiveConstraints = widget.visualDensity.effectiveConstraints(widget.constraints);
+    final MouseCursor? effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(
+      widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
+      _states,
+    );
+    final EdgeInsetsGeometry padding = widget.padding.add(
+      EdgeInsets.only(
+        left: densityAdjustment.dx,
+        top: densityAdjustment.dy,
+        right: densityAdjustment.dx,
+        bottom: densityAdjustment.dy,
+      ),
+    ).clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity);
+
+
+    final Widget result = ConstrainedBox(
+      constraints: effectiveConstraints,
+      child: Material(
+        elevation: _effectiveElevation,
+        textStyle: widget.textStyle?.copyWith(color: effectiveTextColor),
+        shape: effectiveShape,
+        color: widget.fillColor,
+        type: widget.fillColor == null ? MaterialType.transparency : MaterialType.button,
+        animationDuration: widget.animationDuration,
+        clipBehavior: widget.clipBehavior,
+        child: InkWell(
+          focusNode: widget.focusNode,
+          canRequestFocus: widget.enabled,
+          onFocusChange: _handleFocusedChanged,
+          autofocus: widget.autofocus,
+          onHighlightChanged: _handleHighlightChanged,
+          splashColor: widget.splashColor,
+          highlightColor: widget.highlightColor,
+          focusColor: widget.focusColor,
+          hoverColor: widget.hoverColor,
+          onHover: _handleHoveredChanged,
+          onTap: widget.onPressed,
+          onLongPress: widget.onLongPress,
+          enableFeedback: widget.enableFeedback,
+          customBorder: effectiveShape,
+          mouseCursor: effectiveMouseCursor,
+          child: IconTheme.merge(
+            data: IconThemeData(color: effectiveTextColor),
+            child: Container(
+              padding: padding,
+              child: Center(
+                widthFactor: 1.0,
+                heightFactor: 1.0,
+                child: widget.child,
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+    final Size minSize;
+    switch (widget.materialTapTargetSize) {
+      case MaterialTapTargetSize.padded:
+        minSize = Size(
+          kMinInteractiveDimension + densityAdjustment.dx,
+          kMinInteractiveDimension + densityAdjustment.dy,
+        );
+        assert(minSize.width >= 0.0);
+        assert(minSize.height >= 0.0);
+        break;
+      case MaterialTapTargetSize.shrinkWrap:
+        minSize = Size.zero;
+        break;
+    }
+
+    return Semantics(
+      container: true,
+      button: true,
+      enabled: widget.enabled,
+      child: _InputPadding(
+        minSize: minSize,
+        child: result,
+      ),
+    );
+  }
+}
+
+/// A widget to pad the area around a [MaterialButton]'s inner [Material].
+///
+/// Redirect taps that occur in the padded area around the child to the center
+/// of the child. This increases the size of the button and the button's
+/// "tap target", but not its material or its ink splashes.
+class _InputPadding extends SingleChildRenderObjectWidget {
+  const _InputPadding({
+    Key? key,
+    Widget? child,
+    required this.minSize,
+  }) : super(key: key, child: child);
+
+  final Size minSize;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _RenderInputPadding(minSize);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, covariant _RenderInputPadding renderObject) {
+    renderObject.minSize = minSize;
+  }
+}
+
+class _RenderInputPadding extends RenderShiftedBox {
+  _RenderInputPadding(this._minSize, [RenderBox? child]) : super(child);
+
+  Size get minSize => _minSize;
+  Size _minSize;
+  set minSize(Size value) {
+    if (_minSize == value)
+      return;
+    _minSize = value;
+    markNeedsLayout();
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    if (child != null)
+      return math.max(child!.getMinIntrinsicWidth(height), minSize.width);
+    return 0.0;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    if (child != null)
+      return math.max(child!.getMinIntrinsicHeight(width), minSize.height);
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    if (child != null)
+      return math.max(child!.getMaxIntrinsicWidth(height), minSize.width);
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    if (child != null)
+      return math.max(child!.getMaxIntrinsicHeight(width), minSize.height);
+    return 0.0;
+  }
+
+  Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
+    if (child != null) {
+      final Size childSize = layoutChild(child!, constraints);
+      final double height = math.max(childSize.width, minSize.width);
+      final double width = math.max(childSize.height, minSize.height);
+      return constraints.constrain(Size(height, width));
+    }
+    return Size.zero;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _computeSize(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.dryLayoutChild,
+    );
+  }
+
+  @override
+  void performLayout() {
+    size = _computeSize(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.layoutChild,
+    );
+    if (child != null) {
+      final BoxParentData childParentData = child!.parentData! as BoxParentData;
+      childParentData.offset = Alignment.center.alongOffset(size - child!.size as Offset);
+    }
+  }
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    if (super.hitTest(result, position: position)) {
+      return true;
+    }
+    final Offset center = child!.size.center(Offset.zero);
+    return result.addWithRawTransform(
+      transform: MatrixUtils.forceToPoint(center),
+      position: center,
+      hitTest: (BoxHitTestResult result, Offset? position) {
+        assert(position == center);
+        return child!.hitTest(result, position: center);
+      },
+    );
+  }
+}
diff --git a/lib/src/material/button_bar.dart b/lib/src/material/button_bar.dart
new file mode 100644
index 0000000..ba186bb
--- /dev/null
+++ b/lib/src/material/button_bar.dart
@@ -0,0 +1,445 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+import 'package:flute/rendering.dart';
+
+import 'button_bar_theme.dart';
+import 'button_theme.dart';
+import 'dialog.dart';
+
+/// An end-aligned row of buttons, laying out into a column if there is not
+/// enough horizontal space.
+///
+/// Places the buttons horizontally according to the [buttonPadding]. The
+/// children are laid out in a [Row] with [MainAxisAlignment.end]. When the
+/// [Directionality] is [TextDirection.ltr], the button bar's children are
+/// right justified and the last child becomes the rightmost child. When the
+/// [Directionality] [TextDirection.rtl] the children are left justified and
+/// the last child becomes the leftmost child.
+///
+/// If the button bar's width exceeds the maximum width constraint on the
+/// widget, it aligns its buttons in a column. The key difference here
+/// is that the [MainAxisAlignment] will then be treated as a
+/// cross-axis/horizontal alignment. For example, if the buttons overflow and
+/// [ButtonBar.alignment] was set to [MainAxisAlignment.start], the buttons would
+/// align to the horizontal start of the button bar.
+///
+/// The [ButtonBar] can be configured with a [ButtonBarTheme]. For any null
+/// property on the ButtonBar, the surrounding ButtonBarTheme's property
+/// will be used instead. If the ButtonBarTheme's property is null
+/// as well, the property will default to a value described in the field
+/// documentation below.
+///
+/// The [children] are wrapped in a [ButtonTheme] that is a copy of the
+/// surrounding ButtonTheme with the button properties overridden by the
+/// properties of the ButtonBar as described above. These properties include
+/// [buttonTextTheme], [buttonMinWidth], [buttonHeight], [buttonPadding],
+/// and [buttonAlignedDropdown].
+///
+/// Used by [Dialog] to arrange the actions at the bottom of the dialog.
+///
+/// See also:
+///
+///  * [TextButton], a simple flat button without a shadow.
+///  * [ElevatedButton], a filled button whose material elevates when pressed.
+///  * [OutlinedButton], a [TextButton] with a border outline.
+///  * [Card], at the bottom of which it is common to place a [ButtonBar].
+///  * [Dialog], which uses a [ButtonBar] for its actions.
+///  * [ButtonBarTheme], which configures the [ButtonBar].
+class ButtonBar extends StatelessWidget {
+  /// Creates a button bar.
+  ///
+  /// Both [buttonMinWidth] and [buttonHeight] must be non-negative if they
+  /// are not null.
+  const ButtonBar({
+    Key? key,
+    this.alignment,
+    this.mainAxisSize,
+    this.buttonTextTheme,
+    this.buttonMinWidth,
+    this.buttonHeight,
+    this.buttonPadding,
+    this.buttonAlignedDropdown,
+    this.layoutBehavior,
+    this.overflowDirection,
+    this.overflowButtonSpacing,
+    this.children = const <Widget>[],
+  }) : assert(buttonMinWidth == null || buttonMinWidth >= 0.0),
+       assert(buttonHeight == null || buttonHeight >= 0.0),
+       assert(overflowButtonSpacing == null || overflowButtonSpacing >= 0.0),
+       super(key: key);
+
+  /// How the children should be placed along the horizontal axis.
+  ///
+  /// If null then it will use [ButtonBarThemeData.alignment]. If that is null,
+  /// it will default to [MainAxisAlignment.end].
+  final MainAxisAlignment? alignment;
+
+  /// How much horizontal space is available. See [Row.mainAxisSize].
+  ///
+  /// If null then it will use the surrounding [ButtonBarThemeData.mainAxisSize].
+  /// If that is null, it will default to [MainAxisSize.max].
+  final MainAxisSize? mainAxisSize;
+
+  /// Overrides the surrounding [ButtonBarThemeData.buttonTextTheme] to define a
+  /// button's base colors, size, internal padding and shape.
+  ///
+  /// If null then it will use the surrounding
+  /// [ButtonBarThemeData.buttonTextTheme]. If that is null, it will default to
+  /// [ButtonTextTheme.primary].
+  final ButtonTextTheme? buttonTextTheme;
+
+  /// Overrides the surrounding [ButtonThemeData.minWidth] to define a button's
+  /// minimum width.
+  ///
+  /// If null then it will use the surrounding [ButtonBarThemeData.buttonMinWidth].
+  /// If that is null, it will default to 64.0 logical pixels.
+  final double? buttonMinWidth;
+
+  /// Overrides the surrounding [ButtonThemeData.height] to define a button's
+  /// minimum height.
+  ///
+  /// If null then it will use the surrounding [ButtonBarThemeData.buttonHeight].
+  /// If that is null, it will default to 36.0 logical pixels.
+  final double? buttonHeight;
+
+  /// Overrides the surrounding [ButtonThemeData.padding] to define the padding
+  /// for a button's child (typically the button's label).
+  ///
+  /// If null then it will use the surrounding [ButtonBarThemeData.buttonPadding].
+  /// If that is null, it will default to 8.0 logical pixels on the left
+  /// and right.
+  final EdgeInsetsGeometry? buttonPadding;
+
+  /// Overrides the surrounding [ButtonThemeData.alignedDropdown] to define whether
+  /// a [DropdownButton] menu's width will match the button's width.
+  ///
+  /// If null then it will use the surrounding [ButtonBarThemeData.buttonAlignedDropdown].
+  /// If that is null, it will default to false.
+  final bool? buttonAlignedDropdown;
+
+  /// Defines whether a [ButtonBar] should size itself with a minimum size
+  /// constraint or with padding.
+  ///
+  /// Overrides the surrounding [ButtonThemeData.layoutBehavior].
+  ///
+  /// If null then it will use the surrounding [ButtonBarThemeData.layoutBehavior].
+  /// If that is null, it will default [ButtonBarLayoutBehavior.padded].
+  final ButtonBarLayoutBehavior? layoutBehavior;
+
+  /// Defines the vertical direction of a [ButtonBar]'s children if it
+  /// overflows.
+  ///
+  /// If [children] do not fit into a single row, then they
+  /// are arranged in a column. The first action is at the top of the
+  /// column if this property is set to [VerticalDirection.down], since it
+  /// "starts" at the top and "ends" at the bottom. On the other hand,
+  /// the first action will be at the bottom of the column if this
+  /// property is set to [VerticalDirection.up], since it "starts" at the
+  /// bottom and "ends" at the top.
+  ///
+  /// If null then it will use the surrounding
+  /// [ButtonBarThemeData.overflowDirection]. If that is null, it will
+  /// default to [VerticalDirection.down].
+  final VerticalDirection? overflowDirection;
+
+  /// The spacing between buttons when the button bar overflows.
+  ///
+  /// If the [children] do not fit into a single row, they are
+  /// arranged into a column. This parameter provides additional
+  /// vertical space in between buttons when it does overflow.
+  ///
+  /// Note that the button spacing may appear to be more than
+  /// the value provided. This is because most buttons adhere to the
+  /// [MaterialTapTargetSize] of 48px. So, even though a button
+  /// might visually be 36px in height, it might still take up to
+  /// 48px vertically.
+  ///
+  /// If null then no spacing will be added in between buttons in
+  /// an overflow state.
+  final double? overflowButtonSpacing;
+
+  /// The buttons to arrange horizontally.
+  ///
+  /// Typically [ElevatedButton] or [TextButton] widgets.
+  final List<Widget> children;
+
+  @override
+  Widget build(BuildContext context) {
+    final ButtonThemeData parentButtonTheme = ButtonTheme.of(context);
+    final ButtonBarThemeData barTheme = ButtonBarTheme.of(context);
+
+    final ButtonThemeData buttonTheme = parentButtonTheme.copyWith(
+      textTheme: buttonTextTheme ?? barTheme.buttonTextTheme ?? ButtonTextTheme.primary,
+      minWidth: buttonMinWidth ?? barTheme.buttonMinWidth ?? 64.0,
+      height: buttonHeight ?? barTheme.buttonHeight ?? 36.0,
+      padding: buttonPadding ?? barTheme.buttonPadding ?? const EdgeInsets.symmetric(horizontal: 8.0),
+      alignedDropdown: buttonAlignedDropdown ?? barTheme.buttonAlignedDropdown ?? false,
+      layoutBehavior: layoutBehavior ?? barTheme.layoutBehavior ?? ButtonBarLayoutBehavior.padded,
+    );
+
+    // We divide by 4.0 because we want half of the average of the left and right padding.
+    final double paddingUnit = buttonTheme.padding.horizontal / 4.0;
+    final Widget child = ButtonTheme.fromButtonThemeData(
+      data: buttonTheme,
+      child: _ButtonBarRow(
+        mainAxisAlignment: alignment ?? barTheme.alignment ?? MainAxisAlignment.end,
+        mainAxisSize: mainAxisSize ?? barTheme.mainAxisSize ?? MainAxisSize.max,
+        overflowDirection: overflowDirection ?? barTheme.overflowDirection ?? VerticalDirection.down,
+        children: children.map<Widget>((Widget child) {
+          return Padding(
+            padding: EdgeInsets.symmetric(horizontal: paddingUnit),
+            child: child,
+          );
+        }).toList(),
+        overflowButtonSpacing: overflowButtonSpacing,
+      ),
+    );
+    switch (buttonTheme.layoutBehavior) {
+      case ButtonBarLayoutBehavior.padded:
+        return Padding(
+          padding: EdgeInsets.symmetric(
+            vertical: 2.0 * paddingUnit,
+            horizontal: paddingUnit,
+          ),
+          child: child,
+        );
+      case ButtonBarLayoutBehavior.constrained:
+        return Container(
+          padding: EdgeInsets.symmetric(horizontal: paddingUnit),
+          constraints: const BoxConstraints(minHeight: 52.0),
+          alignment: Alignment.center,
+          child: child,
+        );
+    }
+  }
+}
+
+/// Attempts to display buttons in a row, but displays them in a column if
+/// there is not enough horizontal space.
+///
+/// It first attempts to lay out its buttons as though there were no
+/// maximum width constraints on the widget. If the button bar's width is
+/// less than the maximum width constraints of the widget, it then lays
+/// out the widget as though it were placed in a [Row].
+///
+/// However, if the button bar's width exceeds the maximum width constraint on
+/// the widget, it then aligns its buttons in a column. The key difference here
+/// is that the [MainAxisAlignment] will then be treated as a
+/// cross-axis/horizontal alignment. For example, if the buttons overflow and
+/// [ButtonBar.alignment] was set to [MainAxisAlignment.start], the column of
+/// buttons would align to the horizontal start of the button bar.
+class _ButtonBarRow extends Flex {
+  /// Creates a button bar that attempts to display in a row, but displays in
+  /// a column if there is insufficient horizontal space.
+  _ButtonBarRow({
+    required List<Widget> children,
+    Axis direction = Axis.horizontal,
+    MainAxisSize mainAxisSize = MainAxisSize.max,
+    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
+    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
+    TextDirection? textDirection,
+    VerticalDirection overflowDirection = VerticalDirection.down,
+    TextBaseline? textBaseline,
+    this.overflowButtonSpacing,
+  }) : super(
+    children: children,
+    direction: direction,
+    mainAxisSize: mainAxisSize,
+    mainAxisAlignment: mainAxisAlignment,
+    crossAxisAlignment: crossAxisAlignment,
+    textDirection: textDirection,
+    verticalDirection: overflowDirection,
+    textBaseline: textBaseline,
+  );
+
+  final double? overflowButtonSpacing;
+
+  @override
+  _RenderButtonBarRow createRenderObject(BuildContext context) {
+    return _RenderButtonBarRow(
+      direction: direction,
+      mainAxisAlignment: mainAxisAlignment,
+      mainAxisSize: mainAxisSize,
+      crossAxisAlignment: crossAxisAlignment,
+      textDirection: getEffectiveTextDirection(context)!,
+      verticalDirection: verticalDirection,
+      textBaseline: textBaseline,
+      overflowButtonSpacing: overflowButtonSpacing,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, covariant _RenderButtonBarRow renderObject) {
+    renderObject
+      ..direction = direction
+      ..mainAxisAlignment = mainAxisAlignment
+      ..mainAxisSize = mainAxisSize
+      ..crossAxisAlignment = crossAxisAlignment
+      ..textDirection = getEffectiveTextDirection(context)
+      ..verticalDirection = verticalDirection
+      ..textBaseline = textBaseline
+      ..overflowButtonSpacing = overflowButtonSpacing;
+  }
+}
+
+/// Attempts to display buttons in a row, but displays them in a column if
+/// there is not enough horizontal space.
+///
+/// It first attempts to lay out its buttons as though there were no
+/// maximum width constraints on the widget. If the button bar's width is
+/// less than the maximum width constraints of the widget, it then lays
+/// out the widget as though it were placed in a [Row].
+///
+/// However, if the button bar's width exceeds the maximum width constraint on
+/// the widget, it then aligns its buttons in a column. The key difference here
+/// is that the [MainAxisAlignment] will then be treated as a
+/// cross-axis/horizontal alignment. For example, if the buttons overflow and
+/// [ButtonBar.alignment] was set to [MainAxisAlignment.start], the buttons would
+/// align to the horizontal start of the button bar.
+class _RenderButtonBarRow extends RenderFlex {
+  /// Creates a button bar that attempts to display in a row, but displays in
+  /// a column if there is insufficient horizontal space.
+  _RenderButtonBarRow({
+    List<RenderBox>? children,
+    Axis direction = Axis.horizontal,
+    MainAxisSize mainAxisSize = MainAxisSize.max,
+    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
+    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
+    required TextDirection textDirection,
+    VerticalDirection verticalDirection = VerticalDirection.down,
+    TextBaseline? textBaseline,
+    this.overflowButtonSpacing,
+  }) : assert(textDirection != null),
+       assert(overflowButtonSpacing == null || overflowButtonSpacing >= 0),
+       super(
+         children: children,
+         direction: direction,
+         mainAxisSize: mainAxisSize,
+         mainAxisAlignment: mainAxisAlignment,
+         crossAxisAlignment: crossAxisAlignment,
+         textDirection: textDirection,
+         verticalDirection: verticalDirection,
+         textBaseline: textBaseline,
+       );
+
+  bool _hasCheckedLayoutWidth = false;
+  double? overflowButtonSpacing;
+
+  @override
+  BoxConstraints get constraints {
+    if (_hasCheckedLayoutWidth)
+      return super.constraints;
+    return super.constraints.copyWith(maxWidth: double.infinity);
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    final Size size = super.computeDryLayout(constraints.copyWith(maxWidth: double.infinity));
+    if (size.width <= constraints.maxWidth) {
+      return super.computeDryLayout(constraints);
+    }
+    double currentHeight = 0.0;
+    RenderBox? child = firstChild;
+    while (child != null) {
+      final BoxConstraints childConstraints = constraints.copyWith(minWidth: 0.0);
+      final Size childSize = child.getDryLayout(childConstraints);
+      currentHeight += childSize.height;
+      child = childAfter(child);
+      if (overflowButtonSpacing != null && child != null)
+        currentHeight += overflowButtonSpacing!;
+    }
+    return constraints.constrain(Size(constraints.maxWidth, currentHeight));
+  }
+
+  @override
+  void performLayout() {
+    // Set check layout width to false in reload or update cases.
+    _hasCheckedLayoutWidth = false;
+
+    // Perform layout to ensure that button bar knows how wide it would
+    // ideally want to be.
+    super.performLayout();
+    _hasCheckedLayoutWidth = true;
+
+    // If the button bar is constrained by width and it overflows, set the
+    // buttons to align vertically. Otherwise, lay out the button bar
+    // horizontally.
+    if (size.width <= constraints.maxWidth) {
+      // A second performLayout is required to ensure that the original maximum
+      // width constraints are used. The original perform layout call assumes
+      // a maximum width constraint of infinity.
+      super.performLayout();
+    } else {
+      final BoxConstraints childConstraints = constraints.copyWith(minWidth: 0.0);
+      RenderBox? child;
+      double currentHeight = 0.0;
+      switch (verticalDirection) {
+        case VerticalDirection.down:
+          child = firstChild;
+          break;
+        case VerticalDirection.up:
+          child = lastChild;
+          break;
+      }
+
+      while (child != null) {
+        final FlexParentData childParentData = child.parentData! as FlexParentData;
+
+        // Lay out the child with the button bar's original constraints, but
+        // with minimum width set to zero.
+        child.layout(childConstraints, parentUsesSize: true);
+
+        // Set the cross axis alignment for the column to match the main axis
+        // alignment for a row. For [MainAxisAlignment.spaceAround],
+        // [MainAxisAlignment.spaceBetween] and [MainAxisAlignment.spaceEvenly]
+        // cases, use [MainAxisAlignment.start].
+        switch (textDirection!) {
+          case TextDirection.ltr:
+            switch (mainAxisAlignment) {
+              case MainAxisAlignment.center:
+                final double midpoint = (constraints.maxWidth - child.size.width) / 2.0;
+                childParentData.offset = Offset(midpoint, currentHeight);
+                break;
+              case MainAxisAlignment.end:
+                childParentData.offset = Offset(constraints.maxWidth - child.size.width, currentHeight);
+                break;
+              default:
+                childParentData.offset = Offset(0, currentHeight);
+                break;
+            }
+            break;
+          case TextDirection.rtl:
+            switch (mainAxisAlignment) {
+              case MainAxisAlignment.center:
+                final double midpoint = constraints.maxWidth / 2.0 - child.size.width / 2.0;
+                childParentData.offset = Offset(midpoint, currentHeight);
+                break;
+              case MainAxisAlignment.end:
+                childParentData.offset = Offset(0, currentHeight);
+                break;
+              default:
+                childParentData.offset = Offset(constraints.maxWidth - child.size.width, currentHeight);
+                break;
+            }
+            break;
+        }
+        currentHeight += child.size.height;
+        switch (verticalDirection) {
+          case VerticalDirection.down:
+            child = childParentData.nextSibling;
+            break;
+          case VerticalDirection.up:
+            child = childParentData.previousSibling;
+            break;
+        }
+
+        if (overflowButtonSpacing != null && child != null)
+          currentHeight += overflowButtonSpacing!;
+      }
+      size = constraints.constrain(Size(constraints.maxWidth, currentHeight));
+    }
+  }
+}
diff --git a/lib/src/material/button_bar_theme.dart b/lib/src/material/button_bar_theme.dart
new file mode 100644
index 0000000..d315fa9
--- /dev/null
+++ b/lib/src/material/button_bar_theme.dart
@@ -0,0 +1,260 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'button_theme.dart';
+import 'theme.dart';
+
+/// Defines the visual properties of [ButtonBar] widgets.
+///
+/// Used by [ButtonBarTheme] to control the visual properties of [ButtonBar]
+/// instances in a widget subtree.
+///
+/// To obtain this configuration, use [ButtonBarTheme.of] to access the closest
+/// ancestor [ButtonBarTheme] of the current [BuildContext].
+///
+/// See also:
+///
+///  * [ButtonBarTheme], an [InheritedWidget] that propagates the theme down
+///    its subtree.
+///  * [ButtonBar], which uses this to configure itself and its children
+///    button widgets.
+@immutable
+class ButtonBarThemeData with Diagnosticable {
+  /// Constructs the set of properties used to configure [ButtonBar] widgets.
+  ///
+  /// Both [buttonMinWidth] and [buttonHeight] must be non-negative if they
+  /// are not null.
+  const ButtonBarThemeData({
+    this.alignment,
+    this.mainAxisSize,
+    this.buttonTextTheme,
+    this.buttonMinWidth,
+    this.buttonHeight,
+    this.buttonPadding,
+    this.buttonAlignedDropdown,
+    this.layoutBehavior,
+    this.overflowDirection,
+  }) : assert(buttonMinWidth == null || buttonMinWidth >= 0.0),
+       assert(buttonHeight == null || buttonHeight >= 0.0);
+
+  /// How the children should be placed along the horizontal axis.
+  final MainAxisAlignment? alignment;
+
+  /// How much horizontal space is available. See [Row.mainAxisSize].
+  final MainAxisSize? mainAxisSize;
+
+  /// Defines a [ButtonBar] button's base colors, and the defaults for
+  /// the button's minimum size, internal padding, and shape.
+  ///
+  /// This will override the surrounding [ButtonThemeData.textTheme] setting
+  /// for buttons contained in the [ButtonBar].
+  ///
+  /// Despite the name, this property is not a [TextTheme], its value is not a
+  /// collection of [TextStyle]s.
+  final ButtonTextTheme? buttonTextTheme;
+
+  /// The minimum width for [ButtonBar] buttons.
+  ///
+  /// This will override the surrounding [ButtonThemeData.minWidth] setting
+  /// for buttons contained in the [ButtonBar].
+  ///
+  /// The actual horizontal space allocated for a button's child is
+  /// at least this value less the theme's horizontal [ButtonThemeData.padding].
+  final double? buttonMinWidth;
+
+  /// The minimum height for [ButtonBar] buttons.
+  ///
+  /// This will override the surrounding [ButtonThemeData.height] setting
+  /// for buttons contained in the [ButtonBar].
+  final double? buttonHeight;
+
+  /// Padding for a [ButtonBar] button's child (typically the button's label).
+  ///
+  /// This will override the surrounding [ButtonThemeData.padding] setting
+  /// for buttons contained in the [ButtonBar].
+  final EdgeInsetsGeometry? buttonPadding;
+
+  /// If true, then a [DropdownButton] menu's width will match the [ButtonBar]
+  /// button's width.
+  ///
+  /// If false, then the dropdown's menu will be wider than
+  /// its button. In either case the dropdown button will line up the leading
+  /// edge of the menu's value with the leading edge of the values
+  /// displayed by the menu items.
+  ///
+  /// This will override the surrounding [ButtonThemeData.alignedDropdown] setting
+  /// for buttons contained in the [ButtonBar].
+  ///
+  /// This property only affects [DropdownButton] contained in a [ButtonBar]
+  /// and its menu.
+  final bool? buttonAlignedDropdown;
+
+  /// Defines whether a [ButtonBar] should size itself with a minimum size
+  /// constraint or with padding.
+  final ButtonBarLayoutBehavior? layoutBehavior;
+
+  /// Defines the vertical direction of a [ButtonBar]'s children if it
+  /// overflows.
+  ///
+  /// If the [ButtonBar]'s children do not fit into a single row, then they
+  /// are arranged in a column. The first action is at the top of the
+  /// column if this property is set to [VerticalDirection.down], since it
+  /// "starts" at the top and "ends" at the bottom. On the other hand,
+  /// the first action will be at the bottom of the column if this
+  /// property is set to [VerticalDirection.up], since it "starts" at the
+  /// bottom and "ends" at the top.
+  final VerticalDirection? overflowDirection;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  ButtonBarThemeData copyWith({
+    MainAxisAlignment? alignment,
+    MainAxisSize? mainAxisSize,
+    ButtonTextTheme? buttonTextTheme,
+    double? buttonMinWidth,
+    double? buttonHeight,
+    EdgeInsetsGeometry? buttonPadding,
+    bool? buttonAlignedDropdown,
+    ButtonBarLayoutBehavior? layoutBehavior,
+    VerticalDirection? overflowDirection,
+  }) {
+    return ButtonBarThemeData(
+      alignment: alignment ?? this.alignment,
+      mainAxisSize: mainAxisSize ?? this.mainAxisSize,
+      buttonTextTheme: buttonTextTheme ?? this.buttonTextTheme,
+      buttonMinWidth: buttonMinWidth ?? this.buttonMinWidth,
+      buttonHeight: buttonHeight ?? this.buttonHeight,
+      buttonPadding: buttonPadding ?? this.buttonPadding,
+      buttonAlignedDropdown: buttonAlignedDropdown ?? this.buttonAlignedDropdown,
+      layoutBehavior: layoutBehavior ?? this.layoutBehavior,
+      overflowDirection: overflowDirection ?? this.overflowDirection,
+    );
+  }
+
+  /// Linearly interpolate between two button bar themes.
+  ///
+  /// If both arguments are null, then null is returned.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static ButtonBarThemeData? lerp(ButtonBarThemeData? a, ButtonBarThemeData? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    return ButtonBarThemeData(
+      alignment: t < 0.5 ? a?.alignment : b?.alignment,
+      mainAxisSize: t < 0.5 ? a?.mainAxisSize : b?.mainAxisSize,
+      buttonTextTheme: t < 0.5 ? a?.buttonTextTheme : b?.buttonTextTheme,
+      buttonMinWidth: lerpDouble(a?.buttonMinWidth, b?.buttonMinWidth, t),
+      buttonHeight: lerpDouble(a?.buttonHeight, b?.buttonHeight, t),
+      buttonPadding: EdgeInsetsGeometry.lerp(a?.buttonPadding, b?.buttonPadding, t),
+      buttonAlignedDropdown: t < 0.5 ? a?.buttonAlignedDropdown : b?.buttonAlignedDropdown,
+      layoutBehavior: t < 0.5 ? a?.layoutBehavior : b?.layoutBehavior,
+      overflowDirection: t < 0.5 ? a?.overflowDirection : b?.overflowDirection,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      alignment,
+      mainAxisSize,
+      buttonTextTheme,
+      buttonMinWidth,
+      buttonHeight,
+      buttonPadding,
+      buttonAlignedDropdown,
+      layoutBehavior,
+      overflowDirection,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ButtonBarThemeData
+        && other.alignment == alignment
+        && other.mainAxisSize == mainAxisSize
+        && other.buttonTextTheme == buttonTextTheme
+        && other.buttonMinWidth == buttonMinWidth
+        && other.buttonHeight == buttonHeight
+        && other.buttonPadding == buttonPadding
+        && other.buttonAlignedDropdown == buttonAlignedDropdown
+        && other.layoutBehavior == layoutBehavior
+        && other.overflowDirection == overflowDirection;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<MainAxisAlignment>('alignment', alignment, defaultValue: null));
+    properties.add(DiagnosticsProperty<MainAxisSize>('mainAxisSize', mainAxisSize, defaultValue: null));
+    properties.add(DiagnosticsProperty<ButtonTextTheme>('textTheme', buttonTextTheme, defaultValue: null));
+    properties.add(DoubleProperty('minWidth', buttonMinWidth, defaultValue: null));
+    properties.add(DoubleProperty('height', buttonHeight, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', buttonPadding, defaultValue: null));
+    properties.add(FlagProperty(
+        'buttonAlignedDropdown',
+        value: buttonAlignedDropdown,
+        ifTrue: 'dropdown width matches button',
+        defaultValue: null));
+    properties.add(DiagnosticsProperty<ButtonBarLayoutBehavior>('layoutBehavior', layoutBehavior, defaultValue: null));
+    properties.add(DiagnosticsProperty<VerticalDirection>('overflowDirection', overflowDirection, defaultValue: null));
+  }
+}
+
+/// Applies a button bar theme to descendant [ButtonBar] widgets.
+///
+/// A button bar theme describes the layout and properties for the buttons
+/// contained in a [ButtonBar].
+///
+/// Descendant widgets obtain the current theme's [ButtonBarTheme] object using
+/// [ButtonBarTheme.of]. When a widget uses [ButtonBarTheme.of], it is automatically
+/// rebuilt if the theme later changes.
+///
+/// A button bar theme can be specified as part of the overall Material theme
+/// using [ThemeData.buttonBarTheme].
+///
+/// See also:
+///
+///  * [ButtonBarThemeData], which describes the actual configuration of a button
+///    bar theme.
+class ButtonBarTheme extends InheritedWidget {
+  /// Constructs a button bar theme that configures all descendent [ButtonBar]
+  /// widgets.
+  ///
+  /// The [data] must not be null.
+  const ButtonBarTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null), super(key: key, child: child);
+
+  /// The properties used for all descendant [ButtonBar] widgets.
+  final ButtonBarThemeData data;
+
+  /// Returns the configuration [data] from the closest [ButtonBarTheme]
+  /// ancestor. If there is no ancestor, it returns [ThemeData.buttonBarTheme].
+  /// Applications can assume that the returned value will not be null.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// ButtonBarThemeData theme = ButtonBarTheme.of(context);
+  /// ```
+  static ButtonBarThemeData of(BuildContext context) {
+    final ButtonBarTheme? buttonBarTheme = context.dependOnInheritedWidgetOfExactType<ButtonBarTheme>();
+    return buttonBarTheme?.data ?? Theme.of(context).buttonBarTheme;
+  }
+
+  @override
+  bool updateShouldNotify(ButtonBarTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/button_style.dart b/lib/src/material/button_style.dart
new file mode 100644
index 0000000..30706f1
--- /dev/null
+++ b/lib/src/material/button_style.dart
@@ -0,0 +1,437 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'material_state.dart';
+import 'theme_data.dart';
+
+/// The visual properties that most buttons have in common.
+///
+/// Buttons and their themes have a ButtonStyle property which defines the visual
+/// properties whose default values are to be overridden. The default values are
+/// defined by the individual button widgets and are typically based on overall
+/// theme's [ThemeData.colorScheme] and [ThemeData.textTheme].
+///
+/// All of the ButtonStyle properties are null by default.
+///
+/// Many of the ButtonStyle properties are [MaterialStateProperty] objects which
+/// resolve to different values depending on the button's state. For example
+/// the [Color] properties are defined with `MaterialStateProperty<Color>` and
+/// can resolve to different colors depending on if the button is pressed,
+/// hovered, focused, disabled, etc.
+///
+/// These properties can override the default value for just one state or all of
+/// them. For example to create a [ElevatedButton] whose background color is the
+/// color scheme’s primary color with 50% opacity, but only when the button is
+/// pressed, one could write:
+///
+/// ```dart
+/// ElevatedButton(
+///   style: ButtonStyle(
+///     backgroundColor: MaterialStateProperty.resolveWith<Color>(
+///       (Set<MaterialState> states) {
+///         if (states.contains(MaterialState.pressed))
+///           return Theme.of(context).colorScheme.primary.withOpacity(0.5);
+///         return null; // Use the component's default.
+///       },
+///     ),
+///   ),
+/// )
+///```
+///
+/// In this case the background color for all other button states would fallback
+/// to the ElevatedButton’s default values. To unconditionally set the button's
+/// [backgroundColor] for all states one could write:
+///
+/// ```dart
+/// ElevatedButton(
+///   style: ButtonStyle(
+///     backgroundColor: MaterialStateProperty.all<Color>(Colors.green),
+///   ),
+/// )
+///```
+///
+/// Configuring a ButtonStyle directly makes it possible to very
+/// precisely control the button’s visual attributes for all states.
+/// This level of control is typically required when a custom
+/// “branded” look and feel is desirable.  However, in many cases it’s
+/// useful to make relatively sweeping changes based on a few initial
+/// parameters with simple values. The button styleFrom() methods
+/// enable such sweeping changes. See for example:
+/// [TextButton.styleFrom], [ElevatedButton.styleFrom],
+/// [OutlinedButton.styleFrom].
+///
+/// For example, to override the default text and icon colors for a
+/// [TextButton], as well as its overlay color, with all of the
+/// standard opacity adjustments for the pressed, focused, and
+/// hovered states, one could write:
+///
+/// ```dart
+/// TextButton(
+///   style: TextButton.styleFrom(primary: Colors.green),
+/// )
+///```
+///
+/// To configure all of the application's text buttons in the same
+/// way, specify the overall theme's `textButtonTheme`:
+/// ```dart
+/// MaterialApp(
+///   theme: ThemeData(
+///     textButtonTheme: TextButtonThemeData(
+///       style: TextButton.styleFrom(primary: Colors.green),
+///     ),
+///   ),
+///   home: MyAppHome(),
+/// )
+///```
+/// See also:
+///
+///  * [TextButtonTheme], the theme for [TextButton]s.
+///  * [ElevatedButtonTheme], the theme for [ElevatedButton]s.
+///  * [OutlinedButtonTheme], the theme for [OutlinedButton]s.
+@immutable
+class ButtonStyle with Diagnosticable {
+  /// Create a [ButtonStyle].
+  const ButtonStyle({
+    this.textStyle,
+    this.backgroundColor,
+    this.foregroundColor,
+    this.overlayColor,
+    this.shadowColor,
+    this.elevation,
+    this.padding,
+    this.minimumSize,
+    this.side,
+    this.shape,
+    this.mouseCursor,
+    this.visualDensity,
+    this.tapTargetSize,
+    this.animationDuration,
+    this.enableFeedback,
+  });
+
+  /// The style for a button's [Text] widget descendants.
+  ///
+  /// The color of the [textStyle] is typically not used directly, the
+  /// [foregroundColor] is used instead.
+  final MaterialStateProperty<TextStyle?>? textStyle;
+
+  /// The button's background fill color.
+  final MaterialStateProperty<Color?>? backgroundColor;
+
+  /// The color for the button's [Text] and [Icon] widget descendants.
+  ///
+  /// This color is typically used instead of the color of the [textStyle]. All
+  /// of the components that compute defaults from [ButtonStyle] values
+  /// compute a default [foregroundColor] and use that instead of the
+  /// [textStyle]'s color.
+  final MaterialStateProperty<Color?>? foregroundColor;
+
+  /// The highlight color that's typically used to indicate that
+  /// the button is focused, hovered, or pressed.
+  final MaterialStateProperty<Color?>? overlayColor;
+
+  /// The shadow color of the button's [Material].
+  ///
+  /// The material's elevation shadow can be difficult to see for
+  /// dark themes, so by default the button classes add a
+  /// semi-transparent overlay to indicate elevation. See
+  /// [ThemeData.applyElevationOverlayColor].
+  final MaterialStateProperty<Color?>? shadowColor;
+
+  /// The elevation of the button's [Material].
+  final MaterialStateProperty<double?>? elevation;
+
+  /// The padding between the button's boundary and its child.
+  final MaterialStateProperty<EdgeInsetsGeometry?>? padding;
+
+  /// The minimum size of the button itself.
+  ///
+  /// The size of the rectangle the button lies within may be larger
+  /// per [tapTargetSize].
+  final MaterialStateProperty<Size?>? minimumSize;
+
+  /// The color and weight of the button's outline.
+  ///
+  /// This value is combined with [shape] to create a shape decorated
+  /// with an outline.
+  final MaterialStateProperty<BorderSide?>? side;
+
+  /// The shape of the button's underlying [Material].
+  ///
+  /// This shape is combined with [side] to create a shape decorated
+  /// with an outline.
+  final MaterialStateProperty<OutlinedBorder?>? shape;
+
+  /// The cursor for a mouse pointer when it enters or is hovering over
+  /// this button's [InkWell].
+  final MaterialStateProperty<MouseCursor?>? mouseCursor;
+
+  /// Defines how compact the button's layout will be.
+  ///
+  /// {@macro flutter.material.themedata.visualDensity}
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.visualDensity], which specifies the [visualDensity] for all widgets
+  ///    within a [Theme].
+  final VisualDensity? visualDensity;
+
+  /// Configures the minimum size of the area within which the button may be pressed.
+  ///
+  /// If the [tapTargetSize] is larger than [minimumSize], the button will include
+  /// a transparent margin that responds to taps.
+  ///
+  /// Always defaults to [ThemeData.materialTapTargetSize].
+  final MaterialTapTargetSize? tapTargetSize;
+
+  /// Defines the duration of animated changes for [shape] and [elevation].
+  ///
+  /// Typically the component default value is [kThemeChangeDuration].
+  final Duration? animationDuration;
+
+  /// Whether detected gestures should provide acoustic and/or haptic feedback.
+  ///
+  /// For example, on Android a tap will produce a clicking sound and a
+  /// long-press will produce a short vibration, when feedback is enabled.
+  ///
+  /// Typically the component default value is true.
+  ///
+  /// See also:
+  ///
+  ///  * [Feedback] for providing platform-specific feedback to certain actions.
+  final bool? enableFeedback;
+
+  /// Returns a copy of this ButtonStyle with the given fields replaced with
+  /// the new values.
+  ButtonStyle copyWith({
+    MaterialStateProperty<TextStyle?>? textStyle,
+    MaterialStateProperty<Color?>? backgroundColor,
+    MaterialStateProperty<Color?>? foregroundColor,
+    MaterialStateProperty<Color?>? overlayColor,
+    MaterialStateProperty<Color?>? shadowColor,
+    MaterialStateProperty<double?>? elevation,
+    MaterialStateProperty<EdgeInsetsGeometry?>? padding,
+    MaterialStateProperty<Size?>? minimumSize,
+    MaterialStateProperty<BorderSide?>? side,
+    MaterialStateProperty<OutlinedBorder?>? shape,
+    MaterialStateProperty<MouseCursor?>? mouseCursor,
+    VisualDensity? visualDensity,
+    MaterialTapTargetSize? tapTargetSize,
+    Duration? animationDuration,
+    bool? enableFeedback,
+  }) {
+    return ButtonStyle(
+      textStyle: textStyle ?? this.textStyle,
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      foregroundColor: foregroundColor ?? this.foregroundColor,
+      overlayColor: overlayColor ?? this.overlayColor,
+      shadowColor: shadowColor ?? this.shadowColor,
+      elevation: elevation ?? this.elevation,
+      padding: padding ?? this.padding,
+      minimumSize: minimumSize ?? this.minimumSize,
+      side: side ?? this.side,
+      shape: shape ?? this.shape,
+      mouseCursor: mouseCursor ?? this.mouseCursor,
+      visualDensity: visualDensity ?? this.visualDensity,
+      tapTargetSize: tapTargetSize ?? this.tapTargetSize,
+      animationDuration: animationDuration ?? this.animationDuration,
+      enableFeedback: enableFeedback ?? this.enableFeedback,
+    );
+  }
+
+  /// Returns a copy of this ButtonStyle where the non-null fields in [style]
+  /// have replaced the corresponding null fields in this ButtonStyle.
+  ///
+  /// In other words, [style] is used to fill in unspecified (null) fields
+  /// this ButtonStyle.
+  ButtonStyle merge(ButtonStyle? style) {
+    if (style == null)
+      return this;
+    return copyWith(
+      textStyle: textStyle ?? style.textStyle,
+      backgroundColor: backgroundColor ?? style.backgroundColor,
+      foregroundColor: foregroundColor ?? style.foregroundColor,
+      overlayColor: overlayColor ?? style.overlayColor,
+      shadowColor: shadowColor ?? style.shadowColor,
+      elevation: elevation ?? style.elevation,
+      padding: padding ?? style.padding,
+      minimumSize: minimumSize ?? style.minimumSize,
+      side: side ?? style.side,
+      shape: shape ?? style.shape,
+      mouseCursor: mouseCursor ?? style.mouseCursor,
+      visualDensity: visualDensity ?? style.visualDensity,
+      tapTargetSize: tapTargetSize ?? style.tapTargetSize,
+      animationDuration: animationDuration ?? style.animationDuration,
+      enableFeedback: enableFeedback ?? style.enableFeedback,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      textStyle,
+      backgroundColor,
+      foregroundColor,
+      overlayColor,
+      shadowColor,
+      elevation,
+      padding,
+      minimumSize,
+      side,
+      shape,
+      mouseCursor,
+      visualDensity,
+      tapTargetSize,
+      animationDuration,
+      enableFeedback,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ButtonStyle
+        && other.textStyle == textStyle
+        && other.backgroundColor == backgroundColor
+        && other.foregroundColor == foregroundColor
+        && other.overlayColor == overlayColor
+        && other.shadowColor == shadowColor
+        && other.elevation == elevation
+        && other.padding == padding
+        && other.minimumSize == minimumSize
+        && other.side == side
+        && other.shape == shape
+        && other.mouseCursor == mouseCursor
+        && other.visualDensity == visualDensity
+        && other.tapTargetSize == tapTargetSize
+        && other.animationDuration == animationDuration
+        && other.enableFeedback == enableFeedback;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<MaterialStateProperty<TextStyle?>>('textStyle', textStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('backgroundColor', backgroundColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('foregroundColor', foregroundColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('shadowColor', shadowColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<double?>>('elevation', elevation, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<EdgeInsetsGeometry?>>('padding', padding, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Size?>>('minimumSize', minimumSize, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<BorderSide?>>('side', side, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<OutlinedBorder?>>('shape', shape, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
+    properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
+    properties.add(EnumProperty<MaterialTapTargetSize>('tapTargetSize', tapTargetSize, defaultValue: null));
+    properties.add(DiagnosticsProperty<Duration>('animationDuration', animationDuration, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('enableFeedback', enableFeedback, defaultValue: null));
+  }
+
+  /// Linearly interpolate between two [ButtonStyle]s.
+  static ButtonStyle? lerp(ButtonStyle? a, ButtonStyle? b, double t) {
+    assert (t != null);
+    if (a == null && b == null)
+      return null;
+    return ButtonStyle(
+      textStyle: _lerpProperties<TextStyle?>(a?.textStyle, b?.textStyle, t, TextStyle.lerp),
+      backgroundColor: _lerpProperties<Color?>(a?.backgroundColor, b?.backgroundColor, t, Color.lerp),
+      foregroundColor:  _lerpProperties<Color?>(a?.foregroundColor, b?.foregroundColor, t, Color.lerp),
+      overlayColor: _lerpProperties<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
+      shadowColor: _lerpProperties<Color?>(a?.shadowColor, b?.shadowColor, t, Color.lerp),
+      elevation: _lerpProperties<double?>(a?.elevation, b?.elevation, t, lerpDouble),
+      padding:  _lerpProperties<EdgeInsetsGeometry?>(a?.padding, b?.padding, t, EdgeInsetsGeometry.lerp),
+      minimumSize: _lerpProperties<Size?>(a?.minimumSize, b?.minimumSize, t, Size.lerp),
+      side: _lerpSides(a?.side, b?.side, t),
+      shape: _lerpShapes(a?.shape, b?.shape, t),
+      mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
+      visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
+      tapTargetSize: t < 0.5 ? a?.tapTargetSize : b?.tapTargetSize,
+      animationDuration: t < 0.5 ? a?.animationDuration : b?.animationDuration,
+      enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback,
+    );
+  }
+
+  static MaterialStateProperty<T?>? _lerpProperties<T>(MaterialStateProperty<T>? a, MaterialStateProperty<T>? b, double t, T? Function(T?, T?, double) lerpFunction ) {
+    // Avoid creating a _LerpProperties object for a common case.
+    if (a == null && b == null)
+      return null;
+    return _LerpProperties<T>(a, b, t, lerpFunction);
+  }
+
+  // Special case because BorderSide.lerp() doesn't support null arguments
+  static MaterialStateProperty<BorderSide?>? _lerpSides(MaterialStateProperty<BorderSide?>? a, MaterialStateProperty<BorderSide?>? b, double t) {
+    if (a == null && b == null)
+      return null;
+    return _LerpSides(a, b, t);
+  }
+
+  // TODO(hansmuller): OutlinedBorder needs a lerp method - https://github.com/flutter/flutter/issues/60555.
+  static MaterialStateProperty<OutlinedBorder?>? _lerpShapes(MaterialStateProperty<OutlinedBorder?>? a, MaterialStateProperty<OutlinedBorder?>? b, double t) {
+    if (a == null && b == null)
+      return null;
+    return _LerpShapes(a, b, t);
+  }
+}
+
+class _LerpProperties<T> implements MaterialStateProperty<T?> {
+  const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
+
+  final MaterialStateProperty<T>? a;
+  final MaterialStateProperty<T>? b;
+  final double t;
+  final T? Function(T?, T?, double) lerpFunction;
+
+  @override
+  T? resolve(Set<MaterialState> states) {
+    final T? resolvedA = a?.resolve(states);
+    final T? resolvedB = b?.resolve(states);
+    return lerpFunction(resolvedA, resolvedB, t);
+  }
+}
+
+class _LerpSides implements MaterialStateProperty<BorderSide?> {
+  const _LerpSides(this.a, this.b, this.t);
+
+  final MaterialStateProperty<BorderSide?>? a;
+  final MaterialStateProperty<BorderSide?>? b;
+  final double t;
+
+  @override
+  BorderSide? resolve(Set<MaterialState> states) {
+    final BorderSide? resolvedA = a?.resolve(states);
+    final BorderSide? resolvedB = b?.resolve(states);
+    if (resolvedA == null && resolvedB == null)
+      return null;
+    if (resolvedA == null)
+      return BorderSide.lerp(BorderSide(width: 0, color: resolvedB!.color.withAlpha(0)), resolvedB, t);
+    if (resolvedB == null)
+      return BorderSide.lerp(BorderSide(width: 0, color: resolvedA.color.withAlpha(0)), resolvedA, t);
+    return BorderSide.lerp(resolvedA, resolvedB, t);
+  }
+}
+
+class _LerpShapes implements MaterialStateProperty<OutlinedBorder?> {
+  const _LerpShapes(this.a, this.b, this.t);
+
+  final MaterialStateProperty<OutlinedBorder?>? a;
+  final MaterialStateProperty<OutlinedBorder?>? b;
+  final double t;
+
+  @override
+  OutlinedBorder? resolve(Set<MaterialState> states) {
+    final OutlinedBorder? resolvedA = a?.resolve(states);
+    final OutlinedBorder? resolvedB = b?.resolve(states);
+    return ShapeBorder.lerp(resolvedA, resolvedB, t) as OutlinedBorder?;
+  }
+}
diff --git a/lib/src/material/button_style_button.dart b/lib/src/material/button_style_button.dart
new file mode 100644
index 0000000..ae10028
--- /dev/null
+++ b/lib/src/material/button_style_button.dart
@@ -0,0 +1,523 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'button_style.dart';
+import 'colors.dart';
+import 'constants.dart';
+import 'ink_ripple.dart';
+import 'ink_well.dart';
+import 'material.dart';
+import 'material_state.dart';
+import 'theme_data.dart';
+
+/// The base [StatefulWidget] class for buttons whose style is defined by a [ButtonStyle] object.
+///
+/// Concrete subclasses must override [defaultStyleOf] and [themeStyleOf].
+///
+/// See also:
+///
+///  * [TextButton], a simple ButtonStyleButton without a shadow.
+///  * [ElevatedButton], a filled ButtonStyleButton whose material elevates when pressed.
+///  * [OutlinedButton], similar to [TextButton], but with an outline.
+abstract class ButtonStyleButton extends StatefulWidget {
+  /// Create a [ButtonStyleButton].
+  const ButtonStyleButton({
+    Key? key,
+    required this.onPressed,
+    required this.onLongPress,
+    required this.style,
+    required this.focusNode,
+    required this.autofocus,
+    required this.clipBehavior,
+    required this.child,
+  }) : assert(autofocus != null),
+       assert(clipBehavior != null),
+       super(key: key);
+
+  /// Called when the button is tapped or otherwise activated.
+  ///
+  /// If this callback and [onLongPress] are null, then the button will be disabled.
+  ///
+  /// See also:
+  ///
+  ///  * [enabled], which is true if the button is enabled.
+  final VoidCallback? onPressed;
+
+  /// Called when the button is long-pressed.
+  ///
+  /// If this callback and [onPressed] are null, then the button will be disabled.
+  ///
+  /// See also:
+  ///
+  ///  * [enabled], which is true if the button is enabled.
+  final VoidCallback? onLongPress;
+
+  /// Customizes this button's appearance.
+  ///
+  /// Non-null properties of this style override the corresponding
+  /// properties in [themeStyleOf] and [defaultStyleOf]. [MaterialStateProperty]s
+  /// that resolve to non-null values will similarly override the corresponding
+  /// [MaterialStateProperty]s in [themeStyleOf] and [defaultStyleOf].
+  ///
+  /// Null by default.
+  final ButtonStyle? style;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  final Clip clipBehavior;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  /// Typically the button's label.
+  final Widget? child;
+
+  /// Returns a non-null [ButtonStyle] that's based primarily on the [Theme]'s
+  /// [ThemeData.textTheme] and [ThemeData.colorScheme].
+  ///
+  /// The returned style can be overridden by the [style] parameter and
+  /// by the style returned by [themeStyleOf]. For example the default
+  /// style of the [TextButton] subclass can be overridden with its
+  /// [TextButton.style] constructor parameter, or with a
+  /// [TextButtonTheme].
+  ///
+  /// Concrete button subclasses should return a ButtonStyle that
+  /// has no null properties, and where all of the [MaterialStateProperty]
+  /// properties resolve to non-null values.
+  ///
+  /// See also:
+  ///
+  ///  * [themeStyleOf], Returns the ButtonStyle of this button's component theme.
+  @protected
+  ButtonStyle defaultStyleOf(BuildContext context);
+
+  /// Returns the ButtonStyle that belongs to the button's component theme.
+  ///
+  /// The returned style can be overridden by the [style] parameter.
+  ///
+  /// Concrete button subclasses should return the ButtonStyle for the
+  /// nearest subclass-specific inherited theme, and if no such theme
+  /// exists, then the same value from the overall [Theme].
+  ///
+  /// See also:
+  ///
+  ///  * [defaultStyleOf], Returns the default [ButtonStyle] for this button.
+  @protected
+  ButtonStyle? themeStyleOf(BuildContext context);
+
+  /// Whether the button is enabled or disabled.
+  ///
+  /// Buttons are disabled by default. To enable a button, set its [onPressed]
+  /// or [onLongPress] properties to a non-null value.
+  bool get enabled => onPressed != null || onLongPress != null;
+
+  @override
+  _ButtonStyleState createState() => _ButtonStyleState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('enabled', value: enabled, ifFalse: 'disabled'));
+    properties.add(DiagnosticsProperty<ButtonStyle>('style', style, defaultValue: null));
+    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
+  }
+
+  /// Returns null if [value] is null, otherwise `MaterialStateProperty.all<T>(value)`.
+  ///
+  /// A convenience method for subclasses.
+  static MaterialStateProperty<T>? allOrNull<T>(T? value) => value == null ? null : MaterialStateProperty.all<T>(value);
+
+  /// Returns an interpolated value based on the [textScaleFactor] parameter:
+  ///
+  ///  * 0 - 1 [geometry1x]
+  ///  * 1 - 2 lerp([geometry1x], [geometry2x], [textScaleFactor] - 1)
+  ///  * 2 - 3 lerp([geometry2x], [geometry3x], [textScaleFactor] - 2)
+  ///  * otherwise [geometry3x]
+  ///
+  /// A convenience method for subclasses.
+  static EdgeInsetsGeometry scaledPadding(
+    EdgeInsetsGeometry geometry1x,
+    EdgeInsetsGeometry geometry2x,
+    EdgeInsetsGeometry geometry3x,
+    double textScaleFactor,
+  ) {
+    assert(geometry1x != null);
+    assert(geometry2x != null);
+    assert(geometry3x != null);
+    assert(textScaleFactor != null);
+
+    if (textScaleFactor <= 1) {
+      return geometry1x;
+    } else if (textScaleFactor >= 3) {
+      return geometry3x;
+    } else if (textScaleFactor <= 2) {
+      return EdgeInsetsGeometry.lerp(geometry1x, geometry2x, textScaleFactor - 1)!;
+    }
+    return EdgeInsetsGeometry.lerp(geometry2x, geometry3x, textScaleFactor - 2)!;
+  }
+}
+
+/// The base [State] class for buttons whose style is defined by a [ButtonStyle] object.
+///
+/// See also:
+///
+///  * [ButtonStyleButton], the [StatefulWidget] subclass for which this class is the [State].
+///  * [TextButton], a simple button without a shadow.
+///  * [ElevatedButton], a filled button whose material elevates when pressed.
+///  * [OutlinedButton], similar to [TextButton], but with an outline.
+class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStateMixin {
+  AnimationController? _controller;
+  double? _elevation;
+  Color? _backgroundColor;
+  final Set<MaterialState> _states = <MaterialState>{};
+
+  bool get _hovered => _states.contains(MaterialState.hovered);
+  bool get _focused => _states.contains(MaterialState.focused);
+  bool get _pressed => _states.contains(MaterialState.pressed);
+  bool get _disabled => _states.contains(MaterialState.disabled);
+
+  void _updateState(MaterialState state, bool value) {
+    value ? _states.add(state) : _states.remove(state);
+  }
+
+  void _handleHighlightChanged(bool value) {
+    if (_pressed != value) {
+      setState(() {
+        _updateState(MaterialState.pressed, value);
+      });
+    }
+  }
+
+  void _handleHoveredChanged(bool value) {
+    if (_hovered != value) {
+      setState(() {
+        _updateState(MaterialState.hovered, value);
+      });
+    }
+  }
+
+  void _handleFocusedChanged(bool value) {
+    if (_focused != value) {
+      setState(() {
+        _updateState(MaterialState.focused, value);
+      });
+    }
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _updateState(MaterialState.disabled, !widget.enabled);
+  }
+
+  @override
+  void dispose() {
+    _controller?.dispose();
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(ButtonStyleButton oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    _updateState(MaterialState.disabled, !widget.enabled);
+    // If the button is disabled while a press gesture is currently ongoing,
+    // InkWell makes a call to handleHighlightChanged. This causes an exception
+    // because it calls setState in the middle of a build. To preempt this, we
+    // manually update pressed to false when this situation occurs.
+    if (_disabled && _pressed) {
+      _handleHighlightChanged(false);
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ButtonStyle? widgetStyle = widget.style;
+    final ButtonStyle? themeStyle = widget.themeStyleOf(context);
+    final ButtonStyle defaultStyle = widget.defaultStyleOf(context);
+    assert(defaultStyle != null);
+
+    T? effectiveValue<T>(T? Function(ButtonStyle? style) getProperty) {
+      final T? widgetValue  = getProperty(widgetStyle);
+      final T? themeValue   = getProperty(themeStyle);
+      final T? defaultValue = getProperty(defaultStyle);
+      return widgetValue ?? themeValue ?? defaultValue;
+    }
+
+    T? resolve<T>(MaterialStateProperty<T>? Function(ButtonStyle? style) getProperty) {
+      return effectiveValue(
+        (ButtonStyle? style) => getProperty(style)?.resolve(_states),
+      );
+    }
+
+    final double? resolvedElevation = resolve<double?>((ButtonStyle? style) => style?.elevation);
+    final TextStyle? resolvedTextStyle = resolve<TextStyle?>((ButtonStyle? style) => style?.textStyle);
+    Color? resolvedBackgroundColor = resolve<Color?>((ButtonStyle? style) => style?.backgroundColor);
+    final Color? resolvedForegroundColor = resolve<Color?>((ButtonStyle? style) => style?.foregroundColor);
+    final Color? resolvedShadowColor = resolve<Color?>((ButtonStyle? style) => style?.shadowColor);
+    final EdgeInsetsGeometry? resolvedPadding = resolve<EdgeInsetsGeometry?>((ButtonStyle? style) => style?.padding);
+    final Size? resolvedMinimumSize = resolve<Size?>((ButtonStyle? style) => style?.minimumSize);
+    final BorderSide? resolvedSide = resolve<BorderSide?>((ButtonStyle? style) => style?.side);
+    final OutlinedBorder? resolvedShape = resolve<OutlinedBorder?>((ButtonStyle? style) => style?.shape);
+
+    final MaterialStateMouseCursor resolvedMouseCursor = _MouseCursor(
+      (Set<MaterialState> states) => effectiveValue((ButtonStyle? style) => style?.mouseCursor?.resolve(states)),
+    );
+
+    final MaterialStateProperty<Color?> overlayColor = MaterialStateProperty.resolveWith<Color?>(
+      (Set<MaterialState> states) => effectiveValue((ButtonStyle? style) => style?.overlayColor?.resolve(states)),
+    );
+
+    final VisualDensity? resolvedVisualDensity = effectiveValue((ButtonStyle? style) => style?.visualDensity);
+    final MaterialTapTargetSize? resolvedTapTargetSize = effectiveValue((ButtonStyle? style) => style?.tapTargetSize);
+    final Duration? resolvedAnimationDuration = effectiveValue((ButtonStyle? style) => style?.animationDuration);
+    final bool? resolvedEnableFeedback = effectiveValue((ButtonStyle? style) => style?.enableFeedback);
+    final Offset densityAdjustment = resolvedVisualDensity!.baseSizeAdjustment;
+    final BoxConstraints effectiveConstraints = resolvedVisualDensity.effectiveConstraints(
+      BoxConstraints(
+        minWidth: resolvedMinimumSize!.width,
+        minHeight: resolvedMinimumSize.height,
+      ),
+    );
+    final EdgeInsetsGeometry padding = resolvedPadding!.add(
+      EdgeInsets.only(
+        left: densityAdjustment.dx,
+        top: densityAdjustment.dy,
+        right: densityAdjustment.dx,
+        bottom: densityAdjustment.dy,
+      ),
+    ).clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity);
+
+    // If an opaque button's background is becoming translucent while its
+    // elevation is changing, change the elevation first. Material implicitly
+    // animates its elevation but not its color. SKIA renders non-zero
+    // elevations as a shadow colored fill behind the Material's background.
+    if (resolvedAnimationDuration! > Duration.zero
+        && _elevation != null
+        && _backgroundColor != null
+        && _elevation != resolvedElevation
+        && _backgroundColor!.value != resolvedBackgroundColor!.value
+        && _backgroundColor!.opacity == 1
+        && resolvedBackgroundColor.opacity < 1
+        && resolvedElevation == 0) {
+      if (_controller?.duration != resolvedAnimationDuration) {
+        _controller?.dispose();
+        _controller = AnimationController(
+          duration: resolvedAnimationDuration,
+          vsync: this,
+        )
+        ..addStatusListener((AnimationStatus status) {
+          if (status == AnimationStatus.completed) {
+            setState(() { }); // Rebuild with the final background color.
+          }
+        });
+      }
+      resolvedBackgroundColor = _backgroundColor; // Defer changing the background color.
+      _controller!.value = 0;
+      _controller!.forward();
+    }
+    _elevation = resolvedElevation;
+    _backgroundColor = resolvedBackgroundColor;
+
+    final Widget result = ConstrainedBox(
+      constraints: effectiveConstraints,
+      child: Material(
+        elevation: resolvedElevation!,
+        textStyle: resolvedTextStyle?.copyWith(color: resolvedForegroundColor),
+        shape: resolvedShape!.copyWith(side: resolvedSide),
+        color: resolvedBackgroundColor,
+        shadowColor: resolvedShadowColor,
+        type: resolvedBackgroundColor == null ? MaterialType.transparency : MaterialType.button,
+        animationDuration: resolvedAnimationDuration,
+        clipBehavior: widget.clipBehavior,
+        child: InkWell(
+          onTap: widget.onPressed,
+          onLongPress: widget.onLongPress,
+          onHighlightChanged: _handleHighlightChanged,
+          onHover: _handleHoveredChanged,
+          mouseCursor: resolvedMouseCursor,
+          enableFeedback: resolvedEnableFeedback,
+          focusNode: widget.focusNode,
+          canRequestFocus: widget.enabled,
+          onFocusChange: _handleFocusedChanged,
+          autofocus: widget.autofocus,
+          splashFactory: InkRipple.splashFactory,
+          overlayColor: overlayColor,
+          highlightColor: Colors.transparent,
+          customBorder: resolvedShape,
+          child: IconTheme.merge(
+            data: IconThemeData(color: resolvedForegroundColor),
+            child: Padding(
+              padding: padding,
+              child: Center(
+                widthFactor: 1.0,
+                heightFactor: 1.0,
+                child: widget.child,
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final Size minSize;
+    switch (resolvedTapTargetSize!) {
+      case MaterialTapTargetSize.padded:
+        minSize = Size(
+          kMinInteractiveDimension + densityAdjustment.dx,
+          kMinInteractiveDimension + densityAdjustment.dy,
+        );
+        assert(minSize.width >= 0.0);
+        assert(minSize.height >= 0.0);
+        break;
+      case MaterialTapTargetSize.shrinkWrap:
+        minSize = Size.zero;
+        break;
+    }
+
+    return Semantics(
+      container: true,
+      button: true,
+      enabled: widget.enabled,
+      child: _InputPadding(
+        minSize: minSize,
+        child: result,
+      ),
+    );
+  }
+}
+
+class _MouseCursor extends MaterialStateMouseCursor {
+  const _MouseCursor(this.resolveCallback);
+
+  final MaterialPropertyResolver<MouseCursor?> resolveCallback;
+
+  @override
+  MouseCursor resolve(Set<MaterialState> states) => resolveCallback(states)!;
+
+  @override
+  String get debugDescription => 'ButtonStyleButton_MouseCursor';
+}
+
+/// A widget to pad the area around a [MaterialButton]'s inner [Material].
+///
+/// Redirect taps that occur in the padded area around the child to the center
+/// of the child. This increases the size of the button and the button's
+/// "tap target", but not its material or its ink splashes.
+class _InputPadding extends SingleChildRenderObjectWidget {
+  const _InputPadding({
+    Key? key,
+    Widget? child,
+    required this.minSize,
+  }) : super(key: key, child: child);
+
+  final Size minSize;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _RenderInputPadding(minSize);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, covariant _RenderInputPadding renderObject) {
+    renderObject.minSize = minSize;
+  }
+}
+
+class _RenderInputPadding extends RenderShiftedBox {
+  _RenderInputPadding(this._minSize, [RenderBox? child]) : super(child);
+
+  Size get minSize => _minSize;
+  Size _minSize;
+  set minSize(Size value) {
+    if (_minSize == value)
+      return;
+    _minSize = value;
+    markNeedsLayout();
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    if (child != null)
+      return math.max(child!.getMinIntrinsicWidth(height), minSize.width);
+    return 0.0;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    if (child != null)
+      return math.max(child!.getMinIntrinsicHeight(width), minSize.height);
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    if (child != null)
+      return math.max(child!.getMaxIntrinsicWidth(height), minSize.width);
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    if (child != null)
+      return math.max(child!.getMaxIntrinsicHeight(width), minSize.height);
+    return 0.0;
+  }
+
+  Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
+    if (child != null) {
+      final Size childSize = layoutChild(child!, constraints);
+      final double height = math.max(childSize.width, minSize.width);
+      final double width = math.max(childSize.height, minSize.height);
+      return constraints.constrain(Size(height, width));
+    }
+    return Size.zero;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _computeSize(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.dryLayoutChild,
+    );
+  }
+
+  @override
+  void performLayout() {
+    size = _computeSize(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.layoutChild,
+    );
+    if (child != null) {
+      final BoxParentData childParentData = child!.parentData! as BoxParentData;
+      childParentData.offset = Alignment.center.alongOffset(size - child!.size as Offset);
+    }
+  }
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    if (super.hitTest(result, position: position)) {
+      return true;
+    }
+    final Offset center = child!.size.center(Offset.zero);
+    return result.addWithRawTransform(
+      transform: MatrixUtils.forceToPoint(center),
+      position: center,
+      hitTest: (BoxHitTestResult result, Offset? position) {
+        assert(position == center);
+        return child!.hitTest(result, position: center);
+      },
+    );
+  }
+}
diff --git a/lib/src/material/button_theme.dart b/lib/src/material/button_theme.dart
new file mode 100644
index 0000000..abd260f
--- /dev/null
+++ b/lib/src/material/button_theme.dart
@@ -0,0 +1,980 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'color_scheme.dart';
+import 'colors.dart';
+import 'constants.dart';
+import 'flat_button.dart';
+import 'material_button.dart';
+import 'material_state.dart';
+import 'outline_button.dart';
+import 'raised_button.dart';
+import 'theme.dart';
+import 'theme_data.dart' show MaterialTapTargetSize;
+
+/// Used with [ButtonTheme] and [ButtonThemeData] to define a button's base
+/// colors, and the defaults for the button's minimum size, internal padding,
+/// and shape.
+///
+/// See also:
+///
+///  * [RaisedButton], [FlatButton], [OutlineButton], which are configured
+///    based on the ambient [ButtonTheme].
+enum ButtonTextTheme {
+  /// Button text is black or white depending on [ThemeData.brightness].
+  normal,
+
+  /// Button text is [ThemeData.accentColor].
+  accent,
+
+  /// Button text is based on [ThemeData.primaryColor].
+  primary,
+}
+
+/// Used with [ButtonTheme] and [ButtonThemeData] to define how the button bar
+/// should size itself with either constraints or internal padding.
+enum ButtonBarLayoutBehavior {
+  /// Button bars will be constrained to a minimum height of 52.
+  ///
+  /// This setting is require to create button bars which conform to the
+  /// material specification.
+  constrained,
+
+  /// Button bars will calculate their padding from the button theme padding.
+  padded,
+}
+
+/// Used with [ButtonThemeData] to configure the color and geometry of buttons.
+///
+/// ### This class is obsolete.
+///
+/// Please use one or more of the new buttons and their themes
+/// instead: [TextButton] and [TextButtonTheme], [ElevatedButton] and
+/// [ElevatedButtonTheme], [OutlinedButton] and
+/// [OutlinedButtonTheme]. The original classes will be deprecated
+/// soon, please migrate code that uses them.  There's a detailed
+/// migration guide for the new button and button theme classes in
+/// [flutter.dev/go/material-button-migration-guide](https://flutter.dev/go/material-button-migration-guide).
+///
+/// A button theme can be specified as part of the overall Material theme
+/// using [ThemeData.buttonTheme]. The Material theme's button theme data
+/// can be overridden with [ButtonTheme].
+///
+/// The actual appearance of buttons depends on the button theme, the
+/// button's enabled state, its elevation (if any), and the overall [Theme].
+///
+/// See also:
+///
+///  * [FlatButton] [RaisedButton], and [OutlineButton], which are styled
+///    based on the ambient button theme.
+///  * [RawMaterialButton], which can be used to configure a button that doesn't
+///    depend on any inherited themes.
+class ButtonTheme extends InheritedTheme {
+  /// Creates a button theme.
+  ///
+  /// The [textTheme], [minWidth], [height], and [colorScheme] arguments
+  /// must not be null.
+  ButtonTheme({
+    Key? key,
+    ButtonTextTheme textTheme = ButtonTextTheme.normal,
+    ButtonBarLayoutBehavior layoutBehavior = ButtonBarLayoutBehavior.padded,
+    double minWidth = 88.0,
+    double height = 36.0,
+    EdgeInsetsGeometry? padding,
+    ShapeBorder? shape,
+    bool alignedDropdown = false,
+    Color? buttonColor,
+    Color? disabledColor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    ColorScheme? colorScheme,
+    MaterialTapTargetSize? materialTapTargetSize,
+    required Widget child,
+  }) : assert(textTheme != null),
+       assert(minWidth != null && minWidth >= 0.0),
+       assert(height != null && height >= 0.0),
+       assert(alignedDropdown != null),
+       assert(layoutBehavior != null),
+       data = ButtonThemeData(
+         textTheme: textTheme,
+         minWidth: minWidth,
+         height: height,
+         padding: padding,
+         shape: shape,
+         alignedDropdown: alignedDropdown,
+         layoutBehavior: layoutBehavior,
+         buttonColor: buttonColor,
+         disabledColor: disabledColor,
+         focusColor: focusColor,
+         hoverColor: hoverColor,
+         highlightColor: highlightColor,
+         splashColor: splashColor,
+         colorScheme: colorScheme,
+         materialTapTargetSize: materialTapTargetSize,
+       ),
+       super(key: key, child: child);
+
+  /// Creates a button theme from [data].
+  ///
+  /// The [data] argument must not be null.
+  const ButtonTheme.fromButtonThemeData({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null),
+       super(key: key, child: child);
+
+  /// Creates a button theme that is appropriate for button bars, as used in
+  /// dialog footers and in the headers of data tables.
+  ///
+  /// Deprecated. Please use [ButtonBarTheme] instead which offers more
+  /// flexibility to configure [ButtonBar] widgets.
+  ///
+  /// To migrate instances of code that were just wrapping a [ButtonBar]:
+  ///
+  /// ```dart
+  /// ButtonTheme.bar(
+  ///   child: ButtonBar(...)
+  /// );
+  /// ```
+  ///
+  /// you can just remove the `ButtonTheme.bar` as the defaults are now handled
+  /// by [ButtonBar] directly.
+  ///
+  /// If you have more complicated usages of `ButtonTheme.bar` like:
+  ///
+  /// ```dart
+  /// ButtonTheme.bar(
+  ///   padding: EdgeInsets.symmetric(horizontal: 10.0),
+  ///   textTheme: ButtonTextTheme.accent,
+  ///   child: ButtonBar(...),
+  /// );
+  /// ```
+  ///
+  /// you can remove the `ButtonTheme.bar` and move the parameters to the
+  /// [ButtonBar] instance directly:
+  ///
+  /// ```dart
+  /// ButtonBar(
+  ///   padding: EdgeInsets.symmetric(horizontal: 10.0),
+  ///   textTheme: ButtonTextTheme.accent,
+  ///   ...
+  /// );
+  /// ```
+  ///
+  /// You can also replace the defaults for all [ButtonBar] widgets by updating
+  /// [ThemeData.buttonBarTheme] for your app.
+  @Deprecated(
+    'Use ButtonBarTheme instead. '
+    'This feature was deprecated after v1.9.1.'
+  )
+  ButtonTheme.bar({
+    Key? key,
+    ButtonTextTheme textTheme = ButtonTextTheme.accent,
+    double minWidth = 64.0,
+    double height = 36.0,
+    EdgeInsetsGeometry padding = const EdgeInsets.symmetric(horizontal: 8.0),
+    ShapeBorder? shape,
+    bool alignedDropdown = false,
+    Color? buttonColor,
+    Color? disabledColor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    ColorScheme? colorScheme,
+    required Widget child,
+    ButtonBarLayoutBehavior layoutBehavior = ButtonBarLayoutBehavior.padded,
+  }) : assert(textTheme != null),
+       assert(minWidth != null && minWidth >= 0.0),
+       assert(height != null && height >= 0.0),
+       assert(alignedDropdown != null),
+       data = ButtonThemeData(
+         textTheme: textTheme,
+         minWidth: minWidth,
+         height: height,
+         padding: padding,
+         shape: shape,
+         alignedDropdown: alignedDropdown,
+         layoutBehavior: layoutBehavior,
+         buttonColor: buttonColor,
+         disabledColor: disabledColor,
+         focusColor: focusColor,
+         hoverColor: hoverColor,
+         highlightColor: highlightColor,
+         splashColor: splashColor,
+         colorScheme: colorScheme,
+       ),
+       super(key: key, child: child);
+
+  /// Specifies the color and geometry of buttons.
+  final ButtonThemeData data;
+
+  /// The closest instance of this class that encloses the given context.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// ButtonThemeData theme = ButtonTheme.of(context);
+  /// ```
+  static ButtonThemeData of(BuildContext context) {
+    final ButtonTheme? inheritedButtonTheme = context.dependOnInheritedWidgetOfExactType<ButtonTheme>();
+    ButtonThemeData? buttonTheme = inheritedButtonTheme?.data;
+    if (buttonTheme?.colorScheme == null) { // if buttonTheme or buttonTheme.colorScheme is null
+      final ThemeData theme = Theme.of(context);
+      buttonTheme ??= theme.buttonTheme;
+      if (buttonTheme.colorScheme == null) {
+        buttonTheme = buttonTheme.copyWith(
+          colorScheme: theme.buttonTheme.colorScheme ?? theme.colorScheme,
+        );
+        assert(buttonTheme.colorScheme != null);
+      }
+    }
+    return buttonTheme!;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return ButtonTheme.fromButtonThemeData(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(ButtonTheme oldWidget) => data != oldWidget.data;
+}
+
+/// Used with [ButtonTheme] to configure the color and geometry of buttons.
+///
+/// ### This class is obsolete.
+///
+/// Please use one or more of the new buttons and their themes instead:
+///
+///  * [TextButton], [TextButtonTheme], [TextButtonThemeData],
+///  * [ElevatedButton], [ElevatedButtonTheme], [ElevatedButtonThemeData],
+///  * [OutlinedButton], [OutlinedButtonTheme], [OutlinedButtonThemeData]
+///
+/// FlatButton, RaisedButton, and OutlineButton have been replaced by
+/// TextButton, ElevatedButton, and OutlinedButton respectively.
+/// ButtonTheme has been replaced by TextButtonTheme,
+/// ElevatedButtonTheme, and OutlinedButtonTheme. The original classes
+/// will be deprecated soon, please migrate code that uses them.
+/// There's a detailed migration guide for the new button and button
+/// theme classes in
+/// [flutter.dev/go/material-button-migration-guide](https://flutter.dev/go/material-button-migration-guide).
+///
+/// A button theme can be specified as part of the overall Material theme
+/// using [ThemeData.buttonTheme]. The Material theme's button theme data
+/// can be overridden with [ButtonTheme].
+@immutable
+class ButtonThemeData with Diagnosticable {
+  /// Create a button theme object that can be used with [ButtonTheme]
+  /// or [ThemeData].
+  ///
+  /// The [textTheme], [minWidth], [height], [alignedDropdown], and
+  /// [layoutBehavior] parameters must not be null. The [minWidth] and
+  /// [height] parameters must greater than or equal to zero.
+  ///
+  /// The ButtonTheme's methods that have a [MaterialButton] parameter and
+  /// have a name with a `get` prefix are used by [RaisedButton],
+  /// [OutlineButton], and [FlatButton] to configure a [RawMaterialButton].
+  const ButtonThemeData({
+    this.textTheme = ButtonTextTheme.normal,
+    this.minWidth = 88.0,
+    this.height = 36.0,
+    EdgeInsetsGeometry? padding,
+    ShapeBorder? shape,
+    this.layoutBehavior = ButtonBarLayoutBehavior.padded,
+    this.alignedDropdown = false,
+    Color? buttonColor,
+    Color? disabledColor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    this.colorScheme,
+    MaterialTapTargetSize? materialTapTargetSize,
+  }) : assert(textTheme != null),
+       assert(minWidth != null && minWidth >= 0.0),
+       assert(height != null && height >= 0.0),
+       assert(alignedDropdown != null),
+       assert(layoutBehavior != null),
+       _buttonColor = buttonColor,
+       _disabledColor = disabledColor,
+       _focusColor = focusColor,
+       _hoverColor = hoverColor,
+       _highlightColor = highlightColor,
+       _splashColor = splashColor,
+       _padding = padding,
+       _shape = shape,
+       _materialTapTargetSize = materialTapTargetSize;
+
+  /// The minimum width for buttons.
+  ///
+  /// The actual horizontal space allocated for a button's child is
+  /// at least this value less the theme's horizontal [padding].
+  ///
+  /// Defaults to 88.0 logical pixels.
+  final double minWidth;
+
+  /// The minimum height for buttons.
+  ///
+  /// Defaults to 36.0 logical pixels.
+  final double height;
+
+  /// Defines a button's base colors, and the defaults for the button's minimum
+  /// size, internal padding, and shape.
+  ///
+  /// Despite the name, this property is not a [TextTheme], its value is not a
+  /// collection of [TextStyle]s.
+  final ButtonTextTheme textTheme;
+
+  /// Defines whether a [ButtonBar] should size itself with a minimum size
+  /// constraint or with padding.
+  ///
+  /// Defaults to [ButtonBarLayoutBehavior.padded].
+  final ButtonBarLayoutBehavior layoutBehavior;
+
+  /// Simply a convenience that returns [minWidth] and [height] as a
+  /// [BoxConstraints] object:
+  ///
+  /// ```dart
+  /// return BoxConstraints(
+  ///   minWidth: minWidth,
+  ///   minHeight: height,
+  /// );
+  /// ```
+  BoxConstraints get constraints {
+    return BoxConstraints(
+      minWidth: minWidth,
+      minHeight: height,
+    );
+  }
+
+  /// Padding for a button's child (typically the button's label).
+  ///
+  /// Defaults to 24.0 on the left and right if [textTheme] is
+  /// [ButtonTextTheme.primary], 16.0 on the left and right otherwise.
+  ///
+  /// See also:
+  ///
+  ///  * [getPadding], which is used by [RaisedButton], [OutlineButton]
+  ///    and [FlatButton].
+  EdgeInsetsGeometry get padding {
+    if (_padding != null)
+      return _padding!;
+    switch (textTheme) {
+      case ButtonTextTheme.normal:
+      case ButtonTextTheme.accent:
+        return const EdgeInsets.symmetric(horizontal: 16.0);
+      case ButtonTextTheme.primary:
+        return const EdgeInsets.symmetric(horizontal: 24.0);
+    }
+  }
+  final EdgeInsetsGeometry? _padding;
+
+  /// The shape of a button's material.
+  ///
+  /// The button's highlight and splash are clipped to this shape. If the
+  /// button has an elevation, then its drop shadow is defined by this
+  /// shape as well.
+  ///
+  /// Defaults to a rounded rectangle with circular corner radii of 4.0 if
+  /// [textTheme] is [ButtonTextTheme.primary], a rounded rectangle with
+  /// circular corner radii of 2.0 otherwise.
+  ///
+  /// See also:
+  ///
+  ///  * [getShape], which is used by [RaisedButton], [OutlineButton]
+  ///    and [FlatButton].
+  ShapeBorder get shape {
+    if (_shape != null)
+      return _shape!;
+    switch (textTheme) {
+      case ButtonTextTheme.normal:
+      case ButtonTextTheme.accent:
+        return const RoundedRectangleBorder(
+          borderRadius: BorderRadius.all(Radius.circular(2.0)),
+        );
+      case ButtonTextTheme.primary:
+        return const RoundedRectangleBorder(
+          borderRadius: BorderRadius.all(Radius.circular(4.0)),
+        );
+    }
+  }
+  final ShapeBorder? _shape;
+
+  /// If true, then a [DropdownButton] menu's width will match the button's
+  /// width.
+  ///
+  /// If false (the default), then the dropdown's menu will be wider than
+  /// its button. In either case the dropdown button will line up the leading
+  /// edge of the menu's value with the leading edge of the values
+  /// displayed by the menu items.
+  ///
+  /// This property only affects [DropdownButton] and its menu.
+  final bool alignedDropdown;
+
+  /// The background fill color for [RaisedButton]s.
+  ///
+  /// This property is null by default.
+  ///
+  /// If the button is in the focused, hovering, or highlighted state, then the
+  /// [focusColor], [hoverColor], or [highlightColor] will take precedence over
+  /// the [buttonColor].
+  ///
+  /// See also:
+  ///
+  ///  * [getFillColor], which is used by [RaisedButton] to compute its
+  ///    background fill color.
+  final Color? _buttonColor;
+
+  /// The background fill color for disabled [RaisedButton]s.
+  ///
+  /// This property is null by default.
+  ///
+  /// See also:
+  ///
+  ///  * [getDisabledFillColor], which is used by [RaisedButton] to compute its
+  ///    background fill color.
+  final Color? _disabledColor;
+
+  /// The fill color of the button when it has the input focus.
+  ///
+  /// This property is null by default.
+  ///
+  /// If the button is in the hovering or highlighted state, then the [hoverColor]
+  /// or [highlightColor] will take precedence over the [focusColor].
+  ///
+  /// See also:
+  ///
+  ///  * [getFocusColor], which is used by [RaisedButton], [OutlineButton]
+  ///    and [FlatButton].
+  final Color? _focusColor;
+
+  /// The fill color of the button when a pointer is hovering over it.
+  ///
+  /// This property is null by default.
+  ///
+  /// If the button is in the highlighted state, then the [highlightColor] will
+  /// take precedence over the [hoverColor].
+  ///
+  /// See also:
+  ///
+  ///  * [getHoverColor], which is used by [RaisedButton], [OutlineButton]
+  ///    and [FlatButton].
+  final Color? _hoverColor;
+
+  /// The color of the overlay that appears when a button is pressed.
+  ///
+  /// This property is null by default.
+  ///
+  /// See also:
+  ///
+  ///  * [getHighlightColor], which is used by [RaisedButton], [OutlineButton]
+  ///    and [FlatButton].
+  final Color? _highlightColor;
+
+  /// The color of the ink "splash" overlay that appears when a button is tapped.
+  ///
+  /// This property is null by default.
+  ///
+  /// See also:
+  ///
+  ///  * [getSplashColor], which is used by [RaisedButton], [OutlineButton]
+  ///    and [FlatButton].
+  final Color? _splashColor;
+
+  /// A set of thirteen colors that can be used to derive the button theme's
+  /// colors.
+  ///
+  /// This property was added much later than the theme's set of highly
+  /// specific colors, like [ThemeData.buttonColor], [ThemeData.highlightColor],
+  /// [ThemeData.splashColor] etc.
+  ///
+  /// The colors for new button classes can be defined exclusively in terms
+  /// of [colorScheme]. When it's possible, the existing buttons will
+  /// (continue to) gradually migrate to it.
+  final ColorScheme? colorScheme;
+
+  // The minimum size of a button's tap target.
+  //
+  // This property is null by default.
+  //
+  // See also:
+  //
+  //  * [getMaterialTargetTapSize], which is used by [RaisedButton],
+  //    [OutlineButton] and [FlatButton].
+  final MaterialTapTargetSize? _materialTapTargetSize;
+
+  /// The [button]'s overall brightness.
+  ///
+  /// Returns the button's [MaterialButton.colorBrightness] if it is non-null,
+  /// otherwise the color scheme's [ColorScheme.brightness] is returned.
+  Brightness getBrightness(MaterialButton button) {
+    return button.colorBrightness ?? colorScheme!.brightness;
+  }
+
+  /// Defines the [button]'s base colors, and the defaults for the button's
+  /// minimum size, internal padding, and shape.
+  ///
+  /// Despite the name, this property is not the [TextTheme] whose
+  /// [TextTheme.button] is used as the button text's [TextStyle].
+  ButtonTextTheme getTextTheme(MaterialButton button) {
+    return button.textTheme ?? textTheme;
+  }
+
+  /// The foreground color of the [button]'s text and icon when
+  /// [MaterialButton.onPressed] is null (when MaterialButton.enabled is false).
+  ///
+  /// Returns the button's [MaterialButton.disabledColor] if it is non-null.
+  /// Otherwise the color scheme's [ColorScheme.onSurface] color is returned
+  /// with its opacity set to 0.38.
+  ///
+  /// If [MaterialButton.textColor] is a [MaterialStateProperty<Color>], it will be
+  /// used as the `disabledTextColor`. It will be resolved in the [MaterialState.disabled] state.
+  Color getDisabledTextColor(MaterialButton button) {
+    if (button.textColor is MaterialStateProperty<Color?>)
+      return button.textColor!;
+    if (button.disabledTextColor != null)
+      return button.disabledTextColor!;
+    return colorScheme!.onSurface.withOpacity(0.38);
+  }
+
+  /// The [button]'s background color when [MaterialButton.onPressed] is null
+  /// (when [MaterialButton.enabled] is false).
+  ///
+  /// Returns the button's [MaterialButton.disabledColor] if it is non-null.
+  ///
+  /// Otherwise the value of the `disabledColor` constructor parameter
+  /// is returned, if it is non-null.
+  ///
+  /// Otherwise the color scheme's [ColorScheme.onSurface] color is returned
+  /// with its opacity set to 0.38.
+  Color getDisabledFillColor(MaterialButton button) {
+    if (button.disabledColor != null)
+      return button.disabledColor!;
+    if (_disabledColor != null)
+      return _disabledColor!;
+    return colorScheme!.onSurface.withOpacity(0.38);
+  }
+
+  /// The button's background fill color or null for buttons that don't have
+  /// a background color.
+  ///
+  /// Returns [MaterialButton.color] if it is non-null and the button
+  /// is enabled.
+  ///
+  /// Otherwise, returns [MaterialButton.disabledColor] if it is non-null and
+  /// the button is disabled.
+  ///
+  /// Otherwise, if button is a [FlatButton] or an [OutlineButton] then null is
+  /// returned.
+  ///
+  /// Otherwise, if button is a [RaisedButton], returns the `buttonColor`
+  /// constructor parameter if it was non-null and the button is enabled.
+  ///
+  /// Otherwise the fill color depends on the value of [getTextTheme].
+  ///
+  ///  * [ButtonTextTheme.normal] or [ButtonTextTheme.accent], the
+  ///    color scheme's [ColorScheme.primary] color if the [button] is enabled
+  ///    the value of [getDisabledFillColor] otherwise.
+  ///  * [ButtonTextTheme.primary], if the [button] is enabled then the value
+  ///    of the `buttonColor` constructor parameter if it is non-null,
+  ///    otherwise the color scheme's ColorScheme.primary color. If the button
+  ///    is not enabled then the colorScheme's [ColorScheme.onSurface] color
+  ///    with opacity 0.12.
+  Color? getFillColor(MaterialButton button) {
+    final Color? fillColor = button.enabled ? button.color : button.disabledColor;
+    if (fillColor != null)
+      return fillColor;
+
+    if (button is FlatButton || button is OutlineButton || button.runtimeType == MaterialButton)
+      return null;
+
+    if (button.enabled && button is RaisedButton && _buttonColor != null)
+      return _buttonColor;
+
+    switch (getTextTheme(button)) {
+      case ButtonTextTheme.normal:
+      case ButtonTextTheme.accent:
+        return button.enabled ? colorScheme!.primary : getDisabledFillColor(button);
+      case ButtonTextTheme.primary:
+        return button.enabled
+          ? _buttonColor ?? colorScheme!.primary
+          : colorScheme!.onSurface.withOpacity(0.12);
+    }
+  }
+
+  /// The foreground color of the [button]'s text and icon.
+  ///
+  /// If [button] is not [MaterialButton.enabled], the value of
+  /// [getDisabledTextColor] is returned. If the button is enabled and
+  /// [MaterialButton.textColor] is non-null, then [MaterialButton.textColor]
+  /// is returned.
+  ///
+  /// Otherwise the text color depends on the value of [getTextTheme]
+  /// and [getBrightness].
+  ///
+  ///  * [ButtonTextTheme.normal]: [Colors.white] is used if [getBrightness]
+  ///    resolves to [Brightness.dark]. [Colors.black87] is used if
+  ///    [getBrightness] resolves to [Brightness.light].
+  ///  * [ButtonTextTheme.accent]: [ColorScheme.secondary] of [colorScheme].
+  ///  * [ButtonTextTheme.primary]: If [getFillColor] is dark then [Colors.white],
+  ///    otherwise if [button] is a [FlatButton] or an [OutlineButton] then
+  ///    [ColorScheme.primary] of [colorScheme], otherwise [Colors.black].
+  Color getTextColor(MaterialButton button) {
+    if (!button.enabled)
+      return getDisabledTextColor(button);
+
+    if (button.textColor != null)
+      return button.textColor!;
+
+    switch (getTextTheme(button)) {
+      case ButtonTextTheme.normal:
+        return getBrightness(button) == Brightness.dark ? Colors.white : Colors.black87;
+
+      case ButtonTextTheme.accent:
+        return colorScheme!.secondary;
+
+      case ButtonTextTheme.primary:
+        final Color? fillColor = getFillColor(button);
+        final bool fillIsDark = fillColor != null
+          ? ThemeData.estimateBrightnessForColor(fillColor) == Brightness.dark
+          : getBrightness(button) == Brightness.dark;
+        if (fillIsDark)
+          return Colors.white;
+        if (button is FlatButton || button is OutlineButton)
+          return colorScheme!.primary;
+        return Colors.black;
+    }
+  }
+
+  /// The color of the ink "splash" overlay that appears when the (enabled)
+  /// [button] is tapped.
+  ///
+  /// Returns the button's [MaterialButton.splashColor] if it is non-null.
+  ///
+  /// Otherwise, returns the value of the `splashColor` constructor parameter
+  /// it is non-null and [button] is a [RaisedButton] or an [OutlineButton].
+  ///
+  /// Otherwise, returns the value of the `splashColor` constructor parameter
+  /// if it is non-null and [button] is a [FlatButton] and
+  /// [getTextTheme] is not [ButtonTextTheme.primary]
+  ///
+  /// Otherwise, returns [getTextColor] with an opacity of 0.12.
+  Color getSplashColor(MaterialButton button) {
+    if (button.splashColor != null)
+      return button.splashColor!;
+
+    if (_splashColor != null && (button is RaisedButton || button is OutlineButton))
+      return _splashColor!;
+
+    if (_splashColor != null && button is FlatButton) {
+      switch (getTextTheme(button)) {
+        case ButtonTextTheme.normal:
+        case ButtonTextTheme.accent:
+          return _splashColor!;
+        case ButtonTextTheme.primary:
+          break;
+      }
+    }
+
+    return getTextColor(button).withOpacity(0.12);
+  }
+
+  /// The fill color of the button when it has input focus.
+  ///
+  /// Returns the button's [MaterialButton.focusColor] if it is non-null.
+  /// Otherwise the focus color depends on [getTextTheme]:
+  ///
+  ///  * [ButtonTextTheme.normal], [ButtonTextTheme.accent]: returns the
+  ///    value of the `focusColor` constructor parameter if it is non-null,
+  ///    otherwise the value of [getTextColor] with opacity 0.12.
+  ///  * [ButtonTextTheme.primary], returns [Colors.transparent].
+  Color getFocusColor(MaterialButton button) {
+    return button.focusColor ?? _focusColor ?? getTextColor(button).withOpacity(0.12);
+  }
+
+  /// The fill color of the button when it has input focus.
+  ///
+  /// Returns the button's [MaterialButton.focusColor] if it is non-null.
+  /// Otherwise the focus color depends on [getTextTheme]:
+  ///
+  ///  * [ButtonTextTheme.normal], [ButtonTextTheme.accent],
+  ///    [ButtonTextTheme.primary]: returns the value of the `focusColor`
+  ///    constructor parameter if it is non-null, otherwise the value of
+  ///    [getTextColor] with opacity 0.04.
+  Color getHoverColor(MaterialButton button) {
+    return button.hoverColor ?? _hoverColor ?? getTextColor(button).withOpacity(0.04);
+  }
+
+  /// The color of the overlay that appears when the [button] is pressed.
+  ///
+  /// Returns the button's [MaterialButton.highlightColor] if it is non-null.
+  /// Otherwise the highlight color depends on [getTextTheme]:
+  ///
+  ///  * [ButtonTextTheme.normal], [ButtonTextTheme.accent]: returns the
+  ///    value of the `highlightColor` constructor parameter if it is non-null,
+  ///    otherwise the value of [getTextColor] with opacity 0.16.
+  ///  * [ButtonTextTheme.primary], returns [Colors.transparent].
+  Color getHighlightColor(MaterialButton button) {
+    if (button.highlightColor != null)
+      return button.highlightColor!;
+
+    switch (getTextTheme(button)) {
+      case ButtonTextTheme.normal:
+      case ButtonTextTheme.accent:
+        return _highlightColor ?? getTextColor(button).withOpacity(0.16);
+      case ButtonTextTheme.primary:
+        return Colors.transparent;
+    }
+  }
+
+  /// The [button]'s elevation when it is enabled and has not been pressed.
+  ///
+  /// Returns the button's [MaterialButton.elevation] if it is non-null.
+  ///
+  /// If button is a [FlatButton] then elevation is 0.0, otherwise it is 2.0.
+  double getElevation(MaterialButton button) {
+    if (button.elevation != null)
+      return button.elevation!;
+    if (button is FlatButton)
+      return 0.0;
+    return 2.0;
+  }
+
+  /// The [button]'s elevation when it is enabled and has focus.
+  ///
+  /// Returns the button's [MaterialButton.focusElevation] if it is non-null.
+  ///
+  /// If button is a [FlatButton] or an [OutlineButton] then the focus
+  /// elevation is 0.0, otherwise the highlight elevation is 4.0.
+  double getFocusElevation(MaterialButton button) {
+    if (button.focusElevation != null)
+      return button.focusElevation!;
+    if (button is FlatButton)
+      return 0.0;
+    if (button is OutlineButton)
+      return 0.0;
+    return 4.0;
+  }
+
+  /// The [button]'s elevation when it is enabled and has focus.
+  ///
+  /// Returns the button's [MaterialButton.hoverElevation] if it is non-null.
+  ///
+  /// If button is a [FlatButton] or an [OutlineButton] then the hover
+  /// elevation is 0.0, otherwise the highlight elevation is 4.0.
+  double getHoverElevation(MaterialButton button) {
+    if (button.hoverElevation != null)
+      return button.hoverElevation!;
+    if (button is FlatButton)
+      return 0.0;
+    if (button is OutlineButton)
+      return 0.0;
+    return 4.0;
+  }
+
+  /// The [button]'s elevation when it is enabled and has been pressed.
+  ///
+  /// Returns the button's [MaterialButton.highlightElevation] if it is non-null.
+  ///
+  /// If button is a [FlatButton] or an [OutlineButton] then the highlight
+  /// elevation is 0.0, otherwise the highlight elevation is 8.0.
+  double getHighlightElevation(MaterialButton button) {
+    if (button.highlightElevation != null)
+      return button.highlightElevation!;
+    if (button is FlatButton)
+      return 0.0;
+    if (button is OutlineButton)
+      return 0.0;
+    return 8.0;
+  }
+
+  /// The [button]'s elevation when [MaterialButton.onPressed] is null (when
+  /// MaterialButton.enabled is false).
+  ///
+  /// Returns the button's [MaterialButton.elevation] if it is non-null.
+  ///
+  /// Otherwise the disabled elevation is 0.0.
+  double getDisabledElevation(MaterialButton button) {
+    if (button.disabledElevation != null)
+      return button.disabledElevation!;
+    return 0.0;
+  }
+
+  /// Padding for the [button]'s child (typically the button's label).
+  ///
+  /// Returns the button's [MaterialButton.padding] if it is non-null.
+  ///
+  /// If this is a button constructed with [RaisedButton.icon] or
+  /// [FlatButton.icon] or [OutlineButton.icon] then the padding is:
+  /// `EdgeInsetsDirectional.only(start: 12.0, end: 16.0)`.
+  ///
+  /// Otherwise, returns [padding] if it is non-null.
+  ///
+  /// Otherwise, returns horizontal padding of 24.0 on the left and right if
+  /// [getTextTheme] is [ButtonTextTheme.primary], 16.0 on the left and right
+  /// otherwise.
+  EdgeInsetsGeometry getPadding(MaterialButton button) {
+    if (button.padding != null)
+      return button.padding!;
+
+    if (button is MaterialButtonWithIconMixin)
+      return const EdgeInsetsDirectional.only(start: 12.0, end: 16.0);
+
+    if (_padding != null)
+      return _padding!;
+
+    switch (getTextTheme(button)) {
+      case ButtonTextTheme.normal:
+      case ButtonTextTheme.accent:
+        return const EdgeInsets.symmetric(horizontal: 16.0);
+      case ButtonTextTheme.primary:
+        return const EdgeInsets.symmetric(horizontal: 24.0);
+    }
+  }
+
+  /// The shape of the [button]'s [Material].
+  ///
+  /// Returns the button's [MaterialButton.shape] if it is non-null, otherwise
+  /// [shape] is returned.
+  ShapeBorder getShape(MaterialButton button) {
+    return button.shape ?? shape;
+  }
+
+  /// The duration of the [button]'s highlight animation.
+  ///
+  /// Returns the button's [MaterialButton.animationDuration] it if is non-null,
+  /// otherwise 200ms.
+  Duration getAnimationDuration(MaterialButton button) {
+    return button.animationDuration ?? kThemeChangeDuration;
+  }
+
+  /// The [BoxConstraints] that the define the [button]'s size.
+  ///
+  /// By default this method just returns [constraints]. Subclasses
+  /// could override this method to return a value that was,
+  /// for example, based on the button's type.
+  BoxConstraints getConstraints(MaterialButton button) => constraints;
+
+  /// The minimum size of the [button]'s tap target.
+  ///
+  /// Returns the button's [MaterialButton.materialTapTargetSize] if it is non-null.
+  ///
+  /// Otherwise the value of the `materialTapTargetSize` constructor
+  /// parameter is returned if that's non-null.
+  ///
+  /// Otherwise [MaterialTapTargetSize.padded] is returned.
+  MaterialTapTargetSize getMaterialTapTargetSize(MaterialButton button) {
+    return button.materialTapTargetSize ?? _materialTapTargetSize ?? MaterialTapTargetSize.padded;
+  }
+
+  /// Creates a copy of this button theme data object with the matching fields
+  /// replaced with the non-null parameter values.
+  ButtonThemeData copyWith({
+    ButtonTextTheme? textTheme,
+    ButtonBarLayoutBehavior? layoutBehavior,
+    double? minWidth,
+    double? height,
+    EdgeInsetsGeometry? padding,
+    ShapeBorder? shape,
+    bool? alignedDropdown,
+    Color? buttonColor,
+    Color? disabledColor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    ColorScheme? colorScheme,
+    MaterialTapTargetSize? materialTapTargetSize,
+  }) {
+    return ButtonThemeData(
+      textTheme: textTheme ?? this.textTheme,
+      layoutBehavior: layoutBehavior ?? this.layoutBehavior,
+      minWidth: minWidth ?? this.minWidth,
+      height: height ?? this.height,
+      padding: padding ?? this.padding,
+      shape: shape ?? this.shape,
+      alignedDropdown: alignedDropdown ?? this.alignedDropdown,
+      buttonColor: buttonColor ?? _buttonColor,
+      disabledColor: disabledColor ?? _disabledColor,
+      focusColor: focusColor ?? _focusColor,
+      hoverColor: hoverColor ?? _hoverColor,
+      highlightColor: highlightColor ?? _highlightColor,
+      splashColor: splashColor ?? _splashColor,
+      colorScheme: colorScheme ?? this.colorScheme,
+      materialTapTargetSize: materialTapTargetSize ?? _materialTapTargetSize,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ButtonThemeData
+        && other.textTheme == textTheme
+        && other.minWidth == minWidth
+        && other.height == height
+        && other.padding == padding
+        && other.shape == shape
+        && other.alignedDropdown == alignedDropdown
+        && other._buttonColor == _buttonColor
+        && other._disabledColor == _disabledColor
+        && other._focusColor == _focusColor
+        && other._hoverColor == _hoverColor
+        && other._highlightColor == _highlightColor
+        && other._splashColor == _splashColor
+        && other.colorScheme == colorScheme
+        && other._materialTapTargetSize == _materialTapTargetSize;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      textTheme,
+      minWidth,
+      height,
+      padding,
+      shape,
+      alignedDropdown,
+      _buttonColor,
+      _disabledColor,
+      _focusColor,
+      _hoverColor,
+      _highlightColor,
+      _splashColor,
+      colorScheme,
+      _materialTapTargetSize,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    const ButtonThemeData defaultTheme = ButtonThemeData();
+    properties.add(EnumProperty<ButtonTextTheme>('textTheme', textTheme, defaultValue: defaultTheme.textTheme));
+    properties.add(DoubleProperty('minWidth', minWidth, defaultValue: defaultTheme.minWidth));
+    properties.add(DoubleProperty('height', height, defaultValue: defaultTheme.height));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: defaultTheme.padding));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: defaultTheme.shape));
+    properties.add(FlagProperty('alignedDropdown',
+      value: alignedDropdown,
+      defaultValue: defaultTheme.alignedDropdown,
+      ifTrue: 'dropdown width matches button',
+    ));
+    properties.add(ColorProperty('buttonColor', _buttonColor, defaultValue: null));
+    properties.add(ColorProperty('disabledColor', _disabledColor, defaultValue: null));
+    properties.add(ColorProperty('focusColor', _focusColor, defaultValue: null));
+    properties.add(ColorProperty('hoverColor', _hoverColor, defaultValue: null));
+    properties.add(ColorProperty('highlightColor', _highlightColor, defaultValue: null));
+    properties.add(ColorProperty('splashColor', _splashColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<ColorScheme>('colorScheme', colorScheme, defaultValue: defaultTheme.colorScheme));
+    properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', _materialTapTargetSize, defaultValue: null));
+  }
+}
diff --git a/lib/src/material/calendar_date_picker.dart b/lib/src/material/calendar_date_picker.dart
new file mode 100644
index 0000000..9b70bb1
--- /dev/null
+++ b/lib/src/material/calendar_date_picker.dart
@@ -0,0 +1,1297 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'color_scheme.dart';
+import 'date.dart';
+import 'debug.dart';
+import 'divider.dart';
+import 'icon_button.dart';
+import 'icons.dart';
+import 'ink_well.dart';
+import 'material_localizations.dart';
+import 'text_theme.dart';
+import 'theme.dart';
+
+const Duration _monthScrollDuration = Duration(milliseconds: 200);
+
+const double _dayPickerRowHeight = 42.0;
+const int _maxDayPickerRowCount = 6; // A 31 day month that starts on Saturday.
+// One extra row for the day-of-week header.
+const double _maxDayPickerHeight = _dayPickerRowHeight * (_maxDayPickerRowCount + 1);
+const double _monthPickerHorizontalPadding = 8.0;
+
+const int _yearPickerColumnCount = 3;
+const double _yearPickerPadding = 16.0;
+const double _yearPickerRowHeight = 52.0;
+const double _yearPickerRowSpacing = 8.0;
+
+const double _subHeaderHeight = 52.0;
+const double _monthNavButtonsWidth = 108.0;
+
+/// Displays a grid of days for a given month and allows the user to select a
+/// date.
+///
+/// Days are arranged in a rectangular grid with one column for each day of the
+/// week. Controls are provided to change the year and month that the grid is
+/// showing.
+///
+/// The calendar picker widget is rarely used directly. Instead, consider using
+/// [showDatePicker], which will create a dialog that uses this as well as
+/// provides a text entry option.
+///
+/// See also:
+///
+///  * [showDatePicker], which creates a Dialog that contains a
+///    [CalendarDatePicker] and provides an optional compact view where the
+///    user can enter a date as a line of text.
+///  * [showTimePicker], which shows a dialog that contains a material design
+///    time picker.
+///
+class CalendarDatePicker extends StatefulWidget {
+  /// Creates a calendar date picker.
+  ///
+  /// It will display a grid of days for the [initialDate]'s month. The day
+  /// indicated by [initialDate] will be selected.
+  ///
+  /// The optional [onDisplayedMonthChanged] callback can be used to track
+  /// the currently displayed month.
+  ///
+  /// The user interface provides a way to change the year of the month being
+  /// displayed. By default it will show the day grid, but this can be changed
+  /// to start in the year selection interface with [initialCalendarMode] set
+  /// to [DatePickerMode.year].
+  ///
+  /// The [initialDate], [firstDate], [lastDate], [onDateChanged], and
+  /// [initialCalendarMode] must be non-null.
+  ///
+  /// [lastDate] must be after or equal to [firstDate].
+  ///
+  /// [initialDate] must be between [firstDate] and [lastDate] or equal to
+  /// one of them.
+  ///
+  /// [currentDate] represents the current day (i.e. today). This
+  /// date will be highlighted in the day grid. If null, the date of
+  /// `DateTime.now()` will be used.
+  ///
+  /// If [selectableDayPredicate] is non-null, it must return `true` for the
+  /// [initialDate].
+  CalendarDatePicker({
+    Key? key,
+    required DateTime initialDate,
+    required DateTime firstDate,
+    required DateTime lastDate,
+    DateTime? currentDate,
+    required this.onDateChanged,
+    this.onDisplayedMonthChanged,
+    this.initialCalendarMode = DatePickerMode.day,
+    this.selectableDayPredicate,
+  }) : assert(initialDate != null),
+       assert(firstDate != null),
+       assert(lastDate != null),
+       initialDate = DateUtils.dateOnly(initialDate),
+       firstDate = DateUtils.dateOnly(firstDate),
+       lastDate = DateUtils.dateOnly(lastDate),
+       currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()),
+       assert(onDateChanged != null),
+       assert(initialCalendarMode != null),
+       super(key: key) {
+    assert(
+      !this.lastDate.isBefore(this.firstDate),
+      'lastDate ${this.lastDate} must be on or after firstDate ${this.firstDate}.'
+    );
+    assert(
+      !this.initialDate.isBefore(this.firstDate),
+      'initialDate ${this.initialDate} must be on or after firstDate ${this.firstDate}.'
+    );
+    assert(
+      !this.initialDate.isAfter(this.lastDate),
+      'initialDate ${this.initialDate} must be on or before lastDate ${this.lastDate}.'
+    );
+    assert(
+      selectableDayPredicate == null || selectableDayPredicate!(this.initialDate),
+      'Provided initialDate ${this.initialDate} must satisfy provided selectableDayPredicate.'
+    );
+  }
+
+  /// The initially selected [DateTime] that the picker should display.
+  final DateTime initialDate;
+
+  /// The earliest allowable [DateTime] that the user can select.
+  final DateTime firstDate;
+
+  /// The latest allowable [DateTime] that the user can select.
+  final DateTime lastDate;
+
+  /// The [DateTime] representing today. It will be highlighted in the day grid.
+  final DateTime currentDate;
+
+  /// Called when the user selects a date in the picker.
+  final ValueChanged<DateTime> onDateChanged;
+
+  /// Called when the user navigates to a new month/year in the picker.
+  final ValueChanged<DateTime>? onDisplayedMonthChanged;
+
+  /// The initial display of the calendar picker.
+  final DatePickerMode initialCalendarMode;
+
+  /// Function to provide full control over which dates in the calendar can be selected.
+  final SelectableDayPredicate? selectableDayPredicate;
+
+  @override
+  _CalendarDatePickerState createState() => _CalendarDatePickerState();
+}
+
+class _CalendarDatePickerState extends State<CalendarDatePicker> {
+  bool _announcedInitialDate = false;
+  late DatePickerMode _mode;
+  late DateTime _currentDisplayedMonthDate;
+  late DateTime _selectedDate;
+  final GlobalKey _monthPickerKey = GlobalKey();
+  final GlobalKey _yearPickerKey = GlobalKey();
+  late MaterialLocalizations _localizations;
+  late TextDirection _textDirection;
+
+  @override
+  void initState() {
+    super.initState();
+    _mode = widget.initialCalendarMode;
+    _currentDisplayedMonthDate = DateTime(widget.initialDate.year, widget.initialDate.month);
+    _selectedDate = widget.initialDate;
+  }
+
+  @override
+  void didUpdateWidget(CalendarDatePicker oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.initialCalendarMode != oldWidget.initialCalendarMode) {
+      _mode = widget.initialCalendarMode;
+    }
+    if (!DateUtils.isSameDay(widget.initialDate, oldWidget.initialDate)) {
+      _currentDisplayedMonthDate = DateTime(widget.initialDate.year, widget.initialDate.month);
+      _selectedDate = widget.initialDate;
+    }
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    assert(debugCheckHasMaterial(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+    assert(debugCheckHasDirectionality(context));
+    _localizations = MaterialLocalizations.of(context);
+    _textDirection = Directionality.of(context);
+    if (!_announcedInitialDate) {
+      _announcedInitialDate = true;
+      SemanticsService.announce(
+        _localizations.formatFullDate(_selectedDate),
+        _textDirection,
+      );
+    }
+  }
+
+  void _vibrate() {
+    switch (Theme.of(context).platform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        HapticFeedback.vibrate();
+        break;
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        break;
+    }
+  }
+
+  void _handleModeChanged(DatePickerMode mode) {
+    _vibrate();
+    setState(() {
+      _mode = mode;
+      if (_mode == DatePickerMode.day) {
+        SemanticsService.announce(
+          _localizations.formatMonthYear(_selectedDate),
+          _textDirection,
+        );
+      } else {
+        SemanticsService.announce(
+          _localizations.formatYear(_selectedDate),
+          _textDirection,
+        );
+      }
+    });
+  }
+
+  void _handleMonthChanged(DateTime date) {
+    setState(() {
+      if (_currentDisplayedMonthDate.year != date.year || _currentDisplayedMonthDate.month != date.month) {
+        _currentDisplayedMonthDate = DateTime(date.year, date.month);
+        widget.onDisplayedMonthChanged?.call(_currentDisplayedMonthDate);
+      }
+    });
+  }
+
+  void _handleYearChanged(DateTime value) {
+    _vibrate();
+
+    if (value.isBefore(widget.firstDate)) {
+      value = widget.firstDate;
+    } else if (value.isAfter(widget.lastDate)) {
+      value = widget.lastDate;
+    }
+
+    setState(() {
+      _mode = DatePickerMode.day;
+      _handleMonthChanged(value);
+    });
+  }
+
+  void _handleDayChanged(DateTime value) {
+    _vibrate();
+    setState(() {
+      _selectedDate = value;
+      widget.onDateChanged(_selectedDate);
+    });
+  }
+
+  Widget _buildPicker() {
+    switch (_mode) {
+      case DatePickerMode.day:
+        return _MonthPicker(
+          key: _monthPickerKey,
+          initialMonth: _currentDisplayedMonthDate,
+          currentDate: widget.currentDate,
+          firstDate: widget.firstDate,
+          lastDate: widget.lastDate,
+          selectedDate: _selectedDate,
+          onChanged: _handleDayChanged,
+          onDisplayedMonthChanged: _handleMonthChanged,
+          selectableDayPredicate: widget.selectableDayPredicate,
+        );
+      case DatePickerMode.year:
+        return Padding(
+          padding: const EdgeInsets.only(top: _subHeaderHeight),
+          child: YearPicker(
+            key: _yearPickerKey,
+            currentDate: widget.currentDate,
+            firstDate: widget.firstDate,
+            lastDate: widget.lastDate,
+            initialDate: _currentDisplayedMonthDate,
+            selectedDate: _selectedDate,
+            onChanged: _handleYearChanged,
+          ),
+        );
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+    assert(debugCheckHasDirectionality(context));
+    return Stack(
+      children: <Widget>[
+        SizedBox(
+          height: _subHeaderHeight + _maxDayPickerHeight,
+          child: _buildPicker(),
+        ),
+        // Put the mode toggle button on top so that it won't be covered up by the _MonthPicker
+        _DatePickerModeToggleButton(
+          mode: _mode,
+          title: _localizations.formatMonthYear(_currentDisplayedMonthDate),
+          onTitlePressed: () {
+            // Toggle the day/year mode.
+            _handleModeChanged(_mode == DatePickerMode.day ? DatePickerMode.year : DatePickerMode.day);
+          },
+        ),
+      ],
+    );
+  }
+}
+
+/// A button that used to toggle the [DatePickerMode] for a date picker.
+///
+/// This appears above the calendar grid and allows the user to toggle the
+/// [DatePickerMode] to display either the calendar view or the year list.
+class _DatePickerModeToggleButton extends StatefulWidget {
+  const _DatePickerModeToggleButton({
+    required this.mode,
+    required this.title,
+    required this.onTitlePressed,
+  });
+
+  /// The current display of the calendar picker.
+  final DatePickerMode mode;
+
+  /// The text that displays the current month/year being viewed.
+  final String title;
+
+  /// The callback when the title is pressed.
+  final VoidCallback onTitlePressed;
+
+  @override
+  _DatePickerModeToggleButtonState createState() => _DatePickerModeToggleButtonState();
+}
+
+class _DatePickerModeToggleButtonState extends State<_DatePickerModeToggleButton> with SingleTickerProviderStateMixin {
+  late AnimationController _controller;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(
+      value: widget.mode == DatePickerMode.year ? 0.5 : 0,
+      upperBound: 0.5,
+      duration: const Duration(milliseconds: 200),
+      vsync: this,
+    );
+  }
+
+  @override
+  void didUpdateWidget(_DatePickerModeToggleButton oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.mode == widget.mode) {
+      return;
+    }
+
+    if (widget.mode == DatePickerMode.year) {
+      _controller.forward();
+    } else {
+      _controller.reverse();
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ColorScheme colorScheme = Theme.of(context).colorScheme;
+    final TextTheme textTheme = Theme.of(context).textTheme;
+    final Color controlColor = colorScheme.onSurface.withOpacity(0.60);
+
+    return Container(
+      padding: const EdgeInsetsDirectional.only(start: 16, end: 4),
+      height: _subHeaderHeight,
+      child: Row(
+        children: <Widget>[
+          Flexible(
+            child: Semantics(
+              label: MaterialLocalizations.of(context).selectYearSemanticsLabel,
+              excludeSemantics: true,
+              button: true,
+              child: Container(
+                height: _subHeaderHeight,
+                child: InkWell(
+                  onTap: widget.onTitlePressed,
+                  child: Padding(
+                    padding: const EdgeInsets.symmetric(horizontal: 8),
+                    child: Row(
+                      children: <Widget>[
+                        Flexible(
+                          child: Text(
+                            widget.title,
+                            overflow: TextOverflow.ellipsis,
+                            style: textTheme.subtitle2?.copyWith(
+                              color: controlColor,
+                            ),
+                          ),
+                        ),
+                        RotationTransition(
+                          turns: _controller,
+                          child: Icon(
+                            Icons.arrow_drop_down,
+                            color: controlColor,
+                          ),
+                        ),
+                      ],
+                    ),
+                  ),
+                ),
+              ),
+            ),
+          ),
+          if (widget.mode == DatePickerMode.day)
+            // Give space for the prev/next month buttons that are underneath this row
+            const SizedBox(width: _monthNavButtonsWidth),
+        ],
+      ),
+    );
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+}
+
+class _MonthPicker extends StatefulWidget {
+  /// Creates a month picker.
+  _MonthPicker({
+    Key? key,
+    required this.initialMonth,
+    required this.currentDate,
+    required this.firstDate,
+    required this.lastDate,
+    required this.selectedDate,
+    required this.onChanged,
+    required this.onDisplayedMonthChanged,
+    this.selectableDayPredicate,
+  }) : assert(selectedDate != null),
+       assert(currentDate != null),
+       assert(onChanged != null),
+       assert(firstDate != null),
+       assert(lastDate != null),
+       assert(!firstDate.isAfter(lastDate)),
+       assert(!selectedDate.isBefore(firstDate)),
+       assert(!selectedDate.isAfter(lastDate)),
+       super(key: key);
+
+  /// The initial month to display.
+  final DateTime initialMonth;
+
+  /// The current date.
+  ///
+  /// This date is subtly highlighted in the picker.
+  final DateTime currentDate;
+
+  /// The earliest date the user is permitted to pick.
+  ///
+  /// This date must be on or before the [lastDate].
+  final DateTime firstDate;
+
+  /// The latest date the user is permitted to pick.
+  ///
+  /// This date must be on or after the [firstDate].
+  final DateTime lastDate;
+
+  /// The currently selected date.
+  ///
+  /// This date is highlighted in the picker.
+  final DateTime selectedDate;
+
+  /// Called when the user picks a day.
+  final ValueChanged<DateTime> onChanged;
+
+  /// Called when the user navigates to a new month.
+  final ValueChanged<DateTime> onDisplayedMonthChanged;
+
+  /// Optional user supplied predicate function to customize selectable days.
+  final SelectableDayPredicate? selectableDayPredicate;
+
+  @override
+  _MonthPickerState createState() => _MonthPickerState();
+}
+
+class _MonthPickerState extends State<_MonthPicker> {
+  final GlobalKey _pageViewKey = GlobalKey();
+  late DateTime _currentMonth;
+  late DateTime _nextMonthDate;
+  late DateTime _previousMonthDate;
+  late PageController _pageController;
+  late MaterialLocalizations _localizations;
+  late TextDirection _textDirection;
+  Map<LogicalKeySet, Intent>? _shortcutMap;
+  Map<Type, Action<Intent>>? _actionMap;
+  late FocusNode _dayGridFocus;
+  DateTime? _focusedDay;
+
+  @override
+  void initState() {
+    super.initState();
+    _currentMonth = widget.initialMonth;
+    _previousMonthDate = DateUtils.addMonthsToMonthDate(_currentMonth, -1);
+    _nextMonthDate = DateUtils.addMonthsToMonthDate(_currentMonth, 1);
+    _pageController = PageController(initialPage: DateUtils.monthDelta(widget.firstDate, _currentMonth));
+    _shortcutMap = <LogicalKeySet, Intent>{
+      LogicalKeySet(LogicalKeyboardKey.arrowLeft): const DirectionalFocusIntent(TraversalDirection.left),
+      LogicalKeySet(LogicalKeyboardKey.arrowRight): const DirectionalFocusIntent(TraversalDirection.right),
+      LogicalKeySet(LogicalKeyboardKey.arrowDown): const DirectionalFocusIntent(TraversalDirection.down),
+      LogicalKeySet(LogicalKeyboardKey.arrowUp): const DirectionalFocusIntent(TraversalDirection.up),
+    };
+    _actionMap = <Type, Action<Intent>>{
+      NextFocusIntent: CallbackAction<NextFocusIntent>(onInvoke: _handleGridNextFocus),
+      PreviousFocusIntent: CallbackAction<PreviousFocusIntent>(onInvoke: _handleGridPreviousFocus),
+      DirectionalFocusIntent: CallbackAction<DirectionalFocusIntent>(onInvoke: _handleDirectionFocus),
+    };
+    _dayGridFocus = FocusNode(debugLabel: 'Day Grid');
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _localizations = MaterialLocalizations.of(context);
+    _textDirection = Directionality.of(context);
+  }
+
+  @override
+  void didUpdateWidget(_MonthPicker oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.initialMonth != oldWidget.initialMonth) {
+      // We can't interrupt this widget build with a scroll, so do it next frame
+      WidgetsBinding.instance!.addPostFrameCallback(
+        (Duration timeStamp) => _showMonth(widget.initialMonth, jump: true)
+      );
+    }
+  }
+
+  @override
+  void dispose() {
+    _pageController.dispose();
+    _dayGridFocus.dispose();
+    super.dispose();
+  }
+
+  void _handleDateSelected(DateTime selectedDate) {
+    _focusedDay = selectedDate;
+    widget.onChanged(selectedDate);
+  }
+
+  void _handleMonthPageChanged(int monthPage) {
+    setState(() {
+      final DateTime monthDate = DateUtils.addMonthsToMonthDate(widget.firstDate, monthPage);
+      if (!DateUtils.isSameMonth(_currentMonth, monthDate)) {
+        _currentMonth = DateTime(monthDate.year, monthDate.month);
+        _previousMonthDate = DateUtils.addMonthsToMonthDate(_currentMonth, -1);
+        _nextMonthDate = DateUtils.addMonthsToMonthDate(_currentMonth, 1);
+        widget.onDisplayedMonthChanged(_currentMonth);
+        if (_focusedDay != null && !DateUtils.isSameMonth(_focusedDay, _currentMonth)) {
+          // We have navigated to a new month with the grid focused, but the
+          // focused day is not in this month. Choose a new one trying to keep
+          // the same day of the month.
+          _focusedDay = _focusableDayForMonth(_currentMonth, _focusedDay!.day);
+        }
+      }
+    });
+  }
+
+  /// Returns a focusable date for the given month.
+  ///
+  /// If the preferredDay is available in the month it will be returned,
+  /// otherwise the first selectable day in the month will be returned. If
+  /// no dates are selectable in the month, then it will return null.
+  DateTime? _focusableDayForMonth(DateTime month, int preferredDay) {
+    final int daysInMonth = DateUtils.getDaysInMonth(month.year, month.month);
+
+    // Can we use the preferred day in this month?
+    if (preferredDay <= daysInMonth) {
+      final DateTime newFocus = DateTime(month.year, month.month, preferredDay);
+      if (_isSelectable(newFocus))
+        return newFocus;
+    }
+
+    // Start at the 1st and take the first selectable date.
+    for (int day = 1; day <= daysInMonth; day++) {
+      final DateTime newFocus = DateTime(month.year, month.month, day);
+      if (_isSelectable(newFocus))
+        return newFocus;
+    }
+    return null;
+  }
+
+  /// Navigate to the next month.
+  void _handleNextMonth() {
+    if (!_isDisplayingLastMonth) {
+      SemanticsService.announce(
+        _localizations.formatMonthYear(_nextMonthDate),
+        _textDirection,
+      );
+      _pageController.nextPage(
+        duration: _monthScrollDuration,
+        curve: Curves.ease,
+      );
+    }
+  }
+
+  /// Navigate to the previous month.
+  void _handlePreviousMonth() {
+    if (!_isDisplayingFirstMonth) {
+      SemanticsService.announce(
+        _localizations.formatMonthYear(_previousMonthDate),
+        _textDirection,
+      );
+      _pageController.previousPage(
+        duration: _monthScrollDuration,
+        curve: Curves.ease,
+      );
+    }
+  }
+
+  /// Navigate to the given month.
+  void _showMonth(DateTime month, { bool jump = false}) {
+    final int monthPage = DateUtils.monthDelta(widget.firstDate, month);
+    if (jump) {
+      _pageController.jumpToPage(monthPage);
+    } else {
+      _pageController.animateToPage(monthPage,
+        duration: _monthScrollDuration,
+        curve: Curves.ease
+      );
+    }
+  }
+
+  /// True if the earliest allowable month is displayed.
+  bool get _isDisplayingFirstMonth {
+    return !_currentMonth.isAfter(
+      DateTime(widget.firstDate.year, widget.firstDate.month),
+    );
+  }
+
+  /// True if the latest allowable month is displayed.
+  bool get _isDisplayingLastMonth {
+    return !_currentMonth.isBefore(
+      DateTime(widget.lastDate.year, widget.lastDate.month),
+    );
+  }
+
+  /// Handler for when the overall day grid obtains or loses focus.
+  void _handleGridFocusChange(bool focused) {
+    setState(() {
+      if (focused && _focusedDay == null) {
+        if (DateUtils.isSameMonth(widget.selectedDate, _currentMonth)) {
+          _focusedDay = widget.selectedDate;
+        } else if (DateUtils.isSameMonth(widget.currentDate, _currentMonth)) {
+          _focusedDay = _focusableDayForMonth(_currentMonth, widget.currentDate.day);
+        } else {
+          _focusedDay = _focusableDayForMonth(_currentMonth, 1);
+        }
+      }
+    });
+  }
+
+  /// Move focus to the next element after the day grid.
+  void _handleGridNextFocus(NextFocusIntent intent) {
+    _dayGridFocus.requestFocus();
+    _dayGridFocus.nextFocus();
+  }
+
+  /// Move focus to the previous element before the day grid.
+  void _handleGridPreviousFocus(PreviousFocusIntent intent) {
+    _dayGridFocus.requestFocus();
+    _dayGridFocus.previousFocus();
+  }
+
+  /// Move the internal focus date in the direction of the given intent.
+  ///
+  /// This will attempt to move the focused day to the next selectable day in
+  /// the given direction. If the new date is not in the current month, then
+  /// the page view will be scrolled to show the new date's month.
+  ///
+  /// For horizontal directions, it will move forward or backward a day (depending
+  /// on the current [TextDirection]). For vertical directions it will move up and
+  /// down a week at a time.
+  void _handleDirectionFocus(DirectionalFocusIntent intent) {
+    assert(_focusedDay != null);
+    setState(() {
+      final DateTime? nextDate = _nextDateInDirection(_focusedDay!, intent.direction);
+      if (nextDate != null) {
+        _focusedDay = nextDate;
+        if (!DateUtils.isSameMonth(_focusedDay, _currentMonth)) {
+          _showMonth(_focusedDay!);
+        }
+      }
+    });
+  }
+
+  static const Map<TraversalDirection, int> _directionOffset = <TraversalDirection, int>{
+    TraversalDirection.up: -DateTime.daysPerWeek,
+    TraversalDirection.right: 1,
+    TraversalDirection.down: DateTime.daysPerWeek,
+    TraversalDirection.left: -1,
+  };
+
+  int _dayDirectionOffset(TraversalDirection traversalDirection, TextDirection textDirection) {
+    // Swap left and right if the text direction if RTL
+    if (textDirection == TextDirection.rtl) {
+      if (traversalDirection == TraversalDirection.left)
+        traversalDirection = TraversalDirection.right;
+      else if (traversalDirection == TraversalDirection.right)
+        traversalDirection = TraversalDirection.left;
+    }
+    return _directionOffset[traversalDirection]!;
+  }
+
+  DateTime? _nextDateInDirection(DateTime date, TraversalDirection direction) {
+    final TextDirection textDirection = Directionality.of(context);
+    DateTime nextDate = DateUtils.addDaysToDate(date, _dayDirectionOffset(direction, textDirection));
+    while (!nextDate.isBefore(widget.firstDate) && !nextDate.isAfter(widget.lastDate)) {
+      if (_isSelectable(nextDate)) {
+        return nextDate;
+      }
+      nextDate = DateUtils.addDaysToDate(nextDate, _dayDirectionOffset(direction, textDirection));
+    }
+    return null;
+  }
+
+  bool _isSelectable(DateTime date) {
+    return widget.selectableDayPredicate == null || widget.selectableDayPredicate!.call(date);
+  }
+
+  Widget _buildItems(BuildContext context, int index) {
+    final DateTime month = DateUtils.addMonthsToMonthDate(widget.firstDate, index);
+    return _DayPicker(
+      key: ValueKey<DateTime>(month),
+      selectedDate: widget.selectedDate,
+      currentDate: widget.currentDate,
+      onChanged: _handleDateSelected,
+      firstDate: widget.firstDate,
+      lastDate: widget.lastDate,
+      displayedMonth: month,
+      selectableDayPredicate: widget.selectableDayPredicate,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final String previousTooltipText = '${_localizations.previousMonthTooltip} ${_localizations.formatMonthYear(_previousMonthDate)}';
+    final String nextTooltipText = '${_localizations.nextMonthTooltip} ${_localizations.formatMonthYear(_nextMonthDate)}';
+    final Color controlColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.60);
+
+    return Semantics(
+      child: Column(
+        children: <Widget>[
+          Container(
+            padding: const EdgeInsetsDirectional.only(start: 16, end: 4),
+            height: _subHeaderHeight,
+            child: Row(
+              children: <Widget>[
+                const Spacer(),
+                IconButton(
+                  icon: const Icon(Icons.chevron_left),
+                  color: controlColor,
+                  tooltip: _isDisplayingFirstMonth ? null : previousTooltipText,
+                  onPressed: _isDisplayingFirstMonth ? null : _handlePreviousMonth,
+                ),
+                IconButton(
+                  icon: const Icon(Icons.chevron_right),
+                  color: controlColor,
+                  tooltip: _isDisplayingLastMonth ? null : nextTooltipText,
+                  onPressed: _isDisplayingLastMonth ? null : _handleNextMonth,
+                ),
+              ],
+            ),
+          ),
+          Expanded(
+            child: FocusableActionDetector(
+              shortcuts: _shortcutMap,
+              actions: _actionMap,
+              focusNode: _dayGridFocus,
+              onFocusChange: _handleGridFocusChange,
+              child: _FocusedDate(
+                date: _dayGridFocus.hasFocus ? _focusedDay : null,
+                child: PageView.builder(
+                  key: _pageViewKey,
+                  controller: _pageController,
+                  itemBuilder: _buildItems,
+                  itemCount: DateUtils.monthDelta(widget.firstDate, widget.lastDate) + 1,
+                  scrollDirection: Axis.horizontal,
+                  onPageChanged: _handleMonthPageChanged,
+                ),
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+/// InheritedWidget indicating what the current focused date is for its children.
+///
+/// This is used by the [_MonthPicker] to let its children [_DayPicker]s know
+/// what the currently focused date (if any) should be.
+class _FocusedDate extends InheritedWidget {
+  const _FocusedDate({
+    Key? key,
+    required Widget child,
+    this.date
+  }) : super(key: key, child: child);
+
+  final DateTime? date;
+
+  @override
+  bool updateShouldNotify(_FocusedDate oldWidget) {
+    return !DateUtils.isSameDay(date, oldWidget.date);
+  }
+
+  static DateTime? of(BuildContext context) {
+    final _FocusedDate? focusedDate = context.dependOnInheritedWidgetOfExactType<_FocusedDate>();
+    return focusedDate?.date;
+  }
+}
+
+/// Displays the days of a given month and allows choosing a day.
+///
+/// The days are arranged in a rectangular grid with one column for each day of
+/// the week.
+class _DayPicker extends StatefulWidget {
+  /// Creates a day picker.
+  _DayPicker({
+    Key? key,
+    required this.currentDate,
+    required this.displayedMonth,
+    required this.firstDate,
+    required this.lastDate,
+    required this.selectedDate,
+    required this.onChanged,
+    this.selectableDayPredicate,
+  }) : assert(currentDate != null),
+       assert(displayedMonth != null),
+       assert(firstDate != null),
+       assert(lastDate != null),
+       assert(selectedDate != null),
+       assert(onChanged != null),
+       assert(!firstDate.isAfter(lastDate)),
+       assert(!selectedDate.isBefore(firstDate)),
+       assert(!selectedDate.isAfter(lastDate)),
+       super(key: key);
+
+  /// The currently selected date.
+  ///
+  /// This date is highlighted in the picker.
+  final DateTime selectedDate;
+
+  /// The current date at the time the picker is displayed.
+  final DateTime currentDate;
+
+  /// Called when the user picks a day.
+  final ValueChanged<DateTime> onChanged;
+
+  /// The earliest date the user is permitted to pick.
+  ///
+  /// This date must be on or before the [lastDate].
+  final DateTime firstDate;
+
+  /// The latest date the user is permitted to pick.
+  ///
+  /// This date must be on or after the [firstDate].
+  final DateTime lastDate;
+
+  /// The month whose days are displayed by this picker.
+  final DateTime displayedMonth;
+
+  /// Optional user supplied predicate function to customize selectable days.
+  final SelectableDayPredicate? selectableDayPredicate;
+
+  @override
+  _DayPickerState createState() => _DayPickerState();
+}
+
+class _DayPickerState extends State<_DayPicker> {
+
+  /// List of [FocusNode]s, one for each day of the month.
+  late List<FocusNode> _dayFocusNodes;
+
+  @override
+  void initState() {
+    super.initState();
+    final int daysInMonth = DateUtils.getDaysInMonth(widget.displayedMonth.year, widget.displayedMonth.month);
+    _dayFocusNodes = List<FocusNode>.generate(
+      daysInMonth,
+      (int index) => FocusNode(skipTraversal: true, debugLabel: 'Day ${index + 1}')
+    );
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    // Check to see if the focused date is in this month, if so focus it.
+    final DateTime? focusedDate = _FocusedDate.of(context);
+    if (focusedDate != null && DateUtils.isSameMonth(widget.displayedMonth, focusedDate)) {
+      _dayFocusNodes[focusedDate.day - 1].requestFocus();
+    }
+  }
+
+  @override
+  void dispose() {
+    for (final FocusNode node in _dayFocusNodes) {
+      node.dispose();
+    }
+    super.dispose();
+  }
+
+  /// Builds widgets showing abbreviated days of week. The first widget in the
+  /// returned list corresponds to the first day of week for the current locale.
+  ///
+  /// Examples:
+  ///
+  /// ```
+  /// ┌ Sunday is the first day of week in the US (en_US)
+  /// |
+  /// S M T W T F S  <-- the returned list contains these widgets
+  /// _ _ _ _ _ 1 2
+  /// 3 4 5 6 7 8 9
+  ///
+  /// ┌ But it's Monday in the UK (en_GB)
+  /// |
+  /// M T W T F S S  <-- the returned list contains these widgets
+  /// _ _ _ _ 1 2 3
+  /// 4 5 6 7 8 9 10
+  /// ```
+  List<Widget> _dayHeaders(TextStyle? headerStyle, MaterialLocalizations localizations) {
+    final List<Widget> result = <Widget>[];
+    for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) {
+      final String weekday = localizations.narrowWeekdays[i];
+      result.add(ExcludeSemantics(
+        child: Center(child: Text(weekday, style: headerStyle)),
+      ));
+      if (i == (localizations.firstDayOfWeekIndex - 1) % 7)
+        break;
+    }
+    return result;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ColorScheme colorScheme = Theme.of(context).colorScheme;
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final TextTheme textTheme = Theme.of(context).textTheme;
+    final TextStyle? headerStyle = textTheme.caption?.apply(
+      color: colorScheme.onSurface.withOpacity(0.60),
+    );
+    final TextStyle dayStyle = textTheme.caption!;
+    final Color enabledDayColor = colorScheme.onSurface.withOpacity(0.87);
+    final Color disabledDayColor = colorScheme.onSurface.withOpacity(0.38);
+    final Color selectedDayColor = colorScheme.onPrimary;
+    final Color selectedDayBackground = colorScheme.primary;
+    final Color todayColor = colorScheme.primary;
+
+    final int year = widget.displayedMonth.year;
+    final int month = widget.displayedMonth.month;
+
+    final int daysInMonth = DateUtils.getDaysInMonth(year, month);
+    final int dayOffset = DateUtils.firstDayOffset(year, month, localizations);
+
+    final List<Widget> dayItems = _dayHeaders(headerStyle, localizations);
+    // 1-based day of month, e.g. 1-31 for January, and 1-29 for February on
+    // a leap year.
+    int day = -dayOffset;
+    while (day < daysInMonth) {
+      day++;
+      if (day < 1) {
+        dayItems.add(Container());
+      } else {
+        final DateTime dayToBuild = DateTime(year, month, day);
+        final bool isDisabled = dayToBuild.isAfter(widget.lastDate) ||
+            dayToBuild.isBefore(widget.firstDate) ||
+            (widget.selectableDayPredicate != null && !widget.selectableDayPredicate!(dayToBuild));
+        final bool isSelectedDay = DateUtils.isSameDay(widget.selectedDate, dayToBuild);
+        final bool isToday = DateUtils.isSameDay(widget.currentDate, dayToBuild);
+
+        BoxDecoration? decoration;
+        Color dayColor = enabledDayColor;
+        if (isSelectedDay) {
+          // The selected day gets a circle background highlight, and a
+          // contrasting text color.
+          dayColor = selectedDayColor;
+          decoration = BoxDecoration(
+            color: selectedDayBackground,
+            shape: BoxShape.circle,
+          );
+        } else if (isDisabled) {
+          dayColor = disabledDayColor;
+        } else if (isToday) {
+          // The current day gets a different text color and a circle stroke
+          // border.
+          dayColor = todayColor;
+          decoration = BoxDecoration(
+            border: Border.all(color: todayColor, width: 1),
+            shape: BoxShape.circle,
+          );
+        }
+
+        Widget dayWidget = Container(
+          decoration: decoration,
+          child: Center(
+            child: Text(localizations.formatDecimal(day), style: dayStyle.apply(color: dayColor)),
+          ),
+        );
+
+        if (isDisabled) {
+          dayWidget = ExcludeSemantics(
+            child: dayWidget,
+          );
+        } else {
+          dayWidget = InkResponse(
+            focusNode: _dayFocusNodes[day - 1],
+            onTap: () => widget.onChanged(dayToBuild),
+            radius: _dayPickerRowHeight / 2 + 4,
+            splashColor: selectedDayBackground.withOpacity(0.38),
+            child: Semantics(
+              // We want the day of month to be spoken first irrespective of the
+              // locale-specific preferences or TextDirection. This is because
+              // an accessibility user is more likely to be interested in the
+              // day of month before the rest of the date, as they are looking
+              // for the day of month. To do that we prepend day of month to the
+              // formatted full date.
+              label: '${localizations.formatDecimal(day)}, ${localizations.formatFullDate(dayToBuild)}',
+              selected: isSelectedDay,
+              excludeSemantics: true,
+              child: dayWidget,
+            ),
+          );
+        }
+
+        dayItems.add(dayWidget);
+      }
+    }
+
+    return Padding(
+      padding: const EdgeInsets.symmetric(
+        horizontal: _monthPickerHorizontalPadding,
+      ),
+      child: GridView.custom(
+        physics: const ClampingScrollPhysics(),
+        gridDelegate: _dayPickerGridDelegate,
+        childrenDelegate: SliverChildListDelegate(
+          dayItems,
+          addRepaintBoundaries: false,
+        ),
+      ),
+    );
+  }
+}
+
+class _DayPickerGridDelegate extends SliverGridDelegate {
+  const _DayPickerGridDelegate();
+
+  @override
+  SliverGridLayout getLayout(SliverConstraints constraints) {
+    const int columnCount = DateTime.daysPerWeek;
+    final double tileWidth = constraints.crossAxisExtent / columnCount;
+    final double tileHeight = math.min(_dayPickerRowHeight,
+      constraints.viewportMainAxisExtent / (_maxDayPickerRowCount + 1));
+    return SliverGridRegularTileLayout(
+      childCrossAxisExtent: tileWidth,
+      childMainAxisExtent: tileHeight,
+      crossAxisCount: columnCount,
+      crossAxisStride: tileWidth,
+      mainAxisStride: tileHeight,
+      reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
+    );
+  }
+
+  @override
+  bool shouldRelayout(_DayPickerGridDelegate oldDelegate) => false;
+}
+
+const _DayPickerGridDelegate _dayPickerGridDelegate = _DayPickerGridDelegate();
+
+/// A scrollable grid of years to allow picking a year.
+///
+/// The year picker widget is rarely used directly. Instead, consider using
+/// [CalendarDatePicker], or [showDatePicker] which create full date pickers.
+///
+/// See also:
+///
+///  * [CalendarDatePicker], which provides a Material Design date picker
+///    interface.
+///
+///  * [showDatePicker], which shows a dialog containing a Material Design
+///    date picker.
+///
+class YearPicker extends StatefulWidget {
+  /// Creates a year picker.
+  ///
+  /// The [firstDate], [lastDate], [selectedDate], and [onChanged]
+  /// arguments must be non-null. The [lastDate] must be after the [firstDate].
+  YearPicker({
+    Key? key,
+    DateTime? currentDate,
+    required this.firstDate,
+    required this.lastDate,
+    DateTime? initialDate,
+    required this.selectedDate,
+    required this.onChanged,
+    this.dragStartBehavior = DragStartBehavior.start,
+  }) : assert(firstDate != null),
+       assert(lastDate != null),
+       assert(selectedDate != null),
+       assert(onChanged != null),
+       assert(!firstDate.isAfter(lastDate)),
+       currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()),
+       initialDate = DateUtils.dateOnly(initialDate ?? selectedDate),
+       super(key: key);
+
+  /// The current date.
+  ///
+  /// This date is subtly highlighted in the picker.
+  final DateTime currentDate;
+
+  /// The earliest date the user is permitted to pick.
+  final DateTime firstDate;
+
+  /// The latest date the user is permitted to pick.
+  final DateTime lastDate;
+
+  /// The initial date to center the year display around.
+  final DateTime initialDate;
+
+  /// The currently selected date.
+  ///
+  /// This date is highlighted in the picker.
+  final DateTime selectedDate;
+
+  /// Called when the user picks a year.
+  final ValueChanged<DateTime> onChanged;
+
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  @override
+  _YearPickerState createState() => _YearPickerState();
+}
+
+class _YearPickerState extends State<YearPicker> {
+  late ScrollController _scrollController;
+
+  // The approximate number of years necessary to fill the available space.
+  static const int minYears = 18;
+
+  @override
+  void initState() {
+    super.initState();
+    _scrollController = ScrollController(initialScrollOffset: _scrollOffsetForYear(widget.selectedDate));
+  }
+
+  @override
+  void didUpdateWidget(YearPicker oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.selectedDate != oldWidget.selectedDate) {
+      _scrollController.jumpTo(_scrollOffsetForYear(widget.selectedDate));
+    }
+  }
+
+  double _scrollOffsetForYear(DateTime date) {
+    final int initialYearIndex = date.year - widget.firstDate.year;
+    final int initialYearRow = initialYearIndex ~/ _yearPickerColumnCount;
+    // Move the offset down by 2 rows to approximately center it.
+    final int centeredYearRow = initialYearRow - 2;
+    return _itemCount < minYears ? 0 : centeredYearRow * _yearPickerRowHeight;
+  }
+
+  Widget _buildYearItem(BuildContext context, int index) {
+    final ColorScheme colorScheme = Theme.of(context).colorScheme;
+    final TextTheme textTheme = Theme.of(context).textTheme;
+
+    // Backfill the _YearPicker with disabled years if necessary.
+    final int offset = _itemCount < minYears ? (minYears - _itemCount) ~/ 2 : 0;
+    final int year = widget.firstDate.year + index - offset;
+    final bool isSelected = year == widget.selectedDate.year;
+    final bool isCurrentYear = year == widget.currentDate.year;
+    final bool isDisabled = year < widget.firstDate.year || year > widget.lastDate.year;
+    const double decorationHeight = 36.0;
+    const double decorationWidth = 72.0;
+
+    final Color textColor;
+    if (isSelected) {
+      textColor = colorScheme.onPrimary;
+    } else if (isDisabled) {
+      textColor = colorScheme.onSurface.withOpacity(0.38);
+    } else if (isCurrentYear) {
+      textColor = colorScheme.primary;
+    } else {
+      textColor = colorScheme.onSurface.withOpacity(0.87);
+    }
+    final TextStyle? itemStyle = textTheme.bodyText1?.apply(color: textColor);
+
+    BoxDecoration? decoration;
+    if (isSelected) {
+      decoration = BoxDecoration(
+        color: colorScheme.primary,
+        borderRadius: BorderRadius.circular(decorationHeight / 2),
+        shape: BoxShape.rectangle,
+      );
+    } else if (isCurrentYear && !isDisabled) {
+      decoration = BoxDecoration(
+        border: Border.all(
+          color: colorScheme.primary,
+          width: 1,
+        ),
+        borderRadius: BorderRadius.circular(decorationHeight / 2),
+        shape: BoxShape.rectangle,
+      );
+    }
+
+    Widget yearItem = Center(
+      child: Container(
+        decoration: decoration,
+        height: decorationHeight,
+        width: decorationWidth,
+        child: Center(
+          child: Semantics(
+            selected: isSelected,
+            child: Text(year.toString(), style: itemStyle),
+          ),
+        ),
+      ),
+    );
+
+    if (isDisabled) {
+      yearItem = ExcludeSemantics(
+        child: yearItem,
+      );
+    } else {
+      yearItem = InkWell(
+        key: ValueKey<int>(year),
+        onTap: () => widget.onChanged(DateTime(year, widget.initialDate.month, 1)),
+        child: yearItem,
+      );
+    }
+
+    return yearItem;
+  }
+
+  int get _itemCount {
+    return widget.lastDate.year - widget.firstDate.year + 1;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    return Column(
+      children: <Widget>[
+        const Divider(),
+        Expanded(
+          child: GridView.builder(
+            controller: _scrollController,
+            dragStartBehavior: widget.dragStartBehavior,
+            gridDelegate: _yearPickerGridDelegate,
+            itemBuilder: _buildYearItem,
+            itemCount: math.max(_itemCount, minYears),
+            padding: const EdgeInsets.symmetric(horizontal: _yearPickerPadding),
+          ),
+        ),
+        const Divider(),
+      ],
+    );
+  }
+}
+
+class _YearPickerGridDelegate extends SliverGridDelegate {
+  const _YearPickerGridDelegate();
+
+  @override
+  SliverGridLayout getLayout(SliverConstraints constraints) {
+    final double tileWidth =
+      (constraints.crossAxisExtent - (_yearPickerColumnCount - 1) * _yearPickerRowSpacing) / _yearPickerColumnCount;
+    return SliverGridRegularTileLayout(
+      childCrossAxisExtent: tileWidth,
+      childMainAxisExtent: _yearPickerRowHeight,
+      crossAxisCount: _yearPickerColumnCount,
+      crossAxisStride: tileWidth + _yearPickerRowSpacing,
+      mainAxisStride: _yearPickerRowHeight,
+      reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
+    );
+  }
+
+  @override
+  bool shouldRelayout(_YearPickerGridDelegate oldDelegate) => false;
+}
+
+const _YearPickerGridDelegate _yearPickerGridDelegate = _YearPickerGridDelegate();
diff --git a/lib/src/material/card.dart b/lib/src/material/card.dart
new file mode 100644
index 0000000..2451a19
--- /dev/null
+++ b/lib/src/material/card.dart
@@ -0,0 +1,220 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'card_theme.dart';
+import 'material.dart';
+import 'theme.dart';
+
+/// A material design card: a panel with slightly rounded corners and an
+/// elevation shadow.
+///
+/// A card is a sheet of [Material] used to represent some related information,
+/// for example an album, a geographical location, a meal, contact details, etc.
+///
+/// This is what it looks like when run:
+///
+/// ![A card with a slight shadow, consisting of two rows, one with an icon and
+/// some text describing a musical, and the other with buttons for buying
+/// tickets or listening to the show.](https://flutter.github.io/assets-for-api-docs/assets/material/card.png)
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// This sample shows creation of a [Card] widget that shows album information
+/// and two actions.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Center(
+///     child: Card(
+///       child: Column(
+///         mainAxisSize: MainAxisSize.min,
+///         children: <Widget>[
+///           const ListTile(
+///             leading: Icon(Icons.album),
+///             title: Text('The Enchanted Nightingale'),
+///             subtitle: Text('Music by Julie Gable. Lyrics by Sidney Stein.'),
+///           ),
+///           Row(
+///             mainAxisAlignment: MainAxisAlignment.end,
+///             children: <Widget>[
+///               TextButton(
+///                 child: const Text('BUY TICKETS'),
+///                 onPressed: () { /* ... */ },
+///               ),
+///               const SizedBox(width: 8),
+///               TextButton(
+///                 child: const Text('LISTEN'),
+///                 onPressed: () { /* ... */ },
+///               ),
+///               const SizedBox(width: 8),
+///             ],
+///           ),
+///         ],
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// Sometimes the primary action area of a card is the card itself. Cards can be
+/// one large touch target that shows a detail screen when tapped.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// This sample shows creation of a [Card] widget that can be tapped. When
+/// tapped this [Card]'s [InkWell] displays an "ink splash" that fills the
+/// entire card.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Center(
+///     child: Card(
+///       child: InkWell(
+///         splashColor: Colors.blue.withAlpha(30),
+///         onTap: () {
+///           print('Card tapped.');
+///         },
+///         child: Container(
+///           width: 300,
+///           height: 100,
+///           child: Text('A card that can be tapped'),
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+///
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ListTile], to display icons and text in a card.
+///  * [showDialog], to display a modal card.
+///  * <https://material.io/design/components/cards.html>
+class Card extends StatelessWidget {
+  /// Creates a material design card.
+  ///
+  /// The [elevation] must be null or non-negative. The [borderOnForeground]
+  /// must not be null.
+  const Card({
+    Key? key,
+    this.color,
+    this.shadowColor,
+    this.elevation,
+    this.shape,
+    this.borderOnForeground = true,
+    this.margin,
+    this.clipBehavior,
+    this.child,
+    this.semanticContainer = true,
+  }) : assert(elevation == null || elevation >= 0.0),
+       assert(borderOnForeground != null),
+       super(key: key);
+
+  /// The card's background color.
+  ///
+  /// Defines the card's [Material.color].
+  ///
+  /// If this property is null then [CardTheme.color] of [ThemeData.cardTheme]
+  /// is used. If that's null then [ThemeData.cardColor] is used.
+  final Color? color;
+
+  /// The color to paint the shadow below the card.
+  ///
+  /// If null then the ambient [CardTheme]'s shadowColor is used.
+  /// If that's null too, then the overall theme's [ThemeData.shadowColor]
+  /// (default black) is used.
+  final Color? shadowColor;
+
+  /// The z-coordinate at which to place this card. This controls the size of
+  /// the shadow below the card.
+  ///
+  /// Defines the card's [Material.elevation].
+  ///
+  /// If this property is null then [CardTheme.elevation] of
+  /// [ThemeData.cardTheme] is used. If that's null, the default value is 1.0.
+  final double? elevation;
+
+  /// The shape of the card's [Material].
+  ///
+  /// Defines the card's [Material.shape].
+  ///
+  /// If this property is null then [CardTheme.shape] of [ThemeData.cardTheme]
+  /// is used. If that's null then the shape will be a [RoundedRectangleBorder]
+  /// with a circular corner radius of 4.0.
+  final ShapeBorder? shape;
+
+  /// Whether to paint the [shape] border in front of the [child].
+  ///
+  /// The default value is true.
+  /// If false, the border will be painted behind the [child].
+  final bool borderOnForeground;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// If this property is null then [CardTheme.clipBehavior] of
+  /// [ThemeData.cardTheme] is used. If that's null then the behavior will be [Clip.none].
+  final Clip? clipBehavior;
+
+  /// The empty space that surrounds the card.
+  ///
+  /// Defines the card's outer [Container.margin].
+  ///
+  /// If this property is null then [CardTheme.margin] of
+  /// [ThemeData.cardTheme] is used. If that's null, the default margin is 4.0
+  /// logical pixels on all sides: `EdgeInsets.all(4.0)`.
+  final EdgeInsetsGeometry? margin;
+
+  /// Whether this widget represents a single semantic container, or if false
+  /// a collection of individual semantic nodes.
+  ///
+  /// Defaults to true.
+  ///
+  /// Setting this flag to true will attempt to merge all child semantics into
+  /// this node. Setting this flag to false will force all child semantic nodes
+  /// to be explicit.
+  ///
+  /// This flag should be false if the card contains multiple different types
+  /// of content.
+  final bool semanticContainer;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  static const double _defaultElevation = 1.0;
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final CardTheme cardTheme = CardTheme.of(context);
+
+    return Semantics(
+      container: semanticContainer,
+      child: Container(
+        margin: margin ?? cardTheme.margin ?? const EdgeInsets.all(4.0),
+        child: Material(
+          type: MaterialType.card,
+          shadowColor: shadowColor ?? cardTheme.shadowColor ?? theme.shadowColor,
+          color: color ?? cardTheme.color ?? theme.cardColor,
+          elevation: elevation ?? cardTheme.elevation ?? _defaultElevation,
+          shape: shape ?? cardTheme.shape ?? const RoundedRectangleBorder(
+            borderRadius: BorderRadius.all(Radius.circular(4.0)),
+          ),
+          borderOnForeground: borderOnForeground,
+          clipBehavior: clipBehavior ?? cardTheme.clipBehavior ?? Clip.none,
+          child: Semantics(
+            explicitChildNodes: !semanticContainer,
+            child: child,
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/card_theme.dart b/lib/src/material/card_theme.dart
new file mode 100644
index 0000000..8a7b71e
--- /dev/null
+++ b/lib/src/material/card_theme.dart
@@ -0,0 +1,155 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'theme.dart';
+
+/// Defines default property values for descendant [Card] widgets.
+///
+/// Descendant widgets obtain the current [CardTheme] object using
+/// `CardTheme.of(context)`. Instances of [CardTheme] can be
+/// customized with [CardTheme.copyWith].
+///
+/// Typically a [CardTheme] is specified as part of the overall [Theme]
+/// with [ThemeData.cardTheme].
+///
+/// All [CardTheme] properties are `null` by default. When null, the [Card]
+/// will use the values from [ThemeData] if they exist, otherwise it will
+/// provide its own defaults.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class CardTheme with Diagnosticable {
+
+  /// Creates a theme that can be used for [ThemeData.cardTheme].
+  ///
+  /// The [elevation] must be null or non-negative.
+  const CardTheme({
+    this.clipBehavior,
+    this.color,
+    this.shadowColor,
+    this.elevation,
+    this.margin,
+    this.shape,
+  }) : assert(elevation == null || elevation >= 0.0);
+
+  /// Default value for [Card.clipBehavior].
+  ///
+  /// If null, [Card] uses [Clip.none].
+  final Clip? clipBehavior;
+
+  /// Default value for [Card.color].
+  ///
+  /// If null, [Card] uses [ThemeData.cardColor].
+  final Color? color;
+
+  /// Default value for [Card.shadowColor].
+  ///
+  /// If null, [Card] defaults to fully opaque black.
+  final Color? shadowColor;
+
+  /// Default value for [Card.elevation].
+  ///
+  /// If null, [Card] uses a default of 1.0.
+  final double? elevation;
+
+  /// Default value for [Card.margin].
+  ///
+  /// If null, [Card] uses a default margin of 4.0 logical pixels on all sides:
+  /// `EdgeInsets.all(4.0)`.
+  final EdgeInsetsGeometry? margin;
+
+  /// Default value for [Card.shape].
+  ///
+  /// If null, [Card] then uses a [RoundedRectangleBorder] with a circular
+  /// corner radius of 4.0.
+  final ShapeBorder? shape;
+
+  /// Creates a copy of this object with the given fields replaced with the
+  /// new values.
+  CardTheme copyWith({
+    Clip? clipBehavior,
+    Color? color,
+    Color? shadowColor,
+    double? elevation,
+    EdgeInsetsGeometry? margin,
+    ShapeBorder? shape,
+  }) {
+    return CardTheme(
+      clipBehavior: clipBehavior ?? this.clipBehavior,
+      color: color ?? this.color,
+      shadowColor: shadowColor ?? this.shadowColor,
+      elevation: elevation ?? this.elevation,
+      margin: margin ?? this.margin,
+      shape: shape ?? this.shape,
+    );
+  }
+
+  /// The [ThemeData.cardTheme] property of the ambient [Theme].
+  static CardTheme of(BuildContext context) {
+    return Theme.of(context).cardTheme;
+  }
+
+  /// Linearly interpolate between two Card themes.
+  ///
+  /// The argument `t` must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static CardTheme lerp(CardTheme? a, CardTheme? b, double t) {
+    assert(t != null);
+    return CardTheme(
+      clipBehavior: t < 0.5 ? a?.clipBehavior : b?.clipBehavior,
+      color: Color.lerp(a?.color, b?.color, t),
+      shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
+      elevation: lerpDouble(a?.elevation, b?.elevation, t),
+      margin: EdgeInsetsGeometry.lerp(a?.margin, b?.margin, t),
+      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      clipBehavior,
+      color,
+      shadowColor,
+      elevation,
+      margin,
+      shape,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is CardTheme
+        && other.clipBehavior == clipBehavior
+        && other.color == color
+        && other.shadowColor == shadowColor
+        && other.elevation == elevation
+        && other.margin == margin
+        && other.shape == shape;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: null));
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
+  }
+}
diff --git a/lib/src/material/checkbox.dart b/lib/src/material/checkbox.dart
new file mode 100644
index 0000000..6c832de
--- /dev/null
+++ b/lib/src/material/checkbox.dart
@@ -0,0 +1,704 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'constants.dart';
+import 'debug.dart';
+import 'material_state.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+import 'toggleable.dart';
+
+/// A material design checkbox.
+///
+/// The checkbox itself does not maintain any state. Instead, when the state of
+/// the checkbox changes, the widget calls the [onChanged] callback. Most
+/// widgets that use a checkbox will listen for the [onChanged] callback and
+/// rebuild the checkbox with a new [value] to update the visual appearance of
+/// the checkbox.
+///
+/// The checkbox can optionally display three values - true, false, and null -
+/// if [tristate] is true. When [value] is null a dash is displayed. By default
+/// [tristate] is false and the checkbox's [value] must be true or false.
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// See also:
+///
+///  * [CheckboxListTile], which combines this widget with a [ListTile] so that
+///    you can give the checkbox a label.
+///  * [Switch], a widget with semantics similar to [Checkbox].
+///  * [Radio], for selecting among a set of explicit values.
+///  * [Slider], for selecting a value in a range.
+///  * <https://material.io/design/components/selection-controls.html#checkboxes>
+///  * <https://material.io/design/components/lists.html#types>
+class Checkbox extends StatefulWidget {
+  /// Creates a material design checkbox.
+  ///
+  /// The checkbox itself does not maintain any state. Instead, when the state of
+  /// the checkbox changes, the widget calls the [onChanged] callback. Most
+  /// widgets that use a checkbox will listen for the [onChanged] callback and
+  /// rebuild the checkbox with a new [value] to update the visual appearance of
+  /// the checkbox.
+  ///
+  /// The following arguments are required:
+  ///
+  /// * [value], which determines whether the checkbox is checked. The [value]
+  ///   can only be null if [tristate] is true.
+  /// * [onChanged], which is called when the value of the checkbox should
+  ///   change. It can be set to null to disable the checkbox.
+  ///
+  /// The values of [tristate] and [autofocus] must not be null.
+  const Checkbox({
+    Key? key,
+    required this.value,
+    this.tristate = false,
+    required this.onChanged,
+    this.mouseCursor,
+    this.activeColor,
+    this.fillColor,
+    this.checkColor,
+    this.focusColor,
+    this.hoverColor,
+    this.overlayColor,
+    this.splashRadius,
+    this.materialTapTargetSize,
+    this.visualDensity,
+    this.focusNode,
+    this.autofocus = false,
+  }) : assert(tristate != null),
+       assert(tristate || value != null),
+       assert(autofocus != null),
+       super(key: key);
+
+  /// Whether this checkbox is checked.
+  ///
+  /// This property must not be null.
+  final bool? value;
+
+  /// Called when the value of the checkbox should change.
+  ///
+  /// The checkbox passes the new value to the callback but does not actually
+  /// change state until the parent widget rebuilds the checkbox with the new
+  /// value.
+  ///
+  /// If this callback is null, the checkbox will be displayed as disabled
+  /// and will not respond to input gestures.
+  ///
+  /// When the checkbox is tapped, if [tristate] is false (the default) then
+  /// the [onChanged] callback will be applied to `!value`. If [tristate] is
+  /// true this callback cycle from false to true to null.
+  ///
+  /// The callback provided to [onChanged] should update the state of the parent
+  /// [StatefulWidget] using the [State.setState] method, so that the parent
+  /// gets rebuilt; for example:
+  ///
+  /// ```dart
+  /// Checkbox(
+  ///   value: _throwShotAway,
+  ///   onChanged: (bool newValue) {
+  ///     setState(() {
+  ///       _throwShotAway = newValue;
+  ///     });
+  ///   },
+  /// )
+  /// ```
+  final ValueChanged<bool?>? onChanged;
+
+  /// {@template flutter.material.checkbox.mouseCursor}
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  /// {@endtemplate}
+  ///
+  /// When [value] is null and [tristate] is true, [MaterialState.selected] is
+  /// included as a state.
+  ///
+  /// If null, then the value of [CheckboxThemeData.mouseCursor] is used. If
+  /// that is also null, then [MaterialStateMouseCursor.clickable] is used.
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialStateMouseCursor], a [MouseCursor] that implements
+  ///    `MaterialStateProperty` which is used in APIs that need to accept
+  ///    either a [MouseCursor] or a [MaterialStateProperty<MouseCursor>].
+  final MouseCursor? mouseCursor;
+
+  /// The color to use when this checkbox is checked.
+  ///
+  /// Defaults to [ThemeData.toggleableActiveColor].
+  ///
+  /// If [fillColor] returns a non-null color in the [MaterialState.selected]
+  /// state, it will be used instead of this color.
+  final Color? activeColor;
+
+  /// {@template flutter.material.checkbox.fillColor}
+  /// The color that fills the checkbox, in all [MaterialState]s.
+  ///
+  /// Resolves in the following states:
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [activeColor] is used in the selected
+  /// state. If that is also null, the value of [CheckboxThemeData.fillColor]
+  /// is used. If that is also null, then [ThemeData.disabledColor] is used in
+  /// the disabled state, [ThemeData.toggleableActiveColor] is used in the
+  /// selected state, and [ThemeData.unselectedWidgetColor] is used in the
+  /// default state.
+  final MaterialStateProperty<Color?>? fillColor;
+
+  /// {@template flutter.material.checkbox.checkColor}
+  /// The color to use for the check icon when this checkbox is checked.
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [CheckboxThemeData.checkColor] is used. If
+  /// that is also null, then Color(0xFFFFFFFF) is used.
+  final Color? checkColor;
+
+  /// If true the checkbox's [value] can be true, false, or null.
+  ///
+  /// Checkbox displays a dash when its value is null.
+  ///
+  /// When a tri-state checkbox ([tristate] is true) is tapped, its [onChanged]
+  /// callback will be applied to true if the current value is false, to null if
+  /// value is true, and to false if value is null (i.e. it cycles through false
+  /// => true => null => false when tapped).
+  ///
+  /// If tristate is false (the default), [value] must not be null.
+  final bool tristate;
+
+  /// {@template flutter.material.checkbox.materialTapTargetSize}
+  /// Configures the minimum size of the tap target.
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [CheckboxThemeData.materialTapTargetSize] is
+  /// used. If that is also null, then the value of
+  /// [ThemeData.materialTapTargetSize] is used.
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialTapTargetSize], for a description of how this affects tap targets.
+  final MaterialTapTargetSize? materialTapTargetSize;
+
+  /// {@template flutter.material.checkbox.visualDensity}
+  /// Defines how compact the checkbox's layout will be.
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.material.themedata.visualDensity}
+  ///
+  /// If null, then the value of [CheckboxThemeData.visualDensity] is used. If
+  /// that is also null, then the value of [ThemeData.visualDensity] is used.
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.visualDensity], which specifies the [visualDensity] for all
+  ///    widgets within a [Theme].
+  final VisualDensity? visualDensity;
+
+  /// The color for the checkbox's [Material] when it has the input focus.
+  ///
+  /// If [overlayColor] returns a non-null color in the [MaterialState.focused]
+  /// state, it will be used instead.
+  ///
+  /// If null, then the value of [CheckboxThemeData.overlayColor] is used in the
+  /// focused state. If that is also null, then the value of
+  /// [ThemeData.focusColor] is used.
+  final Color? focusColor;
+
+  /// The color for the checkbox's [Material] when a pointer is hovering over it.
+  ///
+  /// If [overlayColor] returns a non-null color in the [MaterialState.hovered]
+  /// state, it will be used instead.
+  ///
+  /// If null, then the value of [CheckboxThemeData.overlayColor] is used in the
+  /// hovered state. If that is also null, then the value of
+  /// [ThemeData.hoverColor] is used.
+  final Color? hoverColor;
+
+  /// {@template flutter.material.checkbox.overlayColor}
+  /// The color for the checkbox's [Material].
+  ///
+  /// Resolves in the following states:
+  ///  * [MaterialState.pressed].
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [activeColor] with alpha
+  /// [kRadialReactionAlpha], [focusColor] and [hoverColor] is used in the
+  /// pressed, focused and hovered state. If that is also null,
+  /// the value of [CheckboxThemeData.overlayColor] is used. If that is
+  /// also null, then the value of [ThemeData.toggleableActiveColor] with alpha
+  /// [kRadialReactionAlpha], [ThemeData.focusColor] and [ThemeData.hoverColor]
+  /// is used in the pressed, focused and hovered state.
+  final MaterialStateProperty<Color?>? overlayColor;
+
+  /// {@template flutter.material.checkbox.splashRadius}
+  /// The splash radius of the circular [Material] ink response.
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [CheckboxThemeData.splashRadius] is used. If
+  /// that is also null, then [kRadialReactionRadius] is used.
+  final double? splashRadius;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  /// The width of a checkbox widget.
+  static const double width = 18.0;
+
+  @override
+  _CheckboxState createState() => _CheckboxState();
+}
+
+class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
+  bool get enabled => widget.onChanged != null;
+  late Map<Type, Action<Intent>> _actionMap;
+
+  @override
+  void initState() {
+    super.initState();
+    _actionMap = <Type, Action<Intent>>{
+      ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: _actionHandler),
+    };
+  }
+
+  void _actionHandler(ActivateIntent intent) {
+    if (widget.onChanged != null) {
+      switch (widget.value) {
+        case false:
+          widget.onChanged!(true);
+          break;
+        case true:
+          widget.onChanged!(widget.tristate ? null : false);
+          break;
+        case null:
+          widget.onChanged!(false);
+          break;
+      }
+    }
+    final RenderObject renderObject = context.findRenderObject()!;
+    renderObject.sendSemanticsEvent(const TapSemanticEvent());
+  }
+
+  bool _focused = false;
+  void _handleFocusHighlightChanged(bool focused) {
+    if (focused != _focused) {
+      setState(() { _focused = focused; });
+    }
+  }
+
+  bool _hovering = false;
+  void _handleHoverChanged(bool hovering) {
+    if (hovering != _hovering) {
+      setState(() { _hovering = hovering; });
+    }
+  }
+
+  Set<MaterialState> get _states => <MaterialState>{
+    if (!enabled) MaterialState.disabled,
+    if (_hovering) MaterialState.hovered,
+    if (_focused) MaterialState.focused,
+    if (widget.value == null || widget.value!) MaterialState.selected,
+  };
+
+  MaterialStateProperty<Color?> get _widgetFillColor {
+    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.disabled)) {
+        return null;
+      }
+      if (states.contains(MaterialState.selected)) {
+        return widget.activeColor;
+      }
+      return null;
+    });
+  }
+
+  MaterialStateProperty<Color> get _defaultFillColor {
+    final ThemeData themeData = Theme.of(context);
+    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.disabled)) {
+        return themeData.disabledColor;
+      }
+      if (states.contains(MaterialState.selected)) {
+        return themeData.toggleableActiveColor;
+      }
+      return themeData.unselectedWidgetColor;
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    final ThemeData themeData = Theme.of(context);
+    final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize
+      ?? themeData.checkboxTheme.materialTapTargetSize
+      ?? themeData.materialTapTargetSize;
+    final VisualDensity effectiveVisualDensity = widget.visualDensity
+      ?? themeData.checkboxTheme.visualDensity
+      ?? themeData.visualDensity;
+    Size size;
+    switch (effectiveMaterialTapTargetSize) {
+      case MaterialTapTargetSize.padded:
+        size = const Size(kMinInteractiveDimension, kMinInteractiveDimension);
+        break;
+      case MaterialTapTargetSize.shrinkWrap:
+        size = const Size(kMinInteractiveDimension - 8.0, kMinInteractiveDimension - 8.0);
+        break;
+    }
+    size += effectiveVisualDensity.baseSizeAdjustment;
+    final BoxConstraints additionalConstraints = BoxConstraints.tight(size);
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, _states)
+      ?? themeData.checkboxTheme.mouseCursor?.resolve(_states)
+      ?? MaterialStateProperty.resolveAs<MouseCursor>(MaterialStateMouseCursor.clickable, _states);
+    // Colors need to be resolved in selected and non selected states separately
+    // so that they can be lerped between.
+    final Set<MaterialState> activeStates = _states..add(MaterialState.selected);
+    final Set<MaterialState> inactiveStates = _states..remove(MaterialState.selected);
+    final Color effectiveActiveColor = widget.fillColor?.resolve(activeStates)
+      ?? _widgetFillColor.resolve(activeStates)
+      ?? themeData.checkboxTheme.fillColor?.resolve(activeStates)
+      ?? _defaultFillColor.resolve(activeStates);
+    final Color effectiveInactiveColor = widget.fillColor?.resolve(inactiveStates)
+      ?? _widgetFillColor.resolve(inactiveStates)
+      ?? themeData.checkboxTheme.fillColor?.resolve(inactiveStates)
+      ?? _defaultFillColor.resolve(inactiveStates);
+
+    final Set<MaterialState> focusedStates = _states..add(MaterialState.focused);
+    final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
+      ?? widget.focusColor
+      ?? themeData.checkboxTheme.overlayColor?.resolve(focusedStates)
+      ?? themeData.focusColor;
+
+    final Set<MaterialState> hoveredStates = _states..add(MaterialState.hovered);
+    final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
+        ?? widget.hoverColor
+        ?? themeData.checkboxTheme.overlayColor?.resolve(hoveredStates)
+        ?? themeData.hoverColor;
+
+    final Set<MaterialState> activePressedStates = activeStates..add(MaterialState.pressed);
+    final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates)
+        ?? themeData.checkboxTheme.overlayColor?.resolve(activePressedStates)
+        ?? effectiveActiveColor.withAlpha(kRadialReactionAlpha);
+
+    final Set<MaterialState> inactivePressedStates = inactiveStates..add(MaterialState.pressed);
+    final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates)
+        ?? themeData.checkboxTheme.overlayColor?.resolve(inactivePressedStates)
+        ?? effectiveActiveColor.withAlpha(kRadialReactionAlpha);
+
+    final Color effectiveCheckColor =  widget.checkColor
+      ?? themeData.checkboxTheme.checkColor?.resolve(_states)
+      ?? const Color(0xFFFFFFFF);
+
+    return FocusableActionDetector(
+      actions: _actionMap,
+      focusNode: widget.focusNode,
+      autofocus: widget.autofocus,
+      enabled: enabled,
+      onShowFocusHighlight: _handleFocusHighlightChanged,
+      onShowHoverHighlight: _handleHoverChanged,
+      mouseCursor: effectiveMouseCursor,
+      child: Builder(
+        builder: (BuildContext context) {
+          return _CheckboxRenderObjectWidget(
+            value: widget.value,
+            tristate: widget.tristate,
+            activeColor: effectiveActiveColor,
+            checkColor: effectiveCheckColor,
+            inactiveColor: effectiveInactiveColor,
+            focusColor: effectiveFocusOverlayColor,
+            hoverColor: effectiveHoverOverlayColor,
+            reactionColor: effectiveActivePressedOverlayColor,
+            inactiveReactionColor: effectiveInactivePressedOverlayColor,
+            splashRadius: widget.splashRadius ?? themeData.checkboxTheme.splashRadius ?? kRadialReactionRadius,
+            onChanged: widget.onChanged,
+            additionalConstraints: additionalConstraints,
+            vsync: this,
+            hasFocus: _focused,
+            hovering: _hovering,
+          );
+        },
+      ),
+    );
+  }
+}
+
+class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
+  const _CheckboxRenderObjectWidget({
+    Key? key,
+    required this.value,
+    required this.tristate,
+    required this.activeColor,
+    required this.checkColor,
+    required this.inactiveColor,
+    required this.focusColor,
+    required this.hoverColor,
+    required this.reactionColor,
+    required this.inactiveReactionColor,
+    required this.splashRadius,
+    required this.onChanged,
+    required this.vsync,
+    required this.additionalConstraints,
+    required this.hasFocus,
+    required this.hovering,
+  }) : assert(tristate != null),
+       assert(tristate || value != null),
+       assert(activeColor != null),
+       assert(inactiveColor != null),
+       assert(vsync != null),
+       super(key: key);
+
+  final bool? value;
+  final bool tristate;
+  final bool hasFocus;
+  final bool hovering;
+  final Color activeColor;
+  final Color checkColor;
+  final Color inactiveColor;
+  final Color focusColor;
+  final Color hoverColor;
+  final Color reactionColor;
+  final Color inactiveReactionColor;
+  final double splashRadius;
+  final ValueChanged<bool?>? onChanged;
+  final TickerProvider vsync;
+  final BoxConstraints additionalConstraints;
+
+  @override
+  _RenderCheckbox createRenderObject(BuildContext context) => _RenderCheckbox(
+    value: value,
+    tristate: tristate,
+    activeColor: activeColor,
+    checkColor: checkColor,
+    inactiveColor: inactiveColor,
+    focusColor: focusColor,
+    hoverColor: hoverColor,
+    reactionColor: reactionColor,
+    inactiveReactionColor: inactiveReactionColor,
+    splashRadius: splashRadius,
+    onChanged: onChanged,
+    vsync: vsync,
+    additionalConstraints: additionalConstraints,
+    hasFocus: hasFocus,
+    hovering: hovering,
+  );
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderCheckbox renderObject) {
+    renderObject
+      // The `tristate` must be changed before `value` due to the assertion at
+      // the beginning of `set value`.
+      ..tristate = tristate
+      ..value = value
+      ..activeColor = activeColor
+      ..checkColor = checkColor
+      ..inactiveColor = inactiveColor
+      ..focusColor = focusColor
+      ..hoverColor = hoverColor
+      ..reactionColor = reactionColor
+      ..inactiveReactionColor = inactiveReactionColor
+      ..splashRadius = splashRadius
+      ..onChanged = onChanged
+      ..additionalConstraints = additionalConstraints
+      ..vsync = vsync
+      ..hasFocus = hasFocus
+      ..hovering = hovering;
+  }
+}
+
+const double _kEdgeSize = Checkbox.width;
+const Radius _kEdgeRadius = Radius.circular(1.0);
+const double _kStrokeWidth = 2.0;
+
+class _RenderCheckbox extends RenderToggleable {
+  _RenderCheckbox({
+    bool? value,
+    required bool tristate,
+    required Color activeColor,
+    required this.checkColor,
+    required Color inactiveColor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? reactionColor,
+    Color? inactiveReactionColor,
+    required double splashRadius,
+    required BoxConstraints additionalConstraints,
+    ValueChanged<bool?>? onChanged,
+    required bool hasFocus,
+    required bool hovering,
+    required TickerProvider vsync,
+  }) : _oldValue = value,
+       super(
+         value: value,
+         tristate: tristate,
+         activeColor: activeColor,
+         inactiveColor: inactiveColor,
+         focusColor: focusColor,
+         hoverColor: hoverColor,
+         reactionColor: reactionColor,
+         inactiveReactionColor: inactiveReactionColor,
+         splashRadius: splashRadius,
+         onChanged: onChanged,
+         additionalConstraints: additionalConstraints,
+         vsync: vsync,
+         hasFocus: hasFocus,
+         hovering: hovering,
+       );
+
+  bool? _oldValue;
+  Color checkColor;
+
+  @override
+  set value(bool? newValue) {
+    if (newValue == value)
+      return;
+    _oldValue = value;
+    super.value = newValue;
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    config.isChecked = value == true;
+  }
+
+  // The square outer bounds of the checkbox at t, with the specified origin.
+  // At t == 0.0, the outer rect's size is _kEdgeSize (Checkbox.width)
+  // At t == 0.5, .. is _kEdgeSize - _kStrokeWidth
+  // At t == 1.0, .. is _kEdgeSize
+  RRect _outerRectAt(Offset origin, double t) {
+    final double inset = 1.0 - (t - 0.5).abs() * 2.0;
+    final double size = _kEdgeSize - inset * _kStrokeWidth;
+    final Rect rect = Rect.fromLTWH(origin.dx + inset, origin.dy + inset, size, size);
+    return RRect.fromRectAndRadius(rect, _kEdgeRadius);
+  }
+
+  // The checkbox's border color if value == false, or its fill color when
+  // value == true or null.
+  Color _colorAt(double t) {
+    // As t goes from 0.0 to 0.25, animate from the inactiveColor to activeColor.
+    return t >= 0.25 ? activeColor : Color.lerp(inactiveColor, activeColor, t * 4.0)!;
+  }
+
+  // White stroke used to paint the check and dash.
+  Paint _createStrokePaint() {
+    return Paint()
+      ..color = checkColor
+      ..style = PaintingStyle.stroke
+      ..strokeWidth = _kStrokeWidth;
+  }
+
+  void _drawBorder(Canvas canvas, RRect outer, double t, Paint paint) {
+    assert(t >= 0.0 && t <= 0.5);
+    final double size = outer.width;
+    // As t goes from 0.0 to 1.0, gradually fill the outer RRect.
+    final RRect inner = outer.deflate(math.min(size / 2.0, _kStrokeWidth + size * t));
+    canvas.drawDRRect(outer, inner, paint);
+  }
+
+  void _drawCheck(Canvas canvas, Offset origin, double t, Paint paint) {
+    assert(t >= 0.0 && t <= 1.0);
+    // As t goes from 0.0 to 1.0, animate the two check mark strokes from the
+    // short side to the long side.
+    final Path path = Path();
+    const Offset start = Offset(_kEdgeSize * 0.15, _kEdgeSize * 0.45);
+    const Offset mid = Offset(_kEdgeSize * 0.4, _kEdgeSize * 0.7);
+    const Offset end = Offset(_kEdgeSize * 0.85, _kEdgeSize * 0.25);
+    if (t < 0.5) {
+      final double strokeT = t * 2.0;
+      final Offset drawMid = Offset.lerp(start, mid, strokeT)!;
+      path.moveTo(origin.dx + start.dx, origin.dy + start.dy);
+      path.lineTo(origin.dx + drawMid.dx, origin.dy + drawMid.dy);
+    } else {
+      final double strokeT = (t - 0.5) * 2.0;
+      final Offset drawEnd = Offset.lerp(mid, end, strokeT)!;
+      path.moveTo(origin.dx + start.dx, origin.dy + start.dy);
+      path.lineTo(origin.dx + mid.dx, origin.dy + mid.dy);
+      path.lineTo(origin.dx + drawEnd.dx, origin.dy + drawEnd.dy);
+    }
+    canvas.drawPath(path, paint);
+  }
+
+  void _drawDash(Canvas canvas, Offset origin, double t, Paint paint) {
+    assert(t >= 0.0 && t <= 1.0);
+    // As t goes from 0.0 to 1.0, animate the horizontal line from the
+    // mid point outwards.
+    const Offset start = Offset(_kEdgeSize * 0.2, _kEdgeSize * 0.5);
+    const Offset mid = Offset(_kEdgeSize * 0.5, _kEdgeSize * 0.5);
+    const Offset end = Offset(_kEdgeSize * 0.8, _kEdgeSize * 0.5);
+    final Offset drawStart = Offset.lerp(start, mid, 1.0 - t)!;
+    final Offset drawEnd = Offset.lerp(mid, end, t)!;
+    canvas.drawLine(origin + drawStart, origin + drawEnd, paint);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final Canvas canvas = context.canvas;
+    paintRadialReaction(canvas, offset, size.center(Offset.zero));
+
+    final Paint strokePaint = _createStrokePaint();
+    final Offset origin = offset + (size / 2.0 - const Size.square(_kEdgeSize) / 2.0 as Offset);
+    final AnimationStatus status = position.status;
+    final double tNormalized = status == AnimationStatus.forward || status == AnimationStatus.completed
+      ? position.value
+      : 1.0 - position.value;
+
+    // Four cases: false to null, false to true, null to false, true to false
+    if (_oldValue == false || value == false) {
+      final double t = value == false ? 1.0 - tNormalized : tNormalized;
+      final RRect outer = _outerRectAt(origin, t);
+      final Paint paint = Paint()..color = _colorAt(t);
+
+      if (t <= 0.5) {
+        _drawBorder(canvas, outer, t, paint);
+      } else {
+        canvas.drawRRect(outer, paint);
+
+        final double tShrink = (t - 0.5) * 2.0;
+        if (_oldValue == null || value == null)
+          _drawDash(canvas, origin, tShrink, strokePaint);
+        else
+          _drawCheck(canvas, origin, tShrink, strokePaint);
+      }
+    } else { // Two cases: null to true, true to null
+      final RRect outer = _outerRectAt(origin, 1.0);
+      final Paint paint = Paint() ..color = _colorAt(1.0);
+      canvas.drawRRect(outer, paint);
+
+      if (tNormalized <= 0.5) {
+        final double tShrink = 1.0 - tNormalized * 2.0;
+        if (_oldValue == true)
+          _drawCheck(canvas, origin, tShrink, strokePaint);
+        else
+          _drawDash(canvas, origin, tShrink, strokePaint);
+      } else {
+        final double tExpand = (tNormalized - 0.5) * 2.0;
+        if (value == true)
+          _drawCheck(canvas, origin, tExpand, strokePaint);
+        else
+          _drawDash(canvas, origin, tExpand, strokePaint);
+      }
+    }
+  }
+}
diff --git a/lib/src/material/checkbox_list_tile.dart b/lib/src/material/checkbox_list_tile.dart
new file mode 100644
index 0000000..d7da60d
--- /dev/null
+++ b/lib/src/material/checkbox_list_tile.dart
@@ -0,0 +1,456 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'checkbox.dart';
+import 'list_tile.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// void setState(VoidCallback fn) { }
+
+/// A [ListTile] with a [Checkbox]. In other words, a checkbox with a label.
+///
+/// The entire list tile is interactive: tapping anywhere in the tile toggles
+/// the checkbox.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=RkSqPAn9szs}
+///
+/// The [value], [onChanged], [activeColor] and [checkColor] properties of this widget are
+/// identical to the similarly-named properties on the [Checkbox] widget.
+///
+/// The [title], [subtitle], [isThreeLine], [dense], and [contentPadding] properties are like
+/// those of the same name on [ListTile].
+///
+/// The [selected] property on this widget is similar to the [ListTile.selected]
+/// property, but the color used is that described by [activeColor], if any,
+/// defaulting to the accent color of the current [Theme]. No effort is made to
+/// coordinate the [selected] state and the [value] state; to have the list tile
+/// appear selected when the checkbox is checked, pass the same value to both.
+///
+/// The checkbox is shown on the right by default in left-to-right languages
+/// (i.e. the trailing edge). This can be changed using [controlAffinity]. The
+/// [secondary] widget is placed on the opposite side. This maps to the
+/// [ListTile.leading] and [ListTile.trailing] properties of [ListTile].
+///
+/// To show the [CheckboxListTile] as disabled, pass null as the [onChanged]
+/// callback.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+///
+/// ![CheckboxListTile sample](https://flutter.github.io/assets-for-api-docs/assets/material/checkbox_list_tile.png)
+///
+/// This widget shows a checkbox that, when checked, slows down all animations
+/// (including the animation of the checkbox itself getting checked!).
+///
+/// This sample requires that you also import 'package:flute/scheduler.dart',
+/// so that you can reference [timeDilation].
+///
+/// ```dart imports
+/// import 'package:flute/scheduler.dart' show timeDilation;
+/// ```
+/// ```dart
+/// @override
+/// Widget build(BuildContext context) {
+///   return CheckboxListTile(
+///     title: const Text('Animate Slowly'),
+///     value: timeDilation != 1.0,
+///     onChanged: (bool value) {
+///       setState(() { timeDilation = value ? 10.0 : 1.0; });
+///     },
+///     secondary: const Icon(Icons.hourglass_empty),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ## Semantics in CheckboxListTile
+///
+/// Since the entirety of the CheckboxListTile is interactive, it should represent
+/// itself as a single interactive entity.
+///
+/// To do so, a CheckboxListTile widget wraps its children with a [MergeSemantics]
+/// widget. [MergeSemantics] will attempt to merge its descendant [Semantics]
+/// nodes into one node in the semantics tree. Therefore, CheckboxListTile will
+/// throw an error if any of its children requires its own [Semantics] node.
+///
+/// For example, you cannot nest a [RichText] widget as a descendant of
+/// CheckboxListTile. [RichText] has an embedded gesture recognizer that
+/// requires its own [Semantics] node, which directly conflicts with
+/// CheckboxListTile's desire to merge all its descendants' semantic nodes
+/// into one. Therefore, it may be necessary to create a custom radio tile
+/// widget to accommodate similar use cases.
+///
+/// {@tool sample --template=stateful_widget_scaffold_center_no_null_safety}
+///
+/// ![Checkbox list tile semantics sample](https://flutter.github.io/assets-for-api-docs/assets/material/checkbox_list_tile_semantics.png)
+///
+/// Here is an example of a custom labeled checkbox widget, called
+/// LinkedLabelCheckbox, that includes an interactive [RichText] widget that
+/// handles tap gestures.
+///
+/// ```dart imports
+/// import 'package:flute/gestures.dart';
+/// ```
+/// ```dart preamble
+/// class LinkedLabelCheckbox extends StatelessWidget {
+///   const LinkedLabelCheckbox({
+///     this.label,
+///     this.padding,
+///     this.value,
+///     this.onChanged,
+///   });
+///
+///   final String label;
+///   final EdgeInsets padding;
+///   final bool value;
+///   final Function onChanged;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Padding(
+///       padding: padding,
+///       child: Row(
+///         children: <Widget>[
+///           Expanded(
+///             child: RichText(
+///               text: TextSpan(
+///                 text: label,
+///                 style: TextStyle(
+///                   color: Colors.blueAccent,
+///                   decoration: TextDecoration.underline,
+///                 ),
+///                 recognizer: TapGestureRecognizer()
+///                   ..onTap = () {
+///                   print('Label has been tapped.');
+///                 },
+///               ),
+///             ),
+///           ),
+///           Checkbox(
+///             value: value,
+///             onChanged: (bool newValue) {
+///               onChanged(newValue);
+///             },
+///           ),
+///         ],
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// ```dart
+/// bool _isSelected = false;
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return LinkedLabelCheckbox(
+///     label: 'Linked, tappable label text',
+///     padding: const EdgeInsets.symmetric(horizontal: 20.0),
+///     value: _isSelected,
+///     onChanged: (bool newValue) {
+///       setState(() {
+///         _isSelected = newValue;
+///       });
+///     },
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ## CheckboxListTile isn't exactly what I want
+///
+/// If the way CheckboxListTile pads and positions its elements isn't quite
+/// what you're looking for, you can create custom labeled checkbox widgets by
+/// combining [Checkbox] with other widgets, such as [Text], [Padding] and
+/// [InkWell].
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+///
+/// ![Custom checkbox list tile sample](https://flutter.github.io/assets-for-api-docs/assets/material/checkbox_list_tile_custom.png)
+///
+/// Here is an example of a custom LabeledCheckbox widget, but you can easily
+/// make your own configurable widget.
+///
+/// ```dart preamble
+/// class LabeledCheckbox extends StatelessWidget {
+///   const LabeledCheckbox({
+///     this.label,
+///     this.padding,
+///     this.value,
+///     this.onChanged,
+///   });
+///
+///   final String label;
+///   final EdgeInsets padding;
+///   final bool value;
+///   final Function onChanged;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return InkWell(
+///       onTap: () {
+///         onChanged(!value);
+///       },
+///       child: Padding(
+///         padding: padding,
+///         child: Row(
+///           children: <Widget>[
+///             Expanded(child: Text(label)),
+///             Checkbox(
+///               value: value,
+///               onChanged: (bool newValue) {
+///                 onChanged(newValue);
+///               },
+///             ),
+///           ],
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// ```dart
+/// bool _isSelected = false;
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return LabeledCheckbox(
+///     label: 'This is the label text',
+///     padding: const EdgeInsets.symmetric(horizontal: 20.0),
+///     value: _isSelected,
+///     onChanged: (bool newValue) {
+///       setState(() {
+///         _isSelected = newValue;
+///       });
+///     },
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ListTileTheme], which can be used to affect the style of list tiles,
+///    including checkbox list tiles.
+///  * [RadioListTile], a similar widget for radio buttons.
+///  * [SwitchListTile], a similar widget for switches.
+///  * [ListTile] and [Checkbox], the widgets from which this widget is made.
+class CheckboxListTile extends StatelessWidget {
+  /// Creates a combination of a list tile and a checkbox.
+  ///
+  /// The checkbox tile itself does not maintain any state. Instead, when the
+  /// state of the checkbox changes, the widget calls the [onChanged] callback.
+  /// Most widgets that use a checkbox will listen for the [onChanged] callback
+  /// and rebuild the checkbox tile with a new [value] to update the visual
+  /// appearance of the checkbox.
+  ///
+  /// The following arguments are required:
+  ///
+  /// * [value], which determines whether the checkbox is checked. The [value]
+  ///   can only be null if [tristate] is true.
+  /// * [onChanged], which is called when the value of the checkbox should
+  ///   change. It can be set to null to disable the checkbox.
+  ///
+  /// The value of [tristate] must not be null.
+  const CheckboxListTile({
+    Key? key,
+    required this.value,
+    required this.onChanged,
+    this.activeColor,
+    this.checkColor,
+    this.tileColor,
+    this.title,
+    this.subtitle,
+    this.isThreeLine = false,
+    this.dense,
+    this.secondary,
+    this.selected = false,
+    this.controlAffinity = ListTileControlAffinity.platform,
+    this.autofocus = false,
+    this.contentPadding,
+    this.tristate = false,
+    this.shape,
+    this.selectedTileColor,
+  }) : assert(tristate != null),
+       assert(tristate || value != null),
+       assert(isThreeLine != null),
+       assert(!isThreeLine || subtitle != null),
+       assert(selected != null),
+       assert(controlAffinity != null),
+       assert(autofocus != null),
+       super(key: key);
+
+  /// Whether this checkbox is checked.
+  final bool? value;
+
+  /// Called when the value of the checkbox should change.
+  ///
+  /// The checkbox passes the new value to the callback but does not actually
+  /// change state until the parent widget rebuilds the checkbox tile with the
+  /// new value.
+  ///
+  /// If null, the checkbox will be displayed as disabled.
+  ///
+  /// The callback provided to [onChanged] should update the state of the parent
+  /// [StatefulWidget] using the [State.setState] method, so that the parent
+  /// gets rebuilt; for example:
+  ///
+  /// ```dart
+  /// CheckboxListTile(
+  ///   value: _throwShotAway,
+  ///   onChanged: (bool newValue) {
+  ///     setState(() {
+  ///       _throwShotAway = newValue;
+  ///     });
+  ///   },
+  ///   title: Text('Throw away your shot'),
+  /// )
+  /// ```
+  final ValueChanged<bool?>? onChanged;
+
+  /// The color to use when this checkbox is checked.
+  ///
+  /// Defaults to accent color of the current [Theme].
+  final Color? activeColor;
+
+  /// The color to use for the check icon when this checkbox is checked.
+  ///
+  /// Defaults to Color(0xFFFFFFFF).
+  final Color? checkColor;
+
+  /// {@macro flutter.material.ListTile.tileColor}
+  final Color? tileColor;
+
+  /// The primary content of the list tile.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? title;
+
+  /// Additional content displayed below the title.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? subtitle;
+
+  /// A widget to display on the opposite side of the tile from the checkbox.
+  ///
+  /// Typically an [Icon] widget.
+  final Widget? secondary;
+
+  /// Whether this list tile is intended to display three lines of text.
+  ///
+  /// If false, the list tile is treated as having one line if the subtitle is
+  /// null and treated as having two lines if the subtitle is non-null.
+  final bool isThreeLine;
+
+  /// Whether this list tile is part of a vertically dense list.
+  ///
+  /// If this property is null then its value is based on [ListTileTheme.dense].
+  final bool? dense;
+
+  /// Whether to render icons and text in the [activeColor].
+  ///
+  /// No effort is made to automatically coordinate the [selected] state and the
+  /// [value] state. To have the list tile appear selected when the checkbox is
+  /// checked, pass the same value to both.
+  ///
+  /// Normally, this property is left to its default value, false.
+  final bool selected;
+
+  /// Where to place the control relative to the text.
+  final ListTileControlAffinity controlAffinity;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  /// Defines insets surrounding the tile's contents.
+  ///
+  /// This value will surround the [Checkbox], [title], [subtitle], and [secondary]
+  /// widgets in [CheckboxListTile].
+  ///
+  /// When the value is null, the `contentPadding` is `EdgeInsets.symmetric(horizontal: 16.0)`.
+  final EdgeInsetsGeometry? contentPadding;
+
+  /// If true the checkbox's [value] can be true, false, or null.
+  ///
+  /// Checkbox displays a dash when its value is null.
+  ///
+  /// When a tri-state checkbox ([tristate] is true) is tapped, its [onChanged]
+  /// callback will be applied to true if the current value is false, to null if
+  /// value is true, and to false if value is null (i.e. it cycles through false
+  /// => true => null => false when tapped).
+  ///
+  /// If tristate is false (the default), [value] must not be null.
+  final bool tristate;
+
+  /// {@macro flutter.material.ListTileTheme.shape}
+  final ShapeBorder? shape;
+
+  /// If non-null, defines the background color when [CheckboxListTile.selected] is true.
+  final Color? selectedTileColor;
+
+  void _handleValueChange() {
+    assert(onChanged != null);
+    switch (value) {
+      case false:
+        onChanged!(true);
+        break;
+      case true:
+        onChanged!(tristate ? null : false);
+        break;
+      case null:
+        onChanged!(false);
+        break;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final Widget control = Checkbox(
+      value: value,
+      onChanged: onChanged,
+      activeColor: activeColor,
+      checkColor: checkColor,
+      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+      autofocus: autofocus,
+      tristate: tristate,
+    );
+    Widget? leading, trailing;
+    switch (controlAffinity) {
+      case ListTileControlAffinity.leading:
+        leading = control;
+        trailing = secondary;
+        break;
+      case ListTileControlAffinity.trailing:
+      case ListTileControlAffinity.platform:
+        leading = secondary;
+        trailing = control;
+        break;
+    }
+    return MergeSemantics(
+      child: ListTileTheme.merge(
+        selectedColor: activeColor ?? Theme.of(context).accentColor,
+        child: ListTile(
+          leading: leading,
+          title: title,
+          subtitle: subtitle,
+          trailing: trailing,
+          isThreeLine: isThreeLine,
+          dense: dense,
+          enabled: onChanged != null,
+          onTap: onChanged != null ? _handleValueChange : null,
+          selected: selected,
+          autofocus: autofocus,
+          contentPadding: contentPadding,
+          shape: shape,
+          selectedTileColor: selectedTileColor,
+          tileColor: tileColor,
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/checkbox_theme.dart b/lib/src/material/checkbox_theme.dart
new file mode 100644
index 0000000..baf8595
--- /dev/null
+++ b/lib/src/material/checkbox_theme.dart
@@ -0,0 +1,235 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'material_state.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// Defines default property values for descendant [Checkbox] widgets.
+///
+/// Descendant widgets obtain the current [CheckboxThemeData] object using
+/// `CheckboxTheme.of(context)`. Instances of [CheckboxThemeData] can be
+/// customized with [CheckboxThemeData.copyWith].
+///
+/// Typically a [CheckboxThemeData] is specified as part of the overall [Theme]
+/// with [ThemeData.checkboxTheme].
+///
+/// All [CheckboxThemeData] properties are `null` by default. When null, the
+/// [Checkbox] will use the values from [ThemeData] if they exist, otherwise it
+/// will provide its own defaults based on the overall [Theme]'s colorScheme.
+/// See the individual [Checkbox] properties for details.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class CheckboxThemeData with Diagnosticable {
+  /// Creates a theme that can be used for [ThemeData.checkboxTheme].
+  const CheckboxThemeData({
+    this.mouseCursor,
+    this.fillColor,
+    this.checkColor,
+    this.overlayColor,
+    this.splashRadius,
+    this.materialTapTargetSize,
+    this.visualDensity,
+  });
+
+  /// {@macro flutter.material.checkbox.mouseCursor}
+  ///
+  /// If specified, overrides the default value of [Checkbox.mouseCursor].
+  final MaterialStateProperty<MouseCursor?>? mouseCursor;
+
+  /// {@macro flutter.material.checkbox.fillColor}
+  ///
+  /// If specified, overrides the default value of [Checkbox.fillColor].
+  final MaterialStateProperty<Color?>? fillColor;
+
+  /// {@macro flutter.material.checkbox.checkColor}
+  ///
+  /// Resolves in the following states:
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  ///
+  /// If specified, overrides the default value of [Checkbox.checkColor].
+  final MaterialStateProperty<Color?>? checkColor;
+
+  /// {@macro flutter.material.checkbox.overlayColor}
+  ///
+  /// If specified, overrides the default value of [Checkbox.overlayColor].
+  final MaterialStateProperty<Color?>? overlayColor;
+
+  /// {@macro flutter.material.checkbox.splashRadius}
+  ///
+  /// If specified, overrides the default value of [Checkbox.splashRadius].
+  final double? splashRadius;
+
+  /// {@macro flutter.material.checkbox.materialTapTargetSize}
+  ///
+  /// If specified, overrides the default value of
+  /// [Checkbox.materialTapTargetSize].
+  final MaterialTapTargetSize? materialTapTargetSize;
+
+  /// {@macro flutter.material.checkbox.visualDensity}
+  ///
+  /// If specified, overrides the default value of [Checkbox.visualDensity].
+  final VisualDensity? visualDensity;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  CheckboxThemeData copyWith({
+    MaterialStateProperty<MouseCursor?>? mouseCursor,
+    MaterialStateProperty<Color?>? fillColor,
+    MaterialStateProperty<Color?>? checkColor,
+    MaterialStateProperty<Color?>? overlayColor,
+    double? splashRadius,
+    MaterialTapTargetSize? materialTapTargetSize,
+    VisualDensity? visualDensity,
+  }) {
+    return CheckboxThemeData(
+      mouseCursor: mouseCursor ?? this.mouseCursor,
+      fillColor: fillColor ?? this.fillColor,
+      checkColor: checkColor ?? this.checkColor,
+      overlayColor: overlayColor ?? this.overlayColor,
+      splashRadius: splashRadius ?? this.splashRadius,
+      materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
+      visualDensity: visualDensity ?? this.visualDensity,
+    );
+  }
+
+  /// Linearly interpolate between two [CheckboxThemeData]s.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static CheckboxThemeData lerp(CheckboxThemeData? a, CheckboxThemeData? b, double t) {
+    return CheckboxThemeData(
+      mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
+      fillColor: _lerpProperties<Color?>(a?.fillColor, b?.fillColor, t, Color.lerp),
+      checkColor: _lerpProperties<Color?>(a?.checkColor, b?.checkColor, t, Color.lerp),
+      overlayColor: _lerpProperties<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
+      splashRadius: lerpDouble(a?.splashRadius, b?.splashRadius, t),
+      materialTapTargetSize: t < 0.5 ? a?.materialTapTargetSize : b?.materialTapTargetSize,
+      visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      mouseCursor,
+      fillColor,
+      checkColor,
+      overlayColor,
+      splashRadius,
+      materialTapTargetSize,
+      visualDensity,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is CheckboxThemeData
+      && other.mouseCursor == mouseCursor
+      && other.fillColor == fillColor
+      && other.checkColor == checkColor
+      && other.overlayColor == overlayColor
+      && other.splashRadius == splashRadius
+      && other.materialTapTargetSize == materialTapTargetSize
+      && other.visualDensity == visualDensity;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('fillColor', fillColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('checkColor', checkColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
+    properties.add(DoubleProperty('splashRadius', splashRadius, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
+    properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
+  }
+
+  static MaterialStateProperty<T>? _lerpProperties<T>(
+    MaterialStateProperty<T>? a,
+    MaterialStateProperty<T>? b,
+    double t,
+    T Function(T?, T?, double) lerpFunction,
+  ) {
+    // Avoid creating a _LerpProperties object for a common case.
+    if (a == null && b == null)
+      return null;
+    return _LerpProperties<T>(a, b, t, lerpFunction);
+  }
+}
+
+class _LerpProperties<T> implements MaterialStateProperty<T> {
+  const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
+
+  final MaterialStateProperty<T>? a;
+  final MaterialStateProperty<T>? b;
+  final double t;
+  final T Function(T?, T?, double) lerpFunction;
+
+  @override
+  T resolve(Set<MaterialState> states) {
+    final T? resolvedA = a?.resolve(states);
+    final T? resolvedB = b?.resolve(states);
+    return lerpFunction(resolvedA, resolvedB, t);
+  }
+}
+
+/// Applies a checkbox theme to descendant [Checkbox] widgets.
+///
+/// Descendant widgets obtain the current theme's [CheckboxTheme] object using
+/// [CheckboxTheme.of]. When a widget uses [CheckboxTheme.of], it is
+/// automatically rebuilt if the theme later changes.
+///
+/// A checkbox theme can be specified as part of the overall Material theme
+/// using [ThemeData.checkboxTheme].
+///
+/// See also:
+///
+///  * [CheckboxThemeData], which describes the actual configuration of a
+///  checkbox theme.
+class CheckboxTheme extends InheritedWidget {
+  /// Constructs a checkbox theme that configures all descendant [Checkbox]
+  /// widgets.
+  const CheckboxTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : super(key: key, child: child);
+
+  /// The properties used for all descendant [Checkbox] widgets.
+  final CheckboxThemeData data;
+
+  /// Returns the configuration [data] from the closest [CheckboxTheme]
+  /// ancestor. If there is no ancestor, it returns [ThemeData.checkboxTheme].
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// CheckboxThemeData theme = CheckboxTheme.of(context);
+  /// ```
+  static CheckboxThemeData of(BuildContext context) {
+    final CheckboxTheme? checkboxTheme = context.dependOnInheritedWidgetOfExactType<CheckboxTheme>();
+    return checkboxTheme?.data ?? Theme.of(context).checkboxTheme;
+  }
+
+  @override
+  bool updateShouldNotify(CheckboxTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/chip.dart b/lib/src/material/chip.dart
new file mode 100644
index 0000000..4007e15
--- /dev/null
+++ b/lib/src/material/chip.dart
@@ -0,0 +1,3075 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/gestures.dart';
+import 'package:flute/painting.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'chip_theme.dart';
+import 'colors.dart';
+import 'constants.dart';
+import 'debug.dart';
+import 'feedback.dart';
+import 'icons.dart';
+import 'ink_well.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'material_state.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+import 'tooltip.dart';
+
+// Some design constants
+const double _kChipHeight = 32.0;
+const double _kDeleteIconSize = 18.0;
+
+const int _kCheckmarkAlpha = 0xde; // 87%
+const int _kDisabledAlpha = 0x61; // 38%
+const double _kCheckmarkStrokeWidth = 2.0;
+
+const Duration _kSelectDuration = Duration(milliseconds: 195);
+const Duration _kCheckmarkDuration = Duration(milliseconds: 150);
+const Duration _kCheckmarkReverseDuration = Duration(milliseconds: 50);
+const Duration _kDrawerDuration = Duration(milliseconds: 150);
+const Duration _kReverseDrawerDuration = Duration(milliseconds: 100);
+const Duration _kDisableDuration = Duration(milliseconds: 75);
+
+const Color _kSelectScrimColor = Color(0x60191919);
+const Icon _kDefaultDeleteIcon = Icon(Icons.cancel, size: _kDeleteIconSize);
+
+/// An interface defining the base attributes for a material design chip.
+///
+/// Chips are compact elements that represent an attribute, text, entity, or
+/// action.
+///
+/// The defaults mentioned in the documentation for each attribute are what
+/// the implementing classes typically use for defaults (but this class doesn't
+/// provide or enforce them).
+///
+/// See also:
+///
+///  * [Chip], a chip that displays information and can be deleted.
+///  * [InputChip], a chip that represents a complex piece of information, such
+///    as an entity (person, place, or thing) or conversational text, in a
+///    compact form.
+///  * [ChoiceChip], allows a single selection from a set of options. Choice
+///    chips contain related descriptive text or categories.
+///  * [FilterChip], uses tags or descriptive words as a way to filter content.
+///  * [ActionChip], represents an action related to primary content.
+///  * <https://material.io/design/components/chips.html>
+abstract class ChipAttributes {
+  // This class is intended to be used as an interface, and should not be
+  // extended directly; this constructor prevents instantiation and extension.
+  // ignore: unused_element
+  ChipAttributes._();
+
+  /// The primary content of the chip.
+  ///
+  /// Typically a [Text] widget.
+  Widget get label;
+
+  /// A widget to display prior to the chip's label.
+  ///
+  /// Typically a [CircleAvatar] widget.
+  Widget? get avatar;
+
+  /// The style to be applied to the chip's label.
+  ///
+  /// If null, the value of the [ChipTheme]'s [ChipThemeData.labelStyle] is used.
+  //
+  /// This only has an effect on widgets that respect the [DefaultTextStyle],
+  /// such as [Text].
+  ///
+  /// If [TextStyle.color] is a [MaterialStateProperty<Color>], [MaterialStateProperty.resolve]
+  /// is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.disabled].
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.pressed].
+  TextStyle? get labelStyle;
+
+  /// The color and weight of the chip's outline.
+  ///
+  /// Defaults to the border side in the ambient [ChipThemeData]. If the theme
+  /// border side resolves to null, the default is the border side of [shape].
+  ///
+  /// This value is combined with [shape] to create a shape decorated with an
+  /// outline. If it is a [MaterialStateBorderSide],
+  /// [MaterialStateProperty.resolve] is used for the following
+  /// [MaterialState]s:
+  ///
+  ///  * [MaterialState.disabled].
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.pressed].
+  BorderSide? get side;
+
+  /// The [OutlinedBorder] to draw around the chip.
+  ///
+  /// Defaults to the shape in the ambient [ChipThemeData]. If the theme
+  /// shape resolves to null, the default is [StadiumBorder].
+  ///
+  /// This shape is combined with [side] to create a shape decorated with an
+  /// outline. If it is a [MaterialStateOutlinedBorder],
+  /// [MaterialStateProperty.resolve] is used for the following
+  /// [MaterialState]s:
+  ///
+  ///  * [MaterialState.disabled].
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.pressed].
+  OutlinedBorder? get shape;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  Clip get clipBehavior;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  FocusNode? get focusNode;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  bool get autofocus;
+
+  /// Color to be used for the unselected, enabled chip's background.
+  ///
+  /// The default is light grey.
+  Color? get backgroundColor;
+
+  /// The padding between the contents of the chip and the outside [shape].
+  ///
+  /// Defaults to 4 logical pixels on all sides.
+  EdgeInsetsGeometry? get padding;
+
+  /// Defines how compact the chip's layout will be.
+  ///
+  /// Chips are unaffected by horizontal density changes.
+  ///
+  /// {@macro flutter.material.themedata.visualDensity}
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.visualDensity], which specifies the [visualDensity] for all
+  ///    widgets within a [Theme].
+  VisualDensity? get visualDensity;
+
+  /// The padding around the [label] widget.
+  ///
+  /// By default, this is 4 logical pixels at the beginning and the end of the
+  /// label, and zero on top and bottom.
+  EdgeInsetsGeometry? get labelPadding;
+
+  /// Configures the minimum size of the tap target.
+  ///
+  /// Defaults to [ThemeData.materialTapTargetSize].
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialTapTargetSize], for a description of how this affects tap targets.
+  MaterialTapTargetSize? get materialTapTargetSize;
+
+  /// Elevation to be applied on the chip relative to its parent.
+  ///
+  /// This controls the size of the shadow below the chip.
+  ///
+  /// Defaults to 0. The value is always non-negative.
+  double? get elevation;
+
+  /// Color of the chip's shadow when the elevation is greater than 0.
+  ///
+  /// The default is [Colors.black].
+  Color? get shadowColor;
+}
+
+/// An interface for material design chips that can be deleted.
+///
+/// The defaults mentioned in the documentation for each attribute are what
+/// the implementing classes typically use for defaults (but this class doesn't
+/// provide or enforce them).
+///
+/// See also:
+///
+///  * [Chip], a chip that displays information and can be deleted.
+///  * [InputChip], a chip that represents a complex piece of information, such
+///    as an entity (person, place, or thing) or conversational text, in a
+///    compact form.
+///  * <https://material.io/design/components/chips.html>
+abstract class DeletableChipAttributes {
+  // This class is intended to be used as an interface, and should not be
+  // extended directly; this constructor prevents instantiation and extension.
+  // ignore: unused_element
+  DeletableChipAttributes._();
+
+  /// The icon displayed when [onDeleted] is set.
+  ///
+  /// Defaults to an [Icon] widget set to use [Icons.cancel].
+  Widget? get deleteIcon;
+
+  /// Called when the user taps the [deleteIcon] to delete the chip.
+  ///
+  /// If null, the delete button will not appear on the chip.
+  ///
+  /// The chip will not automatically remove itself: this just tells the app
+  /// that the user tapped the delete button. In order to delete the chip, you
+  /// have to do something similar to the following sample:
+  ///
+  /// {@tool dartpad --template=stateful_widget_scaffold_center}
+  ///
+  /// This sample shows how to use [onDeleted] to remove an entry when the
+  /// delete button is tapped.
+  ///
+  /// ```dart preamble
+  /// class Actor {
+  ///   const Actor(this.name, this.initials);
+  ///   final String name;
+  ///   final String initials;
+  /// }
+  ///
+  /// class CastList extends StatefulWidget {
+  ///   @override
+  ///   State createState() => CastListState();
+  /// }
+  ///
+  /// class CastListState extends State<CastList> {
+  ///   final List<Actor> _cast = <Actor>[
+  ///     const Actor('Aaron Burr', 'AB'),
+  ///     const Actor('Alexander Hamilton', 'AH'),
+  ///     const Actor('Eliza Hamilton', 'EH'),
+  ///     const Actor('James Madison', 'JM'),
+  ///   ];
+  ///
+  ///   Iterable<Widget> get actorWidgets sync* {
+  ///     for (final Actor actor in _cast) {
+  ///       yield Padding(
+  ///         padding: const EdgeInsets.all(4.0),
+  ///         child: Chip(
+  ///           avatar: CircleAvatar(child: Text(actor.initials)),
+  ///           label: Text(actor.name),
+  ///           onDeleted: () {
+  ///             setState(() {
+  ///               _cast.removeWhere((Actor entry) {
+  ///                 return entry.name == actor.name;
+  ///               });
+  ///             });
+  ///           },
+  ///         ),
+  ///       );
+  ///     }
+  ///   }
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Wrap(
+  ///       children: actorWidgets.toList(),
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  ///
+  /// ```dart
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return CastList();
+  /// }
+  /// ```
+  /// {@end-tool}
+  VoidCallback? get onDeleted;
+
+  /// The [Color] for the delete icon. The default is based on the ambient
+  /// [IconThemeData.color].
+  Color? get deleteIconColor;
+
+  /// Whether to use a tooltip on the chip's delete button showing the
+  /// [deleteButtonTooltipMessage].
+  ///
+  /// Must not be null. Defaults to true.
+  bool get useDeleteButtonTooltip;
+
+  /// The message to be used for the chip's delete button tooltip.
+  ///
+  /// This will be shown only if [useDeleteButtonTooltip] is true.
+  String? get deleteButtonTooltipMessage;
+}
+
+/// An interface for material design chips that can have check marks.
+///
+/// The defaults mentioned in the documentation for each attribute are what
+/// the implementing classes typically use for defaults (but this class doesn't
+/// provide or enforce them).
+///
+/// See also:
+///
+///  * [InputChip], a chip that represents a complex piece of information, such
+///    as an entity (person, place, or thing) or conversational text, in a
+///    compact form.
+///  * [FilterChip], uses tags or descriptive words as a way to filter content.
+///  * <https://material.io/design/components/chips.html>
+abstract class CheckmarkableChipAttributes {
+  // This class is intended to be used as an interface, and should not be
+  // extended directly; this constructor prevents instantiation and extension.
+  // ignore: unused_element
+  CheckmarkableChipAttributes._();
+
+  /// Whether or not to show a check mark when
+  /// [SelectableChipAttributes.selected] is true.
+  ///
+  /// Defaults to true.
+  bool? get showCheckmark;
+
+  /// [Color] of the chip's check mark when a check mark is visible.
+  ///
+  /// This will override the color set by the platform's brightness setting.
+  ///
+  /// If null, it will defer to a color selected by the platform's brightness
+  /// setting.
+  Color? get checkmarkColor;
+}
+
+/// An interface for material design chips that can be selected.
+///
+/// The defaults mentioned in the documentation for each attribute are what
+/// the implementing classes typically use for defaults (but this class doesn't
+/// provide or enforce them).
+///
+/// See also:
+///
+///  * [InputChip], a chip that represents a complex piece of information, such
+///    as an entity (person, place, or thing) or conversational text, in a
+///    compact form.
+///  * [ChoiceChip], allows a single selection from a set of options. Choice
+///    chips contain related descriptive text or categories.
+///  * [FilterChip], uses tags or descriptive words as a way to filter content.
+///  * <https://material.io/design/components/chips.html>
+abstract class SelectableChipAttributes {
+  // This class is intended to be used as an interface, and should not be
+  // extended directly; this constructor prevents instantiation and extension.
+  // ignore: unused_element
+  SelectableChipAttributes._();
+
+  /// Whether or not this chip is selected.
+  ///
+  /// If [onSelected] is not null, this value will be used to determine if the
+  /// select check mark will be shown or not.
+  ///
+  /// Must not be null. Defaults to false.
+  bool get selected;
+
+  /// Called when the chip should change between selected and de-selected
+  /// states.
+  ///
+  /// When the chip is tapped, then the [onSelected] callback, if set, will be
+  /// applied to `!selected` (see [selected]).
+  ///
+  /// The chip passes the new value to the callback but does not actually
+  /// change state until the parent widget rebuilds the chip with the new
+  /// value.
+  ///
+  /// The callback provided to [onSelected] should update the state of the
+  /// parent [StatefulWidget] using the [State.setState] method, so that the
+  /// parent gets rebuilt.
+  ///
+  /// The [onSelected] and [TappableChipAttributes.onPressed] callbacks must not
+  /// both be specified at the same time.
+  ///
+  /// {@tool snippet}
+  ///
+  /// A [StatefulWidget] that illustrates use of onSelected in an [InputChip].
+  ///
+  /// ```dart
+  /// class Wood extends StatefulWidget {
+  ///   @override
+  ///   State<StatefulWidget> createState() => WoodState();
+  /// }
+  ///
+  /// class WoodState extends State<Wood> {
+  ///   bool _useChisel = false;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return InputChip(
+  ///       label: const Text('Use Chisel'),
+  ///       selected: _useChisel,
+  ///       onSelected: (bool newValue) {
+  ///         setState(() {
+  ///           _useChisel = newValue;
+  ///         });
+  ///       },
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ValueChanged<bool>? get onSelected;
+
+  /// Elevation to be applied on the chip relative to its parent during the
+  /// press motion.
+  ///
+  /// This controls the size of the shadow below the chip.
+  ///
+  /// Defaults to 8. The value is always non-negative.
+  double? get pressElevation;
+
+  /// Color to be used for the chip's background, indicating that it is
+  /// selected.
+  ///
+  /// The chip is selected when [selected] is true.
+  Color? get selectedColor;
+
+  /// Color of the chip's shadow when the elevation is greater than 0 and the
+  /// chip is selected.
+  ///
+  /// The default is [Colors.black].
+  Color? get selectedShadowColor;
+
+  /// Tooltip string to be used for the body area (where the label and avatar
+  /// are) of the chip.
+  String? get tooltip;
+
+  /// The shape of the translucent highlight painted over the avatar when the
+  /// [selected] property is true.
+  ///
+  /// Only the outer path of the shape is used.
+  ///
+  /// Defaults to [CircleBorder].
+  ShapeBorder get avatarBorder;
+}
+
+/// An interface for material design chips that can be enabled and disabled.
+///
+/// The defaults mentioned in the documentation for each attribute are what
+/// the implementing classes typically use for defaults (but this class doesn't
+/// provide or enforce them).
+///
+/// See also:
+///
+///  * [InputChip], a chip that represents a complex piece of information, such
+///    as an entity (person, place, or thing) or conversational text, in a
+///    compact form.
+///  * [ChoiceChip], allows a single selection from a set of options. Choice
+///    chips contain related descriptive text or categories.
+///  * [FilterChip], uses tags or descriptive words as a way to filter content.
+///  * <https://material.io/design/components/chips.html>
+abstract class DisabledChipAttributes {
+  // This class is intended to be used as an interface, and should not be
+  // extended directly; this constructor prevents instantiation and extension.
+  // ignore: unused_element
+  DisabledChipAttributes._();
+
+  /// Whether or not this chip is enabled for input.
+  ///
+  /// If this is true, but all of the user action callbacks are null (i.e.
+  /// [SelectableChipAttributes.onSelected], [TappableChipAttributes.onPressed],
+  /// and [DeletableChipAttributes.onDeleted]), then the
+  /// control will still be shown as disabled.
+  ///
+  /// This is typically used if you want the chip to be disabled, but also show
+  /// a delete button.
+  ///
+  /// For classes which don't have this as a constructor argument, [isEnabled]
+  /// returns true if their user action callback is set.
+  ///
+  /// Defaults to true. Cannot be null.
+  bool get isEnabled;
+
+  /// Color to be used for the chip's background indicating that it is disabled.
+  ///
+  /// The chip is disabled when [isEnabled] is false, or all three of
+  /// [SelectableChipAttributes.onSelected], [TappableChipAttributes.onPressed],
+  /// and [DeletableChipAttributes.onDeleted] are null.
+  ///
+  /// It defaults to [Colors.black38].
+  Color? get disabledColor;
+}
+
+/// An interface for material design chips that can be tapped.
+///
+/// The defaults mentioned in the documentation for each attribute are what
+/// the implementing classes typically use for defaults (but this class doesn't
+/// provide or enforce them).
+///
+/// See also:
+///
+///  * [InputChip], a chip that represents a complex piece of information, such
+///    as an entity (person, place, or thing) or conversational text, in a
+///    compact form.
+///  * [ChoiceChip], allows a single selection from a set of options. Choice
+///    chips contain related descriptive text or categories.
+///  * [FilterChip], uses tags or descriptive words as a way to filter content.
+///  * [ActionChip], represents an action related to primary content.
+///  * <https://material.io/design/components/chips.html>
+abstract class TappableChipAttributes {
+  // This class is intended to be used as an interface, and should not be
+  // extended directly; this constructor prevents instantiation and extension.
+  // ignore: unused_element
+  TappableChipAttributes._();
+
+  /// Called when the user taps the chip.
+  ///
+  /// If [onPressed] is set, then this callback will be called when the user
+  /// taps on the label or avatar parts of the chip. If [onPressed] is null,
+  /// then the chip will be disabled.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// class Blacksmith extends StatelessWidget {
+  ///   void startHammering() {
+  ///     print('bang bang bang');
+  ///   }
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return InputChip(
+  ///       label: const Text('Apply Hammer'),
+  ///       onPressed: startHammering,
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  VoidCallback? get onPressed;
+
+  /// Elevation to be applied on the chip relative to its parent during the
+  /// press motion.
+  ///
+  /// This controls the size of the shadow below the chip.
+  ///
+  /// Defaults to 8. The value is always non-negative.
+  double? get pressElevation;
+
+  /// Tooltip string to be used for the body area (where the label and avatar
+  /// are) of the chip.
+  String? get tooltip;
+}
+
+/// A material design chip.
+///
+/// Chips are compact elements that represent an attribute, text, entity, or
+/// action.
+///
+/// Supplying a non-null [onDeleted] callback will cause the chip to include a
+/// button for deleting the chip.
+///
+/// Its ancestors must include [Material], [MediaQuery], [Directionality], and
+/// [MaterialLocalizations]. Typically all of these widgets are provided by
+/// [MaterialApp] and [Scaffold]. The [label] and [clipBehavior] arguments must
+/// not be null.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// Chip(
+///   avatar: CircleAvatar(
+///     backgroundColor: Colors.grey.shade800,
+///     child: Text('AB'),
+///   ),
+///   label: Text('Aaron Burr'),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [InputChip], a chip that represents a complex piece of information, such
+///    as an entity (person, place, or thing) or conversational text, in a
+///    compact form.
+///  * [ChoiceChip], allows a single selection from a set of options. Choice
+///    chips contain related descriptive text or categories.
+///  * [FilterChip], uses tags or descriptive words as a way to filter content.
+///  * [ActionChip], represents an action related to primary content.
+///  * [CircleAvatar], which shows images or initials of entities.
+///  * [Wrap], A widget that displays its children in multiple horizontal or
+///    vertical runs.
+///  * <https://material.io/design/components/chips.html>
+class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttributes {
+  /// Creates a material design chip.
+  ///
+  /// The [label], [autofocus], and [clipBehavior] arguments must not be null.
+  /// The [elevation] must be null or non-negative.
+  const Chip({
+    Key? key,
+    this.avatar,
+    required this.label,
+    this.labelStyle,
+    this.labelPadding,
+    this.deleteIcon,
+    this.onDeleted,
+    this.deleteIconColor,
+    this.useDeleteButtonTooltip = true,
+    this.deleteButtonTooltipMessage,
+    this.side,
+    this.shape,
+    this.clipBehavior = Clip.none,
+    this.focusNode,
+    this.autofocus = false,
+    this.backgroundColor,
+    this.padding,
+    this.visualDensity,
+    this.materialTapTargetSize,
+    this.elevation,
+    this.shadowColor,
+  }) : assert(label != null),
+       assert(autofocus != null),
+       assert(clipBehavior != null),
+       assert(elevation == null || elevation >= 0.0),
+       assert(useDeleteButtonTooltip != null),
+       super(key: key);
+
+  @override
+  final Widget? avatar;
+  @override
+  final Widget label;
+  @override
+  final TextStyle? labelStyle;
+  @override
+  final EdgeInsetsGeometry? labelPadding;
+  @override
+  final BorderSide? side;
+  @override
+  final OutlinedBorder? shape;
+  @override
+  final Clip clipBehavior;
+  @override
+  final FocusNode? focusNode;
+  @override
+  final bool autofocus;
+  @override
+  final Color? backgroundColor;
+  @override
+  final EdgeInsetsGeometry? padding;
+  @override
+  final VisualDensity? visualDensity;
+  @override
+  final Widget? deleteIcon;
+  @override
+  final VoidCallback? onDeleted;
+  @override
+  final Color? deleteIconColor;
+  @override
+  final bool useDeleteButtonTooltip;
+  @override
+  final String? deleteButtonTooltipMessage;
+  @override
+  final MaterialTapTargetSize? materialTapTargetSize;
+  @override
+  final double? elevation;
+  @override
+  final Color? shadowColor;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    return RawChip(
+      avatar: avatar,
+      label: label,
+      labelStyle: labelStyle,
+      labelPadding: labelPadding,
+      deleteIcon: deleteIcon,
+      onDeleted: onDeleted,
+      deleteIconColor: deleteIconColor,
+      useDeleteButtonTooltip: useDeleteButtonTooltip,
+      deleteButtonTooltipMessage: deleteButtonTooltipMessage,
+      tapEnabled: false,
+      side: side,
+      shape: shape,
+      clipBehavior: clipBehavior,
+      focusNode: focusNode,
+      autofocus: autofocus,
+      backgroundColor: backgroundColor,
+      padding: padding,
+      visualDensity: visualDensity,
+      materialTapTargetSize: materialTapTargetSize,
+      elevation: elevation,
+      shadowColor: shadowColor,
+      isEnabled: true,
+    );
+  }
+}
+
+/// A material design input chip.
+///
+/// Input chips represent a complex piece of information, such as an entity
+/// (person, place, or thing) or conversational text, in a compact form.
+///
+/// Input chips can be made selectable by setting [onSelected], deletable by
+/// setting [onDeleted], and pressable like a button with [onPressed]. They have
+/// a [label], and they can have a leading icon (see [avatar]) and a trailing
+/// icon ([deleteIcon]). Colors and padding can be customized.
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// Input chips work together with other UI elements. They can appear:
+///
+///  * In a [Wrap] widget.
+///  * In a horizontally scrollable list, like a [ListView] whose
+///    scrollDirection is [Axis.horizontal].
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// InputChip(
+///   avatar: CircleAvatar(
+///     backgroundColor: Colors.grey.shade800,
+///     child: Text('AB'),
+///   ),
+///   label: Text('Aaron Burr'),
+///   onPressed: () {
+///     print('I am the one thing in life.');
+///   }
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Chip], a chip that displays information and can be deleted.
+///  * [ChoiceChip], allows a single selection from a set of options. Choice
+///    chips contain related descriptive text or categories.
+///  * [FilterChip], uses tags or descriptive words as a way to filter content.
+///  * [ActionChip], represents an action related to primary content.
+///  * [CircleAvatar], which shows images or initials of people.
+///  * [Wrap], A widget that displays its children in multiple horizontal or
+///    vertical runs.
+///  * <https://material.io/design/components/chips.html>
+class InputChip extends StatelessWidget
+    implements
+        ChipAttributes,
+        DeletableChipAttributes,
+        SelectableChipAttributes,
+        CheckmarkableChipAttributes,
+        DisabledChipAttributes,
+        TappableChipAttributes {
+  /// Creates an [InputChip].
+  ///
+  /// The [onPressed] and [onSelected] callbacks must not both be specified at
+  /// the same time.
+  ///
+  /// The [label], [isEnabled], [selected], [autofocus], and [clipBehavior]
+  /// arguments must not be null. The [pressElevation] and [elevation] must be
+  /// null or non-negative. Typically, [pressElevation] is greater than
+  /// [elevation].
+  const InputChip({
+    Key? key,
+    this.avatar,
+    required this.label,
+    this.labelStyle,
+    this.labelPadding,
+    this.selected = false,
+    this.isEnabled = true,
+    this.onSelected,
+    this.deleteIcon,
+    this.onDeleted,
+    this.deleteIconColor,
+    this.useDeleteButtonTooltip = true,
+    this.deleteButtonTooltipMessage,
+    this.onPressed,
+    this.pressElevation,
+    this.disabledColor,
+    this.selectedColor,
+    this.tooltip,
+    this.side,
+    this.shape,
+    this.clipBehavior = Clip.none,
+    this.focusNode,
+    this.autofocus = false,
+    this.backgroundColor,
+    this.padding,
+    this.visualDensity,
+    this.materialTapTargetSize,
+    this.elevation,
+    this.shadowColor,
+    this.selectedShadowColor,
+    this.showCheckmark,
+    this.checkmarkColor,
+    this.avatarBorder = const CircleBorder(),
+  }) : assert(selected != null),
+       assert(isEnabled != null),
+       assert(label != null),
+       assert(clipBehavior != null),
+       assert(autofocus != null),
+       assert(pressElevation == null || pressElevation >= 0.0),
+       assert(elevation == null || elevation >= 0.0),
+       assert(useDeleteButtonTooltip != null),
+       super(key: key);
+
+  @override
+  final Widget? avatar;
+  @override
+  final Widget label;
+  @override
+  final TextStyle? labelStyle;
+  @override
+  final EdgeInsetsGeometry? labelPadding;
+  @override
+  final bool selected;
+  @override
+  final bool isEnabled;
+  @override
+  final ValueChanged<bool>? onSelected;
+  @override
+  final Widget? deleteIcon;
+  @override
+  final VoidCallback? onDeleted;
+  @override
+  final Color? deleteIconColor;
+  @override
+  final bool useDeleteButtonTooltip;
+  @override
+  final String? deleteButtonTooltipMessage;
+  @override
+  final VoidCallback? onPressed;
+  @override
+  final double? pressElevation;
+  @override
+  final Color? disabledColor;
+  @override
+  final Color? selectedColor;
+  @override
+  final String? tooltip;
+  @override
+  final BorderSide? side;
+  @override
+  final OutlinedBorder? shape;
+  @override
+  final Clip clipBehavior;
+  @override
+  final FocusNode? focusNode;
+  @override
+  final bool autofocus;
+  @override
+  final Color? backgroundColor;
+  @override
+  final EdgeInsetsGeometry? padding;
+  @override
+  final VisualDensity? visualDensity;
+  @override
+  final MaterialTapTargetSize? materialTapTargetSize;
+  @override
+  final double? elevation;
+  @override
+  final Color? shadowColor;
+  @override
+  final Color? selectedShadowColor;
+  @override
+  final bool? showCheckmark;
+  @override
+  final Color? checkmarkColor;
+  @override
+  final ShapeBorder avatarBorder;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    return RawChip(
+      avatar: avatar,
+      label: label,
+      labelStyle: labelStyle,
+      labelPadding: labelPadding,
+      deleteIcon: deleteIcon,
+      onDeleted: onDeleted,
+      deleteIconColor: deleteIconColor,
+      useDeleteButtonTooltip: useDeleteButtonTooltip,
+      deleteButtonTooltipMessage: deleteButtonTooltipMessage,
+      onSelected: onSelected,
+      onPressed: onPressed,
+      pressElevation: pressElevation,
+      selected: selected,
+      tapEnabled: true,
+      disabledColor: disabledColor,
+      selectedColor: selectedColor,
+      tooltip: tooltip,
+      side: side,
+      shape: shape,
+      clipBehavior: clipBehavior,
+      focusNode: focusNode,
+      autofocus: autofocus,
+      backgroundColor: backgroundColor,
+      padding: padding,
+      visualDensity: visualDensity,
+      materialTapTargetSize: materialTapTargetSize,
+      elevation: elevation,
+      shadowColor: shadowColor,
+      selectedShadowColor: selectedShadowColor,
+      showCheckmark: showCheckmark,
+      checkmarkColor: checkmarkColor,
+      isEnabled: isEnabled && (onSelected != null || onDeleted != null || onPressed != null),
+      avatarBorder: avatarBorder,
+    );
+  }
+}
+
+/// A material design choice chip.
+///
+/// [ChoiceChip]s represent a single choice from a set. Choice chips contain
+/// related descriptive text or categories.
+///
+/// Requires one of its ancestors to be a [Material] widget. The [selected] and
+/// [label] arguments must not be null.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// class MyThreeOptions extends StatefulWidget {
+///   @override
+///   _MyThreeOptionsState createState() => _MyThreeOptionsState();
+/// }
+///
+/// class _MyThreeOptionsState extends State<MyThreeOptions> {
+///   int? _value = 1;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Wrap(
+///       children: List<Widget>.generate(
+///         3,
+///         (int index) {
+///           return ChoiceChip(
+///             label: Text('Item $index'),
+///             selected: _value == index,
+///             onSelected: (bool selected) {
+///               setState(() {
+///                 _value = selected ? index : null;
+///               });
+///             },
+///           );
+///         },
+///       ).toList(),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Chip], a chip that displays information and can be deleted.
+///  * [InputChip], a chip that represents a complex piece of information, such
+///    as an entity (person, place, or thing) or conversational text, in a
+///    compact form.
+///  * [FilterChip], uses tags or descriptive words as a way to filter content.
+///  * [ActionChip], represents an action related to primary content.
+///  * [CircleAvatar], which shows images or initials of people.
+///  * [Wrap], A widget that displays its children in multiple horizontal or
+///    vertical runs.
+///  * <https://material.io/design/components/chips.html>
+class ChoiceChip extends StatelessWidget
+    implements
+        ChipAttributes,
+        SelectableChipAttributes,
+        DisabledChipAttributes {
+  /// Create a chip that acts like a radio button.
+  ///
+  /// The [label], [selected], [autofocus], and [clipBehavior] arguments must
+  /// not be null. The [pressElevation] and [elevation] must be null or
+  /// non-negative. Typically, [pressElevation] is greater than [elevation].
+  const ChoiceChip({
+    Key? key,
+    this.avatar,
+    required this.label,
+    this.labelStyle,
+    this.labelPadding,
+    this.onSelected,
+    this.pressElevation,
+    required this.selected,
+    this.selectedColor,
+    this.disabledColor,
+    this.tooltip,
+    this.side,
+    this.shape,
+    this.clipBehavior = Clip.none,
+    this.focusNode,
+    this.autofocus = false,
+    this.backgroundColor,
+    this.padding,
+    this.visualDensity,
+    this.materialTapTargetSize,
+    this.elevation,
+    this.shadowColor,
+    this.selectedShadowColor,
+    this.avatarBorder = const CircleBorder(),
+  }) : assert(selected != null),
+       assert(label != null),
+       assert(clipBehavior != null),
+       assert(autofocus != null),
+       assert(pressElevation == null || pressElevation >= 0.0),
+       assert(elevation == null || elevation >= 0.0),
+       super(key: key);
+
+  @override
+  final Widget? avatar;
+  @override
+  final Widget label;
+  @override
+  final TextStyle? labelStyle;
+  @override
+  final EdgeInsetsGeometry? labelPadding;
+  @override
+  final ValueChanged<bool>? onSelected;
+  @override
+  final double? pressElevation;
+  @override
+  final bool selected;
+  @override
+  final Color? disabledColor;
+  @override
+  final Color? selectedColor;
+  @override
+  final String? tooltip;
+  @override
+  final BorderSide? side;
+  @override
+  final OutlinedBorder? shape;
+  @override
+  final Clip clipBehavior;
+  @override
+  final FocusNode? focusNode;
+  @override
+  final bool autofocus;
+  @override
+  final Color? backgroundColor;
+  @override
+  final EdgeInsetsGeometry? padding;
+  @override
+  final VisualDensity? visualDensity;
+  @override
+  final MaterialTapTargetSize? materialTapTargetSize;
+  @override
+  final double? elevation;
+  @override
+  final Color? shadowColor;
+  @override
+  final Color? selectedShadowColor;
+  @override
+  final ShapeBorder avatarBorder;
+
+  @override
+  bool get isEnabled => onSelected != null;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    final ChipThemeData chipTheme = ChipTheme.of(context);
+    return RawChip(
+      avatar: avatar,
+      label: label,
+      labelStyle: labelStyle ?? (selected ? chipTheme.secondaryLabelStyle : null),
+      labelPadding: labelPadding,
+      onSelected: onSelected,
+      pressElevation: pressElevation,
+      selected: selected,
+      showCheckmark: false,
+      onDeleted: null,
+      tooltip: tooltip,
+      side: side,
+      shape: shape,
+      clipBehavior: clipBehavior,
+      focusNode: focusNode,
+      autofocus: autofocus,
+      disabledColor: disabledColor,
+      selectedColor: selectedColor ?? chipTheme.secondarySelectedColor,
+      backgroundColor: backgroundColor,
+      padding: padding,
+      visualDensity: visualDensity,
+      isEnabled: isEnabled,
+      materialTapTargetSize: materialTapTargetSize,
+      elevation: elevation,
+      shadowColor: shadowColor,
+      selectedShadowColor: selectedShadowColor,
+      avatarBorder: avatarBorder,
+    );
+  }
+}
+
+/// A material design filter chip.
+///
+/// Filter chips use tags or descriptive words as a way to filter content.
+///
+/// Filter chips are a good alternative to [Checkbox] or [Switch] widgets.
+/// Unlike these alternatives, filter chips allow for clearly delineated and
+/// exposed options in a compact area.
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// class ActorFilterEntry {
+///   const ActorFilterEntry(this.name, this.initials);
+///   final String name;
+///   final String initials;
+/// }
+///
+/// class CastFilter extends StatefulWidget {
+///   @override
+///   State createState() => CastFilterState();
+/// }
+///
+/// class CastFilterState extends State<CastFilter> {
+///   final List<ActorFilterEntry> _cast = <ActorFilterEntry>[
+///     const ActorFilterEntry('Aaron Burr', 'AB'),
+///     const ActorFilterEntry('Alexander Hamilton', 'AH'),
+///     const ActorFilterEntry('Eliza Hamilton', 'EH'),
+///     const ActorFilterEntry('James Madison', 'JM'),
+///   ];
+///   List<String> _filters = <String>[];
+///
+///   Iterable<Widget> get actorWidgets sync* {
+///     for (final ActorFilterEntry actor in _cast) {
+///       yield Padding(
+///         padding: const EdgeInsets.all(4.0),
+///         child: FilterChip(
+///           avatar: CircleAvatar(child: Text(actor.initials)),
+///           label: Text(actor.name),
+///           selected: _filters.contains(actor.name),
+///           onSelected: (bool value) {
+///             setState(() {
+///               if (value) {
+///                 _filters.add(actor.name);
+///               } else {
+///                 _filters.removeWhere((String name) {
+///                   return name == actor.name;
+///                 });
+///               }
+///             });
+///           },
+///         ),
+///       );
+///     }
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Column(
+///       mainAxisAlignment: MainAxisAlignment.center,
+///       children: <Widget>[
+///         Wrap(
+///           children: actorWidgets.toList(),
+///         ),
+///         Text('Look for: ${_filters.join(', ')}'),
+///       ],
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Chip], a chip that displays information and can be deleted.
+///  * [InputChip], a chip that represents a complex piece of information, such
+///    as an entity (person, place, or thing) or conversational text, in a
+///    compact form.
+///  * [ChoiceChip], allows a single selection from a set of options. Choice
+///    chips contain related descriptive text or categories.
+///  * [ActionChip], represents an action related to primary content.
+///  * [CircleAvatar], which shows images or initials of people.
+///  * [Wrap], A widget that displays its children in multiple horizontal or
+///    vertical runs.
+///  * <https://material.io/design/components/chips.html>
+class FilterChip extends StatelessWidget
+    implements
+        ChipAttributes,
+        SelectableChipAttributes,
+        CheckmarkableChipAttributes,
+        DisabledChipAttributes {
+  /// Create a chip that acts like a checkbox.
+  ///
+  /// The [selected], [label], [autofocus], and [clipBehavior] arguments must
+  /// not be null. The [pressElevation] and [elevation] must be null or
+  /// non-negative. Typically, [pressElevation] is greater than [elevation].
+  const FilterChip({
+    Key? key,
+    this.avatar,
+    required this.label,
+    this.labelStyle,
+    this.labelPadding,
+    this.selected = false,
+    required this.onSelected,
+    this.pressElevation,
+    this.disabledColor,
+    this.selectedColor,
+    this.tooltip,
+    this.side,
+    this.shape,
+    this.clipBehavior = Clip.none,
+    this.focusNode,
+    this.autofocus = false,
+    this.backgroundColor,
+    this.padding,
+    this.visualDensity,
+    this.materialTapTargetSize,
+    this.elevation,
+    this.shadowColor,
+    this.selectedShadowColor,
+    this.showCheckmark,
+    this.checkmarkColor,
+    this.avatarBorder = const CircleBorder(),
+  }) : assert(selected != null),
+       assert(label != null),
+       assert(clipBehavior != null),
+       assert(autofocus != null),
+       assert(pressElevation == null || pressElevation >= 0.0),
+       assert(elevation == null || elevation >= 0.0),
+       super(key: key);
+
+  @override
+  final Widget? avatar;
+  @override
+  final Widget label;
+  @override
+  final TextStyle? labelStyle;
+  @override
+  final EdgeInsetsGeometry? labelPadding;
+  @override
+  final bool selected;
+  @override
+  final ValueChanged<bool>? onSelected;
+  @override
+  final double? pressElevation;
+  @override
+  final Color? disabledColor;
+  @override
+  final Color? selectedColor;
+  @override
+  final String? tooltip;
+  @override
+  final BorderSide? side;
+  @override
+  final OutlinedBorder? shape;
+  @override
+  final Clip clipBehavior;
+  @override
+  final FocusNode? focusNode;
+  @override
+  final bool autofocus;
+  @override
+  final Color? backgroundColor;
+  @override
+  final EdgeInsetsGeometry? padding;
+  @override
+  final VisualDensity? visualDensity;
+  @override
+  final MaterialTapTargetSize? materialTapTargetSize;
+  @override
+  final double? elevation;
+  @override
+  final Color? shadowColor;
+  @override
+  final Color? selectedShadowColor;
+  @override
+  final bool? showCheckmark;
+  @override
+  final Color? checkmarkColor;
+  @override
+  final ShapeBorder avatarBorder;
+
+  @override
+  bool get isEnabled => onSelected != null;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    return RawChip(
+      avatar: avatar,
+      label: label,
+      labelStyle: labelStyle,
+      labelPadding: labelPadding,
+      onSelected: onSelected,
+      pressElevation: pressElevation,
+      selected: selected,
+      tooltip: tooltip,
+      side: side,
+      shape: shape,
+      clipBehavior: clipBehavior,
+      focusNode: focusNode,
+      autofocus: autofocus,
+      backgroundColor: backgroundColor,
+      disabledColor: disabledColor,
+      selectedColor: selectedColor,
+      padding: padding,
+      visualDensity: visualDensity,
+      isEnabled: isEnabled,
+      materialTapTargetSize: materialTapTargetSize,
+      elevation: elevation,
+      shadowColor: shadowColor,
+      selectedShadowColor: selectedShadowColor,
+      showCheckmark: showCheckmark,
+      checkmarkColor: checkmarkColor,
+      avatarBorder: avatarBorder,
+    );
+  }
+}
+
+/// A material design action chip.
+///
+/// Action chips are a set of options which trigger an action related to primary
+/// content. Action chips should appear dynamically and contextually in a UI.
+///
+/// Action chips can be tapped to trigger an action or show progress and
+/// confirmation. They cannot be disabled; if the action is not applicable, the
+/// chip should not be included in the interface. (This contrasts with buttons,
+/// where unavailable choices are usually represented as disabled controls.)
+///
+/// Action chips are displayed after primary content, such as below a card or
+/// persistently at the bottom of a screen.
+///
+/// The material button widgets, [ElevatedButton], [TextButton], and
+/// [OutlinedButton], are an alternative to action chips, which should appear
+/// statically and consistently in a UI.
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// ActionChip(
+///   avatar: CircleAvatar(
+///     backgroundColor: Colors.grey.shade800,
+///     child: Text('AB'),
+///   ),
+///   label: Text('Aaron Burr'),
+///   onPressed: () {
+///     print("If you stand for nothing, Burr, what’ll you fall for?");
+///   }
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Chip], a chip that displays information and can be deleted.
+///  * [InputChip], a chip that represents a complex piece of information, such
+///    as an entity (person, place, or thing) or conversational text, in a
+///    compact form.
+///  * [ChoiceChip], allows a single selection from a set of options. Choice
+///    chips contain related descriptive text or categories.
+///  * [CircleAvatar], which shows images or initials of people.
+///  * [Wrap], A widget that displays its children in multiple horizontal or
+///    vertical runs.
+///  * <https://material.io/design/components/chips.html>
+class ActionChip extends StatelessWidget implements ChipAttributes, TappableChipAttributes {
+  /// Create a chip that acts like a button.
+  ///
+  /// The [label], [onPressed], [autofocus], and [clipBehavior] arguments must
+  /// not be null. The [pressElevation] and [elevation] must be null or
+  /// non-negative. Typically, [pressElevation] is greater than [elevation].
+  const ActionChip({
+    Key? key,
+    this.avatar,
+    required this.label,
+    this.labelStyle,
+    this.labelPadding,
+    required this.onPressed,
+    this.pressElevation,
+    this.tooltip,
+    this.side,
+    this.shape,
+    this.clipBehavior = Clip.none,
+    this.focusNode,
+    this.autofocus = false,
+    this.backgroundColor,
+    this.padding,
+    this.visualDensity,
+    this.materialTapTargetSize,
+    this.elevation,
+    this.shadowColor,
+  }) : assert(label != null),
+       assert(clipBehavior != null),
+       assert(autofocus != null),
+       assert(
+         onPressed != null,
+         'Rather than disabling an ActionChip by setting onPressed to null, '
+         'remove it from the interface entirely.',
+       ),
+       assert(pressElevation == null || pressElevation >= 0.0),
+       assert(elevation == null || elevation >= 0.0),
+       super(key: key);
+
+  @override
+  final Widget? avatar;
+  @override
+  final Widget label;
+  @override
+  final TextStyle? labelStyle;
+  @override
+  final EdgeInsetsGeometry? labelPadding;
+  @override
+  final VoidCallback onPressed;
+  @override
+  final double? pressElevation;
+  @override
+  final String? tooltip;
+  @override
+  final BorderSide? side;
+  @override
+  final OutlinedBorder? shape;
+  @override
+  final Clip clipBehavior;
+  @override
+  final FocusNode? focusNode;
+  @override
+  final bool autofocus;
+  @override
+  final Color? backgroundColor;
+  @override
+  final EdgeInsetsGeometry? padding;
+  @override
+  final VisualDensity? visualDensity;
+  @override
+  final MaterialTapTargetSize? materialTapTargetSize;
+  @override
+  final double? elevation;
+  @override
+  final Color? shadowColor;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    return RawChip(
+      avatar: avatar,
+      label: label,
+      onPressed: onPressed,
+      pressElevation: pressElevation,
+      tooltip: tooltip,
+      labelStyle: labelStyle,
+      backgroundColor: backgroundColor,
+      side: side,
+      shape: shape,
+      clipBehavior: clipBehavior,
+      focusNode: focusNode,
+      autofocus: autofocus,
+      padding: padding,
+      visualDensity: visualDensity,
+      labelPadding: labelPadding,
+      isEnabled: true,
+      materialTapTargetSize: materialTapTargetSize,
+      elevation: elevation,
+      shadowColor: shadowColor,
+    );
+  }
+}
+
+/// A raw material design chip.
+///
+/// This serves as the basis for all of the chip widget types to aggregate.
+/// It is typically not created directly, one of the other chip types
+/// that are appropriate for the use case are used instead:
+///
+///  * [Chip] a simple chip that can only display information and be deleted.
+///  * [InputChip] represents a complex piece of information, such as an entity
+///    (person, place, or thing) or conversational text, in a compact form.
+///  * [ChoiceChip] allows a single selection from a set of options.
+///  * [FilterChip] a chip that uses tags or descriptive words as a way to
+///    filter content.
+///  * [ActionChip]s display a set of actions related to primary content.
+///
+/// Raw chips are typically only used if you want to create your own custom chip
+/// type.
+///
+/// Raw chips can be selected by setting [onSelected], deleted by setting
+/// [onDeleted], and pushed like a button with [onPressed]. They have a [label],
+/// and they can have a leading icon (see [avatar]) and a trailing icon
+/// ([deleteIcon]). Colors and padding can be customized.
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// See also:
+///
+///  * [CircleAvatar], which shows images or initials of people.
+///  * [Wrap], A widget that displays its children in multiple horizontal or
+///    vertical runs.
+///  * <https://material.io/design/components/chips.html>
+class RawChip extends StatefulWidget
+    implements
+        ChipAttributes,
+        DeletableChipAttributes,
+        SelectableChipAttributes,
+        CheckmarkableChipAttributes,
+        DisabledChipAttributes,
+        TappableChipAttributes {
+  /// Creates a RawChip.
+  ///
+  /// The [onPressed] and [onSelected] callbacks must not both be specified at
+  /// the same time.
+  ///
+  /// The [label], [isEnabled], [selected], [autofocus], and [clipBehavior]
+  /// arguments must not be null. The [pressElevation] and [elevation] must be
+  /// null or non-negative. Typically, [pressElevation] is greater than
+  /// [elevation].
+  const RawChip({
+    Key? key,
+    this.avatar,
+    required this.label,
+    this.labelStyle,
+    this.padding,
+    this.visualDensity,
+    this.labelPadding,
+    Widget? deleteIcon,
+    this.onDeleted,
+    this.deleteIconColor,
+    this.useDeleteButtonTooltip = true,
+    this.deleteButtonTooltipMessage,
+    this.onPressed,
+    this.onSelected,
+    this.pressElevation,
+    this.tapEnabled = true,
+    this.selected = false,
+    this.isEnabled = true,
+    this.disabledColor,
+    this.selectedColor,
+    this.tooltip,
+    this.side,
+    this.shape,
+    this.clipBehavior = Clip.none,
+    this.focusNode,
+    this.autofocus = false,
+    this.backgroundColor,
+    this.materialTapTargetSize,
+    this.elevation,
+    this.shadowColor,
+    this.selectedShadowColor,
+    this.showCheckmark = true,
+    this.checkmarkColor,
+    this.avatarBorder = const CircleBorder(),
+  }) : assert(label != null),
+       assert(isEnabled != null),
+       assert(selected != null),
+       assert(clipBehavior != null),
+       assert(autofocus != null),
+       assert(pressElevation == null || pressElevation >= 0.0),
+       assert(elevation == null || elevation >= 0.0),
+       assert(useDeleteButtonTooltip != null),
+       deleteIcon = deleteIcon ?? _kDefaultDeleteIcon,
+       super(key: key);
+
+  @override
+  final Widget? avatar;
+  @override
+  final Widget label;
+  @override
+  final TextStyle? labelStyle;
+  @override
+  final EdgeInsetsGeometry? labelPadding;
+  @override
+  final Widget deleteIcon;
+  @override
+  final VoidCallback? onDeleted;
+  @override
+  final Color? deleteIconColor;
+  @override
+  final bool useDeleteButtonTooltip;
+  @override
+  final String? deleteButtonTooltipMessage;
+  @override
+  final ValueChanged<bool>? onSelected;
+  @override
+  final VoidCallback? onPressed;
+  @override
+  final double? pressElevation;
+  @override
+  final bool selected;
+  @override
+  final bool isEnabled;
+  @override
+  final Color? disabledColor;
+  @override
+  final Color? selectedColor;
+  @override
+  final String? tooltip;
+  @override
+  final BorderSide? side;
+  @override
+  final OutlinedBorder? shape;
+  @override
+  final Clip clipBehavior;
+  @override
+  final FocusNode? focusNode;
+  @override
+  final bool autofocus;
+  @override
+  final Color? backgroundColor;
+  @override
+  final EdgeInsetsGeometry? padding;
+  @override
+  final VisualDensity? visualDensity;
+  @override
+  final MaterialTapTargetSize? materialTapTargetSize;
+  @override
+  final double? elevation;
+  @override
+  final Color? shadowColor;
+  @override
+  final Color? selectedShadowColor;
+  @override
+  final bool? showCheckmark;
+  @override
+  final Color? checkmarkColor;
+  @override
+  final ShapeBorder avatarBorder;
+
+  /// If set, this indicates that the chip should be disabled if all of the
+  /// tap callbacks ([onSelected], [onPressed]) are null.
+  ///
+  /// For example, the [Chip] class sets this to false because it can't be
+  /// disabled, even if no callbacks are set on it, since it is used for
+  /// displaying information only.
+  ///
+  /// Defaults to true.
+  final bool tapEnabled;
+
+  @override
+  _RawChipState createState() => _RawChipState();
+}
+
+class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip> {
+  static const Duration pressedAnimationDuration = Duration(milliseconds: 75);
+
+  late AnimationController selectController;
+  late AnimationController avatarDrawerController;
+  late AnimationController deleteDrawerController;
+  late AnimationController enableController;
+  late Animation<double> checkmarkAnimation;
+  late Animation<double> avatarDrawerAnimation;
+  late Animation<double> deleteDrawerAnimation;
+  late Animation<double> enableAnimation;
+  late Animation<double> selectionFade;
+
+  final Set<MaterialState> _states = <MaterialState>{};
+
+  final GlobalKey deleteIconKey = GlobalKey();
+
+  bool get hasDeleteButton => widget.onDeleted != null;
+  bool get hasAvatar => widget.avatar != null;
+
+  bool get canTap {
+    return widget.isEnabled
+        && widget.tapEnabled
+        && (widget.onPressed != null || widget.onSelected != null);
+  }
+
+  bool _isTapping = false;
+  bool get isTapping => canTap && _isTapping;
+
+  @override
+  void initState() {
+    assert(widget.onSelected == null || widget.onPressed == null);
+    super.initState();
+    _updateState(MaterialState.disabled, !widget.isEnabled);
+    _updateState(MaterialState.selected, widget.selected);
+    selectController = AnimationController(
+      duration: _kSelectDuration,
+      value: widget.selected == true ? 1.0 : 0.0,
+      vsync: this,
+    );
+    selectionFade = CurvedAnimation(
+      parent: selectController,
+      curve: Curves.fastOutSlowIn,
+    );
+    avatarDrawerController = AnimationController(
+      duration: _kDrawerDuration,
+      value: hasAvatar || widget.selected == true ? 1.0 : 0.0,
+      vsync: this,
+    );
+    deleteDrawerController = AnimationController(
+      duration: _kDrawerDuration,
+      value: hasDeleteButton ? 1.0 : 0.0,
+      vsync: this,
+    );
+    enableController = AnimationController(
+      duration: _kDisableDuration,
+      value: widget.isEnabled ? 1.0 : 0.0,
+      vsync: this,
+    );
+
+    // These will delay the start of some animations, and/or reduce their
+    // length compared to the overall select animation, using Intervals.
+    final double checkmarkPercentage = _kCheckmarkDuration.inMilliseconds /
+        _kSelectDuration.inMilliseconds;
+    final double checkmarkReversePercentage = _kCheckmarkReverseDuration.inMilliseconds /
+        _kSelectDuration.inMilliseconds;
+    final double avatarDrawerReversePercentage = _kReverseDrawerDuration.inMilliseconds /
+        _kSelectDuration.inMilliseconds;
+    checkmarkAnimation = CurvedAnimation(
+      parent: selectController,
+      curve: Interval(1.0 - checkmarkPercentage, 1.0, curve: Curves.fastOutSlowIn),
+      reverseCurve: Interval(
+        1.0 - checkmarkReversePercentage,
+        1.0,
+        curve: Curves.fastOutSlowIn,
+      ),
+    );
+    deleteDrawerAnimation = CurvedAnimation(
+      parent: deleteDrawerController,
+      curve: Curves.fastOutSlowIn,
+    );
+    avatarDrawerAnimation = CurvedAnimation(
+      parent: avatarDrawerController,
+      curve: Curves.fastOutSlowIn,
+      reverseCurve: Interval(
+        1.0 - avatarDrawerReversePercentage,
+        1.0,
+        curve: Curves.fastOutSlowIn,
+      ),
+    );
+    enableAnimation = CurvedAnimation(
+      parent: enableController,
+      curve: Curves.fastOutSlowIn,
+    );
+  }
+
+  @override
+  void dispose() {
+    selectController.dispose();
+    avatarDrawerController.dispose();
+    deleteDrawerController.dispose();
+    enableController.dispose();
+    super.dispose();
+  }
+
+  void _updateState(MaterialState state, bool value) {
+    value ? _states.add(state) : _states.remove(state);
+  }
+
+  void _handleTapDown(TapDownDetails details) {
+    if (!canTap) {
+      return;
+    }
+    setState(() {
+      _isTapping = true;
+      _updateState(MaterialState.pressed, true);
+    });
+  }
+
+  void _handleTapCancel() {
+    if (!canTap) {
+      return;
+    }
+    setState(() {
+      _isTapping = false;
+      _updateState(MaterialState.pressed, false);
+    });
+  }
+
+  void _handleTap() {
+    if (!canTap) {
+      return;
+    }
+    setState(() {
+      _isTapping = false;
+      _updateState(MaterialState.pressed, false);
+    });
+    // Only one of these can be set, so only one will be called.
+    widget.onSelected?.call(!widget.selected);
+    widget.onPressed?.call();
+  }
+
+  void _handleFocus(bool isFocused) {
+    setState(() {
+      _updateState(MaterialState.focused, isFocused);
+    });
+  }
+
+  void _handleHover(bool isHovered) {
+    setState(() {
+      _updateState(MaterialState.hovered, isHovered);
+    });
+  }
+
+  OutlinedBorder _getShape(ChipThemeData theme) {
+    final BorderSide? resolvedSide = MaterialStateProperty.resolveAs<BorderSide?>(widget.side, _states)
+      ?? MaterialStateProperty.resolveAs<BorderSide?>(theme.side, _states);
+    final OutlinedBorder resolvedShape = MaterialStateProperty.resolveAs<OutlinedBorder?>(widget.shape, _states)
+      ?? MaterialStateProperty.resolveAs<OutlinedBorder?>(theme.shape, _states)
+      ?? const StadiumBorder();
+    return resolvedShape.copyWith(side: resolvedSide);
+  }
+
+  /// Picks between three different colors, depending upon the state of two
+  /// different animations.
+  Color? getBackgroundColor(ChipThemeData theme) {
+    final ColorTween backgroundTween = ColorTween(
+      begin: widget.disabledColor ?? theme.disabledColor,
+      end: widget.backgroundColor ?? theme.backgroundColor,
+    );
+    final ColorTween selectTween = ColorTween(
+      begin: backgroundTween.evaluate(enableController),
+      end: widget.selectedColor ?? theme.selectedColor,
+    );
+    return selectTween.evaluate(selectionFade);
+  }
+
+  @override
+  void didUpdateWidget(RawChip oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.isEnabled != widget.isEnabled) {
+      setState(() {
+        _updateState(MaterialState.disabled, !widget.isEnabled);
+        if (widget.isEnabled) {
+          enableController.forward();
+        } else {
+          enableController.reverse();
+        }
+      });
+    }
+    if (oldWidget.avatar != widget.avatar || oldWidget.selected != widget.selected) {
+      setState(() {
+        if (hasAvatar || widget.selected == true) {
+          avatarDrawerController.forward();
+        } else {
+          avatarDrawerController.reverse();
+        }
+      });
+    }
+    if (oldWidget.selected != widget.selected) {
+      setState(() {
+        _updateState(MaterialState.selected, widget.selected);
+        if (widget.selected == true) {
+          selectController.forward();
+        } else {
+          selectController.reverse();
+        }
+      });
+    }
+    if (oldWidget.onDeleted != widget.onDeleted) {
+      setState(() {
+        if (hasDeleteButton) {
+          deleteDrawerController.forward();
+        } else {
+          deleteDrawerController.reverse();
+        }
+      });
+    }
+  }
+
+  Widget? _wrapWithTooltip(String? tooltip, VoidCallback? callback, Widget? child) {
+    if(!widget.useDeleteButtonTooltip){
+      return child;
+    }
+    if (child == null || callback == null || tooltip == null) {
+      return child;
+    }
+    return Tooltip(
+      message: tooltip,
+      child: child,
+    );
+  }
+
+  Widget? _buildDeleteIcon(
+    BuildContext context,
+    ThemeData theme,
+    ChipThemeData chipTheme,
+    GlobalKey deleteIconKey,
+  ) {
+    if (!hasDeleteButton) {
+      return null;
+    }
+    return Semantics(
+      container: true,
+      button: true,
+      child: _wrapWithTooltip(
+        widget.deleteButtonTooltipMessage ?? MaterialLocalizations.of(context).deleteButtonTooltip,
+        widget.onDeleted,
+        GestureDetector(
+          key: deleteIconKey,
+          behavior: HitTestBehavior.opaque,
+          onTap: widget.isEnabled
+            ? () {
+                Feedback.forTap(context);
+                widget.onDeleted!();
+            }
+            : null,
+          child: IconTheme(
+            data: theme.iconTheme.copyWith(
+              color: widget.deleteIconColor ?? chipTheme.deleteIconColor,
+            ),
+            child: widget.deleteIcon,
+          ),
+        ),
+      ),
+    );
+  }
+
+  static const double _defaultElevation = 0.0;
+  static const double _defaultPressElevation = 8.0;
+  static const Color _defaultShadowColor = Colors.black;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    assert(debugCheckHasMediaQuery(context));
+    assert(debugCheckHasDirectionality(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+
+    /// The chip at text scale 1 starts with 8px on each side and as text scaling
+    /// gets closer to 2 the label padding is linearly interpolated from 8px to 4px.
+    /// Once the widget has a text scaling of 2 or higher than the label padding
+    /// remains 4px.
+    final EdgeInsetsGeometry _defaultLabelPadding = EdgeInsets.lerp(
+      const EdgeInsets.symmetric(horizontal: 8.0),
+      const EdgeInsets.symmetric(horizontal: 4.0),
+      (MediaQuery.of(context).textScaleFactor - 1.0).clamp(0.0, 1.0),
+    )!;
+
+    final ThemeData theme = Theme.of(context);
+    final ChipThemeData chipTheme = ChipTheme.of(context);
+    final TextDirection? textDirection = Directionality.maybeOf(context);
+    final OutlinedBorder resolvedShape = _getShape(chipTheme);
+    final double elevation = widget.elevation ?? chipTheme.elevation ?? _defaultElevation;
+    final double pressElevation = widget.pressElevation ?? chipTheme.pressElevation ?? _defaultPressElevation;
+    final Color shadowColor = widget.shadowColor ?? chipTheme.shadowColor ?? _defaultShadowColor;
+    final Color selectedShadowColor = widget.selectedShadowColor ?? chipTheme.selectedShadowColor ?? _defaultShadowColor;
+    final Color? checkmarkColor = widget.checkmarkColor ?? chipTheme.checkmarkColor;
+    final bool showCheckmark = widget.showCheckmark ?? chipTheme.showCheckmark ?? true;
+
+    final TextStyle effectiveLabelStyle = chipTheme.labelStyle.merge(widget.labelStyle);
+    final Color? resolvedLabelColor = MaterialStateProperty.resolveAs<Color?>(effectiveLabelStyle.color, _states);
+    final TextStyle resolvedLabelStyle = effectiveLabelStyle.copyWith(color: resolvedLabelColor);
+    final EdgeInsetsGeometry labelPadding = widget.labelPadding ?? chipTheme.labelPadding ?? _defaultLabelPadding;
+
+    Widget result = Material(
+      elevation: isTapping ? pressElevation : elevation,
+      shadowColor: widget.selected ? selectedShadowColor : shadowColor,
+      animationDuration: pressedAnimationDuration,
+      shape: resolvedShape,
+      clipBehavior: widget.clipBehavior,
+      child: InkWell(
+        onFocusChange: _handleFocus,
+        focusNode: widget.focusNode,
+        autofocus: widget.autofocus,
+        canRequestFocus: widget.isEnabled,
+        onTap: canTap ? _handleTap : null,
+        onTapDown: canTap ? _handleTapDown : null,
+        onTapCancel: canTap ? _handleTapCancel : null,
+        onHover: canTap ? _handleHover : null,
+        splashFactory: _LocationAwareInkRippleFactory(
+            hasDeleteButton,
+            context,
+            deleteIconKey,
+        ),
+        customBorder: resolvedShape,
+        child: AnimatedBuilder(
+          animation: Listenable.merge(<Listenable>[selectController, enableController]),
+          builder: (BuildContext context, Widget? child) {
+            return Container(
+              decoration: ShapeDecoration(
+                shape: resolvedShape,
+                color: getBackgroundColor(chipTheme),
+              ),
+              child: child,
+            );
+          },
+          child: _wrapWithTooltip(
+            widget.tooltip,
+            widget.onPressed,
+            _ChipRenderWidget(
+              theme: _ChipRenderTheme(
+                label: DefaultTextStyle(
+                  overflow: TextOverflow.fade,
+                  textAlign: TextAlign.start,
+                  maxLines: 1,
+                  softWrap: false,
+                  style: resolvedLabelStyle,
+                  child: widget.label,
+                ),
+                avatar: AnimatedSwitcher(
+                  child: widget.avatar,
+                  duration: _kDrawerDuration,
+                  switchInCurve: Curves.fastOutSlowIn,
+                ),
+                deleteIcon: AnimatedSwitcher(
+                  child: _buildDeleteIcon(context, theme, chipTheme, deleteIconKey),
+                  duration: _kDrawerDuration,
+                  switchInCurve: Curves.fastOutSlowIn,
+                ),
+                brightness: chipTheme.brightness,
+                padding: (widget.padding ?? chipTheme.padding).resolve(textDirection),
+                visualDensity: widget.visualDensity ?? theme.visualDensity,
+                labelPadding: labelPadding.resolve(textDirection),
+                showAvatar: hasAvatar,
+                showCheckmark: showCheckmark,
+                checkmarkColor: checkmarkColor,
+                canTapBody: canTap,
+              ),
+              value: widget.selected,
+              checkmarkAnimation: checkmarkAnimation,
+              enableAnimation: enableAnimation,
+              avatarDrawerAnimation: avatarDrawerAnimation,
+              deleteDrawerAnimation: deleteDrawerAnimation,
+              isEnabled: widget.isEnabled,
+              avatarBorder: widget.avatarBorder,
+            ),
+          ),
+        ),
+      ),
+    );
+    final BoxConstraints constraints;
+    final Offset densityAdjustment = (widget.visualDensity ?? theme.visualDensity).baseSizeAdjustment;
+    switch (widget.materialTapTargetSize ?? theme.materialTapTargetSize) {
+      case MaterialTapTargetSize.padded:
+        constraints = BoxConstraints(minHeight: kMinInteractiveDimension + densityAdjustment.dy);
+        break;
+      case MaterialTapTargetSize.shrinkWrap:
+        constraints = const BoxConstraints();
+        break;
+    }
+    result = _ChipRedirectingHitDetectionWidget(
+      constraints: constraints,
+      child: Center(
+        child: result,
+        widthFactor: 1.0,
+        heightFactor: 1.0,
+      ),
+    );
+    return Semantics(
+      button: widget.tapEnabled,
+      container: true,
+      selected: widget.selected,
+      enabled: widget.tapEnabled ? canTap : null,
+      child: result,
+    );
+  }
+}
+
+/// Redirects the [position.dy] passed to [RenderBox.hitTest] to the vertical
+/// center of the widget.
+///
+/// The primary purpose of this widget is to allow padding around the [RawChip]
+/// to trigger the child ink feature without increasing the size of the material.
+class _ChipRedirectingHitDetectionWidget extends SingleChildRenderObjectWidget {
+  const _ChipRedirectingHitDetectionWidget({
+    Key? key,
+    Widget? child,
+    required this.constraints,
+  }) : super(key: key, child: child);
+
+  final BoxConstraints constraints;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _RenderChipRedirectingHitDetection(constraints);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, covariant _RenderChipRedirectingHitDetection renderObject) {
+    renderObject.additionalConstraints = constraints;
+  }
+}
+
+class _RenderChipRedirectingHitDetection extends RenderConstrainedBox {
+  _RenderChipRedirectingHitDetection(BoxConstraints additionalConstraints) : super(additionalConstraints: additionalConstraints);
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    if (!size.contains(position))
+      return false;
+    // Only redirects hit detection which occurs above and below the render object.
+    // In order to make this assumption true, I have removed the minimum width
+    // constraints, since any reasonable chip would be at least that wide.
+    final Offset offset = Offset(position.dx, size.height / 2);
+    return result.addWithRawTransform(
+      transform: MatrixUtils.forceToPoint(offset),
+      position: position,
+      hitTest: (BoxHitTestResult result, Offset? position) {
+        assert(position == offset);
+        return child!.hitTest(result, position: offset);
+      },
+    );
+  }
+}
+
+class _ChipRenderWidget extends RenderObjectWidget {
+  const _ChipRenderWidget({
+    Key? key,
+    required this.theme,
+    this.value,
+    this.isEnabled,
+    required this.checkmarkAnimation,
+    required this.avatarDrawerAnimation,
+    required this.deleteDrawerAnimation,
+    required this.enableAnimation,
+    this.avatarBorder,
+  }) : assert(theme != null),
+       super(key: key);
+
+  final _ChipRenderTheme theme;
+  final bool? value;
+  final bool? isEnabled;
+  final Animation<double> checkmarkAnimation;
+  final Animation<double> avatarDrawerAnimation;
+  final Animation<double> deleteDrawerAnimation;
+  final Animation<double> enableAnimation;
+  final ShapeBorder? avatarBorder;
+
+  @override
+  _RenderChipElement createElement() => _RenderChipElement(this);
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderChip renderObject) {
+    renderObject
+      ..theme = theme
+      ..textDirection = Directionality.of(context)
+      ..value = value
+      ..isEnabled = isEnabled
+      ..checkmarkAnimation = checkmarkAnimation
+      ..avatarDrawerAnimation = avatarDrawerAnimation
+      ..deleteDrawerAnimation = deleteDrawerAnimation
+      ..enableAnimation = enableAnimation
+      ..avatarBorder = avatarBorder;
+  }
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _RenderChip(
+      theme: theme,
+      textDirection: Directionality.of(context),
+      value: value,
+      isEnabled: isEnabled,
+      checkmarkAnimation: checkmarkAnimation,
+      avatarDrawerAnimation: avatarDrawerAnimation,
+      deleteDrawerAnimation: deleteDrawerAnimation,
+      enableAnimation: enableAnimation,
+      avatarBorder: avatarBorder,
+    );
+  }
+}
+
+enum _ChipSlot {
+  label,
+  avatar,
+  deleteIcon,
+}
+
+class _RenderChipElement extends RenderObjectElement {
+  _RenderChipElement(_ChipRenderWidget chip) : super(chip);
+
+  final Map<_ChipSlot, Element> slotToChild = <_ChipSlot, Element>{};
+
+  @override
+  _ChipRenderWidget get widget => super.widget as _ChipRenderWidget;
+
+  @override
+  _RenderChip get renderObject => super.renderObject as _RenderChip;
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    slotToChild.values.forEach(visitor);
+  }
+
+  @override
+  void forgetChild(Element child) {
+    assert(slotToChild.containsValue(child));
+    assert(child.slot is _ChipSlot);
+    assert(slotToChild.containsKey(child.slot));
+    slotToChild.remove(child.slot);
+    super.forgetChild(child);
+  }
+
+  void _mountChild(Widget widget, _ChipSlot slot) {
+    final Element? oldChild = slotToChild[slot];
+    final Element? newChild = updateChild(oldChild, widget, slot);
+    if (oldChild != null) {
+      slotToChild.remove(slot);
+    }
+    if (newChild != null) {
+      slotToChild[slot] = newChild;
+    }
+  }
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    _mountChild(widget.theme.avatar, _ChipSlot.avatar);
+    _mountChild(widget.theme.deleteIcon, _ChipSlot.deleteIcon);
+    _mountChild(widget.theme.label, _ChipSlot.label);
+  }
+
+  void _updateChild(Widget widget, _ChipSlot slot) {
+    final Element? oldChild = slotToChild[slot];
+    final Element? newChild = updateChild(oldChild, widget, slot);
+    if (oldChild != null) {
+      slotToChild.remove(slot);
+    }
+    if (newChild != null) {
+      slotToChild[slot] = newChild;
+    }
+  }
+
+  @override
+  void update(_ChipRenderWidget newWidget) {
+    super.update(newWidget);
+    assert(widget == newWidget);
+    _updateChild(widget.theme.label, _ChipSlot.label);
+    _updateChild(widget.theme.avatar, _ChipSlot.avatar);
+    _updateChild(widget.theme.deleteIcon, _ChipSlot.deleteIcon);
+  }
+
+  void _updateRenderObject(RenderObject? child, _ChipSlot slot) {
+    switch (slot) {
+      case _ChipSlot.avatar:
+        renderObject.avatar = child as RenderBox?;
+        break;
+      case _ChipSlot.label:
+        renderObject.label = child as RenderBox?;
+        break;
+      case _ChipSlot.deleteIcon:
+        renderObject.deleteIcon = child as RenderBox?;
+        break;
+    }
+  }
+
+  @override
+  void insertRenderObjectChild(RenderObject child, _ChipSlot slot) {
+    assert(child is RenderBox);
+    _updateRenderObject(child, slot);
+    assert(renderObject.children.keys.contains(slot));
+  }
+
+  @override
+  void removeRenderObjectChild(RenderObject child, _ChipSlot slot) {
+    assert(child is RenderBox);
+    assert(renderObject.children[slot] == child);
+    _updateRenderObject(null, slot);
+    assert(!renderObject.children.keys.contains(slot));
+  }
+
+  @override
+  void moveRenderObjectChild(RenderObject child, dynamic oldSlot, dynamic newSlot) {
+    assert(false, 'not reachable');
+  }
+}
+
+@immutable
+class _ChipRenderTheme {
+  const _ChipRenderTheme({
+    required this.avatar,
+    required this.label,
+    required this.deleteIcon,
+    required this.brightness,
+    required this.padding,
+    required this.visualDensity,
+    required this.labelPadding,
+    required this.showAvatar,
+    required this.showCheckmark,
+    required this.checkmarkColor,
+    required this.canTapBody,
+  });
+
+  final Widget avatar;
+  final Widget label;
+  final Widget deleteIcon;
+  final Brightness brightness;
+  final EdgeInsets padding;
+  final VisualDensity visualDensity;
+  final EdgeInsets labelPadding;
+  final bool showAvatar;
+  final bool showCheckmark;
+  final Color? checkmarkColor;
+  final bool canTapBody;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is _ChipRenderTheme
+        && other.avatar == avatar
+        && other.label == label
+        && other.deleteIcon == deleteIcon
+        && other.brightness == brightness
+        && other.padding == padding
+        && other.labelPadding == labelPadding
+        && other.showAvatar == showAvatar
+        && other.showCheckmark == showCheckmark
+        && other.checkmarkColor == checkmarkColor
+        && other.canTapBody == canTapBody;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      avatar,
+      label,
+      deleteIcon,
+      brightness,
+      padding,
+      labelPadding,
+      showAvatar,
+      showCheckmark,
+      checkmarkColor,
+      canTapBody,
+    );
+  }
+}
+
+class _RenderChip extends RenderBox {
+  _RenderChip({
+    required _ChipRenderTheme theme,
+    required TextDirection textDirection,
+    this.value,
+    this.isEnabled,
+    required this.checkmarkAnimation,
+    required this.avatarDrawerAnimation,
+    required this.deleteDrawerAnimation,
+    required this.enableAnimation,
+    this.avatarBorder,
+  }) : assert(theme != null),
+       assert(textDirection != null),
+       _theme = theme,
+       _textDirection = textDirection {
+    checkmarkAnimation.addListener(markNeedsPaint);
+    avatarDrawerAnimation.addListener(markNeedsLayout);
+    deleteDrawerAnimation.addListener(markNeedsLayout);
+    enableAnimation.addListener(markNeedsPaint);
+  }
+
+  final Map<_ChipSlot, RenderBox> children = <_ChipSlot, RenderBox>{};
+
+  bool? value;
+  bool? isEnabled;
+  late Rect _deleteButtonRect;
+  late Rect _pressRect;
+  Animation<double> checkmarkAnimation;
+  Animation<double> avatarDrawerAnimation;
+  Animation<double> deleteDrawerAnimation;
+  Animation<double> enableAnimation;
+  ShapeBorder? avatarBorder;
+
+  RenderBox? _updateChild(RenderBox? oldChild, RenderBox? newChild, _ChipSlot slot) {
+    if (oldChild != null) {
+      dropChild(oldChild);
+      children.remove(slot);
+    }
+    if (newChild != null) {
+      children[slot] = newChild;
+      adoptChild(newChild);
+    }
+    return newChild;
+  }
+
+  RenderBox? _avatar;
+  RenderBox? get avatar => _avatar;
+  set avatar(RenderBox? value) {
+    _avatar = _updateChild(_avatar, value, _ChipSlot.avatar);
+  }
+
+  RenderBox? _deleteIcon;
+  RenderBox? get deleteIcon => _deleteIcon;
+  set deleteIcon(RenderBox? value) {
+    _deleteIcon = _updateChild(_deleteIcon, value, _ChipSlot.deleteIcon);
+  }
+
+  RenderBox? _label;
+  RenderBox? get label => _label;
+  set label(RenderBox? value) {
+    _label = _updateChild(_label, value, _ChipSlot.label);
+  }
+
+  _ChipRenderTheme get theme => _theme;
+  _ChipRenderTheme _theme;
+  set theme(_ChipRenderTheme value) {
+    if (_theme == value) {
+      return;
+    }
+    _theme = value;
+    markNeedsLayout();
+  }
+
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection;
+  set textDirection(TextDirection? value) {
+    if (_textDirection == value) {
+      return;
+    }
+    _textDirection = value;
+    markNeedsLayout();
+  }
+
+  // The returned list is ordered for hit testing.
+  Iterable<RenderBox> get _children sync* {
+    if (avatar != null) {
+      yield avatar!;
+    }
+    if (label != null) {
+      yield label!;
+    }
+    if (deleteIcon != null) {
+      yield deleteIcon!;
+    }
+  }
+
+  bool get isDrawingCheckmark => theme.showCheckmark && !checkmarkAnimation.isDismissed;
+  bool get deleteIconShowing => !deleteDrawerAnimation.isDismissed;
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    for (final RenderBox child in _children) {
+      child.attach(owner);
+    }
+  }
+
+  @override
+  void detach() {
+    super.detach();
+    for (final RenderBox child in _children) {
+      child.detach();
+    }
+  }
+
+  @override
+  void redepthChildren() {
+    _children.forEach(redepthChild);
+  }
+
+  @override
+  void visitChildren(RenderObjectVisitor visitor) {
+    _children.forEach(visitor);
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    final List<DiagnosticsNode> value = <DiagnosticsNode>[];
+    void add(RenderBox? child, String name) {
+      if (child != null) {
+        value.add(child.toDiagnosticsNode(name: name));
+      }
+    }
+
+    add(avatar, 'avatar');
+    add(label, 'label');
+    add(deleteIcon, 'deleteIcon');
+    return value;
+  }
+
+  @override
+  bool get sizedByParent => false;
+
+  static double _minWidth(RenderBox? box, double height) {
+    return box == null ? 0.0 : box.getMinIntrinsicWidth(height);
+  }
+
+  static double _maxWidth(RenderBox? box, double height) {
+    return box == null ? 0.0 : box.getMaxIntrinsicWidth(height);
+  }
+
+  static double _minHeight(RenderBox? box, double width) {
+    return box == null ? 0.0 : box.getMinIntrinsicHeight(width);
+  }
+
+  static Size _boxSize(RenderBox? box) => box == null ? Size.zero : box.size;
+
+  static Rect _boxRect(RenderBox? box) => box == null ? Rect.zero : _boxParentData(box).offset & box.size;
+
+  static BoxParentData _boxParentData(RenderBox box) => box.parentData! as BoxParentData;
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    // The overall padding isn't affected by missing avatar or delete icon
+    // because we add the padding regardless to give extra padding for the label
+    // when they're missing.
+    final double overallPadding = theme.padding.horizontal +
+        theme.labelPadding.horizontal;
+    return overallPadding +
+        _minWidth(avatar, height) +
+        _minWidth(label, height) +
+        _minWidth(deleteIcon, height);
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    final double overallPadding = theme.padding.horizontal +
+        theme.labelPadding.horizontal;
+    return overallPadding +
+        _maxWidth(avatar, height) +
+        _maxWidth(label, height) +
+        _maxWidth(deleteIcon, height);
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    return math.max(
+      _kChipHeight,
+      theme.padding.vertical + theme.labelPadding.vertical + _minHeight(label, width),
+    );
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) => computeMinIntrinsicHeight(width);
+
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    // The baseline of this widget is the baseline of the label.
+    return label!.getDistanceToActualBaseline(baseline);
+  }
+
+  Size _layoutLabel(BoxConstraints contentConstraints, double iconSizes, Size size, Size rawSize, [ChildLayouter layoutChild = ChildLayoutHelper.layoutChild]) {
+    // Now that we know the label height and the width of the icons, we can
+    // determine how much to shrink the width constraints for the "real" layout.
+    if (contentConstraints.maxWidth.isFinite) {
+      final double maxWidth = math.max(
+        0.0,
+        contentConstraints.maxWidth
+        - iconSizes
+        - theme.labelPadding.horizontal
+        - theme.padding.horizontal,
+      );
+      final Size updatedSize = layoutChild(
+        label!,
+        BoxConstraints(
+          minWidth: 0.0,
+          maxWidth: maxWidth,
+          minHeight: rawSize.height,
+          maxHeight: size.height,
+        ),
+      );
+
+      return Size(
+        updatedSize.width + theme.labelPadding.horizontal,
+        updatedSize.height + theme.labelPadding.vertical,
+      );
+    }
+
+    final Size updatedSize = layoutChild(
+      label!,
+      BoxConstraints(
+        minHeight: rawSize.height,
+        maxHeight: size.height,
+        minWidth: 0.0,
+        maxWidth: size.width,
+      ),
+    );
+
+    return Size(
+      updatedSize.width + theme.labelPadding.horizontal,
+      updatedSize.height + theme.labelPadding.vertical,
+    );
+  }
+
+  Size _layoutAvatar(BoxConstraints contentConstraints, double contentSize, [ChildLayouter layoutChild = ChildLayoutHelper.layoutChild]) {
+    final double requestedSize = math.max(0.0, contentSize);
+    final BoxConstraints avatarConstraints = BoxConstraints.tightFor(
+      width: requestedSize,
+      height: requestedSize,
+    );
+    final Size avatarBoxSize = layoutChild(avatar!, avatarConstraints);
+    if (!theme.showCheckmark && !theme.showAvatar) {
+      return Size(0.0, contentSize);
+    }
+    double avatarWidth = 0.0;
+    double avatarHeight = 0.0;
+    if (theme.showAvatar) {
+      avatarWidth += avatarDrawerAnimation.value * avatarBoxSize.width;
+    } else {
+      avatarWidth += avatarDrawerAnimation.value * contentSize;
+    }
+    avatarHeight += avatarBoxSize.height;
+    return Size(avatarWidth, avatarHeight);
+  }
+
+  Size _layoutDeleteIcon(BoxConstraints contentConstraints, double contentSize, [ChildLayouter layoutChild = ChildLayoutHelper.layoutChild]) {
+    final double requestedSize = math.max(0.0, contentSize);
+    final BoxConstraints deleteIconConstraints = BoxConstraints.tightFor(
+      width: requestedSize,
+      height: requestedSize,
+    );
+    final Size boxSize = layoutChild(deleteIcon!, deleteIconConstraints);
+    if (!deleteIconShowing) {
+      return Size(0.0, contentSize);
+    }
+    double deleteIconWidth = 0.0;
+    double deleteIconHeight = 0.0;
+    deleteIconWidth += deleteDrawerAnimation.value * boxSize.width;
+    deleteIconHeight += boxSize.height;
+    return Size(deleteIconWidth, deleteIconHeight);
+  }
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    if (!size.contains(position)) {
+      return false;
+    }
+    final bool tapIsOnDeleteIcon = _tapIsOnDeleteIcon(
+      hasDeleteButton: deleteIcon != null,
+      tapPosition: position,
+      chipSize: size,
+      textDirection: textDirection!,
+    );
+    final RenderBox? hitTestChild = tapIsOnDeleteIcon
+        ? (deleteIcon ?? label ?? avatar)
+        : (label ?? avatar);
+
+    if (hitTestChild != null) {
+      final Offset center = hitTestChild.size.center(Offset.zero);
+      return result.addWithRawTransform(
+        transform: MatrixUtils.forceToPoint(center),
+        position: position,
+        hitTest: (BoxHitTestResult result, Offset? position) {
+          assert(position == center);
+          return hitTestChild.hitTest(result, position: center);
+        },
+      );
+    }
+    return false;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _computeSizes(constraints, ChildLayoutHelper.dryLayoutChild).size;
+  }
+
+  _ChipSizes _computeSizes(BoxConstraints constraints, ChildLayouter layoutChild) {
+    final BoxConstraints contentConstraints = constraints.loosen();
+    // Find out the height of the label within the constraints.
+    final Offset densityAdjustment = Offset(0.0, theme.visualDensity.baseSizeAdjustment.dy / 2.0);
+    final Size rawLabelSize = layoutChild(label!, contentConstraints);
+    final double contentSize = math.max(
+      _kChipHeight - theme.padding.vertical + theme.labelPadding.vertical,
+      rawLabelSize.height + theme.labelPadding.vertical,
+    );
+    final Size avatarSize = _layoutAvatar(contentConstraints, contentSize, layoutChild);
+    final Size deleteIconSize = _layoutDeleteIcon(contentConstraints, contentSize, layoutChild);
+    final Size labelSize = _layoutLabel(
+      contentConstraints,
+      avatarSize.width + deleteIconSize.width,
+      Size(rawLabelSize.width, contentSize),
+      rawLabelSize,
+      layoutChild,
+    );
+
+    // This is the overall size of the content: it doesn't include
+    // theme.padding, that is added in at the end.
+    final Size overallSize = Size(
+      avatarSize.width + labelSize.width + deleteIconSize.width,
+      contentSize,
+    ) + densityAdjustment;
+    final Size paddedSize = Size(
+      overallSize.width + theme.padding.horizontal,
+      overallSize.height + theme.padding.vertical,
+    );
+    return _ChipSizes(
+      size: constraints.constrain(paddedSize),
+      overall: overallSize,
+      content: contentSize,
+      densityAdjustment: densityAdjustment,
+      avatar: avatarSize,
+      label: labelSize,
+      deleteIcon: deleteIconSize,
+    );
+  }
+
+  @override
+  void performLayout() {
+    final _ChipSizes sizes = _computeSizes(constraints, ChildLayoutHelper.layoutChild);
+
+    // Now we have all of the dimensions. Place the children where they belong.
+
+    const double left = 0.0;
+    final double right = sizes.overall.width;
+
+    Offset centerLayout(Size boxSize, double x) {
+      assert(sizes.content >= boxSize.height);
+      switch (textDirection!) {
+        case TextDirection.rtl:
+          return Offset(x - boxSize.width, (sizes.content - boxSize.height + sizes.densityAdjustment.dy) / 2.0);
+        case TextDirection.ltr:
+          return Offset(x, (sizes.content - boxSize.height + sizes.densityAdjustment.dy) / 2.0);
+      }
+    }
+
+    // These are the offsets to the upper left corners of the boxes (including
+    // the child's padding) containing the children, for each child, but not
+    // including the overall padding.
+    Offset avatarOffset = Offset.zero;
+    Offset labelOffset = Offset.zero;
+    Offset deleteIconOffset = Offset.zero;
+    switch (textDirection!) {
+      case TextDirection.rtl:
+        double start = right;
+        if (theme.showCheckmark || theme.showAvatar) {
+          avatarOffset = centerLayout(sizes.avatar, start);
+          start -= sizes.avatar.width;
+        }
+        labelOffset = centerLayout(sizes.label, start);
+        start -= sizes.label.width;
+        if (deleteIconShowing) {
+          _deleteButtonRect = Rect.fromLTWH(
+            0.0,
+            0.0,
+            sizes.deleteIcon.width + theme.padding.right,
+            sizes.overall.height + theme.padding.vertical,
+          );
+          deleteIconOffset = centerLayout(sizes.deleteIcon, start);
+        } else {
+          _deleteButtonRect = Rect.zero;
+        }
+        start -= sizes.deleteIcon.width;
+        if (theme.canTapBody) {
+          _pressRect = Rect.fromLTWH(
+            _deleteButtonRect.width,
+            0.0,
+            sizes.overall.width - _deleteButtonRect.width + theme.padding.horizontal,
+            sizes.overall.height + theme.padding.vertical,
+          );
+        } else {
+          _pressRect = Rect.zero;
+        }
+        break;
+      case TextDirection.ltr:
+        double start = left;
+        if (theme.showCheckmark || theme.showAvatar) {
+          avatarOffset = centerLayout(sizes.avatar, start - _boxSize(avatar).width + sizes.avatar.width);
+          start += sizes.avatar.width;
+        }
+        labelOffset = centerLayout(sizes.label, start);
+        start += sizes.label.width;
+        if (theme.canTapBody) {
+          _pressRect = Rect.fromLTWH(
+            0.0,
+            0.0,
+            deleteIconShowing
+                ? start + theme.padding.left
+                : sizes.overall.width + theme.padding.horizontal,
+            sizes.overall.height + theme.padding.vertical,
+          );
+        } else {
+          _pressRect = Rect.zero;
+        }
+        start -= _boxSize(deleteIcon).width - sizes.deleteIcon.width;
+        if (deleteIconShowing) {
+          deleteIconOffset = centerLayout(sizes.deleteIcon, start);
+          _deleteButtonRect = Rect.fromLTWH(
+            start + theme.padding.left,
+            0.0,
+            sizes.deleteIcon.width + theme.padding.right,
+            sizes.overall.height + theme.padding.vertical,
+          );
+        } else {
+          _deleteButtonRect = Rect.zero;
+        }
+        break;
+    }
+    // Center the label vertically.
+    labelOffset = labelOffset +
+        Offset(
+          0.0,
+          ((sizes.label.height - theme.labelPadding.vertical) - _boxSize(label).height) / 2.0,
+        );
+    _boxParentData(avatar!).offset = theme.padding.topLeft + avatarOffset;
+    _boxParentData(label!).offset = theme.padding.topLeft + labelOffset + theme.labelPadding.topLeft;
+    _boxParentData(deleteIcon!).offset = theme.padding.topLeft + deleteIconOffset;
+    final Size paddedSize = Size(
+      sizes.overall.width + theme.padding.horizontal,
+      sizes.overall.height + theme.padding.vertical,
+    );
+    size = constraints.constrain(paddedSize);
+    assert(
+        size.height == constraints.constrainHeight(paddedSize.height),
+        "Constrained height ${size.height} doesn't match expected height "
+        '${constraints.constrainWidth(paddedSize.height)}');
+    assert(
+        size.width == constraints.constrainWidth(paddedSize.width),
+        "Constrained width ${size.width} doesn't match expected width "
+        '${constraints.constrainWidth(paddedSize.width)}');
+  }
+
+  static final ColorTween selectionScrimTween = ColorTween(
+    begin: Colors.transparent,
+    end: _kSelectScrimColor,
+  );
+
+  Color get _disabledColor {
+    if (enableAnimation == null || enableAnimation.isCompleted) {
+      return Colors.white;
+    }
+    final ColorTween enableTween;
+    switch (theme.brightness) {
+      case Brightness.light:
+        enableTween = ColorTween(
+          begin: Colors.white.withAlpha(_kDisabledAlpha),
+          end: Colors.white,
+        );
+        break;
+      case Brightness.dark:
+        enableTween = ColorTween(
+          begin: Colors.black.withAlpha(_kDisabledAlpha),
+          end: Colors.black,
+        );
+        break;
+    }
+    return enableTween.evaluate(enableAnimation)!;
+  }
+
+  void _paintCheck(Canvas canvas, Offset origin, double size) {
+    Color? paintColor;
+    if (theme.checkmarkColor != null) {
+      paintColor = theme.checkmarkColor;
+    } else {
+      switch (theme.brightness) {
+        case Brightness.light:
+          paintColor = theme.showAvatar ? Colors.white : Colors.black.withAlpha(_kCheckmarkAlpha);
+          break;
+        case Brightness.dark:
+          paintColor = theme.showAvatar ? Colors.black : Colors.white.withAlpha(_kCheckmarkAlpha);
+          break;
+      }
+    }
+
+    final ColorTween fadeTween = ColorTween(begin: Colors.transparent, end: paintColor);
+
+    paintColor = checkmarkAnimation.status == AnimationStatus.reverse
+        ? fadeTween.evaluate(checkmarkAnimation)
+        : paintColor;
+
+    final Paint paint = Paint()
+      ..color = paintColor!
+      ..style = PaintingStyle.stroke
+      ..strokeWidth = _kCheckmarkStrokeWidth * (avatar != null ? avatar!.size.height / 24.0 : 1.0);
+    final double t = checkmarkAnimation.status == AnimationStatus.reverse
+        ? 1.0
+        : checkmarkAnimation.value;
+    if (t == 0.0) {
+      // Nothing to draw.
+      return;
+    }
+    assert(t > 0.0 && t <= 1.0);
+    // As t goes from 0.0 to 1.0, animate the two check mark strokes from the
+    // short side to the long side.
+    final Path path = Path();
+    final Offset start = Offset(size * 0.15, size * 0.45);
+    final Offset mid = Offset(size * 0.4, size * 0.7);
+    final Offset end = Offset(size * 0.85, size * 0.25);
+    if (t < 0.5) {
+      final double strokeT = t * 2.0;
+      final Offset drawMid = Offset.lerp(start, mid, strokeT)!;
+      path.moveTo(origin.dx + start.dx, origin.dy + start.dy);
+      path.lineTo(origin.dx + drawMid.dx, origin.dy + drawMid.dy);
+    } else {
+      final double strokeT = (t - 0.5) * 2.0;
+      final Offset drawEnd = Offset.lerp(mid, end, strokeT)!;
+      path.moveTo(origin.dx + start.dx, origin.dy + start.dy);
+      path.lineTo(origin.dx + mid.dx, origin.dy + mid.dy);
+      path.lineTo(origin.dx + drawEnd.dx, origin.dy + drawEnd.dy);
+    }
+    canvas.drawPath(path, paint);
+  }
+
+  void _paintSelectionOverlay(PaintingContext context, Offset offset) {
+    if (isDrawingCheckmark) {
+      if (theme.showAvatar) {
+        final Rect avatarRect = _boxRect(avatar).shift(offset);
+        final Paint darkenPaint = Paint()
+          ..color = selectionScrimTween.evaluate(checkmarkAnimation)!
+          ..blendMode = BlendMode.srcATop;
+        final Path path =  avatarBorder!.getOuterPath(avatarRect);
+        context.canvas.drawPath(path, darkenPaint);
+      }
+      // Need to make the check mark be a little smaller than the avatar.
+      final double checkSize = avatar!.size.height * 0.75;
+      final Offset checkOffset = _boxParentData(avatar!).offset +
+          Offset(avatar!.size.height * 0.125, avatar!.size.height * 0.125);
+      _paintCheck(context.canvas, offset + checkOffset, checkSize);
+    }
+  }
+
+  void _paintAvatar(PaintingContext context, Offset offset) {
+    void paintWithOverlay(PaintingContext context, Offset offset) {
+      context.paintChild(avatar!, _boxParentData(avatar!).offset + offset);
+      _paintSelectionOverlay(context, offset);
+    }
+
+    if (theme.showAvatar == false && avatarDrawerAnimation.isDismissed) {
+      return;
+    }
+    final Color disabledColor = _disabledColor;
+    final int disabledColorAlpha = disabledColor.alpha;
+    if (needsCompositing) {
+      context.pushLayer(OpacityLayer(alpha: disabledColorAlpha), paintWithOverlay, offset);
+    } else {
+      if (disabledColorAlpha != 0xff) {
+        context.canvas.saveLayer(
+          _boxRect(avatar).shift(offset).inflate(20.0),
+          Paint()..color = disabledColor,
+        );
+      }
+      paintWithOverlay(context, offset);
+      if (disabledColorAlpha != 0xff) {
+        context.canvas.restore();
+      }
+    }
+  }
+
+  void _paintChild(PaintingContext context, Offset offset, RenderBox? child, bool? isEnabled) {
+    if (child == null) {
+      return;
+    }
+    final int disabledColorAlpha = _disabledColor.alpha;
+    if (!enableAnimation.isCompleted) {
+      if (needsCompositing) {
+        context.pushLayer(
+          OpacityLayer(alpha: disabledColorAlpha),
+          (PaintingContext context, Offset offset) {
+            context.paintChild(child, _boxParentData(child).offset + offset);
+          },
+          offset,
+        );
+      } else {
+        final Rect childRect = _boxRect(child).shift(offset);
+        context.canvas.saveLayer(childRect.inflate(20.0), Paint()..color = _disabledColor);
+        context.paintChild(child, _boxParentData(child).offset + offset);
+        context.canvas.restore();
+      }
+    } else {
+      context.paintChild(child, _boxParentData(child).offset + offset);
+    }
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    _paintAvatar(context, offset);
+    if (deleteIconShowing) {
+      _paintChild(context, offset, deleteIcon, isEnabled);
+    }
+    _paintChild(context, offset, label, isEnabled);
+  }
+
+  // Set this to true to have outlines of the tap targets drawn over
+  // the chip.  This should never be checked in while set to 'true'.
+  static const bool _debugShowTapTargetOutlines = false;
+
+  @override
+  void debugPaint(PaintingContext context, Offset offset) {
+    assert(!_debugShowTapTargetOutlines || () {
+      // Draws a rect around the tap targets to help with visualizing where
+      // they really are.
+      final Paint outlinePaint = Paint()
+        ..color = const Color(0xff800000)
+        ..strokeWidth = 1.0
+        ..style = PaintingStyle.stroke;
+      if (deleteIconShowing) {
+        context.canvas.drawRect(_deleteButtonRect.shift(offset), outlinePaint);
+      }
+      context.canvas.drawRect(
+        _pressRect.shift(offset),
+        outlinePaint..color = const Color(0xff008000),
+      );
+      return true;
+    }());
+  }
+
+  @override
+  bool hitTestSelf(Offset position) => _deleteButtonRect.contains(position) || _pressRect.contains(position);
+}
+
+class _ChipSizes {
+  _ChipSizes({
+    required this.size,
+    required this.overall,
+    required this.content,
+    required this.avatar,
+    required this.label,
+    required this.deleteIcon,
+    required this.densityAdjustment,
+});
+  final Size size;
+  final Size overall;
+  final double content;
+  final Size avatar;
+  final Size label;
+  final Size deleteIcon;
+  final Offset densityAdjustment;
+}
+
+class _LocationAwareInkRippleFactory extends InteractiveInkFeatureFactory {
+  const _LocationAwareInkRippleFactory(
+    this.hasDeleteButton,
+    this.chipContext,
+    this.deleteIconKey,
+  );
+
+  final bool hasDeleteButton;
+  final BuildContext chipContext;
+  final GlobalKey deleteIconKey;
+
+  @override
+  InteractiveInkFeature create({
+    required MaterialInkController controller,
+    required RenderBox referenceBox,
+    required Offset position,
+    required Color color,
+    required TextDirection textDirection,
+    bool containedInkWell = false,
+    RectCallback? rectCallback,
+    BorderRadius? borderRadius,
+    ShapeBorder? customBorder,
+    double? radius,
+    VoidCallback? onRemoved,
+  }) {
+
+    final bool tapIsOnDeleteIcon = _tapIsOnDeleteIcon(
+      hasDeleteButton: hasDeleteButton,
+      tapPosition: position,
+      chipSize: chipContext.size!,
+      textDirection: textDirection,
+    );
+
+    final BuildContext splashContext = tapIsOnDeleteIcon
+        ? deleteIconKey.currentContext!
+        : chipContext;
+
+    final InteractiveInkFeatureFactory splashFactory = Theme.of(splashContext).splashFactory;
+
+    if (tapIsOnDeleteIcon) {
+      final RenderBox currentBox = referenceBox;
+      referenceBox = deleteIconKey.currentContext!.findRenderObject()! as RenderBox;
+      position = referenceBox.globalToLocal(currentBox.localToGlobal(position));
+      containedInkWell = false;
+    }
+
+    return splashFactory.create(
+      controller: controller,
+      referenceBox: referenceBox,
+      position: position,
+      color: color,
+      textDirection: textDirection,
+      containedInkWell: containedInkWell,
+      rectCallback: rectCallback,
+      borderRadius: borderRadius,
+      customBorder: customBorder,
+      radius: radius,
+      onRemoved: onRemoved,
+    );
+  }
+}
+
+bool _tapIsOnDeleteIcon({
+  required bool hasDeleteButton,
+  required Offset tapPosition,
+  required Size chipSize,
+  required TextDirection textDirection,
+}) {
+  bool tapIsOnDeleteIcon;
+  if (!hasDeleteButton) {
+    tapIsOnDeleteIcon = false;
+  } else {
+    switch (textDirection) {
+      case TextDirection.ltr:
+        tapIsOnDeleteIcon = tapPosition.dx / chipSize.width > 0.66;
+        break;
+      case TextDirection.rtl:
+        tapIsOnDeleteIcon = tapPosition.dx / chipSize.width < 0.33;
+        break;
+    }
+  }
+  return tapIsOnDeleteIcon;
+}
diff --git a/lib/src/material/chip_theme.dart b/lib/src/material/chip_theme.dart
new file mode 100644
index 0000000..151c57a
--- /dev/null
+++ b/lib/src/material/chip_theme.dart
@@ -0,0 +1,579 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// Applies a chip theme to descendant [RawChip]-based widgets, like [Chip],
+/// [InputChip], [ChoiceChip], [FilterChip], and [ActionChip].
+///
+/// A chip theme describes the color, shape and text styles for the chips it is
+/// applied to.
+///
+/// Descendant widgets obtain the current theme's [ChipThemeData] object using
+/// [ChipTheme.of]. When a widget uses [ChipTheme.of], it is automatically
+/// rebuilt if the theme later changes.
+///
+/// The [ThemeData] object given by the [Theme.of] call also contains a default
+/// [ThemeData.chipTheme] that can be customized by copying it (using
+/// [ChipThemeData.copyWith]).
+///
+/// See also:
+///
+///  * [Chip], a chip that displays information and can be deleted.
+///  * [InputChip], a chip that represents a complex piece of information, such
+///    as an entity (person, place, or thing) or conversational text, in a
+///    compact form.
+///  * [ChoiceChip], allows a single selection from a set of options. Choice
+///    chips contain related descriptive text or categories.
+///  * [FilterChip], uses tags or descriptive words as a way to filter content.
+///  * [ActionChip], represents an action related to primary content.
+///  * [ChipThemeData], which describes the actual configuration of a chip
+///    theme.
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+class ChipTheme extends InheritedTheme {
+  /// Applies the given theme [data] to [child].
+  ///
+  /// The [data] and [child] arguments must not be null.
+  const ChipTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(child != null),
+       assert(data != null),
+       super(key: key, child: child);
+
+  /// Specifies the color, shape, and text style values for descendant chip
+  /// widgets.
+  final ChipThemeData data;
+
+  /// Returns the data from the closest [ChipTheme] instance that encloses
+  /// the given context.
+  ///
+  /// Defaults to the ambient [ThemeData.chipTheme] if there is no
+  /// [ChipTheme] in the given build context.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// class Spaceship extends StatelessWidget {
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return ChipTheme(
+  ///       data: ChipTheme.of(context).copyWith(backgroundColor: Colors.red),
+  ///       child: ActionChip(
+  ///         label: const Text('Launch'),
+  ///         onPressed: () { print('We have liftoff!'); },
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [ChipThemeData], which describes the actual configuration of a chip
+  ///    theme.
+  static ChipThemeData of(BuildContext context) {
+    final ChipTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<ChipTheme>();
+    return inheritedTheme?.data ?? Theme.of(context).chipTheme;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return ChipTheme(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(ChipTheme oldWidget) => data != oldWidget.data;
+}
+
+/// Holds the color, shape, and text styles for a material design chip theme.
+///
+/// Use this class to configure a [ChipTheme] widget, or to set the
+/// [ThemeData.chipTheme] for a [Theme] widget.
+///
+/// To obtain the current ambient chip theme, use [ChipTheme.of].
+///
+/// The parts of a chip are:
+///
+///  * The "avatar", which is a widget that appears at the beginning of the
+///    chip. This is typically a [CircleAvatar] widget.
+///  * The "label", which is the widget displayed in the center of the chip.
+///    Typically this is a [Text] widget.
+///  * The "delete icon", which is a widget that appears at the end of the chip.
+///  * The chip is disabled when it is not accepting user input. Only some chips
+///    have a disabled state: [InputChip], [ChoiceChip], and [FilterChip].
+///
+/// The simplest way to create a ChipThemeData is to use [copyWith] on the one
+/// you get from [ChipTheme.of], or create an entirely new one with
+/// [ChipThemeData.fromDefaults].
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// class CarColor extends StatefulWidget {
+///   @override
+///   State createState() => _CarColorState();
+/// }
+///
+/// class _CarColorState extends State<CarColor> {
+///   Color _color = Colors.red;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return ChipTheme(
+///       data: ChipTheme.of(context).copyWith(backgroundColor: Colors.lightBlue),
+///       child: ChoiceChip(
+///         label: Text('Light Blue'),
+///         onSelected: (bool value) {
+///           setState(() {
+///             _color = value ? Colors.lightBlue : Colors.red;
+///           });
+///         },
+///         selected: _color == Colors.lightBlue,
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Chip], a chip that displays information and can be deleted.
+///  * [InputChip], a chip that represents a complex piece of information, such
+///    as an entity (person, place, or thing) or conversational text, in a
+///    compact form.
+///  * [ChoiceChip], allows a single selection from a set of options. Choice
+///    chips contain related descriptive text or categories.
+///  * [FilterChip], uses tags or descriptive words as a way to filter content.
+///  * [ActionChip], represents an action related to primary content.
+///  * [CircleAvatar], which shows images or initials of entities.
+///  * [Wrap], A widget that displays its children in multiple horizontal or
+///    vertical runs.
+///  * [ChipTheme] widget, which can override the chip theme of its
+///    children.
+///  * [Theme] widget, which performs a similar function to [ChipTheme],
+///    but for overall themes.
+///  * [ThemeData], which has a default [ChipThemeData].
+@immutable
+class ChipThemeData with Diagnosticable {
+  /// Create a [ChipThemeData] given a set of exact values. All the values
+  /// must be specified except for [shadowColor], [selectedShadowColor],
+  /// [elevation], and [pressElevation], which may be null.
+  ///
+  /// This will rarely be used directly. It is used by [lerp] to
+  /// create intermediate themes based on two themes.
+  const ChipThemeData({
+    required this.backgroundColor,
+    this.deleteIconColor,
+    required this.disabledColor,
+    required this.selectedColor,
+    required this.secondarySelectedColor,
+    this.shadowColor,
+    this.selectedShadowColor,
+    this.showCheckmark,
+    this.checkmarkColor,
+    this.labelPadding,
+    required this.padding,
+    this.side,
+    this.shape,
+    required this.labelStyle,
+    required this.secondaryLabelStyle,
+    required this.brightness,
+    this.elevation,
+    this.pressElevation,
+  }) : assert(backgroundColor != null),
+       assert(disabledColor != null),
+       assert(selectedColor != null),
+       assert(secondarySelectedColor != null),
+       assert(padding != null),
+       assert(labelStyle != null),
+       assert(secondaryLabelStyle != null),
+       assert(brightness != null);
+
+  /// Generates a ChipThemeData from a brightness, a primary color, and a text
+  /// style.
+  ///
+  /// The [brightness] is used to select a primary color from the default
+  /// values.
+  ///
+  /// The optional [primaryColor] is used as the base color for the other
+  /// colors. The opacity of the [primaryColor] is ignored. If a [primaryColor]
+  /// is specified, then the [brightness] is ignored, and the theme brightness
+  /// is determined from the [primaryColor].
+  ///
+  /// Only one of [primaryColor] or [brightness] may be specified.
+  ///
+  /// The [secondaryColor] is used for the selection colors needed by
+  /// [ChoiceChip].
+  ///
+  /// This is used to generate the default chip theme for a [ThemeData].
+  factory ChipThemeData.fromDefaults({
+    Brightness? brightness,
+    Color? primaryColor,
+    required Color secondaryColor,
+    required TextStyle labelStyle,
+  }) {
+    assert(primaryColor != null || brightness != null,
+      'One of primaryColor or brightness must be specified');
+    assert(primaryColor == null || brightness == null,
+      'Only one of primaryColor or brightness may be specified');
+    assert(secondaryColor != null);
+    assert(labelStyle != null);
+
+    if (primaryColor != null) {
+      brightness = ThemeData.estimateBrightnessForColor(primaryColor);
+    }
+
+    // These are Material Design defaults, and are used to derive
+    // component Colors (with opacity) from base colors.
+    const int backgroundAlpha = 0x1f; // 12%
+    const int deleteIconAlpha = 0xde; // 87%
+    const int disabledAlpha = 0x0c; // 38% * 12% = 5%
+    const int selectAlpha = 0x3d; // 12% + 12% = 24%
+    const int textLabelAlpha = 0xde; // 87%
+    const EdgeInsetsGeometry padding = EdgeInsets.all(4.0);
+
+    primaryColor = primaryColor ?? (brightness == Brightness.light ? Colors.black : Colors.white);
+    final Color backgroundColor = primaryColor.withAlpha(backgroundAlpha);
+    final Color deleteIconColor = primaryColor.withAlpha(deleteIconAlpha);
+    final Color disabledColor = primaryColor.withAlpha(disabledAlpha);
+    final Color selectedColor = primaryColor.withAlpha(selectAlpha);
+    final Color secondarySelectedColor = secondaryColor.withAlpha(selectAlpha);
+    final TextStyle secondaryLabelStyle = labelStyle.copyWith(
+      color: secondaryColor.withAlpha(textLabelAlpha),
+    );
+    labelStyle = labelStyle.copyWith(color: primaryColor.withAlpha(textLabelAlpha));
+
+    return ChipThemeData(
+      backgroundColor: backgroundColor,
+      deleteIconColor: deleteIconColor,
+      disabledColor: disabledColor,
+      selectedColor: selectedColor,
+      secondarySelectedColor: secondarySelectedColor,
+      padding: padding,
+      labelStyle: labelStyle,
+      secondaryLabelStyle: secondaryLabelStyle,
+      brightness: brightness!,
+    );
+  }
+
+  /// Color to be used for the unselected, enabled chip's background.
+  ///
+  /// The default is light grey.
+  final Color backgroundColor;
+
+  /// The [Color] for the delete icon. The default is Color(0xde000000)
+  /// (slightly transparent black) for light themes, and Color(0xdeffffff)
+  /// (slightly transparent white) for dark themes.
+  ///
+  /// May be set to null, in which case the ambient [IconThemeData.color] is used.
+  final Color? deleteIconColor;
+
+  /// Color to be used for the chip's background indicating that it is disabled.
+  ///
+  /// The chip is disabled when [DisabledChipAttributes.isEnabled] is false, or
+  /// all three of [SelectableChipAttributes.onSelected],
+  /// [TappableChipAttributes.onPressed], and
+  /// [DeletableChipAttributes.onDeleted] are null.
+  ///
+  /// It defaults to [Colors.black38].
+  final Color disabledColor;
+
+  /// Color to be used for the chip's background, indicating that it is
+  /// selected.
+  ///
+  /// The chip is selected when [SelectableChipAttributes.selected] is true.
+  final Color selectedColor;
+
+  /// An alternate color to be used for the chip's background, indicating that
+  /// it is selected. For example, this color is used by [ChoiceChip] when the
+  /// choice is selected.
+  ///
+  /// The chip is selected when [SelectableChipAttributes.selected] is true.
+  final Color secondarySelectedColor;
+
+  /// Color of the chip's shadow when the elevation is greater than 0.
+  ///
+  /// If null, the chip defaults to [Colors.black].
+  ///
+  /// See also:
+  ///
+  ///  * [selectedShadowColor]
+  final Color? shadowColor;
+
+  /// Color of the chip's shadow when the elevation is greater than 0 and the
+  /// chip is selected.
+  ///
+  /// If null, the chip defaults to [Colors.black].
+  ///
+  /// See also:
+  ///
+  ///  * [shadowColor]
+  final Color? selectedShadowColor;
+
+  /// Whether or not to show a check mark when [SelectableChipAttributes.selected] is true.
+  ///
+  /// For instance, the [ChoiceChip] sets this to false so that it can be
+  /// selected without showing the check mark.
+  ///
+  /// Defaults to true.
+  final bool? showCheckmark;
+
+  /// Color of the chip's check mark when a check mark is visible.
+  ///
+  /// This will override the color set by the platform's brightness setting.
+  final Color? checkmarkColor;
+
+  /// The padding around the [Chip.label] widget.
+  ///
+  /// By default, this is 4 logical pixels at the beginning and the end of the
+  /// label, and zero on top and bottom.
+  final EdgeInsetsGeometry? labelPadding;
+
+  /// The padding between the contents of the chip and the outside [shape].
+  ///
+  /// Defaults to 4 logical pixels on all sides.
+  final EdgeInsetsGeometry padding;
+
+  /// The color and weight of the chip's outline.
+  ///
+  /// If null, the chip defaults to the border side of [shape].
+  ///
+  /// This value is combined with [shape] to create a shape decorated with an
+  /// outline. If it is a [MaterialStateBorderSide],
+  /// [MaterialStateProperty.resolve] is used for the following
+  /// [MaterialState]s:
+  ///
+  ///  * [MaterialState.disabled].
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.pressed].
+  final BorderSide? side;
+
+  /// The border to draw around the chip.
+  ///
+  /// If null, the chip defaults to a [StadiumBorder].
+  ///
+  /// This shape is combined with [side] to create a shape decorated with an
+  /// outline. If it is a [MaterialStateOutlinedBorder],
+  /// [MaterialStateProperty.resolve] is used for the following
+  /// [MaterialState]s:
+  ///
+  ///  * [MaterialState.disabled].
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.pressed].
+  final OutlinedBorder? shape;
+
+  /// The style to be applied to the chip's label.
+  ///
+  /// This only has an effect on label widgets that respect the
+  /// [DefaultTextStyle], such as [Text].
+  final TextStyle labelStyle;
+
+  /// An alternate style to be applied to the chip's label. For example, this
+  /// style is applied to the text of a selected [ChoiceChip].
+  ///
+  /// This only has an effect on label widgets that respect the
+  /// [DefaultTextStyle], such as [Text].
+  final TextStyle secondaryLabelStyle;
+
+  /// The brightness setting for this theme.
+  ///
+  /// This affects various base material color choices in the chip rendering.
+  final Brightness brightness;
+
+  /// The elevation to be applied to the chip.
+  ///
+  /// If null, the chip defaults to 0.
+  final double? elevation;
+
+  /// The elevation to be applied to the chip during the press motion.
+  ///
+  /// If null, the chip defaults to 8.
+  final double? pressElevation;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  ChipThemeData copyWith({
+    Color? backgroundColor,
+    Color? deleteIconColor,
+    Color? disabledColor,
+    Color? selectedColor,
+    Color? secondarySelectedColor,
+    Color? shadowColor,
+    Color? selectedShadowColor,
+    Color? checkmarkColor,
+    EdgeInsetsGeometry? labelPadding,
+    EdgeInsetsGeometry? padding,
+    BorderSide? side,
+    OutlinedBorder? shape,
+    TextStyle? labelStyle,
+    TextStyle? secondaryLabelStyle,
+    Brightness? brightness,
+    double? elevation,
+    double? pressElevation,
+  }) {
+    return ChipThemeData(
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      deleteIconColor: deleteIconColor ?? this.deleteIconColor,
+      disabledColor: disabledColor ?? this.disabledColor,
+      selectedColor: selectedColor ?? this.selectedColor,
+      secondarySelectedColor: secondarySelectedColor ?? this.secondarySelectedColor,
+      shadowColor: shadowColor ?? this.shadowColor,
+      selectedShadowColor: selectedShadowColor ?? this.selectedShadowColor,
+      checkmarkColor: checkmarkColor ?? this.checkmarkColor,
+      labelPadding: labelPadding ?? this.labelPadding,
+      padding: padding ?? this.padding,
+      side: side ?? this.side,
+      shape: shape ?? this.shape,
+      labelStyle: labelStyle ?? this.labelStyle,
+      secondaryLabelStyle: secondaryLabelStyle ?? this.secondaryLabelStyle,
+      brightness: brightness ?? this.brightness,
+      elevation: elevation ?? this.elevation,
+      pressElevation: pressElevation ?? this.pressElevation,
+    );
+  }
+
+  /// Linearly interpolate between two chip themes.
+  ///
+  /// The arguments must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static ChipThemeData? lerp(ChipThemeData? a, ChipThemeData? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    return ChipThemeData(
+      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t)!,
+      deleteIconColor: Color.lerp(a?.deleteIconColor, b?.deleteIconColor, t),
+      disabledColor: Color.lerp(a?.disabledColor, b?.disabledColor, t)!,
+      selectedColor: Color.lerp(a?.selectedColor, b?.selectedColor, t)!,
+      secondarySelectedColor: Color.lerp(a?.secondarySelectedColor, b?.secondarySelectedColor, t)!,
+      shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
+      selectedShadowColor: Color.lerp(a?.selectedShadowColor, b?.selectedShadowColor, t),
+      checkmarkColor: Color.lerp(a?.checkmarkColor, b?.checkmarkColor, t),
+      labelPadding: EdgeInsetsGeometry.lerp(a?.labelPadding, b?.labelPadding, t),
+      padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t)!,
+      side: _lerpSides(a?.side, b?.side, t),
+      shape: _lerpShapes(a?.shape, b?.shape, t),
+      labelStyle: TextStyle.lerp(a?.labelStyle, b?.labelStyle, t)!,
+      secondaryLabelStyle: TextStyle.lerp(a?.secondaryLabelStyle, b?.secondaryLabelStyle, t)!,
+      brightness: t < 0.5 ? a?.brightness ?? Brightness.light : b?.brightness ?? Brightness.light,
+      elevation: lerpDouble(a?.elevation, b?.elevation, t),
+      pressElevation: lerpDouble(a?.pressElevation, b?.pressElevation, t),
+    );
+  }
+
+  // Special case because BorderSide.lerp() doesn't support null arguments.
+  static BorderSide? _lerpSides(BorderSide? a, BorderSide? b, double t) {
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return BorderSide.lerp(BorderSide(width: 0, color: b!.color.withAlpha(0)), b, t);
+    if (b == null)
+      return BorderSide.lerp(BorderSide(width: 0, color: a.color.withAlpha(0)), a, t);
+    return BorderSide.lerp(a, b, t);
+  }
+
+  // TODO(perclasson): OutlinedBorder needs a lerp method - https://github.com/flutter/flutter/issues/60555.
+  static OutlinedBorder? _lerpShapes(OutlinedBorder? a, OutlinedBorder? b, double t) {
+    if (a == null && b == null)
+      return null;
+    return ShapeBorder.lerp(a, b, t) as OutlinedBorder?;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      backgroundColor,
+      deleteIconColor,
+      disabledColor,
+      selectedColor,
+      secondarySelectedColor,
+      shadowColor,
+      selectedShadowColor,
+      checkmarkColor,
+      labelPadding,
+      padding,
+      side,
+      shape,
+      labelStyle,
+      secondaryLabelStyle,
+      brightness,
+      elevation,
+      pressElevation,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is ChipThemeData
+        && other.backgroundColor == backgroundColor
+        && other.deleteIconColor == deleteIconColor
+        && other.disabledColor == disabledColor
+        && other.selectedColor == selectedColor
+        && other.secondarySelectedColor == secondarySelectedColor
+        && other.shadowColor == shadowColor
+        && other.selectedShadowColor == selectedShadowColor
+        && other.checkmarkColor == checkmarkColor
+        && other.labelPadding == labelPadding
+        && other.padding == padding
+        && other.side == side
+        && other.shape == shape
+        && other.labelStyle == labelStyle
+        && other.secondaryLabelStyle == secondaryLabelStyle
+        && other.brightness == brightness
+        && other.elevation == elevation
+        && other.pressElevation == pressElevation;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    final ThemeData defaultTheme = ThemeData.fallback();
+    final ChipThemeData defaultData = ChipThemeData.fromDefaults(
+      secondaryColor: defaultTheme.primaryColor,
+      brightness: defaultTheme.brightness,
+      labelStyle: defaultTheme.textTheme.bodyText1!,
+    );
+    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: defaultData.backgroundColor));
+    properties.add(ColorProperty('deleteIconColor', deleteIconColor, defaultValue: defaultData.deleteIconColor));
+    properties.add(ColorProperty('disabledColor', disabledColor, defaultValue: defaultData.disabledColor));
+    properties.add(ColorProperty('selectedColor', selectedColor, defaultValue: defaultData.selectedColor));
+    properties.add(ColorProperty('secondarySelectedColor', secondarySelectedColor, defaultValue: defaultData.secondarySelectedColor));
+    properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: defaultData.shadowColor));
+    properties.add(ColorProperty('selectedShadowColor', selectedShadowColor, defaultValue: defaultData.selectedShadowColor));
+    properties.add(ColorProperty('checkMarkColor', checkmarkColor, defaultValue: defaultData.checkmarkColor));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('labelPadding', labelPadding, defaultValue: defaultData.labelPadding));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: defaultData.padding));
+    properties.add(DiagnosticsProperty<BorderSide>('side', side, defaultValue: defaultData.side));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: defaultData.shape));
+    properties.add(DiagnosticsProperty<TextStyle>('labelStyle', labelStyle, defaultValue: defaultData.labelStyle));
+    properties.add(DiagnosticsProperty<TextStyle>('secondaryLabelStyle', secondaryLabelStyle, defaultValue: defaultData.secondaryLabelStyle));
+    properties.add(EnumProperty<Brightness>('brightness', brightness, defaultValue: defaultData.brightness));
+    properties.add(DoubleProperty('elevation', elevation, defaultValue: defaultData.elevation));
+    properties.add(DoubleProperty('pressElevation', pressElevation, defaultValue: defaultData.pressElevation));
+  }
+}
diff --git a/lib/src/material/circle_avatar.dart b/lib/src/material/circle_avatar.dart
new file mode 100644
index 0000000..5c7d8f8
--- /dev/null
+++ b/lib/src/material/circle_avatar.dart
@@ -0,0 +1,238 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'constants.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+// Examples can assume:
+// late String userAvatarUrl;
+
+/// A circle that represents a user.
+///
+/// Typically used with a user's profile image, or, in the absence of
+/// such an image, the user's initials. A given user's initials should
+/// always be paired with the same background color, for consistency.
+///
+/// The [onBackgroundImageError] parameter must be null if the [backgroundImage]
+/// is null.
+///
+/// {@tool snippet}
+///
+/// If the avatar is to have an image, the image should be specified in the
+/// [backgroundImage] property:
+///
+/// ```dart
+/// CircleAvatar(
+///   backgroundImage: NetworkImage(userAvatarUrl),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// The image will be cropped to have a circle shape.
+///
+/// {@tool snippet}
+///
+/// If the avatar is to just have the user's initials, they are typically
+/// provided using a [Text] widget as the [child] and a [backgroundColor]:
+///
+/// ```dart
+/// CircleAvatar(
+///   backgroundColor: Colors.brown.shade800,
+///   child: Text('AH'),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Chip], for representing users or concepts in long form.
+///  * [ListTile], which can combine an icon (such as a [CircleAvatar]) with
+///    some text for a fixed height list entry.
+///  * <https://material.io/design/components/chips.html#input-chips>
+class CircleAvatar extends StatelessWidget {
+  /// Creates a circle that represents a user.
+  const CircleAvatar({
+    Key? key,
+    this.child,
+    this.backgroundColor,
+    this.backgroundImage,
+    this.onBackgroundImageError,
+    this.foregroundColor,
+    this.radius,
+    this.minRadius,
+    this.maxRadius,
+  }) : assert(radius == null || (minRadius == null && maxRadius == null)),
+       assert(backgroundImage != null || onBackgroundImageError == null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// Typically a [Text] widget. If the [CircleAvatar] is to have an image, use
+  /// [backgroundImage] instead.
+  final Widget? child;
+
+  /// The color with which to fill the circle. Changing the background
+  /// color will cause the avatar to animate to the new color.
+  ///
+  /// If a [backgroundColor] is not specified, the theme's
+  /// [ThemeData.primaryColorLight] is used with dark foreground colors, and
+  /// [ThemeData.primaryColorDark] with light foreground colors.
+  final Color? backgroundColor;
+
+  /// The default text color for text in the circle.
+  ///
+  /// Defaults to the primary text theme color if no [backgroundColor] is
+  /// specified.
+  ///
+  /// Defaults to [ThemeData.primaryColorLight] for dark background colors, and
+  /// [ThemeData.primaryColorDark] for light background colors.
+  final Color? foregroundColor;
+
+  /// The background image of the circle. Changing the background
+  /// image will cause the avatar to animate to the new image.
+  ///
+  /// If the [CircleAvatar] is to have the user's initials, use [child] instead.
+  final ImageProvider? backgroundImage;
+
+  /// An optional error callback for errors emitted when loading
+  /// [backgroundImage].
+  final ImageErrorListener? onBackgroundImageError;
+
+  /// The size of the avatar, expressed as the radius (half the diameter).
+  ///
+  /// If [radius] is specified, then neither [minRadius] nor [maxRadius] may be
+  /// specified. Specifying [radius] is equivalent to specifying a [minRadius]
+  /// and [maxRadius], both with the value of [radius].
+  ///
+  /// If neither [minRadius] nor [maxRadius] are specified, defaults to 20
+  /// logical pixels. This is the appropriate size for use with
+  /// [ListTile.leading].
+  ///
+  /// Changes to the [radius] are animated (including changing from an explicit
+  /// [radius] to a [minRadius]/[maxRadius] pair or vice versa).
+  final double? radius;
+
+  /// The minimum size of the avatar, expressed as the radius (half the
+  /// diameter).
+  ///
+  /// If [minRadius] is specified, then [radius] must not also be specified.
+  ///
+  /// Defaults to zero.
+  ///
+  /// Constraint changes are animated, but size changes due to the environment
+  /// itself changing are not. For example, changing the [minRadius] from 10 to
+  /// 20 when the [CircleAvatar] is in an unconstrained environment will cause
+  /// the avatar to animate from a 20 pixel diameter to a 40 pixel diameter.
+  /// However, if the [minRadius] is 40 and the [CircleAvatar] has a parent
+  /// [SizedBox] whose size changes instantaneously from 20 pixels to 40 pixels,
+  /// the size will snap to 40 pixels instantly.
+  final double? minRadius;
+
+  /// The maximum size of the avatar, expressed as the radius (half the
+  /// diameter).
+  ///
+  /// If [maxRadius] is specified, then [radius] must not also be specified.
+  ///
+  /// Defaults to [double.infinity].
+  ///
+  /// Constraint changes are animated, but size changes due to the environment
+  /// itself changing are not. For example, changing the [maxRadius] from 10 to
+  /// 20 when the [CircleAvatar] is in an unconstrained environment will cause
+  /// the avatar to animate from a 20 pixel diameter to a 40 pixel diameter.
+  /// However, if the [maxRadius] is 40 and the [CircleAvatar] has a parent
+  /// [SizedBox] whose size changes instantaneously from 20 pixels to 40 pixels,
+  /// the size will snap to 40 pixels instantly.
+  final double? maxRadius;
+
+  // The default radius if nothing is specified.
+  static const double _defaultRadius = 20.0;
+
+  // The default min if only the max is specified.
+  static const double _defaultMinRadius = 0.0;
+
+  // The default max if only the min is specified.
+  static const double _defaultMaxRadius = double.infinity;
+
+  double get _minDiameter {
+    if (radius == null && minRadius == null && maxRadius == null) {
+      return _defaultRadius * 2.0;
+    }
+    return 2.0 * (radius ?? minRadius ?? _defaultMinRadius);
+  }
+
+  double get _maxDiameter {
+    if (radius == null && minRadius == null && maxRadius == null) {
+      return _defaultRadius * 2.0;
+    }
+    return 2.0 * (radius ?? maxRadius ?? _defaultMaxRadius);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    final ThemeData theme = Theme.of(context);
+    TextStyle textStyle = theme.primaryTextTheme.subtitle1!.copyWith(color: foregroundColor);
+    Color? effectiveBackgroundColor = backgroundColor;
+    if (effectiveBackgroundColor == null) {
+      switch (ThemeData.estimateBrightnessForColor(textStyle.color!)) {
+        case Brightness.dark:
+          effectiveBackgroundColor = theme.primaryColorLight;
+          break;
+        case Brightness.light:
+          effectiveBackgroundColor = theme.primaryColorDark;
+          break;
+      }
+    } else if (foregroundColor == null) {
+      switch (ThemeData.estimateBrightnessForColor(backgroundColor!)) {
+        case Brightness.dark:
+          textStyle = textStyle.copyWith(color: theme.primaryColorLight);
+          break;
+        case Brightness.light:
+          textStyle = textStyle.copyWith(color: theme.primaryColorDark);
+          break;
+      }
+    }
+    final double minDiameter = _minDiameter;
+    final double maxDiameter = _maxDiameter;
+    return AnimatedContainer(
+      constraints: BoxConstraints(
+        minHeight: minDiameter,
+        minWidth: minDiameter,
+        maxWidth: maxDiameter,
+        maxHeight: maxDiameter,
+      ),
+      duration: kThemeChangeDuration,
+      decoration: BoxDecoration(
+        color: effectiveBackgroundColor,
+        image: backgroundImage != null
+          ? DecorationImage(
+              image: backgroundImage!,
+              onError: onBackgroundImageError,
+              fit: BoxFit.cover,
+            )
+          : null,
+        shape: BoxShape.circle,
+      ),
+      child: child == null
+          ? null
+          : Center(
+              child: MediaQuery(
+                // Need to ignore the ambient textScaleFactor here so that the
+                // text doesn't escape the avatar when the textScaleFactor is large.
+                data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
+                child: IconTheme(
+                  data: theme.iconTheme.copyWith(color: textStyle.color),
+                  child: DefaultTextStyle(
+                    style: textStyle,
+                    child: child!,
+                  ),
+                ),
+              ),
+            ),
+    );
+  }
+}
diff --git a/lib/src/material/color_scheme.dart b/lib/src/material/color_scheme.dart
new file mode 100644
index 0000000..6ca6ae9
--- /dev/null
+++ b/lib/src/material/color_scheme.dart
@@ -0,0 +1,385 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/services.dart' show Brightness;
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'theme_data.dart';
+
+/// A set of twelve colors based on the
+/// [Material spec](https://material.io/design/color/the-color-system.html)
+/// that can be used to configure the color properties of most components.
+///
+/// The [Theme] has a color scheme, [ThemeData.colorScheme], which is constructed
+/// with [ColorScheme.fromSwatch].
+@immutable
+class ColorScheme with Diagnosticable {
+  /// Create a ColorScheme instance.
+  const ColorScheme({
+    required this.primary,
+    required this.primaryVariant,
+    required this.secondary,
+    required this.secondaryVariant,
+    required this.surface,
+    required this.background,
+    required this.error,
+    required this.onPrimary,
+    required this.onSecondary,
+    required this.onSurface,
+    required this.onBackground,
+    required this.onError,
+    required this.brightness,
+  }) : assert(primary != null),
+       assert(primaryVariant != null),
+       assert(secondary != null),
+       assert(secondaryVariant != null),
+       assert(surface != null),
+       assert(background != null),
+       assert(error != null),
+       assert(onPrimary != null),
+       assert(onSecondary != null),
+       assert(onSurface != null),
+       assert(onBackground != null),
+       assert(onError != null),
+       assert(brightness != null);
+
+  /// Create a ColorScheme based on a purple primary color that matches the
+  /// [baseline Material color scheme](https://material.io/design/color/the-color-system.html#color-theme-creation).
+  const ColorScheme.light({
+    this.primary = const Color(0xff6200ee),
+    this.primaryVariant = const Color(0xff3700b3),
+    this.secondary = const Color(0xff03dac6),
+    this.secondaryVariant = const Color(0xff018786),
+    this.surface = Colors.white,
+    this.background = Colors.white,
+    this.error = const Color(0xffb00020),
+    this.onPrimary = Colors.white,
+    this.onSecondary = Colors.black,
+    this.onSurface = Colors.black,
+    this.onBackground = Colors.black,
+    this.onError = Colors.white,
+    this.brightness = Brightness.light,
+  }) : assert(primary != null),
+       assert(primaryVariant != null),
+       assert(secondary != null),
+       assert(secondaryVariant != null),
+       assert(surface != null),
+       assert(background != null),
+       assert(error != null),
+       assert(onPrimary != null),
+       assert(onSecondary != null),
+       assert(onSurface != null),
+       assert(onBackground != null),
+       assert(onError != null),
+       assert(brightness != null);
+
+  /// Create the recommended dark color scheme that matches the
+  /// [baseline Material color scheme](https://material.io/design/color/dark-theme.html#ui-application).
+  const ColorScheme.dark({
+    this.primary = const Color(0xffbb86fc),
+    this.primaryVariant = const Color(0xff3700B3),
+    this.secondary = const Color(0xff03dac6),
+    this.secondaryVariant = const Color(0xff03dac6),
+    this.surface = const Color(0xff121212),
+    this.background = const Color(0xff121212),
+    this.error = const Color(0xffcf6679),
+    this.onPrimary = Colors.black,
+    this.onSecondary = Colors.black,
+    this.onSurface = Colors.white,
+    this.onBackground = Colors.white,
+    this.onError = Colors.black,
+    this.brightness = Brightness.dark,
+  }) : assert(primary != null),
+       assert(primaryVariant != null),
+       assert(secondary != null),
+       assert(secondaryVariant != null),
+       assert(surface != null),
+       assert(background != null),
+       assert(error != null),
+       assert(onPrimary != null),
+       assert(onSecondary != null),
+       assert(onSurface != null),
+       assert(onBackground != null),
+       assert(onError != null),
+       assert(brightness != null);
+
+
+  /// Create a high contrast ColorScheme based on a purple primary color that
+  /// matches the [baseline Material color scheme](https://material.io/design/color/the-color-system.html#color-theme-creation).
+  const ColorScheme.highContrastLight({
+    this.primary = const Color(0xff0000ba),
+    this.primaryVariant = const Color(0xff000088),
+    this.secondary = const Color(0xff66fff9),
+    this.secondaryVariant = const Color(0xff018786),
+    this.surface = Colors.white,
+    this.background = Colors.white,
+    this.error = const Color(0xff790000),
+    this.onPrimary = Colors.white,
+    this.onSecondary = Colors.black,
+    this.onSurface = Colors.black,
+    this.onBackground = Colors.black,
+    this.onError = Colors.white,
+    this.brightness = Brightness.light,
+  }) : assert(primary != null),
+        assert(primaryVariant != null),
+        assert(secondary != null),
+        assert(secondaryVariant != null),
+        assert(surface != null),
+        assert(background != null),
+        assert(error != null),
+        assert(onPrimary != null),
+        assert(onSecondary != null),
+        assert(onSurface != null),
+        assert(onBackground != null),
+        assert(onError != null),
+        assert(brightness != null);
+
+  /// Create a high contrast ColorScheme based on the dark
+  /// [baseline Material color scheme](https://material.io/design/color/dark-theme.html#ui-application).
+  const ColorScheme.highContrastDark({
+    this.primary = const Color(0xffefb7ff),
+    this.primaryVariant = const Color(0xffbe9eff),
+    this.secondary = const Color(0xff66fff9),
+    this.secondaryVariant = const Color(0xff66fff9),
+    this.surface = const Color(0xff121212),
+    this.background = const Color(0xff121212),
+    this.error = const Color(0xff9b374d),
+    this.onPrimary = Colors.black,
+    this.onSecondary = Colors.black,
+    this.onSurface = Colors.white,
+    this.onBackground = Colors.white,
+    this.onError = Colors.black,
+    this.brightness = Brightness.dark,
+  }) : assert(primary != null),
+        assert(primaryVariant != null),
+        assert(secondary != null),
+        assert(secondaryVariant != null),
+        assert(surface != null),
+        assert(background != null),
+        assert(error != null),
+        assert(onPrimary != null),
+        assert(onSecondary != null),
+        assert(onSurface != null),
+        assert(onBackground != null),
+        assert(onError != null),
+        assert(brightness != null);
+
+  /// Create a color scheme from a [MaterialColor] swatch.
+  ///
+  /// This constructor is used by [ThemeData] to create its default
+  /// color scheme.
+  factory ColorScheme.fromSwatch({
+    MaterialColor primarySwatch = Colors.blue,
+    Color? primaryColorDark,
+    Color? accentColor,
+    Color? cardColor,
+    Color? backgroundColor,
+    Color? errorColor,
+    Brightness brightness = Brightness.light,
+  }) {
+    assert(primarySwatch != null);
+    assert(brightness != null);
+
+    final bool isDark = brightness == Brightness.dark;
+    final bool primaryIsDark = _brightnessFor(primarySwatch) == Brightness.dark;
+    final Color secondary = accentColor ?? (isDark ? Colors.tealAccent[200]! : primarySwatch);
+    final bool secondaryIsDark = _brightnessFor(secondary) == Brightness.dark;
+
+    return ColorScheme(
+      primary: primarySwatch,
+      primaryVariant: primaryColorDark ?? (isDark ? Colors.black : primarySwatch[700]!),
+      secondary: secondary,
+      secondaryVariant: isDark ? Colors.tealAccent[700]! : primarySwatch[700]!,
+      surface: cardColor ?? (isDark ? Colors.grey[800]! : Colors.white),
+      background: backgroundColor ?? (isDark ? Colors.grey[700]! : primarySwatch[200]!),
+      error: errorColor ?? Colors.red[700]!,
+      onPrimary: primaryIsDark ? Colors.white : Colors.black,
+      onSecondary: secondaryIsDark ? Colors.white : Colors.black,
+      onSurface: isDark ? Colors.white : Colors.black,
+      onBackground: primaryIsDark ? Colors.white : Colors.black,
+      onError: isDark ? Colors.black : Colors.white,
+      brightness: brightness,
+    );
+  }
+
+  static Brightness _brightnessFor(Color color) => ThemeData.estimateBrightnessForColor(color);
+
+  /// The color displayed most frequently across your app’s screens and components.
+  final Color primary;
+
+  /// A darker version of the primary color.
+  final Color primaryVariant;
+
+  /// An accent color that, when used sparingly, calls attention to parts
+  /// of your app.
+  final Color secondary;
+
+  /// A darker version of the secondary color.
+  final Color secondaryVariant;
+
+  /// The background color for widgets like [Card].
+  final Color surface;
+
+  /// A color that typically appears behind scrollable content.
+  final Color background;
+
+  /// The color to use for input validation errors, e.g. for
+  /// [InputDecoration.errorText].
+  final Color error;
+
+  /// A color that's clearly legible when drawn on [primary].
+  ///
+  /// To ensure that an app is accessible, a contrast ratio of 4.5:1 for [primary]
+  /// and [onPrimary] is recommended. See
+  /// <https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html>.
+  final Color onPrimary;
+
+  /// A color that's clearly legible when drawn on [secondary].
+  ///
+  /// To ensure that an app is accessible, a contrast ratio of 4.5:1 for [secondary]
+  /// and [onSecondary] is recommended. See
+  /// <https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html>.
+  final Color onSecondary;
+
+  /// A color that's clearly legible when drawn on [surface].
+  ///
+  /// To ensure that an app is accessible, a contrast ratio of 4.5:1 for [surface]
+  /// and [onSurface] is recommended. See
+  /// <https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html>.
+  final Color onSurface;
+
+  /// A color that's clearly legible when drawn on [background].
+  ///
+  /// To ensure that an app is accessible, a contrast ratio of 4.5:1 for [background]
+  /// and [onBackground] is recommended. See
+  /// <https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html>.
+  final Color onBackground;
+
+  /// A color that's clearly legible when drawn on [error].
+  ///
+  /// To ensure that an app is accessible, a contrast ratio of 4.5:1 for [error]
+  /// and [onError] is recommended. See
+  /// <https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html>.
+  final Color onError;
+
+  /// The overall brightness of this color scheme.
+  final Brightness brightness;
+
+  /// Creates a copy of this color scheme with the given fields
+  /// replaced by the non-null parameter values.
+  ColorScheme copyWith({
+    Color? primary,
+    Color? primaryVariant,
+    Color? secondary,
+    Color? secondaryVariant,
+    Color? surface,
+    Color? background,
+    Color? error,
+    Color? onPrimary,
+    Color? onSecondary,
+    Color? onSurface,
+    Color? onBackground,
+    Color? onError,
+    Brightness? brightness,
+  }) {
+    return ColorScheme(
+      primary: primary ?? this.primary,
+      primaryVariant: primaryVariant ?? this.primaryVariant,
+      secondary: secondary ?? this.secondary,
+      secondaryVariant: secondaryVariant ?? this.secondaryVariant,
+      surface: surface ?? this.surface,
+      background: background ?? this.background,
+      error: error ?? this.error,
+      onPrimary: onPrimary ?? this.onPrimary,
+      onSecondary: onSecondary ?? this.onSecondary,
+      onSurface: onSurface ?? this.onSurface,
+      onBackground: onBackground ?? this.onBackground,
+      onError: onError ?? this.onError,
+      brightness: brightness ?? this.brightness,
+    );
+  }
+
+  /// Linearly interpolate between two [ColorScheme] objects.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static ColorScheme lerp(ColorScheme a, ColorScheme b, double t) {
+    return ColorScheme(
+      primary: Color.lerp(a.primary, b.primary, t)!,
+      primaryVariant: Color.lerp(a.primaryVariant, b.primaryVariant, t)!,
+      secondary: Color.lerp(a.secondary, b.secondary, t)!,
+      secondaryVariant: Color.lerp(a.secondaryVariant, b.secondaryVariant, t)!,
+      surface: Color.lerp(a.surface, b.surface, t)!,
+      background: Color.lerp(a.background, b.background, t)!,
+      error: Color.lerp(a.error, b.error, t)!,
+      onPrimary: Color.lerp(a.onPrimary, b.onPrimary, t)!,
+      onSecondary: Color.lerp(a.onSecondary, b.onSecondary, t)!,
+      onSurface: Color.lerp(a.onSurface, b.onSurface, t)!,
+      onBackground: Color.lerp(a.onBackground, b.onBackground, t)!,
+      onError: Color.lerp(a.onError, b.onError, t)!,
+      brightness: t < 0.5 ? a.brightness : b.brightness,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ColorScheme
+        && other.primary == primary
+        && other.primaryVariant == primaryVariant
+        && other.secondary == secondary
+        && other.secondaryVariant == secondaryVariant
+        && other.surface == surface
+        && other.background == background
+        && other.error == error
+        && other.onPrimary == onPrimary
+        && other.onSecondary == onSecondary
+        && other.onSurface == onSurface
+        && other.onBackground == onBackground
+        && other.onError == onError
+        && other.brightness == brightness;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      primary,
+      primaryVariant,
+      secondary,
+      secondaryVariant,
+      surface,
+      background,
+      error,
+      onPrimary,
+      onSecondary,
+      onSurface,
+      onBackground,
+      onError,
+      brightness,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    const ColorScheme defaultScheme = ColorScheme.light();
+    properties.add(ColorProperty('primary', primary, defaultValue: defaultScheme.primary));
+    properties.add(ColorProperty('primaryVariant', primaryVariant, defaultValue: defaultScheme.primaryVariant));
+    properties.add(ColorProperty('secondary', secondary, defaultValue: defaultScheme.secondary));
+    properties.add(ColorProperty('secondaryVariant', secondaryVariant, defaultValue: defaultScheme.secondaryVariant));
+    properties.add(ColorProperty('surface', surface, defaultValue: defaultScheme.surface));
+    properties.add(ColorProperty('background', background, defaultValue: defaultScheme.background));
+    properties.add(ColorProperty('error', error, defaultValue: defaultScheme.error));
+    properties.add(ColorProperty('onPrimary', onPrimary, defaultValue: defaultScheme.onPrimary));
+    properties.add(ColorProperty('onSecondary', onSecondary, defaultValue: defaultScheme.onSecondary));
+    properties.add(ColorProperty('onSurface', onSurface, defaultValue: defaultScheme.onSurface));
+    properties.add(ColorProperty('onBackground', onBackground, defaultValue: defaultScheme.onBackground));
+    properties.add(ColorProperty('onError', onError, defaultValue: defaultScheme.onError));
+    properties.add(DiagnosticsProperty<Brightness>('brightness', brightness, defaultValue: defaultScheme.brightness));
+  }
+}
diff --git a/lib/src/material/colors.dart b/lib/src/material/colors.dart
new file mode 100644
index 0000000..7f0df85
--- /dev/null
+++ b/lib/src/material/colors.dart
@@ -0,0 +1,1931 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show Color;
+
+import 'package:flute/painting.dart';
+
+/// Defines a single color as well a color swatch with ten shades of the color.
+///
+/// The color's shades are referred to by index. The greater the index, the
+/// darker the color. There are 10 valid indices: 50, 100, 200, ..., 900.
+/// The value of this color should the same the value of index 500 and [shade500].
+///
+/// See also:
+///
+///  * [Colors], which defines all of the standard material colors.
+class MaterialColor extends ColorSwatch<int> {
+  /// Creates a color swatch with a variety of shades.
+  ///
+  /// The `primary` argument should be the 32 bit ARGB value of one of the
+  /// values in the swatch, as would be passed to the [new Color] constructor
+  /// for that same color, and as is exposed by [value]. (This is distinct from
+  /// the specific index of the color in the swatch.)
+  const MaterialColor(int primary, Map<int, Color> swatch) : super(primary, swatch);
+
+  /// The lightest shade.
+  Color get shade50 => this[50]!;
+
+  /// The second lightest shade.
+  Color get shade100 => this[100]!;
+
+  /// The third lightest shade.
+  Color get shade200 => this[200]!;
+
+  /// The fourth lightest shade.
+  Color get shade300 => this[300]!;
+
+  /// The fifth lightest shade.
+  Color get shade400 => this[400]!;
+
+  /// The default shade.
+  Color get shade500 => this[500]!;
+
+  /// The fourth darkest shade.
+  Color get shade600 => this[600]!;
+
+  /// The third darkest shade.
+  Color get shade700 => this[700]!;
+
+  /// The second darkest shade.
+  Color get shade800 => this[800]!;
+
+  /// The darkest shade.
+  Color get shade900 => this[900]!;
+}
+
+/// Defines a single accent color as well a swatch of four shades of the
+/// accent color.
+///
+/// The color's shades are referred to by index, the colors with smaller
+/// indices are lighter, larger indices are darker. There are four valid
+/// indices: 100, 200, 400, and 700. The value of this color should be the
+/// same as the value of index 200 and [shade200].
+///
+/// See also:
+///
+///  * [Colors], which defines all of the standard material colors.
+///  * <https://material.io/go/design-theming#color-color-schemes>
+class MaterialAccentColor extends ColorSwatch<int> {
+  /// Creates a color swatch with a variety of shades appropriate for accent
+  /// colors.
+  const MaterialAccentColor(int primary, Map<int, Color> swatch) : super(primary, swatch);
+
+  /// The lightest shade.
+  Color get shade50 => this[50]!;
+
+  /// The second lightest shade.
+  Color get shade100 => this[100]!;
+
+  /// The default shade.
+  Color get shade200 => this[200]!;
+
+  /// The second darkest shade.
+  Color get shade400 => this[400]!;
+
+  /// The darkest shade.
+  Color get shade700 => this[700]!;
+}
+
+/// [Color] and [ColorSwatch] constants which represent Material design's
+/// [color palette](https://material.io/design/color/).
+///
+/// Instead of using an absolute color from these palettes, consider using
+/// [Theme.of] to obtain the local [ThemeData] structure, which exposes the
+/// colors selected for the current theme, such as [ThemeData.primaryColor] and
+/// [ThemeData.accentColor] (among many others).
+///
+/// Most swatches have colors from 100 to 900 in increments of one hundred, plus
+/// the color 50. The smaller the number, the more pale the color. The greater
+/// the number, the darker the color. The accent swatches (e.g. [redAccent]) only
+/// have the values 100, 200, 400, and 700.
+///
+/// In addition, a series of blacks and whites with common opacities are
+/// available. For example, [black54] is a pure black with 54% opacity.
+///
+/// {@tool snippet}
+///
+/// To select a specific color from one of the swatches, index into the swatch
+/// using an integer for the specific color desired, as follows:
+///
+/// ```dart
+/// Color selection = Colors.green[400]!; // Selects a mid-range green.
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// Each [ColorSwatch] constant is a color and can used directly. For example:
+///
+/// ```dart
+/// Container(
+///   color: Colors.blue, // same as Colors.blue[500] or Colors.blue.shade500
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ## Color palettes
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.pink.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.pinkAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.red.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.redAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepOrange.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepOrangeAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orange.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orangeAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.amber.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.amberAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.yellow.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.yellowAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lime.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.limeAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightGreen.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightGreenAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.green.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.greenAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.teal.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.tealAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyan.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyanAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightBlue.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightBlueAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blue.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blueAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.indigo.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.indigoAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.purple.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.purpleAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepPurple.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepPurpleAccent.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blueGrey.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.brown.png)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.grey.png)
+///
+/// ## Blacks and whites
+///
+/// These colors are identified by their transparency. The low transparency
+/// levels (e.g. [Colors.white12] and [Colors.white10]) are very hard to see and
+/// should be avoided in general. They are intended for very subtle effects.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blacks.png)
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.whites.png)
+///
+/// The [Colors.transparent] color isn't shown here because it is entirely
+/// invisible!
+///
+/// See also:
+///
+///  * Cookbook: [Use themes to share colors and font styles](https://flutter.dev/docs/cookbook/design/themes)
+class Colors {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  Colors._();
+
+  /// Completely invisible.
+  static const Color transparent = Color(0x00000000);
+
+  /// Completely opaque black.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blacks.png)
+  ///
+  /// See also:
+  ///
+  ///  * [black87], [black54], [black45], [black38], [black26], [black12], which
+  ///    are variants on this color but with different opacities.
+  ///  * [white], a solid white color.
+  ///  * [transparent], a fully-transparent color.
+  static const Color black = Color(0xFF000000);
+
+  /// Black with 87% opacity.
+  ///
+  /// This is a good contrasting color for text in light themes.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blacks.png)
+  ///
+  /// See also:
+  ///
+  ///  * [Typography.black], which uses this color for its text styles.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  ///  * [black], [black54], [black45], [black38], [black26], [black12], which
+  ///    are variants on this color but with different opacities.
+  static const Color black87 = Color(0xDD000000);
+
+  /// Black with 54% opacity.
+  ///
+  /// This is a color commonly used for headings in light themes. It's also used
+  /// as the mask color behind dialogs.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blacks.png)
+  ///
+  /// See also:
+  ///
+  ///  * [Typography.black], which uses this color for its text styles.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  ///  * [black], [black87], [black45], [black38], [black26], [black12], which
+  ///    are variants on this color but with different opacities.
+  static const Color black54 = Color(0x8A000000);
+
+  /// Black with 45% opacity.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blacks.png)
+  ///
+  /// See also:
+  ///
+  ///  * [black], [black87], [black54], [black38], [black26], [black12], which
+  ///    are variants on this color but with different opacities.
+  static const Color black45 = Color(0x73000000);
+
+  /// Black with 38% opacity.
+  ///
+  /// For light themes, i.e. when the Theme's [ThemeData.brightness] is
+  /// [Brightness.light], this color is used for disabled icons and for
+  /// placeholder text in [DataTable].
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blacks.png)
+  ///
+  /// See also:
+  ///
+  ///  * [black], [black87], [black54], [black45], [black26], [black12], which
+  ///    are variants on this color but with different opacities.
+  static const Color black38 = Color(0x61000000);
+
+  /// Black with 26% opacity.
+  ///
+  /// Used for disabled radio buttons and the text of disabled flat buttons in light themes.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blacks.png)
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.disabledColor], which uses this color by default in light themes.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  ///  * [black], [black87], [black54], [black45], [black38], [black12], which
+  ///    are variants on this color but with different opacities.
+  static const Color black26 = Color(0x42000000);
+
+  /// Black with 12% opacity.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blacks.png)
+  ///
+  /// Used for the background of disabled raised buttons in light themes.
+  ///
+  /// See also:
+  ///
+  ///  * [black], [black87], [black54], [black45], [black38], [black26], which
+  ///    are variants on this color but with different opacities.
+  static const Color black12 = Color(0x1F000000);
+
+  /// Completely opaque white.
+  ///
+  /// This is a good contrasting color for the [ThemeData.primaryColor] in the
+  /// dark theme. See [ThemeData.brightness].
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.whites.png)
+  ///
+  /// See also:
+  ///
+  ///  * [Typography.white], which uses this color for its text styles.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  ///  * [white70, white60, white54, white38, white30, white12, white10], which are variants on this color
+  ///    but with different opacities.
+  ///  * [black], a solid black color.
+  ///  * [transparent], a fully-transparent color.
+  static const Color white = Color(0xFFFFFFFF);
+
+  /// White with 70% opacity.
+  ///
+  /// This is a color commonly used for headings in dark themes.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.whites.png)
+  ///
+  /// See also:
+  ///
+  ///  * [Typography.white], which uses this color for its text styles.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  ///  * [white, white60, white54, white38, white30, white12, white10], which are variants on this color
+  ///    but with different opacities.
+  static const Color white70 = Color(0xB3FFFFFF);
+
+  /// White with 60% opacity.
+  ///
+  /// Used for medium-emphasis text and hint text when [ThemeData.brightness] is
+  /// set to [Brightness.dark].
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.whites.png)
+  ///
+  /// See also:
+  ///
+  ///  * [ExpandIcon], which uses this color for dark themes.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  ///  * [white, white54, white30, white38, white12, white10], which are variants on this color
+  ///    but with different opacities.
+  static const Color white60 = Color(0x99FFFFFF);
+
+  /// White with 54% opacity.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.whites.png)
+  ///
+  /// See also:
+  ///
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  ///  * [white, white60, white38, white30, white12, white10], which are variants on this color
+  ///    but with different opacities.
+  static const Color white54 = Color(0x8AFFFFFF);
+
+  /// White with 38% opacity.
+  ///
+  /// Used for disabled radio buttons and the text of disabled flat buttons in dark themes.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.whites.png)
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.disabledColor], which uses this color by default in dark themes.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  ///  * [white, white60, white54, white70, white30, white12, white10], which are variants on this color
+  ///    but with different opacities.
+  static const Color white38 = Color(0x62FFFFFF);
+
+  /// White with 30% opacity.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.whites.png)
+  ///
+  /// See also:
+  ///
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  ///  * [white, white60, white54, white70, white38, white12, white10], which are variants on this color
+  ///    but with different opacities.
+  static const Color white30 = Color(0x4DFFFFFF);
+
+  /// White with 24% opacity.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.whites.png)
+  ///
+  /// Used for the splash color for filled buttons.
+  ///
+  /// See also:
+  ///
+  ///  * [white, white60, white54, white70, white38, white30, white10], which are variants on this color
+  ///    but with different opacities.
+  static const Color white24 = Color(0x3DFFFFFF);
+
+  /// White with 12% opacity.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.whites.png)
+  ///
+  /// Used for the background of disabled raised buttons in dark themes.
+  ///
+  /// See also:
+  ///
+  ///  * [white, white60, white54, white70, white38, white30, white10], which are variants on this color
+  ///    but with different opacities.
+  static const Color white12 = Color(0x1FFFFFFF);
+
+  /// White with 10% opacity.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.whites.png)
+  ///
+  /// See also:
+  ///
+  ///  * [white, white60, white54, white70, white38, white30, white12], which are variants on this color
+  ///    but with different opacities.
+  ///  * [transparent], a fully-transparent color, not far from this one.
+  static const Color white10 = Color(0x1AFFFFFF);
+
+  /// The red primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.red.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.redAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepOrange.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepOrangeAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.pink.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.pinkAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.red[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [redAccent], the corresponding accent colors.
+  ///  * [deepOrange] and [pink], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor red = MaterialColor(
+    _redPrimaryValue,
+    <int, Color>{
+       50: Color(0xFFFFEBEE),
+      100: Color(0xFFFFCDD2),
+      200: Color(0xFFEF9A9A),
+      300: Color(0xFFE57373),
+      400: Color(0xFFEF5350),
+      500: Color(_redPrimaryValue),
+      600: Color(0xFFE53935),
+      700: Color(0xFFD32F2F),
+      800: Color(0xFFC62828),
+      900: Color(0xFFB71C1C),
+    },
+  );
+  static const int _redPrimaryValue = 0xFFF44336;
+
+  /// The red accent swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.red.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.redAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepOrange.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepOrangeAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.pink.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.pinkAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.redAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [red], the corresponding primary colors.
+  ///  * [deepOrangeAccent] and [pinkAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor redAccent = MaterialAccentColor(
+    _redAccentValue,
+    <int, Color>{
+      100: Color(0xFFFF8A80),
+      200: Color(_redAccentValue),
+      400: Color(0xFFFF1744),
+      700: Color(0xFFD50000),
+    },
+  );
+  static const int _redAccentValue = 0xFFFF5252;
+
+  /// The pink primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.pink.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.pinkAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.red.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.redAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.purple.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.purpleAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.pink[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [pinkAccent], the corresponding accent colors.
+  ///  * [red] and [purple], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor pink = MaterialColor(
+    _pinkPrimaryValue,
+    <int, Color>{
+       50: Color(0xFFFCE4EC),
+      100: Color(0xFFF8BBD0),
+      200: Color(0xFFF48FB1),
+      300: Color(0xFFF06292),
+      400: Color(0xFFEC407A),
+      500: Color(_pinkPrimaryValue),
+      600: Color(0xFFD81B60),
+      700: Color(0xFFC2185B),
+      800: Color(0xFFAD1457),
+      900: Color(0xFF880E4F),
+    },
+  );
+  static const int _pinkPrimaryValue = 0xFFE91E63;
+
+  /// The pink accent color swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.pink.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.pinkAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.red.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.redAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.purple.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.purpleAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.pinkAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [pink], the corresponding primary colors.
+  ///  * [redAccent] and [purpleAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor pinkAccent = MaterialAccentColor(
+    _pinkAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFFFF80AB),
+      200: Color(_pinkAccentPrimaryValue),
+      400: Color(0xFFF50057),
+      700: Color(0xFFC51162),
+    },
+  );
+  static const int _pinkAccentPrimaryValue = 0xFFFF4081;
+
+  /// The purple primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.purple.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.purpleAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepPurple.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepPurpleAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.pink.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.pinkAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.purple[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [purpleAccent], the corresponding accent colors.
+  ///  * [deepPurple] and [pink], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor purple = MaterialColor(
+    _purplePrimaryValue,
+    <int, Color>{
+       50: Color(0xFFF3E5F5),
+      100: Color(0xFFE1BEE7),
+      200: Color(0xFFCE93D8),
+      300: Color(0xFFBA68C8),
+      400: Color(0xFFAB47BC),
+      500: Color(_purplePrimaryValue),
+      600: Color(0xFF8E24AA),
+      700: Color(0xFF7B1FA2),
+      800: Color(0xFF6A1B9A),
+      900: Color(0xFF4A148C),
+    },
+  );
+  static const int _purplePrimaryValue = 0xFF9C27B0;
+
+  /// The purple accent color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.purple.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.purpleAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepPurple.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepPurpleAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.pink.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.pinkAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.purpleAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [purple], the corresponding primary colors.
+  ///  * [deepPurpleAccent] and [pinkAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor purpleAccent = MaterialAccentColor(
+    _purpleAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFFEA80FC),
+      200: Color(_purpleAccentPrimaryValue),
+      400: Color(0xFFD500F9),
+      700: Color(0xFFAA00FF),
+    },
+  );
+  static const int _purpleAccentPrimaryValue = 0xFFE040FB;
+
+  /// The deep purple primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepPurple.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepPurpleAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.purple.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.purpleAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.indigo.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.indigoAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.deepPurple[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [deepPurpleAccent], the corresponding accent colors.
+  ///  * [purple] and [indigo], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor deepPurple = MaterialColor(
+    _deepPurplePrimaryValue,
+    <int, Color>{
+       50: Color(0xFFEDE7F6),
+      100: Color(0xFFD1C4E9),
+      200: Color(0xFFB39DDB),
+      300: Color(0xFF9575CD),
+      400: Color(0xFF7E57C2),
+      500: Color(_deepPurplePrimaryValue),
+      600: Color(0xFF5E35B1),
+      700: Color(0xFF512DA8),
+      800: Color(0xFF4527A0),
+      900: Color(0xFF311B92),
+    },
+  );
+  static const int _deepPurplePrimaryValue = 0xFF673AB7;
+
+  /// The deep purple accent color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepPurple.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepPurpleAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.purple.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.purpleAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.indigo.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.indigoAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.deepPurpleAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [deepPurple], the corresponding primary colors.
+  ///  * [purpleAccent] and [indigoAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor deepPurpleAccent = MaterialAccentColor(
+    _deepPurpleAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFFB388FF),
+      200: Color(_deepPurpleAccentPrimaryValue),
+      400: Color(0xFF651FFF),
+      700: Color(0xFF6200EA),
+    },
+  );
+  static const int _deepPurpleAccentPrimaryValue = 0xFF7C4DFF;
+
+  /// The indigo primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.indigo.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.indigoAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blue.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blueAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepPurple.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepPurpleAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.indigo[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [indigoAccent], the corresponding accent colors.
+  ///  * [blue] and [deepPurple], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor indigo = MaterialColor(
+    _indigoPrimaryValue,
+    <int, Color>{
+       50: Color(0xFFE8EAF6),
+      100: Color(0xFFC5CAE9),
+      200: Color(0xFF9FA8DA),
+      300: Color(0xFF7986CB),
+      400: Color(0xFF5C6BC0),
+      500: Color(_indigoPrimaryValue),
+      600: Color(0xFF3949AB),
+      700: Color(0xFF303F9F),
+      800: Color(0xFF283593),
+      900: Color(0xFF1A237E),
+    },
+  );
+  static const int _indigoPrimaryValue = 0xFF3F51B5;
+
+  /// The indigo accent color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.indigo.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.indigoAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blue.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blueAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepPurple.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepPurpleAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.indigoAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [indigo], the corresponding primary colors.
+  ///  * [blueAccent] and [deepPurpleAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor indigoAccent = MaterialAccentColor(
+    _indigoAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFF8C9EFF),
+      200: Color(_indigoAccentPrimaryValue),
+      400: Color(0xFF3D5AFE),
+      700: Color(0xFF304FFE),
+    },
+  );
+  static const int _indigoAccentPrimaryValue = 0xFF536DFE;
+
+  /// The blue primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blue.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blueAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.indigo.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.indigoAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightBlue.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightBlueAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blueGrey.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.blue[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [blueAccent], the corresponding accent colors.
+  ///  * [indigo], [lightBlue], and [blueGrey], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor blue = MaterialColor(
+    _bluePrimaryValue,
+    <int, Color>{
+       50: Color(0xFFE3F2FD),
+      100: Color(0xFFBBDEFB),
+      200: Color(0xFF90CAF9),
+      300: Color(0xFF64B5F6),
+      400: Color(0xFF42A5F5),
+      500: Color(_bluePrimaryValue),
+      600: Color(0xFF1E88E5),
+      700: Color(0xFF1976D2),
+      800: Color(0xFF1565C0),
+      900: Color(0xFF0D47A1),
+    },
+  );
+  static const int _bluePrimaryValue = 0xFF2196F3;
+
+  /// The blue accent color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blue.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blueAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.indigo.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.indigoAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightBlue.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightBlueAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.blueAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [blue], the corresponding primary colors.
+  ///  * [indigoAccent] and [lightBlueAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor blueAccent = MaterialAccentColor(
+    _blueAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFF82B1FF),
+      200: Color(_blueAccentPrimaryValue),
+      400: Color(0xFF2979FF),
+      700: Color(0xFF2962FF),
+    },
+  );
+  static const int _blueAccentPrimaryValue = 0xFF448AFF;
+
+  /// The light blue primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightBlue.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightBlueAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blue.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blueAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyan.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyanAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.lightBlue[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [lightBlueAccent], the corresponding accent colors.
+  ///  * [blue] and [cyan], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor lightBlue = MaterialColor(
+    _lightBluePrimaryValue,
+    <int, Color>{
+       50: Color(0xFFE1F5FE),
+      100: Color(0xFFB3E5FC),
+      200: Color(0xFF81D4FA),
+      300: Color(0xFF4FC3F7),
+      400: Color(0xFF29B6F6),
+      500: Color(_lightBluePrimaryValue),
+      600: Color(0xFF039BE5),
+      700: Color(0xFF0288D1),
+      800: Color(0xFF0277BD),
+      900: Color(0xFF01579B),
+    },
+  );
+  static const int _lightBluePrimaryValue = 0xFF03A9F4;
+
+  /// The light blue accent swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightBlue.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightBlueAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blue.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blueAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyan.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyanAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.lightBlueAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [lightBlue], the corresponding primary colors.
+  ///  * [blueAccent] and [cyanAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor lightBlueAccent = MaterialAccentColor(
+    _lightBlueAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFF80D8FF),
+      200: Color(_lightBlueAccentPrimaryValue),
+      400: Color(0xFF00B0FF),
+      700: Color(0xFF0091EA),
+    },
+  );
+  static const int _lightBlueAccentPrimaryValue = 0xFF40C4FF;
+
+  /// The cyan primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyan.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyanAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightBlue.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightBlueAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.teal.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.tealAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blueGrey.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.cyan[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [cyanAccent], the corresponding accent colors.
+  ///  * [lightBlue], [teal], and [blueGrey], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor cyan = MaterialColor(
+    _cyanPrimaryValue,
+    <int, Color>{
+       50: Color(0xFFE0F7FA),
+      100: Color(0xFFB2EBF2),
+      200: Color(0xFF80DEEA),
+      300: Color(0xFF4DD0E1),
+      400: Color(0xFF26C6DA),
+      500: Color(_cyanPrimaryValue),
+      600: Color(0xFF00ACC1),
+      700: Color(0xFF0097A7),
+      800: Color(0xFF00838F),
+      900: Color(0xFF006064),
+    },
+  );
+  static const int _cyanPrimaryValue = 0xFF00BCD4;
+
+  /// The cyan accent color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyan.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyanAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightBlue.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightBlueAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.teal.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.tealAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.cyanAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [cyan], the corresponding primary colors.
+  ///  * [lightBlueAccent] and [tealAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor cyanAccent = MaterialAccentColor(
+    _cyanAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFF84FFFF),
+      200: Color(_cyanAccentPrimaryValue),
+      400: Color(0xFF00E5FF),
+      700: Color(0xFF00B8D4),
+    },
+  );
+  static const int _cyanAccentPrimaryValue = 0xFF18FFFF;
+
+  /// The teal primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.teal.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.tealAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.green.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.greenAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyan.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyanAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.teal[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [tealAccent], the corresponding accent colors.
+  ///  * [green] and [cyan], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor teal = MaterialColor(
+    _tealPrimaryValue,
+    <int, Color>{
+       50: Color(0xFFE0F2F1),
+      100: Color(0xFFB2DFDB),
+      200: Color(0xFF80CBC4),
+      300: Color(0xFF4DB6AC),
+      400: Color(0xFF26A69A),
+      500: Color(_tealPrimaryValue),
+      600: Color(0xFF00897B),
+      700: Color(0xFF00796B),
+      800: Color(0xFF00695C),
+      900: Color(0xFF004D40),
+    },
+  );
+  static const int _tealPrimaryValue = 0xFF009688;
+
+  /// The teal accent color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.teal.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.tealAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.green.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.greenAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyan.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyanAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.tealAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [teal], the corresponding primary colors.
+  ///  * [greenAccent] and [cyanAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor tealAccent = MaterialAccentColor(
+    _tealAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFFA7FFEB),
+      200: Color(_tealAccentPrimaryValue),
+      400: Color(0xFF1DE9B6),
+      700: Color(0xFF00BFA5),
+    },
+  );
+  static const int _tealAccentPrimaryValue = 0xFF64FFDA;
+
+  /// The green primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.green.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.greenAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.teal.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.tealAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightGreen.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightGreenAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lime.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.limeAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.green[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [greenAccent], the corresponding accent colors.
+  ///  * [teal], [lightGreen], and [lime], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor green = MaterialColor(
+    _greenPrimaryValue,
+    <int, Color>{
+       50: Color(0xFFE8F5E9),
+      100: Color(0xFFC8E6C9),
+      200: Color(0xFFA5D6A7),
+      300: Color(0xFF81C784),
+      400: Color(0xFF66BB6A),
+      500: Color(_greenPrimaryValue),
+      600: Color(0xFF43A047),
+      700: Color(0xFF388E3C),
+      800: Color(0xFF2E7D32),
+      900: Color(0xFF1B5E20),
+    },
+  );
+  static const int _greenPrimaryValue = 0xFF4CAF50;
+
+  /// The green accent color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.green.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.greenAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.teal.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.tealAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightGreen.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightGreenAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lime.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.limeAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.greenAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [green], the corresponding primary colors.
+  ///  * [tealAccent], [lightGreenAccent], and [limeAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor greenAccent = MaterialAccentColor(
+    _greenAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFFB9F6CA),
+      200: Color(_greenAccentPrimaryValue),
+      400: Color(0xFF00E676),
+      700: Color(0xFF00C853),
+    },
+  );
+  static const int _greenAccentPrimaryValue = 0xFF69F0AE;
+
+  /// The light green primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightGreen.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightGreenAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.green.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.greenAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lime.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.limeAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.lightGreen[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [lightGreenAccent], the corresponding accent colors.
+  ///  * [green] and [lime], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor lightGreen = MaterialColor(
+    _lightGreenPrimaryValue,
+    <int, Color>{
+       50: Color(0xFFF1F8E9),
+      100: Color(0xFFDCEDC8),
+      200: Color(0xFFC5E1A5),
+      300: Color(0xFFAED581),
+      400: Color(0xFF9CCC65),
+      500: Color(_lightGreenPrimaryValue),
+      600: Color(0xFF7CB342),
+      700: Color(0xFF689F38),
+      800: Color(0xFF558B2F),
+      900: Color(0xFF33691E),
+    },
+  );
+  static const int _lightGreenPrimaryValue = 0xFF8BC34A;
+
+  /// The light green accent color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightGreen.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightGreenAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.green.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.greenAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lime.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.limeAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.lightGreenAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [lightGreen], the corresponding primary colors.
+  ///  * [greenAccent] and [limeAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor lightGreenAccent = MaterialAccentColor(
+    _lightGreenAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFFCCFF90),
+      200: Color(_lightGreenAccentPrimaryValue),
+      400: Color(0xFF76FF03),
+      700: Color(0xFF64DD17),
+    },
+  );
+  static const int _lightGreenAccentPrimaryValue = 0xFFB2FF59;
+
+  /// The lime primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lime.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.limeAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightGreen.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightGreenAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.yellow.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.yellowAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.lime[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [limeAccent], the corresponding accent colors.
+  ///  * [lightGreen] and [yellow], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor lime = MaterialColor(
+    _limePrimaryValue,
+    <int, Color>{
+       50: Color(0xFFF9FBE7),
+      100: Color(0xFFF0F4C3),
+      200: Color(0xFFE6EE9C),
+      300: Color(0xFFDCE775),
+      400: Color(0xFFD4E157),
+      500: Color(_limePrimaryValue),
+      600: Color(0xFFC0CA33),
+      700: Color(0xFFAFB42B),
+      800: Color(0xFF9E9D24),
+      900: Color(0xFF827717),
+    },
+  );
+  static const int _limePrimaryValue = 0xFFCDDC39;
+
+  /// The lime accent primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lime.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.limeAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightGreen.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lightGreenAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.yellow.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.yellowAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.limeAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [lime], the corresponding primary colors.
+  ///  * [lightGreenAccent] and [yellowAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor limeAccent = MaterialAccentColor(
+    _limeAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFFF4FF81),
+      200: Color(_limeAccentPrimaryValue),
+      400: Color(0xFFC6FF00),
+      700: Color(0xFFAEEA00),
+    },
+  );
+  static const int _limeAccentPrimaryValue = 0xFFEEFF41;
+
+  /// The yellow primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.yellow.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.yellowAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lime.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.limeAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.amber.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.amberAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.yellow[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [yellowAccent], the corresponding accent colors.
+  ///  * [lime] and [amber], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor yellow = MaterialColor(
+    _yellowPrimaryValue,
+    <int, Color>{
+       50: Color(0xFFFFFDE7),
+      100: Color(0xFFFFF9C4),
+      200: Color(0xFFFFF59D),
+      300: Color(0xFFFFF176),
+      400: Color(0xFFFFEE58),
+      500: Color(_yellowPrimaryValue),
+      600: Color(0xFFFDD835),
+      700: Color(0xFFFBC02D),
+      800: Color(0xFFF9A825),
+      900: Color(0xFFF57F17),
+    },
+  );
+  static const int _yellowPrimaryValue = 0xFFFFEB3B;
+
+  /// The yellow accent color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.yellow.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.yellowAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.lime.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.limeAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.amber.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.amberAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.yellowAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [yellow], the corresponding primary colors.
+  ///  * [limeAccent] and [amberAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor yellowAccent = MaterialAccentColor(
+    _yellowAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFFFFFF8D),
+      200: Color(_yellowAccentPrimaryValue),
+      400: Color(0xFFFFEA00),
+      700: Color(0xFFFFD600),
+    },
+  );
+  static const int _yellowAccentPrimaryValue = 0xFFFFFF00;
+
+  /// The amber primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.amber.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.amberAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.yellow.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.yellowAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orange.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orangeAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.amber[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [amberAccent], the corresponding accent colors.
+  ///  * [yellow] and [orange], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor amber = MaterialColor(
+    _amberPrimaryValue,
+    <int, Color>{
+       50: Color(0xFFFFF8E1),
+      100: Color(0xFFFFECB3),
+      200: Color(0xFFFFE082),
+      300: Color(0xFFFFD54F),
+      400: Color(0xFFFFCA28),
+      500: Color(_amberPrimaryValue),
+      600: Color(0xFFFFB300),
+      700: Color(0xFFFFA000),
+      800: Color(0xFFFF8F00),
+      900: Color(0xFFFF6F00),
+    },
+  );
+  static const int _amberPrimaryValue = 0xFFFFC107;
+
+  /// The amber accent color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.amber.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.amberAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.yellow.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.yellowAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orange.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orangeAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.amberAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [amber], the corresponding primary colors.
+  ///  * [yellowAccent] and [orangeAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor amberAccent = MaterialAccentColor(
+    _amberAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFFFFE57F),
+      200: Color(_amberAccentPrimaryValue),
+      400: Color(0xFFFFC400),
+      700: Color(0xFFFFAB00),
+    },
+  );
+  static const int _amberAccentPrimaryValue = 0xFFFFD740;
+
+  /// The orange primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orange.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orangeAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.amber.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.amberAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepOrange.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepOrangeAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.brown.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.orange[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [orangeAccent], the corresponding accent colors.
+  ///  * [amber], [deepOrange], and [brown], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor orange = MaterialColor(
+    _orangePrimaryValue,
+    <int, Color>{
+       50: Color(0xFFFFF3E0),
+      100: Color(0xFFFFE0B2),
+      200: Color(0xFFFFCC80),
+      300: Color(0xFFFFB74D),
+      400: Color(0xFFFFA726),
+      500: Color(_orangePrimaryValue),
+      600: Color(0xFFFB8C00),
+      700: Color(0xFFF57C00),
+      800: Color(0xFFEF6C00),
+      900: Color(0xFFE65100),
+    },
+  );
+  static const int _orangePrimaryValue = 0xFFFF9800;
+
+  /// The orange accent color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orange.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orangeAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.amber.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.amberAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepOrange.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepOrangeAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.orangeAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [orange], the corresponding primary colors.
+  ///  * [amberAccent] and [deepOrangeAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor orangeAccent = MaterialAccentColor(
+    _orangeAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFFFFD180),
+      200: Color(_orangeAccentPrimaryValue),
+      400: Color(0xFFFF9100),
+      700: Color(0xFFFF6D00),
+    },
+  );
+  static const int _orangeAccentPrimaryValue = 0xFFFFAB40;
+
+  /// The deep orange primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepOrange.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepOrangeAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orange.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orangeAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.red.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.redAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.brown.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.deepOrange[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [deepOrangeAccent], the corresponding accent colors.
+  ///  * [orange], [red], and [brown], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor deepOrange = MaterialColor(
+    _deepOrangePrimaryValue,
+    <int, Color>{
+       50: Color(0xFFFBE9E7),
+      100: Color(0xFFFFCCBC),
+      200: Color(0xFFFFAB91),
+      300: Color(0xFFFF8A65),
+      400: Color(0xFFFF7043),
+      500: Color(_deepOrangePrimaryValue),
+      600: Color(0xFFF4511E),
+      700: Color(0xFFE64A19),
+      800: Color(0xFFD84315),
+      900: Color(0xFFBF360C),
+    },
+  );
+  static const int _deepOrangePrimaryValue = 0xFFFF5722;
+
+  /// The deep orange accent color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepOrange.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.deepOrangeAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orange.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orangeAccent.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.red.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.redAccent.png)
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.deepOrangeAccent[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [deepOrange], the corresponding primary colors.
+  ///  * [orangeAccent] [redAccent], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialAccentColor deepOrangeAccent = MaterialAccentColor(
+    _deepOrangeAccentPrimaryValue,
+    <int, Color>{
+      100: Color(0xFFFF9E80),
+      200: Color(_deepOrangeAccentPrimaryValue),
+      400: Color(0xFFFF3D00),
+      700: Color(0xFFDD2C00),
+    },
+  );
+  static const int _deepOrangeAccentPrimaryValue = 0xFFFF6E40;
+
+  /// The brown primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.brown.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.orange.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blueGrey.png)
+  ///
+  /// This swatch has no corresponding accent color and swatch.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.brown[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [orange] and [blueGrey], vaguely similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor brown = MaterialColor(
+    _brownPrimaryValue,
+    <int, Color>{
+       50: Color(0xFFEFEBE9),
+      100: Color(0xFFD7CCC8),
+      200: Color(0xFFBCAAA4),
+      300: Color(0xFFA1887F),
+      400: Color(0xFF8D6E63),
+      500: Color(_brownPrimaryValue),
+      600: Color(0xFF6D4C41),
+      700: Color(0xFF5D4037),
+      800: Color(0xFF4E342E),
+      900: Color(0xFF3E2723),
+    },
+  );
+  static const int _brownPrimaryValue = 0xFF795548;
+
+  /// The grey primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.grey.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blueGrey.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.brown.png)
+  ///
+  /// This swatch has no corresponding accent swatch.
+  ///
+  /// This swatch, in addition to the values 50 and 100 to 900 in 100
+  /// increments, also features the special values 350 and 850. The 350 value is
+  /// used for raised button while pressed in light themes, and 850 is used for
+  /// the background color of the dark theme. See [ThemeData.brightness].
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.grey[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [blueGrey] and [brown], somewhat similar colors.
+  ///  * [black], [black87], [black54], [black45], [black38], [black26], [black12], which
+  ///    provide a different approach to showing shades of grey.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor grey = MaterialColor(
+    _greyPrimaryValue,
+    <int, Color>{
+       50: Color(0xFFFAFAFA),
+      100: Color(0xFFF5F5F5),
+      200: Color(0xFFEEEEEE),
+      300: Color(0xFFE0E0E0),
+      350: Color(0xFFD6D6D6), // only for raised button while pressed in light theme
+      400: Color(0xFFBDBDBD),
+      500: Color(_greyPrimaryValue),
+      600: Color(0xFF757575),
+      700: Color(0xFF616161),
+      800: Color(0xFF424242),
+      850: Color(0xFF303030), // only for background color in dark theme
+      900: Color(0xFF212121),
+    },
+  );
+  static const int _greyPrimaryValue = 0xFF9E9E9E;
+
+  /// The blue-grey primary color and swatch.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blueGrey.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.grey.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.cyan.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/Colors.blue.png)
+  ///
+  /// This swatch has no corresponding accent swatch.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.blueGrey[400],
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [grey], [cyan], and [blue], similar colors.
+  ///  * [Theme.of], which allows you to select colors from the current theme
+  ///    rather than hard-coding colors in your build methods.
+  static const MaterialColor blueGrey = MaterialColor(
+    _blueGreyPrimaryValue,
+    <int, Color>{
+       50: Color(0xFFECEFF1),
+      100: Color(0xFFCFD8DC),
+      200: Color(0xFFB0BEC5),
+      300: Color(0xFF90A4AE),
+      400: Color(0xFF78909C),
+      500: Color(_blueGreyPrimaryValue),
+      600: Color(0xFF546E7A),
+      700: Color(0xFF455A64),
+      800: Color(0xFF37474F),
+      900: Color(0xFF263238),
+    },
+  );
+  static const int _blueGreyPrimaryValue = 0xFF607D8B;
+
+  /// The material design primary color swatches, excluding grey.
+  static const List<MaterialColor> primaries = <MaterialColor>[
+    red,
+    pink,
+    purple,
+    deepPurple,
+    indigo,
+    blue,
+    lightBlue,
+    cyan,
+    teal,
+    green,
+    lightGreen,
+    lime,
+    yellow,
+    amber,
+    orange,
+    deepOrange,
+    brown,
+    // The grey swatch is intentionally omitted because when picking a color
+    // randomly from this list to colorize an application, picking grey suddenly
+    // makes the app look disabled.
+    blueGrey,
+  ];
+
+  /// The material design accent color swatches.
+  static const List<MaterialAccentColor> accents = <MaterialAccentColor>[
+    redAccent,
+    pinkAccent,
+    purpleAccent,
+    deepPurpleAccent,
+    indigoAccent,
+    blueAccent,
+    lightBlueAccent,
+    cyanAccent,
+    tealAccent,
+    greenAccent,
+    lightGreenAccent,
+    limeAccent,
+    yellowAccent,
+    amberAccent,
+    orangeAccent,
+    deepOrangeAccent,
+  ];
+}
diff --git a/lib/src/material/constants.dart b/lib/src/material/constants.dart
new file mode 100644
index 0000000..403bece
--- /dev/null
+++ b/lib/src/material/constants.dart
@@ -0,0 +1,49 @@
+// Copyright 2014 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.
+
+import 'package:flute/painting.dart';
+
+/// The minimum dimension of any interactive region according to Material
+/// guidelines.
+///
+/// This is used to avoid small regions that are hard for the user to interact
+/// with. It applies to both dimensions of a region, so a square of size
+/// kMinInteractiveDimension x kMinInteractiveDimension is the smallest
+/// acceptable region that should respond to gestures.
+///
+/// See also:
+///
+///  * [kMinInteractiveDimensionCupertino]
+///  * The Material spec on touch targets at <https://material.io/design/usability/accessibility.html#layout-typography>.
+const double kMinInteractiveDimension = 48.0;
+
+/// The height of the toolbar component of the [AppBar].
+const double kToolbarHeight = 56.0;
+
+/// The height of the bottom navigation bar.
+const double kBottomNavigationBarHeight = 56.0;
+
+/// The height of a tab bar containing text.
+const double kTextTabBarHeight = kMinInteractiveDimension;
+
+/// The amount of time theme change animations should last.
+const Duration kThemeChangeDuration = Duration(milliseconds: 200);
+
+/// The default radius of a circular material ink response in logical pixels.
+const double kRadialReactionRadius = 20.0;
+
+/// The amount of time a circular material ink response should take to expand to its full size.
+const Duration kRadialReactionDuration = Duration(milliseconds: 100);
+
+/// The value of the alpha channel to use when drawing a circular material ink response.
+const int kRadialReactionAlpha = 0x1F;
+
+/// The duration of the horizontal scroll animation that occurs when a tab is tapped.
+const Duration kTabScrollDuration = Duration(milliseconds: 300);
+
+/// The horizontal padding included by [Tab]s.
+const EdgeInsets kTabLabelPadding = EdgeInsets.symmetric(horizontal: 16.0);
+
+/// The padding added around material list items.
+const EdgeInsets kMaterialListPadding = EdgeInsets.symmetric(vertical: 8.0);
diff --git a/lib/src/material/curves.dart b/lib/src/material/curves.dart
new file mode 100644
index 0000000..4a4a011
--- /dev/null
+++ b/lib/src/material/curves.dart
@@ -0,0 +1,36 @@
+// Copyright 2014 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.
+
+import 'package:flute/animation.dart';
+
+// The easing curves of the Material Library
+
+/// The standard easing curve in the Material specification.
+///
+/// Elements that begin and end at rest use standard easing.
+/// They speed up quickly and slow down gradually, in order
+/// to emphasize the end of the transition.
+///
+/// See also:
+/// * <https://material.io/design/motion/speed.html#easing>
+const Curve standardEasing = Curves.fastOutSlowIn;
+
+/// The accelerate easing curve in the Material specification.
+///
+/// Elements exiting a screen use acceleration easing,
+/// where they start at rest and end at peak velocity.
+///
+/// See also:
+/// * <https://material.io/design/motion/speed.html#easing>
+const Curve accelerateEasing = Cubic(0.4, 0.0, 1.0, 1.0);
+
+/// The decelerate easing curve in the Material specification.
+///
+/// Incoming elements are animated using deceleration easing,
+/// which starts a transition at peak velocity (the fastest
+/// point of an element’s movement) and ends at rest.
+///
+/// See also:
+/// * <https://material.io/design/motion/speed.html#easing>
+const Curve decelerateEasing = Cubic(0.0, 0.0, 0.2, 1.0);
diff --git a/lib/src/material/data_table.dart b/lib/src/material/data_table.dart
new file mode 100644
index 0000000..b089960
--- /dev/null
+++ b/lib/src/material/data_table.dart
@@ -0,0 +1,1225 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'checkbox.dart';
+import 'constants.dart';
+import 'data_table_theme.dart';
+import 'debug.dart';
+import 'divider.dart';
+import 'dropdown.dart';
+import 'icons.dart';
+import 'ink_well.dart';
+import 'material.dart';
+import 'material_state.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+import 'tooltip.dart';
+
+/// Signature for [DataColumn.onSort] callback.
+typedef DataColumnSortCallback = void Function(int columnIndex, bool ascending);
+
+/// Column configuration for a [DataTable].
+///
+/// One column configuration must be provided for each column to
+/// display in the table. The list of [DataColumn] objects is passed
+/// as the `columns` argument to the [new DataTable] constructor.
+@immutable
+class DataColumn {
+  /// Creates the configuration for a column of a [DataTable].
+  ///
+  /// The [label] argument must not be null.
+  const DataColumn({
+    required this.label,
+    this.tooltip,
+    this.numeric = false,
+    this.onSort,
+  }) : assert(label != null);
+
+  /// The column heading.
+  ///
+  /// Typically, this will be a [Text] widget. It could also be an
+  /// [Icon] (typically using size 18), or a [Row] with an icon and
+  /// some text.
+  ///
+  /// By default, this widget will only occupy the minimal space. If you want
+  /// it to take the entire remaining space, e.g. when you want to use [Center],
+  /// you can wrap it with an [Expanded].
+  ///
+  /// The label should not include the sort indicator.
+  final Widget label;
+
+  /// The column heading's tooltip.
+  ///
+  /// This is a longer description of the column heading, for cases
+  /// where the heading might have been abbreviated to keep the column
+  /// width to a reasonable size.
+  final String? tooltip;
+
+  /// Whether this column represents numeric data or not.
+  ///
+  /// The contents of cells of columns containing numeric data are
+  /// right-aligned.
+  final bool numeric;
+
+  /// Called when the user asks to sort the table using this column.
+  ///
+  /// If null, the column will not be considered sortable.
+  ///
+  /// See [DataTable.sortColumnIndex] and [DataTable.sortAscending].
+  final DataColumnSortCallback? onSort;
+
+  bool get _debugInteractive => onSort != null;
+}
+
+/// Row configuration and cell data for a [DataTable].
+///
+/// One row configuration must be provided for each row to
+/// display in the table. The list of [DataRow] objects is passed
+/// as the `rows` argument to the [new DataTable] constructor.
+///
+/// The data for this row of the table is provided in the [cells]
+/// property of the [DataRow] object.
+@immutable
+class DataRow {
+  /// Creates the configuration for a row of a [DataTable].
+  ///
+  /// The [cells] argument must not be null.
+  const DataRow({
+    this.key,
+    this.selected = false,
+    this.onSelectChanged,
+    this.color,
+    required this.cells,
+  }) : assert(cells != null);
+
+  /// Creates the configuration for a row of a [DataTable], deriving
+  /// the key from a row index.
+  ///
+  /// The [cells] argument must not be null.
+  DataRow.byIndex({
+    int? index,
+    this.selected = false,
+    this.onSelectChanged,
+    this.color,
+    required this.cells,
+  }) : assert(cells != null),
+       key = ValueKey<int?>(index);
+
+  /// A [Key] that uniquely identifies this row. This is used to
+  /// ensure that if a row is added or removed, any stateful widgets
+  /// related to this row (e.g. an in-progress checkbox animation)
+  /// remain on the right row visually.
+  ///
+  /// If the table never changes once created, no key is necessary.
+  final LocalKey? key;
+
+  /// Called when the user selects or unselects a selectable row.
+  ///
+  /// If this is not null, then the row is selectable. The current
+  /// selection state of the row is given by [selected].
+  ///
+  /// If any row is selectable, then the table's heading row will have
+  /// a checkbox that can be checked to select all selectable rows
+  /// (and which is checked if all the rows are selected), and each
+  /// subsequent row will have a checkbox to toggle just that row.
+  ///
+  /// A row whose [onSelectChanged] callback is null is ignored for
+  /// the purposes of determining the state of the "all" checkbox,
+  /// and its checkbox is disabled.
+  final ValueChanged<bool?>? onSelectChanged;
+
+  /// Whether the row is selected.
+  ///
+  /// If [onSelectChanged] is non-null for any row in the table, then
+  /// a checkbox is shown at the start of each row. If the row is
+  /// selected (true), the checkbox will be checked and the row will
+  /// be highlighted.
+  ///
+  /// Otherwise, the checkbox, if present, will not be checked.
+  final bool selected;
+
+  /// The data for this row.
+  ///
+  /// There must be exactly as many cells as there are columns in the
+  /// table.
+  final List<DataCell> cells;
+
+  /// The color for the row.
+  ///
+  /// By default, the color is transparent unless selected. Selected rows has
+  /// a grey translucent color.
+  ///
+  /// The effective color can depend on the [MaterialState] state, if the
+  /// row is selected, pressed, hovered, focused, disabled or enabled. The
+  /// color is painted as an overlay to the row. To make sure that the row's
+  /// [InkWell] is visible (when pressed, hovered and focused), it is
+  /// recommended to use a translucent color.
+  ///
+  /// ```dart
+  /// DataRow(
+  ///   color: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
+  ///     if (states.contains(MaterialState.selected))
+  ///       return Theme.of(context).colorScheme.primary.withOpacity(0.08);
+  ///     return null;  // Use the default value.
+  ///   }),
+  /// )
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * The Material Design specification for overlay colors and how they
+  ///    match a component's state:
+  ///    <https://material.io/design/interaction/states.html#anatomy>.
+  final MaterialStateProperty<Color?>? color;
+
+  bool get _debugInteractive => onSelectChanged != null || cells.any((DataCell cell) => cell._debugInteractive);
+}
+
+/// The data for a cell of a [DataTable].
+///
+/// One list of [DataCell] objects must be provided for each [DataRow]
+/// in the [DataTable], in the new [DataRow] constructor's `cells`
+/// argument.
+@immutable
+class DataCell {
+  /// Creates an object to hold the data for a cell in a [DataTable].
+  ///
+  /// The first argument is the widget to show for the cell, typically
+  /// a [Text] or [DropdownButton] widget; this becomes the [child]
+  /// property and must not be null.
+  ///
+  /// If the cell has no data, then a [Text] widget with placeholder
+  /// text should be provided instead, and then the [placeholder]
+  /// argument should be set to true.
+  const DataCell(
+    this.child, {
+    this.placeholder = false,
+    this.showEditIcon = false,
+    this.onTap,
+  }) : assert(child != null);
+
+  /// A cell that has no content and has zero width and height.
+  static const DataCell empty = DataCell(SizedBox(width: 0.0, height: 0.0));
+
+  /// The data for the row.
+  ///
+  /// Typically a [Text] widget or a [DropdownButton] widget.
+  ///
+  /// If the cell has no data, then a [Text] widget with placeholder
+  /// text should be provided instead, and [placeholder] should be set
+  /// to true.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// Whether the [child] is actually a placeholder.
+  ///
+  /// If this is true, the default text style for the cell is changed
+  /// to be appropriate for placeholder text.
+  final bool placeholder;
+
+  /// Whether to show an edit icon at the end of the cell.
+  ///
+  /// This does not make the cell actually editable; the caller must
+  /// implement editing behavior if desired (initiated from the
+  /// [onTap] callback).
+  ///
+  /// If this is set, [onTap] should also be set, otherwise tapping
+  /// the icon will have no effect.
+  final bool showEditIcon;
+
+  /// Called if the cell is tapped.
+  ///
+  /// If non-null, tapping the cell will call this callback. If
+  /// null, tapping the cell will attempt to select the row (if
+  /// [DataRow.onSelectChanged] is provided).
+  final VoidCallback? onTap;
+
+  bool get _debugInteractive => onTap != null;
+}
+
+/// A material design data table.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=ktTajqbhIcY}
+///
+/// Displaying data in a table is expensive, because to lay out the
+/// table all the data must be measured twice, once to negotiate the
+/// dimensions to use for each column, and once to actually lay out
+/// the table given the results of the negotiation.
+///
+/// For this reason, if you have a lot of data (say, more than a dozen
+/// rows with a dozen columns, though the precise limits depend on the
+/// target device), it is suggested that you use a
+/// [PaginatedDataTable] which automatically splits the data into
+/// multiple pages.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// This sample shows how to display a [DataTable] with three columns: name, age, and
+/// role. The columns are defined by three [DataColumn] objects. The table
+/// contains three rows of data for three example users, the data for which
+/// is defined by three [DataRow] objects.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/data_table.png)
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return DataTable(
+///     columns: const <DataColumn>[
+///       DataColumn(
+///         label: Text(
+///           'Name',
+///           style: TextStyle(fontStyle: FontStyle.italic),
+///         ),
+///       ),
+///       DataColumn(
+///         label: Text(
+///           'Age',
+///           style: TextStyle(fontStyle: FontStyle.italic),
+///         ),
+///       ),
+///       DataColumn(
+///         label: Text(
+///           'Role',
+///           style: TextStyle(fontStyle: FontStyle.italic),
+///         ),
+///       ),
+///     ],
+///     rows: const <DataRow>[
+///       DataRow(
+///         cells: <DataCell>[
+///           DataCell(Text('Sarah')),
+///           DataCell(Text('19')),
+///           DataCell(Text('Student')),
+///         ],
+///       ),
+///       DataRow(
+///         cells: <DataCell>[
+///           DataCell(Text('Janine')),
+///           DataCell(Text('43')),
+///           DataCell(Text('Professor')),
+///         ],
+///       ),
+///       DataRow(
+///         cells: <DataCell>[
+///           DataCell(Text('William')),
+///           DataCell(Text('27')),
+///           DataCell(Text('Associate Professor')),
+///         ],
+///       ),
+///     ],
+///   );
+/// }
+/// ```
+///
+/// {@end-tool}
+///
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+///
+/// This sample shows how to display a [DataTable] with alternate colors per
+/// row, and a custom color for when the row is selected.
+///
+/// ```dart
+/// static const int numItems = 10;
+/// List<bool> selected = List<bool>.generate(numItems, (index) => false);
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return SizedBox(
+///     width: double.infinity,
+///     child: DataTable(
+///       columns: const <DataColumn>[
+///         DataColumn(
+///           label: const Text('Number'),
+///         ),
+///       ],
+///       rows: List<DataRow>.generate(
+///         numItems,
+///         (index) => DataRow(
+///           color: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
+///             // All rows will have the same selected color.
+///             if (states.contains(MaterialState.selected))
+///               return Theme.of(context).colorScheme.primary.withOpacity(0.08);
+///             // Even rows will have a grey color.
+///             if (index % 2 == 0)
+///               return Colors.grey.withOpacity(0.3);
+///             return null;  // Use default value for other states and odd rows.
+///           }),
+///           cells: [DataCell(Text('Row $index'))],
+///           selected: selected[index],
+///           onSelectChanged: (bool value) {
+///             setState(() {
+///               selected[index] = value;
+///             });
+///           },
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// [DataTable] can be sorted on the basis of any column in [columns] in
+/// ascending or descending order. If [sortColumnIndex] is non-null, then the
+/// table will be sorted by the values in the specified column. The boolean
+/// [sortAscending] flag controls the sort order.
+///
+/// See also:
+///
+///  * [DataColumn], which describes a column in the data table.
+///  * [DataRow], which contains the data for a row in the data table.
+///  * [DataCell], which contains the data for a single cell in the data table.
+///  * [PaginatedDataTable], which shows part of the data in a data table and
+///    provides controls for paging through the remainder of the data.
+///  * <https://material.io/design/components/data-tables.html>
+class DataTable extends StatelessWidget {
+  /// Creates a widget describing a data table.
+  ///
+  /// The [columns] argument must be a list of as many [DataColumn]
+  /// objects as the table is to have columns, ignoring the leading
+  /// checkbox column if any. The [columns] argument must have a
+  /// length greater than zero and must not be null.
+  ///
+  /// The [rows] argument must be a list of as many [DataRow] objects
+  /// as the table is to have rows, ignoring the leading heading row
+  /// that contains the column headings (derived from the [columns]
+  /// argument). There may be zero rows, but the rows argument must
+  /// not be null.
+  ///
+  /// Each [DataRow] object in [rows] must have as many [DataCell]
+  /// objects in the [DataRow.cells] list as the table has columns.
+  ///
+  /// If the table is sorted, the column that provides the current
+  /// primary key should be specified by index in [sortColumnIndex], 0
+  /// meaning the first column in [columns], 1 being the next one, and
+  /// so forth.
+  ///
+  /// The actual sort order can be specified using [sortAscending]; if
+  /// the sort order is ascending, this should be true (the default),
+  /// otherwise it should be false.
+  DataTable({
+    Key? key,
+    required this.columns,
+    this.sortColumnIndex,
+    this.sortAscending = true,
+    this.onSelectAll,
+    this.decoration,
+    this.dataRowColor,
+    this.dataRowHeight,
+    this.dataTextStyle,
+    this.headingRowColor,
+    this.headingRowHeight,
+    this.headingTextStyle,
+    this.horizontalMargin,
+    this.columnSpacing,
+    this.showCheckboxColumn = true,
+    this.showBottomBorder = false,
+    this.dividerThickness,
+    required this.rows,
+  }) : assert(columns != null),
+       assert(columns.isNotEmpty),
+       assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)),
+       assert(sortAscending != null),
+       assert(showCheckboxColumn != null),
+       assert(rows != null),
+       assert(!rows.any((DataRow row) => row.cells.length != columns.length)),
+       assert(dividerThickness == null || dividerThickness >= 0),
+       _onlyTextColumn = _initOnlyTextColumn(columns),
+       super(key: key);
+
+  /// The configuration and labels for the columns in the table.
+  final List<DataColumn> columns;
+
+  /// The current primary sort key's column.
+  ///
+  /// If non-null, indicates that the indicated column is the column
+  /// by which the data is sorted. The number must correspond to the
+  /// index of the relevant column in [columns].
+  ///
+  /// Setting this will cause the relevant column to have a sort
+  /// indicator displayed.
+  ///
+  /// When this is null, it implies that the table's sort order does
+  /// not correspond to any of the columns.
+  final int? sortColumnIndex;
+
+  /// Whether the column mentioned in [sortColumnIndex], if any, is sorted
+  /// in ascending order.
+  ///
+  /// If true, the order is ascending (meaning the rows with the
+  /// smallest values for the current sort column are first in the
+  /// table).
+  ///
+  /// If false, the order is descending (meaning the rows with the
+  /// smallest values for the current sort column are last in the
+  /// table).
+  final bool sortAscending;
+
+  /// Invoked when the user selects or unselects every row, using the
+  /// checkbox in the heading row.
+  ///
+  /// If this is null, then the [DataRow.onSelectChanged] callback of
+  /// every row in the table is invoked appropriately instead.
+  ///
+  /// To control whether a particular row is selectable or not, see
+  /// [DataRow.onSelectChanged]. This callback is only relevant if any
+  /// row is selectable.
+  final ValueSetter<bool?>? onSelectAll;
+
+  /// {@template flutter.material.dataTable.decoration}
+  /// The background and border decoration for the table.
+  /// {@endtemplate}
+  ///
+  /// If null, [DataTableThemeData.decoration] is used. By default there is no
+  /// decoration.
+  final Decoration? decoration;
+
+  /// {@template flutter.material.dataTable.dataRowColor}
+  /// The background color for the data rows.
+  ///
+  /// The effective background color can be made to depend on the
+  /// [MaterialState] state, i.e. if the row is selected, pressed, hovered,
+  /// focused, disabled or enabled. The color is painted as an overlay to the
+  /// row. To make sure that the row's [InkWell] is visible (when pressed,
+  /// hovered and focused), it is recommended to use a translucent background
+  /// color.
+  /// {@endtemplate}
+  ///
+  /// If null, [DataTableThemeData.dataRowColor] is used. By default, the
+  /// background color is transparent unless selected. Selected rows have a grey
+  /// translucent color. To set a different color for individual rows, see
+  /// [DataRow.color].
+  ///
+  /// {@template flutter.material.DataTable.dataRowColor}
+  /// ```dart
+  /// DataTable(
+  ///   dataRowColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
+  ///     if (states.contains(MaterialState.selected))
+  ///       return Theme.of(context).colorScheme.primary.withOpacity(0.08);
+  ///     return null;  // Use the default value.
+  ///   }),
+  /// )
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * The Material Design specification for overlay colors and how they
+  ///    match a component's state:
+  ///    <https://material.io/design/interaction/states.html#anatomy>.
+  /// {@endtemplate}
+  final MaterialStateProperty<Color?>? dataRowColor;
+
+  /// {@template flutter.material.dataTable.dataRowHeight}
+  /// The height of each row (excluding the row that contains column headings).
+  /// {@endtemplate}
+  ///
+  /// If null, [DataTableThemeData.dataRowHeight] is used. This value defaults
+  /// to [kMinInteractiveDimension] to adhere to the Material Design
+  /// specifications.
+  final double? dataRowHeight;
+
+  /// {@template flutter.material.dataTable.dataTextStyle}
+  /// The text style for data rows.
+  /// {@endtemplate}
+  ///
+  /// If null, [DataTableThemeData.dataTextStyle] is used. By default, the text
+  /// style is [TextTheme.bodyText2].
+  final TextStyle? dataTextStyle;
+
+  /// {@template flutter.material.dataTable.headingRowColor}
+  /// The background color for the heading row.
+  ///
+  /// The effective background color can be made to depend on the
+  /// [MaterialState] state, i.e. if the row is pressed, hovered, focused when
+  /// sorted. The color is painted as an overlay to the row. To make sure that
+  /// the row's [InkWell] is visible (when pressed, hovered and focused), it is
+  /// recommended to use a translucent color.
+  /// {@endtemplate}
+  ///
+  /// If null, [DataTableThemeData.headingRowColor] is used.
+  ///
+  /// {@template flutter.material.DataTable.headingRowColor}
+  /// ```dart
+  /// DataTable(
+  ///   headingRowColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
+  ///     if (states.contains(MaterialState.hovered))
+  ///       return Theme.of(context).colorScheme.primary.withOpacity(0.08);
+  ///     return null;  // Use the default value.
+  ///   }),
+  /// )
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * The Material Design specification for overlay colors and how they
+  ///    match a component's state:
+  ///    <https://material.io/design/interaction/states.html#anatomy>.
+  /// {@endtemplate}
+  final MaterialStateProperty<Color?>? headingRowColor;
+
+  /// {@template flutter.material.dataTable.headingRowHeight}
+  /// The height of the heading row.
+  /// {@endtemplate}
+  ///
+  /// If null, [DataTableThemeData.headingRowHeight] is used. This value
+  /// defaults to 56.0 to adhere to the Material Design specifications.
+  final double? headingRowHeight;
+
+  /// {@template flutter.material.dataTable.headingTextStyle}
+  /// The text style for the heading row.
+  /// {@endtemplate}
+  ///
+  /// If null, [DataTableThemeData.headingTextStyle] is used. By default, the
+  /// text style is [TextTheme.subtitle2].
+  final TextStyle? headingTextStyle;
+
+  /// {@template flutter.material.dataTable.horizontalMargin}
+  /// The horizontal margin between the edges of the table and the content
+  /// in the first and last cells of each row.
+  ///
+  /// When a checkbox is displayed, it is also the margin between the checkbox
+  /// the content in the first data column.
+  /// {@endtemplate}
+  ///
+  /// If null, [DataTableThemeData.horizontalMargin] is used. This value
+  /// defaults to 24.0 to adhere to the Material Design specifications.
+  final double? horizontalMargin;
+
+  /// {@template flutter.material.dataTable.columnSpacing}
+  /// The horizontal margin between the contents of each data column.
+  /// {@endtemplate}
+  ///
+  /// If null, [DataTableThemeData.columnSpacing] is used. This value defaults
+  /// to 56.0 to adhere to the Material Design specifications.
+  final double? columnSpacing;
+
+  /// {@template flutter.material.dataTable.showCheckboxColumn}
+  /// Whether the widget should display checkboxes for selectable rows.
+  ///
+  /// If true, a [Checkbox] will be placed at the beginning of each row that is
+  /// selectable. However, if [DataRow.onSelectChanged] is not set for any row,
+  /// checkboxes will not be placed, even if this value is true.
+  ///
+  /// If false, all rows will not display a [Checkbox].
+  /// {@endtemplate}
+  final bool showCheckboxColumn;
+
+  /// The data to show in each row (excluding the row that contains
+  /// the column headings).
+  ///
+  /// Must be non-null, but may be empty.
+  final List<DataRow> rows;
+
+  /// {@template flutter.material.dataTable.dividerThickness}
+  /// The width of the divider that appears between [TableRow]s.
+  ///
+  /// Must be greater than or equal to zero.
+  /// {@endtemplate}
+  ///
+  /// If null, [DataTableThemeData.dividerThickness] is used. This value
+  /// defaults to 1.0.
+  final double? dividerThickness;
+
+  /// Whether a border at the bottom of the table is displayed.
+  ///
+  /// By default, a border is not shown at the bottom to allow for a border
+  /// around the table defined by [decoration].
+  final bool showBottomBorder;
+
+  // Set by the constructor to the index of the only Column that is
+  // non-numeric, if there is exactly one, otherwise null.
+  final int? _onlyTextColumn;
+  static int? _initOnlyTextColumn(List<DataColumn> columns) {
+    int? result;
+    for (int index = 0; index < columns.length; index += 1) {
+      final DataColumn column = columns[index];
+      if (!column.numeric) {
+        if (result != null)
+          return null;
+        result = index;
+      }
+    }
+    return result;
+  }
+
+  bool get _debugInteractive {
+    return columns.any((DataColumn column) => column._debugInteractive)
+        || rows.any((DataRow row) => row._debugInteractive);
+  }
+
+  static final LocalKey _headingRowKey = UniqueKey();
+
+  void _handleSelectAll(bool? checked, bool someChecked) {
+    // If some checkboxes are checked, all checkboxes are selected. Otherwise,
+    // use the new checked value but default to false if it's null.
+    final bool effectiveChecked = someChecked || (checked ?? false);
+    if (onSelectAll != null) {
+      onSelectAll!(effectiveChecked);
+    } else {
+      for (final DataRow row in rows) {
+        if (row.onSelectChanged != null && row.selected != effectiveChecked)
+          row.onSelectChanged!(effectiveChecked);
+      }
+    }
+  }
+
+  /// The default height of the heading row.
+  static const double _headingRowHeight = 56.0;
+
+  /// The default horizontal margin between the edges of the table and the content
+  /// in the first and last cells of each row.
+  static const double _horizontalMargin = 24.0;
+
+  /// The default horizontal margin between the contents of each data column.
+  static const double _columnSpacing = 56.0;
+
+  /// The default padding between the heading content and sort arrow.
+  static const double _sortArrowPadding = 2.0;
+
+  /// The default divider thickness.
+  static const double _dividerThickness = 1.0;
+
+  static const Duration _sortArrowAnimationDuration = Duration(milliseconds: 150);
+
+  Widget _buildCheckbox({
+    required BuildContext context,
+    required bool? checked,
+    required VoidCallback? onRowTap,
+    required ValueChanged<bool?>? onCheckboxChanged,
+    required MaterialStateProperty<Color?>? overlayColor,
+    required bool tristate,
+  }) {
+    final ThemeData themeData = Theme.of(context);
+    final double effectiveHorizontalMargin = horizontalMargin
+      ?? themeData.dataTableTheme.horizontalMargin
+      ?? _horizontalMargin;
+    Widget contents = Semantics(
+      container: true,
+      child: Padding(
+        padding: EdgeInsetsDirectional.only(
+          start: effectiveHorizontalMargin,
+          end: effectiveHorizontalMargin / 2.0,
+        ),
+        child: Center(
+          child: Checkbox(
+            // TODO(per): Remove when Checkbox has theme, https://github.com/flutter/flutter/issues/53420.
+            activeColor: themeData.colorScheme.primary,
+            checkColor: themeData.colorScheme.onPrimary,
+            value: checked,
+            onChanged: onCheckboxChanged,
+            tristate: tristate,
+          ),
+        ),
+      ),
+    );
+    if (onRowTap != null) {
+      contents = TableRowInkWell(
+        onTap: onRowTap,
+        child: contents,
+        overlayColor: overlayColor,
+      );
+    }
+    return TableCell(
+      verticalAlignment: TableCellVerticalAlignment.fill,
+      child: contents,
+    );
+  }
+
+  Widget _buildHeadingCell({
+    required BuildContext context,
+    required EdgeInsetsGeometry padding,
+    required Widget label,
+    required String? tooltip,
+    required bool numeric,
+    required VoidCallback? onSort,
+    required bool sorted,
+    required bool ascending,
+    required MaterialStateProperty<Color?>? overlayColor,
+  }) {
+    final ThemeData themeData = Theme.of(context);
+    label = Row(
+      textDirection: numeric ? TextDirection.rtl : null,
+      children: <Widget>[
+        label,
+        if (onSort != null)
+          ...<Widget>[
+            _SortArrow(
+              visible: sorted,
+              up: sorted ? ascending : null,
+              duration: _sortArrowAnimationDuration,
+            ),
+            const SizedBox(width: _sortArrowPadding),
+          ],
+      ],
+    );
+
+    final TextStyle effectiveHeadingTextStyle = headingTextStyle
+      ?? themeData.dataTableTheme.headingTextStyle
+      ?? themeData.textTheme.subtitle2!;
+    final double effectiveHeadingRowHeight = headingRowHeight
+      ?? themeData.dataTableTheme.headingRowHeight
+      ?? _headingRowHeight;
+    label = Container(
+      padding: padding,
+      height: effectiveHeadingRowHeight,
+      alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart,
+      child: AnimatedDefaultTextStyle(
+        style: effectiveHeadingTextStyle,
+        softWrap: false,
+        duration: _sortArrowAnimationDuration,
+        child: label,
+      ),
+    );
+    if (tooltip != null) {
+      label = Tooltip(
+        message: tooltip,
+        child: label,
+      );
+    }
+
+    // TODO(dkwingsmt): Only wrap Inkwell if onSort != null. Blocked by
+    // https://github.com/flutter/flutter/issues/51152
+    label = InkWell(
+      onTap: onSort,
+      overlayColor: overlayColor,
+      child: label,
+    );
+    return label;
+  }
+
+  Widget _buildDataCell({
+    required BuildContext context,
+    required EdgeInsetsGeometry padding,
+    required Widget label,
+    required bool numeric,
+    required bool placeholder,
+    required bool showEditIcon,
+    required VoidCallback? onTap,
+    required VoidCallback? onSelectChanged,
+    required MaterialStateProperty<Color?>? overlayColor,
+  }) {
+    final ThemeData themeData = Theme.of(context);
+    if (showEditIcon) {
+      const Widget icon = Icon(Icons.edit, size: 18.0);
+      label = Expanded(child: label);
+      label = Row(
+        textDirection: numeric ? TextDirection.rtl : null,
+        children: <Widget>[ label, icon ],
+      );
+    }
+
+    final TextStyle effectiveDataTextStyle = dataTextStyle
+      ?? themeData.dataTableTheme.dataTextStyle
+      ?? themeData.textTheme.bodyText2!;
+    final double effectiveDataRowHeight = dataRowHeight
+      ?? themeData.dataTableTheme.dataRowHeight
+      ?? kMinInteractiveDimension;
+    label = Container(
+      padding: padding,
+      height: effectiveDataRowHeight,
+      alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart,
+      child: DefaultTextStyle(
+        style: effectiveDataTextStyle.copyWith(
+          color: placeholder ? effectiveDataTextStyle.color!.withOpacity(0.6) : null,
+        ),
+        child: DropdownButtonHideUnderline(child: label),
+      ),
+    );
+    if (onTap != null) {
+      label = InkWell(
+        onTap: onTap,
+        child: label,
+        overlayColor: overlayColor,
+      );
+    } else if (onSelectChanged != null) {
+      label = TableRowInkWell(
+        onTap: onSelectChanged,
+        child: label,
+        overlayColor: overlayColor,
+      );
+    }
+    return label;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(!_debugInteractive || debugCheckHasMaterial(context));
+
+    final ThemeData theme = Theme.of(context);
+    final MaterialStateProperty<Color?>? effectiveHeadingRowColor = headingRowColor
+      ?? theme.dataTableTheme.headingRowColor;
+    final MaterialStateProperty<Color?>? effectiveDataRowColor = dataRowColor
+      ?? theme.dataTableTheme.dataRowColor;
+    final MaterialStateProperty<Color?> defaultRowColor = MaterialStateProperty.resolveWith(
+      (Set<MaterialState> states) {
+        if (states.contains(MaterialState.selected))
+          return theme.colorScheme.primary.withOpacity(0.08);
+        return null;
+      },
+    );
+    final bool anyRowSelectable = rows.any((DataRow row) => row.onSelectChanged != null);
+    final bool displayCheckboxColumn = showCheckboxColumn && anyRowSelectable;
+    final Iterable<DataRow> rowsWithCheckbox = displayCheckboxColumn ?
+      rows.where((DataRow row) => row.onSelectChanged != null) : <DataRow>[];
+    final Iterable<DataRow> rowsChecked = rowsWithCheckbox.where((DataRow row) => row.selected);
+    final bool allChecked = displayCheckboxColumn && rowsChecked.length == rowsWithCheckbox.length;
+    final bool anyChecked = displayCheckboxColumn && rowsChecked.isNotEmpty;
+    final bool someChecked = anyChecked && !allChecked;
+    final double effectiveHorizontalMargin = horizontalMargin
+      ?? theme.dataTableTheme.horizontalMargin
+      ?? _horizontalMargin;
+    final double effectiveColumnSpacing = columnSpacing
+      ?? theme.dataTableTheme.columnSpacing
+      ?? _columnSpacing;
+
+    final List<TableColumnWidth> tableColumns = List<TableColumnWidth>.filled(columns.length + (displayCheckboxColumn ? 1 : 0), const _NullTableColumnWidth());
+    final List<TableRow> tableRows = List<TableRow>.generate(
+      rows.length + 1, // the +1 is for the header row
+      (int index) {
+        final bool isSelected = index > 0 && rows[index - 1].selected;
+        final bool isDisabled = index > 0 && anyRowSelectable && rows[index - 1].onSelectChanged == null;
+        final Set<MaterialState> states = <MaterialState>{
+          if (isSelected)
+            MaterialState.selected,
+          if (isDisabled)
+            MaterialState.disabled,
+        };
+        final Color? resolvedDataRowColor = index > 0 ? (rows[index - 1].color ?? effectiveDataRowColor)?.resolve(states) : null;
+        final Color? resolvedHeadingRowColor = effectiveHeadingRowColor?.resolve(<MaterialState>{});
+        final Color? rowColor = index > 0 ? resolvedDataRowColor : resolvedHeadingRowColor;
+        final BorderSide borderSide = Divider.createBorderSide(
+          context,
+          width: dividerThickness
+            ?? theme.dataTableTheme.dividerThickness
+            ?? _dividerThickness,
+        );
+        final Border? border = showBottomBorder
+          ? Border(bottom: borderSide)
+          : index == 0 ? null : Border(top: borderSide);
+        return TableRow(
+          key: index == 0 ? _headingRowKey : rows[index - 1].key,
+          decoration: BoxDecoration(
+            border: border,
+            color: rowColor ?? defaultRowColor.resolve(states),
+          ),
+          children: List<Widget>.filled(tableColumns.length, const _NullWidget()),
+        );
+      },
+    );
+
+    int rowIndex;
+
+    int displayColumnIndex = 0;
+    if (displayCheckboxColumn) {
+      tableColumns[0] = FixedColumnWidth(effectiveHorizontalMargin + Checkbox.width + effectiveHorizontalMargin / 2.0);
+      tableRows[0].children![0] = _buildCheckbox(
+        context: context,
+        checked: someChecked ? null : allChecked,
+        onRowTap: null,
+        onCheckboxChanged: (bool? checked) => _handleSelectAll(checked, someChecked),
+        overlayColor: null,
+        tristate: true,
+      );
+      rowIndex = 1;
+      for (final DataRow row in rows) {
+        tableRows[rowIndex].children![0] = _buildCheckbox(
+          context: context,
+          checked: row.selected,
+          onRowTap: () => row.onSelectChanged != null ? row.onSelectChanged!(!row.selected) : null ,
+          onCheckboxChanged: row.onSelectChanged,
+          overlayColor: row.color ?? effectiveDataRowColor,
+          tristate: false,
+        );
+        rowIndex += 1;
+      }
+      displayColumnIndex += 1;
+    }
+
+    for (int dataColumnIndex = 0; dataColumnIndex < columns.length; dataColumnIndex += 1) {
+      final DataColumn column = columns[dataColumnIndex];
+
+      final double paddingStart;
+      if (dataColumnIndex == 0 && displayCheckboxColumn) {
+        paddingStart = effectiveHorizontalMargin / 2.0;
+      } else if (dataColumnIndex == 0 && !displayCheckboxColumn) {
+        paddingStart = effectiveHorizontalMargin;
+      } else {
+        paddingStart = effectiveColumnSpacing / 2.0;
+      }
+
+      final double paddingEnd;
+      if (dataColumnIndex == columns.length - 1) {
+        paddingEnd = effectiveHorizontalMargin;
+      } else {
+        paddingEnd = effectiveColumnSpacing / 2.0;
+      }
+
+      final EdgeInsetsDirectional padding = EdgeInsetsDirectional.only(
+        start: paddingStart,
+        end: paddingEnd,
+      );
+      if (dataColumnIndex == _onlyTextColumn) {
+        tableColumns[displayColumnIndex] = const IntrinsicColumnWidth(flex: 1.0);
+      } else {
+        tableColumns[displayColumnIndex] = const IntrinsicColumnWidth();
+      }
+      tableRows[0].children![displayColumnIndex] = _buildHeadingCell(
+        context: context,
+        padding: padding,
+        label: column.label,
+        tooltip: column.tooltip,
+        numeric: column.numeric,
+        onSort: column.onSort != null ? () => column.onSort!(dataColumnIndex, sortColumnIndex != dataColumnIndex || !sortAscending) : null,
+        sorted: dataColumnIndex == sortColumnIndex,
+        ascending: sortAscending,
+        overlayColor: effectiveHeadingRowColor,
+      );
+      rowIndex = 1;
+      for (final DataRow row in rows) {
+        final DataCell cell = row.cells[dataColumnIndex];
+        tableRows[rowIndex].children![displayColumnIndex] = _buildDataCell(
+          context: context,
+          padding: padding,
+          label: cell.child,
+          numeric: column.numeric,
+          placeholder: cell.placeholder,
+          showEditIcon: cell.showEditIcon,
+          onTap: cell.onTap,
+          onSelectChanged: () => row.onSelectChanged != null ? row.onSelectChanged!(!row.selected) : null,
+          overlayColor: row.color ?? effectiveDataRowColor,
+        );
+        rowIndex += 1;
+      }
+      displayColumnIndex += 1;
+    }
+
+    return Container(
+      decoration: decoration ?? theme.dataTableTheme.decoration,
+      child: Material(
+        type: MaterialType.transparency,
+        child: Table(
+          columnWidths: tableColumns.asMap(),
+          children: tableRows,
+        ),
+      ),
+    );
+  }
+}
+
+/// A rectangular area of a Material that responds to touch but clips
+/// its ink splashes to the current table row of the nearest table.
+///
+/// Must have an ancestor [Material] widget in which to cause ink
+/// reactions and an ancestor [Table] widget to establish a row.
+///
+/// The [TableRowInkWell] must be in the same coordinate space (modulo
+/// translations) as the [Table]. If it's rotated or scaled or
+/// otherwise transformed, it will not be able to describe the
+/// rectangle of the row in its own coordinate system as a [Rect], and
+/// thus the splash will not occur. (In general, this is easy to
+/// achieve: just put the [TableRowInkWell] as the direct child of the
+/// [Table], and put the other contents of the cell inside it.)
+class TableRowInkWell extends InkResponse {
+  /// Creates an ink well for a table row.
+  const TableRowInkWell({
+    Key? key,
+    Widget? child,
+    GestureTapCallback? onTap,
+    GestureTapCallback? onDoubleTap,
+    GestureLongPressCallback? onLongPress,
+    ValueChanged<bool>? onHighlightChanged,
+    MaterialStateProperty<Color?>? overlayColor,
+  }) : super(
+    key: key,
+    child: child,
+    onTap: onTap,
+    onDoubleTap: onDoubleTap,
+    onLongPress: onLongPress,
+    onHighlightChanged: onHighlightChanged,
+    containedInkWell: true,
+    highlightShape: BoxShape.rectangle,
+    overlayColor: overlayColor,
+  );
+
+  @override
+  RectCallback getRectCallback(RenderBox referenceBox) {
+    return () {
+      RenderObject cell = referenceBox;
+      AbstractNode? table = cell.parent;
+      final Matrix4 transform = Matrix4.identity();
+      while (table is RenderObject && table is! RenderTable) {
+        table.applyPaintTransform(cell, transform);
+        assert(table == cell.parent);
+        cell = table;
+        table = table.parent;
+      }
+      if (table is RenderTable) {
+        final TableCellParentData cellParentData = cell.parentData! as TableCellParentData;
+        assert(cellParentData.y != null);
+        final Rect rect = table.getRowBox(cellParentData.y!);
+        // The rect is in the table's coordinate space. We need to change it to the
+        // TableRowInkWell's coordinate space.
+        table.applyPaintTransform(cell, transform);
+        final Offset? offset = MatrixUtils.getAsTranslation(transform);
+        if (offset != null)
+          return rect.shift(-offset);
+      }
+      return Rect.zero;
+    };
+  }
+
+  @override
+  bool debugCheckContext(BuildContext context) {
+    assert(debugCheckHasTable(context));
+    return super.debugCheckContext(context);
+  }
+}
+
+class _SortArrow extends StatefulWidget {
+  const _SortArrow({
+    Key? key,
+    required this.visible,
+    required this.up,
+    required this.duration,
+  }) : super(key: key);
+
+  final bool visible;
+
+  final bool? up;
+
+  final Duration duration;
+
+  @override
+  _SortArrowState createState() => _SortArrowState();
+}
+
+class _SortArrowState extends State<_SortArrow> with TickerProviderStateMixin {
+  late AnimationController _opacityController;
+  late Animation<double> _opacityAnimation;
+
+  late AnimationController _orientationController;
+  late Animation<double> _orientationAnimation;
+  double _orientationOffset = 0.0;
+
+  bool? _up;
+
+  static final Animatable<double> _turnTween = Tween<double>(begin: 0.0, end: math.pi)
+    .chain(CurveTween(curve: Curves.easeIn));
+
+  @override
+  void initState() {
+    super.initState();
+    _opacityAnimation = CurvedAnimation(
+      parent: _opacityController = AnimationController(
+        duration: widget.duration,
+        vsync: this,
+      ),
+      curve: Curves.fastOutSlowIn,
+    )
+    ..addListener(_rebuild);
+    _opacityController.value = widget.visible ? 1.0 : 0.0;
+    _orientationController = AnimationController(
+      duration: widget.duration,
+      vsync: this,
+    );
+    _orientationAnimation = _orientationController.drive(_turnTween)
+      ..addListener(_rebuild)
+      ..addStatusListener(_resetOrientationAnimation);
+    if (widget.visible)
+      _orientationOffset = widget.up! ? 0.0 : math.pi;
+  }
+
+  void _rebuild() {
+    setState(() {
+      // The animations changed, so we need to rebuild.
+    });
+  }
+
+  void _resetOrientationAnimation(AnimationStatus status) {
+    if (status == AnimationStatus.completed) {
+      assert(_orientationAnimation.value == math.pi);
+      _orientationOffset += math.pi;
+      _orientationController.value = 0.0; // TODO(ianh): This triggers a pointless rebuild.
+    }
+  }
+
+  @override
+  void didUpdateWidget(_SortArrow oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    bool skipArrow = false;
+    final bool? newUp = widget.up ?? _up;
+    if (oldWidget.visible != widget.visible) {
+      if (widget.visible && (_opacityController.status == AnimationStatus.dismissed)) {
+        _orientationController.stop();
+        _orientationController.value = 0.0;
+        _orientationOffset = newUp! ? 0.0 : math.pi;
+        skipArrow = true;
+      }
+      if (widget.visible) {
+        _opacityController.forward();
+      } else {
+        _opacityController.reverse();
+      }
+    }
+    if ((_up != newUp) && !skipArrow) {
+      if (_orientationController.status == AnimationStatus.dismissed) {
+        _orientationController.forward();
+      } else {
+        _orientationController.reverse();
+      }
+    }
+    _up = newUp;
+  }
+
+  @override
+  void dispose() {
+    _opacityController.dispose();
+    _orientationController.dispose();
+    super.dispose();
+  }
+
+  static const double _arrowIconBaselineOffset = -1.5;
+  static const double _arrowIconSize = 16.0;
+
+  @override
+  Widget build(BuildContext context) {
+    return Opacity(
+      opacity: _opacityAnimation.value,
+      child: Transform(
+        transform: Matrix4.rotationZ(_orientationOffset + _orientationAnimation.value)
+                             ..setTranslationRaw(0.0, _arrowIconBaselineOffset, 0.0),
+        alignment: Alignment.center,
+        child: const Icon(
+          Icons.arrow_upward,
+          size: _arrowIconSize,
+        ),
+      ),
+    );
+  }
+}
+
+class _NullTableColumnWidth extends TableColumnWidth {
+  const _NullTableColumnWidth();
+
+  @override
+  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) => throw UnimplementedError();
+
+  @override
+  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) => throw UnimplementedError();
+}
+
+class _NullWidget extends Widget {
+  const _NullWidget();
+
+  @override
+  Element createElement() => throw UnimplementedError();
+}
diff --git a/lib/src/material/data_table_source.dart b/lib/src/material/data_table_source.dart
new file mode 100644
index 0000000..c13643d
--- /dev/null
+++ b/lib/src/material/data_table_source.dart
@@ -0,0 +1,62 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'data_table.dart';
+
+/// A data source for obtaining row data for [PaginatedDataTable] objects.
+///
+/// A data table source provides two main pieces of information:
+///
+/// * The number of rows in the data table ([rowCount]).
+/// * The data for each row (indexed from `0` to `rowCount - 1`).
+///
+/// It also provides a listener API ([addListener]/[removeListener]) so that
+/// consumers of the data can be notified when it changes. When the data
+/// changes, call [notifyListeners] to send the notifications.
+///
+/// DataTableSource objects are expected to be long-lived, not recreated with
+/// each build.
+abstract class DataTableSource extends ChangeNotifier {
+  /// Called to obtain the data about a particular row.
+  ///
+  /// The [new DataRow.byIndex] constructor provides a convenient way to construct
+  /// [DataRow] objects for this callback's purposes without having to worry about
+  /// independently keying each row.
+  ///
+  /// If the given index does not correspond to a row, or if no data is yet
+  /// available for a row, then return null. The row will be left blank and a
+  /// loading indicator will be displayed over the table. Once data is available
+  /// or once it is firmly established that the row index in question is beyond
+  /// the end of the table, call [notifyListeners].
+  ///
+  /// Data returned from this method must be consistent for the lifetime of the
+  /// object. If the row count changes, then a new delegate must be provided.
+  DataRow? getRow(int index);
+
+  /// Called to obtain the number of rows to tell the user are available.
+  ///
+  /// If [isRowCountApproximate] is false, then this must be an accurate number,
+  /// and [getRow] must return a non-null value for all indices in the range 0
+  /// to one less than the row count.
+  ///
+  /// If [isRowCountApproximate] is true, then the user will be allowed to
+  /// attempt to display rows up to this [rowCount], and the display will
+  /// indicate that the count is approximate. The row count should therefore be
+  /// greater than the actual number of rows if at all possible.
+  ///
+  /// If the row count changes, call [notifyListeners].
+  int get rowCount;
+
+  /// Called to establish if [rowCount] is a precise number or might be an
+  /// over-estimate. If this returns true (i.e. the count is approximate), and
+  /// then later the exact number becomes available, then call
+  /// [notifyListeners].
+  bool get isRowCountApproximate;
+
+  /// Called to obtain the number of rows that are currently selected.
+  ///
+  /// If the selected row count changes, call [notifyListeners].
+  int get selectedRowCount;
+}
diff --git a/lib/src/material/data_table_theme.dart b/lib/src/material/data_table_theme.dart
new file mode 100644
index 0000000..6fd7033
--- /dev/null
+++ b/lib/src/material/data_table_theme.dart
@@ -0,0 +1,249 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'material_state.dart';
+import 'theme.dart';
+
+/// Defines default property values for descendant [DataTable]
+/// widgets.
+///
+/// Descendant widgets obtain the current [DataTableThemeData] object
+/// using `DataTableTheme.of(context)`. Instances of
+/// [DataTableThemeData] can be customized with
+/// [DataTableThemeData.copyWith].
+///
+/// Typically a [DataTableThemeData] is specified as part of the
+/// overall [Theme] with [ThemeData.dataTableTheme].
+///
+/// All [DataTableThemeData] properties are `null` by default. When
+/// null, the [DataTable] will use the values from [ThemeData] if they exist,
+/// otherwise it will provide its own defaults based on the overall [Theme]'s
+/// textTheme and colorScheme. See the individual [DataTable] properties for
+/// details.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class DataTableThemeData with Diagnosticable {
+  /// Creates a theme that can be used for [ThemeData.dataTableTheme].
+  const DataTableThemeData({
+    this.decoration,
+    this.dataRowColor,
+    this.dataRowHeight,
+    this.dataTextStyle,
+    this.headingRowColor,
+    this.headingRowHeight,
+    this.headingTextStyle,
+    this.horizontalMargin,
+    this.columnSpacing,
+    this.dividerThickness,
+  });
+
+  /// {@macro flutter.material.dataTable.decoration}
+  final Decoration? decoration;
+
+  /// {@macro flutter.material.dataTable.dataRowColor}
+  /// {@macro flutter.material.DataTable.dataRowColor}
+  final MaterialStateProperty<Color?>? dataRowColor;
+
+  /// {@macro flutter.material.dataTable.dataRowHeight}
+  final double? dataRowHeight;
+
+  /// {@macro flutter.material.dataTable.dataTextStyle}
+  final TextStyle? dataTextStyle;
+
+  /// {@macro flutter.material.dataTable.headingRowColor}
+  /// {@macro flutter.material.DataTable.headingRowColor}
+  final MaterialStateProperty<Color?>? headingRowColor;
+
+  /// {@macro flutter.material.dataTable.headingRowHeight}
+  final double? headingRowHeight;
+
+  /// {@macro flutter.material.dataTable.headingTextStyle}
+  final TextStyle? headingTextStyle;
+
+  /// {@macro flutter.material.dataTable.horizontalMargin}
+  final double? horizontalMargin;
+
+  /// {@macro flutter.material.dataTable.columnSpacing}
+  final double? columnSpacing;
+
+  /// {@macro flutter.material.dataTable.dividerThickness}
+  final double? dividerThickness;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  DataTableThemeData copyWith({
+    Decoration? decoration,
+    MaterialStateProperty<Color?>? dataRowColor,
+    double? dataRowHeight,
+    TextStyle? dataTextStyle,
+    MaterialStateProperty<Color?>? headingRowColor,
+    double? headingRowHeight,
+    TextStyle? headingTextStyle,
+    double? horizontalMargin,
+    double? columnSpacing,
+    double? dividerThickness,
+  }) {
+    return DataTableThemeData(
+      decoration: decoration ?? this.decoration,
+      dataRowColor: dataRowColor ?? this.dataRowColor,
+      dataRowHeight: dataRowHeight ?? this.dataRowHeight,
+      dataTextStyle: dataTextStyle ?? this.dataTextStyle,
+      headingRowColor: headingRowColor ?? this.headingRowColor,
+      headingRowHeight: headingRowHeight ?? this.headingRowHeight,
+      headingTextStyle: headingTextStyle ?? this.headingTextStyle,
+      horizontalMargin: horizontalMargin ?? this.horizontalMargin,
+      columnSpacing: columnSpacing ?? this.columnSpacing,
+      dividerThickness: dividerThickness ?? this.dividerThickness,
+    );
+  }
+
+  /// Linearly interpolate between two [DataTableThemeData]s.
+  ///
+  /// The argument `t` must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static DataTableThemeData lerp(DataTableThemeData a, DataTableThemeData b, double t) {
+    assert(t != null);
+    return DataTableThemeData(
+      decoration: Decoration.lerp(a.decoration, b.decoration, t),
+      dataRowColor: _lerpProperties<Color?>(a.dataRowColor, b.dataRowColor, t, Color.lerp),
+      dataRowHeight: lerpDouble(a.dataRowHeight, b.dataRowHeight, t),
+      dataTextStyle: TextStyle.lerp(a.dataTextStyle, b.dataTextStyle, t),
+      headingRowColor: _lerpProperties<Color?>(a.headingRowColor, b.headingRowColor, t, Color.lerp),
+      headingRowHeight: lerpDouble(a.headingRowHeight, b.headingRowHeight, t),
+      headingTextStyle: TextStyle.lerp(a.headingTextStyle, b.headingTextStyle, t),
+      horizontalMargin: lerpDouble(a.horizontalMargin, b.horizontalMargin, t),
+      columnSpacing: lerpDouble(a.columnSpacing, b.columnSpacing, t),
+      dividerThickness: lerpDouble(a.dividerThickness, b.dividerThickness, t)
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      decoration,
+      dataRowColor,
+      dataRowHeight,
+      dataTextStyle,
+      headingRowColor,
+      headingRowHeight,
+      headingTextStyle,
+      horizontalMargin,
+      columnSpacing,
+      dividerThickness,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is DataTableThemeData
+      && other.decoration == decoration
+      && other.dataRowColor == dataRowColor
+      && other.dataRowHeight == dataRowHeight
+      && other.dataTextStyle == dataTextStyle
+      && other.headingRowColor == headingRowColor
+      && other.headingRowHeight == headingRowHeight
+      && other.headingTextStyle == headingTextStyle
+      && other.horizontalMargin == horizontalMargin
+      && other.columnSpacing == columnSpacing
+      && other.dividerThickness == dividerThickness;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Decoration>('decoration', decoration, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('dataRowColor', dataRowColor, defaultValue: null));
+    properties.add(DoubleProperty('dataRowHeight', dataRowHeight, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('dataTextStyle', dataTextStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('headingRowColor', headingRowColor, defaultValue: null));
+    properties.add(DoubleProperty('headingRowHeight', headingRowHeight, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('headingTextStyle', headingTextStyle, defaultValue: null));
+    properties.add(DoubleProperty('horizontalMargin', horizontalMargin, defaultValue: null));
+    properties.add(DoubleProperty('columnSpacing', columnSpacing, defaultValue: null));
+    properties.add(DoubleProperty('dividerThickness', dividerThickness, defaultValue: null));
+  }
+
+  static MaterialStateProperty<T>? _lerpProperties<T>(MaterialStateProperty<T>? a, MaterialStateProperty<T>? b, double t, T Function(T?, T?, double) lerpFunction ) {
+    // Avoid creating a _LerpProperties object for a common case.
+    if (a == null && b == null)
+      return null;
+    return _LerpProperties<T>(a, b, t, lerpFunction);
+  }
+}
+
+class _LerpProperties<T> implements MaterialStateProperty<T> {
+  const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
+
+  final MaterialStateProperty<T>? a;
+  final MaterialStateProperty<T>? b;
+  final double t;
+  final T Function(T?, T?, double) lerpFunction;
+
+  @override
+  T resolve(Set<MaterialState> states) {
+    final T? resolvedA = a?.resolve(states);
+    final T? resolvedB = b?.resolve(states);
+    return lerpFunction(resolvedA, resolvedB, t);
+  }
+}
+
+/// Applies a data table theme to descendant [DataTable] widgets.
+///
+/// Descendant widgets obtain the current theme's [DataTableTheme] object using
+/// [DataTableTheme.of]. When a widget uses [DataTableTheme.of], it is
+/// automatically rebuilt if the theme later changes.
+///
+/// A data table theme can be specified as part of the overall Material
+/// theme using [ThemeData.dataTableTheme].
+///
+/// See also:
+///
+///  * [DataTableThemeData], which describes the actual configuration
+///    of a data table theme.
+class DataTableTheme extends InheritedWidget {
+  /// Constructs a data table theme that configures all descendant
+  /// [DataTable] widgets.
+  ///
+  /// The [data] must not be null.
+  const DataTableTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null), super(key: key, child: child);
+
+  /// The properties used for all descendant [DataTable] widgets.
+  final DataTableThemeData data;
+
+  /// Returns the configuration [data] from the closest
+  /// [DataTableTheme] ancestor. If there is no ancestor, it returns
+  /// [ThemeData.dataTableTheme]. Applications can assume that the
+  /// returned value will not be null.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// DataTableThemeData theme = DataTableTheme.of(context);
+  /// ```
+  static DataTableThemeData of(BuildContext context) {
+    final DataTableTheme? dataTableTheme = context.dependOnInheritedWidgetOfExactType<DataTableTheme>();
+    return dataTableTheme?.data ?? Theme.of(context).dataTableTheme;
+  }
+
+  @override
+  bool updateShouldNotify(DataTableTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/date.dart b/lib/src/material/date.dart
new file mode 100644
index 0000000..04fff37
--- /dev/null
+++ b/lib/src/material/date.dart
@@ -0,0 +1,232 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show hashValues;
+
+import 'package:flute/foundation.dart';
+
+import 'material_localizations.dart';
+
+/// Utility functions for working with dates.
+class DateUtils {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  DateUtils._();
+
+  /// Returns a [DateTime] with the date of the original, but time set to
+  /// midnight.
+  static DateTime dateOnly(DateTime date) {
+    return DateTime(date.year, date.month, date.day);
+  }
+
+  /// Returns a [DateTimeRange] with the dates of the original, but with times
+  /// set to midnight.
+  ///
+  /// See also:
+  ///  * [dateOnly], which does the same thing for a single date.
+  static DateTimeRange datesOnly(DateTimeRange range) {
+    return DateTimeRange(start: dateOnly(range.start), end: dateOnly(range.end));
+  }
+
+  /// Returns true if the two [DateTime] objects have the same day, month, and
+  /// year, or are both null.
+  static bool isSameDay(DateTime? dateA, DateTime? dateB) {
+    return
+      dateA?.year == dateB?.year &&
+      dateA?.month == dateB?.month &&
+      dateA?.day == dateB?.day;
+  }
+
+  /// Returns true if the two [DateTime] objects have the same month and
+  /// year, or are both null.
+  static bool isSameMonth(DateTime? dateA, DateTime? dateB) {
+    return
+      dateA?.year == dateB?.year &&
+      dateA?.month == dateB?.month;
+  }
+
+  /// Determines the number of months between two [DateTime] objects.
+  ///
+  /// For example:
+  /// ```
+  /// DateTime date1 = DateTime(year: 2019, month: 6, day: 15);
+  /// DateTime date2 = DateTime(year: 2020, month: 1, day: 15);
+  /// int delta = monthDelta(date1, date2);
+  /// ```
+  ///
+  /// The value for `delta` would be `7`.
+  static int monthDelta(DateTime startDate, DateTime endDate) {
+    return (endDate.year - startDate.year) * 12 + endDate.month - startDate.month;
+  }
+
+  /// Returns a [DateTime] that is [monthDate] with the added number
+  /// of months and the day set to 1 and time set to midnight.
+  ///
+  /// For example:
+  /// ```
+  /// DateTime date = DateTime(year: 2019, month: 1, day: 15);
+  /// DateTime futureDate = DateUtils.addMonthsToMonthDate(date, 3);
+  /// ```
+  ///
+  /// `date` would be January 15, 2019.
+  /// `futureDate` would be April 1, 2019 since it adds 3 months.
+  static  DateTime addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) {
+    return DateTime(monthDate.year, monthDate.month + monthsToAdd);
+  }
+
+  /// Returns a [DateTime] with the added number of days and time set to
+  /// midnight.
+  static DateTime addDaysToDate(DateTime date, int days) {
+    return DateTime(date.year, date.month, date.day + days);
+  }
+
+  /// Computes the offset from the first day of the week that the first day of
+  /// the [month] falls on.
+  ///
+  /// For example, September 1, 2017 falls on a Friday, which in the calendar
+  /// localized for United States English appears as:
+  ///
+  /// ```
+  /// S M T W T F S
+  /// _ _ _ _ _ 1 2
+  /// ```
+  ///
+  /// The offset for the first day of the months is the number of leading blanks
+  /// in the calendar, i.e. 5.
+  ///
+  /// The same date localized for the Russian calendar has a different offset,
+  /// because the first day of week is Monday rather than Sunday:
+  ///
+  /// ```
+  /// M T W T F S S
+  /// _ _ _ _ 1 2 3
+  /// ```
+  ///
+  /// So the offset is 4, rather than 5.
+  ///
+  /// This code consolidates the following:
+  ///
+  /// - [DateTime.weekday] provides a 1-based index into days of week, with 1
+  ///   falling on Monday.
+  /// - [MaterialLocalizations.firstDayOfWeekIndex] provides a 0-based index
+  ///   into the [MaterialLocalizations.narrowWeekdays] list.
+  /// - [MaterialLocalizations.narrowWeekdays] list provides localized names of
+  ///   days of week, always starting with Sunday and ending with Saturday.
+  static int firstDayOffset(int year, int month, MaterialLocalizations localizations) {
+    // 0-based day of week for the month and year, with 0 representing Monday.
+    final int weekdayFromMonday = DateTime(year, month).weekday - 1;
+
+    // 0-based start of week depending on the locale, with 0 representing Sunday.
+    int firstDayOfWeekIndex = localizations.firstDayOfWeekIndex;
+
+    // firstDayOfWeekIndex recomputed to be Monday-based, in order to compare with
+    // weekdayFromMonday.
+    firstDayOfWeekIndex = (firstDayOfWeekIndex - 1) % 7;
+
+    // Number of days between the first day of week appearing on the calendar,
+    // and the day corresponding to the first of the month.
+    return (weekdayFromMonday - firstDayOfWeekIndex) % 7;
+  }
+
+  /// Returns the number of days in a month, according to the proleptic
+  /// Gregorian calendar.
+  ///
+  /// This applies the leap year logic introduced by the Gregorian reforms of
+  /// 1582. It will not give valid results for dates prior to that time.
+  static int getDaysInMonth(int year, int month) {
+    if (month == DateTime.february) {
+      final bool isLeapYear = (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0);
+      return isLeapYear ? 29 : 28;
+    }
+    const List<int> daysInMonth = <int>[31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
+    return daysInMonth[month - 1];
+  }
+}
+
+/// Mode of the date picker dialog.
+///
+/// Either a calendar or text input. In [calendar] mode, a calendar view is
+/// displayed and the user taps the day they wish to select. In [input] mode a
+/// [TextField] is displayed and the user types in the date they wish to select.
+///
+/// See also:
+///
+///  * [showDatePicker] and [showDateRangePicker], which use this to control
+///    the initial entry mode of their dialogs.
+enum DatePickerEntryMode {
+  /// Tapping on a calendar.
+  calendar,
+
+  /// Text input.
+  input,
+}
+
+/// Initial display of a calendar date picker.
+///
+/// Either a grid of available years or a monthly calendar.
+///
+/// See also:
+///
+///  * [showDatePicker], which shows a dialog that contains a material design
+///    date picker.
+///  * [CalendarDatePicker], widget which implements the material design date picker.
+enum DatePickerMode {
+  /// Choosing a month and day.
+  day,
+
+  /// Choosing a year.
+  year,
+}
+
+/// Signature for predicating dates for enabled date selections.
+///
+/// See [showDatePicker], which has a [SelectableDayPredicate] parameter used
+/// to specify allowable days in the date picker.
+typedef SelectableDayPredicate = bool Function(DateTime day);
+
+/// Encapsulates a start and end [DateTime] that represent the range of dates.
+///
+/// The range includes the [start] and [end] dates. The [start] and [end] dates
+/// may be equal to indicate a date range of a single day. The [start] date must
+/// not be after the [end] date.
+///
+/// See also:
+///  * [showDateRangePicker], which displays a dialog that allows the user to
+///    select a date range.
+@immutable
+class DateTimeRange {
+  /// Creates a date range for the given start and end [DateTime].
+  DateTimeRange({
+    required this.start,
+    required this.end,
+  }) : assert(start != null),
+       assert(end != null),
+       assert(!start.isAfter(end));
+
+  /// The start of the range of dates.
+  final DateTime start;
+
+  /// The end of the range of dates.
+  final DateTime end;
+
+  /// Returns a [Duration] of the time between [start] and [end].
+  ///
+  /// See [DateTime.difference] for more details.
+  Duration get duration => end.difference(start);
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is DateTimeRange
+      && other.start == start
+      && other.end == end;
+  }
+
+  @override
+  int get hashCode => hashValues(start, end);
+
+  @override
+  String toString() => '$start - $end';
+}
diff --git a/lib/src/material/date_picker.dart b/lib/src/material/date_picker.dart
new file mode 100644
index 0000000..2aa7d99
--- /dev/null
+++ b/lib/src/material/date_picker.dart
@@ -0,0 +1,2631 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/gestures.dart' show DragStartBehavior;
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'app_bar.dart';
+import 'back_button.dart';
+import 'calendar_date_picker.dart';
+import 'color_scheme.dart';
+import 'date.dart';
+import 'debug.dart';
+import 'dialog.dart';
+import 'dialog_theme.dart';
+import 'divider.dart';
+import 'icon_button.dart';
+import 'icons.dart';
+import 'ink_well.dart';
+import 'input_border.dart';
+import 'input_date_picker_form_field.dart';
+import 'input_decorator.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'scaffold.dart';
+import 'text_button.dart';
+import 'text_field.dart';
+import 'text_theme.dart';
+import 'theme.dart';
+
+const Size _calendarPortraitDialogSize = Size(330.0, 518.0);
+const Size _calendarLandscapeDialogSize = Size(496.0, 346.0);
+const Size _inputPortraitDialogSize = Size(330.0, 270.0);
+const Size _inputLandscapeDialogSize = Size(496, 160.0);
+const Size _inputRangeLandscapeDialogSize = Size(496, 164.0);
+const Duration _dialogSizeAnimationDuration = Duration(milliseconds: 200);
+const double _inputFormPortraitHeight = 98.0;
+const double _inputFormLandscapeHeight = 108.0;
+
+/// Shows a dialog containing a Material Design date picker.
+///
+/// The returned [Future] resolves to the date selected by the user when the
+/// user confirms the dialog. If the user cancels the dialog, null is returned.
+///
+/// When the date picker is first displayed, it will show the month of
+/// [initialDate], with [initialDate] selected.
+///
+/// The [firstDate] is the earliest allowable date. The [lastDate] is the latest
+/// allowable date. [initialDate] must either fall between these dates,
+/// or be equal to one of them. For each of these [DateTime] parameters, only
+/// their dates are considered. Their time fields are ignored. They must all
+/// be non-null.
+///
+/// The [currentDate] represents the current day (i.e. today). This
+/// date will be highlighted in the day grid. If null, the date of
+/// `DateTime.now()` will be used.
+///
+/// An optional [initialEntryMode] argument can be used to display the date
+/// picker in the [DatePickerEntryMode.calendar] (a calendar month grid)
+/// or [DatePickerEntryMode.input] (a text input field) mode.
+/// It defaults to [DatePickerEntryMode.calendar] and must be non-null.
+///
+/// An optional [selectableDayPredicate] function can be passed in to only allow
+/// certain days for selection. If provided, only the days that
+/// [selectableDayPredicate] returns true for will be selectable. For example,
+/// this can be used to only allow weekdays for selection. If provided, it must
+/// return true for [initialDate].
+///
+/// The following optional string parameters allow you to override the default
+/// text used for various parts of the dialog:
+///
+///   * [helpText], label displayed at the top of the dialog.
+///   * [cancelText], label on the cancel button.
+///   * [confirmText], label on the ok button.
+///   * [errorFormatText], message used when the input text isn't in a proper date format.
+///   * [errorInvalidText], message used when the input text isn't a selectable date.
+///   * [fieldHintText], text used to prompt the user when no text has been entered in the field.
+///   * [fieldLabelText], label for the date text input field.
+///
+/// An optional [locale] argument can be used to set the locale for the date
+/// picker. It defaults to the ambient locale provided by [Localizations].
+///
+/// An optional [textDirection] argument can be used to set the text direction
+/// ([TextDirection.ltr] or [TextDirection.rtl]) for the date picker. It
+/// defaults to the ambient text direction provided by [Directionality]. If both
+/// [locale] and [textDirection] are non-null, [textDirection] overrides the
+/// direction chosen for the [locale].
+///
+/// The [context], [useRootNavigator] and [routeSettings] arguments are passed to
+/// [showDialog], the documentation for which discusses how it is used. [context]
+/// and [useRootNavigator] must be non-null.
+///
+/// The [builder] parameter can be used to wrap the dialog widget
+/// to add inherited widgets like [Theme].
+///
+/// An optional [initialDatePickerMode] argument can be used to have the
+/// calendar date picker initially appear in the [DatePickerMode.year] or
+/// [DatePickerMode.day] mode. It defaults to [DatePickerMode.day], and
+/// must be non-null.
+///
+/// See also:
+///
+///  * [showDateRangePicker], which shows a material design date range picker
+///    used to select a range of dates.
+///  * [CalendarDatePicker], which provides the calendar grid used by the date picker dialog.
+///  * [InputDatePickerFormField], which provides a text input field for entering dates.
+///
+Future<DateTime?> showDatePicker({
+  required BuildContext context,
+  required DateTime initialDate,
+  required DateTime firstDate,
+  required DateTime lastDate,
+  DateTime? currentDate,
+  DatePickerEntryMode initialEntryMode = DatePickerEntryMode.calendar,
+  SelectableDayPredicate? selectableDayPredicate,
+  String? helpText,
+  String? cancelText,
+  String? confirmText,
+  Locale? locale,
+  bool useRootNavigator = true,
+  RouteSettings? routeSettings,
+  TextDirection? textDirection,
+  TransitionBuilder? builder,
+  DatePickerMode initialDatePickerMode = DatePickerMode.day,
+  String? errorFormatText,
+  String? errorInvalidText,
+  String? fieldHintText,
+  String? fieldLabelText,
+}) async {
+  assert(context != null);
+  assert(initialDate != null);
+  assert(firstDate != null);
+  assert(lastDate != null);
+  initialDate = DateUtils.dateOnly(initialDate);
+  firstDate = DateUtils.dateOnly(firstDate);
+  lastDate = DateUtils.dateOnly(lastDate);
+  assert(
+    !lastDate.isBefore(firstDate),
+    'lastDate $lastDate must be on or after firstDate $firstDate.'
+  );
+  assert(
+    !initialDate.isBefore(firstDate),
+    'initialDate $initialDate must be on or after firstDate $firstDate.'
+  );
+  assert(
+    !initialDate.isAfter(lastDate),
+    'initialDate $initialDate must be on or before lastDate $lastDate.'
+  );
+  assert(
+    selectableDayPredicate == null || selectableDayPredicate(initialDate),
+    'Provided initialDate $initialDate must satisfy provided selectableDayPredicate.'
+  );
+  assert(initialEntryMode != null);
+  assert(useRootNavigator != null);
+  assert(initialDatePickerMode != null);
+  assert(debugCheckHasMaterialLocalizations(context));
+
+  Widget dialog = _DatePickerDialog(
+    initialDate: initialDate,
+    firstDate: firstDate,
+    lastDate: lastDate,
+    currentDate: currentDate,
+    initialEntryMode: initialEntryMode,
+    selectableDayPredicate: selectableDayPredicate,
+    helpText: helpText,
+    cancelText: cancelText,
+    confirmText: confirmText,
+    initialCalendarMode: initialDatePickerMode,
+    errorFormatText: errorFormatText,
+    errorInvalidText: errorInvalidText,
+    fieldHintText: fieldHintText,
+    fieldLabelText: fieldLabelText,
+  );
+
+  if (textDirection != null) {
+    dialog = Directionality(
+      textDirection: textDirection,
+      child: dialog,
+    );
+  }
+
+  if (locale != null) {
+    dialog = Localizations.override(
+      context: context,
+      locale: locale,
+      child: dialog,
+    );
+  }
+
+  return showDialog<DateTime>(
+    context: context,
+    useRootNavigator: useRootNavigator,
+    routeSettings: routeSettings,
+    builder: (BuildContext context) {
+      return builder == null ? dialog : builder(context, dialog);
+    },
+  );
+}
+
+class _DatePickerDialog extends StatefulWidget {
+  _DatePickerDialog({
+    Key? key,
+    required DateTime initialDate,
+    required DateTime firstDate,
+    required DateTime lastDate,
+    DateTime? currentDate,
+    this.initialEntryMode = DatePickerEntryMode.calendar,
+    this.selectableDayPredicate,
+    this.cancelText,
+    this.confirmText,
+    this.helpText,
+    this.initialCalendarMode = DatePickerMode.day,
+    this.errorFormatText,
+    this.errorInvalidText,
+    this.fieldHintText,
+    this.fieldLabelText,
+  }) : assert(initialDate != null),
+       assert(firstDate != null),
+       assert(lastDate != null),
+       initialDate = DateUtils.dateOnly(initialDate),
+       firstDate = DateUtils.dateOnly(firstDate),
+       lastDate = DateUtils.dateOnly(lastDate),
+       currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()),
+       assert(initialEntryMode != null),
+       assert(initialCalendarMode != null),
+       super(key: key) {
+    assert(
+      !this.lastDate.isBefore(this.firstDate),
+      'lastDate ${this.lastDate} must be on or after firstDate ${this.firstDate}.'
+    );
+    assert(
+      !this.initialDate.isBefore(this.firstDate),
+      'initialDate ${this.initialDate} must be on or after firstDate ${this.firstDate}.'
+    );
+    assert(
+      !this.initialDate.isAfter(this.lastDate),
+      'initialDate ${this.initialDate} must be on or before lastDate ${this.lastDate}.'
+    );
+    assert(
+      selectableDayPredicate == null || selectableDayPredicate!(this.initialDate),
+      'Provided initialDate ${this.initialDate} must satisfy provided selectableDayPredicate'
+    );
+  }
+
+  /// The initially selected [DateTime] that the picker should display.
+  final DateTime initialDate;
+
+  /// The earliest allowable [DateTime] that the user can select.
+  final DateTime firstDate;
+
+  /// The latest allowable [DateTime] that the user can select.
+  final DateTime lastDate;
+
+  /// The [DateTime] representing today. It will be highlighted in the day grid.
+  final DateTime currentDate;
+
+  final DatePickerEntryMode initialEntryMode;
+
+  /// Function to provide full control over which [DateTime] can be selected.
+  final SelectableDayPredicate? selectableDayPredicate;
+
+  /// The text that is displayed on the cancel button.
+  final String? cancelText;
+
+  /// The text that is displayed on the confirm button.
+  final String? confirmText;
+
+  /// The text that is displayed at the top of the header.
+  ///
+  /// This is used to indicate to the user what they are selecting a date for.
+  final String? helpText;
+
+  /// The initial display of the calendar picker.
+  final DatePickerMode initialCalendarMode;
+
+  final String? errorFormatText;
+
+  final String? errorInvalidText;
+
+  final String? fieldHintText;
+
+  final String? fieldLabelText;
+
+  @override
+  _DatePickerDialogState createState() => _DatePickerDialogState();
+}
+
+class _DatePickerDialogState extends State<_DatePickerDialog> {
+
+  late DatePickerEntryMode _entryMode;
+  late DateTime _selectedDate;
+  late bool _autoValidate;
+  final GlobalKey _calendarPickerKey = GlobalKey();
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
+
+  @override
+  void initState() {
+    super.initState();
+    _entryMode = widget.initialEntryMode;
+    _selectedDate = widget.initialDate;
+    _autoValidate = false;
+  }
+
+  void _handleOk() {
+    if (_entryMode == DatePickerEntryMode.input) {
+      final FormState form = _formKey.currentState!;
+      if (!form.validate()) {
+        setState(() => _autoValidate = true);
+        return;
+      }
+      form.save();
+    }
+    Navigator.pop(context, _selectedDate);
+  }
+
+  void _handleCancel() {
+    Navigator.pop(context);
+  }
+
+  void _handleEntryModeToggle() {
+    setState(() {
+      switch (_entryMode) {
+        case DatePickerEntryMode.calendar:
+          _autoValidate = false;
+          _entryMode = DatePickerEntryMode.input;
+          break;
+        case DatePickerEntryMode.input:
+          _formKey.currentState!.save();
+          _entryMode = DatePickerEntryMode.calendar;
+          break;
+      }
+    });
+  }
+
+  void _handleDateChanged(DateTime date) {
+    setState(() => _selectedDate = date);
+  }
+
+  Size _dialogSize(BuildContext context) {
+    final Orientation orientation = MediaQuery.of(context).orientation;
+    switch (_entryMode) {
+      case DatePickerEntryMode.calendar:
+        switch (orientation) {
+          case Orientation.portrait:
+            return _calendarPortraitDialogSize;
+          case Orientation.landscape:
+            return _calendarLandscapeDialogSize;
+        }
+      case DatePickerEntryMode.input:
+        switch (orientation) {
+          case Orientation.portrait:
+            return _inputPortraitDialogSize;
+          case Orientation.landscape:
+            return _inputLandscapeDialogSize;
+        }
+    }
+  }
+
+  static final Map<LogicalKeySet, Intent> _formShortcutMap = <LogicalKeySet, Intent>{
+    // Pressing enter on the field will move focus to the next field or control.
+    LogicalKeySet(LogicalKeyboardKey.enter): const NextFocusIntent(),
+  };
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final ColorScheme colorScheme = theme.colorScheme;
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final Orientation orientation = MediaQuery.of(context).orientation;
+    final TextTheme textTheme = theme.textTheme;
+    // Constrain the textScaleFactor to the largest supported value to prevent
+    // layout issues.
+    final double textScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 1.3);
+
+    final String dateText = localizations.formatMediumDate(_selectedDate);
+    final Color dateColor = colorScheme.brightness == Brightness.light
+      ? colorScheme.onPrimary
+      : colorScheme.onSurface;
+    final TextStyle? dateStyle = orientation == Orientation.landscape
+      ? textTheme.headline5?.copyWith(color: dateColor)
+      : textTheme.headline4?.copyWith(color: dateColor);
+
+    final Widget actions = Container(
+      alignment: AlignmentDirectional.centerEnd,
+      constraints: const BoxConstraints(minHeight: 52.0),
+      padding: const EdgeInsets.symmetric(horizontal: 8),
+      child: OverflowBar(
+        spacing: 8,
+        children: <Widget>[
+          TextButton(
+            child: Text(widget.cancelText ?? localizations.cancelButtonLabel),
+            onPressed: _handleCancel,
+          ),
+          TextButton(
+            child: Text(widget.confirmText ?? localizations.okButtonLabel),
+            onPressed: _handleOk,
+          ),
+        ],
+      ),
+    );
+
+    final Widget picker;
+    final IconData entryModeIcon;
+    final String entryModeTooltip;
+    switch (_entryMode) {
+      case DatePickerEntryMode.calendar:
+        picker = CalendarDatePicker(
+          key: _calendarPickerKey,
+          initialDate: _selectedDate,
+          firstDate: widget.firstDate,
+          lastDate: widget.lastDate,
+          currentDate: widget.currentDate,
+          onDateChanged: _handleDateChanged,
+          selectableDayPredicate: widget.selectableDayPredicate,
+          initialCalendarMode: widget.initialCalendarMode,
+        );
+        entryModeIcon = Icons.edit;
+        entryModeTooltip = localizations.inputDateModeButtonLabel;
+        break;
+
+      case DatePickerEntryMode.input:
+        picker = Form(
+          key: _formKey,
+          autovalidate: _autoValidate,
+          child: Container(
+            padding: const EdgeInsets.symmetric(horizontal: 24),
+            height: orientation == Orientation.portrait ? _inputFormPortraitHeight : _inputFormLandscapeHeight,
+            child: Shortcuts(
+              shortcuts: _formShortcutMap,
+              child: Column(
+                children: <Widget>[
+                  const Spacer(),
+                  InputDatePickerFormField(
+                    initialDate: _selectedDate,
+                    firstDate: widget.firstDate,
+                    lastDate: widget.lastDate,
+                    onDateSubmitted: _handleDateChanged,
+                    onDateSaved: _handleDateChanged,
+                    selectableDayPredicate: widget.selectableDayPredicate,
+                    errorFormatText: widget.errorFormatText,
+                    errorInvalidText: widget.errorInvalidText,
+                    fieldHintText: widget.fieldHintText,
+                    fieldLabelText: widget.fieldLabelText,
+                    autofocus: true,
+                  ),
+                  const Spacer(),
+                ],
+              ),
+            ),
+          ),
+        );
+        entryModeIcon = Icons.calendar_today;
+        entryModeTooltip = localizations.calendarModeButtonLabel;
+        break;
+    }
+
+    final Widget header = _DatePickerHeader(
+      helpText: widget.helpText ?? localizations.datePickerHelpText,
+      titleText: dateText,
+      titleStyle: dateStyle,
+      orientation: orientation,
+      isShort: orientation == Orientation.landscape,
+      icon: entryModeIcon,
+      iconTooltip: entryModeTooltip,
+      onIconPressed: _handleEntryModeToggle,
+    );
+
+    final Size dialogSize = _dialogSize(context) * textScaleFactor;
+    return Dialog(
+      child: AnimatedContainer(
+        width: dialogSize.width,
+        height: dialogSize.height,
+        duration: _dialogSizeAnimationDuration,
+        curve: Curves.easeIn,
+        child: MediaQuery(
+          data: MediaQuery.of(context).copyWith(
+            textScaleFactor: textScaleFactor,
+          ),
+          child: Builder(builder: (BuildContext context) {
+            switch (orientation) {
+              case Orientation.portrait:
+                return Column(
+                  mainAxisSize: MainAxisSize.min,
+                  crossAxisAlignment: CrossAxisAlignment.stretch,
+                  children: <Widget>[
+                    header,
+                    Expanded(child: picker),
+                    actions,
+                  ],
+                );
+              case Orientation.landscape:
+                return Row(
+                  mainAxisSize: MainAxisSize.min,
+                  crossAxisAlignment: CrossAxisAlignment.stretch,
+                  children: <Widget>[
+                    header,
+                    Flexible(
+                      child: Column(
+                        mainAxisSize: MainAxisSize.min,
+                        crossAxisAlignment: CrossAxisAlignment.stretch,
+                        children: <Widget>[
+                          Expanded(child: picker),
+                          actions,
+                        ],
+                      ),
+                    ),
+                  ],
+                );
+            }
+          }),
+        ),
+      ),
+      insetPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0),
+      clipBehavior: Clip.antiAlias,
+    );
+  }
+}
+
+/// Re-usable widget that displays the selected date (in large font) and the
+/// help text above it.
+///
+/// These types include:
+///
+/// * Single Date picker with calendar mode.
+/// * Single Date picker with manual input mode.
+/// * Date Range picker with manual input mode.
+///
+/// [helpText], [orientation], [icon], [onIconPressed] are required and must be
+/// non-null.
+class _DatePickerHeader extends StatelessWidget {
+
+  /// Creates a header for use in a date picker dialog.
+  const _DatePickerHeader({
+    Key? key,
+    required this.helpText,
+    required this.titleText,
+    this.titleSemanticsLabel,
+    required this.titleStyle,
+    required this.orientation,
+    this.isShort = false,
+    required this.icon,
+    required this.iconTooltip,
+    required this.onIconPressed,
+  }) : assert(helpText != null),
+       assert(orientation != null),
+       assert(isShort != null),
+       super(key: key);
+
+  static const double _datePickerHeaderLandscapeWidth = 152.0;
+  static const double _datePickerHeaderPortraitHeight = 120.0;
+  static const double _headerPaddingLandscape = 16.0;
+
+  /// The text that is displayed at the top of the header.
+  ///
+  /// This is used to indicate to the user what they are selecting a date for.
+  final String helpText;
+
+  /// The text that is displayed at the center of the header.
+  final String titleText;
+
+  /// The semantic label associated with the [titleText].
+  final String? titleSemanticsLabel;
+
+  /// The [TextStyle] that the title text is displayed with.
+  final TextStyle? titleStyle;
+
+  /// The orientation is used to decide how to layout its children.
+  final Orientation orientation;
+
+  /// Indicates the header is being displayed in a shorter/narrower context.
+  ///
+  /// This will be used to tighten up the space between the help text and date
+  /// text if `true`. Additionally, it will use a smaller typography style if
+  /// `true`.
+  ///
+  /// This is necessary for displaying the manual input mode in
+  /// landscape orientation, in order to account for the keyboard height.
+  final bool isShort;
+
+  /// The mode-switching icon that will be displayed in the lower right
+  /// in portrait, and lower left in landscape.
+  ///
+  /// The available icons are described in [Icons].
+  final IconData icon;
+
+  /// The text that is displayed for the tooltip of the icon.
+  final String iconTooltip;
+
+  /// Callback when the user taps the icon in the header.
+  ///
+  /// The picker will use this to toggle between entry modes.
+  final VoidCallback onIconPressed;
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final ColorScheme colorScheme = theme.colorScheme;
+    final TextTheme textTheme = theme.textTheme;
+
+    // The header should use the primary color in light themes and surface color in dark
+    final bool isDark = colorScheme.brightness == Brightness.dark;
+    final Color primarySurfaceColor = isDark ? colorScheme.surface : colorScheme.primary;
+    final Color onPrimarySurfaceColor = isDark ? colorScheme.onSurface : colorScheme.onPrimary;
+
+    final TextStyle? helpStyle = textTheme.overline?.copyWith(
+      color: onPrimarySurfaceColor,
+    );
+
+    final Text help = Text(
+      helpText,
+      style: helpStyle,
+      maxLines: 1,
+      overflow: TextOverflow.ellipsis,
+    );
+    final Text title = Text(
+      titleText,
+      semanticsLabel: titleSemanticsLabel ?? titleText,
+      style: titleStyle,
+      maxLines: orientation == Orientation.portrait ? 1 : 2,
+      overflow: TextOverflow.ellipsis,
+    );
+    final IconButton icon = IconButton(
+      icon: Icon(this.icon),
+      color: onPrimarySurfaceColor,
+      tooltip: iconTooltip,
+      onPressed: onIconPressed,
+    );
+
+    switch (orientation) {
+      case Orientation.portrait:
+        return SizedBox(
+          height: _datePickerHeaderPortraitHeight,
+          child: Material(
+            color: primarySurfaceColor,
+            child: Padding(
+              padding: const EdgeInsetsDirectional.only(
+                start: 24,
+                end: 12,
+              ),
+              child: Column(
+                crossAxisAlignment: CrossAxisAlignment.start,
+                children: <Widget>[
+                  const SizedBox(height: 16),
+                  help,
+                  const Flexible(child: SizedBox(height: 38)),
+                  Row(
+                    children: <Widget>[
+                      Expanded(child: title),
+                      icon,
+                    ],
+                  ),
+                ],
+              ),
+            ),
+          ),
+        );
+      case Orientation.landscape:
+        return SizedBox(
+          width: _datePickerHeaderLandscapeWidth,
+          child: Material(
+            color: primarySurfaceColor,
+            child: Column(
+              crossAxisAlignment: CrossAxisAlignment.start,
+              children: <Widget>[
+                const SizedBox(height: 16),
+                Padding(
+                  padding: const EdgeInsets.symmetric(
+                    horizontal: _headerPaddingLandscape,
+                  ),
+                  child: help,
+                ),
+                SizedBox(height: isShort ? 16 : 56),
+                Expanded(
+                  child: Padding(
+                    padding: const EdgeInsets.symmetric(
+                      horizontal: _headerPaddingLandscape,
+                    ),
+                    child: title,
+                  ),
+                ),
+                Padding(
+                  padding: const EdgeInsets.symmetric(
+                    horizontal: 4,
+                  ),
+                  child: icon,
+                ),
+              ],
+            ),
+          ),
+        );
+    }
+  }
+}
+
+/// Shows a full screen modal dialog containing a Material Design date range
+/// picker.
+///
+/// The returned [Future] resolves to the [DateTimeRange] selected by the user
+/// when the user saves their selection. If the user cancels the dialog, null is
+/// returned.
+///
+/// If [initialDateRange] is non-null, then it will be used as the initially
+/// selected date range. If it is provided, [initialDateRange.start] must be
+/// before or on [initialDateRange.end].
+///
+/// The [firstDate] is the earliest allowable date. The [lastDate] is the latest
+/// allowable date. Both must be non-null.
+///
+/// If an initial date range is provided, [initialDateRange.start]
+/// and [initialDateRange.end] must both fall between or on [firstDate] and
+/// [lastDate]. For all of these [DateTime] values, only their dates are
+/// considered. Their time fields are ignored.
+///
+/// The [currentDate] represents the current day (i.e. today). This
+/// date will be highlighted in the day grid. If null, the date of
+/// `DateTime.now()` will be used.
+///
+/// An optional [initialEntryMode] argument can be used to display the date
+/// picker in the [DatePickerEntryMode.calendar] (a scrollable calendar month
+/// grid) or [DatePickerEntryMode.input] (two text input fields) mode.
+/// It defaults to [DatePickerEntryMode.calendar] and must be non-null.
+///
+/// The following optional string parameters allow you to override the default
+/// text used for various parts of the dialog:
+///
+///   * [helpText], the label displayed at the top of the dialog.
+///   * [cancelText], the label on the cancel button for the text input mode.
+///   * [confirmText],the  label on the ok button for the text input mode.
+///   * [saveText], the label on the save button for the fullscreen calendar
+///     mode.
+///   * [errorFormatText], the message used when an input text isn't in a proper
+///     date format.
+///   * [errorInvalidText], the message used when an input text isn't a
+///     selectable date.
+///   * [errorInvalidRangeText], the message used when the date range is
+///     invalid (e.g. start date is after end date).
+///   * [fieldStartHintText], the text used to prompt the user when no text has
+///     been entered in the start field.
+///   * [fieldEndHintText], the text used to prompt the user when no text has
+///     been entered in the end field.
+///   * [fieldStartLabelText], the label for the start date text input field.
+///   * [fieldEndLabelText], the label for the end date text input field.
+///
+/// An optional [locale] argument can be used to set the locale for the date
+/// picker. It defaults to the ambient locale provided by [Localizations].
+///
+/// An optional [textDirection] argument can be used to set the text direction
+/// ([TextDirection.ltr] or [TextDirection.rtl]) for the date picker. It
+/// defaults to the ambient text direction provided by [Directionality]. If both
+/// [locale] and [textDirection] are non-null, [textDirection] overrides the
+/// direction chosen for the [locale].
+///
+/// The [context], [useRootNavigator] and [routeSettings] arguments are passed
+/// to [showDialog], the documentation for which discusses how it is used.
+/// [context] and [useRootNavigator] must be non-null.
+///
+/// The [builder] parameter can be used to wrap the dialog widget
+/// to add inherited widgets like [Theme].
+///
+/// See also:
+///
+///  * [showDatePicker], which shows a material design date picker used to
+///    select a single date.
+///  * [DateTimeRange], which is used to describe a date range.
+///
+Future<DateTimeRange?> showDateRangePicker({
+  required BuildContext context,
+  DateTimeRange? initialDateRange,
+  required DateTime firstDate,
+  required DateTime lastDate,
+  DateTime? currentDate,
+  DatePickerEntryMode initialEntryMode = DatePickerEntryMode.calendar,
+  String? helpText,
+  String? cancelText,
+  String? confirmText,
+  String? saveText,
+  String? errorFormatText,
+  String? errorInvalidText,
+  String? errorInvalidRangeText,
+  String? fieldStartHintText,
+  String? fieldEndHintText,
+  String? fieldStartLabelText,
+  String? fieldEndLabelText,
+  Locale? locale,
+  bool useRootNavigator = true,
+  RouteSettings? routeSettings,
+  TextDirection? textDirection,
+  TransitionBuilder? builder,
+}) async {
+  assert(context != null);
+  assert(
+    initialDateRange == null || (initialDateRange.start != null && initialDateRange.end != null),
+    'initialDateRange must be null or have non-null start and end dates.'
+  );
+  assert(
+    initialDateRange == null || !initialDateRange.start.isAfter(initialDateRange.end),
+    'initialDateRange\'s start date must not be after it\'s end date.'
+  );
+  initialDateRange = initialDateRange == null ? null : DateUtils.datesOnly(initialDateRange);
+  assert(firstDate != null);
+  firstDate = DateUtils.dateOnly(firstDate);
+  assert(lastDate != null);
+  lastDate = DateUtils.dateOnly(lastDate);
+  assert(
+    !lastDate.isBefore(firstDate),
+    'lastDate $lastDate must be on or after firstDate $firstDate.'
+  );
+  assert(
+    initialDateRange == null || !initialDateRange.start.isBefore(firstDate),
+    'initialDateRange\'s start date must be on or after firstDate $firstDate.'
+  );
+  assert(
+    initialDateRange == null || !initialDateRange.end.isBefore(firstDate),
+    'initialDateRange\'s end date must be on or after firstDate $firstDate.'
+  );
+  assert(
+    initialDateRange == null || !initialDateRange.start.isAfter(lastDate),
+    'initialDateRange\'s start date must be on or before lastDate $lastDate.'
+  );
+  assert(
+    initialDateRange == null || !initialDateRange.end.isAfter(lastDate),
+    'initialDateRange\'s end date must be on or before lastDate $lastDate.'
+  );
+  currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now());
+  assert(initialEntryMode != null);
+  assert(useRootNavigator != null);
+  assert(debugCheckHasMaterialLocalizations(context));
+
+  Widget dialog = _DateRangePickerDialog(
+    initialDateRange: initialDateRange,
+    firstDate: firstDate,
+    lastDate: lastDate,
+    currentDate: currentDate,
+    initialEntryMode: initialEntryMode,
+    helpText: helpText,
+    cancelText: cancelText,
+    confirmText: confirmText,
+    saveText: saveText,
+    errorFormatText: errorFormatText,
+    errorInvalidText: errorInvalidText,
+    errorInvalidRangeText: errorInvalidRangeText,
+    fieldStartHintText: fieldStartHintText,
+    fieldEndHintText: fieldEndHintText,
+    fieldStartLabelText: fieldStartLabelText,
+    fieldEndLabelText: fieldEndLabelText,
+  );
+
+  if (textDirection != null) {
+    dialog = Directionality(
+      textDirection: textDirection,
+      child: dialog,
+    );
+  }
+
+  if (locale != null) {
+    dialog = Localizations.override(
+      context: context,
+      locale: locale,
+      child: dialog,
+    );
+  }
+
+  return showDialog<DateTimeRange>(
+    context: context,
+    useRootNavigator: useRootNavigator,
+    routeSettings: routeSettings,
+    useSafeArea: false,
+    builder: (BuildContext context) {
+      return builder == null ? dialog : builder(context, dialog);
+    },
+  );
+}
+
+/// Returns a locale-appropriate string to describe the start of a date range.
+///
+/// If `startDate` is null, then it defaults to 'Start Date', otherwise if it
+/// is in the same year as the `endDate` then it will use the short month
+/// day format (i.e. 'Jan 21'). Otherwise it will return the short date format
+/// (i.e. 'Jan 21, 2020').
+String _formatRangeStartDate(MaterialLocalizations localizations, DateTime? startDate, DateTime? endDate) {
+  return startDate == null
+    ? localizations.dateRangeStartLabel
+    : (endDate == null || startDate.year == endDate.year)
+      ? localizations.formatShortMonthDay(startDate)
+      : localizations.formatShortDate(startDate);
+}
+
+/// Returns an locale-appropriate string to describe the end of a date range.
+///
+/// If `endDate` is null, then it defaults to 'End Date', otherwise if it
+/// is in the same year as the `startDate` and the `currentDate` then it will
+/// just use the short month day format (i.e. 'Jan 21'), otherwise it will
+/// include the year (i.e. 'Jan 21, 2020').
+String _formatRangeEndDate(MaterialLocalizations localizations, DateTime? startDate, DateTime? endDate, DateTime currentDate) {
+  return endDate == null
+    ? localizations.dateRangeEndLabel
+    : (startDate != null && startDate.year == endDate.year && startDate.year == currentDate.year)
+      ? localizations.formatShortMonthDay(endDate)
+      : localizations.formatShortDate(endDate);
+}
+
+class _DateRangePickerDialog extends StatefulWidget {
+  const _DateRangePickerDialog({
+    Key? key,
+    this.initialDateRange,
+    required this.firstDate,
+    required this.lastDate,
+    this.currentDate,
+    this.initialEntryMode = DatePickerEntryMode.calendar,
+    this.helpText,
+    this.cancelText,
+    this.confirmText,
+    this.saveText,
+    this.errorInvalidRangeText,
+    this.errorFormatText,
+    this.errorInvalidText,
+    this.fieldStartHintText,
+    this.fieldEndHintText,
+    this.fieldStartLabelText,
+    this.fieldEndLabelText,
+  }) : super(key: key);
+
+  final DateTimeRange? initialDateRange;
+  final DateTime firstDate;
+  final DateTime lastDate;
+  final DateTime? currentDate;
+  final DatePickerEntryMode initialEntryMode;
+  final String? cancelText;
+  final String? confirmText;
+  final String? saveText;
+  final String? helpText;
+  final String? errorInvalidRangeText;
+  final String? errorFormatText;
+  final String? errorInvalidText;
+  final String? fieldStartHintText;
+  final String? fieldEndHintText;
+  final String? fieldStartLabelText;
+  final String? fieldEndLabelText;
+
+  @override
+  _DateRangePickerDialogState createState() => _DateRangePickerDialogState();
+}
+
+class _DateRangePickerDialogState extends State<_DateRangePickerDialog> {
+  late DatePickerEntryMode _entryMode;
+  DateTime? _selectedStart;
+  DateTime? _selectedEnd;
+  late bool _autoValidate;
+  final GlobalKey _calendarPickerKey = GlobalKey();
+  final GlobalKey<_InputDateRangePickerState> _inputPickerKey = GlobalKey<_InputDateRangePickerState>();
+
+  @override
+  void initState() {
+    super.initState();
+    _selectedStart = widget.initialDateRange?.start;
+    _selectedEnd = widget.initialDateRange?.end;
+    _entryMode = widget.initialEntryMode;
+    _autoValidate = false;
+  }
+
+  void _handleOk() {
+    if (_entryMode == DatePickerEntryMode.input) {
+      final _InputDateRangePickerState picker = _inputPickerKey.currentState!;
+      if (!picker.validate()) {
+        setState(() {
+          _autoValidate = true;
+        });
+        return;
+      }
+    }
+    final DateTimeRange? selectedRange = _hasSelectedDateRange
+        ? DateTimeRange(start: _selectedStart!, end: _selectedEnd!)
+        : null;
+
+    Navigator.pop(context, selectedRange);
+  }
+
+  void _handleCancel() {
+    Navigator.pop(context);
+  }
+
+  void _handleEntryModeToggle() {
+    setState(() {
+      switch (_entryMode) {
+        case DatePickerEntryMode.calendar:
+          _autoValidate = false;
+          _entryMode = DatePickerEntryMode.input;
+          break;
+
+        case DatePickerEntryMode.input:
+        // Validate the range dates
+          if (_selectedStart != null &&
+              (_selectedStart!.isBefore(widget.firstDate) || _selectedStart!.isAfter(widget.lastDate))) {
+            _selectedStart = null;
+            // With no valid start date, having an end date makes no sense for the UI.
+            _selectedEnd = null;
+          }
+          if (_selectedEnd != null &&
+              (_selectedEnd!.isBefore(widget.firstDate) || _selectedEnd!.isAfter(widget.lastDate))) {
+            _selectedEnd = null;
+          }
+          // If invalid range (start after end), then just use the start date
+          if (_selectedStart != null && _selectedEnd != null && _selectedStart!.isAfter(_selectedEnd!)) {
+            _selectedEnd = null;
+          }
+          _entryMode = DatePickerEntryMode.calendar;
+          break;
+      }
+    });
+  }
+
+  void _handleStartDateChanged(DateTime? date) {
+    setState(() => _selectedStart = date);
+  }
+
+  void _handleEndDateChanged(DateTime? date) {
+    setState(() => _selectedEnd = date);
+  }
+
+  bool get _hasSelectedDateRange => _selectedStart != null && _selectedEnd != null;
+
+  @override
+  Widget build(BuildContext context) {
+    final MediaQueryData mediaQuery = MediaQuery.of(context);
+    final Orientation orientation = mediaQuery.orientation;
+    final double textScaleFactor = math.min(mediaQuery.textScaleFactor, 1.3);
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+
+    final Widget contents;
+    final Size size;
+    ShapeBorder? shape;
+    final double elevation;
+    final EdgeInsets insetPadding;
+    switch (_entryMode) {
+      case DatePickerEntryMode.calendar:
+        contents = _CalendarRangePickerDialog(
+          key: _calendarPickerKey,
+          selectedStartDate: _selectedStart,
+          selectedEndDate: _selectedEnd,
+          firstDate: widget.firstDate,
+          lastDate: widget.lastDate,
+          currentDate: widget.currentDate,
+          onStartDateChanged: _handleStartDateChanged,
+          onEndDateChanged: _handleEndDateChanged,
+          onConfirm: _hasSelectedDateRange ? _handleOk : null,
+          onCancel: _handleCancel,
+          onToggleEntryMode: _handleEntryModeToggle,
+          confirmText: widget.saveText ?? localizations.saveButtonLabel,
+          helpText: widget.helpText ?? localizations.dateRangePickerHelpText,
+        );
+        size = mediaQuery.size;
+        insetPadding = const EdgeInsets.all(0.0);
+        shape = const RoundedRectangleBorder(
+            borderRadius: BorderRadius.all(Radius.zero)
+        );
+        elevation = 0;
+        break;
+
+      case DatePickerEntryMode.input:
+        contents = _InputDateRangePickerDialog(
+          selectedStartDate: _selectedStart,
+          selectedEndDate: _selectedEnd,
+          currentDate: widget.currentDate,
+          picker: Container(
+            padding: const EdgeInsets.symmetric(horizontal: 24),
+            height: orientation == Orientation.portrait
+                ? _inputFormPortraitHeight
+                : _inputFormLandscapeHeight,
+            child: Column(
+              children: <Widget>[
+                const Spacer(),
+                _InputDateRangePicker(
+                  key: _inputPickerKey,
+                  initialStartDate: _selectedStart,
+                  initialEndDate: _selectedEnd,
+                  firstDate: widget.firstDate,
+                  lastDate: widget.lastDate,
+                  onStartDateChanged: _handleStartDateChanged,
+                  onEndDateChanged: _handleEndDateChanged,
+                  autofocus: true,
+                  autovalidate: _autoValidate,
+                  helpText: widget.helpText,
+                  errorInvalidRangeText: widget.errorInvalidRangeText,
+                  errorFormatText: widget.errorFormatText,
+                  errorInvalidText: widget.errorInvalidText,
+                  fieldStartHintText: widget.fieldStartHintText,
+                  fieldEndHintText: widget.fieldEndHintText,
+                  fieldStartLabelText: widget.fieldStartLabelText,
+                  fieldEndLabelText: widget.fieldEndLabelText,
+                ),
+                const Spacer(),
+              ],
+            ),
+          ),
+          onConfirm: _handleOk,
+          onCancel: _handleCancel,
+          onToggleEntryMode: _handleEntryModeToggle,
+          confirmText: widget.confirmText ?? localizations.okButtonLabel,
+          cancelText: widget.cancelText ?? localizations.cancelButtonLabel,
+          helpText: widget.helpText ?? localizations.dateRangePickerHelpText,
+        );
+        final DialogTheme dialogTheme = Theme.of(context).dialogTheme;
+        size = orientation == Orientation.portrait ? _inputPortraitDialogSize : _inputRangeLandscapeDialogSize;
+        insetPadding = const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0);
+        shape = dialogTheme.shape;
+        elevation = dialogTheme.elevation ?? 24;
+        break;
+    }
+
+    return Dialog(
+      child: AnimatedContainer(
+        width: size.width,
+        height: size.height,
+        duration: _dialogSizeAnimationDuration,
+        curve: Curves.easeIn,
+        child: MediaQuery(
+          data: MediaQuery.of(context).copyWith(
+            textScaleFactor: textScaleFactor,
+          ),
+          child: Builder(builder: (BuildContext context) {
+            return contents;
+          }),
+        ),
+      ),
+      insetPadding: insetPadding,
+      shape: shape,
+      elevation: elevation,
+      clipBehavior: Clip.antiAlias,
+    );
+  }
+}
+
+class _CalendarRangePickerDialog extends StatelessWidget {
+  const _CalendarRangePickerDialog({
+    Key? key,
+    required this.selectedStartDate,
+    required this.selectedEndDate,
+    required this.firstDate,
+    required this.lastDate,
+    required this.currentDate,
+    required this.onStartDateChanged,
+    required this.onEndDateChanged,
+    required this.onConfirm,
+    required this.onCancel,
+    required this.onToggleEntryMode,
+    required this.confirmText,
+    required this.helpText,
+  }) : super(key: key);
+
+  final DateTime? selectedStartDate;
+  final DateTime? selectedEndDate;
+  final DateTime firstDate;
+  final DateTime lastDate;
+  final DateTime? currentDate;
+  final ValueChanged<DateTime> onStartDateChanged;
+  final ValueChanged<DateTime?> onEndDateChanged;
+  final VoidCallback? onConfirm;
+  final VoidCallback? onCancel;
+  final VoidCallback? onToggleEntryMode;
+  final String confirmText;
+  final String helpText;
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final ColorScheme colorScheme = theme.colorScheme;
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final Orientation orientation = MediaQuery.of(context).orientation;
+    final TextTheme textTheme = theme.textTheme;
+    final Color headerForeground = colorScheme.brightness == Brightness.light
+        ? colorScheme.onPrimary
+        : colorScheme.onSurface;
+    final Color headerDisabledForeground = headerForeground.withOpacity(0.38);
+    final String startDateText = _formatRangeStartDate(localizations, selectedStartDate, selectedEndDate);
+    final String endDateText = _formatRangeEndDate(localizations, selectedStartDate, selectedEndDate, DateTime.now());
+    final TextStyle? headlineStyle = textTheme.headline5;
+    final TextStyle? startDateStyle = headlineStyle?.apply(
+        color: selectedStartDate != null ? headerForeground : headerDisabledForeground
+    );
+    final TextStyle? endDateStyle = headlineStyle?.apply(
+        color: selectedEndDate != null ? headerForeground : headerDisabledForeground
+    );
+    final TextStyle saveButtonStyle = textTheme.button!.apply(
+        color: onConfirm != null ? headerForeground : headerDisabledForeground
+    );
+
+    final IconButton entryModeIcon = IconButton(
+      padding: EdgeInsets.zero,
+      color: headerForeground,
+      icon: const Icon(Icons.edit),
+      tooltip: localizations.inputDateModeButtonLabel,
+      onPressed: onToggleEntryMode,
+    );
+
+    return SafeArea(
+      top: false,
+      left: false,
+      right: false,
+      child: Scaffold(
+        appBar: AppBar(
+          leading: CloseButton(
+            onPressed: onCancel,
+          ),
+          actions: <Widget>[
+            if (orientation == Orientation.landscape) entryModeIcon,
+            TextButton(
+              onPressed: onConfirm,
+              child: Text(confirmText, style: saveButtonStyle),
+            ),
+            const SizedBox(width: 8),
+          ],
+          bottom: PreferredSize(
+            child: Row(children: <Widget>[
+              SizedBox(width: MediaQuery.of(context).size.width < 360 ? 42 : 72),
+              Expanded(
+                child: Semantics(
+                  label: '$helpText $startDateText to $endDateText',
+                  excludeSemantics: true,
+                  child: Column(
+                    crossAxisAlignment: CrossAxisAlignment.start,
+                    children: <Widget>[
+                      Text(
+                        helpText,
+                        style: textTheme.overline!.apply(
+                          color: headerForeground,
+                        ),
+                      ),
+                      const SizedBox(height: 8),
+                      Row(
+                        children: <Widget>[
+                          Text(
+                            startDateText,
+                            style: startDateStyle,
+                            maxLines: 1,
+                            overflow: TextOverflow.ellipsis,
+                          ),
+                          Text(' – ', style: startDateStyle,
+                          ),
+                          Flexible(
+                            child: Text(
+                              endDateText,
+                              style: endDateStyle,
+                              maxLines: 1,
+                              overflow: TextOverflow.ellipsis,
+                            ),
+                          ),
+                        ],
+                      ),
+                      const SizedBox(height: 16),
+                    ],
+                  ),
+                ),
+              ),
+              if (orientation == Orientation.portrait)
+                Padding(
+                  padding: const EdgeInsets.symmetric(horizontal: 8.0),
+                  child: entryModeIcon,
+                ),
+            ]),
+            preferredSize: const Size(double.infinity, 64),
+          ),
+        ),
+        body: _CalendarDateRangePicker(
+          initialStartDate: selectedStartDate,
+          initialEndDate: selectedEndDate,
+          firstDate: firstDate,
+          lastDate: lastDate,
+          currentDate: currentDate,
+          onStartDateChanged: onStartDateChanged,
+          onEndDateChanged: onEndDateChanged,
+        ),
+      ),
+    );
+  }
+}
+
+const Duration _monthScrollDuration = Duration(milliseconds: 200);
+
+const double _monthItemHeaderHeight = 58.0;
+const double _monthItemFooterHeight = 12.0;
+const double _monthItemRowHeight = 42.0;
+const double _monthItemSpaceBetweenRows = 8.0;
+const double _horizontalPadding = 8.0;
+const double _maxCalendarWidthLandscape = 384.0;
+const double _maxCalendarWidthPortrait = 480.0;
+
+/// Displays a scrollable calendar grid that allows a user to select a range
+/// of dates.
+class _CalendarDateRangePicker extends StatefulWidget {
+  /// Creates a scrollable calendar grid for picking date ranges.
+  _CalendarDateRangePicker({
+    Key? key,
+    DateTime? initialStartDate,
+    DateTime? initialEndDate,
+    required DateTime firstDate,
+    required DateTime lastDate,
+    DateTime? currentDate,
+    required this.onStartDateChanged,
+    required this.onEndDateChanged,
+  }) : initialStartDate = initialStartDate != null ? DateUtils.dateOnly(initialStartDate) : null,
+       initialEndDate = initialEndDate != null ? DateUtils.dateOnly(initialEndDate) : null,
+       assert(firstDate != null),
+       assert(lastDate != null),
+       firstDate = DateUtils.dateOnly(firstDate),
+       lastDate = DateUtils.dateOnly(lastDate),
+       currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()),
+       super(key: key) {
+    assert(
+      this.initialStartDate == null || this.initialEndDate == null || !this.initialStartDate!.isAfter(initialEndDate!),
+      'initialStartDate must be on or before initialEndDate.'
+    );
+    assert(
+      !this.lastDate.isBefore(this.firstDate),
+      'firstDate must be on or before lastDate.'
+    );
+  }
+
+  /// The [DateTime] that represents the start of the initial date range selection.
+  final DateTime? initialStartDate;
+
+  /// The [DateTime] that represents the end of the initial date range selection.
+  final DateTime? initialEndDate;
+
+  /// The earliest allowable [DateTime] that the user can select.
+  final DateTime firstDate;
+
+  /// The latest allowable [DateTime] that the user can select.
+  final DateTime lastDate;
+
+  /// The [DateTime] representing today. It will be highlighted in the day grid.
+  final DateTime currentDate;
+
+  /// Called when the user changes the start date of the selected range.
+  final ValueChanged<DateTime>? onStartDateChanged;
+
+  /// Called when the user changes the end date of the selected range.
+  final ValueChanged<DateTime?>? onEndDateChanged;
+
+  @override
+  _CalendarDateRangePickerState createState() => _CalendarDateRangePickerState();
+}
+
+class _CalendarDateRangePickerState extends State<_CalendarDateRangePicker> {
+  final GlobalKey _scrollViewKey = GlobalKey();
+  DateTime? _startDate;
+  DateTime? _endDate;
+  int _initialMonthIndex = 0;
+  late ScrollController _controller;
+  late bool _showWeekBottomDivider;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = ScrollController();
+    _controller.addListener(_scrollListener);
+
+    _startDate = widget.initialStartDate;
+    _endDate = widget.initialEndDate;
+
+    // Calculate the index for the initially displayed month. This is needed to
+    // divide the list of months into two `SliverList`s.
+    final DateTime initialDate = widget.initialStartDate ?? widget.currentDate;
+    if (!initialDate.isBefore(widget.firstDate) &&
+        !initialDate.isAfter(widget.lastDate)) {
+      _initialMonthIndex = DateUtils.monthDelta(widget.firstDate, initialDate);
+    }
+
+    _showWeekBottomDivider = _initialMonthIndex != 0;
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  void _scrollListener() {
+    if (_controller.offset <= _controller.position.minScrollExtent) {
+      setState(() {
+        _showWeekBottomDivider = false;
+      });
+    } else if (!_showWeekBottomDivider) {
+      setState(() {
+        _showWeekBottomDivider = true;
+      });
+    }
+  }
+
+  int get _numberOfMonths => DateUtils.monthDelta(widget.firstDate, widget.lastDate) + 1;
+
+  void _vibrate() {
+    switch (Theme.of(context).platform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+        HapticFeedback.vibrate();
+        break;
+      default:
+        break;
+    }
+  }
+
+  // This updates the selected date range using this logic:
+  //
+  // * From the unselected state, selecting one date creates the start date.
+  //   * If the next selection is before the start date, reset date range and
+  //     set the start date to that selection.
+  //   * If the next selection is on or after the start date, set the end date
+  //     to that selection.
+  // * After both start and end dates are selected, any subsequent selection
+  //   resets the date range and sets start date to that selection.
+  void _updateSelection(DateTime date) {
+    _vibrate();
+    setState(() {
+      if (_startDate != null && _endDate == null && !date.isBefore(_startDate!)) {
+        _endDate = date;
+        widget.onEndDateChanged?.call(_endDate);
+      } else {
+        _startDate = date;
+        widget.onStartDateChanged?.call(_startDate!);
+        if (_endDate != null) {
+          _endDate = null;
+          widget.onEndDateChanged?.call(_endDate);
+        }
+      }
+    });
+  }
+
+  Widget _buildMonthItem(BuildContext context, int index, bool beforeInitialMonth) {
+    final int monthIndex = beforeInitialMonth
+      ? _initialMonthIndex - index - 1
+      : _initialMonthIndex + index;
+    final DateTime month = DateUtils.addMonthsToMonthDate(widget.firstDate, monthIndex);
+    return _MonthItem(
+      selectedDateStart: _startDate,
+      selectedDateEnd: _endDate,
+      currentDate: widget.currentDate,
+      firstDate: widget.firstDate,
+      lastDate: widget.lastDate,
+      displayedMonth: month,
+      onChanged: _updateSelection,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    const Key sliverAfterKey = Key('sliverAfterKey');
+
+    return Column(
+      children: <Widget>[
+        _DayHeaders(),
+        if (_showWeekBottomDivider) const Divider(height: 0),
+        Expanded(
+          child: _CalendarKeyboardNavigator(
+            firstDate: widget.firstDate,
+            lastDate: widget.lastDate,
+            initialFocusedDay: _startDate ?? widget.initialStartDate ?? widget.currentDate,
+            // In order to prevent performance issues when displaying the
+            // correct initial month, 2 `SliverList`s are used to split the
+            // months. The first item in the second SliverList is the initial
+            // month to be displayed.
+            child: CustomScrollView(
+              key: _scrollViewKey,
+              controller: _controller,
+              center: sliverAfterKey,
+              slivers: <Widget>[
+                SliverList(
+                  delegate: SliverChildBuilderDelegate(
+                    (BuildContext context, int index) => _buildMonthItem(context, index, true),
+                    childCount: _initialMonthIndex,
+                  ),
+                ),
+                SliverList(
+                  key: sliverAfterKey,
+                  delegate: SliverChildBuilderDelegate(
+                    (BuildContext context, int index) => _buildMonthItem(context, index, false),
+                    childCount: _numberOfMonths - _initialMonthIndex,
+                  ),
+                ),
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+}
+
+class _CalendarKeyboardNavigator extends StatefulWidget {
+  const _CalendarKeyboardNavigator({
+    Key? key,
+    required this.child,
+    required this.firstDate,
+    required this.lastDate,
+    required this.initialFocusedDay,
+  }) : super(key: key);
+
+  final Widget child;
+  final DateTime firstDate;
+  final DateTime lastDate;
+  final DateTime initialFocusedDay;
+
+  @override
+  _CalendarKeyboardNavigatorState createState() => _CalendarKeyboardNavigatorState();
+}
+
+class _CalendarKeyboardNavigatorState extends State<_CalendarKeyboardNavigator> {
+
+  late Map<LogicalKeySet, Intent> _shortcutMap;
+  late Map<Type, Action<Intent>> _actionMap;
+  late FocusNode _dayGridFocus;
+  TraversalDirection? _dayTraversalDirection;
+  DateTime? _focusedDay;
+
+  @override
+  void initState() {
+    super.initState();
+
+    _shortcutMap = <LogicalKeySet, Intent>{
+      LogicalKeySet(LogicalKeyboardKey.arrowLeft): const DirectionalFocusIntent(TraversalDirection.left),
+      LogicalKeySet(LogicalKeyboardKey.arrowRight): const DirectionalFocusIntent(TraversalDirection.right),
+      LogicalKeySet(LogicalKeyboardKey.arrowDown): const DirectionalFocusIntent(TraversalDirection.down),
+      LogicalKeySet(LogicalKeyboardKey.arrowUp): const DirectionalFocusIntent(TraversalDirection.up),
+    };
+    _actionMap = <Type, Action<Intent>>{
+      NextFocusIntent: CallbackAction<NextFocusIntent>(onInvoke: _handleGridNextFocus),
+      PreviousFocusIntent: CallbackAction<PreviousFocusIntent>(onInvoke: _handleGridPreviousFocus),
+      DirectionalFocusIntent: CallbackAction<DirectionalFocusIntent>(onInvoke: _handleDirectionFocus),
+    };
+    _dayGridFocus = FocusNode(debugLabel: 'Day Grid');
+  }
+
+  @override
+  void dispose() {
+    _dayGridFocus.dispose();
+    super.dispose();
+  }
+
+  void _handleGridFocusChange(bool focused) {
+    setState(() {
+      if (focused) {
+        _focusedDay ??= widget.initialFocusedDay;
+      }
+    });
+  }
+
+  /// Move focus to the next element after the day grid.
+  void _handleGridNextFocus(NextFocusIntent intent) {
+    _dayGridFocus.requestFocus();
+    _dayGridFocus.nextFocus();
+  }
+
+  /// Move focus to the previous element before the day grid.
+  void _handleGridPreviousFocus(PreviousFocusIntent intent) {
+    _dayGridFocus.requestFocus();
+    _dayGridFocus.previousFocus();
+  }
+
+  /// Move the internal focus date in the direction of the given intent.
+  ///
+  /// This will attempt to move the focused day to the next selectable day in
+  /// the given direction. If the new date is not in the current month, then
+  /// the page view will be scrolled to show the new date's month.
+  ///
+  /// For horizontal directions, it will move forward or backward a day (depending
+  /// on the current [TextDirection]). For vertical directions it will move up and
+  /// down a week at a time.
+  void _handleDirectionFocus(DirectionalFocusIntent intent) {
+    assert(_focusedDay != null);
+    setState(() {
+      final DateTime? nextDate = _nextDateInDirection(_focusedDay!, intent.direction);
+      if (nextDate != null) {
+        _focusedDay = nextDate;
+        _dayTraversalDirection = intent.direction;
+      }
+    });
+  }
+
+  static const Map<TraversalDirection, int> _directionOffset = <TraversalDirection, int>{
+    TraversalDirection.up: -DateTime.daysPerWeek,
+    TraversalDirection.right: 1,
+    TraversalDirection.down: DateTime.daysPerWeek,
+    TraversalDirection.left: -1,
+  };
+
+  int _dayDirectionOffset(TraversalDirection traversalDirection, TextDirection textDirection) {
+    // Swap left and right if the text direction if RTL
+    if (textDirection == TextDirection.rtl) {
+      if (traversalDirection == TraversalDirection.left)
+        traversalDirection = TraversalDirection.right;
+      else if (traversalDirection == TraversalDirection.right)
+        traversalDirection = TraversalDirection.left;
+    }
+    return _directionOffset[traversalDirection]!;
+  }
+
+  DateTime? _nextDateInDirection(DateTime date, TraversalDirection direction) {
+    final TextDirection textDirection = Directionality.of(context);
+    final DateTime nextDate = DateUtils.addDaysToDate(date, _dayDirectionOffset(direction, textDirection));
+    if (!nextDate.isBefore(widget.firstDate) && !nextDate.isAfter(widget.lastDate)) {
+      return nextDate;
+    }
+    return null;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return FocusableActionDetector(
+      shortcuts: _shortcutMap,
+      actions: _actionMap,
+      focusNode: _dayGridFocus,
+      onFocusChange: _handleGridFocusChange,
+      child: _FocusedDate(
+        date: _dayGridFocus.hasFocus ? _focusedDay : null,
+        scrollDirection: _dayGridFocus.hasFocus ? _dayTraversalDirection : null,
+        child: widget.child,
+      ),
+    );
+  }
+}
+
+/// InheritedWidget indicating what the current focused date is for its children.
+///
+/// This is used by the [_MonthPicker] to let its children [_DayPicker]s know
+/// what the currently focused date (if any) should be.
+class _FocusedDate extends InheritedWidget {
+  const _FocusedDate({
+    Key? key,
+    required Widget child,
+    this.date,
+    this.scrollDirection,
+  }) : super(key: key, child: child);
+
+  final DateTime? date;
+  final TraversalDirection? scrollDirection;
+
+  @override
+  bool updateShouldNotify(_FocusedDate oldWidget) {
+    return !DateUtils.isSameDay(date, oldWidget.date) || scrollDirection != oldWidget.scrollDirection;
+  }
+
+  static _FocusedDate? of(BuildContext context) {
+    return context.dependOnInheritedWidgetOfExactType<_FocusedDate>();
+  }
+}
+
+
+class _DayHeaders extends StatelessWidget {
+  /// Builds widgets showing abbreviated days of week. The first widget in the
+  /// returned list corresponds to the first day of week for the current locale.
+  ///
+  /// Examples:
+  ///
+  /// ```
+  /// ┌ Sunday is the first day of week in the US (en_US)
+  /// |
+  /// S M T W T F S  <-- the returned list contains these widgets
+  /// _ _ _ _ _ 1 2
+  /// 3 4 5 6 7 8 9
+  ///
+  /// ┌ But it's Monday in the UK (en_GB)
+  /// |
+  /// M T W T F S S  <-- the returned list contains these widgets
+  /// _ _ _ _ 1 2 3
+  /// 4 5 6 7 8 9 10
+  /// ```
+  List<Widget> _getDayHeaders(TextStyle headerStyle, MaterialLocalizations localizations) {
+    final List<Widget> result = <Widget>[];
+    for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) {
+      final String weekday = localizations.narrowWeekdays[i];
+      result.add(ExcludeSemantics(
+        child: Center(child: Text(weekday, style: headerStyle)),
+      ));
+      if (i == (localizations.firstDayOfWeekIndex - 1) % 7)
+        break;
+    }
+    return result;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData themeData = Theme.of(context);
+    final ColorScheme colorScheme = themeData.colorScheme;
+    final TextStyle textStyle = themeData.textTheme.subtitle2!.apply(color: colorScheme.onSurface);
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final List<Widget> labels = _getDayHeaders(textStyle, localizations);
+
+    // Add leading and trailing containers for edges of the custom grid layout.
+    labels.insert(0, Container());
+    labels.add(Container());
+
+    return Container(
+      constraints: BoxConstraints(
+        maxWidth: MediaQuery.of(context).orientation == Orientation.landscape
+          ? _maxCalendarWidthLandscape
+          : _maxCalendarWidthPortrait,
+        maxHeight: _monthItemRowHeight,
+      ),
+      child: GridView.custom(
+        shrinkWrap: true,
+        gridDelegate: _monthItemGridDelegate,
+        childrenDelegate: SliverChildListDelegate(
+          labels,
+          addRepaintBoundaries: false,
+        ),
+      ),
+    );
+  }
+}
+
+class _MonthItemGridDelegate extends SliverGridDelegate {
+  const _MonthItemGridDelegate();
+
+  @override
+  SliverGridLayout getLayout(SliverConstraints constraints) {
+    final double tileWidth = (constraints.crossAxisExtent - 2 * _horizontalPadding) / DateTime.daysPerWeek;
+    return _MonthSliverGridLayout(
+      crossAxisCount: DateTime.daysPerWeek + 2,
+      dayChildWidth: tileWidth,
+      edgeChildWidth: _horizontalPadding,
+      reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
+    );
+  }
+
+  @override
+  bool shouldRelayout(_MonthItemGridDelegate oldDelegate) => false;
+}
+
+const _MonthItemGridDelegate _monthItemGridDelegate = _MonthItemGridDelegate();
+
+class _MonthSliverGridLayout extends SliverGridLayout {
+  /// Creates a layout that uses equally sized and spaced tiles for each day of
+  /// the week and an additional edge tile for padding at the start and end of
+  /// each row.
+  ///
+  /// This is necessary to facilitate the painting of the range highlight
+  /// correctly.
+  const _MonthSliverGridLayout({
+    required this.crossAxisCount,
+    required this.dayChildWidth,
+    required this.edgeChildWidth,
+    required this.reverseCrossAxis,
+  }) : assert(crossAxisCount != null && crossAxisCount > 0),
+       assert(dayChildWidth != null && dayChildWidth >= 0),
+       assert(edgeChildWidth != null && edgeChildWidth >= 0),
+       assert(reverseCrossAxis != null);
+
+  /// The number of children in the cross axis.
+  final int crossAxisCount;
+
+  /// The width in logical pixels of the day child widgets.
+  final double dayChildWidth;
+
+  /// The width in logical pixels of the edge child widgets.
+  final double edgeChildWidth;
+
+  /// Whether the children should be placed in the opposite order of increasing
+  /// coordinates in the cross axis.
+  ///
+  /// For example, if the cross axis is horizontal, the children are placed from
+  /// left to right when [reverseCrossAxis] is false and from right to left when
+  /// [reverseCrossAxis] is true.
+  ///
+  /// Typically set to the return value of [axisDirectionIsReversed] applied to
+  /// the [SliverConstraints.crossAxisDirection].
+  final bool reverseCrossAxis;
+
+  /// The number of logical pixels from the leading edge of one row to the
+  /// leading edge of the next row.
+  double get _rowHeight {
+    return _monthItemRowHeight + _monthItemSpaceBetweenRows;
+  }
+
+  /// The height in logical pixels of the children widgets.
+  double get _childHeight {
+    return _monthItemRowHeight;
+  }
+
+  @override
+  int getMinChildIndexForScrollOffset(double scrollOffset) {
+    return crossAxisCount * (scrollOffset ~/ _rowHeight);
+  }
+
+  @override
+  int getMaxChildIndexForScrollOffset(double scrollOffset) {
+    final int mainAxisCount = (scrollOffset / _rowHeight).ceil();
+    return math.max(0, crossAxisCount * mainAxisCount - 1);
+  }
+
+  double _getCrossAxisOffset(double crossAxisStart, bool isPadding) {
+    if (reverseCrossAxis) {
+      return
+        ((crossAxisCount - 2) * dayChildWidth + 2 * edgeChildWidth) -
+        crossAxisStart -
+        (isPadding ? edgeChildWidth : dayChildWidth);
+    }
+    return crossAxisStart;
+  }
+
+  @override
+  SliverGridGeometry getGeometryForChildIndex(int index) {
+    final int adjustedIndex = index % crossAxisCount;
+    final bool isEdge = adjustedIndex == 0 || adjustedIndex == crossAxisCount - 1;
+    final double crossAxisStart = math.max(0, (adjustedIndex - 1) * dayChildWidth + edgeChildWidth);
+
+    return SliverGridGeometry(
+      scrollOffset: (index ~/ crossAxisCount) * _rowHeight,
+      crossAxisOffset: _getCrossAxisOffset(crossAxisStart, isEdge),
+      mainAxisExtent: _childHeight,
+      crossAxisExtent: isEdge ? edgeChildWidth : dayChildWidth,
+    );
+  }
+
+  @override
+  double computeMaxScrollOffset(int childCount) {
+    assert(childCount >= 0);
+    final int mainAxisCount = ((childCount - 1) ~/ crossAxisCount) + 1;
+    final double mainAxisSpacing = _rowHeight - _childHeight;
+    return _rowHeight * mainAxisCount - mainAxisSpacing;
+  }
+}
+
+/// Displays the days of a given month and allows choosing a date range.
+///
+/// The days are arranged in a rectangular grid with one column for each day of
+/// the week.
+class _MonthItem extends StatefulWidget {
+  /// Creates a month item.
+  _MonthItem({
+    Key? key,
+    required this.selectedDateStart,
+    required this.selectedDateEnd,
+    required this.currentDate,
+    required this.onChanged,
+    required this.firstDate,
+    required this.lastDate,
+    required this.displayedMonth,
+    this.dragStartBehavior = DragStartBehavior.start,
+  }) : assert(firstDate != null),
+       assert(lastDate != null),
+       assert(!firstDate.isAfter(lastDate)),
+       assert(selectedDateStart == null || !selectedDateStart.isBefore(firstDate)),
+       assert(selectedDateEnd == null || !selectedDateEnd.isBefore(firstDate)),
+       assert(selectedDateStart == null || !selectedDateStart.isAfter(lastDate)),
+       assert(selectedDateEnd == null || !selectedDateEnd.isAfter(lastDate)),
+       assert(selectedDateStart == null || selectedDateEnd == null || !selectedDateStart.isAfter(selectedDateEnd)),
+       assert(currentDate != null),
+       assert(onChanged != null),
+       assert(displayedMonth != null),
+       assert(dragStartBehavior != null),
+       super(key: key);
+
+  /// The currently selected start date.
+  ///
+  /// This date is highlighted in the picker.
+  final DateTime? selectedDateStart;
+
+  /// The currently selected end date.
+  ///
+  /// This date is highlighted in the picker.
+  final DateTime? selectedDateEnd;
+
+  /// The current date at the time the picker is displayed.
+  final DateTime currentDate;
+
+  /// Called when the user picks a day.
+  final ValueChanged<DateTime> onChanged;
+
+  /// The earliest date the user is permitted to pick.
+  final DateTime firstDate;
+
+  /// The latest date the user is permitted to pick.
+  final DateTime lastDate;
+
+  /// The month whose days are displayed by this picker.
+  final DateTime displayedMonth;
+
+  /// Determines the way that drag start behavior is handled.
+  ///
+  /// If set to [DragStartBehavior.start], the drag gesture used to scroll a
+  /// date picker wheel will begin upon the detection of a drag gesture. If set
+  /// to [DragStartBehavior.down] it will begin when a down event is first
+  /// detected.
+  ///
+  /// In general, setting this to [DragStartBehavior.start] will make drag
+  /// animation smoother and setting it to [DragStartBehavior.down] will make
+  /// drag behavior feel slightly more reactive.
+  ///
+  /// By default, the drag start behavior is [DragStartBehavior.start].
+  ///
+  /// See also:
+  ///
+  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for
+  ///    the different behaviors.
+  final DragStartBehavior dragStartBehavior;
+
+  @override
+  _MonthItemState createState() => _MonthItemState();
+}
+
+class _MonthItemState extends State<_MonthItem> {
+  /// List of [FocusNode]s, one for each day of the month.
+  late List<FocusNode> _dayFocusNodes;
+
+  @override
+  void initState() {
+    super.initState();
+    final int daysInMonth = DateUtils.getDaysInMonth(widget.displayedMonth.year, widget.displayedMonth.month);
+    _dayFocusNodes = List<FocusNode>.generate(
+        daysInMonth,
+        (int index) => FocusNode(skipTraversal: true, debugLabel: 'Day ${index + 1}')
+    );
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    // Check to see if the focused date is in this month, if so focus it.
+    final DateTime? focusedDate = _FocusedDate.of(context)?.date;
+    if (focusedDate != null && DateUtils.isSameMonth(widget.displayedMonth, focusedDate)) {
+      _dayFocusNodes[focusedDate.day - 1].requestFocus();
+    }
+  }
+
+  @override
+  void dispose() {
+    for (final FocusNode node in _dayFocusNodes) {
+      node.dispose();
+    }
+    super.dispose();
+  }
+
+  Color _highlightColor(BuildContext context) {
+    return Theme.of(context).colorScheme.primary.withOpacity(0.12);
+  }
+
+  void _dayFocusChanged(bool focused) {
+    if (focused) {
+      final TraversalDirection? focusDirection = _FocusedDate.of(context)?.scrollDirection;
+      if (focusDirection != null) {
+        ScrollPositionAlignmentPolicy policy = ScrollPositionAlignmentPolicy.explicit;
+        switch (focusDirection) {
+          case TraversalDirection.up:
+          case TraversalDirection.left:
+            policy = ScrollPositionAlignmentPolicy.keepVisibleAtStart;
+            break;
+          case TraversalDirection.right:
+          case TraversalDirection.down:
+            policy = ScrollPositionAlignmentPolicy.keepVisibleAtEnd;
+            break;
+        }
+        Scrollable.ensureVisible(primaryFocus!.context!,
+          duration: _monthScrollDuration,
+          alignmentPolicy: policy,
+        );
+      }
+    }
+  }
+
+  Widget _buildDayItem(BuildContext context, DateTime dayToBuild, int firstDayOffset, int daysInMonth) {
+    final ThemeData theme = Theme.of(context);
+    final ColorScheme colorScheme = theme.colorScheme;
+    final TextTheme textTheme = theme.textTheme;
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final TextDirection textDirection = Directionality.of(context);
+    final Color highlightColor = _highlightColor(context);
+    final int day = dayToBuild.day;
+
+    final bool isDisabled = dayToBuild.isAfter(widget.lastDate) || dayToBuild.isBefore(widget.firstDate);
+
+    BoxDecoration? decoration;
+    TextStyle? itemStyle = textTheme.bodyText2;
+
+    final bool isRangeSelected = widget.selectedDateStart != null && widget.selectedDateEnd != null;
+    final bool isSelectedDayStart = widget.selectedDateStart != null && dayToBuild.isAtSameMomentAs(widget.selectedDateStart!);
+    final bool isSelectedDayEnd = widget.selectedDateEnd != null && dayToBuild.isAtSameMomentAs(widget.selectedDateEnd!);
+    final bool isInRange = isRangeSelected &&
+      dayToBuild.isAfter(widget.selectedDateStart!) &&
+      dayToBuild.isBefore(widget.selectedDateEnd!);
+
+    _HighlightPainter? highlightPainter;
+
+    if (isSelectedDayStart || isSelectedDayEnd) {
+      // The selected start and end dates gets a circle background
+      // highlight, and a contrasting text color.
+      itemStyle = textTheme.bodyText2?.apply(color: colorScheme.onPrimary);
+      decoration = BoxDecoration(
+        color: colorScheme.primary,
+        shape: BoxShape.circle,
+      );
+
+      if (isRangeSelected && widget.selectedDateStart != widget.selectedDateEnd) {
+        final _HighlightPainterStyle style = isSelectedDayStart
+          ? _HighlightPainterStyle.highlightTrailing
+          : _HighlightPainterStyle.highlightLeading;
+        highlightPainter = _HighlightPainter(
+          color: highlightColor,
+          style: style,
+          textDirection: textDirection,
+        );
+      }
+    } else if (isInRange) {
+      // The days within the range get a light background highlight.
+      highlightPainter = _HighlightPainter(
+        color: highlightColor,
+        style: _HighlightPainterStyle.highlightAll,
+        textDirection: textDirection,
+      );
+    } else if (isDisabled) {
+      itemStyle = textTheme.bodyText2?.apply(color: colorScheme.onSurface.withOpacity(0.38));
+    } else if (DateUtils.isSameDay(widget.currentDate, dayToBuild)) {
+      // The current day gets a different text color and a circle stroke
+      // border.
+      itemStyle = textTheme.bodyText2?.apply(color: colorScheme.primary);
+      decoration = BoxDecoration(
+        border: Border.all(color: colorScheme.primary, width: 1),
+        shape: BoxShape.circle,
+      );
+    }
+
+    // We want the day of month to be spoken first irrespective of the
+    // locale-specific preferences or TextDirection. This is because
+    // an accessibility user is more likely to be interested in the
+    // day of month before the rest of the date, as they are looking
+    // for the day of month. To do that we prepend day of month to the
+    // formatted full date.
+    String semanticLabel = '${localizations.formatDecimal(day)}, ${localizations.formatFullDate(dayToBuild)}';
+    if (isSelectedDayStart) {
+      semanticLabel = localizations.dateRangeStartDateSemanticLabel(semanticLabel);
+    } else if (isSelectedDayEnd) {
+      semanticLabel = localizations.dateRangeEndDateSemanticLabel(semanticLabel);
+    }
+
+    Widget dayWidget = Container(
+      decoration: decoration,
+      child: Center(
+        child: Semantics(
+          label: semanticLabel,
+          selected: isSelectedDayStart || isSelectedDayEnd,
+          child: ExcludeSemantics(
+            child: Text(localizations.formatDecimal(day), style: itemStyle),
+          ),
+        ),
+      ),
+    );
+
+    if (highlightPainter != null) {
+      dayWidget = CustomPaint(
+        painter: highlightPainter,
+        child: dayWidget,
+      );
+    }
+
+    if (!isDisabled) {
+      dayWidget = InkResponse(
+        focusNode: _dayFocusNodes[day - 1],
+        onTap: () => widget.onChanged(dayToBuild),
+        radius: _monthItemRowHeight / 2 + 4,
+        splashColor: colorScheme.primary.withOpacity(0.38),
+        onFocusChange: _dayFocusChanged,
+        child: dayWidget,
+      );
+    }
+
+    return dayWidget;
+  }
+
+  Widget _buildEdgeContainer(BuildContext context, bool isHighlighted) {
+    return Container(color: isHighlighted ? _highlightColor(context) : null);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData themeData = Theme.of(context);
+    final TextTheme textTheme = themeData.textTheme;
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final int year = widget.displayedMonth.year;
+    final int month = widget.displayedMonth.month;
+    final int daysInMonth = DateUtils.getDaysInMonth(year, month);
+    final int dayOffset = DateUtils.firstDayOffset(year, month, localizations);
+    final int weeks = ((daysInMonth + dayOffset) / DateTime.daysPerWeek).ceil();
+    final double gridHeight =
+        weeks * _monthItemRowHeight + (weeks - 1) * _monthItemSpaceBetweenRows;
+    final List<Widget> dayItems = <Widget>[];
+
+    for (int i = 0; true; i += 1) {
+      // 1-based day of month, e.g. 1-31 for January, and 1-29 for February on
+      // a leap year.
+      final int day = i - dayOffset + 1;
+      if (day > daysInMonth)
+        break;
+      if (day < 1) {
+        dayItems.add(Container());
+      } else {
+        final DateTime dayToBuild = DateTime(year, month, day);
+        final Widget dayItem = _buildDayItem(
+          context,
+          dayToBuild,
+          dayOffset,
+          daysInMonth,
+        );
+        dayItems.add(dayItem);
+      }
+    }
+
+    // Add the leading/trailing edge containers to each week in order to
+    // correctly extend the range highlight.
+    final List<Widget> paddedDayItems = <Widget>[];
+    for (int i = 0; i < weeks; i++) {
+      final int start = i * DateTime.daysPerWeek;
+      final int end = math.min(
+        start + DateTime.daysPerWeek,
+        dayItems.length,
+      );
+      final List<Widget> weekList = dayItems.sublist(start, end);
+
+      final DateTime dateAfterLeadingPadding = DateTime(year, month, start - dayOffset + 1);
+      // Only color the edge container if it is after the start date and
+      // on/before the end date.
+      final bool isLeadingInRange =
+        !(dayOffset > 0 && i == 0) &&
+        widget.selectedDateStart != null &&
+        widget.selectedDateEnd != null &&
+        dateAfterLeadingPadding.isAfter(widget.selectedDateStart!) &&
+        !dateAfterLeadingPadding.isAfter(widget.selectedDateEnd!);
+      weekList.insert(0, _buildEdgeContainer(context, isLeadingInRange));
+
+      // Only add a trailing edge container if it is for a full week and not a
+      // partial week.
+      if (end < dayItems.length || (end == dayItems.length && dayItems.length % DateTime.daysPerWeek == 0)) {
+        final DateTime dateBeforeTrailingPadding =
+        DateTime(year, month, end - dayOffset);
+        // Only color the edge container if it is on/after the start date and
+        // before the end date.
+        final bool isTrailingInRange =
+          widget.selectedDateStart != null &&
+          widget.selectedDateEnd != null &&
+          !dateBeforeTrailingPadding.isBefore(widget.selectedDateStart!) &&
+          dateBeforeTrailingPadding.isBefore(widget.selectedDateEnd!);
+        weekList.add(_buildEdgeContainer(context, isTrailingInRange));
+      }
+
+      paddedDayItems.addAll(weekList);
+    }
+
+    final double maxWidth = MediaQuery.of(context).orientation == Orientation.landscape
+      ? _maxCalendarWidthLandscape
+      : _maxCalendarWidthPortrait;
+    return Column(
+      children: <Widget>[
+        Container(
+          constraints: BoxConstraints(maxWidth: maxWidth),
+          height: _monthItemHeaderHeight,
+          padding: const EdgeInsets.symmetric(horizontal: 16),
+          alignment: AlignmentDirectional.centerStart,
+          child: ExcludeSemantics(
+            child: Text(
+              localizations.formatMonthYear(widget.displayedMonth),
+              style: textTheme.bodyText2!.apply(color: themeData.colorScheme.onSurface),
+            ),
+          ),
+        ),
+        Container(
+          constraints: BoxConstraints(
+            maxWidth: maxWidth,
+            maxHeight: gridHeight,
+          ),
+          child: GridView.custom(
+            physics: const NeverScrollableScrollPhysics(),
+            gridDelegate: _monthItemGridDelegate,
+            childrenDelegate: SliverChildListDelegate(
+              paddedDayItems,
+              addRepaintBoundaries: false,
+            ),
+          ),
+        ),
+        const SizedBox(height: _monthItemFooterHeight),
+      ],
+    );
+  }
+}
+
+/// Determines which style to use to paint the highlight.
+enum _HighlightPainterStyle {
+  /// Paints nothing.
+  none,
+
+  /// Paints a rectangle that occupies the leading half of the space.
+  highlightLeading,
+
+  /// Paints a rectangle that occupies the trailing half of the space.
+  highlightTrailing,
+
+  /// Paints a rectangle that occupies all available space.
+  highlightAll,
+}
+
+/// This custom painter will add a background highlight to its child.
+///
+/// This highlight will be drawn depending on the [style], [color], and
+/// [textDirection] supplied. It will either paint a rectangle on the
+/// left/right, a full rectangle, or nothing at all. This logic is determined by
+/// a combination of the [style] and [textDirection].
+class _HighlightPainter extends CustomPainter {
+  _HighlightPainter({
+    required this.color,
+    this.style = _HighlightPainterStyle.none,
+    this.textDirection,
+  });
+
+  final Color color;
+  final _HighlightPainterStyle style;
+  final TextDirection? textDirection;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    if (style == _HighlightPainterStyle.none) {
+      return;
+    }
+
+    final Paint paint = Paint()
+      ..color = color
+      ..style = PaintingStyle.fill;
+
+    final Rect rectLeft = Rect.fromLTWH(0, 0, size.width / 2, size.height);
+    final Rect rectRight = Rect.fromLTWH(size.width / 2, 0, size.width / 2, size.height);
+
+    switch (style) {
+      case _HighlightPainterStyle.highlightTrailing:
+        canvas.drawRect(
+          textDirection == TextDirection.ltr ? rectRight : rectLeft,
+          paint,
+        );
+        break;
+      case _HighlightPainterStyle.highlightLeading:
+        canvas.drawRect(
+          textDirection == TextDirection.ltr ? rectLeft : rectRight,
+          paint,
+        );
+        break;
+      case _HighlightPainterStyle.highlightAll:
+        canvas.drawRect(
+          Rect.fromLTWH(0, 0, size.width, size.height),
+          paint,
+        );
+        break;
+      case _HighlightPainterStyle.none:
+        break;
+    }
+  }
+
+  @override
+  bool shouldRepaint(CustomPainter oldDelegate) => false;
+}
+
+
+class _InputDateRangePickerDialog extends StatelessWidget {
+  const _InputDateRangePickerDialog({
+    Key? key,
+    required this.selectedStartDate,
+    required this.selectedEndDate,
+    required this.currentDate,
+    required this.picker,
+    required this.onConfirm,
+    required this.onCancel,
+    required this.onToggleEntryMode,
+    required this.confirmText,
+    required this.cancelText,
+    required this.helpText,
+  }) : super(key: key);
+
+  final DateTime? selectedStartDate;
+  final DateTime? selectedEndDate;
+  final DateTime? currentDate;
+  final Widget picker;
+  final VoidCallback onConfirm;
+  final VoidCallback onCancel;
+  final VoidCallback onToggleEntryMode;
+  final String? confirmText;
+  final String? cancelText;
+  final String? helpText;
+
+  String _formatDateRange(BuildContext context, DateTime? start, DateTime? end, DateTime now) {
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final String startText = _formatRangeStartDate(localizations, start, end);
+    final String endText = _formatRangeEndDate(localizations, start, end, now);
+    if (start == null || end == null) {
+      return localizations.unspecifiedDateRange;
+    }
+    if (Directionality.of(context) == TextDirection.ltr) {
+      return '$startText – $endText';
+    } else {
+      return '$endText – $startText';
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final ColorScheme colorScheme = theme.colorScheme;
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final Orientation orientation = MediaQuery.of(context).orientation;
+    final TextTheme textTheme = theme.textTheme;
+
+    final Color dateColor = colorScheme.brightness == Brightness.light
+        ? colorScheme.onPrimary
+        : colorScheme.onSurface;
+    final TextStyle? dateStyle = orientation == Orientation.landscape
+        ? textTheme.headline5?.apply(color: dateColor)
+        : textTheme.headline4?.apply(color: dateColor);
+    final String dateText = _formatDateRange(context, selectedStartDate, selectedEndDate, currentDate!);
+    final String semanticDateText = selectedStartDate != null && selectedEndDate != null
+        ? '${localizations.formatMediumDate(selectedStartDate!)} – ${localizations.formatMediumDate(selectedEndDate!)}'
+        : '';
+
+    final Widget header = _DatePickerHeader(
+      helpText: helpText ?? localizations.dateRangePickerHelpText,
+      titleText: dateText,
+      titleSemanticsLabel: semanticDateText,
+      titleStyle: dateStyle,
+      orientation: orientation,
+      isShort: orientation == Orientation.landscape,
+      icon: Icons.calendar_today,
+      iconTooltip: localizations.calendarModeButtonLabel,
+      onIconPressed: onToggleEntryMode,
+    );
+
+    final Widget actions = Container(
+      alignment: AlignmentDirectional.centerEnd,
+      constraints: const BoxConstraints(minHeight: 52.0),
+      padding: const EdgeInsets.symmetric(horizontal: 8),
+      child: OverflowBar(
+        spacing: 8,
+        children: <Widget>[
+          TextButton(
+            child: Text(cancelText ?? localizations.cancelButtonLabel),
+            onPressed: onCancel,
+          ),
+          TextButton(
+            child: Text(confirmText ?? localizations.okButtonLabel),
+            onPressed: onConfirm,
+          ),
+        ],
+      ),
+    );
+
+    switch (orientation) {
+      case Orientation.portrait:
+        return Column(
+          mainAxisSize: MainAxisSize.min,
+          crossAxisAlignment: CrossAxisAlignment.stretch,
+          children: <Widget>[
+            header,
+            Expanded(child: picker),
+            actions,
+          ],
+        );
+
+      case Orientation.landscape:
+        return Row(
+          mainAxisSize: MainAxisSize.min,
+          crossAxisAlignment: CrossAxisAlignment.stretch,
+          children: <Widget>[
+            header,
+            Flexible(
+              child: Column(
+                mainAxisSize: MainAxisSize.min,
+                crossAxisAlignment: CrossAxisAlignment.stretch,
+                children: <Widget>[
+                  Expanded(child: picker),
+                  actions,
+                ],
+              ),
+            ),
+          ],
+        );
+    }
+  }
+}
+
+/// Provides a pair of text fields that allow the user to enter the start and
+/// end dates that represent a range of dates.
+class _InputDateRangePicker extends StatefulWidget {
+  /// Creates a row with two text fields configured to accept the start and end dates
+  /// of a date range.
+  _InputDateRangePicker({
+    Key? key,
+    DateTime? initialStartDate,
+    DateTime? initialEndDate,
+    required DateTime firstDate,
+    required DateTime lastDate,
+    required this.onStartDateChanged,
+    required this.onEndDateChanged,
+    this.helpText,
+    this.errorFormatText,
+    this.errorInvalidText,
+    this.errorInvalidRangeText,
+    this.fieldStartHintText,
+    this.fieldEndHintText,
+    this.fieldStartLabelText,
+    this.fieldEndLabelText,
+    this.autofocus = false,
+    this.autovalidate = false,
+  }) : initialStartDate = initialStartDate == null ? null : DateUtils.dateOnly(initialStartDate),
+       initialEndDate = initialEndDate == null ? null : DateUtils.dateOnly(initialEndDate),
+       assert(firstDate != null),
+       firstDate = DateUtils.dateOnly(firstDate),
+       assert(lastDate != null),
+       lastDate = DateUtils.dateOnly(lastDate),
+       assert(firstDate != null),
+       assert(lastDate != null),
+       assert(autofocus != null),
+       assert(autovalidate != null),
+       super(key: key);
+
+  /// The [DateTime] that represents the start of the initial date range selection.
+  final DateTime? initialStartDate;
+
+  /// The [DateTime] that represents the end of the initial date range selection.
+  final DateTime? initialEndDate;
+
+  /// The earliest allowable [DateTime] that the user can select.
+  final DateTime firstDate;
+
+  /// The latest allowable [DateTime] that the user can select.
+  final DateTime lastDate;
+
+  /// Called when the user changes the start date of the selected range.
+  final ValueChanged<DateTime?>? onStartDateChanged;
+
+  /// Called when the user changes the end date of the selected range.
+  final ValueChanged<DateTime?>? onEndDateChanged;
+
+  /// The text that is displayed at the top of the header.
+  ///
+  /// This is used to indicate to the user what they are selecting a date for.
+  final String? helpText;
+
+  /// Error text used to indicate the text in a field is not a valid date.
+  final String? errorFormatText;
+
+  /// Error text used to indicate the date in a field is not in the valid range
+  /// of [firstDate] - [lastDate].
+  final String? errorInvalidText;
+
+  /// Error text used to indicate the dates given don't form a valid date
+  /// range (i.e. the start date is after the end date).
+  final String? errorInvalidRangeText;
+
+  /// Hint text shown when the start date field is empty.
+  final String? fieldStartHintText;
+
+  /// Hint text shown when the end date field is empty.
+  final String? fieldEndHintText;
+
+  /// Label used for the start date field.
+  final String? fieldStartLabelText;
+
+  /// Label used for the end date field.
+  final String? fieldEndLabelText;
+
+  /// {@macro flutter.widgets.editableText.autofocus}
+  final bool autofocus;
+
+  /// If true, this the date fields will validate and update their error text
+  /// immediately after every change. Otherwise, you must call
+  /// [_InputDateRangePickerState.validate] to validate.
+  final bool autovalidate;
+
+  @override
+  _InputDateRangePickerState createState() => _InputDateRangePickerState();
+}
+
+/// The current state of an [_InputDateRangePicker]. Can be used to
+/// [validate] the date field entries.
+class _InputDateRangePickerState extends State<_InputDateRangePicker> {
+  late String _startInputText;
+  late String _endInputText;
+  DateTime? _startDate;
+  DateTime? _endDate;
+  late TextEditingController _startController;
+  late TextEditingController _endController;
+  String? _startErrorText;
+  String? _endErrorText;
+  bool _autoSelected = false;
+
+  @override
+  void initState() {
+    super.initState();
+    _startDate = widget.initialStartDate;
+    _startController = TextEditingController();
+    _endDate = widget.initialEndDate;
+    _endController = TextEditingController();
+  }
+
+  @override
+  void dispose() {
+    _startController.dispose();
+    _endController.dispose();
+    super.dispose();
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    if (_startDate != null) {
+      _startInputText = localizations.formatCompactDate(_startDate!);
+      final bool selectText = widget.autofocus && !_autoSelected;
+      _updateController(_startController, _startInputText, selectText);
+      _autoSelected = selectText;
+    }
+
+    if (_endDate != null) {
+      _endInputText = localizations.formatCompactDate(_endDate!);
+      _updateController(_endController, _endInputText, false);
+    }
+  }
+
+  /// Validates that the text in the start and end fields represent a valid
+  /// date range.
+  ///
+  /// Will return true if the range is valid. If not, it will
+  /// return false and display an appropriate error message under one of the
+  /// text fields.
+  bool validate() {
+    String? startError = _validateDate(_startDate);
+    final String? endError = _validateDate(_endDate);
+    if (startError == null && endError == null) {
+      if (_startDate!.isAfter(_endDate!)) {
+        startError = widget.errorInvalidRangeText ?? MaterialLocalizations.of(context).invalidDateRangeLabel;
+      }
+    }
+    setState(() {
+      _startErrorText = startError;
+      _endErrorText = endError;
+    });
+    return startError == null && endError == null;
+  }
+
+  DateTime? _parseDate(String? text) {
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    return localizations.parseCompactDate(text);
+  }
+
+  String? _validateDate(DateTime? date) {
+    if (date == null) {
+      return widget.errorFormatText ?? MaterialLocalizations.of(context).invalidDateFormatLabel;
+    } else if (date.isBefore(widget.firstDate) || date.isAfter(widget.lastDate)) {
+      return widget.errorInvalidText ?? MaterialLocalizations.of(context).dateOutOfRangeLabel;
+    }
+    return null;
+  }
+
+  void _updateController(TextEditingController controller, String text, bool selectText) {
+    TextEditingValue textEditingValue = controller.value.copyWith(text: text);
+    if (selectText) {
+      textEditingValue = textEditingValue.copyWith(selection: TextSelection(
+        baseOffset: 0,
+        extentOffset: text.length,
+      ));
+    }
+    controller.value = textEditingValue;
+  }
+
+  void _handleStartChanged(String text) {
+    setState(() {
+      _startInputText = text;
+      _startDate = _parseDate(text);
+      widget.onStartDateChanged?.call(_startDate);
+    });
+    if (widget.autovalidate) {
+      validate();
+    }
+  }
+
+  void _handleEndChanged(String text) {
+    setState(() {
+      _endInputText = text;
+      _endDate = _parseDate(text);
+      widget.onEndDateChanged?.call(_endDate);
+    });
+    if (widget.autovalidate) {
+      validate();
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final InputDecorationTheme inputTheme = Theme.of(context).inputDecorationTheme;
+    return Row(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: <Widget>[
+        Expanded(
+          child: TextField(
+            controller: _startController,
+            decoration: InputDecoration(
+              border: inputTheme.border ?? const UnderlineInputBorder(),
+              filled: inputTheme.filled,
+              hintText: widget.fieldStartHintText ?? localizations.dateHelpText,
+              labelText: widget.fieldStartLabelText ?? localizations.dateRangeStartLabel,
+              errorText: _startErrorText,
+            ),
+            keyboardType: TextInputType.datetime,
+            onChanged: _handleStartChanged,
+            autofocus: widget.autofocus,
+          ),
+        ),
+        const SizedBox(width: 8),
+        Expanded(
+          child: TextField(
+            controller: _endController,
+            decoration: InputDecoration(
+              border: inputTheme.border ?? const UnderlineInputBorder(),
+              filled: inputTheme.filled,
+              hintText: widget.fieldEndHintText ?? localizations.dateHelpText,
+              labelText: widget.fieldEndLabelText ?? localizations.dateRangeEndLabel,
+              errorText: _endErrorText,
+            ),
+            keyboardType: TextInputType.datetime,
+            onChanged: _handleEndChanged,
+          ),
+        ),
+      ],
+    );
+  }
+}
diff --git a/lib/src/material/date_picker_deprecated.dart b/lib/src/material/date_picker_deprecated.dart
new file mode 100644
index 0000000..cdf27d7
--- /dev/null
+++ b/lib/src/material/date_picker_deprecated.dart
@@ -0,0 +1,609 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:math' as math;
+
+import 'package:flute/gestures.dart' show DragStartBehavior;
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'date.dart';
+import 'icon_button.dart';
+import 'icons.dart';
+import 'material_localizations.dart';
+import 'theme.dart';
+
+// This is the original implementation for the Material Date Picker.
+// These classes are deprecated and the whole file can be removed after
+// this has been on stable for long enough for people to migrate to the new
+// CalendarDatePicker (if needed, as showDatePicker has already been migrated
+// and it is what most apps would have used).
+
+
+// Examples can assume:
+// // @dart = 2.9
+// BuildContext context;
+
+const Duration _kMonthScrollDuration = Duration(milliseconds: 200);
+const double _kDayPickerRowHeight = 42.0;
+const int _kMaxDayPickerRowCount = 6; // A 31 day month that starts on Saturday.
+// Two extra rows: one for the day-of-week header and one for the month header.
+const double _kMaxDayPickerHeight = _kDayPickerRowHeight * (_kMaxDayPickerRowCount + 2);
+
+class _DayPickerGridDelegate extends SliverGridDelegate {
+  const _DayPickerGridDelegate();
+
+  @override
+  SliverGridLayout getLayout(SliverConstraints constraints) {
+    const int columnCount = DateTime.daysPerWeek;
+    final double tileWidth = constraints.crossAxisExtent / columnCount;
+    final double viewTileHeight = constraints.viewportMainAxisExtent / (_kMaxDayPickerRowCount + 1);
+    final double tileHeight = math.max(_kDayPickerRowHeight, viewTileHeight);
+    return SliverGridRegularTileLayout(
+      crossAxisCount: columnCount,
+      mainAxisStride: tileHeight,
+      crossAxisStride: tileWidth,
+      childMainAxisExtent: tileHeight,
+      childCrossAxisExtent: tileWidth,
+      reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
+    );
+  }
+
+  @override
+  bool shouldRelayout(_DayPickerGridDelegate oldDelegate) => false;
+}
+
+const _DayPickerGridDelegate _kDayPickerGridDelegate = _DayPickerGridDelegate();
+
+/// Displays the days of a given month and allows choosing a day.
+///
+/// The days are arranged in a rectangular grid with one column for each day of
+/// the week.
+///
+/// The day picker widget is rarely used directly. Instead, consider using
+/// [showDatePicker], which creates a date picker dialog.
+///
+/// See also:
+///
+///  * [showDatePicker], which shows a dialog that contains a material design
+///    date picker.
+///  * [showTimePicker], which shows a dialog that contains a material design
+///    time picker.
+///
+@Deprecated(
+  'Use CalendarDatePicker instead. '
+  'This feature was deprecated after v1.15.3.'
+)
+class DayPicker extends StatelessWidget {
+  /// Creates a day picker.
+  ///
+  /// Rarely used directly. Instead, typically used as part of a [MonthPicker].
+  DayPicker({
+    Key? key,
+    required this.selectedDate,
+    required this.currentDate,
+    required this.onChanged,
+    required this.firstDate,
+    required this.lastDate,
+    required this.displayedMonth,
+    this.selectableDayPredicate,
+    this.dragStartBehavior = DragStartBehavior.start,
+  }) : assert(selectedDate != null),
+       assert(currentDate != null),
+       assert(onChanged != null),
+       assert(displayedMonth != null),
+       assert(dragStartBehavior != null),
+       assert(!firstDate.isAfter(lastDate)),
+       assert(selectedDate.isAfter(firstDate) || selectedDate.isAtSameMomentAs(firstDate)),
+       super(key: key);
+
+  /// The currently selected date.
+  ///
+  /// This date is highlighted in the picker.
+  final DateTime selectedDate;
+
+  /// The current date at the time the picker is displayed.
+  final DateTime currentDate;
+
+  /// Called when the user picks a day.
+  final ValueChanged<DateTime> onChanged;
+
+  /// The earliest date the user is permitted to pick.
+  final DateTime firstDate;
+
+  /// The latest date the user is permitted to pick.
+  final DateTime lastDate;
+
+  /// The month whose days are displayed by this picker.
+  final DateTime displayedMonth;
+
+  /// Optional user supplied predicate function to customize selectable days.
+  final SelectableDayPredicate? selectableDayPredicate;
+
+  /// Determines the way that drag start behavior is handled.
+  ///
+  /// If set to [DragStartBehavior.start], the drag gesture used to scroll a
+  /// date picker wheel will begin upon the detection of a drag gesture. If set
+  /// to [DragStartBehavior.down] it will begin when a down event is first
+  /// detected.
+  ///
+  /// In general, setting this to [DragStartBehavior.start] will make drag
+  /// animation smoother and setting it to [DragStartBehavior.down] will make
+  /// drag behavior feel slightly more reactive.
+  ///
+  /// By default, the drag start behavior is [DragStartBehavior.start].
+  ///
+  /// See also:
+  ///
+  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
+  final DragStartBehavior dragStartBehavior;
+
+  /// Builds widgets showing abbreviated days of week. The first widget in the
+  /// returned list corresponds to the first day of week for the current locale.
+  ///
+  /// Examples:
+  ///
+  /// ```
+  /// ┌ Sunday is the first day of week in the US (en_US)
+  /// |
+  /// S M T W T F S  <-- the returned list contains these widgets
+  /// _ _ _ _ _ 1 2
+  /// 3 4 5 6 7 8 9
+  ///
+  /// ┌ But it's Monday in the UK (en_GB)
+  /// |
+  /// M T W T F S S  <-- the returned list contains these widgets
+  /// _ _ _ _ 1 2 3
+  /// 4 5 6 7 8 9 10
+  /// ```
+  List<Widget> _getDayHeaders(TextStyle? headerStyle, MaterialLocalizations localizations) {
+    final List<Widget> result = <Widget>[];
+    for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) {
+      final String weekday = localizations.narrowWeekdays[i];
+      result.add(ExcludeSemantics(
+        child: Center(child: Text(weekday, style: headerStyle)),
+      ));
+      if (i == (localizations.firstDayOfWeekIndex - 1) % 7)
+        break;
+    }
+    return result;
+  }
+
+  // Do not use this directly - call getDaysInMonth instead.
+  static const List<int> _daysInMonth = <int>[31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
+
+  /// Returns the number of days in a month, according to the proleptic
+  /// Gregorian calendar.
+  ///
+  /// This applies the leap year logic introduced by the Gregorian reforms of
+  /// 1582. It will not give valid results for dates prior to that time.
+  static int getDaysInMonth(int year, int month) {
+    if (month == DateTime.february) {
+      final bool isLeapYear = (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0);
+      if (isLeapYear)
+        return 29;
+      return 28;
+    }
+    return _daysInMonth[month - 1];
+  }
+
+  /// Computes the offset from the first day of week that the first day of the
+  /// [month] falls on.
+  ///
+  /// For example, September 1, 2017 falls on a Friday, which in the calendar
+  /// localized for United States English appears as:
+  ///
+  /// ```
+  /// S M T W T F S
+  /// _ _ _ _ _ 1 2
+  /// ```
+  ///
+  /// The offset for the first day of the months is the number of leading blanks
+  /// in the calendar, i.e. 5.
+  ///
+  /// The same date localized for the Russian calendar has a different offset,
+  /// because the first day of week is Monday rather than Sunday:
+  ///
+  /// ```
+  /// M T W T F S S
+  /// _ _ _ _ 1 2 3
+  /// ```
+  ///
+  /// So the offset is 4, rather than 5.
+  ///
+  /// This code consolidates the following:
+  ///
+  /// - [DateTime.weekday] provides a 1-based index into days of week, with 1
+  ///   falling on Monday.
+  /// - [MaterialLocalizations.firstDayOfWeekIndex] provides a 0-based index
+  ///   into the [MaterialLocalizations.narrowWeekdays] list.
+  /// - [MaterialLocalizations.narrowWeekdays] list provides localized names of
+  ///   days of week, always starting with Sunday and ending with Saturday.
+  int _computeFirstDayOffset(int year, int month, MaterialLocalizations localizations) {
+    // 0-based day of week, with 0 representing Monday.
+    final int weekdayFromMonday = DateTime(year, month).weekday - 1;
+    // 0-based day of week, with 0 representing Sunday.
+    final int firstDayOfWeekFromSunday = localizations.firstDayOfWeekIndex;
+    // firstDayOfWeekFromSunday recomputed to be Monday-based
+    final int firstDayOfWeekFromMonday = (firstDayOfWeekFromSunday - 1) % 7;
+    // Number of days between the first day of week appearing on the calendar,
+    // and the day corresponding to the 1-st of the month.
+    return (weekdayFromMonday - firstDayOfWeekFromMonday) % 7;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData themeData = Theme.of(context);
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final int year = displayedMonth.year;
+    final int month = displayedMonth.month;
+    final int daysInMonth = getDaysInMonth(year, month);
+    final int firstDayOffset = _computeFirstDayOffset(year, month, localizations);
+    final List<Widget> labels = <Widget>[
+      ..._getDayHeaders(themeData.textTheme.caption, localizations),
+    ];
+    for (int i = 0; true; i += 1) {
+      // 1-based day of month, e.g. 1-31 for January, and 1-29 for February on
+      // a leap year.
+      final int day = i - firstDayOffset + 1;
+      if (day > daysInMonth)
+        break;
+      if (day < 1) {
+        labels.add(Container());
+      } else {
+        final DateTime dayToBuild = DateTime(year, month, day);
+        final bool disabled = dayToBuild.isAfter(lastDate)
+            || dayToBuild.isBefore(firstDate)
+            || (selectableDayPredicate != null && !selectableDayPredicate!(dayToBuild));
+
+        BoxDecoration? decoration;
+        TextStyle? itemStyle = themeData.textTheme.bodyText2;
+
+        final bool isSelectedDay = selectedDate.year == year && selectedDate.month == month && selectedDate.day == day;
+        if (isSelectedDay) {
+          // The selected day gets a circle background highlight, and a contrasting text color.
+          itemStyle = themeData.accentTextTheme.bodyText1;
+          decoration = BoxDecoration(
+            color: themeData.accentColor,
+            shape: BoxShape.circle,
+          );
+        } else if (disabled) {
+          itemStyle = themeData.textTheme.bodyText2!.copyWith(color: themeData.disabledColor);
+        } else if (currentDate.year == year && currentDate.month == month && currentDate.day == day) {
+          // The current day gets a different text color.
+          itemStyle = themeData.textTheme.bodyText1!.copyWith(color: themeData.accentColor);
+        }
+
+        Widget dayWidget = Container(
+          decoration: decoration,
+          child: Center(
+            child: Semantics(
+              // We want the day of month to be spoken first irrespective of the
+              // locale-specific preferences or TextDirection. This is because
+              // an accessibility user is more likely to be interested in the
+              // day of month before the rest of the date, as they are looking
+              // for the day of month. To do that we prepend day of month to the
+              // formatted full date.
+              label: '${localizations.formatDecimal(day)}, ${localizations.formatFullDate(dayToBuild)}',
+              selected: isSelectedDay,
+              sortKey: OrdinalSortKey(day.toDouble()),
+              child: ExcludeSemantics(
+                child: Text(localizations.formatDecimal(day), style: itemStyle),
+              ),
+            ),
+          ),
+        );
+
+        if (!disabled) {
+          dayWidget = GestureDetector(
+            behavior: HitTestBehavior.opaque,
+            onTap: () {
+              onChanged(dayToBuild);
+            },
+            child: dayWidget,
+            dragStartBehavior: dragStartBehavior,
+          );
+        }
+
+        labels.add(dayWidget);
+      }
+    }
+
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 8.0),
+      child: Column(
+        children: <Widget>[
+          Container(
+            height: _kDayPickerRowHeight,
+            child: Center(
+              child: ExcludeSemantics(
+                child: Text(
+                  localizations.formatMonthYear(displayedMonth),
+                  style: themeData.textTheme.subtitle1,
+                ),
+              ),
+            ),
+          ),
+          Flexible(
+            child: GridView.custom(
+              gridDelegate: _kDayPickerGridDelegate,
+              childrenDelegate: SliverChildListDelegate(labels, addRepaintBoundaries: false),
+              padding: EdgeInsets.zero,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+/// A scrollable list of months to allow picking a month.
+///
+/// Shows the days of each month in a rectangular grid with one column for each
+/// day of the week.
+///
+/// The month picker widget is rarely used directly. Instead, consider using
+/// [showDatePicker], which creates a date picker dialog.
+///
+/// See also:
+///
+///  * [showDatePicker], which shows a dialog that contains a material design
+///    date picker.
+///  * [showTimePicker], which shows a dialog that contains a material design
+///    time picker.
+///
+@Deprecated(
+  'Use CalendarDatePicker instead. '
+  'This feature was deprecated after v1.15.3.'
+)
+class MonthPicker extends StatefulWidget {
+  /// Creates a month picker.
+  ///
+  /// Rarely used directly. Instead, typically used as part of the dialog shown
+  /// by [showDatePicker].
+  MonthPicker({
+    Key? key,
+    required this.selectedDate,
+    required this.onChanged,
+    required this.firstDate,
+    required this.lastDate,
+    this.selectableDayPredicate,
+    this.dragStartBehavior = DragStartBehavior.start,
+  }) : assert(selectedDate != null),
+       assert(onChanged != null),
+       assert(!firstDate.isAfter(lastDate)),
+       assert(selectedDate.isAfter(firstDate) || selectedDate.isAtSameMomentAs(firstDate)),
+       super(key: key);
+
+  /// The currently selected date.
+  ///
+  /// This date is highlighted in the picker.
+  final DateTime selectedDate;
+
+  /// Called when the user picks a month.
+  final ValueChanged<DateTime> onChanged;
+
+  /// The earliest date the user is permitted to pick.
+  final DateTime firstDate;
+
+  /// The latest date the user is permitted to pick.
+  final DateTime lastDate;
+
+  /// Optional user supplied predicate function to customize selectable days.
+  final SelectableDayPredicate? selectableDayPredicate;
+
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  @override
+  _MonthPickerState createState() => _MonthPickerState();
+}
+
+class _MonthPickerState extends State<MonthPicker> with SingleTickerProviderStateMixin {
+  static final Animatable<double> _chevronOpacityTween = Tween<double>(begin: 1.0, end: 0.0)
+    .chain(CurveTween(curve: Curves.easeInOut));
+
+  @override
+  void initState() {
+    super.initState();
+    // Initially display the pre-selected date.
+    final int monthPage = _monthDelta(widget.firstDate, widget.selectedDate);
+    _dayPickerController = PageController(initialPage: monthPage);
+    _handleMonthPageChanged(monthPage);
+    _updateCurrentDate();
+
+    // Setup the fade animation for chevrons
+    _chevronOpacityController = AnimationController(
+      duration: const Duration(milliseconds: 250), vsync: this,
+    );
+    _chevronOpacityAnimation = _chevronOpacityController.drive(_chevronOpacityTween);
+  }
+
+  @override
+  void didUpdateWidget(MonthPicker oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.selectedDate != oldWidget.selectedDate) {
+      final int monthPage = _monthDelta(widget.firstDate, widget.selectedDate);
+      _dayPickerController = PageController(initialPage: monthPage);
+      _handleMonthPageChanged(monthPage);
+    }
+  }
+
+  MaterialLocalizations? localizations;
+  TextDirection? textDirection;
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    localizations = MaterialLocalizations.of(context);
+    textDirection = Directionality.of(context);
+  }
+
+  late DateTime _todayDate;
+  late DateTime _currentDisplayedMonthDate;
+  Timer? _timer;
+  late PageController _dayPickerController;
+  late AnimationController _chevronOpacityController;
+  late Animation<double> _chevronOpacityAnimation;
+
+  void _updateCurrentDate() {
+    _todayDate = DateTime.now();
+    final DateTime tomorrow = DateTime(_todayDate.year, _todayDate.month, _todayDate.day + 1);
+    Duration timeUntilTomorrow = tomorrow.difference(_todayDate);
+    timeUntilTomorrow += const Duration(seconds: 1); // so we don't miss it by rounding
+    _timer?.cancel();
+    _timer = Timer(timeUntilTomorrow, () {
+      setState(() {
+        _updateCurrentDate();
+      });
+    });
+  }
+
+  static int _monthDelta(DateTime startDate, DateTime endDate) {
+    return (endDate.year - startDate.year) * 12 + endDate.month - startDate.month;
+  }
+
+  /// Add months to a month truncated date.
+  DateTime _addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) {
+    return DateTime(monthDate.year + monthsToAdd ~/ 12, monthDate.month + monthsToAdd % 12);
+  }
+
+  Widget _buildItems(BuildContext context, int index) {
+    final DateTime month = _addMonthsToMonthDate(widget.firstDate, index);
+    return DayPicker(
+      key: ValueKey<DateTime>(month),
+      selectedDate: widget.selectedDate,
+      currentDate: _todayDate,
+      onChanged: widget.onChanged,
+      firstDate: widget.firstDate,
+      lastDate: widget.lastDate,
+      displayedMonth: month,
+      selectableDayPredicate: widget.selectableDayPredicate,
+      dragStartBehavior: widget.dragStartBehavior,
+    );
+  }
+
+  void _handleNextMonth() {
+    if (!_isDisplayingLastMonth) {
+      SemanticsService.announce(localizations!.formatMonthYear(_nextMonthDate), textDirection!);
+      _dayPickerController.nextPage(duration: _kMonthScrollDuration, curve: Curves.ease);
+    }
+  }
+
+  void _handlePreviousMonth() {
+    if (!_isDisplayingFirstMonth) {
+      SemanticsService.announce(localizations!.formatMonthYear(_previousMonthDate), textDirection!);
+      _dayPickerController.previousPage(duration: _kMonthScrollDuration, curve: Curves.ease);
+    }
+  }
+
+  /// True if the earliest allowable month is displayed.
+  bool get _isDisplayingFirstMonth {
+    return !_currentDisplayedMonthDate.isAfter(
+        DateTime(widget.firstDate.year, widget.firstDate.month));
+  }
+
+  /// True if the latest allowable month is displayed.
+  bool get _isDisplayingLastMonth {
+    return !_currentDisplayedMonthDate.isBefore(
+        DateTime(widget.lastDate.year, widget.lastDate.month));
+  }
+
+  late DateTime _previousMonthDate;
+  late DateTime _nextMonthDate;
+
+  void _handleMonthPageChanged(int monthPage) {
+    setState(() {
+      _previousMonthDate = _addMonthsToMonthDate(widget.firstDate, monthPage - 1);
+      _currentDisplayedMonthDate = _addMonthsToMonthDate(widget.firstDate, monthPage);
+      _nextMonthDate = _addMonthsToMonthDate(widget.firstDate, monthPage + 1);
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      // The month picker just adds month navigation to the day picker, so make
+      // it the same height as the DayPicker
+      height: _kMaxDayPickerHeight,
+      child: Stack(
+        children: <Widget>[
+          Semantics(
+            sortKey: _MonthPickerSortKey.calendar,
+            child: NotificationListener<ScrollStartNotification>(
+              onNotification: (_) {
+                _chevronOpacityController.forward();
+                return false;
+              },
+              child: NotificationListener<ScrollEndNotification>(
+                onNotification: (_) {
+                  _chevronOpacityController.reverse();
+                  return false;
+                },
+                child: PageView.builder(
+                  dragStartBehavior: widget.dragStartBehavior,
+                  key: ValueKey<DateTime>(widget.selectedDate),
+                  controller: _dayPickerController,
+                  scrollDirection: Axis.horizontal,
+                  itemCount: _monthDelta(widget.firstDate, widget.lastDate) + 1,
+                  itemBuilder: _buildItems,
+                  onPageChanged: _handleMonthPageChanged,
+                ),
+              ),
+            ),
+          ),
+          PositionedDirectional(
+            top: 0.0,
+            start: 8.0,
+            child: Semantics(
+              sortKey: _MonthPickerSortKey.previousMonth,
+              child: FadeTransition(
+                opacity: _chevronOpacityAnimation,
+                child: IconButton(
+                  icon: const Icon(Icons.chevron_left),
+                  tooltip: _isDisplayingFirstMonth ? null : '${localizations!.previousMonthTooltip} ${localizations!.formatMonthYear(_previousMonthDate)}',
+                  onPressed: _isDisplayingFirstMonth ? null : _handlePreviousMonth,
+                ),
+              ),
+            ),
+          ),
+          PositionedDirectional(
+            top: 0.0,
+            end: 8.0,
+            child: Semantics(
+              sortKey: _MonthPickerSortKey.nextMonth,
+              child: FadeTransition(
+                opacity: _chevronOpacityAnimation,
+                child: IconButton(
+                  icon: const Icon(Icons.chevron_right),
+                  tooltip: _isDisplayingLastMonth ? null : '${localizations!.nextMonthTooltip} ${localizations!.formatMonthYear(_nextMonthDate)}',
+                  onPressed: _isDisplayingLastMonth ? null : _handleNextMonth,
+                ),
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  @override
+  void dispose() {
+    _timer?.cancel();
+    _chevronOpacityController.dispose();
+    _dayPickerController.dispose();
+    super.dispose();
+  }
+}
+
+// Defines semantic traversal order of the top-level widgets inside the month
+// picker.
+class _MonthPickerSortKey extends OrdinalSortKey {
+  const _MonthPickerSortKey(double order) : super(order);
+
+  static const _MonthPickerSortKey previousMonth = _MonthPickerSortKey(1.0);
+  static const _MonthPickerSortKey nextMonth = _MonthPickerSortKey(2.0);
+  static const _MonthPickerSortKey calendar = _MonthPickerSortKey(3.0);
+}
diff --git a/lib/src/material/debug.dart b/lib/src/material/debug.dart
new file mode 100644
index 0000000..3cdb944
--- /dev/null
+++ b/lib/src/material/debug.dart
@@ -0,0 +1,156 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'material.dart';
+import 'material_localizations.dart';
+import 'scaffold.dart' show Scaffold, ScaffoldMessenger;
+
+/// Asserts that the given context has a [Material] ancestor.
+///
+/// Used by many material design widgets to make sure that they are
+/// only used in contexts where they can print ink onto some material.
+///
+/// To call this function, use the following pattern, typically in the
+/// relevant Widget's build method:
+///
+/// ```dart
+/// assert(debugCheckHasMaterial(context));
+/// ```
+///
+/// Does nothing if asserts are disabled. Always returns true.
+bool debugCheckHasMaterial(BuildContext context) {
+  assert(() {
+    if (context.widget is! Material && context.findAncestorWidgetOfExactType<Material>() == null) {
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('No Material widget found.'),
+        ErrorDescription(
+          '${context.widget.runtimeType} widgets require a Material '
+          'widget ancestor.\n'
+          'In material design, most widgets are conceptually "printed" on '
+          "a sheet of material. In Flutter's material library, that "
+          'material is represented by the Material widget. It is the '
+          'Material widget that renders ink splashes, for instance. '
+          'Because of this, many material library widgets require that '
+          'there be a Material widget in the tree above them.'
+        ),
+        ErrorHint(
+          'To introduce a Material widget, you can either directly '
+          'include one, or use a widget that contains Material itself, '
+          'such as a Card, Dialog, Drawer, or Scaffold.',
+        ),
+        ...context.describeMissingAncestor(expectedAncestorType: Material)
+      ]
+      );
+    }
+    return true;
+  }());
+  return true;
+}
+
+
+/// Asserts that the given context has a [Localizations] ancestor that contains
+/// a [MaterialLocalizations] delegate.
+///
+/// Used by many material design widgets to make sure that they are
+/// only used in contexts where they have access to localizations.
+///
+/// To call this function, use the following pattern, typically in the
+/// relevant Widget's build method:
+///
+/// ```dart
+/// assert(debugCheckHasMaterialLocalizations(context));
+/// ```
+///
+/// Does nothing if asserts are disabled. Always returns true.
+bool debugCheckHasMaterialLocalizations(BuildContext context) {
+  assert(() {
+    if (Localizations.of<MaterialLocalizations>(context, MaterialLocalizations) == null) {
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('No MaterialLocalizations found.'),
+        ErrorDescription(
+          '${context.widget.runtimeType} widgets require MaterialLocalizations '
+          'to be provided by a Localizations widget ancestor.'
+        ),
+        ErrorDescription(
+          'The material library uses Localizations to generate messages, '
+          'labels, and abbreviations.'
+        ),
+        ErrorHint(
+          'To introduce a MaterialLocalizations, either use a '
+          'MaterialApp at the root of your application to include them '
+          'automatically, or add a Localization widget with a '
+          'MaterialLocalizations delegate.'
+        ),
+        ...context.describeMissingAncestor(expectedAncestorType: MaterialLocalizations)
+      ]);
+    }
+    return true;
+  }());
+  return true;
+}
+
+/// Asserts that the given context has a [Scaffold] ancestor.
+///
+/// Used by various widgets to make sure that they are only used in an
+/// appropriate context.
+///
+/// To invoke this function, use the following pattern, typically in the
+/// relevant Widget's build method:
+///
+/// ```dart
+/// assert(debugCheckHasScaffold(context));
+/// ```
+///
+/// Does nothing if asserts are disabled. Always returns true.
+bool debugCheckHasScaffold(BuildContext context) {
+  assert(() {
+    if (context.widget is! Scaffold && context.findAncestorWidgetOfExactType<Scaffold>() == null) {
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('No Scaffold widget found.'),
+        ErrorDescription('${context.widget.runtimeType} widgets require a Scaffold widget ancestor.'),
+        ...context.describeMissingAncestor(expectedAncestorType: Scaffold),
+        ErrorHint(
+          'Typically, the Scaffold widget is introduced by the MaterialApp or '
+          'WidgetsApp widget at the top of your application widget tree.'
+        )
+      ]);
+    }
+    return true;
+  }());
+  return true;
+}
+
+/// Asserts that the given context has a [ScaffoldMessenger] ancestor.
+///
+/// Used by various widgets to make sure that they are only used in an
+/// appropriate context.
+///
+/// To invoke this function, use the following pattern, typically in the
+/// relevant Widget's build method:
+///
+/// ```dart
+/// assert(debugCheckHasScaffoldMessenger(context));
+/// ```
+///
+/// Does nothing if asserts are disabled. Always returns true.
+bool debugCheckHasScaffoldMessenger(BuildContext context) {
+  assert(() {
+    if (context.findAncestorWidgetOfExactType<ScaffoldMessenger>() == null) {
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('No ScaffoldMessenger widget found.'),
+        ErrorDescription('${context.widget.runtimeType} widgets require a ScaffoldMessenger widget ancestor.'),
+        ...context.describeMissingAncestor(expectedAncestorType: ScaffoldMessenger),
+        ErrorHint(
+          'Typically, the ScaffoldMessenger widget is introduced by the MaterialApp '
+          'at the top of your application widget tree.'
+        )
+      ]);
+    }
+    return true;
+  }());
+  return true;
+}
diff --git a/lib/src/material/dialog.dart b/lib/src/material/dialog.dart
new file mode 100644
index 0000000..dac9027
--- /dev/null
+++ b/lib/src/material/dialog.dart
@@ -0,0 +1,1011 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' hide TextStyle;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'button_bar.dart';
+import 'colors.dart';
+import 'debug.dart';
+import 'dialog_theme.dart';
+import 'ink_well.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+// Examples can assume:
+// enum Department { treasury, state }
+// late BuildContext context;
+
+const EdgeInsets _defaultInsetPadding = EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0);
+
+/// A material design dialog.
+///
+/// This dialog widget does not have any opinion about the contents of the
+/// dialog. Rather than using this widget directly, consider using [AlertDialog]
+/// or [SimpleDialog], which implement specific kinds of material design
+/// dialogs.
+///
+/// See also:
+///
+///  * [AlertDialog], for dialogs that have a message and some buttons.
+///  * [SimpleDialog], for dialogs that offer a variety of options.
+///  * [showDialog], which actually displays the dialog and returns its result.
+///  * <https://material.io/design/components/dialogs.html>
+class Dialog extends StatelessWidget {
+  /// Creates a dialog.
+  ///
+  /// Typically used in conjunction with [showDialog].
+  const Dialog({
+    Key? key,
+    this.backgroundColor,
+    this.elevation,
+    this.insetAnimationDuration = const Duration(milliseconds: 100),
+    this.insetAnimationCurve = Curves.decelerate,
+    this.insetPadding = _defaultInsetPadding,
+    this.clipBehavior = Clip.none,
+    this.shape,
+    this.child,
+  }) : assert(clipBehavior != null),
+       super(key: key);
+
+  /// {@template flutter.material.dialog.backgroundColor}
+  /// The background color of the surface of this [Dialog].
+  ///
+  /// This sets the [Material.color] on this [Dialog]'s [Material].
+  ///
+  /// If `null`, [ThemeData.dialogBackgroundColor] is used.
+  /// {@endtemplate}
+  final Color? backgroundColor;
+
+  /// {@template flutter.material.dialog.elevation}
+  /// The z-coordinate of this [Dialog].
+  ///
+  /// If null then [DialogTheme.elevation] is used, and if that's null then the
+  /// dialog's elevation is 24.0.
+  /// {@endtemplate}
+  /// {@macro flutter.material.material.elevation}
+  final double? elevation;
+
+  /// {@template flutter.material.dialog.insetAnimationDuration}
+  /// The duration of the animation to show when the system keyboard intrudes
+  /// into the space that the dialog is placed in.
+  ///
+  /// Defaults to 100 milliseconds.
+  /// {@endtemplate}
+  final Duration insetAnimationDuration;
+
+  /// {@template flutter.material.dialog.insetAnimationCurve}
+  /// The curve to use for the animation shown when the system keyboard intrudes
+  /// into the space that the dialog is placed in.
+  ///
+  /// Defaults to [Curves.decelerate].
+  /// {@endtemplate}
+  final Curve insetAnimationCurve;
+
+  /// {@template flutter.material.dialog.insetPadding}
+  /// The amount of padding added to [MediaQueryData.viewInsets] on the outside
+  /// of the dialog. This defines the minimum space between the screen's edges
+  /// and the dialog.
+  ///
+  /// Defaults to `EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0)`.
+  /// {@endtemplate}
+  final EdgeInsets? insetPadding;
+
+  /// {@template flutter.material.dialog.clipBehavior}
+  /// Controls how the contents of the dialog are clipped (or not) to the given
+  /// [shape].
+  ///
+  /// See the enum [Clip] for details of all possible options and their common
+  /// use cases.
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  /// {@endtemplate}
+  final Clip clipBehavior;
+
+  /// {@template flutter.material.dialog.shape}
+  /// The shape of this dialog's border.
+  ///
+  /// Defines the dialog's [Material.shape].
+  ///
+  /// The default shape is a [RoundedRectangleBorder] with a radius of 4.0
+  /// {@endtemplate}
+  final ShapeBorder? shape;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  static const RoundedRectangleBorder _defaultDialogShape =
+    RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)));
+  static const double _defaultElevation = 24.0;
+
+  @override
+  Widget build(BuildContext context) {
+    final DialogTheme dialogTheme = DialogTheme.of(context);
+    final EdgeInsets effectivePadding = MediaQuery.of(context).viewInsets + (insetPadding ?? const EdgeInsets.all(0.0));
+    return AnimatedPadding(
+      padding: effectivePadding,
+      duration: insetAnimationDuration,
+      curve: insetAnimationCurve,
+      child: MediaQuery.removeViewInsets(
+        removeLeft: true,
+        removeTop: true,
+        removeRight: true,
+        removeBottom: true,
+        context: context,
+        child: Center(
+          child: ConstrainedBox(
+            constraints: const BoxConstraints(minWidth: 280.0),
+            child: Material(
+              color: backgroundColor ?? dialogTheme.backgroundColor ?? Theme.of(context).dialogBackgroundColor,
+              elevation: elevation ?? dialogTheme.elevation ?? _defaultElevation,
+              shape: shape ?? dialogTheme.shape ?? _defaultDialogShape,
+              type: MaterialType.card,
+              clipBehavior: clipBehavior,
+              child: child,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+/// A material design alert dialog.
+///
+/// An alert dialog informs the user about situations that require
+/// acknowledgement. An alert dialog has an optional title and an optional list
+/// of actions. The title is displayed above the content and the actions are
+/// displayed below the content.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=75CsnyRXf5I}
+///
+/// If the content is too large to fit on the screen vertically, the dialog will
+/// display the title and the actions and let the content overflow, which is
+/// rarely desired. Consider using a scrolling widget for [content], such as
+/// [SingleChildScrollView], to avoid overflow. (However, be aware that since
+/// [AlertDialog] tries to size itself using the intrinsic dimensions of its
+/// children, widgets such as [ListView], [GridView], and [CustomScrollView],
+/// which use lazy viewports, will not work. If this is a problem, consider
+/// using [Dialog] directly.)
+///
+/// For dialogs that offer the user a choice between several options, consider
+/// using a [SimpleDialog].
+///
+/// Typically passed as the child widget to [showDialog], which displays the
+/// dialog.
+///
+/// {@animation 350 622 https://flutter.github.io/assets-for-api-docs/assets/material/alert_dialog.mp4}
+///
+/// {@tool snippet}
+///
+/// This snippet shows a method in a [State] which, when called, displays a dialog box
+/// and returns a [Future] that completes when the dialog is dismissed.
+///
+/// ```dart
+/// Future<void> _showMyDialog() async {
+///   return showDialog<void>(
+///     context: context,
+///     barrierDismissible: false, // user must tap button!
+///     builder: (BuildContext context) {
+///       return AlertDialog(
+///         title: Text('AlertDialog Title'),
+///         content: SingleChildScrollView(
+///           child: ListBody(
+///             children: <Widget>[
+///               Text('This is a demo alert dialog.'),
+///               Text('Would you like to approve of this message?'),
+///             ],
+///           ),
+///         ),
+///         actions: <Widget>[
+///           TextButton(
+///             child: Text('Approve'),
+///             onPressed: () {
+///               Navigator.of(context).pop();
+///             },
+///           ),
+///         ],
+///       );
+///     },
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [SimpleDialog], which handles the scrolling of the contents but has no [actions].
+///  * [Dialog], on which [AlertDialog] and [SimpleDialog] are based.
+///  * [CupertinoAlertDialog], an iOS-styled alert dialog.
+///  * [showDialog], which actually displays the dialog and returns its result.
+///  * <https://material.io/design/components/dialogs.html#alert-dialog>
+class AlertDialog extends StatelessWidget {
+  /// Creates an alert dialog.
+  ///
+  /// Typically used in conjunction with [showDialog].
+  ///
+  /// The [contentPadding] must not be null. The [titlePadding] defaults to
+  /// null, which implies a default that depends on the values of the other
+  /// properties. See the documentation of [titlePadding] for details.
+  const AlertDialog({
+    Key? key,
+    this.title,
+    this.titlePadding,
+    this.titleTextStyle,
+    this.content,
+    this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),
+    this.contentTextStyle,
+    this.actions,
+    this.actionsPadding = EdgeInsets.zero,
+    this.actionsOverflowDirection,
+    this.actionsOverflowButtonSpacing,
+    this.buttonPadding,
+    this.backgroundColor,
+    this.elevation,
+    this.semanticLabel,
+    this.insetPadding = _defaultInsetPadding,
+    this.clipBehavior = Clip.none,
+    this.shape,
+    this.scrollable = false,
+  }) : assert(contentPadding != null),
+       assert(clipBehavior != null),
+       super(key: key);
+
+  /// The (optional) title of the dialog is displayed in a large font at the top
+  /// of the dialog.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? title;
+
+  /// Padding around the title.
+  ///
+  /// If there is no title, no padding will be provided. Otherwise, this padding
+  /// is used.
+  ///
+  /// This property defaults to providing 24 pixels on the top, left, and right
+  /// of the title. If the [content] is not null, then no bottom padding is
+  /// provided (but see [contentPadding]). If it _is_ null, then an extra 20
+  /// pixels of bottom padding is added to separate the [title] from the
+  /// [actions].
+  final EdgeInsetsGeometry? titlePadding;
+
+  /// Style for the text in the [title] of this [AlertDialog].
+  ///
+  /// If null, [DialogTheme.titleTextStyle] is used. If that's null, defaults to
+  /// [TextTheme.headline6] of [ThemeData.textTheme].
+  final TextStyle? titleTextStyle;
+
+  /// The (optional) content of the dialog is displayed in the center of the
+  /// dialog in a lighter font.
+  ///
+  /// Typically this is a [SingleChildScrollView] that contains the dialog's
+  /// message. As noted in the [AlertDialog] documentation, it's important
+  /// to use a [SingleChildScrollView] if there's any risk that the content
+  /// will not fit.
+  final Widget? content;
+
+  /// Padding around the content.
+  ///
+  /// If there is no content, no padding will be provided. Otherwise, padding of
+  /// 20 pixels is provided above the content to separate the content from the
+  /// title, and padding of 24 pixels is provided on the left, right, and bottom
+  /// to separate the content from the other edges of the dialog.
+  final EdgeInsetsGeometry contentPadding;
+
+  /// Style for the text in the [content] of this [AlertDialog].
+  ///
+  /// If null, [DialogTheme.contentTextStyle] is used. If that's null, defaults
+  /// to [TextTheme.subtitle1] of [ThemeData.textTheme].
+  final TextStyle? contentTextStyle;
+
+  /// The (optional) set of actions that are displayed at the bottom of the
+  /// dialog.
+  ///
+  /// Typically this is a list of [TextButton] widgets. It is recommended to
+  /// set the [Text.textAlign] to [TextAlign.end] for the [Text] within the
+  /// [TextButton], so that buttons whose labels wrap to an extra line align
+  /// with the overall [ButtonBar]'s alignment within the dialog.
+  ///
+  /// These widgets will be wrapped in a [ButtonBar], which introduces 8 pixels
+  /// of padding on each side.
+  ///
+  /// If the [title] is not null but the [content] _is_ null, then an extra 20
+  /// pixels of padding is added above the [ButtonBar] to separate the [title]
+  /// from the [actions].
+  final List<Widget>? actions;
+
+  /// Padding around the set of [actions] at the bottom of the dialog.
+  ///
+  /// Typically used to provide padding to the button bar between the button bar
+  /// and the edges of the dialog.
+  ///
+  /// If are no [actions], then no padding will be included. The padding around
+  /// the button bar defaults to zero. It is also important to note that
+  /// [buttonPadding] may contribute to the padding on the edges of [actions] as
+  /// well.
+  ///
+  /// {@tool snippet}
+  /// This is an example of a set of actions aligned with the content widget.
+  /// ```dart
+  /// AlertDialog(
+  ///   title: Text('Title'),
+  ///   content: Container(width: 200, height: 200, color: Colors.green),
+  ///   actions: <Widget>[
+  ///     ElevatedButton(onPressed: () {}, child: Text('Button 1')),
+  ///     ElevatedButton(onPressed: () {}, child: Text('Button 2')),
+  ///   ],
+  ///   actionsPadding: EdgeInsets.symmetric(horizontal: 8.0),
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  /// * [ButtonBar], which [actions] configures to lay itself out.
+  final EdgeInsetsGeometry actionsPadding;
+
+  /// The vertical direction of [actions] if the children overflow
+  /// horizontally.
+  ///
+  /// If the dialog's [actions] do not fit into a single row, then they
+  /// are arranged in a column. The first action is at the top of the
+  /// column if this property is set to [VerticalDirection.down], since it
+  /// "starts" at the top and "ends" at the bottom. On the other hand,
+  /// the first action will be at the bottom of the column if this
+  /// property is set to [VerticalDirection.up], since it "starts" at the
+  /// bottom and "ends" at the top.
+  ///
+  /// If null then it will use the surrounding
+  /// [ButtonBarThemeData.overflowDirection]. If that is null, it will
+  /// default to [VerticalDirection.down].
+  ///
+  /// See also:
+  ///
+  /// * [ButtonBar], which [actions] configures to lay itself out.
+  final VerticalDirection? actionsOverflowDirection;
+
+  /// The spacing between [actions] when the button bar overflows.
+  ///
+  /// If the widgets in [actions] do not fit into a single row, they are
+  /// arranged into a column. This parameter provides additional
+  /// vertical space in between buttons when it does overflow.
+  ///
+  /// Note that the button spacing may appear to be more than
+  /// the value provided. This is because most buttons adhere to the
+  /// [MaterialTapTargetSize] of 48px. So, even though a button
+  /// might visually be 36px in height, it might still take up to
+  /// 48px vertically.
+  ///
+  /// If null then no spacing will be added in between buttons in
+  /// an overflow state.
+  final double? actionsOverflowButtonSpacing;
+
+  /// The padding that surrounds each button in [actions].
+  ///
+  /// This is different from [actionsPadding], which defines the padding
+  /// between the entire button bar and the edges of the dialog.
+  ///
+  /// If this property is null, then it will use the surrounding
+  /// [ButtonBarThemeData.buttonPadding]. If that is null, it will default to
+  /// 8.0 logical pixels on the left and right.
+  ///
+  /// See also:
+  ///
+  /// * [ButtonBar], which [actions] configures to lay itself out.
+  final EdgeInsetsGeometry? buttonPadding;
+
+  /// {@macro flutter.material.dialog.backgroundColor}
+  final Color? backgroundColor;
+
+  /// {@macro flutter.material.dialog.elevation}
+  /// {@macro flutter.material.material.elevation}
+  final double? elevation;
+
+  /// The semantic label of the dialog used by accessibility frameworks to
+  /// announce screen transitions when the dialog is opened and closed.
+  ///
+  /// In iOS, if this label is not provided, a semantic label will be inferred
+  /// from the [title] if it is not null.
+  ///
+  /// In Android, if this label is not provided, the dialog will use the
+  /// [MaterialLocalizations.alertDialogLabel] as its label.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsConfiguration.namesRoute], for a description of how this
+  ///    value is used.
+  final String? semanticLabel;
+
+  /// {@macro flutter.material.dialog.insetPadding}
+  final EdgeInsets insetPadding;
+
+  /// {@macro flutter.material.dialog.clipBehavior}
+  final Clip clipBehavior;
+
+  /// {@macro flutter.material.dialog.shape}
+  final ShapeBorder? shape;
+
+  /// Determines whether the [title] and [content] widgets are wrapped in a
+  /// scrollable.
+  ///
+  /// This configuration is used when the [title] and [content] are expected
+  /// to overflow. Both [title] and [content] are wrapped in a scroll view,
+  /// allowing all overflowed content to be visible while still showing the
+  /// button bar.
+  @Deprecated(
+    'Set scrollable to `true`. This parameter will be removed and '
+    'was introduced to migrate AlertDialog to be scrollable by '
+    'default. For more information, see '
+    'https://flutter.dev/docs/release/breaking-changes/scrollable_alert_dialog. '
+    'This feature was deprecated after v1.13.2.'
+  )
+  final bool scrollable;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterialLocalizations(context));
+    final ThemeData theme = Theme.of(context);
+    final DialogTheme dialogTheme = DialogTheme.of(context);
+
+    String? label = semanticLabel;
+    switch (theme.platform) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        break;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        label ??= MaterialLocalizations.of(context).alertDialogLabel;
+    }
+
+    // The paddingScaleFactor is used to adjust the padding of Dialog's
+    // children.
+    final double paddingScaleFactor = _paddingScaleFactor(MediaQuery.of(context).textScaleFactor);
+    final TextDirection? textDirection = Directionality.maybeOf(context);
+
+    Widget? titleWidget;
+    Widget? contentWidget;
+    Widget? actionsWidget;
+    if (title != null) {
+      final EdgeInsets defaultTitlePadding = EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0);
+      final EdgeInsets effectiveTitlePadding = titlePadding?.resolve(textDirection) ?? defaultTitlePadding;
+      titleWidget = Padding(
+        padding: EdgeInsets.only(
+          left: effectiveTitlePadding.left * paddingScaleFactor,
+          right: effectiveTitlePadding.right * paddingScaleFactor,
+          top: effectiveTitlePadding.top * paddingScaleFactor,
+          bottom: effectiveTitlePadding.bottom,
+        ),
+        child: DefaultTextStyle(
+          style: titleTextStyle ?? dialogTheme.titleTextStyle ?? theme.textTheme.headline6!,
+          child: Semantics(
+            child: title,
+            namesRoute: label == null,
+            container: true,
+          ),
+        ),
+      );
+    }
+
+    if (content != null) {
+      final EdgeInsets effectiveContentPadding = contentPadding.resolve(textDirection);
+      contentWidget = Padding(
+        padding: EdgeInsets.only(
+          left: effectiveContentPadding.left * paddingScaleFactor,
+          right: effectiveContentPadding.right * paddingScaleFactor,
+          top: title == null ? effectiveContentPadding.top * paddingScaleFactor : effectiveContentPadding.top,
+          bottom: effectiveContentPadding.bottom,
+        ),
+        child: DefaultTextStyle(
+          style: contentTextStyle ?? dialogTheme.contentTextStyle ?? theme.textTheme.subtitle1!,
+          child: content!,
+        ),
+      );
+    }
+
+
+    if (actions != null) {
+      actionsWidget = Padding(
+        padding: actionsPadding,
+        child: ButtonBar(
+          buttonPadding: buttonPadding,
+          overflowDirection: actionsOverflowDirection,
+          overflowButtonSpacing: actionsOverflowButtonSpacing,
+          children: actions!,
+        ),
+      );
+    }
+
+    List<Widget> columnChildren;
+    if (scrollable) {
+      columnChildren = <Widget>[
+        if (title != null || content != null)
+          Flexible(
+            child: SingleChildScrollView(
+              child: Column(
+                mainAxisSize: MainAxisSize.min,
+                crossAxisAlignment: CrossAxisAlignment.stretch,
+                children: <Widget>[
+                  if (title != null) titleWidget!,
+                  if (content != null) contentWidget!,
+                ],
+              ),
+            ),
+          ),
+        if (actions != null)
+          actionsWidget!,
+      ];
+    } else {
+      columnChildren = <Widget>[
+        if (title != null) titleWidget!,
+        if (content != null) Flexible(child: contentWidget!),
+        if (actions != null) actionsWidget!,
+      ];
+    }
+
+    Widget dialogChild = IntrinsicWidth(
+      child: Column(
+        mainAxisSize: MainAxisSize.min,
+        crossAxisAlignment: CrossAxisAlignment.stretch,
+        children: columnChildren,
+      ),
+    );
+
+    if (label != null)
+      dialogChild = Semantics(
+        scopesRoute: true,
+        explicitChildNodes: true,
+        namesRoute: true,
+        label: label,
+        child: dialogChild,
+      );
+
+    return Dialog(
+      backgroundColor: backgroundColor,
+      elevation: elevation,
+      insetPadding: insetPadding,
+      clipBehavior: clipBehavior,
+      shape: shape,
+      child: dialogChild,
+    );
+  }
+}
+
+/// An option used in a [SimpleDialog].
+///
+/// A simple dialog offers the user a choice between several options. This
+/// widget is commonly used to represent each of the options. If the user
+/// selects this option, the widget will call the [onPressed] callback, which
+/// typically uses [Navigator.pop] to close the dialog.
+///
+/// The padding on a [SimpleDialogOption] is configured to combine with the
+/// default [SimpleDialog.contentPadding] so that each option ends up 8 pixels
+/// from the other vertically, with 20 pixels of spacing between the dialog's
+/// title and the first option, and 24 pixels of spacing between the last option
+/// and the bottom of the dialog.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// SimpleDialogOption(
+///   onPressed: () { Navigator.pop(context, Department.treasury); },
+///   child: const Text('Treasury department'),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [SimpleDialog], for a dialog in which to use this widget.
+///  * [showDialog], which actually displays the dialog and returns its result.
+///  * [TextButton], which are commonly used as actions in other kinds of
+///    dialogs, such as [AlertDialog]s.
+///  * <https://material.io/design/components/dialogs.html#simple-dialog>
+class SimpleDialogOption extends StatelessWidget {
+  /// Creates an option for a [SimpleDialog].
+  const SimpleDialogOption({
+    Key? key,
+    this.onPressed,
+    this.padding,
+    this.child,
+  }) : super(key: key);
+
+  /// The callback that is called when this option is selected.
+  ///
+  /// If this is set to null, the option cannot be selected.
+  ///
+  /// When used in a [SimpleDialog], this will typically call [Navigator.pop]
+  /// with a value for [showDialog] to complete its future with.
+  final VoidCallback? onPressed;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? child;
+
+  /// The amount of space to surround the [child] with.
+  ///
+  /// Defaults to EdgeInsets.symmetric(vertical: 8.0, horizontal: 24.0).
+  final EdgeInsets? padding;
+
+  @override
+  Widget build(BuildContext context) {
+    return InkWell(
+      onTap: onPressed,
+      child: Padding(
+        padding: padding ?? const EdgeInsets.symmetric(vertical: 8.0, horizontal: 24.0),
+        child: child,
+      ),
+    );
+  }
+}
+
+/// A simple material design dialog.
+///
+/// A simple dialog offers the user a choice between several options. A simple
+/// dialog has an optional title that is displayed above the choices.
+///
+/// Choices are normally represented using [SimpleDialogOption] widgets. If
+/// other widgets are used, see [contentPadding] for notes regarding the
+/// conventions for obtaining the spacing expected by Material Design.
+///
+/// For dialogs that inform the user about a situation, consider using an
+/// [AlertDialog].
+///
+/// Typically passed as the child widget to [showDialog], which displays the
+/// dialog.
+///
+/// {@animation 350 622 https://flutter.github.io/assets-for-api-docs/assets/material/simple_dialog.mp4}
+///
+/// {@tool snippet}
+///
+/// In this example, the user is asked to select between two options. These
+/// options are represented as an enum. The [showDialog] method here returns
+/// a [Future] that completes to a value of that enum. If the user cancels
+/// the dialog (e.g. by hitting the back button on Android, or tapping on the
+/// mask behind the dialog) then the future completes with the null value.
+///
+/// The return value in this example is used as the index for a switch statement.
+/// One advantage of using an enum as the return value and then using that to
+/// drive a switch statement is that the analyzer will flag any switch statement
+/// that doesn't mention every value in the enum.
+///
+/// ```dart
+/// Future<void> _askedToLead() async {
+///   switch (await showDialog<Department>(
+///     context: context,
+///     builder: (BuildContext context) {
+///       return SimpleDialog(
+///         title: const Text('Select assignment'),
+///         children: <Widget>[
+///           SimpleDialogOption(
+///             onPressed: () { Navigator.pop(context, Department.treasury); },
+///             child: const Text('Treasury department'),
+///           ),
+///           SimpleDialogOption(
+///             onPressed: () { Navigator.pop(context, Department.state); },
+///             child: const Text('State department'),
+///           ),
+///         ],
+///       );
+///     }
+///   )) {
+///     case Department.treasury:
+///       // Let's go.
+///       // ...
+///     break;
+///     case Department.state:
+///       // ...
+///     break;
+///     case null:
+///       // dialog dismissed
+///     break;
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [SimpleDialogOption], which are options used in this type of dialog.
+///  * [AlertDialog], for dialogs that have a row of buttons below the body.
+///  * [Dialog], on which [SimpleDialog] and [AlertDialog] are based.
+///  * [showDialog], which actually displays the dialog and returns its result.
+///  * <https://material.io/design/components/dialogs.html#simple-dialog>
+class SimpleDialog extends StatelessWidget {
+  /// Creates a simple dialog.
+  ///
+  /// Typically used in conjunction with [showDialog].
+  ///
+  /// The [titlePadding] and [contentPadding] arguments must not be null.
+  const SimpleDialog({
+    Key? key,
+    this.title,
+    this.titlePadding = const EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 0.0),
+    this.titleTextStyle,
+    this.children,
+    this.contentPadding = const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0),
+    this.backgroundColor,
+    this.elevation,
+    this.semanticLabel,
+    this.shape,
+  }) : assert(titlePadding != null),
+       assert(contentPadding != null),
+       super(key: key);
+
+  /// The (optional) title of the dialog is displayed in a large font at the top
+  /// of the dialog.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? title;
+
+  /// Padding around the title.
+  ///
+  /// If there is no title, no padding will be provided.
+  ///
+  /// By default, this provides the recommend Material Design padding of 24
+  /// pixels around the left, top, and right edges of the title.
+  ///
+  /// See [contentPadding] for the conventions regarding padding between the
+  /// [title] and the [children].
+  final EdgeInsetsGeometry titlePadding;
+
+  /// Style for the text in the [title] of this [SimpleDialog].
+  ///
+  /// If null, [DialogTheme.titleTextStyle] is used. If that's null, defaults to
+  /// [TextTheme.headline6] of [ThemeData.textTheme].
+  final TextStyle? titleTextStyle;
+
+  /// The (optional) content of the dialog is displayed in a
+  /// [SingleChildScrollView] underneath the title.
+  ///
+  /// Typically a list of [SimpleDialogOption]s.
+  final List<Widget>? children;
+
+  /// Padding around the content.
+  ///
+  /// By default, this is 12 pixels on the top and 16 pixels on the bottom. This
+  /// is intended to be combined with children that have 24 pixels of padding on
+  /// the left and right, and 8 pixels of padding on the top and bottom, so that
+  /// the content ends up being indented 20 pixels from the title, 24 pixels
+  /// from the bottom, and 24 pixels from the sides.
+  ///
+  /// The [SimpleDialogOption] widget uses such padding.
+  ///
+  /// If there is no [title], the [contentPadding] should be adjusted so that
+  /// the top padding ends up being 24 pixels.
+  final EdgeInsetsGeometry contentPadding;
+
+  /// {@macro flutter.material.dialog.backgroundColor}
+  final Color? backgroundColor;
+
+  /// {@macro flutter.material.dialog.elevation}
+  /// {@macro flutter.material.material.elevation}
+  final double? elevation;
+
+  /// The semantic label of the dialog used by accessibility frameworks to
+  /// announce screen transitions when the dialog is opened and closed.
+  ///
+  /// If this label is not provided, a semantic label will be inferred from the
+  /// [title] if it is not null.  If there is no title, the label will be taken
+  /// from [MaterialLocalizations.dialogLabel].
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsConfiguration.namesRoute], for a description of how this
+  ///    value is used.
+  final String? semanticLabel;
+
+  /// {@macro flutter.material.dialog.shape}
+  final ShapeBorder? shape;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterialLocalizations(context));
+    final ThemeData theme = Theme.of(context);
+
+    String? label = semanticLabel;
+    if (title == null) {
+      switch (theme.platform) {
+        case TargetPlatform.macOS:
+        case TargetPlatform.iOS:
+          break;
+        case TargetPlatform.android:
+        case TargetPlatform.fuchsia:
+        case TargetPlatform.linux:
+        case TargetPlatform.windows:
+          label = semanticLabel ?? MaterialLocalizations.of(context).dialogLabel;
+      }
+    }
+
+    // The paddingScaleFactor is used to adjust the padding of Dialog
+    // children.
+    final double paddingScaleFactor = _paddingScaleFactor(MediaQuery.of(context).textScaleFactor);
+    final TextDirection? textDirection = Directionality.maybeOf(context);
+
+    Widget? titleWidget;
+    if (title != null) {
+      final EdgeInsets effectiveTitlePadding = titlePadding.resolve(textDirection);
+      titleWidget = Padding(
+        padding: EdgeInsets.only(
+          left: effectiveTitlePadding.left * paddingScaleFactor,
+          right: effectiveTitlePadding.right * paddingScaleFactor,
+          top: effectiveTitlePadding.top * paddingScaleFactor,
+          bottom: children == null ? effectiveTitlePadding.bottom * paddingScaleFactor : effectiveTitlePadding.bottom,
+        ),
+        child: DefaultTextStyle(
+          style: titleTextStyle ?? DialogTheme.of(context).titleTextStyle ?? theme.textTheme.headline6!,
+          child: Semantics(namesRoute: label == null, child: title),
+        ),
+      );
+    }
+
+    Widget? contentWidget;
+    if (children != null) {
+      final EdgeInsets effectiveContentPadding = contentPadding.resolve(textDirection);
+      contentWidget = Flexible(
+        child: SingleChildScrollView(
+          padding: EdgeInsets.only(
+            left: effectiveContentPadding.left * paddingScaleFactor,
+            right: effectiveContentPadding.right * paddingScaleFactor,
+            top: title == null ? effectiveContentPadding.top * paddingScaleFactor : effectiveContentPadding.top,
+            bottom: effectiveContentPadding.bottom * paddingScaleFactor,
+          ),
+          child: ListBody(children: children!),
+        ),
+      );
+    }
+
+    Widget dialogChild = IntrinsicWidth(
+      stepWidth: 56.0,
+      child: ConstrainedBox(
+        constraints: const BoxConstraints(minWidth: 280.0),
+        child: Column(
+          mainAxisSize: MainAxisSize.min,
+          crossAxisAlignment: CrossAxisAlignment.stretch,
+          children: <Widget>[
+            if (title != null) titleWidget!,
+            if (children != null) contentWidget!,
+          ],
+        ),
+      ),
+    );
+
+    if (label != null)
+      dialogChild = Semantics(
+        scopesRoute: true,
+        explicitChildNodes: true,
+        namesRoute: true,
+        label: label,
+        child: dialogChild,
+      );
+    return Dialog(
+      backgroundColor: backgroundColor,
+      elevation: elevation,
+      shape: shape,
+      child: dialogChild,
+    );
+  }
+}
+
+Widget _buildMaterialDialogTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
+  return FadeTransition(
+    opacity: CurvedAnimation(
+      parent: animation,
+      curve: Curves.easeOut,
+    ),
+    child: child,
+  );
+}
+
+/// Displays a Material dialog above the current contents of the app, with
+/// Material entrance and exit animations, modal barrier color, and modal
+/// barrier behavior (dialog is dismissible with a tap on the barrier).
+///
+/// This function takes a `builder` which typically builds a [Dialog] widget.
+/// Content below the dialog is dimmed with a [ModalBarrier]. The widget
+/// returned by the `builder` does not share a context with the location that
+/// `showDialog` is originally called from. Use a [StatefulBuilder] or a
+/// custom [StatefulWidget] if the dialog needs to update dynamically.
+///
+/// The `child` argument is deprecated, and should be replaced with `builder`.
+///
+/// The `context` argument is used to look up the [Navigator] and [Theme] for
+/// the dialog. It is only used when the method is called. Its corresponding
+/// widget can be safely removed from the tree before the dialog is closed.
+///
+/// The `barrierDismissible` argument is used to indicate whether tapping on the
+/// barrier will dismiss the dialog. It is `true` by default and can not be `null`.
+///
+/// The `barrierColor` argument is used to specify the color of the modal
+/// barrier that darkens everything below the dialog. If `null` the default color
+/// `Colors.black54` is used.
+///
+/// The `useSafeArea` argument is used to indicate if the dialog should only
+/// display in 'safe' areas of the screen not used by the operating system
+/// (see [SafeArea] for more details). It is `true` by default, which means
+/// the dialog will not overlap operating system areas. If it is set to `false`
+/// the dialog will only be constrained by the screen size. It can not be `null`.
+///
+/// The `useRootNavigator` argument is used to determine whether to push the
+/// dialog to the [Navigator] furthest from or nearest to the given `context`.
+/// By default, `useRootNavigator` is `true` and the dialog route created by
+/// this method is pushed to the root navigator. It can not be `null`.
+///
+/// The `routeSettings` argument is passed to [showGeneralDialog],
+/// see [RouteSettings] for details.
+///
+/// If the application has multiple [Navigator] objects, it may be necessary to
+/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
+/// dialog rather than just `Navigator.pop(context, result)`.
+///
+/// Returns a [Future] that resolves to the value (if any) that was passed to
+/// [Navigator.pop] when the dialog was closed.
+///
+/// See also:
+///
+///  * [AlertDialog], for dialogs that have a row of buttons below a body.
+///  * [SimpleDialog], which handles the scrolling of the contents and does
+///    not show buttons below its body.
+///  * [Dialog], on which [SimpleDialog] and [AlertDialog] are based.
+///  * [showCupertinoDialog], which displays an iOS-style dialog.
+///  * [showGeneralDialog], which allows for customization of the dialog popup.
+///  * <https://material.io/design/components/dialogs.html>
+Future<T?> showDialog<T>({
+  required BuildContext context,
+  WidgetBuilder? builder,
+  bool barrierDismissible = true,
+  Color? barrierColor,
+  bool useSafeArea = true,
+  bool useRootNavigator = true,
+  RouteSettings? routeSettings,
+  @Deprecated(
+    'Instead of using the "child" argument, return the child from a closure '
+    'provided to the "builder" argument. This will ensure that the BuildContext '
+    'is appropriate for widgets built in the dialog. '
+    'This feature was deprecated after v0.2.3.'
+  )
+  Widget? child,
+}) {
+  assert(child == null || builder == null);
+  assert(child != null || builder != null);
+  assert(barrierDismissible != null);
+  assert(useSafeArea != null);
+  assert(useRootNavigator != null);
+  assert(debugCheckHasMaterialLocalizations(context));
+
+  final CapturedThemes themes = InheritedTheme.capture(from: context, to: Navigator.of(context, rootNavigator: useRootNavigator).context);
+  return showGeneralDialog(
+    context: context,
+    pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) {
+      final Widget pageChild = child ?? Builder(builder: builder!);
+      Widget dialog = themes.wrap(pageChild);
+      if (useSafeArea) {
+        dialog = SafeArea(child: dialog);
+      }
+      return dialog;
+    },
+    barrierDismissible: barrierDismissible,
+    barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
+    barrierColor: barrierColor ?? Colors.black54,
+    transitionDuration: const Duration(milliseconds: 150),
+    transitionBuilder: _buildMaterialDialogTransitions,
+    useRootNavigator: useRootNavigator,
+    routeSettings: routeSettings,
+  );
+}
+
+double _paddingScaleFactor(double textScaleFactor) {
+  final double clampedTextScaleFactor = textScaleFactor.clamp(1.0, 2.0).toDouble();
+  // The final padding scale factor is clamped between 1/3 and 1. For example,
+  // a non-scaled padding of 24 will produce a padding between 24 and 8.
+  return lerpDouble(1.0, 1.0 / 3.0, clampedTextScaleFactor - 1.0)!;
+}
diff --git a/lib/src/material/dialog_theme.dart b/lib/src/material/dialog_theme.dart
new file mode 100644
index 0000000..5e877b2
--- /dev/null
+++ b/lib/src/material/dialog_theme.dart
@@ -0,0 +1,130 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'theme.dart';
+
+/// Defines a theme for [Dialog] widgets.
+///
+/// Descendant widgets obtain the current [DialogTheme] object using
+/// `DialogTheme.of(context)`. Instances of [DialogTheme] can be customized with
+/// [DialogTheme.copyWith].
+///
+/// When Shape is `null`, the dialog defaults to a [RoundedRectangleBorder] with
+/// a border radius of 2.0 on all corners.
+///
+/// [titleTextStyle] and [contentTextStyle] are used in [AlertDialog]s.
+/// If null, they default to [TextTheme.headline6] and [TextTheme.subtitle1],
+/// respectively.
+///
+/// See also:
+///
+///  * [Dialog], a material dialog that can be customized using this [DialogTheme].
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class DialogTheme with Diagnosticable {
+  /// Creates a dialog theme that can be used for [ThemeData.dialogTheme].
+  const DialogTheme({
+    this.backgroundColor,
+    this.elevation,
+    this.shape,
+    this.titleTextStyle,
+    this.contentTextStyle,
+  });
+
+  /// Default value for [Dialog.backgroundColor].
+  ///
+  /// If null, [ThemeData.dialogBackgroundColor] is used, if that's null,
+  /// defaults to [Colors.white].
+  final Color? backgroundColor;
+
+  /// Default value for [Dialog.elevation].
+  ///
+  /// If null, the [Dialog] elevation defaults to `24.0`.
+  final double? elevation;
+
+  /// Default value for [Dialog.shape].
+  final ShapeBorder? shape;
+
+  /// Used to configure the [DefaultTextStyle] for the [AlertDialog.title] widget.
+  ///
+  /// If null, defaults to [TextTheme.headline6] of [ThemeData.textTheme].
+  final TextStyle? titleTextStyle;
+
+  /// Used to configure the [DefaultTextStyle] for the [AlertDialog.content] widget.
+  ///
+  /// If null, defaults to [TextTheme.subtitle1] of [ThemeData.textTheme].
+  final TextStyle? contentTextStyle;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  DialogTheme copyWith({
+    Color? backgroundColor,
+    double? elevation,
+    ShapeBorder? shape,
+    TextStyle? titleTextStyle,
+    TextStyle? contentTextStyle,
+  }) {
+    return DialogTheme(
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      elevation: elevation ?? this.elevation,
+      shape: shape ?? this.shape,
+      titleTextStyle: titleTextStyle ?? this.titleTextStyle,
+      contentTextStyle: contentTextStyle ?? this.contentTextStyle,
+    );
+  }
+
+  /// The data from the closest [DialogTheme] instance given the build context.
+  static DialogTheme of(BuildContext context) {
+    return Theme.of(context).dialogTheme;
+  }
+
+  /// Linearly interpolate between two dialog themes.
+  ///
+  /// The arguments must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static DialogTheme lerp(DialogTheme? a, DialogTheme? b, double t) {
+    assert(t != null);
+    return DialogTheme(
+      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
+      elevation: lerpDouble(a?.elevation, b?.elevation, t),
+      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
+      titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t),
+      contentTextStyle: TextStyle.lerp(a?.contentTextStyle, b?.contentTextStyle, t),
+    );
+  }
+
+  @override
+  int get hashCode => shape.hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is DialogTheme
+        && other.backgroundColor == backgroundColor
+        && other.elevation == elevation
+        && other.shape == shape
+        && other.titleTextStyle == titleTextStyle
+        && other.contentTextStyle == contentTextStyle;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ColorProperty('backgroundColor', backgroundColor));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
+    properties.add(DoubleProperty('elevation', elevation));
+    properties.add(DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('contentTextStyle', contentTextStyle, defaultValue: null));
+  }
+}
diff --git a/lib/src/material/divider.dart b/lib/src/material/divider.dart
new file mode 100644
index 0000000..55519c2
--- /dev/null
+++ b/lib/src/material/divider.dart
@@ -0,0 +1,324 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+import 'package:flute/painting.dart';
+
+import 'divider_theme.dart';
+import 'theme.dart';
+
+// Examples can assume:
+// late BuildContext context;
+
+/// A thin horizontal line, with padding on either side.
+///
+/// In the material design language, this represents a divider. Dividers can be
+/// used in lists, [Drawer]s, and elsewhere to separate content.
+///
+/// To create a divider between [ListTile] items, consider using
+/// [ListTile.divideTiles], which is optimized for this case.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=_liUC641Nmk}
+///
+/// The box's total height is controlled by [height]. The appropriate
+/// padding is automatically computed from the height.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold}
+///
+/// This sample shows how to display a Divider between an orange and blue box
+/// inside a column. The Divider is 20 logical pixels in height and contains a
+/// vertically centered black line that is 5 logical pixels thick. The black
+/// line is indented by 20 logical pixels.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/divider.png)
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Center(
+///     child: Column(
+///       children: <Widget>[
+///         Expanded(
+///           child: Container(
+///             color: Colors.amber,
+///             child: const Center(
+///               child: Text('Above'),
+///             ),
+///           ),
+///         ),
+///         const Divider(
+///           height: 20,
+///           thickness: 5,
+///           indent: 20,
+///           endIndent: 20,
+///         ),
+///         // Subheader example from Material spec.
+///         // https://material.io/components/dividers#types
+///         Container(
+///           padding: const EdgeInsets.only(left: 20),
+///           child: Align(
+///             alignment: AlignmentDirectional.centerStart,
+///             child: Text(
+///               'Subheader',
+///               style: Theme.of(context).textTheme.caption,
+///               textAlign: TextAlign.start,
+///             ),
+///           ),
+///         ),
+///         Expanded(
+///           child: Container(
+///             color: Theme.of(context).colorScheme.primary,
+///             child: const Center(
+///               child: Text('Below'),
+///             ),
+///           ),
+///         ),
+///       ],
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+/// See also:
+///
+///  * [PopupMenuDivider], which is the equivalent but for popup menus.
+///  * [ListTile.divideTiles], another approach to dividing widgets in a list.
+///  * <https://material.io/design/components/dividers.html>
+class Divider extends StatelessWidget {
+  /// Creates a material design divider.
+  ///
+  /// The [height], [thickness], [indent], and [endIndent] must be null or
+  /// non-negative.
+  const Divider({
+    Key? key,
+    this.height,
+    this.thickness,
+    this.indent,
+    this.endIndent,
+    this.color,
+  }) : assert(height == null || height >= 0.0),
+       assert(thickness == null || thickness >= 0.0),
+       assert(indent == null || indent >= 0.0),
+       assert(endIndent == null || endIndent >= 0.0),
+       super(key: key);
+
+
+  /// The divider's height extent.
+  ///
+  /// The divider itself is always drawn as a horizontal line that is centered
+  /// within the height specified by this value.
+  ///
+  /// If this is null, then the [DividerThemeData.space] is used. If that is
+  /// also null, then this defaults to 16.0.
+  final double? height;
+
+  /// The thickness of the line drawn within the divider.
+  ///
+  /// A divider with a [thickness] of 0.0 is always drawn as a line with a
+  /// height of exactly one device pixel.
+  ///
+  /// If this is null, then the [DividerThemeData.thickness] is used. If
+  /// that is also null, then this defaults to 0.0.
+  final double? thickness;
+
+  /// The amount of empty space to the leading edge of the divider.
+  ///
+  /// If this is null, then the [DividerThemeData.indent] is used. If that is
+  /// also null, then this defaults to 0.0.
+  final double? indent;
+
+  /// The amount of empty space to the trailing edge of the divider.
+  ///
+  /// If this is null, then the [DividerThemeData.endIndent] is used. If that is
+  /// also null, then this defaults to 0.0.
+  final double? endIndent;
+
+  /// The color to use when painting the line.
+  ///
+  /// If this is null, then the [DividerThemeData.color] is used. If that is
+  /// also null, then [ThemeData.dividerColor] is used.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Divider(
+  ///   color: Colors.deepOrange,
+  /// )
+  /// ```
+  /// {@end-tool}
+  final Color? color;
+
+  /// Computes the [BorderSide] that represents a divider.
+  ///
+  /// If [color] is null, then [DividerThemeData.color] is used. If that is also
+  /// null, then [ThemeData.dividerColor] is used.
+  ///
+  /// If [width] is null, then [DividerThemeData.thickness] is used. If that is
+  /// also null, then this defaults to 0.0 (a hairline border).
+  ///
+  /// If [context] is null, the default color of [BorderSide] is used and the
+  /// default width of 0.0 is used.
+  ///
+  /// {@tool snippet}
+  ///
+  /// This example uses this method to create a box that has a divider above and
+  /// below it. This is sometimes useful with lists, for instance, to separate a
+  /// scrollable section from the rest of the interface.
+  ///
+  /// ```dart
+  /// DecoratedBox(
+  ///   decoration: BoxDecoration(
+  ///     border: Border(
+  ///       top: Divider.createBorderSide(context),
+  ///       bottom: Divider.createBorderSide(context),
+  ///     ),
+  ///   ),
+  ///   // child: ...
+  /// )
+  /// ```
+  /// {@end-tool}
+  static BorderSide createBorderSide(BuildContext? context, { Color? color, double? width }) {
+    final Color? effectiveColor = color
+        ?? (context != null ? (DividerTheme.of(context).color ?? Theme.of(context).dividerColor) : null);
+    final double effectiveWidth =  width
+        ?? (context != null ? DividerTheme.of(context).thickness : null)
+        ?? 0.0;
+
+    // Prevent assertion since it is possible that context is null and no color
+    // is specified.
+    if (effectiveColor == null) {
+      return BorderSide(
+        width: effectiveWidth,
+      );
+    }
+    return BorderSide(
+      color: effectiveColor,
+      width: effectiveWidth,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final DividerThemeData dividerTheme = DividerTheme.of(context);
+    final double height = this.height ?? dividerTheme.space ?? 16.0;
+    final double thickness = this.thickness ?? dividerTheme.thickness ?? 0.0;
+    final double indent = this.indent ?? dividerTheme.indent ?? 0.0;
+    final double endIndent = this.endIndent ?? dividerTheme.endIndent ?? 0.0;
+
+    return SizedBox(
+      height: height,
+      child: Center(
+        child: Container(
+          height: thickness,
+          margin: EdgeInsetsDirectional.only(start: indent, end: endIndent),
+          decoration: BoxDecoration(
+            border: Border(
+              bottom: createBorderSide(context, color: color, width: thickness),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+/// A thin vertical line, with padding on either side.
+///
+/// In the material design language, this represents a divider. Vertical
+/// dividers can be used in horizontally scrolling lists, such as a
+/// [ListView] with [ListView.scrollDirection] set to [Axis.horizontal].
+///
+/// The box's total width is controlled by [width]. The appropriate
+/// padding is automatically computed from the width.
+///
+/// See also:
+///
+///  * [ListView.separated], which can be used to generate vertical dividers.
+///  * <https://material.io/design/components/dividers.html>
+class VerticalDivider extends StatelessWidget {
+  /// Creates a material design vertical divider.
+  ///
+  /// The [width], [thickness], [indent], and [endIndent] must be null or
+  /// non-negative.
+  const VerticalDivider({
+    Key? key,
+    this.width,
+    this.thickness,
+    this.indent,
+    this.endIndent,
+    this.color,
+  }) : assert(width == null || width >= 0.0),
+       assert(thickness == null || thickness >= 0.0),
+       assert(indent == null || indent >= 0.0),
+       assert(endIndent == null || endIndent >= 0.0),
+       super(key: key);
+
+  /// The divider's width.
+  ///
+  /// The divider itself is always drawn as a vertical line that is centered
+  /// within the width specified by this value.
+  ///
+  /// If this is null, then the [DividerThemeData.space] is used. If that is
+  /// also null, then this defaults to 16.0.
+  final double? width;
+
+  /// The thickness of the line drawn within the divider.
+  ///
+  /// A divider with a [thickness] of 0.0 is always drawn as a line with a
+  /// width of exactly one device pixel.
+  ///
+  /// If this is null, then the [DividerThemeData.thickness] is used which
+  /// defaults to 0.0.
+  final double? thickness;
+
+  /// The amount of empty space on top of the divider.
+  ///
+  /// If this is null, then the [DividerThemeData.indent] is used. If that is
+  /// also null, then this defaults to 0.0.
+  final double? indent;
+
+  /// The amount of empty space under the divider.
+  ///
+  /// If this is null, then the [DividerThemeData.endIndent] is used. If that is
+  /// also null, then this defaults to 0.0.
+  final double? endIndent;
+
+  /// The color to use when painting the line.
+  ///
+  /// If this is null, then the [DividerThemeData.color] is used. If that is
+  /// also null, then [ThemeData.dividerColor] is used.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Divider(
+  ///   color: Colors.deepOrange,
+  /// )
+  /// ```
+  /// {@end-tool}
+  final Color? color;
+
+  @override
+  Widget build(BuildContext context) {
+    final DividerThemeData dividerTheme = DividerTheme.of(context);
+    final double width = this.width ?? dividerTheme.space ?? 16.0;
+    final double thickness = this.thickness ?? dividerTheme.thickness ?? 0.0;
+    final double indent = this.indent ?? dividerTheme.indent ?? 0.0;
+    final double endIndent = this.endIndent ?? dividerTheme.endIndent ?? 0.0;
+
+    return SizedBox(
+      width: width,
+      child: Center(
+        child: Container(
+          width: thickness,
+          margin: EdgeInsetsDirectional.only(top: indent, bottom: endIndent),
+          decoration: BoxDecoration(
+            border: Border(
+              left: Divider.createBorderSide(context, color: color, width: thickness),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/divider_theme.dart b/lib/src/material/divider_theme.dart
new file mode 100644
index 0000000..d790062
--- /dev/null
+++ b/lib/src/material/divider_theme.dart
@@ -0,0 +1,174 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'theme.dart';
+
+/// Defines the visual properties of [Divider], [VerticalDivider], dividers
+/// between [ListTile]s, and dividers between rows in [DataTable]s.
+///
+/// Descendant widgets obtain the current [DividerThemeData] object using
+/// `DividerTheme.of(context)`. Instances of [DividerThemeData]
+/// can be customized with [DividerThemeData.copyWith].
+///
+/// Typically a [DividerThemeData] is specified as part of the overall
+/// [Theme] with [ThemeData.dividerTheme].
+///
+/// All [DividerThemeData] properties are `null` by default. When null,
+/// the widgets will provide their own defaults.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class DividerThemeData with Diagnosticable {
+
+  /// Creates a theme that can be used for [DividerTheme] or
+  /// [ThemeData.dividerTheme].
+  const DividerThemeData({
+    this.color,
+    this.space,
+    this.thickness,
+    this.indent,
+    this.endIndent,
+  });
+
+  /// The color of [Divider]s and [VerticalDivider]s, also
+  /// used between [ListTile]s, between rows in [DataTable]s, and so forth.
+  final Color? color;
+
+  /// The [Divider]'s width or the [VerticalDivider]'s height.
+  ///
+  /// This represents the amount of horizontal or vertical space the divider
+  /// takes up.
+  final double? space;
+
+  /// The thickness of the line drawn within the divider.
+  final double? thickness;
+
+  /// The amount of empty space at the leading edge of [Divider] or top edge of
+  /// [VerticalDivider].
+  final double? indent;
+
+  /// The amount of empty space at the trailing edge of [Divider] or bottom edge
+  /// of [VerticalDivider].
+  final double? endIndent;
+
+  /// Creates a copy of this object with the given fields replaced with the
+  /// new values.
+  DividerThemeData copyWith({
+    Color? color,
+    double? space,
+    double? thickness,
+    double? indent,
+    double? endIndent,
+  }) {
+    return DividerThemeData(
+      color: color ?? this.color,
+      space: space ?? this.space,
+      thickness: thickness ?? this.thickness,
+      indent: indent ?? this.indent,
+      endIndent: endIndent ?? this.endIndent,
+    );
+  }
+
+  /// Linearly interpolate between two Divider themes.
+  ///
+  /// The argument `t` must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static DividerThemeData lerp(DividerThemeData? a, DividerThemeData? b, double t) {
+    assert(t != null);
+    return DividerThemeData(
+      color: Color.lerp(a?.color, b?.color, t),
+      space: lerpDouble(a?.space, b?.space, t),
+      thickness: lerpDouble(a?.thickness, b?.thickness, t),
+      indent: lerpDouble(a?.indent, b?.indent, t),
+      endIndent: lerpDouble(a?.endIndent, b?.endIndent, t),
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      color,
+      space,
+      thickness,
+      indent,
+      endIndent,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is DividerThemeData
+        && other.color == color
+        && other.space == space
+        && other.thickness == thickness
+        && other.indent == indent
+        && other.endIndent == endIndent;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(DoubleProperty('space', space, defaultValue: null));
+    properties.add(DoubleProperty('thickness', thickness, defaultValue: null));
+    properties.add(DoubleProperty('indent', indent, defaultValue: null));
+    properties.add(DoubleProperty('endIndent', endIndent, defaultValue: null));
+  }
+}
+
+/// An inherited widget that defines the configuration for
+/// [Divider]s, [VerticalDivider]s, dividers between [ListTile]s, and dividers
+/// between rows in [DataTable]s in this widget's subtree.
+class DividerTheme extends InheritedTheme {
+  /// Creates a divider theme that controls the configurations for
+  /// [Divider]s, [VerticalDivider]s, dividers between [ListTile]s, and dividers
+  /// between rows in [DataTable]s in its widget subtree.
+  const DividerTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null),
+       super(key: key, child: child);
+
+  /// The properties for descendant [Divider]s, [VerticalDivider]s, dividers
+  /// between [ListTile]s, and dividers between rows in [DataTable]s.
+  final DividerThemeData data;
+
+  /// The closest instance of this class's [data] value that encloses the given
+  /// context.
+  ///
+  /// If there is no ancestor, it returns [ThemeData.dividerTheme]. Applications
+  /// can assume that the returned value will not be null.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// DividerThemeData theme = DividerTheme.of(context);
+  /// ```
+  static DividerThemeData of(BuildContext context) {
+    final DividerTheme? dividerTheme = context.dependOnInheritedWidgetOfExactType<DividerTheme>();
+    return dividerTheme?.data ?? Theme.of(context).dividerTheme;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return DividerTheme(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(DividerTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/drawer.dart b/lib/src/material/drawer.dart
new file mode 100644
index 0000000..3808dc9
--- /dev/null
+++ b/lib/src/material/drawer.dart
@@ -0,0 +1,608 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+import 'package:flute/gestures.dart' show DragStartBehavior;
+
+import 'colors.dart';
+import 'debug.dart';
+import 'list_tile.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'theme.dart';
+
+/// The possible alignments of a [Drawer].
+enum DrawerAlignment {
+  /// Denotes that the [Drawer] is at the start side of the [Scaffold].
+  ///
+  /// This corresponds to the left side when the text direction is left-to-right
+  /// and the right side when the text direction is right-to-left.
+  start,
+
+  /// Denotes that the [Drawer] is at the end side of the [Scaffold].
+  ///
+  /// This corresponds to the right side when the text direction is left-to-right
+  /// and the left side when the text direction is right-to-left.
+  end,
+}
+
+// TODO(eseidel): Draw width should vary based on device size:
+// https://material.io/design/components/navigation-drawer.html#specs
+
+// Mobile:
+// Width = Screen width − 56 dp
+// Maximum width: 320dp
+// Maximum width applies only when using a left nav. When using a right nav,
+// the panel can cover the full width of the screen.
+
+// Desktop/Tablet:
+// Maximum width for a left nav is 400dp.
+// The right nav can vary depending on content.
+
+const double _kWidth = 304.0;
+const double _kEdgeDragWidth = 20.0;
+const double _kMinFlingVelocity = 365.0;
+const Duration _kBaseSettleDuration = Duration(milliseconds: 246);
+
+/// A material design panel that slides in horizontally from the edge of a
+/// [Scaffold] to show navigation links in an application.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=WRj86iHihgY}
+///
+/// Drawers are typically used with the [Scaffold.drawer] property. The child of
+/// the drawer is usually a [ListView] whose first child is a [DrawerHeader]
+/// that displays status information about the current user. The remaining
+/// drawer children are often constructed with [ListTile]s, often concluding
+/// with an [AboutListTile].
+///
+/// The [AppBar] automatically displays an appropriate [IconButton] to show the
+/// [Drawer] when a [Drawer] is available in the [Scaffold]. The [Scaffold]
+/// automatically handles the edge-swipe gesture to show the drawer.
+///
+/// {@animation 350 622 https://flutter.github.io/assets-for-api-docs/assets/material/drawer.mp4}
+///
+/// {@tool snippet}
+/// This example shows how to create a [Scaffold] that contains an [AppBar] and
+/// a [Drawer]. A user taps the "menu" icon in the [AppBar] to open the
+/// [Drawer]. The [Drawer] displays four items: A header and three menu items.
+/// The [Drawer] displays the four items using a [ListView], which allows the
+/// user to scroll through the items if need be.
+///
+/// ```dart
+/// Scaffold(
+///   appBar: AppBar(
+///     title: const Text('Drawer Demo'),
+///   ),
+///   drawer: Drawer(
+///     child: ListView(
+///       padding: EdgeInsets.zero,
+///       children: const <Widget>[
+///         DrawerHeader(
+///           decoration: BoxDecoration(
+///             color: Colors.blue,
+///           ),
+///           child: Text(
+///             'Drawer Header',
+///             style: TextStyle(
+///               color: Colors.white,
+///               fontSize: 24,
+///             ),
+///           ),
+///         ),
+///         ListTile(
+///           leading: Icon(Icons.message),
+///           title: Text('Messages'),
+///         ),
+///         ListTile(
+///           leading: Icon(Icons.account_circle),
+///           title: Text('Profile'),
+///         ),
+///         ListTile(
+///           leading: Icon(Icons.settings),
+///           title: Text('Settings'),
+///         ),
+///       ],
+///     ),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// An open drawer can be closed by calling [Navigator.pop]. For example
+/// a drawer item might close the drawer when tapped:
+///
+/// ```dart
+/// ListTile(
+///   leading: Icon(Icons.change_history),
+///   title: Text('Change history'),
+///   onTap: () {
+///     // change app state...
+///     Navigator.pop(context); // close the drawer
+///   },
+/// );
+/// ```
+///
+/// See also:
+///
+///  * [Scaffold.drawer], where one specifies a [Drawer] so that it can be
+///    shown.
+///  * [Scaffold.of], to obtain the current [ScaffoldState], which manages the
+///    display and animation of the drawer.
+///  * [ScaffoldState.openDrawer], which displays its [Drawer], if any.
+///  * <https://material.io/design/components/navigation-drawer.html>
+class Drawer extends StatelessWidget {
+  /// Creates a material design drawer.
+  ///
+  /// Typically used in the [Scaffold.drawer] property.
+  ///
+  /// The [elevation] must be non-negative.
+  const Drawer({
+    Key? key,
+    this.elevation = 16.0,
+    this.child,
+    this.semanticLabel,
+  }) : assert(elevation != null && elevation >= 0.0),
+       super(key: key);
+
+  /// The z-coordinate at which to place this drawer relative to its parent.
+  ///
+  /// This controls the size of the shadow below the drawer.
+  ///
+  /// Defaults to 16, the appropriate elevation for drawers. The value is
+  /// always non-negative.
+  final double elevation;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// Typically a [SliverList].
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// The semantic label of the dialog used by accessibility frameworks to
+  /// announce screen transitions when the drawer is opened and closed.
+  ///
+  /// If this label is not provided, it will default to
+  /// [MaterialLocalizations.drawerLabel].
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsConfiguration.namesRoute], for a description of how this
+  ///    value is used.
+  final String? semanticLabel;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterialLocalizations(context));
+    String? label = semanticLabel;
+    switch (Theme.of(context).platform) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        break;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        label = semanticLabel ?? MaterialLocalizations.of(context).drawerLabel;
+    }
+    return Semantics(
+      scopesRoute: true,
+      namesRoute: true,
+      explicitChildNodes: true,
+      label: label,
+      child: ConstrainedBox(
+        constraints: const BoxConstraints.expand(width: _kWidth),
+        child: Material(
+          elevation: elevation,
+          child: child,
+        ),
+      ),
+    );
+  }
+}
+
+/// Signature for the callback that's called when a [DrawerController] is
+/// opened or closed.
+typedef DrawerCallback = void Function(bool isOpened);
+
+/// Provides interactive behavior for [Drawer] widgets.
+///
+/// Rarely used directly. Drawer controllers are typically created automatically
+/// by [Scaffold] widgets.
+///
+/// The drawer controller provides the ability to open and close a drawer, either
+/// via an animation or via user interaction. When closed, the drawer collapses
+/// to a translucent gesture detector that can be used to listen for edge
+/// swipes.
+///
+/// See also:
+///
+///  * [Drawer], a container with the default width of a drawer.
+///  * [Scaffold.drawer], the [Scaffold] slot for showing a drawer.
+class DrawerController extends StatefulWidget {
+  /// Creates a controller for a [Drawer].
+  ///
+  /// Rarely used directly.
+  ///
+  /// The [child] argument must not be null and is typically a [Drawer].
+  const DrawerController({
+    GlobalKey? key,
+    required this.child,
+    required this.alignment,
+    this.drawerCallback,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.scrimColor,
+    this.edgeDragWidth,
+    this.enableOpenDragGesture = true,
+  }) : assert(child != null),
+       assert(dragStartBehavior != null),
+       assert(alignment != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// Typically a [Drawer].
+  final Widget child;
+
+  /// The alignment of the [Drawer].
+  ///
+  /// This controls the direction in which the user should swipe to open and
+  /// close the drawer.
+  final DrawerAlignment alignment;
+
+  /// Optional callback that is called when a [Drawer] is opened or closed.
+  final DrawerCallback? drawerCallback;
+
+  /// {@template flutter.material.DrawerController.dragStartBehavior}
+  /// Determines the way that drag start behavior is handled.
+  ///
+  /// If set to [DragStartBehavior.start], the drag behavior used for opening
+  /// and closing a drawer will begin upon the detection of a drag gesture. If
+  /// set to [DragStartBehavior.down] it will begin when a down event is first
+  /// detected.
+  ///
+  /// In general, setting this to [DragStartBehavior.start] will make drag
+  /// animation smoother and setting it to [DragStartBehavior.down] will make
+  /// drag behavior feel slightly more reactive.
+  ///
+  /// By default, the drag start behavior is [DragStartBehavior.start].
+  ///
+  /// See also:
+  ///
+  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for
+  ///    the different behaviors.
+  ///
+  /// {@endtemplate}
+  final DragStartBehavior dragStartBehavior;
+
+  /// The color to use for the scrim that obscures primary content while a drawer is open.
+  ///
+  /// By default, the color used is [Colors.black54]
+  final Color? scrimColor;
+
+  /// Determines if the [Drawer] can be opened with a drag gesture.
+  ///
+  /// By default, the drag gesture is enabled.
+  final bool enableOpenDragGesture;
+
+  /// The width of the area within which a horizontal swipe will open the
+  /// drawer.
+  ///
+  /// By default, the value used is 20.0 added to the padding edge of
+  /// `MediaQuery.of(context).padding` that corresponds to [alignment].
+  /// This ensures that the drag area for notched devices is not obscured. For
+  /// example, if [alignment] is set to [DrawerAlignment.start] and
+  /// `TextDirection.of(context)` is set to [TextDirection.ltr],
+  /// 20.0 will be added to `MediaQuery.of(context).padding.left`.
+  final double? edgeDragWidth;
+
+  @override
+  DrawerControllerState createState() => DrawerControllerState();
+}
+
+/// State for a [DrawerController].
+///
+/// Typically used by a [Scaffold] to [open] and [close] the drawer.
+class DrawerControllerState extends State<DrawerController> with SingleTickerProviderStateMixin {
+  @override
+  void initState() {
+    super.initState();
+    _scrimColorTween = _buildScrimColorTween();
+    _controller = AnimationController(duration: _kBaseSettleDuration, vsync: this)
+      ..addListener(_animationChanged)
+      ..addStatusListener(_animationStatusChanged);
+  }
+
+  @override
+  void dispose() {
+    _historyEntry?.remove();
+    _controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(DrawerController oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.scrimColor != oldWidget.scrimColor)
+      _scrimColorTween = _buildScrimColorTween();
+  }
+
+  void _animationChanged() {
+    setState(() {
+      // The animation controller's state is our build state, and it changed already.
+    });
+  }
+
+  LocalHistoryEntry? _historyEntry;
+  final FocusScopeNode _focusScopeNode = FocusScopeNode();
+
+  void _ensureHistoryEntry() {
+    if (_historyEntry == null) {
+      final ModalRoute<dynamic>? route = ModalRoute.of(context);
+      if (route != null) {
+        _historyEntry = LocalHistoryEntry(onRemove: _handleHistoryEntryRemoved);
+        route.addLocalHistoryEntry(_historyEntry!);
+        FocusScope.of(context).setFirstFocus(_focusScopeNode);
+      }
+    }
+  }
+
+  void _animationStatusChanged(AnimationStatus status) {
+    switch (status) {
+      case AnimationStatus.forward:
+        _ensureHistoryEntry();
+        break;
+      case AnimationStatus.reverse:
+        _historyEntry?.remove();
+        _historyEntry = null;
+        break;
+      case AnimationStatus.dismissed:
+        break;
+      case AnimationStatus.completed:
+        break;
+    }
+  }
+
+  void _handleHistoryEntryRemoved() {
+    _historyEntry = null;
+    close();
+  }
+
+  late AnimationController _controller;
+
+  void _handleDragDown(DragDownDetails details) {
+    _controller.stop();
+    _ensureHistoryEntry();
+  }
+
+  void _handleDragCancel() {
+    if (_controller.isDismissed || _controller.isAnimating)
+      return;
+    if (_controller.value < 0.5) {
+      close();
+    } else {
+      open();
+    }
+  }
+
+  final GlobalKey _drawerKey = GlobalKey();
+
+  double get _width {
+    final RenderBox? box = _drawerKey.currentContext?.findRenderObject() as RenderBox?;
+    if (box != null)
+      return box.size.width;
+    return _kWidth; // drawer not being shown currently
+  }
+
+  bool _previouslyOpened = false;
+
+  void _move(DragUpdateDetails details) {
+    double delta = details.primaryDelta! / _width;
+    switch (widget.alignment) {
+      case DrawerAlignment.start:
+        break;
+      case DrawerAlignment.end:
+        delta = -delta;
+        break;
+    }
+    switch (Directionality.of(context)) {
+      case TextDirection.rtl:
+        _controller.value -= delta;
+        break;
+      case TextDirection.ltr:
+        _controller.value += delta;
+        break;
+    }
+
+    final bool opened = _controller.value > 0.5;
+    if (opened != _previouslyOpened && widget.drawerCallback != null)
+      widget.drawerCallback!(opened);
+    _previouslyOpened = opened;
+  }
+
+  void _settle(DragEndDetails details) {
+    if (_controller.isDismissed)
+      return;
+    if (details.velocity.pixelsPerSecond.dx.abs() >= _kMinFlingVelocity) {
+      double visualVelocity = details.velocity.pixelsPerSecond.dx / _width;
+      switch (widget.alignment) {
+        case DrawerAlignment.start:
+          break;
+        case DrawerAlignment.end:
+          visualVelocity = -visualVelocity;
+          break;
+      }
+      switch (Directionality.of(context)) {
+        case TextDirection.rtl:
+          _controller.fling(velocity: -visualVelocity);
+          if (widget.drawerCallback != null)
+            widget.drawerCallback!(visualVelocity < 0.0);
+          break;
+        case TextDirection.ltr:
+          _controller.fling(velocity: visualVelocity);
+          if (widget.drawerCallback != null)
+            widget.drawerCallback!(visualVelocity > 0.0);
+          break;
+      }
+    } else if (_controller.value < 0.5) {
+      close();
+    } else {
+      open();
+    }
+  }
+
+  /// Starts an animation to open the drawer.
+  ///
+  /// Typically called by [ScaffoldState.openDrawer].
+  void open() {
+    _controller.fling(velocity: 1.0);
+    if (widget.drawerCallback != null)
+      widget.drawerCallback!(true);
+  }
+
+  /// Starts an animation to close the drawer.
+  void close() {
+    _controller.fling(velocity: -1.0);
+    if (widget.drawerCallback != null)
+      widget.drawerCallback!(false);
+  }
+
+  late ColorTween _scrimColorTween;
+  final GlobalKey _gestureDetectorKey = GlobalKey();
+
+  ColorTween _buildScrimColorTween() {
+    return ColorTween(begin: Colors.transparent, end: widget.scrimColor ?? Colors.black54);
+  }
+
+  AlignmentDirectional get _drawerOuterAlignment {
+    assert(widget.alignment != null);
+    switch (widget.alignment) {
+      case DrawerAlignment.start:
+        return AlignmentDirectional.centerStart;
+      case DrawerAlignment.end:
+        return AlignmentDirectional.centerEnd;
+    }
+  }
+
+  AlignmentDirectional get _drawerInnerAlignment {
+    assert(widget.alignment != null);
+    switch (widget.alignment) {
+      case DrawerAlignment.start:
+        return AlignmentDirectional.centerEnd;
+      case DrawerAlignment.end:
+        return AlignmentDirectional.centerStart;
+    }
+  }
+
+  Widget _buildDrawer(BuildContext context) {
+    final bool drawerIsStart = widget.alignment == DrawerAlignment.start;
+    final EdgeInsets padding = MediaQuery.of(context).padding;
+    final TextDirection textDirection = Directionality.of(context);
+
+    double? dragAreaWidth = widget.edgeDragWidth;
+    if (widget.edgeDragWidth == null) {
+      switch (textDirection) {
+        case TextDirection.ltr:
+          dragAreaWidth = _kEdgeDragWidth +
+            (drawerIsStart ? padding.left : padding.right);
+          break;
+        case TextDirection.rtl:
+          dragAreaWidth = _kEdgeDragWidth +
+            (drawerIsStart ? padding.right : padding.left);
+          break;
+      }
+    }
+
+    if (_controller.status == AnimationStatus.dismissed) {
+      if (widget.enableOpenDragGesture) {
+        return Align(
+          alignment: _drawerOuterAlignment,
+          child: GestureDetector(
+            key: _gestureDetectorKey,
+            onHorizontalDragUpdate: _move,
+            onHorizontalDragEnd: _settle,
+            behavior: HitTestBehavior.translucent,
+            excludeFromSemantics: true,
+            dragStartBehavior: widget.dragStartBehavior,
+            child: Container(width: dragAreaWidth),
+          ),
+        );
+      } else {
+        return const SizedBox.shrink();
+      }
+    } else {
+      final bool platformHasBackButton;
+      switch (Theme.of(context).platform) {
+        case TargetPlatform.android:
+          platformHasBackButton = true;
+          break;
+        case TargetPlatform.iOS:
+        case TargetPlatform.macOS:
+        case TargetPlatform.fuchsia:
+        case TargetPlatform.linux:
+        case TargetPlatform.windows:
+          platformHasBackButton = false;
+          break;
+      }
+      assert(platformHasBackButton != null);
+      return GestureDetector(
+        key: _gestureDetectorKey,
+        onHorizontalDragDown: _handleDragDown,
+        onHorizontalDragUpdate: _move,
+        onHorizontalDragEnd: _settle,
+        onHorizontalDragCancel: _handleDragCancel,
+        excludeFromSemantics: true,
+        dragStartBehavior: widget.dragStartBehavior,
+        child: RepaintBoundary(
+          child: Stack(
+            children: <Widget>[
+              BlockSemantics(
+                child: ExcludeSemantics(
+                  // On Android, the back button is used to dismiss a modal.
+                  excluding: platformHasBackButton,
+                  child: GestureDetector(
+                    onTap: close,
+                    child: Semantics(
+                      label: MaterialLocalizations.of(context).modalBarrierDismissLabel,
+                      child: MouseRegion(
+                        opaque: true,
+                        child: Container( // The drawer's "scrim"
+                          color: _scrimColorTween.evaluate(_controller),
+                        ),
+                      ),
+                    ),
+                  ),
+                ),
+              ),
+              Align(
+                alignment: _drawerOuterAlignment,
+                child: Align(
+                  alignment: _drawerInnerAlignment,
+                  widthFactor: _controller.value,
+                  child: RepaintBoundary(
+                    child: FocusScope(
+                      key: _drawerKey,
+                      node: _focusScopeNode,
+                      child: widget.child,
+                    ),
+                  ),
+                ),
+              ),
+            ],
+          ),
+        ),
+      );
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterialLocalizations(context));
+    return ListTileTheme(
+      style: ListTileStyle.drawer,
+      child: _buildDrawer(context),
+    );
+  }
+}
diff --git a/lib/src/material/drawer_header.dart b/lib/src/material/drawer_header.dart
new file mode 100644
index 0000000..bf5aa2a
--- /dev/null
+++ b/lib/src/material/drawer_header.dart
@@ -0,0 +1,104 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'debug.dart';
+import 'divider.dart';
+import 'theme.dart';
+
+const double _kDrawerHeaderHeight = 160.0 + 1.0; // bottom edge
+
+/// The top-most region of a material design drawer. The header's [child]
+/// widget, if any, is placed inside a [Container] whose [decoration] can be
+/// passed as an argument, inset by the given [padding].
+///
+/// Part of the material design [Drawer].
+///
+/// Requires one of its ancestors to be a [Material] widget. This condition is
+/// satisfied by putting the [DrawerHeader] in a [Drawer].
+///
+/// See also:
+///
+///  * [UserAccountsDrawerHeader], a variant of [DrawerHeader] that is
+///    specialized for showing user accounts.
+///  * <https://material.io/design/components/navigation-drawer.html>
+class DrawerHeader extends StatelessWidget {
+  /// Creates a material design drawer header.
+  ///
+  /// Requires one of its ancestors to be a [Material] widget.
+  const DrawerHeader({
+    Key? key,
+    this.decoration,
+    this.margin = const EdgeInsets.only(bottom: 8.0),
+    this.padding = const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 8.0),
+    this.duration = const Duration(milliseconds: 250),
+    this.curve = Curves.fastOutSlowIn,
+    required this.child,
+  }) : super(key: key);
+
+  /// Decoration for the main drawer header [Container]; useful for applying
+  /// backgrounds.
+  ///
+  /// This decoration will extend under the system status bar.
+  ///
+  /// If this is changed, it will be animated according to [duration] and [curve].
+  final Decoration? decoration;
+
+  /// The padding by which to inset [child].
+  ///
+  /// The [DrawerHeader] additionally offsets the child by the height of the
+  /// system status bar.
+  ///
+  /// If the child is null, the padding has no effect.
+  final EdgeInsetsGeometry padding;
+
+  /// The margin around the drawer header.
+  final EdgeInsetsGeometry? margin;
+
+  /// The duration for animations of the [decoration].
+  final Duration duration;
+
+  /// The curve for animations of the [decoration].
+  final Curve curve;
+
+  /// A widget to be placed inside the drawer header, inset by the [padding].
+  ///
+  /// This widget will be sized to the size of the header. To position the child
+  /// precisely, consider using an [Align] or [Center] widget.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    assert(debugCheckHasMediaQuery(context));
+    final ThemeData theme = Theme.of(context);
+    final double statusBarHeight = MediaQuery.of(context).padding.top;
+    return Container(
+      height: statusBarHeight + _kDrawerHeaderHeight,
+      margin: margin,
+      decoration: BoxDecoration(
+        border: Border(
+          bottom: Divider.createBorderSide(context),
+        ),
+      ),
+      child: AnimatedContainer(
+        padding: padding.add(EdgeInsets.only(top: statusBarHeight)),
+        decoration: decoration,
+        duration: duration,
+        curve: curve,
+        child: child == null ? null : DefaultTextStyle(
+          style: theme.textTheme.bodyText1!,
+          child: MediaQuery.removePadding(
+            context: context,
+            removeTop: true,
+            child: child!,
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/dropdown.dart b/lib/src/material/dropdown.dart
new file mode 100644
index 0000000..e16f999
--- /dev/null
+++ b/lib/src/material/dropdown.dart
@@ -0,0 +1,1590 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show window;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'button_theme.dart';
+import 'colors.dart';
+import 'constants.dart';
+import 'debug.dart';
+import 'icons.dart';
+import 'ink_well.dart';
+import 'input_decorator.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'scrollbar.dart';
+import 'shadows.dart';
+import 'theme.dart';
+
+const Duration _kDropdownMenuDuration = Duration(milliseconds: 300);
+const double _kMenuItemHeight = kMinInteractiveDimension;
+const double _kDenseButtonHeight = 24.0;
+const EdgeInsets _kMenuItemPadding = EdgeInsets.symmetric(horizontal: 16.0);
+const EdgeInsetsGeometry _kAlignedButtonPadding = EdgeInsetsDirectional.only(start: 16.0, end: 4.0);
+const EdgeInsets _kUnalignedButtonPadding = EdgeInsets.zero;
+const EdgeInsets _kAlignedMenuMargin = EdgeInsets.zero;
+const EdgeInsetsGeometry _kUnalignedMenuMargin = EdgeInsetsDirectional.only(start: 16.0, end: 24.0);
+
+/// A builder to customize dropdown buttons.
+///
+/// Used by [DropdownButton.selectedItemBuilder].
+typedef DropdownButtonBuilder = List<Widget> Function(BuildContext context);
+
+class _DropdownMenuPainter extends CustomPainter {
+  _DropdownMenuPainter({
+    this.color,
+    this.elevation,
+    this.selectedIndex,
+    required this.resize,
+    required this.getSelectedItemOffset,
+  }) : _painter = BoxDecoration(
+         // If you add an image here, you must provide a real
+         // configuration in the paint() function and you must provide some sort
+         // of onChanged callback here.
+         color: color,
+         borderRadius: BorderRadius.circular(2.0),
+         boxShadow: kElevationToShadow[elevation],
+       ).createBoxPainter(),
+       super(repaint: resize);
+
+  final Color? color;
+  final int? elevation;
+  final int? selectedIndex;
+  final Animation<double> resize;
+  final ValueGetter<double> getSelectedItemOffset;
+  final BoxPainter _painter;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final double selectedItemOffset = getSelectedItemOffset();
+    final Tween<double> top = Tween<double>(
+      begin: selectedItemOffset.clamp(0.0, size.height - _kMenuItemHeight),
+      end: 0.0,
+    );
+
+    final Tween<double> bottom = Tween<double>(
+      begin: (top.begin! + _kMenuItemHeight).clamp(_kMenuItemHeight, size.height),
+      end: size.height,
+    );
+
+    final Rect rect = Rect.fromLTRB(0.0, top.evaluate(resize), size.width, bottom.evaluate(resize));
+
+    _painter.paint(canvas, rect.topLeft, ImageConfiguration(size: rect.size));
+  }
+
+  @override
+  bool shouldRepaint(_DropdownMenuPainter oldPainter) {
+    return oldPainter.color != color
+        || oldPainter.elevation != elevation
+        || oldPainter.selectedIndex != selectedIndex
+        || oldPainter.resize != resize;
+  }
+}
+
+// Do not use the platform-specific default scroll configuration.
+// Dropdown menus should never overscroll or display an overscroll indicator.
+class _DropdownScrollBehavior extends ScrollBehavior {
+  const _DropdownScrollBehavior();
+
+  @override
+  TargetPlatform getPlatform(BuildContext context) => Theme.of(context).platform;
+
+  @override
+  Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) => child;
+
+  @override
+  ScrollPhysics getScrollPhysics(BuildContext context) => const ClampingScrollPhysics();
+}
+
+// The widget that is the button wrapping the menu items.
+class _DropdownMenuItemButton<T> extends StatefulWidget {
+  const _DropdownMenuItemButton({
+    Key? key,
+    this.padding,
+    required this.route,
+    required this.buttonRect,
+    required this.constraints,
+    required this.itemIndex,
+  }) : super(key: key);
+
+  final _DropdownRoute<T> route;
+  final EdgeInsets? padding;
+  final Rect? buttonRect;
+  final BoxConstraints? constraints;
+  final int itemIndex;
+
+  @override
+  _DropdownMenuItemButtonState<T> createState() => _DropdownMenuItemButtonState<T>();
+}
+
+class _DropdownMenuItemButtonState<T> extends State<_DropdownMenuItemButton<T>> {
+  void _handleFocusChange(bool focused) {
+    final bool inTraditionalMode;
+    switch (FocusManager.instance.highlightMode) {
+      case FocusHighlightMode.touch:
+        inTraditionalMode = false;
+        break;
+      case FocusHighlightMode.traditional:
+        inTraditionalMode = true;
+        break;
+    }
+
+    if (focused && inTraditionalMode) {
+      final _MenuLimits menuLimits = widget.route.getMenuLimits(
+          widget.buttonRect!, widget.constraints!.maxHeight, widget.itemIndex);
+      widget.route.scrollController!.animateTo(
+        menuLimits.scrollOffset,
+        curve: Curves.easeInOut,
+        duration: const Duration(milliseconds: 100),
+      );
+    }
+  }
+
+  void _handleOnTap() {
+    final DropdownMenuItem<T> dropdownMenuItem = widget.route.items[widget.itemIndex].item!;
+
+    if (dropdownMenuItem.onTap != null) {
+      dropdownMenuItem.onTap!();
+    }
+
+    Navigator.pop(
+      context,
+      _DropdownRouteResult<T>(dropdownMenuItem.value),
+    );
+  }
+
+  static final Map<LogicalKeySet, Intent> _webShortcuts =<LogicalKeySet, Intent>{
+    LogicalKeySet(LogicalKeyboardKey.enter): const ActivateIntent(),
+  };
+
+  @override
+  Widget build(BuildContext context) {
+    final CurvedAnimation opacity;
+    final double unit = 0.5 / (widget.route.items.length + 1.5);
+    if (widget.itemIndex == widget.route.selectedIndex) {
+      opacity = CurvedAnimation(parent: widget.route.animation!, curve: const Threshold(0.0));
+    } else {
+      final double start = (0.5 + (widget.itemIndex + 1) * unit).clamp(0.0, 1.0);
+      final double end = (start + 1.5 * unit).clamp(0.0, 1.0);
+      opacity = CurvedAnimation(parent: widget.route.animation!, curve: Interval(start, end));
+    }
+    Widget child = FadeTransition(
+      opacity: opacity,
+      child: InkWell(
+        autofocus: widget.itemIndex == widget.route.selectedIndex,
+        child: Container(
+          padding: widget.padding,
+          child: widget.route.items[widget.itemIndex],
+        ),
+        onTap: _handleOnTap,
+        onFocusChange: _handleFocusChange,
+      ),
+    );
+    if (kIsWeb) {
+      // On the web, enter doesn't select things, *except* in a <select>
+      // element, which is what a dropdown emulates.
+      child = Shortcuts(
+        shortcuts: _webShortcuts,
+        child: child,
+      );
+    }
+    return child;
+  }
+}
+
+class _DropdownMenu<T> extends StatefulWidget {
+  const _DropdownMenu({
+    Key? key,
+    this.padding,
+    required this.route,
+    this.buttonRect,
+    this.constraints,
+    this.dropdownColor,
+  }) : super(key: key);
+
+  final _DropdownRoute<T> route;
+  final EdgeInsets? padding;
+  final Rect? buttonRect;
+  final BoxConstraints? constraints;
+  final Color? dropdownColor;
+
+  @override
+  _DropdownMenuState<T> createState() => _DropdownMenuState<T>();
+}
+
+class _DropdownMenuState<T> extends State<_DropdownMenu<T>> {
+  late CurvedAnimation _fadeOpacity;
+  late CurvedAnimation _resize;
+
+  @override
+  void initState() {
+    super.initState();
+    // We need to hold these animations as state because of their curve
+    // direction. When the route's animation reverses, if we were to recreate
+    // the CurvedAnimation objects in build, we'd lose
+    // CurvedAnimation._curveDirection.
+    _fadeOpacity = CurvedAnimation(
+      parent: widget.route.animation!,
+      curve: const Interval(0.0, 0.25),
+      reverseCurve: const Interval(0.75, 1.0),
+    );
+    _resize = CurvedAnimation(
+      parent: widget.route.animation!,
+      curve: const Interval(0.25, 0.5),
+      reverseCurve: const Threshold(0.0),
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // The menu is shown in three stages (unit timing in brackets):
+    // [0s - 0.25s] - Fade in a rect-sized menu container with the selected item.
+    // [0.25s - 0.5s] - Grow the otherwise empty menu container from the center
+    //   until it's big enough for as many items as we're going to show.
+    // [0.5s - 1.0s] Fade in the remaining visible items from top to bottom.
+    //
+    // When the menu is dismissed we just fade the entire thing out
+    // in the first 0.25s.
+    assert(debugCheckHasMaterialLocalizations(context));
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final _DropdownRoute<T> route = widget.route;
+    final List<Widget> children = <Widget>[
+      for (int itemIndex = 0; itemIndex < route.items.length; ++itemIndex)
+        _DropdownMenuItemButton<T>(
+          route: widget.route,
+          padding: widget.padding,
+          buttonRect: widget.buttonRect,
+          constraints: widget.constraints,
+          itemIndex: itemIndex,
+        ),
+      ];
+
+    return FadeTransition(
+      opacity: _fadeOpacity,
+      child: CustomPaint(
+        painter: _DropdownMenuPainter(
+          color: widget.dropdownColor ?? Theme.of(context).canvasColor,
+          elevation: route.elevation,
+          selectedIndex: route.selectedIndex,
+          resize: _resize,
+          // This offset is passed as a callback, not a value, because it must
+          // be retrieved at paint time (after layout), not at build time.
+          getSelectedItemOffset: () => route.getItemOffset(route.selectedIndex),
+        ),
+        child: Semantics(
+          scopesRoute: true,
+          namesRoute: true,
+          explicitChildNodes: true,
+          label: localizations.popupMenuLabel,
+          child: Material(
+            type: MaterialType.transparency,
+            textStyle: route.style,
+            child: ScrollConfiguration(
+              behavior: const _DropdownScrollBehavior(),
+              child: Scrollbar(
+                child: ListView(
+                  controller: widget.route.scrollController,
+                  padding: kMaterialListPadding,
+                  shrinkWrap: true,
+                  children: children,
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+class _DropdownMenuRouteLayout<T> extends SingleChildLayoutDelegate {
+  _DropdownMenuRouteLayout({
+    required this.buttonRect,
+    required this.route,
+    required this.textDirection,
+  });
+
+  final Rect buttonRect;
+  final _DropdownRoute<T> route;
+  final TextDirection? textDirection;
+
+  @override
+  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
+    // The maximum height of a simple menu should be one or more rows less than
+    // the view height. This ensures a tappable area outside of the simple menu
+    // with which to dismiss the menu.
+    //   -- https://material.io/design/components/menus.html#usage
+    final double maxHeight = math.max(0.0, constraints.maxHeight - 2 * _kMenuItemHeight);
+    // The width of a menu should be at most the view width. This ensures that
+    // the menu does not extend past the left and right edges of the screen.
+    final double width = math.min(constraints.maxWidth, buttonRect.width);
+    return BoxConstraints(
+      minWidth: width,
+      maxWidth: width,
+      minHeight: 0.0,
+      maxHeight: maxHeight,
+    );
+  }
+
+  @override
+  Offset getPositionForChild(Size size, Size childSize) {
+    final _MenuLimits menuLimits = route.getMenuLimits(buttonRect, size.height, route.selectedIndex);
+
+    assert(() {
+      final Rect container = Offset.zero & size;
+      if (container.intersect(buttonRect) == buttonRect) {
+        // If the button was entirely on-screen, then verify
+        // that the menu is also on-screen.
+        // If the button was a bit off-screen, then, oh well.
+        assert(menuLimits.top >= 0.0);
+        assert(menuLimits.top + menuLimits.height <= size.height);
+      }
+      return true;
+    }());
+    assert(textDirection != null);
+    final double left;
+    switch (textDirection!) {
+      case TextDirection.rtl:
+        left = buttonRect.right.clamp(0.0, size.width) - childSize.width;
+        break;
+      case TextDirection.ltr:
+        left = buttonRect.left.clamp(0.0, size.width - childSize.width);
+        break;
+    }
+
+    return Offset(left, menuLimits.top);
+  }
+
+  @override
+  bool shouldRelayout(_DropdownMenuRouteLayout<T> oldDelegate) {
+    return buttonRect != oldDelegate.buttonRect || textDirection != oldDelegate.textDirection;
+  }
+}
+
+// We box the return value so that the return value can be null. Otherwise,
+// canceling the route (which returns null) would get confused with actually
+// returning a real null value.
+@immutable
+class _DropdownRouteResult<T> {
+  const _DropdownRouteResult(this.result);
+
+  final T? result;
+
+  @override
+  bool operator ==(Object other) {
+    return other is _DropdownRouteResult<T>
+        && other.result == result;
+  }
+
+  @override
+  int get hashCode => result.hashCode;
+}
+
+class _MenuLimits {
+  const _MenuLimits(this.top, this.bottom, this.height, this.scrollOffset);
+  final double top;
+  final double bottom;
+  final double height;
+  final double scrollOffset;
+}
+
+class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
+  _DropdownRoute({
+    required this.items,
+    required this.padding,
+    required this.buttonRect,
+    required this.selectedIndex,
+    this.elevation = 8,
+    required this.capturedThemes,
+    required this.style,
+    this.barrierLabel,
+    this.itemHeight,
+    this.dropdownColor,
+  }) : assert(style != null),
+       itemHeights = List<double>.filled(items.length, itemHeight ?? kMinInteractiveDimension);
+
+  final List<_MenuItem<T>> items;
+  final EdgeInsetsGeometry padding;
+  final Rect buttonRect;
+  final int selectedIndex;
+  final int elevation;
+  final CapturedThemes capturedThemes;
+  final TextStyle style;
+  final double? itemHeight;
+  final Color? dropdownColor;
+
+  final List<double> itemHeights;
+  ScrollController? scrollController;
+
+  @override
+  Duration get transitionDuration => _kDropdownMenuDuration;
+
+  @override
+  bool get barrierDismissible => true;
+
+  @override
+  Color? get barrierColor => null;
+
+  @override
+  final String? barrierLabel;
+
+  @override
+  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
+    return LayoutBuilder(
+      builder: (BuildContext context, BoxConstraints constraints) {
+        return _DropdownRoutePage<T>(
+          route: this,
+          constraints: constraints,
+          items: items,
+          padding: padding,
+          buttonRect: buttonRect,
+          selectedIndex: selectedIndex,
+          elevation: elevation,
+          capturedThemes: capturedThemes,
+          style: style,
+          dropdownColor: dropdownColor,
+        );
+      }
+    );
+  }
+
+  void _dismiss() {
+    if (isActive) {
+      navigator?.removeRoute(this);
+    }
+  }
+
+  double getItemOffset(int index) {
+    double offset = kMaterialListPadding.top;
+    if (items.isNotEmpty && index > 0) {
+      assert(items.length == itemHeights.length);
+      offset += itemHeights
+        .sublist(0, index)
+        .reduce((double total, double height) => total + height);
+    }
+    return offset;
+  }
+
+  // Returns the vertical extent of the menu and the initial scrollOffset
+  // for the ListView that contains the menu items. The vertical center of the
+  // selected item is aligned with the button's vertical center, as far as
+  // that's possible given availableHeight.
+  _MenuLimits getMenuLimits(Rect buttonRect, double availableHeight, int index) {
+    final double maxMenuHeight = availableHeight - 2.0 * _kMenuItemHeight;
+    final double buttonTop = buttonRect.top;
+    final double buttonBottom = math.min(buttonRect.bottom, availableHeight);
+    final double selectedItemOffset = getItemOffset(index);
+
+    // If the button is placed on the bottom or top of the screen, its top or
+    // bottom may be less than [_kMenuItemHeight] from the edge of the screen.
+    // In this case, we want to change the menu limits to align with the top
+    // or bottom edge of the button.
+    final double topLimit = math.min(_kMenuItemHeight, buttonTop);
+    final double bottomLimit = math.max(availableHeight - _kMenuItemHeight, buttonBottom);
+
+    double menuTop = (buttonTop - selectedItemOffset) - (itemHeights[selectedIndex] - buttonRect.height) / 2.0;
+    double preferredMenuHeight = kMaterialListPadding.vertical;
+    if (items.isNotEmpty)
+      preferredMenuHeight += itemHeights.reduce((double total, double height) => total + height);
+
+    // If there are too many elements in the menu, we need to shrink it down
+    // so it is at most the maxMenuHeight.
+    final double menuHeight = math.min(maxMenuHeight, preferredMenuHeight);
+    double menuBottom = menuTop + menuHeight;
+
+    // If the computed top or bottom of the menu are outside of the range
+    // specified, we need to bring them into range. If the item height is larger
+    // than the button height and the button is at the very bottom or top of the
+    // screen, the menu will be aligned with the bottom or top of the button
+    // respectively.
+    if (menuTop < topLimit)
+      menuTop = math.min(buttonTop, topLimit);
+
+    if (menuBottom > bottomLimit) {
+      menuBottom = math.max(buttonBottom, bottomLimit);
+      menuTop = menuBottom - menuHeight;
+    }
+
+    // If all of the menu items will not fit within availableHeight then
+    // compute the scroll offset that will line the selected menu item up
+    // with the select item. This is only done when the menu is first
+    // shown - subsequently we leave the scroll offset where the user left
+    // it. This scroll offset is only accurate for fixed height menu items
+    // (the default).
+    final double scrollOffset = preferredMenuHeight <= maxMenuHeight ? 0 :
+      math.max(0.0, selectedItemOffset - (buttonTop - menuTop));
+
+    return _MenuLimits(menuTop, menuBottom, menuHeight, scrollOffset);
+  }
+}
+
+class _DropdownRoutePage<T> extends StatelessWidget {
+  const _DropdownRoutePage({
+    Key? key,
+    required this.route,
+    required this.constraints,
+    this.items,
+    required this.padding,
+    required this.buttonRect,
+    required this.selectedIndex,
+    this.elevation = 8,
+    required this.capturedThemes,
+    this.style,
+    required this.dropdownColor,
+  }) : super(key: key);
+
+  final _DropdownRoute<T> route;
+  final BoxConstraints constraints;
+  final List<_MenuItem<T>>? items;
+  final EdgeInsetsGeometry padding;
+  final Rect buttonRect;
+  final int selectedIndex;
+  final int elevation;
+  final CapturedThemes capturedThemes;
+  final TextStyle? style;
+  final Color? dropdownColor;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasDirectionality(context));
+
+    // Computing the initialScrollOffset now, before the items have been laid
+    // out. This only works if the item heights are effectively fixed, i.e. either
+    // DropdownButton.itemHeight is specified or DropdownButton.itemHeight is null
+    // and all of the items' intrinsic heights are less than kMinInteractiveDimension.
+    // Otherwise the initialScrollOffset is just a rough approximation based on
+    // treating the items as if their heights were all equal to kMinInteractveDimension.
+    if (route.scrollController == null) {
+      final _MenuLimits menuLimits = route.getMenuLimits(buttonRect, constraints.maxHeight, selectedIndex);
+      route.scrollController = ScrollController(initialScrollOffset: menuLimits.scrollOffset);
+    }
+
+    final TextDirection? textDirection = Directionality.maybeOf(context);
+    final Widget menu = _DropdownMenu<T>(
+      route: route,
+      padding: padding.resolve(textDirection),
+      buttonRect: buttonRect,
+      constraints: constraints,
+      dropdownColor: dropdownColor,
+    );
+
+    return MediaQuery.removePadding(
+      context: context,
+      removeTop: true,
+      removeBottom: true,
+      removeLeft: true,
+      removeRight: true,
+      child: Builder(
+        builder: (BuildContext context) {
+          return CustomSingleChildLayout(
+            delegate: _DropdownMenuRouteLayout<T>(
+              buttonRect: buttonRect,
+              route: route,
+              textDirection: textDirection,
+            ),
+            child: capturedThemes.wrap(menu),
+          );
+        },
+      ),
+    );
+  }
+}
+
+// This widget enables _DropdownRoute to look up the sizes of
+// each menu item. These sizes are used to compute the offset of the selected
+// item so that _DropdownRoutePage can align the vertical center of the
+// selected item lines up with the vertical center of the dropdown button,
+// as closely as possible.
+class _MenuItem<T> extends SingleChildRenderObjectWidget {
+  const _MenuItem({
+    Key? key,
+    required this.onLayout,
+    required this.item,
+  }) : assert(onLayout != null), super(key: key, child: item);
+
+  final ValueChanged<Size> onLayout;
+  final DropdownMenuItem<T>? item;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _RenderMenuItem(onLayout);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, covariant _RenderMenuItem renderObject) {
+    renderObject.onLayout = onLayout;
+  }
+}
+
+class _RenderMenuItem extends RenderProxyBox {
+  _RenderMenuItem(this.onLayout, [RenderBox? child]) : assert(onLayout != null), super(child);
+
+  ValueChanged<Size> onLayout;
+
+  @override
+  void performLayout() {
+    super.performLayout();
+    onLayout(size);
+  }
+}
+
+// The container widget for a menu item created by a [DropdownButton]. It
+// provides the default configuration for [DropdownMenuItem]s, as well as a
+// [DropdownButton]'s hint and disabledHint widgets.
+class _DropdownMenuItemContainer extends StatelessWidget {
+  /// Creates an item for a dropdown menu.
+  ///
+  /// The [child] argument is required.
+  const _DropdownMenuItemContainer({
+    Key? key,
+    required this.child,
+  }) : assert(child != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// Typically a [Text] widget.
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      constraints: const BoxConstraints(minHeight: _kMenuItemHeight),
+      alignment: AlignmentDirectional.centerStart,
+      child: child,
+    );
+  }
+}
+
+/// An item in a menu created by a [DropdownButton].
+///
+/// The type `T` is the type of the value the entry represents. All the entries
+/// in a given menu must represent values with consistent types.
+class DropdownMenuItem<T> extends _DropdownMenuItemContainer {
+  /// Creates an item for a dropdown menu.
+  ///
+  /// The [child] argument is required.
+  const DropdownMenuItem({
+    Key? key,
+    this.onTap,
+    this.value,
+    required Widget child,
+  }) : assert(child != null),
+       super(key: key, child: child);
+
+  /// Called when the dropdown menu item is tapped.
+  final VoidCallback? onTap;
+
+  /// The value to return if the user selects this menu item.
+  ///
+  /// Eventually returned in a call to [DropdownButton.onChanged].
+  final T? value;
+}
+
+/// An inherited widget that causes any descendant [DropdownButton]
+/// widgets to not include their regular underline.
+///
+/// This is used by [DataTable] to remove the underline from any
+/// [DropdownButton] widgets placed within material data tables, as
+/// required by the material design specification.
+class DropdownButtonHideUnderline extends InheritedWidget {
+  /// Creates a [DropdownButtonHideUnderline]. A non-null [child] must
+  /// be given.
+  const DropdownButtonHideUnderline({
+    Key? key,
+    required Widget child,
+  }) : assert(child != null),
+       super(key: key, child: child);
+
+  /// Returns whether the underline of [DropdownButton] widgets should
+  /// be hidden.
+  static bool at(BuildContext context) {
+    return context.dependOnInheritedWidgetOfExactType<DropdownButtonHideUnderline>() != null;
+  }
+
+  @override
+  bool updateShouldNotify(DropdownButtonHideUnderline oldWidget) => false;
+}
+
+/// A material design button for selecting from a list of items.
+///
+/// A dropdown button lets the user select from a number of items. The button
+/// shows the currently selected item as well as an arrow that opens a menu for
+/// selecting another item.
+///
+/// The type `T` is the type of the [value] that each dropdown item represents.
+/// All the entries in a given menu must represent values with consistent types.
+/// Typically, an enum is used. Each [DropdownMenuItem] in [items] must be
+/// specialized with that same type argument.
+///
+/// The [onChanged] callback should update a state variable that defines the
+/// dropdown's value. It should also call [State.setState] to rebuild the
+/// dropdown with the new value.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+///
+/// This sample shows a `DropdownButton` with a large arrow icon,
+/// purple text style, and bold purple underline, whose value is one of "One",
+/// "Two", "Free", or "Four".
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/dropdown_button.png)
+///
+/// ```dart
+/// String dropdownValue = 'One';
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return DropdownButton<String>(
+///     value: dropdownValue,
+///     icon: Icon(Icons.arrow_downward),
+///     iconSize: 24,
+///     elevation: 16,
+///     style: TextStyle(
+///       color: Colors.deepPurple
+///     ),
+///     underline: Container(
+///       height: 2,
+///       color: Colors.deepPurpleAccent,
+///     ),
+///     onChanged: (String newValue) {
+///       setState(() {
+///         dropdownValue = newValue;
+///       });
+///     },
+///     items: <String>['One', 'Two', 'Free', 'Four']
+///       .map<DropdownMenuItem<String>>((String value) {
+///         return DropdownMenuItem<String>(
+///           value: value,
+///           child: Text(value),
+///         );
+///       })
+///       .toList(),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// If the [onChanged] callback is null or the list of [items] is null
+/// then the dropdown button will be disabled, i.e. its arrow will be
+/// displayed in grey and it will not respond to input. A disabled button
+/// will display the [disabledHint] widget if it is non-null. However, if
+/// [disabledHint] is null and [hint] is non-null, the [hint] widget will
+/// instead be displayed.
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// See also:
+///
+///  * [DropdownMenuItem], the class used to represent the [items].
+///  * [DropdownButtonHideUnderline], which prevents its descendant dropdown buttons
+///    from displaying their underlines.
+///  * [ElevatedButton], [TextButton], ordinary buttons that trigger a single action.
+///  * <https://material.io/design/components/menus.html#dropdown-menu>
+class DropdownButton<T> extends StatefulWidget {
+  /// Creates a dropdown button.
+  ///
+  /// The [items] must have distinct values. If [value] isn't null then it
+  /// must be equal to one of the [DropdownMenuItem] values. If [items] or
+  /// [onChanged] is null, the button will be disabled, the down arrow
+  /// will be greyed out.
+  ///
+  /// If [value] is null and the button is enabled, [hint] will be displayed
+  /// if it is non-null.
+  ///
+  /// If [value] is null and the button is disabled, [disabledHint] will be displayed
+  /// if it is non-null. If [disabledHint] is null, then [hint] will be displayed
+  /// if it is non-null.
+  ///
+  /// The [elevation] and [iconSize] arguments must not be null (they both have
+  /// defaults, so do not need to be specified). The boolean [isDense] and
+  /// [isExpanded] arguments must not be null.
+  ///
+  /// The [autofocus] argument must not be null.
+  ///
+  /// The [dropdownColor] argument specifies the background color of the
+  /// dropdown when it is open. If it is null, the current theme's
+  /// [ThemeData.canvasColor] will be used instead.
+  DropdownButton({
+    Key? key,
+    required this.items,
+    this.selectedItemBuilder,
+    this.value,
+    this.hint,
+    this.disabledHint,
+    this.onChanged,
+    this.onTap,
+    this.elevation = 8,
+    this.style,
+    this.underline,
+    this.icon,
+    this.iconDisabledColor,
+    this.iconEnabledColor,
+    this.iconSize = 24.0,
+    this.isDense = false,
+    this.isExpanded = false,
+    this.itemHeight = kMinInteractiveDimension,
+    this.focusColor,
+    this.focusNode,
+    this.autofocus = false,
+    this.dropdownColor,
+    // When adding new arguments, consider adding similar arguments to
+    // DropdownButtonFormField.
+  }) : assert(items == null || items.isEmpty || value == null ||
+              items.where((DropdownMenuItem<T> item) {
+                return item.value == value;
+              }).length == 1,
+                "There should be exactly one item with [DropdownButton]'s value: "
+                '$value. \n'
+                'Either zero or 2 or more [DropdownMenuItem]s were detected '
+                'with the same value',
+              ),
+       assert(elevation != null),
+       assert(iconSize != null),
+       assert(isDense != null),
+       assert(isExpanded != null),
+       assert(autofocus != null),
+       assert(itemHeight == null || itemHeight >=  kMinInteractiveDimension),
+       super(key: key);
+
+  /// The list of items the user can select.
+  ///
+  /// If the [onChanged] callback is null or the list of items is null
+  /// then the dropdown button will be disabled, i.e. its arrow will be
+  /// displayed in grey and it will not respond to input.
+  final List<DropdownMenuItem<T>>? items;
+
+  /// The value of the currently selected [DropdownMenuItem].
+  ///
+  /// If [value] is null and the button is enabled, [hint] will be displayed
+  /// if it is non-null.
+  ///
+  /// If [value] is null and the button is disabled, [disabledHint] will be displayed
+  /// if it is non-null. If [disabledHint] is null, then [hint] will be displayed
+  /// if it is non-null.
+  final T? value;
+
+  /// A placeholder widget that is displayed by the dropdown button.
+  ///
+  /// If [value] is null and the dropdown is enabled ([items] and [onChanged] are non-null),
+  /// this widget is displayed as a placeholder for the dropdown button's value.
+  ///
+  /// If [value] is null and the dropdown is disabled and [disabledHint] is null,
+  /// this widget is used as the placeholder.
+  final Widget? hint;
+
+  /// A preferred placeholder widget that is displayed when the dropdown is disabled.
+  ///
+  /// If [value] is null, the dropdown is disabled ([items] or [onChanged] is null),
+  /// this widget is displayed as a placeholder for the dropdown button's value.
+  final Widget? disabledHint;
+
+  /// {@template flutter.material.dropdownButton.onChanged}
+  /// Called when the user selects an item.
+  ///
+  /// If the [onChanged] callback is null or the list of [DropdownButton.items]
+  /// is null then the dropdown button will be disabled, i.e. its arrow will be
+  /// displayed in grey and it will not respond to input. A disabled button
+  /// will display the [DropdownButton.disabledHint] widget if it is non-null.
+  /// If [DropdownButton.disabledHint] is also null but [DropdownButton.hint] is
+  /// non-null, [DropdownButton.hint] will instead be displayed.
+  /// {@endtemplate}
+  final ValueChanged<T?>? onChanged;
+
+  /// Called when the dropdown button is tapped.
+  ///
+  /// This is distinct from [onChanged], which is called when the user
+  /// selects an item from the dropdown.
+  ///
+  /// The callback will not be invoked if the dropdown button is disabled.
+  final VoidCallback? onTap;
+
+  /// A builder to customize the dropdown buttons corresponding to the
+  /// [DropdownMenuItem]s in [items].
+  ///
+  /// When a [DropdownMenuItem] is selected, the widget that will be displayed
+  /// from the list corresponds to the [DropdownMenuItem] of the same index
+  /// in [items].
+  ///
+  /// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+  ///
+  /// This sample shows a `DropdownButton` with a button with [Text] that
+  /// corresponds to but is unique from [DropdownMenuItem].
+  ///
+  /// ```dart
+  /// final List<String> items = <String>['1','2','3'];
+  /// String selectedItem = '1';
+  ///
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return Padding(
+  ///     padding: const EdgeInsets.symmetric(horizontal: 12.0),
+  ///     child: DropdownButton<String>(
+  ///       value: selectedItem,
+  ///       onChanged: (String string) => setState(() => selectedItem = string),
+  ///       selectedItemBuilder: (BuildContext context) {
+  ///         return items.map<Widget>((String item) {
+  ///           return Text(item);
+  ///         }).toList();
+  ///       },
+  ///       items: items.map((String item) {
+  ///         return DropdownMenuItem<String>(
+  ///           child: Text('Log $item'),
+  ///           value: item,
+  ///         );
+  ///       }).toList(),
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// If this callback is null, the [DropdownMenuItem] from [items]
+  /// that matches [value] will be displayed.
+  final DropdownButtonBuilder? selectedItemBuilder;
+
+  /// The z-coordinate at which to place the menu when open.
+  ///
+  /// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12,
+  /// 16, and 24. See [kElevationToShadow].
+  ///
+  /// Defaults to 8, the appropriate elevation for dropdown buttons.
+  final int elevation;
+
+  /// The text style to use for text in the dropdown button and the dropdown
+  /// menu that appears when you tap the button.
+  ///
+  /// To use a separate text style for selected item when it's displayed within
+  /// the dropdown button, consider using [selectedItemBuilder].
+  ///
+  /// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+  ///
+  /// This sample shows a `DropdownButton` with a dropdown button text style
+  /// that is different than its menu items.
+  ///
+  /// ```dart
+  /// List<String> options = <String>['One', 'Two', 'Free', 'Four'];
+  /// String dropdownValue = 'One';
+  ///
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return Container(
+  ///     alignment: Alignment.center,
+  ///     color: Colors.blue,
+  ///     child: DropdownButton<String>(
+  ///       value: dropdownValue,
+  ///       onChanged: (String newValue) {
+  ///         setState(() {
+  ///           dropdownValue = newValue;
+  ///         });
+  ///       },
+  ///       style: TextStyle(color: Colors.blue),
+  ///       selectedItemBuilder: (BuildContext context) {
+  ///         return options.map((String value) {
+  ///           return Text(
+  ///             dropdownValue,
+  ///             style: TextStyle(color: Colors.white),
+  ///           );
+  ///         }).toList();
+  ///       },
+  ///       items: options.map<DropdownMenuItem<String>>((String value) {
+  ///         return DropdownMenuItem<String>(
+  ///           value: value,
+  ///           child: Text(value),
+  ///         );
+  ///       }).toList(),
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// Defaults to the [TextTheme.subtitle1] value of the current
+  /// [ThemeData.textTheme] of the current [Theme].
+  final TextStyle? style;
+
+  /// The widget to use for drawing the drop-down button's underline.
+  ///
+  /// Defaults to a 0.0 width bottom border with color 0xFFBDBDBD.
+  final Widget? underline;
+
+  /// The widget to use for the drop-down button's icon.
+  ///
+  /// Defaults to an [Icon] with the [Icons.arrow_drop_down] glyph.
+  final Widget? icon;
+
+  /// The color of any [Icon] descendant of [icon] if this button is disabled,
+  /// i.e. if [onChanged] is null.
+  ///
+  /// Defaults to [MaterialColor.shade400] of [Colors.grey] when the theme's
+  /// [ThemeData.brightness] is [Brightness.light] and to
+  /// [Colors.white10] when it is [Brightness.dark]
+  final Color? iconDisabledColor;
+
+  /// The color of any [Icon] descendant of [icon] if this button is enabled,
+  /// i.e. if [onChanged] is defined.
+  ///
+  /// Defaults to [MaterialColor.shade700] of [Colors.grey] when the theme's
+  /// [ThemeData.brightness] is [Brightness.light] and to
+  /// [Colors.white70] when it is [Brightness.dark]
+  final Color? iconEnabledColor;
+
+  /// The size to use for the drop-down button's down arrow icon button.
+  ///
+  /// Defaults to 24.0.
+  final double iconSize;
+
+  /// Reduce the button's height.
+  ///
+  /// By default this button's height is the same as its menu items' heights.
+  /// If isDense is true, the button's height is reduced by about half. This
+  /// can be useful when the button is embedded in a container that adds
+  /// its own decorations, like [InputDecorator].
+  final bool isDense;
+
+  /// Set the dropdown's inner contents to horizontally fill its parent.
+  ///
+  /// By default this button's inner width is the minimum size of its contents.
+  /// If [isExpanded] is true, the inner width is expanded to fill its
+  /// surrounding container.
+  final bool isExpanded;
+
+  /// If null, then the menu item heights will vary according to each menu item's
+  /// intrinsic height.
+  ///
+  /// The default value is [kMinInteractiveDimension], which is also the minimum
+  /// height for menu items.
+  ///
+  /// If this value is null and there isn't enough vertical room for the menu,
+  /// then the menu's initial scroll offset may not align the selected item with
+  /// the dropdown button. That's because, in this case, the initial scroll
+  /// offset is computed as if all of the menu item heights were
+  /// [kMinInteractiveDimension].
+  final double? itemHeight;
+
+  /// The color for the button's [Material] when it has the input focus.
+  final Color? focusColor;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  /// The background color of the dropdown.
+  ///
+  /// If it is not provided, the theme's [ThemeData.canvasColor] will be used
+  /// instead.
+  final Color? dropdownColor;
+
+  @override
+  _DropdownButtonState<T> createState() => _DropdownButtonState<T>();
+}
+
+class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindingObserver {
+  int? _selectedIndex;
+  _DropdownRoute<T>? _dropdownRoute;
+  Orientation? _lastOrientation;
+  FocusNode? _internalNode;
+  FocusNode? get focusNode => widget.focusNode ?? _internalNode;
+  bool _hasPrimaryFocus = false;
+  late Map<Type, Action<Intent>> _actionMap;
+  late FocusHighlightMode _focusHighlightMode;
+
+  // Only used if needed to create _internalNode.
+  FocusNode _createFocusNode() {
+    return FocusNode(debugLabel: '${widget.runtimeType}');
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _updateSelectedIndex();
+    if (widget.focusNode == null) {
+      _internalNode ??= _createFocusNode();
+    }
+    _actionMap = <Type, Action<Intent>>{
+      ActivateIntent: CallbackAction<ActivateIntent>(
+        onInvoke: (ActivateIntent intent) => _handleTap(),
+      ),
+      ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(
+        onInvoke: (ButtonActivateIntent intent) => _handleTap(),
+      ),
+    };
+    focusNode!.addListener(_handleFocusChanged);
+    final FocusManager focusManager = WidgetsBinding.instance!.focusManager;
+    _focusHighlightMode = focusManager.highlightMode;
+    focusManager.addHighlightModeListener(_handleFocusHighlightModeChange);
+  }
+
+  @override
+  void dispose() {
+    WidgetsBinding.instance!.removeObserver(this);
+    _removeDropdownRoute();
+    WidgetsBinding.instance!.focusManager.removeHighlightModeListener(_handleFocusHighlightModeChange);
+    focusNode!.removeListener(_handleFocusChanged);
+    _internalNode?.dispose();
+    super.dispose();
+  }
+
+  void _removeDropdownRoute() {
+    _dropdownRoute?._dismiss();
+    _dropdownRoute = null;
+    _lastOrientation = null;
+  }
+
+  void _handleFocusChanged() {
+    if (_hasPrimaryFocus != focusNode!.hasPrimaryFocus) {
+      setState(() {
+        _hasPrimaryFocus = focusNode!.hasPrimaryFocus;
+      });
+    }
+  }
+
+  void _handleFocusHighlightModeChange(FocusHighlightMode mode) {
+    if (!mounted) {
+      return;
+    }
+    setState(() {
+      _focusHighlightMode = mode;
+    });
+  }
+
+  @override
+  void didUpdateWidget(DropdownButton<T> oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.focusNode != oldWidget.focusNode) {
+      oldWidget.focusNode?.removeListener(_handleFocusChanged);
+      if (widget.focusNode == null) {
+        _internalNode ??= _createFocusNode();
+      }
+      _hasPrimaryFocus = focusNode!.hasPrimaryFocus;
+      focusNode!.addListener(_handleFocusChanged);
+    }
+    _updateSelectedIndex();
+  }
+
+  void _updateSelectedIndex() {
+    if (widget.value == null || widget.items == null || widget.items!.isEmpty) {
+      _selectedIndex = null;
+      return;
+    }
+
+    assert(widget.items!.where((DropdownMenuItem<T> item) => item.value == widget.value).length == 1);
+    for (int itemIndex = 0; itemIndex < widget.items!.length; itemIndex++) {
+      if (widget.items![itemIndex].value == widget.value) {
+        _selectedIndex = itemIndex;
+        return;
+      }
+    }
+  }
+
+  TextStyle? get _textStyle => widget.style ?? Theme.of(context).textTheme.subtitle1;
+
+  void _handleTap() {
+    final RenderBox itemBox = context.findRenderObject()! as RenderBox;
+    final Rect itemRect = itemBox.localToGlobal(Offset.zero) & itemBox.size;
+    final TextDirection? textDirection = Directionality.maybeOf(context);
+    final EdgeInsetsGeometry menuMargin = ButtonTheme.of(context).alignedDropdown
+      ? _kAlignedMenuMargin
+      : _kUnalignedMenuMargin;
+
+    final List<_MenuItem<T>> menuItems = <_MenuItem<T>>[
+    for (int index = 0; index < widget.items!.length; index += 1)
+      _MenuItem<T>(
+        item: widget.items![index],
+        onLayout: (Size size) {
+          // If [_dropdownRoute] is null and onLayout is called, this means
+          // that performLayout was called on a _DropdownRoute that has not
+          // left the widget tree but is already on its way out.
+          //
+          // Since onLayout is used primarily to collect the desired heights
+          // of each menu item before laying them out, not having the _DropdownRoute
+          // collect each item's height to lay out is fine since the route is
+          // already on its way out.
+          if (_dropdownRoute == null)
+            return;
+
+          _dropdownRoute!.itemHeights[index] = size.height;
+        },
+      )
+    ];
+
+    final NavigatorState navigator = Navigator.of(context);
+    assert(_dropdownRoute == null);
+    _dropdownRoute = _DropdownRoute<T>(
+      items: menuItems,
+      buttonRect: menuMargin.resolve(textDirection).inflateRect(itemRect),
+      padding: _kMenuItemPadding.resolve(textDirection),
+      selectedIndex: _selectedIndex ?? 0,
+      elevation: widget.elevation,
+      capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
+      style: _textStyle!,
+      barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
+      itemHeight: widget.itemHeight,
+      dropdownColor: widget.dropdownColor,
+    );
+
+    navigator.push(_dropdownRoute!).then<void>((_DropdownRouteResult<T>? newValue) {
+      _removeDropdownRoute();
+      if (!mounted || newValue == null)
+        return;
+      if (widget.onChanged != null)
+        widget.onChanged!(newValue.result);
+    });
+
+    if (widget.onTap != null) {
+      widget.onTap!();
+    }
+  }
+
+  // When isDense is true, reduce the height of this button from _kMenuItemHeight to
+  // _kDenseButtonHeight, but don't make it smaller than the text that it contains.
+  // Similarly, we don't reduce the height of the button so much that its icon
+  // would be clipped.
+  double get _denseButtonHeight {
+    final double fontSize = _textStyle!.fontSize ?? Theme.of(context).textTheme.subtitle1!.fontSize!;
+    return math.max(fontSize, math.max(widget.iconSize, _kDenseButtonHeight));
+  }
+
+  Color get _iconColor {
+    // These colors are not defined in the Material Design spec.
+    if (_enabled) {
+      if (widget.iconEnabledColor != null)
+        return widget.iconEnabledColor!;
+
+      switch (Theme.of(context).brightness) {
+        case Brightness.light:
+          return Colors.grey.shade700;
+        case Brightness.dark:
+          return Colors.white70;
+      }
+    } else {
+      if (widget.iconDisabledColor != null)
+        return widget.iconDisabledColor!;
+
+      switch (Theme.of(context).brightness) {
+        case Brightness.light:
+          return Colors.grey.shade400;
+        case Brightness.dark:
+          return Colors.white10;
+      }
+    }
+  }
+
+  bool get _enabled => widget.items != null && widget.items!.isNotEmpty && widget.onChanged != null;
+
+  Orientation _getOrientation(BuildContext context) {
+    Orientation? result = MediaQuery.maybeOf(context)?.orientation;
+    if (result == null) {
+      // If there's no MediaQuery, then use the window aspect to determine
+      // orientation.
+      final Size size = window.physicalSize;
+      result = size.width > size.height ? Orientation.landscape : Orientation.portrait;
+    }
+    return result;
+  }
+
+  bool get _showHighlight {
+    switch (_focusHighlightMode) {
+      case FocusHighlightMode.touch:
+        return false;
+      case FocusHighlightMode.traditional:
+        return _hasPrimaryFocus;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+    final Orientation newOrientation = _getOrientation(context);
+    _lastOrientation ??= newOrientation;
+    if (newOrientation != _lastOrientation) {
+      _removeDropdownRoute();
+      _lastOrientation = newOrientation;
+    }
+
+    // The width of the button and the menu are defined by the widest
+    // item and the width of the hint.
+    // We should explicitly type the items list to be a list of <Widget>,
+    // otherwise, no explicit type adding items maybe trigger a crash/failure
+    // when hint and selectedItemBuilder are provided.
+    final List<Widget> items = widget.selectedItemBuilder == null
+      ? (widget.items != null ? List<Widget>.from(widget.items!) : <Widget>[])
+      : List<Widget>.from(widget.selectedItemBuilder!(context));
+
+    int? hintIndex;
+    if (widget.hint != null || (!_enabled && widget.disabledHint != null)) {
+      Widget displayedHint = _enabled ? widget.hint! : widget.disabledHint ?? widget.hint!;
+      if (widget.selectedItemBuilder == null)
+        displayedHint = _DropdownMenuItemContainer(child: displayedHint);
+
+      hintIndex = items.length;
+      items.add(DefaultTextStyle(
+        style: _textStyle!.copyWith(color: Theme.of(context).hintColor),
+        child: IgnorePointer(
+          ignoringSemantics: false,
+          child: displayedHint,
+        ),
+      ));
+    }
+
+    final EdgeInsetsGeometry padding = ButtonTheme.of(context).alignedDropdown
+      ? _kAlignedButtonPadding
+      : _kUnalignedButtonPadding;
+
+    // If value is null (then _selectedIndex is null) then we
+    // display the hint or nothing at all.
+    final Widget innerItemsWidget;
+    if (items.isEmpty) {
+      innerItemsWidget = Container();
+    } else {
+      innerItemsWidget = IndexedStack(
+        index: _selectedIndex ?? hintIndex,
+        alignment: AlignmentDirectional.centerStart,
+        children: widget.isDense ? items : items.map((Widget item) {
+          return widget.itemHeight != null
+            ? SizedBox(height: widget.itemHeight, child: item)
+            : Column(mainAxisSize: MainAxisSize.min, children: <Widget>[item]);
+        }).toList(),
+      );
+    }
+
+    const Icon defaultIcon = Icon(Icons.arrow_drop_down);
+
+    Widget result = DefaultTextStyle(
+      style: _enabled ? _textStyle! : _textStyle!.copyWith(color: Theme.of(context).disabledColor),
+      child: Container(
+        decoration: _showHighlight
+            ? BoxDecoration(
+                color: widget.focusColor ?? Theme.of(context).focusColor,
+                borderRadius: const BorderRadius.all(Radius.circular(4.0)),
+              )
+            : null,
+        padding: padding.resolve(Directionality.of(context)),
+        height: widget.isDense ? _denseButtonHeight : null,
+        child: Row(
+          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          mainAxisSize: MainAxisSize.min,
+          children: <Widget>[
+            if (widget.isExpanded)
+              Expanded(child: innerItemsWidget)
+            else
+              innerItemsWidget,
+            IconTheme(
+              data: IconThemeData(
+                color: _iconColor,
+                size: widget.iconSize,
+              ),
+              child: widget.icon ?? defaultIcon,
+            ),
+          ],
+        ),
+      ),
+    );
+
+    if (!DropdownButtonHideUnderline.at(context)) {
+      final double bottom = (widget.isDense || widget.itemHeight == null) ? 0.0 : 8.0;
+      result = Stack(
+        children: <Widget>[
+          result,
+          Positioned(
+            left: 0.0,
+            right: 0.0,
+            bottom: bottom,
+            child: widget.underline ?? Container(
+              height: 1.0,
+              decoration: const BoxDecoration(
+                border: Border(
+                  bottom: BorderSide(
+                    color: Color(0xFFBDBDBD),
+                    width: 0.0,
+                  ),
+                ),
+              ),
+            ),
+          ),
+        ],
+      );
+    }
+
+    return Semantics(
+      button: true,
+      child: Actions(
+        actions: _actionMap,
+        child: Focus(
+          canRequestFocus: _enabled,
+          focusNode: focusNode,
+          autofocus: widget.autofocus,
+          child: GestureDetector(
+            onTap: _enabled ? _handleTap : null,
+            behavior: HitTestBehavior.opaque,
+            child: result,
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+/// A convenience widget that makes a [DropdownButton] into a [FormField].
+class DropdownButtonFormField<T> extends FormField<T> {
+  /// Creates a [DropdownButton] widget that is a [FormField], wrapped in an
+  /// [InputDecorator].
+  ///
+  /// For a description of the `onSaved`, `validator`, or `autovalidateMode`
+  /// parameters, see [FormField]. For the rest (other than [decoration]), see
+  /// [DropdownButton].
+  ///
+  /// The `items`, `elevation`, `iconSize`, `isDense`, `isExpanded`,
+  /// `autofocus`, and `decoration`  parameters must not be null.
+  DropdownButtonFormField({
+    Key? key,
+    required List<DropdownMenuItem<T>>? items,
+    DropdownButtonBuilder? selectedItemBuilder,
+    T? value,
+    Widget? hint,
+    Widget? disabledHint,
+    this.onChanged,
+    VoidCallback? onTap,
+    int elevation = 8,
+    TextStyle? style,
+    Widget? icon,
+    Color? iconDisabledColor,
+    Color? iconEnabledColor,
+    double iconSize = 24.0,
+    bool isDense = true,
+    bool isExpanded = false,
+    double? itemHeight,
+    Color? focusColor,
+    FocusNode? focusNode,
+    bool autofocus = false,
+    Color? dropdownColor,
+    InputDecoration? decoration,
+    FormFieldSetter<T>? onSaved,
+    FormFieldValidator<T>? validator,
+    @Deprecated(
+      'Use autoValidateMode parameter which provide more specific '
+      'behaviour related to auto validation. '
+      'This feature was deprecated after v1.19.0.'
+    )
+    bool autovalidate = false,
+    AutovalidateMode? autovalidateMode,
+  }) : assert(items == null || items.isEmpty || value == null ||
+              items.where((DropdownMenuItem<T> item) {
+                return item.value == value;
+              }).length == 1,
+                "There should be exactly one item with [DropdownButton]'s value: "
+                '$value. \n'
+                'Either zero or 2 or more [DropdownMenuItem]s were detected '
+                'with the same value',
+              ),
+       assert(elevation != null),
+       assert(iconSize != null),
+       assert(isDense != null),
+       assert(isExpanded != null),
+       assert(itemHeight == null || itemHeight >= kMinInteractiveDimension),
+       assert(autofocus != null),
+       assert(autovalidate != null),
+       assert(
+         autovalidate == false ||
+         autovalidate == true && autovalidateMode == null,
+         'autovalidate and autovalidateMode should not be used together.'
+       ),
+       decoration = decoration ?? InputDecoration(focusColor: focusColor),
+       super(
+         key: key,
+         onSaved: onSaved,
+         initialValue: value,
+         validator: validator,
+         autovalidateMode: autovalidate
+             ? AutovalidateMode.always
+             : (autovalidateMode ?? AutovalidateMode.disabled),
+         builder: (FormFieldState<T> field) {
+           final _DropdownButtonFormFieldState<T> state = field as _DropdownButtonFormFieldState<T>;
+           final InputDecoration decorationArg =  decoration ?? InputDecoration(focusColor: focusColor);
+           final InputDecoration effectiveDecoration = decorationArg.applyDefaults(
+             Theme.of(field.context).inputDecorationTheme,
+           );
+           // An unfocusable Focus widget so that this widget can detect if its
+           // descendants have focus or not.
+           return Focus(
+             canRequestFocus: false,
+             skipTraversal: true,
+             child: Builder(builder: (BuildContext context) {
+               return InputDecorator(
+                 decoration: effectiveDecoration.copyWith(errorText: field.errorText),
+                 isEmpty: state.value == null,
+                 isFocused: Focus.of(context).hasFocus,
+                 child: DropdownButtonHideUnderline(
+                   child: DropdownButton<T>(
+                     items: items,
+                     selectedItemBuilder: selectedItemBuilder,
+                     value: state.value,
+                     hint: hint,
+                     disabledHint: disabledHint,
+                     onChanged: onChanged == null ? null : state.didChange,
+                     onTap: onTap,
+                     elevation: elevation,
+                     style: style,
+                     icon: icon,
+                     iconDisabledColor: iconDisabledColor,
+                     iconEnabledColor: iconEnabledColor,
+                     iconSize: iconSize,
+                     isDense: isDense,
+                     isExpanded: isExpanded,
+                     itemHeight: itemHeight,
+                     focusColor: focusColor,
+                     focusNode: focusNode,
+                     autofocus: autofocus,
+                     dropdownColor: dropdownColor,
+                   ),
+                 ),
+               );
+             }),
+           );
+         },
+       );
+
+  /// {@macro flutter.material.dropdownButton.onChanged}
+  final ValueChanged<T?>? onChanged;
+
+  /// The decoration to show around the dropdown button form field.
+  ///
+  /// By default, draws a horizontal line under the dropdown button field but
+  /// can be configured to show an icon, label, hint text, and error text.
+  ///
+  /// If not specified, an [InputDecorator] with the `focusColor` set to the
+  /// supplied `focusColor` (if any) will be used.
+  final InputDecoration decoration;
+
+  @override
+  FormFieldState<T> createState() => _DropdownButtonFormFieldState<T>();
+}
+
+class _DropdownButtonFormFieldState<T> extends FormFieldState<T> {
+  @override
+  DropdownButtonFormField<T> get widget => super.widget as DropdownButtonFormField<T>;
+
+  @override
+  void didChange(T? value) {
+    super.didChange(value);
+    assert(widget.onChanged != null);
+    widget.onChanged!(value);
+  }
+
+  @override
+  void didUpdateWidget(DropdownButtonFormField<T> oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.initialValue != widget.initialValue) {
+      setValue(widget.initialValue);
+    }
+  }
+}
diff --git a/lib/src/material/elevated_button.dart b/lib/src/material/elevated_button.dart
new file mode 100644
index 0000000..ac01354
--- /dev/null
+++ b/lib/src/material/elevated_button.dart
@@ -0,0 +1,428 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'button_style.dart';
+import 'button_style_button.dart';
+import 'color_scheme.dart';
+import 'constants.dart';
+import 'elevated_button_theme.dart';
+import 'material_state.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// A Material Design "elevated button".
+///
+/// Use elevated buttons to add dimension to otherwise mostly flat
+/// layouts, e.g.  in long busy lists of content, or in wide
+/// spaces. Avoid using elevated buttons on already-elevated content
+/// such as dialogs or cards.
+///
+/// An elevated button is a label [child] displayed on a [Material]
+/// widget whose [Material.elevation] increases when the button is
+/// pressed. The label's [Text] and [Icon] widgets are displayed in
+/// [style]'s [ButtonStyle.foregroundColor] and the button's filled
+/// background is the [ButtonStyle.backgroundColor].
+///
+/// The elevated button's default style is defined by
+/// [defaultStyleOf].  The style of this elevated button can be
+/// overridden with its [style] parameter. The style of all elevated
+/// buttons in a subtree can be overridden with the
+/// [ElevatedButtonTheme], and the style of all of the elevated
+/// buttons in an app can be overridden with the [Theme]'s
+/// [ThemeData.elevatedButtonTheme] property.
+///
+/// The static [styleFrom] method is a convenient way to create a
+/// elevated button [ButtonStyle] from simple values.
+///
+/// If [onPressed] and [onLongPress] callbacks are null, then the
+/// button will be disabled.
+///
+/// See also:
+///
+///  * [TextButton], a simple flat button without a shadow.
+///  * [OutlinedButton], a [TextButton] with a border outline.
+///  * <https://material.io/design/components/buttons.html>
+class ElevatedButton extends ButtonStyleButton {
+  /// Create an ElevatedButton.
+  ///
+  /// The [autofocus] and [clipBehavior] arguments must not be null.
+  const ElevatedButton({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ButtonStyle? style,
+    FocusNode? focusNode,
+    bool autofocus = false,
+    Clip clipBehavior = Clip.none,
+    required Widget? child,
+  }) : super(
+    key: key,
+    onPressed: onPressed,
+    onLongPress: onLongPress,
+    style: style,
+    focusNode: focusNode,
+    autofocus: autofocus,
+    clipBehavior: clipBehavior,
+    child: child,
+  );
+
+  /// Create an elevated button from a pair of widgets that serve as the button's
+  /// [icon] and [label].
+  ///
+  /// The icon and label are arranged in a row and padded by 12 logical pixels
+  /// at the start, and 16 at the end, with an 8 pixel gap in between.
+  ///
+  /// The [icon] and [label] arguments must not be null.
+  factory ElevatedButton.icon({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ButtonStyle? style,
+    FocusNode? focusNode,
+    bool? autofocus,
+    Clip? clipBehavior,
+    required Widget icon,
+    required Widget label,
+  }) = _ElevatedButtonWithIcon;
+
+  /// A static convenience method that constructs an elevated button
+  /// [ButtonStyle] given simple values.
+  ///
+  /// The [onPrimary], and [onSurface] colors are used to to create a
+  /// [MaterialStateProperty] [ButtonStyle.foregroundColor] value in the same
+  /// way that [defaultStyleOf] uses the [ColorScheme] colors with the same
+  /// names. Specify a value for [onPrimary] to specify the color of the
+  /// button's text and icons as well as the overlay colors used to indicate the
+  /// hover, focus, and pressed states. Use [primary] for the button's background
+  /// fill color and [onSurface] to specify the button's disabled text, icon,
+  /// and fill color.
+  ///
+  /// The button's elevations are defined relative to the [elevation]
+  /// parameter. The disabled elevation is the same as the parameter
+  /// value, [elevation] + 2 is used when the button is hovered
+  /// or focused, and elevation + 6 is used when the button is pressed.
+  ///
+  /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
+  /// parameters are used to construct [ButtonStyle].mouseCursor.
+  ///
+  /// All of the other parameters are either used directly or used to
+  /// create a [MaterialStateProperty] with a single value for all
+  /// states.
+  ///
+  /// All parameters default to null, by default this method returns
+  /// a [ButtonStyle] that doesn't override anything.
+  ///
+  /// For example, to override the default text and icon colors for a
+  /// [ElevatedButton], as well as its overlay color, with all of the
+  /// standard opacity adjustments for the pressed, focused, and
+  /// hovered states, one could write:
+  ///
+  /// ```dart
+  /// ElevatedButton(
+  ///   style: ElevatedButton.styleFrom(primary: Colors.green),
+  /// )
+  /// ```
+  static ButtonStyle styleFrom({
+    Color? primary,
+    Color? onPrimary,
+    Color? onSurface,
+    Color? shadowColor,
+    double? elevation,
+    TextStyle? textStyle,
+    EdgeInsetsGeometry? padding,
+    Size? minimumSize,
+    BorderSide? side,
+    OutlinedBorder? shape,
+    MouseCursor? enabledMouseCursor,
+    MouseCursor? disabledMouseCursor,
+    VisualDensity? visualDensity,
+    MaterialTapTargetSize? tapTargetSize,
+    Duration? animationDuration,
+    bool? enableFeedback,
+  }) {
+    final MaterialStateProperty<Color?>? backgroundColor = (onSurface == null && primary == null)
+      ? null
+      : _ElevatedButtonDefaultBackground(primary, onSurface);
+    final MaterialStateProperty<Color?>? foregroundColor = (onSurface == null && onPrimary == null)
+      ? null
+      : _ElevatedButtonDefaultForeground(onPrimary, onSurface);
+    final MaterialStateProperty<Color?>? overlayColor = (onPrimary == null)
+      ? null
+      : _ElevatedButtonDefaultOverlay(onPrimary);
+    final MaterialStateProperty<double>? elevationValue = (elevation == null)
+      ? null
+      : _ElevatedButtonDefaultElevation(elevation);
+    final MaterialStateProperty<MouseCursor?>? mouseCursor = (enabledMouseCursor == null && disabledMouseCursor == null)
+      ? null
+      : _ElevatedButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor);
+
+    return ButtonStyle(
+      textStyle: MaterialStateProperty.all<TextStyle?>(textStyle),
+      backgroundColor: backgroundColor,
+      foregroundColor: foregroundColor,
+      overlayColor: overlayColor,
+      shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
+      elevation: elevationValue,
+      padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
+      minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
+      side: ButtonStyleButton.allOrNull<BorderSide>(side),
+      shape: ButtonStyleButton.allOrNull<OutlinedBorder>(shape),
+      mouseCursor: mouseCursor,
+      visualDensity: visualDensity,
+      tapTargetSize: tapTargetSize,
+      animationDuration: animationDuration,
+      enableFeedback: enableFeedback,
+    );
+  }
+
+  /// Defines the button's default appearance.
+  ///
+  /// The button [child]'s [Text] and [Icon] widgets are rendered with
+  /// the [ButtonStyle]'s foreground color. The button's [InkWell] adds
+  /// the style's overlay color when the button is focused, hovered
+  /// or pressed. The button's background color becomes its [Material]
+  /// color.
+  ///
+  /// All of the ButtonStyle's defaults appear below. In this list
+  /// "Theme.foo" is shorthand for `Theme.of(context).foo`. Color
+  /// scheme values like "onSurface(0.38)" are shorthand for
+  /// `onSurface.withOpacity(0.38)`. [MaterialStateProperty] valued
+  /// properties that are not followed by by a sublist have the same
+  /// value for all states, otherwise the values are as specified for
+  /// each state, and "others" means all other states.
+  ///
+  /// The `textScaleFactor` is the value of
+  /// `MediaQuery.of(context).textScaleFactor` and the names of the
+  /// EdgeInsets constructors and `EdgeInsetsGeometry.lerp` have been
+  /// abbreviated for readability.
+  ///
+  /// The color of the [ButtonStyle.textStyle] is not used, the
+  /// [ButtonStyle.foregroundColor] color is used instead.
+  ///
+  /// * `textStyle` - Theme.textTheme.button
+  /// * `backgroundColor`
+  ///   * disabled - Theme.colorScheme.onSurface(0.12)
+  ///   * others - Theme.colorScheme.primary
+  /// * `foregroundColor`
+  ///   * disabled - Theme.colorScheme.onSurface(0.38)
+  ///   * others - Theme.colorScheme.onPrimary
+  /// * `overlayColor`
+  ///   * hovered - Theme.colorScheme.onPrimary(0.08)
+  ///   * focused or pressed - Theme.colorScheme.onPrimary(0.24)
+  /// * `shadowColor` - Theme.shadowColor
+  /// * `elevation`
+  ///   * disabled - 0
+  ///   * default - 2
+  ///   * hovered or focused - 4
+  ///   * pressed - 8
+  /// * `padding`
+  ///   * textScaleFactor <= 1 - horizontal(16)
+  ///   * `1 < textScaleFactor <= 2` - lerp(horizontal(16), horizontal(8))
+  ///   * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4))
+  ///   * `3 < textScaleFactor` - horizontal(4)
+  /// * `minimumSize` - Size(64, 36)
+  /// * `side` - null
+  /// * `shape` - RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))
+  /// * `mouseCursor`
+  ///   * disabled - SystemMouseCursors.forbidden
+  ///   * others - SystemMouseCursors.click
+  /// * `visualDensity` - theme.visualDensity
+  /// * `tapTargetSize` - theme.materialTapTargetSize
+  /// * `animationDuration` - kThemeChangeDuration
+  /// * `enableFeedback` - true
+  ///
+  /// The default padding values for the [ElevatedButton.icon] factory are slightly different:
+  ///
+  /// * `padding`
+  ///   * `textScaleFactor <= 1` - start(12) end(16)
+  ///   * `1 < textScaleFactor <= 2` - lerp(start(12) end(16), horizontal(8))
+  ///   * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4))
+  ///   * `3 < textScaleFactor` - horizontal(4)
+  ///
+  /// The default value for `side`, which defines the appearance of the button's
+  /// outline, is null. That means that the outline is defined by the button
+  /// shape's [OutlinedBorder.side]. Typically the default value of an
+  /// [OutlinedBorder]'s side is [BorderSide.none], so an outline is not drawn.
+  @override
+  ButtonStyle defaultStyleOf(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final ColorScheme colorScheme = theme.colorScheme;
+
+    final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding(
+      const EdgeInsets.symmetric(horizontal: 16),
+      const EdgeInsets.symmetric(horizontal: 8),
+      const EdgeInsets.symmetric(horizontal: 4),
+      MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
+    );
+
+    return styleFrom(
+      primary: colorScheme.primary,
+      onPrimary: colorScheme.onPrimary,
+      onSurface: colorScheme.onSurface,
+      shadowColor: theme.shadowColor,
+      elevation: 2,
+      textStyle: theme.textTheme.button,
+      padding: scaledPadding,
+      minimumSize: const Size(64, 36),
+      side: null,
+      shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))),
+      enabledMouseCursor: SystemMouseCursors.click,
+      disabledMouseCursor: SystemMouseCursors.forbidden,
+      visualDensity: theme.visualDensity,
+      tapTargetSize: theme.materialTapTargetSize,
+      animationDuration: kThemeChangeDuration,
+      enableFeedback: true,
+    );
+  }
+
+  /// Returns the [ElevatedButtonThemeData.style] of the closest
+  /// [ElevatedButtonTheme] ancestor.
+  @override
+  ButtonStyle? themeStyleOf(BuildContext context) {
+    return ElevatedButtonTheme.of(context).style;
+  }
+}
+
+@immutable
+class _ElevatedButtonDefaultBackground extends MaterialStateProperty<Color?> with Diagnosticable {
+  _ElevatedButtonDefaultBackground(this.primary, this.onSurface);
+
+  final Color? primary;
+  final Color? onSurface;
+
+  @override
+  Color? resolve(Set<MaterialState> states) {
+    if (states.contains(MaterialState.disabled))
+      return onSurface?.withOpacity(0.12);
+    return primary;
+  }
+}
+
+@immutable
+class _ElevatedButtonDefaultForeground extends MaterialStateProperty<Color?> with Diagnosticable {
+  _ElevatedButtonDefaultForeground(this.onPrimary, this.onSurface);
+
+  final Color? onPrimary;
+  final Color? onSurface;
+
+  @override
+  Color? resolve(Set<MaterialState> states) {
+    if (states.contains(MaterialState.disabled))
+      return onSurface?.withOpacity(0.38);
+    return onPrimary;
+  }
+}
+
+@immutable
+class _ElevatedButtonDefaultOverlay extends MaterialStateProperty<Color?> with Diagnosticable {
+  _ElevatedButtonDefaultOverlay(this.onPrimary);
+
+  final Color onPrimary;
+
+  @override
+  Color? resolve(Set<MaterialState> states) {
+    if (states.contains(MaterialState.hovered))
+      return onPrimary.withOpacity(0.08);
+    if (states.contains(MaterialState.focused) || states.contains(MaterialState.pressed))
+      return onPrimary.withOpacity(0.24);
+    return null;
+  }
+}
+
+@immutable
+class _ElevatedButtonDefaultElevation extends MaterialStateProperty<double> with Diagnosticable {
+  _ElevatedButtonDefaultElevation(this.elevation);
+
+  final double elevation;
+
+  @override
+  double resolve(Set<MaterialState> states) {
+    if (states.contains(MaterialState.disabled))
+      return 0;
+    if (states.contains(MaterialState.hovered))
+      return elevation + 2;
+    if (states.contains(MaterialState.focused))
+      return elevation + 2;
+    if (states.contains(MaterialState.pressed))
+      return elevation + 6;
+    return elevation;
+  }
+}
+
+@immutable
+class _ElevatedButtonDefaultMouseCursor extends MaterialStateProperty<MouseCursor?> with Diagnosticable {
+  _ElevatedButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor);
+
+  final MouseCursor? enabledCursor;
+  final MouseCursor? disabledCursor;
+
+  @override
+  MouseCursor? resolve(Set<MaterialState> states) {
+    if (states.contains(MaterialState.disabled))
+      return disabledCursor;
+    return enabledCursor;
+  }
+}
+
+class _ElevatedButtonWithIcon extends ElevatedButton {
+  _ElevatedButtonWithIcon({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ButtonStyle? style,
+    FocusNode? focusNode,
+    bool? autofocus,
+    Clip? clipBehavior,
+    required Widget icon,
+    required Widget label,
+  }) : assert(icon != null),
+       assert(label != null),
+       super(
+         key: key,
+         onPressed: onPressed,
+         onLongPress: onLongPress,
+         style: style,
+         focusNode: focusNode,
+         autofocus: autofocus ?? false,
+         clipBehavior: clipBehavior ?? Clip.none,
+         child: _ElevatedButtonWithIconChild(icon: icon, label: label),
+      );
+
+  @override
+  ButtonStyle defaultStyleOf(BuildContext context) {
+    final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding(
+      const EdgeInsetsDirectional.fromSTEB(12, 0, 16, 0),
+      const EdgeInsets.symmetric(horizontal: 8),
+      const EdgeInsetsDirectional.fromSTEB(8, 0, 4, 0),
+      MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
+    );
+    return super.defaultStyleOf(context).copyWith(
+      padding: MaterialStateProperty.all<EdgeInsetsGeometry>(scaledPadding)
+    );
+  }
+}
+
+class _ElevatedButtonWithIconChild extends StatelessWidget {
+  const _ElevatedButtonWithIconChild({ Key? key, required this.label, required this.icon }) : super(key: key);
+
+  final Widget label;
+  final Widget icon;
+
+  @override
+  Widget build(BuildContext context) {
+    final double scale = MediaQuery.maybeOf(context)?.textScaleFactor ?? 1;
+    final double gap = scale <= 1 ? 8 : lerpDouble(8, 4, math.min(scale - 1, 1))!;
+    return Row(
+      mainAxisSize: MainAxisSize.min,
+      children: <Widget>[icon, SizedBox(width: gap), label],
+    );
+  }
+}
diff --git a/lib/src/material/elevated_button_theme.dart b/lib/src/material/elevated_button_theme.dart
new file mode 100644
index 0000000..7179449
--- /dev/null
+++ b/lib/src/material/elevated_button_theme.dart
@@ -0,0 +1,124 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'button_style.dart';
+import 'theme.dart';
+
+/// A [ButtonStyle] that overrides the default appearance of
+/// [ElevatedButton]s when it's used with [ElevatedButtonTheme] or with the
+/// overall [Theme]'s [ThemeData.elevatedButtonTheme].
+///
+/// The [style]'s properties override [ElevatedButton]'s default style,
+/// i.e.  the [ButtonStyle] returned by [ElevatedButton.defaultStyleOf]. Only
+/// the style's non-null property values or resolved non-null
+/// [MaterialStateProperty] values are used.
+///
+/// See also:
+///
+///  * [ElevatedButtonTheme], the theme which is configured with this class.
+///  * [ElevatedButton.defaultStyleOf], which returns the default [ButtonStyle]
+///    for text buttons.
+///  * [ElevatedButton.styleFrom], which converts simple values into a
+///    [ButtonStyle] that's consistent with [ElevatedButton]'s defaults.
+///  * [MaterialStateProperty.resolve], "resolve" a material state property
+///    to a simple value based on a set of [MaterialState]s.
+///  * [ThemeData.elevatedButtonTheme], which can be used to override the default
+///    [ButtonStyle] for [ElevatedButton]s below the overall [Theme].
+@immutable
+class ElevatedButtonThemeData with Diagnosticable {
+  /// Creates an [ElevatedButtonThemeData].
+  ///
+  /// The [style] may be null.
+  const ElevatedButtonThemeData({ this.style });
+
+  /// Overrides for [ElevatedButton]'s default style.
+  ///
+  /// Non-null properties or non-null resolved [MaterialStateProperty]
+  /// values override the [ButtonStyle] returned by
+  /// [ElevatedButton.defaultStyleOf].
+  ///
+  /// If [style] is null, then this theme doesn't override anything.
+  final ButtonStyle? style;
+
+  /// Linearly interpolate between two elevated button themes.
+  static ElevatedButtonThemeData? lerp(ElevatedButtonThemeData? a, ElevatedButtonThemeData? b, double t) {
+    assert (t != null);
+    if (a == null && b == null)
+      return null;
+    return ElevatedButtonThemeData(
+      style: ButtonStyle.lerp(a?.style, b?.style, t),
+    );
+  }
+
+  @override
+  int get hashCode {
+    return style.hashCode;
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ElevatedButtonThemeData && other.style == style;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ButtonStyle>('style', style, defaultValue: null));
+  }
+}
+
+/// Overrides the default [ButtonStyle] of its [ElevatedButton] descendants.
+///
+/// See also:
+///
+///  * [ElevatedButtonThemeData], which is used to configure this theme.
+///  * [ElevatedButton.defaultStyleOf], which returns the default [ButtonStyle]
+///    for elevated buttons.
+///  * [ElevatedButton.styleFrom], which converts simple values into a
+///    [ButtonStyle] that's consistent with [ElevatedButton]'s defaults.
+///  * [ThemeData.elevatedButtonTheme], which can be used to override the default
+///    [ButtonStyle] for [ElevatedButton]s below the overall [Theme].
+class ElevatedButtonTheme extends InheritedTheme {
+  /// Create a [ElevatedButtonTheme].
+  ///
+  /// The [data] parameter must not be null.
+  const ElevatedButtonTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null), super(key: key, child: child);
+
+  /// The configuration of this theme.
+  final ElevatedButtonThemeData data;
+
+  /// The closest instance of this class that encloses the given context.
+  ///
+  /// If there is no enclosing [ElevatedButtonTheme] widget, then
+  /// [ThemeData.elevatedButtonTheme] is used.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// ElevatedButtonTheme theme = ElevatedButtonTheme.of(context);
+  /// ```
+  static ElevatedButtonThemeData of(BuildContext context) {
+    final ElevatedButtonTheme? buttonTheme = context.dependOnInheritedWidgetOfExactType<ElevatedButtonTheme>();
+    return buttonTheme?.data ?? Theme.of(context).elevatedButtonTheme;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return ElevatedButtonTheme(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(ElevatedButtonTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/elevation_overlay.dart b/lib/src/material/elevation_overlay.dart
new file mode 100644
index 0000000..9239d31
--- /dev/null
+++ b/lib/src/material/elevation_overlay.dart
@@ -0,0 +1,87 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/widgets.dart';
+
+import 'theme.dart';
+
+/// A utility class for dealing with the overlay color needed
+/// to indicate elevation of surfaces in a dark theme.
+class ElevationOverlay {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  ElevationOverlay._();
+
+  /// Applies an overlay color to a surface color to indicate
+  /// the level of its elevation in a dark theme.
+  ///
+  /// Material drop shadows can be difficult to see in a dark theme, so the
+  /// elevation of a surface should be portrayed with an "overlay" in addition
+  /// to the shadow. As the elevation of the component increases, the
+  /// overlay increases in opacity. This function computes and applies this
+  /// overlay to a given color as needed.
+  ///
+  /// If the ambient theme is dark ([ThemeData.brightness] is [Brightness.dark]),
+  /// and [ThemeData.applyElevationOverlayColor] is true, and the given
+  /// [color] is [ColorScheme.surface] then this will return a version of
+  /// the [color] with a semi-transparent [ColorScheme.onSurface] overlaid
+  /// on top of it. The opacity of the overlay is computed based on the
+  /// [elevation].
+  ///
+  /// Otherwise it will just return the [color] unmodified.
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.applyElevationOverlayColor] which controls the whether
+  ///    an overlay color will be applied to indicate elevation.
+  ///  * [overlayColor] which computes the needed overlay color.
+  ///  * [Material] which uses this to apply an elevation overlay to its surface.
+  ///  * <https://material.io/design/color/dark-theme.html>, which specifies how
+  ///    the overlay should be applied.
+  static Color applyOverlay(BuildContext context, Color color, double elevation) {
+    final ThemeData theme = Theme.of(context);
+    if (elevation > 0.0 &&
+        theme.applyElevationOverlayColor &&
+        theme.brightness == Brightness.dark &&
+        color.withOpacity(1.0) == theme.colorScheme.surface.withOpacity(1.0)) {
+      return colorWithOverlay(color, theme.colorScheme.onSurface, elevation);
+    }
+    return color;
+  }
+
+  /// Computes the appropriate overlay color used to indicate elevation in
+  /// dark themes.
+  ///
+  /// See also:
+  ///
+  ///  * https://material.io/design/color/dark-theme.html#properties which
+  ///    specifies the exact overlay values for a given elevation.
+  static Color overlayColor(BuildContext context, double elevation) {
+    final ThemeData theme = Theme.of(context);
+    return _overlayColor(theme.colorScheme.onSurface, elevation);
+  }
+
+  /// Returns a color blended by laying a semi-transparent overlay (using the
+  /// [overlay] color) on top of a surface (using the [surface] color).
+  ///
+  /// The opacity of the overlay depends on [elevation]. As [elevation]
+  /// increases, the opacity will also increase.
+  ///
+  /// See https://material.io/design/color/dark-theme.html#properties.
+  static Color colorWithOverlay(Color surface, Color overlay, double elevation) {
+    return Color.alphaBlend(_overlayColor(overlay, elevation), surface);
+  }
+
+  /// Applies an opacity to [color] based on [elevation].
+  static Color _overlayColor(Color color, double elevation) {
+    // Compute the opacity for the given elevation
+    // This formula matches the values in the spec:
+    // https://material.io/design/color/dark-theme.html#properties
+    final double opacity = (4.5 * math.log(elevation + 1) + 2) / 100.0;
+    return color.withOpacity(opacity);
+  }
+}
diff --git a/lib/src/material/expand_icon.dart b/lib/src/material/expand_icon.dart
new file mode 100644
index 0000000..61372e8
--- /dev/null
+++ b/lib/src/material/expand_icon.dart
@@ -0,0 +1,188 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'debug.dart';
+import 'icon_button.dart';
+import 'icons.dart';
+import 'material_localizations.dart';
+import 'theme.dart';
+
+/// A widget representing a rotating expand/collapse button. The icon rotates
+/// 180 degrees when pressed, then reverts the animation on a second press.
+/// The underlying icon is [Icons.expand_more].
+///
+/// The expand icon does not include a semantic label for accessibility. In
+/// order to be accessible it should be combined with a label using
+/// [MergeSemantics]. This is done automatically by the [ExpansionPanel] widget.
+///
+/// See [IconButton] for a more general implementation of a pressable button
+/// with an icon.
+///
+/// See also:
+///
+///  * https://material.io/design/iconography/system-icons.html
+class ExpandIcon extends StatefulWidget {
+  /// Creates an [ExpandIcon] with the given padding, and a callback that is
+  /// triggered when the icon is pressed.
+  const ExpandIcon({
+    Key? key,
+    this.isExpanded = false,
+    this.size = 24.0,
+    required this.onPressed,
+    this.padding = const EdgeInsets.all(8.0),
+    this.color,
+    this.disabledColor,
+    this.expandedColor,
+  }) : assert(isExpanded != null),
+       assert(size != null),
+       assert(padding != null),
+       super(key: key);
+
+  /// Whether the icon is in an expanded state.
+  ///
+  /// Rebuilding the widget with a different [isExpanded] value will trigger
+  /// the animation, but will not trigger the [onPressed] callback.
+  final bool isExpanded;
+
+  /// The size of the icon.
+  ///
+  /// This property must not be null. It defaults to 24.0.
+  final double size;
+
+  /// The callback triggered when the icon is pressed and the state changes
+  /// between expanded and collapsed. The value passed to the current state.
+  ///
+  /// If this is set to null, the button will be disabled.
+  final ValueChanged<bool>? onPressed;
+
+  /// The padding around the icon. The entire padded icon will react to input
+  /// gestures.
+  ///
+  /// This property must not be null. It defaults to 8.0 padding on all sides.
+  final EdgeInsetsGeometry padding;
+
+
+  /// The color of the icon.
+  ///
+  /// Defaults to [Colors.black54] when the theme's
+  /// [ThemeData.brightness] is [Brightness.light] and to
+  /// [Colors.white60] when it is [Brightness.dark]. This adheres to the
+  /// Material Design specifications for [icons](https://material.io/design/iconography/system-icons.html#color)
+  /// and for [dark theme](https://material.io/design/color/dark-theme.html#ui-application)
+  final Color? color;
+
+  /// The color of the icon when it is disabled,
+  /// i.e. if [onPressed] is null.
+  ///
+  /// Defaults to [Colors.black38] when the theme's
+  /// [ThemeData.brightness] is [Brightness.light] and to
+  /// [Colors.white38] when it is [Brightness.dark]. This adheres to the
+  /// Material Design specifications for [icons](https://material.io/design/iconography/system-icons.html#color)
+  /// and for [dark theme](https://material.io/design/color/dark-theme.html#ui-application)
+  final Color? disabledColor;
+
+  /// The color of the icon when the icon is expanded.
+  ///
+  /// Defaults to [Colors.black54] when the theme's
+  /// [ThemeData.brightness] is [Brightness.light] and to
+  /// [Colors.white] when it is [Brightness.dark]. This adheres to the
+  /// Material Design specifications for [icons](https://material.io/design/iconography/system-icons.html#color)
+  /// and for [dark theme](https://material.io/design/color/dark-theme.html#ui-application)
+  final Color? expandedColor;
+
+  @override
+  _ExpandIconState createState() => _ExpandIconState();
+}
+
+class _ExpandIconState extends State<ExpandIcon> with SingleTickerProviderStateMixin {
+  late AnimationController _controller;
+  late Animation<double> _iconTurns;
+
+  static final Animatable<double> _iconTurnTween = Tween<double>(begin: 0.0, end: 0.5)
+    .chain(CurveTween(curve: Curves.fastOutSlowIn));
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(duration: kThemeAnimationDuration, vsync: this);
+    _iconTurns = _controller.drive(_iconTurnTween);
+    // If the widget is initially expanded, rotate the icon without animating it.
+    if (widget.isExpanded) {
+      _controller.value = math.pi;
+    }
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(ExpandIcon oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.isExpanded != oldWidget.isExpanded) {
+      if (widget.isExpanded) {
+        _controller.forward();
+      } else {
+        _controller.reverse();
+      }
+    }
+  }
+
+  void _handlePressed() {
+    if (widget.onPressed != null)
+      widget.onPressed!(widget.isExpanded);
+  }
+
+  /// Default icon colors and opacities for when [Theme.brightness] is set to
+  /// [Brightness.light] are based on the
+  /// [Material Design system icon specifications](https://material.io/design/iconography/system-icons.html#color).
+  /// Icon colors and opacities for [Brightness.dark] are based on the
+  /// [Material Design dark theme specifications](https://material.io/design/color/dark-theme.html#ui-application)
+  Color get _iconColor {
+    if (widget.isExpanded && widget.expandedColor != null) {
+      return widget.expandedColor!;
+    }
+
+    if (widget.color != null) {
+      return widget.color!;
+    }
+
+    switch(Theme.of(context).brightness) {
+      case Brightness.light:
+        return Colors.black54;
+      case Brightness.dark:
+        return Colors.white60;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final String onTapHint = widget.isExpanded ? localizations.expandedIconTapHint : localizations.collapsedIconTapHint;
+
+    return Semantics(
+      onTapHint: widget.onPressed == null ? null : onTapHint,
+      child: IconButton(
+        padding: widget.padding,
+        iconSize: widget.size,
+        color: _iconColor,
+        disabledColor: widget.disabledColor,
+        onPressed: widget.onPressed == null ? null : _handlePressed,
+        icon: RotationTransition(
+          turns: _iconTurns,
+          child: const Icon(Icons.expand_more),
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/expansion_panel.dart b/lib/src/material/expansion_panel.dart
new file mode 100644
index 0000000..9444af2
--- /dev/null
+++ b/lib/src/material/expansion_panel.dart
@@ -0,0 +1,569 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'constants.dart';
+import 'expand_icon.dart';
+import 'ink_well.dart';
+import 'material_localizations.dart';
+import 'mergeable_material.dart';
+import 'shadows.dart';
+import 'theme.dart';
+
+const double _kPanelHeaderCollapsedHeight = kMinInteractiveDimension;
+const EdgeInsets _kPanelHeaderExpandedDefaultPadding = EdgeInsets.symmetric(
+    vertical: 64.0 - _kPanelHeaderCollapsedHeight
+);
+
+class _SaltedKey<S, V> extends LocalKey {
+  const _SaltedKey(this.salt, this.value);
+
+  final S salt;
+  final V value;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _SaltedKey<S, V>
+        && other.salt == salt
+        && other.value == value;
+  }
+
+  @override
+  int get hashCode => hashValues(runtimeType, salt, value);
+
+  @override
+  String toString() {
+    final String saltString = S == String ? "<'$salt'>" : '<$salt>';
+    final String valueString = V == String ? "<'$value'>" : '<$value>';
+    return '[$saltString $valueString]';
+  }
+}
+
+/// Signature for the callback that's called when an [ExpansionPanel] is
+/// expanded or collapsed.
+///
+/// The position of the panel within an [ExpansionPanelList] is given by
+/// [panelIndex].
+typedef ExpansionPanelCallback = void Function(int panelIndex, bool isExpanded);
+
+/// Signature for the callback that's called when the header of the
+/// [ExpansionPanel] needs to rebuild.
+typedef ExpansionPanelHeaderBuilder = Widget Function(BuildContext context, bool isExpanded);
+
+/// A material expansion panel. It has a header and a body and can be either
+/// expanded or collapsed. The body of the panel is only visible when it is
+/// expanded.
+///
+/// Expansion panels are only intended to be used as children for
+/// [ExpansionPanelList].
+///
+/// See [ExpansionPanelList] for a sample implementation.
+///
+/// See also:
+///
+///  * [ExpansionPanelList]
+///  * <https://material.io/design/components/lists.html#types>
+class ExpansionPanel {
+  /// Creates an expansion panel to be used as a child for [ExpansionPanelList].
+  /// See [ExpansionPanelList] for an example on how to use this widget.
+  ///
+  /// The [headerBuilder], [body], and [isExpanded] arguments must not be null.
+  ExpansionPanel({
+    required this.headerBuilder,
+    required this.body,
+    this.isExpanded = false,
+    this.canTapOnHeader = false,
+    this.backgroundColor,
+  }) : assert(headerBuilder != null),
+       assert(body != null),
+       assert(isExpanded != null),
+       assert(canTapOnHeader != null);
+
+  /// The widget builder that builds the expansion panels' header.
+  final ExpansionPanelHeaderBuilder headerBuilder;
+
+  /// The body of the expansion panel that's displayed below the header.
+  ///
+  /// This widget is visible only when the panel is expanded.
+  final Widget body;
+
+  /// Whether the panel is expanded.
+  ///
+  /// Defaults to false.
+  final bool isExpanded;
+
+  /// Whether tapping on the panel's header will expand/collapse it.
+  ///
+  /// Defaults to false.
+  final bool canTapOnHeader;
+
+  /// Defines the background color of the panel.
+  ///
+  /// Defaults to [ThemeData.cardColor].
+  final Color? backgroundColor;
+}
+
+/// An expansion panel that allows for radio-like functionality.
+/// This means that at any given time, at most, one [ExpansionPanelRadio]
+/// can remain expanded.
+///
+/// A unique identifier [value] must be assigned to each panel.
+/// This identifier allows the [ExpansionPanelList] to determine
+/// which [ExpansionPanelRadio] instance should be expanded.
+///
+/// See [ExpansionPanelList.radio] for a sample implementation.
+class ExpansionPanelRadio extends ExpansionPanel {
+
+  /// An expansion panel that allows for radio functionality.
+  ///
+  /// A unique [value] must be passed into the constructor. The
+  /// [headerBuilder], [body], [value] must not be null.
+  ExpansionPanelRadio({
+    required this.value,
+    required ExpansionPanelHeaderBuilder headerBuilder,
+    required Widget body,
+    bool canTapOnHeader = false,
+    Color? backgroundColor,
+  }) : assert(value != null),
+      super(
+        body: body,
+        headerBuilder: headerBuilder,
+        canTapOnHeader: canTapOnHeader,
+        backgroundColor: backgroundColor,
+      );
+
+  /// The value that uniquely identifies a radio panel so that the currently
+  /// selected radio panel can be identified.
+  final Object value;
+}
+
+/// A material expansion panel list that lays out its children and animates
+/// expansions.
+///
+/// Note that [expansionCallback] behaves differently for [ExpansionPanelList]
+/// and [ExpansionPanelList.radio].
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+///
+/// Here is a simple example of how to implement ExpansionPanelList.
+///
+/// ```dart preamble
+/// // stores ExpansionPanel state information
+/// class Item {
+///   Item({
+///     this.expandedValue,
+///     this.headerValue,
+///     this.isExpanded = false,
+///   });
+///
+///   String expandedValue;
+///   String headerValue;
+///   bool isExpanded;
+/// }
+///
+/// List<Item> generateItems(int numberOfItems) {
+///   return List.generate(numberOfItems, (int index) {
+///     return Item(
+///       headerValue: 'Panel $index',
+///       expandedValue: 'This is item number $index',
+///     );
+///   });
+/// }
+/// ```
+///
+/// ```dart
+/// List<Item> _data = generateItems(8);
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return SingleChildScrollView(
+///     child: Container(
+///       child: _buildPanel(),
+///     ),
+///   );
+/// }
+///
+/// Widget _buildPanel() {
+///   return ExpansionPanelList(
+///     expansionCallback: (int index, bool isExpanded) {
+///       setState(() {
+///         _data[index].isExpanded = !isExpanded;
+///       });
+///     },
+///     children: _data.map<ExpansionPanel>((Item item) {
+///       return ExpansionPanel(
+///         headerBuilder: (BuildContext context, bool isExpanded) {
+///           return ListTile(
+///             title: Text(item.headerValue),
+///           );
+///         },
+///         body: ListTile(
+///           title: Text(item.expandedValue),
+///           subtitle: Text('To delete this panel, tap the trash can icon'),
+///           trailing: Icon(Icons.delete),
+///           onTap: () {
+///             setState(() {
+///               _data.removeWhere((currentItem) => item == currentItem);
+///             });
+///           }
+///         ),
+///         isExpanded: item.isExpanded,
+///       );
+///     }).toList(),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ExpansionPanel]
+///  * [ExpansionPanelList.radio]
+///  * <https://material.io/design/components/lists.html#types>
+class ExpansionPanelList extends StatefulWidget {
+  /// Creates an expansion panel list widget. The [expansionCallback] is
+  /// triggered when an expansion panel expand/collapse button is pushed.
+  ///
+  /// The [children] and [animationDuration] arguments must not be null.
+  const ExpansionPanelList({
+    Key? key,
+    this.children = const <ExpansionPanel>[],
+    this.expansionCallback,
+    this.animationDuration = kThemeAnimationDuration,
+    this.expandedHeaderPadding = _kPanelHeaderExpandedDefaultPadding,
+    this.dividerColor,
+    this.elevation = 2,
+  }) : assert(children != null),
+       assert(animationDuration != null),
+       _allowOnlyOnePanelOpen = false,
+       initialOpenPanelValue = null,
+       super(key: key);
+
+  /// Creates a radio expansion panel list widget.
+  ///
+  /// This widget allows for at most one panel in the list to be open.
+  /// The expansion panel callback is triggered when an expansion panel
+  /// expand/collapse button is pushed. The [children] and [animationDuration]
+  /// arguments must not be null. The [children] objects must be instances
+  /// of [ExpansionPanelRadio].
+  ///
+  /// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+  ///
+  /// Here is a simple example of how to implement ExpansionPanelList.radio.
+  ///
+  /// ```dart preamble
+  /// // stores ExpansionPanel state information
+  /// class Item {
+  ///   Item({
+  ///     this.id,
+  ///     this.expandedValue,
+  ///     this.headerValue,
+  ///   });
+  ///
+  ///   int id;
+  ///   String expandedValue;
+  ///   String headerValue;
+  /// }
+  ///
+  /// List<Item> generateItems(int numberOfItems) {
+  ///   return List.generate(numberOfItems, (int index) {
+  ///     return Item(
+  ///       id: index,
+  ///       headerValue: 'Panel $index',
+  ///       expandedValue: 'This is item number $index',
+  ///     );
+  ///   });
+  /// }
+  /// ```
+  ///
+  /// ```dart
+  /// List<Item> _data = generateItems(8);
+  ///
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return SingleChildScrollView(
+  ///     child: Container(
+  ///       child: _buildPanel(),
+  ///     ),
+  ///   );
+  /// }
+  ///
+  /// Widget _buildPanel() {
+  ///   return ExpansionPanelList.radio(
+  ///     initialOpenPanelValue: 2,
+  ///     children: _data.map<ExpansionPanelRadio>((Item item) {
+  ///       return ExpansionPanelRadio(
+  ///         value: item.id,
+  ///         headerBuilder: (BuildContext context, bool isExpanded) {
+  ///           return ListTile(
+  ///             title: Text(item.headerValue),
+  ///           );
+  ///         },
+  ///         body: ListTile(
+  ///           title: Text(item.expandedValue),
+  ///           subtitle: Text('To delete this panel, tap the trash can icon'),
+  ///           trailing: Icon(Icons.delete),
+  ///           onTap: () {
+  ///             setState(() {
+  ///               _data.removeWhere((currentItem) => item == currentItem);
+  ///             });
+  ///           }
+  ///         )
+  ///       );
+  ///     }).toList(),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  const ExpansionPanelList.radio({
+    Key? key,
+    this.children = const <ExpansionPanelRadio>[],
+    this.expansionCallback,
+    this.animationDuration = kThemeAnimationDuration,
+    this.initialOpenPanelValue,
+    this.expandedHeaderPadding = _kPanelHeaderExpandedDefaultPadding,
+    this.dividerColor,
+    this.elevation = 2,
+  }) : assert(children != null),
+       assert(animationDuration != null),
+       _allowOnlyOnePanelOpen = true,
+       super(key: key);
+
+  /// The children of the expansion panel list. They are laid out in a similar
+  /// fashion to [ListBody].
+  final List<ExpansionPanel> children;
+
+  /// The callback that gets called whenever one of the expand/collapse buttons
+  /// is pressed. The arguments passed to the callback are the index of the
+  /// pressed panel and whether the panel is currently expanded or not.
+  ///
+  /// If ExpansionPanelList.radio is used, the callback may be called a
+  /// second time if a different panel was previously open. The arguments
+  /// passed to the second callback are the index of the panel that will close
+  /// and false, marking that it will be closed.
+  ///
+  /// For ExpansionPanelList, the callback needs to setState when it's notified
+  /// about the closing/opening panel. On the other hand, the callback for
+  /// ExpansionPanelList.radio is simply meant to inform the parent widget of
+  /// changes, as the radio panels' open/close states are managed internally.
+  ///
+  /// This callback is useful in order to keep track of the expanded/collapsed
+  /// panels in a parent widget that may need to react to these changes.
+  final ExpansionPanelCallback? expansionCallback;
+
+  /// The duration of the expansion animation.
+  final Duration animationDuration;
+
+  // Whether multiple panels can be open simultaneously
+  final bool _allowOnlyOnePanelOpen;
+
+  /// The value of the panel that initially begins open. (This value is
+  /// only used when initializing with the [ExpansionPanelList.radio]
+  /// constructor.)
+  final Object? initialOpenPanelValue;
+
+  /// The padding that surrounds the panel header when expanded.
+  ///
+  /// By default, 16px of space is added to the header vertically (above and below)
+  /// during expansion.
+  final EdgeInsets expandedHeaderPadding;
+
+  /// Defines color for the divider when [ExpansionPanel.isExpanded] is false.
+  ///
+  /// If `dividerColor` is null, then [DividerThemeData.color] is used. If that
+  /// is null, then [ThemeData.dividerColor] is used.
+  final Color? dividerColor;
+
+  /// Defines elevation for the [ExpansionPanel] while it's expanded.
+  ///
+  /// This uses [kElevationToShadow] to simulate shadows, it does not use
+  /// [Material]'s arbitrary elevation feature.
+  ///
+  /// The following values can be used to define the elevation: 0, 1, 2, 3, 4, 6,
+  /// 8, 9, 12, 16, 24.
+  ///
+  /// By default, the value of elevation is 2.
+  final int elevation;
+
+  @override
+  State<StatefulWidget> createState() => _ExpansionPanelListState();
+}
+
+class _ExpansionPanelListState extends State<ExpansionPanelList> {
+  ExpansionPanelRadio? _currentOpenPanel;
+
+  @override
+  void initState() {
+    super.initState();
+    if (widget._allowOnlyOnePanelOpen) {
+      assert(_allIdentifiersUnique(), 'All ExpansionPanelRadio identifier values must be unique.');
+      if (widget.initialOpenPanelValue != null) {
+        _currentOpenPanel =
+          searchPanelByValue(widget.children.cast<ExpansionPanelRadio>(), widget.initialOpenPanelValue);
+      }
+    }
+  }
+
+  @override
+  void didUpdateWidget(ExpansionPanelList oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    if (widget._allowOnlyOnePanelOpen) {
+      assert(_allIdentifiersUnique(), 'All ExpansionPanelRadio identifier values must be unique.');
+      // If the previous widget was non-radio ExpansionPanelList, initialize the
+      // open panel to widget.initialOpenPanelValue
+      if (!oldWidget._allowOnlyOnePanelOpen) {
+        _currentOpenPanel =
+          searchPanelByValue(widget.children.cast<ExpansionPanelRadio>(), widget.initialOpenPanelValue);
+      }
+    } else {
+      _currentOpenPanel = null;
+    }
+  }
+
+  bool _allIdentifiersUnique() {
+    final Map<Object, bool> identifierMap = <Object, bool>{};
+    for (final ExpansionPanelRadio child in widget.children.cast<ExpansionPanelRadio>()) {
+      identifierMap[child.value] = true;
+    }
+    return identifierMap.length == widget.children.length;
+  }
+
+  bool _isChildExpanded(int index) {
+    if (widget._allowOnlyOnePanelOpen) {
+      final ExpansionPanelRadio radioWidget = widget.children[index] as ExpansionPanelRadio;
+      return _currentOpenPanel?.value == radioWidget.value;
+    }
+    return widget.children[index].isExpanded;
+  }
+
+  void _handlePressed(bool isExpanded, int index) {
+    if (widget.expansionCallback != null)
+      widget.expansionCallback!(index, isExpanded);
+
+    if (widget._allowOnlyOnePanelOpen) {
+      final ExpansionPanelRadio pressedChild = widget.children[index] as ExpansionPanelRadio;
+
+      // If another ExpansionPanelRadio was already open, apply its
+      // expansionCallback (if any) to false, because it's closing.
+      for (int childIndex = 0; childIndex < widget.children.length; childIndex += 1) {
+        final ExpansionPanelRadio child = widget.children[childIndex] as ExpansionPanelRadio;
+        if (widget.expansionCallback != null &&
+            childIndex != index &&
+            child.value == _currentOpenPanel?.value)
+          widget.expansionCallback!(childIndex, false);
+      }
+
+      setState(() {
+        _currentOpenPanel = isExpanded ? null : pressedChild;
+      });
+    }
+  }
+
+  ExpansionPanelRadio? searchPanelByValue(List<ExpansionPanelRadio> panels, Object? value)  {
+    for (final ExpansionPanelRadio panel in panels) {
+      if (panel.value == value)
+        return panel;
+    }
+    return null;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(kElevationToShadow.containsKey(widget.elevation),
+      'Invalid value for elevation. See the kElevationToShadow constant for'
+      ' possible elevation values.'
+    );
+
+    final List<MergeableMaterialItem> items = <MergeableMaterialItem>[];
+
+    for (int index = 0; index < widget.children.length; index += 1) {
+      if (_isChildExpanded(index) && index != 0 && !_isChildExpanded(index - 1))
+        items.add(MaterialGap(key: _SaltedKey<BuildContext, int>(context, index * 2 - 1)));
+
+      final ExpansionPanel child = widget.children[index];
+      final Widget headerWidget = child.headerBuilder(
+        context,
+        _isChildExpanded(index),
+      );
+
+      Widget expandIconContainer = Container(
+        margin: const EdgeInsetsDirectional.only(end: 8.0),
+        child: ExpandIcon(
+          isExpanded: _isChildExpanded(index),
+          padding: const EdgeInsets.all(16.0),
+          onPressed: !child.canTapOnHeader
+              ? (bool isExpanded) => _handlePressed(isExpanded, index)
+              : null,
+        ),
+      );
+      if (!child.canTapOnHeader) {
+        final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+        expandIconContainer = Semantics(
+          label: _isChildExpanded(index)? localizations.expandedIconTapHint : localizations.collapsedIconTapHint,
+          container: true,
+          child: expandIconContainer,
+        );
+      }
+      Widget header = Row(
+        children: <Widget>[
+          Expanded(
+            child: AnimatedContainer(
+              duration: widget.animationDuration,
+              curve: Curves.fastOutSlowIn,
+              margin: _isChildExpanded(index) ? widget.expandedHeaderPadding : EdgeInsets.zero,
+              child: ConstrainedBox(
+                constraints: const BoxConstraints(minHeight: _kPanelHeaderCollapsedHeight),
+                child: headerWidget,
+              ),
+            ),
+          ),
+          expandIconContainer,
+        ],
+      );
+      if (child.canTapOnHeader) {
+        header = MergeSemantics(
+          child: InkWell(
+            onTap: () => _handlePressed(_isChildExpanded(index), index),
+            child: header,
+          ),
+        );
+      }
+      items.add(
+        MaterialSlice(
+          key: _SaltedKey<BuildContext, int>(context, index * 2),
+          color: child.backgroundColor,
+          child: Column(
+            children: <Widget>[
+              header,
+              AnimatedCrossFade(
+                firstChild: Container(height: 0.0),
+                secondChild: child.body,
+                firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
+                secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
+                sizeCurve: Curves.fastOutSlowIn,
+                crossFadeState: _isChildExpanded(index) ? CrossFadeState.showSecond : CrossFadeState.showFirst,
+                duration: widget.animationDuration,
+              ),
+            ],
+          ),
+        ),
+      );
+
+      if (_isChildExpanded(index) && index != widget.children.length - 1)
+        items.add(MaterialGap(key: _SaltedKey<BuildContext, int>(context, index * 2 + 1)));
+    }
+
+    return MergeableMaterial(
+      hasDividers: true,
+      dividerColor: widget.dividerColor,
+      elevation: widget.elevation,
+      children: items,
+    );
+  }
+}
diff --git a/lib/src/material/expansion_tile.dart b/lib/src/material/expansion_tile.dart
new file mode 100644
index 0000000..1660cdc
--- /dev/null
+++ b/lib/src/material/expansion_tile.dart
@@ -0,0 +1,299 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'icons.dart';
+import 'list_tile.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+const Duration _kExpand = Duration(milliseconds: 200);
+
+/// A single-line [ListTile] with a trailing button that expands or collapses
+/// the tile to reveal or hide the [children].
+///
+/// This widget is typically used with [ListView] to create an
+/// "expand / collapse" list entry. When used with scrolling widgets like
+/// [ListView], a unique [PageStorageKey] must be specified to enable the
+/// [ExpansionTile] to save and restore its expanded state when it is scrolled
+/// in and out of view.
+///
+/// See also:
+///
+///  * [ListTile], useful for creating expansion tile [children] when the
+///    expansion tile represents a sublist.
+///  * The "Expand/collapse" section of
+///    <https://material.io/guidelines/components/lists-controls.html>.
+class ExpansionTile extends StatefulWidget {
+  /// Creates a single-line [ListTile] with a trailing button that expands or collapses
+  /// the tile to reveal or hide the [children]. The [initiallyExpanded] property must
+  /// be non-null.
+  const ExpansionTile({
+    Key? key,
+    this.leading,
+    required this.title,
+    this.subtitle,
+    this.backgroundColor,
+    this.onExpansionChanged,
+    this.children = const <Widget>[],
+    this.trailing,
+    this.initiallyExpanded = false,
+    this.maintainState = false,
+    this.tilePadding,
+    this.expandedCrossAxisAlignment,
+    this.expandedAlignment,
+    this.childrenPadding,
+    this.collapsedBackgroundColor,
+  }) : assert(initiallyExpanded != null),
+       assert(maintainState != null),
+       assert(
+       expandedCrossAxisAlignment != CrossAxisAlignment.baseline,
+       'CrossAxisAlignment.baseline is not supported since the expanded children '
+           'are aligned in a column, not a row. Try to use another constant.',
+       ),
+       super(key: key);
+
+  /// A widget to display before the title.
+  ///
+  /// Typically a [CircleAvatar] widget.
+  final Widget? leading;
+
+  /// The primary content of the list item.
+  ///
+  /// Typically a [Text] widget.
+  final Widget title;
+
+  /// Additional content displayed below the title.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? subtitle;
+
+  /// Called when the tile expands or collapses.
+  ///
+  /// When the tile starts expanding, this function is called with the value
+  /// true. When the tile starts collapsing, this function is called with
+  /// the value false.
+  final ValueChanged<bool>? onExpansionChanged;
+
+  /// The widgets that are displayed when the tile expands.
+  ///
+  /// Typically [ListTile] widgets.
+  final List<Widget> children;
+
+  /// The color to display behind the sublist when expanded.
+  final Color? backgroundColor;
+
+  /// When not null, defines the background color of tile when the sublist is collapsed.
+  final Color? collapsedBackgroundColor;
+
+  /// A widget to display instead of a rotating arrow icon.
+  final Widget? trailing;
+
+  /// Specifies if the list tile is initially expanded (true) or collapsed (false, the default).
+  final bool initiallyExpanded;
+
+  /// Specifies whether the state of the children is maintained when the tile expands and collapses.
+  ///
+  /// When true, the children are kept in the tree while the tile is collapsed.
+  /// When false (default), the children are removed from the tree when the tile is
+  /// collapsed and recreated upon expansion.
+  final bool maintainState;
+
+  /// Specifies padding for the [ListTile].
+  ///
+  /// Analogous to [ListTile.contentPadding], this property defines the insets for
+  /// the [leading], [title], [subtitle] and [trailing] widgets. It does not inset
+  /// the expanded [children] widgets.
+  ///
+  /// When the value is null, the tile's padding is `EdgeInsets.symmetric(horizontal: 16.0)`.
+  final EdgeInsetsGeometry? tilePadding;
+
+  /// Specifies the alignment of [children], which are arranged in a column when
+  /// the tile is expanded.
+  ///
+  /// The internals of the expanded tile make use of a [Column] widget for
+  /// [children], and [Align] widget to align the column. The `expandedAlignment`
+  /// parameter is passed directly into the [Align].
+  ///
+  /// Modifying this property controls the alignment of the column within the
+  /// expanded tile, not the alignment of [children] widgets within the column.
+  /// To align each child within [children], see [expandedCrossAxisAlignment].
+  ///
+  /// The width of the column is the width of the widest child widget in [children].
+  ///
+  /// When the value is null, the value of `expandedAlignment` is [Alignment.center].
+  final Alignment? expandedAlignment;
+
+  /// Specifies the alignment of each child within [children] when the tile is expanded.
+  ///
+  /// The internals of the expanded tile make use of a [Column] widget for
+  /// [children], and the `crossAxisAlignment` parameter is passed directly into the [Column].
+  ///
+  /// Modifying this property controls the cross axis alignment of each child
+  /// within its [Column]. Note that the width of the [Column] that houses
+  /// [children] will be the same as the widest child widget in [children]. It is
+  /// not necessarily the width of [Column] is equal to the width of expanded tile.
+  ///
+  /// To align the [Column] along the expanded tile, use the [expandedAlignment] property
+  /// instead.
+  ///
+  /// When the value is null, the value of `expandedCrossAxisAlignment` is [CrossAxisAlignment.center].
+  final CrossAxisAlignment? expandedCrossAxisAlignment;
+
+  /// Specifies padding for [children].
+  ///
+  /// When the value is null, the value of `childrenPadding` is [EdgeInsets.zero].
+  final EdgeInsetsGeometry? childrenPadding;
+
+  @override
+  _ExpansionTileState createState() => _ExpansionTileState();
+}
+
+class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProviderStateMixin {
+  static final Animatable<double> _easeOutTween = CurveTween(curve: Curves.easeOut);
+  static final Animatable<double> _easeInTween = CurveTween(curve: Curves.easeIn);
+  static final Animatable<double> _halfTween = Tween<double>(begin: 0.0, end: 0.5);
+
+  final ColorTween _borderColorTween = ColorTween();
+  final ColorTween _headerColorTween = ColorTween();
+  final ColorTween _iconColorTween = ColorTween();
+  final ColorTween _backgroundColorTween = ColorTween();
+
+  late AnimationController _controller;
+  late Animation<double> _iconTurns;
+  late Animation<double> _heightFactor;
+  late Animation<Color?> _borderColor;
+  late Animation<Color?> _headerColor;
+  late Animation<Color?> _iconColor;
+  late Animation<Color?> _backgroundColor;
+
+  bool _isExpanded = false;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(duration: _kExpand, vsync: this);
+    _heightFactor = _controller.drive(_easeInTween);
+    _iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
+    _borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween));
+    _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween));
+    _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween));
+    _backgroundColor = _controller.drive(_backgroundColorTween.chain(_easeOutTween));
+
+    _isExpanded = PageStorage.of(context)?.readState(context) as bool? ?? widget.initiallyExpanded;
+    if (_isExpanded)
+      _controller.value = 1.0;
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  void _handleTap() {
+    setState(() {
+      _isExpanded = !_isExpanded;
+      if (_isExpanded) {
+        _controller.forward();
+      } else {
+        _controller.reverse().then<void>((void value) {
+          if (!mounted)
+            return;
+          setState(() {
+            // Rebuild without widget.children.
+          });
+        });
+      }
+      PageStorage.of(context)?.writeState(context, _isExpanded);
+    });
+    if (widget.onExpansionChanged != null)
+      widget.onExpansionChanged!(_isExpanded);
+  }
+
+  Widget _buildChildren(BuildContext context, Widget? child) {
+    final Color borderSideColor = _borderColor.value ?? Colors.transparent;
+
+    return Container(
+      decoration: BoxDecoration(
+        color: _backgroundColor.value ?? Colors.transparent,
+        border: Border(
+          top: BorderSide(color: borderSideColor),
+          bottom: BorderSide(color: borderSideColor),
+        ),
+      ),
+      child: Column(
+        mainAxisSize: MainAxisSize.min,
+        children: <Widget>[
+          ListTileTheme.merge(
+            iconColor: _iconColor.value,
+            textColor: _headerColor.value,
+            child: ListTile(
+              onTap: _handleTap,
+              contentPadding: widget.tilePadding,
+              leading: widget.leading,
+              title: widget.title,
+              subtitle: widget.subtitle,
+              trailing: widget.trailing ?? RotationTransition(
+                turns: _iconTurns,
+                child: const Icon(Icons.expand_more),
+              ),
+            ),
+          ),
+          ClipRect(
+            child: Align(
+              alignment: widget.expandedAlignment ?? Alignment.center,
+              heightFactor: _heightFactor.value,
+              child: child,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  @override
+  void didChangeDependencies() {
+    final ThemeData theme = Theme.of(context);
+    _borderColorTween.end = theme.dividerColor;
+    _headerColorTween
+      ..begin = theme.textTheme.subtitle1!.color
+      ..end = theme.accentColor;
+    _iconColorTween
+      ..begin = theme.unselectedWidgetColor
+      ..end = theme.accentColor;
+    _backgroundColorTween
+      ..begin = widget.collapsedBackgroundColor
+      ..end = widget.backgroundColor;
+    super.didChangeDependencies();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final bool closed = !_isExpanded && _controller.isDismissed;
+    final bool shouldRemoveChildren = closed && !widget.maintainState;
+
+    final Widget result = Offstage(
+      child: TickerMode(
+        child: Padding(
+          padding: widget.childrenPadding ?? EdgeInsets.zero,
+          child: Column(
+            crossAxisAlignment: widget.expandedCrossAxisAlignment ?? CrossAxisAlignment.center,
+            children: widget.children,
+          ),
+        ),
+        enabled: !closed,
+      ),
+      offstage: closed
+    );
+
+    return AnimatedBuilder(
+      animation: _controller.view,
+      builder: _buildChildren,
+      child: shouldRemoveChildren ? null : result,
+    );
+  }
+}
diff --git a/lib/src/material/feedback.dart b/lib/src/material/feedback.dart
new file mode 100644
index 0000000..d5e4c68
--- /dev/null
+++ b/lib/src/material/feedback.dart
@@ -0,0 +1,171 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+import 'package:flute/semantics.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'theme.dart';
+
+/// Provides platform-specific acoustic and/or haptic feedback for certain
+/// actions.
+///
+/// For example, to play the Android-typically click sound when a button is
+/// tapped, call [forTap]. For the Android-specific vibration when long pressing
+/// an element, call [forLongPress]. Alternatively, you can also wrap your
+/// [GestureDetector.onTap] or [GestureDetector.onLongPress] callback in
+/// [wrapForTap] or [wrapForLongPress] to achieve the same (see example code
+/// below).
+///
+/// Calling any of these methods is a no-op on iOS as actions on that platform
+/// typically don't provide haptic or acoustic feedback.
+///
+/// All methods in this class are usually called from within a
+/// [StatelessWidget.build] method or from a [State]'s methods as you have to
+/// provide a [BuildContext].
+///
+/// {@tool snippet}
+///
+/// To trigger platform-specific feedback before executing the actual callback:
+///
+/// ```dart
+/// class WidgetWithWrappedHandler extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return GestureDetector(
+///       onTap: Feedback.wrapForTap(_onTapHandler, context),
+///       onLongPress: Feedback.wrapForLongPress(_onLongPressHandler, context),
+///       child: const Text('X'),
+///     );
+///   }
+///
+///   void _onTapHandler() {
+///     // Respond to tap.
+///   }
+///
+///   void _onLongPressHandler() {
+///     // Respond to long press.
+///   }
+/// }
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// Alternatively, you can also call [forTap] or [forLongPress] directly within
+/// your tap or long press handler:
+///
+/// ```dart
+/// class WidgetWithExplicitCall extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return GestureDetector(
+///       onTap: () {
+///         // Do some work (e.g. check if the tap is valid)
+///         Feedback.forTap(context);
+///         // Do more work (e.g. respond to the tap)
+///       },
+///       onLongPress: () {
+///         // Do some work (e.g. check if the long press is valid)
+///         Feedback.forLongPress(context);
+///         // Do more work (e.g. respond to the long press)
+///       },
+///       child: const Text('X'),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+class Feedback {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  Feedback._();
+
+  /// Provides platform-specific feedback for a tap.
+  ///
+  /// On Android the click system sound is played. On iOS this is a no-op.
+  ///
+  /// See also:
+  ///
+  ///  * [wrapForTap] to trigger platform-specific feedback before executing a
+  ///    [GestureTapCallback].
+  static Future<void> forTap(BuildContext context) async {
+    context.findRenderObject()!.sendSemanticsEvent(const TapSemanticEvent());
+    switch (_platform(context)) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+        return SystemSound.play(SystemSoundType.click);
+      case TargetPlatform.iOS:
+      case TargetPlatform.linux:
+      case TargetPlatform.macOS:
+      case TargetPlatform.windows:
+        return Future<void>.value();
+    }
+  }
+
+  /// Wraps a [GestureTapCallback] to provide platform specific feedback for a
+  /// tap before the provided callback is executed.
+  ///
+  /// On Android the platform-typical click system sound is played. On iOS this
+  /// is a no-op as that platform usually doesn't provide feedback for a tap.
+  ///
+  /// See also:
+  ///
+  ///  * [forTap] to just trigger the platform-specific feedback without wrapping
+  ///    a [GestureTapCallback].
+  static GestureTapCallback? wrapForTap(GestureTapCallback? callback, BuildContext context) {
+    if (callback == null)
+      return null;
+    return () {
+      Feedback.forTap(context);
+      callback();
+    };
+  }
+
+  /// Provides platform-specific feedback for a long press.
+  ///
+  /// On Android the platform-typical vibration is triggered. On iOS this is a
+  /// no-op as that platform usually doesn't provide feedback for long presses.
+  ///
+  /// See also:
+  ///
+  ///  * [wrapForLongPress] to trigger platform-specific feedback before
+  ///    executing a [GestureLongPressCallback].
+  static Future<void> forLongPress(BuildContext context) {
+    context.findRenderObject()!.sendSemanticsEvent(const LongPressSemanticsEvent());
+    switch (_platform(context)) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+        return HapticFeedback.vibrate();
+      case TargetPlatform.iOS:
+      case TargetPlatform.linux:
+      case TargetPlatform.macOS:
+      case TargetPlatform.windows:
+        return Future<void>.value();
+    }
+  }
+
+  /// Wraps a [GestureLongPressCallback] to provide platform specific feedback
+  /// for a long press before the provided callback is executed.
+  ///
+  /// On Android the platform-typical vibration is triggered. On iOS this
+  /// is a no-op as that platform usually doesn't provide feedback for a long
+  /// press.
+  ///
+  /// See also:
+  ///
+  ///  * [forLongPress] to just trigger the platform-specific feedback without
+  ///    wrapping a [GestureLongPressCallback].
+  static GestureLongPressCallback? wrapForLongPress(GestureLongPressCallback? callback, BuildContext context) {
+    if (callback == null)
+      return null;
+    return () {
+      Feedback.forLongPress(context);
+      callback();
+    };
+  }
+
+  static TargetPlatform _platform(BuildContext context) => Theme.of(context).platform;
+}
diff --git a/lib/src/material/flat_button.dart b/lib/src/material/flat_button.dart
new file mode 100644
index 0000000..a1fda4d
--- /dev/null
+++ b/lib/src/material/flat_button.dart
@@ -0,0 +1,312 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'button.dart';
+import 'button_theme.dart';
+import 'material_button.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// A material design "flat button".
+///
+/// ### This class is obsolete, please use [TextButton] instead.
+///
+/// FlatButton, RaisedButton, and OutlineButton have been replaced by
+/// TextButton, ElevatedButton, and OutlinedButton respectively.
+/// ButtonTheme has been replaced by TextButtonTheme,
+/// ElevatedButtonTheme, and OutlinedButtonTheme. The original classes
+/// will be deprecated soon, please migrate code that uses them.
+/// There's a detailed migration guide for the new button and button
+/// theme classes in
+/// [flutter.dev/go/material-button-migration-guide](https://flutter.dev/go/material-button-migration-guide).
+///
+/// A flat button is a text label displayed on a (zero elevation) [Material]
+/// widget that reacts to touches by filling with color.
+///
+/// Use flat buttons on toolbars, in dialogs, or inline with other content but
+/// offset from that content with padding so that the button's presence is
+/// obvious. Flat buttons intentionally do not have visible borders and must
+/// therefore rely on their position relative to other content for context. In
+/// dialogs and cards, they should be grouped together in one of the bottom
+/// corners. Avoid using flat buttons where they would blend in with other
+/// content, for example in the middle of lists.
+///
+/// Material design flat buttons have an all-caps label, some internal padding,
+/// and some defined dimensions. To have a part of your application be
+/// interactive, with ink splashes, without also committing to these stylistic
+/// choices, consider using [InkWell] instead.
+///
+/// If the [onPressed] and [onLongPress] callbacks are null, then this button will be disabled,
+/// will not react to touch, and will be colored as specified by
+/// the [disabledColor] property instead of the [color] property. If you are
+/// trying to change the button's [color] and it is not having any effect, check
+/// that you are passing a non-null [onPressed] handler.
+///
+/// Flat buttons have a minimum size of 88.0 by 36.0 which can be overridden
+/// with [ButtonTheme].
+///
+/// The [clipBehavior] argument must not be null.
+///
+/// {@tool snippet}
+///
+/// This example shows a simple [FlatButton].
+///
+/// ![A simple FlatButton](https://flutter.github.io/assets-for-api-docs/assets/material/flat_button.png)
+///
+/// ```dart
+/// FlatButton(
+///   onPressed: () {
+///     /*...*/
+///   },
+///   child: Text(
+///     "Flat Button",
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+///
+/// This example shows a [FlatButton] that is normally white-on-blue,
+/// with splashes rendered in a different shade of blue.
+/// It turns black-on-grey when disabled.
+/// The button has 8px of padding on each side, and the text is 20px high.
+///
+/// ![A FlatButton with white text on a blue background](https://flutter.github.io/assets-for-api-docs/assets/material/flat_button_properties.png)
+///
+/// ```dart
+/// FlatButton(
+///   color: Colors.blue,
+///   textColor: Colors.white,
+///   disabledColor: Colors.grey,
+///   disabledTextColor: Colors.black,
+///   padding: EdgeInsets.all(8.0),
+///   splashColor: Colors.blueAccent,
+///   onPressed: () {
+///     /*...*/
+///   },
+///   child: Text(
+///     "Flat Button",
+///     style: TextStyle(fontSize: 20.0),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [RaisedButton], a filled button whose material elevates when pressed.
+///  * [DropdownButton], which offers the user a choice of a number of options.
+///  * [SimpleDialogOption], which is used in [SimpleDialog]s.
+///  * [IconButton], to create buttons that just contain icons.
+///  * [InkWell], which implements the ink splash part of a flat button.
+///  * [RawMaterialButton], the widget this widget is based on.
+///  * <https://material.io/design/components/buttons.html>
+///  * Cookbook: [Build a form with validation](https://flutter.dev/docs/cookbook/forms/validation)
+class FlatButton extends MaterialButton {
+  /// Create a simple text button.
+  ///
+  /// The [autofocus] and [clipBehavior] arguments must not be null.
+  const FlatButton({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ValueChanged<bool>? onHighlightChanged,
+    MouseCursor? mouseCursor,
+    ButtonTextTheme? textTheme,
+    Color? textColor,
+    Color? disabledTextColor,
+    Color? color,
+    Color? disabledColor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    Brightness? colorBrightness,
+    EdgeInsetsGeometry? padding,
+    VisualDensity? visualDensity,
+    ShapeBorder? shape,
+    Clip clipBehavior = Clip.none,
+    FocusNode? focusNode,
+    bool autofocus = false,
+    MaterialTapTargetSize? materialTapTargetSize,
+    required Widget child,
+    double? height,
+    double? minWidth,
+  }) : assert(clipBehavior != null),
+       assert(autofocus != null),
+       super(
+         key: key,
+         height: height,
+         minWidth: minWidth,
+         onPressed: onPressed,
+         onLongPress: onLongPress,
+         onHighlightChanged: onHighlightChanged,
+         mouseCursor: mouseCursor,
+         textTheme: textTheme,
+         textColor: textColor,
+         disabledTextColor: disabledTextColor,
+         color: color,
+         disabledColor: disabledColor,
+         focusColor: focusColor,
+         hoverColor: hoverColor,
+         highlightColor: highlightColor,
+         splashColor: splashColor,
+         colorBrightness: colorBrightness,
+         padding: padding,
+         visualDensity: visualDensity,
+         shape: shape,
+         clipBehavior: clipBehavior,
+         focusNode: focusNode,
+         autofocus: autofocus,
+         materialTapTargetSize: materialTapTargetSize,
+         child: child,
+      );
+
+  /// Create a text button from a pair of widgets that serve as the button's
+  /// [icon] and [label].
+  ///
+  /// The icon and label are arranged in a row and padded by 12 logical pixels
+  /// at the start, and 16 at the end, with an 8 pixel gap in between.
+  ///
+  /// The [icon], [label], and [clipBehavior] arguments must not be null.
+  factory FlatButton.icon({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ValueChanged<bool>? onHighlightChanged,
+    MouseCursor? mouseCursor,
+    ButtonTextTheme? textTheme,
+    Color? textColor,
+    Color? disabledTextColor,
+    Color? color,
+    Color? disabledColor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    Brightness? colorBrightness,
+    EdgeInsetsGeometry? padding,
+    ShapeBorder? shape,
+    Clip clipBehavior,
+    FocusNode? focusNode,
+    bool autofocus,
+    MaterialTapTargetSize? materialTapTargetSize,
+    required Widget icon,
+    required Widget label,
+    double? minWidth,
+    double? height,
+  }) = _FlatButtonWithIcon;
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final ButtonThemeData buttonTheme = ButtonTheme.of(context);
+    return RawMaterialButton(
+      onPressed: onPressed,
+      onLongPress: onLongPress,
+      onHighlightChanged: onHighlightChanged,
+      mouseCursor: mouseCursor,
+      fillColor: buttonTheme.getFillColor(this),
+      textStyle: theme.textTheme.button!.copyWith(color: buttonTheme.getTextColor(this)),
+      focusColor: buttonTheme.getFocusColor(this),
+      hoverColor: buttonTheme.getHoverColor(this),
+      highlightColor: buttonTheme.getHighlightColor(this),
+      splashColor: buttonTheme.getSplashColor(this),
+      elevation: buttonTheme.getElevation(this),
+      focusElevation: buttonTheme.getFocusElevation(this),
+      hoverElevation: buttonTheme.getHoverElevation(this),
+      highlightElevation: buttonTheme.getHighlightElevation(this),
+      disabledElevation: buttonTheme.getDisabledElevation(this),
+      padding: buttonTheme.getPadding(this),
+      visualDensity: visualDensity ?? theme.visualDensity,
+      constraints: buttonTheme.getConstraints(this).copyWith(
+        minWidth: minWidth,
+        minHeight: height,
+      ),
+      shape: buttonTheme.getShape(this),
+      clipBehavior: clipBehavior,
+      focusNode: focusNode,
+      autofocus: autofocus,
+      materialTapTargetSize: buttonTheme.getMaterialTapTargetSize(this),
+      animationDuration: buttonTheme.getAnimationDuration(this),
+      child: child,
+    );
+  }
+}
+
+/// The type of FlatButtons created with [FlatButton.icon].
+///
+/// This class only exists to give FlatButtons created with [FlatButton.icon]
+/// a distinct class for the sake of [ButtonTheme]. It can not be instantiated.
+class _FlatButtonWithIcon extends FlatButton with MaterialButtonWithIconMixin {
+  _FlatButtonWithIcon({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ValueChanged<bool>? onHighlightChanged,
+    MouseCursor? mouseCursor,
+    ButtonTextTheme? textTheme,
+    Color? textColor,
+    Color? disabledTextColor,
+    Color? color,
+    Color? disabledColor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    Brightness? colorBrightness,
+    EdgeInsetsGeometry? padding,
+    ShapeBorder? shape,
+    Clip clipBehavior = Clip.none,
+    FocusNode? focusNode,
+    bool autofocus = false,
+    MaterialTapTargetSize? materialTapTargetSize,
+    required Widget icon,
+    required Widget label,
+    double? minWidth,
+    double? height,
+  }) : assert(icon != null),
+       assert(label != null),
+       assert(clipBehavior != null),
+       assert(autofocus != null),
+       super(
+         key: key,
+         onPressed: onPressed,
+         onLongPress: onLongPress,
+         onHighlightChanged: onHighlightChanged,
+         mouseCursor: mouseCursor,
+         textTheme: textTheme,
+         textColor: textColor,
+         disabledTextColor: disabledTextColor,
+         color: color,
+         disabledColor: disabledColor,
+         focusColor: focusColor,
+         hoverColor: hoverColor,
+         highlightColor: highlightColor,
+         splashColor: splashColor,
+         colorBrightness: colorBrightness,
+         padding: padding,
+         shape: shape,
+         clipBehavior: clipBehavior,
+         focusNode: focusNode,
+         autofocus: autofocus,
+         materialTapTargetSize: materialTapTargetSize,
+         child: Row(
+           mainAxisSize: MainAxisSize.min,
+           children: <Widget>[
+             icon,
+             const SizedBox(width: 8.0),
+             label,
+           ],
+         ),
+         minWidth: minWidth,
+         height: height,
+       );
+
+}
diff --git a/lib/src/material/flexible_space_bar.dart b/lib/src/material/flexible_space_bar.dart
new file mode 100644
index 0000000..392d61f
--- /dev/null
+++ b/lib/src/material/flexible_space_bar.dart
@@ -0,0 +1,470 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' as ui;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'constants.dart';
+import 'theme.dart';
+
+/// The collapsing effect while the space bar collapses from its full size.
+enum CollapseMode {
+  /// The background widget will scroll in a parallax fashion.
+  parallax,
+
+  /// The background widget pin in place until it reaches the min extent.
+  pin,
+
+  /// The background widget will act as normal with no collapsing effect.
+  none,
+}
+
+/// The stretching effect while the space bar stretches beyond its full size.
+enum StretchMode {
+  /// The background widget will expand to fill the extra space.
+  zoomBackground,
+
+  /// The background will blur using a [ImageFilter.blur] effect.
+  blurBackground,
+
+  /// The title will fade away as the user over-scrolls.
+  fadeTitle,
+}
+
+/// The part of a material design [AppBar] that expands, collapses, and
+/// stretches.
+///
+/// Most commonly used in the [SliverAppBar.flexibleSpace] field, a flexible
+/// space bar expands and contracts as the app scrolls so that the [AppBar]
+/// reaches from the top of the app to the top of the scrolling contents of the
+/// app. When using [SliverAppBar.flexibleSpace], the [SliverAppBar.expandedHeight]
+/// must be large enough to accommodate the [SliverAppBar.flexibleSpace] widget.
+///
+/// Furthermore is included functionality for stretch behavior. When
+/// [SliverAppBar.stretch] is true, and your [ScrollPhysics] allow for
+/// overscroll, this space will stretch with the overscroll.
+///
+/// The widget that sizes the [AppBar] must wrap it in the widget returned by
+/// [FlexibleSpaceBar.createSettings], to convey sizing information down to the
+/// [FlexibleSpaceBar].
+///
+/// {@tool dartpad --template=freeform_no_null_safety}
+/// This sample application demonstrates the different features of the
+/// [FlexibleSpaceBar] when used in a [SliverAppBar]. This app bar is configured
+/// to stretch into the overscroll space, and uses the
+/// [FlexibleSpaceBar.stretchModes] to apply `fadeTitle`, `blurBackground` and
+/// `zoomBackground`. The app bar also makes use of [CollapseMode.parallax] by
+/// default.
+///
+/// ```dart imports
+/// import 'package:flute/material.dart';
+/// ```
+/// ```dart
+/// void main() => runApp(MaterialApp(home: MyApp()));
+///
+/// class MyApp extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return Scaffold(
+///       body: CustomScrollView(
+///         physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
+///         slivers: <Widget>[
+///           SliverAppBar(
+///             stretch: true,
+///             onStretchTrigger: () {
+///               // Function callback for stretch
+///               return;
+///             },
+///             expandedHeight: 300.0,
+///             flexibleSpace: FlexibleSpaceBar(
+///               stretchModes: <StretchMode>[
+///                 StretchMode.zoomBackground,
+///                 StretchMode.blurBackground,
+///                 StretchMode.fadeTitle,
+///               ],
+///               centerTitle: true,
+///               title: const Text('Flight Report'),
+///               background: Stack(
+///                 fit: StackFit.expand,
+///                 children: [
+///                   Image.network(
+///                     'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg',
+///                     fit: BoxFit.cover,
+///                   ),
+///                   const DecoratedBox(
+///                     decoration: BoxDecoration(
+///                       gradient: LinearGradient(
+///                         begin: Alignment(0.0, 0.5),
+///                         end: Alignment(0.0, 0.0),
+///                         colors: <Color>[
+///                           Color(0x60000000),
+///                           Color(0x00000000),
+///                         ],
+///                       ),
+///                     ),
+///                   ),
+///                 ],
+///               ),
+///             ),
+///           ),
+///           SliverList(
+///             delegate: SliverChildListDelegate([
+///               ListTile(
+///                 leading: Icon(Icons.wb_sunny),
+///                 title: Text('Sunday'),
+///                 subtitle: Text('sunny, h: 80, l: 65'),
+///               ),
+///               ListTile(
+///                 leading: Icon(Icons.wb_sunny),
+///                 title: Text('Monday'),
+///                 subtitle: Text('sunny, h: 80, l: 65'),
+///               ),
+///               // ListTiles++
+///             ]),
+///           ),
+///         ],
+///       ),
+///     );
+///   }
+/// }
+///
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [SliverAppBar], which implements the expanding and contracting.
+///  * [AppBar], which is used by [SliverAppBar].
+///  * <https://material.io/design/components/app-bars-top.html#behavior>
+class FlexibleSpaceBar extends StatefulWidget {
+  /// Creates a flexible space bar.
+  ///
+  /// Most commonly used in the [AppBar.flexibleSpace] field.
+  const FlexibleSpaceBar({
+    Key? key,
+    this.title,
+    this.background,
+    this.centerTitle,
+    this.titlePadding,
+    this.collapseMode = CollapseMode.parallax,
+    this.stretchModes = const <StretchMode>[StretchMode.zoomBackground],
+  }) : assert(collapseMode != null),
+       super(key: key);
+
+  /// The primary contents of the flexible space bar when expanded.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? title;
+
+  /// Shown behind the [title] when expanded.
+  ///
+  /// Typically an [Image] widget with [Image.fit] set to [BoxFit.cover].
+  final Widget? background;
+
+  /// Whether the title should be centered.
+  ///
+  /// By default this property is true if the current target platform
+  /// is [TargetPlatform.iOS] or [TargetPlatform.macOS], false otherwise.
+  final bool? centerTitle;
+
+  /// Collapse effect while scrolling.
+  ///
+  /// Defaults to [CollapseMode.parallax].
+  final CollapseMode collapseMode;
+
+  /// Stretch effect while over-scrolling.
+  ///
+  /// Defaults to include [StretchMode.zoomBackground].
+  final List<StretchMode> stretchModes;
+
+  /// Defines how far the [title] is inset from either the widget's
+  /// bottom-left or its center.
+  ///
+  /// Typically this property is used to adjust how far the title is
+  /// is inset from the bottom-left and it is specified along with
+  /// [centerTitle] false.
+  ///
+  /// By default the value of this property is
+  /// `EdgeInsetsDirectional.only(start: 72, bottom: 16)` if the title is
+  /// not centered, `EdgeInsetsDirectional.only(start: 0, bottom: 16)` otherwise.
+  final EdgeInsetsGeometry? titlePadding;
+
+  /// Wraps a widget that contains an [AppBar] to convey sizing information down
+  /// to the [FlexibleSpaceBar].
+  ///
+  /// Used by [Scaffold] and [SliverAppBar].
+  ///
+  /// `toolbarOpacity` affects how transparent the text within the toolbar
+  /// appears. `minExtent` sets the minimum height of the resulting
+  /// [FlexibleSpaceBar] when fully collapsed. `maxExtent` sets the maximum
+  /// height of the resulting [FlexibleSpaceBar] when fully expanded.
+  /// `currentExtent` sets the scale of the [FlexibleSpaceBar.background] and
+  /// [FlexibleSpaceBar.title] widgets of [FlexibleSpaceBar] upon
+  /// initialization.
+  ///
+  /// See also:
+  ///
+  ///  * [FlexibleSpaceBarSettings] which creates a settings object that can be
+  ///    used to specify these settings to a [FlexibleSpaceBar].
+  static Widget createSettings({
+    double? toolbarOpacity,
+    double? minExtent,
+    double? maxExtent,
+    required double currentExtent,
+    required Widget child,
+  }) {
+    assert(currentExtent != null);
+    return FlexibleSpaceBarSettings(
+      toolbarOpacity: toolbarOpacity ?? 1.0,
+      minExtent: minExtent ?? currentExtent,
+      maxExtent: maxExtent ?? currentExtent,
+      currentExtent: currentExtent,
+      child: child,
+    );
+  }
+
+  @override
+  _FlexibleSpaceBarState createState() => _FlexibleSpaceBarState();
+}
+
+class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
+  bool _getEffectiveCenterTitle(ThemeData theme) {
+    if (widget.centerTitle != null)
+      return widget.centerTitle!;
+    assert(theme.platform != null);
+    switch (theme.platform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        return false;
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        return true;
+    }
+  }
+
+  Alignment _getTitleAlignment(bool effectiveCenterTitle) {
+    if (effectiveCenterTitle)
+      return Alignment.bottomCenter;
+    final TextDirection textDirection = Directionality.of(context);
+    assert(textDirection != null);
+    switch (textDirection) {
+      case TextDirection.rtl:
+        return Alignment.bottomRight;
+      case TextDirection.ltr:
+        return Alignment.bottomLeft;
+    }
+  }
+
+  double _getCollapsePadding(double t, FlexibleSpaceBarSettings settings) {
+    switch (widget.collapseMode) {
+      case CollapseMode.pin:
+        return -(settings.maxExtent - settings.currentExtent);
+      case CollapseMode.none:
+        return 0.0;
+      case CollapseMode.parallax:
+        final double deltaExtent = settings.maxExtent - settings.minExtent;
+        return -Tween<double>(begin: 0.0, end: deltaExtent / 4.0).transform(t);
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return LayoutBuilder(
+      builder: (BuildContext context, BoxConstraints constraints) {
+        final FlexibleSpaceBarSettings settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!;
+        assert(
+          settings != null,
+          'A FlexibleSpaceBar must be wrapped in the widget returned by FlexibleSpaceBar.createSettings().',
+        );
+
+        final List<Widget> children = <Widget>[];
+
+        final double deltaExtent = settings.maxExtent - settings.minExtent;
+
+        // 0.0 -> Expanded
+        // 1.0 -> Collapsed to toolbar
+        final double t = (1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent).clamp(0.0, 1.0);
+
+        // background
+        if (widget.background != null) {
+          final double fadeStart = math.max(0.0, 1.0 - kToolbarHeight / deltaExtent);
+          const double fadeEnd = 1.0;
+          assert(fadeStart <= fadeEnd);
+          final double opacity = 1.0 - Interval(fadeStart, fadeEnd).transform(t);
+          double height = settings.maxExtent;
+
+          // StretchMode.zoomBackground
+          if (widget.stretchModes.contains(StretchMode.zoomBackground) &&
+            constraints.maxHeight > height) {
+            height = constraints.maxHeight;
+          }
+          children.add(Positioned(
+            top: _getCollapsePadding(t, settings),
+            left: 0.0,
+            right: 0.0,
+            height: height,
+            child: Opacity(
+              // IOS is relying on this semantics node to correctly traverse
+              // through the app bar when it is collapsed.
+              alwaysIncludeSemantics: true,
+              opacity: opacity,
+              child: widget.background,
+            ),
+          ));
+
+          // StretchMode.blurBackground
+          if (widget.stretchModes.contains(StretchMode.blurBackground) &&
+            constraints.maxHeight > settings.maxExtent) {
+            final double blurAmount = (constraints.maxHeight - settings.maxExtent) / 10;
+            children.add(Positioned.fill(
+              child: BackdropFilter(
+                child: Container(
+                  color: Colors.transparent,
+                ),
+                filter: ui.ImageFilter.blur(
+                  sigmaX: blurAmount,
+                  sigmaY: blurAmount,
+                )
+              )
+            ));
+          }
+        }
+
+        // title
+        if (widget.title != null) {
+          final ThemeData theme = Theme.of(context);
+
+          Widget? title;
+          switch (theme.platform) {
+            case TargetPlatform.iOS:
+            case TargetPlatform.macOS:
+              title = widget.title;
+              break;
+            case TargetPlatform.android:
+            case TargetPlatform.fuchsia:
+            case TargetPlatform.linux:
+            case TargetPlatform.windows:
+              title = Semantics(
+                namesRoute: true,
+                child: widget.title,
+              );
+              break;
+          }
+
+          // StretchMode.fadeTitle
+          if (widget.stretchModes.contains(StretchMode.fadeTitle) &&
+            constraints.maxHeight > settings.maxExtent) {
+            final double stretchOpacity = 1 -
+              (((constraints.maxHeight - settings.maxExtent) / 100).clamp(0.0, 1.0));
+            title = Opacity(
+              opacity: stretchOpacity,
+              child: title,
+            );
+          }
+
+          final double opacity = settings.toolbarOpacity;
+          if (opacity > 0.0) {
+            TextStyle titleStyle = theme.primaryTextTheme.headline6!;
+            titleStyle = titleStyle.copyWith(
+              color: titleStyle.color!.withOpacity(opacity)
+            );
+            final bool effectiveCenterTitle = _getEffectiveCenterTitle(theme);
+            final EdgeInsetsGeometry padding = widget.titlePadding ??
+              EdgeInsetsDirectional.only(
+                start: effectiveCenterTitle ? 0.0 : 72.0,
+                bottom: 16.0,
+              );
+            final double scaleValue = Tween<double>(begin: 1.5, end: 1.0).transform(t);
+            final Matrix4 scaleTransform = Matrix4.identity()
+              ..scale(scaleValue, scaleValue, 1.0);
+            final Alignment titleAlignment = _getTitleAlignment(effectiveCenterTitle);
+            children.add(Container(
+              padding: padding,
+              child: Transform(
+                alignment: titleAlignment,
+                transform: scaleTransform,
+                child: Align(
+                  alignment: titleAlignment,
+                  child: DefaultTextStyle(
+                    style: titleStyle,
+                    child: LayoutBuilder(
+                      builder: (BuildContext context, BoxConstraints constraints) {
+                        return Container(
+                          width: constraints.maxWidth / scaleValue,
+                          alignment: titleAlignment,
+                          child: title,
+                        );
+                      }
+                    ),
+                  ),
+                ),
+              ),
+            ));
+          }
+        }
+
+        return ClipRect(child: Stack(children: children));
+      }
+    );
+  }
+}
+
+/// Provides sizing and opacity information to a [FlexibleSpaceBar].
+///
+/// See also:
+///
+///  * [FlexibleSpaceBar] which creates a flexible space bar.
+class FlexibleSpaceBarSettings extends InheritedWidget {
+  /// Creates a Flexible Space Bar Settings widget.
+  ///
+  /// Used by [Scaffold] and [SliverAppBar]. [child] must have a
+  /// [FlexibleSpaceBar] widget in its tree for the settings to take affect.
+  ///
+  /// The required [toolbarOpacity], [minExtent], [maxExtent], [currentExtent],
+  /// and [child] parameters must not be null.
+  const FlexibleSpaceBarSettings({
+    Key? key,
+    required this.toolbarOpacity,
+    required this.minExtent,
+    required this.maxExtent,
+    required this.currentExtent,
+    required Widget child,
+  }) : assert(toolbarOpacity != null),
+       assert(minExtent != null && minExtent >= 0),
+       assert(maxExtent != null && maxExtent >= 0),
+       assert(currentExtent != null && currentExtent >= 0),
+       assert(toolbarOpacity >= 0.0),
+       assert(minExtent <= maxExtent),
+       assert(minExtent <= currentExtent),
+       assert(currentExtent <= maxExtent),
+       super(key: key, child: child);
+
+  /// Affects how transparent the text within the toolbar appears.
+  final double toolbarOpacity;
+
+  /// Minimum height of the resulting [FlexibleSpaceBar] when fully collapsed.
+  final double minExtent;
+
+  /// Maximum height of the resulting [FlexibleSpaceBar] when fully expanded.
+  final double maxExtent;
+
+  /// If the [FlexibleSpaceBar.title] or the [FlexibleSpaceBar.background] is
+  /// not null, then this value is used to calculate the relative scale of
+  /// these elements upon initialization.
+  final double currentExtent;
+
+  @override
+  bool updateShouldNotify(FlexibleSpaceBarSettings oldWidget) {
+    return toolbarOpacity != oldWidget.toolbarOpacity
+        || minExtent != oldWidget.minExtent
+        || maxExtent != oldWidget.maxExtent
+        || currentExtent != oldWidget.currentExtent;
+  }
+}
diff --git a/lib/src/material/floating_action_button.dart b/lib/src/material/floating_action_button.dart
new file mode 100644
index 0000000..106ee91
--- /dev/null
+++ b/lib/src/material/floating_action_button.dart
@@ -0,0 +1,613 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'button.dart';
+import 'colors.dart';
+import 'floating_action_button_theme.dart';
+import 'scaffold.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+import 'tooltip.dart';
+
+const BoxConstraints _kSizeConstraints = BoxConstraints.tightFor(
+  width: 56.0,
+  height: 56.0,
+);
+
+const BoxConstraints _kMiniSizeConstraints = BoxConstraints.tightFor(
+  width: 40.0,
+  height: 40.0,
+);
+
+const BoxConstraints _kExtendedSizeConstraints = BoxConstraints(
+  minHeight: 48.0,
+  maxHeight: 48.0,
+);
+
+class _DefaultHeroTag {
+  const _DefaultHeroTag();
+  @override
+  String toString() => '<default FloatingActionButton tag>';
+}
+
+/// A material design floating action button.
+///
+/// A floating action button is a circular icon button that hovers over content
+/// to promote a primary action in the application. Floating action buttons are
+/// most commonly used in the [Scaffold.floatingActionButton] field.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=2uaoEDOgk_I}
+///
+/// Use at most a single floating action button per screen. Floating action
+/// buttons should be used for positive actions such as "create", "share", or
+/// "navigate". (If more than one floating action button is used within a
+/// [Route], then make sure that each button has a unique [heroTag], otherwise
+/// an exception will be thrown.)
+///
+/// If the [onPressed] callback is null, then the button will be disabled and
+/// will not react to touch. It is highly discouraged to disable a floating
+/// action button as there is no indication to the user that the button is
+/// disabled. Consider changing the [backgroundColor] if disabling the floating
+/// action button.
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+/// This example shows how to display a [FloatingActionButton] in a
+/// [Scaffold], with a pink [backgroundColor] and a thumbs up [Icon].
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button.png)
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: AppBar(
+///       title: const Text('Floating Action Button'),
+///     ),
+///     body: Center(
+///       child: const Text('Press the button below!')
+///     ),
+///     floatingActionButton: FloatingActionButton(
+///       onPressed: () {
+///         // Add your onPressed code here!
+///       },
+///       child: Icon(Icons.navigation),
+///       backgroundColor: Colors.green,
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+/// This example shows how to make an extended [FloatingActionButton] in a
+/// [Scaffold], with a  pink [backgroundColor], a thumbs up [Icon] and a
+/// [Text] label that reads "Approve".
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_label.png)
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: AppBar(
+///       title: const Text('Floating Action Button Label'),
+///     ),
+///     body: Center(
+///       child: const Text('Press the button with a label below!'),
+///     ),
+///     floatingActionButton: FloatingActionButton.extended(
+///       onPressed: () {
+///         // Add your onPressed code here!
+///       },
+///       label: Text('Approve'),
+///       icon: Icon(Icons.thumb_up),
+///       backgroundColor: Colors.pink,
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Scaffold], in which floating action buttons typically live.
+///  * [ElevatedButton], a filled button whose material elevates when pressed.
+///  * <https://material.io/design/components/buttons-floating-action-button.html>
+class FloatingActionButton extends StatelessWidget {
+  /// Creates a circular floating action button.
+  ///
+  /// The [mini] and [clipBehavior] arguments must not be null. Additionally,
+  /// [elevation], [highlightElevation], and [disabledElevation] (if specified)
+  /// must be non-negative.
+  const FloatingActionButton({
+    Key? key,
+    this.child,
+    this.tooltip,
+    this.foregroundColor,
+    this.backgroundColor,
+    this.focusColor,
+    this.hoverColor,
+    this.splashColor,
+    this.heroTag = const _DefaultHeroTag(),
+    this.elevation,
+    this.focusElevation,
+    this.hoverElevation,
+    this.highlightElevation,
+    this.disabledElevation,
+    required this.onPressed,
+    this.mouseCursor,
+    this.mini = false,
+    this.shape,
+    this.clipBehavior = Clip.none,
+    this.focusNode,
+    this.autofocus = false,
+    this.materialTapTargetSize,
+    this.isExtended = false,
+  }) : assert(elevation == null || elevation >= 0.0),
+       assert(focusElevation == null || focusElevation >= 0.0),
+       assert(hoverElevation == null || hoverElevation >= 0.0),
+       assert(highlightElevation == null || highlightElevation >= 0.0),
+       assert(disabledElevation == null || disabledElevation >= 0.0),
+       assert(mini != null),
+       assert(clipBehavior != null),
+       assert(isExtended != null),
+       assert(autofocus != null),
+       _sizeConstraints = mini ? _kMiniSizeConstraints : _kSizeConstraints,
+       super(key: key);
+
+  /// Creates a wider [StadiumBorder]-shaped floating action button with
+  /// an optional [icon] and a [label].
+  ///
+  /// The [label], [autofocus], and [clipBehavior] arguments must not be null.
+  /// Additionally, [elevation], [highlightElevation], and [disabledElevation]
+  /// (if specified) must be non-negative.
+  FloatingActionButton.extended({
+    Key? key,
+    this.tooltip,
+    this.foregroundColor,
+    this.backgroundColor,
+    this.focusColor,
+    this.hoverColor,
+    this.heroTag = const _DefaultHeroTag(),
+    this.elevation,
+    this.focusElevation,
+    this.hoverElevation,
+    this.splashColor,
+    this.highlightElevation,
+    this.disabledElevation,
+    required this.onPressed,
+    this.mouseCursor = SystemMouseCursors.click,
+    this.shape,
+    this.isExtended = true,
+    this.materialTapTargetSize,
+    this.clipBehavior = Clip.none,
+    this.focusNode,
+    this.autofocus = false,
+    Widget? icon,
+    required Widget label,
+  }) : assert(elevation == null || elevation >= 0.0),
+       assert(focusElevation == null || focusElevation >= 0.0),
+       assert(hoverElevation == null || hoverElevation >= 0.0),
+       assert(highlightElevation == null || highlightElevation >= 0.0),
+       assert(disabledElevation == null || disabledElevation >= 0.0),
+       assert(isExtended != null),
+       assert(clipBehavior != null),
+       assert(autofocus != null),
+       _sizeConstraints = _kExtendedSizeConstraints,
+       mini = false,
+       child = _ChildOverflowBox(
+         child: Row(
+           mainAxisSize: MainAxisSize.min,
+           children: icon == null
+             ? <Widget>[
+                 const SizedBox(width: 20.0),
+                 label,
+                 const SizedBox(width: 20.0),
+               ]
+             : <Widget>[
+                 const SizedBox(width: 16.0),
+                 icon,
+                 const SizedBox(width: 8.0),
+                 label,
+                 const SizedBox(width: 20.0),
+               ],
+         ),
+       ),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// Typically an [Icon].
+  final Widget? child;
+
+  /// Text that describes the action that will occur when the button is pressed.
+  ///
+  /// This text is displayed when the user long-presses on the button and is
+  /// used for accessibility.
+  final String? tooltip;
+
+  /// The default foreground color for icons and text within the button.
+  ///
+  /// If this property is null, then the
+  /// [FloatingActionButtonThemeData.foregroundColor] of
+  /// [ThemeData.floatingActionButtonTheme] is used. If that property is also
+  /// null, then the [ColorScheme.onSecondary] color of [ThemeData.colorScheme]
+  /// is used.
+  ///
+  /// Although the color of theme's `accentIconTheme` currently provides a
+  /// default that supersedes the `onSecondary` color, this dependency
+  /// has been deprecated:  https://flutter.dev/go/remove-fab-accent-theme-dependency.
+  /// It will be removed in the future.
+  final Color? foregroundColor;
+
+  /// The button's background color.
+  ///
+  /// If this property is null, then the
+  /// [FloatingActionButtonThemeData.backgroundColor] of
+  /// [ThemeData.floatingActionButtonTheme] is used. If that property is also
+  /// null, then the [Theme]'s [ColorScheme.secondary] color is used.
+  final Color? backgroundColor;
+
+  /// The color to use for filling the button when the button has input focus.
+  ///
+  /// Defaults to [ThemeData.focusColor] for the current theme.
+  final Color? focusColor;
+
+  /// The color to use for filling the button when the button has a pointer
+  /// hovering over it.
+  ///
+  /// Defaults to [ThemeData.hoverColor] for the current theme.
+  final Color? hoverColor;
+
+  /// The splash color for this [FloatingActionButton]'s [InkWell].
+  ///
+  /// If null, [FloatingActionButtonThemeData.splashColor] is used, if that is
+  /// null, [ThemeData.splashColor] is used.
+  final Color? splashColor;
+
+  /// The tag to apply to the button's [Hero] widget.
+  ///
+  /// Defaults to a tag that matches other floating action buttons.
+  ///
+  /// Set this to null explicitly if you don't want the floating action button to
+  /// have a hero tag.
+  ///
+  /// If this is not explicitly set, then there can only be one
+  /// [FloatingActionButton] per route (that is, per screen), since otherwise
+  /// there would be a tag conflict (multiple heroes on one route can't have the
+  /// same tag). The material design specification recommends only using one
+  /// floating action button per screen.
+  final Object? heroTag;
+
+  /// The callback that is called when the button is tapped or otherwise activated.
+  ///
+  /// If this is set to null, the button will be disabled.
+  final VoidCallback? onPressed;
+
+  /// {@macro flutter.material.RawMaterialButton.mouseCursor}
+  final MouseCursor? mouseCursor;
+
+  /// The z-coordinate at which to place this button relative to its parent.
+  ///
+  /// This controls the size of the shadow below the floating action button.
+  ///
+  /// Defaults to 6, the appropriate elevation for floating action buttons. The
+  /// value is always non-negative.
+  ///
+  /// See also:
+  ///
+  ///  * [highlightElevation], the elevation when the button is pressed.
+  ///  * [disabledElevation], the elevation when the button is disabled.
+  final double? elevation;
+
+  /// The z-coordinate at which to place this button relative to its parent when
+  /// the button has the input focus.
+  ///
+  /// This controls the size of the shadow below the floating action button.
+  ///
+  /// Defaults to 8, the appropriate elevation for floating action buttons
+  /// while they have focus. The value is always non-negative.
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], the default elevation.
+  ///  * [highlightElevation], the elevation when the button is pressed.
+  ///  * [disabledElevation], the elevation when the button is disabled.
+  final double? focusElevation;
+
+  /// The z-coordinate at which to place this button relative to its parent when
+  /// the button is enabled and has a pointer hovering over it.
+  ///
+  /// This controls the size of the shadow below the floating action button.
+  ///
+  /// Defaults to 8, the appropriate elevation for floating action buttons while
+  /// they have a pointer hovering over them. The value is always non-negative.
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], the default elevation.
+  ///  * [highlightElevation], the elevation when the button is pressed.
+  ///  * [disabledElevation], the elevation when the button is disabled.
+  final double? hoverElevation;
+
+  /// The z-coordinate at which to place this button relative to its parent when
+  /// the user is touching the button.
+  ///
+  /// This controls the size of the shadow below the floating action button.
+  ///
+  /// Defaults to 12, the appropriate elevation for floating action buttons
+  /// while they are being touched. The value is always non-negative.
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], the default elevation.
+  final double? highlightElevation;
+
+  /// The z-coordinate at which to place this button when the button is disabled
+  /// ([onPressed] is null).
+  ///
+  /// This controls the size of the shadow below the floating action button.
+  ///
+  /// Defaults to the same value as [elevation]. Setting this to zero makes the
+  /// floating action button work similar to an [ElevatedButton] but the titular
+  /// "floating" effect is lost. The value is always non-negative.
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], the default elevation.
+  ///  * [highlightElevation], the elevation when the button is pressed.
+  final double? disabledElevation;
+
+  /// Controls the size of this button.
+  ///
+  /// By default, floating action buttons are non-mini and have a height and
+  /// width of 56.0 logical pixels. Mini floating action buttons have a height
+  /// and width of 40.0 logical pixels with a layout width and height of 48.0
+  /// logical pixels. (The extra 4 pixels of padding on each side are added as a
+  /// result of the floating action button having [MaterialTapTargetSize.padded]
+  /// set on the underlying [RawMaterialButton.materialTapTargetSize].)
+  final bool mini;
+
+  /// The shape of the button's [Material].
+  ///
+  /// The button's highlight and splash are clipped to this shape. If the
+  /// button has an elevation, then its drop shadow is defined by this
+  /// shape as well.
+  final ShapeBorder? shape;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  final Clip clipBehavior;
+
+  /// True if this is an "extended" floating action button.
+  ///
+  /// Typically [extended] buttons have a [StadiumBorder] [shape]
+  /// and have been created with the [FloatingActionButton.extended]
+  /// constructor.
+  ///
+  /// The [Scaffold] animates the appearance of ordinary floating
+  /// action buttons with scale and rotation transitions. Extended
+  /// floating action buttons are scaled and faded in.
+  final bool isExtended;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  /// Configures the minimum size of the tap target.
+  ///
+  /// Defaults to [ThemeData.materialTapTargetSize].
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialTapTargetSize], for a description of how this affects tap targets.
+  final MaterialTapTargetSize? materialTapTargetSize;
+
+  final BoxConstraints _sizeConstraints;
+
+  static const double _defaultElevation = 6;
+  static const double _defaultFocusElevation = 6;
+  static const double _defaultHoverElevation = 8;
+  static const double _defaultHighlightElevation = 12;
+  static const ShapeBorder _defaultShape = CircleBorder();
+  static const ShapeBorder _defaultExtendedShape = StadiumBorder();
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final FloatingActionButtonThemeData floatingActionButtonTheme = theme.floatingActionButtonTheme;
+
+    // Applications should no longer use accentIconTheme's color to configure
+    // the foreground color of floating action buttons. For more information, see
+    // https://flutter.dev/go/remove-fab-accent-theme-dependency.
+    if (this.foregroundColor == null && floatingActionButtonTheme.foregroundColor == null) {
+      final bool accentIsDark = theme.accentColorBrightness == Brightness.dark;
+      final Color defaultAccentIconThemeColor = accentIsDark ? Colors.white : Colors.black;
+      if (theme.accentIconTheme.color != defaultAccentIconThemeColor) {
+        debugPrint(
+          'Warning: '
+          'The support for configuring the foreground color of '
+          'FloatingActionButtons using ThemeData.accentIconTheme '
+          'has been deprecated. Please use ThemeData.floatingActionButtonTheme '
+          'instead. See '
+          'https://flutter.dev/go/remove-fab-accent-theme-dependency. '
+          'This feature was deprecated after v1.13.2.'
+        );
+      }
+    }
+
+    final Color foregroundColor = this.foregroundColor
+      ?? floatingActionButtonTheme.foregroundColor
+      ?? theme.colorScheme.onSecondary;
+    final Color backgroundColor = this.backgroundColor
+      ?? floatingActionButtonTheme.backgroundColor
+      ?? theme.colorScheme.secondary;
+    final Color focusColor = this.focusColor
+      ?? floatingActionButtonTheme.focusColor
+      ?? theme.focusColor;
+    final Color hoverColor = this.hoverColor
+      ?? floatingActionButtonTheme.hoverColor
+      ?? theme.hoverColor;
+    final Color splashColor = this.splashColor
+      ?? floatingActionButtonTheme.splashColor
+      ?? theme.splashColor;
+    final double elevation = this.elevation
+      ?? floatingActionButtonTheme.elevation
+      ?? _defaultElevation;
+    final double focusElevation = this.focusElevation
+      ?? floatingActionButtonTheme.focusElevation
+      ?? _defaultFocusElevation;
+    final double hoverElevation = this.hoverElevation
+      ?? floatingActionButtonTheme.hoverElevation
+      ?? _defaultHoverElevation;
+    final double disabledElevation = this.disabledElevation
+      ?? floatingActionButtonTheme.disabledElevation
+      ?? elevation;
+    final double highlightElevation = this.highlightElevation
+      ?? floatingActionButtonTheme.highlightElevation
+      ?? _defaultHighlightElevation;
+    final MaterialTapTargetSize materialTapTargetSize = this.materialTapTargetSize
+      ?? theme.materialTapTargetSize;
+    final TextStyle textStyle = theme.textTheme.button!.copyWith(
+      color: foregroundColor,
+      letterSpacing: 1.2,
+    );
+    final ShapeBorder shape = this.shape
+      ?? floatingActionButtonTheme.shape
+      ?? (isExtended ? _defaultExtendedShape : _defaultShape);
+
+    Widget result = RawMaterialButton(
+      onPressed: onPressed,
+      mouseCursor: mouseCursor,
+      elevation: elevation,
+      focusElevation: focusElevation,
+      hoverElevation: hoverElevation,
+      highlightElevation: highlightElevation,
+      disabledElevation: disabledElevation,
+      constraints: _sizeConstraints,
+      materialTapTargetSize: materialTapTargetSize,
+      fillColor: backgroundColor,
+      focusColor: focusColor,
+      hoverColor: hoverColor,
+      splashColor: splashColor,
+      textStyle: textStyle,
+      shape: shape,
+      clipBehavior: clipBehavior,
+      focusNode: focusNode,
+      autofocus: autofocus,
+      child: child,
+    );
+
+    if (tooltip != null) {
+      result = Tooltip(
+        message: tooltip!,
+        child: result,
+      );
+    }
+
+    if (heroTag != null) {
+      result = Hero(
+        tag: heroTag!,
+        child: result,
+      );
+    }
+
+    return MergeSemantics(child: result);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ObjectFlagProperty<VoidCallback>('onPressed', onPressed, ifNull: 'disabled'));
+    properties.add(StringProperty('tooltip', tooltip, defaultValue: null));
+    properties.add(ColorProperty('foregroundColor', foregroundColor, defaultValue: null));
+    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
+    properties.add(ColorProperty('focusColor', focusColor, defaultValue: null));
+    properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: null));
+    properties.add(ColorProperty('splashColor', splashColor, defaultValue: null));
+    properties.add(ObjectFlagProperty<Object>('heroTag', heroTag, ifPresent: 'hero'));
+    properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
+    properties.add(DoubleProperty('focusElevation', focusElevation, defaultValue: null));
+    properties.add(DoubleProperty('hoverElevation', hoverElevation, defaultValue: null));
+    properties.add(DoubleProperty('highlightElevation', highlightElevation, defaultValue: null));
+    properties.add(DoubleProperty('disabledElevation', disabledElevation, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
+    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
+    properties.add(FlagProperty('isExtended', value: isExtended, ifTrue: 'extended'));
+    properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
+  }
+}
+
+// This widget's size matches its child's size unless its constraints
+// force it to be larger or smaller. The child is centered.
+//
+// Used to encapsulate extended FABs whose size is fixed, using Row
+// and MainAxisSize.min, to be as wide as their label and icon.
+class _ChildOverflowBox extends SingleChildRenderObjectWidget {
+  const _ChildOverflowBox({
+    Key? key,
+    Widget? child,
+  }) : super(key: key, child: child);
+
+  @override
+  _RenderChildOverflowBox createRenderObject(BuildContext context) {
+    return _RenderChildOverflowBox(
+      textDirection: Directionality.of(context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderChildOverflowBox renderObject) {
+    renderObject.textDirection = Directionality.of(context);
+  }
+}
+
+class _RenderChildOverflowBox extends RenderAligningShiftedBox {
+  _RenderChildOverflowBox({
+    RenderBox? child,
+    TextDirection? textDirection,
+  }) : super(child: child, alignment: Alignment.center, textDirection: textDirection);
+
+  @override
+  double computeMinIntrinsicWidth(double height) => 0.0;
+
+  @override
+  double computeMinIntrinsicHeight(double width) => 0.0;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (child != null) {
+      final Size childSize = child!.getDryLayout(const BoxConstraints());
+      return Size(
+        math.max(constraints.minWidth, math.min(constraints.maxWidth, childSize.width)),
+        math.max(constraints.minHeight, math.min(constraints.maxHeight, childSize.height)),
+      );
+    } else {
+      return constraints.biggest;
+    }
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    if (child != null) {
+      child!.layout(const BoxConstraints(), parentUsesSize: true);
+      size = Size(
+        math.max(constraints.minWidth, math.min(constraints.maxWidth, child!.size.width)),
+        math.max(constraints.minHeight, math.min(constraints.maxHeight, child!.size.height)),
+      );
+      alignChild();
+    } else {
+      size = constraints.biggest;
+    }
+  }
+}
diff --git a/lib/src/material/floating_action_button_location.dart b/lib/src/material/floating_action_button_location.dart
new file mode 100644
index 0000000..6dadfa1
--- /dev/null
+++ b/lib/src/material/floating_action_button_location.dart
@@ -0,0 +1,958 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'scaffold.dart';
+
+/// The margin that a [FloatingActionButton] should leave between it and the
+/// edge of the screen.
+///
+/// [FloatingActionButtonLocation.endFloat] uses this to set the appropriate margin
+/// between the [FloatingActionButton] and the end of the screen.
+const double kFloatingActionButtonMargin = 16.0;
+
+/// The amount of time the [FloatingActionButton] takes to transition in or out.
+///
+/// The [Scaffold] uses this to set the duration of [FloatingActionButton]
+/// motion, entrance, and exit animations.
+const Duration kFloatingActionButtonSegue = Duration(milliseconds: 200);
+
+/// The fraction of a circle the [FloatingActionButton] should turn when it enters.
+///
+/// Its value corresponds to 0.125 of a full circle, equivalent to 45 degrees or pi/4 radians.
+const double kFloatingActionButtonTurnInterval = 0.125;
+
+/// If a [FloatingActionButton] is used on a [Scaffold] in certain positions,
+/// it is moved [kMiniButtonOffsetAdjustment] pixels closer to the edge of the screen.
+///
+/// This is intended to be used with [FloatingActionButton.mini] set to true,
+/// so that the floating action button appears to align with [CircleAvatar]s
+/// in the [ListTile.leading] slot of a [ListTile] in a [ListView] in the
+/// [Scaffold.body].
+///
+/// More specifically:
+/// * In the following positions, the [FloatingActionButton] is moved *horizontally*
+/// closer to the edge of the screen:
+///   * [FloatingActionButtonLocation.miniStartTop]
+///   * [FloatingActionButtonLocation.miniStartFloat]
+///   * [FloatingActionButtonLocation.miniStartDocked]
+///   * [FloatingActionButtonLocation.miniEndTop]
+///   * [FloatingActionButtonLocation.miniEndFloat]
+///   * [FloatingActionButtonLocation.miniEndDocked]
+/// * In the following positions, the [FloatingActionButton] is moved *vertically*
+/// closer to the bottom of the screen:
+///   * [FloatingActionButtonLocation.miniStartFloat]
+///   * [FloatingActionButtonLocation.miniCenterFloat]
+///   * [FloatingActionButtonLocation.miniEndFloat]
+const double kMiniButtonOffsetAdjustment = 4.0;
+
+/// An object that defines a position for the [FloatingActionButton]
+/// based on the [Scaffold]'s [ScaffoldPrelayoutGeometry].
+///
+/// Flutter provides [FloatingActionButtonLocation]s for the common
+/// [FloatingActionButton] placements in Material Design applications. These
+/// locations are available as static members of this class.
+///
+/// ## Floating Action Button placements
+///
+/// The following diagrams show the available placement locations for the FloatingActionButton.
+///
+/// * [FloatingActionButtonLocation.centerDocked]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_center_docked.png)
+///
+/// * [FloatingActionButtonLocation.centerFloat]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_center_float.png)
+///
+/// * [FloatingActionButtonLocation.centerTop]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_center_top.png)
+///
+/// * [FloatingActionButtonLocation.endDocked]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_end_docked.png)
+///
+/// * [FloatingActionButtonLocation.endFloat]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_end_float.png)
+///
+/// * [FloatingActionButtonLocation.endTop]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_end_top.png)
+///
+/// * [FloatingActionButtonLocation.startDocked]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_start_docked.png)
+///
+/// * [FloatingActionButtonLocation.startFloat]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_start_float.png)
+///
+/// * [FloatingActionButtonLocation.startTop]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_start_top.png)
+///
+/// * [FloatingActionButtonLocation.miniCenterDocked]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_center_docked.png)
+///
+/// * [FloatingActionButtonLocation.miniCenterFloat]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_center_float.png)
+///
+/// * [FloatingActionButtonLocation.miniCenterTop]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_center_top.png)
+///
+/// * [FloatingActionButtonLocation.miniEndDocked]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_end_docked.png)
+///
+/// * [FloatingActionButtonLocation.miniEndFloat]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_end_float.png)
+///
+/// * [FloatingActionButtonLocation.miniEndTop]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_end_top.png)
+///
+/// * [FloatingActionButtonLocation.miniStartDocked]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_start_docked.png)
+///
+/// * [FloatingActionButtonLocation.miniStartFloat]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_start_float.png)
+///
+/// * [FloatingActionButtonLocation.miniStartTop]:
+///
+///   ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_start_top.png)
+///
+/// See also:
+///
+///  * [FloatingActionButton], which is a circular button typically shown in the
+///    bottom right corner of the app.
+///  * [FloatingActionButtonAnimator], which is used to animate the
+///    [Scaffold.floatingActionButton] from one [FloatingActionButtonLocation] to
+///    another.
+///  * [ScaffoldPrelayoutGeometry], the geometry that
+///    [FloatingActionButtonLocation]s use to position the [FloatingActionButton].
+abstract class FloatingActionButtonLocation {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const FloatingActionButtonLocation();
+
+  /// Start-aligned [FloatingActionButton], floating over the transition between
+  /// the [Scaffold.appBar] and the [Scaffold.body].
+  ///
+  /// To align a floating action button with [CircleAvatar]s in the
+  /// [ListTile.leading] slots of [ListTile]s in a [ListView] in the [Scaffold.body],
+  /// use [miniStartTop] and set [FloatingActionButton.mini] to true.
+  ///
+  /// This is unlikely to be a useful location for apps that lack a top [AppBar]
+  /// or that use a [SliverAppBar] in the scaffold body itself.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_start_top.png)
+  static const FloatingActionButtonLocation startTop = _StartTopFabLocation();
+
+  /// Start-aligned [FloatingActionButton], floating over the transition between
+  /// the [Scaffold.appBar] and the [Scaffold.body], optimized for mini floating
+  /// action buttons.
+  ///
+  /// This is intended to be used with [FloatingActionButton.mini] set to true,
+  /// so that the floating action button appears to align with [CircleAvatar]s
+  /// in the [ListTile.leading] slot of a [ListTile] in a [ListView] in the
+  /// [Scaffold.body].
+  ///
+  /// This is unlikely to be a useful location for apps that lack a top [AppBar]
+  /// or that use a [SliverAppBar] in the scaffold body itself.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_start_top.png)
+  static const FloatingActionButtonLocation miniStartTop = _MiniStartTopFabLocation();
+
+  /// Centered [FloatingActionButton], floating over the transition between
+  /// the [Scaffold.appBar] and the [Scaffold.body].
+  ///
+  /// This is unlikely to be a useful location for apps that lack a top [AppBar]
+  /// or that use a [SliverAppBar] in the scaffold body itself.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_center_top.png)
+  static const FloatingActionButtonLocation centerTop = _CenterTopFabLocation();
+
+  /// Centered [FloatingActionButton], floating over the transition between
+  /// the [Scaffold.appBar] and the [Scaffold.body], intended to be used with
+  /// [FloatingActionButton.mini] set to true.
+  ///
+  /// This is unlikely to be a useful location for apps that lack a top [AppBar]
+  /// or that use a [SliverAppBar] in the scaffold body itself.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_center_top.png)
+  static const FloatingActionButtonLocation miniCenterTop = _MiniCenterTopFabLocation();
+
+  /// End-aligned [FloatingActionButton], floating over the transition between
+  /// the [Scaffold.appBar] and the [Scaffold.body].
+  ///
+  /// To align a floating action button with [CircleAvatar]s in the
+  /// [ListTile.trailing] slots of [ListTile]s in a [ListView] in the [Scaffold.body],
+  /// use [miniEndTop] and set [FloatingActionButton.mini] to true.
+  ///
+  /// This is unlikely to be a useful location for apps that lack a top [AppBar]
+  /// or that use a [SliverAppBar] in the scaffold body itself.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_end_top.png)
+  static const FloatingActionButtonLocation endTop = _EndTopFabLocation();
+
+  /// End-aligned [FloatingActionButton], floating over the transition between
+  /// the [Scaffold.appBar] and the [Scaffold.body], optimized for mini floating
+  /// action buttons.
+  ///
+  /// This is intended to be used with [FloatingActionButton.mini] set to true,
+  /// so that the floating action button appears to align with [CircleAvatar]s
+  /// in the [ListTile.trailing] slot of a [ListTile] in a [ListView] in the
+  /// [Scaffold.body].
+  ///
+  /// This is unlikely to be a useful location for apps that lack a top [AppBar]
+  /// or that use a [SliverAppBar] in the scaffold body itself.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_end_top.png)
+  static const FloatingActionButtonLocation miniEndTop = _MiniEndTopFabLocation();
+
+  /// Start-aligned [FloatingActionButton], floating at the bottom of the screen.
+  ///
+  /// To align a floating action button with [CircleAvatar]s in the
+  /// [ListTile.leading] slots of [ListTile]s in a [ListView] in the [Scaffold.body],
+  /// use [miniStartFloat] and set [FloatingActionButton.mini] to true.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_start_float.png)
+  static const FloatingActionButtonLocation startFloat = _StartFloatFabLocation();
+
+  /// Start-aligned [FloatingActionButton], floating at the bottom of the screen,
+  /// optimized for mini floating action buttons.
+  ///
+  /// This is intended to be used with [FloatingActionButton.mini] set to true,
+  /// so that the floating action button appears to align with [CircleAvatar]s
+  /// in the [ListTile.leading] slot of a [ListTile] in a [ListView] in the
+  /// [Scaffold.body].
+  ///
+  /// Compared to [FloatingActionButtonLocation.startFloat], floating action
+  /// buttons using this location will move horizontally _and_ vertically
+  /// closer to the edges, by [kMiniButtonOffsetAdjustment] each.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_start_float.png)
+  static const FloatingActionButtonLocation miniStartFloat = _MiniStartFloatFabLocation();
+
+  /// Centered [FloatingActionButton], floating at the bottom of the screen.
+  ///
+  /// To position a mini floating action button, use [miniCenterFloat] and
+  /// set [FloatingActionButton.mini] to true.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_center_float.png)
+  static const FloatingActionButtonLocation centerFloat = _CenterFloatFabLocation();
+
+  /// Centered [FloatingActionButton], floating at the bottom of the screen,
+  /// optimized for mini floating action buttons.
+  ///
+  /// This is intended to be used with [FloatingActionButton.mini] set to true,
+  /// so that the floating action button appears to align horizontally with
+  /// the locations [FloatingActionButtonLocation.miniStartFloat]
+  /// and [FloatingActionButtonLocation.miniEndFloat].
+  ///
+  /// Compared to [FloatingActionButtonLocation.centerFloat], floating action
+  /// buttons using this location will move vertically down
+  /// by [kMiniButtonOffsetAdjustment].
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_center_float.png)
+  static const FloatingActionButtonLocation miniCenterFloat = _MiniCenterFloatFabLocation();
+
+  /// End-aligned [FloatingActionButton], floating at the bottom of the screen.
+  ///
+  /// This is the default alignment of [FloatingActionButton]s in Material applications.
+  ///
+  /// To align a floating action button with [CircleAvatar]s in the
+  /// [ListTile.trailing] slots of [ListTile]s in a [ListView] in the [Scaffold.body],
+  /// use [miniEndFloat] and set [FloatingActionButton.mini] to true.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_end_float.png)
+  static const FloatingActionButtonLocation endFloat = _EndFloatFabLocation();
+
+  /// End-aligned [FloatingActionButton], floating at the bottom of the screen,
+  /// optimized for mini floating action buttons.
+  ///
+  /// This is intended to be used with [FloatingActionButton.mini] set to true,
+  /// so that the floating action button appears to align with [CircleAvatar]s
+  /// in the [ListTile.trailing] slot of a [ListTile] in a [ListView] in the
+  /// [Scaffold.body].
+  ///
+  /// Compared to [FloatingActionButtonLocation.endFloat], floating action
+  /// buttons using this location will move horizontally _and_ vertically
+  /// closer to the edges, by [kMiniButtonOffsetAdjustment] each.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_end_float.png)
+  static const FloatingActionButtonLocation miniEndFloat = _MiniEndFloatFabLocation();
+
+  /// Start-aligned [FloatingActionButton], floating over the
+  /// [Scaffold.bottomNavigationBar] so that the center of the floating
+  /// action button lines up with the top of the bottom navigation bar.
+  ///
+  /// To align a floating action button with [CircleAvatar]s in the
+  /// [ListTile.leading] slots of [ListTile]s in a [ListView] in the [Scaffold.body],
+  /// use [miniStartDocked] and set [FloatingActionButton.mini] to true.
+  ///
+  /// If the value of [Scaffold.bottomNavigationBar] is a [BottomAppBar],
+  /// the bottom app bar can include a "notch" in its shape that accommodates
+  /// the overlapping floating action button.
+  ///
+  /// This is unlikely to be a useful location for apps that lack a bottom
+  /// navigation bar.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_start_docked.png)
+  static const FloatingActionButtonLocation startDocked = _StartDockedFabLocation();
+
+  /// Start-aligned [FloatingActionButton], floating over the
+  /// [Scaffold.bottomNavigationBar] so that the center of the floating
+  /// action button lines up with the top of the bottom navigation bar,
+  /// optimized for mini floating action buttons.
+  ///
+  /// If the value of [Scaffold.bottomNavigationBar] is a [BottomAppBar],
+  /// the bottom app bar can include a "notch" in its shape that accommodates
+  /// the overlapping floating action button.
+  ///
+  /// This is intended to be used with [FloatingActionButton.mini] set to true,
+  /// so that the floating action button appears to align with [CircleAvatar]s
+  /// in the [ListTile.leading] slot of a [ListTile] in a [ListView] in the
+  /// [Scaffold.body].
+  ///
+  /// This is unlikely to be a useful location for apps that lack a bottom
+  /// navigation bar.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_start_docked.png)
+  static const FloatingActionButtonLocation miniStartDocked = _MiniStartDockedFabLocation();
+
+  /// Centered [FloatingActionButton], floating over the
+  /// [Scaffold.bottomNavigationBar] so that the center of the floating
+  /// action button lines up with the top of the bottom navigation bar.
+  ///
+  /// If the value of [Scaffold.bottomNavigationBar] is a [BottomAppBar],
+  /// the bottom app bar can include a "notch" in its shape that accommodates
+  /// the overlapping floating action button.
+  ///
+  /// This is unlikely to be a useful location for apps that lack a bottom
+  /// navigation bar.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_center_docked.png)
+  static const FloatingActionButtonLocation centerDocked = _CenterDockedFabLocation();
+
+  /// Centered [FloatingActionButton], floating over the
+  /// [Scaffold.bottomNavigationBar] so that the center of the floating
+  /// action button lines up with the top of the bottom navigation bar;
+  /// intended to be used with [FloatingActionButton.mini] set to true.
+  ///
+  /// If the value of [Scaffold.bottomNavigationBar] is a [BottomAppBar],
+  /// the bottom app bar can include a "notch" in its shape that accommodates
+  /// the overlapping floating action button.
+  ///
+  /// This is unlikely to be a useful location for apps that lack a bottom
+  /// navigation bar.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_center_docked.png)
+  static const FloatingActionButtonLocation miniCenterDocked = _MiniCenterDockedFabLocation();
+
+  /// End-aligned [FloatingActionButton], floating over the
+  /// [Scaffold.bottomNavigationBar] so that the center of the floating
+  /// action button lines up with the top of the bottom navigation bar.
+  ///
+  /// If the value of [Scaffold.bottomNavigationBar] is a [BottomAppBar],
+  /// the bottom app bar can include a "notch" in its shape that accommodates
+  /// the overlapping floating action button.
+  ///
+  /// This is unlikely to be a useful location for apps that lack a bottom
+  /// navigation bar.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_end_docked.png)
+  static const FloatingActionButtonLocation endDocked = _EndDockedFabLocation();
+
+  /// End-aligned [FloatingActionButton], floating over the
+  /// [Scaffold.bottomNavigationBar] so that the center of the floating
+  /// action button lines up with the top of the bottom navigation bar,
+  /// optimized for mini floating action buttons.
+  ///
+  /// To align a floating action button with [CircleAvatar]s in the
+  /// [ListTile.trailing] slots of [ListTile]s in a [ListView] in the [Scaffold.body],
+  /// use [miniEndDocked] and set [FloatingActionButton.mini] to true.
+  ///
+  /// If the value of [Scaffold.bottomNavigationBar] is a [BottomAppBar],
+  /// the bottom app bar can include a "notch" in its shape that accommodates
+  /// the overlapping floating action button.
+  ///
+  /// This is intended to be used with [FloatingActionButton.mini] set to true,
+  /// so that the floating action button appears to align with [CircleAvatar]s
+  /// in the [ListTile.trailing] slot of a [ListTile] in a [ListView] in the
+  /// [Scaffold.body].
+  ///
+  /// This is unlikely to be a useful location for apps that lack a bottom
+  /// navigation bar.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_location_mini_end_docked.png)
+  static const FloatingActionButtonLocation miniEndDocked = _MiniEndDockedFabLocation();
+
+  /// Places the [FloatingActionButton] based on the [Scaffold]'s layout.
+  ///
+  /// This uses a [ScaffoldPrelayoutGeometry], which the [Scaffold] constructs
+  /// during its layout phase after it has laid out every widget it can lay out
+  /// except the [FloatingActionButton]. The [Scaffold] uses the [Offset]
+  /// returned from this method to position the [FloatingActionButton] and
+  /// complete its layout.
+  Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry);
+
+  @override
+  String toString() => objectRuntimeType(this, 'FloatingActionButtonLocation');
+}
+
+/// A base class that simplifies building [FloatingActionButtonLocation]s when
+/// used with mixins [FabTopOffsetY], [FabFloatOffsetY], [FabDockedOffsetY],
+/// [FabStartOffsetX], [FabCenterOffsetX], [FabEndOffsetX], and [FabMiniOffsetAdjustment].
+///
+/// A subclass of [FloatingActionButtonLocation] which implements its [getOffset] method
+/// using three other methods: [getOffsetX], [getOffsetY], and [isMini].
+///
+/// Different mixins on this class override different methods, so that combining
+/// a set of mixins creates a floating action button location.
+///
+/// For example: the location [FloatingActionButtonLocation.miniEndTop]
+/// is based on a class that extends [StandardFabLocation]
+/// with mixins [FabMiniOffsetAdjustment], [FabEndOffsetX], and [FabTopOffsetY].
+///
+/// You can create your own subclass of [StandardFabLocation]
+/// to implement a custom [FloatingActionButtonLocation].
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+///
+/// This is an example of a user-defined [FloatingActionButtonLocation].
+///
+/// The example shows a [Scaffold] with an [AppBar], a [BottomAppBar], and a
+/// [FloatingActionButton] using a custom [FloatingActionButtonLocation].
+///
+/// The new [FloatingActionButtonLocation] is defined
+/// by extending [StandardFabLocation] with two mixins,
+/// [FabEndOffsetX] and [FabFloatOffsetY], and overriding the
+/// [getOffsetX] method to adjust the FAB's x-coordinate, creating a
+/// [FloatingActionButtonLocation] slightly different from
+/// [FloatingActionButtonLocation.endFloat].
+///
+/// ```dart preamble
+/// class AlmostEndFloatFabLocation extends StandardFabLocation
+///     with FabEndOffsetX, FabFloatOffsetY {
+///   @override
+///   double getOffsetX (ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) {
+///     final double directionalAdjustment =
+///       scaffoldGeometry.textDirection == TextDirection.ltr ? -50.0 : 50.0;
+///     return super.getOffsetX(scaffoldGeometry, adjustment) + directionalAdjustment;
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: AppBar(
+///       title: Text('Home page'),
+///     ),
+///     floatingActionButton: FloatingActionButton(
+///       onPressed: () { print('FAB pressed.'); },
+///       tooltip: 'Increment',
+///       child: Icon(Icons.add),
+///     ),
+///     floatingActionButtonLocation: AlmostEndFloatFabLocation(),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+abstract class StandardFabLocation extends FloatingActionButtonLocation {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const StandardFabLocation();
+
+  /// Obtains the x-offset to place the [FloatingActionButton] based on the
+  /// [Scaffold]'s layout.
+  ///
+  /// Used by [getOffset] to compute its x-coordinate.
+  double getOffsetX(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment);
+
+  /// Obtains the y-offset to place the [FloatingActionButton] based on the
+  /// [Scaffold]'s layout.
+  ///
+  /// Used by [getOffset] to compute its y-coordinate.
+  double getOffsetY(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment);
+
+  /// A function returning whether this [StandardFabLocation] is optimized for
+  /// mini [FloatingActionButton]s.
+  bool isMini () => false;
+
+  @override
+  Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
+    final double adjustment = isMini() ? kMiniButtonOffsetAdjustment : 0.0;
+    return Offset(
+      getOffsetX(scaffoldGeometry, adjustment),
+      getOffsetY(scaffoldGeometry, adjustment),
+    );
+  }
+
+  /// Calculates x-offset for left-aligned [FloatingActionButtonLocation]s.
+  static double _leftOffsetX(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) {
+    return kFloatingActionButtonMargin
+        + scaffoldGeometry.minInsets.left
+        - adjustment;
+  }
+
+  /// Calculates x-offset for right-aligned [FloatingActionButtonLocation]s.
+  static double _rightOffsetX(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) {
+    return scaffoldGeometry.scaffoldSize.width
+        - kFloatingActionButtonMargin
+        - scaffoldGeometry.minInsets.right
+        - scaffoldGeometry.floatingActionButtonSize.width
+        + adjustment;
+  }
+
+
+}
+
+/// Mixin for a "top" floating action button location, such as
+/// [FloatingActionButtonLocation.startTop].
+///
+/// The `adjustment`, typically [kMiniButtonOffsetAdjustment], is ignored in the
+/// Y axis of "top" positions. For "top" positions, the X offset is adjusted to
+/// move closer to the edge of the screen. This is so that a minified floating
+/// action button appears to align with [CircleAvatar]s in the
+/// [ListTile.leading] slot of a [ListTile] in a [ListView] in the
+/// [Scaffold.body].
+mixin FabTopOffsetY on StandardFabLocation {
+  /// Calculates y-offset for [FloatingActionButtonLocation]s floating over
+  /// the transition between the [Scaffold.appBar] and the [Scaffold.body].
+  @override
+  double getOffsetY(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) {
+    if (scaffoldGeometry.contentTop > scaffoldGeometry.minViewPadding.top) {
+      final double fabHalfHeight = scaffoldGeometry.floatingActionButtonSize.height / 2.0;
+      return scaffoldGeometry.contentTop - fabHalfHeight;
+    }
+    // Otherwise, ensure we are placed within the bounds of a safe area.
+    return scaffoldGeometry.minViewPadding.top;
+  }
+}
+
+/// Mixin for a "float" floating action button location, such as [FloatingActionButtonLocation.centerFloat].
+mixin FabFloatOffsetY on StandardFabLocation {
+  /// Calculates y-offset for [FloatingActionButtonLocation]s floating at
+  /// the bottom of the screen.
+  @override
+  double getOffsetY(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) {
+    final double contentBottom = scaffoldGeometry.contentBottom;
+    final double bottomContentHeight = scaffoldGeometry.scaffoldSize.height - contentBottom;
+    final double bottomSheetHeight = scaffoldGeometry.bottomSheetSize.height;
+    final double fabHeight = scaffoldGeometry.floatingActionButtonSize.height;
+    final double snackBarHeight = scaffoldGeometry.snackBarSize.height;
+    final double safeMargin = math.max(
+      kFloatingActionButtonMargin,
+      scaffoldGeometry.minViewPadding.bottom - bottomContentHeight,
+    );
+
+    double fabY = contentBottom - fabHeight - safeMargin;
+    if (snackBarHeight > 0.0)
+      fabY = math.min(fabY, contentBottom - snackBarHeight - fabHeight - kFloatingActionButtonMargin);
+    if (bottomSheetHeight > 0.0)
+      fabY = math.min(fabY, contentBottom - bottomSheetHeight - fabHeight / 2.0);
+
+    return fabY + adjustment;
+  }
+}
+
+/// Mixin for a "docked" floating action button location, such as [FloatingActionButtonLocation.endDocked].
+mixin FabDockedOffsetY on StandardFabLocation {
+  /// Calculates y-offset for [FloatingActionButtonLocation]s floating over the
+  /// [Scaffold.bottomNavigationBar] so that the center of the floating
+  /// action button lines up with the top of the bottom navigation bar.
+  @override
+  double getOffsetY(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) {
+    final double contentBottom = scaffoldGeometry.contentBottom;
+    final double contentMargin = scaffoldGeometry.scaffoldSize.height - contentBottom;
+    final double bottomViewPadding = scaffoldGeometry.minViewPadding.bottom;
+    final double bottomSheetHeight = scaffoldGeometry.bottomSheetSize.height;
+    final double fabHeight = scaffoldGeometry.floatingActionButtonSize.height;
+    final double snackBarHeight = scaffoldGeometry.snackBarSize.height;
+    final double safeMargin = bottomViewPadding > contentMargin ? bottomViewPadding : 0.0;
+
+    double fabY = contentBottom - fabHeight / 2.0 - safeMargin;
+    // The FAB should sit with a margin between it and the snack bar.
+    if (snackBarHeight > 0.0)
+      fabY = math.min(fabY, contentBottom - snackBarHeight - fabHeight - kFloatingActionButtonMargin);
+    // The FAB should sit with its center in front of the top of the bottom sheet.
+    if (bottomSheetHeight > 0.0)
+      fabY = math.min(fabY, contentBottom - bottomSheetHeight - fabHeight / 2.0);
+    final double maxFabY = scaffoldGeometry.scaffoldSize.height - fabHeight - safeMargin;
+    return math.min(maxFabY, fabY);
+  }
+}
+
+/// Mixin for a "start" floating action button location, such as [FloatingActionButtonLocation.startTop].
+mixin FabStartOffsetX on StandardFabLocation {
+  /// Calculates x-offset for start-aligned [FloatingActionButtonLocation]s.
+  @override
+  double getOffsetX(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) {
+    assert(scaffoldGeometry.textDirection != null);
+    switch (scaffoldGeometry.textDirection) {
+      case TextDirection.rtl:
+        return StandardFabLocation._rightOffsetX(scaffoldGeometry, adjustment);
+      case TextDirection.ltr:
+        return StandardFabLocation._leftOffsetX(scaffoldGeometry, adjustment);
+    }
+  }
+}
+
+/// Mixin for a "center" floating action button location, such as [FloatingActionButtonLocation.centerFloat].
+mixin FabCenterOffsetX on StandardFabLocation {
+  /// Calculates x-offset for center-aligned [FloatingActionButtonLocation]s.
+  @override
+  double getOffsetX(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) {
+    return (scaffoldGeometry.scaffoldSize.width - scaffoldGeometry.floatingActionButtonSize.width) / 2.0;
+  }
+}
+
+/// Mixin for an "end" floating action button location, such as [FloatingActionButtonLocation.endDocked].
+mixin FabEndOffsetX on StandardFabLocation {
+  /// Calculates x-offset for end-aligned [FloatingActionButtonLocation]s.
+  @override
+  double getOffsetX(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) {
+    assert(scaffoldGeometry.textDirection != null);
+    switch (scaffoldGeometry.textDirection) {
+      case TextDirection.rtl:
+        return StandardFabLocation._leftOffsetX(scaffoldGeometry, adjustment);
+      case TextDirection.ltr:
+        return StandardFabLocation._rightOffsetX(scaffoldGeometry, adjustment);
+    }
+  }
+}
+
+/// Mixin for a "mini" floating action button location, such as [FloatingActionButtonLocation.miniStartTop].
+mixin FabMiniOffsetAdjustment on StandardFabLocation {
+  @override
+  bool isMini () => true;
+}
+
+class _StartTopFabLocation extends StandardFabLocation
+    with FabStartOffsetX, FabTopOffsetY {
+  const _StartTopFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.startTop';
+}
+
+class _MiniStartTopFabLocation extends StandardFabLocation
+    with FabMiniOffsetAdjustment, FabStartOffsetX, FabTopOffsetY {
+  const _MiniStartTopFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.miniStartTop';
+}
+
+class _CenterTopFabLocation extends StandardFabLocation
+    with FabCenterOffsetX, FabTopOffsetY {
+  const _CenterTopFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.centerTop';
+}
+
+class _MiniCenterTopFabLocation extends StandardFabLocation
+    with FabMiniOffsetAdjustment, FabCenterOffsetX, FabTopOffsetY {
+  const _MiniCenterTopFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.miniCenterTop';
+}
+
+class _EndTopFabLocation extends StandardFabLocation
+    with FabEndOffsetX, FabTopOffsetY {
+  const _EndTopFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.endTop';
+}
+
+class _MiniEndTopFabLocation extends StandardFabLocation
+    with FabMiniOffsetAdjustment, FabEndOffsetX, FabTopOffsetY {
+  const _MiniEndTopFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.miniEndTop';
+}
+
+class _StartFloatFabLocation extends StandardFabLocation
+    with FabStartOffsetX, FabFloatOffsetY {
+  const _StartFloatFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.startFloat';
+}
+
+class _MiniStartFloatFabLocation extends StandardFabLocation
+    with FabMiniOffsetAdjustment, FabStartOffsetX, FabFloatOffsetY {
+  const _MiniStartFloatFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.miniStartFloat';
+}
+
+class _CenterFloatFabLocation extends StandardFabLocation
+    with FabCenterOffsetX, FabFloatOffsetY {
+  const _CenterFloatFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.centerFloat';
+}
+
+class _MiniCenterFloatFabLocation extends StandardFabLocation
+    with FabMiniOffsetAdjustment, FabCenterOffsetX, FabFloatOffsetY {
+  const _MiniCenterFloatFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.miniCenterFloat';
+}
+
+class _EndFloatFabLocation extends StandardFabLocation
+    with FabEndOffsetX, FabFloatOffsetY {
+  const _EndFloatFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.endFloat';
+}
+
+class _MiniEndFloatFabLocation extends StandardFabLocation
+    with FabMiniOffsetAdjustment, FabEndOffsetX, FabFloatOffsetY {
+  const _MiniEndFloatFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.miniEndFloat';
+}
+
+class _StartDockedFabLocation extends StandardFabLocation
+    with FabStartOffsetX, FabDockedOffsetY {
+  const _StartDockedFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.startDocked';
+}
+
+class _MiniStartDockedFabLocation extends StandardFabLocation
+    with FabMiniOffsetAdjustment, FabStartOffsetX, FabDockedOffsetY {
+  const _MiniStartDockedFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.miniStartDocked';
+}
+
+class _CenterDockedFabLocation extends StandardFabLocation
+    with FabCenterOffsetX, FabDockedOffsetY {
+  const _CenterDockedFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.centerDocked';
+}
+
+class _MiniCenterDockedFabLocation extends StandardFabLocation
+    with FabMiniOffsetAdjustment, FabCenterOffsetX, FabDockedOffsetY {
+  const _MiniCenterDockedFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.miniCenterDocked';
+}
+
+class _EndDockedFabLocation extends StandardFabLocation
+    with FabEndOffsetX, FabDockedOffsetY {
+  const _EndDockedFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.endDocked';
+}
+
+class _MiniEndDockedFabLocation extends StandardFabLocation
+    with FabMiniOffsetAdjustment, FabEndOffsetX, FabDockedOffsetY {
+  const _MiniEndDockedFabLocation();
+
+  @override
+  String toString() => 'FloatingActionButtonLocation.miniEndDocked';
+}
+
+/// Provider of animations to move the [FloatingActionButton] between [FloatingActionButtonLocation]s.
+///
+/// The [Scaffold] uses [Scaffold.floatingActionButtonAnimator] to define:
+///
+///  * The [Offset] of the [FloatingActionButton] between the old and new
+///    [FloatingActionButtonLocation]s as part of the transition animation.
+///  * An [Animation] to scale the [FloatingActionButton] during the transition.
+///  * An [Animation] to rotate the [FloatingActionButton] during the transition.
+///  * Where to start a new animation from if an animation is interrupted.
+///
+/// See also:
+///
+///  * [FloatingActionButton], which is a circular button typically shown in the
+///    bottom right corner of the app.
+///  * [FloatingActionButtonLocation], which the [Scaffold] uses to place the
+///    [Scaffold.floatingActionButton] within the [Scaffold]'s layout.
+abstract class FloatingActionButtonAnimator {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const FloatingActionButtonAnimator();
+
+  /// Moves the [FloatingActionButton] by scaling out and then in at a new
+  /// [FloatingActionButtonLocation].
+  ///
+  /// This animator shrinks the [FloatingActionButton] down until it disappears, then
+  /// grows it back to full size at its new [FloatingActionButtonLocation].
+  ///
+  /// This is the default [FloatingActionButton] motion animation.
+  static const FloatingActionButtonAnimator scaling = _ScalingFabMotionAnimator();
+
+  /// Gets the [FloatingActionButton]'s position relative to the origin of the
+  /// [Scaffold] based on [progress].
+  ///
+  /// [begin] is the [Offset] provided by the previous
+  /// [FloatingActionButtonLocation].
+  ///
+  /// [end] is the [Offset] provided by the new
+  /// [FloatingActionButtonLocation].
+  ///
+  /// [progress] is the current progress of the transition animation.
+  /// When [progress] is 0.0, the returned [Offset] should be equal to [begin].
+  /// when [progress] is 1.0, the returned [Offset] should be equal to [end].
+  Offset getOffset({ required Offset begin, required Offset end, required double progress });
+
+  /// Animates the scale of the [FloatingActionButton].
+  ///
+  /// The animation should both start and end with a value of 1.0.
+  ///
+  /// For example, to create an animation that linearly scales out and then back in,
+  /// you could join animations that pass each other:
+  ///
+  /// ```dart
+  ///   @override
+  ///   Animation<double> getScaleAnimation({@required Animation<double> parent}) {
+  ///     // The animations will cross at value 0, and the train will return to 1.0.
+  ///     return TrainHoppingAnimation(
+  ///       Tween<double>(begin: 1.0, end: -1.0).animate(parent),
+  ///       Tween<double>(begin: -1.0, end: 1.0).animate(parent),
+  ///     );
+  ///   }
+  /// ```
+  Animation<double> getScaleAnimation({ required Animation<double> parent });
+
+  /// Animates the rotation of [Scaffold.floatingActionButton].
+  ///
+  /// The animation should both start and end with a value of 0.0 or 1.0.
+  ///
+  /// The animation values are a fraction of a full circle, with 0.0 and 1.0
+  /// corresponding to 0 and 360 degrees, while 0.5 corresponds to 180 degrees.
+  ///
+  /// For example, to create a rotation animation that rotates the
+  /// [FloatingActionButton] through a full circle:
+  ///
+  /// ```dart
+  /// @override
+  /// Animation<double> getRotationAnimation({@required Animation<double> parent}) {
+  ///   return Tween<double>(begin: 0.0, end: 1.0).animate(parent);
+  /// }
+  /// ```
+  Animation<double> getRotationAnimation({ required Animation<double> parent });
+
+  /// Gets the progress value to restart a motion animation from when the animation is interrupted.
+  ///
+  /// [previousValue] is the value of the animation before it was interrupted.
+  ///
+  /// The restart of the animation will affect all three parts of the motion animation:
+  /// offset animation, scale animation, and rotation animation.
+  ///
+  /// An interruption triggers if the [Scaffold] is given a new [FloatingActionButtonLocation]
+  /// while it is still animating a transition between two previous [FloatingActionButtonLocation]s.
+  ///
+  /// A sensible default is usually 0.0, which is the same as restarting
+  /// the animation from the beginning, regardless of the original state of the animation.
+  double getAnimationRestart(double previousValue) => 0.0;
+
+  @override
+  String toString() => objectRuntimeType(this, 'FloatingActionButtonAnimator');
+}
+
+class _ScalingFabMotionAnimator extends FloatingActionButtonAnimator {
+  const _ScalingFabMotionAnimator();
+
+  @override
+  Offset getOffset({ required Offset begin, required Offset end, required double progress }) {
+    if (progress < 0.5) {
+      return begin;
+    } else {
+      return end;
+    }
+  }
+
+  @override
+  Animation<double> getScaleAnimation({ required Animation<double> parent }) {
+    // Animate the scale down from 1 to 0 in the first half of the animation
+    // then from 0 back to 1 in the second half.
+    const Curve curve = Interval(0.5, 1.0, curve: Curves.ease);
+    return _AnimationSwap<double>(
+      ReverseAnimation(parent.drive(CurveTween(curve: curve.flipped))),
+      parent.drive(CurveTween(curve: curve)),
+      parent,
+      0.5,
+    );
+  }
+
+  // Because we only see the last half of the rotation tween,
+  // it needs to go twice as far.
+  static final Animatable<double> _rotationTween = Tween<double>(
+    begin: 1.0 - kFloatingActionButtonTurnInterval * 2.0,
+    end: 1.0,
+  );
+
+  static final Animatable<double> _thresholdCenterTween = CurveTween(curve: const Threshold(0.5));
+
+  @override
+  Animation<double> getRotationAnimation({ required Animation<double> parent }) {
+    // This rotation will turn on the way in, but not on the way out.
+    return _AnimationSwap<double>(
+      parent.drive(_rotationTween),
+      ReverseAnimation(parent.drive(_thresholdCenterTween)),
+      parent,
+      0.5,
+    );
+  }
+
+  // If the animation was just starting, we'll continue from where we left off.
+  // If the animation was finishing, we'll treat it as if we were starting at that point in reverse.
+  // This avoids a size jump during the animation.
+  @override
+  double getAnimationRestart(double previousValue) => math.min(1.0 - previousValue, previousValue);
+}
+
+/// An animation that swaps from one animation to the next when the [parent] passes [swapThreshold].
+///
+/// The [value] of this animation is the value of [first] when [parent.value] < [swapThreshold]
+/// and the value of [next] otherwise.
+class _AnimationSwap<T> extends CompoundAnimation<T> {
+  /// Creates an [_AnimationSwap].
+  ///
+  /// Both arguments must be non-null. Either can be an [_AnimationSwap] itself
+  /// to combine multiple animations.
+  _AnimationSwap(Animation<T> first, Animation<T> next, this.parent, this.swapThreshold) : super(first: first, next: next);
+
+  final Animation<double> parent;
+  final double swapThreshold;
+
+  @override
+  T get value => parent.value < swapThreshold ? first.value : next.value;
+}
diff --git a/lib/src/material/floating_action_button_theme.dart b/lib/src/material/floating_action_button_theme.dart
new file mode 100644
index 0000000..8f097a4
--- /dev/null
+++ b/lib/src/material/floating_action_button_theme.dart
@@ -0,0 +1,200 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+/// Defines default property values for descendant [FloatingActionButton]
+/// widgets.
+///
+/// Descendant widgets obtain the current [FloatingActionButtonThemeData] object
+/// using `Theme.of(context).floatingActionButtonTheme`. Instances of
+/// [FloatingActionButtonThemeData] can be customized with
+/// [FloatingActionButtonThemeData.copyWith].
+///
+/// Typically a [FloatingActionButtonThemeData] is specified as part of the
+/// overall [Theme] with [ThemeData.floatingActionButtonTheme].
+///
+/// All [FloatingActionButtonThemeData] properties are `null` by default.
+/// When null, the [FloatingActionButton] will use the values from [ThemeData]
+/// if they exist, otherwise it will provide its own defaults.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class FloatingActionButtonThemeData with Diagnosticable {
+  /// Creates a theme that can be used for
+  /// [ThemeData.floatingActionButtonTheme].
+  const FloatingActionButtonThemeData({
+    this.foregroundColor,
+    this.backgroundColor,
+    this.focusColor,
+    this.hoverColor,
+    this.splashColor,
+    this.elevation,
+    this.focusElevation,
+    this.hoverElevation,
+    this.disabledElevation,
+    this.highlightElevation,
+    this.shape,
+  });
+
+  /// Color to be used for the unselected, enabled [FloatingActionButton]'s
+  /// foreground.
+  final Color? foregroundColor;
+
+  /// Color to be used for the unselected, enabled [FloatingActionButton]'s
+  /// background.
+  final Color? backgroundColor;
+
+  /// The color to use for filling the button when the button has input focus.
+  final Color? focusColor;
+
+  /// The color to use for filling the button when the button has a pointer
+  /// hovering over it.
+  final Color? hoverColor;
+
+  /// The splash color for this [FloatingActionButton]'s [InkWell].
+  final Color? splashColor;
+
+  /// The z-coordinate to be used for the unselected, enabled
+  /// [FloatingActionButton]'s elevation foreground.
+  final double? elevation;
+
+  /// The z-coordinate at which to place this button relative to its parent when
+  /// the button has the input focus.
+  ///
+  /// This controls the size of the shadow below the floating action button.
+  final double? focusElevation;
+
+  /// The z-coordinate at which to place this button relative to its parent when
+  /// the button is enabled and has a pointer hovering over it.
+  ///
+  /// This controls the size of the shadow below the floating action button.
+  final double? hoverElevation;
+
+  /// The z-coordinate to be used for the disabled [FloatingActionButton]'s
+  /// elevation foreground.
+  final double? disabledElevation;
+
+  /// The z-coordinate to be used for the selected, enabled
+  /// [FloatingActionButton]'s elevation foreground.
+  final double? highlightElevation;
+
+  /// The shape to be used for the floating action button's [Material].
+  final ShapeBorder? shape;
+
+  /// Creates a copy of this object with the given fields replaced with the
+  /// new values.
+  FloatingActionButtonThemeData copyWith({
+    Color? foregroundColor,
+    Color? backgroundColor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? splashColor,
+    double? elevation,
+    double? focusElevation,
+    double? hoverElevation,
+    double? disabledElevation,
+    double? highlightElevation,
+    ShapeBorder? shape,
+  }) {
+    return FloatingActionButtonThemeData(
+      foregroundColor: foregroundColor ?? this.foregroundColor,
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      focusColor: focusColor ?? this.focusColor,
+      hoverColor: hoverColor ?? this.hoverColor,
+      splashColor: splashColor ?? this.splashColor,
+      elevation: elevation ?? this.elevation,
+      focusElevation: focusElevation ?? this.focusElevation,
+      hoverElevation: hoverElevation ?? this.hoverElevation,
+      disabledElevation: disabledElevation ?? this.disabledElevation,
+      highlightElevation: highlightElevation ?? this.highlightElevation,
+      shape: shape ?? this.shape,
+    );
+  }
+
+  /// Linearly interpolate between two floating action button themes.
+  ///
+  /// If both arguments are null then null is returned.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static FloatingActionButtonThemeData? lerp(FloatingActionButtonThemeData? a, FloatingActionButtonThemeData? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    return FloatingActionButtonThemeData(
+      foregroundColor: Color.lerp(a?.foregroundColor, b?.foregroundColor, t),
+      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
+      focusColor: Color.lerp(a?.focusColor, b?.focusColor, t),
+      hoverColor: Color.lerp(a?.hoverColor, b?.hoverColor, t),
+      splashColor: Color.lerp(a?.splashColor, b?.splashColor, t),
+      elevation: lerpDouble(a?.elevation, b?.elevation, t),
+      focusElevation: lerpDouble(a?.focusElevation, b?.focusElevation, t),
+      hoverElevation: lerpDouble(a?.hoverElevation, b?.hoverElevation, t),
+      disabledElevation: lerpDouble(a?.disabledElevation, b?.disabledElevation, t),
+      highlightElevation: lerpDouble(a?.highlightElevation, b?.highlightElevation, t),
+      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      foregroundColor,
+      backgroundColor,
+      focusColor,
+      hoverColor,
+      splashColor,
+      elevation,
+      focusElevation,
+      hoverElevation,
+      disabledElevation,
+      highlightElevation,
+      shape,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is FloatingActionButtonThemeData
+        && other.foregroundColor == foregroundColor
+        && other.backgroundColor == backgroundColor
+        && other.focusColor == focusColor
+        && other.hoverColor == hoverColor
+        && other.splashColor == splashColor
+        && other.elevation == elevation
+        && other.focusElevation == focusElevation
+        && other.hoverElevation == hoverElevation
+        && other.disabledElevation == disabledElevation
+        && other.highlightElevation == highlightElevation
+        && other.shape == shape;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    const FloatingActionButtonThemeData defaultData = FloatingActionButtonThemeData();
+
+    properties.add(ColorProperty('foregroundColor', foregroundColor, defaultValue: defaultData.foregroundColor));
+    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: defaultData.backgroundColor));
+    properties.add(ColorProperty('focusColor', focusColor, defaultValue: defaultData.focusColor));
+    properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: defaultData.hoverColor));
+    properties.add(ColorProperty('splashColor', splashColor, defaultValue: defaultData.splashColor));
+    properties.add(DoubleProperty('elevation', elevation, defaultValue: defaultData.elevation));
+    properties.add(DoubleProperty('focusElevation', focusElevation, defaultValue: defaultData.focusElevation));
+    properties.add(DoubleProperty('hoverElevation', hoverElevation, defaultValue: defaultData.hoverElevation));
+    properties.add(DoubleProperty('disabledElevation', disabledElevation, defaultValue: defaultData.disabledElevation));
+    properties.add(DoubleProperty('highlightElevation', highlightElevation, defaultValue: defaultData.highlightElevation));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: defaultData.shape));
+  }
+}
diff --git a/lib/src/material/flutter_logo.dart b/lib/src/material/flutter_logo.dart
new file mode 100644
index 0000000..ea231b3
--- /dev/null
+++ b/lib/src/material/flutter_logo.dart
@@ -0,0 +1,77 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+/// The Flutter logo, in widget form. This widget respects the [IconTheme].
+/// For guidelines on using the Flutter logo, visit https://flutter.dev/brand.
+///
+/// See also:
+///
+///  * [IconTheme], which provides ambient configuration for icons.
+///  * [Icon], for showing icons the Material design icon library.
+///  * [ImageIcon], for showing icons from [AssetImage]s or other [ImageProvider]s.
+class FlutterLogo extends StatelessWidget {
+  /// Creates a widget that paints the Flutter logo.
+  ///
+  /// The [size] defaults to the value given by the current [IconTheme].
+  ///
+  /// The [textColor], [style], [duration], and [curve] arguments must not be
+  /// null.
+  const FlutterLogo({
+    Key? key,
+    this.size,
+    this.textColor = const Color(0xFF757575),
+    this.style = FlutterLogoStyle.markOnly,
+    this.duration = const Duration(milliseconds: 750),
+    this.curve = Curves.fastOutSlowIn,
+  }) : assert(textColor != null),
+       assert(style != null),
+       assert(duration != null),
+       assert(curve != null),
+       super(key: key);
+
+  /// The size of the logo in logical pixels.
+  ///
+  /// The logo will be fit into a square this size.
+  ///
+  /// Defaults to the current [IconTheme] size, if any. If there is no
+  /// [IconTheme], or it does not specify an explicit size, then it defaults to
+  /// 24.0.
+  final double? size;
+
+  /// The color used to paint the "Flutter" text on the logo, if [style] is
+  /// [FlutterLogoStyle.horizontal] or [FlutterLogoStyle.stacked].
+  ///
+  /// If possible, the default (a medium grey) should be used against a white
+  /// background.
+  final Color textColor;
+
+  /// Whether and where to draw the "Flutter" text. By default, only the logo
+  /// itself is drawn.
+  final FlutterLogoStyle style;
+
+  /// The length of time for the animation if the [style] or [textColor]
+  /// properties are changed.
+  final Duration duration;
+
+  /// The curve for the logo animation if the [style] or [textColor] change.
+  final Curve curve;
+
+  @override
+  Widget build(BuildContext context) {
+    final IconThemeData iconTheme = IconTheme.of(context);
+    final double? iconSize = size ?? iconTheme.size;
+    return AnimatedContainer(
+      width: iconSize,
+      height: iconSize,
+      duration: duration,
+      curve: curve,
+      decoration: FlutterLogoDecoration(
+        style: style,
+        textColor: textColor,
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/grid_tile.dart b/lib/src/material/grid_tile.dart
new file mode 100644
index 0000000..01a8f97
--- /dev/null
+++ b/lib/src/material/grid_tile.dart
@@ -0,0 +1,73 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+/// A tile in a material design grid list.
+///
+/// A grid list is a [GridView] of tiles in a vertical and horizontal
+/// array. Each tile typically contains some visually rich content (e.g., an
+/// image) together with a [GridTileBar] in either a [header] or a [footer].
+///
+/// See also:
+///
+///  * [GridView], which is a scrollable grid of tiles.
+///  * [GridTileBar], which is typically used in either the [header] or
+///    [footer].
+///  * <https://material.io/design/components/image-lists.html>
+class GridTile extends StatelessWidget {
+  /// Creates a grid tile.
+  ///
+  /// Must have a child. Does not typically have both a header and a footer.
+  const GridTile({
+    Key? key,
+    this.header,
+    this.footer,
+    required this.child,
+  }) : assert(child != null),
+       super(key: key);
+
+  /// The widget to show over the top of this grid tile.
+  ///
+  /// Typically a [GridTileBar].
+  final Widget? header;
+
+  /// The widget to show over the bottom of this grid tile.
+  ///
+  /// Typically a [GridTileBar].
+  final Widget? footer;
+
+  /// The widget that fills the tile.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    if (header == null && footer == null)
+      return child;
+
+    return Stack(
+      children: <Widget>[
+        Positioned.fill(
+          child: child,
+        ),
+        if (header != null)
+          Positioned(
+            top: 0.0,
+            left: 0.0,
+            right: 0.0,
+            child: header!,
+          ),
+        if (footer != null)
+          Positioned(
+            left: 0.0,
+            bottom: 0.0,
+            right: 0.0,
+            child: footer!,
+          ),
+      ],
+    );
+  }
+}
diff --git a/lib/src/material/grid_tile_bar.dart b/lib/src/material/grid_tile_bar.dart
new file mode 100644
index 0000000..7a74c51
--- /dev/null
+++ b/lib/src/material/grid_tile_bar.dart
@@ -0,0 +1,127 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'theme.dart';
+
+/// A header used in a material design [GridTile].
+///
+/// Typically used to add a one or two line header or footer on a [GridTile].
+///
+/// For a one-line header, include a [title] widget. To add a second line, also
+/// include a [subtitle] widget. Use [leading] or [trailing] to add an icon.
+///
+/// See also:
+///
+///  * [GridTile]
+///  * <https://material.io/design/components/image-lists.html#anatomy>
+class GridTileBar extends StatelessWidget {
+  /// Creates a grid tile bar.
+  ///
+  /// Typically used to with [GridTile].
+  const GridTileBar({
+    Key? key,
+    this.backgroundColor,
+    this.leading,
+    this.title,
+    this.subtitle,
+    this.trailing,
+  }) : super(key: key);
+
+  /// The color to paint behind the child widgets.
+  ///
+  /// Defaults to transparent.
+  final Color? backgroundColor;
+
+  /// A widget to display before the title.
+  ///
+  /// Typically an [Icon] or an [IconButton] widget.
+  final Widget? leading;
+
+  /// The primary content of the list item.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? title;
+
+  /// Additional content displayed below the title.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? subtitle;
+
+  /// A widget to display after the title.
+  ///
+  /// Typically an [Icon] or an [IconButton] widget.
+  final Widget? trailing;
+
+  @override
+  Widget build(BuildContext context) {
+    BoxDecoration? decoration;
+    if (backgroundColor != null)
+      decoration = BoxDecoration(color: backgroundColor);
+
+    final EdgeInsetsDirectional padding = EdgeInsetsDirectional.only(
+      start: leading != null ? 8.0 : 16.0,
+      end: trailing != null ? 8.0 : 16.0,
+    );
+
+    final ThemeData theme = Theme.of(context);
+    final ThemeData darkTheme = ThemeData(
+      brightness: Brightness.dark,
+      accentColor: theme.accentColor,
+      accentColorBrightness: theme.accentColorBrightness,
+    );
+    return Container(
+      padding: padding,
+      decoration: decoration,
+      height: (title != null && subtitle != null) ? 68.0 : 48.0,
+      child: Theme(
+        data: darkTheme,
+        child: IconTheme.merge(
+          data: const IconThemeData(color: Colors.white),
+          child: Row(
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: <Widget>[
+              if (leading != null)
+                Padding(padding: const EdgeInsetsDirectional.only(end: 8.0), child: leading),
+              if (title != null && subtitle != null)
+                Expanded(
+                  child: Column(
+                    mainAxisAlignment: MainAxisAlignment.center,
+                    crossAxisAlignment: CrossAxisAlignment.start,
+                    children: <Widget>[
+                      DefaultTextStyle(
+                        style: darkTheme.textTheme.subtitle1!,
+                        softWrap: false,
+                        overflow: TextOverflow.ellipsis,
+                        child: title!,
+                      ),
+                      DefaultTextStyle(
+                        style: darkTheme.textTheme.caption!,
+                        softWrap: false,
+                        overflow: TextOverflow.ellipsis,
+                        child: subtitle!,
+                      ),
+                    ],
+                  ),
+                )
+              else if (title != null || subtitle != null)
+                Expanded(
+                  child: DefaultTextStyle(
+                    style: darkTheme.textTheme.subtitle1!,
+                    softWrap: false,
+                    overflow: TextOverflow.ellipsis,
+                    child: title ?? subtitle!,
+                  ),
+                ),
+              if (trailing != null)
+                Padding(padding: const EdgeInsetsDirectional.only(start: 8.0), child: trailing),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/icon_button.dart b/lib/src/material/icon_button.dart
new file mode 100644
index 0000000..1e5e2af
--- /dev/null
+++ b/lib/src/material/icon_button.dart
@@ -0,0 +1,411 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'constants.dart';
+import 'debug.dart';
+import 'icons.dart';
+import 'ink_well.dart';
+import 'material.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+import 'tooltip.dart';
+
+// Minimum logical pixel size of the IconButton.
+// See: <https://material.io/design/usability/accessibility.html#layout-typography>.
+const double _kMinButtonSize = kMinInteractiveDimension;
+
+/// A material design icon button.
+///
+/// An icon button is a picture printed on a [Material] widget that reacts to
+/// touches by filling with color (ink).
+///
+/// Icon buttons are commonly used in the [AppBar.actions] field, but they can
+/// be used in many other places as well.
+///
+/// If the [onPressed] callback is null, then the button will be disabled and
+/// will not react to touch.
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// The hit region of an icon button will, if possible, be at least
+/// kMinInteractiveDimension pixels in size, regardless of the actual
+/// [iconSize], to satisfy the [touch target size](https://material.io/design/layout/spacing-methods.html#touch-targets)
+/// requirements in the Material Design specification. The [alignment] controls
+/// how the icon itself is positioned within the hit region.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+///
+/// This sample shows an `IconButton` that uses the Material icon "volume_up" to
+/// increase the volume.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/icon_button.png)
+///
+/// ```dart preamble
+/// double _volume = 0.0;
+/// ```
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Column(
+///     mainAxisSize: MainAxisSize.min,
+///     children: <Widget>[
+///       IconButton(
+///         icon: Icon(Icons.volume_up),
+///         tooltip: 'Increase volume by 10',
+///         onPressed: () {
+///           setState(() {
+///             _volume += 10;
+///           });
+///         },
+///       ),
+///       Text('Volume : $_volume')
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ### Adding a filled background
+///
+/// Icon buttons don't support specifying a background color or other
+/// background decoration because typically the icon is just displayed
+/// on top of the parent widget's background. Icon buttons that appear
+/// in [AppBar.actions] are an example of this.
+///
+/// It's easy enough to create an icon button with a filled background
+/// using the [Ink] widget. The [Ink] widget renders a decoration on
+/// the underlying [Material] along with the splash and highlight
+/// [InkResponse] contributed by descendant widgets.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// In this sample the icon button's background color is defined with an [Ink]
+/// widget whose child is an [IconButton]. The icon button's filled background
+/// is a light shade of blue, it's a filled circle, and it's as big as the
+/// button is.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/icon_button_background.png)
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Material(
+///     color: Colors.white,
+///     child: Center(
+///       child: Ink(
+///         decoration: const ShapeDecoration(
+///           color: Colors.lightBlue,
+///           shape: CircleBorder(),
+///         ),
+///         child: IconButton(
+///           icon: Icon(Icons.android),
+///           color: Colors.white,
+///           onPressed: () {},
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Icons], a library of predefined icons.
+///  * [BackButton], an icon button for a "back" affordance which adapts to the
+///    current platform's conventions.
+///  * [CloseButton], an icon button for closing pages.
+///  * [AppBar], to show a toolbar at the top of an application.
+///  * [TextButton], [ElevatedButton], [OutlinedButton], for buttons with text labels and an optional icon.
+///  * [InkResponse] and [InkWell], for the ink splash effect itself.
+class IconButton extends StatelessWidget {
+  /// Creates an icon button.
+  ///
+  /// Icon buttons are commonly used in the [AppBar.actions] field, but they can
+  /// be used in many other places as well.
+  ///
+  /// Requires one of its ancestors to be a [Material] widget.
+  ///
+  /// The [iconSize], [padding], [autofocus], and [alignment] arguments must not
+  /// be null (though they each have default values).
+  ///
+  /// The [icon] argument must be specified, and is typically either an [Icon]
+  /// or an [ImageIcon].
+  const IconButton({
+    Key? key,
+    this.iconSize = 24.0,
+    this.visualDensity,
+    this.padding = const EdgeInsets.all(8.0),
+    this.alignment = Alignment.center,
+    this.splashRadius,
+    required this.icon,
+    this.color,
+    this.focusColor,
+    this.hoverColor,
+    this.highlightColor,
+    this.splashColor,
+    this.disabledColor,
+    required this.onPressed,
+    this.mouseCursor = SystemMouseCursors.click,
+    this.focusNode,
+    this.autofocus = false,
+    this.tooltip,
+    this.enableFeedback = true,
+    this.constraints,
+  }) : assert(iconSize != null),
+       assert(padding != null),
+       assert(alignment != null),
+       assert(splashRadius == null || splashRadius > 0),
+       assert(autofocus != null),
+       assert(icon != null),
+       super(key: key);
+
+  /// The size of the icon inside the button.
+  ///
+  /// This property must not be null. It defaults to 24.0.
+  ///
+  /// The size given here is passed down to the widget in the [icon] property
+  /// via an [IconTheme]. Setting the size here instead of in, for example, the
+  /// [Icon.size] property allows the [IconButton] to size the splash area to
+  /// fit the [Icon]. If you were to set the size of the [Icon] using
+  /// [Icon.size] instead, then the [IconButton] would default to 24.0 and then
+  /// the [Icon] itself would likely get clipped.
+  final double iconSize;
+
+  /// Defines how compact the icon button's layout will be.
+  ///
+  /// {@macro flutter.material.themedata.visualDensity}
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.visualDensity], which specifies the [visualDensity] for all
+  ///    widgets within a [Theme].
+  final VisualDensity? visualDensity;
+
+  /// The padding around the button's icon. The entire padded icon will react
+  /// to input gestures.
+  ///
+  /// This property must not be null. It defaults to 8.0 padding on all sides.
+  final EdgeInsetsGeometry padding;
+
+  /// Defines how the icon is positioned within the IconButton.
+  ///
+  /// This property must not be null. It defaults to [Alignment.center].
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], a class with convenient constants typically used to
+  ///    specify an [AlignmentGeometry].
+  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
+  ///    relative to text direction.
+  final AlignmentGeometry alignment;
+
+  /// The splash radius.
+  ///
+  /// If null, default splash radius of [Material.defaultSplashRadius] is used.
+  final double? splashRadius;
+
+  /// The icon to display inside the button.
+  ///
+  /// The [Icon.size] and [Icon.color] of the icon is configured automatically
+  /// based on the [iconSize] and [color] properties of _this_ widget using an
+  /// [IconTheme] and therefore should not be explicitly given in the icon
+  /// widget.
+  ///
+  /// This property must not be null.
+  ///
+  /// See [Icon], [ImageIcon].
+  final Widget icon;
+
+  /// The color for the button's icon when it has the input focus.
+  ///
+  /// Defaults to [ThemeData.focusColor] of the ambient theme.
+  final Color? focusColor;
+
+  /// The color for the button's icon when a pointer is hovering over it.
+  ///
+  /// Defaults to [ThemeData.hoverColor] of the ambient theme.
+  final Color? hoverColor;
+
+  /// The color to use for the icon inside the button, if the icon is enabled.
+  /// Defaults to leaving this up to the [icon] widget.
+  ///
+  /// The icon is enabled if [onPressed] is not null.
+  ///
+  /// ```dart
+  /// IconButton(
+  ///   color: Colors.blue,
+  ///   onPressed: _handleTap,
+  ///   icon: Icons.widgets,
+  /// )
+  /// ```
+  final Color? color;
+
+  /// The primary color of the button when the button is in the down (pressed) state.
+  /// The splash is represented as a circular overlay that appears above the
+  /// [highlightColor] overlay. The splash overlay has a center point that matches
+  /// the hit point of the user touch event. The splash overlay will expand to
+  /// fill the button area if the touch is held for long enough time. If the splash
+  /// color has transparency then the highlight and button color will show through.
+  ///
+  /// Defaults to the Theme's splash color, [ThemeData.splashColor].
+  final Color? splashColor;
+
+  /// The secondary color of the button when the button is in the down (pressed)
+  /// state. The highlight color is represented as a solid color that is overlaid over the
+  /// button color (if any). If the highlight color has transparency, the button color
+  /// will show through. The highlight fades in quickly as the button is held down.
+  ///
+  /// Defaults to the Theme's highlight color, [ThemeData.highlightColor].
+  final Color? highlightColor;
+
+  /// The color to use for the icon inside the button, if the icon is disabled.
+  /// Defaults to the [ThemeData.disabledColor] of the current [Theme].
+  ///
+  /// The icon is disabled if [onPressed] is null.
+  final Color? disabledColor;
+
+  /// The callback that is called when the button is tapped or otherwise activated.
+  ///
+  /// If this is set to null, the button will be disabled.
+  final VoidCallback? onPressed;
+
+  /// {@macro flutter.material.RawMaterialButton.mouseCursor}
+  ///
+  /// Defaults to [SystemMouseCursors.click].
+  final MouseCursor mouseCursor;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  /// Text that describes the action that will occur when the button is pressed.
+  ///
+  /// This text is displayed when the user long-presses on the button and is
+  /// used for accessibility.
+  final String? tooltip;
+
+  /// Whether detected gestures should provide acoustic and/or haptic feedback.
+  ///
+  /// For example, on Android a tap will produce a clicking sound and a
+  /// long-press will produce a short vibration, when feedback is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [Feedback] for providing platform-specific feedback to certain actions.
+  final bool enableFeedback;
+
+  /// Optional size constraints for the button.
+  ///
+  /// When unspecified, defaults to:
+  /// ```dart
+  /// const BoxConstraints(
+  ///   minWidth: kMinInteractiveDimension,
+  ///   minHeight: kMinInteractiveDimension,
+  /// )
+  /// ```
+  /// where [kMinInteractiveDimension] is 48.0, and then with visual density
+  /// applied.
+  ///
+  /// The default constraints ensure that the button is accessible.
+  /// Specifying this parameter enables creation of buttons smaller than
+  /// the minimum size, but it is not recommended.
+  ///
+  /// The visual density uses the [visualDensity] parameter if specified,
+  /// and `Theme.of(context).visualDensity` otherwise.
+  final BoxConstraints? constraints;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    final ThemeData theme = Theme.of(context);
+    Color? currentColor;
+    if (onPressed != null)
+      currentColor = color;
+    else
+      currentColor = disabledColor ?? theme.disabledColor;
+
+    final VisualDensity effectiveVisualDensity = visualDensity ?? theme.visualDensity;
+
+    final BoxConstraints unadjustedConstraints = constraints ?? const BoxConstraints(
+      minWidth: _kMinButtonSize,
+      minHeight: _kMinButtonSize,
+    );
+    final BoxConstraints adjustedConstraints = effectiveVisualDensity.effectiveConstraints(unadjustedConstraints);
+
+    Widget result = ConstrainedBox(
+      constraints: adjustedConstraints,
+      child: Padding(
+        padding: padding,
+        child: SizedBox(
+          height: iconSize,
+          width: iconSize,
+          child: Align(
+            alignment: alignment,
+            child: IconTheme.merge(
+              data: IconThemeData(
+                size: iconSize,
+                color: currentColor,
+              ),
+              child: icon,
+            ),
+          ),
+        ),
+      ),
+    );
+
+    if (tooltip != null) {
+      result = Tooltip(
+        message: tooltip!,
+        child: result,
+      );
+    }
+
+    return Semantics(
+      button: true,
+      enabled: onPressed != null,
+      child: InkResponse(
+        focusNode: focusNode,
+        autofocus: autofocus,
+        canRequestFocus: onPressed != null,
+        onTap: onPressed,
+        mouseCursor: mouseCursor,
+        enableFeedback: enableFeedback,
+        child: result,
+        focusColor: focusColor ?? theme.focusColor,
+        hoverColor: hoverColor ?? theme.hoverColor,
+        highlightColor: highlightColor ?? theme.highlightColor,
+        splashColor: splashColor ?? theme.splashColor,
+        radius: splashRadius ?? math.max(
+          Material.defaultSplashRadius,
+          (iconSize + math.min(padding.horizontal, padding.vertical)) * 0.7,
+          // x 0.5 for diameter -> radius and + 40% overflow derived from other Material apps.
+        ),
+      ),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Widget>('icon', icon, showName: false));
+    properties.add(StringProperty('tooltip', tooltip, defaultValue: null, quoted: false));
+    properties.add(ObjectFlagProperty<VoidCallback>('onPressed', onPressed, ifNull: 'disabled'));
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(ColorProperty('disabledColor', disabledColor, defaultValue: null));
+    properties.add(ColorProperty('focusColor', focusColor, defaultValue: null));
+    properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: null));
+    properties.add(ColorProperty('highlightColor', highlightColor, defaultValue: null));
+    properties.add(ColorProperty('splashColor', splashColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
+    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
+  }
+}
diff --git a/lib/src/material/icons.dart b/lib/src/material/icons.dart
new file mode 100644
index 0000000..eedc8ac
--- /dev/null
+++ b/lib/src/material/icons.dart
@@ -0,0 +1,17132 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart' show defaultTargetPlatform;
+import 'package:flute/widgets.dart';
+
+// ignore_for_file: non_constant_identifier_names
+class _PlatformAdaptiveIcons {
+  static bool _isCupertino() {
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        return false;
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        return true;
+    }
+  }
+
+  // Generated code: do not hand-edit.
+  // See https://github.com/flutter/flutter/wiki/Updating-Material-Design-Fonts-&-Icons
+  // BEGIN GENERATED PLATFORM ADAPTIVE ICONS
+
+  /// Platform-adaptive icon for <i class="material-icons md-36">arrow_back</i> &#x2014; material icon named "arrow back" and <i class="material-icons md-36">arrow_back_ios</i> &#x2014; material icon named "arrow back ios".;
+  IconData get arrow_back => !_isCupertino() ? Icons.arrow_back : Icons.arrow_back_ios;
+
+  /// Platform-adaptive icon for <i class="material-icons-outlined md-36">arrow_back</i> &#x2014; material icon named "arrow back outlined" and <i class="material-icons-outlined md-36">arrow_back_ios</i> &#x2014; material icon named "arrow back ios outlined".;
+  IconData get arrow_back_outlined => !_isCupertino() ? Icons.arrow_back_outlined : Icons.arrow_back_ios_outlined;
+
+  /// Platform-adaptive icon for <i class="material-icons-round md-36">arrow_back</i> &#x2014; material icon named "arrow back rounded" and <i class="material-icons-round md-36">arrow_back_ios</i> &#x2014; material icon named "arrow back ios rounded".;
+  IconData get arrow_back_rounded => !_isCupertino() ? Icons.arrow_back_rounded : Icons.arrow_back_ios_rounded;
+
+  /// Platform-adaptive icon for <i class="material-icons-sharp md-36">arrow_back</i> &#x2014; material icon named "arrow back sharp" and <i class="material-icons-sharp md-36">arrow_back_ios</i> &#x2014; material icon named "arrow back ios sharp".;
+  IconData get arrow_back_sharp => !_isCupertino() ? Icons.arrow_back_sharp : Icons.arrow_back_ios_sharp;
+
+  /// Platform-adaptive icon for <i class="material-icons md-36">arrow_forward</i> &#x2014; material icon named "arrow forward" and <i class="material-icons md-36">arrow_forward_ios</i> &#x2014; material icon named "arrow forward ios".;
+  IconData get arrow_forward => !_isCupertino() ? Icons.arrow_forward : Icons.arrow_forward_ios;
+
+  /// Platform-adaptive icon for <i class="material-icons-outlined md-36">arrow_forward</i> &#x2014; material icon named "arrow forward outlined" and <i class="material-icons-outlined md-36">arrow_forward_ios</i> &#x2014; material icon named "arrow forward ios outlined".;
+  IconData get arrow_forward_outlined => !_isCupertino() ? Icons.arrow_forward_outlined : Icons.arrow_forward_ios_outlined;
+
+  /// Platform-adaptive icon for <i class="material-icons-round md-36">arrow_forward</i> &#x2014; material icon named "arrow forward rounded" and <i class="material-icons-round md-36">arrow_forward_ios</i> &#x2014; material icon named "arrow forward ios rounded".;
+  IconData get arrow_forward_rounded => !_isCupertino() ? Icons.arrow_forward_rounded : Icons.arrow_forward_ios_rounded;
+
+  /// Platform-adaptive icon for <i class="material-icons-sharp md-36">arrow_forward</i> &#x2014; material icon named "arrow forward sharp" and <i class="material-icons-sharp md-36">arrow_forward_ios</i> &#x2014; material icon named "arrow forward ios sharp".;
+  IconData get arrow_forward_sharp => !_isCupertino() ? Icons.arrow_forward_sharp : Icons.arrow_forward_ios_sharp;
+
+  /// Platform-adaptive icon for <i class="material-icons md-36">flip_camera_android</i> &#x2014; material icon named "flip camera android" and <i class="material-icons md-36">flip_camera_ios</i> &#x2014; material icon named "flip camera ios".;
+  IconData get flip_camera => !_isCupertino() ? Icons.flip_camera_android : Icons.flip_camera_ios;
+
+  /// Platform-adaptive icon for <i class="material-icons-outlined md-36">flip_camera_android</i> &#x2014; material icon named "flip camera android outlined" and <i class="material-icons-outlined md-36">flip_camera_ios</i> &#x2014; material icon named "flip camera ios outlined".;
+  IconData get flip_camera_outlined => !_isCupertino() ? Icons.flip_camera_android_outlined : Icons.flip_camera_ios_outlined;
+
+  /// Platform-adaptive icon for <i class="material-icons-round md-36">flip_camera_android</i> &#x2014; material icon named "flip camera android rounded" and <i class="material-icons-round md-36">flip_camera_ios</i> &#x2014; material icon named "flip camera ios rounded".;
+  IconData get flip_camera_rounded => !_isCupertino() ? Icons.flip_camera_android_rounded : Icons.flip_camera_ios_rounded;
+
+  /// Platform-adaptive icon for <i class="material-icons-sharp md-36">flip_camera_android</i> &#x2014; material icon named "flip camera android sharp" and <i class="material-icons-sharp md-36">flip_camera_ios</i> &#x2014; material icon named "flip camera ios sharp".;
+  IconData get flip_camera_sharp => !_isCupertino() ? Icons.flip_camera_android_sharp : Icons.flip_camera_ios_sharp;
+
+  /// Platform-adaptive icon for <i class="material-icons md-36">more_vert</i> &#x2014; material icon named "more vert" and <i class="material-icons md-36">more_horiz</i> &#x2014; material icon named "more horiz".;
+  IconData get more => !_isCupertino() ? Icons.more_vert : Icons.more_horiz;
+
+  /// Platform-adaptive icon for <i class="material-icons-outlined md-36">more_vert</i> &#x2014; material icon named "more vert outlined" and <i class="material-icons-outlined md-36">more_horiz</i> &#x2014; material icon named "more horiz outlined".;
+  IconData get more_outlined => !_isCupertino() ? Icons.more_vert_outlined : Icons.more_horiz_outlined;
+
+  /// Platform-adaptive icon for <i class="material-icons-round md-36">more_vert</i> &#x2014; material icon named "more vert rounded" and <i class="material-icons-round md-36">more_horiz</i> &#x2014; material icon named "more horiz rounded".;
+  IconData get more_rounded => !_isCupertino() ? Icons.more_vert_rounded : Icons.more_horiz_rounded;
+
+  /// Platform-adaptive icon for <i class="material-icons-sharp md-36">more_vert</i> &#x2014; material icon named "more vert sharp" and <i class="material-icons-sharp md-36">more_horiz</i> &#x2014; material icon named "more horiz sharp".;
+  IconData get more_sharp => !_isCupertino() ? Icons.more_vert_sharp : Icons.more_horiz_sharp;
+
+  /// Platform-adaptive icon for <i class="material-icons md-36">share</i> &#x2014; material icon named "share" and <i class="material-icons md-36">ios_share</i> &#x2014; material icon named "ios share".;
+  IconData get share => !_isCupertino() ? Icons.share : Icons.ios_share;
+  // END GENERATED PLATFORM ADAPTIVE ICONS
+}
+
+/// Identifiers for the supported material design icons.
+///
+/// Use with the [Icon] class to show specific icons.
+///
+/// Icons are identified by their name as listed below.
+///
+/// To use this class, make sure you set `uses-material-design: true` in your
+/// project's `pubspec.yaml` file in the `flutter` section. This ensures that
+/// the MaterialIcons font is included in your application. This font is used to
+/// display the icons. For example:
+///
+/// ```yaml
+/// name: my_awesome_application
+/// flutter:
+///   uses-material-design: true
+/// ```
+///
+/// {@tool snippet}
+/// This example shows how to create a [Row] of [Icon]s in different colors and
+/// sizes. The first [Icon] uses a [Icon.semanticLabel] to announce in accessibility
+/// modes like TalkBack and VoiceOver.
+///
+/// ![A row of icons representing a pink heart, a green musical note, and a blue umbrella](https://flutter.github.io/assets-for-api-docs/assets/widgets/icon.png)
+///
+/// ```dart
+/// Row(
+///   mainAxisAlignment: MainAxisAlignment.spaceAround,
+///   children: const <Widget>[
+///     Icon(
+///       Icons.favorite,
+///       color: Colors.pink,
+///       size: 24.0,
+///       semanticLabel: 'Text to announce in accessibility modes',
+///     ),
+///     Icon(
+///       Icons.audiotrack,
+///       color: Colors.green,
+///       size: 30.0,
+///     ),
+///     Icon(
+///       Icons.beach_access,
+///       color: Colors.blue,
+///       size: 36.0,
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Icon]
+///  * [IconButton]
+///  * <https://material.io/resources/icons>
+class Icons {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  Icons._();
+
+  /// A set of platform-adaptive material design icons.
+  ///
+  /// Provides a convenient way to show a certain set of platform-appropriate
+  /// icons on Apple platforms.
+  ///
+  /// Use with the [Icon] class to show specific icons.
+  ///
+  /// {@tool snippet}
+  /// This example shows how to create a share icon that uses the material icon
+  /// named "share" on non-Apple platforms, and the icon named "ios share" on
+  /// Apple platforms.
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.adaptive.share,
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [Icon]
+  ///  * [IconButton]
+  ///  * <https://design.google.com/icons/>
+  static _PlatformAdaptiveIcons get adaptive => _PlatformAdaptiveIcons();
+
+  // Generated code: do not hand-edit.
+  // See https://github.com/flutter/flutter/wiki/Updating-Material-Design-Fonts-&-Icons
+  // BEGIN GENERATED ICONS
+
+  /// <i class="material-icons md-36">10k</i> &#x2014; material icon named "10k".
+  static const IconData ten_k = IconData(0xe52a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">10mp</i> &#x2014; material icon named "10mp".
+  static const IconData ten_mp = IconData(0xe52b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">11mp</i> &#x2014; material icon named "11mp".
+  static const IconData eleven_mp = IconData(0xe52c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">12mp</i> &#x2014; material icon named "12mp".
+  static const IconData twelve_mp = IconData(0xe52d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">13mp</i> &#x2014; material icon named "13mp".
+  static const IconData thirteen_mp = IconData(0xe52e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">14mp</i> &#x2014; material icon named "14mp".
+  static const IconData fourteen_mp = IconData(0xe52f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">15mp</i> &#x2014; material icon named "15mp".
+  static const IconData fifteen_mp = IconData(0xe530, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">16mp</i> &#x2014; material icon named "16mp".
+  static const IconData sixteen_mp = IconData(0xe531, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">17mp</i> &#x2014; material icon named "17mp".
+  static const IconData seventeen_mp = IconData(0xe532, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">18mp</i> &#x2014; material icon named "18mp".
+  static const IconData eighteen_mp = IconData(0xe533, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">19mp</i> &#x2014; material icon named "19mp".
+  static const IconData nineteen_mp = IconData(0xe534, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">1k</i> &#x2014; material icon named "1k".
+  static const IconData one_k = IconData(0xe535, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">1k_plus</i> &#x2014; material icon named "1k plus".
+  static const IconData one_k_plus = IconData(0xe536, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">20mp</i> &#x2014; material icon named "20mp".
+  static const IconData twenty_mp = IconData(0xe537, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">21mp</i> &#x2014; material icon named "21mp".
+  static const IconData twenty_one_mp = IconData(0xe538, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">22mp</i> &#x2014; material icon named "22mp".
+  static const IconData twenty_two_mp = IconData(0xe539, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">23mp</i> &#x2014; material icon named "23mp".
+  static const IconData twenty_three_mp = IconData(0xe53a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">24mp</i> &#x2014; material icon named "24mp".
+  static const IconData twenty_four_mp = IconData(0xe53b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">2k</i> &#x2014; material icon named "2k".
+  static const IconData two_k = IconData(0xe53c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">2k_plus</i> &#x2014; material icon named "2k plus".
+  static const IconData two_k_plus = IconData(0xe53d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">2mp</i> &#x2014; material icon named "2mp".
+  static const IconData two_mp = IconData(0xe53e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">360</i> &#x2014; material icon named "360".
+  static const IconData threesixty = IconData(0xe53f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">360</i> &#x2014; material icon named "360 outlined".
+  static const IconData threesixty_outlined = IconData(0xe000, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">360</i> &#x2014; material icon named "360 rounded".
+  static const IconData threesixty_rounded = IconData(0xf02e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">360</i> &#x2014; material icon named "360 sharp".
+  static const IconData threesixty_sharp = IconData(0xeb00, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">3d_rotation</i> &#x2014; material icon named "3d rotation".
+  static const IconData threed_rotation = IconData(0xe540, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">3d_rotation</i> &#x2014; material icon named "3d rotation outlined".
+  static const IconData threed_rotation_outlined = IconData(0xe001, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">3d_rotation</i> &#x2014; material icon named "3d rotation rounded".
+  static const IconData threed_rotation_rounded = IconData(0xf02f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">3d_rotation</i> &#x2014; material icon named "3d rotation sharp".
+  static const IconData threed_rotation_sharp = IconData(0xeb01, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">3k</i> &#x2014; material icon named "3k".
+  static const IconData three_k = IconData(0xe541, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">3k_plus</i> &#x2014; material icon named "3k plus".
+  static const IconData three_k_plus = IconData(0xe542, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">3mp</i> &#x2014; material icon named "3mp".
+  static const IconData three_mp = IconData(0xe543, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">4k</i> &#x2014; material icon named "4k".
+  static const IconData four_k = IconData(0xe544, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">4k</i> &#x2014; material icon named "4k outlined".
+  static const IconData four_k_outlined = IconData(0xe002, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">4k_plus</i> &#x2014; material icon named "4k plus".
+  static const IconData four_k_plus = IconData(0xe545, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">4k</i> &#x2014; material icon named "4k rounded".
+  static const IconData four_k_rounded = IconData(0xf030, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">4k</i> &#x2014; material icon named "4k sharp".
+  static const IconData four_k_sharp = IconData(0xeb02, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">4mp</i> &#x2014; material icon named "4mp".
+  static const IconData four_mp = IconData(0xe546, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">5g</i> &#x2014; material icon named "5g".
+  static const IconData five_g = IconData(0xe547, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">5g</i> &#x2014; material icon named "5g outlined".
+  static const IconData five_g_outlined = IconData(0xe003, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">5g</i> &#x2014; material icon named "5g rounded".
+  static const IconData five_g_rounded = IconData(0xf031, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">5g</i> &#x2014; material icon named "5g sharp".
+  static const IconData five_g_sharp = IconData(0xeb03, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">5k</i> &#x2014; material icon named "5k".
+  static const IconData five_k = IconData(0xe548, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">5k_plus</i> &#x2014; material icon named "5k plus".
+  static const IconData five_k_plus = IconData(0xe549, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">5mp</i> &#x2014; material icon named "5mp".
+  static const IconData five_mp = IconData(0xe54a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">6_ft_apart</i> &#x2014; material icon named "6 ft apart".
+  static const IconData six_ft_apart = IconData(0xe54b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">6_ft_apart</i> &#x2014; material icon named "6 ft apart outlined".
+  static const IconData six_ft_apart_outlined = IconData(0xe004, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">6_ft_apart</i> &#x2014; material icon named "6 ft apart rounded".
+  static const IconData six_ft_apart_rounded = IconData(0xf032, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">6_ft_apart</i> &#x2014; material icon named "6 ft apart sharp".
+  static const IconData six_ft_apart_sharp = IconData(0xeb04, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">6k</i> &#x2014; material icon named "6k".
+  static const IconData six_k = IconData(0xe54c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">6k_plus</i> &#x2014; material icon named "6k plus".
+  static const IconData six_k_plus = IconData(0xe54d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">6mp</i> &#x2014; material icon named "6mp".
+  static const IconData six_mp = IconData(0xe54e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">7k</i> &#x2014; material icon named "7k".
+  static const IconData seven_k = IconData(0xe54f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">7k_plus</i> &#x2014; material icon named "7k plus".
+  static const IconData seven_k_plus = IconData(0xe550, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">7mp</i> &#x2014; material icon named "7mp".
+  static const IconData seven_mp = IconData(0xe551, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">8k</i> &#x2014; material icon named "8k".
+  static const IconData eight_k = IconData(0xe552, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">8k_plus</i> &#x2014; material icon named "8k plus".
+  static const IconData eight_k_plus = IconData(0xe553, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">8mp</i> &#x2014; material icon named "8mp".
+  static const IconData eight_mp = IconData(0xe554, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">9k</i> &#x2014; material icon named "9k".
+  static const IconData nine_k = IconData(0xe555, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">9k_plus</i> &#x2014; material icon named "9k plus".
+  static const IconData nine_k_plus = IconData(0xe556, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">9mp</i> &#x2014; material icon named "9mp".
+  static const IconData nine_mp = IconData(0xe557, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">ac_unit</i> &#x2014; material icon named "ac unit".
+  static const IconData ac_unit = IconData(0xe558, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">ac_unit</i> &#x2014; material icon named "ac unit outlined".
+  static const IconData ac_unit_outlined = IconData(0xe005, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">ac_unit</i> &#x2014; material icon named "ac unit rounded".
+  static const IconData ac_unit_rounded = IconData(0xf033, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">ac_unit</i> &#x2014; material icon named "ac unit sharp".
+  static const IconData ac_unit_sharp = IconData(0xeb05, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">access_alarm</i> &#x2014; material icon named "access alarm".
+  static const IconData access_alarm = IconData(0xe559, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">access_alarm</i> &#x2014; material icon named "access alarm outlined".
+  static const IconData access_alarm_outlined = IconData(0xe006, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">access_alarm</i> &#x2014; material icon named "access alarm rounded".
+  static const IconData access_alarm_rounded = IconData(0xf034, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">access_alarm</i> &#x2014; material icon named "access alarm sharp".
+  static const IconData access_alarm_sharp = IconData(0xeb06, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">access_alarms</i> &#x2014; material icon named "access alarms".
+  static const IconData access_alarms = IconData(0xe55a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">access_alarms</i> &#x2014; material icon named "access alarms outlined".
+  static const IconData access_alarms_outlined = IconData(0xe007, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">access_alarms</i> &#x2014; material icon named "access alarms rounded".
+  static const IconData access_alarms_rounded = IconData(0xf035, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">access_alarms</i> &#x2014; material icon named "access alarms sharp".
+  static const IconData access_alarms_sharp = IconData(0xeb07, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">access_time</i> &#x2014; material icon named "access time".
+  static const IconData access_time = IconData(0xe55b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">access_time</i> &#x2014; material icon named "access time outlined".
+  static const IconData access_time_outlined = IconData(0xe008, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">access_time</i> &#x2014; material icon named "access time rounded".
+  static const IconData access_time_rounded = IconData(0xf036, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">access_time</i> &#x2014; material icon named "access time sharp".
+  static const IconData access_time_sharp = IconData(0xeb08, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">accessibility</i> &#x2014; material icon named "accessibility".
+  static const IconData accessibility = IconData(0xe55c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">accessibility_new</i> &#x2014; material icon named "accessibility new".
+  static const IconData accessibility_new = IconData(0xe55d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">accessibility_new</i> &#x2014; material icon named "accessibility new outlined".
+  static const IconData accessibility_new_outlined = IconData(0xe009, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">accessibility_new</i> &#x2014; material icon named "accessibility new rounded".
+  static const IconData accessibility_new_rounded = IconData(0xf037, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">accessibility_new</i> &#x2014; material icon named "accessibility new sharp".
+  static const IconData accessibility_new_sharp = IconData(0xeb09, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">accessibility</i> &#x2014; material icon named "accessibility outlined".
+  static const IconData accessibility_outlined = IconData(0xe00a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">accessibility</i> &#x2014; material icon named "accessibility rounded".
+  static const IconData accessibility_rounded = IconData(0xf038, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">accessibility</i> &#x2014; material icon named "accessibility sharp".
+  static const IconData accessibility_sharp = IconData(0xeb0a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">accessible</i> &#x2014; material icon named "accessible".
+  static const IconData accessible = IconData(0xe55e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">accessible_forward</i> &#x2014; material icon named "accessible forward".
+  static const IconData accessible_forward = IconData(0xe55f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">accessible_forward</i> &#x2014; material icon named "accessible forward outlined".
+  static const IconData accessible_forward_outlined = IconData(0xe00b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">accessible_forward</i> &#x2014; material icon named "accessible forward rounded".
+  static const IconData accessible_forward_rounded = IconData(0xf039, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">accessible_forward</i> &#x2014; material icon named "accessible forward sharp".
+  static const IconData accessible_forward_sharp = IconData(0xeb0b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">accessible</i> &#x2014; material icon named "accessible outlined".
+  static const IconData accessible_outlined = IconData(0xe00c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">accessible</i> &#x2014; material icon named "accessible rounded".
+  static const IconData accessible_rounded = IconData(0xf03a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">accessible</i> &#x2014; material icon named "accessible sharp".
+  static const IconData accessible_sharp = IconData(0xeb0c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">account_balance</i> &#x2014; material icon named "account balance".
+  static const IconData account_balance = IconData(0xe560, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">account_balance</i> &#x2014; material icon named "account balance outlined".
+  static const IconData account_balance_outlined = IconData(0xe00d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">account_balance</i> &#x2014; material icon named "account balance rounded".
+  static const IconData account_balance_rounded = IconData(0xf03b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">account_balance</i> &#x2014; material icon named "account balance sharp".
+  static const IconData account_balance_sharp = IconData(0xeb0d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">account_balance_wallet</i> &#x2014; material icon named "account balance wallet".
+  static const IconData account_balance_wallet = IconData(0xe561, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">account_balance_wallet</i> &#x2014; material icon named "account balance wallet outlined".
+  static const IconData account_balance_wallet_outlined = IconData(0xe00e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">account_balance_wallet</i> &#x2014; material icon named "account balance wallet rounded".
+  static const IconData account_balance_wallet_rounded = IconData(0xf03c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">account_balance_wallet</i> &#x2014; material icon named "account balance wallet sharp".
+  static const IconData account_balance_wallet_sharp = IconData(0xeb0e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">account_box</i> &#x2014; material icon named "account box".
+  static const IconData account_box = IconData(0xe562, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">account_box</i> &#x2014; material icon named "account box outlined".
+  static const IconData account_box_outlined = IconData(0xe00f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">account_box</i> &#x2014; material icon named "account box rounded".
+  static const IconData account_box_rounded = IconData(0xf03d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">account_box</i> &#x2014; material icon named "account box sharp".
+  static const IconData account_box_sharp = IconData(0xeb0f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">account_circle</i> &#x2014; material icon named "account circle".
+  static const IconData account_circle = IconData(0xe563, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">account_circle</i> &#x2014; material icon named "account circle outlined".
+  static const IconData account_circle_outlined = IconData(0xe010, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">account_circle</i> &#x2014; material icon named "account circle rounded".
+  static const IconData account_circle_rounded = IconData(0xf03e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">account_circle</i> &#x2014; material icon named "account circle sharp".
+  static const IconData account_circle_sharp = IconData(0xeb10, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">account_tree</i> &#x2014; material icon named "account tree".
+  static const IconData account_tree = IconData(0xe564, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">account_tree</i> &#x2014; material icon named "account tree outlined".
+  static const IconData account_tree_outlined = IconData(0xe011, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">account_tree</i> &#x2014; material icon named "account tree rounded".
+  static const IconData account_tree_rounded = IconData(0xf03f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">account_tree</i> &#x2014; material icon named "account tree sharp".
+  static const IconData account_tree_sharp = IconData(0xeb11, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">ad_units</i> &#x2014; material icon named "ad units".
+  static const IconData ad_units = IconData(0xe565, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">ad_units</i> &#x2014; material icon named "ad units outlined".
+  static const IconData ad_units_outlined = IconData(0xe012, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">ad_units</i> &#x2014; material icon named "ad units rounded".
+  static const IconData ad_units_rounded = IconData(0xf040, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">ad_units</i> &#x2014; material icon named "ad units sharp".
+  static const IconData ad_units_sharp = IconData(0xeb12, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">adb</i> &#x2014; material icon named "adb".
+  static const IconData adb = IconData(0xe566, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">adb</i> &#x2014; material icon named "adb outlined".
+  static const IconData adb_outlined = IconData(0xe013, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">adb</i> &#x2014; material icon named "adb rounded".
+  static const IconData adb_rounded = IconData(0xf041, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">adb</i> &#x2014; material icon named "adb sharp".
+  static const IconData adb_sharp = IconData(0xeb13, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add</i> &#x2014; material icon named "add".
+  static const IconData add = IconData(0xe567, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_a_photo</i> &#x2014; material icon named "add a photo".
+  static const IconData add_a_photo = IconData(0xe568, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_a_photo</i> &#x2014; material icon named "add a photo outlined".
+  static const IconData add_a_photo_outlined = IconData(0xe014, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_a_photo</i> &#x2014; material icon named "add a photo rounded".
+  static const IconData add_a_photo_rounded = IconData(0xf042, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_a_photo</i> &#x2014; material icon named "add a photo sharp".
+  static const IconData add_a_photo_sharp = IconData(0xeb14, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_alarm</i> &#x2014; material icon named "add alarm".
+  static const IconData add_alarm = IconData(0xe569, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_alarm</i> &#x2014; material icon named "add alarm outlined".
+  static const IconData add_alarm_outlined = IconData(0xe015, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_alarm</i> &#x2014; material icon named "add alarm rounded".
+  static const IconData add_alarm_rounded = IconData(0xf043, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_alarm</i> &#x2014; material icon named "add alarm sharp".
+  static const IconData add_alarm_sharp = IconData(0xeb15, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_alert</i> &#x2014; material icon named "add alert".
+  static const IconData add_alert = IconData(0xe56a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_alert</i> &#x2014; material icon named "add alert outlined".
+  static const IconData add_alert_outlined = IconData(0xe016, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_alert</i> &#x2014; material icon named "add alert rounded".
+  static const IconData add_alert_rounded = IconData(0xf044, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_alert</i> &#x2014; material icon named "add alert sharp".
+  static const IconData add_alert_sharp = IconData(0xeb16, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_box</i> &#x2014; material icon named "add box".
+  static const IconData add_box = IconData(0xe56b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_box</i> &#x2014; material icon named "add box outlined".
+  static const IconData add_box_outlined = IconData(0xe017, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_box</i> &#x2014; material icon named "add box rounded".
+  static const IconData add_box_rounded = IconData(0xf045, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_box</i> &#x2014; material icon named "add box sharp".
+  static const IconData add_box_sharp = IconData(0xeb17, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_business</i> &#x2014; material icon named "add business".
+  static const IconData add_business = IconData(0xe56c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_business</i> &#x2014; material icon named "add business outlined".
+  static const IconData add_business_outlined = IconData(0xe018, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_business</i> &#x2014; material icon named "add business rounded".
+  static const IconData add_business_rounded = IconData(0xf046, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_business</i> &#x2014; material icon named "add business sharp".
+  static const IconData add_business_sharp = IconData(0xeb18, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_call</i> &#x2014; material icon named "add call".
+  static const IconData add_call = IconData(0xe56d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_chart</i> &#x2014; material icon named "add chart".
+  static const IconData add_chart = IconData(0xe56e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_circle</i> &#x2014; material icon named "add circle".
+  static const IconData add_circle = IconData(0xe56f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_circle_outline</i> &#x2014; material icon named "add circle outline".
+  static const IconData add_circle_outline = IconData(0xe570, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_circle_outline</i> &#x2014; material icon named "add circle outline outlined".
+  static const IconData add_circle_outline_outlined = IconData(0xe019, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_circle_outline</i> &#x2014; material icon named "add circle outline rounded".
+  static const IconData add_circle_outline_rounded = IconData(0xf047, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_circle_outline</i> &#x2014; material icon named "add circle outline sharp".
+  static const IconData add_circle_outline_sharp = IconData(0xeb19, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_circle</i> &#x2014; material icon named "add circle outlined".
+  static const IconData add_circle_outlined = IconData(0xe01a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_circle</i> &#x2014; material icon named "add circle rounded".
+  static const IconData add_circle_rounded = IconData(0xf048, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_circle</i> &#x2014; material icon named "add circle sharp".
+  static const IconData add_circle_sharp = IconData(0xeb1a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_comment</i> &#x2014; material icon named "add comment".
+  static const IconData add_comment = IconData(0xe571, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_comment</i> &#x2014; material icon named "add comment outlined".
+  static const IconData add_comment_outlined = IconData(0xe01b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_comment</i> &#x2014; material icon named "add comment rounded".
+  static const IconData add_comment_rounded = IconData(0xf049, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_comment</i> &#x2014; material icon named "add comment sharp".
+  static const IconData add_comment_sharp = IconData(0xeb1b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_ic_call</i> &#x2014; material icon named "add ic call".
+  static const IconData add_ic_call = IconData(0xe572, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_ic_call</i> &#x2014; material icon named "add ic call outlined".
+  static const IconData add_ic_call_outlined = IconData(0xe01c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_ic_call</i> &#x2014; material icon named "add ic call rounded".
+  static const IconData add_ic_call_rounded = IconData(0xf04a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_ic_call</i> &#x2014; material icon named "add ic call sharp".
+  static const IconData add_ic_call_sharp = IconData(0xeb1c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_link</i> &#x2014; material icon named "add link".
+  static const IconData add_link = IconData(0xe573, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_location</i> &#x2014; material icon named "add location".
+  static const IconData add_location = IconData(0xe574, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_location_alt</i> &#x2014; material icon named "add location alt".
+  static const IconData add_location_alt = IconData(0xe575, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_location_alt</i> &#x2014; material icon named "add location alt outlined".
+  static const IconData add_location_alt_outlined = IconData(0xe01d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_location_alt</i> &#x2014; material icon named "add location alt rounded".
+  static const IconData add_location_alt_rounded = IconData(0xf04b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_location_alt</i> &#x2014; material icon named "add location alt sharp".
+  static const IconData add_location_alt_sharp = IconData(0xeb1d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_location</i> &#x2014; material icon named "add location outlined".
+  static const IconData add_location_outlined = IconData(0xe01e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_location</i> &#x2014; material icon named "add location rounded".
+  static const IconData add_location_rounded = IconData(0xf04c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_location</i> &#x2014; material icon named "add location sharp".
+  static const IconData add_location_sharp = IconData(0xeb1e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_moderator</i> &#x2014; material icon named "add moderator".
+  static const IconData add_moderator = IconData(0xe576, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add</i> &#x2014; material icon named "add outlined".
+  static const IconData add_outlined = IconData(0xe01f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_photo_alternate</i> &#x2014; material icon named "add photo alternate".
+  static const IconData add_photo_alternate = IconData(0xe577, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_photo_alternate</i> &#x2014; material icon named "add photo alternate outlined".
+  static const IconData add_photo_alternate_outlined = IconData(0xe020, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_photo_alternate</i> &#x2014; material icon named "add photo alternate rounded".
+  static const IconData add_photo_alternate_rounded = IconData(0xf04d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_photo_alternate</i> &#x2014; material icon named "add photo alternate sharp".
+  static const IconData add_photo_alternate_sharp = IconData(0xeb1f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_road</i> &#x2014; material icon named "add road".
+  static const IconData add_road = IconData(0xe578, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_road</i> &#x2014; material icon named "add road outlined".
+  static const IconData add_road_outlined = IconData(0xe021, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_road</i> &#x2014; material icon named "add road rounded".
+  static const IconData add_road_rounded = IconData(0xf04e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_road</i> &#x2014; material icon named "add road sharp".
+  static const IconData add_road_sharp = IconData(0xeb20, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add</i> &#x2014; material icon named "add rounded".
+  static const IconData add_rounded = IconData(0xf04f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add</i> &#x2014; material icon named "add sharp".
+  static const IconData add_sharp = IconData(0xeb21, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_shopping_cart</i> &#x2014; material icon named "add shopping cart".
+  static const IconData add_shopping_cart = IconData(0xe579, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_shopping_cart</i> &#x2014; material icon named "add shopping cart outlined".
+  static const IconData add_shopping_cart_outlined = IconData(0xe022, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_shopping_cart</i> &#x2014; material icon named "add shopping cart rounded".
+  static const IconData add_shopping_cart_rounded = IconData(0xf050, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_shopping_cart</i> &#x2014; material icon named "add shopping cart sharp".
+  static const IconData add_shopping_cart_sharp = IconData(0xeb22, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_to_drive</i> &#x2014; material icon named "add to drive".
+  static const IconData add_to_drive = IconData(0xe57a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_to_home_screen</i> &#x2014; material icon named "add to home screen".
+  static const IconData add_to_home_screen = IconData(0xe57b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_to_home_screen</i> &#x2014; material icon named "add to home screen outlined".
+  static const IconData add_to_home_screen_outlined = IconData(0xe023, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_to_home_screen</i> &#x2014; material icon named "add to home screen rounded".
+  static const IconData add_to_home_screen_rounded = IconData(0xf051, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_to_home_screen</i> &#x2014; material icon named "add to home screen sharp".
+  static const IconData add_to_home_screen_sharp = IconData(0xeb23, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_to_photos</i> &#x2014; material icon named "add to photos".
+  static const IconData add_to_photos = IconData(0xe57c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_to_photos</i> &#x2014; material icon named "add to photos outlined".
+  static const IconData add_to_photos_outlined = IconData(0xe024, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_to_photos</i> &#x2014; material icon named "add to photos rounded".
+  static const IconData add_to_photos_rounded = IconData(0xf052, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_to_photos</i> &#x2014; material icon named "add to photos sharp".
+  static const IconData add_to_photos_sharp = IconData(0xeb24, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">add_to_queue</i> &#x2014; material icon named "add to queue".
+  static const IconData add_to_queue = IconData(0xe57d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">add_to_queue</i> &#x2014; material icon named "add to queue outlined".
+  static const IconData add_to_queue_outlined = IconData(0xe025, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">add_to_queue</i> &#x2014; material icon named "add to queue rounded".
+  static const IconData add_to_queue_rounded = IconData(0xf053, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">add_to_queue</i> &#x2014; material icon named "add to queue sharp".
+  static const IconData add_to_queue_sharp = IconData(0xeb25, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">addchart</i> &#x2014; material icon named "addchart".
+  static const IconData addchart = IconData(0xe57e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">addchart</i> &#x2014; material icon named "addchart outlined".
+  static const IconData addchart_outlined = IconData(0xe026, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">addchart</i> &#x2014; material icon named "addchart rounded".
+  static const IconData addchart_rounded = IconData(0xf054, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">addchart</i> &#x2014; material icon named "addchart sharp".
+  static const IconData addchart_sharp = IconData(0xeb26, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">adjust</i> &#x2014; material icon named "adjust".
+  static const IconData adjust = IconData(0xe57f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">adjust</i> &#x2014; material icon named "adjust outlined".
+  static const IconData adjust_outlined = IconData(0xe027, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">adjust</i> &#x2014; material icon named "adjust rounded".
+  static const IconData adjust_rounded = IconData(0xf055, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">adjust</i> &#x2014; material icon named "adjust sharp".
+  static const IconData adjust_sharp = IconData(0xeb27, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">admin_panel_settings</i> &#x2014; material icon named "admin panel settings".
+  static const IconData admin_panel_settings = IconData(0xe580, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">admin_panel_settings</i> &#x2014; material icon named "admin panel settings outlined".
+  static const IconData admin_panel_settings_outlined = IconData(0xe028, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">admin_panel_settings</i> &#x2014; material icon named "admin panel settings rounded".
+  static const IconData admin_panel_settings_rounded = IconData(0xf056, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">admin_panel_settings</i> &#x2014; material icon named "admin panel settings sharp".
+  static const IconData admin_panel_settings_sharp = IconData(0xeb28, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">agriculture</i> &#x2014; material icon named "agriculture".
+  static const IconData agriculture = IconData(0xe581, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">agriculture</i> &#x2014; material icon named "agriculture outlined".
+  static const IconData agriculture_outlined = IconData(0xe029, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">agriculture</i> &#x2014; material icon named "agriculture rounded".
+  static const IconData agriculture_rounded = IconData(0xf057, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">agriculture</i> &#x2014; material icon named "agriculture sharp".
+  static const IconData agriculture_sharp = IconData(0xeb29, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">airline_seat_flat</i> &#x2014; material icon named "airline seat flat".
+  static const IconData airline_seat_flat = IconData(0xe582, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">airline_seat_flat_angled</i> &#x2014; material icon named "airline seat flat angled".
+  static const IconData airline_seat_flat_angled = IconData(0xe583, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">airline_seat_flat_angled</i> &#x2014; material icon named "airline seat flat angled outlined".
+  static const IconData airline_seat_flat_angled_outlined = IconData(0xe02a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">airline_seat_flat_angled</i> &#x2014; material icon named "airline seat flat angled rounded".
+  static const IconData airline_seat_flat_angled_rounded = IconData(0xf058, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">airline_seat_flat_angled</i> &#x2014; material icon named "airline seat flat angled sharp".
+  static const IconData airline_seat_flat_angled_sharp = IconData(0xeb2a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">airline_seat_flat</i> &#x2014; material icon named "airline seat flat outlined".
+  static const IconData airline_seat_flat_outlined = IconData(0xe02b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">airline_seat_flat</i> &#x2014; material icon named "airline seat flat rounded".
+  static const IconData airline_seat_flat_rounded = IconData(0xf059, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">airline_seat_flat</i> &#x2014; material icon named "airline seat flat sharp".
+  static const IconData airline_seat_flat_sharp = IconData(0xeb2b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">airline_seat_individual_suite</i> &#x2014; material icon named "airline seat individual suite".
+  static const IconData airline_seat_individual_suite = IconData(0xe584, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">airline_seat_individual_suite</i> &#x2014; material icon named "airline seat individual suite outlined".
+  static const IconData airline_seat_individual_suite_outlined = IconData(0xe02c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">airline_seat_individual_suite</i> &#x2014; material icon named "airline seat individual suite rounded".
+  static const IconData airline_seat_individual_suite_rounded = IconData(0xf05a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">airline_seat_individual_suite</i> &#x2014; material icon named "airline seat individual suite sharp".
+  static const IconData airline_seat_individual_suite_sharp = IconData(0xeb2c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">airline_seat_legroom_extra</i> &#x2014; material icon named "airline seat legroom extra".
+  static const IconData airline_seat_legroom_extra = IconData(0xe585, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">airline_seat_legroom_extra</i> &#x2014; material icon named "airline seat legroom extra outlined".
+  static const IconData airline_seat_legroom_extra_outlined = IconData(0xe02d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">airline_seat_legroom_extra</i> &#x2014; material icon named "airline seat legroom extra rounded".
+  static const IconData airline_seat_legroom_extra_rounded = IconData(0xf05b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">airline_seat_legroom_extra</i> &#x2014; material icon named "airline seat legroom extra sharp".
+  static const IconData airline_seat_legroom_extra_sharp = IconData(0xeb2d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">airline_seat_legroom_normal</i> &#x2014; material icon named "airline seat legroom normal".
+  static const IconData airline_seat_legroom_normal = IconData(0xe586, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">airline_seat_legroom_normal</i> &#x2014; material icon named "airline seat legroom normal outlined".
+  static const IconData airline_seat_legroom_normal_outlined = IconData(0xe02e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">airline_seat_legroom_normal</i> &#x2014; material icon named "airline seat legroom normal rounded".
+  static const IconData airline_seat_legroom_normal_rounded = IconData(0xf05c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">airline_seat_legroom_normal</i> &#x2014; material icon named "airline seat legroom normal sharp".
+  static const IconData airline_seat_legroom_normal_sharp = IconData(0xeb2e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">airline_seat_legroom_reduced</i> &#x2014; material icon named "airline seat legroom reduced".
+  static const IconData airline_seat_legroom_reduced = IconData(0xe587, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">airline_seat_legroom_reduced</i> &#x2014; material icon named "airline seat legroom reduced outlined".
+  static const IconData airline_seat_legroom_reduced_outlined = IconData(0xe02f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">airline_seat_legroom_reduced</i> &#x2014; material icon named "airline seat legroom reduced rounded".
+  static const IconData airline_seat_legroom_reduced_rounded = IconData(0xf05d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">airline_seat_legroom_reduced</i> &#x2014; material icon named "airline seat legroom reduced sharp".
+  static const IconData airline_seat_legroom_reduced_sharp = IconData(0xeb2f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">airline_seat_recline_extra</i> &#x2014; material icon named "airline seat recline extra".
+  static const IconData airline_seat_recline_extra = IconData(0xe588, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">airline_seat_recline_extra</i> &#x2014; material icon named "airline seat recline extra outlined".
+  static const IconData airline_seat_recline_extra_outlined = IconData(0xe030, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">airline_seat_recline_extra</i> &#x2014; material icon named "airline seat recline extra rounded".
+  static const IconData airline_seat_recline_extra_rounded = IconData(0xf05e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">airline_seat_recline_extra</i> &#x2014; material icon named "airline seat recline extra sharp".
+  static const IconData airline_seat_recline_extra_sharp = IconData(0xeb30, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">airline_seat_recline_normal</i> &#x2014; material icon named "airline seat recline normal".
+  static const IconData airline_seat_recline_normal = IconData(0xe589, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">airline_seat_recline_normal</i> &#x2014; material icon named "airline seat recline normal outlined".
+  static const IconData airline_seat_recline_normal_outlined = IconData(0xe031, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">airline_seat_recline_normal</i> &#x2014; material icon named "airline seat recline normal rounded".
+  static const IconData airline_seat_recline_normal_rounded = IconData(0xf05f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">airline_seat_recline_normal</i> &#x2014; material icon named "airline seat recline normal sharp".
+  static const IconData airline_seat_recline_normal_sharp = IconData(0xeb31, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">airplanemode_active</i> &#x2014; material icon named "airplanemode active".
+  static const IconData airplanemode_active = IconData(0xe58a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">airplanemode_active</i> &#x2014; material icon named "airplanemode active outlined".
+  static const IconData airplanemode_active_outlined = IconData(0xe032, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">airplanemode_active</i> &#x2014; material icon named "airplanemode active rounded".
+  static const IconData airplanemode_active_rounded = IconData(0xf060, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">airplanemode_active</i> &#x2014; material icon named "airplanemode active sharp".
+  static const IconData airplanemode_active_sharp = IconData(0xeb32, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">airplanemode_inactive</i> &#x2014; material icon named "airplanemode inactive".
+  static const IconData airplanemode_inactive = IconData(0xe58b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">airplanemode_inactive</i> &#x2014; material icon named "airplanemode inactive outlined".
+  static const IconData airplanemode_inactive_outlined = IconData(0xe033, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">airplanemode_inactive</i> &#x2014; material icon named "airplanemode inactive rounded".
+  static const IconData airplanemode_inactive_rounded = IconData(0xf061, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">airplanemode_inactive</i> &#x2014; material icon named "airplanemode inactive sharp".
+  static const IconData airplanemode_inactive_sharp = IconData(0xeb33, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">airplanemode_off</i> &#x2014; material icon named "airplanemode off".
+  static const IconData airplanemode_off = IconData(0xe58b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">airplanemode_off</i> &#x2014; material icon named "airplanemode off outlined".
+  static const IconData airplanemode_off_outlined = IconData(0xe033, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">airplanemode_off</i> &#x2014; material icon named "airplanemode off rounded".
+  static const IconData airplanemode_off_rounded = IconData(0xf061, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">airplanemode_off</i> &#x2014; material icon named "airplanemode off sharp".
+  static const IconData airplanemode_off_sharp = IconData(0xeb33, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">airplanemode_on</i> &#x2014; material icon named "airplanemode on".
+  static const IconData airplanemode_on = IconData(0xe58a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">airplanemode_on</i> &#x2014; material icon named "airplanemode on outlined".
+  static const IconData airplanemode_on_outlined = IconData(0xe032, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">airplanemode_on</i> &#x2014; material icon named "airplanemode on rounded".
+  static const IconData airplanemode_on_rounded = IconData(0xf060, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">airplanemode_on</i> &#x2014; material icon named "airplanemode on sharp".
+  static const IconData airplanemode_on_sharp = IconData(0xeb32, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">airplay</i> &#x2014; material icon named "airplay".
+  static const IconData airplay = IconData(0xe58c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">airplay</i> &#x2014; material icon named "airplay outlined".
+  static const IconData airplay_outlined = IconData(0xe034, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">airplay</i> &#x2014; material icon named "airplay rounded".
+  static const IconData airplay_rounded = IconData(0xf062, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">airplay</i> &#x2014; material icon named "airplay sharp".
+  static const IconData airplay_sharp = IconData(0xeb34, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">airport_shuttle</i> &#x2014; material icon named "airport shuttle".
+  static const IconData airport_shuttle = IconData(0xe58d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">airport_shuttle</i> &#x2014; material icon named "airport shuttle outlined".
+  static const IconData airport_shuttle_outlined = IconData(0xe035, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">airport_shuttle</i> &#x2014; material icon named "airport shuttle rounded".
+  static const IconData airport_shuttle_rounded = IconData(0xf063, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">airport_shuttle</i> &#x2014; material icon named "airport shuttle sharp".
+  static const IconData airport_shuttle_sharp = IconData(0xeb35, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">alarm</i> &#x2014; material icon named "alarm".
+  static const IconData alarm = IconData(0xe58e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">alarm_add</i> &#x2014; material icon named "alarm add".
+  static const IconData alarm_add = IconData(0xe58f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">alarm_add</i> &#x2014; material icon named "alarm add outlined".
+  static const IconData alarm_add_outlined = IconData(0xe036, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">alarm_add</i> &#x2014; material icon named "alarm add rounded".
+  static const IconData alarm_add_rounded = IconData(0xf064, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">alarm_add</i> &#x2014; material icon named "alarm add sharp".
+  static const IconData alarm_add_sharp = IconData(0xeb36, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">alarm_off</i> &#x2014; material icon named "alarm off".
+  static const IconData alarm_off = IconData(0xe590, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">alarm_off</i> &#x2014; material icon named "alarm off outlined".
+  static const IconData alarm_off_outlined = IconData(0xe037, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">alarm_off</i> &#x2014; material icon named "alarm off rounded".
+  static const IconData alarm_off_rounded = IconData(0xf065, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">alarm_off</i> &#x2014; material icon named "alarm off sharp".
+  static const IconData alarm_off_sharp = IconData(0xeb37, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">alarm_on</i> &#x2014; material icon named "alarm on".
+  static const IconData alarm_on = IconData(0xe591, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">alarm_on</i> &#x2014; material icon named "alarm on outlined".
+  static const IconData alarm_on_outlined = IconData(0xe038, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">alarm_on</i> &#x2014; material icon named "alarm on rounded".
+  static const IconData alarm_on_rounded = IconData(0xf066, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">alarm_on</i> &#x2014; material icon named "alarm on sharp".
+  static const IconData alarm_on_sharp = IconData(0xeb38, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">alarm</i> &#x2014; material icon named "alarm outlined".
+  static const IconData alarm_outlined = IconData(0xe039, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">alarm</i> &#x2014; material icon named "alarm rounded".
+  static const IconData alarm_rounded = IconData(0xf067, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">alarm</i> &#x2014; material icon named "alarm sharp".
+  static const IconData alarm_sharp = IconData(0xeb39, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">album</i> &#x2014; material icon named "album".
+  static const IconData album = IconData(0xe592, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">album</i> &#x2014; material icon named "album outlined".
+  static const IconData album_outlined = IconData(0xe03a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">album</i> &#x2014; material icon named "album rounded".
+  static const IconData album_rounded = IconData(0xf068, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">album</i> &#x2014; material icon named "album sharp".
+  static const IconData album_sharp = IconData(0xeb3a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">all_inbox</i> &#x2014; material icon named "all inbox".
+  static const IconData all_inbox = IconData(0xe593, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">all_inbox</i> &#x2014; material icon named "all inbox outlined".
+  static const IconData all_inbox_outlined = IconData(0xe03b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">all_inbox</i> &#x2014; material icon named "all inbox rounded".
+  static const IconData all_inbox_rounded = IconData(0xf069, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">all_inbox</i> &#x2014; material icon named "all inbox sharp".
+  static const IconData all_inbox_sharp = IconData(0xeb3b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">all_inclusive</i> &#x2014; material icon named "all inclusive".
+  static const IconData all_inclusive = IconData(0xe594, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">all_inclusive</i> &#x2014; material icon named "all inclusive outlined".
+  static const IconData all_inclusive_outlined = IconData(0xe03c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">all_inclusive</i> &#x2014; material icon named "all inclusive rounded".
+  static const IconData all_inclusive_rounded = IconData(0xf06a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">all_inclusive</i> &#x2014; material icon named "all inclusive sharp".
+  static const IconData all_inclusive_sharp = IconData(0xeb3c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">all_out</i> &#x2014; material icon named "all out".
+  static const IconData all_out = IconData(0xe595, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">all_out</i> &#x2014; material icon named "all out outlined".
+  static const IconData all_out_outlined = IconData(0xe03d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">all_out</i> &#x2014; material icon named "all out rounded".
+  static const IconData all_out_rounded = IconData(0xf06b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">all_out</i> &#x2014; material icon named "all out sharp".
+  static const IconData all_out_sharp = IconData(0xeb3d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">alt_route</i> &#x2014; material icon named "alt route".
+  static const IconData alt_route = IconData(0xe596, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">alt_route</i> &#x2014; material icon named "alt route outlined".
+  static const IconData alt_route_outlined = IconData(0xe03e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">alt_route</i> &#x2014; material icon named "alt route rounded".
+  static const IconData alt_route_rounded = IconData(0xf06c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">alt_route</i> &#x2014; material icon named "alt route sharp".
+  static const IconData alt_route_sharp = IconData(0xeb3e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">alternate_email</i> &#x2014; material icon named "alternate email".
+  static const IconData alternate_email = IconData(0xe597, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">alternate_email</i> &#x2014; material icon named "alternate email outlined".
+  static const IconData alternate_email_outlined = IconData(0xe03f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">alternate_email</i> &#x2014; material icon named "alternate email rounded".
+  static const IconData alternate_email_rounded = IconData(0xf06d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">alternate_email</i> &#x2014; material icon named "alternate email sharp".
+  static const IconData alternate_email_sharp = IconData(0xeb3f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">amp_stories</i> &#x2014; material icon named "amp stories".
+  static const IconData amp_stories = IconData(0xe598, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">amp_stories</i> &#x2014; material icon named "amp stories outlined".
+  static const IconData amp_stories_outlined = IconData(0xe040, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">amp_stories</i> &#x2014; material icon named "amp stories rounded".
+  static const IconData amp_stories_rounded = IconData(0xf06e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">amp_stories</i> &#x2014; material icon named "amp stories sharp".
+  static const IconData amp_stories_sharp = IconData(0xeb40, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">analytics</i> &#x2014; material icon named "analytics".
+  static const IconData analytics = IconData(0xe599, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">analytics</i> &#x2014; material icon named "analytics outlined".
+  static const IconData analytics_outlined = IconData(0xe041, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">analytics</i> &#x2014; material icon named "analytics rounded".
+  static const IconData analytics_rounded = IconData(0xf06f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">analytics</i> &#x2014; material icon named "analytics sharp".
+  static const IconData analytics_sharp = IconData(0xeb41, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">anchor</i> &#x2014; material icon named "anchor".
+  static const IconData anchor = IconData(0xe59a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">anchor</i> &#x2014; material icon named "anchor outlined".
+  static const IconData anchor_outlined = IconData(0xe042, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">anchor</i> &#x2014; material icon named "anchor rounded".
+  static const IconData anchor_rounded = IconData(0xf070, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">anchor</i> &#x2014; material icon named "anchor sharp".
+  static const IconData anchor_sharp = IconData(0xeb42, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">android</i> &#x2014; material icon named "android".
+  static const IconData android = IconData(0xe59b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">android</i> &#x2014; material icon named "android outlined".
+  static const IconData android_outlined = IconData(0xe043, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">android</i> &#x2014; material icon named "android rounded".
+  static const IconData android_rounded = IconData(0xf071, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">android</i> &#x2014; material icon named "android sharp".
+  static const IconData android_sharp = IconData(0xeb43, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">animation</i> &#x2014; material icon named "animation".
+  static const IconData animation = IconData(0xe59c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">announcement</i> &#x2014; material icon named "announcement".
+  static const IconData announcement = IconData(0xe59d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">announcement</i> &#x2014; material icon named "announcement outlined".
+  static const IconData announcement_outlined = IconData(0xe044, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">announcement</i> &#x2014; material icon named "announcement rounded".
+  static const IconData announcement_rounded = IconData(0xf072, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">announcement</i> &#x2014; material icon named "announcement sharp".
+  static const IconData announcement_sharp = IconData(0xeb44, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">apartment</i> &#x2014; material icon named "apartment".
+  static const IconData apartment = IconData(0xe59e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">apartment</i> &#x2014; material icon named "apartment outlined".
+  static const IconData apartment_outlined = IconData(0xe045, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">apartment</i> &#x2014; material icon named "apartment rounded".
+  static const IconData apartment_rounded = IconData(0xf073, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">apartment</i> &#x2014; material icon named "apartment sharp".
+  static const IconData apartment_sharp = IconData(0xeb45, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">api</i> &#x2014; material icon named "api".
+  static const IconData api = IconData(0xe59f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">api</i> &#x2014; material icon named "api outlined".
+  static const IconData api_outlined = IconData(0xe046, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">api</i> &#x2014; material icon named "api rounded".
+  static const IconData api_rounded = IconData(0xf074, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">api</i> &#x2014; material icon named "api sharp".
+  static const IconData api_sharp = IconData(0xeb46, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">app_blocking</i> &#x2014; material icon named "app blocking".
+  static const IconData app_blocking = IconData(0xe5a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">app_blocking</i> &#x2014; material icon named "app blocking outlined".
+  static const IconData app_blocking_outlined = IconData(0xe047, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">app_blocking</i> &#x2014; material icon named "app blocking rounded".
+  static const IconData app_blocking_rounded = IconData(0xf075, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">app_blocking</i> &#x2014; material icon named "app blocking sharp".
+  static const IconData app_blocking_sharp = IconData(0xeb47, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">app_registration</i> &#x2014; material icon named "app registration".
+  static const IconData app_registration = IconData(0xe5a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">app_settings_alt</i> &#x2014; material icon named "app settings alt".
+  static const IconData app_settings_alt = IconData(0xe5a2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">app_settings_alt</i> &#x2014; material icon named "app settings alt outlined".
+  static const IconData app_settings_alt_outlined = IconData(0xe048, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">app_settings_alt</i> &#x2014; material icon named "app settings alt rounded".
+  static const IconData app_settings_alt_rounded = IconData(0xf076, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">app_settings_alt</i> &#x2014; material icon named "app settings alt sharp".
+  static const IconData app_settings_alt_sharp = IconData(0xeb48, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">approval</i> &#x2014; material icon named "approval".
+  static const IconData approval = IconData(0xe5a3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">apps</i> &#x2014; material icon named "apps".
+  static const IconData apps = IconData(0xe5a4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">apps</i> &#x2014; material icon named "apps outlined".
+  static const IconData apps_outlined = IconData(0xe049, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">apps</i> &#x2014; material icon named "apps rounded".
+  static const IconData apps_rounded = IconData(0xf077, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">apps</i> &#x2014; material icon named "apps sharp".
+  static const IconData apps_sharp = IconData(0xeb49, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">architecture</i> &#x2014; material icon named "architecture".
+  static const IconData architecture = IconData(0xe5a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">architecture</i> &#x2014; material icon named "architecture outlined".
+  static const IconData architecture_outlined = IconData(0xe04a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">architecture</i> &#x2014; material icon named "architecture rounded".
+  static const IconData architecture_rounded = IconData(0xf078, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">architecture</i> &#x2014; material icon named "architecture sharp".
+  static const IconData architecture_sharp = IconData(0xeb4a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">archive</i> &#x2014; material icon named "archive".
+  static const IconData archive = IconData(0xe5a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">archive</i> &#x2014; material icon named "archive outlined".
+  static const IconData archive_outlined = IconData(0xe04b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">archive</i> &#x2014; material icon named "archive rounded".
+  static const IconData archive_rounded = IconData(0xf079, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">archive</i> &#x2014; material icon named "archive sharp".
+  static const IconData archive_sharp = IconData(0xeb4b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">arrow_back</i> &#x2014; material icon named "arrow back".
+  static const IconData arrow_back = IconData(0xe5a7, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">arrow_back_ios</i> &#x2014; material icon named "arrow back ios".
+  static const IconData arrow_back_ios = IconData(0xe5a8, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">arrow_back_ios</i> &#x2014; material icon named "arrow back ios outlined".
+  static const IconData arrow_back_ios_outlined = IconData(0xe04c, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">arrow_back_ios</i> &#x2014; material icon named "arrow back ios rounded".
+  static const IconData arrow_back_ios_rounded = IconData(0xf07a, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">arrow_back_ios</i> &#x2014; material icon named "arrow back ios sharp".
+  static const IconData arrow_back_ios_sharp = IconData(0xeb4c, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">arrow_back</i> &#x2014; material icon named "arrow back outlined".
+  static const IconData arrow_back_outlined = IconData(0xe04d, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">arrow_back</i> &#x2014; material icon named "arrow back rounded".
+  static const IconData arrow_back_rounded = IconData(0xf07b, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">arrow_back</i> &#x2014; material icon named "arrow back sharp".
+  static const IconData arrow_back_sharp = IconData(0xeb4d, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">arrow_circle_down</i> &#x2014; material icon named "arrow circle down".
+  static const IconData arrow_circle_down = IconData(0xe5a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">arrow_circle_down</i> &#x2014; material icon named "arrow circle down outlined".
+  static const IconData arrow_circle_down_outlined = IconData(0xe04e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">arrow_circle_down</i> &#x2014; material icon named "arrow circle down rounded".
+  static const IconData arrow_circle_down_rounded = IconData(0xf07c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">arrow_circle_down</i> &#x2014; material icon named "arrow circle down sharp".
+  static const IconData arrow_circle_down_sharp = IconData(0xeb4e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">arrow_circle_up</i> &#x2014; material icon named "arrow circle up".
+  static const IconData arrow_circle_up = IconData(0xe5aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">arrow_circle_up</i> &#x2014; material icon named "arrow circle up outlined".
+  static const IconData arrow_circle_up_outlined = IconData(0xe04f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">arrow_circle_up</i> &#x2014; material icon named "arrow circle up rounded".
+  static const IconData arrow_circle_up_rounded = IconData(0xf07d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">arrow_circle_up</i> &#x2014; material icon named "arrow circle up sharp".
+  static const IconData arrow_circle_up_sharp = IconData(0xeb4f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">arrow_downward</i> &#x2014; material icon named "arrow downward".
+  static const IconData arrow_downward = IconData(0xe5ab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">arrow_downward</i> &#x2014; material icon named "arrow downward outlined".
+  static const IconData arrow_downward_outlined = IconData(0xe050, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">arrow_downward</i> &#x2014; material icon named "arrow downward rounded".
+  static const IconData arrow_downward_rounded = IconData(0xf07e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">arrow_downward</i> &#x2014; material icon named "arrow downward sharp".
+  static const IconData arrow_downward_sharp = IconData(0xeb50, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">arrow_drop_down</i> &#x2014; material icon named "arrow drop down".
+  static const IconData arrow_drop_down = IconData(0xe5ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">arrow_drop_down_circle</i> &#x2014; material icon named "arrow drop down circle".
+  static const IconData arrow_drop_down_circle = IconData(0xe5ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">arrow_drop_down_circle</i> &#x2014; material icon named "arrow drop down circle outlined".
+  static const IconData arrow_drop_down_circle_outlined = IconData(0xe051, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">arrow_drop_down_circle</i> &#x2014; material icon named "arrow drop down circle rounded".
+  static const IconData arrow_drop_down_circle_rounded = IconData(0xf07f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">arrow_drop_down_circle</i> &#x2014; material icon named "arrow drop down circle sharp".
+  static const IconData arrow_drop_down_circle_sharp = IconData(0xeb51, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">arrow_drop_down</i> &#x2014; material icon named "arrow drop down outlined".
+  static const IconData arrow_drop_down_outlined = IconData(0xe052, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">arrow_drop_down</i> &#x2014; material icon named "arrow drop down rounded".
+  static const IconData arrow_drop_down_rounded = IconData(0xf080, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">arrow_drop_down</i> &#x2014; material icon named "arrow drop down sharp".
+  static const IconData arrow_drop_down_sharp = IconData(0xeb52, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">arrow_drop_up</i> &#x2014; material icon named "arrow drop up".
+  static const IconData arrow_drop_up = IconData(0xe5ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">arrow_drop_up</i> &#x2014; material icon named "arrow drop up outlined".
+  static const IconData arrow_drop_up_outlined = IconData(0xe053, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">arrow_drop_up</i> &#x2014; material icon named "arrow drop up rounded".
+  static const IconData arrow_drop_up_rounded = IconData(0xf081, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">arrow_drop_up</i> &#x2014; material icon named "arrow drop up sharp".
+  static const IconData arrow_drop_up_sharp = IconData(0xeb53, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">arrow_forward</i> &#x2014; material icon named "arrow forward".
+  static const IconData arrow_forward = IconData(0xe5af, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">arrow_forward_ios</i> &#x2014; material icon named "arrow forward ios".
+  static const IconData arrow_forward_ios = IconData(0xe5b0, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">arrow_forward_ios</i> &#x2014; material icon named "arrow forward ios outlined".
+  static const IconData arrow_forward_ios_outlined = IconData(0xe054, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">arrow_forward_ios</i> &#x2014; material icon named "arrow forward ios rounded".
+  static const IconData arrow_forward_ios_rounded = IconData(0xf082, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">arrow_forward_ios</i> &#x2014; material icon named "arrow forward ios sharp".
+  static const IconData arrow_forward_ios_sharp = IconData(0xeb54, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">arrow_forward</i> &#x2014; material icon named "arrow forward outlined".
+  static const IconData arrow_forward_outlined = IconData(0xe055, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">arrow_forward</i> &#x2014; material icon named "arrow forward rounded".
+  static const IconData arrow_forward_rounded = IconData(0xf083, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">arrow_forward</i> &#x2014; material icon named "arrow forward sharp".
+  static const IconData arrow_forward_sharp = IconData(0xeb55, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">arrow_left</i> &#x2014; material icon named "arrow left".
+  static const IconData arrow_left = IconData(0xe5b1, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">arrow_left</i> &#x2014; material icon named "arrow left outlined".
+  static const IconData arrow_left_outlined = IconData(0xe056, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">arrow_left</i> &#x2014; material icon named "arrow left rounded".
+  static const IconData arrow_left_rounded = IconData(0xf084, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">arrow_left</i> &#x2014; material icon named "arrow left sharp".
+  static const IconData arrow_left_sharp = IconData(0xeb56, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">arrow_right</i> &#x2014; material icon named "arrow right".
+  static const IconData arrow_right = IconData(0xe5b2, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">arrow_right_alt</i> &#x2014; material icon named "arrow right alt".
+  static const IconData arrow_right_alt = IconData(0xe5b3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">arrow_right_alt</i> &#x2014; material icon named "arrow right alt outlined".
+  static const IconData arrow_right_alt_outlined = IconData(0xe057, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">arrow_right_alt</i> &#x2014; material icon named "arrow right alt rounded".
+  static const IconData arrow_right_alt_rounded = IconData(0xf085, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">arrow_right_alt</i> &#x2014; material icon named "arrow right alt sharp".
+  static const IconData arrow_right_alt_sharp = IconData(0xeb57, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">arrow_right</i> &#x2014; material icon named "arrow right outlined".
+  static const IconData arrow_right_outlined = IconData(0xe058, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">arrow_right</i> &#x2014; material icon named "arrow right rounded".
+  static const IconData arrow_right_rounded = IconData(0xf086, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">arrow_right</i> &#x2014; material icon named "arrow right sharp".
+  static const IconData arrow_right_sharp = IconData(0xeb58, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">arrow_upward</i> &#x2014; material icon named "arrow upward".
+  static const IconData arrow_upward = IconData(0xe5b4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">arrow_upward</i> &#x2014; material icon named "arrow upward outlined".
+  static const IconData arrow_upward_outlined = IconData(0xe059, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">arrow_upward</i> &#x2014; material icon named "arrow upward rounded".
+  static const IconData arrow_upward_rounded = IconData(0xf087, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">arrow_upward</i> &#x2014; material icon named "arrow upward sharp".
+  static const IconData arrow_upward_sharp = IconData(0xeb59, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">art_track</i> &#x2014; material icon named "art track".
+  static const IconData art_track = IconData(0xe5b5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">art_track</i> &#x2014; material icon named "art track outlined".
+  static const IconData art_track_outlined = IconData(0xe05a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">art_track</i> &#x2014; material icon named "art track rounded".
+  static const IconData art_track_rounded = IconData(0xf088, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">art_track</i> &#x2014; material icon named "art track sharp".
+  static const IconData art_track_sharp = IconData(0xeb5a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">article</i> &#x2014; material icon named "article".
+  static const IconData article = IconData(0xe5b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">article</i> &#x2014; material icon named "article outlined".
+  static const IconData article_outlined = IconData(0xe05b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">article</i> &#x2014; material icon named "article rounded".
+  static const IconData article_rounded = IconData(0xf089, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">article</i> &#x2014; material icon named "article sharp".
+  static const IconData article_sharp = IconData(0xeb5b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">aspect_ratio</i> &#x2014; material icon named "aspect ratio".
+  static const IconData aspect_ratio = IconData(0xe5b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">aspect_ratio</i> &#x2014; material icon named "aspect ratio outlined".
+  static const IconData aspect_ratio_outlined = IconData(0xe05c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">aspect_ratio</i> &#x2014; material icon named "aspect ratio rounded".
+  static const IconData aspect_ratio_rounded = IconData(0xf08a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">aspect_ratio</i> &#x2014; material icon named "aspect ratio sharp".
+  static const IconData aspect_ratio_sharp = IconData(0xeb5c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">assessment</i> &#x2014; material icon named "assessment".
+  static const IconData assessment = IconData(0xe5b8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">assessment</i> &#x2014; material icon named "assessment outlined".
+  static const IconData assessment_outlined = IconData(0xe05d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">assessment</i> &#x2014; material icon named "assessment rounded".
+  static const IconData assessment_rounded = IconData(0xf08b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">assessment</i> &#x2014; material icon named "assessment sharp".
+  static const IconData assessment_sharp = IconData(0xeb5d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">assignment</i> &#x2014; material icon named "assignment".
+  static const IconData assignment = IconData(0xe5b9, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">assignment_ind</i> &#x2014; material icon named "assignment ind".
+  static const IconData assignment_ind = IconData(0xe5ba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">assignment_ind</i> &#x2014; material icon named "assignment ind outlined".
+  static const IconData assignment_ind_outlined = IconData(0xe05e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">assignment_ind</i> &#x2014; material icon named "assignment ind rounded".
+  static const IconData assignment_ind_rounded = IconData(0xf08c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">assignment_ind</i> &#x2014; material icon named "assignment ind sharp".
+  static const IconData assignment_ind_sharp = IconData(0xeb5e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">assignment_late</i> &#x2014; material icon named "assignment late".
+  static const IconData assignment_late = IconData(0xe5bb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">assignment_late</i> &#x2014; material icon named "assignment late outlined".
+  static const IconData assignment_late_outlined = IconData(0xe05f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">assignment_late</i> &#x2014; material icon named "assignment late rounded".
+  static const IconData assignment_late_rounded = IconData(0xf08d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">assignment_late</i> &#x2014; material icon named "assignment late sharp".
+  static const IconData assignment_late_sharp = IconData(0xeb5f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">assignment</i> &#x2014; material icon named "assignment outlined".
+  static const IconData assignment_outlined = IconData(0xe060, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">assignment_return</i> &#x2014; material icon named "assignment return".
+  static const IconData assignment_return = IconData(0xe5bc, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">assignment_return</i> &#x2014; material icon named "assignment return outlined".
+  static const IconData assignment_return_outlined = IconData(0xe061, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">assignment_return</i> &#x2014; material icon named "assignment return rounded".
+  static const IconData assignment_return_rounded = IconData(0xf08e, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">assignment_return</i> &#x2014; material icon named "assignment return sharp".
+  static const IconData assignment_return_sharp = IconData(0xeb60, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">assignment_returned</i> &#x2014; material icon named "assignment returned".
+  static const IconData assignment_returned = IconData(0xe5bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">assignment_returned</i> &#x2014; material icon named "assignment returned outlined".
+  static const IconData assignment_returned_outlined = IconData(0xe062, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">assignment_returned</i> &#x2014; material icon named "assignment returned rounded".
+  static const IconData assignment_returned_rounded = IconData(0xf08f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">assignment_returned</i> &#x2014; material icon named "assignment returned sharp".
+  static const IconData assignment_returned_sharp = IconData(0xeb61, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">assignment</i> &#x2014; material icon named "assignment rounded".
+  static const IconData assignment_rounded = IconData(0xf090, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">assignment</i> &#x2014; material icon named "assignment sharp".
+  static const IconData assignment_sharp = IconData(0xeb62, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">assignment_turned_in</i> &#x2014; material icon named "assignment turned in".
+  static const IconData assignment_turned_in = IconData(0xe5be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">assignment_turned_in</i> &#x2014; material icon named "assignment turned in outlined".
+  static const IconData assignment_turned_in_outlined = IconData(0xe063, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">assignment_turned_in</i> &#x2014; material icon named "assignment turned in rounded".
+  static const IconData assignment_turned_in_rounded = IconData(0xf091, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">assignment_turned_in</i> &#x2014; material icon named "assignment turned in sharp".
+  static const IconData assignment_turned_in_sharp = IconData(0xeb63, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">assistant</i> &#x2014; material icon named "assistant".
+  static const IconData assistant = IconData(0xe5bf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">assistant_direction</i> &#x2014; material icon named "assistant direction".
+  static const IconData assistant_direction = IconData(0xe5c0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">assistant_navigation</i> &#x2014; material icon named "assistant navigation".
+  static const IconData assistant_navigation = IconData(0xe5c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">assistant</i> &#x2014; material icon named "assistant outlined".
+  static const IconData assistant_outlined = IconData(0xe064, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">assistant_photo</i> &#x2014; material icon named "assistant photo".
+  static const IconData assistant_photo = IconData(0xe5c2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">assistant_photo</i> &#x2014; material icon named "assistant photo outlined".
+  static const IconData assistant_photo_outlined = IconData(0xe065, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">assistant_photo</i> &#x2014; material icon named "assistant photo rounded".
+  static const IconData assistant_photo_rounded = IconData(0xf092, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">assistant_photo</i> &#x2014; material icon named "assistant photo sharp".
+  static const IconData assistant_photo_sharp = IconData(0xeb64, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">assistant</i> &#x2014; material icon named "assistant rounded".
+  static const IconData assistant_rounded = IconData(0xf093, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">assistant</i> &#x2014; material icon named "assistant sharp".
+  static const IconData assistant_sharp = IconData(0xeb65, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">atm</i> &#x2014; material icon named "atm".
+  static const IconData atm = IconData(0xe5c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">atm</i> &#x2014; material icon named "atm outlined".
+  static const IconData atm_outlined = IconData(0xe066, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">atm</i> &#x2014; material icon named "atm rounded".
+  static const IconData atm_rounded = IconData(0xf094, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">atm</i> &#x2014; material icon named "atm sharp".
+  static const IconData atm_sharp = IconData(0xeb66, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">attach_email</i> &#x2014; material icon named "attach email".
+  static const IconData attach_email = IconData(0xe5c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">attach_email</i> &#x2014; material icon named "attach email outlined".
+  static const IconData attach_email_outlined = IconData(0xe067, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">attach_email</i> &#x2014; material icon named "attach email rounded".
+  static const IconData attach_email_rounded = IconData(0xf095, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">attach_email</i> &#x2014; material icon named "attach email sharp".
+  static const IconData attach_email_sharp = IconData(0xeb67, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">attach_file</i> &#x2014; material icon named "attach file".
+  static const IconData attach_file = IconData(0xe5c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">attach_file</i> &#x2014; material icon named "attach file outlined".
+  static const IconData attach_file_outlined = IconData(0xe068, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">attach_file</i> &#x2014; material icon named "attach file rounded".
+  static const IconData attach_file_rounded = IconData(0xf096, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">attach_file</i> &#x2014; material icon named "attach file sharp".
+  static const IconData attach_file_sharp = IconData(0xeb68, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">attach_money</i> &#x2014; material icon named "attach money".
+  static const IconData attach_money = IconData(0xe5c6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">attach_money</i> &#x2014; material icon named "attach money outlined".
+  static const IconData attach_money_outlined = IconData(0xe069, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">attach_money</i> &#x2014; material icon named "attach money rounded".
+  static const IconData attach_money_rounded = IconData(0xf097, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">attach_money</i> &#x2014; material icon named "attach money sharp".
+  static const IconData attach_money_sharp = IconData(0xeb69, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">attachment</i> &#x2014; material icon named "attachment".
+  static const IconData attachment = IconData(0xe5c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">attachment</i> &#x2014; material icon named "attachment outlined".
+  static const IconData attachment_outlined = IconData(0xe06a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">attachment</i> &#x2014; material icon named "attachment rounded".
+  static const IconData attachment_rounded = IconData(0xf098, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">attachment</i> &#x2014; material icon named "attachment sharp".
+  static const IconData attachment_sharp = IconData(0xeb6a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">attractions</i> &#x2014; material icon named "attractions".
+  static const IconData attractions = IconData(0xe5c8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">attribution</i> &#x2014; material icon named "attribution outlined".
+  static const IconData attribution_outlined = IconData(0xe06b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">attribution</i> &#x2014; material icon named "attribution rounded".
+  static const IconData attribution_rounded = IconData(0xf099, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">attribution</i> &#x2014; material icon named "attribution sharp".
+  static const IconData attribution_sharp = IconData(0xeb6b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">audiotrack</i> &#x2014; material icon named "audiotrack".
+  static const IconData audiotrack = IconData(0xe5c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">audiotrack</i> &#x2014; material icon named "audiotrack outlined".
+  static const IconData audiotrack_outlined = IconData(0xe06c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">audiotrack</i> &#x2014; material icon named "audiotrack rounded".
+  static const IconData audiotrack_rounded = IconData(0xf09a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">audiotrack</i> &#x2014; material icon named "audiotrack sharp".
+  static const IconData audiotrack_sharp = IconData(0xeb6c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">auto_awesome</i> &#x2014; material icon named "auto awesome".
+  static const IconData auto_awesome = IconData(0xe5ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">auto_awesome_mosaic</i> &#x2014; material icon named "auto awesome mosaic".
+  static const IconData auto_awesome_mosaic = IconData(0xe5cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">auto_awesome_motion</i> &#x2014; material icon named "auto awesome motion".
+  static const IconData auto_awesome_motion = IconData(0xe5cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">auto_delete</i> &#x2014; material icon named "auto delete".
+  static const IconData auto_delete = IconData(0xe5cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">auto_delete</i> &#x2014; material icon named "auto delete outlined".
+  static const IconData auto_delete_outlined = IconData(0xe06d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">auto_delete</i> &#x2014; material icon named "auto delete rounded".
+  static const IconData auto_delete_rounded = IconData(0xf09b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">auto_delete</i> &#x2014; material icon named "auto delete sharp".
+  static const IconData auto_delete_sharp = IconData(0xeb6d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">auto_fix_high</i> &#x2014; material icon named "auto fix high".
+  static const IconData auto_fix_high = IconData(0xe5ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">auto_fix_normal</i> &#x2014; material icon named "auto fix normal".
+  static const IconData auto_fix_normal = IconData(0xe5cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">auto_fix_off</i> &#x2014; material icon named "auto fix off".
+  static const IconData auto_fix_off = IconData(0xe5d0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">auto_stories</i> &#x2014; material icon named "auto stories".
+  static const IconData auto_stories = IconData(0xe5d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">autorenew</i> &#x2014; material icon named "autorenew".
+  static const IconData autorenew = IconData(0xe5d2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">autorenew</i> &#x2014; material icon named "autorenew outlined".
+  static const IconData autorenew_outlined = IconData(0xe06e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">autorenew</i> &#x2014; material icon named "autorenew rounded".
+  static const IconData autorenew_rounded = IconData(0xf09c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">autorenew</i> &#x2014; material icon named "autorenew sharp".
+  static const IconData autorenew_sharp = IconData(0xeb6e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">av_timer</i> &#x2014; material icon named "av timer".
+  static const IconData av_timer = IconData(0xe5d3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">av_timer</i> &#x2014; material icon named "av timer outlined".
+  static const IconData av_timer_outlined = IconData(0xe06f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">av_timer</i> &#x2014; material icon named "av timer rounded".
+  static const IconData av_timer_rounded = IconData(0xf09d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">av_timer</i> &#x2014; material icon named "av timer sharp".
+  static const IconData av_timer_sharp = IconData(0xeb6f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">baby_changing_station</i> &#x2014; material icon named "baby changing station".
+  static const IconData baby_changing_station = IconData(0xe5d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">baby_changing_station</i> &#x2014; material icon named "baby changing station outlined".
+  static const IconData baby_changing_station_outlined = IconData(0xe070, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">baby_changing_station</i> &#x2014; material icon named "baby changing station rounded".
+  static const IconData baby_changing_station_rounded = IconData(0xf09e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">baby_changing_station</i> &#x2014; material icon named "baby changing station sharp".
+  static const IconData baby_changing_station_sharp = IconData(0xeb70, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">backpack</i> &#x2014; material icon named "backpack".
+  static const IconData backpack = IconData(0xe5d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">backpack</i> &#x2014; material icon named "backpack outlined".
+  static const IconData backpack_outlined = IconData(0xe071, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">backpack</i> &#x2014; material icon named "backpack rounded".
+  static const IconData backpack_rounded = IconData(0xf09f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">backpack</i> &#x2014; material icon named "backpack sharp".
+  static const IconData backpack_sharp = IconData(0xeb71, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">backspace</i> &#x2014; material icon named "backspace".
+  static const IconData backspace = IconData(0xe5d6, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">backspace</i> &#x2014; material icon named "backspace outlined".
+  static const IconData backspace_outlined = IconData(0xe072, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">backspace</i> &#x2014; material icon named "backspace rounded".
+  static const IconData backspace_rounded = IconData(0xf0a0, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">backspace</i> &#x2014; material icon named "backspace sharp".
+  static const IconData backspace_sharp = IconData(0xeb72, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">backup</i> &#x2014; material icon named "backup".
+  static const IconData backup = IconData(0xe5d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">backup</i> &#x2014; material icon named "backup outlined".
+  static const IconData backup_outlined = IconData(0xe073, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">backup</i> &#x2014; material icon named "backup rounded".
+  static const IconData backup_rounded = IconData(0xf0a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">backup</i> &#x2014; material icon named "backup sharp".
+  static const IconData backup_sharp = IconData(0xeb73, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">backup_table</i> &#x2014; material icon named "backup table".
+  static const IconData backup_table = IconData(0xe5d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">backup_table</i> &#x2014; material icon named "backup table outlined".
+  static const IconData backup_table_outlined = IconData(0xe074, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">backup_table</i> &#x2014; material icon named "backup table rounded".
+  static const IconData backup_table_rounded = IconData(0xf0a2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">backup_table</i> &#x2014; material icon named "backup table sharp".
+  static const IconData backup_table_sharp = IconData(0xeb74, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">badge</i> &#x2014; material icon named "badge".
+  static const IconData badge = IconData(0xe5d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bakery_dining</i> &#x2014; material icon named "bakery dining".
+  static const IconData bakery_dining = IconData(0xe5da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">ballot</i> &#x2014; material icon named "ballot".
+  static const IconData ballot = IconData(0xe5db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">ballot</i> &#x2014; material icon named "ballot outlined".
+  static const IconData ballot_outlined = IconData(0xe075, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">ballot</i> &#x2014; material icon named "ballot rounded".
+  static const IconData ballot_rounded = IconData(0xf0a3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">ballot</i> &#x2014; material icon named "ballot sharp".
+  static const IconData ballot_sharp = IconData(0xeb75, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bar_chart</i> &#x2014; material icon named "bar chart".
+  static const IconData bar_chart = IconData(0xe5dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bar_chart</i> &#x2014; material icon named "bar chart outlined".
+  static const IconData bar_chart_outlined = IconData(0xe076, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bar_chart</i> &#x2014; material icon named "bar chart rounded".
+  static const IconData bar_chart_rounded = IconData(0xf0a4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bar_chart</i> &#x2014; material icon named "bar chart sharp".
+  static const IconData bar_chart_sharp = IconData(0xeb76, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">batch_prediction</i> &#x2014; material icon named "batch prediction".
+  static const IconData batch_prediction = IconData(0xe5dd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">batch_prediction</i> &#x2014; material icon named "batch prediction outlined".
+  static const IconData batch_prediction_outlined = IconData(0xe077, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">batch_prediction</i> &#x2014; material icon named "batch prediction rounded".
+  static const IconData batch_prediction_rounded = IconData(0xf0a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">batch_prediction</i> &#x2014; material icon named "batch prediction sharp".
+  static const IconData batch_prediction_sharp = IconData(0xeb77, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bathtub</i> &#x2014; material icon named "bathtub".
+  static const IconData bathtub = IconData(0xe5de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bathtub</i> &#x2014; material icon named "bathtub outlined".
+  static const IconData bathtub_outlined = IconData(0xe078, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bathtub</i> &#x2014; material icon named "bathtub rounded".
+  static const IconData bathtub_rounded = IconData(0xf0a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bathtub</i> &#x2014; material icon named "bathtub sharp".
+  static const IconData bathtub_sharp = IconData(0xeb78, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">battery_alert</i> &#x2014; material icon named "battery alert".
+  static const IconData battery_alert = IconData(0xe5df, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">battery_alert</i> &#x2014; material icon named "battery alert outlined".
+  static const IconData battery_alert_outlined = IconData(0xe079, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">battery_alert</i> &#x2014; material icon named "battery alert rounded".
+  static const IconData battery_alert_rounded = IconData(0xf0a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">battery_alert</i> &#x2014; material icon named "battery alert sharp".
+  static const IconData battery_alert_sharp = IconData(0xeb79, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">battery_charging_full</i> &#x2014; material icon named "battery charging full".
+  static const IconData battery_charging_full = IconData(0xe5e0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">battery_charging_full</i> &#x2014; material icon named "battery charging full outlined".
+  static const IconData battery_charging_full_outlined = IconData(0xe07a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">battery_charging_full</i> &#x2014; material icon named "battery charging full rounded".
+  static const IconData battery_charging_full_rounded = IconData(0xf0a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">battery_charging_full</i> &#x2014; material icon named "battery charging full sharp".
+  static const IconData battery_charging_full_sharp = IconData(0xeb7a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">battery_full</i> &#x2014; material icon named "battery full".
+  static const IconData battery_full = IconData(0xe5e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">battery_full</i> &#x2014; material icon named "battery full outlined".
+  static const IconData battery_full_outlined = IconData(0xe07b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">battery_full</i> &#x2014; material icon named "battery full rounded".
+  static const IconData battery_full_rounded = IconData(0xf0a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">battery_full</i> &#x2014; material icon named "battery full sharp".
+  static const IconData battery_full_sharp = IconData(0xeb7b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">battery_std</i> &#x2014; material icon named "battery std".
+  static const IconData battery_std = IconData(0xe5e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">battery_std</i> &#x2014; material icon named "battery std outlined".
+  static const IconData battery_std_outlined = IconData(0xe07c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">battery_std</i> &#x2014; material icon named "battery std rounded".
+  static const IconData battery_std_rounded = IconData(0xf0aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">battery_std</i> &#x2014; material icon named "battery std sharp".
+  static const IconData battery_std_sharp = IconData(0xeb7c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">battery_unknown</i> &#x2014; material icon named "battery unknown".
+  static const IconData battery_unknown = IconData(0xe5e3, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">battery_unknown</i> &#x2014; material icon named "battery unknown outlined".
+  static const IconData battery_unknown_outlined = IconData(0xe07d, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">battery_unknown</i> &#x2014; material icon named "battery unknown rounded".
+  static const IconData battery_unknown_rounded = IconData(0xf0ab, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">battery_unknown</i> &#x2014; material icon named "battery unknown sharp".
+  static const IconData battery_unknown_sharp = IconData(0xeb7d, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">beach_access</i> &#x2014; material icon named "beach access".
+  static const IconData beach_access = IconData(0xe5e4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">beach_access</i> &#x2014; material icon named "beach access outlined".
+  static const IconData beach_access_outlined = IconData(0xe07e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">beach_access</i> &#x2014; material icon named "beach access rounded".
+  static const IconData beach_access_rounded = IconData(0xf0ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">beach_access</i> &#x2014; material icon named "beach access sharp".
+  static const IconData beach_access_sharp = IconData(0xeb7e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bedtime</i> &#x2014; material icon named "bedtime".
+  static const IconData bedtime = IconData(0xe5e5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bedtime</i> &#x2014; material icon named "bedtime outlined".
+  static const IconData bedtime_outlined = IconData(0xe07f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bedtime</i> &#x2014; material icon named "bedtime rounded".
+  static const IconData bedtime_rounded = IconData(0xf0ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bedtime</i> &#x2014; material icon named "bedtime sharp".
+  static const IconData bedtime_sharp = IconData(0xeb7f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">beenhere</i> &#x2014; material icon named "beenhere".
+  static const IconData beenhere = IconData(0xe5e6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">beenhere</i> &#x2014; material icon named "beenhere outlined".
+  static const IconData beenhere_outlined = IconData(0xe080, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">beenhere</i> &#x2014; material icon named "beenhere rounded".
+  static const IconData beenhere_rounded = IconData(0xf0ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">beenhere</i> &#x2014; material icon named "beenhere sharp".
+  static const IconData beenhere_sharp = IconData(0xeb80, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bento</i> &#x2014; material icon named "bento".
+  static const IconData bento = IconData(0xe5e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bento</i> &#x2014; material icon named "bento outlined".
+  static const IconData bento_outlined = IconData(0xe081, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bento</i> &#x2014; material icon named "bento rounded".
+  static const IconData bento_rounded = IconData(0xf0af, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bento</i> &#x2014; material icon named "bento sharp".
+  static const IconData bento_sharp = IconData(0xeb81, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bike_scooter</i> &#x2014; material icon named "bike scooter".
+  static const IconData bike_scooter = IconData(0xe5e8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bike_scooter</i> &#x2014; material icon named "bike scooter outlined".
+  static const IconData bike_scooter_outlined = IconData(0xe082, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bike_scooter</i> &#x2014; material icon named "bike scooter rounded".
+  static const IconData bike_scooter_rounded = IconData(0xf0b0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bike_scooter</i> &#x2014; material icon named "bike scooter sharp".
+  static const IconData bike_scooter_sharp = IconData(0xeb82, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">biotech</i> &#x2014; material icon named "biotech".
+  static const IconData biotech = IconData(0xe5e9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">biotech</i> &#x2014; material icon named "biotech outlined".
+  static const IconData biotech_outlined = IconData(0xe083, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">biotech</i> &#x2014; material icon named "biotech rounded".
+  static const IconData biotech_rounded = IconData(0xf0b1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">biotech</i> &#x2014; material icon named "biotech sharp".
+  static const IconData biotech_sharp = IconData(0xeb83, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">block</i> &#x2014; material icon named "block".
+  static const IconData block = IconData(0xe5ea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">block_flipped</i> &#x2014; material icon named "block flipped".
+  static const IconData block_flipped = IconData(0xe5eb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">block</i> &#x2014; material icon named "block outlined".
+  static const IconData block_outlined = IconData(0xe084, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">block</i> &#x2014; material icon named "block rounded".
+  static const IconData block_rounded = IconData(0xf0b2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">block</i> &#x2014; material icon named "block sharp".
+  static const IconData block_sharp = IconData(0xeb84, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bluetooth</i> &#x2014; material icon named "bluetooth".
+  static const IconData bluetooth = IconData(0xe5ec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bluetooth_audio</i> &#x2014; material icon named "bluetooth audio".
+  static const IconData bluetooth_audio = IconData(0xe5ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bluetooth_audio</i> &#x2014; material icon named "bluetooth audio outlined".
+  static const IconData bluetooth_audio_outlined = IconData(0xe085, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bluetooth_audio</i> &#x2014; material icon named "bluetooth audio rounded".
+  static const IconData bluetooth_audio_rounded = IconData(0xf0b3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bluetooth_audio</i> &#x2014; material icon named "bluetooth audio sharp".
+  static const IconData bluetooth_audio_sharp = IconData(0xeb85, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bluetooth_connected</i> &#x2014; material icon named "bluetooth connected".
+  static const IconData bluetooth_connected = IconData(0xe5ee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bluetooth_connected</i> &#x2014; material icon named "bluetooth connected outlined".
+  static const IconData bluetooth_connected_outlined = IconData(0xe086, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bluetooth_connected</i> &#x2014; material icon named "bluetooth connected rounded".
+  static const IconData bluetooth_connected_rounded = IconData(0xf0b4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bluetooth_connected</i> &#x2014; material icon named "bluetooth connected sharp".
+  static const IconData bluetooth_connected_sharp = IconData(0xeb86, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bluetooth_disabled</i> &#x2014; material icon named "bluetooth disabled".
+  static const IconData bluetooth_disabled = IconData(0xe5ef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bluetooth_disabled</i> &#x2014; material icon named "bluetooth disabled outlined".
+  static const IconData bluetooth_disabled_outlined = IconData(0xe087, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bluetooth_disabled</i> &#x2014; material icon named "bluetooth disabled rounded".
+  static const IconData bluetooth_disabled_rounded = IconData(0xf0b5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bluetooth_disabled</i> &#x2014; material icon named "bluetooth disabled sharp".
+  static const IconData bluetooth_disabled_sharp = IconData(0xeb87, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bluetooth</i> &#x2014; material icon named "bluetooth outlined".
+  static const IconData bluetooth_outlined = IconData(0xe088, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bluetooth</i> &#x2014; material icon named "bluetooth rounded".
+  static const IconData bluetooth_rounded = IconData(0xf0b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bluetooth_searching</i> &#x2014; material icon named "bluetooth searching".
+  static const IconData bluetooth_searching = IconData(0xe5f0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bluetooth_searching</i> &#x2014; material icon named "bluetooth searching outlined".
+  static const IconData bluetooth_searching_outlined = IconData(0xe089, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bluetooth_searching</i> &#x2014; material icon named "bluetooth searching rounded".
+  static const IconData bluetooth_searching_rounded = IconData(0xf0b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bluetooth_searching</i> &#x2014; material icon named "bluetooth searching sharp".
+  static const IconData bluetooth_searching_sharp = IconData(0xeb88, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bluetooth</i> &#x2014; material icon named "bluetooth sharp".
+  static const IconData bluetooth_sharp = IconData(0xeb89, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">blur_circular</i> &#x2014; material icon named "blur circular".
+  static const IconData blur_circular = IconData(0xe5f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">blur_circular</i> &#x2014; material icon named "blur circular outlined".
+  static const IconData blur_circular_outlined = IconData(0xe08a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">blur_circular</i> &#x2014; material icon named "blur circular rounded".
+  static const IconData blur_circular_rounded = IconData(0xf0b8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">blur_circular</i> &#x2014; material icon named "blur circular sharp".
+  static const IconData blur_circular_sharp = IconData(0xeb8a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">blur_linear</i> &#x2014; material icon named "blur linear".
+  static const IconData blur_linear = IconData(0xe5f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">blur_linear</i> &#x2014; material icon named "blur linear outlined".
+  static const IconData blur_linear_outlined = IconData(0xe08b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">blur_linear</i> &#x2014; material icon named "blur linear rounded".
+  static const IconData blur_linear_rounded = IconData(0xf0b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">blur_linear</i> &#x2014; material icon named "blur linear sharp".
+  static const IconData blur_linear_sharp = IconData(0xeb8b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">blur_off</i> &#x2014; material icon named "blur off".
+  static const IconData blur_off = IconData(0xe5f3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">blur_off</i> &#x2014; material icon named "blur off outlined".
+  static const IconData blur_off_outlined = IconData(0xe08c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">blur_off</i> &#x2014; material icon named "blur off rounded".
+  static const IconData blur_off_rounded = IconData(0xf0ba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">blur_off</i> &#x2014; material icon named "blur off sharp".
+  static const IconData blur_off_sharp = IconData(0xeb8c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">blur_on</i> &#x2014; material icon named "blur on".
+  static const IconData blur_on = IconData(0xe5f4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">blur_on</i> &#x2014; material icon named "blur on outlined".
+  static const IconData blur_on_outlined = IconData(0xe08d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">blur_on</i> &#x2014; material icon named "blur on rounded".
+  static const IconData blur_on_rounded = IconData(0xf0bb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">blur_on</i> &#x2014; material icon named "blur on sharp".
+  static const IconData blur_on_sharp = IconData(0xeb8d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bolt</i> &#x2014; material icon named "bolt".
+  static const IconData bolt = IconData(0xe5f5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">book</i> &#x2014; material icon named "book".
+  static const IconData book = IconData(0xe5f6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">book_online</i> &#x2014; material icon named "book online".
+  static const IconData book_online = IconData(0xe5f7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">book_online</i> &#x2014; material icon named "book online outlined".
+  static const IconData book_online_outlined = IconData(0xe08e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">book_online</i> &#x2014; material icon named "book online rounded".
+  static const IconData book_online_rounded = IconData(0xf0bc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">book_online</i> &#x2014; material icon named "book online sharp".
+  static const IconData book_online_sharp = IconData(0xeb8e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">book</i> &#x2014; material icon named "book outlined".
+  static const IconData book_outlined = IconData(0xe08f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">book</i> &#x2014; material icon named "book rounded".
+  static const IconData book_rounded = IconData(0xf0bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">book</i> &#x2014; material icon named "book sharp".
+  static const IconData book_sharp = IconData(0xeb8f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bookmark</i> &#x2014; material icon named "bookmark".
+  static const IconData bookmark = IconData(0xe5f8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bookmark_border</i> &#x2014; material icon named "bookmark border".
+  static const IconData bookmark_border = IconData(0xe5f9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bookmark_border</i> &#x2014; material icon named "bookmark border outlined".
+  static const IconData bookmark_border_outlined = IconData(0xe090, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bookmark_border</i> &#x2014; material icon named "bookmark border rounded".
+  static const IconData bookmark_border_rounded = IconData(0xf0be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bookmark_border</i> &#x2014; material icon named "bookmark border sharp".
+  static const IconData bookmark_border_sharp = IconData(0xeb90, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bookmark_outline</i> &#x2014; material icon named "bookmark outline".
+  static const IconData bookmark_outline = IconData(0xe5f9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bookmark_outline</i> &#x2014; material icon named "bookmark outline outlined".
+  static const IconData bookmark_outline_outlined = IconData(0xe090, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bookmark_outline</i> &#x2014; material icon named "bookmark outline rounded".
+  static const IconData bookmark_outline_rounded = IconData(0xf0be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bookmark_outline</i> &#x2014; material icon named "bookmark outline sharp".
+  static const IconData bookmark_outline_sharp = IconData(0xeb90, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bookmark</i> &#x2014; material icon named "bookmark outlined".
+  static const IconData bookmark_outlined = IconData(0xe091, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bookmark</i> &#x2014; material icon named "bookmark rounded".
+  static const IconData bookmark_rounded = IconData(0xf0bf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bookmark</i> &#x2014; material icon named "bookmark sharp".
+  static const IconData bookmark_sharp = IconData(0xeb91, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bookmarks</i> &#x2014; material icon named "bookmarks".
+  static const IconData bookmarks = IconData(0xe5fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bookmarks</i> &#x2014; material icon named "bookmarks outlined".
+  static const IconData bookmarks_outlined = IconData(0xe092, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bookmarks</i> &#x2014; material icon named "bookmarks rounded".
+  static const IconData bookmarks_rounded = IconData(0xf0c0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bookmarks</i> &#x2014; material icon named "bookmarks sharp".
+  static const IconData bookmarks_sharp = IconData(0xeb92, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">border_all</i> &#x2014; material icon named "border all".
+  static const IconData border_all = IconData(0xe5fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">border_all</i> &#x2014; material icon named "border all outlined".
+  static const IconData border_all_outlined = IconData(0xe093, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">border_all</i> &#x2014; material icon named "border all rounded".
+  static const IconData border_all_rounded = IconData(0xf0c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">border_all</i> &#x2014; material icon named "border all sharp".
+  static const IconData border_all_sharp = IconData(0xeb93, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">border_bottom</i> &#x2014; material icon named "border bottom".
+  static const IconData border_bottom = IconData(0xe5fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">border_bottom</i> &#x2014; material icon named "border bottom outlined".
+  static const IconData border_bottom_outlined = IconData(0xe094, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">border_bottom</i> &#x2014; material icon named "border bottom rounded".
+  static const IconData border_bottom_rounded = IconData(0xf0c2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">border_bottom</i> &#x2014; material icon named "border bottom sharp".
+  static const IconData border_bottom_sharp = IconData(0xeb94, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">border_clear</i> &#x2014; material icon named "border clear".
+  static const IconData border_clear = IconData(0xe5fd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">border_clear</i> &#x2014; material icon named "border clear outlined".
+  static const IconData border_clear_outlined = IconData(0xe095, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">border_clear</i> &#x2014; material icon named "border clear rounded".
+  static const IconData border_clear_rounded = IconData(0xf0c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">border_clear</i> &#x2014; material icon named "border clear sharp".
+  static const IconData border_clear_sharp = IconData(0xeb95, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">border_color</i> &#x2014; material icon named "border color".
+  static const IconData border_color = IconData(0xe5fe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">border_horizontal</i> &#x2014; material icon named "border horizontal".
+  static const IconData border_horizontal = IconData(0xe5ff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">border_horizontal</i> &#x2014; material icon named "border horizontal outlined".
+  static const IconData border_horizontal_outlined = IconData(0xe096, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">border_horizontal</i> &#x2014; material icon named "border horizontal rounded".
+  static const IconData border_horizontal_rounded = IconData(0xf0c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">border_horizontal</i> &#x2014; material icon named "border horizontal sharp".
+  static const IconData border_horizontal_sharp = IconData(0xeb96, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">border_inner</i> &#x2014; material icon named "border inner".
+  static const IconData border_inner = IconData(0xe600, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">border_inner</i> &#x2014; material icon named "border inner outlined".
+  static const IconData border_inner_outlined = IconData(0xe097, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">border_inner</i> &#x2014; material icon named "border inner rounded".
+  static const IconData border_inner_rounded = IconData(0xf0c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">border_inner</i> &#x2014; material icon named "border inner sharp".
+  static const IconData border_inner_sharp = IconData(0xeb97, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">border_left</i> &#x2014; material icon named "border left".
+  static const IconData border_left = IconData(0xe601, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">border_left</i> &#x2014; material icon named "border left outlined".
+  static const IconData border_left_outlined = IconData(0xe098, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">border_left</i> &#x2014; material icon named "border left rounded".
+  static const IconData border_left_rounded = IconData(0xf0c6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">border_left</i> &#x2014; material icon named "border left sharp".
+  static const IconData border_left_sharp = IconData(0xeb98, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">border_outer</i> &#x2014; material icon named "border outer".
+  static const IconData border_outer = IconData(0xe602, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">border_outer</i> &#x2014; material icon named "border outer outlined".
+  static const IconData border_outer_outlined = IconData(0xe099, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">border_outer</i> &#x2014; material icon named "border outer rounded".
+  static const IconData border_outer_rounded = IconData(0xf0c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">border_outer</i> &#x2014; material icon named "border outer sharp".
+  static const IconData border_outer_sharp = IconData(0xeb99, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">border_right</i> &#x2014; material icon named "border right".
+  static const IconData border_right = IconData(0xe603, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">border_right</i> &#x2014; material icon named "border right outlined".
+  static const IconData border_right_outlined = IconData(0xe09a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">border_right</i> &#x2014; material icon named "border right rounded".
+  static const IconData border_right_rounded = IconData(0xf0c8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">border_right</i> &#x2014; material icon named "border right sharp".
+  static const IconData border_right_sharp = IconData(0xeb9a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">border_style</i> &#x2014; material icon named "border style".
+  static const IconData border_style = IconData(0xe604, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">border_style</i> &#x2014; material icon named "border style outlined".
+  static const IconData border_style_outlined = IconData(0xe09b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">border_style</i> &#x2014; material icon named "border style rounded".
+  static const IconData border_style_rounded = IconData(0xf0c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">border_style</i> &#x2014; material icon named "border style sharp".
+  static const IconData border_style_sharp = IconData(0xeb9b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">border_top</i> &#x2014; material icon named "border top".
+  static const IconData border_top = IconData(0xe605, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">border_top</i> &#x2014; material icon named "border top outlined".
+  static const IconData border_top_outlined = IconData(0xe09c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">border_top</i> &#x2014; material icon named "border top rounded".
+  static const IconData border_top_rounded = IconData(0xf0ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">border_top</i> &#x2014; material icon named "border top sharp".
+  static const IconData border_top_sharp = IconData(0xeb9c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">border_vertical</i> &#x2014; material icon named "border vertical".
+  static const IconData border_vertical = IconData(0xe606, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">border_vertical</i> &#x2014; material icon named "border vertical outlined".
+  static const IconData border_vertical_outlined = IconData(0xe09d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">border_vertical</i> &#x2014; material icon named "border vertical rounded".
+  static const IconData border_vertical_rounded = IconData(0xf0cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">border_vertical</i> &#x2014; material icon named "border vertical sharp".
+  static const IconData border_vertical_sharp = IconData(0xeb9d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">branding_watermark</i> &#x2014; material icon named "branding watermark".
+  static const IconData branding_watermark = IconData(0xe607, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">branding_watermark</i> &#x2014; material icon named "branding watermark outlined".
+  static const IconData branding_watermark_outlined = IconData(0xe09e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">branding_watermark</i> &#x2014; material icon named "branding watermark rounded".
+  static const IconData branding_watermark_rounded = IconData(0xf0cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">branding_watermark</i> &#x2014; material icon named "branding watermark sharp".
+  static const IconData branding_watermark_sharp = IconData(0xeb9e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">breakfast_dining</i> &#x2014; material icon named "breakfast dining".
+  static const IconData breakfast_dining = IconData(0xe608, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">brightness_1</i> &#x2014; material icon named "brightness 1".
+  static const IconData brightness_1 = IconData(0xe609, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">brightness_1</i> &#x2014; material icon named "brightness 1 outlined".
+  static const IconData brightness_1_outlined = IconData(0xe09f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">brightness_1</i> &#x2014; material icon named "brightness 1 rounded".
+  static const IconData brightness_1_rounded = IconData(0xf0cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">brightness_1</i> &#x2014; material icon named "brightness 1 sharp".
+  static const IconData brightness_1_sharp = IconData(0xeb9f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">brightness_2</i> &#x2014; material icon named "brightness 2".
+  static const IconData brightness_2 = IconData(0xe60a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">brightness_2</i> &#x2014; material icon named "brightness 2 outlined".
+  static const IconData brightness_2_outlined = IconData(0xe0a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">brightness_2</i> &#x2014; material icon named "brightness 2 rounded".
+  static const IconData brightness_2_rounded = IconData(0xf0ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">brightness_2</i> &#x2014; material icon named "brightness 2 sharp".
+  static const IconData brightness_2_sharp = IconData(0xeba0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">brightness_3</i> &#x2014; material icon named "brightness 3".
+  static const IconData brightness_3 = IconData(0xe60b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">brightness_3</i> &#x2014; material icon named "brightness 3 outlined".
+  static const IconData brightness_3_outlined = IconData(0xe0a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">brightness_3</i> &#x2014; material icon named "brightness 3 rounded".
+  static const IconData brightness_3_rounded = IconData(0xf0cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">brightness_3</i> &#x2014; material icon named "brightness 3 sharp".
+  static const IconData brightness_3_sharp = IconData(0xeba1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">brightness_4</i> &#x2014; material icon named "brightness 4".
+  static const IconData brightness_4 = IconData(0xe60c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">brightness_4</i> &#x2014; material icon named "brightness 4 outlined".
+  static const IconData brightness_4_outlined = IconData(0xe0a2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">brightness_4</i> &#x2014; material icon named "brightness 4 rounded".
+  static const IconData brightness_4_rounded = IconData(0xf0d0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">brightness_4</i> &#x2014; material icon named "brightness 4 sharp".
+  static const IconData brightness_4_sharp = IconData(0xeba2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">brightness_5</i> &#x2014; material icon named "brightness 5".
+  static const IconData brightness_5 = IconData(0xe60d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">brightness_5</i> &#x2014; material icon named "brightness 5 outlined".
+  static const IconData brightness_5_outlined = IconData(0xe0a3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">brightness_5</i> &#x2014; material icon named "brightness 5 rounded".
+  static const IconData brightness_5_rounded = IconData(0xf0d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">brightness_5</i> &#x2014; material icon named "brightness 5 sharp".
+  static const IconData brightness_5_sharp = IconData(0xeba3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">brightness_6</i> &#x2014; material icon named "brightness 6".
+  static const IconData brightness_6 = IconData(0xe60e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">brightness_6</i> &#x2014; material icon named "brightness 6 outlined".
+  static const IconData brightness_6_outlined = IconData(0xe0a4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">brightness_6</i> &#x2014; material icon named "brightness 6 rounded".
+  static const IconData brightness_6_rounded = IconData(0xf0d2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">brightness_6</i> &#x2014; material icon named "brightness 6 sharp".
+  static const IconData brightness_6_sharp = IconData(0xeba4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">brightness_7</i> &#x2014; material icon named "brightness 7".
+  static const IconData brightness_7 = IconData(0xe60f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">brightness_7</i> &#x2014; material icon named "brightness 7 outlined".
+  static const IconData brightness_7_outlined = IconData(0xe0a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">brightness_7</i> &#x2014; material icon named "brightness 7 rounded".
+  static const IconData brightness_7_rounded = IconData(0xf0d3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">brightness_7</i> &#x2014; material icon named "brightness 7 sharp".
+  static const IconData brightness_7_sharp = IconData(0xeba5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">brightness_auto</i> &#x2014; material icon named "brightness auto".
+  static const IconData brightness_auto = IconData(0xe610, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">brightness_auto</i> &#x2014; material icon named "brightness auto outlined".
+  static const IconData brightness_auto_outlined = IconData(0xe0a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">brightness_auto</i> &#x2014; material icon named "brightness auto rounded".
+  static const IconData brightness_auto_rounded = IconData(0xf0d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">brightness_auto</i> &#x2014; material icon named "brightness auto sharp".
+  static const IconData brightness_auto_sharp = IconData(0xeba6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">brightness_high</i> &#x2014; material icon named "brightness high".
+  static const IconData brightness_high = IconData(0xe611, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">brightness_high</i> &#x2014; material icon named "brightness high outlined".
+  static const IconData brightness_high_outlined = IconData(0xe0a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">brightness_high</i> &#x2014; material icon named "brightness high rounded".
+  static const IconData brightness_high_rounded = IconData(0xf0d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">brightness_high</i> &#x2014; material icon named "brightness high sharp".
+  static const IconData brightness_high_sharp = IconData(0xeba7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">brightness_low</i> &#x2014; material icon named "brightness low".
+  static const IconData brightness_low = IconData(0xe612, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">brightness_low</i> &#x2014; material icon named "brightness low outlined".
+  static const IconData brightness_low_outlined = IconData(0xe0a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">brightness_low</i> &#x2014; material icon named "brightness low rounded".
+  static const IconData brightness_low_rounded = IconData(0xf0d6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">brightness_low</i> &#x2014; material icon named "brightness low sharp".
+  static const IconData brightness_low_sharp = IconData(0xeba8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">brightness_medium</i> &#x2014; material icon named "brightness medium".
+  static const IconData brightness_medium = IconData(0xe613, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">brightness_medium</i> &#x2014; material icon named "brightness medium outlined".
+  static const IconData brightness_medium_outlined = IconData(0xe0a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">brightness_medium</i> &#x2014; material icon named "brightness medium rounded".
+  static const IconData brightness_medium_rounded = IconData(0xf0d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">brightness_medium</i> &#x2014; material icon named "brightness medium sharp".
+  static const IconData brightness_medium_sharp = IconData(0xeba9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">broken_image</i> &#x2014; material icon named "broken image".
+  static const IconData broken_image = IconData(0xe614, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">broken_image</i> &#x2014; material icon named "broken image outlined".
+  static const IconData broken_image_outlined = IconData(0xe0aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">broken_image</i> &#x2014; material icon named "broken image rounded".
+  static const IconData broken_image_rounded = IconData(0xf0d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">broken_image</i> &#x2014; material icon named "broken image sharp".
+  static const IconData broken_image_sharp = IconData(0xebaa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">browser_not_supported</i> &#x2014; material icon named "browser not supported".
+  static const IconData browser_not_supported = IconData(0xe615, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">browser_not_supported</i> &#x2014; material icon named "browser not supported outlined".
+  static const IconData browser_not_supported_outlined = IconData(0xe0ab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">browser_not_supported</i> &#x2014; material icon named "browser not supported rounded".
+  static const IconData browser_not_supported_rounded = IconData(0xf0d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">browser_not_supported</i> &#x2014; material icon named "browser not supported sharp".
+  static const IconData browser_not_supported_sharp = IconData(0xebab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">brunch_dining</i> &#x2014; material icon named "brunch dining".
+  static const IconData brunch_dining = IconData(0xe616, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">brush</i> &#x2014; material icon named "brush".
+  static const IconData brush = IconData(0xe617, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">brush</i> &#x2014; material icon named "brush outlined".
+  static const IconData brush_outlined = IconData(0xe0ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">brush</i> &#x2014; material icon named "brush rounded".
+  static const IconData brush_rounded = IconData(0xf0da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">brush</i> &#x2014; material icon named "brush sharp".
+  static const IconData brush_sharp = IconData(0xebac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bubble_chart</i> &#x2014; material icon named "bubble chart".
+  static const IconData bubble_chart = IconData(0xe618, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bubble_chart</i> &#x2014; material icon named "bubble chart outlined".
+  static const IconData bubble_chart_outlined = IconData(0xe0ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bubble_chart</i> &#x2014; material icon named "bubble chart rounded".
+  static const IconData bubble_chart_rounded = IconData(0xf0db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bubble_chart</i> &#x2014; material icon named "bubble chart sharp".
+  static const IconData bubble_chart_sharp = IconData(0xebad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bug_report</i> &#x2014; material icon named "bug report".
+  static const IconData bug_report = IconData(0xe619, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">bug_report</i> &#x2014; material icon named "bug report outlined".
+  static const IconData bug_report_outlined = IconData(0xe0ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">bug_report</i> &#x2014; material icon named "bug report rounded".
+  static const IconData bug_report_rounded = IconData(0xf0dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">bug_report</i> &#x2014; material icon named "bug report sharp".
+  static const IconData bug_report_sharp = IconData(0xebae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">build</i> &#x2014; material icon named "build".
+  static const IconData build = IconData(0xe61a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">build_circle</i> &#x2014; material icon named "build circle".
+  static const IconData build_circle = IconData(0xe61b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">build_circle</i> &#x2014; material icon named "build circle outlined".
+  static const IconData build_circle_outlined = IconData(0xe0af, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">build_circle</i> &#x2014; material icon named "build circle rounded".
+  static const IconData build_circle_rounded = IconData(0xf0dd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">build_circle</i> &#x2014; material icon named "build circle sharp".
+  static const IconData build_circle_sharp = IconData(0xebaf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">build</i> &#x2014; material icon named "build outlined".
+  static const IconData build_outlined = IconData(0xe0b0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">build</i> &#x2014; material icon named "build rounded".
+  static const IconData build_rounded = IconData(0xf0de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">build</i> &#x2014; material icon named "build sharp".
+  static const IconData build_sharp = IconData(0xebb0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">burst_mode</i> &#x2014; material icon named "burst mode".
+  static const IconData burst_mode = IconData(0xe61c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">burst_mode</i> &#x2014; material icon named "burst mode outlined".
+  static const IconData burst_mode_outlined = IconData(0xe0b1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">burst_mode</i> &#x2014; material icon named "burst mode rounded".
+  static const IconData burst_mode_rounded = IconData(0xf0df, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">burst_mode</i> &#x2014; material icon named "burst mode sharp".
+  static const IconData burst_mode_sharp = IconData(0xebb1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">bus_alert</i> &#x2014; material icon named "bus alert".
+  static const IconData bus_alert = IconData(0xe61d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">business</i> &#x2014; material icon named "business".
+  static const IconData business = IconData(0xe61e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">business_center</i> &#x2014; material icon named "business center".
+  static const IconData business_center = IconData(0xe61f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">business_center</i> &#x2014; material icon named "business center outlined".
+  static const IconData business_center_outlined = IconData(0xe0b2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">business_center</i> &#x2014; material icon named "business center rounded".
+  static const IconData business_center_rounded = IconData(0xf0e0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">business_center</i> &#x2014; material icon named "business center sharp".
+  static const IconData business_center_sharp = IconData(0xebb2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">business</i> &#x2014; material icon named "business outlined".
+  static const IconData business_outlined = IconData(0xe0b3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">business</i> &#x2014; material icon named "business rounded".
+  static const IconData business_rounded = IconData(0xf0e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">business</i> &#x2014; material icon named "business sharp".
+  static const IconData business_sharp = IconData(0xebb3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cached</i> &#x2014; material icon named "cached".
+  static const IconData cached = IconData(0xe620, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cached</i> &#x2014; material icon named "cached outlined".
+  static const IconData cached_outlined = IconData(0xe0b4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cached</i> &#x2014; material icon named "cached rounded".
+  static const IconData cached_rounded = IconData(0xf0e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cached</i> &#x2014; material icon named "cached sharp".
+  static const IconData cached_sharp = IconData(0xebb4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cake</i> &#x2014; material icon named "cake".
+  static const IconData cake = IconData(0xe621, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cake</i> &#x2014; material icon named "cake outlined".
+  static const IconData cake_outlined = IconData(0xe0b5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cake</i> &#x2014; material icon named "cake rounded".
+  static const IconData cake_rounded = IconData(0xf0e3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cake</i> &#x2014; material icon named "cake sharp".
+  static const IconData cake_sharp = IconData(0xebb5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">calculate</i> &#x2014; material icon named "calculate".
+  static const IconData calculate = IconData(0xe622, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">calculate</i> &#x2014; material icon named "calculate outlined".
+  static const IconData calculate_outlined = IconData(0xe0b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">calculate</i> &#x2014; material icon named "calculate rounded".
+  static const IconData calculate_rounded = IconData(0xf0e4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">calculate</i> &#x2014; material icon named "calculate sharp".
+  static const IconData calculate_sharp = IconData(0xebb6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">calendar_today</i> &#x2014; material icon named "calendar today".
+  static const IconData calendar_today = IconData(0xe623, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">calendar_today</i> &#x2014; material icon named "calendar today outlined".
+  static const IconData calendar_today_outlined = IconData(0xe0b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">calendar_today</i> &#x2014; material icon named "calendar today rounded".
+  static const IconData calendar_today_rounded = IconData(0xf0e5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">calendar_today</i> &#x2014; material icon named "calendar today sharp".
+  static const IconData calendar_today_sharp = IconData(0xebb7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">calendar_view_day</i> &#x2014; material icon named "calendar view day".
+  static const IconData calendar_view_day = IconData(0xe624, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">calendar_view_day</i> &#x2014; material icon named "calendar view day outlined".
+  static const IconData calendar_view_day_outlined = IconData(0xe0b8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">calendar_view_day</i> &#x2014; material icon named "calendar view day rounded".
+  static const IconData calendar_view_day_rounded = IconData(0xf0e6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">calendar_view_day</i> &#x2014; material icon named "calendar view day sharp".
+  static const IconData calendar_view_day_sharp = IconData(0xebb8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">call</i> &#x2014; material icon named "call".
+  static const IconData call = IconData(0xe625, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">call_end</i> &#x2014; material icon named "call end".
+  static const IconData call_end = IconData(0xe626, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">call_end</i> &#x2014; material icon named "call end outlined".
+  static const IconData call_end_outlined = IconData(0xe0b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">call_end</i> &#x2014; material icon named "call end rounded".
+  static const IconData call_end_rounded = IconData(0xf0e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">call_end</i> &#x2014; material icon named "call end sharp".
+  static const IconData call_end_sharp = IconData(0xebb9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">call_made</i> &#x2014; material icon named "call made".
+  static const IconData call_made = IconData(0xe627, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">call_made</i> &#x2014; material icon named "call made outlined".
+  static const IconData call_made_outlined = IconData(0xe0ba, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">call_made</i> &#x2014; material icon named "call made rounded".
+  static const IconData call_made_rounded = IconData(0xf0e8, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">call_made</i> &#x2014; material icon named "call made sharp".
+  static const IconData call_made_sharp = IconData(0xebba, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">call_merge</i> &#x2014; material icon named "call merge".
+  static const IconData call_merge = IconData(0xe628, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">call_merge</i> &#x2014; material icon named "call merge outlined".
+  static const IconData call_merge_outlined = IconData(0xe0bb, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">call_merge</i> &#x2014; material icon named "call merge rounded".
+  static const IconData call_merge_rounded = IconData(0xf0e9, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">call_merge</i> &#x2014; material icon named "call merge sharp".
+  static const IconData call_merge_sharp = IconData(0xebbb, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">call_missed</i> &#x2014; material icon named "call missed".
+  static const IconData call_missed = IconData(0xe629, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">call_missed_outgoing</i> &#x2014; material icon named "call missed outgoing".
+  static const IconData call_missed_outgoing = IconData(0xe62a, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">call_missed_outgoing</i> &#x2014; material icon named "call missed outgoing outlined".
+  static const IconData call_missed_outgoing_outlined = IconData(0xe0bc, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">call_missed_outgoing</i> &#x2014; material icon named "call missed outgoing rounded".
+  static const IconData call_missed_outgoing_rounded = IconData(0xf0ea, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">call_missed_outgoing</i> &#x2014; material icon named "call missed outgoing sharp".
+  static const IconData call_missed_outgoing_sharp = IconData(0xebbc, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">call_missed</i> &#x2014; material icon named "call missed outlined".
+  static const IconData call_missed_outlined = IconData(0xe0bd, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">call_missed</i> &#x2014; material icon named "call missed rounded".
+  static const IconData call_missed_rounded = IconData(0xf0eb, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">call_missed</i> &#x2014; material icon named "call missed sharp".
+  static const IconData call_missed_sharp = IconData(0xebbd, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">call</i> &#x2014; material icon named "call outlined".
+  static const IconData call_outlined = IconData(0xe0be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">call_received</i> &#x2014; material icon named "call received".
+  static const IconData call_received = IconData(0xe62b, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">call_received</i> &#x2014; material icon named "call received outlined".
+  static const IconData call_received_outlined = IconData(0xe0bf, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">call_received</i> &#x2014; material icon named "call received rounded".
+  static const IconData call_received_rounded = IconData(0xf0ec, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">call_received</i> &#x2014; material icon named "call received sharp".
+  static const IconData call_received_sharp = IconData(0xebbe, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">call</i> &#x2014; material icon named "call rounded".
+  static const IconData call_rounded = IconData(0xf0ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">call</i> &#x2014; material icon named "call sharp".
+  static const IconData call_sharp = IconData(0xebbf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">call_split</i> &#x2014; material icon named "call split".
+  static const IconData call_split = IconData(0xe62c, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">call_split</i> &#x2014; material icon named "call split outlined".
+  static const IconData call_split_outlined = IconData(0xe0c0, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">call_split</i> &#x2014; material icon named "call split rounded".
+  static const IconData call_split_rounded = IconData(0xf0ee, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">call_split</i> &#x2014; material icon named "call split sharp".
+  static const IconData call_split_sharp = IconData(0xebc0, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">call_to_action</i> &#x2014; material icon named "call to action".
+  static const IconData call_to_action = IconData(0xe62d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">call_to_action</i> &#x2014; material icon named "call to action outlined".
+  static const IconData call_to_action_outlined = IconData(0xe0c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">call_to_action</i> &#x2014; material icon named "call to action rounded".
+  static const IconData call_to_action_rounded = IconData(0xf0ef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">call_to_action</i> &#x2014; material icon named "call to action sharp".
+  static const IconData call_to_action_sharp = IconData(0xebc1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">camera</i> &#x2014; material icon named "camera".
+  static const IconData camera = IconData(0xe62e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">camera_alt</i> &#x2014; material icon named "camera alt".
+  static const IconData camera_alt = IconData(0xe62f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">camera_alt</i> &#x2014; material icon named "camera alt outlined".
+  static const IconData camera_alt_outlined = IconData(0xe0c2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">camera_alt</i> &#x2014; material icon named "camera alt rounded".
+  static const IconData camera_alt_rounded = IconData(0xf0f0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">camera_alt</i> &#x2014; material icon named "camera alt sharp".
+  static const IconData camera_alt_sharp = IconData(0xebc2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">camera_enhance</i> &#x2014; material icon named "camera enhance".
+  static const IconData camera_enhance = IconData(0xe630, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">camera_enhance</i> &#x2014; material icon named "camera enhance outlined".
+  static const IconData camera_enhance_outlined = IconData(0xe0c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">camera_enhance</i> &#x2014; material icon named "camera enhance rounded".
+  static const IconData camera_enhance_rounded = IconData(0xf0f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">camera_enhance</i> &#x2014; material icon named "camera enhance sharp".
+  static const IconData camera_enhance_sharp = IconData(0xebc3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">camera_front</i> &#x2014; material icon named "camera front".
+  static const IconData camera_front = IconData(0xe631, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">camera_front</i> &#x2014; material icon named "camera front outlined".
+  static const IconData camera_front_outlined = IconData(0xe0c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">camera_front</i> &#x2014; material icon named "camera front rounded".
+  static const IconData camera_front_rounded = IconData(0xf0f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">camera_front</i> &#x2014; material icon named "camera front sharp".
+  static const IconData camera_front_sharp = IconData(0xebc4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">camera</i> &#x2014; material icon named "camera outlined".
+  static const IconData camera_outlined = IconData(0xe0c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">camera_rear</i> &#x2014; material icon named "camera rear".
+  static const IconData camera_rear = IconData(0xe632, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">camera_rear</i> &#x2014; material icon named "camera rear outlined".
+  static const IconData camera_rear_outlined = IconData(0xe0c6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">camera_rear</i> &#x2014; material icon named "camera rear rounded".
+  static const IconData camera_rear_rounded = IconData(0xf0f3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">camera_rear</i> &#x2014; material icon named "camera rear sharp".
+  static const IconData camera_rear_sharp = IconData(0xebc5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">camera_roll</i> &#x2014; material icon named "camera roll".
+  static const IconData camera_roll = IconData(0xe633, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">camera_roll</i> &#x2014; material icon named "camera roll outlined".
+  static const IconData camera_roll_outlined = IconData(0xe0c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">camera_roll</i> &#x2014; material icon named "camera roll rounded".
+  static const IconData camera_roll_rounded = IconData(0xf0f4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">camera_roll</i> &#x2014; material icon named "camera roll sharp".
+  static const IconData camera_roll_sharp = IconData(0xebc6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">camera</i> &#x2014; material icon named "camera rounded".
+  static const IconData camera_rounded = IconData(0xf0f5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">camera</i> &#x2014; material icon named "camera sharp".
+  static const IconData camera_sharp = IconData(0xebc7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">campaign</i> &#x2014; material icon named "campaign".
+  static const IconData campaign = IconData(0xe634, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">campaign</i> &#x2014; material icon named "campaign outlined".
+  static const IconData campaign_outlined = IconData(0xe0c8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">campaign</i> &#x2014; material icon named "campaign rounded".
+  static const IconData campaign_rounded = IconData(0xf0f6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">campaign</i> &#x2014; material icon named "campaign sharp".
+  static const IconData campaign_sharp = IconData(0xebc8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cancel</i> &#x2014; material icon named "cancel".
+  static const IconData cancel = IconData(0xe635, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cancel</i> &#x2014; material icon named "cancel outlined".
+  static const IconData cancel_outlined = IconData(0xe0c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cancel_presentation</i> &#x2014; material icon named "cancel presentation".
+  static const IconData cancel_presentation = IconData(0xe636, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cancel_presentation</i> &#x2014; material icon named "cancel presentation outlined".
+  static const IconData cancel_presentation_outlined = IconData(0xe0ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cancel_presentation</i> &#x2014; material icon named "cancel presentation rounded".
+  static const IconData cancel_presentation_rounded = IconData(0xf0f7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cancel_presentation</i> &#x2014; material icon named "cancel presentation sharp".
+  static const IconData cancel_presentation_sharp = IconData(0xebc9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cancel</i> &#x2014; material icon named "cancel rounded".
+  static const IconData cancel_rounded = IconData(0xf0f8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cancel_schedule_send</i> &#x2014; material icon named "cancel schedule send".
+  static const IconData cancel_schedule_send = IconData(0xe637, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cancel_schedule_send</i> &#x2014; material icon named "cancel schedule send outlined".
+  static const IconData cancel_schedule_send_outlined = IconData(0xe0cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cancel_schedule_send</i> &#x2014; material icon named "cancel schedule send rounded".
+  static const IconData cancel_schedule_send_rounded = IconData(0xf0f9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cancel_schedule_send</i> &#x2014; material icon named "cancel schedule send sharp".
+  static const IconData cancel_schedule_send_sharp = IconData(0xebca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cancel</i> &#x2014; material icon named "cancel sharp".
+  static const IconData cancel_sharp = IconData(0xebcb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">car_rental</i> &#x2014; material icon named "car rental".
+  static const IconData car_rental = IconData(0xe638, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">car_repair</i> &#x2014; material icon named "car repair".
+  static const IconData car_repair = IconData(0xe639, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">card_giftcard</i> &#x2014; material icon named "card giftcard".
+  static const IconData card_giftcard = IconData(0xe63a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">card_giftcard</i> &#x2014; material icon named "card giftcard outlined".
+  static const IconData card_giftcard_outlined = IconData(0xe0cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">card_giftcard</i> &#x2014; material icon named "card giftcard rounded".
+  static const IconData card_giftcard_rounded = IconData(0xf0fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">card_giftcard</i> &#x2014; material icon named "card giftcard sharp".
+  static const IconData card_giftcard_sharp = IconData(0xebcc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">card_membership</i> &#x2014; material icon named "card membership".
+  static const IconData card_membership = IconData(0xe63b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">card_membership</i> &#x2014; material icon named "card membership outlined".
+  static const IconData card_membership_outlined = IconData(0xe0cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">card_membership</i> &#x2014; material icon named "card membership rounded".
+  static const IconData card_membership_rounded = IconData(0xf0fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">card_membership</i> &#x2014; material icon named "card membership sharp".
+  static const IconData card_membership_sharp = IconData(0xebcd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">card_travel</i> &#x2014; material icon named "card travel".
+  static const IconData card_travel = IconData(0xe63c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">card_travel</i> &#x2014; material icon named "card travel outlined".
+  static const IconData card_travel_outlined = IconData(0xe0ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">card_travel</i> &#x2014; material icon named "card travel rounded".
+  static const IconData card_travel_rounded = IconData(0xf0fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">card_travel</i> &#x2014; material icon named "card travel sharp".
+  static const IconData card_travel_sharp = IconData(0xebce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">carpenter</i> &#x2014; material icon named "carpenter".
+  static const IconData carpenter = IconData(0xe63d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">carpenter</i> &#x2014; material icon named "carpenter outlined".
+  static const IconData carpenter_outlined = IconData(0xe0cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">carpenter</i> &#x2014; material icon named "carpenter rounded".
+  static const IconData carpenter_rounded = IconData(0xf0fd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">carpenter</i> &#x2014; material icon named "carpenter sharp".
+  static const IconData carpenter_sharp = IconData(0xebcf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cases</i> &#x2014; material icon named "cases".
+  static const IconData cases = IconData(0xe63e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">casino</i> &#x2014; material icon named "casino".
+  static const IconData casino = IconData(0xe63f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">casino</i> &#x2014; material icon named "casino outlined".
+  static const IconData casino_outlined = IconData(0xe0d0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">casino</i> &#x2014; material icon named "casino rounded".
+  static const IconData casino_rounded = IconData(0xf0fe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">casino</i> &#x2014; material icon named "casino sharp".
+  static const IconData casino_sharp = IconData(0xebd0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cast</i> &#x2014; material icon named "cast".
+  static const IconData cast = IconData(0xe640, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cast_connected</i> &#x2014; material icon named "cast connected".
+  static const IconData cast_connected = IconData(0xe641, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cast_connected</i> &#x2014; material icon named "cast connected outlined".
+  static const IconData cast_connected_outlined = IconData(0xe0d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cast_connected</i> &#x2014; material icon named "cast connected rounded".
+  static const IconData cast_connected_rounded = IconData(0xf0ff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cast_connected</i> &#x2014; material icon named "cast connected sharp".
+  static const IconData cast_connected_sharp = IconData(0xebd1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cast_for_education</i> &#x2014; material icon named "cast for education".
+  static const IconData cast_for_education = IconData(0xe642, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cast_for_education</i> &#x2014; material icon named "cast for education outlined".
+  static const IconData cast_for_education_outlined = IconData(0xe0d2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cast_for_education</i> &#x2014; material icon named "cast for education rounded".
+  static const IconData cast_for_education_rounded = IconData(0xf100, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cast_for_education</i> &#x2014; material icon named "cast for education sharp".
+  static const IconData cast_for_education_sharp = IconData(0xebd2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cast</i> &#x2014; material icon named "cast outlined".
+  static const IconData cast_outlined = IconData(0xe0d3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cast</i> &#x2014; material icon named "cast rounded".
+  static const IconData cast_rounded = IconData(0xf101, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cast</i> &#x2014; material icon named "cast sharp".
+  static const IconData cast_sharp = IconData(0xebd3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">category</i> &#x2014; material icon named "category".
+  static const IconData category = IconData(0xe643, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">category</i> &#x2014; material icon named "category outlined".
+  static const IconData category_outlined = IconData(0xe0d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">category</i> &#x2014; material icon named "category rounded".
+  static const IconData category_rounded = IconData(0xf102, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">category</i> &#x2014; material icon named "category sharp".
+  static const IconData category_sharp = IconData(0xebd4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">celebration</i> &#x2014; material icon named "celebration".
+  static const IconData celebration = IconData(0xe644, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">center_focus_strong</i> &#x2014; material icon named "center focus strong".
+  static const IconData center_focus_strong = IconData(0xe645, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">center_focus_strong</i> &#x2014; material icon named "center focus strong outlined".
+  static const IconData center_focus_strong_outlined = IconData(0xe0d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">center_focus_strong</i> &#x2014; material icon named "center focus strong rounded".
+  static const IconData center_focus_strong_rounded = IconData(0xf103, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">center_focus_strong</i> &#x2014; material icon named "center focus strong sharp".
+  static const IconData center_focus_strong_sharp = IconData(0xebd5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">center_focus_weak</i> &#x2014; material icon named "center focus weak".
+  static const IconData center_focus_weak = IconData(0xe646, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">center_focus_weak</i> &#x2014; material icon named "center focus weak outlined".
+  static const IconData center_focus_weak_outlined = IconData(0xe0d6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">center_focus_weak</i> &#x2014; material icon named "center focus weak rounded".
+  static const IconData center_focus_weak_rounded = IconData(0xf104, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">center_focus_weak</i> &#x2014; material icon named "center focus weak sharp".
+  static const IconData center_focus_weak_sharp = IconData(0xebd6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">change_history</i> &#x2014; material icon named "change history".
+  static const IconData change_history = IconData(0xe647, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">change_history</i> &#x2014; material icon named "change history outlined".
+  static const IconData change_history_outlined = IconData(0xe0d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">change_history</i> &#x2014; material icon named "change history rounded".
+  static const IconData change_history_rounded = IconData(0xf105, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">change_history</i> &#x2014; material icon named "change history sharp".
+  static const IconData change_history_sharp = IconData(0xebd7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">charging_station</i> &#x2014; material icon named "charging station".
+  static const IconData charging_station = IconData(0xe648, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">charging_station</i> &#x2014; material icon named "charging station outlined".
+  static const IconData charging_station_outlined = IconData(0xe0d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">charging_station</i> &#x2014; material icon named "charging station rounded".
+  static const IconData charging_station_rounded = IconData(0xf106, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">charging_station</i> &#x2014; material icon named "charging station sharp".
+  static const IconData charging_station_sharp = IconData(0xebd8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">chat</i> &#x2014; material icon named "chat".
+  static const IconData chat = IconData(0xe649, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">chat_bubble</i> &#x2014; material icon named "chat bubble".
+  static const IconData chat_bubble = IconData(0xe64a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">chat_bubble_outline</i> &#x2014; material icon named "chat bubble outline".
+  static const IconData chat_bubble_outline = IconData(0xe64b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">chat_bubble_outline</i> &#x2014; material icon named "chat bubble outline outlined".
+  static const IconData chat_bubble_outline_outlined = IconData(0xe0d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">chat_bubble_outline</i> &#x2014; material icon named "chat bubble outline rounded".
+  static const IconData chat_bubble_outline_rounded = IconData(0xf107, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">chat_bubble_outline</i> &#x2014; material icon named "chat bubble outline sharp".
+  static const IconData chat_bubble_outline_sharp = IconData(0xebd9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">chat_bubble</i> &#x2014; material icon named "chat bubble outlined".
+  static const IconData chat_bubble_outlined = IconData(0xe0da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">chat_bubble</i> &#x2014; material icon named "chat bubble rounded".
+  static const IconData chat_bubble_rounded = IconData(0xf108, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">chat_bubble</i> &#x2014; material icon named "chat bubble sharp".
+  static const IconData chat_bubble_sharp = IconData(0xebda, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">chat</i> &#x2014; material icon named "chat outlined".
+  static const IconData chat_outlined = IconData(0xe0db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">chat</i> &#x2014; material icon named "chat rounded".
+  static const IconData chat_rounded = IconData(0xf109, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">chat</i> &#x2014; material icon named "chat sharp".
+  static const IconData chat_sharp = IconData(0xebdb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">check</i> &#x2014; material icon named "check".
+  static const IconData check = IconData(0xe64c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">check_box</i> &#x2014; material icon named "check box".
+  static const IconData check_box = IconData(0xe64d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">check_box_outline_blank</i> &#x2014; material icon named "check box outline blank".
+  static const IconData check_box_outline_blank = IconData(0xe64e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">check_box_outline_blank</i> &#x2014; material icon named "check box outline blank outlined".
+  static const IconData check_box_outline_blank_outlined = IconData(0xe0dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">check_box_outline_blank</i> &#x2014; material icon named "check box outline blank rounded".
+  static const IconData check_box_outline_blank_rounded = IconData(0xf10a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">check_box_outline_blank</i> &#x2014; material icon named "check box outline blank sharp".
+  static const IconData check_box_outline_blank_sharp = IconData(0xebdc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">check_box</i> &#x2014; material icon named "check box outlined".
+  static const IconData check_box_outlined = IconData(0xe0dd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">check_box</i> &#x2014; material icon named "check box rounded".
+  static const IconData check_box_rounded = IconData(0xf10b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">check_box</i> &#x2014; material icon named "check box sharp".
+  static const IconData check_box_sharp = IconData(0xebdd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">check_circle</i> &#x2014; material icon named "check circle".
+  static const IconData check_circle = IconData(0xe64f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">check_circle_outline</i> &#x2014; material icon named "check circle outline".
+  static const IconData check_circle_outline = IconData(0xe650, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">check_circle_outline</i> &#x2014; material icon named "check circle outline outlined".
+  static const IconData check_circle_outline_outlined = IconData(0xe0de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">check_circle_outline</i> &#x2014; material icon named "check circle outline rounded".
+  static const IconData check_circle_outline_rounded = IconData(0xf10c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">check_circle_outline</i> &#x2014; material icon named "check circle outline sharp".
+  static const IconData check_circle_outline_sharp = IconData(0xebde, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">check_circle</i> &#x2014; material icon named "check circle outlined".
+  static const IconData check_circle_outlined = IconData(0xe0df, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">check_circle</i> &#x2014; material icon named "check circle rounded".
+  static const IconData check_circle_rounded = IconData(0xf10d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">check_circle</i> &#x2014; material icon named "check circle sharp".
+  static const IconData check_circle_sharp = IconData(0xebdf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">check</i> &#x2014; material icon named "check outlined".
+  static const IconData check_outlined = IconData(0xe0e0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">check</i> &#x2014; material icon named "check rounded".
+  static const IconData check_rounded = IconData(0xf10e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">check</i> &#x2014; material icon named "check sharp".
+  static const IconData check_sharp = IconData(0xebe0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">checkroom</i> &#x2014; material icon named "checkroom".
+  static const IconData checkroom = IconData(0xe651, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">checkroom</i> &#x2014; material icon named "checkroom outlined".
+  static const IconData checkroom_outlined = IconData(0xe0e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">checkroom</i> &#x2014; material icon named "checkroom rounded".
+  static const IconData checkroom_rounded = IconData(0xf10f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">checkroom</i> &#x2014; material icon named "checkroom sharp".
+  static const IconData checkroom_sharp = IconData(0xebe1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">chevron_left</i> &#x2014; material icon named "chevron left".
+  static const IconData chevron_left = IconData(0xe652, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">chevron_left</i> &#x2014; material icon named "chevron left outlined".
+  static const IconData chevron_left_outlined = IconData(0xe0e2, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">chevron_left</i> &#x2014; material icon named "chevron left rounded".
+  static const IconData chevron_left_rounded = IconData(0xf110, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">chevron_left</i> &#x2014; material icon named "chevron left sharp".
+  static const IconData chevron_left_sharp = IconData(0xebe2, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">chevron_right</i> &#x2014; material icon named "chevron right".
+  static const IconData chevron_right = IconData(0xe653, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">chevron_right</i> &#x2014; material icon named "chevron right outlined".
+  static const IconData chevron_right_outlined = IconData(0xe0e3, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">chevron_right</i> &#x2014; material icon named "chevron right rounded".
+  static const IconData chevron_right_rounded = IconData(0xf111, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">chevron_right</i> &#x2014; material icon named "chevron right sharp".
+  static const IconData chevron_right_sharp = IconData(0xebe3, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">child_care</i> &#x2014; material icon named "child care".
+  static const IconData child_care = IconData(0xe654, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">child_care</i> &#x2014; material icon named "child care outlined".
+  static const IconData child_care_outlined = IconData(0xe0e4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">child_care</i> &#x2014; material icon named "child care rounded".
+  static const IconData child_care_rounded = IconData(0xf112, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">child_care</i> &#x2014; material icon named "child care sharp".
+  static const IconData child_care_sharp = IconData(0xebe4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">child_friendly</i> &#x2014; material icon named "child friendly".
+  static const IconData child_friendly = IconData(0xe655, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">child_friendly</i> &#x2014; material icon named "child friendly outlined".
+  static const IconData child_friendly_outlined = IconData(0xe0e5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">child_friendly</i> &#x2014; material icon named "child friendly rounded".
+  static const IconData child_friendly_rounded = IconData(0xf113, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">child_friendly</i> &#x2014; material icon named "child friendly sharp".
+  static const IconData child_friendly_sharp = IconData(0xebe5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">chrome_reader_mode</i> &#x2014; material icon named "chrome reader mode".
+  static const IconData chrome_reader_mode = IconData(0xe656, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">chrome_reader_mode</i> &#x2014; material icon named "chrome reader mode outlined".
+  static const IconData chrome_reader_mode_outlined = IconData(0xe0e6, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">chrome_reader_mode</i> &#x2014; material icon named "chrome reader mode rounded".
+  static const IconData chrome_reader_mode_rounded = IconData(0xf114, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">chrome_reader_mode</i> &#x2014; material icon named "chrome reader mode sharp".
+  static const IconData chrome_reader_mode_sharp = IconData(0xebe6, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">circle</i> &#x2014; material icon named "circle".
+  static const IconData circle = IconData(0xe657, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">circle_notifications</i> &#x2014; material icon named "circle notifications".
+  static const IconData circle_notifications = IconData(0xe658, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">class</i> &#x2014; material icon named "class".
+  static const IconData class_ = IconData(0xe659, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">class</i> &#x2014; material icon named "class outlined".
+  static const IconData class__outlined = IconData(0xe0e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">class</i> &#x2014; material icon named "class rounded".
+  static const IconData class__rounded = IconData(0xf115, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">class</i> &#x2014; material icon named "class sharp".
+  static const IconData class__sharp = IconData(0xebe7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">clean_hands</i> &#x2014; material icon named "clean hands".
+  static const IconData clean_hands = IconData(0xe65a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">clean_hands</i> &#x2014; material icon named "clean hands outlined".
+  static const IconData clean_hands_outlined = IconData(0xe0e8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">clean_hands</i> &#x2014; material icon named "clean hands rounded".
+  static const IconData clean_hands_rounded = IconData(0xf116, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">clean_hands</i> &#x2014; material icon named "clean hands sharp".
+  static const IconData clean_hands_sharp = IconData(0xebe8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cleaning_services</i> &#x2014; material icon named "cleaning services".
+  static const IconData cleaning_services = IconData(0xe65b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cleaning_services</i> &#x2014; material icon named "cleaning services outlined".
+  static const IconData cleaning_services_outlined = IconData(0xe0e9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cleaning_services</i> &#x2014; material icon named "cleaning services rounded".
+  static const IconData cleaning_services_rounded = IconData(0xf117, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cleaning_services</i> &#x2014; material icon named "cleaning services sharp".
+  static const IconData cleaning_services_sharp = IconData(0xebe9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">clear</i> &#x2014; material icon named "clear".
+  static const IconData clear = IconData(0xe65c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">clear_all</i> &#x2014; material icon named "clear all".
+  static const IconData clear_all = IconData(0xe65d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">clear_all</i> &#x2014; material icon named "clear all outlined".
+  static const IconData clear_all_outlined = IconData(0xe0ea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">clear_all</i> &#x2014; material icon named "clear all rounded".
+  static const IconData clear_all_rounded = IconData(0xf118, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">clear_all</i> &#x2014; material icon named "clear all sharp".
+  static const IconData clear_all_sharp = IconData(0xebea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">clear</i> &#x2014; material icon named "clear outlined".
+  static const IconData clear_outlined = IconData(0xe0eb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">clear</i> &#x2014; material icon named "clear rounded".
+  static const IconData clear_rounded = IconData(0xf119, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">clear</i> &#x2014; material icon named "clear sharp".
+  static const IconData clear_sharp = IconData(0xebeb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">close</i> &#x2014; material icon named "close".
+  static const IconData close = IconData(0xe65e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">close_fullscreen</i> &#x2014; material icon named "close fullscreen".
+  static const IconData close_fullscreen = IconData(0xe65f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">close_fullscreen</i> &#x2014; material icon named "close fullscreen outlined".
+  static const IconData close_fullscreen_outlined = IconData(0xe0ec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">close_fullscreen</i> &#x2014; material icon named "close fullscreen rounded".
+  static const IconData close_fullscreen_rounded = IconData(0xf11a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">close_fullscreen</i> &#x2014; material icon named "close fullscreen sharp".
+  static const IconData close_fullscreen_sharp = IconData(0xebec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">close</i> &#x2014; material icon named "close outlined".
+  static const IconData close_outlined = IconData(0xe0ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">close</i> &#x2014; material icon named "close rounded".
+  static const IconData close_rounded = IconData(0xf11b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">close</i> &#x2014; material icon named "close sharp".
+  static const IconData close_sharp = IconData(0xebed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">closed_caption</i> &#x2014; material icon named "closed caption".
+  static const IconData closed_caption = IconData(0xe660, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">closed_caption_disabled</i> &#x2014; material icon named "closed caption disabled".
+  static const IconData closed_caption_disabled = IconData(0xe661, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">closed_caption_disabled</i> &#x2014; material icon named "closed caption disabled outlined".
+  static const IconData closed_caption_disabled_outlined = IconData(0xe0ee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">closed_caption_disabled</i> &#x2014; material icon named "closed caption disabled rounded".
+  static const IconData closed_caption_disabled_rounded = IconData(0xf11c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">closed_caption_disabled</i> &#x2014; material icon named "closed caption disabled sharp".
+  static const IconData closed_caption_disabled_sharp = IconData(0xebee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">closed_caption_off</i> &#x2014; material icon named "closed caption off".
+  static const IconData closed_caption_off = IconData(0xe662, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">closed_caption</i> &#x2014; material icon named "closed caption outlined".
+  static const IconData closed_caption_outlined = IconData(0xe0ef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">closed_caption</i> &#x2014; material icon named "closed caption rounded".
+  static const IconData closed_caption_rounded = IconData(0xf11d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">closed_caption</i> &#x2014; material icon named "closed caption sharp".
+  static const IconData closed_caption_sharp = IconData(0xebef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cloud</i> &#x2014; material icon named "cloud".
+  static const IconData cloud = IconData(0xe663, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cloud_circle</i> &#x2014; material icon named "cloud circle".
+  static const IconData cloud_circle = IconData(0xe664, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cloud_circle</i> &#x2014; material icon named "cloud circle outlined".
+  static const IconData cloud_circle_outlined = IconData(0xe0f0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cloud_circle</i> &#x2014; material icon named "cloud circle rounded".
+  static const IconData cloud_circle_rounded = IconData(0xf11e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cloud_circle</i> &#x2014; material icon named "cloud circle sharp".
+  static const IconData cloud_circle_sharp = IconData(0xebf0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cloud_done</i> &#x2014; material icon named "cloud done".
+  static const IconData cloud_done = IconData(0xe665, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cloud_done</i> &#x2014; material icon named "cloud done outlined".
+  static const IconData cloud_done_outlined = IconData(0xe0f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cloud_done</i> &#x2014; material icon named "cloud done rounded".
+  static const IconData cloud_done_rounded = IconData(0xf11f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cloud_done</i> &#x2014; material icon named "cloud done sharp".
+  static const IconData cloud_done_sharp = IconData(0xebf1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cloud_download</i> &#x2014; material icon named "cloud download".
+  static const IconData cloud_download = IconData(0xe666, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cloud_download</i> &#x2014; material icon named "cloud download outlined".
+  static const IconData cloud_download_outlined = IconData(0xe0f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cloud_download</i> &#x2014; material icon named "cloud download rounded".
+  static const IconData cloud_download_rounded = IconData(0xf120, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cloud_download</i> &#x2014; material icon named "cloud download sharp".
+  static const IconData cloud_download_sharp = IconData(0xebf2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cloud_off</i> &#x2014; material icon named "cloud off".
+  static const IconData cloud_off = IconData(0xe667, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cloud_off</i> &#x2014; material icon named "cloud off outlined".
+  static const IconData cloud_off_outlined = IconData(0xe0f3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cloud_off</i> &#x2014; material icon named "cloud off rounded".
+  static const IconData cloud_off_rounded = IconData(0xf121, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cloud_off</i> &#x2014; material icon named "cloud off sharp".
+  static const IconData cloud_off_sharp = IconData(0xebf3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cloud</i> &#x2014; material icon named "cloud outlined".
+  static const IconData cloud_outlined = IconData(0xe0f4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cloud_queue</i> &#x2014; material icon named "cloud queue".
+  static const IconData cloud_queue = IconData(0xe668, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cloud_queue</i> &#x2014; material icon named "cloud queue outlined".
+  static const IconData cloud_queue_outlined = IconData(0xe0f5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cloud_queue</i> &#x2014; material icon named "cloud queue rounded".
+  static const IconData cloud_queue_rounded = IconData(0xf122, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cloud_queue</i> &#x2014; material icon named "cloud queue sharp".
+  static const IconData cloud_queue_sharp = IconData(0xebf4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cloud</i> &#x2014; material icon named "cloud rounded".
+  static const IconData cloud_rounded = IconData(0xf123, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cloud</i> &#x2014; material icon named "cloud sharp".
+  static const IconData cloud_sharp = IconData(0xebf5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cloud_upload</i> &#x2014; material icon named "cloud upload".
+  static const IconData cloud_upload = IconData(0xe669, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cloud_upload</i> &#x2014; material icon named "cloud upload outlined".
+  static const IconData cloud_upload_outlined = IconData(0xe0f6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cloud_upload</i> &#x2014; material icon named "cloud upload rounded".
+  static const IconData cloud_upload_rounded = IconData(0xf124, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cloud_upload</i> &#x2014; material icon named "cloud upload sharp".
+  static const IconData cloud_upload_sharp = IconData(0xebf6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">code</i> &#x2014; material icon named "code".
+  static const IconData code = IconData(0xe66a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">code</i> &#x2014; material icon named "code outlined".
+  static const IconData code_outlined = IconData(0xe0f7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">code</i> &#x2014; material icon named "code rounded".
+  static const IconData code_rounded = IconData(0xf125, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">code</i> &#x2014; material icon named "code sharp".
+  static const IconData code_sharp = IconData(0xebf7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">collections</i> &#x2014; material icon named "collections".
+  static const IconData collections = IconData(0xe66b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">collections_bookmark</i> &#x2014; material icon named "collections bookmark".
+  static const IconData collections_bookmark = IconData(0xe66c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">collections_bookmark</i> &#x2014; material icon named "collections bookmark outlined".
+  static const IconData collections_bookmark_outlined = IconData(0xe0f8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">collections_bookmark</i> &#x2014; material icon named "collections bookmark rounded".
+  static const IconData collections_bookmark_rounded = IconData(0xf126, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">collections_bookmark</i> &#x2014; material icon named "collections bookmark sharp".
+  static const IconData collections_bookmark_sharp = IconData(0xebf8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">collections</i> &#x2014; material icon named "collections outlined".
+  static const IconData collections_outlined = IconData(0xe0f9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">collections</i> &#x2014; material icon named "collections rounded".
+  static const IconData collections_rounded = IconData(0xf127, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">collections</i> &#x2014; material icon named "collections sharp".
+  static const IconData collections_sharp = IconData(0xebf9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">color_lens</i> &#x2014; material icon named "color lens".
+  static const IconData color_lens = IconData(0xe66d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">color_lens</i> &#x2014; material icon named "color lens outlined".
+  static const IconData color_lens_outlined = IconData(0xe0fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">color_lens</i> &#x2014; material icon named "color lens rounded".
+  static const IconData color_lens_rounded = IconData(0xf128, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">color_lens</i> &#x2014; material icon named "color lens sharp".
+  static const IconData color_lens_sharp = IconData(0xebfa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">colorize</i> &#x2014; material icon named "colorize".
+  static const IconData colorize = IconData(0xe66e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">colorize</i> &#x2014; material icon named "colorize outlined".
+  static const IconData colorize_outlined = IconData(0xe0fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">colorize</i> &#x2014; material icon named "colorize rounded".
+  static const IconData colorize_rounded = IconData(0xf129, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">colorize</i> &#x2014; material icon named "colorize sharp".
+  static const IconData colorize_sharp = IconData(0xebfb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">comment</i> &#x2014; material icon named "comment".
+  static const IconData comment = IconData(0xe66f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">comment_bank</i> &#x2014; material icon named "comment bank".
+  static const IconData comment_bank = IconData(0xe670, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">comment_bank</i> &#x2014; material icon named "comment bank outlined".
+  static const IconData comment_bank_outlined = IconData(0xe0fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">comment_bank</i> &#x2014; material icon named "comment bank rounded".
+  static const IconData comment_bank_rounded = IconData(0xf12a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">comment_bank</i> &#x2014; material icon named "comment bank sharp".
+  static const IconData comment_bank_sharp = IconData(0xebfc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">comment</i> &#x2014; material icon named "comment outlined".
+  static const IconData comment_outlined = IconData(0xe0fd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">comment</i> &#x2014; material icon named "comment rounded".
+  static const IconData comment_rounded = IconData(0xf12b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">comment</i> &#x2014; material icon named "comment sharp".
+  static const IconData comment_sharp = IconData(0xebfd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">commute</i> &#x2014; material icon named "commute".
+  static const IconData commute = IconData(0xe671, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">commute</i> &#x2014; material icon named "commute outlined".
+  static const IconData commute_outlined = IconData(0xe0fe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">commute</i> &#x2014; material icon named "commute rounded".
+  static const IconData commute_rounded = IconData(0xf12c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">commute</i> &#x2014; material icon named "commute sharp".
+  static const IconData commute_sharp = IconData(0xebfe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">compare</i> &#x2014; material icon named "compare".
+  static const IconData compare = IconData(0xe672, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">compare_arrows</i> &#x2014; material icon named "compare arrows".
+  static const IconData compare_arrows = IconData(0xe673, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">compare_arrows</i> &#x2014; material icon named "compare arrows outlined".
+  static const IconData compare_arrows_outlined = IconData(0xe0ff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">compare_arrows</i> &#x2014; material icon named "compare arrows rounded".
+  static const IconData compare_arrows_rounded = IconData(0xf12d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">compare_arrows</i> &#x2014; material icon named "compare arrows sharp".
+  static const IconData compare_arrows_sharp = IconData(0xebff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">compare</i> &#x2014; material icon named "compare outlined".
+  static const IconData compare_outlined = IconData(0xe100, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">compare</i> &#x2014; material icon named "compare rounded".
+  static const IconData compare_rounded = IconData(0xf12e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">compare</i> &#x2014; material icon named "compare sharp".
+  static const IconData compare_sharp = IconData(0xec00, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">compass_calibration</i> &#x2014; material icon named "compass calibration".
+  static const IconData compass_calibration = IconData(0xe674, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">compass_calibration</i> &#x2014; material icon named "compass calibration outlined".
+  static const IconData compass_calibration_outlined = IconData(0xe101, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">compass_calibration</i> &#x2014; material icon named "compass calibration rounded".
+  static const IconData compass_calibration_rounded = IconData(0xf12f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">compass_calibration</i> &#x2014; material icon named "compass calibration sharp".
+  static const IconData compass_calibration_sharp = IconData(0xec01, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">compress</i> &#x2014; material icon named "compress".
+  static const IconData compress = IconData(0xe675, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">computer</i> &#x2014; material icon named "computer".
+  static const IconData computer = IconData(0xe676, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">computer</i> &#x2014; material icon named "computer outlined".
+  static const IconData computer_outlined = IconData(0xe102, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">computer</i> &#x2014; material icon named "computer rounded".
+  static const IconData computer_rounded = IconData(0xf130, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">computer</i> &#x2014; material icon named "computer sharp".
+  static const IconData computer_sharp = IconData(0xec02, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">confirmation_num</i> &#x2014; material icon named "confirmation num".
+  static const IconData confirmation_num = IconData(0xe677, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">confirmation_num</i> &#x2014; material icon named "confirmation num outlined".
+  static const IconData confirmation_num_outlined = IconData(0xe103, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">confirmation_num</i> &#x2014; material icon named "confirmation num rounded".
+  static const IconData confirmation_num_rounded = IconData(0xf131, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">confirmation_num</i> &#x2014; material icon named "confirmation num sharp".
+  static const IconData confirmation_num_sharp = IconData(0xec03, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">confirmation_number</i> &#x2014; material icon named "confirmation number".
+  static const IconData confirmation_number = IconData(0xe677, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">confirmation_number</i> &#x2014; material icon named "confirmation number outlined".
+  static const IconData confirmation_number_outlined = IconData(0xe103, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">confirmation_number</i> &#x2014; material icon named "confirmation number rounded".
+  static const IconData confirmation_number_rounded = IconData(0xf131, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">confirmation_number</i> &#x2014; material icon named "confirmation number sharp".
+  static const IconData confirmation_number_sharp = IconData(0xec03, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">connect_without_contact</i> &#x2014; material icon named "connect without contact".
+  static const IconData connect_without_contact = IconData(0xe678, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">connect_without_contact</i> &#x2014; material icon named "connect without contact outlined".
+  static const IconData connect_without_contact_outlined = IconData(0xe104, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">connect_without_contact</i> &#x2014; material icon named "connect without contact rounded".
+  static const IconData connect_without_contact_rounded = IconData(0xf132, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">connect_without_contact</i> &#x2014; material icon named "connect without contact sharp".
+  static const IconData connect_without_contact_sharp = IconData(0xec04, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">connected_tv</i> &#x2014; material icon named "connected tv".
+  static const IconData connected_tv = IconData(0xe679, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">construction</i> &#x2014; material icon named "construction".
+  static const IconData construction = IconData(0xe67a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">construction</i> &#x2014; material icon named "construction outlined".
+  static const IconData construction_outlined = IconData(0xe105, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">construction</i> &#x2014; material icon named "construction rounded".
+  static const IconData construction_rounded = IconData(0xf133, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">construction</i> &#x2014; material icon named "construction sharp".
+  static const IconData construction_sharp = IconData(0xec05, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">contact_mail</i> &#x2014; material icon named "contact mail".
+  static const IconData contact_mail = IconData(0xe67b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">contact_mail</i> &#x2014; material icon named "contact mail outlined".
+  static const IconData contact_mail_outlined = IconData(0xe106, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">contact_mail</i> &#x2014; material icon named "contact mail rounded".
+  static const IconData contact_mail_rounded = IconData(0xf134, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">contact_mail</i> &#x2014; material icon named "contact mail sharp".
+  static const IconData contact_mail_sharp = IconData(0xec06, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">contact_page</i> &#x2014; material icon named "contact page".
+  static const IconData contact_page = IconData(0xe67c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">contact_page</i> &#x2014; material icon named "contact page outlined".
+  static const IconData contact_page_outlined = IconData(0xe107, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">contact_page</i> &#x2014; material icon named "contact page rounded".
+  static const IconData contact_page_rounded = IconData(0xf135, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">contact_page</i> &#x2014; material icon named "contact page sharp".
+  static const IconData contact_page_sharp = IconData(0xec07, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">contact_phone</i> &#x2014; material icon named "contact phone".
+  static const IconData contact_phone = IconData(0xe67d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">contact_phone</i> &#x2014; material icon named "contact phone outlined".
+  static const IconData contact_phone_outlined = IconData(0xe108, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">contact_phone</i> &#x2014; material icon named "contact phone rounded".
+  static const IconData contact_phone_rounded = IconData(0xf136, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">contact_phone</i> &#x2014; material icon named "contact phone sharp".
+  static const IconData contact_phone_sharp = IconData(0xec08, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">contact_support</i> &#x2014; material icon named "contact support".
+  static const IconData contact_support = IconData(0xe67e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">contact_support</i> &#x2014; material icon named "contact support outlined".
+  static const IconData contact_support_outlined = IconData(0xe109, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">contact_support</i> &#x2014; material icon named "contact support rounded".
+  static const IconData contact_support_rounded = IconData(0xf137, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">contact_support</i> &#x2014; material icon named "contact support sharp".
+  static const IconData contact_support_sharp = IconData(0xec09, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">contactless</i> &#x2014; material icon named "contactless".
+  static const IconData contactless = IconData(0xe67f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">contactless</i> &#x2014; material icon named "contactless outlined".
+  static const IconData contactless_outlined = IconData(0xe10a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">contactless</i> &#x2014; material icon named "contactless rounded".
+  static const IconData contactless_rounded = IconData(0xf138, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">contactless</i> &#x2014; material icon named "contactless sharp".
+  static const IconData contactless_sharp = IconData(0xec0a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">contacts</i> &#x2014; material icon named "contacts".
+  static const IconData contacts = IconData(0xe680, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">contacts</i> &#x2014; material icon named "contacts outlined".
+  static const IconData contacts_outlined = IconData(0xe10b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">contacts</i> &#x2014; material icon named "contacts rounded".
+  static const IconData contacts_rounded = IconData(0xf139, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">contacts</i> &#x2014; material icon named "contacts sharp".
+  static const IconData contacts_sharp = IconData(0xec0b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">content_copy</i> &#x2014; material icon named "content copy".
+  static const IconData content_copy = IconData(0xe681, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">content_copy</i> &#x2014; material icon named "content copy outlined".
+  static const IconData content_copy_outlined = IconData(0xe10c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">content_copy</i> &#x2014; material icon named "content copy rounded".
+  static const IconData content_copy_rounded = IconData(0xf13a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">content_copy</i> &#x2014; material icon named "content copy sharp".
+  static const IconData content_copy_sharp = IconData(0xec0c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">content_cut</i> &#x2014; material icon named "content cut".
+  static const IconData content_cut = IconData(0xe682, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">content_cut</i> &#x2014; material icon named "content cut outlined".
+  static const IconData content_cut_outlined = IconData(0xe10d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">content_cut</i> &#x2014; material icon named "content cut rounded".
+  static const IconData content_cut_rounded = IconData(0xf13b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">content_cut</i> &#x2014; material icon named "content cut sharp".
+  static const IconData content_cut_sharp = IconData(0xec0d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">content_paste</i> &#x2014; material icon named "content paste".
+  static const IconData content_paste = IconData(0xe683, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">content_paste</i> &#x2014; material icon named "content paste outlined".
+  static const IconData content_paste_outlined = IconData(0xe10e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">content_paste</i> &#x2014; material icon named "content paste rounded".
+  static const IconData content_paste_rounded = IconData(0xf13c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">content_paste</i> &#x2014; material icon named "content paste sharp".
+  static const IconData content_paste_sharp = IconData(0xec0e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">control_camera</i> &#x2014; material icon named "control camera".
+  static const IconData control_camera = IconData(0xe684, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">control_camera</i> &#x2014; material icon named "control camera outlined".
+  static const IconData control_camera_outlined = IconData(0xe10f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">control_camera</i> &#x2014; material icon named "control camera rounded".
+  static const IconData control_camera_rounded = IconData(0xf13d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">control_camera</i> &#x2014; material icon named "control camera sharp".
+  static const IconData control_camera_sharp = IconData(0xec0f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">control_point</i> &#x2014; material icon named "control point".
+  static const IconData control_point = IconData(0xe685, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">control_point_duplicate</i> &#x2014; material icon named "control point duplicate".
+  static const IconData control_point_duplicate = IconData(0xe686, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">control_point_duplicate</i> &#x2014; material icon named "control point duplicate outlined".
+  static const IconData control_point_duplicate_outlined = IconData(0xe110, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">control_point_duplicate</i> &#x2014; material icon named "control point duplicate rounded".
+  static const IconData control_point_duplicate_rounded = IconData(0xf13e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">control_point_duplicate</i> &#x2014; material icon named "control point duplicate sharp".
+  static const IconData control_point_duplicate_sharp = IconData(0xec10, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">control_point</i> &#x2014; material icon named "control point outlined".
+  static const IconData control_point_outlined = IconData(0xe111, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">control_point</i> &#x2014; material icon named "control point rounded".
+  static const IconData control_point_rounded = IconData(0xf13f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">control_point</i> &#x2014; material icon named "control point sharp".
+  static const IconData control_point_sharp = IconData(0xec11, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">copy</i> &#x2014; material icon named "copy".
+  static const IconData copy = IconData(0xe681, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">copy</i> &#x2014; material icon named "copy outlined".
+  static const IconData copy_outlined = IconData(0xe10c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">copy</i> &#x2014; material icon named "copy rounded".
+  static const IconData copy_rounded = IconData(0xf13a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">copy</i> &#x2014; material icon named "copy sharp".
+  static const IconData copy_sharp = IconData(0xec0c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">copyright</i> &#x2014; material icon named "copyright".
+  static const IconData copyright = IconData(0xe687, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">copyright</i> &#x2014; material icon named "copyright outlined".
+  static const IconData copyright_outlined = IconData(0xe112, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">copyright</i> &#x2014; material icon named "copyright rounded".
+  static const IconData copyright_rounded = IconData(0xf140, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">copyright</i> &#x2014; material icon named "copyright sharp".
+  static const IconData copyright_sharp = IconData(0xec12, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">coronavirus</i> &#x2014; material icon named "coronavirus".
+  static const IconData coronavirus = IconData(0xe688, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">coronavirus</i> &#x2014; material icon named "coronavirus outlined".
+  static const IconData coronavirus_outlined = IconData(0xe113, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">coronavirus</i> &#x2014; material icon named "coronavirus rounded".
+  static const IconData coronavirus_rounded = IconData(0xf141, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">coronavirus</i> &#x2014; material icon named "coronavirus sharp".
+  static const IconData coronavirus_sharp = IconData(0xec13, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">corporate_fare</i> &#x2014; material icon named "corporate fare".
+  static const IconData corporate_fare = IconData(0xe689, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">corporate_fare</i> &#x2014; material icon named "corporate fare outlined".
+  static const IconData corporate_fare_outlined = IconData(0xe114, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">corporate_fare</i> &#x2014; material icon named "corporate fare rounded".
+  static const IconData corporate_fare_rounded = IconData(0xf142, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">corporate_fare</i> &#x2014; material icon named "corporate fare sharp".
+  static const IconData corporate_fare_sharp = IconData(0xec14, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">countertops</i> &#x2014; material icon named "countertops".
+  static const IconData countertops = IconData(0xe68a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">countertops</i> &#x2014; material icon named "countertops outlined".
+  static const IconData countertops_outlined = IconData(0xe115, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">countertops</i> &#x2014; material icon named "countertops rounded".
+  static const IconData countertops_rounded = IconData(0xf143, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">countertops</i> &#x2014; material icon named "countertops sharp".
+  static const IconData countertops_sharp = IconData(0xec15, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">create</i> &#x2014; material icon named "create".
+  static const IconData create = IconData(0xe68b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">create_new_folder</i> &#x2014; material icon named "create new folder".
+  static const IconData create_new_folder = IconData(0xe68c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">create_new_folder</i> &#x2014; material icon named "create new folder outlined".
+  static const IconData create_new_folder_outlined = IconData(0xe116, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">create_new_folder</i> &#x2014; material icon named "create new folder rounded".
+  static const IconData create_new_folder_rounded = IconData(0xf144, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">create_new_folder</i> &#x2014; material icon named "create new folder sharp".
+  static const IconData create_new_folder_sharp = IconData(0xec16, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">create</i> &#x2014; material icon named "create outlined".
+  static const IconData create_outlined = IconData(0xe117, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">create</i> &#x2014; material icon named "create rounded".
+  static const IconData create_rounded = IconData(0xf145, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">create</i> &#x2014; material icon named "create sharp".
+  static const IconData create_sharp = IconData(0xec17, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">credit_card</i> &#x2014; material icon named "credit card".
+  static const IconData credit_card = IconData(0xe68d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">credit_card</i> &#x2014; material icon named "credit card outlined".
+  static const IconData credit_card_outlined = IconData(0xe118, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">credit_card</i> &#x2014; material icon named "credit card rounded".
+  static const IconData credit_card_rounded = IconData(0xf146, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">credit_card</i> &#x2014; material icon named "credit card sharp".
+  static const IconData credit_card_sharp = IconData(0xec18, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">crop</i> &#x2014; material icon named "crop".
+  static const IconData crop = IconData(0xe68e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">crop_16_9</i> &#x2014; material icon named "crop 16 9".
+  static const IconData crop_16_9 = IconData(0xe68f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">crop_16_9</i> &#x2014; material icon named "crop 16 9 outlined".
+  static const IconData crop_16_9_outlined = IconData(0xe119, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">crop_16_9</i> &#x2014; material icon named "crop 16 9 rounded".
+  static const IconData crop_16_9_rounded = IconData(0xf147, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">crop_16_9</i> &#x2014; material icon named "crop 16 9 sharp".
+  static const IconData crop_16_9_sharp = IconData(0xec19, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">crop_3_2</i> &#x2014; material icon named "crop 3 2".
+  static const IconData crop_3_2 = IconData(0xe690, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">crop_3_2</i> &#x2014; material icon named "crop 3 2 outlined".
+  static const IconData crop_3_2_outlined = IconData(0xe11a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">crop_3_2</i> &#x2014; material icon named "crop 3 2 rounded".
+  static const IconData crop_3_2_rounded = IconData(0xf148, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">crop_3_2</i> &#x2014; material icon named "crop 3 2 sharp".
+  static const IconData crop_3_2_sharp = IconData(0xec1a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">crop_5_4</i> &#x2014; material icon named "crop 5 4".
+  static const IconData crop_5_4 = IconData(0xe691, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">crop_5_4</i> &#x2014; material icon named "crop 5 4 outlined".
+  static const IconData crop_5_4_outlined = IconData(0xe11b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">crop_5_4</i> &#x2014; material icon named "crop 5 4 rounded".
+  static const IconData crop_5_4_rounded = IconData(0xf149, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">crop_5_4</i> &#x2014; material icon named "crop 5 4 sharp".
+  static const IconData crop_5_4_sharp = IconData(0xec1b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">crop_7_5</i> &#x2014; material icon named "crop 7 5".
+  static const IconData crop_7_5 = IconData(0xe692, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">crop_7_5</i> &#x2014; material icon named "crop 7 5 outlined".
+  static const IconData crop_7_5_outlined = IconData(0xe11c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">crop_7_5</i> &#x2014; material icon named "crop 7 5 rounded".
+  static const IconData crop_7_5_rounded = IconData(0xf14a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">crop_7_5</i> &#x2014; material icon named "crop 7 5 sharp".
+  static const IconData crop_7_5_sharp = IconData(0xec1c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">crop_din</i> &#x2014; material icon named "crop din".
+  static const IconData crop_din = IconData(0xe693, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">crop_din</i> &#x2014; material icon named "crop din outlined".
+  static const IconData crop_din_outlined = IconData(0xe11d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">crop_din</i> &#x2014; material icon named "crop din rounded".
+  static const IconData crop_din_rounded = IconData(0xf14b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">crop_din</i> &#x2014; material icon named "crop din sharp".
+  static const IconData crop_din_sharp = IconData(0xec1d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">crop_free</i> &#x2014; material icon named "crop free".
+  static const IconData crop_free = IconData(0xe694, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">crop_free</i> &#x2014; material icon named "crop free outlined".
+  static const IconData crop_free_outlined = IconData(0xe11e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">crop_free</i> &#x2014; material icon named "crop free rounded".
+  static const IconData crop_free_rounded = IconData(0xf14c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">crop_free</i> &#x2014; material icon named "crop free sharp".
+  static const IconData crop_free_sharp = IconData(0xec1e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">crop_landscape</i> &#x2014; material icon named "crop landscape".
+  static const IconData crop_landscape = IconData(0xe695, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">crop_landscape</i> &#x2014; material icon named "crop landscape outlined".
+  static const IconData crop_landscape_outlined = IconData(0xe11f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">crop_landscape</i> &#x2014; material icon named "crop landscape rounded".
+  static const IconData crop_landscape_rounded = IconData(0xf14d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">crop_landscape</i> &#x2014; material icon named "crop landscape sharp".
+  static const IconData crop_landscape_sharp = IconData(0xec1f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">crop_original</i> &#x2014; material icon named "crop original".
+  static const IconData crop_original = IconData(0xe696, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">crop_original</i> &#x2014; material icon named "crop original outlined".
+  static const IconData crop_original_outlined = IconData(0xe120, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">crop_original</i> &#x2014; material icon named "crop original rounded".
+  static const IconData crop_original_rounded = IconData(0xf14e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">crop_original</i> &#x2014; material icon named "crop original sharp".
+  static const IconData crop_original_sharp = IconData(0xec20, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">crop</i> &#x2014; material icon named "crop outlined".
+  static const IconData crop_outlined = IconData(0xe121, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">crop_portrait</i> &#x2014; material icon named "crop portrait".
+  static const IconData crop_portrait = IconData(0xe697, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">crop_portrait</i> &#x2014; material icon named "crop portrait outlined".
+  static const IconData crop_portrait_outlined = IconData(0xe122, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">crop_portrait</i> &#x2014; material icon named "crop portrait rounded".
+  static const IconData crop_portrait_rounded = IconData(0xf14f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">crop_portrait</i> &#x2014; material icon named "crop portrait sharp".
+  static const IconData crop_portrait_sharp = IconData(0xec21, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">crop_rotate</i> &#x2014; material icon named "crop rotate".
+  static const IconData crop_rotate = IconData(0xe698, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">crop_rotate</i> &#x2014; material icon named "crop rotate outlined".
+  static const IconData crop_rotate_outlined = IconData(0xe123, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">crop_rotate</i> &#x2014; material icon named "crop rotate rounded".
+  static const IconData crop_rotate_rounded = IconData(0xf150, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">crop_rotate</i> &#x2014; material icon named "crop rotate sharp".
+  static const IconData crop_rotate_sharp = IconData(0xec22, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">crop</i> &#x2014; material icon named "crop rounded".
+  static const IconData crop_rounded = IconData(0xf151, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">crop</i> &#x2014; material icon named "crop sharp".
+  static const IconData crop_sharp = IconData(0xec23, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">crop_square</i> &#x2014; material icon named "crop square".
+  static const IconData crop_square = IconData(0xe699, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">crop_square</i> &#x2014; material icon named "crop square outlined".
+  static const IconData crop_square_outlined = IconData(0xe124, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">crop_square</i> &#x2014; material icon named "crop square rounded".
+  static const IconData crop_square_rounded = IconData(0xf152, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">crop_square</i> &#x2014; material icon named "crop square sharp".
+  static const IconData crop_square_sharp = IconData(0xec24, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">cut</i> &#x2014; material icon named "cut".
+  static const IconData cut = IconData(0xe682, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">cut</i> &#x2014; material icon named "cut outlined".
+  static const IconData cut_outlined = IconData(0xe10d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">cut</i> &#x2014; material icon named "cut rounded".
+  static const IconData cut_rounded = IconData(0xf13b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">cut</i> &#x2014; material icon named "cut sharp".
+  static const IconData cut_sharp = IconData(0xec0d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dangerous</i> &#x2014; material icon named "dangerous".
+  static const IconData dangerous = IconData(0xe69a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dashboard</i> &#x2014; material icon named "dashboard".
+  static const IconData dashboard = IconData(0xe69b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dashboard_customize</i> &#x2014; material icon named "dashboard customize".
+  static const IconData dashboard_customize = IconData(0xe69c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">dashboard</i> &#x2014; material icon named "dashboard outlined".
+  static const IconData dashboard_outlined = IconData(0xe125, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">dashboard</i> &#x2014; material icon named "dashboard rounded".
+  static const IconData dashboard_rounded = IconData(0xf153, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">dashboard</i> &#x2014; material icon named "dashboard sharp".
+  static const IconData dashboard_sharp = IconData(0xec25, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">data_usage</i> &#x2014; material icon named "data usage".
+  static const IconData data_usage = IconData(0xe69d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">data_usage</i> &#x2014; material icon named "data usage outlined".
+  static const IconData data_usage_outlined = IconData(0xe126, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">data_usage</i> &#x2014; material icon named "data usage rounded".
+  static const IconData data_usage_rounded = IconData(0xf154, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">data_usage</i> &#x2014; material icon named "data usage sharp".
+  static const IconData data_usage_sharp = IconData(0xec26, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">date_range</i> &#x2014; material icon named "date range".
+  static const IconData date_range = IconData(0xe69e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">date_range</i> &#x2014; material icon named "date range outlined".
+  static const IconData date_range_outlined = IconData(0xe127, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">date_range</i> &#x2014; material icon named "date range rounded".
+  static const IconData date_range_rounded = IconData(0xf155, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">date_range</i> &#x2014; material icon named "date range sharp".
+  static const IconData date_range_sharp = IconData(0xec27, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">deck</i> &#x2014; material icon named "deck".
+  static const IconData deck = IconData(0xe69f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">deck</i> &#x2014; material icon named "deck outlined".
+  static const IconData deck_outlined = IconData(0xe128, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">deck</i> &#x2014; material icon named "deck rounded".
+  static const IconData deck_rounded = IconData(0xf156, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">deck</i> &#x2014; material icon named "deck sharp".
+  static const IconData deck_sharp = IconData(0xec28, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dehaze</i> &#x2014; material icon named "dehaze".
+  static const IconData dehaze = IconData(0xe6a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">dehaze</i> &#x2014; material icon named "dehaze outlined".
+  static const IconData dehaze_outlined = IconData(0xe129, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">dehaze</i> &#x2014; material icon named "dehaze rounded".
+  static const IconData dehaze_rounded = IconData(0xf157, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">dehaze</i> &#x2014; material icon named "dehaze sharp".
+  static const IconData dehaze_sharp = IconData(0xec29, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">delete</i> &#x2014; material icon named "delete".
+  static const IconData delete = IconData(0xe6a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">delete_forever</i> &#x2014; material icon named "delete forever".
+  static const IconData delete_forever = IconData(0xe6a2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">delete_forever</i> &#x2014; material icon named "delete forever outlined".
+  static const IconData delete_forever_outlined = IconData(0xe12a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">delete_forever</i> &#x2014; material icon named "delete forever rounded".
+  static const IconData delete_forever_rounded = IconData(0xf158, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">delete_forever</i> &#x2014; material icon named "delete forever sharp".
+  static const IconData delete_forever_sharp = IconData(0xec2a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">delete_outline</i> &#x2014; material icon named "delete outline".
+  static const IconData delete_outline = IconData(0xe6a3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">delete_outline</i> &#x2014; material icon named "delete outline outlined".
+  static const IconData delete_outline_outlined = IconData(0xe12b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">delete_outline</i> &#x2014; material icon named "delete outline rounded".
+  static const IconData delete_outline_rounded = IconData(0xf159, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">delete_outline</i> &#x2014; material icon named "delete outline sharp".
+  static const IconData delete_outline_sharp = IconData(0xec2b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">delete</i> &#x2014; material icon named "delete outlined".
+  static const IconData delete_outlined = IconData(0xe12c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">delete</i> &#x2014; material icon named "delete rounded".
+  static const IconData delete_rounded = IconData(0xf15a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">delete</i> &#x2014; material icon named "delete sharp".
+  static const IconData delete_sharp = IconData(0xec2c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">delete_sweep</i> &#x2014; material icon named "delete sweep".
+  static const IconData delete_sweep = IconData(0xe6a4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">delete_sweep</i> &#x2014; material icon named "delete sweep outlined".
+  static const IconData delete_sweep_outlined = IconData(0xe12d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">delete_sweep</i> &#x2014; material icon named "delete sweep rounded".
+  static const IconData delete_sweep_rounded = IconData(0xf15b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">delete_sweep</i> &#x2014; material icon named "delete sweep sharp".
+  static const IconData delete_sweep_sharp = IconData(0xec2d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">delivery_dining</i> &#x2014; material icon named "delivery dining".
+  static const IconData delivery_dining = IconData(0xe6a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">departure_board</i> &#x2014; material icon named "departure board".
+  static const IconData departure_board = IconData(0xe6a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">departure_board</i> &#x2014; material icon named "departure board outlined".
+  static const IconData departure_board_outlined = IconData(0xe12e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">departure_board</i> &#x2014; material icon named "departure board rounded".
+  static const IconData departure_board_rounded = IconData(0xf15c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">departure_board</i> &#x2014; material icon named "departure board sharp".
+  static const IconData departure_board_sharp = IconData(0xec2e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">description</i> &#x2014; material icon named "description".
+  static const IconData description = IconData(0xe6a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">description</i> &#x2014; material icon named "description outlined".
+  static const IconData description_outlined = IconData(0xe12f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">description</i> &#x2014; material icon named "description rounded".
+  static const IconData description_rounded = IconData(0xf15d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">description</i> &#x2014; material icon named "description sharp".
+  static const IconData description_sharp = IconData(0xec2f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">design_services</i> &#x2014; material icon named "design services".
+  static const IconData design_services = IconData(0xe6a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">design_services</i> &#x2014; material icon named "design services outlined".
+  static const IconData design_services_outlined = IconData(0xe130, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">design_services</i> &#x2014; material icon named "design services rounded".
+  static const IconData design_services_rounded = IconData(0xf15e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">design_services</i> &#x2014; material icon named "design services sharp".
+  static const IconData design_services_sharp = IconData(0xec30, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">desktop_access_disabled</i> &#x2014; material icon named "desktop access disabled".
+  static const IconData desktop_access_disabled = IconData(0xe6a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">desktop_access_disabled</i> &#x2014; material icon named "desktop access disabled outlined".
+  static const IconData desktop_access_disabled_outlined = IconData(0xe131, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">desktop_access_disabled</i> &#x2014; material icon named "desktop access disabled rounded".
+  static const IconData desktop_access_disabled_rounded = IconData(0xf15f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">desktop_access_disabled</i> &#x2014; material icon named "desktop access disabled sharp".
+  static const IconData desktop_access_disabled_sharp = IconData(0xec31, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">desktop_mac</i> &#x2014; material icon named "desktop mac".
+  static const IconData desktop_mac = IconData(0xe6aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">desktop_mac</i> &#x2014; material icon named "desktop mac outlined".
+  static const IconData desktop_mac_outlined = IconData(0xe132, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">desktop_mac</i> &#x2014; material icon named "desktop mac rounded".
+  static const IconData desktop_mac_rounded = IconData(0xf160, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">desktop_mac</i> &#x2014; material icon named "desktop mac sharp".
+  static const IconData desktop_mac_sharp = IconData(0xec32, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">desktop_windows</i> &#x2014; material icon named "desktop windows".
+  static const IconData desktop_windows = IconData(0xe6ab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">desktop_windows</i> &#x2014; material icon named "desktop windows outlined".
+  static const IconData desktop_windows_outlined = IconData(0xe133, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">desktop_windows</i> &#x2014; material icon named "desktop windows rounded".
+  static const IconData desktop_windows_rounded = IconData(0xf161, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">desktop_windows</i> &#x2014; material icon named "desktop windows sharp".
+  static const IconData desktop_windows_sharp = IconData(0xec33, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">details</i> &#x2014; material icon named "details".
+  static const IconData details = IconData(0xe6ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">details</i> &#x2014; material icon named "details outlined".
+  static const IconData details_outlined = IconData(0xe134, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">details</i> &#x2014; material icon named "details rounded".
+  static const IconData details_rounded = IconData(0xf162, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">details</i> &#x2014; material icon named "details sharp".
+  static const IconData details_sharp = IconData(0xec34, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">developer_board</i> &#x2014; material icon named "developer board".
+  static const IconData developer_board = IconData(0xe6ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">developer_board</i> &#x2014; material icon named "developer board outlined".
+  static const IconData developer_board_outlined = IconData(0xe135, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">developer_board</i> &#x2014; material icon named "developer board rounded".
+  static const IconData developer_board_rounded = IconData(0xf163, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">developer_board</i> &#x2014; material icon named "developer board sharp".
+  static const IconData developer_board_sharp = IconData(0xec35, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">developer_mode</i> &#x2014; material icon named "developer mode".
+  static const IconData developer_mode = IconData(0xe6ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">developer_mode</i> &#x2014; material icon named "developer mode outlined".
+  static const IconData developer_mode_outlined = IconData(0xe136, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">developer_mode</i> &#x2014; material icon named "developer mode rounded".
+  static const IconData developer_mode_rounded = IconData(0xf164, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">developer_mode</i> &#x2014; material icon named "developer mode sharp".
+  static const IconData developer_mode_sharp = IconData(0xec36, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">device_hub</i> &#x2014; material icon named "device hub".
+  static const IconData device_hub = IconData(0xe6af, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">device_hub</i> &#x2014; material icon named "device hub outlined".
+  static const IconData device_hub_outlined = IconData(0xe137, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">device_hub</i> &#x2014; material icon named "device hub rounded".
+  static const IconData device_hub_rounded = IconData(0xf165, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">device_hub</i> &#x2014; material icon named "device hub sharp".
+  static const IconData device_hub_sharp = IconData(0xec37, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">device_thermostat</i> &#x2014; material icon named "device thermostat".
+  static const IconData device_thermostat = IconData(0xe6b0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">device_unknown</i> &#x2014; material icon named "device unknown".
+  static const IconData device_unknown = IconData(0xe6b1, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">device_unknown</i> &#x2014; material icon named "device unknown outlined".
+  static const IconData device_unknown_outlined = IconData(0xe138, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">device_unknown</i> &#x2014; material icon named "device unknown rounded".
+  static const IconData device_unknown_rounded = IconData(0xf166, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">device_unknown</i> &#x2014; material icon named "device unknown sharp".
+  static const IconData device_unknown_sharp = IconData(0xec38, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">devices</i> &#x2014; material icon named "devices".
+  static const IconData devices = IconData(0xe6b2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">devices_other</i> &#x2014; material icon named "devices other".
+  static const IconData devices_other = IconData(0xe6b3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">devices_other</i> &#x2014; material icon named "devices other outlined".
+  static const IconData devices_other_outlined = IconData(0xe139, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">devices_other</i> &#x2014; material icon named "devices other rounded".
+  static const IconData devices_other_rounded = IconData(0xf167, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">devices_other</i> &#x2014; material icon named "devices other sharp".
+  static const IconData devices_other_sharp = IconData(0xec39, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">devices</i> &#x2014; material icon named "devices outlined".
+  static const IconData devices_outlined = IconData(0xe13a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">devices</i> &#x2014; material icon named "devices rounded".
+  static const IconData devices_rounded = IconData(0xf168, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">devices</i> &#x2014; material icon named "devices sharp".
+  static const IconData devices_sharp = IconData(0xec3a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dialer_sip</i> &#x2014; material icon named "dialer sip".
+  static const IconData dialer_sip = IconData(0xe6b4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">dialer_sip</i> &#x2014; material icon named "dialer sip outlined".
+  static const IconData dialer_sip_outlined = IconData(0xe13b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">dialer_sip</i> &#x2014; material icon named "dialer sip rounded".
+  static const IconData dialer_sip_rounded = IconData(0xf169, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">dialer_sip</i> &#x2014; material icon named "dialer sip sharp".
+  static const IconData dialer_sip_sharp = IconData(0xec3b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dialpad</i> &#x2014; material icon named "dialpad".
+  static const IconData dialpad = IconData(0xe6b5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">dialpad</i> &#x2014; material icon named "dialpad outlined".
+  static const IconData dialpad_outlined = IconData(0xe13c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">dialpad</i> &#x2014; material icon named "dialpad rounded".
+  static const IconData dialpad_rounded = IconData(0xf16a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">dialpad</i> &#x2014; material icon named "dialpad sharp".
+  static const IconData dialpad_sharp = IconData(0xec3c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dinner_dining</i> &#x2014; material icon named "dinner dining".
+  static const IconData dinner_dining = IconData(0xe6b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">directions</i> &#x2014; material icon named "directions".
+  static const IconData directions = IconData(0xe6b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">directions_bike</i> &#x2014; material icon named "directions bike".
+  static const IconData directions_bike = IconData(0xe6b8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">directions_bike</i> &#x2014; material icon named "directions bike outlined".
+  static const IconData directions_bike_outlined = IconData(0xe13d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">directions_bike</i> &#x2014; material icon named "directions bike rounded".
+  static const IconData directions_bike_rounded = IconData(0xf16b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">directions_bike</i> &#x2014; material icon named "directions bike sharp".
+  static const IconData directions_bike_sharp = IconData(0xec3d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">directions_boat</i> &#x2014; material icon named "directions boat".
+  static const IconData directions_boat = IconData(0xe6b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">directions_boat</i> &#x2014; material icon named "directions boat outlined".
+  static const IconData directions_boat_outlined = IconData(0xe13e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">directions_boat</i> &#x2014; material icon named "directions boat rounded".
+  static const IconData directions_boat_rounded = IconData(0xf16c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">directions_boat</i> &#x2014; material icon named "directions boat sharp".
+  static const IconData directions_boat_sharp = IconData(0xec3e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">directions_bus</i> &#x2014; material icon named "directions bus".
+  static const IconData directions_bus = IconData(0xe6ba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">directions_bus</i> &#x2014; material icon named "directions bus outlined".
+  static const IconData directions_bus_outlined = IconData(0xe13f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">directions_bus</i> &#x2014; material icon named "directions bus rounded".
+  static const IconData directions_bus_rounded = IconData(0xf16d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">directions_bus</i> &#x2014; material icon named "directions bus sharp".
+  static const IconData directions_bus_sharp = IconData(0xec3f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">directions_car</i> &#x2014; material icon named "directions car".
+  static const IconData directions_car = IconData(0xe6bb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">directions_car</i> &#x2014; material icon named "directions car outlined".
+  static const IconData directions_car_outlined = IconData(0xe140, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">directions_car</i> &#x2014; material icon named "directions car rounded".
+  static const IconData directions_car_rounded = IconData(0xf16e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">directions_car</i> &#x2014; material icon named "directions car sharp".
+  static const IconData directions_car_sharp = IconData(0xec40, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">directions_ferry</i> &#x2014; material icon named "directions ferry".
+  static const IconData directions_ferry = IconData(0xe6b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">directions_ferry</i> &#x2014; material icon named "directions ferry outlined".
+  static const IconData directions_ferry_outlined = IconData(0xe13e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">directions_ferry</i> &#x2014; material icon named "directions ferry rounded".
+  static const IconData directions_ferry_rounded = IconData(0xf16c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">directions_ferry</i> &#x2014; material icon named "directions ferry sharp".
+  static const IconData directions_ferry_sharp = IconData(0xec3e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">directions_off</i> &#x2014; material icon named "directions off".
+  static const IconData directions_off = IconData(0xe6bc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">directions_off</i> &#x2014; material icon named "directions off outlined".
+  static const IconData directions_off_outlined = IconData(0xe141, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">directions_off</i> &#x2014; material icon named "directions off rounded".
+  static const IconData directions_off_rounded = IconData(0xf16f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">directions_off</i> &#x2014; material icon named "directions off sharp".
+  static const IconData directions_off_sharp = IconData(0xec41, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">directions</i> &#x2014; material icon named "directions outlined".
+  static const IconData directions_outlined = IconData(0xe142, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">directions_railway</i> &#x2014; material icon named "directions railway".
+  static const IconData directions_railway = IconData(0xe6bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">directions_railway</i> &#x2014; material icon named "directions railway outlined".
+  static const IconData directions_railway_outlined = IconData(0xe143, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">directions_railway</i> &#x2014; material icon named "directions railway rounded".
+  static const IconData directions_railway_rounded = IconData(0xf170, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">directions_railway</i> &#x2014; material icon named "directions railway sharp".
+  static const IconData directions_railway_sharp = IconData(0xec42, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">directions</i> &#x2014; material icon named "directions rounded".
+  static const IconData directions_rounded = IconData(0xf171, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">directions_run</i> &#x2014; material icon named "directions run".
+  static const IconData directions_run = IconData(0xe6be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">directions_run</i> &#x2014; material icon named "directions run outlined".
+  static const IconData directions_run_outlined = IconData(0xe144, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">directions_run</i> &#x2014; material icon named "directions run rounded".
+  static const IconData directions_run_rounded = IconData(0xf172, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">directions_run</i> &#x2014; material icon named "directions run sharp".
+  static const IconData directions_run_sharp = IconData(0xec43, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">directions</i> &#x2014; material icon named "directions sharp".
+  static const IconData directions_sharp = IconData(0xec44, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">directions_subway</i> &#x2014; material icon named "directions subway".
+  static const IconData directions_subway = IconData(0xe6bf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">directions_subway</i> &#x2014; material icon named "directions subway outlined".
+  static const IconData directions_subway_outlined = IconData(0xe145, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">directions_subway</i> &#x2014; material icon named "directions subway rounded".
+  static const IconData directions_subway_rounded = IconData(0xf173, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">directions_subway</i> &#x2014; material icon named "directions subway sharp".
+  static const IconData directions_subway_sharp = IconData(0xec45, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">directions_train</i> &#x2014; material icon named "directions train".
+  static const IconData directions_train = IconData(0xe6bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">directions_train</i> &#x2014; material icon named "directions train outlined".
+  static const IconData directions_train_outlined = IconData(0xe143, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">directions_train</i> &#x2014; material icon named "directions train rounded".
+  static const IconData directions_train_rounded = IconData(0xf170, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">directions_train</i> &#x2014; material icon named "directions train sharp".
+  static const IconData directions_train_sharp = IconData(0xec42, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">directions_transit</i> &#x2014; material icon named "directions transit".
+  static const IconData directions_transit = IconData(0xe6c0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">directions_transit</i> &#x2014; material icon named "directions transit outlined".
+  static const IconData directions_transit_outlined = IconData(0xe146, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">directions_transit</i> &#x2014; material icon named "directions transit rounded".
+  static const IconData directions_transit_rounded = IconData(0xf174, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">directions_transit</i> &#x2014; material icon named "directions transit sharp".
+  static const IconData directions_transit_sharp = IconData(0xec46, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">directions_walk</i> &#x2014; material icon named "directions walk".
+  static const IconData directions_walk = IconData(0xe6c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">directions_walk</i> &#x2014; material icon named "directions walk outlined".
+  static const IconData directions_walk_outlined = IconData(0xe147, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">directions_walk</i> &#x2014; material icon named "directions walk rounded".
+  static const IconData directions_walk_rounded = IconData(0xf175, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">directions_walk</i> &#x2014; material icon named "directions walk sharp".
+  static const IconData directions_walk_sharp = IconData(0xec47, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dirty_lens</i> &#x2014; material icon named "dirty lens".
+  static const IconData dirty_lens = IconData(0xe6c2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">disc_full</i> &#x2014; material icon named "disc full".
+  static const IconData disc_full = IconData(0xe6c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">disc_full</i> &#x2014; material icon named "disc full outlined".
+  static const IconData disc_full_outlined = IconData(0xe148, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">disc_full</i> &#x2014; material icon named "disc full rounded".
+  static const IconData disc_full_rounded = IconData(0xf176, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">disc_full</i> &#x2014; material icon named "disc full sharp".
+  static const IconData disc_full_sharp = IconData(0xec48, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dnd_forwardslash</i> &#x2014; material icon named "dnd forwardslash".
+  static const IconData dnd_forwardslash = IconData(0xe6c6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dns</i> &#x2014; material icon named "dns".
+  static const IconData dns = IconData(0xe6c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">dns</i> &#x2014; material icon named "dns outlined".
+  static const IconData dns_outlined = IconData(0xe149, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">dns</i> &#x2014; material icon named "dns rounded".
+  static const IconData dns_rounded = IconData(0xf177, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">dns</i> &#x2014; material icon named "dns sharp".
+  static const IconData dns_sharp = IconData(0xec49, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">do_disturb_alt</i> &#x2014; material icon named "do disturb alt outlined".
+  static const IconData do_disturb_alt_outlined = IconData(0xe14a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">do_disturb_alt</i> &#x2014; material icon named "do disturb alt rounded".
+  static const IconData do_disturb_alt_rounded = IconData(0xf178, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">do_disturb_alt</i> &#x2014; material icon named "do disturb alt sharp".
+  static const IconData do_disturb_alt_sharp = IconData(0xec4a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">do_disturb_off</i> &#x2014; material icon named "do disturb off outlined".
+  static const IconData do_disturb_off_outlined = IconData(0xe14b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">do_disturb_off</i> &#x2014; material icon named "do disturb off rounded".
+  static const IconData do_disturb_off_rounded = IconData(0xf179, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">do_disturb_off</i> &#x2014; material icon named "do disturb off sharp".
+  static const IconData do_disturb_off_sharp = IconData(0xec4b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">do_disturb_on</i> &#x2014; material icon named "do disturb on outlined".
+  static const IconData do_disturb_on_outlined = IconData(0xe14c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">do_disturb_on</i> &#x2014; material icon named "do disturb on rounded".
+  static const IconData do_disturb_on_rounded = IconData(0xf17a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">do_disturb_on</i> &#x2014; material icon named "do disturb on sharp".
+  static const IconData do_disturb_on_sharp = IconData(0xec4c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">do_disturb</i> &#x2014; material icon named "do disturb outlined".
+  static const IconData do_disturb_outlined = IconData(0xe14d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">do_disturb</i> &#x2014; material icon named "do disturb rounded".
+  static const IconData do_disturb_rounded = IconData(0xf17b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">do_disturb</i> &#x2014; material icon named "do disturb sharp".
+  static const IconData do_disturb_sharp = IconData(0xec4d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">do_not_disturb</i> &#x2014; material icon named "do not disturb".
+  static const IconData do_not_disturb = IconData(0xe6c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">do_not_disturb_alt</i> &#x2014; material icon named "do not disturb alt".
+  static const IconData do_not_disturb_alt = IconData(0xe6c6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">do_not_disturb_off</i> &#x2014; material icon named "do not disturb off".
+  static const IconData do_not_disturb_off = IconData(0xe6c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">do_not_disturb_on</i> &#x2014; material icon named "do not disturb on".
+  static const IconData do_not_disturb_on = IconData(0xe6c8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">do_not_step</i> &#x2014; material icon named "do not step".
+  static const IconData do_not_step = IconData(0xe6c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">do_not_step</i> &#x2014; material icon named "do not step outlined".
+  static const IconData do_not_step_outlined = IconData(0xe14e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">do_not_step</i> &#x2014; material icon named "do not step rounded".
+  static const IconData do_not_step_rounded = IconData(0xf17c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">do_not_step</i> &#x2014; material icon named "do not step sharp".
+  static const IconData do_not_step_sharp = IconData(0xec4e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">do_not_touch</i> &#x2014; material icon named "do not touch".
+  static const IconData do_not_touch = IconData(0xe6ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">do_not_touch</i> &#x2014; material icon named "do not touch outlined".
+  static const IconData do_not_touch_outlined = IconData(0xe14f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">do_not_touch</i> &#x2014; material icon named "do not touch rounded".
+  static const IconData do_not_touch_rounded = IconData(0xf17d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">do_not_touch</i> &#x2014; material icon named "do not touch sharp".
+  static const IconData do_not_touch_sharp = IconData(0xec4f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dock</i> &#x2014; material icon named "dock".
+  static const IconData dock = IconData(0xe6cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">dock</i> &#x2014; material icon named "dock outlined".
+  static const IconData dock_outlined = IconData(0xe150, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">dock</i> &#x2014; material icon named "dock rounded".
+  static const IconData dock_rounded = IconData(0xf17e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">dock</i> &#x2014; material icon named "dock sharp".
+  static const IconData dock_sharp = IconData(0xec50, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">domain</i> &#x2014; material icon named "domain".
+  static const IconData domain = IconData(0xe6cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">domain_disabled</i> &#x2014; material icon named "domain disabled".
+  static const IconData domain_disabled = IconData(0xe6cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">domain_disabled</i> &#x2014; material icon named "domain disabled outlined".
+  static const IconData domain_disabled_outlined = IconData(0xe151, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">domain_disabled</i> &#x2014; material icon named "domain disabled rounded".
+  static const IconData domain_disabled_rounded = IconData(0xf17f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">domain_disabled</i> &#x2014; material icon named "domain disabled sharp".
+  static const IconData domain_disabled_sharp = IconData(0xec51, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">domain</i> &#x2014; material icon named "domain outlined".
+  static const IconData domain_outlined = IconData(0xe152, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">domain</i> &#x2014; material icon named "domain rounded".
+  static const IconData domain_rounded = IconData(0xf180, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">domain</i> &#x2014; material icon named "domain sharp".
+  static const IconData domain_sharp = IconData(0xec52, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">domain_verification</i> &#x2014; material icon named "domain verification".
+  static const IconData domain_verification = IconData(0xe6ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">domain_verification</i> &#x2014; material icon named "domain verification outlined".
+  static const IconData domain_verification_outlined = IconData(0xe153, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">domain_verification</i> &#x2014; material icon named "domain verification rounded".
+  static const IconData domain_verification_rounded = IconData(0xf181, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">domain_verification</i> &#x2014; material icon named "domain verification sharp".
+  static const IconData domain_verification_sharp = IconData(0xec53, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">done</i> &#x2014; material icon named "done".
+  static const IconData done = IconData(0xe6cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">done_all</i> &#x2014; material icon named "done all".
+  static const IconData done_all = IconData(0xe6d0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">done_all</i> &#x2014; material icon named "done all outlined".
+  static const IconData done_all_outlined = IconData(0xe154, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">done_all</i> &#x2014; material icon named "done all rounded".
+  static const IconData done_all_rounded = IconData(0xf182, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">done_all</i> &#x2014; material icon named "done all sharp".
+  static const IconData done_all_sharp = IconData(0xec54, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">done_outline</i> &#x2014; material icon named "done outline".
+  static const IconData done_outline = IconData(0xe6d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">done_outline</i> &#x2014; material icon named "done outline outlined".
+  static const IconData done_outline_outlined = IconData(0xe155, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">done_outline</i> &#x2014; material icon named "done outline rounded".
+  static const IconData done_outline_rounded = IconData(0xf183, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">done_outline</i> &#x2014; material icon named "done outline sharp".
+  static const IconData done_outline_sharp = IconData(0xec55, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">done</i> &#x2014; material icon named "done outlined".
+  static const IconData done_outlined = IconData(0xe156, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">done</i> &#x2014; material icon named "done rounded".
+  static const IconData done_rounded = IconData(0xf184, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">done</i> &#x2014; material icon named "done sharp".
+  static const IconData done_sharp = IconData(0xec56, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">donut_large</i> &#x2014; material icon named "donut large".
+  static const IconData donut_large = IconData(0xe6d2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">donut_large</i> &#x2014; material icon named "donut large outlined".
+  static const IconData donut_large_outlined = IconData(0xe157, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">donut_large</i> &#x2014; material icon named "donut large rounded".
+  static const IconData donut_large_rounded = IconData(0xf185, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">donut_large</i> &#x2014; material icon named "donut large sharp".
+  static const IconData donut_large_sharp = IconData(0xec57, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">donut_small</i> &#x2014; material icon named "donut small".
+  static const IconData donut_small = IconData(0xe6d3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">donut_small</i> &#x2014; material icon named "donut small outlined".
+  static const IconData donut_small_outlined = IconData(0xe158, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">donut_small</i> &#x2014; material icon named "donut small rounded".
+  static const IconData donut_small_rounded = IconData(0xf186, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">donut_small</i> &#x2014; material icon named "donut small sharp".
+  static const IconData donut_small_sharp = IconData(0xec58, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">double_arrow</i> &#x2014; material icon named "double arrow".
+  static const IconData double_arrow = IconData(0xe6d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">double_arrow</i> &#x2014; material icon named "double arrow outlined".
+  static const IconData double_arrow_outlined = IconData(0xe159, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">double_arrow</i> &#x2014; material icon named "double arrow rounded".
+  static const IconData double_arrow_rounded = IconData(0xf187, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">double_arrow</i> &#x2014; material icon named "double arrow sharp".
+  static const IconData double_arrow_sharp = IconData(0xec59, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">download_done</i> &#x2014; material icon named "download done outlined".
+  static const IconData download_done_outlined = IconData(0xe15a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">download_done</i> &#x2014; material icon named "download done rounded".
+  static const IconData download_done_rounded = IconData(0xf188, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">download_done</i> &#x2014; material icon named "download done sharp".
+  static const IconData download_done_sharp = IconData(0xec5a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">download</i> &#x2014; material icon named "download outlined".
+  static const IconData download_outlined = IconData(0xe15b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">download</i> &#x2014; material icon named "download rounded".
+  static const IconData download_rounded = IconData(0xf189, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">download</i> &#x2014; material icon named "download sharp".
+  static const IconData download_sharp = IconData(0xec5b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">drafts</i> &#x2014; material icon named "drafts".
+  static const IconData drafts = IconData(0xe6d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">drafts</i> &#x2014; material icon named "drafts outlined".
+  static const IconData drafts_outlined = IconData(0xe15c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">drafts</i> &#x2014; material icon named "drafts rounded".
+  static const IconData drafts_rounded = IconData(0xf18a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">drafts</i> &#x2014; material icon named "drafts sharp".
+  static const IconData drafts_sharp = IconData(0xec5c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">drag_handle</i> &#x2014; material icon named "drag handle".
+  static const IconData drag_handle = IconData(0xe6d6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">drag_handle</i> &#x2014; material icon named "drag handle outlined".
+  static const IconData drag_handle_outlined = IconData(0xe15d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">drag_handle</i> &#x2014; material icon named "drag handle rounded".
+  static const IconData drag_handle_rounded = IconData(0xf18b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">drag_handle</i> &#x2014; material icon named "drag handle sharp".
+  static const IconData drag_handle_sharp = IconData(0xec5d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">drag_indicator</i> &#x2014; material icon named "drag indicator".
+  static const IconData drag_indicator = IconData(0xe6d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">drag_indicator</i> &#x2014; material icon named "drag indicator outlined".
+  static const IconData drag_indicator_outlined = IconData(0xe15e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">drag_indicator</i> &#x2014; material icon named "drag indicator rounded".
+  static const IconData drag_indicator_rounded = IconData(0xf18c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">drag_indicator</i> &#x2014; material icon named "drag indicator sharp".
+  static const IconData drag_indicator_sharp = IconData(0xec5e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">drive_eta</i> &#x2014; material icon named "drive eta".
+  static const IconData drive_eta = IconData(0xe6d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">drive_eta</i> &#x2014; material icon named "drive eta outlined".
+  static const IconData drive_eta_outlined = IconData(0xe15f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">drive_eta</i> &#x2014; material icon named "drive eta rounded".
+  static const IconData drive_eta_rounded = IconData(0xf18d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">drive_eta</i> &#x2014; material icon named "drive eta sharp".
+  static const IconData drive_eta_sharp = IconData(0xec5f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">drive_file_move</i> &#x2014; material icon named "drive file move".
+  static const IconData drive_file_move = IconData(0xe6d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">drive_file_move_outline</i> &#x2014; material icon named "drive file move outline".
+  static const IconData drive_file_move_outline = IconData(0xe6da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">drive_file_rename_outline</i> &#x2014; material icon named "drive file rename outline".
+  static const IconData drive_file_rename_outline = IconData(0xe6db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">drive_folder_upload</i> &#x2014; material icon named "drive folder upload".
+  static const IconData drive_folder_upload = IconData(0xe6dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dry</i> &#x2014; material icon named "dry".
+  static const IconData dry = IconData(0xe6dd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dry_cleaning</i> &#x2014; material icon named "dry cleaning".
+  static const IconData dry_cleaning = IconData(0xe6de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">dry</i> &#x2014; material icon named "dry outlined".
+  static const IconData dry_outlined = IconData(0xe160, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">dry</i> &#x2014; material icon named "dry rounded".
+  static const IconData dry_rounded = IconData(0xf18e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">dry</i> &#x2014; material icon named "dry sharp".
+  static const IconData dry_sharp = IconData(0xec60, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">duo</i> &#x2014; material icon named "duo".
+  static const IconData duo = IconData(0xe6df, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">duo</i> &#x2014; material icon named "duo outlined".
+  static const IconData duo_outlined = IconData(0xe161, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">duo</i> &#x2014; material icon named "duo rounded".
+  static const IconData duo_rounded = IconData(0xf18f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">duo</i> &#x2014; material icon named "duo sharp".
+  static const IconData duo_sharp = IconData(0xec61, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dvr</i> &#x2014; material icon named "dvr".
+  static const IconData dvr = IconData(0xe6e0, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">dvr</i> &#x2014; material icon named "dvr outlined".
+  static const IconData dvr_outlined = IconData(0xe162, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">dvr</i> &#x2014; material icon named "dvr rounded".
+  static const IconData dvr_rounded = IconData(0xf190, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">dvr</i> &#x2014; material icon named "dvr sharp".
+  static const IconData dvr_sharp = IconData(0xec62, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">dynamic_feed</i> &#x2014; material icon named "dynamic feed".
+  static const IconData dynamic_feed = IconData(0xe6e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">dynamic_feed</i> &#x2014; material icon named "dynamic feed outlined".
+  static const IconData dynamic_feed_outlined = IconData(0xe163, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">dynamic_feed</i> &#x2014; material icon named "dynamic feed rounded".
+  static const IconData dynamic_feed_rounded = IconData(0xf191, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">dynamic_feed</i> &#x2014; material icon named "dynamic feed sharp".
+  static const IconData dynamic_feed_sharp = IconData(0xec63, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">dynamic_form</i> &#x2014; material icon named "dynamic form".
+  static const IconData dynamic_form = IconData(0xe6e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">dynamic_form</i> &#x2014; material icon named "dynamic form outlined".
+  static const IconData dynamic_form_outlined = IconData(0xe164, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">dynamic_form</i> &#x2014; material icon named "dynamic form rounded".
+  static const IconData dynamic_form_rounded = IconData(0xf192, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">dynamic_form</i> &#x2014; material icon named "dynamic form sharp".
+  static const IconData dynamic_form_sharp = IconData(0xec64, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">east</i> &#x2014; material icon named "east".
+  static const IconData east = IconData(0xe6e3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">east</i> &#x2014; material icon named "east outlined".
+  static const IconData east_outlined = IconData(0xe165, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">east</i> &#x2014; material icon named "east rounded".
+  static const IconData east_rounded = IconData(0xf193, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">east</i> &#x2014; material icon named "east sharp".
+  static const IconData east_sharp = IconData(0xec65, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">eco</i> &#x2014; material icon named "eco".
+  static const IconData eco = IconData(0xe6e4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">eco</i> &#x2014; material icon named "eco outlined".
+  static const IconData eco_outlined = IconData(0xe166, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">eco</i> &#x2014; material icon named "eco rounded".
+  static const IconData eco_rounded = IconData(0xf194, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">eco</i> &#x2014; material icon named "eco sharp".
+  static const IconData eco_sharp = IconData(0xec66, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">edit</i> &#x2014; material icon named "edit".
+  static const IconData edit = IconData(0xe6e5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">edit_attributes</i> &#x2014; material icon named "edit attributes".
+  static const IconData edit_attributes = IconData(0xe6e6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">edit_attributes</i> &#x2014; material icon named "edit attributes outlined".
+  static const IconData edit_attributes_outlined = IconData(0xe167, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">edit_attributes</i> &#x2014; material icon named "edit attributes rounded".
+  static const IconData edit_attributes_rounded = IconData(0xf195, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">edit_attributes</i> &#x2014; material icon named "edit attributes sharp".
+  static const IconData edit_attributes_sharp = IconData(0xec67, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">edit_location</i> &#x2014; material icon named "edit location".
+  static const IconData edit_location = IconData(0xe6e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">edit_location</i> &#x2014; material icon named "edit location outlined".
+  static const IconData edit_location_outlined = IconData(0xe168, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">edit_location</i> &#x2014; material icon named "edit location rounded".
+  static const IconData edit_location_rounded = IconData(0xf196, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">edit_location</i> &#x2014; material icon named "edit location sharp".
+  static const IconData edit_location_sharp = IconData(0xec68, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">edit_off</i> &#x2014; material icon named "edit off".
+  static const IconData edit_off = IconData(0xe6e8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">edit</i> &#x2014; material icon named "edit outlined".
+  static const IconData edit_outlined = IconData(0xe169, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">edit_road</i> &#x2014; material icon named "edit road".
+  static const IconData edit_road = IconData(0xe6e9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">edit_road</i> &#x2014; material icon named "edit road outlined".
+  static const IconData edit_road_outlined = IconData(0xe16a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">edit_road</i> &#x2014; material icon named "edit road rounded".
+  static const IconData edit_road_rounded = IconData(0xf197, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">edit_road</i> &#x2014; material icon named "edit road sharp".
+  static const IconData edit_road_sharp = IconData(0xec69, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">edit</i> &#x2014; material icon named "edit rounded".
+  static const IconData edit_rounded = IconData(0xf198, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">edit</i> &#x2014; material icon named "edit sharp".
+  static const IconData edit_sharp = IconData(0xec6a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">eject</i> &#x2014; material icon named "eject".
+  static const IconData eject = IconData(0xe6ea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">eject</i> &#x2014; material icon named "eject outlined".
+  static const IconData eject_outlined = IconData(0xe16b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">eject</i> &#x2014; material icon named "eject rounded".
+  static const IconData eject_rounded = IconData(0xf199, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">eject</i> &#x2014; material icon named "eject sharp".
+  static const IconData eject_sharp = IconData(0xec6b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">elderly</i> &#x2014; material icon named "elderly".
+  static const IconData elderly = IconData(0xe6eb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">elderly</i> &#x2014; material icon named "elderly outlined".
+  static const IconData elderly_outlined = IconData(0xe16c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">elderly</i> &#x2014; material icon named "elderly rounded".
+  static const IconData elderly_rounded = IconData(0xf19a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">elderly</i> &#x2014; material icon named "elderly sharp".
+  static const IconData elderly_sharp = IconData(0xec6c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">electric_bike</i> &#x2014; material icon named "electric bike".
+  static const IconData electric_bike = IconData(0xe6ec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">electric_bike</i> &#x2014; material icon named "electric bike outlined".
+  static const IconData electric_bike_outlined = IconData(0xe16d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">electric_bike</i> &#x2014; material icon named "electric bike rounded".
+  static const IconData electric_bike_rounded = IconData(0xf19b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">electric_bike</i> &#x2014; material icon named "electric bike sharp".
+  static const IconData electric_bike_sharp = IconData(0xec6d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">electric_car</i> &#x2014; material icon named "electric car".
+  static const IconData electric_car = IconData(0xe6ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">electric_car</i> &#x2014; material icon named "electric car outlined".
+  static const IconData electric_car_outlined = IconData(0xe16e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">electric_car</i> &#x2014; material icon named "electric car rounded".
+  static const IconData electric_car_rounded = IconData(0xf19c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">electric_car</i> &#x2014; material icon named "electric car sharp".
+  static const IconData electric_car_sharp = IconData(0xec6e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">electric_moped</i> &#x2014; material icon named "electric moped".
+  static const IconData electric_moped = IconData(0xe6ee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">electric_moped</i> &#x2014; material icon named "electric moped outlined".
+  static const IconData electric_moped_outlined = IconData(0xe16f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">electric_moped</i> &#x2014; material icon named "electric moped rounded".
+  static const IconData electric_moped_rounded = IconData(0xf19d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">electric_moped</i> &#x2014; material icon named "electric moped sharp".
+  static const IconData electric_moped_sharp = IconData(0xec6f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">electric_rickshaw</i> &#x2014; material icon named "electric rickshaw".
+  static const IconData electric_rickshaw = IconData(0xe6ef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">electric_rickshaw</i> &#x2014; material icon named "electric rickshaw rounded".
+  static const IconData electric_rickshaw_rounded = IconData(0xf19e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">electric_rickshaw</i> &#x2014; material icon named "electric rickshaw sharp".
+  static const IconData electric_rickshaw_sharp = IconData(0xec70, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">electric_scooter</i> &#x2014; material icon named "electric scooter".
+  static const IconData electric_scooter = IconData(0xe6f0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">electric_scooter</i> &#x2014; material icon named "electric scooter outlined".
+  static const IconData electric_scooter_outlined = IconData(0xe170, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">electric_scooter</i> &#x2014; material icon named "electric scooter rounded".
+  static const IconData electric_scooter_rounded = IconData(0xf19f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">electric_scooter</i> &#x2014; material icon named "electric scooter sharp".
+  static const IconData electric_scooter_sharp = IconData(0xec71, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">electrical_services</i> &#x2014; material icon named "electrical services".
+  static const IconData electrical_services = IconData(0xe6f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">electrical_services</i> &#x2014; material icon named "electrical services outlined".
+  static const IconData electrical_services_outlined = IconData(0xe171, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">electrical_services</i> &#x2014; material icon named "electrical services rounded".
+  static const IconData electrical_services_rounded = IconData(0xf1a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">electrical_services</i> &#x2014; material icon named "electrical services sharp".
+  static const IconData electrical_services_sharp = IconData(0xec72, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">elevator</i> &#x2014; material icon named "elevator".
+  static const IconData elevator = IconData(0xe6f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">elevator</i> &#x2014; material icon named "elevator outlined".
+  static const IconData elevator_outlined = IconData(0xe172, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">elevator</i> &#x2014; material icon named "elevator rounded".
+  static const IconData elevator_rounded = IconData(0xf1a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">elevator</i> &#x2014; material icon named "elevator sharp".
+  static const IconData elevator_sharp = IconData(0xec73, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">email</i> &#x2014; material icon named "email".
+  static const IconData email = IconData(0xe6f3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">email</i> &#x2014; material icon named "email outlined".
+  static const IconData email_outlined = IconData(0xe173, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">email</i> &#x2014; material icon named "email rounded".
+  static const IconData email_rounded = IconData(0xf1a2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">email</i> &#x2014; material icon named "email sharp".
+  static const IconData email_sharp = IconData(0xec74, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">emoji_emotions</i> &#x2014; material icon named "emoji emotions".
+  static const IconData emoji_emotions = IconData(0xe6f4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">emoji_emotions</i> &#x2014; material icon named "emoji emotions outlined".
+  static const IconData emoji_emotions_outlined = IconData(0xe174, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">emoji_emotions</i> &#x2014; material icon named "emoji emotions rounded".
+  static const IconData emoji_emotions_rounded = IconData(0xf1a3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">emoji_emotions</i> &#x2014; material icon named "emoji emotions sharp".
+  static const IconData emoji_emotions_sharp = IconData(0xec75, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">emoji_events</i> &#x2014; material icon named "emoji events".
+  static const IconData emoji_events = IconData(0xe6f5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">emoji_events</i> &#x2014; material icon named "emoji events outlined".
+  static const IconData emoji_events_outlined = IconData(0xe175, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">emoji_events</i> &#x2014; material icon named "emoji events rounded".
+  static const IconData emoji_events_rounded = IconData(0xf1a4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">emoji_events</i> &#x2014; material icon named "emoji events sharp".
+  static const IconData emoji_events_sharp = IconData(0xec76, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">emoji_flags</i> &#x2014; material icon named "emoji flags".
+  static const IconData emoji_flags = IconData(0xe6f6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">emoji_flags</i> &#x2014; material icon named "emoji flags outlined".
+  static const IconData emoji_flags_outlined = IconData(0xe176, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">emoji_flags</i> &#x2014; material icon named "emoji flags rounded".
+  static const IconData emoji_flags_rounded = IconData(0xf1a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">emoji_flags</i> &#x2014; material icon named "emoji flags sharp".
+  static const IconData emoji_flags_sharp = IconData(0xec77, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">emoji_food_beverage</i> &#x2014; material icon named "emoji food beverage".
+  static const IconData emoji_food_beverage = IconData(0xe6f7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">emoji_food_beverage</i> &#x2014; material icon named "emoji food beverage outlined".
+  static const IconData emoji_food_beverage_outlined = IconData(0xe177, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">emoji_food_beverage</i> &#x2014; material icon named "emoji food beverage rounded".
+  static const IconData emoji_food_beverage_rounded = IconData(0xf1a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">emoji_food_beverage</i> &#x2014; material icon named "emoji food beverage sharp".
+  static const IconData emoji_food_beverage_sharp = IconData(0xec78, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">emoji_nature</i> &#x2014; material icon named "emoji nature".
+  static const IconData emoji_nature = IconData(0xe6f8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">emoji_nature</i> &#x2014; material icon named "emoji nature outlined".
+  static const IconData emoji_nature_outlined = IconData(0xe178, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">emoji_nature</i> &#x2014; material icon named "emoji nature rounded".
+  static const IconData emoji_nature_rounded = IconData(0xf1a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">emoji_nature</i> &#x2014; material icon named "emoji nature sharp".
+  static const IconData emoji_nature_sharp = IconData(0xec79, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">emoji_objects</i> &#x2014; material icon named "emoji objects".
+  static const IconData emoji_objects = IconData(0xe6f9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">emoji_objects</i> &#x2014; material icon named "emoji objects outlined".
+  static const IconData emoji_objects_outlined = IconData(0xe179, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">emoji_objects</i> &#x2014; material icon named "emoji objects rounded".
+  static const IconData emoji_objects_rounded = IconData(0xf1a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">emoji_objects</i> &#x2014; material icon named "emoji objects sharp".
+  static const IconData emoji_objects_sharp = IconData(0xec7a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">emoji_people</i> &#x2014; material icon named "emoji people".
+  static const IconData emoji_people = IconData(0xe6fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">emoji_people</i> &#x2014; material icon named "emoji people outlined".
+  static const IconData emoji_people_outlined = IconData(0xe17a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">emoji_people</i> &#x2014; material icon named "emoji people rounded".
+  static const IconData emoji_people_rounded = IconData(0xf1a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">emoji_people</i> &#x2014; material icon named "emoji people sharp".
+  static const IconData emoji_people_sharp = IconData(0xec7b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">emoji_symbols</i> &#x2014; material icon named "emoji symbols".
+  static const IconData emoji_symbols = IconData(0xe6fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">emoji_symbols</i> &#x2014; material icon named "emoji symbols outlined".
+  static const IconData emoji_symbols_outlined = IconData(0xe17b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">emoji_symbols</i> &#x2014; material icon named "emoji symbols rounded".
+  static const IconData emoji_symbols_rounded = IconData(0xf1aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">emoji_symbols</i> &#x2014; material icon named "emoji symbols sharp".
+  static const IconData emoji_symbols_sharp = IconData(0xec7c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">emoji_transportation</i> &#x2014; material icon named "emoji transportation".
+  static const IconData emoji_transportation = IconData(0xe6fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">emoji_transportation</i> &#x2014; material icon named "emoji transportation outlined".
+  static const IconData emoji_transportation_outlined = IconData(0xe17c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">emoji_transportation</i> &#x2014; material icon named "emoji transportation rounded".
+  static const IconData emoji_transportation_rounded = IconData(0xf1ab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">emoji_transportation</i> &#x2014; material icon named "emoji transportation sharp".
+  static const IconData emoji_transportation_sharp = IconData(0xec7d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">engineering</i> &#x2014; material icon named "engineering".
+  static const IconData engineering = IconData(0xe6fd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">engineering</i> &#x2014; material icon named "engineering outlined".
+  static const IconData engineering_outlined = IconData(0xe17d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">engineering</i> &#x2014; material icon named "engineering rounded".
+  static const IconData engineering_rounded = IconData(0xf1ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">engineering</i> &#x2014; material icon named "engineering sharp".
+  static const IconData engineering_sharp = IconData(0xec7e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">enhance_photo_translate</i> &#x2014; material icon named "enhance photo translate".
+  static const IconData enhance_photo_translate = IconData(0xe630, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">enhance_photo_translate</i> &#x2014; material icon named "enhance photo translate outlined".
+  static const IconData enhance_photo_translate_outlined = IconData(0xe0c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">enhance_photo_translate</i> &#x2014; material icon named "enhance photo translate rounded".
+  static const IconData enhance_photo_translate_rounded = IconData(0xf0f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">enhance_photo_translate</i> &#x2014; material icon named "enhance photo translate sharp".
+  static const IconData enhance_photo_translate_sharp = IconData(0xebc3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">enhanced_encryption</i> &#x2014; material icon named "enhanced encryption".
+  static const IconData enhanced_encryption = IconData(0xe6fe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">enhanced_encryption</i> &#x2014; material icon named "enhanced encryption outlined".
+  static const IconData enhanced_encryption_outlined = IconData(0xe17e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">enhanced_encryption</i> &#x2014; material icon named "enhanced encryption rounded".
+  static const IconData enhanced_encryption_rounded = IconData(0xf1ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">enhanced_encryption</i> &#x2014; material icon named "enhanced encryption sharp".
+  static const IconData enhanced_encryption_sharp = IconData(0xec7f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">equalizer</i> &#x2014; material icon named "equalizer".
+  static const IconData equalizer = IconData(0xe6ff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">equalizer</i> &#x2014; material icon named "equalizer outlined".
+  static const IconData equalizer_outlined = IconData(0xe17f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">equalizer</i> &#x2014; material icon named "equalizer rounded".
+  static const IconData equalizer_rounded = IconData(0xf1ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">equalizer</i> &#x2014; material icon named "equalizer sharp".
+  static const IconData equalizer_sharp = IconData(0xec80, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">error</i> &#x2014; material icon named "error".
+  static const IconData error = IconData(0xe700, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">error_outline</i> &#x2014; material icon named "error outline".
+  static const IconData error_outline = IconData(0xe701, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">error_outline</i> &#x2014; material icon named "error outline outlined".
+  static const IconData error_outline_outlined = IconData(0xe180, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">error_outline</i> &#x2014; material icon named "error outline rounded".
+  static const IconData error_outline_rounded = IconData(0xf1af, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">error_outline</i> &#x2014; material icon named "error outline sharp".
+  static const IconData error_outline_sharp = IconData(0xec81, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">error</i> &#x2014; material icon named "error outlined".
+  static const IconData error_outlined = IconData(0xe181, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">error</i> &#x2014; material icon named "error rounded".
+  static const IconData error_rounded = IconData(0xf1b0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">error</i> &#x2014; material icon named "error sharp".
+  static const IconData error_sharp = IconData(0xec82, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">escalator</i> &#x2014; material icon named "escalator".
+  static const IconData escalator = IconData(0xe702, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">escalator</i> &#x2014; material icon named "escalator outlined".
+  static const IconData escalator_outlined = IconData(0xe182, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">escalator</i> &#x2014; material icon named "escalator rounded".
+  static const IconData escalator_rounded = IconData(0xf1b1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">escalator</i> &#x2014; material icon named "escalator sharp".
+  static const IconData escalator_sharp = IconData(0xec83, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">escalator_warning</i> &#x2014; material icon named "escalator warning".
+  static const IconData escalator_warning = IconData(0xe703, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">escalator_warning</i> &#x2014; material icon named "escalator warning outlined".
+  static const IconData escalator_warning_outlined = IconData(0xe183, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">escalator_warning</i> &#x2014; material icon named "escalator warning rounded".
+  static const IconData escalator_warning_rounded = IconData(0xf1b2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">escalator_warning</i> &#x2014; material icon named "escalator warning sharp".
+  static const IconData escalator_warning_sharp = IconData(0xec84, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">euro</i> &#x2014; material icon named "euro".
+  static const IconData euro = IconData(0xe704, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">euro</i> &#x2014; material icon named "euro outlined".
+  static const IconData euro_outlined = IconData(0xe184, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">euro</i> &#x2014; material icon named "euro rounded".
+  static const IconData euro_rounded = IconData(0xf1b3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">euro</i> &#x2014; material icon named "euro sharp".
+  static const IconData euro_sharp = IconData(0xec85, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">euro_symbol</i> &#x2014; material icon named "euro symbol".
+  static const IconData euro_symbol = IconData(0xe705, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">euro_symbol</i> &#x2014; material icon named "euro symbol outlined".
+  static const IconData euro_symbol_outlined = IconData(0xe185, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">euro_symbol</i> &#x2014; material icon named "euro symbol rounded".
+  static const IconData euro_symbol_rounded = IconData(0xf1b4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">euro_symbol</i> &#x2014; material icon named "euro symbol sharp".
+  static const IconData euro_symbol_sharp = IconData(0xec86, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">ev_station</i> &#x2014; material icon named "ev station".
+  static const IconData ev_station = IconData(0xe706, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">ev_station</i> &#x2014; material icon named "ev station outlined".
+  static const IconData ev_station_outlined = IconData(0xe186, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">ev_station</i> &#x2014; material icon named "ev station rounded".
+  static const IconData ev_station_rounded = IconData(0xf1b5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">ev_station</i> &#x2014; material icon named "ev station sharp".
+  static const IconData ev_station_sharp = IconData(0xec87, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">event</i> &#x2014; material icon named "event".
+  static const IconData event = IconData(0xe707, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">event_available</i> &#x2014; material icon named "event available".
+  static const IconData event_available = IconData(0xe708, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">event_available</i> &#x2014; material icon named "event available outlined".
+  static const IconData event_available_outlined = IconData(0xe187, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">event_available</i> &#x2014; material icon named "event available rounded".
+  static const IconData event_available_rounded = IconData(0xf1b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">event_available</i> &#x2014; material icon named "event available sharp".
+  static const IconData event_available_sharp = IconData(0xec88, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">event_busy</i> &#x2014; material icon named "event busy".
+  static const IconData event_busy = IconData(0xe709, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">event_busy</i> &#x2014; material icon named "event busy outlined".
+  static const IconData event_busy_outlined = IconData(0xe188, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">event_busy</i> &#x2014; material icon named "event busy rounded".
+  static const IconData event_busy_rounded = IconData(0xf1b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">event_busy</i> &#x2014; material icon named "event busy sharp".
+  static const IconData event_busy_sharp = IconData(0xec89, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">event_note</i> &#x2014; material icon named "event note".
+  static const IconData event_note = IconData(0xe70a, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">event_note</i> &#x2014; material icon named "event note outlined".
+  static const IconData event_note_outlined = IconData(0xe189, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">event_note</i> &#x2014; material icon named "event note rounded".
+  static const IconData event_note_rounded = IconData(0xf1b8, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">event_note</i> &#x2014; material icon named "event note sharp".
+  static const IconData event_note_sharp = IconData(0xec8a, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">event</i> &#x2014; material icon named "event outlined".
+  static const IconData event_outlined = IconData(0xe18a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">event</i> &#x2014; material icon named "event rounded".
+  static const IconData event_rounded = IconData(0xf1b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">event_seat</i> &#x2014; material icon named "event seat".
+  static const IconData event_seat = IconData(0xe70b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">event_seat</i> &#x2014; material icon named "event seat outlined".
+  static const IconData event_seat_outlined = IconData(0xe18b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">event_seat</i> &#x2014; material icon named "event seat rounded".
+  static const IconData event_seat_rounded = IconData(0xf1ba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">event_seat</i> &#x2014; material icon named "event seat sharp".
+  static const IconData event_seat_sharp = IconData(0xec8b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">event</i> &#x2014; material icon named "event sharp".
+  static const IconData event_sharp = IconData(0xec8c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">exit_to_app</i> &#x2014; material icon named "exit to app".
+  static const IconData exit_to_app = IconData(0xe70c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">exit_to_app</i> &#x2014; material icon named "exit to app outlined".
+  static const IconData exit_to_app_outlined = IconData(0xe18c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">exit_to_app</i> &#x2014; material icon named "exit to app rounded".
+  static const IconData exit_to_app_rounded = IconData(0xf1bb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">exit_to_app</i> &#x2014; material icon named "exit to app sharp".
+  static const IconData exit_to_app_sharp = IconData(0xec8d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">expand</i> &#x2014; material icon named "expand".
+  static const IconData expand = IconData(0xe70d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">expand_less</i> &#x2014; material icon named "expand less".
+  static const IconData expand_less = IconData(0xe70e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">expand_less</i> &#x2014; material icon named "expand less outlined".
+  static const IconData expand_less_outlined = IconData(0xe18d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">expand_less</i> &#x2014; material icon named "expand less rounded".
+  static const IconData expand_less_rounded = IconData(0xf1bc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">expand_less</i> &#x2014; material icon named "expand less sharp".
+  static const IconData expand_less_sharp = IconData(0xec8e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">expand_more</i> &#x2014; material icon named "expand more".
+  static const IconData expand_more = IconData(0xe70f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">expand_more</i> &#x2014; material icon named "expand more outlined".
+  static const IconData expand_more_outlined = IconData(0xe18e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">expand_more</i> &#x2014; material icon named "expand more rounded".
+  static const IconData expand_more_rounded = IconData(0xf1bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">expand_more</i> &#x2014; material icon named "expand more sharp".
+  static const IconData expand_more_sharp = IconData(0xec8f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">explicit</i> &#x2014; material icon named "explicit".
+  static const IconData explicit = IconData(0xe710, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">explicit</i> &#x2014; material icon named "explicit outlined".
+  static const IconData explicit_outlined = IconData(0xe18f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">explicit</i> &#x2014; material icon named "explicit rounded".
+  static const IconData explicit_rounded = IconData(0xf1be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">explicit</i> &#x2014; material icon named "explicit sharp".
+  static const IconData explicit_sharp = IconData(0xec90, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">explore</i> &#x2014; material icon named "explore".
+  static const IconData explore = IconData(0xe711, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">explore_off</i> &#x2014; material icon named "explore off".
+  static const IconData explore_off = IconData(0xe712, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">explore_off</i> &#x2014; material icon named "explore off outlined".
+  static const IconData explore_off_outlined = IconData(0xe190, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">explore_off</i> &#x2014; material icon named "explore off rounded".
+  static const IconData explore_off_rounded = IconData(0xf1bf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">explore_off</i> &#x2014; material icon named "explore off sharp".
+  static const IconData explore_off_sharp = IconData(0xec91, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">explore</i> &#x2014; material icon named "explore outlined".
+  static const IconData explore_outlined = IconData(0xe191, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">explore</i> &#x2014; material icon named "explore rounded".
+  static const IconData explore_rounded = IconData(0xf1c0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">explore</i> &#x2014; material icon named "explore sharp".
+  static const IconData explore_sharp = IconData(0xec92, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">exposure</i> &#x2014; material icon named "exposure".
+  static const IconData exposure = IconData(0xe713, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">exposure_minus_1</i> &#x2014; material icon named "exposure minus 1".
+  static const IconData exposure_minus_1 = IconData(0xe714, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">exposure_minus_1</i> &#x2014; material icon named "exposure minus 1 outlined".
+  static const IconData exposure_minus_1_outlined = IconData(0xe192, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">exposure_minus_1</i> &#x2014; material icon named "exposure minus 1 rounded".
+  static const IconData exposure_minus_1_rounded = IconData(0xf1c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">exposure_minus_1</i> &#x2014; material icon named "exposure minus 1 sharp".
+  static const IconData exposure_minus_1_sharp = IconData(0xec93, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">exposure_minus_2</i> &#x2014; material icon named "exposure minus 2".
+  static const IconData exposure_minus_2 = IconData(0xe715, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">exposure_minus_2</i> &#x2014; material icon named "exposure minus 2 outlined".
+  static const IconData exposure_minus_2_outlined = IconData(0xe193, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">exposure_minus_2</i> &#x2014; material icon named "exposure minus 2 rounded".
+  static const IconData exposure_minus_2_rounded = IconData(0xf1c2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">exposure_minus_2</i> &#x2014; material icon named "exposure minus 2 sharp".
+  static const IconData exposure_minus_2_sharp = IconData(0xec94, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">exposure_neg_1</i> &#x2014; material icon named "exposure neg 1".
+  static const IconData exposure_neg_1 = IconData(0xe714, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">exposure_neg_1</i> &#x2014; material icon named "exposure neg 1 outlined".
+  static const IconData exposure_neg_1_outlined = IconData(0xe192, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">exposure_neg_1</i> &#x2014; material icon named "exposure neg 1 rounded".
+  static const IconData exposure_neg_1_rounded = IconData(0xf1c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">exposure_neg_1</i> &#x2014; material icon named "exposure neg 1 sharp".
+  static const IconData exposure_neg_1_sharp = IconData(0xec93, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">exposure_neg_2</i> &#x2014; material icon named "exposure neg 2".
+  static const IconData exposure_neg_2 = IconData(0xe715, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">exposure_neg_2</i> &#x2014; material icon named "exposure neg 2 outlined".
+  static const IconData exposure_neg_2_outlined = IconData(0xe193, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">exposure_neg_2</i> &#x2014; material icon named "exposure neg 2 rounded".
+  static const IconData exposure_neg_2_rounded = IconData(0xf1c2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">exposure_neg_2</i> &#x2014; material icon named "exposure neg 2 sharp".
+  static const IconData exposure_neg_2_sharp = IconData(0xec94, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">exposure</i> &#x2014; material icon named "exposure outlined".
+  static const IconData exposure_outlined = IconData(0xe194, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">exposure_plus_1</i> &#x2014; material icon named "exposure plus 1".
+  static const IconData exposure_plus_1 = IconData(0xe716, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">exposure_plus_1</i> &#x2014; material icon named "exposure plus 1 outlined".
+  static const IconData exposure_plus_1_outlined = IconData(0xe195, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">exposure_plus_1</i> &#x2014; material icon named "exposure plus 1 rounded".
+  static const IconData exposure_plus_1_rounded = IconData(0xf1c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">exposure_plus_1</i> &#x2014; material icon named "exposure plus 1 sharp".
+  static const IconData exposure_plus_1_sharp = IconData(0xec95, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">exposure_plus_2</i> &#x2014; material icon named "exposure plus 2".
+  static const IconData exposure_plus_2 = IconData(0xe717, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">exposure_plus_2</i> &#x2014; material icon named "exposure plus 2 outlined".
+  static const IconData exposure_plus_2_outlined = IconData(0xe196, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">exposure_plus_2</i> &#x2014; material icon named "exposure plus 2 rounded".
+  static const IconData exposure_plus_2_rounded = IconData(0xf1c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">exposure_plus_2</i> &#x2014; material icon named "exposure plus 2 sharp".
+  static const IconData exposure_plus_2_sharp = IconData(0xec96, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">exposure</i> &#x2014; material icon named "exposure rounded".
+  static const IconData exposure_rounded = IconData(0xf1c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">exposure</i> &#x2014; material icon named "exposure sharp".
+  static const IconData exposure_sharp = IconData(0xec97, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">exposure_zero</i> &#x2014; material icon named "exposure zero".
+  static const IconData exposure_zero = IconData(0xe718, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">exposure_zero</i> &#x2014; material icon named "exposure zero outlined".
+  static const IconData exposure_zero_outlined = IconData(0xe197, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">exposure_zero</i> &#x2014; material icon named "exposure zero rounded".
+  static const IconData exposure_zero_rounded = IconData(0xf1c6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">exposure_zero</i> &#x2014; material icon named "exposure zero sharp".
+  static const IconData exposure_zero_sharp = IconData(0xec98, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">extension</i> &#x2014; material icon named "extension".
+  static const IconData extension = IconData(0xe719, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">extension</i> &#x2014; material icon named "extension outlined".
+  static const IconData extension_outlined = IconData(0xe198, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">extension</i> &#x2014; material icon named "extension rounded".
+  static const IconData extension_rounded = IconData(0xf1c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">extension</i> &#x2014; material icon named "extension sharp".
+  static const IconData extension_sharp = IconData(0xec99, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">face</i> &#x2014; material icon named "face".
+  static const IconData face = IconData(0xe71a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">face</i> &#x2014; material icon named "face outlined".
+  static const IconData face_outlined = IconData(0xe199, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">face_retouching_natural</i> &#x2014; material icon named "face retouching natural".
+  static const IconData face_retouching_natural = IconData(0xe71b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">face</i> &#x2014; material icon named "face rounded".
+  static const IconData face_rounded = IconData(0xf1c8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">face</i> &#x2014; material icon named "face sharp".
+  static const IconData face_sharp = IconData(0xec9a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">face_unlock</i> &#x2014; material icon named "face unlock outlined".
+  static const IconData face_unlock_outlined = IconData(0xe19a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">face_unlock</i> &#x2014; material icon named "face unlock rounded".
+  static const IconData face_unlock_rounded = IconData(0xf1c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">face_unlock</i> &#x2014; material icon named "face unlock sharp".
+  static const IconData face_unlock_sharp = IconData(0xec9b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fact_check</i> &#x2014; material icon named "fact check".
+  static const IconData fact_check = IconData(0xe71c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fact_check</i> &#x2014; material icon named "fact check outlined".
+  static const IconData fact_check_outlined = IconData(0xe19b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fact_check</i> &#x2014; material icon named "fact check rounded".
+  static const IconData fact_check_rounded = IconData(0xf1ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fact_check</i> &#x2014; material icon named "fact check sharp".
+  static const IconData fact_check_sharp = IconData(0xec9c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">family_restroom</i> &#x2014; material icon named "family restroom".
+  static const IconData family_restroom = IconData(0xe71d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">family_restroom</i> &#x2014; material icon named "family restroom outlined".
+  static const IconData family_restroom_outlined = IconData(0xe19c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">family_restroom</i> &#x2014; material icon named "family restroom rounded".
+  static const IconData family_restroom_rounded = IconData(0xf1cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">family_restroom</i> &#x2014; material icon named "family restroom sharp".
+  static const IconData family_restroom_sharp = IconData(0xec9d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fast_forward</i> &#x2014; material icon named "fast forward".
+  static const IconData fast_forward = IconData(0xe71e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fast_forward</i> &#x2014; material icon named "fast forward outlined".
+  static const IconData fast_forward_outlined = IconData(0xe19d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fast_forward</i> &#x2014; material icon named "fast forward rounded".
+  static const IconData fast_forward_rounded = IconData(0xf1cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fast_forward</i> &#x2014; material icon named "fast forward sharp".
+  static const IconData fast_forward_sharp = IconData(0xec9e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fast_rewind</i> &#x2014; material icon named "fast rewind".
+  static const IconData fast_rewind = IconData(0xe71f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fast_rewind</i> &#x2014; material icon named "fast rewind outlined".
+  static const IconData fast_rewind_outlined = IconData(0xe19e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fast_rewind</i> &#x2014; material icon named "fast rewind rounded".
+  static const IconData fast_rewind_rounded = IconData(0xf1cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fast_rewind</i> &#x2014; material icon named "fast rewind sharp".
+  static const IconData fast_rewind_sharp = IconData(0xec9f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fastfood</i> &#x2014; material icon named "fastfood".
+  static const IconData fastfood = IconData(0xe720, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fastfood</i> &#x2014; material icon named "fastfood outlined".
+  static const IconData fastfood_outlined = IconData(0xe19f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fastfood</i> &#x2014; material icon named "fastfood rounded".
+  static const IconData fastfood_rounded = IconData(0xf1ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fastfood</i> &#x2014; material icon named "fastfood sharp".
+  static const IconData fastfood_sharp = IconData(0xeca0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">favorite</i> &#x2014; material icon named "favorite".
+  static const IconData favorite = IconData(0xe721, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">favorite_border</i> &#x2014; material icon named "favorite border".
+  static const IconData favorite_border = IconData(0xe722, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">favorite_border</i> &#x2014; material icon named "favorite border outlined".
+  static const IconData favorite_border_outlined = IconData(0xe1a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">favorite_border</i> &#x2014; material icon named "favorite border rounded".
+  static const IconData favorite_border_rounded = IconData(0xf1cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">favorite_border</i> &#x2014; material icon named "favorite border sharp".
+  static const IconData favorite_border_sharp = IconData(0xeca1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">favorite_outline</i> &#x2014; material icon named "favorite outline".
+  static const IconData favorite_outline = IconData(0xe722, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">favorite_outline</i> &#x2014; material icon named "favorite outline outlined".
+  static const IconData favorite_outline_outlined = IconData(0xe1a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">favorite_outline</i> &#x2014; material icon named "favorite outline rounded".
+  static const IconData favorite_outline_rounded = IconData(0xf1cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">favorite_outline</i> &#x2014; material icon named "favorite outline sharp".
+  static const IconData favorite_outline_sharp = IconData(0xeca1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">favorite</i> &#x2014; material icon named "favorite outlined".
+  static const IconData favorite_outlined = IconData(0xe1a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">favorite</i> &#x2014; material icon named "favorite rounded".
+  static const IconData favorite_rounded = IconData(0xf1d0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">favorite</i> &#x2014; material icon named "favorite sharp".
+  static const IconData favorite_sharp = IconData(0xeca2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">featured_play_list</i> &#x2014; material icon named "featured play list".
+  static const IconData featured_play_list = IconData(0xe723, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">featured_play_list</i> &#x2014; material icon named "featured play list outlined".
+  static const IconData featured_play_list_outlined = IconData(0xe1a2, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">featured_play_list</i> &#x2014; material icon named "featured play list rounded".
+  static const IconData featured_play_list_rounded = IconData(0xf1d1, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">featured_play_list</i> &#x2014; material icon named "featured play list sharp".
+  static const IconData featured_play_list_sharp = IconData(0xeca3, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">featured_video</i> &#x2014; material icon named "featured video".
+  static const IconData featured_video = IconData(0xe724, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">featured_video</i> &#x2014; material icon named "featured video outlined".
+  static const IconData featured_video_outlined = IconData(0xe1a3, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">featured_video</i> &#x2014; material icon named "featured video rounded".
+  static const IconData featured_video_rounded = IconData(0xf1d2, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">featured_video</i> &#x2014; material icon named "featured video sharp".
+  static const IconData featured_video_sharp = IconData(0xeca4, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">feedback</i> &#x2014; material icon named "feedback".
+  static const IconData feedback = IconData(0xe725, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">feedback</i> &#x2014; material icon named "feedback outlined".
+  static const IconData feedback_outlined = IconData(0xe1a4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">feedback</i> &#x2014; material icon named "feedback rounded".
+  static const IconData feedback_rounded = IconData(0xf1d3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">feedback</i> &#x2014; material icon named "feedback sharp".
+  static const IconData feedback_sharp = IconData(0xeca5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fence</i> &#x2014; material icon named "fence".
+  static const IconData fence = IconData(0xe726, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fence</i> &#x2014; material icon named "fence outlined".
+  static const IconData fence_outlined = IconData(0xe1a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fence</i> &#x2014; material icon named "fence rounded".
+  static const IconData fence_rounded = IconData(0xf1d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fence</i> &#x2014; material icon named "fence sharp".
+  static const IconData fence_sharp = IconData(0xeca6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">festival</i> &#x2014; material icon named "festival".
+  static const IconData festival = IconData(0xe727, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fiber_dvr</i> &#x2014; material icon named "fiber dvr".
+  static const IconData fiber_dvr = IconData(0xe728, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fiber_dvr</i> &#x2014; material icon named "fiber dvr outlined".
+  static const IconData fiber_dvr_outlined = IconData(0xe1a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fiber_dvr</i> &#x2014; material icon named "fiber dvr rounded".
+  static const IconData fiber_dvr_rounded = IconData(0xf1d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fiber_dvr</i> &#x2014; material icon named "fiber dvr sharp".
+  static const IconData fiber_dvr_sharp = IconData(0xeca7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fiber_manual_record</i> &#x2014; material icon named "fiber manual record".
+  static const IconData fiber_manual_record = IconData(0xe729, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fiber_manual_record</i> &#x2014; material icon named "fiber manual record outlined".
+  static const IconData fiber_manual_record_outlined = IconData(0xe1a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fiber_manual_record</i> &#x2014; material icon named "fiber manual record rounded".
+  static const IconData fiber_manual_record_rounded = IconData(0xf1d6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fiber_manual_record</i> &#x2014; material icon named "fiber manual record sharp".
+  static const IconData fiber_manual_record_sharp = IconData(0xeca8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fiber_new</i> &#x2014; material icon named "fiber new".
+  static const IconData fiber_new = IconData(0xe72a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fiber_new</i> &#x2014; material icon named "fiber new outlined".
+  static const IconData fiber_new_outlined = IconData(0xe1a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fiber_new</i> &#x2014; material icon named "fiber new rounded".
+  static const IconData fiber_new_rounded = IconData(0xf1d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fiber_new</i> &#x2014; material icon named "fiber new sharp".
+  static const IconData fiber_new_sharp = IconData(0xeca9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fiber_pin</i> &#x2014; material icon named "fiber pin".
+  static const IconData fiber_pin = IconData(0xe72b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fiber_pin</i> &#x2014; material icon named "fiber pin outlined".
+  static const IconData fiber_pin_outlined = IconData(0xe1a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fiber_pin</i> &#x2014; material icon named "fiber pin rounded".
+  static const IconData fiber_pin_rounded = IconData(0xf1d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fiber_pin</i> &#x2014; material icon named "fiber pin sharp".
+  static const IconData fiber_pin_sharp = IconData(0xecaa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fiber_smart_record</i> &#x2014; material icon named "fiber smart record".
+  static const IconData fiber_smart_record = IconData(0xe72c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fiber_smart_record</i> &#x2014; material icon named "fiber smart record outlined".
+  static const IconData fiber_smart_record_outlined = IconData(0xe1aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fiber_smart_record</i> &#x2014; material icon named "fiber smart record rounded".
+  static const IconData fiber_smart_record_rounded = IconData(0xf1d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fiber_smart_record</i> &#x2014; material icon named "fiber smart record sharp".
+  static const IconData fiber_smart_record_sharp = IconData(0xecab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">file_copy</i> &#x2014; material icon named "file copy".
+  static const IconData file_copy = IconData(0xe72d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">file_copy</i> &#x2014; material icon named "file copy outlined".
+  static const IconData file_copy_outlined = IconData(0xe1ab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">file_copy</i> &#x2014; material icon named "file copy rounded".
+  static const IconData file_copy_rounded = IconData(0xf1da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">file_copy</i> &#x2014; material icon named "file copy sharp".
+  static const IconData file_copy_sharp = IconData(0xecac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">file_download</i> &#x2014; material icon named "file download".
+  static const IconData file_download = IconData(0xe72e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">file_download_done</i> &#x2014; material icon named "file download done".
+  static const IconData file_download_done = IconData(0xe72f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">file_present</i> &#x2014; material icon named "file present".
+  static const IconData file_present = IconData(0xe730, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">file_upload</i> &#x2014; material icon named "file upload".
+  static const IconData file_upload = IconData(0xe731, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter</i> &#x2014; material icon named "filter".
+  static const IconData filter = IconData(0xe732, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_1</i> &#x2014; material icon named "filter 1".
+  static const IconData filter_1 = IconData(0xe733, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_1</i> &#x2014; material icon named "filter 1 outlined".
+  static const IconData filter_1_outlined = IconData(0xe1ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_1</i> &#x2014; material icon named "filter 1 rounded".
+  static const IconData filter_1_rounded = IconData(0xf1db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_1</i> &#x2014; material icon named "filter 1 sharp".
+  static const IconData filter_1_sharp = IconData(0xecad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_2</i> &#x2014; material icon named "filter 2".
+  static const IconData filter_2 = IconData(0xe734, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_2</i> &#x2014; material icon named "filter 2 outlined".
+  static const IconData filter_2_outlined = IconData(0xe1ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_2</i> &#x2014; material icon named "filter 2 rounded".
+  static const IconData filter_2_rounded = IconData(0xf1dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_2</i> &#x2014; material icon named "filter 2 sharp".
+  static const IconData filter_2_sharp = IconData(0xecae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_3</i> &#x2014; material icon named "filter 3".
+  static const IconData filter_3 = IconData(0xe735, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_3</i> &#x2014; material icon named "filter 3 outlined".
+  static const IconData filter_3_outlined = IconData(0xe1ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_3</i> &#x2014; material icon named "filter 3 rounded".
+  static const IconData filter_3_rounded = IconData(0xf1dd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_3</i> &#x2014; material icon named "filter 3 sharp".
+  static const IconData filter_3_sharp = IconData(0xecaf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_4</i> &#x2014; material icon named "filter 4".
+  static const IconData filter_4 = IconData(0xe736, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_4</i> &#x2014; material icon named "filter 4 outlined".
+  static const IconData filter_4_outlined = IconData(0xe1af, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_4</i> &#x2014; material icon named "filter 4 rounded".
+  static const IconData filter_4_rounded = IconData(0xf1de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_4</i> &#x2014; material icon named "filter 4 sharp".
+  static const IconData filter_4_sharp = IconData(0xecb0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_5</i> &#x2014; material icon named "filter 5".
+  static const IconData filter_5 = IconData(0xe737, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_5</i> &#x2014; material icon named "filter 5 outlined".
+  static const IconData filter_5_outlined = IconData(0xe1b0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_5</i> &#x2014; material icon named "filter 5 rounded".
+  static const IconData filter_5_rounded = IconData(0xf1df, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_5</i> &#x2014; material icon named "filter 5 sharp".
+  static const IconData filter_5_sharp = IconData(0xecb1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_6</i> &#x2014; material icon named "filter 6".
+  static const IconData filter_6 = IconData(0xe738, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_6</i> &#x2014; material icon named "filter 6 outlined".
+  static const IconData filter_6_outlined = IconData(0xe1b1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_6</i> &#x2014; material icon named "filter 6 rounded".
+  static const IconData filter_6_rounded = IconData(0xf1e0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_6</i> &#x2014; material icon named "filter 6 sharp".
+  static const IconData filter_6_sharp = IconData(0xecb2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_7</i> &#x2014; material icon named "filter 7".
+  static const IconData filter_7 = IconData(0xe739, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_7</i> &#x2014; material icon named "filter 7 outlined".
+  static const IconData filter_7_outlined = IconData(0xe1b2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_7</i> &#x2014; material icon named "filter 7 rounded".
+  static const IconData filter_7_rounded = IconData(0xf1e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_7</i> &#x2014; material icon named "filter 7 sharp".
+  static const IconData filter_7_sharp = IconData(0xecb3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_8</i> &#x2014; material icon named "filter 8".
+  static const IconData filter_8 = IconData(0xe73a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_8</i> &#x2014; material icon named "filter 8 outlined".
+  static const IconData filter_8_outlined = IconData(0xe1b3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_8</i> &#x2014; material icon named "filter 8 rounded".
+  static const IconData filter_8_rounded = IconData(0xf1e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_8</i> &#x2014; material icon named "filter 8 sharp".
+  static const IconData filter_8_sharp = IconData(0xecb4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_9</i> &#x2014; material icon named "filter 9".
+  static const IconData filter_9 = IconData(0xe73b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_9</i> &#x2014; material icon named "filter 9 outlined".
+  static const IconData filter_9_outlined = IconData(0xe1b4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_9_plus</i> &#x2014; material icon named "filter 9 plus".
+  static const IconData filter_9_plus = IconData(0xe73c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_9_plus</i> &#x2014; material icon named "filter 9 plus outlined".
+  static const IconData filter_9_plus_outlined = IconData(0xe1b5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_9_plus</i> &#x2014; material icon named "filter 9 plus rounded".
+  static const IconData filter_9_plus_rounded = IconData(0xf1e3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_9_plus</i> &#x2014; material icon named "filter 9 plus sharp".
+  static const IconData filter_9_plus_sharp = IconData(0xecb5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_9</i> &#x2014; material icon named "filter 9 rounded".
+  static const IconData filter_9_rounded = IconData(0xf1e4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_9</i> &#x2014; material icon named "filter 9 sharp".
+  static const IconData filter_9_sharp = IconData(0xecb6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_alt</i> &#x2014; material icon named "filter alt".
+  static const IconData filter_alt = IconData(0xe73d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_alt</i> &#x2014; material icon named "filter alt outlined".
+  static const IconData filter_alt_outlined = IconData(0xe1b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_alt</i> &#x2014; material icon named "filter alt rounded".
+  static const IconData filter_alt_rounded = IconData(0xf1e5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_alt</i> &#x2014; material icon named "filter alt sharp".
+  static const IconData filter_alt_sharp = IconData(0xecb7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_b_and_w</i> &#x2014; material icon named "filter b and w".
+  static const IconData filter_b_and_w = IconData(0xe73e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_b_and_w</i> &#x2014; material icon named "filter b and w outlined".
+  static const IconData filter_b_and_w_outlined = IconData(0xe1b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_b_and_w</i> &#x2014; material icon named "filter b and w rounded".
+  static const IconData filter_b_and_w_rounded = IconData(0xf1e6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_b_and_w</i> &#x2014; material icon named "filter b and w sharp".
+  static const IconData filter_b_and_w_sharp = IconData(0xecb8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_center_focus</i> &#x2014; material icon named "filter center focus".
+  static const IconData filter_center_focus = IconData(0xe73f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_center_focus</i> &#x2014; material icon named "filter center focus outlined".
+  static const IconData filter_center_focus_outlined = IconData(0xe1b8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_center_focus</i> &#x2014; material icon named "filter center focus rounded".
+  static const IconData filter_center_focus_rounded = IconData(0xf1e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_center_focus</i> &#x2014; material icon named "filter center focus sharp".
+  static const IconData filter_center_focus_sharp = IconData(0xecb9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_drama</i> &#x2014; material icon named "filter drama".
+  static const IconData filter_drama = IconData(0xe740, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_drama</i> &#x2014; material icon named "filter drama outlined".
+  static const IconData filter_drama_outlined = IconData(0xe1b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_drama</i> &#x2014; material icon named "filter drama rounded".
+  static const IconData filter_drama_rounded = IconData(0xf1e8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_drama</i> &#x2014; material icon named "filter drama sharp".
+  static const IconData filter_drama_sharp = IconData(0xecba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_frames</i> &#x2014; material icon named "filter frames".
+  static const IconData filter_frames = IconData(0xe741, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_frames</i> &#x2014; material icon named "filter frames outlined".
+  static const IconData filter_frames_outlined = IconData(0xe1ba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_frames</i> &#x2014; material icon named "filter frames rounded".
+  static const IconData filter_frames_rounded = IconData(0xf1e9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_frames</i> &#x2014; material icon named "filter frames sharp".
+  static const IconData filter_frames_sharp = IconData(0xecbb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_hdr</i> &#x2014; material icon named "filter hdr".
+  static const IconData filter_hdr = IconData(0xe742, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_hdr</i> &#x2014; material icon named "filter hdr outlined".
+  static const IconData filter_hdr_outlined = IconData(0xe1bb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_hdr</i> &#x2014; material icon named "filter hdr rounded".
+  static const IconData filter_hdr_rounded = IconData(0xf1ea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_hdr</i> &#x2014; material icon named "filter hdr sharp".
+  static const IconData filter_hdr_sharp = IconData(0xecbc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_list</i> &#x2014; material icon named "filter list".
+  static const IconData filter_list = IconData(0xe743, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_list_alt</i> &#x2014; material icon named "filter list alt".
+  static const IconData filter_list_alt = IconData(0xe744, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_list</i> &#x2014; material icon named "filter list outlined".
+  static const IconData filter_list_outlined = IconData(0xe1bc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_list</i> &#x2014; material icon named "filter list rounded".
+  static const IconData filter_list_rounded = IconData(0xf1eb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_list</i> &#x2014; material icon named "filter list sharp".
+  static const IconData filter_list_sharp = IconData(0xecbd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_none</i> &#x2014; material icon named "filter none".
+  static const IconData filter_none = IconData(0xe745, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_none</i> &#x2014; material icon named "filter none outlined".
+  static const IconData filter_none_outlined = IconData(0xe1bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_none</i> &#x2014; material icon named "filter none rounded".
+  static const IconData filter_none_rounded = IconData(0xf1ec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_none</i> &#x2014; material icon named "filter none sharp".
+  static const IconData filter_none_sharp = IconData(0xecbe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter</i> &#x2014; material icon named "filter outlined".
+  static const IconData filter_outlined = IconData(0xe1be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter</i> &#x2014; material icon named "filter rounded".
+  static const IconData filter_rounded = IconData(0xf1ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter</i> &#x2014; material icon named "filter sharp".
+  static const IconData filter_sharp = IconData(0xecbf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_tilt_shift</i> &#x2014; material icon named "filter tilt shift".
+  static const IconData filter_tilt_shift = IconData(0xe746, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_tilt_shift</i> &#x2014; material icon named "filter tilt shift outlined".
+  static const IconData filter_tilt_shift_outlined = IconData(0xe1bf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_tilt_shift</i> &#x2014; material icon named "filter tilt shift rounded".
+  static const IconData filter_tilt_shift_rounded = IconData(0xf1ee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_tilt_shift</i> &#x2014; material icon named "filter tilt shift sharp".
+  static const IconData filter_tilt_shift_sharp = IconData(0xecc0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">filter_vintage</i> &#x2014; material icon named "filter vintage".
+  static const IconData filter_vintage = IconData(0xe747, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">filter_vintage</i> &#x2014; material icon named "filter vintage outlined".
+  static const IconData filter_vintage_outlined = IconData(0xe1c0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">filter_vintage</i> &#x2014; material icon named "filter vintage rounded".
+  static const IconData filter_vintage_rounded = IconData(0xf1ef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">filter_vintage</i> &#x2014; material icon named "filter vintage sharp".
+  static const IconData filter_vintage_sharp = IconData(0xecc1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">find_in_page</i> &#x2014; material icon named "find in page".
+  static const IconData find_in_page = IconData(0xe748, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">find_in_page</i> &#x2014; material icon named "find in page outlined".
+  static const IconData find_in_page_outlined = IconData(0xe1c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">find_in_page</i> &#x2014; material icon named "find in page rounded".
+  static const IconData find_in_page_rounded = IconData(0xf1f0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">find_in_page</i> &#x2014; material icon named "find in page sharp".
+  static const IconData find_in_page_sharp = IconData(0xecc2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">find_replace</i> &#x2014; material icon named "find replace".
+  static const IconData find_replace = IconData(0xe749, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">find_replace</i> &#x2014; material icon named "find replace outlined".
+  static const IconData find_replace_outlined = IconData(0xe1c2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">find_replace</i> &#x2014; material icon named "find replace rounded".
+  static const IconData find_replace_rounded = IconData(0xf1f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">find_replace</i> &#x2014; material icon named "find replace sharp".
+  static const IconData find_replace_sharp = IconData(0xecc3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fingerprint</i> &#x2014; material icon named "fingerprint".
+  static const IconData fingerprint = IconData(0xe74a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fingerprint</i> &#x2014; material icon named "fingerprint outlined".
+  static const IconData fingerprint_outlined = IconData(0xe1c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fingerprint</i> &#x2014; material icon named "fingerprint rounded".
+  static const IconData fingerprint_rounded = IconData(0xf1f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fingerprint</i> &#x2014; material icon named "fingerprint sharp".
+  static const IconData fingerprint_sharp = IconData(0xecc4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fire_extinguisher</i> &#x2014; material icon named "fire extinguisher".
+  static const IconData fire_extinguisher = IconData(0xe74b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fire_extinguisher</i> &#x2014; material icon named "fire extinguisher outlined".
+  static const IconData fire_extinguisher_outlined = IconData(0xe1c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fire_extinguisher</i> &#x2014; material icon named "fire extinguisher rounded".
+  static const IconData fire_extinguisher_rounded = IconData(0xf1f3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fire_extinguisher</i> &#x2014; material icon named "fire extinguisher sharp".
+  static const IconData fire_extinguisher_sharp = IconData(0xecc5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fire_hydrant</i> &#x2014; material icon named "fire hydrant".
+  static const IconData fire_hydrant = IconData(0xe74c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fireplace</i> &#x2014; material icon named "fireplace".
+  static const IconData fireplace = IconData(0xe74d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fireplace</i> &#x2014; material icon named "fireplace outlined".
+  static const IconData fireplace_outlined = IconData(0xe1c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fireplace</i> &#x2014; material icon named "fireplace rounded".
+  static const IconData fireplace_rounded = IconData(0xf1f4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fireplace</i> &#x2014; material icon named "fireplace sharp".
+  static const IconData fireplace_sharp = IconData(0xecc6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">first_page</i> &#x2014; material icon named "first page".
+  static const IconData first_page = IconData(0xe74e, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">first_page</i> &#x2014; material icon named "first page outlined".
+  static const IconData first_page_outlined = IconData(0xe1c6, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">first_page</i> &#x2014; material icon named "first page rounded".
+  static const IconData first_page_rounded = IconData(0xf1f5, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">first_page</i> &#x2014; material icon named "first page sharp".
+  static const IconData first_page_sharp = IconData(0xecc7, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">fit_screen</i> &#x2014; material icon named "fit screen".
+  static const IconData fit_screen = IconData(0xe74f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fitness_center</i> &#x2014; material icon named "fitness center".
+  static const IconData fitness_center = IconData(0xe750, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fitness_center</i> &#x2014; material icon named "fitness center outlined".
+  static const IconData fitness_center_outlined = IconData(0xe1c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fitness_center</i> &#x2014; material icon named "fitness center rounded".
+  static const IconData fitness_center_rounded = IconData(0xf1f6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fitness_center</i> &#x2014; material icon named "fitness center sharp".
+  static const IconData fitness_center_sharp = IconData(0xecc8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">flag</i> &#x2014; material icon named "flag".
+  static const IconData flag = IconData(0xe751, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">flag</i> &#x2014; material icon named "flag outlined".
+  static const IconData flag_outlined = IconData(0xe1c8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">flag</i> &#x2014; material icon named "flag rounded".
+  static const IconData flag_rounded = IconData(0xf1f7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">flag</i> &#x2014; material icon named "flag sharp".
+  static const IconData flag_sharp = IconData(0xecc9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">flaky</i> &#x2014; material icon named "flaky".
+  static const IconData flaky = IconData(0xe752, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">flaky</i> &#x2014; material icon named "flaky outlined".
+  static const IconData flaky_outlined = IconData(0xe1c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">flaky</i> &#x2014; material icon named "flaky rounded".
+  static const IconData flaky_rounded = IconData(0xf1f8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">flaky</i> &#x2014; material icon named "flaky sharp".
+  static const IconData flaky_sharp = IconData(0xecca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">flare</i> &#x2014; material icon named "flare".
+  static const IconData flare = IconData(0xe753, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">flare</i> &#x2014; material icon named "flare outlined".
+  static const IconData flare_outlined = IconData(0xe1ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">flare</i> &#x2014; material icon named "flare rounded".
+  static const IconData flare_rounded = IconData(0xf1f9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">flare</i> &#x2014; material icon named "flare sharp".
+  static const IconData flare_sharp = IconData(0xeccb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">flash_auto</i> &#x2014; material icon named "flash auto".
+  static const IconData flash_auto = IconData(0xe754, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">flash_auto</i> &#x2014; material icon named "flash auto outlined".
+  static const IconData flash_auto_outlined = IconData(0xe1cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">flash_auto</i> &#x2014; material icon named "flash auto rounded".
+  static const IconData flash_auto_rounded = IconData(0xf1fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">flash_auto</i> &#x2014; material icon named "flash auto sharp".
+  static const IconData flash_auto_sharp = IconData(0xeccc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">flash_off</i> &#x2014; material icon named "flash off".
+  static const IconData flash_off = IconData(0xe755, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">flash_off</i> &#x2014; material icon named "flash off outlined".
+  static const IconData flash_off_outlined = IconData(0xe1cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">flash_off</i> &#x2014; material icon named "flash off rounded".
+  static const IconData flash_off_rounded = IconData(0xf1fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">flash_off</i> &#x2014; material icon named "flash off sharp".
+  static const IconData flash_off_sharp = IconData(0xeccd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">flash_on</i> &#x2014; material icon named "flash on".
+  static const IconData flash_on = IconData(0xe756, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">flash_on</i> &#x2014; material icon named "flash on outlined".
+  static const IconData flash_on_outlined = IconData(0xe1cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">flash_on</i> &#x2014; material icon named "flash on rounded".
+  static const IconData flash_on_rounded = IconData(0xf1fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">flash_on</i> &#x2014; material icon named "flash on sharp".
+  static const IconData flash_on_sharp = IconData(0xecce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">flight</i> &#x2014; material icon named "flight".
+  static const IconData flight = IconData(0xe757, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">flight_land</i> &#x2014; material icon named "flight land".
+  static const IconData flight_land = IconData(0xe758, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">flight_land</i> &#x2014; material icon named "flight land outlined".
+  static const IconData flight_land_outlined = IconData(0xe1ce, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">flight_land</i> &#x2014; material icon named "flight land rounded".
+  static const IconData flight_land_rounded = IconData(0xf1fd, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">flight_land</i> &#x2014; material icon named "flight land sharp".
+  static const IconData flight_land_sharp = IconData(0xeccf, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">flight</i> &#x2014; material icon named "flight outlined".
+  static const IconData flight_outlined = IconData(0xe1cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">flight</i> &#x2014; material icon named "flight rounded".
+  static const IconData flight_rounded = IconData(0xf1fe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">flight</i> &#x2014; material icon named "flight sharp".
+  static const IconData flight_sharp = IconData(0xecd0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">flight_takeoff</i> &#x2014; material icon named "flight takeoff".
+  static const IconData flight_takeoff = IconData(0xe759, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">flight_takeoff</i> &#x2014; material icon named "flight takeoff outlined".
+  static const IconData flight_takeoff_outlined = IconData(0xe1d0, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">flight_takeoff</i> &#x2014; material icon named "flight takeoff rounded".
+  static const IconData flight_takeoff_rounded = IconData(0xf1ff, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">flight_takeoff</i> &#x2014; material icon named "flight takeoff sharp".
+  static const IconData flight_takeoff_sharp = IconData(0xecd1, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">flip</i> &#x2014; material icon named "flip".
+  static const IconData flip = IconData(0xe75a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">flip_camera_android</i> &#x2014; material icon named "flip camera android".
+  static const IconData flip_camera_android = IconData(0xe75b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">flip_camera_android</i> &#x2014; material icon named "flip camera android outlined".
+  static const IconData flip_camera_android_outlined = IconData(0xe1d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">flip_camera_android</i> &#x2014; material icon named "flip camera android rounded".
+  static const IconData flip_camera_android_rounded = IconData(0xf200, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">flip_camera_android</i> &#x2014; material icon named "flip camera android sharp".
+  static const IconData flip_camera_android_sharp = IconData(0xecd2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">flip_camera_ios</i> &#x2014; material icon named "flip camera ios".
+  static const IconData flip_camera_ios = IconData(0xe75c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">flip_camera_ios</i> &#x2014; material icon named "flip camera ios outlined".
+  static const IconData flip_camera_ios_outlined = IconData(0xe1d2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">flip_camera_ios</i> &#x2014; material icon named "flip camera ios rounded".
+  static const IconData flip_camera_ios_rounded = IconData(0xf201, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">flip_camera_ios</i> &#x2014; material icon named "flip camera ios sharp".
+  static const IconData flip_camera_ios_sharp = IconData(0xecd3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">flip</i> &#x2014; material icon named "flip outlined".
+  static const IconData flip_outlined = IconData(0xe1d3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">flip</i> &#x2014; material icon named "flip rounded".
+  static const IconData flip_rounded = IconData(0xf202, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">flip</i> &#x2014; material icon named "flip sharp".
+  static const IconData flip_sharp = IconData(0xecd4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">flip_to_back</i> &#x2014; material icon named "flip to back".
+  static const IconData flip_to_back = IconData(0xe75d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">flip_to_back</i> &#x2014; material icon named "flip to back outlined".
+  static const IconData flip_to_back_outlined = IconData(0xe1d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">flip_to_back</i> &#x2014; material icon named "flip to back rounded".
+  static const IconData flip_to_back_rounded = IconData(0xf203, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">flip_to_back</i> &#x2014; material icon named "flip to back sharp".
+  static const IconData flip_to_back_sharp = IconData(0xecd5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">flip_to_front</i> &#x2014; material icon named "flip to front".
+  static const IconData flip_to_front = IconData(0xe75e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">flip_to_front</i> &#x2014; material icon named "flip to front outlined".
+  static const IconData flip_to_front_outlined = IconData(0xe1d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">flip_to_front</i> &#x2014; material icon named "flip to front rounded".
+  static const IconData flip_to_front_rounded = IconData(0xf204, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">flip_to_front</i> &#x2014; material icon named "flip to front sharp".
+  static const IconData flip_to_front_sharp = IconData(0xecd6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">folder</i> &#x2014; material icon named "folder".
+  static const IconData folder = IconData(0xe75f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">folder_open</i> &#x2014; material icon named "folder open".
+  static const IconData folder_open = IconData(0xe760, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">folder_open</i> &#x2014; material icon named "folder open outlined".
+  static const IconData folder_open_outlined = IconData(0xe1d6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">folder_open</i> &#x2014; material icon named "folder open rounded".
+  static const IconData folder_open_rounded = IconData(0xf205, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">folder_open</i> &#x2014; material icon named "folder open sharp".
+  static const IconData folder_open_sharp = IconData(0xecd7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">folder</i> &#x2014; material icon named "folder outlined".
+  static const IconData folder_outlined = IconData(0xe1d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">folder</i> &#x2014; material icon named "folder rounded".
+  static const IconData folder_rounded = IconData(0xf206, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">folder_shared</i> &#x2014; material icon named "folder shared".
+  static const IconData folder_shared = IconData(0xe761, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">folder_shared</i> &#x2014; material icon named "folder shared outlined".
+  static const IconData folder_shared_outlined = IconData(0xe1d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">folder_shared</i> &#x2014; material icon named "folder shared rounded".
+  static const IconData folder_shared_rounded = IconData(0xf207, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">folder_shared</i> &#x2014; material icon named "folder shared sharp".
+  static const IconData folder_shared_sharp = IconData(0xecd8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">folder</i> &#x2014; material icon named "folder sharp".
+  static const IconData folder_sharp = IconData(0xecd9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">folder_special</i> &#x2014; material icon named "folder special".
+  static const IconData folder_special = IconData(0xe762, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">folder_special</i> &#x2014; material icon named "folder special outlined".
+  static const IconData folder_special_outlined = IconData(0xe1d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">folder_special</i> &#x2014; material icon named "folder special rounded".
+  static const IconData folder_special_rounded = IconData(0xf208, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">folder_special</i> &#x2014; material icon named "folder special sharp".
+  static const IconData folder_special_sharp = IconData(0xecda, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">follow_the_signs</i> &#x2014; material icon named "follow the signs".
+  static const IconData follow_the_signs = IconData(0xe763, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">follow_the_signs</i> &#x2014; material icon named "follow the signs outlined".
+  static const IconData follow_the_signs_outlined = IconData(0xe1da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">follow_the_signs</i> &#x2014; material icon named "follow the signs rounded".
+  static const IconData follow_the_signs_rounded = IconData(0xf209, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">follow_the_signs</i> &#x2014; material icon named "follow the signs sharp".
+  static const IconData follow_the_signs_sharp = IconData(0xecdb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">font_download</i> &#x2014; material icon named "font download".
+  static const IconData font_download = IconData(0xe764, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">font_download</i> &#x2014; material icon named "font download outlined".
+  static const IconData font_download_outlined = IconData(0xe1db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">font_download</i> &#x2014; material icon named "font download rounded".
+  static const IconData font_download_rounded = IconData(0xf20a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">font_download</i> &#x2014; material icon named "font download sharp".
+  static const IconData font_download_sharp = IconData(0xecdc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">food_bank</i> &#x2014; material icon named "food bank".
+  static const IconData food_bank = IconData(0xe765, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">food_bank</i> &#x2014; material icon named "food bank outlined".
+  static const IconData food_bank_outlined = IconData(0xe1dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">food_bank</i> &#x2014; material icon named "food bank rounded".
+  static const IconData food_bank_rounded = IconData(0xf20b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">food_bank</i> &#x2014; material icon named "food bank sharp".
+  static const IconData food_bank_sharp = IconData(0xecdd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_align_center</i> &#x2014; material icon named "format align center".
+  static const IconData format_align_center = IconData(0xe766, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_align_center</i> &#x2014; material icon named "format align center outlined".
+  static const IconData format_align_center_outlined = IconData(0xe1dd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_align_center</i> &#x2014; material icon named "format align center rounded".
+  static const IconData format_align_center_rounded = IconData(0xf20c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_align_center</i> &#x2014; material icon named "format align center sharp".
+  static const IconData format_align_center_sharp = IconData(0xecde, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_align_justify</i> &#x2014; material icon named "format align justify".
+  static const IconData format_align_justify = IconData(0xe767, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_align_justify</i> &#x2014; material icon named "format align justify outlined".
+  static const IconData format_align_justify_outlined = IconData(0xe1de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_align_justify</i> &#x2014; material icon named "format align justify rounded".
+  static const IconData format_align_justify_rounded = IconData(0xf20d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_align_justify</i> &#x2014; material icon named "format align justify sharp".
+  static const IconData format_align_justify_sharp = IconData(0xecdf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_align_left</i> &#x2014; material icon named "format align left".
+  static const IconData format_align_left = IconData(0xe768, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_align_left</i> &#x2014; material icon named "format align left outlined".
+  static const IconData format_align_left_outlined = IconData(0xe1df, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_align_left</i> &#x2014; material icon named "format align left rounded".
+  static const IconData format_align_left_rounded = IconData(0xf20e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_align_left</i> &#x2014; material icon named "format align left sharp".
+  static const IconData format_align_left_sharp = IconData(0xece0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_align_right</i> &#x2014; material icon named "format align right".
+  static const IconData format_align_right = IconData(0xe769, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_align_right</i> &#x2014; material icon named "format align right outlined".
+  static const IconData format_align_right_outlined = IconData(0xe1e0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_align_right</i> &#x2014; material icon named "format align right rounded".
+  static const IconData format_align_right_rounded = IconData(0xf20f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_align_right</i> &#x2014; material icon named "format align right sharp".
+  static const IconData format_align_right_sharp = IconData(0xece1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_bold</i> &#x2014; material icon named "format bold".
+  static const IconData format_bold = IconData(0xe76a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_bold</i> &#x2014; material icon named "format bold outlined".
+  static const IconData format_bold_outlined = IconData(0xe1e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_bold</i> &#x2014; material icon named "format bold rounded".
+  static const IconData format_bold_rounded = IconData(0xf210, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_bold</i> &#x2014; material icon named "format bold sharp".
+  static const IconData format_bold_sharp = IconData(0xece2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_clear</i> &#x2014; material icon named "format clear".
+  static const IconData format_clear = IconData(0xe76b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_clear</i> &#x2014; material icon named "format clear outlined".
+  static const IconData format_clear_outlined = IconData(0xe1e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_clear</i> &#x2014; material icon named "format clear rounded".
+  static const IconData format_clear_rounded = IconData(0xf211, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_clear</i> &#x2014; material icon named "format clear sharp".
+  static const IconData format_clear_sharp = IconData(0xece3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_color_fill</i> &#x2014; material icon named "format color fill".
+  static const IconData format_color_fill = IconData(0xe76c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_color_reset</i> &#x2014; material icon named "format color reset".
+  static const IconData format_color_reset = IconData(0xe76d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_color_reset</i> &#x2014; material icon named "format color reset outlined".
+  static const IconData format_color_reset_outlined = IconData(0xe1e3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_color_reset</i> &#x2014; material icon named "format color reset rounded".
+  static const IconData format_color_reset_rounded = IconData(0xf212, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_color_reset</i> &#x2014; material icon named "format color reset sharp".
+  static const IconData format_color_reset_sharp = IconData(0xece4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_color_text</i> &#x2014; material icon named "format color text".
+  static const IconData format_color_text = IconData(0xe76e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_indent_decrease</i> &#x2014; material icon named "format indent decrease".
+  static const IconData format_indent_decrease = IconData(0xe76f, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">format_indent_decrease</i> &#x2014; material icon named "format indent decrease outlined".
+  static const IconData format_indent_decrease_outlined = IconData(0xe1e4, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">format_indent_decrease</i> &#x2014; material icon named "format indent decrease rounded".
+  static const IconData format_indent_decrease_rounded = IconData(0xf213, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">format_indent_decrease</i> &#x2014; material icon named "format indent decrease sharp".
+  static const IconData format_indent_decrease_sharp = IconData(0xece5, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">format_indent_increase</i> &#x2014; material icon named "format indent increase".
+  static const IconData format_indent_increase = IconData(0xe770, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">format_indent_increase</i> &#x2014; material icon named "format indent increase outlined".
+  static const IconData format_indent_increase_outlined = IconData(0xe1e5, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">format_indent_increase</i> &#x2014; material icon named "format indent increase rounded".
+  static const IconData format_indent_increase_rounded = IconData(0xf214, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">format_indent_increase</i> &#x2014; material icon named "format indent increase sharp".
+  static const IconData format_indent_increase_sharp = IconData(0xece6, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">format_italic</i> &#x2014; material icon named "format italic".
+  static const IconData format_italic = IconData(0xe771, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_italic</i> &#x2014; material icon named "format italic outlined".
+  static const IconData format_italic_outlined = IconData(0xe1e6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_italic</i> &#x2014; material icon named "format italic rounded".
+  static const IconData format_italic_rounded = IconData(0xf215, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_italic</i> &#x2014; material icon named "format italic sharp".
+  static const IconData format_italic_sharp = IconData(0xece7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_line_spacing</i> &#x2014; material icon named "format line spacing".
+  static const IconData format_line_spacing = IconData(0xe772, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_line_spacing</i> &#x2014; material icon named "format line spacing outlined".
+  static const IconData format_line_spacing_outlined = IconData(0xe1e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_line_spacing</i> &#x2014; material icon named "format line spacing rounded".
+  static const IconData format_line_spacing_rounded = IconData(0xf216, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_line_spacing</i> &#x2014; material icon named "format line spacing sharp".
+  static const IconData format_line_spacing_sharp = IconData(0xece8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_list_bulleted</i> &#x2014; material icon named "format list bulleted".
+  static const IconData format_list_bulleted = IconData(0xe773, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">format_list_bulleted</i> &#x2014; material icon named "format list bulleted outlined".
+  static const IconData format_list_bulleted_outlined = IconData(0xe1e8, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">format_list_bulleted</i> &#x2014; material icon named "format list bulleted rounded".
+  static const IconData format_list_bulleted_rounded = IconData(0xf217, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">format_list_bulleted</i> &#x2014; material icon named "format list bulleted sharp".
+  static const IconData format_list_bulleted_sharp = IconData(0xece9, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">format_list_numbered</i> &#x2014; material icon named "format list numbered".
+  static const IconData format_list_numbered = IconData(0xe774, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_list_numbered</i> &#x2014; material icon named "format list numbered outlined".
+  static const IconData format_list_numbered_outlined = IconData(0xe1e9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_list_numbered</i> &#x2014; material icon named "format list numbered rounded".
+  static const IconData format_list_numbered_rounded = IconData(0xf218, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_list_numbered_rtl</i> &#x2014; material icon named "format list numbered rtl".
+  static const IconData format_list_numbered_rtl = IconData(0xe775, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_list_numbered_rtl</i> &#x2014; material icon named "format list numbered rtl outlined".
+  static const IconData format_list_numbered_rtl_outlined = IconData(0xe1ea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_list_numbered_rtl</i> &#x2014; material icon named "format list numbered rtl rounded".
+  static const IconData format_list_numbered_rtl_rounded = IconData(0xf219, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_list_numbered_rtl</i> &#x2014; material icon named "format list numbered rtl sharp".
+  static const IconData format_list_numbered_rtl_sharp = IconData(0xecea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_list_numbered</i> &#x2014; material icon named "format list numbered sharp".
+  static const IconData format_list_numbered_sharp = IconData(0xeceb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_paint</i> &#x2014; material icon named "format paint".
+  static const IconData format_paint = IconData(0xe776, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_paint</i> &#x2014; material icon named "format paint outlined".
+  static const IconData format_paint_outlined = IconData(0xe1eb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_paint</i> &#x2014; material icon named "format paint rounded".
+  static const IconData format_paint_rounded = IconData(0xf21a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_paint</i> &#x2014; material icon named "format paint sharp".
+  static const IconData format_paint_sharp = IconData(0xecec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_quote</i> &#x2014; material icon named "format quote".
+  static const IconData format_quote = IconData(0xe777, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_quote</i> &#x2014; material icon named "format quote outlined".
+  static const IconData format_quote_outlined = IconData(0xe1ec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_quote</i> &#x2014; material icon named "format quote rounded".
+  static const IconData format_quote_rounded = IconData(0xf21b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_quote</i> &#x2014; material icon named "format quote sharp".
+  static const IconData format_quote_sharp = IconData(0xeced, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_shapes</i> &#x2014; material icon named "format shapes".
+  static const IconData format_shapes = IconData(0xe778, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_shapes</i> &#x2014; material icon named "format shapes outlined".
+  static const IconData format_shapes_outlined = IconData(0xe1ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_shapes</i> &#x2014; material icon named "format shapes rounded".
+  static const IconData format_shapes_rounded = IconData(0xf21c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_shapes</i> &#x2014; material icon named "format shapes sharp".
+  static const IconData format_shapes_sharp = IconData(0xecee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_size</i> &#x2014; material icon named "format size".
+  static const IconData format_size = IconData(0xe779, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_size</i> &#x2014; material icon named "format size outlined".
+  static const IconData format_size_outlined = IconData(0xe1ee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_size</i> &#x2014; material icon named "format size rounded".
+  static const IconData format_size_rounded = IconData(0xf21d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_size</i> &#x2014; material icon named "format size sharp".
+  static const IconData format_size_sharp = IconData(0xecef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_strikethrough</i> &#x2014; material icon named "format strikethrough".
+  static const IconData format_strikethrough = IconData(0xe77a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_strikethrough</i> &#x2014; material icon named "format strikethrough outlined".
+  static const IconData format_strikethrough_outlined = IconData(0xe1ef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_strikethrough</i> &#x2014; material icon named "format strikethrough rounded".
+  static const IconData format_strikethrough_rounded = IconData(0xf21e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_strikethrough</i> &#x2014; material icon named "format strikethrough sharp".
+  static const IconData format_strikethrough_sharp = IconData(0xecf0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_textdirection_l_to_r</i> &#x2014; material icon named "format textdirection l to r".
+  static const IconData format_textdirection_l_to_r = IconData(0xe77b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_textdirection_l_to_r</i> &#x2014; material icon named "format textdirection l to r outlined".
+  static const IconData format_textdirection_l_to_r_outlined = IconData(0xe1f0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_textdirection_l_to_r</i> &#x2014; material icon named "format textdirection l to r rounded".
+  static const IconData format_textdirection_l_to_r_rounded = IconData(0xf21f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_textdirection_l_to_r</i> &#x2014; material icon named "format textdirection l to r sharp".
+  static const IconData format_textdirection_l_to_r_sharp = IconData(0xecf1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_textdirection_r_to_l</i> &#x2014; material icon named "format textdirection r to l".
+  static const IconData format_textdirection_r_to_l = IconData(0xe77c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_textdirection_r_to_l</i> &#x2014; material icon named "format textdirection r to l outlined".
+  static const IconData format_textdirection_r_to_l_outlined = IconData(0xe1f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_textdirection_r_to_l</i> &#x2014; material icon named "format textdirection r to l rounded".
+  static const IconData format_textdirection_r_to_l_rounded = IconData(0xf220, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_textdirection_r_to_l</i> &#x2014; material icon named "format textdirection r to l sharp".
+  static const IconData format_textdirection_r_to_l_sharp = IconData(0xecf2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_underline</i> &#x2014; material icon named "format underline".
+  static const IconData format_underline = IconData(0xe77d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_underline</i> &#x2014; material icon named "format underline outlined".
+  static const IconData format_underline_outlined = IconData(0xe1f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_underline</i> &#x2014; material icon named "format underline rounded".
+  static const IconData format_underline_rounded = IconData(0xf221, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_underline</i> &#x2014; material icon named "format underline sharp".
+  static const IconData format_underline_sharp = IconData(0xecf3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">format_underlined</i> &#x2014; material icon named "format underlined".
+  static const IconData format_underlined = IconData(0xe77d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">format_underlined</i> &#x2014; material icon named "format underlined outlined".
+  static const IconData format_underlined_outlined = IconData(0xe1f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">format_underlined</i> &#x2014; material icon named "format underlined rounded".
+  static const IconData format_underlined_rounded = IconData(0xf221, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">format_underlined</i> &#x2014; material icon named "format underlined sharp".
+  static const IconData format_underlined_sharp = IconData(0xecf3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">forum</i> &#x2014; material icon named "forum".
+  static const IconData forum = IconData(0xe77e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">forum</i> &#x2014; material icon named "forum outlined".
+  static const IconData forum_outlined = IconData(0xe1f3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">forum</i> &#x2014; material icon named "forum rounded".
+  static const IconData forum_rounded = IconData(0xf222, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">forum</i> &#x2014; material icon named "forum sharp".
+  static const IconData forum_sharp = IconData(0xecf4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">forward</i> &#x2014; material icon named "forward".
+  static const IconData forward = IconData(0xe77f, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">forward_10</i> &#x2014; material icon named "forward 10".
+  static const IconData forward_10 = IconData(0xe780, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">forward_10</i> &#x2014; material icon named "forward 10 outlined".
+  static const IconData forward_10_outlined = IconData(0xe1f4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">forward_10</i> &#x2014; material icon named "forward 10 rounded".
+  static const IconData forward_10_rounded = IconData(0xf223, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">forward_10</i> &#x2014; material icon named "forward 10 sharp".
+  static const IconData forward_10_sharp = IconData(0xecf5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">forward_30</i> &#x2014; material icon named "forward 30".
+  static const IconData forward_30 = IconData(0xe781, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">forward_30</i> &#x2014; material icon named "forward 30 outlined".
+  static const IconData forward_30_outlined = IconData(0xe1f5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">forward_30</i> &#x2014; material icon named "forward 30 rounded".
+  static const IconData forward_30_rounded = IconData(0xf224, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">forward_30</i> &#x2014; material icon named "forward 30 sharp".
+  static const IconData forward_30_sharp = IconData(0xecf6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">forward_5</i> &#x2014; material icon named "forward 5".
+  static const IconData forward_5 = IconData(0xe782, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">forward_5</i> &#x2014; material icon named "forward 5 outlined".
+  static const IconData forward_5_outlined = IconData(0xe1f6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">forward_5</i> &#x2014; material icon named "forward 5 rounded".
+  static const IconData forward_5_rounded = IconData(0xf225, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">forward_5</i> &#x2014; material icon named "forward 5 sharp".
+  static const IconData forward_5_sharp = IconData(0xecf7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">forward</i> &#x2014; material icon named "forward outlined".
+  static const IconData forward_outlined = IconData(0xe1f7, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">forward</i> &#x2014; material icon named "forward rounded".
+  static const IconData forward_rounded = IconData(0xf226, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">forward</i> &#x2014; material icon named "forward sharp".
+  static const IconData forward_sharp = IconData(0xecf8, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">forward_to_inbox</i> &#x2014; material icon named "forward to inbox".
+  static const IconData forward_to_inbox = IconData(0xe783, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">forward_to_inbox</i> &#x2014; material icon named "forward to inbox outlined".
+  static const IconData forward_to_inbox_outlined = IconData(0xe1f8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">forward_to_inbox</i> &#x2014; material icon named "forward to inbox rounded".
+  static const IconData forward_to_inbox_rounded = IconData(0xf227, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">forward_to_inbox</i> &#x2014; material icon named "forward to inbox sharp".
+  static const IconData forward_to_inbox_sharp = IconData(0xecf9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">foundation</i> &#x2014; material icon named "foundation".
+  static const IconData foundation = IconData(0xe784, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">foundation</i> &#x2014; material icon named "foundation outlined".
+  static const IconData foundation_outlined = IconData(0xe1f9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">foundation</i> &#x2014; material icon named "foundation rounded".
+  static const IconData foundation_rounded = IconData(0xf228, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">foundation</i> &#x2014; material icon named "foundation sharp".
+  static const IconData foundation_sharp = IconData(0xecfa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">free_breakfast</i> &#x2014; material icon named "free breakfast".
+  static const IconData free_breakfast = IconData(0xe785, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">free_breakfast</i> &#x2014; material icon named "free breakfast outlined".
+  static const IconData free_breakfast_outlined = IconData(0xe1fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">free_breakfast</i> &#x2014; material icon named "free breakfast rounded".
+  static const IconData free_breakfast_rounded = IconData(0xf229, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">free_breakfast</i> &#x2014; material icon named "free breakfast sharp".
+  static const IconData free_breakfast_sharp = IconData(0xecfb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fullscreen</i> &#x2014; material icon named "fullscreen".
+  static const IconData fullscreen = IconData(0xe786, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">fullscreen_exit</i> &#x2014; material icon named "fullscreen exit".
+  static const IconData fullscreen_exit = IconData(0xe787, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fullscreen_exit</i> &#x2014; material icon named "fullscreen exit outlined".
+  static const IconData fullscreen_exit_outlined = IconData(0xe1fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fullscreen_exit</i> &#x2014; material icon named "fullscreen exit rounded".
+  static const IconData fullscreen_exit_rounded = IconData(0xf22a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fullscreen_exit</i> &#x2014; material icon named "fullscreen exit sharp".
+  static const IconData fullscreen_exit_sharp = IconData(0xecfc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">fullscreen</i> &#x2014; material icon named "fullscreen outlined".
+  static const IconData fullscreen_outlined = IconData(0xe1fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">fullscreen</i> &#x2014; material icon named "fullscreen rounded".
+  static const IconData fullscreen_rounded = IconData(0xf22b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">fullscreen</i> &#x2014; material icon named "fullscreen sharp".
+  static const IconData fullscreen_sharp = IconData(0xecfd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">functions</i> &#x2014; material icon named "functions".
+  static const IconData functions = IconData(0xe788, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">functions</i> &#x2014; material icon named "functions outlined".
+  static const IconData functions_outlined = IconData(0xe1fd, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">functions</i> &#x2014; material icon named "functions rounded".
+  static const IconData functions_rounded = IconData(0xf22c, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">functions</i> &#x2014; material icon named "functions sharp".
+  static const IconData functions_sharp = IconData(0xecfe, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">g_translate</i> &#x2014; material icon named "g translate".
+  static const IconData g_translate = IconData(0xe789, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">g_translate</i> &#x2014; material icon named "g translate outlined".
+  static const IconData g_translate_outlined = IconData(0xe1fe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">g_translate</i> &#x2014; material icon named "g translate rounded".
+  static const IconData g_translate_rounded = IconData(0xf22d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">g_translate</i> &#x2014; material icon named "g translate sharp".
+  static const IconData g_translate_sharp = IconData(0xecff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">gamepad</i> &#x2014; material icon named "gamepad".
+  static const IconData gamepad = IconData(0xe78a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">gamepad</i> &#x2014; material icon named "gamepad outlined".
+  static const IconData gamepad_outlined = IconData(0xe1ff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">gamepad</i> &#x2014; material icon named "gamepad rounded".
+  static const IconData gamepad_rounded = IconData(0xf22e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">gamepad</i> &#x2014; material icon named "gamepad sharp".
+  static const IconData gamepad_sharp = IconData(0xed00, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">games</i> &#x2014; material icon named "games".
+  static const IconData games = IconData(0xe78b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">games</i> &#x2014; material icon named "games outlined".
+  static const IconData games_outlined = IconData(0xe200, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">games</i> &#x2014; material icon named "games rounded".
+  static const IconData games_rounded = IconData(0xf22f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">games</i> &#x2014; material icon named "games sharp".
+  static const IconData games_sharp = IconData(0xed01, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">gavel</i> &#x2014; material icon named "gavel".
+  static const IconData gavel = IconData(0xe78c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">gavel</i> &#x2014; material icon named "gavel outlined".
+  static const IconData gavel_outlined = IconData(0xe201, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">gavel</i> &#x2014; material icon named "gavel rounded".
+  static const IconData gavel_rounded = IconData(0xf230, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">gavel</i> &#x2014; material icon named "gavel sharp".
+  static const IconData gavel_sharp = IconData(0xed02, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">gesture</i> &#x2014; material icon named "gesture".
+  static const IconData gesture = IconData(0xe78d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">gesture</i> &#x2014; material icon named "gesture outlined".
+  static const IconData gesture_outlined = IconData(0xe202, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">gesture</i> &#x2014; material icon named "gesture rounded".
+  static const IconData gesture_rounded = IconData(0xf231, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">gesture</i> &#x2014; material icon named "gesture sharp".
+  static const IconData gesture_sharp = IconData(0xed03, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">get_app</i> &#x2014; material icon named "get app".
+  static const IconData get_app = IconData(0xe78e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">get_app</i> &#x2014; material icon named "get app outlined".
+  static const IconData get_app_outlined = IconData(0xe203, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">get_app</i> &#x2014; material icon named "get app rounded".
+  static const IconData get_app_rounded = IconData(0xf232, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">get_app</i> &#x2014; material icon named "get app sharp".
+  static const IconData get_app_sharp = IconData(0xed04, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">gif</i> &#x2014; material icon named "gif".
+  static const IconData gif = IconData(0xe78f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">gif</i> &#x2014; material icon named "gif outlined".
+  static const IconData gif_outlined = IconData(0xe204, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">gif</i> &#x2014; material icon named "gif rounded".
+  static const IconData gif_rounded = IconData(0xf233, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">gif</i> &#x2014; material icon named "gif sharp".
+  static const IconData gif_sharp = IconData(0xed05, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">golf_course</i> &#x2014; material icon named "golf course".
+  static const IconData golf_course = IconData(0xe790, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">golf_course</i> &#x2014; material icon named "golf course outlined".
+  static const IconData golf_course_outlined = IconData(0xe205, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">golf_course</i> &#x2014; material icon named "golf course rounded".
+  static const IconData golf_course_rounded = IconData(0xf234, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">golf_course</i> &#x2014; material icon named "golf course sharp".
+  static const IconData golf_course_sharp = IconData(0xed06, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">gps_fixed</i> &#x2014; material icon named "gps fixed".
+  static const IconData gps_fixed = IconData(0xe791, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">gps_fixed</i> &#x2014; material icon named "gps fixed outlined".
+  static const IconData gps_fixed_outlined = IconData(0xe206, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">gps_fixed</i> &#x2014; material icon named "gps fixed rounded".
+  static const IconData gps_fixed_rounded = IconData(0xf235, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">gps_fixed</i> &#x2014; material icon named "gps fixed sharp".
+  static const IconData gps_fixed_sharp = IconData(0xed07, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">gps_not_fixed</i> &#x2014; material icon named "gps not fixed".
+  static const IconData gps_not_fixed = IconData(0xe792, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">gps_not_fixed</i> &#x2014; material icon named "gps not fixed outlined".
+  static const IconData gps_not_fixed_outlined = IconData(0xe207, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">gps_not_fixed</i> &#x2014; material icon named "gps not fixed rounded".
+  static const IconData gps_not_fixed_rounded = IconData(0xf236, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">gps_not_fixed</i> &#x2014; material icon named "gps not fixed sharp".
+  static const IconData gps_not_fixed_sharp = IconData(0xed08, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">gps_off</i> &#x2014; material icon named "gps off".
+  static const IconData gps_off = IconData(0xe793, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">gps_off</i> &#x2014; material icon named "gps off outlined".
+  static const IconData gps_off_outlined = IconData(0xe208, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">gps_off</i> &#x2014; material icon named "gps off rounded".
+  static const IconData gps_off_rounded = IconData(0xf237, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">gps_off</i> &#x2014; material icon named "gps off sharp".
+  static const IconData gps_off_sharp = IconData(0xed09, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">grade</i> &#x2014; material icon named "grade".
+  static const IconData grade = IconData(0xe794, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">grade</i> &#x2014; material icon named "grade outlined".
+  static const IconData grade_outlined = IconData(0xe209, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">grade</i> &#x2014; material icon named "grade rounded".
+  static const IconData grade_rounded = IconData(0xf238, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">grade</i> &#x2014; material icon named "grade sharp".
+  static const IconData grade_sharp = IconData(0xed0a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">gradient</i> &#x2014; material icon named "gradient".
+  static const IconData gradient = IconData(0xe795, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">gradient</i> &#x2014; material icon named "gradient outlined".
+  static const IconData gradient_outlined = IconData(0xe20a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">gradient</i> &#x2014; material icon named "gradient rounded".
+  static const IconData gradient_rounded = IconData(0xf239, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">gradient</i> &#x2014; material icon named "gradient sharp".
+  static const IconData gradient_sharp = IconData(0xed0b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">grading</i> &#x2014; material icon named "grading".
+  static const IconData grading = IconData(0xe796, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">grading</i> &#x2014; material icon named "grading outlined".
+  static const IconData grading_outlined = IconData(0xe20b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">grading</i> &#x2014; material icon named "grading rounded".
+  static const IconData grading_rounded = IconData(0xf23a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">grading</i> &#x2014; material icon named "grading sharp".
+  static const IconData grading_sharp = IconData(0xed0c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">grain</i> &#x2014; material icon named "grain".
+  static const IconData grain = IconData(0xe797, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">grain</i> &#x2014; material icon named "grain outlined".
+  static const IconData grain_outlined = IconData(0xe20c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">grain</i> &#x2014; material icon named "grain rounded".
+  static const IconData grain_rounded = IconData(0xf23b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">grain</i> &#x2014; material icon named "grain sharp".
+  static const IconData grain_sharp = IconData(0xed0d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">graphic_eq</i> &#x2014; material icon named "graphic eq".
+  static const IconData graphic_eq = IconData(0xe798, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">graphic_eq</i> &#x2014; material icon named "graphic eq outlined".
+  static const IconData graphic_eq_outlined = IconData(0xe20d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">graphic_eq</i> &#x2014; material icon named "graphic eq rounded".
+  static const IconData graphic_eq_rounded = IconData(0xf23c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">graphic_eq</i> &#x2014; material icon named "graphic eq sharp".
+  static const IconData graphic_eq_sharp = IconData(0xed0e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">grass</i> &#x2014; material icon named "grass".
+  static const IconData grass = IconData(0xe799, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">grass</i> &#x2014; material icon named "grass outlined".
+  static const IconData grass_outlined = IconData(0xe20e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">grass</i> &#x2014; material icon named "grass rounded".
+  static const IconData grass_rounded = IconData(0xf23d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">grass</i> &#x2014; material icon named "grass sharp".
+  static const IconData grass_sharp = IconData(0xed0f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">grid_off</i> &#x2014; material icon named "grid off".
+  static const IconData grid_off = IconData(0xe79a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">grid_off</i> &#x2014; material icon named "grid off outlined".
+  static const IconData grid_off_outlined = IconData(0xe20f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">grid_off</i> &#x2014; material icon named "grid off rounded".
+  static const IconData grid_off_rounded = IconData(0xf23e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">grid_off</i> &#x2014; material icon named "grid off sharp".
+  static const IconData grid_off_sharp = IconData(0xed10, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">grid_on</i> &#x2014; material icon named "grid on".
+  static const IconData grid_on = IconData(0xe79b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">grid_on</i> &#x2014; material icon named "grid on outlined".
+  static const IconData grid_on_outlined = IconData(0xe210, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">grid_on</i> &#x2014; material icon named "grid on rounded".
+  static const IconData grid_on_rounded = IconData(0xf23f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">grid_on</i> &#x2014; material icon named "grid on sharp".
+  static const IconData grid_on_sharp = IconData(0xed11, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">grid_view</i> &#x2014; material icon named "grid view".
+  static const IconData grid_view = IconData(0xe79c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">group</i> &#x2014; material icon named "group".
+  static const IconData group = IconData(0xe79d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">group_add</i> &#x2014; material icon named "group add".
+  static const IconData group_add = IconData(0xe79e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">group_add</i> &#x2014; material icon named "group add outlined".
+  static const IconData group_add_outlined = IconData(0xe211, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">group_add</i> &#x2014; material icon named "group add rounded".
+  static const IconData group_add_rounded = IconData(0xf240, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">group_add</i> &#x2014; material icon named "group add sharp".
+  static const IconData group_add_sharp = IconData(0xed12, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">group</i> &#x2014; material icon named "group outlined".
+  static const IconData group_outlined = IconData(0xe212, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">group</i> &#x2014; material icon named "group rounded".
+  static const IconData group_rounded = IconData(0xf241, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">group</i> &#x2014; material icon named "group sharp".
+  static const IconData group_sharp = IconData(0xed13, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">group_work</i> &#x2014; material icon named "group work".
+  static const IconData group_work = IconData(0xe79f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">group_work</i> &#x2014; material icon named "group work outlined".
+  static const IconData group_work_outlined = IconData(0xe213, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">group_work</i> &#x2014; material icon named "group work rounded".
+  static const IconData group_work_rounded = IconData(0xf242, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">group_work</i> &#x2014; material icon named "group work sharp".
+  static const IconData group_work_sharp = IconData(0xed14, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hail</i> &#x2014; material icon named "hail".
+  static const IconData hail = IconData(0xe7a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">handyman</i> &#x2014; material icon named "handyman".
+  static const IconData handyman = IconData(0xe7a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">handyman</i> &#x2014; material icon named "handyman outlined".
+  static const IconData handyman_outlined = IconData(0xe214, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">handyman</i> &#x2014; material icon named "handyman rounded".
+  static const IconData handyman_rounded = IconData(0xf243, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">handyman</i> &#x2014; material icon named "handyman sharp".
+  static const IconData handyman_sharp = IconData(0xed15, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hardware</i> &#x2014; material icon named "hardware".
+  static const IconData hardware = IconData(0xe7a2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hd</i> &#x2014; material icon named "hd".
+  static const IconData hd = IconData(0xe7a3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hd</i> &#x2014; material icon named "hd outlined".
+  static const IconData hd_outlined = IconData(0xe215, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hd</i> &#x2014; material icon named "hd rounded".
+  static const IconData hd_rounded = IconData(0xf244, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hd</i> &#x2014; material icon named "hd sharp".
+  static const IconData hd_sharp = IconData(0xed16, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hdr_enhanced_select</i> &#x2014; material icon named "hdr enhanced select".
+  static const IconData hdr_enhanced_select = IconData(0xe7a4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hdr_off</i> &#x2014; material icon named "hdr off".
+  static const IconData hdr_off = IconData(0xe7a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hdr_off</i> &#x2014; material icon named "hdr off outlined".
+  static const IconData hdr_off_outlined = IconData(0xe216, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hdr_off</i> &#x2014; material icon named "hdr off rounded".
+  static const IconData hdr_off_rounded = IconData(0xf245, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hdr_off</i> &#x2014; material icon named "hdr off sharp".
+  static const IconData hdr_off_sharp = IconData(0xed17, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hdr_on</i> &#x2014; material icon named "hdr on".
+  static const IconData hdr_on = IconData(0xe7a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hdr_on</i> &#x2014; material icon named "hdr on outlined".
+  static const IconData hdr_on_outlined = IconData(0xe217, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hdr_on</i> &#x2014; material icon named "hdr on rounded".
+  static const IconData hdr_on_rounded = IconData(0xf246, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hdr_on</i> &#x2014; material icon named "hdr on sharp".
+  static const IconData hdr_on_sharp = IconData(0xed18, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hdr_strong</i> &#x2014; material icon named "hdr strong".
+  static const IconData hdr_strong = IconData(0xe7a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hdr_strong</i> &#x2014; material icon named "hdr strong outlined".
+  static const IconData hdr_strong_outlined = IconData(0xe218, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hdr_strong</i> &#x2014; material icon named "hdr strong rounded".
+  static const IconData hdr_strong_rounded = IconData(0xf247, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hdr_strong</i> &#x2014; material icon named "hdr strong sharp".
+  static const IconData hdr_strong_sharp = IconData(0xed19, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hdr_weak</i> &#x2014; material icon named "hdr weak".
+  static const IconData hdr_weak = IconData(0xe7a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hdr_weak</i> &#x2014; material icon named "hdr weak outlined".
+  static const IconData hdr_weak_outlined = IconData(0xe219, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hdr_weak</i> &#x2014; material icon named "hdr weak rounded".
+  static const IconData hdr_weak_rounded = IconData(0xf248, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hdr_weak</i> &#x2014; material icon named "hdr weak sharp".
+  static const IconData hdr_weak_sharp = IconData(0xed1a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">headset</i> &#x2014; material icon named "headset".
+  static const IconData headset = IconData(0xe7a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">headset_mic</i> &#x2014; material icon named "headset mic".
+  static const IconData headset_mic = IconData(0xe7aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">headset_mic</i> &#x2014; material icon named "headset mic outlined".
+  static const IconData headset_mic_outlined = IconData(0xe21a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">headset_mic</i> &#x2014; material icon named "headset mic rounded".
+  static const IconData headset_mic_rounded = IconData(0xf249, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">headset_mic</i> &#x2014; material icon named "headset mic sharp".
+  static const IconData headset_mic_sharp = IconData(0xed1b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">headset_off</i> &#x2014; material icon named "headset off".
+  static const IconData headset_off = IconData(0xe7ab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">headset</i> &#x2014; material icon named "headset outlined".
+  static const IconData headset_outlined = IconData(0xe21b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">headset</i> &#x2014; material icon named "headset rounded".
+  static const IconData headset_rounded = IconData(0xf24a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">headset</i> &#x2014; material icon named "headset sharp".
+  static const IconData headset_sharp = IconData(0xed1c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">healing</i> &#x2014; material icon named "healing".
+  static const IconData healing = IconData(0xe7ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">healing</i> &#x2014; material icon named "healing outlined".
+  static const IconData healing_outlined = IconData(0xe21c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">healing</i> &#x2014; material icon named "healing rounded".
+  static const IconData healing_rounded = IconData(0xf24b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">healing</i> &#x2014; material icon named "healing sharp".
+  static const IconData healing_sharp = IconData(0xed1d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hearing</i> &#x2014; material icon named "hearing".
+  static const IconData hearing = IconData(0xe7ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hearing_disabled</i> &#x2014; material icon named "hearing disabled".
+  static const IconData hearing_disabled = IconData(0xe7ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hearing_disabled</i> &#x2014; material icon named "hearing disabled outlined".
+  static const IconData hearing_disabled_outlined = IconData(0xe21d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hearing_disabled</i> &#x2014; material icon named "hearing disabled rounded".
+  static const IconData hearing_disabled_rounded = IconData(0xf24c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hearing_disabled</i> &#x2014; material icon named "hearing disabled sharp".
+  static const IconData hearing_disabled_sharp = IconData(0xed1e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hearing</i> &#x2014; material icon named "hearing outlined".
+  static const IconData hearing_outlined = IconData(0xe21e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hearing</i> &#x2014; material icon named "hearing rounded".
+  static const IconData hearing_rounded = IconData(0xf24d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hearing</i> &#x2014; material icon named "hearing sharp".
+  static const IconData hearing_sharp = IconData(0xed1f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">height</i> &#x2014; material icon named "height".
+  static const IconData height = IconData(0xe7af, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">height</i> &#x2014; material icon named "height outlined".
+  static const IconData height_outlined = IconData(0xe21f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">height</i> &#x2014; material icon named "height rounded".
+  static const IconData height_rounded = IconData(0xf24e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">height</i> &#x2014; material icon named "height sharp".
+  static const IconData height_sharp = IconData(0xed20, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">help</i> &#x2014; material icon named "help".
+  static const IconData help = IconData(0xe7b0, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">help_center</i> &#x2014; material icon named "help center".
+  static const IconData help_center = IconData(0xe7b1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">help_center</i> &#x2014; material icon named "help center outlined".
+  static const IconData help_center_outlined = IconData(0xe220, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">help_center</i> &#x2014; material icon named "help center rounded".
+  static const IconData help_center_rounded = IconData(0xf24f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">help_center</i> &#x2014; material icon named "help center sharp".
+  static const IconData help_center_sharp = IconData(0xed21, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">help_outline</i> &#x2014; material icon named "help outline".
+  static const IconData help_outline = IconData(0xe7b2, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">help_outline</i> &#x2014; material icon named "help outline outlined".
+  static const IconData help_outline_outlined = IconData(0xe221, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">help_outline</i> &#x2014; material icon named "help outline rounded".
+  static const IconData help_outline_rounded = IconData(0xf250, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">help_outline</i> &#x2014; material icon named "help outline sharp".
+  static const IconData help_outline_sharp = IconData(0xed22, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">help</i> &#x2014; material icon named "help outlined".
+  static const IconData help_outlined = IconData(0xe222, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">help</i> &#x2014; material icon named "help rounded".
+  static const IconData help_rounded = IconData(0xf251, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">help</i> &#x2014; material icon named "help sharp".
+  static const IconData help_sharp = IconData(0xed23, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">high_quality</i> &#x2014; material icon named "high quality".
+  static const IconData high_quality = IconData(0xe7b3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">high_quality</i> &#x2014; material icon named "high quality outlined".
+  static const IconData high_quality_outlined = IconData(0xe223, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">high_quality</i> &#x2014; material icon named "high quality rounded".
+  static const IconData high_quality_rounded = IconData(0xf252, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">high_quality</i> &#x2014; material icon named "high quality sharp".
+  static const IconData high_quality_sharp = IconData(0xed24, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">highlight</i> &#x2014; material icon named "highlight".
+  static const IconData highlight = IconData(0xe7b4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">highlight_alt</i> &#x2014; material icon named "highlight alt".
+  static const IconData highlight_alt = IconData(0xe7b5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">highlight_alt</i> &#x2014; material icon named "highlight alt outlined".
+  static const IconData highlight_alt_outlined = IconData(0xe224, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">highlight_alt</i> &#x2014; material icon named "highlight alt rounded".
+  static const IconData highlight_alt_rounded = IconData(0xf253, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">highlight_alt</i> &#x2014; material icon named "highlight alt sharp".
+  static const IconData highlight_alt_sharp = IconData(0xed25, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">highlight_off</i> &#x2014; material icon named "highlight off".
+  static const IconData highlight_off = IconData(0xe7b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">highlight_off</i> &#x2014; material icon named "highlight off outlined".
+  static const IconData highlight_off_outlined = IconData(0xe225, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">highlight_off</i> &#x2014; material icon named "highlight off rounded".
+  static const IconData highlight_off_rounded = IconData(0xf254, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">highlight_off</i> &#x2014; material icon named "highlight off sharp".
+  static const IconData highlight_off_sharp = IconData(0xed26, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">highlight</i> &#x2014; material icon named "highlight outlined".
+  static const IconData highlight_outlined = IconData(0xe226, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">highlight_remove</i> &#x2014; material icon named "highlight remove".
+  static const IconData highlight_remove = IconData(0xe7b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">highlight_remove</i> &#x2014; material icon named "highlight remove outlined".
+  static const IconData highlight_remove_outlined = IconData(0xe225, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">highlight_remove</i> &#x2014; material icon named "highlight remove rounded".
+  static const IconData highlight_remove_rounded = IconData(0xf254, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">highlight_remove</i> &#x2014; material icon named "highlight remove sharp".
+  static const IconData highlight_remove_sharp = IconData(0xed26, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">highlight</i> &#x2014; material icon named "highlight rounded".
+  static const IconData highlight_rounded = IconData(0xf255, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">highlight</i> &#x2014; material icon named "highlight sharp".
+  static const IconData highlight_sharp = IconData(0xed27, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">history</i> &#x2014; material icon named "history".
+  static const IconData history = IconData(0xe7b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">history_edu</i> &#x2014; material icon named "history edu".
+  static const IconData history_edu = IconData(0xe7b8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">history_edu</i> &#x2014; material icon named "history edu outlined".
+  static const IconData history_edu_outlined = IconData(0xe227, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">history_edu</i> &#x2014; material icon named "history edu rounded".
+  static const IconData history_edu_rounded = IconData(0xf256, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">history_edu</i> &#x2014; material icon named "history edu sharp".
+  static const IconData history_edu_sharp = IconData(0xed28, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">history</i> &#x2014; material icon named "history outlined".
+  static const IconData history_outlined = IconData(0xe228, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">history</i> &#x2014; material icon named "history rounded".
+  static const IconData history_rounded = IconData(0xf257, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">history</i> &#x2014; material icon named "history sharp".
+  static const IconData history_sharp = IconData(0xed29, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">history_toggle_off</i> &#x2014; material icon named "history toggle off".
+  static const IconData history_toggle_off = IconData(0xe7b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">history_toggle_off</i> &#x2014; material icon named "history toggle off outlined".
+  static const IconData history_toggle_off_outlined = IconData(0xe229, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">history_toggle_off</i> &#x2014; material icon named "history toggle off rounded".
+  static const IconData history_toggle_off_rounded = IconData(0xf258, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">history_toggle_off</i> &#x2014; material icon named "history toggle off sharp".
+  static const IconData history_toggle_off_sharp = IconData(0xed2a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">home</i> &#x2014; material icon named "home".
+  static const IconData home = IconData(0xe7ba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">home_filled</i> &#x2014; material icon named "home filled".
+  static const IconData home_filled = IconData(0xe7bb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">home</i> &#x2014; material icon named "home outlined".
+  static const IconData home_outlined = IconData(0xe22a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">home_repair_service</i> &#x2014; material icon named "home repair service".
+  static const IconData home_repair_service = IconData(0xe7bc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">home_repair_service</i> &#x2014; material icon named "home repair service outlined".
+  static const IconData home_repair_service_outlined = IconData(0xe22b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">home_repair_service</i> &#x2014; material icon named "home repair service rounded".
+  static const IconData home_repair_service_rounded = IconData(0xf259, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">home_repair_service</i> &#x2014; material icon named "home repair service sharp".
+  static const IconData home_repair_service_sharp = IconData(0xed2b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">home</i> &#x2014; material icon named "home rounded".
+  static const IconData home_rounded = IconData(0xf25a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">home</i> &#x2014; material icon named "home sharp".
+  static const IconData home_sharp = IconData(0xed2c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">home_work</i> &#x2014; material icon named "home work".
+  static const IconData home_work = IconData(0xe7bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">home_work</i> &#x2014; material icon named "home work outlined".
+  static const IconData home_work_outlined = IconData(0xe22c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">home_work</i> &#x2014; material icon named "home work rounded".
+  static const IconData home_work_rounded = IconData(0xf25b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">home_work</i> &#x2014; material icon named "home work sharp".
+  static const IconData home_work_sharp = IconData(0xed2d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">horizontal_rule</i> &#x2014; material icon named "horizontal rule".
+  static const IconData horizontal_rule = IconData(0xe7be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">horizontal_rule</i> &#x2014; material icon named "horizontal rule outlined".
+  static const IconData horizontal_rule_outlined = IconData(0xe22d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">horizontal_rule</i> &#x2014; material icon named "horizontal rule rounded".
+  static const IconData horizontal_rule_rounded = IconData(0xf25c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">horizontal_rule</i> &#x2014; material icon named "horizontal rule sharp".
+  static const IconData horizontal_rule_sharp = IconData(0xed2e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">horizontal_split</i> &#x2014; material icon named "horizontal split".
+  static const IconData horizontal_split = IconData(0xe7bf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">horizontal_split</i> &#x2014; material icon named "horizontal split outlined".
+  static const IconData horizontal_split_outlined = IconData(0xe22e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">horizontal_split</i> &#x2014; material icon named "horizontal split rounded".
+  static const IconData horizontal_split_rounded = IconData(0xf25d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">horizontal_split</i> &#x2014; material icon named "horizontal split sharp".
+  static const IconData horizontal_split_sharp = IconData(0xed2f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hot_tub</i> &#x2014; material icon named "hot tub".
+  static const IconData hot_tub = IconData(0xe7c0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hot_tub</i> &#x2014; material icon named "hot tub outlined".
+  static const IconData hot_tub_outlined = IconData(0xe22f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hot_tub</i> &#x2014; material icon named "hot tub rounded".
+  static const IconData hot_tub_rounded = IconData(0xf25e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hot_tub</i> &#x2014; material icon named "hot tub sharp".
+  static const IconData hot_tub_sharp = IconData(0xed30, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hotel</i> &#x2014; material icon named "hotel".
+  static const IconData hotel = IconData(0xe7c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hotel</i> &#x2014; material icon named "hotel outlined".
+  static const IconData hotel_outlined = IconData(0xe230, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hotel</i> &#x2014; material icon named "hotel rounded".
+  static const IconData hotel_rounded = IconData(0xf25f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hotel</i> &#x2014; material icon named "hotel sharp".
+  static const IconData hotel_sharp = IconData(0xed31, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hourglass_bottom</i> &#x2014; material icon named "hourglass bottom".
+  static const IconData hourglass_bottom = IconData(0xe7c2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hourglass_bottom</i> &#x2014; material icon named "hourglass bottom outlined".
+  static const IconData hourglass_bottom_outlined = IconData(0xe231, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hourglass_bottom</i> &#x2014; material icon named "hourglass bottom rounded".
+  static const IconData hourglass_bottom_rounded = IconData(0xf260, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hourglass_bottom</i> &#x2014; material icon named "hourglass bottom sharp".
+  static const IconData hourglass_bottom_sharp = IconData(0xed32, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hourglass_disabled</i> &#x2014; material icon named "hourglass disabled".
+  static const IconData hourglass_disabled = IconData(0xe7c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hourglass_disabled</i> &#x2014; material icon named "hourglass disabled outlined".
+  static const IconData hourglass_disabled_outlined = IconData(0xe232, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hourglass_disabled</i> &#x2014; material icon named "hourglass disabled rounded".
+  static const IconData hourglass_disabled_rounded = IconData(0xf261, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hourglass_disabled</i> &#x2014; material icon named "hourglass disabled sharp".
+  static const IconData hourglass_disabled_sharp = IconData(0xed33, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hourglass_empty</i> &#x2014; material icon named "hourglass empty".
+  static const IconData hourglass_empty = IconData(0xe7c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hourglass_empty</i> &#x2014; material icon named "hourglass empty outlined".
+  static const IconData hourglass_empty_outlined = IconData(0xe233, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hourglass_empty</i> &#x2014; material icon named "hourglass empty rounded".
+  static const IconData hourglass_empty_rounded = IconData(0xf262, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hourglass_empty</i> &#x2014; material icon named "hourglass empty sharp".
+  static const IconData hourglass_empty_sharp = IconData(0xed34, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hourglass_full</i> &#x2014; material icon named "hourglass full".
+  static const IconData hourglass_full = IconData(0xe7c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hourglass_full</i> &#x2014; material icon named "hourglass full outlined".
+  static const IconData hourglass_full_outlined = IconData(0xe234, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hourglass_full</i> &#x2014; material icon named "hourglass full rounded".
+  static const IconData hourglass_full_rounded = IconData(0xf263, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hourglass_full</i> &#x2014; material icon named "hourglass full sharp".
+  static const IconData hourglass_full_sharp = IconData(0xed35, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hourglass_top</i> &#x2014; material icon named "hourglass top".
+  static const IconData hourglass_top = IconData(0xe7c6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hourglass_top</i> &#x2014; material icon named "hourglass top outlined".
+  static const IconData hourglass_top_outlined = IconData(0xe235, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hourglass_top</i> &#x2014; material icon named "hourglass top rounded".
+  static const IconData hourglass_top_rounded = IconData(0xf264, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hourglass_top</i> &#x2014; material icon named "hourglass top sharp".
+  static const IconData hourglass_top_sharp = IconData(0xed36, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">house</i> &#x2014; material icon named "house".
+  static const IconData house = IconData(0xe7c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">house</i> &#x2014; material icon named "house outlined".
+  static const IconData house_outlined = IconData(0xe236, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">house</i> &#x2014; material icon named "house rounded".
+  static const IconData house_rounded = IconData(0xf265, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">house</i> &#x2014; material icon named "house sharp".
+  static const IconData house_sharp = IconData(0xed37, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">house_siding</i> &#x2014; material icon named "house siding".
+  static const IconData house_siding = IconData(0xe7c8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">house_siding</i> &#x2014; material icon named "house siding outlined".
+  static const IconData house_siding_outlined = IconData(0xe237, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">house_siding</i> &#x2014; material icon named "house siding rounded".
+  static const IconData house_siding_rounded = IconData(0xf266, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">house_siding</i> &#x2014; material icon named "house siding sharp".
+  static const IconData house_siding_sharp = IconData(0xed38, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">how_to_reg</i> &#x2014; material icon named "how to reg".
+  static const IconData how_to_reg = IconData(0xe7c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">how_to_reg</i> &#x2014; material icon named "how to reg outlined".
+  static const IconData how_to_reg_outlined = IconData(0xe238, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">how_to_reg</i> &#x2014; material icon named "how to reg rounded".
+  static const IconData how_to_reg_rounded = IconData(0xf267, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">how_to_reg</i> &#x2014; material icon named "how to reg sharp".
+  static const IconData how_to_reg_sharp = IconData(0xed39, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">how_to_vote</i> &#x2014; material icon named "how to vote".
+  static const IconData how_to_vote = IconData(0xe7ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">how_to_vote</i> &#x2014; material icon named "how to vote outlined".
+  static const IconData how_to_vote_outlined = IconData(0xe239, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">how_to_vote</i> &#x2014; material icon named "how to vote rounded".
+  static const IconData how_to_vote_rounded = IconData(0xf268, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">how_to_vote</i> &#x2014; material icon named "how to vote sharp".
+  static const IconData how_to_vote_sharp = IconData(0xed3a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">http</i> &#x2014; material icon named "http".
+  static const IconData http = IconData(0xe7cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">http</i> &#x2014; material icon named "http outlined".
+  static const IconData http_outlined = IconData(0xe23a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">http</i> &#x2014; material icon named "http rounded".
+  static const IconData http_rounded = IconData(0xf269, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">http</i> &#x2014; material icon named "http sharp".
+  static const IconData http_sharp = IconData(0xed3b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">https</i> &#x2014; material icon named "https".
+  static const IconData https = IconData(0xe7cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">https</i> &#x2014; material icon named "https outlined".
+  static const IconData https_outlined = IconData(0xe23b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">https</i> &#x2014; material icon named "https rounded".
+  static const IconData https_rounded = IconData(0xf26a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">https</i> &#x2014; material icon named "https sharp".
+  static const IconData https_sharp = IconData(0xed3c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">hvac</i> &#x2014; material icon named "hvac".
+  static const IconData hvac = IconData(0xe7cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">hvac</i> &#x2014; material icon named "hvac outlined".
+  static const IconData hvac_outlined = IconData(0xe23c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">hvac</i> &#x2014; material icon named "hvac rounded".
+  static const IconData hvac_rounded = IconData(0xf26b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">hvac</i> &#x2014; material icon named "hvac sharp".
+  static const IconData hvac_sharp = IconData(0xed3d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">icecream</i> &#x2014; material icon named "icecream".
+  static const IconData icecream = IconData(0xe7ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">image</i> &#x2014; material icon named "image".
+  static const IconData image = IconData(0xe7cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">image_aspect_ratio</i> &#x2014; material icon named "image aspect ratio".
+  static const IconData image_aspect_ratio = IconData(0xe7d0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">image_aspect_ratio</i> &#x2014; material icon named "image aspect ratio outlined".
+  static const IconData image_aspect_ratio_outlined = IconData(0xe23d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">image_aspect_ratio</i> &#x2014; material icon named "image aspect ratio rounded".
+  static const IconData image_aspect_ratio_rounded = IconData(0xf26c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">image_aspect_ratio</i> &#x2014; material icon named "image aspect ratio sharp".
+  static const IconData image_aspect_ratio_sharp = IconData(0xed3e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">image_not_supported</i> &#x2014; material icon named "image not supported".
+  static const IconData image_not_supported = IconData(0xe7d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">image_not_supported</i> &#x2014; material icon named "image not supported outlined".
+  static const IconData image_not_supported_outlined = IconData(0xe23e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">image_not_supported</i> &#x2014; material icon named "image not supported rounded".
+  static const IconData image_not_supported_rounded = IconData(0xf26d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">image_not_supported</i> &#x2014; material icon named "image not supported sharp".
+  static const IconData image_not_supported_sharp = IconData(0xed3f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">image</i> &#x2014; material icon named "image outlined".
+  static const IconData image_outlined = IconData(0xe23f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">image</i> &#x2014; material icon named "image rounded".
+  static const IconData image_rounded = IconData(0xf26e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">image_search</i> &#x2014; material icon named "image search".
+  static const IconData image_search = IconData(0xe7d2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">image_search</i> &#x2014; material icon named "image search outlined".
+  static const IconData image_search_outlined = IconData(0xe240, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">image_search</i> &#x2014; material icon named "image search rounded".
+  static const IconData image_search_rounded = IconData(0xf26f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">image_search</i> &#x2014; material icon named "image search sharp".
+  static const IconData image_search_sharp = IconData(0xed40, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">image</i> &#x2014; material icon named "image sharp".
+  static const IconData image_sharp = IconData(0xed41, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">imagesearch_roller</i> &#x2014; material icon named "imagesearch roller".
+  static const IconData imagesearch_roller = IconData(0xe7d3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">import_contacts</i> &#x2014; material icon named "import contacts".
+  static const IconData import_contacts = IconData(0xe7d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">import_contacts</i> &#x2014; material icon named "import contacts outlined".
+  static const IconData import_contacts_outlined = IconData(0xe241, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">import_contacts</i> &#x2014; material icon named "import contacts rounded".
+  static const IconData import_contacts_rounded = IconData(0xf270, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">import_contacts</i> &#x2014; material icon named "import contacts sharp".
+  static const IconData import_contacts_sharp = IconData(0xed42, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">import_export</i> &#x2014; material icon named "import export".
+  static const IconData import_export = IconData(0xe7d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">import_export</i> &#x2014; material icon named "import export outlined".
+  static const IconData import_export_outlined = IconData(0xe242, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">import_export</i> &#x2014; material icon named "import export rounded".
+  static const IconData import_export_rounded = IconData(0xf271, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">import_export</i> &#x2014; material icon named "import export sharp".
+  static const IconData import_export_sharp = IconData(0xed43, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">important_devices</i> &#x2014; material icon named "important devices".
+  static const IconData important_devices = IconData(0xe7d6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">important_devices</i> &#x2014; material icon named "important devices outlined".
+  static const IconData important_devices_outlined = IconData(0xe243, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">important_devices</i> &#x2014; material icon named "important devices rounded".
+  static const IconData important_devices_rounded = IconData(0xf272, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">important_devices</i> &#x2014; material icon named "important devices sharp".
+  static const IconData important_devices_sharp = IconData(0xed44, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">inbox</i> &#x2014; material icon named "inbox".
+  static const IconData inbox = IconData(0xe7d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">inbox</i> &#x2014; material icon named "inbox outlined".
+  static const IconData inbox_outlined = IconData(0xe244, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">inbox</i> &#x2014; material icon named "inbox rounded".
+  static const IconData inbox_rounded = IconData(0xf273, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">inbox</i> &#x2014; material icon named "inbox sharp".
+  static const IconData inbox_sharp = IconData(0xed45, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">indeterminate_check_box</i> &#x2014; material icon named "indeterminate check box".
+  static const IconData indeterminate_check_box = IconData(0xe7d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">indeterminate_check_box</i> &#x2014; material icon named "indeterminate check box outlined".
+  static const IconData indeterminate_check_box_outlined = IconData(0xe245, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">indeterminate_check_box</i> &#x2014; material icon named "indeterminate check box rounded".
+  static const IconData indeterminate_check_box_rounded = IconData(0xf274, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">indeterminate_check_box</i> &#x2014; material icon named "indeterminate check box sharp".
+  static const IconData indeterminate_check_box_sharp = IconData(0xed46, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">info</i> &#x2014; material icon named "info".
+  static const IconData info = IconData(0xe7d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">info_outline</i> &#x2014; material icon named "info outline".
+  static const IconData info_outline = IconData(0xe7da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">info_outline</i> &#x2014; material icon named "info outline rounded".
+  static const IconData info_outline_rounded = IconData(0xf275, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">info_outline</i> &#x2014; material icon named "info outline sharp".
+  static const IconData info_outline_sharp = IconData(0xed47, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">info</i> &#x2014; material icon named "info outlined".
+  static const IconData info_outlined = IconData(0xe246, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">info</i> &#x2014; material icon named "info rounded".
+  static const IconData info_rounded = IconData(0xf276, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">info</i> &#x2014; material icon named "info sharp".
+  static const IconData info_sharp = IconData(0xed48, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">input</i> &#x2014; material icon named "input".
+  static const IconData input = IconData(0xe7db, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">input</i> &#x2014; material icon named "input outlined".
+  static const IconData input_outlined = IconData(0xe247, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">input</i> &#x2014; material icon named "input rounded".
+  static const IconData input_rounded = IconData(0xf277, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">input</i> &#x2014; material icon named "input sharp".
+  static const IconData input_sharp = IconData(0xed49, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">insert_chart</i> &#x2014; material icon named "insert chart".
+  static const IconData insert_chart = IconData(0xe7dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">insert_chart_outlined</i> &#x2014; material icon named "insert chart outlined".
+  static const IconData insert_chart_outlined = IconData(0xe248, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">insert_chart</i> &#x2014; material icon named "insert chart outlined outlined".
+  static const IconData insert_chart_outlined_outlined = IconData(0xe249, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">insert_chart_outlined</i> &#x2014; material icon named "insert chart outlined rounded".
+  static const IconData insert_chart_outlined_rounded = IconData(0xf278, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">insert_chart_outlined</i> &#x2014; material icon named "insert chart outlined sharp".
+  static const IconData insert_chart_outlined_sharp = IconData(0xed4a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">insert_chart</i> &#x2014; material icon named "insert chart rounded".
+  static const IconData insert_chart_rounded = IconData(0xf279, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">insert_chart</i> &#x2014; material icon named "insert chart sharp".
+  static const IconData insert_chart_sharp = IconData(0xed4b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">insert_comment</i> &#x2014; material icon named "insert comment".
+  static const IconData insert_comment = IconData(0xe7de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">insert_comment</i> &#x2014; material icon named "insert comment outlined".
+  static const IconData insert_comment_outlined = IconData(0xe24a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">insert_comment</i> &#x2014; material icon named "insert comment rounded".
+  static const IconData insert_comment_rounded = IconData(0xf27a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">insert_comment</i> &#x2014; material icon named "insert comment sharp".
+  static const IconData insert_comment_sharp = IconData(0xed4c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">insert_drive_file</i> &#x2014; material icon named "insert drive file".
+  static const IconData insert_drive_file = IconData(0xe7df, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">insert_drive_file</i> &#x2014; material icon named "insert drive file outlined".
+  static const IconData insert_drive_file_outlined = IconData(0xe24b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">insert_drive_file</i> &#x2014; material icon named "insert drive file rounded".
+  static const IconData insert_drive_file_rounded = IconData(0xf27b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">insert_drive_file</i> &#x2014; material icon named "insert drive file sharp".
+  static const IconData insert_drive_file_sharp = IconData(0xed4d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">insert_emoticon</i> &#x2014; material icon named "insert emoticon".
+  static const IconData insert_emoticon = IconData(0xe7e0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">insert_emoticon</i> &#x2014; material icon named "insert emoticon outlined".
+  static const IconData insert_emoticon_outlined = IconData(0xe24c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">insert_emoticon</i> &#x2014; material icon named "insert emoticon rounded".
+  static const IconData insert_emoticon_rounded = IconData(0xf27c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">insert_emoticon</i> &#x2014; material icon named "insert emoticon sharp".
+  static const IconData insert_emoticon_sharp = IconData(0xed4e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">insert_invitation</i> &#x2014; material icon named "insert invitation".
+  static const IconData insert_invitation = IconData(0xe7e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">insert_invitation</i> &#x2014; material icon named "insert invitation outlined".
+  static const IconData insert_invitation_outlined = IconData(0xe24d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">insert_invitation</i> &#x2014; material icon named "insert invitation rounded".
+  static const IconData insert_invitation_rounded = IconData(0xf27d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">insert_invitation</i> &#x2014; material icon named "insert invitation sharp".
+  static const IconData insert_invitation_sharp = IconData(0xed4f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">insert_link</i> &#x2014; material icon named "insert link".
+  static const IconData insert_link = IconData(0xe7e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">insert_link</i> &#x2014; material icon named "insert link outlined".
+  static const IconData insert_link_outlined = IconData(0xe24e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">insert_link</i> &#x2014; material icon named "insert link rounded".
+  static const IconData insert_link_rounded = IconData(0xf27e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">insert_link</i> &#x2014; material icon named "insert link sharp".
+  static const IconData insert_link_sharp = IconData(0xed50, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">insert_photo</i> &#x2014; material icon named "insert photo".
+  static const IconData insert_photo = IconData(0xe7e3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">insert_photo</i> &#x2014; material icon named "insert photo outlined".
+  static const IconData insert_photo_outlined = IconData(0xe24f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">insert_photo</i> &#x2014; material icon named "insert photo rounded".
+  static const IconData insert_photo_rounded = IconData(0xf27f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">insert_photo</i> &#x2014; material icon named "insert photo sharp".
+  static const IconData insert_photo_sharp = IconData(0xed51, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">insights</i> &#x2014; material icon named "insights".
+  static const IconData insights = IconData(0xe7e4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">insights</i> &#x2014; material icon named "insights outlined".
+  static const IconData insights_outlined = IconData(0xe250, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">insights</i> &#x2014; material icon named "insights rounded".
+  static const IconData insights_rounded = IconData(0xf280, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">insights</i> &#x2014; material icon named "insights sharp".
+  static const IconData insights_sharp = IconData(0xed52, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">integration_instructions</i> &#x2014; material icon named "integration instructions".
+  static const IconData integration_instructions = IconData(0xe7e5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">integration_instructions</i> &#x2014; material icon named "integration instructions outlined".
+  static const IconData integration_instructions_outlined = IconData(0xe251, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">integration_instructions</i> &#x2014; material icon named "integration instructions rounded".
+  static const IconData integration_instructions_rounded = IconData(0xf281, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">integration_instructions</i> &#x2014; material icon named "integration instructions sharp".
+  static const IconData integration_instructions_sharp = IconData(0xed53, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">inventory</i> &#x2014; material icon named "inventory".
+  static const IconData inventory = IconData(0xe7e6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">invert_colors</i> &#x2014; material icon named "invert colors".
+  static const IconData invert_colors = IconData(0xe7e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">invert_colors_off</i> &#x2014; material icon named "invert colors off".
+  static const IconData invert_colors_off = IconData(0xe7e8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">invert_colors_off</i> &#x2014; material icon named "invert colors off outlined".
+  static const IconData invert_colors_off_outlined = IconData(0xe252, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">invert_colors_off</i> &#x2014; material icon named "invert colors off rounded".
+  static const IconData invert_colors_off_rounded = IconData(0xf282, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">invert_colors_off</i> &#x2014; material icon named "invert colors off sharp".
+  static const IconData invert_colors_off_sharp = IconData(0xed54, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">invert_colors_on</i> &#x2014; material icon named "invert colors on".
+  static const IconData invert_colors_on = IconData(0xe7e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">invert_colors_on</i> &#x2014; material icon named "invert colors on outlined".
+  static const IconData invert_colors_on_outlined = IconData(0xe253, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">invert_colors_on</i> &#x2014; material icon named "invert colors on rounded".
+  static const IconData invert_colors_on_rounded = IconData(0xf283, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">invert_colors_on</i> &#x2014; material icon named "invert colors on sharp".
+  static const IconData invert_colors_on_sharp = IconData(0xed55, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">invert_colors</i> &#x2014; material icon named "invert colors outlined".
+  static const IconData invert_colors_outlined = IconData(0xe253, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">invert_colors</i> &#x2014; material icon named "invert colors rounded".
+  static const IconData invert_colors_rounded = IconData(0xf283, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">invert_colors</i> &#x2014; material icon named "invert colors sharp".
+  static const IconData invert_colors_sharp = IconData(0xed55, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">ios_share</i> &#x2014; material icon named "ios share".
+  static const IconData ios_share = IconData(0xe7e9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">iso</i> &#x2014; material icon named "iso".
+  static const IconData iso = IconData(0xe7ea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">iso</i> &#x2014; material icon named "iso outlined".
+  static const IconData iso_outlined = IconData(0xe254, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">iso</i> &#x2014; material icon named "iso rounded".
+  static const IconData iso_rounded = IconData(0xf284, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">iso</i> &#x2014; material icon named "iso sharp".
+  static const IconData iso_sharp = IconData(0xed56, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">keyboard</i> &#x2014; material icon named "keyboard".
+  static const IconData keyboard = IconData(0xe7eb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">keyboard_arrow_down</i> &#x2014; material icon named "keyboard arrow down".
+  static const IconData keyboard_arrow_down = IconData(0xe7ec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">keyboard_arrow_down</i> &#x2014; material icon named "keyboard arrow down outlined".
+  static const IconData keyboard_arrow_down_outlined = IconData(0xe255, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">keyboard_arrow_down</i> &#x2014; material icon named "keyboard arrow down rounded".
+  static const IconData keyboard_arrow_down_rounded = IconData(0xf285, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">keyboard_arrow_down</i> &#x2014; material icon named "keyboard arrow down sharp".
+  static const IconData keyboard_arrow_down_sharp = IconData(0xed57, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">keyboard_arrow_left</i> &#x2014; material icon named "keyboard arrow left".
+  static const IconData keyboard_arrow_left = IconData(0xe7ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">keyboard_arrow_left</i> &#x2014; material icon named "keyboard arrow left outlined".
+  static const IconData keyboard_arrow_left_outlined = IconData(0xe256, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">keyboard_arrow_left</i> &#x2014; material icon named "keyboard arrow left rounded".
+  static const IconData keyboard_arrow_left_rounded = IconData(0xf286, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">keyboard_arrow_left</i> &#x2014; material icon named "keyboard arrow left sharp".
+  static const IconData keyboard_arrow_left_sharp = IconData(0xed58, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">keyboard_arrow_right</i> &#x2014; material icon named "keyboard arrow right".
+  static const IconData keyboard_arrow_right = IconData(0xe7ee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">keyboard_arrow_right</i> &#x2014; material icon named "keyboard arrow right outlined".
+  static const IconData keyboard_arrow_right_outlined = IconData(0xe257, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">keyboard_arrow_right</i> &#x2014; material icon named "keyboard arrow right rounded".
+  static const IconData keyboard_arrow_right_rounded = IconData(0xf287, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">keyboard_arrow_right</i> &#x2014; material icon named "keyboard arrow right sharp".
+  static const IconData keyboard_arrow_right_sharp = IconData(0xed59, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">keyboard_arrow_up</i> &#x2014; material icon named "keyboard arrow up".
+  static const IconData keyboard_arrow_up = IconData(0xe7ef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">keyboard_arrow_up</i> &#x2014; material icon named "keyboard arrow up outlined".
+  static const IconData keyboard_arrow_up_outlined = IconData(0xe258, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">keyboard_arrow_up</i> &#x2014; material icon named "keyboard arrow up rounded".
+  static const IconData keyboard_arrow_up_rounded = IconData(0xf288, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">keyboard_arrow_up</i> &#x2014; material icon named "keyboard arrow up sharp".
+  static const IconData keyboard_arrow_up_sharp = IconData(0xed5a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">keyboard_backspace</i> &#x2014; material icon named "keyboard backspace".
+  static const IconData keyboard_backspace = IconData(0xe7f0, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">keyboard_backspace</i> &#x2014; material icon named "keyboard backspace outlined".
+  static const IconData keyboard_backspace_outlined = IconData(0xe259, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">keyboard_backspace</i> &#x2014; material icon named "keyboard backspace rounded".
+  static const IconData keyboard_backspace_rounded = IconData(0xf289, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">keyboard_backspace</i> &#x2014; material icon named "keyboard backspace sharp".
+  static const IconData keyboard_backspace_sharp = IconData(0xed5b, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">keyboard_capslock</i> &#x2014; material icon named "keyboard capslock".
+  static const IconData keyboard_capslock = IconData(0xe7f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">keyboard_capslock</i> &#x2014; material icon named "keyboard capslock outlined".
+  static const IconData keyboard_capslock_outlined = IconData(0xe25a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">keyboard_capslock</i> &#x2014; material icon named "keyboard capslock rounded".
+  static const IconData keyboard_capslock_rounded = IconData(0xf28a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">keyboard_capslock</i> &#x2014; material icon named "keyboard capslock sharp".
+  static const IconData keyboard_capslock_sharp = IconData(0xed5c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">keyboard_control</i> &#x2014; material icon named "keyboard control".
+  static const IconData keyboard_control = IconData(0xe886, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">keyboard_control</i> &#x2014; material icon named "keyboard control outlined".
+  static const IconData keyboard_control_outlined = IconData(0xe2e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">keyboard_control</i> &#x2014; material icon named "keyboard control rounded".
+  static const IconData keyboard_control_rounded = IconData(0xf315, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">keyboard_control</i> &#x2014; material icon named "keyboard control sharp".
+  static const IconData keyboard_control_sharp = IconData(0xede7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">keyboard_hide</i> &#x2014; material icon named "keyboard hide".
+  static const IconData keyboard_hide = IconData(0xe7f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">keyboard_hide</i> &#x2014; material icon named "keyboard hide outlined".
+  static const IconData keyboard_hide_outlined = IconData(0xe25b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">keyboard_hide</i> &#x2014; material icon named "keyboard hide rounded".
+  static const IconData keyboard_hide_rounded = IconData(0xf28b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">keyboard_hide</i> &#x2014; material icon named "keyboard hide sharp".
+  static const IconData keyboard_hide_sharp = IconData(0xed5d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">keyboard</i> &#x2014; material icon named "keyboard outlined".
+  static const IconData keyboard_outlined = IconData(0xe25c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">keyboard_return</i> &#x2014; material icon named "keyboard return".
+  static const IconData keyboard_return = IconData(0xe7f3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">keyboard_return</i> &#x2014; material icon named "keyboard return outlined".
+  static const IconData keyboard_return_outlined = IconData(0xe25d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">keyboard_return</i> &#x2014; material icon named "keyboard return rounded".
+  static const IconData keyboard_return_rounded = IconData(0xf28c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">keyboard_return</i> &#x2014; material icon named "keyboard return sharp".
+  static const IconData keyboard_return_sharp = IconData(0xed5e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">keyboard</i> &#x2014; material icon named "keyboard rounded".
+  static const IconData keyboard_rounded = IconData(0xf28d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">keyboard</i> &#x2014; material icon named "keyboard sharp".
+  static const IconData keyboard_sharp = IconData(0xed5f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">keyboard_tab</i> &#x2014; material icon named "keyboard tab".
+  static const IconData keyboard_tab = IconData(0xe7f4, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">keyboard_tab</i> &#x2014; material icon named "keyboard tab outlined".
+  static const IconData keyboard_tab_outlined = IconData(0xe25e, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">keyboard_tab</i> &#x2014; material icon named "keyboard tab rounded".
+  static const IconData keyboard_tab_rounded = IconData(0xf28e, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">keyboard_tab</i> &#x2014; material icon named "keyboard tab sharp".
+  static const IconData keyboard_tab_sharp = IconData(0xed60, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">keyboard_voice</i> &#x2014; material icon named "keyboard voice".
+  static const IconData keyboard_voice = IconData(0xe7f5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">keyboard_voice</i> &#x2014; material icon named "keyboard voice outlined".
+  static const IconData keyboard_voice_outlined = IconData(0xe25f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">keyboard_voice</i> &#x2014; material icon named "keyboard voice rounded".
+  static const IconData keyboard_voice_rounded = IconData(0xf28f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">keyboard_voice</i> &#x2014; material icon named "keyboard voice sharp".
+  static const IconData keyboard_voice_sharp = IconData(0xed61, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">king_bed</i> &#x2014; material icon named "king bed".
+  static const IconData king_bed = IconData(0xe7f6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">king_bed</i> &#x2014; material icon named "king bed outlined".
+  static const IconData king_bed_outlined = IconData(0xe260, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">king_bed</i> &#x2014; material icon named "king bed rounded".
+  static const IconData king_bed_rounded = IconData(0xf290, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">king_bed</i> &#x2014; material icon named "king bed sharp".
+  static const IconData king_bed_sharp = IconData(0xed62, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">kitchen</i> &#x2014; material icon named "kitchen".
+  static const IconData kitchen = IconData(0xe7f7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">kitchen</i> &#x2014; material icon named "kitchen outlined".
+  static const IconData kitchen_outlined = IconData(0xe261, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">kitchen</i> &#x2014; material icon named "kitchen rounded".
+  static const IconData kitchen_rounded = IconData(0xf291, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">kitchen</i> &#x2014; material icon named "kitchen sharp".
+  static const IconData kitchen_sharp = IconData(0xed63, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">label</i> &#x2014; material icon named "label".
+  static const IconData label = IconData(0xe7f8, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">label_important</i> &#x2014; material icon named "label important".
+  static const IconData label_important = IconData(0xe7f9, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">label_important_outline</i> &#x2014; material icon named "label important outline".
+  static const IconData label_important_outline = IconData(0xe7fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">label_important_outline</i> &#x2014; material icon named "label important outline rounded".
+  static const IconData label_important_outline_rounded = IconData(0xf292, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">label_important_outline</i> &#x2014; material icon named "label important outline sharp".
+  static const IconData label_important_outline_sharp = IconData(0xed64, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">label_important</i> &#x2014; material icon named "label important outlined".
+  static const IconData label_important_outlined = IconData(0xe262, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">label_important</i> &#x2014; material icon named "label important rounded".
+  static const IconData label_important_rounded = IconData(0xf293, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">label_important</i> &#x2014; material icon named "label important sharp".
+  static const IconData label_important_sharp = IconData(0xed65, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">label_off</i> &#x2014; material icon named "label off".
+  static const IconData label_off = IconData(0xe7fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">label_off</i> &#x2014; material icon named "label off outlined".
+  static const IconData label_off_outlined = IconData(0xe263, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">label_off</i> &#x2014; material icon named "label off rounded".
+  static const IconData label_off_rounded = IconData(0xf294, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">label_off</i> &#x2014; material icon named "label off sharp".
+  static const IconData label_off_sharp = IconData(0xed66, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">label_outline</i> &#x2014; material icon named "label outline".
+  static const IconData label_outline = IconData(0xe7fc, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">label_outline</i> &#x2014; material icon named "label outline rounded".
+  static const IconData label_outline_rounded = IconData(0xf295, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">label_outline</i> &#x2014; material icon named "label outline sharp".
+  static const IconData label_outline_sharp = IconData(0xed67, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">label</i> &#x2014; material icon named "label outlined".
+  static const IconData label_outlined = IconData(0xe264, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">label</i> &#x2014; material icon named "label rounded".
+  static const IconData label_rounded = IconData(0xf296, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">label</i> &#x2014; material icon named "label sharp".
+  static const IconData label_sharp = IconData(0xed68, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">landscape</i> &#x2014; material icon named "landscape".
+  static const IconData landscape = IconData(0xe7fd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">landscape</i> &#x2014; material icon named "landscape outlined".
+  static const IconData landscape_outlined = IconData(0xe265, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">landscape</i> &#x2014; material icon named "landscape rounded".
+  static const IconData landscape_rounded = IconData(0xf297, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">landscape</i> &#x2014; material icon named "landscape sharp".
+  static const IconData landscape_sharp = IconData(0xed69, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">language</i> &#x2014; material icon named "language".
+  static const IconData language = IconData(0xe7fe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">language</i> &#x2014; material icon named "language outlined".
+  static const IconData language_outlined = IconData(0xe266, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">language</i> &#x2014; material icon named "language rounded".
+  static const IconData language_rounded = IconData(0xf298, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">language</i> &#x2014; material icon named "language sharp".
+  static const IconData language_sharp = IconData(0xed6a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">laptop</i> &#x2014; material icon named "laptop".
+  static const IconData laptop = IconData(0xe7ff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">laptop_chromebook</i> &#x2014; material icon named "laptop chromebook".
+  static const IconData laptop_chromebook = IconData(0xe800, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">laptop_chromebook</i> &#x2014; material icon named "laptop chromebook outlined".
+  static const IconData laptop_chromebook_outlined = IconData(0xe267, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">laptop_chromebook</i> &#x2014; material icon named "laptop chromebook rounded".
+  static const IconData laptop_chromebook_rounded = IconData(0xf299, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">laptop_chromebook</i> &#x2014; material icon named "laptop chromebook sharp".
+  static const IconData laptop_chromebook_sharp = IconData(0xed6b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">laptop_mac</i> &#x2014; material icon named "laptop mac".
+  static const IconData laptop_mac = IconData(0xe801, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">laptop_mac</i> &#x2014; material icon named "laptop mac outlined".
+  static const IconData laptop_mac_outlined = IconData(0xe268, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">laptop_mac</i> &#x2014; material icon named "laptop mac rounded".
+  static const IconData laptop_mac_rounded = IconData(0xf29a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">laptop_mac</i> &#x2014; material icon named "laptop mac sharp".
+  static const IconData laptop_mac_sharp = IconData(0xed6c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">laptop</i> &#x2014; material icon named "laptop outlined".
+  static const IconData laptop_outlined = IconData(0xe269, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">laptop</i> &#x2014; material icon named "laptop rounded".
+  static const IconData laptop_rounded = IconData(0xf29b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">laptop</i> &#x2014; material icon named "laptop sharp".
+  static const IconData laptop_sharp = IconData(0xed6d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">laptop_windows</i> &#x2014; material icon named "laptop windows".
+  static const IconData laptop_windows = IconData(0xe802, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">laptop_windows</i> &#x2014; material icon named "laptop windows outlined".
+  static const IconData laptop_windows_outlined = IconData(0xe26a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">laptop_windows</i> &#x2014; material icon named "laptop windows rounded".
+  static const IconData laptop_windows_rounded = IconData(0xf29c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">laptop_windows</i> &#x2014; material icon named "laptop windows sharp".
+  static const IconData laptop_windows_sharp = IconData(0xed6e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">last_page</i> &#x2014; material icon named "last page".
+  static const IconData last_page = IconData(0xe803, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">last_page</i> &#x2014; material icon named "last page outlined".
+  static const IconData last_page_outlined = IconData(0xe26b, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">last_page</i> &#x2014; material icon named "last page rounded".
+  static const IconData last_page_rounded = IconData(0xf29d, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">last_page</i> &#x2014; material icon named "last page sharp".
+  static const IconData last_page_sharp = IconData(0xed6f, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">launch</i> &#x2014; material icon named "launch".
+  static const IconData launch = IconData(0xe804, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">launch</i> &#x2014; material icon named "launch outlined".
+  static const IconData launch_outlined = IconData(0xe26c, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">launch</i> &#x2014; material icon named "launch rounded".
+  static const IconData launch_rounded = IconData(0xf29e, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">launch</i> &#x2014; material icon named "launch sharp".
+  static const IconData launch_sharp = IconData(0xed70, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">layers</i> &#x2014; material icon named "layers".
+  static const IconData layers = IconData(0xe805, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">layers_clear</i> &#x2014; material icon named "layers clear".
+  static const IconData layers_clear = IconData(0xe806, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">layers_clear</i> &#x2014; material icon named "layers clear outlined".
+  static const IconData layers_clear_outlined = IconData(0xe26d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">layers_clear</i> &#x2014; material icon named "layers clear rounded".
+  static const IconData layers_clear_rounded = IconData(0xf29f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">layers_clear</i> &#x2014; material icon named "layers clear sharp".
+  static const IconData layers_clear_sharp = IconData(0xed71, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">layers</i> &#x2014; material icon named "layers outlined".
+  static const IconData layers_outlined = IconData(0xe26e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">layers</i> &#x2014; material icon named "layers rounded".
+  static const IconData layers_rounded = IconData(0xf2a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">layers</i> &#x2014; material icon named "layers sharp".
+  static const IconData layers_sharp = IconData(0xed72, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">leaderboard</i> &#x2014; material icon named "leaderboard".
+  static const IconData leaderboard = IconData(0xe807, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">leaderboard</i> &#x2014; material icon named "leaderboard outlined".
+  static const IconData leaderboard_outlined = IconData(0xe26f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">leaderboard</i> &#x2014; material icon named "leaderboard rounded".
+  static const IconData leaderboard_rounded = IconData(0xf2a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">leaderboard</i> &#x2014; material icon named "leaderboard sharp".
+  static const IconData leaderboard_sharp = IconData(0xed73, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">leak_add</i> &#x2014; material icon named "leak add".
+  static const IconData leak_add = IconData(0xe808, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">leak_add</i> &#x2014; material icon named "leak add outlined".
+  static const IconData leak_add_outlined = IconData(0xe270, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">leak_add</i> &#x2014; material icon named "leak add rounded".
+  static const IconData leak_add_rounded = IconData(0xf2a2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">leak_add</i> &#x2014; material icon named "leak add sharp".
+  static const IconData leak_add_sharp = IconData(0xed74, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">leak_remove</i> &#x2014; material icon named "leak remove".
+  static const IconData leak_remove = IconData(0xe809, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">leak_remove</i> &#x2014; material icon named "leak remove outlined".
+  static const IconData leak_remove_outlined = IconData(0xe271, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">leak_remove</i> &#x2014; material icon named "leak remove rounded".
+  static const IconData leak_remove_rounded = IconData(0xf2a3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">leak_remove</i> &#x2014; material icon named "leak remove sharp".
+  static const IconData leak_remove_sharp = IconData(0xed75, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">leave_bags_at_home</i> &#x2014; material icon named "leave bags at home".
+  static const IconData leave_bags_at_home = IconData(0xe80a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">leave_bags_at_home</i> &#x2014; material icon named "leave bags at home outlined".
+  static const IconData leave_bags_at_home_outlined = IconData(0xe272, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">leave_bags_at_home</i> &#x2014; material icon named "leave bags at home rounded".
+  static const IconData leave_bags_at_home_rounded = IconData(0xf2a4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">leave_bags_at_home</i> &#x2014; material icon named "leave bags at home sharp".
+  static const IconData leave_bags_at_home_sharp = IconData(0xed76, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">legend_toggle</i> &#x2014; material icon named "legend toggle".
+  static const IconData legend_toggle = IconData(0xe80b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">legend_toggle</i> &#x2014; material icon named "legend toggle outlined".
+  static const IconData legend_toggle_outlined = IconData(0xe273, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">legend_toggle</i> &#x2014; material icon named "legend toggle rounded".
+  static const IconData legend_toggle_rounded = IconData(0xf2a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">legend_toggle</i> &#x2014; material icon named "legend toggle sharp".
+  static const IconData legend_toggle_sharp = IconData(0xed77, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">lens</i> &#x2014; material icon named "lens".
+  static const IconData lens = IconData(0xe80c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">lens</i> &#x2014; material icon named "lens outlined".
+  static const IconData lens_outlined = IconData(0xe274, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">lens</i> &#x2014; material icon named "lens rounded".
+  static const IconData lens_rounded = IconData(0xf2a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">lens</i> &#x2014; material icon named "lens sharp".
+  static const IconData lens_sharp = IconData(0xed78, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">library_add</i> &#x2014; material icon named "library add".
+  static const IconData library_add = IconData(0xe80d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">library_add_check</i> &#x2014; material icon named "library add check".
+  static const IconData library_add_check = IconData(0xe80e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">library_add_check</i> &#x2014; material icon named "library add check outlined".
+  static const IconData library_add_check_outlined = IconData(0xe275, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">library_add_check</i> &#x2014; material icon named "library add check rounded".
+  static const IconData library_add_check_rounded = IconData(0xf2a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">library_add_check</i> &#x2014; material icon named "library add check sharp".
+  static const IconData library_add_check_sharp = IconData(0xed79, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">library_add</i> &#x2014; material icon named "library add outlined".
+  static const IconData library_add_outlined = IconData(0xe276, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">library_add</i> &#x2014; material icon named "library add rounded".
+  static const IconData library_add_rounded = IconData(0xf2a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">library_add</i> &#x2014; material icon named "library add sharp".
+  static const IconData library_add_sharp = IconData(0xed7a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">library_books</i> &#x2014; material icon named "library books".
+  static const IconData library_books = IconData(0xe80f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">library_books</i> &#x2014; material icon named "library books outlined".
+  static const IconData library_books_outlined = IconData(0xe277, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">library_books</i> &#x2014; material icon named "library books rounded".
+  static const IconData library_books_rounded = IconData(0xf2a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">library_books</i> &#x2014; material icon named "library books sharp".
+  static const IconData library_books_sharp = IconData(0xed7b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">library_music</i> &#x2014; material icon named "library music".
+  static const IconData library_music = IconData(0xe810, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">library_music</i> &#x2014; material icon named "library music outlined".
+  static const IconData library_music_outlined = IconData(0xe278, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">library_music</i> &#x2014; material icon named "library music rounded".
+  static const IconData library_music_rounded = IconData(0xf2aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">library_music</i> &#x2014; material icon named "library music sharp".
+  static const IconData library_music_sharp = IconData(0xed7c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">lightbulb</i> &#x2014; material icon named "lightbulb".
+  static const IconData lightbulb = IconData(0xe811, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">lightbulb_outline</i> &#x2014; material icon named "lightbulb outline".
+  static const IconData lightbulb_outline = IconData(0xe812, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">lightbulb_outline</i> &#x2014; material icon named "lightbulb outline rounded".
+  static const IconData lightbulb_outline_rounded = IconData(0xf2ab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">lightbulb_outline</i> &#x2014; material icon named "lightbulb outline sharp".
+  static const IconData lightbulb_outline_sharp = IconData(0xed7d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">lightbulb</i> &#x2014; material icon named "lightbulb outlined".
+  static const IconData lightbulb_outlined = IconData(0xe279, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">line_style</i> &#x2014; material icon named "line style".
+  static const IconData line_style = IconData(0xe813, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">line_style</i> &#x2014; material icon named "line style outlined".
+  static const IconData line_style_outlined = IconData(0xe27a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">line_style</i> &#x2014; material icon named "line style rounded".
+  static const IconData line_style_rounded = IconData(0xf2ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">line_style</i> &#x2014; material icon named "line style sharp".
+  static const IconData line_style_sharp = IconData(0xed7e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">line_weight</i> &#x2014; material icon named "line weight".
+  static const IconData line_weight = IconData(0xe814, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">line_weight</i> &#x2014; material icon named "line weight outlined".
+  static const IconData line_weight_outlined = IconData(0xe27b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">line_weight</i> &#x2014; material icon named "line weight rounded".
+  static const IconData line_weight_rounded = IconData(0xf2ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">line_weight</i> &#x2014; material icon named "line weight sharp".
+  static const IconData line_weight_sharp = IconData(0xed7f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">linear_scale</i> &#x2014; material icon named "linear scale".
+  static const IconData linear_scale = IconData(0xe815, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">linear_scale</i> &#x2014; material icon named "linear scale outlined".
+  static const IconData linear_scale_outlined = IconData(0xe27c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">linear_scale</i> &#x2014; material icon named "linear scale rounded".
+  static const IconData linear_scale_rounded = IconData(0xf2ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">linear_scale</i> &#x2014; material icon named "linear scale sharp".
+  static const IconData linear_scale_sharp = IconData(0xed80, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">link</i> &#x2014; material icon named "link".
+  static const IconData link = IconData(0xe816, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">link_off</i> &#x2014; material icon named "link off".
+  static const IconData link_off = IconData(0xe817, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">link_off</i> &#x2014; material icon named "link off outlined".
+  static const IconData link_off_outlined = IconData(0xe27d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">link_off</i> &#x2014; material icon named "link off rounded".
+  static const IconData link_off_rounded = IconData(0xf2af, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">link_off</i> &#x2014; material icon named "link off sharp".
+  static const IconData link_off_sharp = IconData(0xed81, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">link</i> &#x2014; material icon named "link outlined".
+  static const IconData link_outlined = IconData(0xe27e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">link</i> &#x2014; material icon named "link rounded".
+  static const IconData link_rounded = IconData(0xf2b0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">link</i> &#x2014; material icon named "link sharp".
+  static const IconData link_sharp = IconData(0xed82, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">linked_camera</i> &#x2014; material icon named "linked camera".
+  static const IconData linked_camera = IconData(0xe818, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">linked_camera</i> &#x2014; material icon named "linked camera outlined".
+  static const IconData linked_camera_outlined = IconData(0xe27f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">linked_camera</i> &#x2014; material icon named "linked camera rounded".
+  static const IconData linked_camera_rounded = IconData(0xf2b1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">linked_camera</i> &#x2014; material icon named "linked camera sharp".
+  static const IconData linked_camera_sharp = IconData(0xed83, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">liquor</i> &#x2014; material icon named "liquor".
+  static const IconData liquor = IconData(0xe819, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">list</i> &#x2014; material icon named "list".
+  static const IconData list = IconData(0xe81a, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">list_alt</i> &#x2014; material icon named "list alt".
+  static const IconData list_alt = IconData(0xe81b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">list_alt</i> &#x2014; material icon named "list alt outlined".
+  static const IconData list_alt_outlined = IconData(0xe280, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">list_alt</i> &#x2014; material icon named "list alt rounded".
+  static const IconData list_alt_rounded = IconData(0xf2b2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">list_alt</i> &#x2014; material icon named "list alt sharp".
+  static const IconData list_alt_sharp = IconData(0xed84, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">list</i> &#x2014; material icon named "list outlined".
+  static const IconData list_outlined = IconData(0xe281, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">list</i> &#x2014; material icon named "list rounded".
+  static const IconData list_rounded = IconData(0xf2b3, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">list</i> &#x2014; material icon named "list sharp".
+  static const IconData list_sharp = IconData(0xed85, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">live_help</i> &#x2014; material icon named "live help".
+  static const IconData live_help = IconData(0xe81c, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">live_help</i> &#x2014; material icon named "live help outlined".
+  static const IconData live_help_outlined = IconData(0xe282, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">live_help</i> &#x2014; material icon named "live help rounded".
+  static const IconData live_help_rounded = IconData(0xf2b4, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">live_help</i> &#x2014; material icon named "live help sharp".
+  static const IconData live_help_sharp = IconData(0xed86, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">live_tv</i> &#x2014; material icon named "live tv".
+  static const IconData live_tv = IconData(0xe81d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">live_tv</i> &#x2014; material icon named "live tv outlined".
+  static const IconData live_tv_outlined = IconData(0xe283, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">live_tv</i> &#x2014; material icon named "live tv rounded".
+  static const IconData live_tv_rounded = IconData(0xf2b5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">live_tv</i> &#x2014; material icon named "live tv sharp".
+  static const IconData live_tv_sharp = IconData(0xed87, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_activity</i> &#x2014; material icon named "local activity".
+  static const IconData local_activity = IconData(0xe81e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_activity</i> &#x2014; material icon named "local activity outlined".
+  static const IconData local_activity_outlined = IconData(0xe284, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_activity</i> &#x2014; material icon named "local activity rounded".
+  static const IconData local_activity_rounded = IconData(0xf2b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_activity</i> &#x2014; material icon named "local activity sharp".
+  static const IconData local_activity_sharp = IconData(0xed88, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_airport</i> &#x2014; material icon named "local airport".
+  static const IconData local_airport = IconData(0xe81f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_airport</i> &#x2014; material icon named "local airport outlined".
+  static const IconData local_airport_outlined = IconData(0xe285, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_airport</i> &#x2014; material icon named "local airport rounded".
+  static const IconData local_airport_rounded = IconData(0xf2b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_airport</i> &#x2014; material icon named "local airport sharp".
+  static const IconData local_airport_sharp = IconData(0xed89, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_atm</i> &#x2014; material icon named "local atm".
+  static const IconData local_atm = IconData(0xe820, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_atm</i> &#x2014; material icon named "local atm outlined".
+  static const IconData local_atm_outlined = IconData(0xe286, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_atm</i> &#x2014; material icon named "local atm rounded".
+  static const IconData local_atm_rounded = IconData(0xf2b8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_atm</i> &#x2014; material icon named "local atm sharp".
+  static const IconData local_atm_sharp = IconData(0xed8a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_attraction</i> &#x2014; material icon named "local attraction".
+  static const IconData local_attraction = IconData(0xe81e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_attraction</i> &#x2014; material icon named "local attraction outlined".
+  static const IconData local_attraction_outlined = IconData(0xe284, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_attraction</i> &#x2014; material icon named "local attraction rounded".
+  static const IconData local_attraction_rounded = IconData(0xf2b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_attraction</i> &#x2014; material icon named "local attraction sharp".
+  static const IconData local_attraction_sharp = IconData(0xed88, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_bar</i> &#x2014; material icon named "local bar".
+  static const IconData local_bar = IconData(0xe821, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_bar</i> &#x2014; material icon named "local bar outlined".
+  static const IconData local_bar_outlined = IconData(0xe287, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_bar</i> &#x2014; material icon named "local bar rounded".
+  static const IconData local_bar_rounded = IconData(0xf2b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_bar</i> &#x2014; material icon named "local bar sharp".
+  static const IconData local_bar_sharp = IconData(0xed8b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_cafe</i> &#x2014; material icon named "local cafe".
+  static const IconData local_cafe = IconData(0xe822, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_cafe</i> &#x2014; material icon named "local cafe outlined".
+  static const IconData local_cafe_outlined = IconData(0xe288, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_cafe</i> &#x2014; material icon named "local cafe rounded".
+  static const IconData local_cafe_rounded = IconData(0xf2ba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_cafe</i> &#x2014; material icon named "local cafe sharp".
+  static const IconData local_cafe_sharp = IconData(0xed8c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_car_wash</i> &#x2014; material icon named "local car wash".
+  static const IconData local_car_wash = IconData(0xe823, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_car_wash</i> &#x2014; material icon named "local car wash outlined".
+  static const IconData local_car_wash_outlined = IconData(0xe289, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_car_wash</i> &#x2014; material icon named "local car wash rounded".
+  static const IconData local_car_wash_rounded = IconData(0xf2bb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_car_wash</i> &#x2014; material icon named "local car wash sharp".
+  static const IconData local_car_wash_sharp = IconData(0xed8d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_convenience_store</i> &#x2014; material icon named "local convenience store".
+  static const IconData local_convenience_store = IconData(0xe824, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_convenience_store</i> &#x2014; material icon named "local convenience store outlined".
+  static const IconData local_convenience_store_outlined = IconData(0xe28a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_convenience_store</i> &#x2014; material icon named "local convenience store rounded".
+  static const IconData local_convenience_store_rounded = IconData(0xf2bc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_convenience_store</i> &#x2014; material icon named "local convenience store sharp".
+  static const IconData local_convenience_store_sharp = IconData(0xed8e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_dining</i> &#x2014; material icon named "local dining".
+  static const IconData local_dining = IconData(0xe825, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_dining</i> &#x2014; material icon named "local dining outlined".
+  static const IconData local_dining_outlined = IconData(0xe28b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_dining</i> &#x2014; material icon named "local dining rounded".
+  static const IconData local_dining_rounded = IconData(0xf2bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_dining</i> &#x2014; material icon named "local dining sharp".
+  static const IconData local_dining_sharp = IconData(0xed8f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_drink</i> &#x2014; material icon named "local drink".
+  static const IconData local_drink = IconData(0xe826, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_drink</i> &#x2014; material icon named "local drink outlined".
+  static const IconData local_drink_outlined = IconData(0xe28c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_drink</i> &#x2014; material icon named "local drink rounded".
+  static const IconData local_drink_rounded = IconData(0xf2be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_drink</i> &#x2014; material icon named "local drink sharp".
+  static const IconData local_drink_sharp = IconData(0xed90, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_fire_department</i> &#x2014; material icon named "local fire department".
+  static const IconData local_fire_department = IconData(0xe827, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_fire_department</i> &#x2014; material icon named "local fire department outlined".
+  static const IconData local_fire_department_outlined = IconData(0xe28d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_fire_department</i> &#x2014; material icon named "local fire department rounded".
+  static const IconData local_fire_department_rounded = IconData(0xf2bf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_fire_department</i> &#x2014; material icon named "local fire department sharp".
+  static const IconData local_fire_department_sharp = IconData(0xed91, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_florist</i> &#x2014; material icon named "local florist".
+  static const IconData local_florist = IconData(0xe828, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_florist</i> &#x2014; material icon named "local florist outlined".
+  static const IconData local_florist_outlined = IconData(0xe28e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_florist</i> &#x2014; material icon named "local florist rounded".
+  static const IconData local_florist_rounded = IconData(0xf2c0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_florist</i> &#x2014; material icon named "local florist sharp".
+  static const IconData local_florist_sharp = IconData(0xed92, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_gas_station</i> &#x2014; material icon named "local gas station".
+  static const IconData local_gas_station = IconData(0xe829, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_gas_station</i> &#x2014; material icon named "local gas station outlined".
+  static const IconData local_gas_station_outlined = IconData(0xe28f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_gas_station</i> &#x2014; material icon named "local gas station rounded".
+  static const IconData local_gas_station_rounded = IconData(0xf2c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_gas_station</i> &#x2014; material icon named "local gas station sharp".
+  static const IconData local_gas_station_sharp = IconData(0xed93, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_grocery_store</i> &#x2014; material icon named "local grocery store".
+  static const IconData local_grocery_store = IconData(0xe82a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_grocery_store</i> &#x2014; material icon named "local grocery store outlined".
+  static const IconData local_grocery_store_outlined = IconData(0xe290, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_grocery_store</i> &#x2014; material icon named "local grocery store rounded".
+  static const IconData local_grocery_store_rounded = IconData(0xf2c2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_grocery_store</i> &#x2014; material icon named "local grocery store sharp".
+  static const IconData local_grocery_store_sharp = IconData(0xed94, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_hospital</i> &#x2014; material icon named "local hospital".
+  static const IconData local_hospital = IconData(0xe82b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_hospital</i> &#x2014; material icon named "local hospital outlined".
+  static const IconData local_hospital_outlined = IconData(0xe291, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_hospital</i> &#x2014; material icon named "local hospital rounded".
+  static const IconData local_hospital_rounded = IconData(0xf2c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_hospital</i> &#x2014; material icon named "local hospital sharp".
+  static const IconData local_hospital_sharp = IconData(0xed95, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_hotel</i> &#x2014; material icon named "local hotel".
+  static const IconData local_hotel = IconData(0xe82c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_hotel</i> &#x2014; material icon named "local hotel outlined".
+  static const IconData local_hotel_outlined = IconData(0xe292, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_hotel</i> &#x2014; material icon named "local hotel rounded".
+  static const IconData local_hotel_rounded = IconData(0xf2c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_hotel</i> &#x2014; material icon named "local hotel sharp".
+  static const IconData local_hotel_sharp = IconData(0xed96, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_laundry_service</i> &#x2014; material icon named "local laundry service".
+  static const IconData local_laundry_service = IconData(0xe82d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_laundry_service</i> &#x2014; material icon named "local laundry service outlined".
+  static const IconData local_laundry_service_outlined = IconData(0xe293, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_laundry_service</i> &#x2014; material icon named "local laundry service rounded".
+  static const IconData local_laundry_service_rounded = IconData(0xf2c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_laundry_service</i> &#x2014; material icon named "local laundry service sharp".
+  static const IconData local_laundry_service_sharp = IconData(0xed97, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_library</i> &#x2014; material icon named "local library".
+  static const IconData local_library = IconData(0xe82e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_library</i> &#x2014; material icon named "local library outlined".
+  static const IconData local_library_outlined = IconData(0xe294, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_library</i> &#x2014; material icon named "local library rounded".
+  static const IconData local_library_rounded = IconData(0xf2c6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_library</i> &#x2014; material icon named "local library sharp".
+  static const IconData local_library_sharp = IconData(0xed98, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_mall</i> &#x2014; material icon named "local mall".
+  static const IconData local_mall = IconData(0xe82f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_mall</i> &#x2014; material icon named "local mall outlined".
+  static const IconData local_mall_outlined = IconData(0xe295, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_mall</i> &#x2014; material icon named "local mall rounded".
+  static const IconData local_mall_rounded = IconData(0xf2c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_mall</i> &#x2014; material icon named "local mall sharp".
+  static const IconData local_mall_sharp = IconData(0xed99, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_movies</i> &#x2014; material icon named "local movies".
+  static const IconData local_movies = IconData(0xe830, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_movies</i> &#x2014; material icon named "local movies outlined".
+  static const IconData local_movies_outlined = IconData(0xe296, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_movies</i> &#x2014; material icon named "local movies rounded".
+  static const IconData local_movies_rounded = IconData(0xf2c8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_movies</i> &#x2014; material icon named "local movies sharp".
+  static const IconData local_movies_sharp = IconData(0xed9a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_offer</i> &#x2014; material icon named "local offer".
+  static const IconData local_offer = IconData(0xe831, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_offer</i> &#x2014; material icon named "local offer outlined".
+  static const IconData local_offer_outlined = IconData(0xe297, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_offer</i> &#x2014; material icon named "local offer rounded".
+  static const IconData local_offer_rounded = IconData(0xf2c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_offer</i> &#x2014; material icon named "local offer sharp".
+  static const IconData local_offer_sharp = IconData(0xed9b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_parking</i> &#x2014; material icon named "local parking".
+  static const IconData local_parking = IconData(0xe832, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_parking</i> &#x2014; material icon named "local parking outlined".
+  static const IconData local_parking_outlined = IconData(0xe298, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_parking</i> &#x2014; material icon named "local parking rounded".
+  static const IconData local_parking_rounded = IconData(0xf2ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_parking</i> &#x2014; material icon named "local parking sharp".
+  static const IconData local_parking_sharp = IconData(0xed9c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_pharmacy</i> &#x2014; material icon named "local pharmacy".
+  static const IconData local_pharmacy = IconData(0xe833, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_pharmacy</i> &#x2014; material icon named "local pharmacy outlined".
+  static const IconData local_pharmacy_outlined = IconData(0xe299, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_pharmacy</i> &#x2014; material icon named "local pharmacy rounded".
+  static const IconData local_pharmacy_rounded = IconData(0xf2cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_pharmacy</i> &#x2014; material icon named "local pharmacy sharp".
+  static const IconData local_pharmacy_sharp = IconData(0xed9d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_phone</i> &#x2014; material icon named "local phone".
+  static const IconData local_phone = IconData(0xe834, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_phone</i> &#x2014; material icon named "local phone outlined".
+  static const IconData local_phone_outlined = IconData(0xe29a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_phone</i> &#x2014; material icon named "local phone rounded".
+  static const IconData local_phone_rounded = IconData(0xf2cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_phone</i> &#x2014; material icon named "local phone sharp".
+  static const IconData local_phone_sharp = IconData(0xed9e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_pizza</i> &#x2014; material icon named "local pizza".
+  static const IconData local_pizza = IconData(0xe835, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_pizza</i> &#x2014; material icon named "local pizza outlined".
+  static const IconData local_pizza_outlined = IconData(0xe29b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_pizza</i> &#x2014; material icon named "local pizza rounded".
+  static const IconData local_pizza_rounded = IconData(0xf2cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_pizza</i> &#x2014; material icon named "local pizza sharp".
+  static const IconData local_pizza_sharp = IconData(0xed9f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_play</i> &#x2014; material icon named "local play".
+  static const IconData local_play = IconData(0xe836, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_play</i> &#x2014; material icon named "local play outlined".
+  static const IconData local_play_outlined = IconData(0xe29c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_play</i> &#x2014; material icon named "local play rounded".
+  static const IconData local_play_rounded = IconData(0xf2ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_play</i> &#x2014; material icon named "local play sharp".
+  static const IconData local_play_sharp = IconData(0xeda0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_police</i> &#x2014; material icon named "local police".
+  static const IconData local_police = IconData(0xe837, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_police</i> &#x2014; material icon named "local police outlined".
+  static const IconData local_police_outlined = IconData(0xe29d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_police</i> &#x2014; material icon named "local police rounded".
+  static const IconData local_police_rounded = IconData(0xf2cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_police</i> &#x2014; material icon named "local police sharp".
+  static const IconData local_police_sharp = IconData(0xeda1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_post_office</i> &#x2014; material icon named "local post office".
+  static const IconData local_post_office = IconData(0xe838, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_post_office</i> &#x2014; material icon named "local post office outlined".
+  static const IconData local_post_office_outlined = IconData(0xe29e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_post_office</i> &#x2014; material icon named "local post office rounded".
+  static const IconData local_post_office_rounded = IconData(0xf2d0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_post_office</i> &#x2014; material icon named "local post office sharp".
+  static const IconData local_post_office_sharp = IconData(0xeda2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_print_shop</i> &#x2014; material icon named "local print shop".
+  static const IconData local_print_shop = IconData(0xe839, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_print_shop</i> &#x2014; material icon named "local print shop outlined".
+  static const IconData local_print_shop_outlined = IconData(0xe29f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_print_shop</i> &#x2014; material icon named "local print shop rounded".
+  static const IconData local_print_shop_rounded = IconData(0xf2d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_print_shop</i> &#x2014; material icon named "local print shop sharp".
+  static const IconData local_print_shop_sharp = IconData(0xeda3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_printshop</i> &#x2014; material icon named "local printshop".
+  static const IconData local_printshop = IconData(0xe839, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_printshop</i> &#x2014; material icon named "local printshop outlined".
+  static const IconData local_printshop_outlined = IconData(0xe29f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_printshop</i> &#x2014; material icon named "local printshop rounded".
+  static const IconData local_printshop_rounded = IconData(0xf2d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_printshop</i> &#x2014; material icon named "local printshop sharp".
+  static const IconData local_printshop_sharp = IconData(0xeda3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_restaurant</i> &#x2014; material icon named "local restaurant".
+  static const IconData local_restaurant = IconData(0xe825, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_restaurant</i> &#x2014; material icon named "local restaurant outlined".
+  static const IconData local_restaurant_outlined = IconData(0xe28b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_restaurant</i> &#x2014; material icon named "local restaurant rounded".
+  static const IconData local_restaurant_rounded = IconData(0xf2bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_restaurant</i> &#x2014; material icon named "local restaurant sharp".
+  static const IconData local_restaurant_sharp = IconData(0xed8f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_see</i> &#x2014; material icon named "local see".
+  static const IconData local_see = IconData(0xe83a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_see</i> &#x2014; material icon named "local see outlined".
+  static const IconData local_see_outlined = IconData(0xe2a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_see</i> &#x2014; material icon named "local see rounded".
+  static const IconData local_see_rounded = IconData(0xf2d2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_see</i> &#x2014; material icon named "local see sharp".
+  static const IconData local_see_sharp = IconData(0xeda4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_shipping</i> &#x2014; material icon named "local shipping".
+  static const IconData local_shipping = IconData(0xe83b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_shipping</i> &#x2014; material icon named "local shipping outlined".
+  static const IconData local_shipping_outlined = IconData(0xe2a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_shipping</i> &#x2014; material icon named "local shipping rounded".
+  static const IconData local_shipping_rounded = IconData(0xf2d3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_shipping</i> &#x2014; material icon named "local shipping sharp".
+  static const IconData local_shipping_sharp = IconData(0xeda5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">local_taxi</i> &#x2014; material icon named "local taxi".
+  static const IconData local_taxi = IconData(0xe83c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">local_taxi</i> &#x2014; material icon named "local taxi outlined".
+  static const IconData local_taxi_outlined = IconData(0xe2a2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">local_taxi</i> &#x2014; material icon named "local taxi rounded".
+  static const IconData local_taxi_rounded = IconData(0xf2d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">local_taxi</i> &#x2014; material icon named "local taxi sharp".
+  static const IconData local_taxi_sharp = IconData(0xeda6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">location_city</i> &#x2014; material icon named "location city".
+  static const IconData location_city = IconData(0xe83d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">location_city</i> &#x2014; material icon named "location city outlined".
+  static const IconData location_city_outlined = IconData(0xe2a3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">location_city</i> &#x2014; material icon named "location city rounded".
+  static const IconData location_city_rounded = IconData(0xf2d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">location_city</i> &#x2014; material icon named "location city sharp".
+  static const IconData location_city_sharp = IconData(0xeda7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">location_disabled</i> &#x2014; material icon named "location disabled".
+  static const IconData location_disabled = IconData(0xe83e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">location_disabled</i> &#x2014; material icon named "location disabled outlined".
+  static const IconData location_disabled_outlined = IconData(0xe2a4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">location_disabled</i> &#x2014; material icon named "location disabled rounded".
+  static const IconData location_disabled_rounded = IconData(0xf2d6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">location_disabled</i> &#x2014; material icon named "location disabled sharp".
+  static const IconData location_disabled_sharp = IconData(0xeda8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">location_history</i> &#x2014; material icon named "location history".
+  static const IconData location_history = IconData(0xe906, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">location_history</i> &#x2014; material icon named "location history outlined".
+  static const IconData location_history_outlined = IconData(0xe353, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">location_history</i> &#x2014; material icon named "location history rounded".
+  static const IconData location_history_rounded = IconData(0xf385, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">location_history</i> &#x2014; material icon named "location history sharp".
+  static const IconData location_history_sharp = IconData(0xee57, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">location_off</i> &#x2014; material icon named "location off".
+  static const IconData location_off = IconData(0xe83f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">location_off</i> &#x2014; material icon named "location off outlined".
+  static const IconData location_off_outlined = IconData(0xe2a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">location_off</i> &#x2014; material icon named "location off rounded".
+  static const IconData location_off_rounded = IconData(0xf2d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">location_off</i> &#x2014; material icon named "location off sharp".
+  static const IconData location_off_sharp = IconData(0xeda9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">location_on</i> &#x2014; material icon named "location on".
+  static const IconData location_on = IconData(0xe840, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">location_on</i> &#x2014; material icon named "location on outlined".
+  static const IconData location_on_outlined = IconData(0xe2a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">location_on</i> &#x2014; material icon named "location on rounded".
+  static const IconData location_on_rounded = IconData(0xf2d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">location_on</i> &#x2014; material icon named "location on sharp".
+  static const IconData location_on_sharp = IconData(0xedaa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">location_pin</i> &#x2014; material icon named "location pin".
+  static const IconData location_pin = IconData(0xe841, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">location_searching</i> &#x2014; material icon named "location searching".
+  static const IconData location_searching = IconData(0xe842, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">location_searching</i> &#x2014; material icon named "location searching outlined".
+  static const IconData location_searching_outlined = IconData(0xe2a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">location_searching</i> &#x2014; material icon named "location searching rounded".
+  static const IconData location_searching_rounded = IconData(0xf2d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">location_searching</i> &#x2014; material icon named "location searching sharp".
+  static const IconData location_searching_sharp = IconData(0xedab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">lock</i> &#x2014; material icon named "lock".
+  static const IconData lock = IconData(0xe843, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">lock_clock</i> &#x2014; material icon named "lock clock".
+  static const IconData lock_clock = IconData(0xe844, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">lock_open</i> &#x2014; material icon named "lock open".
+  static const IconData lock_open = IconData(0xe845, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">lock_open</i> &#x2014; material icon named "lock open outlined".
+  static const IconData lock_open_outlined = IconData(0xe2a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">lock_open</i> &#x2014; material icon named "lock open rounded".
+  static const IconData lock_open_rounded = IconData(0xf2da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">lock_open</i> &#x2014; material icon named "lock open sharp".
+  static const IconData lock_open_sharp = IconData(0xedac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">lock_outline</i> &#x2014; material icon named "lock outline".
+  static const IconData lock_outline = IconData(0xe846, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">lock_outline</i> &#x2014; material icon named "lock outline rounded".
+  static const IconData lock_outline_rounded = IconData(0xf2db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">lock_outline</i> &#x2014; material icon named "lock outline sharp".
+  static const IconData lock_outline_sharp = IconData(0xedad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">lock</i> &#x2014; material icon named "lock outlined".
+  static const IconData lock_outlined = IconData(0xe2a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">lock</i> &#x2014; material icon named "lock rounded".
+  static const IconData lock_rounded = IconData(0xf2dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">lock</i> &#x2014; material icon named "lock sharp".
+  static const IconData lock_sharp = IconData(0xedae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">login</i> &#x2014; material icon named "login".
+  static const IconData login = IconData(0xe847, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">login</i> &#x2014; material icon named "login outlined".
+  static const IconData login_outlined = IconData(0xe2aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">login</i> &#x2014; material icon named "login rounded".
+  static const IconData login_rounded = IconData(0xf2dd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">login</i> &#x2014; material icon named "login sharp".
+  static const IconData login_sharp = IconData(0xedaf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">logout</i> &#x2014; material icon named "logout".
+  static const IconData logout = IconData(0xe848, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">looks</i> &#x2014; material icon named "looks".
+  static const IconData looks = IconData(0xe849, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">looks_3</i> &#x2014; material icon named "looks 3".
+  static const IconData looks_3 = IconData(0xe84a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">looks_3</i> &#x2014; material icon named "looks 3 outlined".
+  static const IconData looks_3_outlined = IconData(0xe2ab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">looks_3</i> &#x2014; material icon named "looks 3 rounded".
+  static const IconData looks_3_rounded = IconData(0xf2de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">looks_3</i> &#x2014; material icon named "looks 3 sharp".
+  static const IconData looks_3_sharp = IconData(0xedb0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">looks_4</i> &#x2014; material icon named "looks 4".
+  static const IconData looks_4 = IconData(0xe84b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">looks_4</i> &#x2014; material icon named "looks 4 outlined".
+  static const IconData looks_4_outlined = IconData(0xe2ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">looks_4</i> &#x2014; material icon named "looks 4 rounded".
+  static const IconData looks_4_rounded = IconData(0xf2df, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">looks_4</i> &#x2014; material icon named "looks 4 sharp".
+  static const IconData looks_4_sharp = IconData(0xedb1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">looks_5</i> &#x2014; material icon named "looks 5".
+  static const IconData looks_5 = IconData(0xe84c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">looks_5</i> &#x2014; material icon named "looks 5 outlined".
+  static const IconData looks_5_outlined = IconData(0xe2ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">looks_5</i> &#x2014; material icon named "looks 5 rounded".
+  static const IconData looks_5_rounded = IconData(0xf2e0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">looks_5</i> &#x2014; material icon named "looks 5 sharp".
+  static const IconData looks_5_sharp = IconData(0xedb2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">looks_6</i> &#x2014; material icon named "looks 6".
+  static const IconData looks_6 = IconData(0xe84d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">looks_6</i> &#x2014; material icon named "looks 6 outlined".
+  static const IconData looks_6_outlined = IconData(0xe2ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">looks_6</i> &#x2014; material icon named "looks 6 rounded".
+  static const IconData looks_6_rounded = IconData(0xf2e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">looks_6</i> &#x2014; material icon named "looks 6 sharp".
+  static const IconData looks_6_sharp = IconData(0xedb3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">looks_one</i> &#x2014; material icon named "looks one".
+  static const IconData looks_one = IconData(0xe84e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">looks_one</i> &#x2014; material icon named "looks one outlined".
+  static const IconData looks_one_outlined = IconData(0xe2af, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">looks_one</i> &#x2014; material icon named "looks one rounded".
+  static const IconData looks_one_rounded = IconData(0xf2e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">looks_one</i> &#x2014; material icon named "looks one sharp".
+  static const IconData looks_one_sharp = IconData(0xedb4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">looks</i> &#x2014; material icon named "looks outlined".
+  static const IconData looks_outlined = IconData(0xe2b0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">looks</i> &#x2014; material icon named "looks rounded".
+  static const IconData looks_rounded = IconData(0xf2e3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">looks</i> &#x2014; material icon named "looks sharp".
+  static const IconData looks_sharp = IconData(0xedb5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">looks_two</i> &#x2014; material icon named "looks two".
+  static const IconData looks_two = IconData(0xe84f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">looks_two</i> &#x2014; material icon named "looks two outlined".
+  static const IconData looks_two_outlined = IconData(0xe2b1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">looks_two</i> &#x2014; material icon named "looks two rounded".
+  static const IconData looks_two_rounded = IconData(0xf2e4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">looks_two</i> &#x2014; material icon named "looks two sharp".
+  static const IconData looks_two_sharp = IconData(0xedb6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">loop</i> &#x2014; material icon named "loop".
+  static const IconData loop = IconData(0xe850, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">loop</i> &#x2014; material icon named "loop outlined".
+  static const IconData loop_outlined = IconData(0xe2b2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">loop</i> &#x2014; material icon named "loop rounded".
+  static const IconData loop_rounded = IconData(0xf2e5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">loop</i> &#x2014; material icon named "loop sharp".
+  static const IconData loop_sharp = IconData(0xedb7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">loupe</i> &#x2014; material icon named "loupe".
+  static const IconData loupe = IconData(0xe851, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">loupe</i> &#x2014; material icon named "loupe outlined".
+  static const IconData loupe_outlined = IconData(0xe2b3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">loupe</i> &#x2014; material icon named "loupe rounded".
+  static const IconData loupe_rounded = IconData(0xf2e6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">loupe</i> &#x2014; material icon named "loupe sharp".
+  static const IconData loupe_sharp = IconData(0xedb8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">low_priority</i> &#x2014; material icon named "low priority".
+  static const IconData low_priority = IconData(0xe852, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">low_priority</i> &#x2014; material icon named "low priority outlined".
+  static const IconData low_priority_outlined = IconData(0xe2b4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">low_priority</i> &#x2014; material icon named "low priority rounded".
+  static const IconData low_priority_rounded = IconData(0xf2e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">low_priority</i> &#x2014; material icon named "low priority sharp".
+  static const IconData low_priority_sharp = IconData(0xedb9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">loyalty</i> &#x2014; material icon named "loyalty".
+  static const IconData loyalty = IconData(0xe853, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">loyalty</i> &#x2014; material icon named "loyalty outlined".
+  static const IconData loyalty_outlined = IconData(0xe2b5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">loyalty</i> &#x2014; material icon named "loyalty rounded".
+  static const IconData loyalty_rounded = IconData(0xf2e8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">loyalty</i> &#x2014; material icon named "loyalty sharp".
+  static const IconData loyalty_sharp = IconData(0xedba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">lunch_dining</i> &#x2014; material icon named "lunch dining".
+  static const IconData lunch_dining = IconData(0xe854, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mail</i> &#x2014; material icon named "mail".
+  static const IconData mail = IconData(0xe855, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mail_outline</i> &#x2014; material icon named "mail outline".
+  static const IconData mail_outline = IconData(0xe856, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mail_outline</i> &#x2014; material icon named "mail outline outlined".
+  static const IconData mail_outline_outlined = IconData(0xe2b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mail_outline</i> &#x2014; material icon named "mail outline rounded".
+  static const IconData mail_outline_rounded = IconData(0xf2e9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mail_outline</i> &#x2014; material icon named "mail outline sharp".
+  static const IconData mail_outline_sharp = IconData(0xedbb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mail</i> &#x2014; material icon named "mail outlined".
+  static const IconData mail_outlined = IconData(0xe2b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mail</i> &#x2014; material icon named "mail rounded".
+  static const IconData mail_rounded = IconData(0xf2ea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mail</i> &#x2014; material icon named "mail sharp".
+  static const IconData mail_sharp = IconData(0xedbc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">map</i> &#x2014; material icon named "map".
+  static const IconData map = IconData(0xe857, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">map</i> &#x2014; material icon named "map outlined".
+  static const IconData map_outlined = IconData(0xe2b8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">map</i> &#x2014; material icon named "map rounded".
+  static const IconData map_rounded = IconData(0xf2eb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">map</i> &#x2014; material icon named "map sharp".
+  static const IconData map_sharp = IconData(0xedbd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">maps_ugc</i> &#x2014; material icon named "maps ugc".
+  static const IconData maps_ugc = IconData(0xe858, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">maps_ugc</i> &#x2014; material icon named "maps ugc outlined".
+  static const IconData maps_ugc_outlined = IconData(0xe2b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">maps_ugc</i> &#x2014; material icon named "maps ugc rounded".
+  static const IconData maps_ugc_rounded = IconData(0xf2ec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">maps_ugc</i> &#x2014; material icon named "maps ugc sharp".
+  static const IconData maps_ugc_sharp = IconData(0xedbe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">margin</i> &#x2014; material icon named "margin".
+  static const IconData margin = IconData(0xe859, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mark_as_unread</i> &#x2014; material icon named "mark as unread".
+  static const IconData mark_as_unread = IconData(0xe85a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mark_chat_read</i> &#x2014; material icon named "mark chat read".
+  static const IconData mark_chat_read = IconData(0xe85b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mark_chat_read</i> &#x2014; material icon named "mark chat read outlined".
+  static const IconData mark_chat_read_outlined = IconData(0xe2ba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mark_chat_read</i> &#x2014; material icon named "mark chat read rounded".
+  static const IconData mark_chat_read_rounded = IconData(0xf2ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mark_chat_read</i> &#x2014; material icon named "mark chat read sharp".
+  static const IconData mark_chat_read_sharp = IconData(0xedbf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mark_chat_unread</i> &#x2014; material icon named "mark chat unread".
+  static const IconData mark_chat_unread = IconData(0xe85c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mark_chat_unread</i> &#x2014; material icon named "mark chat unread outlined".
+  static const IconData mark_chat_unread_outlined = IconData(0xe2bb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mark_chat_unread</i> &#x2014; material icon named "mark chat unread rounded".
+  static const IconData mark_chat_unread_rounded = IconData(0xf2ee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mark_chat_unread</i> &#x2014; material icon named "mark chat unread sharp".
+  static const IconData mark_chat_unread_sharp = IconData(0xedc0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mark_email_read</i> &#x2014; material icon named "mark email read".
+  static const IconData mark_email_read = IconData(0xe85d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mark_email_read</i> &#x2014; material icon named "mark email read outlined".
+  static const IconData mark_email_read_outlined = IconData(0xe2bc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mark_email_read</i> &#x2014; material icon named "mark email read rounded".
+  static const IconData mark_email_read_rounded = IconData(0xf2ef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mark_email_read</i> &#x2014; material icon named "mark email read sharp".
+  static const IconData mark_email_read_sharp = IconData(0xedc1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mark_email_unread</i> &#x2014; material icon named "mark email unread".
+  static const IconData mark_email_unread = IconData(0xe85e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mark_email_unread</i> &#x2014; material icon named "mark email unread outlined".
+  static const IconData mark_email_unread_outlined = IconData(0xe2bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mark_email_unread</i> &#x2014; material icon named "mark email unread rounded".
+  static const IconData mark_email_unread_rounded = IconData(0xf2f0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mark_email_unread</i> &#x2014; material icon named "mark email unread sharp".
+  static const IconData mark_email_unread_sharp = IconData(0xedc2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">markunread</i> &#x2014; material icon named "markunread".
+  static const IconData markunread = IconData(0xe85f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">markunread_mailbox</i> &#x2014; material icon named "markunread mailbox".
+  static const IconData markunread_mailbox = IconData(0xe860, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">markunread_mailbox</i> &#x2014; material icon named "markunread mailbox outlined".
+  static const IconData markunread_mailbox_outlined = IconData(0xe2be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">markunread_mailbox</i> &#x2014; material icon named "markunread mailbox rounded".
+  static const IconData markunread_mailbox_rounded = IconData(0xf2f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">markunread_mailbox</i> &#x2014; material icon named "markunread mailbox sharp".
+  static const IconData markunread_mailbox_sharp = IconData(0xedc3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">markunread</i> &#x2014; material icon named "markunread outlined".
+  static const IconData markunread_outlined = IconData(0xe2bf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">markunread</i> &#x2014; material icon named "markunread rounded".
+  static const IconData markunread_rounded = IconData(0xf2f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">markunread</i> &#x2014; material icon named "markunread sharp".
+  static const IconData markunread_sharp = IconData(0xedc4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">masks</i> &#x2014; material icon named "masks".
+  static const IconData masks = IconData(0xe861, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">masks</i> &#x2014; material icon named "masks outlined".
+  static const IconData masks_outlined = IconData(0xe2c0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">masks</i> &#x2014; material icon named "masks rounded".
+  static const IconData masks_rounded = IconData(0xf2f3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">masks</i> &#x2014; material icon named "masks sharp".
+  static const IconData masks_sharp = IconData(0xedc5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">maximize</i> &#x2014; material icon named "maximize".
+  static const IconData maximize = IconData(0xe862, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">maximize</i> &#x2014; material icon named "maximize outlined".
+  static const IconData maximize_outlined = IconData(0xe2c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">maximize</i> &#x2014; material icon named "maximize rounded".
+  static const IconData maximize_rounded = IconData(0xf2f4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">maximize</i> &#x2014; material icon named "maximize sharp".
+  static const IconData maximize_sharp = IconData(0xedc6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mediation</i> &#x2014; material icon named "mediation".
+  static const IconData mediation = IconData(0xe863, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mediation</i> &#x2014; material icon named "mediation outlined".
+  static const IconData mediation_outlined = IconData(0xe2c2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mediation</i> &#x2014; material icon named "mediation rounded".
+  static const IconData mediation_rounded = IconData(0xf2f5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mediation</i> &#x2014; material icon named "mediation sharp".
+  static const IconData mediation_sharp = IconData(0xedc7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">medical_services</i> &#x2014; material icon named "medical services".
+  static const IconData medical_services = IconData(0xe864, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">medical_services</i> &#x2014; material icon named "medical services outlined".
+  static const IconData medical_services_outlined = IconData(0xe2c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">medical_services</i> &#x2014; material icon named "medical services rounded".
+  static const IconData medical_services_rounded = IconData(0xf2f6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">medical_services</i> &#x2014; material icon named "medical services sharp".
+  static const IconData medical_services_sharp = IconData(0xedc8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">meeting_room</i> &#x2014; material icon named "meeting room".
+  static const IconData meeting_room = IconData(0xe865, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">meeting_room</i> &#x2014; material icon named "meeting room outlined".
+  static const IconData meeting_room_outlined = IconData(0xe2c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">meeting_room</i> &#x2014; material icon named "meeting room rounded".
+  static const IconData meeting_room_rounded = IconData(0xf2f7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">meeting_room</i> &#x2014; material icon named "meeting room sharp".
+  static const IconData meeting_room_sharp = IconData(0xedc9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">memory</i> &#x2014; material icon named "memory".
+  static const IconData memory = IconData(0xe866, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">memory</i> &#x2014; material icon named "memory outlined".
+  static const IconData memory_outlined = IconData(0xe2c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">memory</i> &#x2014; material icon named "memory rounded".
+  static const IconData memory_rounded = IconData(0xf2f8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">memory</i> &#x2014; material icon named "memory sharp".
+  static const IconData memory_sharp = IconData(0xedca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">menu</i> &#x2014; material icon named "menu".
+  static const IconData menu = IconData(0xe867, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">menu_book</i> &#x2014; material icon named "menu book".
+  static const IconData menu_book = IconData(0xe868, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">menu_book</i> &#x2014; material icon named "menu book outlined".
+  static const IconData menu_book_outlined = IconData(0xe2c6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">menu_book</i> &#x2014; material icon named "menu book rounded".
+  static const IconData menu_book_rounded = IconData(0xf2f9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">menu_book</i> &#x2014; material icon named "menu book sharp".
+  static const IconData menu_book_sharp = IconData(0xedcb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">menu_open</i> &#x2014; material icon named "menu open".
+  static const IconData menu_open = IconData(0xe869, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">menu_open</i> &#x2014; material icon named "menu open outlined".
+  static const IconData menu_open_outlined = IconData(0xe2c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">menu_open</i> &#x2014; material icon named "menu open rounded".
+  static const IconData menu_open_rounded = IconData(0xf2fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">menu_open</i> &#x2014; material icon named "menu open sharp".
+  static const IconData menu_open_sharp = IconData(0xedcc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">menu</i> &#x2014; material icon named "menu outlined".
+  static const IconData menu_outlined = IconData(0xe2c8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">menu</i> &#x2014; material icon named "menu rounded".
+  static const IconData menu_rounded = IconData(0xf2fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">menu</i> &#x2014; material icon named "menu sharp".
+  static const IconData menu_sharp = IconData(0xedcd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">merge_type</i> &#x2014; material icon named "merge type".
+  static const IconData merge_type = IconData(0xe86a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">merge_type</i> &#x2014; material icon named "merge type outlined".
+  static const IconData merge_type_outlined = IconData(0xe2c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">merge_type</i> &#x2014; material icon named "merge type rounded".
+  static const IconData merge_type_rounded = IconData(0xf2fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">merge_type</i> &#x2014; material icon named "merge type sharp".
+  static const IconData merge_type_sharp = IconData(0xedce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">message</i> &#x2014; material icon named "message".
+  static const IconData message = IconData(0xe86b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">message</i> &#x2014; material icon named "message outlined".
+  static const IconData message_outlined = IconData(0xe2ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">message</i> &#x2014; material icon named "message rounded".
+  static const IconData message_rounded = IconData(0xf2fd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">message</i> &#x2014; material icon named "message sharp".
+  static const IconData message_sharp = IconData(0xedcf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">messenger</i> &#x2014; material icon named "messenger".
+  static const IconData messenger = IconData(0xe64a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">messenger_outline</i> &#x2014; material icon named "messenger outline".
+  static const IconData messenger_outline = IconData(0xe64b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">messenger_outline</i> &#x2014; material icon named "messenger outline outlined".
+  static const IconData messenger_outline_outlined = IconData(0xe0d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">messenger_outline</i> &#x2014; material icon named "messenger outline rounded".
+  static const IconData messenger_outline_rounded = IconData(0xf107, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">messenger_outline</i> &#x2014; material icon named "messenger outline sharp".
+  static const IconData messenger_outline_sharp = IconData(0xebd9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">messenger</i> &#x2014; material icon named "messenger outlined".
+  static const IconData messenger_outlined = IconData(0xe0da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">messenger</i> &#x2014; material icon named "messenger rounded".
+  static const IconData messenger_rounded = IconData(0xf108, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">messenger</i> &#x2014; material icon named "messenger sharp".
+  static const IconData messenger_sharp = IconData(0xebda, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mic</i> &#x2014; material icon named "mic".
+  static const IconData mic = IconData(0xe86c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mic_external_off</i> &#x2014; material icon named "mic external off".
+  static const IconData mic_external_off = IconData(0xe86d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mic_external_on</i> &#x2014; material icon named "mic external on".
+  static const IconData mic_external_on = IconData(0xe86e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mic_none</i> &#x2014; material icon named "mic none".
+  static const IconData mic_none = IconData(0xe86f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mic_none</i> &#x2014; material icon named "mic none outlined".
+  static const IconData mic_none_outlined = IconData(0xe2cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mic_none</i> &#x2014; material icon named "mic none rounded".
+  static const IconData mic_none_rounded = IconData(0xf2fe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mic_none</i> &#x2014; material icon named "mic none sharp".
+  static const IconData mic_none_sharp = IconData(0xedd0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mic_off</i> &#x2014; material icon named "mic off".
+  static const IconData mic_off = IconData(0xe870, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mic_off</i> &#x2014; material icon named "mic off outlined".
+  static const IconData mic_off_outlined = IconData(0xe2cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mic_off</i> &#x2014; material icon named "mic off rounded".
+  static const IconData mic_off_rounded = IconData(0xf2ff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mic_off</i> &#x2014; material icon named "mic off sharp".
+  static const IconData mic_off_sharp = IconData(0xedd1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mic</i> &#x2014; material icon named "mic outlined".
+  static const IconData mic_outlined = IconData(0xe2cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mic</i> &#x2014; material icon named "mic rounded".
+  static const IconData mic_rounded = IconData(0xf300, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mic</i> &#x2014; material icon named "mic sharp".
+  static const IconData mic_sharp = IconData(0xedd2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">microwave</i> &#x2014; material icon named "microwave".
+  static const IconData microwave = IconData(0xe871, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">microwave</i> &#x2014; material icon named "microwave outlined".
+  static const IconData microwave_outlined = IconData(0xe2ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">microwave</i> &#x2014; material icon named "microwave rounded".
+  static const IconData microwave_rounded = IconData(0xf301, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">microwave</i> &#x2014; material icon named "microwave sharp".
+  static const IconData microwave_sharp = IconData(0xedd3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">military_tech</i> &#x2014; material icon named "military tech".
+  static const IconData military_tech = IconData(0xe872, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">military_tech</i> &#x2014; material icon named "military tech outlined".
+  static const IconData military_tech_outlined = IconData(0xe2cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">military_tech</i> &#x2014; material icon named "military tech rounded".
+  static const IconData military_tech_rounded = IconData(0xf302, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">military_tech</i> &#x2014; material icon named "military tech sharp".
+  static const IconData military_tech_sharp = IconData(0xedd4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">minimize</i> &#x2014; material icon named "minimize".
+  static const IconData minimize = IconData(0xe873, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">minimize</i> &#x2014; material icon named "minimize outlined".
+  static const IconData minimize_outlined = IconData(0xe2d0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">minimize</i> &#x2014; material icon named "minimize rounded".
+  static const IconData minimize_rounded = IconData(0xf303, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">minimize</i> &#x2014; material icon named "minimize sharp".
+  static const IconData minimize_sharp = IconData(0xedd5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">miscellaneous_services</i> &#x2014; material icon named "miscellaneous services".
+  static const IconData miscellaneous_services = IconData(0xe874, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">miscellaneous_services</i> &#x2014; material icon named "miscellaneous services outlined".
+  static const IconData miscellaneous_services_outlined = IconData(0xe2d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">miscellaneous_services</i> &#x2014; material icon named "miscellaneous services rounded".
+  static const IconData miscellaneous_services_rounded = IconData(0xf304, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">miscellaneous_services</i> &#x2014; material icon named "miscellaneous services sharp".
+  static const IconData miscellaneous_services_sharp = IconData(0xedd6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">missed_video_call</i> &#x2014; material icon named "missed video call".
+  static const IconData missed_video_call = IconData(0xe875, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">missed_video_call</i> &#x2014; material icon named "missed video call outlined".
+  static const IconData missed_video_call_outlined = IconData(0xe2d2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">missed_video_call</i> &#x2014; material icon named "missed video call rounded".
+  static const IconData missed_video_call_rounded = IconData(0xf305, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">missed_video_call</i> &#x2014; material icon named "missed video call sharp".
+  static const IconData missed_video_call_sharp = IconData(0xedd7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mms</i> &#x2014; material icon named "mms".
+  static const IconData mms = IconData(0xe876, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mms</i> &#x2014; material icon named "mms outlined".
+  static const IconData mms_outlined = IconData(0xe2d3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mms</i> &#x2014; material icon named "mms rounded".
+  static const IconData mms_rounded = IconData(0xf306, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mms</i> &#x2014; material icon named "mms sharp".
+  static const IconData mms_sharp = IconData(0xedd8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mobile_friendly</i> &#x2014; material icon named "mobile friendly".
+  static const IconData mobile_friendly = IconData(0xe877, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mobile_friendly</i> &#x2014; material icon named "mobile friendly outlined".
+  static const IconData mobile_friendly_outlined = IconData(0xe2d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mobile_friendly</i> &#x2014; material icon named "mobile friendly rounded".
+  static const IconData mobile_friendly_rounded = IconData(0xf307, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mobile_friendly</i> &#x2014; material icon named "mobile friendly sharp".
+  static const IconData mobile_friendly_sharp = IconData(0xedd9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mobile_off</i> &#x2014; material icon named "mobile off".
+  static const IconData mobile_off = IconData(0xe878, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mobile_off</i> &#x2014; material icon named "mobile off outlined".
+  static const IconData mobile_off_outlined = IconData(0xe2d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mobile_off</i> &#x2014; material icon named "mobile off rounded".
+  static const IconData mobile_off_rounded = IconData(0xf308, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mobile_off</i> &#x2014; material icon named "mobile off sharp".
+  static const IconData mobile_off_sharp = IconData(0xedda, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mobile_screen_share</i> &#x2014; material icon named "mobile screen share".
+  static const IconData mobile_screen_share = IconData(0xe879, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">mobile_screen_share</i> &#x2014; material icon named "mobile screen share outlined".
+  static const IconData mobile_screen_share_outlined = IconData(0xe2d6, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">mobile_screen_share</i> &#x2014; material icon named "mobile screen share rounded".
+  static const IconData mobile_screen_share_rounded = IconData(0xf309, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">mobile_screen_share</i> &#x2014; material icon named "mobile screen share sharp".
+  static const IconData mobile_screen_share_sharp = IconData(0xeddb, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">mode_comment</i> &#x2014; material icon named "mode comment".
+  static const IconData mode_comment = IconData(0xe87a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mode_comment</i> &#x2014; material icon named "mode comment outlined".
+  static const IconData mode_comment_outlined = IconData(0xe2d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mode_comment</i> &#x2014; material icon named "mode comment rounded".
+  static const IconData mode_comment_rounded = IconData(0xf30a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mode_comment</i> &#x2014; material icon named "mode comment sharp".
+  static const IconData mode_comment_sharp = IconData(0xeddc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mode_edit</i> &#x2014; material icon named "mode edit".
+  static const IconData mode_edit = IconData(0xe87b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mode</i> &#x2014; material icon named "mode outlined".
+  static const IconData mode_outlined = IconData(0xe2d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mode</i> &#x2014; material icon named "mode rounded".
+  static const IconData mode_rounded = IconData(0xf30b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mode</i> &#x2014; material icon named "mode sharp".
+  static const IconData mode_sharp = IconData(0xeddd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">model_training</i> &#x2014; material icon named "model training".
+  static const IconData model_training = IconData(0xe87c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">model_training</i> &#x2014; material icon named "model training outlined".
+  static const IconData model_training_outlined = IconData(0xe2d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">model_training</i> &#x2014; material icon named "model training rounded".
+  static const IconData model_training_rounded = IconData(0xf30c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">model_training</i> &#x2014; material icon named "model training sharp".
+  static const IconData model_training_sharp = IconData(0xedde, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">monetization_on</i> &#x2014; material icon named "monetization on".
+  static const IconData monetization_on = IconData(0xe87d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">monetization_on</i> &#x2014; material icon named "monetization on outlined".
+  static const IconData monetization_on_outlined = IconData(0xe2da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">monetization_on</i> &#x2014; material icon named "monetization on rounded".
+  static const IconData monetization_on_rounded = IconData(0xf30d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">monetization_on</i> &#x2014; material icon named "monetization on sharp".
+  static const IconData monetization_on_sharp = IconData(0xeddf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">money</i> &#x2014; material icon named "money".
+  static const IconData money = IconData(0xe87e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">money_off</i> &#x2014; material icon named "money off".
+  static const IconData money_off = IconData(0xe87f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">money_off_csred</i> &#x2014; material icon named "money off csred outlined".
+  static const IconData money_off_csred_outlined = IconData(0xe2db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">money_off_csred</i> &#x2014; material icon named "money off csred rounded".
+  static const IconData money_off_csred_rounded = IconData(0xf30e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">money_off_csred</i> &#x2014; material icon named "money off csred sharp".
+  static const IconData money_off_csred_sharp = IconData(0xede0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">money_off</i> &#x2014; material icon named "money off outlined".
+  static const IconData money_off_outlined = IconData(0xe2dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">money_off</i> &#x2014; material icon named "money off rounded".
+  static const IconData money_off_rounded = IconData(0xf30f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">money_off</i> &#x2014; material icon named "money off sharp".
+  static const IconData money_off_sharp = IconData(0xede1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">money</i> &#x2014; material icon named "money outlined".
+  static const IconData money_outlined = IconData(0xe2dd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">money</i> &#x2014; material icon named "money rounded".
+  static const IconData money_rounded = IconData(0xf310, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">money</i> &#x2014; material icon named "money sharp".
+  static const IconData money_sharp = IconData(0xede2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">monitor</i> &#x2014; material icon named "monitor".
+  static const IconData monitor = IconData(0xe880, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">monochrome_photos</i> &#x2014; material icon named "monochrome photos".
+  static const IconData monochrome_photos = IconData(0xe881, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">monochrome_photos</i> &#x2014; material icon named "monochrome photos outlined".
+  static const IconData monochrome_photos_outlined = IconData(0xe2de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">monochrome_photos</i> &#x2014; material icon named "monochrome photos rounded".
+  static const IconData monochrome_photos_rounded = IconData(0xf311, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">monochrome_photos</i> &#x2014; material icon named "monochrome photos sharp".
+  static const IconData monochrome_photos_sharp = IconData(0xede3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mood</i> &#x2014; material icon named "mood".
+  static const IconData mood = IconData(0xe882, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mood_bad</i> &#x2014; material icon named "mood bad".
+  static const IconData mood_bad = IconData(0xe883, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mood_bad</i> &#x2014; material icon named "mood bad outlined".
+  static const IconData mood_bad_outlined = IconData(0xe2df, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mood_bad</i> &#x2014; material icon named "mood bad rounded".
+  static const IconData mood_bad_rounded = IconData(0xf312, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mood_bad</i> &#x2014; material icon named "mood bad sharp".
+  static const IconData mood_bad_sharp = IconData(0xede4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mood</i> &#x2014; material icon named "mood outlined".
+  static const IconData mood_outlined = IconData(0xe2e0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mood</i> &#x2014; material icon named "mood rounded".
+  static const IconData mood_rounded = IconData(0xf313, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mood</i> &#x2014; material icon named "mood sharp".
+  static const IconData mood_sharp = IconData(0xede5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">moped</i> &#x2014; material icon named "moped".
+  static const IconData moped = IconData(0xe884, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">moped</i> &#x2014; material icon named "moped outlined".
+  static const IconData moped_outlined = IconData(0xe2e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">moped</i> &#x2014; material icon named "moped rounded".
+  static const IconData moped_rounded = IconData(0xf314, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">moped</i> &#x2014; material icon named "moped sharp".
+  static const IconData moped_sharp = IconData(0xede6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">more</i> &#x2014; material icon named "more".
+  static const IconData more = IconData(0xe885, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">more_horiz</i> &#x2014; material icon named "more horiz".
+  static const IconData more_horiz = IconData(0xe886, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">more_horiz</i> &#x2014; material icon named "more horiz outlined".
+  static const IconData more_horiz_outlined = IconData(0xe2e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">more_horiz</i> &#x2014; material icon named "more horiz rounded".
+  static const IconData more_horiz_rounded = IconData(0xf315, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">more_horiz</i> &#x2014; material icon named "more horiz sharp".
+  static const IconData more_horiz_sharp = IconData(0xede7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">more</i> &#x2014; material icon named "more outlined".
+  static const IconData more_outlined = IconData(0xe2e3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">more</i> &#x2014; material icon named "more rounded".
+  static const IconData more_rounded = IconData(0xf316, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">more</i> &#x2014; material icon named "more sharp".
+  static const IconData more_sharp = IconData(0xede8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">more_time</i> &#x2014; material icon named "more time".
+  static const IconData more_time = IconData(0xe887, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">more_time</i> &#x2014; material icon named "more time outlined".
+  static const IconData more_time_outlined = IconData(0xe2e4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">more_time</i> &#x2014; material icon named "more time rounded".
+  static const IconData more_time_rounded = IconData(0xf317, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">more_time</i> &#x2014; material icon named "more time sharp".
+  static const IconData more_time_sharp = IconData(0xede9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">more_vert</i> &#x2014; material icon named "more vert".
+  static const IconData more_vert = IconData(0xe888, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">more_vert</i> &#x2014; material icon named "more vert outlined".
+  static const IconData more_vert_outlined = IconData(0xe2e5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">more_vert</i> &#x2014; material icon named "more vert rounded".
+  static const IconData more_vert_rounded = IconData(0xf318, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">more_vert</i> &#x2014; material icon named "more vert sharp".
+  static const IconData more_vert_sharp = IconData(0xedea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">motion_photos_off</i> &#x2014; material icon named "motion photos off".
+  static const IconData motion_photos_off = IconData(0xe889, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">motion_photos_on</i> &#x2014; material icon named "motion photos on".
+  static const IconData motion_photos_on = IconData(0xe88a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">motion_photos_on</i> &#x2014; material icon named "motion photos on outlined".
+  static const IconData motion_photos_on_outlined = IconData(0xe2e6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">motion_photos_on</i> &#x2014; material icon named "motion photos on rounded".
+  static const IconData motion_photos_on_rounded = IconData(0xf319, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">motion_photos_on</i> &#x2014; material icon named "motion photos on sharp".
+  static const IconData motion_photos_on_sharp = IconData(0xedeb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">motion_photos_pause</i> &#x2014; material icon named "motion photos pause".
+  static const IconData motion_photos_pause = IconData(0xe88b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">motion_photos_pause</i> &#x2014; material icon named "motion photos pause outlined".
+  static const IconData motion_photos_pause_outlined = IconData(0xe2e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">motion_photos_pause</i> &#x2014; material icon named "motion photos pause rounded".
+  static const IconData motion_photos_pause_rounded = IconData(0xf31a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">motion_photos_pause</i> &#x2014; material icon named "motion photos pause sharp".
+  static const IconData motion_photos_pause_sharp = IconData(0xedec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">motion_photos_paused</i> &#x2014; material icon named "motion photos paused".
+  static const IconData motion_photos_paused = IconData(0xe88c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">motion_photos_paused</i> &#x2014; material icon named "motion photos paused outlined".
+  static const IconData motion_photos_paused_outlined = IconData(0xe2e8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">motion_photos_paused</i> &#x2014; material icon named "motion photos paused rounded".
+  static const IconData motion_photos_paused_rounded = IconData(0xf31b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">motion_photos_paused</i> &#x2014; material icon named "motion photos paused sharp".
+  static const IconData motion_photos_paused_sharp = IconData(0xeded, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">motorcycle</i> &#x2014; material icon named "motorcycle".
+  static const IconData motorcycle = IconData(0xe88d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">motorcycle</i> &#x2014; material icon named "motorcycle outlined".
+  static const IconData motorcycle_outlined = IconData(0xe2e9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">motorcycle</i> &#x2014; material icon named "motorcycle rounded".
+  static const IconData motorcycle_rounded = IconData(0xf31c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">motorcycle</i> &#x2014; material icon named "motorcycle sharp".
+  static const IconData motorcycle_sharp = IconData(0xedee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mouse</i> &#x2014; material icon named "mouse".
+  static const IconData mouse = IconData(0xe88e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">mouse</i> &#x2014; material icon named "mouse outlined".
+  static const IconData mouse_outlined = IconData(0xe2ea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">mouse</i> &#x2014; material icon named "mouse rounded".
+  static const IconData mouse_rounded = IconData(0xf31d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">mouse</i> &#x2014; material icon named "mouse sharp".
+  static const IconData mouse_sharp = IconData(0xedef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">move_to_inbox</i> &#x2014; material icon named "move to inbox".
+  static const IconData move_to_inbox = IconData(0xe88f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">move_to_inbox</i> &#x2014; material icon named "move to inbox outlined".
+  static const IconData move_to_inbox_outlined = IconData(0xe2eb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">move_to_inbox</i> &#x2014; material icon named "move to inbox rounded".
+  static const IconData move_to_inbox_rounded = IconData(0xf31e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">move_to_inbox</i> &#x2014; material icon named "move to inbox sharp".
+  static const IconData move_to_inbox_sharp = IconData(0xedf0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">movie</i> &#x2014; material icon named "movie".
+  static const IconData movie = IconData(0xe890, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">movie_creation</i> &#x2014; material icon named "movie creation".
+  static const IconData movie_creation = IconData(0xe891, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">movie_creation</i> &#x2014; material icon named "movie creation outlined".
+  static const IconData movie_creation_outlined = IconData(0xe2ec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">movie_creation</i> &#x2014; material icon named "movie creation rounded".
+  static const IconData movie_creation_rounded = IconData(0xf31f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">movie_creation</i> &#x2014; material icon named "movie creation sharp".
+  static const IconData movie_creation_sharp = IconData(0xedf1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">movie_filter</i> &#x2014; material icon named "movie filter".
+  static const IconData movie_filter = IconData(0xe892, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">movie_filter</i> &#x2014; material icon named "movie filter outlined".
+  static const IconData movie_filter_outlined = IconData(0xe2ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">movie_filter</i> &#x2014; material icon named "movie filter rounded".
+  static const IconData movie_filter_rounded = IconData(0xf320, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">movie_filter</i> &#x2014; material icon named "movie filter sharp".
+  static const IconData movie_filter_sharp = IconData(0xedf2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">movie</i> &#x2014; material icon named "movie outlined".
+  static const IconData movie_outlined = IconData(0xe2ee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">movie</i> &#x2014; material icon named "movie rounded".
+  static const IconData movie_rounded = IconData(0xf321, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">movie</i> &#x2014; material icon named "movie sharp".
+  static const IconData movie_sharp = IconData(0xedf3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">mp</i> &#x2014; material icon named "mp".
+  static const IconData mp = IconData(0xe893, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">multiline_chart</i> &#x2014; material icon named "multiline chart".
+  static const IconData multiline_chart = IconData(0xe894, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">multiline_chart</i> &#x2014; material icon named "multiline chart outlined".
+  static const IconData multiline_chart_outlined = IconData(0xe2ef, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">multiline_chart</i> &#x2014; material icon named "multiline chart rounded".
+  static const IconData multiline_chart_rounded = IconData(0xf322, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">multiline_chart</i> &#x2014; material icon named "multiline chart sharp".
+  static const IconData multiline_chart_sharp = IconData(0xedf4, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">multiple_stop</i> &#x2014; material icon named "multiple stop".
+  static const IconData multiple_stop = IconData(0xe895, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">multiple_stop</i> &#x2014; material icon named "multiple stop outlined".
+  static const IconData multiple_stop_outlined = IconData(0xe2f0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">multiple_stop</i> &#x2014; material icon named "multiple stop rounded".
+  static const IconData multiple_stop_rounded = IconData(0xf323, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">multiple_stop</i> &#x2014; material icon named "multiple stop sharp".
+  static const IconData multiple_stop_sharp = IconData(0xedf5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">multitrack_audio</i> &#x2014; material icon named "multitrack audio".
+  static const IconData multitrack_audio = IconData(0xe798, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">multitrack_audio</i> &#x2014; material icon named "multitrack audio outlined".
+  static const IconData multitrack_audio_outlined = IconData(0xe20d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">multitrack_audio</i> &#x2014; material icon named "multitrack audio rounded".
+  static const IconData multitrack_audio_rounded = IconData(0xf23c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">multitrack_audio</i> &#x2014; material icon named "multitrack audio sharp".
+  static const IconData multitrack_audio_sharp = IconData(0xed0e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">museum</i> &#x2014; material icon named "museum".
+  static const IconData museum = IconData(0xe896, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">museum</i> &#x2014; material icon named "museum outlined".
+  static const IconData museum_outlined = IconData(0xe2f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">museum</i> &#x2014; material icon named "museum rounded".
+  static const IconData museum_rounded = IconData(0xf324, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">museum</i> &#x2014; material icon named "museum sharp".
+  static const IconData museum_sharp = IconData(0xedf6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">music_note</i> &#x2014; material icon named "music note".
+  static const IconData music_note = IconData(0xe897, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">music_note</i> &#x2014; material icon named "music note outlined".
+  static const IconData music_note_outlined = IconData(0xe2f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">music_note</i> &#x2014; material icon named "music note rounded".
+  static const IconData music_note_rounded = IconData(0xf325, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">music_note</i> &#x2014; material icon named "music note sharp".
+  static const IconData music_note_sharp = IconData(0xedf7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">music_off</i> &#x2014; material icon named "music off".
+  static const IconData music_off = IconData(0xe898, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">music_off</i> &#x2014; material icon named "music off outlined".
+  static const IconData music_off_outlined = IconData(0xe2f3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">music_off</i> &#x2014; material icon named "music off rounded".
+  static const IconData music_off_rounded = IconData(0xf326, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">music_off</i> &#x2014; material icon named "music off sharp".
+  static const IconData music_off_sharp = IconData(0xedf8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">music_video</i> &#x2014; material icon named "music video".
+  static const IconData music_video = IconData(0xe899, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">music_video</i> &#x2014; material icon named "music video outlined".
+  static const IconData music_video_outlined = IconData(0xe2f4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">music_video</i> &#x2014; material icon named "music video rounded".
+  static const IconData music_video_rounded = IconData(0xf327, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">music_video</i> &#x2014; material icon named "music video sharp".
+  static const IconData music_video_sharp = IconData(0xedf9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">my_library_add</i> &#x2014; material icon named "my library add".
+  static const IconData my_library_add = IconData(0xe80d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">my_library_add</i> &#x2014; material icon named "my library add outlined".
+  static const IconData my_library_add_outlined = IconData(0xe276, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">my_library_add</i> &#x2014; material icon named "my library add rounded".
+  static const IconData my_library_add_rounded = IconData(0xf2a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">my_library_add</i> &#x2014; material icon named "my library add sharp".
+  static const IconData my_library_add_sharp = IconData(0xed7a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">my_library_books</i> &#x2014; material icon named "my library books".
+  static const IconData my_library_books = IconData(0xe80f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">my_library_books</i> &#x2014; material icon named "my library books outlined".
+  static const IconData my_library_books_outlined = IconData(0xe277, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">my_library_books</i> &#x2014; material icon named "my library books rounded".
+  static const IconData my_library_books_rounded = IconData(0xf2a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">my_library_books</i> &#x2014; material icon named "my library books sharp".
+  static const IconData my_library_books_sharp = IconData(0xed7b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">my_library_music</i> &#x2014; material icon named "my library music".
+  static const IconData my_library_music = IconData(0xe810, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">my_library_music</i> &#x2014; material icon named "my library music outlined".
+  static const IconData my_library_music_outlined = IconData(0xe278, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">my_library_music</i> &#x2014; material icon named "my library music rounded".
+  static const IconData my_library_music_rounded = IconData(0xf2aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">my_library_music</i> &#x2014; material icon named "my library music sharp".
+  static const IconData my_library_music_sharp = IconData(0xed7c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">my_location</i> &#x2014; material icon named "my location".
+  static const IconData my_location = IconData(0xe89a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">my_location</i> &#x2014; material icon named "my location outlined".
+  static const IconData my_location_outlined = IconData(0xe2f5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">my_location</i> &#x2014; material icon named "my location rounded".
+  static const IconData my_location_rounded = IconData(0xf328, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">my_location</i> &#x2014; material icon named "my location sharp".
+  static const IconData my_location_sharp = IconData(0xedfa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">nat</i> &#x2014; material icon named "nat".
+  static const IconData nat = IconData(0xe89b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">nat</i> &#x2014; material icon named "nat outlined".
+  static const IconData nat_outlined = IconData(0xe2f6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">nat</i> &#x2014; material icon named "nat rounded".
+  static const IconData nat_rounded = IconData(0xf329, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">nat</i> &#x2014; material icon named "nat sharp".
+  static const IconData nat_sharp = IconData(0xedfb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">nature</i> &#x2014; material icon named "nature".
+  static const IconData nature = IconData(0xe89c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">nature</i> &#x2014; material icon named "nature outlined".
+  static const IconData nature_outlined = IconData(0xe2f7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">nature_people</i> &#x2014; material icon named "nature people".
+  static const IconData nature_people = IconData(0xe89d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">nature_people</i> &#x2014; material icon named "nature people outlined".
+  static const IconData nature_people_outlined = IconData(0xe2f8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">nature_people</i> &#x2014; material icon named "nature people rounded".
+  static const IconData nature_people_rounded = IconData(0xf32a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">nature_people</i> &#x2014; material icon named "nature people sharp".
+  static const IconData nature_people_sharp = IconData(0xedfc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">nature</i> &#x2014; material icon named "nature rounded".
+  static const IconData nature_rounded = IconData(0xf32b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">nature</i> &#x2014; material icon named "nature sharp".
+  static const IconData nature_sharp = IconData(0xedfd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">navigate_before</i> &#x2014; material icon named "navigate before".
+  static const IconData navigate_before = IconData(0xe89e, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">navigate_before</i> &#x2014; material icon named "navigate before outlined".
+  static const IconData navigate_before_outlined = IconData(0xe2f9, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">navigate_before</i> &#x2014; material icon named "navigate before rounded".
+  static const IconData navigate_before_rounded = IconData(0xf32c, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">navigate_before</i> &#x2014; material icon named "navigate before sharp".
+  static const IconData navigate_before_sharp = IconData(0xedfe, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">navigate_next</i> &#x2014; material icon named "navigate next".
+  static const IconData navigate_next = IconData(0xe89f, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">navigate_next</i> &#x2014; material icon named "navigate next outlined".
+  static const IconData navigate_next_outlined = IconData(0xe2fa, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">navigate_next</i> &#x2014; material icon named "navigate next rounded".
+  static const IconData navigate_next_rounded = IconData(0xf32d, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">navigate_next</i> &#x2014; material icon named "navigate next sharp".
+  static const IconData navigate_next_sharp = IconData(0xedff, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">navigation</i> &#x2014; material icon named "navigation".
+  static const IconData navigation = IconData(0xe8a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">navigation</i> &#x2014; material icon named "navigation outlined".
+  static const IconData navigation_outlined = IconData(0xe2fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">navigation</i> &#x2014; material icon named "navigation rounded".
+  static const IconData navigation_rounded = IconData(0xf32e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">navigation</i> &#x2014; material icon named "navigation sharp".
+  static const IconData navigation_sharp = IconData(0xee00, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">near_me</i> &#x2014; material icon named "near me".
+  static const IconData near_me = IconData(0xe8a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">near_me_disabled</i> &#x2014; material icon named "near me disabled".
+  static const IconData near_me_disabled = IconData(0xe8a2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">near_me_disabled</i> &#x2014; material icon named "near me disabled outlined".
+  static const IconData near_me_disabled_outlined = IconData(0xe2fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">near_me_disabled</i> &#x2014; material icon named "near me disabled rounded".
+  static const IconData near_me_disabled_rounded = IconData(0xf32f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">near_me_disabled</i> &#x2014; material icon named "near me disabled sharp".
+  static const IconData near_me_disabled_sharp = IconData(0xee01, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">near_me</i> &#x2014; material icon named "near me outlined".
+  static const IconData near_me_outlined = IconData(0xe2fd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">near_me</i> &#x2014; material icon named "near me rounded".
+  static const IconData near_me_rounded = IconData(0xf330, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">near_me</i> &#x2014; material icon named "near me sharp".
+  static const IconData near_me_sharp = IconData(0xee02, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">network_cell</i> &#x2014; material icon named "network cell".
+  static const IconData network_cell = IconData(0xe8a3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">network_check</i> &#x2014; material icon named "network check".
+  static const IconData network_check = IconData(0xe8a4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">network_check</i> &#x2014; material icon named "network check outlined".
+  static const IconData network_check_outlined = IconData(0xe2fe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">network_check</i> &#x2014; material icon named "network check rounded".
+  static const IconData network_check_rounded = IconData(0xf331, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">network_check</i> &#x2014; material icon named "network check sharp".
+  static const IconData network_check_sharp = IconData(0xee03, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">network_locked</i> &#x2014; material icon named "network locked".
+  static const IconData network_locked = IconData(0xe8a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">network_locked</i> &#x2014; material icon named "network locked outlined".
+  static const IconData network_locked_outlined = IconData(0xe2ff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">network_locked</i> &#x2014; material icon named "network locked rounded".
+  static const IconData network_locked_rounded = IconData(0xf332, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">network_locked</i> &#x2014; material icon named "network locked sharp".
+  static const IconData network_locked_sharp = IconData(0xee04, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">network_wifi</i> &#x2014; material icon named "network wifi".
+  static const IconData network_wifi = IconData(0xe8a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">new_releases</i> &#x2014; material icon named "new releases".
+  static const IconData new_releases = IconData(0xe8a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">new_releases</i> &#x2014; material icon named "new releases outlined".
+  static const IconData new_releases_outlined = IconData(0xe300, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">new_releases</i> &#x2014; material icon named "new releases rounded".
+  static const IconData new_releases_rounded = IconData(0xf333, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">new_releases</i> &#x2014; material icon named "new releases sharp".
+  static const IconData new_releases_sharp = IconData(0xee05, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">next_plan</i> &#x2014; material icon named "next plan".
+  static const IconData next_plan = IconData(0xe8a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">next_plan</i> &#x2014; material icon named "next plan outlined".
+  static const IconData next_plan_outlined = IconData(0xe301, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">next_plan</i> &#x2014; material icon named "next plan rounded".
+  static const IconData next_plan_rounded = IconData(0xf334, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">next_plan</i> &#x2014; material icon named "next plan sharp".
+  static const IconData next_plan_sharp = IconData(0xee06, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">next_week</i> &#x2014; material icon named "next week".
+  static const IconData next_week = IconData(0xe8a9, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">next_week</i> &#x2014; material icon named "next week outlined".
+  static const IconData next_week_outlined = IconData(0xe302, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">next_week</i> &#x2014; material icon named "next week rounded".
+  static const IconData next_week_rounded = IconData(0xf335, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">next_week</i> &#x2014; material icon named "next week sharp".
+  static const IconData next_week_sharp = IconData(0xee07, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">nfc</i> &#x2014; material icon named "nfc".
+  static const IconData nfc = IconData(0xe8aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">nfc</i> &#x2014; material icon named "nfc outlined".
+  static const IconData nfc_outlined = IconData(0xe303, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">nfc</i> &#x2014; material icon named "nfc rounded".
+  static const IconData nfc_rounded = IconData(0xf336, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">nfc</i> &#x2014; material icon named "nfc sharp".
+  static const IconData nfc_sharp = IconData(0xee08, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">night_shelter</i> &#x2014; material icon named "night shelter".
+  static const IconData night_shelter = IconData(0xe8ab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">night_shelter</i> &#x2014; material icon named "night shelter outlined".
+  static const IconData night_shelter_outlined = IconData(0xe304, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">night_shelter</i> &#x2014; material icon named "night shelter rounded".
+  static const IconData night_shelter_rounded = IconData(0xf337, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">night_shelter</i> &#x2014; material icon named "night shelter sharp".
+  static const IconData night_shelter_sharp = IconData(0xee09, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">nightlife</i> &#x2014; material icon named "nightlife".
+  static const IconData nightlife = IconData(0xe8ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">nightlight_round</i> &#x2014; material icon named "nightlight round".
+  static const IconData nightlight_round = IconData(0xe8ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">nights_stay</i> &#x2014; material icon named "nights stay".
+  static const IconData nights_stay = IconData(0xe8ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">nights_stay</i> &#x2014; material icon named "nights stay outlined".
+  static const IconData nights_stay_outlined = IconData(0xe305, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">nights_stay</i> &#x2014; material icon named "nights stay rounded".
+  static const IconData nights_stay_rounded = IconData(0xf338, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">nights_stay</i> &#x2014; material icon named "nights stay sharp".
+  static const IconData nights_stay_sharp = IconData(0xee0a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">no_cell</i> &#x2014; material icon named "no cell".
+  static const IconData no_cell = IconData(0xe8af, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">no_cell</i> &#x2014; material icon named "no cell outlined".
+  static const IconData no_cell_outlined = IconData(0xe306, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">no_cell</i> &#x2014; material icon named "no cell rounded".
+  static const IconData no_cell_rounded = IconData(0xf339, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">no_cell</i> &#x2014; material icon named "no cell sharp".
+  static const IconData no_cell_sharp = IconData(0xee0b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">no_drinks</i> &#x2014; material icon named "no drinks".
+  static const IconData no_drinks = IconData(0xe8b0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">no_drinks</i> &#x2014; material icon named "no drinks outlined".
+  static const IconData no_drinks_outlined = IconData(0xe307, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">no_drinks</i> &#x2014; material icon named "no drinks rounded".
+  static const IconData no_drinks_rounded = IconData(0xf33a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">no_drinks</i> &#x2014; material icon named "no drinks sharp".
+  static const IconData no_drinks_sharp = IconData(0xee0c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">no_encryption</i> &#x2014; material icon named "no encryption".
+  static const IconData no_encryption = IconData(0xe8b1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">no_encryption_gmailerrorred</i> &#x2014; material icon named "no encryption gmailerrorred outlined".
+  static const IconData no_encryption_gmailerrorred_outlined = IconData(0xe308, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">no_encryption_gmailerrorred</i> &#x2014; material icon named "no encryption gmailerrorred rounded".
+  static const IconData no_encryption_gmailerrorred_rounded = IconData(0xf33b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">no_encryption_gmailerrorred</i> &#x2014; material icon named "no encryption gmailerrorred sharp".
+  static const IconData no_encryption_gmailerrorred_sharp = IconData(0xee0d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">no_encryption</i> &#x2014; material icon named "no encryption outlined".
+  static const IconData no_encryption_outlined = IconData(0xe309, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">no_encryption</i> &#x2014; material icon named "no encryption rounded".
+  static const IconData no_encryption_rounded = IconData(0xf33c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">no_encryption</i> &#x2014; material icon named "no encryption sharp".
+  static const IconData no_encryption_sharp = IconData(0xee0e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">no_flash</i> &#x2014; material icon named "no flash".
+  static const IconData no_flash = IconData(0xe8b2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">no_flash</i> &#x2014; material icon named "no flash outlined".
+  static const IconData no_flash_outlined = IconData(0xe30a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">no_flash</i> &#x2014; material icon named "no flash rounded".
+  static const IconData no_flash_rounded = IconData(0xf33d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">no_flash</i> &#x2014; material icon named "no flash sharp".
+  static const IconData no_flash_sharp = IconData(0xee0f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">no_food</i> &#x2014; material icon named "no food".
+  static const IconData no_food = IconData(0xe8b3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">no_food</i> &#x2014; material icon named "no food outlined".
+  static const IconData no_food_outlined = IconData(0xe30b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">no_food</i> &#x2014; material icon named "no food rounded".
+  static const IconData no_food_rounded = IconData(0xf33e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">no_food</i> &#x2014; material icon named "no food sharp".
+  static const IconData no_food_sharp = IconData(0xee10, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">no_meals</i> &#x2014; material icon named "no meals".
+  static const IconData no_meals = IconData(0xe8b4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">no_meals_ouline</i> &#x2014; material icon named "no meals ouline".
+  static const IconData no_meals_ouline = IconData(0xe8b5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">no_meals</i> &#x2014; material icon named "no meals outlined".
+  static const IconData no_meals_outlined = IconData(0xe30c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">no_meals</i> &#x2014; material icon named "no meals rounded".
+  static const IconData no_meals_rounded = IconData(0xf33f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">no_meals</i> &#x2014; material icon named "no meals sharp".
+  static const IconData no_meals_sharp = IconData(0xee11, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">no_meeting_room</i> &#x2014; material icon named "no meeting room".
+  static const IconData no_meeting_room = IconData(0xe8b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">no_meeting_room</i> &#x2014; material icon named "no meeting room outlined".
+  static const IconData no_meeting_room_outlined = IconData(0xe30d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">no_meeting_room</i> &#x2014; material icon named "no meeting room rounded".
+  static const IconData no_meeting_room_rounded = IconData(0xf340, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">no_meeting_room</i> &#x2014; material icon named "no meeting room sharp".
+  static const IconData no_meeting_room_sharp = IconData(0xee12, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">no_photography</i> &#x2014; material icon named "no photography".
+  static const IconData no_photography = IconData(0xe8b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">no_photography</i> &#x2014; material icon named "no photography outlined".
+  static const IconData no_photography_outlined = IconData(0xe30e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">no_photography</i> &#x2014; material icon named "no photography rounded".
+  static const IconData no_photography_rounded = IconData(0xf341, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">no_photography</i> &#x2014; material icon named "no photography sharp".
+  static const IconData no_photography_sharp = IconData(0xee13, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">no_sim</i> &#x2014; material icon named "no sim".
+  static const IconData no_sim = IconData(0xe8b8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">no_sim</i> &#x2014; material icon named "no sim outlined".
+  static const IconData no_sim_outlined = IconData(0xe30f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">no_sim</i> &#x2014; material icon named "no sim rounded".
+  static const IconData no_sim_rounded = IconData(0xf342, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">no_sim</i> &#x2014; material icon named "no sim sharp".
+  static const IconData no_sim_sharp = IconData(0xee14, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">no_stroller</i> &#x2014; material icon named "no stroller".
+  static const IconData no_stroller = IconData(0xe8b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">no_stroller</i> &#x2014; material icon named "no stroller outlined".
+  static const IconData no_stroller_outlined = IconData(0xe310, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">no_stroller</i> &#x2014; material icon named "no stroller rounded".
+  static const IconData no_stroller_rounded = IconData(0xf343, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">no_stroller</i> &#x2014; material icon named "no stroller sharp".
+  static const IconData no_stroller_sharp = IconData(0xee15, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">no_transfer</i> &#x2014; material icon named "no transfer".
+  static const IconData no_transfer = IconData(0xe8ba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">no_transfer</i> &#x2014; material icon named "no transfer outlined".
+  static const IconData no_transfer_outlined = IconData(0xe311, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">no_transfer</i> &#x2014; material icon named "no transfer rounded".
+  static const IconData no_transfer_rounded = IconData(0xf344, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">no_transfer</i> &#x2014; material icon named "no transfer sharp".
+  static const IconData no_transfer_sharp = IconData(0xee16, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">north</i> &#x2014; material icon named "north".
+  static const IconData north = IconData(0xe8bb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">north_east</i> &#x2014; material icon named "north east".
+  static const IconData north_east = IconData(0xe8bc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">north_east</i> &#x2014; material icon named "north east outlined".
+  static const IconData north_east_outlined = IconData(0xe312, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">north_east</i> &#x2014; material icon named "north east rounded".
+  static const IconData north_east_rounded = IconData(0xf345, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">north_east</i> &#x2014; material icon named "north east sharp".
+  static const IconData north_east_sharp = IconData(0xee17, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">north</i> &#x2014; material icon named "north outlined".
+  static const IconData north_outlined = IconData(0xe313, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">north</i> &#x2014; material icon named "north rounded".
+  static const IconData north_rounded = IconData(0xf346, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">north</i> &#x2014; material icon named "north sharp".
+  static const IconData north_sharp = IconData(0xee18, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">north_west</i> &#x2014; material icon named "north west".
+  static const IconData north_west = IconData(0xe8bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">north_west</i> &#x2014; material icon named "north west outlined".
+  static const IconData north_west_outlined = IconData(0xe314, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">north_west</i> &#x2014; material icon named "north west rounded".
+  static const IconData north_west_rounded = IconData(0xf347, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">north_west</i> &#x2014; material icon named "north west sharp".
+  static const IconData north_west_sharp = IconData(0xee19, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">not_accessible</i> &#x2014; material icon named "not accessible".
+  static const IconData not_accessible = IconData(0xe8be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">not_accessible</i> &#x2014; material icon named "not accessible outlined".
+  static const IconData not_accessible_outlined = IconData(0xe315, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">not_accessible</i> &#x2014; material icon named "not accessible rounded".
+  static const IconData not_accessible_rounded = IconData(0xf348, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">not_accessible</i> &#x2014; material icon named "not accessible sharp".
+  static const IconData not_accessible_sharp = IconData(0xee1a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">not_interested</i> &#x2014; material icon named "not interested".
+  static const IconData not_interested = IconData(0xe8bf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">not_interested</i> &#x2014; material icon named "not interested outlined".
+  static const IconData not_interested_outlined = IconData(0xe316, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">not_interested</i> &#x2014; material icon named "not interested rounded".
+  static const IconData not_interested_rounded = IconData(0xf349, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">not_interested</i> &#x2014; material icon named "not interested sharp".
+  static const IconData not_interested_sharp = IconData(0xee1b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">not_listed_location</i> &#x2014; material icon named "not listed location".
+  static const IconData not_listed_location = IconData(0xe8c0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">not_listed_location</i> &#x2014; material icon named "not listed location outlined".
+  static const IconData not_listed_location_outlined = IconData(0xe317, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">not_listed_location</i> &#x2014; material icon named "not listed location rounded".
+  static const IconData not_listed_location_rounded = IconData(0xf34a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">not_listed_location</i> &#x2014; material icon named "not listed location sharp".
+  static const IconData not_listed_location_sharp = IconData(0xee1c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">not_started</i> &#x2014; material icon named "not started".
+  static const IconData not_started = IconData(0xe8c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">not_started</i> &#x2014; material icon named "not started outlined".
+  static const IconData not_started_outlined = IconData(0xe318, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">not_started</i> &#x2014; material icon named "not started rounded".
+  static const IconData not_started_rounded = IconData(0xf34b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">not_started</i> &#x2014; material icon named "not started sharp".
+  static const IconData not_started_sharp = IconData(0xee1d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">note</i> &#x2014; material icon named "note".
+  static const IconData note = IconData(0xe8c2, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">note_add</i> &#x2014; material icon named "note add".
+  static const IconData note_add = IconData(0xe8c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">note_add</i> &#x2014; material icon named "note add outlined".
+  static const IconData note_add_outlined = IconData(0xe319, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">note_add</i> &#x2014; material icon named "note add rounded".
+  static const IconData note_add_rounded = IconData(0xf34c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">note_add</i> &#x2014; material icon named "note add sharp".
+  static const IconData note_add_sharp = IconData(0xee1e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">note</i> &#x2014; material icon named "note outlined".
+  static const IconData note_outlined = IconData(0xe31a, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">note</i> &#x2014; material icon named "note rounded".
+  static const IconData note_rounded = IconData(0xf34d, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">note</i> &#x2014; material icon named "note sharp".
+  static const IconData note_sharp = IconData(0xee1f, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">notes</i> &#x2014; material icon named "notes".
+  static const IconData notes = IconData(0xe8c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">notes</i> &#x2014; material icon named "notes outlined".
+  static const IconData notes_outlined = IconData(0xe31b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">notes</i> &#x2014; material icon named "notes rounded".
+  static const IconData notes_rounded = IconData(0xf34e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">notes</i> &#x2014; material icon named "notes sharp".
+  static const IconData notes_sharp = IconData(0xee20, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">notification_important</i> &#x2014; material icon named "notification important".
+  static const IconData notification_important = IconData(0xe8c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">notification_important</i> &#x2014; material icon named "notification important outlined".
+  static const IconData notification_important_outlined = IconData(0xe31c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">notification_important</i> &#x2014; material icon named "notification important rounded".
+  static const IconData notification_important_rounded = IconData(0xf34f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">notification_important</i> &#x2014; material icon named "notification important sharp".
+  static const IconData notification_important_sharp = IconData(0xee21, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">notifications</i> &#x2014; material icon named "notifications".
+  static const IconData notifications = IconData(0xe8c6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">notifications_active</i> &#x2014; material icon named "notifications active".
+  static const IconData notifications_active = IconData(0xe8c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">notifications_active</i> &#x2014; material icon named "notifications active outlined".
+  static const IconData notifications_active_outlined = IconData(0xe31d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">notifications_active</i> &#x2014; material icon named "notifications active rounded".
+  static const IconData notifications_active_rounded = IconData(0xf350, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">notifications_active</i> &#x2014; material icon named "notifications active sharp".
+  static const IconData notifications_active_sharp = IconData(0xee22, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">notifications_none</i> &#x2014; material icon named "notifications none".
+  static const IconData notifications_none = IconData(0xe8c8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">notifications_none</i> &#x2014; material icon named "notifications none outlined".
+  static const IconData notifications_none_outlined = IconData(0xe31e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">notifications_none</i> &#x2014; material icon named "notifications none rounded".
+  static const IconData notifications_none_rounded = IconData(0xf351, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">notifications_none</i> &#x2014; material icon named "notifications none sharp".
+  static const IconData notifications_none_sharp = IconData(0xee23, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">notifications_off</i> &#x2014; material icon named "notifications off".
+  static const IconData notifications_off = IconData(0xe8c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">notifications_off</i> &#x2014; material icon named "notifications off outlined".
+  static const IconData notifications_off_outlined = IconData(0xe31f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">notifications_off</i> &#x2014; material icon named "notifications off rounded".
+  static const IconData notifications_off_rounded = IconData(0xf352, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">notifications_off</i> &#x2014; material icon named "notifications off sharp".
+  static const IconData notifications_off_sharp = IconData(0xee24, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">notifications_on</i> &#x2014; material icon named "notifications on".
+  static const IconData notifications_on = IconData(0xe8c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">notifications_on</i> &#x2014; material icon named "notifications on outlined".
+  static const IconData notifications_on_outlined = IconData(0xe31d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">notifications_on</i> &#x2014; material icon named "notifications on rounded".
+  static const IconData notifications_on_rounded = IconData(0xf350, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">notifications_on</i> &#x2014; material icon named "notifications on sharp".
+  static const IconData notifications_on_sharp = IconData(0xee22, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">notifications</i> &#x2014; material icon named "notifications outlined".
+  static const IconData notifications_outlined = IconData(0xe320, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">notifications_paused</i> &#x2014; material icon named "notifications paused".
+  static const IconData notifications_paused = IconData(0xe8ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">notifications_paused</i> &#x2014; material icon named "notifications paused outlined".
+  static const IconData notifications_paused_outlined = IconData(0xe321, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">notifications_paused</i> &#x2014; material icon named "notifications paused rounded".
+  static const IconData notifications_paused_rounded = IconData(0xf353, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">notifications_paused</i> &#x2014; material icon named "notifications paused sharp".
+  static const IconData notifications_paused_sharp = IconData(0xee25, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">notifications</i> &#x2014; material icon named "notifications rounded".
+  static const IconData notifications_rounded = IconData(0xf354, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">notifications</i> &#x2014; material icon named "notifications sharp".
+  static const IconData notifications_sharp = IconData(0xee26, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">now_wallpaper</i> &#x2014; material icon named "now wallpaper".
+  static const IconData now_wallpaper = IconData(0xead5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">now_wallpaper</i> &#x2014; material icon named "now wallpaper outlined".
+  static const IconData now_wallpaper_outlined = IconData(0xe503, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">now_wallpaper</i> &#x2014; material icon named "now wallpaper rounded".
+  static const IconData now_wallpaper_rounded = IconData(0xf534, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">now_wallpaper</i> &#x2014; material icon named "now wallpaper sharp".
+  static const IconData now_wallpaper_sharp = IconData(0xf007, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">now_widgets</i> &#x2014; material icon named "now widgets".
+  static const IconData now_widgets = IconData(0xeaec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">now_widgets</i> &#x2014; material icon named "now widgets outlined".
+  static const IconData now_widgets_outlined = IconData(0xe518, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">now_widgets</i> &#x2014; material icon named "now widgets rounded".
+  static const IconData now_widgets_rounded = IconData(0xf549, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">now_widgets</i> &#x2014; material icon named "now widgets sharp".
+  static const IconData now_widgets_sharp = IconData(0xf01c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">offline_bolt</i> &#x2014; material icon named "offline bolt".
+  static const IconData offline_bolt = IconData(0xe8cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">offline_bolt</i> &#x2014; material icon named "offline bolt outlined".
+  static const IconData offline_bolt_outlined = IconData(0xe322, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">offline_bolt</i> &#x2014; material icon named "offline bolt rounded".
+  static const IconData offline_bolt_rounded = IconData(0xf355, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">offline_bolt</i> &#x2014; material icon named "offline bolt sharp".
+  static const IconData offline_bolt_sharp = IconData(0xee27, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">offline_pin</i> &#x2014; material icon named "offline pin".
+  static const IconData offline_pin = IconData(0xe8cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">offline_pin</i> &#x2014; material icon named "offline pin outlined".
+  static const IconData offline_pin_outlined = IconData(0xe323, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">offline_pin</i> &#x2014; material icon named "offline pin rounded".
+  static const IconData offline_pin_rounded = IconData(0xf356, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">offline_pin</i> &#x2014; material icon named "offline pin sharp".
+  static const IconData offline_pin_sharp = IconData(0xee28, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">offline_share</i> &#x2014; material icon named "offline share".
+  static const IconData offline_share = IconData(0xe8cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">ondemand_video</i> &#x2014; material icon named "ondemand video".
+  static const IconData ondemand_video = IconData(0xe8ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">ondemand_video</i> &#x2014; material icon named "ondemand video outlined".
+  static const IconData ondemand_video_outlined = IconData(0xe324, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">ondemand_video</i> &#x2014; material icon named "ondemand video rounded".
+  static const IconData ondemand_video_rounded = IconData(0xf357, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">ondemand_video</i> &#x2014; material icon named "ondemand video sharp".
+  static const IconData ondemand_video_sharp = IconData(0xee29, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">online_prediction</i> &#x2014; material icon named "online prediction".
+  static const IconData online_prediction = IconData(0xe8cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">online_prediction</i> &#x2014; material icon named "online prediction outlined".
+  static const IconData online_prediction_outlined = IconData(0xe325, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">online_prediction</i> &#x2014; material icon named "online prediction rounded".
+  static const IconData online_prediction_rounded = IconData(0xf358, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">online_prediction</i> &#x2014; material icon named "online prediction sharp".
+  static const IconData online_prediction_sharp = IconData(0xee2a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">opacity</i> &#x2014; material icon named "opacity".
+  static const IconData opacity = IconData(0xe8d0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">opacity</i> &#x2014; material icon named "opacity outlined".
+  static const IconData opacity_outlined = IconData(0xe326, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">opacity</i> &#x2014; material icon named "opacity rounded".
+  static const IconData opacity_rounded = IconData(0xf359, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">opacity</i> &#x2014; material icon named "opacity sharp".
+  static const IconData opacity_sharp = IconData(0xee2b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">open_in_browser</i> &#x2014; material icon named "open in browser".
+  static const IconData open_in_browser = IconData(0xe8d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">open_in_browser</i> &#x2014; material icon named "open in browser outlined".
+  static const IconData open_in_browser_outlined = IconData(0xe327, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">open_in_browser</i> &#x2014; material icon named "open in browser rounded".
+  static const IconData open_in_browser_rounded = IconData(0xf35a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">open_in_browser</i> &#x2014; material icon named "open in browser sharp".
+  static const IconData open_in_browser_sharp = IconData(0xee2c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">open_in_full</i> &#x2014; material icon named "open in full".
+  static const IconData open_in_full = IconData(0xe8d2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">open_in_full</i> &#x2014; material icon named "open in full outlined".
+  static const IconData open_in_full_outlined = IconData(0xe328, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">open_in_full</i> &#x2014; material icon named "open in full rounded".
+  static const IconData open_in_full_rounded = IconData(0xf35b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">open_in_full</i> &#x2014; material icon named "open in full sharp".
+  static const IconData open_in_full_sharp = IconData(0xee2d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">open_in_new</i> &#x2014; material icon named "open in new".
+  static const IconData open_in_new = IconData(0xe8d3, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">open_in_new</i> &#x2014; material icon named "open in new outlined".
+  static const IconData open_in_new_outlined = IconData(0xe329, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">open_in_new</i> &#x2014; material icon named "open in new rounded".
+  static const IconData open_in_new_rounded = IconData(0xf35c, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">open_in_new</i> &#x2014; material icon named "open in new sharp".
+  static const IconData open_in_new_sharp = IconData(0xee2e, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">open_with</i> &#x2014; material icon named "open with".
+  static const IconData open_with = IconData(0xe8d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">open_with</i> &#x2014; material icon named "open with outlined".
+  static const IconData open_with_outlined = IconData(0xe32a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">open_with</i> &#x2014; material icon named "open with rounded".
+  static const IconData open_with_rounded = IconData(0xf35d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">open_with</i> &#x2014; material icon named "open with sharp".
+  static const IconData open_with_sharp = IconData(0xee2f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">outbond</i> &#x2014; material icon named "outbond".
+  static const IconData outbond = IconData(0xe8d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">outbond</i> &#x2014; material icon named "outbond outlined".
+  static const IconData outbond_outlined = IconData(0xe32b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">outbond</i> &#x2014; material icon named "outbond rounded".
+  static const IconData outbond_rounded = IconData(0xf35e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">outbond</i> &#x2014; material icon named "outbond sharp".
+  static const IconData outbond_sharp = IconData(0xee30, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">outbox</i> &#x2014; material icon named "outbox".
+  static const IconData outbox = IconData(0xe8d6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">outdoor_grill</i> &#x2014; material icon named "outdoor grill".
+  static const IconData outdoor_grill = IconData(0xe8d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">outdoor_grill</i> &#x2014; material icon named "outdoor grill outlined".
+  static const IconData outdoor_grill_outlined = IconData(0xe32c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">outdoor_grill</i> &#x2014; material icon named "outdoor grill rounded".
+  static const IconData outdoor_grill_rounded = IconData(0xf35f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">outdoor_grill</i> &#x2014; material icon named "outdoor grill sharp".
+  static const IconData outdoor_grill_sharp = IconData(0xee31, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">outgoing_mail</i> &#x2014; material icon named "outgoing mail".
+  static const IconData outgoing_mail = IconData(0xe8d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">outlet</i> &#x2014; material icon named "outlet".
+  static const IconData outlet = IconData(0xe8d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">outlet</i> &#x2014; material icon named "outlet outlined".
+  static const IconData outlet_outlined = IconData(0xe32d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">outlet</i> &#x2014; material icon named "outlet rounded".
+  static const IconData outlet_rounded = IconData(0xf360, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">outlet</i> &#x2014; material icon named "outlet sharp".
+  static const IconData outlet_sharp = IconData(0xee32, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">outlined_flag</i> &#x2014; material icon named "outlined flag".
+  static const IconData outlined_flag = IconData(0xe8da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">outlined_flag</i> &#x2014; material icon named "outlined flag outlined".
+  static const IconData outlined_flag_outlined = IconData(0xe32e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">outlined_flag</i> &#x2014; material icon named "outlined flag rounded".
+  static const IconData outlined_flag_rounded = IconData(0xf361, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">outlined_flag</i> &#x2014; material icon named "outlined flag sharp".
+  static const IconData outlined_flag_sharp = IconData(0xee33, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">padding</i> &#x2014; material icon named "padding".
+  static const IconData padding = IconData(0xe8db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pages</i> &#x2014; material icon named "pages".
+  static const IconData pages = IconData(0xe8dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pages</i> &#x2014; material icon named "pages outlined".
+  static const IconData pages_outlined = IconData(0xe32f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pages</i> &#x2014; material icon named "pages rounded".
+  static const IconData pages_rounded = IconData(0xf362, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pages</i> &#x2014; material icon named "pages sharp".
+  static const IconData pages_sharp = IconData(0xee34, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pageview</i> &#x2014; material icon named "pageview".
+  static const IconData pageview = IconData(0xe8dd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pageview</i> &#x2014; material icon named "pageview outlined".
+  static const IconData pageview_outlined = IconData(0xe330, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pageview</i> &#x2014; material icon named "pageview rounded".
+  static const IconData pageview_rounded = IconData(0xf363, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pageview</i> &#x2014; material icon named "pageview sharp".
+  static const IconData pageview_sharp = IconData(0xee35, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">palette</i> &#x2014; material icon named "palette".
+  static const IconData palette = IconData(0xe8de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">palette</i> &#x2014; material icon named "palette outlined".
+  static const IconData palette_outlined = IconData(0xe331, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">palette</i> &#x2014; material icon named "palette rounded".
+  static const IconData palette_rounded = IconData(0xf364, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">palette</i> &#x2014; material icon named "palette sharp".
+  static const IconData palette_sharp = IconData(0xee36, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pan_tool</i> &#x2014; material icon named "pan tool".
+  static const IconData pan_tool = IconData(0xe8df, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pan_tool</i> &#x2014; material icon named "pan tool outlined".
+  static const IconData pan_tool_outlined = IconData(0xe332, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pan_tool</i> &#x2014; material icon named "pan tool rounded".
+  static const IconData pan_tool_rounded = IconData(0xf365, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pan_tool</i> &#x2014; material icon named "pan tool sharp".
+  static const IconData pan_tool_sharp = IconData(0xee37, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">panorama</i> &#x2014; material icon named "panorama".
+  static const IconData panorama = IconData(0xe8e0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">panorama_fish_eye</i> &#x2014; material icon named "panorama fish eye".
+  static const IconData panorama_fish_eye = IconData(0xe8e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">panorama_fish_eye</i> &#x2014; material icon named "panorama fish eye outlined".
+  static const IconData panorama_fish_eye_outlined = IconData(0xe333, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">panorama_fish_eye</i> &#x2014; material icon named "panorama fish eye rounded".
+  static const IconData panorama_fish_eye_rounded = IconData(0xf366, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">panorama_fish_eye</i> &#x2014; material icon named "panorama fish eye sharp".
+  static const IconData panorama_fish_eye_sharp = IconData(0xee38, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">panorama_fisheye</i> &#x2014; material icon named "panorama fisheye".
+  static const IconData panorama_fisheye = IconData(0xe8e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">panorama_fisheye</i> &#x2014; material icon named "panorama fisheye outlined".
+  static const IconData panorama_fisheye_outlined = IconData(0xe333, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">panorama_fisheye</i> &#x2014; material icon named "panorama fisheye rounded".
+  static const IconData panorama_fisheye_rounded = IconData(0xf366, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">panorama_fisheye</i> &#x2014; material icon named "panorama fisheye sharp".
+  static const IconData panorama_fisheye_sharp = IconData(0xee38, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">panorama_horizontal</i> &#x2014; material icon named "panorama horizontal".
+  static const IconData panorama_horizontal = IconData(0xe8e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">panorama_horizontal</i> &#x2014; material icon named "panorama horizontal outlined".
+  static const IconData panorama_horizontal_outlined = IconData(0xe334, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">panorama_horizontal</i> &#x2014; material icon named "panorama horizontal rounded".
+  static const IconData panorama_horizontal_rounded = IconData(0xf367, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">panorama_horizontal_select</i> &#x2014; material icon named "panorama horizontal select".
+  static const IconData panorama_horizontal_select = IconData(0xe8e3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">panorama_horizontal</i> &#x2014; material icon named "panorama horizontal sharp".
+  static const IconData panorama_horizontal_sharp = IconData(0xee39, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">panorama</i> &#x2014; material icon named "panorama outlined".
+  static const IconData panorama_outlined = IconData(0xe335, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">panorama_photosphere</i> &#x2014; material icon named "panorama photosphere".
+  static const IconData panorama_photosphere = IconData(0xe8e4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">panorama_photosphere_select</i> &#x2014; material icon named "panorama photosphere select".
+  static const IconData panorama_photosphere_select = IconData(0xe8e5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">panorama</i> &#x2014; material icon named "panorama rounded".
+  static const IconData panorama_rounded = IconData(0xf368, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">panorama</i> &#x2014; material icon named "panorama sharp".
+  static const IconData panorama_sharp = IconData(0xee3a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">panorama_vertical</i> &#x2014; material icon named "panorama vertical".
+  static const IconData panorama_vertical = IconData(0xe8e6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">panorama_vertical</i> &#x2014; material icon named "panorama vertical outlined".
+  static const IconData panorama_vertical_outlined = IconData(0xe336, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">panorama_vertical</i> &#x2014; material icon named "panorama vertical rounded".
+  static const IconData panorama_vertical_rounded = IconData(0xf369, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">panorama_vertical_select</i> &#x2014; material icon named "panorama vertical select".
+  static const IconData panorama_vertical_select = IconData(0xe8e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">panorama_vertical</i> &#x2014; material icon named "panorama vertical sharp".
+  static const IconData panorama_vertical_sharp = IconData(0xee3b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">panorama_wide_angle</i> &#x2014; material icon named "panorama wide angle".
+  static const IconData panorama_wide_angle = IconData(0xe8e8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">panorama_wide_angle</i> &#x2014; material icon named "panorama wide angle outlined".
+  static const IconData panorama_wide_angle_outlined = IconData(0xe337, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">panorama_wide_angle</i> &#x2014; material icon named "panorama wide angle rounded".
+  static const IconData panorama_wide_angle_rounded = IconData(0xf36a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">panorama_wide_angle_select</i> &#x2014; material icon named "panorama wide angle select".
+  static const IconData panorama_wide_angle_select = IconData(0xe8e9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">panorama_wide_angle</i> &#x2014; material icon named "panorama wide angle sharp".
+  static const IconData panorama_wide_angle_sharp = IconData(0xee3c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">park</i> &#x2014; material icon named "park".
+  static const IconData park = IconData(0xe8ea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">party_mode</i> &#x2014; material icon named "party mode".
+  static const IconData party_mode = IconData(0xe8eb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">party_mode</i> &#x2014; material icon named "party mode outlined".
+  static const IconData party_mode_outlined = IconData(0xe338, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">party_mode</i> &#x2014; material icon named "party mode rounded".
+  static const IconData party_mode_rounded = IconData(0xf36b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">party_mode</i> &#x2014; material icon named "party mode sharp".
+  static const IconData party_mode_sharp = IconData(0xee3d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">paste</i> &#x2014; material icon named "paste".
+  static const IconData paste = IconData(0xe683, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">paste</i> &#x2014; material icon named "paste outlined".
+  static const IconData paste_outlined = IconData(0xe10e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">paste</i> &#x2014; material icon named "paste rounded".
+  static const IconData paste_rounded = IconData(0xf13c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">paste</i> &#x2014; material icon named "paste sharp".
+  static const IconData paste_sharp = IconData(0xec0e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pause</i> &#x2014; material icon named "pause".
+  static const IconData pause = IconData(0xe8ec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pause_circle_filled</i> &#x2014; material icon named "pause circle filled".
+  static const IconData pause_circle_filled = IconData(0xe8ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pause_circle_filled</i> &#x2014; material icon named "pause circle filled outlined".
+  static const IconData pause_circle_filled_outlined = IconData(0xe339, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pause_circle_filled</i> &#x2014; material icon named "pause circle filled rounded".
+  static const IconData pause_circle_filled_rounded = IconData(0xf36c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pause_circle_filled</i> &#x2014; material icon named "pause circle filled sharp".
+  static const IconData pause_circle_filled_sharp = IconData(0xee3e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pause_circle_outline</i> &#x2014; material icon named "pause circle outline".
+  static const IconData pause_circle_outline = IconData(0xe8ee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pause_circle_outline</i> &#x2014; material icon named "pause circle outline outlined".
+  static const IconData pause_circle_outline_outlined = IconData(0xe33a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pause_circle_outline</i> &#x2014; material icon named "pause circle outline rounded".
+  static const IconData pause_circle_outline_rounded = IconData(0xf36d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pause_circle_outline</i> &#x2014; material icon named "pause circle outline sharp".
+  static const IconData pause_circle_outline_sharp = IconData(0xee3f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pause</i> &#x2014; material icon named "pause outlined".
+  static const IconData pause_outlined = IconData(0xe33b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pause_presentation</i> &#x2014; material icon named "pause presentation".
+  static const IconData pause_presentation = IconData(0xe8ef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pause_presentation</i> &#x2014; material icon named "pause presentation outlined".
+  static const IconData pause_presentation_outlined = IconData(0xe33c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pause_presentation</i> &#x2014; material icon named "pause presentation rounded".
+  static const IconData pause_presentation_rounded = IconData(0xf36e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pause_presentation</i> &#x2014; material icon named "pause presentation sharp".
+  static const IconData pause_presentation_sharp = IconData(0xee40, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pause</i> &#x2014; material icon named "pause rounded".
+  static const IconData pause_rounded = IconData(0xf36f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pause</i> &#x2014; material icon named "pause sharp".
+  static const IconData pause_sharp = IconData(0xee41, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">payment</i> &#x2014; material icon named "payment".
+  static const IconData payment = IconData(0xe8f0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">payment</i> &#x2014; material icon named "payment outlined".
+  static const IconData payment_outlined = IconData(0xe33d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">payment</i> &#x2014; material icon named "payment rounded".
+  static const IconData payment_rounded = IconData(0xf370, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">payment</i> &#x2014; material icon named "payment sharp".
+  static const IconData payment_sharp = IconData(0xee42, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">payments</i> &#x2014; material icon named "payments".
+  static const IconData payments = IconData(0xe8f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">payments</i> &#x2014; material icon named "payments outlined".
+  static const IconData payments_outlined = IconData(0xe33e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">payments</i> &#x2014; material icon named "payments rounded".
+  static const IconData payments_rounded = IconData(0xf371, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">payments</i> &#x2014; material icon named "payments sharp".
+  static const IconData payments_sharp = IconData(0xee43, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pedal_bike</i> &#x2014; material icon named "pedal bike".
+  static const IconData pedal_bike = IconData(0xe8f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pedal_bike</i> &#x2014; material icon named "pedal bike outlined".
+  static const IconData pedal_bike_outlined = IconData(0xe33f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pedal_bike</i> &#x2014; material icon named "pedal bike rounded".
+  static const IconData pedal_bike_rounded = IconData(0xf372, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pedal_bike</i> &#x2014; material icon named "pedal bike sharp".
+  static const IconData pedal_bike_sharp = IconData(0xee44, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pending</i> &#x2014; material icon named "pending".
+  static const IconData pending = IconData(0xe8f3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pending_actions</i> &#x2014; material icon named "pending actions".
+  static const IconData pending_actions = IconData(0xe8f4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pending_actions</i> &#x2014; material icon named "pending actions outlined".
+  static const IconData pending_actions_outlined = IconData(0xe340, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pending_actions</i> &#x2014; material icon named "pending actions rounded".
+  static const IconData pending_actions_rounded = IconData(0xf373, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pending_actions</i> &#x2014; material icon named "pending actions sharp".
+  static const IconData pending_actions_sharp = IconData(0xee45, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pending</i> &#x2014; material icon named "pending outlined".
+  static const IconData pending_outlined = IconData(0xe341, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pending</i> &#x2014; material icon named "pending rounded".
+  static const IconData pending_rounded = IconData(0xf374, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pending</i> &#x2014; material icon named "pending sharp".
+  static const IconData pending_sharp = IconData(0xee46, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">people</i> &#x2014; material icon named "people".
+  static const IconData people = IconData(0xe8f5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">people_alt</i> &#x2014; material icon named "people alt".
+  static const IconData people_alt = IconData(0xe8f6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">people_alt</i> &#x2014; material icon named "people alt outlined".
+  static const IconData people_alt_outlined = IconData(0xe342, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">people_alt</i> &#x2014; material icon named "people alt rounded".
+  static const IconData people_alt_rounded = IconData(0xf375, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">people_alt</i> &#x2014; material icon named "people alt sharp".
+  static const IconData people_alt_sharp = IconData(0xee47, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">people_outline</i> &#x2014; material icon named "people outline".
+  static const IconData people_outline = IconData(0xe8f7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">people_outline</i> &#x2014; material icon named "people outline outlined".
+  static const IconData people_outline_outlined = IconData(0xe343, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">people_outline</i> &#x2014; material icon named "people outline rounded".
+  static const IconData people_outline_rounded = IconData(0xf376, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">people_outline</i> &#x2014; material icon named "people outline sharp".
+  static const IconData people_outline_sharp = IconData(0xee48, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">people</i> &#x2014; material icon named "people outlined".
+  static const IconData people_outlined = IconData(0xe344, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">people</i> &#x2014; material icon named "people rounded".
+  static const IconData people_rounded = IconData(0xf377, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">people</i> &#x2014; material icon named "people sharp".
+  static const IconData people_sharp = IconData(0xee49, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">perm_camera_mic</i> &#x2014; material icon named "perm camera mic".
+  static const IconData perm_camera_mic = IconData(0xe8f8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">perm_camera_mic</i> &#x2014; material icon named "perm camera mic outlined".
+  static const IconData perm_camera_mic_outlined = IconData(0xe345, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">perm_camera_mic</i> &#x2014; material icon named "perm camera mic rounded".
+  static const IconData perm_camera_mic_rounded = IconData(0xf378, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">perm_camera_mic</i> &#x2014; material icon named "perm camera mic sharp".
+  static const IconData perm_camera_mic_sharp = IconData(0xee4a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">perm_contact_cal</i> &#x2014; material icon named "perm contact cal".
+  static const IconData perm_contact_cal = IconData(0xe8f9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">perm_contact_cal</i> &#x2014; material icon named "perm contact cal outlined".
+  static const IconData perm_contact_cal_outlined = IconData(0xe346, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">perm_contact_cal</i> &#x2014; material icon named "perm contact cal rounded".
+  static const IconData perm_contact_cal_rounded = IconData(0xf379, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">perm_contact_cal</i> &#x2014; material icon named "perm contact cal sharp".
+  static const IconData perm_contact_cal_sharp = IconData(0xee4b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">perm_contact_calendar</i> &#x2014; material icon named "perm contact calendar".
+  static const IconData perm_contact_calendar = IconData(0xe8f9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">perm_contact_calendar</i> &#x2014; material icon named "perm contact calendar outlined".
+  static const IconData perm_contact_calendar_outlined = IconData(0xe346, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">perm_contact_calendar</i> &#x2014; material icon named "perm contact calendar rounded".
+  static const IconData perm_contact_calendar_rounded = IconData(0xf379, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">perm_contact_calendar</i> &#x2014; material icon named "perm contact calendar sharp".
+  static const IconData perm_contact_calendar_sharp = IconData(0xee4b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">perm_data_setting</i> &#x2014; material icon named "perm data setting".
+  static const IconData perm_data_setting = IconData(0xe8fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">perm_data_setting</i> &#x2014; material icon named "perm data setting outlined".
+  static const IconData perm_data_setting_outlined = IconData(0xe347, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">perm_data_setting</i> &#x2014; material icon named "perm data setting rounded".
+  static const IconData perm_data_setting_rounded = IconData(0xf37a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">perm_data_setting</i> &#x2014; material icon named "perm data setting sharp".
+  static const IconData perm_data_setting_sharp = IconData(0xee4c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">perm_device_info</i> &#x2014; material icon named "perm device info".
+  static const IconData perm_device_info = IconData(0xe8fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">perm_device_info</i> &#x2014; material icon named "perm device info outlined".
+  static const IconData perm_device_info_outlined = IconData(0xe348, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">perm_device_info</i> &#x2014; material icon named "perm device info rounded".
+  static const IconData perm_device_info_rounded = IconData(0xf37b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">perm_device_info</i> &#x2014; material icon named "perm device info sharp".
+  static const IconData perm_device_info_sharp = IconData(0xee4d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">perm_device_information</i> &#x2014; material icon named "perm device information".
+  static const IconData perm_device_information = IconData(0xe8fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">perm_device_information</i> &#x2014; material icon named "perm device information outlined".
+  static const IconData perm_device_information_outlined = IconData(0xe348, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">perm_device_information</i> &#x2014; material icon named "perm device information rounded".
+  static const IconData perm_device_information_rounded = IconData(0xf37b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">perm_device_information</i> &#x2014; material icon named "perm device information sharp".
+  static const IconData perm_device_information_sharp = IconData(0xee4d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">perm_identity</i> &#x2014; material icon named "perm identity".
+  static const IconData perm_identity = IconData(0xe8fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">perm_identity</i> &#x2014; material icon named "perm identity outlined".
+  static const IconData perm_identity_outlined = IconData(0xe349, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">perm_identity</i> &#x2014; material icon named "perm identity rounded".
+  static const IconData perm_identity_rounded = IconData(0xf37c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">perm_identity</i> &#x2014; material icon named "perm identity sharp".
+  static const IconData perm_identity_sharp = IconData(0xee4e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">perm_media</i> &#x2014; material icon named "perm media".
+  static const IconData perm_media = IconData(0xe8fd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">perm_media</i> &#x2014; material icon named "perm media outlined".
+  static const IconData perm_media_outlined = IconData(0xe34a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">perm_media</i> &#x2014; material icon named "perm media rounded".
+  static const IconData perm_media_rounded = IconData(0xf37d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">perm_media</i> &#x2014; material icon named "perm media sharp".
+  static const IconData perm_media_sharp = IconData(0xee4f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">perm_phone_msg</i> &#x2014; material icon named "perm phone msg".
+  static const IconData perm_phone_msg = IconData(0xe8fe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">perm_phone_msg</i> &#x2014; material icon named "perm phone msg outlined".
+  static const IconData perm_phone_msg_outlined = IconData(0xe34b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">perm_phone_msg</i> &#x2014; material icon named "perm phone msg rounded".
+  static const IconData perm_phone_msg_rounded = IconData(0xf37e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">perm_phone_msg</i> &#x2014; material icon named "perm phone msg sharp".
+  static const IconData perm_phone_msg_sharp = IconData(0xee50, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">perm_scan_wifi</i> &#x2014; material icon named "perm scan wifi".
+  static const IconData perm_scan_wifi = IconData(0xe8ff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">perm_scan_wifi</i> &#x2014; material icon named "perm scan wifi outlined".
+  static const IconData perm_scan_wifi_outlined = IconData(0xe34c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">perm_scan_wifi</i> &#x2014; material icon named "perm scan wifi rounded".
+  static const IconData perm_scan_wifi_rounded = IconData(0xf37f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">perm_scan_wifi</i> &#x2014; material icon named "perm scan wifi sharp".
+  static const IconData perm_scan_wifi_sharp = IconData(0xee51, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">person</i> &#x2014; material icon named "person".
+  static const IconData person = IconData(0xe900, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">person_add</i> &#x2014; material icon named "person add".
+  static const IconData person_add = IconData(0xe901, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">person_add_alt</i> &#x2014; material icon named "person add alt".
+  static const IconData person_add_alt = IconData(0xe902, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">person_add_alt_1</i> &#x2014; material icon named "person add alt 1".
+  static const IconData person_add_alt_1 = IconData(0xe903, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">person_add_alt_1</i> &#x2014; material icon named "person add alt 1 outlined".
+  static const IconData person_add_alt_1_outlined = IconData(0xe34d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">person_add_alt_1</i> &#x2014; material icon named "person add alt 1 rounded".
+  static const IconData person_add_alt_1_rounded = IconData(0xf380, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">person_add_alt_1</i> &#x2014; material icon named "person add alt 1 sharp".
+  static const IconData person_add_alt_1_sharp = IconData(0xee52, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">person_add_disabled</i> &#x2014; material icon named "person add disabled".
+  static const IconData person_add_disabled = IconData(0xe904, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">person_add_disabled</i> &#x2014; material icon named "person add disabled outlined".
+  static const IconData person_add_disabled_outlined = IconData(0xe34e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">person_add_disabled</i> &#x2014; material icon named "person add disabled rounded".
+  static const IconData person_add_disabled_rounded = IconData(0xf381, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">person_add_disabled</i> &#x2014; material icon named "person add disabled sharp".
+  static const IconData person_add_disabled_sharp = IconData(0xee53, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">person_add</i> &#x2014; material icon named "person add outlined".
+  static const IconData person_add_outlined = IconData(0xe34f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">person_add</i> &#x2014; material icon named "person add rounded".
+  static const IconData person_add_rounded = IconData(0xf382, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">person_add</i> &#x2014; material icon named "person add sharp".
+  static const IconData person_add_sharp = IconData(0xee54, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">person_outline</i> &#x2014; material icon named "person outline".
+  static const IconData person_outline = IconData(0xe905, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">person_outline</i> &#x2014; material icon named "person outline outlined".
+  static const IconData person_outline_outlined = IconData(0xe350, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">person_outline</i> &#x2014; material icon named "person outline rounded".
+  static const IconData person_outline_rounded = IconData(0xf383, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">person_outline</i> &#x2014; material icon named "person outline sharp".
+  static const IconData person_outline_sharp = IconData(0xee55, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">person</i> &#x2014; material icon named "person outlined".
+  static const IconData person_outlined = IconData(0xe351, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">person_pin</i> &#x2014; material icon named "person pin".
+  static const IconData person_pin = IconData(0xe906, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">person_pin_circle</i> &#x2014; material icon named "person pin circle".
+  static const IconData person_pin_circle = IconData(0xe907, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">person_pin_circle</i> &#x2014; material icon named "person pin circle outlined".
+  static const IconData person_pin_circle_outlined = IconData(0xe352, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">person_pin_circle</i> &#x2014; material icon named "person pin circle rounded".
+  static const IconData person_pin_circle_rounded = IconData(0xf384, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">person_pin_circle</i> &#x2014; material icon named "person pin circle sharp".
+  static const IconData person_pin_circle_sharp = IconData(0xee56, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">person_pin</i> &#x2014; material icon named "person pin outlined".
+  static const IconData person_pin_outlined = IconData(0xe353, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">person_pin</i> &#x2014; material icon named "person pin rounded".
+  static const IconData person_pin_rounded = IconData(0xf385, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">person_pin</i> &#x2014; material icon named "person pin sharp".
+  static const IconData person_pin_sharp = IconData(0xee57, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">person_remove</i> &#x2014; material icon named "person remove".
+  static const IconData person_remove = IconData(0xe908, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">person_remove_alt_1</i> &#x2014; material icon named "person remove alt 1".
+  static const IconData person_remove_alt_1 = IconData(0xe909, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">person_remove_alt_1</i> &#x2014; material icon named "person remove alt 1 outlined".
+  static const IconData person_remove_alt_1_outlined = IconData(0xe354, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">person_remove_alt_1</i> &#x2014; material icon named "person remove alt 1 rounded".
+  static const IconData person_remove_alt_1_rounded = IconData(0xf386, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">person_remove_alt_1</i> &#x2014; material icon named "person remove alt 1 sharp".
+  static const IconData person_remove_alt_1_sharp = IconData(0xee58, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">person_remove</i> &#x2014; material icon named "person remove outlined".
+  static const IconData person_remove_outlined = IconData(0xe355, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">person_remove</i> &#x2014; material icon named "person remove rounded".
+  static const IconData person_remove_rounded = IconData(0xf387, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">person_remove</i> &#x2014; material icon named "person remove sharp".
+  static const IconData person_remove_sharp = IconData(0xee59, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">person</i> &#x2014; material icon named "person rounded".
+  static const IconData person_rounded = IconData(0xf388, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">person_search</i> &#x2014; material icon named "person search".
+  static const IconData person_search = IconData(0xe90a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">person_search</i> &#x2014; material icon named "person search outlined".
+  static const IconData person_search_outlined = IconData(0xe356, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">person_search</i> &#x2014; material icon named "person search rounded".
+  static const IconData person_search_rounded = IconData(0xf389, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">person_search</i> &#x2014; material icon named "person search sharp".
+  static const IconData person_search_sharp = IconData(0xee5a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">person</i> &#x2014; material icon named "person sharp".
+  static const IconData person_sharp = IconData(0xee5b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">personal_video</i> &#x2014; material icon named "personal video".
+  static const IconData personal_video = IconData(0xe90b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">personal_video</i> &#x2014; material icon named "personal video outlined".
+  static const IconData personal_video_outlined = IconData(0xe357, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">personal_video</i> &#x2014; material icon named "personal video rounded".
+  static const IconData personal_video_rounded = IconData(0xf38a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">personal_video</i> &#x2014; material icon named "personal video sharp".
+  static const IconData personal_video_sharp = IconData(0xee5c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pest_control</i> &#x2014; material icon named "pest control".
+  static const IconData pest_control = IconData(0xe90c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pest_control</i> &#x2014; material icon named "pest control outlined".
+  static const IconData pest_control_outlined = IconData(0xe358, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pest_control_rodent</i> &#x2014; material icon named "pest control rodent".
+  static const IconData pest_control_rodent = IconData(0xe90d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pest_control_rodent</i> &#x2014; material icon named "pest control rodent outlined".
+  static const IconData pest_control_rodent_outlined = IconData(0xe359, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pest_control_rodent</i> &#x2014; material icon named "pest control rodent rounded".
+  static const IconData pest_control_rodent_rounded = IconData(0xf38b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pest_control_rodent</i> &#x2014; material icon named "pest control rodent sharp".
+  static const IconData pest_control_rodent_sharp = IconData(0xee5d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pest_control</i> &#x2014; material icon named "pest control rounded".
+  static const IconData pest_control_rounded = IconData(0xf38c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pest_control</i> &#x2014; material icon named "pest control sharp".
+  static const IconData pest_control_sharp = IconData(0xee5e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pets</i> &#x2014; material icon named "pets".
+  static const IconData pets = IconData(0xe90e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pets</i> &#x2014; material icon named "pets outlined".
+  static const IconData pets_outlined = IconData(0xe35a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pets</i> &#x2014; material icon named "pets rounded".
+  static const IconData pets_rounded = IconData(0xf38d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pets</i> &#x2014; material icon named "pets sharp".
+  static const IconData pets_sharp = IconData(0xee5f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phone</i> &#x2014; material icon named "phone".
+  static const IconData phone = IconData(0xe90f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phone_android</i> &#x2014; material icon named "phone android".
+  static const IconData phone_android = IconData(0xe910, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phone_android</i> &#x2014; material icon named "phone android outlined".
+  static const IconData phone_android_outlined = IconData(0xe35b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phone_android</i> &#x2014; material icon named "phone android rounded".
+  static const IconData phone_android_rounded = IconData(0xf38e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phone_android</i> &#x2014; material icon named "phone android sharp".
+  static const IconData phone_android_sharp = IconData(0xee60, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phone_bluetooth_speaker</i> &#x2014; material icon named "phone bluetooth speaker".
+  static const IconData phone_bluetooth_speaker = IconData(0xe911, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phone_bluetooth_speaker</i> &#x2014; material icon named "phone bluetooth speaker outlined".
+  static const IconData phone_bluetooth_speaker_outlined = IconData(0xe35c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phone_bluetooth_speaker</i> &#x2014; material icon named "phone bluetooth speaker rounded".
+  static const IconData phone_bluetooth_speaker_rounded = IconData(0xf38f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phone_bluetooth_speaker</i> &#x2014; material icon named "phone bluetooth speaker sharp".
+  static const IconData phone_bluetooth_speaker_sharp = IconData(0xee61, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phone_callback</i> &#x2014; material icon named "phone callback".
+  static const IconData phone_callback = IconData(0xe912, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phone_callback</i> &#x2014; material icon named "phone callback outlined".
+  static const IconData phone_callback_outlined = IconData(0xe35d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phone_callback</i> &#x2014; material icon named "phone callback rounded".
+  static const IconData phone_callback_rounded = IconData(0xf390, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phone_callback</i> &#x2014; material icon named "phone callback sharp".
+  static const IconData phone_callback_sharp = IconData(0xee62, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phone_disabled</i> &#x2014; material icon named "phone disabled".
+  static const IconData phone_disabled = IconData(0xe913, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phone_disabled</i> &#x2014; material icon named "phone disabled outlined".
+  static const IconData phone_disabled_outlined = IconData(0xe35e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phone_disabled</i> &#x2014; material icon named "phone disabled rounded".
+  static const IconData phone_disabled_rounded = IconData(0xf391, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phone_disabled</i> &#x2014; material icon named "phone disabled sharp".
+  static const IconData phone_disabled_sharp = IconData(0xee63, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phone_enabled</i> &#x2014; material icon named "phone enabled".
+  static const IconData phone_enabled = IconData(0xe914, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phone_enabled</i> &#x2014; material icon named "phone enabled outlined".
+  static const IconData phone_enabled_outlined = IconData(0xe35f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phone_enabled</i> &#x2014; material icon named "phone enabled rounded".
+  static const IconData phone_enabled_rounded = IconData(0xf392, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phone_enabled</i> &#x2014; material icon named "phone enabled sharp".
+  static const IconData phone_enabled_sharp = IconData(0xee64, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phone_forwarded</i> &#x2014; material icon named "phone forwarded".
+  static const IconData phone_forwarded = IconData(0xe915, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phone_forwarded</i> &#x2014; material icon named "phone forwarded outlined".
+  static const IconData phone_forwarded_outlined = IconData(0xe360, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phone_forwarded</i> &#x2014; material icon named "phone forwarded rounded".
+  static const IconData phone_forwarded_rounded = IconData(0xf393, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phone_forwarded</i> &#x2014; material icon named "phone forwarded sharp".
+  static const IconData phone_forwarded_sharp = IconData(0xee65, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phone_in_talk</i> &#x2014; material icon named "phone in talk".
+  static const IconData phone_in_talk = IconData(0xe916, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phone_in_talk</i> &#x2014; material icon named "phone in talk outlined".
+  static const IconData phone_in_talk_outlined = IconData(0xe361, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phone_in_talk</i> &#x2014; material icon named "phone in talk rounded".
+  static const IconData phone_in_talk_rounded = IconData(0xf394, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phone_in_talk</i> &#x2014; material icon named "phone in talk sharp".
+  static const IconData phone_in_talk_sharp = IconData(0xee66, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phone_iphone</i> &#x2014; material icon named "phone iphone".
+  static const IconData phone_iphone = IconData(0xe917, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phone_iphone</i> &#x2014; material icon named "phone iphone outlined".
+  static const IconData phone_iphone_outlined = IconData(0xe362, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phone_iphone</i> &#x2014; material icon named "phone iphone rounded".
+  static const IconData phone_iphone_rounded = IconData(0xf395, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phone_iphone</i> &#x2014; material icon named "phone iphone sharp".
+  static const IconData phone_iphone_sharp = IconData(0xee67, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phone_locked</i> &#x2014; material icon named "phone locked".
+  static const IconData phone_locked = IconData(0xe918, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phone_locked</i> &#x2014; material icon named "phone locked outlined".
+  static const IconData phone_locked_outlined = IconData(0xe363, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phone_locked</i> &#x2014; material icon named "phone locked rounded".
+  static const IconData phone_locked_rounded = IconData(0xf396, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phone_locked</i> &#x2014; material icon named "phone locked sharp".
+  static const IconData phone_locked_sharp = IconData(0xee68, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phone_missed</i> &#x2014; material icon named "phone missed".
+  static const IconData phone_missed = IconData(0xe919, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phone_missed</i> &#x2014; material icon named "phone missed outlined".
+  static const IconData phone_missed_outlined = IconData(0xe364, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phone_missed</i> &#x2014; material icon named "phone missed rounded".
+  static const IconData phone_missed_rounded = IconData(0xf397, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phone_missed</i> &#x2014; material icon named "phone missed sharp".
+  static const IconData phone_missed_sharp = IconData(0xee69, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phone</i> &#x2014; material icon named "phone outlined".
+  static const IconData phone_outlined = IconData(0xe365, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phone_paused</i> &#x2014; material icon named "phone paused".
+  static const IconData phone_paused = IconData(0xe91a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phone_paused</i> &#x2014; material icon named "phone paused outlined".
+  static const IconData phone_paused_outlined = IconData(0xe366, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phone_paused</i> &#x2014; material icon named "phone paused rounded".
+  static const IconData phone_paused_rounded = IconData(0xf398, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phone_paused</i> &#x2014; material icon named "phone paused sharp".
+  static const IconData phone_paused_sharp = IconData(0xee6a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phone</i> &#x2014; material icon named "phone rounded".
+  static const IconData phone_rounded = IconData(0xf399, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phone</i> &#x2014; material icon named "phone sharp".
+  static const IconData phone_sharp = IconData(0xee6b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phonelink</i> &#x2014; material icon named "phonelink".
+  static const IconData phonelink = IconData(0xe91b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phonelink_erase</i> &#x2014; material icon named "phonelink erase".
+  static const IconData phonelink_erase = IconData(0xe91c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phonelink_erase</i> &#x2014; material icon named "phonelink erase outlined".
+  static const IconData phonelink_erase_outlined = IconData(0xe367, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phonelink_erase</i> &#x2014; material icon named "phonelink erase rounded".
+  static const IconData phonelink_erase_rounded = IconData(0xf39a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phonelink_erase</i> &#x2014; material icon named "phonelink erase sharp".
+  static const IconData phonelink_erase_sharp = IconData(0xee6c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phonelink_lock</i> &#x2014; material icon named "phonelink lock".
+  static const IconData phonelink_lock = IconData(0xe91d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phonelink_lock</i> &#x2014; material icon named "phonelink lock outlined".
+  static const IconData phonelink_lock_outlined = IconData(0xe368, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phonelink_lock</i> &#x2014; material icon named "phonelink lock rounded".
+  static const IconData phonelink_lock_rounded = IconData(0xf39b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phonelink_lock</i> &#x2014; material icon named "phonelink lock sharp".
+  static const IconData phonelink_lock_sharp = IconData(0xee6d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phonelink_off</i> &#x2014; material icon named "phonelink off".
+  static const IconData phonelink_off = IconData(0xe91e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phonelink_off</i> &#x2014; material icon named "phonelink off outlined".
+  static const IconData phonelink_off_outlined = IconData(0xe369, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phonelink_off</i> &#x2014; material icon named "phonelink off rounded".
+  static const IconData phonelink_off_rounded = IconData(0xf39c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phonelink_off</i> &#x2014; material icon named "phonelink off sharp".
+  static const IconData phonelink_off_sharp = IconData(0xee6e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phonelink</i> &#x2014; material icon named "phonelink outlined".
+  static const IconData phonelink_outlined = IconData(0xe36a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phonelink_ring</i> &#x2014; material icon named "phonelink ring".
+  static const IconData phonelink_ring = IconData(0xe91f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phonelink_ring</i> &#x2014; material icon named "phonelink ring outlined".
+  static const IconData phonelink_ring_outlined = IconData(0xe36b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phonelink_ring</i> &#x2014; material icon named "phonelink ring rounded".
+  static const IconData phonelink_ring_rounded = IconData(0xf39d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phonelink_ring</i> &#x2014; material icon named "phonelink ring sharp".
+  static const IconData phonelink_ring_sharp = IconData(0xee6f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phonelink</i> &#x2014; material icon named "phonelink rounded".
+  static const IconData phonelink_rounded = IconData(0xf39e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">phonelink_setup</i> &#x2014; material icon named "phonelink setup".
+  static const IconData phonelink_setup = IconData(0xe920, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">phonelink_setup</i> &#x2014; material icon named "phonelink setup outlined".
+  static const IconData phonelink_setup_outlined = IconData(0xe36c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">phonelink_setup</i> &#x2014; material icon named "phonelink setup rounded".
+  static const IconData phonelink_setup_rounded = IconData(0xf39f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phonelink_setup</i> &#x2014; material icon named "phonelink setup sharp".
+  static const IconData phonelink_setup_sharp = IconData(0xee70, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">phonelink</i> &#x2014; material icon named "phonelink sharp".
+  static const IconData phonelink_sharp = IconData(0xee71, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">photo</i> &#x2014; material icon named "photo".
+  static const IconData photo = IconData(0xe921, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">photo_album</i> &#x2014; material icon named "photo album".
+  static const IconData photo_album = IconData(0xe922, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">photo_album</i> &#x2014; material icon named "photo album outlined".
+  static const IconData photo_album_outlined = IconData(0xe36d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">photo_album</i> &#x2014; material icon named "photo album rounded".
+  static const IconData photo_album_rounded = IconData(0xf3a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">photo_album</i> &#x2014; material icon named "photo album sharp".
+  static const IconData photo_album_sharp = IconData(0xee72, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">photo_camera</i> &#x2014; material icon named "photo camera".
+  static const IconData photo_camera = IconData(0xe923, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">photo_camera_back</i> &#x2014; material icon named "photo camera back".
+  static const IconData photo_camera_back = IconData(0xe924, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">photo_camera_front</i> &#x2014; material icon named "photo camera front".
+  static const IconData photo_camera_front = IconData(0xe925, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">photo_camera</i> &#x2014; material icon named "photo camera outlined".
+  static const IconData photo_camera_outlined = IconData(0xe36e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">photo_camera</i> &#x2014; material icon named "photo camera rounded".
+  static const IconData photo_camera_rounded = IconData(0xf3a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">photo_camera</i> &#x2014; material icon named "photo camera sharp".
+  static const IconData photo_camera_sharp = IconData(0xee73, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">photo_filter</i> &#x2014; material icon named "photo filter".
+  static const IconData photo_filter = IconData(0xe926, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">photo_filter</i> &#x2014; material icon named "photo filter outlined".
+  static const IconData photo_filter_outlined = IconData(0xe36f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">photo_filter</i> &#x2014; material icon named "photo filter rounded".
+  static const IconData photo_filter_rounded = IconData(0xf3a2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">photo_filter</i> &#x2014; material icon named "photo filter sharp".
+  static const IconData photo_filter_sharp = IconData(0xee74, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">photo_library</i> &#x2014; material icon named "photo library".
+  static const IconData photo_library = IconData(0xe927, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">photo_library</i> &#x2014; material icon named "photo library outlined".
+  static const IconData photo_library_outlined = IconData(0xe370, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">photo_library</i> &#x2014; material icon named "photo library rounded".
+  static const IconData photo_library_rounded = IconData(0xf3a3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">photo_library</i> &#x2014; material icon named "photo library sharp".
+  static const IconData photo_library_sharp = IconData(0xee75, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">photo</i> &#x2014; material icon named "photo outlined".
+  static const IconData photo_outlined = IconData(0xe371, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">photo</i> &#x2014; material icon named "photo rounded".
+  static const IconData photo_rounded = IconData(0xf3a4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">photo</i> &#x2014; material icon named "photo sharp".
+  static const IconData photo_sharp = IconData(0xee76, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">photo_size_select_actual</i> &#x2014; material icon named "photo size select actual".
+  static const IconData photo_size_select_actual = IconData(0xe928, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">photo_size_select_actual</i> &#x2014; material icon named "photo size select actual outlined".
+  static const IconData photo_size_select_actual_outlined = IconData(0xe372, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">photo_size_select_actual</i> &#x2014; material icon named "photo size select actual rounded".
+  static const IconData photo_size_select_actual_rounded = IconData(0xf3a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">photo_size_select_actual</i> &#x2014; material icon named "photo size select actual sharp".
+  static const IconData photo_size_select_actual_sharp = IconData(0xee77, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">photo_size_select_large</i> &#x2014; material icon named "photo size select large".
+  static const IconData photo_size_select_large = IconData(0xe929, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">photo_size_select_large</i> &#x2014; material icon named "photo size select large outlined".
+  static const IconData photo_size_select_large_outlined = IconData(0xe373, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">photo_size_select_large</i> &#x2014; material icon named "photo size select large rounded".
+  static const IconData photo_size_select_large_rounded = IconData(0xf3a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">photo_size_select_large</i> &#x2014; material icon named "photo size select large sharp".
+  static const IconData photo_size_select_large_sharp = IconData(0xee78, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">photo_size_select_small</i> &#x2014; material icon named "photo size select small".
+  static const IconData photo_size_select_small = IconData(0xe92a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">photo_size_select_small</i> &#x2014; material icon named "photo size select small outlined".
+  static const IconData photo_size_select_small_outlined = IconData(0xe374, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">photo_size_select_small</i> &#x2014; material icon named "photo size select small rounded".
+  static const IconData photo_size_select_small_rounded = IconData(0xf3a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">photo_size_select_small</i> &#x2014; material icon named "photo size select small sharp".
+  static const IconData photo_size_select_small_sharp = IconData(0xee79, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">picture_as_pdf</i> &#x2014; material icon named "picture as pdf".
+  static const IconData picture_as_pdf = IconData(0xe92b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">picture_as_pdf</i> &#x2014; material icon named "picture as pdf outlined".
+  static const IconData picture_as_pdf_outlined = IconData(0xe375, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">picture_as_pdf</i> &#x2014; material icon named "picture as pdf rounded".
+  static const IconData picture_as_pdf_rounded = IconData(0xf3a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">picture_as_pdf</i> &#x2014; material icon named "picture as pdf sharp".
+  static const IconData picture_as_pdf_sharp = IconData(0xee7a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">picture_in_picture</i> &#x2014; material icon named "picture in picture".
+  static const IconData picture_in_picture = IconData(0xe92c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">picture_in_picture_alt</i> &#x2014; material icon named "picture in picture alt".
+  static const IconData picture_in_picture_alt = IconData(0xe92d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">picture_in_picture_alt</i> &#x2014; material icon named "picture in picture alt outlined".
+  static const IconData picture_in_picture_alt_outlined = IconData(0xe376, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">picture_in_picture_alt</i> &#x2014; material icon named "picture in picture alt rounded".
+  static const IconData picture_in_picture_alt_rounded = IconData(0xf3a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">picture_in_picture_alt</i> &#x2014; material icon named "picture in picture alt sharp".
+  static const IconData picture_in_picture_alt_sharp = IconData(0xee7b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">picture_in_picture</i> &#x2014; material icon named "picture in picture outlined".
+  static const IconData picture_in_picture_outlined = IconData(0xe377, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">picture_in_picture</i> &#x2014; material icon named "picture in picture rounded".
+  static const IconData picture_in_picture_rounded = IconData(0xf3aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">picture_in_picture</i> &#x2014; material icon named "picture in picture sharp".
+  static const IconData picture_in_picture_sharp = IconData(0xee7c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pie_chart</i> &#x2014; material icon named "pie chart".
+  static const IconData pie_chart = IconData(0xe92e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pie_chart_outline</i> &#x2014; material icon named "pie chart outline outlined".
+  static const IconData pie_chart_outline_outlined = IconData(0xe378, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pie_chart_outline</i> &#x2014; material icon named "pie chart outline rounded".
+  static const IconData pie_chart_outline_rounded = IconData(0xf3ab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pie_chart_outline</i> &#x2014; material icon named "pie chart outline sharp".
+  static const IconData pie_chart_outline_sharp = IconData(0xee7d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pie_chart</i> &#x2014; material icon named "pie chart outlined".
+  static const IconData pie_chart_outlined = IconData(0xe379, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pie_chart</i> &#x2014; material icon named "pie chart rounded".
+  static const IconData pie_chart_rounded = IconData(0xf3ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pie_chart</i> &#x2014; material icon named "pie chart sharp".
+  static const IconData pie_chart_sharp = IconData(0xee7e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pin_drop</i> &#x2014; material icon named "pin drop".
+  static const IconData pin_drop = IconData(0xe930, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pin_drop</i> &#x2014; material icon named "pin drop outlined".
+  static const IconData pin_drop_outlined = IconData(0xe37a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pin_drop</i> &#x2014; material icon named "pin drop rounded".
+  static const IconData pin_drop_rounded = IconData(0xf3ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pin_drop</i> &#x2014; material icon named "pin drop sharp".
+  static const IconData pin_drop_sharp = IconData(0xee7f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pivot_table_chart</i> &#x2014; material icon named "pivot table chart".
+  static const IconData pivot_table_chart = IconData(0xe931, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">place</i> &#x2014; material icon named "place".
+  static const IconData place = IconData(0xe932, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">place</i> &#x2014; material icon named "place outlined".
+  static const IconData place_outlined = IconData(0xe37b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">place</i> &#x2014; material icon named "place rounded".
+  static const IconData place_rounded = IconData(0xf3ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">place</i> &#x2014; material icon named "place sharp".
+  static const IconData place_sharp = IconData(0xee80, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">plagiarism</i> &#x2014; material icon named "plagiarism".
+  static const IconData plagiarism = IconData(0xe933, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">plagiarism</i> &#x2014; material icon named "plagiarism outlined".
+  static const IconData plagiarism_outlined = IconData(0xe37c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">plagiarism</i> &#x2014; material icon named "plagiarism rounded".
+  static const IconData plagiarism_rounded = IconData(0xf3af, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">plagiarism</i> &#x2014; material icon named "plagiarism sharp".
+  static const IconData plagiarism_sharp = IconData(0xee81, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">play_arrow</i> &#x2014; material icon named "play arrow".
+  static const IconData play_arrow = IconData(0xe934, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">play_arrow</i> &#x2014; material icon named "play arrow outlined".
+  static const IconData play_arrow_outlined = IconData(0xe37d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">play_arrow</i> &#x2014; material icon named "play arrow rounded".
+  static const IconData play_arrow_rounded = IconData(0xf3b0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">play_arrow</i> &#x2014; material icon named "play arrow sharp".
+  static const IconData play_arrow_sharp = IconData(0xee82, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">play_circle_fill</i> &#x2014; material icon named "play circle fill".
+  static const IconData play_circle_fill = IconData(0xe935, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">play_circle_fill</i> &#x2014; material icon named "play circle fill outlined".
+  static const IconData play_circle_fill_outlined = IconData(0xe37e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">play_circle_fill</i> &#x2014; material icon named "play circle fill rounded".
+  static const IconData play_circle_fill_rounded = IconData(0xf3b1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">play_circle_fill</i> &#x2014; material icon named "play circle fill sharp".
+  static const IconData play_circle_fill_sharp = IconData(0xee83, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">play_circle_filled</i> &#x2014; material icon named "play circle filled".
+  static const IconData play_circle_filled = IconData(0xe935, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">play_circle_filled</i> &#x2014; material icon named "play circle filled outlined".
+  static const IconData play_circle_filled_outlined = IconData(0xe37e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">play_circle_filled</i> &#x2014; material icon named "play circle filled rounded".
+  static const IconData play_circle_filled_rounded = IconData(0xf3b1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">play_circle_filled</i> &#x2014; material icon named "play circle filled sharp".
+  static const IconData play_circle_filled_sharp = IconData(0xee83, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">play_circle_outline</i> &#x2014; material icon named "play circle outline".
+  static const IconData play_circle_outline = IconData(0xe936, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">play_circle_outline</i> &#x2014; material icon named "play circle outline outlined".
+  static const IconData play_circle_outline_outlined = IconData(0xe37f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">play_circle_outline</i> &#x2014; material icon named "play circle outline rounded".
+  static const IconData play_circle_outline_rounded = IconData(0xf3b2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">play_circle_outline</i> &#x2014; material icon named "play circle outline sharp".
+  static const IconData play_circle_outline_sharp = IconData(0xee84, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">play_disabled</i> &#x2014; material icon named "play disabled".
+  static const IconData play_disabled = IconData(0xe937, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">play_for_work</i> &#x2014; material icon named "play for work".
+  static const IconData play_for_work = IconData(0xe938, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">play_for_work</i> &#x2014; material icon named "play for work outlined".
+  static const IconData play_for_work_outlined = IconData(0xe380, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">play_for_work</i> &#x2014; material icon named "play for work rounded".
+  static const IconData play_for_work_rounded = IconData(0xf3b3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">play_for_work</i> &#x2014; material icon named "play for work sharp".
+  static const IconData play_for_work_sharp = IconData(0xee85, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">playlist_add</i> &#x2014; material icon named "playlist add".
+  static const IconData playlist_add = IconData(0xe939, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">playlist_add_check</i> &#x2014; material icon named "playlist add check".
+  static const IconData playlist_add_check = IconData(0xe93a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">playlist_add_check</i> &#x2014; material icon named "playlist add check outlined".
+  static const IconData playlist_add_check_outlined = IconData(0xe381, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">playlist_add_check</i> &#x2014; material icon named "playlist add check rounded".
+  static const IconData playlist_add_check_rounded = IconData(0xf3b4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">playlist_add_check</i> &#x2014; material icon named "playlist add check sharp".
+  static const IconData playlist_add_check_sharp = IconData(0xee86, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">playlist_add</i> &#x2014; material icon named "playlist add outlined".
+  static const IconData playlist_add_outlined = IconData(0xe382, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">playlist_add</i> &#x2014; material icon named "playlist add rounded".
+  static const IconData playlist_add_rounded = IconData(0xf3b5, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">playlist_add</i> &#x2014; material icon named "playlist add sharp".
+  static const IconData playlist_add_sharp = IconData(0xee87, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">playlist_play</i> &#x2014; material icon named "playlist play".
+  static const IconData playlist_play = IconData(0xe93b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">playlist_play</i> &#x2014; material icon named "playlist play outlined".
+  static const IconData playlist_play_outlined = IconData(0xe383, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">playlist_play</i> &#x2014; material icon named "playlist play rounded".
+  static const IconData playlist_play_rounded = IconData(0xf3b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">playlist_play</i> &#x2014; material icon named "playlist play sharp".
+  static const IconData playlist_play_sharp = IconData(0xee88, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">plumbing</i> &#x2014; material icon named "plumbing".
+  static const IconData plumbing = IconData(0xe93c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">plumbing</i> &#x2014; material icon named "plumbing outlined".
+  static const IconData plumbing_outlined = IconData(0xe384, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">plumbing</i> &#x2014; material icon named "plumbing rounded".
+  static const IconData plumbing_rounded = IconData(0xf3b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">plumbing</i> &#x2014; material icon named "plumbing sharp".
+  static const IconData plumbing_sharp = IconData(0xee89, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">plus_one</i> &#x2014; material icon named "plus one".
+  static const IconData plus_one = IconData(0xe93d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">plus_one</i> &#x2014; material icon named "plus one outlined".
+  static const IconData plus_one_outlined = IconData(0xe385, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">plus_one</i> &#x2014; material icon named "plus one rounded".
+  static const IconData plus_one_rounded = IconData(0xf3b8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">plus_one</i> &#x2014; material icon named "plus one sharp".
+  static const IconData plus_one_sharp = IconData(0xee8a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">point_of_sale</i> &#x2014; material icon named "point of sale".
+  static const IconData point_of_sale = IconData(0xe93e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">point_of_sale</i> &#x2014; material icon named "point of sale outlined".
+  static const IconData point_of_sale_outlined = IconData(0xe386, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">point_of_sale</i> &#x2014; material icon named "point of sale rounded".
+  static const IconData point_of_sale_rounded = IconData(0xf3b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">point_of_sale</i> &#x2014; material icon named "point of sale sharp".
+  static const IconData point_of_sale_sharp = IconData(0xee8b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">policy</i> &#x2014; material icon named "policy".
+  static const IconData policy = IconData(0xe93f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">policy</i> &#x2014; material icon named "policy outlined".
+  static const IconData policy_outlined = IconData(0xe387, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">policy</i> &#x2014; material icon named "policy rounded".
+  static const IconData policy_rounded = IconData(0xf3ba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">policy</i> &#x2014; material icon named "policy sharp".
+  static const IconData policy_sharp = IconData(0xee8c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">poll</i> &#x2014; material icon named "poll".
+  static const IconData poll = IconData(0xe940, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">poll</i> &#x2014; material icon named "poll outlined".
+  static const IconData poll_outlined = IconData(0xe388, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">poll</i> &#x2014; material icon named "poll rounded".
+  static const IconData poll_rounded = IconData(0xf3bb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">poll</i> &#x2014; material icon named "poll sharp".
+  static const IconData poll_sharp = IconData(0xee8d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">polymer</i> &#x2014; material icon named "polymer".
+  static const IconData polymer = IconData(0xe941, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">polymer</i> &#x2014; material icon named "polymer outlined".
+  static const IconData polymer_outlined = IconData(0xe389, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">polymer</i> &#x2014; material icon named "polymer rounded".
+  static const IconData polymer_rounded = IconData(0xf3bc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">polymer</i> &#x2014; material icon named "polymer sharp".
+  static const IconData polymer_sharp = IconData(0xee8e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pool</i> &#x2014; material icon named "pool".
+  static const IconData pool = IconData(0xe942, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pool</i> &#x2014; material icon named "pool outlined".
+  static const IconData pool_outlined = IconData(0xe38a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pool</i> &#x2014; material icon named "pool rounded".
+  static const IconData pool_rounded = IconData(0xf3bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pool</i> &#x2014; material icon named "pool sharp".
+  static const IconData pool_sharp = IconData(0xee8f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">portable_wifi_off</i> &#x2014; material icon named "portable wifi off".
+  static const IconData portable_wifi_off = IconData(0xe943, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">portable_wifi_off</i> &#x2014; material icon named "portable wifi off outlined".
+  static const IconData portable_wifi_off_outlined = IconData(0xe38b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">portable_wifi_off</i> &#x2014; material icon named "portable wifi off rounded".
+  static const IconData portable_wifi_off_rounded = IconData(0xf3be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">portable_wifi_off</i> &#x2014; material icon named "portable wifi off sharp".
+  static const IconData portable_wifi_off_sharp = IconData(0xee90, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">portrait</i> &#x2014; material icon named "portrait".
+  static const IconData portrait = IconData(0xe944, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">portrait</i> &#x2014; material icon named "portrait outlined".
+  static const IconData portrait_outlined = IconData(0xe38c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">portrait</i> &#x2014; material icon named "portrait rounded".
+  static const IconData portrait_rounded = IconData(0xf3bf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">portrait</i> &#x2014; material icon named "portrait sharp".
+  static const IconData portrait_sharp = IconData(0xee91, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">post_add</i> &#x2014; material icon named "post add".
+  static const IconData post_add = IconData(0xe945, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">post_add</i> &#x2014; material icon named "post add outlined".
+  static const IconData post_add_outlined = IconData(0xe38d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">post_add</i> &#x2014; material icon named "post add rounded".
+  static const IconData post_add_rounded = IconData(0xf3c0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">post_add</i> &#x2014; material icon named "post add sharp".
+  static const IconData post_add_sharp = IconData(0xee92, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">power</i> &#x2014; material icon named "power".
+  static const IconData power = IconData(0xe946, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">power_input</i> &#x2014; material icon named "power input".
+  static const IconData power_input = IconData(0xe947, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">power_input</i> &#x2014; material icon named "power input outlined".
+  static const IconData power_input_outlined = IconData(0xe38e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">power_input</i> &#x2014; material icon named "power input rounded".
+  static const IconData power_input_rounded = IconData(0xf3c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">power_input</i> &#x2014; material icon named "power input sharp".
+  static const IconData power_input_sharp = IconData(0xee93, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">power_off</i> &#x2014; material icon named "power off".
+  static const IconData power_off = IconData(0xe948, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">power_off</i> &#x2014; material icon named "power off outlined".
+  static const IconData power_off_outlined = IconData(0xe38f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">power_off</i> &#x2014; material icon named "power off rounded".
+  static const IconData power_off_rounded = IconData(0xf3c2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">power_off</i> &#x2014; material icon named "power off sharp".
+  static const IconData power_off_sharp = IconData(0xee94, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">power</i> &#x2014; material icon named "power outlined".
+  static const IconData power_outlined = IconData(0xe390, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">power</i> &#x2014; material icon named "power rounded".
+  static const IconData power_rounded = IconData(0xf3c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">power_settings_new</i> &#x2014; material icon named "power settings new".
+  static const IconData power_settings_new = IconData(0xe949, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">power_settings_new</i> &#x2014; material icon named "power settings new outlined".
+  static const IconData power_settings_new_outlined = IconData(0xe391, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">power_settings_new</i> &#x2014; material icon named "power settings new rounded".
+  static const IconData power_settings_new_rounded = IconData(0xf3c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">power_settings_new</i> &#x2014; material icon named "power settings new sharp".
+  static const IconData power_settings_new_sharp = IconData(0xee95, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">power</i> &#x2014; material icon named "power sharp".
+  static const IconData power_sharp = IconData(0xee96, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">precision_manufacturing</i> &#x2014; material icon named "precision manufacturing outlined".
+  static const IconData precision_manufacturing_outlined = IconData(0xe392, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">precision_manufacturing</i> &#x2014; material icon named "precision manufacturing rounded".
+  static const IconData precision_manufacturing_rounded = IconData(0xf3c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">precision_manufacturing</i> &#x2014; material icon named "precision manufacturing sharp".
+  static const IconData precision_manufacturing_sharp = IconData(0xee97, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">pregnant_woman</i> &#x2014; material icon named "pregnant woman".
+  static const IconData pregnant_woman = IconData(0xe94a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">pregnant_woman</i> &#x2014; material icon named "pregnant woman outlined".
+  static const IconData pregnant_woman_outlined = IconData(0xe393, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">pregnant_woman</i> &#x2014; material icon named "pregnant woman rounded".
+  static const IconData pregnant_woman_rounded = IconData(0xf3c6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">pregnant_woman</i> &#x2014; material icon named "pregnant woman sharp".
+  static const IconData pregnant_woman_sharp = IconData(0xee98, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">present_to_all</i> &#x2014; material icon named "present to all".
+  static const IconData present_to_all = IconData(0xe94b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">present_to_all</i> &#x2014; material icon named "present to all outlined".
+  static const IconData present_to_all_outlined = IconData(0xe394, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">present_to_all</i> &#x2014; material icon named "present to all rounded".
+  static const IconData present_to_all_rounded = IconData(0xf3c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">present_to_all</i> &#x2014; material icon named "present to all sharp".
+  static const IconData present_to_all_sharp = IconData(0xee99, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">preview</i> &#x2014; material icon named "preview".
+  static const IconData preview = IconData(0xe94c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">preview</i> &#x2014; material icon named "preview outlined".
+  static const IconData preview_outlined = IconData(0xe395, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">preview</i> &#x2014; material icon named "preview rounded".
+  static const IconData preview_rounded = IconData(0xf3c8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">preview</i> &#x2014; material icon named "preview sharp".
+  static const IconData preview_sharp = IconData(0xee9a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">print</i> &#x2014; material icon named "print".
+  static const IconData print = IconData(0xe94d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">print_disabled</i> &#x2014; material icon named "print disabled".
+  static const IconData print_disabled = IconData(0xe94e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">print_disabled</i> &#x2014; material icon named "print disabled outlined".
+  static const IconData print_disabled_outlined = IconData(0xe396, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">print_disabled</i> &#x2014; material icon named "print disabled rounded".
+  static const IconData print_disabled_rounded = IconData(0xf3c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">print_disabled</i> &#x2014; material icon named "print disabled sharp".
+  static const IconData print_disabled_sharp = IconData(0xee9b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">print</i> &#x2014; material icon named "print outlined".
+  static const IconData print_outlined = IconData(0xe397, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">print</i> &#x2014; material icon named "print rounded".
+  static const IconData print_rounded = IconData(0xf3ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">print</i> &#x2014; material icon named "print sharp".
+  static const IconData print_sharp = IconData(0xee9c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">priority_high</i> &#x2014; material icon named "priority high".
+  static const IconData priority_high = IconData(0xe94f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">priority_high</i> &#x2014; material icon named "priority high outlined".
+  static const IconData priority_high_outlined = IconData(0xe398, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">priority_high</i> &#x2014; material icon named "priority high rounded".
+  static const IconData priority_high_rounded = IconData(0xf3cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">priority_high</i> &#x2014; material icon named "priority high sharp".
+  static const IconData priority_high_sharp = IconData(0xee9d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">privacy_tip</i> &#x2014; material icon named "privacy tip".
+  static const IconData privacy_tip = IconData(0xe950, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">privacy_tip</i> &#x2014; material icon named "privacy tip outlined".
+  static const IconData privacy_tip_outlined = IconData(0xe399, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">privacy_tip</i> &#x2014; material icon named "privacy tip rounded".
+  static const IconData privacy_tip_rounded = IconData(0xf3cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">privacy_tip</i> &#x2014; material icon named "privacy tip sharp".
+  static const IconData privacy_tip_sharp = IconData(0xee9e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">psychology</i> &#x2014; material icon named "psychology".
+  static const IconData psychology = IconData(0xe951, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">psychology</i> &#x2014; material icon named "psychology outlined".
+  static const IconData psychology_outlined = IconData(0xe39a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">psychology</i> &#x2014; material icon named "psychology rounded".
+  static const IconData psychology_rounded = IconData(0xf3cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">psychology</i> &#x2014; material icon named "psychology sharp".
+  static const IconData psychology_sharp = IconData(0xee9f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">public</i> &#x2014; material icon named "public".
+  static const IconData public = IconData(0xe952, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">public_off</i> &#x2014; material icon named "public off".
+  static const IconData public_off = IconData(0xe953, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">public_off</i> &#x2014; material icon named "public off outlined".
+  static const IconData public_off_outlined = IconData(0xe39b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">public_off</i> &#x2014; material icon named "public off rounded".
+  static const IconData public_off_rounded = IconData(0xf3ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">public_off</i> &#x2014; material icon named "public off sharp".
+  static const IconData public_off_sharp = IconData(0xeea0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">public</i> &#x2014; material icon named "public outlined".
+  static const IconData public_outlined = IconData(0xe39c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">public</i> &#x2014; material icon named "public rounded".
+  static const IconData public_rounded = IconData(0xf3cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">public</i> &#x2014; material icon named "public sharp".
+  static const IconData public_sharp = IconData(0xeea1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">publish</i> &#x2014; material icon named "publish".
+  static const IconData publish = IconData(0xe954, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">publish</i> &#x2014; material icon named "publish outlined".
+  static const IconData publish_outlined = IconData(0xe39d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">publish</i> &#x2014; material icon named "publish rounded".
+  static const IconData publish_rounded = IconData(0xf3d0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">publish</i> &#x2014; material icon named "publish sharp".
+  static const IconData publish_sharp = IconData(0xeea2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">push_pin</i> &#x2014; material icon named "push pin".
+  static const IconData push_pin = IconData(0xe955, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">push_pin</i> &#x2014; material icon named "push pin outlined".
+  static const IconData push_pin_outlined = IconData(0xe39e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">push_pin</i> &#x2014; material icon named "push pin rounded".
+  static const IconData push_pin_rounded = IconData(0xf3d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">push_pin</i> &#x2014; material icon named "push pin sharp".
+  static const IconData push_pin_sharp = IconData(0xeea3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">qr_code</i> &#x2014; material icon named "qr code".
+  static const IconData qr_code = IconData(0xe956, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">qr_code</i> &#x2014; material icon named "qr code outlined".
+  static const IconData qr_code_outlined = IconData(0xe39f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">qr_code</i> &#x2014; material icon named "qr code rounded".
+  static const IconData qr_code_rounded = IconData(0xf3d2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">qr_code_scanner</i> &#x2014; material icon named "qr code scanner".
+  static const IconData qr_code_scanner = IconData(0xe957, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">qr_code_scanner</i> &#x2014; material icon named "qr code scanner outlined".
+  static const IconData qr_code_scanner_outlined = IconData(0xe3a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">qr_code_scanner</i> &#x2014; material icon named "qr code scanner rounded".
+  static const IconData qr_code_scanner_rounded = IconData(0xf3d3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">qr_code_scanner</i> &#x2014; material icon named "qr code scanner sharp".
+  static const IconData qr_code_scanner_sharp = IconData(0xeea4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">qr_code</i> &#x2014; material icon named "qr code sharp".
+  static const IconData qr_code_sharp = IconData(0xeea5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">query_builder</i> &#x2014; material icon named "query builder".
+  static const IconData query_builder = IconData(0xe958, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">query_builder</i> &#x2014; material icon named "query builder outlined".
+  static const IconData query_builder_outlined = IconData(0xe3a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">query_builder</i> &#x2014; material icon named "query builder rounded".
+  static const IconData query_builder_rounded = IconData(0xf3d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">query_builder</i> &#x2014; material icon named "query builder sharp".
+  static const IconData query_builder_sharp = IconData(0xeea6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">question_answer</i> &#x2014; material icon named "question answer".
+  static const IconData question_answer = IconData(0xe959, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">question_answer</i> &#x2014; material icon named "question answer outlined".
+  static const IconData question_answer_outlined = IconData(0xe3a2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">question_answer</i> &#x2014; material icon named "question answer rounded".
+  static const IconData question_answer_rounded = IconData(0xf3d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">question_answer</i> &#x2014; material icon named "question answer sharp".
+  static const IconData question_answer_sharp = IconData(0xeea7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">queue</i> &#x2014; material icon named "queue".
+  static const IconData queue = IconData(0xe95a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">queue_music</i> &#x2014; material icon named "queue music".
+  static const IconData queue_music = IconData(0xe95b, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">queue_music</i> &#x2014; material icon named "queue music outlined".
+  static const IconData queue_music_outlined = IconData(0xe3a3, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">queue_music</i> &#x2014; material icon named "queue music rounded".
+  static const IconData queue_music_rounded = IconData(0xf3d6, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">queue_music</i> &#x2014; material icon named "queue music sharp".
+  static const IconData queue_music_sharp = IconData(0xeea8, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">queue</i> &#x2014; material icon named "queue outlined".
+  static const IconData queue_outlined = IconData(0xe3a4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">queue_play_next</i> &#x2014; material icon named "queue play next".
+  static const IconData queue_play_next = IconData(0xe95c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">queue_play_next</i> &#x2014; material icon named "queue play next outlined".
+  static const IconData queue_play_next_outlined = IconData(0xe3a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">queue_play_next</i> &#x2014; material icon named "queue play next rounded".
+  static const IconData queue_play_next_rounded = IconData(0xf3d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">queue_play_next</i> &#x2014; material icon named "queue play next sharp".
+  static const IconData queue_play_next_sharp = IconData(0xeea9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">queue</i> &#x2014; material icon named "queue rounded".
+  static const IconData queue_rounded = IconData(0xf3d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">queue</i> &#x2014; material icon named "queue sharp".
+  static const IconData queue_sharp = IconData(0xeeaa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">quick_contacts_dialer</i> &#x2014; material icon named "quick contacts dialer".
+  static const IconData quick_contacts_dialer = IconData(0xe67d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">quick_contacts_dialer</i> &#x2014; material icon named "quick contacts dialer outlined".
+  static const IconData quick_contacts_dialer_outlined = IconData(0xe108, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">quick_contacts_dialer</i> &#x2014; material icon named "quick contacts dialer rounded".
+  static const IconData quick_contacts_dialer_rounded = IconData(0xf136, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">quick_contacts_dialer</i> &#x2014; material icon named "quick contacts dialer sharp".
+  static const IconData quick_contacts_dialer_sharp = IconData(0xec08, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">quick_contacts_mail</i> &#x2014; material icon named "quick contacts mail".
+  static const IconData quick_contacts_mail = IconData(0xe67b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">quick_contacts_mail</i> &#x2014; material icon named "quick contacts mail outlined".
+  static const IconData quick_contacts_mail_outlined = IconData(0xe106, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">quick_contacts_mail</i> &#x2014; material icon named "quick contacts mail rounded".
+  static const IconData quick_contacts_mail_rounded = IconData(0xf134, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">quick_contacts_mail</i> &#x2014; material icon named "quick contacts mail sharp".
+  static const IconData quick_contacts_mail_sharp = IconData(0xec06, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">quickreply</i> &#x2014; material icon named "quickreply".
+  static const IconData quickreply = IconData(0xe95d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">quickreply</i> &#x2014; material icon named "quickreply outlined".
+  static const IconData quickreply_outlined = IconData(0xe3a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">quickreply</i> &#x2014; material icon named "quickreply rounded".
+  static const IconData quickreply_rounded = IconData(0xf3d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">quickreply</i> &#x2014; material icon named "quickreply sharp".
+  static const IconData quickreply_sharp = IconData(0xeeab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">radio</i> &#x2014; material icon named "radio".
+  static const IconData radio = IconData(0xe95e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">radio_button_checked</i> &#x2014; material icon named "radio button checked".
+  static const IconData radio_button_checked = IconData(0xe95f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">radio_button_checked</i> &#x2014; material icon named "radio button checked outlined".
+  static const IconData radio_button_checked_outlined = IconData(0xe3a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">radio_button_checked</i> &#x2014; material icon named "radio button checked rounded".
+  static const IconData radio_button_checked_rounded = IconData(0xf3da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">radio_button_checked</i> &#x2014; material icon named "radio button checked sharp".
+  static const IconData radio_button_checked_sharp = IconData(0xeeac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">radio_button_off</i> &#x2014; material icon named "radio button off".
+  static const IconData radio_button_off = IconData(0xe960, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">radio_button_off</i> &#x2014; material icon named "radio button off outlined".
+  static const IconData radio_button_off_outlined = IconData(0xe3a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">radio_button_off</i> &#x2014; material icon named "radio button off rounded".
+  static const IconData radio_button_off_rounded = IconData(0xf3db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">radio_button_off</i> &#x2014; material icon named "radio button off sharp".
+  static const IconData radio_button_off_sharp = IconData(0xeead, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">radio_button_on</i> &#x2014; material icon named "radio button on".
+  static const IconData radio_button_on = IconData(0xe95f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">radio_button_on</i> &#x2014; material icon named "radio button on outlined".
+  static const IconData radio_button_on_outlined = IconData(0xe3a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">radio_button_on</i> &#x2014; material icon named "radio button on rounded".
+  static const IconData radio_button_on_rounded = IconData(0xf3da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">radio_button_on</i> &#x2014; material icon named "radio button on sharp".
+  static const IconData radio_button_on_sharp = IconData(0xeeac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">radio_button_unchecked</i> &#x2014; material icon named "radio button unchecked".
+  static const IconData radio_button_unchecked = IconData(0xe960, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">radio_button_unchecked</i> &#x2014; material icon named "radio button unchecked outlined".
+  static const IconData radio_button_unchecked_outlined = IconData(0xe3a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">radio_button_unchecked</i> &#x2014; material icon named "radio button unchecked rounded".
+  static const IconData radio_button_unchecked_rounded = IconData(0xf3db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">radio_button_unchecked</i> &#x2014; material icon named "radio button unchecked sharp".
+  static const IconData radio_button_unchecked_sharp = IconData(0xeead, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">radio</i> &#x2014; material icon named "radio outlined".
+  static const IconData radio_outlined = IconData(0xe3a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">radio</i> &#x2014; material icon named "radio rounded".
+  static const IconData radio_rounded = IconData(0xf3dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">radio</i> &#x2014; material icon named "radio sharp".
+  static const IconData radio_sharp = IconData(0xeeae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">railway_alert</i> &#x2014; material icon named "railway alert".
+  static const IconData railway_alert = IconData(0xe961, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">ramen_dining</i> &#x2014; material icon named "ramen dining".
+  static const IconData ramen_dining = IconData(0xe962, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">rate_review</i> &#x2014; material icon named "rate review".
+  static const IconData rate_review = IconData(0xe963, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">rate_review</i> &#x2014; material icon named "rate review outlined".
+  static const IconData rate_review_outlined = IconData(0xe3aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">rate_review</i> &#x2014; material icon named "rate review rounded".
+  static const IconData rate_review_rounded = IconData(0xf3dd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">rate_review</i> &#x2014; material icon named "rate review sharp".
+  static const IconData rate_review_sharp = IconData(0xeeaf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">read_more</i> &#x2014; material icon named "read more".
+  static const IconData read_more = IconData(0xe964, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">read_more</i> &#x2014; material icon named "read more outlined".
+  static const IconData read_more_outlined = IconData(0xe3ab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">read_more</i> &#x2014; material icon named "read more rounded".
+  static const IconData read_more_rounded = IconData(0xf3de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">read_more</i> &#x2014; material icon named "read more sharp".
+  static const IconData read_more_sharp = IconData(0xeeb0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">receipt</i> &#x2014; material icon named "receipt".
+  static const IconData receipt = IconData(0xe965, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">receipt_long</i> &#x2014; material icon named "receipt long".
+  static const IconData receipt_long = IconData(0xe966, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">receipt_long</i> &#x2014; material icon named "receipt long outlined".
+  static const IconData receipt_long_outlined = IconData(0xe3ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">receipt_long</i> &#x2014; material icon named "receipt long rounded".
+  static const IconData receipt_long_rounded = IconData(0xf3df, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">receipt_long</i> &#x2014; material icon named "receipt long sharp".
+  static const IconData receipt_long_sharp = IconData(0xeeb1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">receipt</i> &#x2014; material icon named "receipt outlined".
+  static const IconData receipt_outlined = IconData(0xe3ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">receipt</i> &#x2014; material icon named "receipt rounded".
+  static const IconData receipt_rounded = IconData(0xf3e0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">receipt</i> &#x2014; material icon named "receipt sharp".
+  static const IconData receipt_sharp = IconData(0xeeb2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">recent_actors</i> &#x2014; material icon named "recent actors".
+  static const IconData recent_actors = IconData(0xe967, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">recent_actors</i> &#x2014; material icon named "recent actors outlined".
+  static const IconData recent_actors_outlined = IconData(0xe3ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">recent_actors</i> &#x2014; material icon named "recent actors rounded".
+  static const IconData recent_actors_rounded = IconData(0xf3e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">recent_actors</i> &#x2014; material icon named "recent actors sharp".
+  static const IconData recent_actors_sharp = IconData(0xeeb3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">recommend</i> &#x2014; material icon named "recommend".
+  static const IconData recommend = IconData(0xe968, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">record_voice_over</i> &#x2014; material icon named "record voice over".
+  static const IconData record_voice_over = IconData(0xe969, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">record_voice_over</i> &#x2014; material icon named "record voice over outlined".
+  static const IconData record_voice_over_outlined = IconData(0xe3af, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">record_voice_over</i> &#x2014; material icon named "record voice over rounded".
+  static const IconData record_voice_over_rounded = IconData(0xf3e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">record_voice_over</i> &#x2014; material icon named "record voice over sharp".
+  static const IconData record_voice_over_sharp = IconData(0xeeb4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">redeem</i> &#x2014; material icon named "redeem".
+  static const IconData redeem = IconData(0xe96a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">redeem</i> &#x2014; material icon named "redeem outlined".
+  static const IconData redeem_outlined = IconData(0xe3b0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">redeem</i> &#x2014; material icon named "redeem rounded".
+  static const IconData redeem_rounded = IconData(0xf3e3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">redeem</i> &#x2014; material icon named "redeem sharp".
+  static const IconData redeem_sharp = IconData(0xeeb5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">redo</i> &#x2014; material icon named "redo".
+  static const IconData redo = IconData(0xe96b, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">redo</i> &#x2014; material icon named "redo outlined".
+  static const IconData redo_outlined = IconData(0xe3b1, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">redo</i> &#x2014; material icon named "redo rounded".
+  static const IconData redo_rounded = IconData(0xf3e4, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">redo</i> &#x2014; material icon named "redo sharp".
+  static const IconData redo_sharp = IconData(0xeeb6, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">reduce_capacity</i> &#x2014; material icon named "reduce capacity".
+  static const IconData reduce_capacity = IconData(0xe96c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">reduce_capacity</i> &#x2014; material icon named "reduce capacity outlined".
+  static const IconData reduce_capacity_outlined = IconData(0xe3b2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">reduce_capacity</i> &#x2014; material icon named "reduce capacity rounded".
+  static const IconData reduce_capacity_rounded = IconData(0xf3e5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">reduce_capacity</i> &#x2014; material icon named "reduce capacity sharp".
+  static const IconData reduce_capacity_sharp = IconData(0xeeb7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">refresh</i> &#x2014; material icon named "refresh".
+  static const IconData refresh = IconData(0xe96d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">refresh</i> &#x2014; material icon named "refresh outlined".
+  static const IconData refresh_outlined = IconData(0xe3b3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">refresh</i> &#x2014; material icon named "refresh rounded".
+  static const IconData refresh_rounded = IconData(0xf3e6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">refresh</i> &#x2014; material icon named "refresh sharp".
+  static const IconData refresh_sharp = IconData(0xeeb8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">remove</i> &#x2014; material icon named "remove".
+  static const IconData remove = IconData(0xe96e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">remove_circle</i> &#x2014; material icon named "remove circle".
+  static const IconData remove_circle = IconData(0xe96f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">remove_circle_outline</i> &#x2014; material icon named "remove circle outline".
+  static const IconData remove_circle_outline = IconData(0xe970, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">remove_circle_outline</i> &#x2014; material icon named "remove circle outline outlined".
+  static const IconData remove_circle_outline_outlined = IconData(0xe3b4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">remove_circle_outline</i> &#x2014; material icon named "remove circle outline rounded".
+  static const IconData remove_circle_outline_rounded = IconData(0xf3e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">remove_circle_outline</i> &#x2014; material icon named "remove circle outline sharp".
+  static const IconData remove_circle_outline_sharp = IconData(0xeeb9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">remove_circle</i> &#x2014; material icon named "remove circle outlined".
+  static const IconData remove_circle_outlined = IconData(0xe3b5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">remove_circle</i> &#x2014; material icon named "remove circle rounded".
+  static const IconData remove_circle_rounded = IconData(0xf3e8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">remove_circle</i> &#x2014; material icon named "remove circle sharp".
+  static const IconData remove_circle_sharp = IconData(0xeeba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">remove_done</i> &#x2014; material icon named "remove done".
+  static const IconData remove_done = IconData(0xe971, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">remove_from_queue</i> &#x2014; material icon named "remove from queue".
+  static const IconData remove_from_queue = IconData(0xe972, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">remove_from_queue</i> &#x2014; material icon named "remove from queue outlined".
+  static const IconData remove_from_queue_outlined = IconData(0xe3b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">remove_from_queue</i> &#x2014; material icon named "remove from queue rounded".
+  static const IconData remove_from_queue_rounded = IconData(0xf3e9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">remove_from_queue</i> &#x2014; material icon named "remove from queue sharp".
+  static const IconData remove_from_queue_sharp = IconData(0xeebb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">remove_moderator</i> &#x2014; material icon named "remove moderator".
+  static const IconData remove_moderator = IconData(0xe973, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">remove</i> &#x2014; material icon named "remove outlined".
+  static const IconData remove_outlined = IconData(0xe3b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">remove_red_eye</i> &#x2014; material icon named "remove red eye".
+  static const IconData remove_red_eye = IconData(0xe974, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">remove_red_eye</i> &#x2014; material icon named "remove red eye outlined".
+  static const IconData remove_red_eye_outlined = IconData(0xe3b8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">remove_red_eye</i> &#x2014; material icon named "remove red eye rounded".
+  static const IconData remove_red_eye_rounded = IconData(0xf3ea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">remove_red_eye</i> &#x2014; material icon named "remove red eye sharp".
+  static const IconData remove_red_eye_sharp = IconData(0xeebc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">remove</i> &#x2014; material icon named "remove rounded".
+  static const IconData remove_rounded = IconData(0xf3eb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">remove</i> &#x2014; material icon named "remove sharp".
+  static const IconData remove_sharp = IconData(0xeebd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">remove_shopping_cart</i> &#x2014; material icon named "remove shopping cart".
+  static const IconData remove_shopping_cart = IconData(0xe975, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">remove_shopping_cart</i> &#x2014; material icon named "remove shopping cart outlined".
+  static const IconData remove_shopping_cart_outlined = IconData(0xe3b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">remove_shopping_cart</i> &#x2014; material icon named "remove shopping cart rounded".
+  static const IconData remove_shopping_cart_rounded = IconData(0xf3ec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">remove_shopping_cart</i> &#x2014; material icon named "remove shopping cart sharp".
+  static const IconData remove_shopping_cart_sharp = IconData(0xeebe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">reorder</i> &#x2014; material icon named "reorder".
+  static const IconData reorder = IconData(0xe976, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">reorder</i> &#x2014; material icon named "reorder outlined".
+  static const IconData reorder_outlined = IconData(0xe3ba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">reorder</i> &#x2014; material icon named "reorder rounded".
+  static const IconData reorder_rounded = IconData(0xf3ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">reorder</i> &#x2014; material icon named "reorder sharp".
+  static const IconData reorder_sharp = IconData(0xeebf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">repeat</i> &#x2014; material icon named "repeat".
+  static const IconData repeat = IconData(0xe977, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">repeat_on</i> &#x2014; material icon named "repeat on".
+  static const IconData repeat_on = IconData(0xe978, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">repeat_one</i> &#x2014; material icon named "repeat one".
+  static const IconData repeat_one = IconData(0xe979, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">repeat_one_on</i> &#x2014; material icon named "repeat one on".
+  static const IconData repeat_one_on = IconData(0xe97a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">repeat_one</i> &#x2014; material icon named "repeat one outlined".
+  static const IconData repeat_one_outlined = IconData(0xe3bb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">repeat_one</i> &#x2014; material icon named "repeat one rounded".
+  static const IconData repeat_one_rounded = IconData(0xf3ee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">repeat_one</i> &#x2014; material icon named "repeat one sharp".
+  static const IconData repeat_one_sharp = IconData(0xeec0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">repeat</i> &#x2014; material icon named "repeat outlined".
+  static const IconData repeat_outlined = IconData(0xe3bc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">repeat</i> &#x2014; material icon named "repeat rounded".
+  static const IconData repeat_rounded = IconData(0xf3ef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">repeat</i> &#x2014; material icon named "repeat sharp".
+  static const IconData repeat_sharp = IconData(0xeec1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">replay</i> &#x2014; material icon named "replay".
+  static const IconData replay = IconData(0xe97b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">replay_10</i> &#x2014; material icon named "replay 10".
+  static const IconData replay_10 = IconData(0xe97c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">replay_10</i> &#x2014; material icon named "replay 10 outlined".
+  static const IconData replay_10_outlined = IconData(0xe3bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">replay_10</i> &#x2014; material icon named "replay 10 rounded".
+  static const IconData replay_10_rounded = IconData(0xf3f0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">replay_10</i> &#x2014; material icon named "replay 10 sharp".
+  static const IconData replay_10_sharp = IconData(0xeec2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">replay_30</i> &#x2014; material icon named "replay 30".
+  static const IconData replay_30 = IconData(0xe97d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">replay_30</i> &#x2014; material icon named "replay 30 outlined".
+  static const IconData replay_30_outlined = IconData(0xe3be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">replay_30</i> &#x2014; material icon named "replay 30 rounded".
+  static const IconData replay_30_rounded = IconData(0xf3f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">replay_30</i> &#x2014; material icon named "replay 30 sharp".
+  static const IconData replay_30_sharp = IconData(0xeec3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">replay_5</i> &#x2014; material icon named "replay 5".
+  static const IconData replay_5 = IconData(0xe97e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">replay_5</i> &#x2014; material icon named "replay 5 outlined".
+  static const IconData replay_5_outlined = IconData(0xe3bf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">replay_5</i> &#x2014; material icon named "replay 5 rounded".
+  static const IconData replay_5_rounded = IconData(0xf3f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">replay_5</i> &#x2014; material icon named "replay 5 sharp".
+  static const IconData replay_5_sharp = IconData(0xeec4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">replay_circle_filled</i> &#x2014; material icon named "replay circle filled".
+  static const IconData replay_circle_filled = IconData(0xe97f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">replay</i> &#x2014; material icon named "replay outlined".
+  static const IconData replay_outlined = IconData(0xe3c0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">replay</i> &#x2014; material icon named "replay rounded".
+  static const IconData replay_rounded = IconData(0xf3f3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">replay</i> &#x2014; material icon named "replay sharp".
+  static const IconData replay_sharp = IconData(0xeec5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">reply</i> &#x2014; material icon named "reply".
+  static const IconData reply = IconData(0xe980, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">reply_all</i> &#x2014; material icon named "reply all".
+  static const IconData reply_all = IconData(0xe981, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">reply_all</i> &#x2014; material icon named "reply all outlined".
+  static const IconData reply_all_outlined = IconData(0xe3c1, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">reply_all</i> &#x2014; material icon named "reply all rounded".
+  static const IconData reply_all_rounded = IconData(0xf3f4, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">reply_all</i> &#x2014; material icon named "reply all sharp".
+  static const IconData reply_all_sharp = IconData(0xeec6, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">reply</i> &#x2014; material icon named "reply outlined".
+  static const IconData reply_outlined = IconData(0xe3c2, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">reply</i> &#x2014; material icon named "reply rounded".
+  static const IconData reply_rounded = IconData(0xf3f5, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">reply</i> &#x2014; material icon named "reply sharp".
+  static const IconData reply_sharp = IconData(0xeec7, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">report</i> &#x2014; material icon named "report".
+  static const IconData report = IconData(0xe982, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">report_gmailerrorred</i> &#x2014; material icon named "report gmailerrorred outlined".
+  static const IconData report_gmailerrorred_outlined = IconData(0xe3c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">report_gmailerrorred</i> &#x2014; material icon named "report gmailerrorred rounded".
+  static const IconData report_gmailerrorred_rounded = IconData(0xf3f6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">report_gmailerrorred</i> &#x2014; material icon named "report gmailerrorred sharp".
+  static const IconData report_gmailerrorred_sharp = IconData(0xeec8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">report_off</i> &#x2014; material icon named "report off".
+  static const IconData report_off = IconData(0xe983, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">report_off</i> &#x2014; material icon named "report off outlined".
+  static const IconData report_off_outlined = IconData(0xe3c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">report_off</i> &#x2014; material icon named "report off rounded".
+  static const IconData report_off_rounded = IconData(0xf3f7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">report_off</i> &#x2014; material icon named "report off sharp".
+  static const IconData report_off_sharp = IconData(0xeec9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">report</i> &#x2014; material icon named "report outlined".
+  static const IconData report_outlined = IconData(0xe3c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">report_problem</i> &#x2014; material icon named "report problem".
+  static const IconData report_problem = IconData(0xe984, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">report_problem</i> &#x2014; material icon named "report problem outlined".
+  static const IconData report_problem_outlined = IconData(0xe3c6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">report_problem</i> &#x2014; material icon named "report problem rounded".
+  static const IconData report_problem_rounded = IconData(0xf3f8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">report_problem</i> &#x2014; material icon named "report problem sharp".
+  static const IconData report_problem_sharp = IconData(0xeeca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">report</i> &#x2014; material icon named "report rounded".
+  static const IconData report_rounded = IconData(0xf3f9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">report</i> &#x2014; material icon named "report sharp".
+  static const IconData report_sharp = IconData(0xeecb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">request_page</i> &#x2014; material icon named "request page".
+  static const IconData request_page = IconData(0xe985, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">request_page</i> &#x2014; material icon named "request page outlined".
+  static const IconData request_page_outlined = IconData(0xe3c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">request_page</i> &#x2014; material icon named "request page rounded".
+  static const IconData request_page_rounded = IconData(0xf3fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">request_page</i> &#x2014; material icon named "request page sharp".
+  static const IconData request_page_sharp = IconData(0xeecc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">request_quote</i> &#x2014; material icon named "request quote".
+  static const IconData request_quote = IconData(0xe986, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">request_quote</i> &#x2014; material icon named "request quote outlined".
+  static const IconData request_quote_outlined = IconData(0xe3c8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">request_quote</i> &#x2014; material icon named "request quote rounded".
+  static const IconData request_quote_rounded = IconData(0xf3fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">request_quote</i> &#x2014; material icon named "request quote sharp".
+  static const IconData request_quote_sharp = IconData(0xeecd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">reset_tv</i> &#x2014; material icon named "reset tv".
+  static const IconData reset_tv = IconData(0xe987, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">restaurant</i> &#x2014; material icon named "restaurant".
+  static const IconData restaurant = IconData(0xe988, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">restaurant_menu</i> &#x2014; material icon named "restaurant menu".
+  static const IconData restaurant_menu = IconData(0xe989, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">restaurant_menu</i> &#x2014; material icon named "restaurant menu outlined".
+  static const IconData restaurant_menu_outlined = IconData(0xe3c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">restaurant_menu</i> &#x2014; material icon named "restaurant menu rounded".
+  static const IconData restaurant_menu_rounded = IconData(0xf3fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">restaurant_menu</i> &#x2014; material icon named "restaurant menu sharp".
+  static const IconData restaurant_menu_sharp = IconData(0xeece, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">restaurant</i> &#x2014; material icon named "restaurant outlined".
+  static const IconData restaurant_outlined = IconData(0xe3ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">restaurant</i> &#x2014; material icon named "restaurant rounded".
+  static const IconData restaurant_rounded = IconData(0xf3fd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">restaurant</i> &#x2014; material icon named "restaurant sharp".
+  static const IconData restaurant_sharp = IconData(0xeecf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">restore</i> &#x2014; material icon named "restore".
+  static const IconData restore = IconData(0xe98a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">restore_from_trash</i> &#x2014; material icon named "restore from trash".
+  static const IconData restore_from_trash = IconData(0xe98b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">restore_from_trash</i> &#x2014; material icon named "restore from trash outlined".
+  static const IconData restore_from_trash_outlined = IconData(0xe3cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">restore_from_trash</i> &#x2014; material icon named "restore from trash rounded".
+  static const IconData restore_from_trash_rounded = IconData(0xf3fe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">restore_from_trash</i> &#x2014; material icon named "restore from trash sharp".
+  static const IconData restore_from_trash_sharp = IconData(0xeed0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">restore</i> &#x2014; material icon named "restore outlined".
+  static const IconData restore_outlined = IconData(0xe3cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">restore_page</i> &#x2014; material icon named "restore page".
+  static const IconData restore_page = IconData(0xe98c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">restore_page</i> &#x2014; material icon named "restore page outlined".
+  static const IconData restore_page_outlined = IconData(0xe3cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">restore_page</i> &#x2014; material icon named "restore page rounded".
+  static const IconData restore_page_rounded = IconData(0xf3ff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">restore_page</i> &#x2014; material icon named "restore page sharp".
+  static const IconData restore_page_sharp = IconData(0xeed1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">restore</i> &#x2014; material icon named "restore rounded".
+  static const IconData restore_rounded = IconData(0xf400, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">restore</i> &#x2014; material icon named "restore sharp".
+  static const IconData restore_sharp = IconData(0xeed2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">rice_bowl</i> &#x2014; material icon named "rice bowl".
+  static const IconData rice_bowl = IconData(0xe98d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">rice_bowl</i> &#x2014; material icon named "rice bowl outlined".
+  static const IconData rice_bowl_outlined = IconData(0xe3ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">rice_bowl</i> &#x2014; material icon named "rice bowl rounded".
+  static const IconData rice_bowl_rounded = IconData(0xf401, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">rice_bowl</i> &#x2014; material icon named "rice bowl sharp".
+  static const IconData rice_bowl_sharp = IconData(0xeed3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">ring_volume</i> &#x2014; material icon named "ring volume".
+  static const IconData ring_volume = IconData(0xe98e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">ring_volume</i> &#x2014; material icon named "ring volume outlined".
+  static const IconData ring_volume_outlined = IconData(0xe3cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">ring_volume</i> &#x2014; material icon named "ring volume rounded".
+  static const IconData ring_volume_rounded = IconData(0xf402, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">ring_volume</i> &#x2014; material icon named "ring volume sharp".
+  static const IconData ring_volume_sharp = IconData(0xeed4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">roofing</i> &#x2014; material icon named "roofing".
+  static const IconData roofing = IconData(0xe98f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">roofing</i> &#x2014; material icon named "roofing outlined".
+  static const IconData roofing_outlined = IconData(0xe3d0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">roofing</i> &#x2014; material icon named "roofing rounded".
+  static const IconData roofing_rounded = IconData(0xf403, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">roofing</i> &#x2014; material icon named "roofing sharp".
+  static const IconData roofing_sharp = IconData(0xeed5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">room</i> &#x2014; material icon named "room".
+  static const IconData room = IconData(0xe990, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">room</i> &#x2014; material icon named "room outlined".
+  static const IconData room_outlined = IconData(0xe3d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">room_preferences</i> &#x2014; material icon named "room preferences".
+  static const IconData room_preferences = IconData(0xe991, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">room_preferences</i> &#x2014; material icon named "room preferences outlined".
+  static const IconData room_preferences_outlined = IconData(0xe3d2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">room_preferences</i> &#x2014; material icon named "room preferences rounded".
+  static const IconData room_preferences_rounded = IconData(0xf404, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">room_preferences</i> &#x2014; material icon named "room preferences sharp".
+  static const IconData room_preferences_sharp = IconData(0xeed6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">room</i> &#x2014; material icon named "room rounded".
+  static const IconData room_rounded = IconData(0xf405, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">room_service</i> &#x2014; material icon named "room service".
+  static const IconData room_service = IconData(0xe992, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">room_service</i> &#x2014; material icon named "room service outlined".
+  static const IconData room_service_outlined = IconData(0xe3d3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">room_service</i> &#x2014; material icon named "room service rounded".
+  static const IconData room_service_rounded = IconData(0xf406, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">room_service</i> &#x2014; material icon named "room service sharp".
+  static const IconData room_service_sharp = IconData(0xeed7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">room</i> &#x2014; material icon named "room sharp".
+  static const IconData room_sharp = IconData(0xeed8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">rotate_90_degrees_ccw</i> &#x2014; material icon named "rotate 90 degrees ccw".
+  static const IconData rotate_90_degrees_ccw = IconData(0xe993, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">rotate_90_degrees_ccw</i> &#x2014; material icon named "rotate 90 degrees ccw outlined".
+  static const IconData rotate_90_degrees_ccw_outlined = IconData(0xe3d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">rotate_90_degrees_ccw</i> &#x2014; material icon named "rotate 90 degrees ccw rounded".
+  static const IconData rotate_90_degrees_ccw_rounded = IconData(0xf407, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">rotate_90_degrees_ccw</i> &#x2014; material icon named "rotate 90 degrees ccw sharp".
+  static const IconData rotate_90_degrees_ccw_sharp = IconData(0xeed9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">rotate_left</i> &#x2014; material icon named "rotate left".
+  static const IconData rotate_left = IconData(0xe994, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">rotate_left</i> &#x2014; material icon named "rotate left outlined".
+  static const IconData rotate_left_outlined = IconData(0xe3d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">rotate_left</i> &#x2014; material icon named "rotate left rounded".
+  static const IconData rotate_left_rounded = IconData(0xf408, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">rotate_left</i> &#x2014; material icon named "rotate left sharp".
+  static const IconData rotate_left_sharp = IconData(0xeeda, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">rotate_right</i> &#x2014; material icon named "rotate right".
+  static const IconData rotate_right = IconData(0xe995, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">rotate_right</i> &#x2014; material icon named "rotate right outlined".
+  static const IconData rotate_right_outlined = IconData(0xe3d6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">rotate_right</i> &#x2014; material icon named "rotate right rounded".
+  static const IconData rotate_right_rounded = IconData(0xf409, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">rotate_right</i> &#x2014; material icon named "rotate right sharp".
+  static const IconData rotate_right_sharp = IconData(0xeedb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">rounded_corner</i> &#x2014; material icon named "rounded corner".
+  static const IconData rounded_corner = IconData(0xe996, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">rounded_corner</i> &#x2014; material icon named "rounded corner outlined".
+  static const IconData rounded_corner_outlined = IconData(0xe3d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">rounded_corner</i> &#x2014; material icon named "rounded corner rounded".
+  static const IconData rounded_corner_rounded = IconData(0xf40a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">rounded_corner</i> &#x2014; material icon named "rounded corner sharp".
+  static const IconData rounded_corner_sharp = IconData(0xeedc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">router</i> &#x2014; material icon named "router".
+  static const IconData router = IconData(0xe997, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">router</i> &#x2014; material icon named "router outlined".
+  static const IconData router_outlined = IconData(0xe3d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">router</i> &#x2014; material icon named "router rounded".
+  static const IconData router_rounded = IconData(0xf40b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">router</i> &#x2014; material icon named "router sharp".
+  static const IconData router_sharp = IconData(0xeedd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">rowing</i> &#x2014; material icon named "rowing".
+  static const IconData rowing = IconData(0xe998, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">rowing</i> &#x2014; material icon named "rowing outlined".
+  static const IconData rowing_outlined = IconData(0xe3d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">rowing</i> &#x2014; material icon named "rowing rounded".
+  static const IconData rowing_rounded = IconData(0xf40c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">rowing</i> &#x2014; material icon named "rowing sharp".
+  static const IconData rowing_sharp = IconData(0xeede, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">rss_feed</i> &#x2014; material icon named "rss feed".
+  static const IconData rss_feed = IconData(0xe999, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">rss_feed</i> &#x2014; material icon named "rss feed outlined".
+  static const IconData rss_feed_outlined = IconData(0xe3da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">rss_feed</i> &#x2014; material icon named "rss feed rounded".
+  static const IconData rss_feed_rounded = IconData(0xf40d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">rss_feed</i> &#x2014; material icon named "rss feed sharp".
+  static const IconData rss_feed_sharp = IconData(0xeedf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">rtt</i> &#x2014; material icon named "rtt".
+  static const IconData rtt = IconData(0xe99a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">rule</i> &#x2014; material icon named "rule".
+  static const IconData rule = IconData(0xe99b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">rule_folder</i> &#x2014; material icon named "rule folder".
+  static const IconData rule_folder = IconData(0xe99c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">rule_folder</i> &#x2014; material icon named "rule folder outlined".
+  static const IconData rule_folder_outlined = IconData(0xe3db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">rule_folder</i> &#x2014; material icon named "rule folder rounded".
+  static const IconData rule_folder_rounded = IconData(0xf40e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">rule_folder</i> &#x2014; material icon named "rule folder sharp".
+  static const IconData rule_folder_sharp = IconData(0xeee0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">rule</i> &#x2014; material icon named "rule outlined".
+  static const IconData rule_outlined = IconData(0xe3dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">rule</i> &#x2014; material icon named "rule rounded".
+  static const IconData rule_rounded = IconData(0xf40f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">rule</i> &#x2014; material icon named "rule sharp".
+  static const IconData rule_sharp = IconData(0xeee1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">run_circle</i> &#x2014; material icon named "run circle".
+  static const IconData run_circle = IconData(0xe99d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">run_circle</i> &#x2014; material icon named "run circle outlined".
+  static const IconData run_circle_outlined = IconData(0xe3dd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">run_circle</i> &#x2014; material icon named "run circle rounded".
+  static const IconData run_circle_rounded = IconData(0xf410, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">run_circle</i> &#x2014; material icon named "run circle sharp".
+  static const IconData run_circle_sharp = IconData(0xeee2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">rv_hookup</i> &#x2014; material icon named "rv hookup".
+  static const IconData rv_hookup = IconData(0xe99e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">rv_hookup</i> &#x2014; material icon named "rv hookup outlined".
+  static const IconData rv_hookup_outlined = IconData(0xe3de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">rv_hookup</i> &#x2014; material icon named "rv hookup rounded".
+  static const IconData rv_hookup_rounded = IconData(0xf411, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">rv_hookup</i> &#x2014; material icon named "rv hookup sharp".
+  static const IconData rv_hookup_sharp = IconData(0xeee3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sanitizer</i> &#x2014; material icon named "sanitizer".
+  static const IconData sanitizer = IconData(0xe99f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sanitizer</i> &#x2014; material icon named "sanitizer outlined".
+  static const IconData sanitizer_outlined = IconData(0xe3df, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sanitizer</i> &#x2014; material icon named "sanitizer rounded".
+  static const IconData sanitizer_rounded = IconData(0xf412, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sanitizer</i> &#x2014; material icon named "sanitizer sharp".
+  static const IconData sanitizer_sharp = IconData(0xeee4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">satellite</i> &#x2014; material icon named "satellite".
+  static const IconData satellite = IconData(0xe9a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">satellite</i> &#x2014; material icon named "satellite outlined".
+  static const IconData satellite_outlined = IconData(0xe3e0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">satellite</i> &#x2014; material icon named "satellite rounded".
+  static const IconData satellite_rounded = IconData(0xf413, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">satellite</i> &#x2014; material icon named "satellite sharp".
+  static const IconData satellite_sharp = IconData(0xeee5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">save</i> &#x2014; material icon named "save".
+  static const IconData save = IconData(0xe9a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">save_alt</i> &#x2014; material icon named "save alt".
+  static const IconData save_alt = IconData(0xe9a2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">save_alt</i> &#x2014; material icon named "save alt outlined".
+  static const IconData save_alt_outlined = IconData(0xe3e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">save_alt</i> &#x2014; material icon named "save alt rounded".
+  static const IconData save_alt_rounded = IconData(0xf414, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">save_alt</i> &#x2014; material icon named "save alt sharp".
+  static const IconData save_alt_sharp = IconData(0xeee6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">save</i> &#x2014; material icon named "save outlined".
+  static const IconData save_outlined = IconData(0xe3e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">save</i> &#x2014; material icon named "save rounded".
+  static const IconData save_rounded = IconData(0xf415, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">save</i> &#x2014; material icon named "save sharp".
+  static const IconData save_sharp = IconData(0xeee7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">saved_search</i> &#x2014; material icon named "saved search".
+  static const IconData saved_search = IconData(0xe9a3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">scanner</i> &#x2014; material icon named "scanner".
+  static const IconData scanner = IconData(0xe9a4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">scanner</i> &#x2014; material icon named "scanner outlined".
+  static const IconData scanner_outlined = IconData(0xe3e3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">scanner</i> &#x2014; material icon named "scanner rounded".
+  static const IconData scanner_rounded = IconData(0xf416, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">scanner</i> &#x2014; material icon named "scanner sharp".
+  static const IconData scanner_sharp = IconData(0xeee8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">scatter_plot</i> &#x2014; material icon named "scatter plot".
+  static const IconData scatter_plot = IconData(0xe9a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">scatter_plot</i> &#x2014; material icon named "scatter plot outlined".
+  static const IconData scatter_plot_outlined = IconData(0xe3e4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">scatter_plot</i> &#x2014; material icon named "scatter plot rounded".
+  static const IconData scatter_plot_rounded = IconData(0xf417, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">scatter_plot</i> &#x2014; material icon named "scatter plot sharp".
+  static const IconData scatter_plot_sharp = IconData(0xeee9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">schedule</i> &#x2014; material icon named "schedule".
+  static const IconData schedule = IconData(0xe9a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">schedule</i> &#x2014; material icon named "schedule outlined".
+  static const IconData schedule_outlined = IconData(0xe3e5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">schedule</i> &#x2014; material icon named "schedule rounded".
+  static const IconData schedule_rounded = IconData(0xf418, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">schedule_send</i> &#x2014; material icon named "schedule send".
+  static const IconData schedule_send = IconData(0xe9a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">schedule</i> &#x2014; material icon named "schedule sharp".
+  static const IconData schedule_sharp = IconData(0xeeea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">school</i> &#x2014; material icon named "school".
+  static const IconData school = IconData(0xe9a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">school</i> &#x2014; material icon named "school outlined".
+  static const IconData school_outlined = IconData(0xe3e6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">school</i> &#x2014; material icon named "school rounded".
+  static const IconData school_rounded = IconData(0xf419, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">school</i> &#x2014; material icon named "school sharp".
+  static const IconData school_sharp = IconData(0xeeeb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">science</i> &#x2014; material icon named "science".
+  static const IconData science = IconData(0xe9a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">science</i> &#x2014; material icon named "science outlined".
+  static const IconData science_outlined = IconData(0xe3e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">science</i> &#x2014; material icon named "science rounded".
+  static const IconData science_rounded = IconData(0xf41a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">science</i> &#x2014; material icon named "science sharp".
+  static const IconData science_sharp = IconData(0xeeec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">score</i> &#x2014; material icon named "score".
+  static const IconData score = IconData(0xe9aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">score</i> &#x2014; material icon named "score outlined".
+  static const IconData score_outlined = IconData(0xe3e8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">score</i> &#x2014; material icon named "score rounded".
+  static const IconData score_rounded = IconData(0xf41b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">score</i> &#x2014; material icon named "score sharp".
+  static const IconData score_sharp = IconData(0xeeed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">screen_lock_landscape</i> &#x2014; material icon named "screen lock landscape".
+  static const IconData screen_lock_landscape = IconData(0xe9ab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">screen_lock_landscape</i> &#x2014; material icon named "screen lock landscape outlined".
+  static const IconData screen_lock_landscape_outlined = IconData(0xe3e9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">screen_lock_landscape</i> &#x2014; material icon named "screen lock landscape rounded".
+  static const IconData screen_lock_landscape_rounded = IconData(0xf41c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">screen_lock_landscape</i> &#x2014; material icon named "screen lock landscape sharp".
+  static const IconData screen_lock_landscape_sharp = IconData(0xeeee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">screen_lock_portrait</i> &#x2014; material icon named "screen lock portrait".
+  static const IconData screen_lock_portrait = IconData(0xe9ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">screen_lock_portrait</i> &#x2014; material icon named "screen lock portrait outlined".
+  static const IconData screen_lock_portrait_outlined = IconData(0xe3ea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">screen_lock_portrait</i> &#x2014; material icon named "screen lock portrait rounded".
+  static const IconData screen_lock_portrait_rounded = IconData(0xf41d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">screen_lock_portrait</i> &#x2014; material icon named "screen lock portrait sharp".
+  static const IconData screen_lock_portrait_sharp = IconData(0xeeef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">screen_lock_rotation</i> &#x2014; material icon named "screen lock rotation".
+  static const IconData screen_lock_rotation = IconData(0xe9ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">screen_lock_rotation</i> &#x2014; material icon named "screen lock rotation outlined".
+  static const IconData screen_lock_rotation_outlined = IconData(0xe3eb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">screen_lock_rotation</i> &#x2014; material icon named "screen lock rotation rounded".
+  static const IconData screen_lock_rotation_rounded = IconData(0xf41e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">screen_lock_rotation</i> &#x2014; material icon named "screen lock rotation sharp".
+  static const IconData screen_lock_rotation_sharp = IconData(0xeef0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">screen_rotation</i> &#x2014; material icon named "screen rotation".
+  static const IconData screen_rotation = IconData(0xe9ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">screen_rotation</i> &#x2014; material icon named "screen rotation outlined".
+  static const IconData screen_rotation_outlined = IconData(0xe3ec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">screen_rotation</i> &#x2014; material icon named "screen rotation rounded".
+  static const IconData screen_rotation_rounded = IconData(0xf41f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">screen_rotation</i> &#x2014; material icon named "screen rotation sharp".
+  static const IconData screen_rotation_sharp = IconData(0xeef1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">screen_search_desktop</i> &#x2014; material icon named "screen search desktop".
+  static const IconData screen_search_desktop = IconData(0xe9af, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">screen_share</i> &#x2014; material icon named "screen share".
+  static const IconData screen_share = IconData(0xe9b0, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">screen_share</i> &#x2014; material icon named "screen share outlined".
+  static const IconData screen_share_outlined = IconData(0xe3ed, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">screen_share</i> &#x2014; material icon named "screen share rounded".
+  static const IconData screen_share_rounded = IconData(0xf420, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">screen_share</i> &#x2014; material icon named "screen share sharp".
+  static const IconData screen_share_sharp = IconData(0xeef2, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">sd</i> &#x2014; material icon named "sd".
+  static const IconData sd = IconData(0xe9b1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sd_card</i> &#x2014; material icon named "sd card".
+  static const IconData sd_card = IconData(0xe9b2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sd_card_alert</i> &#x2014; material icon named "sd card alert outlined".
+  static const IconData sd_card_alert_outlined = IconData(0xe3ee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sd_card_alert</i> &#x2014; material icon named "sd card alert rounded".
+  static const IconData sd_card_alert_rounded = IconData(0xf421, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sd_card_alert</i> &#x2014; material icon named "sd card alert sharp".
+  static const IconData sd_card_alert_sharp = IconData(0xeef3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sd_card</i> &#x2014; material icon named "sd card outlined".
+  static const IconData sd_card_outlined = IconData(0xe3ef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sd_card</i> &#x2014; material icon named "sd card rounded".
+  static const IconData sd_card_rounded = IconData(0xf422, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sd_card</i> &#x2014; material icon named "sd card sharp".
+  static const IconData sd_card_sharp = IconData(0xeef4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sd_storage</i> &#x2014; material icon named "sd storage".
+  static const IconData sd_storage = IconData(0xe9b3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sd_storage</i> &#x2014; material icon named "sd storage outlined".
+  static const IconData sd_storage_outlined = IconData(0xe3f0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sd_storage</i> &#x2014; material icon named "sd storage rounded".
+  static const IconData sd_storage_rounded = IconData(0xf423, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sd_storage</i> &#x2014; material icon named "sd storage sharp".
+  static const IconData sd_storage_sharp = IconData(0xeef5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">search</i> &#x2014; material icon named "search".
+  static const IconData search = IconData(0xe9b4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">search_off</i> &#x2014; material icon named "search off".
+  static const IconData search_off = IconData(0xe9b5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">search_off</i> &#x2014; material icon named "search off outlined".
+  static const IconData search_off_outlined = IconData(0xe3f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">search_off</i> &#x2014; material icon named "search off rounded".
+  static const IconData search_off_rounded = IconData(0xf424, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">search_off</i> &#x2014; material icon named "search off sharp".
+  static const IconData search_off_sharp = IconData(0xeef6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">search</i> &#x2014; material icon named "search outlined".
+  static const IconData search_outlined = IconData(0xe3f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">search</i> &#x2014; material icon named "search rounded".
+  static const IconData search_rounded = IconData(0xf425, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">search</i> &#x2014; material icon named "search sharp".
+  static const IconData search_sharp = IconData(0xeef7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">security</i> &#x2014; material icon named "security".
+  static const IconData security = IconData(0xe9b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">security</i> &#x2014; material icon named "security outlined".
+  static const IconData security_outlined = IconData(0xe3f3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">security</i> &#x2014; material icon named "security rounded".
+  static const IconData security_rounded = IconData(0xf426, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">security</i> &#x2014; material icon named "security sharp".
+  static const IconData security_sharp = IconData(0xeef8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">segment</i> &#x2014; material icon named "segment".
+  static const IconData segment = IconData(0xe9b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">select_all</i> &#x2014; material icon named "select all".
+  static const IconData select_all = IconData(0xe9b8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">select_all</i> &#x2014; material icon named "select all outlined".
+  static const IconData select_all_outlined = IconData(0xe3f4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">select_all</i> &#x2014; material icon named "select all rounded".
+  static const IconData select_all_rounded = IconData(0xf427, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">select_all</i> &#x2014; material icon named "select all sharp".
+  static const IconData select_all_sharp = IconData(0xeef9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">self_improvement</i> &#x2014; material icon named "self improvement".
+  static const IconData self_improvement = IconData(0xe9b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">self_improvement</i> &#x2014; material icon named "self improvement outlined".
+  static const IconData self_improvement_outlined = IconData(0xe3f5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">self_improvement</i> &#x2014; material icon named "self improvement rounded".
+  static const IconData self_improvement_rounded = IconData(0xf428, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">self_improvement</i> &#x2014; material icon named "self improvement sharp".
+  static const IconData self_improvement_sharp = IconData(0xeefa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">send</i> &#x2014; material icon named "send".
+  static const IconData send = IconData(0xe9ba, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">send_and_archive</i> &#x2014; material icon named "send and archive".
+  static const IconData send_and_archive = IconData(0xe9bb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">send</i> &#x2014; material icon named "send outlined".
+  static const IconData send_outlined = IconData(0xe3f6, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">send</i> &#x2014; material icon named "send rounded".
+  static const IconData send_rounded = IconData(0xf429, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">send</i> &#x2014; material icon named "send sharp".
+  static const IconData send_sharp = IconData(0xeefb, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">send_to_mobile</i> &#x2014; material icon named "send to mobile".
+  static const IconData send_to_mobile = IconData(0xe9bc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sensor_door</i> &#x2014; material icon named "sensor door".
+  static const IconData sensor_door = IconData(0xe9bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sensor_door</i> &#x2014; material icon named "sensor door outlined".
+  static const IconData sensor_door_outlined = IconData(0xe3f7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sensor_door</i> &#x2014; material icon named "sensor door rounded".
+  static const IconData sensor_door_rounded = IconData(0xf42a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sensor_door</i> &#x2014; material icon named "sensor door sharp".
+  static const IconData sensor_door_sharp = IconData(0xeefc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sensor_window</i> &#x2014; material icon named "sensor window".
+  static const IconData sensor_window = IconData(0xe9be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sensor_window</i> &#x2014; material icon named "sensor window outlined".
+  static const IconData sensor_window_outlined = IconData(0xe3f8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sensor_window</i> &#x2014; material icon named "sensor window rounded".
+  static const IconData sensor_window_rounded = IconData(0xf42b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sensor_window</i> &#x2014; material icon named "sensor window sharp".
+  static const IconData sensor_window_sharp = IconData(0xeefd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sentiment_dissatisfied</i> &#x2014; material icon named "sentiment dissatisfied".
+  static const IconData sentiment_dissatisfied = IconData(0xe9bf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sentiment_dissatisfied</i> &#x2014; material icon named "sentiment dissatisfied outlined".
+  static const IconData sentiment_dissatisfied_outlined = IconData(0xe3f9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sentiment_dissatisfied</i> &#x2014; material icon named "sentiment dissatisfied rounded".
+  static const IconData sentiment_dissatisfied_rounded = IconData(0xf42c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sentiment_dissatisfied</i> &#x2014; material icon named "sentiment dissatisfied sharp".
+  static const IconData sentiment_dissatisfied_sharp = IconData(0xeefe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sentiment_neutral</i> &#x2014; material icon named "sentiment neutral".
+  static const IconData sentiment_neutral = IconData(0xe9c0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sentiment_neutral</i> &#x2014; material icon named "sentiment neutral outlined".
+  static const IconData sentiment_neutral_outlined = IconData(0xe3fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sentiment_neutral</i> &#x2014; material icon named "sentiment neutral rounded".
+  static const IconData sentiment_neutral_rounded = IconData(0xf42d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sentiment_satisfied</i> &#x2014; material icon named "sentiment satisfied".
+  static const IconData sentiment_satisfied = IconData(0xe9c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sentiment_satisfied_alt</i> &#x2014; material icon named "sentiment satisfied alt".
+  static const IconData sentiment_satisfied_alt = IconData(0xe9c2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sentiment_satisfied_alt</i> &#x2014; material icon named "sentiment satisfied alt outlined".
+  static const IconData sentiment_satisfied_alt_outlined = IconData(0xe3fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sentiment_satisfied_alt</i> &#x2014; material icon named "sentiment satisfied alt rounded".
+  static const IconData sentiment_satisfied_alt_rounded = IconData(0xf42e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sentiment_satisfied_alt</i> &#x2014; material icon named "sentiment satisfied alt sharp".
+  static const IconData sentiment_satisfied_alt_sharp = IconData(0xeeff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sentiment_satisfied</i> &#x2014; material icon named "sentiment satisfied outlined".
+  static const IconData sentiment_satisfied_outlined = IconData(0xe3fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sentiment_satisfied</i> &#x2014; material icon named "sentiment satisfied rounded".
+  static const IconData sentiment_satisfied_rounded = IconData(0xf42f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sentiment_satisfied</i> &#x2014; material icon named "sentiment satisfied sharp".
+  static const IconData sentiment_satisfied_sharp = IconData(0xef00, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sentiment_very_dissatisfied</i> &#x2014; material icon named "sentiment very dissatisfied".
+  static const IconData sentiment_very_dissatisfied = IconData(0xe9c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sentiment_very_dissatisfied</i> &#x2014; material icon named "sentiment very dissatisfied outlined".
+  static const IconData sentiment_very_dissatisfied_outlined = IconData(0xe3fd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sentiment_very_dissatisfied</i> &#x2014; material icon named "sentiment very dissatisfied rounded".
+  static const IconData sentiment_very_dissatisfied_rounded = IconData(0xf430, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sentiment_very_dissatisfied</i> &#x2014; material icon named "sentiment very dissatisfied sharp".
+  static const IconData sentiment_very_dissatisfied_sharp = IconData(0xef01, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sentiment_very_satisfied</i> &#x2014; material icon named "sentiment very satisfied".
+  static const IconData sentiment_very_satisfied = IconData(0xe9c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sentiment_very_satisfied</i> &#x2014; material icon named "sentiment very satisfied outlined".
+  static const IconData sentiment_very_satisfied_outlined = IconData(0xe3fe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sentiment_very_satisfied</i> &#x2014; material icon named "sentiment very satisfied rounded".
+  static const IconData sentiment_very_satisfied_rounded = IconData(0xf431, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sentiment_very_satisfied</i> &#x2014; material icon named "sentiment very satisfied sharp".
+  static const IconData sentiment_very_satisfied_sharp = IconData(0xef02, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">set_meal</i> &#x2014; material icon named "set meal".
+  static const IconData set_meal = IconData(0xe9c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">set_meal</i> &#x2014; material icon named "set meal outlined".
+  static const IconData set_meal_outlined = IconData(0xe3ff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">set_meal</i> &#x2014; material icon named "set meal rounded".
+  static const IconData set_meal_rounded = IconData(0xf432, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">set_meal</i> &#x2014; material icon named "set meal sharp".
+  static const IconData set_meal_sharp = IconData(0xef03, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings</i> &#x2014; material icon named "settings".
+  static const IconData settings = IconData(0xe9c6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_applications</i> &#x2014; material icon named "settings applications".
+  static const IconData settings_applications = IconData(0xe9c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_applications</i> &#x2014; material icon named "settings applications outlined".
+  static const IconData settings_applications_outlined = IconData(0xe400, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_applications</i> &#x2014; material icon named "settings applications rounded".
+  static const IconData settings_applications_rounded = IconData(0xf433, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_applications</i> &#x2014; material icon named "settings applications sharp".
+  static const IconData settings_applications_sharp = IconData(0xef04, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_backup_restore</i> &#x2014; material icon named "settings backup restore".
+  static const IconData settings_backup_restore = IconData(0xe9c8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_backup_restore</i> &#x2014; material icon named "settings backup restore outlined".
+  static const IconData settings_backup_restore_outlined = IconData(0xe401, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_backup_restore</i> &#x2014; material icon named "settings backup restore rounded".
+  static const IconData settings_backup_restore_rounded = IconData(0xf434, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_backup_restore</i> &#x2014; material icon named "settings backup restore sharp".
+  static const IconData settings_backup_restore_sharp = IconData(0xef05, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_bluetooth</i> &#x2014; material icon named "settings bluetooth".
+  static const IconData settings_bluetooth = IconData(0xe9c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_bluetooth</i> &#x2014; material icon named "settings bluetooth outlined".
+  static const IconData settings_bluetooth_outlined = IconData(0xe402, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_bluetooth</i> &#x2014; material icon named "settings bluetooth rounded".
+  static const IconData settings_bluetooth_rounded = IconData(0xf435, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_bluetooth</i> &#x2014; material icon named "settings bluetooth sharp".
+  static const IconData settings_bluetooth_sharp = IconData(0xef06, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_brightness</i> &#x2014; material icon named "settings brightness".
+  static const IconData settings_brightness = IconData(0xe9ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_brightness</i> &#x2014; material icon named "settings brightness outlined".
+  static const IconData settings_brightness_outlined = IconData(0xe403, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_brightness</i> &#x2014; material icon named "settings brightness rounded".
+  static const IconData settings_brightness_rounded = IconData(0xf436, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_brightness</i> &#x2014; material icon named "settings brightness sharp".
+  static const IconData settings_brightness_sharp = IconData(0xef07, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_cell</i> &#x2014; material icon named "settings cell".
+  static const IconData settings_cell = IconData(0xe9cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_cell</i> &#x2014; material icon named "settings cell outlined".
+  static const IconData settings_cell_outlined = IconData(0xe404, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_cell</i> &#x2014; material icon named "settings cell rounded".
+  static const IconData settings_cell_rounded = IconData(0xf437, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_cell</i> &#x2014; material icon named "settings cell sharp".
+  static const IconData settings_cell_sharp = IconData(0xef08, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_display</i> &#x2014; material icon named "settings display".
+  static const IconData settings_display = IconData(0xe9ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_display</i> &#x2014; material icon named "settings display outlined".
+  static const IconData settings_display_outlined = IconData(0xe403, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_display</i> &#x2014; material icon named "settings display rounded".
+  static const IconData settings_display_rounded = IconData(0xf436, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_display</i> &#x2014; material icon named "settings display sharp".
+  static const IconData settings_display_sharp = IconData(0xef07, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_ethernet</i> &#x2014; material icon named "settings ethernet".
+  static const IconData settings_ethernet = IconData(0xe9cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_ethernet</i> &#x2014; material icon named "settings ethernet outlined".
+  static const IconData settings_ethernet_outlined = IconData(0xe405, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_ethernet</i> &#x2014; material icon named "settings ethernet rounded".
+  static const IconData settings_ethernet_rounded = IconData(0xf438, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_ethernet</i> &#x2014; material icon named "settings ethernet sharp".
+  static const IconData settings_ethernet_sharp = IconData(0xef09, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_input_antenna</i> &#x2014; material icon named "settings input antenna".
+  static const IconData settings_input_antenna = IconData(0xe9cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_input_antenna</i> &#x2014; material icon named "settings input antenna outlined".
+  static const IconData settings_input_antenna_outlined = IconData(0xe406, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_input_antenna</i> &#x2014; material icon named "settings input antenna rounded".
+  static const IconData settings_input_antenna_rounded = IconData(0xf439, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_input_antenna</i> &#x2014; material icon named "settings input antenna sharp".
+  static const IconData settings_input_antenna_sharp = IconData(0xef0a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_input_component</i> &#x2014; material icon named "settings input component".
+  static const IconData settings_input_component = IconData(0xe9ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_input_component</i> &#x2014; material icon named "settings input component outlined".
+  static const IconData settings_input_component_outlined = IconData(0xe407, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_input_component</i> &#x2014; material icon named "settings input component rounded".
+  static const IconData settings_input_component_rounded = IconData(0xf43a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_input_component</i> &#x2014; material icon named "settings input component sharp".
+  static const IconData settings_input_component_sharp = IconData(0xef0b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_input_composite</i> &#x2014; material icon named "settings input composite".
+  static const IconData settings_input_composite = IconData(0xe9cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_input_composite</i> &#x2014; material icon named "settings input composite outlined".
+  static const IconData settings_input_composite_outlined = IconData(0xe408, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_input_composite</i> &#x2014; material icon named "settings input composite rounded".
+  static const IconData settings_input_composite_rounded = IconData(0xf43b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_input_composite</i> &#x2014; material icon named "settings input composite sharp".
+  static const IconData settings_input_composite_sharp = IconData(0xef0c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_input_hdmi</i> &#x2014; material icon named "settings input hdmi".
+  static const IconData settings_input_hdmi = IconData(0xe9d0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_input_hdmi</i> &#x2014; material icon named "settings input hdmi outlined".
+  static const IconData settings_input_hdmi_outlined = IconData(0xe409, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_input_hdmi</i> &#x2014; material icon named "settings input hdmi rounded".
+  static const IconData settings_input_hdmi_rounded = IconData(0xf43c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_input_hdmi</i> &#x2014; material icon named "settings input hdmi sharp".
+  static const IconData settings_input_hdmi_sharp = IconData(0xef0d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_input_svideo</i> &#x2014; material icon named "settings input svideo".
+  static const IconData settings_input_svideo = IconData(0xe9d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_input_svideo</i> &#x2014; material icon named "settings input svideo outlined".
+  static const IconData settings_input_svideo_outlined = IconData(0xe40a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_input_svideo</i> &#x2014; material icon named "settings input svideo rounded".
+  static const IconData settings_input_svideo_rounded = IconData(0xf43d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_input_svideo</i> &#x2014; material icon named "settings input svideo sharp".
+  static const IconData settings_input_svideo_sharp = IconData(0xef0e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings</i> &#x2014; material icon named "settings outlined".
+  static const IconData settings_outlined = IconData(0xe40b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_overscan</i> &#x2014; material icon named "settings overscan".
+  static const IconData settings_overscan = IconData(0xe9d2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_overscan</i> &#x2014; material icon named "settings overscan outlined".
+  static const IconData settings_overscan_outlined = IconData(0xe40c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_overscan</i> &#x2014; material icon named "settings overscan rounded".
+  static const IconData settings_overscan_rounded = IconData(0xf43e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_overscan</i> &#x2014; material icon named "settings overscan sharp".
+  static const IconData settings_overscan_sharp = IconData(0xef0f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_phone</i> &#x2014; material icon named "settings phone".
+  static const IconData settings_phone = IconData(0xe9d3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_phone</i> &#x2014; material icon named "settings phone outlined".
+  static const IconData settings_phone_outlined = IconData(0xe40d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_phone</i> &#x2014; material icon named "settings phone rounded".
+  static const IconData settings_phone_rounded = IconData(0xf43f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_phone</i> &#x2014; material icon named "settings phone sharp".
+  static const IconData settings_phone_sharp = IconData(0xef10, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_power</i> &#x2014; material icon named "settings power".
+  static const IconData settings_power = IconData(0xe9d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_power</i> &#x2014; material icon named "settings power outlined".
+  static const IconData settings_power_outlined = IconData(0xe40e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_power</i> &#x2014; material icon named "settings power rounded".
+  static const IconData settings_power_rounded = IconData(0xf440, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_power</i> &#x2014; material icon named "settings power sharp".
+  static const IconData settings_power_sharp = IconData(0xef11, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_remote</i> &#x2014; material icon named "settings remote".
+  static const IconData settings_remote = IconData(0xe9d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_remote</i> &#x2014; material icon named "settings remote outlined".
+  static const IconData settings_remote_outlined = IconData(0xe40f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_remote</i> &#x2014; material icon named "settings remote rounded".
+  static const IconData settings_remote_rounded = IconData(0xf441, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_remote</i> &#x2014; material icon named "settings remote sharp".
+  static const IconData settings_remote_sharp = IconData(0xef12, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings</i> &#x2014; material icon named "settings rounded".
+  static const IconData settings_rounded = IconData(0xf442, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings</i> &#x2014; material icon named "settings sharp".
+  static const IconData settings_sharp = IconData(0xef13, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_system_daydream</i> &#x2014; material icon named "settings system daydream".
+  static const IconData settings_system_daydream = IconData(0xe9d6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_system_daydream</i> &#x2014; material icon named "settings system daydream outlined".
+  static const IconData settings_system_daydream_outlined = IconData(0xe410, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_system_daydream</i> &#x2014; material icon named "settings system daydream rounded".
+  static const IconData settings_system_daydream_rounded = IconData(0xf443, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_system_daydream</i> &#x2014; material icon named "settings system daydream sharp".
+  static const IconData settings_system_daydream_sharp = IconData(0xef14, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">settings_voice</i> &#x2014; material icon named "settings voice".
+  static const IconData settings_voice = IconData(0xe9d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">settings_voice</i> &#x2014; material icon named "settings voice outlined".
+  static const IconData settings_voice_outlined = IconData(0xe411, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">settings_voice</i> &#x2014; material icon named "settings voice rounded".
+  static const IconData settings_voice_rounded = IconData(0xf444, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">settings_voice</i> &#x2014; material icon named "settings voice sharp".
+  static const IconData settings_voice_sharp = IconData(0xef15, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">share</i> &#x2014; material icon named "share".
+  static const IconData share = IconData(0xe9d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">share</i> &#x2014; material icon named "share outlined".
+  static const IconData share_outlined = IconData(0xe412, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">share</i> &#x2014; material icon named "share rounded".
+  static const IconData share_rounded = IconData(0xf445, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">share</i> &#x2014; material icon named "share sharp".
+  static const IconData share_sharp = IconData(0xef16, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">shield</i> &#x2014; material icon named "shield".
+  static const IconData shield = IconData(0xe9d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">shop</i> &#x2014; material icon named "shop".
+  static const IconData shop = IconData(0xe9da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">shop</i> &#x2014; material icon named "shop outlined".
+  static const IconData shop_outlined = IconData(0xe413, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">shop</i> &#x2014; material icon named "shop rounded".
+  static const IconData shop_rounded = IconData(0xf446, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">shop</i> &#x2014; material icon named "shop sharp".
+  static const IconData shop_sharp = IconData(0xef17, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">shop_two</i> &#x2014; material icon named "shop two".
+  static const IconData shop_two = IconData(0xe9db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">shop_two</i> &#x2014; material icon named "shop two outlined".
+  static const IconData shop_two_outlined = IconData(0xe414, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">shop_two</i> &#x2014; material icon named "shop two rounded".
+  static const IconData shop_two_rounded = IconData(0xf447, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">shop_two</i> &#x2014; material icon named "shop two sharp".
+  static const IconData shop_two_sharp = IconData(0xef18, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">shopping_bag</i> &#x2014; material icon named "shopping bag".
+  static const IconData shopping_bag = IconData(0xe9dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">shopping_bag</i> &#x2014; material icon named "shopping bag outlined".
+  static const IconData shopping_bag_outlined = IconData(0xe415, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">shopping_bag</i> &#x2014; material icon named "shopping bag rounded".
+  static const IconData shopping_bag_rounded = IconData(0xf448, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">shopping_bag</i> &#x2014; material icon named "shopping bag sharp".
+  static const IconData shopping_bag_sharp = IconData(0xef19, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">shopping_basket</i> &#x2014; material icon named "shopping basket".
+  static const IconData shopping_basket = IconData(0xe9dd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">shopping_basket</i> &#x2014; material icon named "shopping basket outlined".
+  static const IconData shopping_basket_outlined = IconData(0xe416, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">shopping_basket</i> &#x2014; material icon named "shopping basket rounded".
+  static const IconData shopping_basket_rounded = IconData(0xf449, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">shopping_basket</i> &#x2014; material icon named "shopping basket sharp".
+  static const IconData shopping_basket_sharp = IconData(0xef1a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">shopping_cart</i> &#x2014; material icon named "shopping cart".
+  static const IconData shopping_cart = IconData(0xe9de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">shopping_cart</i> &#x2014; material icon named "shopping cart outlined".
+  static const IconData shopping_cart_outlined = IconData(0xe417, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">shopping_cart</i> &#x2014; material icon named "shopping cart rounded".
+  static const IconData shopping_cart_rounded = IconData(0xf44a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">shopping_cart</i> &#x2014; material icon named "shopping cart sharp".
+  static const IconData shopping_cart_sharp = IconData(0xef1b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">short_text</i> &#x2014; material icon named "short text".
+  static const IconData short_text = IconData(0xe9df, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">short_text</i> &#x2014; material icon named "short text outlined".
+  static const IconData short_text_outlined = IconData(0xe418, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">short_text</i> &#x2014; material icon named "short text rounded".
+  static const IconData short_text_rounded = IconData(0xf44b, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">short_text</i> &#x2014; material icon named "short text sharp".
+  static const IconData short_text_sharp = IconData(0xef1c, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">show_chart</i> &#x2014; material icon named "show chart".
+  static const IconData show_chart = IconData(0xe9e0, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">show_chart</i> &#x2014; material icon named "show chart outlined".
+  static const IconData show_chart_outlined = IconData(0xe419, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">show_chart</i> &#x2014; material icon named "show chart rounded".
+  static const IconData show_chart_rounded = IconData(0xf44c, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">show_chart</i> &#x2014; material icon named "show chart sharp".
+  static const IconData show_chart_sharp = IconData(0xef1d, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">shuffle</i> &#x2014; material icon named "shuffle".
+  static const IconData shuffle = IconData(0xe9e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">shuffle_on</i> &#x2014; material icon named "shuffle on".
+  static const IconData shuffle_on = IconData(0xe9e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">shuffle</i> &#x2014; material icon named "shuffle outlined".
+  static const IconData shuffle_outlined = IconData(0xe41a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">shuffle</i> &#x2014; material icon named "shuffle rounded".
+  static const IconData shuffle_rounded = IconData(0xf44d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">shuffle</i> &#x2014; material icon named "shuffle sharp".
+  static const IconData shuffle_sharp = IconData(0xef1e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">shutter_speed</i> &#x2014; material icon named "shutter speed".
+  static const IconData shutter_speed = IconData(0xe9e3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">shutter_speed</i> &#x2014; material icon named "shutter speed outlined".
+  static const IconData shutter_speed_outlined = IconData(0xe41b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">shutter_speed</i> &#x2014; material icon named "shutter speed rounded".
+  static const IconData shutter_speed_rounded = IconData(0xf44e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">shutter_speed</i> &#x2014; material icon named "shutter speed sharp".
+  static const IconData shutter_speed_sharp = IconData(0xef1f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sick</i> &#x2014; material icon named "sick".
+  static const IconData sick = IconData(0xe9e4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sick</i> &#x2014; material icon named "sick outlined".
+  static const IconData sick_outlined = IconData(0xe41c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sick</i> &#x2014; material icon named "sick rounded".
+  static const IconData sick_rounded = IconData(0xf44f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sick</i> &#x2014; material icon named "sick sharp".
+  static const IconData sick_sharp = IconData(0xef20, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">signal_cellular_4_bar</i> &#x2014; material icon named "signal cellular 4 bar".
+  static const IconData signal_cellular_4_bar = IconData(0xe9e5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">signal_cellular_4_bar</i> &#x2014; material icon named "signal cellular 4 bar outlined".
+  static const IconData signal_cellular_4_bar_outlined = IconData(0xe41d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">signal_cellular_4_bar</i> &#x2014; material icon named "signal cellular 4 bar rounded".
+  static const IconData signal_cellular_4_bar_rounded = IconData(0xf450, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">signal_cellular_4_bar</i> &#x2014; material icon named "signal cellular 4 bar sharp".
+  static const IconData signal_cellular_4_bar_sharp = IconData(0xef21, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">signal_cellular_alt</i> &#x2014; material icon named "signal cellular alt".
+  static const IconData signal_cellular_alt = IconData(0xe9e6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">signal_cellular_alt</i> &#x2014; material icon named "signal cellular alt outlined".
+  static const IconData signal_cellular_alt_outlined = IconData(0xe41e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">signal_cellular_alt</i> &#x2014; material icon named "signal cellular alt rounded".
+  static const IconData signal_cellular_alt_rounded = IconData(0xf451, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">signal_cellular_alt</i> &#x2014; material icon named "signal cellular alt sharp".
+  static const IconData signal_cellular_alt_sharp = IconData(0xef22, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">signal_cellular_connected_no_internet_4_bar</i> &#x2014; material icon named "signal cellular connected no internet 4 bar".
+  static const IconData signal_cellular_connected_no_internet_4_bar = IconData(0xe9e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">signal_cellular_connected_no_internet_4_bar</i> &#x2014; material icon named "signal cellular connected no internet 4 bar outlined".
+  static const IconData signal_cellular_connected_no_internet_4_bar_outlined = IconData(0xe41f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">signal_cellular_connected_no_internet_4_bar</i> &#x2014; material icon named "signal cellular connected no internet 4 bar rounded".
+  static const IconData signal_cellular_connected_no_internet_4_bar_rounded = IconData(0xf452, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">signal_cellular_connected_no_internet_4_bar</i> &#x2014; material icon named "signal cellular connected no internet 4 bar sharp".
+  static const IconData signal_cellular_connected_no_internet_4_bar_sharp = IconData(0xef23, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">signal_cellular_no_sim</i> &#x2014; material icon named "signal cellular no sim".
+  static const IconData signal_cellular_no_sim = IconData(0xe9e8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">signal_cellular_no_sim</i> &#x2014; material icon named "signal cellular no sim outlined".
+  static const IconData signal_cellular_no_sim_outlined = IconData(0xe420, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">signal_cellular_no_sim</i> &#x2014; material icon named "signal cellular no sim rounded".
+  static const IconData signal_cellular_no_sim_rounded = IconData(0xf453, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">signal_cellular_no_sim</i> &#x2014; material icon named "signal cellular no sim sharp".
+  static const IconData signal_cellular_no_sim_sharp = IconData(0xef24, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">signal_cellular_null</i> &#x2014; material icon named "signal cellular null".
+  static const IconData signal_cellular_null = IconData(0xe9e9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">signal_cellular_null</i> &#x2014; material icon named "signal cellular null outlined".
+  static const IconData signal_cellular_null_outlined = IconData(0xe421, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">signal_cellular_null</i> &#x2014; material icon named "signal cellular null rounded".
+  static const IconData signal_cellular_null_rounded = IconData(0xf454, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">signal_cellular_null</i> &#x2014; material icon named "signal cellular null sharp".
+  static const IconData signal_cellular_null_sharp = IconData(0xef25, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">signal_cellular_off</i> &#x2014; material icon named "signal cellular off".
+  static const IconData signal_cellular_off = IconData(0xe9ea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">signal_cellular_off</i> &#x2014; material icon named "signal cellular off outlined".
+  static const IconData signal_cellular_off_outlined = IconData(0xe422, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">signal_cellular_off</i> &#x2014; material icon named "signal cellular off rounded".
+  static const IconData signal_cellular_off_rounded = IconData(0xf455, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">signal_cellular_off</i> &#x2014; material icon named "signal cellular off sharp".
+  static const IconData signal_cellular_off_sharp = IconData(0xef26, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">signal_wifi_4_bar</i> &#x2014; material icon named "signal wifi 4 bar".
+  static const IconData signal_wifi_4_bar = IconData(0xe9eb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">signal_wifi_4_bar_lock</i> &#x2014; material icon named "signal wifi 4 bar lock".
+  static const IconData signal_wifi_4_bar_lock = IconData(0xe9ec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">signal_wifi_4_bar_lock</i> &#x2014; material icon named "signal wifi 4 bar lock outlined".
+  static const IconData signal_wifi_4_bar_lock_outlined = IconData(0xe423, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">signal_wifi_4_bar_lock</i> &#x2014; material icon named "signal wifi 4 bar lock rounded".
+  static const IconData signal_wifi_4_bar_lock_rounded = IconData(0xf456, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">signal_wifi_4_bar_lock</i> &#x2014; material icon named "signal wifi 4 bar lock sharp".
+  static const IconData signal_wifi_4_bar_lock_sharp = IconData(0xef27, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">signal_wifi_4_bar</i> &#x2014; material icon named "signal wifi 4 bar outlined".
+  static const IconData signal_wifi_4_bar_outlined = IconData(0xe424, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">signal_wifi_4_bar</i> &#x2014; material icon named "signal wifi 4 bar rounded".
+  static const IconData signal_wifi_4_bar_rounded = IconData(0xf457, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">signal_wifi_4_bar</i> &#x2014; material icon named "signal wifi 4 bar sharp".
+  static const IconData signal_wifi_4_bar_sharp = IconData(0xef28, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">signal_wifi_off</i> &#x2014; material icon named "signal wifi off".
+  static const IconData signal_wifi_off = IconData(0xe9ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">signal_wifi_off</i> &#x2014; material icon named "signal wifi off outlined".
+  static const IconData signal_wifi_off_outlined = IconData(0xe425, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">signal_wifi_off</i> &#x2014; material icon named "signal wifi off rounded".
+  static const IconData signal_wifi_off_rounded = IconData(0xf458, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">signal_wifi_off</i> &#x2014; material icon named "signal wifi off sharp".
+  static const IconData signal_wifi_off_sharp = IconData(0xef29, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sim_card</i> &#x2014; material icon named "sim card".
+  static const IconData sim_card = IconData(0xe9ee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sim_card_alert</i> &#x2014; material icon named "sim card alert".
+  static const IconData sim_card_alert = IconData(0xe9ef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sim_card</i> &#x2014; material icon named "sim card outlined".
+  static const IconData sim_card_outlined = IconData(0xe426, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sim_card</i> &#x2014; material icon named "sim card rounded".
+  static const IconData sim_card_rounded = IconData(0xf459, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sim_card</i> &#x2014; material icon named "sim card sharp".
+  static const IconData sim_card_sharp = IconData(0xef2a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">single_bed</i> &#x2014; material icon named "single bed".
+  static const IconData single_bed = IconData(0xe9f0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">single_bed</i> &#x2014; material icon named "single bed outlined".
+  static const IconData single_bed_outlined = IconData(0xe427, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">single_bed</i> &#x2014; material icon named "single bed rounded".
+  static const IconData single_bed_rounded = IconData(0xf45a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">single_bed</i> &#x2014; material icon named "single bed sharp".
+  static const IconData single_bed_sharp = IconData(0xef2b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">skip_next</i> &#x2014; material icon named "skip next".
+  static const IconData skip_next = IconData(0xe9f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">skip_next</i> &#x2014; material icon named "skip next outlined".
+  static const IconData skip_next_outlined = IconData(0xe428, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">skip_next</i> &#x2014; material icon named "skip next rounded".
+  static const IconData skip_next_rounded = IconData(0xf45b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">skip_next</i> &#x2014; material icon named "skip next sharp".
+  static const IconData skip_next_sharp = IconData(0xef2c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">skip_previous</i> &#x2014; material icon named "skip previous".
+  static const IconData skip_previous = IconData(0xe9f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">skip_previous</i> &#x2014; material icon named "skip previous outlined".
+  static const IconData skip_previous_outlined = IconData(0xe429, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">skip_previous</i> &#x2014; material icon named "skip previous rounded".
+  static const IconData skip_previous_rounded = IconData(0xf45c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">skip_previous</i> &#x2014; material icon named "skip previous sharp".
+  static const IconData skip_previous_sharp = IconData(0xef2d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">slideshow</i> &#x2014; material icon named "slideshow".
+  static const IconData slideshow = IconData(0xe9f3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">slideshow</i> &#x2014; material icon named "slideshow outlined".
+  static const IconData slideshow_outlined = IconData(0xe42a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">slideshow</i> &#x2014; material icon named "slideshow rounded".
+  static const IconData slideshow_rounded = IconData(0xf45d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">slideshow</i> &#x2014; material icon named "slideshow sharp".
+  static const IconData slideshow_sharp = IconData(0xef2e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">slow_motion_video</i> &#x2014; material icon named "slow motion video".
+  static const IconData slow_motion_video = IconData(0xe9f4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">slow_motion_video</i> &#x2014; material icon named "slow motion video outlined".
+  static const IconData slow_motion_video_outlined = IconData(0xe42b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">slow_motion_video</i> &#x2014; material icon named "slow motion video rounded".
+  static const IconData slow_motion_video_rounded = IconData(0xf45e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">slow_motion_video</i> &#x2014; material icon named "slow motion video sharp".
+  static const IconData slow_motion_video_sharp = IconData(0xef2f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">smart_button</i> &#x2014; material icon named "smart button".
+  static const IconData smart_button = IconData(0xe9f5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">smart_button</i> &#x2014; material icon named "smart button outlined".
+  static const IconData smart_button_outlined = IconData(0xe42c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">smart_button</i> &#x2014; material icon named "smart button rounded".
+  static const IconData smart_button_rounded = IconData(0xf45f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">smart_button</i> &#x2014; material icon named "smart button sharp".
+  static const IconData smart_button_sharp = IconData(0xef30, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">smartphone</i> &#x2014; material icon named "smartphone".
+  static const IconData smartphone = IconData(0xe9f6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">smartphone</i> &#x2014; material icon named "smartphone outlined".
+  static const IconData smartphone_outlined = IconData(0xe42d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">smartphone</i> &#x2014; material icon named "smartphone rounded".
+  static const IconData smartphone_rounded = IconData(0xf460, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">smartphone</i> &#x2014; material icon named "smartphone sharp".
+  static const IconData smartphone_sharp = IconData(0xef31, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">smoke_free</i> &#x2014; material icon named "smoke free".
+  static const IconData smoke_free = IconData(0xe9f7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">smoke_free</i> &#x2014; material icon named "smoke free outlined".
+  static const IconData smoke_free_outlined = IconData(0xe42e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">smoke_free</i> &#x2014; material icon named "smoke free rounded".
+  static const IconData smoke_free_rounded = IconData(0xf461, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">smoke_free</i> &#x2014; material icon named "smoke free sharp".
+  static const IconData smoke_free_sharp = IconData(0xef32, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">smoking_rooms</i> &#x2014; material icon named "smoking rooms".
+  static const IconData smoking_rooms = IconData(0xe9f8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">smoking_rooms</i> &#x2014; material icon named "smoking rooms outlined".
+  static const IconData smoking_rooms_outlined = IconData(0xe42f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">smoking_rooms</i> &#x2014; material icon named "smoking rooms rounded".
+  static const IconData smoking_rooms_rounded = IconData(0xf462, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">smoking_rooms</i> &#x2014; material icon named "smoking rooms sharp".
+  static const IconData smoking_rooms_sharp = IconData(0xef33, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sms</i> &#x2014; material icon named "sms".
+  static const IconData sms = IconData(0xe9f9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sms_failed</i> &#x2014; material icon named "sms failed".
+  static const IconData sms_failed = IconData(0xe9fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sms_failed</i> &#x2014; material icon named "sms failed outlined".
+  static const IconData sms_failed_outlined = IconData(0xe430, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sms_failed</i> &#x2014; material icon named "sms failed rounded".
+  static const IconData sms_failed_rounded = IconData(0xf463, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sms_failed</i> &#x2014; material icon named "sms failed sharp".
+  static const IconData sms_failed_sharp = IconData(0xef34, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sms</i> &#x2014; material icon named "sms outlined".
+  static const IconData sms_outlined = IconData(0xe431, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sms</i> &#x2014; material icon named "sms rounded".
+  static const IconData sms_rounded = IconData(0xf464, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sms</i> &#x2014; material icon named "sms sharp".
+  static const IconData sms_sharp = IconData(0xef35, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">snippet_folder</i> &#x2014; material icon named "snippet folder".
+  static const IconData snippet_folder = IconData(0xe9fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">snippet_folder</i> &#x2014; material icon named "snippet folder outlined".
+  static const IconData snippet_folder_outlined = IconData(0xe432, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">snippet_folder</i> &#x2014; material icon named "snippet folder rounded".
+  static const IconData snippet_folder_rounded = IconData(0xf465, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">snippet_folder</i> &#x2014; material icon named "snippet folder sharp".
+  static const IconData snippet_folder_sharp = IconData(0xef36, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">snooze</i> &#x2014; material icon named "snooze".
+  static const IconData snooze = IconData(0xe9fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">snooze</i> &#x2014; material icon named "snooze outlined".
+  static const IconData snooze_outlined = IconData(0xe433, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">snooze</i> &#x2014; material icon named "snooze rounded".
+  static const IconData snooze_rounded = IconData(0xf466, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">snooze</i> &#x2014; material icon named "snooze sharp".
+  static const IconData snooze_sharp = IconData(0xef37, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">soap</i> &#x2014; material icon named "soap".
+  static const IconData soap = IconData(0xe9fd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">soap</i> &#x2014; material icon named "soap outlined".
+  static const IconData soap_outlined = IconData(0xe434, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">soap</i> &#x2014; material icon named "soap rounded".
+  static const IconData soap_rounded = IconData(0xf467, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">soap</i> &#x2014; material icon named "soap sharp".
+  static const IconData soap_sharp = IconData(0xef38, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sort</i> &#x2014; material icon named "sort".
+  static const IconData sort = IconData(0xe9fe, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">sort_by_alpha</i> &#x2014; material icon named "sort by alpha".
+  static const IconData sort_by_alpha = IconData(0xe9ff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sort_by_alpha</i> &#x2014; material icon named "sort by alpha outlined".
+  static const IconData sort_by_alpha_outlined = IconData(0xe435, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sort_by_alpha</i> &#x2014; material icon named "sort by alpha rounded".
+  static const IconData sort_by_alpha_rounded = IconData(0xf468, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sort_by_alpha</i> &#x2014; material icon named "sort by alpha sharp".
+  static const IconData sort_by_alpha_sharp = IconData(0xef39, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sort</i> &#x2014; material icon named "sort outlined".
+  static const IconData sort_outlined = IconData(0xe436, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">sort</i> &#x2014; material icon named "sort rounded".
+  static const IconData sort_rounded = IconData(0xf469, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">sort</i> &#x2014; material icon named "sort sharp".
+  static const IconData sort_sharp = IconData(0xef3a, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">source</i> &#x2014; material icon named "source".
+  static const IconData source = IconData(0xea00, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">source</i> &#x2014; material icon named "source outlined".
+  static const IconData source_outlined = IconData(0xe437, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">source</i> &#x2014; material icon named "source rounded".
+  static const IconData source_rounded = IconData(0xf46a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">source</i> &#x2014; material icon named "source sharp".
+  static const IconData source_sharp = IconData(0xef3b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">south</i> &#x2014; material icon named "south".
+  static const IconData south = IconData(0xea01, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">south_east</i> &#x2014; material icon named "south east".
+  static const IconData south_east = IconData(0xea02, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">south_east</i> &#x2014; material icon named "south east outlined".
+  static const IconData south_east_outlined = IconData(0xe438, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">south_east</i> &#x2014; material icon named "south east rounded".
+  static const IconData south_east_rounded = IconData(0xf46b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">south_east</i> &#x2014; material icon named "south east sharp".
+  static const IconData south_east_sharp = IconData(0xef3c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">south</i> &#x2014; material icon named "south outlined".
+  static const IconData south_outlined = IconData(0xe439, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">south</i> &#x2014; material icon named "south rounded".
+  static const IconData south_rounded = IconData(0xf46c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">south</i> &#x2014; material icon named "south sharp".
+  static const IconData south_sharp = IconData(0xef3d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">south_west</i> &#x2014; material icon named "south west".
+  static const IconData south_west = IconData(0xea03, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">south_west</i> &#x2014; material icon named "south west outlined".
+  static const IconData south_west_outlined = IconData(0xe43a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">south_west</i> &#x2014; material icon named "south west rounded".
+  static const IconData south_west_rounded = IconData(0xf46d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">south_west</i> &#x2014; material icon named "south west sharp".
+  static const IconData south_west_sharp = IconData(0xef3e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">spa</i> &#x2014; material icon named "spa".
+  static const IconData spa = IconData(0xea04, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">spa</i> &#x2014; material icon named "spa outlined".
+  static const IconData spa_outlined = IconData(0xe43b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">spa</i> &#x2014; material icon named "spa rounded".
+  static const IconData spa_rounded = IconData(0xf46e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">spa</i> &#x2014; material icon named "spa sharp".
+  static const IconData spa_sharp = IconData(0xef3f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">space_bar</i> &#x2014; material icon named "space bar".
+  static const IconData space_bar = IconData(0xea05, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">space_bar</i> &#x2014; material icon named "space bar outlined".
+  static const IconData space_bar_outlined = IconData(0xe43c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">space_bar</i> &#x2014; material icon named "space bar rounded".
+  static const IconData space_bar_rounded = IconData(0xf46f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">space_bar</i> &#x2014; material icon named "space bar sharp".
+  static const IconData space_bar_sharp = IconData(0xef40, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">speaker</i> &#x2014; material icon named "speaker".
+  static const IconData speaker = IconData(0xea06, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">speaker_group</i> &#x2014; material icon named "speaker group".
+  static const IconData speaker_group = IconData(0xea07, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">speaker_group</i> &#x2014; material icon named "speaker group outlined".
+  static const IconData speaker_group_outlined = IconData(0xe43d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">speaker_group</i> &#x2014; material icon named "speaker group rounded".
+  static const IconData speaker_group_rounded = IconData(0xf470, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">speaker_group</i> &#x2014; material icon named "speaker group sharp".
+  static const IconData speaker_group_sharp = IconData(0xef41, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">speaker_notes</i> &#x2014; material icon named "speaker notes".
+  static const IconData speaker_notes = IconData(0xea08, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">speaker_notes_off</i> &#x2014; material icon named "speaker notes off".
+  static const IconData speaker_notes_off = IconData(0xea09, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">speaker_notes_off</i> &#x2014; material icon named "speaker notes off outlined".
+  static const IconData speaker_notes_off_outlined = IconData(0xe43e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">speaker_notes_off</i> &#x2014; material icon named "speaker notes off rounded".
+  static const IconData speaker_notes_off_rounded = IconData(0xf471, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">speaker_notes_off</i> &#x2014; material icon named "speaker notes off sharp".
+  static const IconData speaker_notes_off_sharp = IconData(0xef42, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">speaker_notes</i> &#x2014; material icon named "speaker notes outlined".
+  static const IconData speaker_notes_outlined = IconData(0xe43f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">speaker_notes</i> &#x2014; material icon named "speaker notes rounded".
+  static const IconData speaker_notes_rounded = IconData(0xf472, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">speaker_notes</i> &#x2014; material icon named "speaker notes sharp".
+  static const IconData speaker_notes_sharp = IconData(0xef43, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">speaker</i> &#x2014; material icon named "speaker outlined".
+  static const IconData speaker_outlined = IconData(0xe440, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">speaker_phone</i> &#x2014; material icon named "speaker phone".
+  static const IconData speaker_phone = IconData(0xea0a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">speaker_phone</i> &#x2014; material icon named "speaker phone outlined".
+  static const IconData speaker_phone_outlined = IconData(0xe441, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">speaker_phone</i> &#x2014; material icon named "speaker phone rounded".
+  static const IconData speaker_phone_rounded = IconData(0xf473, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">speaker_phone</i> &#x2014; material icon named "speaker phone sharp".
+  static const IconData speaker_phone_sharp = IconData(0xef44, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">speaker</i> &#x2014; material icon named "speaker rounded".
+  static const IconData speaker_rounded = IconData(0xf474, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">speaker</i> &#x2014; material icon named "speaker sharp".
+  static const IconData speaker_sharp = IconData(0xef45, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">speed</i> &#x2014; material icon named "speed".
+  static const IconData speed = IconData(0xea0b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">speed</i> &#x2014; material icon named "speed outlined".
+  static const IconData speed_outlined = IconData(0xe442, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">speed</i> &#x2014; material icon named "speed rounded".
+  static const IconData speed_rounded = IconData(0xf475, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">speed</i> &#x2014; material icon named "speed sharp".
+  static const IconData speed_sharp = IconData(0xef46, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">spellcheck</i> &#x2014; material icon named "spellcheck".
+  static const IconData spellcheck = IconData(0xea0c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">spellcheck</i> &#x2014; material icon named "spellcheck outlined".
+  static const IconData spellcheck_outlined = IconData(0xe443, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">spellcheck</i> &#x2014; material icon named "spellcheck rounded".
+  static const IconData spellcheck_rounded = IconData(0xf476, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">spellcheck</i> &#x2014; material icon named "spellcheck sharp".
+  static const IconData spellcheck_sharp = IconData(0xef47, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports</i> &#x2014; material icon named "sports".
+  static const IconData sports = IconData(0xea0d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_bar</i> &#x2014; material icon named "sports bar".
+  static const IconData sports_bar = IconData(0xea0e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_bar</i> &#x2014; material icon named "sports bar outlined".
+  static const IconData sports_bar_outlined = IconData(0xe444, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_bar</i> &#x2014; material icon named "sports bar rounded".
+  static const IconData sports_bar_rounded = IconData(0xf477, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_bar</i> &#x2014; material icon named "sports bar sharp".
+  static const IconData sports_bar_sharp = IconData(0xef48, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_baseball</i> &#x2014; material icon named "sports baseball".
+  static const IconData sports_baseball = IconData(0xea0f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_baseball</i> &#x2014; material icon named "sports baseball outlined".
+  static const IconData sports_baseball_outlined = IconData(0xe445, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_baseball</i> &#x2014; material icon named "sports baseball rounded".
+  static const IconData sports_baseball_rounded = IconData(0xf478, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_baseball</i> &#x2014; material icon named "sports baseball sharp".
+  static const IconData sports_baseball_sharp = IconData(0xef49, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_basketball</i> &#x2014; material icon named "sports basketball".
+  static const IconData sports_basketball = IconData(0xea10, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_basketball</i> &#x2014; material icon named "sports basketball outlined".
+  static const IconData sports_basketball_outlined = IconData(0xe446, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_basketball</i> &#x2014; material icon named "sports basketball rounded".
+  static const IconData sports_basketball_rounded = IconData(0xf479, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_basketball</i> &#x2014; material icon named "sports basketball sharp".
+  static const IconData sports_basketball_sharp = IconData(0xef4a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_cricket</i> &#x2014; material icon named "sports cricket".
+  static const IconData sports_cricket = IconData(0xea11, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_cricket</i> &#x2014; material icon named "sports cricket outlined".
+  static const IconData sports_cricket_outlined = IconData(0xe447, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_cricket</i> &#x2014; material icon named "sports cricket rounded".
+  static const IconData sports_cricket_rounded = IconData(0xf47a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_cricket</i> &#x2014; material icon named "sports cricket sharp".
+  static const IconData sports_cricket_sharp = IconData(0xef4b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_esports</i> &#x2014; material icon named "sports esports".
+  static const IconData sports_esports = IconData(0xea12, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_esports</i> &#x2014; material icon named "sports esports outlined".
+  static const IconData sports_esports_outlined = IconData(0xe448, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_esports</i> &#x2014; material icon named "sports esports rounded".
+  static const IconData sports_esports_rounded = IconData(0xf47b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_esports</i> &#x2014; material icon named "sports esports sharp".
+  static const IconData sports_esports_sharp = IconData(0xef4c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_football</i> &#x2014; material icon named "sports football".
+  static const IconData sports_football = IconData(0xea13, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_football</i> &#x2014; material icon named "sports football outlined".
+  static const IconData sports_football_outlined = IconData(0xe449, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_football</i> &#x2014; material icon named "sports football rounded".
+  static const IconData sports_football_rounded = IconData(0xf47c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_football</i> &#x2014; material icon named "sports football sharp".
+  static const IconData sports_football_sharp = IconData(0xef4d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_golf</i> &#x2014; material icon named "sports golf".
+  static const IconData sports_golf = IconData(0xea14, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_golf</i> &#x2014; material icon named "sports golf outlined".
+  static const IconData sports_golf_outlined = IconData(0xe44a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_golf</i> &#x2014; material icon named "sports golf rounded".
+  static const IconData sports_golf_rounded = IconData(0xf47d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_golf</i> &#x2014; material icon named "sports golf sharp".
+  static const IconData sports_golf_sharp = IconData(0xef4e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_handball</i> &#x2014; material icon named "sports handball".
+  static const IconData sports_handball = IconData(0xea15, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_handball</i> &#x2014; material icon named "sports handball outlined".
+  static const IconData sports_handball_outlined = IconData(0xe44b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_handball</i> &#x2014; material icon named "sports handball rounded".
+  static const IconData sports_handball_rounded = IconData(0xf47e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_handball</i> &#x2014; material icon named "sports handball sharp".
+  static const IconData sports_handball_sharp = IconData(0xef4f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_hockey</i> &#x2014; material icon named "sports hockey".
+  static const IconData sports_hockey = IconData(0xea16, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_hockey</i> &#x2014; material icon named "sports hockey outlined".
+  static const IconData sports_hockey_outlined = IconData(0xe44c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_hockey</i> &#x2014; material icon named "sports hockey rounded".
+  static const IconData sports_hockey_rounded = IconData(0xf47f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_hockey</i> &#x2014; material icon named "sports hockey sharp".
+  static const IconData sports_hockey_sharp = IconData(0xef50, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_kabaddi</i> &#x2014; material icon named "sports kabaddi".
+  static const IconData sports_kabaddi = IconData(0xea17, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_kabaddi</i> &#x2014; material icon named "sports kabaddi outlined".
+  static const IconData sports_kabaddi_outlined = IconData(0xe44d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_kabaddi</i> &#x2014; material icon named "sports kabaddi rounded".
+  static const IconData sports_kabaddi_rounded = IconData(0xf480, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_kabaddi</i> &#x2014; material icon named "sports kabaddi sharp".
+  static const IconData sports_kabaddi_sharp = IconData(0xef51, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_mma</i> &#x2014; material icon named "sports mma".
+  static const IconData sports_mma = IconData(0xea18, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_mma</i> &#x2014; material icon named "sports mma outlined".
+  static const IconData sports_mma_outlined = IconData(0xe44e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_mma</i> &#x2014; material icon named "sports mma rounded".
+  static const IconData sports_mma_rounded = IconData(0xf481, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_mma</i> &#x2014; material icon named "sports mma sharp".
+  static const IconData sports_mma_sharp = IconData(0xef52, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_motorsports</i> &#x2014; material icon named "sports motorsports".
+  static const IconData sports_motorsports = IconData(0xea19, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_motorsports</i> &#x2014; material icon named "sports motorsports outlined".
+  static const IconData sports_motorsports_outlined = IconData(0xe44f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_motorsports</i> &#x2014; material icon named "sports motorsports rounded".
+  static const IconData sports_motorsports_rounded = IconData(0xf482, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_motorsports</i> &#x2014; material icon named "sports motorsports sharp".
+  static const IconData sports_motorsports_sharp = IconData(0xef53, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports</i> &#x2014; material icon named "sports outlined".
+  static const IconData sports_outlined = IconData(0xe450, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports</i> &#x2014; material icon named "sports rounded".
+  static const IconData sports_rounded = IconData(0xf483, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_rugby</i> &#x2014; material icon named "sports rugby".
+  static const IconData sports_rugby = IconData(0xea1a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_rugby</i> &#x2014; material icon named "sports rugby outlined".
+  static const IconData sports_rugby_outlined = IconData(0xe451, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_rugby</i> &#x2014; material icon named "sports rugby rounded".
+  static const IconData sports_rugby_rounded = IconData(0xf484, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_rugby</i> &#x2014; material icon named "sports rugby sharp".
+  static const IconData sports_rugby_sharp = IconData(0xef54, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports</i> &#x2014; material icon named "sports sharp".
+  static const IconData sports_sharp = IconData(0xef55, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_soccer</i> &#x2014; material icon named "sports soccer".
+  static const IconData sports_soccer = IconData(0xea1b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_soccer</i> &#x2014; material icon named "sports soccer outlined".
+  static const IconData sports_soccer_outlined = IconData(0xe452, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_soccer</i> &#x2014; material icon named "sports soccer rounded".
+  static const IconData sports_soccer_rounded = IconData(0xf485, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_soccer</i> &#x2014; material icon named "sports soccer sharp".
+  static const IconData sports_soccer_sharp = IconData(0xef56, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_tennis</i> &#x2014; material icon named "sports tennis".
+  static const IconData sports_tennis = IconData(0xea1c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_tennis</i> &#x2014; material icon named "sports tennis outlined".
+  static const IconData sports_tennis_outlined = IconData(0xe453, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_tennis</i> &#x2014; material icon named "sports tennis rounded".
+  static const IconData sports_tennis_rounded = IconData(0xf486, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_tennis</i> &#x2014; material icon named "sports tennis sharp".
+  static const IconData sports_tennis_sharp = IconData(0xef57, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sports_volleyball</i> &#x2014; material icon named "sports volleyball".
+  static const IconData sports_volleyball = IconData(0xea1d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sports_volleyball</i> &#x2014; material icon named "sports volleyball outlined".
+  static const IconData sports_volleyball_outlined = IconData(0xe454, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sports_volleyball</i> &#x2014; material icon named "sports volleyball rounded".
+  static const IconData sports_volleyball_rounded = IconData(0xf487, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sports_volleyball</i> &#x2014; material icon named "sports volleyball sharp".
+  static const IconData sports_volleyball_sharp = IconData(0xef58, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">square_foot</i> &#x2014; material icon named "square foot".
+  static const IconData square_foot = IconData(0xea1e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">square_foot</i> &#x2014; material icon named "square foot outlined".
+  static const IconData square_foot_outlined = IconData(0xe455, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">square_foot</i> &#x2014; material icon named "square foot rounded".
+  static const IconData square_foot_rounded = IconData(0xf488, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">square_foot</i> &#x2014; material icon named "square foot sharp".
+  static const IconData square_foot_sharp = IconData(0xef59, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">stacked_bar_chart</i> &#x2014; material icon named "stacked bar chart".
+  static const IconData stacked_bar_chart = IconData(0xea1f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">stacked_line_chart</i> &#x2014; material icon named "stacked line chart".
+  static const IconData stacked_line_chart = IconData(0xea20, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">stacked_line_chart</i> &#x2014; material icon named "stacked line chart outlined".
+  static const IconData stacked_line_chart_outlined = IconData(0xe456, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">stacked_line_chart</i> &#x2014; material icon named "stacked line chart rounded".
+  static const IconData stacked_line_chart_rounded = IconData(0xf489, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">stacked_line_chart</i> &#x2014; material icon named "stacked line chart sharp".
+  static const IconData stacked_line_chart_sharp = IconData(0xef5a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">stairs</i> &#x2014; material icon named "stairs".
+  static const IconData stairs = IconData(0xea21, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">stairs</i> &#x2014; material icon named "stairs outlined".
+  static const IconData stairs_outlined = IconData(0xe457, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">stairs</i> &#x2014; material icon named "stairs rounded".
+  static const IconData stairs_rounded = IconData(0xf48a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">stairs</i> &#x2014; material icon named "stairs sharp".
+  static const IconData stairs_sharp = IconData(0xef5b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">star</i> &#x2014; material icon named "star".
+  static const IconData star = IconData(0xea22, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">star_border</i> &#x2014; material icon named "star border".
+  static const IconData star_border = IconData(0xea23, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">star_border</i> &#x2014; material icon named "star border outlined".
+  static const IconData star_border_outlined = IconData(0xe458, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">star_border_purple500</i> &#x2014; material icon named "star border purple500 outlined".
+  static const IconData star_border_purple500_outlined = IconData(0xe459, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">star_border_purple500</i> &#x2014; material icon named "star border purple500 sharp".
+  static const IconData star_border_purple500_sharp = IconData(0xef5c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">star_border</i> &#x2014; material icon named "star border rounded".
+  static const IconData star_border_rounded = IconData(0xf48b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">star_border</i> &#x2014; material icon named "star border sharp".
+  static const IconData star_border_sharp = IconData(0xef5d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">star_half</i> &#x2014; material icon named "star half".
+  static const IconData star_half = IconData(0xea24, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">star_half</i> &#x2014; material icon named "star half outlined".
+  static const IconData star_half_outlined = IconData(0xe45a, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">star_half</i> &#x2014; material icon named "star half rounded".
+  static const IconData star_half_rounded = IconData(0xf48c, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">star_half</i> &#x2014; material icon named "star half sharp".
+  static const IconData star_half_sharp = IconData(0xef5e, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">star_outline</i> &#x2014; material icon named "star outline".
+  static const IconData star_outline = IconData(0xea25, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">star_outline</i> &#x2014; material icon named "star outline outlined".
+  static const IconData star_outline_outlined = IconData(0xe45b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">star_outline</i> &#x2014; material icon named "star outline rounded".
+  static const IconData star_outline_rounded = IconData(0xf48d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">star_outline</i> &#x2014; material icon named "star outline sharp".
+  static const IconData star_outline_sharp = IconData(0xef5f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">star</i> &#x2014; material icon named "star outlined".
+  static const IconData star_outlined = IconData(0xe45c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">star_purple500</i> &#x2014; material icon named "star purple500 outlined".
+  static const IconData star_purple500_outlined = IconData(0xe45d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">star_purple500</i> &#x2014; material icon named "star purple500 sharp".
+  static const IconData star_purple500_sharp = IconData(0xef60, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">star_rate</i> &#x2014; material icon named "star rate".
+  static const IconData star_rate = IconData(0xea26, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">star_rate</i> &#x2014; material icon named "star rate outlined".
+  static const IconData star_rate_outlined = IconData(0xe45e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">star_rate</i> &#x2014; material icon named "star rate rounded".
+  static const IconData star_rate_rounded = IconData(0xf48e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">star_rate</i> &#x2014; material icon named "star rate sharp".
+  static const IconData star_rate_sharp = IconData(0xef61, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">star</i> &#x2014; material icon named "star rounded".
+  static const IconData star_rounded = IconData(0xf48f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">star</i> &#x2014; material icon named "star sharp".
+  static const IconData star_sharp = IconData(0xef62, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">stars</i> &#x2014; material icon named "stars".
+  static const IconData stars = IconData(0xea27, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">stars</i> &#x2014; material icon named "stars outlined".
+  static const IconData stars_outlined = IconData(0xe45f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">stars</i> &#x2014; material icon named "stars rounded".
+  static const IconData stars_rounded = IconData(0xf490, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">stars</i> &#x2014; material icon named "stars sharp".
+  static const IconData stars_sharp = IconData(0xef63, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">stay_current_landscape</i> &#x2014; material icon named "stay current landscape".
+  static const IconData stay_current_landscape = IconData(0xea28, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">stay_current_landscape</i> &#x2014; material icon named "stay current landscape outlined".
+  static const IconData stay_current_landscape_outlined = IconData(0xe460, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">stay_current_landscape</i> &#x2014; material icon named "stay current landscape rounded".
+  static const IconData stay_current_landscape_rounded = IconData(0xf491, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">stay_current_landscape</i> &#x2014; material icon named "stay current landscape sharp".
+  static const IconData stay_current_landscape_sharp = IconData(0xef64, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">stay_current_portrait</i> &#x2014; material icon named "stay current portrait".
+  static const IconData stay_current_portrait = IconData(0xea29, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">stay_current_portrait</i> &#x2014; material icon named "stay current portrait outlined".
+  static const IconData stay_current_portrait_outlined = IconData(0xe461, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">stay_current_portrait</i> &#x2014; material icon named "stay current portrait rounded".
+  static const IconData stay_current_portrait_rounded = IconData(0xf492, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">stay_current_portrait</i> &#x2014; material icon named "stay current portrait sharp".
+  static const IconData stay_current_portrait_sharp = IconData(0xef65, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">stay_primary_landscape</i> &#x2014; material icon named "stay primary landscape".
+  static const IconData stay_primary_landscape = IconData(0xea2a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">stay_primary_landscape</i> &#x2014; material icon named "stay primary landscape outlined".
+  static const IconData stay_primary_landscape_outlined = IconData(0xe462, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">stay_primary_landscape</i> &#x2014; material icon named "stay primary landscape rounded".
+  static const IconData stay_primary_landscape_rounded = IconData(0xf493, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">stay_primary_landscape</i> &#x2014; material icon named "stay primary landscape sharp".
+  static const IconData stay_primary_landscape_sharp = IconData(0xef66, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">stay_primary_portrait</i> &#x2014; material icon named "stay primary portrait".
+  static const IconData stay_primary_portrait = IconData(0xea2b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">stay_primary_portrait</i> &#x2014; material icon named "stay primary portrait outlined".
+  static const IconData stay_primary_portrait_outlined = IconData(0xe463, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">stay_primary_portrait</i> &#x2014; material icon named "stay primary portrait rounded".
+  static const IconData stay_primary_portrait_rounded = IconData(0xf494, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">stay_primary_portrait</i> &#x2014; material icon named "stay primary portrait sharp".
+  static const IconData stay_primary_portrait_sharp = IconData(0xef67, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sticky_note_2</i> &#x2014; material icon named "sticky note 2".
+  static const IconData sticky_note_2 = IconData(0xea2c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sticky_note_2</i> &#x2014; material icon named "sticky note 2 outlined".
+  static const IconData sticky_note_2_outlined = IconData(0xe464, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sticky_note_2</i> &#x2014; material icon named "sticky note 2 rounded".
+  static const IconData sticky_note_2_rounded = IconData(0xf495, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sticky_note_2</i> &#x2014; material icon named "sticky note 2 sharp".
+  static const IconData sticky_note_2_sharp = IconData(0xef68, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">stop</i> &#x2014; material icon named "stop".
+  static const IconData stop = IconData(0xea2d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">stop_circle</i> &#x2014; material icon named "stop circle".
+  static const IconData stop_circle = IconData(0xea2e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">stop_circle</i> &#x2014; material icon named "stop circle outlined".
+  static const IconData stop_circle_outlined = IconData(0xe465, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">stop_circle</i> &#x2014; material icon named "stop circle rounded".
+  static const IconData stop_circle_rounded = IconData(0xf496, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">stop_circle</i> &#x2014; material icon named "stop circle sharp".
+  static const IconData stop_circle_sharp = IconData(0xef69, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">stop</i> &#x2014; material icon named "stop outlined".
+  static const IconData stop_outlined = IconData(0xe466, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">stop</i> &#x2014; material icon named "stop rounded".
+  static const IconData stop_rounded = IconData(0xf497, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">stop_screen_share</i> &#x2014; material icon named "stop screen share".
+  static const IconData stop_screen_share = IconData(0xea2f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">stop_screen_share</i> &#x2014; material icon named "stop screen share outlined".
+  static const IconData stop_screen_share_outlined = IconData(0xe467, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">stop_screen_share</i> &#x2014; material icon named "stop screen share rounded".
+  static const IconData stop_screen_share_rounded = IconData(0xf498, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">stop_screen_share</i> &#x2014; material icon named "stop screen share sharp".
+  static const IconData stop_screen_share_sharp = IconData(0xef6a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">stop</i> &#x2014; material icon named "stop sharp".
+  static const IconData stop_sharp = IconData(0xef6b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">storage</i> &#x2014; material icon named "storage".
+  static const IconData storage = IconData(0xea30, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">storage</i> &#x2014; material icon named "storage outlined".
+  static const IconData storage_outlined = IconData(0xe468, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">storage</i> &#x2014; material icon named "storage rounded".
+  static const IconData storage_rounded = IconData(0xf499, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">storage</i> &#x2014; material icon named "storage sharp".
+  static const IconData storage_sharp = IconData(0xef6c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">store</i> &#x2014; material icon named "store".
+  static const IconData store = IconData(0xea31, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">store_mall_directory</i> &#x2014; material icon named "store mall directory".
+  static const IconData store_mall_directory = IconData(0xea32, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">store_mall_directory</i> &#x2014; material icon named "store mall directory outlined".
+  static const IconData store_mall_directory_outlined = IconData(0xe469, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">store_mall_directory</i> &#x2014; material icon named "store mall directory rounded".
+  static const IconData store_mall_directory_rounded = IconData(0xf49a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">store_mall_directory</i> &#x2014; material icon named "store mall directory sharp".
+  static const IconData store_mall_directory_sharp = IconData(0xef6d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">store</i> &#x2014; material icon named "store outlined".
+  static const IconData store_outlined = IconData(0xe46a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">store</i> &#x2014; material icon named "store rounded".
+  static const IconData store_rounded = IconData(0xf49b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">store</i> &#x2014; material icon named "store sharp".
+  static const IconData store_sharp = IconData(0xef6e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">storefront</i> &#x2014; material icon named "storefront".
+  static const IconData storefront = IconData(0xea33, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">storefront</i> &#x2014; material icon named "storefront outlined".
+  static const IconData storefront_outlined = IconData(0xe46b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">storefront</i> &#x2014; material icon named "storefront rounded".
+  static const IconData storefront_rounded = IconData(0xf49c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">storefront</i> &#x2014; material icon named "storefront sharp".
+  static const IconData storefront_sharp = IconData(0xef6f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">straighten</i> &#x2014; material icon named "straighten".
+  static const IconData straighten = IconData(0xea34, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">straighten</i> &#x2014; material icon named "straighten outlined".
+  static const IconData straighten_outlined = IconData(0xe46c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">straighten</i> &#x2014; material icon named "straighten rounded".
+  static const IconData straighten_rounded = IconData(0xf49d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">straighten</i> &#x2014; material icon named "straighten sharp".
+  static const IconData straighten_sharp = IconData(0xef70, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">stream</i> &#x2014; material icon named "stream".
+  static const IconData stream = IconData(0xea35, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">streetview</i> &#x2014; material icon named "streetview".
+  static const IconData streetview = IconData(0xea36, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">streetview</i> &#x2014; material icon named "streetview outlined".
+  static const IconData streetview_outlined = IconData(0xe46d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">streetview</i> &#x2014; material icon named "streetview rounded".
+  static const IconData streetview_rounded = IconData(0xf49e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">streetview</i> &#x2014; material icon named "streetview sharp".
+  static const IconData streetview_sharp = IconData(0xef71, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">strikethrough_s</i> &#x2014; material icon named "strikethrough s".
+  static const IconData strikethrough_s = IconData(0xea37, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">strikethrough_s</i> &#x2014; material icon named "strikethrough s outlined".
+  static const IconData strikethrough_s_outlined = IconData(0xe46e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">strikethrough_s</i> &#x2014; material icon named "strikethrough s rounded".
+  static const IconData strikethrough_s_rounded = IconData(0xf49f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">strikethrough_s</i> &#x2014; material icon named "strikethrough s sharp".
+  static const IconData strikethrough_s_sharp = IconData(0xef72, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">stroller</i> &#x2014; material icon named "stroller".
+  static const IconData stroller = IconData(0xea38, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">stroller</i> &#x2014; material icon named "stroller outlined".
+  static const IconData stroller_outlined = IconData(0xe46f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">stroller</i> &#x2014; material icon named "stroller rounded".
+  static const IconData stroller_rounded = IconData(0xf4a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">stroller</i> &#x2014; material icon named "stroller sharp".
+  static const IconData stroller_sharp = IconData(0xef73, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">style</i> &#x2014; material icon named "style".
+  static const IconData style = IconData(0xea39, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">style</i> &#x2014; material icon named "style outlined".
+  static const IconData style_outlined = IconData(0xe470, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">style</i> &#x2014; material icon named "style rounded".
+  static const IconData style_rounded = IconData(0xf4a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">style</i> &#x2014; material icon named "style sharp".
+  static const IconData style_sharp = IconData(0xef74, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">subdirectory_arrow_left</i> &#x2014; material icon named "subdirectory arrow left".
+  static const IconData subdirectory_arrow_left = IconData(0xea3a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">subdirectory_arrow_left</i> &#x2014; material icon named "subdirectory arrow left outlined".
+  static const IconData subdirectory_arrow_left_outlined = IconData(0xe471, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">subdirectory_arrow_left</i> &#x2014; material icon named "subdirectory arrow left rounded".
+  static const IconData subdirectory_arrow_left_rounded = IconData(0xf4a2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">subdirectory_arrow_left</i> &#x2014; material icon named "subdirectory arrow left sharp".
+  static const IconData subdirectory_arrow_left_sharp = IconData(0xef75, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">subdirectory_arrow_right</i> &#x2014; material icon named "subdirectory arrow right".
+  static const IconData subdirectory_arrow_right = IconData(0xea3b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">subdirectory_arrow_right</i> &#x2014; material icon named "subdirectory arrow right outlined".
+  static const IconData subdirectory_arrow_right_outlined = IconData(0xe472, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">subdirectory_arrow_right</i> &#x2014; material icon named "subdirectory arrow right rounded".
+  static const IconData subdirectory_arrow_right_rounded = IconData(0xf4a3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">subdirectory_arrow_right</i> &#x2014; material icon named "subdirectory arrow right sharp".
+  static const IconData subdirectory_arrow_right_sharp = IconData(0xef76, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">subject</i> &#x2014; material icon named "subject".
+  static const IconData subject = IconData(0xea3c, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">subject</i> &#x2014; material icon named "subject outlined".
+  static const IconData subject_outlined = IconData(0xe473, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">subject</i> &#x2014; material icon named "subject rounded".
+  static const IconData subject_rounded = IconData(0xf4a4, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">subject</i> &#x2014; material icon named "subject sharp".
+  static const IconData subject_sharp = IconData(0xef77, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">subscript</i> &#x2014; material icon named "subscript".
+  static const IconData subscript = IconData(0xea3d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">subscript</i> &#x2014; material icon named "subscript outlined".
+  static const IconData subscript_outlined = IconData(0xe474, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">subscript</i> &#x2014; material icon named "subscript rounded".
+  static const IconData subscript_rounded = IconData(0xf4a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">subscript</i> &#x2014; material icon named "subscript sharp".
+  static const IconData subscript_sharp = IconData(0xef78, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">subscriptions</i> &#x2014; material icon named "subscriptions".
+  static const IconData subscriptions = IconData(0xea3e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">subscriptions</i> &#x2014; material icon named "subscriptions outlined".
+  static const IconData subscriptions_outlined = IconData(0xe475, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">subscriptions</i> &#x2014; material icon named "subscriptions rounded".
+  static const IconData subscriptions_rounded = IconData(0xf4a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">subscriptions</i> &#x2014; material icon named "subscriptions sharp".
+  static const IconData subscriptions_sharp = IconData(0xef79, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">subtitles</i> &#x2014; material icon named "subtitles".
+  static const IconData subtitles = IconData(0xea3f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">subtitles_off</i> &#x2014; material icon named "subtitles off".
+  static const IconData subtitles_off = IconData(0xea40, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">subtitles_off</i> &#x2014; material icon named "subtitles off outlined".
+  static const IconData subtitles_off_outlined = IconData(0xe476, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">subtitles_off</i> &#x2014; material icon named "subtitles off rounded".
+  static const IconData subtitles_off_rounded = IconData(0xf4a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">subtitles_off</i> &#x2014; material icon named "subtitles off sharp".
+  static const IconData subtitles_off_sharp = IconData(0xef7a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">subtitles</i> &#x2014; material icon named "subtitles outlined".
+  static const IconData subtitles_outlined = IconData(0xe477, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">subtitles</i> &#x2014; material icon named "subtitles rounded".
+  static const IconData subtitles_rounded = IconData(0xf4a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">subtitles</i> &#x2014; material icon named "subtitles sharp".
+  static const IconData subtitles_sharp = IconData(0xef7b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">subway</i> &#x2014; material icon named "subway".
+  static const IconData subway = IconData(0xea41, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">subway</i> &#x2014; material icon named "subway outlined".
+  static const IconData subway_outlined = IconData(0xe478, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">subway</i> &#x2014; material icon named "subway rounded".
+  static const IconData subway_rounded = IconData(0xf4a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">subway</i> &#x2014; material icon named "subway sharp".
+  static const IconData subway_sharp = IconData(0xef7c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">superscript</i> &#x2014; material icon named "superscript".
+  static const IconData superscript = IconData(0xea42, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">superscript</i> &#x2014; material icon named "superscript outlined".
+  static const IconData superscript_outlined = IconData(0xe479, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">superscript</i> &#x2014; material icon named "superscript rounded".
+  static const IconData superscript_rounded = IconData(0xf4aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">superscript</i> &#x2014; material icon named "superscript sharp".
+  static const IconData superscript_sharp = IconData(0xef7d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">supervised_user_circle</i> &#x2014; material icon named "supervised user circle".
+  static const IconData supervised_user_circle = IconData(0xea43, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">supervised_user_circle</i> &#x2014; material icon named "supervised user circle outlined".
+  static const IconData supervised_user_circle_outlined = IconData(0xe47a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">supervised_user_circle</i> &#x2014; material icon named "supervised user circle rounded".
+  static const IconData supervised_user_circle_rounded = IconData(0xf4ab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">supervised_user_circle</i> &#x2014; material icon named "supervised user circle sharp".
+  static const IconData supervised_user_circle_sharp = IconData(0xef7e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">supervisor_account</i> &#x2014; material icon named "supervisor account".
+  static const IconData supervisor_account = IconData(0xea44, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">supervisor_account</i> &#x2014; material icon named "supervisor account outlined".
+  static const IconData supervisor_account_outlined = IconData(0xe47b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">supervisor_account</i> &#x2014; material icon named "supervisor account rounded".
+  static const IconData supervisor_account_rounded = IconData(0xf4ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">supervisor_account</i> &#x2014; material icon named "supervisor account sharp".
+  static const IconData supervisor_account_sharp = IconData(0xef7f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">support</i> &#x2014; material icon named "support".
+  static const IconData support = IconData(0xea45, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">support_agent</i> &#x2014; material icon named "support agent".
+  static const IconData support_agent = IconData(0xea46, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">support_agent</i> &#x2014; material icon named "support agent outlined".
+  static const IconData support_agent_outlined = IconData(0xe47c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">support_agent</i> &#x2014; material icon named "support agent rounded".
+  static const IconData support_agent_rounded = IconData(0xf4ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">support_agent</i> &#x2014; material icon named "support agent sharp".
+  static const IconData support_agent_sharp = IconData(0xef80, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">support</i> &#x2014; material icon named "support outlined".
+  static const IconData support_outlined = IconData(0xe47d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">support</i> &#x2014; material icon named "support rounded".
+  static const IconData support_rounded = IconData(0xf4ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">support</i> &#x2014; material icon named "support sharp".
+  static const IconData support_sharp = IconData(0xef81, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">surround_sound</i> &#x2014; material icon named "surround sound".
+  static const IconData surround_sound = IconData(0xea47, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">surround_sound</i> &#x2014; material icon named "surround sound outlined".
+  static const IconData surround_sound_outlined = IconData(0xe47e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">surround_sound</i> &#x2014; material icon named "surround sound rounded".
+  static const IconData surround_sound_rounded = IconData(0xf4af, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">surround_sound</i> &#x2014; material icon named "surround sound sharp".
+  static const IconData surround_sound_sharp = IconData(0xef82, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">swap_calls</i> &#x2014; material icon named "swap calls".
+  static const IconData swap_calls = IconData(0xea48, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">swap_calls</i> &#x2014; material icon named "swap calls outlined".
+  static const IconData swap_calls_outlined = IconData(0xe47f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">swap_calls</i> &#x2014; material icon named "swap calls rounded".
+  static const IconData swap_calls_rounded = IconData(0xf4b0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">swap_calls</i> &#x2014; material icon named "swap calls sharp".
+  static const IconData swap_calls_sharp = IconData(0xef83, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">swap_horiz</i> &#x2014; material icon named "swap horiz".
+  static const IconData swap_horiz = IconData(0xea49, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">swap_horiz</i> &#x2014; material icon named "swap horiz outlined".
+  static const IconData swap_horiz_outlined = IconData(0xe480, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">swap_horiz</i> &#x2014; material icon named "swap horiz rounded".
+  static const IconData swap_horiz_rounded = IconData(0xf4b1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">swap_horiz</i> &#x2014; material icon named "swap horiz sharp".
+  static const IconData swap_horiz_sharp = IconData(0xef84, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">swap_horizontal_circle</i> &#x2014; material icon named "swap horizontal circle".
+  static const IconData swap_horizontal_circle = IconData(0xea4a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">swap_horizontal_circle</i> &#x2014; material icon named "swap horizontal circle outlined".
+  static const IconData swap_horizontal_circle_outlined = IconData(0xe481, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">swap_horizontal_circle</i> &#x2014; material icon named "swap horizontal circle rounded".
+  static const IconData swap_horizontal_circle_rounded = IconData(0xf4b2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">swap_horizontal_circle</i> &#x2014; material icon named "swap horizontal circle sharp".
+  static const IconData swap_horizontal_circle_sharp = IconData(0xef85, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">swap_vert</i> &#x2014; material icon named "swap vert".
+  static const IconData swap_vert = IconData(0xea4b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">swap_vert_circle</i> &#x2014; material icon named "swap vert circle".
+  static const IconData swap_vert_circle = IconData(0xea4c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">swap_vert_circle</i> &#x2014; material icon named "swap vert circle outlined".
+  static const IconData swap_vert_circle_outlined = IconData(0xe483, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">swap_vert_circle</i> &#x2014; material icon named "swap vert circle rounded".
+  static const IconData swap_vert_circle_rounded = IconData(0xf4b4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">swap_vert_circle</i> &#x2014; material icon named "swap vert circle sharp".
+  static const IconData swap_vert_circle_sharp = IconData(0xef87, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">swap_vert</i> &#x2014; material icon named "swap vert outlined".
+  static const IconData swap_vert_outlined = IconData(0xe482, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">swap_vert</i> &#x2014; material icon named "swap vert rounded".
+  static const IconData swap_vert_rounded = IconData(0xf4b3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">swap_vert</i> &#x2014; material icon named "swap vert sharp".
+  static const IconData swap_vert_sharp = IconData(0xef86, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">swap_vertical_circle</i> &#x2014; material icon named "swap vertical circle".
+  static const IconData swap_vertical_circle = IconData(0xea4c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">swap_vertical_circle</i> &#x2014; material icon named "swap vertical circle outlined".
+  static const IconData swap_vertical_circle_outlined = IconData(0xe483, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">swap_vertical_circle</i> &#x2014; material icon named "swap vertical circle rounded".
+  static const IconData swap_vertical_circle_rounded = IconData(0xf4b4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">swap_vertical_circle</i> &#x2014; material icon named "swap vertical circle sharp".
+  static const IconData swap_vertical_circle_sharp = IconData(0xef87, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">swipe</i> &#x2014; material icon named "swipe".
+  static const IconData swipe = IconData(0xea4d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">switch_account</i> &#x2014; material icon named "switch account".
+  static const IconData switch_account = IconData(0xea4e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">switch_camera</i> &#x2014; material icon named "switch camera".
+  static const IconData switch_camera = IconData(0xea4f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">switch_camera</i> &#x2014; material icon named "switch camera outlined".
+  static const IconData switch_camera_outlined = IconData(0xe484, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">switch_camera</i> &#x2014; material icon named "switch camera rounded".
+  static const IconData switch_camera_rounded = IconData(0xf4b5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">switch_camera</i> &#x2014; material icon named "switch camera sharp".
+  static const IconData switch_camera_sharp = IconData(0xef88, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">switch_left</i> &#x2014; material icon named "switch left".
+  static const IconData switch_left = IconData(0xea50, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">switch_left</i> &#x2014; material icon named "switch left outlined".
+  static const IconData switch_left_outlined = IconData(0xe485, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">switch_left</i> &#x2014; material icon named "switch left rounded".
+  static const IconData switch_left_rounded = IconData(0xf4b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">switch_left</i> &#x2014; material icon named "switch left sharp".
+  static const IconData switch_left_sharp = IconData(0xef89, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">switch_right</i> &#x2014; material icon named "switch right".
+  static const IconData switch_right = IconData(0xea51, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">switch_right</i> &#x2014; material icon named "switch right outlined".
+  static const IconData switch_right_outlined = IconData(0xe486, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">switch_right</i> &#x2014; material icon named "switch right rounded".
+  static const IconData switch_right_rounded = IconData(0xf4b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">switch_right</i> &#x2014; material icon named "switch right sharp".
+  static const IconData switch_right_sharp = IconData(0xef8a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">switch_video</i> &#x2014; material icon named "switch video".
+  static const IconData switch_video = IconData(0xea52, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">switch_video</i> &#x2014; material icon named "switch video outlined".
+  static const IconData switch_video_outlined = IconData(0xe487, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">switch_video</i> &#x2014; material icon named "switch video rounded".
+  static const IconData switch_video_rounded = IconData(0xf4b8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">switch_video</i> &#x2014; material icon named "switch video sharp".
+  static const IconData switch_video_sharp = IconData(0xef8b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sync</i> &#x2014; material icon named "sync".
+  static const IconData sync = IconData(0xea53, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sync_alt</i> &#x2014; material icon named "sync alt".
+  static const IconData sync_alt = IconData(0xea54, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sync_alt</i> &#x2014; material icon named "sync alt outlined".
+  static const IconData sync_alt_outlined = IconData(0xe488, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sync_alt</i> &#x2014; material icon named "sync alt rounded".
+  static const IconData sync_alt_rounded = IconData(0xf4b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sync_alt</i> &#x2014; material icon named "sync alt sharp".
+  static const IconData sync_alt_sharp = IconData(0xef8c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sync_disabled</i> &#x2014; material icon named "sync disabled".
+  static const IconData sync_disabled = IconData(0xea55, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sync_disabled</i> &#x2014; material icon named "sync disabled outlined".
+  static const IconData sync_disabled_outlined = IconData(0xe489, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sync_disabled</i> &#x2014; material icon named "sync disabled rounded".
+  static const IconData sync_disabled_rounded = IconData(0xf4ba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sync_disabled</i> &#x2014; material icon named "sync disabled sharp".
+  static const IconData sync_disabled_sharp = IconData(0xef8d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sync</i> &#x2014; material icon named "sync outlined".
+  static const IconData sync_outlined = IconData(0xe48a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">sync_problem</i> &#x2014; material icon named "sync problem".
+  static const IconData sync_problem = IconData(0xea56, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">sync_problem</i> &#x2014; material icon named "sync problem outlined".
+  static const IconData sync_problem_outlined = IconData(0xe48b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sync_problem</i> &#x2014; material icon named "sync problem rounded".
+  static const IconData sync_problem_rounded = IconData(0xf4bb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sync_problem</i> &#x2014; material icon named "sync problem sharp".
+  static const IconData sync_problem_sharp = IconData(0xef8e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">sync</i> &#x2014; material icon named "sync rounded".
+  static const IconData sync_rounded = IconData(0xf4bc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">sync</i> &#x2014; material icon named "sync sharp".
+  static const IconData sync_sharp = IconData(0xef8f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">system_update</i> &#x2014; material icon named "system update".
+  static const IconData system_update = IconData(0xea57, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">system_update_alt</i> &#x2014; material icon named "system update alt".
+  static const IconData system_update_alt = IconData(0xea58, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">system_update_alt</i> &#x2014; material icon named "system update alt outlined".
+  static const IconData system_update_alt_outlined = IconData(0xe48c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">system_update_alt</i> &#x2014; material icon named "system update alt rounded".
+  static const IconData system_update_alt_rounded = IconData(0xf4bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">system_update_alt</i> &#x2014; material icon named "system update alt sharp".
+  static const IconData system_update_alt_sharp = IconData(0xef90, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">system_update</i> &#x2014; material icon named "system update outlined".
+  static const IconData system_update_outlined = IconData(0xe48d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">system_update</i> &#x2014; material icon named "system update rounded".
+  static const IconData system_update_rounded = IconData(0xf4be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">system_update</i> &#x2014; material icon named "system update sharp".
+  static const IconData system_update_sharp = IconData(0xef91, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">system_update_tv</i> &#x2014; material icon named "system update tv".
+  static const IconData system_update_tv = IconData(0xea58, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">system_update_tv</i> &#x2014; material icon named "system update tv outlined".
+  static const IconData system_update_tv_outlined = IconData(0xe48c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">system_update_tv</i> &#x2014; material icon named "system update tv rounded".
+  static const IconData system_update_tv_rounded = IconData(0xf4bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">system_update_tv</i> &#x2014; material icon named "system update tv sharp".
+  static const IconData system_update_tv_sharp = IconData(0xef90, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tab</i> &#x2014; material icon named "tab".
+  static const IconData tab = IconData(0xea59, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tab</i> &#x2014; material icon named "tab outlined".
+  static const IconData tab_outlined = IconData(0xe48e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tab</i> &#x2014; material icon named "tab rounded".
+  static const IconData tab_rounded = IconData(0xf4bf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tab</i> &#x2014; material icon named "tab sharp".
+  static const IconData tab_sharp = IconData(0xef92, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tab_unselected</i> &#x2014; material icon named "tab unselected".
+  static const IconData tab_unselected = IconData(0xea5a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tab_unselected</i> &#x2014; material icon named "tab unselected outlined".
+  static const IconData tab_unselected_outlined = IconData(0xe48f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tab_unselected</i> &#x2014; material icon named "tab unselected rounded".
+  static const IconData tab_unselected_rounded = IconData(0xf4c0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tab_unselected</i> &#x2014; material icon named "tab unselected sharp".
+  static const IconData tab_unselected_sharp = IconData(0xef93, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">table_chart</i> &#x2014; material icon named "table chart".
+  static const IconData table_chart = IconData(0xea5b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">table_chart</i> &#x2014; material icon named "table chart outlined".
+  static const IconData table_chart_outlined = IconData(0xe490, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">table_chart</i> &#x2014; material icon named "table chart rounded".
+  static const IconData table_chart_rounded = IconData(0xf4c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">table_chart</i> &#x2014; material icon named "table chart sharp".
+  static const IconData table_chart_sharp = IconData(0xef94, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">table_rows</i> &#x2014; material icon named "table rows".
+  static const IconData table_rows = IconData(0xea5c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">table_rows</i> &#x2014; material icon named "table rows outlined".
+  static const IconData table_rows_outlined = IconData(0xe491, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">table_rows</i> &#x2014; material icon named "table rows rounded".
+  static const IconData table_rows_rounded = IconData(0xf4c2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">table_rows</i> &#x2014; material icon named "table rows sharp".
+  static const IconData table_rows_sharp = IconData(0xef95, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">table_view</i> &#x2014; material icon named "table view".
+  static const IconData table_view = IconData(0xea5d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">table_view</i> &#x2014; material icon named "table view outlined".
+  static const IconData table_view_outlined = IconData(0xe492, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">table_view</i> &#x2014; material icon named "table view rounded".
+  static const IconData table_view_rounded = IconData(0xf4c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">table_view</i> &#x2014; material icon named "table view sharp".
+  static const IconData table_view_sharp = IconData(0xef96, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tablet</i> &#x2014; material icon named "tablet".
+  static const IconData tablet = IconData(0xea5e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tablet_android</i> &#x2014; material icon named "tablet android".
+  static const IconData tablet_android = IconData(0xea5f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tablet_android</i> &#x2014; material icon named "tablet android outlined".
+  static const IconData tablet_android_outlined = IconData(0xe493, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tablet_android</i> &#x2014; material icon named "tablet android rounded".
+  static const IconData tablet_android_rounded = IconData(0xf4c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tablet_android</i> &#x2014; material icon named "tablet android sharp".
+  static const IconData tablet_android_sharp = IconData(0xef97, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tablet_mac</i> &#x2014; material icon named "tablet mac".
+  static const IconData tablet_mac = IconData(0xea60, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tablet_mac</i> &#x2014; material icon named "tablet mac outlined".
+  static const IconData tablet_mac_outlined = IconData(0xe494, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tablet_mac</i> &#x2014; material icon named "tablet mac rounded".
+  static const IconData tablet_mac_rounded = IconData(0xf4c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tablet_mac</i> &#x2014; material icon named "tablet mac sharp".
+  static const IconData tablet_mac_sharp = IconData(0xef98, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tablet</i> &#x2014; material icon named "tablet outlined".
+  static const IconData tablet_outlined = IconData(0xe495, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tablet</i> &#x2014; material icon named "tablet rounded".
+  static const IconData tablet_rounded = IconData(0xf4c6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tablet</i> &#x2014; material icon named "tablet sharp".
+  static const IconData tablet_sharp = IconData(0xef99, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tag</i> &#x2014; material icon named "tag".
+  static const IconData tag = IconData(0xea61, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tag_faces</i> &#x2014; material icon named "tag faces".
+  static const IconData tag_faces = IconData(0xea62, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tag_faces</i> &#x2014; material icon named "tag faces outlined".
+  static const IconData tag_faces_outlined = IconData(0xe496, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tag_faces</i> &#x2014; material icon named "tag faces rounded".
+  static const IconData tag_faces_rounded = IconData(0xf4c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tag_faces</i> &#x2014; material icon named "tag faces sharp".
+  static const IconData tag_faces_sharp = IconData(0xef9a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">takeout_dining</i> &#x2014; material icon named "takeout dining".
+  static const IconData takeout_dining = IconData(0xea63, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tap_and_play</i> &#x2014; material icon named "tap and play".
+  static const IconData tap_and_play = IconData(0xea64, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tap_and_play</i> &#x2014; material icon named "tap and play outlined".
+  static const IconData tap_and_play_outlined = IconData(0xe497, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tap_and_play</i> &#x2014; material icon named "tap and play rounded".
+  static const IconData tap_and_play_rounded = IconData(0xf4c8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tap_and_play</i> &#x2014; material icon named "tap and play sharp".
+  static const IconData tap_and_play_sharp = IconData(0xef9b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tapas</i> &#x2014; material icon named "tapas".
+  static const IconData tapas = IconData(0xea65, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tapas</i> &#x2014; material icon named "tapas outlined".
+  static const IconData tapas_outlined = IconData(0xe498, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tapas</i> &#x2014; material icon named "tapas rounded".
+  static const IconData tapas_rounded = IconData(0xf4c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tapas</i> &#x2014; material icon named "tapas sharp".
+  static const IconData tapas_sharp = IconData(0xef9c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">taxi_alert</i> &#x2014; material icon named "taxi alert".
+  static const IconData taxi_alert = IconData(0xea66, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">terrain</i> &#x2014; material icon named "terrain".
+  static const IconData terrain = IconData(0xea67, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">terrain</i> &#x2014; material icon named "terrain outlined".
+  static const IconData terrain_outlined = IconData(0xe499, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">terrain</i> &#x2014; material icon named "terrain rounded".
+  static const IconData terrain_rounded = IconData(0xf4ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">terrain</i> &#x2014; material icon named "terrain sharp".
+  static const IconData terrain_sharp = IconData(0xef9d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">text_fields</i> &#x2014; material icon named "text fields".
+  static const IconData text_fields = IconData(0xea68, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">text_fields</i> &#x2014; material icon named "text fields outlined".
+  static const IconData text_fields_outlined = IconData(0xe49a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">text_fields</i> &#x2014; material icon named "text fields rounded".
+  static const IconData text_fields_rounded = IconData(0xf4cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">text_fields</i> &#x2014; material icon named "text fields sharp".
+  static const IconData text_fields_sharp = IconData(0xef9e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">text_format</i> &#x2014; material icon named "text format".
+  static const IconData text_format = IconData(0xea69, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">text_format</i> &#x2014; material icon named "text format outlined".
+  static const IconData text_format_outlined = IconData(0xe49b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">text_format</i> &#x2014; material icon named "text format rounded".
+  static const IconData text_format_rounded = IconData(0xf4cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">text_format</i> &#x2014; material icon named "text format sharp".
+  static const IconData text_format_sharp = IconData(0xef9f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">text_rotate_up</i> &#x2014; material icon named "text rotate up".
+  static const IconData text_rotate_up = IconData(0xea6a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">text_rotate_up</i> &#x2014; material icon named "text rotate up outlined".
+  static const IconData text_rotate_up_outlined = IconData(0xe49c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">text_rotate_up</i> &#x2014; material icon named "text rotate up rounded".
+  static const IconData text_rotate_up_rounded = IconData(0xf4cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">text_rotate_up</i> &#x2014; material icon named "text rotate up sharp".
+  static const IconData text_rotate_up_sharp = IconData(0xefa0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">text_rotate_vertical</i> &#x2014; material icon named "text rotate vertical".
+  static const IconData text_rotate_vertical = IconData(0xea6b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">text_rotate_vertical</i> &#x2014; material icon named "text rotate vertical outlined".
+  static const IconData text_rotate_vertical_outlined = IconData(0xe49d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">text_rotate_vertical</i> &#x2014; material icon named "text rotate vertical rounded".
+  static const IconData text_rotate_vertical_rounded = IconData(0xf4ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">text_rotate_vertical</i> &#x2014; material icon named "text rotate vertical sharp".
+  static const IconData text_rotate_vertical_sharp = IconData(0xefa1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">text_rotation_angledown</i> &#x2014; material icon named "text rotation angledown".
+  static const IconData text_rotation_angledown = IconData(0xea6c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">text_rotation_angledown</i> &#x2014; material icon named "text rotation angledown outlined".
+  static const IconData text_rotation_angledown_outlined = IconData(0xe49e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">text_rotation_angledown</i> &#x2014; material icon named "text rotation angledown rounded".
+  static const IconData text_rotation_angledown_rounded = IconData(0xf4cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">text_rotation_angledown</i> &#x2014; material icon named "text rotation angledown sharp".
+  static const IconData text_rotation_angledown_sharp = IconData(0xefa2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">text_rotation_angleup</i> &#x2014; material icon named "text rotation angleup".
+  static const IconData text_rotation_angleup = IconData(0xea6d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">text_rotation_angleup</i> &#x2014; material icon named "text rotation angleup outlined".
+  static const IconData text_rotation_angleup_outlined = IconData(0xe49f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">text_rotation_angleup</i> &#x2014; material icon named "text rotation angleup rounded".
+  static const IconData text_rotation_angleup_rounded = IconData(0xf4d0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">text_rotation_angleup</i> &#x2014; material icon named "text rotation angleup sharp".
+  static const IconData text_rotation_angleup_sharp = IconData(0xefa3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">text_rotation_down</i> &#x2014; material icon named "text rotation down".
+  static const IconData text_rotation_down = IconData(0xea6e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">text_rotation_down</i> &#x2014; material icon named "text rotation down outlined".
+  static const IconData text_rotation_down_outlined = IconData(0xe4a0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">text_rotation_down</i> &#x2014; material icon named "text rotation down rounded".
+  static const IconData text_rotation_down_rounded = IconData(0xf4d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">text_rotation_down</i> &#x2014; material icon named "text rotation down sharp".
+  static const IconData text_rotation_down_sharp = IconData(0xefa4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">text_rotation_none</i> &#x2014; material icon named "text rotation none".
+  static const IconData text_rotation_none = IconData(0xea6f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">text_rotation_none</i> &#x2014; material icon named "text rotation none outlined".
+  static const IconData text_rotation_none_outlined = IconData(0xe4a1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">text_rotation_none</i> &#x2014; material icon named "text rotation none rounded".
+  static const IconData text_rotation_none_rounded = IconData(0xf4d2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">text_rotation_none</i> &#x2014; material icon named "text rotation none sharp".
+  static const IconData text_rotation_none_sharp = IconData(0xefa5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">text_snippet</i> &#x2014; material icon named "text snippet".
+  static const IconData text_snippet = IconData(0xea70, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">text_snippet</i> &#x2014; material icon named "text snippet outlined".
+  static const IconData text_snippet_outlined = IconData(0xe4a2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">text_snippet</i> &#x2014; material icon named "text snippet rounded".
+  static const IconData text_snippet_rounded = IconData(0xf4d3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">text_snippet</i> &#x2014; material icon named "text snippet sharp".
+  static const IconData text_snippet_sharp = IconData(0xefa6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">textsms</i> &#x2014; material icon named "textsms".
+  static const IconData textsms = IconData(0xea71, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">textsms</i> &#x2014; material icon named "textsms outlined".
+  static const IconData textsms_outlined = IconData(0xe4a3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">textsms</i> &#x2014; material icon named "textsms rounded".
+  static const IconData textsms_rounded = IconData(0xf4d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">textsms</i> &#x2014; material icon named "textsms sharp".
+  static const IconData textsms_sharp = IconData(0xefa7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">texture</i> &#x2014; material icon named "texture".
+  static const IconData texture = IconData(0xea72, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">texture</i> &#x2014; material icon named "texture outlined".
+  static const IconData texture_outlined = IconData(0xe4a4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">texture</i> &#x2014; material icon named "texture rounded".
+  static const IconData texture_rounded = IconData(0xf4d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">texture</i> &#x2014; material icon named "texture sharp".
+  static const IconData texture_sharp = IconData(0xefa8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">theater_comedy</i> &#x2014; material icon named "theater comedy".
+  static const IconData theater_comedy = IconData(0xea73, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">theaters</i> &#x2014; material icon named "theaters".
+  static const IconData theaters = IconData(0xea74, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">theaters</i> &#x2014; material icon named "theaters outlined".
+  static const IconData theaters_outlined = IconData(0xe4a5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">theaters</i> &#x2014; material icon named "theaters rounded".
+  static const IconData theaters_rounded = IconData(0xf4d6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">theaters</i> &#x2014; material icon named "theaters sharp".
+  static const IconData theaters_sharp = IconData(0xefa9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">thermostat</i> &#x2014; material icon named "thermostat outlined".
+  static const IconData thermostat_outlined = IconData(0xe4a6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">thermostat</i> &#x2014; material icon named "thermostat rounded".
+  static const IconData thermostat_rounded = IconData(0xf4d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">thermostat</i> &#x2014; material icon named "thermostat sharp".
+  static const IconData thermostat_sharp = IconData(0xefaa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">thumb_down</i> &#x2014; material icon named "thumb down".
+  static const IconData thumb_down = IconData(0xea75, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">thumb_down_alt</i> &#x2014; material icon named "thumb down alt".
+  static const IconData thumb_down_alt = IconData(0xea76, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">thumb_down_alt</i> &#x2014; material icon named "thumb down alt outlined".
+  static const IconData thumb_down_alt_outlined = IconData(0xe4a7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">thumb_down_alt</i> &#x2014; material icon named "thumb down alt rounded".
+  static const IconData thumb_down_alt_rounded = IconData(0xf4d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">thumb_down_alt</i> &#x2014; material icon named "thumb down alt sharp".
+  static const IconData thumb_down_alt_sharp = IconData(0xefab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">thumb_down_off_alt</i> &#x2014; material icon named "thumb down off alt".
+  static const IconData thumb_down_off_alt = IconData(0xea77, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">thumb_down</i> &#x2014; material icon named "thumb down outlined".
+  static const IconData thumb_down_outlined = IconData(0xe4a8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">thumb_down</i> &#x2014; material icon named "thumb down rounded".
+  static const IconData thumb_down_rounded = IconData(0xf4d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">thumb_down</i> &#x2014; material icon named "thumb down sharp".
+  static const IconData thumb_down_sharp = IconData(0xefac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">thumb_up</i> &#x2014; material icon named "thumb up".
+  static const IconData thumb_up = IconData(0xea78, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">thumb_up_alt</i> &#x2014; material icon named "thumb up alt".
+  static const IconData thumb_up_alt = IconData(0xea79, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">thumb_up_alt</i> &#x2014; material icon named "thumb up alt outlined".
+  static const IconData thumb_up_alt_outlined = IconData(0xe4a9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">thumb_up_alt</i> &#x2014; material icon named "thumb up alt rounded".
+  static const IconData thumb_up_alt_rounded = IconData(0xf4da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">thumb_up_alt</i> &#x2014; material icon named "thumb up alt sharp".
+  static const IconData thumb_up_alt_sharp = IconData(0xefad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">thumb_up_off_alt</i> &#x2014; material icon named "thumb up off alt".
+  static const IconData thumb_up_off_alt = IconData(0xea7a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">thumb_up</i> &#x2014; material icon named "thumb up outlined".
+  static const IconData thumb_up_outlined = IconData(0xe4aa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">thumb_up</i> &#x2014; material icon named "thumb up rounded".
+  static const IconData thumb_up_rounded = IconData(0xf4db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">thumb_up</i> &#x2014; material icon named "thumb up sharp".
+  static const IconData thumb_up_sharp = IconData(0xefae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">thumbs_up_down</i> &#x2014; material icon named "thumbs up down".
+  static const IconData thumbs_up_down = IconData(0xea7b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">thumbs_up_down</i> &#x2014; material icon named "thumbs up down outlined".
+  static const IconData thumbs_up_down_outlined = IconData(0xe4ab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">thumbs_up_down</i> &#x2014; material icon named "thumbs up down rounded".
+  static const IconData thumbs_up_down_rounded = IconData(0xf4dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">thumbs_up_down</i> &#x2014; material icon named "thumbs up down sharp".
+  static const IconData thumbs_up_down_sharp = IconData(0xefaf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">time_to_leave</i> &#x2014; material icon named "time to leave".
+  static const IconData time_to_leave = IconData(0xea7c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">time_to_leave</i> &#x2014; material icon named "time to leave outlined".
+  static const IconData time_to_leave_outlined = IconData(0xe4ac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">time_to_leave</i> &#x2014; material icon named "time to leave rounded".
+  static const IconData time_to_leave_rounded = IconData(0xf4dd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">time_to_leave</i> &#x2014; material icon named "time to leave sharp".
+  static const IconData time_to_leave_sharp = IconData(0xefb0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">timelapse</i> &#x2014; material icon named "timelapse".
+  static const IconData timelapse = IconData(0xea7d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">timelapse</i> &#x2014; material icon named "timelapse outlined".
+  static const IconData timelapse_outlined = IconData(0xe4ad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">timelapse</i> &#x2014; material icon named "timelapse rounded".
+  static const IconData timelapse_rounded = IconData(0xf4de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">timelapse</i> &#x2014; material icon named "timelapse sharp".
+  static const IconData timelapse_sharp = IconData(0xefb1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">timeline</i> &#x2014; material icon named "timeline".
+  static const IconData timeline = IconData(0xea7e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">timeline</i> &#x2014; material icon named "timeline outlined".
+  static const IconData timeline_outlined = IconData(0xe4ae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">timeline</i> &#x2014; material icon named "timeline rounded".
+  static const IconData timeline_rounded = IconData(0xf4df, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">timeline</i> &#x2014; material icon named "timeline sharp".
+  static const IconData timeline_sharp = IconData(0xefb2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">timer</i> &#x2014; material icon named "timer".
+  static const IconData timer = IconData(0xea7f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">timer_10</i> &#x2014; material icon named "timer 10".
+  static const IconData timer_10 = IconData(0xea80, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">timer_10</i> &#x2014; material icon named "timer 10 outlined".
+  static const IconData timer_10_outlined = IconData(0xe4af, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">timer_10</i> &#x2014; material icon named "timer 10 rounded".
+  static const IconData timer_10_rounded = IconData(0xf4e0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">timer_10</i> &#x2014; material icon named "timer 10 sharp".
+  static const IconData timer_10_sharp = IconData(0xefb3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">timer_3</i> &#x2014; material icon named "timer 3".
+  static const IconData timer_3 = IconData(0xea81, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">timer_3</i> &#x2014; material icon named "timer 3 outlined".
+  static const IconData timer_3_outlined = IconData(0xe4b0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">timer_3</i> &#x2014; material icon named "timer 3 rounded".
+  static const IconData timer_3_rounded = IconData(0xf4e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">timer_3</i> &#x2014; material icon named "timer 3 sharp".
+  static const IconData timer_3_sharp = IconData(0xefb4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">timer_off</i> &#x2014; material icon named "timer off".
+  static const IconData timer_off = IconData(0xea82, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">timer_off</i> &#x2014; material icon named "timer off outlined".
+  static const IconData timer_off_outlined = IconData(0xe4b1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">timer_off</i> &#x2014; material icon named "timer off rounded".
+  static const IconData timer_off_rounded = IconData(0xf4e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">timer_off</i> &#x2014; material icon named "timer off sharp".
+  static const IconData timer_off_sharp = IconData(0xefb5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">timer</i> &#x2014; material icon named "timer outlined".
+  static const IconData timer_outlined = IconData(0xe4b2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">timer</i> &#x2014; material icon named "timer rounded".
+  static const IconData timer_rounded = IconData(0xf4e3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">timer</i> &#x2014; material icon named "timer sharp".
+  static const IconData timer_sharp = IconData(0xefb6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">title</i> &#x2014; material icon named "title".
+  static const IconData title = IconData(0xea83, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">title</i> &#x2014; material icon named "title outlined".
+  static const IconData title_outlined = IconData(0xe4b3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">title</i> &#x2014; material icon named "title rounded".
+  static const IconData title_rounded = IconData(0xf4e4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">title</i> &#x2014; material icon named "title sharp".
+  static const IconData title_sharp = IconData(0xefb7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">toc</i> &#x2014; material icon named "toc".
+  static const IconData toc = IconData(0xea84, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">toc</i> &#x2014; material icon named "toc outlined".
+  static const IconData toc_outlined = IconData(0xe4b4, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">toc</i> &#x2014; material icon named "toc rounded".
+  static const IconData toc_rounded = IconData(0xf4e5, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">toc</i> &#x2014; material icon named "toc sharp".
+  static const IconData toc_sharp = IconData(0xefb8, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">today</i> &#x2014; material icon named "today".
+  static const IconData today = IconData(0xea85, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">today</i> &#x2014; material icon named "today outlined".
+  static const IconData today_outlined = IconData(0xe4b5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">today</i> &#x2014; material icon named "today rounded".
+  static const IconData today_rounded = IconData(0xf4e6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">today</i> &#x2014; material icon named "today sharp".
+  static const IconData today_sharp = IconData(0xefb9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">toggle_off</i> &#x2014; material icon named "toggle off".
+  static const IconData toggle_off = IconData(0xea86, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">toggle_off</i> &#x2014; material icon named "toggle off outlined".
+  static const IconData toggle_off_outlined = IconData(0xe4b6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">toggle_off</i> &#x2014; material icon named "toggle off rounded".
+  static const IconData toggle_off_rounded = IconData(0xf4e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">toggle_off</i> &#x2014; material icon named "toggle off sharp".
+  static const IconData toggle_off_sharp = IconData(0xefba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">toggle_on</i> &#x2014; material icon named "toggle on".
+  static const IconData toggle_on = IconData(0xea87, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">toggle_on</i> &#x2014; material icon named "toggle on outlined".
+  static const IconData toggle_on_outlined = IconData(0xe4b7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">toggle_on</i> &#x2014; material icon named "toggle on rounded".
+  static const IconData toggle_on_rounded = IconData(0xf4e8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">toggle_on</i> &#x2014; material icon named "toggle on sharp".
+  static const IconData toggle_on_sharp = IconData(0xefbb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">toll</i> &#x2014; material icon named "toll".
+  static const IconData toll = IconData(0xea88, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">toll</i> &#x2014; material icon named "toll outlined".
+  static const IconData toll_outlined = IconData(0xe4b8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">toll</i> &#x2014; material icon named "toll rounded".
+  static const IconData toll_rounded = IconData(0xf4e9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">toll</i> &#x2014; material icon named "toll sharp".
+  static const IconData toll_sharp = IconData(0xefbc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tonality</i> &#x2014; material icon named "tonality".
+  static const IconData tonality = IconData(0xea89, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tonality</i> &#x2014; material icon named "tonality outlined".
+  static const IconData tonality_outlined = IconData(0xe4b9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tonality</i> &#x2014; material icon named "tonality rounded".
+  static const IconData tonality_rounded = IconData(0xf4ea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tonality</i> &#x2014; material icon named "tonality sharp".
+  static const IconData tonality_sharp = IconData(0xefbd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">topic</i> &#x2014; material icon named "topic".
+  static const IconData topic = IconData(0xea8a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">topic</i> &#x2014; material icon named "topic outlined".
+  static const IconData topic_outlined = IconData(0xe4ba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">topic</i> &#x2014; material icon named "topic rounded".
+  static const IconData topic_rounded = IconData(0xf4eb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">topic</i> &#x2014; material icon named "topic sharp".
+  static const IconData topic_sharp = IconData(0xefbe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">touch_app</i> &#x2014; material icon named "touch app".
+  static const IconData touch_app = IconData(0xea8b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">touch_app</i> &#x2014; material icon named "touch app outlined".
+  static const IconData touch_app_outlined = IconData(0xe4bb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">touch_app</i> &#x2014; material icon named "touch app rounded".
+  static const IconData touch_app_rounded = IconData(0xf4ec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">touch_app</i> &#x2014; material icon named "touch app sharp".
+  static const IconData touch_app_sharp = IconData(0xefbf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tour</i> &#x2014; material icon named "tour".
+  static const IconData tour = IconData(0xea8c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tour</i> &#x2014; material icon named "tour outlined".
+  static const IconData tour_outlined = IconData(0xe4bc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tour</i> &#x2014; material icon named "tour rounded".
+  static const IconData tour_rounded = IconData(0xf4ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tour</i> &#x2014; material icon named "tour sharp".
+  static const IconData tour_sharp = IconData(0xefc0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">toys</i> &#x2014; material icon named "toys".
+  static const IconData toys = IconData(0xea8d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">toys</i> &#x2014; material icon named "toys outlined".
+  static const IconData toys_outlined = IconData(0xe4bd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">toys</i> &#x2014; material icon named "toys rounded".
+  static const IconData toys_rounded = IconData(0xf4ee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">toys</i> &#x2014; material icon named "toys sharp".
+  static const IconData toys_sharp = IconData(0xefc1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">track_changes</i> &#x2014; material icon named "track changes".
+  static const IconData track_changes = IconData(0xea8e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">track_changes</i> &#x2014; material icon named "track changes outlined".
+  static const IconData track_changes_outlined = IconData(0xe4be, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">track_changes</i> &#x2014; material icon named "track changes rounded".
+  static const IconData track_changes_rounded = IconData(0xf4ef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">track_changes</i> &#x2014; material icon named "track changes sharp".
+  static const IconData track_changes_sharp = IconData(0xefc2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">traffic</i> &#x2014; material icon named "traffic".
+  static const IconData traffic = IconData(0xea8f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">traffic</i> &#x2014; material icon named "traffic outlined".
+  static const IconData traffic_outlined = IconData(0xe4bf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">traffic</i> &#x2014; material icon named "traffic rounded".
+  static const IconData traffic_rounded = IconData(0xf4f0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">traffic</i> &#x2014; material icon named "traffic sharp".
+  static const IconData traffic_sharp = IconData(0xefc3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">train</i> &#x2014; material icon named "train".
+  static const IconData train = IconData(0xea90, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">train</i> &#x2014; material icon named "train outlined".
+  static const IconData train_outlined = IconData(0xe4c0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">train</i> &#x2014; material icon named "train rounded".
+  static const IconData train_rounded = IconData(0xf4f1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">train</i> &#x2014; material icon named "train sharp".
+  static const IconData train_sharp = IconData(0xefc4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tram</i> &#x2014; material icon named "tram".
+  static const IconData tram = IconData(0xea91, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tram</i> &#x2014; material icon named "tram outlined".
+  static const IconData tram_outlined = IconData(0xe4c1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tram</i> &#x2014; material icon named "tram rounded".
+  static const IconData tram_rounded = IconData(0xf4f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tram</i> &#x2014; material icon named "tram sharp".
+  static const IconData tram_sharp = IconData(0xefc5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">transfer_within_a_station</i> &#x2014; material icon named "transfer within a station".
+  static const IconData transfer_within_a_station = IconData(0xea92, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">transfer_within_a_station</i> &#x2014; material icon named "transfer within a station outlined".
+  static const IconData transfer_within_a_station_outlined = IconData(0xe4c2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">transfer_within_a_station</i> &#x2014; material icon named "transfer within a station rounded".
+  static const IconData transfer_within_a_station_rounded = IconData(0xf4f3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">transfer_within_a_station</i> &#x2014; material icon named "transfer within a station sharp".
+  static const IconData transfer_within_a_station_sharp = IconData(0xefc6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">transform</i> &#x2014; material icon named "transform".
+  static const IconData transform = IconData(0xea93, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">transform</i> &#x2014; material icon named "transform outlined".
+  static const IconData transform_outlined = IconData(0xe4c3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">transform</i> &#x2014; material icon named "transform rounded".
+  static const IconData transform_rounded = IconData(0xf4f4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">transform</i> &#x2014; material icon named "transform sharp".
+  static const IconData transform_sharp = IconData(0xefc7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">transit_enterexit</i> &#x2014; material icon named "transit enterexit".
+  static const IconData transit_enterexit = IconData(0xea94, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">transit_enterexit</i> &#x2014; material icon named "transit enterexit outlined".
+  static const IconData transit_enterexit_outlined = IconData(0xe4c4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">transit_enterexit</i> &#x2014; material icon named "transit enterexit rounded".
+  static const IconData transit_enterexit_rounded = IconData(0xf4f5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">transit_enterexit</i> &#x2014; material icon named "transit enterexit sharp".
+  static const IconData transit_enterexit_sharp = IconData(0xefc8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">translate</i> &#x2014; material icon named "translate".
+  static const IconData translate = IconData(0xea95, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">translate</i> &#x2014; material icon named "translate outlined".
+  static const IconData translate_outlined = IconData(0xe4c5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">translate</i> &#x2014; material icon named "translate rounded".
+  static const IconData translate_rounded = IconData(0xf4f6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">translate</i> &#x2014; material icon named "translate sharp".
+  static const IconData translate_sharp = IconData(0xefc9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">trending_down</i> &#x2014; material icon named "trending down".
+  static const IconData trending_down = IconData(0xea96, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">trending_down</i> &#x2014; material icon named "trending down outlined".
+  static const IconData trending_down_outlined = IconData(0xe4c6, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">trending_down</i> &#x2014; material icon named "trending down rounded".
+  static const IconData trending_down_rounded = IconData(0xf4f7, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">trending_down</i> &#x2014; material icon named "trending down sharp".
+  static const IconData trending_down_sharp = IconData(0xefca, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">trending_flat</i> &#x2014; material icon named "trending flat".
+  static const IconData trending_flat = IconData(0xea97, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">trending_flat</i> &#x2014; material icon named "trending flat outlined".
+  static const IconData trending_flat_outlined = IconData(0xe4c7, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">trending_flat</i> &#x2014; material icon named "trending flat rounded".
+  static const IconData trending_flat_rounded = IconData(0xf4f8, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">trending_flat</i> &#x2014; material icon named "trending flat sharp".
+  static const IconData trending_flat_sharp = IconData(0xefcb, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">trending_neutral</i> &#x2014; material icon named "trending neutral".
+  static const IconData trending_neutral = IconData(0xea97, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">trending_neutral</i> &#x2014; material icon named "trending neutral outlined".
+  static const IconData trending_neutral_outlined = IconData(0xe4c7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">trending_neutral</i> &#x2014; material icon named "trending neutral rounded".
+  static const IconData trending_neutral_rounded = IconData(0xf4f8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">trending_neutral</i> &#x2014; material icon named "trending neutral sharp".
+  static const IconData trending_neutral_sharp = IconData(0xefcb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">trending_up</i> &#x2014; material icon named "trending up".
+  static const IconData trending_up = IconData(0xea98, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">trending_up</i> &#x2014; material icon named "trending up outlined".
+  static const IconData trending_up_outlined = IconData(0xe4c8, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">trending_up</i> &#x2014; material icon named "trending up rounded".
+  static const IconData trending_up_rounded = IconData(0xf4f9, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">trending_up</i> &#x2014; material icon named "trending up sharp".
+  static const IconData trending_up_sharp = IconData(0xefcc, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">trip_origin</i> &#x2014; material icon named "trip origin".
+  static const IconData trip_origin = IconData(0xea99, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">trip_origin</i> &#x2014; material icon named "trip origin outlined".
+  static const IconData trip_origin_outlined = IconData(0xe4c9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">trip_origin</i> &#x2014; material icon named "trip origin rounded".
+  static const IconData trip_origin_rounded = IconData(0xf4fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">trip_origin</i> &#x2014; material icon named "trip origin sharp".
+  static const IconData trip_origin_sharp = IconData(0xefcd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tty</i> &#x2014; material icon named "tty".
+  static const IconData tty = IconData(0xea9a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tty</i> &#x2014; material icon named "tty outlined".
+  static const IconData tty_outlined = IconData(0xe4ca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tty</i> &#x2014; material icon named "tty rounded".
+  static const IconData tty_rounded = IconData(0xf4fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tty</i> &#x2014; material icon named "tty sharp".
+  static const IconData tty_sharp = IconData(0xefce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tune</i> &#x2014; material icon named "tune".
+  static const IconData tune = IconData(0xea9b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tune</i> &#x2014; material icon named "tune outlined".
+  static const IconData tune_outlined = IconData(0xe4cb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tune</i> &#x2014; material icon named "tune rounded".
+  static const IconData tune_rounded = IconData(0xf4fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tune</i> &#x2014; material icon named "tune sharp".
+  static const IconData tune_sharp = IconData(0xefcf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">turned_in</i> &#x2014; material icon named "turned in".
+  static const IconData turned_in = IconData(0xea9c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">turned_in_not</i> &#x2014; material icon named "turned in not".
+  static const IconData turned_in_not = IconData(0xea9d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">turned_in_not</i> &#x2014; material icon named "turned in not outlined".
+  static const IconData turned_in_not_outlined = IconData(0xe4cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">turned_in_not</i> &#x2014; material icon named "turned in not rounded".
+  static const IconData turned_in_not_rounded = IconData(0xf4fd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">turned_in_not</i> &#x2014; material icon named "turned in not sharp".
+  static const IconData turned_in_not_sharp = IconData(0xefd0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">turned_in</i> &#x2014; material icon named "turned in outlined".
+  static const IconData turned_in_outlined = IconData(0xe4cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">turned_in</i> &#x2014; material icon named "turned in rounded".
+  static const IconData turned_in_rounded = IconData(0xf4fe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">turned_in</i> &#x2014; material icon named "turned in sharp".
+  static const IconData turned_in_sharp = IconData(0xefd1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tv</i> &#x2014; material icon named "tv".
+  static const IconData tv = IconData(0xea9e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">tv_off</i> &#x2014; material icon named "tv off".
+  static const IconData tv_off = IconData(0xea9f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tv_off</i> &#x2014; material icon named "tv off outlined".
+  static const IconData tv_off_outlined = IconData(0xe4ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tv_off</i> &#x2014; material icon named "tv off rounded".
+  static const IconData tv_off_rounded = IconData(0xf4ff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tv_off</i> &#x2014; material icon named "tv off sharp".
+  static const IconData tv_off_sharp = IconData(0xefd2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">tv</i> &#x2014; material icon named "tv outlined".
+  static const IconData tv_outlined = IconData(0xe4cf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">tv</i> &#x2014; material icon named "tv rounded".
+  static const IconData tv_rounded = IconData(0xf500, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">tv</i> &#x2014; material icon named "tv sharp".
+  static const IconData tv_sharp = IconData(0xefd3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">two_wheeler</i> &#x2014; material icon named "two wheeler".
+  static const IconData two_wheeler = IconData(0xeaa0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">two_wheeler</i> &#x2014; material icon named "two wheeler outlined".
+  static const IconData two_wheeler_outlined = IconData(0xe4d0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">two_wheeler</i> &#x2014; material icon named "two wheeler rounded".
+  static const IconData two_wheeler_rounded = IconData(0xf501, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">two_wheeler</i> &#x2014; material icon named "two wheeler sharp".
+  static const IconData two_wheeler_sharp = IconData(0xefd4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">umbrella</i> &#x2014; material icon named "umbrella".
+  static const IconData umbrella = IconData(0xeaa1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">umbrella</i> &#x2014; material icon named "umbrella outlined".
+  static const IconData umbrella_outlined = IconData(0xe4d1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">umbrella</i> &#x2014; material icon named "umbrella rounded".
+  static const IconData umbrella_rounded = IconData(0xf502, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">umbrella</i> &#x2014; material icon named "umbrella sharp".
+  static const IconData umbrella_sharp = IconData(0xefd5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">unarchive</i> &#x2014; material icon named "unarchive".
+  static const IconData unarchive = IconData(0xeaa2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">unarchive</i> &#x2014; material icon named "unarchive outlined".
+  static const IconData unarchive_outlined = IconData(0xe4d2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">unarchive</i> &#x2014; material icon named "unarchive rounded".
+  static const IconData unarchive_rounded = IconData(0xf503, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">unarchive</i> &#x2014; material icon named "unarchive sharp".
+  static const IconData unarchive_sharp = IconData(0xefd6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">undo</i> &#x2014; material icon named "undo".
+  static const IconData undo = IconData(0xeaa3, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">undo</i> &#x2014; material icon named "undo outlined".
+  static const IconData undo_outlined = IconData(0xe4d3, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">undo</i> &#x2014; material icon named "undo rounded".
+  static const IconData undo_rounded = IconData(0xf504, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">undo</i> &#x2014; material icon named "undo sharp".
+  static const IconData undo_sharp = IconData(0xefd7, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">unfold_less</i> &#x2014; material icon named "unfold less".
+  static const IconData unfold_less = IconData(0xeaa4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">unfold_less</i> &#x2014; material icon named "unfold less outlined".
+  static const IconData unfold_less_outlined = IconData(0xe4d4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">unfold_less</i> &#x2014; material icon named "unfold less rounded".
+  static const IconData unfold_less_rounded = IconData(0xf505, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">unfold_less</i> &#x2014; material icon named "unfold less sharp".
+  static const IconData unfold_less_sharp = IconData(0xefd8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">unfold_more</i> &#x2014; material icon named "unfold more".
+  static const IconData unfold_more = IconData(0xeaa5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">unfold_more</i> &#x2014; material icon named "unfold more outlined".
+  static const IconData unfold_more_outlined = IconData(0xe4d5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">unfold_more</i> &#x2014; material icon named "unfold more rounded".
+  static const IconData unfold_more_rounded = IconData(0xf506, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">unfold_more</i> &#x2014; material icon named "unfold more sharp".
+  static const IconData unfold_more_sharp = IconData(0xefd9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">unsubscribe</i> &#x2014; material icon named "unsubscribe".
+  static const IconData unsubscribe = IconData(0xeaa6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">unsubscribe</i> &#x2014; material icon named "unsubscribe outlined".
+  static const IconData unsubscribe_outlined = IconData(0xe4d6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">unsubscribe</i> &#x2014; material icon named "unsubscribe rounded".
+  static const IconData unsubscribe_rounded = IconData(0xf507, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">unsubscribe</i> &#x2014; material icon named "unsubscribe sharp".
+  static const IconData unsubscribe_sharp = IconData(0xefda, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">update</i> &#x2014; material icon named "update".
+  static const IconData update = IconData(0xeaa7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">update</i> &#x2014; material icon named "update outlined".
+  static const IconData update_outlined = IconData(0xe4d7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">update</i> &#x2014; material icon named "update rounded".
+  static const IconData update_rounded = IconData(0xf508, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">update</i> &#x2014; material icon named "update sharp".
+  static const IconData update_sharp = IconData(0xefdb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">upgrade</i> &#x2014; material icon named "upgrade".
+  static const IconData upgrade = IconData(0xeaa8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">upgrade</i> &#x2014; material icon named "upgrade outlined".
+  static const IconData upgrade_outlined = IconData(0xe4d8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">upgrade</i> &#x2014; material icon named "upgrade rounded".
+  static const IconData upgrade_rounded = IconData(0xf509, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">upgrade</i> &#x2014; material icon named "upgrade sharp".
+  static const IconData upgrade_sharp = IconData(0xefdc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">upload_file</i> &#x2014; material icon named "upload file".
+  static const IconData upload_file = IconData(0xeaa9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">upload</i> &#x2014; material icon named "upload outlined".
+  static const IconData upload_outlined = IconData(0xe4d9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">upload</i> &#x2014; material icon named "upload rounded".
+  static const IconData upload_rounded = IconData(0xf50a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">upload</i> &#x2014; material icon named "upload sharp".
+  static const IconData upload_sharp = IconData(0xefdd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">usb</i> &#x2014; material icon named "usb".
+  static const IconData usb = IconData(0xeaaa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">usb</i> &#x2014; material icon named "usb outlined".
+  static const IconData usb_outlined = IconData(0xe4da, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">usb</i> &#x2014; material icon named "usb rounded".
+  static const IconData usb_rounded = IconData(0xf50b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">usb</i> &#x2014; material icon named "usb sharp".
+  static const IconData usb_sharp = IconData(0xefde, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">verified</i> &#x2014; material icon named "verified".
+  static const IconData verified = IconData(0xeaab, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">verified</i> &#x2014; material icon named "verified outlined".
+  static const IconData verified_outlined = IconData(0xe4db, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">verified</i> &#x2014; material icon named "verified rounded".
+  static const IconData verified_rounded = IconData(0xf50c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">verified</i> &#x2014; material icon named "verified sharp".
+  static const IconData verified_sharp = IconData(0xefdf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">verified_user</i> &#x2014; material icon named "verified user".
+  static const IconData verified_user = IconData(0xeaac, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">verified_user</i> &#x2014; material icon named "verified user outlined".
+  static const IconData verified_user_outlined = IconData(0xe4dc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">verified_user</i> &#x2014; material icon named "verified user rounded".
+  static const IconData verified_user_rounded = IconData(0xf50d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">verified_user</i> &#x2014; material icon named "verified user sharp".
+  static const IconData verified_user_sharp = IconData(0xefe0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">vertical_align_bottom</i> &#x2014; material icon named "vertical align bottom".
+  static const IconData vertical_align_bottom = IconData(0xeaad, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">vertical_align_bottom</i> &#x2014; material icon named "vertical align bottom outlined".
+  static const IconData vertical_align_bottom_outlined = IconData(0xe4dd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">vertical_align_bottom</i> &#x2014; material icon named "vertical align bottom rounded".
+  static const IconData vertical_align_bottom_rounded = IconData(0xf50e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">vertical_align_bottom</i> &#x2014; material icon named "vertical align bottom sharp".
+  static const IconData vertical_align_bottom_sharp = IconData(0xefe1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">vertical_align_center</i> &#x2014; material icon named "vertical align center".
+  static const IconData vertical_align_center = IconData(0xeaae, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">vertical_align_center</i> &#x2014; material icon named "vertical align center outlined".
+  static const IconData vertical_align_center_outlined = IconData(0xe4de, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">vertical_align_center</i> &#x2014; material icon named "vertical align center rounded".
+  static const IconData vertical_align_center_rounded = IconData(0xf50f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">vertical_align_center</i> &#x2014; material icon named "vertical align center sharp".
+  static const IconData vertical_align_center_sharp = IconData(0xefe2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">vertical_align_top</i> &#x2014; material icon named "vertical align top".
+  static const IconData vertical_align_top = IconData(0xeaaf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">vertical_align_top</i> &#x2014; material icon named "vertical align top outlined".
+  static const IconData vertical_align_top_outlined = IconData(0xe4df, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">vertical_align_top</i> &#x2014; material icon named "vertical align top rounded".
+  static const IconData vertical_align_top_rounded = IconData(0xf510, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">vertical_align_top</i> &#x2014; material icon named "vertical align top sharp".
+  static const IconData vertical_align_top_sharp = IconData(0xefe3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">vertical_split</i> &#x2014; material icon named "vertical split".
+  static const IconData vertical_split = IconData(0xeab0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">vertical_split</i> &#x2014; material icon named "vertical split outlined".
+  static const IconData vertical_split_outlined = IconData(0xe4e0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">vertical_split</i> &#x2014; material icon named "vertical split rounded".
+  static const IconData vertical_split_rounded = IconData(0xf511, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">vertical_split</i> &#x2014; material icon named "vertical split sharp".
+  static const IconData vertical_split_sharp = IconData(0xefe4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">vibration</i> &#x2014; material icon named "vibration".
+  static const IconData vibration = IconData(0xeab1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">vibration</i> &#x2014; material icon named "vibration outlined".
+  static const IconData vibration_outlined = IconData(0xe4e1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">vibration</i> &#x2014; material icon named "vibration rounded".
+  static const IconData vibration_rounded = IconData(0xf512, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">vibration</i> &#x2014; material icon named "vibration sharp".
+  static const IconData vibration_sharp = IconData(0xefe5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">video_call</i> &#x2014; material icon named "video call".
+  static const IconData video_call = IconData(0xeab2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">video_call</i> &#x2014; material icon named "video call outlined".
+  static const IconData video_call_outlined = IconData(0xe4e2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">video_call</i> &#x2014; material icon named "video call rounded".
+  static const IconData video_call_rounded = IconData(0xf513, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">video_call</i> &#x2014; material icon named "video call sharp".
+  static const IconData video_call_sharp = IconData(0xefe6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">video_collection</i> &#x2014; material icon named "video collection".
+  static const IconData video_collection = IconData(0xeab4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">video_collection</i> &#x2014; material icon named "video collection outlined".
+  static const IconData video_collection_outlined = IconData(0xe4e4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">video_collection</i> &#x2014; material icon named "video collection rounded".
+  static const IconData video_collection_rounded = IconData(0xf515, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">video_collection</i> &#x2014; material icon named "video collection sharp".
+  static const IconData video_collection_sharp = IconData(0xefe8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">video_label</i> &#x2014; material icon named "video label".
+  static const IconData video_label = IconData(0xeab3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">video_label</i> &#x2014; material icon named "video label outlined".
+  static const IconData video_label_outlined = IconData(0xe4e3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">video_label</i> &#x2014; material icon named "video label rounded".
+  static const IconData video_label_rounded = IconData(0xf514, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">video_label</i> &#x2014; material icon named "video label sharp".
+  static const IconData video_label_sharp = IconData(0xefe7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">video_library</i> &#x2014; material icon named "video library".
+  static const IconData video_library = IconData(0xeab4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">video_library</i> &#x2014; material icon named "video library outlined".
+  static const IconData video_library_outlined = IconData(0xe4e4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">video_library</i> &#x2014; material icon named "video library rounded".
+  static const IconData video_library_rounded = IconData(0xf515, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">video_library</i> &#x2014; material icon named "video library sharp".
+  static const IconData video_library_sharp = IconData(0xefe8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">video_settings</i> &#x2014; material icon named "video settings".
+  static const IconData video_settings = IconData(0xeab5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">video_settings</i> &#x2014; material icon named "video settings outlined".
+  static const IconData video_settings_outlined = IconData(0xe4e5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">video_settings</i> &#x2014; material icon named "video settings rounded".
+  static const IconData video_settings_rounded = IconData(0xf516, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">video_settings</i> &#x2014; material icon named "video settings sharp".
+  static const IconData video_settings_sharp = IconData(0xefe9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">videocam</i> &#x2014; material icon named "videocam".
+  static const IconData videocam = IconData(0xeab6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">videocam_off</i> &#x2014; material icon named "videocam off".
+  static const IconData videocam_off = IconData(0xeab7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">videocam_off</i> &#x2014; material icon named "videocam off outlined".
+  static const IconData videocam_off_outlined = IconData(0xe4e6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">videocam_off</i> &#x2014; material icon named "videocam off rounded".
+  static const IconData videocam_off_rounded = IconData(0xf517, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">videocam_off</i> &#x2014; material icon named "videocam off sharp".
+  static const IconData videocam_off_sharp = IconData(0xefea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">videocam</i> &#x2014; material icon named "videocam outlined".
+  static const IconData videocam_outlined = IconData(0xe4e7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">videocam</i> &#x2014; material icon named "videocam rounded".
+  static const IconData videocam_rounded = IconData(0xf518, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">videocam</i> &#x2014; material icon named "videocam sharp".
+  static const IconData videocam_sharp = IconData(0xefeb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">videogame_asset</i> &#x2014; material icon named "videogame asset".
+  static const IconData videogame_asset = IconData(0xeab8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">videogame_asset</i> &#x2014; material icon named "videogame asset outlined".
+  static const IconData videogame_asset_outlined = IconData(0xe4e8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">videogame_asset</i> &#x2014; material icon named "videogame asset rounded".
+  static const IconData videogame_asset_rounded = IconData(0xf519, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">videogame_asset</i> &#x2014; material icon named "videogame asset sharp".
+  static const IconData videogame_asset_sharp = IconData(0xefec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">view_agenda</i> &#x2014; material icon named "view agenda".
+  static const IconData view_agenda = IconData(0xeab9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">view_agenda</i> &#x2014; material icon named "view agenda outlined".
+  static const IconData view_agenda_outlined = IconData(0xe4e9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">view_agenda</i> &#x2014; material icon named "view agenda rounded".
+  static const IconData view_agenda_rounded = IconData(0xf51a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">view_agenda</i> &#x2014; material icon named "view agenda sharp".
+  static const IconData view_agenda_sharp = IconData(0xefed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">view_array</i> &#x2014; material icon named "view array".
+  static const IconData view_array = IconData(0xeaba, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">view_array</i> &#x2014; material icon named "view array outlined".
+  static const IconData view_array_outlined = IconData(0xe4ea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">view_array</i> &#x2014; material icon named "view array rounded".
+  static const IconData view_array_rounded = IconData(0xf51b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">view_array</i> &#x2014; material icon named "view array sharp".
+  static const IconData view_array_sharp = IconData(0xefee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">view_carousel</i> &#x2014; material icon named "view carousel".
+  static const IconData view_carousel = IconData(0xeabb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">view_carousel</i> &#x2014; material icon named "view carousel outlined".
+  static const IconData view_carousel_outlined = IconData(0xe4eb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">view_carousel</i> &#x2014; material icon named "view carousel rounded".
+  static const IconData view_carousel_rounded = IconData(0xf51c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">view_carousel</i> &#x2014; material icon named "view carousel sharp".
+  static const IconData view_carousel_sharp = IconData(0xefef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">view_column</i> &#x2014; material icon named "view column".
+  static const IconData view_column = IconData(0xeabc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">view_column</i> &#x2014; material icon named "view column outlined".
+  static const IconData view_column_outlined = IconData(0xe4ec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">view_column</i> &#x2014; material icon named "view column rounded".
+  static const IconData view_column_rounded = IconData(0xf51d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">view_column</i> &#x2014; material icon named "view column sharp".
+  static const IconData view_column_sharp = IconData(0xeff0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">view_comfortable</i> &#x2014; material icon named "view comfortable".
+  static const IconData view_comfortable = IconData(0xeabd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">view_comfortable</i> &#x2014; material icon named "view comfortable outlined".
+  static const IconData view_comfortable_outlined = IconData(0xe4ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">view_comfortable</i> &#x2014; material icon named "view comfortable rounded".
+  static const IconData view_comfortable_rounded = IconData(0xf51e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">view_comfortable</i> &#x2014; material icon named "view comfortable sharp".
+  static const IconData view_comfortable_sharp = IconData(0xeff1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">view_comfy</i> &#x2014; material icon named "view comfy".
+  static const IconData view_comfy = IconData(0xeabd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">view_comfy</i> &#x2014; material icon named "view comfy outlined".
+  static const IconData view_comfy_outlined = IconData(0xe4ed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">view_comfy</i> &#x2014; material icon named "view comfy rounded".
+  static const IconData view_comfy_rounded = IconData(0xf51e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">view_comfy</i> &#x2014; material icon named "view comfy sharp".
+  static const IconData view_comfy_sharp = IconData(0xeff1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">view_compact</i> &#x2014; material icon named "view compact".
+  static const IconData view_compact = IconData(0xeabe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">view_compact</i> &#x2014; material icon named "view compact outlined".
+  static const IconData view_compact_outlined = IconData(0xe4ee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">view_compact</i> &#x2014; material icon named "view compact rounded".
+  static const IconData view_compact_rounded = IconData(0xf51f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">view_compact</i> &#x2014; material icon named "view compact sharp".
+  static const IconData view_compact_sharp = IconData(0xeff2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">view_day</i> &#x2014; material icon named "view day".
+  static const IconData view_day = IconData(0xeabf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">view_day</i> &#x2014; material icon named "view day outlined".
+  static const IconData view_day_outlined = IconData(0xe4ef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">view_day</i> &#x2014; material icon named "view day rounded".
+  static const IconData view_day_rounded = IconData(0xf520, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">view_day</i> &#x2014; material icon named "view day sharp".
+  static const IconData view_day_sharp = IconData(0xeff3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">view_headline</i> &#x2014; material icon named "view headline".
+  static const IconData view_headline = IconData(0xeac0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">view_headline</i> &#x2014; material icon named "view headline outlined".
+  static const IconData view_headline_outlined = IconData(0xe4f0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">view_headline</i> &#x2014; material icon named "view headline rounded".
+  static const IconData view_headline_rounded = IconData(0xf521, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">view_headline</i> &#x2014; material icon named "view headline sharp".
+  static const IconData view_headline_sharp = IconData(0xeff4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">view_in_ar</i> &#x2014; material icon named "view in ar".
+  static const IconData view_in_ar = IconData(0xeac1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">view_list</i> &#x2014; material icon named "view list".
+  static const IconData view_list = IconData(0xeac2, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">view_list</i> &#x2014; material icon named "view list outlined".
+  static const IconData view_list_outlined = IconData(0xe4f1, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">view_list</i> &#x2014; material icon named "view list rounded".
+  static const IconData view_list_rounded = IconData(0xf522, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">view_list</i> &#x2014; material icon named "view list sharp".
+  static const IconData view_list_sharp = IconData(0xeff5, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">view_module</i> &#x2014; material icon named "view module".
+  static const IconData view_module = IconData(0xeac3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">view_module</i> &#x2014; material icon named "view module outlined".
+  static const IconData view_module_outlined = IconData(0xe4f2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">view_module</i> &#x2014; material icon named "view module rounded".
+  static const IconData view_module_rounded = IconData(0xf523, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">view_module</i> &#x2014; material icon named "view module sharp".
+  static const IconData view_module_sharp = IconData(0xeff6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">view_quilt</i> &#x2014; material icon named "view quilt".
+  static const IconData view_quilt = IconData(0xeac4, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">view_quilt</i> &#x2014; material icon named "view quilt outlined".
+  static const IconData view_quilt_outlined = IconData(0xe4f3, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">view_quilt</i> &#x2014; material icon named "view quilt rounded".
+  static const IconData view_quilt_rounded = IconData(0xf524, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">view_quilt</i> &#x2014; material icon named "view quilt sharp".
+  static const IconData view_quilt_sharp = IconData(0xeff7, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">view_sidebar</i> &#x2014; material icon named "view sidebar".
+  static const IconData view_sidebar = IconData(0xeac5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">view_sidebar</i> &#x2014; material icon named "view sidebar outlined".
+  static const IconData view_sidebar_outlined = IconData(0xe4f4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">view_sidebar</i> &#x2014; material icon named "view sidebar rounded".
+  static const IconData view_sidebar_rounded = IconData(0xf525, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">view_sidebar</i> &#x2014; material icon named "view sidebar sharp".
+  static const IconData view_sidebar_sharp = IconData(0xeff8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">view_stream</i> &#x2014; material icon named "view stream".
+  static const IconData view_stream = IconData(0xeac6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">view_stream</i> &#x2014; material icon named "view stream outlined".
+  static const IconData view_stream_outlined = IconData(0xe4f5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">view_stream</i> &#x2014; material icon named "view stream rounded".
+  static const IconData view_stream_rounded = IconData(0xf526, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">view_stream</i> &#x2014; material icon named "view stream sharp".
+  static const IconData view_stream_sharp = IconData(0xeff9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">view_week</i> &#x2014; material icon named "view week".
+  static const IconData view_week = IconData(0xeac7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">view_week</i> &#x2014; material icon named "view week outlined".
+  static const IconData view_week_outlined = IconData(0xe4f6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">view_week</i> &#x2014; material icon named "view week rounded".
+  static const IconData view_week_rounded = IconData(0xf527, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">view_week</i> &#x2014; material icon named "view week sharp".
+  static const IconData view_week_sharp = IconData(0xeffa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">vignette</i> &#x2014; material icon named "vignette".
+  static const IconData vignette = IconData(0xeac8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">vignette</i> &#x2014; material icon named "vignette outlined".
+  static const IconData vignette_outlined = IconData(0xe4f7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">vignette</i> &#x2014; material icon named "vignette rounded".
+  static const IconData vignette_rounded = IconData(0xf528, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">vignette</i> &#x2014; material icon named "vignette sharp".
+  static const IconData vignette_sharp = IconData(0xeffb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">visibility</i> &#x2014; material icon named "visibility".
+  static const IconData visibility = IconData(0xeac9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">visibility_off</i> &#x2014; material icon named "visibility off".
+  static const IconData visibility_off = IconData(0xeaca, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">visibility_off</i> &#x2014; material icon named "visibility off outlined".
+  static const IconData visibility_off_outlined = IconData(0xe4f8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">visibility_off</i> &#x2014; material icon named "visibility off rounded".
+  static const IconData visibility_off_rounded = IconData(0xf529, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">visibility_off</i> &#x2014; material icon named "visibility off sharp".
+  static const IconData visibility_off_sharp = IconData(0xeffc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">visibility</i> &#x2014; material icon named "visibility outlined".
+  static const IconData visibility_outlined = IconData(0xe4f9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">visibility</i> &#x2014; material icon named "visibility rounded".
+  static const IconData visibility_rounded = IconData(0xf52a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">visibility</i> &#x2014; material icon named "visibility sharp".
+  static const IconData visibility_sharp = IconData(0xeffd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">voice_chat</i> &#x2014; material icon named "voice chat".
+  static const IconData voice_chat = IconData(0xeacb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">voice_chat</i> &#x2014; material icon named "voice chat outlined".
+  static const IconData voice_chat_outlined = IconData(0xe4fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">voice_chat</i> &#x2014; material icon named "voice chat rounded".
+  static const IconData voice_chat_rounded = IconData(0xf52b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">voice_chat</i> &#x2014; material icon named "voice chat sharp".
+  static const IconData voice_chat_sharp = IconData(0xeffe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">voice_over_off</i> &#x2014; material icon named "voice over off".
+  static const IconData voice_over_off = IconData(0xeacc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">voice_over_off</i> &#x2014; material icon named "voice over off outlined".
+  static const IconData voice_over_off_outlined = IconData(0xe4fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">voice_over_off</i> &#x2014; material icon named "voice over off rounded".
+  static const IconData voice_over_off_rounded = IconData(0xf52c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">voice_over_off</i> &#x2014; material icon named "voice over off sharp".
+  static const IconData voice_over_off_sharp = IconData(0xefff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">voicemail</i> &#x2014; material icon named "voicemail".
+  static const IconData voicemail = IconData(0xeacd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">voicemail</i> &#x2014; material icon named "voicemail outlined".
+  static const IconData voicemail_outlined = IconData(0xe4fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">voicemail</i> &#x2014; material icon named "voicemail rounded".
+  static const IconData voicemail_rounded = IconData(0xf52d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">voicemail</i> &#x2014; material icon named "voicemail sharp".
+  static const IconData voicemail_sharp = IconData(0xf000, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">volume_down</i> &#x2014; material icon named "volume down".
+  static const IconData volume_down = IconData(0xeace, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">volume_down</i> &#x2014; material icon named "volume down outlined".
+  static const IconData volume_down_outlined = IconData(0xe4fd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">volume_down</i> &#x2014; material icon named "volume down rounded".
+  static const IconData volume_down_rounded = IconData(0xf52e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">volume_down</i> &#x2014; material icon named "volume down sharp".
+  static const IconData volume_down_sharp = IconData(0xf001, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">volume_mute</i> &#x2014; material icon named "volume mute".
+  static const IconData volume_mute = IconData(0xeacf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">volume_mute</i> &#x2014; material icon named "volume mute outlined".
+  static const IconData volume_mute_outlined = IconData(0xe4fe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">volume_mute</i> &#x2014; material icon named "volume mute rounded".
+  static const IconData volume_mute_rounded = IconData(0xf52f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">volume_mute</i> &#x2014; material icon named "volume mute sharp".
+  static const IconData volume_mute_sharp = IconData(0xf002, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">volume_off</i> &#x2014; material icon named "volume off".
+  static const IconData volume_off = IconData(0xead0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">volume_off</i> &#x2014; material icon named "volume off outlined".
+  static const IconData volume_off_outlined = IconData(0xe4ff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">volume_off</i> &#x2014; material icon named "volume off rounded".
+  static const IconData volume_off_rounded = IconData(0xf530, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">volume_off</i> &#x2014; material icon named "volume off sharp".
+  static const IconData volume_off_sharp = IconData(0xf003, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">volume_up</i> &#x2014; material icon named "volume up".
+  static const IconData volume_up = IconData(0xead1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">volume_up</i> &#x2014; material icon named "volume up outlined".
+  static const IconData volume_up_outlined = IconData(0xe500, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">volume_up</i> &#x2014; material icon named "volume up rounded".
+  static const IconData volume_up_rounded = IconData(0xf531, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">volume_up</i> &#x2014; material icon named "volume up sharp".
+  static const IconData volume_up_sharp = IconData(0xf004, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">volunteer_activism</i> &#x2014; material icon named "volunteer activism".
+  static const IconData volunteer_activism = IconData(0xead2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">vpn_key</i> &#x2014; material icon named "vpn key".
+  static const IconData vpn_key = IconData(0xead3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">vpn_key</i> &#x2014; material icon named "vpn key outlined".
+  static const IconData vpn_key_outlined = IconData(0xe501, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">vpn_key</i> &#x2014; material icon named "vpn key rounded".
+  static const IconData vpn_key_rounded = IconData(0xf532, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">vpn_key</i> &#x2014; material icon named "vpn key sharp".
+  static const IconData vpn_key_sharp = IconData(0xf005, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">vpn_lock</i> &#x2014; material icon named "vpn lock".
+  static const IconData vpn_lock = IconData(0xead4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">vpn_lock</i> &#x2014; material icon named "vpn lock outlined".
+  static const IconData vpn_lock_outlined = IconData(0xe502, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">vpn_lock</i> &#x2014; material icon named "vpn lock rounded".
+  static const IconData vpn_lock_rounded = IconData(0xf533, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">vpn_lock</i> &#x2014; material icon named "vpn lock sharp".
+  static const IconData vpn_lock_sharp = IconData(0xf006, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wallet_giftcard</i> &#x2014; material icon named "wallet giftcard".
+  static const IconData wallet_giftcard = IconData(0xe63a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wallet_giftcard</i> &#x2014; material icon named "wallet giftcard outlined".
+  static const IconData wallet_giftcard_outlined = IconData(0xe0cc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wallet_giftcard</i> &#x2014; material icon named "wallet giftcard rounded".
+  static const IconData wallet_giftcard_rounded = IconData(0xf0fa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wallet_giftcard</i> &#x2014; material icon named "wallet giftcard sharp".
+  static const IconData wallet_giftcard_sharp = IconData(0xebcc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wallet_membership</i> &#x2014; material icon named "wallet membership".
+  static const IconData wallet_membership = IconData(0xe63b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wallet_membership</i> &#x2014; material icon named "wallet membership outlined".
+  static const IconData wallet_membership_outlined = IconData(0xe0cd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wallet_membership</i> &#x2014; material icon named "wallet membership rounded".
+  static const IconData wallet_membership_rounded = IconData(0xf0fb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wallet_membership</i> &#x2014; material icon named "wallet membership sharp".
+  static const IconData wallet_membership_sharp = IconData(0xebcd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wallet_travel</i> &#x2014; material icon named "wallet travel".
+  static const IconData wallet_travel = IconData(0xe63c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wallet_travel</i> &#x2014; material icon named "wallet travel outlined".
+  static const IconData wallet_travel_outlined = IconData(0xe0ce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wallet_travel</i> &#x2014; material icon named "wallet travel rounded".
+  static const IconData wallet_travel_rounded = IconData(0xf0fc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wallet_travel</i> &#x2014; material icon named "wallet travel sharp".
+  static const IconData wallet_travel_sharp = IconData(0xebce, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wallpaper</i> &#x2014; material icon named "wallpaper".
+  static const IconData wallpaper = IconData(0xead5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wallpaper</i> &#x2014; material icon named "wallpaper outlined".
+  static const IconData wallpaper_outlined = IconData(0xe503, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wallpaper</i> &#x2014; material icon named "wallpaper rounded".
+  static const IconData wallpaper_rounded = IconData(0xf534, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wallpaper</i> &#x2014; material icon named "wallpaper sharp".
+  static const IconData wallpaper_sharp = IconData(0xf007, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">warning</i> &#x2014; material icon named "warning".
+  static const IconData warning = IconData(0xead6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">warning_amber</i> &#x2014; material icon named "warning amber outlined".
+  static const IconData warning_amber_outlined = IconData(0xe504, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">warning_amber</i> &#x2014; material icon named "warning amber rounded".
+  static const IconData warning_amber_rounded = IconData(0xf535, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">warning_amber</i> &#x2014; material icon named "warning amber sharp".
+  static const IconData warning_amber_sharp = IconData(0xf008, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">warning</i> &#x2014; material icon named "warning outlined".
+  static const IconData warning_outlined = IconData(0xe505, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">warning</i> &#x2014; material icon named "warning rounded".
+  static const IconData warning_rounded = IconData(0xf536, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">warning</i> &#x2014; material icon named "warning sharp".
+  static const IconData warning_sharp = IconData(0xf009, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wash</i> &#x2014; material icon named "wash".
+  static const IconData wash = IconData(0xead7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wash</i> &#x2014; material icon named "wash outlined".
+  static const IconData wash_outlined = IconData(0xe506, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wash</i> &#x2014; material icon named "wash rounded".
+  static const IconData wash_rounded = IconData(0xf537, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wash</i> &#x2014; material icon named "wash sharp".
+  static const IconData wash_sharp = IconData(0xf00a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">watch</i> &#x2014; material icon named "watch".
+  static const IconData watch = IconData(0xead8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">watch_later</i> &#x2014; material icon named "watch later".
+  static const IconData watch_later = IconData(0xead9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">watch_later</i> &#x2014; material icon named "watch later outlined".
+  static const IconData watch_later_outlined = IconData(0xe507, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">watch_later</i> &#x2014; material icon named "watch later rounded".
+  static const IconData watch_later_rounded = IconData(0xf538, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">watch_later</i> &#x2014; material icon named "watch later sharp".
+  static const IconData watch_later_sharp = IconData(0xf00b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">watch</i> &#x2014; material icon named "watch outlined".
+  static const IconData watch_outlined = IconData(0xe508, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">watch</i> &#x2014; material icon named "watch rounded".
+  static const IconData watch_rounded = IconData(0xf539, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">watch</i> &#x2014; material icon named "watch sharp".
+  static const IconData watch_sharp = IconData(0xf00c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">water_damage</i> &#x2014; material icon named "water damage".
+  static const IconData water_damage = IconData(0xeada, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">water_damage</i> &#x2014; material icon named "water damage outlined".
+  static const IconData water_damage_outlined = IconData(0xe509, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">water_damage</i> &#x2014; material icon named "water damage rounded".
+  static const IconData water_damage_rounded = IconData(0xf53a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">water_damage</i> &#x2014; material icon named "water damage sharp".
+  static const IconData water_damage_sharp = IconData(0xf00d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">waterfall_chart</i> &#x2014; material icon named "waterfall chart".
+  static const IconData waterfall_chart = IconData(0xeadb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">waves</i> &#x2014; material icon named "waves".
+  static const IconData waves = IconData(0xeadc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">waves</i> &#x2014; material icon named "waves outlined".
+  static const IconData waves_outlined = IconData(0xe50a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">waves</i> &#x2014; material icon named "waves rounded".
+  static const IconData waves_rounded = IconData(0xf53b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">waves</i> &#x2014; material icon named "waves sharp".
+  static const IconData waves_sharp = IconData(0xf00e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wb_auto</i> &#x2014; material icon named "wb auto".
+  static const IconData wb_auto = IconData(0xeadd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wb_auto</i> &#x2014; material icon named "wb auto outlined".
+  static const IconData wb_auto_outlined = IconData(0xe50b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wb_auto</i> &#x2014; material icon named "wb auto rounded".
+  static const IconData wb_auto_rounded = IconData(0xf53c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wb_auto</i> &#x2014; material icon named "wb auto sharp".
+  static const IconData wb_auto_sharp = IconData(0xf00f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wb_cloudy</i> &#x2014; material icon named "wb cloudy".
+  static const IconData wb_cloudy = IconData(0xeade, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wb_cloudy</i> &#x2014; material icon named "wb cloudy outlined".
+  static const IconData wb_cloudy_outlined = IconData(0xe50c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wb_cloudy</i> &#x2014; material icon named "wb cloudy rounded".
+  static const IconData wb_cloudy_rounded = IconData(0xf53d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wb_cloudy</i> &#x2014; material icon named "wb cloudy sharp".
+  static const IconData wb_cloudy_sharp = IconData(0xf010, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wb_incandescent</i> &#x2014; material icon named "wb incandescent".
+  static const IconData wb_incandescent = IconData(0xeadf, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wb_incandescent</i> &#x2014; material icon named "wb incandescent outlined".
+  static const IconData wb_incandescent_outlined = IconData(0xe50d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wb_incandescent</i> &#x2014; material icon named "wb incandescent rounded".
+  static const IconData wb_incandescent_rounded = IconData(0xf53e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wb_incandescent</i> &#x2014; material icon named "wb incandescent sharp".
+  static const IconData wb_incandescent_sharp = IconData(0xf011, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wb_iridescent</i> &#x2014; material icon named "wb iridescent".
+  static const IconData wb_iridescent = IconData(0xeae0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wb_iridescent</i> &#x2014; material icon named "wb iridescent outlined".
+  static const IconData wb_iridescent_outlined = IconData(0xe50e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wb_iridescent</i> &#x2014; material icon named "wb iridescent rounded".
+  static const IconData wb_iridescent_rounded = IconData(0xf53f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wb_iridescent</i> &#x2014; material icon named "wb iridescent sharp".
+  static const IconData wb_iridescent_sharp = IconData(0xf012, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wb_shade</i> &#x2014; material icon named "wb shade".
+  static const IconData wb_shade = IconData(0xeae1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wb_sunny</i> &#x2014; material icon named "wb sunny".
+  static const IconData wb_sunny = IconData(0xeae2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wb_sunny</i> &#x2014; material icon named "wb sunny outlined".
+  static const IconData wb_sunny_outlined = IconData(0xe50f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wb_sunny</i> &#x2014; material icon named "wb sunny rounded".
+  static const IconData wb_sunny_rounded = IconData(0xf540, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wb_sunny</i> &#x2014; material icon named "wb sunny sharp".
+  static const IconData wb_sunny_sharp = IconData(0xf013, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wb_twighlight</i> &#x2014; material icon named "wb twighlight".
+  static const IconData wb_twighlight = IconData(0xeae3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wc</i> &#x2014; material icon named "wc".
+  static const IconData wc = IconData(0xeae4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wc</i> &#x2014; material icon named "wc outlined".
+  static const IconData wc_outlined = IconData(0xe510, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wc</i> &#x2014; material icon named "wc rounded".
+  static const IconData wc_rounded = IconData(0xf541, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wc</i> &#x2014; material icon named "wc sharp".
+  static const IconData wc_sharp = IconData(0xf014, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">web</i> &#x2014; material icon named "web".
+  static const IconData web = IconData(0xeae5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">web_asset</i> &#x2014; material icon named "web asset".
+  static const IconData web_asset = IconData(0xeae6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">web_asset</i> &#x2014; material icon named "web asset outlined".
+  static const IconData web_asset_outlined = IconData(0xe511, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">web_asset</i> &#x2014; material icon named "web asset rounded".
+  static const IconData web_asset_rounded = IconData(0xf542, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">web_asset</i> &#x2014; material icon named "web asset sharp".
+  static const IconData web_asset_sharp = IconData(0xf015, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">web</i> &#x2014; material icon named "web outlined".
+  static const IconData web_outlined = IconData(0xe512, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">web</i> &#x2014; material icon named "web rounded".
+  static const IconData web_rounded = IconData(0xf543, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">web</i> &#x2014; material icon named "web sharp".
+  static const IconData web_sharp = IconData(0xf016, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">weekend</i> &#x2014; material icon named "weekend".
+  static const IconData weekend = IconData(0xeae7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">weekend</i> &#x2014; material icon named "weekend outlined".
+  static const IconData weekend_outlined = IconData(0xe513, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">weekend</i> &#x2014; material icon named "weekend rounded".
+  static const IconData weekend_rounded = IconData(0xf544, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">weekend</i> &#x2014; material icon named "weekend sharp".
+  static const IconData weekend_sharp = IconData(0xf017, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">west</i> &#x2014; material icon named "west".
+  static const IconData west = IconData(0xeae8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">west</i> &#x2014; material icon named "west outlined".
+  static const IconData west_outlined = IconData(0xe514, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">west</i> &#x2014; material icon named "west rounded".
+  static const IconData west_rounded = IconData(0xf545, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">west</i> &#x2014; material icon named "west sharp".
+  static const IconData west_sharp = IconData(0xf018, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">whatshot</i> &#x2014; material icon named "whatshot".
+  static const IconData whatshot = IconData(0xeae9, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">whatshot</i> &#x2014; material icon named "whatshot outlined".
+  static const IconData whatshot_outlined = IconData(0xe515, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">whatshot</i> &#x2014; material icon named "whatshot rounded".
+  static const IconData whatshot_rounded = IconData(0xf546, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">whatshot</i> &#x2014; material icon named "whatshot sharp".
+  static const IconData whatshot_sharp = IconData(0xf019, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wheelchair_pickup</i> &#x2014; material icon named "wheelchair pickup".
+  static const IconData wheelchair_pickup = IconData(0xeaea, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wheelchair_pickup</i> &#x2014; material icon named "wheelchair pickup outlined".
+  static const IconData wheelchair_pickup_outlined = IconData(0xe516, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wheelchair_pickup</i> &#x2014; material icon named "wheelchair pickup rounded".
+  static const IconData wheelchair_pickup_rounded = IconData(0xf547, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wheelchair_pickup</i> &#x2014; material icon named "wheelchair pickup sharp".
+  static const IconData wheelchair_pickup_sharp = IconData(0xf01a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">where_to_vote</i> &#x2014; material icon named "where to vote".
+  static const IconData where_to_vote = IconData(0xeaeb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">where_to_vote</i> &#x2014; material icon named "where to vote outlined".
+  static const IconData where_to_vote_outlined = IconData(0xe517, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">where_to_vote</i> &#x2014; material icon named "where to vote rounded".
+  static const IconData where_to_vote_rounded = IconData(0xf548, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">where_to_vote</i> &#x2014; material icon named "where to vote sharp".
+  static const IconData where_to_vote_sharp = IconData(0xf01b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">widgets</i> &#x2014; material icon named "widgets".
+  static const IconData widgets = IconData(0xeaec, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">widgets</i> &#x2014; material icon named "widgets outlined".
+  static const IconData widgets_outlined = IconData(0xe518, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">widgets</i> &#x2014; material icon named "widgets rounded".
+  static const IconData widgets_rounded = IconData(0xf549, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">widgets</i> &#x2014; material icon named "widgets sharp".
+  static const IconData widgets_sharp = IconData(0xf01c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wifi</i> &#x2014; material icon named "wifi".
+  static const IconData wifi = IconData(0xeaed, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wifi_calling</i> &#x2014; material icon named "wifi calling".
+  static const IconData wifi_calling = IconData(0xeaee, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wifi_calling</i> &#x2014; material icon named "wifi calling outlined".
+  static const IconData wifi_calling_outlined = IconData(0xe519, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wifi_calling</i> &#x2014; material icon named "wifi calling rounded".
+  static const IconData wifi_calling_rounded = IconData(0xf54a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wifi_calling</i> &#x2014; material icon named "wifi calling sharp".
+  static const IconData wifi_calling_sharp = IconData(0xf01d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wifi_lock</i> &#x2014; material icon named "wifi lock".
+  static const IconData wifi_lock = IconData(0xeaef, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wifi_lock</i> &#x2014; material icon named "wifi lock outlined".
+  static const IconData wifi_lock_outlined = IconData(0xe51a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wifi_lock</i> &#x2014; material icon named "wifi lock rounded".
+  static const IconData wifi_lock_rounded = IconData(0xf54b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wifi_lock</i> &#x2014; material icon named "wifi lock sharp".
+  static const IconData wifi_lock_sharp = IconData(0xf01e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wifi_off</i> &#x2014; material icon named "wifi off".
+  static const IconData wifi_off = IconData(0xeaf0, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wifi_off</i> &#x2014; material icon named "wifi off outlined".
+  static const IconData wifi_off_outlined = IconData(0xe51b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wifi_off</i> &#x2014; material icon named "wifi off rounded".
+  static const IconData wifi_off_rounded = IconData(0xf54c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wifi_off</i> &#x2014; material icon named "wifi off sharp".
+  static const IconData wifi_off_sharp = IconData(0xf01f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wifi</i> &#x2014; material icon named "wifi outlined".
+  static const IconData wifi_outlined = IconData(0xe51c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wifi_protected_setup</i> &#x2014; material icon named "wifi protected setup".
+  static const IconData wifi_protected_setup = IconData(0xeaf1, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wifi_protected_setup</i> &#x2014; material icon named "wifi protected setup outlined".
+  static const IconData wifi_protected_setup_outlined = IconData(0xe51d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wifi_protected_setup</i> &#x2014; material icon named "wifi protected setup rounded".
+  static const IconData wifi_protected_setup_rounded = IconData(0xf54d, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wifi_protected_setup</i> &#x2014; material icon named "wifi protected setup sharp".
+  static const IconData wifi_protected_setup_sharp = IconData(0xf020, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wifi</i> &#x2014; material icon named "wifi rounded".
+  static const IconData wifi_rounded = IconData(0xf54e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wifi</i> &#x2014; material icon named "wifi sharp".
+  static const IconData wifi_sharp = IconData(0xf021, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wifi_tethering</i> &#x2014; material icon named "wifi tethering".
+  static const IconData wifi_tethering = IconData(0xeaf2, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wifi_tethering</i> &#x2014; material icon named "wifi tethering outlined".
+  static const IconData wifi_tethering_outlined = IconData(0xe51e, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wifi_tethering</i> &#x2014; material icon named "wifi tethering rounded".
+  static const IconData wifi_tethering_rounded = IconData(0xf54f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wifi_tethering</i> &#x2014; material icon named "wifi tethering sharp".
+  static const IconData wifi_tethering_sharp = IconData(0xf022, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wine_bar</i> &#x2014; material icon named "wine bar".
+  static const IconData wine_bar = IconData(0xeaf3, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wine_bar</i> &#x2014; material icon named "wine bar outlined".
+  static const IconData wine_bar_outlined = IconData(0xe51f, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wine_bar</i> &#x2014; material icon named "wine bar rounded".
+  static const IconData wine_bar_rounded = IconData(0xf550, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wine_bar</i> &#x2014; material icon named "wine bar sharp".
+  static const IconData wine_bar_sharp = IconData(0xf023, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">work</i> &#x2014; material icon named "work".
+  static const IconData work = IconData(0xeaf4, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">work_off</i> &#x2014; material icon named "work off".
+  static const IconData work_off = IconData(0xeaf5, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">work_off</i> &#x2014; material icon named "work off outlined".
+  static const IconData work_off_outlined = IconData(0xe520, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">work_off</i> &#x2014; material icon named "work off rounded".
+  static const IconData work_off_rounded = IconData(0xf551, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">work_off</i> &#x2014; material icon named "work off sharp".
+  static const IconData work_off_sharp = IconData(0xf024, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">work_outline</i> &#x2014; material icon named "work outline".
+  static const IconData work_outline = IconData(0xeaf6, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">work_outline</i> &#x2014; material icon named "work outline outlined".
+  static const IconData work_outline_outlined = IconData(0xe521, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">work_outline</i> &#x2014; material icon named "work outline rounded".
+  static const IconData work_outline_rounded = IconData(0xf552, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">work_outline</i> &#x2014; material icon named "work outline sharp".
+  static const IconData work_outline_sharp = IconData(0xf025, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">work</i> &#x2014; material icon named "work outlined".
+  static const IconData work_outlined = IconData(0xe522, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">work</i> &#x2014; material icon named "work rounded".
+  static const IconData work_rounded = IconData(0xf553, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">work</i> &#x2014; material icon named "work sharp".
+  static const IconData work_sharp = IconData(0xf026, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">workspaces_filled</i> &#x2014; material icon named "workspaces filled".
+  static const IconData workspaces_filled = IconData(0xeaf7, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">workspaces_outline</i> &#x2014; material icon named "workspaces outline".
+  static const IconData workspaces_outline = IconData(0xeaf8, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wrap_text</i> &#x2014; material icon named "wrap text".
+  static const IconData wrap_text = IconData(0xeaf9, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-outlined md-36">wrap_text</i> &#x2014; material icon named "wrap text outlined".
+  static const IconData wrap_text_outlined = IconData(0xe523, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-round md-36">wrap_text</i> &#x2014; material icon named "wrap text rounded".
+  static const IconData wrap_text_rounded = IconData(0xf554, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons-sharp md-36">wrap_text</i> &#x2014; material icon named "wrap text sharp".
+  static const IconData wrap_text_sharp = IconData(0xf027, fontFamily: 'MaterialIcons', matchTextDirection: true);
+
+  /// <i class="material-icons md-36">wrong_location</i> &#x2014; material icon named "wrong location".
+  static const IconData wrong_location = IconData(0xeafa, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wrong_location</i> &#x2014; material icon named "wrong location outlined".
+  static const IconData wrong_location_outlined = IconData(0xe524, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wrong_location</i> &#x2014; material icon named "wrong location rounded".
+  static const IconData wrong_location_rounded = IconData(0xf555, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wrong_location</i> &#x2014; material icon named "wrong location sharp".
+  static const IconData wrong_location_sharp = IconData(0xf028, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">wysiwyg</i> &#x2014; material icon named "wysiwyg".
+  static const IconData wysiwyg = IconData(0xeafb, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">wysiwyg</i> &#x2014; material icon named "wysiwyg outlined".
+  static const IconData wysiwyg_outlined = IconData(0xe525, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">wysiwyg</i> &#x2014; material icon named "wysiwyg rounded".
+  static const IconData wysiwyg_rounded = IconData(0xf556, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">wysiwyg</i> &#x2014; material icon named "wysiwyg sharp".
+  static const IconData wysiwyg_sharp = IconData(0xf029, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">youtube_searched_for</i> &#x2014; material icon named "youtube searched for".
+  static const IconData youtube_searched_for = IconData(0xeafc, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">youtube_searched_for</i> &#x2014; material icon named "youtube searched for outlined".
+  static const IconData youtube_searched_for_outlined = IconData(0xe526, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">youtube_searched_for</i> &#x2014; material icon named "youtube searched for rounded".
+  static const IconData youtube_searched_for_rounded = IconData(0xf557, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">youtube_searched_for</i> &#x2014; material icon named "youtube searched for sharp".
+  static const IconData youtube_searched_for_sharp = IconData(0xf02a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">zoom_in</i> &#x2014; material icon named "zoom in".
+  static const IconData zoom_in = IconData(0xeafd, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">zoom_in</i> &#x2014; material icon named "zoom in outlined".
+  static const IconData zoom_in_outlined = IconData(0xe527, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">zoom_in</i> &#x2014; material icon named "zoom in rounded".
+  static const IconData zoom_in_rounded = IconData(0xf558, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">zoom_in</i> &#x2014; material icon named "zoom in sharp".
+  static const IconData zoom_in_sharp = IconData(0xf02b, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">zoom_out</i> &#x2014; material icon named "zoom out".
+  static const IconData zoom_out = IconData(0xeafe, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons md-36">zoom_out_map</i> &#x2014; material icon named "zoom out map".
+  static const IconData zoom_out_map = IconData(0xeaff, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">zoom_out_map</i> &#x2014; material icon named "zoom out map outlined".
+  static const IconData zoom_out_map_outlined = IconData(0xe528, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">zoom_out_map</i> &#x2014; material icon named "zoom out map rounded".
+  static const IconData zoom_out_map_rounded = IconData(0xf559, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">zoom_out_map</i> &#x2014; material icon named "zoom out map sharp".
+  static const IconData zoom_out_map_sharp = IconData(0xf02c, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-outlined md-36">zoom_out</i> &#x2014; material icon named "zoom out outlined".
+  static const IconData zoom_out_outlined = IconData(0xe529, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-round md-36">zoom_out</i> &#x2014; material icon named "zoom out rounded".
+  static const IconData zoom_out_rounded = IconData(0xf55a, fontFamily: 'MaterialIcons');
+
+  /// <i class="material-icons-sharp md-36">zoom_out</i> &#x2014; material icon named "zoom out sharp".
+  static const IconData zoom_out_sharp = IconData(0xf02d, fontFamily: 'MaterialIcons');
+  // END GENERATED ICONS
+}
diff --git a/lib/src/material/ink_decoration.dart b/lib/src/material/ink_decoration.dart
new file mode 100644
index 0000000..a1f7d74
--- /dev/null
+++ b/lib/src/material/ink_decoration.dart
@@ -0,0 +1,378 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'debug.dart';
+import 'material.dart';
+
+/// A convenience widget for drawing images and other decorations on [Material]
+/// widgets, so that [InkWell] and [InkResponse] splashes will render over them.
+///
+/// Ink splashes and highlights, as rendered by [InkWell] and [InkResponse],
+/// draw on the actual underlying [Material], under whatever widgets are drawn
+/// over the material (such as [Text] and [Icon]s). If an opaque image is drawn
+/// over the [Material] (maybe using a [Container] or [DecoratedBox]), these ink
+/// effects will not be visible, as they will be entirely obscured by the opaque
+/// graphics drawn above the [Material].
+///
+/// This widget draws the given [Decoration] directly on the [Material], in the
+/// same way that [InkWell] and [InkResponse] draw there. This allows the
+/// splashes to be drawn above the otherwise opaque graphics.
+///
+/// An alternative solution is to use a [MaterialType.transparency] material
+/// above the opaque graphics, so that the ink responses from [InkWell]s and
+/// [InkResponse]s will be drawn on the transparent material on top of the
+/// opaque graphics, rather than under the opaque graphics on the underlying
+/// [Material].
+///
+/// ## Limitations
+///
+/// This widget is subject to the same limitations as other ink effects, as
+/// described in the documentation for [Material]. Most notably, the position of
+/// an [Ink] widget must not change during the lifetime of the [Material] object
+/// unless a [LayoutChangedNotification] is dispatched each frame that the
+/// position changes. This is done automatically for [ListView] and other
+/// scrolling widgets, but is not done for animated transitions such as
+/// [SlideTransition].
+///
+/// Additionally, if multiple [Ink] widgets paint on the same [Material] in the
+/// same location, their relative order is not guaranteed. The decorations will
+/// be painted in the order that they were added to the material, which
+/// generally speaking will match the order they are given in the widget tree,
+/// but this order may appear to be somewhat random in more dynamic situations.
+///
+/// {@tool snippet}
+///
+/// This example shows how a [Material] widget can have a yellow rectangle drawn
+/// on it using [Ink], while still having ink effects over the yellow rectangle:
+///
+/// ```dart
+/// Material(
+///   color: Colors.teal[900],
+///   child: Center(
+///     child: Ink(
+///       color: Colors.yellow,
+///       width: 200.0,
+///       height: 100.0,
+///       child: InkWell(
+///         onTap: () { /* ... */ },
+///         child: Center(
+///           child: Text('YELLOW'),
+///         )
+///       ),
+///     ),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// The following example shows how an image can be printed on a [Material]
+/// widget with an [InkWell] above it:
+///
+/// ```dart
+/// Material(
+///   color: Colors.grey[800],
+///   child: Center(
+///     child: Ink.image(
+///       image: AssetImage('cat.jpeg'),
+///       fit: BoxFit.cover,
+///       width: 300.0,
+///       height: 200.0,
+///       child: InkWell(
+///         onTap: () { /* ... */ },
+///         child: Align(
+///           alignment: Alignment.topLeft,
+///           child: Padding(
+///             padding: const EdgeInsets.all(10.0),
+///             child: Text('KITTEN', style: TextStyle(fontWeight: FontWeight.w900, color: Colors.white)),
+///           ),
+///         )
+///       ),
+///     ),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Container], a more generic form of this widget which paints itself,
+///    rather that deferring to the nearest [Material] widget.
+///  * [InkDecoration], the [InkFeature] subclass used by this widget to paint
+///    on [Material] widgets.
+///  * [InkWell] and [InkResponse], which also draw on [Material] widgets.
+class Ink extends StatefulWidget {
+  /// Paints a decoration (which can be a simple color) on a [Material].
+  ///
+  /// The [height] and [width] values include the [padding].
+  ///
+  /// The `color` argument is a shorthand for
+  /// `decoration: BoxDecoration(color: color)`, which means you cannot supply
+  /// both a `color` and a `decoration` argument. If you want to have both a
+  /// `color` and a `decoration`, you can pass the color as the `color`
+  /// argument to the `BoxDecoration`.
+  ///
+  /// If there is no intention to render anything on this decoration, consider
+  /// using a [Container] with a [BoxDecoration] instead.
+  Ink({
+    Key? key,
+    this.padding,
+    Color? color,
+    Decoration? decoration,
+    this.width,
+    this.height,
+    this.child,
+  }) : assert(padding == null || padding.isNonNegative),
+       assert(decoration == null || decoration.debugAssertIsValid()),
+       assert(color == null || decoration == null,
+         'Cannot provide both a color and a decoration\n'
+         'The color argument is just a shorthand for "decoration: BoxDecoration(color: color)".'
+       ),
+       decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null),
+       super(key: key);
+
+  /// Creates a widget that shows an image (obtained from an [ImageProvider]) on
+  /// a [Material].
+  ///
+  /// This argument is a shorthand for passing a [BoxDecoration] that has only
+  /// its [BoxDecoration.image] property set to the [Ink] constructor. The
+  /// properties of the [DecorationImage] of that [BoxDecoration] are set
+  /// according to the arguments passed to this method.
+  ///
+  /// The `image` argument must not be null. If there is no
+  /// intention to render anything on this image, consider using a
+  /// [Container] with a [BoxDecoration.image] instead. The `onImageError`
+  /// argument may be provided to listen for errors when resolving the image.
+  ///
+  /// The `alignment`, `repeat`, and `matchTextDirection` arguments must not
+  /// be null either, but they have default values.
+  ///
+  /// See [paintImage] for a description of the meaning of these arguments.
+  Ink.image({
+    Key? key,
+    this.padding,
+    required ImageProvider image,
+    ImageErrorListener? onImageError,
+    ColorFilter? colorFilter,
+    BoxFit? fit,
+    AlignmentGeometry alignment = Alignment.center,
+    Rect? centerSlice,
+    ImageRepeat repeat = ImageRepeat.noRepeat,
+    bool matchTextDirection = false,
+    this.width,
+    this.height,
+    this.child,
+  }) : assert(padding == null || padding.isNonNegative),
+       assert(image != null),
+       assert(alignment != null),
+       assert(repeat != null),
+       assert(matchTextDirection != null),
+       decoration = BoxDecoration(
+         image: DecorationImage(
+           image: image,
+           onError: onImageError,
+           colorFilter: colorFilter,
+           fit: fit,
+           alignment: alignment,
+           centerSlice: centerSlice,
+           repeat: repeat,
+           matchTextDirection: matchTextDirection,
+         ),
+       ),
+       super(key: key);
+
+  /// The [child] contained by the container.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// Empty space to inscribe inside the [decoration]. The [child], if any, is
+  /// placed inside this padding.
+  ///
+  /// This padding is in addition to any padding inherent in the [decoration];
+  /// see [Decoration.padding].
+  final EdgeInsetsGeometry? padding;
+
+  /// The decoration to paint on the nearest ancestor [Material] widget.
+  ///
+  /// A shorthand for specifying just a solid color is available in the
+  /// constructor: set the `color` argument instead of the `decoration`
+  /// argument.
+  ///
+  /// A shorthand for specifying just an image is also available using the
+  /// [Ink.image] constructor.
+  final Decoration? decoration;
+
+  /// A width to apply to the [decoration] and the [child]. The width includes
+  /// any [padding].
+  final double? width;
+
+  /// A height to apply to the [decoration] and the [child]. The height includes
+  /// any [padding].
+  final double? height;
+
+  EdgeInsetsGeometry? get _paddingIncludingDecoration {
+    if (decoration == null || decoration!.padding == null)
+      return padding;
+    final EdgeInsetsGeometry? decorationPadding = decoration!.padding;
+    if (padding == null)
+      return decorationPadding;
+    return padding!.add(decorationPadding!);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
+    properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
+  }
+
+  @override
+  _InkState createState() => _InkState();
+}
+
+class _InkState extends State<Ink> {
+  InkDecoration? _ink;
+
+  void _handleRemoved() {
+    _ink = null;
+  }
+
+  @override
+  void deactivate() {
+    _ink?.dispose();
+    assert(_ink == null);
+    super.deactivate();
+  }
+
+  Widget _build(BuildContext context, BoxConstraints constraints) {
+    if (_ink == null) {
+      _ink = InkDecoration(
+        decoration: widget.decoration,
+        configuration: createLocalImageConfiguration(context),
+        controller: Material.of(context)!,
+        referenceBox: context.findRenderObject()! as RenderBox,
+        onRemoved: _handleRemoved,
+      );
+    } else {
+      _ink!.decoration = widget.decoration;
+      _ink!.configuration = createLocalImageConfiguration(context);
+    }
+    Widget? current = widget.child;
+    final EdgeInsetsGeometry? effectivePadding = widget._paddingIncludingDecoration;
+    if (effectivePadding != null)
+      current = Padding(padding: effectivePadding, child: current);
+    return current ?? Container();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    Widget result = LayoutBuilder(
+      builder: _build,
+    );
+    if (widget.width != null || widget.height != null) {
+      result = SizedBox(
+        width: widget.width,
+        height: widget.height,
+        child: result,
+      );
+    }
+    return result;
+  }
+}
+
+/// A decoration on a part of a [Material].
+///
+/// This object is rarely created directly. Instead of creating an ink
+/// decoration directly, consider using an [Ink] widget, which uses this class
+/// in combination with [Padding] and [ConstrainedBox] to draw a decoration on a
+/// [Material].
+///
+/// See also:
+///
+///  * [Ink], the corresponding widget.
+///  * [InkResponse], which uses gestures to trigger ink highlights and ink
+///    splashes in the parent [Material].
+///  * [InkWell], which is a rectangular [InkResponse] (the most common type of
+///    ink response).
+///  * [Material], which is the widget on which the ink is painted.
+class InkDecoration extends InkFeature {
+  /// Draws a decoration on a [Material].
+  InkDecoration({
+    required Decoration? decoration,
+    required ImageConfiguration configuration,
+    required MaterialInkController controller,
+    required RenderBox referenceBox,
+    VoidCallback? onRemoved,
+  }) : assert(configuration != null),
+       _configuration = configuration,
+       super(controller: controller, referenceBox: referenceBox, onRemoved: onRemoved) {
+    this.decoration = decoration;
+    controller.addInkFeature(this);
+  }
+
+  BoxPainter? _painter;
+
+  /// What to paint on the [Material].
+  ///
+  /// The decoration is painted at the position and size of the [referenceBox],
+  /// on the [Material] that owns the [controller].
+  Decoration? get decoration => _decoration;
+  Decoration? _decoration;
+  set decoration(Decoration? value) {
+    if (value == _decoration)
+      return;
+    _decoration = value;
+    _painter?.dispose();
+    _painter = _decoration?.createBoxPainter(_handleChanged);
+    controller.markNeedsPaint();
+  }
+
+  /// The configuration to pass to the [BoxPainter] obtained from the
+  /// [decoration], when painting.
+  ///
+  /// The [ImageConfiguration.size] field is ignored (and replaced by the size
+  /// of the [referenceBox], at paint time).
+  ImageConfiguration get configuration => _configuration;
+  ImageConfiguration _configuration;
+  set configuration(ImageConfiguration value) {
+    assert(value != null);
+    if (value == _configuration)
+      return;
+    _configuration = value;
+    controller.markNeedsPaint();
+  }
+
+  void _handleChanged() {
+    controller.markNeedsPaint();
+  }
+
+  @override
+  void dispose() {
+    _painter?.dispose();
+    super.dispose();
+  }
+
+  @override
+  void paintFeature(Canvas canvas, Matrix4 transform) {
+    if (_painter == null)
+      return;
+    final Offset? originOffset = MatrixUtils.getAsTranslation(transform);
+    final ImageConfiguration sizedConfiguration = configuration.copyWith(
+      size: referenceBox.size,
+    );
+    if (originOffset == null) {
+      canvas.save();
+      canvas.transform(transform.storage);
+      _painter!.paint(canvas, Offset.zero, sizedConfiguration);
+      canvas.restore();
+    } else {
+      _painter!.paint(canvas, originOffset, sizedConfiguration);
+    }
+  }
+}
diff --git a/lib/src/material/ink_highlight.dart b/lib/src/material/ink_highlight.dart
new file mode 100644
index 0000000..e4c4ca9
--- /dev/null
+++ b/lib/src/material/ink_highlight.dart
@@ -0,0 +1,152 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'ink_well.dart' show InteractiveInkFeature;
+import 'material.dart';
+
+const Duration _kDefaultHighlightFadeDuration = Duration(milliseconds: 200);
+
+/// A visual emphasis on a part of a [Material] receiving user interaction.
+///
+/// This object is rarely created directly. Instead of creating an ink highlight
+/// directly, consider using an [InkResponse] or [InkWell] widget, which uses
+/// gestures (such as tap and long-press) to trigger ink highlights.
+///
+/// See also:
+///
+///  * [InkResponse], which uses gestures to trigger ink highlights and ink
+///    splashes in the parent [Material].
+///  * [InkWell], which is a rectangular [InkResponse] (the most common type of
+///    ink response).
+///  * [Material], which is the widget on which the ink highlight is painted.
+///  * [InkSplash], which is an ink feature that shows a reaction to user input
+///    on a [Material].
+///  * [Ink], a convenience widget for drawing images and other decorations on
+///    Material widgets.
+class InkHighlight extends InteractiveInkFeature {
+  /// Begin a highlight animation.
+  ///
+  /// The [controller] argument is typically obtained via
+  /// `Material.of(context)`.
+  ///
+  /// If a `rectCallback` is given, then it provides the highlight rectangle,
+  /// otherwise, the highlight rectangle is coincident with the [referenceBox].
+  ///
+  /// When the highlight is removed, `onRemoved` will be called.
+  InkHighlight({
+    required MaterialInkController controller,
+    required RenderBox referenceBox,
+    required Color color,
+    required TextDirection textDirection,
+    BoxShape shape = BoxShape.rectangle,
+    double? radius,
+    BorderRadius? borderRadius,
+    ShapeBorder? customBorder,
+    RectCallback? rectCallback,
+    VoidCallback? onRemoved,
+    Duration fadeDuration = _kDefaultHighlightFadeDuration,
+  }) : assert(color != null),
+       assert(shape != null),
+       assert(textDirection != null),
+       assert(fadeDuration != null),
+       _shape = shape,
+       _radius = radius,
+       _borderRadius = borderRadius ?? BorderRadius.zero,
+       _customBorder = customBorder,
+       _textDirection = textDirection,
+       _rectCallback = rectCallback,
+       super(controller: controller, referenceBox: referenceBox, color: color, onRemoved: onRemoved) {
+    _alphaController = AnimationController(duration: fadeDuration, vsync: controller.vsync)
+      ..addListener(controller.markNeedsPaint)
+      ..addStatusListener(_handleAlphaStatusChanged)
+      ..forward();
+    _alpha = _alphaController.drive(IntTween(
+      begin: 0,
+      end: color.alpha,
+    ));
+
+    controller.addInkFeature(this);
+  }
+
+  final BoxShape _shape;
+  final double? _radius;
+  final BorderRadius _borderRadius;
+  final ShapeBorder? _customBorder;
+  final RectCallback? _rectCallback;
+  final TextDirection _textDirection;
+
+  late Animation<int> _alpha;
+  late AnimationController _alphaController;
+
+  /// Whether this part of the material is being visually emphasized.
+  bool get active => _active;
+  bool _active = true;
+
+  /// Start visually emphasizing this part of the material.
+  void activate() {
+    _active = true;
+    _alphaController.forward();
+  }
+
+  /// Stop visually emphasizing this part of the material.
+  void deactivate() {
+    _active = false;
+    _alphaController.reverse();
+  }
+
+  void _handleAlphaStatusChanged(AnimationStatus status) {
+    if (status == AnimationStatus.dismissed && !_active)
+      dispose();
+  }
+
+  @override
+  void dispose() {
+    _alphaController.dispose();
+    super.dispose();
+  }
+
+  void _paintHighlight(Canvas canvas, Rect rect, Paint paint) {
+    assert(_shape != null);
+    canvas.save();
+    if (_customBorder != null) {
+      canvas.clipPath(_customBorder!.getOuterPath(rect, textDirection: _textDirection));
+    }
+    switch (_shape) {
+      case BoxShape.circle:
+        canvas.drawCircle(rect.center, _radius ?? Material.defaultSplashRadius, paint);
+        break;
+      case BoxShape.rectangle:
+        if (_borderRadius != BorderRadius.zero) {
+          final RRect clipRRect = RRect.fromRectAndCorners(
+            rect,
+            topLeft: _borderRadius.topLeft, topRight: _borderRadius.topRight,
+            bottomLeft: _borderRadius.bottomLeft, bottomRight: _borderRadius.bottomRight,
+          );
+          canvas.drawRRect(clipRRect, paint);
+        } else {
+          canvas.drawRect(rect, paint);
+        }
+        break;
+    }
+    canvas.restore();
+  }
+
+  @override
+  void paintFeature(Canvas canvas, Matrix4 transform) {
+    final Paint paint = Paint()..color = color.withAlpha(_alpha.value);
+    final Offset? originOffset = MatrixUtils.getAsTranslation(transform);
+    final Rect rect = _rectCallback != null ? _rectCallback!() : Offset.zero & referenceBox.size;
+    if (originOffset == null) {
+      canvas.save();
+      canvas.transform(transform.storage);
+      _paintHighlight(canvas, rect, paint);
+      canvas.restore();
+    } else {
+      _paintHighlight(canvas, rect.shift(originOffset), paint);
+    }
+  }
+}
diff --git a/lib/src/material/ink_ripple.dart b/lib/src/material/ink_ripple.dart
new file mode 100644
index 0000000..cf4b076
--- /dev/null
+++ b/lib/src/material/ink_ripple.dart
@@ -0,0 +1,251 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'ink_well.dart';
+import 'material.dart';
+
+const Duration _kUnconfirmedRippleDuration = Duration(seconds: 1);
+const Duration _kFadeInDuration = Duration(milliseconds: 75);
+const Duration _kRadiusDuration = Duration(milliseconds: 225);
+const Duration _kFadeOutDuration = Duration(milliseconds: 375);
+const Duration _kCancelDuration = Duration(milliseconds: 75);
+
+// The fade out begins 225ms after the _fadeOutController starts. See confirm().
+const double _kFadeOutIntervalStart = 225.0 / 375.0;
+
+RectCallback? _getClipCallback(RenderBox referenceBox, bool containedInkWell, RectCallback? rectCallback) {
+  if (rectCallback != null) {
+    assert(containedInkWell);
+    return rectCallback;
+  }
+  if (containedInkWell)
+    return () => Offset.zero & referenceBox.size;
+  return null;
+}
+
+double _getTargetRadius(RenderBox referenceBox, bool containedInkWell, RectCallback? rectCallback, Offset position) {
+  final Size size = rectCallback != null ? rectCallback().size : referenceBox.size;
+  final double d1 = size.bottomRight(Offset.zero).distance;
+  final double d2 = (size.topRight(Offset.zero) - size.bottomLeft(Offset.zero)).distance;
+  return math.max(d1, d2) / 2.0;
+}
+
+class _InkRippleFactory extends InteractiveInkFeatureFactory {
+  const _InkRippleFactory();
+
+  @override
+  InteractiveInkFeature create({
+    required MaterialInkController controller,
+    required RenderBox referenceBox,
+    required Offset position,
+    required Color color,
+    required TextDirection textDirection,
+    bool containedInkWell = false,
+    RectCallback? rectCallback,
+    BorderRadius? borderRadius,
+    ShapeBorder? customBorder,
+    double? radius,
+    VoidCallback? onRemoved,
+  }) {
+    return InkRipple(
+      controller: controller,
+      referenceBox: referenceBox,
+      position: position,
+      color: color,
+      containedInkWell: containedInkWell,
+      rectCallback: rectCallback,
+      borderRadius: borderRadius,
+      customBorder: customBorder,
+      radius: radius,
+      onRemoved: onRemoved,
+      textDirection: textDirection,
+    );
+  }
+}
+
+/// A visual reaction on a piece of [Material] to user input.
+///
+/// A circular ink feature whose origin starts at the input touch point and
+/// whose radius expands from 60% of the final radius. The splash origin
+/// animates to the center of its [referenceBox].
+///
+/// This object is rarely created directly. Instead of creating an ink ripple,
+/// consider using an [InkResponse] or [InkWell] widget, which uses
+/// gestures (such as tap and long-press) to trigger ink splashes. This class
+/// is used when the [Theme]'s [ThemeData.splashFactory] is [InkRipple.splashFactory].
+///
+/// See also:
+///
+///  * [InkSplash], which is an ink splash feature that expands less
+///    aggressively than the ripple.
+///  * [InkResponse], which uses gestures to trigger ink highlights and ink
+///    splashes in the parent [Material].
+///  * [InkWell], which is a rectangular [InkResponse] (the most common type of
+///    ink response).
+///  * [Material], which is the widget on which the ink splash is painted.
+///  * [InkHighlight], which is an ink feature that emphasizes a part of a
+///    [Material].
+class InkRipple extends InteractiveInkFeature {
+  /// Begin a ripple, centered at [position] relative to [referenceBox].
+  ///
+  /// The [controller] argument is typically obtained via
+  /// `Material.of(context)`.
+  ///
+  /// If [containedInkWell] is true, then the ripple will be sized to fit
+  /// the well rectangle, then clipped to it when drawn. The well
+  /// rectangle is the box returned by [rectCallback], if provided, or
+  /// otherwise is the bounds of the [referenceBox].
+  ///
+  /// If [containedInkWell] is false, then [rectCallback] should be null.
+  /// The ink ripple is clipped only to the edges of the [Material].
+  /// This is the default.
+  ///
+  /// When the ripple is removed, [onRemoved] will be called.
+  InkRipple({
+    required MaterialInkController controller,
+    required RenderBox referenceBox,
+    required Offset position,
+    required Color color,
+    required TextDirection textDirection,
+    bool containedInkWell = false,
+    RectCallback? rectCallback,
+    BorderRadius? borderRadius,
+    ShapeBorder? customBorder,
+    double? radius,
+    VoidCallback? onRemoved,
+  }) : assert(color != null),
+       assert(position != null),
+       assert(textDirection != null),
+       _position = position,
+       _borderRadius = borderRadius ?? BorderRadius.zero,
+       _customBorder = customBorder,
+       _textDirection = textDirection,
+       _targetRadius = radius ?? _getTargetRadius(referenceBox, containedInkWell, rectCallback, position),
+       _clipCallback = _getClipCallback(referenceBox, containedInkWell, rectCallback),
+       super(controller: controller, referenceBox: referenceBox, color: color, onRemoved: onRemoved) {
+    assert(_borderRadius != null);
+
+    // Immediately begin fading-in the initial splash.
+    _fadeInController = AnimationController(duration: _kFadeInDuration, vsync: controller.vsync)
+      ..addListener(controller.markNeedsPaint)
+      ..forward();
+    _fadeIn = _fadeInController.drive(IntTween(
+      begin: 0,
+      end: color.alpha,
+    ));
+
+    // Controls the splash radius and its center. Starts upon confirm.
+    _radiusController = AnimationController(duration: _kUnconfirmedRippleDuration, vsync: controller.vsync)
+      ..addListener(controller.markNeedsPaint)
+      ..forward();
+     // Initial splash diameter is 60% of the target diameter, final
+     // diameter is 10dps larger than the target diameter.
+    _radius = _radiusController.drive(
+      Tween<double>(
+        begin: _targetRadius * 0.30,
+        end: _targetRadius + 5.0,
+      ).chain(_easeCurveTween),
+    );
+
+    // Controls the splash radius and its center. Starts upon confirm however its
+    // Interval delays changes until the radius expansion has completed.
+    _fadeOutController = AnimationController(duration: _kFadeOutDuration, vsync: controller.vsync)
+      ..addListener(controller.markNeedsPaint)
+      ..addStatusListener(_handleAlphaStatusChanged);
+    _fadeOut = _fadeOutController.drive(
+      IntTween(
+        begin: color.alpha,
+        end: 0,
+      ).chain(_fadeOutIntervalTween),
+    );
+
+    controller.addInkFeature(this);
+  }
+
+  final Offset _position;
+  final BorderRadius _borderRadius;
+  final ShapeBorder? _customBorder;
+  final double _targetRadius;
+  final RectCallback? _clipCallback;
+  final TextDirection _textDirection;
+
+  late Animation<double> _radius;
+  late AnimationController _radiusController;
+
+  late Animation<int> _fadeIn;
+  late AnimationController _fadeInController;
+
+  late Animation<int> _fadeOut;
+  late AnimationController _fadeOutController;
+
+  /// Used to specify this type of ink splash for an [InkWell], [InkResponse]
+  /// or material [Theme].
+  static const InteractiveInkFeatureFactory splashFactory = _InkRippleFactory();
+
+  static final Animatable<double> _easeCurveTween = CurveTween(curve: Curves.ease);
+  static final Animatable<double> _fadeOutIntervalTween = CurveTween(curve: const Interval(_kFadeOutIntervalStart, 1.0));
+
+  @override
+  void confirm() {
+    _radiusController
+      ..duration = _kRadiusDuration
+      ..forward();
+    // This confirm may have been preceded by a cancel.
+    _fadeInController.forward();
+    _fadeOutController.animateTo(1.0, duration: _kFadeOutDuration);
+  }
+
+  @override
+  void cancel() {
+    _fadeInController.stop();
+    // Watch out: setting _fadeOutController's value to 1.0 will
+    // trigger a call to _handleAlphaStatusChanged() which will
+    // dispose _fadeOutController.
+    final double fadeOutValue = 1.0 - _fadeInController.value;
+    _fadeOutController.value = fadeOutValue;
+    if (fadeOutValue < 1.0)
+      _fadeOutController.animateTo(1.0, duration: _kCancelDuration);
+  }
+
+  void _handleAlphaStatusChanged(AnimationStatus status) {
+    if (status == AnimationStatus.completed)
+      dispose();
+  }
+
+  @override
+  void dispose() {
+    _radiusController.dispose();
+    _fadeInController.dispose();
+    _fadeOutController.dispose();
+    super.dispose();
+  }
+
+  @override
+  void paintFeature(Canvas canvas, Matrix4 transform) {
+    final int alpha = _fadeInController.isAnimating ? _fadeIn.value : _fadeOut.value;
+    final Paint paint = Paint()..color = color.withAlpha(alpha);
+    // Splash moves to the center of the reference box.
+    final Offset center = Offset.lerp(
+      _position,
+      referenceBox.size.center(Offset.zero),
+      Curves.ease.transform(_radiusController.value),
+    )!;
+    paintInkCircle(
+      canvas: canvas,
+      transform: transform,
+      paint: paint,
+      center: center,
+      textDirection: _textDirection,
+      radius: _radius.value,
+      customBorder: _customBorder,
+      borderRadius: _borderRadius,
+      clipCallback: _clipCallback,
+    );
+  }
+}
diff --git a/lib/src/material/ink_splash.dart b/lib/src/material/ink_splash.dart
new file mode 100644
index 0000000..358c67e
--- /dev/null
+++ b/lib/src/material/ink_splash.dart
@@ -0,0 +1,219 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'ink_well.dart';
+import 'material.dart';
+
+const Duration _kUnconfirmedSplashDuration = Duration(seconds: 1);
+const Duration _kSplashFadeDuration = Duration(milliseconds: 200);
+
+const double _kSplashInitialSize = 0.0; // logical pixels
+const double _kSplashConfirmedVelocity = 1.0; // logical pixels per millisecond
+
+RectCallback? _getClipCallback(RenderBox referenceBox, bool containedInkWell, RectCallback? rectCallback) {
+  if (rectCallback != null) {
+    assert(containedInkWell);
+    return rectCallback;
+  }
+  if (containedInkWell)
+    return () => Offset.zero & referenceBox.size;
+  return null;
+}
+
+double _getTargetRadius(RenderBox referenceBox, bool containedInkWell, RectCallback? rectCallback, Offset position) {
+  if (containedInkWell) {
+    final Size size = rectCallback != null ? rectCallback().size : referenceBox.size;
+    return _getSplashRadiusForPositionInSize(size, position);
+  }
+  return Material.defaultSplashRadius;
+}
+
+double _getSplashRadiusForPositionInSize(Size bounds, Offset position) {
+  final double d1 = (position - bounds.topLeft(Offset.zero)).distance;
+  final double d2 = (position - bounds.topRight(Offset.zero)).distance;
+  final double d3 = (position - bounds.bottomLeft(Offset.zero)).distance;
+  final double d4 = (position - bounds.bottomRight(Offset.zero)).distance;
+  return math.max(math.max(d1, d2), math.max(d3, d4)).ceilToDouble();
+}
+
+class _InkSplashFactory extends InteractiveInkFeatureFactory {
+  const _InkSplashFactory();
+
+  @override
+  InteractiveInkFeature create({
+    required MaterialInkController controller,
+    required RenderBox referenceBox,
+    required Offset position,
+    required Color color,
+    required TextDirection textDirection,
+    bool containedInkWell = false,
+    RectCallback? rectCallback,
+    BorderRadius? borderRadius,
+    ShapeBorder? customBorder,
+    double? radius,
+    VoidCallback? onRemoved,
+  }) {
+    return InkSplash(
+      controller: controller,
+      referenceBox: referenceBox,
+      position: position,
+      color: color,
+      containedInkWell: containedInkWell,
+      rectCallback: rectCallback,
+      borderRadius: borderRadius,
+      customBorder: customBorder,
+      radius: radius,
+      onRemoved: onRemoved,
+      textDirection: textDirection,
+    );
+  }
+}
+
+/// A visual reaction on a piece of [Material] to user input.
+///
+/// A circular ink feature whose origin starts at the input touch point
+/// and whose radius expands from zero.
+///
+/// This object is rarely created directly. Instead of creating an ink splash
+/// directly, consider using an [InkResponse] or [InkWell] widget, which uses
+/// gestures (such as tap and long-press) to trigger ink splashes.
+///
+/// See also:
+///
+///  * [InkRipple], which is an ink splash feature that expands more
+///    aggressively than this class does.
+///  * [InkResponse], which uses gestures to trigger ink highlights and ink
+///    splashes in the parent [Material].
+///  * [InkWell], which is a rectangular [InkResponse] (the most common type of
+///    ink response).
+///  * [Material], which is the widget on which the ink splash is painted.
+///  * [InkHighlight], which is an ink feature that emphasizes a part of a
+///    [Material].
+///  * [Ink], a convenience widget for drawing images and other decorations on
+///    Material widgets.
+class InkSplash extends InteractiveInkFeature {
+  /// Begin a splash, centered at position relative to [referenceBox].
+  ///
+  /// The [controller] argument is typically obtained via
+  /// `Material.of(context)`.
+  ///
+  /// If `containedInkWell` is true, then the splash will be sized to fit
+  /// the well rectangle, then clipped to it when drawn. The well
+  /// rectangle is the box returned by `rectCallback`, if provided, or
+  /// otherwise is the bounds of the [referenceBox].
+  ///
+  /// If `containedInkWell` is false, then `rectCallback` should be null.
+  /// The ink splash is clipped only to the edges of the [Material].
+  /// This is the default.
+  ///
+  /// When the splash is removed, `onRemoved` will be called.
+  InkSplash({
+    required MaterialInkController controller,
+    required RenderBox referenceBox,
+    required TextDirection textDirection,
+    Offset? position,
+    required Color color,
+    bool containedInkWell = false,
+    RectCallback? rectCallback,
+    BorderRadius? borderRadius,
+    ShapeBorder? customBorder,
+    double? radius,
+    VoidCallback? onRemoved,
+  }) : assert(textDirection != null),
+       _position = position,
+       _borderRadius = borderRadius ?? BorderRadius.zero,
+       _customBorder = customBorder,
+       _targetRadius = radius ?? _getTargetRadius(referenceBox, containedInkWell, rectCallback, position!),
+       _clipCallback = _getClipCallback(referenceBox, containedInkWell, rectCallback),
+       _repositionToReferenceBox = !containedInkWell,
+       _textDirection = textDirection,
+       super(controller: controller, referenceBox: referenceBox, color: color, onRemoved: onRemoved) {
+    assert(_borderRadius != null);
+    _radiusController = AnimationController(duration: _kUnconfirmedSplashDuration, vsync: controller.vsync)
+      ..addListener(controller.markNeedsPaint)
+      ..forward();
+    _radius = _radiusController.drive(Tween<double>(
+      begin: _kSplashInitialSize,
+      end: _targetRadius,
+    ));
+    _alphaController = AnimationController(duration: _kSplashFadeDuration, vsync: controller.vsync)
+      ..addListener(controller.markNeedsPaint)
+      ..addStatusListener(_handleAlphaStatusChanged);
+    _alpha = _alphaController!.drive(IntTween(
+      begin: color.alpha,
+      end: 0,
+    ));
+
+    controller.addInkFeature(this);
+  }
+
+  final Offset? _position;
+  final BorderRadius _borderRadius;
+  final ShapeBorder? _customBorder;
+  final double _targetRadius;
+  final RectCallback? _clipCallback;
+  final bool _repositionToReferenceBox;
+  final TextDirection _textDirection;
+
+  late Animation<double> _radius;
+  late AnimationController _radiusController;
+
+  late Animation<int> _alpha;
+  AnimationController? _alphaController;
+
+  /// Used to specify this type of ink splash for an [InkWell], [InkResponse]
+  /// or material [Theme].
+  static const InteractiveInkFeatureFactory splashFactory = _InkSplashFactory();
+
+  @override
+  void confirm() {
+    final int duration = (_targetRadius / _kSplashConfirmedVelocity).floor();
+    _radiusController
+      ..duration = Duration(milliseconds: duration)
+      ..forward();
+    _alphaController!.forward();
+  }
+
+  @override
+  void cancel() {
+    _alphaController?.forward();
+  }
+
+  void _handleAlphaStatusChanged(AnimationStatus status) {
+    if (status == AnimationStatus.completed)
+      dispose();
+  }
+
+  @override
+  void dispose() {
+    _radiusController.dispose();
+    _alphaController!.dispose();
+    _alphaController = null;
+    super.dispose();
+  }
+
+  @override
+  void paintFeature(Canvas canvas, Matrix4 transform) {
+    final Paint paint = Paint()..color = color.withAlpha(_alpha.value);
+    Offset? center = _position;
+    if (_repositionToReferenceBox)
+      center = Offset.lerp(center, referenceBox.size.center(Offset.zero), _radiusController.value);
+    paintInkCircle(
+      canvas: canvas,
+      transform: transform,
+      paint: paint,
+      center: center!,
+      textDirection: _textDirection,
+      radius: _radius.value,
+      customBorder: _customBorder,
+      borderRadius: _borderRadius,
+      clipCallback: _clipCallback,
+    );
+  }
+}
diff --git a/lib/src/material/ink_well.dart b/lib/src/material/ink_well.dart
new file mode 100644
index 0000000..59e4375
--- /dev/null
+++ b/lib/src/material/ink_well.dart
@@ -0,0 +1,1276 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'debug.dart';
+import 'feedback.dart';
+import 'ink_highlight.dart';
+import 'material.dart';
+import 'material_state.dart';
+import 'theme.dart';
+
+/// An ink feature that displays a [color] "splash" in response to a user
+/// gesture that can be confirmed or canceled.
+///
+/// Subclasses call [confirm] when an input gesture is recognized. For
+/// example a press event might trigger an ink feature that's confirmed
+/// when the corresponding up event is seen.
+///
+/// Subclasses call [cancel] when an input gesture is aborted before it
+/// is recognized. For example a press event might trigger an ink feature
+/// that's canceled when the pointer is dragged out of the reference
+/// box.
+///
+/// The [InkWell] and [InkResponse] widgets generate instances of this
+/// class.
+abstract class InteractiveInkFeature extends InkFeature {
+  /// Creates an InteractiveInkFeature.
+  ///
+  /// The [controller] and [referenceBox] arguments must not be null.
+  InteractiveInkFeature({
+    required MaterialInkController controller,
+    required RenderBox referenceBox,
+    required Color color,
+    VoidCallback? onRemoved,
+  }) : assert(controller != null),
+       assert(referenceBox != null),
+       _color = color,
+       super(controller: controller, referenceBox: referenceBox, onRemoved: onRemoved);
+
+  /// Called when the user input that triggered this feature's appearance was confirmed.
+  ///
+  /// Typically causes the ink to propagate faster across the material. By default this
+  /// method does nothing.
+  void confirm() { }
+
+  /// Called when the user input that triggered this feature's appearance was canceled.
+  ///
+  /// Typically causes the ink to gradually disappear. By default this method does
+  /// nothing.
+  void cancel() { }
+
+  /// The ink's color.
+  Color get color => _color;
+  Color _color;
+  set color(Color value) {
+    if (value == _color)
+      return;
+    _color = value;
+    controller.markNeedsPaint();
+  }
+
+  /// Draws an ink splash or ink ripple on the passed in [Canvas].
+  ///
+  /// The [transform] argument is the [Matrix4] transform that typically
+  /// shifts the coordinate space of the canvas to the space in which
+  /// the ink circle is to be painted.
+  ///
+  /// [center] is the [Offset] from origin of the canvas where the center
+  /// of the circle is drawn.
+  ///
+  /// [paint] takes a [Paint] object that describes the styles used to draw the ink circle.
+  /// For example, [paint] can specify properties like color, strokewidth, colorFilter.
+  ///
+  /// [radius] is the radius of ink circle to be drawn on canvas.
+  ///
+  /// [clipCallback] is the callback used to obtain the [Rect] used for clipping the ink effect.
+  /// If [clipCallback] is null, no clipping is performed on the ink circle.
+  ///
+  /// Clipping can happen in 3 different ways:
+  ///  1. If [customBorder] is provided, it is used to determine the path
+  ///     for clipping.
+  ///  2. If [customBorder] is null, and [borderRadius] is provided, the canvas
+  ///     is clipped by an [RRect] created from [clipCallback] and [borderRadius].
+  ///  3. If [borderRadius] is the default [BorderRadius.zero], then the [Rect] provided
+  ///      by [clipCallback] is used for clipping.
+  ///
+  /// [textDirection] is used by [customBorder] if it is non-null. This allows the [customBorder]'s path
+  /// to be properly defined if it was the path was expressed in terms of "start" and "end" instead of
+  /// "left" and "right".
+  ///
+  /// For examples on how the function is used, see [InkSplash] and [InkRipple].
+  @protected
+  void paintInkCircle({
+    required Canvas canvas,
+    required Matrix4 transform,
+    required Paint paint,
+    required Offset center,
+    required double radius,
+    TextDirection? textDirection,
+    ShapeBorder? customBorder,
+    BorderRadius borderRadius = BorderRadius.zero,
+    RectCallback? clipCallback,
+    }) {
+    assert(canvas != null);
+    assert(transform != null);
+    assert(paint != null);
+    assert(center != null);
+    assert(radius != null);
+    assert(borderRadius != null);
+
+    final Offset? originOffset = MatrixUtils.getAsTranslation(transform);
+    canvas.save();
+    if (originOffset == null) {
+      canvas.transform(transform.storage);
+    } else {
+      canvas.translate(originOffset.dx, originOffset.dy);
+    }
+    if (clipCallback != null) {
+      final Rect rect = clipCallback();
+      if (customBorder != null) {
+        canvas.clipPath(customBorder.getOuterPath(rect, textDirection: textDirection));
+      } else if (borderRadius != BorderRadius.zero) {
+        canvas.clipRRect(RRect.fromRectAndCorners(
+          rect,
+          topLeft: borderRadius.topLeft, topRight: borderRadius.topRight,
+          bottomLeft: borderRadius.bottomLeft, bottomRight: borderRadius.bottomRight,
+        ));
+      } else {
+        canvas.clipRect(rect);
+      }
+    }
+    canvas.drawCircle(center, radius, paint);
+    canvas.restore();
+  }
+}
+
+/// An encapsulation of an [InteractiveInkFeature] constructor used by
+/// [InkWell], [InkResponse], and [ThemeData].
+///
+/// Interactive ink feature implementations should provide a static const
+/// `splashFactory` value that's an instance of this class. The `splashFactory`
+/// can be used to configure an [InkWell], [InkResponse] or [ThemeData].
+///
+/// See also:
+///
+///  * [InkSplash.splashFactory]
+///  * [InkRipple.splashFactory]
+abstract class InteractiveInkFeatureFactory {
+  /// Subclasses should provide a const constructor.
+  const InteractiveInkFeatureFactory();
+
+  /// The factory method.
+  ///
+  /// Subclasses should override this method to return a new instance of an
+  /// [InteractiveInkFeature].
+  @factory
+  InteractiveInkFeature create({
+    required MaterialInkController controller,
+    required RenderBox referenceBox,
+    required Offset position,
+    required Color color,
+    required TextDirection textDirection,
+    bool containedInkWell = false,
+    RectCallback? rectCallback,
+    BorderRadius? borderRadius,
+    ShapeBorder? customBorder,
+    double? radius,
+    VoidCallback? onRemoved,
+  });
+}
+
+abstract class _ParentInkResponseState {
+  void markChildInkResponsePressed(_ParentInkResponseState childState, bool value);
+}
+
+class _ParentInkResponseProvider extends InheritedWidget {
+  const _ParentInkResponseProvider({
+    required this.state,
+    required Widget child,
+  }) : super(child: child);
+
+  final _ParentInkResponseState state;
+
+  @override
+  bool updateShouldNotify(_ParentInkResponseProvider oldWidget) => state != oldWidget.state;
+
+  static _ParentInkResponseState? of(BuildContext context) {
+    return context.dependOnInheritedWidgetOfExactType<_ParentInkResponseProvider>()?.state;
+  }
+}
+
+typedef _GetRectCallback = RectCallback? Function(RenderBox referenceBox);
+typedef _CheckContext = bool Function(BuildContext context);
+
+/// An area of a [Material] that responds to touch. Has a configurable shape and
+/// can be configured to clip splashes that extend outside its bounds or not.
+///
+/// For a variant of this widget that is specialized for rectangular areas that
+/// always clip splashes, see [InkWell].
+///
+/// An [InkResponse] widget does two things when responding to a tap:
+///
+///  * It starts to animate a _highlight_. The shape of the highlight is
+///    determined by [highlightShape]. If it is a [BoxShape.circle], the
+///    default, then the highlight is a circle of fixed size centered in the
+///    [InkResponse]. If it is [BoxShape.rectangle], then the highlight is a box
+///    the size of the [InkResponse] itself, unless [getRectCallback] is
+///    provided, in which case that callback defines the rectangle. The color of
+///    the highlight is set by [highlightColor].
+///
+///  * Simultaneously, it starts to animate a _splash_. This is a growing circle
+///    initially centered on the tap location. If this is a [containedInkWell],
+///    the splash grows to the [radius] while remaining centered at the tap
+///    location. Otherwise, the splash migrates to the center of the box as it
+///    grows.
+///
+/// The following two diagrams show how [InkResponse] looks when tapped if the
+/// [highlightShape] is [BoxShape.circle] (the default) and [containedInkWell]
+/// is false (also the default).
+///
+/// The first diagram shows how it looks if the [InkResponse] is relatively
+/// large:
+///
+/// ![The highlight is a disc centered in the box, smaller than the child widget.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_response_large.png)
+///
+/// The second diagram shows how it looks if the [InkResponse] is small:
+///
+/// ![The highlight is a disc overflowing the box, centered on the child.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_response_small.png)
+///
+/// The main thing to notice from these diagrams is that the splashes happily
+/// exceed the bounds of the widget (because [containedInkWell] is false).
+///
+/// The following diagram shows the effect when the [InkResponse] has a
+/// [highlightShape] of [BoxShape.rectangle] with [containedInkWell] set to
+/// true. These are the values used by [InkWell].
+///
+/// ![The highlight is a rectangle the size of the box.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_well.png)
+///
+/// The [InkResponse] widget must have a [Material] widget as an ancestor. The
+/// [Material] widget is where the ink reactions are actually painted. This
+/// matches the material design premise wherein the [Material] is what is
+/// actually reacting to touches by spreading ink.
+///
+/// If a Widget uses this class directly, it should include the following line
+/// at the top of its build function to call [debugCheckHasMaterial]:
+///
+/// ```dart
+/// assert(debugCheckHasMaterial(context));
+/// ```
+///
+/// ## Troubleshooting
+///
+/// ### The ink splashes aren't visible!
+///
+/// If there is an opaque graphic, e.g. painted using a [Container], [Image], or
+/// [DecoratedBox], between the [Material] widget and the [InkResponse] widget,
+/// then the splash won't be visible because it will be under the opaque graphic.
+/// This is because ink splashes draw on the underlying [Material] itself, as
+/// if the ink was spreading inside the material.
+///
+/// The [Ink] widget can be used as a replacement for [Image], [Container], or
+/// [DecoratedBox] to ensure that the image or decoration also paints in the
+/// [Material] itself, below the ink.
+///
+/// If this is not possible for some reason, e.g. because you are using an
+/// opaque [CustomPaint] widget, alternatively consider using a second
+/// [Material] above the opaque widget but below the [InkResponse] (as an
+/// ancestor to the ink response). The [MaterialType.transparency] material
+/// kind can be used for this purpose.
+///
+/// See also:
+///
+///  * [GestureDetector], for listening for gestures without ink splashes.
+///  * [ElevatedButton] and [TextButton], two kinds of buttons in material design.
+///  * [IconButton], which combines [InkResponse] with an [Icon].
+class InkResponse extends StatelessWidget {
+  /// Creates an area of a [Material] that responds to touch.
+  ///
+  /// Must have an ancestor [Material] widget in which to cause ink reactions.
+  ///
+  /// The [mouseCursor], [containedInkWell], [highlightShape], [enableFeedback],
+  /// and [excludeFromSemantics] arguments must not be null.
+  const InkResponse({
+    Key? key,
+    this.child,
+    this.onTap,
+    this.onTapDown,
+    this.onTapCancel,
+    this.onDoubleTap,
+    this.onLongPress,
+    this.onHighlightChanged,
+    this.onHover,
+    this.mouseCursor,
+    this.containedInkWell = false,
+    this.highlightShape = BoxShape.circle,
+    this.radius,
+    this.borderRadius,
+    this.customBorder,
+    this.focusColor,
+    this.hoverColor,
+    this.highlightColor,
+    this.overlayColor,
+    this.splashColor,
+    this.splashFactory,
+    this.enableFeedback = true,
+    this.excludeFromSemantics = false,
+    this.focusNode,
+    this.canRequestFocus = true,
+    this.onFocusChange,
+    this.autofocus = false,
+  }) : assert(containedInkWell != null),
+       assert(highlightShape != null),
+       assert(enableFeedback != null),
+       assert(excludeFromSemantics != null),
+       assert(autofocus != null),
+       assert(canRequestFocus != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// Called when the user taps this part of the material.
+  final GestureTapCallback? onTap;
+
+  /// Called when the user taps down this part of the material.
+  final GestureTapDownCallback? onTapDown;
+
+  /// Called when the user cancels a tap that was started on this part of the
+  /// material.
+  final GestureTapCallback? onTapCancel;
+
+  /// Called when the user double taps this part of the material.
+  final GestureTapCallback? onDoubleTap;
+
+  /// Called when the user long-presses on this part of the material.
+  final GestureLongPressCallback? onLongPress;
+
+  /// Called when this part of the material either becomes highlighted or stops
+  /// being highlighted.
+  ///
+  /// The value passed to the callback is true if this part of the material has
+  /// become highlighted and false if this part of the material has stopped
+  /// being highlighted.
+  ///
+  /// If all of [onTap], [onDoubleTap], and [onLongPress] become null while a
+  /// gesture is ongoing, then [onTapCancel] will be fired and
+  /// [onHighlightChanged] will be fired with the value false _during the
+  /// build_. This means, for instance, that in that scenario [State.setState]
+  /// cannot be called.
+  final ValueChanged<bool>? onHighlightChanged;
+
+  /// Called when a pointer enters or exits the ink response area.
+  ///
+  /// The value passed to the callback is true if a pointer has entered this
+  /// part of the material and false if a pointer has exited this part of the
+  /// material.
+  final ValueChanged<bool>? onHover;
+
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  ///
+  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
+  final MouseCursor? mouseCursor;
+
+  /// Whether this ink response should be clipped its bounds.
+  ///
+  /// This flag also controls whether the splash migrates to the center of the
+  /// [InkResponse] or not. If [containedInkWell] is true, the splash remains
+  /// centered around the tap location. If it is false, the splash migrates to
+  /// the center of the [InkResponse] as it grows.
+  ///
+  /// See also:
+  ///
+  ///  * [highlightShape], the shape of the focus, hover, and pressed
+  ///    highlights.
+  ///  * [borderRadius], which controls the corners when the box is a rectangle.
+  ///  * [getRectCallback], which controls the size and position of the box when
+  ///    it is a rectangle.
+  final bool containedInkWell;
+
+  /// The shape (e.g., circle, rectangle) to use for the highlight drawn around
+  /// this part of the material when pressed, hovered over, or focused.
+  ///
+  /// The same shape is used for the pressed highlight (see [highlightColor]),
+  /// the focus highlight (see [focusColor]), and the hover highlight (see
+  /// [hoverColor]).
+  ///
+  /// If the shape is [BoxShape.circle], then the highlight is centered on the
+  /// [InkResponse]. If the shape is [BoxShape.rectangle], then the highlight
+  /// fills the [InkResponse], or the rectangle provided by [getRectCallback] if
+  /// the callback is specified.
+  ///
+  /// See also:
+  ///
+  ///  * [containedInkWell], which controls clipping behavior.
+  ///  * [borderRadius], which controls the corners when the box is a rectangle.
+  ///  * [highlightColor], the color of the highlight.
+  ///  * [getRectCallback], which controls the size and position of the box when
+  ///    it is a rectangle.
+  final BoxShape highlightShape;
+
+  /// The radius of the ink splash.
+  ///
+  /// Splashes grow up to this size. By default, this size is determined from
+  /// the size of the rectangle provided by [getRectCallback], or the size of
+  /// the [InkResponse] itself.
+  ///
+  /// See also:
+  ///
+  ///  * [splashColor], the color of the splash.
+  ///  * [splashFactory], which defines the appearance of the splash.
+  final double? radius;
+
+  /// The clipping radius of the containing rect. This is effective only if
+  /// [customBorder] is null.
+  ///
+  /// If this is null, it is interpreted as [BorderRadius.zero].
+  final BorderRadius? borderRadius;
+
+  /// The custom clip border which overrides [borderRadius].
+  final ShapeBorder? customBorder;
+
+  /// The color of the ink response when the parent widget is focused. If this
+  /// property is null then the focus color of the theme,
+  /// [ThemeData.focusColor], will be used.
+  ///
+  /// See also:
+  ///
+  ///  * [highlightShape], the shape of the focus, hover, and pressed
+  ///    highlights.
+  ///  * [hoverColor], the color of the hover highlight.
+  ///  * [splashColor], the color of the splash.
+  ///  * [splashFactory], which defines the appearance of the splash.
+  final Color? focusColor;
+
+  /// The color of the ink response when a pointer is hovering over it. If this
+  /// property is null then the hover color of the theme,
+  /// [ThemeData.hoverColor], will be used.
+  ///
+  /// See also:
+  ///
+  ///  * [highlightShape], the shape of the focus, hover, and pressed
+  ///    highlights.
+  ///  * [highlightColor], the color of the pressed highlight.
+  ///  * [focusColor], the color of the focus highlight.
+  ///  * [splashColor], the color of the splash.
+  ///  * [splashFactory], which defines the appearance of the splash.
+  final Color? hoverColor;
+
+  /// The highlight color of the ink response when pressed. If this property is
+  /// null then the highlight color of the theme, [ThemeData.highlightColor],
+  /// will be used.
+  ///
+  /// See also:
+  ///
+  ///  * [hoverColor], the color of the hover highlight.
+  ///  * [focusColor], the color of the focus highlight.
+  ///  * [highlightShape], the shape of the focus, hover, and pressed
+  ///    highlights.
+  ///  * [splashColor], the color of the splash.
+  ///  * [splashFactory], which defines the appearance of the splash.
+  final Color? highlightColor;
+
+  /// Defines the ink response focus, hover, and splash colors.
+  ///
+  /// This default null property can be used as an alternative to
+  /// [focusColor], [hoverColor], and [splashColor]. If non-null,
+  /// it is resolved against one of [MaterialState.focused],
+  /// [MaterialState.hovered], and [MaterialState.pressed]. It's
+  /// convenient to use when the parent widget can pass along its own
+  /// MaterialStateProperty value for the overlay color.
+  ///
+  /// [MaterialState.pressed] triggers a ripple (an ink splash), per
+  /// the current Material Design spec. The [overlayColor] doesn't map
+  /// a state to [highlightColor] because a separate highlight is not
+  /// used by the current design guidelines.  See
+  /// https://material.io/design/interaction/states.html#pressed
+  ///
+  /// If the overlay color is null or resolves to null, then [focusColor],
+  /// [hoverColor], [splashColor] and their defaults are used instead.
+  ///
+  /// See also:
+  ///
+  ///  * The Material Design specification for overlay colors and how they
+  ///    match a component's state:
+  ///    <https://material.io/design/interaction/states.html#anatomy>.
+  final MaterialStateProperty<Color?>? overlayColor;
+
+  /// The splash color of the ink response. If this property is null then the
+  /// splash color of the theme, [ThemeData.splashColor], will be used.
+  ///
+  /// See also:
+  ///
+  ///  * [splashFactory], which defines the appearance of the splash.
+  ///  * [radius], the (maximum) size of the ink splash.
+  ///  * [highlightColor], the color of the highlight.
+  final Color? splashColor;
+
+  /// Defines the appearance of the splash.
+  ///
+  /// Defaults to the value of the theme's splash factory: [ThemeData.splashFactory].
+  ///
+  /// See also:
+  ///
+  ///  * [radius], the (maximum) size of the ink splash.
+  ///  * [splashColor], the color of the splash.
+  ///  * [highlightColor], the color of the highlight.
+  ///  * [InkSplash.splashFactory], which defines the default splash.
+  ///  * [InkRipple.splashFactory], which defines a splash that spreads out
+  ///    more aggressively than the default.
+  final InteractiveInkFeatureFactory? splashFactory;
+
+  /// Whether detected gestures should provide acoustic and/or haptic feedback.
+  ///
+  /// For example, on Android a tap will produce a clicking sound and a
+  /// long-press will produce a short vibration, when feedback is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [Feedback] for providing platform-specific feedback to certain actions.
+  final bool enableFeedback;
+
+  /// Whether to exclude the gestures introduced by this widget from the
+  /// semantics tree.
+  ///
+  /// For example, a long-press gesture for showing a tooltip is usually
+  /// excluded because the tooltip itself is included in the semantics
+  /// tree directly and so having a gesture to show it would result in
+  /// duplication of information.
+  final bool excludeFromSemantics;
+
+  /// Handler called when the focus changes.
+  ///
+  /// Called with true if this widget's node gains focus, and false if it loses
+  /// focus.
+  final ValueChanged<bool>? onFocusChange;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// {@macro flutter.widgets.Focus.canRequestFocus}
+  final bool canRequestFocus;
+
+  /// The rectangle to use for the highlight effect and for clipping
+  /// the splash effects if [containedInkWell] is true.
+  ///
+  /// This method is intended to be overridden by descendants that
+  /// specialize [InkResponse] for unusual cases. For example,
+  /// [TableRowInkWell] implements this method to return the rectangle
+  /// corresponding to the row that the widget is in.
+  ///
+  /// The default behavior returns null, which is equivalent to
+  /// returning the referenceBox argument's bounding box (though
+  /// slightly more efficient).
+  RectCallback? getRectCallback(RenderBox referenceBox) => null;
+
+  @override
+  Widget build(BuildContext context) {
+    final _ParentInkResponseState? parentState = _ParentInkResponseProvider.of(context);
+    return _InkResponseStateWidget(
+      child: child,
+      onTap: onTap,
+      onTapDown: onTapDown,
+      onTapCancel: onTapCancel,
+      onDoubleTap: onDoubleTap,
+      onLongPress: onLongPress,
+      onHighlightChanged: onHighlightChanged,
+      onHover: onHover,
+      mouseCursor: mouseCursor,
+      containedInkWell: containedInkWell,
+      highlightShape: highlightShape,
+      radius: radius,
+      borderRadius: borderRadius,
+      customBorder: customBorder,
+      focusColor: focusColor,
+      hoverColor: hoverColor,
+      highlightColor: highlightColor,
+      overlayColor: overlayColor,
+      splashColor: splashColor,
+      splashFactory: splashFactory,
+      enableFeedback: enableFeedback,
+      excludeFromSemantics: excludeFromSemantics,
+      focusNode: focusNode,
+      canRequestFocus: canRequestFocus,
+      onFocusChange: onFocusChange,
+      autofocus: autofocus,
+      parentState: parentState,
+      getRectCallback: getRectCallback,
+      debugCheckContext: debugCheckContext,
+    );
+  }
+
+  /// Asserts that the given context satisfies the prerequisites for
+  /// this class.
+  ///
+  /// This method is intended to be overridden by descendants that
+  /// specialize [InkResponse] for unusual cases. For example,
+  /// [TableRowInkWell] implements this method to verify that the widget is
+  /// in a table.
+  @mustCallSuper
+  bool debugCheckContext(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    assert(debugCheckHasDirectionality(context));
+    return true;
+  }
+}
+
+class _InkResponseStateWidget extends StatefulWidget {
+  const _InkResponseStateWidget({
+    this.child,
+    this.onTap,
+    this.onTapDown,
+    this.onTapCancel,
+    this.onDoubleTap,
+    this.onLongPress,
+    this.onHighlightChanged,
+    this.onHover,
+    this.mouseCursor,
+    this.containedInkWell = false,
+    this.highlightShape = BoxShape.circle,
+    this.radius,
+    this.borderRadius,
+    this.customBorder,
+    this.focusColor,
+    this.hoverColor,
+    this.highlightColor,
+    this.overlayColor,
+    this.splashColor,
+    this.splashFactory,
+    this.enableFeedback = true,
+    this.excludeFromSemantics = false,
+    this.focusNode,
+    this.canRequestFocus = true,
+    this.onFocusChange,
+    this.autofocus = false,
+    this.parentState,
+    this.getRectCallback,
+    required this.debugCheckContext,
+  }) : assert(containedInkWell != null),
+       assert(highlightShape != null),
+       assert(enableFeedback != null),
+       assert(excludeFromSemantics != null),
+       assert(autofocus != null),
+       assert(canRequestFocus != null);
+
+  final Widget? child;
+  final GestureTapCallback? onTap;
+  final GestureTapDownCallback? onTapDown;
+  final GestureTapCallback? onTapCancel;
+  final GestureTapCallback? onDoubleTap;
+  final GestureLongPressCallback? onLongPress;
+  final ValueChanged<bool>? onHighlightChanged;
+  final ValueChanged<bool>? onHover;
+  final MouseCursor? mouseCursor;
+  final bool containedInkWell;
+  final BoxShape highlightShape;
+  final double? radius;
+  final BorderRadius? borderRadius;
+  final ShapeBorder? customBorder;
+  final Color? focusColor;
+  final Color? hoverColor;
+  final Color? highlightColor;
+  final MaterialStateProperty<Color?>? overlayColor;
+  final Color? splashColor;
+  final InteractiveInkFeatureFactory? splashFactory;
+  final bool enableFeedback;
+  final bool excludeFromSemantics;
+  final ValueChanged<bool>? onFocusChange;
+  final bool autofocus;
+  final FocusNode? focusNode;
+  final bool canRequestFocus;
+  final _ParentInkResponseState? parentState;
+  final _GetRectCallback? getRectCallback;
+  final _CheckContext debugCheckContext;
+
+  @override
+  _InkResponseState createState() => _InkResponseState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    final List<String> gestures = <String>[
+      if (onTap != null) 'tap',
+      if (onDoubleTap != null) 'double tap',
+      if (onLongPress != null) 'long press',
+      if (onTapDown != null) 'tap down',
+      if (onTapCancel != null) 'tap cancel',
+    ];
+    properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
+    properties.add(DiagnosticsProperty<MouseCursor>('mouseCursor', mouseCursor));
+    properties.add(DiagnosticsProperty<bool>('containedInkWell', containedInkWell, level: DiagnosticLevel.fine));
+    properties.add(DiagnosticsProperty<BoxShape>(
+      'highlightShape',
+      highlightShape,
+      description: '${containedInkWell ? "clipped to " : ""}$highlightShape',
+      showName: false,
+    ));
+  }
+}
+
+/// Used to index the allocated highlights for the different types of highlights
+/// in [_InkResponseState].
+enum _HighlightType {
+  pressed,
+  hover,
+  focus,
+}
+
+class _InkResponseState extends State<_InkResponseStateWidget>
+    with AutomaticKeepAliveClientMixin<_InkResponseStateWidget>
+    implements _ParentInkResponseState {
+  Set<InteractiveInkFeature>? _splashes;
+  InteractiveInkFeature? _currentSplash;
+  bool _hovering = false;
+  final Map<_HighlightType, InkHighlight?> _highlights = <_HighlightType, InkHighlight?>{};
+  late final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{
+    ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: _simulateTap),
+    ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(onInvoke: _simulateTap),
+  };
+
+  bool get highlightsExist => _highlights.values.where((InkHighlight? highlight) => highlight != null).isNotEmpty;
+
+  final ObserverList<_ParentInkResponseState> _activeChildren = ObserverList<_ParentInkResponseState>();
+
+  @override
+  void markChildInkResponsePressed(_ParentInkResponseState childState, bool value) {
+    assert(childState != null);
+    final bool lastAnyPressed = _anyChildInkResponsePressed;
+    if (value) {
+      _activeChildren.add(childState);
+    } else {
+      _activeChildren.remove(childState);
+    }
+    final bool nowAnyPressed = _anyChildInkResponsePressed;
+    if (nowAnyPressed != lastAnyPressed) {
+      widget.parentState?.markChildInkResponsePressed(this, nowAnyPressed);
+    }
+  }
+  bool get _anyChildInkResponsePressed => _activeChildren.isNotEmpty;
+
+  void _simulateTap([Intent? intent]) {
+    _startSplash(context: context);
+    _handleTap();
+  }
+
+  void _simulateLongPress() {
+    _startSplash(context: context);
+    _handleLongPress();
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    FocusManager.instance.addHighlightModeListener(_handleFocusHighlightModeChange);
+  }
+
+  @override
+  void didUpdateWidget(_InkResponseStateWidget oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (_isWidgetEnabled(widget) != _isWidgetEnabled(oldWidget)) {
+      if (enabled) {
+        // Don't call wigdet.onHover because many wigets, including the button
+        // widgets, apply setState to an ancestor context from onHover.
+        updateHighlight(_HighlightType.hover, value: _hovering, callOnHover: false);
+      }
+      _updateFocusHighlights();
+    }
+  }
+
+  @override
+  void dispose() {
+    FocusManager.instance.removeHighlightModeListener(_handleFocusHighlightModeChange);
+    super.dispose();
+  }
+
+  @override
+  bool get wantKeepAlive => highlightsExist || (_splashes != null && _splashes!.isNotEmpty);
+
+  Color getHighlightColorForType(_HighlightType type) {
+    const Set<MaterialState> focused = <MaterialState>{MaterialState.focused};
+    const Set<MaterialState> hovered = <MaterialState>{MaterialState.hovered};
+
+    switch (type) {
+      // The pressed state triggers a ripple (ink splash), per the current
+      // Material Design spec. A separate highlight is no longer used.
+      // See https://material.io/design/interaction/states.html#pressed
+      case _HighlightType.pressed:
+        return widget.highlightColor ?? Theme.of(context).highlightColor;
+      case _HighlightType.focus:
+        return widget.overlayColor?.resolve(focused) ?? widget.focusColor ?? Theme.of(context).focusColor;
+      case _HighlightType.hover:
+        return widget.overlayColor?.resolve(hovered) ?? widget.hoverColor ?? Theme.of(context).hoverColor;
+    }
+  }
+
+  Duration getFadeDurationForType(_HighlightType type) {
+    switch (type) {
+      case _HighlightType.pressed:
+        return const Duration(milliseconds: 200);
+      case _HighlightType.hover:
+      case _HighlightType.focus:
+        return const Duration(milliseconds: 50);
+    }
+  }
+
+  void updateHighlight(_HighlightType type, { required bool value, bool callOnHover = true }) {
+    final InkHighlight? highlight = _highlights[type];
+    void handleInkRemoval() {
+      assert(_highlights[type] != null);
+      _highlights[type] = null;
+      updateKeepAlive();
+    }
+
+    if (type == _HighlightType.pressed) {
+      widget.parentState?.markChildInkResponsePressed(this, value);
+    }
+    if (value == (highlight != null && highlight.active))
+      return;
+    if (value) {
+      if (highlight == null) {
+        final RenderBox referenceBox = context.findRenderObject()! as RenderBox;
+        _highlights[type] = InkHighlight(
+          controller: Material.of(context)!,
+          referenceBox: referenceBox,
+          color: getHighlightColorForType(type),
+          shape: widget.highlightShape,
+          radius: widget.radius,
+          borderRadius: widget.borderRadius,
+          customBorder: widget.customBorder,
+          rectCallback: widget.getRectCallback!(referenceBox),
+          onRemoved: handleInkRemoval,
+          textDirection: Directionality.of(context),
+          fadeDuration: getFadeDurationForType(type),
+        );
+        updateKeepAlive();
+      } else {
+        highlight.activate();
+      }
+    } else {
+      highlight!.deactivate();
+    }
+    assert(value == (_highlights[type] != null && _highlights[type]!.active));
+
+    switch (type) {
+      case _HighlightType.pressed:
+        if (widget.onHighlightChanged != null)
+          widget.onHighlightChanged!(value);
+        break;
+      case _HighlightType.hover:
+        if (callOnHover && widget.onHover != null)
+          widget.onHover!(value);
+        break;
+      case _HighlightType.focus:
+        break;
+    }
+  }
+
+  InteractiveInkFeature _createInkFeature(Offset globalPosition) {
+    final MaterialInkController inkController = Material.of(context)!;
+    final RenderBox referenceBox = context.findRenderObject()! as RenderBox;
+    final Offset position = referenceBox.globalToLocal(globalPosition);
+    const Set<MaterialState> pressed = <MaterialState>{MaterialState.pressed};
+    final Color color =  widget.overlayColor?.resolve(pressed) ?? widget.splashColor ?? Theme.of(context).splashColor;
+    final RectCallback? rectCallback = widget.containedInkWell ? widget.getRectCallback!(referenceBox) : null;
+    final BorderRadius? borderRadius = widget.borderRadius;
+    final ShapeBorder? customBorder = widget.customBorder;
+
+    InteractiveInkFeature? splash;
+    void onRemoved() {
+      if (_splashes != null) {
+        assert(_splashes!.contains(splash));
+        _splashes!.remove(splash);
+        if (_currentSplash == splash)
+          _currentSplash = null;
+        updateKeepAlive();
+      } // else we're probably in deactivate()
+    }
+
+    splash = (widget.splashFactory ?? Theme.of(context).splashFactory).create(
+      controller: inkController,
+      referenceBox: referenceBox,
+      position: position,
+      color: color,
+      containedInkWell: widget.containedInkWell,
+      rectCallback: rectCallback,
+      radius: widget.radius,
+      borderRadius: borderRadius,
+      customBorder: customBorder,
+      onRemoved: onRemoved,
+      textDirection: Directionality.of(context),
+    );
+
+    return splash;
+  }
+
+  void _handleFocusHighlightModeChange(FocusHighlightMode mode) {
+    if (!mounted) {
+      return;
+    }
+    setState(() {
+      _updateFocusHighlights();
+    });
+  }
+
+  bool get _shouldShowFocus {
+    final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
+    switch (mode) {
+      case NavigationMode.traditional:
+        return enabled && _hasFocus;
+      case NavigationMode.directional:
+        return _hasFocus;
+    }
+  }
+
+  void _updateFocusHighlights() {
+    final bool showFocus;
+    switch (FocusManager.instance.highlightMode) {
+      case FocusHighlightMode.touch:
+        showFocus = false;
+        break;
+      case FocusHighlightMode.traditional:
+        showFocus = _shouldShowFocus;
+        break;
+    }
+    updateHighlight(_HighlightType.focus, value: showFocus);
+  }
+
+  bool _hasFocus = false;
+  void _handleFocusUpdate(bool hasFocus) {
+    _hasFocus = hasFocus;
+    _updateFocusHighlights();
+    if (widget.onFocusChange != null) {
+      widget.onFocusChange!(hasFocus);
+    }
+  }
+
+  void _handleTapDown(TapDownDetails details) {
+    if (_anyChildInkResponsePressed)
+      return;
+    _startSplash(details: details);
+    if (widget.onTapDown != null) {
+      widget.onTapDown!(details);
+    }
+  }
+
+  void _startSplash({TapDownDetails? details, BuildContext? context}) {
+    assert(details != null || context != null);
+
+    final Offset globalPosition;
+    if (context != null) {
+      final RenderBox referenceBox = context.findRenderObject()! as RenderBox;
+      assert(referenceBox.hasSize, 'InkResponse must be done with layout before starting a splash.');
+      globalPosition = referenceBox.localToGlobal(referenceBox.paintBounds.center);
+    } else {
+      globalPosition = details!.globalPosition;
+    }
+    final InteractiveInkFeature splash = _createInkFeature(globalPosition);
+    _splashes ??= HashSet<InteractiveInkFeature>();
+    _splashes!.add(splash);
+    _currentSplash = splash;
+    updateKeepAlive();
+    updateHighlight(_HighlightType.pressed, value: true);
+  }
+
+  void _handleTap() {
+    _currentSplash?.confirm();
+    _currentSplash = null;
+    updateHighlight(_HighlightType.pressed, value: false);
+    if (widget.onTap != null) {
+      if (widget.enableFeedback)
+        Feedback.forTap(context);
+      widget.onTap!();
+    }
+  }
+
+  void _handleTapCancel() {
+    _currentSplash?.cancel();
+    _currentSplash = null;
+    if (widget.onTapCancel != null) {
+      widget.onTapCancel!();
+    }
+    updateHighlight(_HighlightType.pressed, value: false);
+  }
+
+  void _handleDoubleTap() {
+    _currentSplash?.confirm();
+    _currentSplash = null;
+    if (widget.onDoubleTap != null)
+      widget.onDoubleTap!();
+  }
+
+  void _handleLongPress() {
+    _currentSplash?.confirm();
+    _currentSplash = null;
+    if (widget.onLongPress != null) {
+      if (widget.enableFeedback)
+        Feedback.forLongPress(context);
+      widget.onLongPress!();
+    }
+  }
+
+  @override
+  void deactivate() {
+    if (_splashes != null) {
+      final Set<InteractiveInkFeature> splashes = _splashes!;
+      _splashes = null;
+      for (final InteractiveInkFeature splash in splashes)
+        splash.dispose();
+      _currentSplash = null;
+    }
+    assert(_currentSplash == null);
+    for (final _HighlightType highlight in _highlights.keys) {
+      _highlights[highlight]?.dispose();
+      _highlights[highlight] = null;
+    }
+    widget.parentState?.markChildInkResponsePressed(this, false);
+    super.deactivate();
+  }
+
+  bool _isWidgetEnabled(_InkResponseStateWidget widget) {
+    return widget.onTap != null || widget.onDoubleTap != null || widget.onLongPress != null;
+  }
+
+  bool get enabled => _isWidgetEnabled(widget);
+
+  void _handleMouseEnter(PointerEnterEvent event) {
+    _hovering = true;
+    if (enabled) {
+      _handleHoverChange();
+    }
+  }
+
+  void _handleMouseExit(PointerExitEvent event) {
+    _hovering = false;
+    // If the exit occurs after we've been disabled, we still
+    // want to take down the highlights and run widget.onHover.
+    _handleHoverChange();
+  }
+
+  void _handleHoverChange() {
+    updateHighlight(_HighlightType.hover, value: _hovering);
+  }
+
+  bool get _canRequestFocus {
+    final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
+    switch (mode) {
+      case NavigationMode.traditional:
+        return enabled && widget.canRequestFocus;
+      case NavigationMode.directional:
+        return true;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(widget.debugCheckContext(context));
+    super.build(context); // See AutomaticKeepAliveClientMixin.
+    for (final _HighlightType type in _highlights.keys) {
+      _highlights[type]?.color = getHighlightColorForType(type);
+    }
+
+    const Set<MaterialState> pressed = <MaterialState>{MaterialState.pressed};
+    _currentSplash?.color = widget.overlayColor?.resolve(pressed) ?? widget.splashColor ?? Theme.of(context).splashColor;
+
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
+      widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
+      <MaterialState>{
+        if (!enabled) MaterialState.disabled,
+        if (_hovering && enabled) MaterialState.hovered,
+        if (_hasFocus) MaterialState.focused,
+      },
+    );
+    return _ParentInkResponseProvider(
+      state: this,
+      child: Actions(
+        actions: _actionMap,
+        child: Focus(
+          focusNode: widget.focusNode,
+          canRequestFocus: _canRequestFocus,
+          onFocusChange: _handleFocusUpdate,
+          autofocus: widget.autofocus,
+          child: MouseRegion(
+            cursor: effectiveMouseCursor,
+            onEnter: _handleMouseEnter,
+            onExit: _handleMouseExit,
+            child: Semantics(
+              onTap: widget.excludeFromSemantics || widget.onTap == null ? null : _simulateTap,
+              onLongPress: widget.excludeFromSemantics || widget.onLongPress == null ? null : _simulateLongPress,
+              child: GestureDetector(
+                onTapDown: enabled ? _handleTapDown : null,
+                onTap: enabled ? _handleTap : null,
+                onTapCancel: enabled ? _handleTapCancel : null,
+                onDoubleTap: widget.onDoubleTap != null ? _handleDoubleTap : null,
+                onLongPress: widget.onLongPress != null ? _handleLongPress : null,
+                behavior: HitTestBehavior.opaque,
+                excludeFromSemantics: true,
+                child: widget.child,
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+/// A rectangular area of a [Material] that responds to touch.
+///
+/// For a variant of this widget that does not clip splashes, see [InkResponse].
+///
+/// The following diagram shows how an [InkWell] looks when tapped, when using
+/// default values.
+///
+/// ![The highlight is a rectangle the size of the box.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_well.png)
+///
+/// The [InkWell] widget must have a [Material] widget as an ancestor. The
+/// [Material] widget is where the ink reactions are actually painted. This
+/// matches the material design premise wherein the [Material] is what is
+/// actually reacting to touches by spreading ink.
+///
+/// If a Widget uses this class directly, it should include the following line
+/// at the top of its build function to call [debugCheckHasMaterial]:
+///
+/// ```dart
+/// assert(debugCheckHasMaterial(context));
+/// ```
+///
+/// ## Troubleshooting
+///
+/// ### The ink splashes aren't visible!
+///
+/// If there is an opaque graphic, e.g. painted using a [Container], [Image], or
+/// [DecoratedBox], between the [Material] widget and the [InkWell] widget, then
+/// the splash won't be visible because it will be under the opaque graphic.
+/// This is because ink splashes draw on the underlying [Material] itself, as
+/// if the ink was spreading inside the material.
+///
+/// The [Ink] widget can be used as a replacement for [Image], [Container], or
+/// [DecoratedBox] to ensure that the image or decoration also paints in the
+/// [Material] itself, below the ink.
+///
+/// If this is not possible for some reason, e.g. because you are using an
+/// opaque [CustomPaint] widget, alternatively consider using a second
+/// [Material] above the opaque widget but below the [InkWell] (as an
+/// ancestor to the ink well). The [MaterialType.transparency] material
+/// kind can be used for this purpose.
+///
+/// ### The ink splashes don't track the size of an animated container
+/// If the size of an InkWell's [Material] ancestor changes while the InkWell's
+/// splashes are expanding, you may notice that the splashes aren't clipped
+/// correctly. This can't be avoided.
+///
+/// An example of this situation is as follows:
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+///
+/// Tap the container to cause it to grow. Then, tap it again and hold before
+/// the widget reaches its maximum size to observe the clipped ink splash.
+///
+/// ```dart
+/// double sideLength = 50;
+///
+/// Widget build(BuildContext context) {
+///   return AnimatedContainer(
+///     height: sideLength,
+///     width: sideLength,
+///     duration: Duration(seconds: 2),
+///     curve: Curves.easeIn,
+///     child: Material(
+///       color: Colors.yellow,
+///       child: InkWell(
+///         onTap: () {
+///           setState(() {
+///             sideLength == 50 ? sideLength = 100 : sideLength = 50;
+///           });
+///         },
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// An InkWell's splashes will not properly update to conform to changes if the
+/// size of its underlying [Material], where the splashes are rendered, changes
+/// during animation. You should avoid using InkWells within [Material] widgets
+/// that are changing size.
+///
+/// See also:
+///
+///  * [GestureDetector], for listening for gestures without ink splashes.
+///  * [ElevatedButton] and [TextButton], two kinds of buttons in material design.
+///  * [InkResponse], a variant of [InkWell] that doesn't force a rectangular
+///    shape on the ink reaction.
+class InkWell extends InkResponse {
+  /// Creates an ink well.
+  ///
+  /// Must have an ancestor [Material] widget in which to cause ink reactions.
+  ///
+  /// The [mouseCursor], [enableFeedback], and [excludeFromSemantics] arguments
+  /// must not be null.
+  const InkWell({
+    Key? key,
+    Widget? child,
+    GestureTapCallback? onTap,
+    GestureTapCallback? onDoubleTap,
+    GestureLongPressCallback? onLongPress,
+    GestureTapDownCallback? onTapDown,
+    GestureTapCancelCallback? onTapCancel,
+    ValueChanged<bool>? onHighlightChanged,
+    ValueChanged<bool>? onHover,
+    MouseCursor? mouseCursor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    MaterialStateProperty<Color?>? overlayColor,
+    Color? splashColor,
+    InteractiveInkFeatureFactory? splashFactory,
+    double? radius,
+    BorderRadius? borderRadius,
+    ShapeBorder? customBorder,
+    bool? enableFeedback = true,
+    bool excludeFromSemantics = false,
+    FocusNode? focusNode,
+    bool canRequestFocus = true,
+    ValueChanged<bool>? onFocusChange,
+    bool autofocus = false,
+  }) : super(
+    key: key,
+    child: child,
+    onTap: onTap,
+    onDoubleTap: onDoubleTap,
+    onLongPress: onLongPress,
+    onTapDown: onTapDown,
+    onTapCancel: onTapCancel,
+    onHighlightChanged: onHighlightChanged,
+    onHover: onHover,
+    mouseCursor: mouseCursor,
+    containedInkWell: true,
+    highlightShape: BoxShape.rectangle,
+    focusColor: focusColor,
+    hoverColor: hoverColor,
+    highlightColor: highlightColor,
+    overlayColor: overlayColor,
+    splashColor: splashColor,
+    splashFactory: splashFactory,
+    radius: radius,
+    borderRadius: borderRadius,
+    customBorder: customBorder,
+    enableFeedback: enableFeedback ?? true,
+    excludeFromSemantics: excludeFromSemantics,
+    focusNode: focusNode,
+    canRequestFocus: canRequestFocus,
+    onFocusChange: onFocusChange,
+    autofocus: autofocus,
+  );
+}
diff --git a/lib/src/material/input_border.dart b/lib/src/material/input_border.dart
new file mode 100644
index 0000000..fc82590
--- /dev/null
+++ b/lib/src/material/input_border.dart
@@ -0,0 +1,508 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/widgets.dart';
+
+/// Defines the appearance of an [InputDecorator]'s border.
+///
+/// An input decorator's border is specified by [InputDecoration.border].
+///
+/// The border is drawn relative to the input decorator's "container" which
+/// is the optionally filled area above the decorator's helper, error,
+/// and counter.
+///
+/// Input border's are decorated with a line whose weight and color are defined
+/// by [borderSide]. The input decorator's renderer animates the input border's
+/// appearance in response to state changes, like gaining or losing the focus,
+/// by creating new copies of its input border with [copyWith].
+///
+/// See also:
+///
+///  * [UnderlineInputBorder], the default [InputDecorator] border which
+///    draws a horizontal line at the bottom of the input decorator's container.
+///  * [OutlineInputBorder], an [InputDecorator] border which draws a
+///    rounded rectangle around the input decorator's container.
+///  * [InputDecoration], which is used to configure an [InputDecorator].
+abstract class InputBorder extends ShapeBorder {
+  /// Creates a border for an [InputDecorator].
+  ///
+  /// The [borderSide] parameter must not be null. Applications typically do
+  /// not specify a [borderSide] parameter because the input decorator
+  /// substitutes its own, using [copyWith], based on the current theme and
+  /// [InputDecorator.isFocused].
+  const InputBorder({
+    this.borderSide = BorderSide.none,
+  }) : assert(borderSide != null);
+
+  /// No input border.
+  ///
+  /// Use this value with [InputDecoration.border] to specify that no border
+  /// should be drawn. The [InputDecoration.collapsed] constructor sets
+  /// its border to this value.
+  static const InputBorder none = _NoInputBorder();
+
+  /// Defines the border line's color and weight.
+  ///
+  /// The [InputDecorator] creates copies of its input border, using [copyWith],
+  /// based on the current theme and [InputDecorator.isFocused].
+  final BorderSide borderSide;
+
+  /// Creates a copy of this input border with the specified `borderSide`.
+  InputBorder copyWith({ BorderSide? borderSide });
+
+  /// True if this border will enclose the [InputDecorator]'s container.
+  ///
+  /// This property affects the alignment of container's contents. For example
+  /// when an input decorator is configured with an [OutlineInputBorder] its
+  /// label is centered with its container.
+  bool get isOutline;
+
+  /// Paint this input border on [canvas].
+  ///
+  /// The [rect] parameter bounds the [InputDecorator]'s container.
+  ///
+  /// The additional `gap` parameters reflect the state of the [InputDecorator]'s
+  /// floating label. When an input decorator gains the focus, its label
+  /// animates upwards, to make room for the input child. The [gapStart] and
+  /// [gapExtent] parameters define a floating label width interval, and
+  /// [gapPercentage] defines the animation's progress (0.0 to 1.0).
+  @override
+  void paint(
+    Canvas canvas,
+    Rect rect, {
+    double? gapStart,
+    double gapExtent = 0.0,
+    double gapPercentage = 0.0,
+    TextDirection? textDirection,
+  });
+}
+
+// Used to create the InputBorder.none singleton.
+class _NoInputBorder extends InputBorder {
+  const _NoInputBorder() : super(borderSide: BorderSide.none);
+
+  @override
+  _NoInputBorder copyWith({ BorderSide? borderSide }) => const _NoInputBorder();
+
+  @override
+  bool get isOutline => false;
+
+  @override
+  EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
+
+  @override
+  _NoInputBorder scale(double t) => const _NoInputBorder();
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()..addRect(rect);
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()..addRect(rect);
+  }
+
+  @override
+  void paint(
+    Canvas canvas,
+    Rect rect, {
+    double? gapStart,
+    double gapExtent = 0.0,
+    double gapPercentage = 0.0,
+    TextDirection? textDirection,
+  }) {
+    // Do not paint.
+  }
+}
+
+/// Draws a horizontal line at the bottom of an [InputDecorator]'s container and
+/// defines the container's shape.
+///
+/// The input decorator's "container" is the optionally filled area above the
+/// decorator's helper, error, and counter.
+///
+/// See also:
+///
+///  * [OutlineInputBorder], an [InputDecorator] border which draws a
+///    rounded rectangle around the input decorator's container.
+///  * [InputDecoration], which is used to configure an [InputDecorator].
+class UnderlineInputBorder extends InputBorder {
+  /// Creates an underline border for an [InputDecorator].
+  ///
+  /// The [borderSide] parameter defaults to [BorderSide.none] (it must not be
+  /// null). Applications typically do not specify a [borderSide] parameter
+  /// because the input decorator substitutes its own, using [copyWith], based
+  /// on the current theme and [InputDecorator.isFocused].
+  ///
+  /// The [borderRadius] parameter defaults to a value where the top left
+  /// and right corners have a circular radius of 4.0. The [borderRadius]
+  /// parameter must not be null.
+  const UnderlineInputBorder({
+    BorderSide borderSide = const BorderSide(),
+    this.borderRadius = const BorderRadius.only(
+      topLeft: Radius.circular(4.0),
+      topRight: Radius.circular(4.0),
+    ),
+  }) : assert(borderRadius != null),
+       super(borderSide: borderSide);
+
+  /// The radii of the border's rounded rectangle corners.
+  ///
+  /// When this border is used with a filled input decorator, see
+  /// [InputDecoration.filled], the border radius defines the shape
+  /// of the background fill as well as the bottom left and right
+  /// edges of the underline itself.
+  ///
+  /// By default the top right and top left corners have a circular radius
+  /// of 4.0.
+  final BorderRadius borderRadius;
+
+  @override
+  bool get isOutline => false;
+
+  @override
+  UnderlineInputBorder copyWith({ BorderSide? borderSide, BorderRadius? borderRadius }) {
+    return UnderlineInputBorder(
+      borderSide: borderSide ?? this.borderSide,
+      borderRadius: borderRadius ?? this.borderRadius,
+    );
+  }
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    return EdgeInsets.only(bottom: borderSide.width);
+  }
+
+  @override
+  UnderlineInputBorder scale(double t) {
+    return UnderlineInputBorder(borderSide: borderSide.scale(t));
+  }
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()
+      ..addRect(Rect.fromLTWH(rect.left, rect.top, rect.width, math.max(0.0, rect.height - borderSide.width)));
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
+  }
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    if (a is UnderlineInputBorder) {
+      return UnderlineInputBorder(
+        borderSide: BorderSide.lerp(a.borderSide, borderSide, t),
+        borderRadius: BorderRadius.lerp(a.borderRadius, borderRadius, t)!,
+      );
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    if (b is UnderlineInputBorder) {
+      return UnderlineInputBorder(
+        borderSide: BorderSide.lerp(borderSide, b.borderSide, t),
+        borderRadius: BorderRadius.lerp(borderRadius, b.borderRadius, t)!,
+      );
+    }
+    return super.lerpTo(b, t);
+  }
+
+  /// Draw a horizontal line at the bottom of [rect].
+  ///
+  /// The [borderSide] defines the line's color and weight. The `textDirection`
+  /// `gap` and `textDirection` parameters are ignored.
+  @override
+  void paint(
+    Canvas canvas,
+    Rect rect, {
+    double? gapStart,
+    double gapExtent = 0.0,
+    double gapPercentage = 0.0,
+    TextDirection? textDirection,
+  }) {
+    if (borderRadius.bottomLeft != Radius.zero || borderRadius.bottomRight != Radius.zero)
+      canvas.clipPath(getOuterPath(rect, textDirection: textDirection));
+    canvas.drawLine(rect.bottomLeft, rect.bottomRight, borderSide.toPaint());
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is InputBorder
+        && other.borderSide == borderSide;
+  }
+
+  @override
+  int get hashCode => borderSide.hashCode;
+}
+
+/// Draws a rounded rectangle around an [InputDecorator]'s container.
+///
+/// When the input decorator's label is floating, for example because its
+/// input child has the focus, the label appears in a gap in the border outline.
+///
+/// The input decorator's "container" is the optionally filled area above the
+/// decorator's helper, error, and counter.
+///
+/// See also:
+///
+///  * [UnderlineInputBorder], the default [InputDecorator] border which
+///    draws a horizontal line at the bottom of the input decorator's container.
+///  * [InputDecoration], which is used to configure an [InputDecorator].
+class OutlineInputBorder extends InputBorder {
+  /// Creates a rounded rectangle outline border for an [InputDecorator].
+  ///
+  /// If the [borderSide] parameter is [BorderSide.none], it will not draw a
+  /// border. However, it will still define a shape (which you can see if
+  /// [InputDecoration.filled] is true).
+  ///
+  /// If an application does not specify a [borderSide] parameter of
+  /// value [BorderSide.none], the input decorator substitutes its own, using
+  /// [copyWith], based on the current theme and [InputDecorator.isFocused].
+  ///
+  /// The [borderRadius] parameter defaults to a value where all four
+  /// corners have a circular radius of 4.0. The [borderRadius] parameter
+  /// must not be null and the corner radii must be circular, i.e. their
+  /// [Radius.x] and [Radius.y] values must be the same.
+  ///
+  /// See also:
+  ///
+  ///  * [InputDecoration.hasFloatingPlaceholder], which should be set to false
+  ///    when the [borderSide] is [BorderSide.none]. If let as true, the label
+  ///    will extend beyond the container as if the border were still being
+  ///    drawn.
+  const OutlineInputBorder({
+    BorderSide borderSide = const BorderSide(),
+    this.borderRadius = const BorderRadius.all(Radius.circular(4.0)),
+    this.gapPadding = 4.0,
+  }) : assert(borderRadius != null),
+       assert(gapPadding != null && gapPadding >= 0.0),
+       super(borderSide: borderSide);
+
+  // The label text's gap can extend into the corners (even both the top left
+  // and the top right corner). To avoid the more complicated problem of finding
+  // how far the gap penetrates into an elliptical corner, just require them
+  // to be circular.
+  //
+  // This can't be checked by the constructor because const constructor.
+  static bool _cornersAreCircular(BorderRadius borderRadius) {
+    return borderRadius.topLeft.x == borderRadius.topLeft.y
+        && borderRadius.bottomLeft.x == borderRadius.bottomLeft.y
+        && borderRadius.topRight.x == borderRadius.topRight.y
+        && borderRadius.bottomRight.x == borderRadius.bottomRight.y;
+  }
+
+  /// Horizontal padding on either side of the border's
+  /// [InputDecoration.labelText] width gap.
+  ///
+  /// This value is used by the [paint] method to compute the actual gap width.
+  final double gapPadding;
+
+  /// The radii of the border's rounded rectangle corners.
+  ///
+  /// The corner radii must be circular, i.e. their [Radius.x] and [Radius.y]
+  /// values must be the same.
+  final BorderRadius borderRadius;
+
+  @override
+  bool get isOutline => true;
+
+  @override
+  OutlineInputBorder copyWith({
+    BorderSide? borderSide,
+    BorderRadius? borderRadius,
+    double? gapPadding,
+  }) {
+    return OutlineInputBorder(
+      borderSide: borderSide ?? this.borderSide,
+      borderRadius: borderRadius ?? this.borderRadius,
+      gapPadding: gapPadding ?? this.gapPadding,
+    );
+  }
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    return EdgeInsets.all(borderSide.width);
+  }
+
+  @override
+  OutlineInputBorder scale(double t) {
+    return OutlineInputBorder(
+      borderSide: borderSide.scale(t),
+      borderRadius: borderRadius * t,
+      gapPadding: gapPadding * t,
+    );
+  }
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    if (a is OutlineInputBorder) {
+      final OutlineInputBorder outline = a;
+      return OutlineInputBorder(
+        borderRadius: BorderRadius.lerp(outline.borderRadius, borderRadius, t)!,
+        borderSide: BorderSide.lerp(outline.borderSide, borderSide, t),
+        gapPadding: outline.gapPadding,
+      );
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    if (b is OutlineInputBorder) {
+      final OutlineInputBorder outline = b;
+      return OutlineInputBorder(
+        borderRadius: BorderRadius.lerp(borderRadius, outline.borderRadius, t)!,
+        borderSide: BorderSide.lerp(borderSide, outline.borderSide, t),
+        gapPadding: outline.gapPadding,
+      );
+    }
+    return super.lerpTo(b, t);
+  }
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()
+      ..addRRect(borderRadius.resolve(textDirection).toRRect(rect).deflate(borderSide.width));
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()
+      ..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
+  }
+
+  Path _gapBorderPath(Canvas canvas, RRect center, double start, double extent) {
+    // When the corner radii on any side add up to be greater than the
+    // given height, each radius has to be scaled to not exceed the
+    // size of the width/height of the RRect.
+    final RRect scaledRRect = center.scaleRadii();
+
+    final Rect tlCorner = Rect.fromLTWH(
+      scaledRRect.left,
+      scaledRRect.top,
+      scaledRRect.tlRadiusX * 2.0,
+      scaledRRect.tlRadiusY * 2.0,
+    );
+    final Rect trCorner = Rect.fromLTWH(
+      scaledRRect.right - scaledRRect.trRadiusX * 2.0,
+      scaledRRect.top,
+      scaledRRect.trRadiusX * 2.0,
+      scaledRRect.trRadiusY * 2.0,
+    );
+    final Rect brCorner = Rect.fromLTWH(
+      scaledRRect.right - scaledRRect.brRadiusX * 2.0,
+      scaledRRect.bottom - scaledRRect.brRadiusY * 2.0,
+      scaledRRect.brRadiusX * 2.0,
+      scaledRRect.brRadiusY * 2.0,
+    );
+    final Rect blCorner = Rect.fromLTWH(
+      scaledRRect.left,
+      scaledRRect.bottom - scaledRRect.blRadiusY * 2.0,
+      scaledRRect.blRadiusX * 2.0,
+      scaledRRect.blRadiusX * 2.0,
+    );
+
+    const double cornerArcSweep = math.pi / 2.0;
+    final double tlCornerArcSweep = start < scaledRRect.tlRadiusX
+      ? math.asin((start / scaledRRect.tlRadiusX).clamp(-1.0, 1.0))
+      : math.pi / 2.0;
+
+    final Path path = Path()
+      ..addArc(tlCorner, math.pi, tlCornerArcSweep)
+      ..moveTo(scaledRRect.left + scaledRRect.tlRadiusX, scaledRRect.top);
+
+    if (start > scaledRRect.tlRadiusX)
+      path.lineTo(scaledRRect.left + start, scaledRRect.top);
+
+    const double trCornerArcStart = (3 * math.pi) / 2.0;
+    const double trCornerArcSweep = cornerArcSweep;
+    if (start + extent < scaledRRect.width - scaledRRect.trRadiusX) {
+      path
+        ..relativeMoveTo(extent, 0.0)
+        ..lineTo(scaledRRect.right - scaledRRect.trRadiusX, scaledRRect.top)
+        ..addArc(trCorner, trCornerArcStart, trCornerArcSweep);
+    } else if (start + extent < scaledRRect.width) {
+      final double dx = scaledRRect.width - (start + extent);
+      final double sweep = math.acos(dx / scaledRRect.trRadiusX);
+      path.addArc(trCorner, trCornerArcStart + sweep, trCornerArcSweep - sweep);
+    }
+
+    return path
+      ..moveTo(scaledRRect.right, scaledRRect.top + scaledRRect.trRadiusY)
+      ..lineTo(scaledRRect.right, scaledRRect.bottom - scaledRRect.brRadiusY)
+      ..addArc(brCorner, 0.0, cornerArcSweep)
+      ..lineTo(scaledRRect.left + scaledRRect.blRadiusX, scaledRRect.bottom)
+      ..addArc(blCorner, math.pi / 2.0, cornerArcSweep)
+      ..lineTo(scaledRRect.left, scaledRRect.top + scaledRRect.tlRadiusY);
+  }
+
+  /// Draw a rounded rectangle around [rect] using [borderRadius].
+  ///
+  /// The [borderSide] defines the line's color and weight.
+  ///
+  /// The top side of the rounded rectangle may be interrupted by a single gap
+  /// if [gapExtent] is non-null. In that case the gap begins at
+  /// `gapStart - gapPadding` (assuming that the [textDirection] is [TextDirection.ltr]).
+  /// The gap's width is `(gapPadding + gapExtent + gapPadding) * gapPercentage`.
+  @override
+  void paint(
+    Canvas canvas,
+    Rect rect, {
+    double? gapStart,
+    double gapExtent = 0.0,
+    double gapPercentage = 0.0,
+    TextDirection? textDirection,
+  }) {
+    assert(gapExtent != null);
+    assert(gapPercentage >= 0.0 && gapPercentage <= 1.0);
+    assert(_cornersAreCircular(borderRadius));
+
+    final Paint paint = borderSide.toPaint();
+    final RRect outer = borderRadius.toRRect(rect);
+    final RRect center = outer.deflate(borderSide.width / 2.0);
+    if (gapStart == null || gapExtent <= 0.0 || gapPercentage == 0.0) {
+      canvas.drawRRect(center, paint);
+    } else {
+      final double extent = lerpDouble(0.0, gapExtent + gapPadding * 2.0, gapPercentage)!;
+      switch (textDirection!) {
+        case TextDirection.rtl:
+          final Path path = _gapBorderPath(canvas, center, math.max(0.0, gapStart + gapPadding - extent), extent);
+          canvas.drawPath(path, paint);
+          break;
+
+        case TextDirection.ltr:
+          final Path path = _gapBorderPath(canvas, center, math.max(0.0, gapStart - gapPadding), extent);
+          canvas.drawPath(path, paint);
+          break;
+      }
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is OutlineInputBorder
+        && other.borderSide == borderSide
+        && other.borderRadius == borderRadius
+        && other.gapPadding == gapPadding;
+  }
+
+  @override
+  int get hashCode => hashValues(borderSide, borderRadius, gapPadding);
+}
diff --git a/lib/src/material/input_date_picker_form_field.dart b/lib/src/material/input_date_picker_form_field.dart
new file mode 100644
index 0000000..28013f6
--- /dev/null
+++ b/lib/src/material/input_date_picker_form_field.dart
@@ -0,0 +1,253 @@
+// Copyright 2014 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.
+
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'date.dart';
+import 'input_border.dart';
+import 'input_decorator.dart';
+import 'material_localizations.dart';
+import 'text_form_field.dart';
+import 'theme.dart';
+
+/// A [TextFormField] configured to accept and validate a date entered by a user.
+///
+/// When the field is saved or submitted, the text will be parsed into a
+/// [DateTime] according to the ambient locale's compact date format. If the
+/// input text doesn't parse into a date, the [errorFormatText] message will
+/// be displayed under the field.
+///
+/// [firstDate], [lastDate], and [selectableDayPredicate] provide constraints on
+/// what days are valid. If the input date isn't in the date range or doesn't pass
+/// the given predicate, then the [errorInvalidText] message will be displayed
+/// under the field.
+///
+/// See also:
+///
+///  * [showDatePicker], which shows a dialog that contains a material design
+///    date picker which includes support for text entry of dates.
+///  * [MaterialLocalizations.parseCompactDate], which is used to parse the text
+///    input into a [DateTime].
+///
+class InputDatePickerFormField extends StatefulWidget {
+  /// Creates a [TextFormField] configured to accept and validate a date.
+  ///
+  /// If the optional [initialDate] is provided, then it will be used to populate
+  /// the text field. If the [fieldHintText] is provided, it will be shown.
+  ///
+  /// If [initialDate] is provided, it must not be before [firstDate] or after
+  /// [lastDate]. If [selectableDayPredicate] is provided, it must return `true`
+  /// for [initialDate].
+  ///
+  /// [firstDate] must be on or before [lastDate].
+  ///
+  /// [firstDate], [lastDate], and [autofocus] must be non-null.
+  ///
+  InputDatePickerFormField({
+    Key? key,
+    DateTime? initialDate,
+    required DateTime firstDate,
+    required DateTime lastDate,
+    this.onDateSubmitted,
+    this.onDateSaved,
+    this.selectableDayPredicate,
+    this.errorFormatText,
+    this.errorInvalidText,
+    this.fieldHintText,
+    this.fieldLabelText,
+    this.autofocus = false,
+  }) : assert(firstDate != null),
+       assert(lastDate != null),
+       assert(autofocus != null),
+       initialDate = initialDate != null ? DateUtils.dateOnly(initialDate) : null,
+       firstDate = DateUtils.dateOnly(firstDate),
+       lastDate = DateUtils.dateOnly(lastDate),
+       super(key: key) {
+    assert(
+      !this.lastDate.isBefore(this.firstDate),
+      'lastDate ${this.lastDate} must be on or after firstDate ${this.firstDate}.'
+    );
+    assert(
+      initialDate == null || !this.initialDate!.isBefore(this.firstDate),
+      'initialDate ${this.initialDate} must be on or after firstDate ${this.firstDate}.'
+    );
+    assert(
+      initialDate == null || !this.initialDate!.isAfter(this.lastDate),
+      'initialDate ${this.initialDate} must be on or before lastDate ${this.lastDate}.'
+    );
+    assert(
+      selectableDayPredicate == null || initialDate == null || selectableDayPredicate!(this.initialDate!),
+      'Provided initialDate ${this.initialDate} must satisfy provided selectableDayPredicate.'
+    );
+  }
+
+  /// If provided, it will be used as the default value of the field.
+  final DateTime? initialDate;
+
+  /// The earliest allowable [DateTime] that the user can input.
+  final DateTime firstDate;
+
+  /// The latest allowable [DateTime] that the user can input.
+  final DateTime lastDate;
+
+  /// An optional method to call when the user indicates they are done editing
+  /// the text in the field. Will only be called if the input represents a valid
+  /// [DateTime].
+  final ValueChanged<DateTime>? onDateSubmitted;
+
+  /// An optional method to call with the final date when the form is
+  /// saved via [FormState.save]. Will only be called if the input represents
+  /// a valid [DateTime].
+  final ValueChanged<DateTime>? onDateSaved;
+
+  /// Function to provide full control over which [DateTime] can be selected.
+  final SelectableDayPredicate? selectableDayPredicate;
+
+  /// The error text displayed if the entered date is not in the correct format.
+  final String? errorFormatText;
+
+  /// The error text displayed if the date is not valid.
+  ///
+  /// A date is not valid if it is earlier than [firstDate], later than
+  /// [lastDate], or doesn't pass the [selectableDayPredicate].
+  final String? errorInvalidText;
+
+  /// The hint text displayed in the [TextField].
+  ///
+  /// If this is null, it will default to the date format string. For example,
+  /// 'mm/dd/yyyy' for en_US.
+  final String? fieldHintText;
+
+  /// The label text displayed in the [TextField].
+  ///
+  /// If this is null, it will default to the words representing the date format
+  /// string. For example, 'Month, Day, Year' for en_US.
+  final String? fieldLabelText;
+
+  /// {@macro flutter.widgets.editableText.autofocus}
+  final bool autofocus;
+
+  @override
+  _InputDatePickerFormFieldState createState() => _InputDatePickerFormFieldState();
+}
+
+class _InputDatePickerFormFieldState extends State<InputDatePickerFormField> {
+  final TextEditingController _controller = TextEditingController();
+  DateTime? _selectedDate;
+  String? _inputText;
+  bool _autoSelected = false;
+
+  @override
+  void initState() {
+    super.initState();
+    _selectedDate = widget.initialDate;
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _updateValueForSelectedDate();
+  }
+
+  @override
+  void didUpdateWidget(InputDatePickerFormField oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.initialDate != oldWidget.initialDate) {
+      // Can't update the form field in the middle of a build, so do it next frame
+      WidgetsBinding.instance!.addPostFrameCallback((Duration timeStamp) {
+        setState(() {
+          _selectedDate = widget.initialDate;
+          _updateValueForSelectedDate();
+        });
+      });
+    }
+  }
+
+  void _updateValueForSelectedDate() {
+    if (_selectedDate != null) {
+      final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+      _inputText = localizations.formatCompactDate(_selectedDate!);
+      TextEditingValue textEditingValue = _controller.value.copyWith(text: _inputText);
+      // Select the new text if we are auto focused and haven't selected the text before.
+      if (widget.autofocus && !_autoSelected) {
+        textEditingValue = textEditingValue.copyWith(selection: TextSelection(
+          baseOffset: 0,
+          extentOffset: _inputText!.length,
+        ));
+        _autoSelected = true;
+      }
+      _controller.value = textEditingValue;
+    } else {
+      _inputText = '';
+      _controller.value = _controller.value.copyWith(text: _inputText);
+    }
+  }
+
+  DateTime? _parseDate(String? text) {
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    return localizations.parseCompactDate(text);
+  }
+
+  bool _isValidAcceptableDate(DateTime? date) {
+    return
+      date != null &&
+      !date.isBefore(widget.firstDate) &&
+      !date.isAfter(widget.lastDate) &&
+      (widget.selectableDayPredicate == null || widget.selectableDayPredicate!(date));
+  }
+
+  String? _validateDate(String? text) {
+    final DateTime? date = _parseDate(text);
+    if (date == null) {
+      return widget.errorFormatText ?? MaterialLocalizations.of(context).invalidDateFormatLabel;
+    } else if (!_isValidAcceptableDate(date)) {
+      return widget.errorInvalidText ?? MaterialLocalizations.of(context).dateOutOfRangeLabel;
+    }
+    return null;
+  }
+
+  void _updateDate(String? text, ValueChanged<DateTime>? callback) {
+    final DateTime? date = _parseDate(text);
+    if (_isValidAcceptableDate(date)) {
+      _selectedDate = date;
+      _inputText = text;
+      callback?.call(_selectedDate!);
+    }
+  }
+
+  void _handleSaved(String? text) {
+    _updateDate(text, widget.onDateSaved);
+  }
+
+  void _handleSubmitted(String text) {
+    _updateDate(text, widget.onDateSubmitted);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final InputDecorationTheme inputTheme = Theme.of(context).inputDecorationTheme;
+    return TextFormField(
+      decoration: InputDecoration(
+        border: inputTheme.border ?? const UnderlineInputBorder(),
+        filled: inputTheme.filled,
+        hintText: widget.fieldHintText ?? localizations.dateHelpText,
+        labelText: widget.fieldLabelText ?? localizations.dateInputLabel,
+      ),
+      validator: _validateDate,
+      keyboardType: TextInputType.datetime,
+      onSaved: _handleSaved,
+      onFieldSubmitted: _handleSubmitted,
+      autofocus: widget.autofocus,
+      controller: _controller,
+    );
+  }
+}
diff --git a/lib/src/material/input_decorator.dart b/lib/src/material/input_decorator.dart
new file mode 100644
index 0000000..eac5063
--- /dev/null
+++ b/lib/src/material/input_decorator.dart
@@ -0,0 +1,4101 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'constants.dart';
+import 'input_border.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+const Duration _kTransitionDuration = Duration(milliseconds: 200);
+const Curve _kTransitionCurve = Curves.fastOutSlowIn;
+const double _kFinalLabelScale = 0.75;
+
+// Defines the gap in the InputDecorator's outline border where the
+// floating label will appear.
+class _InputBorderGap extends ChangeNotifier {
+  double? _start;
+  double? get start => _start;
+  set start(double? value) {
+    if (value != _start) {
+      _start = value;
+      notifyListeners();
+    }
+  }
+
+  double _extent = 0.0;
+  double get extent => _extent;
+  set extent(double value) {
+    if (value != _extent) {
+      _extent = value;
+      notifyListeners();
+    }
+  }
+
+  @override
+  // ignore: avoid_equals_and_hash_code_on_mutable_classes, this class is not used in collection
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _InputBorderGap
+        && other.start == start
+        && other.extent == extent;
+  }
+
+  @override
+  // ignore: avoid_equals_and_hash_code_on_mutable_classes, this class is not used in collection
+  int get hashCode => hashValues(start, extent);
+}
+
+// Used to interpolate between two InputBorders.
+class _InputBorderTween extends Tween<InputBorder> {
+  _InputBorderTween({InputBorder? begin, InputBorder? end}) : super(begin: begin, end: end);
+
+  @override
+  InputBorder lerp(double t) => ShapeBorder.lerp(begin, end, t)! as InputBorder;
+}
+
+// Passes the _InputBorderGap parameters along to an InputBorder's paint method.
+class _InputBorderPainter extends CustomPainter {
+  _InputBorderPainter({
+    required Listenable repaint,
+    required this.borderAnimation,
+    required this.border,
+    required this.gapAnimation,
+    required this.gap,
+    required this.textDirection,
+    required this.fillColor,
+    required this.hoverAnimation,
+    required this.hoverColorTween,
+  }) : super(repaint: repaint);
+
+  final Animation<double> borderAnimation;
+  final _InputBorderTween border;
+  final Animation<double> gapAnimation;
+  final _InputBorderGap gap;
+  final TextDirection textDirection;
+  final Color fillColor;
+  final ColorTween hoverColorTween;
+  final Animation<double> hoverAnimation;
+
+  Color get blendedColor => Color.alphaBlend(hoverColorTween.evaluate(hoverAnimation)!, fillColor);
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final InputBorder borderValue = border.evaluate(borderAnimation);
+    final Rect canvasRect = Offset.zero & size;
+    final Color blendedFillColor = blendedColor;
+    if (blendedFillColor.alpha > 0) {
+      canvas.drawPath(
+        borderValue.getOuterPath(canvasRect, textDirection: textDirection),
+        Paint()
+          ..color = blendedFillColor
+          ..style = PaintingStyle.fill,
+      );
+    }
+
+    borderValue.paint(
+      canvas,
+      canvasRect,
+      gapStart: gap.start,
+      gapExtent: gap.extent,
+      gapPercentage: gapAnimation.value,
+      textDirection: textDirection,
+    );
+  }
+
+  @override
+  bool shouldRepaint(_InputBorderPainter oldPainter) {
+    return borderAnimation != oldPainter.borderAnimation
+        || hoverAnimation != oldPainter.hoverAnimation
+        || gapAnimation != oldPainter.gapAnimation
+        || border != oldPainter.border
+        || gap != oldPainter.gap
+        || textDirection != oldPainter.textDirection;
+  }
+}
+
+// An analog of AnimatedContainer, which can animate its shaped border, for
+// _InputBorder. This specialized animated container is needed because the
+// _InputBorderGap, which is computed at layout time, is required by the
+// _InputBorder's paint method.
+class _BorderContainer extends StatefulWidget {
+  const _BorderContainer({
+    Key? key,
+    required this.border,
+    required this.gap,
+    required this.gapAnimation,
+    required this.fillColor,
+    required this.hoverColor,
+    required this.isHovering,
+    this.child,
+  }) : assert(border != null),
+       assert(gap != null),
+       assert(fillColor != null),
+       super(key: key);
+
+  final InputBorder border;
+  final _InputBorderGap gap;
+  final Animation<double> gapAnimation;
+  final Color fillColor;
+  final Color hoverColor;
+  final bool isHovering;
+  final Widget? child;
+
+  @override
+  _BorderContainerState createState() => _BorderContainerState();
+}
+
+class _BorderContainerState extends State<_BorderContainer> with TickerProviderStateMixin {
+  static const Duration _kHoverDuration = Duration(milliseconds: 15);
+
+  late AnimationController _controller;
+  late AnimationController _hoverColorController;
+  late Animation<double> _borderAnimation;
+  late _InputBorderTween _border;
+  late Animation<double> _hoverAnimation;
+  late ColorTween _hoverColorTween;
+
+  @override
+  void initState() {
+    super.initState();
+    _hoverColorController = AnimationController(
+      duration: _kHoverDuration,
+      value: widget.isHovering ? 1.0 : 0.0,
+      vsync: this,
+    );
+    _controller = AnimationController(
+      duration: _kTransitionDuration,
+      vsync: this,
+    );
+    _borderAnimation = CurvedAnimation(
+      parent: _controller,
+      curve: _kTransitionCurve,
+    );
+    _border = _InputBorderTween(
+      begin: widget.border,
+      end: widget.border,
+    );
+    _hoverAnimation = CurvedAnimation(
+      parent: _hoverColorController,
+      curve: Curves.linear,
+    );
+    _hoverColorTween = ColorTween(begin: Colors.transparent, end: widget.hoverColor);
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    _hoverColorController.dispose();
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(_BorderContainer oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.border != oldWidget.border) {
+      _border = _InputBorderTween(
+        begin: oldWidget.border,
+        end: widget.border,
+      );
+      _controller
+        ..value = 0.0
+        ..forward();
+    }
+    if (widget.hoverColor != oldWidget.hoverColor) {
+      _hoverColorTween = ColorTween(begin: Colors.transparent, end: widget.hoverColor);
+    }
+    if (widget.isHovering != oldWidget.isHovering) {
+      if (widget.isHovering) {
+        _hoverColorController.forward();
+      } else {
+        _hoverColorController.reverse();
+      }
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return CustomPaint(
+      foregroundPainter: _InputBorderPainter(
+        repaint: Listenable.merge(<Listenable>[
+          _borderAnimation,
+          widget.gap,
+          _hoverColorController,
+        ]),
+        borderAnimation: _borderAnimation,
+        border: _border,
+        gapAnimation: widget.gapAnimation,
+        gap: widget.gap,
+        textDirection: Directionality.of(context),
+        fillColor: widget.fillColor,
+        hoverColorTween: _hoverColorTween,
+        hoverAnimation: _hoverAnimation,
+      ),
+      child: widget.child,
+    );
+  }
+}
+
+// Used to "shake" the floating label to the left to the left and right
+// when the errorText first appears.
+class _Shaker extends AnimatedWidget {
+  const _Shaker({
+    Key? key,
+    required Animation<double> animation,
+    this.child,
+  }) : super(key: key, listenable: animation);
+
+  final Widget? child;
+
+  Animation<double> get animation => listenable as Animation<double>;
+
+  double get translateX {
+    const double shakeDelta = 4.0;
+    final double t = animation.value;
+    if (t <= 0.25)
+      return -t * shakeDelta;
+    else if (t < 0.75)
+      return (t - 0.5) * shakeDelta;
+    else
+      return (1.0 - t) * 4.0 * shakeDelta;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Transform(
+      transform: Matrix4.translationValues(translateX, 0.0, 0.0),
+      child: child,
+    );
+  }
+}
+
+// Display the helper and error text. When the error text appears
+// it fades and the helper text fades out. The error text also
+// slides upwards a little when it first appears.
+class _HelperError extends StatefulWidget {
+  const _HelperError({
+    Key? key,
+    this.textAlign,
+    this.helperText,
+    this.helperStyle,
+    this.helperMaxLines,
+    this.errorText,
+    this.errorStyle,
+    this.errorMaxLines,
+  }) : super(key: key);
+
+  final TextAlign? textAlign;
+  final String? helperText;
+  final TextStyle? helperStyle;
+  final int? helperMaxLines;
+  final String? errorText;
+  final TextStyle? errorStyle;
+  final int? errorMaxLines;
+
+  @override
+  _HelperErrorState createState() => _HelperErrorState();
+}
+
+class _HelperErrorState extends State<_HelperError> with SingleTickerProviderStateMixin {
+  // If the height of this widget and the counter are zero ("empty") at
+  // layout time, no space is allocated for the subtext.
+  static const Widget empty = SizedBox();
+
+  late AnimationController _controller;
+  Widget? _helper;
+  Widget? _error;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(
+      duration: _kTransitionDuration,
+      vsync: this,
+    );
+    if (widget.errorText != null) {
+      _error = _buildError();
+      _controller.value = 1.0;
+    } else if (widget.helperText != null) {
+      _helper = _buildHelper();
+    }
+    _controller.addListener(_handleChange);
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  void _handleChange() {
+    setState(() {
+      // The _controller's value has changed.
+    });
+  }
+
+  @override
+  void didUpdateWidget(_HelperError old) {
+    super.didUpdateWidget(old);
+
+    final String? newErrorText = widget.errorText;
+    final String? newHelperText = widget.helperText;
+    final String? oldErrorText = old.errorText;
+    final String? oldHelperText = old.helperText;
+
+    final bool errorTextStateChanged = (newErrorText != null) != (oldErrorText != null);
+    final bool helperTextStateChanged = newErrorText == null && (newHelperText != null) != (oldHelperText != null);
+
+    if (errorTextStateChanged || helperTextStateChanged) {
+      if (newErrorText != null) {
+        _error = _buildError();
+        _controller.forward();
+      } else if (newHelperText != null) {
+        _helper = _buildHelper();
+        _controller.reverse();
+      } else {
+        _controller.reverse();
+      }
+    }
+  }
+
+  Widget _buildHelper() {
+    assert(widget.helperText != null);
+    return Semantics(
+      container: true,
+      child: Opacity(
+        opacity: 1.0 - _controller.value,
+        child: Text(
+          widget.helperText!,
+          style: widget.helperStyle,
+          textAlign: widget.textAlign,
+          overflow: TextOverflow.ellipsis,
+          maxLines: widget.helperMaxLines,
+        ),
+      ),
+    );
+  }
+
+  Widget _buildError() {
+    assert(widget.errorText != null);
+    return Semantics(
+      container: true,
+      liveRegion: true,
+      child: Opacity(
+        opacity: _controller.value,
+        child: FractionalTranslation(
+          translation: Tween<Offset>(
+            begin: const Offset(0.0, -0.25),
+            end: const Offset(0.0, 0.0),
+          ).evaluate(_controller.view),
+          child: Text(
+            widget.errorText!,
+            style: widget.errorStyle,
+            textAlign: widget.textAlign,
+            overflow: TextOverflow.ellipsis,
+            maxLines: widget.errorMaxLines,
+          ),
+        ),
+      ),
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (_controller.isDismissed) {
+      _error = null;
+      if (widget.helperText != null) {
+        return _helper = _buildHelper();
+      } else {
+        _helper = null;
+        return empty;
+      }
+    }
+
+    if (_controller.isCompleted) {
+      _helper = null;
+      if (widget.errorText != null) {
+        return _error = _buildError();
+      } else {
+        _error = null;
+        return empty;
+      }
+    }
+
+    if (_helper == null && widget.errorText != null)
+      return _buildError();
+
+    if (_error == null && widget.helperText != null)
+      return _buildHelper();
+
+    if (widget.errorText != null) {
+      return Stack(
+        children: <Widget>[
+          Opacity(
+            opacity: 1.0 - _controller.value,
+            child: _helper,
+          ),
+          _buildError(),
+        ],
+      );
+    }
+
+    if (widget.helperText != null) {
+      return Stack(
+        children: <Widget>[
+          _buildHelper(),
+          Opacity(
+            opacity: _controller.value,
+            child: _error,
+          ),
+        ],
+      );
+    }
+
+    return empty;
+  }
+}
+
+/// Defines the behavior of the floating label.
+enum FloatingLabelBehavior {
+  /// The label will always be positioned within the content, or hidden.
+  never,
+  /// The label will float when the input is focused, or has content.
+  auto,
+  /// The label will always float above the content.
+  always,
+}
+
+// Identifies the children of a _RenderDecorationElement.
+enum _DecorationSlot {
+  icon,
+  input,
+  label,
+  hint,
+  prefix,
+  suffix,
+  prefixIcon,
+  suffixIcon,
+  helperError,
+  counter,
+  container,
+}
+
+// An analog of InputDecoration for the _Decorator widget.
+@immutable
+class _Decoration {
+  const _Decoration({
+    required this.contentPadding,
+    required this.isCollapsed,
+    required this.floatingLabelHeight,
+    required this.floatingLabelProgress,
+    this.border,
+    this.borderGap,
+    required this.alignLabelWithHint,
+    required this.isDense,
+    this.visualDensity,
+    this.icon,
+    this.input,
+    this.label,
+    this.hint,
+    this.prefix,
+    this.suffix,
+    this.prefixIcon,
+    this.suffixIcon,
+    this.helperError,
+    this.counter,
+    this.container,
+    this.fixTextFieldOutlineLabel = false,
+  }) : assert(contentPadding != null),
+       assert(isCollapsed != null),
+       assert(floatingLabelHeight != null),
+       assert(floatingLabelProgress != null),
+       assert(fixTextFieldOutlineLabel != null);
+
+  final EdgeInsetsGeometry contentPadding;
+  final bool isCollapsed;
+  final double floatingLabelHeight;
+  final double floatingLabelProgress;
+  final InputBorder? border;
+  final _InputBorderGap? borderGap;
+  final bool alignLabelWithHint;
+  final bool? isDense;
+  final VisualDensity? visualDensity;
+  final Widget? icon;
+  final Widget? input;
+  final Widget? label;
+  final Widget? hint;
+  final Widget? prefix;
+  final Widget? suffix;
+  final Widget? prefixIcon;
+  final Widget? suffixIcon;
+  final Widget? helperError;
+  final Widget? counter;
+  final Widget? container;
+  final bool fixTextFieldOutlineLabel;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _Decoration
+        && other.contentPadding == contentPadding
+        && other.isCollapsed == isCollapsed
+        && other.floatingLabelHeight == floatingLabelHeight
+        && other.floatingLabelProgress == floatingLabelProgress
+        && other.border == border
+        && other.borderGap == borderGap
+        && other.alignLabelWithHint == alignLabelWithHint
+        && other.isDense == isDense
+        && other.visualDensity == visualDensity
+        && other.icon == icon
+        && other.input == input
+        && other.label == label
+        && other.hint == hint
+        && other.prefix == prefix
+        && other.suffix == suffix
+        && other.prefixIcon == prefixIcon
+        && other.suffixIcon == suffixIcon
+        && other.helperError == helperError
+        && other.counter == counter
+        && other.container == container
+        && other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      contentPadding,
+      floatingLabelHeight,
+      floatingLabelProgress,
+      border,
+      borderGap,
+      alignLabelWithHint,
+      isDense,
+      visualDensity,
+      icon,
+      input,
+      label,
+      hint,
+      prefix,
+      suffix,
+      prefixIcon,
+      suffixIcon,
+      helperError,
+      counter,
+      container,
+      fixTextFieldOutlineLabel,
+    );
+  }
+}
+
+// A container for the layout values computed by _RenderDecoration._layout.
+// These values are used by _RenderDecoration.performLayout to position
+// all of the renderer children of a _RenderDecoration.
+class _RenderDecorationLayout {
+  const _RenderDecorationLayout({
+    required this.boxToBaseline,
+    required this.inputBaseline, // for InputBorderType.underline
+    required this.outlineBaseline, // for InputBorderType.outline
+    required this.subtextBaseline,
+    required this.containerHeight,
+    required this.subtextHeight,
+  });
+
+  final Map<RenderBox?, double> boxToBaseline;
+  final double inputBaseline;
+  final double outlineBaseline;
+  final double subtextBaseline; // helper/error counter
+  final double containerHeight;
+  final double subtextHeight;
+}
+
+// The workhorse: layout and paint a _Decorator widget's _Decoration.
+class _RenderDecoration extends RenderBox {
+  _RenderDecoration({
+    required _Decoration decoration,
+    required TextDirection textDirection,
+    required TextBaseline textBaseline,
+    required bool isFocused,
+    required bool expands,
+    TextAlignVertical? textAlignVertical,
+  }) : assert(decoration != null),
+       assert(textDirection != null),
+       assert(textBaseline != null),
+       assert(expands != null),
+       _decoration = decoration,
+       _textDirection = textDirection,
+       _textBaseline = textBaseline,
+       _textAlignVertical = textAlignVertical,
+       _isFocused = isFocused,
+       _expands = expands;
+
+  static const double subtextGap = 8.0;
+  final Map<_DecorationSlot, RenderBox> children = <_DecorationSlot, RenderBox>{};
+
+  RenderBox? _updateChild(RenderBox? oldChild, RenderBox? newChild, _DecorationSlot slot) {
+    if (oldChild != null) {
+      dropChild(oldChild);
+      children.remove(slot);
+    }
+    if (newChild != null) {
+      children[slot] = newChild;
+      adoptChild(newChild);
+    }
+    return newChild;
+  }
+
+  RenderBox? _icon;
+  RenderBox? get icon => _icon;
+  set icon(RenderBox? value) {
+    _icon = _updateChild(_icon, value, _DecorationSlot.icon);
+  }
+
+  RenderBox? _input;
+  RenderBox? get input => _input;
+  set input(RenderBox? value) {
+    _input = _updateChild(_input, value, _DecorationSlot.input);
+  }
+
+  RenderBox? _label;
+  RenderBox? get label => _label;
+  set label(RenderBox? value) {
+    _label = _updateChild(_label, value, _DecorationSlot.label);
+  }
+
+  RenderBox? _hint;
+  RenderBox? get hint => _hint;
+  set hint(RenderBox? value) {
+    _hint = _updateChild(_hint, value, _DecorationSlot.hint);
+  }
+
+  RenderBox? _prefix;
+  RenderBox? get prefix => _prefix;
+  set prefix(RenderBox? value) {
+    _prefix = _updateChild(_prefix, value, _DecorationSlot.prefix);
+  }
+
+  RenderBox? _suffix;
+  RenderBox? get suffix => _suffix;
+  set suffix(RenderBox? value) {
+    _suffix = _updateChild(_suffix, value, _DecorationSlot.suffix);
+  }
+
+  RenderBox? _prefixIcon;
+  RenderBox? get prefixIcon => _prefixIcon;
+  set prefixIcon(RenderBox? value) {
+    _prefixIcon = _updateChild(_prefixIcon, value, _DecorationSlot.prefixIcon);
+  }
+
+  RenderBox? _suffixIcon;
+  RenderBox? get suffixIcon => _suffixIcon;
+  set suffixIcon(RenderBox? value) {
+    _suffixIcon = _updateChild(_suffixIcon, value, _DecorationSlot.suffixIcon);
+  }
+
+  RenderBox? _helperError;
+  RenderBox? get helperError => _helperError;
+  set helperError(RenderBox? value) {
+    _helperError = _updateChild(_helperError, value, _DecorationSlot.helperError);
+  }
+
+  RenderBox? _counter;
+  RenderBox? get counter => _counter;
+  set counter(RenderBox? value) {
+    _counter = _updateChild(_counter, value, _DecorationSlot.counter);
+  }
+
+  RenderBox? _container;
+  RenderBox? get container => _container;
+  set container(RenderBox? value) {
+    _container = _updateChild(_container, value, _DecorationSlot.container);
+  }
+
+  // The returned list is ordered for hit testing.
+  Iterable<RenderBox> get _children sync* {
+    if (icon != null)
+      yield icon!;
+    if (input != null)
+      yield input!;
+    if (prefixIcon != null)
+      yield prefixIcon!;
+    if (suffixIcon != null)
+      yield suffixIcon!;
+    if (prefix != null)
+      yield prefix!;
+    if (suffix != null)
+      yield suffix!;
+    if (label != null)
+      yield label!;
+    if (hint != null)
+      yield hint!;
+    if (helperError != null)
+      yield helperError!;
+    if (counter != null)
+      yield counter!;
+    if (container != null)
+      yield container!;
+  }
+
+  _Decoration get decoration => _decoration;
+  _Decoration _decoration;
+  set decoration(_Decoration value) {
+    assert(value != null);
+    if (_decoration == value)
+      return;
+    _decoration = value;
+    markNeedsLayout();
+  }
+
+  TextDirection get textDirection => _textDirection;
+  TextDirection _textDirection;
+  set textDirection(TextDirection value) {
+    assert(value != null);
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    markNeedsLayout();
+  }
+
+  TextBaseline get textBaseline => _textBaseline;
+  TextBaseline _textBaseline;
+  set textBaseline(TextBaseline value) {
+    assert(value != null);
+    if (_textBaseline == value)
+      return;
+    _textBaseline = value;
+    markNeedsLayout();
+  }
+
+  TextAlignVertical get _defaultTextAlignVertical => _isOutlineAligned
+      ? TextAlignVertical.center
+      : TextAlignVertical.top;
+  TextAlignVertical? get textAlignVertical => _textAlignVertical ?? _defaultTextAlignVertical;
+  TextAlignVertical? _textAlignVertical;
+  set textAlignVertical(TextAlignVertical? value) {
+    if (_textAlignVertical == value) {
+      return;
+    }
+    // No need to relayout if the effective value is still the same.
+    if (textAlignVertical!.y == (value?.y ?? _defaultTextAlignVertical.y)) {
+      _textAlignVertical = value;
+      return;
+    }
+    _textAlignVertical = value;
+    markNeedsLayout();
+  }
+
+  bool get isFocused => _isFocused;
+  bool _isFocused;
+  set isFocused(bool value) {
+    assert(value != null);
+    if (_isFocused == value)
+      return;
+    _isFocused = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  bool get expands => _expands;
+  bool _expands = false;
+  set expands(bool value) {
+    assert(value != null);
+    if (_expands == value)
+      return;
+    _expands = value;
+    markNeedsLayout();
+  }
+
+  // Indicates that the decoration should be aligned to accommodate an outline
+  // border.
+  bool get _isOutlineAligned {
+    return !decoration.isCollapsed && decoration.border!.isOutline;
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    for (final RenderBox child in _children)
+      child.attach(owner);
+  }
+
+  @override
+  void detach() {
+    super.detach();
+    for (final RenderBox child in _children)
+      child.detach();
+  }
+
+  @override
+  void redepthChildren() {
+    _children.forEach(redepthChild);
+  }
+
+  @override
+  void visitChildren(RenderObjectVisitor visitor) {
+    _children.forEach(visitor);
+  }
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    if (icon != null)
+      visitor(icon!);
+    if (prefix != null)
+      visitor(prefix!);
+    if (prefixIcon != null)
+      visitor(prefixIcon!);
+
+    if (label != null) {
+      visitor(label!);
+    }
+    if (hint != null) {
+      if (isFocused) {
+        visitor(hint!);
+      } else if (label == null) {
+        visitor(hint!);
+      }
+    }
+
+    if (input != null)
+      visitor(input!);
+    if (suffixIcon != null)
+      visitor(suffixIcon!);
+    if (suffix != null)
+      visitor(suffix!);
+    if (container != null)
+      visitor(container!);
+    if (helperError != null)
+      visitor(helperError!);
+    if (counter != null)
+      visitor(counter!);
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    final List<DiagnosticsNode> value = <DiagnosticsNode>[];
+    void add(RenderBox? child, String name) {
+      if (child != null)
+        value.add(child.toDiagnosticsNode(name: name));
+    }
+    add(icon, 'icon');
+    add(input, 'input');
+    add(label, 'label');
+    add(hint, 'hint');
+    add(prefix, 'prefix');
+    add(suffix, 'suffix');
+    add(prefixIcon, 'prefixIcon');
+    add(suffixIcon, 'suffixIcon');
+    add(helperError, 'helperError');
+    add(counter, 'counter');
+    add(container, 'container');
+    return value;
+  }
+
+  @override
+  bool get sizedByParent => false;
+
+  static double _minWidth(RenderBox? box, double height) {
+    return box == null ? 0.0 : box.getMinIntrinsicWidth(height);
+  }
+
+  static double _maxWidth(RenderBox? box, double height) {
+    return box == null ? 0.0 : box.getMaxIntrinsicWidth(height);
+  }
+
+  static double _minHeight(RenderBox? box, double width) {
+    return box == null ? 0.0 : box.getMinIntrinsicHeight(width);
+  }
+
+  static Size _boxSize(RenderBox? box) => box == null ? Size.zero : box.size;
+
+  static BoxParentData _boxParentData(RenderBox box) => box.parentData! as BoxParentData;
+
+  EdgeInsets get contentPadding => decoration.contentPadding as EdgeInsets;
+
+  // Lay out the given box if needed, and return its baseline.
+  double _layoutLineBox(RenderBox? box, BoxConstraints constraints) {
+    if (box == null) {
+      return 0.0;
+    }
+    box.layout(constraints, parentUsesSize: true);
+    // Since internally, all layout is performed against the alphabetic baseline,
+    // (eg, ascents/descents are all relative to alphabetic, even if the font is
+    // an ideographic or hanging font), we should always obtain the reference
+    // baseline from the alphabetic baseline. The ideographic baseline is for
+    // use post-layout and is derived from the alphabetic baseline combined with
+    // the font metrics.
+    final double? baseline = box.getDistanceToBaseline(TextBaseline.alphabetic);
+    assert(baseline != null && baseline >= 0.0);
+    return baseline!;
+  }
+
+  // Returns a value used by performLayout to position all of the renderers.
+  // This method applies layout to all of the renderers except the container.
+  // For convenience, the container is laid out in performLayout().
+  _RenderDecorationLayout _layout(BoxConstraints layoutConstraints) {
+    assert(
+      layoutConstraints.maxWidth < double.infinity,
+      'An InputDecorator, which is typically created by a TextField, cannot '
+      'have an unbounded width.\n'
+      'This happens when the parent widget does not provide a finite width '
+      'constraint. For example, if the InputDecorator is contained by a Row, '
+      'then its width must be constrained. An Expanded widget or a SizedBox '
+      'can be used to constrain the width of the InputDecorator or the '
+      'TextField that contains it.',
+    );
+
+    // Margin on each side of subtext (counter and helperError)
+    final Map<RenderBox?, double> boxToBaseline = <RenderBox?, double>{};
+    final BoxConstraints boxConstraints = layoutConstraints.loosen();
+
+    // Layout all the widgets used by InputDecorator
+    boxToBaseline[prefix] = _layoutLineBox(prefix, boxConstraints);
+    boxToBaseline[suffix] = _layoutLineBox(suffix, boxConstraints);
+    boxToBaseline[icon] = _layoutLineBox(icon, boxConstraints);
+    boxToBaseline[prefixIcon] = _layoutLineBox(prefixIcon, boxConstraints);
+    boxToBaseline[suffixIcon] = _layoutLineBox(suffixIcon, boxConstraints);
+
+    final double inputWidth = math.max(0.0, constraints.maxWidth - (
+      _boxSize(icon).width
+      + contentPadding.left
+      + _boxSize(prefixIcon).width
+      + _boxSize(prefix).width
+      + _boxSize(suffix).width
+      + _boxSize(suffixIcon).width
+      + contentPadding.right));
+    // Increase the available width for the label when it is scaled down.
+    final double invertedLabelScale = lerpDouble(1.00, 1 / _kFinalLabelScale, decoration.floatingLabelProgress)!;
+    double suffixIconWidth = _boxSize(suffixIcon).width;
+    if (decoration.border!.isOutline) {
+      suffixIconWidth = lerpDouble(suffixIconWidth, 0.0, decoration.floatingLabelProgress)!;
+    }
+    final double labelWidth = math.max(0.0, constraints.maxWidth - (
+      _boxSize(icon).width
+      + contentPadding.left
+      + _boxSize(prefixIcon).width
+      + suffixIconWidth
+      + contentPadding.right));
+    boxToBaseline[label] = _layoutLineBox(
+      label,
+      boxConstraints.copyWith(maxWidth: labelWidth * invertedLabelScale),
+    );
+    boxToBaseline[hint] = _layoutLineBox(
+      hint,
+      boxConstraints.copyWith(minWidth: inputWidth, maxWidth: inputWidth),
+    );
+    boxToBaseline[counter] = _layoutLineBox(counter, boxConstraints);
+
+    // The helper or error text can occupy the full width less the space
+    // occupied by the icon and counter.
+    boxToBaseline[helperError] = _layoutLineBox(
+      helperError,
+      boxConstraints.copyWith(
+        maxWidth: math.max(0.0, boxConstraints.maxWidth
+          - _boxSize(icon).width
+          - _boxSize(counter).width
+          - contentPadding.horizontal,
+        ),
+      ),
+    );
+
+    // The height of the input needs to accommodate label above and counter and
+    // helperError below, when they exist.
+    final double labelHeight = label == null
+      ? 0
+      : decoration.floatingLabelHeight;
+    final double topHeight = decoration.border!.isOutline
+      ? math.max(labelHeight - boxToBaseline[label]!, 0)
+      : labelHeight;
+    final double counterHeight = counter == null
+      ? 0
+      : boxToBaseline[counter]! + subtextGap;
+    final bool helperErrorExists = helperError?.size != null
+        && helperError!.size.height > 0;
+    final double helperErrorHeight = !helperErrorExists
+      ? 0
+      : helperError!.size.height + subtextGap;
+    final double bottomHeight = math.max(
+      counterHeight,
+      helperErrorHeight,
+    );
+    final Offset densityOffset = decoration.visualDensity!.baseSizeAdjustment;
+    boxToBaseline[input] = _layoutLineBox(
+      input,
+      boxConstraints.deflate(EdgeInsets.only(
+        top: contentPadding.top + topHeight + densityOffset.dy / 2,
+        bottom: contentPadding.bottom + bottomHeight + densityOffset.dy / 2,
+      )).copyWith(
+        minWidth: inputWidth,
+        maxWidth: inputWidth,
+      ),
+    );
+
+    // The field can be occupied by a hint or by the input itself
+    final double hintHeight = hint == null ? 0 : hint!.size.height;
+    final double inputDirectHeight = input == null ? 0 : input!.size.height;
+    final double inputHeight = math.max(hintHeight, inputDirectHeight);
+    final double inputInternalBaseline = math.max(
+      boxToBaseline[input]!,
+      boxToBaseline[hint]!,
+    );
+
+    // Calculate the amount that prefix/suffix affects height above and below
+    // the input.
+    final double prefixHeight = prefix?.size.height ?? 0;
+    final double suffixHeight = suffix?.size.height ?? 0;
+    final double fixHeight = math.max(
+      boxToBaseline[prefix]!,
+      boxToBaseline[suffix]!,
+    );
+    final double fixAboveInput = math.max(0, fixHeight - inputInternalBaseline);
+    final double fixBelowBaseline = math.max(
+      prefixHeight - boxToBaseline[prefix]!,
+      suffixHeight - boxToBaseline[suffix]!,
+    );
+    // TODO(justinmc): fixBelowInput should have no effect when there is no
+    // prefix/suffix below the input.
+    // https://github.com/flutter/flutter/issues/66050
+    final double fixBelowInput = math.max(
+      0,
+      fixBelowBaseline - (inputHeight - inputInternalBaseline),
+    );
+
+    // Calculate the height of the input text container.
+    final double prefixIconHeight = prefixIcon == null ? 0 : prefixIcon!.size.height;
+    final double suffixIconHeight = suffixIcon == null ? 0 : suffixIcon!.size.height;
+    final double fixIconHeight = math.max(prefixIconHeight, suffixIconHeight);
+    final double contentHeight = math.max(
+      fixIconHeight,
+      topHeight
+      + contentPadding.top
+      + fixAboveInput
+      + inputHeight
+      + fixBelowInput
+      + contentPadding.bottom
+      + densityOffset.dy,
+    );
+    final double minContainerHeight = decoration.isDense! || decoration.isCollapsed || expands
+      ? 0.0
+      : kMinInteractiveDimension;
+    final double maxContainerHeight = boxConstraints.maxHeight - bottomHeight;
+    final double containerHeight = expands
+      ? maxContainerHeight
+      : math.min(math.max(contentHeight, minContainerHeight), maxContainerHeight);
+
+    // Ensure the text is vertically centered in cases where the content is
+    // shorter than kMinInteractiveDimension.
+    final double interactiveAdjustment = minContainerHeight > contentHeight
+      ? (minContainerHeight - contentHeight) / 2.0
+      : 0.0;
+
+    // Try to consider the prefix/suffix as part of the text when aligning it.
+    // If the prefix/suffix overflows however, allow it to extend outside of the
+    // input and align the remaining part of the text and prefix/suffix.
+    final double overflow = math.max(0, contentHeight - maxContainerHeight);
+    // Map textAlignVertical from -1:1 to 0:1 so that it can be used to scale
+    // the baseline from its minimum to maximum values.
+    final double textAlignVerticalFactor = (textAlignVertical!.y + 1.0) / 2.0;
+    // Adjust to try to fit top overflow inside the input on an inverse scale of
+    // textAlignVertical, so that top aligned text adjusts the most and bottom
+    // aligned text doesn't adjust at all.
+    final double baselineAdjustment = fixAboveInput - overflow * (1 - textAlignVerticalFactor);
+
+    // The baselines that will be used to draw the actual input text content.
+    final double topInputBaseline = contentPadding.top
+      + topHeight
+      + inputInternalBaseline
+      + baselineAdjustment
+      + interactiveAdjustment;
+    final double maxContentHeight = containerHeight
+      - contentPadding.top
+      - topHeight
+      - contentPadding.bottom;
+    final double alignableHeight = fixAboveInput + inputHeight + fixBelowInput;
+    final double maxVerticalOffset = maxContentHeight - alignableHeight;
+    final double textAlignVerticalOffset = maxVerticalOffset * textAlignVerticalFactor;
+    final double inputBaseline = topInputBaseline + textAlignVerticalOffset + densityOffset.dy / 2.0;
+
+    // The three main alignments for the baseline when an outline is present are
+    //
+    //  * top (-1.0): topmost point considering padding.
+    //  * center (0.0): the absolute center of the input ignoring padding but
+    //      accommodating the border and floating label.
+    //  * bottom (1.0): bottommost point considering padding.
+    //
+    // That means that if the padding is uneven, center is not the exact
+    // midpoint of top and bottom. To account for this, the above center and
+    // below center alignments are interpolated independently.
+    final double outlineCenterBaseline = inputInternalBaseline
+      + baselineAdjustment / 2.0
+      + (containerHeight - (2.0 + inputHeight)) / 2.0;
+    final double outlineTopBaseline = topInputBaseline;
+    final double outlineBottomBaseline = topInputBaseline + maxVerticalOffset;
+    final double outlineBaseline = _interpolateThree(
+      outlineTopBaseline,
+      outlineCenterBaseline,
+      outlineBottomBaseline,
+      textAlignVertical!,
+    );
+
+    // Find the positions of the text below the input when it exists.
+    double subtextCounterBaseline = 0;
+    double subtextHelperBaseline = 0;
+    double subtextCounterHeight = 0;
+    double subtextHelperHeight = 0;
+    if (counter != null) {
+      subtextCounterBaseline =
+        containerHeight + subtextGap + boxToBaseline[counter]!;
+      subtextCounterHeight = counter!.size.height + subtextGap;
+    }
+    if (helperErrorExists) {
+      subtextHelperBaseline =
+        containerHeight + subtextGap + boxToBaseline[helperError]!;
+      subtextHelperHeight = helperErrorHeight;
+    }
+    final double subtextBaseline = math.max(
+      subtextCounterBaseline,
+      subtextHelperBaseline,
+    );
+    final double subtextHeight = math.max(
+      subtextCounterHeight,
+      subtextHelperHeight,
+    );
+
+    return _RenderDecorationLayout(
+      boxToBaseline: boxToBaseline,
+      containerHeight: containerHeight,
+      inputBaseline: inputBaseline,
+      outlineBaseline: outlineBaseline,
+      subtextBaseline: subtextBaseline,
+      subtextHeight: subtextHeight,
+    );
+  }
+
+  // Interpolate between three stops using textAlignVertical. This is used to
+  // calculate the outline baseline, which ignores padding when the alignment is
+  // middle. When the alignment is less than zero, it interpolates between the
+  // centered text box's top and the top of the content padding. When the
+  // alignment is greater than zero, it interpolates between the centered box's
+  // top and the position that would align the bottom of the box with the bottom
+  // padding.
+  double _interpolateThree(double begin, double middle, double end, TextAlignVertical textAlignVertical) {
+    if (textAlignVertical.y <= 0) {
+      // It's possible for begin, middle, and end to not be in order because of
+      // excessive padding. Those cases are handled by using middle.
+      if (begin >= middle) {
+        return middle;
+      }
+      // Do a standard linear interpolation on the first half, between begin and
+      // middle.
+      final double t = textAlignVertical.y + 1;
+      return begin + (middle - begin) * t;
+    }
+
+    if (middle >= end) {
+      return middle;
+    }
+    // Do a standard linear interpolation on the second half, between middle and
+    // end.
+    final double t = textAlignVertical.y;
+    return middle + (end - middle) * t;
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    return _minWidth(icon, height)
+      + contentPadding.left
+      + _minWidth(prefixIcon, height)
+      + _minWidth(prefix, height)
+      + math.max(_minWidth(input, height), _minWidth(hint, height))
+      + _minWidth(suffix, height)
+      + _minWidth(suffixIcon, height)
+      + contentPadding.right;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    return _maxWidth(icon, height)
+      + contentPadding.left
+      + _maxWidth(prefixIcon, height)
+      + _maxWidth(prefix, height)
+      + math.max(_maxWidth(input, height), _maxWidth(hint, height))
+      + _maxWidth(suffix, height)
+      + _maxWidth(suffixIcon, height)
+      + contentPadding.right;
+  }
+
+  double _lineHeight(double width, List<RenderBox?> boxes) {
+    double height = 0.0;
+    for (final RenderBox? box in boxes) {
+      if (box == null)
+        continue;
+      height = math.max(_minHeight(box, width), height);
+    }
+    return height;
+    // TODO(hansmuller): this should compute the overall line height for the
+    // boxes when they've been baseline-aligned.
+    // See https://github.com/flutter/flutter/issues/13715
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    double subtextHeight = _lineHeight(width, <RenderBox?>[helperError, counter]);
+    if (subtextHeight > 0.0)
+      subtextHeight += subtextGap;
+    final Offset densityOffset = decoration.visualDensity!.baseSizeAdjustment;
+    final double containerHeight = contentPadding.top
+      + (label == null ? 0.0 : decoration.floatingLabelHeight)
+      + _lineHeight(width, <RenderBox?>[prefix, input, suffix])
+      + subtextHeight
+      + contentPadding.bottom
+      + densityOffset.dy;
+    final double minContainerHeight = decoration.isDense! || expands
+      ? 0.0
+      : kMinInteractiveDimension;
+    return math.max(containerHeight, minContainerHeight);
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    return computeMinIntrinsicHeight(width);
+  }
+
+  @override
+  double computeDistanceToActualBaseline(TextBaseline baseline) {
+    return _boxParentData(input!).offset.dy + input!.computeDistanceToActualBaseline(baseline)!;
+  }
+
+  // Records where the label was painted.
+  Matrix4? _labelTransform;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    assert(debugCannotComputeDryLayout(
+      reason: 'Layout requires baseline metrics, which are only available after a full layout.',
+    ));
+    return const Size(0, 0);
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    _labelTransform = null;
+    final _RenderDecorationLayout layout = _layout(constraints);
+
+    final double overallWidth = constraints.maxWidth;
+    final double overallHeight = layout.containerHeight + layout.subtextHeight;
+
+    if (container != null) {
+      final BoxConstraints containerConstraints = BoxConstraints.tightFor(
+        height: layout.containerHeight,
+        width: overallWidth - _boxSize(icon).width,
+      );
+      container!.layout(containerConstraints, parentUsesSize: true);
+      final double x;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          x = 0.0;
+          break;
+        case TextDirection.ltr:
+          x = _boxSize(icon).width;
+          break;
+       }
+      _boxParentData(container!).offset = Offset(x, 0.0);
+    }
+
+    double? height;
+    double centerLayout(RenderBox box, double x) {
+      _boxParentData(box).offset = Offset(x, (height! - box.size.height) / 2.0);
+      return box.size.width;
+    }
+
+    double? baseline;
+    double baselineLayout(RenderBox box, double x) {
+      _boxParentData(box).offset = Offset(x, baseline! - layout.boxToBaseline[box]!);
+      return box.size.width;
+    }
+
+    final double left = contentPadding.left;
+    final double right = overallWidth - contentPadding.right;
+
+    height = layout.containerHeight;
+    baseline = _isOutlineAligned ? layout.outlineBaseline : layout.inputBaseline;
+
+    if (icon != null) {
+      final double x;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          x = overallWidth - icon!.size.width;
+          break;
+        case TextDirection.ltr:
+          x = 0.0;
+          break;
+       }
+      centerLayout(icon!, x);
+    }
+
+    switch (textDirection) {
+      case TextDirection.rtl: {
+        double start = right - _boxSize(icon).width;
+        double end = left;
+        if (prefixIcon != null) {
+          start += contentPadding.left;
+          start -= centerLayout(prefixIcon!, start - prefixIcon!.size.width);
+        }
+        if (label != null) {
+          if (decoration.alignLabelWithHint) {
+            baselineLayout(label!, start - label!.size.width);
+          } else {
+            centerLayout(label!, start - label!.size.width);
+          }
+        }
+        if (prefix != null)
+          start -= baselineLayout(prefix!, start - prefix!.size.width);
+        if (input != null)
+          baselineLayout(input!, start - input!.size.width);
+        if (hint != null)
+          baselineLayout(hint!, start - hint!.size.width);
+        if (suffixIcon != null) {
+          end -= contentPadding.left;
+          end += centerLayout(suffixIcon!, end);
+        }
+        if (suffix != null)
+          end += baselineLayout(suffix!, end);
+        break;
+      }
+      case TextDirection.ltr: {
+        double start = left + _boxSize(icon).width;
+        double end = right;
+        if (prefixIcon != null) {
+          start -= contentPadding.left;
+          start += centerLayout(prefixIcon!, start);
+        }
+        if (label != null) {
+          if (decoration.alignLabelWithHint) {
+            baselineLayout(label!, start);
+          } else {
+            centerLayout(label!, start);
+          }
+        }
+        if (prefix != null)
+          start += baselineLayout(prefix!, start);
+        if (input != null)
+          baselineLayout(input!, start);
+        if (hint != null)
+          baselineLayout(hint!, start);
+        if (suffixIcon != null) {
+          end += contentPadding.right;
+          end -= centerLayout(suffixIcon!, end - suffixIcon!.size.width);
+        }
+        if (suffix != null)
+          end -= baselineLayout(suffix!, end - suffix!.size.width);
+        break;
+      }
+    }
+
+    if (helperError != null || counter != null) {
+      height = layout.subtextHeight;
+      baseline = layout.subtextBaseline;
+
+      switch (textDirection) {
+        case TextDirection.rtl:
+          if (helperError != null)
+            baselineLayout(helperError!, right - helperError!.size.width - _boxSize(icon).width);
+          if (counter != null)
+            baselineLayout(counter!, left);
+          break;
+        case TextDirection.ltr:
+          if (helperError != null)
+            baselineLayout(helperError!, left + _boxSize(icon).width);
+          if (counter != null)
+            baselineLayout(counter!, right - counter!.size.width);
+          break;
+      }
+    }
+
+    if (label != null) {
+      final double labelX = _boxParentData(label!).offset.dx;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          decoration.borderGap!.start = labelX + label!.size.width;
+          break;
+        case TextDirection.ltr:
+          // The value of _InputBorderGap.start is relative to the origin of the
+          // _BorderContainer which is inset by the icon's width.
+          decoration.borderGap!.start = labelX - _boxSize(icon).width;
+          break;
+      }
+      decoration.borderGap!.extent = label!.size.width * 0.75;
+    } else {
+      decoration.borderGap!.start = null;
+      decoration.borderGap!.extent = 0.0;
+    }
+
+    size = constraints.constrain(Size(overallWidth, overallHeight));
+    assert(size.width == constraints.constrainWidth(overallWidth));
+    assert(size.height == constraints.constrainHeight(overallHeight));
+  }
+
+  void _paintLabel(PaintingContext context, Offset offset) {
+    context.paintChild(label!, offset);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    void doPaint(RenderBox? child) {
+      if (child != null)
+        context.paintChild(child, _boxParentData(child).offset + offset);
+    }
+    doPaint(container);
+
+    if (label != null) {
+      final Offset labelOffset = _boxParentData(label!).offset;
+      final double labelHeight = label!.size.height;
+      final double borderWeight = decoration.border!.borderSide.width;
+      final double t = decoration.floatingLabelProgress;
+      // The center of the outline border label ends up a little below the
+      // center of the top border line.
+      final bool isOutlineBorder = decoration.border != null && decoration.border!.isOutline;
+      // Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
+      // Center the scaled label relative to the border.
+      final double floatingY = decoration.fixTextFieldOutlineLabel
+        ? isOutlineBorder ? (-labelHeight * _kFinalLabelScale) / 2.0 + borderWeight / 2.0 : contentPadding.top
+        : isOutlineBorder ? -labelHeight * 0.25 : contentPadding.top;
+      final double scale = lerpDouble(1.0, _kFinalLabelScale, t)!;
+      final double dx;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          dx = labelOffset.dx + label!.size.width * (1.0 - scale); // origin is on the right
+          break;
+        case TextDirection.ltr:
+          dx = labelOffset.dx; // origin on the left
+          break;
+      }
+      final double dy = lerpDouble(0.0, floatingY - labelOffset.dy, t)!;
+      _labelTransform = Matrix4.identity()
+        ..translate(dx, labelOffset.dy + dy)
+        ..scale(scale);
+      _transformLayer = context.pushTransform(needsCompositing, offset, _labelTransform!, _paintLabel,
+          oldLayer: _transformLayer);
+    } else {
+      _transformLayer = null;
+    }
+
+    doPaint(icon);
+    doPaint(prefix);
+    doPaint(suffix);
+    doPaint(prefixIcon);
+    doPaint(suffixIcon);
+    doPaint(hint);
+    doPaint(input);
+    doPaint(helperError);
+    doPaint(counter);
+  }
+
+  TransformLayer? _transformLayer;
+
+  @override
+  bool hitTestSelf(Offset position) => true;
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    assert(position != null);
+    for (final RenderBox child in _children) {
+      // The label must be handled specially since we've transformed it.
+      final Offset offset = _boxParentData(child).offset;
+      final bool isHit = result.addWithPaintOffset(
+        offset: offset,
+        position: position,
+        hitTest: (BoxHitTestResult result, Offset transformed) {
+          assert(transformed == position - offset);
+          return child.hitTest(result, position: transformed);
+        },
+      );
+      if (isHit)
+        return true;
+    }
+    return false;
+  }
+
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) {
+    if (child == label && _labelTransform != null) {
+      final Offset labelOffset = _boxParentData(label!).offset;
+      transform
+        ..multiply(_labelTransform!)
+        ..translate(-labelOffset.dx, -labelOffset.dy);
+    }
+    super.applyPaintTransform(child, transform);
+  }
+}
+
+class _DecorationElement extends RenderObjectElement {
+  _DecorationElement(_Decorator widget) : super(widget);
+
+  final Map<_DecorationSlot, Element> slotToChild = <_DecorationSlot, Element>{};
+
+  @override
+  _Decorator get widget => super.widget as _Decorator;
+
+  @override
+  _RenderDecoration get renderObject => super.renderObject as _RenderDecoration;
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    slotToChild.values.forEach(visitor);
+  }
+
+  @override
+  void forgetChild(Element child) {
+    assert(slotToChild.containsValue(child));
+    assert(child.slot is _DecorationSlot);
+    assert(slotToChild.containsKey(child.slot));
+    slotToChild.remove(child.slot);
+    super.forgetChild(child);
+  }
+
+  void _mountChild(Widget? widget, _DecorationSlot slot) {
+    final Element? oldChild = slotToChild[slot];
+    final Element? newChild = updateChild(oldChild, widget, slot);
+    if (oldChild != null) {
+      slotToChild.remove(slot);
+    }
+    if (newChild != null) {
+      slotToChild[slot] = newChild;
+    }
+  }
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    _mountChild(widget.decoration.icon, _DecorationSlot.icon);
+    _mountChild(widget.decoration.input, _DecorationSlot.input);
+    _mountChild(widget.decoration.label, _DecorationSlot.label);
+    _mountChild(widget.decoration.hint, _DecorationSlot.hint);
+    _mountChild(widget.decoration.prefix, _DecorationSlot.prefix);
+    _mountChild(widget.decoration.suffix, _DecorationSlot.suffix);
+    _mountChild(widget.decoration.prefixIcon, _DecorationSlot.prefixIcon);
+    _mountChild(widget.decoration.suffixIcon, _DecorationSlot.suffixIcon);
+    _mountChild(widget.decoration.helperError, _DecorationSlot.helperError);
+    _mountChild(widget.decoration.counter, _DecorationSlot.counter);
+    _mountChild(widget.decoration.container, _DecorationSlot.container);
+  }
+
+  void _updateChild(Widget? widget, _DecorationSlot slot) {
+    final Element? oldChild = slotToChild[slot];
+    final Element? newChild = updateChild(oldChild, widget, slot);
+    if (oldChild != null) {
+      slotToChild.remove(slot);
+    }
+    if (newChild != null) {
+      slotToChild[slot] = newChild;
+    }
+  }
+
+  @override
+  void update(_Decorator newWidget) {
+    super.update(newWidget);
+    assert(widget == newWidget);
+    _updateChild(widget.decoration.icon, _DecorationSlot.icon);
+    _updateChild(widget.decoration.input, _DecorationSlot.input);
+    _updateChild(widget.decoration.label, _DecorationSlot.label);
+    _updateChild(widget.decoration.hint, _DecorationSlot.hint);
+    _updateChild(widget.decoration.prefix, _DecorationSlot.prefix);
+    _updateChild(widget.decoration.suffix, _DecorationSlot.suffix);
+    _updateChild(widget.decoration.prefixIcon, _DecorationSlot.prefixIcon);
+    _updateChild(widget.decoration.suffixIcon, _DecorationSlot.suffixIcon);
+    _updateChild(widget.decoration.helperError, _DecorationSlot.helperError);
+    _updateChild(widget.decoration.counter, _DecorationSlot.counter);
+    _updateChild(widget.decoration.container, _DecorationSlot.container);
+  }
+
+  void _updateRenderObject(RenderBox? child, _DecorationSlot slot) {
+    switch (slot) {
+      case _DecorationSlot.icon:
+        renderObject.icon = child;
+        break;
+      case _DecorationSlot.input:
+        renderObject.input = child;
+        break;
+      case _DecorationSlot.label:
+        renderObject.label = child;
+        break;
+      case _DecorationSlot.hint:
+        renderObject.hint = child;
+        break;
+      case _DecorationSlot.prefix:
+        renderObject.prefix = child;
+        break;
+      case _DecorationSlot.suffix:
+        renderObject.suffix = child;
+        break;
+      case _DecorationSlot.prefixIcon:
+        renderObject.prefixIcon = child;
+        break;
+      case _DecorationSlot.suffixIcon:
+        renderObject.suffixIcon = child;
+        break;
+      case _DecorationSlot.helperError:
+        renderObject.helperError = child;
+        break;
+      case _DecorationSlot.counter:
+        renderObject.counter = child;
+        break;
+      case _DecorationSlot.container:
+        renderObject.container = child;
+        break;
+    }
+  }
+
+  @override
+  void insertRenderObjectChild(RenderObject child, _DecorationSlot slot) {
+    assert(child is RenderBox);
+    _updateRenderObject(child as RenderBox, slot);
+    assert(renderObject.children.keys.contains(slot));
+  }
+
+  @override
+  void removeRenderObjectChild(RenderObject child, _DecorationSlot slot) {
+    assert(child is RenderBox);
+    assert(renderObject.children[slot] == child);
+    _updateRenderObject(null, slot);
+    assert(!renderObject.children.keys.contains(slot));
+  }
+
+  @override
+  void moveRenderObjectChild(RenderObject child, dynamic oldSlot, dynamic newSlot) {
+    assert(false, 'not reachable');
+  }
+}
+
+class _Decorator extends RenderObjectWidget {
+  const _Decorator({
+    Key? key,
+    required this.textAlignVertical,
+    required this.decoration,
+    required this.textDirection,
+    required this.textBaseline,
+    required this.isFocused,
+    required this.expands,
+  }) : assert(decoration != null),
+       assert(textDirection != null),
+       assert(textBaseline != null),
+       assert(expands != null),
+       super(key: key);
+
+  final _Decoration decoration;
+  final TextDirection textDirection;
+  final TextBaseline textBaseline;
+  final TextAlignVertical? textAlignVertical;
+  final bool isFocused;
+  final bool expands;
+
+  @override
+  _DecorationElement createElement() => _DecorationElement(this);
+
+  @override
+  _RenderDecoration createRenderObject(BuildContext context) {
+    return _RenderDecoration(
+      decoration: decoration,
+      textDirection: textDirection,
+      textBaseline: textBaseline,
+      textAlignVertical: textAlignVertical,
+      isFocused: isFocused,
+      expands: expands,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderDecoration renderObject) {
+    renderObject
+     ..decoration = decoration
+     ..expands = expands
+     ..isFocused = isFocused
+     ..textAlignVertical = textAlignVertical
+     ..textBaseline = textBaseline
+     ..textDirection = textDirection;
+  }
+}
+
+class _AffixText extends StatelessWidget {
+  const _AffixText({
+    required this.labelIsFloating,
+    this.text,
+    this.style,
+    this.child,
+  });
+
+  final bool labelIsFloating;
+  final String? text;
+  final TextStyle? style;
+  final Widget? child;
+
+  @override
+  Widget build(BuildContext context) {
+    return DefaultTextStyle.merge(
+      style: style,
+      child: AnimatedOpacity(
+        duration: _kTransitionDuration,
+        curve: _kTransitionCurve,
+        opacity: labelIsFloating ? 1.0 : 0.0,
+        child: child ?? (text == null ? null : Text(text!, style: style,)),
+      ),
+    );
+  }
+}
+
+/// Defines the appearance of a Material Design text field.
+///
+/// [InputDecorator] displays the visual elements of a Material Design text
+/// field around its input [child]. The visual elements themselves are defined
+/// by an [InputDecoration] object and their layout and appearance depend
+/// on the `baseStyle`, `textAlign`, `isFocused`, and `isEmpty` parameters.
+///
+/// [TextField] uses this widget to decorate its [EditableText] child.
+///
+/// [InputDecorator] can be used to create widgets that look and behave like a
+/// [TextField] but support other kinds of input.
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// See also:
+///
+///  * [TextField], which uses an [InputDecorator] to display a border,
+///    labels, and icons, around its [EditableText] child.
+///  * [Decoration] and [DecoratedBox], for drawing arbitrary decorations
+///    around other widgets.
+class InputDecorator extends StatefulWidget {
+  /// Creates a widget that displays a border, labels, and icons,
+  /// for a [TextField].
+  ///
+  /// The [isFocused], [isHovering], [expands], and [isEmpty] arguments must not
+  /// be null.
+  const InputDecorator({
+    Key? key,
+    required this.decoration,
+    this.baseStyle,
+    this.textAlign,
+    this.textAlignVertical,
+    this.isFocused = false,
+    this.isHovering = false,
+    this.expands = false,
+    this.isEmpty = false,
+    this.child,
+  }) : assert(decoration != null),
+       assert(isFocused != null),
+       assert(isHovering != null),
+       assert(expands != null),
+       assert(isEmpty != null),
+       super(key: key);
+
+  /// The text and styles to use when decorating the child.
+  ///
+  /// Null [InputDecoration] properties are initialized with the corresponding
+  /// values from [ThemeData.inputDecorationTheme].
+  ///
+  /// Must not be null.
+  final InputDecoration decoration;
+
+  /// The style on which to base the label, hint, counter, and error styles
+  /// if the [decoration] does not provide explicit styles.
+  ///
+  /// If null, `baseStyle` defaults to the `subtitle1` style from the
+  /// current [Theme], see [ThemeData.textTheme].
+  ///
+  /// The [TextStyle.textBaseline] of the [baseStyle] is used to determine
+  /// the baseline used for text alignment.
+  final TextStyle? baseStyle;
+
+  /// How the text in the decoration should be aligned horizontally.
+  final TextAlign? textAlign;
+
+  /// {@template flutter.material.InputDecorator.textAlignVertical}
+  /// How the text should be aligned vertically.
+  ///
+  /// Determines the alignment of the baseline within the available space of
+  /// the input (typically a TextField). For example, TextAlignVertical.top will
+  /// place the baseline such that the text, and any attached decoration like
+  /// prefix and suffix, is as close to the top of the input as possible without
+  /// overflowing. The heights of the prefix and suffix are similarly included
+  /// for other alignment values. If the height is greater than the height
+  /// available, then the prefix and suffix will be allowed to overflow first
+  /// before the text scrolls.
+  /// {@endtemplate}
+  final TextAlignVertical? textAlignVertical;
+
+  /// Whether the input field has focus.
+  ///
+  /// Determines the position of the label text and the color and weight of the
+  /// border.
+  ///
+  /// Defaults to false.
+  ///
+  /// See also:
+  ///
+  ///  * [InputDecoration.hoverColor], which is also blended into the focus
+  ///    color and fill color when the [isHovering] is true to produce the final
+  ///    color.
+  final bool isFocused;
+
+  /// Whether the input field is being hovered over by a mouse pointer.
+  ///
+  /// Determines the container fill color, which is a blend of
+  /// [InputDecoration.hoverColor] with [InputDecoration.fillColor] when
+  /// true, and [InputDecoration.fillColor] when not.
+  ///
+  /// Defaults to false.
+  final bool isHovering;
+
+  /// If true, the height of the input field will be as large as possible.
+  ///
+  /// If wrapped in a widget that constrains its child's height, like Expanded
+  /// or SizedBox, the input field will only be affected if [expands] is set to
+  /// true.
+  ///
+  /// See [TextField.minLines] and [TextField.maxLines] for related ways to
+  /// affect the height of an input. When [expands] is true, both must be null
+  /// in order to avoid ambiguity in determining the height.
+  ///
+  /// Defaults to false.
+  final bool expands;
+
+  /// Whether the input field is empty.
+  ///
+  /// Determines the position of the label text and whether to display the hint
+  /// text.
+  ///
+  /// Defaults to false.
+  final bool isEmpty;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// Typically an [EditableText], [DropdownButton], or [InkWell].
+  final Widget? child;
+
+  /// Whether the label needs to get out of the way of the input, either by
+  /// floating or disappearing.
+  ///
+  /// Will withdraw when not empty, or when focused while enabled.
+  bool get _labelShouldWithdraw => !isEmpty || (isFocused && decoration.enabled);
+
+  @override
+  _InputDecoratorState createState() => _InputDecoratorState();
+
+  /// The RenderBox that defines this decorator's "container". That's the
+  /// area which is filled if [InputDecoration.filled] is true. It's the area
+  /// adjacent to [InputDecoration.icon] and above the widgets that contain
+  /// [InputDecoration.helperText], [InputDecoration.errorText], and
+  /// [InputDecoration.counterText].
+  ///
+  /// [TextField] renders ink splashes within the container.
+  static RenderBox? containerOf(BuildContext context) {
+    final _RenderDecoration? result = context.findAncestorRenderObjectOfType<_RenderDecoration>();
+    return result?.container;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<InputDecoration>('decoration', decoration));
+    properties.add(DiagnosticsProperty<TextStyle>('baseStyle', baseStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('isFocused', isFocused));
+    properties.add(DiagnosticsProperty<bool>('expands', expands, defaultValue: false));
+    properties.add(DiagnosticsProperty<bool>('isEmpty', isEmpty));
+  }
+}
+
+class _InputDecoratorState extends State<InputDecorator> with TickerProviderStateMixin {
+  late AnimationController _floatingLabelController;
+  late AnimationController _shakingLabelController;
+  final _InputBorderGap _borderGap = _InputBorderGap();
+
+  @override
+  void initState() {
+    super.initState();
+
+    final bool labelIsInitiallyFloating = widget.decoration.floatingLabelBehavior == FloatingLabelBehavior.always
+        || (widget.decoration.floatingLabelBehavior != FloatingLabelBehavior.never &&
+            widget.decoration.hasFloatingPlaceholder &&
+            widget._labelShouldWithdraw);
+
+    _floatingLabelController = AnimationController(
+      duration: _kTransitionDuration,
+      vsync: this,
+      value: labelIsInitiallyFloating ? 1.0 : 0.0
+    );
+    _floatingLabelController.addListener(_handleChange);
+
+    _shakingLabelController = AnimationController(
+      duration: _kTransitionDuration,
+      vsync: this,
+    );
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _effectiveDecoration = null;
+  }
+
+  @override
+  void dispose() {
+    _floatingLabelController.dispose();
+    _shakingLabelController.dispose();
+    super.dispose();
+  }
+
+  void _handleChange() {
+    setState(() {
+      // The _floatingLabelController's value has changed.
+    });
+  }
+
+  InputDecoration? _effectiveDecoration;
+  InputDecoration? get decoration {
+    _effectiveDecoration ??= widget.decoration.applyDefaults(
+      Theme.of(context).inputDecorationTheme,
+    );
+    return _effectiveDecoration;
+  }
+
+  TextAlign? get textAlign => widget.textAlign;
+  bool get isFocused => widget.isFocused;
+  bool get isHovering => widget.isHovering && decoration!.enabled;
+  bool get isEmpty => widget.isEmpty;
+  bool get _floatingLabelEnabled {
+    return decoration!.hasFloatingPlaceholder && decoration!.floatingLabelBehavior != FloatingLabelBehavior.never;
+  }
+
+  @override
+  void didUpdateWidget(InputDecorator old) {
+    super.didUpdateWidget(old);
+    if (widget.decoration != old.decoration)
+      _effectiveDecoration = null;
+
+    final bool floatBehaviorChanged = widget.decoration.floatingLabelBehavior != old.decoration.floatingLabelBehavior
+        || widget.decoration.hasFloatingPlaceholder != old.decoration.hasFloatingPlaceholder;
+
+    if (widget._labelShouldWithdraw != old._labelShouldWithdraw || floatBehaviorChanged) {
+      if (_floatingLabelEnabled
+          && (widget._labelShouldWithdraw || widget.decoration.floatingLabelBehavior == FloatingLabelBehavior.always))
+        _floatingLabelController.forward();
+      else
+        _floatingLabelController.reverse();
+    }
+
+    final String? errorText = decoration!.errorText;
+    final String? oldErrorText = old.decoration.errorText;
+
+    if (_floatingLabelController.isCompleted && errorText != null && errorText != oldErrorText) {
+      _shakingLabelController
+        ..value = 0.0
+        ..forward();
+    }
+  }
+
+  Color _getActiveColor(ThemeData themeData) {
+    if (isFocused) {
+      switch (themeData.brightness) {
+        case Brightness.dark:
+          return themeData.accentColor;
+        case Brightness.light:
+          return themeData.primaryColor;
+      }
+    }
+    return themeData.hintColor;
+  }
+
+  Color _getDefaultBorderColor(ThemeData themeData) {
+    if (isFocused) {
+      switch (themeData.brightness) {
+        case Brightness.dark:
+          return themeData.accentColor;
+        case Brightness.light:
+          return themeData.primaryColor;
+      }
+    }
+    if (decoration!.filled!) {
+      return themeData.hintColor;
+    }
+    final Color enabledColor = themeData.colorScheme.onSurface.withOpacity(0.38);
+    if (isHovering) {
+      final Color hoverColor = decoration!.hoverColor ?? themeData.inputDecorationTheme.hoverColor ?? themeData.hoverColor;
+      return Color.alphaBlend(hoverColor.withOpacity(0.12), enabledColor);
+    }
+    return enabledColor;
+  }
+
+  Color _getFillColor(ThemeData themeData) {
+    if (decoration!.filled != true) // filled == null same as filled == false
+      return Colors.transparent;
+    if (decoration!.fillColor != null)
+      return decoration!.fillColor!;
+
+    // dark theme: 10% white (enabled), 5% white (disabled)
+    // light theme: 4% black (enabled), 2% black (disabled)
+    const Color darkEnabled = Color(0x1AFFFFFF);
+    const Color darkDisabled = Color(0x0DFFFFFF);
+    const Color lightEnabled = Color(0x0A000000);
+    const Color lightDisabled = Color(0x05000000);
+
+    switch (themeData.brightness) {
+      case Brightness.dark:
+        return decoration!.enabled ? darkEnabled : darkDisabled;
+      case Brightness.light:
+        return decoration!.enabled ? lightEnabled : lightDisabled;
+    }
+  }
+
+  Color _getHoverColor(ThemeData themeData) {
+    if (decoration!.filled == null || !decoration!.filled! || isFocused || !decoration!.enabled)
+      return Colors.transparent;
+    return decoration!.hoverColor ?? themeData.inputDecorationTheme.hoverColor ?? themeData.hoverColor;
+  }
+
+  Color _getDefaultIconColor(ThemeData themeData) {
+    if (!decoration!.enabled && !isFocused)
+      return themeData.disabledColor;
+
+    switch (themeData.brightness) {
+      case Brightness.dark:
+        return Colors.white70;
+      case Brightness.light:
+        return Colors.black45;
+    }
+  }
+
+  // True if the label will be shown and the hint will not.
+  // If we're not focused, there's no value, labelText was provided, and
+  // floatingLabelBehavior isn't set to always, then the label appears where the
+  // hint would.
+  bool get _hasInlineLabel {
+    return !widget._labelShouldWithdraw
+        && decoration!.labelText != null
+        && decoration!.floatingLabelBehavior != FloatingLabelBehavior.always;
+  }
+
+  // If the label is a floating placeholder, it's always shown.
+  bool get _shouldShowLabel => _hasInlineLabel || _floatingLabelEnabled;
+
+  // The base style for the inline label or hint when they're displayed "inline",
+  // i.e. when they appear in place of the empty text field.
+  TextStyle _getInlineStyle(ThemeData themeData) {
+    return themeData.textTheme.subtitle1!.merge(widget.baseStyle)
+      .copyWith(color: decoration!.enabled ? themeData.hintColor : themeData.disabledColor);
+  }
+
+  TextStyle _getFloatingLabelStyle(ThemeData themeData) {
+    final Color color = decoration!.errorText != null
+      ? decoration!.errorStyle?.color ?? themeData.errorColor
+      : _getActiveColor(themeData);
+    final TextStyle style = themeData.textTheme.subtitle1!.merge(widget.baseStyle);
+    // Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
+    // Setting TextStyle.height to 1 ensures that the label's height will equal
+    // its font size.
+    return themeData.fixTextFieldOutlineLabel
+      ? style
+        .copyWith(height: 1, color: decoration!.enabled ? color : themeData.disabledColor)
+        .merge(decoration!.labelStyle)
+      : style
+        .copyWith(color: decoration!.enabled ? color : themeData.disabledColor)
+        .merge(decoration!.labelStyle);
+
+  }
+
+  TextStyle _getHelperStyle(ThemeData themeData) {
+    final Color color = decoration!.enabled ? themeData.hintColor : Colors.transparent;
+    return themeData.textTheme.caption!.copyWith(color: color).merge(decoration!.helperStyle);
+  }
+
+  TextStyle _getErrorStyle(ThemeData themeData) {
+    final Color color = decoration!.enabled ? themeData.errorColor : Colors.transparent;
+    return themeData.textTheme.caption!.copyWith(color: color).merge(decoration!.errorStyle);
+  }
+
+  InputBorder _getDefaultBorder(ThemeData themeData) {
+    if (decoration!.border?.borderSide == BorderSide.none) {
+      return decoration!.border!;
+    }
+
+    final Color borderColor;
+    if (decoration!.enabled || isFocused) {
+      borderColor = decoration!.errorText == null
+        ? _getDefaultBorderColor(themeData)
+        : themeData.errorColor;
+    } else {
+      borderColor = (decoration!.filled == true && decoration!.border?.isOutline != true)
+        ? Colors.transparent
+        : themeData.disabledColor;
+    }
+
+    final double borderWeight;
+    if (decoration!.isCollapsed || decoration?.border == InputBorder.none || !decoration!.enabled)
+      borderWeight = 0.0;
+    else
+      borderWeight = isFocused ? 2.0 : 1.0;
+
+    final InputBorder border = decoration!.border ?? const UnderlineInputBorder();
+    return border.copyWith(borderSide: BorderSide(color: borderColor, width: borderWeight));
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData themeData = Theme.of(context);
+    final TextStyle inlineStyle = _getInlineStyle(themeData);
+    final TextBaseline textBaseline = inlineStyle.textBaseline!;
+
+    final TextStyle hintStyle = inlineStyle.merge(decoration!.hintStyle);
+    final Widget? hint = decoration!.hintText == null ? null : AnimatedOpacity(
+      opacity: (isEmpty && !_hasInlineLabel) ? 1.0 : 0.0,
+      duration: _kTransitionDuration,
+      curve: _kTransitionCurve,
+      alwaysIncludeSemantics: true,
+      child: Text(
+        decoration!.hintText!,
+        style: hintStyle,
+        overflow: TextOverflow.ellipsis,
+        textAlign: textAlign,
+        maxLines: decoration!.hintMaxLines,
+      ),
+    );
+
+    final bool isError = decoration!.errorText != null;
+    InputBorder? border;
+    if (!decoration!.enabled)
+      border = isError ? decoration!.errorBorder : decoration!.disabledBorder;
+    else if (isFocused)
+      border = isError ? decoration!.focusedErrorBorder : decoration!.focusedBorder;
+    else
+      border = isError ? decoration!.errorBorder : decoration!.enabledBorder;
+    border ??= _getDefaultBorder(themeData);
+
+    final Widget container = _BorderContainer(
+      border: border,
+      gap: _borderGap,
+      gapAnimation: _floatingLabelController.view,
+      fillColor: _getFillColor(themeData),
+      hoverColor: _getHoverColor(themeData),
+      isHovering: isHovering,
+    );
+
+    // Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
+    // Setting TextStyle.height to 1 ensures that the label's height will equal
+    // its font size.
+    final TextStyle inlineLabelStyle = themeData.fixTextFieldOutlineLabel
+      ? inlineStyle.merge(decoration!.labelStyle).copyWith(height: 1)
+      : inlineStyle.merge(decoration!.labelStyle);
+    final Widget? label = decoration!.labelText == null ? null : _Shaker(
+      animation: _shakingLabelController.view,
+      child: AnimatedOpacity(
+        duration: _kTransitionDuration,
+        curve: _kTransitionCurve,
+        opacity: _shouldShowLabel ? 1.0 : 0.0,
+        child: AnimatedDefaultTextStyle(
+          duration:_kTransitionDuration,
+          curve: _kTransitionCurve,
+          style: widget._labelShouldWithdraw
+            ? _getFloatingLabelStyle(themeData)
+            : inlineLabelStyle,
+          child: Text(
+            decoration!.labelText!,
+            overflow: TextOverflow.ellipsis,
+            textAlign: textAlign,
+          ),
+        ),
+      ),
+    );
+
+    final Widget? prefix = decoration!.prefix == null && decoration!.prefixText == null ? null :
+      _AffixText(
+        labelIsFloating: widget._labelShouldWithdraw,
+        text: decoration!.prefixText,
+        style: decoration!.prefixStyle ?? hintStyle,
+        child: decoration!.prefix,
+      );
+
+    final Widget? suffix = decoration!.suffix == null && decoration!.suffixText == null ? null :
+      _AffixText(
+        labelIsFloating: widget._labelShouldWithdraw,
+        text: decoration!.suffixText,
+        style: decoration!.suffixStyle ?? hintStyle,
+        child: decoration!.suffix,
+      );
+
+    final Color activeColor = _getActiveColor(themeData);
+    final bool decorationIsDense = decoration!.isDense == true; // isDense == null, same as false
+    final double iconSize = decorationIsDense ? 18.0 : 24.0;
+    final Color iconColor = isFocused ? activeColor : _getDefaultIconColor(themeData);
+
+    final Widget? icon = decoration!.icon == null ? null :
+      Padding(
+        padding: const EdgeInsetsDirectional.only(end: 16.0),
+        child: IconTheme.merge(
+          data: IconThemeData(
+            color: iconColor,
+            size: iconSize,
+          ),
+          child: decoration!.icon!,
+        ),
+      );
+
+    final Widget? prefixIcon = decoration!.prefixIcon == null ? null :
+      Center(
+        widthFactor: 1.0,
+        heightFactor: 1.0,
+        child: ConstrainedBox(
+          constraints: decoration!.prefixIconConstraints ?? themeData.visualDensity.effectiveConstraints(
+            const BoxConstraints(
+              minWidth: kMinInteractiveDimension,
+              minHeight: kMinInteractiveDimension,
+            ),
+          ),
+          child: IconTheme.merge(
+            data: IconThemeData(
+              color: iconColor,
+              size: iconSize,
+            ),
+            child: decoration!.prefixIcon!,
+          ),
+        ),
+      );
+
+    final Widget? suffixIcon = decoration!.suffixIcon == null ? null :
+      Center(
+        widthFactor: 1.0,
+        heightFactor: 1.0,
+        child: ConstrainedBox(
+          constraints: decoration!.suffixIconConstraints ?? themeData.visualDensity.effectiveConstraints(
+            const BoxConstraints(
+              minWidth: kMinInteractiveDimension,
+              minHeight: kMinInteractiveDimension,
+            ),
+          ),
+          child: IconTheme.merge(
+            data: IconThemeData(
+              color: iconColor,
+              size: iconSize,
+            ),
+            child: decoration!.suffixIcon!,
+          ),
+        ),
+      );
+
+    final Widget helperError = _HelperError(
+      textAlign: textAlign,
+      helperText: decoration!.helperText,
+      helperStyle: _getHelperStyle(themeData),
+      helperMaxLines: decoration!.helperMaxLines,
+      errorText: decoration!.errorText,
+      errorStyle: _getErrorStyle(themeData),
+      errorMaxLines: decoration!.errorMaxLines,
+    );
+
+    Widget? counter;
+    if (decoration!.counter != null) {
+      counter = decoration!.counter;
+    } else if (decoration!.counterText != null && decoration!.counterText != '') {
+      counter = Semantics(
+        container: true,
+        liveRegion: isFocused,
+        child: Text(
+          decoration!.counterText!,
+          style: _getHelperStyle(themeData).merge(decoration!.counterStyle),
+          overflow: TextOverflow.ellipsis,
+          semanticsLabel: decoration!.semanticCounterText,
+        ),
+      );
+    }
+
+    // The _Decoration widget and _RenderDecoration assume that contentPadding
+    // has been resolved to EdgeInsets.
+    final TextDirection textDirection = Directionality.of(context);
+    final EdgeInsets? decorationContentPadding = decoration!.contentPadding?.resolve(textDirection);
+
+    final EdgeInsets contentPadding;
+    final double floatingLabelHeight;
+    if (decoration!.isCollapsed) {
+      floatingLabelHeight = 0.0;
+      contentPadding = decorationContentPadding ?? EdgeInsets.zero;
+    } else if (!border.isOutline) {
+      // 4.0: the vertical gap between the inline elements and the floating label.
+      floatingLabelHeight = (4.0 + 0.75 * inlineLabelStyle.fontSize!) * MediaQuery.textScaleFactorOf(context);
+      if (decoration!.filled == true) { // filled == null same as filled == false
+        contentPadding = decorationContentPadding ?? (decorationIsDense
+          ? const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0)
+          : const EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0));
+      } else {
+        // Not left or right padding for underline borders that aren't filled
+        // is a small concession to backwards compatibility. This eliminates
+        // the most noticeable layout change introduced by #13734.
+        contentPadding = decorationContentPadding ?? (decorationIsDense
+          ? const EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 8.0)
+          : const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 12.0));
+      }
+    } else {
+      floatingLabelHeight = 0.0;
+      contentPadding = decorationContentPadding ?? (decorationIsDense
+        ? const EdgeInsets.fromLTRB(12.0, 20.0, 12.0, 12.0)
+        : const EdgeInsets.fromLTRB(12.0, 24.0, 12.0, 16.0));
+    }
+
+    return _Decorator(
+      decoration: _Decoration(
+        contentPadding: contentPadding,
+        isCollapsed: decoration!.isCollapsed,
+        floatingLabelHeight: floatingLabelHeight,
+        floatingLabelProgress: _floatingLabelController.value,
+        border: border,
+        borderGap: _borderGap,
+        alignLabelWithHint: decoration!.alignLabelWithHint ?? false,
+        isDense: decoration!.isDense,
+        visualDensity: themeData.visualDensity,
+        icon: icon,
+        input: widget.child,
+        label: label,
+        hint: hint,
+        prefix: prefix,
+        suffix: suffix,
+        prefixIcon: prefixIcon,
+        suffixIcon: suffixIcon,
+        helperError: helperError,
+        counter: counter,
+        container: container,
+        fixTextFieldOutlineLabel: themeData.fixTextFieldOutlineLabel,
+      ),
+      textDirection: textDirection,
+      textBaseline: textBaseline,
+      textAlignVertical: widget.textAlignVertical,
+      isFocused: isFocused,
+      expands: widget.expands,
+    );
+  }
+}
+
+/// The border, labels, icons, and styles used to decorate a Material
+/// Design text field.
+///
+/// The [TextField] and [InputDecorator] classes use [InputDecoration] objects
+/// to describe their decoration. (In fact, this class is merely the
+/// configuration of an [InputDecorator], which does all the heavy lifting.)
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// This sample shows how to style a `TextField` using an `InputDecorator`. The
+/// TextField displays a "send message" icon to the left of the input area,
+/// which is surrounded by a border an all sides. It displays the `hintText`
+/// inside the input area to help the user understand what input is required. It
+/// displays the `helperText` and `counterText` below the input area.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/input_decoration.png)
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return TextField(
+///     decoration: InputDecoration(
+///       icon: Icon(Icons.send),
+///       hintText: 'Hint Text',
+///       helperText: 'Helper Text',
+///       counterText: '0 characters',
+///       border: const OutlineInputBorder(),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// This sample shows how to style a "collapsed" `TextField` using an
+/// `InputDecorator`. The collapsed `TextField` surrounds the hint text and
+/// input area with a border, but does not add padding around them.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/input_decoration_collapsed.png)
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return TextField(
+///     decoration: InputDecoration.collapsed(
+///       hintText: 'Hint Text',
+///       border: OutlineInputBorder(),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// This sample shows how to create a `TextField` with hint text, a red border
+/// on all sides, and an error message. To display a red border and error
+/// message, provide `errorText` to the `InputDecoration` constructor.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/input_decoration_error.png)
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return TextField(
+///     decoration: InputDecoration(
+///       hintText: 'Hint Text',
+///       errorText: 'Error Text',
+///       border: OutlineInputBorder(),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// This sample shows how to style a `TextField` with a round border and
+/// additional text before and after the input area. It displays "Prefix" before
+/// the input area, and "Suffix" after the input area.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/input_decoration_prefix_suffix.png)
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return TextFormField(
+///     initialValue: 'abc',
+///     decoration: const InputDecoration(
+///       prefix: Text('Prefix'),
+///       suffix: Text('Suffix'),
+///       border: OutlineInputBorder(),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [TextField], which is a text input widget that uses an
+///    [InputDecoration].
+///  * [InputDecorator], which is a widget that draws an [InputDecoration]
+///    around an input child widget.
+///  * [Decoration] and [DecoratedBox], for drawing borders and backgrounds
+///    around a child widget.
+@immutable
+class InputDecoration {
+  /// Creates a bundle of the border, labels, icons, and styles used to
+  /// decorate a Material Design text field.
+  ///
+  /// Unless specified by [ThemeData.inputDecorationTheme], [InputDecorator]
+  /// defaults [isDense] to false and [filled] to false. The default border is
+  /// an instance of [UnderlineInputBorder]. If [border] is [InputBorder.none]
+  /// then no border is drawn.
+  ///
+  /// The [enabled] argument must not be null.
+  ///
+  /// Only one of [prefix] and [prefixText] can be specified.
+  ///
+  /// Similarly, only one of [suffix] and [suffixText] can be specified.
+  const InputDecoration({
+    this.icon,
+    this.labelText,
+    this.labelStyle,
+    this.helperText,
+    this.helperStyle,
+    this.helperMaxLines,
+    this.hintText,
+    this.hintStyle,
+    this.hintMaxLines,
+    this.errorText,
+    this.errorStyle,
+    this.errorMaxLines,
+    @Deprecated(
+      'Use floatingLabelBehavior instead. '
+      'This feature was deprecated after v1.13.2.'
+    )
+    this.hasFloatingPlaceholder = true,
+    this.floatingLabelBehavior,
+    this.isCollapsed = false,
+    this.isDense,
+    this.contentPadding,
+    this.prefixIcon,
+    this.prefixIconConstraints,
+    this.prefix,
+    this.prefixText,
+    this.prefixStyle,
+    this.suffixIcon,
+    this.suffix,
+    this.suffixText,
+    this.suffixStyle,
+    this.suffixIconConstraints,
+    this.counter,
+    this.counterText,
+    this.counterStyle,
+    this.filled,
+    this.fillColor,
+    this.focusColor,
+    this.hoverColor,
+    this.errorBorder,
+    this.focusedBorder,
+    this.focusedErrorBorder,
+    this.disabledBorder,
+    this.enabledBorder,
+    this.border,
+    this.enabled = true,
+    this.semanticCounterText,
+    this.alignLabelWithHint,
+  }) : assert(enabled != null),
+       assert(!(prefix != null && prefixText != null), 'Declaring both prefix and prefixText is not supported.'),
+       assert(!(suffix != null && suffixText != null), 'Declaring both suffix and suffixText is not supported.');
+
+  /// Defines an [InputDecorator] that is the same size as the input field.
+  ///
+  /// This type of input decoration does not include a border by default.
+  ///
+  /// Sets the [isCollapsed] property to true.
+  const InputDecoration.collapsed({
+    required this.hintText,
+    @Deprecated(
+      'Use floatingLabelBehavior instead. '
+      'This feature was deprecated after v1.13.2.'
+    )
+    this.hasFloatingPlaceholder = true,
+    this.floatingLabelBehavior,
+    this.hintStyle,
+    this.filled = false,
+    this.fillColor,
+    this.focusColor,
+    this.hoverColor,
+    this.border = InputBorder.none,
+    this.enabled = true,
+  }) : assert(enabled != null),
+       assert(!(!hasFloatingPlaceholder && identical(floatingLabelBehavior, FloatingLabelBehavior.always)),
+              'hasFloatingPlaceholder=false conflicts with FloatingLabelBehavior.always'),
+       icon = null,
+       labelText = null,
+       labelStyle = null,
+       helperText = null,
+       helperStyle = null,
+       helperMaxLines = null,
+       hintMaxLines = null,
+       errorText = null,
+       errorStyle = null,
+       errorMaxLines = null,
+       isDense = false,
+       contentPadding = EdgeInsets.zero,
+       isCollapsed = true,
+       prefixIcon = null,
+       prefix = null,
+       prefixText = null,
+       prefixStyle = null,
+       prefixIconConstraints = null,
+       suffix = null,
+       suffixIcon = null,
+       suffixText = null,
+       suffixStyle = null,
+       suffixIconConstraints = null,
+       counter = null,
+       counterText = null,
+       counterStyle = null,
+       errorBorder = null,
+       focusedBorder = null,
+       focusedErrorBorder = null,
+       disabledBorder = null,
+       enabledBorder = null,
+       semanticCounterText = null,
+       alignLabelWithHint = false;
+
+  /// An icon to show before the input field and outside of the decoration's
+  /// container.
+  ///
+  /// The size and color of the icon is configured automatically using an
+  /// [IconTheme] and therefore does not need to be explicitly given in the
+  /// icon widget.
+  ///
+  /// The trailing edge of the icon is padded by 16dps.
+  ///
+  /// The decoration's container is the area which is filled if [filled] is
+  /// true and bordered per the [border]. It's the area adjacent to
+  /// [icon] and above the widgets that contain [helperText],
+  /// [errorText], and [counterText].
+  ///
+  /// See [Icon], [ImageIcon].
+  final Widget? icon;
+
+  /// Text that describes the input field.
+  ///
+  /// When the input field is empty and unfocused, the label is displayed on
+  /// top of the input field (i.e., at the same location on the screen where
+  /// text may be entered in the input field). When the input field receives
+  /// focus (or if the field is non-empty), the label moves above (i.e.,
+  /// vertically adjacent to) the input field.
+  final String? labelText;
+
+  /// The style to use for the [labelText] when the label is above (i.e.,
+  /// vertically adjacent to) the input field.
+  ///
+  /// When the [labelText] is on top of the input field, the text uses the
+  /// [hintStyle] instead.
+  ///
+  /// If null, defaults to a value derived from the base [TextStyle] for the
+  /// input field and the current [Theme].
+  final TextStyle? labelStyle;
+
+  /// Text that provides context about the [InputDecorator.child]'s value, such
+  /// as how the value will be used.
+  ///
+  /// If non-null, the text is displayed below the [InputDecorator.child], in
+  /// the same location as [errorText]. If a non-null [errorText] value is
+  /// specified then the helper text is not shown.
+  final String? helperText;
+
+  /// The style to use for the [helperText].
+  final TextStyle? helperStyle;
+
+  /// The maximum number of lines the [helperText] can occupy.
+  ///
+  /// Defaults to null, which means that the [helperText] will be limited
+  /// to a single line with [TextOverflow.ellipsis].
+  ///
+  /// This value is passed along to the [Text.maxLines] attribute
+  /// of the [Text] widget used to display the helper.
+  ///
+  /// See also:
+  ///
+  ///  * [errorMaxLines], the equivalent but for the [errorText].
+  final int? helperMaxLines;
+
+  /// Text that suggests what sort of input the field accepts.
+  ///
+  /// Displayed on top of the [InputDecorator.child] (i.e., at the same location
+  /// on the screen where text may be entered in the [InputDecorator.child])
+  /// when the input [isEmpty] and either (a) [labelText] is null or (b) the
+  /// input has the focus.
+  final String? hintText;
+
+  /// The style to use for the [hintText].
+  ///
+  /// Also used for the [labelText] when the [labelText] is displayed on
+  /// top of the input field (i.e., at the same location on the screen where
+  /// text may be entered in the [InputDecorator.child]).
+  ///
+  /// If null, defaults to a value derived from the base [TextStyle] for the
+  /// input field and the current [Theme].
+  final TextStyle? hintStyle;
+
+  /// The maximum number of lines the [hintText] can occupy.
+  ///
+  /// Defaults to the value of [TextField.maxLines] attribute.
+  ///
+  /// This value is passed along to the [Text.maxLines] attribute
+  /// of the [Text] widget used to display the hint text. [TextOverflow.ellipsis] is
+  /// used to handle the overflow when it is limited to single line.
+  final int? hintMaxLines;
+
+  /// Text that appears below the [InputDecorator.child] and the border.
+  ///
+  /// If non-null, the border's color animates to red and the [helperText] is
+  /// not shown.
+  ///
+  /// In a [TextFormField], this is overridden by the value returned from
+  /// [TextFormField.validator], if that is not null.
+  final String? errorText;
+
+  /// The style to use for the [errorText].
+  ///
+  /// If null, defaults of a value derived from the base [TextStyle] for the
+  /// input field and the current [Theme].
+  final TextStyle? errorStyle;
+
+
+  /// The maximum number of lines the [errorText] can occupy.
+  ///
+  /// Defaults to null, which means that the [errorText] will be limited
+  /// to a single line with [TextOverflow.ellipsis].
+  ///
+  /// This value is passed along to the [Text.maxLines] attribute
+  /// of the [Text] widget used to display the error.
+  ///
+  /// See also:
+  ///
+  ///  * [helperMaxLines], the equivalent but for the [helperText].
+  final int? errorMaxLines;
+
+  /// Whether the label floats on focus.
+  ///
+  /// If this is false, the placeholder disappears when the input has focus or
+  /// text has been entered.
+  /// If this is true, the placeholder will rise to the top of the input when
+  /// the input has focus or text has been entered.
+  ///
+  /// Defaults to true.
+  ///
+  @Deprecated(
+    'Use floatingLabelBehavior instead. '
+    'This feature was deprecated after v1.13.2.'
+  )
+  final bool hasFloatingPlaceholder;
+
+  /// {@template flutter.material.inputDecoration.floatingLabelBehavior}
+  /// Defines how the floating label should be displayed.
+  ///
+  /// When [FloatingLabelBehavior.auto] the label will float to the top only when
+  /// the field is focused or has some text content, otherwise it will appear
+  /// in the field in place of the content.
+  ///
+  /// When [FloatingLabelBehavior.always] the label will always float at the top
+  /// of the field above the content.
+  ///
+  /// When [FloatingLabelBehavior.never] the label will always appear in an empty
+  /// field in place of the content.
+  /// {@endtemplate}
+  ///
+  /// If null, [InputDecorationTheme.floatingLabelBehavior] will be used.
+  final FloatingLabelBehavior? floatingLabelBehavior;
+
+  /// Whether the [InputDecorator.child] is part of a dense form (i.e., uses less vertical
+  /// space).
+  ///
+  /// Defaults to false.
+  final bool? isDense;
+
+  /// The padding for the input decoration's container.
+  ///
+  /// The decoration's container is the area which is filled if [filled] is true
+  /// and bordered per the [border]. It's the area adjacent to [icon] and above
+  /// the widgets that contain [helperText], [errorText], and [counterText].
+  ///
+  /// By default the `contentPadding` reflects [isDense] and the type of the
+  /// [border].
+  ///
+  /// If [isCollapsed] is true then `contentPadding` is [EdgeInsets.zero].
+  ///
+  /// If `isOutline` property of [border] is false and if [filled] is true then
+  /// `contentPadding` is `EdgeInsets.fromLTRB(12, 8, 12, 8)` when [isDense]
+  /// is true and `EdgeInsets.fromLTRB(12, 12, 12, 12)` when [isDense] is false.
+  /// If `isOutline` property of [border] is false and if [filled] is false then
+  /// `contentPadding` is `EdgeInsets.fromLTRB(0, 8, 0, 8)` when [isDense] is
+  /// true and `EdgeInsets.fromLTRB(0, 12, 0, 12)` when [isDense] is false.
+  ///
+  /// If `isOutline` property of [border] is true then `contentPadding` is
+  /// `EdgeInsets.fromLTRB(12, 20, 12, 12)` when [isDense] is true
+  /// and `EdgeInsets.fromLTRB(12, 24, 12, 16)` when [isDense] is false.
+  final EdgeInsetsGeometry? contentPadding;
+
+  /// Whether the decoration is the same size as the input field.
+  ///
+  /// A collapsed decoration cannot have [labelText], [errorText], an [icon].
+  ///
+  /// To create a collapsed input decoration, use [InputDecoration.collapsed].
+  final bool isCollapsed;
+
+  /// An icon that appears before the [prefix] or [prefixText] and before
+  /// the editable part of the text field, within the decoration's container.
+  ///
+  /// The size and color of the prefix icon is configured automatically using an
+  /// [IconTheme] and therefore does not need to be explicitly given in the
+  /// icon widget.
+  ///
+  /// The prefix icon is constrained with a minimum size of 48px by 48px, but
+  /// can be expanded beyond that. Anything larger than 24px will require
+  /// additional padding to ensure it matches the material spec of 12px padding
+  /// between the left edge of the input and leading edge of the prefix icon.
+  /// The following snippet shows how to pad the leading edge of the prefix
+  /// icon:
+  ///
+  /// ```dart
+  /// prefixIcon: Padding(
+  ///   padding: const EdgeInsetsDirectional.only(start: 12.0),
+  ///   child: myIcon, // myIcon is a 48px-wide widget.
+  /// )
+  /// ```
+  ///
+  /// The decoration's container is the area which is filled if [filled] is
+  /// true and bordered per the [border]. It's the area adjacent to
+  /// [icon] and above the widgets that contain [helperText],
+  /// [errorText], and [counterText].
+  ///
+  /// See also:
+  ///
+  ///  * [Icon] and [ImageIcon], which are typically used to show icons.
+  ///  * [prefix] and [prefixText], which are other ways to show content
+  ///    before the text field (but after the icon).
+  ///  * [suffixIcon], which is the same but on the trailing edge.
+  final Widget? prefixIcon;
+
+  /// The constraints for the prefix icon.
+  ///
+  /// This can be used to modify the [BoxConstraints] surrounding [prefixIcon].
+  ///
+  /// This property is particularly useful for getting the decoration's height
+  /// less than 48px. This can be achieved by setting [isDense] to true and
+  /// setting the constraints' minimum height and width to a value lower than
+  /// 48px.
+  ///
+  /// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+  /// This example shows the differences between two `TextField` widgets when
+  /// [prefixIconConstraints] is set to the default value and when one is not.
+  ///
+  /// Note that [isDense] must be set to true to be able to
+  /// set the constraints smaller than 48px.
+  ///
+  /// If null, [BoxConstraints] with a minimum width and height of 48px is
+  /// used.
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return Padding(
+  ///     padding: const EdgeInsets.symmetric(horizontal: 8.0),
+  ///     child: Column(
+  ///       mainAxisAlignment: MainAxisAlignment.center,
+  ///       children: <Widget>[
+  ///         TextField(
+  ///           decoration: InputDecoration(
+  ///             hintText: 'Normal Icon Constraints',
+  ///             prefixIcon: Icon(Icons.search),
+  ///           ),
+  ///         ),
+  ///         SizedBox(height: 10),
+  ///         TextField(
+  ///           decoration: InputDecoration(
+  ///             isDense: true,
+  ///             hintText:'Smaller Icon Constraints',
+  ///             prefixIcon: Icon(Icons.search),
+  ///             prefixIconConstraints: BoxConstraints(
+  ///               minHeight: 32,
+  ///               minWidth: 32,
+  ///             ),
+  ///           ),
+  ///         ),
+  ///       ],
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  final BoxConstraints? prefixIconConstraints;
+
+  /// Optional widget to place on the line before the input.
+  ///
+  /// This can be used, for example, to add some padding to text that would
+  /// otherwise be specified using [prefixText], or to add a custom widget in
+  /// front of the input. The widget's baseline is lined up with the input
+  /// baseline.
+  ///
+  /// Only one of [prefix] and [prefixText] can be specified.
+  ///
+  /// The [prefix] appears after the [prefixIcon], if both are specified.
+  ///
+  /// See also:
+  ///
+  ///  * [suffix], the equivalent but on the trailing edge.
+  final Widget? prefix;
+
+  /// Optional text prefix to place on the line before the input.
+  ///
+  /// Uses the [prefixStyle]. Uses [hintStyle] if [prefixStyle] isn't specified.
+  /// The prefix text is not returned as part of the user's input.
+  ///
+  /// If a more elaborate prefix is required, consider using [prefix] instead.
+  /// Only one of [prefix] and [prefixText] can be specified.
+  ///
+  /// The [prefixText] appears after the [prefixIcon], if both are specified.
+  ///
+  /// See also:
+  ///
+  ///  * [suffixText], the equivalent but on the trailing edge.
+  final String? prefixText;
+
+  /// The style to use for the [prefixText].
+  ///
+  /// If null, defaults to the [hintStyle].
+  ///
+  /// See also:
+  ///
+  ///  * [suffixStyle], the equivalent but on the trailing edge.
+  final TextStyle? prefixStyle;
+
+  /// An icon that appears after the editable part of the text field and
+  /// after the [suffix] or [suffixText], within the decoration's container.
+  ///
+  /// The size and color of the suffix icon is configured automatically using an
+  /// [IconTheme] and therefore does not need to be explicitly given in the
+  /// icon widget.
+  ///
+  /// The suffix icon is constrained with a minimum size of 48px by 48px, but
+  /// can be expanded beyond that. Anything larger than 24px will require
+  /// additional padding to ensure it matches the material spec of 12px padding
+  /// between the right edge of the input and trailing edge of the prefix icon.
+  /// The following snippet shows how to pad the trailing edge of the suffix
+  /// icon:
+  ///
+  /// ```dart
+  /// suffixIcon: Padding(
+  ///   padding: const EdgeInsetsDirectional.only(end: 12.0),
+  ///   child: myIcon, // myIcon is a 48px-wide widget.
+  /// )
+  /// ```
+  ///
+  /// The decoration's container is the area which is filled if [filled] is
+  /// true and bordered per the [border]. It's the area adjacent to
+  /// [icon] and above the widgets that contain [helperText],
+  /// [errorText], and [counterText].
+  ///
+  /// See also:
+  ///
+  ///  * [Icon] and [ImageIcon], which are typically used to show icons.
+  ///  * [suffix] and [suffixText], which are other ways to show content
+  ///    after the text field (but before the icon).
+  ///  * [prefixIcon], which is the same but on the leading edge.
+  final Widget? suffixIcon;
+
+  /// Optional widget to place on the line after the input.
+  ///
+  /// This can be used, for example, to add some padding to the text that would
+  /// otherwise be specified using [suffixText], or to add a custom widget after
+  /// the input. The widget's baseline is lined up with the input baseline.
+  ///
+  /// Only one of [suffix] and [suffixText] can be specified.
+  ///
+  /// The [suffix] appears before the [suffixIcon], if both are specified.
+  ///
+  /// See also:
+  ///
+  ///  * [prefix], the equivalent but on the leading edge.
+  final Widget? suffix;
+
+  /// Optional text suffix to place on the line after the input.
+  ///
+  /// Uses the [suffixStyle]. Uses [hintStyle] if [suffixStyle] isn't specified.
+  /// The suffix text is not returned as part of the user's input.
+  ///
+  /// If a more elaborate suffix is required, consider using [suffix] instead.
+  /// Only one of [suffix] and [suffixText] can be specified.
+  ///
+  /// The [suffixText] appears before the [suffixIcon], if both are specified.
+  ///
+  /// See also:
+  ///
+  ///  * [prefixText], the equivalent but on the leading edge.
+  final String? suffixText;
+
+  /// The style to use for the [suffixText].
+  ///
+  /// If null, defaults to the [hintStyle].
+  ///
+  /// See also:
+  ///
+  ///  * [prefixStyle], the equivalent but on the leading edge.
+  final TextStyle? suffixStyle;
+
+  /// The constraints for the suffix icon.
+  ///
+  /// This can be used to modify the [BoxConstraints] surrounding [suffixIcon].
+  ///
+  /// This property is particularly useful for getting the decoration's height
+  /// less than 48px. This can be achieved by setting [isDense] to true and
+  /// setting the constraints' minimum height and width to a value lower than
+  /// 48px.
+  ///
+  /// If null, a [BoxConstraints] with a minimum width and height of 48px is
+  /// used.
+  ///
+  /// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+  /// This example shows the differences between two `TextField` widgets when
+  /// [suffixIconConstraints] is set to the default value and when one is not.
+  ///
+  /// Note that [isDense] must be set to true to be able to
+  /// set the constraints smaller than 48px.
+  ///
+  /// If null, [BoxConstraints] with a minimum width and height of 48px is
+  /// used.
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return Padding(
+  ///     padding: const EdgeInsets.symmetric(horizontal: 8.0),
+  ///     child: Column(
+  ///       mainAxisAlignment: MainAxisAlignment.center,
+  ///       children: <Widget>[
+  ///         TextField(
+  ///           decoration: InputDecoration(
+  ///             hintText: 'Normal Icon Constraints',
+  ///             suffixIcon: Icon(Icons.search),
+  ///           ),
+  ///         ),
+  ///         SizedBox(height: 10),
+  ///         TextField(
+  ///           decoration: InputDecoration(
+  ///             isDense: true,
+  ///             hintText:'Smaller Icon Constraints',
+  ///             suffixIcon: Icon(Icons.search),
+  ///             suffixIconConstraints: BoxConstraints(
+  ///               minHeight: 32,
+  ///               minWidth: 32,
+  ///             ),
+  ///           ),
+  ///         ),
+  ///       ],
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  final BoxConstraints? suffixIconConstraints;
+
+  /// Optional text to place below the line as a character count.
+  ///
+  /// Rendered using [counterStyle]. Uses [helperStyle] if [counterStyle] is
+  /// null.
+  ///
+  /// The semantic label can be replaced by providing a [semanticCounterText].
+  ///
+  /// If null or an empty string and [counter] isn't specified, then nothing
+  /// will appear in the counter's location.
+  final String? counterText;
+
+  /// Optional custom counter widget to go in the place otherwise occupied by
+  /// [counterText].  If this property is non null, then [counterText] is
+  /// ignored.
+  final Widget? counter;
+
+  /// The style to use for the [counterText].
+  ///
+  /// If null, defaults to the [helperStyle].
+  final TextStyle? counterStyle;
+
+  /// If true the decoration's container is filled with [fillColor].
+  ///
+  /// When [InputDecorator.isHovering] is true, the [hoverColor] is also blended
+  /// into the final fill color.
+  ///
+  /// Typically this field set to true if [border] is an
+  /// [UnderlineInputBorder].
+  ///
+  /// The decoration's container is the area which is filled if [filled] is
+  /// true and bordered per the [border]. It's the area adjacent to
+  /// [icon] and above the widgets that contain [helperText],
+  /// [errorText], and [counterText].
+  ///
+  /// This property is false by default.
+  final bool? filled;
+
+  /// The base fill color of the decoration's container color.
+  ///
+  /// When [InputDecorator.isHovering] is true, the
+  /// [hoverColor] is also blended into the final fill color.
+  ///
+  /// By default the fillColor is based on the current [Theme].
+  ///
+  /// The decoration's container is the area which is filled if [filled] is true
+  /// and bordered per the [border]. It's the area adjacent to [icon] and above
+  /// the widgets that contain [helperText], [errorText], and [counterText].
+  final Color? fillColor;
+
+  /// By default the [focusColor] is based on the current [Theme].
+  ///
+  /// The decoration's container is the area which is filled if [filled] is
+  /// true and bordered per the [border]. It's the area adjacent to
+  /// [icon] and above the widgets that contain [helperText],
+  /// [errorText], and [counterText].
+  final Color? focusColor;
+
+  /// The color of the focus highlight for the decoration shown if the container
+  /// is being hovered over by a mouse.
+  ///
+  /// If [filled] is true, the color is blended with [fillColor] and fills the
+  /// decoration's container.
+  ///
+  /// If [filled] is false, and [InputDecorator.isFocused] is false, the color
+  /// is blended over the [enabledBorder]'s color.
+  ///
+  /// By default the [hoverColor] is based on the current [Theme].
+  ///
+  /// The decoration's container is the area which is filled if [filled] is
+  /// true and bordered per the [border]. It's the area adjacent to
+  /// [icon] and above the widgets that contain [helperText],
+  /// [errorText], and [counterText].
+  final Color? hoverColor;
+
+  /// The border to display when the [InputDecorator] does not have the focus and
+  /// is showing an error.
+  ///
+  /// See also:
+  ///
+  ///  * [InputDecorator.isFocused], which is true if the [InputDecorator]'s child
+  ///    has the focus.
+  ///  * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null.
+  ///  * [border], for a description of where the [InputDecorator] border appears.
+  ///  * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal
+  ///    line at the bottom of the input decorator's container.
+  ///  * [OutlineInputBorder], an [InputDecorator] border which draws a
+  ///    rounded rectangle around the input decorator's container.
+  ///  * [InputBorder.none], which doesn't draw a border.
+  ///  * [focusedBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is null.
+  ///  * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [disabledBorder], displayed when [InputDecoration.enabled] is false
+  ///    and [InputDecoration.errorText] is null.
+  ///  * [enabledBorder], displayed when [InputDecoration.enabled] is true
+  ///    and [InputDecoration.errorText] is null.
+  final InputBorder? errorBorder;
+
+  /// The border to display when the [InputDecorator] has the focus and is not
+  /// showing an error.
+  ///
+  /// See also:
+  ///
+  ///  * [InputDecorator.isFocused], which is true if the [InputDecorator]'s child
+  ///    has the focus.
+  ///  * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null.
+  ///  * [border], for a description of where the [InputDecorator] border appears.
+  ///  * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal
+  ///    line at the bottom of the input decorator's container.
+  ///  * [OutlineInputBorder], an [InputDecorator] border which draws a
+  ///    rounded rectangle around the input decorator's container.
+  ///  * [InputBorder.none], which doesn't draw a border.
+  ///  * [errorBorder], displayed when [InputDecorator.isFocused] is false
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [disabledBorder], displayed when [InputDecoration.enabled] is false
+  ///    and [InputDecoration.errorText] is null.
+  ///  * [enabledBorder], displayed when [InputDecoration.enabled] is true
+  ///    and [InputDecoration.errorText] is null.
+  final InputBorder? focusedBorder;
+
+  /// The border to display when the [InputDecorator] has the focus and is
+  /// showing an error.
+  ///
+  /// See also:
+  ///
+  ///  * [InputDecorator.isFocused], which is true if the [InputDecorator]'s child
+  ///    has the focus.
+  ///  * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null.
+  ///  * [border], for a description of where the [InputDecorator] border appears.
+  ///  * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal
+  ///    line at the bottom of the input decorator's container.
+  ///  * [OutlineInputBorder], an [InputDecorator] border which draws a
+  ///    rounded rectangle around the input decorator's container.
+  ///  * [InputBorder.none], which doesn't draw a border.
+  ///  * [errorBorder], displayed when [InputDecorator.isFocused] is false
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [focusedBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is null.
+  ///  * [disabledBorder], displayed when [InputDecoration.enabled] is false
+  ///    and [InputDecoration.errorText] is null.
+  ///  * [enabledBorder], displayed when [InputDecoration.enabled] is true
+  ///    and [InputDecoration.errorText] is null.
+  final InputBorder? focusedErrorBorder;
+
+  /// The border to display when the [InputDecorator] is disabled and is not
+  /// showing an error.
+  ///
+  /// See also:
+  ///
+  ///  * [InputDecoration.enabled], which is false if the [InputDecorator] is disabled.
+  ///  * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null.
+  ///  * [border], for a description of where the [InputDecorator] border appears.
+  ///  * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal
+  ///    line at the bottom of the input decorator's container.
+  ///  * [OutlineInputBorder], an [InputDecorator] border which draws a
+  ///    rounded rectangle around the input decorator's container.
+  ///  * [InputBorder.none], which doesn't draw a border.
+  ///  * [errorBorder], displayed when [InputDecorator.isFocused] is false
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [focusedBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is null.
+  ///  * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [enabledBorder], displayed when [InputDecoration.enabled] is true
+  ///    and [InputDecoration.errorText] is null.
+  final InputBorder? disabledBorder;
+
+  /// The border to display when the [InputDecorator] is enabled and is not
+  /// showing an error.
+  ///
+  /// See also:
+  ///
+  ///  * [InputDecoration.enabled], which is false if the [InputDecorator] is disabled.
+  ///  * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null.
+  ///  * [border], for a description of where the [InputDecorator] border appears.
+  ///  * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal
+  ///    line at the bottom of the input decorator's container.
+  ///  * [OutlineInputBorder], an [InputDecorator] border which draws a
+  ///    rounded rectangle around the input decorator's container.
+  ///  * [InputBorder.none], which doesn't draw a border.
+  ///  * [errorBorder], displayed when [InputDecorator.isFocused] is false
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [focusedBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is null.
+  ///  * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [disabledBorder], displayed when [InputDecoration.enabled] is false
+  ///    and [InputDecoration.errorText] is null.
+  final InputBorder? enabledBorder;
+
+  /// The shape of the border to draw around the decoration's container.
+  ///
+  /// This border's [InputBorder.borderSide], i.e. the border's color and width,
+  /// will be overridden to reflect the input decorator's state. Only the
+  /// border's shape is used. If custom  [BorderSide] values are desired for
+  /// a given state, all four borders – [errorBorder], [focusedBorder],
+  /// [enabledBorder], [disabledBorder] – must be set.
+  ///
+  /// The decoration's container is the area which is filled if [filled] is
+  /// true and bordered per the [border]. It's the area adjacent to
+  /// [InputDecoration.icon] and above the widgets that contain
+  /// [InputDecoration.helperText], [InputDecoration.errorText], and
+  /// [InputDecoration.counterText].
+  ///
+  /// The border's bounds, i.e. the value of `border.getOuterPath()`, define
+  /// the area to be filled.
+  ///
+  /// This property is only used when the appropriate one of [errorBorder],
+  /// [focusedBorder], [focusedErrorBorder], [disabledBorder], or [enabledBorder]
+  /// is not specified. This border's [InputBorder.borderSide] property is
+  /// configured by the InputDecorator, depending on the values of
+  /// [InputDecoration.errorText], [InputDecoration.enabled],
+  /// [InputDecorator.isFocused] and the current [Theme].
+  ///
+  /// Typically one of [UnderlineInputBorder] or [OutlineInputBorder].
+  /// If null, InputDecorator's default is `const UnderlineInputBorder()`.
+  ///
+  /// See also:
+  ///
+  ///  * [InputBorder.none], which doesn't draw a border.
+  ///  * [UnderlineInputBorder], which draws a horizontal line at the
+  ///    bottom of the input decorator's container.
+  ///  * [OutlineInputBorder], an [InputDecorator] border which draws a
+  ///    rounded rectangle around the input decorator's container.
+  final InputBorder? border;
+
+  /// If false [helperText],[errorText], and [counterText] are not displayed,
+  /// and the opacity of the remaining visual elements is reduced.
+  ///
+  /// This property is true by default.
+  final bool enabled;
+
+  /// A semantic label for the [counterText].
+  ///
+  /// Defaults to null.
+  ///
+  /// If provided, this replaces the semantic label of the [counterText].
+  final String? semanticCounterText;
+
+  /// Typically set to true when the [InputDecorator] contains a multiline
+  /// [TextField] ([TextField.maxLines] is null or > 1) to override the default
+  /// behavior of aligning the label with the center of the [TextField].
+  ///
+  /// Defaults to false.
+  final bool? alignLabelWithHint;
+
+  /// Creates a copy of this input decoration with the given fields replaced
+  /// by the new values.
+  InputDecoration copyWith({
+    Widget? icon,
+    String? labelText,
+    TextStyle? labelStyle,
+    String? helperText,
+    TextStyle? helperStyle,
+    int? helperMaxLines,
+    String? hintText,
+    TextStyle? hintStyle,
+    int? hintMaxLines,
+    String? errorText,
+    TextStyle? errorStyle,
+    int? errorMaxLines,
+    bool? hasFloatingPlaceholder,
+    FloatingLabelBehavior? floatingLabelBehavior,
+    bool? isCollapsed,
+    bool? isDense,
+    EdgeInsetsGeometry? contentPadding,
+    Widget? prefixIcon,
+    Widget? prefix,
+    String? prefixText,
+    BoxConstraints? prefixIconConstraints,
+    TextStyle? prefixStyle,
+    Widget? suffixIcon,
+    Widget? suffix,
+    String? suffixText,
+    TextStyle? suffixStyle,
+    BoxConstraints? suffixIconConstraints,
+    Widget? counter,
+    String? counterText,
+    TextStyle? counterStyle,
+    bool? filled,
+    Color? fillColor,
+    Color? focusColor,
+    Color? hoverColor,
+    InputBorder? errorBorder,
+    InputBorder? focusedBorder,
+    InputBorder? focusedErrorBorder,
+    InputBorder? disabledBorder,
+    InputBorder? enabledBorder,
+    InputBorder? border,
+    bool? enabled,
+    String? semanticCounterText,
+    bool? alignLabelWithHint,
+  }) {
+    return InputDecoration(
+      icon: icon ?? this.icon,
+      labelText: labelText ?? this.labelText,
+      labelStyle: labelStyle ?? this.labelStyle,
+      helperText: helperText ?? this.helperText,
+      helperStyle: helperStyle ?? this.helperStyle,
+      helperMaxLines : helperMaxLines ?? this.helperMaxLines,
+      hintText: hintText ?? this.hintText,
+      hintStyle: hintStyle ?? this.hintStyle,
+      hintMaxLines: hintMaxLines ?? this.hintMaxLines,
+      errorText: errorText ?? this.errorText,
+      errorStyle: errorStyle ?? this.errorStyle,
+      errorMaxLines: errorMaxLines ?? this.errorMaxLines,
+      hasFloatingPlaceholder: hasFloatingPlaceholder ?? this.hasFloatingPlaceholder,
+      floatingLabelBehavior: floatingLabelBehavior ?? this.floatingLabelBehavior,
+      isCollapsed: isCollapsed ?? this.isCollapsed,
+      isDense: isDense ?? this.isDense,
+      contentPadding: contentPadding ?? this.contentPadding,
+      prefixIcon: prefixIcon ?? this.prefixIcon,
+      prefix: prefix ?? this.prefix,
+      prefixText: prefixText ?? this.prefixText,
+      prefixStyle: prefixStyle ?? this.prefixStyle,
+      prefixIconConstraints: prefixIconConstraints ?? this.prefixIconConstraints,
+      suffixIcon: suffixIcon ?? this.suffixIcon,
+      suffix: suffix ?? this.suffix,
+      suffixText: suffixText ?? this.suffixText,
+      suffixStyle: suffixStyle ?? this.suffixStyle,
+      suffixIconConstraints: suffixIconConstraints ?? this.suffixIconConstraints,
+      counter: counter ?? this.counter,
+      counterText: counterText ?? this.counterText,
+      counterStyle: counterStyle ?? this.counterStyle,
+      filled: filled ?? this.filled,
+      fillColor: fillColor ?? this.fillColor,
+      focusColor: focusColor ?? this.focusColor,
+      hoverColor: hoverColor ?? this.hoverColor,
+      errorBorder: errorBorder ?? this.errorBorder,
+      focusedBorder: focusedBorder ?? this.focusedBorder,
+      focusedErrorBorder: focusedErrorBorder ?? this.focusedErrorBorder,
+      disabledBorder: disabledBorder ?? this.disabledBorder,
+      enabledBorder: enabledBorder ?? this.enabledBorder,
+      border: border ?? this.border,
+      enabled: enabled ?? this.enabled,
+      semanticCounterText: semanticCounterText ?? this.semanticCounterText,
+      alignLabelWithHint: alignLabelWithHint ?? this.alignLabelWithHint,
+    );
+  }
+
+  /// Used by widgets like [TextField] and [InputDecorator] to create a new
+  /// [InputDecoration] with default values taken from the [theme].
+  ///
+  /// Only null valued properties from this [InputDecoration] are replaced
+  /// by the corresponding values from [theme].
+  InputDecoration applyDefaults(InputDecorationTheme theme) {
+    return copyWith(
+      labelStyle: labelStyle ?? theme.labelStyle,
+      helperStyle: helperStyle ?? theme.helperStyle,
+      helperMaxLines : helperMaxLines ?? theme.helperMaxLines,
+      hintStyle: hintStyle ?? theme.hintStyle,
+      errorStyle: errorStyle ?? theme.errorStyle,
+      errorMaxLines: errorMaxLines ?? theme.errorMaxLines,
+      hasFloatingPlaceholder: hasFloatingPlaceholder,
+      floatingLabelBehavior: floatingLabelBehavior ?? theme.floatingLabelBehavior,
+      isCollapsed: isCollapsed,
+      isDense: isDense ?? theme.isDense,
+      contentPadding: contentPadding ?? theme.contentPadding,
+      prefixStyle: prefixStyle ?? theme.prefixStyle,
+      suffixStyle: suffixStyle ?? theme.suffixStyle,
+      counterStyle: counterStyle ?? theme.counterStyle,
+      filled: filled ?? theme.filled,
+      fillColor: fillColor ?? theme.fillColor,
+      focusColor: focusColor ?? theme.focusColor,
+      hoverColor: hoverColor ?? theme.hoverColor,
+      errorBorder: errorBorder ?? theme.errorBorder,
+      focusedBorder: focusedBorder ?? theme.focusedBorder,
+      focusedErrorBorder: focusedErrorBorder ?? theme.focusedErrorBorder,
+      disabledBorder: disabledBorder ?? theme.disabledBorder,
+      enabledBorder: enabledBorder ?? theme.enabledBorder,
+      border: border ?? theme.border,
+      alignLabelWithHint: alignLabelWithHint ?? theme.alignLabelWithHint,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is InputDecoration
+        && other.icon == icon
+        && other.labelText == labelText
+        && other.labelStyle == labelStyle
+        && other.helperText == helperText
+        && other.helperStyle == helperStyle
+        && other.helperMaxLines == helperMaxLines
+        && other.hintText == hintText
+        && other.hintStyle == hintStyle
+        && other.hintMaxLines == hintMaxLines
+        && other.errorText == errorText
+        && other.errorStyle == errorStyle
+        && other.errorMaxLines == errorMaxLines
+        && other.hasFloatingPlaceholder == hasFloatingPlaceholder
+        && other.floatingLabelBehavior == floatingLabelBehavior
+        && other.isDense == isDense
+        && other.contentPadding == contentPadding
+        && other.isCollapsed == isCollapsed
+        && other.prefixIcon == prefixIcon
+        && other.prefix == prefix
+        && other.prefixText == prefixText
+        && other.prefixStyle == prefixStyle
+        && other.prefixIconConstraints == prefixIconConstraints
+        && other.suffixIcon == suffixIcon
+        && other.suffix == suffix
+        && other.suffixText == suffixText
+        && other.suffixStyle == suffixStyle
+        && other.suffixIconConstraints == suffixIconConstraints
+        && other.counter == counter
+        && other.counterText == counterText
+        && other.counterStyle == counterStyle
+        && other.filled == filled
+        && other.fillColor == fillColor
+        && other.focusColor == focusColor
+        && other.hoverColor == hoverColor
+        && other.errorBorder == errorBorder
+        && other.focusedBorder == focusedBorder
+        && other.focusedErrorBorder == focusedErrorBorder
+        && other.disabledBorder == disabledBorder
+        && other.enabledBorder == enabledBorder
+        && other.border == border
+        && other.enabled == enabled
+        && other.semanticCounterText == semanticCounterText
+        && other.alignLabelWithHint == alignLabelWithHint;
+  }
+
+  @override
+  int get hashCode {
+    final List<Object?> values = <Object?>[
+      icon,
+      labelText,
+      labelStyle,
+      helperText,
+      helperStyle,
+      helperMaxLines,
+      hintText,
+      hintStyle,
+      hintMaxLines,
+      errorText,
+      errorStyle,
+      errorMaxLines,
+      hasFloatingPlaceholder,
+      floatingLabelBehavior,
+      isDense,
+      contentPadding,
+      isCollapsed,
+      filled,
+      fillColor,
+      focusColor,
+      hoverColor,
+      border,
+      enabled,
+      prefixIcon,
+      prefix,
+      prefixText,
+      prefixStyle,
+      prefixIconConstraints,
+      suffixIcon,
+      suffix,
+      suffixText,
+      suffixStyle,
+      suffixIconConstraints,
+      counter,
+      counterText,
+      counterStyle,
+      errorBorder,
+      focusedBorder,
+      focusedErrorBorder,
+      disabledBorder,
+      enabledBorder,
+      border,
+      enabled,
+      semanticCounterText,
+      alignLabelWithHint,
+    ];
+    return hashList(values);
+  }
+
+  @override
+  String toString() {
+    final List<String> description = <String>[
+      if (icon != null) 'icon: $icon',
+      if (labelText != null) 'labelText: "$labelText"',
+      if (helperText != null) 'helperText: "$helperText"',
+      if (helperMaxLines != null) 'helperMaxLines: "$helperMaxLines"',
+      if (hintText != null) 'hintText: "$hintText"',
+      if (hintMaxLines != null) 'hintMaxLines: "$hintMaxLines"',
+      if (errorText != null) 'errorText: "$errorText"',
+      if (errorStyle != null) 'errorStyle: "$errorStyle"',
+      if (errorMaxLines != null) 'errorMaxLines: "$errorMaxLines"',
+      if (hasFloatingPlaceholder == false) 'hasFloatingPlaceholder: false',
+      if (floatingLabelBehavior != null) 'floatingLabelBehavior: $floatingLabelBehavior',
+      if (isDense ?? false) 'isDense: $isDense',
+      if (contentPadding != null) 'contentPadding: $contentPadding',
+      if (isCollapsed) 'isCollapsed: $isCollapsed',
+      if (prefixIcon != null) 'prefixIcon: $prefixIcon',
+      if (prefix != null) 'prefix: $prefix',
+      if (prefixText != null) 'prefixText: $prefixText',
+      if (prefixStyle != null) 'prefixStyle: $prefixStyle',
+      if (prefixIconConstraints != null) 'prefixIconConstraints: $prefixIconConstraints',
+      if (suffixIcon != null) 'suffixIcon: $suffixIcon',
+      if (suffix != null) 'suffix: $suffix',
+      if (suffixText != null) 'suffixText: $suffixText',
+      if (suffixStyle != null) 'suffixStyle: $suffixStyle',
+      if (suffixIconConstraints != null) 'suffixIconConstraints: $suffixIconConstraints',
+      if (counter != null) 'counter: $counter',
+      if (counterText != null) 'counterText: $counterText',
+      if (counterStyle != null) 'counterStyle: $counterStyle',
+      if (filled == true) 'filled: true', // filled == null same as filled == false
+      if (fillColor != null) 'fillColor: $fillColor',
+      if (focusColor != null) 'focusColor: $focusColor',
+      if (hoverColor != null) 'hoverColor: $hoverColor',
+      if (errorBorder != null) 'errorBorder: $errorBorder',
+      if (focusedBorder != null) 'focusedBorder: $focusedBorder',
+      if (focusedErrorBorder != null) 'focusedErrorBorder: $focusedErrorBorder',
+      if (disabledBorder != null) 'disabledBorder: $disabledBorder',
+      if (enabledBorder != null) 'enabledBorder: $enabledBorder',
+      if (border != null) 'border: $border',
+      if (!enabled) 'enabled: false',
+      if (semanticCounterText != null) 'semanticCounterText: $semanticCounterText',
+      if (alignLabelWithHint != null) 'alignLabelWithHint: $alignLabelWithHint',
+    ];
+    return 'InputDecoration(${description.join(', ')})';
+  }
+}
+
+/// Defines the default appearance of [InputDecorator]s.
+///
+/// This class is used to define the value of [ThemeData.inputDecorationTheme].
+/// The [InputDecorator], [TextField], and [TextFormField] widgets use
+/// the current input decoration theme to initialize null [InputDecoration]
+/// properties.
+///
+/// The [InputDecoration.applyDefaults] method is used to combine a input
+/// decoration theme with an [InputDecoration] object.
+@immutable
+class InputDecorationTheme with Diagnosticable {
+  /// Creates a value for [ThemeData.inputDecorationTheme] that
+  /// defines default values for [InputDecorator].
+  ///
+  /// The values of [isDense], [isCollapsed], [filled], and [border] must
+  /// not be null.
+  const InputDecorationTheme({
+    this.labelStyle,
+    this.helperStyle,
+    this.helperMaxLines,
+    this.hintStyle,
+    this.errorStyle,
+    this.errorMaxLines,
+    @Deprecated(
+      'Use floatingLabelBehavior instead. '
+      'This feature was deprecated after v1.13.2.'
+    )
+    this.hasFloatingPlaceholder = true,
+    this.floatingLabelBehavior = FloatingLabelBehavior.auto,
+    this.isDense = false,
+    this.contentPadding,
+    this.isCollapsed = false,
+    this.prefixStyle,
+    this.suffixStyle,
+    this.counterStyle,
+    this.filled = false,
+    this.fillColor,
+    this.focusColor,
+    this.hoverColor,
+    this.errorBorder,
+    this.focusedBorder,
+    this.focusedErrorBorder,
+    this.disabledBorder,
+    this.enabledBorder,
+    this.border,
+    this.alignLabelWithHint = false,
+  }) : assert(isDense != null),
+       assert(isCollapsed != null),
+       assert(filled != null),
+       assert(alignLabelWithHint != null),
+       assert(!(!hasFloatingPlaceholder && identical(floatingLabelBehavior, FloatingLabelBehavior.always)),
+        'hasFloatingPlaceholder=false conflicts with FloatingLabelBehavior.always');
+
+  /// The style to use for [InputDecoration.labelText] when the label is
+  /// above (i.e., vertically adjacent to) the input field.
+  ///
+  /// When the [InputDecoration.labelText] is on top of the input field, the
+  /// text uses the [hintStyle] instead.
+  ///
+  /// If null, defaults to a value derived from the base [TextStyle] for the
+  /// input field and the current [Theme].
+  final TextStyle? labelStyle;
+
+  /// The style to use for [InputDecoration.helperText].
+  final TextStyle? helperStyle;
+
+  /// The maximum number of lines the [InputDecoration.helperText] can occupy.
+  ///
+  /// Defaults to null, which means that the [InputDecoration.helperText] will
+  /// be limited to a single line with [TextOverflow.ellipsis].
+  ///
+  /// This value is passed along to the [Text.maxLines] attribute
+  /// of the [Text] widget used to display the helper.
+  ///
+  /// See also:
+  ///
+  ///  * [errorMaxLines], the equivalent but for the [InputDecoration.errorText].
+  final int? helperMaxLines;
+
+  /// The style to use for the [InputDecoration.hintText].
+  ///
+  /// Also used for the [InputDecoration.labelText] when the
+  /// [InputDecoration.labelText] is displayed on top of the input field (i.e.,
+  /// at the same location on the screen where text may be entered in the input
+  /// field).
+  ///
+  /// If null, defaults to a value derived from the base [TextStyle] for the
+  /// input field and the current [Theme].
+  final TextStyle? hintStyle;
+
+  /// The style to use for the [InputDecoration.errorText].
+  ///
+  /// If null, defaults of a value derived from the base [TextStyle] for the
+  /// input field and the current [Theme].
+  final TextStyle? errorStyle;
+
+  /// The maximum number of lines the [InputDecoration.errorText] can occupy.
+  ///
+  /// Defaults to null, which means that the [InputDecoration.errorText] will be
+  /// limited to a single line with [TextOverflow.ellipsis].
+  ///
+  /// This value is passed along to the [Text.maxLines] attribute
+  /// of the [Text] widget used to display the error.
+  ///
+  /// See also:
+  ///
+  ///  * [helperMaxLines], the equivalent but for the [InputDecoration.helperText].
+  final int? errorMaxLines;
+
+  /// Whether the placeholder text floats to become a label on focus.
+  ///
+  /// If this is false, the placeholder disappears when the input has focus or
+  /// text has been entered.
+  /// If this is true, the placeholder will rise to the top of the input when
+  /// the input has focus or text has been entered.
+  ///
+  /// Defaults to true.
+  @Deprecated(
+    'Use floatingLabelBehavior instead. '
+    'This feature was deprecated after v1.13.2.'
+  )
+  final bool hasFloatingPlaceholder;
+
+  /// {@macro flutter.material.inputDecoration.floatingLabelBehavior}
+  ///
+  /// Defaults to [FloatingLabelBehavior.auto].
+  final FloatingLabelBehavior floatingLabelBehavior;
+
+  /// Whether the input decorator's child is part of a dense form (i.e., uses
+  /// less vertical space).
+  ///
+  /// Defaults to false.
+  final bool isDense;
+
+  /// The padding for the input decoration's container.
+  ///
+  /// The decoration's container is the area which is filled if
+  /// [InputDecoration.filled] is true and bordered per the [border].
+  /// It's the area adjacent to [InputDecoration.icon] and above the
+  /// [InputDecoration.icon] and above the widgets that contain
+  /// [InputDecoration.helperText], [InputDecoration.errorText], and
+  /// [InputDecoration.counterText].
+  ///
+  /// By default the `contentPadding` reflects [isDense] and the type of the
+  /// [border]. If [isCollapsed] is true then `contentPadding` is
+  /// [EdgeInsets.zero].
+  final EdgeInsetsGeometry? contentPadding;
+
+  /// Whether the decoration is the same size as the input field.
+  ///
+  /// A collapsed decoration cannot have [InputDecoration.labelText],
+  /// [InputDecoration.errorText], or an [InputDecoration.icon].
+  final bool isCollapsed;
+
+  /// The style to use for the [InputDecoration.prefixText].
+  ///
+  /// If null, defaults to the [hintStyle].
+  final TextStyle? prefixStyle;
+
+  /// The style to use for the [InputDecoration.suffixText].
+  ///
+  /// If null, defaults to the [hintStyle].
+  final TextStyle? suffixStyle;
+
+  /// The style to use for the [InputDecoration.counterText].
+  ///
+  /// If null, defaults to the [helperStyle].
+  final TextStyle? counterStyle;
+
+  /// If true the decoration's container is filled with [fillColor].
+  ///
+  /// Typically this field set to true if [border] is an
+  /// [UnderlineInputBorder].
+  ///
+  /// The decoration's container is the area, defined by the border's
+  /// [InputBorder.getOuterPath], which is filled if [filled] is
+  /// true and bordered per the [border].
+  ///
+  /// This property is false by default.
+  final bool filled;
+
+  /// The color to fill the decoration's container with, if [filled] is true.
+  ///
+  /// By default the fillColor is based on the current [Theme].
+  ///
+  /// The decoration's container is the area, defined by the border's
+  /// [InputBorder.getOuterPath], which is filled if [filled] is
+  /// true and bordered per the [border].
+  final Color? fillColor;
+
+  /// The color to blend with the decoration's [fillColor] with, if [filled] is
+  /// true and the container has the input focus.
+  ///
+  /// By default the [focusColor] is based on the current [Theme].
+  ///
+  /// The decoration's container is the area, defined by the border's
+  /// [InputBorder.getOuterPath], which is filled if [filled] is
+  /// true and bordered per the [border].
+  final Color? focusColor;
+
+  /// The color to blend with the decoration's [fillColor] with, if the
+  /// decoration is being hovered over by a mouse pointer.
+  ///
+  /// By default the [hoverColor] is based on the current [Theme].
+  ///
+  /// The decoration's container is the area, defined by the border's
+  /// [InputBorder.getOuterPath], which is filled if [filled] is
+  /// true and bordered per the [border].
+  ///
+  /// The container will be filled when hovered over even if [filled] is false.
+  final Color? hoverColor;
+
+  /// The border to display when the [InputDecorator] does not have the focus and
+  /// is showing an error.
+  ///
+  /// See also:
+  ///
+  ///  * [InputDecorator.isFocused], which is true if the [InputDecorator]'s child
+  ///    has the focus.
+  ///  * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null.
+  ///  * [border], for a description of where the [InputDecorator] border appears.
+  ///  * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal
+  ///    line at the bottom of the input decorator's container.
+  ///  * [OutlineInputBorder], an [InputDecorator] border which draws a
+  ///    rounded rectangle around the input decorator's container.
+  ///  * [InputBorder.none], which doesn't draw a border.
+  ///  * [focusedBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is null.
+  ///  * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [disabledBorder], displayed when [InputDecoration.enabled] is false
+  ///    and [InputDecoration.errorText] is null.
+  ///  * [enabledBorder], displayed when [InputDecoration.enabled] is true
+  ///    and [InputDecoration.errorText] is null.
+  final InputBorder? errorBorder;
+
+  /// The border to display when the [InputDecorator] has the focus and is not
+  /// showing an error.
+  ///
+  /// See also:
+  ///
+  ///  * [InputDecorator.isFocused], which is true if the [InputDecorator]'s child
+  ///    has the focus.
+  ///  * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null.
+  ///  * [border], for a description of where the [InputDecorator] border appears.
+  ///  * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal
+  ///    line at the bottom of the input decorator's container.
+  ///  * [OutlineInputBorder], an [InputDecorator] border which draws a
+  ///    rounded rectangle around the input decorator's container.
+  ///  * [InputBorder.none], which doesn't draw a border.
+  ///  * [errorBorder], displayed when [InputDecorator.isFocused] is false
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [disabledBorder], displayed when [InputDecoration.enabled] is false
+  ///    and [InputDecoration.errorText] is null.
+  ///  * [enabledBorder], displayed when [InputDecoration.enabled] is true
+  ///    and [InputDecoration.errorText] is null.
+  final InputBorder? focusedBorder;
+
+  /// The border to display when the [InputDecorator] has the focus and is
+  /// showing an error.
+  ///
+  /// See also:
+  ///
+  ///  * [InputDecorator.isFocused], which is true if the [InputDecorator]'s child
+  ///    has the focus.
+  ///  * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null.
+  ///  * [border], for a description of where the [InputDecorator] border appears.
+  ///  * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal
+  ///    line at the bottom of the input decorator's container.
+  ///  * [OutlineInputBorder], an [InputDecorator] border which draws a
+  ///    rounded rectangle around the input decorator's container.
+  ///  * [InputBorder.none], which doesn't draw a border.
+  ///  * [errorBorder], displayed when [InputDecorator.isFocused] is false
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [focusedBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is null.
+  ///  * [disabledBorder], displayed when [InputDecoration.enabled] is false
+  ///    and [InputDecoration.errorText] is null.
+  ///  * [enabledBorder], displayed when [InputDecoration.enabled] is true
+  ///    and [InputDecoration.errorText] is null.
+  final InputBorder? focusedErrorBorder;
+
+  /// The border to display when the [InputDecorator] is disabled and is not
+  /// showing an error.
+  ///
+  /// See also:
+  ///
+  ///  * [InputDecoration.enabled], which is false if the [InputDecorator] is disabled.
+  ///  * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null.
+  ///  * [border], for a description of where the [InputDecorator] border appears.
+  ///  * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal
+  ///    line at the bottom of the input decorator's container.
+  ///  * [OutlineInputBorder], an [InputDecorator] border which draws a
+  ///    rounded rectangle around the input decorator's container.
+  ///  * [InputBorder.none], which doesn't draw a border.
+  ///  * [errorBorder], displayed when [InputDecorator.isFocused] is false
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [focusedBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is null.
+  ///  * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [enabledBorder], displayed when [InputDecoration.enabled] is true
+  ///    and [InputDecoration.errorText] is null.
+  final InputBorder? disabledBorder;
+
+  /// The border to display when the [InputDecorator] is enabled and is not
+  /// showing an error.
+  ///
+  /// See also:
+  ///
+  ///  * [InputDecoration.enabled], which is false if the [InputDecorator] is disabled.
+  ///  * [InputDecoration.errorText], the error shown by the [InputDecorator], if non-null.
+  ///  * [border], for a description of where the [InputDecorator] border appears.
+  ///  * [UnderlineInputBorder], an [InputDecorator] border which draws a horizontal
+  ///    line at the bottom of the input decorator's container.
+  ///  * [OutlineInputBorder], an [InputDecorator] border which draws a
+  ///    rounded rectangle around the input decorator's container.
+  ///  * [InputBorder.none], which doesn't draw a border.
+  ///  * [errorBorder], displayed when [InputDecorator.isFocused] is false
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [focusedBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is null.
+  ///  * [focusedErrorBorder], displayed when [InputDecorator.isFocused] is true
+  ///    and [InputDecoration.errorText] is non-null.
+  ///  * [disabledBorder], displayed when [InputDecoration.enabled] is false
+  ///    and [InputDecoration.errorText] is null.
+  final InputBorder? enabledBorder;
+
+  /// The shape of the border to draw around the decoration's container.
+  ///
+  /// The decoration's container is the area which is filled if [filled] is
+  /// true and bordered per the [border]. It's the area adjacent to
+  /// [InputDecoration.icon] and above the widgets that contain
+  /// [InputDecoration.helperText], [InputDecoration.errorText], and
+  /// [InputDecoration.counterText].
+  ///
+  /// The border's bounds, i.e. the value of `border.getOuterPath()`, define
+  /// the area to be filled.
+  ///
+  /// This property is only used when the appropriate one of [errorBorder],
+  /// [focusedBorder], [focusedErrorBorder], [disabledBorder], or [enabledBorder]
+  /// is not specified. This border's [InputBorder.borderSide] property is
+  /// configured by the InputDecorator, depending on the values of
+  /// [InputDecoration.errorText], [InputDecoration.enabled],
+  /// [InputDecorator.isFocused] and the current [Theme].
+  ///
+  /// Typically one of [UnderlineInputBorder] or [OutlineInputBorder].
+  /// If null, InputDecorator's default is `const UnderlineInputBorder()`.
+  ///
+  /// See also:
+  ///
+  ///  * [InputBorder.none], which doesn't draw a border.
+  ///  * [UnderlineInputBorder], which draws a horizontal line at the
+  ///    bottom of the input decorator's container.
+  ///  * [OutlineInputBorder], an [InputDecorator] border which draws a
+  ///    rounded rectangle around the input decorator's container.
+  final InputBorder? border;
+
+  /// Typically set to true when the [InputDecorator] contains a multiline
+  /// [TextField] ([TextField.maxLines] is null or > 1) to override the default
+  /// behavior of aligning the label with the center of the [TextField].
+  final bool alignLabelWithHint;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  InputDecorationTheme copyWith({
+    TextStyle? labelStyle,
+    TextStyle? helperStyle,
+    int? helperMaxLines,
+    TextStyle? hintStyle,
+    TextStyle? errorStyle,
+    int? errorMaxLines,
+    @Deprecated(
+      'Use floatingLabelBehavior instead. '
+      'This feature was deprecated after v1.13.2.'
+    )
+    bool? hasFloatingPlaceholder,
+    FloatingLabelBehavior? floatingLabelBehavior,
+    bool? isDense,
+    EdgeInsetsGeometry? contentPadding,
+    bool? isCollapsed,
+    TextStyle? prefixStyle,
+    TextStyle? suffixStyle,
+    TextStyle? counterStyle,
+    bool? filled,
+    Color? fillColor,
+    Color? focusColor,
+    Color? hoverColor,
+    InputBorder? errorBorder,
+    InputBorder? focusedBorder,
+    InputBorder? focusedErrorBorder,
+    InputBorder? disabledBorder,
+    InputBorder? enabledBorder,
+    InputBorder? border,
+    bool? alignLabelWithHint,
+  }) {
+    return InputDecorationTheme(
+      labelStyle: labelStyle ?? this.labelStyle,
+      helperStyle: helperStyle ?? this.helperStyle,
+      helperMaxLines: helperMaxLines ?? this.helperMaxLines,
+      hintStyle: hintStyle ?? this.hintStyle,
+      errorStyle: errorStyle ?? this.errorStyle,
+      errorMaxLines: errorMaxLines ?? this.errorMaxLines,
+      hasFloatingPlaceholder: hasFloatingPlaceholder ?? this.hasFloatingPlaceholder,
+      floatingLabelBehavior: floatingLabelBehavior ?? this.floatingLabelBehavior,
+      isDense: isDense ?? this.isDense,
+      contentPadding: contentPadding ?? this.contentPadding,
+      isCollapsed: isCollapsed ?? this.isCollapsed,
+      prefixStyle: prefixStyle ?? this.prefixStyle,
+      suffixStyle: suffixStyle ?? this.suffixStyle,
+      counterStyle: counterStyle ?? this.counterStyle,
+      filled: filled ?? this.filled,
+      fillColor: fillColor ?? this.fillColor,
+      focusColor: focusColor ?? this.focusColor,
+      hoverColor: hoverColor ?? this.hoverColor,
+      errorBorder: errorBorder ?? this.errorBorder,
+      focusedBorder: focusedBorder ?? this.focusedBorder,
+      focusedErrorBorder: focusedErrorBorder ?? this.focusedErrorBorder,
+      disabledBorder: disabledBorder ?? this.disabledBorder,
+      enabledBorder: enabledBorder ?? this.enabledBorder,
+      border: border ?? this.border,
+      alignLabelWithHint: alignLabelWithHint ?? this.alignLabelWithHint,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashList(<dynamic>[
+      labelStyle,
+      helperStyle,
+      helperMaxLines,
+      hintStyle,
+      errorStyle,
+      errorMaxLines,
+      hasFloatingPlaceholder,
+      floatingLabelBehavior,
+      isDense,
+      contentPadding,
+      isCollapsed,
+      prefixStyle,
+      suffixStyle,
+      counterStyle,
+      filled,
+      fillColor,
+      focusColor,
+      hoverColor,
+      errorBorder,
+      focusedBorder,
+      focusedErrorBorder,
+      disabledBorder,
+      enabledBorder,
+      border,
+      alignLabelWithHint,
+    ]);
+  }
+
+  @override
+  bool operator==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is InputDecorationTheme
+        && other.labelStyle == labelStyle
+        && other.helperStyle == helperStyle
+        && other.helperMaxLines == helperMaxLines
+        && other.hintStyle == hintStyle
+        && other.errorStyle == errorStyle
+        && other.errorMaxLines == errorMaxLines
+        && other.isDense == isDense
+        && other.contentPadding == contentPadding
+        && other.isCollapsed == isCollapsed
+        && other.prefixStyle == prefixStyle
+        && other.suffixStyle == suffixStyle
+        && other.counterStyle == counterStyle
+        && other.floatingLabelBehavior == floatingLabelBehavior
+        && other.filled == filled
+        && other.fillColor == fillColor
+        && other.focusColor == focusColor
+        && other.hoverColor == hoverColor
+        && other.errorBorder == errorBorder
+        && other.focusedBorder == focusedBorder
+        && other.focusedErrorBorder == focusedErrorBorder
+        && other.disabledBorder == disabledBorder
+        && other.enabledBorder == enabledBorder
+        && other.border == border
+        && other.alignLabelWithHint == alignLabelWithHint
+        && other.disabledBorder == disabledBorder;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    const InputDecorationTheme defaultTheme = InputDecorationTheme();
+    properties.add(DiagnosticsProperty<TextStyle>('labelStyle', labelStyle, defaultValue: defaultTheme.labelStyle));
+    properties.add(DiagnosticsProperty<TextStyle>('helperStyle', helperStyle, defaultValue: defaultTheme.helperStyle));
+    properties.add(IntProperty('helperMaxLines', helperMaxLines, defaultValue: defaultTheme.helperMaxLines));
+    properties.add(DiagnosticsProperty<TextStyle>('hintStyle', hintStyle, defaultValue: defaultTheme.hintStyle));
+    properties.add(DiagnosticsProperty<TextStyle>('errorStyle', errorStyle, defaultValue: defaultTheme.errorStyle));
+    properties.add(IntProperty('errorMaxLines', errorMaxLines, defaultValue: defaultTheme.errorMaxLines));
+    properties.add(DiagnosticsProperty<bool>('hasFloatingPlaceholder', hasFloatingPlaceholder, defaultValue: defaultTheme.hasFloatingPlaceholder));
+    properties.add(DiagnosticsProperty<FloatingLabelBehavior>('floatingLabelBehavior', floatingLabelBehavior, defaultValue: defaultTheme.floatingLabelBehavior));
+    properties.add(DiagnosticsProperty<bool>('isDense', isDense, defaultValue: defaultTheme.isDense));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('contentPadding', contentPadding, defaultValue: defaultTheme.contentPadding));
+    properties.add(DiagnosticsProperty<bool>('isCollapsed', isCollapsed, defaultValue: defaultTheme.isCollapsed));
+    properties.add(DiagnosticsProperty<TextStyle>('prefixStyle', prefixStyle, defaultValue: defaultTheme.prefixStyle));
+    properties.add(DiagnosticsProperty<TextStyle>('suffixStyle', suffixStyle, defaultValue: defaultTheme.suffixStyle));
+    properties.add(DiagnosticsProperty<TextStyle>('counterStyle', counterStyle, defaultValue: defaultTheme.counterStyle));
+    properties.add(DiagnosticsProperty<bool>('filled', filled, defaultValue: defaultTheme.filled));
+    properties.add(ColorProperty('fillColor', fillColor, defaultValue: defaultTheme.fillColor));
+    properties.add(ColorProperty('focusColor', focusColor, defaultValue: defaultTheme.focusColor));
+    properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: defaultTheme.hoverColor));
+    properties.add(DiagnosticsProperty<InputBorder>('errorBorder', errorBorder, defaultValue: defaultTheme.errorBorder));
+    properties.add(DiagnosticsProperty<InputBorder>('focusedBorder', focusedBorder, defaultValue: defaultTheme.focusedErrorBorder));
+    properties.add(DiagnosticsProperty<InputBorder>('focusedErrorBorder', focusedErrorBorder, defaultValue: defaultTheme.focusedErrorBorder));
+    properties.add(DiagnosticsProperty<InputBorder>('disabledBorder', disabledBorder, defaultValue: defaultTheme.disabledBorder));
+    properties.add(DiagnosticsProperty<InputBorder>('enabledBorder', enabledBorder, defaultValue: defaultTheme.enabledBorder));
+    properties.add(DiagnosticsProperty<InputBorder>('border', border, defaultValue: defaultTheme.border));
+    properties.add(DiagnosticsProperty<bool>('alignLabelWithHint', alignLabelWithHint, defaultValue: defaultTheme.alignLabelWithHint));
+  }
+}
diff --git a/lib/src/material/list_tile.dart b/lib/src/material/list_tile.dart
new file mode 100644
index 0000000..b2b7736
--- /dev/null
+++ b/lib/src/material/list_tile.dart
@@ -0,0 +1,1859 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/widgets.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+import 'colors.dart';
+import 'constants.dart';
+import 'debug.dart';
+import 'divider.dart';
+import 'ink_well.dart';
+import 'material_state.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// Defines the title font used for [ListTile] descendants of a [ListTileTheme].
+///
+/// List tiles that appear in a [Drawer] use the theme's [TextTheme.bodyText1]
+/// text style, which is a little smaller than the theme's [TextTheme.subtitle1]
+/// text style, which is used by default.
+enum ListTileStyle {
+  /// Use a title font that's appropriate for a [ListTile] in a list.
+  list,
+
+  /// Use a title font that's appropriate for a [ListTile] that appears in a [Drawer].
+  drawer,
+}
+
+/// An inherited widget that defines color and style parameters for [ListTile]s
+/// in this widget's subtree.
+///
+/// Values specified here are used for [ListTile] properties that are not given
+/// an explicit non-null value.
+///
+/// The [Drawer] widget specifies a tile theme for its children which sets
+/// [style] to [ListTileStyle.drawer].
+class ListTileTheme extends InheritedTheme {
+  /// Creates a list tile theme that controls the color and style parameters for
+  /// [ListTile]s.
+  const ListTileTheme({
+    Key? key,
+    this.dense = false,
+    this.shape,
+    this.style = ListTileStyle.list,
+    this.selectedColor,
+    this.iconColor,
+    this.textColor,
+    this.contentPadding,
+    this.tileColor,
+    this.selectedTileColor,
+    this.enableFeedback,
+    this.horizontalTitleGap,
+    this.minVerticalPadding,
+    this.minLeadingWidth,
+    required Widget child,
+  }) : super(key: key, child: child);
+
+  /// Creates a list tile theme that controls the color and style parameters for
+  /// [ListTile]s, and merges in the current list tile theme, if any.
+  ///
+  /// The [child] argument must not be null.
+  static Widget merge({
+    Key? key,
+    bool? dense,
+    ShapeBorder? shape,
+    ListTileStyle? style,
+    Color? selectedColor,
+    Color? iconColor,
+    Color? textColor,
+    EdgeInsetsGeometry? contentPadding,
+    Color? tileColor,
+    Color? selectedTileColor,
+    bool? enableFeedback,
+    double? horizontalTitleGap,
+    double? minVerticalPadding,
+    double? minLeadingWidth,
+    required Widget child,
+  }) {
+    assert(child != null);
+    return Builder(
+      builder: (BuildContext context) {
+        final ListTileTheme parent = ListTileTheme.of(context);
+        return ListTileTheme(
+          key: key,
+          dense: dense ?? parent.dense,
+          shape: shape ?? parent.shape,
+          style: style ?? parent.style,
+          selectedColor: selectedColor ?? parent.selectedColor,
+          iconColor: iconColor ?? parent.iconColor,
+          textColor: textColor ?? parent.textColor,
+          contentPadding: contentPadding ?? parent.contentPadding,
+          tileColor: tileColor ?? parent.tileColor,
+          selectedTileColor: selectedTileColor ?? parent.selectedTileColor,
+          enableFeedback: enableFeedback ?? parent.enableFeedback,
+          horizontalTitleGap: horizontalTitleGap ?? parent.horizontalTitleGap,
+          minVerticalPadding: minVerticalPadding ?? parent.minVerticalPadding,
+          minLeadingWidth: minLeadingWidth ?? parent.minLeadingWidth,
+          child: child,
+        );
+      },
+    );
+  }
+
+  /// If true then [ListTile]s will have the vertically dense layout.
+  final bool dense;
+
+  /// {@template flutter.material.ListTileTheme.shape}
+  /// If specified, [shape] defines the shape of the [ListTile]'s [InkWell] border.
+  /// {@endtemplate}
+  final ShapeBorder? shape;
+
+  /// If specified, [style] defines the font used for [ListTile] titles.
+  final ListTileStyle style;
+
+  /// If specified, the color used for icons and text when a [ListTile] is selected.
+  final Color? selectedColor;
+
+  /// If specified, the icon color used for enabled [ListTile]s that are not selected.
+  final Color? iconColor;
+
+  /// If specified, the text color used for enabled [ListTile]s that are not selected.
+  final Color? textColor;
+
+  /// The tile's internal padding.
+  ///
+  /// Insets a [ListTile]'s contents: its [ListTile.leading], [ListTile.title],
+  /// [ListTile.subtitle], and [ListTile.trailing] widgets.
+  final EdgeInsetsGeometry? contentPadding;
+
+  /// If specified, defines the background color for `ListTile` when
+  /// [ListTile.selected] is false.
+  ///
+  /// If [ListTile.tileColor] is provided, [tileColor] is ignored.
+  final Color? tileColor;
+
+  /// If specified, defines the background color for `ListTile` when
+  /// [ListTile.selected] is true.
+  ///
+  /// If [ListTile.selectedTileColor] is provided, [selectedTileColor] is ignored.
+  final Color? selectedTileColor;
+
+  /// The horizontal gap between the titles and the leading/trailing widgets.
+  ///
+  /// If specified, overrides the default value of [ListTile.horizontalTitleGap].
+  final double? horizontalTitleGap;
+
+  /// The minimum padding on the top and bottom of the title and subtitle widgets.
+  ///
+  /// If specified, overrides the default value of [ListTile.minVerticalPadding].
+  final double? minVerticalPadding;
+
+  /// The minimum width allocated for the [ListTile.leading] widget.
+  ///
+  /// If specified, overrides the default value of [ListTile.minLeadingWidth].
+  final double? minLeadingWidth;
+
+  /// If specified, defines the feedback property for `ListTile`.
+  ///
+  /// If [ListTile.enableFeedback] is provided, [enableFeedback] is ignored.
+  final bool? enableFeedback;
+
+  /// The closest instance of this class that encloses the given context.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// ListTileTheme theme = ListTileTheme.of(context);
+  /// ```
+  static ListTileTheme of(BuildContext context) {
+    final ListTileTheme? result = context.dependOnInheritedWidgetOfExactType<ListTileTheme>();
+    return result ?? const ListTileTheme(child: SizedBox());
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return ListTileTheme(
+      dense: dense,
+      shape: shape,
+      style: style,
+      selectedColor: selectedColor,
+      iconColor: iconColor,
+      textColor: textColor,
+      contentPadding: contentPadding,
+      tileColor: tileColor,
+      selectedTileColor: selectedTileColor,
+      enableFeedback: enableFeedback,
+      horizontalTitleGap: horizontalTitleGap,
+      minVerticalPadding: minVerticalPadding,
+      minLeadingWidth: minLeadingWidth,
+      child: child,
+    );
+  }
+
+  @override
+  bool updateShouldNotify(ListTileTheme oldWidget) {
+    return dense != oldWidget.dense
+        || shape != oldWidget.shape
+        || style != oldWidget.style
+        || selectedColor != oldWidget.selectedColor
+        || iconColor != oldWidget.iconColor
+        || textColor != oldWidget.textColor
+        || contentPadding != oldWidget.contentPadding
+        || tileColor != oldWidget.tileColor
+        || selectedTileColor != oldWidget.selectedTileColor
+        || enableFeedback != oldWidget.enableFeedback
+        || horizontalTitleGap != oldWidget.horizontalTitleGap
+        || minVerticalPadding != oldWidget.minVerticalPadding
+        || minLeadingWidth != oldWidget.minLeadingWidth;
+  }
+}
+
+/// Where to place the control in widgets that use [ListTile] to position a
+/// control next to a label.
+///
+/// See also:
+///
+///  * [CheckboxListTile], which combines a [ListTile] with a [Checkbox].
+///  * [RadioListTile], which combines a [ListTile] with a [Radio] button.
+///  * [SwitchListTile], which combines a [ListTile] with a [Switch].
+enum ListTileControlAffinity {
+  /// Position the control on the leading edge, and the secondary widget, if
+  /// any, on the trailing edge.
+  leading,
+
+  /// Position the control on the trailing edge, and the secondary widget, if
+  /// any, on the leading edge.
+  trailing,
+
+  /// Position the control relative to the text in the fashion that is typical
+  /// for the current platform, and place the secondary widget on the opposite
+  /// side.
+  platform,
+}
+
+/// A single fixed-height row that typically contains some text as well as
+/// a leading or trailing icon.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=l8dj0yPBvgQ}
+///
+/// A list tile contains one to three lines of text optionally flanked by icons or
+/// other widgets, such as check boxes. The icons (or other widgets) for the
+/// tile are defined with the [leading] and [trailing] parameters. The first
+/// line of text is not optional and is specified with [title]. The value of
+/// [subtitle], which _is_ optional, will occupy the space allocated for an
+/// additional line of text, or two lines if [isThreeLine] is true. If [dense]
+/// is true then the overall height of this tile and the size of the
+/// [DefaultTextStyle]s that wrap the [title] and [subtitle] widget are reduced.
+///
+/// It is the responsibility of the caller to ensure that [title] does not wrap,
+/// and to ensure that [subtitle] doesn't wrap (if [isThreeLine] is false) or
+/// wraps to two lines (if it is true).
+///
+/// The heights of the [leading] and [trailing] widgets are constrained
+/// according to the
+/// [Material spec](https://material.io/design/components/lists.html).
+/// An exception is made for one-line ListTiles for accessibility. Please
+/// see the example below to see how to adhere to both Material spec and
+/// accessibility requirements.
+///
+/// Note that [leading] and [trailing] widgets can expand as far as they wish
+/// horizontally, so ensure that they are properly constrained.
+///
+/// List tiles are typically used in [ListView]s, or arranged in [Column]s in
+/// [Drawer]s and [Card]s.
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// {@tool snippet}
+///
+/// This example uses a [ListView] to demonstrate different configurations of
+/// [ListTile]s in [Card]s.
+///
+/// ![Different variations of ListTile](https://flutter.github.io/assets-for-api-docs/assets/material/list_tile.png)
+///
+/// ```dart
+/// ListView(
+///   children: const <Widget>[
+///     Card(child: ListTile(title: Text('One-line ListTile'))),
+///     Card(
+///       child: ListTile(
+///         leading: FlutterLogo(),
+///         title: Text('One-line with leading widget'),
+///       ),
+///     ),
+///     Card(
+///       child: ListTile(
+///         title: Text('One-line with trailing widget'),
+///         trailing: Icon(Icons.more_vert),
+///       ),
+///     ),
+///     Card(
+///       child: ListTile(
+///         leading: FlutterLogo(),
+///         title: Text('One-line with both widgets'),
+///         trailing: Icon(Icons.more_vert),
+///       ),
+///     ),
+///     Card(
+///       child: ListTile(
+///         title: Text('One-line dense ListTile'),
+///         dense: true,
+///       ),
+///     ),
+///     Card(
+///       child: ListTile(
+///         leading: FlutterLogo(size: 56.0),
+///         title: Text('Two-line ListTile'),
+///         subtitle: Text('Here is a second line'),
+///         trailing: Icon(Icons.more_vert),
+///       ),
+///     ),
+///     Card(
+///       child: ListTile(
+///         leading: FlutterLogo(size: 72.0),
+///         title: Text('Three-line ListTile'),
+///         subtitle: Text(
+///           'A sufficiently long subtitle warrants three lines.'
+///         ),
+///         trailing: Icon(Icons.more_vert),
+///         isThreeLine: true,
+///       ),
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// To use a [ListTile] within a [Row], it needs to be wrapped in an
+/// [Expanded] widget. [ListTile] requires fixed width constraints,
+/// whereas a [Row] does not constrain its children.
+///
+/// ```dart
+/// Row(
+///   children: const <Widget>[
+///     Expanded(
+///       child: ListTile(
+///         leading: FlutterLogo(),
+///         title: Text('These ListTiles are expanded '),
+///       ),
+///     ),
+///     Expanded(
+///       child: ListTile(
+///         trailing: FlutterLogo(),
+///         title: Text('to fill the available space.'),
+///       ),
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// Tiles can be much more elaborate. Here is a tile which can be tapped, but
+/// which is disabled when the `_act` variable is not 2. When the tile is
+/// tapped, the whole row has an ink splash effect (see [InkWell]).
+///
+/// ```dart
+/// int _act = 1;
+/// // ...
+/// ListTile(
+///   leading: const Icon(Icons.flight_land),
+///   title: const Text("Trix's airplane"),
+///   subtitle: _act != 2 ? const Text('The airplane is only in Act II.') : null,
+///   enabled: _act == 2,
+///   onTap: () { /* react to the tile being tapped */ }
+/// )
+/// ```
+/// {@end-tool}
+///
+/// To be accessible, tappable [leading] and [trailing] widgets have to
+/// be at least 48x48 in size. However, to adhere to the Material spec,
+/// [trailing] and [leading] widgets in one-line ListTiles should visually be
+/// at most 32 ([dense]: true) or 40 ([dense]: false) in height, which may
+/// conflict with the accessibility requirement.
+///
+/// For this reason, a one-line ListTile allows the height of [leading]
+/// and [trailing] widgets to be constrained by the height of the ListTile.
+/// This allows for the creation of tappable [leading] and [trailing] widgets
+/// that are large enough, but it is up to the developer to ensure that
+/// their widgets follow the Material spec.
+///
+/// {@tool snippet}
+///
+/// Here is an example of a one-line, non-[dense] ListTile with a
+/// tappable leading widget that adheres to accessibility requirements and
+/// the Material spec. To adjust the use case below for a one-line, [dense]
+/// ListTile, adjust the vertical padding to 8.0.
+///
+/// ```dart
+/// ListTile(
+///   leading: GestureDetector(
+///     behavior: HitTestBehavior.translucent,
+///     onTap: () {},
+///     child: Container(
+///       width: 48,
+///       height: 48,
+///       padding: EdgeInsets.symmetric(vertical: 4.0),
+///       alignment: Alignment.center,
+///       child: CircleAvatar(),
+///     ),
+///   ),
+///   title: Text('title'),
+///   dense: false,
+/// ),
+/// ```
+/// {@end-tool}
+///
+/// ## The ListTile layout isn't exactly what I want
+///
+/// If the way ListTile pads and positions its elements isn't quite what
+/// you're looking for, it's easy to create custom list items with a
+/// combination of other widgets, such as [Row]s and [Column]s.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold}
+///
+/// Here is an example of a custom list item that resembles a YouTube-related
+/// video list item created with [Expanded] and [Container] widgets.
+///
+/// ![Custom list item a](https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_list_item_a.png)
+///
+/// ```dart preamble
+/// class CustomListItem extends StatelessWidget {
+///   const CustomListItem({
+///     required this.thumbnail,
+///     required this.title,
+///     required this.user,
+///     required this.viewCount,
+///   });
+///
+///   final Widget thumbnail;
+///   final String title;
+///   final String user;
+///   final int viewCount;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Padding(
+///       padding: const EdgeInsets.symmetric(vertical: 5.0),
+///       child: Row(
+///         crossAxisAlignment: CrossAxisAlignment.start,
+///         children: <Widget>[
+///           Expanded(
+///             flex: 2,
+///             child: thumbnail,
+///           ),
+///           Expanded(
+///             flex: 3,
+///             child: _VideoDescription(
+///               title: title,
+///               user: user,
+///               viewCount: viewCount,
+///             ),
+///           ),
+///           const Icon(
+///             Icons.more_vert,
+///             size: 16.0,
+///           ),
+///         ],
+///       ),
+///     );
+///   }
+/// }
+///
+/// class _VideoDescription extends StatelessWidget {
+///   const _VideoDescription({
+///     Key? key,
+///     required this.title,
+///     required this.user,
+///     required this.viewCount,
+///   }) : super(key: key);
+///
+///   final String title;
+///   final String user;
+///   final int viewCount;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Padding(
+///       padding: const EdgeInsets.fromLTRB(5.0, 0.0, 0.0, 0.0),
+///       child: Column(
+///         crossAxisAlignment: CrossAxisAlignment.start,
+///         children: <Widget>[
+///           Text(
+///             title,
+///             style: const TextStyle(
+///               fontWeight: FontWeight.w500,
+///               fontSize: 14.0,
+///             ),
+///           ),
+///           const Padding(padding: EdgeInsets.symmetric(vertical: 2.0)),
+///           Text(
+///             user,
+///             style: const TextStyle(fontSize: 10.0),
+///           ),
+///           const Padding(padding: EdgeInsets.symmetric(vertical: 1.0)),
+///           Text(
+///             '$viewCount views',
+///             style: const TextStyle(fontSize: 10.0),
+///           ),
+///         ],
+///       ),
+///     );
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return ListView(
+///     padding: const EdgeInsets.all(8.0),
+///     itemExtent: 106.0,
+///     children: <CustomListItem>[
+///       CustomListItem(
+///         user: 'Flutter',
+///         viewCount: 999000,
+///         thumbnail: Container(
+///           decoration: const BoxDecoration(color: Colors.blue),
+///         ),
+///         title: 'The Flutter YouTube Channel',
+///       ),
+///       CustomListItem(
+///         user: 'Dash',
+///         viewCount: 884000,
+///         thumbnail: Container(
+///           decoration: const BoxDecoration(color: Colors.yellow),
+///         ),
+///         title: 'Announcing Flutter 1.0',
+///       ),
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateless_widget_scaffold}
+///
+/// Here is an example of an article list item with multiline titles and
+/// subtitles. It utilizes [Row]s and [Column]s, as well as [Expanded] and
+/// [AspectRatio] widgets to organize its layout.
+///
+/// ![Custom list item b](https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_list_item_b.png)
+///
+/// ```dart preamble
+/// class _ArticleDescription extends StatelessWidget {
+///   _ArticleDescription({
+///     Key? key,
+///     required this.title,
+///     required this.subtitle,
+///     required this.author,
+///     required this.publishDate,
+///     required this.readDuration,
+///   }) : super(key: key);
+///
+///   final String title;
+///   final String subtitle;
+///   final String author;
+///   final String publishDate;
+///   final String readDuration;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Column(
+///       crossAxisAlignment: CrossAxisAlignment.start,
+///       children: <Widget>[
+///         Expanded(
+///           flex: 1,
+///           child: Column(
+///             crossAxisAlignment: CrossAxisAlignment.start,
+///             children: <Widget>[
+///               Text(
+///                 '$title',
+///                 maxLines: 2,
+///                 overflow: TextOverflow.ellipsis,
+///                 style: const TextStyle(
+///                   fontWeight: FontWeight.bold,
+///                 ),
+///               ),
+///               const Padding(padding: EdgeInsets.only(bottom: 2.0)),
+///               Text(
+///                 '$subtitle',
+///                 maxLines: 2,
+///                 overflow: TextOverflow.ellipsis,
+///                 style: const TextStyle(
+///                   fontSize: 12.0,
+///                   color: Colors.black54,
+///                 ),
+///               ),
+///             ],
+///           ),
+///         ),
+///         Expanded(
+///           flex: 1,
+///           child: Column(
+///             crossAxisAlignment: CrossAxisAlignment.start,
+///             mainAxisAlignment: MainAxisAlignment.end,
+///             children: <Widget>[
+///               Text(
+///                 '$author',
+///                 style: const TextStyle(
+///                   fontSize: 12.0,
+///                   color: Colors.black87,
+///                 ),
+///               ),
+///               Text(
+///                 '$publishDate - $readDuration',
+///                 style: const TextStyle(
+///                   fontSize: 12.0,
+///                   color: Colors.black54,
+///                 ),
+///               ),
+///             ],
+///           ),
+///         ),
+///       ],
+///     );
+///   }
+/// }
+///
+/// class CustomListItemTwo extends StatelessWidget {
+///   CustomListItemTwo({
+///     Key? key,
+///     required this.thumbnail,
+///     required this.title,
+///     required this.subtitle,
+///     required this.author,
+///     required this.publishDate,
+///     required this.readDuration,
+///   }) : super(key: key);
+///
+///   final Widget thumbnail;
+///   final String title;
+///   final String subtitle;
+///   final String author;
+///   final String publishDate;
+///   final String readDuration;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Padding(
+///       padding: const EdgeInsets.symmetric(vertical: 10.0),
+///       child: SizedBox(
+///         height: 100,
+///         child: Row(
+///           crossAxisAlignment: CrossAxisAlignment.start,
+///           children: <Widget>[
+///             AspectRatio(
+///               aspectRatio: 1.0,
+///               child: thumbnail,
+///             ),
+///             Expanded(
+///               child: Padding(
+///                 padding: const EdgeInsets.fromLTRB(20.0, 0.0, 2.0, 0.0),
+///                 child: _ArticleDescription(
+///                   title: title,
+///                   subtitle: subtitle,
+///                   author: author,
+///                   publishDate: publishDate,
+///                   readDuration: readDuration,
+///                 ),
+///               ),
+///             )
+///           ],
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return ListView(
+///     padding: const EdgeInsets.all(10.0),
+///     children: <Widget>[
+///       CustomListItemTwo(
+///         thumbnail: Container(
+///           decoration: const BoxDecoration(color: Colors.pink),
+///         ),
+///         title: 'Flutter 1.0 Launch',
+///         subtitle:
+///           'Flutter continues to improve and expand its horizons.'
+///           'This text should max out at two lines and clip',
+///         author: 'Dash',
+///         publishDate: 'Dec 28',
+///         readDuration: '5 mins',
+///       ),
+///       CustomListItemTwo(
+///         thumbnail: Container(
+///           decoration: const BoxDecoration(color: Colors.blue),
+///         ),
+///         title: 'Flutter 1.2 Release - Continual updates to the framework',
+///         subtitle: 'Flutter once again improves and makes updates.',
+///         author: 'Flutter',
+///         publishDate: 'Feb 26',
+///         readDuration: '12 mins',
+///       ),
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ListTileTheme], which defines visual properties for [ListTile]s.
+///  * [ListView], which can display an arbitrary number of [ListTile]s
+///    in a scrolling list.
+///  * [CircleAvatar], which shows an icon representing a person and is often
+///    used as the [leading] element of a ListTile.
+///  * [Card], which can be used with [Column] to show a few [ListTile]s.
+///  * [Divider], which can be used to separate [ListTile]s.
+///  * [ListTile.divideTiles], a utility for inserting [Divider]s in between [ListTile]s.
+///  * [CheckboxListTile], [RadioListTile], and [SwitchListTile], widgets
+///    that combine [ListTile] with other controls.
+///  * <https://material.io/design/components/lists.html>
+///  * Cookbook: [Use lists](https://flutter.dev/docs/cookbook/lists/basic-list)
+///  * Cookbook: [Implement swipe to dismiss](https://flutter.dev/docs/cookbook/gestures/dismissible)
+class ListTile extends StatelessWidget {
+  /// Creates a list tile.
+  ///
+  /// If [isThreeLine] is true, then [subtitle] must not be null.
+  ///
+  /// Requires one of its ancestors to be a [Material] widget.
+  const ListTile({
+    Key? key,
+    this.leading,
+    this.title,
+    this.subtitle,
+    this.trailing,
+    this.isThreeLine = false,
+    this.dense,
+    this.visualDensity,
+    this.shape,
+    this.contentPadding,
+    this.enabled = true,
+    this.onTap,
+    this.onLongPress,
+    this.mouseCursor,
+    this.selected = false,
+    this.focusColor,
+    this.hoverColor,
+    this.focusNode,
+    this.autofocus = false,
+    this.tileColor,
+    this.selectedTileColor,
+    this.enableFeedback,
+    this.horizontalTitleGap,
+    this.minVerticalPadding,
+    this.minLeadingWidth,
+  }) : assert(isThreeLine != null),
+       assert(enabled != null),
+       assert(selected != null),
+       assert(autofocus != null),
+       assert(!isThreeLine || subtitle != null),
+       super(key: key);
+
+  /// A widget to display before the title.
+  ///
+  /// Typically an [Icon] or a [CircleAvatar] widget.
+  final Widget? leading;
+
+  /// The primary content of the list tile.
+  ///
+  /// Typically a [Text] widget.
+  ///
+  /// This should not wrap. To enforce the single line limit, use
+  /// [Text.maxLines].
+  final Widget? title;
+
+  /// Additional content displayed below the title.
+  ///
+  /// Typically a [Text] widget.
+  ///
+  /// If [isThreeLine] is false, this should not wrap.
+  ///
+  /// If [isThreeLine] is true, this should be configured to take a maximum of
+  /// two lines. For example, you can use [Text.maxLines] to enforce the number
+  /// of lines.
+  ///
+  /// The subtitle's default [TextStyle] depends on [TextTheme.bodyText2] except
+  /// [TextStyle.color]. The [TextStyle.color] depends on the value of [enabled]
+  /// and [selected].
+  ///
+  /// When [enabled] is false, the text color is set to [ThemeData.disabledColor].
+  ///
+  /// When [selected] is true, the text color is set to [ListTileTheme.selectedColor]
+  /// if it's not null. If [ListTileTheme.selectedColor] is null, the text color
+  /// is set to [ThemeData.primaryColor] when [ThemeData.brightness] is
+  /// [Brightness.light] and to [ThemeData.accentColor] when it is [Brightness.dark].
+  ///
+  /// When [selected] is false, the text color is set to [ListTileTheme.textColor]
+  /// if it's not null and to [TextTheme.caption]'s color if [ListTileTheme.textColor]
+  /// is null.
+  final Widget? subtitle;
+
+  /// A widget to display after the title.
+  ///
+  /// Typically an [Icon] widget.
+  ///
+  /// To show right-aligned metadata (assuming left-to-right reading order;
+  /// left-aligned for right-to-left reading order), consider using a [Row] with
+  /// [CrossAxisAlignment.baseline] alignment whose first item is [Expanded] and
+  /// whose second child is the metadata text, instead of using the [trailing]
+  /// property.
+  final Widget? trailing;
+
+  /// Whether this list tile is intended to display three lines of text.
+  ///
+  /// If true, then [subtitle] must be non-null (since it is expected to give
+  /// the second and third lines of text).
+  ///
+  /// If false, the list tile is treated as having one line if the subtitle is
+  /// null and treated as having two lines if the subtitle is non-null.
+  ///
+  /// When using a [Text] widget for [title] and [subtitle], you can enforce
+  /// line limits using [Text.maxLines].
+  final bool isThreeLine;
+
+  /// Whether this list tile is part of a vertically dense list.
+  ///
+  /// If this property is null then its value is based on [ListTileTheme.dense].
+  ///
+  /// Dense list tiles default to a smaller height.
+  final bool? dense;
+
+  /// Defines how compact the list tile's layout will be.
+  ///
+  /// {@macro flutter.material.themedata.visualDensity}
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.visualDensity], which specifies the [visualDensity] for all
+  ///    widgets within a [Theme].
+  final VisualDensity? visualDensity;
+
+  /// The shape of the tile's [InkWell].
+  ///
+  /// Defines the tile's [InkWell.customBorder].
+  ///
+  /// If this property is null then [CardTheme.shape] of [ThemeData.cardTheme]
+  /// is used. If that's null then the shape will be a [RoundedRectangleBorder]
+  /// with a circular corner radius of 4.0.
+  final ShapeBorder? shape;
+
+  /// The tile's internal padding.
+  ///
+  /// Insets a [ListTile]'s contents: its [leading], [title], [subtitle],
+  /// and [trailing] widgets.
+  ///
+  /// If null, `EdgeInsets.symmetric(horizontal: 16.0)` is used.
+  final EdgeInsetsGeometry? contentPadding;
+
+  /// Whether this list tile is interactive.
+  ///
+  /// If false, this list tile is styled with the disabled color from the
+  /// current [Theme] and the [onTap] and [onLongPress] callbacks are
+  /// inoperative.
+  final bool enabled;
+
+  /// Called when the user taps this list tile.
+  ///
+  /// Inoperative if [enabled] is false.
+  final GestureTapCallback? onTap;
+
+  /// Called when the user long-presses on this list tile.
+  ///
+  /// Inoperative if [enabled] is false.
+  final GestureLongPressCallback? onLongPress;
+
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.disabled].
+  ///
+  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
+  final MouseCursor? mouseCursor;
+
+  /// If this tile is also [enabled] then icons and text are rendered with the same color.
+  ///
+  /// By default the selected color is the theme's primary color. The selected color
+  /// can be overridden with a [ListTileTheme].
+  ///
+  /// {@tool dartpad --template=stateful_widget_scaffold}
+  ///
+  /// Here is an example of using a [StatefulWidget] to keep track of the
+  /// selected index, and using that to set the `selected` property on the
+  /// corresponding [ListTile].
+  ///
+  /// ```dart
+  ///   int _selectedIndex = 0;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return ListView.builder(
+  ///       itemCount: 10,
+  ///       itemBuilder: (BuildContext context, int index) {
+  ///         return ListTile(
+  ///           title: Text('Item $index'),
+  ///           selected: index == _selectedIndex,
+  ///           onTap: () {
+  ///             setState(() {
+  ///               _selectedIndex = index;
+  ///             });
+  ///           },
+  ///         );
+  ///       },
+  ///     );
+  ///   }
+  /// ```
+  /// {@end-tool}
+  final bool selected;
+
+  /// The color for the tile's [Material] when it has the input focus.
+  final Color? focusColor;
+
+  /// The color for the tile's [Material] when a pointer is hovering over it.
+  final Color? hoverColor;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  /// {@template flutter.material.ListTile.tileColor}
+  /// Defines the background color of `ListTile` when [selected] is false.
+  ///
+  /// When the value is null, the `tileColor` is set to [ListTileTheme.tileColor]
+  /// if it's not null and to [Colors.transparent] if it's null.
+  /// {@endtemplate}
+  final Color? tileColor;
+
+  /// Defines the background color of `ListTile` when [selected] is true.
+  ///
+  /// When the value if null, the `selectedTileColor` is set to [ListTileTheme.selectedTileColor]
+  /// if it's not null and to [Colors.transparent] if it's null.
+  final Color? selectedTileColor;
+
+  /// Whether detected gestures should provide acoustic and/or haptic feedback.
+  ///
+  /// For example, on Android a tap will produce a clicking sound and a
+  /// long-press will produce a short vibration, when feedback is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [Feedback] for providing platform-specific feedback to certain actions.
+  final bool? enableFeedback;
+
+  /// The horizontal gap between the titles and the leading/trailing widgets.
+  ///
+  /// If null, then the value of [ListTileTheme.horizontalTitleGap] is used. If
+  /// that is also null, then a default value of 16 is used.
+  final double? horizontalTitleGap;
+
+  /// The minimum padding on the top and bottom of the title and subtitle widgets.
+  ///
+  /// If null, then the value of [ListTileTheme.minVerticalPadding] is used. If
+  /// that is also null, then a default value of 4 is used.
+  final double? minVerticalPadding;
+
+  /// The minimum width allocated for the [ListTile.leading] widget.
+  ///
+  /// If null, then the value of [ListTileTheme.minLeadingWidth] is used. If
+  /// that is also null, then a default value of 40 is used.
+  final double? minLeadingWidth;
+
+  /// Add a one pixel border in between each tile. If color isn't specified the
+  /// [ThemeData.dividerColor] of the context's [Theme] is used.
+  ///
+  /// See also:
+  ///
+  ///  * [Divider], which you can use to obtain this effect manually.
+  static Iterable<Widget> divideTiles({ BuildContext? context, required Iterable<Widget> tiles, Color? color }) sync* {
+    assert(tiles != null);
+    assert(color != null || context != null);
+
+    final Iterator<Widget> iterator = tiles.iterator;
+    final bool isNotEmpty = iterator.moveNext();
+
+    final Decoration decoration = BoxDecoration(
+      border: Border(
+        bottom: Divider.createBorderSide(context, color: color),
+      ),
+    );
+
+    Widget tile = iterator.current;
+    while (iterator.moveNext()) {
+      yield DecoratedBox(
+        position: DecorationPosition.foreground,
+        decoration: decoration,
+        child: tile,
+      );
+      tile = iterator.current;
+    }
+    if (isNotEmpty)
+      yield tile;
+  }
+
+  Color? _iconColor(ThemeData theme, ListTileTheme? tileTheme) {
+    if (!enabled)
+      return theme.disabledColor;
+
+    if (selected && tileTheme?.selectedColor != null)
+      return tileTheme!.selectedColor;
+
+    if (!selected && tileTheme?.iconColor != null)
+      return tileTheme!.iconColor;
+
+    switch (theme.brightness) {
+      case Brightness.light:
+        return selected ? theme.primaryColor : Colors.black45;
+      case Brightness.dark:
+        return selected ? theme.accentColor : null; // null - use current icon theme color
+    }
+  }
+
+  Color? _textColor(ThemeData theme, ListTileTheme? tileTheme, Color? defaultColor) {
+    if (!enabled)
+      return theme.disabledColor;
+
+    if (selected && tileTheme?.selectedColor != null)
+      return tileTheme!.selectedColor;
+
+    if (!selected && tileTheme?.textColor != null)
+      return tileTheme!.textColor;
+
+    if (selected) {
+      switch (theme.brightness) {
+        case Brightness.light:
+          return theme.primaryColor;
+        case Brightness.dark:
+          return theme.accentColor;
+      }
+    }
+    return defaultColor;
+  }
+
+  bool _isDenseLayout(ListTileTheme? tileTheme) {
+    return dense ?? tileTheme?.dense ?? false;
+  }
+
+  TextStyle _titleTextStyle(ThemeData theme, ListTileTheme? tileTheme) {
+    final TextStyle style;
+    if (tileTheme != null) {
+      switch (tileTheme.style) {
+        case ListTileStyle.drawer:
+          style = theme.textTheme.bodyText1!;
+          break;
+        case ListTileStyle.list:
+          style = theme.textTheme.subtitle1!;
+          break;
+      }
+    } else {
+      style = theme.textTheme.subtitle1!;
+    }
+    final Color? color = _textColor(theme, tileTheme, style.color);
+    return _isDenseLayout(tileTheme)
+      ? style.copyWith(fontSize: 13.0, color: color)
+      : style.copyWith(color: color);
+  }
+
+  TextStyle _subtitleTextStyle(ThemeData theme, ListTileTheme? tileTheme) {
+    final TextStyle style = theme.textTheme.bodyText2!;
+    final Color? color = _textColor(theme, tileTheme, theme.textTheme.caption!.color);
+    return _isDenseLayout(tileTheme)
+      ? style.copyWith(color: color, fontSize: 12.0)
+      : style.copyWith(color: color);
+  }
+
+  Color _tileBackgroundColor(ListTileTheme? tileTheme) {
+    if (!selected) {
+      if (tileColor != null)
+        return tileColor!;
+      if (tileTheme?.tileColor != null)
+        return tileTheme!.tileColor!;
+    }
+
+    if (selected) {
+      if (selectedTileColor != null)
+        return selectedTileColor!;
+      if (tileTheme?.selectedTileColor != null)
+        return tileTheme!.selectedTileColor!;
+    }
+
+    return Colors.transparent;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    final ThemeData theme = Theme.of(context);
+    final ListTileTheme tileTheme = ListTileTheme.of(context);
+
+    IconThemeData? iconThemeData;
+    if (leading != null || trailing != null)
+      iconThemeData = IconThemeData(color: _iconColor(theme, tileTheme));
+
+    Widget? leadingIcon;
+    if (leading != null) {
+      leadingIcon = IconTheme.merge(
+        data: iconThemeData!,
+        child: leading!,
+      );
+    }
+
+    final TextStyle titleStyle = _titleTextStyle(theme, tileTheme);
+    final Widget titleText = AnimatedDefaultTextStyle(
+      style: titleStyle,
+      duration: kThemeChangeDuration,
+      child: title ?? const SizedBox(),
+    );
+
+    Widget? subtitleText;
+    TextStyle? subtitleStyle;
+    if (subtitle != null) {
+      subtitleStyle = _subtitleTextStyle(theme, tileTheme);
+      subtitleText = AnimatedDefaultTextStyle(
+        style: subtitleStyle,
+        duration: kThemeChangeDuration,
+        child: subtitle!,
+      );
+    }
+
+    Widget? trailingIcon;
+    if (trailing != null) {
+      trailingIcon = IconTheme.merge(
+        data: iconThemeData!,
+        child: trailing!,
+      );
+    }
+
+    const EdgeInsets _defaultContentPadding = EdgeInsets.symmetric(horizontal: 16.0);
+    final TextDirection textDirection = Directionality.of(context);
+    final EdgeInsets resolvedContentPadding = contentPadding?.resolve(textDirection)
+      ?? tileTheme.contentPadding?.resolve(textDirection)
+      ?? _defaultContentPadding;
+
+    final MouseCursor resolvedMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
+      mouseCursor ?? MaterialStateMouseCursor.clickable,
+      <MaterialState>{
+        if (!enabled || (onTap == null && onLongPress == null)) MaterialState.disabled,
+        if (selected) MaterialState.selected,
+      },
+    );
+
+    return InkWell(
+      customBorder: shape ?? tileTheme.shape,
+      onTap: enabled ? onTap : null,
+      onLongPress: enabled ? onLongPress : null,
+      mouseCursor: resolvedMouseCursor,
+      canRequestFocus: enabled,
+      focusNode: focusNode,
+      focusColor: focusColor,
+      hoverColor: hoverColor,
+      autofocus: autofocus,
+      enableFeedback: enableFeedback ?? tileTheme.enableFeedback ?? true,
+      child: Semantics(
+        selected: selected,
+        enabled: enabled,
+        child: ColoredBox(
+          color: _tileBackgroundColor(tileTheme),
+          child: SafeArea(
+            top: false,
+            bottom: false,
+            minimum: resolvedContentPadding,
+            child: _ListTile(
+              leading: leadingIcon,
+              title: titleText,
+              subtitle: subtitleText,
+              trailing: trailingIcon,
+              isDense: _isDenseLayout(tileTheme),
+              visualDensity: visualDensity ?? theme.visualDensity,
+              isThreeLine: isThreeLine,
+              textDirection: textDirection,
+              titleBaselineType: titleStyle.textBaseline!,
+              subtitleBaselineType: subtitleStyle?.textBaseline,
+              horizontalTitleGap: horizontalTitleGap ?? tileTheme.horizontalTitleGap ?? 16,
+              minVerticalPadding: minVerticalPadding ?? tileTheme.minVerticalPadding ?? 4,
+              minLeadingWidth: minLeadingWidth ?? tileTheme.minLeadingWidth ?? 40,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+// Identifies the children of a _ListTileElement.
+enum _ListTileSlot {
+  leading,
+  title,
+  subtitle,
+  trailing,
+}
+
+class _ListTile extends RenderObjectWidget {
+  const _ListTile({
+    Key? key,
+    this.leading,
+    required this.title,
+    this.subtitle,
+    this.trailing,
+    required this.isThreeLine,
+    required this.isDense,
+    required this.visualDensity,
+    required this.textDirection,
+    required this.titleBaselineType,
+    required this.horizontalTitleGap,
+    required this.minVerticalPadding,
+    required this.minLeadingWidth,
+    this.subtitleBaselineType,
+  }) : assert(isThreeLine != null),
+       assert(isDense != null),
+       assert(visualDensity != null),
+       assert(textDirection != null),
+       assert(titleBaselineType != null),
+       assert(horizontalTitleGap != null),
+       assert(minVerticalPadding != null),
+       assert(minLeadingWidth != null),
+       super(key: key);
+
+  final Widget? leading;
+  final Widget title;
+  final Widget? subtitle;
+  final Widget? trailing;
+  final bool isThreeLine;
+  final bool isDense;
+  final VisualDensity visualDensity;
+  final TextDirection textDirection;
+  final TextBaseline titleBaselineType;
+  final TextBaseline? subtitleBaselineType;
+  final double horizontalTitleGap;
+  final double minVerticalPadding;
+  final double minLeadingWidth;
+
+  @override
+  _ListTileElement createElement() => _ListTileElement(this);
+
+  @override
+  _RenderListTile createRenderObject(BuildContext context) {
+    return _RenderListTile(
+      isThreeLine: isThreeLine,
+      isDense: isDense,
+      visualDensity: visualDensity,
+      textDirection: textDirection,
+      titleBaselineType: titleBaselineType,
+      subtitleBaselineType: subtitleBaselineType,
+      horizontalTitleGap: horizontalTitleGap,
+      minVerticalPadding: minVerticalPadding,
+      minLeadingWidth: minLeadingWidth,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderListTile renderObject) {
+    renderObject
+      ..isThreeLine = isThreeLine
+      ..isDense = isDense
+      ..visualDensity = visualDensity
+      ..textDirection = textDirection
+      ..titleBaselineType = titleBaselineType
+      ..subtitleBaselineType = subtitleBaselineType
+      ..horizontalTitleGap = horizontalTitleGap
+      ..minLeadingWidth = minLeadingWidth
+      ..minVerticalPadding = minVerticalPadding;
+  }
+}
+
+class _ListTileElement extends RenderObjectElement {
+  _ListTileElement(_ListTile widget) : super(widget);
+
+  final Map<_ListTileSlot, Element> slotToChild = <_ListTileSlot, Element>{};
+
+  @override
+  _ListTile get widget => super.widget as _ListTile;
+
+  @override
+  _RenderListTile get renderObject => super.renderObject as _RenderListTile;
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    slotToChild.values.forEach(visitor);
+  }
+
+  @override
+  void forgetChild(Element child) {
+    assert(slotToChild.containsValue(child));
+    assert(child.slot is _ListTileSlot);
+    assert(slotToChild.containsKey(child.slot));
+    slotToChild.remove(child.slot);
+    super.forgetChild(child);
+  }
+
+  void _mountChild(Widget? widget, _ListTileSlot slot) {
+    final Element? oldChild = slotToChild[slot];
+    final Element? newChild = updateChild(oldChild, widget, slot);
+    if (oldChild != null) {
+      slotToChild.remove(slot);
+    }
+    if (newChild != null) {
+      slotToChild[slot] = newChild;
+    }
+  }
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    _mountChild(widget.leading, _ListTileSlot.leading);
+    _mountChild(widget.title, _ListTileSlot.title);
+    _mountChild(widget.subtitle, _ListTileSlot.subtitle);
+    _mountChild(widget.trailing, _ListTileSlot.trailing);
+  }
+
+  void _updateChild(Widget? widget, _ListTileSlot slot) {
+    final Element? oldChild = slotToChild[slot];
+    final Element? newChild = updateChild(oldChild, widget, slot);
+    if (oldChild != null) {
+      slotToChild.remove(slot);
+    }
+    if (newChild != null) {
+      slotToChild[slot] = newChild;
+    }
+  }
+
+  @override
+  void update(_ListTile newWidget) {
+    super.update(newWidget);
+    assert(widget == newWidget);
+    _updateChild(widget.leading, _ListTileSlot.leading);
+    _updateChild(widget.title, _ListTileSlot.title);
+    _updateChild(widget.subtitle, _ListTileSlot.subtitle);
+    _updateChild(widget.trailing, _ListTileSlot.trailing);
+  }
+
+  void _updateRenderObject(RenderBox? child, _ListTileSlot slot) {
+    switch (slot) {
+      case _ListTileSlot.leading:
+        renderObject.leading = child;
+        break;
+      case _ListTileSlot.title:
+        renderObject.title = child;
+        break;
+      case _ListTileSlot.subtitle:
+        renderObject.subtitle = child;
+        break;
+      case _ListTileSlot.trailing:
+        renderObject.trailing = child;
+        break;
+    }
+  }
+
+  @override
+  void insertRenderObjectChild(RenderObject child, _ListTileSlot slot) {
+    assert(child is RenderBox);
+    _updateRenderObject(child as RenderBox, slot);
+    assert(renderObject.children.keys.contains(slot));
+  }
+
+  @override
+  void removeRenderObjectChild(RenderObject child, _ListTileSlot slot) {
+    assert(child is RenderBox);
+    assert(renderObject.children[slot] == child);
+    _updateRenderObject(null, slot);
+    assert(!renderObject.children.keys.contains(slot));
+  }
+
+  @override
+  void moveRenderObjectChild(RenderObject child, dynamic oldSlot, dynamic newSlot) {
+    assert(false, 'not reachable');
+  }
+}
+
+class _RenderListTile extends RenderBox {
+  _RenderListTile({
+    required bool isDense,
+    required VisualDensity visualDensity,
+    required bool isThreeLine,
+    required TextDirection textDirection,
+    required TextBaseline titleBaselineType,
+    TextBaseline? subtitleBaselineType,
+    required double horizontalTitleGap,
+    required double minVerticalPadding,
+    required double minLeadingWidth,
+  }) : assert(isDense != null),
+       assert(visualDensity != null),
+       assert(isThreeLine != null),
+       assert(textDirection != null),
+       assert(titleBaselineType != null),
+       assert(horizontalTitleGap != null),
+       assert(minVerticalPadding != null),
+       assert(minLeadingWidth != null),
+       _isDense = isDense,
+       _visualDensity = visualDensity,
+       _isThreeLine = isThreeLine,
+       _textDirection = textDirection,
+       _titleBaselineType = titleBaselineType,
+       _subtitleBaselineType = subtitleBaselineType,
+       _horizontalTitleGap = horizontalTitleGap,
+       _minVerticalPadding = minVerticalPadding,
+       _minLeadingWidth = minLeadingWidth;
+
+  final Map<_ListTileSlot, RenderBox> children = <_ListTileSlot, RenderBox>{};
+
+  RenderBox? _updateChild(RenderBox? oldChild, RenderBox? newChild, _ListTileSlot slot) {
+    if (oldChild != null) {
+      dropChild(oldChild);
+      children.remove(slot);
+    }
+    if (newChild != null) {
+      children[slot] = newChild;
+      adoptChild(newChild);
+    }
+    return newChild;
+  }
+
+  RenderBox? _leading;
+  RenderBox? get leading => _leading;
+  set leading(RenderBox? value) {
+    _leading = _updateChild(_leading, value, _ListTileSlot.leading);
+  }
+
+  RenderBox? _title;
+  RenderBox? get title => _title;
+  set title(RenderBox? value) {
+    _title = _updateChild(_title, value, _ListTileSlot.title);
+  }
+
+  RenderBox? _subtitle;
+  RenderBox? get subtitle => _subtitle;
+  set subtitle(RenderBox? value) {
+    _subtitle = _updateChild(_subtitle, value, _ListTileSlot.subtitle);
+  }
+
+  RenderBox? _trailing;
+  RenderBox? get trailing => _trailing;
+  set trailing(RenderBox? value) {
+    _trailing = _updateChild(_trailing, value, _ListTileSlot.trailing);
+  }
+
+  // The returned list is ordered for hit testing.
+  Iterable<RenderBox> get _children sync* {
+    if (leading != null)
+      yield leading!;
+    if (title != null)
+      yield title!;
+    if (subtitle != null)
+      yield subtitle!;
+    if (trailing != null)
+      yield trailing!;
+  }
+
+  bool get isDense => _isDense;
+  bool _isDense;
+  set isDense(bool value) {
+    assert(value != null);
+    if (_isDense == value)
+      return;
+    _isDense = value;
+    markNeedsLayout();
+  }
+
+  VisualDensity get visualDensity => _visualDensity;
+  VisualDensity _visualDensity;
+  set visualDensity(VisualDensity value) {
+    assert(value != null);
+    if (_visualDensity == value)
+      return;
+    _visualDensity = value;
+    markNeedsLayout();
+  }
+
+  bool get isThreeLine => _isThreeLine;
+  bool _isThreeLine;
+  set isThreeLine(bool value) {
+    assert(value != null);
+    if (_isThreeLine == value)
+      return;
+    _isThreeLine = value;
+    markNeedsLayout();
+  }
+
+  TextDirection get textDirection => _textDirection;
+  TextDirection _textDirection;
+  set textDirection(TextDirection value) {
+    assert(value != null);
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    markNeedsLayout();
+  }
+
+  TextBaseline get titleBaselineType => _titleBaselineType;
+  TextBaseline _titleBaselineType;
+  set titleBaselineType(TextBaseline value) {
+    assert(value != null);
+    if (_titleBaselineType == value)
+      return;
+    _titleBaselineType = value;
+    markNeedsLayout();
+  }
+
+  TextBaseline? get subtitleBaselineType => _subtitleBaselineType;
+  TextBaseline? _subtitleBaselineType;
+  set subtitleBaselineType(TextBaseline? value) {
+    if (_subtitleBaselineType == value)
+      return;
+    _subtitleBaselineType = value;
+    markNeedsLayout();
+  }
+
+  double get horizontalTitleGap => _horizontalTitleGap;
+  double _horizontalTitleGap;
+  double get _effectiveHorizontalTitleGap => _horizontalTitleGap + visualDensity.horizontal * 2.0;
+
+  set horizontalTitleGap(double value) {
+    assert(value != null);
+    if (_horizontalTitleGap == value)
+      return;
+    _horizontalTitleGap = value;
+    markNeedsLayout();
+  }
+
+  double get minVerticalPadding => _minVerticalPadding;
+  double _minVerticalPadding;
+
+  set minVerticalPadding(double value) {
+    assert(value != null);
+    if (_minVerticalPadding == value)
+      return;
+    _minVerticalPadding = value;
+    markNeedsLayout();
+  }
+
+  double get minLeadingWidth => _minLeadingWidth;
+  double _minLeadingWidth;
+
+  set minLeadingWidth(double value) {
+    assert(value != null);
+    if (_minLeadingWidth == value)
+      return;
+    _minLeadingWidth = value;
+    markNeedsLayout();
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    for (final RenderBox child in _children)
+      child.attach(owner);
+  }
+
+  @override
+  void detach() {
+    super.detach();
+    for (final RenderBox child in _children)
+      child.detach();
+  }
+
+  @override
+  void redepthChildren() {
+    _children.forEach(redepthChild);
+  }
+
+  @override
+  void visitChildren(RenderObjectVisitor visitor) {
+    _children.forEach(visitor);
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    final List<DiagnosticsNode> value = <DiagnosticsNode>[];
+    void add(RenderBox? child, String name) {
+      if (child != null)
+        value.add(child.toDiagnosticsNode(name: name));
+    }
+    add(leading, 'leading');
+    add(title, 'title');
+    add(subtitle, 'subtitle');
+    add(trailing, 'trailing');
+    return value;
+  }
+
+  @override
+  bool get sizedByParent => false;
+
+  static double _minWidth(RenderBox? box, double height) {
+    return box == null ? 0.0 : box.getMinIntrinsicWidth(height);
+  }
+
+  static double _maxWidth(RenderBox? box, double height) {
+    return box == null ? 0.0 : box.getMaxIntrinsicWidth(height);
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    final double leadingWidth = leading != null
+      ? math.max(leading!.getMinIntrinsicWidth(height), _minLeadingWidth) + _effectiveHorizontalTitleGap
+      : 0.0;
+    return leadingWidth
+      + math.max(_minWidth(title, height), _minWidth(subtitle, height))
+      + _maxWidth(trailing, height);
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    final double leadingWidth = leading != null
+      ? math.max(leading!.getMaxIntrinsicWidth(height), _minLeadingWidth) + _effectiveHorizontalTitleGap
+      : 0.0;
+    return leadingWidth
+      + math.max(_maxWidth(title, height), _maxWidth(subtitle, height))
+      + _maxWidth(trailing, height);
+  }
+
+  double get _defaultTileHeight {
+    final bool hasSubtitle = subtitle != null;
+    final bool isTwoLine = !isThreeLine && hasSubtitle;
+    final bool isOneLine = !isThreeLine && !hasSubtitle;
+
+    final Offset baseDensity = visualDensity.baseSizeAdjustment;
+    if (isOneLine)
+      return (isDense ? 48.0 : 56.0) + baseDensity.dy;
+    if (isTwoLine)
+      return (isDense ? 64.0 : 72.0) + baseDensity.dy;
+    return (isDense ? 76.0 : 88.0) + baseDensity.dy;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    return math.max(
+      _defaultTileHeight,
+      title!.getMinIntrinsicHeight(width) + (subtitle?.getMinIntrinsicHeight(width) ?? 0.0),
+    );
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    return computeMinIntrinsicHeight(width);
+  }
+
+  @override
+  double computeDistanceToActualBaseline(TextBaseline baseline) {
+    assert(title != null);
+    final BoxParentData parentData = title!.parentData! as BoxParentData;
+    return parentData.offset.dy + title!.getDistanceToActualBaseline(baseline)!;
+  }
+
+  static double? _boxBaseline(RenderBox box, TextBaseline baseline) {
+    return box.getDistanceToBaseline(baseline);
+  }
+
+  static Size _layoutBox(RenderBox? box, BoxConstraints constraints) {
+    if (box == null)
+      return Size.zero;
+    box.layout(constraints, parentUsesSize: true);
+    return box.size;
+  }
+
+  static void _positionBox(RenderBox box, Offset offset) {
+    final BoxParentData parentData = box.parentData! as BoxParentData;
+    parentData.offset = offset;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    assert(debugCannotComputeDryLayout(
+      reason: 'Layout requires baseline metrics, which are only available after a full layout.'
+    ));
+    return const Size(0, 0);
+  }
+
+  // All of the dimensions below were taken from the Material Design spec:
+  // https://material.io/design/components/lists.html#specs
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    final bool hasLeading = leading != null;
+    final bool hasSubtitle = subtitle != null;
+    final bool hasTrailing = trailing != null;
+    final bool isTwoLine = !isThreeLine && hasSubtitle;
+    final bool isOneLine = !isThreeLine && !hasSubtitle;
+    final Offset densityAdjustment = visualDensity.baseSizeAdjustment;
+
+    final BoxConstraints maxIconHeightConstraint = BoxConstraints(
+      // One-line trailing and leading widget heights do not follow
+      // Material specifications, but this sizing is required to adhere
+      // to accessibility requirements for smallest tappable widget.
+      // Two- and three-line trailing widget heights are constrained
+      // properly according to the Material spec.
+      maxHeight: (isDense ? 48.0 : 56.0) + densityAdjustment.dy,
+    );
+    final BoxConstraints looseConstraints = constraints.loosen();
+    final BoxConstraints iconConstraints = looseConstraints.enforce(maxIconHeightConstraint);
+
+    final double tileWidth = looseConstraints.maxWidth;
+    final Size leadingSize = _layoutBox(leading, iconConstraints);
+    final Size trailingSize = _layoutBox(trailing, iconConstraints);
+    assert(
+      tileWidth != leadingSize.width || tileWidth == 0.0,
+      'Leading widget consumes entire tile width. Please use a sized widget, '
+      'or consider replacing ListTile with a custom widget '
+      '(see https://api.flutter.dev/flutter/material/ListTile-class.html#material.ListTile.4)'
+    );
+    assert(
+      tileWidth != trailingSize.width || tileWidth == 0.0,
+      'Trailing widget consumes entire tile width. Please use a sized widget, '
+      'or consider replacing ListTile with a custom widget '
+      '(see https://api.flutter.dev/flutter/material/ListTile-class.html#material.ListTile.4)'
+    );
+
+    final double titleStart = hasLeading
+      ? math.max(_minLeadingWidth, leadingSize.width) + _effectiveHorizontalTitleGap
+      : 0.0;
+    final double adjustedTrailingWidth = hasTrailing
+        ? math.max(trailingSize.width + _effectiveHorizontalTitleGap, 32.0)
+        : 0.0;
+    final BoxConstraints textConstraints = looseConstraints.tighten(
+      width: tileWidth - titleStart - adjustedTrailingWidth,
+    );
+    final Size titleSize = _layoutBox(title, textConstraints);
+    final Size subtitleSize = _layoutBox(subtitle, textConstraints);
+
+    double? titleBaseline;
+    double? subtitleBaseline;
+    if (isTwoLine) {
+      titleBaseline = isDense ? 28.0 : 32.0;
+      subtitleBaseline = isDense ? 48.0 : 52.0;
+    } else if (isThreeLine) {
+      titleBaseline = isDense ? 22.0 : 28.0;
+      subtitleBaseline = isDense ? 42.0 : 48.0;
+    } else {
+      assert(isOneLine);
+    }
+
+    final double defaultTileHeight = _defaultTileHeight;
+
+    double tileHeight;
+    double titleY;
+    double? subtitleY;
+    if (!hasSubtitle) {
+      tileHeight = math.max(defaultTileHeight, titleSize.height + 2.0 * _minVerticalPadding);
+      titleY = (tileHeight - titleSize.height) / 2.0;
+    } else {
+      assert(subtitleBaselineType != null);
+      titleY = titleBaseline! - _boxBaseline(title!, titleBaselineType)!;
+      subtitleY = subtitleBaseline! - _boxBaseline(subtitle!, subtitleBaselineType!)! + visualDensity.vertical * 2.0;
+      tileHeight = defaultTileHeight;
+
+      // If the title and subtitle overlap, move the title upwards by half
+      // the overlap and the subtitle down by the same amount, and adjust
+      // tileHeight so that both titles fit.
+      final double titleOverlap = titleY + titleSize.height - subtitleY;
+      if (titleOverlap > 0.0) {
+        titleY -= titleOverlap / 2.0;
+        subtitleY += titleOverlap / 2.0;
+      }
+
+      // If the title or subtitle overflow tileHeight then punt: title
+      // and subtitle are arranged in a column, tileHeight = column height plus
+      // _minVerticalPadding on top and bottom.
+      if (titleY < _minVerticalPadding ||
+          (subtitleY + subtitleSize.height + _minVerticalPadding) > tileHeight) {
+        tileHeight = titleSize.height + subtitleSize.height + 2.0 * _minVerticalPadding;
+        titleY = _minVerticalPadding;
+        subtitleY = titleSize.height + _minVerticalPadding;
+      }
+    }
+
+    // This attempts to implement the redlines for the vertical position of the
+    // leading and trailing icons on the spec page:
+    //   https://material.io/design/components/lists.html#specs
+    // The interpretation for these redlines is as follows:
+    //  - For large tiles (> 72dp), both leading and trailing controls should be
+    //    a fixed distance from top. As per guidelines this is set to 16dp.
+    //  - For smaller tiles, trailing should always be centered. Leading can be
+    //    centered or closer to the top. It should never be further than 16dp
+    //    to the top.
+    final double leadingY;
+    final double trailingY;
+    if (tileHeight > 72.0) {
+      leadingY = 16.0;
+      trailingY = 16.0;
+    } else {
+      leadingY = math.min((tileHeight - leadingSize.height) / 2.0, 16.0);
+      trailingY = (tileHeight - trailingSize.height) / 2.0;
+    }
+
+    switch (textDirection) {
+      case TextDirection.rtl: {
+        if (hasLeading)
+          _positionBox(leading!, Offset(tileWidth - leadingSize.width, leadingY));
+        _positionBox(title!, Offset(adjustedTrailingWidth, titleY));
+        if (hasSubtitle)
+          _positionBox(subtitle!, Offset(adjustedTrailingWidth, subtitleY!));
+        if (hasTrailing)
+          _positionBox(trailing!, Offset(0.0, trailingY));
+        break;
+      }
+      case TextDirection.ltr: {
+        if (hasLeading)
+          _positionBox(leading!, Offset(0.0, leadingY));
+        _positionBox(title!, Offset(titleStart, titleY));
+        if (hasSubtitle)
+          _positionBox(subtitle!, Offset(titleStart, subtitleY!));
+        if (hasTrailing)
+          _positionBox(trailing!, Offset(tileWidth - trailingSize.width, trailingY));
+        break;
+      }
+    }
+
+    size = constraints.constrain(Size(tileWidth, tileHeight));
+    assert(size.width == constraints.constrainWidth(tileWidth));
+    assert(size.height == constraints.constrainHeight(tileHeight));
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    void doPaint(RenderBox? child) {
+      if (child != null) {
+        final BoxParentData parentData = child.parentData! as BoxParentData;
+        context.paintChild(child, parentData.offset + offset);
+      }
+    }
+    doPaint(leading);
+    doPaint(title);
+    doPaint(subtitle);
+    doPaint(trailing);
+  }
+
+  @override
+  bool hitTestSelf(Offset position) => true;
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    assert(position != null);
+    for (final RenderBox child in _children) {
+      final BoxParentData parentData = child.parentData! as BoxParentData;
+      final bool isHit = result.addWithPaintOffset(
+        offset: parentData.offset,
+        position: position,
+        hitTest: (BoxHitTestResult result, Offset transformed) {
+          assert(transformed == position - parentData.offset);
+          return child.hitTest(result, position: transformed);
+        },
+      );
+      if (isHit)
+        return true;
+    }
+    return false;
+  }
+}
diff --git a/lib/src/material/material.dart b/lib/src/material/material.dart
new file mode 100644
index 0000000..e6a3579
--- /dev/null
+++ b/lib/src/material/material.dart
@@ -0,0 +1,835 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'constants.dart';
+import 'elevation_overlay.dart';
+import 'theme.dart';
+
+/// Signature for the callback used by ink effects to obtain the rectangle for the effect.
+///
+/// Used by [InkHighlight] and [InkSplash], for example.
+typedef RectCallback = Rect Function();
+
+/// The various kinds of material in material design. Used to
+/// configure the default behavior of [Material] widgets.
+///
+/// See also:
+///
+///  * [Material], in particular [Material.type].
+///  * [kMaterialEdges]
+enum MaterialType {
+  /// Rectangle using default theme canvas color.
+  canvas,
+
+  /// Rounded edges, card theme color.
+  card,
+
+  /// A circle, no color by default (used for floating action buttons).
+  circle,
+
+  /// Rounded edges, no color by default (used for [MaterialButton] buttons).
+  button,
+
+  /// A transparent piece of material that draws ink splashes and highlights.
+  ///
+  /// While the material metaphor describes child widgets as printed on the
+  /// material itself and do not hide ink effects, in practice the [Material]
+  /// widget draws child widgets on top of the ink effects.
+  /// A [Material] with type transparency can be placed on top of opaque widgets
+  /// to show ink effects on top of them.
+  ///
+  /// Prefer using the [Ink] widget for showing ink effects on top of opaque
+  /// widgets.
+  transparency
+}
+
+/// The border radii used by the various kinds of material in material design.
+///
+/// See also:
+///
+///  * [MaterialType]
+///  * [Material]
+final Map<MaterialType, BorderRadius?> kMaterialEdges = <MaterialType, BorderRadius?>{
+  MaterialType.canvas: null,
+  MaterialType.card: BorderRadius.circular(2.0),
+  MaterialType.circle: null,
+  MaterialType.button: BorderRadius.circular(2.0),
+  MaterialType.transparency: null,
+};
+
+/// An interface for creating [InkSplash]s and [InkHighlight]s on a material.
+///
+/// Typically obtained via [Material.of].
+abstract class MaterialInkController {
+  /// The color of the material.
+  Color? get color;
+
+  /// The ticker provider used by the controller.
+  ///
+  /// Ink features that are added to this controller with [addInkFeature] should
+  /// use this vsync to drive their animations.
+  TickerProvider get vsync;
+
+  /// Add an [InkFeature], such as an [InkSplash] or an [InkHighlight].
+  ///
+  /// The ink feature will paint as part of this controller.
+  void addInkFeature(InkFeature feature);
+
+  /// Notifies the controller that one of its ink features needs to repaint.
+  void markNeedsPaint();
+}
+
+/// A piece of material.
+///
+/// The Material widget is responsible for:
+///
+/// 1. Clipping: If [clipBehavior] is not [Clip.none], Material clips its widget
+///    sub-tree to the shape specified by [shape], [type], and [borderRadius].
+///    By default, [clipBehavior] is [Clip.none] for performance considerations.
+/// 2. Elevation: Material elevates its widget sub-tree on the Z axis by
+///    [elevation] pixels, and draws the appropriate shadow.
+/// 3. Ink effects: Material shows ink effects implemented by [InkFeature]s
+///    like [InkSplash] and [InkHighlight] below its children.
+///
+/// ## The Material Metaphor
+///
+/// Material is the central metaphor in material design. Each piece of material
+/// exists at a given elevation, which influences how that piece of material
+/// visually relates to other pieces of material and how that material casts
+/// shadows.
+///
+/// Most user interface elements are either conceptually printed on a piece of
+/// material or themselves made of material. Material reacts to user input using
+/// [InkSplash] and [InkHighlight] effects. To trigger a reaction on the
+/// material, use a [MaterialInkController] obtained via [Material.of].
+///
+/// In general, the features of a [Material] should not change over time (e.g. a
+/// [Material] should not change its [color], [shadowColor] or [type]).
+/// Changes to [elevation] and [shadowColor] are animated for [animationDuration].
+/// Changes to [shape] are animated if [type] is not [MaterialType.transparency]
+/// and [ShapeBorder.lerp] between the previous and next [shape] values is
+/// supported. Shape changes are also animated for [animationDuration].
+///
+/// ## Shape
+///
+/// The shape for material is determined by [shape], [type], and [borderRadius].
+///
+///  - If [shape] is non null, it determines the shape.
+///  - If [shape] is null and [borderRadius] is non null, the shape is a
+///    rounded rectangle, with corners specified by [borderRadius].
+///  - If [shape] and [borderRadius] are null, [type] determines the
+///    shape as follows:
+///    - [MaterialType.canvas]: the default material shape is a rectangle.
+///    - [MaterialType.card]: the default material shape is a rectangle with
+///      rounded edges. The edge radii is specified by [kMaterialEdges].
+///    - [MaterialType.circle]: the default material shape is a circle.
+///    - [MaterialType.button]: the default material shape is a rectangle with
+///      rounded edges. The edge radii is specified by [kMaterialEdges].
+///    - [MaterialType.transparency]: the default material shape is a rectangle.
+///
+/// ## Border
+///
+/// If [shape] is not null, then its border will also be painted (if any).
+///
+/// ## Layout change notifications
+///
+/// If the layout changes (e.g. because there's a list on the material, and it's
+/// been scrolled), a [LayoutChangedNotification] must be dispatched at the
+/// relevant subtree. This in particular means that transitions (e.g.
+/// [SlideTransition]) should not be placed inside [Material] widgets so as to
+/// move subtrees that contain [InkResponse]s, [InkWell]s, [Ink]s, or other
+/// widgets that use the [InkFeature] mechanism. Otherwise, in-progress ink
+/// features (e.g., ink splashes and ink highlights) won't move to account for
+/// the new layout.
+///
+/// ## Painting over the material
+///
+/// Material widgets will often trigger reactions on their nearest material
+/// ancestor. For example, [ListTile.hoverColor] triggers a reaction on the
+/// tile's material when a pointer is hovering over it. These reactions will be
+/// obscured if any widget in between them and the material paints in such a
+/// way as to obscure the material (such as setting a [BoxDecoration.color] on
+/// a [DecoratedBox]). To avoid this behavior, use [InkDecoration] to decorate
+/// the material itself.
+///
+/// See also:
+///
+///  * [MergeableMaterial], a piece of material that can split and re-merge.
+///  * [Card], a wrapper for a [Material] of [type] [MaterialType.card].
+///  * <https://material.io/design/>
+class Material extends StatefulWidget {
+  /// Creates a piece of material.
+  ///
+  /// The [type], [elevation], [shadowColor], [borderOnForeground],
+  /// [clipBehavior], and [animationDuration] arguments must not be null.
+  /// Additionally, [elevation] must be non-negative.
+  ///
+  /// If a [shape] is specified, then the [borderRadius] property must be
+  /// null and the [type] property must not be [MaterialType.circle]. If the
+  /// [borderRadius] is specified, then the [type] property must not be
+  /// [MaterialType.circle]. In both cases, these restrictions are intended to
+  /// catch likely errors.
+  const Material({
+    Key? key,
+    this.type = MaterialType.canvas,
+    this.elevation = 0.0,
+    this.color,
+    this.shadowColor,
+    this.textStyle,
+    this.borderRadius,
+    this.shape,
+    this.borderOnForeground = true,
+    this.clipBehavior = Clip.none,
+    this.animationDuration = kThemeChangeDuration,
+    this.child,
+  }) : assert(type != null),
+       assert(elevation != null && elevation >= 0.0),
+       assert(!(shape != null && borderRadius != null)),
+       assert(animationDuration != null),
+       assert(!(identical(type, MaterialType.circle) && (borderRadius != null || shape != null))),
+       assert(borderOnForeground != null),
+       assert(clipBehavior != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// The kind of material to show (e.g., card or canvas). This
+  /// affects the shape of the widget, the roundness of its corners if
+  /// the shape is rectangular, and the default color.
+  final MaterialType type;
+
+  /// {@template flutter.material.material.elevation}
+  /// The z-coordinate at which to place this material relative to its parent.
+  ///
+  /// This controls the size of the shadow below the material and the opacity
+  /// of the elevation overlay color if it is applied.
+  ///
+  /// If this is non-zero, the contents of the material are clipped, because the
+  /// widget conceptually defines an independent printed piece of material.
+  ///
+  /// Defaults to 0. Changing this value will cause the shadow and the elevation
+  /// overlay to animate over [Material.animationDuration].
+  ///
+  /// The value is non-negative.
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.applyElevationOverlayColor] which controls the whether
+  ///    an overlay color will be applied to indicate elevation.
+  ///  * [Material.color] which may have an elevation overlay applied.
+  ///
+  /// {@endtemplate}
+  final double elevation;
+
+  /// The color to paint the material.
+  ///
+  /// Must be opaque. To create a transparent piece of material, use
+  /// [MaterialType.transparency].
+  ///
+  /// To support dark themes, if the surrounding
+  /// [ThemeData.applyElevationOverlayColor] is true and [ThemeData.brightness]
+  /// is [Brightness.dark] then a semi-transparent overlay color will be
+  /// composited on top of this color to indicate the elevation.
+  ///
+  /// By default, the color is derived from the [type] of material.
+  final Color? color;
+
+  /// The color to paint the shadow below the material.
+  ///
+  /// If null, [ThemeData.shadowColor] is used, which defaults to fully opaque black.
+  ///
+  /// Shadows can be difficult to see in a dark theme, so the elevation of a
+  /// surface should be portrayed with an "overlay" in addition to the shadow.
+  /// As the elevation of the component increases, the overlay increases in
+  /// opacity.
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.applyElevationOverlayColor], which turns elevation overlay
+  /// on or off for dark themes.
+  final Color? shadowColor;
+
+  /// The typographical style to use for text within this material.
+  final TextStyle? textStyle;
+
+  /// Defines the material's shape as well its shadow.
+  ///
+  /// If shape is non null, the [borderRadius] is ignored and the material's
+  /// clip boundary and shadow are defined by the shape.
+  ///
+  /// A shadow is only displayed if the [elevation] is greater than
+  /// zero.
+  final ShapeBorder? shape;
+
+  /// Whether to paint the [shape] border in front of the [child].
+  ///
+  /// The default value is true.
+  /// If false, the border will be painted behind the [child].
+  final bool borderOnForeground;
+
+  /// {@template flutter.material.Material.clipBehavior}
+  /// The content will be clipped (or not) according to this option.
+  ///
+  /// See the enum [Clip] for details of all possible options and their common
+  /// use cases.
+  /// {@endtemplate}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  final Clip clipBehavior;
+
+  /// Defines the duration of animated changes for [shape], [elevation],
+  /// [shadowColor] and the elevation overlay if it is applied.
+  ///
+  /// The default value is [kThemeChangeDuration].
+  final Duration animationDuration;
+
+  /// If non-null, the corners of this box are rounded by this
+  /// [BorderRadiusGeometry] value.
+  ///
+  /// Otherwise, the corners specified for the current [type] of material are
+  /// used.
+  ///
+  /// If [shape] is non null then the border radius is ignored.
+  ///
+  /// Must be null if [type] is [MaterialType.circle].
+  final BorderRadiusGeometry? borderRadius;
+
+  /// The ink controller from the closest instance of this class that
+  /// encloses the given context.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// MaterialInkController inkController = Material.of(context);
+  /// ```
+  static MaterialInkController? of(BuildContext context) {
+    return context.findAncestorRenderObjectOfType<_RenderInkFeatures>();
+  }
+
+  @override
+  _MaterialState createState() => _MaterialState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<MaterialType>('type', type));
+    properties.add(DoubleProperty('elevation', elevation, defaultValue: 0.0));
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
+    textStyle?.debugFillProperties(properties, prefix: 'textStyle.');
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('borderOnForeground', borderOnForeground, defaultValue: true));
+    properties.add(DiagnosticsProperty<BorderRadiusGeometry>('borderRadius', borderRadius, defaultValue: null));
+  }
+
+  /// The default radius of an ink splash in logical pixels.
+  static const double defaultSplashRadius = 35.0;
+}
+
+class _MaterialState extends State<Material> with TickerProviderStateMixin {
+  final GlobalKey _inkFeatureRenderer = GlobalKey(debugLabel: 'ink renderer');
+
+  Color? _getBackgroundColor(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    Color? color = widget.color;
+    if (color == null) {
+      switch (widget.type) {
+        case MaterialType.canvas:
+          color = theme.canvasColor;
+          break;
+        case MaterialType.card:
+          color = theme.cardColor;
+          break;
+        default:
+          break;
+      }
+    }
+    return color;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final Color? backgroundColor = _getBackgroundColor(context);
+    assert(
+      backgroundColor != null || widget.type == MaterialType.transparency,
+      'If Material type is not MaterialType.transparency, a color must '
+      'either be passed in through the `color` property, or be defined '
+      'in the theme (ex. canvasColor != null if type is set to '
+      'MaterialType.canvas)'
+    );
+    Widget? contents = widget.child;
+    if (contents != null) {
+      contents = AnimatedDefaultTextStyle(
+        style: widget.textStyle ?? Theme.of(context).textTheme.bodyText2!,
+        duration: widget.animationDuration,
+        child: contents,
+      );
+    }
+    contents = NotificationListener<LayoutChangedNotification>(
+      onNotification: (LayoutChangedNotification notification) {
+        final _RenderInkFeatures renderer = _inkFeatureRenderer.currentContext!.findRenderObject()! as _RenderInkFeatures;
+        renderer._didChangeLayout();
+        return false;
+      },
+      child: _InkFeatures(
+        key: _inkFeatureRenderer,
+        absorbHitTest: widget.type != MaterialType.transparency,
+        color: backgroundColor,
+        child: contents,
+        vsync: this,
+      ),
+    );
+
+    // PhysicalModel has a temporary workaround for a performance issue that
+    // speeds up rectangular non transparent material (the workaround is to
+    // skip the call to ui.Canvas.saveLayer if the border radius is 0).
+    // Until the saveLayer performance issue is resolved, we're keeping this
+    // special case here for canvas material type that is using the default
+    // shape (rectangle). We could go down this fast path for explicitly
+    // specified rectangles (e.g shape RoundedRectangleBorder with radius 0, but
+    // we choose not to as we want the change from the fast-path to the
+    // slow-path to be noticeable in the construction site of Material.
+    if (widget.type == MaterialType.canvas && widget.shape == null && widget.borderRadius == null) {
+      return AnimatedPhysicalModel(
+        curve: Curves.fastOutSlowIn,
+        duration: widget.animationDuration,
+        shape: BoxShape.rectangle,
+        clipBehavior: widget.clipBehavior,
+        borderRadius: BorderRadius.zero,
+        elevation: widget.elevation,
+        color: ElevationOverlay.applyOverlay(context, backgroundColor!, widget.elevation),
+        shadowColor: widget.shadowColor ?? Theme.of(context).shadowColor,
+        animateColor: false,
+        child: contents,
+      );
+    }
+
+    final ShapeBorder shape = _getShape();
+
+    if (widget.type == MaterialType.transparency) {
+      return _transparentInterior(
+        context: context,
+        shape: shape,
+        clipBehavior: widget.clipBehavior,
+        contents: contents,
+      );
+    }
+
+    return _MaterialInterior(
+      curve: Curves.fastOutSlowIn,
+      duration: widget.animationDuration,
+      shape: shape,
+      borderOnForeground: widget.borderOnForeground,
+      clipBehavior: widget.clipBehavior,
+      elevation: widget.elevation,
+      color: backgroundColor!,
+      shadowColor: widget.shadowColor ?? Theme.of(context).shadowColor,
+      child: contents,
+    );
+  }
+
+  static Widget _transparentInterior({
+    required BuildContext context,
+    required ShapeBorder shape,
+    required Clip clipBehavior,
+    required Widget contents,
+  }) {
+    final _ShapeBorderPaint child = _ShapeBorderPaint(
+      child: contents,
+      shape: shape,
+    );
+    if (clipBehavior == Clip.none) {
+      return child;
+    }
+    return ClipPath(
+      child: child,
+      clipper: ShapeBorderClipper(
+        shape: shape,
+        textDirection: Directionality.maybeOf(context),
+      ),
+      clipBehavior: clipBehavior,
+    );
+  }
+
+  // Determines the shape for this Material.
+  //
+  // If a shape was specified, it will determine the shape.
+  // If a borderRadius was specified, the shape is a rounded
+  // rectangle.
+  // Otherwise, the shape is determined by the widget type as described in the
+  // Material class documentation.
+  ShapeBorder _getShape() {
+    if (widget.shape != null)
+      return widget.shape!;
+    if (widget.borderRadius != null)
+      return RoundedRectangleBorder(borderRadius: widget.borderRadius!);
+    switch (widget.type) {
+      case MaterialType.canvas:
+      case MaterialType.transparency:
+        return const RoundedRectangleBorder();
+
+      case MaterialType.card:
+      case MaterialType.button:
+        return RoundedRectangleBorder(
+          borderRadius: widget.borderRadius ?? kMaterialEdges[widget.type]!,
+        );
+
+      case MaterialType.circle:
+        return const CircleBorder();
+    }
+  }
+}
+
+class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController {
+  _RenderInkFeatures({
+    RenderBox? child,
+    required this.vsync,
+    required this.absorbHitTest,
+    this.color,
+  }) : assert(vsync != null),
+       super(child);
+
+  // This class should exist in a 1:1 relationship with a MaterialState object,
+  // since there's no current support for dynamically changing the ticker
+  // provider.
+  @override
+  final TickerProvider vsync;
+
+  // This is here to satisfy the MaterialInkController contract.
+  // The actual painting of this color is done by a Container in the
+  // MaterialState build method.
+  @override
+  Color? color;
+
+  bool absorbHitTest;
+
+  List<InkFeature>? _inkFeatures;
+
+  @override
+  void addInkFeature(InkFeature feature) {
+    assert(!feature._debugDisposed);
+    assert(feature._controller == this);
+    _inkFeatures ??= <InkFeature>[];
+    assert(!_inkFeatures!.contains(feature));
+    _inkFeatures!.add(feature);
+    markNeedsPaint();
+  }
+
+  void _removeFeature(InkFeature feature) {
+    assert(_inkFeatures != null);
+    _inkFeatures!.remove(feature);
+    markNeedsPaint();
+  }
+
+  void _didChangeLayout() {
+    if (_inkFeatures != null && _inkFeatures!.isNotEmpty)
+      markNeedsPaint();
+  }
+
+  @override
+  bool hitTestSelf(Offset position) => absorbHitTest;
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (_inkFeatures != null && _inkFeatures!.isNotEmpty) {
+      final Canvas canvas = context.canvas;
+      canvas.save();
+      canvas.translate(offset.dx, offset.dy);
+      canvas.clipRect(Offset.zero & size);
+      for (final InkFeature inkFeature in _inkFeatures!)
+        inkFeature._paint(canvas);
+      canvas.restore();
+    }
+    super.paint(context, offset);
+  }
+}
+
+class _InkFeatures extends SingleChildRenderObjectWidget {
+  const _InkFeatures({
+    Key? key,
+    this.color,
+    required this.vsync,
+    required this.absorbHitTest,
+    Widget? child,
+  }) : super(key: key, child: child);
+
+  // This widget must be owned by a MaterialState, which must be provided as the vsync.
+  // This relationship must be 1:1 and cannot change for the lifetime of the MaterialState.
+
+  final Color? color;
+
+  final TickerProvider vsync;
+
+  final bool absorbHitTest;
+
+  @override
+  _RenderInkFeatures createRenderObject(BuildContext context) {
+    return _RenderInkFeatures(
+      color: color,
+      absorbHitTest: absorbHitTest,
+      vsync: vsync,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderInkFeatures renderObject) {
+    renderObject..color = color
+                ..absorbHitTest = absorbHitTest;
+    assert(vsync == renderObject.vsync);
+  }
+}
+
+/// A visual reaction on a piece of [Material].
+///
+/// To add an ink feature to a piece of [Material], obtain the
+/// [MaterialInkController] via [Material.of] and call
+/// [MaterialInkController.addInkFeature].
+abstract class InkFeature {
+  /// Initializes fields for subclasses.
+  InkFeature({
+    required MaterialInkController controller,
+    required this.referenceBox,
+    this.onRemoved,
+  }) : assert(controller != null),
+       assert(referenceBox != null),
+       _controller = controller as _RenderInkFeatures;
+
+  /// The [MaterialInkController] associated with this [InkFeature].
+  ///
+  /// Typically used by subclasses to call
+  /// [MaterialInkController.markNeedsPaint] when they need to repaint.
+  MaterialInkController get controller => _controller;
+  final _RenderInkFeatures _controller;
+
+  /// The render box whose visual position defines the frame of reference for this ink feature.
+  final RenderBox referenceBox;
+
+  /// Called when the ink feature is no longer visible on the material.
+  final VoidCallback? onRemoved;
+
+  bool _debugDisposed = false;
+
+  /// Free up the resources associated with this ink feature.
+  @mustCallSuper
+  void dispose() {
+    assert(!_debugDisposed);
+    assert(() {
+      _debugDisposed = true;
+      return true;
+    }());
+    _controller._removeFeature(this);
+    if (onRemoved != null)
+      onRemoved!();
+  }
+
+  void _paint(Canvas canvas) {
+    assert(referenceBox.attached);
+    assert(!_debugDisposed);
+    // find the chain of renderers from us to the feature's referenceBox
+    final List<RenderObject> descendants = <RenderObject>[referenceBox];
+    RenderObject node = referenceBox;
+    while (node != _controller) {
+      node = node.parent! as RenderObject;
+      descendants.add(node);
+    }
+    // determine the transform that gets our coordinate system to be like theirs
+    final Matrix4 transform = Matrix4.identity();
+    assert(descendants.length >= 2);
+    for (int index = descendants.length - 1; index > 0; index -= 1)
+      descendants[index].applyPaintTransform(descendants[index - 1], transform);
+    paintFeature(canvas, transform);
+  }
+
+  /// Override this method to paint the ink feature.
+  ///
+  /// The transform argument gives the coordinate conversion from the coordinate
+  /// system of the canvas to the coordinate system of the [referenceBox].
+  @protected
+  void paintFeature(Canvas canvas, Matrix4 transform);
+
+  @override
+  String toString() => describeIdentity(this);
+}
+
+/// An interpolation between two [ShapeBorder]s.
+///
+/// This class specializes the interpolation of [Tween] to use [ShapeBorder.lerp].
+class ShapeBorderTween extends Tween<ShapeBorder?> {
+  /// Creates a [ShapeBorder] tween.
+  ///
+  /// the [begin] and [end] properties may be null; see [ShapeBorder.lerp] for
+  /// the null handling semantics.
+  ShapeBorderTween({ShapeBorder? begin, ShapeBorder? end}) : super(begin: begin, end: end);
+
+  /// Returns the value this tween has at the given animation clock value.
+  @override
+  ShapeBorder? lerp(double t) {
+    return ShapeBorder.lerp(begin, end, t);
+  }
+}
+
+/// The interior of non-transparent material.
+///
+/// Animates [elevation], [shadowColor], and [shape].
+class _MaterialInterior extends ImplicitlyAnimatedWidget {
+  /// Creates a const instance of [_MaterialInterior].
+  ///
+  /// The [child], [shape], [clipBehavior], [color], and [shadowColor] arguments
+  /// must not be null. The [elevation] must be specified and greater than or
+  /// equal to zero.
+  const _MaterialInterior({
+    Key? key,
+    required this.child,
+    required this.shape,
+    this.borderOnForeground = true,
+    this.clipBehavior = Clip.none,
+    required this.elevation,
+    required this.color,
+    required this.shadowColor,
+    Curve curve = Curves.linear,
+    required Duration duration,
+  }) : assert(child != null),
+       assert(shape != null),
+       assert(clipBehavior != null),
+       assert(elevation != null && elevation >= 0.0),
+       assert(color != null),
+       assert(shadowColor != null),
+       super(key: key, curve: curve, duration: duration);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The border of the widget.
+  ///
+  /// This border will be painted, and in addition the outer path of the border
+  /// determines the physical shape.
+  final ShapeBorder shape;
+
+  /// Whether to paint the border in front of the child.
+  ///
+  /// The default value is true.
+  /// If false, the border will be painted behind the child.
+  final bool borderOnForeground;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  final Clip clipBehavior;
+
+  /// The target z-coordinate at which to place this physical object relative
+  /// to its parent.
+  ///
+  /// The value is non-negative.
+  final double elevation;
+
+  /// The target background color.
+  final Color color;
+
+  /// The target shadow color.
+  final Color shadowColor;
+
+  @override
+  _MaterialInteriorState createState() => _MaterialInteriorState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(DiagnosticsProperty<ShapeBorder>('shape', shape));
+    description.add(DoubleProperty('elevation', elevation));
+    description.add(ColorProperty('color', color));
+    description.add(ColorProperty('shadowColor', shadowColor));
+  }
+}
+
+class _MaterialInteriorState extends AnimatedWidgetBaseState<_MaterialInterior> {
+  Tween<double>? _elevation;
+  ColorTween? _shadowColor;
+  ShapeBorderTween? _border;
+
+  @override
+  void forEachTween(TweenVisitor<dynamic> visitor) {
+    _elevation = visitor(
+      _elevation,
+      widget.elevation,
+      (dynamic value) => Tween<double>(begin: value as double),
+    ) as Tween<double>?;
+    _shadowColor = visitor(
+      _shadowColor,
+      widget.shadowColor,
+      (dynamic value) => ColorTween(begin: value as Color),
+    ) as ColorTween?;
+    _border = visitor(
+      _border,
+      widget.shape,
+      (dynamic value) => ShapeBorderTween(begin: value as ShapeBorder),
+    ) as ShapeBorderTween?;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ShapeBorder shape = _border!.evaluate(animation)!;
+    final double elevation = _elevation!.evaluate(animation);
+    return PhysicalShape(
+      child: _ShapeBorderPaint(
+        child: widget.child,
+        shape: shape,
+        borderOnForeground: widget.borderOnForeground,
+      ),
+      clipper: ShapeBorderClipper(
+        shape: shape,
+        textDirection: Directionality.maybeOf(context),
+      ),
+      clipBehavior: widget.clipBehavior,
+      elevation: elevation,
+      color: ElevationOverlay.applyOverlay(context, widget.color, elevation),
+      shadowColor: _shadowColor!.evaluate(animation)!,
+    );
+  }
+}
+
+class _ShapeBorderPaint extends StatelessWidget {
+  const _ShapeBorderPaint({
+    required this.child,
+    required this.shape,
+    this.borderOnForeground = true,
+  });
+
+  final Widget child;
+  final ShapeBorder shape;
+  final bool borderOnForeground;
+
+  @override
+  Widget build(BuildContext context) {
+    return CustomPaint(
+      child: child,
+      painter: borderOnForeground ? null : _ShapeBorderPainter(shape, Directionality.maybeOf(context)),
+      foregroundPainter: borderOnForeground ? _ShapeBorderPainter(shape, Directionality.maybeOf(context)) : null,
+    );
+  }
+}
+
+class _ShapeBorderPainter extends CustomPainter {
+  _ShapeBorderPainter(this.border, this.textDirection);
+  final ShapeBorder border;
+  final TextDirection? textDirection;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    border.paint(canvas, Offset.zero & size, textDirection: textDirection);
+  }
+
+  @override
+  bool shouldRepaint(_ShapeBorderPainter oldDelegate) {
+    return oldDelegate.border != border;
+  }
+}
diff --git a/lib/src/material/material_button.dart b/lib/src/material/material_button.dart
new file mode 100644
index 0000000..868121e
--- /dev/null
+++ b/lib/src/material/material_button.dart
@@ -0,0 +1,465 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'button.dart';
+import 'button_theme.dart';
+import 'constants.dart';
+import 'ink_well.dart';
+import 'material.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// A utility class for building Material buttons that depend on the
+/// ambient [ButtonTheme] and [Theme].
+///
+/// ### This class is obsolete.
+///
+/// FlatButton, RaisedButton, and OutlineButton have been replaced by
+/// TextButton, ElevatedButton, and OutlinedButton respectively.
+/// ButtonTheme has been replaced by TextButtonTheme,
+/// ElevatedButtonTheme, and OutlinedButtonTheme. The appearance of the
+/// new widgets can be customized by specifying a [ButtonStyle]
+/// or by creating a one-off style using a `styleFrom` method like
+/// [TextButton.styleFrom]. The original button classes
+/// will be deprecated soon, please migrate code that uses them.
+/// There's a detailed migration guide for the new button and button
+/// theme classes in
+/// [flutter.dev/go/material-button-migration-guide](https://flutter.dev/go/material-button-migration-guide).
+///
+/// The button's size will expand to fit the child widget, if necessary.
+///
+/// MaterialButtons whose [onPressed] and [onLongPress] callbacks are null will be disabled. To have
+/// an enabled button, make sure to pass a non-null value for [onPressed] or [onLongPress].
+///
+/// Rather than using this class directly, consider using [FlatButton],
+/// [OutlineButton], or [RaisedButton], which configure this class with
+/// appropriate defaults that match the material design specification.
+///
+/// To create a button directly, without inheriting theme defaults, use
+/// [RawMaterialButton].
+///
+/// If you want an ink-splash effect for taps, but don't want to use a button,
+/// consider using [InkWell] directly.
+///
+/// See also:
+///
+///  * [IconButton], to create buttons that contain icons rather than text.
+class MaterialButton extends StatelessWidget {
+  /// Creates a material button.
+  ///
+  /// Rather than creating a material button directly, consider using
+  /// [FlatButton] or [RaisedButton]. To create a custom Material button
+  /// consider using [RawMaterialButton].
+  ///
+  /// The [autofocus] and [clipBehavior] arguments must not be null.
+  /// Additionally,  [elevation], [hoverElevation], [focusElevation],
+  /// [highlightElevation], and [disabledElevation] must be non-negative, if
+  /// specified.
+  const MaterialButton({
+    Key? key,
+    required this.onPressed,
+    this.onLongPress,
+    this.onHighlightChanged,
+    this.mouseCursor,
+    this.textTheme,
+    this.textColor,
+    this.disabledTextColor,
+    this.color,
+    this.disabledColor,
+    this.focusColor,
+    this.hoverColor,
+    this.highlightColor,
+    this.splashColor,
+    this.colorBrightness,
+    this.elevation,
+    this.focusElevation,
+    this.hoverElevation,
+    this.highlightElevation,
+    this.disabledElevation,
+    this.padding,
+    this.visualDensity,
+    this.shape,
+    this.clipBehavior = Clip.none,
+    this.focusNode,
+    this.autofocus = false,
+    this.materialTapTargetSize,
+    this.animationDuration,
+    this.minWidth,
+    this.height,
+    this.enableFeedback = true,
+    this.child,
+  }) : assert(clipBehavior != null),
+       assert(autofocus != null),
+       assert(elevation == null || elevation >= 0.0),
+       assert(focusElevation == null || focusElevation >= 0.0),
+       assert(hoverElevation == null || hoverElevation >= 0.0),
+       assert(highlightElevation == null || highlightElevation >= 0.0),
+       assert(disabledElevation == null || disabledElevation >= 0.0),
+       super(key: key);
+
+  /// The callback that is called when the button is tapped or otherwise activated.
+  ///
+  /// If this callback and [onLongPress] are null, then the button will be disabled.
+  ///
+  /// See also:
+  ///
+  ///  * [enabled], which is true if the button is enabled.
+  final VoidCallback? onPressed;
+
+  /// The callback that is called when the button is long-pressed.
+  ///
+  /// If this callback and [onPressed] are null, then the button will be disabled.
+  ///
+  /// See also:
+  ///
+  ///  * [enabled], which is true if the button is enabled.
+  final VoidCallback? onLongPress;
+
+  /// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
+  /// callback.
+  ///
+  /// If [onPressed] changes from null to non-null while a gesture is ongoing,
+  /// this can fire during the build phase (in which case calling
+  /// [State.setState] is not allowed).
+  final ValueChanged<bool>? onHighlightChanged;
+
+  /// {@macro flutter.material.RawMaterialButton.mouseCursor}
+  final MouseCursor? mouseCursor;
+
+  /// Defines the button's base colors, and the defaults for the button's minimum
+  /// size, internal padding, and shape.
+  ///
+  /// Defaults to `ButtonTheme.of(context).textTheme`.
+  final ButtonTextTheme? textTheme;
+
+  /// The color to use for this button's text.
+  ///
+  /// The button's [Material.textStyle] will be the current theme's button text
+  /// style, [TextTheme.button] of [ThemeData.textTheme], configured with this
+  /// color.
+  ///
+  /// The default text color depends on the button theme's text theme,
+  /// [ButtonThemeData.textTheme].
+  ///
+  /// If [textColor] is a [MaterialStateProperty<Color>], [disabledTextColor]
+  /// will be ignored.
+  ///
+  /// See also:
+  ///
+  ///  * [disabledTextColor], the text color to use when the button has been
+  ///    disabled.
+  final Color? textColor;
+
+  /// The color to use for this button's text when the button is disabled.
+  ///
+  /// The button's [Material.textStyle] will be the current theme's button text
+  /// style, [TextTheme.button] of [ThemeData.textTheme], configured with this
+  /// color.
+  ///
+  /// The default value is the theme's disabled color,
+  /// [ThemeData.disabledColor].
+  ///
+  /// If [textColor] is a [MaterialStateProperty<Color>], [disabledTextColor]
+  /// will be ignored.
+  ///
+  /// See also:
+  ///
+  ///  * [textColor] - The color to use for this button's text when the button is [enabled].
+  final Color? disabledTextColor;
+
+  /// The button's fill color, displayed by its [Material], while it
+  /// is in its default (unpressed, [enabled]) state.
+  ///
+  /// The default fill color is the theme's button color, [ThemeData.buttonColor].
+  ///
+  /// See also:
+  ///
+  ///  * [disabledColor] - the fill color of the button when the button is disabled.
+  final Color? color;
+
+  /// The fill color of the button when the button is disabled.
+  ///
+  /// The default value of this color is the theme's disabled color,
+  /// [ThemeData.disabledColor].
+  ///
+  /// See also:
+  ///
+  ///  * [color] - the fill color of the button when the button is [enabled].
+  final Color? disabledColor;
+
+  /// The splash color of the button's [InkWell].
+  ///
+  /// The ink splash indicates that the button has been touched. It
+  /// appears on top of the button's child and spreads in an expanding
+  /// circle beginning where the touch occurred.
+  ///
+  /// The default splash color is the current theme's splash color,
+  /// [ThemeData.splashColor].
+  ///
+  /// The appearance of the splash can be configured with the theme's splash
+  /// factory, [ThemeData.splashFactory].
+  final Color? splashColor;
+
+  /// The fill color of the button's [Material] when it has the input focus.
+  ///
+  /// The button changed focus color when the button has the input focus. It
+  /// appears behind the button's child.
+  final Color? focusColor;
+
+  /// The fill color of the button's [Material] when a pointer is hovering over
+  /// it.
+  ///
+  /// The button changes fill color when a pointer is hovering over the button.
+  /// It appears behind the button's child.
+  final Color? hoverColor;
+
+  /// The highlight color of the button's [InkWell].
+  ///
+  /// The highlight indicates that the button is actively being pressed. It
+  /// appears on top of the button's child and quickly spreads to fill
+  /// the button, and then fades out.
+  ///
+  /// If [textTheme] is [ButtonTextTheme.primary], the default highlight color is
+  /// transparent (in other words the highlight doesn't appear). Otherwise it's
+  /// the current theme's highlight color, [ThemeData.highlightColor].
+  final Color? highlightColor;
+
+  /// The z-coordinate at which to place this button relative to its parent.
+  ///
+  /// This controls the size of the shadow below the raised button.
+  ///
+  /// Defaults to 2, the appropriate elevation for raised buttons. The value
+  /// is always non-negative.
+  ///
+  /// See also:
+  ///
+  ///  * [FlatButton], a button with no elevation or fill color.
+  ///  * [focusElevation], the elevation when the button is focused.
+  ///  * [hoverElevation], the elevation when a pointer is hovering over the
+  ///    button.
+  ///  * [disabledElevation], the elevation when the button is disabled.
+  ///  * [highlightElevation], the elevation when the button is pressed.
+  final double? elevation;
+
+  /// The elevation for the button's [Material] when the button
+  /// is [enabled] and a pointer is hovering over it.
+  ///
+  /// Defaults to 4.0. The value is always non-negative.
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], the default elevation.
+  ///  * [focusElevation], the elevation when the button is focused.
+  ///  * [disabledElevation], the elevation when the button is disabled.
+  ///  * [highlightElevation], the elevation when the button is pressed.
+  final double? hoverElevation;
+
+  /// The elevation for the button's [Material] when the button
+  /// is [enabled] and has the input focus.
+  ///
+  /// Defaults to 4.0. The value is always non-negative.
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], the default elevation.
+  ///  * [hoverElevation], the elevation when a pointer is hovering over the
+  ///    button.
+  ///  * [disabledElevation], the elevation when the button is disabled.
+  ///  * [highlightElevation], the elevation when the button is pressed.
+  final double? focusElevation;
+
+  /// The elevation for the button's [Material] relative to its parent when the
+  /// button is [enabled] and pressed.
+  ///
+  /// This controls the size of the shadow below the button. When a tap
+  /// down gesture occurs within the button, its [InkWell] displays a
+  /// [highlightColor] "highlight".
+  ///
+  /// Defaults to 8.0. The value is always non-negative.
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], the default elevation.
+  ///  * [focusElevation], the elevation when the button is focused.
+  ///  * [hoverElevation], the elevation when a pointer is hovering over the
+  ///    button.
+  ///  * [disabledElevation], the elevation when the button is disabled.
+  final double? highlightElevation;
+
+  /// The elevation for the button's [Material] relative to its parent when the
+  /// button is not [enabled].
+  ///
+  /// Defaults to 0.0. The value is always non-negative.
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], the default elevation.
+  ///  * [highlightElevation], the elevation when the button is pressed.
+  final double? disabledElevation;
+
+  /// The theme brightness to use for this button.
+  ///
+  /// Defaults to the theme's brightness in [ThemeData.brightness]. Setting
+  /// this value determines the button text's colors based on
+  /// [ButtonThemeData.getTextColor].
+  ///
+  /// See also:
+  ///
+  ///  * [ButtonTextTheme], uses [Brightness] to determine text color.
+  final Brightness? colorBrightness;
+
+  /// The button's label.
+  ///
+  /// Often a [Text] widget in all caps.
+  final Widget? child;
+
+  /// Whether the button is enabled or disabled.
+  ///
+  /// Buttons are disabled by default. To enable a button, set its [onPressed]
+  /// or [onLongPress] properties to a non-null value.
+  bool get enabled => onPressed != null || onLongPress != null;
+
+  /// The internal padding for the button's [child].
+  ///
+  /// Defaults to the value from the current [ButtonTheme],
+  /// [ButtonThemeData.padding].
+  final EdgeInsetsGeometry? padding;
+
+  /// Defines how compact the button's layout will be.
+  ///
+  /// {@macro flutter.material.themedata.visualDensity}
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.visualDensity], which specifies the [visualDensity] for all
+  ///    widgets within a [Theme].
+  final VisualDensity? visualDensity;
+
+  /// The shape of the button's [Material].
+  ///
+  /// The button's highlight and splash are clipped to this shape. If the
+  /// button has an elevation, then its drop shadow is defined by this
+  /// shape as well.
+  ///
+  /// Defaults to the value from the current [ButtonTheme],
+  /// [ButtonThemeData.shape].
+  final ShapeBorder? shape;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  final Clip clipBehavior;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  /// Defines the duration of animated changes for [shape] and [elevation].
+  ///
+  /// The default value is [kThemeChangeDuration].
+  final Duration? animationDuration;
+
+  /// Configures the minimum size of the tap target.
+  ///
+  /// Defaults to [ThemeData.materialTapTargetSize].
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialTapTargetSize], for a description of how this affects tap targets.
+  final MaterialTapTargetSize? materialTapTargetSize;
+
+  /// The smallest horizontal extent that the button will occupy.
+  ///
+  /// Defaults to the value from the current [ButtonTheme].
+  final double? minWidth;
+
+  /// The vertical extent of the button.
+  ///
+  /// Defaults to the value from the current [ButtonTheme].
+  final double? height;
+
+  /// Whether detected gestures should provide acoustic and/or haptic feedback.
+  ///
+  /// For example, on Android a tap will produce a clicking sound and a
+  /// long-press will produce a short vibration, when feedback is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [Feedback] for providing platform-specific feedback to certain actions.
+  final bool enableFeedback;
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final ButtonThemeData buttonTheme = ButtonTheme.of(context);
+
+    return RawMaterialButton(
+      onPressed: onPressed,
+      onLongPress: onLongPress,
+      enableFeedback: enableFeedback,
+      onHighlightChanged: onHighlightChanged,
+      mouseCursor: mouseCursor,
+      fillColor: buttonTheme.getFillColor(this),
+      textStyle: theme.textTheme.button!.copyWith(color: buttonTheme.getTextColor(this)),
+      focusColor: focusColor ?? buttonTheme.getFocusColor(this),
+      hoverColor: hoverColor ?? buttonTheme.getHoverColor(this),
+      highlightColor: highlightColor ?? theme.highlightColor,
+      splashColor: splashColor ?? theme.splashColor,
+      elevation: buttonTheme.getElevation(this),
+      focusElevation: buttonTheme.getFocusElevation(this),
+      hoverElevation: buttonTheme.getHoverElevation(this),
+      highlightElevation: buttonTheme.getHighlightElevation(this),
+      padding: buttonTheme.getPadding(this),
+      visualDensity: visualDensity ?? theme.visualDensity,
+      constraints: buttonTheme.getConstraints(this).copyWith(
+        minWidth: minWidth,
+        minHeight: height,
+      ),
+      shape: buttonTheme.getShape(this),
+      clipBehavior: clipBehavior,
+      focusNode: focusNode,
+      autofocus: autofocus,
+      animationDuration: buttonTheme.getAnimationDuration(this),
+      child: child,
+      materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
+      disabledElevation: disabledElevation ?? 0.0,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('enabled', value: enabled, ifFalse: 'disabled'));
+    properties.add(DiagnosticsProperty<ButtonTextTheme>('textTheme', textTheme, defaultValue: null));
+    properties.add(ColorProperty('textColor', textColor, defaultValue: null));
+    properties.add(ColorProperty('disabledTextColor', disabledTextColor, defaultValue: null));
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(ColorProperty('disabledColor', disabledColor, defaultValue: null));
+    properties.add(ColorProperty('focusColor', focusColor, defaultValue: null));
+    properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: null));
+    properties.add(ColorProperty('highlightColor', highlightColor, defaultValue: null));
+    properties.add(ColorProperty('splashColor', splashColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<Brightness>('colorBrightness', colorBrightness, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
+    properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
+    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
+  }
+}
+
+/// The type of [MaterialButton]s created with [RaisedButton.icon], [FlatButton.icon],
+/// and [OutlineButton.icon].
+///
+/// This mixin only exists to give the "label and icon" button widgets a distinct
+/// type for the sake of [ButtonTheme].
+mixin MaterialButtonWithIconMixin { }
diff --git a/lib/src/material/material_localizations.dart b/lib/src/material/material_localizations.dart
new file mode 100644
index 0000000..d685833
--- /dev/null
+++ b/lib/src/material/material_localizations.dart
@@ -0,0 +1,1073 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'debug.dart';
+import 'time.dart';
+import 'typography.dart';
+
+// ADDING A NEW STRING
+//
+// If you (someone contributing to the Flutter framework) want to add a new
+// string to the MaterialLocalizations object (e.g. because you've added a new
+// widget and it has a tooltip), follow these steps:
+//
+// 1. Add the new getter to MaterialLocalizations below.
+//
+// 2. Implement a default value in DefaultMaterialLocalizations below.
+//
+// 3. Add a test to test/material/localizations_test.dart that verifies that
+//    this new value is implemented.
+//
+// 4. Update the flutter_localizations package. To add a new string to the
+//    flutter_localizations package, you must first add it to the English
+//    translations (lib/src/l10n/material_en.arb), including a description.
+//
+//    Then you need to add new entries for the string to all of the other
+//    language locale files by running:
+//    ```
+//    dart dev/tools/localization/bin/gen_missing_localizations.dart
+//    ```
+//    Which will copy the english strings into the other locales as placeholders
+//    until they can be translated.
+//
+//    Finally you need to re-generate lib/src/l10n/localizations.dart by running:
+//    ```
+//    dart dev/tools/localization/bin/gen_localizations.dart --overwrite
+//    ```
+//
+//    There is a README file with further information in the lib/src/l10n/
+//    directory.
+//
+// 5. If you are a Google employee, you should then also follow the instructions
+//    at go/flutter-l10n. If you're not, don't worry about it.
+//
+// UPDATING AN EXISTING STRING
+//
+// If you (someone contributing to the Flutter framework) want to modify an
+// existing string in the MaterialLocalizations objects, follow these steps:
+//
+// 1. Modify the default value of the relevant getter(s) in
+//    DefaultMaterialLocalizations below.
+//
+// 2. Update the flutter_localizations package. Modify the out-of-date English
+//    strings in lib/src/l10n/material_en.arb.
+//
+//    You also need to re-generate lib/src/l10n/localizations.dart by running:
+//    ```
+//    dart dev/tools/localization/bin/gen_localizations.dart --overwrite
+//    ```
+//
+//    This script may result in your updated getters being created in newer
+//    locales and set to the old value of the strings. This is to be expected.
+//    Leave them as they were generated, and they will be picked up for
+//    translation.
+//
+//    There is a README file with further information in the lib/src/l10n/
+//    directory.
+//
+// 3. If you are a Google employee, you should then also follow the instructions
+//    at go/flutter-l10n. If you're not, don't worry about it.
+
+/// Defines the localized resource values used by the Material widgets.
+///
+/// See also:
+///
+///  * [DefaultMaterialLocalizations], the default, English-only, implementation
+///    of this interface.
+///  * [GlobalMaterialLocalizations], which provides material localizations for
+///    many languages.
+abstract class MaterialLocalizations {
+  /// The tooltip for the leading [AppBar] menu (a.k.a. 'hamburger') button.
+  String get openAppDrawerTooltip;
+
+  /// The [BackButton]'s tooltip.
+  String get backButtonTooltip;
+
+  /// The [CloseButton]'s tooltip.
+  String get closeButtonTooltip;
+
+  /// The tooltip for the delete button on a [Chip].
+  String get deleteButtonTooltip;
+
+  /// The tooltip for the more button on an overflowing text selection menu.
+  String get moreButtonTooltip;
+
+  /// The tooltip for the [MonthPicker]'s "next month" button.
+  String get nextMonthTooltip;
+
+  /// The tooltip for the [MonthPicker]'s "previous month" button.
+  String get previousMonthTooltip;
+
+  /// The tooltip for the [PaginatedDataTable]'s "next page" button.
+  String get nextPageTooltip;
+
+  /// The tooltip for the [PaginatedDataTable]'s "previous page" button.
+  String get previousPageTooltip;
+
+  /// The default [PopupMenuButton] tooltip.
+  String get showMenuTooltip;
+
+  /// The default title for [AboutListTile].
+  String aboutListTileTitle(String applicationName);
+
+  /// Title for the [LicensePage] widget.
+  String get licensesPageTitle;
+
+  /// Subtitle for a package in the [LicensePage] widget.
+  String licensesPackageDetailText(int licenseCount);
+
+  /// Title for the [PaginatedDataTable]'s row info footer.
+  String pageRowsInfoTitle(int firstRow, int lastRow, int rowCount, bool rowCountIsApproximate);
+
+  /// Title for the [PaginatedDataTable]'s "rows per page" footer.
+  String get rowsPerPageTitle;
+
+  /// The accessibility label used on a tab in a [TabBar].
+  ///
+  /// This message describes the index of the selected tab and how many tabs
+  /// there are, e.g. 'Tab 1 of 2' in United States English.
+  ///
+  /// `tabIndex` and `tabCount` must be greater than or equal to one.
+  String tabLabel({ required int tabIndex, required int tabCount });
+
+  /// Title for the [PaginatedDataTable]'s selected row count header.
+  String selectedRowCountTitle(int selectedRowCount);
+
+  /// Label for "cancel" buttons and menu items.
+  String get cancelButtonLabel;
+
+  /// Label for "close" buttons and menu items.
+  String get closeButtonLabel;
+
+  /// Label for "continue" buttons and menu items.
+  String get continueButtonLabel;
+
+  /// Label for "copy" edit buttons and menu items.
+  String get copyButtonLabel;
+
+  /// Label for "cut" edit buttons and menu items.
+  String get cutButtonLabel;
+
+  /// Label for OK buttons and menu items.
+  String get okButtonLabel;
+
+  /// Label for "paste" edit buttons and menu items.
+  String get pasteButtonLabel;
+
+  /// Label for "select all" edit buttons and menu items.
+  String get selectAllButtonLabel;
+
+  /// Label for the [AboutDialog] button that shows the [LicensePage].
+  String get viewLicensesButtonLabel;
+
+  /// The abbreviation for ante meridiem (before noon) shown in the time picker.
+  String get anteMeridiemAbbreviation;
+
+  /// The abbreviation for post meridiem (after noon) shown in the time picker.
+  String get postMeridiemAbbreviation;
+
+  /// The text-to-speech announcement made when a time picker invoked using
+  /// [showTimePicker] is set to the hour picker mode.
+  String get timePickerHourModeAnnouncement;
+
+  /// The text-to-speech announcement made when a time picker invoked using
+  /// [showTimePicker] is set to the minute picker mode.
+  String get timePickerMinuteModeAnnouncement;
+
+  /// Label read out by accessibility tools (TalkBack or VoiceOver) for a modal
+  /// barrier to indicate that a tap dismisses the barrier.
+  ///
+  /// A modal barrier can for example be found behind an alert or popup to block
+  /// user interaction with elements behind it.
+  String get modalBarrierDismissLabel;
+
+  /// Label read out by accessibility tools (TalkBack or VoiceOver) when a
+  /// drawer widget is opened.
+  String get drawerLabel;
+
+  /// Label read out by accessibility tools (TalkBack or VoiceOver) when a
+  /// popup menu widget is opened.
+  String get popupMenuLabel;
+
+  /// Label read out by accessibility tools (TalkBack or VoiceOver) when a
+  /// dialog widget is opened.
+  String get dialogLabel;
+
+  /// Label read out by accessibility tools (TalkBack or VoiceOver) when an
+  /// alert dialog widget is opened.
+  String get alertDialogLabel;
+
+  /// Label indicating that a text field is a search field. This will be used
+  /// as a hint text in the text field.
+  String get searchFieldLabel;
+
+  /// The format used to lay out the time picker.
+  ///
+  /// The documentation for [TimeOfDayFormat] enum values provides details on
+  /// each supported layout.
+  TimeOfDayFormat timeOfDayFormat({ bool alwaysUse24HourFormat = false });
+
+  /// Defines the localized [TextStyle] geometry for [ThemeData.textTheme].
+  ///
+  /// The [scriptCategory] defines the overall geometry of a [TextTheme] for
+  /// the [Typography.geometryThemeFor] method in terms of the
+  /// three language categories defined in https://material.io/go/design-typography.
+  ///
+  /// Generally speaking, font sizes for [ScriptCategory.tall] and
+  /// [ScriptCategory.dense] scripts - for text styles that are smaller than the
+  /// title style - are one unit larger than they are for
+  /// [ScriptCategory.englishLike] scripts.
+  ScriptCategory get scriptCategory;
+
+  /// Formats [number] as a decimal, inserting locale-appropriate thousands
+  /// separators as necessary.
+  String formatDecimal(int number);
+
+  /// Formats [TimeOfDay.hour] in the given time of day according to the value
+  /// of [timeOfDayFormat].
+  ///
+  /// If [alwaysUse24HourFormat] is true, formats hour using [HourFormat.HH]
+  /// rather than the default for the current locale.
+  String formatHour(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat = false });
+
+  /// Formats [TimeOfDay.minute] in the given time of day according to the value
+  /// of [timeOfDayFormat].
+  String formatMinute(TimeOfDay timeOfDay);
+
+  /// Formats [timeOfDay] according to the value of [timeOfDayFormat].
+  ///
+  /// If [alwaysUse24HourFormat] is true, formats hour using [HourFormat.HH]
+  /// rather than the default for the current locale. This value is usually
+  /// passed from [MediaQueryData.alwaysUse24HourFormat], which has platform-
+  /// specific behavior.
+  String formatTimeOfDay(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat = false });
+
+  /// Full unabbreviated year format, e.g. 2017 rather than 17.
+  String formatYear(DateTime date);
+
+  /// Formats the date in a compact format.
+  ///
+  /// Usually just the numeric values for the for day, month and year are used.
+  ///
+  /// Examples:
+  ///
+  /// - US English: 02/21/2019
+  /// - Russian: 21.02.2019
+  ///
+  /// See also:
+  ///   * [parseCompactDate], which will convert a compact date string to a [DateTime].
+  String formatCompactDate(DateTime date);
+
+  /// Formats the date using a short-width format.
+  ///
+  /// Includes the abbreviation of the month, the day and year.
+  ///
+  /// Examples:
+  ///
+  /// - US English: Feb 21, 2019
+  /// - Russian: 21 февр. 2019 г.
+  String formatShortDate(DateTime date);
+
+  /// Formats the date using a medium-width format.
+  ///
+  /// Abbreviates month and days of week. This appears in the header of the date
+  /// picker invoked using [showDatePicker].
+  ///
+  /// Examples:
+  ///
+  /// - US English: Wed, Sep 27
+  /// - Russian: ср, сент. 27
+  String formatMediumDate(DateTime date);
+
+  /// Formats day of week, month, day of month and year in a long-width format.
+  ///
+  /// Does not abbreviate names. Appears in spoken announcements of the date
+  /// picker invoked using [showDatePicker], when accessibility mode is on.
+  ///
+  /// Examples:
+  ///
+  /// - US English: Wednesday, September 27, 2017
+  /// - Russian: Среда, Сентябрь 27, 2017
+  String formatFullDate(DateTime date);
+
+  /// Formats the month and the year of the given [date].
+  ///
+  /// The returned string does not contain the day of the month. This appears
+  /// in the date picker invoked using [showDatePicker].
+  String formatMonthYear(DateTime date);
+
+  /// Formats the month and day of the given [date].
+  ///
+  /// Examples:
+  ///
+  /// - US English: Feb 21
+  /// - Russian: 21 февр.
+  String formatShortMonthDay(DateTime date);
+
+  /// Converts the given compact date formatted string into a [DateTime].
+  ///
+  /// The format of the string must be a valid compact date format for the
+  /// given locale. If the text doesn't represent a valid date, `null` will be
+  /// returned.
+  ///
+  /// See also:
+  ///   * [formatCompactDate], which will convert a [DateTime] into a string in the compact format.
+  DateTime? parseCompactDate(String? inputString);
+
+  /// List of week day names in narrow format, usually 1- or 2-letter
+  /// abbreviations of full names.
+  ///
+  /// The list begins with the value corresponding to Sunday and ends with
+  /// Saturday. Use [firstDayOfWeekIndex] to find the first day of week in this
+  /// list.
+  ///
+  /// Examples:
+  ///
+  /// - US English: S, M, T, W, T, F, S
+  /// - Russian: вс, пн, вт, ср, чт, пт, сб - notice that the list begins with
+  ///   вс (Sunday) even though the first day of week for Russian is Monday.
+  List<String> get narrowWeekdays;
+
+  /// Index of the first day of week, where 0 points to Sunday, and 6 points to
+  /// Saturday.
+  ///
+  /// This getter is compatible with [narrowWeekdays]. For example:
+  ///
+  /// ```dart
+  /// var localizations = MaterialLocalizations.of(context);
+  /// // The name of the first day of week for the current locale.
+  /// var firstDayOfWeek = localizations.narrowWeekdays[localizations.firstDayOfWeekIndex];
+  /// ```
+  int get firstDayOfWeekIndex;
+
+  /// The character string used to separate the parts of a compact date format
+  /// (i.e. mm/dd/yyyy has a separator of '/').
+  String get dateSeparator;
+
+  /// The help text used on an empty [InputDatePickerFormField] to indicate
+  /// to the user the date format being asked for.
+  String get dateHelpText;
+
+  /// The semantic label used to announce when the user has entered the year
+  /// selection mode of the [CalendarDatePicker] which is used in the data picker
+  /// dialog created with [showDatePicker].
+  String get selectYearSemanticsLabel;
+
+  /// The label used to indicate a date that has not been entered or selected
+  /// yet in the date picker.
+  String get unspecifiedDate;
+
+  /// The label used to indicate a date range that has not been entered or
+  /// selected yet in the date range picker.
+  String get unspecifiedDateRange;
+
+  /// The label used to describe the text field used in an [InputDatePickerFormField].
+  String get dateInputLabel;
+
+  /// The label used for the starting date input field in the date range picker
+  /// created with [showDateRangePicker].
+  String get dateRangeStartLabel;
+
+  /// The label used for the ending date input field in the date range picker
+  /// created with [showDateRangePicker].
+  String get dateRangeEndLabel;
+
+  /// The semantics label used for the selected start date in the date range
+  /// picker's day grid.
+  String dateRangeStartDateSemanticLabel(String formattedDate);
+
+  /// The semantics label used for the selected end date in the date range
+  /// picker's day grid.
+  String dateRangeEndDateSemanticLabel(String formattedDate);
+
+  /// Error message displayed to the user when they have entered a text string
+  /// in an [InputDatePickerFormField] that is not in a valid date format.
+  String get invalidDateFormatLabel;
+
+  /// Error message displayed to the user when they have entered an invalid
+  /// date range in the input mode of the date range picker created with
+  /// [showDateRangePicker].
+  String get invalidDateRangeLabel;
+
+  /// Error message displayed to the user when they have entered a date that
+  /// is outside the valid range for the date picker.
+  /// [showDateRangePicker].
+  String get dateOutOfRangeLabel;
+
+  /// Label for a 'SAVE' button. Currently used by the full screen mode of the
+  /// date range picker.
+  String get saveButtonLabel;
+
+  /// Label used in the header of the date picker dialog created with
+  /// [showDatePicker].
+  String get datePickerHelpText;
+
+  /// Label used in the header of the date range picker dialog created with
+  /// [showDateRangePicker].
+  String get dateRangePickerHelpText;
+
+  /// Tooltip used for the calendar mode button of the date pickers.
+  String get calendarModeButtonLabel;
+
+  /// Tooltip used for the text input mode button of the date pickers.
+  String get inputDateModeButtonLabel;
+
+  /// Label used in the header of the time picker dialog created with
+  /// [showTimePicker] when in [TimePickerEntryMode.dial].
+  String get timePickerDialHelpText;
+
+  /// Label used in the header of the time picker dialog created with
+  /// [showTimePicker] when in [TimePickerEntryMode.input].
+  String get timePickerInputHelpText;
+
+  /// Label used below the hour text field of the time picker dialog created
+  /// with [showTimePicker] when in [TimePickerEntryMode.input].
+  String get timePickerHourLabel;
+
+  /// Label used below the minute text field of the time picker dialog created
+  /// with [showTimePicker] when in [TimePickerEntryMode.input].
+  String get timePickerMinuteLabel;
+
+  /// Error message for the time picker dialog created with [showTimePicker]
+  /// when in [TimePickerEntryMode.input].
+  String get invalidTimeLabel;
+
+  /// Tooltip used to put the time picker into [TimePickerEntryMode.dial].
+  String get dialModeButtonLabel;
+
+  /// Tooltip used to put the time picker into [TimePickerEntryMode.input].
+  String get inputTimeModeButtonLabel;
+
+  /// The semantics label used to indicate which account is signed in in the
+  /// [UserAccountsDrawerHeader] widget.
+  String get signedInLabel;
+
+  /// The semantics label used for the button on [UserAccountsDrawerHeader] that
+  /// hides the list of accounts.
+  String get hideAccountsLabel;
+
+  /// The semantics label used for the button on [UserAccountsDrawerHeader] that
+  /// shows the list of accounts.
+  String get showAccountsLabel;
+
+  /// The semantics label used for [ReorderableListView] to reorder an item in the
+  /// list to the start of the list.
+  String get reorderItemToStart;
+
+  /// The semantics label used for [ReorderableListView] to reorder an item in the
+  /// list to the end of the list.
+  String get reorderItemToEnd;
+
+  /// The semantics label used for [ReorderableListView] to reorder an item in the
+  /// list one space up the list.
+  String get reorderItemUp;
+
+  /// The semantics label used for [ReorderableListView] to reorder an item in the
+  /// list one space down the list.
+  String get reorderItemDown;
+
+  /// The semantics label used for [ReorderableListView] to reorder an item in the
+  /// list one space left in the list.
+  String get reorderItemLeft;
+
+  /// The semantics label used for [ReorderableListView] to reorder an item in the
+  /// list one space right in the list.
+  String get reorderItemRight;
+
+  /// The semantics hint to describe the tap action on an expanded [ExpandIcon].
+  String get expandedIconTapHint => 'Collapse';
+
+  /// The semantics hint to describe the tap action on a collapsed [ExpandIcon].
+  String get collapsedIconTapHint => 'Expand';
+
+  /// The label for the [TextField]'s character counter.
+  String remainingTextFieldCharacterCount(int remaining);
+
+  /// The default semantics label for a [RefreshIndicator].
+  String get refreshIndicatorSemanticLabel;
+
+  /// The `MaterialLocalizations` from the closest [Localizations] instance
+  /// that encloses the given context.
+  ///
+  /// If no [MaterialLocalizations] are available in the given `context`, this
+  /// method throws an exception.
+  ///
+  /// This method is just a convenient shorthand for:
+  /// `Localizations.of<MaterialLocalizations>(context, MaterialLocalizations)!`.
+  ///
+  /// References to the localized resources defined by this class are typically
+  /// written in terms of this method. For example:
+  ///
+  /// ```dart
+  /// tooltip: MaterialLocalizations.of(context).backButtonTooltip,
+  /// ```
+  static MaterialLocalizations of(BuildContext context) {
+    assert(debugCheckHasMaterialLocalizations(context));
+    return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations)!;
+  }
+}
+
+class _MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
+  const _MaterialLocalizationsDelegate();
+
+  @override
+  bool isSupported(Locale locale) => locale.languageCode == 'en';
+
+  @override
+  Future<MaterialLocalizations> load(Locale locale) => DefaultMaterialLocalizations.load(locale);
+
+  @override
+  bool shouldReload(_MaterialLocalizationsDelegate old) => false;
+
+  @override
+  String toString() => 'DefaultMaterialLocalizations.delegate(en_US)';
+}
+
+/// US English strings for the material widgets.
+///
+/// See also:
+///
+///  * [GlobalMaterialLocalizations], which provides material localizations for
+///    many languages.
+///  * [MaterialApp.localizationsDelegates], which automatically includes
+///    [DefaultMaterialLocalizations.delegate] by default.
+class DefaultMaterialLocalizations implements MaterialLocalizations {
+  /// Constructs an object that defines the material widgets' localized strings
+  /// for US English (only).
+  ///
+  /// [LocalizationsDelegate] implementations typically call the static [load]
+  /// function, rather than constructing this class directly.
+  const DefaultMaterialLocalizations();
+
+  // Ordered to match DateTime.monday=1, DateTime.sunday=6
+  static const List<String> _shortWeekdays = <String>[
+    'Mon',
+    'Tue',
+    'Wed',
+    'Thu',
+    'Fri',
+    'Sat',
+    'Sun',
+  ];
+
+  // Ordered to match DateTime.monday=1, DateTime.sunday=6
+  static const List<String> _weekdays = <String>[
+    'Monday',
+    'Tuesday',
+    'Wednesday',
+    'Thursday',
+    'Friday',
+    'Saturday',
+    'Sunday',
+  ];
+
+  static const List<String> _narrowWeekdays = <String>[
+    'S',
+    'M',
+    'T',
+    'W',
+    'T',
+    'F',
+    'S',
+  ];
+
+  static const List<String> _shortMonths = <String>[
+    'Jan',
+    'Feb',
+    'Mar',
+    'Apr',
+    'May',
+    'Jun',
+    'Jul',
+    'Aug',
+    'Sep',
+    'Oct',
+    'Nov',
+    'Dec',
+  ];
+
+  static const List<String> _months = <String>[
+    'January',
+    'February',
+    'March',
+    'April',
+    'May',
+    'June',
+    'July',
+    'August',
+    'September',
+    'October',
+    'November',
+    'December',
+  ];
+
+  /// Returns the number of days in a month, according to the proleptic
+  /// Gregorian calendar.
+  ///
+  /// This applies the leap year logic introduced by the Gregorian reforms of
+  /// 1582. It will not give valid results for dates prior to that time.
+  int _getDaysInMonth(int year, int month) {
+    if (month == DateTime.february) {
+      final bool isLeapYear = (year % 4 == 0) && (year % 100 != 0) ||
+          (year % 400 == 0);
+      if (isLeapYear)
+        return 29;
+      return 28;
+    }
+    const List<int> daysInMonth = <int>[31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
+    return daysInMonth[month - 1];
+  }
+
+  @override
+  String formatHour(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat = false }) {
+    final TimeOfDayFormat format = timeOfDayFormat(alwaysUse24HourFormat: alwaysUse24HourFormat);
+    switch (format) {
+      case TimeOfDayFormat.h_colon_mm_space_a:
+        return formatDecimal(timeOfDay.hourOfPeriod == 0 ? 12 : timeOfDay.hourOfPeriod);
+      case TimeOfDayFormat.HH_colon_mm:
+        return _formatTwoDigitZeroPad(timeOfDay.hour);
+      default:
+        throw AssertionError('$runtimeType does not support $format.');
+    }
+  }
+
+  /// Formats [number] using two digits, assuming it's in the 0-99 inclusive
+  /// range. Not designed to format values outside this range.
+  String _formatTwoDigitZeroPad(int number) {
+    assert(0 <= number && number < 100);
+
+    if (number < 10)
+      return '0$number';
+
+    return '$number';
+  }
+
+  @override
+  String formatMinute(TimeOfDay timeOfDay) {
+    final int minute = timeOfDay.minute;
+    return minute < 10 ? '0$minute' : minute.toString();
+  }
+
+  @override
+  String formatYear(DateTime date) => date.year.toString();
+
+  @override
+  String formatCompactDate(DateTime date) {
+    // Assumes US mm/dd/yyyy format
+    final String month = _formatTwoDigitZeroPad(date.month);
+    final String day = _formatTwoDigitZeroPad(date.day);
+    final String year = date.year.toString().padLeft(4, '0');
+    return '$month/$day/$year';
+  }
+
+  @override
+  String formatShortDate(DateTime date) {
+    final String month = _shortMonths[date.month - DateTime.january];
+    return '$month ${date.day}, ${date.year}';
+  }
+
+  @override
+  String formatMediumDate(DateTime date) {
+    final String day = _shortWeekdays[date.weekday - DateTime.monday];
+    final String month = _shortMonths[date.month - DateTime.january];
+    return '$day, $month ${date.day}';
+  }
+
+  @override
+  String formatFullDate(DateTime date) {
+    final String month = _months[date.month - DateTime.january];
+    return '${_weekdays[date.weekday - DateTime.monday]}, $month ${date.day}, ${date.year}';
+  }
+
+  @override
+  String formatMonthYear(DateTime date) {
+    final String year = formatYear(date);
+    final String month = _months[date.month - DateTime.january];
+    return '$month $year';
+  }
+
+  @override
+  String formatShortMonthDay(DateTime date) {
+    final String month = _shortMonths[date.month - DateTime.january];
+    return '$month ${date.day}';
+  }
+
+  @override
+  DateTime? parseCompactDate(String? inputString) {
+    if (inputString == null) {
+      return null;
+    }
+
+    // Assumes US mm/dd/yyyy format
+    final List<String> inputParts = inputString.split('/');
+    if (inputParts.length != 3) {
+      return null;
+    }
+
+    final int? year = int.tryParse(inputParts[2], radix: 10);
+    if (year == null || year < 1) {
+      return null;
+    }
+
+    final int? month = int.tryParse(inputParts[0], radix: 10);
+    if (month == null || month < 1 || month > 12) {
+      return null;
+    }
+
+    final int? day = int.tryParse(inputParts[1], radix: 10);
+    if (day == null || day < 1 || day > _getDaysInMonth(year, month)) {
+      return null;
+    }
+    return DateTime(year, month, day);
+  }
+
+  @override
+  List<String> get narrowWeekdays => _narrowWeekdays;
+
+  @override
+  int get firstDayOfWeekIndex => 0; // narrowWeekdays[0] is 'S' for Sunday
+
+  @override
+  String get dateSeparator => '/';
+
+  @override
+  String get dateHelpText => 'mm/dd/yyyy';
+
+  @override
+  String get selectYearSemanticsLabel => 'Select year';
+
+  @override
+  String get unspecifiedDate => 'Date';
+
+  @override
+  String get unspecifiedDateRange => 'Date Range';
+
+  @override
+  String get dateInputLabel => 'Enter Date';
+
+  @override
+  String get dateRangeStartLabel => 'Start Date';
+
+  @override
+  String get dateRangeEndLabel => 'End Date';
+
+  @override
+  String dateRangeStartDateSemanticLabel(String fullDate) => 'Start date $fullDate';
+
+  @override
+  String dateRangeEndDateSemanticLabel(String fullDate) => 'End date $fullDate';
+
+  @override
+  String get invalidDateFormatLabel => 'Invalid format.';
+
+  @override
+  String get invalidDateRangeLabel => 'Invalid range.';
+
+  @override
+  String get dateOutOfRangeLabel => 'Out of range.';
+
+  @override
+  String get saveButtonLabel => 'SAVE';
+
+  @override
+  String get datePickerHelpText => 'SELECT DATE';
+
+  @override
+  String get dateRangePickerHelpText => 'SELECT RANGE';
+
+  @override
+  String get calendarModeButtonLabel => 'Switch to calendar';
+
+  @override
+  String get inputDateModeButtonLabel => 'Switch to input';
+
+  @override
+  String get timePickerDialHelpText => 'SELECT TIME';
+
+  @override
+  String get timePickerInputHelpText => 'ENTER TIME';
+
+  @override
+  String get timePickerHourLabel => 'Hour';
+
+  @override
+  String get timePickerMinuteLabel => 'Minute';
+
+  @override
+  String get invalidTimeLabel => 'Enter a valid time';
+
+  @override
+  String get dialModeButtonLabel => 'Switch to dial picker mode';
+
+  @override
+  String get inputTimeModeButtonLabel => 'Switch to text input mode';
+
+  String _formatDayPeriod(TimeOfDay timeOfDay) {
+    switch (timeOfDay.period) {
+      case DayPeriod.am:
+        return anteMeridiemAbbreviation;
+      case DayPeriod.pm:
+        return postMeridiemAbbreviation;
+    }
+  }
+
+  @override
+  String formatDecimal(int number) {
+    if (number > -1000 && number < 1000)
+      return number.toString();
+
+    final String digits = number.abs().toString();
+    final StringBuffer result = StringBuffer(number < 0 ? '-' : '');
+    final int maxDigitIndex = digits.length - 1;
+    for (int i = 0; i <= maxDigitIndex; i += 1) {
+      result.write(digits[i]);
+      if (i < maxDigitIndex && (maxDigitIndex - i) % 3 == 0)
+        result.write(',');
+    }
+    return result.toString();
+  }
+
+  @override
+  String formatTimeOfDay(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat = false }) {
+    // Not using intl.DateFormat for two reasons:
+    //
+    // - DateFormat supports more formats than our material time picker does,
+    //   and we want to be consistent across time picker format and the string
+    //   formatting of the time of day.
+    // - DateFormat operates on DateTime, which is sensitive to time eras and
+    //   time zones, while here we want to format hour and minute within one day
+    //   no matter what date the day falls on.
+    final StringBuffer buffer = StringBuffer();
+
+    // Add hour:minute.
+    buffer
+      ..write(formatHour(timeOfDay, alwaysUse24HourFormat: alwaysUse24HourFormat))
+      ..write(':')
+      ..write(formatMinute(timeOfDay));
+
+    if (alwaysUse24HourFormat) {
+      // There's no AM/PM indicator in 24-hour format.
+      return '$buffer';
+    }
+
+    // Add AM/PM indicator.
+    buffer
+      ..write(' ')
+      ..write(_formatDayPeriod(timeOfDay));
+    return '$buffer';
+  }
+
+  @override
+  String get openAppDrawerTooltip => 'Open navigation menu';
+
+  @override
+  String get backButtonTooltip => 'Back';
+
+  @override
+  String get closeButtonTooltip => 'Close';
+
+  @override
+  String get deleteButtonTooltip => 'Delete';
+
+  @override
+  String get moreButtonTooltip => 'More';
+
+  @override
+  String get nextMonthTooltip => 'Next month';
+
+  @override
+  String get previousMonthTooltip => 'Previous month';
+
+  @override
+  String get nextPageTooltip => 'Next page';
+
+  @override
+  String get previousPageTooltip => 'Previous page';
+
+  @override
+  String get showMenuTooltip => 'Show menu';
+
+  @override
+  String get drawerLabel => 'Navigation menu';
+
+  @override
+  String get popupMenuLabel => 'Popup menu';
+
+  @override
+  String get dialogLabel => 'Dialog';
+
+  @override
+  String get alertDialogLabel => 'Alert';
+
+  @override
+  String get searchFieldLabel => 'Search';
+
+  @override
+  String aboutListTileTitle(String applicationName) => 'About $applicationName';
+
+  @override
+  String get licensesPageTitle => 'Licenses';
+
+  @override
+  String licensesPackageDetailText(int licenseCount) {
+    assert (licenseCount >= 0);
+    switch (licenseCount) {
+      case 0:
+        return 'No licenses.';
+      case 1:
+        return '1 license.';
+      default:
+        return '$licenseCount licenses.';
+    }
+  }
+
+  @override
+  String pageRowsInfoTitle(int firstRow, int lastRow, int rowCount, bool rowCountIsApproximate) {
+    return rowCountIsApproximate
+      ? '$firstRow–$lastRow of about $rowCount'
+      : '$firstRow–$lastRow of $rowCount';
+  }
+
+  @override
+  String get rowsPerPageTitle => 'Rows per page:';
+
+  @override
+  String tabLabel({ required int tabIndex, required int tabCount }) {
+    assert(tabIndex >= 1);
+    assert(tabCount >= 1);
+    return 'Tab $tabIndex of $tabCount';
+  }
+
+  @override
+  String selectedRowCountTitle(int selectedRowCount) {
+    switch (selectedRowCount) {
+      case 0:
+        return 'No items selected';
+      case 1:
+        return '1 item selected';
+      default:
+        return '$selectedRowCount items selected';
+    }
+  }
+
+  @override
+  String get cancelButtonLabel => 'CANCEL';
+
+  @override
+  String get closeButtonLabel => 'CLOSE';
+
+  @override
+  String get continueButtonLabel => 'CONTINUE';
+
+  @override
+  String get copyButtonLabel => 'Copy';
+
+  @override
+  String get cutButtonLabel => 'Cut';
+
+  @override
+  String get okButtonLabel => 'OK';
+
+  @override
+  String get pasteButtonLabel => 'Paste';
+
+  @override
+  String get selectAllButtonLabel => 'Select all';
+
+  @override
+  String get viewLicensesButtonLabel => 'VIEW LICENSES';
+
+  @override
+  String get anteMeridiemAbbreviation => 'AM';
+
+  @override
+  String get postMeridiemAbbreviation => 'PM';
+
+  @override
+  String get timePickerHourModeAnnouncement => 'Select hours';
+
+  @override
+  String get timePickerMinuteModeAnnouncement => 'Select minutes';
+
+  @override
+  String get modalBarrierDismissLabel => 'Dismiss';
+
+  @override
+  ScriptCategory get scriptCategory => ScriptCategory.englishLike;
+
+  @override
+  TimeOfDayFormat timeOfDayFormat({ bool alwaysUse24HourFormat = false }) {
+    return alwaysUse24HourFormat
+      ? TimeOfDayFormat.HH_colon_mm
+      : TimeOfDayFormat.h_colon_mm_space_a;
+  }
+
+  @override
+  String get signedInLabel => 'Signed in';
+
+  @override
+  String get hideAccountsLabel => 'Hide accounts';
+
+  @override
+  String get showAccountsLabel => 'Show accounts';
+
+  @override
+  String get reorderItemUp => 'Move up';
+
+  @override
+  String get reorderItemDown => 'Move down';
+
+  @override
+  String get reorderItemLeft => 'Move left';
+
+  @override
+  String get reorderItemRight => 'Move right';
+
+  @override
+  String get reorderItemToEnd => 'Move to the end';
+
+  @override
+  String get reorderItemToStart => 'Move to the start';
+
+  @override
+  String get expandedIconTapHint => 'Collapse';
+
+  @override
+  String get collapsedIconTapHint => 'Expand';
+
+  @override
+  String get refreshIndicatorSemanticLabel => 'Refresh';
+
+  /// Creates an object that provides US English resource values for the material
+  /// library widgets.
+  ///
+  /// The [locale] parameter is ignored.
+  ///
+  /// This method is typically used to create a [LocalizationsDelegate].
+  /// The [MaterialApp] does so by default.
+  static Future<MaterialLocalizations> load(Locale locale) {
+    return SynchronousFuture<MaterialLocalizations>(const DefaultMaterialLocalizations());
+  }
+
+  /// A [LocalizationsDelegate] that uses [DefaultMaterialLocalizations.load]
+  /// to create an instance of this class.
+  ///
+  /// [MaterialApp] automatically adds this value to [MaterialApp.localizationsDelegates].
+  static const LocalizationsDelegate<MaterialLocalizations> delegate = _MaterialLocalizationsDelegate();
+
+  @override
+  String remainingTextFieldCharacterCount(int remaining) {
+    switch (remaining) {
+      case 0:
+        return 'No characters remaining';
+      case 1:
+        return '1 character remaining';
+      default:
+        return '$remaining characters remaining';
+    }
+  }
+}
diff --git a/lib/src/material/material_state.dart b/lib/src/material/material_state.dart
new file mode 100644
index 0000000..e66e070
--- /dev/null
+++ b/lib/src/material/material_state.dart
@@ -0,0 +1,517 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show Color;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+/// Interactive states that some of the Material widgets can take on when
+/// receiving input from the user.
+///
+/// States are defined by https://material.io/design/interaction/states.html#usage.
+///
+/// Some Material widgets track their current state in a `Set<MaterialState>`.
+///
+/// See also:
+///
+///  * [MaterialStateProperty], an interface for objects that "resolve" to
+///    different values depending on a widget's material state.
+///  * [MaterialStateColor], a [Color] that implements `MaterialStateProperty`
+///    which is used in APIs that need to accept either a [Color] or a
+///    `MaterialStateProperty<Color>`.
+///  * [MaterialStateMouseCursor], a [MouseCursor] that implements
+///    `MaterialStateProperty` which is used in APIs that need to accept either
+///    a [MouseCursor] or a [MaterialStateProperty<MouseCursor>].
+///  * [MaterialStateOutlinedBorder], an [OutlinedBorder] that implements
+///    `MaterialStateProperty` which is used in APIs that need to accept either
+///    an [OutlinedBorder] or a [MaterialStateProperty<OutlinedBorder>].
+///  * [MaterialStateBorderSide], a [BorderSide] that implements
+///    `MaterialStateProperty` which is used in APIs that need to accept either
+///    a [BorderSide] or a [MaterialStateProperty<BorderSide>].
+
+enum MaterialState {
+  /// The state when the user drags their mouse cursor over the given widget.
+  ///
+  /// See: https://material.io/design/interaction/states.html#hover.
+  hovered,
+
+  /// The state when the user navigates with the keyboard to a given widget.
+  ///
+  /// This can also sometimes be triggered when a widget is tapped. For example,
+  /// when a [TextField] is tapped, it becomes [focused].
+  ///
+  /// See: https://material.io/design/interaction/states.html#focus.
+  focused,
+
+  /// The state when the user is actively pressing down on the given widget.
+  ///
+  /// See: https://material.io/design/interaction/states.html#pressed.
+  pressed,
+
+  /// The state when this widget is being dragged from one place to another by
+  /// the user.
+  ///
+  /// https://material.io/design/interaction/states.html#dragged.
+  dragged,
+
+  /// The state when this item has been selected.
+  ///
+  /// This applies to things that can be toggled (such as chips and checkboxes)
+  /// and things that are selected from a set of options (such as tabs and radio buttons).
+  ///
+  /// See: https://material.io/design/interaction/states.html#selected.
+  selected,
+
+  /// The state when this widget disabled and can not be interacted with.
+  ///
+  /// Disabled widgets should not respond to hover, focus, press, or drag
+  /// interactions.
+  ///
+  /// See: https://material.io/design/interaction/states.html#disabled.
+  disabled,
+
+  /// The state when the widget has entered some form of invalid state.
+  ///
+  /// See https://material.io/design/interaction/states.html#usage.
+  error,
+}
+
+/// Signature for the function that returns a value of type `T` based on a given
+/// set of states.
+typedef MaterialPropertyResolver<T> = T Function(Set<MaterialState> states);
+
+/// Defines a [Color] that is also a [MaterialStateProperty].
+///
+/// This class exists to enable widgets with [Color] valued properties
+/// to also accept [MaterialStateProperty<Color>] values. A material
+/// state color property represents a color which depends on
+/// a widget's "interactive state". This state is represented as a
+/// [Set] of [MaterialState]s, like [MaterialState.pressed],
+/// [MaterialState.focused] and [MaterialState.hovered].
+///
+/// To use a [MaterialStateColor], you can either:
+///   1. Create a subclass of [MaterialStateColor] and implement the abstract `resolve` method.
+///   2. Use [MaterialStateColor.resolveWith] and pass in a callback that
+///      will be used to resolve the color in the given states.
+///
+/// If a [MaterialStateColor] is used for a property or a parameter that doesn't
+/// support resolving [MaterialStateProperty<Color>]s, then its default color
+/// value will be used for all states.
+///
+/// To define a `const` [MaterialStateColor], you'll need to extend
+/// [MaterialStateColor] and override its [resolve] method. You'll also need
+/// to provide a `defaultValue` to the super constructor, so that we can know
+/// at compile-time what its default color is.
+///
+/// {@tool snippet}
+///
+/// This example defines a `MaterialStateColor` with a const constructor.
+///
+/// ```dart
+/// class MyColor extends MaterialStateColor {
+///   static const int _defaultColor = 0xcafefeed;
+///   static const int _pressedColor = 0xdeadbeef;
+///
+///   const MyColor() : super(_defaultColor);
+///
+///   @override
+///   Color resolve(Set<MaterialState> states) {
+///     if (states.contains(MaterialState.pressed)) {
+///       return const Color(_pressedColor);
+///     }
+///     return const Color(_defaultColor);
+///   }
+/// }
+/// ```
+/// {@end-tool}
+abstract class MaterialStateColor extends Color implements MaterialStateProperty<Color> {
+  /// Creates a [MaterialStateColor].
+  const MaterialStateColor(int defaultValue) : super(defaultValue);
+
+  /// Creates a [MaterialStateColor] from a [MaterialPropertyResolver<Color>]
+  /// callback function.
+  ///
+  /// If used as a regular color, the color resolved in the default state (the
+  /// empty set of states) will be used.
+  ///
+  /// The given callback parameter must return a non-null color in the default
+  /// state.
+  static MaterialStateColor resolveWith(MaterialPropertyResolver<Color> callback) => _MaterialStateColor(callback);
+
+  /// Returns a [Color] that's to be used when a Material component is in the
+  /// specified state.
+  @override
+  Color resolve(Set<MaterialState> states);
+}
+
+/// A [MaterialStateColor] created from a [MaterialPropertyResolver<Color>]
+/// callback alone.
+///
+/// If used as a regular color, the color resolved in the default state will
+/// be used.
+///
+/// Used by [MaterialStateColor.resolveWith].
+class _MaterialStateColor extends MaterialStateColor {
+  _MaterialStateColor(this._resolve) : super(_resolve(_defaultStates).value);
+
+  final MaterialPropertyResolver<Color> _resolve;
+
+  /// The default state for a Material component, the empty set of interaction states.
+  static const Set<MaterialState> _defaultStates = <MaterialState>{};
+
+  @override
+  Color resolve(Set<MaterialState> states) => _resolve(states);
+}
+
+/// Defines a [MouseCursor] whose value depends on a set of [MaterialState]s which
+/// represent the interactive state of a component.
+///
+/// This kind of [MouseCursor] is useful when the set of interactive
+/// actions a widget supports varies with its state. For example, a
+/// mouse pointer hovering over a disabled [ListTile] should not
+/// display [SystemMouseCursors.click], since a disabled list tile
+/// doesn't respond to mouse clicks. [ListTile]'s default mouse cursor
+/// is a [MaterialStateMouseCursor.clickable], which resolves to
+/// [SystemMouseCursors.basic] when the button is disabled.
+///
+/// To use a [MaterialStateMouseCursor], you should create a subclass of
+/// [MaterialStateMouseCursor] and implement the abstract `resolve` method.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_center}
+///
+/// This example defines a mouse cursor that resolves to
+/// [SystemMouseCursors.forbidden] when its widget is disabled.
+///
+/// ```dart imports
+/// import 'package:flute/rendering.dart';
+/// ```
+///
+/// ```dart preamble
+/// class ListTileCursor extends MaterialStateMouseCursor {
+///   @override
+///   MouseCursor resolve(Set<MaterialState> states) {
+///     if (states.contains(MaterialState.disabled)) {
+///       return SystemMouseCursors.forbidden;
+///     }
+///     return SystemMouseCursors.click;
+///   }
+///   @override
+///   String get debugDescription => 'ListTileCursor()';
+/// }
+/// ```
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return ListTile(
+///     title: Text('Disabled ListTile'),
+///     enabled: false,
+///     mouseCursor: ListTileCursor(),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// This class should only be used for parameters which are documented to take
+/// [MaterialStateMouseCursor], otherwise only the default state will be used.
+///
+/// See also:
+///
+///  * [MouseCursor] for introduction on the mouse cursor system.
+///  * [SystemMouseCursors], which defines cursors that are supported by
+///    native platforms.
+abstract class MaterialStateMouseCursor extends MouseCursor implements MaterialStateProperty<MouseCursor> {
+  /// Creates a [MaterialStateMouseCursor].
+  const MaterialStateMouseCursor();
+
+  @protected
+  @override
+  MouseCursorSession createSession(int device) {
+    return resolve(<MaterialState>{}).createSession(device);
+  }
+
+  /// Returns a [MouseCursor] that's to be used when a Material component is in
+  /// the specified state.
+  ///
+  /// This method should never return null.
+  @override
+  MouseCursor resolve(Set<MaterialState> states);
+
+  /// A mouse cursor for clickable material widgets, which resolves differently
+  /// when the widget is disabled.
+  ///
+  /// By default this cursor resolves to [SystemMouseCursors.click]. If the widget is
+  /// disabled, the cursor resolves to [SystemMouseCursors.basic].
+  ///
+  /// This cursor is the default for many Material widgets.
+  static const MaterialStateMouseCursor clickable = _EnabledAndDisabledMouseCursor(
+    enabledCursor: SystemMouseCursors.click,
+    disabledCursor: SystemMouseCursors.basic,
+    name: 'clickable',
+  );
+
+  /// A mouse cursor for material widgets related to text, which resolves differently
+  /// when the widget is disabled.
+  ///
+  /// By default this cursor resolves to [SystemMouseCursors.text]. If the widget is
+  /// disabled, the cursor resolves to [SystemMouseCursors.basic].
+  ///
+  /// This cursor is the default for many Material widgets.
+  static const MaterialStateMouseCursor textable = _EnabledAndDisabledMouseCursor(
+    enabledCursor: SystemMouseCursors.text,
+    disabledCursor: SystemMouseCursors.basic,
+    name: 'textable',
+  );
+}
+
+class _EnabledAndDisabledMouseCursor extends MaterialStateMouseCursor {
+  const _EnabledAndDisabledMouseCursor({
+    required this.enabledCursor,
+    required this.disabledCursor,
+    required this.name,
+  });
+
+  final MouseCursor enabledCursor;
+  final MouseCursor disabledCursor;
+  final String name;
+
+  @override
+  MouseCursor resolve(Set<MaterialState> states) {
+    if (states.contains(MaterialState.disabled)) {
+      return disabledCursor;
+    }
+    return enabledCursor;
+  }
+
+  @override
+  String get debugDescription => 'MaterialStateMouseCursor($name)';
+}
+
+/// Defines a [BorderSide] whose value depends on a set of [MaterialState]s
+/// which represent the interactive state of a component.
+///
+/// To use a [MaterialStateBorderSide], you should create a subclass of a
+/// [MaterialStateBorderSide] and override the abstract `resolve` method.
+///
+/// {@tool dartpad --template=stateful_widget_material}
+///
+/// This example defines a subclass of [MaterialStateBorderSide], that resolves
+/// to a red border side when its widget is selected.
+///
+/// ```dart preamble
+/// class RedSelectedBorderSide extends MaterialStateBorderSide {
+///   @override
+///   BorderSide? resolve(Set<MaterialState> states) {
+///     if (states.contains(MaterialState.selected)) {
+///       return BorderSide(
+///         width: 1,
+///         color: Colors.red,
+///       );
+///     }
+///     return null;  // Defer to default value on the theme or widget.
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// bool isSelected = true;
+///
+/// Widget build(BuildContext context) {
+///   return FilterChip(
+///     label: Text('Select chip'),
+///     selected: isSelected,
+///     onSelected: (bool value) {
+///       setState(() {
+///         isSelected = value;
+///       });
+///     },
+///     side: RedSelectedBorderSide(),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// This class should only be used for parameters which are documented to take
+/// [MaterialStateBorderSide], otherwise only the default state will be used.
+abstract class MaterialStateBorderSide extends BorderSide implements MaterialStateProperty<BorderSide?> {
+  /// Creates a [MaterialStateBorderSide].
+  const MaterialStateBorderSide();
+
+  /// Returns a [BorderSide] that's to be used when a Material component is
+  /// in the specified state. Return null to defer to the default value of the
+  /// widget or theme.
+  @override
+  BorderSide? resolve(Set<MaterialState> states);
+}
+
+/// Defines an [OutlinedBorder] whose value depends on a set of [MaterialState]s
+/// which represent the interactive state of a component.
+///
+/// To use a [MaterialStateOutlinedBorder], you should create a subclass of an
+/// [OutlinedBorder] and implement [MaterialStateOutlinedBorder]'s abstract
+/// `resolve` method.
+///
+/// {@tool dartpad --template=stateful_widget_material}
+///
+/// This example defines a subclass of [RoundedRectangleBorder] and an
+/// implementation of [MaterialStateOutlinedBorder], that resolves to
+/// [RoundedRectangleBorder] when its widget is selected.
+///
+/// ```dart preamble
+/// class SelectedBorder extends RoundedRectangleBorder implements MaterialStateOutlinedBorder {
+///   @override
+///   OutlinedBorder? resolve(Set<MaterialState> states) {
+///     if (states.contains(MaterialState.selected)) {
+///       return RoundedRectangleBorder();
+///     }
+///     return null;  // Defer to default value on the theme or widget.
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// bool isSelected = true;
+///
+/// Widget build(BuildContext context) {
+///   return FilterChip(
+///     label: Text('Select chip'),
+///     selected: isSelected,
+///     onSelected: (bool value) {
+///       setState(() {
+///         isSelected = value;
+///       });
+///     },
+///     shape: SelectedBorder(),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// This class should only be used for parameters which are documented to take
+/// [MaterialStateOutlinedBorder], otherwise only the default state will be used.
+///
+/// See also:
+///
+///  * [ShapeBorder] the base class for shape outlines.
+abstract class MaterialStateOutlinedBorder extends OutlinedBorder implements MaterialStateProperty<OutlinedBorder?> {
+  /// Creates a [MaterialStateOutlinedBorder].
+  const MaterialStateOutlinedBorder();
+
+  /// Returns an [OutlinedBorder] that's to be used when a Material component is
+  /// in the specified state. Return null to defer to the default value of the
+  /// widget or theme.
+  @override
+  OutlinedBorder? resolve(Set<MaterialState> states);
+}
+
+/// Interface for classes that [resolve] to a value of type `T` based
+/// on a widget's interactive "state", which is defined as a set
+/// of [MaterialState]s.
+///
+/// Material state properties represent values that depend on a widget's material
+/// "state".  The state is encoded as a set of [MaterialState] values, like
+/// [MaterialState.focused], [MaterialState.hovered], [MaterialState.pressed].  For
+/// example the [InkWell.overlayColor] defines the color that fills the ink well
+/// when it's pressed (the "splash color"), focused, or hovered. The [InkWell]
+/// uses the overlay color's [resolve] method to compute the color for the
+/// ink well's current state.
+///
+/// [ButtonStyle], which is used to configure the appearance of
+/// buttons like [TextButton], [ElevatedButton], and [OutlinedButton],
+/// has many material state properties.  The button widgets keep track
+/// of their current material state and [resolve] the button style's
+/// material state properties when their value is needed.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_center}
+///
+/// This example shows how you can override the default text and icon
+/// color (the "foreground color") of a [TextButton] with a
+/// [MaterialStateProperty]. In this example, the button's text color
+/// will be `Colors.blue` when the button is being pressed, hovered,
+/// or focused. Otherwise, the text color will be `Colors.red`.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   Color getColor(Set<MaterialState> states) {
+///     const Set<MaterialState> interactiveStates = <MaterialState>{
+///       MaterialState.pressed,
+///       MaterialState.hovered,
+///       MaterialState.focused,
+///     };
+///     if (states.any(interactiveStates.contains)) {
+///       return Colors.blue;
+///     }
+///     return Colors.red;
+///   }
+///   return TextButton(
+///     style: ButtonStyle(
+///       foregroundColor: MaterialStateProperty.resolveWith(getColor),
+///     ),
+///     onPressed: () {},
+///     child: Text('TextButton'),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [MaterialStateColor], a [Color] that implements `MaterialStateProperty`
+///    which is used in APIs that need to accept either a [Color] or a
+///    `MaterialStateProperty<Color>`.
+///  * [MaterialStateMouseCursor], a [MouseCursor] that implements `MaterialStateProperty`
+///    which is used in APIs that need to accept either a [MouseCursor] or a
+///    [MaterialStateProperty<MouseCursor>].
+abstract class MaterialStateProperty<T> {
+
+  /// Returns a value of type `T` that depends on [states].
+  ///
+  /// Widgets like [TextButton] and [ElevatedButton] apply this method to their
+  /// current [MaterialState]s to compute colors and other visual parameters
+  /// at build time.
+  T resolve(Set<MaterialState> states);
+
+  /// Resolves the value for the given set of states if `value` is a
+  /// [MaterialStateProperty], otherwise returns the value itself.
+  ///
+  /// This is useful for widgets that have parameters which can optionally be a
+  /// [MaterialStateProperty]. For example, [InkWell.mouseCursor] can be a
+  /// [MouseCursor] or a [MaterialStateProperty<MouseCursor>].
+  static T resolveAs<T>(T value, Set<MaterialState> states) {
+    if (value is MaterialStateProperty<T>) {
+      final MaterialStateProperty<T> property = value;
+      return property.resolve(states);
+    }
+    return value;
+  }
+
+  /// Convenience method for creating a [MaterialStateProperty] from a
+  /// [MaterialPropertyResolver] function alone.
+  static MaterialStateProperty<T> resolveWith<T>(MaterialPropertyResolver<T> callback) => _MaterialStatePropertyWith<T>(callback);
+
+  /// Convenience method for creating a [MaterialStateProperty] that resolves
+  /// to a single value for all states.
+  static MaterialStateProperty<T> all<T>(T value) => _MaterialStatePropertyAll<T>(value);
+}
+
+class _MaterialStatePropertyWith<T> implements MaterialStateProperty<T> {
+  _MaterialStatePropertyWith(this._resolve);
+
+  final MaterialPropertyResolver<T> _resolve;
+
+  @override
+  T resolve(Set<MaterialState> states) => _resolve(states);
+}
+
+class _MaterialStatePropertyAll<T> implements MaterialStateProperty<T> {
+  _MaterialStatePropertyAll(this.value);
+
+  final T value;
+
+  @override
+  T resolve(Set<MaterialState> states) => value;
+
+  @override
+  String toString() => 'MaterialStateProperty.all($value)';
+}
diff --git a/lib/src/material/mergeable_material.dart b/lib/src/material/mergeable_material.dart
new file mode 100644
index 0000000..13b79f3
--- /dev/null
+++ b/lib/src/material/mergeable_material.dart
@@ -0,0 +1,722 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'divider.dart';
+import 'material.dart';
+import 'shadows.dart';
+import 'theme.dart';
+
+/// The base type for [MaterialSlice] and [MaterialGap].
+///
+/// All [MergeableMaterialItem] objects need a [LocalKey].
+@immutable
+abstract class MergeableMaterialItem {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  ///
+  /// The argument is the [key], which must not be null.
+  const MergeableMaterialItem(this.key) : assert(key != null);
+
+  /// The key for this item of the list.
+  ///
+  /// The key is used to match parts of the mergeable material from frame to
+  /// frame so that state is maintained appropriately even as slices are added
+  /// or removed.
+  final LocalKey key;
+}
+
+/// A class that can be used as a child to [MergeableMaterial]. It is a slice
+/// of [Material] that animates merging with other slices.
+///
+/// All [MaterialSlice] objects need a [LocalKey].
+class MaterialSlice extends MergeableMaterialItem {
+  /// Creates a slice of [Material] that's mergeable within a
+  /// [MergeableMaterial].
+  const MaterialSlice({
+    required LocalKey key,
+    required this.child,
+    this.color,
+  }) : assert(key != null),
+       super(key);
+
+  /// The contents of this slice.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// Defines the color for the slice.
+  ///
+  /// By default, the value of `color` is [ThemeData.cardColor].
+  final Color? color;
+
+  @override
+  String toString() {
+    return 'MergeableSlice(key: $key, child: $child, color: $color)';
+  }
+}
+
+/// A class that represents a gap within [MergeableMaterial].
+///
+/// All [MaterialGap] objects need a [LocalKey].
+class MaterialGap extends MergeableMaterialItem {
+  /// Creates a Material gap with a given size.
+  const MaterialGap({
+    required LocalKey key,
+    this.size = 16.0,
+  }) : assert(key != null),
+       super(key);
+
+  /// The main axis extent of this gap. For example, if the [MergeableMaterial]
+  /// is vertical, then this is the height of the gap.
+  final double size;
+
+  @override
+  String toString() {
+    return 'MaterialGap(key: $key, child: $size)';
+  }
+}
+
+/// Displays a list of [MergeableMaterialItem] children. The list contains
+/// [MaterialSlice] items whose boundaries are either "merged" with adjacent
+/// items or separated by a [MaterialGap]. The [children] are distributed along
+/// the given [mainAxis] in the same way as the children of a [ListBody]. When
+/// the list of children changes, gaps are automatically animated open or closed
+/// as needed.
+///
+/// To enable this widget to correlate its list of children with the previous
+/// one, each child must specify a key.
+///
+/// When a new gap is added to the list of children the adjacent items are
+/// animated apart. Similarly when a gap is removed the adjacent items are
+/// brought back together.
+///
+/// When a new slice is added or removed, the app is responsible for animating
+/// the transition of the slices, while the gaps will be animated automatically.
+///
+/// See also:
+///
+///  * [Card], a piece of material that does not support splitting and merging
+///    but otherwise looks the same.
+class MergeableMaterial extends StatefulWidget {
+  /// Creates a mergeable Material list of items.
+  const MergeableMaterial({
+    Key? key,
+    this.mainAxis = Axis.vertical,
+    this.elevation = 2,
+    this.hasDividers = false,
+    this.children = const <MergeableMaterialItem>[],
+    this.dividerColor,
+  }) : super(key: key);
+
+  /// The children of the [MergeableMaterial].
+  final List<MergeableMaterialItem> children;
+
+  /// The main layout axis.
+  final Axis mainAxis;
+
+  /// The z-coordinate at which to place all the [Material] slices.
+  ///
+  /// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24.
+  ///
+  /// Defaults to 2, the appropriate elevation for cards.
+  ///
+  /// This uses [kElevationToShadow] to simulate shadows, it does not use
+  /// [Material]'s arbitrary elevation feature.
+  final int elevation;
+
+  /// Whether connected pieces of [MaterialSlice] have dividers between them.
+  final bool hasDividers;
+
+  /// Defines color used for dividers if [hasDividers] is true.
+  ///
+  /// If `dividerColor` is null, then [DividerThemeData.color] is used. If that
+  /// is null, then [ThemeData.dividerColor] is used.
+  final Color? dividerColor;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<Axis>('mainAxis', mainAxis));
+    properties.add(DoubleProperty('elevation', elevation.toDouble()));
+  }
+
+  @override
+  _MergeableMaterialState createState() => _MergeableMaterialState();
+}
+
+class _AnimationTuple {
+  _AnimationTuple({
+    required this.controller,
+    required this.startAnimation,
+    required this.endAnimation,
+    required this.gapAnimation,
+    this.gapStart = 0.0,
+  });
+
+  final AnimationController controller;
+  final CurvedAnimation startAnimation;
+  final CurvedAnimation endAnimation;
+  final CurvedAnimation gapAnimation;
+  double gapStart;
+}
+
+class _MergeableMaterialState extends State<MergeableMaterial> with TickerProviderStateMixin {
+  late List<MergeableMaterialItem> _children;
+  final Map<LocalKey, _AnimationTuple?> _animationTuples = <LocalKey, _AnimationTuple?>{};
+
+  @override
+  void initState() {
+    super.initState();
+    _children = List<MergeableMaterialItem>.from(widget.children);
+
+    for (int i = 0; i < _children.length; i += 1) {
+      final MergeableMaterialItem child = _children[i];
+      if (child is MaterialGap) {
+        _initGap(child);
+        _animationTuples[child.key]!.controller.value = 1.0; // Gaps are initially full-sized.
+      }
+    }
+    assert(_debugGapsAreValid(_children));
+  }
+
+  void _initGap(MaterialGap gap) {
+    final AnimationController controller = AnimationController(
+      duration: kThemeAnimationDuration,
+      vsync: this,
+    );
+
+    final CurvedAnimation startAnimation = CurvedAnimation(
+      parent: controller,
+      curve: Curves.fastOutSlowIn,
+    );
+    final CurvedAnimation endAnimation = CurvedAnimation(
+      parent: controller,
+      curve: Curves.fastOutSlowIn,
+    );
+    final CurvedAnimation gapAnimation = CurvedAnimation(
+      parent: controller,
+      curve: Curves.fastOutSlowIn,
+    );
+
+    controller.addListener(_handleTick);
+
+    _animationTuples[gap.key] = _AnimationTuple(
+      controller: controller,
+      startAnimation: startAnimation,
+      endAnimation: endAnimation,
+      gapAnimation: gapAnimation,
+    );
+  }
+
+  @override
+  void dispose() {
+    for (final MergeableMaterialItem child in _children) {
+      if (child is MaterialGap)
+        _animationTuples[child.key]!.controller.dispose();
+    }
+    super.dispose();
+  }
+
+  void _handleTick() {
+    setState(() {
+      // The animation's state is our build state, and it changed already.
+    });
+  }
+
+  bool _debugHasConsecutiveGaps(List<MergeableMaterialItem> children) {
+    for (int i = 0; i < widget.children.length - 1; i += 1) {
+      if (widget.children[i] is MaterialGap &&
+          widget.children[i + 1] is MaterialGap)
+        return true;
+    }
+    return false;
+  }
+
+  bool _debugGapsAreValid(List<MergeableMaterialItem> children) {
+    // Check for consecutive gaps.
+    if (_debugHasConsecutiveGaps(children))
+      return false;
+
+    // First and last children must not be gaps.
+    if (children.isNotEmpty) {
+      if (children.first is MaterialGap || children.last is MaterialGap)
+        return false;
+    }
+
+    return true;
+  }
+
+  void _insertChild(int index, MergeableMaterialItem child) {
+    _children.insert(index, child);
+
+    if (child is MaterialGap)
+      _initGap(child);
+  }
+
+  void _removeChild(int index) {
+    final MergeableMaterialItem child = _children.removeAt(index);
+
+    if (child is MaterialGap)
+      _animationTuples[child.key] = null;
+  }
+
+  bool _isClosingGap(int index) {
+    if (index < _children.length - 1 && _children[index] is MaterialGap) {
+      return _animationTuples[_children[index].key]!.controller.status ==
+          AnimationStatus.reverse;
+    }
+
+    return false;
+  }
+
+  void _removeEmptyGaps() {
+    int j = 0;
+
+    while (j < _children.length) {
+      if (
+        _children[j] is MaterialGap &&
+        _animationTuples[_children[j].key]!.controller.status == AnimationStatus.dismissed
+      ) {
+        _removeChild(j);
+      } else {
+        j += 1;
+      }
+    }
+  }
+
+  @override
+  void didUpdateWidget(MergeableMaterial oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    final Set<LocalKey> oldKeys = oldWidget.children.map<LocalKey>(
+      (MergeableMaterialItem child) => child.key
+    ).toSet();
+    final Set<LocalKey> newKeys = widget.children.map<LocalKey>(
+      (MergeableMaterialItem child) => child.key
+    ).toSet();
+    final Set<LocalKey> newOnly = newKeys.difference(oldKeys);
+    final Set<LocalKey> oldOnly = oldKeys.difference(newKeys);
+
+    final List<MergeableMaterialItem> newChildren = widget.children;
+    int i = 0;
+    int j = 0;
+
+    assert(_debugGapsAreValid(newChildren));
+
+    _removeEmptyGaps();
+
+    while (i < newChildren.length && j < _children.length) {
+      if (newOnly.contains(newChildren[i].key) ||
+          oldOnly.contains(_children[j].key)) {
+        final int startNew = i;
+        final int startOld = j;
+
+        // Skip new keys.
+        while (newOnly.contains(newChildren[i].key))
+          i += 1;
+
+        // Skip old keys.
+        while (oldOnly.contains(_children[j].key) || _isClosingGap(j))
+          j += 1;
+
+        final int newLength = i - startNew;
+        final int oldLength = j - startOld;
+
+        if (newLength > 0) {
+          if (oldLength > 1 ||
+              oldLength == 1 && _children[startOld] is MaterialSlice) {
+            if (newLength == 1 && newChildren[startNew] is MaterialGap) {
+              // Shrink all gaps into the size of the new one.
+              double gapSizeSum = 0.0;
+
+              while (startOld < j) {
+                final MergeableMaterialItem child = _children[startOld];
+                if (child is MaterialGap) {
+                  final MaterialGap gap = child;
+                  gapSizeSum += gap.size;
+                }
+
+                _removeChild(startOld);
+                j -= 1;
+              }
+
+              _insertChild(startOld, newChildren[startNew]);
+              _animationTuples[newChildren[startNew].key]!
+                ..gapStart = gapSizeSum
+                ..controller.forward();
+
+              j += 1;
+            } else {
+              // No animation if replaced items are more than one.
+              for (int k = 0; k < oldLength; k += 1)
+                _removeChild(startOld);
+              for (int k = 0; k < newLength; k += 1)
+                _insertChild(startOld + k, newChildren[startNew + k]);
+
+              j += newLength - oldLength;
+            }
+          } else if (oldLength == 1) {
+            if (newLength == 1 && newChildren[startNew] is MaterialGap &&
+                _children[startOld].key == newChildren[startNew].key) {
+              /// Special case: gap added back.
+              _animationTuples[newChildren[startNew].key]!.controller.forward();
+            } else {
+              final double gapSize = _getGapSize(startOld);
+
+              _removeChild(startOld);
+
+              for (int k = 0; k < newLength; k += 1)
+                _insertChild(startOld + k, newChildren[startNew + k]);
+
+              j += newLength - 1;
+              double gapSizeSum = 0.0;
+
+              for (int k = startNew; k < i; k += 1) {
+                final MergeableMaterialItem newChild = newChildren[k];
+                if (newChild is MaterialGap) {
+                  gapSizeSum += newChild.size;
+                }
+              }
+
+              // All gaps get proportional sizes of the original gap and they will
+              // animate to their actual size.
+              for (int k = startNew; k < i; k += 1) {
+                final MergeableMaterialItem newChild = newChildren[k];
+                if (newChild is MaterialGap) {
+                  _animationTuples[newChild.key]!.gapStart = gapSize * newChild.size / gapSizeSum;
+                  _animationTuples[newChild.key]!.controller
+                    ..value = 0.0
+                    ..forward();
+                }
+              }
+            }
+          } else {
+            // Grow gaps.
+            for (int k = 0; k < newLength; k += 1) {
+              final MergeableMaterialItem newChild = newChildren[startNew + k];
+
+              _insertChild(startOld + k, newChild);
+
+              if (newChild is MaterialGap) {
+                _animationTuples[newChild.key]!.controller.forward();
+              }
+            }
+
+            j += newLength;
+          }
+        } else {
+          // If more than a gap disappeared, just remove slices and shrink gaps.
+          if (oldLength > 1 ||
+              oldLength == 1 && _children[startOld] is MaterialSlice) {
+            double gapSizeSum = 0.0;
+
+            while (startOld < j) {
+              final MergeableMaterialItem child = _children[startOld];
+              if (child is MaterialGap) {
+                gapSizeSum += child.size;
+              }
+
+              _removeChild(startOld);
+              j -= 1;
+            }
+
+            if (gapSizeSum != 0.0) {
+              final MaterialGap gap = MaterialGap(
+                key: UniqueKey(),
+                size: gapSizeSum,
+              );
+              _insertChild(startOld, gap);
+              _animationTuples[gap.key]!.gapStart = 0.0;
+              _animationTuples[gap.key]!.controller
+                ..value = 1.0
+                ..reverse();
+
+              j += 1;
+            }
+          } else if (oldLength == 1) {
+            // Shrink gap.
+            final MaterialGap gap = _children[startOld] as MaterialGap;
+            _animationTuples[gap.key]!.gapStart = 0.0;
+            _animationTuples[gap.key]!.controller.reverse();
+          }
+        }
+      } else {
+        // Check whether the items are the same type. If they are, it means that
+        // their places have been swapped.
+        if ((_children[j] is MaterialGap) == (newChildren[i] is MaterialGap)) {
+          _children[j] = newChildren[i];
+
+          i += 1;
+          j += 1;
+        } else {
+          // This is a closing gap which we need to skip.
+          assert(_children[j] is MaterialGap);
+          j += 1;
+        }
+      }
+    }
+
+    // Handle remaining items.
+    while (j < _children.length)
+      _removeChild(j);
+    while (i < newChildren.length) {
+      _insertChild(j, newChildren[i]);
+
+      i += 1;
+      j += 1;
+    }
+  }
+
+  BorderRadius _borderRadius(int index, bool start, bool end) {
+    assert(kMaterialEdges[MaterialType.card]!.topLeft == kMaterialEdges[MaterialType.card]!.topRight);
+    assert(kMaterialEdges[MaterialType.card]!.topLeft == kMaterialEdges[MaterialType.card]!.bottomLeft);
+    assert(kMaterialEdges[MaterialType.card]!.topLeft == kMaterialEdges[MaterialType.card]!.bottomRight);
+    final Radius cardRadius = kMaterialEdges[MaterialType.card]!.topLeft;
+
+    Radius startRadius = Radius.zero;
+    Radius endRadius = Radius.zero;
+
+    if (index > 0 && _children[index - 1] is MaterialGap) {
+      startRadius = Radius.lerp(
+        Radius.zero,
+        cardRadius,
+        _animationTuples[_children[index - 1].key]!.startAnimation.value,
+      )!;
+    }
+    if (index < _children.length - 2 && _children[index + 1] is MaterialGap) {
+      endRadius = Radius.lerp(
+        Radius.zero,
+        cardRadius,
+        _animationTuples[_children[index + 1].key]!.endAnimation.value,
+      )!;
+    }
+
+    if (widget.mainAxis == Axis.vertical) {
+      return BorderRadius.vertical(
+        top: start ? cardRadius : startRadius,
+        bottom: end ? cardRadius : endRadius,
+      );
+    } else {
+      return BorderRadius.horizontal(
+        left: start ? cardRadius : startRadius,
+        right: end ? cardRadius : endRadius,
+      );
+    }
+  }
+
+  double _getGapSize(int index) {
+    final MaterialGap gap = _children[index] as MaterialGap;
+
+    return lerpDouble(
+      _animationTuples[gap.key]!.gapStart,
+      gap.size,
+      _animationTuples[gap.key]!.gapAnimation.value,
+    )!;
+  }
+
+  bool _willNeedDivider(int index) {
+    if (index < 0)
+      return false;
+    if (index >= _children.length)
+      return false;
+    return _children[index] is MaterialSlice || _isClosingGap(index);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    _removeEmptyGaps();
+
+    final List<Widget> widgets = <Widget>[];
+    List<Widget> slices = <Widget>[];
+    int i;
+
+    for (i = 0; i < _children.length; i += 1) {
+      if (_children[i] is MaterialGap) {
+        assert(slices.isNotEmpty);
+        widgets.add(
+          ListBody(
+            mainAxis: widget.mainAxis,
+            children: slices,
+          ),
+        );
+        slices = <Widget>[];
+
+        widgets.add(
+          SizedBox(
+            width: widget.mainAxis == Axis.horizontal ? _getGapSize(i) : null,
+            height: widget.mainAxis == Axis.vertical ? _getGapSize(i) : null,
+          ),
+        );
+      } else {
+        final MaterialSlice slice = _children[i] as MaterialSlice;
+        Widget child = slice.child;
+
+        if (widget.hasDividers) {
+          final bool hasTopDivider = _willNeedDivider(i - 1);
+          final bool hasBottomDivider = _willNeedDivider(i + 1);
+
+          final BorderSide divider = Divider.createBorderSide(
+            context,
+            width: 0.5, // TODO(ianh): This probably looks terrible when the dpr isn't a power of two.
+            color: widget.dividerColor,
+          );
+
+          final Border border;
+          if (i == 0) {
+            border = Border(
+              bottom: hasBottomDivider ? divider : BorderSide.none
+            );
+          } else if (i == _children.length - 1) {
+            border = Border(
+              top: hasTopDivider ? divider : BorderSide.none
+            );
+          } else {
+            border = Border(
+              top: hasTopDivider ? divider : BorderSide.none,
+              bottom: hasBottomDivider ? divider : BorderSide.none,
+            );
+          }
+
+          child = AnimatedContainer(
+            key: _MergeableMaterialSliceKey(_children[i].key),
+            decoration: BoxDecoration(border: border),
+            duration: kThemeAnimationDuration,
+            curve: Curves.fastOutSlowIn,
+            child: child,
+          );
+        }
+
+        slices.add(
+          Container(
+            decoration: BoxDecoration(
+              color: (_children[i] as MaterialSlice).color ?? Theme.of(context).cardColor,
+              borderRadius: _borderRadius(i, i == 0, i == _children.length - 1),
+              shape: BoxShape.rectangle,
+            ),
+            child: Material(
+              type: MaterialType.transparency,
+              child: child,
+            ),
+          ),
+        );
+      }
+    }
+
+    if (slices.isNotEmpty) {
+      widgets.add(
+        ListBody(
+          mainAxis: widget.mainAxis,
+          children: slices,
+        ),
+      );
+      slices = <Widget>[];
+    }
+
+    return _MergeableMaterialListBody(
+      mainAxis: widget.mainAxis,
+      boxShadows: kElevationToShadow[widget.elevation]!,
+      items: _children,
+      children: widgets,
+    );
+  }
+}
+
+// The parent hierarchy can change and lead to the slice being
+// rebuilt. Using a global key solves the issue.
+class _MergeableMaterialSliceKey extends GlobalKey {
+  const _MergeableMaterialSliceKey(this.value) : super.constructor();
+
+  final LocalKey value;
+
+  @override
+  bool operator ==(Object other) {
+    return other is _MergeableMaterialSliceKey
+        && other.value == value;
+  }
+
+  @override
+  int get hashCode => value.hashCode;
+
+  @override
+  String toString() {
+    return '_MergeableMaterialSliceKey($value)';
+  }
+}
+
+class _MergeableMaterialListBody extends ListBody {
+  _MergeableMaterialListBody({
+    required List<Widget> children,
+    Axis mainAxis = Axis.vertical,
+    required this.items,
+    required this.boxShadows,
+  }) : super(children: children, mainAxis: mainAxis);
+
+  final List<MergeableMaterialItem> items;
+  final List<BoxShadow> boxShadows;
+
+  AxisDirection _getDirection(BuildContext context) {
+    return getAxisDirectionFromAxisReverseAndDirectionality(context, mainAxis, false);
+  }
+
+  @override
+  RenderListBody createRenderObject(BuildContext context) {
+    return _RenderMergeableMaterialListBody(
+      axisDirection: _getDirection(context),
+      boxShadows: boxShadows,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderListBody renderObject) {
+    final _RenderMergeableMaterialListBody materialRenderListBody = renderObject as _RenderMergeableMaterialListBody;
+    materialRenderListBody
+      ..axisDirection = _getDirection(context)
+      ..boxShadows = boxShadows;
+  }
+}
+
+class _RenderMergeableMaterialListBody extends RenderListBody {
+  _RenderMergeableMaterialListBody({
+    List<RenderBox>? children,
+    AxisDirection axisDirection = AxisDirection.down,
+    required this.boxShadows,
+  }) : super(children: children, axisDirection: axisDirection);
+
+  List<BoxShadow> boxShadows;
+
+  void _paintShadows(Canvas canvas, Rect rect) {
+    for (final BoxShadow boxShadow in boxShadows) {
+      final Paint paint = boxShadow.toPaint();
+      // TODO(dragostis): Right now, we are only interpolating the border radii
+      // of the visible Material slices, not the shadows; they are not getting
+      // interpolated and always have the same rounded radii. Once shadow
+      // performance is better, shadows should be redrawn every single time the
+      // slices' radii get interpolated and use those radii not the defaults.
+      canvas.drawRRect(kMaterialEdges[MaterialType.card]!.toRRect(rect), paint);
+    }
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    RenderBox? child = firstChild;
+    int i = 0;
+
+    while (child != null) {
+      final ListBodyParentData childParentData = child.parentData! as ListBodyParentData;
+      final Rect rect = (childParentData.offset + offset) & child.size;
+      if (i.isEven)
+        _paintShadows(context.canvas, rect);
+      child = childParentData.nextSibling;
+
+      i += 1;
+    }
+
+    defaultPaint(context, offset);
+  }
+}
diff --git a/lib/src/material/navigation_rail.dart b/lib/src/material/navigation_rail.dart
new file mode 100644
index 0000000..551a78a
--- /dev/null
+++ b/lib/src/material/navigation_rail.dart
@@ -0,0 +1,872 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' hide TextStyle;
+
+import 'package:flute/widgets.dart';
+
+import '../../scheduler.dart';
+
+import 'color_scheme.dart';
+import 'ink_well.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'navigation_rail_theme.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// A material widget that is meant to be displayed at the left or right of an
+/// app to navigate between a small number of views, typically between three and
+/// five.
+///
+/// The navigation rail is meant for layouts with wide viewports, such as a
+/// desktop web or tablet landscape layout. For smaller layouts, like mobile
+/// portrait, a [BottomNavigationBar] should be used instead.
+///
+/// A navigation rail is usually used as the first or last element of a [Row]
+/// which defines the app's [Scaffold] body.
+///
+/// The appearance of all of the [NavigationRail]s within an app can be
+/// specified with [NavigationRailTheme]. The default values for null theme
+/// properties are based on the [Theme]'s [ThemeData.textTheme],
+/// [ThemeData.iconTheme], and [ThemeData.colorScheme].
+///
+/// Adaptive layouts can build different instances of the [Scaffold] in order to
+/// have a navigation rail for more horizontal layouts and a bottom navigation
+/// bar for more vertical layouts. See
+/// [https://github.com/flutter/samples/blob/master/experimental/web_dashboard/lib/src/widgets/third_party/adaptive_scaffold.dart]
+/// for an example.
+///
+/// {@tool dartpad --template=stateful_widget_material}
+///
+/// This example shows a [NavigationRail] used within a Scaffold with 3
+/// [NavigationRailDestination]s. The main content is separated by a divider
+/// (although elevation on the navigation rail can be used instead). The
+/// `_selectedIndex` is updated by the `onDestinationSelected` callback.
+///
+/// ```dart
+/// int _selectedIndex = 0;
+///
+///  @override
+///  Widget build(BuildContext context) {
+///    return Scaffold(
+///      body: Row(
+///        children: <Widget>[
+///          NavigationRail(
+///            selectedIndex: _selectedIndex,
+///            onDestinationSelected: (int index) {
+///              setState(() {
+///                _selectedIndex = index;
+///              });
+///            },
+///            labelType: NavigationRailLabelType.selected,
+///            destinations: [
+///              NavigationRailDestination(
+///                icon: Icon(Icons.favorite_border),
+///                selectedIcon: Icon(Icons.favorite),
+///                label: Text('First'),
+///              ),
+///              NavigationRailDestination(
+///                icon: Icon(Icons.bookmark_border),
+///                selectedIcon: Icon(Icons.book),
+///                label: Text('Second'),
+///              ),
+///              NavigationRailDestination(
+///                icon: Icon(Icons.star_border),
+///                selectedIcon: Icon(Icons.star),
+///                label: Text('Third'),
+///              ),
+///            ],
+///          ),
+///          VerticalDivider(thickness: 1, width: 1),
+///          // This is the main content.
+///          Expanded(
+///            child: Center(
+///              child: Text('selectedIndex: $_selectedIndex'),
+///            ),
+///          )
+///        ],
+///      ),
+///    );
+///  }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Scaffold], which can display the navigation rail within a [Row] of the
+///    [Scaffold.body] slot.
+///  * [NavigationRailDestination], which is used as a model to create tappable
+///    destinations in the navigation rail.
+///  * [BottomNavigationBar], which is a similar navigation widget that's laid
+///     out horizontally.
+///  * [https://material.io/components/navigation-rail/]
+class NavigationRail extends StatefulWidget {
+  /// Creates a material design navigation rail.
+  ///
+  /// The value of [destinations] must be a list of one or more
+  /// [NavigationRailDestination] values.
+  ///
+  /// If [elevation] is specified, it must be non-negative.
+  ///
+  /// If [minWidth] is specified, it must be non-negative, and if
+  /// [minExtendedWidth] is specified, it must be non-negative and greater than
+  /// [minWidth].
+  ///
+  /// The argument [extended] must not be null. [extended] can only be set to
+  /// true when when the [labelType] is null or [NavigationRailLabelType.none].
+  ///
+  /// If [backgroundColor], [elevation], [groupAlignment], [labelType],
+  /// [unselectedLabelTextStyle], [selectedLabelTextStyle],
+  /// [unselectedIconTheme], or [selectedIconTheme] are null, then their
+  /// [NavigationRailThemeData] values will be used. If the corresponding
+  /// [NavigationRailThemeData] property is null, then the navigation rail
+  /// defaults are used. See the individual properties for more information.
+  ///
+  /// Typically used within a [Row] that defines the [Scaffold.body] property.
+  const NavigationRail({
+    this.backgroundColor,
+    this.extended = false,
+    this.leading,
+    this.trailing,
+    required this.destinations,
+    required this.selectedIndex,
+    this.onDestinationSelected,
+    this.elevation,
+    this.groupAlignment,
+    this.labelType,
+    this.unselectedLabelTextStyle,
+    this.selectedLabelTextStyle,
+    this.unselectedIconTheme,
+    this.selectedIconTheme,
+    this.minWidth,
+    this.minExtendedWidth,
+  }) :  assert(destinations != null && destinations.length >= 2),
+        assert(selectedIndex != null),
+        assert(0 <= selectedIndex && selectedIndex < destinations.length),
+        assert(elevation == null || elevation > 0),
+        assert(minWidth == null || minWidth > 0),
+        assert(minExtendedWidth == null || minExtendedWidth > 0),
+        assert((minWidth == null || minExtendedWidth == null) || minExtendedWidth >= minWidth),
+        assert(extended != null),
+        assert(!extended || (labelType == null || labelType == NavigationRailLabelType.none));
+
+  /// Sets the color of the Container that holds all of the [NavigationRail]'s
+  /// contents.
+  ///
+  /// The default value is [NavigationRailThemeData.backgroundColor]. If
+  /// [NavigationRailThemeData.backgroundColor] is null, then the default value
+  /// is based on [ColorScheme.surface] of [ThemeData.colorScheme].
+  final Color? backgroundColor;
+
+  /// Indicates that the [NavigationRail] should be in the extended state.
+  ///
+  /// The extended state has a wider rail container, and the labels are
+  /// positioned next to the icons. [minExtendedWidth] can be used to set the
+  /// minimum width of the rail when it is in this state.
+  ///
+  /// The rail will implicitly animate between the extended and normal state.
+  ///
+  /// If the rail is going to be in the extended state, then the [labelType]
+  /// must be set to [NavigationRailLabelType.none].
+  ///
+  /// The default value is false.
+  final bool extended;
+
+  /// The leading widget in the rail that is placed above the destinations.
+  ///
+  /// It is placed at the top of the rail, above the [destinations]. Its
+  /// location is not affected by [groupAlignment].
+  ///
+  /// This is commonly a [FloatingActionButton], but may also be a non-button,
+  /// such as a logo.
+  ///
+  /// The default value is null.
+  final Widget? leading;
+
+  /// The trailing widget in the rail that is placed below the destinations.
+  ///
+  /// The trailing widget is placed below the last [NavigationRailDestination].
+  /// It's location is affected by [groupAlignment].
+  ///
+  /// This is commonly a list of additional options or destinations that is
+  /// usually only rendered when [extended] is true.
+  ///
+  /// The default value is null.
+  final Widget? trailing;
+
+  /// Defines the appearance of the button items that are arrayed within the
+  /// navigation rail.
+  ///
+  /// The value must be a list of two or more [NavigationRailDestination]
+  /// values.
+  final List<NavigationRailDestination> destinations;
+
+  /// The index into [destinations] for the current selected
+  /// [NavigationRailDestination].
+  final int selectedIndex;
+
+  /// Called when one of the [destinations] is selected.
+  ///
+  /// The stateful widget that creates the navigation rail needs to keep
+  /// track of the index of the selected [NavigationRailDestination] and call
+  /// `setState` to rebuild the navigation rail with the new [selectedIndex].
+  final ValueChanged<int>? onDestinationSelected;
+
+  /// The rail's elevation or z-coordinate.
+  ///
+  /// If [Directionality] is [TextDirection.LTR], the inner side is the right
+  /// side, and if [Directionality] is [TextDirection.RTL], it is the left side.
+  ///
+  /// The default value is 0.
+  final double? elevation;
+
+  /// The vertical alignment for the group of [destinations] within the rail.
+  ///
+  /// The [NavigationRailDestination]s are grouped together with the [trailing]
+  /// widget, between the [leading] widget and the bottom of the rail.
+  ///
+  /// The value must be between -1.0 and 1.0.
+  ///
+  /// If [groupAlignment] is -1.0, then the items are aligned to the top. If
+  /// [groupAlignment] is 0.0, then the items are aligned to the center. If
+  /// [groupAlignment] is 1.0, then the items are aligned to the bottom.
+  ///
+  /// The default is -1.0.
+  ///
+  /// See also:
+  ///   * [Alignment.y]
+  ///
+  final double? groupAlignment;
+
+  /// Defines the layout and behavior of the labels for the default, unextended
+  /// [NavigationRail].
+  ///
+  /// When a navigation rail is [extended], the labels are always shown.
+  ///
+  /// The default value is [NavigationRailThemeData.labelType]. If
+  /// [NavigationRailThemeData.labelType] is null, then the default value is
+  /// [NavigationRailLabelType.none].
+  ///
+  /// See also:
+  ///
+  ///   * [NavigationRailLabelType] for information on the meaning of different
+  ///   types.
+  final NavigationRailLabelType? labelType;
+
+  /// The [TextStyle] of a destination's label when it is unselected.
+  ///
+  /// When one of the [destinations] is selected the [selectedLabelTextStyle]
+  /// will be used instead.
+  ///
+  /// The default value is based on the [Theme]'s [TextTheme.bodyText1]. The
+  /// default color is based on the [Theme]'s [ColorScheme.onSurface].
+  ///
+  /// Properties from this text style, or
+  /// [NavigationRailThemeData.unselectedLabelTextStyle] if this is null, are
+  /// merged into the defaults.
+  final TextStyle? unselectedLabelTextStyle;
+
+  /// The [TextStyle] of a destination's label when it is selected.
+  ///
+  /// When a [NavigationRailDestination] is not selected,
+  /// [unselectedLabelTextStyle] will be used.
+  ///
+  /// The default value is based on the [TextTheme.bodyText1] of
+  /// [ThemeData.textTheme]. The default color is based on the [Theme]'s
+  /// [ColorScheme.primary].
+  ///
+  /// Properties from this text style,
+  /// or [NavigationRailThemeData.selectedLabelTextStyle] if this is null, are
+  /// merged into the defaults.
+  final TextStyle? selectedLabelTextStyle;
+
+  /// The visual properties of the icon in the unselected destination.
+  ///
+  /// If this field is not provided, or provided with any null properties, then
+  /// a copy of the [IconThemeData.fallback] with a custom [NavigationRail]
+  /// specific color will be used.
+  ///
+  /// The default value is Is the [Theme]'s [ThemeData.iconTheme] with a color
+  /// of the [Theme]'s [ColorScheme.onSurface] with an opacity of 0.64.
+  /// Properties from this icon theme, or
+  /// [NavigationRailThemeData.unselectedIconTheme] if this is null, are
+  /// merged into the defaults.
+  final IconThemeData? unselectedIconTheme;
+
+  /// The visual properties of the icon in the selected destination.
+  ///
+  /// When a [NavigationRailDestination] is not selected,
+  /// [unselectedIconTheme] will be used.
+  ///
+  /// The default value is Is the [Theme]'s [ThemeData.iconTheme] with a color
+  /// of the [Theme]'s [ColorScheme.primary]. Properties from this icon theme,
+  /// or [NavigationRailThemeData.selectedIconTheme] if this is null, are
+  /// merged into the defaults.
+  final IconThemeData? selectedIconTheme;
+
+  /// The smallest possible width for the rail regardless of the destination's
+  /// icon or label size.
+  ///
+  /// The default is 72.
+  ///
+  /// This value also defines the min width and min height of the destinations.
+  ///
+  /// To make a compact rail, set this to 56 and use
+  /// [NavigationRailLabelType.none].
+  final double? minWidth;
+
+  /// The final width when the animation is complete for setting [extended] to
+  /// true.
+  ///
+  /// This is only used when [extended] is set to true.
+  ///
+  /// The default value is 256.
+  final double? minExtendedWidth;
+
+  /// Returns the animation that controls the [NavigationRail.extended] state.
+  ///
+  /// This can be used to synchronize animations in the [leading] or [trailing]
+  /// widget, such as an animated menu or a [FloatingActionButton] animation.
+  ///
+  /// {@tool snippet}
+  ///
+  /// This example shows how to use this animation to create a
+  /// [FloatingActionButton] that animates itself between the normal and
+  /// extended states of the [NavigationRail].
+  ///
+  /// An instance of `ExtendableFab` would be created for
+  /// [NavigationRail.leading].
+  ///
+  /// ```dart
+  /// import 'package:flute/ui.dart';
+  ///
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   final Animation<double> animation = NavigationRail.extendedAnimation(context);
+  ///   return AnimatedBuilder(
+  ///     animation: animation,
+  ///     builder: (BuildContext context, Widget? child) {
+  ///       // The extended fab has a shorter height than the regular fab.
+  ///       return Container(
+  ///         height: 56,
+  ///         padding: EdgeInsets.symmetric(
+  ///           vertical: lerpDouble(0, 6, animation.value)!,
+  ///         ),
+  ///         child: animation.value == 0
+  ///           ? FloatingActionButton(
+  ///               child: Icon(Icons.add),
+  ///               onPressed: () {},
+  ///             )
+  ///           : Align(
+  ///               alignment: AlignmentDirectional.centerStart,
+  ///               widthFactor: animation.value,
+  ///               child: Padding(
+  ///                 padding: const EdgeInsetsDirectional.only(start: 8),
+  ///                 child: FloatingActionButton.extended(
+  ///                   icon: Icon(Icons.add),
+  ///                   label: Text('CREATE'),
+  ///                   onPressed: () {},
+  ///                 ),
+  ///               ),
+  ///             ),
+  ///       );
+  ///     },
+  ///   );
+  /// }
+  /// ```
+  ///
+  /// {@end-tool}
+  static Animation<double> extendedAnimation(BuildContext context) {
+    return context.dependOnInheritedWidgetOfExactType<_ExtendedNavigationRailAnimation>()!.animation;
+  }
+
+  @override
+  _NavigationRailState createState() => _NavigationRailState();
+}
+
+class _NavigationRailState extends State<NavigationRail> with TickerProviderStateMixin {
+  late List<AnimationController> _destinationControllers;
+  late List<Animation<double>> _destinationAnimations;
+  late AnimationController _extendedController;
+  late Animation<double> _extendedAnimation;
+
+  @override
+  void initState() {
+    super.initState();
+    _initControllers();
+  }
+
+  @override
+  void dispose() {
+    _disposeControllers();
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(NavigationRail oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    if (widget.extended != oldWidget.extended) {
+      if (widget.extended) {
+        _extendedController.forward();
+      } else {
+        _extendedController.reverse();
+      }
+    }
+
+    // No animated segue if the length of the items list changes.
+    if (widget.destinations.length != oldWidget.destinations.length) {
+      _resetState();
+      return;
+    }
+
+    if (widget.selectedIndex != oldWidget.selectedIndex) {
+      _destinationControllers[oldWidget.selectedIndex].reverse();
+      _destinationControllers[widget.selectedIndex].forward();
+      return;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final NavigationRailThemeData navigationRailTheme = NavigationRailTheme.of(context);
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+
+    final Color backgroundColor = widget.backgroundColor ?? navigationRailTheme.backgroundColor ?? theme.colorScheme.surface;
+    final double elevation = widget.elevation ?? navigationRailTheme.elevation ?? 0;
+    final double minWidth = widget.minWidth ?? _minRailWidth;
+    final double minExtendedWidth = widget.minExtendedWidth ?? _minExtendedRailWidth;
+    final Color baseSelectedColor = theme.colorScheme.primary;
+    final Color baseUnselectedColor = theme.colorScheme.onSurface.withOpacity(0.64);
+    final IconThemeData? defaultUnselectedIconTheme = widget.unselectedIconTheme ?? navigationRailTheme.unselectedIconTheme;
+    final IconThemeData unselectedIconTheme = IconThemeData(
+      size: defaultUnselectedIconTheme?.size ?? 24.0,
+      color: defaultUnselectedIconTheme?.color ?? theme.colorScheme.onSurface,
+      opacity: defaultUnselectedIconTheme?.opacity ?? 0.64,
+    );
+    final IconThemeData? defaultSelectedIconTheme = widget.selectedIconTheme ?? navigationRailTheme.selectedIconTheme;
+    final IconThemeData selectedIconTheme = IconThemeData(
+      size: defaultSelectedIconTheme?.size ?? 24.0,
+      color: defaultSelectedIconTheme?.color ?? theme.colorScheme.primary,
+      opacity: defaultSelectedIconTheme?.opacity ?? 1.0,
+    );
+    final TextStyle unselectedLabelTextStyle = theme.textTheme.bodyText1!.copyWith(color: baseUnselectedColor).merge(widget.unselectedLabelTextStyle ?? navigationRailTheme.unselectedLabelTextStyle);
+    final TextStyle selectedLabelTextStyle = theme.textTheme.bodyText1!.copyWith(color: baseSelectedColor).merge(widget.selectedLabelTextStyle ?? navigationRailTheme.selectedLabelTextStyle);
+    final double groupAlignment = widget.groupAlignment ?? navigationRailTheme.groupAlignment ?? -1.0;
+    final NavigationRailLabelType labelType = widget.labelType ?? navigationRailTheme.labelType ?? NavigationRailLabelType.none;
+
+    return _ExtendedNavigationRailAnimation(
+      animation: _extendedAnimation,
+      child: Semantics(
+        explicitChildNodes: true,
+        child: Material(
+          elevation: elevation,
+          color: backgroundColor,
+          child: Column(
+            children: <Widget>[
+              _verticalSpacer,
+              if (widget.leading != null)
+                ...<Widget>[
+                  ConstrainedBox(
+                    constraints: BoxConstraints(
+                      minWidth: lerpDouble(minWidth, minExtendedWidth, _extendedAnimation.value)!,
+                    ),
+                    child: widget.leading,
+                  ),
+                  _verticalSpacer,
+                ],
+              Expanded(
+                child: Align(
+                  alignment: Alignment(0, groupAlignment),
+                  child: Column(
+                    mainAxisSize: MainAxisSize.min,
+                    children: <Widget>[
+                      for (int i = 0; i < widget.destinations.length; i += 1)
+                        _RailDestination(
+                          minWidth: minWidth,
+                          minExtendedWidth: minExtendedWidth,
+                          extendedTransitionAnimation: _extendedAnimation,
+                          selected: widget.selectedIndex == i,
+                          icon: widget.selectedIndex == i ? widget.destinations[i].selectedIcon : widget.destinations[i].icon,
+                          label: widget.destinations[i].label!,
+                          destinationAnimation: _destinationAnimations[i],
+                          labelType: labelType,
+                          iconTheme: widget.selectedIndex == i ? selectedIconTheme : unselectedIconTheme,
+                          labelTextStyle: widget.selectedIndex == i ? selectedLabelTextStyle : unselectedLabelTextStyle,
+                          onTap: () {
+                            widget.onDestinationSelected!(i);
+                          },
+                          indexLabel: localizations.tabLabel(
+                            tabIndex: i + 1,
+                            tabCount: widget.destinations.length,
+                          ),
+                        ),
+                      if (widget.trailing != null)
+                        ConstrainedBox(
+                          constraints: BoxConstraints(
+                            minWidth: lerpDouble(minWidth, minExtendedWidth, _extendedAnimation.value)!,
+                          ),
+                          child: widget.trailing,
+                        ),
+                    ],
+                  ),
+                ),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  void _disposeControllers() {
+    for (final AnimationController controller in _destinationControllers) {
+      controller.dispose();
+    }
+    _extendedController.dispose();
+  }
+
+  void _initControllers() {
+    _destinationControllers = List<AnimationController>.generate(widget.destinations.length, (int index) {
+      return AnimationController(
+        duration: kThemeAnimationDuration,
+        vsync: this,
+      )..addListener(_rebuild);
+    });
+    _destinationAnimations = _destinationControllers.map((AnimationController controller) => controller.view).toList();
+    _destinationControllers[widget.selectedIndex].value = 1.0;
+    _extendedController = AnimationController(
+      duration: kThemeAnimationDuration,
+      vsync: this,
+      value: widget.extended ? 1.0 : 0.0,
+    );
+    _extendedAnimation = CurvedAnimation(
+      parent: _extendedController,
+      curve: Curves.easeInOut,
+    );
+    _extendedController.addListener(() {
+      _rebuild();
+    });
+  }
+
+  void _resetState() {
+    _disposeControllers();
+    _initControllers();
+  }
+
+  void _rebuild() {
+    setState(() {
+      // Rebuilding when any of the controllers tick, i.e. when the items are
+      // animating.
+    });
+  }
+}
+
+class _RailDestination extends StatelessWidget {
+  _RailDestination({
+    required this.minWidth,
+    required this.minExtendedWidth,
+    required this.icon,
+    required this.label,
+    required this.destinationAnimation,
+    required this.extendedTransitionAnimation,
+    required this.labelType,
+    required this.selected,
+    required this.iconTheme,
+    required this.labelTextStyle,
+    required this.onTap,
+    required this.indexLabel,
+  }) : assert(minWidth != null),
+       assert(minExtendedWidth != null),
+       assert(icon != null),
+       assert(label != null),
+       assert(destinationAnimation != null),
+       assert(extendedTransitionAnimation != null),
+       assert(labelType != null),
+       assert(selected != null),
+       assert(iconTheme != null),
+       assert(labelTextStyle != null),
+       assert(onTap != null),
+       assert(indexLabel != null),
+       _positionAnimation = CurvedAnimation(
+          parent: ReverseAnimation(destinationAnimation),
+          curve: Curves.easeInOut,
+          reverseCurve: Curves.easeInOut.flipped,
+       );
+
+  final double minWidth;
+  final double minExtendedWidth;
+  final Widget icon;
+  final Widget label;
+  final Animation<double> destinationAnimation;
+  final NavigationRailLabelType labelType;
+  final bool selected;
+  final Animation<double> extendedTransitionAnimation;
+  final IconThemeData iconTheme;
+  final TextStyle labelTextStyle;
+  final VoidCallback onTap;
+  final String indexLabel;
+
+  final Animation<double> _positionAnimation;
+
+  @override
+  Widget build(BuildContext context) {
+    final Widget themedIcon = IconTheme(
+      data: iconTheme,
+      child: icon,
+    );
+    final Widget styledLabel = DefaultTextStyle(
+      style: labelTextStyle,
+      child: label,
+    );
+    final Widget content;
+    switch (labelType) {
+      case NavigationRailLabelType.none:
+        final Widget iconPart = SizedBox(
+          width: minWidth,
+          height: minWidth,
+          child: Align(
+            alignment: Alignment.center,
+            child: themedIcon,
+          ),
+        );
+        if (extendedTransitionAnimation.value == 0) {
+          content = Stack(
+            children: <Widget>[
+              iconPart,
+              // For semantics when label is not showing,
+              SizedBox(
+                width: 0,
+                height: 0,
+                child: Opacity(
+                  alwaysIncludeSemantics: true,
+                  opacity: 0.0,
+                  child: label,
+                ),
+              ),
+            ]
+          );
+        } else {
+          content = ConstrainedBox(
+            constraints: BoxConstraints(
+              minWidth: lerpDouble(minWidth, minExtendedWidth, extendedTransitionAnimation.value)!,
+            ),
+            child: ClipRect(
+              child: Row(
+                children: <Widget>[
+                  iconPart,
+                  Align(
+                    heightFactor: 1.0,
+                    widthFactor: extendedTransitionAnimation.value,
+                    alignment: AlignmentDirectional.centerStart,
+                    child: Opacity(
+                      alwaysIncludeSemantics: true,
+                      opacity: _extendedLabelFadeValue(),
+                      child: styledLabel,
+                    ),
+                  ),
+                  SizedBox(width: _horizontalDestinationPadding * extendedTransitionAnimation.value),
+                ],
+              ),
+            ),
+          );
+        }
+        break;
+      case NavigationRailLabelType.selected:
+        final double appearingAnimationValue = 1 - _positionAnimation.value;
+        final double verticalPadding = lerpDouble(_verticalDestinationPaddingNoLabel, _verticalDestinationPaddingWithLabel, appearingAnimationValue)!;
+        content = Container(
+          constraints: BoxConstraints(
+            minWidth: minWidth,
+            minHeight: minWidth,
+          ),
+          padding: const EdgeInsets.symmetric(horizontal: _horizontalDestinationPadding),
+          child: ClipRect(
+            child: Column(
+              mainAxisSize: MainAxisSize.min,
+              mainAxisAlignment: MainAxisAlignment.center,
+              children: <Widget>[
+                SizedBox(height: verticalPadding),
+                themedIcon,
+                Align(
+                  alignment: Alignment.topCenter,
+                  heightFactor: appearingAnimationValue,
+                  widthFactor: 1.0,
+                  child: Opacity(
+                    alwaysIncludeSemantics: true,
+                    opacity: selected ? _normalLabelFadeInValue() : _normalLabelFadeOutValue(),
+                    child: styledLabel,
+                  ),
+                ),
+                SizedBox(height: verticalPadding),
+              ],
+            ),
+          ),
+        );
+        break;
+      case NavigationRailLabelType.all:
+        content = Container(
+          constraints: BoxConstraints(
+            minWidth: minWidth,
+            minHeight: minWidth,
+          ),
+          padding: const EdgeInsets.symmetric(horizontal: _horizontalDestinationPadding),
+          child: Column(
+            children: <Widget>[
+              const SizedBox(height: _verticalDestinationPaddingWithLabel),
+              themedIcon,
+              styledLabel,
+              const SizedBox(height: _verticalDestinationPaddingWithLabel),
+            ],
+          ),
+        );
+        break;
+    }
+
+    final ColorScheme colors = Theme.of(context).colorScheme;
+    return Semantics(
+      container: true,
+      selected: selected,
+      child: Stack(
+        children: <Widget>[
+          Material(
+            type: MaterialType.transparency,
+            clipBehavior: Clip.none,
+            child: InkResponse(
+              onTap: onTap,
+              onHover: (_) {},
+              highlightShape: BoxShape.rectangle,
+              borderRadius: BorderRadius.all(Radius.circular(minWidth / 2.0)),
+              containedInkWell: true,
+              splashColor: colors.primary.withOpacity(0.12),
+              hoverColor: colors.primary.withOpacity(0.04),
+              child: content,
+            ),
+          ),
+          Semantics(
+            label: indexLabel,
+          ),
+        ]
+      ),
+    );
+  }
+
+  double _normalLabelFadeInValue() {
+    if (destinationAnimation.value < 0.25) {
+      return 0;
+    } else if (destinationAnimation.value < 0.75) {
+      return (destinationAnimation.value - 0.25) * 2;
+    } else {
+      return 1;
+    }
+  }
+
+  double _normalLabelFadeOutValue() {
+    if (destinationAnimation.value > 0.75) {
+      return (destinationAnimation.value - 0.75) * 4.0;
+    } else {
+      return 0;
+    }
+  }
+
+  double _extendedLabelFadeValue() {
+    return extendedTransitionAnimation.value < 0.25 ? extendedTransitionAnimation.value * 4.0 : 1.0;
+  }
+}
+
+/// Defines the behavior of the labels of a [NavigationRail].
+///
+/// See also:
+///
+///   * [NavigationRail]
+enum NavigationRailLabelType {
+  /// Only the [NavigationRailDestination]s are shown.
+  none,
+
+  /// Only the selected [NavigationRailDestination] will show its label.
+  ///
+  /// The label will animate in and out as new [NavigationRailDestination]s are
+  /// selected.
+  selected,
+
+  /// All [NavigationRailDestination]s will show their label.
+  all,
+}
+
+/// Defines a [NavigationRail] button that represents one "destination" view.
+///
+/// See also:
+///
+///  * [NavigationRail]
+class NavigationRailDestination {
+  /// Creates a destination that is used with [NavigationRail.destinations].
+  ///
+  /// [icon] and [label] must be non-null. When the [NavigationRail.labelType]
+  /// is [NavigationRailLabelType.none], the label is still used for semantics,
+  /// and may still be used if [NavigationRail.extended] is true.
+  const NavigationRailDestination({
+    required this.icon,
+    Widget? selectedIcon,
+    this.label,
+  }) : selectedIcon = selectedIcon ?? icon,
+       assert(icon != null);
+
+  /// The icon of the destination.
+  ///
+  /// Typically the icon is an [Icon] or an [ImageIcon] widget. If another type
+  /// of widget is provided then it should configure itself to match the current
+  /// [IconTheme] size and color.
+  ///
+  /// If [selectedIcon] is provided, this will only be displayed when the
+  /// destination is not selected.
+  ///
+  /// To make the [NavigationRail] more accessible, consider choosing an
+  /// icon with a stroked and filled version, such as [Icons.cloud] and
+  /// [Icons.cloud_queue]. The [icon] should be set to the stroked version and
+  /// [selectedIcon] to the filled version.
+  final Widget icon;
+
+  /// An alternative icon displayed when this destination is selected.
+  ///
+  /// If this icon is not provided, the [NavigationRail] will display [icon] in
+  /// either state. The size, color, and opacity of the
+  /// [NavigationRail.selectedIconTheme] will still apply.
+  ///
+  /// See also:
+  ///
+  ///  * [NavigationRailDestination.icon], for a description of how to pair
+  ///    icons.
+  final Widget selectedIcon;
+
+  /// The label for the destination.
+  ///
+  /// The label must be provided when used with the [NavigationRail]. When the
+  /// [NavigationRail.labelType] is [NavigationRailLabelType.none], the label is
+  /// still used for semantics, and may still be used if
+  /// [NavigationRail.extended] is true.
+  final Widget? label;
+}
+
+class _ExtendedNavigationRailAnimation extends InheritedWidget {
+  const _ExtendedNavigationRailAnimation({
+    Key? key,
+    required this.animation,
+    required Widget child,
+  }) : assert(child != null),
+       super(key: key, child: child);
+
+  final Animation<double> animation;
+
+  @override
+  bool updateShouldNotify(_ExtendedNavigationRailAnimation old) => animation != old.animation;
+}
+
+const double _minRailWidth = 72.0;
+const double _minExtendedRailWidth = 256.0;
+const double _horizontalDestinationPadding = 8.0;
+const double _verticalDestinationPaddingNoLabel = 24.0;
+const double _verticalDestinationPaddingWithLabel = 16.0;
+const Widget _verticalSpacer = SizedBox(height: 8.0);
diff --git a/lib/src/material/navigation_rail_theme.dart b/lib/src/material/navigation_rail_theme.dart
new file mode 100644
index 0000000..3c4fc9e
--- /dev/null
+++ b/lib/src/material/navigation_rail_theme.dart
@@ -0,0 +1,215 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'navigation_rail.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// Defines default property values for descendant [NavigationRail]
+/// widgets.
+///
+/// Descendant widgets obtain the current [NavigationRailThemeData] object
+/// using `NavigationRailTheme.of(context)`. Instances of
+/// [NavigationRailThemeData] can be customized with
+/// [NavigationRailThemeData.copyWith].
+///
+/// Typically a [NavigationRailThemeData] is specified as part of the
+/// overall [Theme] with [ThemeData.navigationRailTheme].
+///
+/// All [NavigationRailThemeData] properties are `null` by default.
+/// When null, the [NavigationRail] will use the values from [ThemeData]
+/// if they exist, otherwise it will provide its own defaults based on the
+/// overall [Theme]'s textTheme and colorScheme. See the individual
+/// [NavigationRail] properties for details.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class NavigationRailThemeData with Diagnosticable {
+  /// Creates a theme that can be used for [ThemeData.navigationRailTheme].
+  const NavigationRailThemeData({
+    this.backgroundColor,
+    this.elevation,
+    this.unselectedLabelTextStyle,
+    this.selectedLabelTextStyle,
+    this.unselectedIconTheme,
+    this.selectedIconTheme,
+    this.groupAlignment,
+    this.labelType,
+  });
+
+  /// Color to be used for the [NavigationRail]'s background.
+  final Color? backgroundColor;
+
+  /// The z-coordinate to be used for the [NavigationRail]'s elevation.
+  final double? elevation;
+
+  /// The style to merge with the default text style for
+  /// [NavigationRailDestination] labels, when the destination is not selected.
+  final TextStyle? unselectedLabelTextStyle;
+
+  /// The style to merge with the default text style for
+  /// [NavigationRailDestination] labels, when the destination is selected.
+  final TextStyle? selectedLabelTextStyle;
+
+  /// The theme to merge with the default icon theme for
+  /// [NavigationRailDestination] icons, when the destination is not selected.
+  final IconThemeData? unselectedIconTheme;
+
+  /// The theme to merge with the default icon theme for
+  /// [NavigationRailDestination] icons, when the destination is selected.
+  final IconThemeData? selectedIconTheme;
+
+  /// The alignment for the [NavigationRailDestination]s as they are positioned
+  /// within the [NavigationRail].
+  final double? groupAlignment;
+
+  /// The type that defines the layout and behavior of the labels in the
+  /// [NavigationRail].
+  final NavigationRailLabelType? labelType;
+
+  /// Creates a copy of this object with the given fields replaced with the
+  /// new values.
+  NavigationRailThemeData copyWith({
+    Color? backgroundColor,
+    double? elevation,
+    TextStyle? unselectedLabelTextStyle,
+    TextStyle? selectedLabelTextStyle,
+    IconThemeData? unselectedIconTheme,
+    IconThemeData? selectedIconTheme,
+    double? groupAlignment,
+    NavigationRailLabelType? labelType,
+  }) {
+    return NavigationRailThemeData(
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      elevation: elevation ?? this.elevation,
+      unselectedLabelTextStyle: unselectedLabelTextStyle ?? this.unselectedLabelTextStyle,
+      selectedLabelTextStyle: selectedLabelTextStyle ?? this.selectedLabelTextStyle,
+      unselectedIconTheme: unselectedIconTheme ?? this.unselectedIconTheme,
+      selectedIconTheme: selectedIconTheme ?? this.selectedIconTheme,
+      groupAlignment: groupAlignment ?? this.groupAlignment,
+      labelType: labelType ?? this.labelType,
+    );
+  }
+
+  /// Linearly interpolate between two navigation rail themes.
+  ///
+  /// If both arguments are null then null is returned.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static NavigationRailThemeData? lerp(NavigationRailThemeData? a, NavigationRailThemeData? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    return NavigationRailThemeData(
+      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
+      elevation: lerpDouble(a?.elevation, b?.elevation, t),
+      unselectedLabelTextStyle: TextStyle.lerp(a?.unselectedLabelTextStyle, b?.unselectedLabelTextStyle, t),
+      selectedLabelTextStyle: TextStyle.lerp(a?.selectedLabelTextStyle, b?.selectedLabelTextStyle, t),
+      unselectedIconTheme: IconThemeData.lerp(a?.unselectedIconTheme, b?.unselectedIconTheme, t),
+      selectedIconTheme: IconThemeData.lerp(a?.selectedIconTheme, b?.selectedIconTheme, t),
+      groupAlignment: lerpDouble(a?.groupAlignment, b?.groupAlignment, t),
+      labelType: t < 0.5 ? a?.labelType : b?.labelType,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      backgroundColor,
+      elevation,
+      unselectedLabelTextStyle,
+      selectedLabelTextStyle,
+      unselectedIconTheme,
+      selectedIconTheme,
+      groupAlignment,
+      labelType,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is NavigationRailThemeData
+        && other.backgroundColor == backgroundColor
+        && other.elevation == elevation
+        && other.unselectedLabelTextStyle == unselectedLabelTextStyle
+        && other.selectedLabelTextStyle == selectedLabelTextStyle
+        && other.unselectedIconTheme == unselectedIconTheme
+        && other.selectedIconTheme == selectedIconTheme
+        && other.groupAlignment == groupAlignment
+        && other.labelType == labelType;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    const NavigationRailThemeData defaultData = NavigationRailThemeData();
+
+    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: defaultData.backgroundColor));
+    properties.add(DoubleProperty('elevation', elevation, defaultValue: defaultData.elevation));
+    properties.add(DiagnosticsProperty<TextStyle>('unselectedLabelTextStyle', unselectedLabelTextStyle, defaultValue: defaultData.unselectedLabelTextStyle));
+    properties.add(DiagnosticsProperty<TextStyle>('selectedLabelTextStyle', selectedLabelTextStyle, defaultValue: defaultData.selectedLabelTextStyle));
+    properties.add(DiagnosticsProperty<IconThemeData>('unselectedIconTheme', unselectedIconTheme, defaultValue: defaultData.unselectedIconTheme));
+    properties.add(DiagnosticsProperty<IconThemeData>('selectedIconTheme', selectedIconTheme, defaultValue: defaultData.selectedIconTheme));
+    properties.add(DoubleProperty('groupAlignment', groupAlignment, defaultValue: defaultData.groupAlignment));
+    properties.add(DiagnosticsProperty<NavigationRailLabelType>('labelType', labelType, defaultValue: defaultData.labelType));
+  }
+}
+
+/// An inherited widget that defines visual properties for [NavigationRail]s and
+/// [NavigationRailDestination]s in this widget's subtree.
+///
+/// Values specified here are used for [NavigationRail] properties that are not
+/// given an explicit non-null value.
+class NavigationRailTheme extends InheritedTheme {
+  /// Creates a navigation rail theme that controls the
+  /// [NavigationRailThemeData] properties for a [NavigationRail].
+  ///
+  /// The data argument must not be null.
+  const NavigationRailTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null), super(key: key, child: child);
+
+  /// Specifies the background color, elevation, label text style, icon theme,
+  /// group alignment, and label type and border values for descendant
+  /// [NavigationRail] widgets.
+  final NavigationRailThemeData data;
+
+  /// The closest instance of this class that encloses the given context.
+  ///
+  /// If there is no enclosing [NavigationRailTheme] widget, then
+  /// [ThemeData.navigationRailTheme] is used.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// NavigationRailTheme theme = NavigationRailTheme.of(context);
+  /// ```
+  static NavigationRailThemeData of(BuildContext context) {
+    final NavigationRailTheme? navigationRailTheme = context.dependOnInheritedWidgetOfExactType<NavigationRailTheme>();
+    return navigationRailTheme?.data ?? Theme.of(context).navigationRailTheme;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return NavigationRailTheme(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(NavigationRailTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/outline_button.dart b/lib/src/material/outline_button.dart
new file mode 100644
index 0000000..1c9f958
--- /dev/null
+++ b/lib/src/material/outline_button.dart
@@ -0,0 +1,623 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'button_theme.dart';
+import 'colors.dart';
+import 'material_button.dart';
+import 'material_state.dart';
+import 'raised_button.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+// The total time to make the button's fill color opaque and change
+// its elevation. Only applies when highlightElevation > 0.0.
+const Duration _kPressDuration = Duration(milliseconds: 150);
+
+// Half of _kPressDuration: just the time to change the button's
+// elevation. Only applies when highlightElevation > 0.0.
+const Duration _kElevationDuration = Duration(milliseconds: 75);
+
+/// Similar to a [FlatButton] with a thin grey rounded rectangle border.
+///
+/// ### This class is obsolete, please use [OutlinedButton] instead.
+///
+/// FlatButton, RaisedButton, and OutlineButton have been replaced by
+/// TextButton, ElevatedButton, and OutlinedButton respectively.
+/// ButtonTheme has been replaced by TextButtonTheme,
+/// ElevatedButtonTheme, and OutlinedButtonTheme. The original classes
+/// will be deprecated soon, please migrate code that uses them.
+/// There's a detailed migration guide for the new button and button
+/// theme classes in
+/// [flutter.dev/go/material-button-migration-guide](https://flutter.dev/go/material-button-migration-guide).
+///
+/// The outline button's border shape is defined by [shape]
+/// and its appearance is defined by [borderSide], [disabledBorderColor],
+/// and [highlightedBorderColor]. By default the border is a one pixel
+/// wide grey rounded rectangle that does not change when the button is
+/// pressed or disabled. By default the button's background is transparent.
+///
+/// If the [onPressed] or [onLongPress] callbacks are null, then the button will be disabled and by
+/// default will resemble a flat button in the [disabledColor].
+///
+/// The button's [highlightElevation], which defines the size of the
+/// drop shadow when the button is pressed, is 0.0 (no shadow) by default.
+/// If [highlightElevation] is given a value greater than 0.0 then the button
+/// becomes a cross between [RaisedButton] and [FlatButton]: a bordered
+/// button whose elevation increases and whose background becomes opaque
+/// when the button is pressed.
+///
+/// If you want an ink-splash effect for taps, but don't want to use a button,
+/// consider using [InkWell] directly.
+///
+/// Outline buttons have a minimum size of 88.0 by 36.0 which can be overridden
+/// with [ButtonTheme].
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_center_no_null_safety}
+///
+/// Here is an example of a basic [OutlineButton].
+///
+/// ```dart
+///   Widget build(BuildContext context) {
+///     return OutlineButton(
+///       onPressed: () {
+///         print('Received click');
+///       },
+///       child: Text('Click Me'),
+///     );
+///   }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [RaisedButton], a filled material design button with a shadow.
+///  * [FlatButton], a material design button without a shadow.
+///  * [DropdownButton], a button that shows options to select from.
+///  * [FloatingActionButton], the round button in material applications.
+///  * [IconButton], to create buttons that just contain icons.
+///  * [InkWell], which implements the ink splash part of a flat button.
+///  * <https://material.io/design/components/buttons.html>
+class OutlineButton extends MaterialButton {
+  /// Create an outline button.
+  ///
+  /// The [highlightElevation] argument must be null or a positive value
+  /// and the [autofocus] and [clipBehavior] arguments must not be null.
+  const OutlineButton({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    MouseCursor? mouseCursor,
+    ButtonTextTheme? textTheme,
+    Color? textColor,
+    Color? disabledTextColor,
+    Color? color,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    double? highlightElevation,
+    this.borderSide,
+    this.disabledBorderColor,
+    this.highlightedBorderColor,
+    EdgeInsetsGeometry? padding,
+    VisualDensity? visualDensity,
+    ShapeBorder? shape,
+    Clip clipBehavior = Clip.none,
+    FocusNode? focusNode,
+    bool autofocus = false,
+    MaterialTapTargetSize? materialTapTargetSize,
+    Widget? child,
+  }) : assert(highlightElevation == null || highlightElevation >= 0.0),
+       assert(clipBehavior != null),
+       assert(autofocus != null),
+       super(
+         key: key,
+         onPressed: onPressed,
+         onLongPress: onLongPress,
+         mouseCursor: mouseCursor,
+         textTheme: textTheme,
+         textColor: textColor,
+         disabledTextColor: disabledTextColor,
+         color: color,
+         focusColor: focusColor,
+         hoverColor: hoverColor,
+         highlightColor: highlightColor,
+         splashColor: splashColor,
+         highlightElevation: highlightElevation,
+         padding: padding,
+         visualDensity: visualDensity,
+         shape: shape,
+         clipBehavior: clipBehavior,
+         focusNode: focusNode,
+         materialTapTargetSize: materialTapTargetSize,
+         autofocus: autofocus,
+         child: child,
+       );
+
+  /// Create an outline button from a pair of widgets that serve as the button's
+  /// [icon] and [label].
+  ///
+  /// The icon and label are arranged in a row and padded by 12 logical pixels
+  /// at the start, and 16 at the end, with an 8 pixel gap in between.
+  ///
+  /// The [highlightElevation] argument must be null or a positive value. The
+  /// [icon], [label], [autofocus], and [clipBehavior] arguments must not be null.
+  factory OutlineButton.icon({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    MouseCursor? mouseCursor,
+    ButtonTextTheme? textTheme,
+    Color? textColor,
+    Color? disabledTextColor,
+    Color? color,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    double? highlightElevation,
+    Color? highlightedBorderColor,
+    Color? disabledBorderColor,
+    BorderSide? borderSide,
+    EdgeInsetsGeometry? padding,
+    VisualDensity? visualDensity,
+    ShapeBorder? shape,
+    Clip clipBehavior,
+    FocusNode? focusNode,
+    bool autofocus,
+    MaterialTapTargetSize? materialTapTargetSize,
+    required Widget icon,
+    required Widget label,
+  }) = _OutlineButtonWithIcon;
+
+  /// The outline border's color when the button is [enabled] and pressed.
+  ///
+  /// By default the border's color does not change when the button
+  /// is pressed.
+  ///
+  /// This field is ignored if [BorderSide.color] is a [MaterialStateProperty<Color>].
+  final Color? highlightedBorderColor;
+
+  /// The outline border's color when the button is not [enabled].
+  ///
+  /// By default the outline border's color does not change when the
+  /// button is disabled.
+  ///
+  /// This field is ignored if [BorderSide.color] is a [MaterialStateProperty<Color>].
+  final Color? disabledBorderColor;
+
+  /// Defines the color of the border when the button is enabled but not
+  /// pressed, and the border outline's width and style in general.
+  ///
+  /// If the border side's [BorderSide.style] is [BorderStyle.none], then
+  /// an outline is not drawn.
+  ///
+  /// If null the default border's style is [BorderStyle.solid], its
+  /// [BorderSide.width] is 1.0, and its color is a light shade of grey.
+  ///
+  /// If [BorderSide.color] is a [MaterialStateProperty<Color>], [MaterialStateProperty.resolve]
+  /// is used in all states and both [highlightedBorderColor] and [disabledBorderColor]
+  /// are ignored.
+  final BorderSide? borderSide;
+
+  @override
+  Widget build(BuildContext context) {
+    final ButtonThemeData buttonTheme = ButtonTheme.of(context);
+    return _OutlineButton(
+      autofocus: autofocus,
+      onPressed: onPressed,
+      onLongPress: onLongPress,
+      mouseCursor: mouseCursor,
+      brightness: buttonTheme.getBrightness(this),
+      textTheme: textTheme,
+      textColor: buttonTheme.getTextColor(this),
+      disabledTextColor: buttonTheme.getDisabledTextColor(this),
+      color: color,
+      focusColor: buttonTheme.getFocusColor(this),
+      hoverColor: buttonTheme.getHoverColor(this),
+      highlightColor: buttonTheme.getHighlightColor(this),
+      splashColor: buttonTheme.getSplashColor(this),
+      highlightElevation: buttonTheme.getHighlightElevation(this),
+      borderSide: borderSide,
+      disabledBorderColor: disabledBorderColor,
+      highlightedBorderColor: highlightedBorderColor ?? buttonTheme.colorScheme!.primary,
+      padding: buttonTheme.getPadding(this),
+      visualDensity: visualDensity,
+      shape: buttonTheme.getShape(this),
+      clipBehavior: clipBehavior,
+      focusNode: focusNode,
+      materialTapTargetSize: materialTapTargetSize,
+      child: child,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<BorderSide>('borderSide', borderSide, defaultValue: null));
+    properties.add(ColorProperty('disabledBorderColor', disabledBorderColor, defaultValue: null));
+    properties.add(ColorProperty('highlightedBorderColor', highlightedBorderColor, defaultValue: null));
+  }
+}
+
+// The type of OutlineButtons created with OutlineButton.icon.
+//
+// This class only exists to give OutlineButtons created with OutlineButton.icon
+// a distinct class for the sake of ButtonTheme. It can not be instantiated.
+class _OutlineButtonWithIcon extends OutlineButton with MaterialButtonWithIconMixin {
+  _OutlineButtonWithIcon({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    MouseCursor? mouseCursor,
+    ButtonTextTheme? textTheme,
+    Color? textColor,
+    Color? disabledTextColor,
+    Color? color,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    double? highlightElevation,
+    Color? highlightedBorderColor,
+    Color? disabledBorderColor,
+    BorderSide? borderSide,
+    EdgeInsetsGeometry? padding,
+    VisualDensity? visualDensity,
+    ShapeBorder? shape,
+    Clip clipBehavior = Clip.none,
+    FocusNode? focusNode,
+    bool autofocus = false,
+    MaterialTapTargetSize? materialTapTargetSize,
+    required Widget icon,
+    required Widget label,
+  }) : assert(highlightElevation == null || highlightElevation >= 0.0),
+       assert(clipBehavior != null),
+       assert(autofocus != null),
+       assert(icon != null),
+       assert(label != null),
+       super(
+         key: key,
+         onPressed: onPressed,
+         onLongPress: onLongPress,
+         mouseCursor: mouseCursor,
+         textTheme: textTheme,
+         textColor: textColor,
+         disabledTextColor: disabledTextColor,
+         color: color,
+         focusColor: focusColor,
+         hoverColor: hoverColor,
+         highlightColor: highlightColor,
+         splashColor: splashColor,
+         highlightElevation: highlightElevation,
+         disabledBorderColor: disabledBorderColor,
+         highlightedBorderColor: highlightedBorderColor,
+         borderSide: borderSide,
+         padding: padding,
+         visualDensity: visualDensity,
+         shape: shape,
+         clipBehavior: clipBehavior,
+         focusNode: focusNode,
+         autofocus: autofocus,
+         materialTapTargetSize: materialTapTargetSize,
+         child: Row(
+           mainAxisSize: MainAxisSize.min,
+           children: <Widget>[
+             icon,
+             const SizedBox(width: 8.0),
+             label,
+           ],
+         ),
+       );
+}
+
+class _OutlineButton extends StatefulWidget {
+  const _OutlineButton({
+    Key? key,
+    required this.onPressed,
+    this.onLongPress,
+    this.mouseCursor,
+    required this.brightness,
+    this.textTheme,
+    required this.textColor,
+    required this.disabledTextColor,
+    this.color,
+    required this.focusColor,
+    required this.hoverColor,
+    required this.highlightColor,
+    required this.splashColor,
+    required this.highlightElevation,
+    this.borderSide,
+    this.disabledBorderColor,
+    required this.highlightedBorderColor,
+    required this.padding,
+    this.visualDensity,
+    required this.shape,
+    this.clipBehavior = Clip.none,
+    this.focusNode,
+    this.autofocus = false,
+    this.child,
+    this.materialTapTargetSize,
+  }) : assert(highlightElevation != null && highlightElevation >= 0.0),
+       assert(highlightedBorderColor != null),
+       assert(clipBehavior != null),
+       assert(autofocus != null),
+       super(key: key);
+
+  final VoidCallback? onPressed;
+  final VoidCallback? onLongPress;
+  final MouseCursor? mouseCursor;
+  final Brightness brightness;
+  final ButtonTextTheme? textTheme;
+  final Color textColor;
+  final Color disabledTextColor;
+  final Color? color;
+  final Color splashColor;
+  final Color focusColor;
+  final Color hoverColor;
+  final Color highlightColor;
+  final double highlightElevation;
+  final BorderSide? borderSide;
+  final Color? disabledBorderColor;
+  final Color highlightedBorderColor;
+  final EdgeInsetsGeometry padding;
+  final VisualDensity? visualDensity;
+  final ShapeBorder shape;
+  final Clip clipBehavior;
+  final FocusNode? focusNode;
+  final bool autofocus;
+  final Widget? child;
+  final MaterialTapTargetSize? materialTapTargetSize;
+
+  bool get enabled => onPressed != null || onLongPress != null;
+
+  @override
+  _OutlineButtonState createState() => _OutlineButtonState();
+}
+
+
+class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProviderStateMixin {
+  late AnimationController _controller;
+  late Animation<double> _fillAnimation;
+  late Animation<double> _elevationAnimation;
+  bool _pressed = false;
+
+  @override
+  void initState() {
+    super.initState();
+
+    // When highlightElevation > 0.0, the Material widget animates its
+    // shape (which includes the outline border) and elevation over
+    // _kElevationDuration. When pressed, the button makes its fill
+    // color opaque white first, and then sets its
+    // highlightElevation. We can't change the elevation while the
+    // button's fill is translucent, because the shadow fills the
+    // interior of the button.
+
+    _controller = AnimationController(
+      duration: _kPressDuration,
+      vsync: this,
+    );
+    _fillAnimation = CurvedAnimation(
+      parent: _controller,
+      curve: const Interval(0.0, 0.5,
+        curve: Curves.fastOutSlowIn,
+      ),
+    );
+    _elevationAnimation = CurvedAnimation(
+      parent: _controller,
+      curve: const Interval(0.5, 0.5),
+      reverseCurve: const Interval(1.0, 1.0),
+    );
+  }
+
+  @override
+  void didUpdateWidget(_OutlineButton oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (_pressed && !widget.enabled) {
+      _pressed = false;
+      _controller.reverse();
+    }
+  }
+
+  void _handleHighlightChanged(bool value) {
+    if (_pressed == value)
+      return;
+    setState(() {
+      _pressed = value;
+      if (value)
+        _controller.forward();
+      else
+        _controller.reverse();
+    });
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  Color _getFillColor() {
+    if (widget.highlightElevation == null || widget.highlightElevation == 0.0)
+      return Colors.transparent;
+    final Color color = widget.color ?? Theme.of(context).canvasColor;
+    final Tween<Color?> colorTween = ColorTween(
+      begin: color.withAlpha(0x00),
+      end: color.withAlpha(0xFF),
+    );
+    return colorTween.evaluate(_fillAnimation)!;
+  }
+
+  Color? get _outlineColor {
+    // If outline color is a `MaterialStateProperty`, it will be used in all
+    // states, otherwise we determine the outline color in the current state.
+    if (widget.borderSide?.color is MaterialStateProperty<Color?>)
+      return widget.borderSide!.color;
+    if (!widget.enabled)
+      return widget.disabledBorderColor;
+    if (_pressed)
+      return widget.highlightedBorderColor;
+    return widget.borderSide?.color;
+  }
+
+  BorderSide _getOutline() {
+    if (widget.borderSide?.style == BorderStyle.none)
+      return widget.borderSide!;
+
+    final Color themeColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.12);
+
+    return BorderSide(
+      color: _outlineColor ?? themeColor,
+      width: widget.borderSide?.width ?? 1.0,
+    );
+  }
+
+  double _getHighlightElevation() {
+    if (widget.highlightElevation == null || widget.highlightElevation == 0.0)
+      return 0.0;
+    return Tween<double>(
+      begin: 0.0,
+      end: widget.highlightElevation,
+    ).evaluate(_elevationAnimation);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+
+    return AnimatedBuilder(
+      animation: _controller,
+      builder: (BuildContext context, Widget? child) {
+        return RaisedButton(
+          autofocus: widget.autofocus,
+          textColor: widget.textColor,
+          disabledTextColor: widget.disabledTextColor,
+          color: _getFillColor(),
+          splashColor: widget.splashColor,
+          focusColor: widget.focusColor,
+          hoverColor: widget.hoverColor,
+          highlightColor: widget.highlightColor,
+          disabledColor: Colors.transparent,
+          onPressed: widget.onPressed,
+          onLongPress: widget.onLongPress,
+          mouseCursor: widget.mouseCursor,
+          elevation: 0.0,
+          disabledElevation: 0.0,
+          focusElevation: 0.0,
+          hoverElevation: 0.0,
+          highlightElevation: _getHighlightElevation(),
+          onHighlightChanged: _handleHighlightChanged,
+          padding: widget.padding,
+          visualDensity: widget.visualDensity ?? theme.visualDensity,
+          shape: _OutlineBorder(
+            shape: widget.shape,
+            side: _getOutline(),
+          ),
+          clipBehavior: widget.clipBehavior,
+          focusNode: widget.focusNode,
+          animationDuration: _kElevationDuration,
+          materialTapTargetSize: widget.materialTapTargetSize,
+          child: widget.child,
+        );
+      },
+    );
+  }
+}
+
+// Render the button's outline border using using the OutlineButton's
+// border parameters and the button or buttonTheme's shape.
+class _OutlineBorder extends ShapeBorder implements MaterialStateProperty<ShapeBorder>{
+  const _OutlineBorder({
+    required this.shape,
+    required this.side,
+  }) : assert(shape != null),
+       assert(side != null);
+
+  final ShapeBorder shape;
+  final BorderSide side;
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    return EdgeInsets.all(side.width);
+  }
+
+  @override
+  ShapeBorder scale(double t) {
+    return _OutlineBorder(
+      shape: shape.scale(t),
+      side: side.scale(t),
+    );
+  }
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    assert(t != null);
+    if (a is _OutlineBorder) {
+      return _OutlineBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        shape: ShapeBorder.lerp(a.shape, shape, t)!,
+      );
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    assert(t != null);
+    if (b is _OutlineBorder) {
+      return _OutlineBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        shape: ShapeBorder.lerp(shape, b.shape, t)!,
+      );
+    }
+    return super.lerpTo(b, t);
+  }
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    return shape.getInnerPath(rect.deflate(side.width), textDirection: textDirection);
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    return shape.getOuterPath(rect, textDirection: textDirection);
+  }
+
+  @override
+  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
+    switch (side.style) {
+      case BorderStyle.none:
+        break;
+      case BorderStyle.solid:
+        canvas.drawPath(shape.getOuterPath(rect, textDirection: textDirection), side.toPaint());
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _OutlineBorder
+        && other.side == side
+        && other.shape == shape;
+  }
+
+  @override
+  int get hashCode => hashValues(side, shape);
+
+  @override
+  ShapeBorder resolve(Set<MaterialState> states) {
+    return _OutlineBorder(
+      shape: shape,
+      side: side.copyWith(color: MaterialStateProperty.resolveAs<Color>(side.color, states),
+    ));
+  }
+}
diff --git a/lib/src/material/outlined_button.dart b/lib/src/material/outlined_button.dart
new file mode 100644
index 0000000..8f8a0b3
--- /dev/null
+++ b/lib/src/material/outlined_button.dart
@@ -0,0 +1,351 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'button_style.dart';
+import 'button_style_button.dart';
+import 'color_scheme.dart';
+import 'colors.dart';
+import 'constants.dart';
+import 'material_state.dart';
+import 'outlined_button_theme.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// A Material Design "Outlined Button"; essentially a [TextButton]
+/// with an outlined border.
+///
+/// Outlined buttons are medium-emphasis buttons. They contain actions
+/// that are important, but they aren’t the primary action in an app.
+///
+/// An outlined button is a label [child] displayed on a (zero
+/// elevation) [Material] widget. The label's [Text] and [Icon]
+/// widgets are displayed in the [style]'s
+/// [ButtonStyle.foregroundColor] and the outline's weight and color
+/// are defined by [ButtonStyle.side].  The button reacts to touches
+/// by filling with the [style]'s [ButtonStyle.backgroundColor].
+///
+/// The outlined button's default style is defined by [defaultStyleOf].
+/// The style of this outline button can be overridden with its [style]
+/// parameter. The style of all text buttons in a subtree can be
+/// overridden with the [OutlinedButtonTheme] and the style of all of the
+/// outlined buttons in an app can be overridden with the [Theme]'s
+/// [ThemeData.outlinedButtonTheme] property.
+///
+/// The static [styleFrom] method is a convenient way to create a
+/// outlined button [ButtonStyle] from simple values.
+///
+/// See also:
+///
+///  * [ElevatedButton], a filled material design button with a shadow.
+///  * [TextButton], a material design button without a shadow.
+///  * <https://material.io/design/components/buttons.html>
+class OutlinedButton extends ButtonStyleButton {
+  /// Create an OutlinedButton.
+  ///
+  /// The [autofocus] and [clipBehavior] arguments must not be null.
+  const OutlinedButton({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ButtonStyle? style,
+    FocusNode? focusNode,
+    bool autofocus = false,
+    Clip clipBehavior = Clip.none,
+    required Widget child,
+  }) : super(
+    key: key,
+    onPressed: onPressed,
+    onLongPress: onLongPress,
+    style: style,
+    focusNode: focusNode,
+    autofocus: autofocus,
+    clipBehavior: clipBehavior,
+    child: child,
+  );
+
+  /// Create a text button from a pair of widgets that serve as the button's
+  /// [icon] and [label].
+  ///
+  /// The icon and label are arranged in a row and padded by 12 logical pixels
+  /// at the start, and 16 at the end, with an 8 pixel gap in between.
+  ///
+  /// The [icon] and [label] arguments must not be null.
+  factory OutlinedButton.icon({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ButtonStyle? style,
+    FocusNode? focusNode,
+    bool? autofocus,
+    Clip? clipBehavior,
+    required Widget icon,
+    required Widget label,
+  }) = _OutlinedButtonWithIcon;
+
+  /// A static convenience method that constructs an outlined button
+  /// [ButtonStyle] given simple values.
+  ///
+  /// The [primary], and [onSurface] colors are used to to create a
+  /// [MaterialStateProperty] [ButtonStyle.foregroundColor] value in the same
+  /// way that [defaultStyleOf] uses the [ColorScheme] colors with the same
+  /// names. Specify a value for [primary] to specify the color of the button's
+  /// text and icons as well as the overlay colors used to indicate the hover,
+  /// focus, and pressed states. Use [onSurface] to specify the button's
+  /// disabled text and icon color.
+  ///
+  /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
+  /// parameters are used to construct [ButtonStyle.mouseCursor].
+  ///
+  /// All of the other parameters are either used directly or used to
+  /// create a [MaterialStateProperty] with a single value for all
+  /// states.
+  ///
+  /// All parameters default to null, by default this method returns
+  /// a [ButtonStyle] that doesn't override anything.
+  ///
+  /// For example, to override the default shape and outline for an
+  /// [OutlinedButton], one could write:
+  ///
+  /// ```dart
+  /// OutlinedButton(
+  ///   style: OutlinedButton.styleFrom(
+  ///      shape: StadiumBorder(),
+  ///      side: BorderSide(width: 2, color: Colors.green),
+  ///   ),
+  /// )
+  /// ```
+  static ButtonStyle styleFrom({
+    Color? primary,
+    Color? onSurface,
+    Color? backgroundColor,
+    Color? shadowColor,
+    double? elevation,
+    TextStyle? textStyle,
+    EdgeInsetsGeometry? padding,
+    Size? minimumSize,
+    BorderSide? side,
+    OutlinedBorder? shape,
+    MouseCursor? enabledMouseCursor,
+    MouseCursor? disabledMouseCursor,
+    VisualDensity? visualDensity,
+    MaterialTapTargetSize? tapTargetSize,
+    Duration? animationDuration,
+    bool? enableFeedback,
+  }) {
+    final MaterialStateProperty<Color?>? foregroundColor = (onSurface == null && primary == null)
+      ? null
+      : _OutlinedButtonDefaultForeground(primary, onSurface);
+    final MaterialStateProperty<Color?>? overlayColor = (primary == null)
+      ? null
+      : _OutlinedButtonDefaultOverlay(primary);
+    final MaterialStateProperty<MouseCursor>? mouseCursor = (enabledMouseCursor == null && disabledMouseCursor == null)
+      ? null
+      : _OutlinedButtonDefaultMouseCursor(enabledMouseCursor!, disabledMouseCursor!);
+
+    return ButtonStyle(
+      textStyle: ButtonStyleButton.allOrNull<TextStyle>(textStyle),
+      foregroundColor: foregroundColor,
+      backgroundColor: ButtonStyleButton.allOrNull<Color>(backgroundColor),
+      overlayColor: overlayColor,
+      shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
+      elevation: ButtonStyleButton.allOrNull<double>(elevation),
+      padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
+      minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
+      side: ButtonStyleButton.allOrNull<BorderSide>(side),
+      shape: ButtonStyleButton.allOrNull<OutlinedBorder>(shape),
+      mouseCursor: mouseCursor,
+      visualDensity: visualDensity,
+      tapTargetSize: tapTargetSize,
+      animationDuration: animationDuration,
+      enableFeedback: enableFeedback,
+    );
+  }
+
+  /// Defines the button's default appearance.
+  ///
+  /// With the exception of [ButtonStyle.side], which defines the
+  /// outline, and [ButtonStyle.padding], the returned style is the
+  /// same as for [TextButton].
+  ///
+  /// The button [child]'s [Text] and [Icon] widgets are rendered with
+  /// the [ButtonStyle]'s foreground color. The button's [InkWell] adds
+  /// the style's overlay color when the button is focused, hovered
+  /// or pressed. The button's background color becomes its [Material]
+  /// color and is transparent by default.
+  ///
+  /// All of the ButtonStyle's defaults appear below. In this list
+  /// "Theme.foo" is shorthand for `Theme.of(context).foo`. Color
+  /// scheme values like "onSurface(0.38)" are shorthand for
+  /// `onSurface.withOpacity(0.38)`. [MaterialStateProperty] valued
+  /// properties that are not followed by by a sublist have the same
+  /// value for all states, otherwise the values are as specified for
+  /// each state and "others" means all other states.
+  ///
+  /// The color of the [ButtonStyle.textStyle] is not used, the
+  /// [ButtonStyle.foregroundColor] is used instead.
+  ///
+  /// * `textStyle` - Theme.textTheme.button
+  /// * `backgroundColor` - transparent
+  /// * `foregroundColor`
+  ///   * disabled - Theme.colorScheme.onSurface(0.38)
+  ///   * others - Theme.colorScheme.primary
+  /// * `overlayColor`
+  ///   * hovered - Theme.colorScheme.primary(0.04)
+  ///   * focused or pressed - Theme.colorScheme.primary(0.12)
+  /// * `shadowColor` - Theme.shadowColor
+  /// * `elevation` - 0
+  /// * `padding`
+  ///   * `textScaleFactor <= 1` - horizontal(16)
+  ///   * `1 < textScaleFactor <= 2` - lerp(horizontal(16), horizontal(8))
+  ///   * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4))
+  ///   * `3 < textScaleFactor` - horizontal(4)
+  /// * `minimumSize` - Size(64, 36)
+  /// * `side` - BorderSide(width: 1, color: Theme.colorScheme.onSurface(0.12))
+  /// * `shape` - RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))
+  /// * `mouseCursor`
+  ///   * disabled - SystemMouseCursors.forbidden
+  ///   * others - SystemMouseCursors.click
+  /// * `visualDensity` - theme.visualDensity
+  /// * `tapTargetSize` - theme.materialTapTargetSize
+  /// * `animationDuration` - kThemeChangeDuration
+  /// * `enableFeedback` - true
+  @override
+  ButtonStyle defaultStyleOf(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final ColorScheme colorScheme = theme.colorScheme;
+
+    final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding(
+      const EdgeInsets.symmetric(horizontal: 16),
+      const EdgeInsets.symmetric(horizontal: 8),
+      const EdgeInsets.symmetric(horizontal: 4),
+      MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
+    );
+
+    return styleFrom(
+      primary: colorScheme.primary,
+      onSurface: colorScheme.onSurface,
+      backgroundColor: Colors.transparent,
+      shadowColor: theme.shadowColor,
+      elevation: 0,
+      textStyle: theme.textTheme.button,
+      padding: scaledPadding,
+      minimumSize: const Size(64, 36),
+      side: BorderSide(
+        color: Theme.of(context).colorScheme.onSurface.withOpacity(0.12),
+        width: 1,
+      ),
+      shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))),
+      enabledMouseCursor: SystemMouseCursors.click,
+      disabledMouseCursor: SystemMouseCursors.forbidden,
+      visualDensity: theme.visualDensity,
+      tapTargetSize: theme.materialTapTargetSize,
+      animationDuration: kThemeChangeDuration,
+      enableFeedback: true,
+    );
+  }
+
+  @override
+  ButtonStyle? themeStyleOf(BuildContext context) {
+    return OutlinedButtonTheme.of(context).style;
+  }
+}
+
+@immutable
+class _OutlinedButtonDefaultForeground extends MaterialStateProperty<Color?>  with Diagnosticable {
+  _OutlinedButtonDefaultForeground(this.primary, this.onSurface);
+
+  final Color? primary;
+  final Color? onSurface;
+
+  @override
+  Color? resolve(Set<MaterialState> states) {
+    if (states.contains(MaterialState.disabled))
+      return onSurface?.withOpacity(0.38);
+    return primary;
+  }
+}
+
+@immutable
+class _OutlinedButtonDefaultOverlay extends MaterialStateProperty<Color?> with Diagnosticable {
+  _OutlinedButtonDefaultOverlay(this.primary);
+
+  final Color primary;
+
+  @override
+  Color? resolve(Set<MaterialState> states) {
+    if (states.contains(MaterialState.hovered))
+      return primary.withOpacity(0.04);
+    if (states.contains(MaterialState.focused) || states.contains(MaterialState.pressed))
+      return primary.withOpacity(0.12);
+    return null;
+  }
+}
+
+@immutable
+class _OutlinedButtonDefaultMouseCursor extends MaterialStateProperty<MouseCursor> with Diagnosticable {
+  _OutlinedButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor);
+
+  final MouseCursor enabledCursor;
+  final MouseCursor disabledCursor;
+
+  @override
+  MouseCursor resolve(Set<MaterialState> states) {
+    if (states.contains(MaterialState.disabled))
+      return disabledCursor;
+    return enabledCursor;
+  }
+}
+
+class _OutlinedButtonWithIcon extends OutlinedButton {
+  _OutlinedButtonWithIcon({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ButtonStyle? style,
+    FocusNode? focusNode,
+    bool? autofocus,
+    Clip? clipBehavior,
+    required Widget icon,
+    required Widget label,
+  }) : assert(icon != null),
+       assert(label != null),
+       super(
+         key: key,
+         onPressed: onPressed,
+         onLongPress: onLongPress,
+         style: style,
+         focusNode: focusNode,
+         autofocus: autofocus ?? false,
+         clipBehavior: clipBehavior ?? Clip.none,
+         child: _OutlinedButtonWithIconChild(icon: icon, label: label),
+      );
+}
+
+class _OutlinedButtonWithIconChild extends StatelessWidget {
+  const _OutlinedButtonWithIconChild({
+    Key? key,
+    required this.label,
+    required this.icon,
+  }) : super(key: key);
+
+  final Widget label;
+  final Widget icon;
+
+  @override
+  Widget build(BuildContext context) {
+    final double scale = MediaQuery.maybeOf(context)?.textScaleFactor ?? 1;
+    final double gap = scale <= 1 ? 8 : lerpDouble(8, 4, math.min(scale - 1, 1))!;
+    return Row(
+      mainAxisSize: MainAxisSize.min,
+      children: <Widget>[icon, SizedBox(width: gap), label],
+    );
+  }
+}
diff --git a/lib/src/material/outlined_button_theme.dart b/lib/src/material/outlined_button_theme.dart
new file mode 100644
index 0000000..f6746da
--- /dev/null
+++ b/lib/src/material/outlined_button_theme.dart
@@ -0,0 +1,124 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'button_style.dart';
+import 'theme.dart';
+
+/// A [ButtonStyle] that overrides the default appearance of
+/// [OutlinedButton]s when it's used with [OutlinedButtonTheme] or with the
+/// overall [Theme]'s [ThemeData.outlinedButtonTheme].
+///
+/// The [style]'s properties override [OutlinedButton]'s default style,
+/// i.e.  the [ButtonStyle] returned by [OutlinedButton.defaultStyleOf]. Only
+/// the style's non-null property values or resolved non-null
+/// [MaterialStateProperty] values are used.
+///
+/// See also:
+///
+///  * [OutlinedButtonTheme], the theme which is configured with this class.
+///  * [OutlinedButton.defaultStyleOf], which returns the default [ButtonStyle]
+///    for outlined buttons.
+///  * [OutlinedButton.styleFrom], which converts simple values into a
+///    [ButtonStyle] that's consistent with [OutlinedButton]'s defaults.
+///  * [MaterialStateProperty.resolve], "resolve" a material state property
+///    to a simple value based on a set of [MaterialState]s.
+///  * [ThemeData.outlinedButtonTheme], which can be used to override the default
+///    [ButtonStyle] for [OutlinedButton]s below the overall [Theme].
+@immutable
+class OutlinedButtonThemeData with Diagnosticable {
+  /// Creates a [OutlinedButtonThemeData].
+  ///
+  /// The [style] may be null.
+  const OutlinedButtonThemeData({ this.style });
+
+  /// Overrides for [OutlinedButton]'s default style.
+  ///
+  /// Non-null properties or non-null resolved [MaterialStateProperty]
+  /// values override the [ButtonStyle] returned by
+  /// [OutlinedButton.defaultStyleOf].
+  ///
+  /// If [style] is null, then this theme doesn't override anything.
+  final ButtonStyle? style;
+
+  /// Linearly interpolate between two outlined button themes.
+  static OutlinedButtonThemeData? lerp(OutlinedButtonThemeData? a, OutlinedButtonThemeData? b, double t) {
+    assert (t != null);
+    if (a == null && b == null)
+      return null;
+    return OutlinedButtonThemeData(
+      style: ButtonStyle.lerp(a?.style, b?.style, t),
+    );
+  }
+
+  @override
+  int get hashCode {
+    return style.hashCode;
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is OutlinedButtonThemeData && other.style == style;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ButtonStyle>('style', style, defaultValue: null));
+  }
+}
+
+/// Overrides the default [ButtonStyle] of its [OutlinedButton] descendants.
+///
+/// See also:
+///
+///  * [OutlinedButtonThemeData], which is used to configure this theme.
+///  * [OutlinedButton.defaultStyleOf], which returns the default [ButtonStyle]
+///    for outlined buttons.
+///  * [OutlinedButton.styleFrom], which converts simple values into a
+///    [ButtonStyle] that's consistent with [OutlinedButton]'s defaults.
+///  * [ThemeData.outlinedButtonTheme], which can be used to override the default
+///    [ButtonStyle] for [OutlinedButton]s below the overall [Theme].
+class OutlinedButtonTheme extends InheritedTheme {
+  /// Create a [OutlinedButtonTheme].
+  ///
+  /// The [data] parameter must not be null.
+  const OutlinedButtonTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null), super(key: key, child: child);
+
+  /// The configuration of this theme.
+  final OutlinedButtonThemeData data;
+
+  /// The closest instance of this class that encloses the given context.
+  ///
+  /// If there is no enclosing [OutlinedButtonTheme] widget, then
+  /// [ThemeData.outlinedButtonTheme] is used.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// OutlinedButtonTheme theme = OutlinedButtonTheme.of(context);
+  /// ```
+  static OutlinedButtonThemeData of(BuildContext context) {
+    final OutlinedButtonTheme? buttonTheme = context.dependOnInheritedWidgetOfExactType<OutlinedButtonTheme>();
+    return buttonTheme?.data ?? Theme.of(context).outlinedButtonTheme;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return OutlinedButtonTheme(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(OutlinedButtonTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/page.dart b/lib/src/material/page.dart
new file mode 100644
index 0000000..033b362
--- /dev/null
+++ b/lib/src/material/page.dart
@@ -0,0 +1,207 @@
+// Copyright 2014 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.
+
+import 'package:flute/cupertino.dart';
+import 'package:flute/widgets.dart';
+
+import 'page_transitions_theme.dart';
+import 'theme.dart';
+
+/// A modal route that replaces the entire screen with a platform-adaptive
+/// transition.
+///
+/// {@macro flutter.material.materialRouteTransitionMixin}
+///
+/// By default, when a modal route is replaced by another, the previous route
+/// remains in memory. To free all the resources when this is not necessary, set
+/// [maintainState] to false.
+///
+/// The `fullscreenDialog` property specifies whether the incoming route is a
+/// fullscreen modal dialog. On iOS, those routes animate from the bottom to the
+/// top rather than horizontally.
+///
+/// The type `T` specifies the return type of the route which can be supplied as
+/// the route is popped from the stack via [Navigator.pop] by providing the
+/// optional `result` argument.
+///
+/// See also:
+///
+///  * [MaterialRouteTransitionMixin], which provides the material transition
+///    for this route.
+///  * [MaterialPage], which is a [Page] of this class.
+class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixin<T> {
+  /// Construct a MaterialPageRoute whose contents are defined by [builder].
+  ///
+  /// The values of [builder], [maintainState], and [PageRoute.fullscreenDialog]
+  /// must not be null.
+  MaterialPageRoute({
+    required this.builder,
+    RouteSettings? settings,
+    this.maintainState = true,
+    bool fullscreenDialog = false,
+  }) : assert(builder != null),
+       assert(maintainState != null),
+       assert(fullscreenDialog != null),
+       super(settings: settings, fullscreenDialog: fullscreenDialog) {
+    assert(opaque);
+  }
+
+  /// Builds the primary contents of the route.
+  final WidgetBuilder builder;
+
+  @override
+  Widget buildContent(BuildContext context) => builder(context);
+
+  @override
+  final bool maintainState;
+
+  @override
+  String get debugLabel => '${super.debugLabel}(${settings.name})';
+}
+
+
+/// A mixin that provides platform-adaptive transitions for a [PageRoute].
+///
+/// {@template flutter.material.materialRouteTransitionMixin}
+/// For Android, the entrance transition for the page slides the route upwards
+/// and fades it in. The exit transition is the same, but in reverse.
+///
+/// The transition is adaptive to the platform and on iOS, the route slides in
+/// from the right and exits in reverse. The route also shifts to the left in
+/// parallax when another page enters to cover it. (These directions are flipped
+/// in environments with a right-to-left reading direction.)
+/// {@endtemplate}
+///
+/// See also:
+///
+///  * [PageTransitionsTheme], which defines the default page transitions used
+///    by the [MaterialRouteTransitionMixin.buildTransitions].
+mixin MaterialRouteTransitionMixin<T> on PageRoute<T> {
+  /// Builds the primary contents of the route.
+  @protected
+  Widget buildContent(BuildContext context);
+
+  @override
+  Duration get transitionDuration => const Duration(milliseconds: 300);
+
+  @override
+  Color? get barrierColor => null;
+
+  @override
+  String? get barrierLabel => null;
+
+  @override
+  bool canTransitionTo(TransitionRoute<dynamic> nextRoute) {
+    // Don't perform outgoing animation if the next route is a fullscreen dialog.
+    return (nextRoute is MaterialRouteTransitionMixin && !nextRoute.fullscreenDialog)
+      || (nextRoute is CupertinoRouteTransitionMixin && !nextRoute.fullscreenDialog);
+  }
+
+  @override
+  Widget buildPage(
+    BuildContext context,
+    Animation<double> animation,
+    Animation<double> secondaryAnimation,
+  ) {
+    final Widget result = buildContent(context);
+    assert(() {
+      if (result == null) {
+        throw FlutterError(
+          'The builder for route "${settings.name}" returned null.\n'
+          'Route builders must never return null.'
+        );
+      }
+      return true;
+    }());
+    return Semantics(
+      scopesRoute: true,
+      explicitChildNodes: true,
+      child: result,
+    );
+  }
+
+  @override
+  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
+    final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme;
+    return theme.buildTransitions<T>(this, context, animation, secondaryAnimation, child);
+  }
+}
+
+/// A page that creates a material style [PageRoute].
+///
+/// {@macro flutter.material.materialRouteTransitionMixin}
+///
+/// By default, when the created route is replaced by another, the previous
+/// route remains in memory. To free all the resources when this is not
+/// necessary, set [maintainState] to false.
+///
+/// The `fullscreenDialog` property specifies whether the created route is a
+/// fullscreen modal dialog. On iOS, those routes animate from the bottom to the
+/// top rather than horizontally.
+///
+/// The type `T` specifies the return type of the route which can be supplied as
+/// the route is popped from the stack via [Navigator.transitionDelegate] by
+/// providing the optional `result` argument to the
+/// [RouteTransitionRecord.markForPop] in the [TransitionDelegate.resolve].
+///
+/// See also:
+///
+///  * [MaterialPageRoute], which is the [PageRoute] version of this class
+class MaterialPage<T> extends Page<T> {
+  /// Creates a material page.
+  const MaterialPage({
+    required this.child,
+    this.maintainState = true,
+    this.fullscreenDialog = false,
+    LocalKey? key,
+    String? name,
+    Object? arguments,
+  }) : assert(child != null),
+       assert(maintainState != null),
+       assert(fullscreenDialog != null),
+       super(key: key, name: name, arguments: arguments);
+
+  /// The content to be shown in the [Route] created by this page.
+  final Widget child;
+
+  /// {@macro flutter.widgets.ModalRoute.maintainState}
+  final bool maintainState;
+
+  /// {@macro flutter.widgets.PageRoute.fullscreenDialog}
+  final bool fullscreenDialog;
+
+  @override
+  Route<T> createRoute(BuildContext context) {
+    return _PageBasedMaterialPageRoute<T>(page: this);
+  }
+}
+
+// A page-based version of MaterialPageRoute.
+//
+// This route uses the builder from the page to build its content. This ensures
+// the content is up to date after page updates.
+class _PageBasedMaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixin<T> {
+  _PageBasedMaterialPageRoute({
+    required MaterialPage<T> page,
+  }) : assert(page != null),
+       super(settings: page) {
+    assert(opaque);
+  }
+
+  MaterialPage<T> get _page => settings as MaterialPage<T>;
+
+  @override
+  Widget buildContent(BuildContext context) {
+    return _page.child;
+  }
+
+  @override
+  bool get maintainState => _page.maintainState;
+
+  @override
+  bool get fullscreenDialog => _page.fullscreenDialog;
+
+  @override
+  String get debugLabel => '${super.debugLabel}(${_page.name})';
+}
diff --git a/lib/src/material/page_transitions_theme.dart b/lib/src/material/page_transitions_theme.dart
new file mode 100644
index 0000000..d7b3f9b
--- /dev/null
+++ b/lib/src/material/page_transitions_theme.dart
@@ -0,0 +1,634 @@
+// Copyright 2014 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.
+
+import 'package:flute/cupertino.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'theme.dart';
+
+// Slides the page upwards and fades it in, starting from 1/4 screen
+// below the top.
+class _FadeUpwardsPageTransition extends StatelessWidget {
+  _FadeUpwardsPageTransition({
+    Key? key,
+    required Animation<double> routeAnimation, // The route's linear 0.0 - 1.0 animation.
+    required this.child,
+  }) : _positionAnimation = routeAnimation.drive(_bottomUpTween.chain(_fastOutSlowInTween)),
+       _opacityAnimation = routeAnimation.drive(_easeInTween),
+       super(key: key);
+
+  // Fractional offset from 1/4 screen below the top to fully on screen.
+  static final Tween<Offset> _bottomUpTween = Tween<Offset>(
+    begin: const Offset(0.0, 0.25),
+    end: Offset.zero,
+  );
+  static final Animatable<double> _fastOutSlowInTween = CurveTween(curve: Curves.fastOutSlowIn);
+  static final Animatable<double> _easeInTween = CurveTween(curve: Curves.easeIn);
+
+  final Animation<Offset> _positionAnimation;
+  final Animation<double> _opacityAnimation;
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    return SlideTransition(
+      position: _positionAnimation,
+      // TODO(ianh): tell the transform to be un-transformed for hit testing
+      child: FadeTransition(
+        opacity: _opacityAnimation,
+        child: child,
+      ),
+    );
+  }
+}
+
+// This transition is intended to match the default for Android P.
+class _OpenUpwardsPageTransition extends StatelessWidget {
+  const _OpenUpwardsPageTransition({
+    Key? key,
+    required this.animation,
+    required this.secondaryAnimation,
+    required this.child,
+  }) : super(key: key);
+
+  // The new page slides upwards just a little as its clip
+  // rectangle exposes the page from bottom to top.
+  static final Tween<Offset> _primaryTranslationTween = Tween<Offset>(
+    begin: const Offset(0.0, 0.05),
+    end: Offset.zero,
+  );
+
+  // The old page slides upwards a little as the new page appears.
+  static final Tween<Offset> _secondaryTranslationTween = Tween<Offset>(
+    begin: Offset.zero,
+    end: const Offset(0.0, -0.025),
+  );
+
+  // The scrim obscures the old page by becoming increasingly opaque.
+  static final Tween<double> _scrimOpacityTween = Tween<double>(
+    begin: 0.0,
+    end: 0.25,
+  );
+
+  // Used by all of the transition animations.
+  static const Curve _transitionCurve = Cubic(0.20, 0.00, 0.00, 1.00);
+
+  final Animation<double> animation;
+  final Animation<double> secondaryAnimation;
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    return LayoutBuilder(
+      builder: (BuildContext context, BoxConstraints constraints) {
+        final Size size = constraints.biggest;
+
+        final CurvedAnimation primaryAnimation = CurvedAnimation(
+          parent: animation,
+          curve: _transitionCurve,
+          reverseCurve: _transitionCurve.flipped,
+        );
+
+        // Gradually expose the new page from bottom to top.
+        final Animation<double> clipAnimation = Tween<double>(
+          begin: 0.0,
+          end: size.height,
+        ).animate(primaryAnimation);
+
+        final Animation<double> opacityAnimation = _scrimOpacityTween.animate(primaryAnimation);
+        final Animation<Offset> primaryTranslationAnimation = _primaryTranslationTween.animate(primaryAnimation);
+
+        final Animation<Offset> secondaryTranslationAnimation = _secondaryTranslationTween.animate(
+          CurvedAnimation(
+            parent: secondaryAnimation,
+            curve: _transitionCurve,
+            reverseCurve: _transitionCurve.flipped,
+          ),
+        );
+
+        return AnimatedBuilder(
+          animation: animation,
+          builder: (BuildContext context, Widget? child) {
+            return Container(
+              color: Colors.black.withOpacity(opacityAnimation.value),
+              alignment: Alignment.bottomLeft,
+              child: ClipRect(
+                child: SizedBox(
+                  height: clipAnimation.value,
+                  child: OverflowBox(
+                    alignment: Alignment.bottomLeft,
+                    maxHeight: size.height,
+                    child: child,
+                  ),
+                ),
+              ),
+            );
+          },
+          child: AnimatedBuilder(
+            animation: secondaryAnimation,
+            child: FractionalTranslation(
+              translation: primaryTranslationAnimation.value,
+              child: child,
+            ),
+            builder: (BuildContext context, Widget? child) {
+              return FractionalTranslation(
+                translation: secondaryTranslationAnimation.value,
+                child: child,
+              );
+            },
+          ),
+        );
+      },
+    );
+  }
+}
+
+// Zooms and fades a new page in, zooming out the previous page. This transition
+// is designed to match the Android 10 activity transition.
+class _ZoomPageTransition extends StatelessWidget {
+  /// Creates a [_ZoomPageTransition].
+  ///
+  /// The [animation] and [secondaryAnimation] argument are required and must
+  /// not be null.
+  const _ZoomPageTransition({
+    Key? key,
+    required this.animation,
+    required this.secondaryAnimation,
+    this.child,
+  }) : assert(animation != null),
+       assert(secondaryAnimation != null),
+       super(key: key);
+
+  // A curve sequence that is similar to the 'fastOutExtraSlowIn' curve used in
+  // the native transition.
+  static final List<TweenSequenceItem<double>> fastOutExtraSlowInTweenSequenceItems = <TweenSequenceItem<double>>[
+    TweenSequenceItem<double>(
+      tween: Tween<double>(begin: 0.0, end: 0.4)
+        .chain(CurveTween(curve: const Cubic(0.05, 0.0, 0.133333, 0.06))),
+      weight: 0.166666,
+    ),
+    TweenSequenceItem<double>(
+      tween: Tween<double>(begin: 0.4, end: 1.0)
+        .chain(CurveTween(curve: const Cubic(0.208333, 0.82, 0.25, 1.0))),
+      weight: 1.0 - 0.166666,
+    ),
+  ];
+  static final TweenSequence<double> _scaleCurveSequence = TweenSequence<double>(fastOutExtraSlowInTweenSequenceItems);
+
+  /// The animation that drives the [child]'s entrance and exit.
+  ///
+  /// See also:
+  ///
+  ///  * [TransitionRoute.animation], which is the value given to this property
+  ///    when the [_ZoomPageTransition] is used as a page transition.
+  final Animation<double> animation;
+
+  /// The animation that transitions [child] when new content is pushed on top
+  /// of it.
+  ///
+  /// See also:
+  ///
+  ///  * [TransitionRoute.secondaryAnimation], which is the value given to this
+  ///    property when the [_ZoomPageTransition] is used as a page transition.
+  final Animation<double> secondaryAnimation;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// This widget will transition in and out as driven by [animation] and
+  /// [secondaryAnimation].
+  final Widget? child;
+
+  @override
+  Widget build(BuildContext context) {
+    return DualTransitionBuilder(
+      animation: animation,
+      forwardBuilder: (
+        BuildContext context,
+        Animation<double> animation,
+        Widget? child,
+      ) {
+        return _ZoomEnterTransition(
+          animation: animation,
+          child: child,
+        );
+      },
+      reverseBuilder: (
+        BuildContext context,
+        Animation<double> animation,
+        Widget? child,
+      ) {
+        return _ZoomExitTransition(
+          animation: animation,
+          reverse: true,
+          child: child,
+        );
+      },
+      child: DualTransitionBuilder(
+        animation: ReverseAnimation(secondaryAnimation),
+        forwardBuilder: (
+          BuildContext context,
+          Animation<double> animation,
+          Widget? child,
+        ) {
+          return _ZoomEnterTransition(
+            animation: animation,
+            reverse: true,
+            child: child,
+          );
+        },
+        reverseBuilder: (
+          BuildContext context,
+          Animation<double> animation,
+          Widget? child,
+        ) {
+          return _ZoomExitTransition(
+            animation: animation,
+            child: child,
+          );
+        },
+        child: child,
+      ),
+    );
+  }
+}
+
+class _ZoomEnterTransition extends StatelessWidget {
+  const _ZoomEnterTransition({
+    Key? key,
+    required this.animation,
+    this.reverse = false,
+    this.child,
+  }) : assert(animation != null),
+       assert(reverse != null),
+       super(key: key);
+
+  final Animation<double> animation;
+  final Widget? child;
+  final bool reverse;
+
+  static final Animatable<double> _fadeInTransition = Tween<double>(
+    begin: 0.0,
+    end: 1.00,
+  ).chain(CurveTween(curve: const Interval(0.125, 0.250)));
+
+  static final Animatable<double> _scaleDownTransition = Tween<double>(
+    begin: 1.10,
+    end: 1.00,
+  ).chain(_ZoomPageTransition._scaleCurveSequence);
+
+  static final Animatable<double> _scaleUpTransition = Tween<double>(
+    begin: 0.85,
+    end: 1.00,
+  ).chain(_ZoomPageTransition._scaleCurveSequence);
+
+  static final Animatable<double?> _scrimOpacityTween = Tween<double?>(
+    begin: 0.0,
+    end: 0.60,
+  ).chain(CurveTween(curve: const Interval(0.2075, 0.4175)));
+
+  @override
+  Widget build(BuildContext context) {
+    double opacity = 0;
+    // The transition's scrim opacity only increases on the forward transition. In the reverse
+    // transition, the opacity should always be 0.0.
+    //
+    // Therefore, we need to only apply the scrim opacity animation when the transition
+    // is running forwards.
+    //
+    // The reason that we check that the animation's status is not `completed` instead
+    // of checking that it is `forward` is that this allows the interrupted reversal of the
+    // forward transition to smoothly fade the scrim away. This prevents a disjointed
+    // removal of the scrim.
+    if (!reverse && animation.status != AnimationStatus.completed) {
+      opacity = _scrimOpacityTween.evaluate(animation)!;
+    }
+
+    final Animation<double> fadeTransition = reverse
+      ? kAlwaysCompleteAnimation
+      : _fadeInTransition.animate(animation);
+
+    final Animation<double> scaleTransition = (reverse
+      ? _scaleDownTransition
+      : _scaleUpTransition
+    ).animate(animation);
+
+    return AnimatedBuilder(
+      animation: animation,
+      builder: (BuildContext context, Widget? child) {
+        return Container(
+          color: Colors.black.withOpacity(opacity),
+          child: child,
+        );
+      },
+      child: FadeTransition(
+        opacity: fadeTransition,
+        child: ScaleTransition(
+          scale: scaleTransition,
+          child: child,
+        ),
+      ),
+    );
+  }
+}
+
+class _ZoomExitTransition extends StatelessWidget {
+  const _ZoomExitTransition({
+    Key? key,
+    required this.animation,
+    this.reverse = false,
+    this.child,
+  }) : assert(animation != null),
+       assert(reverse != null),
+       super(key: key);
+
+  final Animation<double> animation;
+  final bool reverse;
+  final Widget? child;
+
+  static final Animatable<double> _fadeOutTransition = Tween<double>(
+    begin: 1.0,
+    end: 0.0,
+  ).chain(CurveTween(curve: const Interval(0.0825, 0.2075)));
+
+  static final Animatable<double> _scaleUpTransition = Tween<double>(
+    begin: 1.00,
+    end: 1.05,
+  ).chain(_ZoomPageTransition._scaleCurveSequence);
+
+  static final Animatable<double> _scaleDownTransition = Tween<double>(
+    begin: 1.00,
+    end: 0.90,
+  ).chain(_ZoomPageTransition._scaleCurveSequence);
+
+  @override
+  Widget build(BuildContext context) {
+    final Animation<double> fadeTransition = reverse
+      ? _fadeOutTransition.animate(animation)
+      : kAlwaysCompleteAnimation;
+    final Animation<double> scaleTransition = (reverse
+      ? _scaleDownTransition
+      : _scaleUpTransition
+    ).animate(animation);
+
+    return FadeTransition(
+      opacity: fadeTransition,
+      child: ScaleTransition(
+        scale: scaleTransition,
+        child: child,
+      ),
+    );
+  }
+}
+
+/// Used by [PageTransitionsTheme] to define a [MaterialPageRoute] page
+/// transition animation.
+///
+/// Apps can configure the map of builders for [ThemeData.pageTransitionsTheme]
+/// to customize the default [MaterialPageRoute] page transition animation
+/// for different platforms.
+///
+/// See also:
+///
+///  * [FadeUpwardsPageTransitionsBuilder], which defines a default page transition.
+///  * [OpenUpwardsPageTransitionsBuilder], which defines a page transition
+///    that's similar to the one provided by Android P.
+///  * [ZoomPageTransitionsBuilder], which defines a page transition similar
+///    to the one provided in Android 10.
+///  * [CupertinoPageTransitionsBuilder], which defines a horizontal page
+///    transition that matches native iOS page transitions.
+abstract class PageTransitionsBuilder {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const PageTransitionsBuilder();
+
+  /// Wraps the child with one or more transition widgets which define how [route]
+  /// arrives on and leaves the screen.
+  ///
+  /// The [MaterialPageRoute.buildTransitions] method looks up the current
+  /// current [PageTransitionsTheme] with `Theme.of(context).pageTransitionsTheme`
+  /// and delegates to this method with a [PageTransitionsBuilder] based
+  /// on the theme's [ThemeData.platform].
+  Widget buildTransitions<T>(
+    PageRoute<T> route,
+    BuildContext context,
+    Animation<double> animation,
+    Animation<double> secondaryAnimation,
+    Widget child,
+  );
+}
+
+/// Used by [PageTransitionsTheme] to define a default [MaterialPageRoute] page
+/// transition animation.
+///
+/// The default animation fades the new page in while translating it upwards,
+/// starting from about 25% below the top of the screen.
+///
+/// See also:
+///
+///  * [OpenUpwardsPageTransitionsBuilder], which defines a page transition
+///    that's similar to the one provided by Android P.
+///  * [ZoomPageTransitionsBuilder], which defines a page transition similar
+///    to the one provided in Android 10.
+///  * [CupertinoPageTransitionsBuilder], which defines a horizontal page
+///    transition that matches native iOS page transitions.
+class FadeUpwardsPageTransitionsBuilder extends PageTransitionsBuilder {
+  /// Construct a [FadeUpwardsPageTransitionsBuilder].
+  const FadeUpwardsPageTransitionsBuilder();
+
+  @override
+  Widget buildTransitions<T>(
+    PageRoute<T>? route,
+    BuildContext? context,
+    Animation<double> animation,
+    Animation<double>? secondaryAnimation,
+    Widget child,
+  ) {
+    return _FadeUpwardsPageTransition(routeAnimation: animation, child: child);
+  }
+}
+
+/// Used by [PageTransitionsTheme] to define a vertical [MaterialPageRoute] page
+/// transition animation that looks like the default page transition
+/// used on Android P.
+///
+/// See also:
+///
+///  * [FadeUpwardsPageTransitionsBuilder], which defines a default page transition.
+///  * [ZoomPageTransitionsBuilder], which defines a page transition similar
+///    to the one provided in Android 10.
+///  * [CupertinoPageTransitionsBuilder], which defines a horizontal page
+///    transition that matches native iOS page transitions.
+class OpenUpwardsPageTransitionsBuilder extends PageTransitionsBuilder {
+  /// Construct a [OpenUpwardsPageTransitionsBuilder].
+  const OpenUpwardsPageTransitionsBuilder();
+
+  @override
+  Widget buildTransitions<T>(
+    PageRoute<T>? route,
+    BuildContext? context,
+    Animation<double> animation,
+    Animation<double> secondaryAnimation,
+    Widget child,
+  ) {
+    return _OpenUpwardsPageTransition(
+      animation: animation,
+      secondaryAnimation: secondaryAnimation,
+      child: child,
+    );
+  }
+}
+
+/// Used by [PageTransitionsTheme] to define a zooming [MaterialPageRoute] page
+/// transition animation that looks like the default page transition used on
+/// Android 10.
+///
+/// See also:
+///
+///  * [FadeUpwardsPageTransitionsBuilder], which defines a default page transition.
+///  * [OpenUpwardsPageTransitionsBuilder], which defines a page transition
+///    similar to the one provided by Android P.
+///  * [CupertinoPageTransitionsBuilder], which defines a horizontal page
+///    transition that matches native iOS page transitions.
+class ZoomPageTransitionsBuilder extends PageTransitionsBuilder {
+  /// Construct a [ZoomPageTransitionsBuilder].
+  const ZoomPageTransitionsBuilder();
+
+  @override
+  Widget buildTransitions<T>(
+    PageRoute<T>? route,
+    BuildContext? context,
+    Animation<double> animation,
+    Animation<double> secondaryAnimation,
+    Widget? child,
+  ) {
+    return _ZoomPageTransition(
+      animation: animation,
+      secondaryAnimation: secondaryAnimation,
+      child: child,
+    );
+  }
+}
+
+/// Used by [PageTransitionsTheme] to define a horizontal [MaterialPageRoute]
+/// page transition animation that matches native iOS page transitions.
+///
+/// See also:
+///
+///  * [FadeUpwardsPageTransitionsBuilder], which defines a default page transition.
+///  * [OpenUpwardsPageTransitionsBuilder], which defines a page transition
+///    that's similar to the one provided by Android P.
+///  * [ZoomPageTransitionsBuilder], which defines a page transition similar
+///    to the one provided in Android 10.
+class CupertinoPageTransitionsBuilder extends PageTransitionsBuilder {
+  /// Construct a [CupertinoPageTransitionsBuilder].
+  const CupertinoPageTransitionsBuilder();
+
+  @override
+  Widget buildTransitions<T>(
+    PageRoute<T> route,
+    BuildContext context,
+    Animation<double> animation,
+    Animation<double> secondaryAnimation,
+    Widget child,
+  ) {
+    return CupertinoRouteTransitionMixin.buildPageTransitions<T>(route, context, animation, secondaryAnimation, child);
+  }
+}
+
+/// Defines the page transition animations used by [MaterialPageRoute]
+/// for different [TargetPlatform]s.
+///
+/// The [MaterialPageRoute.buildTransitions] method looks up the current
+/// current [PageTransitionsTheme] with `Theme.of(context).pageTransitionsTheme`
+/// and delegates to [buildTransitions].
+///
+/// If a builder with a matching platform is not found, then the
+/// [FadeUpwardsPageTransitionsBuilder] is used.
+///
+/// See also:
+///
+///  * [ThemeData.pageTransitionsTheme], which defines the default page
+///    transitions for the overall theme.
+///  * [FadeUpwardsPageTransitionsBuilder], which defines a default page transition.
+///  * [OpenUpwardsPageTransitionsBuilder], which defines a page transition
+///    that's similar to the one provided by Android P.
+///  * [CupertinoPageTransitionsBuilder], which defines a horizontal page
+///    transition that matches native iOS page transitions.
+@immutable
+class PageTransitionsTheme with Diagnosticable {
+  /// Construct a PageTransitionsTheme.
+  ///
+  /// By default the list of builders is: [FadeUpwardsPageTransitionsBuilder]
+  /// for [TargetPlatform.android], and [CupertinoPageTransitionsBuilder] for
+  /// [TargetPlatform.iOS] and [TargetPlatform.macOS].
+  const PageTransitionsTheme({ Map<TargetPlatform, PageTransitionsBuilder>? builders }) : _builders = builders;
+
+  static const Map<TargetPlatform, PageTransitionsBuilder> _defaultBuilders = <TargetPlatform, PageTransitionsBuilder>{
+    TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
+    TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
+    TargetPlatform.linux: FadeUpwardsPageTransitionsBuilder(),
+    TargetPlatform.macOS: CupertinoPageTransitionsBuilder(),
+    TargetPlatform.windows: FadeUpwardsPageTransitionsBuilder(),
+  };
+
+  /// The [PageTransitionsBuilder]s supported by this theme.
+  Map<TargetPlatform, PageTransitionsBuilder> get builders => _builders ?? _defaultBuilders;
+  final Map<TargetPlatform, PageTransitionsBuilder>? _builders;
+
+  /// Delegates to the builder for the current [ThemeData.platform]
+  /// or [FadeUpwardsPageTransitionsBuilder].
+  ///
+  /// [MaterialPageRoute.buildTransitions] delegates to this method.
+  Widget buildTransitions<T>(
+    PageRoute<T> route,
+    BuildContext context,
+    Animation<double> animation,
+    Animation<double> secondaryAnimation,
+    Widget child,
+  ) {
+    TargetPlatform platform = Theme.of(context).platform;
+
+    if (CupertinoRouteTransitionMixin.isPopGestureInProgress(route))
+      platform = TargetPlatform.iOS;
+
+    final PageTransitionsBuilder matchingBuilder =
+      builders[platform] ?? const FadeUpwardsPageTransitionsBuilder();
+    return matchingBuilder.buildTransitions<T>(route, context, animation, secondaryAnimation, child);
+  }
+
+  // Just used to the builders Map to a list with one PageTransitionsBuilder per platform
+  // for the operator == overload.
+  List<PageTransitionsBuilder?> _all(Map<TargetPlatform, PageTransitionsBuilder> builders) {
+    return TargetPlatform.values.map((TargetPlatform platform) => builders[platform]).toList();
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    if (other is PageTransitionsTheme && identical(builders, other.builders))
+      return true;
+    return other is PageTransitionsTheme
+        && listEquals<PageTransitionsBuilder?>(_all(other.builders), _all(builders));
+  }
+
+  @override
+  int get hashCode => hashList(_all(builders));
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(
+      DiagnosticsProperty<Map<TargetPlatform, PageTransitionsBuilder>>(
+        'builders',
+        builders,
+        defaultValue: PageTransitionsTheme._defaultBuilders,
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/paginated_data_table.dart b/lib/src/material/paginated_data_table.dart
new file mode 100644
index 0000000..2af44e1
--- /dev/null
+++ b/lib/src/material/paginated_data_table.dart
@@ -0,0 +1,518 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/widgets.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/gestures.dart' show DragStartBehavior;
+
+import 'button_bar.dart';
+import 'card.dart';
+import 'constants.dart';
+import 'data_table.dart';
+import 'data_table_source.dart';
+import 'debug.dart';
+import 'dropdown.dart';
+import 'icon_button.dart';
+import 'icons.dart';
+import 'ink_decoration.dart';
+import 'material_localizations.dart';
+import 'progress_indicator.dart';
+import 'theme.dart';
+
+/// A material design data table that shows data using multiple pages.
+///
+/// A paginated data table shows [rowsPerPage] rows of data per page and
+/// provides controls for showing other pages.
+///
+/// Data is read lazily from from a [DataTableSource]. The widget is presented
+/// as a [Card].
+///
+/// See also:
+///
+///  * [DataTable], which is not paginated.
+///  * <https://material.io/go/design-data-tables#data-tables-tables-within-cards>
+class PaginatedDataTable extends StatefulWidget {
+  /// Creates a widget describing a paginated [DataTable] on a [Card].
+  ///
+  /// The [header] should give the card's header, typically a [Text] widget.
+  ///
+  /// The [columns] argument must be a list of as many [DataColumn] objects as
+  /// the table is to have columns, ignoring the leading checkbox column if any.
+  /// The [columns] argument must have a length greater than zero and cannot be
+  /// null.
+  ///
+  /// If the table is sorted, the column that provides the current primary key
+  /// should be specified by index in [sortColumnIndex], 0 meaning the first
+  /// column in [columns], 1 being the next one, and so forth.
+  ///
+  /// The actual sort order can be specified using [sortAscending]; if the sort
+  /// order is ascending, this should be true (the default), otherwise it should
+  /// be false.
+  ///
+  /// The [source] must not be null. The [source] should be a long-lived
+  /// [DataTableSource]. The same source should be provided each time a
+  /// particular [PaginatedDataTable] widget is created; avoid creating a new
+  /// [DataTableSource] with each new instance of the [PaginatedDataTable]
+  /// widget unless the data table really is to now show entirely different
+  /// data from a new source.
+  ///
+  /// The [rowsPerPage] and [availableRowsPerPage] must not be null (they
+  /// both have defaults, though, so don't have to be specified).
+  ///
+  /// Themed by [DataTableTheme]. [DataTableThemeData.decoration] is ignored.
+  /// To modify the border or background color of the [PaginatedDataTable], use
+  /// [CardTheme], since a [Card] wraps the inner [DataTable].
+  PaginatedDataTable({
+    Key? key,
+    this.header,
+    this.actions,
+    required this.columns,
+    this.sortColumnIndex,
+    this.sortAscending = true,
+    this.onSelectAll,
+    this.dataRowHeight = kMinInteractiveDimension,
+    this.headingRowHeight = 56.0,
+    this.horizontalMargin = 24.0,
+    this.columnSpacing = 56.0,
+    this.showCheckboxColumn = true,
+    this.initialFirstRowIndex = 0,
+    this.onPageChanged,
+    this.rowsPerPage = defaultRowsPerPage,
+    this.availableRowsPerPage = const <int>[defaultRowsPerPage, defaultRowsPerPage * 2, defaultRowsPerPage * 5, defaultRowsPerPage * 10],
+    this.onRowsPerPageChanged,
+    this.dragStartBehavior = DragStartBehavior.start,
+    required this.source,
+  }) : assert(actions == null || (actions != null && header != null)),
+       assert(columns != null),
+       assert(dragStartBehavior != null),
+       assert(columns.isNotEmpty),
+       assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)),
+       assert(sortAscending != null),
+       assert(dataRowHeight != null),
+       assert(headingRowHeight != null),
+       assert(horizontalMargin != null),
+       assert(columnSpacing != null),
+       assert(showCheckboxColumn != null),
+       assert(rowsPerPage != null),
+       assert(rowsPerPage > 0),
+       assert(() {
+         if (onRowsPerPageChanged != null)
+           assert(availableRowsPerPage != null && availableRowsPerPage.contains(rowsPerPage));
+         return true;
+       }()),
+       assert(source != null),
+       super(key: key);
+
+  /// The table card's optional header.
+  ///
+  /// This is typically a [Text] widget, but can also be a [Row] of
+  /// [TextButton]s. To show icon buttons at the top end side of the table with
+  /// a header, set the [actions] property.
+  ///
+  /// If items in the table are selectable, then, when the selection is not
+  /// empty, the header is replaced by a count of the selected items. The
+  /// [actions] are still visible when items are selected.
+  final Widget? header;
+
+  /// Icon buttons to show at the top end side of the table. The [header] must
+  /// not be null to show the actions.
+  ///
+  /// Typically, the exact actions included in this list will vary based on
+  /// whether any rows are selected or not.
+  ///
+  /// These should be size 24.0 with default padding (8.0).
+  final List<Widget>? actions;
+
+  /// The configuration and labels for the columns in the table.
+  final List<DataColumn> columns;
+
+  /// The current primary sort key's column.
+  ///
+  /// See [DataTable.sortColumnIndex].
+  final int? sortColumnIndex;
+
+  /// Whether the column mentioned in [sortColumnIndex], if any, is sorted
+  /// in ascending order.
+  ///
+  /// See [DataTable.sortAscending].
+  final bool sortAscending;
+
+  /// Invoked when the user selects or unselects every row, using the
+  /// checkbox in the heading row.
+  ///
+  /// See [DataTable.onSelectAll].
+  final ValueSetter<bool?>? onSelectAll;
+
+  /// The height of each row (excluding the row that contains column headings).
+  ///
+  /// This value is optional and defaults to kMinInteractiveDimension if not
+  /// specified.
+  final double dataRowHeight;
+
+  /// The height of the heading row.
+  ///
+  /// This value is optional and defaults to 56.0 if not specified.
+  final double headingRowHeight;
+
+  /// The horizontal margin between the edges of the table and the content
+  /// in the first and last cells of each row.
+  ///
+  /// When a checkbox is displayed, it is also the margin between the checkbox
+  /// the content in the first data column.
+  ///
+  /// This value defaults to 24.0 to adhere to the Material Design specifications.
+  final double horizontalMargin;
+
+  /// The horizontal margin between the contents of each data column.
+  ///
+  /// This value defaults to 56.0 to adhere to the Material Design specifications.
+  final double columnSpacing;
+
+  /// {@macro flutter.material.dataTable.showCheckboxColumn}
+  final bool showCheckboxColumn;
+
+  /// The index of the first row to display when the widget is first created.
+  final int? initialFirstRowIndex;
+
+  /// Invoked when the user switches to another page.
+  ///
+  /// The value is the index of the first row on the currently displayed page.
+  final ValueChanged<int>? onPageChanged;
+
+  /// The number of rows to show on each page.
+  ///
+  /// See also:
+  ///
+  ///  * [onRowsPerPageChanged]
+  ///  * [defaultRowsPerPage]
+  final int rowsPerPage;
+
+  /// The default value for [rowsPerPage].
+  ///
+  /// Useful when initializing the field that will hold the current
+  /// [rowsPerPage], when implemented [onRowsPerPageChanged].
+  static const int defaultRowsPerPage = 10;
+
+  /// The options to offer for the rowsPerPage.
+  ///
+  /// The current [rowsPerPage] must be a value in this list.
+  ///
+  /// The values in this list should be sorted in ascending order.
+  final List<int> availableRowsPerPage;
+
+  /// Invoked when the user selects a different number of rows per page.
+  ///
+  /// If this is null, then the value given by [rowsPerPage] will be used
+  /// and no affordance will be provided to change the value.
+  final ValueChanged<int?>? onRowsPerPageChanged;
+
+  /// The data source which provides data to show in each row. Must be non-null.
+  ///
+  /// This object should generally have a lifetime longer than the
+  /// [PaginatedDataTable] widget itself; it should be reused each time the
+  /// [PaginatedDataTable] constructor is called.
+  final DataTableSource source;
+
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  @override
+  PaginatedDataTableState createState() => PaginatedDataTableState();
+}
+
+/// Holds the state of a [PaginatedDataTable].
+///
+/// The table can be programmatically paged using the [pageTo] method.
+class PaginatedDataTableState extends State<PaginatedDataTable> {
+  late int _firstRowIndex;
+  late int _rowCount;
+  late bool _rowCountApproximate;
+  int _selectedRowCount = 0;
+  final Map<int, DataRow?> _rows = <int, DataRow?>{};
+
+  @override
+  void initState() {
+    super.initState();
+    _firstRowIndex = PageStorage.of(context)?.readState(context) as int? ?? widget.initialFirstRowIndex ?? 0;
+    widget.source.addListener(_handleDataSourceChanged);
+    _handleDataSourceChanged();
+  }
+
+  @override
+  void didUpdateWidget(PaginatedDataTable oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.source != widget.source) {
+      oldWidget.source.removeListener(_handleDataSourceChanged);
+      widget.source.addListener(_handleDataSourceChanged);
+      _handleDataSourceChanged();
+    }
+  }
+
+  @override
+  void dispose() {
+    widget.source.removeListener(_handleDataSourceChanged);
+    super.dispose();
+  }
+
+  void _handleDataSourceChanged() {
+    setState(() {
+      _rowCount = widget.source.rowCount;
+      _rowCountApproximate = widget.source.isRowCountApproximate;
+      _selectedRowCount = widget.source.selectedRowCount;
+      _rows.clear();
+    });
+  }
+
+  /// Ensures that the given row is visible.
+  void pageTo(int rowIndex) {
+    final int oldFirstRowIndex = _firstRowIndex;
+    setState(() {
+      final int rowsPerPage = widget.rowsPerPage;
+      _firstRowIndex = (rowIndex ~/ rowsPerPage) * rowsPerPage;
+    });
+    if ((widget.onPageChanged != null) &&
+        (oldFirstRowIndex != _firstRowIndex))
+      widget.onPageChanged!(_firstRowIndex);
+  }
+
+  DataRow _getBlankRowFor(int index) {
+    return DataRow.byIndex(
+      index: index,
+      cells: widget.columns.map<DataCell>((DataColumn column) => DataCell.empty).toList(),
+    );
+  }
+
+  DataRow _getProgressIndicatorRowFor(int index) {
+    bool haveProgressIndicator = false;
+    final List<DataCell> cells = widget.columns.map<DataCell>((DataColumn column) {
+      if (!column.numeric) {
+        haveProgressIndicator = true;
+        return const DataCell(CircularProgressIndicator());
+      }
+      return DataCell.empty;
+    }).toList();
+    if (!haveProgressIndicator) {
+      haveProgressIndicator = true;
+      cells[0] = const DataCell(CircularProgressIndicator());
+    }
+    return DataRow.byIndex(
+      index: index,
+      cells: cells,
+    );
+  }
+
+  List<DataRow> _getRows(int firstRowIndex, int rowsPerPage) {
+    final List<DataRow> result = <DataRow>[];
+    final int nextPageFirstRowIndex = firstRowIndex + rowsPerPage;
+    bool haveProgressIndicator = false;
+    for (int index = firstRowIndex; index < nextPageFirstRowIndex; index += 1) {
+      DataRow? row;
+      if (index < _rowCount || _rowCountApproximate) {
+        row = _rows.putIfAbsent(index, () => widget.source.getRow(index));
+        if (row == null && !haveProgressIndicator) {
+          row ??= _getProgressIndicatorRowFor(index);
+          haveProgressIndicator = true;
+        }
+      }
+      row ??= _getBlankRowFor(index);
+      result.add(row);
+    }
+    return result;
+  }
+
+  void _handlePrevious() {
+    pageTo(math.max(_firstRowIndex - widget.rowsPerPage, 0));
+  }
+
+  void _handleNext() {
+    pageTo(_firstRowIndex + widget.rowsPerPage);
+  }
+
+  final GlobalKey _tableKey = GlobalKey();
+
+  @override
+  Widget build(BuildContext context) {
+    // TODO(ianh): This whole build function doesn't handle RTL yet.
+    assert(debugCheckHasMaterialLocalizations(context));
+    final ThemeData themeData = Theme.of(context);
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    // HEADER
+    final List<Widget> headerWidgets = <Widget>[];
+    double startPadding = 24.0;
+    if (_selectedRowCount == 0 && widget.header != null) {
+      headerWidgets.add(Expanded(child: widget.header!));
+      if (widget.header is ButtonBar) {
+        // We adjust the padding when a button bar is present, because the
+        // ButtonBar introduces 2 pixels of outside padding, plus 2 pixels
+        // around each button on each side, and the button itself will have 8
+        // pixels internally on each side, yet we want the left edge of the
+        // inside of the button to line up with the 24.0 left inset.
+        startPadding = 12.0;
+      }
+    } else if (widget.header != null) {
+      headerWidgets.add(Expanded(
+        child: Text(localizations.selectedRowCountTitle(_selectedRowCount)),
+      ));
+    }
+    if (widget.actions != null) {
+      headerWidgets.addAll(
+        widget.actions!.map<Widget>((Widget action) {
+          return Padding(
+            // 8.0 is the default padding of an icon button
+            padding: const EdgeInsetsDirectional.only(start: 24.0 - 8.0 * 2.0),
+            child: action,
+          );
+        }).toList()
+      );
+    }
+
+    // FOOTER
+    final TextStyle? footerTextStyle = themeData.textTheme.caption;
+    final List<Widget> footerWidgets = <Widget>[];
+    if (widget.onRowsPerPageChanged != null) {
+      final List<Widget> availableRowsPerPage = widget.availableRowsPerPage
+        .where((int value) => value <= _rowCount || value == widget.rowsPerPage)
+        .map<DropdownMenuItem<int>>((int value) {
+          return DropdownMenuItem<int>(
+            value: value,
+            child: Text('$value'),
+          );
+        })
+        .toList();
+      footerWidgets.addAll(<Widget>[
+        Container(width: 14.0), // to match trailing padding in case we overflow and end up scrolling
+        Text(localizations.rowsPerPageTitle),
+        ConstrainedBox(
+          constraints: const BoxConstraints(minWidth: 64.0), // 40.0 for the text, 24.0 for the icon
+          child: Align(
+            alignment: AlignmentDirectional.centerEnd,
+            child: DropdownButtonHideUnderline(
+              child: DropdownButton<int>(
+                items: availableRowsPerPage.cast<DropdownMenuItem<int>>(),
+                value: widget.rowsPerPage,
+                onChanged: widget.onRowsPerPageChanged,
+                style: footerTextStyle,
+                iconSize: 24.0,
+              ),
+            ),
+          ),
+        ),
+      ]);
+    }
+    footerWidgets.addAll(<Widget>[
+      Container(width: 32.0),
+      Text(
+        localizations.pageRowsInfoTitle(
+          _firstRowIndex + 1,
+          _firstRowIndex + widget.rowsPerPage,
+          _rowCount,
+          _rowCountApproximate,
+        ),
+      ),
+      Container(width: 32.0),
+      IconButton(
+        icon: const Icon(Icons.chevron_left),
+        padding: EdgeInsets.zero,
+        tooltip: localizations.previousPageTooltip,
+        onPressed: _firstRowIndex <= 0 ? null : _handlePrevious,
+      ),
+      Container(width: 24.0),
+      IconButton(
+        icon: const Icon(Icons.chevron_right),
+        padding: EdgeInsets.zero,
+        tooltip: localizations.nextPageTooltip,
+        onPressed: (!_rowCountApproximate && (_firstRowIndex + widget.rowsPerPage >= _rowCount)) ? null : _handleNext,
+      ),
+      Container(width: 14.0),
+    ]);
+
+    // CARD
+    return LayoutBuilder(
+      builder: (BuildContext context, BoxConstraints constraints) {
+        return Card(
+          semanticContainer: false,
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.stretch,
+            children: <Widget>[
+              if (headerWidgets.isNotEmpty)
+                Semantics(
+                  container: true,
+                  child: DefaultTextStyle(
+                    // These typographic styles aren't quite the regular ones. We pick the closest ones from the regular
+                    // list and then tweak them appropriately.
+                    // See https://material.io/design/components/data-tables.html#tables-within-cards
+                    style: _selectedRowCount > 0 ? themeData.textTheme.subtitle1!.copyWith(color: themeData.accentColor)
+                                                 : themeData.textTheme.headline6!.copyWith(fontWeight: FontWeight.w400),
+                    child: IconTheme.merge(
+                      data: const IconThemeData(
+                        opacity: 0.54
+                      ),
+                      child: Ink(
+                        height: 64.0,
+                        color: _selectedRowCount > 0 ? themeData.secondaryHeaderColor : null,
+                        child: Padding(
+                          padding: EdgeInsetsDirectional.only(start: startPadding, end: 14.0),
+                          child: Row(
+                            mainAxisAlignment: MainAxisAlignment.end,
+                            children: headerWidgets,
+                          ),
+                        ),
+                      ),
+                    ),
+                  ),
+                ),
+              SingleChildScrollView(
+                scrollDirection: Axis.horizontal,
+                dragStartBehavior: widget.dragStartBehavior,
+                child: ConstrainedBox(
+                  constraints: BoxConstraints(minWidth: constraints.minWidth),
+                  child: DataTable(
+                    key: _tableKey,
+                    columns: widget.columns,
+                    sortColumnIndex: widget.sortColumnIndex,
+                    sortAscending: widget.sortAscending,
+                    onSelectAll: widget.onSelectAll,
+                    // Make sure no decoration is set on the DataTable
+                    // from the theme, as its already wrapped in a Card.
+                    decoration: const BoxDecoration(),
+                    dataRowHeight: widget.dataRowHeight,
+                    headingRowHeight: widget.headingRowHeight,
+                    horizontalMargin: widget.horizontalMargin,
+                    columnSpacing: widget.columnSpacing,
+                    showCheckboxColumn: widget.showCheckboxColumn,
+                    showBottomBorder: true,
+                    rows: _getRows(_firstRowIndex, widget.rowsPerPage),
+                  ),
+                ),
+              ),
+              DefaultTextStyle(
+                style: footerTextStyle!,
+                child: IconTheme.merge(
+                  data: const IconThemeData(
+                    opacity: 0.54
+                  ),
+                  child: Container(
+                    // TODO(bkonyi): this won't handle text zoom correctly,
+                    //  https://github.com/flutter/flutter/issues/48522
+                    height: 56.0,
+                    child: SingleChildScrollView(
+                      dragStartBehavior: widget.dragStartBehavior,
+                      scrollDirection: Axis.horizontal,
+                      reverse: true,
+                      child: Row(
+                        children: footerWidgets,
+                      ),
+                    ),
+                  ),
+                ),
+              ),
+            ],
+          ),
+        );
+      },
+    );
+  }
+}
diff --git a/lib/src/material/popup_menu.dart b/lib/src/material/popup_menu.dart
new file mode 100644
index 0000000..f95b043
--- /dev/null
+++ b/lib/src/material/popup_menu.dart
@@ -0,0 +1,1142 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'constants.dart';
+import 'debug.dart';
+import 'divider.dart';
+import 'icon_button.dart';
+import 'icons.dart';
+import 'ink_well.dart';
+import 'list_tile.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'material_state.dart';
+import 'popup_menu_theme.dart';
+import 'theme.dart';
+import 'tooltip.dart';
+
+// Examples can assume:
+// enum Commands { heroAndScholar, hurricaneCame }
+// dynamic _heroAndScholar;
+// dynamic _selection;
+// late BuildContext context;
+// void setState(VoidCallback fn) { }
+
+const Duration _kMenuDuration = Duration(milliseconds: 300);
+const double _kMenuCloseIntervalEnd = 2.0 / 3.0;
+const double _kMenuHorizontalPadding = 16.0;
+const double _kMenuDividerHeight = 16.0;
+const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
+const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
+const double _kMenuVerticalPadding = 8.0;
+const double _kMenuWidthStep = 56.0;
+const double _kMenuScreenPadding = 8.0;
+
+/// A base class for entries in a material design popup menu.
+///
+/// The popup menu widget uses this interface to interact with the menu items.
+/// To show a popup menu, use the [showMenu] function. To create a button that
+/// shows a popup menu, consider using [PopupMenuButton].
+///
+/// The type `T` is the type of the value(s) the entry represents. All the
+/// entries in a given menu must represent values with consistent types.
+///
+/// A [PopupMenuEntry] may represent multiple values, for example a row with
+/// several icons, or a single entry, for example a menu item with an icon (see
+/// [PopupMenuItem]), or no value at all (for example, [PopupMenuDivider]).
+///
+/// See also:
+///
+///  * [PopupMenuItem], a popup menu entry for a single value.
+///  * [PopupMenuDivider], a popup menu entry that is just a horizontal line.
+///  * [CheckedPopupMenuItem], a popup menu item with a checkmark.
+///  * [showMenu], a method to dynamically show a popup menu at a given location.
+///  * [PopupMenuButton], an [IconButton] that automatically shows a menu when
+///    it is tapped.
+abstract class PopupMenuEntry<T> extends StatefulWidget {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const PopupMenuEntry({ Key? key }) : super(key: key);
+
+  /// The amount of vertical space occupied by this entry.
+  ///
+  /// This value is used at the time the [showMenu] method is called, if the
+  /// `initialValue` argument is provided, to determine the position of this
+  /// entry when aligning the selected entry over the given `position`. It is
+  /// otherwise ignored.
+  double get height;
+
+  /// Whether this entry represents a particular value.
+  ///
+  /// This method is used by [showMenu], when it is called, to align the entry
+  /// representing the `initialValue`, if any, to the given `position`, and then
+  /// later is called on each entry to determine if it should be highlighted (if
+  /// the method returns true, the entry will have its background color set to
+  /// the ambient [ThemeData.highlightColor]). If `initialValue` is null, then
+  /// this method is not called.
+  ///
+  /// If the [PopupMenuEntry] represents a single value, this should return true
+  /// if the argument matches that value. If it represents multiple values, it
+  /// should return true if the argument matches any of them.
+  bool represents(T? value);
+}
+
+/// A horizontal divider in a material design popup menu.
+///
+/// This widget adapts the [Divider] for use in popup menus.
+///
+/// See also:
+///
+///  * [PopupMenuItem], for the kinds of items that this widget divides.
+///  * [showMenu], a method to dynamically show a popup menu at a given location.
+///  * [PopupMenuButton], an [IconButton] that automatically shows a menu when
+///    it is tapped.
+class PopupMenuDivider extends PopupMenuEntry<Never> {
+  /// Creates a horizontal divider for a popup menu.
+  ///
+  /// By default, the divider has a height of 16 logical pixels.
+  const PopupMenuDivider({ Key? key, this.height = _kMenuDividerHeight }) : super(key: key);
+
+  /// The height of the divider entry.
+  ///
+  /// Defaults to 16 pixels.
+  @override
+  final double height;
+
+  @override
+  bool represents(void value) => false;
+
+  @override
+  _PopupMenuDividerState createState() => _PopupMenuDividerState();
+}
+
+class _PopupMenuDividerState extends State<PopupMenuDivider> {
+  @override
+  Widget build(BuildContext context) => Divider(height: widget.height);
+}
+
+// This widget only exists to enable _PopupMenuRoute to save the sizes of
+// each menu item. The sizes are used by _PopupMenuRouteLayout to compute the
+// y coordinate of the menu's origin so that the center of selected menu
+// item lines up with the center of its PopupMenuButton.
+class _MenuItem extends SingleChildRenderObjectWidget {
+  const _MenuItem({
+    Key? key,
+    required this.onLayout,
+    required Widget? child,
+  }) : assert(onLayout != null), super(key: key, child: child);
+
+  final ValueChanged<Size> onLayout;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _RenderMenuItem(onLayout);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, covariant _RenderMenuItem renderObject) {
+    renderObject.onLayout = onLayout;
+  }
+}
+
+class _RenderMenuItem extends RenderShiftedBox {
+  _RenderMenuItem(this.onLayout, [RenderBox? child]) : assert(onLayout != null), super(child);
+
+  ValueChanged<Size> onLayout;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (child == null) {
+      return Size.zero;
+    }
+    return child!.getDryLayout(constraints);
+  }
+
+  @override
+  void performLayout() {
+    if (child == null) {
+      size = Size.zero;
+    } else {
+      child!.layout(constraints, parentUsesSize: true);
+      size = constraints.constrain(child!.size);
+      final BoxParentData childParentData = child!.parentData! as BoxParentData;
+      childParentData.offset = Offset.zero;
+    }
+    onLayout(size);
+  }
+}
+
+/// An item in a material design popup menu.
+///
+/// To show a popup menu, use the [showMenu] function. To create a button that
+/// shows a popup menu, consider using [PopupMenuButton].
+///
+/// To show a checkmark next to a popup menu item, consider using
+/// [CheckedPopupMenuItem].
+///
+/// Typically the [child] of a [PopupMenuItem] is a [Text] widget. More
+/// elaborate menus with icons can use a [ListTile]. By default, a
+/// [PopupMenuItem] is [kMinInteractiveDimension] pixels high. If you use a widget
+/// with a different height, it must be specified in the [height] property.
+///
+/// {@tool snippet}
+///
+/// Here, a [Text] widget is used with a popup menu item. The `WhyFarther` type
+/// is an enum, not shown here.
+///
+/// ```dart
+/// const PopupMenuItem<WhyFarther>(
+///   value: WhyFarther.harder,
+///   child: Text('Working a lot harder'),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See the example at [PopupMenuButton] for how this example could be used in a
+/// complete menu, and see the example at [CheckedPopupMenuItem] for one way to
+/// keep the text of [PopupMenuItem]s that use [Text] widgets in their [child]
+/// slot aligned with the text of [CheckedPopupMenuItem]s or of [PopupMenuItem]
+/// that use a [ListTile] in their [child] slot.
+///
+/// See also:
+///
+///  * [PopupMenuDivider], which can be used to divide items from each other.
+///  * [CheckedPopupMenuItem], a variant of [PopupMenuItem] with a checkmark.
+///  * [showMenu], a method to dynamically show a popup menu at a given location.
+///  * [PopupMenuButton], an [IconButton] that automatically shows a menu when
+///    it is tapped.
+class PopupMenuItem<T> extends PopupMenuEntry<T> {
+  /// Creates an item for a popup menu.
+  ///
+  /// By default, the item is [enabled].
+  ///
+  /// The `enabled` and `height` arguments must not be null.
+  const PopupMenuItem({
+    Key? key,
+    this.value,
+    this.enabled = true,
+    this.height = kMinInteractiveDimension,
+    this.textStyle,
+    this.mouseCursor,
+    required this.child,
+  }) : assert(enabled != null),
+       assert(height != null),
+       super(key: key);
+
+  /// The value that will be returned by [showMenu] if this entry is selected.
+  final T? value;
+
+  /// Whether the user is permitted to select this item.
+  ///
+  /// Defaults to true. If this is false, then the item will not react to
+  /// touches.
+  final bool enabled;
+
+  /// The minimum height of the menu item.
+  ///
+  /// Defaults to [kMinInteractiveDimension] pixels.
+  @override
+  final double height;
+
+  /// The text style of the popup menu item.
+  ///
+  /// If this property is null, then [PopupMenuThemeData.textStyle] is used.
+  /// If [PopupMenuThemeData.textStyle] is also null, then [TextTheme.subtitle1]
+  /// of [ThemeData.textTheme] is used.
+  final TextStyle? textStyle;
+
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]:
+  ///
+  ///  * [MaterialState.disabled].
+  ///
+  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
+  final MouseCursor? mouseCursor;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// Typically a single-line [ListTile] (for menus with icons) or a [Text]. An
+  /// appropriate [DefaultTextStyle] is put in scope for the child. In either
+  /// case, the text should be short enough that it won't wrap.
+  final Widget? child;
+
+  @override
+  bool represents(T? value) => value == this.value;
+
+  @override
+  PopupMenuItemState<T, PopupMenuItem<T>> createState() => PopupMenuItemState<T, PopupMenuItem<T>>();
+}
+
+/// The [State] for [PopupMenuItem] subclasses.
+///
+/// By default this implements the basic styling and layout of Material Design
+/// popup menu items.
+///
+/// The [buildChild] method can be overridden to adjust exactly what gets placed
+/// in the menu. By default it returns [PopupMenuItem.child].
+///
+/// The [handleTap] method can be overridden to adjust exactly what happens when
+/// the item is tapped. By default, it uses [Navigator.pop] to return the
+/// [PopupMenuItem.value] from the menu route.
+///
+/// This class takes two type arguments. The second, `W`, is the exact type of
+/// the [Widget] that is using this [State]. It must be a subclass of
+/// [PopupMenuItem]. The first, `T`, must match the type argument of that widget
+/// class, and is the type of values returned from this menu.
+class PopupMenuItemState<T, W extends PopupMenuItem<T>> extends State<W> {
+  /// The menu item contents.
+  ///
+  /// Used by the [build] method.
+  ///
+  /// By default, this returns [PopupMenuItem.child]. Override this to put
+  /// something else in the menu entry.
+  @protected
+  Widget? buildChild() => widget.child;
+
+  /// The handler for when the user selects the menu item.
+  ///
+  /// Used by the [InkWell] inserted by the [build] method.
+  ///
+  /// By default, uses [Navigator.pop] to return the [PopupMenuItem.value] from
+  /// the menu route.
+  @protected
+  void handleTap() {
+    Navigator.pop<T>(context, widget.value);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
+    TextStyle style = widget.textStyle ?? popupMenuTheme.textStyle ?? theme.textTheme.subtitle1!;
+
+    if (!widget.enabled)
+      style = style.copyWith(color: theme.disabledColor);
+
+    Widget item = AnimatedDefaultTextStyle(
+      style: style,
+      duration: kThemeChangeDuration,
+      child: Container(
+        alignment: AlignmentDirectional.centerStart,
+        constraints: BoxConstraints(minHeight: widget.height),
+        padding: const EdgeInsets.symmetric(horizontal: _kMenuHorizontalPadding),
+        child: buildChild(),
+      ),
+    );
+
+    if (!widget.enabled) {
+      final bool isDark = theme.brightness == Brightness.dark;
+      item = IconTheme.merge(
+        data: IconThemeData(opacity: isDark ? 0.5 : 0.38),
+        child: item,
+      );
+    }
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
+      widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
+      <MaterialState>{
+        if (!widget.enabled) MaterialState.disabled,
+      },
+    );
+
+    return MergeSemantics(
+      child: Semantics(
+        enabled: widget.enabled,
+        button: true,
+        child: InkWell(
+          onTap: widget.enabled ? handleTap : null,
+          canRequestFocus: widget.enabled,
+          mouseCursor: effectiveMouseCursor,
+          child: item,
+        ),
+      )
+    );
+  }
+}
+
+/// An item with a checkmark in a material design popup menu.
+///
+/// To show a popup menu, use the [showMenu] function. To create a button that
+/// shows a popup menu, consider using [PopupMenuButton].
+///
+/// A [CheckedPopupMenuItem] is kMinInteractiveDimension pixels high, which
+/// matches the default minimum height of a [PopupMenuItem]. The horizontal
+/// layout uses [ListTile]; the checkmark is an [Icons.done] icon, shown in the
+/// [ListTile.leading] position.
+///
+/// {@tool snippet}
+///
+/// Suppose a `Commands` enum exists that lists the possible commands from a
+/// particular popup menu, including `Commands.heroAndScholar` and
+/// `Commands.hurricaneCame`, and further suppose that there is a
+/// `_heroAndScholar` member field which is a boolean. The example below shows a
+/// menu with one menu item with a checkmark that can toggle the boolean, and
+/// one menu item without a checkmark for selecting the second option. (It also
+/// shows a divider placed between the two menu items.)
+///
+/// ```dart
+/// PopupMenuButton<Commands>(
+///   onSelected: (Commands result) {
+///     switch (result) {
+///       case Commands.heroAndScholar:
+///         setState(() { _heroAndScholar = !_heroAndScholar; });
+///         break;
+///       case Commands.hurricaneCame:
+///         // ...handle hurricane option
+///         break;
+///       // ...other items handled here
+///     }
+///   },
+///   itemBuilder: (BuildContext context) => <PopupMenuEntry<Commands>>[
+///     CheckedPopupMenuItem<Commands>(
+///       checked: _heroAndScholar,
+///       value: Commands.heroAndScholar,
+///       child: const Text('Hero and scholar'),
+///     ),
+///     const PopupMenuDivider(),
+///     const PopupMenuItem<Commands>(
+///       value: Commands.hurricaneCame,
+///       child: ListTile(leading: Icon(null), title: Text('Bring hurricane')),
+///     ),
+///     // ...other items listed here
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// In particular, observe how the second menu item uses a [ListTile] with a
+/// blank [Icon] in the [ListTile.leading] position to get the same alignment as
+/// the item with the checkmark.
+///
+/// See also:
+///
+///  * [PopupMenuItem], a popup menu entry for picking a command (as opposed to
+///    toggling a value).
+///  * [PopupMenuDivider], a popup menu entry that is just a horizontal line.
+///  * [showMenu], a method to dynamically show a popup menu at a given location.
+///  * [PopupMenuButton], an [IconButton] that automatically shows a menu when
+///    it is tapped.
+class CheckedPopupMenuItem<T> extends PopupMenuItem<T> {
+  /// Creates a popup menu item with a checkmark.
+  ///
+  /// By default, the menu item is [enabled] but unchecked. To mark the item as
+  /// checked, set [checked] to true.
+  ///
+  /// The `checked` and `enabled` arguments must not be null.
+  const CheckedPopupMenuItem({
+    Key? key,
+    T? value,
+    this.checked = false,
+    bool enabled = true,
+    Widget? child,
+  }) : assert(checked != null),
+       super(
+    key: key,
+    value: value,
+    enabled: enabled,
+    child: child,
+  );
+
+  /// Whether to display a checkmark next to the menu item.
+  ///
+  /// Defaults to false.
+  ///
+  /// When true, an [Icons.done] checkmark is displayed.
+  ///
+  /// When this popup menu item is selected, the checkmark will fade in or out
+  /// as appropriate to represent the implied new state.
+  final bool checked;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// Typically a [Text]. An appropriate [DefaultTextStyle] is put in scope for
+  /// the child. The text should be short enough that it won't wrap.
+  ///
+  /// This widget is placed in the [ListTile.title] slot of a [ListTile] whose
+  /// [ListTile.leading] slot is an [Icons.done] icon.
+  @override
+  Widget? get child => super.child;
+
+  @override
+  _CheckedPopupMenuItemState<T> createState() => _CheckedPopupMenuItemState<T>();
+}
+
+class _CheckedPopupMenuItemState<T> extends PopupMenuItemState<T, CheckedPopupMenuItem<T>> with SingleTickerProviderStateMixin {
+  static const Duration _fadeDuration = Duration(milliseconds: 150);
+  late AnimationController _controller;
+  Animation<double> get _opacity => _controller.view;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(duration: _fadeDuration, vsync: this)
+      ..value = widget.checked ? 1.0 : 0.0
+      ..addListener(() => setState(() { /* animation changed */ }));
+  }
+
+  @override
+  void handleTap() {
+    // This fades the checkmark in or out when tapped.
+    if (widget.checked)
+      _controller.reverse();
+    else
+      _controller.forward();
+    super.handleTap();
+  }
+
+  @override
+  Widget buildChild() {
+    return ListTile(
+      enabled: widget.enabled,
+      leading: FadeTransition(
+        opacity: _opacity,
+        child: Icon(_controller.isDismissed ? null : Icons.done),
+      ),
+      title: widget.child,
+    );
+  }
+}
+
+class _PopupMenu<T> extends StatelessWidget {
+  const _PopupMenu({
+    Key? key,
+    required this.route,
+    required this.semanticLabel,
+  }) : super(key: key);
+
+  final _PopupMenuRoute<T> route;
+  final String? semanticLabel;
+
+  @override
+  Widget build(BuildContext context) {
+    final double unit = 1.0 / (route.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
+    final List<Widget> children = <Widget>[];
+    final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
+
+    for (int i = 0; i < route.items.length; i += 1) {
+      final double start = (i + 1) * unit;
+      final double end = (start + 1.5 * unit).clamp(0.0, 1.0);
+      final CurvedAnimation opacity = CurvedAnimation(
+        parent: route.animation!,
+        curve: Interval(start, end),
+      );
+      Widget item = route.items[i];
+      if (route.initialValue != null && route.items[i].represents(route.initialValue)) {
+        item = Container(
+          color: Theme.of(context).highlightColor,
+          child: item,
+        );
+      }
+      children.add(
+        _MenuItem(
+          onLayout: (Size size) {
+            route.itemSizes[i] = size;
+          },
+          child: FadeTransition(
+            opacity: opacity,
+            child: item,
+          ),
+        ),
+      );
+    }
+
+    final CurveTween opacity = CurveTween(curve: const Interval(0.0, 1.0 / 3.0));
+    final CurveTween width = CurveTween(curve: Interval(0.0, unit));
+    final CurveTween height = CurveTween(curve: Interval(0.0, unit * route.items.length));
+
+    final Widget child = ConstrainedBox(
+      constraints: const BoxConstraints(
+        minWidth: _kMenuMinWidth,
+        maxWidth: _kMenuMaxWidth,
+      ),
+      child: IntrinsicWidth(
+        stepWidth: _kMenuWidthStep,
+        child: Semantics(
+          scopesRoute: true,
+          namesRoute: true,
+          explicitChildNodes: true,
+          label: semanticLabel,
+          child: SingleChildScrollView(
+            padding: const EdgeInsets.symmetric(
+              vertical: _kMenuVerticalPadding
+            ),
+            child: ListBody(children: children),
+          ),
+        ),
+      ),
+    );
+
+    return AnimatedBuilder(
+      animation: route.animation!,
+      builder: (BuildContext context, Widget? child) {
+        return Opacity(
+          opacity: opacity.evaluate(route.animation!),
+          child: Material(
+            shape: route.shape ?? popupMenuTheme.shape,
+            color: route.color ?? popupMenuTheme.color,
+            type: MaterialType.card,
+            elevation: route.elevation ?? popupMenuTheme.elevation ?? 8.0,
+            child: Align(
+              alignment: AlignmentDirectional.topEnd,
+              widthFactor: width.evaluate(route.animation!),
+              heightFactor: height.evaluate(route.animation!),
+              child: child,
+            ),
+          ),
+        );
+      },
+      child: child,
+    );
+  }
+}
+
+// Positioning of the menu on the screen.
+class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
+  _PopupMenuRouteLayout(this.position, this.itemSizes, this.selectedItemIndex, this.textDirection);
+
+  // Rectangle of underlying button, relative to the overlay's dimensions.
+  final RelativeRect position;
+
+  // The sizes of each item are computed when the menu is laid out, and before
+  // the route is laid out.
+  List<Size?> itemSizes;
+
+  // The index of the selected item, or null if PopupMenuButton.initialValue
+  // was not specified.
+  final int? selectedItemIndex;
+
+  // Whether to prefer going to the left or to the right.
+  final TextDirection textDirection;
+
+  // We put the child wherever position specifies, so long as it will fit within
+  // the specified parent size padded (inset) by 8. If necessary, we adjust the
+  // child's position so that it fits.
+
+  @override
+  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
+    // The menu can be at most the size of the overlay minus 8.0 pixels in each
+    // direction.
+    return BoxConstraints.loose(
+      constraints.biggest - const Offset(_kMenuScreenPadding * 2.0, _kMenuScreenPadding * 2.0) as Size,
+    );
+  }
+
+  @override
+  Offset getPositionForChild(Size size, Size childSize) {
+    // size: The size of the overlay.
+    // childSize: The size of the menu, when fully open, as determined by
+    // getConstraintsForChild.
+
+    // Find the ideal vertical position.
+    double y = position.top;
+    if (selectedItemIndex != null && itemSizes != null) {
+      double selectedItemOffset = _kMenuVerticalPadding;
+      for (int index = 0; index < selectedItemIndex!; index += 1)
+        selectedItemOffset += itemSizes[index]!.height;
+      selectedItemOffset += itemSizes[selectedItemIndex!]!.height / 2;
+      y = position.top + (size.height - position.top - position.bottom) / 2.0 - selectedItemOffset;
+    }
+
+    // Find the ideal horizontal position.
+    double x;
+    if (position.left > position.right) {
+      // Menu button is closer to the right edge, so grow to the left, aligned to the right edge.
+      x = size.width - position.right - childSize.width;
+    } else if (position.left < position.right) {
+      // Menu button is closer to the left edge, so grow to the right, aligned to the left edge.
+      x = position.left;
+    } else {
+      // Menu button is equidistant from both edges, so grow in reading direction.
+      assert(textDirection != null);
+      switch (textDirection) {
+        case TextDirection.rtl:
+          x = size.width - position.right - childSize.width;
+          break;
+        case TextDirection.ltr:
+          x = position.left;
+          break;
+      }
+    }
+
+    // Avoid going outside an area defined as the rectangle 8.0 pixels from the
+    // edge of the screen in every direction.
+    if (x < _kMenuScreenPadding)
+      x = _kMenuScreenPadding;
+    else if (x + childSize.width > size.width - _kMenuScreenPadding)
+      x = size.width - childSize.width - _kMenuScreenPadding;
+    if (y < _kMenuScreenPadding)
+      y = _kMenuScreenPadding;
+    else if (y + childSize.height > size.height - _kMenuScreenPadding)
+      y = size.height - childSize.height - _kMenuScreenPadding;
+    return Offset(x, y);
+  }
+
+  @override
+  bool shouldRelayout(_PopupMenuRouteLayout oldDelegate) {
+    // If called when the old and new itemSizes have been initialized then
+    // we expect them to have the same length because there's no practical
+    // way to change length of the items list once the menu has been shown.
+    assert(itemSizes.length == oldDelegate.itemSizes.length);
+
+    return position != oldDelegate.position
+        || selectedItemIndex != oldDelegate.selectedItemIndex
+        || textDirection != oldDelegate.textDirection
+        || !listEquals(itemSizes, oldDelegate.itemSizes);
+  }
+}
+
+class _PopupMenuRoute<T> extends PopupRoute<T> {
+  _PopupMenuRoute({
+    required this.position,
+    required this.items,
+    this.initialValue,
+    this.elevation,
+    required this.barrierLabel,
+    this.semanticLabel,
+    this.shape,
+    this.color,
+    required this.capturedThemes,
+  }) : itemSizes = List<Size?>.filled(items.length, null);
+
+  final RelativeRect position;
+  final List<PopupMenuEntry<T>> items;
+  final List<Size?> itemSizes;
+  final T? initialValue;
+  final double? elevation;
+  final String? semanticLabel;
+  final ShapeBorder? shape;
+  final Color? color;
+  final CapturedThemes capturedThemes;
+
+  @override
+  Animation<double> createAnimation() {
+    return CurvedAnimation(
+      parent: super.createAnimation(),
+      curve: Curves.linear,
+      reverseCurve: const Interval(0.0, _kMenuCloseIntervalEnd),
+    );
+  }
+
+  @override
+  Duration get transitionDuration => _kMenuDuration;
+
+  @override
+  bool get barrierDismissible => true;
+
+  @override
+  Color? get barrierColor => null;
+
+  @override
+  final String barrierLabel;
+
+  @override
+  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
+
+    int? selectedItemIndex;
+    if (initialValue != null) {
+      for (int index = 0; selectedItemIndex == null && index < items.length; index += 1) {
+        if (items[index].represents(initialValue))
+          selectedItemIndex = index;
+      }
+    }
+
+    final Widget menu = _PopupMenu<T>(route: this, semanticLabel: semanticLabel);
+
+    return SafeArea(
+      child: Builder(
+        builder: (BuildContext context) {
+          return CustomSingleChildLayout(
+            delegate: _PopupMenuRouteLayout(
+              position,
+              itemSizes,
+              selectedItemIndex,
+              Directionality.of(context),
+            ),
+            child: capturedThemes.wrap(menu),
+          );
+        },
+      ),
+    );
+  }
+}
+
+/// Show a popup menu that contains the `items` at `position`.
+///
+/// `items` should be non-null and not empty.
+///
+/// If `initialValue` is specified then the first item with a matching value
+/// will be highlighted and the value of `position` gives the rectangle whose
+/// vertical center will be aligned with the vertical center of the highlighted
+/// item (when possible).
+///
+/// If `initialValue` is not specified then the top of the menu will be aligned
+/// with the top of the `position` rectangle.
+///
+/// In both cases, the menu position will be adjusted if necessary to fit on the
+/// screen.
+///
+/// Horizontally, the menu is positioned so that it grows in the direction that
+/// has the most room. For example, if the `position` describes a rectangle on
+/// the left edge of the screen, then the left edge of the menu is aligned with
+/// the left edge of the `position`, and the menu grows to the right. If both
+/// edges of the `position` are equidistant from the opposite edge of the
+/// screen, then the ambient [Directionality] is used as a tie-breaker,
+/// preferring to grow in the reading direction.
+///
+/// The positioning of the `initialValue` at the `position` is implemented by
+/// iterating over the `items` to find the first whose
+/// [PopupMenuEntry.represents] method returns true for `initialValue`, and then
+/// summing the values of [PopupMenuEntry.height] for all the preceding widgets
+/// in the list.
+///
+/// The `elevation` argument specifies the z-coordinate at which to place the
+/// menu. The elevation defaults to 8, the appropriate elevation for popup
+/// menus.
+///
+/// The `context` argument is used to look up the [Navigator] and [Theme] for
+/// the menu. It is only used when the method is called. Its corresponding
+/// widget can be safely removed from the tree before the popup menu is closed.
+///
+/// The `useRootNavigator` argument is used to determine whether to push the
+/// menu to the [Navigator] furthest from or nearest to the given `context`. It
+/// is `false` by default.
+///
+/// The `semanticLabel` argument is used by accessibility frameworks to
+/// announce screen transitions when the menu is opened and closed. If this
+/// label is not provided, it will default to
+/// [MaterialLocalizations.popupMenuLabel].
+///
+/// See also:
+///
+///  * [PopupMenuItem], a popup menu entry for a single value.
+///  * [PopupMenuDivider], a popup menu entry that is just a horizontal line.
+///  * [CheckedPopupMenuItem], a popup menu item with a checkmark.
+///  * [PopupMenuButton], which provides an [IconButton] that shows a menu by
+///    calling this method automatically.
+///  * [SemanticsConfiguration.namesRoute], for a description of edge triggered
+///    semantics.
+Future<T?> showMenu<T>({
+  required BuildContext context,
+  required RelativeRect position,
+  required List<PopupMenuEntry<T>> items,
+  T? initialValue,
+  double? elevation,
+  String? semanticLabel,
+  ShapeBorder? shape,
+  Color? color,
+  bool useRootNavigator = false,
+}) {
+  assert(context != null);
+  assert(position != null);
+  assert(useRootNavigator != null);
+  assert(items != null && items.isNotEmpty);
+  assert(debugCheckHasMaterialLocalizations(context));
+
+  switch (Theme.of(context).platform) {
+    case TargetPlatform.iOS:
+    case TargetPlatform.macOS:
+      break;
+    case TargetPlatform.android:
+    case TargetPlatform.fuchsia:
+    case TargetPlatform.linux:
+    case TargetPlatform.windows:
+      semanticLabel ??= MaterialLocalizations.of(context).popupMenuLabel;
+  }
+
+  final NavigatorState navigator = Navigator.of(context, rootNavigator: useRootNavigator);
+  return navigator.push(_PopupMenuRoute<T>(
+    position: position,
+    items: items,
+    initialValue: initialValue,
+    elevation: elevation,
+    semanticLabel: semanticLabel,
+    barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
+    shape: shape,
+    color: color,
+    capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
+  ));
+}
+
+/// Signature for the callback invoked when a menu item is selected. The
+/// argument is the value of the [PopupMenuItem] that caused its menu to be
+/// dismissed.
+///
+/// Used by [PopupMenuButton.onSelected].
+typedef PopupMenuItemSelected<T> = void Function(T value);
+
+/// Signature for the callback invoked when a [PopupMenuButton] is dismissed
+/// without selecting an item.
+///
+/// Used by [PopupMenuButton.onCanceled].
+typedef PopupMenuCanceled = void Function();
+
+/// Signature used by [PopupMenuButton] to lazily construct the items shown when
+/// the button is pressed.
+///
+/// Used by [PopupMenuButton.itemBuilder].
+typedef PopupMenuItemBuilder<T> = List<PopupMenuEntry<T>> Function(BuildContext context);
+
+/// Displays a menu when pressed and calls [onSelected] when the menu is dismissed
+/// because an item was selected. The value passed to [onSelected] is the value of
+/// the selected menu item.
+///
+/// One of [child] or [icon] may be provided, but not both. If [icon] is provided,
+/// then [PopupMenuButton] behaves like an [IconButton].
+///
+/// If both are null, then a standard overflow icon is created (depending on the
+/// platform).
+///
+/// {@tool snippet}
+///
+/// This example shows a menu with four items, selecting between an enum's
+/// values and setting a `_selection` field based on the selection.
+///
+/// ```dart
+/// // This is the type used by the popup menu below.
+/// enum WhyFarther { harder, smarter, selfStarter, tradingCharter }
+///
+/// // This menu button widget updates a _selection field (of type WhyFarther,
+/// // not shown here).
+/// PopupMenuButton<WhyFarther>(
+///   onSelected: (WhyFarther result) { setState(() { _selection = result; }); },
+///   itemBuilder: (BuildContext context) => <PopupMenuEntry<WhyFarther>>[
+///     const PopupMenuItem<WhyFarther>(
+///       value: WhyFarther.harder,
+///       child: Text('Working a lot harder'),
+///     ),
+///     const PopupMenuItem<WhyFarther>(
+///       value: WhyFarther.smarter,
+///       child: Text('Being a lot smarter'),
+///     ),
+///     const PopupMenuItem<WhyFarther>(
+///       value: WhyFarther.selfStarter,
+///       child: Text('Being a self-starter'),
+///     ),
+///     const PopupMenuItem<WhyFarther>(
+///       value: WhyFarther.tradingCharter,
+///       child: Text('Placed in charge of trading charter'),
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [PopupMenuItem], a popup menu entry for a single value.
+///  * [PopupMenuDivider], a popup menu entry that is just a horizontal line.
+///  * [CheckedPopupMenuItem], a popup menu item with a checkmark.
+///  * [showMenu], a method to dynamically show a popup menu at a given location.
+class PopupMenuButton<T> extends StatefulWidget {
+  /// Creates a button that shows a popup menu.
+  ///
+  /// The [itemBuilder] argument must not be null.
+  const PopupMenuButton({
+    Key? key,
+    required this.itemBuilder,
+    this.initialValue,
+    this.onSelected,
+    this.onCanceled,
+    this.tooltip,
+    this.elevation,
+    this.padding = const EdgeInsets.all(8.0),
+    this.child,
+    this.icon,
+    this.offset = Offset.zero,
+    this.enabled = true,
+    this.shape,
+    this.color,
+    this.enableFeedback,
+  }) : assert(itemBuilder != null),
+       assert(offset != null),
+       assert(enabled != null),
+       assert(!(child != null && icon != null),
+           'You can only pass [child] or [icon], not both.'),
+       super(key: key);
+
+  /// Called when the button is pressed to create the items to show in the menu.
+  final PopupMenuItemBuilder<T> itemBuilder;
+
+  /// The value of the menu item, if any, that should be highlighted when the menu opens.
+  final T? initialValue;
+
+  /// Called when the user selects a value from the popup menu created by this button.
+  ///
+  /// If the popup menu is dismissed without selecting a value, [onCanceled] is
+  /// called instead.
+  final PopupMenuItemSelected<T>? onSelected;
+
+  /// Called when the user dismisses the popup menu without selecting an item.
+  ///
+  /// If the user selects a value, [onSelected] is called instead.
+  final PopupMenuCanceled? onCanceled;
+
+  /// Text that describes the action that will occur when the button is pressed.
+  ///
+  /// This text is displayed when the user long-presses on the button and is
+  /// used for accessibility.
+  final String? tooltip;
+
+  /// The z-coordinate at which to place the menu when open. This controls the
+  /// size of the shadow below the menu.
+  ///
+  /// Defaults to 8, the appropriate elevation for popup menus.
+  final double? elevation;
+
+  /// Matches IconButton's 8 dps padding by default. In some cases, notably where
+  /// this button appears as the trailing element of a list item, it's useful to be able
+  /// to set the padding to zero.
+  final EdgeInsetsGeometry padding;
+
+  /// If provided, [child] is the widget used for this button
+  /// and the button will utilize an [InkWell] for taps.
+  final Widget? child;
+
+  /// If provided, the [icon] is used for this button
+  /// and the button will behave like an [IconButton].
+  final Widget? icon;
+
+  /// The offset applied to the Popup Menu Button.
+  ///
+  /// When not set, the Popup Menu Button will be positioned directly next to
+  /// the button that was used to create it.
+  final Offset offset;
+
+  /// Whether this popup menu button is interactive.
+  ///
+  /// Must be non-null, defaults to `true`
+  ///
+  /// If `true` the button will respond to presses by displaying the menu.
+  ///
+  /// If `false`, the button is styled with the disabled color from the
+  /// current [Theme] and will not respond to presses or show the popup
+  /// menu and [onSelected], [onCanceled] and [itemBuilder] will not be called.
+  ///
+  /// This can be useful in situations where the app needs to show the button,
+  /// but doesn't currently have anything to show in the menu.
+  final bool enabled;
+
+  /// If provided, the shape used for the menu.
+  ///
+  /// If this property is null, then [PopupMenuThemeData.shape] is used.
+  /// If [PopupMenuThemeData.shape] is also null, then the default shape for
+  /// [MaterialType.card] is used. This default shape is a rectangle with
+  /// rounded edges of BorderRadius.circular(2.0).
+  final ShapeBorder? shape;
+
+  /// If provided, the background color used for the menu.
+  ///
+  /// If this property is null, then [PopupMenuThemeData.color] is used.
+  /// If [PopupMenuThemeData.color] is also null, then
+  /// Theme.of(context).cardColor is used.
+  final Color? color;
+
+  /// Whether detected gestures should provide acoustic and/or haptic feedback.
+  ///
+  /// For example, on Android a tap will produce a clicking sound and a
+  /// long-press will produce a short vibration, when feedback is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [Feedback] for providing platform-specific feedback to certain actions.
+  final bool? enableFeedback;
+
+  @override
+  PopupMenuButtonState<T> createState() => PopupMenuButtonState<T>();
+}
+
+/// The [State] for a [PopupMenuButton].
+///
+/// See [showButtonMenu] for a way to programmatically open the popup menu
+/// of your button state.
+class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
+  /// A method to show a popup menu with the items supplied to
+  /// [PopupMenuButton.itemBuilder] at the position of your [PopupMenuButton].
+  ///
+  /// By default, it is called when the user taps the button and [PopupMenuButton.enabled]
+  /// is set to `true`. Moreover, you can open the button by calling the method manually.
+  ///
+  /// You would access your [PopupMenuButtonState] using a [GlobalKey] and
+  /// show the menu of the button with `globalKey.currentState.showButtonMenu`.
+  void showButtonMenu() {
+    final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
+    final RenderBox button = context.findRenderObject()! as RenderBox;
+    final RenderBox overlay = Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox;
+    final RelativeRect position = RelativeRect.fromRect(
+      Rect.fromPoints(
+        button.localToGlobal(widget.offset, ancestor: overlay),
+        button.localToGlobal(button.size.bottomRight(Offset.zero) + widget.offset, ancestor: overlay),
+      ),
+      Offset.zero & overlay.size,
+    );
+    final List<PopupMenuEntry<T>> items = widget.itemBuilder(context);
+    // Only show the menu if there is something to show
+    if (items.isNotEmpty) {
+      showMenu<T?>(
+        context: context,
+        elevation: widget.elevation ?? popupMenuTheme.elevation,
+        items: items,
+        initialValue: widget.initialValue,
+        position: position,
+        shape: widget.shape ?? popupMenuTheme.shape,
+        color: widget.color ?? popupMenuTheme.color,
+      )
+      .then<void>((T? newValue) {
+        if (!mounted)
+          return null;
+        if (newValue == null) {
+          if (widget.onCanceled != null)
+            widget.onCanceled!();
+          return null;
+        }
+        if (widget.onSelected != null)
+          widget.onSelected!(newValue);
+      });
+    }
+  }
+
+  bool get _canRequestFocus {
+    final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
+    switch (mode) {
+      case NavigationMode.traditional:
+        return widget.enabled;
+      case NavigationMode.directional:
+        return true;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final bool enableFeedback = widget.enableFeedback
+      ?? PopupMenuTheme.of(context).enableFeedback
+      ?? true;
+
+    assert(debugCheckHasMaterialLocalizations(context));
+
+    if (widget.child != null)
+      return Tooltip(
+        message: widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip,
+        child: InkWell(
+          onTap: widget.enabled ? showButtonMenu : null,
+          canRequestFocus: _canRequestFocus,
+          child: widget.child,
+          enableFeedback: enableFeedback,
+        ),
+      );
+
+    return IconButton(
+      icon: widget.icon ?? Icon(Icons.adaptive.more),
+      padding: widget.padding,
+      tooltip: widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip,
+      onPressed: widget.enabled ? showButtonMenu : null,
+      enableFeedback: enableFeedback,
+    );
+  }
+}
diff --git a/lib/src/material/popup_menu_theme.dart b/lib/src/material/popup_menu_theme.dart
new file mode 100644
index 0000000..147104d
--- /dev/null
+++ b/lib/src/material/popup_menu_theme.dart
@@ -0,0 +1,172 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'theme.dart';
+
+/// Defines the visual properties of the routes used to display popup menus
+/// as well as [PopupMenuItem] and [PopupMenuDivider] widgets.
+///
+/// Descendant widgets obtain the current [PopupMenuThemeData] object
+/// using `PopupMenuTheme.of(context)`. Instances of
+/// [PopupMenuThemeData] can be customized with
+/// [PopupMenuThemeData.copyWith].
+///
+/// Typically, a [PopupMenuThemeData] is specified as part of the
+/// overall [Theme] with [ThemeData.popupMenuTheme]. Otherwise,
+/// [PopupMenuTheme] can be used to configure its own widget subtree.
+///
+/// All [PopupMenuThemeData] properties are `null` by default.
+/// If any of these properties are null, the popup menu will provide its
+/// own defaults.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class PopupMenuThemeData with Diagnosticable {
+  /// Creates the set of properties used to configure [PopupMenuTheme].
+  const PopupMenuThemeData({
+    this.color,
+    this.shape,
+    this.elevation,
+    this.textStyle,
+    this.enableFeedback,
+  });
+
+  /// The background color of the popup menu.
+  final Color? color;
+
+  /// The shape of the popup menu.
+  final ShapeBorder? shape;
+
+  /// The elevation of the popup menu.
+  final double? elevation;
+
+  /// The text style of items in the popup menu.
+  final TextStyle? textStyle;
+
+  /// If specified, defines the feedback property for [PopupMenuButton].
+  ///
+  /// If [PopupMenuButton.enableFeedback] is provided, [enableFeedback] is ignored.
+  final bool? enableFeedback;
+
+  /// Creates a copy of this object with the given fields replaced with the
+  /// new values.
+  PopupMenuThemeData copyWith({
+    Color? color,
+    ShapeBorder? shape,
+    double? elevation,
+    TextStyle? textStyle,
+    bool? enableFeedback,
+  }) {
+    return PopupMenuThemeData(
+      color: color ?? this.color,
+      shape: shape ?? this.shape,
+      elevation: elevation ?? this.elevation,
+      textStyle: textStyle ?? this.textStyle,
+      enableFeedback: enableFeedback ?? this.enableFeedback,
+    );
+  }
+
+  /// Linearly interpolate between two popup menu themes.
+  ///
+  /// If both arguments are null, then null is returned.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static PopupMenuThemeData? lerp(PopupMenuThemeData? a, PopupMenuThemeData? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    return PopupMenuThemeData(
+      color: Color.lerp(a?.color, b?.color, t),
+      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
+      elevation: lerpDouble(a?.elevation, b?.elevation, t),
+      textStyle: TextStyle.lerp(a?.textStyle, b?.textStyle, t),
+      enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      color,
+      shape,
+      elevation,
+      textStyle,
+      enableFeedback,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is PopupMenuThemeData
+        && other.elevation == elevation
+        && other.color == color
+        && other.shape == shape
+        && other.textStyle == textStyle
+        && other.enableFeedback == enableFeedback;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
+    properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('text style', textStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('enableFeedback', enableFeedback, defaultValue: null));
+  }
+}
+
+/// An inherited widget that defines the configuration for
+/// popup menus in this widget's subtree.
+///
+/// Values specified here are used for popup menu properties that are not
+/// given an explicit non-null value.
+class PopupMenuTheme extends InheritedTheme {
+  /// Creates a popup menu theme that controls the configurations for
+  /// popup menus in its widget subtree.
+  ///
+  /// The data argument must not be null.
+  const PopupMenuTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null), super(key: key, child: child);
+
+  /// The properties for descendant popup menu widgets.
+  final PopupMenuThemeData data;
+
+  /// The closest instance of this class's [data] value that encloses the given
+  /// context. If there is no ancestor, it returns [ThemeData.popupMenuTheme].
+  /// Applications can assume that the returned value will not be null.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// PopupMenuThemeData theme = PopupMenuTheme.of(context);
+  /// ```
+  static PopupMenuThemeData of(BuildContext context) {
+    final PopupMenuTheme? popupMenuTheme = context.dependOnInheritedWidgetOfExactType<PopupMenuTheme>();
+    return popupMenuTheme?.data ?? Theme.of(context).popupMenuTheme;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return PopupMenuTheme(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(PopupMenuTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/progress_indicator.dart b/lib/src/material/progress_indicator.dart
new file mode 100644
index 0000000..581fc56
--- /dev/null
+++ b/lib/src/material/progress_indicator.dart
@@ -0,0 +1,859 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/cupertino.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'material.dart';
+import 'theme.dart';
+
+const double _kMinCircularProgressIndicatorSize = 36.0;
+const int _kIndeterminateLinearDuration = 1800;
+const int _kIndeterminateCircularDuration = 1333 * 2222;
+
+enum _ActivityIndicatorType { material, adaptive }
+
+/// A base class for material design progress indicators.
+///
+/// This widget cannot be instantiated directly. For a linear progress
+/// indicator, see [LinearProgressIndicator]. For a circular progress indicator,
+/// see [CircularProgressIndicator].
+///
+/// See also:
+///
+///  * <https://material.io/design/components/progress-indicators.html>
+abstract class ProgressIndicator extends StatefulWidget {
+  /// Creates a progress indicator.
+  ///
+  /// {@template flutter.material.ProgressIndicator.ProgressIndicator}
+  /// The [value] argument can either be null for an indeterminate
+  /// progress indicator, or non-null for a determinate progress
+  /// indicator.
+  ///
+  /// ## Accessibility
+  ///
+  /// The [semanticsLabel] can be used to identify the purpose of this progress
+  /// bar for screen reading software. The [semanticsValue] property may be used
+  /// for determinate progress indicators to indicate how much progress has been made.
+  /// {@endtemplate}
+  const ProgressIndicator({
+    Key? key,
+    this.value,
+    this.backgroundColor,
+    this.valueColor,
+    this.semanticsLabel,
+    this.semanticsValue,
+  }) : super(key: key);
+
+  /// If non-null, the value of this progress indicator.
+  ///
+  /// A value of 0.0 means no progress and 1.0 means that progress is complete.
+  ///
+  /// If null, this progress indicator is indeterminate, which means the
+  /// indicator displays a predetermined animation that does not indicate how
+  /// much actual progress is being made.
+  ///
+  /// This property is ignored if used in an adaptive constructor inside an iOS
+  /// environment.
+  final double? value;
+
+  /// The progress indicator's background color.
+  ///
+  /// The current theme's [ThemeData.backgroundColor] by default.
+  ///
+  /// This property is ignored if used in an adaptive constructor inside an iOS
+  /// environment.
+  final Color? backgroundColor;
+
+  /// The progress indicator's color as an animated value.
+  ///
+  /// To specify a constant color use: `AlwaysStoppedAnimation<Color>(color)`.
+  ///
+  /// If null, the progress indicator is rendered with the current theme's
+  /// [ThemeData.accentColor].
+  ///
+  /// This property is ignored if used in an adaptive constructor inside an iOS
+  /// environment.
+  final Animation<Color?>? valueColor;
+
+  /// {@template flutter.progress_indicator.ProgressIndicator.semanticsLabel}
+  /// The [SemanticsProperties.label] for this progress indicator.
+  ///
+  /// This value indicates the purpose of the progress bar, and will be
+  /// read out by screen readers to indicate the purpose of this progress
+  /// indicator.
+  ///
+  /// This property is ignored if used in an adaptive constructor inside an iOS
+  /// environment.
+  /// {@endtemplate}
+  final String? semanticsLabel;
+
+  /// {@template flutter.progress_indicator.ProgressIndicator.semanticsValue}
+  /// The [SemanticsProperties.value] for this progress indicator.
+  ///
+  /// This will be used in conjunction with the [semanticsLabel] by
+  /// screen reading software to identify the widget, and is primarily
+  /// intended for use with determinate progress indicators to announce
+  /// how far along they are.
+  ///
+  /// For determinate progress indicators, this will be defaulted to
+  /// [ProgressIndicator.value] expressed as a percentage, i.e. `0.1` will
+  /// become '10%'.
+  ///
+  /// This property is ignored if used in an adaptive constructor inside an iOS
+  /// environment.
+  /// {@endtemplate}
+  final String? semanticsValue;
+
+  Color _getBackgroundColor(BuildContext context) => backgroundColor ?? Theme.of(context).backgroundColor;
+  Color _getValueColor(BuildContext context) => valueColor?.value ?? Theme.of(context).accentColor;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(PercentProperty('value', value, showName: false, ifNull: '<indeterminate>'));
+  }
+
+  Widget _buildSemanticsWrapper({
+    required BuildContext context,
+    required Widget child,
+  }) {
+    String? expandedSemanticsValue = semanticsValue;
+    if (value != null) {
+      expandedSemanticsValue ??= '${(value! * 100).round()}%';
+    }
+    return Semantics(
+      label: semanticsLabel,
+      value: expandedSemanticsValue,
+      child: child,
+    );
+  }
+}
+
+class _LinearProgressIndicatorPainter extends CustomPainter {
+  const _LinearProgressIndicatorPainter({
+    required this.backgroundColor,
+    required this.valueColor,
+    this.value,
+    required this.animationValue,
+    required this.textDirection,
+  }) : assert(textDirection != null);
+
+  final Color backgroundColor;
+  final Color valueColor;
+  final double? value;
+  final double animationValue;
+  final TextDirection textDirection;
+
+  // The indeterminate progress animation displays two lines whose leading (head)
+  // and trailing (tail) endpoints are defined by the following four curves.
+  static const Curve line1Head = Interval(
+    0.0,
+    750.0 / _kIndeterminateLinearDuration,
+    curve: Cubic(0.2, 0.0, 0.8, 1.0),
+  );
+  static const Curve line1Tail = Interval(
+    333.0 / _kIndeterminateLinearDuration,
+    (333.0 + 750.0) / _kIndeterminateLinearDuration,
+    curve: Cubic(0.4, 0.0, 1.0, 1.0),
+  );
+  static const Curve line2Head = Interval(
+    1000.0 / _kIndeterminateLinearDuration,
+    (1000.0 + 567.0) / _kIndeterminateLinearDuration,
+    curve: Cubic(0.0, 0.0, 0.65, 1.0),
+  );
+  static const Curve line2Tail = Interval(
+    1267.0 / _kIndeterminateLinearDuration,
+    (1267.0 + 533.0) / _kIndeterminateLinearDuration,
+    curve: Cubic(0.10, 0.0, 0.45, 1.0),
+  );
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final Paint paint = Paint()
+      ..color = backgroundColor
+      ..style = PaintingStyle.fill;
+    canvas.drawRect(Offset.zero & size, paint);
+
+    paint.color = valueColor;
+
+    void drawBar(double x, double width) {
+      if (width <= 0.0)
+        return;
+
+      final double left;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          left = size.width - width - x;
+          break;
+        case TextDirection.ltr:
+          left = x;
+          break;
+      }
+      canvas.drawRect(Offset(left, 0.0) & Size(width, size.height), paint);
+    }
+
+    if (value != null) {
+      drawBar(0.0, value!.clamp(0.0, 1.0) * size.width);
+    } else {
+      final double x1 = size.width * line1Tail.transform(animationValue);
+      final double width1 = size.width * line1Head.transform(animationValue) - x1;
+
+      final double x2 = size.width * line2Tail.transform(animationValue);
+      final double width2 = size.width * line2Head.transform(animationValue) - x2;
+
+      drawBar(x1, width1);
+      drawBar(x2, width2);
+    }
+  }
+
+  @override
+  bool shouldRepaint(_LinearProgressIndicatorPainter oldPainter) {
+    return oldPainter.backgroundColor != backgroundColor
+        || oldPainter.valueColor != valueColor
+        || oldPainter.value != value
+        || oldPainter.animationValue != animationValue
+        || oldPainter.textDirection != textDirection;
+  }
+}
+
+/// A material design linear progress indicator, also known as a progress bar.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=O-rhXZLtpv0}
+///
+/// A widget that shows progress along a line. There are two kinds of linear
+/// progress indicators:
+///
+///  * _Determinate_. Determinate progress indicators have a specific value at
+///    each point in time, and the value should increase monotonically from 0.0
+///    to 1.0, at which time the indicator is complete. To create a determinate
+///    progress indicator, use a non-null [value] between 0.0 and 1.0.
+///  * _Indeterminate_. Indeterminate progress indicators do not have a specific
+///    value at each point in time and instead indicate that progress is being
+///    made without indicating how much progress remains. To create an
+///    indeterminate progress indicator, use a null [value].
+///
+/// The indicator line is displayed with [valueColor], an animated value. To
+/// specify a constant color value use: `AlwaysStoppedAnimation<Color>(color)`.
+///
+/// The minimum height of the indicator can be specified using [minHeight].
+/// The indicator can be made taller by wrapping the widget with a [SizedBox].
+///
+/// {@tool dartpad --template=stateful_widget_material_ticker_no_null_safety}
+///
+/// This example shows a [LinearProgressIndicator] with a changing value.
+///
+/// ```dart
+///  AnimationController controller;
+///
+///  @override
+///  void initState() {
+///    controller = AnimationController(
+///      vsync: this,
+///      duration: const Duration(seconds: 5),
+///    )..addListener(() {
+///        setState(() {});
+///      });
+///    controller.repeat(reverse: true);
+///    super.initState();
+///  }
+///
+/// @override
+/// void dispose() {
+///   controller.dispose();
+///   super.dispose();
+/// }
+///
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: Padding(
+///       padding: const EdgeInsets.all(20.0),
+///       child: Column(
+///         mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+///         children: [
+///           Text(
+///             'Linear progress indicator with a fixed color',
+///             style: const TextStyle(fontSize: 20),
+///           ),
+///           LinearProgressIndicator(
+///             value: controller.value,
+///             semanticsLabel: 'Linear progress indicator',
+///           ),
+///         ],
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [CircularProgressIndicator], which shows progress along a circular arc.
+///  * [RefreshIndicator], which automatically displays a [CircularProgressIndicator]
+///    when the underlying vertical scrollable is overscrolled.
+///  * <https://material.io/design/components/progress-indicators.html#linear-progress-indicators>
+class LinearProgressIndicator extends ProgressIndicator {
+  /// Creates a linear progress indicator.
+  ///
+  /// {@macro flutter.material.ProgressIndicator.ProgressIndicator}
+  const LinearProgressIndicator({
+    Key? key,
+    double? value,
+    Color? backgroundColor,
+    Animation<Color>? valueColor,
+    this.minHeight,
+    String? semanticsLabel,
+    String? semanticsValue,
+  }) : assert(minHeight == null || minHeight > 0),
+       super(
+        key: key,
+        value: value,
+        backgroundColor: backgroundColor,
+        valueColor: valueColor,
+        semanticsLabel: semanticsLabel,
+        semanticsValue: semanticsValue,
+      );
+
+  /// The minimum height of the line used to draw the indicator.
+  ///
+  /// This defaults to 4dp.
+  final double? minHeight;
+
+  @override
+  _LinearProgressIndicatorState createState() => _LinearProgressIndicatorState();
+}
+
+class _LinearProgressIndicatorState extends State<LinearProgressIndicator> with SingleTickerProviderStateMixin {
+  late AnimationController _controller;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(
+      duration: const Duration(milliseconds: _kIndeterminateLinearDuration),
+      vsync: this,
+    );
+    if (widget.value == null)
+      _controller.repeat();
+  }
+
+  @override
+  void didUpdateWidget(LinearProgressIndicator oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.value == null && !_controller.isAnimating)
+      _controller.repeat();
+    else if (widget.value != null && _controller.isAnimating)
+      _controller.stop();
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  Widget _buildIndicator(BuildContext context, double animationValue, TextDirection textDirection) {
+    return widget._buildSemanticsWrapper(
+      context: context,
+      child: Container(
+        constraints: BoxConstraints(
+          minWidth: double.infinity,
+          minHeight: widget.minHeight ?? 4.0,
+        ),
+        child: CustomPaint(
+          painter: _LinearProgressIndicatorPainter(
+            backgroundColor: widget._getBackgroundColor(context),
+            valueColor: widget._getValueColor(context),
+            value: widget.value, // may be null
+            animationValue: animationValue, // ignored if widget.value is not null
+            textDirection: textDirection,
+          ),
+        ),
+      ),
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final TextDirection textDirection = Directionality.of(context);
+
+    if (widget.value != null)
+      return _buildIndicator(context, _controller.value, textDirection);
+
+    return AnimatedBuilder(
+      animation: _controller.view,
+      builder: (BuildContext context, Widget? child) {
+        return _buildIndicator(context, _controller.value, textDirection);
+      },
+    );
+  }
+}
+
+class _CircularProgressIndicatorPainter extends CustomPainter {
+  _CircularProgressIndicatorPainter({
+    this.backgroundColor,
+    required this.valueColor,
+    required this.value,
+    required this.headValue,
+    required this.tailValue,
+    required this.offsetValue,
+    required this.rotationValue,
+    required this.strokeWidth,
+  }) : arcStart = value != null
+         ? _startAngle
+         : _startAngle + tailValue * 3 / 2 * math.pi + rotationValue * math.pi * 2.0 + offsetValue * 0.5 * math.pi,
+       arcSweep = value != null
+         ? value.clamp(0.0, 1.0) * _sweep
+         : math.max(headValue * 3 / 2 * math.pi - tailValue * 3 / 2 * math.pi, _epsilon);
+
+  final Color? backgroundColor;
+  final Color valueColor;
+  final double? value;
+  final double headValue;
+  final double tailValue;
+  final double offsetValue;
+  final double rotationValue;
+  final double strokeWidth;
+  final double arcStart;
+  final double arcSweep;
+
+  static const double _twoPi = math.pi * 2.0;
+  static const double _epsilon = .001;
+  // Canvas.drawArc(r, 0, 2*PI) doesn't draw anything, so just get close.
+  static const double _sweep = _twoPi - _epsilon;
+  static const double _startAngle = -math.pi / 2.0;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final Paint paint = Paint()
+      ..color = valueColor
+      ..strokeWidth = strokeWidth
+      ..style = PaintingStyle.stroke;
+    if (backgroundColor != null) {
+      final Paint backgroundPaint = Paint()
+        ..color = backgroundColor!
+        ..strokeWidth = strokeWidth
+        ..style = PaintingStyle.stroke;
+      canvas.drawArc(Offset.zero & size, 0, _sweep, false, backgroundPaint);
+    }
+
+    if (value == null) // Indeterminate
+      paint.strokeCap = StrokeCap.square;
+
+    canvas.drawArc(Offset.zero & size, arcStart, arcSweep, false, paint);
+  }
+
+  @override
+  bool shouldRepaint(_CircularProgressIndicatorPainter oldPainter) {
+    return oldPainter.backgroundColor != backgroundColor
+        || oldPainter.valueColor != valueColor
+        || oldPainter.value != value
+        || oldPainter.headValue != headValue
+        || oldPainter.tailValue != tailValue
+        || oldPainter.offsetValue != offsetValue
+        || oldPainter.rotationValue != rotationValue
+        || oldPainter.strokeWidth != strokeWidth;
+  }
+}
+
+/// A material design circular progress indicator, which spins to indicate that
+/// the application is busy.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=O-rhXZLtpv0}
+///
+/// A widget that shows progress along a circle. There are two kinds of circular
+/// progress indicators:
+///
+///  * _Determinate_. Determinate progress indicators have a specific value at
+///    each point in time, and the value should increase monotonically from 0.0
+///    to 1.0, at which time the indicator is complete. To create a determinate
+///    progress indicator, use a non-null [value] between 0.0 and 1.0.
+///  * _Indeterminate_. Indeterminate progress indicators do not have a specific
+///    value at each point in time and instead indicate that progress is being
+///    made without indicating how much progress remains. To create an
+///    indeterminate progress indicator, use a null [value].
+///
+/// The indicator arc is displayed with [valueColor], an animated value. To
+/// specify a constant color use: `AlwaysStoppedAnimation<Color>(color)`.
+///
+/// {@tool dartpad --template=stateful_widget_material_ticker_no_null_safety}
+///
+/// This example shows a [CircularProgressIndicator] with a changing value.
+///
+/// ```dart
+///  AnimationController controller;
+///
+///  @override
+///  void initState() {
+///    controller = AnimationController(
+///      vsync: this,
+///      duration: const Duration(seconds: 5),
+///    )..addListener(() {
+///        setState(() {});
+///      });
+///    controller.repeat(reverse: true);
+///    super.initState();
+///  }
+///
+/// @override
+/// void dispose() {
+///   controller.dispose();
+///   super.dispose();
+/// }
+///
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: Padding(
+///       padding: const EdgeInsets.all(20.0),
+///       child: Column(
+///         mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+///         children: [
+///           Text(
+///             'Linear progress indicator with a fixed color',
+///             style: Theme.of(context).textTheme.headline6,
+///           ),
+///           CircularProgressIndicator(
+///             value: controller.value,
+///             semanticsLabel: 'Linear progress indicator',
+///           ),
+///         ],
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [LinearProgressIndicator], which displays progress along a line.
+///  * [RefreshIndicator], which automatically displays a [CircularProgressIndicator]
+///    when the underlying vertical scrollable is overscrolled.
+///  * <https://material.io/design/components/progress-indicators.html#circular-progress-indicators>
+class CircularProgressIndicator extends ProgressIndicator {
+  /// Creates a circular progress indicator.
+  ///
+  /// {@macro flutter.material.ProgressIndicator.ProgressIndicator}
+  const CircularProgressIndicator({
+    Key? key,
+    double? value,
+    Color? backgroundColor,
+    Animation<Color?>? valueColor,
+    this.strokeWidth = 4.0,
+    String? semanticsLabel,
+    String? semanticsValue,
+  }) : _indicatorType = _ActivityIndicatorType.material,
+       super(
+         key: key,
+         value: value,
+         backgroundColor: backgroundColor,
+         valueColor: valueColor,
+         semanticsLabel: semanticsLabel,
+         semanticsValue: semanticsValue,
+       );
+
+  /// Creates an adaptive progress indicator that is a
+  /// [CupertinoActivityIndicator] in iOS and [CircularProgressIndicator] in
+  /// material theme/non-iOS.
+  ///
+  /// The [value], [backgroundColor], [valueColor], [strokeWidth],
+  /// [semanticsLabel], and [semanticsValue] will be ignored in iOS.
+  ///
+  /// {@macro flutter.material.ProgressIndicator.ProgressIndicator}
+  const CircularProgressIndicator.adaptive({
+    Key? key,
+    double? value,
+    Color? backgroundColor,
+    Animation<Color?>? valueColor,
+    this.strokeWidth = 4.0,
+    String? semanticsLabel,
+    String? semanticsValue,
+  }) : _indicatorType = _ActivityIndicatorType.adaptive,
+       super(
+         key: key,
+         value: value,
+         backgroundColor: backgroundColor,
+         valueColor: valueColor,
+         semanticsLabel: semanticsLabel,
+         semanticsValue: semanticsValue,
+       );
+
+  final _ActivityIndicatorType _indicatorType;
+
+  /// The width of the line used to draw the circle.
+  ///
+  /// This property is ignored if used in an adaptive constructor inside an iOS
+  /// environment.
+  final double strokeWidth;
+
+  @override
+  _CircularProgressIndicatorState createState() => _CircularProgressIndicatorState();
+}
+
+class _CircularProgressIndicatorState extends State<CircularProgressIndicator> with SingleTickerProviderStateMixin {
+  static const int _pathCount = _kIndeterminateCircularDuration ~/ 1333;
+  static const int _rotationCount = _kIndeterminateCircularDuration ~/ 2222;
+
+  static final Animatable<double> _strokeHeadTween = CurveTween(
+    curve: const Interval(0.0, 0.5, curve: Curves.fastOutSlowIn),
+  ).chain(CurveTween(
+    curve: const SawTooth(_pathCount),
+  ));
+  static final Animatable<double> _strokeTailTween = CurveTween(
+    curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
+  ).chain(CurveTween(
+    curve: const SawTooth(_pathCount),
+  ));
+  static final Animatable<double> _offsetTween = CurveTween(curve: const SawTooth(_pathCount));
+  static final Animatable<double> _rotationTween = CurveTween(curve: const SawTooth(_rotationCount));
+
+  late AnimationController _controller;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(
+      duration: const Duration(milliseconds: _kIndeterminateCircularDuration),
+      vsync: this,
+    );
+    if (widget.value == null)
+      _controller.repeat();
+  }
+
+  @override
+  void didUpdateWidget(CircularProgressIndicator oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.value == null && !_controller.isAnimating)
+      _controller.repeat();
+    else if (widget.value != null && _controller.isAnimating)
+      _controller.stop();
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  Widget _buildCupertinoIndicator(BuildContext context) {
+    return CupertinoActivityIndicator(key: widget.key);
+  }
+
+  Widget _buildMaterialIndicator(BuildContext context, double headValue, double tailValue, double offsetValue, double rotationValue) {
+    return widget._buildSemanticsWrapper(
+      context: context,
+      child: Container(
+        constraints: const BoxConstraints(
+          minWidth: _kMinCircularProgressIndicatorSize,
+          minHeight: _kMinCircularProgressIndicatorSize,
+        ),
+        child: CustomPaint(
+          painter: _CircularProgressIndicatorPainter(
+            backgroundColor: widget.backgroundColor,
+            valueColor: widget._getValueColor(context),
+            value: widget.value, // may be null
+            headValue: headValue, // remaining arguments are ignored if widget.value is not null
+            tailValue: tailValue,
+            offsetValue: offsetValue,
+            rotationValue: rotationValue,
+            strokeWidth: widget.strokeWidth,
+          ),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildAnimation() {
+    return AnimatedBuilder(
+      animation: _controller,
+      builder: (BuildContext context, Widget? child) {
+        return _buildMaterialIndicator(
+          context,
+          _strokeHeadTween.evaluate(_controller),
+          _strokeTailTween.evaluate(_controller),
+          _offsetTween.evaluate(_controller),
+          _rotationTween.evaluate(_controller),
+        );
+      },
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    switch (widget._indicatorType) {
+      case _ActivityIndicatorType.material:
+        if (widget.value != null)
+          return _buildMaterialIndicator(context, 0.0, 0.0, 0, 0.0);
+        return _buildAnimation();
+      case _ActivityIndicatorType.adaptive:
+        final ThemeData theme = Theme.of(context);
+        assert(theme.platform != null);
+        switch (theme.platform) {
+          case TargetPlatform.iOS:
+          case TargetPlatform.macOS:
+            return _buildCupertinoIndicator(context);
+          case TargetPlatform.android:
+          case TargetPlatform.fuchsia:
+          case TargetPlatform.linux:
+          case TargetPlatform.windows:
+            if (widget.value != null)
+              return _buildMaterialIndicator(context, 0.0, 0.0, 0, 0.0);
+            return _buildAnimation();
+        }
+    }
+  }
+}
+
+class _RefreshProgressIndicatorPainter extends _CircularProgressIndicatorPainter {
+  _RefreshProgressIndicatorPainter({
+    required Color valueColor,
+    required double? value,
+    required double headValue,
+    required double tailValue,
+    required double offsetValue,
+    required double rotationValue,
+    required double strokeWidth,
+    required this.arrowheadScale,
+  }) : super(
+    valueColor: valueColor,
+    value: value,
+    headValue: headValue,
+    tailValue: tailValue,
+    offsetValue: offsetValue,
+    rotationValue: rotationValue,
+    strokeWidth: strokeWidth,
+  );
+
+  final double arrowheadScale;
+
+  void paintArrowhead(Canvas canvas, Size size) {
+    // ux, uy: a unit vector whose direction parallels the base of the arrowhead.
+    // (So ux, -uy points in the direction the arrowhead points.)
+    final double arcEnd = arcStart + arcSweep;
+    final double ux = math.cos(arcEnd);
+    final double uy = math.sin(arcEnd);
+
+    assert(size.width == size.height);
+    final double radius = size.width / 2.0;
+    final double arrowheadPointX = radius + ux * radius + -uy * strokeWidth * 2.0 * arrowheadScale;
+    final double arrowheadPointY = radius + uy * radius +  ux * strokeWidth * 2.0 * arrowheadScale;
+    final double arrowheadRadius = strokeWidth * 1.5 * arrowheadScale;
+    final double innerRadius = radius - arrowheadRadius;
+    final double outerRadius = radius + arrowheadRadius;
+
+    final Path path = Path()
+      ..moveTo(radius + ux * innerRadius, radius + uy * innerRadius)
+      ..lineTo(radius + ux * outerRadius, radius + uy * outerRadius)
+      ..lineTo(arrowheadPointX, arrowheadPointY)
+      ..close();
+    final Paint paint = Paint()
+      ..color = valueColor
+      ..strokeWidth = strokeWidth
+      ..style = PaintingStyle.fill;
+    canvas.drawPath(path, paint);
+  }
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    super.paint(canvas, size);
+    if (arrowheadScale > 0.0)
+      paintArrowhead(canvas, size);
+  }
+}
+
+/// An indicator for the progress of refreshing the contents of a widget.
+///
+/// Typically used for swipe-to-refresh interactions. See [RefreshIndicator] for
+/// a complete implementation of swipe-to-refresh driven by a [Scrollable]
+/// widget.
+///
+/// The indicator arc is displayed with [valueColor], an animated value. To
+/// specify a constant color use: `AlwaysStoppedAnimation<Color>(color)`.
+///
+/// See also:
+///
+///  * [RefreshIndicator], which automatically displays a [CircularProgressIndicator]
+///    when the underlying vertical scrollable is overscrolled.
+class RefreshProgressIndicator extends CircularProgressIndicator {
+  /// Creates a refresh progress indicator.
+  ///
+  /// Rather than creating a refresh progress indicator directly, consider using
+  /// a [RefreshIndicator] together with a [Scrollable] widget.
+  ///
+  /// {@macro flutter.material.ProgressIndicator.ProgressIndicator}
+  const RefreshProgressIndicator({
+    Key? key,
+    double? value,
+    Color? backgroundColor,
+    Animation<Color?>? valueColor,
+    double strokeWidth = 2.0, // Different default than CircularProgressIndicator.
+    String? semanticsLabel,
+    String? semanticsValue,
+  }) : super(
+    key: key,
+    value: value,
+    backgroundColor: backgroundColor,
+    valueColor: valueColor,
+    strokeWidth: strokeWidth,
+    semanticsLabel: semanticsLabel,
+    semanticsValue: semanticsValue,
+  );
+
+  @override
+  _RefreshProgressIndicatorState createState() => _RefreshProgressIndicatorState();
+}
+
+class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState {
+  static const double _indicatorSize = 40.0;
+
+  // Always show the indeterminate version of the circular progress indicator.
+  // When value is non-null the sweep of the progress indicator arrow's arc
+  // varies from 0 to about 270 degrees. When value is null the arrow animates
+  // starting from wherever we left it.
+  @override
+  Widget build(BuildContext context) {
+    if (widget.value != null)
+      _controller.value = widget.value! * (1333 / 2 / _kIndeterminateCircularDuration);
+    else if (!_controller.isAnimating)
+      _controller.repeat();
+    return _buildAnimation();
+  }
+
+  @override
+  Widget _buildMaterialIndicator(BuildContext context, double headValue, double tailValue, double offsetValue, double rotationValue) {
+    final double arrowheadScale = widget.value == null ? 0.0 : (widget.value! * 2.0).clamp(0.0, 1.0);
+    return widget._buildSemanticsWrapper(
+      context: context,
+      child: Container(
+        width: _indicatorSize,
+        height: _indicatorSize,
+        margin: const EdgeInsets.all(4.0), // accommodate the shadow
+        child: Material(
+          type: MaterialType.circle,
+          color: widget.backgroundColor ?? Theme.of(context).canvasColor,
+          elevation: 2.0,
+          child: Padding(
+            padding: const EdgeInsets.all(12.0),
+            child: CustomPaint(
+              painter: _RefreshProgressIndicatorPainter(
+                valueColor: widget._getValueColor(context),
+                value: null, // Draw the indeterminate progress indicator.
+                headValue: headValue,
+                tailValue: tailValue,
+                offsetValue: offsetValue,
+                rotationValue: rotationValue,
+                strokeWidth: widget.strokeWidth,
+                arrowheadScale: arrowheadScale,
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/radio.dart b/lib/src/material/radio.dart
new file mode 100644
index 0000000..c6f61d2
--- /dev/null
+++ b/lib/src/material/radio.dart
@@ -0,0 +1,674 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'constants.dart';
+import 'debug.dart';
+import 'material_state.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+import 'toggleable.dart';
+
+const double _kOuterRadius = 8.0;
+const double _kInnerRadius = 4.5;
+
+/// A material design radio button.
+///
+/// Used to select between a number of mutually exclusive values. When one radio
+/// button in a group is selected, the other radio buttons in the group cease to
+/// be selected. The values are of type `T`, the type parameter of the [Radio]
+/// class. Enums are commonly used for this purpose.
+///
+/// The radio button itself does not maintain any state. Instead, selecting the
+/// radio invokes the [onChanged] callback, passing [value] as a parameter. If
+/// [groupValue] and [value] match, this radio will be selected. Most widgets
+/// will respond to [onChanged] by calling [State.setState] to update the
+/// radio button's [groupValue].
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+///
+/// Here is an example of Radio widgets wrapped in ListTiles, which is similar
+/// to what you could get with the RadioListTile widget.
+///
+/// The currently selected character is passed into `groupValue`, which is
+/// maintained by the example's `State`. In this case, the first `Radio`
+/// will start off selected because `_character` is initialized to
+/// `SingingCharacter.lafayette`.
+///
+/// If the second radio button is pressed, the example's state is updated
+/// with `setState`, updating `_character` to `SingingCharacter.jefferson`.
+/// This causes the buttons to rebuild with the updated `groupValue`, and
+/// therefore the selection of the second button.
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// ```dart preamble
+/// enum SingingCharacter { lafayette, jefferson }
+/// ```
+///
+/// ```dart
+/// SingingCharacter _character = SingingCharacter.lafayette;
+///
+/// Widget build(BuildContext context) {
+///   return Column(
+///     children: <Widget>[
+///       ListTile(
+///         title: const Text('Lafayette'),
+///         leading: Radio(
+///           value: SingingCharacter.lafayette,
+///           groupValue: _character,
+///           onChanged: (SingingCharacter value) {
+///             setState(() { _character = value; });
+///           },
+///         ),
+///       ),
+///       ListTile(
+///         title: const Text('Thomas Jefferson'),
+///         leading: Radio(
+///           value: SingingCharacter.jefferson,
+///           groupValue: _character,
+///           onChanged: (SingingCharacter value) {
+///             setState(() { _character = value; });
+///           },
+///         ),
+///       ),
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [RadioListTile], which combines this widget with a [ListTile] so that
+///    you can give the radio button a label.
+///  * [Slider], for selecting a value in a range.
+///  * [Checkbox] and [Switch], for toggling a particular value on or off.
+///  * <https://material.io/design/components/selection-controls.html#radio-buttons>
+class Radio<T> extends StatefulWidget {
+  /// Creates a material design radio button.
+  ///
+  /// The radio button itself does not maintain any state. Instead, when the
+  /// radio button is selected, the widget calls the [onChanged] callback. Most
+  /// widgets that use a radio button will listen for the [onChanged] callback
+  /// and rebuild the radio button with a new [groupValue] to update the visual
+  /// appearance of the radio button.
+  ///
+  /// The following arguments are required:
+  ///
+  /// * [value] and [groupValue] together determine whether the radio button is
+  ///   selected.
+  /// * [onChanged] is called when the user selects this radio button.
+  const Radio({
+    Key? key,
+    required this.value,
+    required this.groupValue,
+    required this.onChanged,
+    this.mouseCursor,
+    this.toggleable = false,
+    this.activeColor,
+    this.fillColor,
+    this.focusColor,
+    this.hoverColor,
+    this.overlayColor,
+    this.splashRadius,
+    this.materialTapTargetSize,
+    this.visualDensity,
+    this.focusNode,
+    this.autofocus = false,
+  }) : assert(autofocus != null),
+       assert(toggleable != null),
+       super(key: key);
+
+  /// The value represented by this radio button.
+  final T value;
+
+  /// The currently selected value for a group of radio buttons.
+  ///
+  /// This radio button is considered selected if its [value] matches the
+  /// [groupValue].
+  final T? groupValue;
+
+  /// Called when the user selects this radio button.
+  ///
+  /// The radio button passes [value] as a parameter to this callback. The radio
+  /// button does not actually change state until the parent widget rebuilds the
+  /// radio button with the new [groupValue].
+  ///
+  /// If null, the radio button will be displayed as disabled.
+  ///
+  /// The provided callback will not be invoked if this radio button is already
+  /// selected.
+  ///
+  /// The callback provided to [onChanged] should update the state of the parent
+  /// [StatefulWidget] using the [State.setState] method, so that the parent
+  /// gets rebuilt; for example:
+  ///
+  /// ```dart
+  /// Radio<SingingCharacter>(
+  ///   value: SingingCharacter.lafayette,
+  ///   groupValue: _character,
+  ///   onChanged: (SingingCharacter newValue) {
+  ///     setState(() {
+  ///       _character = newValue;
+  ///     });
+  ///   },
+  /// )
+  /// ```
+  final ValueChanged<T?>? onChanged;
+
+  /// {@template flutter.material.radio.mouseCursor}
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [RadioThemeData.mouseCursor] is used.
+  /// If that is also null, then [MaterialStateMouseCursor.clickable] is used.
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialStateMouseCursor], a [MouseCursor] that implements
+  ///    `MaterialStateProperty` which is used in APIs that need to accept
+  ///    either a [MouseCursor] or a [MaterialStateProperty<MouseCursor>].
+  final MouseCursor? mouseCursor;
+
+  /// Set to true if this radio button is allowed to be returned to an
+  /// indeterminate state by selecting it again when selected.
+  ///
+  /// To indicate returning to an indeterminate state, [onChanged] will be
+  /// called with null.
+  ///
+  /// If true, [onChanged] can be called with [value] when selected while
+  /// [groupValue] != [value], or with null when selected again while
+  /// [groupValue] == [value].
+  ///
+  /// If false, [onChanged] will be called with [value] when it is selected
+  /// while [groupValue] != [value], and only by selecting another radio button
+  /// in the group (i.e. changing the value of [groupValue]) can this radio
+  /// button be unselected.
+  ///
+  /// The default is false.
+  ///
+  /// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+  /// This example shows how to enable deselecting a radio button by setting the
+  /// [toggleable] attribute.
+  ///
+  /// ```dart
+  /// int groupValue;
+  /// static const List<String> selections = <String>[
+  ///   'Hercules Mulligan',
+  ///   'Eliza Hamilton',
+  ///   'Philip Schuyler',
+  ///   'Maria Reynolds',
+  ///   'Samuel Seabury',
+  /// ];
+  ///
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return Scaffold(
+  ///     body: ListView.builder(
+  ///       itemBuilder: (context, index) {
+  ///         return Row(
+  ///           mainAxisSize: MainAxisSize.min,
+  ///           crossAxisAlignment: CrossAxisAlignment.center,
+  ///           children: <Widget>[
+  ///             Radio<int>(
+  ///                 value: index,
+  ///                 groupValue: groupValue,
+  ///                 // TRY THIS: Try setting the toggleable value to false and
+  ///                 // see how that changes the behavior of the widget.
+  ///                 toggleable: true,
+  ///                 onChanged: (int value) {
+  ///                   setState(() {
+  ///                     groupValue = value;
+  ///                   });
+  ///                 }),
+  ///             Text(selections[index]),
+  ///           ],
+  ///         );
+  ///       },
+  ///       itemCount: selections.length,
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  final bool toggleable;
+
+  /// The color to use when this radio button is selected.
+  ///
+  /// Defaults to [ThemeData.toggleableActiveColor].
+  ///
+  /// If [fillColor] returns a non-null color in the [MaterialState.selected]
+  /// state, it will be used instead of this color.
+  final Color? activeColor;
+
+  /// {@template flutter.material.radio.fillColor}
+  /// The color that fills the radio button, in all [MaterialState]s.
+  ///
+  /// Resolves in the following states:
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [activeColor] is used in the selected state. If
+  /// that is also null, then the value of [RadioThemeData.fillColor] is used.
+  /// If that is also null, then [ThemeData.disabledColor] is used in
+  /// the disabled state, [ThemeData.toggleableActiveColor] is used in the
+  /// selected state, and [ThemeData.unselectedWidgetColor] is used in the
+  /// default state.
+  final MaterialStateProperty<Color?>? fillColor;
+
+  /// {@template flutter.material.radio.materialTapTargetSize}
+  /// Configures the minimum size of the tap target.
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [RadioThemeData.materialTapTargetSize] is used.
+  /// If that is also null, then the value of [ThemeData.materialTapTargetSize]
+  /// is used.
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialTapTargetSize], for a description of how this affects tap targets.
+  final MaterialTapTargetSize? materialTapTargetSize;
+
+  /// {@template flutter.material.radio.visualDensity}
+  /// Defines how compact the radio's layout will be.
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.material.themedata.visualDensity}
+  ///
+  /// If null, then the value of [RadioThemeData.visualDensity] is used. If that
+  /// is also null, then the value of [ThemeData.visualDensity] is used.
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.visualDensity], which specifies the [visualDensity] for all
+  ///    widgets within a [Theme].
+  final VisualDensity? visualDensity;
+
+  /// The color for the radio's [Material] when it has the input focus.
+  ///
+  /// If [overlayColor] returns a non-null color in the [MaterialState.focused]
+  /// state, it will be used instead.
+  ///
+  /// If null, then the value of [RadioThemeData.overlayColor] is used in the
+  /// focused state. If that is also null, then the value of
+  /// [ThemeData.focusColor] is used.
+  final Color? focusColor;
+
+  /// The color for the radio's [Material] when a pointer is hovering over it.
+  ///
+  /// If [overlayColor] returns a non-null color in the [MaterialState.hovered]
+  /// state, it will be used instead.
+  ///
+  /// If null, then the value of [RadioThemeData.overlayColor] is used in the
+  /// hovered state. If that is also null, then the value of
+  /// [ThemeData.hoverColor] is used.
+  final Color? hoverColor;
+
+  /// {@template flutter.material.radio.overlayColor}
+  /// The color for the checkbox's [Material].
+  ///
+  /// Resolves in the following states:
+  ///  * [MaterialState.pressed].
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [activeColor] with alpha
+  /// [kRadialReactionAlpha], [focusColor] and [hoverColor] is used in the
+  /// pressed, focused and hovered state. If that is also null,
+  /// the value of [RadioThemeData.overlayColor] is used. If that is also null,
+  /// then the value of [ThemeData.toggleableActiveColor] with alpha
+  /// [kRadialReactionAlpha], [ThemeData.focusColor] and [ThemeData.hoverColor]
+  /// is used in the pressed, focused and hovered state.
+  final MaterialStateProperty<Color?>? overlayColor;
+
+  /// {@template flutter.material.radio.splashRadius}
+  /// The splash radius of the circular [Material] ink response.
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [RadioThemeData.splashRadius] is used. If that
+  /// is also null, then [kRadialReactionRadius] is used.
+  final double? splashRadius;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  @override
+  _RadioState<T> createState() => _RadioState<T>();
+}
+
+class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
+  bool get enabled => widget.onChanged != null;
+  late Map<Type, Action<Intent>> _actionMap;
+
+  @override
+  void initState() {
+    super.initState();
+    _actionMap = <Type, Action<Intent>>{
+      ActivateIntent: CallbackAction<ActivateIntent>(
+        onInvoke: _actionHandler,
+      ),
+    };
+  }
+
+  void _actionHandler(ActivateIntent intent) {
+    if (widget.onChanged != null) {
+      widget.onChanged!(widget.value);
+    }
+    final RenderObject renderObject = context.findRenderObject()!;
+    renderObject.sendSemanticsEvent(const TapSemanticEvent());
+  }
+
+  bool _focused = false;
+  void _handleHighlightChanged(bool focused) {
+    if (_focused != focused) {
+      setState(() { _focused = focused; });
+    }
+  }
+
+  bool _hovering = false;
+  void _handleHoverChanged(bool hovering) {
+    if (_hovering != hovering) {
+      setState(() { _hovering = hovering; });
+    }
+  }
+
+  void _handleChanged(bool? selected) {
+    if (selected == null) {
+      widget.onChanged!(null);
+      return;
+    }
+    if (selected) {
+      widget.onChanged!(widget.value);
+    }
+  }
+
+  bool get _selected => widget.value == widget.groupValue;
+
+  Set<MaterialState> get _states => <MaterialState>{
+    if (!enabled) MaterialState.disabled,
+    if (_hovering) MaterialState.hovered,
+    if (_focused) MaterialState.focused,
+    if (_selected) MaterialState.selected,
+  };
+
+  MaterialStateProperty<Color?> get _widgetFillColor {
+    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.disabled)) {
+        return null;
+      }
+      if (states.contains(MaterialState.selected)) {
+        return widget.activeColor;
+      }
+      return null;
+    });
+  }
+
+  MaterialStateProperty<Color> get _defaultFillColor {
+    final ThemeData themeData = Theme.of(context);
+    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.disabled)) {
+        return themeData.disabledColor;
+      }
+      if (states.contains(MaterialState.selected)) {
+        return themeData.toggleableActiveColor;
+      }
+      return themeData.unselectedWidgetColor;
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    final ThemeData themeData = Theme.of(context);
+    final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize
+      ?? themeData.radioTheme.materialTapTargetSize
+      ?? themeData.materialTapTargetSize;
+    final VisualDensity effectiveVisualDensity = widget.visualDensity
+      ?? themeData.radioTheme.visualDensity
+      ?? themeData.visualDensity;
+    Size size;
+    switch (effectiveMaterialTapTargetSize) {
+      case MaterialTapTargetSize.padded:
+        size = const Size(kMinInteractiveDimension, kMinInteractiveDimension);
+        break;
+      case MaterialTapTargetSize.shrinkWrap:
+        size = const Size(kMinInteractiveDimension - 8.0, kMinInteractiveDimension - 8.0);
+        break;
+    }
+    size += effectiveVisualDensity.baseSizeAdjustment;
+    final BoxConstraints additionalConstraints = BoxConstraints.tight(size);
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, _states)
+      ?? themeData.radioTheme.mouseCursor?.resolve(_states)
+      ?? MaterialStateProperty.resolveAs<MouseCursor>(MaterialStateMouseCursor.clickable, _states);
+
+    // Colors need to be resolved in selected and non selected states separately
+    // so that they can be lerped between.
+    final Set<MaterialState> activeStates = _states..add(MaterialState.selected);
+    final Set<MaterialState> inactiveStates = _states..remove(MaterialState.selected);
+    final Color effectiveActiveColor = widget.fillColor?.resolve(activeStates)
+      ?? _widgetFillColor.resolve(activeStates)
+      ?? themeData.radioTheme.fillColor?.resolve(activeStates)
+      ?? _defaultFillColor.resolve(activeStates);
+    final Color effectiveInactiveColor = widget.fillColor?.resolve(inactiveStates)
+      ?? _widgetFillColor.resolve(inactiveStates)
+      ?? themeData.radioTheme.fillColor?.resolve(inactiveStates)
+      ?? _defaultFillColor.resolve(inactiveStates);
+
+    final Set<MaterialState> focusedStates = _states..add(MaterialState.focused);
+    final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
+      ?? widget.focusColor
+      ?? themeData.radioTheme.overlayColor?.resolve(focusedStates)
+      ?? themeData.focusColor;
+
+    final Set<MaterialState> hoveredStates = _states..add(MaterialState.hovered);
+    final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
+        ?? widget.hoverColor
+        ?? themeData.radioTheme.overlayColor?.resolve(hoveredStates)
+        ?? themeData.hoverColor;
+
+    final Set<MaterialState> activePressedStates = activeStates..add(MaterialState.pressed);
+    final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates)
+        ?? themeData.radioTheme.overlayColor?.resolve(activePressedStates)
+        ?? effectiveActiveColor.withAlpha(kRadialReactionAlpha);
+
+    final Set<MaterialState> inactivePressedStates = inactiveStates..add(MaterialState.pressed);
+    final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates)
+        ?? themeData.radioTheme.overlayColor?.resolve(inactivePressedStates)
+        ?? effectiveActiveColor.withAlpha(kRadialReactionAlpha);
+
+
+    return FocusableActionDetector(
+      actions: _actionMap,
+      focusNode: widget.focusNode,
+      autofocus: widget.autofocus,
+      mouseCursor: effectiveMouseCursor,
+      enabled: enabled,
+      onShowFocusHighlight: _handleHighlightChanged,
+      onShowHoverHighlight: _handleHoverChanged,
+      child: Builder(
+        builder: (BuildContext context) {
+          return _RadioRenderObjectWidget(
+            selected: _selected,
+            activeColor: effectiveActiveColor,
+            inactiveColor: effectiveInactiveColor,
+            focusColor: effectiveFocusOverlayColor,
+            hoverColor: effectiveHoverOverlayColor,
+            reactionColor: effectiveActivePressedOverlayColor,
+            inactiveReactionColor: effectiveInactivePressedOverlayColor,
+            splashRadius: widget.splashRadius ?? themeData.radioTheme.splashRadius ?? kRadialReactionRadius,
+            onChanged: enabled ? _handleChanged : null,
+            toggleable: widget.toggleable,
+            additionalConstraints: additionalConstraints,
+            vsync: this,
+            hasFocus: _focused,
+            hovering: _hovering,
+          );
+        },
+      ),
+    );
+  }
+}
+
+class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
+  const _RadioRenderObjectWidget({
+    Key? key,
+    required this.selected,
+    required this.activeColor,
+    required this.inactiveColor,
+    required this.focusColor,
+    required this.hoverColor,
+    required this.reactionColor,
+    required this.inactiveReactionColor,
+    required this.additionalConstraints,
+    this.onChanged,
+    required this.toggleable,
+    required this.vsync,
+    required this.hasFocus,
+    required this.hovering,
+    required this.splashRadius,
+  }) : assert(selected != null),
+       assert(activeColor != null),
+       assert(inactiveColor != null),
+       assert(vsync != null),
+       assert(toggleable != null),
+       super(key: key);
+
+  final bool selected;
+  final bool hasFocus;
+  final bool hovering;
+  final Color inactiveColor;
+  final Color activeColor;
+  final Color focusColor;
+  final Color hoverColor;
+  final Color reactionColor;
+  final Color inactiveReactionColor;
+  final double splashRadius;
+  final ValueChanged<bool?>? onChanged;
+  final bool toggleable;
+  final TickerProvider vsync;
+  final BoxConstraints additionalConstraints;
+
+  @override
+  _RenderRadio createRenderObject(BuildContext context) => _RenderRadio(
+    value: selected,
+    activeColor: activeColor,
+    inactiveColor: inactiveColor,
+    focusColor: focusColor,
+    hoverColor: hoverColor,
+    reactionColor: reactionColor,
+    inactiveReactionColor: inactiveReactionColor,
+    splashRadius: splashRadius,
+    onChanged: onChanged,
+    tristate: toggleable,
+    vsync: vsync,
+    additionalConstraints: additionalConstraints,
+    hasFocus: hasFocus,
+    hovering: hovering,
+  );
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderRadio renderObject) {
+    renderObject
+      ..value = selected
+      ..activeColor = activeColor
+      ..inactiveColor = inactiveColor
+      ..focusColor = focusColor
+      ..hoverColor = hoverColor
+      ..reactionColor = reactionColor
+      ..inactiveReactionColor = inactiveReactionColor
+      ..splashRadius = splashRadius
+      ..onChanged = onChanged
+      ..tristate = toggleable
+      ..additionalConstraints = additionalConstraints
+      ..vsync = vsync
+      ..hasFocus = hasFocus
+      ..hovering = hovering;
+  }
+}
+
+class _RenderRadio extends RenderToggleable {
+  _RenderRadio({
+    required bool value,
+    required Color activeColor,
+    required Color inactiveColor,
+    required Color focusColor,
+    required Color hoverColor,
+    required Color reactionColor,
+    required Color inactiveReactionColor,
+    required double splashRadius,
+    required ValueChanged<bool?>? onChanged,
+    required bool tristate,
+    required BoxConstraints additionalConstraints,
+    required TickerProvider vsync,
+    required bool hasFocus,
+    required bool hovering,
+  }) : super(
+         value: value,
+         activeColor: activeColor,
+         inactiveColor: inactiveColor,
+         focusColor: focusColor,
+         hoverColor: hoverColor,
+         reactionColor: reactionColor,
+         inactiveReactionColor: inactiveReactionColor,
+         splashRadius: splashRadius,
+         onChanged: onChanged,
+         tristate: tristate,
+         additionalConstraints: additionalConstraints,
+         vsync: vsync,
+         hasFocus: hasFocus,
+         hovering: hovering,
+       );
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final Canvas canvas = context.canvas;
+
+    paintRadialReaction(canvas, offset, size.center(Offset.zero));
+
+    final Offset center = (offset & size).center;
+
+    // Outer circle
+    final Paint paint = Paint()
+      ..color = Color.lerp(inactiveColor, activeColor, position.value)!
+      ..style = PaintingStyle.stroke
+      ..strokeWidth = 2.0;
+    canvas.drawCircle(center, _kOuterRadius, paint);
+
+    // Inner circle
+    if (!position.isDismissed) {
+      paint.style = PaintingStyle.fill;
+      canvas.drawCircle(center, _kInnerRadius * position.value, paint);
+    }
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    config
+      ..isInMutuallyExclusiveGroup = true
+      ..isChecked = value == true;
+  }
+}
diff --git a/lib/src/material/radio_list_tile.dart b/lib/src/material/radio_list_tile.dart
new file mode 100644
index 0000000..8015dfc
--- /dev/null
+++ b/lib/src/material/radio_list_tile.dart
@@ -0,0 +1,552 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'list_tile.dart';
+import 'radio.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// void setState(VoidCallback fn) { }
+
+/// A [ListTile] with a [Radio]. In other words, a radio button with a label.
+///
+/// The entire list tile is interactive: tapping anywhere in the tile selects
+/// the radio button.
+///
+/// The [value], [groupValue], [onChanged], and [activeColor] properties of this
+/// widget are identical to the similarly-named properties on the [Radio]
+/// widget. The type parameter `T` serves the same purpose as that of the
+/// [Radio] class' type parameter.
+///
+/// The [title], [subtitle], [isThreeLine], and [dense] properties are like
+/// those of the same name on [ListTile].
+///
+/// The [selected] property on this widget is similar to the [ListTile.selected]
+/// property, but the color used is that described by [activeColor], if any,
+/// defaulting to the accent color of the current [Theme]. No effort is made to
+/// coordinate the [selected] state and the [checked] state; to have the list
+/// tile appear selected when the radio button is the selected radio button, set
+/// [selected] to true when [value] matches [groupValue].
+///
+/// The radio button is shown on the left by default in left-to-right languages
+/// (i.e. the leading edge). This can be changed using [controlAffinity]. The
+/// [secondary] widget is placed on the opposite side. This maps to the
+/// [ListTile.leading] and [ListTile.trailing] properties of [ListTile].
+///
+/// To show the [RadioListTile] as disabled, pass null as the [onChanged]
+/// callback.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+///
+/// ![RadioListTile sample](https://flutter.github.io/assets-for-api-docs/assets/material/radio_list_tile.png)
+///
+/// This widget shows a pair of radio buttons that control the `_character`
+/// field. The field is of the type `SingingCharacter`, an enum.
+///
+/// ```dart preamble
+/// enum SingingCharacter { lafayette, jefferson }
+/// ```
+/// ```dart
+/// SingingCharacter _character = SingingCharacter.lafayette;
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Column(
+///     children: <Widget>[
+///       RadioListTile<SingingCharacter>(
+///         title: const Text('Lafayette'),
+///         value: SingingCharacter.lafayette,
+///         groupValue: _character,
+///         onChanged: (SingingCharacter value) { setState(() { _character = value; }); },
+///       ),
+///       RadioListTile<SingingCharacter>(
+///         title: const Text('Thomas Jefferson'),
+///         value: SingingCharacter.jefferson,
+///         groupValue: _character,
+///         onChanged: (SingingCharacter value) { setState(() { _character = value; }); },
+///       ),
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ## Semantics in RadioListTile
+///
+/// Since the entirety of the RadioListTile is interactive, it should represent
+/// itself as a single interactive entity.
+///
+/// To do so, a RadioListTile widget wraps its children with a [MergeSemantics]
+/// widget. [MergeSemantics] will attempt to merge its descendant [Semantics]
+/// nodes into one node in the semantics tree. Therefore, RadioListTile will
+/// throw an error if any of its children requires its own [Semantics] node.
+///
+/// For example, you cannot nest a [RichText] widget as a descendant of
+/// RadioListTile. [RichText] has an embedded gesture recognizer that
+/// requires its own [Semantics] node, which directly conflicts with
+/// RadioListTile's desire to merge all its descendants' semantic nodes
+/// into one. Therefore, it may be necessary to create a custom radio tile
+/// widget to accommodate similar use cases.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+///
+/// ![Radio list tile semantics sample](https://flutter.github.io/assets-for-api-docs/assets/material/radio_list_tile_semantics.png)
+///
+/// Here is an example of a custom labeled radio widget, called
+/// LinkedLabelRadio, that includes an interactive [RichText] widget that
+/// handles tap gestures.
+///
+/// ```dart imports
+/// import 'package:flute/gestures.dart';
+/// ```
+/// ```dart preamble
+/// class LinkedLabelRadio extends StatelessWidget {
+///   const LinkedLabelRadio({
+///     this.label,
+///     this.padding,
+///     this.groupValue,
+///     this.value,
+///     this.onChanged,
+///   });
+///
+///   final String label;
+///   final EdgeInsets padding;
+///   final bool groupValue;
+///   final bool value;
+///   final Function onChanged;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Padding(
+///       padding: padding,
+///       child: Row(
+///         children: <Widget>[
+///           Radio<bool>(
+///             groupValue: groupValue,
+///             value: value,
+///             onChanged: (bool newValue) {
+///               onChanged(newValue);
+///             }
+///           ),
+///           RichText(
+///             text: TextSpan(
+///               text: label,
+///               style: TextStyle(
+///                 color: Colors.blueAccent,
+///                 decoration: TextDecoration.underline,
+///               ),
+///               recognizer: TapGestureRecognizer()
+///                 ..onTap = () {
+///                 print('Label has been tapped.');
+///               },
+///             ),
+///           ),
+///         ],
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// ```dart
+/// bool _isRadioSelected = false;
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: Column(
+///       mainAxisAlignment: MainAxisAlignment.center,
+///       children: <Widget>[
+///         LinkedLabelRadio(
+///           label: 'First tappable label text',
+///           padding: EdgeInsets.symmetric(horizontal: 5.0),
+///           value: true,
+///           groupValue: _isRadioSelected,
+///           onChanged: (bool newValue) {
+///             setState(() {
+///               _isRadioSelected = newValue;
+///             });
+///           },
+///         ),
+///         LinkedLabelRadio(
+///           label: 'Second tappable label text',
+///           padding: EdgeInsets.symmetric(horizontal: 5.0),
+///           value: false,
+///           groupValue: _isRadioSelected,
+///           onChanged: (bool newValue) {
+///             setState(() {
+///               _isRadioSelected = newValue;
+///             });
+///           },
+///         ),
+///       ],
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ## RadioListTile isn't exactly what I want
+///
+/// If the way RadioListTile pads and positions its elements isn't quite what
+/// you're looking for, you can create custom labeled radio widgets by
+/// combining [Radio] with other widgets, such as [Text], [Padding] and
+/// [InkWell].
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+///
+/// ![Custom radio list tile sample](https://flutter.github.io/assets-for-api-docs/assets/material/radio_list_tile_custom.png)
+///
+/// Here is an example of a custom LabeledRadio widget, but you can easily
+/// make your own configurable widget.
+///
+/// ```dart preamble
+/// class LabeledRadio extends StatelessWidget {
+///   const LabeledRadio({
+///     this.label,
+///     this.padding,
+///     this.groupValue,
+///     this.value,
+///     this.onChanged,
+///   });
+///
+///   final String label;
+///   final EdgeInsets padding;
+///   final bool groupValue;
+///   final bool value;
+///   final Function onChanged;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return InkWell(
+///       onTap: () {
+///         if (value != groupValue)
+///           onChanged(value);
+///       },
+///       child: Padding(
+///         padding: padding,
+///         child: Row(
+///           children: <Widget>[
+///             Radio<bool>(
+///               groupValue: groupValue,
+///               value: value,
+///               onChanged: (bool newValue) {
+///                 onChanged(newValue);
+///               },
+///             ),
+///             Text(label),
+///           ],
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// ```dart
+/// bool _isRadioSelected = false;
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: Column(
+///       mainAxisAlignment: MainAxisAlignment.center,
+///       children: <LabeledRadio>[
+///         LabeledRadio(
+///           label: 'This is the first label text',
+///           padding: const EdgeInsets.symmetric(horizontal: 5.0),
+///           value: true,
+///           groupValue: _isRadioSelected,
+///           onChanged: (bool newValue) {
+///             setState(() {
+///               _isRadioSelected = newValue;
+///             });
+///           },
+///         ),
+///         LabeledRadio(
+///           label: 'This is the second label text',
+///           padding: const EdgeInsets.symmetric(horizontal: 5.0),
+///           value: false,
+///           groupValue: _isRadioSelected,
+///           onChanged: (bool newValue) {
+///             setState(() {
+///               _isRadioSelected = newValue;
+///             });
+///           },
+///         ),
+///       ],
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ListTileTheme], which can be used to affect the style of list tiles,
+///    including radio list tiles.
+///  * [CheckboxListTile], a similar widget for checkboxes.
+///  * [SwitchListTile], a similar widget for switches.
+///  * [ListTile] and [Radio], the widgets from which this widget is made.
+class RadioListTile<T> extends StatelessWidget {
+  /// Creates a combination of a list tile and a radio button.
+  ///
+  /// The radio tile itself does not maintain any state. Instead, when the radio
+  /// button is selected, the widget calls the [onChanged] callback. Most
+  /// widgets that use a radio button will listen for the [onChanged] callback
+  /// and rebuild the radio tile with a new [groupValue] to update the visual
+  /// appearance of the radio button.
+  ///
+  /// The following arguments are required:
+  ///
+  /// * [value] and [groupValue] together determine whether the radio button is
+  ///   selected.
+  /// * [onChanged] is called when the user selects this radio button.
+  const RadioListTile({
+    Key? key,
+    required this.value,
+    required this.groupValue,
+    required this.onChanged,
+    this.toggleable = false,
+    this.activeColor,
+    this.title,
+    this.subtitle,
+    this.isThreeLine = false,
+    this.dense,
+    this.secondary,
+    this.selected = false,
+    this.controlAffinity = ListTileControlAffinity.platform,
+    this.autofocus = false,
+    this.contentPadding,
+    this.shape,
+    this.tileColor,
+    this.selectedTileColor,
+  }) : assert(toggleable != null),
+       assert(isThreeLine != null),
+       assert(!isThreeLine || subtitle != null),
+       assert(selected != null),
+       assert(controlAffinity != null),
+       assert(autofocus != null),
+       super(key: key);
+
+  /// The value represented by this radio button.
+  final T value;
+
+  /// The currently selected value for this group of radio buttons.
+  ///
+  /// This radio button is considered selected if its [value] matches the
+  /// [groupValue].
+  final T? groupValue;
+
+  /// Called when the user selects this radio button.
+  ///
+  /// The radio button passes [value] as a parameter to this callback. The radio
+  /// button does not actually change state until the parent widget rebuilds the
+  /// radio tile with the new [groupValue].
+  ///
+  /// If null, the radio button will be displayed as disabled.
+  ///
+  /// The provided callback will not be invoked if this radio button is already
+  /// selected.
+  ///
+  /// The callback provided to [onChanged] should update the state of the parent
+  /// [StatefulWidget] using the [State.setState] method, so that the parent
+  /// gets rebuilt; for example:
+  ///
+  /// ```dart
+  /// RadioListTile<SingingCharacter>(
+  ///   title: const Text('Lafayette'),
+  ///   value: SingingCharacter.lafayette,
+  ///   groupValue: _character,
+  ///   onChanged: (SingingCharacter newValue) {
+  ///     setState(() {
+  ///       _character = newValue;
+  ///     });
+  ///   },
+  /// )
+  /// ```
+  final ValueChanged<T?>? onChanged;
+
+  /// Set to true if this radio list tile is allowed to be returned to an
+  /// indeterminate state by selecting it again when selected.
+  ///
+  /// To indicate returning to an indeterminate state, [onChanged] will be
+  /// called with null.
+  ///
+  /// If true, [onChanged] can be called with [value] when selected while
+  /// [groupValue] != [value], or with null when selected again while
+  /// [groupValue] == [value].
+  ///
+  /// If false, [onChanged] will be called with [value] when it is selected
+  /// while [groupValue] != [value], and only by selecting another radio button
+  /// in the group (i.e. changing the value of [groupValue]) can this radio
+  /// list tile be unselected.
+  ///
+  /// The default is false.
+  ///
+  /// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+  /// This example shows how to enable deselecting a radio button by setting the
+  /// [toggleable] attribute.
+  ///
+  /// ```dart
+  /// int groupValue;
+  /// static const List<String> selections = <String>[
+  ///   'Hercules Mulligan',
+  ///   'Eliza Hamilton',
+  ///   'Philip Schuyler',
+  ///   'Maria Reynolds',
+  ///   'Samuel Seabury',
+  /// ];
+  ///
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return Scaffold(
+  ///     body: ListView.builder(
+  ///       itemBuilder: (context, index) {
+  ///         return RadioListTile<int>(
+  ///           value: index,
+  ///           groupValue: groupValue,
+  ///           toggleable: true,
+  ///           title: Text(selections[index]),
+  ///           onChanged: (int value) {
+  ///             setState(() {
+  ///               groupValue = value;
+  ///             });
+  ///           },
+  ///         );
+  ///       },
+  ///       itemCount: selections.length,
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  final bool toggleable;
+
+  /// The color to use when this radio button is selected.
+  ///
+  /// Defaults to accent color of the current [Theme].
+  final Color? activeColor;
+
+  /// The primary content of the list tile.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? title;
+
+  /// Additional content displayed below the title.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? subtitle;
+
+  /// A widget to display on the opposite side of the tile from the radio button.
+  ///
+  /// Typically an [Icon] widget.
+  final Widget? secondary;
+
+  /// Whether this list tile is intended to display three lines of text.
+  ///
+  /// If false, the list tile is treated as having one line if the subtitle is
+  /// null and treated as having two lines if the subtitle is non-null.
+  final bool isThreeLine;
+
+  /// Whether this list tile is part of a vertically dense list.
+  ///
+  /// If this property is null then its value is based on [ListTileTheme.dense].
+  final bool? dense;
+
+  /// Whether to render icons and text in the [activeColor].
+  ///
+  /// No effort is made to automatically coordinate the [selected] state and the
+  /// [checked] state. To have the list tile appear selected when the radio
+  /// button is the selected radio button, set [selected] to true when [value]
+  /// matches [groupValue].
+  ///
+  /// Normally, this property is left to its default value, false.
+  final bool selected;
+
+  /// Where to place the control relative to the text.
+  final ListTileControlAffinity controlAffinity;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  /// Defines the insets surrounding the contents of the tile.
+  ///
+  /// Insets the [Radio], [title], [subtitle], and [secondary] widgets
+  /// in [RadioListTile].
+  ///
+  /// When null, `EdgeInsets.symmetric(horizontal: 16.0)` is used.
+  final EdgeInsetsGeometry? contentPadding;
+
+  /// Whether this radio button is checked.
+  ///
+  /// To control this value, set [value] and [groupValue] appropriately.
+  bool get checked => value == groupValue;
+
+  /// If specified, [shape] defines the shape of the [RadioListTile]'s [InkWell] border.
+  final ShapeBorder? shape;
+
+  /// If specified, defines the background color for `RadioListTile` when
+  /// [RadioListTile.selected] is false.
+  final Color? tileColor;
+
+  /// If non-null, defines the background color when [RadioListTile.selected] is true.
+  final Color? selectedTileColor;
+
+  @override
+  Widget build(BuildContext context) {
+    final Widget control = Radio<T>(
+      value: value,
+      groupValue: groupValue,
+      onChanged: onChanged,
+      toggleable: toggleable,
+      activeColor: activeColor,
+      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+      autofocus: autofocus,
+    );
+    Widget? leading, trailing;
+    switch (controlAffinity) {
+      case ListTileControlAffinity.leading:
+      case ListTileControlAffinity.platform:
+        leading = control;
+        trailing = secondary;
+        break;
+      case ListTileControlAffinity.trailing:
+        leading = secondary;
+        trailing = control;
+        break;
+    }
+    return MergeSemantics(
+      child: ListTileTheme.merge(
+        selectedColor: activeColor ?? Theme.of(context).accentColor,
+        child: ListTile(
+          leading: leading,
+          title: title,
+          subtitle: subtitle,
+          trailing: trailing,
+          isThreeLine: isThreeLine,
+          dense: dense,
+          enabled: onChanged != null,
+          shape: shape,
+          tileColor: tileColor,
+          selectedTileColor: selectedTileColor,
+          onTap: onChanged != null ? () {
+            if (toggleable && checked) {
+              onChanged!(null);
+              return;
+            }
+            if (!checked) {
+              onChanged!(value);
+            }
+          } : null,
+          selected: selected,
+          autofocus: autofocus,
+          contentPadding: contentPadding,
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/radio_theme.dart b/lib/src/material/radio_theme.dart
new file mode 100644
index 0000000..d8a7b0c
--- /dev/null
+++ b/lib/src/material/radio_theme.dart
@@ -0,0 +1,216 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'material_state.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// Defines default property values for descendant [Radio] widgets.
+///
+/// Descendant widgets obtain the current [RadioThemeData] object using
+/// `RadioTheme.of(context)`. Instances of [RadioThemeData] can be customized
+/// with [RadioThemeData.copyWith].
+///
+/// Typically a [RadioThemeData] is specified as part of the overall [Theme]
+/// with [ThemeData.radioTheme].
+///
+/// All [RadioThemeData] properties are `null` by default. When null, the
+/// [Radio] will use the values from [ThemeData] if they exist, otherwise it
+/// will provide its own defaults based on the overall [Theme]'s colorScheme.
+/// See the individual [Radio] properties for details.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class RadioThemeData with Diagnosticable {
+  /// Creates a theme that can be used for [ThemeData.radioTheme].
+  const RadioThemeData({
+    this.mouseCursor,
+    this.fillColor,
+    this.overlayColor,
+    this.splashRadius,
+    this.materialTapTargetSize,
+    this.visualDensity,
+  });
+
+  /// {@macro flutter.material.radio.mouseCursor}
+  ///
+  /// If specified, overrides the default value of [Radio.mouseCursor].
+  final MaterialStateProperty<MouseCursor?>? mouseCursor;
+
+  /// {@macro flutter.material.radio.fillColor}
+  ///
+  /// If specified, overrides the default value of [Radio.fillColor].
+  final MaterialStateProperty<Color?>? fillColor;
+
+  /// {@macro flutter.material.radio.overlayColor}
+  ///
+  /// If specified, overrides the default value of [Radio.overlayColor].
+  final MaterialStateProperty<Color?>? overlayColor;
+
+  /// {@macro flutter.material.radio.splashRadius}
+  ///
+  /// If specified, overrides the default value of [Radio.splashRadius].
+  final double? splashRadius;
+
+  /// {@macro flutter.material.radio.materialTapTargetSize}
+  ///
+  /// If specified, overrides the default value of
+  /// [Radio.materialTapTargetSize].
+  final MaterialTapTargetSize? materialTapTargetSize;
+
+  /// {@macro flutter.material.radio.visualDensity}
+  ///
+  /// If specified, overrides the default value of [Radio.visualDensity].
+  final VisualDensity? visualDensity;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  RadioThemeData copyWith({
+    MaterialStateProperty<MouseCursor?>? mouseCursor,
+    MaterialStateProperty<Color?>? fillColor,
+    MaterialStateProperty<Color?>? overlayColor,
+    double? splashRadius,
+    MaterialTapTargetSize? materialTapTargetSize,
+    VisualDensity? visualDensity,
+  }) {
+    return RadioThemeData(
+      mouseCursor: mouseCursor ?? this.mouseCursor,
+      fillColor: fillColor ?? this.fillColor,
+      overlayColor: overlayColor ?? this.overlayColor,
+      splashRadius: splashRadius ?? this.splashRadius,
+      materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
+      visualDensity: visualDensity ?? this.visualDensity,
+    );
+  }
+
+  /// Linearly interpolate between two [RadioThemeData]s.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static RadioThemeData lerp(RadioThemeData? a, RadioThemeData? b, double t) {
+    return RadioThemeData(
+      mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
+      fillColor: _lerpProperties<Color?>(a?.fillColor, b?.fillColor, t, Color.lerp),
+      materialTapTargetSize: t < 0.5 ? a?.materialTapTargetSize : b?.materialTapTargetSize,
+      overlayColor: _lerpProperties<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
+      splashRadius: lerpDouble(a?.splashRadius, b?.splashRadius, t),
+      visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      mouseCursor,
+      fillColor,
+      overlayColor,
+      splashRadius,
+      materialTapTargetSize,
+      visualDensity,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is RadioThemeData
+      && other.mouseCursor == mouseCursor
+      && other.fillColor == fillColor
+      && other.overlayColor == overlayColor
+      && other.splashRadius == splashRadius
+      && other.materialTapTargetSize == materialTapTargetSize
+      && other.visualDensity == visualDensity;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('fillColor', fillColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
+    properties.add(DoubleProperty('splashRadius', splashRadius, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
+    properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
+  }
+
+  static MaterialStateProperty<T>? _lerpProperties<T>(
+    MaterialStateProperty<T>? a,
+    MaterialStateProperty<T>? b,
+    double t,
+    T Function(T?, T?, double) lerpFunction,
+  ) {
+    // Avoid creating a _LerpProperties object for a common case.
+    if (a == null && b == null)
+      return null;
+    return _LerpProperties<T>(a, b, t, lerpFunction);
+  }
+}
+
+class _LerpProperties<T> implements MaterialStateProperty<T> {
+  const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
+
+  final MaterialStateProperty<T>? a;
+  final MaterialStateProperty<T>? b;
+  final double t;
+  final T Function(T?, T?, double) lerpFunction;
+
+  @override
+  T resolve(Set<MaterialState> states) {
+    final T? resolvedA = a?.resolve(states);
+    final T? resolvedB = b?.resolve(states);
+    return lerpFunction(resolvedA, resolvedB, t);
+  }
+}
+
+/// Applies a radio theme to descendant [Radio] widgets.
+///
+/// Descendant widgets obtain the current theme's [RadioTheme] object using
+/// [RadioTheme.of]. When a widget uses [RadioTheme.of], it is automatically
+/// rebuilt if the theme later changes.
+///
+/// A radio theme can be specified as part of the overall Material theme using
+/// [ThemeData.radioTheme].
+///
+/// See also:
+///
+///  * [RadioThemeData], which describes the actual configuration of a radio
+///    theme.
+class RadioTheme extends InheritedWidget {
+  /// Constructs a radio theme that configures all descendant [Radio] widgets.
+  const RadioTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : super(key: key, child: child);
+
+  /// The properties used for all descendant [Radio] widgets.
+  final RadioThemeData data;
+
+  /// Returns the configuration [data] from the closest [RadioTheme] ancestor.
+  /// If there is no ancestor, it returns [ThemeData.radioTheme].
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// RadioThemeData theme = RadioTheme.of(context);
+  /// ```
+  static RadioThemeData of(BuildContext context) {
+    final RadioTheme? radioTheme = context.dependOnInheritedWidgetOfExactType<RadioTheme>();
+    return radioTheme?.data ?? Theme.of(context).radioTheme;
+  }
+
+  @override
+  bool updateShouldNotify(RadioTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/raised_button.dart b/lib/src/material/raised_button.dart
new file mode 100644
index 0000000..69f80df
--- /dev/null
+++ b/lib/src/material/raised_button.dart
@@ -0,0 +1,347 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'button.dart';
+import 'button_theme.dart';
+import 'material_button.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// A material design "raised button".
+///
+/// ### This class is obsolete, please use [ElevatedButton] instead.
+///
+/// FlatButton, RaisedButton, and OutlineButton have been replaced by
+/// TextButton, ElevatedButton, and OutlinedButton respectively.
+/// ButtonTheme has been replaced by TextButtonTheme,
+/// ElevatedButtonTheme, and OutlinedButtonTheme. The original classes
+/// will be deprecated soon, please migrate code that uses them.
+/// There's a detailed migration guide for the new button and button
+/// theme classes in
+/// [flutter.dev/go/material-button-migration-guide](https://flutter.dev/go/material-button-migration-guide).
+///
+/// A raised button is based on a [Material] widget whose [Material.elevation]
+/// increases when the button is pressed.
+///
+/// Use raised buttons to add dimension to otherwise mostly flat layouts, e.g.
+/// in long busy lists of content, or in wide spaces. Avoid using raised buttons
+/// on already-raised content such as dialogs or cards.
+///
+/// If [onPressed] and [onLongPress] callbacks are null, then the button will be disabled and by
+/// default will resemble a flat button in the [disabledColor]. If you are
+/// trying to change the button's [color] and it is not having any effect, check
+/// that you are passing a non-null [onPressed] or [onLongPress] callbacks.
+///
+/// If you want an ink-splash effect for taps, but don't want to use a button,
+/// consider using [InkWell] directly.
+///
+/// Raised buttons have a minimum size of 88.0 by 36.0 which can be overridden
+/// with [ButtonTheme].
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// This sample shows how to render a disabled RaisedButton, an enabled RaisedButton
+/// and lastly a RaisedButton with gradient background.
+///
+/// ![Three raised buttons, one enabled, another disabled, and the last one
+/// styled with a blue gradient background](https://flutter.github.io/assets-for-api-docs/assets/material/raised_button.png)
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Center(
+///     child: Column(
+///       mainAxisSize: MainAxisSize.min,
+///       children: <Widget>[
+///         const RaisedButton(
+///           onPressed: null,
+///           child: Text(
+///             'Disabled Button',
+///             style: TextStyle(fontSize: 20)
+///           ),
+///         ),
+///         const SizedBox(height: 30),
+///         RaisedButton(
+///           onPressed: () {},
+///           child: const Text(
+///             'Enabled Button',
+///             style: TextStyle(fontSize: 20)
+///           ),
+///         ),
+///         const SizedBox(height: 30),
+///         RaisedButton(
+///           onPressed: () {},
+///           textColor: Colors.white,
+///           padding: const EdgeInsets.all(0.0),
+///           child: Container(
+///             decoration: const BoxDecoration(
+///               gradient: LinearGradient(
+///                 colors: <Color>[
+///                   Color(0xFF0D47A1),
+///                   Color(0xFF1976D2),
+///                   Color(0xFF42A5F5),
+///                 ],
+///               ),
+///             ),
+///             padding: const EdgeInsets.all(10.0),
+///             child: const Text(
+///               'Gradient Button',
+///               style: TextStyle(fontSize: 20)
+///             ),
+///           ),
+///         ),
+///       ],
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [FlatButton], a material design button without a shadow.
+///  * [DropdownButton], a button that shows options to select from.
+///  * [FloatingActionButton], the round button in material applications.
+///  * [IconButton], to create buttons that just contain icons.
+///  * [InkWell], which implements the ink splash part of a flat button.
+///  * [RawMaterialButton], the widget this widget is based on.
+///  * <https://material.io/design/components/buttons.html>
+///  * Cookbook: [Build a form with validation](https://flutter.dev/docs/cookbook/forms/validation)
+class RaisedButton extends MaterialButton {
+  /// Create a filled button.
+  ///
+  /// The [autofocus] and [clipBehavior] arguments must not be null.
+  /// Additionally,  [elevation], [hoverElevation], [focusElevation],
+  /// [highlightElevation], and [disabledElevation] must be non-negative, if
+  /// specified.
+  const RaisedButton({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ValueChanged<bool>? onHighlightChanged,
+    MouseCursor? mouseCursor,
+    ButtonTextTheme? textTheme,
+    Color? textColor,
+    Color? disabledTextColor,
+    Color? color,
+    Color? disabledColor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    Brightness? colorBrightness,
+    double? elevation,
+    double? focusElevation,
+    double? hoverElevation,
+    double? highlightElevation,
+    double? disabledElevation,
+    EdgeInsetsGeometry? padding,
+    VisualDensity? visualDensity,
+    ShapeBorder? shape,
+    Clip clipBehavior = Clip.none,
+    FocusNode? focusNode,
+    bool autofocus = false,
+    MaterialTapTargetSize? materialTapTargetSize,
+    Duration? animationDuration,
+    Widget? child,
+  }) : assert(autofocus != null),
+       assert(elevation == null || elevation >= 0.0),
+       assert(focusElevation == null || focusElevation >= 0.0),
+       assert(hoverElevation == null || hoverElevation >= 0.0),
+       assert(highlightElevation == null || highlightElevation >= 0.0),
+       assert(disabledElevation == null || disabledElevation >= 0.0),
+       assert(clipBehavior != null),
+       super(
+         key: key,
+         onPressed: onPressed,
+         onLongPress: onLongPress,
+         onHighlightChanged: onHighlightChanged,
+         mouseCursor: mouseCursor,
+         textTheme: textTheme,
+         textColor: textColor,
+         disabledTextColor: disabledTextColor,
+         color: color,
+         disabledColor: disabledColor,
+         focusColor: focusColor,
+         hoverColor: hoverColor,
+         highlightColor: highlightColor,
+         splashColor: splashColor,
+         colorBrightness: colorBrightness,
+         elevation: elevation,
+         focusElevation: focusElevation,
+         hoverElevation: hoverElevation,
+         highlightElevation: highlightElevation,
+         disabledElevation: disabledElevation,
+         padding: padding,
+         visualDensity: visualDensity,
+         shape: shape,
+         clipBehavior: clipBehavior,
+         focusNode: focusNode,
+         autofocus: autofocus,
+         materialTapTargetSize: materialTapTargetSize,
+         animationDuration: animationDuration,
+         child: child,
+       );
+
+  /// Create a filled button from a pair of widgets that serve as the button's
+  /// [icon] and [label].
+  ///
+  /// The icon and label are arranged in a row and padded by 12 logical pixels
+  /// at the start, and 16 at the end, with an 8 pixel gap in between.
+  ///
+  /// The [elevation], [highlightElevation], [disabledElevation], [icon],
+  /// [label], and [clipBehavior] arguments must not be null.
+  factory RaisedButton.icon({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ValueChanged<bool>? onHighlightChanged,
+    MouseCursor? mouseCursor,
+    ButtonTextTheme? textTheme,
+    Color? textColor,
+    Color? disabledTextColor,
+    Color? color,
+    Color? disabledColor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    Brightness? colorBrightness,
+    double? elevation,
+    double? highlightElevation,
+    double? disabledElevation,
+    ShapeBorder? shape,
+    Clip clipBehavior,
+    FocusNode? focusNode,
+    bool autofocus,
+    EdgeInsetsGeometry? padding,
+    MaterialTapTargetSize? materialTapTargetSize,
+    Duration? animationDuration,
+    required Widget icon,
+    required Widget label,
+  }) = _RaisedButtonWithIcon;
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final ButtonThemeData buttonTheme = ButtonTheme.of(context);
+    return RawMaterialButton(
+      onPressed: onPressed,
+      onLongPress: onLongPress,
+      onHighlightChanged: onHighlightChanged,
+      mouseCursor: mouseCursor,
+      clipBehavior: clipBehavior,
+      fillColor: buttonTheme.getFillColor(this),
+      textStyle: theme.textTheme.button!.copyWith(color: buttonTheme.getTextColor(this)),
+      focusColor: buttonTheme.getFocusColor(this),
+      hoverColor: buttonTheme.getHoverColor(this),
+      highlightColor: buttonTheme.getHighlightColor(this),
+      splashColor: buttonTheme.getSplashColor(this),
+      elevation: buttonTheme.getElevation(this),
+      focusElevation: buttonTheme.getFocusElevation(this),
+      hoverElevation: buttonTheme.getHoverElevation(this),
+      highlightElevation: buttonTheme.getHighlightElevation(this),
+      disabledElevation: buttonTheme.getDisabledElevation(this),
+      padding: buttonTheme.getPadding(this),
+      visualDensity: visualDensity ?? theme.visualDensity,
+      constraints: buttonTheme.getConstraints(this),
+      shape: buttonTheme.getShape(this),
+      focusNode: focusNode,
+      autofocus: autofocus,
+      animationDuration: buttonTheme.getAnimationDuration(this),
+      materialTapTargetSize: buttonTheme.getMaterialTapTargetSize(this),
+      child: child,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
+    properties.add(DiagnosticsProperty<double>('focusElevation', focusElevation, defaultValue: null));
+    properties.add(DiagnosticsProperty<double>('hoverElevation', hoverElevation, defaultValue: null));
+    properties.add(DiagnosticsProperty<double>('highlightElevation', highlightElevation, defaultValue: null));
+    properties.add(DiagnosticsProperty<double>('disabledElevation', disabledElevation, defaultValue: null));
+  }
+}
+
+/// The type of RaisedButtons created with [RaisedButton.icon].
+///
+/// This class only exists to give RaisedButtons created with [RaisedButton.icon]
+/// a distinct class for the sake of [ButtonTheme]. It can not be instantiated.
+class _RaisedButtonWithIcon extends RaisedButton with MaterialButtonWithIconMixin {
+  _RaisedButtonWithIcon({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ValueChanged<bool>? onHighlightChanged,
+    MouseCursor? mouseCursor,
+    ButtonTextTheme? textTheme,
+    Color? textColor,
+    Color? disabledTextColor,
+    Color? color,
+    Color? disabledColor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    Brightness? colorBrightness,
+    double? elevation,
+    double? highlightElevation,
+    double? disabledElevation,
+    ShapeBorder? shape,
+    Clip clipBehavior = Clip.none,
+    FocusNode? focusNode,
+    bool autofocus = false,
+    EdgeInsetsGeometry? padding,
+    MaterialTapTargetSize? materialTapTargetSize,
+    Duration? animationDuration,
+    required Widget icon,
+    required Widget label,
+  }) : assert(elevation == null || elevation >= 0.0),
+       assert(highlightElevation == null || highlightElevation >= 0.0),
+       assert(disabledElevation == null || disabledElevation >= 0.0),
+       assert(clipBehavior != null),
+       assert(icon != null),
+       assert(label != null),
+       assert(autofocus != null),
+       super(
+         key: key,
+         onPressed: onPressed,
+         onLongPress: onLongPress,
+         onHighlightChanged: onHighlightChanged,
+         mouseCursor: mouseCursor,
+         textTheme: textTheme,
+         textColor: textColor,
+         disabledTextColor: disabledTextColor,
+         color: color,
+         disabledColor: disabledColor,
+         focusColor: focusColor,
+         hoverColor: hoverColor,
+         highlightColor: highlightColor,
+         splashColor: splashColor,
+         colorBrightness: colorBrightness,
+         elevation: elevation,
+         highlightElevation: highlightElevation,
+         disabledElevation: disabledElevation,
+         shape: shape,
+         clipBehavior: clipBehavior,
+         focusNode: focusNode,
+         autofocus: autofocus,
+         padding: padding,
+         materialTapTargetSize: materialTapTargetSize,
+         animationDuration: animationDuration,
+         child: Row(
+           mainAxisSize: MainAxisSize.min,
+           children: <Widget>[
+             icon,
+             const SizedBox(width: 8.0),
+             label,
+           ],
+         ),
+       );
+}
diff --git a/lib/src/material/range_slider.dart b/lib/src/material/range_slider.dart
new file mode 100644
index 0000000..53d9360
--- /dev/null
+++ b/lib/src/material/range_slider.dart
@@ -0,0 +1,1727 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:math' as math;
+import 'package:flute/ui.dart' as ui;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart' show timeDilation;
+import 'package:flute/widgets.dart';
+
+import 'constants.dart';
+import 'debug.dart';
+import 'slider_theme.dart';
+import 'theme.dart';
+
+// Examples can assume:
+// RangeValues _rangeValues = RangeValues(0.3, 0.7);
+// RangeValues _dollarsRange = RangeValues(50, 100);
+// void setState(VoidCallback fn) { }
+
+/// [RangeSlider] uses this callback to paint the value indicator on the overlay.
+/// Since the value indicator is painted on the Overlay; this method paints the
+/// value indicator in a [RenderBox] that appears in the [Overlay].
+typedef PaintRangeValueIndicator = void Function(PaintingContext context, Offset offset);
+
+/// A Material Design range slider.
+///
+/// Used to select a range from a range of values.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=ufb4gIPDmEs}
+///
+/// {@tool dartpad --template=stateful_widget_scaffold}
+///
+/// ![A range slider widget, consisting of 5 divisions and showing the default
+/// value indicator.](https://flutter.github.io/assets-for-api-docs/assets/material/range_slider.png)
+///
+/// This range values are in intervals of 20 because the Range Slider has 5
+/// divisions, from 0 to 100. This means are values are split between 0, 20, 40,
+/// 60, 80, and 100. The range values are initialized with 40 and 80 in this demo.
+///
+/// ```dart
+/// RangeValues _currentRangeValues = const RangeValues(40, 80);
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return RangeSlider(
+///     values: _currentRangeValues,
+///     min: 0,
+///     max: 100,
+///     divisions: 5,
+///     labels: RangeLabels(
+///       _currentRangeValues.start.round().toString(),
+///       _currentRangeValues.end.round().toString(),
+///     ),
+///     onChanged: (RangeValues values) {
+///       setState(() {
+///         _currentRangeValues = values;
+///       });
+///     },
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// A range slider can be used to select from either a continuous or a discrete
+/// set of values. The default is to use a continuous range of values from [min]
+/// to [max]. To use discrete values, use a non-null value for [divisions], which
+/// indicates the number of discrete intervals. For example, if [min] is 0.0 and
+/// [max] is 50.0 and [divisions] is 5, then the slider can take on the
+/// discrete values 0.0, 10.0, 20.0, 30.0, 40.0, and 50.0.
+///
+/// The terms for the parts of a slider are:
+///
+///  * The "thumbs", which are the shapes that slide horizontally when the user
+///    drags them to change the selected range.
+///  * The "track", which is the horizontal line that the thumbs can be dragged
+///    along.
+///  * The "tick marks", which mark the discrete values of a discrete slider.
+///  * The "overlay", which is a highlight that's drawn over a thumb in response
+///    to a user tap-down gesture.
+///  * The "value indicators", which are the shapes that pop up when the user
+///    is dragging a thumb to show the value being selected.
+///  * The "active" segment of the slider is the segment between the two thumbs.
+///  * The "inactive" slider segments are the two track intervals outside of the
+///    slider's thumbs.
+///
+/// The range slider will be disabled if [onChanged] is null or if the range
+/// given by [min]..[max] is empty (i.e. if [min] is equal to [max]).
+///
+/// The range slider widget itself does not maintain any state. Instead, when
+/// the state of the slider changes, the widget calls the [onChanged] callback.
+/// Most widgets that use a range slider will listen for the [onChanged] callback
+/// and rebuild the slider with new [values] to update the visual appearance of
+/// the slider. To know when the value starts to change, or when it is done
+/// changing, set the optional callbacks [onChangeStart] and/or [onChangeEnd].
+///
+/// By default, a slider will be as wide as possible, centered vertically. When
+/// given unbounded constraints, it will attempt to make the track 144 pixels
+/// wide (including margins on each side) and will shrink-wrap vertically.
+///
+/// Requires one of its ancestors to be a [Material] widget. This is typically
+/// provided by a [Scaffold] widget.
+///
+/// Requires one of its ancestors to be a [MediaQuery] widget. Typically, a
+/// [MediaQuery] widget is introduced by the [MaterialApp] or [WidgetsApp]
+/// widget at the top of your application widget tree.
+///
+/// To determine how it should be displayed (e.g. colors, thumb shape, etc.),
+/// a slider uses the [SliderThemeData] available from either a [SliderTheme]
+/// widget, or the [ThemeData.sliderTheme] inside a [Theme] widget above it in
+/// the widget tree. You can also override some of the colors with the
+/// [activeColor] and [inactiveColor] properties, although more fine-grained
+/// control of the colors, and other visual properties is achieved using a
+/// [SliderThemeData].
+///
+/// See also:
+///
+///  * [SliderTheme] and [SliderThemeData] for information about controlling
+///    the visual appearance of the slider.
+///  * [Slider], for a single-valued slider.
+///  * [Radio], for selecting among a set of explicit values.
+///  * [Checkbox] and [Switch], for toggling a particular value on or off.
+///  * <https://material.io/design/components/sliders.html>
+///  * [MediaQuery], from which the text scale factor is obtained.
+class RangeSlider extends StatefulWidget {
+  /// Creates a Material Design range slider.
+  ///
+  /// The range slider widget itself does not maintain any state. Instead, when
+  /// the state of the slider changes, the widget calls the [onChanged] callback.
+  /// Most widgets that use a range slider will listen for the [onChanged] callback
+  /// and rebuild the slider with a new [value] to update the visual appearance of
+  /// the slider. To know when the value starts to change, or when it is done
+  /// changing, set the optional callbacks [onChangeStart] and/or [onChangeEnd].
+  ///
+  /// * [values], which  determines currently selected values for this range
+  ///   slider.
+  /// * [onChanged], which is called while the user is selecting a new value for
+  ///   the range slider.
+  /// * [onChangeStart], which is called when the user starts to select a new
+  ///   value for the range slider.
+  /// * [onChangeEnd], which is called when the user is done selecting a new
+  ///   value for the range slider.
+  ///
+  /// You can override some of the colors with the [activeColor] and
+  /// [inactiveColor] properties, although more fine-grained control of the
+  /// appearance is achieved using a [SliderThemeData].
+  ///
+  /// The [values], [min], [max] must not be null. The [min] must be less than
+  /// or equal to the [max]. [values.start] must be less than or equal to
+  /// [values.end]. [values.start] and [values.end] must be greater than or
+  /// equal to the [min] and less than or equal to the [max]. The [divisions]
+  /// must be null or greater than 0.
+  RangeSlider({
+    Key? key,
+    required this.values,
+    required this.onChanged,
+    this.onChangeStart,
+    this.onChangeEnd,
+    this.min = 0.0,
+    this.max = 1.0,
+    this.divisions,
+    this.labels,
+    this.activeColor,
+    this.inactiveColor,
+    this.semanticFormatterCallback,
+  }) : assert(values != null),
+       assert(min != null),
+       assert(max != null),
+       assert(min <= max),
+       assert(values.start <= values.end),
+       assert(values.start >= min && values.start <= max),
+       assert(values.end >= min && values.end <= max),
+       assert(divisions == null || divisions > 0),
+       super(key: key);
+
+  /// The currently selected values for this range slider.
+  ///
+  /// The slider's thumbs are drawn at horizontal positions that corresponds to
+  /// these values.
+  final RangeValues values;
+
+  /// Called when the user is selecting a new value for the slider by dragging.
+  ///
+  /// The slider passes the new values to the callback but does not actually
+  /// change state until the parent widget rebuilds the slider with the new
+  /// values.
+  ///
+  /// If null, the slider will be displayed as disabled.
+  ///
+  /// The callback provided to [onChanged] should update the state of the parent
+  /// [StatefulWidget] using the [State.setState] method, so that the parent
+  /// gets rebuilt; for example:
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// RangeSlider(
+  ///   values: _rangeValues,
+  ///   min: 1.0,
+  ///   max: 10.0,
+  ///   onChanged: (RangeValues newValues) {
+  ///     setState(() {
+  ///       _rangeValues = newValues;
+  ///     });
+  ///   },
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [onChangeStart], which  is called when the user starts  changing the
+  ///    values.
+  ///  * [onChangeEnd], which is called when the user stops changing the values.
+  final ValueChanged<RangeValues>? onChanged;
+
+  /// Called when the user starts selecting new values for the slider.
+  ///
+  /// This callback shouldn't be used to update the slider [values] (use
+  /// [onChanged] for that). Rather, it should be used to be notified when the
+  /// user has started selecting a new value by starting a drag or with a tap.
+  ///
+  /// The values passed will be the last [values] that the slider had before the
+  /// change began.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// RangeSlider(
+  ///   values: _rangeValues,
+  ///   min: 1.0,
+  ///   max: 10.0,
+  ///   onChanged: (RangeValues newValues) {
+  ///     setState(() {
+  ///       _rangeValues = newValues;
+  ///     });
+  ///   },
+  ///   onChangeStart: (RangeValues startValues) {
+  ///     print('Started change at $startValues');
+  ///   },
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [onChangeEnd] for a callback that is called when the value change is
+  ///    complete.
+  final ValueChanged<RangeValues>? onChangeStart;
+
+  /// Called when the user is done selecting new values for the slider.
+  ///
+  /// This differs from [onChanged] because it is only called once at the end
+  /// of the interaction, while [onChanged] is called as the value is getting
+  /// updated within the interaction.
+  ///
+  /// This callback shouldn't be used to update the slider [values] (use
+  /// [onChanged] for that). Rather, it should be used to know when the user has
+  /// completed selecting a new [values] by ending a drag or a click.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// RangeSlider(
+  ///   values: _rangeValues,
+  ///   min: 1.0,
+  ///   max: 10.0,
+  ///   onChanged: (RangeValues newValues) {
+  ///     setState(() {
+  ///       _rangeValues = newValues;
+  ///     });
+  ///   },
+  ///   onChangeEnd: (RangeValues endValues) {
+  ///     print('Ended change at $endValues');
+  ///   },
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [onChangeStart] for a callback that is called when a value change
+  ///    begins.
+  final ValueChanged<RangeValues>? onChangeEnd;
+
+  /// The minimum value the user can select.
+  ///
+  /// Defaults to 0.0. Must be less than or equal to [max].
+  ///
+  /// If the [max] is equal to the [min], then the slider is disabled.
+  final double min;
+
+  /// The maximum value the user can select.
+  ///
+  /// Defaults to 1.0. Must be greater than or equal to [min].
+  ///
+  /// If the [max] is equal to the [min], then the slider is disabled.
+  final double max;
+
+  /// The number of discrete divisions.
+  ///
+  /// Typically used with [labels] to show the current discrete values.
+  ///
+  /// If null, the slider is continuous.
+  final int? divisions;
+
+  /// Labels to show as text in the [SliderThemeData.rangeValueIndicatorShape].
+  ///
+  /// There are two labels: one for the start thumb and one for the end thumb.
+  ///
+  /// Each label is rendered using the active [ThemeData]'s
+  /// [TextTheme.bodyText1] text style, with the theme data's
+  /// [ColorScheme.onPrimary] color. The label's text style can be overridden
+  /// with [SliderThemeData.valueIndicatorTextStyle].
+  ///
+  /// If null, then the value indicator will not be displayed.
+  ///
+  /// See also:
+  ///
+  ///  * [RangeSliderValueIndicatorShape] for how to create a custom value
+  ///    indicator shape.
+  final RangeLabels? labels;
+
+  /// The color of the track's active segment, i.e. the span of track between
+  /// the thumbs.
+  ///
+  /// Defaults to [ColorScheme.primary].
+  ///
+  /// Using a [SliderTheme] gives more fine-grained control over the
+  /// appearance of various components of the slider.
+  final Color? activeColor;
+
+  /// The color of the track's inactive segments, i.e. the span of tracks
+  /// between the min and the start thumb, and the end thumb and the max.
+  ///
+  /// Defaults to [ColorScheme.primary] with 24% opacity.
+  ///
+  /// Using a [SliderTheme] gives more fine-grained control over the
+  /// appearance of various components of the slider.
+  final Color? inactiveColor;
+
+  /// The callback used to create a semantic value from the slider's values.
+  ///
+  /// Defaults to formatting values as a percentage.
+  ///
+  /// This is used by accessibility frameworks like TalkBack on Android to
+  /// inform users what the currently selected value is with more context.
+  ///
+  /// {@tool snippet}
+  ///
+  /// In the example below, a slider for currency values is configured to
+  /// announce a value with a currency label.
+  ///
+  /// ```dart
+  /// RangeSlider(
+  ///   values: _dollarsRange,
+  ///   min: 20.0,
+  ///   max: 330.0,
+  ///   onChanged: (RangeValues newValues) {
+  ///     setState(() {
+  ///       _dollarsRange = newValues;
+  ///     });
+  ///   },
+  ///   semanticFormatterCallback: (double newValue) {
+  ///     return '${newValue.round()} dollars';
+  ///   }
+  ///  )
+  /// ```
+  /// {@end-tool}
+  final SemanticFormatterCallback? semanticFormatterCallback;
+
+  // Touch width for the tap boundary of the slider thumbs.
+  static const double _minTouchTargetWidth = kMinInteractiveDimension;
+
+  @override
+  _RangeSliderState createState() => _RangeSliderState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('valueStart', values.start));
+    properties.add(DoubleProperty('valueEnd', values.end));
+    properties.add(ObjectFlagProperty<ValueChanged<RangeValues>>('onChanged', onChanged, ifNull: 'disabled'));
+    properties.add(ObjectFlagProperty<ValueChanged<RangeValues>>.has('onChangeStart', onChangeStart));
+    properties.add(ObjectFlagProperty<ValueChanged<RangeValues>>.has('onChangeEnd', onChangeEnd));
+    properties.add(DoubleProperty('min', min));
+    properties.add(DoubleProperty('max', max));
+    properties.add(IntProperty('divisions', divisions));
+    properties.add(StringProperty('labelStart', labels?.start));
+    properties.add(StringProperty('labelEnd', labels?.end));
+    properties.add(ColorProperty('activeColor', activeColor));
+    properties.add(ColorProperty('inactiveColor', inactiveColor));
+    properties.add(ObjectFlagProperty<ValueChanged<double>>.has('semanticFormatterCallback', semanticFormatterCallback));
+  }
+}
+
+class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin {
+  static const Duration enableAnimationDuration = Duration(milliseconds: 75);
+  static const Duration valueIndicatorAnimationDuration = Duration(milliseconds: 100);
+
+  // Animation controller that is run when the overlay (a.k.a radial reaction)
+  // changes visibility in response to user interaction.
+  late AnimationController overlayController;
+
+  // Animation controller that is run when the value indicators change visibility.
+  late AnimationController valueIndicatorController;
+
+  // Animation controller that is run when enabling/disabling the slider.
+  late AnimationController enableController;
+
+  // Animation controllers that are run when transitioning between one value
+  // and the next on a discrete slider.
+  late AnimationController startPositionController;
+  late AnimationController endPositionController;
+  Timer? interactionTimer;
+  // Value Indicator paint Animation that appears on the Overlay.
+  PaintRangeValueIndicator? paintTopValueIndicator;
+  PaintRangeValueIndicator? paintBottomValueIndicator;
+
+
+  @override
+  void initState() {
+    super.initState();
+    overlayController = AnimationController(
+      duration: kRadialReactionDuration,
+      vsync: this,
+    );
+    valueIndicatorController = AnimationController(
+      duration: valueIndicatorAnimationDuration,
+      vsync: this,
+    );
+    enableController = AnimationController(
+      duration: enableAnimationDuration,
+      vsync: this,
+      value: widget.onChanged != null ? 1.0 : 0.0,
+    );
+    startPositionController = AnimationController(
+      duration: Duration.zero,
+      vsync: this,
+      value: _unlerp(widget.values.start),
+    );
+    endPositionController = AnimationController(
+      duration: Duration.zero,
+      vsync: this,
+      value: _unlerp(widget.values.end),
+    );
+  }
+
+  @override
+  void didUpdateWidget(RangeSlider oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.onChanged == widget.onChanged)
+      return;
+    final bool wasEnabled = oldWidget.onChanged != null;
+    final bool isEnabled = widget.onChanged != null;
+    if (wasEnabled != isEnabled) {
+      if (isEnabled) {
+        enableController.forward();
+      } else {
+        enableController.reverse();
+      }
+    }
+  }
+
+  @override
+  void dispose() {
+    interactionTimer?.cancel();
+    overlayController.dispose();
+    valueIndicatorController.dispose();
+    enableController.dispose();
+    startPositionController.dispose();
+    endPositionController.dispose();
+    if (overlayEntry != null) {
+      overlayEntry!.remove();
+      overlayEntry = null;
+    }
+    super.dispose();
+  }
+
+  void _handleChanged(RangeValues values) {
+    assert(widget.onChanged != null);
+    final RangeValues lerpValues = _lerpRangeValues(values);
+    if (lerpValues != widget.values) {
+      widget.onChanged!(lerpValues);
+    }
+  }
+
+  void _handleDragStart(RangeValues values) {
+    assert(widget.onChangeStart != null);
+    widget.onChangeStart!(_lerpRangeValues(values));
+  }
+
+  void _handleDragEnd(RangeValues values) {
+    assert(widget.onChangeEnd != null);
+    widget.onChangeEnd!(_lerpRangeValues(values));
+  }
+
+  // Returns a number between min and max, proportional to value, which must
+  // be between 0.0 and 1.0.
+  double _lerp(double value) => ui.lerpDouble(widget.min, widget.max, value)!;
+
+  // Returns a new range value with the start and end lerped.
+  RangeValues _lerpRangeValues(RangeValues values) {
+    return RangeValues(_lerp(values.start), _lerp(values.end));
+  }
+
+  // Returns a number between 0.0 and 1.0, given a value between min and max.
+  double _unlerp(double value) {
+    assert(value <= widget.max);
+    assert(value >= widget.min);
+    return widget.max > widget.min ? (value - widget.min) / (widget.max - widget.min) : 0.0;
+  }
+
+  // Returns a new range value with the start and end unlerped.
+  RangeValues _unlerpRangeValues(RangeValues values) {
+    return RangeValues(_unlerp(values.start), _unlerp(values.end));
+  }
+
+  // Finds closest thumb. If the thumbs are close to each other, no thumb is
+  // immediately selected while the drag displacement is zero. If the first
+  // non-zero displacement is negative, then the left thumb is selected, and if its
+  // positive, then the right thumb is selected.
+  static final RangeThumbSelector _defaultRangeThumbSelector = (
+    TextDirection textDirection,
+    RangeValues values,
+    double tapValue,
+    Size thumbSize,
+    Size trackSize,
+    double dx, // The horizontal delta or displacement of the drag update.
+  ) {
+    final double touchRadius = math.max(thumbSize.width, RangeSlider._minTouchTargetWidth) / 2;
+    final bool inStartTouchTarget = (tapValue - values.start).abs() * trackSize.width < touchRadius;
+    final bool inEndTouchTarget = (tapValue - values.end).abs() * trackSize.width < touchRadius;
+
+    // Use dx if the thumb touch targets overlap. If dx is 0 and the drag
+    // position is in both touch targets, no thumb is selected because it is
+    // ambiguous to which thumb should be selected. If the dx is non-zero, the
+    // thumb selection is determined by the direction of the dx. The left thumb
+    // is chosen for negative dx, and the right thumb is chosen for positive dx.
+    if (inStartTouchTarget && inEndTouchTarget) {
+      final bool towardsStart;
+      final bool towardsEnd;
+      switch (textDirection) {
+        case TextDirection.ltr:
+          towardsStart = dx < 0;
+          towardsEnd = dx > 0;
+          break;
+        case TextDirection.rtl:
+          towardsStart = dx > 0;
+          towardsEnd = dx < 0;
+          break;
+      }
+      if (towardsStart)
+        return Thumb.start;
+      if (towardsEnd)
+        return Thumb.end;
+    } else {
+      // Snap position on the track if its in the inactive range.
+      if (tapValue < values.start || inStartTouchTarget)
+        return Thumb.start;
+      if (tapValue > values.end || inEndTouchTarget)
+        return Thumb.end;
+    }
+    return null;
+  };
+
+
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    assert(debugCheckHasMediaQuery(context));
+
+    final ThemeData theme = Theme.of(context);
+    SliderThemeData sliderTheme = SliderTheme.of(context);
+
+    // If the widget has active or inactive colors specified, then we plug them
+    // in to the slider theme as best we can. If the developer wants more
+    // control than that, then they need to use a SliderTheme. The default
+    // colors come from the ThemeData.colorScheme. These colors, along with
+    // the default shapes and text styles are aligned to the Material
+    // Guidelines.
+
+    const double _defaultTrackHeight = 4;
+    const RangeSliderTrackShape _defaultTrackShape = RoundedRectRangeSliderTrackShape();
+    const RangeSliderTickMarkShape _defaultTickMarkShape = RoundRangeSliderTickMarkShape();
+    const SliderComponentShape _defaultOverlayShape = RoundSliderOverlayShape();
+    const RangeSliderThumbShape _defaultThumbShape = RoundRangeSliderThumbShape();
+    const RangeSliderValueIndicatorShape _defaultValueIndicatorShape = RectangularRangeSliderValueIndicatorShape();
+    const ShowValueIndicator _defaultShowValueIndicator = ShowValueIndicator.onlyForDiscrete;
+    const double _defaultMinThumbSeparation = 8;
+
+    // The value indicator's color is not the same as the thumb and active track
+    // (which can be defined by activeColor) if the
+    // RectangularSliderValueIndicatorShape is used. In all other cases, the
+    // value indicator is assumed to be the same as the active color.
+    final RangeSliderValueIndicatorShape valueIndicatorShape = sliderTheme.rangeValueIndicatorShape ?? _defaultValueIndicatorShape;
+    final Color valueIndicatorColor;
+    if (valueIndicatorShape is RectangularRangeSliderValueIndicatorShape) {
+      valueIndicatorColor = sliderTheme.valueIndicatorColor ?? Color.alphaBlend(theme.colorScheme.onSurface.withOpacity(0.60), theme.colorScheme.surface.withOpacity(0.90));
+    } else {
+      valueIndicatorColor = widget.activeColor ?? sliderTheme.valueIndicatorColor ?? theme.colorScheme.primary;
+    }
+
+    sliderTheme = sliderTheme.copyWith(
+      trackHeight: sliderTheme.trackHeight ?? _defaultTrackHeight,
+      activeTrackColor: widget.activeColor ?? sliderTheme.activeTrackColor ?? theme.colorScheme.primary,
+      inactiveTrackColor: widget.inactiveColor ?? sliderTheme.inactiveTrackColor ?? theme.colorScheme.primary.withOpacity(0.24),
+      disabledActiveTrackColor: sliderTheme.disabledActiveTrackColor ?? theme.colorScheme.onSurface.withOpacity(0.32),
+      disabledInactiveTrackColor: sliderTheme.disabledInactiveTrackColor ?? theme.colorScheme.onSurface.withOpacity(0.12),
+      activeTickMarkColor: widget.inactiveColor ?? sliderTheme.activeTickMarkColor ?? theme.colorScheme.onPrimary.withOpacity(0.54),
+      inactiveTickMarkColor: widget.activeColor ?? sliderTheme.inactiveTickMarkColor ?? theme.colorScheme.primary.withOpacity(0.54),
+      disabledActiveTickMarkColor: sliderTheme.disabledActiveTickMarkColor ?? theme.colorScheme.onPrimary.withOpacity(0.12),
+      disabledInactiveTickMarkColor: sliderTheme.disabledInactiveTickMarkColor ?? theme.colorScheme.onSurface.withOpacity(0.12),
+      thumbColor: widget.activeColor ?? sliderTheme.thumbColor ?? theme.colorScheme.primary,
+      overlappingShapeStrokeColor: sliderTheme.overlappingShapeStrokeColor ?? theme.colorScheme.surface,
+      disabledThumbColor: sliderTheme.disabledThumbColor ?? Color.alphaBlend(theme.colorScheme.onSurface.withOpacity(.38), theme.colorScheme.surface),
+      overlayColor: widget.activeColor?.withOpacity(0.12) ?? sliderTheme.overlayColor ?? theme.colorScheme.primary.withOpacity(0.12),
+      valueIndicatorColor: valueIndicatorColor,
+      rangeTrackShape: sliderTheme.rangeTrackShape ?? _defaultTrackShape,
+      rangeTickMarkShape: sliderTheme.rangeTickMarkShape ?? _defaultTickMarkShape,
+      rangeThumbShape: sliderTheme.rangeThumbShape ?? _defaultThumbShape,
+      overlayShape: sliderTheme.overlayShape ?? _defaultOverlayShape,
+      rangeValueIndicatorShape: valueIndicatorShape,
+      showValueIndicator: sliderTheme.showValueIndicator ?? _defaultShowValueIndicator,
+      valueIndicatorTextStyle: sliderTheme.valueIndicatorTextStyle ?? theme.textTheme.bodyText1!.copyWith(
+        color: theme.colorScheme.onPrimary,
+      ),
+      minThumbSeparation: sliderTheme.minThumbSeparation ?? _defaultMinThumbSeparation,
+      thumbSelector: sliderTheme.thumbSelector ?? _defaultRangeThumbSelector,
+    );
+
+    // This size is used as the max bounds for the painting of the value
+    // indicators. It must be kept in sync with the function with the same name
+    // in slider.dart.
+    Size _screenSize() => MediaQuery.of(context).size;
+
+    return CompositedTransformTarget(
+      link: _layerLink,
+      child: _RangeSliderRenderObjectWidget(
+        values: _unlerpRangeValues(widget.values),
+        divisions: widget.divisions,
+        labels: widget.labels,
+        sliderTheme: sliderTheme,
+        textScaleFactor: MediaQuery.of(context).textScaleFactor,
+        screenSize: _screenSize(),
+        onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,
+        onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
+        onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
+        state: this,
+        semanticFormatterCallback: widget.semanticFormatterCallback,
+      ),
+    );
+  }
+
+  final LayerLink _layerLink = LayerLink();
+
+  OverlayEntry? overlayEntry;
+
+  void showValueIndicator() {
+    if (overlayEntry == null) {
+      overlayEntry = OverlayEntry(
+        builder: (BuildContext context) {
+          return CompositedTransformFollower(
+            link: _layerLink,
+            child: _ValueIndicatorRenderObjectWidget(
+              state: this,
+            ),
+          );
+        },
+      );
+      Overlay.of(context)!.insert(overlayEntry!);
+    }
+  }
+}
+
+class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget {
+  const _RangeSliderRenderObjectWidget({
+    Key? key,
+    required this.values,
+    required this.divisions,
+    required this.labels,
+    required this.sliderTheme,
+    required this.textScaleFactor,
+    required this.screenSize,
+    required this.onChanged,
+    required this.onChangeStart,
+    required this.onChangeEnd,
+    required this.state,
+    required this.semanticFormatterCallback,
+  }) : super(key: key);
+
+  final RangeValues values;
+  final int? divisions;
+  final RangeLabels? labels;
+  final SliderThemeData sliderTheme;
+  final double textScaleFactor;
+  final Size screenSize;
+  final ValueChanged<RangeValues>? onChanged;
+  final ValueChanged<RangeValues>? onChangeStart;
+  final ValueChanged<RangeValues>? onChangeEnd;
+  final SemanticFormatterCallback? semanticFormatterCallback;
+  final _RangeSliderState state;
+
+  @override
+  _RenderRangeSlider createRenderObject(BuildContext context) {
+    return _RenderRangeSlider(
+      values: values,
+      divisions: divisions,
+      labels: labels,
+      sliderTheme: sliderTheme,
+      theme: Theme.of(context),
+      textScaleFactor: textScaleFactor,
+      screenSize: screenSize,
+      onChanged: onChanged,
+      onChangeStart: onChangeStart,
+      onChangeEnd: onChangeEnd,
+      state: state,
+      textDirection: Directionality.of(context),
+      semanticFormatterCallback: semanticFormatterCallback,
+      platform: Theme.of(context).platform,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderRangeSlider renderObject) {
+    renderObject
+      // We should update the `divisions` ahead of `values`, because the `values`
+      // setter dependent on the `divisions`.
+      ..divisions = divisions
+      ..values = values
+      ..labels = labels
+      ..sliderTheme = sliderTheme
+      ..theme = Theme.of(context)
+      ..textScaleFactor = textScaleFactor
+      ..screenSize = screenSize
+      ..onChanged = onChanged
+      ..onChangeStart = onChangeStart
+      ..onChangeEnd = onChangeEnd
+      ..textDirection = Directionality.of(context)
+      ..semanticFormatterCallback = semanticFormatterCallback
+      ..platform = Theme.of(context).platform;
+  }
+}
+
+class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
+  _RenderRangeSlider({
+    required RangeValues values,
+    required int? divisions,
+    required RangeLabels? labels,
+    required SliderThemeData sliderTheme,
+    required ThemeData? theme,
+    required double textScaleFactor,
+    required Size screenSize,
+    required TargetPlatform platform,
+    required ValueChanged<RangeValues>? onChanged,
+    required SemanticFormatterCallback? semanticFormatterCallback,
+    required this.onChangeStart,
+    required this.onChangeEnd,
+    required _RangeSliderState state,
+    required TextDirection textDirection,
+  })  : assert(values != null),
+        assert(values.start >= 0.0 && values.start <= 1.0),
+        assert(values.end >= 0.0 && values.end <= 1.0),
+        assert(state != null),
+        assert(textDirection != null),
+        _platform = platform,
+        _semanticFormatterCallback = semanticFormatterCallback,
+        _labels = labels,
+        _values = values,
+        _divisions = divisions,
+        _sliderTheme = sliderTheme,
+        _theme = theme,
+        _textScaleFactor = textScaleFactor,
+        _screenSize = screenSize,
+        _onChanged = onChanged,
+        _state = state,
+        _textDirection = textDirection {
+    _updateLabelPainters();
+    final GestureArenaTeam team = GestureArenaTeam();
+    _drag = HorizontalDragGestureRecognizer()
+      ..team = team
+      ..onStart = _handleDragStart
+      ..onUpdate = _handleDragUpdate
+      ..onEnd = _handleDragEnd
+      ..onCancel = _handleDragCancel;
+    _tap = TapGestureRecognizer()
+      ..team = team
+      ..onTapDown = _handleTapDown
+      ..onTapUp = _handleTapUp
+      ..onTapCancel = _handleTapCancel;
+    _overlayAnimation = CurvedAnimation(
+      parent: _state.overlayController,
+      curve: Curves.fastOutSlowIn,
+    );
+    _valueIndicatorAnimation = CurvedAnimation(
+      parent: _state.valueIndicatorController,
+      curve: Curves.fastOutSlowIn,
+    )..addStatusListener((AnimationStatus status) {
+      if (status == AnimationStatus.dismissed && _state.overlayEntry != null) {
+        _state.overlayEntry!.remove();
+        _state.overlayEntry = null;
+      }
+    });
+    _enableAnimation = CurvedAnimation(
+      parent: _state.enableController,
+      curve: Curves.easeInOut,
+    );
+  }
+
+  // Keep track of the last selected thumb so they can be drawn in the
+  // right order.
+  Thumb? _lastThumbSelection;
+
+  static const Duration _positionAnimationDuration = Duration(milliseconds: 75);
+
+  // This value is the touch target, 48, multiplied by 3.
+  static const double _minPreferredTrackWidth = 144.0;
+
+  // Compute the largest width and height needed to paint the slider shapes,
+  // other than the track shape. It is assumed that these shapes are vertically
+  // centered on the track.
+  double get _maxSliderPartWidth => _sliderPartSizes.map((Size size) => size.width).reduce(math.max);
+  double get _maxSliderPartHeight => _sliderPartSizes.map((Size size) => size.height).reduce(math.max);
+  List<Size> get _sliderPartSizes => <Size>[
+    _sliderTheme.overlayShape!.getPreferredSize(isEnabled, isDiscrete),
+    _sliderTheme.rangeThumbShape!.getPreferredSize(isEnabled, isDiscrete),
+    _sliderTheme.rangeTickMarkShape!.getPreferredSize(isEnabled: isEnabled, sliderTheme: sliderTheme),
+  ];
+  double? get _minPreferredTrackHeight => _sliderTheme.trackHeight;
+
+  // This rect is used in gesture calculations, where the gesture coordinates
+  // are relative to the sliders origin. Therefore, the offset is passed as
+  // (0,0).
+  Rect get _trackRect => _sliderTheme.rangeTrackShape!.getPreferredRect(
+    parentBox: this,
+    offset: Offset.zero,
+    sliderTheme: _sliderTheme,
+    isDiscrete: false,
+  );
+
+  static const Duration _minimumInteractionTime = Duration(milliseconds: 500);
+
+  final _RangeSliderState _state;
+  late Animation<double> _overlayAnimation;
+  late Animation<double> _valueIndicatorAnimation;
+  late Animation<double> _enableAnimation;
+  final TextPainter _startLabelPainter = TextPainter();
+  final TextPainter _endLabelPainter = TextPainter();
+  late HorizontalDragGestureRecognizer _drag;
+  late TapGestureRecognizer _tap;
+  bool _active = false;
+  late RangeValues _newValues;
+
+  bool get isEnabled => onChanged != null;
+
+  bool get isDiscrete => divisions != null && divisions! > 0;
+
+  double get _minThumbSeparationValue => isDiscrete ? 0 : sliderTheme.minThumbSeparation! / _trackRect.width;
+
+  RangeValues get values => _values;
+  RangeValues _values;
+  set values(RangeValues newValues) {
+    assert(newValues != null);
+    assert(newValues.start != null && newValues.start >= 0.0 && newValues.start <= 1.0);
+    assert(newValues.end != null && newValues.end >= 0.0 && newValues.end <= 1.0);
+    assert(newValues.start <= newValues.end);
+    final RangeValues convertedValues = isDiscrete ? _discretizeRangeValues(newValues) : newValues;
+    if (convertedValues == _values) {
+      return;
+    }
+    _values = convertedValues;
+    if (isDiscrete) {
+      // Reset the duration to match the distance that we're traveling, so that
+      // whatever the distance, we still do it in _positionAnimationDuration,
+      // and if we get re-targeted in the middle, it still takes that long to
+      // get to the new location.
+      final double startDistance = (_values.start -  _state.startPositionController.value).abs();
+      _state.startPositionController.duration = startDistance != 0.0 ? _positionAnimationDuration * (1.0 / startDistance) : Duration.zero;
+      _state.startPositionController.animateTo(_values.start, curve: Curves.easeInOut);
+      final double endDistance = (_values.end -  _state.endPositionController.value).abs();
+      _state.endPositionController.duration = endDistance != 0.0 ? _positionAnimationDuration * (1.0 / endDistance) : Duration.zero;
+      _state.endPositionController.animateTo(_values.end, curve: Curves.easeInOut);
+    } else {
+      _state.startPositionController.value = convertedValues.start;
+      _state.endPositionController.value = convertedValues.end;
+    }
+    markNeedsSemanticsUpdate();
+  }
+
+  TargetPlatform _platform;
+  TargetPlatform get platform => _platform;
+  set platform(TargetPlatform value) {
+    if (_platform == value)
+      return;
+    _platform = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  SemanticFormatterCallback? _semanticFormatterCallback;
+  SemanticFormatterCallback? get semanticFormatterCallback => _semanticFormatterCallback;
+  set semanticFormatterCallback(SemanticFormatterCallback? value) {
+    if (_semanticFormatterCallback == value)
+      return;
+    _semanticFormatterCallback = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  int? get divisions => _divisions;
+  int? _divisions;
+  set divisions(int? value) {
+    if (value == _divisions) {
+      return;
+    }
+    _divisions = value;
+    markNeedsPaint();
+  }
+
+  RangeLabels? get labels => _labels;
+  RangeLabels? _labels;
+  set labels(RangeLabels? labels) {
+    if (labels == _labels)
+      return;
+    _labels = labels;
+    _updateLabelPainters();
+  }
+
+  SliderThemeData get sliderTheme => _sliderTheme;
+  SliderThemeData _sliderTheme;
+  set sliderTheme(SliderThemeData value) {
+    if (value == _sliderTheme)
+      return;
+    _sliderTheme = value;
+    markNeedsPaint();
+  }
+
+  ThemeData? get theme => _theme;
+  ThemeData? _theme;
+  set theme(ThemeData? value) {
+    if (value == _theme)
+      return;
+    _theme = value;
+    markNeedsPaint();
+  }
+
+  double get textScaleFactor => _textScaleFactor;
+  double _textScaleFactor;
+  set textScaleFactor(double value) {
+    if (value == _textScaleFactor)
+      return;
+    _textScaleFactor = value;
+    _updateLabelPainters();
+  }
+
+  Size get screenSize => _screenSize;
+  Size _screenSize;
+  set screenSize(Size value) {
+    if (value == screenSize)
+      return;
+    _screenSize = value;
+    markNeedsPaint();
+  }
+
+  ValueChanged<RangeValues>? get onChanged => _onChanged;
+  ValueChanged<RangeValues>? _onChanged;
+  set onChanged(ValueChanged<RangeValues>? value) {
+    if (value == _onChanged)
+      return;
+    final bool wasEnabled = isEnabled;
+    _onChanged = value;
+    if (wasEnabled != isEnabled) {
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  ValueChanged<RangeValues>? onChangeStart;
+  ValueChanged<RangeValues>? onChangeEnd;
+
+  TextDirection get textDirection => _textDirection;
+  TextDirection _textDirection;
+  set textDirection(TextDirection value) {
+    assert(value != null);
+    if (value == _textDirection)
+      return;
+    _textDirection = value;
+    _updateLabelPainters();
+  }
+
+  bool get showValueIndicator {
+    switch (_sliderTheme.showValueIndicator!) {
+      case ShowValueIndicator.onlyForDiscrete:
+        return isDiscrete;
+      case ShowValueIndicator.onlyForContinuous:
+        return !isDiscrete;
+      case ShowValueIndicator.always:
+        return true;
+      case ShowValueIndicator.never:
+        return false;
+    }
+  }
+
+  Size get _thumbSize => _sliderTheme.rangeThumbShape!.getPreferredSize(isEnabled, isDiscrete);
+
+  double get _adjustmentUnit {
+    switch (_platform) {
+      case TargetPlatform.iOS:
+        // Matches iOS implementation of material slider.
+        return 0.1;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.macOS:
+      case TargetPlatform.windows:
+        // Matches Android implementation of material slider.
+        return 0.05;
+    }
+  }
+
+  void _updateLabelPainters() {
+    _updateLabelPainter(Thumb.start);
+    _updateLabelPainter(Thumb.end);
+  }
+
+  void _updateLabelPainter(Thumb thumb) {
+    if (labels == null)
+      return;
+
+    final String text;
+    final TextPainter labelPainter;
+    switch (thumb) {
+      case Thumb.start:
+        text = labels!.start;
+        labelPainter = _startLabelPainter;
+        break;
+      case Thumb.end:
+        text = labels!.end;
+        labelPainter = _endLabelPainter;
+        break;
+    }
+
+    if (labels != null) {
+      labelPainter
+        ..text = TextSpan(
+          style: _sliderTheme.valueIndicatorTextStyle,
+          text: text,
+        )
+        ..textDirection = textDirection
+        ..textScaleFactor = textScaleFactor
+        ..layout();
+    } else {
+      labelPainter.text = null;
+    }
+    // Changing the textDirection can result in the layout changing, because the
+    // bidi algorithm might line up the glyphs differently which can result in
+    // different ligatures, different shapes, etc. So we always markNeedsLayout.
+    markNeedsLayout();
+  }
+
+  @override
+  void systemFontsDidChange() {
+    super.systemFontsDidChange();
+    _startLabelPainter.markNeedsLayout();
+    _endLabelPainter.markNeedsLayout();
+    _updateLabelPainters();
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    _overlayAnimation.addListener(markNeedsPaint);
+    _valueIndicatorAnimation.addListener(markNeedsPaint);
+    _enableAnimation.addListener(markNeedsPaint);
+    _state.startPositionController.addListener(markNeedsPaint);
+    _state.endPositionController.addListener(markNeedsPaint);
+  }
+
+  @override
+  void detach() {
+    _overlayAnimation.removeListener(markNeedsPaint);
+    _valueIndicatorAnimation.removeListener(markNeedsPaint);
+    _enableAnimation.removeListener(markNeedsPaint);
+    _state.startPositionController.removeListener(markNeedsPaint);
+    _state.endPositionController.removeListener(markNeedsPaint);
+    super.detach();
+  }
+
+  double _getValueFromVisualPosition(double visualPosition) {
+    switch (textDirection) {
+      case TextDirection.rtl:
+        return 1.0 - visualPosition;
+      case TextDirection.ltr:
+        return visualPosition;
+    }
+  }
+
+  double _getValueFromGlobalPosition(Offset globalPosition) {
+    final double visualPosition = (globalToLocal(globalPosition).dx - _trackRect.left) / _trackRect.width;
+    return _getValueFromVisualPosition(visualPosition);
+  }
+
+  double _discretize(double value) {
+    double result = value.clamp(0.0, 1.0);
+    if (isDiscrete) {
+      result = (result * divisions!).round() / divisions!;
+    }
+    return result;
+  }
+
+  RangeValues _discretizeRangeValues(RangeValues values) {
+    return RangeValues(_discretize(values.start), _discretize(values.end));
+  }
+
+  void _startInteraction(Offset globalPosition) {
+    _state.showValueIndicator();
+    final double tapValue = _getValueFromGlobalPosition(globalPosition).clamp(0.0, 1.0);
+    _lastThumbSelection = sliderTheme.thumbSelector!(textDirection, values, tapValue, _thumbSize, size, 0);
+
+    if (_lastThumbSelection != null) {
+      _active = true;
+      // We supply the *current* values as the start locations, so that if we have
+      // a tap, it consists of a call to onChangeStart with the previous value and
+      // a call to onChangeEnd with the new value.
+      final RangeValues currentValues = _discretizeRangeValues(values);
+      if (_lastThumbSelection == Thumb.start) {
+        _newValues = RangeValues(tapValue, currentValues.end);
+      } else if (_lastThumbSelection == Thumb.end) {
+        _newValues = RangeValues(currentValues.start, tapValue);
+      }
+      _updateLabelPainter(_lastThumbSelection!);
+
+      if (onChangeStart != null) {
+        onChangeStart!(currentValues);
+      }
+
+      onChanged!(_discretizeRangeValues(_newValues));
+
+      _state.overlayController.forward();
+      if (showValueIndicator) {
+        _state.valueIndicatorController.forward();
+        _state.interactionTimer?.cancel();
+        _state.interactionTimer =
+          Timer(_minimumInteractionTime * timeDilation, () {
+            _state.interactionTimer = null;
+            if (!_active && _state.valueIndicatorController.status == AnimationStatus.completed) {
+              _state.valueIndicatorController.reverse();
+            }
+          });
+      }
+    }
+  }
+
+  void _handleDragUpdate(DragUpdateDetails details) {
+    if (!_state.mounted) {
+      return;
+    }
+
+    final double dragValue = _getValueFromGlobalPosition(details.globalPosition);
+
+    // If no selection has been made yet, test for thumb selection again now
+    // that the value of dx can be non-zero. If this is the first selection of
+    // the interaction, then onChangeStart must be called.
+    bool shouldCallOnChangeStart = false;
+    if (_lastThumbSelection == null) {
+      _lastThumbSelection = sliderTheme.thumbSelector!(textDirection, values, dragValue, _thumbSize, size, details.delta.dx);
+      if (_lastThumbSelection != null) {
+        shouldCallOnChangeStart = true;
+        _active = true;
+        _state.overlayController.forward();
+        if (showValueIndicator) {
+          _state.valueIndicatorController.forward();
+        }
+      }
+    }
+
+    if (isEnabled && _lastThumbSelection != null) {
+      final RangeValues currentValues = _discretizeRangeValues(values);
+      if (onChangeStart != null && shouldCallOnChangeStart) {
+        onChangeStart!(currentValues);
+      }
+      final double currentDragValue = _discretize(dragValue);
+
+      if (_lastThumbSelection == Thumb.start) {
+        _newValues = RangeValues(math.min(currentDragValue, currentValues.end - _minThumbSeparationValue), currentValues.end);
+      } else if (_lastThumbSelection == Thumb.end) {
+        _newValues = RangeValues(currentValues.start, math.max(currentDragValue, currentValues.start + _minThumbSeparationValue));
+      }
+      onChanged!(_newValues);
+    }
+  }
+
+  void _endInteraction() {
+    if (!_state.mounted) {
+      return;
+    }
+
+    if (showValueIndicator && _state.interactionTimer == null) {
+      _state.valueIndicatorController.reverse();
+    }
+
+    if (_active && _state.mounted && _lastThumbSelection != null) {
+      final RangeValues discreteValues = _discretizeRangeValues(_newValues);
+      if (onChangeEnd != null) {
+        onChangeEnd!(discreteValues);
+      }
+      _active = false;
+    }
+    _state.overlayController.reverse();
+  }
+
+  void _handleDragStart(DragStartDetails details) {
+    _startInteraction(details.globalPosition);
+  }
+
+  void _handleDragEnd(DragEndDetails details) {
+    _endInteraction();
+  }
+
+  void _handleDragCancel() {
+    _endInteraction();
+  }
+
+  void _handleTapDown(TapDownDetails details) {
+    _startInteraction(details.globalPosition);
+  }
+
+  void _handleTapUp(TapUpDetails details) {
+    _endInteraction();
+  }
+
+  void _handleTapCancel() {
+    _endInteraction();
+  }
+
+  @override
+  bool hitTestSelf(Offset position) => true;
+
+  @override
+  void handleEvent(PointerEvent event, HitTestEntry entry) {
+    assert(debugHandleEvent(event, entry));
+    if (event is PointerDownEvent && isEnabled) {
+      // We need to add the drag first so that it has priority.
+      _drag.addPointer(event);
+      _tap.addPointer(event);
+    }
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) => _minPreferredTrackWidth + _maxSliderPartWidth;
+
+  @override
+  double computeMaxIntrinsicWidth(double height) => _minPreferredTrackWidth + _maxSliderPartWidth;
+
+  @override
+  double computeMinIntrinsicHeight(double width) => math.max(_minPreferredTrackHeight!, _maxSliderPartHeight);
+
+  @override
+  double computeMaxIntrinsicHeight(double width) => math.max(_minPreferredTrackHeight!, _maxSliderPartHeight);
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return Size(
+      constraints.hasBoundedWidth ? constraints.maxWidth : _minPreferredTrackWidth + _maxSliderPartWidth,
+      constraints.hasBoundedHeight ? constraints.maxHeight : math.max(_minPreferredTrackHeight!, _maxSliderPartHeight),
+    );
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final double startValue = _state.startPositionController.value;
+    final double endValue = _state.endPositionController.value;
+
+    // The visual position is the position of the thumb from 0 to 1 from left
+    // to right. In left to right, this is the same as the value, but it is
+    // reversed for right to left text.
+    final double startVisualPosition;
+    final double endVisualPosition;
+    switch (textDirection) {
+      case TextDirection.rtl:
+        startVisualPosition = 1.0 - startValue;
+        endVisualPosition = 1.0 - endValue;
+        break;
+      case TextDirection.ltr:
+        startVisualPosition = startValue;
+        endVisualPosition = endValue;
+        break;
+    }
+
+    final Rect trackRect = _sliderTheme.rangeTrackShape!.getPreferredRect(
+        parentBox: this,
+        offset: offset,
+        sliderTheme: _sliderTheme,
+        isDiscrete: isDiscrete,
+    );
+    final Offset startThumbCenter = Offset(trackRect.left + startVisualPosition * trackRect.width, trackRect.center.dy);
+    final Offset endThumbCenter = Offset(trackRect.left + endVisualPosition * trackRect.width, trackRect.center.dy);
+
+    _sliderTheme.rangeTrackShape!.paint(
+        context,
+        offset,
+        parentBox: this,
+        sliderTheme: _sliderTheme,
+        enableAnimation: _enableAnimation,
+        textDirection: _textDirection,
+        startThumbCenter: startThumbCenter,
+        endThumbCenter: endThumbCenter,
+        isDiscrete: isDiscrete,
+        isEnabled: isEnabled,
+    );
+
+    final bool startThumbSelected = _lastThumbSelection == Thumb.start;
+    final bool endThumbSelected = _lastThumbSelection == Thumb.end;
+    final Size resolvedscreenSize = screenSize.isEmpty ? size : screenSize;
+
+    if (!_overlayAnimation.isDismissed) {
+      if (startThumbSelected) {
+        _sliderTheme.overlayShape!.paint(
+          context,
+          startThumbCenter,
+          activationAnimation: _overlayAnimation,
+          enableAnimation: _enableAnimation,
+          isDiscrete: isDiscrete,
+          labelPainter: _startLabelPainter,
+          parentBox: this,
+          sliderTheme: _sliderTheme,
+          textDirection: _textDirection,
+          value: startValue,
+          textScaleFactor: _textScaleFactor,
+          sizeWithOverflow: resolvedscreenSize,
+        );
+      }
+      if (endThumbSelected) {
+        _sliderTheme.overlayShape!.paint(
+          context,
+          endThumbCenter,
+          activationAnimation: _overlayAnimation,
+          enableAnimation: _enableAnimation,
+          isDiscrete: isDiscrete,
+          labelPainter: _endLabelPainter,
+          parentBox: this,
+          sliderTheme: _sliderTheme,
+          textDirection: _textDirection,
+          value: endValue,
+          textScaleFactor: _textScaleFactor,
+          sizeWithOverflow: resolvedscreenSize,
+        );
+      }
+    }
+
+    if (isDiscrete) {
+      final double tickMarkWidth = _sliderTheme.rangeTickMarkShape!.getPreferredSize(
+        isEnabled: isEnabled,
+        sliderTheme: _sliderTheme,
+      ).width;
+      final double padding = trackRect.height;
+      final double adjustedTrackWidth = trackRect.width - padding;
+      // If the tick marks would be too dense, don't bother painting them.
+      if (adjustedTrackWidth / divisions! >= 3.0 * tickMarkWidth) {
+        final double dy = trackRect.center.dy;
+        for (int i = 0; i <= divisions!; i++) {
+          final double value = i / divisions!;
+          // The ticks are mapped to be within the track, so the tick mark width
+          // must be subtracted from the track width.
+          final double dx = trackRect.left + value * adjustedTrackWidth + padding / 2;
+          final Offset tickMarkOffset = Offset(dx, dy);
+          _sliderTheme.rangeTickMarkShape!.paint(
+            context,
+            tickMarkOffset,
+            parentBox: this,
+            sliderTheme: _sliderTheme,
+            enableAnimation: _enableAnimation,
+            textDirection: _textDirection,
+            startThumbCenter: startThumbCenter,
+            endThumbCenter: endThumbCenter,
+            isEnabled: isEnabled,
+          );
+        }
+      }
+    }
+
+    final double thumbDelta = (endThumbCenter.dx - startThumbCenter.dx).abs();
+
+    final bool isLastThumbStart = _lastThumbSelection == Thumb.start;
+    final Thumb bottomThumb = isLastThumbStart ? Thumb.end : Thumb.start;
+    final Thumb topThumb = isLastThumbStart ? Thumb.start : Thumb.end;
+    final Offset bottomThumbCenter = isLastThumbStart ? endThumbCenter : startThumbCenter;
+    final Offset topThumbCenter = isLastThumbStart ? startThumbCenter : endThumbCenter;
+    final TextPainter bottomLabelPainter = isLastThumbStart ? _endLabelPainter : _startLabelPainter;
+    final TextPainter topLabelPainter = isLastThumbStart ? _startLabelPainter : _endLabelPainter;
+    final double bottomValue = isLastThumbStart ? endValue : startValue;
+    final double topValue = isLastThumbStart ? startValue : endValue;
+    final bool shouldPaintValueIndicators = isEnabled && labels != null && !_valueIndicatorAnimation.isDismissed && showValueIndicator;
+
+    if (shouldPaintValueIndicators) {
+      _state.paintBottomValueIndicator = (PaintingContext context, Offset offset) {
+        if (attached) {
+          _sliderTheme.rangeValueIndicatorShape!.paint(
+            context,
+            bottomThumbCenter,
+            activationAnimation: _valueIndicatorAnimation,
+            enableAnimation: _enableAnimation,
+            isDiscrete: isDiscrete,
+            isOnTop: false,
+            labelPainter: bottomLabelPainter,
+            parentBox: this,
+            sliderTheme: _sliderTheme,
+            textDirection: _textDirection,
+            thumb: bottomThumb,
+            value: bottomValue,
+            textScaleFactor: textScaleFactor,
+            sizeWithOverflow: resolvedscreenSize,
+          );
+        }
+      };
+    }
+
+    _sliderTheme.rangeThumbShape!.paint(
+      context,
+      bottomThumbCenter,
+      activationAnimation: _valueIndicatorAnimation,
+      enableAnimation: _enableAnimation,
+      isDiscrete: isDiscrete,
+      isOnTop: false,
+      textDirection: textDirection,
+      sliderTheme: _sliderTheme,
+      thumb: bottomThumb,
+      isPressed: bottomThumb == Thumb.start ? startThumbSelected : endThumbSelected,
+    );
+
+    if (shouldPaintValueIndicators) {
+      final double startOffset = sliderTheme.rangeValueIndicatorShape!.getHorizontalShift(
+        parentBox: this,
+        center: startThumbCenter,
+        labelPainter: _startLabelPainter,
+        activationAnimation: _valueIndicatorAnimation,
+        textScaleFactor: textScaleFactor,
+        sizeWithOverflow: resolvedscreenSize,
+      );
+      final double endOffset = sliderTheme.rangeValueIndicatorShape!.getHorizontalShift(
+        parentBox: this,
+        center: endThumbCenter,
+        labelPainter: _endLabelPainter,
+        activationAnimation: _valueIndicatorAnimation,
+        textScaleFactor: textScaleFactor,
+        sizeWithOverflow: resolvedscreenSize,
+      );
+      final double startHalfWidth = sliderTheme.rangeValueIndicatorShape!.getPreferredSize(
+        isEnabled,
+        isDiscrete,
+        labelPainter: _startLabelPainter,
+        textScaleFactor: textScaleFactor,
+      ).width / 2;
+      final double endHalfWidth = sliderTheme.rangeValueIndicatorShape!.getPreferredSize(
+        isEnabled,
+        isDiscrete,
+        labelPainter: _endLabelPainter,
+        textScaleFactor: textScaleFactor,
+      ).width / 2;
+      double innerOverflow = startHalfWidth + endHalfWidth;
+      switch (textDirection) {
+        case TextDirection.ltr:
+          innerOverflow += startOffset;
+          innerOverflow -= endOffset;
+          break;
+        case TextDirection.rtl:
+          innerOverflow -= startOffset;
+          innerOverflow += endOffset;
+          break;
+      }
+
+      _state.paintTopValueIndicator = (PaintingContext context, Offset offset) {
+        if (attached) {
+          _sliderTheme.rangeValueIndicatorShape!.paint(
+            context,
+            topThumbCenter,
+            activationAnimation: _valueIndicatorAnimation,
+            enableAnimation: _enableAnimation,
+            isDiscrete: isDiscrete,
+            isOnTop: thumbDelta < innerOverflow,
+            labelPainter: topLabelPainter,
+            parentBox: this,
+            sliderTheme: _sliderTheme,
+            textDirection: _textDirection,
+            thumb: topThumb,
+            value: topValue,
+            textScaleFactor: textScaleFactor,
+            sizeWithOverflow: resolvedscreenSize,
+          );
+        }
+      };
+    }
+
+    _sliderTheme.rangeThumbShape!.paint(
+      context,
+      topThumbCenter,
+      activationAnimation: _overlayAnimation,
+      enableAnimation: _enableAnimation,
+      isDiscrete: isDiscrete,
+      isOnTop: thumbDelta < sliderTheme.rangeThumbShape!.getPreferredSize(isEnabled, isDiscrete).width,
+      textDirection: textDirection,
+      sliderTheme: _sliderTheme,
+      thumb: topThumb,
+      isPressed: topThumb == Thumb.start ? startThumbSelected : endThumbSelected,
+    );
+  }
+
+  /// Describe the semantics of the start thumb.
+  SemanticsNode? _startSemanticsNode = SemanticsNode();
+
+  /// Describe the semantics of the end thumb.
+  SemanticsNode? _endSemanticsNode = SemanticsNode();
+
+  // Create the semantics configuration for a single value.
+  SemanticsConfiguration _createSemanticsConfiguration(
+      double value,
+      double increasedValue,
+      double decreasedValue,
+      String? label,
+      VoidCallback increaseAction,
+      VoidCallback decreaseAction,
+  ) {
+    final SemanticsConfiguration config = SemanticsConfiguration();
+    config.isEnabled = isEnabled;
+    config.textDirection = textDirection;
+    config.isSlider = true;
+    if (isEnabled) {
+      config.onIncrease = increaseAction;
+      config.onDecrease = decreaseAction;
+    }
+    config.label = label ?? '';
+    if (semanticFormatterCallback != null) {
+      config.value = semanticFormatterCallback!(_state._lerp(value));
+      config.increasedValue = semanticFormatterCallback!(_state._lerp(increasedValue));
+      config.decreasedValue = semanticFormatterCallback!(_state._lerp(decreasedValue));
+    } else {
+      config.value = '${(value * 100).round()}%';
+      config.increasedValue = '${(increasedValue * 100).round()}%';
+      config.decreasedValue = '${(decreasedValue * 100).round()}%';
+    }
+
+    return config;
+  }
+
+  @override
+  void assembleSemanticsNode(
+      SemanticsNode node,
+      SemanticsConfiguration config,
+      Iterable<SemanticsNode> children,
+  ) {
+    assert(children.isEmpty);
+
+    final SemanticsConfiguration startSemanticsConfiguration = _createSemanticsConfiguration(
+      values.start,
+      _increasedStartValue,
+      _decreasedStartValue,
+      labels?.start,
+      _increaseStartAction,
+      _decreaseStartAction,
+    );
+    final SemanticsConfiguration endSemanticsConfiguration = _createSemanticsConfiguration(
+      values.end,
+      _increasedEndValue,
+      _decreasedEndValue,
+      labels?.end,
+      _increaseEndAction,
+      _decreaseEndAction,
+    );
+
+    // Split the semantics node area between the start and end nodes.
+    final Rect leftRect = Rect.fromPoints(node.rect.topLeft, node.rect.bottomCenter);
+    final Rect rightRect = Rect.fromPoints(node.rect.topCenter, node.rect.bottomRight);
+    switch (textDirection) {
+      case TextDirection.ltr:
+        _startSemanticsNode!.rect = leftRect;
+        _endSemanticsNode!.rect = rightRect;
+        break;
+      case TextDirection.rtl:
+        _startSemanticsNode!.rect = rightRect;
+        _endSemanticsNode!.rect = leftRect;
+        break;
+    }
+
+    _startSemanticsNode!.updateWith(config: startSemanticsConfiguration);
+    _endSemanticsNode!.updateWith(config: endSemanticsConfiguration);
+
+    final List<SemanticsNode> finalChildren = <SemanticsNode>[
+      _startSemanticsNode!,
+      _endSemanticsNode!,
+    ];
+
+    node.updateWith(config: config, childrenInInversePaintOrder: finalChildren);
+  }
+
+  @override
+  void clearSemantics() {
+    super.clearSemantics();
+    _startSemanticsNode = null;
+    _endSemanticsNode = null;
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    config.isSemanticBoundary = true;
+  }
+
+  double get _semanticActionUnit => divisions != null ? 1.0 / divisions! : _adjustmentUnit;
+
+  void _increaseStartAction() {
+    if (isEnabled) {
+        onChanged!(RangeValues(_increasedStartValue, values.end));
+    }
+  }
+
+  void _decreaseStartAction() {
+    if (isEnabled) {
+      onChanged!(RangeValues(_decreasedStartValue, values.end));
+    }
+  }
+
+  void _increaseEndAction() {
+    if (isEnabled) {
+      onChanged!(RangeValues(values.start, _increasedEndValue));
+    }
+  }
+
+  void _decreaseEndAction() {
+    if (isEnabled) {
+      onChanged!(RangeValues(values.start, _decreasedEndValue));
+    }
+  }
+
+  double get _increasedStartValue {
+    // Due to floating-point operations, this value can actually be greater than
+    // expected (e.g. 0.4 + 0.2 = 0.600000000001), so we limit to 2 decimal points.
+    final double increasedStartValue = double.parse((values.start + _semanticActionUnit).toStringAsFixed(2));
+    return increasedStartValue <= values.end - _minThumbSeparationValue ? increasedStartValue : values.start;
+  }
+
+  double get _decreasedStartValue {
+    return (values.start - _semanticActionUnit).clamp(0.0, 1.0);
+  }
+
+  double get _increasedEndValue {
+    return (values.end + _semanticActionUnit).clamp(0.0, 1.0);
+  }
+
+  double get _decreasedEndValue {
+    final double decreasedEndValue = values.end - _semanticActionUnit;
+    return decreasedEndValue >= values.start + _minThumbSeparationValue ? decreasedEndValue : values.end;
+  }
+}
+
+
+class _ValueIndicatorRenderObjectWidget extends LeafRenderObjectWidget {
+  const _ValueIndicatorRenderObjectWidget({
+    required this.state,
+  });
+
+  final _RangeSliderState state;
+
+  @override
+  _RenderValueIndicator createRenderObject(BuildContext context) {
+    return _RenderValueIndicator(
+      state: state,
+    );
+  }
+  @override
+  void updateRenderObject(BuildContext context, _RenderValueIndicator renderObject) {
+    renderObject._state = state;
+  }
+}
+
+class _RenderValueIndicator extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
+  _RenderValueIndicator({
+    required _RangeSliderState state,
+  }) :_state = state {
+    _valueIndicatorAnimation = CurvedAnimation(
+      parent: _state.valueIndicatorController,
+      curve: Curves.fastOutSlowIn,
+    );
+  }
+
+  late Animation<double> _valueIndicatorAnimation;
+  late _RangeSliderState _state;
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    _valueIndicatorAnimation.addListener(markNeedsPaint);
+    _state.startPositionController.addListener(markNeedsPaint);
+    _state.endPositionController.addListener(markNeedsPaint);
+  }
+
+  @override
+  void detach() {
+    _valueIndicatorAnimation.removeListener(markNeedsPaint);
+    _state.startPositionController.removeListener(markNeedsPaint);
+    _state.endPositionController.removeListener(markNeedsPaint);
+    super.detach();
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (_state.paintBottomValueIndicator != null) {
+      _state.paintBottomValueIndicator!(context, offset);
+    }
+    if (_state.paintTopValueIndicator != null) {
+      _state.paintTopValueIndicator!(context, offset);
+    }
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return constraints.smallest;
+  }
+}
diff --git a/lib/src/material/refresh_indicator.dart b/lib/src/material/refresh_indicator.dart
new file mode 100644
index 0000000..2f967cf
--- /dev/null
+++ b/lib/src/material/refresh_indicator.dart
@@ -0,0 +1,487 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:math' as math;
+
+import 'package:flute/widgets.dart';
+
+import 'debug.dart';
+import 'material_localizations.dart';
+import 'progress_indicator.dart';
+import 'theme.dart';
+
+// The over-scroll distance that moves the indicator to its maximum
+// displacement, as a percentage of the scrollable's container extent.
+const double _kDragContainerExtentPercentage = 0.25;
+
+// How much the scroll's drag gesture can overshoot the RefreshIndicator's
+// displacement; max displacement = _kDragSizeFactorLimit * displacement.
+const double _kDragSizeFactorLimit = 1.5;
+
+// When the scroll ends, the duration of the refresh indicator's animation
+// to the RefreshIndicator's displacement.
+const Duration _kIndicatorSnapDuration = Duration(milliseconds: 150);
+
+// The duration of the ScaleTransition that starts when the refresh action
+// has completed.
+const Duration _kIndicatorScaleDuration = Duration(milliseconds: 200);
+
+/// The signature for a function that's called when the user has dragged a
+/// [RefreshIndicator] far enough to demonstrate that they want the app to
+/// refresh. The returned [Future] must complete when the refresh operation is
+/// finished.
+///
+/// Used by [RefreshIndicator.onRefresh].
+typedef RefreshCallback = Future<void> Function();
+
+// The state machine moves through these modes only when the scrollable
+// identified by scrollableKey has been scrolled to its min or max limit.
+enum _RefreshIndicatorMode {
+  drag,     // Pointer is down.
+  armed,    // Dragged far enough that an up event will run the onRefresh callback.
+  snap,     // Animating to the indicator's final "displacement".
+  refresh,  // Running the refresh callback.
+  done,     // Animating the indicator's fade-out after refreshing.
+  canceled, // Animating the indicator's fade-out after not arming.
+}
+
+/// A widget that supports the Material "swipe to refresh" idiom.
+///
+/// When the child's [Scrollable] descendant overscrolls, an animated circular
+/// progress indicator is faded into view. When the scroll ends, if the
+/// indicator has been dragged far enough for it to become completely opaque,
+/// the [onRefresh] callback is called. The callback is expected to update the
+/// scrollable's contents and then complete the [Future] it returns. The refresh
+/// indicator disappears after the callback's [Future] has completed.
+///
+/// ## Troubleshooting
+///
+/// ### Refresh indicator does not show up
+///
+/// The [RefreshIndicator] will appear if its scrollable descendant can be
+/// overscrolled, i.e. if the scrollable's content is bigger than its viewport.
+/// To ensure that the [RefreshIndicator] will always appear, even if the
+/// scrollable's content fits within its viewport, set the scrollable's
+/// [Scrollable.physics] property to [AlwaysScrollableScrollPhysics]:
+///
+/// ```dart
+/// ListView(
+///   physics: const AlwaysScrollableScrollPhysics(),
+///   children: ...
+/// )
+/// ```
+///
+/// A [RefreshIndicator] can only be used with a vertical scroll view.
+///
+/// See also:
+///
+///  * <https://material.io/design/platform-guidance/android-swipe-to-refresh.html>
+///  * [RefreshIndicatorState], can be used to programmatically show the refresh indicator.
+///  * [RefreshProgressIndicator], widget used by [RefreshIndicator] to show
+///    the inner circular progress spinner during refreshes.
+///  * [CupertinoSliverRefreshControl], an iOS equivalent of the pull-to-refresh pattern.
+///    Must be used as a sliver inside a [CustomScrollView] instead of wrapping
+///    around a [ScrollView] because it's a part of the scrollable instead of
+///    being overlaid on top of it.
+class RefreshIndicator extends StatefulWidget {
+  /// Creates a refresh indicator.
+  ///
+  /// The [onRefresh], [child], and [notificationPredicate] arguments must be
+  /// non-null. The default
+  /// [displacement] is 40.0 logical pixels.
+  ///
+  /// The [semanticsLabel] is used to specify an accessibility label for this widget.
+  /// If it is null, it will be defaulted to [MaterialLocalizations.refreshIndicatorSemanticLabel].
+  /// An empty string may be passed to avoid having anything read by screen reading software.
+  /// The [semanticsValue] may be used to specify progress on the widget.
+  const RefreshIndicator({
+    Key? key,
+    required this.child,
+    this.displacement = 40.0,
+    required this.onRefresh,
+    this.color,
+    this.backgroundColor,
+    this.notificationPredicate = defaultScrollNotificationPredicate,
+    this.semanticsLabel,
+    this.semanticsValue,
+    this.strokeWidth = 2.0
+  }) : assert(child != null),
+       assert(onRefresh != null),
+       assert(notificationPredicate != null),
+       assert(strokeWidth != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// The refresh indicator will be stacked on top of this child. The indicator
+  /// will appear when child's Scrollable descendant is over-scrolled.
+  ///
+  /// Typically a [ListView] or [CustomScrollView].
+  final Widget child;
+
+  /// The distance from the child's top or bottom edge to where the refresh
+  /// indicator will settle. During the drag that exposes the refresh indicator,
+  /// its actual displacement may significantly exceed this value.
+  final double displacement;
+
+  /// A function that's called when the user has dragged the refresh indicator
+  /// far enough to demonstrate that they want the app to refresh. The returned
+  /// [Future] must complete when the refresh operation is finished.
+  final RefreshCallback onRefresh;
+
+  /// The progress indicator's foreground color. The current theme's
+  /// [ThemeData.accentColor] by default.
+  final Color? color;
+
+  /// The progress indicator's background color. The current theme's
+  /// [ThemeData.canvasColor] by default.
+  final Color? backgroundColor;
+
+  /// A check that specifies whether a [ScrollNotification] should be
+  /// handled by this widget.
+  ///
+  /// By default, checks whether `notification.depth == 0`. Set it to something
+  /// else for more complicated layouts.
+  final ScrollNotificationPredicate notificationPredicate;
+
+  /// {@macro flutter.progress_indicator.ProgressIndicator.semanticsLabel}
+  ///
+  /// This will be defaulted to [MaterialLocalizations.refreshIndicatorSemanticLabel]
+  /// if it is null.
+  final String? semanticsLabel;
+
+  /// {@macro flutter.progress_indicator.ProgressIndicator.semanticsValue}
+  final String? semanticsValue;
+
+  /// Defines `strokeWidth` for `RefreshIndicator`.
+  ///
+  /// By default, the value of `strokeWidth` is 2.0 pixels.
+  final double strokeWidth;
+
+  @override
+  RefreshIndicatorState createState() => RefreshIndicatorState();
+}
+
+/// Contains the state for a [RefreshIndicator]. This class can be used to
+/// programmatically show the refresh indicator, see the [show] method.
+class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderStateMixin<RefreshIndicator> {
+  late AnimationController _positionController;
+  late AnimationController _scaleController;
+  late Animation<double> _positionFactor;
+  late Animation<double> _scaleFactor;
+  late Animation<double> _value;
+  late Animation<Color?> _valueColor;
+
+  _RefreshIndicatorMode? _mode;
+  late Future<void> _pendingRefreshFuture;
+  bool? _isIndicatorAtTop;
+  double? _dragOffset;
+
+  static final Animatable<double> _threeQuarterTween = Tween<double>(begin: 0.0, end: 0.75);
+  static final Animatable<double> _kDragSizeFactorLimitTween = Tween<double>(begin: 0.0, end: _kDragSizeFactorLimit);
+  static final Animatable<double> _oneToZeroTween = Tween<double>(begin: 1.0, end: 0.0);
+
+  @override
+  void initState() {
+    super.initState();
+    _positionController = AnimationController(vsync: this);
+    _positionFactor = _positionController.drive(_kDragSizeFactorLimitTween);
+    _value = _positionController.drive(_threeQuarterTween); // The "value" of the circular progress indicator during a drag.
+
+    _scaleController = AnimationController(vsync: this);
+    _scaleFactor = _scaleController.drive(_oneToZeroTween);
+  }
+
+  @override
+  void didChangeDependencies() {
+    final ThemeData theme = Theme.of(context);
+    _valueColor = _positionController.drive(
+      ColorTween(
+        begin: (widget.color ?? theme.accentColor).withOpacity(0.0),
+        end: (widget.color ?? theme.accentColor).withOpacity(1.0),
+      ).chain(CurveTween(
+        curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit)
+      )),
+    );
+    super.didChangeDependencies();
+  }
+
+  @override
+  void dispose() {
+    _positionController.dispose();
+    _scaleController.dispose();
+    super.dispose();
+  }
+
+  bool _handleScrollNotification(ScrollNotification notification) {
+    if (!widget.notificationPredicate(notification))
+      return false;
+    if ((notification is ScrollStartNotification || notification is ScrollUpdateNotification) &&
+        notification.metrics.extentBefore == 0.0 &&
+        _mode == null && _start(notification.metrics.axisDirection)) {
+      setState(() {
+        _mode = _RefreshIndicatorMode.drag;
+      });
+      return false;
+    }
+    bool? indicatorAtTopNow;
+    switch (notification.metrics.axisDirection) {
+      case AxisDirection.down:
+        indicatorAtTopNow = true;
+        break;
+      case AxisDirection.up:
+        indicatorAtTopNow = false;
+        break;
+      case AxisDirection.left:
+      case AxisDirection.right:
+        indicatorAtTopNow = null;
+        break;
+    }
+    if (indicatorAtTopNow != _isIndicatorAtTop) {
+      if (_mode == _RefreshIndicatorMode.drag || _mode == _RefreshIndicatorMode.armed)
+        _dismiss(_RefreshIndicatorMode.canceled);
+    } else if (notification is ScrollUpdateNotification) {
+      if (_mode == _RefreshIndicatorMode.drag || _mode == _RefreshIndicatorMode.armed) {
+        if (notification.metrics.extentBefore > 0.0) {
+          _dismiss(_RefreshIndicatorMode.canceled);
+        } else {
+          _dragOffset = _dragOffset! - notification.scrollDelta!;
+          _checkDragOffset(notification.metrics.viewportDimension);
+        }
+      }
+      if (_mode == _RefreshIndicatorMode.armed && notification.dragDetails == null) {
+        // On iOS start the refresh when the Scrollable bounces back from the
+        // overscroll (ScrollNotification indicating this don't have dragDetails
+        // because the scroll activity is not directly triggered by a drag).
+        _show();
+      }
+    } else if (notification is OverscrollNotification) {
+      if (_mode == _RefreshIndicatorMode.drag || _mode == _RefreshIndicatorMode.armed) {
+        _dragOffset = _dragOffset! - notification.overscroll;
+        _checkDragOffset(notification.metrics.viewportDimension);
+      }
+    } else if (notification is ScrollEndNotification) {
+      switch (_mode) {
+        case _RefreshIndicatorMode.armed:
+          _show();
+          break;
+        case _RefreshIndicatorMode.drag:
+          _dismiss(_RefreshIndicatorMode.canceled);
+          break;
+        default:
+          // do nothing
+          break;
+      }
+    }
+    return false;
+  }
+
+  bool _handleGlowNotification(OverscrollIndicatorNotification notification) {
+    if (notification.depth != 0 || !notification.leading)
+      return false;
+    if (_mode == _RefreshIndicatorMode.drag) {
+      notification.disallowGlow();
+      return true;
+    }
+    return false;
+  }
+
+  bool _start(AxisDirection direction) {
+    assert(_mode == null);
+    assert(_isIndicatorAtTop == null);
+    assert(_dragOffset == null);
+    switch (direction) {
+      case AxisDirection.down:
+        _isIndicatorAtTop = true;
+        break;
+      case AxisDirection.up:
+        _isIndicatorAtTop = false;
+        break;
+      case AxisDirection.left:
+      case AxisDirection.right:
+        _isIndicatorAtTop = null;
+        // we do not support horizontal scroll views.
+        return false;
+    }
+    _dragOffset = 0.0;
+    _scaleController.value = 0.0;
+    _positionController.value = 0.0;
+    return true;
+  }
+
+  void _checkDragOffset(double containerExtent) {
+    assert(_mode == _RefreshIndicatorMode.drag || _mode == _RefreshIndicatorMode.armed);
+    double newValue = _dragOffset! / (containerExtent * _kDragContainerExtentPercentage);
+    if (_mode == _RefreshIndicatorMode.armed)
+      newValue = math.max(newValue, 1.0 / _kDragSizeFactorLimit);
+    _positionController.value = newValue.clamp(0.0, 1.0); // this triggers various rebuilds
+    if (_mode == _RefreshIndicatorMode.drag && _valueColor.value!.alpha == 0xFF)
+      _mode = _RefreshIndicatorMode.armed;
+  }
+
+  // Stop showing the refresh indicator.
+  Future<void> _dismiss(_RefreshIndicatorMode newMode) async {
+    await Future<void>.value();
+    // This can only be called from _show() when refreshing and
+    // _handleScrollNotification in response to a ScrollEndNotification or
+    // direction change.
+    assert(newMode == _RefreshIndicatorMode.canceled || newMode == _RefreshIndicatorMode.done);
+    setState(() {
+      _mode = newMode;
+    });
+    switch (_mode) {
+      case _RefreshIndicatorMode.done:
+        await _scaleController.animateTo(1.0, duration: _kIndicatorScaleDuration);
+        break;
+      case _RefreshIndicatorMode.canceled:
+        await _positionController.animateTo(0.0, duration: _kIndicatorScaleDuration);
+        break;
+      default:
+        assert(false);
+    }
+    if (mounted && _mode == newMode) {
+      _dragOffset = null;
+      _isIndicatorAtTop = null;
+      setState(() {
+        _mode = null;
+      });
+    }
+  }
+
+  void _show() {
+    assert(_mode != _RefreshIndicatorMode.refresh);
+    assert(_mode != _RefreshIndicatorMode.snap);
+    final Completer<void> completer = Completer<void>();
+    _pendingRefreshFuture = completer.future;
+    _mode = _RefreshIndicatorMode.snap;
+    _positionController
+      .animateTo(1.0 / _kDragSizeFactorLimit, duration: _kIndicatorSnapDuration)
+      .then<void>((void value) {
+        if (mounted && _mode == _RefreshIndicatorMode.snap) {
+          assert(widget.onRefresh != null);
+          setState(() {
+            // Show the indeterminate progress indicator.
+            _mode = _RefreshIndicatorMode.refresh;
+          });
+
+          final Future<void> refreshResult = widget.onRefresh();
+          assert(() {
+            if (refreshResult == null)
+              FlutterError.reportError(FlutterErrorDetails(
+                exception: FlutterError(
+                  'The onRefresh callback returned null.\n'
+                  'The RefreshIndicator onRefresh callback must return a Future.'
+                ),
+                context: ErrorDescription('when calling onRefresh'),
+                library: 'material library',
+              ));
+            return true;
+          }());
+          // `refreshResult` has a non-nullable type, but might be null when
+          // running with weak checking, so we need to null check it anyway (and
+          // ignore the warning that the null-handling logic is dead code).
+          if (refreshResult == null) // ignore: dead_code
+            return;
+          refreshResult.whenComplete(() {
+            if (mounted && _mode == _RefreshIndicatorMode.refresh) {
+              completer.complete();
+              _dismiss(_RefreshIndicatorMode.done);
+            }
+          });
+        }
+      });
+  }
+
+  /// Show the refresh indicator and run the refresh callback as if it had
+  /// been started interactively. If this method is called while the refresh
+  /// callback is running, it quietly does nothing.
+  ///
+  /// Creating the [RefreshIndicator] with a [GlobalKey<RefreshIndicatorState>]
+  /// makes it possible to refer to the [RefreshIndicatorState].
+  ///
+  /// The future returned from this method completes when the
+  /// [RefreshIndicator.onRefresh] callback's future completes.
+  ///
+  /// If you await the future returned by this function from a [State], you
+  /// should check that the state is still [mounted] before calling [setState].
+  ///
+  /// When initiated in this manner, the refresh indicator is independent of any
+  /// actual scroll view. It defaults to showing the indicator at the top. To
+  /// show it at the bottom, set `atTop` to false.
+  Future<void> show({ bool atTop = true }) {
+    if (_mode != _RefreshIndicatorMode.refresh &&
+        _mode != _RefreshIndicatorMode.snap) {
+      if (_mode == null)
+        _start(atTop ? AxisDirection.down : AxisDirection.up);
+      _show();
+    }
+    return _pendingRefreshFuture;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterialLocalizations(context));
+    final Widget child = NotificationListener<ScrollNotification>(
+      onNotification: _handleScrollNotification,
+      child: NotificationListener<OverscrollIndicatorNotification>(
+        onNotification: _handleGlowNotification,
+        child: widget.child,
+      ),
+    );
+    assert(() {
+      if (_mode == null) {
+        assert(_dragOffset == null);
+        assert(_isIndicatorAtTop == null);
+      } else {
+        assert(_dragOffset != null);
+        assert(_isIndicatorAtTop != null);
+      }
+      return true;
+    }());
+
+    final bool showIndeterminateIndicator =
+      _mode == _RefreshIndicatorMode.refresh || _mode == _RefreshIndicatorMode.done;
+
+    return Stack(
+      children: <Widget>[
+        child,
+        if (_mode != null) Positioned(
+          top: _isIndicatorAtTop! ? 0.0 : null,
+          bottom: !_isIndicatorAtTop! ? 0.0 : null,
+          left: 0.0,
+          right: 0.0,
+          child: SizeTransition(
+            axisAlignment: _isIndicatorAtTop! ? 1.0 : -1.0,
+            sizeFactor: _positionFactor, // this is what brings it down
+            child: Container(
+              padding: _isIndicatorAtTop!
+                ? EdgeInsets.only(top: widget.displacement)
+                : EdgeInsets.only(bottom: widget.displacement),
+              alignment: _isIndicatorAtTop!
+                ? Alignment.topCenter
+                : Alignment.bottomCenter,
+              child: ScaleTransition(
+                scale: _scaleFactor,
+                child: AnimatedBuilder(
+                  animation: _positionController,
+                  builder: (BuildContext context, Widget? child) {
+                    return RefreshProgressIndicator(
+                      semanticsLabel: widget.semanticsLabel ?? MaterialLocalizations.of(context).refreshIndicatorSemanticLabel,
+                      semanticsValue: widget.semanticsValue,
+                      value: showIndeterminateIndicator ? null : _value.value,
+                      valueColor: _valueColor,
+                      backgroundColor: widget.backgroundColor,
+                      strokeWidth: widget.strokeWidth,
+                    );
+                  },
+                ),
+              ),
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+}
diff --git a/lib/src/material/reorderable_list.dart b/lib/src/material/reorderable_list.dart
new file mode 100644
index 0000000..75a5001
--- /dev/null
+++ b/lib/src/material/reorderable_list.dart
@@ -0,0 +1,652 @@
+// Copyright 2014 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.
+
+import 'dart:math';
+
+import 'package:flute/widgets.dart';
+import 'package:flute/rendering.dart';
+
+import 'debug.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+
+// Examples can assume:
+// class MyDataObject { }
+
+/// The callback used by [ReorderableListView] to move an item to a new
+/// position in a list.
+///
+/// Implementations should remove the corresponding list item at [oldIndex]
+/// and reinsert it at [newIndex].
+///
+/// If [oldIndex] is before [newIndex], removing the item at [oldIndex] from the
+/// list will reduce the list's length by one. Implementations used by
+/// [ReorderableListView] will need to account for this when inserting before
+/// [newIndex].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=3fB1mxOsqJE}
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// final List<MyDataObject> backingList = <MyDataObject>[/* ... */];
+///
+/// void handleReorder(int oldIndex, int newIndex) {
+///   if (oldIndex < newIndex) {
+///     // removing the item at oldIndex will shorten the list by 1.
+///     newIndex -= 1;
+///   }
+///   final MyDataObject element = backingList.removeAt(oldIndex);
+///   backingList.insert(newIndex, element);
+/// }
+/// ```
+/// {@end-tool}
+typedef ReorderCallback = void Function(int oldIndex, int newIndex);
+
+/// A list whose items the user can interactively reorder by dragging.
+///
+/// This class is appropriate for views with a small number of
+/// children because constructing the [List] requires doing work for every
+/// child that could possibly be displayed in the list view instead of just
+/// those children that are actually visible.
+///
+/// All [children] must have a key.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=3fB1mxOsqJE}
+///
+/// This sample shows by dragging the user can reorder the items of the list.
+/// The [onReorder] parameter is required and will be called when a child
+/// widget is dragged to a new position.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold}
+///
+/// ```dart
+/// List<String> _list = List.generate(5, (i) => "${i}");
+///
+/// Widget build(BuildContext context){
+///   return ReorderableListView(
+///     padding : const EdgeInsets.symmetric(horizontal:40),
+///     children:[
+///       for(var i=0 ; i<_list.length ; i++)
+///         ListTile(
+///              key:Key('$i'),
+///              title: Text(_list[i]),
+///         ),
+///     ],
+///     onReorder: (oldIndex, newIndex){
+///       setState((){
+///         if(oldIndex < newIndex){
+///           newIndex-=1;
+///         }
+///         final element = _list.removeAt(oldIndex);
+///         _list.insert(newIndex, element);
+///       });
+///     },
+///   );
+/// }
+///
+/// ```
+///
+///{@end-tool}
+///
+class ReorderableListView extends StatefulWidget {
+
+  /// Creates a reorderable list.
+  ReorderableListView({
+    Key? key,
+    this.header,
+    required this.children,
+    required this.onReorder,
+    this.scrollController,
+    this.scrollDirection = Axis.vertical,
+    this.padding,
+    this.reverse = false,
+  }) : assert(scrollDirection != null),
+       assert(onReorder != null),
+       assert(children != null),
+       assert(
+         children.every((Widget w) => w.key != null),
+         'All children of this widget must have a key.',
+       ),
+       super(key: key);
+
+  /// A non-reorderable header widget to show before the list.
+  ///
+  /// If null, no header will appear before the list.
+  final Widget? header;
+
+  /// The widgets to display.
+  final List<Widget> children;
+
+  /// The [Axis] along which the list scrolls.
+  ///
+  /// List [children] can only drag along this [Axis].
+  final Axis scrollDirection;
+
+  /// Creates a [ScrollPosition] to manage and determine which portion
+  /// of the content is visible in the scroll view.
+  ///
+  /// This can be used in many ways, such as setting an initial scroll offset,
+  /// (via [ScrollController.initialScrollOffset]), reading the current scroll position
+  /// (via [ScrollController.offset]), or changing it (via [ScrollController.jumpTo] or
+  /// [ScrollController.animateTo]).
+  final ScrollController? scrollController;
+
+  /// The amount of space by which to inset the [children].
+  final EdgeInsets? padding;
+
+  /// Whether the scroll view scrolls in the reading direction.
+  ///
+  /// For example, if the reading direction is left-to-right and
+  /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from
+  /// left to right when [reverse] is false and from right to left when
+  /// [reverse] is true.
+  ///
+  /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
+  /// scrolls from top to bottom when [reverse] is false and from bottom to top
+  /// when [reverse] is true.
+  ///
+  /// Defaults to false.
+  final bool reverse;
+
+  /// Called when a list child is dropped into a new position to shuffle the
+  /// underlying list.
+  ///
+  /// This [ReorderableListView] calls [onReorder] after a list child is dropped
+  /// into a new position.
+  final ReorderCallback onReorder;
+
+  @override
+  _ReorderableListViewState createState() => _ReorderableListViewState();
+}
+
+// This top-level state manages an Overlay that contains the list and
+// also any Draggables it creates.
+//
+// _ReorderableListContent manages the list itself and reorder operations.
+//
+// The Overlay doesn't properly keep state by building new overlay entries,
+// and so we cache a single OverlayEntry for use as the list layer.
+// That overlay entry then builds a _ReorderableListContent which may
+// insert Draggables into the Overlay above itself.
+class _ReorderableListViewState extends State<ReorderableListView> {
+  // We use an inner overlay so that the dragging list item doesn't draw outside of the list itself.
+  final GlobalKey _overlayKey = GlobalKey(debugLabel: '$ReorderableListView overlay key');
+
+  // This entry contains the scrolling list itself.
+  late OverlayEntry _listOverlayEntry;
+
+  @override
+  void initState() {
+    super.initState();
+    _listOverlayEntry = OverlayEntry(
+      opaque: true,
+      builder: (BuildContext context) {
+        return _ReorderableListContent(
+          header: widget.header,
+          children: widget.children,
+          scrollController: widget.scrollController,
+          scrollDirection: widget.scrollDirection,
+          onReorder: widget.onReorder,
+          padding: widget.padding,
+          reverse: widget.reverse,
+        );
+      },
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Overlay(
+      key: _overlayKey,
+      initialEntries: <OverlayEntry>[
+        _listOverlayEntry,
+    ]);
+  }
+}
+
+// This widget is responsible for the inside of the Overlay in the
+// ReorderableListView.
+class _ReorderableListContent extends StatefulWidget {
+  const _ReorderableListContent({
+    required this.header,
+    required this.children,
+    required this.scrollController,
+    required this.scrollDirection,
+    required this.padding,
+    required this.onReorder,
+    required this.reverse,
+  });
+
+  final Widget? header;
+  final List<Widget> children;
+  final ScrollController? scrollController;
+  final Axis scrollDirection;
+  final EdgeInsets? padding;
+  final ReorderCallback onReorder;
+  final bool reverse;
+
+  @override
+  _ReorderableListContentState createState() => _ReorderableListContentState();
+}
+
+class _ReorderableListContentState extends State<_ReorderableListContent> with TickerProviderStateMixin<_ReorderableListContent> {
+
+  // The extent along the [widget.scrollDirection] axis to allow a child to
+  // drop into when the user reorders list children.
+  //
+  // This value is used when the extents haven't yet been calculated from
+  // the currently dragging widget, such as when it first builds.
+  static const double _defaultDropAreaExtent = 100.0;
+
+  // How long an animation to reorder an element in the list takes.
+  static const Duration _reorderAnimationDuration = Duration(milliseconds: 200);
+
+  // How long an animation to scroll to an off-screen element in the
+  // list takes.
+  static const Duration _scrollAnimationDuration = Duration(milliseconds: 200);
+
+  // Controls scrolls and measures scroll progress.
+  late ScrollController _scrollController;
+
+  // This controls the entrance of the dragging widget into a new place.
+  late AnimationController _entranceController;
+
+  // This controls the 'ghost' of the dragging widget, which is left behind
+  // where the widget used to be.
+  late AnimationController _ghostController;
+
+  // The member of widget.children currently being dragged.
+  //
+  // Null if no drag is underway.
+  Key? _dragging;
+
+  // The last computed size of the feedback widget being dragged.
+  Size? _draggingFeedbackSize;
+
+  // The location that the dragging widget occupied before it started to drag.
+  int _dragStartIndex = 0;
+
+  // The index that the dragging widget most recently left.
+  // This is used to show an animation of the widget's position.
+  int _ghostIndex = 0;
+
+  // The index that the dragging widget currently occupies.
+  int _currentIndex = 0;
+
+  // The widget to move the dragging widget too after the current index.
+  int _nextIndex = 0;
+
+  // Whether or not we are currently scrolling this view to show a widget.
+  bool _scrolling = false;
+
+  double get _dropAreaExtent {
+    if (_draggingFeedbackSize == null) {
+      return _defaultDropAreaExtent;
+    }
+    final double dropAreaWithoutMargin;
+    switch (widget.scrollDirection) {
+      case Axis.horizontal:
+        dropAreaWithoutMargin = _draggingFeedbackSize!.width;
+        break;
+      case Axis.vertical:
+        dropAreaWithoutMargin = _draggingFeedbackSize!.height;
+        break;
+    }
+    return dropAreaWithoutMargin;
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _entranceController = AnimationController(vsync: this, duration: _reorderAnimationDuration);
+    _ghostController = AnimationController(vsync: this, duration: _reorderAnimationDuration);
+    _entranceController.addStatusListener(_onEntranceStatusChanged);
+  }
+
+  @override
+  void didChangeDependencies() {
+    _scrollController = widget.scrollController ?? PrimaryScrollController.of(context) ?? ScrollController();
+    super.didChangeDependencies();
+  }
+
+  @override
+  void dispose() {
+    _entranceController.dispose();
+    _ghostController.dispose();
+    super.dispose();
+  }
+
+  // Animates the droppable space from _currentIndex to _nextIndex.
+  void _requestAnimationToNextIndex() {
+    if (_entranceController.isCompleted) {
+      _ghostIndex = _currentIndex;
+      if (_nextIndex == _currentIndex) {
+        return;
+      }
+      _currentIndex = _nextIndex;
+      _ghostController.reverse(from: 1.0);
+      _entranceController.forward(from: 0.0);
+    }
+  }
+
+  // Requests animation to the latest next index if it changes during an animation.
+  void _onEntranceStatusChanged(AnimationStatus status) {
+    if (status == AnimationStatus.completed) {
+      setState(() {
+        _requestAnimationToNextIndex();
+      });
+    }
+  }
+
+  // Scrolls to a target context if that context is not on the screen.
+  void _scrollTo(BuildContext context) {
+    if (_scrolling)
+      return;
+    final RenderObject contextObject = context.findRenderObject()!;
+    final RenderAbstractViewport viewport = RenderAbstractViewport.of(contextObject)!;
+    assert(viewport != null);
+    // If and only if the current scroll offset falls in-between the offsets
+    // necessary to reveal the selected context at the top or bottom of the
+    // screen, then it is already on-screen.
+    final double margin = _dropAreaExtent;
+    final double scrollOffset = _scrollController.offset;
+    final double topOffset = max(
+      _scrollController.position.minScrollExtent,
+      viewport.getOffsetToReveal(contextObject, 0.0).offset - margin,
+    );
+    final double bottomOffset = min(
+      _scrollController.position.maxScrollExtent,
+      viewport.getOffsetToReveal(contextObject, 1.0).offset + margin,
+    );
+    final bool onScreen = scrollOffset <= topOffset && scrollOffset >= bottomOffset;
+
+    // If the context is off screen, then we request a scroll to make it visible.
+    if (!onScreen) {
+      _scrolling = true;
+      _scrollController.position.animateTo(
+        scrollOffset < bottomOffset ? bottomOffset : topOffset,
+        duration: _scrollAnimationDuration,
+        curve: Curves.easeInOut,
+      ).then((void value) {
+        setState(() {
+          _scrolling = false;
+        });
+      });
+    }
+  }
+
+  // Wraps children in Row or Column, so that the children flow in
+  // the widget's scrollDirection.
+  Widget _buildContainerForScrollDirection({ required List<Widget> children }) {
+    switch (widget.scrollDirection) {
+      case Axis.horizontal:
+        return Row(children: children);
+      case Axis.vertical:
+        return Column(children: children);
+    }
+  }
+
+  // Wraps one of the widget's children in a DragTarget and Draggable.
+  // Handles up the logic for dragging and reordering items in the list.
+  Widget _wrap(Widget toWrap, int index, BoxConstraints constraints) {
+    assert(toWrap.key != null);
+    final _ReorderableListViewChildGlobalKey keyIndexGlobalKey = _ReorderableListViewChildGlobalKey(toWrap.key!, this);
+    // We pass the toWrapWithGlobalKey into the Draggable so that when a list
+    // item gets dragged, the accessibility framework can preserve the selected
+    // state of the dragging item.
+
+    // Starts dragging toWrap.
+    void onDragStarted() {
+      setState(() {
+        _dragging = toWrap.key;
+        _dragStartIndex = index;
+        _ghostIndex = index;
+        _currentIndex = index;
+        _entranceController.value = 1.0;
+        _draggingFeedbackSize = keyIndexGlobalKey.currentContext!.size;
+      });
+    }
+
+    // Places the value from startIndex one space before the element at endIndex.
+    void reorder(int startIndex, int endIndex) {
+      setState(() {
+        if (startIndex != endIndex)
+          widget.onReorder(startIndex, endIndex);
+        // Animates leftover space in the drop area closed.
+        _ghostController.reverse(from: 0);
+        _entranceController.reverse(from: 0);
+        _dragging = null;
+      });
+    }
+
+    // Drops toWrap into the last position it was hovering over.
+    void onDragEnded() {
+      reorder(_dragStartIndex, _currentIndex);
+    }
+
+    Widget wrapWithSemantics() {
+      // First, determine which semantics actions apply.
+      final Map<CustomSemanticsAction, VoidCallback> semanticsActions = <CustomSemanticsAction, VoidCallback>{};
+
+      // Create the appropriate semantics actions.
+      void moveToStart() => reorder(index, 0);
+      void moveToEnd() => reorder(index, widget.children.length);
+      void moveBefore() => reorder(index, index - 1);
+      // To move after, we go to index+2 because we are moving it to the space
+      // before index+2, which is after the space at index+1.
+      void moveAfter() => reorder(index, index + 2);
+
+      final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+
+      // If the item can move to before its current position in the list.
+      if (index > 0) {
+        semanticsActions[CustomSemanticsAction(label: localizations.reorderItemToStart)] = moveToStart;
+        String reorderItemBefore = localizations.reorderItemUp;
+        if (widget.scrollDirection == Axis.horizontal) {
+          reorderItemBefore = Directionality.of(context) == TextDirection.ltr
+              ? localizations.reorderItemLeft
+              : localizations.reorderItemRight;
+        }
+        semanticsActions[CustomSemanticsAction(label: reorderItemBefore)] = moveBefore;
+      }
+
+      // If the item can move to after its current position in the list.
+      if (index < widget.children.length - 1) {
+        String reorderItemAfter = localizations.reorderItemDown;
+        if (widget.scrollDirection == Axis.horizontal) {
+          reorderItemAfter = Directionality.of(context) == TextDirection.ltr
+              ? localizations.reorderItemRight
+              : localizations.reorderItemLeft;
+        }
+        semanticsActions[CustomSemanticsAction(label: reorderItemAfter)] = moveAfter;
+        semanticsActions[CustomSemanticsAction(label: localizations.reorderItemToEnd)] = moveToEnd;
+      }
+
+      // We pass toWrap with a GlobalKey into the Draggable so that when a list
+      // item gets dragged, the accessibility framework can preserve the selected
+      // state of the dragging item.
+      //
+      // We also apply the relevant custom accessibility actions for moving the item
+      // up, down, to the start, and to the end of the list.
+      return KeyedSubtree(
+        key: keyIndexGlobalKey,
+        child: MergeSemantics(
+          child: Semantics(
+            customSemanticsActions: semanticsActions,
+            child: toWrap,
+          ),
+        ),
+      );
+    }
+
+    Widget buildDragTarget(BuildContext context, List<Key?> acceptedCandidates, List<dynamic> rejectedCandidates) {
+      final Widget toWrapWithSemantics = wrapWithSemantics();
+
+      // We build the draggable inside of a layout builder so that we can
+      // constrain the size of the feedback dragging widget.
+      Widget child = LongPressDraggable<Key>(
+        maxSimultaneousDrags: 1,
+        axis: widget.scrollDirection,
+        data: toWrap.key,
+        ignoringFeedbackSemantics: false,
+        feedback: Container(
+          alignment: Alignment.topLeft,
+          // These constraints will limit the cross axis of the drawn widget.
+          constraints: constraints,
+          child: Material(
+            elevation: 6.0,
+            child: toWrapWithSemantics,
+          ),
+        ),
+        child: _dragging == toWrap.key ? const SizedBox() : toWrapWithSemantics,
+        childWhenDragging: const SizedBox(),
+        dragAnchor: DragAnchor.child,
+        onDragStarted: onDragStarted,
+        // When the drag ends inside a DragTarget widget, the drag
+        // succeeds, and we reorder the widget into position appropriately.
+        onDragCompleted: onDragEnded,
+        // When the drag does not end inside a DragTarget widget, the
+        // drag fails, but we still reorder the widget to the last position it
+        // had been dragged to.
+        onDraggableCanceled: (Velocity velocity, Offset offset) {
+          onDragEnded();
+        },
+      );
+
+      // The target for dropping at the end of the list doesn't need to be
+      // draggable.
+      if (index >= widget.children.length) {
+        child = toWrap;
+      }
+
+      // Determine the size of the drop area to show under the dragging widget.
+      final Widget spacing;
+      switch (widget.scrollDirection) {
+        case Axis.horizontal:
+          spacing = SizedBox(width: _dropAreaExtent);
+          break;
+        case Axis.vertical:
+          spacing = SizedBox(height: _dropAreaExtent);
+          break;
+      }
+
+      // We open up a space under where the dragging widget currently is to
+      // show it can be dropped.
+      if (_currentIndex == index) {
+        return _buildContainerForScrollDirection(children: <Widget>[
+          SizeTransition(
+            sizeFactor: _entranceController,
+            axis: widget.scrollDirection,
+            child: spacing,
+          ),
+          child,
+        ]);
+      }
+      // We close up the space under where the dragging widget previously was
+      // with the ghostController animation.
+      if (_ghostIndex == index) {
+        return _buildContainerForScrollDirection(children: <Widget>[
+          SizeTransition(
+            sizeFactor: _ghostController,
+            axis: widget.scrollDirection,
+            child: spacing,
+          ),
+          child,
+        ]);
+      }
+      return child;
+    }
+
+    // We wrap the drag target in a Builder so that we can scroll to its specific context.
+    return Builder(builder: (BuildContext context) {
+      return DragTarget<Key>(
+        builder: buildDragTarget,
+        onWillAccept: (Key? toAccept) {
+          setState(() {
+            _nextIndex = index;
+            _requestAnimationToNextIndex();
+          });
+          _scrollTo(context);
+          // If the target is not the original starting point, then we will accept the drop.
+          return _dragging == toAccept && toAccept != toWrap.key;
+        },
+        onAccept: (Key accepted) { },
+        onLeave: (Object? leaving) { },
+      );
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterialLocalizations(context));
+    // We use the layout builder to constrain the cross-axis size of dragging child widgets.
+    return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
+      const Key endWidgetKey = Key('DraggableList - End Widget');
+      final Widget finalDropArea;
+      switch (widget.scrollDirection) {
+        case Axis.horizontal:
+          finalDropArea = SizedBox(
+            key: endWidgetKey,
+            width: _defaultDropAreaExtent,
+            height: constraints.maxHeight,
+          );
+          break;
+        case Axis.vertical:
+          finalDropArea = SizedBox(
+            key: endWidgetKey,
+            height: _defaultDropAreaExtent,
+            width: constraints.maxWidth,
+          );
+          break;
+      }
+
+      // If the reorderable list only has one child element, reordering
+      // should not be allowed.
+      final bool hasMoreThanOneChildElement = widget.children.length > 1;
+
+      return SingleChildScrollView(
+        scrollDirection: widget.scrollDirection,
+        padding: widget.padding,
+        controller: _scrollController,
+        reverse: widget.reverse,
+        child: _buildContainerForScrollDirection(
+          children: <Widget>[
+            if (widget.reverse && hasMoreThanOneChildElement) _wrap(finalDropArea, widget.children.length, constraints),
+            if (widget.header != null) widget.header!,
+            for (int i = 0; i < widget.children.length; i += 1) _wrap(widget.children[i], i, constraints),
+            if (!widget.reverse && hasMoreThanOneChildElement) _wrap(finalDropArea, widget.children.length, constraints),
+          ],
+        ),
+      );
+    });
+  }
+}
+
+// A global key that takes its identity from the object and uses a value of a
+// particular type to identify itself.
+//
+// The difference with GlobalObjectKey is that it uses [==] instead of [identical]
+// of the objects used to generate widgets.
+@optionalTypeArgs
+class _ReorderableListViewChildGlobalKey extends GlobalObjectKey {
+
+  const _ReorderableListViewChildGlobalKey(this.subKey, this.state) : super(subKey);
+
+  final Key subKey;
+
+  final _ReorderableListContentState state;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _ReorderableListViewChildGlobalKey
+        && other.subKey == subKey
+        && other.state == state;
+  }
+
+  @override
+  int get hashCode => hashValues(subKey, state);
+}
diff --git a/lib/src/material/scaffold.dart b/lib/src/material/scaffold.dart
new file mode 100644
index 0000000..e56724c
--- /dev/null
+++ b/lib/src/material/scaffold.dart
@@ -0,0 +1,3445 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+import 'package:flute/gestures.dart' show DragStartBehavior;
+
+import 'app_bar.dart';
+import 'bottom_sheet.dart';
+import 'button_bar.dart';
+import 'colors.dart';
+import 'curves.dart';
+import 'debug.dart';
+import 'divider.dart';
+import 'drawer.dart';
+import 'flexible_space_bar.dart';
+import 'floating_action_button.dart';
+import 'floating_action_button_location.dart';
+import 'material.dart';
+import 'snack_bar.dart';
+import 'snack_bar_theme.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+// Examples can assume:
+// late TabController tabController;
+// void setState(VoidCallback fn) { }
+// late String appBarTitle;
+// late int tabCount;
+// late TickerProvider tickerProvider;
+
+const FloatingActionButtonLocation _kDefaultFloatingActionButtonLocation = FloatingActionButtonLocation.endFloat;
+const FloatingActionButtonAnimator _kDefaultFloatingActionButtonAnimator = FloatingActionButtonAnimator.scaling;
+
+const Curve _standardBottomSheetCurve = standardEasing;
+// When the top of the BottomSheet crosses this threshold, it will start to
+// shrink the FAB and show a scrim.
+const double _kBottomSheetDominatesPercentage = 0.3;
+const double _kMinBottomSheetScrimOpacity = 0.1;
+const double _kMaxBottomSheetScrimOpacity = 0.6;
+
+enum _ScaffoldSlot {
+  body,
+  appBar,
+  bodyScrim,
+  bottomSheet,
+  snackBar,
+  persistentFooter,
+  bottomNavigationBar,
+  floatingActionButton,
+  drawer,
+  endDrawer,
+  statusBar,
+}
+
+/// Manages [SnackBar]s for descendant [Scaffold]s.
+///
+/// This class provides APIs for showing snack bars.
+///
+/// To display a snack bar, obtain the [ScaffoldMessengerState] for the current
+/// [BuildContext] via [ScaffoldMessenger.of] and use the
+/// [ScaffoldMessengerState.showSnackBar] function.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_center}
+///
+/// Here is an example of showing a [SnackBar] when the user presses a button.
+///
+/// ```dart
+///   Widget build(BuildContext context) {
+///     return OutlinedButton(
+///       onPressed: () {
+///         ScaffoldMessenger.of(context).showSnackBar(
+///           const SnackBar(
+///             content: Text('A SnackBar has been shown.'),
+///           ),
+///         );
+///       },
+///       child: const Text('Show SnackBar'),
+///     );
+///   }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [SnackBar], which is a temporary notification typically shown near the
+///    bottom of the app using the [ScaffoldMessengerState.showSnackBar] method.
+///  * [debugCheckHasScaffoldMessenger], which asserts that the given context
+///    has a [ScaffoldMessenger] ancestor.
+///  * Cookbook: [Display a SnackBar](https://flutter.dev/docs/cookbook/design/snackbars)
+class ScaffoldMessenger extends StatefulWidget {
+  /// Creates a widget that manages [SnackBar]s for [Scaffold] descendants.
+  const ScaffoldMessenger({
+    Key? key,
+    required this.child,
+  }) : assert(child != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The state from the closest instance of this class that encloses the given
+  /// context.
+  ///
+  /// {@tool dartpad --template=stateless_widget_scaffold_center}
+  /// Typical usage of the [ScaffoldMessenger.of] function is to call it in
+  /// response to a user gesture or an application state change.
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return ElevatedButton(
+  ///     child: const Text('SHOW A SNACKBAR'),
+  ///     onPressed: () {
+  ///       ScaffoldMessenger.of(context).showSnackBar(
+  ///         const SnackBar(
+  ///           content: Text('Have a snack!'),
+  ///         ),
+  ///       );
+  ///     },
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// A less elegant but more expedient solution is to assign a [GlobalKey] to the
+  /// [ScaffoldMessenger], then use the `key.currentState` property to obtain the
+  /// [ScaffoldMessengerState] rather than using the [ScaffoldMessenger.of]
+  /// function. The [MaterialApp.scaffoldMessengerKey] refers to the root
+  /// ScaffoldMessenger that is provided by default.
+  ///
+  /// {@tool dartpad --template=freeform}
+  /// Sometimes [SnackBar]s are produced by code that doesn't have ready access
+  /// to a valid [BuildContext]. One such example of this is when you show a
+  /// SnackBar from a method outside of the `build` function. In these
+  /// cases, you can assign a [GlobalKey] to the [ScaffoldMessenger]. This
+  /// example shows a key being used to obtain the [ScaffoldMessengerState]
+  /// provided by the [MaterialApp].
+  ///
+  /// ```dart imports
+  /// import 'package:flute/material.dart';
+  /// ```
+  /// ```dart
+  /// void main() => runApp(MyApp());
+  ///
+  /// class MyApp extends StatefulWidget {
+  ///   @override
+  ///  _MyAppState createState() => _MyAppState();
+  /// }
+  ///
+  /// class _MyAppState extends State<MyApp> {
+  ///   final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
+  ///   int _counter = 0;
+  ///
+  ///   void _incrementCounter() {
+  ///     setState(() {
+  ///       _counter++;
+  ///     });
+  ///     if (_counter % 10 == 0) {
+  ///       _scaffoldMessengerKey.currentState!.showSnackBar(const SnackBar(
+  ///         content: Text('A multiple of ten!'),
+  ///       ));
+  ///     }
+  ///   }
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return MaterialApp(
+  ///       scaffoldMessengerKey: _scaffoldMessengerKey,
+  ///       home: Scaffold(
+  ///         appBar: AppBar(title: Text('ScaffoldMessenger Demo')),
+  ///         body: Center(
+  ///           child: Column(
+  ///             mainAxisAlignment: MainAxisAlignment.center,
+  ///             children: <Widget>[
+  ///               Text(
+  ///                 'You have pushed the button this many times:',
+  ///               ),
+  ///               Text(
+  ///                 '$_counter',
+  ///                 style: Theme.of(context).textTheme.headline4,
+  ///               ),
+  ///             ],
+  ///           ),
+  ///         ),
+  ///         floatingActionButton: FloatingActionButton(
+  ///           onPressed: _incrementCounter,
+  ///           tooltip: 'Increment',
+  ///           child: Icon(Icons.add),
+  ///         ),
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  ///
+  /// ```
+  /// {@end-tool}
+  ///
+  /// If there is no [ScaffoldMessenger] in scope, then this will assert in
+  /// debug mode, and throw an exception in release mode.
+  ///
+  /// See also:
+  ///
+  ///  * [maybeOf], which is a similar function but will return null instead of
+  ///    throwing if there is no [ScaffoldMessenger] ancestor.
+  ///  * [debugCheckHasScaffoldMessenger], which asserts that the given context
+  ///    has a [ScaffoldMessenger] ancestor.
+  static ScaffoldMessengerState of(BuildContext context) {
+    assert(context != null);
+    assert(debugCheckHasScaffoldMessenger(context));
+
+    final _ScaffoldMessengerScope scope = context.dependOnInheritedWidgetOfExactType<_ScaffoldMessengerScope>()!;
+    return scope._scaffoldMessengerState;
+  }
+
+  /// The state from the closest instance of this class that encloses the given
+  /// context, if any.
+  ///
+  /// Will return null if a [ScaffoldMessenger] is not found in the given context.
+  ///
+  /// See also:
+  ///
+  ///  * [of], which is a similar function, except that it will throw an
+  ///    exception if a [ScaffoldMessenger] is not found in the given context.
+  static ScaffoldMessengerState? maybeOf(BuildContext context) {
+    assert(context != null);
+
+    final _ScaffoldMessengerScope? scope = context.dependOnInheritedWidgetOfExactType<_ScaffoldMessengerScope>();
+    return scope?._scaffoldMessengerState;
+  }
+
+  @override
+  ScaffoldMessengerState createState() => ScaffoldMessengerState();
+}
+
+/// State for a [ScaffoldMessenger].
+///
+/// A [ScaffoldMessengerState] object can be used to [showSnackBar] for every
+/// registered [Scaffold] that is a descendant of the associated
+/// [ScaffoldMessenger]. Scaffolds will register to receive [SnackBar]s from
+/// their closest ScaffoldMessenger ancestor.
+///
+/// Typically obtained via [ScaffoldMessenger.of].
+class ScaffoldMessengerState extends State<ScaffoldMessenger> with TickerProviderStateMixin {
+  final LinkedHashSet<ScaffoldState> _scaffolds = LinkedHashSet<ScaffoldState>();
+  final Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>> _snackBars = Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>>();
+  AnimationController? _snackBarController;
+  Timer? _snackBarTimer;
+  bool? _accessibleNavigation;
+
+  @override
+  void didChangeDependencies() {
+    final MediaQueryData mediaQuery = MediaQuery.of(context);
+    // If we transition from accessible navigation to non-accessible navigation
+    // and there is a SnackBar that would have timed out that has already
+    // completed its timer, dismiss that SnackBar. If the timer hasn't finished
+    // yet, let it timeout as normal.
+    if (_accessibleNavigation == true
+        && !mediaQuery.accessibleNavigation
+        && _snackBarTimer != null
+        && !_snackBarTimer!.isActive) {
+      hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
+    }
+    _accessibleNavigation = mediaQuery.accessibleNavigation;
+    super.didChangeDependencies();
+  }
+
+  void _register(ScaffoldState scaffold) {
+    _scaffolds.add(scaffold);
+    if (_snackBars.isNotEmpty) {
+      scaffold._updateSnackBar();
+    }
+  }
+
+  void _unregister(ScaffoldState scaffold) {
+    final bool removed = _scaffolds.remove(scaffold);
+    // ScaffoldStates should only be removed once.
+    assert(removed);
+  }
+
+  /// Shows  a [SnackBar] across all registered [Scaffold]s.
+  ///
+  /// A scaffold can show at most one snack bar at a time. If this function is
+  /// called while another snack bar is already visible, the given snack bar
+  /// will be added to a queue and displayed after the earlier snack bars have
+  /// closed.
+  ///
+  /// To control how long a [SnackBar] remains visible, use [SnackBar.duration].
+  ///
+  /// To remove the [SnackBar] with an exit animation, use [hideCurrentSnackBar]
+  /// or call [ScaffoldFeatureController.close] on the returned
+  /// [ScaffoldFeatureController]. To remove a [SnackBar] suddenly (without an
+  /// animation), use [removeCurrentSnackBar].
+  ///
+  /// See [ScaffoldMessenger.of] for information about how to obtain the
+  /// [ScaffoldMessengerState].
+  ///
+  /// {@tool dartpad --template=stateless_widget_scaffold_center}
+  ///
+  /// Here is an example of showing a [SnackBar] when the user presses a button.
+  ///
+  /// ```dart
+  ///   Widget build(BuildContext context) {
+  ///     return OutlinedButton(
+  ///       onPressed: () {
+  ///         ScaffoldMessenger.of(context).showSnackBar(
+  ///           const SnackBar(
+  ///             content: Text('A SnackBar has been shown.'),
+  ///           ),
+  ///         );
+  ///       },
+  ///       child: const Text('Show SnackBar'),
+  ///     );
+  ///   }
+  /// ```
+  /// {@end-tool}
+  ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(SnackBar snackBar) {
+    _snackBarController ??= SnackBar.createAnimationController(vsync: this)
+      ..addStatusListener(_handleStatusChanged);
+    if (_snackBars.isEmpty) {
+      assert(_snackBarController!.isDismissed);
+      _snackBarController!.forward();
+    }
+    late ScaffoldFeatureController<SnackBar, SnackBarClosedReason> controller;
+    controller = ScaffoldFeatureController<SnackBar, SnackBarClosedReason>._(
+      // We provide a fallback key so that if back-to-back snackbars happen to
+      // match in structure, material ink splashes and highlights don't survive
+      // from one to the next.
+      snackBar.withAnimation(_snackBarController!, fallbackKey: UniqueKey()),
+      Completer<SnackBarClosedReason>(),
+        () {
+          assert(_snackBars.first == controller);
+          hideCurrentSnackBar(reason: SnackBarClosedReason.hide);
+        },
+      null, // SnackBar doesn't use a builder function so setState() wouldn't rebuild it
+    );
+    setState(() {
+      _snackBars.addLast(controller);
+    });
+    _updateScaffolds();
+    return controller;
+  }
+
+  void _handleStatusChanged(AnimationStatus status) {
+    switch (status) {
+      case AnimationStatus.dismissed:
+        assert(_snackBars.isNotEmpty);
+        setState(() {
+          _snackBars.removeFirst();
+        });
+        _updateScaffolds();
+        if (_snackBars.isNotEmpty) {
+          _snackBarController!.forward();
+        }
+        break;
+      case AnimationStatus.completed:
+        setState(() {
+          assert(_snackBarTimer == null);
+          // build will create a new timer if necessary to dismiss the snackBar.
+        });
+        _updateScaffolds();
+        break;
+      case AnimationStatus.forward:
+        break;
+      case AnimationStatus.reverse:
+        break;
+    }
+  }
+
+  void _updateScaffolds() {
+    for (final ScaffoldState scaffold in _scaffolds) {
+      scaffold._updateSnackBar();
+    }
+  }
+
+  /// Removes the current [SnackBar] (if any) immediately from registered
+  /// [Scaffold]s.
+  ///
+  /// The removed snack bar does not run its normal exit animation. If there are
+  /// any queued snack bars, they begin their entrance animation immediately.
+  void removeCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.remove }) {
+    assert(reason != null);
+    if (_snackBars.isEmpty)
+      return;
+    final Completer<SnackBarClosedReason> completer = _snackBars.first._completer;
+    if (!completer.isCompleted)
+      completer.complete(reason);
+    _snackBarTimer?.cancel();
+    _snackBarTimer = null;
+    // This will trigger the animation's status callback.
+    _snackBarController!.value = 0.0;
+  }
+
+  /// Removes the current [SnackBar] by running its normal exit animation.
+  ///
+  /// The closed completer is called after the animation is complete.
+  void hideCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.hide }) {
+    assert(reason != null);
+    if (_snackBars.isEmpty || _snackBarController!.status == AnimationStatus.dismissed)
+      return;
+    final Completer<SnackBarClosedReason> completer = _snackBars.first._completer;
+    if (_accessibleNavigation!) {
+      _snackBarController!.value = 0.0;
+      completer.complete(reason);
+    } else {
+      _snackBarController!.reverse().then<void>((void value) {
+        assert(mounted);
+        if (!completer.isCompleted)
+          completer.complete(reason);
+      });
+    }
+    _snackBarTimer?.cancel();
+    _snackBarTimer = null;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    final MediaQueryData mediaQuery = MediaQuery.of(context);
+    _accessibleNavigation = mediaQuery.accessibleNavigation;
+
+    if (_snackBars.isNotEmpty) {
+      final ModalRoute<dynamic>? route = ModalRoute.of(context);
+      if (route == null || route.isCurrent) {
+        if (_snackBarController!.isCompleted && _snackBarTimer == null) {
+          final SnackBar snackBar = _snackBars.first._widget;
+          _snackBarTimer = Timer(snackBar.duration, () {
+            assert(_snackBarController!.status == AnimationStatus.forward ||
+                   _snackBarController!.status == AnimationStatus.completed);
+            // Look up MediaQuery again in case the setting changed.
+            final MediaQueryData mediaQuery = MediaQuery.of(context);
+            if (mediaQuery.accessibleNavigation && snackBar.action != null)
+              return;
+            hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
+          });
+        }
+      }
+    }
+
+    return _ScaffoldMessengerScope(
+      scaffoldMessengerState: this,
+      child: widget.child,
+    );
+  }
+
+  @override
+  void dispose() {
+    _snackBarController?.dispose();
+    _snackBarTimer?.cancel();
+    _snackBarTimer = null;
+    super.dispose();
+  }
+}
+
+class _ScaffoldMessengerScope extends InheritedWidget {
+  const _ScaffoldMessengerScope({
+    Key? key,
+    required Widget child,
+    required ScaffoldMessengerState scaffoldMessengerState,
+  }) : _scaffoldMessengerState = scaffoldMessengerState,
+      super(key: key, child: child);
+
+  final ScaffoldMessengerState _scaffoldMessengerState;
+
+  @override
+  bool updateShouldNotify(_ScaffoldMessengerScope old) => _scaffoldMessengerState != old._scaffoldMessengerState;
+}
+
+/// The geometry of the [Scaffold] after all its contents have been laid out
+/// except the [FloatingActionButton].
+///
+/// The [Scaffold] passes this pre-layout geometry to its
+/// [FloatingActionButtonLocation], which produces an [Offset] that the
+/// [Scaffold] uses to position the [FloatingActionButton].
+///
+/// For a description of the [Scaffold]'s geometry after it has
+/// finished laying out, see the [ScaffoldGeometry].
+@immutable
+class ScaffoldPrelayoutGeometry {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const ScaffoldPrelayoutGeometry({
+    required this.bottomSheetSize,
+    required this.contentBottom,
+    required this.contentTop,
+    required this.floatingActionButtonSize,
+    required this.minInsets,
+    required this.minViewPadding,
+    required this.scaffoldSize,
+    required this.snackBarSize,
+    required this.textDirection,
+  });
+
+  /// The [Size] of [Scaffold.floatingActionButton].
+  ///
+  /// If [Scaffold.floatingActionButton] is null, this will be [Size.zero].
+  final Size floatingActionButtonSize;
+
+  /// The [Size] of the [Scaffold]'s [BottomSheet].
+  ///
+  /// If the [Scaffold] is not currently showing a [BottomSheet],
+  /// this will be [Size.zero].
+  final Size bottomSheetSize;
+
+  /// The vertical distance from the Scaffold's origin to the bottom of
+  /// [Scaffold.body].
+  ///
+  /// This is useful in a [FloatingActionButtonLocation] designed to
+  /// place the [FloatingActionButton] at the bottom of the screen, while
+  /// keeping it above the [BottomSheet], the [Scaffold.bottomNavigationBar],
+  /// or the keyboard.
+  ///
+  /// The [Scaffold.body] is laid out with respect to [minInsets] already. This
+  /// means that a [FloatingActionButtonLocation] does not need to factor in
+  /// [EdgeInsets.bottom] of [minInsets] when aligning a [FloatingActionButton]
+  /// to [contentBottom].
+  final double contentBottom;
+
+  /// The vertical distance from the [Scaffold]'s origin to the top of
+  /// [Scaffold.body].
+  ///
+  /// This is useful in a [FloatingActionButtonLocation] designed to
+  /// place the [FloatingActionButton] at the top of the screen, while
+  /// keeping it below the [Scaffold.appBar].
+  ///
+  /// The [Scaffold.body] is laid out with respect to [minInsets] already. This
+  /// means that a [FloatingActionButtonLocation] does not need to factor in
+  /// [EdgeInsets.top] of [minInsets] when aligning a [FloatingActionButton] to
+  /// [contentTop].
+  final double contentTop;
+
+  /// The minimum padding to inset the [FloatingActionButton] by for it
+  /// to remain visible.
+  ///
+  /// This value is the result of calling [MediaQueryData.padding] in the
+  /// [Scaffold]'s [BuildContext],
+  /// and is useful for insetting the [FloatingActionButton] to avoid features like
+  /// the system status bar or the keyboard.
+  ///
+  /// If [Scaffold.resizeToAvoidBottomInset] is set to false,
+  /// [EdgeInsets.bottom] of [minInsets] will be 0.0.
+  final EdgeInsets minInsets;
+
+  /// The minimum padding to inset interactive elements to be within a safe,
+  /// un-obscured space.
+  ///
+  /// This value reflects the [MediaQueryData.viewPadding] of the [Scaffold]'s
+  /// [BuildContext] when [Scaffold.resizeToAvoidBottomInset] is false or and
+  /// the [MediaQueryData.viewInsets] > 0.0. This helps distinguish between
+  /// different types of obstructions on the screen, such as software keyboards
+  /// and physical device notches.
+  final EdgeInsets minViewPadding;
+
+  /// The [Size] of the whole [Scaffold].
+  ///
+  /// If the [Size] of the [Scaffold]'s contents is modified by values such as
+  /// [Scaffold.resizeToAvoidBottomInset] or the keyboard opening, then the
+  /// [scaffoldSize] will not reflect those changes.
+  ///
+  /// This means that [FloatingActionButtonLocation]s designed to reposition
+  /// the [FloatingActionButton] based on events such as the keyboard popping
+  /// up should use [minInsets] to make sure that the [FloatingActionButton] is
+  /// inset by enough to remain visible.
+  ///
+  /// See [minInsets] and [MediaQueryData.padding] for more information on the
+  /// appropriate insets to apply.
+  final Size scaffoldSize;
+
+  /// The [Size] of the [Scaffold]'s [SnackBar].
+  ///
+  /// If the [Scaffold] is not showing a [SnackBar], this will be [Size.zero].
+  final Size snackBarSize;
+
+  /// The [TextDirection] of the [Scaffold]'s [BuildContext].
+  final TextDirection textDirection;
+}
+
+/// A snapshot of a transition between two [FloatingActionButtonLocation]s.
+///
+/// [ScaffoldState] uses this to seamlessly change transition animations
+/// when a running [FloatingActionButtonLocation] transition is interrupted by a new transition.
+@immutable
+class _TransitionSnapshotFabLocation extends FloatingActionButtonLocation {
+
+  const _TransitionSnapshotFabLocation(this.begin, this.end, this.animator, this.progress);
+
+  final FloatingActionButtonLocation begin;
+  final FloatingActionButtonLocation end;
+  final FloatingActionButtonAnimator animator;
+  final double progress;
+
+  @override
+  Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
+    return animator.getOffset(
+      begin: begin.getOffset(scaffoldGeometry),
+      end: end.getOffset(scaffoldGeometry),
+      progress: progress,
+    );
+  }
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, '_TransitionSnapshotFabLocation')}(begin: $begin, end: $end, progress: $progress)';
+  }
+}
+
+/// Geometry information for [Scaffold] components after layout is finished.
+///
+/// To get a [ValueNotifier] for the scaffold geometry of a given
+/// [BuildContext], use [Scaffold.geometryOf].
+///
+/// The ScaffoldGeometry is only available during the paint phase, because
+/// its value is computed during the animation and layout phases prior to painting.
+///
+/// For an example of using the [ScaffoldGeometry], see the [BottomAppBar],
+/// which uses the [ScaffoldGeometry] to paint a notch around the
+/// [FloatingActionButton].
+///
+/// For information about the [Scaffold]'s geometry that is used while laying
+/// out the [FloatingActionButton], see [ScaffoldPrelayoutGeometry].
+@immutable
+class ScaffoldGeometry {
+  /// Create an object that describes the geometry of a [Scaffold].
+  const ScaffoldGeometry({
+    this.bottomNavigationBarTop,
+    this.floatingActionButtonArea,
+  });
+
+  /// The distance from the [Scaffold]'s top edge to the top edge of the
+  /// rectangle in which the [Scaffold.bottomNavigationBar] bar is laid out.
+  ///
+  /// Null if [Scaffold.bottomNavigationBar] is null.
+  final double? bottomNavigationBarTop;
+
+  /// The [Scaffold.floatingActionButton]'s bounding rectangle.
+  ///
+  /// This is null when there is no floating action button showing.
+  final Rect? floatingActionButtonArea;
+
+  ScaffoldGeometry _scaleFloatingActionButton(double scaleFactor) {
+    if (scaleFactor == 1.0)
+      return this;
+
+    if (scaleFactor == 0.0) {
+      return ScaffoldGeometry(
+        bottomNavigationBarTop: bottomNavigationBarTop,
+      );
+    }
+
+    final Rect scaledButton = Rect.lerp(
+      floatingActionButtonArea!.center & Size.zero,
+      floatingActionButtonArea,
+      scaleFactor,
+    )!;
+    return copyWith(floatingActionButtonArea: scaledButton);
+  }
+
+  /// Creates a copy of this [ScaffoldGeometry] but with the given fields replaced with
+  /// the new values.
+  ScaffoldGeometry copyWith({
+    double? bottomNavigationBarTop,
+    Rect? floatingActionButtonArea,
+  }) {
+    return ScaffoldGeometry(
+      bottomNavigationBarTop: bottomNavigationBarTop ?? this.bottomNavigationBarTop,
+      floatingActionButtonArea: floatingActionButtonArea ?? this.floatingActionButtonArea,
+    );
+  }
+}
+
+class _ScaffoldGeometryNotifier extends ChangeNotifier implements ValueListenable<ScaffoldGeometry> {
+  _ScaffoldGeometryNotifier(this.geometry, this.context)
+    : assert (context != null);
+
+  final BuildContext context;
+  double? floatingActionButtonScale;
+  ScaffoldGeometry geometry;
+
+  @override
+  ScaffoldGeometry get value {
+    assert(() {
+      final RenderObject? renderObject = context.findRenderObject();
+      if (renderObject == null || !renderObject.owner!.debugDoingPaint)
+        throw FlutterError(
+            'Scaffold.geometryOf() must only be accessed during the paint phase.\n'
+            'The ScaffoldGeometry is only available during the paint phase, because '
+            'its value is computed during the animation and layout phases prior to painting.'
+        );
+      return true;
+    }());
+    return geometry._scaleFloatingActionButton(floatingActionButtonScale!);
+  }
+
+  void _updateWith({
+    double? bottomNavigationBarTop,
+    Rect? floatingActionButtonArea,
+    double? floatingActionButtonScale,
+  }) {
+    this.floatingActionButtonScale = floatingActionButtonScale ?? this.floatingActionButtonScale;
+    geometry = geometry.copyWith(
+      bottomNavigationBarTop: bottomNavigationBarTop,
+      floatingActionButtonArea: floatingActionButtonArea,
+    );
+    notifyListeners();
+  }
+}
+
+// Used to communicate the height of the Scaffold's bottomNavigationBar and
+// persistentFooterButtons to the LayoutBuilder which builds the Scaffold's body.
+//
+// Scaffold expects a _BodyBoxConstraints to be passed to the _BodyBuilder
+// widget's LayoutBuilder, see _ScaffoldLayout.performLayout(). The BoxConstraints
+// methods that construct new BoxConstraints objects, like copyWith() have not
+// been overridden here because we expect the _BodyBoxConstraintsObject to be
+// passed along unmodified to the LayoutBuilder. If that changes in the future
+// then _BodyBuilder will assert.
+class _BodyBoxConstraints extends BoxConstraints {
+  const _BodyBoxConstraints({
+    double minWidth = 0.0,
+    double maxWidth = double.infinity,
+    double minHeight = 0.0,
+    double maxHeight = double.infinity,
+    required this.bottomWidgetsHeight,
+    required this.appBarHeight,
+  }) : assert(bottomWidgetsHeight != null),
+       assert(bottomWidgetsHeight >= 0),
+       assert(appBarHeight != null),
+       assert(appBarHeight >= 0),
+       super(minWidth: minWidth, maxWidth: maxWidth, minHeight: minHeight, maxHeight: maxHeight);
+
+  final double bottomWidgetsHeight;
+  final double appBarHeight;
+
+  // RenderObject.layout() will only short-circuit its call to its performLayout
+  // method if the new layout constraints are not == to the current constraints.
+  // If the height of the bottom widgets has changed, even though the constraints'
+  // min and max values have not, we still want performLayout to happen.
+  @override
+  bool operator ==(Object other) {
+    if (super != other)
+      return false;
+    return other is _BodyBoxConstraints
+        && other.bottomWidgetsHeight == bottomWidgetsHeight
+        && other.appBarHeight == appBarHeight;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(super.hashCode, bottomWidgetsHeight, appBarHeight);
+  }
+}
+
+// Used when Scaffold.extendBody is true to wrap the scaffold's body in a MediaQuery
+// whose padding accounts for the height of the bottomNavigationBar and/or the
+// persistentFooterButtons.
+//
+// The bottom widgets' height is passed along via the _BodyBoxConstraints parameter.
+// The constraints parameter is constructed in_ScaffoldLayout.performLayout().
+class _BodyBuilder extends StatelessWidget {
+  const _BodyBuilder({
+    Key? key,
+    required this.extendBody,
+    required this.extendBodyBehindAppBar,
+    required this.body,
+  }) : assert(extendBody != null),
+       assert(extendBodyBehindAppBar != null),
+       assert(body != null),
+       super(key: key);
+
+  final Widget body;
+  final bool extendBody;
+  final bool extendBodyBehindAppBar;
+
+  @override
+  Widget build(BuildContext context) {
+    if (!extendBody && !extendBodyBehindAppBar)
+      return body;
+
+    return LayoutBuilder(
+      builder: (BuildContext context, BoxConstraints constraints) {
+        final _BodyBoxConstraints bodyConstraints = constraints as _BodyBoxConstraints;
+        final MediaQueryData metrics = MediaQuery.of(context);
+
+        final double bottom = extendBody
+          ? math.max(metrics.padding.bottom, bodyConstraints.bottomWidgetsHeight)
+          : metrics.padding.bottom;
+
+        final double top = extendBodyBehindAppBar
+          ? math.max(metrics.padding.top, bodyConstraints.appBarHeight)
+          : metrics.padding.top;
+
+        return MediaQuery(
+          data: metrics.copyWith(
+            padding: metrics.padding.copyWith(
+              top: top,
+              bottom: bottom,
+            ),
+          ),
+          child: body,
+        );
+      },
+    );
+  }
+}
+
+class _ScaffoldLayout extends MultiChildLayoutDelegate {
+  _ScaffoldLayout({
+    required this.minInsets,
+    required this.minViewPadding,
+    required this.textDirection,
+    required this.geometryNotifier,
+    // for floating action button
+    required this.previousFloatingActionButtonLocation,
+    required this.currentFloatingActionButtonLocation,
+    required this.floatingActionButtonMoveAnimationProgress,
+    required this.floatingActionButtonMotionAnimator,
+    required this.isSnackBarFloating,
+    required this.snackBarWidth,
+    required this.extendBody,
+    required this.extendBodyBehindAppBar,
+  }) : assert(minInsets != null),
+       assert(textDirection != null),
+       assert(geometryNotifier != null),
+       assert(previousFloatingActionButtonLocation != null),
+       assert(currentFloatingActionButtonLocation != null),
+       assert(extendBody != null),
+       assert(extendBodyBehindAppBar != null);
+
+  final bool extendBody;
+  final bool extendBodyBehindAppBar;
+  final EdgeInsets minInsets;
+  final EdgeInsets minViewPadding;
+  final TextDirection textDirection;
+  final _ScaffoldGeometryNotifier geometryNotifier;
+
+  final FloatingActionButtonLocation previousFloatingActionButtonLocation;
+  final FloatingActionButtonLocation currentFloatingActionButtonLocation;
+  final double floatingActionButtonMoveAnimationProgress;
+  final FloatingActionButtonAnimator floatingActionButtonMotionAnimator;
+
+  final bool isSnackBarFloating;
+  final double? snackBarWidth;
+
+  @override
+  void performLayout(Size size) {
+    final BoxConstraints looseConstraints = BoxConstraints.loose(size);
+
+    // This part of the layout has the same effect as putting the app bar and
+    // body in a column and making the body flexible. What's different is that
+    // in this case the app bar appears _after_ the body in the stacking order,
+    // so the app bar's shadow is drawn on top of the body.
+
+    final BoxConstraints fullWidthConstraints = looseConstraints.tighten(width: size.width);
+    final double bottom = size.height;
+    double contentTop = 0.0;
+    double bottomWidgetsHeight = 0.0;
+    double appBarHeight = 0.0;
+
+    if (hasChild(_ScaffoldSlot.appBar)) {
+      appBarHeight = layoutChild(_ScaffoldSlot.appBar, fullWidthConstraints).height;
+      contentTop = extendBodyBehindAppBar ? 0.0 : appBarHeight;
+      positionChild(_ScaffoldSlot.appBar, Offset.zero);
+    }
+
+    double? bottomNavigationBarTop;
+    if (hasChild(_ScaffoldSlot.bottomNavigationBar)) {
+      final double bottomNavigationBarHeight = layoutChild(_ScaffoldSlot.bottomNavigationBar, fullWidthConstraints).height;
+      bottomWidgetsHeight += bottomNavigationBarHeight;
+      bottomNavigationBarTop = math.max(0.0, bottom - bottomWidgetsHeight);
+      positionChild(_ScaffoldSlot.bottomNavigationBar, Offset(0.0, bottomNavigationBarTop));
+    }
+
+    if (hasChild(_ScaffoldSlot.persistentFooter)) {
+      final BoxConstraints footerConstraints = BoxConstraints(
+        maxWidth: fullWidthConstraints.maxWidth,
+        maxHeight: math.max(0.0, bottom - bottomWidgetsHeight - contentTop),
+      );
+      final double persistentFooterHeight = layoutChild(_ScaffoldSlot.persistentFooter, footerConstraints).height;
+      bottomWidgetsHeight += persistentFooterHeight;
+      positionChild(_ScaffoldSlot.persistentFooter, Offset(0.0, math.max(0.0, bottom - bottomWidgetsHeight)));
+    }
+
+    // Set the content bottom to account for the greater of the height of any
+    // bottom-anchored material widgets or of the keyboard or other
+    // bottom-anchored system UI.
+    final double contentBottom = math.max(0.0, bottom - math.max(minInsets.bottom, bottomWidgetsHeight));
+
+    if (hasChild(_ScaffoldSlot.body)) {
+      double bodyMaxHeight = math.max(0.0, contentBottom - contentTop);
+
+      if (extendBody) {
+        bodyMaxHeight += bottomWidgetsHeight;
+        bodyMaxHeight = bodyMaxHeight.clamp(0.0, looseConstraints.maxHeight - contentTop).toDouble();
+        assert(bodyMaxHeight <= math.max(0.0, looseConstraints.maxHeight - contentTop));
+      }
+
+      final BoxConstraints bodyConstraints = _BodyBoxConstraints(
+        maxWidth: fullWidthConstraints.maxWidth,
+        maxHeight: bodyMaxHeight,
+        bottomWidgetsHeight: extendBody ? bottomWidgetsHeight : 0.0,
+        appBarHeight: appBarHeight,
+      );
+      layoutChild(_ScaffoldSlot.body, bodyConstraints);
+      positionChild(_ScaffoldSlot.body, Offset(0.0, contentTop));
+    }
+
+    // The BottomSheet and the SnackBar are anchored to the bottom of the parent,
+    // they're as wide as the parent and are given their intrinsic height. The
+    // only difference is that SnackBar appears on the top side of the
+    // BottomNavigationBar while the BottomSheet is stacked on top of it.
+    //
+    // If all three elements are present then either the center of the FAB straddles
+    // the top edge of the BottomSheet or the bottom of the FAB is
+    // kFloatingActionButtonMargin above the SnackBar, whichever puts the FAB
+    // the farthest above the bottom of the parent. If only the FAB is has a
+    // non-zero height then it's inset from the parent's right and bottom edges
+    // by kFloatingActionButtonMargin.
+
+    Size bottomSheetSize = Size.zero;
+    Size snackBarSize = Size.zero;
+    if (hasChild(_ScaffoldSlot.bodyScrim)) {
+      final BoxConstraints bottomSheetScrimConstraints = BoxConstraints(
+        maxWidth: fullWidthConstraints.maxWidth,
+        maxHeight: contentBottom,
+      );
+      layoutChild(_ScaffoldSlot.bodyScrim, bottomSheetScrimConstraints);
+      positionChild(_ScaffoldSlot.bodyScrim, Offset.zero);
+    }
+
+    // Set the size of the SnackBar early if the behavior is fixed so
+    // the FAB can be positioned correctly.
+    if (hasChild(_ScaffoldSlot.snackBar) && !isSnackBarFloating) {
+      snackBarSize = layoutChild(_ScaffoldSlot.snackBar, fullWidthConstraints);
+    }
+
+    if (hasChild(_ScaffoldSlot.bottomSheet)) {
+      final BoxConstraints bottomSheetConstraints = BoxConstraints(
+        maxWidth: fullWidthConstraints.maxWidth,
+        maxHeight: math.max(0.0, contentBottom - contentTop),
+      );
+      bottomSheetSize = layoutChild(_ScaffoldSlot.bottomSheet, bottomSheetConstraints);
+      positionChild(_ScaffoldSlot.bottomSheet, Offset((size.width - bottomSheetSize.width) / 2.0, contentBottom - bottomSheetSize.height));
+    }
+
+    late Rect floatingActionButtonRect;
+    if (hasChild(_ScaffoldSlot.floatingActionButton)) {
+      final Size fabSize = layoutChild(_ScaffoldSlot.floatingActionButton, looseConstraints);
+
+      // To account for the FAB position being changed, we'll animate between
+      // the old and new positions.
+      final ScaffoldPrelayoutGeometry currentGeometry = ScaffoldPrelayoutGeometry(
+        bottomSheetSize: bottomSheetSize,
+        contentBottom: contentBottom,
+        /// [appBarHeight] should be used instead of [contentTop] because
+        /// ScaffoldPrelayoutGeometry.contentTop must not be affected by [extendBodyBehindAppBar].
+        contentTop: appBarHeight,
+        floatingActionButtonSize: fabSize,
+        minInsets: minInsets,
+        scaffoldSize: size,
+        snackBarSize: snackBarSize,
+        textDirection: textDirection,
+        minViewPadding: minViewPadding,
+      );
+      final Offset currentFabOffset = currentFloatingActionButtonLocation.getOffset(currentGeometry);
+      final Offset previousFabOffset = previousFloatingActionButtonLocation.getOffset(currentGeometry);
+      final Offset fabOffset = floatingActionButtonMotionAnimator.getOffset(
+        begin: previousFabOffset,
+        end: currentFabOffset,
+        progress: floatingActionButtonMoveAnimationProgress,
+      );
+      positionChild(_ScaffoldSlot.floatingActionButton, fabOffset);
+      floatingActionButtonRect = fabOffset & fabSize;
+    }
+
+    if (hasChild(_ScaffoldSlot.snackBar)) {
+      final bool hasCustomWidth = snackBarWidth != null && snackBarWidth! < size.width;
+      if (snackBarSize == Size.zero) {
+        snackBarSize = layoutChild(
+          _ScaffoldSlot.snackBar,
+          hasCustomWidth ? looseConstraints : fullWidthConstraints,
+        );
+      }
+
+      final double snackBarYOffsetBase;
+      if (floatingActionButtonRect.size != Size.zero && isSnackBarFloating) {
+        snackBarYOffsetBase = floatingActionButtonRect.top;
+      } else {
+        // SnackBarBehavior.fixed applies a SafeArea automatically.
+        // SnackBarBehavior.floating does not since the positioning is affected
+        // if there is a FloatingActionButton (see condition above). If there is
+        // no FAB, make sure we account for safe space when the SnackBar is
+        // floating.
+        final double safeYOffsetBase = size.height - minViewPadding.bottom;
+        snackBarYOffsetBase = isSnackBarFloating
+          ? math.min(contentBottom, safeYOffsetBase)
+          : contentBottom;
+      }
+
+      final double xOffset = hasCustomWidth ? (size.width - snackBarWidth!) / 2 : 0.0;
+      positionChild(_ScaffoldSlot.snackBar, Offset(xOffset, snackBarYOffsetBase - snackBarSize.height));
+    }
+
+    if (hasChild(_ScaffoldSlot.statusBar)) {
+      layoutChild(_ScaffoldSlot.statusBar, fullWidthConstraints.tighten(height: minInsets.top));
+      positionChild(_ScaffoldSlot.statusBar, Offset.zero);
+    }
+
+    if (hasChild(_ScaffoldSlot.drawer)) {
+      layoutChild(_ScaffoldSlot.drawer, BoxConstraints.tight(size));
+      positionChild(_ScaffoldSlot.drawer, Offset.zero);
+    }
+
+    if (hasChild(_ScaffoldSlot.endDrawer)) {
+      layoutChild(_ScaffoldSlot.endDrawer, BoxConstraints.tight(size));
+      positionChild(_ScaffoldSlot.endDrawer, Offset.zero);
+    }
+
+    geometryNotifier._updateWith(
+      bottomNavigationBarTop: bottomNavigationBarTop,
+      floatingActionButtonArea: floatingActionButtonRect,
+    );
+  }
+
+  @override
+  bool shouldRelayout(_ScaffoldLayout oldDelegate) {
+    return oldDelegate.minInsets != minInsets
+        || oldDelegate.textDirection != textDirection
+        || oldDelegate.floatingActionButtonMoveAnimationProgress != floatingActionButtonMoveAnimationProgress
+        || oldDelegate.previousFloatingActionButtonLocation != previousFloatingActionButtonLocation
+        || oldDelegate.currentFloatingActionButtonLocation != currentFloatingActionButtonLocation
+        || oldDelegate.extendBody != extendBody
+        || oldDelegate.extendBodyBehindAppBar != extendBodyBehindAppBar;
+  }
+}
+
+/// Handler for scale and rotation animations in the [FloatingActionButton].
+///
+/// Currently, there are two types of [FloatingActionButton] animations:
+///
+/// * Entrance/Exit animations, which this widget triggers
+///   when the [FloatingActionButton] is added, updated, or removed.
+/// * Motion animations, which are triggered by the [Scaffold]
+///   when its [FloatingActionButtonLocation] is updated.
+class _FloatingActionButtonTransition extends StatefulWidget {
+  const _FloatingActionButtonTransition({
+    Key? key,
+    required this.child,
+    required this.fabMoveAnimation,
+    required this.fabMotionAnimator,
+    required this.geometryNotifier,
+    required this.currentController,
+  }) : assert(fabMoveAnimation != null),
+       assert(fabMotionAnimator != null),
+       assert(currentController != null),
+       super(key: key);
+
+  final Widget? child;
+  final Animation<double> fabMoveAnimation;
+  final FloatingActionButtonAnimator fabMotionAnimator;
+  final _ScaffoldGeometryNotifier geometryNotifier;
+
+  /// Controls the current child widget.child as it exits.
+  final AnimationController currentController;
+
+  @override
+  _FloatingActionButtonTransitionState createState() => _FloatingActionButtonTransitionState();
+}
+
+class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTransition> with TickerProviderStateMixin {
+  // The animations applied to the Floating Action Button when it is entering or exiting.
+  // Controls the previous widget.child as it exits.
+  late AnimationController _previousController;
+  late Animation<double> _previousScaleAnimation;
+  late Animation<double> _previousRotationAnimation;
+  // The animations to run, considering the widget's fabMoveAnimation and the current/previous entrance/exit animations.
+  late Animation<double> _currentScaleAnimation;
+  late Animation<double> _extendedCurrentScaleAnimation;
+  late Animation<double> _currentRotationAnimation;
+  Widget? _previousChild;
+
+  @override
+  void initState() {
+    super.initState();
+
+    _previousController = AnimationController(
+      duration: kFloatingActionButtonSegue,
+      vsync: this,
+    )..addStatusListener(_handlePreviousAnimationStatusChanged);
+    _updateAnimations();
+
+    if (widget.child != null) {
+      // If we start out with a child, have the child appear fully visible instead
+      // of animating in.
+      widget.currentController.value = 1.0;
+    } else {
+      // If we start without a child we update the geometry object with a
+      // floating action button scale of 0, as it is not showing on the screen.
+      _updateGeometryScale(0.0);
+    }
+  }
+
+  @override
+  void dispose() {
+    _previousController.dispose();
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(_FloatingActionButtonTransition oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    final bool oldChildIsNull = oldWidget.child == null;
+    final bool newChildIsNull = widget.child == null;
+    if (oldChildIsNull == newChildIsNull && oldWidget.child?.key == widget.child?.key)
+      return;
+    if (oldWidget.fabMotionAnimator != widget.fabMotionAnimator || oldWidget.fabMoveAnimation != widget.fabMoveAnimation) {
+      // Get the right scale and rotation animations to use for this widget.
+      _updateAnimations();
+    }
+    if (_previousController.status == AnimationStatus.dismissed) {
+      final double currentValue = widget.currentController.value;
+      if (currentValue == 0.0 || oldWidget.child == null) {
+        // The current child hasn't started its entrance animation yet. We can
+        // just skip directly to the new child's entrance.
+        _previousChild = null;
+        if (widget.child != null)
+          widget.currentController.forward();
+      } else {
+        // Otherwise, we need to copy the state from the current controller to
+        // the previous controller and run an exit animation for the previous
+        // widget before running the entrance animation for the new child.
+        _previousChild = oldWidget.child;
+        _previousController
+          ..value = currentValue
+          ..reverse();
+        widget.currentController.value = 0.0;
+      }
+    }
+  }
+
+  static final Animatable<double> _entranceTurnTween = Tween<double>(
+    begin: 1.0 - kFloatingActionButtonTurnInterval,
+    end: 1.0,
+  ).chain(CurveTween(curve: Curves.easeIn));
+
+  void _updateAnimations() {
+    // Get the animations for exit and entrance.
+    final CurvedAnimation previousExitScaleAnimation = CurvedAnimation(
+      parent: _previousController,
+      curve: Curves.easeIn,
+    );
+    final Animation<double> previousExitRotationAnimation = Tween<double>(begin: 1.0, end: 1.0).animate(
+      CurvedAnimation(
+        parent: _previousController,
+        curve: Curves.easeIn,
+      ),
+    );
+
+    final CurvedAnimation currentEntranceScaleAnimation = CurvedAnimation(
+      parent: widget.currentController,
+      curve: Curves.easeIn,
+    );
+    final Animation<double> currentEntranceRotationAnimation = widget.currentController.drive(_entranceTurnTween);
+
+    // Get the animations for when the FAB is moving.
+    final Animation<double> moveScaleAnimation = widget.fabMotionAnimator.getScaleAnimation(parent: widget.fabMoveAnimation);
+    final Animation<double> moveRotationAnimation = widget.fabMotionAnimator.getRotationAnimation(parent: widget.fabMoveAnimation);
+
+    // Aggregate the animations.
+    _previousScaleAnimation = AnimationMin<double>(moveScaleAnimation, previousExitScaleAnimation);
+    _currentScaleAnimation = AnimationMin<double>(moveScaleAnimation, currentEntranceScaleAnimation);
+    _extendedCurrentScaleAnimation = _currentScaleAnimation.drive(CurveTween(curve: const Interval(0.0, 0.1)));
+
+    _previousRotationAnimation = TrainHoppingAnimation(previousExitRotationAnimation, moveRotationAnimation);
+    _currentRotationAnimation = TrainHoppingAnimation(currentEntranceRotationAnimation, moveRotationAnimation);
+
+    _currentScaleAnimation.addListener(_onProgressChanged);
+    _previousScaleAnimation.addListener(_onProgressChanged);
+  }
+
+  void _handlePreviousAnimationStatusChanged(AnimationStatus status) {
+    setState(() {
+      if (status == AnimationStatus.dismissed) {
+        assert(widget.currentController.status == AnimationStatus.dismissed);
+        if (widget.child != null)
+          widget.currentController.forward();
+      }
+    });
+  }
+
+  bool _isExtendedFloatingActionButton(Widget? widget) {
+    return widget is FloatingActionButton
+        && widget.isExtended;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Stack(
+      alignment: Alignment.centerRight,
+      children: <Widget>[
+        if (_previousController.status != AnimationStatus.dismissed)
+          if (_isExtendedFloatingActionButton(_previousChild))
+            FadeTransition(
+              opacity: _previousScaleAnimation,
+              child: _previousChild,
+            )
+          else
+            ScaleTransition(
+              scale: _previousScaleAnimation,
+              child: RotationTransition(
+                turns: _previousRotationAnimation,
+                child: _previousChild,
+              ),
+            ),
+        if (_isExtendedFloatingActionButton(widget.child))
+          ScaleTransition(
+            scale: _extendedCurrentScaleAnimation,
+            child: FadeTransition(
+              opacity: _currentScaleAnimation,
+              child: widget.child,
+            ),
+          )
+        else
+          ScaleTransition(
+            scale: _currentScaleAnimation,
+            child: RotationTransition(
+              turns: _currentRotationAnimation,
+              child: widget.child,
+            ),
+          ),
+      ],
+    );
+  }
+
+  void _onProgressChanged() {
+    _updateGeometryScale(math.max(_previousScaleAnimation.value, _currentScaleAnimation.value));
+  }
+
+  void _updateGeometryScale(double scale) {
+    widget.geometryNotifier._updateWith(
+      floatingActionButtonScale: scale,
+    );
+  }
+}
+
+/// Implements the basic material design visual layout structure.
+///
+/// This class provides APIs for showing drawers and bottom sheets.
+///
+/// To display a persistent bottom sheet, obtain the
+/// [ScaffoldState] for the current [BuildContext] via [Scaffold.of] and use the
+/// [ScaffoldState.showBottomSheet] function.
+///
+/// {@tool dartpad --template=stateful_widget_material}
+/// This example shows a [Scaffold] with a [body] and [FloatingActionButton].
+/// The [body] is a [Text] placed in a [Center] in order to center the text
+/// within the [Scaffold]. The [FloatingActionButton] is connected to a
+/// callback that increments a counter.
+///
+/// ![The Scaffold has a white background with a blue AppBar at the top. A blue FloatingActionButton is positioned at the bottom right corner of the Scaffold.](https://flutter.github.io/assets-for-api-docs/assets/material/scaffold.png)
+///
+/// ```dart
+/// int _count = 0;
+///
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: AppBar(
+///       title: const Text('Sample Code'),
+///     ),
+///     body: Center(
+///       child: Text('You have pressed the button $_count times.')
+///     ),
+///     floatingActionButton: FloatingActionButton(
+///       onPressed: () => setState(() => _count++),
+///       tooltip: 'Increment Counter',
+///       child: const Icon(Icons.add),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateful_widget_material}
+/// This example shows a [Scaffold] with a blueGrey [backgroundColor], [body]
+/// and [FloatingActionButton]. The [body] is a [Text] placed in a [Center] in
+/// order to center the text within the [Scaffold]. The [FloatingActionButton]
+/// is connected to a callback that increments a counter.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/scaffold_background_color.png)
+///
+/// ```dart
+/// int _count = 0;
+///
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: AppBar(
+///       title: const Text('Sample Code'),
+///     ),
+///     body: Center(
+///       child: Text('You have pressed the button $_count times.')
+///     ),
+///     backgroundColor: Colors.blueGrey.shade200,
+///     floatingActionButton: FloatingActionButton(
+///       onPressed: () => setState(() => _count++),
+///       tooltip: 'Increment Counter',
+///       child: const Icon(Icons.add),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateful_widget_material}
+/// This example shows a [Scaffold] with an [AppBar], a [BottomAppBar] and a
+/// [FloatingActionButton]. The [body] is a [Text] placed in a [Center] in order
+/// to center the text within the [Scaffold]. The [FloatingActionButton] is
+/// centered and docked within the [BottomAppBar] using
+/// [FloatingActionButtonLocation.centerDocked]. The [FloatingActionButton] is
+/// connected to a callback that increments a counter.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/scaffold_bottom_app_bar.png)
+///
+/// ```dart
+/// int _count = 0;
+///
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: AppBar(
+///       title: const Text('Sample Code'),
+///     ),
+///     body: Center(
+///       child: Text('You have pressed the button $_count times.'),
+///     ),
+///     bottomNavigationBar: BottomAppBar(
+///       shape: const CircularNotchedRectangle(),
+///       child: Container(height: 50.0,),
+///     ),
+///     floatingActionButton: FloatingActionButton(
+///       onPressed: () => setState(() {
+///         _count++;
+///       }),
+///       tooltip: 'Increment Counter',
+///       child: Icon(Icons.add),
+///     ),
+///     floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ## Scaffold layout, the keyboard, and display "notches"
+///
+/// The scaffold will expand to fill the available space. That usually
+/// means that it will occupy its entire window or device screen. When
+/// the device's keyboard appears the Scaffold's ancestor [MediaQuery]
+/// widget's [MediaQueryData.viewInsets] changes and the Scaffold will
+/// be rebuilt. By default the scaffold's [body] is resized to make
+/// room for the keyboard. To prevent the resize set
+/// [resizeToAvoidBottomInset] to false. In either case the focused
+/// widget will be scrolled into view if it's within a scrollable
+/// container.
+///
+/// The [MediaQueryData.padding] value defines areas that might
+/// not be completely visible, like the display "notch" on the iPhone
+/// X. The scaffold's [body] is not inset by this padding value
+/// although an [appBar] or [bottomNavigationBar] will typically
+/// cause the body to avoid the padding. The [SafeArea]
+/// widget can be used within the scaffold's body to avoid areas
+/// like display notches.
+///
+/// ## Troubleshooting
+///
+/// ### Nested Scaffolds
+///
+/// The Scaffold is designed to be a top level container for
+/// a [MaterialApp]. This means that adding a Scaffold
+/// to each route on a Material app will provide the app with
+/// Material's basic visual layout structure.
+///
+/// It is typically not necessary to nest Scaffolds. For example, in a
+/// tabbed UI, where the [bottomNavigationBar] is a [TabBar]
+/// and the body is a [TabBarView], you might be tempted to make each tab bar
+/// view a scaffold with a differently titled AppBar. Rather, it would be
+/// better to add a listener to the [TabController] that updates the
+/// AppBar
+///
+/// {@tool snippet}
+/// Add a listener to the app's tab controller so that the [AppBar] title of the
+/// app's one and only scaffold is reset each time a new tab is selected.
+///
+/// ```dart
+/// TabController(vsync: tickerProvider, length: tabCount)..addListener(() {
+///   if (!tabController.indexIsChanging) {
+///     setState(() {
+///       // Rebuild the enclosing scaffold with a new AppBar title
+///       appBarTitle = 'Tab ${tabController.index}';
+///     });
+///   }
+/// })
+/// ```
+/// {@end-tool}
+///
+/// Although there are some use cases, like a presentation app that
+/// shows embedded flutter content, where nested scaffolds are
+/// appropriate, it's best to avoid nesting scaffolds.
+///
+/// See also:
+///
+///  * [AppBar], which is a horizontal bar typically shown at the top of an app
+///    using the [appBar] property.
+///  * [BottomAppBar], which is a horizontal bar typically shown at the bottom
+///    of an app using the [bottomNavigationBar] property.
+///  * [FloatingActionButton], which is a circular button typically shown in the
+///    bottom right corner of the app using the [floatingActionButton] property.
+///  * [Drawer], which is a vertical panel that is typically displayed to the
+///    left of the body (and often hidden on phones) using the [drawer]
+///    property.
+///  * [BottomNavigationBar], which is a horizontal array of buttons typically
+///    shown along the bottom of the app using the [bottomNavigationBar]
+///    property.
+///  * [BottomSheet], which is an overlay typically shown near the bottom of the
+///    app. A bottom sheet can either be persistent, in which case it is shown
+///    using the [ScaffoldState.showBottomSheet] method, or modal, in which case
+///    it is shown using the [showModalBottomSheet] function.
+///  * [ScaffoldState], which is the state associated with this widget.
+///  * <https://material.io/design/layout/responsive-layout-grid.html>
+///  * Cookbook: [Add a Drawer to a screen](https://flutter.dev/docs/cookbook/design/drawer)
+class Scaffold extends StatefulWidget {
+  /// Creates a visual scaffold for material design widgets.
+  const Scaffold({
+    Key? key,
+    this.appBar,
+    this.body,
+    this.floatingActionButton,
+    this.floatingActionButtonLocation,
+    this.floatingActionButtonAnimator,
+    this.persistentFooterButtons,
+    this.drawer,
+    this.onDrawerChanged,
+    this.endDrawer,
+    this.onEndDrawerChanged,
+    this.bottomNavigationBar,
+    this.bottomSheet,
+    this.backgroundColor,
+    this.resizeToAvoidBottomPadding,
+    this.resizeToAvoidBottomInset,
+    this.primary = true,
+    this.drawerDragStartBehavior = DragStartBehavior.start,
+    this.extendBody = false,
+    this.extendBodyBehindAppBar = false,
+    this.drawerScrimColor,
+    this.drawerEdgeDragWidth,
+    this.drawerEnableOpenDragGesture = true,
+    this.endDrawerEnableOpenDragGesture = true,
+  }) : assert(primary != null),
+       assert(extendBody != null),
+       assert(extendBodyBehindAppBar != null),
+       assert(drawerDragStartBehavior != null),
+       super(key: key);
+
+  /// If true, and [bottomNavigationBar] or [persistentFooterButtons]
+  /// is specified, then the [body] extends to the bottom of the Scaffold,
+  /// instead of only extending to the top of the [bottomNavigationBar]
+  /// or the [persistentFooterButtons].
+  ///
+  /// If true, a [MediaQuery] widget whose bottom padding matches the height
+  /// of the [bottomNavigationBar] will be added above the scaffold's [body].
+  ///
+  /// This property is often useful when the [bottomNavigationBar] has
+  /// a non-rectangular shape, like [CircularNotchedRectangle], which
+  /// adds a [FloatingActionButton] sized notch to the top edge of the bar.
+  /// In this case specifying `extendBody: true` ensures that that scaffold's
+  /// body will be visible through the bottom navigation bar's notch.
+  ///
+  /// See also:
+  ///
+  ///  * [extendBodyBehindAppBar], which extends the height of the body
+  ///    to the top of the scaffold.
+  final bool extendBody;
+
+  /// If true, and an [appBar] is specified, then the height of the [body] is
+  /// extended to include the height of the app bar and the top of the body
+  /// is aligned with the top of the app bar.
+  ///
+  /// This is useful if the app bar's [AppBar.backgroundColor] is not
+  /// completely opaque.
+  ///
+  /// This property is false by default. It must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [extendBody], which extends the height of the body to the bottom
+  ///    of the scaffold.
+  final bool extendBodyBehindAppBar;
+
+  /// An app bar to display at the top of the scaffold.
+  final PreferredSizeWidget? appBar;
+
+  /// The primary content of the scaffold.
+  ///
+  /// Displayed below the [appBar], above the bottom of the ambient
+  /// [MediaQuery]'s [MediaQueryData.viewInsets], and behind the
+  /// [floatingActionButton] and [drawer]. If [resizeToAvoidBottomInset] is
+  /// false then the body is not resized when the onscreen keyboard appears,
+  /// i.e. it is not inset by `viewInsets.bottom`.
+  ///
+  /// The widget in the body of the scaffold is positioned at the top-left of
+  /// the available space between the app bar and the bottom of the scaffold. To
+  /// center this widget instead, consider putting it in a [Center] widget and
+  /// having that be the body. To expand this widget instead, consider
+  /// putting it in a [SizedBox.expand].
+  ///
+  /// If you have a column of widgets that should normally fit on the screen,
+  /// but may overflow and would in such cases need to scroll, consider using a
+  /// [ListView] as the body of the scaffold. This is also a good choice for
+  /// the case where your body is a scrollable list.
+  final Widget? body;
+
+  /// A button displayed floating above [body], in the bottom right corner.
+  ///
+  /// Typically a [FloatingActionButton].
+  final Widget? floatingActionButton;
+
+  /// Responsible for determining where the [floatingActionButton] should go.
+  ///
+  /// If null, the [ScaffoldState] will use the default location, [FloatingActionButtonLocation.endFloat].
+  final FloatingActionButtonLocation? floatingActionButtonLocation;
+
+  /// Animator to move the [floatingActionButton] to a new [floatingActionButtonLocation].
+  ///
+  /// If null, the [ScaffoldState] will use the default animator, [FloatingActionButtonAnimator.scaling].
+  final FloatingActionButtonAnimator? floatingActionButtonAnimator;
+
+  /// A set of buttons that are displayed at the bottom of the scaffold.
+  ///
+  /// Typically this is a list of [TextButton] widgets. These buttons are
+  /// persistently visible, even if the [body] of the scaffold scrolls.
+  ///
+  /// These widgets will be wrapped in a [ButtonBar].
+  ///
+  /// The [persistentFooterButtons] are rendered above the
+  /// [bottomNavigationBar] but below the [body].
+  final List<Widget>? persistentFooterButtons;
+
+  /// A panel displayed to the side of the [body], often hidden on mobile
+  /// devices. Swipes in from either left-to-right ([TextDirection.ltr]) or
+  /// right-to-left ([TextDirection.rtl])
+  ///
+  /// Typically a [Drawer].
+  ///
+  /// To open the drawer, use the [ScaffoldState.openDrawer] function.
+  ///
+  /// To close the drawer, use [Navigator.pop].
+  ///
+  /// {@tool dartpad --template=stateful_widget_material}
+  /// To disable the drawer edge swipe, set the
+  /// [Scaffold.drawerEnableOpenDragGesture] to false. Then, use
+  /// [ScaffoldState.openDrawer] to open the drawer and [Navigator.pop] to close
+  /// it.
+  ///
+  /// ```dart
+  /// final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
+  ///
+  /// void _openDrawer() {
+  ///   _scaffoldKey.currentState!.openDrawer();
+  /// }
+  ///
+  /// void _closeDrawer() {
+  ///   Navigator.of(context).pop();
+  /// }
+  ///
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return Scaffold(
+  ///     key: _scaffoldKey,
+  ///     appBar: AppBar(title: const Text('Drawer Demo')),
+  ///     body: Center(
+  ///       child: ElevatedButton(
+  ///         onPressed: _openDrawer,
+  ///         child: const Text('Open Drawer'),
+  ///       ),
+  ///     ),
+  ///     drawer: Drawer(
+  ///       child: Center(
+  ///         child: Column(
+  ///           mainAxisAlignment: MainAxisAlignment.center,
+  ///           children: <Widget>[
+  ///             const Text('This is the Drawer'),
+  ///             ElevatedButton(
+  ///               onPressed: _closeDrawer,
+  ///               child: const Text('Close Drawer'),
+  ///             ),
+  ///           ],
+  ///         ),
+  ///       ),
+  ///     ),
+  ///     // Disable opening the drawer with a swipe gesture.
+  ///     drawerEnableOpenDragGesture: false,
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  final Widget? drawer;
+
+  /// Optional callback that is called when the [Scaffold.drawer] is opened or closed.
+  final DrawerCallback? onDrawerChanged;
+
+  /// A panel displayed to the side of the [body], often hidden on mobile
+  /// devices. Swipes in from right-to-left ([TextDirection.ltr]) or
+  /// left-to-right ([TextDirection.rtl])
+  ///
+  /// Typically a [Drawer].
+  ///
+  /// To open the drawer, use the [ScaffoldState.openEndDrawer] function.
+  ///
+  /// To close the drawer, use [Navigator.pop].
+  ///
+  /// {@tool dartpad --template=stateful_widget_material}
+  /// To disable the drawer edge swipe, set the
+  /// [Scaffold.endDrawerEnableOpenDragGesture] to false. Then, use
+  /// [ScaffoldState.openEndDrawer] to open the drawer and [Navigator.pop] to
+  /// close it.
+  ///
+  /// ```dart
+  /// final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
+  ///
+  /// void _openEndDrawer() {
+  ///   _scaffoldKey.currentState!.openEndDrawer();
+  /// }
+  ///
+  /// void _closeEndDrawer() {
+  ///   Navigator.of(context).pop();
+  /// }
+  ///
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return Scaffold(
+  ///     key: _scaffoldKey,
+  ///     appBar: AppBar(title: Text('Drawer Demo')),
+  ///     body: Center(
+  ///       child: ElevatedButton(
+  ///         onPressed: _openEndDrawer,
+  ///         child: Text('Open End Drawer'),
+  ///       ),
+  ///     ),
+  ///     endDrawer: Drawer(
+  ///       child: Center(
+  ///         child: Column(
+  ///           mainAxisAlignment: MainAxisAlignment.center,
+  ///           children: <Widget>[
+  ///             const Text('This is the Drawer'),
+  ///             ElevatedButton(
+  ///               onPressed: _closeEndDrawer,
+  ///               child: const Text('Close Drawer'),
+  ///             ),
+  ///           ],
+  ///         ),
+  ///       ),
+  ///     ),
+  ///     // Disable opening the end drawer with a swipe gesture.
+  ///     endDrawerEnableOpenDragGesture: false,
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  final Widget? endDrawer;
+
+  /// Optional callback that is called when the [Scaffold.endDrawer] is opened or closed.
+  final DrawerCallback? onEndDrawerChanged;
+
+  /// The color to use for the scrim that obscures primary content while a drawer is open.
+  ///
+  /// By default, the color is [Colors.black54]
+  final Color? drawerScrimColor;
+
+  /// The color of the [Material] widget that underlies the entire Scaffold.
+  ///
+  /// The theme's [ThemeData.scaffoldBackgroundColor] by default.
+  final Color? backgroundColor;
+
+  /// A bottom navigation bar to display at the bottom of the scaffold.
+  ///
+  /// Snack bars slide from underneath the bottom navigation bar while bottom
+  /// sheets are stacked on top.
+  ///
+  /// The [bottomNavigationBar] is rendered below the [persistentFooterButtons]
+  /// and the [body].
+  final Widget? bottomNavigationBar;
+
+  /// The persistent bottom sheet to display.
+  ///
+  /// A persistent bottom sheet shows information that supplements the primary
+  /// content of the app. A persistent bottom sheet remains visible even when
+  /// the user interacts with other parts of the app.
+  ///
+  /// A closely related widget is a modal bottom sheet, which is an alternative
+  /// to a menu or a dialog and prevents the user from interacting with the rest
+  /// of the app. Modal bottom sheets can be created and displayed with the
+  /// [showModalBottomSheet] function.
+  ///
+  /// Unlike the persistent bottom sheet displayed by [showBottomSheet]
+  /// this bottom sheet is not a [LocalHistoryEntry] and cannot be dismissed
+  /// with the scaffold appbar's back button.
+  ///
+  /// If a persistent bottom sheet created with [showBottomSheet] is already
+  /// visible, it must be closed before building the Scaffold with a new
+  /// [bottomSheet].
+  ///
+  /// The value of [bottomSheet] can be any widget at all. It's unlikely to
+  /// actually be a [BottomSheet], which is used by the implementations of
+  /// [showBottomSheet] and [showModalBottomSheet]. Typically it's a widget
+  /// that includes [Material].
+  ///
+  /// See also:
+  ///
+  ///  * [showBottomSheet], which displays a bottom sheet as a route that can
+  ///    be dismissed with the scaffold's back button.
+  ///  * [showModalBottomSheet], which displays a modal bottom sheet.
+  final Widget? bottomSheet;
+
+  /// This flag is deprecated, please use [resizeToAvoidBottomInset]
+  /// instead.
+  ///
+  /// Originally the name referred [MediaQueryData.padding]. Now it refers
+  /// [MediaQueryData.viewInsets], so using [resizeToAvoidBottomInset]
+  /// should be clearer to readers.
+  @Deprecated(
+    'Use resizeToAvoidBottomInset to specify if the body should resize when the keyboard appears. '
+    'This feature was deprecated after v1.1.9.'
+  )
+  final bool? resizeToAvoidBottomPadding;
+
+  /// If true the [body] and the scaffold's floating widgets should size
+  /// themselves to avoid the onscreen keyboard whose height is defined by the
+  /// ambient [MediaQuery]'s [MediaQueryData.viewInsets] `bottom` property.
+  ///
+  /// For example, if there is an onscreen keyboard displayed above the
+  /// scaffold, the body can be resized to avoid overlapping the keyboard, which
+  /// prevents widgets inside the body from being obscured by the keyboard.
+  ///
+  /// Defaults to true.
+  final bool? resizeToAvoidBottomInset;
+
+  /// Whether this scaffold is being displayed at the top of the screen.
+  ///
+  /// If true then the height of the [appBar] will be extended by the height
+  /// of the screen's status bar, i.e. the top padding for [MediaQuery].
+  ///
+  /// The default value of this property, like the default value of
+  /// [AppBar.primary], is true.
+  final bool primary;
+
+  /// {@macro flutter.material.DrawerController.dragStartBehavior}
+  final DragStartBehavior drawerDragStartBehavior;
+
+  /// The width of the area within which a horizontal swipe will open the
+  /// drawer.
+  ///
+  /// By default, the value used is 20.0 added to the padding edge of
+  /// `MediaQuery.of(context).padding` that corresponds to the surrounding
+  /// [TextDirection]. This ensures that the drag area for notched devices is
+  /// not obscured. For example, if `TextDirection.of(context)` is set to
+  /// [TextDirection.ltr], 20.0 will be added to
+  /// `MediaQuery.of(context).padding.left`.
+  final double? drawerEdgeDragWidth;
+
+  /// Determines if the [Scaffold.drawer] can be opened with a drag
+  /// gesture.
+  ///
+  /// By default, the drag gesture is enabled.
+  final bool drawerEnableOpenDragGesture;
+
+  /// Determines if the [Scaffold.endDrawer] can be opened with a
+  /// drag gesture.
+  ///
+  /// By default, the drag gesture is enabled.
+  final bool endDrawerEnableOpenDragGesture;
+
+  /// Finds the [ScaffoldState] from the closest instance of this class that
+  /// encloses the given context.
+  ///
+  /// If no instance of this class encloses the given context, will cause an
+  /// assert in debug mode, and throw an exception in release mode.
+  ///
+  /// {@tool dartpad --template=freeform}
+  /// Typical usage of the [Scaffold.of] function is to call it from within the
+  /// `build` method of a child of a [Scaffold].
+  ///
+  /// ```dart imports
+  /// import 'package:flute/material.dart';
+  /// ```
+  ///
+  /// ```dart main
+  /// void main() => runApp(MyApp());
+  /// ```
+  ///
+  /// ```dart preamble
+  /// class MyApp extends StatelessWidget {
+  ///   // This widget is the root of your application.
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return MaterialApp(
+  ///       title: 'Flutter Code Sample for Scaffold.of.',
+  ///       theme: ThemeData(
+  ///         primarySwatch: Colors.blue,
+  ///       ),
+  ///       home: Scaffold(
+  ///         body: MyScaffoldBody(),
+  ///         appBar: AppBar(title: const Text('Scaffold.of Example')),
+  ///       ),
+  ///       color: Colors.white,
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  ///
+  /// ```dart
+  /// class MyScaffoldBody extends StatelessWidget {
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Center(
+  ///       child: ElevatedButton(
+  ///         child: const Text('SHOW BOTTOM SHEET'),
+  ///         onPressed: () {
+  ///           Scaffold.of(context).showBottomSheet<void>(
+  ///             (BuildContext context) {
+  ///               return Container(
+  ///                 alignment: Alignment.center,
+  ///                 height: 200,
+  ///                 color: Colors.amber,
+  ///                 child: Center(
+  ///                   child: Column(
+  ///                     mainAxisSize: MainAxisSize.min,
+  ///                     children: <Widget>[
+  ///                       const Text('BottomSheet'),
+  ///                       ElevatedButton(
+  ///                         child: const Text('Close BottomSheet'),
+  ///                         onPressed: () {
+  ///                           Navigator.pop(context);
+  ///                         },
+  ///                       )
+  ///                     ],
+  ///                   ),
+  ///                 ),
+  ///               );
+  ///             },
+  ///           );
+  ///         },
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@tool dartpad --template=stateless_widget_material}
+  /// When the [Scaffold] is actually created in the same `build` function, the
+  /// `context` argument to the `build` function can't be used to find the
+  /// [Scaffold] (since it's "above" the widget being returned in the widget
+  /// tree). In such cases, the following technique with a [Builder] can be used
+  /// to provide a new scope with a [BuildContext] that is "under" the
+  /// [Scaffold]:
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return Scaffold(
+  ///     appBar: AppBar(title: const Text('Demo')),
+  ///     body: Builder(
+  ///       // Create an inner BuildContext so that the onPressed methods
+  ///       // can refer to the Scaffold with Scaffold.of().
+  ///       builder: (BuildContext context) {
+  ///         return Center(
+  ///           child: ElevatedButton(
+  ///             child: const Text('SHOW BOTTOM SHEET'),
+  ///             onPressed: () {
+  ///               Scaffold.of(context).showBottomSheet<void>(
+  ///                 (BuildContext context) {
+  ///                   return Container(
+  ///                     alignment: Alignment.center,
+  ///                     height: 200,
+  ///                     color: Colors.amber,
+  ///                     child: Center(
+  ///                       child: Column(
+  ///                         mainAxisSize: MainAxisSize.min,
+  ///                         children: <Widget>[
+  ///                           const Text('BottomSheet'),
+  ///                           ElevatedButton(
+  ///                             child: const Text('Close BottomSheet'),
+  ///                             onPressed: () {
+  ///                               Navigator.pop(context);
+  ///                             },
+  ///                           )
+  ///                         ],
+  ///                       ),
+  ///                     ),
+  ///                   );
+  ///                 },
+  ///               );
+  ///             },
+  ///           ),
+  ///         );
+  ///       },
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// A more efficient solution is to split your build function into several
+  /// widgets. This introduces a new context from which you can obtain the
+  /// [Scaffold]. In this solution, you would have an outer widget that creates
+  /// the [Scaffold] populated by instances of your new inner widgets, and then
+  /// in these inner widgets you would use [Scaffold.of].
+  ///
+  /// A less elegant but more expedient solution is assign a [GlobalKey] to the
+  /// [Scaffold], then use the `key.currentState` property to obtain the
+  /// [ScaffoldState] rather than using the [Scaffold.of] function.
+  ///
+  /// If there is no [Scaffold] in scope, then this will throw an exception.
+  /// To return null if there is no [Scaffold], use [maybeOf] instead.
+  static ScaffoldState of(BuildContext context) {
+    assert(context != null);
+    final ScaffoldState? result = context.findAncestorStateOfType<ScaffoldState>();
+    if (result != null)
+      return result;
+    throw FlutterError.fromParts(<DiagnosticsNode>[
+      ErrorSummary(
+        'Scaffold.of() called with a context that does not contain a Scaffold.'
+      ),
+      ErrorDescription(
+        'No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of(). '
+        'This usually happens when the context provided is from the same StatefulWidget as that '
+        'whose build function actually creates the Scaffold widget being sought.'
+      ),
+      ErrorHint(
+        'There are several ways to avoid this problem. The simplest is to use a Builder to get a '
+        'context that is "under" the Scaffold. For an example of this, please see the '
+        'documentation for Scaffold.of():\n'
+        '  https://api.flutter.dev/flutter/material/Scaffold/of.html'
+      ),
+      ErrorHint(
+        'A more efficient solution is to split your build function into several widgets. This '
+        'introduces a new context from which you can obtain the Scaffold. In this solution, '
+        'you would have an outer widget that creates the Scaffold populated by instances of '
+        'your new inner widgets, and then in these inner widgets you would use Scaffold.of().\n'
+        'A less elegant but more expedient solution is assign a GlobalKey to the Scaffold, '
+        'then use the key.currentState property to obtain the ScaffoldState rather than '
+        'using the Scaffold.of() function.'
+      ),
+      context.describeElement('The context used was')
+    ]);
+  }
+
+  /// Finds the [ScaffoldState] from the closest instance of this class that
+  /// encloses the given context.
+  ///
+  /// If no instance of this class encloses the given context, will return null.
+  /// To throw an exception instead, use [of] instead of this function.
+  ///
+  /// See also:
+  ///
+  ///  * [of], a similar function to this one that throws if no instance
+  ///    encloses the given context. Also includes some sample code in its
+  ///    documentation.
+  static ScaffoldState? maybeOf(BuildContext context) {
+    assert(context != null);
+    return context.findAncestorStateOfType<ScaffoldState>();
+  }
+
+  /// Returns a [ValueListenable] for the [ScaffoldGeometry] for the closest
+  /// [Scaffold] ancestor of the given context.
+  ///
+  /// The [ValueListenable.value] is only available at paint time.
+  ///
+  /// Notifications are guaranteed to be sent before the first paint pass
+  /// with the new geometry, but there is no guarantee whether a build or
+  /// layout passes are going to happen between the notification and the next
+  /// paint pass.
+  ///
+  /// The closest [Scaffold] ancestor for the context might change, e.g when
+  /// an element is moved from one scaffold to another. For [StatefulWidget]s
+  /// using this listenable, a change of the [Scaffold] ancestor will
+  /// trigger a [State.didChangeDependencies].
+  ///
+  /// A typical pattern for listening to the scaffold geometry would be to
+  /// call [Scaffold.geometryOf] in [State.didChangeDependencies], compare the
+  /// return value with the previous listenable, if it has changed, unregister
+  /// the listener, and register a listener to the new [ScaffoldGeometry]
+  /// listenable.
+  static ValueListenable<ScaffoldGeometry> geometryOf(BuildContext context) {
+    final _ScaffoldScope? scaffoldScope = context.dependOnInheritedWidgetOfExactType<_ScaffoldScope>();
+    if (scaffoldScope == null)
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary(
+          'Scaffold.geometryOf() called with a context that does not contain a Scaffold.'
+        ),
+        ErrorDescription(
+          'This usually happens when the context provided is from the same StatefulWidget as that '
+          'whose build function actually creates the Scaffold widget being sought.'
+        ),
+        ErrorHint(
+          'There are several ways to avoid this problem. The simplest is to use a Builder to get a '
+          'context that is "under" the Scaffold. For an example of this, please see the '
+          'documentation for Scaffold.of():\n'
+          '  https://api.flutter.dev/flutter/material/Scaffold/of.html'
+        ),
+        ErrorHint(
+          'A more efficient solution is to split your build function into several widgets. This '
+          'introduces a new context from which you can obtain the Scaffold. In this solution, '
+          'you would have an outer widget that creates the Scaffold populated by instances of '
+          'your new inner widgets, and then in these inner widgets you would use Scaffold.geometryOf().',
+        ),
+        context.describeElement('The context used was')
+      ]);
+    return scaffoldScope.geometryNotifier;
+  }
+
+  /// Whether the Scaffold that most tightly encloses the given context has a
+  /// drawer.
+  ///
+  /// If this is being used during a build (for example to decide whether to
+  /// show an "open drawer" button), set the `registerForUpdates` argument to
+  /// true. This will then set up an [InheritedWidget] relationship with the
+  /// [Scaffold] so that the client widget gets rebuilt whenever the [hasDrawer]
+  /// value changes.
+  ///
+  /// See also:
+  ///
+  ///  * [Scaffold.of], which provides access to the [ScaffoldState] object as a
+  ///    whole, from which you can show bottom sheets, and so forth.
+  static bool hasDrawer(BuildContext context, { bool registerForUpdates = true }) {
+    assert(registerForUpdates != null);
+    assert(context != null);
+    if (registerForUpdates) {
+      final _ScaffoldScope? scaffold = context.dependOnInheritedWidgetOfExactType<_ScaffoldScope>();
+      return scaffold?.hasDrawer ?? false;
+    } else {
+      final ScaffoldState? scaffold = context.findAncestorStateOfType<ScaffoldState>();
+      return scaffold?.hasDrawer ?? false;
+    }
+  }
+
+  @override
+  ScaffoldState createState() => ScaffoldState();
+}
+
+/// State for a [Scaffold].
+///
+/// Can display [BottomSheet]s. Retrieve a [ScaffoldState] from the current
+/// [BuildContext] using [Scaffold.of].
+class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
+
+  // DRAWER API
+
+  final GlobalKey<DrawerControllerState> _drawerKey = GlobalKey<DrawerControllerState>();
+  final GlobalKey<DrawerControllerState> _endDrawerKey = GlobalKey<DrawerControllerState>();
+
+  /// Whether this scaffold has a non-null [Scaffold.appBar].
+  bool get hasAppBar => widget.appBar != null;
+  /// Whether this scaffold has a non-null [Scaffold.drawer].
+  bool get hasDrawer => widget.drawer != null;
+  /// Whether this scaffold has a non-null [Scaffold.endDrawer].
+  bool get hasEndDrawer => widget.endDrawer != null;
+  /// Whether this scaffold has a non-null [Scaffold.floatingActionButton].
+  bool get hasFloatingActionButton => widget.floatingActionButton != null;
+
+  double? _appBarMaxHeight;
+  /// The max height the [Scaffold.appBar] uses.
+  ///
+  /// This is based on the appBar preferred height plus the top padding.
+  double? get appBarMaxHeight => _appBarMaxHeight;
+  bool _drawerOpened = false;
+  bool _endDrawerOpened = false;
+
+  /// Whether the [Scaffold.drawer] is opened.
+  ///
+  /// See also:
+  ///
+  ///  * [ScaffoldState.openDrawer], which opens the [Scaffold.drawer] of a
+  ///    [Scaffold].
+  bool get isDrawerOpen => _drawerOpened;
+
+  /// Whether the [Scaffold.endDrawer] is opened.
+  ///
+  /// See also:
+  ///
+  ///  * [ScaffoldState.openEndDrawer], which opens the [Scaffold.endDrawer] of
+  ///    a [Scaffold].
+  bool get isEndDrawerOpen => _endDrawerOpened;
+
+  void _drawerOpenedCallback(bool isOpened) {
+    setState(() {
+      _drawerOpened = isOpened;
+    });
+    widget.onDrawerChanged?.call(isOpened);
+  }
+
+  void _endDrawerOpenedCallback(bool isOpened) {
+    setState(() {
+      _endDrawerOpened = isOpened;
+    });
+    widget.onEndDrawerChanged?.call(isOpened);
+  }
+
+  /// Opens the [Drawer] (if any).
+  ///
+  /// If the scaffold has a non-null [Scaffold.drawer], this function will cause
+  /// the drawer to begin its entrance animation.
+  ///
+  /// Normally this is not needed since the [Scaffold] automatically shows an
+  /// appropriate [IconButton], and handles the edge-swipe gesture, to show the
+  /// drawer.
+  ///
+  /// To close the drawer once it is open, use [Navigator.pop].
+  ///
+  /// See [Scaffold.of] for information about how to obtain the [ScaffoldState].
+  void openDrawer() {
+    if (_endDrawerKey.currentState != null && _endDrawerOpened)
+      _endDrawerKey.currentState!.close();
+    _drawerKey.currentState?.open();
+  }
+
+  /// Opens the end side [Drawer] (if any).
+  ///
+  /// If the scaffold has a non-null [Scaffold.endDrawer], this function will cause
+  /// the end side drawer to begin its entrance animation.
+  ///
+  /// Normally this is not needed since the [Scaffold] automatically shows an
+  /// appropriate [IconButton], and handles the edge-swipe gesture, to show the
+  /// drawer.
+  ///
+  /// To close the end side drawer once it is open, use [Navigator.pop].
+  ///
+  /// See [Scaffold.of] for information about how to obtain the [ScaffoldState].
+  void openEndDrawer() {
+    if (_drawerKey.currentState != null && _drawerOpened)
+      _drawerKey.currentState!.close();
+    _endDrawerKey.currentState?.open();
+  }
+
+  // SNACKBAR API
+  final Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>> _snackBars = Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>>();
+  AnimationController? _snackBarController;
+  Timer? _snackBarTimer;
+  bool? _accessibleNavigation;
+  ScaffoldMessengerState? _scaffoldMessenger;
+
+  /// [ScaffoldMessengerState.showSnackBar] shows a [SnackBar] at the bottom of
+  /// the scaffold. This method should not be used, and will be deprecated in
+  /// the near future..
+  ///
+  /// A scaffold can show at most one snack bar at a time. If this function is
+  /// called while another snack bar is already visible, the given snack bar
+  /// will be added to a queue and displayed after the earlier snack bars have
+  /// closed.
+  ///
+  /// To control how long a [SnackBar] remains visible, use [SnackBar.duration].
+  ///
+  /// To remove the [SnackBar] with an exit animation, use
+  /// [ScaffoldMessengerState.hideCurrentSnackBar] or call
+  /// [ScaffoldFeatureController.close] on the returned [ScaffoldFeatureController].
+  /// To remove a [SnackBar] suddenly (without an animation), use
+  /// [ScaffoldMessengerState.removeCurrentSnackBar].
+  ///
+  /// See [ScaffoldMessenger.of] for information about how to obtain the
+  /// [ScaffoldMessengerState].
+  ///
+  /// {@tool dartpad --template=stateless_widget_scaffold_center}
+  ///
+  /// Here is an example of showing a [SnackBar] when the user presses a button.
+  ///
+  /// ```dart
+  ///   Widget build(BuildContext context) {
+  ///     return OutlinedButton(
+  ///       onPressed: () {
+  ///         ScaffoldMessenger.of(context).showSnackBar(
+  ///           SnackBar(
+  ///             content: const Text('A SnackBar has been shown.'),
+  ///           ),
+  ///         );
+  ///       },
+  ///       child: const Text('Show SnackBar'),
+  ///     );
+  ///   }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///   * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
+  @Deprecated(
+    'Use ScaffoldMessenger.showSnackBar. '
+    'This feature was deprecated after v1.23.0-14.0.pre.'
+  )
+  ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(SnackBar snackbar) {
+    _snackBarController ??= SnackBar.createAnimationController(vsync: this)
+      ..addStatusListener(_handleSnackBarStatusChange);
+    if (_snackBars.isEmpty) {
+      assert(_snackBarController!.isDismissed);
+      _snackBarController!.forward();
+    }
+    late ScaffoldFeatureController<SnackBar, SnackBarClosedReason> controller;
+    controller = ScaffoldFeatureController<SnackBar, SnackBarClosedReason>._(
+      // We provide a fallback key so that if back-to-back snackbars happen to
+      // match in structure, material ink splashes and highlights don't survive
+      // from one to the next.
+      snackbar.withAnimation(_snackBarController!, fallbackKey: UniqueKey()),
+      Completer<SnackBarClosedReason>(),
+      () {
+        assert(_snackBars.first == controller);
+        hideCurrentSnackBar(reason: SnackBarClosedReason.hide);
+      },
+      null, // SnackBar doesn't use a builder function so setState() wouldn't rebuild it
+    );
+    setState(() {
+      _snackBars.addLast(controller);
+    });
+    return controller;
+  }
+
+  void _handleSnackBarStatusChange(AnimationStatus status) {
+    switch (status) {
+      case AnimationStatus.dismissed:
+        assert(_snackBars.isNotEmpty);
+        setState(() {
+          _snackBars.removeFirst();
+        });
+        if (_snackBars.isNotEmpty)
+          _snackBarController!.forward();
+        break;
+      case AnimationStatus.completed:
+        setState(() {
+          assert(_snackBarTimer == null);
+          // build will create a new timer if necessary to dismiss the snack bar
+        });
+        break;
+      case AnimationStatus.forward:
+      case AnimationStatus.reverse:
+        break;
+    }
+  }
+
+  /// [ScaffoldMessengerState.removeCurrentSnackBar] removes the current
+  /// [SnackBar] (if any) immediately. This method should not be used, and will
+  /// be deprecated in the near future.
+  ///
+  /// The removed snack bar does not run its normal exit animation. If there are
+  /// any queued snack bars, they begin their entrance animation immediately.
+  ///
+  /// See also:
+  ///
+  ///   * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
+  @Deprecated(
+    'Use ScaffoldMessenger.removeCurrentSnackBar. '
+    'This feature was deprecated after v1.23.0-14.0.pre.'
+  )
+  void removeCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.remove }) {
+    assert(reason != null);
+
+    // SnackBars and SnackBarActions can call to hide and remove themselves, but
+    // they are not aware of who presented them, the Scaffold or the
+    // ScaffoldMessenger. As such, when the SnackBar classes call upon Scaffold
+    // to remove (the current default), we should re-direct to the
+    // ScaffoldMessenger here if that is where the SnackBar originated from.
+    if (_messengerSnackBar != null) {
+      // ScaffoldMessenger is presenting SnackBars.
+      assert(debugCheckHasScaffoldMessenger(context));
+      assert(
+        _scaffoldMessenger != null,
+        'A SnackBar was shown by the ScaffoldMessenger, but has been called upon'
+          'to be removed from a Scaffold that is not registered with a '
+          'ScaffoldMessenger, this can happen if a Scaffold has been rebuilt '
+          'without an ancestor ScaffoldMessenger.',
+      );
+      _scaffoldMessenger!.removeCurrentSnackBar(reason: reason);
+      return;
+    }
+
+    if (_snackBars.isEmpty)
+      return;
+    final Completer<SnackBarClosedReason> completer = _snackBars.first._completer;
+    if (!completer.isCompleted)
+      completer.complete(reason);
+    _snackBarTimer?.cancel();
+    _snackBarTimer = null;
+    _snackBarController!.value = 0.0;
+  }
+
+  /// [ScaffoldMessengerState.hideCurrentSnackBar] removes the current
+  /// [SnackBar] by running its normal exit animation. This method should not be
+  /// used, and will be deprecated in the near future.
+  ///
+  /// The closed completer is called after the animation is complete.
+  ///
+  /// See also:
+  ///
+  ///   * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
+  @Deprecated(
+    'Use ScaffoldMessenger.hideCurrentSnackBar. '
+    'This feature was deprecated after v1.23.0-14.0.pre.'
+  )
+  void hideCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.hide }) {
+    assert(reason != null);
+
+    // SnackBars and SnackBarActions can call to hide and remove themselves, but
+    // they are not aware of who presented them, the Scaffold or the
+    // ScaffoldMessenger. As such, when the SnackBar classes call upon Scaffold
+    // to remove (the current default), we should re-direct to the
+    // ScaffoldMessenger here if that is where the SnackBar originated from.
+    if (_messengerSnackBar != null) {
+      // ScaffoldMessenger is presenting SnackBars.
+      assert(debugCheckHasScaffoldMessenger(context));
+      assert(
+      _scaffoldMessenger != null,
+      'A SnackBar was shown by the ScaffoldMessenger, but has been called upon'
+        'to be removed from a Scaffold that is not registered with a '
+        'ScaffoldMessenger, this can happen if a Scaffold has been rebuilt '
+        'without an ancestor ScaffoldMessenger.',
+      );
+      _scaffoldMessenger!.hideCurrentSnackBar(reason: reason);
+      return;
+    }
+
+    if (_snackBars.isEmpty || _snackBarController!.status == AnimationStatus.dismissed)
+      return;
+    final MediaQueryData mediaQuery = MediaQuery.of(context);
+    final Completer<SnackBarClosedReason> completer = _snackBars.first._completer;
+    if (mediaQuery.accessibleNavigation) {
+      _snackBarController!.value = 0.0;
+      completer.complete(reason);
+    } else {
+      _snackBarController!.reverse().then<void>((void value) {
+        assert(mounted);
+        if (!completer.isCompleted)
+          completer.complete(reason);
+      });
+    }
+    _snackBarTimer?.cancel();
+    _snackBarTimer = null;
+  }
+
+  // The _messengerSnackBar represents the current SnackBar being managed by
+  // the ScaffoldMessenger, instead of the Scaffold.
+  ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? _messengerSnackBar;
+
+  // This is used to update the _messengerSnackBar by the ScaffoldMessenger.
+  void _updateSnackBar() {
+    setState(() {
+        _messengerSnackBar = _scaffoldMessenger!._snackBars.isNotEmpty
+          ? _scaffoldMessenger!._snackBars.first
+          : null;
+    });
+  }
+
+  // PERSISTENT BOTTOM SHEET API
+
+  // Contains bottom sheets that may still be animating out of view.
+  // Important if the app/user takes an action that could repeatedly show a
+  // bottom sheet.
+  final List<_StandardBottomSheet> _dismissedBottomSheets = <_StandardBottomSheet>[];
+  PersistentBottomSheetController<dynamic>? _currentBottomSheet;
+
+  void _maybeBuildPersistentBottomSheet() {
+    if (widget.bottomSheet != null && _currentBottomSheet == null) {
+      // The new _currentBottomSheet is not a local history entry so a "back" button
+      // will not be added to the Scaffold's appbar and the bottom sheet will not
+      // support drag or swipe to dismiss.
+      final AnimationController animationController = BottomSheet.createAnimationController(this)..value = 1.0;
+      LocalHistoryEntry? _persistentSheetHistoryEntry;
+      bool _persistentBottomSheetExtentChanged(DraggableScrollableNotification notification) {
+        if (notification.extent > notification.initialExtent) {
+          if (_persistentSheetHistoryEntry == null) {
+            _persistentSheetHistoryEntry = LocalHistoryEntry(onRemove: () {
+              if (notification.extent > notification.initialExtent) {
+                DraggableScrollableActuator.reset(notification.context);
+              }
+              showBodyScrim(false, 0.0);
+              _floatingActionButtonVisibilityValue = 1.0;
+              _persistentSheetHistoryEntry = null;
+            });
+            ModalRoute.of(context)!.addLocalHistoryEntry(_persistentSheetHistoryEntry!);
+          }
+        } else if (_persistentSheetHistoryEntry != null) {
+          ModalRoute.of(context)!.removeLocalHistoryEntry(_persistentSheetHistoryEntry!);
+        }
+        return false;
+      }
+
+      _currentBottomSheet = _buildBottomSheet<void>(
+        (BuildContext context) {
+          return NotificationListener<DraggableScrollableNotification>(
+            onNotification: _persistentBottomSheetExtentChanged,
+            child: DraggableScrollableActuator(
+              child: widget.bottomSheet!,
+            ),
+          );
+        },
+        true,
+        animationController: animationController,
+      );
+    }
+  }
+
+  void _closeCurrentBottomSheet() {
+    if (_currentBottomSheet != null) {
+      if (!_currentBottomSheet!._isLocalHistoryEntry) {
+        _currentBottomSheet!.close();
+      }
+      assert(() {
+        _currentBottomSheet?._completer.future.whenComplete(() {
+          assert(_currentBottomSheet == null);
+        });
+        return true;
+      }());
+    }
+  }
+
+  PersistentBottomSheetController<T> _buildBottomSheet<T>(
+    WidgetBuilder builder,
+    bool isPersistent, {
+    required AnimationController animationController,
+    Color? backgroundColor,
+    double? elevation,
+    ShapeBorder? shape,
+    Clip? clipBehavior,
+  }) {
+    assert(() {
+      if (widget.bottomSheet != null && isPersistent && _currentBottomSheet != null) {
+        throw FlutterError(
+          'Scaffold.bottomSheet cannot be specified while a bottom sheet '
+          'displayed with showBottomSheet() is still visible.\n'
+          'Rebuild the Scaffold with a null bottomSheet before calling showBottomSheet().'
+        );
+      }
+      return true;
+    }());
+
+    final Completer<T> completer = Completer<T>();
+    final GlobalKey<_StandardBottomSheetState> bottomSheetKey = GlobalKey<_StandardBottomSheetState>();
+    late _StandardBottomSheet bottomSheet;
+
+    bool removedEntry = false;
+    void _removeCurrentBottomSheet() {
+      removedEntry = true;
+      if (_currentBottomSheet == null) {
+        return;
+      }
+      assert(_currentBottomSheet!._widget == bottomSheet);
+      assert(bottomSheetKey.currentState != null);
+      _showFloatingActionButton();
+
+      bottomSheetKey.currentState!.close();
+      setState(() {
+        _currentBottomSheet = null;
+      });
+
+      if (animationController.status != AnimationStatus.dismissed) {
+        _dismissedBottomSheets.add(bottomSheet);
+      }
+      completer.complete();
+    }
+
+    final LocalHistoryEntry? entry = isPersistent
+      ? null
+      : LocalHistoryEntry(onRemove: () {
+          if (!removedEntry) {
+            _removeCurrentBottomSheet();
+          }
+        });
+
+    bottomSheet = _StandardBottomSheet(
+      key: bottomSheetKey,
+      animationController: animationController,
+      enableDrag: !isPersistent,
+      onClosing: () {
+        if (_currentBottomSheet == null) {
+          return;
+        }
+        assert(_currentBottomSheet!._widget == bottomSheet);
+        if (!isPersistent && !removedEntry) {
+          assert(entry != null);
+          entry!.remove();
+          removedEntry = true;
+        }
+      },
+      onDismissed: () {
+        if (_dismissedBottomSheets.contains(bottomSheet)) {
+          setState(() {
+            _dismissedBottomSheets.remove(bottomSheet);
+          });
+        }
+      },
+      builder: builder,
+      isPersistent: isPersistent,
+      backgroundColor: backgroundColor,
+      elevation: elevation,
+      shape: shape,
+      clipBehavior: clipBehavior,
+    );
+
+    if (!isPersistent)
+      ModalRoute.of(context)!.addLocalHistoryEntry(entry!);
+
+    return PersistentBottomSheetController<T>._(
+      bottomSheet,
+      completer,
+      entry != null
+        ? entry.remove
+        : _removeCurrentBottomSheet,
+      (VoidCallback fn) { bottomSheetKey.currentState?.setState(fn); },
+      !isPersistent,
+    );
+  }
+
+  /// Shows a material design bottom sheet in the nearest [Scaffold]. To show
+  /// a persistent bottom sheet, use the [Scaffold.bottomSheet].
+  ///
+  /// Returns a controller that can be used to close and otherwise manipulate the
+  /// bottom sheet.
+  ///
+  /// To rebuild the bottom sheet (e.g. if it is stateful), call
+  /// [PersistentBottomSheetController.setState] on the controller returned by
+  /// this method.
+  ///
+  /// The new bottom sheet becomes a [LocalHistoryEntry] for the enclosing
+  /// [ModalRoute] and a back button is added to the app bar of the [Scaffold]
+  /// that closes the bottom sheet.
+  ///
+  /// To create a persistent bottom sheet that is not a [LocalHistoryEntry] and
+  /// does not add a back button to the enclosing Scaffold's app bar, use the
+  /// [Scaffold.bottomSheet] constructor parameter.
+  ///
+  /// A persistent bottom sheet shows information that supplements the primary
+  /// content of the app. A persistent bottom sheet remains visible even when
+  /// the user interacts with other parts of the app.
+  ///
+  /// A closely related widget is a modal bottom sheet, which is an alternative
+  /// to a menu or a dialog and prevents the user from interacting with the rest
+  /// of the app. Modal bottom sheets can be created and displayed with the
+  /// [showModalBottomSheet] function.
+  ///
+  /// {@tool dartpad --template=stateless_widget_scaffold}
+  ///
+  /// This example demonstrates how to use `showBottomSheet` to display a
+  /// bottom sheet when a user taps a button. It also demonstrates how to
+  /// close a bottom sheet using the Navigator.
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return Center(
+  ///     child: ElevatedButton(
+  ///       child: const Text('showBottomSheet'),
+  ///       onPressed: () {
+  ///         Scaffold.of(context).showBottomSheet<void>(
+  ///           (BuildContext context) {
+  ///             return Container(
+  ///               height: 200,
+  ///               color: Colors.amber,
+  ///               child: Center(
+  ///                 child: Column(
+  ///                   mainAxisAlignment: MainAxisAlignment.center,
+  ///                   mainAxisSize: MainAxisSize.min,
+  ///                   children: <Widget>[
+  ///                     const Text('BottomSheet'),
+  ///                     ElevatedButton(
+  ///                       child: const Text('Close BottomSheet'),
+  ///                       onPressed: () {
+  ///                         Navigator.pop(context);
+  ///                       }
+  ///                     )
+  ///                   ],
+  ///                 ),
+  ///               ),
+  ///             );
+  ///           },
+  ///         );
+  ///       },
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  /// See also:
+  ///
+  ///  * [BottomSheet], which becomes the parent of the widget returned by the
+  ///    `builder`.
+  ///  * [showBottomSheet], which calls this method given a [BuildContext].
+  ///  * [showModalBottomSheet], which can be used to display a modal bottom
+  ///    sheet.
+  ///  * [Scaffold.of], for information about how to obtain the [ScaffoldState].
+  ///  * <https://material.io/design/components/sheets-bottom.html#standard-bottom-sheet>
+  PersistentBottomSheetController<T> showBottomSheet<T>(
+    WidgetBuilder builder, {
+    Color? backgroundColor,
+    double? elevation,
+    ShapeBorder? shape,
+    Clip? clipBehavior,
+  }) {
+    assert(() {
+      if (widget.bottomSheet != null) {
+        throw FlutterError(
+          'Scaffold.bottomSheet cannot be specified while a bottom sheet '
+          'displayed with showBottomSheet() is still visible.\n'
+          'Rebuild the Scaffold with a null bottomSheet before calling showBottomSheet().'
+        );
+      }
+      return true;
+    }());
+    assert(debugCheckHasMediaQuery(context));
+
+    _closeCurrentBottomSheet();
+    final AnimationController controller = BottomSheet.createAnimationController(this)..forward();
+    setState(() {
+      _currentBottomSheet = _buildBottomSheet<T>(
+        builder,
+        false,
+        animationController: controller,
+        backgroundColor: backgroundColor,
+        elevation: elevation,
+        shape: shape,
+        clipBehavior: clipBehavior,
+      );
+    });
+    return _currentBottomSheet! as PersistentBottomSheetController<T>;
+  }
+
+  // Floating Action Button API
+  late AnimationController _floatingActionButtonMoveController;
+  late FloatingActionButtonAnimator _floatingActionButtonAnimator;
+  FloatingActionButtonLocation? _previousFloatingActionButtonLocation;
+  FloatingActionButtonLocation? _floatingActionButtonLocation;
+
+  late AnimationController _floatingActionButtonVisibilityController;
+
+  /// Gets the current value of the visibility animation for the
+  /// [Scaffold.floatingActionButton].
+  double get _floatingActionButtonVisibilityValue => _floatingActionButtonVisibilityController.value;
+
+  /// Sets the current value of the visibility animation for the
+  /// [Scaffold.floatingActionButton].  This value must not be null.
+  set _floatingActionButtonVisibilityValue(double newValue) {
+    assert(newValue != null);
+    _floatingActionButtonVisibilityController.value = newValue.clamp(
+      _floatingActionButtonVisibilityController.lowerBound,
+      _floatingActionButtonVisibilityController.upperBound,
+    );
+  }
+
+  /// Shows the [Scaffold.floatingActionButton].
+  TickerFuture _showFloatingActionButton() {
+    return _floatingActionButtonVisibilityController.forward();
+  }
+
+  // Moves the Floating Action Button to the new Floating Action Button Location.
+  void _moveFloatingActionButton(final FloatingActionButtonLocation newLocation) {
+    FloatingActionButtonLocation? previousLocation = _floatingActionButtonLocation;
+    double restartAnimationFrom = 0.0;
+    // If the Floating Action Button is moving right now, we need to start from a snapshot of the current transition.
+    if (_floatingActionButtonMoveController.isAnimating) {
+      previousLocation = _TransitionSnapshotFabLocation(_previousFloatingActionButtonLocation!, _floatingActionButtonLocation!, _floatingActionButtonAnimator, _floatingActionButtonMoveController.value);
+      restartAnimationFrom = _floatingActionButtonAnimator.getAnimationRestart(_floatingActionButtonMoveController.value);
+    }
+
+    setState(() {
+      _previousFloatingActionButtonLocation = previousLocation;
+      _floatingActionButtonLocation = newLocation;
+    });
+
+    // Animate the motion even when the fab is null so that if the exit animation is running,
+    // the old fab will start the motion transition while it exits instead of jumping to the
+    // new position.
+    _floatingActionButtonMoveController.forward(from: restartAnimationFrom);
+  }
+
+  // iOS FEATURES - status bar tap, back gesture
+
+  // On iOS, tapping the status bar scrolls the app's primary scrollable to the
+  // top. We implement this by looking up the  primary scroll controller and
+  // scrolling it to the top when tapped.
+  void _handleStatusBarTap() {
+    final ScrollController? _primaryScrollController = PrimaryScrollController.of(context);
+    if (_primaryScrollController != null && _primaryScrollController.hasClients) {
+      _primaryScrollController.animateTo(
+        0.0,
+        duration: const Duration(milliseconds: 300),
+        curve: Curves.linear, // TODO(ianh): Use a more appropriate curve.
+      );
+    }
+  }
+
+  // INTERNALS
+
+  late _ScaffoldGeometryNotifier _geometryNotifier;
+
+  // Backwards compatibility for deprecated resizeToAvoidBottomPadding property
+  bool get _resizeToAvoidBottomInset {
+    return widget.resizeToAvoidBottomInset ?? widget.resizeToAvoidBottomPadding ?? true;
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _geometryNotifier = _ScaffoldGeometryNotifier(const ScaffoldGeometry(), context);
+    _floatingActionButtonLocation = widget.floatingActionButtonLocation ?? _kDefaultFloatingActionButtonLocation;
+    _floatingActionButtonAnimator = widget.floatingActionButtonAnimator ?? _kDefaultFloatingActionButtonAnimator;
+    _previousFloatingActionButtonLocation = _floatingActionButtonLocation;
+    _floatingActionButtonMoveController = AnimationController(
+      vsync: this,
+      lowerBound: 0.0,
+      upperBound: 1.0,
+      value: 1.0,
+      duration: kFloatingActionButtonSegue * 2,
+    );
+
+    _floatingActionButtonVisibilityController = AnimationController(
+      duration: kFloatingActionButtonSegue,
+      vsync: this,
+    );
+  }
+
+  @override
+  void didUpdateWidget(Scaffold oldWidget) {
+    // Update the Floating Action Button Animator, and then schedule the Floating Action Button for repositioning.
+    if (widget.floatingActionButtonAnimator != oldWidget.floatingActionButtonAnimator) {
+      _floatingActionButtonAnimator = widget.floatingActionButtonAnimator ?? _kDefaultFloatingActionButtonAnimator;
+    }
+    if (widget.floatingActionButtonLocation != oldWidget.floatingActionButtonLocation) {
+      _moveFloatingActionButton(widget.floatingActionButtonLocation ?? _kDefaultFloatingActionButtonLocation);
+    }
+    if (widget.bottomSheet != oldWidget.bottomSheet) {
+      assert(() {
+        if (widget.bottomSheet != null && _currentBottomSheet?._isLocalHistoryEntry == true) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary(
+              'Scaffold.bottomSheet cannot be specified while a bottom sheet displayed '
+              'with showBottomSheet() is still visible.'
+            ),
+            ErrorHint(
+              'Use the PersistentBottomSheetController '
+              'returned by showBottomSheet() to close the old bottom sheet before creating '
+              'a Scaffold with a (non null) bottomSheet.'
+            ),
+          ]);
+        }
+        return true;
+      }());
+      _closeCurrentBottomSheet();
+      _maybeBuildPersistentBottomSheet();
+    }
+    super.didUpdateWidget(oldWidget);
+  }
+
+  @override
+  void didChangeDependencies() {
+    // nullOk is valid here since  both the Scaffold and ScaffoldMessenger are
+    // currently available for managing SnackBars.
+    final ScaffoldMessengerState? _currentScaffoldMessenger = ScaffoldMessenger.maybeOf(context);
+    // If our ScaffoldMessenger has changed, unregister with the old one first.
+    if (_scaffoldMessenger != null &&
+      (_currentScaffoldMessenger == null || _scaffoldMessenger != _currentScaffoldMessenger)) {
+      _scaffoldMessenger?._unregister(this);
+    }
+    // Register with the current ScaffoldMessenger, if there is one.
+    _scaffoldMessenger = _currentScaffoldMessenger;
+    _scaffoldMessenger?._register(this);
+
+    // TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger
+    final MediaQueryData mediaQuery = MediaQuery.of(context);
+    // If we transition from accessible navigation to non-accessible navigation
+    // and there is a SnackBar that would have timed out that has already
+    // completed its timer, dismiss that SnackBar. If the timer hasn't finished
+    // yet, let it timeout as normal.
+    if (_accessibleNavigation == true
+      && !mediaQuery.accessibleNavigation
+      && _snackBarTimer != null
+      && !_snackBarTimer!.isActive) {
+      hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
+    }
+    _accessibleNavigation = mediaQuery.accessibleNavigation;
+
+    _maybeBuildPersistentBottomSheet();
+    super.didChangeDependencies();
+  }
+
+  @override
+  void dispose() {
+    // TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger
+    _snackBarController?.dispose();
+    _snackBarTimer?.cancel();
+    _snackBarTimer = null;
+
+    _geometryNotifier.dispose();
+    for (final _StandardBottomSheet bottomSheet in _dismissedBottomSheets) {
+      bottomSheet.animationController.dispose();
+    }
+    if (_currentBottomSheet != null) {
+      _currentBottomSheet!._widget.animationController.dispose();
+    }
+    _floatingActionButtonMoveController.dispose();
+    _floatingActionButtonVisibilityController.dispose();
+    _scaffoldMessenger?._unregister(this);
+    super.dispose();
+  }
+
+  void _addIfNonNull(
+    List<LayoutId> children,
+    Widget? child,
+    Object childId, {
+    required bool removeLeftPadding,
+    required bool removeTopPadding,
+    required bool removeRightPadding,
+    required bool removeBottomPadding,
+    bool removeBottomInset = false,
+    bool maintainBottomViewPadding = false,
+  }) {
+    MediaQueryData data = MediaQuery.of(context).removePadding(
+      removeLeft: removeLeftPadding,
+      removeTop: removeTopPadding,
+      removeRight: removeRightPadding,
+      removeBottom: removeBottomPadding,
+    );
+    if (removeBottomInset)
+      data = data.removeViewInsets(removeBottom: true);
+
+    if (maintainBottomViewPadding && data.viewInsets.bottom != 0.0) {
+      data = data.copyWith(
+        padding: data.padding.copyWith(bottom: data.viewPadding.bottom)
+      );
+    }
+
+    if (child != null) {
+      children.add(
+        LayoutId(
+          id: childId,
+          child: MediaQuery(data: data, child: child),
+        ),
+      );
+    }
+  }
+
+  void _buildEndDrawer(List<LayoutId> children, TextDirection textDirection) {
+    if (widget.endDrawer != null) {
+      assert(hasEndDrawer);
+      _addIfNonNull(
+        children,
+        DrawerController(
+          key: _endDrawerKey,
+          alignment: DrawerAlignment.end,
+          child: widget.endDrawer!,
+          drawerCallback: _endDrawerOpenedCallback,
+          dragStartBehavior: widget.drawerDragStartBehavior,
+          scrimColor: widget.drawerScrimColor,
+          edgeDragWidth: widget.drawerEdgeDragWidth,
+          enableOpenDragGesture: widget.endDrawerEnableOpenDragGesture,
+        ),
+        _ScaffoldSlot.endDrawer,
+        // remove the side padding from the side we're not touching
+        removeLeftPadding: textDirection == TextDirection.ltr,
+        removeTopPadding: false,
+        removeRightPadding: textDirection == TextDirection.rtl,
+        removeBottomPadding: false,
+      );
+    }
+  }
+
+  void _buildDrawer(List<LayoutId> children, TextDirection textDirection) {
+    if (widget.drawer != null) {
+      assert(hasDrawer);
+      _addIfNonNull(
+        children,
+        DrawerController(
+          key: _drawerKey,
+          alignment: DrawerAlignment.start,
+          child: widget.drawer!,
+          drawerCallback: _drawerOpenedCallback,
+          dragStartBehavior: widget.drawerDragStartBehavior,
+          scrimColor: widget.drawerScrimColor,
+          edgeDragWidth: widget.drawerEdgeDragWidth,
+          enableOpenDragGesture: widget.drawerEnableOpenDragGesture,
+        ),
+        _ScaffoldSlot.drawer,
+        // remove the side padding from the side we're not touching
+        removeLeftPadding: textDirection == TextDirection.rtl,
+        removeTopPadding: false,
+        removeRightPadding: textDirection == TextDirection.ltr,
+        removeBottomPadding: false,
+      );
+    }
+  }
+
+  bool _showBodyScrim = false;
+  Color _bodyScrimColor = Colors.black;
+
+  /// Whether to show a [ModalBarrier] over the body of the scaffold.
+  ///
+  /// The `value` parameter must not be null.
+  void showBodyScrim(bool value, double opacity) {
+    assert(value != null);
+    if (_showBodyScrim == value && _bodyScrimColor.opacity == opacity) {
+      return;
+    }
+    setState(() {
+      _showBodyScrim = value;
+      _bodyScrimColor = Colors.black.withOpacity(opacity);
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    assert(debugCheckHasDirectionality(context));
+    final MediaQueryData mediaQuery = MediaQuery.of(context);
+    final ThemeData themeData = Theme.of(context);
+    final TextDirection textDirection = Directionality.of(context);
+
+    // TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger
+    _accessibleNavigation = mediaQuery.accessibleNavigation;
+    if (_snackBars.isNotEmpty) {
+      final ModalRoute<dynamic>? route = ModalRoute.of(context);
+      if (route == null || route.isCurrent) {
+        if (_snackBarController!.isCompleted && _snackBarTimer == null) {
+          final SnackBar snackBar = _snackBars.first._widget;
+          _snackBarTimer = Timer(snackBar.duration, () {
+            assert(_snackBarController!.status == AnimationStatus.forward ||
+                   _snackBarController!.status == AnimationStatus.completed);
+            // Look up MediaQuery again in case the setting changed.
+            final MediaQueryData mediaQuery = MediaQuery.of(context);
+            if (mediaQuery.accessibleNavigation && snackBar.action != null)
+              return;
+            hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
+          });
+        }
+      } else {
+        _snackBarTimer?.cancel();
+        _snackBarTimer = null;
+      }
+    }
+
+    final List<LayoutId> children = <LayoutId>[];
+    _addIfNonNull(
+      children,
+      widget.body == null ? null : _BodyBuilder(
+        extendBody: widget.extendBody,
+        extendBodyBehindAppBar: widget.extendBodyBehindAppBar,
+        body: widget.body!,
+      ),
+      _ScaffoldSlot.body,
+      removeLeftPadding: false,
+      removeTopPadding: widget.appBar != null,
+      removeRightPadding: false,
+      removeBottomPadding: widget.bottomNavigationBar != null || widget.persistentFooterButtons != null,
+      removeBottomInset: _resizeToAvoidBottomInset,
+    );
+    if (_showBodyScrim) {
+      _addIfNonNull(
+        children,
+        ModalBarrier(
+          dismissible: false,
+          color: _bodyScrimColor,
+        ),
+        _ScaffoldSlot.bodyScrim,
+        removeLeftPadding: true,
+        removeTopPadding: true,
+        removeRightPadding: true,
+        removeBottomPadding: true,
+      );
+    }
+
+    if (widget.appBar != null) {
+      final double topPadding = widget.primary ? mediaQuery.padding.top : 0.0;
+      _appBarMaxHeight = widget.appBar!.preferredSize.height + topPadding;
+      assert(_appBarMaxHeight! >= 0.0 && _appBarMaxHeight!.isFinite);
+      _addIfNonNull(
+        children,
+        ConstrainedBox(
+          constraints: BoxConstraints(maxHeight: _appBarMaxHeight!),
+          child: FlexibleSpaceBar.createSettings(
+            currentExtent: _appBarMaxHeight!,
+            child: widget.appBar!,
+          ),
+        ),
+        _ScaffoldSlot.appBar,
+        removeLeftPadding: false,
+        removeTopPadding: false,
+        removeRightPadding: false,
+        removeBottomPadding: true,
+      );
+    }
+
+    bool isSnackBarFloating = false;
+    double? snackBarWidth;
+    // We should only be using one API for SnackBars. Currently, we can use the
+    // Scaffold, which creates a SnackBar queue (_snackBars), or the
+    // ScaffoldMessenger, which sends a SnackBar to descendant Scaffolds.
+    // (_messengerSnackBar).
+    assert(
+      _snackBars.isEmpty || _messengerSnackBar == null,
+      'Only one API should be used to manage SnackBars. The ScaffoldMessenger is '
+      'the preferred API instead of the Scaffold methods.'
+    );
+
+    // SnackBar set by ScaffoldMessenger
+    if (_messengerSnackBar != null) {
+      final SnackBarBehavior snackBarBehavior = _messengerSnackBar?._widget.behavior
+        ?? themeData.snackBarTheme.behavior
+        ?? SnackBarBehavior.fixed;
+      isSnackBarFloating = snackBarBehavior == SnackBarBehavior.floating;
+      snackBarWidth = _messengerSnackBar?._widget.width;
+
+      _addIfNonNull(
+        children,
+        _messengerSnackBar?._widget,
+        _ScaffoldSlot.snackBar,
+        removeLeftPadding: false,
+        removeTopPadding: true,
+        removeRightPadding: false,
+        removeBottomPadding: widget.bottomNavigationBar != null || widget.persistentFooterButtons != null,
+        maintainBottomViewPadding: !_resizeToAvoidBottomInset,
+      );
+    }
+
+    // SnackBar set by Scaffold
+    // TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger
+    if (_snackBars.isNotEmpty) {
+      final SnackBarBehavior snackBarBehavior = _snackBars.first._widget.behavior
+        ?? themeData.snackBarTheme.behavior
+        ?? SnackBarBehavior.fixed;
+      isSnackBarFloating = snackBarBehavior == SnackBarBehavior.floating;
+      snackBarWidth = _snackBars.first._widget.width;
+
+      _addIfNonNull(
+        children,
+        _snackBars.first._widget,
+        _ScaffoldSlot.snackBar,
+        removeLeftPadding: false,
+        removeTopPadding: true,
+        removeRightPadding: false,
+        removeBottomPadding: widget.bottomNavigationBar != null || widget.persistentFooterButtons != null,
+        maintainBottomViewPadding: !_resizeToAvoidBottomInset,
+      );
+    }
+
+    if (widget.persistentFooterButtons != null) {
+      _addIfNonNull(
+        children,
+        Container(
+          decoration: BoxDecoration(
+            border: Border(
+              top: Divider.createBorderSide(context, width: 1.0),
+            ),
+          ),
+          child: SafeArea(
+            top: false,
+            child: ButtonBar(
+              children: widget.persistentFooterButtons!,
+            ),
+          ),
+        ),
+        _ScaffoldSlot.persistentFooter,
+        removeLeftPadding: false,
+        removeTopPadding: true,
+        removeRightPadding: false,
+        removeBottomPadding: false,
+        maintainBottomViewPadding: !_resizeToAvoidBottomInset,
+      );
+    }
+
+    if (widget.bottomNavigationBar != null) {
+      _addIfNonNull(
+        children,
+        widget.bottomNavigationBar,
+        _ScaffoldSlot.bottomNavigationBar,
+        removeLeftPadding: false,
+        removeTopPadding: true,
+        removeRightPadding: false,
+        removeBottomPadding: false,
+        maintainBottomViewPadding: !_resizeToAvoidBottomInset,
+      );
+    }
+
+    if (_currentBottomSheet != null || _dismissedBottomSheets.isNotEmpty) {
+      final Widget stack = Stack(
+        alignment: Alignment.bottomCenter,
+        children: <Widget>[
+          ..._dismissedBottomSheets,
+          if (_currentBottomSheet != null) _currentBottomSheet!._widget,
+        ],
+      );
+      _addIfNonNull(
+        children,
+        stack,
+        _ScaffoldSlot.bottomSheet,
+        removeLeftPadding: false,
+        removeTopPadding: true,
+        removeRightPadding: false,
+        removeBottomPadding: _resizeToAvoidBottomInset,
+      );
+    }
+
+    _addIfNonNull(
+      children,
+      _FloatingActionButtonTransition(
+        child: widget.floatingActionButton,
+        fabMoveAnimation: _floatingActionButtonMoveController,
+        fabMotionAnimator: _floatingActionButtonAnimator,
+        geometryNotifier: _geometryNotifier,
+        currentController: _floatingActionButtonVisibilityController,
+      ),
+      _ScaffoldSlot.floatingActionButton,
+      removeLeftPadding: true,
+      removeTopPadding: true,
+      removeRightPadding: true,
+      removeBottomPadding: true,
+    );
+
+    switch (themeData.platform) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        _addIfNonNull(
+          children,
+          GestureDetector(
+            behavior: HitTestBehavior.opaque,
+            onTap: _handleStatusBarTap,
+            // iOS accessibility automatically adds scroll-to-top to the clock in the status bar
+            excludeFromSemantics: true,
+          ),
+          _ScaffoldSlot.statusBar,
+          removeLeftPadding: false,
+          removeTopPadding: true,
+          removeRightPadding: false,
+          removeBottomPadding: true,
+        );
+        break;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        break;
+    }
+
+    if (_endDrawerOpened) {
+      _buildDrawer(children, textDirection);
+      _buildEndDrawer(children, textDirection);
+    } else {
+      _buildEndDrawer(children, textDirection);
+      _buildDrawer(children, textDirection);
+    }
+
+    // The minimum insets for contents of the Scaffold to keep visible.
+    final EdgeInsets minInsets = mediaQuery.padding.copyWith(
+      bottom: _resizeToAvoidBottomInset ? mediaQuery.viewInsets.bottom : 0.0,
+    );
+
+    // The minimum viewPadding for interactive elements positioned by the
+    // Scaffold to keep within safe interactive areas.
+    final EdgeInsets minViewPadding = mediaQuery.viewPadding.copyWith(
+      bottom: _resizeToAvoidBottomInset &&  mediaQuery.viewInsets.bottom != 0.0 ? 0.0 : null,
+    );
+
+    // extendBody locked when keyboard is open
+    final bool _extendBody = minInsets.bottom <= 0 && widget.extendBody;
+
+    return _ScaffoldScope(
+      hasDrawer: hasDrawer,
+      geometryNotifier: _geometryNotifier,
+      child: Material(
+        color: widget.backgroundColor ?? themeData.scaffoldBackgroundColor,
+        child: AnimatedBuilder(animation: _floatingActionButtonMoveController, builder: (BuildContext context, Widget? child) {
+          return CustomMultiChildLayout(
+            children: children,
+            delegate: _ScaffoldLayout(
+              extendBody: _extendBody,
+              extendBodyBehindAppBar: widget.extendBodyBehindAppBar,
+              minInsets: minInsets,
+              minViewPadding: minViewPadding,
+              currentFloatingActionButtonLocation: _floatingActionButtonLocation!,
+              floatingActionButtonMoveAnimationProgress: _floatingActionButtonMoveController.value,
+              floatingActionButtonMotionAnimator: _floatingActionButtonAnimator,
+              geometryNotifier: _geometryNotifier,
+              previousFloatingActionButtonLocation: _previousFloatingActionButtonLocation!,
+              textDirection: textDirection,
+              isSnackBarFloating: isSnackBarFloating,
+              snackBarWidth: snackBarWidth,
+            ),
+          );
+        }),
+      ),
+    );
+  }
+}
+
+/// An interface for controlling a feature of a [Scaffold].
+///
+/// Commonly obtained from [ScaffoldMessengerState.showSnackBar] or
+/// [ScaffoldState.showBottomSheet].
+class ScaffoldFeatureController<T extends Widget, U> {
+  const ScaffoldFeatureController._(this._widget, this._completer, this.close, this.setState);
+  final T _widget;
+  final Completer<U> _completer;
+
+  /// Completes when the feature controlled by this object is no longer visible.
+  Future<U> get closed => _completer.future;
+
+  /// Remove the feature (e.g., bottom sheet or snack bar) from the scaffold.
+  final VoidCallback close;
+
+  /// Mark the feature (e.g., bottom sheet or snack bar) as needing to rebuild.
+  final StateSetter? setState;
+}
+
+// TODO(guidezpl): Look into making this public. A copy of this class is in
+//  bottom_sheet.dart, for now, https://github.com/flutter/flutter/issues/51627
+/// A curve that progresses linearly until a specified [startingPoint], at which
+/// point [curve] will begin. Unlike [Interval], [curve] will not start at zero,
+/// but will use [startingPoint] as the Y position.
+///
+/// For example, if [startingPoint] is set to `0.5`, and [curve] is set to
+/// [Curves.easeOut], then the bottom-left quarter of the curve will be a
+/// straight line, and the top-right quarter will contain the entire contents of
+/// [Curves.easeOut].
+///
+/// This is useful in situations where a widget must track the user's finger
+/// (which requires a linear animation), and afterwards can be flung using a
+/// curve specified with the [curve] argument, after the finger is released. In
+/// such a case, the value of [startingPoint] would be the progress of the
+/// animation at the time when the finger was released.
+///
+/// The [startingPoint] and [curve] arguments must not be null.
+class _BottomSheetSuspendedCurve extends ParametricCurve<double> {
+  /// Creates a suspended curve.
+  const _BottomSheetSuspendedCurve(
+      this.startingPoint, {
+        this.curve = Curves.easeOutCubic,
+      }) : assert(startingPoint != null),
+        assert(curve != null);
+
+  /// The progress value at which [curve] should begin.
+  ///
+  /// This defaults to [Curves.easeOutCubic].
+  final double startingPoint;
+
+  /// The curve to use when [startingPoint] is reached.
+  final Curve curve;
+
+  @override
+  double transform(double t) {
+    assert(t >= 0.0 && t <= 1.0);
+    assert(startingPoint >= 0.0 && startingPoint <= 1.0);
+
+    if (t < startingPoint) {
+      return t;
+    }
+
+    if (t == 1.0) {
+      return t;
+    }
+
+    final double curveProgress = (t - startingPoint) / (1 - startingPoint);
+    final double transformed = curve.transform(curveProgress);
+    return lerpDouble(startingPoint, 1, transformed)!;
+  }
+
+  @override
+  String toString() {
+    return '${describeIdentity(this)}($startingPoint, $curve)';
+  }
+}
+
+class _StandardBottomSheet extends StatefulWidget {
+  const _StandardBottomSheet({
+    Key? key,
+    required this.animationController,
+    this.enableDrag = true,
+    required this.onClosing,
+    required this.onDismissed,
+    required this.builder,
+    this.isPersistent = false,
+    this.backgroundColor,
+    this.elevation,
+    this.shape,
+    this.clipBehavior,
+  }) : super(key: key);
+
+  final AnimationController animationController; // we control it, but it must be disposed by whoever created it.
+  final bool enableDrag;
+  final VoidCallback? onClosing;
+  final VoidCallback? onDismissed;
+  final WidgetBuilder builder;
+  final bool isPersistent;
+  final Color? backgroundColor;
+  final double? elevation;
+  final ShapeBorder? shape;
+  final Clip? clipBehavior;
+
+  @override
+  _StandardBottomSheetState createState() => _StandardBottomSheetState();
+}
+
+class _StandardBottomSheetState extends State<_StandardBottomSheet> {
+  ParametricCurve<double> animationCurve = _standardBottomSheetCurve;
+
+  @override
+  void initState() {
+    super.initState();
+    assert(widget.animationController != null);
+    assert(widget.animationController.status == AnimationStatus.forward
+        || widget.animationController.status == AnimationStatus.completed);
+    widget.animationController.addStatusListener(_handleStatusChange);
+  }
+
+  @override
+  void didUpdateWidget(_StandardBottomSheet oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    assert(widget.animationController == oldWidget.animationController);
+  }
+
+  void close() {
+    assert(widget.animationController != null);
+    widget.animationController.reverse();
+    widget.onClosing?.call();
+  }
+
+  void _handleDragStart(DragStartDetails details) {
+    // Allow the bottom sheet to track the user's finger accurately.
+    animationCurve = Curves.linear;
+  }
+
+  void _handleDragEnd(DragEndDetails details, { bool? isClosing }) {
+    // Allow the bottom sheet to animate smoothly from its current position.
+    animationCurve = _BottomSheetSuspendedCurve(
+      widget.animationController.value,
+      curve: _standardBottomSheetCurve,
+    );
+  }
+
+  void _handleStatusChange(AnimationStatus status) {
+    if (status == AnimationStatus.dismissed) {
+      widget.onDismissed?.call();
+    }
+  }
+
+  bool extentChanged(DraggableScrollableNotification notification) {
+    final double extentRemaining = 1.0 - notification.extent;
+    final ScaffoldState scaffold = Scaffold.of(context);
+    if (extentRemaining < _kBottomSheetDominatesPercentage) {
+      scaffold._floatingActionButtonVisibilityValue = extentRemaining * _kBottomSheetDominatesPercentage * 10;
+      scaffold.showBodyScrim(true,  math.max(
+        _kMinBottomSheetScrimOpacity,
+        _kMaxBottomSheetScrimOpacity - scaffold._floatingActionButtonVisibilityValue,
+      ));
+    } else {
+      scaffold._floatingActionButtonVisibilityValue = 1.0;
+      scaffold.showBodyScrim(false, 0.0);
+    }
+    // If the Scaffold.bottomSheet != null, we're a persistent bottom sheet.
+    if (notification.extent == notification.minExtent && scaffold.widget.bottomSheet == null) {
+      close();
+    }
+    return false;
+  }
+
+  Widget _wrapBottomSheet(Widget bottomSheet) {
+    return Semantics(
+      container: true,
+      onDismiss: close,
+      child:  NotificationListener<DraggableScrollableNotification>(
+        onNotification: extentChanged,
+        child: bottomSheet,
+      ),
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return AnimatedBuilder(
+      animation: widget.animationController,
+      builder: (BuildContext context, Widget? child) {
+        return Align(
+          alignment: AlignmentDirectional.topStart,
+          heightFactor: animationCurve.transform(widget.animationController.value),
+          child: child,
+        );
+      },
+      child: _wrapBottomSheet(
+        BottomSheet(
+          animationController: widget.animationController,
+          enableDrag: widget.enableDrag,
+          onDragStart: _handleDragStart,
+          onDragEnd: _handleDragEnd,
+          onClosing: widget.onClosing!,
+          builder: widget.builder,
+          backgroundColor: widget.backgroundColor,
+          elevation: widget.elevation,
+          shape: widget.shape,
+          clipBehavior: widget.clipBehavior,
+        ),
+      ),
+    );
+  }
+
+}
+
+/// A [ScaffoldFeatureController] for standard bottom sheets.
+///
+/// This is the type of objects returned by [ScaffoldState.showBottomSheet].
+///
+/// This controller is used to display both standard and persistent bottom
+/// sheets. A bottom sheet is only persistent if it is set as the
+/// [Scaffold.bottomSheet].
+class PersistentBottomSheetController<T> extends ScaffoldFeatureController<_StandardBottomSheet, T> {
+  const PersistentBottomSheetController._(
+    _StandardBottomSheet widget,
+    Completer<T> completer,
+    VoidCallback close,
+    StateSetter setState,
+    this._isLocalHistoryEntry,
+  ) : super._(widget, completer, close, setState);
+
+  final bool _isLocalHistoryEntry;
+}
+
+class _ScaffoldScope extends InheritedWidget {
+  const _ScaffoldScope({
+    Key? key,
+    required this.hasDrawer,
+    required this.geometryNotifier,
+    required Widget child,
+  }) : assert(hasDrawer != null),
+       super(key: key, child: child);
+
+  final bool hasDrawer;
+  final _ScaffoldGeometryNotifier geometryNotifier;
+
+  @override
+  bool updateShouldNotify(_ScaffoldScope oldWidget) {
+    return hasDrawer != oldWidget.hasDrawer;
+  }
+}
diff --git a/lib/src/material/scrollbar.dart b/lib/src/material/scrollbar.dart
new file mode 100644
index 0000000..0d60b7d
--- /dev/null
+++ b/lib/src/material/scrollbar.dart
@@ -0,0 +1,238 @@
+// Copyright 2014 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.
+
+import 'package:flute/cupertino.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/widgets.dart';
+
+import 'color_scheme.dart';
+import 'material_state.dart';
+import 'theme.dart';
+
+const double _kScrollbarThickness = 8.0;
+const double _kScrollbarThicknessWithTrack = 12.0;
+const double _kScrollbarMargin = 2.0;
+const double _kScrollbarMinLength = 48.0;
+const Radius _kScrollbarRadius = Radius.circular(8.0);
+const Duration _kScrollbarFadeDuration = Duration(milliseconds: 300);
+const Duration _kScrollbarTimeToFade = Duration(milliseconds: 600);
+
+/// A material design scrollbar.
+///
+/// To add a scrollbar thumb to a [ScrollView], simply wrap the scroll view
+/// widget in a [Scrollbar] widget.
+///
+/// {@macro flutter.widgets.Scrollbar}
+///
+/// The color of the Scrollbar will change when dragged, as well as when
+/// hovered over. A scrollbar track can also been drawn when triggered by a
+/// hover event, which is controlled by [showTrackOnHover]. The thickness of the
+/// track and scrollbar thumb will become larger when hovering, unless
+/// overridden by [hoverThickness].
+///
+// TODO(Piinks): Add code sample
+///
+/// See also:
+///
+///  * [RawScrollbar], a basic scrollbar that fades in and out, extended
+///    by this class to add more animations and behaviors.
+///  * [CupertinoScrollbar], an iOS style scrollbar.
+///  * [ListView], which displays a linear, scrollable list of children.
+///  * [GridView], which displays a 2 dimensional, scrollable array of children.
+class Scrollbar extends RawScrollbar {
+  /// Creates a material design scrollbar that by default will connect to the
+  /// closest Scrollable descendant of [child].
+  ///
+  /// The [child] should be a source of [ScrollNotification] notifications,
+  /// typically a [Scrollable] widget.
+  ///
+  /// If the [controller] is null, the default behavior is to
+  /// enable scrollbar dragging using the [PrimaryScrollController].
+  ///
+  /// When null, [thickness] and [radius] defaults will result in a rounded
+  /// rectangular thumb that is 8.0 dp wide with a radius of 8.0 pixels.
+  const Scrollbar({
+    Key? key,
+    required Widget child,
+    ScrollController? controller,
+    bool isAlwaysShown = false,
+    this.showTrackOnHover = false,
+    this.hoverThickness,
+    double? thickness,
+    Radius? radius,
+  }) : super(
+         key: key,
+         child: child,
+         controller: controller,
+         isAlwaysShown: isAlwaysShown,
+         thickness: thickness ?? _kScrollbarThickness,
+         radius: radius,
+         fadeDuration: _kScrollbarFadeDuration,
+         timeToFade: _kScrollbarTimeToFade,
+         pressDuration: Duration.zero,
+       );
+
+  /// Controls if the track will show on hover and remain, including during drag.
+  ///
+  /// Defaults to false, cannot be null.
+  final bool showTrackOnHover;
+
+  /// The thickness of the scrollbar when a hover state is active and
+  /// [showTrackOnHover] is true.
+  ///
+  /// Defaults to 12.0 dp when null.
+  final double? hoverThickness;
+
+  @override
+  _ScrollbarState createState() => _ScrollbarState();
+}
+
+class _ScrollbarState extends RawScrollbarState<Scrollbar> {
+  late AnimationController _hoverAnimationController;
+  bool _dragIsActive = false;
+  bool _hoverIsActive = false;
+  late ColorScheme _colorScheme;
+
+  Set<MaterialState> get _states => <MaterialState>{
+    if (_dragIsActive) MaterialState.dragged,
+    if (_hoverIsActive) MaterialState.hovered,
+  };
+
+  MaterialStateProperty<Color> get _thumbColor {
+    final Color onSurface = _colorScheme.onSurface;
+    final Brightness brightness = _colorScheme.brightness;
+    late Color dragColor;
+    late Color hoverColor;
+    late Color idleColor;
+    switch (brightness) {
+      case Brightness.light:
+        dragColor = onSurface.withOpacity(0.6);
+        hoverColor = onSurface.withOpacity(0.5);
+        idleColor = onSurface.withOpacity(0.1);
+        break;
+      case Brightness.dark:
+        dragColor = onSurface.withOpacity(0.75);
+        hoverColor = onSurface.withOpacity(0.65);
+        idleColor = onSurface.withOpacity(0.3);
+        break;
+    }
+
+    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.dragged))
+        return dragColor;
+
+      // If the track is visible, the thumb color hover animation is ignored and
+      // changes immediately.
+      if (states.contains(MaterialState.hovered) && widget.showTrackOnHover)
+        return hoverColor;
+
+      return Color.lerp(
+        idleColor,
+        hoverColor,
+        _hoverAnimationController.value,
+      )!;
+    });
+  }
+
+  MaterialStateProperty<Color> get _trackColor {
+    final Color onSurface = _colorScheme.onSurface;
+    final Brightness brightness = _colorScheme.brightness;
+    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.hovered) && widget.showTrackOnHover) {
+        return brightness == Brightness.light
+          ? onSurface.withOpacity(0.03)
+          : onSurface.withOpacity(0.05);
+      }
+      return const Color(0x00000000);
+    });
+  }
+
+  MaterialStateProperty<Color> get _trackBorderColor {
+    final Color onSurface = _colorScheme.onSurface;
+    final Brightness brightness = _colorScheme.brightness;
+    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.hovered) && widget.showTrackOnHover) {
+        return brightness == Brightness.light
+          ? onSurface.withOpacity(0.1)
+          : onSurface.withOpacity(0.25);
+      }
+      return const Color(0x00000000);
+    });
+  }
+
+  MaterialStateProperty<double> get _thickness {
+    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.hovered) && widget.showTrackOnHover)
+        return widget.hoverThickness ?? _kScrollbarThicknessWithTrack;
+      return widget.thickness ?? _kScrollbarThickness;
+    });
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _hoverAnimationController = AnimationController(
+      vsync: this,
+      duration: const Duration(milliseconds: 200),
+    );
+    _hoverAnimationController.addListener(() {
+      updateScrollbarPainter();
+    });
+  }
+
+  @override
+  void updateScrollbarPainter() {
+    _colorScheme = Theme.of(context).colorScheme;
+    scrollbarPainter
+      ..color = _thumbColor.resolve(_states)
+      ..trackColor = _trackColor.resolve(_states)
+      ..trackBorderColor = _trackBorderColor.resolve(_states)
+      ..textDirection = Directionality.of(context)
+      ..thickness = _thickness.resolve(_states)
+      ..radius = widget.radius ?? _kScrollbarRadius
+      ..crossAxisMargin = _kScrollbarMargin
+      ..minLength = _kScrollbarMinLength
+      ..padding = MediaQuery.of(context).padding;
+  }
+
+  @override
+  void handleThumbPressStart(Offset localPosition) {
+    super.handleThumbPressStart(localPosition);
+    setState(() { _dragIsActive = true; });
+  }
+
+  @override
+  void handleThumbPressEnd(Offset localPosition, Velocity velocity) {
+    super.handleThumbPressEnd(localPosition, velocity);
+    setState(() { _dragIsActive = false; });
+  }
+
+  @override
+  void handleHover(PointerHoverEvent event) {
+    super.handleHover(event);
+    // Check if the position of the pointer falls over the painted scrollbar
+    if (isPointerOverScrollbar(event.position)) {
+      // Pointer is hovering over the scrollbar
+      setState(() { _hoverIsActive = true; });
+      _hoverAnimationController.forward();
+    } else if (_hoverIsActive) {
+      // Pointer was, but is no longer over painted scrollbar.
+      setState(() { _hoverIsActive = false; });
+      _hoverAnimationController.reverse();
+    }
+  }
+
+  @override
+  void handleHoverExit(PointerExitEvent event) {
+    super.handleHoverExit(event);
+    setState(() { _hoverIsActive = false; });
+    _hoverAnimationController.reverse();
+  }
+
+  @override
+  void dispose() {
+    _hoverAnimationController.dispose();
+    super.dispose();
+  }
+}
diff --git a/lib/src/material/search.dart b/lib/src/material/search.dart
new file mode 100644
index 0000000..0f77e84
--- /dev/null
+++ b/lib/src/material/search.dart
@@ -0,0 +1,590 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'app_bar.dart';
+import 'app_bar_theme.dart';
+import 'color_scheme.dart';
+import 'colors.dart';
+import 'debug.dart';
+import 'input_border.dart';
+import 'input_decorator.dart';
+import 'material_localizations.dart';
+import 'scaffold.dart';
+import 'text_field.dart';
+import 'theme.dart';
+
+/// Shows a full screen search page and returns the search result selected by
+/// the user when the page is closed.
+///
+/// The search page consists of an app bar with a search field and a body which
+/// can either show suggested search queries or the search results.
+///
+/// The appearance of the search page is determined by the provided
+/// `delegate`. The initial query string is given by `query`, which defaults
+/// to the empty string. When `query` is set to null, `delegate.query` will
+/// be used as the initial query.
+///
+/// This method returns the selected search result, which can be set in the
+/// [SearchDelegate.close] call. If the search page is closed with the system
+/// back button, it returns null.
+///
+/// A given [SearchDelegate] can only be associated with one active [showSearch]
+/// call. Call [SearchDelegate.close] before re-using the same delegate instance
+/// for another [showSearch] call.
+///
+/// The transition to the search page triggered by this method looks best if the
+/// screen triggering the transition contains an [AppBar] at the top and the
+/// transition is called from an [IconButton] that's part of [AppBar.actions].
+/// The animation provided by [SearchDelegate.transitionAnimation] can be used
+/// to trigger additional animations in the underlying page while the search
+/// page fades in or out. This is commonly used to animate an [AnimatedIcon] in
+/// the [AppBar.leading] position e.g. from the hamburger menu to the back arrow
+/// used to exit the search page.
+///
+/// ## Handling emojis and other complex characters
+/// {@macro flutter.widgets.EditableText.onChanged}
+///
+/// See also:
+///
+///  * [SearchDelegate] to define the content of the search page.
+Future<T?> showSearch<T>({
+  required BuildContext context,
+  required SearchDelegate<T> delegate,
+  String? query = '',
+}) {
+  assert(delegate != null);
+  assert(context != null);
+  delegate.query = query ?? delegate.query;
+  delegate._currentBody = _SearchBody.suggestions;
+  return Navigator.of(context).push(_SearchPageRoute<T>(
+    delegate: delegate,
+  ));
+}
+
+/// Delegate for [showSearch] to define the content of the search page.
+///
+/// The search page always shows an [AppBar] at the top where users can
+/// enter their search queries. The buttons shown before and after the search
+/// query text field can be customized via [SearchDelegate.buildLeading]
+/// and [SearchDelegate.buildActions]. Additonally, a widget can be placed
+/// across the bottom of the [AppBar] via [SearchDelegate.buildBottom].
+///
+/// The body below the [AppBar] can either show suggested queries (returned by
+/// [SearchDelegate.buildSuggestions]) or - once the user submits a search  - the
+/// results of the search as returned by [SearchDelegate.buildResults].
+///
+/// [SearchDelegate.query] always contains the current query entered by the user
+/// and should be used to build the suggestions and results.
+///
+/// The results can be brought on screen by calling [SearchDelegate.showResults]
+/// and you can go back to showing the suggestions by calling
+/// [SearchDelegate.showSuggestions].
+///
+/// Once the user has selected a search result, [SearchDelegate.close] should be
+/// called to remove the search page from the top of the navigation stack and
+/// to notify the caller of [showSearch] about the selected search result.
+///
+/// A given [SearchDelegate] can only be associated with one active [showSearch]
+/// call. Call [SearchDelegate.close] before re-using the same delegate instance
+/// for another [showSearch] call.
+///
+/// ## Handling emojis and other complex characters
+/// {@macro flutter.widgets.EditableText.onChanged}
+abstract class SearchDelegate<T> {
+  /// Constructor to be called by subclasses which may specify
+  /// [searchFieldLabel], either [searchFieldStyle] or [searchFieldDecorationTheme],
+  /// [keyboardType] and/or [textInputAction]. Only one of [searchFieldLabel]
+  /// and [searchFieldDecorationTheme] may be non-null.
+  ///
+  /// {@tool snippet}
+  /// ```dart
+  /// class CustomSearchHintDelegate extends SearchDelegate {
+  ///   CustomSearchHintDelegate({
+  ///     required String hintText,
+  ///   }) : super(
+  ///     searchFieldLabel: hintText,
+  ///     keyboardType: TextInputType.text,
+  ///     textInputAction: TextInputAction.search,
+  ///   );
+  ///
+  ///   @override
+  ///   Widget buildLeading(BuildContext context) => Text("leading");
+  ///
+  ///   PreferredSizeWidget buildBottom(BuildContext context) {
+  ///     return PreferredSize(
+  ///        preferredSize: Size.fromHeight(56.0),
+  ///        child: Text("bottom"));
+  ///   }
+  ///
+  ///   @override
+  ///   Widget buildSuggestions(BuildContext context) => Text("suggestions");
+  ///
+  ///   @override
+  ///   Widget buildResults(BuildContext context) => Text('results');
+  ///
+  ///   @override
+  ///   List<Widget> buildActions(BuildContext context) => [];
+  /// }
+  /// ```
+  /// {@end-tool}
+  SearchDelegate({
+    this.searchFieldLabel,
+    this.searchFieldStyle,
+    this.searchFieldDecorationTheme,
+    this.keyboardType,
+    this.textInputAction = TextInputAction.search,
+  }) : assert(searchFieldStyle == null || searchFieldDecorationTheme == null);
+
+  /// Suggestions shown in the body of the search page while the user types a
+  /// query into the search field.
+  ///
+  /// The delegate method is called whenever the content of [query] changes.
+  /// The suggestions should be based on the current [query] string. If the query
+  /// string is empty, it is good practice to show suggested queries based on
+  /// past queries or the current context.
+  ///
+  /// Usually, this method will return a [ListView] with one [ListTile] per
+  /// suggestion. When [ListTile.onTap] is called, [query] should be updated
+  /// with the corresponding suggestion and the results page should be shown
+  /// by calling [showResults].
+  Widget buildSuggestions(BuildContext context);
+
+  /// The results shown after the user submits a search from the search page.
+  ///
+  /// The current value of [query] can be used to determine what the user
+  /// searched for.
+  ///
+  /// This method might be applied more than once to the same query.
+  /// If your [buildResults] method is computationally expensive, you may want
+  /// to cache the search results for one or more queries.
+  ///
+  /// Typically, this method returns a [ListView] with the search results.
+  /// When the user taps on a particular search result, [close] should be called
+  /// with the selected result as argument. This will close the search page and
+  /// communicate the result back to the initial caller of [showSearch].
+  Widget buildResults(BuildContext context);
+
+  /// A widget to display before the current query in the [AppBar].
+  ///
+  /// Typically an [IconButton] configured with a [BackButtonIcon] that exits
+  /// the search with [close]. One can also use an [AnimatedIcon] driven by
+  /// [transitionAnimation], which animates from e.g. a hamburger menu to the
+  /// back button as the search overlay fades in.
+  ///
+  /// Returns null if no widget should be shown.
+  ///
+  /// See also:
+  ///
+  ///  * [AppBar.leading], the intended use for the return value of this method.
+  Widget buildLeading(BuildContext context);
+
+  /// Widgets to display after the search query in the [AppBar].
+  ///
+  /// If the [query] is not empty, this should typically contain a button to
+  /// clear the query and show the suggestions again (via [showSuggestions]) if
+  /// the results are currently shown.
+  ///
+  /// Returns null if no widget should be shown.
+  ///
+  /// See also:
+  ///
+  ///  * [AppBar.actions], the intended use for the return value of this method.
+  List<Widget> buildActions(BuildContext context);
+
+  /// Widget to display across the bottom of the [AppBar].
+  ///
+  /// Returns null by default, i.e. a bottom widget is not included.
+  ///
+  /// See also:
+  ///
+  ///  * [AppBar.bottom], the intended use for the return value of this method.
+  ///
+  PreferredSizeWidget? buildBottom(BuildContext context) => null;
+
+  /// The theme used to configure the search page.
+  ///
+  /// The returned [ThemeData] will be used to wrap the entire search page,
+  /// so it can be used to configure any of its components with the appropriate
+  /// theme properties.
+  ///
+  /// Unless overridden, the default theme will configure the AppBar containing
+  /// the search input text field with a white background and black text on light
+  /// themes. For dark themes the default is a dark grey background with light
+  /// color text.
+  ///
+  /// See also:
+  ///
+  ///  * [AppBarTheme], which configures the AppBar's appearance.
+  ///  * [InputDecorationTheme], which configures the appearance of the search
+  ///    text field.
+  ThemeData appBarTheme(BuildContext context) {
+    assert(context != null);
+    final ThemeData theme = Theme.of(context);
+    final ColorScheme colorScheme = theme.colorScheme;
+    assert(theme != null);
+    return theme.copyWith(
+      appBarTheme: AppBarTheme(
+        brightness: colorScheme.brightness,
+        color: colorScheme.brightness == Brightness.dark ? Colors.grey[900] : Colors.white,
+        iconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
+        textTheme: theme.textTheme,
+      ),
+      inputDecorationTheme: searchFieldDecorationTheme ??
+          InputDecorationTheme(
+            hintStyle: searchFieldStyle ?? theme.inputDecorationTheme.hintStyle,
+            border: InputBorder.none,
+          ),
+    );
+  }
+
+  /// The current query string shown in the [AppBar].
+  ///
+  /// The user manipulates this string via the keyboard.
+  ///
+  /// If the user taps on a suggestion provided by [buildSuggestions] this
+  /// string should be updated to that suggestion via the setter.
+  String get query => _queryTextController.text;
+  set query(String value) {
+    assert(query != null);
+    _queryTextController.text = value;
+  }
+
+  /// Transition from the suggestions returned by [buildSuggestions] to the
+  /// [query] results returned by [buildResults].
+  ///
+  /// If the user taps on a suggestion provided by [buildSuggestions] the
+  /// screen should typically transition to the page showing the search
+  /// results for the suggested query. This transition can be triggered
+  /// by calling this method.
+  ///
+  /// See also:
+  ///
+  ///  * [showSuggestions] to show the search suggestions again.
+  void showResults(BuildContext context) {
+    _focusNode?.unfocus();
+    _currentBody = _SearchBody.results;
+  }
+
+  /// Transition from showing the results returned by [buildResults] to showing
+  /// the suggestions returned by [buildSuggestions].
+  ///
+  /// Calling this method will also put the input focus back into the search
+  /// field of the [AppBar].
+  ///
+  /// If the results are currently shown this method can be used to go back
+  /// to showing the search suggestions.
+  ///
+  /// See also:
+  ///
+  ///  * [showResults] to show the search results.
+  void showSuggestions(BuildContext context) {
+    assert(_focusNode != null, '_focusNode must be set by route before showSuggestions is called.');
+    _focusNode!.requestFocus();
+    _currentBody = _SearchBody.suggestions;
+  }
+
+  /// Closes the search page and returns to the underlying route.
+  ///
+  /// The value provided for `result` is used as the return value of the call
+  /// to [showSearch] that launched the search initially.
+  void close(BuildContext context, T result) {
+    _currentBody = null;
+    _focusNode?.unfocus();
+    Navigator.of(context)
+      ..popUntil((Route<dynamic> route) => route == _route)
+      ..pop(result);
+  }
+
+  /// The hint text that is shown in the search field when it is empty.
+  ///
+  /// If this value is set to null, the value of
+  /// `MaterialLocalizations.of(context).searchFieldLabel` will be used instead.
+  final String? searchFieldLabel;
+
+  /// The style of the [searchFieldLabel].
+  ///
+  /// If this value is set to null, the value of the ambient [Theme]'s
+  /// [InputDecorationTheme.hintStyle] will be used instead.
+  ///
+  /// Only one of [searchFieldStyle] or [searchFieldDecorationTheme] can
+  /// be non-null.
+  final TextStyle? searchFieldStyle;
+
+  /// The [InputDecorationTheme] used to configure the search field's visuals.
+  ///
+  /// Only one of [searchFieldStyle] or [searchFieldDecorationTheme] can
+  /// be non-null.
+  final InputDecorationTheme? searchFieldDecorationTheme;
+
+  /// The type of action button to use for the keyboard.
+  ///
+  /// Defaults to the default value specified in [TextField].
+  final TextInputType? keyboardType;
+
+  /// The text input action configuring the soft keyboard to a particular action
+  /// button.
+  ///
+  /// Defaults to [TextInputAction.search].
+  final TextInputAction textInputAction;
+
+  /// [Animation] triggered when the search pages fades in or out.
+  ///
+  /// This animation is commonly used to animate [AnimatedIcon]s of
+  /// [IconButton]s returned by [buildLeading] or [buildActions]. It can also be
+  /// used to animate [IconButton]s contained within the route below the search
+  /// page.
+  Animation<double> get transitionAnimation => _proxyAnimation;
+
+  // The focus node to use for manipulating focus on the search page. This is
+  // managed, owned, and set by the _SearchPageRoute using this delegate.
+  FocusNode? _focusNode;
+
+  final TextEditingController _queryTextController = TextEditingController();
+
+  final ProxyAnimation _proxyAnimation = ProxyAnimation(kAlwaysDismissedAnimation);
+
+  final ValueNotifier<_SearchBody?> _currentBodyNotifier = ValueNotifier<_SearchBody?>(null);
+
+  _SearchBody? get _currentBody => _currentBodyNotifier.value;
+  set _currentBody(_SearchBody? value) {
+    _currentBodyNotifier.value = value;
+  }
+
+  _SearchPageRoute<T>? _route;
+}
+
+/// Describes the body that is currently shown under the [AppBar] in the
+/// search page.
+enum _SearchBody {
+  /// Suggested queries are shown in the body.
+  ///
+  /// The suggested queries are generated by [SearchDelegate.buildSuggestions].
+  suggestions,
+
+  /// Search results are currently shown in the body.
+  ///
+  /// The search results are generated by [SearchDelegate.buildResults].
+  results,
+}
+
+class _SearchPageRoute<T> extends PageRoute<T> {
+  _SearchPageRoute({
+    required this.delegate,
+  }) : assert(delegate != null) {
+    assert(
+      delegate._route == null,
+      'The ${delegate.runtimeType} instance is currently used by another active '
+      'search. Please close that search by calling close() on the SearchDelegate '
+      'before opening another search with the same delegate instance.',
+    );
+    delegate._route = this;
+  }
+
+  final SearchDelegate<T> delegate;
+
+  @override
+  Color? get barrierColor => null;
+
+  @override
+  String? get barrierLabel => null;
+
+  @override
+  Duration get transitionDuration => const Duration(milliseconds: 300);
+
+  @override
+  bool get maintainState => false;
+
+  @override
+  Widget buildTransitions(
+    BuildContext context,
+    Animation<double> animation,
+    Animation<double> secondaryAnimation,
+    Widget child,
+  ) {
+    return FadeTransition(
+      opacity: animation,
+      child: child,
+    );
+  }
+
+  @override
+  Animation<double> createAnimation() {
+    final Animation<double> animation = super.createAnimation();
+    delegate._proxyAnimation.parent = animation;
+    return animation;
+  }
+
+  @override
+  Widget buildPage(
+    BuildContext context,
+    Animation<double> animation,
+    Animation<double> secondaryAnimation,
+  ) {
+    return _SearchPage<T>(
+      delegate: delegate,
+      animation: animation,
+    );
+  }
+
+  @override
+  void didComplete(T? result) {
+    super.didComplete(result);
+    assert(delegate._route == this);
+    delegate._route = null;
+    delegate._currentBody = null;
+  }
+}
+
+class _SearchPage<T> extends StatefulWidget {
+  const _SearchPage({
+    required this.delegate,
+    required this.animation,
+  });
+
+  final SearchDelegate<T> delegate;
+  final Animation<double> animation;
+
+  @override
+  State<StatefulWidget> createState() => _SearchPageState<T>();
+}
+
+class _SearchPageState<T> extends State<_SearchPage<T>> {
+  // This node is owned, but not hosted by, the search page. Hosting is done by
+  // the text field.
+  FocusNode focusNode = FocusNode();
+
+  @override
+  void initState() {
+    super.initState();
+    widget.delegate._queryTextController.addListener(_onQueryChanged);
+    widget.animation.addStatusListener(_onAnimationStatusChanged);
+    widget.delegate._currentBodyNotifier.addListener(_onSearchBodyChanged);
+    focusNode.addListener(_onFocusChanged);
+    widget.delegate._focusNode = focusNode;
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    widget.delegate._queryTextController.removeListener(_onQueryChanged);
+    widget.animation.removeStatusListener(_onAnimationStatusChanged);
+    widget.delegate._currentBodyNotifier.removeListener(_onSearchBodyChanged);
+    widget.delegate._focusNode = null;
+    focusNode.dispose();
+  }
+
+  void _onAnimationStatusChanged(AnimationStatus status) {
+    if (status != AnimationStatus.completed) {
+      return;
+    }
+    widget.animation.removeStatusListener(_onAnimationStatusChanged);
+    if (widget.delegate._currentBody == _SearchBody.suggestions) {
+      focusNode.requestFocus();
+    }
+  }
+
+  @override
+  void didUpdateWidget(_SearchPage<T> oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.delegate != oldWidget.delegate) {
+      oldWidget.delegate._queryTextController.removeListener(_onQueryChanged);
+      widget.delegate._queryTextController.addListener(_onQueryChanged);
+      oldWidget.delegate._currentBodyNotifier.removeListener(_onSearchBodyChanged);
+      widget.delegate._currentBodyNotifier.addListener(_onSearchBodyChanged);
+      oldWidget.delegate._focusNode = null;
+      widget.delegate._focusNode = focusNode;
+    }
+  }
+
+  void _onFocusChanged() {
+    if (focusNode.hasFocus && widget.delegate._currentBody != _SearchBody.suggestions) {
+      widget.delegate.showSuggestions(context);
+    }
+  }
+
+  void _onQueryChanged() {
+    setState(() {
+      // rebuild ourselves because query changed.
+    });
+  }
+
+  void _onSearchBodyChanged() {
+    setState(() {
+      // rebuild ourselves because search body changed.
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterialLocalizations(context));
+    final ThemeData theme = widget.delegate.appBarTheme(context);
+    final String searchFieldLabel = widget.delegate.searchFieldLabel
+      ?? MaterialLocalizations.of(context).searchFieldLabel;
+    Widget? body;
+    switch(widget.delegate._currentBody) {
+      case _SearchBody.suggestions:
+        body = KeyedSubtree(
+          key: const ValueKey<_SearchBody>(_SearchBody.suggestions),
+          child: widget.delegate.buildSuggestions(context),
+        );
+        break;
+      case _SearchBody.results:
+        body = KeyedSubtree(
+          key: const ValueKey<_SearchBody>(_SearchBody.results),
+          child: widget.delegate.buildResults(context),
+        );
+        break;
+      case null:
+        break;
+    }
+
+    late final String routeName;
+    switch (theme.platform) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        routeName = '';
+        break;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        routeName = searchFieldLabel;
+    }
+
+    return Semantics(
+      explicitChildNodes: true,
+      scopesRoute: true,
+      namesRoute: true,
+      label: routeName,
+      child: Theme(
+        data: theme,
+        child: Scaffold(
+          appBar: AppBar(
+            leading: widget.delegate.buildLeading(context),
+            title: TextField(
+              controller: widget.delegate._queryTextController,
+              focusNode: focusNode,
+              style: theme.textTheme.headline6,
+              textInputAction: widget.delegate.textInputAction,
+              keyboardType: widget.delegate.keyboardType,
+              onSubmitted: (String _) {
+                widget.delegate.showResults(context);
+              },
+              decoration: InputDecoration(hintText: searchFieldLabel),
+            ),
+            actions: widget.delegate.buildActions(context),
+            bottom: widget.delegate.buildBottom(context),
+          ),
+          body: AnimatedSwitcher(
+            duration: const Duration(milliseconds: 300),
+            child: body,
+          ),
+        )
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/selectable_text.dart b/lib/src/material/selectable_text.dart
new file mode 100644
index 0000000..b88f95e
--- /dev/null
+++ b/lib/src/material/selectable_text.dart
@@ -0,0 +1,669 @@
+// Copyright 2014 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.
+
+import 'package:flute/cupertino.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+
+import 'feedback.dart';
+import 'text_selection.dart';
+import 'text_selection_theme.dart';
+import 'theme.dart';
+
+/// An eyeballed value that moves the cursor slightly left of where it is
+/// rendered for text on Android so its positioning more accurately matches the
+/// native iOS text cursor positioning.
+///
+/// This value is in device pixels, not logical pixels as is typically used
+/// throughout the codebase.
+const int iOSHorizontalOffset = -2;
+
+class _TextSpanEditingController extends TextEditingController {
+  _TextSpanEditingController({required TextSpan textSpan}):
+    assert(textSpan != null),
+    _textSpan = textSpan,
+    super(text: textSpan.toPlainText());
+
+  final TextSpan _textSpan;
+
+  @override
+  TextSpan buildTextSpan({TextStyle? style ,bool? withComposing}) {
+    // This does not care about composing.
+    return TextSpan(
+      style: style,
+      children: <TextSpan>[_textSpan],
+    );
+  }
+
+  @override
+  set text(String? newText) {
+    // This should never be reached.
+    throw UnimplementedError();
+  }
+}
+
+class _SelectableTextSelectionGestureDetectorBuilder extends TextSelectionGestureDetectorBuilder {
+  _SelectableTextSelectionGestureDetectorBuilder({
+    required _SelectableTextState state,
+  }) : _state = state,
+       super(delegate: state);
+
+  final _SelectableTextState _state;
+
+  @override
+  void onForcePressStart(ForcePressDetails details) {
+    super.onForcePressStart(details);
+    if (delegate.selectionEnabled && shouldShowSelectionToolbar) {
+      editableText.showToolbar();
+    }
+  }
+
+  @override
+  void onForcePressEnd(ForcePressDetails details) {
+    // Not required.
+  }
+
+  @override
+  void onSingleLongTapMoveUpdate(LongPressMoveUpdateDetails details) {
+    if (delegate.selectionEnabled) {
+      renderEditable.selectWordsInRange(
+        from: details.globalPosition - details.offsetFromOrigin,
+        to: details.globalPosition,
+        cause: SelectionChangedCause.longPress,
+      );
+    }
+  }
+
+  @override
+  void onSingleTapUp(TapUpDetails details) {
+    editableText.hideToolbar();
+    if (delegate.selectionEnabled) {
+      switch (Theme.of(_state.context).platform) {
+        case TargetPlatform.iOS:
+        case TargetPlatform.macOS:
+          renderEditable.selectWordEdge(cause: SelectionChangedCause.tap);
+          break;
+        case TargetPlatform.android:
+        case TargetPlatform.fuchsia:
+        case TargetPlatform.linux:
+        case TargetPlatform.windows:
+          renderEditable.selectPosition(cause: SelectionChangedCause.tap);
+          break;
+      }
+    }
+    if (_state.widget.onTap != null)
+      _state.widget.onTap!();
+  }
+
+  @override
+  void onSingleLongTapStart(LongPressStartDetails details) {
+    if (delegate.selectionEnabled) {
+      renderEditable.selectWord(cause: SelectionChangedCause.longPress);
+      Feedback.forLongPress(_state.context);
+    }
+  }
+}
+
+/// A run of selectable text with a single style.
+///
+/// The [SelectableText] widget displays a string of text with a single style.
+/// The string might break across multiple lines or might all be displayed on
+/// the same line depending on the layout constraints.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=ZSU3ZXOs6hc}
+///
+/// The [style] argument is optional. When omitted, the text will use the style
+/// from the closest enclosing [DefaultTextStyle]. If the given style's
+/// [TextStyle.inherit] property is true (the default), the given style will
+/// be merged with the closest enclosing [DefaultTextStyle]. This merging
+/// behavior is useful, for example, to make the text bold while using the
+/// default font family and size.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// SelectableText(
+///   'Hello! How are you?',
+///   textAlign: TextAlign.center,
+///   style: TextStyle(fontWeight: FontWeight.bold),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// Using the [SelectableText.rich] constructor, the [SelectableText] widget can
+/// display a paragraph with differently styled [TextSpan]s. The sample
+/// that follows displays "Hello beautiful world" with different styles
+/// for each word.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// const SelectableText.rich(
+///   TextSpan(
+///     text: 'Hello', // default text style
+///     children: <TextSpan>[
+///       TextSpan(text: ' beautiful ', style: TextStyle(fontStyle: FontStyle.italic)),
+///       TextSpan(text: 'world', style: TextStyle(fontWeight: FontWeight.bold)),
+///     ],
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ## Interactivity
+///
+/// To make [SelectableText] react to touch events, use callback [onTap] to achieve
+/// the desired behavior.
+///
+/// See also:
+///
+///  * [Text], which is the non selectable version of this widget.
+///  * [TextField], which is the editable version of this widget.
+class SelectableText extends StatefulWidget {
+  /// Creates a selectable text widget.
+  ///
+  /// If the [style] argument is null, the text will use the style from the
+  /// closest enclosing [DefaultTextStyle].
+  ///
+
+  /// The [showCursor], [autofocus], [dragStartBehavior], and [data] parameters
+  /// must not be null. If specified, the [maxLines] argument must be greater
+  /// than zero.
+  const SelectableText(
+    String this.data, {
+    Key? key,
+    this.focusNode,
+    this.style,
+    this.strutStyle,
+    this.textAlign,
+    this.textDirection,
+    this.textScaleFactor,
+    this.showCursor = false,
+    this.autofocus = false,
+    ToolbarOptions? toolbarOptions,
+    this.minLines,
+    this.maxLines,
+    this.cursorWidth = 2.0,
+    this.cursorHeight,
+    this.cursorRadius,
+    this.cursorColor,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.enableInteractiveSelection = true,
+    this.selectionControls,
+    this.onTap,
+    this.scrollPhysics,
+    this.textHeightBehavior,
+    this.textWidthBasis,
+    this.onSelectionChanged,
+  }) :  assert(showCursor != null),
+        assert(autofocus != null),
+        assert(dragStartBehavior != null),
+        assert(maxLines == null || maxLines > 0),
+        assert(minLines == null || minLines > 0),
+        assert(
+          (maxLines == null) || (minLines == null) || (maxLines >= minLines),
+          'minLines can\'t be greater than maxLines',
+        ),
+        assert(
+          data != null,
+          'A non-null String must be provided to a SelectableText widget.',
+        ),
+        textSpan = null,
+        toolbarOptions = toolbarOptions ??
+          const ToolbarOptions(
+            selectAll: true,
+            copy: true,
+          ),
+        super(key: key);
+
+  /// Creates a selectable text widget with a [TextSpan].
+  ///
+  /// The [textSpan] parameter must not be null and only contain [TextSpan] in
+  /// [textSpan.children]. Other type of [InlineSpan] is not allowed.
+  ///
+  /// The [autofocus] and [dragStartBehavior] arguments must not be null.
+  const SelectableText.rich(
+    TextSpan this.textSpan, {
+    Key? key,
+    this.focusNode,
+    this.style,
+    this.strutStyle,
+    this.textAlign,
+    this.textDirection,
+    this.textScaleFactor,
+    this.showCursor = false,
+    this.autofocus = false,
+    ToolbarOptions? toolbarOptions,
+    this.minLines,
+    this.maxLines,
+    this.cursorWidth = 2.0,
+    this.cursorHeight,
+    this.cursorRadius,
+    this.cursorColor,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.enableInteractiveSelection = true,
+    this.selectionControls,
+    this.onTap,
+    this.scrollPhysics,
+    this.textHeightBehavior,
+    this.textWidthBasis,
+    this.onSelectionChanged,
+  }) :  assert(showCursor != null),
+    assert(autofocus != null),
+    assert(dragStartBehavior != null),
+    assert(maxLines == null || maxLines > 0),
+    assert(minLines == null || minLines > 0),
+    assert(
+      (maxLines == null) || (minLines == null) || (maxLines >= minLines),
+      'minLines can\'t be greater than maxLines',
+    ),
+    assert(
+      textSpan != null,
+      'A non-null TextSpan must be provided to a SelectableText.rich widget.',
+    ),
+    data = null,
+    toolbarOptions = toolbarOptions ??
+      const ToolbarOptions(
+        selectAll: true,
+        copy: true,
+      ),
+    super(key: key);
+
+  /// The text to display.
+  ///
+  /// This will be null if a [textSpan] is provided instead.
+  final String? data;
+
+  /// The text to display as a [TextSpan].
+  ///
+  /// This will be null if [data] is provided instead.
+  final TextSpan? textSpan;
+
+  /// Defines the focus for this widget.
+  ///
+  /// Text is only selectable when widget is focused.
+  ///
+  /// The [focusNode] is a long-lived object that's typically managed by a
+  /// [StatefulWidget] parent. See [FocusNode] for more information.
+  ///
+  /// To give the focus to this widget, provide a [focusNode] and then
+  /// use the current [FocusScope] to request the focus:
+  ///
+  /// ```dart
+  /// FocusScope.of(context).requestFocus(myFocusNode);
+  /// ```
+  ///
+  /// This happens automatically when the widget is tapped.
+  ///
+  /// To be notified when the widget gains or loses the focus, add a listener
+  /// to the [focusNode]:
+  ///
+  /// ```dart
+  /// focusNode.addListener(() { print(myFocusNode.hasFocus); });
+  /// ```
+  ///
+  /// If null, this widget will create its own [FocusNode].
+  final FocusNode? focusNode;
+
+  /// The style to use for the text.
+  ///
+  /// If null, defaults [DefaultTextStyle] of context.
+  final TextStyle? style;
+
+  /// {@macro flutter.widgets.editableText.strutStyle}
+  final StrutStyle? strutStyle;
+
+  /// {@macro flutter.widgets.editableText.textAlign}
+  final TextAlign? textAlign;
+
+  /// {@macro flutter.widgets.editableText.textDirection}
+  final TextDirection? textDirection;
+
+  /// {@macro flutter.widgets.editableText.textScaleFactor}
+  final double? textScaleFactor;
+
+  /// {@macro flutter.widgets.editableText.autofocus}
+  final bool autofocus;
+
+  /// {@macro flutter.widgets.editableText.minLines}
+  final int? minLines;
+
+  /// {@macro flutter.widgets.editableText.maxLines}
+  final int? maxLines;
+
+  /// {@macro flutter.widgets.editableText.showCursor}
+  final bool showCursor;
+
+  /// {@macro flutter.widgets.editableText.cursorWidth}
+  final double cursorWidth;
+
+  /// {@macro flutter.widgets.editableText.cursorHeight}
+  final double? cursorHeight;
+
+  /// {@macro flutter.widgets.editableText.cursorRadius}
+  final Radius? cursorRadius;
+
+  /// The color to use when painting the cursor.
+  ///
+  /// Defaults to the theme's `cursorColor` when null.
+  final Color? cursorColor;
+
+  /// {@macro flutter.widgets.editableText.enableInteractiveSelection}
+  final bool enableInteractiveSelection;
+
+  /// {@macro flutter.widgets.editableText.selectionControls}
+  final TextSelectionControls? selectionControls;
+
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  /// Configuration of toolbar options.
+  ///
+  /// Paste and cut will be disabled regardless.
+  ///
+  /// If not set, select all and copy will be enabled by default.
+  final ToolbarOptions toolbarOptions;
+
+  /// {@macro flutter.widgets.editableText.selectionEnabled}
+  bool get selectionEnabled => enableInteractiveSelection;
+
+  /// Called when the user taps on this selectable text.
+  ///
+  /// The selectable text builds a [GestureDetector] to handle input events like tap,
+  /// to trigger focus requests, to move the caret, adjust the selection, etc.
+  /// Handling some of those events by wrapping the selectable text with a competing
+  /// GestureDetector is problematic.
+  ///
+  /// To unconditionally handle taps, without interfering with the selectable text's
+  /// internal gesture detector, provide this callback.
+  ///
+  /// To be notified when the text field gains or loses the focus, provide a
+  /// [focusNode] and add a listener to that.
+  ///
+  /// To listen to arbitrary pointer events without competing with the
+  /// selectable text's internal gesture detector, use a [Listener].
+  final GestureTapCallback? onTap;
+
+  /// {@macro flutter.widgets.editableText.scrollPhysics}
+  final ScrollPhysics? scrollPhysics;
+
+  /// {@macro flutter.dart:ui.textHeightBehavior}
+  final TextHeightBehavior? textHeightBehavior;
+
+  /// {@macro flutter.painting.textPainter.textWidthBasis}
+  final TextWidthBasis? textWidthBasis;
+
+  /// {@macro flutter.widgets.editableText.onSelectionChanged}
+  final SelectionChangedCallback? onSelectionChanged;
+
+  @override
+  _SelectableTextState createState() => _SelectableTextState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<String>('data', data, defaultValue: null));
+    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('style', style, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false));
+    properties.add(DiagnosticsProperty<bool>('showCursor', showCursor, defaultValue: false));
+    properties.add(IntProperty('minLines', minLines, defaultValue: null));
+    properties.add(IntProperty('maxLines', maxLines, defaultValue: null));
+    properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    properties.add(DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null));
+    properties.add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0));
+    properties.add(DoubleProperty('cursorHeight', cursorHeight, defaultValue: null));
+    properties.add(DiagnosticsProperty<Radius>('cursorRadius', cursorRadius, defaultValue: null));
+    properties.add(DiagnosticsProperty<Color>('cursorColor', cursorColor, defaultValue: null));
+    properties.add(FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'));
+    properties.add(DiagnosticsProperty<TextSelectionControls>('selectionControls', selectionControls, defaultValue: null));
+    properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextHeightBehavior>('textHeightBehavior', textHeightBehavior, defaultValue: null));
+  }
+}
+
+class _SelectableTextState extends State<SelectableText> with AutomaticKeepAliveClientMixin implements TextSelectionGestureDetectorBuilderDelegate {
+  EditableTextState? get _editableText => editableTextKey.currentState;
+
+  late _TextSpanEditingController _controller;
+
+  FocusNode? _focusNode;
+  FocusNode get _effectiveFocusNode => widget.focusNode ?? (_focusNode ??= FocusNode());
+
+  bool _showSelectionHandles = false;
+
+  late _SelectableTextSelectionGestureDetectorBuilder _selectionGestureDetectorBuilder;
+
+  // API for TextSelectionGestureDetectorBuilderDelegate.
+  @override
+  late bool forcePressEnabled;
+
+  @override
+  final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>();
+
+  @override
+  bool get selectionEnabled => widget.selectionEnabled;
+  // End of API for TextSelectionGestureDetectorBuilderDelegate.
+
+  @override
+  void initState() {
+    super.initState();
+    _selectionGestureDetectorBuilder = _SelectableTextSelectionGestureDetectorBuilder(state: this);
+    _controller = _TextSpanEditingController(
+        textSpan: widget.textSpan ?? TextSpan(text: widget.data)
+    );
+    _controller.addListener(_onControllerChanged);
+  }
+
+  @override
+  void didUpdateWidget(SelectableText oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.data != oldWidget.data || widget.textSpan != oldWidget.textSpan) {
+      _controller.removeListener(_onControllerChanged);
+      _controller = _TextSpanEditingController(
+          textSpan: widget.textSpan ?? TextSpan(text: widget.data)
+      );
+      _controller.addListener(_onControllerChanged);
+    }
+    if (_effectiveFocusNode.hasFocus && _controller.selection.isCollapsed) {
+      _showSelectionHandles = false;
+    } else {
+      _showSelectionHandles = true;
+    }
+  }
+
+  @override
+  void dispose() {
+    _focusNode?.dispose();
+    _controller.removeListener(_onControllerChanged);
+    super.dispose();
+  }
+
+  void _onControllerChanged() {
+    final bool showSelectionHandles = !_effectiveFocusNode.hasFocus
+      || !_controller.selection.isCollapsed;
+    if (showSelectionHandles == _showSelectionHandles) {
+      return;
+    }
+    setState(() {
+      _showSelectionHandles = showSelectionHandles;
+    });
+  }
+
+  void _handleSelectionChanged(TextSelection selection, SelectionChangedCause? cause) {
+    final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause);
+    if (willShowSelectionHandles != _showSelectionHandles) {
+      setState(() {
+        _showSelectionHandles = willShowSelectionHandles;
+      });
+    }
+
+    if (widget.onSelectionChanged != null) {
+      widget.onSelectionChanged!(selection, cause);
+    }
+
+    switch (Theme.of(context).platform) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        if (cause == SelectionChangedCause.longPress) {
+          _editableText?.bringIntoView(selection.base);
+        }
+        return;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+      // Do nothing.
+    }
+  }
+
+  /// Toggle the toolbar when a selection handle is tapped.
+  void _handleSelectionHandleTapped() {
+    if (_controller.selection.isCollapsed) {
+      _editableText!.toggleToolbar();
+    }
+  }
+
+  bool _shouldShowSelectionHandles(SelectionChangedCause? cause) {
+    // When the text field is activated by something that doesn't trigger the
+    // selection overlay, we shouldn't show the handles either.
+    if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar)
+      return false;
+
+    if (_controller.selection.isCollapsed)
+      return false;
+
+    if (cause == SelectionChangedCause.keyboard)
+      return false;
+
+    if (cause == SelectionChangedCause.longPress)
+      return true;
+
+    if (_controller.text.isNotEmpty)
+      return true;
+
+    return false;
+  }
+
+  @override
+  bool get wantKeepAlive => true;
+
+  @override
+  Widget build(BuildContext context) {
+    super.build(context); // See AutomaticKeepAliveClientMixin.
+    assert(() {
+      return _controller._textSpan.visitChildren((InlineSpan span) => span.runtimeType == TextSpan);
+    }(), 'SelectableText only supports TextSpan; Other type of InlineSpan is not allowed');
+    assert(debugCheckHasMediaQuery(context));
+    assert(debugCheckHasDirectionality(context));
+    assert(
+      !(widget.style != null && widget.style!.inherit == false &&
+          (widget.style!.fontSize == null || widget.style!.textBaseline == null)),
+      'inherit false style must supply fontSize and textBaseline',
+    );
+
+    final ThemeData theme = Theme.of(context);
+    final TextSelectionThemeData selectionTheme = TextSelectionTheme.of(context);
+    final FocusNode focusNode = _effectiveFocusNode;
+
+    TextSelectionControls? textSelectionControls =  widget.selectionControls;
+    final bool paintCursorAboveText;
+    final bool cursorOpacityAnimates;
+    Offset? cursorOffset;
+    Color? cursorColor = widget.cursorColor;
+    final Color selectionColor;
+    Radius? cursorRadius = widget.cursorRadius;
+
+    switch (theme.platform) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context);
+        forcePressEnabled = true;
+        textSelectionControls ??= cupertinoTextSelectionControls;
+        paintCursorAboveText = true;
+        cursorOpacityAnimates = true;
+        cursorColor ??= selectionTheme.cursorColor ?? cupertinoTheme.primaryColor;
+        selectionColor = selectionTheme.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40);
+        cursorRadius ??= const Radius.circular(2.0);
+        cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0);
+        break;
+
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        forcePressEnabled = false;
+        textSelectionControls ??= materialTextSelectionControls;
+        paintCursorAboveText = false;
+        cursorOpacityAnimates = false;
+        cursorColor ??= selectionTheme.cursorColor ?? theme.colorScheme.primary;
+        selectionColor = selectionTheme.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40);
+        break;
+    }
+
+    final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
+    TextStyle? effectiveTextStyle = widget.style;
+    if (effectiveTextStyle == null || effectiveTextStyle.inherit)
+      effectiveTextStyle = defaultTextStyle.style.merge(widget.style);
+    if (MediaQuery.boldTextOverride(context))
+      effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold));
+    final Widget child = RepaintBoundary(
+      child: EditableText(
+        key: editableTextKey,
+        style: effectiveTextStyle,
+        readOnly: true,
+        textWidthBasis: widget.textWidthBasis ?? defaultTextStyle.textWidthBasis,
+        textHeightBehavior: widget.textHeightBehavior ?? defaultTextStyle.textHeightBehavior,
+        showSelectionHandles: _showSelectionHandles,
+        showCursor: widget.showCursor,
+        controller: _controller,
+        focusNode: focusNode,
+        strutStyle: widget.strutStyle ?? const StrutStyle(),
+        textAlign: widget.textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
+        textDirection: widget.textDirection,
+        textScaleFactor: widget.textScaleFactor,
+        autofocus: widget.autofocus,
+        forceLine: false,
+        toolbarOptions: widget.toolbarOptions,
+        minLines: widget.minLines,
+        maxLines: widget.maxLines ?? defaultTextStyle.maxLines,
+        selectionColor: selectionColor,
+        selectionControls: widget.selectionEnabled ? textSelectionControls : null,
+        onSelectionChanged: _handleSelectionChanged,
+        onSelectionHandleTapped: _handleSelectionHandleTapped,
+        rendererIgnoresPointer: true,
+        cursorWidth: widget.cursorWidth,
+        cursorHeight: widget.cursorHeight,
+        cursorRadius: cursorRadius,
+        cursorColor: cursorColor,
+        cursorOpacityAnimates: cursorOpacityAnimates,
+        cursorOffset: cursorOffset,
+        paintCursorAboveText: paintCursorAboveText,
+        backgroundCursorColor: CupertinoColors.inactiveGray,
+        enableInteractiveSelection: widget.enableInteractiveSelection,
+        dragStartBehavior: widget.dragStartBehavior,
+        scrollPhysics: widget.scrollPhysics,
+      ),
+    );
+
+    return Semantics(
+      onTap: () {
+        if (!_controller.selection.isValid)
+          _controller.selection = TextSelection.collapsed(offset: _controller.text.length);
+        _effectiveFocusNode.requestFocus();
+      },
+      onLongPress: () {
+        _effectiveFocusNode.requestFocus();
+      },
+      child: _selectionGestureDetectorBuilder.buildGestureDetector(
+        behavior: HitTestBehavior.translucent,
+        child: child,
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/shadows.dart b/lib/src/material/shadows.dart
new file mode 100644
index 0000000..da31e92
--- /dev/null
+++ b/lib/src/material/shadows.dart
@@ -0,0 +1,96 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show Color, Offset;
+
+import 'package:flute/painting.dart';
+
+// Based on https://material.io/design/environment/elevation.html
+// Currently, only the elevation values that are bound to one or more widgets are
+// defined here.
+
+/// Map of elevation offsets used by material design to [BoxShadow] definitions.
+///
+/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24.
+///
+/// Each entry has three shadows which must be combined to obtain the defined
+/// effect for that elevation.
+///
+/// This is useful when simulating a shadow with a [BoxDecoration] or other
+/// class that uses a list of [BoxShadow] objects.
+///
+/// See also:
+///
+///  * [Material], which takes an arbitrary double for its elevation and generates
+///    a shadow dynamically.
+///  * <https://material.io/design/environment/elevation.html>
+const Map<int, List<BoxShadow>> kElevationToShadow = _elevationToShadow; // to hide the literal from the docs
+
+const Color _kKeyUmbraOpacity = Color(0x33000000); // alpha = 0.2
+const Color _kKeyPenumbraOpacity = Color(0x24000000); // alpha = 0.14
+const Color _kAmbientShadowOpacity = Color(0x1F000000); // alpha = 0.12
+const Map<int, List<BoxShadow>> _elevationToShadow = <int, List<BoxShadow>>{
+  // The empty list depicts no elevation.
+  0: <BoxShadow>[],
+
+  1: <BoxShadow>[
+    BoxShadow(offset: Offset(0.0, 2.0), blurRadius: 1.0, spreadRadius: -1.0, color: _kKeyUmbraOpacity),
+    BoxShadow(offset: Offset(0.0, 1.0), blurRadius: 1.0, spreadRadius: 0.0, color: _kKeyPenumbraOpacity),
+    BoxShadow(offset: Offset(0.0, 1.0), blurRadius: 3.0, spreadRadius: 0.0, color: _kAmbientShadowOpacity),
+  ],
+
+  2: <BoxShadow>[
+    BoxShadow(offset: Offset(0.0, 3.0), blurRadius: 1.0, spreadRadius: -2.0, color: _kKeyUmbraOpacity),
+    BoxShadow(offset: Offset(0.0, 2.0), blurRadius: 2.0, spreadRadius: 0.0, color: _kKeyPenumbraOpacity),
+    BoxShadow(offset: Offset(0.0, 1.0), blurRadius: 5.0, spreadRadius: 0.0, color: _kAmbientShadowOpacity),
+  ],
+
+  3: <BoxShadow>[
+    BoxShadow(offset: Offset(0.0, 3.0), blurRadius: 3.0, spreadRadius: -2.0, color: _kKeyUmbraOpacity),
+    BoxShadow(offset: Offset(0.0, 3.0), blurRadius: 4.0, spreadRadius: 0.0, color: _kKeyPenumbraOpacity),
+    BoxShadow(offset: Offset(0.0, 1.0), blurRadius: 8.0, spreadRadius: 0.0, color: _kAmbientShadowOpacity),
+  ],
+
+  4: <BoxShadow>[
+    BoxShadow(offset: Offset(0.0, 2.0), blurRadius: 4.0, spreadRadius: -1.0, color: _kKeyUmbraOpacity),
+    BoxShadow(offset: Offset(0.0, 4.0), blurRadius: 5.0, spreadRadius: 0.0, color: _kKeyPenumbraOpacity),
+    BoxShadow(offset: Offset(0.0, 1.0), blurRadius: 10.0, spreadRadius: 0.0, color: _kAmbientShadowOpacity),
+  ],
+
+  6: <BoxShadow>[
+    BoxShadow(offset: Offset(0.0, 3.0), blurRadius: 5.0, spreadRadius: -1.0, color: _kKeyUmbraOpacity),
+    BoxShadow(offset: Offset(0.0, 6.0), blurRadius: 10.0, spreadRadius: 0.0, color: _kKeyPenumbraOpacity),
+    BoxShadow(offset: Offset(0.0, 1.0), blurRadius: 18.0, spreadRadius: 0.0, color: _kAmbientShadowOpacity),
+  ],
+
+  8: <BoxShadow>[
+    BoxShadow(offset: Offset(0.0, 5.0), blurRadius: 5.0, spreadRadius: -3.0, color: _kKeyUmbraOpacity),
+    BoxShadow(offset: Offset(0.0, 8.0), blurRadius: 10.0, spreadRadius: 1.0, color: _kKeyPenumbraOpacity),
+    BoxShadow(offset: Offset(0.0, 3.0), blurRadius: 14.0, spreadRadius: 2.0, color: _kAmbientShadowOpacity),
+  ],
+
+  9: <BoxShadow>[
+    BoxShadow(offset: Offset(0.0, 5.0), blurRadius: 6.0, spreadRadius: -3.0, color: _kKeyUmbraOpacity),
+    BoxShadow(offset: Offset(0.0, 9.0), blurRadius: 12.0, spreadRadius: 1.0, color: _kKeyPenumbraOpacity),
+    BoxShadow(offset: Offset(0.0, 3.0), blurRadius: 16.0, spreadRadius: 2.0, color: _kAmbientShadowOpacity),
+  ],
+
+  12: <BoxShadow>[
+    BoxShadow(offset: Offset(0.0, 7.0), blurRadius: 8.0, spreadRadius: -4.0, color: _kKeyUmbraOpacity),
+    BoxShadow(offset: Offset(0.0, 12.0), blurRadius: 17.0, spreadRadius: 2.0, color: _kKeyPenumbraOpacity),
+    BoxShadow(offset: Offset(0.0, 5.0), blurRadius: 22.0, spreadRadius: 4.0, color: _kAmbientShadowOpacity),
+  ],
+
+  16: <BoxShadow>[
+    BoxShadow(offset: Offset(0.0, 8.0), blurRadius: 10.0, spreadRadius: -5.0, color: _kKeyUmbraOpacity),
+    BoxShadow(offset: Offset(0.0, 16.0), blurRadius: 24.0, spreadRadius: 2.0, color: _kKeyPenumbraOpacity),
+    BoxShadow(offset: Offset(0.0, 6.0), blurRadius: 30.0, spreadRadius: 5.0, color: _kAmbientShadowOpacity),
+  ],
+
+  24: <BoxShadow>[
+    BoxShadow(offset: Offset(0.0, 11.0), blurRadius: 15.0, spreadRadius: -7.0, color: _kKeyUmbraOpacity),
+    BoxShadow(offset: Offset(0.0, 24.0), blurRadius: 38.0, spreadRadius: 3.0, color: _kKeyPenumbraOpacity),
+    BoxShadow(offset: Offset(0.0, 9.0), blurRadius: 46.0, spreadRadius: 8.0, color: _kAmbientShadowOpacity),
+  ],
+};
diff --git a/lib/src/material/slider.dart b/lib/src/material/slider.dart
new file mode 100644
index 0000000..6d2ca4a
--- /dev/null
+++ b/lib/src/material/slider.dart
@@ -0,0 +1,1597 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:math' as math;
+
+import 'package:flute/cupertino.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart' show timeDilation;
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'constants.dart';
+import 'debug.dart';
+import 'material.dart';
+import 'material_state.dart';
+import 'slider_theme.dart';
+import 'theme.dart';
+
+// Examples can assume:
+// int _dollars = 0;
+// int _duelCommandment = 1;
+// void setState(VoidCallback fn) { }
+
+/// [Slider] uses this callback to paint the value indicator on the overlay.
+///
+/// Since the value indicator is painted on the Overlay; this method paints the
+/// value indicator in a [RenderBox] that appears in the [Overlay].
+typedef PaintValueIndicator = void Function(PaintingContext context, Offset offset);
+
+enum _SliderType { material, adaptive }
+
+/// A Material Design slider.
+///
+/// Used to select from a range of values.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=ufb4gIPDmEs}
+///
+/// {@tool dartpad --template=stateful_widget_scaffold}
+///
+/// ![A slider widget, consisting of 5 divisions and showing the default value
+/// indicator.](https://flutter.github.io/assets-for-api-docs/assets/material/slider.png)
+///
+/// The Sliders value is part of the Stateful widget subclass to change the value
+/// setState was called.
+///
+/// ```dart
+/// double _currentSliderValue = 20;
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Slider(
+///     value: _currentSliderValue,
+///     min: 0,
+///     max: 100,
+///     divisions: 5,
+///     label: _currentSliderValue.round().toString(),
+///     onChanged: (double value) {
+///       setState(() {
+///         _currentSliderValue = value;
+///       });
+///     },
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// A slider can be used to select from either a continuous or a discrete set of
+/// values. The default is to use a continuous range of values from [min] to
+/// [max]. To use discrete values, use a non-null value for [divisions], which
+/// indicates the number of discrete intervals. For example, if [min] is 0.0 and
+/// [max] is 50.0 and [divisions] is 5, then the slider can take on the
+/// discrete values 0.0, 10.0, 20.0, 30.0, 40.0, and 50.0.
+///
+/// The terms for the parts of a slider are:
+///
+///  * The "thumb", which is a shape that slides horizontally when the user
+///    drags it.
+///  * The "track", which is the line that the slider thumb slides along.
+///  * The "value indicator", which is a shape that pops up when the user
+///    is dragging the thumb to indicate the value being selected.
+///  * The "active" side of the slider is the side between the thumb and the
+///    minimum value.
+///  * The "inactive" side of the slider is the side between the thumb and the
+///    maximum value.
+///
+/// The slider will be disabled if [onChanged] is null or if the range given by
+/// [min]..[max] is empty (i.e. if [min] is equal to [max]).
+///
+/// The slider widget itself does not maintain any state. Instead, when the state
+/// of the slider changes, the widget calls the [onChanged] callback. Most
+/// widgets that use a slider will listen for the [onChanged] callback and
+/// rebuild the slider with a new [value] to update the visual appearance of the
+/// slider. To know when the value starts to change, or when it is done
+/// changing, set the optional callbacks [onChangeStart] and/or [onChangeEnd].
+///
+/// By default, a slider will be as wide as possible, centered vertically. When
+/// given unbounded constraints, it will attempt to make the track 144 pixels
+/// wide (with margins on each side) and will shrink-wrap vertically.
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// Requires one of its ancestors to be a [MediaQuery] widget. Typically, these
+/// are introduced by the [MaterialApp] or [WidgetsApp] widget at the top of
+/// your application widget tree.
+///
+/// To determine how it should be displayed (e.g. colors, thumb shape, etc.),
+/// a slider uses the [SliderThemeData] available from either a [SliderTheme]
+/// widget or the [ThemeData.sliderTheme] a [Theme] widget above it in the
+/// widget tree. You can also override some of the colors with the [activeColor]
+/// and [inactiveColor] properties, although more fine-grained control of the
+/// look is achieved using a [SliderThemeData].
+///
+/// See also:
+///
+///  * [SliderTheme] and [SliderThemeData] for information about controlling
+///    the visual appearance of the slider.
+///  * [Radio], for selecting among a set of explicit values.
+///  * [Checkbox] and [Switch], for toggling a particular value on or off.
+///  * <https://material.io/design/components/sliders.html>
+///  * [MediaQuery], from which the text scale factor is obtained.
+class Slider extends StatefulWidget {
+  /// Creates a Material Design slider.
+  ///
+  /// The slider itself does not maintain any state. Instead, when the state of
+  /// the slider changes, the widget calls the [onChanged] callback. Most
+  /// widgets that use a slider will listen for the [onChanged] callback and
+  /// rebuild the slider with a new [value] to update the visual appearance of
+  /// the slider.
+  ///
+  /// * [value] determines currently selected value for this slider.
+  /// * [onChanged] is called while the user is selecting a new value for the
+  ///   slider.
+  /// * [onChangeStart] is called when the user starts to select a new value for
+  ///   the slider.
+  /// * [onChangeEnd] is called when the user is done selecting a new value for
+  ///   the slider.
+  ///
+  /// You can override some of the colors with the [activeColor] and
+  /// [inactiveColor] properties, although more fine-grained control of the
+  /// appearance is achieved using a [SliderThemeData].
+  const Slider({
+    Key? key,
+    required this.value,
+    required this.onChanged,
+    this.onChangeStart,
+    this.onChangeEnd,
+    this.min = 0.0,
+    this.max = 1.0,
+    this.divisions,
+    this.label,
+    this.activeColor,
+    this.inactiveColor,
+    this.mouseCursor,
+    this.semanticFormatterCallback,
+    this.focusNode,
+    this.autofocus = false,
+  }) : _sliderType = _SliderType.material,
+       assert(value != null),
+       assert(min != null),
+       assert(max != null),
+       assert(min <= max),
+       assert(value >= min && value <= max),
+       assert(divisions == null || divisions > 0),
+       super(key: key);
+
+  /// Creates an adaptive [Slider] based on the target platform, following
+  /// Material design's
+  /// [Cross-platform guidelines](https://material.io/design/platform-guidance/cross-platform-adaptation.html).
+  ///
+  /// Creates a [CupertinoSlider] if the target platform is iOS, creates a
+  /// Material Design slider otherwise.
+  ///
+  /// If a [CupertinoSlider] is created, the following parameters are
+  /// ignored: [label], [inactiveColor], [semanticFormatterCallback].
+  ///
+  /// The target platform is based on the current [Theme]: [ThemeData.platform].
+  const Slider.adaptive({
+    Key? key,
+    required this.value,
+    required this.onChanged,
+    this.onChangeStart,
+    this.onChangeEnd,
+    this.min = 0.0,
+    this.max = 1.0,
+    this.divisions,
+    this.label,
+    this.mouseCursor,
+    this.activeColor,
+    this.inactiveColor,
+    this.semanticFormatterCallback,
+    this.focusNode,
+    this.autofocus = false,
+  }) : _sliderType = _SliderType.adaptive,
+       assert(value != null),
+       assert(min != null),
+       assert(max != null),
+       assert(min <= max),
+       assert(value >= min && value <= max),
+       assert(divisions == null || divisions > 0),
+       super(key: key);
+
+  /// The currently selected value for this slider.
+  ///
+  /// The slider's thumb is drawn at a position that corresponds to this value.
+  final double value;
+
+  /// Called during a drag when the user is selecting a new value for the slider
+  /// by dragging.
+  ///
+  /// The slider passes the new value to the callback but does not actually
+  /// change state until the parent widget rebuilds the slider with the new
+  /// value.
+  ///
+  /// If null, the slider will be displayed as disabled.
+  ///
+  /// The callback provided to onChanged should update the state of the parent
+  /// [StatefulWidget] using the [State.setState] method, so that the parent
+  /// gets rebuilt; for example:
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Slider(
+  ///   value: _duelCommandment.toDouble(),
+  ///   min: 1.0,
+  ///   max: 10.0,
+  ///   divisions: 10,
+  ///   label: '$_duelCommandment',
+  ///   onChanged: (double newValue) {
+  ///     setState(() {
+  ///       _duelCommandment = newValue.round();
+  ///     });
+  ///   },
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [onChangeStart] for a callback that is called when the user starts
+  ///    changing the value.
+  ///  * [onChangeEnd] for a callback that is called when the user stops
+  ///    changing the value.
+  final ValueChanged<double>? onChanged;
+
+  /// Called when the user starts selecting a new value for the slider.
+  ///
+  /// This callback shouldn't be used to update the slider [value] (use
+  /// [onChanged] for that), but rather to be notified when the user has started
+  /// selecting a new value by starting a drag or with a tap.
+  ///
+  /// The value passed will be the last [value] that the slider had before the
+  /// change began.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Slider(
+  ///   value: _duelCommandment.toDouble(),
+  ///   min: 1.0,
+  ///   max: 10.0,
+  ///   divisions: 10,
+  ///   label: '$_duelCommandment',
+  ///   onChanged: (double newValue) {
+  ///     setState(() {
+  ///       _duelCommandment = newValue.round();
+  ///     });
+  ///   },
+  ///   onChangeStart: (double startValue) {
+  ///     print('Started change at $startValue');
+  ///   },
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [onChangeEnd] for a callback that is called when the value change is
+  ///    complete.
+  final ValueChanged<double>? onChangeStart;
+
+  /// Called when the user is done selecting a new value for the slider.
+  ///
+  /// This callback shouldn't be used to update the slider [value] (use
+  /// [onChanged] for that), but rather to know when the user has completed
+  /// selecting a new [value] by ending a drag or a click.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// Slider(
+  ///   value: _duelCommandment.toDouble(),
+  ///   min: 1.0,
+  ///   max: 10.0,
+  ///   divisions: 10,
+  ///   label: '$_duelCommandment',
+  ///   onChanged: (double newValue) {
+  ///     setState(() {
+  ///       _duelCommandment = newValue.round();
+  ///     });
+  ///   },
+  ///   onChangeEnd: (double newValue) {
+  ///     print('Ended change on $newValue');
+  ///   },
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [onChangeStart] for a callback that is called when a value change
+  ///    begins.
+  final ValueChanged<double>? onChangeEnd;
+
+  /// The minimum value the user can select.
+  ///
+  /// Defaults to 0.0. Must be less than or equal to [max].
+  ///
+  /// If the [max] is equal to the [min], then the slider is disabled.
+  final double min;
+
+  /// The maximum value the user can select.
+  ///
+  /// Defaults to 1.0. Must be greater than or equal to [min].
+  ///
+  /// If the [max] is equal to the [min], then the slider is disabled.
+  final double max;
+
+  /// The number of discrete divisions.
+  ///
+  /// Typically used with [label] to show the current discrete value.
+  ///
+  /// If null, the slider is continuous.
+  final int? divisions;
+
+  /// A label to show above the slider when the slider is active.
+  ///
+  /// It is used to display the value of a discrete slider, and it is displayed
+  /// as part of the value indicator shape.
+  ///
+  /// The label is rendered using the active [ThemeData]'s [TextTheme.bodyText1]
+  /// text style, with the theme data's [ColorScheme.onPrimary] color. The
+  /// label's text style can be overridden with
+  /// [SliderThemeData.valueIndicatorTextStyle].
+  ///
+  /// If null, then the value indicator will not be displayed.
+  ///
+  /// Ignored if this slider is created with [Slider.adaptive].
+  ///
+  /// See also:
+  ///
+  ///  * [SliderComponentShape] for how to create a custom value indicator
+  ///    shape.
+  final String? label;
+
+  /// The color to use for the portion of the slider track that is active.
+  ///
+  /// The "active" side of the slider is the side between the thumb and the
+  /// minimum value.
+  ///
+  /// Defaults to [SliderThemeData.activeTrackColor] of the current
+  /// [SliderTheme].
+  ///
+  /// Using a [SliderTheme] gives much more fine-grained control over the
+  /// appearance of various components of the slider.
+  final Color? activeColor;
+
+  /// The color for the inactive portion of the slider track.
+  ///
+  /// The "inactive" side of the slider is the side between the thumb and the
+  /// maximum value.
+  ///
+  /// Defaults to the [SliderThemeData.inactiveTrackColor] of the current
+  /// [SliderTheme].
+  ///
+  /// Using a [SliderTheme] gives much more fine-grained control over the
+  /// appearance of various components of the slider.
+  ///
+  /// Ignored if this slider is created with [Slider.adaptive].
+  final Color? inactiveColor;
+
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  ///
+  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
+  final MouseCursor? mouseCursor;
+
+  /// The callback used to create a semantic value from a slider value.
+  ///
+  /// Defaults to formatting values as a percentage.
+  ///
+  /// This is used by accessibility frameworks like TalkBack on Android to
+  /// inform users what the currently selected value is with more context.
+  ///
+  /// {@tool snippet}
+  ///
+  /// In the example below, a slider for currency values is configured to
+  /// announce a value with a currency label.
+  ///
+  /// ```dart
+  /// Slider(
+  ///   value: _dollars.toDouble(),
+  ///   min: 20.0,
+  ///   max: 330.0,
+  ///   label: '$_dollars dollars',
+  ///   onChanged: (double newValue) {
+  ///     setState(() {
+  ///       _dollars = newValue.round();
+  ///     });
+  ///   },
+  ///   semanticFormatterCallback: (double newValue) {
+  ///     return '${newValue.round()} dollars';
+  ///   }
+  ///  )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// Ignored if this slider is created with [Slider.adaptive]
+  final SemanticFormatterCallback? semanticFormatterCallback;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  final _SliderType _sliderType ;
+
+  @override
+  _SliderState createState() => _SliderState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('value', value));
+    properties.add(ObjectFlagProperty<ValueChanged<double>>('onChanged', onChanged, ifNull: 'disabled'));
+    properties.add(ObjectFlagProperty<ValueChanged<double>>.has('onChangeStart', onChangeStart));
+    properties.add(ObjectFlagProperty<ValueChanged<double>>.has('onChangeEnd', onChangeEnd));
+    properties.add(DoubleProperty('min', min));
+    properties.add(DoubleProperty('max', max));
+    properties.add(IntProperty('divisions', divisions));
+    properties.add(StringProperty('label', label));
+    properties.add(ColorProperty('activeColor', activeColor));
+    properties.add(ColorProperty('inactiveColor', inactiveColor));
+    properties.add(ObjectFlagProperty<ValueChanged<double>>.has('semanticFormatterCallback', semanticFormatterCallback));
+    properties.add(ObjectFlagProperty<FocusNode>.has('focusNode', focusNode));
+    properties.add(FlagProperty('autofocus', value: autofocus, ifTrue: 'autofocus'));
+  }
+}
+
+class _SliderState extends State<Slider> with TickerProviderStateMixin {
+  static const Duration enableAnimationDuration = Duration(milliseconds: 75);
+  static const Duration valueIndicatorAnimationDuration = Duration(milliseconds: 100);
+
+  // Animation controller that is run when the overlay (a.k.a radial reaction)
+  // is shown in response to user interaction.
+  late AnimationController overlayController;
+  // Animation controller that is run when the value indicator is being shown
+  // or hidden.
+  late AnimationController valueIndicatorController;
+  // Animation controller that is run when enabling/disabling the slider.
+  late AnimationController enableController;
+  // Animation controller that is run when transitioning between one value
+  // and the next on a discrete slider.
+  late AnimationController positionController;
+  Timer? interactionTimer;
+
+  final GlobalKey _renderObjectKey = GlobalKey();
+  // Keyboard mapping for a focused slider.
+  late Map<LogicalKeySet, Intent> _shortcutMap;
+  // Action mapping for a focused slider.
+  late Map<Type, Action<Intent>> _actionMap;
+
+  bool get _enabled => widget.onChanged != null;
+  // Value Indicator Animation that appears on the Overlay.
+  PaintValueIndicator? paintValueIndicator;
+
+  @override
+  void initState() {
+    super.initState();
+    overlayController = AnimationController(
+      duration: kRadialReactionDuration,
+      vsync: this,
+    );
+    valueIndicatorController = AnimationController(
+      duration: valueIndicatorAnimationDuration,
+      vsync: this,
+    );
+    enableController = AnimationController(
+      duration: enableAnimationDuration,
+      vsync: this,
+    );
+    positionController = AnimationController(
+      duration: Duration.zero,
+      vsync: this,
+    );
+    enableController.value = widget.onChanged != null ? 1.0 : 0.0;
+    positionController.value = _unlerp(widget.value);
+    _shortcutMap = <LogicalKeySet, Intent>{
+      LogicalKeySet(LogicalKeyboardKey.arrowUp): const _AdjustSliderIntent.up(),
+      LogicalKeySet(LogicalKeyboardKey.arrowDown): const _AdjustSliderIntent.down(),
+      LogicalKeySet(LogicalKeyboardKey.arrowLeft): const _AdjustSliderIntent.left(),
+      LogicalKeySet(LogicalKeyboardKey.arrowRight): const _AdjustSliderIntent.right(),
+    };
+    _actionMap = <Type, Action<Intent>>{
+      _AdjustSliderIntent: CallbackAction<_AdjustSliderIntent>(
+        onInvoke: _actionHandler,
+      ),
+    };
+  }
+
+  @override
+  void dispose() {
+    interactionTimer?.cancel();
+    overlayController.dispose();
+    valueIndicatorController.dispose();
+    enableController.dispose();
+    positionController.dispose();
+    if (overlayEntry != null) {
+      overlayEntry!.remove();
+      overlayEntry = null;
+    }
+    super.dispose();
+  }
+
+  void _handleChanged(double value) {
+    assert(widget.onChanged != null);
+    final double lerpValue = _lerp(value);
+    if (lerpValue != widget.value) {
+      widget.onChanged!(lerpValue);
+    }
+  }
+
+  void _handleDragStart(double value) {
+    assert(widget.onChangeStart != null);
+    widget.onChangeStart!(_lerp(value));
+  }
+
+  void _handleDragEnd(double value) {
+    assert(widget.onChangeEnd != null);
+    widget.onChangeEnd!(_lerp(value));
+  }
+
+  void _actionHandler(_AdjustSliderIntent intent) {
+    final _RenderSlider renderSlider = _renderObjectKey.currentContext!.findRenderObject()! as _RenderSlider;
+    final TextDirection textDirection = Directionality.of(_renderObjectKey.currentContext!);
+    switch (intent.type) {
+      case _SliderAdjustmentType.right:
+        switch (textDirection) {
+          case TextDirection.rtl:
+            renderSlider.decreaseAction();
+            break;
+          case TextDirection.ltr:
+            renderSlider.increaseAction();
+            break;
+        }
+        break;
+      case _SliderAdjustmentType.left:
+        switch (textDirection) {
+          case TextDirection.rtl:
+            renderSlider.increaseAction();
+            break;
+          case TextDirection.ltr:
+            renderSlider.decreaseAction();
+            break;
+        }
+        break;
+      case _SliderAdjustmentType.up:
+        renderSlider.increaseAction();
+        break;
+      case _SliderAdjustmentType.down:
+        renderSlider.decreaseAction();
+        break;
+    }
+  }
+
+  bool _focused = false;
+  void _handleFocusHighlightChanged(bool focused) {
+    if (focused != _focused) {
+      setState(() { _focused = focused; });
+    }
+  }
+
+  bool _hovering = false;
+  void _handleHoverChanged(bool hovering) {
+    if (hovering != _hovering) {
+      setState(() { _hovering = hovering; });
+    }
+  }
+
+  // Returns a number between min and max, proportional to value, which must
+  // be between 0.0 and 1.0.
+  double _lerp(double value) {
+    assert(value >= 0.0);
+    assert(value <= 1.0);
+    return value * (widget.max - widget.min) + widget.min;
+  }
+
+  // Returns a number between 0.0 and 1.0, given a value between min and max.
+  double _unlerp(double value) {
+    assert(value <= widget.max);
+    assert(value >= widget.min);
+    return widget.max > widget.min ? (value - widget.min) / (widget.max - widget.min) : 0.0;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    assert(debugCheckHasMediaQuery(context));
+
+    switch (widget._sliderType) {
+      case _SliderType.material:
+        return _buildMaterialSlider(context);
+
+      case _SliderType.adaptive: {
+        final ThemeData theme = Theme.of(context);
+        assert(theme.platform != null);
+        switch (theme.platform) {
+          case TargetPlatform.android:
+          case TargetPlatform.fuchsia:
+          case TargetPlatform.linux:
+          case TargetPlatform.windows:
+            return _buildMaterialSlider(context);
+          case TargetPlatform.iOS:
+          case TargetPlatform.macOS:
+            return _buildCupertinoSlider(context);
+        }
+      }
+    }
+  }
+
+  Widget _buildMaterialSlider(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    SliderThemeData sliderTheme = SliderTheme.of(context);
+
+    // If the widget has active or inactive colors specified, then we plug them
+    // in to the slider theme as best we can. If the developer wants more
+    // control than that, then they need to use a SliderTheme. The default
+    // colors come from the ThemeData.colorScheme. These colors, along with
+    // the default shapes and text styles are aligned to the Material
+    // Guidelines.
+
+    const double _defaultTrackHeight = 4;
+    const SliderTrackShape _defaultTrackShape = RoundedRectSliderTrackShape();
+    const SliderTickMarkShape _defaultTickMarkShape = RoundSliderTickMarkShape();
+    const SliderComponentShape _defaultOverlayShape = RoundSliderOverlayShape();
+    const SliderComponentShape _defaultThumbShape = RoundSliderThumbShape();
+    const SliderComponentShape _defaultValueIndicatorShape = RectangularSliderValueIndicatorShape();
+    const ShowValueIndicator _defaultShowValueIndicator = ShowValueIndicator.onlyForDiscrete;
+
+    // The value indicator's color is not the same as the thumb and active track
+    // (which can be defined by activeColor) if the
+    // RectangularSliderValueIndicatorShape is used. In all other cases, the
+    // value indicator is assumed to be the same as the active color.
+    final SliderComponentShape valueIndicatorShape = sliderTheme.valueIndicatorShape ?? _defaultValueIndicatorShape;
+    final Color valueIndicatorColor;
+    if (valueIndicatorShape is RectangularSliderValueIndicatorShape) {
+      valueIndicatorColor = sliderTheme.valueIndicatorColor ?? Color.alphaBlend(theme.colorScheme.onSurface.withOpacity(0.60), theme.colorScheme.surface.withOpacity(0.90));
+    } else {
+      valueIndicatorColor = widget.activeColor ?? sliderTheme.valueIndicatorColor ?? theme.colorScheme.primary;
+    }
+
+    sliderTheme = sliderTheme.copyWith(
+      trackHeight: sliderTheme.trackHeight ?? _defaultTrackHeight,
+      activeTrackColor: widget.activeColor ?? sliderTheme.activeTrackColor ?? theme.colorScheme.primary,
+      inactiveTrackColor: widget.inactiveColor ?? sliderTheme.inactiveTrackColor ?? theme.colorScheme.primary.withOpacity(0.24),
+      disabledActiveTrackColor: sliderTheme.disabledActiveTrackColor ?? theme.colorScheme.onSurface.withOpacity(0.32),
+      disabledInactiveTrackColor: sliderTheme.disabledInactiveTrackColor ?? theme.colorScheme.onSurface.withOpacity(0.12),
+      activeTickMarkColor: widget.inactiveColor ?? sliderTheme.activeTickMarkColor ?? theme.colorScheme.onPrimary.withOpacity(0.54),
+      inactiveTickMarkColor: widget.activeColor ?? sliderTheme.inactiveTickMarkColor ?? theme.colorScheme.primary.withOpacity(0.54),
+      disabledActiveTickMarkColor: sliderTheme.disabledActiveTickMarkColor ?? theme.colorScheme.onPrimary.withOpacity(0.12),
+      disabledInactiveTickMarkColor: sliderTheme.disabledInactiveTickMarkColor ?? theme.colorScheme.onSurface.withOpacity(0.12),
+      thumbColor: widget.activeColor ?? sliderTheme.thumbColor ?? theme.colorScheme.primary,
+      disabledThumbColor: sliderTheme.disabledThumbColor ?? Color.alphaBlend(theme.colorScheme.onSurface.withOpacity(.38), theme.colorScheme.surface),
+      overlayColor: widget.activeColor?.withOpacity(0.12) ?? sliderTheme.overlayColor ?? theme.colorScheme.primary.withOpacity(0.12),
+      valueIndicatorColor: valueIndicatorColor,
+      trackShape: sliderTheme.trackShape ?? _defaultTrackShape,
+      tickMarkShape: sliderTheme.tickMarkShape ?? _defaultTickMarkShape,
+      thumbShape: sliderTheme.thumbShape ?? _defaultThumbShape,
+      overlayShape: sliderTheme.overlayShape ?? _defaultOverlayShape,
+      valueIndicatorShape: valueIndicatorShape,
+      showValueIndicator: sliderTheme.showValueIndicator ?? _defaultShowValueIndicator,
+      valueIndicatorTextStyle: sliderTheme.valueIndicatorTextStyle ?? theme.textTheme.bodyText1!.copyWith(
+        color: theme.colorScheme.onPrimary,
+      ),
+    );
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
+      widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
+      <MaterialState>{
+        if (!_enabled) MaterialState.disabled,
+        if (_hovering) MaterialState.hovered,
+        if (_focused) MaterialState.focused,
+      },
+    );
+
+    // This size is used as the max bounds for the painting of the value
+    // indicators It must be kept in sync with the function with the same name
+    // in range_slider.dart.
+    Size _screenSize() => MediaQuery.of(context).size;
+
+    return Semantics(
+      container: true,
+      slider: true,
+      child: FocusableActionDetector(
+        actions: _actionMap,
+        shortcuts: _shortcutMap,
+        focusNode: widget.focusNode,
+        autofocus: widget.autofocus,
+        enabled: _enabled,
+        onShowFocusHighlight: _handleFocusHighlightChanged,
+        onShowHoverHighlight: _handleHoverChanged,
+        mouseCursor: effectiveMouseCursor,
+        child: CompositedTransformTarget(
+          link: _layerLink,
+          child: _SliderRenderObjectWidget(
+            key: _renderObjectKey,
+            value: _unlerp(widget.value),
+            divisions: widget.divisions,
+            label: widget.label,
+            sliderTheme: sliderTheme,
+            textScaleFactor: MediaQuery.of(context).textScaleFactor,
+            screenSize: _screenSize(),
+            onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,
+            onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
+            onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
+            state: this,
+            semanticFormatterCallback: widget.semanticFormatterCallback,
+            hasFocus: _focused,
+            hovering: _hovering,
+          ),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildCupertinoSlider(BuildContext context) {
+    // The render box of a slider has a fixed height but takes up the available
+    // width. Wrapping the [CupertinoSlider] in this manner will help maintain
+    // the same size.
+    return SizedBox(
+      width: double.infinity,
+      child: CupertinoSlider(
+        value: widget.value,
+        onChanged: widget.onChanged,
+        onChangeStart: widget.onChangeStart,
+        onChangeEnd: widget.onChangeEnd,
+        min: widget.min,
+        max: widget.max,
+        divisions: widget.divisions,
+        activeColor: widget.activeColor,
+      ),
+    );
+  }
+  final LayerLink _layerLink = LayerLink();
+
+  OverlayEntry? overlayEntry;
+
+  void showValueIndicator() {
+    if (overlayEntry == null) {
+      overlayEntry = OverlayEntry(
+        builder: (BuildContext context) {
+          return CompositedTransformFollower(
+            link: _layerLink,
+            child: _ValueIndicatorRenderObjectWidget(
+              state: this,
+            ),
+          );
+        },
+      );
+      Overlay.of(context)!.insert(overlayEntry!);
+    }
+  }
+}
+
+
+class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
+  const _SliderRenderObjectWidget({
+    Key? key,
+    required this.value,
+    required this.divisions,
+    required this.label,
+    required this.sliderTheme,
+    required this.textScaleFactor,
+    required this.screenSize,
+    required this.onChanged,
+    required this.onChangeStart,
+    required this.onChangeEnd,
+    required this.state,
+    required this.semanticFormatterCallback,
+    required this.hasFocus,
+    required this.hovering,
+  }) : super(key: key);
+
+  final double value;
+  final int? divisions;
+  final String? label;
+  final SliderThemeData sliderTheme;
+  final double textScaleFactor;
+  final Size screenSize;
+  final ValueChanged<double>? onChanged;
+  final ValueChanged<double>? onChangeStart;
+  final ValueChanged<double>? onChangeEnd;
+  final SemanticFormatterCallback? semanticFormatterCallback;
+  final _SliderState state;
+  final bool hasFocus;
+  final bool hovering;
+
+  @override
+  _RenderSlider createRenderObject(BuildContext context) {
+    return _RenderSlider(
+      value: value,
+      divisions: divisions,
+      label: label,
+      sliderTheme: sliderTheme,
+      textScaleFactor: textScaleFactor,
+      screenSize: screenSize,
+      onChanged: onChanged,
+      onChangeStart: onChangeStart,
+      onChangeEnd: onChangeEnd,
+      state: state,
+      textDirection: Directionality.of(context),
+      semanticFormatterCallback: semanticFormatterCallback,
+      platform: Theme.of(context).platform,
+      hasFocus: hasFocus,
+      hovering: hovering,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderSlider renderObject) {
+    renderObject
+      // We should update the `divisions` ahead of `value`, because the `value`
+      // setter dependent on the `divisions`.
+      ..divisions = divisions
+      ..value = value
+      ..label = label
+      ..sliderTheme = sliderTheme
+      ..textScaleFactor = textScaleFactor
+      ..screenSize = screenSize
+      ..onChanged = onChanged
+      ..onChangeStart = onChangeStart
+      ..onChangeEnd = onChangeEnd
+      ..textDirection = Directionality.of(context)
+      ..semanticFormatterCallback = semanticFormatterCallback
+      ..platform = Theme.of(context).platform
+      ..hasFocus = hasFocus
+      ..hovering = hovering;
+    // Ticker provider cannot change since there's a 1:1 relationship between
+    // the _SliderRenderObjectWidget object and the _SliderState object.
+  }
+}
+
+class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
+  _RenderSlider({
+    required double value,
+    required int? divisions,
+    required String? label,
+    required SliderThemeData sliderTheme,
+    required double textScaleFactor,
+    required Size screenSize,
+    required TargetPlatform platform,
+    required ValueChanged<double>? onChanged,
+    required SemanticFormatterCallback? semanticFormatterCallback,
+    required this.onChangeStart,
+    required this.onChangeEnd,
+    required _SliderState state,
+    required TextDirection textDirection,
+    required bool hasFocus,
+    required bool hovering,
+  }) : assert(value != null && value >= 0.0 && value <= 1.0),
+        assert(state != null),
+        assert(textDirection != null),
+        _platform = platform,
+        _semanticFormatterCallback = semanticFormatterCallback,
+        _label = label,
+        _value = value,
+        _divisions = divisions,
+        _sliderTheme = sliderTheme,
+        _textScaleFactor = textScaleFactor,
+        _screenSize = screenSize,
+        _onChanged = onChanged,
+        _state = state,
+        _textDirection = textDirection,
+        _hasFocus = hasFocus,
+        _hovering = hovering {
+    _updateLabelPainter();
+    final GestureArenaTeam team = GestureArenaTeam();
+    _drag = HorizontalDragGestureRecognizer()
+      ..team = team
+      ..onStart = _handleDragStart
+      ..onUpdate = _handleDragUpdate
+      ..onEnd = _handleDragEnd
+      ..onCancel = _endInteraction;
+    _tap = TapGestureRecognizer()
+      ..team = team
+      ..onTapDown = _handleTapDown
+      ..onTapUp = _handleTapUp
+      ..onTapCancel = _endInteraction;
+    _overlayAnimation = CurvedAnimation(
+      parent: _state.overlayController,
+      curve: Curves.fastOutSlowIn,
+    );
+    _valueIndicatorAnimation = CurvedAnimation(
+      parent: _state.valueIndicatorController,
+      curve: Curves.fastOutSlowIn,
+    )..addStatusListener((AnimationStatus status) {
+      if (status == AnimationStatus.dismissed && _state.overlayEntry != null) {
+        _state.overlayEntry!.remove();
+        _state.overlayEntry = null;
+      }
+    });
+    _enableAnimation = CurvedAnimation(
+      parent: _state.enableController,
+      curve: Curves.easeInOut,
+    );
+  }
+  static const Duration _positionAnimationDuration = Duration(milliseconds: 75);
+  static const Duration _minimumInteractionTime = Duration(milliseconds: 500);
+
+  // This value is the touch target, 48, multiplied by 3.
+  static const double _minPreferredTrackWidth = 144.0;
+
+  // Compute the largest width and height needed to paint the slider shapes,
+  // other than the track shape. It is assumed that these shapes are vertically
+  // centered on the track.
+  double get _maxSliderPartWidth => _sliderPartSizes.map((Size size) => size.width).reduce(math.max);
+  double get _maxSliderPartHeight => _sliderPartSizes.map((Size size) => size.height).reduce(math.max);
+  List<Size> get _sliderPartSizes => <Size>[
+    _sliderTheme.overlayShape!.getPreferredSize(isInteractive, isDiscrete),
+    _sliderTheme.thumbShape!.getPreferredSize(isInteractive, isDiscrete),
+    _sliderTheme.tickMarkShape!.getPreferredSize(isEnabled: isInteractive, sliderTheme: sliderTheme),
+  ];
+  double get _minPreferredTrackHeight => _sliderTheme.trackHeight!;
+
+  final _SliderState _state;
+  late Animation<double> _overlayAnimation;
+  late Animation<double> _valueIndicatorAnimation;
+  late Animation<double> _enableAnimation;
+  final TextPainter _labelPainter = TextPainter();
+  late HorizontalDragGestureRecognizer _drag;
+  late TapGestureRecognizer _tap;
+  bool _active = false;
+  double _currentDragValue = 0.0;
+
+  // This rect is used in gesture calculations, where the gesture coordinates
+  // are relative to the sliders origin. Therefore, the offset is passed as
+  // (0,0).
+  Rect get _trackRect => _sliderTheme.trackShape!.getPreferredRect(
+    parentBox: this,
+    offset: Offset.zero,
+    sliderTheme: _sliderTheme,
+    isDiscrete: false,
+  );
+
+  bool get isInteractive => onChanged != null;
+
+  bool get isDiscrete => divisions != null && divisions! > 0;
+
+  double get value => _value;
+  double _value;
+  set value(double newValue) {
+    assert(newValue != null && newValue >= 0.0 && newValue <= 1.0);
+    final double convertedValue = isDiscrete ? _discretize(newValue) : newValue;
+    if (convertedValue == _value) {
+      return;
+    }
+    _value = convertedValue;
+    if (isDiscrete) {
+      // Reset the duration to match the distance that we're traveling, so that
+      // whatever the distance, we still do it in _positionAnimationDuration,
+      // and if we get re-targeted in the middle, it still takes that long to
+      // get to the new location.
+      final double distance = (_value - _state.positionController.value).abs();
+      _state.positionController.duration = distance != 0.0
+        ? _positionAnimationDuration * (1.0 / distance)
+        : Duration.zero;
+      _state.positionController.animateTo(convertedValue, curve: Curves.easeInOut);
+    } else {
+      _state.positionController.value = convertedValue;
+    }
+    markNeedsSemanticsUpdate();
+  }
+
+  TargetPlatform _platform;
+  TargetPlatform get platform => _platform;
+  set platform(TargetPlatform value) {
+    if (_platform == value)
+      return;
+    _platform = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  SemanticFormatterCallback? _semanticFormatterCallback;
+  SemanticFormatterCallback? get semanticFormatterCallback => _semanticFormatterCallback;
+  set semanticFormatterCallback(SemanticFormatterCallback? value) {
+    if (_semanticFormatterCallback == value)
+      return;
+    _semanticFormatterCallback = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  int? get divisions => _divisions;
+  int? _divisions;
+  set divisions(int? value) {
+    if (value == _divisions) {
+      return;
+    }
+    _divisions = value;
+    markNeedsPaint();
+  }
+
+  String? get label => _label;
+  String? _label;
+  set label(String? value) {
+    if (value == _label) {
+      return;
+    }
+    _label = value;
+    _updateLabelPainter();
+  }
+
+  SliderThemeData get sliderTheme => _sliderTheme;
+  SliderThemeData _sliderTheme;
+  set sliderTheme(SliderThemeData value) {
+    if (value == _sliderTheme) {
+      return;
+    }
+    _sliderTheme = value;
+    markNeedsPaint();
+  }
+
+  double get textScaleFactor => _textScaleFactor;
+  double _textScaleFactor;
+  set textScaleFactor(double value) {
+    if (value == _textScaleFactor) {
+      return;
+    }
+    _textScaleFactor = value;
+    _updateLabelPainter();
+  }
+
+  Size get screenSize => _screenSize;
+  Size _screenSize;
+  set screenSize(Size value) {
+    if (value == _screenSize) {
+      return;
+    }
+    _screenSize = value;
+    markNeedsPaint();
+  }
+
+  ValueChanged<double>? get onChanged => _onChanged;
+  ValueChanged<double>? _onChanged;
+  set onChanged(ValueChanged<double>? value) {
+    if (value == _onChanged) {
+      return;
+    }
+    final bool wasInteractive = isInteractive;
+    _onChanged = value;
+    if (wasInteractive != isInteractive) {
+      if (isInteractive) {
+        _state.enableController.forward();
+      } else {
+        _state.enableController.reverse();
+      }
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  ValueChanged<double>? onChangeStart;
+  ValueChanged<double>? onChangeEnd;
+
+  TextDirection get textDirection => _textDirection;
+  TextDirection _textDirection;
+  set textDirection(TextDirection value) {
+    assert(value != null);
+    if (value == _textDirection) {
+      return;
+    }
+    _textDirection = value;
+    _updateLabelPainter();
+  }
+
+  /// True if this slider has the input focus.
+  bool get hasFocus => _hasFocus;
+  bool _hasFocus;
+  set hasFocus(bool value) {
+    assert(value != null);
+    if (value == _hasFocus)
+      return;
+    _hasFocus = value;
+    _updateForFocusOrHover(_hasFocus);
+    markNeedsSemanticsUpdate();
+  }
+
+  /// True if this slider is being hovered over by a pointer.
+  bool get hovering => _hovering;
+  bool _hovering;
+  set hovering(bool value) {
+    assert(value != null);
+    if (value == _hovering)
+      return;
+    _hovering = value;
+    _updateForFocusOrHover(_hovering);
+  }
+
+  void _updateForFocusOrHover(bool hasFocusOrIsHovering) {
+    if (hasFocusOrIsHovering) {
+      _state.overlayController.forward();
+      if (showValueIndicator) {
+        _state.valueIndicatorController.forward();
+      }
+    } else {
+      _state.overlayController.reverse();
+      if (showValueIndicator) {
+        _state.valueIndicatorController.reverse();
+      }
+    }
+  }
+
+  bool get showValueIndicator {
+    switch (_sliderTheme.showValueIndicator!) {
+      case ShowValueIndicator.onlyForDiscrete:
+        return isDiscrete;
+      case ShowValueIndicator.onlyForContinuous:
+        return !isDiscrete;
+      case ShowValueIndicator.always:
+        return true;
+      case ShowValueIndicator.never:
+        return false;
+    }
+  }
+
+  double get _adjustmentUnit {
+    switch (_platform) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        // Matches iOS implementation of material slider.
+        return 0.1;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        // Matches Android implementation of material slider.
+        return 0.05;
+    }
+  }
+
+  void _updateLabelPainter() {
+    if (label != null) {
+      _labelPainter
+        ..text = TextSpan(
+          style: _sliderTheme.valueIndicatorTextStyle,
+          text: label,
+        )
+        ..textDirection = textDirection
+        ..textScaleFactor = textScaleFactor
+        ..layout();
+    } else {
+      _labelPainter.text = null;
+    }
+    // Changing the textDirection can result in the layout changing, because the
+    // bidi algorithm might line up the glyphs differently which can result in
+    // different ligatures, different shapes, etc. So we always markNeedsLayout.
+    markNeedsLayout();
+  }
+
+  @override
+  void systemFontsDidChange() {
+    super.systemFontsDidChange();
+    _labelPainter.markNeedsLayout();
+    _updateLabelPainter();
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    _overlayAnimation.addListener(markNeedsPaint);
+    _valueIndicatorAnimation.addListener(markNeedsPaint);
+    _enableAnimation.addListener(markNeedsPaint);
+    _state.positionController.addListener(markNeedsPaint);
+  }
+
+  @override
+  void detach() {
+    _overlayAnimation.removeListener(markNeedsPaint);
+    _valueIndicatorAnimation.removeListener(markNeedsPaint);
+    _enableAnimation.removeListener(markNeedsPaint);
+    _state.positionController.removeListener(markNeedsPaint);
+    super.detach();
+  }
+
+  double _getValueFromVisualPosition(double visualPosition) {
+    switch (textDirection) {
+      case TextDirection.rtl:
+        return 1.0 - visualPosition;
+      case TextDirection.ltr:
+        return visualPosition;
+    }
+  }
+
+  double _getValueFromGlobalPosition(Offset globalPosition) {
+    final double visualPosition = (globalToLocal(globalPosition).dx - _trackRect.left) / _trackRect.width;
+    return _getValueFromVisualPosition(visualPosition);
+  }
+
+  double _discretize(double value) {
+    double result = value.clamp(0.0, 1.0);
+    if (isDiscrete) {
+      result = (result * divisions!).round() / divisions!;
+    }
+    return result;
+  }
+
+  void _startInteraction(Offset globalPosition) {
+    _state.showValueIndicator();
+    if (isInteractive) {
+      _active = true;
+      // We supply the *current* value as the start location, so that if we have
+      // a tap, it consists of a call to onChangeStart with the previous value and
+      // a call to onChangeEnd with the new value.
+      if (onChangeStart != null) {
+        onChangeStart!(_discretize(value));
+      }
+      _currentDragValue = _getValueFromGlobalPosition(globalPosition);
+      onChanged!(_discretize(_currentDragValue));
+      _state.overlayController.forward();
+      if (showValueIndicator) {
+        _state.valueIndicatorController.forward();
+        _state.interactionTimer?.cancel();
+        _state.interactionTimer = Timer(_minimumInteractionTime * timeDilation, () {
+          _state.interactionTimer = null;
+          if (!_active &&
+              _state.valueIndicatorController.status == AnimationStatus.completed) {
+            _state.valueIndicatorController.reverse();
+          }
+        });
+      }
+    }
+  }
+
+  void _endInteraction() {
+    if (!_state.mounted) {
+      return;
+    }
+
+    if (_active && _state.mounted) {
+      if (onChangeEnd != null) {
+        onChangeEnd!(_discretize(_currentDragValue));
+      }
+      _active = false;
+      _currentDragValue = 0.0;
+      _state.overlayController.reverse();
+
+      if (showValueIndicator && _state.interactionTimer == null) {
+        _state.valueIndicatorController.reverse();
+      }
+    }
+  }
+
+  void _handleDragStart(DragStartDetails details) {
+    _startInteraction(details.globalPosition);
+  }
+
+  void _handleDragUpdate(DragUpdateDetails details) {
+    if (!_state.mounted) {
+      return;
+    }
+
+    if (isInteractive) {
+      final double valueDelta = details.primaryDelta! / _trackRect.width;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          _currentDragValue -= valueDelta;
+          break;
+        case TextDirection.ltr:
+          _currentDragValue += valueDelta;
+          break;
+      }
+      onChanged!(_discretize(_currentDragValue));
+    }
+  }
+
+  void _handleDragEnd(DragEndDetails details) {
+    _endInteraction();
+  }
+
+  void _handleTapDown(TapDownDetails details) {
+    _startInteraction(details.globalPosition);
+  }
+
+  void _handleTapUp(TapUpDetails details) {
+    _endInteraction();
+  }
+
+  @override
+  bool hitTestSelf(Offset position) => true;
+
+  @override
+  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
+    assert(debugHandleEvent(event, entry));
+    if (event is PointerDownEvent && isInteractive) {
+      // We need to add the drag first so that it has priority.
+      _drag.addPointer(event);
+      _tap.addPointer(event);
+    }
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) => _minPreferredTrackWidth + _maxSliderPartWidth;
+
+  @override
+  double computeMaxIntrinsicWidth(double height) => _minPreferredTrackWidth + _maxSliderPartWidth;
+
+  @override
+  double computeMinIntrinsicHeight(double width) => math.max(_minPreferredTrackHeight, _maxSliderPartHeight);
+
+  @override
+  double computeMaxIntrinsicHeight(double width) => math.max(_minPreferredTrackHeight, _maxSliderPartHeight);
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return Size(
+      constraints.hasBoundedWidth ? constraints.maxWidth : _minPreferredTrackWidth + _maxSliderPartWidth,
+      constraints.hasBoundedHeight ? constraints.maxHeight : math.max(_minPreferredTrackHeight, _maxSliderPartHeight),
+    );
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final double value = _state.positionController.value;
+
+    // The visual position is the position of the thumb from 0 to 1 from left
+    // to right. In left to right, this is the same as the value, but it is
+    // reversed for right to left text.
+    final double visualPosition;
+    switch (textDirection) {
+      case TextDirection.rtl:
+        visualPosition = 1.0 - value;
+        break;
+      case TextDirection.ltr:
+        visualPosition = value;
+        break;
+    }
+
+    final Rect trackRect = _sliderTheme.trackShape!.getPreferredRect(
+      parentBox: this,
+      offset: offset,
+      sliderTheme: _sliderTheme,
+      isDiscrete: isDiscrete,
+    );
+    final Offset thumbCenter = Offset(trackRect.left + visualPosition * trackRect.width, trackRect.center.dy);
+
+    _sliderTheme.trackShape!.paint(
+      context,
+      offset,
+      parentBox: this,
+      sliderTheme: _sliderTheme,
+      enableAnimation: _enableAnimation,
+      textDirection: _textDirection,
+      thumbCenter: thumbCenter,
+      isDiscrete: isDiscrete,
+      isEnabled: isInteractive,
+    );
+
+    if (!_overlayAnimation.isDismissed) {
+      _sliderTheme.overlayShape!.paint(
+        context,
+        thumbCenter,
+        activationAnimation: _overlayAnimation,
+        enableAnimation: _enableAnimation,
+        isDiscrete: isDiscrete,
+        labelPainter: _labelPainter,
+        parentBox: this,
+        sliderTheme: _sliderTheme,
+        textDirection: _textDirection,
+        value: _value,
+        textScaleFactor: _textScaleFactor,
+        sizeWithOverflow: screenSize.isEmpty ? size : screenSize,
+      );
+    }
+
+    if (isDiscrete) {
+      final double tickMarkWidth = _sliderTheme.tickMarkShape!.getPreferredSize(
+        isEnabled: isInteractive,
+        sliderTheme: _sliderTheme,
+      ).width;
+      final double padding = trackRect.height;
+      final double adjustedTrackWidth = trackRect.width - padding;
+      // If the tick marks would be too dense, don't bother painting them.
+      if (adjustedTrackWidth / divisions! >= 3.0 * tickMarkWidth) {
+        final double dy = trackRect.center.dy;
+        for (int i = 0; i <= divisions!; i++) {
+          final double value = i / divisions!;
+          // The ticks are mapped to be within the track, so the tick mark width
+          // must be subtracted from the track width.
+          final double dx = trackRect.left + value * adjustedTrackWidth + padding / 2;
+          final Offset tickMarkOffset = Offset(dx, dy);
+          _sliderTheme.tickMarkShape!.paint(
+            context,
+            tickMarkOffset,
+            parentBox: this,
+            sliderTheme: _sliderTheme,
+            enableAnimation: _enableAnimation,
+            textDirection: _textDirection,
+            thumbCenter: thumbCenter,
+            isEnabled: isInteractive,
+          );
+        }
+      }
+    }
+
+    if (isInteractive && label != null && !_valueIndicatorAnimation.isDismissed) {
+      if (showValueIndicator) {
+        _state.paintValueIndicator = (PaintingContext context, Offset offset) {
+          if (attached) {
+            _sliderTheme.valueIndicatorShape!.paint(
+              context,
+              offset + thumbCenter,
+              activationAnimation: _valueIndicatorAnimation,
+              enableAnimation: _enableAnimation,
+              isDiscrete: isDiscrete,
+              labelPainter: _labelPainter,
+              parentBox: this,
+              sliderTheme: _sliderTheme,
+              textDirection: _textDirection,
+              value: _value,
+              textScaleFactor: textScaleFactor,
+              sizeWithOverflow: screenSize.isEmpty ? size : screenSize,
+            );
+          }
+        };
+      }
+    }
+
+    _sliderTheme.thumbShape!.paint(
+      context,
+      thumbCenter,
+      activationAnimation: _overlayAnimation,
+      enableAnimation: _enableAnimation,
+      isDiscrete: isDiscrete,
+      labelPainter: _labelPainter,
+      parentBox: this,
+      sliderTheme: _sliderTheme,
+      textDirection: _textDirection,
+      value: _value,
+      textScaleFactor: textScaleFactor,
+      sizeWithOverflow: screenSize.isEmpty ? size : screenSize,
+    );
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+
+    // The Slider widget has its own Focus widget with semantics information,
+    // and we want that semantics node to collect the semantics information here
+    // so that it's all in the same node: otherwise Talkback sees that the node
+    // has focusable children, and it won't focus the Slider's Focus widget
+    // because it thinks the Focus widget's node doesn't have anything to say
+    // (which it doesn't, but this child does). Aggregating the semantic
+    // information into one node means that Talkback will recognize that it has
+    // something to say and focus it when it receives keyboard focus.
+    // (See https://github.com/flutter/flutter/issues/57038 for context).
+    config.isSemanticBoundary = false;
+
+    config.isEnabled = isInteractive;
+    config.textDirection = textDirection;
+    if (isInteractive) {
+      config.onIncrease = increaseAction;
+      config.onDecrease = decreaseAction;
+    }
+    config.label = _label ?? '';
+    if (semanticFormatterCallback != null) {
+      config.value = semanticFormatterCallback!(_state._lerp(value));
+      config.increasedValue = semanticFormatterCallback!(_state._lerp((value + _semanticActionUnit).clamp(0.0, 1.0)));
+      config.decreasedValue = semanticFormatterCallback!(_state._lerp((value - _semanticActionUnit).clamp(0.0, 1.0)));
+    } else {
+      config.value = '${(value * 100).round()}%';
+      config.increasedValue = '${((value + _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%';
+      config.decreasedValue = '${((value - _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%';
+    }
+  }
+
+  double get _semanticActionUnit => divisions != null ? 1.0 / divisions! : _adjustmentUnit;
+
+  void increaseAction() {
+    if (isInteractive) {
+      onChanged!((value + _semanticActionUnit).clamp(0.0, 1.0));
+    }
+  }
+
+  void decreaseAction() {
+    if (isInteractive) {
+      onChanged!((value - _semanticActionUnit).clamp(0.0, 1.0));
+    }
+  }
+}
+
+class _AdjustSliderIntent extends Intent {
+  const _AdjustSliderIntent({
+    required this.type
+  });
+
+  const _AdjustSliderIntent.right() : type = _SliderAdjustmentType.right;
+
+  const _AdjustSliderIntent.left() : type = _SliderAdjustmentType.left;
+
+  const _AdjustSliderIntent.up() : type = _SliderAdjustmentType.up;
+
+  const _AdjustSliderIntent.down() : type = _SliderAdjustmentType.down;
+
+  final _SliderAdjustmentType type;
+}
+
+enum _SliderAdjustmentType {
+  right,
+  left,
+  up,
+  down,
+}
+
+class _ValueIndicatorRenderObjectWidget extends LeafRenderObjectWidget {
+  const _ValueIndicatorRenderObjectWidget({
+    required this.state,
+  });
+
+  final _SliderState state;
+
+  @override
+  _RenderValueIndicator createRenderObject(BuildContext context) {
+    return _RenderValueIndicator(
+      state: state,
+    );
+  }
+  @override
+  void updateRenderObject(BuildContext context, _RenderValueIndicator renderObject) {
+    renderObject._state = state;
+  }
+}
+
+class _RenderValueIndicator extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
+  _RenderValueIndicator({
+    required _SliderState state,
+  }) : _state = state {
+    _valueIndicatorAnimation = CurvedAnimation(
+      parent: _state.valueIndicatorController,
+      curve: Curves.fastOutSlowIn,
+    );
+  }
+  late Animation<double> _valueIndicatorAnimation;
+  _SliderState _state;
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    _valueIndicatorAnimation.addListener(markNeedsPaint);
+    _state.positionController.addListener(markNeedsPaint);
+  }
+
+  @override
+  void detach() {
+    _valueIndicatorAnimation.removeListener(markNeedsPaint);
+    _state.positionController.removeListener(markNeedsPaint);
+    super.detach();
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (_state.paintValueIndicator != null) {
+      _state.paintValueIndicator!(context, offset);
+    }
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return constraints.smallest;
+  }
+}
diff --git a/lib/src/material/slider_theme.dart b/lib/src/material/slider_theme.dart
new file mode 100644
index 0000000..6318492
--- /dev/null
+++ b/lib/src/material/slider_theme.dart
@@ -0,0 +1,3324 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show Path, lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// Applies a slider theme to descendant [Slider] widgets.
+///
+/// A slider theme describes the colors and shape choices of the slider
+/// components.
+///
+/// Descendant widgets obtain the current theme's [SliderThemeData] object using
+/// [SliderTheme.of]. When a widget uses [SliderTheme.of], it is automatically
+/// rebuilt if the theme later changes.
+///
+/// The slider is as big as the largest of
+/// the [SliderComponentShape.getPreferredSize] of the thumb shape,
+/// the [SliderComponentShape.getPreferredSize] of the overlay shape,
+/// and the [SliderTickMarkShape.getPreferredSize] of the tick mark shape.
+///
+/// See also:
+///
+///  * [SliderThemeData], which describes the actual configuration of a slider
+///    theme.
+/// {@template flutter.material.SliderTheme.sliderComponentShape}
+///  * [SliderComponentShape], which can be used to create custom shapes for
+///    the [Slider]'s thumb, overlay, and value indicator and the
+///    [RangeSlider]'s overlay.
+/// {@endtemplate}
+/// {@template flutter.material.SliderTheme.sliderTrackShape}
+///  * [SliderTrackShape], which can be used to create custom shapes for the
+///    [Slider]'s track.
+/// {@endtemplate}
+/// {@template flutter.material.SliderTheme.sliderTickMarkShape}
+///  * [SliderTickMarkShape], which can be used to create custom shapes for the
+///    [Slider]'s tick marks.
+/// {@endtemplate}
+/// {@template flutter.material.SliderTheme.rangeSliderThumbShape}
+///  * [RangeSliderThumbShape], which can be used to create custom shapes for
+///    the [RangeSlider]'s thumb.
+/// {@endtemplate}
+/// {@template flutter.material.SliderTheme.rangeSliderValueIndicatorShape}
+///  * [RangeSliderValueIndicatorShape], which can be used to create custom
+///    shapes for the [RangeSlider]'s value indicator.
+/// {@endtemplate}
+/// {@template flutter.material.SliderTheme.rangeSliderTrackShape}
+///  * [RangeSliderTrackShape], which can be used to create custom shapes for
+///    the [RangeSlider]'s track.
+/// {@endtemplate}
+/// {@template flutter.material.SliderTheme.rangeSliderTickMarkShape}
+///  * [RangeSliderTickMarkShape], which can be used to create custom shapes for
+///    the [RangeSlider]'s tick marks.
+/// {@endtemplate}
+class SliderTheme extends InheritedTheme {
+  /// Applies the given theme [data] to [child].
+  ///
+  /// The [data] and [child] arguments must not be null.
+  const SliderTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(child != null),
+       assert(data != null),
+       super(key: key, child: child);
+
+  /// Specifies the color and shape values for descendant slider widgets.
+  final SliderThemeData data;
+
+  /// Returns the data from the closest [SliderTheme] instance that encloses
+  /// the given context.
+  ///
+  /// Defaults to the ambient [ThemeData.sliderTheme] if there is no
+  /// [SliderTheme] in the given build context.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// class Launch extends StatefulWidget {
+  ///   @override
+  ///   State createState() => LaunchState();
+  /// }
+  ///
+  /// class LaunchState extends State<Launch> {
+  ///   double _rocketThrust = 0;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return SliderTheme(
+  ///       data: SliderTheme.of(context).copyWith(activeTrackColor: const Color(0xff804040)),
+  ///       child: Slider(
+  ///         onChanged: (double value) { setState(() { _rocketThrust = value; }); },
+  ///         value: _rocketThrust,
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [SliderThemeData], which describes the actual configuration of a slider
+  ///    theme.
+  static SliderThemeData of(BuildContext context) {
+    final SliderTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<SliderTheme>();
+    return inheritedTheme != null ? inheritedTheme.data : Theme.of(context).sliderTheme;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return SliderTheme(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(SliderTheme oldWidget) => data != oldWidget.data;
+}
+
+/// Describes the conditions under which the value indicator on a [Slider]
+/// will be shown. Used with [SliderThemeData.showValueIndicator].
+///
+/// See also:
+///
+///  * [Slider], a Material Design slider widget.
+///  * [SliderThemeData], which describes the actual configuration of a slider
+///    theme.
+enum ShowValueIndicator {
+  /// The value indicator will only be shown for discrete sliders (sliders
+  /// where [Slider.divisions] is non-null).
+  onlyForDiscrete,
+
+  /// The value indicator will only be shown for continuous sliders (sliders
+  /// where [Slider.divisions] is null).
+  onlyForContinuous,
+
+  /// The value indicator will be shown for all types of sliders.
+  always,
+
+  /// The value indicator will never be shown.
+  never,
+}
+
+/// Identifier for a thumb.
+///
+/// There are 2 thumbs in a [RangeSlider], [start] and [end].
+///
+/// For [TextDirection.ltr], the [start] thumb is the left-most thumb and the
+/// [end] thumb is the right-most thumb. For [TextDirection.rtl] the [start]
+/// thumb is the right-most thumb, and the [end] thumb is the left-most thumb.
+enum Thumb {
+  /// Left-most thumb for [TextDirection.ltr], otherwise, right-most thumb.
+  start,
+
+  /// Right-most thumb for [TextDirection.ltr], otherwise, left-most thumb.
+  end,
+}
+
+/// Holds the color, shape, and typography values for a material design slider
+/// theme.
+///
+/// Use this class to configure a [SliderTheme] widget, or to set the
+/// [ThemeData.sliderTheme] for a [Theme] widget.
+///
+/// To obtain the current ambient slider theme, use [SliderTheme.of].
+///
+/// This theme is for both the [Slider] and the [RangeSlider]. The properties
+/// that are only for the [Slider] are: [tickMarkShape], [thumbShape],
+/// [trackShape], and [valueIndicatorShape]. The properties that are only for
+/// the [RangeSlider] are [rangeTickMarkShape], [rangeThumbShape],
+/// [rangeTrackShape], [rangeValueIndicatorShape],
+/// [overlappingShapeStrokeColor], [minThumbSeparation], and [thumbSelector].
+/// All other properties are used by both the [Slider] and the [RangeSlider].
+///
+/// The parts of a slider are:
+///
+///  * The "thumb", which is a shape that slides horizontally when the user
+///    drags it.
+///  * The "track", which is the line that the slider thumb slides along.
+///  * The "tick marks", which are regularly spaced marks that are drawn when
+///    using discrete divisions.
+///  * The "value indicator", which appears when the user is dragging the thumb
+///    to indicate the value being selected.
+///  * The "overlay", which appears around the thumb, and is shown when the
+///    thumb is pressed, focused, or hovered. It is painted underneath the
+///    thumb, so it must extend beyond the bounds of the thumb itself to
+///    actually be visible.
+///  * The "active" side of the slider is the side between the thumb and the
+///    minimum value.
+///  * The "inactive" side of the slider is the side between the thumb and the
+///    maximum value.
+///  * The [Slider] is disabled when it is not accepting user input. See
+///    [Slider] for details on when this happens.
+///
+/// The thumb, track, tick marks, value indicator, and overlay can be customized
+/// by creating subclasses of [SliderTrackShape],
+/// [SliderComponentShape], and/or [SliderTickMarkShape]. See
+/// [RoundSliderThumbShape], [RectangularSliderTrackShape],
+/// [RoundSliderTickMarkShape], [RectangularSliderValueIndicatorShape], and
+/// [RoundSliderOverlayShape] for examples.
+///
+/// The track painting can be skipped by specifying 0 for [trackHeight].
+/// The thumb painting can be skipped by specifying
+/// [SliderComponentShape.noThumb] for [SliderThemeData.thumbShape].
+/// The overlay painting can be skipped by specifying
+/// [SliderComponentShape.noOverlay] for [SliderThemeData.overlayShape].
+/// The tick mark painting can be skipped by specifying
+/// [SliderTickMarkShape.noTickMark] for [SliderThemeData.tickMarkShape].
+/// The value indicator painting can be skipped by specifying the
+/// appropriate [ShowValueIndicator] for [SliderThemeData.showValueIndicator].
+///
+/// See also:
+///
+///  * [SliderTheme] widget, which can override the slider theme of its
+///    children.
+///  * [Theme] widget, which performs a similar function to [SliderTheme],
+///    but for overall themes.
+///  * [ThemeData], which has a default [SliderThemeData].
+/// {@macro flutter.material.SliderTheme.sliderComponentShape}
+/// {@macro flutter.material.SliderTheme.sliderTrackShape}
+/// {@macro flutter.material.SliderTheme.sliderTickMarkShape}
+/// {@macro flutter.material.SliderTheme.rangeSliderThumbShape}
+/// {@macro flutter.material.SliderTheme.rangeSliderValueIndicatorShape}
+/// {@macro flutter.material.SliderTheme.rangeSliderTrackShape}
+/// {@macro flutter.material.SliderTheme.rangeSliderTickMarkShape}
+@immutable
+class SliderThemeData with Diagnosticable {
+  /// Create a [SliderThemeData] given a set of exact values.
+  ///
+  /// This will rarely be used directly. It is used by [lerp] to
+  /// create intermediate themes based on two themes.
+  ///
+  /// The simplest way to create a SliderThemeData is to use
+  /// [copyWith] on the one you get from [SliderTheme.of], or create an
+  /// entirely new one with [SliderThemeData.fromPrimaryColors].
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// class Blissful extends StatefulWidget {
+  ///   @override
+  ///   State createState() => BlissfulState();
+  /// }
+  ///
+  /// class BlissfulState extends State<Blissful> {
+  ///   double _bliss = 0;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return SliderTheme(
+  ///       data: SliderTheme.of(context).copyWith(activeTrackColor: const Color(0xff404080)),
+  ///       child: Slider(
+  ///         onChanged: (double value) { setState(() { _bliss = value; }); },
+  ///         value: _bliss,
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  const SliderThemeData({
+    this.trackHeight,
+    this.activeTrackColor,
+    this.inactiveTrackColor,
+    this.disabledActiveTrackColor,
+    this.disabledInactiveTrackColor,
+    this.activeTickMarkColor,
+    this.inactiveTickMarkColor,
+    this.disabledActiveTickMarkColor,
+    this.disabledInactiveTickMarkColor,
+    this.thumbColor,
+    this.overlappingShapeStrokeColor,
+    this.disabledThumbColor,
+    this.overlayColor,
+    this.valueIndicatorColor,
+    this.overlayShape,
+    this.tickMarkShape,
+    this.thumbShape,
+    this.trackShape,
+    this.valueIndicatorShape,
+    this.rangeTickMarkShape,
+    this.rangeThumbShape,
+    this.rangeTrackShape,
+    this.rangeValueIndicatorShape,
+    this.showValueIndicator,
+    this.valueIndicatorTextStyle,
+    this.minThumbSeparation,
+    this.thumbSelector,
+  });
+
+  /// Generates a SliderThemeData from three main colors.
+  ///
+  /// Usually these are the primary, dark and light colors from
+  /// a [ThemeData].
+  ///
+  /// The opacities of these colors will be overridden with the Material Design
+  /// defaults when assigning them to the slider theme component colors.
+  ///
+  /// This is used to generate the default slider theme for a [ThemeData].
+  factory SliderThemeData.fromPrimaryColors({
+    required Color primaryColor,
+    required Color primaryColorDark,
+    required Color primaryColorLight,
+    required TextStyle valueIndicatorTextStyle,
+  }) {
+    assert(primaryColor != null);
+    assert(primaryColorDark != null);
+    assert(primaryColorLight != null);
+    assert(valueIndicatorTextStyle != null);
+
+    // These are Material Design defaults, and are used to derive
+    // component Colors (with opacity) from base colors.
+    const int activeTrackAlpha = 0xff;
+    const int inactiveTrackAlpha = 0x3d; // 24% opacity
+    const int disabledActiveTrackAlpha = 0x52; // 32% opacity
+    const int disabledInactiveTrackAlpha = 0x1f; // 12% opacity
+    const int activeTickMarkAlpha = 0x8a; // 54% opacity
+    const int inactiveTickMarkAlpha = 0x8a; // 54% opacity
+    const int disabledActiveTickMarkAlpha = 0x1f; // 12% opacity
+    const int disabledInactiveTickMarkAlpha = 0x1f; // 12% opacity
+    const int thumbAlpha = 0xff;
+    const int disabledThumbAlpha = 0x52; // 32% opacity
+    const int overlayAlpha = 0x1f; // 12% opacity
+    const int valueIndicatorAlpha = 0xff;
+
+    return SliderThemeData(
+      trackHeight: 2.0,
+      activeTrackColor: primaryColor.withAlpha(activeTrackAlpha),
+      inactiveTrackColor: primaryColor.withAlpha(inactiveTrackAlpha),
+      disabledActiveTrackColor: primaryColorDark.withAlpha(disabledActiveTrackAlpha),
+      disabledInactiveTrackColor: primaryColorDark.withAlpha(disabledInactiveTrackAlpha),
+      activeTickMarkColor: primaryColorLight.withAlpha(activeTickMarkAlpha),
+      inactiveTickMarkColor: primaryColor.withAlpha(inactiveTickMarkAlpha),
+      disabledActiveTickMarkColor: primaryColorLight.withAlpha(disabledActiveTickMarkAlpha),
+      disabledInactiveTickMarkColor: primaryColorDark.withAlpha(disabledInactiveTickMarkAlpha),
+      thumbColor: primaryColor.withAlpha(thumbAlpha),
+      overlappingShapeStrokeColor: Colors.white,
+      disabledThumbColor: primaryColorDark.withAlpha(disabledThumbAlpha),
+      overlayColor: primaryColor.withAlpha(overlayAlpha),
+      valueIndicatorColor: primaryColor.withAlpha(valueIndicatorAlpha),
+      overlayShape: const RoundSliderOverlayShape(),
+      tickMarkShape: const RoundSliderTickMarkShape(),
+      thumbShape: const RoundSliderThumbShape(),
+      trackShape: const RoundedRectSliderTrackShape(),
+      valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
+      rangeTickMarkShape: const RoundRangeSliderTickMarkShape(),
+      rangeThumbShape: const RoundRangeSliderThumbShape(),
+      rangeTrackShape: const RoundedRectRangeSliderTrackShape(),
+      rangeValueIndicatorShape: const PaddleRangeSliderValueIndicatorShape(),
+      valueIndicatorTextStyle: valueIndicatorTextStyle,
+      showValueIndicator: ShowValueIndicator.onlyForDiscrete,
+    );
+  }
+
+  /// The height of the [Slider] track.
+  final double? trackHeight;
+
+  /// The color of the [Slider] track between the [Slider.min] position and the
+  /// current thumb position.
+  final Color? activeTrackColor;
+
+  /// The color of the [Slider] track between the current thumb position and the
+  /// [Slider.max] position.
+  final Color? inactiveTrackColor;
+
+  /// The color of the [Slider] track between the [Slider.min] position and the
+  /// current thumb position when the [Slider] is disabled.
+  final Color? disabledActiveTrackColor;
+
+  /// The color of the [Slider] track between the current thumb position and the
+  /// [Slider.max] position when the [Slider] is disabled.
+  final Color? disabledInactiveTrackColor;
+
+  /// The color of the track's tick marks that are drawn between the [Slider.min]
+  /// position and the current thumb position.
+  final Color? activeTickMarkColor;
+
+  /// The color of the track's tick marks that are drawn between the current
+  /// thumb position and the [Slider.max] position.
+  final Color? inactiveTickMarkColor;
+
+  /// The color of the track's tick marks that are drawn between the [Slider.min]
+  /// position and the current thumb position when the [Slider] is disabled.
+  final Color? disabledActiveTickMarkColor;
+
+  /// The color of the track's tick marks that are drawn between the current
+  /// thumb position and the [Slider.max] position when the [Slider] is
+  /// disabled.
+  final Color? disabledInactiveTickMarkColor;
+
+  /// The color given to the [thumbShape] to draw itself with.
+  final Color? thumbColor;
+
+  /// The color given to the perimeter of the top [rangeThumbShape] when the
+  /// thumbs are overlapping and the top [rangeValueIndicatorShape] when the
+  /// value indicators are overlapping.
+  final Color? overlappingShapeStrokeColor;
+
+  /// The color given to the [thumbShape] to draw itself with when the
+  /// [Slider] is disabled.
+  final Color? disabledThumbColor;
+
+  /// The color of the overlay drawn around the slider thumb when it is
+  /// pressed, focused, or hovered.
+  ///
+  /// This is typically a semi-transparent color.
+  final Color? overlayColor;
+
+  /// The color given to the [valueIndicatorShape] to draw itself with.
+  final Color? valueIndicatorColor;
+
+
+  /// The shape that will be used to draw the [Slider]'s overlay.
+  ///
+  /// Both the [overlayColor] and a non default [overlayShape] may be specified.
+  /// The default [overlayShape] refers to the [overlayColor].
+  ///
+  /// The default value is [RoundSliderOverlayShape].
+  final SliderComponentShape? overlayShape;
+
+  /// The shape that will be used to draw the [Slider]'s tick marks.
+  ///
+  /// The [SliderTickMarkShape.getPreferredSize] is used to help determine the
+  /// location of each tick mark on the track. The slider's minimum size will
+  /// be at least this big.
+  ///
+  /// The default value is [RoundSliderTickMarkShape].
+  ///
+  /// See also:
+  ///
+  ///  * [RoundRangeSliderTickMarkShape], which is the default tick mark
+  ///    shape for the range slider.
+  final SliderTickMarkShape? tickMarkShape;
+
+  /// The shape that will be used to draw the [Slider]'s thumb.
+  ///
+  /// The default value is [RoundSliderThumbShape].
+  ///
+  /// See also:
+  ///
+  ///  * [RoundRangeSliderThumbShape], which is the default thumb shape for
+  ///    the [RangeSlider].
+  final SliderComponentShape? thumbShape;
+
+  /// The shape that will be used to draw the [Slider]'s track.
+  ///
+  /// The [SliderTrackShape.getPreferredRect] method is used to map
+  /// slider-relative gesture coordinates to the correct thumb position on the
+  /// track. It is also used to horizontally position tick marks, when the
+  /// slider is discrete.
+  ///
+  /// The default value is [RoundedRectSliderTrackShape].
+  ///
+  /// See also:
+  ///
+  ///  * [RoundedRectRangeSliderTrackShape], which is the default track
+  ///    shape for the [RangeSlider].
+  final SliderTrackShape? trackShape;
+
+  /// The shape that will be used to draw the [Slider]'s value
+  /// indicator.
+  ///
+  /// The default value is [PaddleSliderValueIndicatorShape].
+  ///
+  /// See also:
+  ///
+  ///  * [PaddleRangeSliderValueIndicatorShape], which is the default value
+  ///    indicator shape for the [RangeSlider].
+  final SliderComponentShape? valueIndicatorShape;
+
+  /// The shape that will be used to draw the [RangeSlider]'s tick marks.
+  ///
+  /// The [RangeSliderTickMarkShape.getPreferredSize] is used to help determine
+  /// the location of each tick mark on the track. The slider's minimum size
+  /// will be at least this big.
+  ///
+  /// The default value is [RoundRangeSliderTickMarkShape].
+  ///
+  /// See also:
+  ///
+  ///  * [RoundSliderTickMarkShape], which is the default tick mark shape
+  ///    for the [Slider].
+  final RangeSliderTickMarkShape? rangeTickMarkShape;
+
+  /// The shape that will be used for the [RangeSlider]'s thumbs.
+  ///
+  /// By default the same shape is used for both thumbs, but strokes the top
+  /// thumb when it overlaps the bottom thumb. The top thumb is always the last
+  /// selected thumb.
+  ///
+  /// The default value is [RoundRangeSliderThumbShape].
+  ///
+  /// See also:
+  ///
+  ///  * [RoundSliderThumbShape], which is the default thumb shape for the
+  ///    [Slider].
+  final RangeSliderThumbShape? rangeThumbShape;
+
+  /// The shape that will be used to draw the [RangeSlider]'s track.
+  ///
+  /// The [SliderTrackShape.getPreferredRect] method is used to map
+  /// slider-relative gesture coordinates to the correct thumb position on the
+  /// track. It is also used to horizontally position the tick marks, when the
+  /// slider is discrete.
+  ///
+  /// The default value is [RoundedRectRangeSliderTrackShape].
+  ///
+  /// See also:
+  ///
+  ///  * [RoundedRectSliderTrackShape], which is the default track
+  ///    shape for the [Slider].
+  final RangeSliderTrackShape? rangeTrackShape;
+
+  /// The shape that will be used for the [RangeSlider]'s value indicators.
+  ///
+  /// The default shape uses the same value indicator for each thumb, but
+  /// strokes the top value indicator when it overlaps the bottom value
+  /// indicator. The top indicator corresponds to the top thumb, which is always
+  /// the most recently selected thumb.
+  ///
+  /// The default value is [PaddleRangeSliderValueIndicatorShape].
+  ///
+  /// See also:
+  ///
+  ///  * [PaddleSliderValueIndicatorShape], which is the default value
+  ///    indicator shape for the [Slider].
+  final RangeSliderValueIndicatorShape? rangeValueIndicatorShape;
+
+  /// Whether the value indicator should be shown for different types of
+  /// sliders.
+  ///
+  /// By default, [showValueIndicator] is set to
+  /// [ShowValueIndicator.onlyForDiscrete]. The value indicator is only shown
+  /// when the thumb is being touched.
+  final ShowValueIndicator? showValueIndicator;
+
+  /// The text style for the text on the value indicator.
+  final TextStyle? valueIndicatorTextStyle;
+
+  /// Limits the thumb's separation distance.
+  ///
+  /// Use this only if you want to control the visual appearance of the thumbs
+  /// in terms of a logical pixel value. This can be done when you want a
+  /// specific look for thumbs when they are close together. To limit with the
+  /// real values, rather than logical pixels, the values can be restricted by
+  /// the parent.
+  final double? minThumbSeparation;
+
+  /// Determines which thumb should be selected when the slider is interacted
+  /// with.
+  ///
+  /// If null, the default thumb selector finds the closest thumb, excluding
+  /// taps that are between the thumbs and not within any one touch target.
+  /// When the selection is within the touch target bounds of both thumbs, no
+  /// thumb is selected until the selection is moved.
+  ///
+  /// Override this for custom thumb selection.
+  final RangeThumbSelector? thumbSelector;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  SliderThemeData copyWith({
+    double? trackHeight,
+    Color? activeTrackColor,
+    Color? inactiveTrackColor,
+    Color? disabledActiveTrackColor,
+    Color? disabledInactiveTrackColor,
+    Color? activeTickMarkColor,
+    Color? inactiveTickMarkColor,
+    Color? disabledActiveTickMarkColor,
+    Color? disabledInactiveTickMarkColor,
+    Color? thumbColor,
+    Color? overlappingShapeStrokeColor,
+    Color? disabledThumbColor,
+    Color? overlayColor,
+    Color? valueIndicatorColor,
+    SliderComponentShape? overlayShape,
+    SliderTickMarkShape? tickMarkShape,
+    SliderComponentShape? thumbShape,
+    SliderTrackShape? trackShape,
+    SliderComponentShape? valueIndicatorShape,
+    RangeSliderTickMarkShape? rangeTickMarkShape,
+    RangeSliderThumbShape? rangeThumbShape,
+    RangeSliderTrackShape? rangeTrackShape,
+    RangeSliderValueIndicatorShape? rangeValueIndicatorShape,
+    ShowValueIndicator? showValueIndicator,
+    TextStyle? valueIndicatorTextStyle,
+    double? minThumbSeparation,
+    RangeThumbSelector? thumbSelector,
+  }) {
+    return SliderThemeData(
+      trackHeight: trackHeight ?? this.trackHeight,
+      activeTrackColor: activeTrackColor ?? this.activeTrackColor,
+      inactiveTrackColor: inactiveTrackColor ?? this.inactiveTrackColor,
+      disabledActiveTrackColor: disabledActiveTrackColor ?? this.disabledActiveTrackColor,
+      disabledInactiveTrackColor: disabledInactiveTrackColor ?? this.disabledInactiveTrackColor,
+      activeTickMarkColor: activeTickMarkColor ?? this.activeTickMarkColor,
+      inactiveTickMarkColor: inactiveTickMarkColor ?? this.inactiveTickMarkColor,
+      disabledActiveTickMarkColor: disabledActiveTickMarkColor ?? this.disabledActiveTickMarkColor,
+      disabledInactiveTickMarkColor: disabledInactiveTickMarkColor ?? this.disabledInactiveTickMarkColor,
+      thumbColor: thumbColor ?? this.thumbColor,
+      overlappingShapeStrokeColor: overlappingShapeStrokeColor ?? this.overlappingShapeStrokeColor,
+      disabledThumbColor: disabledThumbColor ?? this.disabledThumbColor,
+      overlayColor: overlayColor ?? this.overlayColor,
+      valueIndicatorColor: valueIndicatorColor ?? this.valueIndicatorColor,
+      overlayShape: overlayShape ?? this.overlayShape,
+      tickMarkShape: tickMarkShape ?? this.tickMarkShape,
+      thumbShape: thumbShape ?? this.thumbShape,
+      trackShape: trackShape ?? this.trackShape,
+      valueIndicatorShape: valueIndicatorShape ?? this.valueIndicatorShape,
+      rangeTickMarkShape: rangeTickMarkShape ?? this.rangeTickMarkShape,
+      rangeThumbShape: rangeThumbShape ?? this.rangeThumbShape,
+      rangeTrackShape: rangeTrackShape ?? this.rangeTrackShape,
+      rangeValueIndicatorShape: rangeValueIndicatorShape ?? this.rangeValueIndicatorShape,
+      showValueIndicator: showValueIndicator ?? this.showValueIndicator,
+      valueIndicatorTextStyle: valueIndicatorTextStyle ?? this.valueIndicatorTextStyle,
+      minThumbSeparation: minThumbSeparation ?? this.minThumbSeparation,
+      thumbSelector: thumbSelector ?? this.thumbSelector,
+    );
+  }
+
+  /// Linearly interpolate between two slider themes.
+  ///
+  /// The arguments must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static SliderThemeData lerp(SliderThemeData a, SliderThemeData b, double t) {
+    assert(a != null);
+    assert(b != null);
+    assert(t != null);
+    return SliderThemeData(
+      trackHeight: lerpDouble(a.trackHeight, b.trackHeight, t),
+      activeTrackColor: Color.lerp(a.activeTrackColor, b.activeTrackColor, t),
+      inactiveTrackColor: Color.lerp(a.inactiveTrackColor, b.inactiveTrackColor, t),
+      disabledActiveTrackColor: Color.lerp(a.disabledActiveTrackColor, b.disabledActiveTrackColor, t),
+      disabledInactiveTrackColor: Color.lerp(a.disabledInactiveTrackColor, b.disabledInactiveTrackColor, t),
+      activeTickMarkColor: Color.lerp(a.activeTickMarkColor, b.activeTickMarkColor, t),
+      inactiveTickMarkColor: Color.lerp(a.inactiveTickMarkColor, b.inactiveTickMarkColor, t),
+      disabledActiveTickMarkColor: Color.lerp(a.disabledActiveTickMarkColor, b.disabledActiveTickMarkColor, t),
+      disabledInactiveTickMarkColor: Color.lerp(a.disabledInactiveTickMarkColor, b.disabledInactiveTickMarkColor, t),
+      thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t),
+      overlappingShapeStrokeColor: Color.lerp(a.overlappingShapeStrokeColor, b.overlappingShapeStrokeColor, t),
+      disabledThumbColor: Color.lerp(a.disabledThumbColor, b.disabledThumbColor, t),
+      overlayColor: Color.lerp(a.overlayColor, b.overlayColor, t),
+      valueIndicatorColor: Color.lerp(a.valueIndicatorColor, b.valueIndicatorColor, t),
+      overlayShape: t < 0.5 ? a.overlayShape : b.overlayShape,
+      tickMarkShape: t < 0.5 ? a.tickMarkShape : b.tickMarkShape,
+      thumbShape: t < 0.5 ? a.thumbShape : b.thumbShape,
+      trackShape: t < 0.5 ? a.trackShape : b.trackShape,
+      valueIndicatorShape: t < 0.5 ? a.valueIndicatorShape : b.valueIndicatorShape,
+      rangeTickMarkShape: t < 0.5 ? a.rangeTickMarkShape : b.rangeTickMarkShape,
+      rangeThumbShape: t < 0.5 ? a.rangeThumbShape : b.rangeThumbShape,
+      rangeTrackShape: t < 0.5 ? a.rangeTrackShape : b.rangeTrackShape,
+      rangeValueIndicatorShape: t < 0.5 ? a.rangeValueIndicatorShape : b.rangeValueIndicatorShape,
+      showValueIndicator: t < 0.5 ? a.showValueIndicator : b.showValueIndicator,
+      valueIndicatorTextStyle: TextStyle.lerp(a.valueIndicatorTextStyle, b.valueIndicatorTextStyle, t),
+      minThumbSeparation: lerpDouble(a.minThumbSeparation, b.minThumbSeparation, t),
+      thumbSelector: t < 0.5 ? a.thumbSelector : b.thumbSelector,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashList(<Object?>[
+      trackHeight,
+      activeTrackColor,
+      inactiveTrackColor,
+      disabledActiveTrackColor,
+      disabledInactiveTrackColor,
+      activeTickMarkColor,
+      inactiveTickMarkColor,
+      disabledActiveTickMarkColor,
+      disabledInactiveTickMarkColor,
+      thumbColor,
+      overlappingShapeStrokeColor,
+      disabledThumbColor,
+      overlayColor,
+      valueIndicatorColor,
+      overlayShape,
+      tickMarkShape,
+      thumbShape,
+      trackShape,
+      valueIndicatorShape,
+      rangeTickMarkShape,
+      rangeThumbShape,
+      rangeTrackShape,
+      rangeValueIndicatorShape,
+      showValueIndicator,
+      valueIndicatorTextStyle,
+      minThumbSeparation,
+      thumbSelector,
+    ]);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is SliderThemeData
+        && other.trackHeight == trackHeight
+        && other.activeTrackColor == activeTrackColor
+        && other.inactiveTrackColor == inactiveTrackColor
+        && other.disabledActiveTrackColor == disabledActiveTrackColor
+        && other.disabledInactiveTrackColor == disabledInactiveTrackColor
+        && other.activeTickMarkColor == activeTickMarkColor
+        && other.inactiveTickMarkColor == inactiveTickMarkColor
+        && other.disabledActiveTickMarkColor == disabledActiveTickMarkColor
+        && other.disabledInactiveTickMarkColor == disabledInactiveTickMarkColor
+        && other.thumbColor == thumbColor
+        && other.overlappingShapeStrokeColor == overlappingShapeStrokeColor
+        && other.disabledThumbColor == disabledThumbColor
+        && other.overlayColor == overlayColor
+        && other.valueIndicatorColor == valueIndicatorColor
+        && other.overlayShape == overlayShape
+        && other.tickMarkShape == tickMarkShape
+        && other.thumbShape == thumbShape
+        && other.trackShape == trackShape
+        && other.valueIndicatorShape == valueIndicatorShape
+        && other.rangeTickMarkShape == rangeTickMarkShape
+        && other.rangeThumbShape == rangeThumbShape
+        && other.rangeTrackShape == rangeTrackShape
+        && other.rangeValueIndicatorShape == rangeValueIndicatorShape
+        && other.showValueIndicator == showValueIndicator
+        && other.valueIndicatorTextStyle == valueIndicatorTextStyle
+        && other.minThumbSeparation == minThumbSeparation
+        && other.thumbSelector == thumbSelector;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    const SliderThemeData defaultData = SliderThemeData();
+    properties.add(DoubleProperty('trackHeight', trackHeight, defaultValue: defaultData.trackHeight));
+    properties.add(ColorProperty('activeTrackColor', activeTrackColor, defaultValue: defaultData.activeTrackColor));
+    properties.add(ColorProperty('inactiveTrackColor', inactiveTrackColor, defaultValue: defaultData.inactiveTrackColor));
+    properties.add(ColorProperty('disabledActiveTrackColor', disabledActiveTrackColor, defaultValue: defaultData.disabledActiveTrackColor));
+    properties.add(ColorProperty('disabledInactiveTrackColor', disabledInactiveTrackColor, defaultValue: defaultData.disabledInactiveTrackColor));
+    properties.add(ColorProperty('activeTickMarkColor', activeTickMarkColor, defaultValue: defaultData.activeTickMarkColor));
+    properties.add(ColorProperty('inactiveTickMarkColor', inactiveTickMarkColor, defaultValue: defaultData.inactiveTickMarkColor));
+    properties.add(ColorProperty('disabledActiveTickMarkColor', disabledActiveTickMarkColor, defaultValue: defaultData.disabledActiveTickMarkColor));
+    properties.add(ColorProperty('disabledInactiveTickMarkColor', disabledInactiveTickMarkColor, defaultValue: defaultData.disabledInactiveTickMarkColor));
+    properties.add(ColorProperty('thumbColor', thumbColor, defaultValue: defaultData.thumbColor));
+    properties.add(ColorProperty('overlappingShapeStrokeColor', overlappingShapeStrokeColor, defaultValue: defaultData.overlappingShapeStrokeColor));
+    properties.add(ColorProperty('disabledThumbColor', disabledThumbColor, defaultValue: defaultData.disabledThumbColor));
+    properties.add(ColorProperty('overlayColor', overlayColor, defaultValue: defaultData.overlayColor));
+    properties.add(ColorProperty('valueIndicatorColor', valueIndicatorColor, defaultValue: defaultData.valueIndicatorColor));
+    properties.add(DiagnosticsProperty<SliderComponentShape>('overlayShape', overlayShape, defaultValue: defaultData.overlayShape));
+    properties.add(DiagnosticsProperty<SliderTickMarkShape>('tickMarkShape', tickMarkShape, defaultValue: defaultData.tickMarkShape));
+    properties.add(DiagnosticsProperty<SliderComponentShape>('thumbShape', thumbShape, defaultValue: defaultData.thumbShape));
+    properties.add(DiagnosticsProperty<SliderTrackShape>('trackShape', trackShape, defaultValue: defaultData.trackShape));
+    properties.add(DiagnosticsProperty<SliderComponentShape>('valueIndicatorShape', valueIndicatorShape, defaultValue: defaultData.valueIndicatorShape));
+    properties.add(DiagnosticsProperty<RangeSliderTickMarkShape>('rangeTickMarkShape', rangeTickMarkShape, defaultValue: defaultData.rangeTickMarkShape));
+    properties.add(DiagnosticsProperty<RangeSliderThumbShape>('rangeThumbShape', rangeThumbShape, defaultValue: defaultData.rangeThumbShape));
+    properties.add(DiagnosticsProperty<RangeSliderTrackShape>('rangeTrackShape', rangeTrackShape, defaultValue: defaultData.rangeTrackShape));
+    properties.add(DiagnosticsProperty<RangeSliderValueIndicatorShape>('rangeValueIndicatorShape', rangeValueIndicatorShape, defaultValue: defaultData.rangeValueIndicatorShape));
+    properties.add(EnumProperty<ShowValueIndicator>('showValueIndicator', showValueIndicator, defaultValue: defaultData.showValueIndicator));
+    properties.add(DiagnosticsProperty<TextStyle>('valueIndicatorTextStyle', valueIndicatorTextStyle, defaultValue: defaultData.valueIndicatorTextStyle));
+    properties.add(DoubleProperty('minThumbSeparation', minThumbSeparation, defaultValue: defaultData.minThumbSeparation));
+    properties.add(DiagnosticsProperty<RangeThumbSelector>('thumbSelector', thumbSelector, defaultValue: defaultData.thumbSelector));
+  }
+}
+
+/// Base class for slider thumb, thumb overlay, and value indicator shapes.
+///
+/// Create a subclass of this if you would like a custom shape.
+///
+/// All shapes are painted to the same canvas and ordering is important.
+/// The overlay is painted first, then the value indicator, then the thumb.
+///
+/// The thumb painting can be skipped by specifying [noThumb] for
+/// [SliderThemeData.thumbShape].
+///
+/// The overlay painting can be skipped by specifying [noOverlay] for
+/// [SliderThemeData.overlayShape].
+///
+/// See also:
+///
+///  * [RoundSliderThumbShape], which is the default [Slider]'s thumb shape that
+///    paints a solid circle.
+///  * [RoundSliderOverlayShape], which is the default [Slider] and
+///    [RangeSlider]'s overlay shape that paints a transparent circle.
+///  * [PaddleSliderValueIndicatorShape], which is the default [Slider]'s value
+///    indicator shape that paints a custom path with text in it.
+abstract class SliderComponentShape {
+  /// This abstract const constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const SliderComponentShape();
+
+  /// Returns the preferred size of the shape, based on the given conditions.
+  Size getPreferredSize(bool isEnabled, bool isDiscrete);
+
+  /// Paints the shape, taking into account the state passed to it.
+  ///
+  /// {@template flutter.material.SliderComponentShape.paint.context}
+  /// The `context` argument is the same as the one that includes the [Slider]'s
+  /// render box.
+  /// {@endtemplate}
+  ///
+  /// {@template flutter.material.SliderComponentShape.paint.center}
+  /// The `center` argument is the offset for where this shape's center should be
+  /// painted. This offset is relative to the origin of the [context] canvas.
+  /// {@endtemplate}
+  ///
+  /// The `activationAnimation` argument is an animation triggered when the user
+  /// begins to interact with the slider. It reverses when the user stops interacting
+  /// with the slider.
+  ///
+  /// {@template flutter.material.SliderComponentShape.paint.enableAnimation}
+  /// The `enableAnimation` argument is an animation triggered when the [Slider]
+  /// is enabled, and it reverses when the slider is disabled. The [Slider] is
+  /// enabled when [Slider.onChanged] is not null.Use this to paint intermediate
+  /// frames for this shape when the slider changes enabled state.
+  /// {@endtemplate}
+  ///
+  /// {@template flutter.material.SliderComponentShape.paint.isDiscrete}
+  /// The `isDiscrete` argument is true if [Slider.divisions] is non-null. When
+  /// true, the slider will render tick marks on top of the track.
+  /// {@endtemplate}
+  ///
+  /// If the `labelPainter` argument is non-null, then [TextPainter.paint]
+  /// should be called on the `labelPainter` with the location that the label
+  /// should appear. If the `labelPainter` argument is null, then no label was
+  /// supplied to the [Slider].
+  ///
+  /// {@template flutter.material.SliderComponentShape.paint.parentBox}
+  /// The `parentBox` argument is the [RenderBox] of the [Slider]. Its attributes,
+  /// such as size, can be used to assist in painting this shape.
+  /// {@endtemplate}
+  ///
+  /// {@template flutter.material.SliderComponentShape.paint.sliderTheme}
+  /// the `sliderTheme` argument is the theme assigned to the [Slider] that this
+  /// shape belongs to.
+  /// {@endtemplate}
+  ///
+  /// The `textDirection` argument can be used to determine how any extra text
+  /// or graphics (besides the text painted by the `labelPainter`) should be
+  /// positioned. The `labelPainter` already has the [textDirection] set.
+  ///
+  /// The `value` argument is the current parametric value (from 0.0 to 1.0) of
+  /// the slider.
+  ///
+  /// {@template flutter.material.SliderComponentShape.paint.textScaleFactor}
+  /// The `textScaleFactor` argument can be used to determine whether the
+  /// component should paint larger or smaller, depending on whether
+  /// [textScaleFactor] is greater than 1 for larger, and between 0 and 1 for
+  /// smaller. It usually comes from [MediaQueryData.textScaleFactor].
+  /// {@endtemplate}
+  ///
+  /// {@template flutter.material.SliderComponentShape.paint.sizeWithOverflow}
+  /// The `sizeWithOverflow` argument can be used to determine the bounds the
+  /// drawing of the components that are outside of the regular slider bounds.
+  /// It's the size of the box, whose center is aligned with the slider's
+  /// bounds, that the value indicators must be drawn within. Typically, it is
+  /// bigger than the slider.
+  /// {@endtemplate}
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required Animation<double> activationAnimation,
+    required Animation<double> enableAnimation,
+    required bool isDiscrete,
+    required TextPainter labelPainter,
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required TextDirection textDirection,
+    required double value,
+    required double textScaleFactor,
+    required Size sizeWithOverflow,
+  });
+
+  /// Special instance of [SliderComponentShape] to skip the thumb drawing.
+  ///
+  /// See also:
+  ///
+  ///  * [SliderThemeData.thumbShape], which is the shape that the [Slider]
+  ///    uses when painting the thumb.
+  static final SliderComponentShape noThumb = _EmptySliderComponentShape();
+
+  /// Special instance of [SliderComponentShape] to skip the overlay drawing.
+  ///
+  /// See also:
+  ///
+  ///  * [SliderThemeData.overlayShape], which is the shape that the [Slider]
+  ///    uses when painting the overlay.
+  static final SliderComponentShape noOverlay = _EmptySliderComponentShape();
+}
+
+/// Base class for [Slider] tick mark shapes.
+///
+/// Create a subclass of this if you would like a custom slider tick mark shape.
+///
+/// The tick mark painting can be skipped by specifying [noTickMark] for
+/// [SliderThemeData.tickMarkShape].
+///
+/// See also:
+///
+///  * [RoundSliderTickMarkShape], which is the default [Slider]'s tick mark
+///    shape that paints a solid circle.
+/// {@macro flutter.material.SliderTheme.sliderTrackShape}
+/// {@macro flutter.material.SliderTheme.sliderComponentShape}
+abstract class SliderTickMarkShape {
+  /// This abstract const constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const SliderTickMarkShape();
+
+  /// Returns the preferred size of the shape.
+  ///
+  /// It is used to help position the tick marks within the slider.
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.sliderTheme}
+  ///
+  /// {@template flutter.material.SliderTickMarkShape.getPreferredSize.isEnabled}
+  /// The `isEnabled` argument is false when [Slider.onChanged] is null and true
+  /// otherwise. When true, the slider will respond to input.
+  /// {@endtemplate}
+  Size getPreferredSize({
+    required SliderThemeData sliderTheme,
+    required bool isEnabled,
+  });
+
+  /// Paints the slider track.
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.context}
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.center}
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.parentBox}
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.sliderTheme}
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.enableAnimation}
+  ///
+  /// {@macro flutter.material.SliderTickMarkShape.getPreferredSize.isEnabled}
+  ///
+  /// The `textDirection` argument can be used to determine how the tick marks
+  /// are painting depending on whether they are on an active track segment or
+  /// not. The track segment between the start of the slider and the thumb is
+  /// the active track segment. The track segment between the thumb and the end
+  /// of the slider is the inactive track segment. In LTR text direction, the
+  /// start of the slider is on the left, and in RTL text direction, the start
+  /// of the slider is on the right.
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required Animation<double> enableAnimation,
+    required Offset thumbCenter,
+    required bool isEnabled,
+    required TextDirection textDirection,
+  });
+
+  /// Special instance of [SliderTickMarkShape] to skip the tick mark painting.
+  ///
+  /// See also:
+  ///
+  ///  * [SliderThemeData.tickMarkShape], which is the shape that the [Slider]
+  ///    uses when painting tick marks.
+  static final SliderTickMarkShape noTickMark = _EmptySliderTickMarkShape();
+}
+
+/// Base class for slider track shapes.
+///
+/// The slider's thumb moves along the track. A discrete slider's tick marks
+/// are drawn after the track, but before the thumb, and are aligned with the
+/// track.
+///
+/// The [getPreferredRect] helps position the slider thumb and tick marks
+/// relative to the track.
+///
+/// See also:
+///
+///  * [RoundedRectSliderTrackShape] for the default [Slider]'s track shape that
+///    paints a stadium-like track.
+/// {@macro flutter.material.SliderTheme.sliderTickMarkShape}
+/// {@macro flutter.material.SliderTheme.sliderComponentShape}
+abstract class SliderTrackShape {
+  /// This abstract const constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const SliderTrackShape();
+
+  /// Returns the preferred bounds of the shape.
+  ///
+  /// It is used to provide horizontal boundaries for the thumb's position, and
+  /// to help position the slider thumb and tick marks relative to the track.
+  ///
+  /// The `parentBox` argument can be used to help determine the preferredRect relative to
+  /// attributes of the render box of the slider itself, such as size.
+  ///
+  /// The `offset` argument is relative to the caller's bounding box. It can be used to
+  /// convert gesture coordinates from global to slider-relative coordinates.
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.sliderTheme}
+  ///
+  /// {@macro flutter.material.SliderTickMarkShape.getPreferredSize.isEnabled}
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.isDiscrete}
+  Rect getPreferredRect({
+    required RenderBox parentBox,
+    Offset offset = Offset.zero,
+    required SliderThemeData sliderTheme,
+    bool isEnabled,
+    bool isDiscrete,
+  });
+
+  /// Paints the track shape based on the state passed to it.
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.context}
+  ///
+  /// The `offset` argument the offset of the origin of the `parentBox` to the
+  /// origin of its `context` canvas. This shape must be painted relative to
+  /// this offset. See [PaintingContextCallback].
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.parentBox}
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.sliderTheme}
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.enableAnimation}
+  ///
+  /// The `thumbCenter` argument is the offset of the center of the thumb
+  /// relative to the origin of the [PaintingContext.canvas]. It can be used as
+  /// the point that divides the track into 2 segments.
+  ///
+  /// {@macro flutter.material.SliderTickMarkShape.getPreferredSize.isEnabled}
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.isDiscrete}
+  ///
+  /// The `textDirection` argument can be used to determine how the track
+  /// segments are painted depending on whether they are active or not.
+  ///
+  /// {@template flutter.material.SliderTrackShape.paint.trackSegment}
+  /// The track segment between the start of the slider and the thumb is the
+  /// active track segment. The track segment between the thumb and the end of the
+  /// slider is the inactive track segment. In [TextDirection.ltr], the start of
+  /// the slider is on the left, and in [TextDirection.rtl], the start of the
+  /// slider is on the right.
+  /// {@endtemplate}
+  void paint(
+    PaintingContext context,
+    Offset offset, {
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required Animation<double> enableAnimation,
+    required Offset thumbCenter,
+    bool isEnabled,
+    bool isDiscrete,
+    required TextDirection textDirection,
+  });
+}
+
+/// Base class for [RangeSlider] thumb shapes.
+///
+/// See also:
+///
+///  * [RoundRangeSliderThumbShape] for the default [RangeSlider]'s thumb shape
+///    that paints a solid circle.
+/// {@macro flutter.material.SliderTheme.rangeSliderTickMarkShape}
+/// {@macro flutter.material.SliderTheme.rangeSliderTrackShape}
+/// {@macro flutter.material.SliderTheme.rangeSliderValueIndicatorShape}
+/// {@macro flutter.material.SliderTheme.sliderComponentShape}
+abstract class RangeSliderThumbShape {
+  /// This abstract const constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const RangeSliderThumbShape();
+
+  /// Returns the preferred size of the shape, based on the given conditions.
+  ///
+  /// {@template flutter.material.RangeSliderThumbShape.getPreferredSize.isDiscrete}
+  /// The `isDiscrete` argument is true if [RangeSlider.divisions] is non-null.
+  /// When true, the slider will render tick marks on top of the track.
+  /// {@endtemplate}
+  ///
+  /// {@template flutter.material.RangeSliderThumbShape.getPreferredSize.isEnabled}
+  /// The `isEnabled` argument is false when [RangeSlider.onChanged] is null and
+  /// true otherwise. When true, the slider will respond to input.
+  /// {@endtemplate}
+  Size getPreferredSize(bool isEnabled, bool isDiscrete);
+
+  /// Paints the thumb shape based on the state passed to it.
+  ///
+  /// {@template flutter.material.RangeSliderThumbShape.paint.context}
+  /// The `context` argument represents the [RangeSlider]'s render box.
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.center}
+  ///
+  /// {@template flutter.material.RangeSliderThumbShape.paint.activationAnimation}
+  /// The `activationAnimation` argument is an animation triggered when the user
+  /// begins to interact with the [RangeSlider]. It reverses when the user stops
+  /// interacting with the slider.
+  /// {@endtemplate}
+  ///
+  /// {@template flutter.material.RangeSliderThumbShape.paint.enableAnimation}
+  /// The `enableAnimation` argument is an animation triggered when the
+  /// [RangeSlider] is enabled, and it reverses when the slider is disabled. The
+  /// [RangeSlider] is enabled when [RangeSlider.onChanged] is not null. Use
+  /// this to paint intermediate frames for this shape when the slider changes
+  /// enabled state.
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isDiscrete}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isEnabled}
+  ///
+  /// If the `isOnTop` argument is true, this thumb is painted on top of the
+  /// other slider thumb because this thumb is the one that was most recently
+  /// selected.
+  ///
+  /// {@template flutter.material.RangeSliderThumbShape.paint.sliderTheme}
+  /// The `sliderTheme` argument is the theme assigned to the [RangeSlider] that
+  /// this shape belongs to.
+  /// {@endtemplate}
+  ///
+  /// The `textDirection` argument can be used to determine how the orientation
+  /// of either slider thumb should be changed, such as drawing different
+  /// shapes for the left and right thumb.
+  ///
+  /// {@template flutter.material.RangeSliderThumbShape.paint.thumb}
+  /// The `thumb` argument is the specifier for which of the two thumbs this
+  /// method should paint (start or end).
+  /// {@endtemplate}
+  ///
+  /// The `isPressed` argument can be used to give the selected thumb
+  /// additional selected or pressed state visual feedback, such as a larger
+  /// shadow.
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required Animation<double> activationAnimation,
+    required Animation<double> enableAnimation,
+    bool isDiscrete,
+    bool isEnabled,
+    bool isOnTop,
+    TextDirection textDirection,
+    required SliderThemeData sliderTheme,
+    Thumb thumb,
+    bool isPressed,
+  });
+}
+
+/// Base class for [RangeSlider] value indicator shapes.
+///
+/// See also:
+///
+///  * [PaddleRangeSliderValueIndicatorShape] for the default [RangeSlider]'s
+///    value indicator shape that paints a custom path with text in it.
+/// {@macro flutter.material.SliderTheme.rangeSliderTickMarkShape}
+/// {@macro flutter.material.SliderTheme.rangeSliderThumbShape}
+/// {@macro flutter.material.SliderTheme.rangeSliderTrackShape}
+/// {@macro flutter.material.SliderTheme.sliderComponentShape}
+abstract class RangeSliderValueIndicatorShape {
+  /// This abstract const constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const RangeSliderValueIndicatorShape();
+
+  /// Returns the preferred size of the shape, based on the given conditions.
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isEnabled}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isDiscrete}
+  ///
+  /// The `labelPainter` argument helps determine the width of the shape. It is
+  /// variable width because it is derived from a formatted string.
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.textScaleFactor}
+  Size getPreferredSize(
+    bool isEnabled,
+    bool isDiscrete, {
+    required TextPainter labelPainter,
+    required double textScaleFactor,
+  });
+
+  /// Determines the best offset to keep this shape on the screen.
+  ///
+  /// Override this method when the center of the value indicator should be
+  /// shifted from the vertical center of the thumb.
+  double getHorizontalShift({
+    RenderBox? parentBox,
+    Offset? center,
+    TextPainter? labelPainter,
+    Animation<double>? activationAnimation,
+    double? textScaleFactor,
+    Size? sizeWithOverflow,
+  }) {
+    return 0;
+  }
+
+  /// Paints the value indicator shape based on the state passed to it.
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.paint.context}
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.center}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.paint.activationAnimation}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.paint.enableAnimation}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isDiscrete}
+  ///
+  /// The `isOnTop` argument is the top-most value indicator between the two value
+  /// indicators, which is always the indicator for the most recently selected thumb. In
+  /// the default case, this is used to paint a stroke around the top indicator
+  /// for better visibility between the two indicators.
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.textScaleFactor}
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.sizeWithOverflow}
+  ///
+  /// {@template flutter.material.RangeSliderValueIndicatorShape.paint.parentBox}
+  /// The `parentBox` argument is the [RenderBox] of the [RangeSlider]. Its
+  /// attributes, such as size, can be used to assist in painting this shape.
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.paint.sliderTheme}
+  ///
+  /// The `textDirection` argument can be used to determine how any extra text
+  /// or graphics, besides the text painted by the [labelPainter] should be
+  /// positioned. The `labelPainter` argument already has the `textDirection`
+  /// set.
+  ///
+  /// The `value` argument is the current parametric value (from 0.0 to 1.0) of
+  /// the slider.
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.paint.thumb}
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required Animation<double> activationAnimation,
+    required Animation<double> enableAnimation,
+    bool isDiscrete,
+    bool isOnTop,
+    required TextPainter labelPainter,
+    double textScaleFactor,
+    Size sizeWithOverflow,
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    TextDirection textDirection,
+    double value,
+    Thumb thumb,
+  });
+}
+
+/// Base class for [RangeSlider] tick mark shapes.
+///
+/// This is a simplified version of [SliderComponentShape] with a
+/// [SliderThemeData] passed when getting the preferred size.
+///
+/// See also:
+///
+///  * [RoundRangeSliderTickMarkShape] for the default [RangeSlider]'s tick mark
+///    shape that paints a solid circle.
+/// {@macro flutter.material.SliderTheme.rangeSliderThumbShape}
+/// {@macro flutter.material.SliderTheme.rangeSliderTrackShape}
+/// {@macro flutter.material.SliderTheme.rangeSliderValueIndicatorShape}
+/// {@macro flutter.material.SliderTheme.sliderComponentShape}
+abstract class RangeSliderTickMarkShape {
+  /// This abstract const constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const RangeSliderTickMarkShape();
+
+  /// Returns the preferred size of the shape.
+  ///
+  /// It is used to help position the tick marks within the slider.
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.paint.sliderTheme}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isEnabled}
+  Size getPreferredSize({
+    required SliderThemeData sliderTheme,
+    bool isEnabled,
+  });
+
+  /// Paints the slider track.
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.paint.context}
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.center}
+  ///
+  /// {@macro flutter.material.RangeSliderValueIndicatorShape.paint.parentBox}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.paint.sliderTheme}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.paint.enableAnimation}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isEnabled}
+  ///
+  /// The `textDirection` argument can be used to determine how the tick marks
+  /// are painted depending on whether they are on an active track segment or not.
+  ///
+  /// {@template flutter.material.RangeSliderTickMarkShape.paint.trackSegment}
+  /// The track segment between the two thumbs is the active track segment. The
+  /// track segments between the thumb and each end of the slider are the inactive
+  /// track segments. In [TextDirection.ltr], the start of the slider is on the
+  /// left, and in [TextDirection.rtl], the start of the slider is on the right.
+  /// {@endtemplate}
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required Animation<double> enableAnimation,
+    required Offset startThumbCenter,
+    required Offset endThumbCenter,
+    bool isEnabled,
+    required TextDirection textDirection,
+  });
+}
+
+/// Base class for [RangeSlider] track shapes.
+///
+/// The slider's thumbs move along the track. A discrete slider's tick marks
+/// are drawn after the track, but before the thumb, and are aligned with the
+/// track.
+///
+/// The [getPreferredRect] helps position the slider thumbs and tick marks
+/// relative to the track.
+///
+/// See also:
+///
+///  * [RoundedRectRangeSliderTrackShape] for the default [RangeSlider]'s track
+///    shape that paints a stadium-like track.
+/// {@macro flutter.material.SliderTheme.rangeSliderTickMarkShape}
+/// {@macro flutter.material.SliderTheme.rangeSliderThumbShape}
+/// {@macro flutter.material.SliderTheme.rangeSliderValueIndicatorShape}
+/// {@macro flutter.material.SliderTheme.sliderComponentShape}
+abstract class RangeSliderTrackShape {
+  /// This abstract const constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const RangeSliderTrackShape();
+
+  /// Returns the preferred bounds of the shape.
+  ///
+  /// It is used to provide horizontal boundaries for the position of the
+  /// thumbs, and to help position the slider thumbs and tick marks relative to
+  /// the track.
+  ///
+  /// The `parentBox` argument can be used to help determine the preferredRect
+  /// relative to attributes of the render box of the slider itself, such as
+  /// size.
+  ///
+  /// The `offset` argument is relative to the caller's bounding box. It can be
+  /// used to convert gesture coordinates from global to slider-relative
+  /// coordinates.
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.paint.sliderTheme}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isEnabled}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isDiscrete}
+  Rect getPreferredRect({
+    required RenderBox parentBox,
+    Offset offset = Offset.zero,
+    required SliderThemeData sliderTheme,
+    bool isEnabled,
+    bool isDiscrete,
+  });
+
+  /// Paints the track shape based on the state passed to it.
+  ///
+  /// {@macro flutter.material.SliderComponentShape.paint.context}
+  ///
+  /// The `offset` argument is the offset of the origin of the `parentBox` to
+  /// the origin of its `context` canvas. This shape must be painted relative
+  /// to this offset. See [PaintingContextCallback].
+  ///
+  /// {@macro flutter.material.RangeSliderValueIndicatorShape.paint.parentBox}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.paint.sliderTheme}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.paint.enableAnimation}
+  ///
+  /// The `startThumbCenter` argument is the offset of the center of the start
+  /// thumb relative to the origin of the [PaintingContext.canvas]. It can be
+  /// used as one point that divides the track between inactive and active.
+  ///
+  /// The `endThumbCenter` argument is the offset of the center of the end
+  /// thumb relative to the origin of the [PaintingContext.canvas]. It can be
+  /// used as one point that divides the track between inactive and active.
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isEnabled}
+  ///
+  /// {@macro flutter.material.RangeSliderThumbShape.getPreferredSize.isDiscrete}
+  ///
+  /// The `textDirection` argument can be used to determine how the track
+  /// segments are painted depending on whether they are on an active track
+  /// segment or not.
+  ///
+  /// {@macro flutter.material.RangeSliderTickMarkShape.paint.trackSegment}
+  void paint(
+    PaintingContext context,
+    Offset offset, {
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required Animation<double> enableAnimation,
+    required Offset startThumbCenter,
+    required Offset endThumbCenter,
+    bool isEnabled = false,
+    bool isDiscrete = false,
+    required TextDirection textDirection,
+  });
+}
+
+/// Base track shape that provides an implementation of [getPreferredRect] for
+/// default sizing.
+///
+/// The height is set from [SliderThemeData.trackHeight] and the width of the
+/// parent box less the larger of the widths of [SliderThemeData.thumbShape] and
+/// [SliderThemeData.overlayShape].
+///
+/// See also:
+///
+///  * [RectangularSliderTrackShape], which is a track shape with sharp
+///    rectangular edges
+///  * [RoundedRectSliderTrackShape], which is a track shape with round
+///    stadium-like edges.
+abstract class BaseSliderTrackShape {
+  /// Returns a rect that represents the track bounds that fits within the
+  /// [Slider].
+  ///
+  /// The width is the width of the [Slider] or [RangeSlider], but padded by
+  /// the max  of the overlay and thumb radius. The height is defined by the
+  /// [SliderThemeData.trackHeight].
+  ///
+  /// The [Rect] is centered both horizontally and vertically within the slider
+  /// bounds.
+  Rect getPreferredRect({
+    required RenderBox parentBox,
+    Offset offset = Offset.zero,
+    required SliderThemeData sliderTheme,
+    bool isEnabled = false,
+    bool isDiscrete = false,
+  }) {
+    assert(isEnabled != null);
+    assert(isDiscrete != null);
+    assert(parentBox != null);
+    assert(sliderTheme != null);
+    final double thumbWidth = sliderTheme.thumbShape!.getPreferredSize(isEnabled, isDiscrete).width;
+    final double overlayWidth = sliderTheme.overlayShape!.getPreferredSize(isEnabled, isDiscrete).width;
+    final double trackHeight = sliderTheme.trackHeight!;
+    assert(overlayWidth >= 0);
+    assert(trackHeight >= 0);
+
+    final double trackLeft = offset.dx + overlayWidth / 2;
+    final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
+    final double trackRight = trackLeft + parentBox.size.width - math.max(thumbWidth, overlayWidth);
+    final double trackBottom = trackTop + trackHeight;
+    // If the parentBox'size less than slider's size the trackRight will be less than trackLeft, so switch them.
+    return Rect.fromLTRB(math.min(trackLeft, trackRight), trackTop, math.max(trackLeft, trackRight), trackBottom);
+  }
+}
+
+/// A [Slider] track that's a simple rectangle.
+///
+/// It paints a solid colored rectangle, vertically centered in the
+/// `parentBox`. The track rectangle extends to the bounds of the `parentBox`,
+/// but is padded by the [RoundSliderOverlayShape] radius. The height is defined
+/// by the [SliderThemeData.trackHeight]. The color is determined by the
+/// [Slider]'s enabled state and the track segment's active state which are
+/// defined by:
+///   [SliderThemeData.activeTrackColor],
+///   [SliderThemeData.inactiveTrackColor],
+///   [SliderThemeData.disabledActiveTrackColor],
+///   [SliderThemeData.disabledInactiveTrackColor].
+///
+/// {@macro flutter.material.SliderTrackShape.paint.trackSegment}
+///
+/// ![A slider widget, consisting of 5 divisions and showing the rectangular slider track shape.]
+/// (https://flutter.github.io/assets-for-api-docs/assets/material/rectangular_slider_track_shape.png)
+///
+/// See also:
+///
+///  * [Slider], for the component that is meant to display this shape.
+///  * [SliderThemeData], where an instance of this class is set to inform the
+///    slider of the visual details of the its track.
+/// {@macro flutter.material.SliderTheme.sliderTrackShape}
+///  * [RoundedRectSliderTrackShape], for a similar track with rounded edges.
+class RectangularSliderTrackShape extends SliderTrackShape with BaseSliderTrackShape {
+  /// Creates a slider track that draws 2 rectangles.
+  const RectangularSliderTrackShape({ this.disabledThumbGapWidth = 2.0 });
+
+  /// Horizontal spacing, or gap, between the disabled thumb and the track.
+  ///
+  /// This is only used when the slider is disabled. There is no gap around
+  /// the thumb and any part of the track when the slider is enabled. The
+  /// Material spec defaults this gap width 2, which is half of the disabled
+  /// thumb radius.
+  @Deprecated(
+    'It no longer has any effect because the thumb does not shrink when the slider is disabled now. '
+    'This feature was deprecated after v1.5.7.'
+  )
+  final double disabledThumbGapWidth;
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset offset, {
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required Animation<double> enableAnimation,
+    required TextDirection textDirection,
+    required Offset thumbCenter,
+    bool isDiscrete = false,
+    bool isEnabled = false,
+  }) {
+    assert(context != null);
+    assert(offset != null);
+    assert(parentBox != null);
+    assert(sliderTheme != null);
+    assert(sliderTheme.disabledActiveTrackColor != null);
+    assert(sliderTheme.disabledInactiveTrackColor != null);
+    assert(sliderTheme.activeTrackColor != null);
+    assert(sliderTheme.inactiveTrackColor != null);
+    assert(sliderTheme.thumbShape != null);
+    assert(enableAnimation != null);
+    assert(textDirection != null);
+    assert(thumbCenter != null);
+    assert(isEnabled != null);
+    assert(isDiscrete != null);
+    // If the slider [SliderThemeData.trackHeight] is less than or equal to 0,
+    // then it makes no difference whether the track is painted or not,
+    // therefore the painting can be a no-op.
+    if (sliderTheme.trackHeight! <= 0) {
+      return;
+    }
+
+    // Assign the track segment paints, which are left: active, right: inactive,
+    // but reversed for right to left text.
+    final ColorTween activeTrackColorTween = ColorTween(begin: sliderTheme.disabledActiveTrackColor, end: sliderTheme.activeTrackColor);
+    final ColorTween inactiveTrackColorTween = ColorTween(begin: sliderTheme.disabledInactiveTrackColor, end: sliderTheme.inactiveTrackColor);
+    final Paint activePaint = Paint()..color = activeTrackColorTween.evaluate(enableAnimation)!;
+    final Paint inactivePaint = Paint()..color = inactiveTrackColorTween.evaluate(enableAnimation)!;
+    final Paint leftTrackPaint;
+    final Paint rightTrackPaint;
+    switch (textDirection) {
+      case TextDirection.ltr:
+        leftTrackPaint = activePaint;
+        rightTrackPaint = inactivePaint;
+        break;
+      case TextDirection.rtl:
+        leftTrackPaint = inactivePaint;
+        rightTrackPaint = activePaint;
+        break;
+    }
+
+    final Rect trackRect = getPreferredRect(
+      parentBox: parentBox,
+      offset: offset,
+      sliderTheme: sliderTheme,
+      isEnabled: isEnabled,
+      isDiscrete: isDiscrete,
+    );
+
+    final Rect leftTrackSegment = Rect.fromLTRB(trackRect.left, trackRect.top, thumbCenter.dx, trackRect.bottom);
+    if (!leftTrackSegment.isEmpty)
+      context.canvas.drawRect(leftTrackSegment, leftTrackPaint);
+    final Rect rightTrackSegment = Rect.fromLTRB(thumbCenter.dx, trackRect.top, trackRect.right, trackRect.bottom);
+    if (!rightTrackSegment.isEmpty)
+      context.canvas.drawRect(rightTrackSegment, rightTrackPaint);
+  }
+}
+
+/// The default shape of a [Slider]'s track.
+///
+/// It paints a solid colored rectangle with rounded edges, vertically centered
+/// in the `parentBox`. The track rectangle extends to the bounds of the
+/// `parentBox`, but is padded by the larger of [RoundSliderOverlayShape]'s
+/// radius and [RoundSliderThumbShape]'s radius. The height is defined by the
+/// [SliderThemeData.trackHeight]. The color is determined by the [Slider]'s
+/// enabled state and the track segment's active state which are defined by:
+///   [SliderThemeData.activeTrackColor],
+///   [SliderThemeData.inactiveTrackColor],
+///   [SliderThemeData.disabledActiveTrackColor],
+///   [SliderThemeData.disabledInactiveTrackColor].
+///
+/// {@macro flutter.material.SliderTrackShape.paint.trackSegment}
+///
+/// ![A slider widget, consisting of 5 divisions and showing the rounded rect slider track shape.]
+/// (https://flutter.github.io/assets-for-api-docs/assets/material/rounded_rect_slider_track_shape.png)
+///
+/// See also:
+///
+///  * [Slider], for the component that is meant to display this shape.
+///  * [SliderThemeData], where an instance of this class is set to inform the
+///    slider of the visual details of the its track.
+/// {@macro flutter.material.SliderTheme.sliderTrackShape}
+///  * [RectangularSliderTrackShape], for a similar track with sharp edges.
+class RoundedRectSliderTrackShape extends SliderTrackShape with BaseSliderTrackShape {
+  /// Create a slider track that draws two rectangles with rounded outer edges.
+  const RoundedRectSliderTrackShape();
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset offset, {
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required Animation<double> enableAnimation,
+    required TextDirection textDirection,
+    required Offset thumbCenter,
+    bool isDiscrete = false,
+    bool isEnabled = false,
+    double additionalActiveTrackHeight = 2,
+  }) {
+    assert(context != null);
+    assert(offset != null);
+    assert(parentBox != null);
+    assert(sliderTheme != null);
+    assert(sliderTheme.disabledActiveTrackColor != null);
+    assert(sliderTheme.disabledInactiveTrackColor != null);
+    assert(sliderTheme.activeTrackColor != null);
+    assert(sliderTheme.inactiveTrackColor != null);
+    assert(sliderTheme.thumbShape != null);
+    assert(enableAnimation != null);
+    assert(textDirection != null);
+    assert(thumbCenter != null);
+    // If the slider [SliderThemeData.trackHeight] is less than or equal to 0,
+    // then it makes no difference whether the track is painted or not,
+    // therefore the painting  can be a no-op.
+    if (sliderTheme.trackHeight == null || sliderTheme.trackHeight! <= 0) {
+      return;
+    }
+
+    // Assign the track segment paints, which are leading: active and
+    // trailing: inactive.
+    final ColorTween activeTrackColorTween = ColorTween(begin: sliderTheme.disabledActiveTrackColor, end: sliderTheme.activeTrackColor);
+    final ColorTween inactiveTrackColorTween = ColorTween(begin: sliderTheme.disabledInactiveTrackColor, end: sliderTheme.inactiveTrackColor);
+    final Paint activePaint = Paint()..color = activeTrackColorTween.evaluate(enableAnimation)!;
+    final Paint inactivePaint = Paint()..color = inactiveTrackColorTween.evaluate(enableAnimation)!;
+    final Paint leftTrackPaint;
+    final Paint rightTrackPaint;
+    switch (textDirection) {
+      case TextDirection.ltr:
+        leftTrackPaint = activePaint;
+        rightTrackPaint = inactivePaint;
+        break;
+      case TextDirection.rtl:
+        leftTrackPaint = inactivePaint;
+        rightTrackPaint = activePaint;
+        break;
+    }
+
+    final Rect trackRect = getPreferredRect(
+      parentBox: parentBox,
+      offset: offset,
+      sliderTheme: sliderTheme,
+      isEnabled: isEnabled,
+      isDiscrete: isDiscrete,
+    );
+    final Radius trackRadius = Radius.circular(trackRect.height / 2);
+    final Radius activeTrackRadius = Radius.circular(trackRect.height / 2 + 1);
+
+    context.canvas.drawRRect(
+      RRect.fromLTRBAndCorners(
+        trackRect.left,
+        (textDirection == TextDirection.ltr) ? trackRect.top - (additionalActiveTrackHeight / 2): trackRect.top,
+        thumbCenter.dx,
+        (textDirection == TextDirection.ltr) ? trackRect.bottom + (additionalActiveTrackHeight / 2) : trackRect.bottom,
+        topLeft: (textDirection == TextDirection.ltr) ? activeTrackRadius : trackRadius,
+        bottomLeft: (textDirection == TextDirection.ltr) ? activeTrackRadius: trackRadius,
+      ),
+      leftTrackPaint,
+    );
+    context.canvas.drawRRect(
+      RRect.fromLTRBAndCorners(
+        thumbCenter.dx,
+        (textDirection == TextDirection.rtl) ? trackRect.top - (additionalActiveTrackHeight / 2) : trackRect.top,
+        trackRect.right,
+        (textDirection == TextDirection.rtl) ? trackRect.bottom + (additionalActiveTrackHeight / 2) : trackRect.bottom,
+        topRight: (textDirection == TextDirection.rtl) ? activeTrackRadius : trackRadius,
+        bottomRight: (textDirection == TextDirection.rtl) ? activeTrackRadius : trackRadius,
+      ),
+      rightTrackPaint,
+    );
+  }
+}
+
+/// A [RangeSlider] track that's a simple rectangle.
+///
+/// It paints a solid colored rectangle, vertically centered in the
+/// `parentBox`. The track rectangle extends to the bounds of the `parentBox`,
+/// but is padded by the [RoundSliderOverlayShape] radius. The height is
+/// defined by the [SliderThemeData.trackHeight]. The color is determined by the
+/// [Slider]'s enabled state and the track segment's active state which are
+/// defined by:
+///   [SliderThemeData.activeTrackColor],
+///   [SliderThemeData.inactiveTrackColor],
+///   [SliderThemeData.disabledActiveTrackColor],
+///   [SliderThemeData.disabledInactiveTrackColor].
+///
+/// {@macro flutter.material.RangeSliderTickMarkShape.paint.trackSegment}
+///
+/// ![A range slider widget, consisting of 5 divisions and showing the rectangular range slider track shape.]
+/// (https://flutter.github.io/assets-for-api-docs/assets/material/rectangular_range_slider_track_shape.png)
+///
+/// See also:
+///
+///  * [RangeSlider], for the component that is meant to display this shape.
+///  * [SliderThemeData], where an instance of this class is set to inform the
+///    slider of the visual details of the its track.
+/// {@macro flutter.material.SliderTheme.rangeSliderTrackShape}
+///  * [RoundedRectRangeSliderTrackShape], for a similar track with rounded
+///    edges.
+class RectangularRangeSliderTrackShape extends RangeSliderTrackShape {
+  /// Create a slider track with rectangular outer edges.
+  ///
+  /// The middle track segment is the selected range and is active, and the two
+  /// outer track segments are inactive.
+  const RectangularRangeSliderTrackShape();
+
+  @override
+  Rect getPreferredRect({
+    required RenderBox parentBox,
+    Offset offset = Offset.zero,
+    required SliderThemeData sliderTheme,
+    bool isEnabled = false,
+    bool isDiscrete = false,
+  }) {
+    assert(parentBox != null);
+    assert(offset != null);
+    assert(sliderTheme != null);
+    assert(sliderTheme.overlayShape != null);
+    assert(isEnabled != null);
+    assert(isDiscrete != null);
+    final double overlayWidth = sliderTheme.overlayShape!.getPreferredSize(isEnabled, isDiscrete).width;
+    final double trackHeight = sliderTheme.trackHeight!;
+    assert(overlayWidth >= 0);
+    assert(trackHeight >= 0);
+
+    final double trackLeft = offset.dx + overlayWidth / 2;
+    final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
+    final double trackRight = trackLeft + parentBox.size.width - overlayWidth;
+    final double trackBottom = trackTop + trackHeight;
+    // If the parentBox'size less than slider's size the trackRight will be less than trackLeft, so switch them.
+    return Rect.fromLTRB(math.min(trackLeft, trackRight), trackTop, math.max(trackLeft, trackRight), trackBottom);
+  }
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset offset, {
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required Animation<double>? enableAnimation,
+    required Offset startThumbCenter,
+    required Offset endThumbCenter,
+    bool isEnabled = false,
+    bool isDiscrete = false,
+    required TextDirection textDirection,
+  }) {
+    assert(context != null);
+    assert(offset != null);
+    assert(parentBox != null);
+    assert(sliderTheme != null);
+    assert(sliderTheme.disabledActiveTrackColor != null);
+    assert(sliderTheme.disabledInactiveTrackColor != null);
+    assert(sliderTheme.activeTrackColor != null);
+    assert(sliderTheme.inactiveTrackColor != null);
+    assert(sliderTheme.rangeThumbShape != null);
+    assert(enableAnimation != null);
+    assert(startThumbCenter != null);
+    assert(endThumbCenter != null);
+    assert(isEnabled != null);
+    assert(isDiscrete != null);
+    assert(textDirection != null);
+    // Assign the track segment paints, which are left: active, right: inactive,
+    // but reversed for right to left text.
+    final ColorTween activeTrackColorTween = ColorTween(begin: sliderTheme.disabledActiveTrackColor, end: sliderTheme.activeTrackColor);
+    final ColorTween inactiveTrackColorTween = ColorTween(begin: sliderTheme.disabledInactiveTrackColor, end: sliderTheme.inactiveTrackColor);
+    final Paint activePaint = Paint()..color = activeTrackColorTween.evaluate(enableAnimation!)!;
+    final Paint inactivePaint = Paint()..color = inactiveTrackColorTween.evaluate(enableAnimation)!;
+
+    final Offset leftThumbOffset;
+    final Offset rightThumbOffset;
+    switch (textDirection) {
+      case TextDirection.ltr:
+        leftThumbOffset = startThumbCenter;
+        rightThumbOffset = endThumbCenter;
+        break;
+      case TextDirection.rtl:
+        leftThumbOffset = endThumbCenter;
+        rightThumbOffset = startThumbCenter;
+        break;
+    }
+
+    final Rect trackRect = getPreferredRect(
+      parentBox: parentBox,
+      offset: offset,
+      sliderTheme: sliderTheme,
+      isEnabled: isEnabled,
+      isDiscrete: isDiscrete,
+    );
+    final Rect leftTrackSegment = Rect.fromLTRB(trackRect.left, trackRect.top, leftThumbOffset.dx, trackRect.bottom);
+    if (!leftTrackSegment.isEmpty)
+      context.canvas.drawRect(leftTrackSegment, inactivePaint);
+    final Rect middleTrackSegment = Rect.fromLTRB(leftThumbOffset.dx, trackRect.top, rightThumbOffset.dx, trackRect.bottom);
+    if (!middleTrackSegment.isEmpty)
+      context.canvas.drawRect(middleTrackSegment, activePaint);
+    final Rect rightTrackSegment = Rect.fromLTRB(rightThumbOffset.dx, trackRect.top, trackRect.right, trackRect.bottom);
+    if (!rightTrackSegment.isEmpty)
+      context.canvas.drawRect(rightTrackSegment, inactivePaint);
+  }
+}
+
+/// The default shape of a [RangeSlider]'s track.
+///
+/// It paints a solid colored rectangle with rounded edges, vertically centered
+/// in the `parentBox`. The track rectangle extends to the bounds of the
+/// `parentBox`, but is padded by the larger of [RoundSliderOverlayShape]'s
+/// radius and [RoundRangeSliderThumbShape]'s radius. The height is defined by
+/// the [SliderThemeData.trackHeight]. The color is determined by the
+/// [RangeSlider]'s enabled state and the track segment's active state which are
+/// defined by:
+///   [SliderThemeData.activeTrackColor],
+///   [SliderThemeData.inactiveTrackColor],
+///   [SliderThemeData.disabledActiveTrackColor],
+///   [SliderThemeData.disabledInactiveTrackColor].
+///
+/// {@macro flutter.material.RangeSliderTickMarkShape.paint.trackSegment}
+///
+/// ![A range slider widget, consisting of 5 divisions and showing the rounded rect range slider track shape.]
+/// (https://flutter.github.io/assets-for-api-docs/assets/material/rounded_rect_range_slider_track_shape.png)
+///
+/// See also:
+///
+///  * [RangeSlider], for the component that is meant to display this shape.
+///  * [SliderThemeData], where an instance of this class is set to inform the
+///    slider of the visual details of the its track.
+/// {@macro flutter.material.SliderTheme.rangeSliderTrackShape}
+///  * [RectangularRangeSliderTrackShape], for a similar track with sharp edges.
+class RoundedRectRangeSliderTrackShape extends RangeSliderTrackShape {
+  /// Create a slider track with rounded outer edges.
+  ///
+  /// The middle track segment is the selected range and is active, and the two
+  /// outer track segments are inactive.
+  const RoundedRectRangeSliderTrackShape();
+
+  @override
+  Rect getPreferredRect({
+    required RenderBox parentBox,
+    Offset offset = Offset.zero,
+    required SliderThemeData sliderTheme,
+    bool isEnabled = false,
+    bool isDiscrete = false,
+  }) {
+    assert(parentBox != null);
+    assert(offset != null);
+    assert(sliderTheme != null);
+    assert(sliderTheme.overlayShape != null);
+    assert(sliderTheme.trackHeight != null);
+    assert(isEnabled != null);
+    assert(isDiscrete != null);
+    final double overlayWidth = sliderTheme.overlayShape!.getPreferredSize(isEnabled, isDiscrete).width;
+    final double trackHeight = sliderTheme.trackHeight!;
+    assert(overlayWidth >= 0);
+    assert(trackHeight >= 0);
+
+    final double trackLeft = offset.dx + overlayWidth / 2;
+    final double trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
+    final double trackRight = trackLeft + parentBox.size.width - overlayWidth;
+    final double trackBottom = trackTop + trackHeight;
+    // If the parentBox'size less than slider's size the trackRight will be less than trackLeft, so switch them.
+    return Rect.fromLTRB(math.min(trackLeft, trackRight), trackTop, math.max(trackLeft, trackRight), trackBottom);
+  }
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset offset, {
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required Animation<double> enableAnimation,
+    required Offset startThumbCenter,
+    required Offset endThumbCenter,
+    bool isEnabled = false,
+    bool isDiscrete = false,
+    required TextDirection textDirection,
+    double additionalActiveTrackHeight = 2,
+  }) {
+    assert(context != null);
+    assert(offset != null);
+    assert(parentBox != null);
+    assert(sliderTheme != null);
+    assert(sliderTheme.disabledActiveTrackColor != null);
+    assert(sliderTheme.disabledInactiveTrackColor != null);
+    assert(sliderTheme.activeTrackColor != null);
+    assert(sliderTheme.inactiveTrackColor != null);
+    assert(sliderTheme.rangeThumbShape != null);
+    assert(enableAnimation != null);
+    assert(startThumbCenter != null);
+    assert(endThumbCenter != null);
+    assert(isEnabled != null);
+    assert(isDiscrete != null);
+    assert(textDirection != null);
+
+    if (sliderTheme.trackHeight == null || sliderTheme.trackHeight! <= 0) {
+      return;
+    }
+
+    // Assign the track segment paints, which are left: active, right: inactive,
+    // but reversed for right to left text.
+    final ColorTween activeTrackColorTween = ColorTween(
+      begin: sliderTheme.disabledActiveTrackColor,
+      end: sliderTheme.activeTrackColor);
+    final ColorTween inactiveTrackColorTween = ColorTween(
+      begin: sliderTheme.disabledInactiveTrackColor,
+      end: sliderTheme.inactiveTrackColor);
+    final Paint activePaint = Paint()
+      ..color = activeTrackColorTween.evaluate(enableAnimation)!;
+    final Paint inactivePaint = Paint()
+      ..color = inactiveTrackColorTween.evaluate(enableAnimation)!;
+
+    final Offset leftThumbOffset;
+    final Offset rightThumbOffset;
+    switch (textDirection) {
+      case TextDirection.ltr:
+        leftThumbOffset = startThumbCenter;
+        rightThumbOffset = endThumbCenter;
+        break;
+      case TextDirection.rtl:
+        leftThumbOffset = endThumbCenter;
+        rightThumbOffset = startThumbCenter;
+        break;
+    }
+    final Size thumbSize = sliderTheme.rangeThumbShape!.getPreferredSize(isEnabled, isDiscrete);
+    final double thumbRadius = thumbSize.width / 2;
+    assert(thumbRadius > 0);
+
+    final Rect trackRect = getPreferredRect(
+      parentBox: parentBox,
+      offset: offset,
+      sliderTheme: sliderTheme,
+      isEnabled: isEnabled,
+      isDiscrete: isDiscrete,
+    );
+
+    final Radius trackRadius = Radius.circular(trackRect.height / 2);
+
+    context.canvas.drawRRect(
+      RRect.fromLTRBAndCorners(
+        trackRect.left,
+        trackRect.top,
+        leftThumbOffset.dx,
+        trackRect.bottom,
+        topLeft: trackRadius,
+        bottomLeft: trackRadius,
+      ),
+      inactivePaint,
+    );
+    context.canvas.drawRect(
+      Rect.fromLTRB(
+        leftThumbOffset.dx,
+        trackRect.top - (additionalActiveTrackHeight / 2),
+        rightThumbOffset.dx,
+        trackRect.bottom + (additionalActiveTrackHeight / 2),
+      ),
+      activePaint,
+    );
+    context.canvas.drawRRect(
+      RRect.fromLTRBAndCorners(
+        rightThumbOffset.dx,
+        trackRect.top,
+        trackRect.right,
+        trackRect.bottom,
+        topRight: trackRadius,
+        bottomRight: trackRadius,
+      ),
+      inactivePaint,
+    );
+  }
+}
+
+/// The default shape of each [Slider] tick mark.
+///
+/// Tick marks are only displayed if the slider is discrete, which can be done
+/// by setting the [Slider.divisions] to an integer value.
+///
+/// It paints a solid circle, centered in the on the track.
+/// The color is determined by the [Slider]'s enabled state and track's active
+/// states. These colors are defined in:
+///   [SliderThemeData.activeTrackColor],
+///   [SliderThemeData.inactiveTrackColor],
+///   [SliderThemeData.disabledActiveTrackColor],
+///   [SliderThemeData.disabledInactiveTrackColor].
+///
+/// ![A slider widget, consisting of 5 divisions and showing the round slider slider tick mark shape.]
+/// (https://flutter.github.io/assets-for-api-docs/assets/material/rounded_slider_tick_mark_shape.png)
+///
+/// See also:
+///
+///  * [Slider], which includes tick marks defined by this shape.
+///  * [SliderTheme], which can be used to configure the tick mark shape of all
+///    sliders in a widget subtree.
+class RoundSliderTickMarkShape extends SliderTickMarkShape {
+  /// Create a slider tick mark that draws a circle.
+  const RoundSliderTickMarkShape({
+    this.tickMarkRadius,
+  });
+
+  /// The preferred radius of the round tick mark.
+  ///
+  /// If it is not provided, then 1/4 of the [SliderThemeData.trackHeight] is used.
+  final double? tickMarkRadius;
+
+  @override
+  Size getPreferredSize({
+    required SliderThemeData sliderTheme,
+    required bool isEnabled,
+  }) {
+    assert(sliderTheme != null);
+    assert(sliderTheme.trackHeight != null);
+    assert(isEnabled != null);
+    // The tick marks are tiny circles. If no radius is provided, then the
+    // radius is defaulted to be a fraction of the
+    // [SliderThemeData.trackHeight]. The fraction is 1/4.
+    return Size.fromRadius(tickMarkRadius ?? sliderTheme.trackHeight! / 4);
+  }
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required Animation<double> enableAnimation,
+    required TextDirection textDirection,
+    required Offset thumbCenter,
+    required bool isEnabled,
+  }) {
+    assert(context != null);
+    assert(center != null);
+    assert(parentBox != null);
+    assert(sliderTheme != null);
+    assert(sliderTheme.disabledActiveTickMarkColor != null);
+    assert(sliderTheme.disabledInactiveTickMarkColor != null);
+    assert(sliderTheme.activeTickMarkColor != null);
+    assert(sliderTheme.inactiveTickMarkColor != null);
+    assert(enableAnimation != null);
+    assert(textDirection != null);
+    assert(thumbCenter != null);
+    assert(isEnabled != null);
+    // The paint color of the tick mark depends on its position relative
+    // to the thumb and the text direction.
+    Color? begin;
+    Color? end;
+    switch (textDirection) {
+      case TextDirection.ltr:
+        final bool isTickMarkRightOfThumb = center.dx > thumbCenter.dx;
+        begin = isTickMarkRightOfThumb ? sliderTheme.disabledInactiveTickMarkColor : sliderTheme.disabledActiveTickMarkColor;
+        end = isTickMarkRightOfThumb ? sliderTheme.inactiveTickMarkColor : sliderTheme.activeTickMarkColor;
+        break;
+      case TextDirection.rtl:
+        final bool isTickMarkLeftOfThumb = center.dx < thumbCenter.dx;
+        begin = isTickMarkLeftOfThumb ? sliderTheme.disabledInactiveTickMarkColor : sliderTheme.disabledActiveTickMarkColor;
+        end = isTickMarkLeftOfThumb ? sliderTheme.inactiveTickMarkColor : sliderTheme.activeTickMarkColor;
+        break;
+    }
+    final Paint paint = Paint()..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation)!;
+
+    // The tick marks are tiny circles that are the same height as the track.
+    final double tickMarkRadius = getPreferredSize(
+       isEnabled: isEnabled,
+       sliderTheme: sliderTheme,
+     ).width / 2;
+    if (tickMarkRadius > 0) {
+      context.canvas.drawCircle(center, tickMarkRadius, paint);
+    }
+  }
+}
+
+/// The default shape of each [RangeSlider] tick mark.
+///
+/// Tick marks are only displayed if the slider is discrete, which can be done
+/// by setting the [RangeSlider.divisions] to an integer value.
+///
+/// It paints a solid circle, centered on the track.
+/// The color is determined by the [Slider]'s enabled state and track's active
+/// states. These colors are defined in:
+///   [SliderThemeData.activeTrackColor],
+///   [SliderThemeData.inactiveTrackColor],
+///   [SliderThemeData.disabledActiveTrackColor],
+///   [SliderThemeData.disabledInactiveTrackColor].
+///
+/// ![A slider widget, consisting of 5 divisions and showing the round range slider tick mark shape.]
+/// (https://flutter.github.io/assets-for-api-docs/assets/material/round_range_slider_tick_mark_shape.png )
+///
+/// See also:
+///
+///  * [RangeSlider], which includes tick marks defined by this shape.
+///  * [SliderTheme], which can be used to configure the tick mark shape of all
+///    sliders in a widget subtree.
+class RoundRangeSliderTickMarkShape extends RangeSliderTickMarkShape {
+  /// Create a range slider tick mark that draws a circle.
+  const RoundRangeSliderTickMarkShape({
+    this.tickMarkRadius,
+  });
+
+  /// The preferred radius of the round tick mark.
+  ///
+  /// If it is not provided, then 1/4 of the [SliderThemeData.trackHeight] is used.
+  final double? tickMarkRadius;
+
+  @override
+  Size getPreferredSize({
+    required SliderThemeData sliderTheme,
+    bool isEnabled = false,
+  }) {
+    assert(sliderTheme != null);
+    assert(sliderTheme.trackHeight != null);
+    assert(isEnabled != null);
+    return Size.fromRadius(tickMarkRadius ?? sliderTheme.trackHeight! / 4);
+  }
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required Animation<double> enableAnimation,
+    required Offset startThumbCenter,
+    required Offset endThumbCenter,
+    bool isEnabled = false,
+    required TextDirection textDirection,
+  }) {
+    assert(context != null);
+    assert(center != null);
+    assert(parentBox != null);
+    assert(sliderTheme != null);
+    assert(sliderTheme.disabledActiveTickMarkColor != null);
+    assert(sliderTheme.disabledInactiveTickMarkColor != null);
+    assert(sliderTheme.activeTickMarkColor != null);
+    assert(sliderTheme.inactiveTickMarkColor != null);
+    assert(enableAnimation != null);
+    assert(startThumbCenter != null);
+    assert(endThumbCenter != null);
+    assert(isEnabled != null);
+    assert(textDirection != null);
+
+    final bool isBetweenThumbs;
+    switch (textDirection) {
+      case TextDirection.ltr:
+        isBetweenThumbs = startThumbCenter.dx < center.dx && center.dx < endThumbCenter.dx;
+        break;
+      case TextDirection.rtl:
+        isBetweenThumbs = endThumbCenter.dx < center.dx && center.dx < startThumbCenter.dx;
+        break;
+    }
+    final Color? begin = isBetweenThumbs ? sliderTheme.disabledActiveTickMarkColor : sliderTheme.disabledInactiveTickMarkColor;
+    final Color? end = isBetweenThumbs ? sliderTheme.activeTickMarkColor : sliderTheme.inactiveTickMarkColor;
+    final Paint paint = Paint()..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation)!;
+
+    // The tick marks are tiny circles that are the same height as the track.
+    final double tickMarkRadius = getPreferredSize(
+      isEnabled: isEnabled,
+      sliderTheme: sliderTheme,
+    ).width / 2;
+    if (tickMarkRadius > 0) {
+      context.canvas.drawCircle(center, tickMarkRadius, paint);
+    }
+  }
+}
+
+/// A special version of [SliderTickMarkShape] that has a zero size and paints
+/// nothing.
+///
+/// This class is used to create a special instance of a [SliderTickMarkShape]
+/// that will not paint any tick mark shape. A static reference is stored in
+/// [SliderTickMarkShape.noTickMark]. When this value is specified for
+/// [SliderThemeData.tickMarkShape], the tick mark painting is skipped.
+class _EmptySliderTickMarkShape extends SliderTickMarkShape {
+  @override
+  Size getPreferredSize({
+    required SliderThemeData sliderTheme,
+    required bool isEnabled,
+  }) {
+    return Size.zero;
+  }
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required Animation<double> enableAnimation,
+    required Offset thumbCenter,
+    required bool isEnabled,
+    required TextDirection textDirection,
+  }) {
+    // no-op.
+  }
+}
+
+/// A special version of [SliderComponentShape] that has a zero size and paints
+/// nothing.
+///
+/// This class is used to create a special instance of a [SliderComponentShape]
+/// that will not paint any component shape. A static reference is stored in
+/// [SliderTickMarkShape.noThumb] and [SliderTickMarkShape.noOverlay]. When this value
+/// is specified for [SliderThemeData.thumbShape], the thumb painting is
+/// skipped.  When this value is specified for [SliderThemeData.overlayShape],
+/// the overlay painting is skipped.
+class _EmptySliderComponentShape extends SliderComponentShape {
+  @override
+  Size getPreferredSize(bool isEnabled, bool isDiscrete) => Size.zero;
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required Animation<double> activationAnimation,
+    required Animation<double> enableAnimation,
+    required bool isDiscrete,
+    required TextPainter labelPainter,
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required TextDirection textDirection,
+    required double value,
+    required double textScaleFactor,
+    required Size sizeWithOverflow,
+  }) {
+    // no-op.
+  }
+}
+
+/// The default shape of a [Slider]'s thumb.
+///
+/// There is a shadow for the resting, pressed, hovered, and focused state.
+///
+/// ![A slider widget, consisting of 5 divisions and showing the round slider thumb shape.]
+/// (https://flutter.github.io/assets-for-api-docs/assets/material/round_slider_thumb_shape.png)
+///
+/// See also:
+///
+///  * [Slider], which includes a thumb defined by this shape.
+///  * [SliderTheme], which can be used to configure the thumb shape of all
+///    sliders in a widget subtree.
+class RoundSliderThumbShape extends SliderComponentShape {
+  /// Create a slider thumb that draws a circle.
+  const RoundSliderThumbShape({
+    this.enabledThumbRadius = 10.0,
+    this.disabledThumbRadius,
+    this.elevation = 1.0,
+    this.pressedElevation = 6.0,
+  });
+
+  /// The preferred radius of the round thumb shape when the slider is enabled.
+  ///
+  /// If it is not provided, then the material default of 10 is used.
+  final double enabledThumbRadius;
+
+  /// The preferred radius of the round thumb shape when the slider is disabled.
+  ///
+  /// If no disabledRadius is provided, then it is equal to the
+  /// [enabledThumbRadius]
+  final double? disabledThumbRadius;
+  double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;
+
+  /// The resting elevation adds shadow to the unpressed thumb.
+  ///
+  /// The default is 1.
+  ///
+  /// Use 0 for no shadow. The higher the value, the larger the shadow. For
+  /// example, a value of 12 will create a very large shadow.
+  ///
+  final double elevation;
+
+  /// The pressed elevation adds shadow to the pressed thumb.
+  ///
+  /// The default is 6.
+  ///
+  /// Use 0 for no shadow. The higher the value, the larger the shadow. For
+  /// example, a value of 12 will create a very large shadow.
+  final double pressedElevation;
+
+  @override
+  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
+    return Size.fromRadius(isEnabled == true ? enabledThumbRadius : _disabledThumbRadius);
+  }
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required Animation<double> activationAnimation,
+    required Animation<double> enableAnimation,
+    required bool isDiscrete,
+    required TextPainter labelPainter,
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required TextDirection textDirection,
+    required double value,
+    required double textScaleFactor,
+    required Size sizeWithOverflow,
+  }) {
+    assert(context != null);
+    assert(center != null);
+    assert(enableAnimation != null);
+    assert(sliderTheme != null);
+    assert(sliderTheme.disabledThumbColor != null);
+    assert(sliderTheme.thumbColor != null);
+
+    final Canvas canvas = context.canvas;
+    final Tween<double> radiusTween = Tween<double>(
+      begin: _disabledThumbRadius,
+      end: enabledThumbRadius,
+    );
+    final ColorTween colorTween = ColorTween(
+      begin: sliderTheme.disabledThumbColor,
+      end: sliderTheme.thumbColor,
+    );
+
+    final Color color = colorTween.evaluate(enableAnimation)!;
+    final double radius = radiusTween.evaluate(enableAnimation);
+
+    final Tween<double> elevationTween = Tween<double>(
+      begin: elevation,
+      end: pressedElevation,
+    );
+
+    final double evaluatedElevation = elevationTween.evaluate(activationAnimation);
+    final Path path = Path()
+      ..addArc(Rect.fromCenter(center: center, width: 2 * radius, height: 2 * radius), 0, math.pi * 2);
+    canvas.drawShadow(path, Colors.black, evaluatedElevation, true);
+
+    canvas.drawCircle(
+      center,
+      radius,
+      Paint()..color = color,
+    );
+  }
+}
+
+/// The default shape of a [RangeSlider]'s thumbs.
+///
+/// There is a shadow for the resting and pressed state.
+///
+/// ![A slider widget, consisting of 5 divisions and showing the round range slider thumb shape.]
+/// (https://flutter.github.io/assets-for-api-docs/assets/material/round_range_slider_thumb_shape.png)
+///
+/// See also:
+///
+///  * [RangeSlider], which includes thumbs defined by this shape.
+///  * [SliderTheme], which can be used to configure the thumb shapes of all
+///    range sliders in a widget subtree.
+class RoundRangeSliderThumbShape extends RangeSliderThumbShape {
+  /// Create a slider thumb that draws a circle.
+  const RoundRangeSliderThumbShape({
+    this.enabledThumbRadius = 10.0,
+    this.disabledThumbRadius,
+    this.elevation = 1.0,
+    this.pressedElevation = 6.0,
+  }) : assert(enabledThumbRadius != null);
+
+  /// The preferred radius of the round thumb shape when the slider is enabled.
+  ///
+  /// If it is not provided, then the material default of 10 is used.
+  final double enabledThumbRadius;
+
+  /// The preferred radius of the round thumb shape when the slider is disabled.
+  ///
+  /// If no disabledRadius is provided, then it is equal to the
+  /// [enabledThumbRadius].
+  final double? disabledThumbRadius;
+  double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;
+
+  /// The resting elevation adds shadow to the unpressed thumb.
+  ///
+  /// The default is 1.
+  final double elevation;
+
+  /// The pressed elevation adds shadow to the pressed thumb.
+  ///
+  /// The default is 6.
+  final double pressedElevation;
+
+  @override
+  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
+    return Size.fromRadius(isEnabled == true ? enabledThumbRadius : _disabledThumbRadius);
+  }
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required Animation<double> activationAnimation,
+    required Animation<double> enableAnimation,
+    bool isDiscrete = false,
+    bool isEnabled = false,
+    bool? isOnTop,
+    required SliderThemeData sliderTheme,
+    TextDirection? textDirection,
+    Thumb? thumb,
+    bool? isPressed,
+  }) {
+    assert(context != null);
+    assert(center != null);
+    assert(activationAnimation != null);
+    assert(sliderTheme != null);
+    assert(sliderTheme.showValueIndicator != null);
+    assert(sliderTheme.overlappingShapeStrokeColor != null);
+    assert(enableAnimation != null);
+    final Canvas canvas = context.canvas;
+    final Tween<double> radiusTween = Tween<double>(
+      begin: _disabledThumbRadius,
+      end: enabledThumbRadius,
+    );
+    final ColorTween colorTween = ColorTween(
+      begin: sliderTheme.disabledThumbColor,
+      end: sliderTheme.thumbColor,
+    );
+    final double radius = radiusTween.evaluate(enableAnimation);
+    final Tween<double> elevationTween = Tween<double>(
+      begin: elevation,
+      end: pressedElevation,
+    );
+
+    // Add a stroke of 1dp around the circle if this thumb would overlap
+    // the other thumb.
+    if (isOnTop == true) {
+      final Paint strokePaint = Paint()
+        ..color = sliderTheme.overlappingShapeStrokeColor!
+        ..strokeWidth = 1.0
+        ..style = PaintingStyle.stroke;
+      canvas.drawCircle(center, radius, strokePaint);
+    }
+
+    final Color color = colorTween.evaluate(enableAnimation)!;
+
+    final double evaluatedElevation = isPressed! ? elevationTween.evaluate(activationAnimation) : elevation;
+    final Path shadowPath = Path()
+      ..addArc(Rect.fromCenter(center: center, width: 2 * radius, height: 2 * radius), 0, math.pi * 2);
+    canvas.drawShadow(shadowPath, Colors.black, evaluatedElevation, true);
+
+    canvas.drawCircle(
+      center,
+      radius,
+      Paint()..color = color,
+    );
+  }
+}
+
+/// The default shape of a [Slider]'s thumb overlay.
+///
+/// The shape of the overlay is a circle with the same center as the thumb, but
+/// with a larger radius. It animates to full size when the thumb is pressed,
+/// and animates back down to size 0 when it is released. It is painted behind
+/// the thumb, and is expected to extend beyond the bounds of the thumb so that
+/// it is visible.
+///
+/// The overlay color is defined by [SliderThemeData.overlayColor].
+///
+/// See also:
+///
+///  * [Slider], which includes an overlay defined by this shape.
+///  * [SliderTheme], which can be used to configure the overlay shape of all
+///    sliders in a widget subtree.
+class RoundSliderOverlayShape extends SliderComponentShape {
+  /// Create a slider thumb overlay that draws a circle.
+  const RoundSliderOverlayShape({ this.overlayRadius = 24.0 });
+
+  /// The preferred radius of the round thumb shape when enabled.
+  ///
+  /// If it is not provided, then half of the [SliderThemeData.trackHeight] is
+  /// used.
+  final double overlayRadius;
+
+  @override
+  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
+    return Size.fromRadius(overlayRadius);
+  }
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required Animation<double> activationAnimation,
+    required Animation<double> enableAnimation,
+    required bool isDiscrete,
+    required TextPainter labelPainter,
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required TextDirection textDirection,
+    required double value,
+    required double textScaleFactor,
+    required Size sizeWithOverflow,
+  }) {
+    assert(context != null);
+    assert(center != null);
+    assert(activationAnimation != null);
+    assert(enableAnimation != null);
+    assert(labelPainter != null);
+    assert(parentBox != null);
+    assert(sliderTheme != null);
+    assert(textDirection != null);
+    assert(value != null);
+
+    final Canvas canvas = context.canvas;
+    final Tween<double> radiusTween = Tween<double>(
+      begin: 0.0,
+      end: overlayRadius,
+    );
+
+    canvas.drawCircle(
+      center,
+      radiusTween.evaluate(activationAnimation),
+      Paint()..color = sliderTheme.overlayColor!,
+    );
+  }
+}
+
+/// The default shape of a [Slider]'s value indicator.
+///
+/// ![A slider widget, consisting of 5 divisions and showing the rectangular slider value indicator shape.]
+/// (https://flutter.github.io/assets-for-api-docs/assets/material/rectangular_slider_value_indicator_shape.png)
+///
+/// See also:
+///
+///  * [Slider], which includes a value indicator defined by this shape.
+///  * [SliderTheme], which can be used to configure the slider value indicator
+///    of all sliders in a widget subtree.
+class RectangularSliderValueIndicatorShape extends SliderComponentShape {
+  /// Create a slider value indicator that resembles a rectangular tooltip.
+  const RectangularSliderValueIndicatorShape();
+
+  static const _RectangularSliderValueIndicatorPathPainter _pathPainter = _RectangularSliderValueIndicatorPathPainter();
+
+  @override
+  Size getPreferredSize(
+     bool isEnabled,
+     bool isDiscrete, {
+     TextPainter? labelPainter,
+     double? textScaleFactor,
+  }) {
+     assert(labelPainter != null);
+     assert(textScaleFactor != null && textScaleFactor >= 0);
+     return _pathPainter.getPreferredSize(labelPainter!, textScaleFactor!);
+  }
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required Animation<double> activationAnimation,
+    required Animation<double> enableAnimation,
+    required bool isDiscrete,
+    required TextPainter labelPainter,
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required TextDirection textDirection,
+    required double value,
+    required double textScaleFactor,
+    required Size sizeWithOverflow,
+  }) {
+    final Canvas canvas = context.canvas;
+    final double scale = activationAnimation.value;
+    _pathPainter.paint(
+      parentBox: parentBox,
+      canvas: canvas,
+      center: center,
+      scale: scale,
+      labelPainter: labelPainter,
+      textScaleFactor: textScaleFactor,
+      sizeWithOverflow: sizeWithOverflow,
+      backgroundPaintColor: sliderTheme.valueIndicatorColor!);
+  }
+}
+
+/// The default shape of a [RangeSlider]'s value indicators.
+///
+/// ![A slider widget, consisting of 5 divisions and showing the rectangular range slider value indicator shape.]
+/// (https://flutter.github.io/assets-for-api-docs/assets/material/rectangular_range_slider_value_indicator_shape.png)
+///
+/// See also:
+///
+///  * [RangeSlider], which includes value indicators defined by this shape.
+///  * [SliderTheme], which can be used to configure the range slider value
+///    indicator of all sliders in a widget subtree.
+class RectangularRangeSliderValueIndicatorShape
+    extends RangeSliderValueIndicatorShape {
+  /// Create a range slider value indicator that resembles a rectangular tooltip.
+  const RectangularRangeSliderValueIndicatorShape();
+
+  static const _RectangularSliderValueIndicatorPathPainter _pathPainter = _RectangularSliderValueIndicatorPathPainter();
+
+  @override
+  Size getPreferredSize(
+    bool isEnabled,
+    bool isDiscrete, {
+    required TextPainter labelPainter,
+    required double textScaleFactor,
+  }) {
+    assert(labelPainter != null);
+    assert(textScaleFactor != null && textScaleFactor >= 0);
+    return _pathPainter.getPreferredSize(labelPainter, textScaleFactor);
+  }
+
+  @override
+  double getHorizontalShift({
+    RenderBox? parentBox,
+    Offset? center,
+    TextPainter? labelPainter,
+    Animation<double>? activationAnimation,
+    double? textScaleFactor,
+    Size? sizeWithOverflow,
+  }) {
+    return _pathPainter.getHorizontalShift(
+      parentBox: parentBox!,
+      center: center!,
+      labelPainter: labelPainter!,
+      textScaleFactor: textScaleFactor!,
+      sizeWithOverflow: sizeWithOverflow!,
+      scale: activationAnimation!.value,
+    );
+  }
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    Animation<double>? activationAnimation,
+    Animation<double>? enableAnimation,
+    bool? isDiscrete,
+    bool? isOnTop,
+    TextPainter? labelPainter,
+    double? textScaleFactor,
+    Size? sizeWithOverflow,
+    RenderBox? parentBox,
+    SliderThemeData? sliderTheme,
+    TextDirection? textDirection,
+    double? value,
+    Thumb? thumb,
+  }) {
+    final Canvas canvas = context.canvas;
+    final double scale = activationAnimation!.value;
+    _pathPainter.paint(
+      parentBox: parentBox!,
+      canvas: canvas,
+      center: center,
+      scale: scale,
+      labelPainter: labelPainter!,
+      textScaleFactor: textScaleFactor!,
+      sizeWithOverflow: sizeWithOverflow!,
+      backgroundPaintColor: sliderTheme!.valueIndicatorColor!,
+      strokePaintColor: isOnTop! ? sliderTheme.overlappingShapeStrokeColor : null,
+    );
+  }
+}
+
+class _RectangularSliderValueIndicatorPathPainter {
+  const _RectangularSliderValueIndicatorPathPainter();
+
+  static const double _triangleHeight = 8.0;
+  static const double _labelPadding = 16.0;
+  static const double _preferredHeight = 32.0;
+  static const double _minLabelWidth = 16.0;
+  static const double _bottomTipYOffset = 14.0;
+  static const double _preferredHalfHeight = _preferredHeight / 2;
+  static const double _upperRectRadius = 4;
+
+  Size getPreferredSize(
+    TextPainter labelPainter,
+    double textScaleFactor,
+  ) {
+    assert(labelPainter != null);
+    return Size(
+      _upperRectangleWidth(labelPainter, 1, textScaleFactor),
+      labelPainter.height + _labelPadding,
+    );
+  }
+
+  double getHorizontalShift({
+    required RenderBox parentBox,
+    required Offset center,
+    required TextPainter labelPainter,
+    required double textScaleFactor,
+    required Size sizeWithOverflow,
+    required double scale,
+  }) {
+    assert(!sizeWithOverflow.isEmpty);
+
+    const double edgePadding = 8.0;
+    final double rectangleWidth = _upperRectangleWidth(labelPainter, scale, textScaleFactor);
+    /// Value indicator draws on the Overlay and by using the global Offset
+    /// we are making sure we use the bounds of the Overlay instead of the Slider.
+    final Offset globalCenter = parentBox.localToGlobal(center);
+
+    // The rectangle must be shifted towards the center so that it minimizes the
+    // chance of it rendering outside the bounds of the render box. If the shift
+    // is negative, then the lobe is shifted from right to left, and if it is
+    // positive, then the lobe is shifted from left to right.
+    final double overflowLeft = math.max(0, rectangleWidth / 2 - globalCenter.dx + edgePadding);
+    final double overflowRight = math.max(0, rectangleWidth / 2 - (sizeWithOverflow.width - globalCenter.dx - edgePadding));
+
+    if (rectangleWidth < sizeWithOverflow.width) {
+      return overflowLeft - overflowRight;
+    } else if (overflowLeft - overflowRight > 0) {
+      return overflowLeft - (edgePadding * textScaleFactor);
+    } else {
+      return -overflowRight + (edgePadding * textScaleFactor);
+    }
+  }
+
+  double _upperRectangleWidth(TextPainter labelPainter, double scale, double textScaleFactor) {
+    final double unscaledWidth = math.max(_minLabelWidth * textScaleFactor, labelPainter.width) + _labelPadding * 2;
+    return unscaledWidth * scale;
+  }
+
+  void paint({
+    required RenderBox parentBox,
+    required Canvas canvas,
+    required Offset center,
+    required double scale,
+    required TextPainter labelPainter,
+    required double textScaleFactor,
+    required Size sizeWithOverflow,
+    required Color backgroundPaintColor,
+    Color? strokePaintColor,
+  }) {
+    if (scale == 0.0) {
+      // Zero scale essentially means "do not draw anything", so it's safe to just return.
+      return;
+    }
+    assert(!sizeWithOverflow.isEmpty);
+
+    final double rectangleWidth = _upperRectangleWidth(labelPainter, scale, textScaleFactor);
+    final double horizontalShift = getHorizontalShift(
+      parentBox: parentBox,
+      center: center,
+      labelPainter: labelPainter,
+      textScaleFactor: textScaleFactor,
+      sizeWithOverflow: sizeWithOverflow,
+      scale: scale,
+    );
+
+    final double rectHeight = labelPainter.height + _labelPadding;
+    final Rect upperRect = Rect.fromLTWH(
+      -rectangleWidth / 2 + horizontalShift,
+      -_triangleHeight - rectHeight,
+      rectangleWidth,
+      rectHeight,
+    );
+
+    final Path trianglePath = Path()
+      ..lineTo(-_triangleHeight, -_triangleHeight)
+      ..lineTo(_triangleHeight, -_triangleHeight)
+      ..close();
+    final Paint fillPaint = Paint()..color = backgroundPaintColor;
+    final RRect upperRRect = RRect.fromRectAndRadius(upperRect, const Radius.circular(_upperRectRadius));
+    trianglePath.addRRect(upperRRect);
+
+    canvas.save();
+    // Prepare the canvas for the base of the tooltip, which is relative to the
+    // center of the thumb.
+    canvas.translate(center.dx, center.dy - _bottomTipYOffset);
+    canvas.scale(scale, scale);
+    if (strokePaintColor != null) {
+      final Paint strokePaint = Paint()
+        ..color = strokePaintColor
+        ..strokeWidth = 1.0
+        ..style = PaintingStyle.stroke;
+      canvas.drawPath(trianglePath, strokePaint);
+    }
+    canvas.drawPath(trianglePath, fillPaint);
+
+    // The label text is centered within the value indicator.
+    final double bottomTipToUpperRectTranslateY = -_preferredHalfHeight / 2 - upperRect.height;
+    canvas.translate(0, bottomTipToUpperRectTranslateY);
+    final Offset boxCenter = Offset(horizontalShift, upperRect.height / 2);
+    final Offset halfLabelPainterOffset = Offset(labelPainter.width / 2, labelPainter.height / 2);
+    final Offset labelOffset = boxCenter - halfLabelPainterOffset;
+    labelPainter.paint(canvas, labelOffset);
+    canvas.restore();
+  }
+}
+
+/// A variant shape of a [Slider]'s value indicator . The value indicator is in
+/// the shape of an upside-down pear.
+///
+/// ![A slider widget, consisting of 5 divisions and showing the paddle slider value indicator shape.]
+/// (https://flutter.github.io/assets-for-api-docs/assets/material/paddle_slider_value_indicator_shape.png)
+///
+/// See also:
+///
+///  * [Slider], which includes a value indicator defined by this shape.
+///  * [SliderTheme], which can be used to configure the slider value indicator
+///    of all sliders in a widget subtree.
+class PaddleSliderValueIndicatorShape extends SliderComponentShape {
+  /// Create a slider value indicator in the shape of an upside-down pear.
+  const PaddleSliderValueIndicatorShape();
+
+  static const _PaddleSliderValueIndicatorPathPainter _pathPainter = _PaddleSliderValueIndicatorPathPainter();
+
+  @override
+  Size getPreferredSize(
+    bool isEnabled,
+    bool isDiscrete, {
+    TextPainter? labelPainter,
+    double? textScaleFactor,
+  }) {
+    assert(labelPainter != null);
+    assert(textScaleFactor != null && textScaleFactor >= 0);
+    return _pathPainter.getPreferredSize(labelPainter!, textScaleFactor!);
+  }
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required Animation<double> activationAnimation,
+    required Animation<double> enableAnimation,
+    required bool isDiscrete,
+    required TextPainter labelPainter,
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    required TextDirection textDirection,
+    required double value,
+    required double textScaleFactor,
+    required Size sizeWithOverflow,
+  }) {
+    assert(context != null);
+    assert(center != null);
+    assert(activationAnimation != null);
+    assert(enableAnimation != null);
+    assert(labelPainter != null);
+    assert(parentBox != null);
+    assert(sliderTheme != null);
+    assert(!sizeWithOverflow.isEmpty);
+    final ColorTween enableColor = ColorTween(
+      begin: sliderTheme.disabledThumbColor,
+      end: sliderTheme.valueIndicatorColor,
+    );
+    _pathPainter.paint(
+      context.canvas,
+      center,
+      Paint()..color = enableColor.evaluate(enableAnimation)!,
+      activationAnimation.value,
+      labelPainter,
+      textScaleFactor,
+      sizeWithOverflow,
+      null,
+    );
+  }
+}
+
+/// A variant shape of a [RangeSlider]'s value indicators. The value indicator
+/// is in the shape of an upside-down pear.
+///
+/// ![A slider widget, consisting of 5 divisions and showing the paddle range slider value indicator shape.]
+/// (https://flutter.github.io/assets-for-api-docs/assets/material/paddle_range_slider_value_indicator_shape.png)
+///
+/// See also:
+///
+///  * [RangeSlider], which includes value indicators defined by this shape.
+///  * [SliderTheme], which can be used to configure the range slider value
+///    indicator of all sliders in a widget subtree.
+class PaddleRangeSliderValueIndicatorShape extends RangeSliderValueIndicatorShape {
+  /// Create a slider value indicator in the shape of an upside-down pear.
+  const PaddleRangeSliderValueIndicatorShape();
+
+  static const _PaddleSliderValueIndicatorPathPainter _pathPainter = _PaddleSliderValueIndicatorPathPainter();
+
+  @override
+  Size getPreferredSize(
+    bool isEnabled,
+    bool isDiscrete, {
+    required TextPainter labelPainter,
+    required double textScaleFactor,
+  }) {
+    assert(labelPainter != null);
+    assert(textScaleFactor != null && textScaleFactor >= 0);
+    return _pathPainter.getPreferredSize(labelPainter, textScaleFactor);
+  }
+
+  @override
+  double getHorizontalShift({
+    RenderBox? parentBox,
+    Offset? center,
+    TextPainter? labelPainter,
+    Animation<double>? activationAnimation,
+    double? textScaleFactor,
+    Size? sizeWithOverflow,
+  }) {
+    return _pathPainter.getHorizontalShift(
+      center: center!,
+      labelPainter: labelPainter!,
+      scale: activationAnimation!.value,
+      textScaleFactor: textScaleFactor!,
+      sizeWithOverflow: sizeWithOverflow!,
+    );
+  }
+
+  @override
+  void paint(
+    PaintingContext context,
+    Offset center, {
+    required Animation<double> activationAnimation,
+    required Animation<double> enableAnimation,
+    bool? isDiscrete,
+    bool isOnTop = false,
+    required TextPainter labelPainter,
+    required RenderBox parentBox,
+    required SliderThemeData sliderTheme,
+    TextDirection? textDirection,
+    Thumb? thumb,
+    double? value,
+    double? textScaleFactor,
+    Size? sizeWithOverflow,
+  }) {
+    assert(context != null);
+    assert(center != null);
+    assert(activationAnimation != null);
+    assert(enableAnimation != null);
+    assert(labelPainter != null);
+    assert(parentBox != null);
+    assert(sliderTheme != null);
+    assert(!sizeWithOverflow!.isEmpty);
+    final ColorTween enableColor = ColorTween(
+      begin: sliderTheme.disabledThumbColor,
+      end: sliderTheme.valueIndicatorColor,
+    );
+    // Add a stroke of 1dp around the top paddle.
+    _pathPainter.paint(
+      context.canvas,
+      center,
+      Paint()..color = enableColor.evaluate(enableAnimation)!,
+      activationAnimation.value,
+      labelPainter,
+      textScaleFactor!,
+      sizeWithOverflow!,
+      isOnTop ? sliderTheme.overlappingShapeStrokeColor : null,
+    );
+  }
+}
+
+class _PaddleSliderValueIndicatorPathPainter {
+  const _PaddleSliderValueIndicatorPathPainter();
+
+  // These constants define the shape of the default value indicator.
+  // The value indicator changes shape based on the size of
+  // the label: The top lobe spreads horizontally, and the
+  // top arc on the neck moves down to keep it merging smoothly
+  // with the top lobe as it expands.
+
+  // Radius of the top lobe of the value indicator.
+  static const double _topLobeRadius = 16.0;
+  static const double _minLabelWidth = 16.0;
+  // Radius of the bottom lobe of the value indicator.
+  static const double _bottomLobeRadius = 10.0;
+  static const double _labelPadding = 8.0;
+  static const double _distanceBetweenTopBottomCenters = 40.0;
+  static const double _middleNeckWidth = 3.0;
+  static const double _bottomNeckRadius = 4.5;
+  // The base of the triangle between the top lobe center and the centers of
+  // the two top neck arcs.
+  static const double _neckTriangleBase = _topNeckRadius + _middleNeckWidth / 2;
+  static const double _rightBottomNeckCenterX = _middleNeckWidth / 2 + _bottomNeckRadius;
+  static const double _rightBottomNeckAngleStart = math.pi;
+  static const Offset _topLobeCenter = Offset(0.0, -_distanceBetweenTopBottomCenters);
+  static const double _topNeckRadius = 13.0;
+  // The length of the hypotenuse of the triangle formed by the center
+  // of the left top lobe arc and the center of the top left neck arc.
+  // Used to calculate the position of the center of the arc.
+  static const double _neckTriangleHypotenuse = _topLobeRadius + _topNeckRadius;
+  // Some convenience values to help readability.
+  static const double _twoSeventyDegrees = 3.0 * math.pi / 2.0;
+  static const double _ninetyDegrees = math.pi / 2.0;
+  static const double _thirtyDegrees = math.pi / 6.0;
+  static const double _preferredHeight = _distanceBetweenTopBottomCenters + _topLobeRadius + _bottomLobeRadius;
+  // Set to true if you want a rectangle to be drawn around the label bubble.
+  // This helps with building tests that check that the label draws in the right
+  // place (because it prints the rect in the failed test output). It should not
+  // be checked in while set to "true".
+  static const bool _debuggingLabelLocation = false;
+
+  Size getPreferredSize(
+    TextPainter labelPainter,
+    double textScaleFactor,
+  ) {
+    assert(labelPainter != null);
+    assert(textScaleFactor != null && textScaleFactor >= 0);
+    final double width = math.max(_minLabelWidth * textScaleFactor, labelPainter.width) + _labelPadding * 2 * textScaleFactor;
+    return Size(width, _preferredHeight * textScaleFactor);
+  }
+
+  // Adds an arc to the path that has the attributes passed in. This is
+  // a convenience to make adding arcs have less boilerplate.
+  static void _addArc(Path path, Offset center, double radius, double startAngle, double endAngle) {
+    assert(center.isFinite);
+    final Rect arcRect = Rect.fromCircle(center: center, radius: radius);
+    path.arcTo(arcRect, startAngle, endAngle - startAngle, false);
+  }
+
+  double getHorizontalShift({
+    required Offset center,
+    required TextPainter labelPainter,
+    required double scale,
+    required double textScaleFactor,
+    required Size sizeWithOverflow,
+  }) {
+    assert(!sizeWithOverflow.isEmpty);
+    final double inverseTextScale = textScaleFactor != 0 ? 1.0 / textScaleFactor : 0.0;
+    final double labelHalfWidth = labelPainter.width / 2.0;
+    final double halfWidthNeeded = math.max(
+      0.0,
+      inverseTextScale * labelHalfWidth - (_topLobeRadius - _labelPadding),
+    );
+    final double shift = _getIdealOffset(halfWidthNeeded, textScaleFactor * scale, center, sizeWithOverflow.width);
+    return shift * textScaleFactor;
+  }
+
+  // Determines the "best" offset to keep the bubble within the slider. The
+  // calling code will bound that with the available movement in the paddle shape.
+  double _getIdealOffset(
+    double halfWidthNeeded,
+    double scale,
+    Offset center,
+    double widthWithOverflow,
+  ) {
+    const double edgeMargin = 8.0;
+    final Rect topLobeRect = Rect.fromLTWH(
+      -_topLobeRadius - halfWidthNeeded,
+      -_topLobeRadius - _distanceBetweenTopBottomCenters,
+      2.0 * (_topLobeRadius + halfWidthNeeded),
+      2.0 * _topLobeRadius,
+    );
+    // We can just multiply by scale instead of a transform, since we're scaling
+    // around (0, 0).
+    final Offset topLeft = (topLobeRect.topLeft * scale) + center;
+    final Offset bottomRight = (topLobeRect.bottomRight * scale) + center;
+    double shift = 0.0;
+
+    if (topLeft.dx < edgeMargin) {
+      shift = edgeMargin - topLeft.dx;
+    }
+
+    final double endGlobal = widthWithOverflow;
+    if (bottomRight.dx > endGlobal - edgeMargin) {
+      shift = endGlobal - edgeMargin - bottomRight.dx;
+    }
+
+    shift = scale == 0.0 ? 0.0 : shift / scale;
+    if (shift < 0.0) {
+      // Shifting to the left.
+      shift = math.max(shift, -halfWidthNeeded);
+    } else {
+      // Shifting to the right.
+      shift = math.min(shift, halfWidthNeeded);
+    }
+    return shift;
+  }
+
+  void paint(
+    Canvas canvas,
+    Offset center,
+    Paint paint,
+    double scale,
+    TextPainter labelPainter,
+    double textScaleFactor,
+    Size sizeWithOverflow,
+    Color? strokePaintColor,
+  ) {
+    if (scale == 0.0) {
+      // Zero scale essentially means "do not draw anything", so it's safe to just return. Otherwise,
+      // our math below will attempt to divide by zero and send needless NaNs to the engine.
+      return;
+    }
+    assert(!sizeWithOverflow.isEmpty);
+
+    // The entire value indicator should scale with the size of the label,
+    // to keep it large enough to encompass the label text.
+    final double overallScale = scale * textScaleFactor;
+    final double inverseTextScale = textScaleFactor != 0 ? 1.0 / textScaleFactor : 0.0;
+    final double labelHalfWidth = labelPainter.width / 2.0;
+
+    canvas.save();
+    canvas.translate(center.dx, center.dy);
+    canvas.scale(overallScale, overallScale);
+
+    final double bottomNeckTriangleHypotenuse = _bottomNeckRadius + _bottomLobeRadius / overallScale;
+    final double rightBottomNeckCenterY = -math.sqrt(math.pow(bottomNeckTriangleHypotenuse, 2) - math.pow(_rightBottomNeckCenterX, 2));
+    final double rightBottomNeckAngleEnd = math.pi + math.atan(rightBottomNeckCenterY / _rightBottomNeckCenterX);
+    final Path path = Path()..moveTo(_middleNeckWidth / 2, rightBottomNeckCenterY);
+    _addArc(
+      path,
+      Offset(_rightBottomNeckCenterX, rightBottomNeckCenterY),
+      _bottomNeckRadius,
+      _rightBottomNeckAngleStart,
+      rightBottomNeckAngleEnd,
+    );
+    _addArc(
+      path,
+      Offset.zero,
+      _bottomLobeRadius / overallScale,
+      rightBottomNeckAngleEnd - math.pi,
+      2 * math.pi - rightBottomNeckAngleEnd,
+    );
+    _addArc(
+      path,
+      Offset(-_rightBottomNeckCenterX, rightBottomNeckCenterY),
+      _bottomNeckRadius,
+      math.pi - rightBottomNeckAngleEnd,
+      0,
+    );
+
+    // This is the needed extra width for the label.  It is only positive when
+    // the label exceeds the minimum size contained by the round top lobe.
+    final double halfWidthNeeded = math.max(
+      0.0,
+      inverseTextScale * labelHalfWidth - (_topLobeRadius - _labelPadding),
+    );
+
+    final double shift = _getIdealOffset( halfWidthNeeded, overallScale, center, sizeWithOverflow.width);
+    final double leftWidthNeeded = halfWidthNeeded - shift;
+    final double rightWidthNeeded = halfWidthNeeded + shift;
+
+    // The parameter that describes how far along the transition from round to
+    // stretched we are.
+    final double leftAmount = math.max(0.0, math.min(1.0, leftWidthNeeded / _neckTriangleBase));
+    final double rightAmount = math.max(0.0, math.min(1.0, rightWidthNeeded / _neckTriangleBase));
+    // The angle between the top neck arc's center and the top lobe's center
+    // and vertical. The base amount is chosen so that the neck is smooth,
+    // even when the lobe is shifted due to its size.
+    final double leftTheta = (1.0 - leftAmount) * _thirtyDegrees;
+    final double rightTheta = (1.0 - rightAmount) * _thirtyDegrees;
+    // The center of the top left neck arc.
+    final Offset leftTopNeckCenter = Offset(
+      -_neckTriangleBase,
+      _topLobeCenter.dy + math.cos(leftTheta) * _neckTriangleHypotenuse,
+    );
+    final Offset neckRightCenter = Offset(
+      _neckTriangleBase,
+      _topLobeCenter.dy + math.cos(rightTheta) * _neckTriangleHypotenuse,
+    );
+    final double leftNeckArcAngle = _ninetyDegrees - leftTheta;
+    final double rightNeckArcAngle = math.pi + _ninetyDegrees - rightTheta;
+    // The distance between the end of the bottom neck arc and the beginning of
+    // the top neck arc. We use this to shrink/expand it based on the scale
+    // factor of the value indicator.
+    final double neckStretchBaseline = math.max(0.0, rightBottomNeckCenterY - math.max(leftTopNeckCenter.dy, neckRightCenter.dy));
+    final double t = math.pow(inverseTextScale, 3.0) as double;
+    final double stretch = (neckStretchBaseline * t).clamp(0.0, 10.0 * neckStretchBaseline);
+    final Offset neckStretch = Offset(0.0, neckStretchBaseline - stretch);
+
+    assert(!_debuggingLabelLocation || () {
+      final Offset leftCenter = _topLobeCenter - Offset(leftWidthNeeded, 0.0) + neckStretch;
+      final Offset rightCenter = _topLobeCenter + Offset(rightWidthNeeded, 0.0) + neckStretch;
+      final Rect valueRect = Rect.fromLTRB(
+        leftCenter.dx - _topLobeRadius,
+        leftCenter.dy - _topLobeRadius,
+        rightCenter.dx + _topLobeRadius,
+        rightCenter.dy + _topLobeRadius,
+      );
+      final Paint outlinePaint = Paint()
+        ..color = const Color(0xffff0000)
+        ..style = PaintingStyle.stroke
+        ..strokeWidth = 1.0;
+      canvas.drawRect(valueRect, outlinePaint);
+      return true;
+    }());
+
+    _addArc(
+      path,
+      leftTopNeckCenter + neckStretch,
+      _topNeckRadius,
+      0.0,
+      -leftNeckArcAngle,
+    );
+    _addArc(
+      path,
+      _topLobeCenter - Offset(leftWidthNeeded, 0.0) + neckStretch,
+      _topLobeRadius,
+      _ninetyDegrees + leftTheta,
+      _twoSeventyDegrees,
+    );
+    _addArc(
+      path,
+      _topLobeCenter + Offset(rightWidthNeeded, 0.0) + neckStretch,
+      _topLobeRadius,
+      _twoSeventyDegrees,
+      _twoSeventyDegrees + math.pi - rightTheta,
+    );
+    _addArc(
+      path,
+      neckRightCenter + neckStretch,
+      _topNeckRadius,
+      rightNeckArcAngle,
+      math.pi,
+    );
+
+    if (strokePaintColor != null) {
+      final Paint strokePaint = Paint()
+        ..color = strokePaintColor
+        ..strokeWidth = 1.0
+        ..style = PaintingStyle.stroke;
+      canvas.drawPath(path, strokePaint);
+    }
+
+    canvas.drawPath(path, paint);
+
+    // Draw the label.
+    canvas.save();
+    canvas.translate(shift, -_distanceBetweenTopBottomCenters + neckStretch.dy);
+    canvas.scale(inverseTextScale, inverseTextScale);
+    labelPainter.paint(canvas, Offset.zero - Offset(labelHalfWidth, labelPainter.height / 2.0));
+    canvas.restore();
+    canvas.restore();
+  }
+}
+
+/// A callback that formats a numeric value from a [Slider] or [RangeSlider] widget.
+///
+/// See also:
+///
+///  * [Slider.semanticFormatterCallback], which shows an example use case.
+///  * [RangeSlider.semanticFormatterCallback], which shows an example use case.
+typedef SemanticFormatterCallback = String Function(double value);
+
+/// Decides which thumbs (if any) should be selected.
+///
+/// The default finds the closest thumb, but if the thumbs are close to each
+/// other, it waits for movement defined by [dx] to determine the selected
+/// thumb.
+///
+/// Override [SliderThemeData.thumbSelector] for custom thumb selection.
+typedef RangeThumbSelector = Thumb? Function(
+  TextDirection textDirection,
+  RangeValues values,
+  double tapValue,
+  Size thumbSize,
+  Size trackSize,
+  double dx,
+);
+
+/// Object for representing range slider thumb values.
+///
+/// This object is passed into [RangeSlider.values] to set its values, and it
+/// is emitted in [RangeSlider.onChanged], [RangeSlider.onChangeStart], and
+/// [RangeSlider.onChangeEnd] when the values change.
+@immutable
+class RangeValues {
+  /// Creates pair of start and end values.
+  const RangeValues(this.start, this.end);
+
+  /// The value of the start thumb.
+  ///
+  /// For LTR text direction, the start is the left thumb, and for RTL text
+  /// direction, the start is the right thumb.
+  final double start;
+
+  /// The value of the end thumb.
+  ///
+  /// For LTR text direction, the end is the right thumb, and for RTL text
+  /// direction, the end is the left thumb.
+  final double end;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is RangeValues
+        && other.start == start
+        && other.end == end;
+  }
+
+  @override
+  int get hashCode => hashValues(start, end);
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'RangeValues')}($start, $end)';
+  }
+}
+
+/// Object for setting range slider label values that appear in the value
+/// indicator for each thumb.
+///
+/// Used in combination with [SliderThemeData.showValueIndicator] to display
+/// labels above the thumbs.
+@immutable
+class RangeLabels {
+  /// Creates pair of start and end labels.
+  const RangeLabels(this.start, this.end);
+
+  /// The label of the start thumb.
+  ///
+  /// For LTR text direction, the start is the left thumb, and for RTL text
+  /// direction, the start is the right thumb.
+  final String start;
+
+  /// The label of the end thumb.
+  ///
+  /// For LTR text direction, the end is the right thumb, and for RTL text
+  /// direction, the end is the left thumb.
+  final String end;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is RangeLabels
+        && other.start == start
+        && other.end == end;
+  }
+
+  @override
+  int get hashCode => hashValues(start, end);
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'RangeLabels')}($start, $end)';
+  }
+}
diff --git a/lib/src/material/snack_bar.dart b/lib/src/material/snack_bar.dart
new file mode 100644
index 0000000..5d1113f
--- /dev/null
+++ b/lib/src/material/snack_bar.dart
@@ -0,0 +1,632 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'button_style.dart';
+import 'color_scheme.dart';
+import 'material.dart';
+import 'material_state.dart';
+import 'scaffold.dart';
+import 'snack_bar_theme.dart';
+import 'text_button.dart';
+import 'text_button_theme.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+const double _singleLineVerticalPadding = 14.0;
+
+// TODO(ianh): We should check if the given text and actions are going to fit on
+// one line or not, and if they are, use the single-line layout, and if not, use
+// the multiline layout, https://github.com/flutter/flutter/issues/32782
+// See https://material.io/components/snackbars#specs, 'Longer Action Text' does
+// not match spec.
+
+const Duration _snackBarTransitionDuration = Duration(milliseconds: 250);
+const Duration _snackBarDisplayDuration = Duration(milliseconds: 4000);
+const Curve _snackBarHeightCurve = Curves.fastOutSlowIn;
+const Curve _snackBarFadeInCurve = Interval(0.45, 1.0, curve: Curves.fastOutSlowIn);
+const Curve _snackBarFadeOutCurve = Interval(0.72, 1.0, curve: Curves.fastOutSlowIn);
+
+/// Specify how a [SnackBar] was closed.
+///
+/// The [ScaffoldMessengerState.showSnackBar] function returns a
+/// [ScaffoldFeatureController]. The value of the controller's closed property
+/// is a Future that resolves to a SnackBarClosedReason. Applications that need
+/// to know how a snackbar was closed can use this value.
+///
+/// Example:
+///
+/// ```dart
+/// ScaffoldMessenger.of(context).showSnackBar(
+///   SnackBar( ... )
+/// ).closed.then((SnackBarClosedReason reason) {
+///    ...
+/// });
+/// ```
+enum SnackBarClosedReason {
+  /// The snack bar was closed after the user tapped a [SnackBarAction].
+  action,
+
+  /// The snack bar was closed through a [SemanticsAction.dismiss].
+  dismiss,
+
+  /// The snack bar was closed by a user's swipe.
+  swipe,
+
+  /// The snack bar was closed by the [ScaffoldFeatureController] close callback
+  /// or by calling [ScaffoldMessengerState.hideCurrentSnackBar] directly.
+  hide,
+
+  /// The snack bar was closed by an call to [ScaffoldMessengerState.removeCurrentSnackBar].
+  remove,
+
+  /// The snack bar was closed because its timer expired.
+  timeout,
+}
+
+/// A button for a [SnackBar], known as an "action".
+///
+/// Snack bar actions are always enabled. If you want to disable a snack bar
+/// action, simply don't include it in the snack bar.
+///
+/// Snack bar actions can only be pressed once. Subsequent presses are ignored.
+///
+/// See also:
+///
+///  * [SnackBar]
+///  * <https://material.io/design/components/snackbars.html>
+class SnackBarAction extends StatefulWidget {
+  /// Creates an action for a [SnackBar].
+  ///
+  /// The [label] and [onPressed] arguments must be non-null.
+  const SnackBarAction({
+    Key? key,
+    this.textColor,
+    this.disabledTextColor,
+    required this.label,
+    required this.onPressed,
+  }) : assert(label != null),
+       assert(onPressed != null),
+       super(key: key);
+
+  /// The button label color. If not provided, defaults to
+  /// [SnackBarThemeData.actionTextColor].
+  final Color? textColor;
+
+  /// The button disabled label color. This color is shown after the
+  /// [SnackBarAction] is dismissed.
+  final Color? disabledTextColor;
+
+  /// The button label.
+  final String label;
+
+  /// The callback to be called when the button is pressed. Must not be null.
+  ///
+  /// This callback will be called at most once each time this action is
+  /// displayed in a [SnackBar].
+  final VoidCallback onPressed;
+
+  @override
+  State<SnackBarAction> createState() => _SnackBarActionState();
+}
+
+class _SnackBarActionState extends State<SnackBarAction> {
+  bool _haveTriggeredAction = false;
+
+  void _handlePressed() {
+    if (_haveTriggeredAction)
+      return;
+    setState(() {
+      _haveTriggeredAction = true;
+    });
+    widget.onPressed();
+    Scaffold.of(context).hideCurrentSnackBar(reason: SnackBarClosedReason.action);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    Color? resolveForegroundColor(Set<MaterialState> states) {
+      final SnackBarThemeData snackBarTheme = Theme.of(context).snackBarTheme;
+      if (states.contains(MaterialState.disabled))
+        return widget.disabledTextColor ?? snackBarTheme.disabledActionTextColor;
+      return widget.textColor ?? snackBarTheme.actionTextColor;
+    }
+
+    return TextButton(
+      style: ButtonStyle(
+        foregroundColor: MaterialStateProperty.resolveWith<Color?>(resolveForegroundColor),
+      ),
+      onPressed: _haveTriggeredAction ? null : _handlePressed,
+      child: Text(widget.label),
+    );
+  }
+}
+
+/// A lightweight message with an optional action which briefly displays at the
+/// bottom of the screen.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=zpO6n_oZWw0}
+///
+/// To display a snack bar, call `ScaffoldMessenger.of(context).showSnackBar()`,
+/// passing an instance of [SnackBar] that describes the message.
+///
+/// To control how long the [SnackBar] remains visible, specify a [duration].
+///
+/// A SnackBar with an action will not time out when TalkBack or VoiceOver are
+/// enabled. This is controlled by [AccessibilityFeatures.accessibleNavigation].
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_center_no_null_safety}
+///
+/// Here is an example of a [SnackBar] with an [action] button implemented using
+/// [SnackBarAction].
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return ElevatedButton(
+///     child: Text("Show Snackbar"),
+///     onPressed: () {
+///       ScaffoldMessenger.of(context).showSnackBar(
+///         SnackBar(
+///           content: Text("Awesome Snackbar!"),
+///           action: SnackBarAction(
+///             label: "Action",
+///             onPressed: () {
+///               // Code to execute.
+///             },
+///           ),
+///         ),
+///       );
+///     },
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_center_no_null_safety}
+///
+/// Here is an example of a customized [SnackBar]. It utilizes
+/// [behavior], [shape], [padding], [width], and [duration] to customize the
+/// location, appearance, and the duration for which the [SnackBar] is visible.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return ElevatedButton(
+///     child: Text("Show Snackbar"),
+///     onPressed: () {
+///       ScaffoldMessenger.of(context).showSnackBar(
+///         SnackBar(
+///           action: SnackBarAction(
+///             label: "Action",
+///             onPressed: () {
+///               // Code to execute.
+///             },
+///           ),
+///           content: Text("Awesome SnackBar!"),
+///           duration: Duration(milliseconds: 1500),
+///           width: 280.0, // Width of the SnackBar.
+///           padding: EdgeInsets.symmetric(
+///             horizontal: 8.0), // Inner padding for SnackBar content.
+///           behavior: SnackBarBehavior.floating,
+///           shape: RoundedRectangleBorder(
+///             borderRadius: BorderRadius.circular(10.0),
+///           ),
+///         ),
+///       );
+///     },
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ScaffoldMessenger.of], to obtain the current [ScaffoldMessengerState],
+///    which manages the display and animation of snack bars.
+///  * [ScaffoldMessengerState.showSnackBar], which displays a [SnackBar].
+///  * [ScaffoldMessengerState.removeCurrentSnackBar], which abruptly hides the
+///    currently displayed snack bar, if any, and allows the next to be displayed.
+///  * [SnackBarAction], which is used to specify an [action] button to show
+///    on the snack bar.
+///  * [SnackBarThemeData], to configure the default property values for
+///    [SnackBar] widgets.
+///  * <https://material.io/design/components/snackbars.html>
+class SnackBar extends StatefulWidget {
+  /// Creates a snack bar.
+  ///
+  /// The [content] argument must be non-null. The [elevation] must be null or
+  /// non-negative.
+  const SnackBar({
+    Key? key,
+    required this.content,
+    this.backgroundColor,
+    this.elevation,
+    this.margin,
+    this.padding,
+    this.width,
+    this.shape,
+    this.behavior,
+    this.action,
+    this.duration = _snackBarDisplayDuration,
+    this.animation,
+    this.onVisible,
+  }) : assert(elevation == null || elevation >= 0.0),
+       assert(content != null),
+       assert(
+         margin == null || behavior == SnackBarBehavior.floating,
+         'Margin can only be used with floating behavior',
+       ),
+       assert(
+         width == null || behavior == SnackBarBehavior.floating,
+         'Width can only be used with floating behavior',
+       ),
+       assert(
+         width == null || margin == null,
+         'Width and margin can not be used together',
+       ),
+       assert(duration != null),
+       super(key: key);
+
+  /// The primary content of the snack bar.
+  ///
+  /// Typically a [Text] widget.
+  final Widget content;
+
+  /// The snack bar's background color. If not specified it will use
+  /// [SnackBarThemeData.backgroundColor] of [ThemeData.snackBarTheme]. If that
+  /// is not specified it will default to a dark variation of
+  /// [ColorScheme.surface] for light themes, or [ColorScheme.onSurface] for
+  /// dark themes.
+  final Color? backgroundColor;
+
+  /// The z-coordinate at which to place the snack bar. This controls the size
+  /// of the shadow below the snack bar.
+  ///
+  /// Defines the card's [Material.elevation].
+  ///
+  /// If this property is null, then [SnackBarThemeData.elevation] of
+  /// [ThemeData.snackBarTheme] is used, if that is also null, the default value
+  /// is 6.0.
+  final double? elevation;
+
+  /// Empty space to surround the snack bar.
+  ///
+  /// This property is only used when [behavior] is [SnackBarBehavior.floating].
+  /// It can not be used if [width] is specified.
+  ///
+  /// If this property is null, then the default is
+  /// `EdgeInsets.fromLTRB(15.0, 5.0, 15.0, 10.0)`.
+  final EdgeInsetsGeometry? margin;
+
+  /// The amount of padding to apply to the snack bar's content and optional
+  /// action.
+  ///
+  /// If this property is null, then the default depends on the [behavior] and
+  /// the presence of an [action]. The start padding is 24 if [behavior] is
+  /// [SnackBarBehavior.fixed] and 16 if it is [SnackBarBehavior.floating]. If
+  /// there is no [action], the same padding is added to the end.
+  final EdgeInsetsGeometry? padding;
+
+  /// The width of the snack bar.
+  ///
+  /// If width is specified, the snack bar will be centered horizontally in the
+  /// available space. This property is only used when [behavior] is
+  /// [SnackBarBehavior.floating]. It can not be used if [margin] is specified.
+  ///
+  /// If this property is null, then the snack bar will take up the full device
+  /// width less the margin.
+  final double? width;
+
+  /// The shape of the snack bar's [Material].
+  ///
+  /// Defines the snack bar's [Material.shape].
+  ///
+  /// If this property is null then [SnackBarThemeData.shape] of
+  /// [ThemeData.snackBarTheme] is used. If that's null then the shape will
+  /// depend on the [SnackBarBehavior]. For [SnackBarBehavior.fixed], no
+  /// overriding shape is specified, so the [SnackBar] is rectangular. For
+  /// [SnackBarBehavior.floating], it uses a [RoundedRectangleBorder] with a
+  /// circular corner radius of 4.0.
+  final ShapeBorder? shape;
+
+  /// This defines the behavior and location of the snack bar.
+  ///
+  /// Defines where a [SnackBar] should appear within a [Scaffold] and how its
+  /// location should be adjusted when the scaffold also includes a
+  /// [FloatingActionButton] or a [BottomNavigationBar]
+  ///
+  /// If this property is null, then [SnackBarThemeData.behavior] of
+  /// [ThemeData.snackBarTheme] is used. If that is null, then the default is
+  /// [SnackBarBehavior.fixed].
+  final SnackBarBehavior? behavior;
+
+  /// (optional) An action that the user can take based on the snack bar.
+  ///
+  /// For example, the snack bar might let the user undo the operation that
+  /// prompted the snackbar. Snack bars can have at most one action.
+  ///
+  /// The action should not be "dismiss" or "cancel".
+  final SnackBarAction? action;
+
+  /// The amount of time the snack bar should be displayed.
+  ///
+  /// Defaults to 4.0s.
+  ///
+  /// See also:
+  ///
+  ///  * [ScaffoldMessengerState.removeCurrentSnackBar], which abruptly hides the
+  ///    currently displayed snack bar, if any, and allows the next to be
+  ///    displayed.
+  ///  * <https://material.io/design/components/snackbars.html>
+  final Duration duration;
+
+  /// The animation driving the entrance and exit of the snack bar.
+  final Animation<double>? animation;
+
+  /// Called the first time that the snackbar is visible within a [Scaffold].
+  final VoidCallback? onVisible;
+
+  // API for ScaffoldMessengerState.showSnackBar():
+
+  /// Creates an animation controller useful for driving a snack bar's entrance and exit animation.
+  static AnimationController createAnimationController({ required TickerProvider vsync }) {
+    return AnimationController(
+      duration: _snackBarTransitionDuration,
+      debugLabel: 'SnackBar',
+      vsync: vsync,
+    );
+  }
+
+  /// Creates a copy of this snack bar but with the animation replaced with the given animation.
+  ///
+  /// If the original snack bar lacks a key, the newly created snack bar will
+  /// use the given fallback key.
+  SnackBar withAnimation(Animation<double> newAnimation, { Key? fallbackKey }) {
+    return SnackBar(
+      key: key ?? fallbackKey,
+      content: content,
+      backgroundColor: backgroundColor,
+      elevation: elevation,
+      margin: margin,
+      padding: padding,
+      width: width,
+      shape: shape,
+      behavior: behavior,
+      action: action,
+      duration: duration,
+      animation: newAnimation,
+      onVisible: onVisible,
+    );
+  }
+
+  @override
+  State<SnackBar> createState() => _SnackBarState();
+}
+
+class _SnackBarState extends State<SnackBar> {
+  bool _wasVisible = false;
+
+  @override
+  void initState() {
+    super.initState();
+    widget.animation!.addStatusListener(_onAnimationStatusChanged);
+  }
+
+  @override
+  void didUpdateWidget(SnackBar oldWidget) {
+    if (widget.animation != oldWidget.animation) {
+      oldWidget.animation!.removeStatusListener(_onAnimationStatusChanged);
+      widget.animation!.addStatusListener(_onAnimationStatusChanged);
+    }
+    super.didUpdateWidget(oldWidget);
+  }
+
+  @override
+  void dispose() {
+    widget.animation!.removeStatusListener(_onAnimationStatusChanged);
+    super.dispose();
+  }
+
+  void _onAnimationStatusChanged(AnimationStatus animationStatus) {
+    switch (animationStatus) {
+      case AnimationStatus.dismissed:
+      case AnimationStatus.forward:
+      case AnimationStatus.reverse:
+        break;
+      case AnimationStatus.completed:
+        if (widget.onVisible != null && !_wasVisible) {
+          widget.onVisible!();
+        }
+        _wasVisible = true;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    final MediaQueryData mediaQueryData = MediaQuery.of(context);
+    assert(widget.animation != null);
+    final ThemeData theme = Theme.of(context);
+    final ColorScheme colorScheme = theme.colorScheme;
+    final SnackBarThemeData snackBarTheme = theme.snackBarTheme;
+    final bool isThemeDark = theme.brightness == Brightness.dark;
+    final Color buttonColor = isThemeDark ? colorScheme.primaryVariant : colorScheme.secondary;
+
+    // SnackBar uses a theme that is the opposite brightness from
+    // the surrounding theme.
+    final Brightness brightness = isThemeDark ? Brightness.light : Brightness.dark;
+    final Color themeBackgroundColor = isThemeDark
+      ? colorScheme.onSurface
+      : Color.alphaBlend(colorScheme.onSurface.withOpacity(0.80), colorScheme.surface);
+    final ThemeData inverseTheme = theme.copyWith(
+      colorScheme: ColorScheme(
+        primary: colorScheme.onPrimary,
+        primaryVariant: colorScheme.onPrimary,
+        // For the button color, the spec says it should be primaryVariant, but for
+        // backward compatibility on light themes we are leaving it as secondary.
+        secondary: buttonColor,
+        secondaryVariant: colorScheme.onSecondary,
+        surface: colorScheme.onSurface,
+        background: themeBackgroundColor,
+        error: colorScheme.onError,
+        onPrimary: colorScheme.primary,
+        onSecondary: colorScheme.secondary,
+        onSurface: colorScheme.surface,
+        onBackground: colorScheme.background,
+        onError: colorScheme.error,
+        brightness: brightness,
+      ),
+    );
+
+    final TextStyle? contentTextStyle = snackBarTheme.contentTextStyle ?? ThemeData(brightness: brightness).textTheme.subtitle1;
+    final SnackBarBehavior snackBarBehavior = widget.behavior ?? snackBarTheme.behavior ?? SnackBarBehavior.fixed;
+    final bool isFloatingSnackBar = snackBarBehavior == SnackBarBehavior.floating;
+    final double horizontalPadding = isFloatingSnackBar ? 16.0 : 24.0;
+    final EdgeInsetsGeometry padding = widget.padding
+      ?? EdgeInsetsDirectional.only(start: horizontalPadding, end: widget.action != null ? 0 : horizontalPadding);
+
+    final double actionHorizontalMargin = (widget.padding?.resolve(TextDirection.ltr).right ?? horizontalPadding) / 2;
+
+    final CurvedAnimation heightAnimation = CurvedAnimation(parent: widget.animation!, curve: _snackBarHeightCurve);
+    final CurvedAnimation fadeInAnimation = CurvedAnimation(parent: widget.animation!, curve: _snackBarFadeInCurve);
+    final CurvedAnimation fadeOutAnimation = CurvedAnimation(
+      parent: widget.animation!,
+      curve: _snackBarFadeOutCurve,
+      reverseCurve: const Threshold(0.0),
+    );
+
+    Widget snackBar = Padding(
+      padding: padding,
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.center,
+        children: <Widget>[
+          Expanded(
+            child: Container(
+              padding: const EdgeInsets.symmetric(vertical: _singleLineVerticalPadding),
+              child: DefaultTextStyle(
+                style: contentTextStyle!,
+                child: widget.content,
+              ),
+            ),
+          ),
+          if (widget.action != null)
+            Padding(
+              padding: EdgeInsets.symmetric(horizontal: actionHorizontalMargin),
+              child: TextButtonTheme(
+                data: TextButtonThemeData(
+                  style: TextButton.styleFrom(
+                    primary: buttonColor,
+                    padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
+                  ),
+                ),
+                child: widget.action!,
+              ),
+            ),
+        ],
+      ),
+    );
+
+    if (!isFloatingSnackBar) {
+      snackBar = SafeArea(
+        top: false,
+        child: snackBar,
+      );
+    }
+
+    final double elevation = widget.elevation ?? snackBarTheme.elevation ?? 6.0;
+    final Color backgroundColor = widget.backgroundColor ?? snackBarTheme.backgroundColor ?? inverseTheme.colorScheme.background;
+    final ShapeBorder? shape = widget.shape
+      ?? snackBarTheme.shape
+      ?? (isFloatingSnackBar ? RoundedRectangleBorder(borderRadius: BorderRadius.circular(4.0)) : null);
+
+    snackBar = Material(
+      shape: shape,
+      elevation: elevation,
+      color: backgroundColor,
+      child: Theme(
+        data: inverseTheme,
+        child: mediaQueryData.accessibleNavigation
+            ? snackBar
+            : FadeTransition(
+                opacity: fadeOutAnimation,
+                child: snackBar,
+              ),
+      ),
+    );
+
+    if (isFloatingSnackBar) {
+      const double topMargin = 5.0;
+      const double bottomMargin = 10.0;
+      // If width is provided, do not include horizontal margins.
+      if (widget.width != null) {
+        snackBar = Container(
+          margin: const EdgeInsets.only(top: topMargin, bottom: bottomMargin),
+          width: widget.width,
+          child: snackBar,
+        );
+      } else {
+        const double horizontalMargin = 15.0;
+        snackBar = Padding(
+          padding: widget.margin ?? const EdgeInsets.fromLTRB(
+            horizontalMargin,
+            topMargin,
+            horizontalMargin,
+            bottomMargin,
+          ),
+          child: snackBar,
+        );
+      }
+      snackBar = SafeArea(
+        top: false,
+        bottom: false,
+        child: snackBar,
+      );
+    }
+
+    snackBar = Semantics(
+      container: true,
+      liveRegion: true,
+      onDismiss: () {
+        Scaffold.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.dismiss);
+      },
+      child: Dismissible(
+        key: const Key('dismissible'),
+        direction: DismissDirection.down,
+        resizeDuration: null,
+        onDismissed: (DismissDirection direction) {
+          Scaffold.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.swipe);
+        },
+        child: snackBar,
+      ),
+    );
+
+    final Widget snackBarTransition;
+    if (mediaQueryData.accessibleNavigation) {
+      snackBarTransition = snackBar;
+    } else if (isFloatingSnackBar) {
+      snackBarTransition = FadeTransition(
+        opacity: fadeInAnimation,
+        child: snackBar,
+      );
+    } else {
+      snackBarTransition = AnimatedBuilder(
+        animation: heightAnimation,
+        builder: (BuildContext context, Widget? child) {
+          return Align(
+            alignment: AlignmentDirectional.topStart,
+            heightFactor: heightAnimation.value,
+            child: child,
+          );
+        },
+        child: snackBar,
+      );
+    }
+
+    return Hero(
+      child: ClipRect(child: snackBarTransition),
+      tag: '<SnackBar Hero tag - ${widget.content}>',
+    );
+  }
+}
diff --git a/lib/src/material/snack_bar_theme.dart b/lib/src/material/snack_bar_theme.dart
new file mode 100644
index 0000000..1f08de4
--- /dev/null
+++ b/lib/src/material/snack_bar_theme.dart
@@ -0,0 +1,187 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'theme.dart';
+
+/// Defines where a [SnackBar] should appear within a [Scaffold] and how its
+/// location should be adjusted when the scaffold also includes a
+/// [FloatingActionButton] or a [BottomNavigationBar].
+enum SnackBarBehavior {
+  /// Fixes the [SnackBar] at the bottom of the [Scaffold].
+  ///
+  /// The exception is that the [SnackBar] will be shown above a
+  /// [BottomNavigationBar]. Additionally, the [SnackBar] will cause other
+  /// non-fixed widgets inside [Scaffold] to be pushed above (for example, the
+  /// [FloatingActionButton]).
+  fixed,
+
+  /// This behavior will cause [SnackBar] to be shown above other widgets in the
+  /// [Scaffold]. This includes being displayed above a [BottomNavigationBar]
+  /// and a [FloatingActionButton].
+  ///
+  /// See <https://material.io/design/components/snackbars.html> for more details.
+  floating,
+}
+
+/// Customizes default property values for [SnackBar] widgets.
+///
+/// Descendant widgets obtain the current [SnackBarThemeData] object using
+/// `Theme.of(context).snackBarTheme`. Instances of [SnackBarThemeData] can be
+/// customized with [SnackBarThemeData.copyWith].
+///
+/// Typically a [SnackBarThemeData] is specified as part of the overall [Theme]
+/// with [ThemeData.snackBarTheme]. The default for [ThemeData.snackBarTheme]
+/// provides all `null` properties.
+///
+/// All [SnackBarThemeData] properties are `null` by default. When null, the
+/// [SnackBar] will provide its own defaults.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class SnackBarThemeData with Diagnosticable {
+
+  /// Creates a theme that can be used for [ThemeData.snackBarTheme].
+  ///
+  /// The [elevation] must be null or non-negative.
+  const SnackBarThemeData({
+    this.backgroundColor,
+    this.actionTextColor,
+    this.disabledActionTextColor,
+    this.contentTextStyle,
+    this.elevation,
+    this.shape,
+    this.behavior,
+  }) : assert(elevation == null || elevation >= 0.0);
+
+  /// Default value for [SnackBar.backgroundColor].
+  ///
+  /// If null, [SnackBar] defaults to dark grey: `Color(0xFF323232)`.
+  final Color? backgroundColor;
+
+  /// Default value for [SnackBarAction.textColor].
+  ///
+  /// If null, [SnackBarAction] defaults to [ColorScheme.secondary] of
+  /// [ThemeData.colorScheme] .
+  final Color? actionTextColor;
+
+  /// Default value for [SnackBarAction.disabledTextColor].
+  ///
+  /// If null, [SnackBarAction] defaults to [ColorScheme.onSurface] with its
+  /// opacity set to 0.30 if the [Theme]'s brightness is [Brightness.dark], 0.38
+  /// otherwise.
+  final Color? disabledActionTextColor;
+
+  /// Used to configure the [DefaultTextStyle] for the [SnackBar.content] widget.
+  ///
+  /// If null, [SnackBar] defines its default.
+  final TextStyle? contentTextStyle;
+
+  /// Default value for [SnackBar.elevation].
+  ///
+  /// If null, [SnackBar] uses a default of 6.0.
+  final double? elevation;
+
+  /// Default value for [SnackBar.shape].
+  ///
+  /// If null, [SnackBar] provides different defaults depending on the
+  /// [SnackBarBehavior]. For [SnackBarBehavior.fixed], no overriding shape is
+  /// specified, so the [SnackBar] is rectangular. For
+  /// [SnackBarBehavior.floating], it uses a [RoundedRectangleBorder] with a
+  /// circular corner radius of 4.0.
+  final ShapeBorder? shape;
+
+  /// Default value for [SnackBar.behavior].
+  ///
+  /// If null, [SnackBar] will default to [SnackBarBehavior.fixed].
+  final SnackBarBehavior? behavior;
+
+  /// Creates a copy of this object with the given fields replaced with the
+  /// new values.
+  SnackBarThemeData copyWith({
+    Color? backgroundColor,
+    Color? actionTextColor,
+    Color? disabledActionTextColor,
+    TextStyle? contentTextStyle,
+    double? elevation,
+    ShapeBorder? shape,
+    SnackBarBehavior? behavior,
+  }) {
+    return SnackBarThemeData(
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      actionTextColor: actionTextColor ?? this.actionTextColor,
+      disabledActionTextColor: disabledActionTextColor ?? this.disabledActionTextColor,
+      contentTextStyle: contentTextStyle ?? this.contentTextStyle,
+      elevation: elevation ?? this.elevation,
+      shape: shape ?? this.shape,
+      behavior: behavior ?? this.behavior,
+    );
+  }
+
+  /// Linearly interpolate between two SnackBar Themes.
+  ///
+  /// The argument `t` must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static SnackBarThemeData lerp(SnackBarThemeData? a, SnackBarThemeData? b, double t) {
+    assert(t != null);
+    return SnackBarThemeData(
+      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
+      actionTextColor: Color.lerp(a?.actionTextColor, b?.actionTextColor, t),
+      disabledActionTextColor: Color.lerp(a?.disabledActionTextColor, b?.disabledActionTextColor, t),
+      contentTextStyle: TextStyle.lerp(a?.contentTextStyle, b?.contentTextStyle, t),
+      elevation: lerpDouble(a?.elevation, b?.elevation, t),
+      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
+      behavior: t < 0.5 ? a?.behavior : b?.behavior,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      backgroundColor,
+      actionTextColor,
+      disabledActionTextColor,
+      contentTextStyle,
+      elevation,
+      shape,
+      behavior,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is SnackBarThemeData
+        && other.backgroundColor == backgroundColor
+        && other.actionTextColor == actionTextColor
+        && other.disabledActionTextColor == disabledActionTextColor
+        && other.contentTextStyle == contentTextStyle
+        && other.elevation == elevation
+        && other.shape == shape
+        && other.behavior == behavior;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
+    properties.add(ColorProperty('actionTextColor', actionTextColor, defaultValue: null));
+    properties.add(ColorProperty('disabledActionTextColor', disabledActionTextColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('contentTextStyle', contentTextStyle, defaultValue: null));
+    properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
+    properties.add(DiagnosticsProperty<SnackBarBehavior>('behavior', behavior, defaultValue: null));
+  }
+}
diff --git a/lib/src/material/stepper.dart b/lib/src/material/stepper.dart
new file mode 100755
index 0000000..6c3f9ff
--- /dev/null
+++ b/lib/src/material/stepper.dart
@@ -0,0 +1,796 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'button_style.dart';
+import 'color_scheme.dart';
+import 'colors.dart';
+import 'debug.dart';
+import 'icons.dart';
+import 'ink_well.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'material_state.dart';
+import 'text_button.dart';
+import 'text_theme.dart';
+import 'theme.dart';
+
+// TODO(dragostis): Missing functionality:
+//   * mobile horizontal mode with adding/removing steps
+//   * alternative labeling
+//   * stepper feedback in the case of high-latency interactions
+
+/// The state of a [Step] which is used to control the style of the circle and
+/// text.
+///
+/// See also:
+///
+///  * [Step]
+enum StepState {
+  /// A step that displays its index in its circle.
+  indexed,
+
+  /// A step that displays a pencil icon in its circle.
+  editing,
+
+  /// A step that displays a tick icon in its circle.
+  complete,
+
+  /// A step that is disabled and does not to react to taps.
+  disabled,
+
+  /// A step that is currently having an error. e.g. the user has submitted wrong
+  /// input.
+  error,
+}
+
+/// Defines the [Stepper]'s main axis.
+enum StepperType {
+  /// A vertical layout of the steps with their content in-between the titles.
+  vertical,
+
+  /// A horizontal layout of the steps with their content below the titles.
+  horizontal,
+}
+
+const TextStyle _kStepStyle = TextStyle(
+  fontSize: 12.0,
+  color: Colors.white,
+);
+const Color _kErrorLight = Colors.red;
+final Color _kErrorDark = Colors.red.shade400;
+const Color _kCircleActiveLight = Colors.white;
+const Color _kCircleActiveDark = Colors.black87;
+const Color _kDisabledLight = Colors.black38;
+const Color _kDisabledDark = Colors.white38;
+const double _kStepSize = 24.0;
+const double _kTriangleHeight = _kStepSize * 0.866025; // Triangle height. sqrt(3.0) / 2.0
+
+/// A material step used in [Stepper]. The step can have a title and subtitle,
+/// an icon within its circle, some content and a state that governs its
+/// styling.
+///
+/// See also:
+///
+///  * [Stepper]
+///  * <https://material.io/archive/guidelines/components/steppers.html>
+@immutable
+class Step {
+  /// Creates a step for a [Stepper].
+  ///
+  /// The [title], [content], and [state] arguments must not be null.
+  const Step({
+    required this.title,
+    this.subtitle,
+    required this.content,
+    this.state = StepState.indexed,
+    this.isActive = false,
+  }) : assert(title != null),
+       assert(content != null),
+       assert(state != null);
+
+  /// The title of the step that typically describes it.
+  final Widget title;
+
+  /// The subtitle of the step that appears below the title and has a smaller
+  /// font size. It typically gives more details that complement the title.
+  ///
+  /// If null, the subtitle is not shown.
+  final Widget? subtitle;
+
+  /// The content of the step that appears below the [title] and [subtitle].
+  ///
+  /// Below the content, every step has a 'continue' and 'cancel' button.
+  final Widget content;
+
+  /// The state of the step which determines the styling of its components
+  /// and whether steps are interactive.
+  final StepState state;
+
+  /// Whether or not the step is active. The flag only influences styling.
+  final bool isActive;
+}
+
+/// A material stepper widget that displays progress through a sequence of
+/// steps. Steppers are particularly useful in the case of forms where one step
+/// requires the completion of another one, or where multiple steps need to be
+/// completed in order to submit the whole form.
+///
+/// The widget is a flexible wrapper. A parent class should pass [currentStep]
+/// to this widget based on some logic triggered by the three callbacks that it
+/// provides.
+///
+/// {@tool sample --template=stateful_widget_scaffold_center_no_null_safety}
+///
+/// ```dart
+/// int _index = 0;
+/// Widget build(BuildContext context) {
+///   return Container(
+///     height: 300,
+///     width: 300,
+///     child: Stepper(
+///       currentStep: _index,
+///       onStepCancel: () {
+///         if (_index <= 0) {
+///          return;
+///         }
+///         setState(() {
+///           _index--;
+///         });
+///       },
+///       onStepContinue: () {
+///         if (_index >= 1) {
+///          return;
+///         }
+///         setState(() {
+///           _index++;
+///         });
+///       },
+///       onStepTapped: (index) {
+///         setState(() {
+///           _index = index;
+///         });
+///       },
+///       steps: [
+///         Step(
+///           title: Text("Step 1 title"),
+///           content: Container(
+///             alignment: Alignment.centerLeft,
+///             child: Text("Content for Step 1")
+///           ),
+///         ),
+///         Step(
+///           title: Text("Step 2 title"),
+///           content: Text("Content for Step 2"),
+///         ),
+///       ],
+///     ),
+///   );
+/// }
+/// ```
+///
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Step]
+///  * <https://material.io/archive/guidelines/components/steppers.html>
+class Stepper extends StatefulWidget {
+  /// Creates a stepper from a list of steps.
+  ///
+  /// This widget is not meant to be rebuilt with a different list of steps
+  /// unless a key is provided in order to distinguish the old stepper from the
+  /// new one.
+  ///
+  /// The [steps], [type], and [currentStep] arguments must not be null.
+  const Stepper({
+    Key? key,
+    required this.steps,
+    this.physics,
+    this.type = StepperType.vertical,
+    this.currentStep = 0,
+    this.onStepTapped,
+    this.onStepContinue,
+    this.onStepCancel,
+    this.controlsBuilder,
+  }) : assert(steps != null),
+       assert(type != null),
+       assert(currentStep != null),
+       assert(0 <= currentStep && currentStep < steps.length),
+       super(key: key);
+
+  /// The steps of the stepper whose titles, subtitles, icons always get shown.
+  ///
+  /// The length of [steps] must not change.
+  final List<Step> steps;
+
+  /// How the stepper's scroll view should respond to user input.
+  ///
+  /// For example, determines how the scroll view continues to
+  /// animate after the user stops dragging the scroll view.
+  ///
+  /// If the stepper is contained within another scrollable it
+  /// can be helpful to set this property to [ClampingScrollPhysics].
+  final ScrollPhysics? physics;
+
+  /// The type of stepper that determines the layout. In the case of
+  /// [StepperType.horizontal], the content of the current step is displayed
+  /// underneath as opposed to the [StepperType.vertical] case where it is
+  /// displayed in-between.
+  final StepperType type;
+
+  /// The index into [steps] of the current step whose content is displayed.
+  final int currentStep;
+
+  /// The callback called when a step is tapped, with its index passed as
+  /// an argument.
+  final ValueChanged<int>? onStepTapped;
+
+  /// The callback called when the 'continue' button is tapped.
+  ///
+  /// If null, the 'continue' button will be disabled.
+  final VoidCallback? onStepContinue;
+
+  /// The callback called when the 'cancel' button is tapped.
+  ///
+  /// If null, the 'cancel' button will be disabled.
+  final VoidCallback? onStepCancel;
+
+  /// The callback for creating custom controls.
+  ///
+  /// If null, the default controls from the current theme will be used.
+  ///
+  /// This callback which takes in a context and two functions: [onStepContinue]
+  /// and [onStepCancel]. These can be used to control the stepper.
+  /// For example, keeping track of the [currentStep] within the callback can
+  /// change the text of the continue or cancel button depending on which step users are at.
+  ///
+  /// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+  /// Creates a stepper control with custom buttons.
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return Stepper(
+  ///     controlsBuilder:
+  ///       (BuildContext context, { VoidCallback onStepContinue, VoidCallback onStepCancel }) {
+  ///          return Row(
+  ///            children: <Widget>[
+  ///              TextButton(
+  ///                onPressed: onStepContinue,
+  ///                child: const Text('NEXT'),
+  ///              ),
+  ///              TextButton(
+  ///                onPressed: onStepCancel,
+  ///                child: const Text('CANCEL'),
+  ///              ),
+  ///            ],
+  ///          );
+  ///       },
+  ///     steps: const <Step>[
+  ///       Step(
+  ///         title: Text('A'),
+  ///         content: SizedBox(
+  ///           width: 100.0,
+  ///           height: 100.0,
+  ///         ),
+  ///       ),
+  ///       Step(
+  ///         title: Text('B'),
+  ///         content: SizedBox(
+  ///           width: 100.0,
+  ///           height: 100.0,
+  ///         ),
+  ///       ),
+  ///     ],
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  final ControlsWidgetBuilder? controlsBuilder;
+
+  @override
+  _StepperState createState() => _StepperState();
+}
+
+class _StepperState extends State<Stepper> with TickerProviderStateMixin {
+  late List<GlobalKey> _keys;
+  final Map<int, StepState> _oldStates = <int, StepState>{};
+
+  @override
+  void initState() {
+    super.initState();
+    _keys = List<GlobalKey>.generate(
+      widget.steps.length,
+      (int i) => GlobalKey(),
+    );
+
+    for (int i = 0; i < widget.steps.length; i += 1)
+      _oldStates[i] = widget.steps[i].state;
+  }
+
+  @override
+  void didUpdateWidget(Stepper oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    assert(widget.steps.length == oldWidget.steps.length);
+
+    for (int i = 0; i < oldWidget.steps.length; i += 1)
+      _oldStates[i] = oldWidget.steps[i].state;
+  }
+
+  bool _isFirst(int index) {
+    return index == 0;
+  }
+
+  bool _isLast(int index) {
+    return widget.steps.length - 1 == index;
+  }
+
+  bool _isCurrent(int index) {
+    return widget.currentStep == index;
+  }
+
+  bool _isDark() {
+    return Theme.of(context).brightness == Brightness.dark;
+  }
+
+  Widget _buildLine(bool visible) {
+    return Container(
+      width: visible ? 1.0 : 0.0,
+      height: 16.0,
+      color: Colors.grey.shade400,
+    );
+  }
+
+  Widget _buildCircleChild(int index, bool oldState) {
+    final StepState state = oldState ? _oldStates[index]! : widget.steps[index].state;
+    final bool isDarkActive = _isDark() && widget.steps[index].isActive;
+    assert(state != null);
+    switch (state) {
+      case StepState.indexed:
+      case StepState.disabled:
+        return Text(
+          '${index + 1}',
+          style: isDarkActive ? _kStepStyle.copyWith(color: Colors.black87) : _kStepStyle,
+        );
+      case StepState.editing:
+        return Icon(
+          Icons.edit,
+          color: isDarkActive ? _kCircleActiveDark : _kCircleActiveLight,
+          size: 18.0,
+        );
+      case StepState.complete:
+        return Icon(
+          Icons.check,
+          color: isDarkActive ? _kCircleActiveDark : _kCircleActiveLight,
+          size: 18.0,
+        );
+      case StepState.error:
+        return const Text('!', style: _kStepStyle);
+    }
+  }
+
+  Color _circleColor(int index) {
+    final ThemeData themeData = Theme.of(context);
+    if (!_isDark()) {
+      return widget.steps[index].isActive ? themeData.primaryColor : Colors.black38;
+    } else {
+      return widget.steps[index].isActive ? themeData.accentColor : themeData.backgroundColor;
+    }
+  }
+
+  Widget _buildCircle(int index, bool oldState) {
+    return Container(
+      margin: const EdgeInsets.symmetric(vertical: 8.0),
+      width: _kStepSize,
+      height: _kStepSize,
+      child: AnimatedContainer(
+        curve: Curves.fastOutSlowIn,
+        duration: kThemeAnimationDuration,
+        decoration: BoxDecoration(
+          color: _circleColor(index),
+          shape: BoxShape.circle,
+        ),
+        child: Center(
+          child: _buildCircleChild(index, oldState && widget.steps[index].state == StepState.error),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildTriangle(int index, bool oldState) {
+    return Container(
+      margin: const EdgeInsets.symmetric(vertical: 8.0),
+      width: _kStepSize,
+      height: _kStepSize,
+      child: Center(
+        child: SizedBox(
+          width: _kStepSize,
+          height: _kTriangleHeight, // Height of 24dp-long-sided equilateral triangle.
+          child: CustomPaint(
+            painter: _TrianglePainter(
+              color: _isDark() ? _kErrorDark : _kErrorLight,
+            ),
+            child: Align(
+              alignment: const Alignment(0.0, 0.8), // 0.8 looks better than the geometrical 0.33.
+              child: _buildCircleChild(index, oldState && widget.steps[index].state != StepState.error),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildIcon(int index) {
+    if (widget.steps[index].state != _oldStates[index]) {
+      return AnimatedCrossFade(
+        firstChild: _buildCircle(index, true),
+        secondChild: _buildTriangle(index, true),
+        firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
+        secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
+        sizeCurve: Curves.fastOutSlowIn,
+        crossFadeState: widget.steps[index].state == StepState.error ? CrossFadeState.showSecond : CrossFadeState.showFirst,
+        duration: kThemeAnimationDuration,
+      );
+    } else {
+      if (widget.steps[index].state != StepState.error)
+        return _buildCircle(index, false);
+      else
+        return _buildTriangle(index, false);
+    }
+  }
+
+  Widget _buildVerticalControls() {
+    if (widget.controlsBuilder != null)
+      return widget.controlsBuilder!(context, onStepContinue: widget.onStepContinue, onStepCancel: widget.onStepCancel);
+
+    final Color cancelColor;
+    switch (Theme.of(context).brightness) {
+      case Brightness.light:
+        cancelColor = Colors.black54;
+        break;
+      case Brightness.dark:
+        cancelColor = Colors.white70;
+        break;
+    }
+
+    final ThemeData themeData = Theme.of(context);
+    final ColorScheme colorScheme = themeData.colorScheme;
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+
+    const OutlinedBorder buttonShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2)));
+    const EdgeInsets buttonPadding = EdgeInsets.symmetric(horizontal: 16.0);
+
+    return Container(
+      margin: const EdgeInsets.only(top: 16.0),
+      child: ConstrainedBox(
+        constraints: const BoxConstraints.tightFor(height: 48.0),
+        child: Row(
+          // The Material spec no longer includes a Stepper widget. The continue
+          // and cancel button styles have been configured to match the original
+          // version of this widget.
+          children: <Widget>[
+            TextButton(
+              onPressed: widget.onStepContinue,
+              style: ButtonStyle(
+                foregroundColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
+                  return states.contains(MaterialState.disabled) ? null : (_isDark() ? colorScheme.onSurface : colorScheme.onPrimary);
+                }),
+                backgroundColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
+                  return _isDark() || states.contains(MaterialState.disabled) ? null : colorScheme.primary;
+                }),
+                padding: MaterialStateProperty.all<EdgeInsetsGeometry>(buttonPadding),
+                shape: MaterialStateProperty.all<OutlinedBorder>(buttonShape),
+              ),
+              child: Text(localizations.continueButtonLabel),
+            ),
+            Container(
+              margin: const EdgeInsetsDirectional.only(start: 8.0),
+              child: TextButton(
+                onPressed: widget.onStepCancel,
+                style: TextButton.styleFrom(
+                  primary: cancelColor,
+                  padding: buttonPadding,
+                  shape: buttonShape,
+                ),
+                child: Text(localizations.cancelButtonLabel),
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  TextStyle _titleStyle(int index) {
+    final ThemeData themeData = Theme.of(context);
+    final TextTheme textTheme = themeData.textTheme;
+
+    assert(widget.steps[index].state != null);
+    switch (widget.steps[index].state) {
+      case StepState.indexed:
+      case StepState.editing:
+      case StepState.complete:
+        return textTheme.bodyText1!;
+      case StepState.disabled:
+        return textTheme.bodyText1!.copyWith(
+          color: _isDark() ? _kDisabledDark : _kDisabledLight
+        );
+      case StepState.error:
+        return textTheme.bodyText1!.copyWith(
+          color: _isDark() ? _kErrorDark : _kErrorLight
+        );
+    }
+  }
+
+  TextStyle _subtitleStyle(int index) {
+    final ThemeData themeData = Theme.of(context);
+    final TextTheme textTheme = themeData.textTheme;
+
+    assert(widget.steps[index].state != null);
+    switch (widget.steps[index].state) {
+      case StepState.indexed:
+      case StepState.editing:
+      case StepState.complete:
+        return textTheme.caption!;
+      case StepState.disabled:
+        return textTheme.caption!.copyWith(
+          color: _isDark() ? _kDisabledDark : _kDisabledLight
+        );
+      case StepState.error:
+        return textTheme.caption!.copyWith(
+          color: _isDark() ? _kErrorDark : _kErrorLight
+        );
+    }
+  }
+
+  Widget _buildHeaderText(int index) {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      mainAxisSize: MainAxisSize.min,
+      children: <Widget>[
+        AnimatedDefaultTextStyle(
+          style: _titleStyle(index),
+          duration: kThemeAnimationDuration,
+          curve: Curves.fastOutSlowIn,
+          child: widget.steps[index].title,
+        ),
+        if (widget.steps[index].subtitle != null)
+          Container(
+            margin: const EdgeInsets.only(top: 2.0),
+            child: AnimatedDefaultTextStyle(
+              style: _subtitleStyle(index),
+              duration: kThemeAnimationDuration,
+              curve: Curves.fastOutSlowIn,
+              child: widget.steps[index].subtitle!,
+            ),
+          ),
+      ],
+    );
+  }
+
+  Widget _buildVerticalHeader(int index) {
+    return Container(
+      margin: const EdgeInsets.symmetric(horizontal: 24.0),
+      child: Row(
+        children: <Widget>[
+          Column(
+            children: <Widget>[
+              // Line parts are always added in order for the ink splash to
+              // flood the tips of the connector lines.
+              _buildLine(!_isFirst(index)),
+              _buildIcon(index),
+              _buildLine(!_isLast(index)),
+            ],
+          ),
+          Expanded(
+            child: Container(
+              margin: const EdgeInsetsDirectional.only(start: 12.0),
+              child: _buildHeaderText(index),
+            )
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildVerticalBody(int index) {
+    return Stack(
+      children: <Widget>[
+        PositionedDirectional(
+          start: 24.0,
+          top: 0.0,
+          bottom: 0.0,
+          child: SizedBox(
+            width: 24.0,
+            child: Center(
+              child: SizedBox(
+                width: _isLast(index) ? 0.0 : 1.0,
+                child: Container(
+                  color: Colors.grey.shade400,
+                ),
+              ),
+            ),
+          ),
+        ),
+        AnimatedCrossFade(
+          firstChild: Container(height: 0.0),
+          secondChild: Container(
+            margin: const EdgeInsetsDirectional.only(
+              start: 60.0,
+              end: 24.0,
+              bottom: 24.0,
+            ),
+            child: Column(
+              children: <Widget>[
+                widget.steps[index].content,
+                _buildVerticalControls(),
+              ],
+            ),
+          ),
+          firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
+          secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
+          sizeCurve: Curves.fastOutSlowIn,
+          crossFadeState: _isCurrent(index) ? CrossFadeState.showSecond : CrossFadeState.showFirst,
+          duration: kThemeAnimationDuration,
+        ),
+      ],
+    );
+  }
+
+  Widget _buildVertical() {
+    return ListView(
+      shrinkWrap: true,
+      physics: widget.physics,
+      children: <Widget>[
+        for (int i = 0; i < widget.steps.length; i += 1)
+          Column(
+            key: _keys[i],
+            children: <Widget>[
+              InkWell(
+                onTap: widget.steps[i].state != StepState.disabled ? () {
+                  // In the vertical case we need to scroll to the newly tapped
+                  // step.
+                  Scrollable.ensureVisible(
+                    _keys[i].currentContext!,
+                    curve: Curves.fastOutSlowIn,
+                    duration: kThemeAnimationDuration,
+                  );
+
+                  if (widget.onStepTapped != null)
+                    widget.onStepTapped!(i);
+                } : null,
+                canRequestFocus: widget.steps[i].state != StepState.disabled,
+                child: _buildVerticalHeader(i),
+              ),
+              _buildVerticalBody(i),
+            ],
+          ),
+      ],
+    );
+  }
+
+  Widget _buildHorizontal() {
+    final List<Widget> children = <Widget>[
+      for (int i = 0; i < widget.steps.length; i += 1) ...<Widget>[
+        InkResponse(
+          onTap: widget.steps[i].state != StepState.disabled ? () {
+            if (widget.onStepTapped != null)
+              widget.onStepTapped!(i);
+          } : null,
+          canRequestFocus: widget.steps[i].state != StepState.disabled,
+          child: Row(
+            children: <Widget>[
+              Container(
+                height: 72.0,
+                child: Center(
+                  child: _buildIcon(i),
+                ),
+              ),
+              Container(
+                margin: const EdgeInsetsDirectional.only(start: 12.0),
+                child: _buildHeaderText(i),
+              ),
+            ],
+          ),
+        ),
+        if (!_isLast(i))
+          Expanded(
+            child: Container(
+              margin: const EdgeInsets.symmetric(horizontal: 8.0),
+              height: 1.0,
+              color: Colors.grey.shade400,
+            ),
+          ),
+      ],
+    ];
+
+    return Column(
+      children: <Widget>[
+        Material(
+          elevation: 2.0,
+          child: Container(
+            margin: const EdgeInsets.symmetric(horizontal: 24.0),
+            child: Row(
+              children: children,
+            ),
+          ),
+        ),
+        Expanded(
+          child: ListView(
+            physics: widget.physics,
+            padding: const EdgeInsets.all(24.0),
+            children: <Widget>[
+              AnimatedSize(
+                curve: Curves.fastOutSlowIn,
+                duration: kThemeAnimationDuration,
+                vsync: this,
+                child: widget.steps[widget.currentStep].content,
+              ),
+              _buildVerticalControls(),
+            ],
+          ),
+        ),
+      ],
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+    assert(() {
+      if (context.findAncestorWidgetOfExactType<Stepper>() != null)
+        throw FlutterError(
+          'Steppers must not be nested.\n'
+          'The material specification advises that one should avoid embedding '
+          'steppers within steppers. '
+          'https://material.io/archive/guidelines/components/steppers.html#steppers-usage'
+        );
+      return true;
+    }());
+    assert(widget.type != null);
+    switch (widget.type) {
+      case StepperType.vertical:
+        return _buildVertical();
+      case StepperType.horizontal:
+        return _buildHorizontal();
+    }
+  }
+}
+
+// Paints a triangle whose base is the bottom of the bounding rectangle and its
+// top vertex the middle of its top.
+class _TrianglePainter extends CustomPainter {
+  _TrianglePainter({
+    required this.color,
+  });
+
+  final Color color;
+
+  @override
+  bool hitTest(Offset point) => true; // Hitting the rectangle is fine enough.
+
+  @override
+  bool shouldRepaint(_TrianglePainter oldPainter) {
+    return oldPainter.color != color;
+  }
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final double base = size.width;
+    final double halfBase = size.width / 2.0;
+    final double height = size.height;
+    final List<Offset> points = <Offset>[
+      Offset(0.0, height),
+      Offset(base, height),
+      Offset(halfBase, 0.0),
+    ];
+
+    canvas.drawPath(
+      Path()..addPolygon(points, true),
+      Paint()..color = color,
+    );
+  }
+}
diff --git a/lib/src/material/switch.dart b/lib/src/material/switch.dart
new file mode 100644
index 0000000..0bbf168
--- /dev/null
+++ b/lib/src/material/switch.dart
@@ -0,0 +1,1070 @@
+// Copyright 2014 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.
+
+import 'package:flute/cupertino.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'constants.dart';
+import 'debug.dart';
+import 'material_state.dart';
+import 'shadows.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+import 'toggleable.dart';
+
+const double _kTrackHeight = 14.0;
+const double _kTrackWidth = 33.0;
+const double _kTrackRadius = _kTrackHeight / 2.0;
+const double _kThumbRadius = 10.0;
+const double _kSwitchMinSize = kMinInteractiveDimension - 8.0;
+const double _kSwitchWidth = _kTrackWidth - 2 * _kTrackRadius + _kSwitchMinSize;
+const double _kSwitchHeight = _kSwitchMinSize + 8.0;
+const double _kSwitchHeightCollapsed = _kSwitchMinSize;
+
+enum _SwitchType { material, adaptive }
+
+/// A material design switch.
+///
+/// Used to toggle the on/off state of a single setting.
+///
+/// The switch itself does not maintain any state. Instead, when the state of
+/// the switch changes, the widget calls the [onChanged] callback. Most widgets
+/// that use a switch will listen for the [onChanged] callback and rebuild the
+/// switch with a new [value] to update the visual appearance of the switch.
+///
+/// If the [onChanged] callback is null, then the switch will be disabled (it
+/// will not respond to input). A disabled switch's thumb and track are rendered
+/// in shades of grey by default. The default appearance of a disabled switch
+/// can be overridden with [inactiveThumbColor] and [inactiveTrackColor].
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// See also:
+///
+///  * [SwitchListTile], which combines this widget with a [ListTile] so that
+///    you can give the switch a label.
+///  * [Checkbox], another widget with similar semantics.
+///  * [Radio], for selecting among a set of explicit values.
+///  * [Slider], for selecting a value in a range.
+///  * <https://material.io/design/components/selection-controls.html#switches>
+class Switch extends StatefulWidget {
+  /// Creates a material design switch.
+  ///
+  /// The switch itself does not maintain any state. Instead, when the state of
+  /// the switch changes, the widget calls the [onChanged] callback. Most widgets
+  /// that use a switch will listen for the [onChanged] callback and rebuild the
+  /// switch with a new [value] to update the visual appearance of the switch.
+  ///
+  /// The following arguments are required:
+  ///
+  /// * [value] determines whether this switch is on or off.
+  /// * [onChanged] is called when the user toggles the switch on or off.
+  const Switch({
+    Key? key,
+    required this.value,
+    required this.onChanged,
+    this.activeColor,
+    this.activeTrackColor,
+    this.inactiveThumbColor,
+    this.inactiveTrackColor,
+    this.activeThumbImage,
+    this.onActiveThumbImageError,
+    this.inactiveThumbImage,
+    this.onInactiveThumbImageError,
+    this.thumbColor,
+    this.trackColor,
+    this.materialTapTargetSize,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.mouseCursor,
+    this.focusColor,
+    this.hoverColor,
+    this.overlayColor,
+    this.splashRadius,
+    this.focusNode,
+    this.autofocus = false,
+  })  : _switchType = _SwitchType.material,
+        assert(dragStartBehavior != null),
+        assert(activeThumbImage != null || onActiveThumbImageError == null),
+        assert(inactiveThumbImage != null || onInactiveThumbImageError == null),
+        super(key: key);
+
+  /// Creates an adaptive [Switch] based on whether the target platform is iOS
+  /// or macOS, following Material design's
+  /// [Cross-platform guidelines](https://material.io/design/platform-guidance/cross-platform-adaptation.html).
+  ///
+  /// On iOS and macOS, this constructor creates a [CupertinoSwitch], which has
+  /// matching functionality and presentation as Material switches, and are the
+  /// graphics expected on iOS. On other platforms, this creates a Material
+  /// design [Switch].
+  ///
+  /// If a [CupertinoSwitch] is created, the following parameters are ignored:
+  /// [activeTrackColor], [inactiveThumbColor], [inactiveTrackColor],
+  /// [activeThumbImage], [onActiveThumbImageError], [inactiveThumbImage],
+  /// [onInactiveThumbImageError], [materialTapTargetSize].
+  ///
+  /// The target platform is based on the current [Theme]: [ThemeData.platform].
+  const Switch.adaptive({
+    Key? key,
+    required this.value,
+    required this.onChanged,
+    this.activeColor,
+    this.activeTrackColor,
+    this.inactiveThumbColor,
+    this.inactiveTrackColor,
+    this.activeThumbImage,
+    this.onActiveThumbImageError,
+    this.inactiveThumbImage,
+    this.onInactiveThumbImageError,
+    this.materialTapTargetSize,
+    this.thumbColor,
+    this.trackColor,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.mouseCursor,
+    this.focusColor,
+    this.hoverColor,
+    this.overlayColor,
+    this.splashRadius,
+    this.focusNode,
+    this.autofocus = false,
+  })  : assert(autofocus != null),
+        assert(activeThumbImage != null || onActiveThumbImageError == null),
+        assert(inactiveThumbImage != null || onInactiveThumbImageError == null),
+        _switchType = _SwitchType.adaptive,
+        super(key: key);
+
+  /// Whether this switch is on or off.
+  ///
+  /// This property must not be null.
+  final bool value;
+
+  /// Called when the user toggles the switch on or off.
+  ///
+  /// The switch passes the new value to the callback but does not actually
+  /// change state until the parent widget rebuilds the switch with the new
+  /// value.
+  ///
+  /// If null, the switch will be displayed as disabled.
+  ///
+  /// The callback provided to [onChanged] should update the state of the parent
+  /// [StatefulWidget] using the [State.setState] method, so that the parent
+  /// gets rebuilt; for example:
+  ///
+  /// ```dart
+  /// Switch(
+  ///   value: _giveVerse,
+  ///   onChanged: (bool newValue) {
+  ///     setState(() {
+  ///       _giveVerse = newValue;
+  ///     });
+  ///   },
+  /// )
+  /// ```
+  final ValueChanged<bool>? onChanged;
+
+  /// The color to use when this switch is on.
+  ///
+  /// Defaults to [ThemeData.toggleableActiveColor].
+  ///
+  /// If [thumbColor] returns a non-null color in the [MaterialState.selected]
+  /// state, it will be used instead of this color.
+  final Color? activeColor;
+
+  /// The color to use on the track when this switch is on.
+  ///
+  /// Defaults to [ThemeData.toggleableActiveColor] with the opacity set at 50%.
+  ///
+  /// Ignored if this switch is created with [Switch.adaptive].
+  ///
+  /// If [trackColor] returns a non-null color in the [MaterialState.selected]
+  /// state, it will be used instead of this color.
+  final Color? activeTrackColor;
+
+  /// The color to use on the thumb when this switch is off.
+  ///
+  /// Defaults to the colors described in the Material design specification.
+  ///
+  /// Ignored if this switch is created with [Switch.adaptive].
+  ///
+  /// If [thumbColor] returns a non-null color in the default state, it will be
+  /// used instead of this color.
+  final Color? inactiveThumbColor;
+
+  /// The color to use on the track when this switch is off.
+  ///
+  /// Defaults to the colors described in the Material design specification.
+  ///
+  /// Ignored if this switch is created with [Switch.adaptive].
+  ///
+  /// If [trackColor] returns a non-null color in the default state, it will be
+  /// used instead of this color.
+  final Color? inactiveTrackColor;
+
+  /// An image to use on the thumb of this switch when the switch is on.
+  ///
+  /// Ignored if this switch is created with [Switch.adaptive].
+  final ImageProvider? activeThumbImage;
+
+  /// An optional error callback for errors emitted when loading
+  /// [activeThumbImage].
+  final ImageErrorListener? onActiveThumbImageError;
+
+  /// An image to use on the thumb of this switch when the switch is off.
+  ///
+  /// Ignored if this switch is created with [Switch.adaptive].
+  final ImageProvider? inactiveThumbImage;
+
+  /// An optional error callback for errors emitted when loading
+  /// [inactiveThumbImage].
+  final ImageErrorListener? onInactiveThumbImageError;
+
+  /// {@template flutter.material.switch.thumbColor}
+  /// The color of this [Switch]'s thumb.
+  ///
+  /// Resolved in the following states:
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [activeColor] is used in the selected
+  /// state and [inactiveThumbColor] in the default state. If that is also null,
+  /// then the value of [SwitchThemeData.thumbColor] is used. If that is also
+  /// null, then the following colors are used:
+  ///
+  /// | State    | Light theme                       | Dark theme                        |
+  /// |----------|-----------------------------------|-----------------------------------|
+  /// | Default  | `Colors.grey.shade50`             | `Colors.grey.shade400`            |
+  /// | Selected | [ThemeData.toggleableActiveColor] | [ThemeData.toggleableActiveColor] |
+  /// | Disabled | `Colors.grey.shade400`            | `Colors.grey.shade800`            |
+  final MaterialStateProperty<Color?>? thumbColor;
+
+  /// {@template flutter.material.switch.trackColor}
+  /// The color of this [Switch]'s track.
+  ///
+  /// Resolved in the following states:
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [activeTrackColor] is used in the selected
+  /// state and [inactiveTrackColor] in the default state. If that is also null,
+  /// then the value of [SwitchThemeData.trackColor] is used. If that is also
+  /// null, then the following colors are used:
+  ///
+  /// | State    | Light theme                     | Dark theme                      |
+  /// |----------|---------------------------------|---------------------------------|
+  /// | Default  | `Colors.grey.shade50`           | `Colors.grey.shade400`          |
+  /// | Selected | [activeColor] with alpha `0x80` | [activeColor] with alpha `0x80` |
+  /// | Disabled | `Color(0x52000000)`             | `Colors.white30`                |
+  final MaterialStateProperty<Color?>? trackColor;
+
+  /// {@template flutter.material.switch.materialTapTargetSize}
+  /// Configures the minimum size of the tap target.
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [SwitchThemeData.materialTapTargetSize] is
+  /// used. If that is also null, then the value of
+  /// [ThemeData.materialTapTargetSize] is used.
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialTapTargetSize], for a description of how this affects tap targets.
+  final MaterialTapTargetSize? materialTapTargetSize;
+
+  final _SwitchType _switchType;
+
+  /// {@macro flutter.cupertino.CupertinoSwitch.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  /// {@template flutter.material.switch.mouseCursor}
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [SwitchThemeData.mouseCursor] is used. If that
+  /// is also null, then [MaterialStateMouseCursor.clickable] is used.
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialStateMouseCursor], a [MouseCursor] that implements
+  ///    `MaterialStateProperty` which is used in APIs that need to accept
+  ///    either a [MouseCursor] or a [MaterialStateProperty<MouseCursor>].
+  final MouseCursor? mouseCursor;
+
+  /// The color for the button's [Material] when it has the input focus.
+  ///
+  /// If [overlayColor] returns a non-null color in the [MaterialState.focused]
+  /// state, it will be used instead.
+  ///
+  /// If null, then the value of [SwitchThemeData.overlayColor] is used in the
+  /// focused state. If that is also null, then the value of
+  /// [ThemeData.focusColor] is used.
+  final Color? focusColor;
+
+  /// The color for the button's [Material] when a pointer is hovering over it.
+  ///
+  /// If [overlayColor] returns a non-null color in the [MaterialState.hovered]
+  /// state, it will be used instead.
+  ///
+  /// If null, then the value of [SwitchThemeData.overlayColor] is used in the
+  /// hovered state. If that is also null, then the value of
+  /// [ThemeData.hoverColor] is used.
+  final Color? hoverColor;
+
+  /// {@template flutter.material.switch.overlayColor}
+  /// The color for the switch's [Material].
+  ///
+  /// Resolves in the following states:
+  ///  * [MaterialState.pressed].
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [activeColor] with alpha
+  /// [kRadialReactionAlpha], [focusColor] and [hoverColor] is used in the
+  /// pressed, focused and hovered state. If that is also null,
+  /// the value of [SwitchThemeData.overlayColor] is used. If that is
+  /// also null, then the value of [ThemeData.toggleableActiveColor] with alpha
+  /// [kRadialReactionAlpha], [ThemeData.focusColor] and [ThemeData.hoverColor]
+  /// is used in the pressed, focused and hovered state.
+  final MaterialStateProperty<Color?>? overlayColor;
+
+  /// {@template flutter.material.switch.splashRadius}
+  /// The splash radius of the circular [Material] ink response.
+  /// {@endtemplate}
+  ///
+  /// If null, then the value of [SwitchThemeData.splashRadius] is used. If that
+  /// is also null, then [kRadialReactionRadius] is used.
+  final double? splashRadius;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  @override
+  _SwitchState createState() => _SwitchState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('value', value: value, ifTrue: 'on', ifFalse: 'off', showName: true));
+    properties.add(ObjectFlagProperty<ValueChanged<bool>>('onChanged', onChanged, ifNull: 'disabled'));
+  }
+}
+
+class _SwitchState extends State<Switch> with TickerProviderStateMixin {
+  late Map<Type, Action<Intent>> _actionMap;
+
+  @override
+  void initState() {
+    super.initState();
+    _actionMap = <Type, Action<Intent>>{
+      ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: _actionHandler),
+    };
+  }
+
+  void _actionHandler(ActivateIntent intent) {
+    if (widget.onChanged != null) {
+      widget.onChanged!(!widget.value);
+    }
+    final RenderObject renderObject = context.findRenderObject()!;
+    renderObject.sendSemanticsEvent(const TapSemanticEvent());
+  }
+
+  bool _focused = false;
+  void _handleFocusHighlightChanged(bool focused) {
+    if (focused != _focused) {
+      setState(() { _focused = focused; });
+    }
+  }
+
+  bool _hovering = false;
+  void _handleHoverChanged(bool hovering) {
+    if (hovering != _hovering) {
+      setState(() { _hovering = hovering; });
+    }
+  }
+
+  Size getSwitchSize(ThemeData theme) {
+    final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize
+      ?? theme.switchTheme.materialTapTargetSize
+      ?? theme.materialTapTargetSize;
+    switch (effectiveMaterialTapTargetSize) {
+      case MaterialTapTargetSize.padded:
+        return const Size(_kSwitchWidth, _kSwitchHeight);
+      case MaterialTapTargetSize.shrinkWrap:
+        return const Size(_kSwitchWidth, _kSwitchHeightCollapsed);
+    }
+  }
+
+  bool get enabled => widget.onChanged != null;
+
+  void _didFinishDragging() {
+    // The user has finished dragging the thumb of this switch. Rebuild the switch
+    // to update the animation.
+    setState(() {});
+  }
+
+  Set<MaterialState> get _states => <MaterialState>{
+    if (!enabled) MaterialState.disabled,
+    if (_hovering) MaterialState.hovered,
+    if (_focused) MaterialState.focused,
+    if (widget.value) MaterialState.selected,
+  };
+
+  MaterialStateProperty<Color?> get _widgetThumbColor {
+    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.disabled)) {
+        return widget.inactiveThumbColor;
+      }
+      if (states.contains(MaterialState.selected)) {
+        return widget.activeColor;
+      }
+      return widget.inactiveThumbColor;
+    });
+  }
+
+  MaterialStateProperty<Color> get _defaultThumbColor {
+    final ThemeData theme = Theme.of(context);
+    final bool isDark = theme.brightness == Brightness.dark;
+
+    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.disabled)) {
+        return isDark ? Colors.grey.shade800 : Colors.grey.shade400;
+      }
+      if (states.contains(MaterialState.selected)) {
+        return theme.toggleableActiveColor;
+      }
+      return isDark ? Colors.grey.shade400 : Colors.grey.shade50;
+    });
+  }
+
+  MaterialStateProperty<Color?> get _widgetTrackColor {
+    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.disabled)) {
+        return widget.inactiveTrackColor;
+      }
+      if (states.contains(MaterialState.selected)) {
+        return widget.activeTrackColor;
+      }
+      return widget.inactiveTrackColor;
+    });
+  }
+
+  MaterialStateProperty<Color> get _defaultTrackColor {
+    final ThemeData theme = Theme.of(context);
+    final bool isDark = theme.brightness == Brightness.dark;
+    const Color black32 = Color(0x52000000); // Black with 32% opacity
+
+    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.disabled)) {
+        return isDark ? Colors.white10 : Colors.black12;
+      }
+      if (states.contains(MaterialState.selected)) {
+        final Set<MaterialState> activeState = states..add(MaterialState.selected);
+        final Color activeColor = _widgetThumbColor.resolve(activeState) ?? _defaultThumbColor.resolve(activeState);
+        return activeColor.withAlpha(0x80);
+      }
+      return isDark ? Colors.white30 : black32;
+    });
+  }
+
+  Widget buildMaterialSwitch(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    final ThemeData theme = Theme.of(context);
+
+    // Colors need to be resolved in selected and non selected states separately
+    // so that they can be lerped between.
+    final Set<MaterialState> activeStates = _states..add(MaterialState.selected);
+    final Set<MaterialState> inactiveStates = _states..remove(MaterialState.selected);
+    final Color effectiveActiveThumbColor = widget.thumbColor?.resolve(activeStates)
+      ?? _widgetThumbColor.resolve(activeStates)
+      ?? theme.switchTheme.thumbColor?.resolve(activeStates)
+      ?? _defaultThumbColor.resolve(activeStates);
+    final Color effectiveInactiveThumbColor = widget.thumbColor?.resolve(inactiveStates)
+      ?? _widgetThumbColor.resolve(inactiveStates)
+      ?? theme.switchTheme.thumbColor?.resolve(inactiveStates)
+      ?? _defaultThumbColor.resolve(inactiveStates);
+    final Color effectiveActiveTrackColor = widget.trackColor?.resolve(activeStates)
+      ?? _widgetTrackColor.resolve(activeStates)
+      ?? theme.switchTheme.trackColor?.resolve(activeStates)
+      ?? _defaultTrackColor.resolve(activeStates);
+    final Color effectiveInactiveTrackColor = widget.trackColor?.resolve(inactiveStates)
+      ?? _widgetTrackColor.resolve(inactiveStates)
+      ?? theme.switchTheme.trackColor?.resolve(inactiveStates)
+      ?? _defaultTrackColor.resolve(inactiveStates);
+
+
+    final Set<MaterialState> focusedStates = _states..add(MaterialState.focused);
+    final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
+      ?? widget.focusColor
+      ?? theme.switchTheme.overlayColor?.resolve(focusedStates)
+      ?? theme.focusColor;
+
+    final Set<MaterialState> hoveredStates = _states..add(MaterialState.hovered);
+    final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
+        ?? widget.hoverColor
+        ?? theme.switchTheme.overlayColor?.resolve(hoveredStates)
+        ?? theme.hoverColor;
+
+    final Set<MaterialState> activePressedStates = activeStates..add(MaterialState.pressed);
+    final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates)
+        ?? theme.switchTheme.overlayColor?.resolve(activePressedStates)
+        ?? effectiveActiveThumbColor.withAlpha(kRadialReactionAlpha);
+
+    final Set<MaterialState> inactivePressedStates = inactiveStates..add(MaterialState.pressed);
+    final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates)
+        ?? theme.switchTheme.overlayColor?.resolve(inactivePressedStates)
+        ?? effectiveActiveThumbColor.withAlpha(kRadialReactionAlpha);
+
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, _states)
+      ?? theme.switchTheme.mouseCursor?.resolve(_states)
+      ?? MaterialStateProperty.resolveAs<MouseCursor>(MaterialStateMouseCursor.clickable, _states);
+
+    return FocusableActionDetector(
+      actions: _actionMap,
+      focusNode: widget.focusNode,
+      autofocus: widget.autofocus,
+      enabled: enabled,
+      onShowFocusHighlight: _handleFocusHighlightChanged,
+      onShowHoverHighlight: _handleHoverChanged,
+      mouseCursor: effectiveMouseCursor,
+      child: Builder(
+        builder: (BuildContext context) {
+          return _SwitchRenderObjectWidget(
+            dragStartBehavior: widget.dragStartBehavior,
+            value: widget.value,
+            activeColor: effectiveActiveThumbColor,
+            inactiveColor: effectiveInactiveThumbColor,
+            surfaceColor: theme.colorScheme.surface,
+            focusColor: effectiveFocusOverlayColor,
+            hoverColor: effectiveHoverOverlayColor,
+            reactionColor: effectiveActivePressedOverlayColor,
+            inactiveReactionColor: effectiveInactivePressedOverlayColor,
+            splashRadius: widget.splashRadius ?? theme.switchTheme.splashRadius ?? kRadialReactionRadius,
+            activeThumbImage: widget.activeThumbImage,
+            onActiveThumbImageError: widget.onActiveThumbImageError,
+            inactiveThumbImage: widget.inactiveThumbImage,
+            onInactiveThumbImageError: widget.onInactiveThumbImageError,
+            activeTrackColor: effectiveActiveTrackColor,
+            inactiveTrackColor: effectiveInactiveTrackColor,
+            configuration: createLocalImageConfiguration(context),
+            onChanged: widget.onChanged,
+            additionalConstraints: BoxConstraints.tight(getSwitchSize(theme)),
+            hasFocus: _focused,
+            hovering: _hovering,
+            state: this,
+          );
+        },
+      ),
+    );
+  }
+
+  Widget buildCupertinoSwitch(BuildContext context) {
+    final Size size = getSwitchSize(Theme.of(context));
+    return Focus(
+      focusNode: widget.focusNode,
+      autofocus: widget.autofocus,
+      child: Container(
+        width: size.width, // Same size as the Material switch.
+        height: size.height,
+        alignment: Alignment.center,
+        child: CupertinoSwitch(
+          dragStartBehavior: widget.dragStartBehavior,
+          value: widget.value,
+          onChanged: widget.onChanged,
+          activeColor: widget.activeColor,
+          trackColor: widget.inactiveTrackColor
+        ),
+      ),
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    switch (widget._switchType) {
+      case _SwitchType.material:
+        return buildMaterialSwitch(context);
+
+      case _SwitchType.adaptive: {
+        final ThemeData theme = Theme.of(context);
+        assert(theme.platform != null);
+        switch (theme.platform) {
+          case TargetPlatform.android:
+          case TargetPlatform.fuchsia:
+          case TargetPlatform.linux:
+          case TargetPlatform.windows:
+            return buildMaterialSwitch(context);
+          case TargetPlatform.iOS:
+          case TargetPlatform.macOS:
+            return buildCupertinoSwitch(context);
+        }
+      }
+    }
+  }
+}
+
+class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
+  const _SwitchRenderObjectWidget({
+    Key? key,
+    required this.value,
+    required this.activeColor,
+    required this.inactiveColor,
+    required this.hoverColor,
+    required this.focusColor,
+    required this.reactionColor,
+    required this.inactiveReactionColor,
+    required this.splashRadius,
+    required this.activeThumbImage,
+    required this.onActiveThumbImageError,
+    required this.inactiveThumbImage,
+    required this.onInactiveThumbImageError,
+    required this.activeTrackColor,
+    required this.inactiveTrackColor,
+    required this.configuration,
+    required this.onChanged,
+    required this.additionalConstraints,
+    required this.dragStartBehavior,
+    required this.hasFocus,
+    required this.hovering,
+    required this.state,
+    required this.surfaceColor,
+  }) : super(key: key);
+
+  final bool value;
+  final Color activeColor;
+  final Color inactiveColor;
+  final Color hoverColor;
+  final Color focusColor;
+  final Color reactionColor;
+  final Color inactiveReactionColor;
+  final double splashRadius;
+  final ImageProvider? activeThumbImage;
+  final ImageErrorListener? onActiveThumbImageError;
+  final ImageProvider? inactiveThumbImage;
+  final ImageErrorListener? onInactiveThumbImageError;
+  final Color activeTrackColor;
+  final Color inactiveTrackColor;
+  final ImageConfiguration configuration;
+  final ValueChanged<bool>? onChanged;
+  final BoxConstraints additionalConstraints;
+  final DragStartBehavior dragStartBehavior;
+  final bool hasFocus;
+  final bool hovering;
+  final _SwitchState state;
+  final Color surfaceColor;
+
+  @override
+  _RenderSwitch createRenderObject(BuildContext context) {
+    return _RenderSwitch(
+      dragStartBehavior: dragStartBehavior,
+      value: value,
+      activeColor: activeColor,
+      inactiveColor: inactiveColor,
+      hoverColor: hoverColor,
+      focusColor: focusColor,
+      reactionColor: reactionColor,
+      inactiveReactionColor: inactiveReactionColor,
+      splashRadius: splashRadius,
+      activeThumbImage: activeThumbImage,
+      onActiveThumbImageError: onActiveThumbImageError,
+      inactiveThumbImage: inactiveThumbImage,
+      onInactiveThumbImageError: onInactiveThumbImageError,
+      activeTrackColor: activeTrackColor,
+      inactiveTrackColor: inactiveTrackColor,
+      configuration: configuration,
+      onChanged: onChanged != null ? _handleValueChanged : null,
+      textDirection: Directionality.of(context),
+      additionalConstraints: additionalConstraints,
+      hasFocus: hasFocus,
+      hovering: hovering,
+      state: state,
+      surfaceColor: surfaceColor,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderSwitch renderObject) {
+    renderObject
+      ..value = value
+      ..activeColor = activeColor
+      ..inactiveColor = inactiveColor
+      ..hoverColor = hoverColor
+      ..focusColor = focusColor
+      ..reactionColor = reactionColor
+      ..inactiveReactionColor = inactiveReactionColor
+      ..splashRadius = splashRadius
+      ..activeThumbImage = activeThumbImage
+      ..onActiveThumbImageError = onActiveThumbImageError
+      ..inactiveThumbImage = inactiveThumbImage
+      ..onInactiveThumbImageError = onInactiveThumbImageError
+      ..activeTrackColor = activeTrackColor
+      ..inactiveTrackColor = inactiveTrackColor
+      ..configuration = configuration
+      ..onChanged = onChanged != null ? _handleValueChanged : null
+      ..textDirection = Directionality.of(context)
+      ..additionalConstraints = additionalConstraints
+      ..dragStartBehavior = dragStartBehavior
+      ..hasFocus = hasFocus
+      ..hovering = hovering
+      ..vsync = state
+      ..surfaceColor = surfaceColor;
+  }
+
+  void _handleValueChanged(bool? value) {
+    // Wrap the onChanged callback because the RenderToggleable supports tri-state
+    // values (i.e. value can be null), but the Switch doesn't. We pass false
+    // for the tristate param to RenderToggleable, so value should never
+    // be null.
+    assert(value != null);
+    if (onChanged != null) {
+      onChanged!(value!);
+    }
+  }
+}
+
+class _RenderSwitch extends RenderToggleable {
+  _RenderSwitch({
+    required bool value,
+    required Color activeColor,
+    required Color inactiveColor,
+    required Color hoverColor,
+    required Color focusColor,
+    required Color reactionColor,
+    required Color inactiveReactionColor,
+    required double splashRadius,
+    required ImageProvider? activeThumbImage,
+    required ImageErrorListener? onActiveThumbImageError,
+    required ImageProvider? inactiveThumbImage,
+    required ImageErrorListener? onInactiveThumbImageError,
+    required Color activeTrackColor,
+    required Color inactiveTrackColor,
+    required ImageConfiguration configuration,
+    required BoxConstraints additionalConstraints,
+    required TextDirection textDirection,
+    required ValueChanged<bool?>? onChanged,
+    required DragStartBehavior dragStartBehavior,
+    required bool hasFocus,
+    required bool hovering,
+    required this.state,
+    required Color surfaceColor,
+  }) : assert(textDirection != null),
+       _activeThumbImage = activeThumbImage,
+       _onActiveThumbImageError = onActiveThumbImageError,
+       _inactiveThumbImage = inactiveThumbImage,
+       _onInactiveThumbImageError = onInactiveThumbImageError,
+       _activeTrackColor = activeTrackColor,
+       _inactiveTrackColor = inactiveTrackColor,
+       _configuration = configuration,
+       _textDirection = textDirection,
+       _surfaceColor = surfaceColor,
+       super(
+         value: value,
+         tristate: false,
+         activeColor: activeColor,
+         inactiveColor: inactiveColor,
+         hoverColor: hoverColor,
+         focusColor: focusColor,
+         reactionColor: reactionColor,
+         inactiveReactionColor: inactiveReactionColor,
+         splashRadius: splashRadius,
+         onChanged: onChanged,
+         additionalConstraints: additionalConstraints,
+         hasFocus: hasFocus,
+         hovering: hovering,
+         vsync: state,
+       ) {
+    _drag = HorizontalDragGestureRecognizer()
+      ..onStart = _handleDragStart
+      ..onUpdate = _handleDragUpdate
+      ..onEnd = _handleDragEnd
+      ..dragStartBehavior = dragStartBehavior;
+  }
+
+  ImageProvider? get activeThumbImage => _activeThumbImage;
+  ImageProvider? _activeThumbImage;
+  set activeThumbImage(ImageProvider? value) {
+    if (value == _activeThumbImage)
+      return;
+    _activeThumbImage = value;
+    markNeedsPaint();
+  }
+
+  ImageErrorListener? get onActiveThumbImageError => _onActiveThumbImageError;
+  ImageErrorListener? _onActiveThumbImageError;
+  set onActiveThumbImageError(ImageErrorListener? value) {
+    if (value == _onActiveThumbImageError) {
+      return;
+    }
+    _onActiveThumbImageError = value;
+    markNeedsPaint();
+  }
+
+  ImageProvider? get inactiveThumbImage => _inactiveThumbImage;
+  ImageProvider? _inactiveThumbImage;
+  set inactiveThumbImage(ImageProvider? value) {
+    if (value == _inactiveThumbImage)
+      return;
+    _inactiveThumbImage = value;
+    markNeedsPaint();
+  }
+
+  ImageErrorListener? get onInactiveThumbImageError => _onInactiveThumbImageError;
+  ImageErrorListener? _onInactiveThumbImageError;
+  set onInactiveThumbImageError(ImageErrorListener? value) {
+    if (value == _onInactiveThumbImageError) {
+      return;
+    }
+    _onInactiveThumbImageError = value;
+    markNeedsPaint();
+  }
+
+  Color get activeTrackColor => _activeTrackColor;
+  Color _activeTrackColor;
+  set activeTrackColor(Color value) {
+    assert(value != null);
+    if (value == _activeTrackColor)
+      return;
+    _activeTrackColor = value;
+    markNeedsPaint();
+  }
+
+  Color get inactiveTrackColor => _inactiveTrackColor;
+  Color _inactiveTrackColor;
+  set inactiveTrackColor(Color value) {
+    assert(value != null);
+    if (value == _inactiveTrackColor)
+      return;
+    _inactiveTrackColor = value;
+    markNeedsPaint();
+  }
+
+  ImageConfiguration get configuration => _configuration;
+  ImageConfiguration _configuration;
+  set configuration(ImageConfiguration value) {
+    assert(value != null);
+    if (value == _configuration)
+      return;
+    _configuration = value;
+    markNeedsPaint();
+  }
+
+  TextDirection get textDirection => _textDirection;
+  TextDirection _textDirection;
+  set textDirection(TextDirection value) {
+    assert(value != null);
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    markNeedsPaint();
+  }
+
+  DragStartBehavior get dragStartBehavior => _drag.dragStartBehavior;
+  set dragStartBehavior(DragStartBehavior value) {
+    assert(value != null);
+    if (_drag.dragStartBehavior == value)
+      return;
+    _drag.dragStartBehavior = value;
+  }
+
+  Color get surfaceColor => _surfaceColor;
+  Color _surfaceColor;
+  set surfaceColor(Color value) {
+    assert(value != null);
+    if (value == _surfaceColor)
+      return;
+    _surfaceColor = value;
+    markNeedsPaint();
+  }
+
+  _SwitchState state;
+
+  @override
+  set value(bool? newValue) {
+    assert(value != null);
+    super.value = newValue;
+    // The widget is rebuilt and we have pending position animation to play.
+    if (_needsPositionAnimation) {
+      _needsPositionAnimation = false;
+      position.reverseCurve = null;
+      if (newValue!)
+        positionController.forward();
+      else
+        positionController.reverse();
+    }
+  }
+
+  @override
+  void detach() {
+    _cachedThumbPainter?.dispose();
+    _cachedThumbPainter = null;
+    super.detach();
+  }
+
+  double get _trackInnerLength => size.width - _kSwitchMinSize;
+
+  late HorizontalDragGestureRecognizer _drag;
+
+  bool _needsPositionAnimation = false;
+
+  void _handleDragStart(DragStartDetails details) {
+    if (isInteractive)
+      reactionController.forward();
+  }
+
+  void _handleDragUpdate(DragUpdateDetails details) {
+    if (isInteractive) {
+      position.reverseCurve = null;
+      final double delta = details.primaryDelta! / _trackInnerLength;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          positionController.value -= delta;
+          break;
+        case TextDirection.ltr:
+          positionController.value += delta;
+          break;
+      }
+    }
+  }
+
+  void _handleDragEnd(DragEndDetails details) {
+    _needsPositionAnimation = true;
+
+    if (position.value >= 0.5 != value)
+      onChanged!(!value!);
+    reactionController.reverse();
+    state._didFinishDragging();
+  }
+
+  @override
+  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
+    assert(debugHandleEvent(event, entry));
+    if (event is PointerDownEvent && onChanged != null)
+      _drag.addPointer(event);
+    super.handleEvent(event, entry);
+  }
+
+  Color? _cachedThumbColor;
+  ImageProvider? _cachedThumbImage;
+  ImageErrorListener? _cachedThumbErrorListener;
+  BoxPainter? _cachedThumbPainter;
+
+  BoxDecoration _createDefaultThumbDecoration(Color color, ImageProvider? image, ImageErrorListener? errorListener) {
+    return BoxDecoration(
+      color: color,
+      image: image == null ? null : DecorationImage(image: image, onError: errorListener),
+      shape: BoxShape.circle,
+      boxShadow: kElevationToShadow[1],
+    );
+  }
+
+  bool _isPainting = false;
+
+  void _handleDecorationChanged() {
+    // If the image decoration is available synchronously, we'll get called here
+    // during paint. There's no reason to mark ourselves as needing paint if we
+    // are already in the middle of painting. (In fact, doing so would trigger
+    // an assert).
+    if (!_isPainting)
+      markNeedsPaint();
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    config.isToggled = value == true;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final Canvas canvas = context.canvas;
+    final bool isEnabled = onChanged != null;
+    final double currentValue = position.value;
+
+    final double visualPosition;
+    switch (textDirection) {
+      case TextDirection.rtl:
+        visualPosition = 1.0 - currentValue;
+        break;
+      case TextDirection.ltr:
+        visualPosition = currentValue;
+        break;
+    }
+
+    final Color trackColor = Color.lerp(inactiveTrackColor, activeTrackColor, currentValue)!;
+    final Color lerpedThumbColor = Color.lerp(inactiveColor, activeColor, currentValue)!;
+    // Blend the thumb color against a `surfaceColor` background in case the
+    // thumbColor is not opaque. This way we do not see through the thumb to the
+    // track underneath.
+    final Color thumbColor = Color.alphaBlend(lerpedThumbColor, surfaceColor);
+
+    final ImageProvider? thumbImage = isEnabled
+      ? (currentValue < 0.5 ? inactiveThumbImage : activeThumbImage)
+      : inactiveThumbImage;
+
+    final ImageErrorListener? thumbErrorListener = isEnabled
+      ? (currentValue < 0.5 ? onInactiveThumbImageError : onActiveThumbImageError)
+      : onInactiveThumbImageError;
+
+    // Paint the track
+    final Paint paint = Paint()
+      ..color = trackColor;
+    const double trackHorizontalPadding = kRadialReactionRadius - _kTrackRadius;
+    final Rect trackRect = Rect.fromLTWH(
+      offset.dx + trackHorizontalPadding,
+      offset.dy + (size.height - _kTrackHeight) / 2.0,
+      size.width - 2.0 * trackHorizontalPadding,
+      _kTrackHeight,
+    );
+    final RRect trackRRect = RRect.fromRectAndRadius(trackRect, const Radius.circular(_kTrackRadius));
+    canvas.drawRRect(trackRRect, paint);
+
+    final Offset thumbPosition = Offset(
+      kRadialReactionRadius + visualPosition * _trackInnerLength,
+      size.height / 2.0,
+    );
+
+    paintRadialReaction(canvas, offset, thumbPosition);
+
+    try {
+      _isPainting = true;
+      if (_cachedThumbPainter == null || thumbColor != _cachedThumbColor || thumbImage != _cachedThumbImage || thumbErrorListener != _cachedThumbErrorListener) {
+        _cachedThumbColor = thumbColor;
+        _cachedThumbImage = thumbImage;
+        _cachedThumbErrorListener = thumbErrorListener;
+        _cachedThumbPainter = _createDefaultThumbDecoration(thumbColor, thumbImage, thumbErrorListener).createBoxPainter(_handleDecorationChanged);
+      }
+      final BoxPainter thumbPainter = _cachedThumbPainter!;
+
+      // The thumb contracts slightly during the animation
+      final double inset = 1.0 - (currentValue - 0.5).abs() * 2.0;
+      final double radius = _kThumbRadius - inset;
+      thumbPainter.paint(
+        canvas,
+        thumbPosition + offset - Offset(radius, radius),
+        configuration.copyWith(size: Size.fromRadius(radius)),
+      );
+    } finally {
+      _isPainting = false;
+    }
+  }
+}
diff --git a/lib/src/material/switch_list_tile.dart b/lib/src/material/switch_list_tile.dart
new file mode 100644
index 0000000..157f985
--- /dev/null
+++ b/lib/src/material/switch_list_tile.dart
@@ -0,0 +1,527 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'list_tile.dart';
+import 'switch.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// void setState(VoidCallback fn) { }
+// bool _isSelected;
+
+enum _SwitchListTileType { material, adaptive }
+
+/// A [ListTile] with a [Switch]. In other words, a switch with a label.
+///
+/// The entire list tile is interactive: tapping anywhere in the tile toggles
+/// the switch. Tapping and dragging the [Switch] also triggers the [onChanged]
+/// callback.
+///
+/// To ensure that [onChanged] correctly triggers, the state passed
+/// into [value] must be properly managed. This is typically done by invoking
+/// [State.setState] in [onChanged] to toggle the state value.
+///
+/// The [value], [onChanged], [activeColor], [activeThumbImage], and
+/// [inactiveThumbImage] properties of this widget are identical to the
+/// similarly-named properties on the [Switch] widget.
+///
+/// The [title], [subtitle], [isThreeLine], and [dense] properties are like
+/// those of the same name on [ListTile].
+///
+/// The [selected] property on this widget is similar to the [ListTile.selected]
+/// property, but the color used is that described by [activeColor], if any,
+/// defaulting to the accent color of the current [Theme]. No effort is made to
+/// coordinate the [selected] state and the [value] state; to have the list tile
+/// appear selected when the switch is on, pass the same value to both.
+///
+/// The switch is shown on the right by default in left-to-right languages (i.e.
+/// in the [ListTile.trailing] slot) which can be changed using [controlAffinity].
+/// The [secondary] widget is placed in the [ListTile.leading] slot.
+///
+/// To show the [SwitchListTile] as disabled, pass null as the [onChanged]
+/// callback.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+///
+/// ![SwitchListTile sample](https://flutter.github.io/assets-for-api-docs/assets/material/switch_list_tile.png)
+///
+/// This widget shows a switch that, when toggled, changes the state of a [bool]
+/// member field called `_lights`.
+///
+/// ```dart
+/// bool _lights = false;
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return SwitchListTile(
+///     title: const Text('Lights'),
+///     value: _lights,
+///     onChanged: (bool value) { setState(() { _lights = value; }); },
+///     secondary: const Icon(Icons.lightbulb_outline),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ## Semantics in SwitchListTile
+///
+/// Since the entirety of the SwitchListTile is interactive, it should represent
+/// itself as a single interactive entity.
+///
+/// To do so, a SwitchListTile widget wraps its children with a [MergeSemantics]
+/// widget. [MergeSemantics] will attempt to merge its descendant [Semantics]
+/// nodes into one node in the semantics tree. Therefore, SwitchListTile will
+/// throw an error if any of its children requires its own [Semantics] node.
+///
+/// For example, you cannot nest a [RichText] widget as a descendant of
+/// SwitchListTile. [RichText] has an embedded gesture recognizer that
+/// requires its own [Semantics] node, which directly conflicts with
+/// SwitchListTile's desire to merge all its descendants' semantic nodes
+/// into one. Therefore, it may be necessary to create a custom radio tile
+/// widget to accommodate similar use cases.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+///
+/// ![Switch list tile semantics sample](https://flutter.github.io/assets-for-api-docs/assets/material/switch_list_tile_semantics.png)
+///
+/// Here is an example of a custom labeled radio widget, called
+/// LinkedLabelRadio, that includes an interactive [RichText] widget that
+/// handles tap gestures.
+///
+/// ```dart imports
+/// import 'package:flute/gestures.dart';
+/// ```
+/// ```dart preamble
+/// class LinkedLabelSwitch extends StatelessWidget {
+///   const LinkedLabelSwitch({
+///     this.label,
+///     this.padding,
+///     this.value,
+///     this.onChanged,
+///   });
+///
+///   final String label;
+///   final EdgeInsets padding;
+///   final bool value;
+///   final Function onChanged;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Padding(
+///       padding: padding,
+///       child: Row(
+///         children: <Widget>[
+///           Expanded(
+///             child: RichText(
+///               text: TextSpan(
+///                 text: label,
+///                 style: TextStyle(
+///                   color: Colors.blueAccent,
+///                   decoration: TextDecoration.underline,
+///                 ),
+///                 recognizer: TapGestureRecognizer()
+///                   ..onTap = () {
+///                   print('Label has been tapped.');
+///                 },
+///               ),
+///             ),
+///           ),
+///           Switch(
+///             value: value,
+///             onChanged: (bool newValue) {
+///               onChanged(newValue);
+///             },
+///           ),
+///         ],
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// ```dart
+/// bool _isSelected = false;
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return LinkedLabelSwitch(
+///     label: 'Linked, tappable label text',
+///     padding: const EdgeInsets.symmetric(horizontal: 20.0),
+///     value: _isSelected,
+///     onChanged: (bool newValue) {
+///       setState(() {
+///         _isSelected = newValue;
+///       });
+///     },
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ## SwitchListTile isn't exactly what I want
+///
+/// If the way SwitchListTile pads and positions its elements isn't quite what
+/// you're looking for, you can create custom labeled switch widgets by
+/// combining [Switch] with other widgets, such as [Text], [Padding] and
+/// [InkWell].
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+///
+/// ![Custom switch list tile sample](https://flutter.github.io/assets-for-api-docs/assets/material/switch_list_tile_custom.png)
+///
+/// Here is an example of a custom LabeledSwitch widget, but you can easily
+/// make your own configurable widget.
+///
+/// ```dart preamble
+/// class LabeledSwitch extends StatelessWidget {
+///   const LabeledSwitch({
+///     this.label,
+///     this.padding,
+///     this.groupValue,
+///     this.value,
+///     this.onChanged,
+///   });
+///
+///   final String label;
+///   final EdgeInsets padding;
+///   final bool groupValue;
+///   final bool value;
+///   final Function onChanged;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return InkWell(
+///       onTap: () {
+///         onChanged(!value);
+///       },
+///       child: Padding(
+///         padding: padding,
+///         child: Row(
+///           children: <Widget>[
+///             Expanded(child: Text(label)),
+///             Switch(
+///               value: value,
+///               onChanged: (bool newValue) {
+///                 onChanged(newValue);
+///               },
+///             ),
+///           ],
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// ```dart
+/// bool _isSelected = false;
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return LabeledSwitch(
+///     label: 'This is the label text',
+///     padding: const EdgeInsets.symmetric(horizontal: 20.0),
+///     value: _isSelected,
+///     onChanged: (bool newValue) {
+///       setState(() {
+///         _isSelected = newValue;
+///       });
+///     },
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ListTileTheme], which can be used to affect the style of list tiles,
+///    including switch list tiles.
+///  * [CheckboxListTile], a similar widget for checkboxes.
+///  * [RadioListTile], a similar widget for radio buttons.
+///  * [ListTile] and [Switch], the widgets from which this widget is made.
+class SwitchListTile extends StatelessWidget {
+  /// Creates a combination of a list tile and a switch.
+  ///
+  /// The switch tile itself does not maintain any state. Instead, when the
+  /// state of the switch changes, the widget calls the [onChanged] callback.
+  /// Most widgets that use a switch will listen for the [onChanged] callback
+  /// and rebuild the switch tile with a new [value] to update the visual
+  /// appearance of the switch.
+  ///
+  /// The following arguments are required:
+  ///
+  /// * [value] determines whether this switch is on or off.
+  /// * [onChanged] is called when the user toggles the switch on or off.
+  const SwitchListTile({
+    Key? key,
+    required this.value,
+    required this.onChanged,
+    this.tileColor,
+    this.activeColor,
+    this.activeTrackColor,
+    this.inactiveThumbColor,
+    this.inactiveTrackColor,
+    this.activeThumbImage,
+    this.inactiveThumbImage,
+    this.title,
+    this.subtitle,
+    this.isThreeLine = false,
+    this.dense,
+    this.contentPadding,
+    this.secondary,
+    this.selected = false,
+    this.autofocus = false,
+    this.controlAffinity = ListTileControlAffinity.platform,
+    this.shape,
+    this.selectedTileColor,
+  }) : _switchListTileType = _SwitchListTileType.material,
+       assert(value != null),
+       assert(isThreeLine != null),
+       assert(!isThreeLine || subtitle != null),
+       assert(selected != null),
+       assert(autofocus != null),
+       super(key: key);
+
+  /// Creates a Material [ListTile] with an adaptive [Switch], following
+  /// Material design's
+  /// [Cross-platform guidelines](https://material.io/design/platform-guidance/cross-platform-adaptation.html).
+  ///
+  /// This widget uses [Switch.adaptive] to change the graphics of the switch
+  /// component based on the ambient [ThemeData.platform]. On iOS and macOS, a
+  /// [CupertinoSwitch] will be used. On other platforms a Material design
+  /// [Switch] will be used.
+  ///
+  /// If a [CupertinoSwitch] is created, the following parameters are
+  /// ignored: [activeTrackColor], [inactiveThumbColor], [inactiveTrackColor],
+  /// [activeThumbImage], [inactiveThumbImage].
+  const SwitchListTile.adaptive({
+    Key? key,
+    required this.value,
+    required this.onChanged,
+    this.tileColor,
+    this.activeColor,
+    this.activeTrackColor,
+    this.inactiveThumbColor,
+    this.inactiveTrackColor,
+    this.activeThumbImage,
+    this.inactiveThumbImage,
+    this.title,
+    this.subtitle,
+    this.isThreeLine = false,
+    this.dense,
+    this.contentPadding,
+    this.secondary,
+    this.selected = false,
+    this.autofocus = false,
+    this.controlAffinity = ListTileControlAffinity.platform,
+    this.shape,
+    this.selectedTileColor,
+  }) : _switchListTileType = _SwitchListTileType.adaptive,
+       assert(value != null),
+       assert(isThreeLine != null),
+       assert(!isThreeLine || subtitle != null),
+       assert(selected != null),
+       assert(autofocus != null),
+       super(key: key);
+
+  /// Whether this switch is checked.
+  ///
+  /// This property must not be null.
+  final bool value;
+
+  /// Called when the user toggles the switch on or off.
+  ///
+  /// The switch passes the new value to the callback but does not actually
+  /// change state until the parent widget rebuilds the switch tile with the
+  /// new value.
+  ///
+  /// If null, the switch will be displayed as disabled.
+  ///
+  /// The callback provided to [onChanged] should update the state of the parent
+  /// [StatefulWidget] using the [State.setState] method, so that the parent
+  /// gets rebuilt; for example:
+  ///
+  /// ```dart
+  /// SwitchListTile(
+  ///   value: _isSelected,
+  ///   onChanged: (bool newValue) {
+  ///     setState(() {
+  ///       _isSelected = newValue;
+  ///     });
+  ///   },
+  ///   title: Text('Selection'),
+  /// )
+  /// ```
+  final ValueChanged<bool>? onChanged;
+
+  /// The color to use when this switch is on.
+  ///
+  /// Defaults to accent color of the current [Theme].
+  final Color? activeColor;
+
+  /// The color to use on the track when this switch is on.
+  ///
+  /// Defaults to [ThemeData.toggleableActiveColor] with the opacity set at 50%.
+  ///
+  /// Ignored if created with [SwitchListTile.adaptive].
+  final Color? activeTrackColor;
+
+  /// The color to use on the thumb when this switch is off.
+  ///
+  /// Defaults to the colors described in the Material design specification.
+  ///
+  /// Ignored if created with [SwitchListTile.adaptive].
+  final Color? inactiveThumbColor;
+
+  /// The color to use on the track when this switch is off.
+  ///
+  /// Defaults to the colors described in the Material design specification.
+  ///
+  /// Ignored if created with [SwitchListTile.adaptive].
+  final Color? inactiveTrackColor;
+
+  /// {@macro flutter.material.ListTile.tileColor}
+  final Color? tileColor;
+
+  /// An image to use on the thumb of this switch when the switch is on.
+  final ImageProvider? activeThumbImage;
+
+  /// An image to use on the thumb of this switch when the switch is off.
+  ///
+  /// Ignored if created with [SwitchListTile.adaptive].
+  final ImageProvider? inactiveThumbImage;
+
+  /// The primary content of the list tile.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? title;
+
+  /// Additional content displayed below the title.
+  ///
+  /// Typically a [Text] widget.
+  final Widget? subtitle;
+
+  /// A widget to display on the opposite side of the tile from the switch.
+  ///
+  /// Typically an [Icon] widget.
+  final Widget? secondary;
+
+  /// Whether this list tile is intended to display three lines of text.
+  ///
+  /// If false, the list tile is treated as having one line if the subtitle is
+  /// null and treated as having two lines if the subtitle is non-null.
+  final bool isThreeLine;
+
+  /// Whether this list tile is part of a vertically dense list.
+  ///
+  /// If this property is null then its value is based on [ListTileTheme.dense].
+  final bool? dense;
+
+  /// The tile's internal padding.
+  ///
+  /// Insets a [SwitchListTile]'s contents: its [title], [subtitle],
+  /// [secondary], and [Switch] widgets.
+  ///
+  /// If null, [ListTile]'s default of `EdgeInsets.symmetric(horizontal: 16.0)`
+  /// is used.
+  final EdgeInsetsGeometry? contentPadding;
+
+  /// Whether to render icons and text in the [activeColor].
+  ///
+  /// No effort is made to automatically coordinate the [selected] state and the
+  /// [value] state. To have the list tile appear selected when the switch is
+  /// on, pass the same value to both.
+  ///
+  /// Normally, this property is left to its default value, false.
+  final bool selected;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  /// If adaptive, creates the switch with [Switch.adaptive].
+  final _SwitchListTileType _switchListTileType;
+
+  /// Defines the position of control and [secondary], relative to text.
+  ///
+  /// By default, the value of `controlAffinity` is [ListTileControlAffinity.platform].
+  final ListTileControlAffinity controlAffinity;
+
+  /// {@macro flutter.material.ListTileTheme.shape}
+  final ShapeBorder? shape;
+
+  /// If non-null, defines the background color when [SwitchListTile.selected] is true.
+  final Color? selectedTileColor;
+
+  @override
+  Widget build(BuildContext context) {
+    final Widget control;
+    switch (_switchListTileType) {
+      case _SwitchListTileType.adaptive:
+        control = Switch.adaptive(
+          value: value,
+          onChanged: onChanged,
+          activeColor: activeColor,
+          activeThumbImage: activeThumbImage,
+          inactiveThumbImage: inactiveThumbImage,
+          materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+          activeTrackColor: activeTrackColor,
+          inactiveTrackColor: inactiveTrackColor,
+          inactiveThumbColor: inactiveThumbColor,
+          autofocus: autofocus,
+        );
+        break;
+
+      case _SwitchListTileType.material:
+        control = Switch(
+          value: value,
+          onChanged: onChanged,
+          activeColor: activeColor,
+          activeThumbImage: activeThumbImage,
+          inactiveThumbImage: inactiveThumbImage,
+          materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+          activeTrackColor: activeTrackColor,
+          inactiveTrackColor: inactiveTrackColor,
+          inactiveThumbColor: inactiveThumbColor,
+          autofocus: autofocus,
+        );
+    }
+
+    Widget? leading, trailing;
+    switch (controlAffinity) {
+      case ListTileControlAffinity.leading:
+        leading = control;
+        trailing = secondary;
+        break;
+      case ListTileControlAffinity.trailing:
+      case ListTileControlAffinity.platform:
+        leading = secondary;
+        trailing = control;
+        break;
+    }
+
+    return MergeSemantics(
+      child: ListTileTheme.merge(
+        selectedColor: activeColor ?? Theme.of(context).accentColor,
+        child: ListTile(
+          leading: leading,
+          title: title,
+          subtitle: subtitle,
+          trailing: trailing,
+          isThreeLine: isThreeLine,
+          dense: dense,
+          contentPadding: contentPadding,
+          enabled: onChanged != null,
+          onTap: onChanged != null ? () { onChanged!(!value); } : null,
+          selected: selected,
+          selectedTileColor: selectedTileColor,
+          autofocus: autofocus,
+          shape: shape,
+          tileColor: tileColor,
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/switch_theme.dart b/lib/src/material/switch_theme.dart
new file mode 100644
index 0000000..8ae8981
--- /dev/null
+++ b/lib/src/material/switch_theme.dart
@@ -0,0 +1,216 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'material_state.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// Defines default property values for descendant [Switch] widgets.
+///
+/// Descendant widgets obtain the current [SwitchThemeData] object using
+/// `SwitchTheme.of(context)`. Instances of [SwitchThemeData] can be customized
+/// with [SwitchThemeData.copyWith].
+///
+/// Typically a [SwitchThemeData] is specified as part of the overall [Theme]
+/// with [ThemeData.switchTheme].
+///
+/// All [SwitchThemeData] properties are `null` by default. When null, the
+/// [Switch] will use the values from [ThemeData] if they exist, otherwise it
+/// will provide its own defaults based on the overall [Theme]'s colorScheme.
+/// See the individual [Switch] properties for details.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class SwitchThemeData with Diagnosticable {
+  /// Creates a theme that can be used for [ThemeData.switchTheme].
+  const SwitchThemeData({
+    this.thumbColor,
+    this.trackColor,
+    this.materialTapTargetSize,
+    this.mouseCursor,
+    this.overlayColor,
+    this.splashRadius,
+  });
+
+  /// {@macro flutter.material.switch.thumbColor}
+  ///
+  /// If specified, overrides the default value of [Switch.thumbColor].
+  final MaterialStateProperty<Color?>? thumbColor;
+
+  /// {@macro flutter.material.switch.trackColor}
+  ///
+  /// If specified, overrides the default value of [Switch.trackColor].
+  final MaterialStateProperty<Color?>? trackColor;
+
+  /// {@macro flutter.material.switch.materialTapTargetSize}
+  ///
+  /// If specified, overrides the default value of
+  /// [Switch.materialTapTargetSize].
+  final MaterialTapTargetSize? materialTapTargetSize;
+
+  /// {@macro flutter.material.switch.mouseCursor}
+  ///
+  /// If specified, overrides the default value of [Switch.mouseCursor].
+  final MaterialStateProperty<MouseCursor?>? mouseCursor;
+
+  /// {@macro flutter.material.switch.overlayColor}
+  ///
+  /// If specified, overrides the default value of [Switch.overlayColor].
+  final MaterialStateProperty<Color?>? overlayColor;
+
+  /// {@macro flutter.material.switch.splashRadius}
+  ///
+  /// If specified, overrides the default value of [Switch.splashRadius].
+  final double? splashRadius;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  SwitchThemeData copyWith({
+    MaterialStateProperty<Color?>? thumbColor,
+    MaterialStateProperty<Color?>? trackColor,
+    MaterialTapTargetSize? materialTapTargetSize,
+    MaterialStateProperty<MouseCursor?>? mouseCursor,
+    MaterialStateProperty<Color?>? overlayColor,
+    double? splashRadius,
+  }) {
+    return SwitchThemeData(
+      thumbColor: thumbColor ?? this.thumbColor,
+      trackColor: trackColor ?? this.trackColor,
+      materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
+      mouseCursor: mouseCursor ?? this.mouseCursor,
+      overlayColor: overlayColor ?? this.overlayColor,
+      splashRadius: splashRadius ?? this.splashRadius,
+    );
+  }
+
+  /// Linearly interpolate between two [SwitchThemeData]s.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static SwitchThemeData lerp(SwitchThemeData? a, SwitchThemeData? b, double t) {
+    return SwitchThemeData(
+      thumbColor: _lerpProperties<Color?>(a?.thumbColor, b?.thumbColor, t, Color.lerp),
+      trackColor: _lerpProperties<Color?>(a?.trackColor, b?.trackColor, t, Color.lerp),
+      materialTapTargetSize: t < 0.5 ? a?.materialTapTargetSize : b?.materialTapTargetSize,
+      mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
+      overlayColor: _lerpProperties<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
+      splashRadius: lerpDouble(a?.splashRadius, b?.splashRadius, t),
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      thumbColor,
+      trackColor,
+      materialTapTargetSize,
+      mouseCursor,
+      overlayColor,
+      splashRadius,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is SwitchThemeData
+      && other.thumbColor == thumbColor
+      && other.trackColor == trackColor
+      && other.materialTapTargetSize == materialTapTargetSize
+      && other.mouseCursor == mouseCursor
+      && other.overlayColor == overlayColor
+      && other.splashRadius == splashRadius;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('thumbColor', thumbColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('trackColor', trackColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
+    properties.add(DoubleProperty('splashRadius', splashRadius, defaultValue: null));
+  }
+
+  static MaterialStateProperty<T>? _lerpProperties<T>(
+    MaterialStateProperty<T>? a,
+    MaterialStateProperty<T>? b,
+    double t,
+    T Function(T?, T?, double) lerpFunction,
+  ) {
+    // Avoid creating a _LerpProperties object for a common case.
+    if (a == null && b == null)
+      return null;
+    return _LerpProperties<T>(a, b, t, lerpFunction);
+  }
+}
+
+class _LerpProperties<T> implements MaterialStateProperty<T> {
+  const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
+
+  final MaterialStateProperty<T>? a;
+  final MaterialStateProperty<T>? b;
+  final double t;
+  final T Function(T?, T?, double) lerpFunction;
+
+  @override
+  T resolve(Set<MaterialState> states) {
+    final T? resolvedA = a?.resolve(states);
+    final T? resolvedB = b?.resolve(states);
+    return lerpFunction(resolvedA, resolvedB, t);
+  }
+}
+
+/// Applies a switch theme to descendant [Switch] widgets.
+///
+/// Descendant widgets obtain the current theme's [SwitchTheme] object using
+/// [SwitchTheme.of]. When a widget uses [SwitchTheme.of], it is automatically
+/// rebuilt if the theme later changes.
+///
+/// A switch theme can be specified as part of the overall Material theme using
+/// [ThemeData.switchTheme].
+///
+/// See also:
+///
+///  * [SwitchThemeData], which describes the actual configuration of a switch
+///    theme.
+class SwitchTheme extends InheritedWidget {
+  /// Constructs a switch theme that configures all descendant [Switch] widgets.
+  const SwitchTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : super(key: key, child: child);
+
+  /// The properties used for all descendant [Switch] widgets.
+  final SwitchThemeData data;
+
+  /// Returns the configuration [data] from the closest [SwitchTheme] ancestor.
+  /// If there is no ancestor, it returns [ThemeData.switchTheme].
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// SwitchThemeData theme = SwitchTheme.of(context);
+  /// ```
+  static SwitchThemeData of(BuildContext context) {
+    final SwitchTheme? switchTheme = context.dependOnInheritedWidgetOfExactType<SwitchTheme>();
+    return switchTheme?.data ?? Theme.of(context).switchTheme;
+  }
+
+  @override
+  bool updateShouldNotify(SwitchTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/tab_bar_theme.dart b/lib/src/material/tab_bar_theme.dart
new file mode 100644
index 0000000..116e7b0
--- /dev/null
+++ b/lib/src/material/tab_bar_theme.dart
@@ -0,0 +1,135 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'tabs.dart';
+import 'theme.dart';
+
+/// Defines a theme for [TabBar] widgets.
+///
+/// A tab bar theme describes the color of the tab label and the size/shape of
+/// the [TabBar.indicator].
+///
+/// Descendant widgets obtain the current theme's [TabBarTheme] object using
+/// `TabBarTheme.of(context)`. Instances of [TabBarTheme] can be customized with
+/// [TabBarTheme.copyWith].
+///
+/// See also:
+///
+///  * [TabBar], a widget that displays a horizontal row of tabs.
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+@immutable
+class TabBarTheme with Diagnosticable {
+  /// Creates a tab bar theme that can be used with [ThemeData.tabBarTheme].
+  const TabBarTheme({
+    this.indicator,
+    this.indicatorSize,
+    this.labelColor,
+    this.labelPadding,
+    this.labelStyle,
+    this.unselectedLabelColor,
+    this.unselectedLabelStyle,
+  });
+
+  /// Default value for [TabBar.indicator].
+  final Decoration? indicator;
+
+  /// Default value for [TabBar.indicatorSize].
+  final TabBarIndicatorSize? indicatorSize;
+
+  /// Default value for [TabBar.labelColor].
+  final Color? labelColor;
+
+  /// Default value for [TabBar.labelPadding].
+  final EdgeInsetsGeometry? labelPadding;
+
+  /// Default value for [TabBar.labelStyle].
+  final TextStyle? labelStyle;
+
+  /// Default value for [TabBar.unselectedLabelColor].
+  final Color? unselectedLabelColor;
+
+  /// Default value for [TabBar.unselectedLabelStyle].
+  final TextStyle? unselectedLabelStyle;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  TabBarTheme copyWith({
+    Decoration? indicator,
+    TabBarIndicatorSize? indicatorSize,
+    Color? labelColor,
+    EdgeInsetsGeometry? labelPadding,
+    TextStyle? labelStyle,
+    Color? unselectedLabelColor,
+    TextStyle? unselectedLabelStyle,
+  }) {
+    return TabBarTheme(
+      indicator: indicator ?? this.indicator,
+      indicatorSize: indicatorSize ?? this.indicatorSize,
+      labelColor: labelColor ?? this.labelColor,
+      labelPadding: labelPadding ?? this.labelPadding,
+      labelStyle: labelStyle ?? this.labelStyle,
+      unselectedLabelColor: unselectedLabelColor ?? this.unselectedLabelColor,
+      unselectedLabelStyle: unselectedLabelStyle ?? this.unselectedLabelStyle,
+    );
+  }
+
+  /// The data from the closest [TabBarTheme] instance given the build context.
+  static TabBarTheme of(BuildContext context) {
+    return Theme.of(context).tabBarTheme;
+  }
+
+  /// Linearly interpolate between two tab bar themes.
+  ///
+  /// The arguments must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static TabBarTheme lerp(TabBarTheme a, TabBarTheme b, double t) {
+    assert(a != null);
+    assert(b != null);
+    assert(t != null);
+    return TabBarTheme(
+      indicator: Decoration.lerp(a.indicator, b.indicator, t),
+      indicatorSize: t < 0.5 ? a.indicatorSize : b.indicatorSize,
+      labelColor: Color.lerp(a.labelColor, b.labelColor, t),
+      labelPadding: EdgeInsetsGeometry.lerp(a.labelPadding, b.labelPadding, t),
+      labelStyle: TextStyle.lerp(a.labelStyle, b.labelStyle, t),
+      unselectedLabelColor: Color.lerp(a.unselectedLabelColor, b.unselectedLabelColor, t),
+      unselectedLabelStyle: TextStyle.lerp(a.unselectedLabelStyle, b.unselectedLabelStyle, t),
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      indicator,
+      indicatorSize,
+      labelColor,
+      labelPadding,
+      labelStyle,
+      unselectedLabelColor,
+      unselectedLabelStyle,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is TabBarTheme
+        && other.indicator == indicator
+        && other.indicatorSize == indicatorSize
+        && other.labelColor == labelColor
+        && other.labelPadding == labelPadding
+        && other.labelStyle == labelStyle
+        && other.unselectedLabelColor == unselectedLabelColor
+        && other.unselectedLabelStyle == unselectedLabelStyle;
+  }
+}
diff --git a/lib/src/material/tab_controller.dart b/lib/src/material/tab_controller.dart
new file mode 100644
index 0000000..056cbdd
--- /dev/null
+++ b/lib/src/material/tab_controller.dart
@@ -0,0 +1,466 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/widgets.dart';
+
+import 'constants.dart';
+
+// Examples can assume:
+// late BuildContext context;
+
+/// Coordinates tab selection between a [TabBar] and a [TabBarView].
+///
+/// The [index] property is the index of the selected tab and the [animation]
+/// represents the current scroll positions of the tab bar and the tab bar view.
+/// The selected tab's index can be changed with [animateTo].
+///
+/// A stateful widget that builds a [TabBar] or a [TabBarView] can create
+/// a [TabController] and share it directly.
+///
+/// When the [TabBar] and [TabBarView] don't have a convenient stateful
+/// ancestor, a [TabController] can be shared by providing a
+/// [DefaultTabController] inherited widget.
+///
+/// {@animation 700 540 https://flutter.github.io/assets-for-api-docs/assets/material/tabs.mp4}
+///
+/// {@tool snippet}
+///
+/// This widget introduces a [Scaffold] with an [AppBar] and a [TabBar].
+///
+/// ```dart
+/// class MyTabbedPage extends StatefulWidget {
+///   const MyTabbedPage({ Key? key }) : super(key: key);
+///   @override
+///   _MyTabbedPageState createState() => _MyTabbedPageState();
+/// }
+///
+/// class _MyTabbedPageState extends State<MyTabbedPage> with SingleTickerProviderStateMixin {
+///   final List<Tab> myTabs = <Tab>[
+///     Tab(text: 'LEFT'),
+///     Tab(text: 'RIGHT'),
+///   ];
+///
+///   late TabController _tabController;
+///
+///   @override
+///   void initState() {
+///     super.initState();
+///     _tabController = TabController(vsync: this, length: myTabs.length);
+///   }
+///
+///  @override
+///  void dispose() {
+///    _tabController.dispose();
+///    super.dispose();
+///  }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Scaffold(
+///       appBar: AppBar(
+///         bottom: TabBar(
+///           controller: _tabController,
+///           tabs: myTabs,
+///         ),
+///       ),
+///       body: TabBarView(
+///         controller: _tabController,
+///         children: myTabs.map((Tab tab) {
+///           final String label = tab.text!.toLowerCase();
+///           return Center(
+///             child: Text(
+///               'This is the $label tab',
+///               style: const TextStyle(fontSize: 36),
+///             ),
+///           );
+///         }).toList(),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateless_widget_material}
+///
+/// This example shows how to listen to page updates in [TabBar] and [TabBarView]
+/// when using [DefaultTabController].
+///
+/// ```dart preamble
+/// final List<Tab> tabs = <Tab>[
+///   Tab(text: 'Zeroth'),
+///   Tab(text: 'First'),
+///   Tab(text: 'Second'),
+/// ];
+/// ```
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return DefaultTabController(
+///     length: tabs.length,
+///     // The Builder widget is used to have a different BuildContext to access
+///     // closest DefaultTabController.
+///     child: Builder(
+///       builder: (BuildContext context) {
+///         final TabController tabController = DefaultTabController.of(context)!;
+///         tabController.addListener(() {
+///           if (!tabController.indexIsChanging) {
+///             // Your code goes here.
+///             // To get index of current tab use tabController.index
+///           }
+///         });
+///         return Scaffold(
+///           appBar: AppBar(
+///             bottom: TabBar(
+///               tabs: tabs,
+///             ),
+///           ),
+///           body: TabBarView(
+///             children: tabs.map((Tab tab){
+///               return Center(
+///                 child: Text(
+///                   tab.text! + ' Tab',
+///                   style: Theme.of(context).textTheme.headline5,
+///                 ),
+///               );
+///             }).toList(),
+///           ),
+///         );
+///       }
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+class TabController extends ChangeNotifier {
+  /// Creates an object that manages the state required by [TabBar] and a
+  /// [TabBarView].
+  ///
+  /// The [length] must not be null or negative. Typically it's a value greater
+  /// than one, i.e. typically there are two or more tabs. The [length] must
+  /// match [TabBar.tabs]'s and [TabBarView.children]'s length.
+  ///
+  /// The `initialIndex` must be valid given [length] and must not be null. If
+  /// [length] is zero, then `initialIndex` must be 0 (the default).
+  TabController({ int initialIndex = 0, required this.length, required TickerProvider vsync })
+    : assert(length != null && length >= 0),
+      assert(initialIndex != null && initialIndex >= 0 && (length == 0 || initialIndex < length)),
+      _index = initialIndex,
+      _previousIndex = initialIndex,
+      _animationController = AnimationController.unbounded(
+        value: initialIndex.toDouble(),
+        vsync: vsync,
+      );
+
+  // Private constructor used by `_copyWith`. This allows a new TabController to
+  // be created without having to create a new animationController.
+  TabController._({
+    required int index,
+    required int previousIndex,
+    required AnimationController? animationController,
+    required this.length,
+  }) : _index = index,
+       _previousIndex = previousIndex,
+       _animationController = animationController;
+
+
+  /// Creates a new [TabController] with `index`, `previousIndex`, and `length`
+  /// if they are non-null.
+  ///
+  /// This method is used by [DefaultTabController].
+  ///
+  /// When [DefaultTabController.length] is updated, this method is called to
+  /// create a new [TabController] without creating a new [AnimationController].
+  TabController _copyWith({
+    required int? index,
+    required int? length,
+    required int? previousIndex,
+  }) {
+    return TabController._(
+      index: index ?? _index,
+      length: length ?? this.length,
+      animationController: _animationController,
+      previousIndex: previousIndex ?? _previousIndex,
+    );
+  }
+
+  /// An animation whose value represents the current position of the [TabBar]'s
+  /// selected tab indicator as well as the scrollOffsets of the [TabBar]
+  /// and [TabBarView].
+  ///
+  /// The animation's value ranges from 0.0 to [length] - 1.0. After the
+  /// selected tab is changed, the animation's value equals [index]. The
+  /// animation's value can be [offset] by +/- 1.0 to reflect [TabBarView]
+  /// drag scrolling.
+  ///
+  /// If this [TabController] was disposed, then return null.
+  Animation<double>? get animation => _animationController?.view;
+  AnimationController? _animationController;
+
+  /// The total number of tabs.
+  ///
+  /// Typically greater than one. Must match [TabBar.tabs]'s and
+  /// [TabBarView.children]'s length.
+  final int length;
+
+  void _changeIndex(int value, { Duration? duration, Curve? curve }) {
+    assert(value != null);
+    assert(value >= 0 && (value < length || length == 0));
+    assert(duration != null || curve == null);
+    assert(_indexIsChangingCount >= 0);
+    if (value == _index || length < 2)
+      return;
+    _previousIndex = index;
+    _index = value;
+    if (duration != null) {
+      _indexIsChangingCount += 1;
+      notifyListeners(); // Because the value of indexIsChanging may have changed.
+      _animationController!
+        .animateTo(_index.toDouble(), duration: duration, curve: curve!)
+        .whenCompleteOrCancel(() {
+          if (_animationController != null) { // don't notify if we've been disposed
+            _indexIsChangingCount -= 1;
+            notifyListeners();
+          }
+        });
+    } else {
+      _indexIsChangingCount += 1;
+      _animationController!.value = _index.toDouble();
+      _indexIsChangingCount -= 1;
+      notifyListeners();
+    }
+  }
+
+  /// The index of the currently selected tab.
+  ///
+  /// Changing the index also updates [previousIndex], sets the [animation]'s
+  /// value to index, resets [indexIsChanging] to false, and notifies listeners.
+  ///
+  /// To change the currently selected tab and play the [animation] use [animateTo].
+  ///
+  /// The value of [index] must be valid given [length]. If [length] is zero,
+  /// then [index] will also be zero.
+  int get index => _index;
+  int _index;
+  set index(int value) {
+    _changeIndex(value);
+  }
+
+  /// The index of the previously selected tab.
+  ///
+  /// Initially the same as [index].
+  int get previousIndex => _previousIndex;
+  int _previousIndex;
+
+  /// True while we're animating from [previousIndex] to [index] as a
+  /// consequence of calling [animateTo].
+  ///
+  /// This value is true during the [animateTo] animation that's triggered when
+  /// the user taps a [TabBar] tab. It is false when [offset] is changing as a
+  /// consequence of the user dragging (and "flinging") the [TabBarView].
+  bool get indexIsChanging => _indexIsChangingCount != 0;
+  int _indexIsChangingCount = 0;
+
+  /// Immediately sets [index] and [previousIndex] and then plays the
+  /// [animation] from its current value to [index].
+  ///
+  /// While the animation is running [indexIsChanging] is true. When the
+  /// animation completes [offset] will be 0.0.
+  void animateTo(int value, { Duration duration = kTabScrollDuration, Curve curve = Curves.ease }) {
+    _changeIndex(value, duration: duration, curve: curve);
+  }
+
+  /// The difference between the [animation]'s value and [index].
+  ///
+  /// The offset value must be between -1.0 and 1.0.
+  ///
+  /// This property is typically set by the [TabBarView] when the user
+  /// drags left or right. A value between -1.0 and 0.0 implies that the
+  /// TabBarView has been dragged to the left. Similarly a value between
+  /// 0.0 and 1.0 implies that the TabBarView has been dragged to the right.
+  double get offset => _animationController!.value - _index.toDouble();
+  set offset(double value) {
+    assert(value != null);
+    assert(value >= -1.0 && value <= 1.0);
+    assert(!indexIsChanging);
+    if (value == offset)
+      return;
+    _animationController!.value = value + _index.toDouble();
+  }
+
+  @override
+  void dispose() {
+    _animationController?.dispose();
+    _animationController = null;
+    super.dispose();
+  }
+}
+
+class _TabControllerScope extends InheritedWidget {
+  const _TabControllerScope({
+    Key? key,
+    required this.controller,
+    required this.enabled,
+    required Widget child,
+  }) : super(key: key, child: child);
+
+  final TabController controller;
+  final bool enabled;
+
+  @override
+  bool updateShouldNotify(_TabControllerScope old) {
+    return enabled != old.enabled || controller != old.controller;
+  }
+}
+
+/// The [TabController] for descendant widgets that don't specify one
+/// explicitly.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=POtoEH-5l40}
+///
+/// [DefaultTabController] is an inherited widget that is used to share a
+/// [TabController] with a [TabBar] or a [TabBarView]. It's used when sharing an
+/// explicitly created [TabController] isn't convenient because the tab bar
+/// widgets are created by a stateless parent widget or by different parent
+/// widgets.
+///
+/// {@animation 700 540 https://flutter.github.io/assets-for-api-docs/assets/material/tabs.mp4}
+///
+/// ```dart
+/// class MyDemo extends StatelessWidget {
+///   final List<Tab> myTabs = <Tab>[
+///     Tab(text: 'LEFT'),
+///     Tab(text: 'RIGHT'),
+///   ];
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return DefaultTabController(
+///       length: myTabs.length,
+///       child: Scaffold(
+///         appBar: AppBar(
+///           bottom: TabBar(
+///             tabs: myTabs,
+///           ),
+///         ),
+///         body: TabBarView(
+///           children: myTabs.map((Tab tab) {
+///             final String label = tab.text.toLowerCase();
+///             return Center(
+///               child: Text(
+///                 'This is the $label tab',
+///                 style: const TextStyle(fontSize: 36),
+///               ),
+///             );
+///           }).toList(),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+class DefaultTabController extends StatefulWidget {
+  /// Creates a default tab controller for the given [child] widget.
+  ///
+  /// The [length] argument is typically greater than one. The [length] must
+  /// match [TabBar.tabs]'s and [TabBarView.children]'s length.
+  ///
+  /// The [initialIndex] argument must not be null.
+  const DefaultTabController({
+    Key? key,
+    required this.length,
+    this.initialIndex = 0,
+    required this.child,
+  }) : assert(initialIndex != null),
+       assert(length >= 0),
+       assert(length == 0 || (initialIndex >= 0 && initialIndex < length)),
+       super(key: key);
+
+  /// The total number of tabs.
+  ///
+  /// Typically greater than one. Must match [TabBar.tabs]'s and
+  /// [TabBarView.children]'s length.
+  final int length;
+
+  /// The initial index of the selected tab.
+  ///
+  /// Defaults to zero.
+  final int initialIndex;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// Typically a [Scaffold] whose [AppBar] includes a [TabBar].
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The closest instance of this class that encloses the given context.
+  ///
+  /// {@tool snippet}
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// TabController controller = DefaultTabController.of(context)!;
+  /// ```
+  /// {@end-tool}
+  static TabController? of(BuildContext context) {
+    final _TabControllerScope? scope = context.dependOnInheritedWidgetOfExactType<_TabControllerScope>();
+    return scope?.controller;
+  }
+
+  @override
+  _DefaultTabControllerState createState() => _DefaultTabControllerState();
+}
+
+class _DefaultTabControllerState extends State<DefaultTabController> with SingleTickerProviderStateMixin {
+  late TabController _controller;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = TabController(
+      vsync: this,
+      length: widget.length,
+      initialIndex: widget.initialIndex,
+    );
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _TabControllerScope(
+      controller: _controller,
+      enabled: TickerMode.of(context),
+      child: widget.child,
+    );
+  }
+
+  @override
+  void didUpdateWidget(DefaultTabController oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.length != widget.length) {
+      // If the length is shortened while the last tab is selected, we should
+      // automatically update the index of the controller to be the new last tab.
+      int? newIndex;
+      int previousIndex = _controller.previousIndex;
+      if (_controller.index >= widget.length) {
+        newIndex = math.max(0, widget.length - 1);
+        previousIndex = _controller.index;
+      }
+      _controller = _controller._copyWith(
+        length: widget.length,
+        index: newIndex,
+        previousIndex: previousIndex,
+      );
+    }
+  }
+}
diff --git a/lib/src/material/tab_indicator.dart b/lib/src/material/tab_indicator.dart
new file mode 100644
index 0000000..78a53c0
--- /dev/null
+++ b/lib/src/material/tab_indicator.dart
@@ -0,0 +1,101 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+
+/// Used with [TabBar.indicator] to draw a horizontal line below the
+/// selected tab.
+///
+/// The selected tab underline is inset from the tab's boundary by [insets].
+/// The [borderSide] defines the line's color and weight.
+///
+/// The [TabBar.indicatorSize] property can be used to define the indicator's
+/// bounds in terms of its (centered) widget with [TabBarIndicatorSize.label],
+/// or the entire tab with [TabBarIndicatorSize.tab].
+class UnderlineTabIndicator extends Decoration {
+  /// Create an underline style selected tab indicator.
+  ///
+  /// The [borderSide] and [insets] arguments must not be null.
+  const UnderlineTabIndicator({
+    this.borderSide = const BorderSide(width: 2.0, color: Colors.white),
+    this.insets = EdgeInsets.zero,
+  }) : assert(borderSide != null),
+       assert(insets != null);
+
+  /// The color and weight of the horizontal line drawn below the selected tab.
+  final BorderSide borderSide;
+
+  /// Locates the selected tab's underline relative to the tab's boundary.
+  ///
+  /// The [TabBar.indicatorSize] property can be used to define the tab
+  /// indicator's bounds in terms of its (centered) tab widget with
+  /// [TabBarIndicatorSize.label], or the entire tab with
+  /// [TabBarIndicatorSize.tab].
+  final EdgeInsetsGeometry insets;
+
+  @override
+  Decoration? lerpFrom(Decoration? a, double t) {
+    if (a is UnderlineTabIndicator) {
+      return UnderlineTabIndicator(
+        borderSide: BorderSide.lerp(a.borderSide, borderSide, t),
+        insets: EdgeInsetsGeometry.lerp(a.insets, insets, t)!,
+      );
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  Decoration? lerpTo(Decoration? b, double t) {
+    if (b is UnderlineTabIndicator) {
+      return UnderlineTabIndicator(
+        borderSide: BorderSide.lerp(borderSide, b.borderSide, t),
+        insets: EdgeInsetsGeometry.lerp(insets, b.insets, t)!,
+      );
+    }
+    return super.lerpTo(b, t);
+  }
+
+  @override
+  _UnderlinePainter createBoxPainter([ VoidCallback? onChanged ]) {
+    return _UnderlinePainter(this, onChanged);
+  }
+
+  Rect _indicatorRectFor(Rect rect, TextDirection textDirection) {
+    assert(rect != null);
+    assert(textDirection != null);
+    final Rect indicator = insets.resolve(textDirection).deflateRect(rect);
+    return Rect.fromLTWH(
+      indicator.left,
+      indicator.bottom - borderSide.width,
+      indicator.width,
+      borderSide.width,
+    );
+  }
+
+  @override
+  Path getClipPath(Rect rect, TextDirection textDirection) {
+    return Path()..addRect(_indicatorRectFor(rect, textDirection));
+  }
+}
+
+class _UnderlinePainter extends BoxPainter {
+  _UnderlinePainter(this.decoration, VoidCallback? onChanged)
+    : assert(decoration != null),
+      super(onChanged);
+
+  final UnderlineTabIndicator decoration;
+
+  @override
+  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
+    assert(configuration != null);
+    assert(configuration.size != null);
+    final Rect rect = offset & configuration.size!;
+    final TextDirection textDirection = configuration.textDirection!;
+    final Rect indicator = decoration._indicatorRectFor(rect, textDirection).deflate(decoration.borderSide.width / 2.0);
+    final Paint paint = decoration.borderSide.toPaint()..strokeCap = StrokeCap.square;
+    canvas.drawLine(indicator.bottomLeft, indicator.bottomRight, paint);
+  }
+}
diff --git a/lib/src/material/tabs.dart b/lib/src/material/tabs.dart
new file mode 100644
index 0000000..00c12ba
--- /dev/null
+++ b/lib/src/material/tabs.dart
@@ -0,0 +1,1560 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+import 'package:flute/gestures.dart' show DragStartBehavior;
+
+import 'app_bar.dart';
+import 'colors.dart';
+import 'constants.dart';
+import 'debug.dart';
+import 'ink_well.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'material_state.dart';
+import 'tab_bar_theme.dart';
+import 'tab_controller.dart';
+import 'tab_indicator.dart';
+import 'theme.dart';
+
+const double _kTabHeight = 46.0;
+const double _kTextAndIconTabHeight = 72.0;
+
+/// Defines how the bounds of the selected tab indicator are computed.
+///
+/// See also:
+///
+///  * [TabBar], which displays a row of tabs.
+///  * [TabBarView], which displays a widget for the currently selected tab.
+///  * [TabBar.indicator], which defines the appearance of the selected tab
+///    indicator relative to the tab's bounds.
+enum TabBarIndicatorSize {
+  /// The tab indicator's bounds are as wide as the space occupied by the tab
+  /// in the tab bar: from the right edge of the previous tab to the left edge
+  /// of the next tab.
+  tab,
+
+  /// The tab's bounds are only as wide as the (centered) tab widget itself.
+  ///
+  /// This value is used to align the tab's label, typically a [Tab]
+  /// widget's text or icon, with the selected tab indicator.
+  label,
+}
+
+/// A material design [TabBar] tab.
+///
+/// If both [icon] and [text] are provided, the text is displayed below
+/// the icon.
+///
+/// See also:
+///
+///  * [TabBar], which displays a row of tabs.
+///  * [TabBarView], which displays a widget for the currently selected tab.
+///  * [TabController], which coordinates tab selection between a [TabBar] and a [TabBarView].
+///  * <https://material.io/design/components/tabs.html>
+class Tab extends StatelessWidget {
+  /// Creates a material design [TabBar] tab.
+  ///
+  /// At least one of [text], [icon], and [child] must be non-null. The [text]
+  /// and [child] arguments must not be used at the same time. The
+  /// [iconMargin] is only useful when [icon] and either one of [text] or
+  /// [child] is non-null.
+  const Tab({
+    Key? key,
+    this.text,
+    this.icon,
+    this.iconMargin = const EdgeInsets.only(bottom: 10.0),
+    this.child,
+  }) : assert(text != null || child != null || icon != null),
+       assert(text == null || child == null),
+       super(key: key);
+
+  /// The text to display as the tab's label.
+  ///
+  /// Must not be used in combination with [child].
+  final String? text;
+
+  /// The widget to be used as the tab's label.
+  ///
+  /// Usually a [Text] widget, possibly wrapped in a [Semantics] widget.
+  ///
+  /// Must not be used in combination with [text].
+  final Widget? child;
+
+  /// An icon to display as the tab's label.
+  final Widget? icon;
+
+  /// The margin added around the tab's icon.
+  ///
+  /// Only useful when used in combination with [icon], and either one of
+  /// [text] or [child] is non-null.
+  final EdgeInsetsGeometry iconMargin;
+
+  Widget _buildLabelText() {
+    return child ?? Text(text!, softWrap: false, overflow: TextOverflow.fade);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+
+    final double height;
+    final Widget label;
+    if (icon == null) {
+      height = _kTabHeight;
+      label = _buildLabelText();
+    } else if (text == null && child == null) {
+      height = _kTabHeight;
+      label = icon!;
+    } else {
+      height = _kTextAndIconTabHeight;
+      label = Column(
+        mainAxisAlignment: MainAxisAlignment.center,
+        crossAxisAlignment: CrossAxisAlignment.center,
+        children: <Widget>[
+          Container(
+            child: icon,
+            margin: iconMargin,
+          ),
+          _buildLabelText(),
+        ],
+      );
+    }
+
+    return SizedBox(
+      height: height,
+      child: Center(
+        child: label,
+        widthFactor: 1.0,
+      ),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(StringProperty('text', text, defaultValue: null));
+    properties.add(DiagnosticsProperty<Widget>('icon', icon, defaultValue: null));
+  }
+}
+
+class _TabStyle extends AnimatedWidget {
+  const _TabStyle({
+    Key? key,
+    required Animation<double> animation,
+    required this.selected,
+    required this.labelColor,
+    required this.unselectedLabelColor,
+    required this.labelStyle,
+    required this.unselectedLabelStyle,
+    required this.child,
+  }) : super(key: key, listenable: animation);
+
+  final TextStyle? labelStyle;
+  final TextStyle? unselectedLabelStyle;
+  final bool selected;
+  final Color? labelColor;
+  final Color? unselectedLabelColor;
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData themeData = Theme.of(context);
+    final TabBarTheme tabBarTheme = TabBarTheme.of(context);
+    final Animation<double> animation = listenable as Animation<double>;
+
+    // To enable TextStyle.lerp(style1, style2, value), both styles must have
+    // the same value of inherit. Force that to be inherit=true here.
+    final TextStyle defaultStyle = (labelStyle
+      ?? tabBarTheme.labelStyle
+      ?? themeData.primaryTextTheme.bodyText1!
+    ).copyWith(inherit: true);
+    final TextStyle defaultUnselectedStyle = (unselectedLabelStyle
+      ?? tabBarTheme.unselectedLabelStyle
+      ?? labelStyle
+      ?? themeData.primaryTextTheme.bodyText1!
+    ).copyWith(inherit: true);
+    final TextStyle textStyle = selected
+      ? TextStyle.lerp(defaultStyle, defaultUnselectedStyle, animation.value)!
+      : TextStyle.lerp(defaultUnselectedStyle, defaultStyle, animation.value)!;
+
+    final Color selectedColor = labelColor
+       ?? tabBarTheme.labelColor
+       ?? themeData.primaryTextTheme.bodyText1!.color!;
+    final Color unselectedColor = unselectedLabelColor
+      ?? tabBarTheme.unselectedLabelColor
+      ?? selectedColor.withAlpha(0xB2); // 70% alpha
+    final Color color = selected
+      ? Color.lerp(selectedColor, unselectedColor, animation.value)!
+      : Color.lerp(unselectedColor, selectedColor, animation.value)!;
+
+    return DefaultTextStyle(
+      style: textStyle.copyWith(color: color),
+      child: IconTheme.merge(
+        data: IconThemeData(
+          size: 24.0,
+          color: color,
+        ),
+        child: child,
+      ),
+    );
+  }
+}
+
+typedef _LayoutCallback = void Function(List<double> xOffsets, TextDirection textDirection, double width);
+
+class _TabLabelBarRenderer extends RenderFlex {
+  _TabLabelBarRenderer({
+    List<RenderBox>? children,
+    required Axis direction,
+    required MainAxisSize mainAxisSize,
+    required MainAxisAlignment mainAxisAlignment,
+    required CrossAxisAlignment crossAxisAlignment,
+    required TextDirection textDirection,
+    required VerticalDirection verticalDirection,
+    required this.onPerformLayout,
+  }) : assert(onPerformLayout != null),
+       assert(textDirection != null),
+       super(
+         children: children,
+         direction: direction,
+         mainAxisSize: mainAxisSize,
+         mainAxisAlignment: mainAxisAlignment,
+         crossAxisAlignment: crossAxisAlignment,
+         textDirection: textDirection,
+         verticalDirection: verticalDirection,
+       );
+
+  _LayoutCallback onPerformLayout;
+
+  @override
+  void performLayout() {
+    super.performLayout();
+    // xOffsets will contain childCount+1 values, giving the offsets of the
+    // leading edge of the first tab as the first value, of the leading edge of
+    // the each subsequent tab as each subsequent value, and of the trailing
+    // edge of the last tab as the last value.
+    RenderBox? child = firstChild;
+    final List<double> xOffsets = <double>[];
+    while (child != null) {
+      final FlexParentData childParentData = child.parentData! as FlexParentData;
+      xOffsets.add(childParentData.offset.dx);
+      assert(child.parentData == childParentData);
+      child = childParentData.nextSibling;
+    }
+    assert(textDirection != null);
+    switch (textDirection!) {
+      case TextDirection.rtl:
+        xOffsets.insert(0, size.width);
+        break;
+      case TextDirection.ltr:
+        xOffsets.add(size.width);
+        break;
+    }
+    onPerformLayout(xOffsets, textDirection!, size.width);
+  }
+}
+
+// This class and its renderer class only exist to report the widths of the tabs
+// upon layout. The tab widths are only used at paint time (see _IndicatorPainter)
+// or in response to input.
+class _TabLabelBar extends Flex {
+  _TabLabelBar({
+    Key? key,
+    List<Widget> children = const <Widget>[],
+    required this.onPerformLayout,
+  }) : super(
+    key: key,
+    children: children,
+    direction: Axis.horizontal,
+    mainAxisSize: MainAxisSize.max,
+    mainAxisAlignment: MainAxisAlignment.start,
+    crossAxisAlignment: CrossAxisAlignment.center,
+    verticalDirection: VerticalDirection.down,
+  );
+
+  final _LayoutCallback onPerformLayout;
+
+  @override
+  RenderFlex createRenderObject(BuildContext context) {
+    return _TabLabelBarRenderer(
+      direction: direction,
+      mainAxisAlignment: mainAxisAlignment,
+      mainAxisSize: mainAxisSize,
+      crossAxisAlignment: crossAxisAlignment,
+      textDirection: getEffectiveTextDirection(context)!,
+      verticalDirection: verticalDirection,
+      onPerformLayout: onPerformLayout,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _TabLabelBarRenderer renderObject) {
+    super.updateRenderObject(context, renderObject);
+    renderObject.onPerformLayout = onPerformLayout;
+  }
+}
+
+double _indexChangeProgress(TabController controller) {
+  final double controllerValue = controller.animation!.value;
+  final double previousIndex = controller.previousIndex.toDouble();
+  final double currentIndex = controller.index.toDouble();
+
+  // The controller's offset is changing because the user is dragging the
+  // TabBarView's PageView to the left or right.
+  if (!controller.indexIsChanging)
+    return (currentIndex - controllerValue).abs().clamp(0.0, 1.0);
+
+  // The TabController animation's value is changing from previousIndex to currentIndex.
+  return (controllerValue - currentIndex).abs() / (currentIndex - previousIndex).abs();
+}
+
+class _IndicatorPainter extends CustomPainter {
+  _IndicatorPainter({
+    required this.controller,
+    required this.indicator,
+    required this.indicatorSize,
+    required this.tabKeys,
+    required _IndicatorPainter? old,
+    required this.indicatorPadding,
+  }) : assert(controller != null),
+       assert(indicator != null),
+       super(repaint: controller.animation) {
+    if (old != null)
+      saveTabOffsets(old._currentTabOffsets, old._currentTextDirection);
+  }
+
+  final TabController controller;
+  final Decoration indicator;
+  final TabBarIndicatorSize? indicatorSize;
+  final EdgeInsetsGeometry indicatorPadding;
+  final List<GlobalKey> tabKeys;
+
+  // _currentTabOffsets and _currentTextDirection are set each time TabBar
+  // layout is completed. These values can be null when TabBar contains no
+  // tabs, since there are nothing to lay out.
+  List<double>? _currentTabOffsets;
+  TextDirection? _currentTextDirection;
+
+  Rect? _currentRect;
+  BoxPainter? _painter;
+  bool _needsPaint = false;
+  void markNeedsPaint() {
+    _needsPaint = true;
+  }
+
+  void dispose() {
+    _painter?.dispose();
+  }
+
+  void saveTabOffsets(List<double>? tabOffsets, TextDirection? textDirection) {
+    _currentTabOffsets = tabOffsets;
+    _currentTextDirection = textDirection;
+  }
+
+  // _currentTabOffsets[index] is the offset of the start edge of the tab at index, and
+  // _currentTabOffsets[_currentTabOffsets.length] is the end edge of the last tab.
+  int get maxTabIndex => _currentTabOffsets!.length - 2;
+
+  double centerOf(int tabIndex) {
+    assert(_currentTabOffsets != null);
+    assert(_currentTabOffsets!.isNotEmpty);
+    assert(tabIndex >= 0);
+    assert(tabIndex <= maxTabIndex);
+    return (_currentTabOffsets![tabIndex] + _currentTabOffsets![tabIndex + 1]) / 2.0;
+  }
+
+  Rect indicatorRect(Size tabBarSize, int tabIndex) {
+    assert(_currentTabOffsets != null);
+    assert(_currentTextDirection != null);
+    assert(_currentTabOffsets!.isNotEmpty);
+    assert(tabIndex >= 0);
+    assert(tabIndex <= maxTabIndex);
+    double tabLeft, tabRight;
+    switch (_currentTextDirection!) {
+      case TextDirection.rtl:
+        tabLeft = _currentTabOffsets![tabIndex + 1];
+        tabRight = _currentTabOffsets![tabIndex];
+        break;
+      case TextDirection.ltr:
+        tabLeft = _currentTabOffsets![tabIndex];
+        tabRight = _currentTabOffsets![tabIndex + 1];
+        break;
+    }
+
+    if (indicatorSize == TabBarIndicatorSize.label) {
+      final double tabWidth = tabKeys[tabIndex].currentContext!.size!.width;
+      final double delta = ((tabRight - tabLeft) - tabWidth) / 2.0;
+      tabLeft += delta;
+      tabRight -= delta;
+    }
+
+    final EdgeInsets insets = indicatorPadding.resolve(_currentTextDirection);
+    final Rect rect = Rect.fromLTWH(tabLeft, 0.0, tabRight - tabLeft, tabBarSize.height);
+
+    if (!(rect.size >= insets.collapsedSize)) {
+      throw FlutterError(
+          'indicatorPadding insets should be less than Tab Size\n'
+          'Rect Size : ${rect.size}, Insets: ${insets.toString()}'
+      );
+    }
+    return insets.deflateRect(rect);
+  }
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    _needsPaint = false;
+    _painter ??= indicator.createBoxPainter(markNeedsPaint);
+
+    final double index = controller.index.toDouble();
+    final double value = controller.animation!.value;
+    final bool ltr = index > value;
+    final int from = (ltr ? value.floor() : value.ceil()).clamp(0, maxTabIndex).toInt();
+    final int to = (ltr ? from + 1 : from - 1).clamp(0, maxTabIndex).toInt();
+    final Rect fromRect = indicatorRect(size, from);
+    final Rect toRect = indicatorRect(size, to);
+    _currentRect = Rect.lerp(fromRect, toRect, (value - from).abs());
+    assert(_currentRect != null);
+
+    final ImageConfiguration configuration = ImageConfiguration(
+      size: _currentRect!.size,
+      textDirection: _currentTextDirection,
+    );
+    _painter!.paint(canvas, _currentRect!.topLeft, configuration);
+  }
+
+  @override
+  bool shouldRepaint(_IndicatorPainter old) {
+    return _needsPaint
+        || controller != old.controller
+        || indicator != old.indicator
+        || tabKeys.length != old.tabKeys.length
+        || (!listEquals(_currentTabOffsets, old._currentTabOffsets))
+        || _currentTextDirection != old._currentTextDirection;
+  }
+}
+
+class _ChangeAnimation extends Animation<double> with AnimationWithParentMixin<double> {
+  _ChangeAnimation(this.controller);
+
+  final TabController controller;
+
+  @override
+  Animation<double> get parent => controller.animation!;
+
+  @override
+  void removeStatusListener(AnimationStatusListener listener) {
+    if (controller.animation != null)
+      super.removeStatusListener(listener);
+  }
+
+  @override
+  void removeListener(VoidCallback listener) {
+    if (controller.animation != null)
+      super.removeListener(listener);
+  }
+
+  @override
+  double get value => _indexChangeProgress(controller);
+}
+
+class _DragAnimation extends Animation<double> with AnimationWithParentMixin<double> {
+  _DragAnimation(this.controller, this.index);
+
+  final TabController controller;
+  final int index;
+
+  @override
+  Animation<double> get parent => controller.animation!;
+
+  @override
+  void removeStatusListener(AnimationStatusListener listener) {
+    if (controller.animation != null)
+      super.removeStatusListener(listener);
+  }
+
+  @override
+  void removeListener(VoidCallback listener) {
+    if (controller.animation != null)
+      super.removeListener(listener);
+  }
+
+  @override
+  double get value {
+    assert(!controller.indexIsChanging);
+    final double controllerMaxValue = (controller.length - 1).toDouble();
+    final double controllerValue = controller.animation!.value.clamp(0.0, controllerMaxValue);
+    return (controllerValue - index.toDouble()).abs().clamp(0.0, 1.0);
+  }
+}
+
+// This class, and TabBarScrollController, only exist to handle the case
+// where a scrollable TabBar has a non-zero initialIndex. In that case we can
+// only compute the scroll position's initial scroll offset (the "correct"
+// pixels value) after the TabBar viewport width and scroll limits are known.
+class _TabBarScrollPosition extends ScrollPositionWithSingleContext {
+  _TabBarScrollPosition({
+    required ScrollPhysics physics,
+    required ScrollContext context,
+    required ScrollPosition? oldPosition,
+    required this.tabBar,
+  }) : super(
+    physics: physics,
+    context: context,
+    initialPixels: null,
+    oldPosition: oldPosition,
+  );
+
+  final _TabBarState tabBar;
+
+  bool? _initialViewportDimensionWasZero;
+
+  @override
+  bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
+    bool result = true;
+    if (_initialViewportDimensionWasZero != true) {
+      // If the viewport never had a non-zero dimension, we just want to jump
+      // to the initial scroll position to avoid strange scrolling effects in
+      // release mode: In release mode, the viewport temporarily may have a
+      // dimension of zero before the actual dimension is calculated. In that
+      // scenario, setting the actual dimension would cause a strange scroll
+      // effect without this guard because the super call below would starts a
+      // ballistic scroll activity.
+      assert(viewportDimension != null);
+      _initialViewportDimensionWasZero = viewportDimension != 0.0;
+      correctPixels(tabBar._initialScrollOffset(viewportDimension, minScrollExtent, maxScrollExtent));
+      result = false;
+    }
+    return super.applyContentDimensions(minScrollExtent, maxScrollExtent) && result;
+  }
+}
+
+// This class, and TabBarScrollPosition, only exist to handle the case
+// where a scrollable TabBar has a non-zero initialIndex.
+class _TabBarScrollController extends ScrollController {
+  _TabBarScrollController(this.tabBar);
+
+  final _TabBarState tabBar;
+
+  @override
+  ScrollPosition createScrollPosition(ScrollPhysics physics, ScrollContext context, ScrollPosition? oldPosition) {
+    return _TabBarScrollPosition(
+      physics: physics,
+      context: context,
+      oldPosition: oldPosition,
+      tabBar: tabBar,
+    );
+  }
+}
+
+/// A material design widget that displays a horizontal row of tabs.
+///
+/// Typically created as the [AppBar.bottom] part of an [AppBar] and in
+/// conjunction with a [TabBarView].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=POtoEH-5l40}
+///
+/// If a [TabController] is not provided, then a [DefaultTabController] ancestor
+/// must be provided instead. The tab controller's [TabController.length] must
+/// equal the length of the [tabs] list and the length of the
+/// [TabBarView.children] list.
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// Uses values from [TabBarTheme] if it is set in the current context.
+///
+/// To see a sample implementation, visit the [TabController] documentation.
+///
+/// See also:
+///
+///  * [TabBarView], which displays page views that correspond to each tab.
+class TabBar extends StatefulWidget implements PreferredSizeWidget {
+  /// Creates a material design tab bar.
+  ///
+  /// The [tabs] argument must not be null and its length must match the [controller]'s
+  /// [TabController.length].
+  ///
+  /// If a [TabController] is not provided, then there must be a
+  /// [DefaultTabController] ancestor.
+  ///
+  /// The [indicatorWeight] parameter defaults to 2, and must not be null.
+  ///
+  /// The [indicatorPadding] parameter defaults to [EdgeInsets.zero], and must not be null.
+  ///
+  /// If [indicator] is not null or provided from [TabBarTheme],
+  /// then [indicatorWeight], [indicatorPadding], and [indicatorColor] are ignored.
+  const TabBar({
+    Key? key,
+    required this.tabs,
+    this.controller,
+    this.isScrollable = false,
+    this.indicatorColor,
+    this.automaticIndicatorColorAdjustment = true,
+    this.indicatorWeight = 2.0,
+    this.indicatorPadding = EdgeInsets.zero,
+    this.indicator,
+    this.indicatorSize,
+    this.labelColor,
+    this.labelStyle,
+    this.labelPadding,
+    this.unselectedLabelColor,
+    this.unselectedLabelStyle,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.overlayColor,
+    this.mouseCursor,
+    this.enableFeedback,
+    this.onTap,
+    this.physics,
+  }) : assert(tabs != null),
+       assert(isScrollable != null),
+       assert(dragStartBehavior != null),
+       assert(indicator != null || (indicatorWeight != null && indicatorWeight > 0.0)),
+       assert(indicator != null || (indicatorPadding != null)),
+       super(key: key);
+
+  /// Typically a list of two or more [Tab] widgets.
+  ///
+  /// The length of this list must match the [controller]'s [TabController.length]
+  /// and the length of the [TabBarView.children] list.
+  final List<Widget> tabs;
+
+  /// This widget's selection and animation state.
+  ///
+  /// If [TabController] is not provided, then the value of [DefaultTabController.of]
+  /// will be used.
+  final TabController? controller;
+
+  /// Whether this tab bar can be scrolled horizontally.
+  ///
+  /// If [isScrollable] is true, then each tab is as wide as needed for its label
+  /// and the entire [TabBar] is scrollable. Otherwise each tab gets an equal
+  /// share of the available space.
+  final bool isScrollable;
+
+  /// The color of the line that appears below the selected tab.
+  ///
+  /// If this parameter is null, then the value of the Theme's indicatorColor
+  /// property is used.
+  ///
+  /// If [indicator] is specified or provided from [TabBarTheme],
+  /// this property is ignored.
+  final Color? indicatorColor;
+
+  /// The thickness of the line that appears below the selected tab.
+  ///
+  /// The value of this parameter must be greater than zero and its default
+  /// value is 2.0.
+  ///
+  /// If [indicator] is specified or provided from [TabBarTheme],
+  /// this property is ignored.
+  final double indicatorWeight;
+
+
+  /// Padding for indicator.
+  /// This property will now no longer be ignored even if indicator is declared
+  /// or provided by [TabBarTheme]
+  ///
+  /// For [isScrollable] tab bars, specifying [kTabLabelPadding] will align
+  /// the indicator with the tab's text for [Tab] widgets and all but the
+  /// shortest [Tab.text] values.
+  ///
+  /// The default value of [indicatorPadding] is [EdgeInsets.zero].
+  final EdgeInsetsGeometry indicatorPadding;
+
+  /// Defines the appearance of the selected tab indicator.
+  ///
+  /// If [indicator] is specified or provided from [TabBarTheme],
+  /// the [indicatorColor], and [indicatorWeight] properties are ignored.
+  ///
+  /// The default, underline-style, selected tab indicator can be defined with
+  /// [UnderlineTabIndicator].
+  ///
+  /// The indicator's size is based on the tab's bounds. If [indicatorSize]
+  /// is [TabBarIndicatorSize.tab] the tab's bounds are as wide as the space
+  /// occupied by the tab in the tab bar. If [indicatorSize] is
+  /// [TabBarIndicatorSize.label], then the tab's bounds are only as wide as
+  /// the tab widget itself.
+  final Decoration? indicator;
+
+  /// Whether this tab bar should automatically adjust the [indicatorColor].
+  ///
+  /// If [automaticIndicatorColorAdjustment] is true,
+  /// then the [indicatorColor] will be automatically adjusted to [Colors.white]
+  /// when the [indicatorColor] is same as [Material.color] of the [Material] parent widget.
+  final bool automaticIndicatorColorAdjustment;
+
+  /// Defines how the selected tab indicator's size is computed.
+  ///
+  /// The size of the selected tab indicator is defined relative to the
+  /// tab's overall bounds if [indicatorSize] is [TabBarIndicatorSize.tab]
+  /// (the default) or relative to the bounds of the tab's widget if
+  /// [indicatorSize] is [TabBarIndicatorSize.label].
+  ///
+  /// The selected tab's location appearance can be refined further with
+  /// the [indicatorColor], [indicatorWeight], [indicatorPadding], and
+  /// [indicator] properties.
+  final TabBarIndicatorSize? indicatorSize;
+
+  /// The color of selected tab labels.
+  ///
+  /// Unselected tab labels are rendered with the same color rendered at 70%
+  /// opacity unless [unselectedLabelColor] is non-null.
+  ///
+  /// If this parameter is null, then the color of the [ThemeData.primaryTextTheme]'s
+  /// bodyText1 text color is used.
+  final Color? labelColor;
+
+  /// The color of unselected tab labels.
+  ///
+  /// If this property is null, unselected tab labels are rendered with the
+  /// [labelColor] with 70% opacity.
+  final Color? unselectedLabelColor;
+
+  /// The text style of the selected tab labels.
+  ///
+  /// If [unselectedLabelStyle] is null, then this text style will be used for
+  /// both selected and unselected label styles.
+  ///
+  /// If this property is null, then the text style of the
+  /// [ThemeData.primaryTextTheme]'s bodyText1 definition is used.
+  final TextStyle? labelStyle;
+
+  /// The padding added to each of the tab labels.
+  ///
+  /// If this property is null, then kTabLabelPadding is used.
+  final EdgeInsetsGeometry? labelPadding;
+
+  /// The text style of the unselected tab labels.
+  ///
+  /// If this property is null, then the [labelStyle] value is used. If [labelStyle]
+  /// is null, then the text style of the [ThemeData.primaryTextTheme]'s
+  /// bodyText1 definition is used.
+  final TextStyle? unselectedLabelStyle;
+
+  /// Defines the ink response focus, hover, and splash colors.
+  ///
+  /// If non-null, it is resolved against one of [MaterialState.focused],
+  /// [MaterialState.hovered], and [MaterialState.pressed].
+  ///
+  /// [MaterialState.pressed] triggers a ripple (an ink splash), per
+  /// the current Material Design spec. The [overlayColor] doesn't map
+  /// a state to [InkResponse.highlightColor] because a separate highlight
+  /// is not used by the current design guidelines. See
+  /// https://material.io/design/interaction/states.html#pressed
+  ///
+  /// If the overlay color is null or resolves to null, then the default values
+  /// for [InkResponse.focusColor], [InkResponse.hoverColor], [InkResponse.splashColor]
+  /// will be used instead.
+  final MaterialStateProperty<Color?>? overlayColor;
+
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// individual tab widgets.
+  ///
+  /// If this property is null, [SystemMouseCursors.click] will be used.
+  final MouseCursor? mouseCursor;
+
+  /// Whether detected gestures should provide acoustic and/or haptic feedback.
+  ///
+  /// For example, on Android a tap will produce a clicking sound and a long-press
+  /// will produce a short vibration, when feedback is enabled.
+  ///
+  /// Defaults to true.
+  final bool? enableFeedback;
+
+  /// An optional callback that's called when the [TabBar] is tapped.
+  ///
+  /// The callback is applied to the index of the tab where the tap occurred.
+  ///
+  /// This callback has no effect on the default handling of taps. It's for
+  /// applications that want to do a little extra work when a tab is tapped,
+  /// even if the tap doesn't change the TabController's index. TabBar [onTap]
+  /// callbacks should not make changes to the TabController since that would
+  /// interfere with the default tap handler.
+  final ValueChanged<int>? onTap;
+
+  /// How the [TabBar]'s scroll view should respond to user input.
+  ///
+  /// For example, determines how the scroll view continues to animate after the
+  /// user stops dragging the scroll view.
+  ///
+  /// Defaults to matching platform conventions.
+  final ScrollPhysics? physics;
+
+  /// A size whose height depends on if the tabs have both icons and text.
+  ///
+  /// [AppBar] uses this size to compute its own preferred size.
+  @override
+  Size get preferredSize {
+    for (final Widget item in tabs) {
+      if (item is Tab) {
+        final Tab tab = item;
+        if ((tab.text != null || tab.child != null) && tab.icon != null)
+          return Size.fromHeight(_kTextAndIconTabHeight + indicatorWeight);
+      }
+    }
+    return Size.fromHeight(_kTabHeight + indicatorWeight);
+  }
+
+  @override
+  _TabBarState createState() => _TabBarState();
+}
+
+class _TabBarState extends State<TabBar> {
+  ScrollController? _scrollController;
+  TabController? _controller;
+  _IndicatorPainter? _indicatorPainter;
+  int? _currentIndex;
+  late double _tabStripWidth;
+  late List<GlobalKey> _tabKeys;
+
+  @override
+  void initState() {
+    super.initState();
+    // If indicatorSize is TabIndicatorSize.label, _tabKeys[i] is used to find
+    // the width of tab widget i. See _IndicatorPainter.indicatorRect().
+    _tabKeys = widget.tabs.map((Widget tab) => GlobalKey()).toList();
+  }
+
+  Decoration get _indicator {
+    if (widget.indicator != null)
+      return widget.indicator!;
+    final TabBarTheme tabBarTheme = TabBarTheme.of(context);
+    if (tabBarTheme.indicator != null)
+      return tabBarTheme.indicator!;
+
+    Color color = widget.indicatorColor ?? Theme.of(context).indicatorColor;
+    // ThemeData tries to avoid this by having indicatorColor avoid being the
+    // primaryColor. However, it's possible that the tab bar is on a
+    // Material that isn't the primaryColor. In that case, if the indicator
+    // color ends up matching the material's color, then this overrides it.
+    // When that happens, automatic transitions of the theme will likely look
+    // ugly as the indicator color suddenly snaps to white at one end, but it's
+    // not clear how to avoid that any further.
+    //
+    // The material's color might be null (if it's a transparency). In that case
+    // there's no good way for us to find out what the color is so we don't.
+    //
+    // TODO(xu-baolin): Remove automatic adjustment to white color indicator
+    // with a better long-term solution.
+    // https://github.com/flutter/flutter/pull/68171#pullrequestreview-517753917
+    if (widget.automaticIndicatorColorAdjustment && color.value == Material.of(context)?.color?.value)
+      color = Colors.white;
+
+    return UnderlineTabIndicator(
+      borderSide: BorderSide(
+        width: widget.indicatorWeight,
+        color: color,
+      ),
+    );
+  }
+
+  // If the TabBar is rebuilt with a new tab controller, the caller should
+  // dispose the old one. In that case the old controller's animation will be
+  // null and should not be accessed.
+  bool get _controllerIsValid => _controller?.animation != null;
+
+  void _updateTabController() {
+    final TabController? newController = widget.controller ?? DefaultTabController.of(context);
+    assert(() {
+      if (newController == null) {
+        throw FlutterError(
+          'No TabController for ${widget.runtimeType}.\n'
+          'When creating a ${widget.runtimeType}, you must either provide an explicit '
+          'TabController using the "controller" property, or you must ensure that there '
+          'is a DefaultTabController above the ${widget.runtimeType}.\n'
+          'In this case, there was neither an explicit controller nor a default controller.'
+        );
+      }
+      return true;
+    }());
+
+    if (newController == _controller)
+      return;
+
+    if (_controllerIsValid) {
+      _controller!.animation!.removeListener(_handleTabControllerAnimationTick);
+      _controller!.removeListener(_handleTabControllerTick);
+    }
+    _controller = newController;
+    if (_controller != null) {
+      _controller!.animation!.addListener(_handleTabControllerAnimationTick);
+      _controller!.addListener(_handleTabControllerTick);
+      _currentIndex = _controller!.index;
+    }
+  }
+
+  void _initIndicatorPainter() {
+    _indicatorPainter = !_controllerIsValid ? null : _IndicatorPainter(
+      controller: _controller!,
+      indicator: _indicator,
+      indicatorSize: widget.indicatorSize ?? TabBarTheme.of(context).indicatorSize,
+      indicatorPadding: widget.indicatorPadding,
+      tabKeys: _tabKeys,
+      old: _indicatorPainter,
+    );
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    assert(debugCheckHasMaterial(context));
+    _updateTabController();
+    _initIndicatorPainter();
+  }
+
+  @override
+  void didUpdateWidget(TabBar oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.controller != oldWidget.controller) {
+      _updateTabController();
+      _initIndicatorPainter();
+    } else if (widget.indicatorColor != oldWidget.indicatorColor ||
+        widget.indicatorWeight != oldWidget.indicatorWeight ||
+        widget.indicatorSize != oldWidget.indicatorSize ||
+        widget.indicator != oldWidget.indicator) {
+      _initIndicatorPainter();
+    }
+
+    if (widget.tabs.length > oldWidget.tabs.length) {
+      final int delta = widget.tabs.length - oldWidget.tabs.length;
+      _tabKeys.addAll(List<GlobalKey>.generate(delta, (int n) => GlobalKey()));
+    } else if (widget.tabs.length < oldWidget.tabs.length) {
+      _tabKeys.removeRange(widget.tabs.length, oldWidget.tabs.length);
+    }
+  }
+
+  @override
+  void dispose() {
+    _indicatorPainter!.dispose();
+    if (_controllerIsValid) {
+      _controller!.animation!.removeListener(_handleTabControllerAnimationTick);
+      _controller!.removeListener(_handleTabControllerTick);
+    }
+    _controller = null;
+    // We don't own the _controller Animation, so it's not disposed here.
+    super.dispose();
+  }
+
+  int get maxTabIndex => _indicatorPainter!.maxTabIndex;
+
+  double _tabScrollOffset(int index, double viewportWidth, double minExtent, double maxExtent) {
+    if (!widget.isScrollable)
+      return 0.0;
+    double tabCenter = _indicatorPainter!.centerOf(index);
+    switch (Directionality.of(context)) {
+      case TextDirection.rtl:
+        tabCenter = _tabStripWidth - tabCenter;
+        break;
+      case TextDirection.ltr:
+        break;
+    }
+    return (tabCenter - viewportWidth / 2.0).clamp(minExtent, maxExtent);
+  }
+
+  double _tabCenteredScrollOffset(int index) {
+    final ScrollPosition position = _scrollController!.position;
+    return _tabScrollOffset(index, position.viewportDimension, position.minScrollExtent, position.maxScrollExtent);
+  }
+
+  double _initialScrollOffset(double viewportWidth, double minExtent, double maxExtent) {
+    return _tabScrollOffset(_currentIndex!, viewportWidth, minExtent, maxExtent);
+  }
+
+  void _scrollToCurrentIndex() {
+    final double offset = _tabCenteredScrollOffset(_currentIndex!);
+    _scrollController!.animateTo(offset, duration: kTabScrollDuration, curve: Curves.ease);
+  }
+
+  void _scrollToControllerValue() {
+    final double? leadingPosition = _currentIndex! > 0 ? _tabCenteredScrollOffset(_currentIndex! - 1) : null;
+    final double middlePosition = _tabCenteredScrollOffset(_currentIndex!);
+    final double? trailingPosition = _currentIndex! < maxTabIndex ? _tabCenteredScrollOffset(_currentIndex! + 1) : null;
+
+    final double index = _controller!.index.toDouble();
+    final double value = _controller!.animation!.value;
+    final double offset;
+    if (value == index - 1.0)
+      offset = leadingPosition ?? middlePosition;
+    else if (value == index + 1.0)
+      offset = trailingPosition ?? middlePosition;
+    else if (value == index)
+      offset = middlePosition;
+    else if (value < index)
+      offset = leadingPosition == null ? middlePosition : lerpDouble(middlePosition, leadingPosition, index - value)!;
+    else
+      offset = trailingPosition == null ? middlePosition : lerpDouble(middlePosition, trailingPosition, value - index)!;
+
+    _scrollController!.jumpTo(offset);
+  }
+
+  void _handleTabControllerAnimationTick() {
+    assert(mounted);
+    if (!_controller!.indexIsChanging && widget.isScrollable) {
+      // Sync the TabBar's scroll position with the TabBarView's PageView.
+      _currentIndex = _controller!.index;
+      _scrollToControllerValue();
+    }
+  }
+
+  void _handleTabControllerTick() {
+    if (_controller!.index != _currentIndex) {
+      _currentIndex = _controller!.index;
+      if (widget.isScrollable)
+        _scrollToCurrentIndex();
+    }
+    setState(() {
+      // Rebuild the tabs after a (potentially animated) index change
+      // has completed.
+    });
+  }
+
+  // Called each time layout completes.
+  void _saveTabOffsets(List<double> tabOffsets, TextDirection textDirection, double width) {
+    _tabStripWidth = width;
+    _indicatorPainter?.saveTabOffsets(tabOffsets, textDirection);
+  }
+
+  void _handleTap(int index) {
+    assert(index >= 0 && index < widget.tabs.length);
+    _controller!.animateTo(index);
+    if (widget.onTap != null) {
+      widget.onTap!(index);
+    }
+  }
+
+  Widget _buildStyledTab(Widget child, bool selected, Animation<double> animation) {
+    return _TabStyle(
+      animation: animation,
+      selected: selected,
+      labelColor: widget.labelColor,
+      unselectedLabelColor: widget.unselectedLabelColor,
+      labelStyle: widget.labelStyle,
+      unselectedLabelStyle: widget.unselectedLabelStyle,
+      child: child,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterialLocalizations(context));
+    assert(() {
+      if (_controller!.length != widget.tabs.length) {
+        throw FlutterError(
+          "Controller's length property (${_controller!.length}) does not match the "
+          "number of tabs (${widget.tabs.length}) present in TabBar's tabs property."
+        );
+      }
+      return true;
+    }());
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    if (_controller!.length == 0) {
+      return Container(
+        height: _kTabHeight + widget.indicatorWeight,
+      );
+    }
+
+    final TabBarTheme tabBarTheme = TabBarTheme.of(context);
+
+    final List<Widget> wrappedTabs = <Widget>[
+      for (int i = 0; i < widget.tabs.length; i += 1)
+        Center(
+          heightFactor: 1.0,
+          child: Padding(
+            padding: widget.labelPadding ?? tabBarTheme.labelPadding ?? kTabLabelPadding,
+            child: KeyedSubtree(
+              key: _tabKeys[i],
+              child: widget.tabs[i],
+            ),
+          ),
+        )
+    ];
+
+    // If the controller was provided by DefaultTabController and we're part
+    // of a Hero (typically the AppBar), then we will not be able to find the
+    // controller during a Hero transition. See https://github.com/flutter/flutter/issues/213.
+    if (_controller != null) {
+      final int previousIndex = _controller!.previousIndex;
+
+      if (_controller!.indexIsChanging) {
+        // The user tapped on a tab, the tab controller's animation is running.
+        assert(_currentIndex != previousIndex);
+        final Animation<double> animation = _ChangeAnimation(_controller!);
+        wrappedTabs[_currentIndex!] = _buildStyledTab(wrappedTabs[_currentIndex!], true, animation);
+        wrappedTabs[previousIndex] = _buildStyledTab(wrappedTabs[previousIndex], false, animation);
+      } else {
+        // The user is dragging the TabBarView's PageView left or right.
+        final int tabIndex = _currentIndex!;
+        final Animation<double> centerAnimation = _DragAnimation(_controller!, tabIndex);
+        wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], true, centerAnimation);
+        if (_currentIndex! > 0) {
+          final int tabIndex = _currentIndex! - 1;
+          final Animation<double> previousAnimation = ReverseAnimation(_DragAnimation(_controller!, tabIndex));
+          wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], false, previousAnimation);
+        }
+        if (_currentIndex! < widget.tabs.length - 1) {
+          final int tabIndex = _currentIndex! + 1;
+          final Animation<double> nextAnimation = ReverseAnimation(_DragAnimation(_controller!, tabIndex));
+          wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], false, nextAnimation);
+        }
+      }
+    }
+
+    // Add the tap handler to each tab. If the tab bar is not scrollable,
+    // then give all of the tabs equal flexibility so that they each occupy
+    // the same share of the tab bar's overall width.
+    final int tabCount = widget.tabs.length;
+    for (int index = 0; index < tabCount; index += 1) {
+      wrappedTabs[index] = InkWell(
+        mouseCursor: widget.mouseCursor ?? SystemMouseCursors.click,
+        onTap: () { _handleTap(index); },
+        enableFeedback: widget.enableFeedback ?? true,
+        overlayColor: widget.overlayColor,
+        child: Padding(
+          padding: EdgeInsets.only(bottom: widget.indicatorWeight),
+          child: Stack(
+            children: <Widget>[
+              wrappedTabs[index],
+              Semantics(
+                selected: index == _currentIndex,
+                label: localizations.tabLabel(tabIndex: index + 1, tabCount: tabCount),
+              ),
+            ],
+          ),
+        ),
+      );
+      if (!widget.isScrollable)
+        wrappedTabs[index] = Expanded(child: wrappedTabs[index]);
+    }
+
+    Widget tabBar = CustomPaint(
+      painter: _indicatorPainter,
+      child: _TabStyle(
+        animation: kAlwaysDismissedAnimation,
+        selected: false,
+        labelColor: widget.labelColor,
+        unselectedLabelColor: widget.unselectedLabelColor,
+        labelStyle: widget.labelStyle,
+        unselectedLabelStyle: widget.unselectedLabelStyle,
+        child: _TabLabelBar(
+          onPerformLayout: _saveTabOffsets,
+          children: wrappedTabs,
+        ),
+      ),
+    );
+
+    if (widget.isScrollable) {
+      _scrollController ??= _TabBarScrollController(this);
+      tabBar = SingleChildScrollView(
+        dragStartBehavior: widget.dragStartBehavior,
+        scrollDirection: Axis.horizontal,
+        controller: _scrollController,
+        physics: widget.physics,
+        child: tabBar,
+      );
+    }
+
+    return tabBar;
+  }
+}
+
+/// A page view that displays the widget which corresponds to the currently
+/// selected tab.
+///
+/// This widget is typically used in conjunction with a [TabBar].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=POtoEH-5l40}
+///
+/// If a [TabController] is not provided, then there must be a [DefaultTabController]
+/// ancestor.
+///
+/// The tab controller's [TabController.length] must equal the length of the
+/// [children] list and the length of the [TabBar.tabs] list.
+///
+/// To see a sample implementation, visit the [TabController] documentation.
+class TabBarView extends StatefulWidget {
+  /// Creates a page view with one child per tab.
+  ///
+  /// The length of [children] must be the same as the [controller]'s length.
+  const TabBarView({
+    Key? key,
+    required this.children,
+    this.controller,
+    this.physics,
+    this.dragStartBehavior = DragStartBehavior.start,
+  }) : assert(children != null),
+       assert(dragStartBehavior != null),
+       super(key: key);
+
+  /// This widget's selection and animation state.
+  ///
+  /// If [TabController] is not provided, then the value of [DefaultTabController.of]
+  /// will be used.
+  final TabController? controller;
+
+  /// One widget per tab.
+  ///
+  /// Its length must match the length of the [TabBar.tabs]
+  /// list, as well as the [controller]'s [TabController.length].
+  final List<Widget> children;
+
+  /// How the page view should respond to user input.
+  ///
+  /// For example, determines how the page view continues to animate after the
+  /// user stops dragging the page view.
+  ///
+  /// The physics are modified to snap to page boundaries using
+  /// [PageScrollPhysics] prior to being used.
+  ///
+  /// Defaults to matching platform conventions.
+  final ScrollPhysics? physics;
+
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  @override
+  _TabBarViewState createState() => _TabBarViewState();
+}
+
+class _TabBarViewState extends State<TabBarView> {
+  TabController? _controller;
+  late PageController _pageController;
+  late List<Widget> _children;
+  late List<Widget> _childrenWithKey;
+  int? _currentIndex;
+  int _warpUnderwayCount = 0;
+
+  // If the TabBarView is rebuilt with a new tab controller, the caller should
+  // dispose the old one. In that case the old controller's animation will be
+  // null and should not be accessed.
+  bool get _controllerIsValid => _controller?.animation != null;
+
+  void _updateTabController() {
+    final TabController? newController = widget.controller ?? DefaultTabController.of(context);
+    assert(() {
+      if (newController == null) {
+        throw FlutterError(
+          'No TabController for ${widget.runtimeType}.\n'
+          'When creating a ${widget.runtimeType}, you must either provide an explicit '
+          'TabController using the "controller" property, or you must ensure that there '
+          'is a DefaultTabController above the ${widget.runtimeType}.\n'
+          'In this case, there was neither an explicit controller nor a default controller.'
+        );
+      }
+      return true;
+    }());
+
+    if (newController == _controller)
+      return;
+
+    if (_controllerIsValid)
+      _controller!.animation!.removeListener(_handleTabControllerAnimationTick);
+    _controller = newController;
+    if (_controller != null)
+      _controller!.animation!.addListener(_handleTabControllerAnimationTick);
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _updateChildren();
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _updateTabController();
+    _currentIndex = _controller?.index;
+    _pageController = PageController(initialPage: _currentIndex ?? 0);
+  }
+
+  @override
+  void didUpdateWidget(TabBarView oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.controller != oldWidget.controller)
+      _updateTabController();
+    if (widget.children != oldWidget.children && _warpUnderwayCount == 0)
+      _updateChildren();
+  }
+
+  @override
+  void dispose() {
+    if (_controllerIsValid)
+      _controller!.animation!.removeListener(_handleTabControllerAnimationTick);
+    _controller = null;
+    // We don't own the _controller Animation, so it's not disposed here.
+    super.dispose();
+  }
+
+  void _updateChildren() {
+    _children = widget.children;
+    _childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(widget.children);
+  }
+
+  void _handleTabControllerAnimationTick() {
+    if (_warpUnderwayCount > 0 || !_controller!.indexIsChanging)
+      return; // This widget is driving the controller's animation.
+
+    if (_controller!.index != _currentIndex) {
+      _currentIndex = _controller!.index;
+      _warpToCurrentIndex();
+    }
+  }
+
+  Future<void> _warpToCurrentIndex() async {
+    if (!mounted)
+      return Future<void>.value();
+
+    if (_pageController.page == _currentIndex!.toDouble())
+      return Future<void>.value();
+
+    final int previousIndex = _controller!.previousIndex;
+    if ((_currentIndex! - previousIndex).abs() == 1) {
+      _warpUnderwayCount += 1;
+      await _pageController.animateToPage(_currentIndex!, duration: kTabScrollDuration, curve: Curves.ease);
+      _warpUnderwayCount -= 1;
+      return Future<void>.value();
+    }
+
+    assert((_currentIndex! - previousIndex).abs() > 1);
+    final int initialPage = _currentIndex! > previousIndex
+        ? _currentIndex! - 1
+        : _currentIndex! + 1;
+    final List<Widget> originalChildren = _childrenWithKey;
+    setState(() {
+      _warpUnderwayCount += 1;
+
+      _childrenWithKey = List<Widget>.from(_childrenWithKey, growable: false);
+      final Widget temp = _childrenWithKey[initialPage];
+      _childrenWithKey[initialPage] = _childrenWithKey[previousIndex];
+      _childrenWithKey[previousIndex] = temp;
+    });
+    _pageController.jumpToPage(initialPage);
+
+    await _pageController.animateToPage(_currentIndex!, duration: kTabScrollDuration, curve: Curves.ease);
+    if (!mounted)
+      return Future<void>.value();
+    setState(() {
+      _warpUnderwayCount -= 1;
+      if (widget.children != _children) {
+        _updateChildren();
+      } else {
+        _childrenWithKey = originalChildren;
+      }
+    });
+  }
+
+  // Called when the PageView scrolls
+  bool _handleScrollNotification(ScrollNotification notification) {
+    if (_warpUnderwayCount > 0)
+      return false;
+
+    if (notification.depth != 0)
+      return false;
+
+    _warpUnderwayCount += 1;
+    if (notification is ScrollUpdateNotification && !_controller!.indexIsChanging) {
+      if ((_pageController.page! - _controller!.index).abs() > 1.0) {
+        _controller!.index = _pageController.page!.floor();
+        _currentIndex =_controller!.index;
+      }
+      _controller!.offset = (_pageController.page! - _controller!.index).clamp(-1.0, 1.0);
+    } else if (notification is ScrollEndNotification) {
+      _controller!.index = _pageController.page!.round();
+      _currentIndex = _controller!.index;
+      if (!_controller!.indexIsChanging)
+        _controller!.offset = (_pageController.page! - _controller!.index).clamp(-1.0, 1.0);
+    }
+    _warpUnderwayCount -= 1;
+
+    return false;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(() {
+      if (_controller!.length != widget.children.length) {
+        throw FlutterError(
+          "Controller's length property (${_controller!.length}) does not match the "
+          "number of tabs (${widget.children.length}) present in TabBar's tabs property."
+        );
+      }
+      return true;
+    }());
+    return NotificationListener<ScrollNotification>(
+      onNotification: _handleScrollNotification,
+      child: PageView(
+        dragStartBehavior: widget.dragStartBehavior,
+        controller: _pageController,
+        physics: widget.physics == null
+          ? const PageScrollPhysics().applyTo(const ClampingScrollPhysics())
+          : const PageScrollPhysics().applyTo(widget.physics),
+        children: _childrenWithKey,
+      ),
+    );
+  }
+}
+
+/// Displays a single circle with the specified border and background colors.
+///
+/// Used by [TabPageSelector] to indicate the selected page.
+class TabPageSelectorIndicator extends StatelessWidget {
+  /// Creates an indicator used by [TabPageSelector].
+  ///
+  /// The [backgroundColor], [borderColor], and [size] parameters must not be null.
+  const TabPageSelectorIndicator({
+    Key? key,
+    required this.backgroundColor,
+    required this.borderColor,
+    required this.size,
+  }) : assert(backgroundColor != null),
+       assert(borderColor != null),
+       assert(size != null),
+       super(key: key);
+
+  /// The indicator circle's background color.
+  final Color backgroundColor;
+
+  /// The indicator circle's border color.
+  final Color borderColor;
+
+  /// The indicator circle's diameter.
+  final double size;
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      width: size,
+      height: size,
+      margin: const EdgeInsets.all(4.0),
+      decoration: BoxDecoration(
+        color: backgroundColor,
+        border: Border.all(color: borderColor),
+        shape: BoxShape.circle,
+      ),
+    );
+  }
+}
+
+/// Displays a row of small circular indicators, one per tab.
+///
+/// The selected tab's indicator is highlighted. Often used in conjunction with
+/// a [TabBarView].
+///
+/// If a [TabController] is not provided, then there must be a
+/// [DefaultTabController] ancestor.
+class TabPageSelector extends StatelessWidget {
+  /// Creates a compact widget that indicates which tab has been selected.
+  const TabPageSelector({
+    Key? key,
+    this.controller,
+    this.indicatorSize = 12.0,
+    this.color,
+    this.selectedColor,
+  }) : assert(indicatorSize != null && indicatorSize > 0.0),
+       super(key: key);
+
+  /// This widget's selection and animation state.
+  ///
+  /// If [TabController] is not provided, then the value of
+  /// [DefaultTabController.of] will be used.
+  final TabController? controller;
+
+  /// The indicator circle's diameter (the default value is 12.0).
+  final double indicatorSize;
+
+  /// The indicator circle's fill color for unselected pages.
+  ///
+  /// If this parameter is null, then the indicator is filled with [Colors.transparent].
+  final Color? color;
+
+  /// The indicator circle's fill color for selected pages and border color
+  /// for all indicator circles.
+  ///
+  /// If this parameter is null, then the indicator is filled with the theme's
+  /// accent color, [ThemeData.accentColor].
+  final Color? selectedColor;
+
+  Widget _buildTabIndicator(
+    int tabIndex,
+    TabController tabController,
+    ColorTween selectedColorTween,
+    ColorTween previousColorTween,
+  ) {
+    final Color background;
+    if (tabController.indexIsChanging) {
+      // The selection's animation is animating from previousValue to value.
+      final double t = 1.0 - _indexChangeProgress(tabController);
+      if (tabController.index == tabIndex)
+        background = selectedColorTween.lerp(t)!;
+      else if (tabController.previousIndex == tabIndex)
+        background = previousColorTween.lerp(t)!;
+      else
+        background = selectedColorTween.begin!;
+    } else {
+      // The selection's offset reflects how far the TabBarView has / been dragged
+      // to the previous page (-1.0 to 0.0) or the next page (0.0 to 1.0).
+      final double offset = tabController.offset;
+      if (tabController.index == tabIndex) {
+        background = selectedColorTween.lerp(1.0 - offset.abs())!;
+      } else if (tabController.index == tabIndex - 1 && offset > 0.0) {
+        background = selectedColorTween.lerp(offset)!;
+      } else if (tabController.index == tabIndex + 1 && offset < 0.0) {
+        background = selectedColorTween.lerp(-offset)!;
+      } else {
+        background = selectedColorTween.begin!;
+      }
+    }
+    return TabPageSelectorIndicator(
+      backgroundColor: background,
+      borderColor: selectedColorTween.end!,
+      size: indicatorSize,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final Color fixColor = color ?? Colors.transparent;
+    final Color fixSelectedColor = selectedColor ?? Theme.of(context).accentColor;
+    final ColorTween selectedColorTween = ColorTween(begin: fixColor, end: fixSelectedColor);
+    final ColorTween previousColorTween = ColorTween(begin: fixSelectedColor, end: fixColor);
+    final TabController? tabController = controller ?? DefaultTabController.of(context);
+    assert(() {
+      if (tabController == null) {
+        throw FlutterError(
+          'No TabController for $runtimeType.\n'
+          'When creating a $runtimeType, you must either provide an explicit TabController '
+          'using the "controller" property, or you must ensure that there is a '
+          'DefaultTabController above the $runtimeType.\n'
+          'In this case, there was neither an explicit controller nor a default controller.'
+        );
+      }
+      return true;
+    }());
+    final Animation<double> animation = CurvedAnimation(
+      parent: tabController!.animation!,
+      curve: Curves.fastOutSlowIn,
+    );
+    return AnimatedBuilder(
+      animation: animation,
+      builder: (BuildContext context, Widget? child) {
+        return Semantics(
+          label: 'Page ${tabController.index + 1} of ${tabController.length}',
+          child: Row(
+            mainAxisSize: MainAxisSize.min,
+            children: List<Widget>.generate(tabController.length, (int tabIndex) {
+              return _buildTabIndicator(tabIndex, tabController, selectedColorTween, previousColorTween);
+            }).toList(),
+          ),
+        );
+      },
+    );
+  }
+}
diff --git a/lib/src/material/text_button.dart b/lib/src/material/text_button.dart
new file mode 100644
index 0000000..9d36ddb
--- /dev/null
+++ b/lib/src/material/text_button.dart
@@ -0,0 +1,394 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'button_style.dart';
+import 'button_style_button.dart';
+import 'color_scheme.dart';
+import 'colors.dart';
+import 'constants.dart';
+import 'material_state.dart';
+import 'text_button_theme.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+
+/// A Material Design "Text Button".
+///
+/// Use text buttons on toolbars, in dialogs, or inline with other
+/// content but offset from that content with padding so that the
+/// button's presence is obvious. Text buttons do not have visible
+/// borders and must therefore rely on their position relative to
+/// other content for context. In dialogs and cards, they should be
+/// grouped together in one of the bottom corners. Avoid using text
+/// buttons where they would blend in with other content, for example
+/// in the middle of lists.
+///
+/// A text button is a label [child] displayed on a (zero elevation)
+/// [Material] widget. The label's [Text] and [Icon] widgets are
+/// displayed in the [style]'s [ButtonStyle.foregroundColor]. The
+/// button reacts to touches by filling with the [style]'s
+/// [ButtonStyle.backgroundColor].
+///
+/// The text button's default style is defined by [defaultStyleOf].
+/// The style of this text button can be overridden with its [style]
+/// parameter. The style of all text buttons in a subtree can be
+/// overridden with the [TextButtonTheme] and the style of all of the
+/// text buttons in an app can be overridden with the [Theme]'s
+/// [ThemeData.textButtonTheme] property.
+///
+/// The static [styleFrom] method is a convenient way to create a
+/// text button [ButtonStyle] from simple values.
+///
+/// If the [onPressed] and [onLongPress] callbacks are null, then this
+/// button will be disabled, it will not react to touch.
+///
+/// See also:
+///
+///  * [OutlinedButton], a [TextButton] with a border outline.
+///  * [ElevatedButton], a filled button whose material elevates when pressed.
+///  * <https://material.io/design/components/buttons.html>
+class TextButton extends ButtonStyleButton {
+  /// Create a TextButton.
+  ///
+  /// The [autofocus] and [clipBehavior] arguments must not be null.
+  const TextButton({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ButtonStyle? style,
+    FocusNode? focusNode,
+    bool autofocus = false,
+    Clip clipBehavior = Clip.none,
+    required Widget child,
+  }) : super(
+    key: key,
+    onPressed: onPressed,
+    onLongPress: onLongPress,
+    style: style,
+    focusNode: focusNode,
+    autofocus: autofocus,
+    clipBehavior: clipBehavior,
+    child: child,
+  );
+
+  /// Create a text button from a pair of widgets that serve as the button's
+  /// [icon] and [label].
+  ///
+  /// The icon and label are arranged in a row and padded by 8 logical pixels
+  /// at the ends, with an 8 pixel gap in between.
+  ///
+  /// The [icon] and [label] arguments must not be null.
+  factory TextButton.icon({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ButtonStyle? style,
+    FocusNode? focusNode,
+    bool? autofocus,
+    Clip? clipBehavior,
+    required Widget icon,
+    required Widget label,
+  }) = _TextButtonWithIcon;
+
+  /// A static convenience method that constructs a text button
+  /// [ButtonStyle] given simple values.
+  ///
+  /// The [primary], and [onSurface] colors are used to to create a
+  /// [MaterialStateProperty] [ButtonStyle.foregroundColor] value in the same
+  /// way that [defaultStyleOf] uses the [ColorScheme] colors with the same
+  /// names. Specify a value for [primary] to specify the color of the button's
+  /// text and icons as well as the overlay colors used to indicate the hover,
+  /// focus, and pressed states. Use [onSurface] to specify the button's
+  /// disabled text and icon color.
+  ///
+  /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
+  /// parameters are used to construct [ButtonStyle.mouseCursor].
+  ///
+  /// All of the other parameters are either used directly or used to
+  /// create a [MaterialStateProperty] with a single value for all
+  /// states.
+  ///
+  /// All parameters default to null. By default this method returns
+  /// a [ButtonStyle] that doesn't override anything.
+  ///
+  /// For example, to override the default text and icon colors for a
+  /// [TextButton], as well as its overlay color, with all of the
+  /// standard opacity adjustments for the pressed, focused, and
+  /// hovered states, one could write:
+  ///
+  /// ```dart
+  /// TextButton(
+  ///   style: TextButton.styleFrom(primary: Colors.green),
+  /// )
+  /// ```
+  static ButtonStyle styleFrom({
+    Color? primary,
+    Color? onSurface,
+    Color? backgroundColor,
+    Color? shadowColor,
+    double? elevation,
+    TextStyle? textStyle,
+    EdgeInsetsGeometry? padding,
+    Size? minimumSize,
+    BorderSide? side,
+    OutlinedBorder? shape,
+    MouseCursor? enabledMouseCursor,
+    MouseCursor? disabledMouseCursor,
+    VisualDensity? visualDensity,
+    MaterialTapTargetSize? tapTargetSize,
+    Duration? animationDuration,
+    bool? enableFeedback,
+  }) {
+    final MaterialStateProperty<Color?>? foregroundColor = (onSurface == null && primary == null)
+      ? null
+      : _TextButtonDefaultForeground(primary, onSurface);
+    final MaterialStateProperty<Color?>? overlayColor = (primary == null)
+      ? null
+      : _TextButtonDefaultOverlay(primary);
+    final MaterialStateProperty<MouseCursor>? mouseCursor = (enabledMouseCursor == null && disabledMouseCursor == null)
+      ? null
+      : _TextButtonDefaultMouseCursor(enabledMouseCursor!, disabledMouseCursor!);
+
+    return ButtonStyle(
+      textStyle: ButtonStyleButton.allOrNull<TextStyle>(textStyle),
+      backgroundColor: ButtonStyleButton.allOrNull<Color>(backgroundColor),
+      foregroundColor: foregroundColor,
+      overlayColor: overlayColor,
+      shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
+      elevation: ButtonStyleButton.allOrNull<double>(elevation),
+      padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
+      minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
+      side: ButtonStyleButton.allOrNull<BorderSide>(side),
+      shape: ButtonStyleButton.allOrNull<OutlinedBorder>(shape),
+      mouseCursor: mouseCursor,
+      visualDensity: visualDensity,
+      tapTargetSize: tapTargetSize,
+      animationDuration: animationDuration,
+      enableFeedback: enableFeedback,
+    );
+  }
+
+  /// Defines the button's default appearance.
+  ///
+  /// The button [child]'s [Text] and [Icon] widgets are rendered with
+  /// the [ButtonStyle]'s foreground color. The button's [InkWell] adds
+  /// the style's overlay color when the button is focused, hovered
+  /// or pressed. The button's background color becomes its [Material]
+  /// color and is transparent by default.
+  ///
+  /// All of the ButtonStyle's defaults appear below.
+  ///
+  /// In this list "Theme.foo" is shorthand for
+  /// `Theme.of(context).foo`. Color scheme values like
+  /// "onSurface(0.38)" are shorthand for
+  /// `onSurface.withOpacity(0.38)`. [MaterialStateProperty] valued
+  /// properties that are not followed by by a sublist have the same
+  /// value for all states, otherwise the values are as specified for
+  /// each state and "others" means all other states.
+  ///
+  /// The `textScaleFactor` is the value of
+  /// `MediaQuery.of(context).textScaleFactor` and the names of the
+  /// EdgeInsets constructors and `EdgeInsetsGeometry.lerp` have been
+  /// abbreviated for readability.
+  ///
+  /// The color of the [ButtonStyle.textStyle] is not used, the
+  /// [ButtonStyle.foregroundColor] color is used instead.
+  ///
+  /// * `textStyle` - Theme.textTheme.button
+  /// * `backgroundColor` - transparent
+  /// * `foregroundColor`
+  ///   * disabled - Theme.colorScheme.onSurface(0.38)
+  ///   * others - Theme.colorScheme.primary
+  /// * `overlayColor`
+  ///   * hovered - Theme.colorScheme.primary(0.04)
+  ///   * focused or pressed - Theme.colorScheme.primary(0.12)
+  /// * `shadowColor` - Theme.shadowColor
+  /// * `elevation` - 0
+  /// * `padding`
+  ///   * `textScaleFactor <= 1` - all(8)
+  ///   * `1 < textScaleFactor <= 2` - lerp(all(8), horizontal(8))
+  ///   * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4))
+  ///   * `3 < textScaleFactor` - horizontal(4)
+  /// * `minimumSize` - Size(64, 36)
+  /// * `side` - null
+  /// * `shape` - RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))
+  /// * `mouseCursor`
+  ///   * disabled - SystemMouseCursors.forbidden
+  ///   * others - SystemMouseCursors.click
+  /// * `visualDensity` - theme.visualDensity
+  /// * `tapTargetSize` - theme.materialTapTargetSize
+  /// * `animationDuration` - kThemeChangeDuration
+  /// * `enableFeedback` - true
+  ///
+  /// The default padding values for the [TextButton.icon] factory are slightly different:
+  ///
+  /// * `padding`
+  ///   * `textScaleFactor <= 1` - all(8)
+  ///   * `1 < textScaleFactor <= 2 `- lerp(all(8), horizontal(4))
+  ///   * `2 < textScaleFactor` - horizontal(4)
+  ///
+  /// The default value for `side`, which defines the appearance of the button's
+  /// outline, is null. That means that the outline is defined by the button
+  /// shape's [OutlinedBorder.side]. Typically the default value of an
+  /// [OutlinedBorder]'s side is [BorderSide.none], so an outline is not drawn.
+  @override
+  ButtonStyle defaultStyleOf(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final ColorScheme colorScheme = theme.colorScheme;
+
+    final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding(
+      const EdgeInsets.all(8),
+      const EdgeInsets.symmetric(horizontal: 8),
+      const EdgeInsets.symmetric(horizontal: 4),
+      MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
+    );
+
+    return styleFrom(
+      primary: colorScheme.primary,
+      onSurface: colorScheme.onSurface,
+      backgroundColor: Colors.transparent,
+      shadowColor: theme.shadowColor,
+      elevation: 0,
+      textStyle: theme.textTheme.button,
+      padding: scaledPadding,
+      minimumSize: const Size(64, 36),
+      side: null,
+      shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))),
+      enabledMouseCursor: SystemMouseCursors.click,
+      disabledMouseCursor: SystemMouseCursors.forbidden,
+      visualDensity: theme.visualDensity,
+      tapTargetSize: theme.materialTapTargetSize,
+      animationDuration: kThemeChangeDuration,
+      enableFeedback: true,
+    );
+  }
+
+  /// Returns the [TextButtonThemeData.style] of the closest
+  /// [TextButtonTheme] ancestor.
+  @override
+  ButtonStyle? themeStyleOf(BuildContext context) {
+    return TextButtonTheme.of(context).style;
+  }
+}
+
+@immutable
+class _TextButtonDefaultForeground extends MaterialStateProperty<Color?> {
+  _TextButtonDefaultForeground(this.primary, this.onSurface);
+
+  final Color? primary;
+  final Color? onSurface;
+
+  @override
+  Color? resolve(Set<MaterialState> states) {
+    if (states.contains(MaterialState.disabled))
+      return onSurface?.withOpacity(0.38);
+    return primary;
+  }
+
+  @override
+  String toString() {
+    return '{disabled: ${onSurface?.withOpacity(0.38)}, otherwise: $primary}';
+  }
+}
+
+@immutable
+class _TextButtonDefaultOverlay extends MaterialStateProperty<Color?> {
+  _TextButtonDefaultOverlay(this.primary);
+
+  final Color primary;
+
+  @override
+  Color? resolve(Set<MaterialState> states) {
+    if (states.contains(MaterialState.hovered))
+      return primary.withOpacity(0.04);
+    if (states.contains(MaterialState.focused) || states.contains(MaterialState.pressed))
+      return primary.withOpacity(0.12);
+    return null;
+  }
+
+  @override
+  String toString() {
+    return '{hovered: ${primary.withOpacity(0.04)}, focused,pressed: ${primary.withOpacity(0.12)}, otherwise: null}';
+  }
+}
+
+@immutable
+class _TextButtonDefaultMouseCursor extends MaterialStateProperty<MouseCursor> with Diagnosticable {
+  _TextButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor);
+
+  final MouseCursor enabledCursor;
+  final MouseCursor disabledCursor;
+
+  @override
+  MouseCursor resolve(Set<MaterialState> states) {
+    if (states.contains(MaterialState.disabled))
+      return disabledCursor;
+    return enabledCursor;
+  }
+}
+
+class _TextButtonWithIcon extends TextButton {
+  _TextButtonWithIcon({
+    Key? key,
+    required VoidCallback? onPressed,
+    VoidCallback? onLongPress,
+    ButtonStyle? style,
+    FocusNode? focusNode,
+    bool? autofocus,
+    Clip? clipBehavior,
+    required Widget icon,
+    required Widget label,
+  }) : assert(icon != null),
+       assert(label != null),
+       super(
+         key: key,
+         onPressed: onPressed,
+         onLongPress: onLongPress,
+         style: style,
+         focusNode: focusNode,
+         autofocus: autofocus ?? false,
+         clipBehavior: clipBehavior ?? Clip.none,
+         child: _TextButtonWithIconChild(icon: icon, label: label),
+      );
+
+  @override
+  ButtonStyle defaultStyleOf(BuildContext context) {
+    final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding(
+      const EdgeInsets.all(8),
+      const EdgeInsets.symmetric(horizontal: 4),
+      const EdgeInsets.symmetric(horizontal: 4),
+      MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
+    );
+    return super.defaultStyleOf(context).copyWith(
+      padding: MaterialStateProperty.all<EdgeInsetsGeometry>(scaledPadding)
+    );
+  }
+}
+
+class _TextButtonWithIconChild extends StatelessWidget {
+  const _TextButtonWithIconChild({
+    Key? key,
+    required this.label,
+    required this.icon,
+  }) : super(key: key);
+
+  final Widget label;
+  final Widget icon;
+
+  @override
+  Widget build(BuildContext context) {
+    final double scale = MediaQuery.maybeOf(context)?.textScaleFactor ?? 1;
+    final double gap = scale <= 1 ? 8 : lerpDouble(8, 4, math.min(scale - 1, 1))!;
+    return Row(
+      mainAxisSize: MainAxisSize.min,
+      children: <Widget>[icon, SizedBox(width: gap), label],
+    );
+  }
+}
diff --git a/lib/src/material/text_button_theme.dart b/lib/src/material/text_button_theme.dart
new file mode 100644
index 0000000..7d90a8a
--- /dev/null
+++ b/lib/src/material/text_button_theme.dart
@@ -0,0 +1,124 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'button_style.dart';
+import 'theme.dart';
+
+/// A [ButtonStyle] that overrides the default appearance of
+/// [TextButton]s when it's used with [TextButtonTheme] or with the
+/// overall [Theme]'s [ThemeData.textButtonTheme].
+///
+/// The [style]'s properties override [TextButton]'s default style,
+/// i.e.  the [ButtonStyle] returned by [TextButton.defaultStyleOf]. Only
+/// the style's non-null property values or resolved non-null
+/// [MaterialStateProperty] values are used.
+///
+/// See also:
+///
+///  * [TextButtonTheme], the theme which is configured with this class.
+///  * [TextButton.defaultStyleOf], which returns the default [ButtonStyle]
+///    for text buttons.
+///  * [TextButton.styleFrom], which converts simple values into a
+///    [ButtonStyle] that's consistent with [TextButton]'s defaults.
+///  * [MaterialStateProperty.resolve], "resolve" a material state property
+///    to a simple value based on a set of [MaterialState]s.
+///  * [ThemeData.textButtonTheme], which can be used to override the default
+///    [ButtonStyle] for [TextButton]s below the overall [Theme].
+@immutable
+class TextButtonThemeData with Diagnosticable {
+  /// Creates a [TextButtonThemeData].
+  ///
+  /// The [style] may be null.
+  const TextButtonThemeData({ this.style });
+
+  /// Overrides for [TextButton]'s default style.
+  ///
+  /// Non-null properties or non-null resolved [MaterialStateProperty]
+  /// values override the [ButtonStyle] returned by
+  /// [TextButton.defaultStyleOf].
+  ///
+  /// If [style] is null, then this theme doesn't override anything.
+  final ButtonStyle? style;
+
+  /// Linearly interpolate between two text button themes.
+  static TextButtonThemeData? lerp(TextButtonThemeData? a, TextButtonThemeData? b, double t) {
+    assert (t != null);
+    if (a == null && b == null)
+      return null;
+    return TextButtonThemeData(
+      style: ButtonStyle.lerp(a?.style, b?.style, t),
+    );
+  }
+
+  @override
+  int get hashCode {
+    return style.hashCode;
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is TextButtonThemeData && other.style == style;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ButtonStyle>('style', style, defaultValue: null));
+  }
+}
+
+/// Overrides the default [ButtonStyle] of its [TextButton] descendants.
+///
+/// See also:
+///
+///  * [TextButtonThemeData], which is used to configure this theme.
+///  * [TextButton.defaultStyleOf], which returns the default [ButtonStyle]
+///    for text buttons.
+///  * [TextButton.styleFrom], which converts simple values into a
+///    [ButtonStyle] that's consistent with [TextButton]'s defaults.
+///  * [ThemeData.textButtonTheme], which can be used to override the default
+///    [ButtonStyle] for [TextButton]s below the overall [Theme].
+class TextButtonTheme extends InheritedTheme {
+  /// Create a [TextButtonTheme].
+  ///
+  /// The [data] parameter must not be null.
+  const TextButtonTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null), super(key: key, child: child);
+
+  /// The configuration of this theme.
+  final TextButtonThemeData data;
+
+  /// The closest instance of this class that encloses the given context.
+  ///
+  /// If there is no enclosing [TextButtonTheme] widget, then
+  /// [ThemeData.textButtonTheme] is used.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// TextButtonTheme theme = TextButtonTheme.of(context);
+  /// ```
+  static TextButtonThemeData of(BuildContext context) {
+    final TextButtonTheme? buttonTheme = context.dependOnInheritedWidgetOfExactType<TextButtonTheme>();
+    return buttonTheme?.data ?? Theme.of(context).textButtonTheme;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return TextButtonTheme(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(TextButtonTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/text_field.dart b/lib/src/material/text_field.dart
new file mode 100644
index 0000000..9244123
--- /dev/null
+++ b/lib/src/material/text_field.dart
@@ -0,0 +1,1301 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui show BoxHeightStyle, BoxWidthStyle;
+
+import 'package:characters/characters.dart';
+import 'package:flute/cupertino.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+
+import 'debug.dart';
+import 'feedback.dart';
+import 'input_decorator.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'material_state.dart';
+import 'selectable_text.dart' show iOSHorizontalOffset;
+import 'text_selection.dart';
+import 'text_selection_theme.dart';
+import 'theme.dart';
+
+export 'package:flute/services.dart' show TextInputType, TextInputAction, TextCapitalization, SmartQuotesType, SmartDashesType;
+
+/// Signature for the [TextField.buildCounter] callback.
+typedef InputCounterWidgetBuilder = Widget? Function(
+  /// The build context for the TextField.
+  BuildContext context, {
+  /// The length of the string currently in the input.
+  required int currentLength,
+  /// The maximum string length that can be entered into the TextField.
+  required int? maxLength,
+  /// Whether or not the TextField is currently focused.  Mainly provided for
+  /// the [liveRegion] parameter in the [Semantics] widget for accessibility.
+  required bool isFocused,
+});
+
+class _TextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDetectorBuilder {
+  _TextFieldSelectionGestureDetectorBuilder({
+    required _TextFieldState state,
+  }) : _state = state,
+       super(delegate: state);
+
+  final _TextFieldState _state;
+
+  @override
+  void onForcePressStart(ForcePressDetails details) {
+    super.onForcePressStart(details);
+    if (delegate.selectionEnabled && shouldShowSelectionToolbar) {
+      editableText.showToolbar();
+    }
+  }
+
+  @override
+  void onForcePressEnd(ForcePressDetails details) {
+    // Not required.
+  }
+
+  @override
+  void onSingleLongTapMoveUpdate(LongPressMoveUpdateDetails details) {
+    if (delegate.selectionEnabled) {
+      switch (Theme.of(_state.context).platform) {
+        case TargetPlatform.iOS:
+        case TargetPlatform.macOS:
+          renderEditable.selectPositionAt(
+            from: details.globalPosition,
+            cause: SelectionChangedCause.longPress,
+          );
+          break;
+        case TargetPlatform.android:
+        case TargetPlatform.fuchsia:
+        case TargetPlatform.linux:
+        case TargetPlatform.windows:
+          renderEditable.selectWordsInRange(
+            from: details.globalPosition - details.offsetFromOrigin,
+            to: details.globalPosition,
+            cause: SelectionChangedCause.longPress,
+          );
+          break;
+      }
+    }
+  }
+
+  @override
+  void onSingleTapUp(TapUpDetails details) {
+    editableText.hideToolbar();
+    if (delegate.selectionEnabled) {
+      switch (Theme.of(_state.context).platform) {
+        case TargetPlatform.iOS:
+        case TargetPlatform.macOS:
+          switch (details.kind) {
+            case PointerDeviceKind.mouse:
+            case PointerDeviceKind.stylus:
+            case PointerDeviceKind.invertedStylus:
+              // Precise devices should place the cursor at a precise position.
+              renderEditable.selectPosition(cause: SelectionChangedCause.tap);
+              break;
+            case PointerDeviceKind.touch:
+            case PointerDeviceKind.unknown:
+              // On macOS/iOS/iPadOS a touch tap places the cursor at the edge
+              // of the word.
+              renderEditable.selectWordEdge(cause: SelectionChangedCause.tap);
+              break;
+          }
+          break;
+        case TargetPlatform.android:
+        case TargetPlatform.fuchsia:
+        case TargetPlatform.linux:
+        case TargetPlatform.windows:
+          renderEditable.selectPosition(cause: SelectionChangedCause.tap);
+          break;
+      }
+    }
+    _state._requestKeyboard();
+    if (_state.widget.onTap != null)
+      _state.widget.onTap!();
+  }
+
+  @override
+  void onSingleLongTapStart(LongPressStartDetails details) {
+    if (delegate.selectionEnabled) {
+      switch (Theme.of(_state.context).platform) {
+        case TargetPlatform.iOS:
+        case TargetPlatform.macOS:
+          renderEditable.selectPositionAt(
+            from: details.globalPosition,
+            cause: SelectionChangedCause.longPress,
+          );
+          break;
+        case TargetPlatform.android:
+        case TargetPlatform.fuchsia:
+        case TargetPlatform.linux:
+        case TargetPlatform.windows:
+          renderEditable.selectWord(cause: SelectionChangedCause.longPress);
+          Feedback.forLongPress(_state.context);
+          break;
+      }
+    }
+  }
+}
+
+/// A material design text field.
+///
+/// A text field lets the user enter text, either with hardware keyboard or with
+/// an onscreen keyboard.
+///
+/// The text field calls the [onChanged] callback whenever the user changes the
+/// text in the field. If the user indicates that they are done typing in the
+/// field (e.g., by pressing a button on the soft keyboard), the text field
+/// calls the [onSubmitted] callback.
+///
+/// To control the text that is displayed in the text field, use the
+/// [controller]. For example, to set the initial value of the text field, use
+/// a [controller] that already contains some text. The [controller] can also
+/// control the selection and composing region (and to observe changes to the
+/// text, selection, and composing region).
+///
+/// By default, a text field has a [decoration] that draws a divider below the
+/// text field. You can use the [decoration] property to control the decoration,
+/// for example by adding a label or an icon. If you set the [decoration]
+/// property to null, the decoration will be removed entirely, including the
+/// extra padding introduced by the decoration to save space for the labels.
+///
+/// If [decoration] is non-null (which is the default), the text field requires
+/// one of its ancestors to be a [Material] widget.
+///
+/// To integrate the [TextField] into a [Form] with other [FormField] widgets,
+/// consider using [TextFormField].
+///
+/// Remember to call [TextEditingController.dispose] of the [TextEditingController]
+/// when it is no longer needed. This will ensure we discard any resources used
+/// by the object.
+///
+/// {@tool snippet}
+/// This example shows how to create a [TextField] that will obscure input. The
+/// [InputDecoration] surrounds the field in a border using [OutlineInputBorder]
+/// and adds a label.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/text_field.png)
+///
+/// ```dart
+/// TextField(
+///   obscureText: true,
+///   decoration: InputDecoration(
+///     border: OutlineInputBorder(),
+///     labelText: 'Password',
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ## Reading values
+///
+/// A common way to read a value from a TextField is to use the [onSubmitted]
+/// callback. This callback is applied to the text field's current value when
+/// the user finishes editing.
+///
+/// {@tool dartpad --template=stateful_widget_material}
+///
+/// This sample shows how to get a value from a TextField via the [onSubmitted]
+/// callback.
+///
+/// ```dart
+/// late TextEditingController _controller;
+///
+/// void initState() {
+///   super.initState();
+///   _controller = TextEditingController();
+/// }
+///
+/// void dispose() {
+///   _controller.dispose();
+///   super.dispose();
+/// }
+///
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: Center(
+///       child: TextField(
+///         controller: _controller,
+///         onSubmitted: (String value) async {
+///           await showDialog<void>(
+///             context: context,
+///             builder: (BuildContext context) {
+///               return AlertDialog(
+///                 title: const Text('Thanks!'),
+///                 content: Text ('You typed "$value", which has length ${value.characters.length}.'),
+///                 actions: <Widget>[
+///                   TextButton(
+///                     onPressed: () { Navigator.pop(context); },
+///                     child: const Text('OK'),
+///                   ),
+///                 ],
+///               );
+///             },
+///           );
+///         },
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// For most applications the [onSubmitted] callback will be sufficient for
+/// reacting to user input.
+///
+/// The [onEditingComplete] callback also runs when the user finishes editing.
+/// It's different from [onSubmitted] because it has a default value which
+/// updates the text controller and yields the keyboard focus. Applications that
+/// require different behavior can override the default [onEditingComplete]
+/// callback.
+///
+/// Keep in mind you can also always read the current string from a TextField's
+/// [TextEditingController] using [TextEditingController.text].
+///
+  /// ## Handling emojis and other complex characters
+/// {@macro flutter.widgets.EditableText.onChanged}
+///
+/// In the live Dartpad example above, try typing the emoji 👨‍👩‍👦
+/// into the field and submitting. Because the example code measures the length
+/// with `value.characters.length`, the emoji is correctly counted as a single
+/// character.
+///
+/// See also:
+///
+///  * [TextFormField], which integrates with the [Form] widget.
+///  * [InputDecorator], which shows the labels and other visual elements that
+///    surround the actual text editing widget.
+///  * [EditableText], which is the raw text editing control at the heart of a
+///    [TextField]. The [EditableText] widget is rarely used directly unless
+///    you are implementing an entirely different design language, such as
+///    Cupertino.
+///  * <https://material.io/design/components/text-fields.html>
+///  * Cookbook: [Create and style a text field](https://flutter.dev/docs/cookbook/forms/text-input)
+///  * Cookbook: [Handle changes to a text field](https://flutter.dev/docs/cookbook/forms/text-field-changes)
+///  * Cookbook: [Retrieve the value of a text field](https://flutter.dev/docs/cookbook/forms/retrieve-input)
+///  * Cookbook: [Focus and text fields](https://flutter.dev/docs/cookbook/forms/focus)
+class TextField extends StatefulWidget {
+  /// Creates a Material Design text field.
+  ///
+  /// If [decoration] is non-null (which is the default), the text field requires
+  /// one of its ancestors to be a [Material] widget.
+  ///
+  /// To remove the decoration entirely (including the extra padding introduced
+  /// by the decoration to save space for the labels), set the [decoration] to
+  /// null.
+  ///
+  /// The [maxLines] property can be set to null to remove the restriction on
+  /// the number of lines. By default, it is one, meaning this is a single-line
+  /// text field. [maxLines] must not be zero.
+  ///
+  /// The [maxLength] property is set to null by default, which means the
+  /// number of characters allowed in the text field is not restricted. If
+  /// [maxLength] is set a character counter will be displayed below the
+  /// field showing how many characters have been entered. If the value is
+  /// set to a positive integer it will also display the maximum allowed
+  /// number of characters to be entered.  If the value is set to
+  /// [TextField.noMaxLength] then only the current length is displayed.
+  ///
+  /// After [maxLength] characters have been input, additional input
+  /// is ignored, unless [maxLengthEnforcement] is set to
+  /// [MaxLengthEnforcement.none].
+  /// The text field enforces the length with a [LengthLimitingTextInputFormatter],
+  /// which is evaluated after the supplied [inputFormatters], if any.
+  /// The [maxLength] value must be either null or greater than zero.
+  ///
+  /// If [maxLengthEnforced] is set to false, then more than [maxLength]
+  /// characters may be entered, and the error counter and divider will
+  /// switch to the [decoration.errorStyle] when the limit is exceeded.
+  ///
+  /// The text cursor is not shown if [showCursor] is false or if [showCursor]
+  /// is null (the default) and [readOnly] is true.
+  ///
+  /// The [selectionHeightStyle] and [selectionWidthStyle] properties allow
+  /// changing the shape of the selection highlighting. These properties default
+  /// to [ui.BoxHeightStyle.tight] and [ui.BoxWidthStyle.tight] respectively and
+  /// must not be null.
+  ///
+  /// The [textAlign], [autofocus], [obscureText], [readOnly], [autocorrect],
+  /// [maxLengthEnforced], [scrollPadding], [maxLines], [maxLength],
+  /// [selectionHeightStyle], [selectionWidthStyle], and [enableSuggestions]
+  /// arguments must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [maxLength], which discusses the precise meaning of "number of
+  ///    characters" and how it may differ from the intuitive meaning.
+  const TextField({
+    Key? key,
+    this.controller,
+    this.focusNode,
+    this.decoration = const InputDecoration(),
+    TextInputType? keyboardType,
+    this.textInputAction,
+    this.textCapitalization = TextCapitalization.none,
+    this.style,
+    this.strutStyle,
+    this.textAlign = TextAlign.start,
+    this.textAlignVertical,
+    this.textDirection,
+    this.readOnly = false,
+    ToolbarOptions? toolbarOptions,
+    this.showCursor,
+    this.autofocus = false,
+    this.obscuringCharacter = '•',
+    this.obscureText = false,
+    this.autocorrect = true,
+    SmartDashesType? smartDashesType,
+    SmartQuotesType? smartQuotesType,
+    this.enableSuggestions = true,
+    this.maxLines = 1,
+    this.minLines,
+    this.expands = false,
+    this.maxLength,
+    @Deprecated(
+      'Use maxLengthEnforcement parameter which provides more specific '
+      'behavior related to the maxLength limit. '
+      'This feature was deprecated after v1.25.0-5.0.pre.'
+    )
+    this.maxLengthEnforced = true,
+    this.maxLengthEnforcement,
+    this.onChanged,
+    this.onEditingComplete,
+    this.onSubmitted,
+    this.onAppPrivateCommand,
+    this.inputFormatters,
+    this.enabled,
+    this.cursorWidth = 2.0,
+    this.cursorHeight,
+    this.cursorRadius,
+    this.cursorColor,
+    this.selectionHeightStyle = ui.BoxHeightStyle.tight,
+    this.selectionWidthStyle = ui.BoxWidthStyle.tight,
+    this.keyboardAppearance,
+    this.scrollPadding = const EdgeInsets.all(20.0),
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.enableInteractiveSelection = true,
+    this.selectionControls,
+    this.onTap,
+    this.mouseCursor,
+    this.buildCounter,
+    this.scrollController,
+    this.scrollPhysics,
+    this.autofillHints,
+    this.restorationId,
+  }) : assert(textAlign != null),
+       assert(readOnly != null),
+       assert(autofocus != null),
+       assert(obscuringCharacter != null && obscuringCharacter.length == 1),
+       assert(obscureText != null),
+       assert(autocorrect != null),
+       smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
+       smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
+       assert(enableSuggestions != null),
+       assert(enableInteractiveSelection != null),
+       assert(maxLengthEnforced != null),
+       assert(
+         maxLengthEnforced || maxLengthEnforcement == null,
+         'maxLengthEnforced is deprecated, use only maxLengthEnforcement',
+       ),
+       assert(scrollPadding != null),
+       assert(dragStartBehavior != null),
+       assert(selectionHeightStyle != null),
+       assert(selectionWidthStyle != null),
+       assert(maxLines == null || maxLines > 0),
+       assert(minLines == null || minLines > 0),
+       assert(
+         (maxLines == null) || (minLines == null) || (maxLines >= minLines),
+         "minLines can't be greater than maxLines",
+       ),
+       assert(expands != null),
+       assert(
+         !expands || (maxLines == null && minLines == null),
+         'minLines and maxLines must be null when expands is true.',
+       ),
+       assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
+       assert(maxLength == null || maxLength == TextField.noMaxLength || maxLength > 0),
+       // Assert the following instead of setting it directly to avoid surprising the user by silently changing the value they set.
+       assert(!identical(textInputAction, TextInputAction.newline) ||
+         maxLines == 1 ||
+         !identical(keyboardType, TextInputType.text),
+         'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.'),
+       keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
+       toolbarOptions = toolbarOptions ?? (obscureText ?
+         const ToolbarOptions(
+           selectAll: true,
+           paste: true,
+         ) :
+         const ToolbarOptions(
+           copy: true,
+           cut: true,
+           selectAll: true,
+           paste: true,
+         )),
+       super(key: key);
+
+  /// Controls the text being edited.
+  ///
+  /// If null, this widget will create its own [TextEditingController].
+  final TextEditingController? controller;
+
+  /// Defines the keyboard focus for this widget.
+  ///
+  /// The [focusNode] is a long-lived object that's typically managed by a
+  /// [StatefulWidget] parent. See [FocusNode] for more information.
+  ///
+  /// To give the keyboard focus to this widget, provide a [focusNode] and then
+  /// use the current [FocusScope] to request the focus:
+  ///
+  /// ```dart
+  /// FocusScope.of(context).requestFocus(myFocusNode);
+  /// ```
+  ///
+  /// This happens automatically when the widget is tapped.
+  ///
+  /// To be notified when the widget gains or loses the focus, add a listener
+  /// to the [focusNode]:
+  ///
+  /// ```dart
+  /// focusNode.addListener(() { print(myFocusNode.hasFocus); });
+  /// ```
+  ///
+  /// If null, this widget will create its own [FocusNode].
+  ///
+  /// ## Keyboard
+  ///
+  /// Requesting the focus will typically cause the keyboard to be shown
+  /// if it's not showing already.
+  ///
+  /// On Android, the user can hide the keyboard - without changing the focus -
+  /// with the system back button. They can restore the keyboard's visibility
+  /// by tapping on a text field.  The user might hide the keyboard and
+  /// switch to a physical keyboard, or they might just need to get it
+  /// out of the way for a moment, to expose something it's
+  /// obscuring. In this case requesting the focus again will not
+  /// cause the focus to change, and will not make the keyboard visible.
+  ///
+  /// This widget builds an [EditableText] and will ensure that the keyboard is
+  /// showing when it is tapped by calling [EditableTextState.requestKeyboard()].
+  final FocusNode? focusNode;
+
+  /// The decoration to show around the text field.
+  ///
+  /// By default, draws a horizontal line under the text field but can be
+  /// configured to show an icon, label, hint text, and error text.
+  ///
+  /// Specify null to remove the decoration entirely (including the
+  /// extra padding introduced by the decoration to save space for the labels).
+  final InputDecoration? decoration;
+
+  /// {@macro flutter.widgets.editableText.keyboardType}
+  final TextInputType keyboardType;
+
+  /// The type of action button to use for the keyboard.
+  ///
+  /// Defaults to [TextInputAction.newline] if [keyboardType] is
+  /// [TextInputType.multiline] and [TextInputAction.done] otherwise.
+  final TextInputAction? textInputAction;
+
+  /// {@macro flutter.widgets.editableText.textCapitalization}
+  final TextCapitalization textCapitalization;
+
+  /// The style to use for the text being edited.
+  ///
+  /// This text style is also used as the base style for the [decoration].
+  ///
+  /// If null, defaults to the `subtitle1` text style from the current [Theme].
+  final TextStyle? style;
+
+  /// {@macro flutter.widgets.editableText.strutStyle}
+  final StrutStyle? strutStyle;
+
+  /// {@macro flutter.widgets.editableText.textAlign}
+  final TextAlign textAlign;
+
+  /// {@macro flutter.material.InputDecorator.textAlignVertical}
+  final TextAlignVertical? textAlignVertical;
+
+  /// {@macro flutter.widgets.editableText.textDirection}
+  final TextDirection? textDirection;
+
+  /// {@macro flutter.widgets.editableText.autofocus}
+  final bool autofocus;
+
+  /// {@macro flutter.widgets.editableText.obscuringCharacter}
+  final String obscuringCharacter;
+
+  /// {@macro flutter.widgets.editableText.obscureText}
+  final bool obscureText;
+
+  /// {@macro flutter.widgets.editableText.autocorrect}
+  final bool autocorrect;
+
+  /// {@macro flutter.services.TextInputConfiguration.smartDashesType}
+  final SmartDashesType smartDashesType;
+
+  /// {@macro flutter.services.TextInputConfiguration.smartQuotesType}
+  final SmartQuotesType smartQuotesType;
+
+  /// {@macro flutter.services.TextInputConfiguration.enableSuggestions}
+  final bool enableSuggestions;
+
+  /// {@macro flutter.widgets.editableText.maxLines}
+  final int? maxLines;
+
+  /// {@macro flutter.widgets.editableText.minLines}
+  final int? minLines;
+
+  /// {@macro flutter.widgets.editableText.expands}
+  final bool expands;
+
+  /// {@macro flutter.widgets.editableText.readOnly}
+  final bool readOnly;
+
+  /// Configuration of toolbar options.
+  ///
+  /// If not set, select all and paste will default to be enabled. Copy and cut
+  /// will be disabled if [obscureText] is true. If [readOnly] is true,
+  /// paste and cut will be disabled regardless.
+  final ToolbarOptions toolbarOptions;
+
+  /// {@macro flutter.widgets.editableText.showCursor}
+  final bool? showCursor;
+
+  /// If [maxLength] is set to this value, only the "current input length"
+  /// part of the character counter is shown.
+  static const int noMaxLength = -1;
+
+  /// The maximum number of characters (Unicode scalar values) to allow in the
+  /// text field.
+  ///
+  /// If set, a character counter will be displayed below the
+  /// field showing how many characters have been entered. If set to a number
+  /// greater than 0, it will also display the maximum number allowed. If set
+  /// to [TextField.noMaxLength] then only the current character count is displayed.
+  ///
+  /// After [maxLength] characters have been input, additional input
+  /// is ignored, unless [maxLengthEnforcement] is set to
+  /// [MaxLengthEnforcement.none].
+  ///
+  /// The text field enforces the length with a [LengthLimitingTextInputFormatter],
+  /// which is evaluated after the supplied [inputFormatters], if any.
+  ///
+  /// This value must be either null, [TextField.noMaxLength], or greater than 0.
+  /// If null (the default) then there is no limit to the number of characters
+  /// that can be entered. If set to [TextField.noMaxLength], then no limit will
+  /// be enforced, but the number of characters entered will still be displayed.
+  ///
+  /// Whitespace characters (e.g. newline, space, tab) are included in the
+  /// character count.
+  ///
+  /// If [maxLengthEnforced] is set to false or [maxLengthEnforcement] is
+  /// [MaxLengthEnforcement.none], then more than [maxLength]
+  /// characters may be entered, but the error counter and divider will switch
+  /// to the [decoration]'s [InputDecoration.errorStyle] when the limit is
+  /// exceeded.
+  ///
+  /// {@macro flutter.services.lengthLimitingTextInputFormatter.maxLength}
+  final int? maxLength;
+
+  /// If [maxLength] is set, [maxLengthEnforced] indicates whether or not to
+  /// enforce the limit, or merely provide a character counter and warning when
+  /// [maxLength] is exceeded.
+  ///
+  /// If true, prevents the field from allowing more than [maxLength]
+  /// characters.
+  @Deprecated(
+    'Use maxLengthEnforcement parameter which provides more specific '
+    'behavior related to the maxLength limit. '
+    'This feature was deprecated after v1.25.0-5.0.pre.'
+  )
+  final bool maxLengthEnforced;
+
+  /// Determines how the [maxLength] limit should be enforced.
+  ///
+  /// {@macro flutter.services.textFormatter.effectiveMaxLengthEnforcement}
+  ///
+  /// {@macro flutter.services.textFormatter.maxLengthEnforcement}
+  final MaxLengthEnforcement? maxLengthEnforcement;
+
+  /// {@macro flutter.widgets.editableText.onChanged}
+  ///
+  /// See also:
+  ///
+  ///  * [inputFormatters], which are called before [onChanged]
+  ///    runs and can validate and change ("format") the input value.
+  ///  * [onEditingComplete], [onSubmitted]:
+  ///    which are more specialized input change notifications.
+  final ValueChanged<String>? onChanged;
+
+  /// {@macro flutter.widgets.editableText.onEditingComplete}
+  final VoidCallback? onEditingComplete;
+
+  /// {@macro flutter.widgets.editableText.onSubmitted}
+  ///
+  /// See also:
+  ///
+  ///  * [TextInputAction.next] and [TextInputAction.previous], which
+  ///    automatically shift the focus to the next/previous focusable item when
+  ///    the user is done editing.
+  final ValueChanged<String>? onSubmitted;
+
+  /// {@macro flutter.widgets.editableText.onAppPrivateCommand}
+  final AppPrivateCommandCallback? onAppPrivateCommand;
+
+  /// {@macro flutter.widgets.editableText.inputFormatters}
+  final List<TextInputFormatter>? inputFormatters;
+
+  /// If false the text field is "disabled": it ignores taps and its
+  /// [decoration] is rendered in grey.
+  ///
+  /// If non-null this property overrides the [decoration]'s
+  /// [InputDecoration.enabled] property.
+  final bool? enabled;
+
+  /// {@macro flutter.widgets.editableText.cursorWidth}
+  final double cursorWidth;
+
+  /// {@macro flutter.widgets.editableText.cursorHeight}
+  final double? cursorHeight;
+
+  /// {@macro flutter.widgets.editableText.cursorRadius}
+  final Radius? cursorRadius;
+
+  /// The color of the cursor.
+  ///
+  /// The cursor indicates the current location of text insertion point in
+  /// the field.
+  ///
+  /// If this is null it will default to the ambient
+  /// [TextSelectionThemeData.cursorColor]. If that is null, and the
+  /// [ThemeData.platform] is [TargetPlatform.iOS] or [TargetPlatform.macOS]
+  /// it will use [CupertinoThemeData.primaryColor]. Otherwise it will use
+  /// the value of [ColorScheme.primary] of [ThemeData.colorScheme].
+  final Color? cursorColor;
+
+  /// Controls how tall the selection highlight boxes are computed to be.
+  ///
+  /// See [ui.BoxHeightStyle] for details on available styles.
+  final ui.BoxHeightStyle selectionHeightStyle;
+
+  /// Controls how wide the selection highlight boxes are computed to be.
+  ///
+  /// See [ui.BoxWidthStyle] for details on available styles.
+  final ui.BoxWidthStyle selectionWidthStyle;
+
+  /// The appearance of the keyboard.
+  ///
+  /// This setting is only honored on iOS devices.
+  ///
+  /// If unset, defaults to the brightness of [ThemeData.primaryColorBrightness].
+  final Brightness? keyboardAppearance;
+
+  /// {@macro flutter.widgets.editableText.scrollPadding}
+  final EdgeInsets scrollPadding;
+
+  /// {@macro flutter.widgets.editableText.enableInteractiveSelection}
+  final bool enableInteractiveSelection;
+
+  /// {@macro flutter.widgets.editableText.selectionControls}
+  final TextSelectionControls? selectionControls;
+
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  /// {@macro flutter.widgets.editableText.selectionEnabled}
+  bool get selectionEnabled => enableInteractiveSelection;
+
+  /// {@template flutter.material.textfield.onTap}
+  /// Called for each distinct tap except for every second tap of a double tap.
+  ///
+  /// The text field builds a [GestureDetector] to handle input events like tap,
+  /// to trigger focus requests, to move the caret, adjust the selection, etc.
+  /// Handling some of those events by wrapping the text field with a competing
+  /// GestureDetector is problematic.
+  ///
+  /// To unconditionally handle taps, without interfering with the text field's
+  /// internal gesture detector, provide this callback.
+  ///
+  /// If the text field is created with [enabled] false, taps will not be
+  /// recognized.
+  ///
+  /// To be notified when the text field gains or loses the focus, provide a
+  /// [focusNode] and add a listener to that.
+  ///
+  /// To listen to arbitrary pointer events without competing with the
+  /// text field's internal gesture detector, use a [Listener].
+  /// {@endtemplate}
+  final GestureTapCallback? onTap;
+
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+  ///
+  ///  * [MaterialState.error].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  ///
+  /// If this property is null, [MaterialStateMouseCursor.textable] will be used.
+  ///
+  /// The [mouseCursor] is the only property of [TextField] that controls the
+  /// appearance of the mouse pointer. All other properties related to "cursor"
+  /// stand for the text cursor, which is usually a blinking vertical line at
+  /// the editing position.
+  final MouseCursor? mouseCursor;
+
+  /// Callback that generates a custom [InputDecoration.counter] widget.
+  ///
+  /// See [InputCounterWidgetBuilder] for an explanation of the passed in
+  /// arguments.  The returned widget will be placed below the line in place of
+  /// the default widget built when [InputDecoration.counterText] is specified.
+  ///
+  /// The returned widget will be wrapped in a [Semantics] widget for
+  /// accessibility, but it also needs to be accessible itself. For example,
+  /// if returning a Text widget, set the [Text.semanticsLabel] property.
+  ///
+  /// {@tool snippet}
+  /// ```dart
+  /// Widget counter(
+  ///   BuildContext context,
+  ///   {
+  ///     required int currentLength,
+  ///     required int? maxLength,
+  ///     required bool isFocused,
+  ///   }
+  /// ) {
+  ///   return Text(
+  ///     '$currentLength of $maxLength characters',
+  ///     semanticsLabel: 'character count',
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// If buildCounter returns null, then no counter and no Semantics widget will
+  /// be created at all.
+  final InputCounterWidgetBuilder? buildCounter;
+
+  /// {@macro flutter.widgets.editableText.scrollPhysics}
+  final ScrollPhysics? scrollPhysics;
+
+  /// {@macro flutter.widgets.editableText.scrollController}
+  final ScrollController? scrollController;
+
+  /// {@macro flutter.widgets.editableText.autofillHints}
+  /// {@macro flutter.services.AutofillConfiguration.autofillHints}
+  final Iterable<String>? autofillHints;
+
+  /// {@template flutter.material.textfield.restorationId}
+  /// Restoration ID to save and restore the state of the text field.
+  ///
+  /// If non-null, the text field will persist and restore its current scroll
+  /// offset and - if no [controller] has been provided - the content of the
+  /// text field. If a [controller] has been provided, it is the responsibility
+  /// of the owner of that controller to persist and restore it, e.g. by using
+  /// a [RestorableTextEditingController].
+  ///
+  /// The state of this widget is persisted in a [RestorationBucket] claimed
+  /// from the surrounding [RestorationScope] using the provided restoration ID.
+  ///
+  /// See also:
+  ///
+  ///  * [RestorationManager], which explains how state restoration works in
+  ///    Flutter.
+  /// {@endtemplate}
+  final String? restorationId;
+
+  @override
+  _TextFieldState createState() => _TextFieldState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<TextEditingController>('controller', controller, defaultValue: null));
+    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('enabled', enabled, defaultValue: null));
+    properties.add(DiagnosticsProperty<InputDecoration>('decoration', decoration, defaultValue: const InputDecoration()));
+    properties.add(DiagnosticsProperty<TextInputType>('keyboardType', keyboardType, defaultValue: TextInputType.text));
+    properties.add(DiagnosticsProperty<TextStyle>('style', style, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false));
+    properties.add(DiagnosticsProperty<String>('obscuringCharacter', obscuringCharacter, defaultValue: '•'));
+    properties.add(DiagnosticsProperty<bool>('obscureText', obscureText, defaultValue: false));
+    properties.add(DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: true));
+    properties.add(EnumProperty<SmartDashesType>('smartDashesType', smartDashesType, defaultValue: obscureText ? SmartDashesType.disabled : SmartDashesType.enabled));
+    properties.add(EnumProperty<SmartQuotesType>('smartQuotesType', smartQuotesType, defaultValue: obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled));
+    properties.add(DiagnosticsProperty<bool>('enableSuggestions', enableSuggestions, defaultValue: true));
+    properties.add(IntProperty('maxLines', maxLines, defaultValue: 1));
+    properties.add(IntProperty('minLines', minLines, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('expands', expands, defaultValue: false));
+    properties.add(IntProperty('maxLength', maxLength, defaultValue: null));
+    properties.add(FlagProperty('maxLengthEnforced', value: maxLengthEnforced, defaultValue: true, ifFalse: 'maxLength not enforced'));
+    properties.add(EnumProperty<MaxLengthEnforcement>('maxLengthEnforcement', maxLengthEnforcement, defaultValue: null));
+    properties.add(EnumProperty<TextInputAction>('textInputAction', textInputAction, defaultValue: null));
+    properties.add(EnumProperty<TextCapitalization>('textCapitalization', textCapitalization, defaultValue: TextCapitalization.none));
+    properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: TextAlign.start));
+    properties.add(DiagnosticsProperty<TextAlignVertical>('textAlignVertical', textAlignVertical, defaultValue: null));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    properties.add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0));
+    properties.add(DoubleProperty('cursorHeight', cursorHeight, defaultValue: null));
+    properties.add(DiagnosticsProperty<Radius>('cursorRadius', cursorRadius, defaultValue: null));
+    properties.add(ColorProperty('cursorColor', cursorColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<Brightness>('keyboardAppearance', keyboardAppearance, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('scrollPadding', scrollPadding, defaultValue: const EdgeInsets.all(20.0)));
+    properties.add(FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'));
+    properties.add(DiagnosticsProperty<TextSelectionControls>('selectionControls', selectionControls, defaultValue: null));
+    properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, defaultValue: null));
+    properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
+  }
+}
+
+class _TextFieldState extends State<TextField> with RestorationMixin implements TextSelectionGestureDetectorBuilderDelegate {
+  RestorableTextEditingController? _controller;
+  TextEditingController get _effectiveController => widget.controller ?? _controller!.value;
+
+  FocusNode? _focusNode;
+  FocusNode get _effectiveFocusNode => widget.focusNode ?? (_focusNode ??= FocusNode());
+
+  MaxLengthEnforcement get _effectiveMaxLengthEnforcement => widget.maxLengthEnforcement
+    ?? LengthLimitingTextInputFormatter.getDefaultMaxLengthEnforcement(Theme.of(context).platform);
+
+  bool _isHovering = false;
+
+  bool get needsCounter => widget.maxLength != null
+    && widget.decoration != null
+    && widget.decoration!.counterText == null;
+
+  bool _showSelectionHandles = false;
+
+  late _TextFieldSelectionGestureDetectorBuilder _selectionGestureDetectorBuilder;
+
+  // API for TextSelectionGestureDetectorBuilderDelegate.
+  @override
+  late bool forcePressEnabled;
+
+  @override
+  final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>();
+
+  @override
+  bool get selectionEnabled => widget.selectionEnabled;
+  // End of API for TextSelectionGestureDetectorBuilderDelegate.
+
+  bool get _isEnabled =>  widget.enabled ?? widget.decoration?.enabled ?? true;
+
+  int get _currentLength => _effectiveController.value.text.characters.length;
+
+  bool get _hasIntrinsicError => widget.maxLength != null && widget.maxLength! > 0 && _effectiveController.value.text.characters.length > widget.maxLength!;
+
+  bool get _hasError => widget.decoration?.errorText != null || _hasIntrinsicError;
+
+  InputDecoration _getEffectiveDecoration() {
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final ThemeData themeData = Theme.of(context);
+    final InputDecoration effectiveDecoration = (widget.decoration ?? const InputDecoration())
+      .applyDefaults(themeData.inputDecorationTheme)
+      .copyWith(
+        enabled: _isEnabled,
+        hintMaxLines: widget.decoration?.hintMaxLines ?? widget.maxLines,
+      );
+
+    // No need to build anything if counter or counterText were given directly.
+    if (effectiveDecoration.counter != null || effectiveDecoration.counterText != null)
+      return effectiveDecoration;
+
+    // If buildCounter was provided, use it to generate a counter widget.
+    Widget? counter;
+    final int currentLength = _currentLength;
+    if (effectiveDecoration.counter == null
+        && effectiveDecoration.counterText == null
+        && widget.buildCounter != null) {
+      final bool isFocused = _effectiveFocusNode.hasFocus;
+      final Widget? builtCounter = widget.buildCounter!(
+        context,
+        currentLength: currentLength,
+        maxLength: widget.maxLength,
+        isFocused: isFocused,
+      );
+      // If buildCounter returns null, don't add a counter widget to the field.
+      if (builtCounter != null) {
+        counter = Semantics(
+          container: true,
+          liveRegion: isFocused,
+          child: builtCounter,
+        );
+      }
+      return effectiveDecoration.copyWith(counter: counter);
+    }
+
+    if (widget.maxLength == null)
+      return effectiveDecoration; // No counter widget
+
+    String counterText = '$currentLength';
+    String semanticCounterText = '';
+
+    // Handle a real maxLength (positive number)
+    if (widget.maxLength! > 0) {
+      // Show the maxLength in the counter
+      counterText += '/${widget.maxLength}';
+      final int remaining = (widget.maxLength! - currentLength).clamp(0, widget.maxLength!);
+      semanticCounterText = localizations.remainingTextFieldCharacterCount(remaining);
+    }
+
+    if (_hasIntrinsicError) {
+      return effectiveDecoration.copyWith(
+        errorText: effectiveDecoration.errorText ?? '',
+        counterStyle: effectiveDecoration.errorStyle
+          ?? themeData.textTheme.caption!.copyWith(color: themeData.errorColor),
+        counterText: counterText,
+        semanticCounterText: semanticCounterText,
+      );
+    }
+
+    return effectiveDecoration.copyWith(
+      counterText: counterText,
+      semanticCounterText: semanticCounterText,
+    );
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _selectionGestureDetectorBuilder = _TextFieldSelectionGestureDetectorBuilder(state: this);
+    if (widget.controller == null) {
+      _createLocalController();
+    }
+    _effectiveFocusNode.canRequestFocus = _isEnabled;
+  }
+
+  bool get _canRequestFocus {
+    final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
+    switch (mode) {
+      case NavigationMode.traditional:
+        return _isEnabled;
+      case NavigationMode.directional:
+        return true;
+    }
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _effectiveFocusNode.canRequestFocus = _canRequestFocus;
+  }
+
+  @override
+  void didUpdateWidget(TextField oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.controller == null && oldWidget.controller != null) {
+      _createLocalController(oldWidget.controller!.value);
+    } else if (widget.controller != null && oldWidget.controller == null) {
+      unregisterFromRestoration(_controller!);
+      _controller!.dispose();
+      _controller = null;
+    }
+    _effectiveFocusNode.canRequestFocus = _canRequestFocus;
+    if (_effectiveFocusNode.hasFocus && widget.readOnly != oldWidget.readOnly && _isEnabled) {
+      if(_effectiveController.selection.isCollapsed) {
+        _showSelectionHandles = !widget.readOnly;
+      }
+    }
+  }
+
+  @override
+  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
+    if (_controller != null) {
+      _registerController();
+    }
+  }
+
+  void _registerController() {
+    assert(_controller != null);
+    registerForRestoration(_controller!, 'controller');
+  }
+
+  void _createLocalController([TextEditingValue? value]) {
+    assert(_controller == null);
+    _controller = value == null
+        ? RestorableTextEditingController()
+        : RestorableTextEditingController.fromValue(value);
+    if (!restorePending) {
+      _registerController();
+    }
+  }
+
+  @override
+  String? get restorationId => widget.restorationId;
+
+  @override
+  void dispose() {
+    _focusNode?.dispose();
+    _controller?.dispose();
+    super.dispose();
+  }
+
+  EditableTextState? get _editableText => editableTextKey.currentState;
+
+  void _requestKeyboard() {
+    _editableText?.requestKeyboard();
+  }
+
+  bool _shouldShowSelectionHandles(SelectionChangedCause? cause) {
+    // When the text field is activated by something that doesn't trigger the
+    // selection overlay, we shouldn't show the handles either.
+    if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar)
+      return false;
+
+    if (cause == SelectionChangedCause.keyboard)
+      return false;
+
+    if (widget.readOnly && _effectiveController.selection.isCollapsed)
+      return false;
+
+    if (!_isEnabled)
+      return false;
+
+    if (cause == SelectionChangedCause.longPress)
+      return true;
+
+    if (_effectiveController.text.isNotEmpty)
+      return true;
+
+    return false;
+  }
+
+  void _handleSelectionChanged(TextSelection selection, SelectionChangedCause? cause) {
+    final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause);
+    if (willShowSelectionHandles != _showSelectionHandles) {
+      setState(() {
+        _showSelectionHandles = willShowSelectionHandles;
+      });
+    }
+
+    switch (Theme.of(context).platform) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        if (cause == SelectionChangedCause.longPress) {
+          _editableText?.bringIntoView(selection.base);
+        }
+        return;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        // Do nothing.
+    }
+  }
+
+  /// Toggle the toolbar when a selection handle is tapped.
+  void _handleSelectionHandleTapped() {
+    if (_effectiveController.selection.isCollapsed) {
+      _editableText!.toggleToolbar();
+    }
+  }
+
+  void _handleHover(bool hovering) {
+    if (hovering != _isHovering) {
+      setState(() {
+        _isHovering = hovering;
+      });
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+    assert(debugCheckHasDirectionality(context));
+    assert(
+      !(widget.style != null && widget.style!.inherit == false &&
+        (widget.style!.fontSize == null || widget.style!.textBaseline == null)),
+      'inherit false style must supply fontSize and textBaseline',
+    );
+
+    final ThemeData theme = Theme.of(context);
+    final TextSelectionThemeData selectionTheme = TextSelectionTheme.of(context);
+    final TextStyle style = theme.textTheme.subtitle1!.merge(widget.style);
+    final Brightness keyboardAppearance = widget.keyboardAppearance ?? theme.primaryColorBrightness;
+    final TextEditingController controller = _effectiveController;
+    final FocusNode focusNode = _effectiveFocusNode;
+    final List<TextInputFormatter> formatters = <TextInputFormatter>[
+      ...?widget.inputFormatters,
+      if (widget.maxLength != null && widget.maxLengthEnforced)
+        LengthLimitingTextInputFormatter(
+          widget.maxLength,
+          maxLengthEnforcement: _effectiveMaxLengthEnforcement,
+        ),
+    ];
+
+    TextSelectionControls? textSelectionControls = widget.selectionControls;
+    final bool paintCursorAboveText;
+    final bool cursorOpacityAnimates;
+    Offset? cursorOffset;
+    Color? cursorColor = widget.cursorColor;
+    final Color selectionColor;
+    Color? autocorrectionTextRectColor;
+    Radius? cursorRadius = widget.cursorRadius;
+
+    switch (theme.platform) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context);
+        forcePressEnabled = true;
+        textSelectionControls ??= cupertinoTextSelectionControls;
+        paintCursorAboveText = true;
+        cursorOpacityAnimates = true;
+        cursorColor ??= selectionTheme.cursorColor ?? cupertinoTheme.primaryColor;
+        selectionColor = selectionTheme.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40);
+        cursorRadius ??= const Radius.circular(2.0);
+        cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0);
+        autocorrectionTextRectColor = selectionColor;
+        break;
+
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        forcePressEnabled = false;
+        textSelectionControls ??= materialTextSelectionControls;
+        paintCursorAboveText = false;
+        cursorOpacityAnimates = false;
+        cursorColor ??= selectionTheme.cursorColor ?? theme.colorScheme.primary;
+        selectionColor = selectionTheme.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40);
+        break;
+    }
+
+    Widget child = RepaintBoundary(
+      child: UnmanagedRestorationScope(
+        bucket: bucket,
+        child: EditableText(
+          key: editableTextKey,
+          readOnly: widget.readOnly || !_isEnabled,
+          toolbarOptions: widget.toolbarOptions,
+          showCursor: widget.showCursor,
+          showSelectionHandles: _showSelectionHandles,
+          controller: controller,
+          focusNode: focusNode,
+          keyboardType: widget.keyboardType,
+          textInputAction: widget.textInputAction,
+          textCapitalization: widget.textCapitalization,
+          style: style,
+          strutStyle: widget.strutStyle,
+          textAlign: widget.textAlign,
+          textDirection: widget.textDirection,
+          autofocus: widget.autofocus,
+          obscuringCharacter: widget.obscuringCharacter,
+          obscureText: widget.obscureText,
+          autocorrect: widget.autocorrect,
+          smartDashesType: widget.smartDashesType,
+          smartQuotesType: widget.smartQuotesType,
+          enableSuggestions: widget.enableSuggestions,
+          maxLines: widget.maxLines,
+          minLines: widget.minLines,
+          expands: widget.expands,
+          selectionColor: selectionColor,
+          selectionControls: widget.selectionEnabled ? textSelectionControls : null,
+          onChanged: widget.onChanged,
+          onSelectionChanged: _handleSelectionChanged,
+          onEditingComplete: widget.onEditingComplete,
+          onSubmitted: widget.onSubmitted,
+          onAppPrivateCommand: widget.onAppPrivateCommand,
+          onSelectionHandleTapped: _handleSelectionHandleTapped,
+          inputFormatters: formatters,
+          rendererIgnoresPointer: true,
+          mouseCursor: MouseCursor.defer, // TextField will handle the cursor
+          cursorWidth: widget.cursorWidth,
+          cursorHeight: widget.cursorHeight,
+          cursorRadius: cursorRadius,
+          cursorColor: cursorColor,
+          selectionHeightStyle: widget.selectionHeightStyle,
+          selectionWidthStyle: widget.selectionWidthStyle,
+          cursorOpacityAnimates: cursorOpacityAnimates,
+          cursorOffset: cursorOffset,
+          paintCursorAboveText: paintCursorAboveText,
+          backgroundCursorColor: CupertinoColors.inactiveGray,
+          scrollPadding: widget.scrollPadding,
+          keyboardAppearance: keyboardAppearance,
+          enableInteractiveSelection: widget.enableInteractiveSelection,
+          dragStartBehavior: widget.dragStartBehavior,
+          scrollController: widget.scrollController,
+          scrollPhysics: widget.scrollPhysics,
+          autofillHints: widget.autofillHints,
+          autocorrectionTextRectColor: autocorrectionTextRectColor,
+          restorationId: 'editable',
+        ),
+      ),
+    );
+
+    if (widget.decoration != null) {
+      child = AnimatedBuilder(
+        animation: Listenable.merge(<Listenable>[ focusNode, controller ]),
+        builder: (BuildContext context, Widget? child) {
+          return InputDecorator(
+            decoration: _getEffectiveDecoration(),
+            baseStyle: widget.style,
+            textAlign: widget.textAlign,
+            textAlignVertical: widget.textAlignVertical,
+            isHovering: _isHovering,
+            isFocused: focusNode.hasFocus,
+            isEmpty: controller.value.text.isEmpty,
+            expands: widget.expands,
+            child: child,
+          );
+        },
+        child: child,
+      );
+    }
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
+      widget.mouseCursor ?? MaterialStateMouseCursor.textable,
+      <MaterialState>{
+        if (!_isEnabled) MaterialState.disabled,
+        if (_isHovering) MaterialState.hovered,
+        if (focusNode.hasFocus) MaterialState.focused,
+        if (_hasError) MaterialState.error,
+      },
+    );
+
+    final int? semanticsMaxValueLength;
+    if (widget.maxLengthEnforced &&
+      _effectiveMaxLengthEnforcement != MaxLengthEnforcement.none &&
+      widget.maxLength != null &&
+      widget.maxLength! > 0) {
+      semanticsMaxValueLength = widget.maxLength;
+    } else {
+      semanticsMaxValueLength = null;
+    }
+
+    return MouseRegion(
+      cursor: effectiveMouseCursor,
+      onEnter: (PointerEnterEvent event) => _handleHover(true),
+      onExit: (PointerExitEvent event) => _handleHover(false),
+      child: IgnorePointer(
+        ignoring: !_isEnabled,
+        child: AnimatedBuilder(
+          animation: controller, // changes the _currentLength
+          builder: (BuildContext context, Widget? child) {
+            return Semantics(
+              maxValueLength: semanticsMaxValueLength,
+              currentValueLength: _currentLength,
+              onTap: () {
+                if (!_effectiveController.selection.isValid)
+                  _effectiveController.selection = TextSelection.collapsed(offset: _effectiveController.text.length);
+                _requestKeyboard();
+              },
+              child: child,
+            );
+          },
+          child: _selectionGestureDetectorBuilder.buildGestureDetector(
+            behavior: HitTestBehavior.translucent,
+            child: child,
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/text_form_field.dart b/lib/src/material/text_form_field.dart
new file mode 100644
index 0000000..59bdd67
--- /dev/null
+++ b/lib/src/material/text_form_field.dart
@@ -0,0 +1,376 @@
+// Copyright 2014 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.
+
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'input_decorator.dart';
+import 'text_field.dart';
+import 'theme.dart';
+
+export 'package:flute/services.dart' show SmartQuotesType, SmartDashesType;
+
+/// A [FormField] that contains a [TextField].
+///
+/// This is a convenience widget that wraps a [TextField] widget in a
+/// [FormField].
+///
+/// A [Form] ancestor is not required. The [Form] simply makes it easier to
+/// save, reset, or validate multiple fields at once. To use without a [Form],
+/// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to
+/// save or reset the form field.
+///
+/// When a [controller] is specified, its [TextEditingController.text]
+/// defines the [initialValue]. If this [FormField] is part of a scrolling
+/// container that lazily constructs its children, like a [ListView] or a
+/// [CustomScrollView], then a [controller] should be specified.
+/// The controller's lifetime should be managed by a stateful widget ancestor
+/// of the scrolling container.
+///
+/// If a [controller] is not specified, [initialValue] can be used to give
+/// the automatically generated controller an initial value.
+///
+/// Remember to call [TextEditingController.dispose] of the [TextEditingController]
+/// when it is no longer needed. This will ensure we discard any resources used
+/// by the object.
+///
+/// By default, `decoration` will apply the [ThemeData.inputDecorationTheme] for
+/// the current context to the [InputDecoration], see
+/// [InputDecoration.applyDefaults].
+///
+/// For a documentation about the various parameters, see [TextField].
+///
+/// {@tool snippet}
+///
+/// Creates a [TextFormField] with an [InputDecoration] and validator function.
+///
+/// ![If the user enters valid text, the TextField appears normally without any warnings to the user](https://flutter.github.io/assets-for-api-docs/assets/material/text_form_field.png)
+///
+/// ![If the user enters invalid text, the error message returned from the validator function is displayed in dark red underneath the input](https://flutter.github.io/assets-for-api-docs/assets/material/text_form_field_error.png)
+///
+/// ```dart
+/// TextFormField(
+///   decoration: const InputDecoration(
+///     icon: Icon(Icons.person),
+///     hintText: 'What do people call you?',
+///     labelText: 'Name *',
+///   ),
+///   onSaved: (String? value) {
+///     // This optional block of code can be used to run
+///     // code when the user saves the form.
+///   },
+///   validator: (String? value) {
+///     return (value != null && value.contains('@')) ? 'Do not use the @ char.' : null;
+///   },
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateful_widget_material}
+/// This example shows how to move the focus to the next field when the user
+/// presses the SPACE key.
+///
+/// ```dart imports
+/// import 'package:flute/services.dart';
+/// ```
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Material(
+///     child: Center(
+///       child: Shortcuts(
+///         shortcuts: <LogicalKeySet, Intent>{
+///           // Pressing space in the field will now move to the next field.
+///           LogicalKeySet(LogicalKeyboardKey.space): const NextFocusIntent(),
+///         },
+///         child: FocusTraversalGroup(
+///           child: Form(
+///             autovalidateMode: AutovalidateMode.always,
+///             onChanged: () {
+///               Form.of(primaryFocus!.context!)!.save();
+///             },
+///             child: Wrap(
+///               children: List<Widget>.generate(5, (int index) {
+///                 return Padding(
+///                   padding: const EdgeInsets.all(8.0),
+///                   child: ConstrainedBox(
+///                     constraints: BoxConstraints.tight(const Size(200, 50)),
+///                     child: TextFormField(
+///                       onSaved: (String? value) {
+///                         print('Value for field $index saved as "$value"');
+///                       },
+///                     ),
+///                   ),
+///                 );
+///               }),
+///             ),
+///           ),
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * <https://material.io/design/components/text-fields.html>
+///  * [TextField], which is the underlying text field without the [Form]
+///    integration.
+///  * [InputDecorator], which shows the labels and other visual elements that
+///    surround the actual text editing widget.
+///  * Learn how to use a [TextEditingController] in one of our [cookbook recipes](https://flutter.dev/docs/cookbook/forms/text-field-changes#2-use-a-texteditingcontroller).
+class TextFormField extends FormField<String> {
+  /// Creates a [FormField] that contains a [TextField].
+  ///
+  /// When a [controller] is specified, [initialValue] must be null (the
+  /// default). If [controller] is null, then a [TextEditingController]
+  /// will be constructed automatically and its `text` will be initialized
+  /// to [initialValue] or the empty string.
+  ///
+  /// For documentation about the various parameters, see the [TextField] class
+  /// and [new TextField], the constructor.
+  TextFormField({
+    Key? key,
+    this.controller,
+    String? initialValue,
+    FocusNode? focusNode,
+    InputDecoration? decoration = const InputDecoration(),
+    TextInputType? keyboardType,
+    TextCapitalization textCapitalization = TextCapitalization.none,
+    TextInputAction? textInputAction,
+    TextStyle? style,
+    StrutStyle? strutStyle,
+    TextDirection? textDirection,
+    TextAlign textAlign = TextAlign.start,
+    TextAlignVertical? textAlignVertical,
+    bool autofocus = false,
+    bool readOnly = false,
+    ToolbarOptions? toolbarOptions,
+    bool? showCursor,
+    String obscuringCharacter = '•',
+    bool obscureText = false,
+    bool autocorrect = true,
+    SmartDashesType? smartDashesType,
+    SmartQuotesType? smartQuotesType,
+    bool enableSuggestions = true,
+    @Deprecated(
+      'Use autoValidateMode parameter which provide more specific '
+      'behaviour related to auto validation. '
+      'This feature was deprecated after v1.19.0.'
+    )
+    bool autovalidate = false,
+    @Deprecated(
+      'Use maxLengthEnforcement parameter which provides more specific '
+      'behavior related to the maxLength limit. '
+      'This feature was deprecated after v1.25.0-5.0.pre.'
+    )
+    bool maxLengthEnforced = true,
+    MaxLengthEnforcement? maxLengthEnforcement,
+    int? maxLines = 1,
+    int? minLines,
+    bool expands = false,
+    int? maxLength,
+    ValueChanged<String>? onChanged,
+    GestureTapCallback? onTap,
+    VoidCallback? onEditingComplete,
+    ValueChanged<String>? onFieldSubmitted,
+    FormFieldSetter<String>? onSaved,
+    FormFieldValidator<String>? validator,
+    List<TextInputFormatter>? inputFormatters,
+    bool? enabled,
+    double cursorWidth = 2.0,
+    double? cursorHeight,
+    Radius? cursorRadius,
+    Color? cursorColor,
+    Brightness? keyboardAppearance,
+    EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
+    bool enableInteractiveSelection = true,
+    TextSelectionControls? selectionControls,
+    InputCounterWidgetBuilder? buildCounter,
+    ScrollPhysics? scrollPhysics,
+    Iterable<String>? autofillHints,
+    AutovalidateMode? autovalidateMode,
+  }) : assert(initialValue == null || controller == null),
+       assert(textAlign != null),
+       assert(autofocus != null),
+       assert(readOnly != null),
+       assert(obscuringCharacter != null && obscuringCharacter.length == 1),
+       assert(obscureText != null),
+       assert(autocorrect != null),
+       assert(enableSuggestions != null),
+       assert(autovalidate != null),
+       assert(
+         autovalidate == false ||
+         autovalidate == true && autovalidateMode == null,
+         'autovalidate and autovalidateMode should not be used together.'
+       ),
+       assert(maxLengthEnforced != null),
+       assert(
+         maxLengthEnforced || maxLengthEnforcement == null,
+         'maxLengthEnforced is deprecated, use only maxLengthEnforcement',
+       ),
+       assert(scrollPadding != null),
+       assert(maxLines == null || maxLines > 0),
+       assert(minLines == null || minLines > 0),
+       assert(
+         (maxLines == null) || (minLines == null) || (maxLines >= minLines),
+         "minLines can't be greater than maxLines",
+       ),
+       assert(expands != null),
+       assert(
+         !expands || (maxLines == null && minLines == null),
+         'minLines and maxLines must be null when expands is true.',
+       ),
+       assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
+       assert(maxLength == null || maxLength > 0),
+       assert(enableInteractiveSelection != null),
+       super(
+       key: key,
+       initialValue: controller != null ? controller.text : (initialValue ?? ''),
+       onSaved: onSaved,
+       validator: validator,
+       enabled: enabled ?? decoration?.enabled ?? true,
+       autovalidateMode: autovalidate
+           ? AutovalidateMode.always
+           : (autovalidateMode ?? AutovalidateMode.disabled),
+       builder: (FormFieldState<String> field) {
+         final _TextFormFieldState state = field as _TextFormFieldState;
+         final InputDecoration effectiveDecoration = (decoration ?? const InputDecoration())
+             .applyDefaults(Theme.of(field.context).inputDecorationTheme);
+         void onChangedHandler(String value) {
+           field.didChange(value);
+           if (onChanged != null) {
+             onChanged(value);
+           }
+         }
+         return TextField(
+           controller: state._effectiveController,
+           focusNode: focusNode,
+           decoration: effectiveDecoration.copyWith(errorText: field.errorText),
+           keyboardType: keyboardType,
+           textInputAction: textInputAction,
+           style: style,
+           strutStyle: strutStyle,
+           textAlign: textAlign,
+           textAlignVertical: textAlignVertical,
+           textDirection: textDirection,
+           textCapitalization: textCapitalization,
+           autofocus: autofocus,
+           toolbarOptions: toolbarOptions,
+           readOnly: readOnly,
+           showCursor: showCursor,
+           obscuringCharacter: obscuringCharacter,
+           obscureText: obscureText,
+           autocorrect: autocorrect,
+           smartDashesType: smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
+           smartQuotesType: smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
+           enableSuggestions: enableSuggestions,
+           maxLengthEnforced: maxLengthEnforced,
+           maxLengthEnforcement: maxLengthEnforcement,
+           maxLines: maxLines,
+           minLines: minLines,
+           expands: expands,
+           maxLength: maxLength,
+           onChanged: onChangedHandler,
+           onTap: onTap,
+           onEditingComplete: onEditingComplete,
+           onSubmitted: onFieldSubmitted,
+           inputFormatters: inputFormatters,
+           enabled: enabled ?? decoration?.enabled ?? true,
+           cursorWidth: cursorWidth,
+           cursorHeight: cursorHeight,
+           cursorRadius: cursorRadius,
+           cursorColor: cursorColor,
+           scrollPadding: scrollPadding,
+           scrollPhysics: scrollPhysics,
+           keyboardAppearance: keyboardAppearance,
+           enableInteractiveSelection: enableInteractiveSelection,
+           selectionControls: selectionControls,
+           buildCounter: buildCounter,
+           autofillHints: autofillHints,
+         );
+       },
+     );
+
+  /// Controls the text being edited.
+  ///
+  /// If null, this widget will create its own [TextEditingController] and
+  /// initialize its [TextEditingController.text] with [initialValue].
+  final TextEditingController? controller;
+
+  @override
+  _TextFormFieldState createState() => _TextFormFieldState();
+}
+
+class _TextFormFieldState extends FormFieldState<String> {
+  TextEditingController? _controller;
+
+  TextEditingController? get _effectiveController => widget.controller ?? _controller;
+
+  @override
+  TextFormField get widget => super.widget as TextFormField;
+
+  @override
+  void initState() {
+    super.initState();
+    if (widget.controller == null) {
+      _controller = TextEditingController(text: widget.initialValue);
+    } else {
+      widget.controller!.addListener(_handleControllerChanged);
+    }
+  }
+
+  @override
+  void didUpdateWidget(TextFormField oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.controller != oldWidget.controller) {
+      oldWidget.controller?.removeListener(_handleControllerChanged);
+      widget.controller?.addListener(_handleControllerChanged);
+
+      if (oldWidget.controller != null && widget.controller == null)
+        _controller = TextEditingController.fromValue(oldWidget.controller!.value);
+      if (widget.controller != null) {
+        setValue(widget.controller!.text);
+        if (oldWidget.controller == null)
+          _controller = null;
+      }
+    }
+  }
+
+  @override
+  void dispose() {
+    widget.controller?.removeListener(_handleControllerChanged);
+    super.dispose();
+  }
+
+  @override
+  void didChange(String? value) {
+    super.didChange(value);
+
+    if (_effectiveController!.text != value)
+      _effectiveController!.text = value ?? '';
+  }
+
+  @override
+  void reset() {
+    // setState will be called in the superclass, so even though state is being
+    // manipulated, no setState call is needed here.
+    _effectiveController!.text = widget.initialValue ?? '';
+    super.reset();
+  }
+
+  void _handleControllerChanged() {
+    // Suppress changes that originated from within this class.
+    //
+    // In the case where a controller has been passed in to this widget, we
+    // register this change listener. In these cases, we'll also receive change
+    // notifications for changes originating from within this class -- for
+    // example, the reset() method. In such cases, the FormField value will
+    // already have been set.
+    if (_effectiveController!.text != value)
+      didChange(_effectiveController!.text);
+  }
+}
diff --git a/lib/src/material/text_selection.dart b/lib/src/material/text_selection.dart
new file mode 100644
index 0000000..356fca5
--- /dev/null
+++ b/lib/src/material/text_selection.dart
@@ -0,0 +1,292 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'debug.dart';
+import 'material_localizations.dart';
+import 'text_selection_theme.dart';
+import 'text_selection_toolbar.dart';
+import 'text_selection_toolbar_text_button.dart';
+import 'theme.dart';
+
+const double _kHandleSize = 22.0;
+
+// Padding between the toolbar and the anchor.
+const double _kToolbarContentDistanceBelow = _kHandleSize - 2.0;
+const double _kToolbarContentDistance = 8.0;
+
+/// Android Material styled text selection controls.
+class MaterialTextSelectionControls extends TextSelectionControls {
+  /// Returns the size of the Material handle.
+  @override
+  Size getHandleSize(double textLineHeight) => const Size(_kHandleSize, _kHandleSize);
+
+  /// Builder for material-style copy/paste text selection toolbar.
+  @override
+  Widget buildToolbar(
+    BuildContext context,
+    Rect globalEditableRegion,
+    double textLineHeight,
+    Offset selectionMidpoint,
+    List<TextSelectionPoint> endpoints,
+    TextSelectionDelegate delegate,
+    ClipboardStatusNotifier clipboardStatus,
+  ) {
+    return _TextSelectionControlsToolbar(
+      globalEditableRegion: globalEditableRegion,
+      textLineHeight: textLineHeight,
+      selectionMidpoint: selectionMidpoint,
+      endpoints: endpoints,
+      delegate: delegate,
+      clipboardStatus: clipboardStatus,
+      handleCut: canCut(delegate) ? () => handleCut(delegate) : null,
+      handleCopy: canCopy(delegate) ? () => handleCopy(delegate, clipboardStatus) : null,
+      handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null,
+      handleSelectAll: canSelectAll(delegate) ? () => handleSelectAll(delegate) : null,
+    );
+  }
+
+  /// Builder for material-style text selection handles.
+  @override
+  Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textHeight) {
+    final ThemeData theme = Theme.of(context);
+    final Color handleColor = TextSelectionTheme.of(context).selectionHandleColor ?? theme.colorScheme.primary;
+    final Widget handle = SizedBox(
+      width: _kHandleSize,
+      height: _kHandleSize,
+      child: CustomPaint(
+        painter: _TextSelectionHandlePainter(
+          color: handleColor,
+        ),
+      ),
+    );
+
+    // [handle] is a circle, with a rectangle in the top left quadrant of that
+    // circle (an onion pointing to 10:30). We rotate [handle] to point
+    // straight up or up-right depending on the handle type.
+    switch (type) {
+      case TextSelectionHandleType.left: // points up-right
+        return Transform.rotate(
+          angle: math.pi / 2.0,
+          child: handle,
+        );
+      case TextSelectionHandleType.right: // points up-left
+        return handle;
+      case TextSelectionHandleType.collapsed: // points up
+        return Transform.rotate(
+          angle: math.pi / 4.0,
+          child: handle,
+        );
+    }
+  }
+
+  /// Gets anchor for material-style text selection handles.
+  ///
+  /// See [TextSelectionControls.getHandleAnchor].
+  @override
+  Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight) {
+    switch (type) {
+      case TextSelectionHandleType.left:
+        return const Offset(_kHandleSize, 0);
+      case TextSelectionHandleType.right:
+        return Offset.zero;
+      default:
+        return const Offset(_kHandleSize / 2, -4);
+    }
+  }
+
+  @override
+  bool canSelectAll(TextSelectionDelegate delegate) {
+    // Android allows SelectAll when selection is not collapsed, unless
+    // everything has already been selected.
+    final TextEditingValue value = delegate.textEditingValue;
+    return delegate.selectAllEnabled &&
+           value.text.isNotEmpty &&
+           !(value.selection.start == 0 && value.selection.end == value.text.length);
+  }
+}
+
+// The label and callback for the available default text selection menu buttons.
+class _TextSelectionToolbarItemData {
+  const _TextSelectionToolbarItemData({
+    required this.label,
+    required this.onPressed,
+  });
+
+  final String label;
+  final VoidCallback onPressed;
+}
+
+// The highest level toolbar widget, built directly by buildToolbar.
+class _TextSelectionControlsToolbar extends StatefulWidget {
+  const _TextSelectionControlsToolbar({
+    Key? key,
+    required this.clipboardStatus,
+    required this.delegate,
+    required this.endpoints,
+    required this.globalEditableRegion,
+    required this.handleCut,
+    required this.handleCopy,
+    required this.handlePaste,
+    required this.handleSelectAll,
+    required this.selectionMidpoint,
+    required this.textLineHeight,
+  }) : super(key: key);
+
+  final ClipboardStatusNotifier clipboardStatus;
+  final TextSelectionDelegate delegate;
+  final List<TextSelectionPoint> endpoints;
+  final Rect globalEditableRegion;
+  final VoidCallback? handleCut;
+  final VoidCallback? handleCopy;
+  final VoidCallback? handlePaste;
+  final VoidCallback? handleSelectAll;
+  final Offset selectionMidpoint;
+  final double textLineHeight;
+
+  @override
+  _TextSelectionControlsToolbarState createState() => _TextSelectionControlsToolbarState();
+}
+
+class _TextSelectionControlsToolbarState extends State<_TextSelectionControlsToolbar> with TickerProviderStateMixin {
+  void _onChangedClipboardStatus() {
+    setState(() {
+      // Inform the widget that the value of clipboardStatus has changed.
+    });
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    widget.clipboardStatus.addListener(_onChangedClipboardStatus);
+    widget.clipboardStatus.update();
+  }
+
+  @override
+  void didUpdateWidget(_TextSelectionControlsToolbar oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.clipboardStatus != oldWidget.clipboardStatus) {
+      widget.clipboardStatus.addListener(_onChangedClipboardStatus);
+      oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus);
+    }
+    widget.clipboardStatus.update();
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    // When used in an Overlay, it can happen that this is disposed after its
+    // creator has already disposed _clipboardStatus.
+    if (!widget.clipboardStatus.disposed) {
+      widget.clipboardStatus.removeListener(_onChangedClipboardStatus);
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // If there are no buttons to be shown, don't render anything.
+    if (widget.handleCut == null && widget.handleCopy == null
+        && widget.handlePaste == null && widget.handleSelectAll == null) {
+      return const SizedBox.shrink();
+    }
+    // If the paste button is desired, don't render anything until the state of
+    // the clipboard is known, since it's used to determine if paste is shown.
+    if (widget.handlePaste != null
+        && widget.clipboardStatus.value == ClipboardStatus.unknown) {
+      return const SizedBox.shrink();
+    }
+
+    // Calculate the positioning of the menu. It is placed above the selection
+    // if there is enough room, or otherwise below.
+    final TextSelectionPoint startTextSelectionPoint = widget.endpoints[0];
+    final TextSelectionPoint endTextSelectionPoint = widget.endpoints.length > 1
+      ? widget.endpoints[1]
+      : widget.endpoints[0];
+    final Offset anchorAbove = Offset(
+      widget.globalEditableRegion.left + widget.selectionMidpoint.dx,
+      widget.globalEditableRegion.top + startTextSelectionPoint.point.dy - widget.textLineHeight - _kToolbarContentDistance
+    );
+    final Offset anchorBelow = Offset(
+      widget.globalEditableRegion.left + widget.selectionMidpoint.dx,
+      widget.globalEditableRegion.top + endTextSelectionPoint.point.dy + _kToolbarContentDistanceBelow,
+    );
+
+    // Determine which buttons will appear so that the order and total number is
+    // known. A button's position in the menu can slightly affect its
+    // appearance.
+    assert(debugCheckHasMaterialLocalizations(context));
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final List<_TextSelectionToolbarItemData> itemDatas = <_TextSelectionToolbarItemData>[
+      if (widget.handleCut != null)
+        _TextSelectionToolbarItemData(
+          label: localizations.cutButtonLabel,
+          onPressed: widget.handleCut!,
+        ),
+      if (widget.handleCopy != null)
+        _TextSelectionToolbarItemData(
+          label: localizations.copyButtonLabel,
+          onPressed: widget.handleCopy!,
+        ),
+      if (widget.handlePaste != null
+          && widget.clipboardStatus.value == ClipboardStatus.pasteable)
+        _TextSelectionToolbarItemData(
+          label: localizations.pasteButtonLabel,
+          onPressed: widget.handlePaste!,
+        ),
+      if (widget.handleSelectAll != null)
+        _TextSelectionToolbarItemData(
+          label: localizations.selectAllButtonLabel,
+          onPressed: widget.handleSelectAll!,
+        ),
+    ];
+
+    // If there is no option available, build an empty widget.
+    if (itemDatas.isEmpty) {
+      return const SizedBox(width: 0.0, height: 0.0);
+    }
+
+    return TextSelectionToolbar(
+      anchorAbove: anchorAbove,
+      anchorBelow: anchorBelow,
+      children: itemDatas.asMap().entries.map((MapEntry<int, _TextSelectionToolbarItemData> entry) {
+        return TextSelectionToolbarTextButton(
+          padding: TextSelectionToolbarTextButton.getPadding(entry.key, itemDatas.length),
+          onPressed: entry.value.onPressed,
+          child: Text(entry.value.label),
+        );
+      }).toList(),
+    );
+  }
+}
+
+/// Draws a single text selection handle which points up and to the left.
+class _TextSelectionHandlePainter extends CustomPainter {
+  _TextSelectionHandlePainter({ required this.color });
+
+  final Color color;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final Paint paint = Paint()..color = color;
+    final double radius = size.width/2.0;
+    final Rect circle = Rect.fromCircle(center: Offset(radius, radius), radius: radius);
+    final Rect point = Rect.fromLTWH(0.0, 0.0, radius, radius);
+    final Path path = Path()..addOval(circle)..addRect(point);
+    canvas.drawPath(path, paint);
+  }
+
+  @override
+  bool shouldRepaint(_TextSelectionHandlePainter oldPainter) {
+    return color != oldPainter.color;
+  }
+}
+
+/// Text selection controls that follow the Material Design specification.
+final TextSelectionControls materialTextSelectionControls = MaterialTextSelectionControls();
diff --git a/lib/src/material/text_selection_theme.dart b/lib/src/material/text_selection_theme.dart
new file mode 100644
index 0000000..e1b83f8
--- /dev/null
+++ b/lib/src/material/text_selection_theme.dart
@@ -0,0 +1,165 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'theme.dart';
+
+/// Defines the visual properties needed for text selection in [TextField] and
+/// [SelectableText] widgets.
+///
+/// Used by [TextSelectionTheme] to control the visual properties of text
+/// selection in a widget subtree.
+///
+/// Use [TextSelectionTheme.of] to access the closest ancestor
+/// [TextSelectionTheme] of the current [BuildContext].
+///
+/// See also:
+///
+///  * [TextSelectionTheme], an [InheritedWidget] that propagates the theme down its
+///    subtree.
+///  * [InputDecorationTheme], which defines most other visual properties of
+///    text fields.
+@immutable
+class TextSelectionThemeData with Diagnosticable {
+  /// Creates the set of properties used to configure [TextField]s.
+  const TextSelectionThemeData({
+    this.cursorColor,
+    this.selectionColor,
+    this.selectionHandleColor,
+  });
+
+  /// The color of the cursor in the text field.
+  ///
+  /// The cursor indicates the current location of text insertion point in
+  /// the field.
+  final Color? cursorColor;
+
+  /// The background color of selected text.
+  final Color? selectionColor;
+
+  /// The color of the selection handles on the text field.
+  ///
+  /// Selection handles are used to indicate the bounds of the selected text,
+  /// or as a handle to drag the cursor to a new location in the text.
+  final Color? selectionHandleColor;
+
+  /// Creates a copy of this object with the given fields replaced with the
+  /// specified values.
+  TextSelectionThemeData copyWith({
+    Color? cursorColor,
+    Color? selectionColor,
+    Color? selectionHandleColor,
+  }) {
+    return TextSelectionThemeData(
+      cursorColor: cursorColor ?? this.cursorColor,
+      selectionColor: selectionColor ?? this.selectionColor,
+      selectionHandleColor: selectionHandleColor ?? this.selectionHandleColor,
+    );
+  }
+
+  /// Linearly interpolate between two text field themes.
+  ///
+  /// If both arguments are null, then null is returned.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static TextSelectionThemeData? lerp(TextSelectionThemeData? a, TextSelectionThemeData? b, double t) {
+    if (a == null && b == null)
+      return null;
+    assert(t != null);
+    return TextSelectionThemeData(
+      cursorColor: Color.lerp(a?.cursorColor, b?.cursorColor, t),
+      selectionColor: Color.lerp(a?.selectionColor, b?.selectionColor, t),
+      selectionHandleColor: Color.lerp(a?.selectionHandleColor, b?.selectionHandleColor, t),
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      cursorColor,
+      selectionColor,
+      selectionHandleColor,
+    );
+  }
+
+  @override
+  bool operator==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is TextSelectionThemeData
+      && other.cursorColor == cursorColor
+      && other.selectionColor == selectionColor
+      && other.selectionHandleColor == selectionHandleColor;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ColorProperty('cursorColor', cursorColor, defaultValue: null));
+    properties.add(ColorProperty('selectionColor', selectionColor, defaultValue: null));
+    properties.add(ColorProperty('selectionHandleColor', selectionHandleColor, defaultValue: null));
+  }
+}
+
+/// An inherited widget that defines the appearance of text selection in
+/// this widget's subtree.
+///
+/// Values specified here are used for [TextField] and [SelectableText]
+/// properties that are not given an explicit non-null value.
+///
+/// {@tool snippet}
+///
+/// Here is an example of a text selection theme that applies a blue cursor
+/// color with light blue selection handles to the child text field.
+///
+/// ```dart
+/// TextSelectionTheme(
+///   data: TextSelectionThemeData(
+///     cursorColor: Colors.blue,
+///     selectionHandleColor: Colors.lightBlue,
+///   ),
+///   child: TextField(),
+/// ),
+/// ```
+/// {@end-tool}
+class TextSelectionTheme extends InheritedTheme {
+  /// Creates a text selection theme widget that specifies the text
+  /// selection properties for all widgets below it in the widget tree.
+  ///
+  /// The data argument must not be null.
+  const TextSelectionTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null), super(key: key, child: child);
+
+  /// The properties for descendant [TextField] and [SelectableText] widgets.
+  final TextSelectionThemeData data;
+
+  /// Returns the [data] from the closest [TextSelectionTheme] ancestor. If
+  /// there is no ancestor, it returns [ThemeData.textSelectionTheme].
+  /// Applications can assume that the returned value will not be null.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// TextSelectionThemeData theme = TextSelectionTheme.of(context);
+  /// ```
+  static TextSelectionThemeData of(BuildContext context) {
+    final TextSelectionTheme? selectionTheme = context.dependOnInheritedWidgetOfExactType<TextSelectionTheme>();
+    return selectionTheme?.data ?? Theme.of(context).textSelectionTheme;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return TextSelectionTheme(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(TextSelectionTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/text_selection_toolbar.dart b/lib/src/material/text_selection_toolbar.dart
new file mode 100644
index 0000000..5be39ec
--- /dev/null
+++ b/lib/src/material/text_selection_toolbar.dart
@@ -0,0 +1,751 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart' show listEquals;
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'debug.dart';
+import 'icon_button.dart';
+import 'icons.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+
+// Minimal padding from all edges of the selection toolbar to all edges of the
+// viewport.
+const double _kToolbarScreenPadding = 8.0;
+const double _kToolbarHeight = 44.0;
+
+/// The type for a Function that builds a toolbar's container with the given
+/// child.
+///
+/// See also:
+///
+///   * [TextSelectionToolbar.toolbarBuilder], which is of this type.
+typedef ToolbarBuilder = Widget Function(BuildContext context, Widget child);
+
+/// A fully-functional Material-style text selection toolbar.
+///
+/// Tries to position itself above [anchorAbove], but if it doesn't fit, then
+/// it positions itself below [anchorBelow].
+///
+/// If any children don't fit in the menu, an overflow menu will automatically
+/// be created.
+class TextSelectionToolbar extends StatelessWidget {
+  /// Creates an instance of TextSelectionToolbar.
+  const TextSelectionToolbar({
+    Key? key,
+    required this.anchorAbove,
+    required this.anchorBelow,
+    this.toolbarBuilder = _defaultToolbarBuilder,
+    required this.children,
+  }) : assert(children.length > 0),
+       super(key: key);
+
+  /// The focal point above which the toolbar attempts to position itself.
+  ///
+  /// If there is not enough room above before reaching the top of the screen,
+  /// then the toolbar will position itself below [anchorBelow].
+  final Offset anchorAbove;
+
+  /// The focal point below which the toolbar attempts to position itself, if it
+  /// doesn't fit above [anchorAbove].
+  final Offset anchorBelow;
+
+  /// The children that will be displayed in the text selection toolbar.
+  ///
+  /// Typically these are buttons.
+  ///
+  /// Must not be empty.
+  ///
+  /// See also:
+  ///   * [TextSelectionToolbarTextButton], which builds a default Material-
+  ///     style text selection toolbar text button.
+  final List<Widget> children;
+
+  /// Builds the toolbar container.
+  ///
+  /// Useful for customizing the high-level background of the toolbar. The given
+  /// child Widget will contain all of the [children].
+  final ToolbarBuilder toolbarBuilder;
+
+  // Build the default Android Material text selection menu toolbar.
+  static Widget _defaultToolbarBuilder(BuildContext context, Widget child) {
+    return _TextSelectionToolbarContainer(
+      child: child,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final double paddingTop = MediaQuery.of(context).padding.top
+        + _kToolbarScreenPadding;
+    final double availableHeight = anchorAbove.dy - paddingTop;
+    final bool fitsAbove = _kToolbarHeight <= availableHeight;
+    final Offset anchor = fitsAbove ? anchorAbove : anchorBelow;
+    final Offset localAnchor = Offset(
+      anchor.dx - _kToolbarScreenPadding,
+      anchor.dy - paddingTop,
+    );
+
+    return Padding(
+      padding: EdgeInsets.fromLTRB(
+        _kToolbarScreenPadding,
+        paddingTop,
+        _kToolbarScreenPadding,
+        _kToolbarScreenPadding,
+      ),
+      child: Stack(
+        children: <Widget>[
+          CustomSingleChildLayout(
+            delegate: _TextSelectionToolbarLayoutDelegate(
+              localAnchor,
+              fitsAbove,
+            ),
+            child: _TextSelectionToolbarOverflowable(
+              isAbove: fitsAbove,
+              toolbarBuilder: toolbarBuilder,
+              children: children,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+// Positions the toolbar at the given anchor, ensuring that it remains on
+// screen.
+class _TextSelectionToolbarLayoutDelegate extends SingleChildLayoutDelegate {
+  _TextSelectionToolbarLayoutDelegate(
+    this.anchor,
+    this.fitsAbove,
+  );
+
+  // Anchor position of the toolbar in global coordinates.
+  final Offset anchor;
+
+  // Whether the closed toolbar fits above the anchor position.
+  //
+  // If the closed toolbar doesn't fit, then the menu is rendered below the
+  // anchor position. It should never happen that the toolbar extends below the
+  // padded bottom of the screen.
+  final bool fitsAbove;
+
+  // Return the value that centers width as closely as possible to position
+  // while fitting inside of min and max.
+  static double _centerOn(double position, double width, double max) {
+    // If it overflows on the left, put it as far left as possible.
+    if (position - width / 2.0 < 0.0) {
+      return 0.0;
+    }
+
+    // If it overflows on the right, put it as far right as possible.
+    if (position + width / 2.0 > max) {
+      return max - width;
+    }
+
+    // Otherwise it fits while perfectly centered.
+    return position - width / 2.0;
+  }
+
+  @override
+  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
+    return constraints.loosen();
+  }
+
+  @override
+  Offset getPositionForChild(Size size, Size childSize) {
+    return Offset(
+      _centerOn(
+        anchor.dx,
+        childSize.width,
+        size.width,
+      ),
+      fitsAbove
+        ? math.max(0.0, anchor.dy - childSize.height)
+        : anchor.dy,
+    );
+  }
+
+  @override
+  bool shouldRelayout(_TextSelectionToolbarLayoutDelegate oldDelegate) {
+    return anchor != oldDelegate.anchor || fitsAbove != oldDelegate.fitsAbove;
+  }
+}
+
+// A toolbar containing the given children. If they overflow the width
+// available, then the overflowing children will be displayed in an overflow
+// menu.
+class _TextSelectionToolbarOverflowable extends StatefulWidget {
+  const _TextSelectionToolbarOverflowable({
+    Key? key,
+    required this.isAbove,
+    required this.toolbarBuilder,
+    required this.children,
+  }) : assert(children.length > 0),
+       super(key: key);
+
+  final List<Widget> children;
+
+  // When true, the toolbar fits above its anchor and will be positioned there.
+  final bool isAbove;
+
+  // Builds the toolbar that will be populated with the children and fit inside
+  // of the layout that adjusts to overflow.
+  final ToolbarBuilder toolbarBuilder;
+
+  @override
+  _TextSelectionToolbarOverflowableState createState() => _TextSelectionToolbarOverflowableState();
+}
+
+class _TextSelectionToolbarOverflowableState extends State<_TextSelectionToolbarOverflowable> with TickerProviderStateMixin {
+  // Whether or not the overflow menu is open. When it is closed, the menu
+  // items that don't overflow are shown. When it is open, only the overflowing
+  // menu items are shown.
+  bool _overflowOpen = false;
+
+  // The key for _TextSelectionToolbarTrailingEdgeAlign.
+  UniqueKey _containerKey = UniqueKey();
+
+  // Close the menu and reset layout calculations, as in when the menu has
+  // changed and saved values are no longer relevant. This should be called in
+  // setState or another context where a rebuild is happening.
+  void _reset() {
+    // Change _TextSelectionToolbarTrailingEdgeAlign's key when the menu changes in
+    // order to cause it to rebuild. This lets it recalculate its
+    // saved width for the new set of children, and it prevents AnimatedSize
+    // from animating the size change.
+    _containerKey = UniqueKey();
+    // If the menu items change, make sure the overflow menu is closed. This
+    // prevents getting into a broken state where _overflowOpen is true when
+    // there are not enough children to cause overflow.
+    _overflowOpen = false;
+  }
+
+  @override
+  void didUpdateWidget(_TextSelectionToolbarOverflowable oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    // If the children are changing at all, the current page should be reset.
+    if (!listEquals(widget.children, oldWidget.children)) {
+      _reset();
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterialLocalizations(context));
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+
+    return _TextSelectionToolbarTrailingEdgeAlign(
+      key: _containerKey,
+      overflowOpen: _overflowOpen,
+      textDirection: Directionality.of(context),
+      child: AnimatedSize(
+        vsync: this,
+        // This duration was eyeballed on a Pixel 2 emulator running Android
+        // API 28.
+        duration: const Duration(milliseconds: 140),
+        child: widget.toolbarBuilder(context, _TextSelectionToolbarItemsLayout(
+          isAbove: widget.isAbove,
+          overflowOpen: _overflowOpen,
+          children: <Widget>[
+            // TODO(justinmc): This overflow button should have its own slot in
+            // _TextSelectionToolbarItemsLayout separate from children, similar
+            // to how it's done in Cupertino's text selection menu.
+            // https://github.com/flutter/flutter/issues/69908
+            // The navButton that shows and hides the overflow menu is the
+            // first child.
+            _TextSelectionToolbarOverflowButton(
+              icon: Icon(_overflowOpen ? Icons.arrow_back : Icons.more_vert),
+              onPressed: () {
+                setState(() {
+                  _overflowOpen = !_overflowOpen;
+                });
+              },
+              tooltip: _overflowOpen
+                  ? localizations.backButtonTooltip
+                  : localizations.moreButtonTooltip,
+            ),
+            ...widget.children,
+          ],
+        )),
+      ),
+    );
+  }
+}
+
+// When the overflow menu is open, it tries to align its trailing edge to the
+// trailing edge of the closed menu. This widget handles this effect by
+// measuring and maintaining the width of the closed menu and aligning the child
+// to that side.
+class _TextSelectionToolbarTrailingEdgeAlign extends SingleChildRenderObjectWidget {
+  const _TextSelectionToolbarTrailingEdgeAlign({
+    Key? key,
+    required Widget child,
+    required this.overflowOpen,
+    required this.textDirection,
+  }) : assert(child != null),
+       assert(overflowOpen != null),
+       super(key: key, child: child);
+
+  final bool overflowOpen;
+  final TextDirection textDirection;
+
+  @override
+  _TextSelectionToolbarTrailingEdgeAlignRenderBox createRenderObject(BuildContext context) {
+    return _TextSelectionToolbarTrailingEdgeAlignRenderBox(
+      overflowOpen: overflowOpen,
+      textDirection: textDirection,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _TextSelectionToolbarTrailingEdgeAlignRenderBox renderObject) {
+    renderObject
+        ..overflowOpen = overflowOpen
+        ..textDirection = textDirection;
+  }
+}
+
+class _TextSelectionToolbarTrailingEdgeAlignRenderBox extends RenderProxyBox {
+  _TextSelectionToolbarTrailingEdgeAlignRenderBox({
+    required bool overflowOpen,
+    required TextDirection textDirection,
+  }) : _textDirection = textDirection,
+       _overflowOpen = overflowOpen,
+       super();
+
+  // The width of the menu when it was closed. This is used to achieve the
+  // behavior where the open menu aligns its trailing edge to the closed menu's
+  // trailing edge.
+  double? _closedWidth;
+
+  bool _overflowOpen;
+  bool get overflowOpen => _overflowOpen;
+  set overflowOpen(bool value) {
+    if (value == overflowOpen) {
+      return;
+    }
+    _overflowOpen = value;
+    markNeedsLayout();
+  }
+
+  TextDirection _textDirection;
+  TextDirection get textDirection => _textDirection;
+  set textDirection(TextDirection value) {
+    if (value == textDirection) {
+      return;
+    }
+    _textDirection = value;
+    markNeedsLayout();
+  }
+
+  @override
+  void performLayout() {
+    child!.layout(constraints.loosen(), parentUsesSize: true);
+
+    // Save the width when the menu is closed. If the menu changes, this width
+    // is invalid, so it's important that this RenderBox be recreated in that
+    // case. Currently, this is achieved by providing a new key to
+    // _TextSelectionToolbarTrailingEdgeAlign.
+    if (!overflowOpen && _closedWidth == null) {
+      _closedWidth = child!.size.width;
+    }
+
+    size = constraints.constrain(Size(
+      // If the open menu is wider than the closed menu, just use its own width
+      // and don't worry about aligning the trailing edges.
+      // _closedWidth is used even when the menu is closed to allow it to
+      // animate its size while keeping the same edge alignment.
+      _closedWidth == null || child!.size.width > _closedWidth! ? child!.size.width : _closedWidth!,
+      child!.size.height,
+    ));
+
+    // Set the offset in the parent data such that the child will be aligned to
+    // the trailing edge, depending on the text direction.
+    final ToolbarItemsParentData childParentData = child!.parentData! as ToolbarItemsParentData;
+    childParentData.offset = Offset(
+      textDirection == TextDirection.rtl ? 0.0 : size.width - child!.size.width,
+      0.0,
+    );
+  }
+
+  // Paint at the offset set in the parent data.
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final ToolbarItemsParentData childParentData = child!.parentData! as ToolbarItemsParentData;
+    context.paintChild(child!, childParentData.offset + offset);
+  }
+
+  // Include the parent data offset in the hit test.
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    // The x, y parameters have the top left of the node's box as the origin.
+    final ToolbarItemsParentData childParentData = child!.parentData! as ToolbarItemsParentData;
+    return result.addWithPaintOffset(
+      offset: childParentData.offset,
+      position: position,
+      hitTest: (BoxHitTestResult result, Offset transformed) {
+        assert(transformed == position - childParentData.offset);
+        return child!.hitTest(result, position: transformed);
+      },
+    );
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! ToolbarItemsParentData) {
+      child.parentData = ToolbarItemsParentData();
+    }
+  }
+
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) {
+    final ToolbarItemsParentData childParentData = child.parentData! as ToolbarItemsParentData;
+    transform.translate(childParentData.offset.dx, childParentData.offset.dy);
+    super.applyPaintTransform(child, transform);
+  }
+}
+
+// Renders the menu items in the correct positions in the menu and its overflow
+// submenu based on calculating which item would first overflow.
+class _TextSelectionToolbarItemsLayout extends MultiChildRenderObjectWidget {
+  _TextSelectionToolbarItemsLayout({
+    Key? key,
+    required this.isAbove,
+    required this.overflowOpen,
+    required List<Widget> children,
+  }) : assert(children != null),
+       assert(isAbove != null),
+       assert(overflowOpen != null),
+       super(key: key, children: children);
+
+  final bool isAbove;
+  final bool overflowOpen;
+
+  @override
+  _RenderTextSelectionToolbarItemsLayout createRenderObject(BuildContext context) {
+    return _RenderTextSelectionToolbarItemsLayout(
+      isAbove: isAbove,
+      overflowOpen: overflowOpen,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderTextSelectionToolbarItemsLayout renderObject) {
+    renderObject
+      ..isAbove = isAbove
+      ..overflowOpen = overflowOpen;
+  }
+
+  @override
+  _TextSelectionToolbarItemsLayoutElement createElement() => _TextSelectionToolbarItemsLayoutElement(this);
+}
+
+class _TextSelectionToolbarItemsLayoutElement extends MultiChildRenderObjectElement {
+  _TextSelectionToolbarItemsLayoutElement(
+    MultiChildRenderObjectWidget widget,
+  ) : super(widget);
+
+  static bool _shouldPaint(Element child) {
+    return (child.renderObject!.parentData! as ToolbarItemsParentData).shouldPaint;
+  }
+
+  @override
+  void debugVisitOnstageChildren(ElementVisitor visitor) {
+    children.where(_shouldPaint).forEach(visitor);
+  }
+}
+
+class _RenderTextSelectionToolbarItemsLayout extends RenderBox with ContainerRenderObjectMixin<RenderBox, ToolbarItemsParentData> {
+  _RenderTextSelectionToolbarItemsLayout({
+    required bool isAbove,
+    required bool overflowOpen,
+  }) : assert(overflowOpen != null),
+       assert(isAbove != null),
+       _isAbove = isAbove,
+       _overflowOpen = overflowOpen,
+       super();
+
+  // The index of the last item that doesn't overflow.
+  int _lastIndexThatFits = -1;
+
+  bool _isAbove;
+  bool get isAbove => _isAbove;
+  set isAbove(bool value) {
+    if (value == isAbove) {
+      return;
+    }
+    _isAbove = value;
+    markNeedsLayout();
+  }
+
+  bool _overflowOpen;
+  bool get overflowOpen => _overflowOpen;
+  set overflowOpen(bool value) {
+    if (value == overflowOpen) {
+      return;
+    }
+    _overflowOpen = value;
+    markNeedsLayout();
+  }
+
+  // Layout the necessary children, and figure out where the children first
+  // overflow, if at all.
+  void _layoutChildren() {
+    // When overflow is not open, the toolbar is always a specific height.
+    final BoxConstraints sizedConstraints = _overflowOpen
+      ? constraints
+      : BoxConstraints.loose(Size(
+          constraints.maxWidth,
+          _kToolbarHeight,
+        ));
+
+    int i = -1;
+    double width = 0.0;
+    visitChildren((RenderObject renderObjectChild) {
+      i++;
+
+      // No need to layout children inside the overflow menu when it's closed.
+      // The opposite is not true. It is necessary to layout the children that
+      // don't overflow when the overflow menu is open in order to calculate
+      // _lastIndexThatFits.
+      if (_lastIndexThatFits != -1 && !overflowOpen) {
+        return;
+      }
+
+      final RenderBox child = renderObjectChild as RenderBox;
+      child.layout(sizedConstraints.loosen(), parentUsesSize: true);
+      width += child.size.width;
+
+      if (width > sizedConstraints.maxWidth && _lastIndexThatFits == -1) {
+        _lastIndexThatFits = i - 1;
+      }
+    });
+
+    // If the last child overflows, but only because of the width of the
+    // overflow button, then just show it and hide the overflow button.
+    final RenderBox navButton = firstChild!;
+    if (_lastIndexThatFits != -1
+        && _lastIndexThatFits == childCount - 2
+        && width - navButton.size.width <= sizedConstraints.maxWidth) {
+      _lastIndexThatFits = -1;
+    }
+  }
+
+  // Returns true when the child should be painted, false otherwise.
+  bool _shouldPaintChild(RenderObject renderObjectChild, int index) {
+    // Paint the navButton when there is overflow.
+    if (renderObjectChild == firstChild) {
+      return _lastIndexThatFits != -1;
+    }
+
+    // If there is no overflow, all children besides the navButton are painted.
+    if (_lastIndexThatFits == -1) {
+      return true;
+    }
+
+    // When there is overflow, paint if the child is in the part of the menu
+    // that is currently open. Overflowing children are painted when the
+    // overflow menu is open, and the children that fit are painted when the
+    // overflow menu is closed.
+    return (index > _lastIndexThatFits) == overflowOpen;
+  }
+
+  // Decide which children will be painted, set their shouldPaint, and set the
+  // offset that painted children will be placed at.
+  void _placeChildren() {
+    int i = -1;
+    Size nextSize = const Size(0.0, 0.0);
+    double fitWidth = 0.0;
+    final RenderBox navButton = firstChild!;
+    double overflowHeight = overflowOpen && !isAbove ? navButton.size.height : 0.0;
+    visitChildren((RenderObject renderObjectChild) {
+      i++;
+
+      final RenderBox child = renderObjectChild as RenderBox;
+      final ToolbarItemsParentData childParentData = child.parentData! as ToolbarItemsParentData;
+
+      // Handle placing the navigation button after iterating all children.
+      if (renderObjectChild == navButton) {
+        return;
+      }
+
+      // There is no need to place children that won't be painted.
+      if (!_shouldPaintChild(renderObjectChild, i)) {
+        childParentData.shouldPaint = false;
+        return;
+      }
+      childParentData.shouldPaint = true;
+
+      if (!overflowOpen) {
+        childParentData.offset = Offset(fitWidth, 0.0);
+        fitWidth += child.size.width;
+        nextSize = Size(
+          fitWidth,
+          math.max(child.size.height, nextSize.height),
+        );
+      } else {
+        childParentData.offset = Offset(0.0, overflowHeight);
+        overflowHeight += child.size.height;
+        nextSize = Size(
+          math.max(child.size.width, nextSize.width),
+          overflowHeight,
+        );
+      }
+    });
+
+    // Place the navigation button if needed.
+    final ToolbarItemsParentData navButtonParentData = navButton.parentData! as ToolbarItemsParentData;
+    if (_shouldPaintChild(firstChild!, 0)) {
+      navButtonParentData.shouldPaint = true;
+      if (overflowOpen) {
+        navButtonParentData.offset = isAbove
+          ? Offset(0.0, overflowHeight)
+          : Offset.zero;
+        nextSize = Size(
+          nextSize.width,
+          isAbove ? nextSize.height + navButton.size.height : nextSize.height,
+        );
+      } else {
+        navButtonParentData.offset = Offset(fitWidth, 0.0);
+        nextSize = Size(nextSize.width + navButton.size.width, nextSize.height);
+      }
+    } else {
+      navButtonParentData.shouldPaint = false;
+    }
+
+    size = nextSize;
+  }
+
+  @override
+  void performLayout() {
+    _lastIndexThatFits = -1;
+    if (firstChild == null) {
+      size = constraints.smallest;
+      return;
+    }
+
+    _layoutChildren();
+    _placeChildren();
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    visitChildren((RenderObject renderObjectChild) {
+      final RenderBox child = renderObjectChild as RenderBox;
+      final ToolbarItemsParentData childParentData = child.parentData! as ToolbarItemsParentData;
+      if (!childParentData.shouldPaint) {
+        return;
+      }
+
+      context.paintChild(child, childParentData.offset + offset);
+    });
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! ToolbarItemsParentData) {
+      child.parentData = ToolbarItemsParentData();
+    }
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    // The x, y parameters have the top left of the node's box as the origin.
+    RenderBox? child = lastChild;
+    while (child != null) {
+      final ToolbarItemsParentData childParentData = child.parentData! as ToolbarItemsParentData;
+
+      // Don't hit test children aren't shown.
+      if (!childParentData.shouldPaint) {
+        child = childParentData.previousSibling;
+        continue;
+      }
+
+      final bool isHit = result.addWithPaintOffset(
+        offset: childParentData.offset,
+        position: position,
+        hitTest: (BoxHitTestResult result, Offset transformed) {
+          assert(transformed == position - childParentData.offset);
+          return child!.hitTest(result, position: transformed);
+        },
+      );
+      if (isHit)
+        return true;
+      child = childParentData.previousSibling;
+    }
+    return false;
+  }
+
+  // Visit only the children that should be painted.
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    visitChildren((RenderObject renderObjectChild) {
+      final RenderBox child = renderObjectChild as RenderBox;
+      final ToolbarItemsParentData childParentData = child.parentData! as ToolbarItemsParentData;
+      if (childParentData.shouldPaint) {
+        visitor(renderObjectChild);
+      }
+    });
+  }
+}
+
+// The Material-styled toolbar outline. Fill it with any widgets you want. No
+// overflow ability.
+class _TextSelectionToolbarContainer extends StatelessWidget {
+  const _TextSelectionToolbarContainer({
+    Key? key,
+    required this.child,
+  }) : super(key: key);
+
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    return Material(
+      // This value was eyeballed to match the native text selection menu on
+      // a Pixel 2 running Android 10.
+      borderRadius: const BorderRadius.all(Radius.circular(7.0)),
+      clipBehavior: Clip.antiAlias,
+      elevation: 1.0,
+      type: MaterialType.card,
+      child: child,
+    );
+  }
+}
+
+// A button styled like a Material native Android text selection overflow menu
+// forward and back controls.
+class _TextSelectionToolbarOverflowButton extends StatelessWidget {
+  const _TextSelectionToolbarOverflowButton({
+    Key? key,
+    required this.icon,
+    this.onPressed,
+    this.tooltip,
+  }) : super(key: key);
+
+  final Icon icon;
+  final VoidCallback? onPressed;
+  final String? tooltip;
+
+  @override
+  Widget build(BuildContext context) {
+    return Material(
+      type: MaterialType.card,
+      color: const Color(0x00000000),
+      child: IconButton(
+        // TODO(justinmc): This should be an AnimatedIcon, but
+        // AnimatedIcons doesn't yet support arrow_back to more_vert.
+        // https://github.com/flutter/flutter/issues/51209
+        icon: icon,
+        onPressed: onPressed,
+        tooltip: tooltip,
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/text_selection_toolbar_text_button.dart b/lib/src/material/text_selection_toolbar_text_button.dart
new file mode 100644
index 0000000..0393e8c
--- /dev/null
+++ b/lib/src/material/text_selection_toolbar_text_button.dart
@@ -0,0 +1,121 @@
+// Copyright 2014 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.
+
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'constants.dart';
+import 'text_button.dart';
+import 'theme.dart';
+
+enum _TextSelectionToolbarItemPosition {
+  /// The first item among multiple in the menu.
+  first,
+
+  /// One of several items, not the first or last.
+  middle,
+
+  /// The last item among multiple in the menu.
+  last,
+
+  /// The only item in the menu.
+  only,
+}
+
+/// A button styled like a Material native Android text selection menu button.
+class TextSelectionToolbarTextButton extends StatelessWidget {
+  /// Creates an instance of TextSelectionToolbarTextButton.
+  const TextSelectionToolbarTextButton({
+    Key? key,
+    required this.child,
+    required this.padding,
+    this.onPressed,
+  }) : super(key: key);
+
+  // These values were eyeballed to match the native text selection menu on a
+  // Pixel 2 running Android 10.
+  static const double _kMiddlePadding = 9.5;
+  static const double _kEndPadding = 14.5;
+
+  /// The child of this button.
+  ///
+  /// Usually a [Text].
+  final Widget child;
+
+  /// Called when this button is pressed.
+  final VoidCallback? onPressed;
+
+  /// The padding between the button's edge and its child.
+  ///
+  /// In a standard Material [TextSelectionToolbar], the padding depends on the
+  /// button's position within the toolbar.
+  ///
+  /// See also:
+  ///
+  ///  * [getPadding], which calculates the standard padding based on the
+  ///    button's position.
+  ///  * [ButtonStyle.padding], which is where this padding is applied.
+  final EdgeInsets padding;
+
+  /// Returns the standard padding for a button at index out of a total number
+  /// of buttons.
+  ///
+  /// Standard Material [TextSelectionToolbar]s have buttons with different
+  /// padding depending on their position in the toolbar.
+  static EdgeInsets getPadding(int index, int total) {
+    assert(total > 0 && index >= 0 && index < total);
+    final _TextSelectionToolbarItemPosition position = _getPosition(index, total);
+    return EdgeInsets.only(
+      left: _getLeftPadding(position),
+      right: _getRightPadding(position),
+    );
+  }
+
+  static double _getLeftPadding(_TextSelectionToolbarItemPosition position) {
+    if (position == _TextSelectionToolbarItemPosition.first
+        || position == _TextSelectionToolbarItemPosition.only) {
+      return _kEndPadding;
+    }
+    return _kMiddlePadding;
+  }
+
+  static double _getRightPadding(_TextSelectionToolbarItemPosition position) {
+    if (position == _TextSelectionToolbarItemPosition.last
+        || position == _TextSelectionToolbarItemPosition.only) {
+      return _kEndPadding;
+    }
+    return _kMiddlePadding;
+  }
+
+  static _TextSelectionToolbarItemPosition _getPosition(int index, int total) {
+    if (index == 0) {
+      return total == 1
+          ? _TextSelectionToolbarItemPosition.only
+          : _TextSelectionToolbarItemPosition.first;
+    }
+    if (index == total - 1) {
+      return _TextSelectionToolbarItemPosition.last;
+    }
+    return _TextSelectionToolbarItemPosition.middle;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // TODO(hansmuller): Should be colorScheme.onSurface
+    final ThemeData theme = Theme.of(context);
+    final bool isDark = theme.colorScheme.brightness == Brightness.dark;
+    final Color primary = isDark ? Colors.white : Colors.black87;
+
+    return TextButton(
+      style: TextButton.styleFrom(
+        primary: primary,
+        shape: const RoundedRectangleBorder(),
+        minimumSize: const Size(kMinInteractiveDimension, kMinInteractiveDimension),
+        padding: padding,
+      ),
+      onPressed: onPressed,
+      child: child,
+    );
+  }
+}
diff --git a/lib/src/material/text_theme.dart b/lib/src/material/text_theme.dart
new file mode 100644
index 0000000..c61fb59
--- /dev/null
+++ b/lib/src/material/text_theme.dart
@@ -0,0 +1,766 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart';
+
+import 'typography.dart';
+
+/// Material design text theme.
+///
+/// Definitions for the various typographical styles found in Material Design
+/// (e.g., button, caption). Rather than creating a [TextTheme] directly,
+/// you can obtain an instance as [Typography.black] or [Typography.white].
+///
+/// To obtain the current text theme, call [Theme.of] with the current
+/// [BuildContext] and read the [ThemeData.textTheme] property.
+///
+/// The names of the TextTheme properties match this table from the
+/// [Material Design spec](https://material.io/design/typography/the-type-system.html#type-scale)
+/// with two exceptions: the styles called H1-H6 in the spec are
+/// headline1-headline6 in the API, and body1,body2 are called
+/// bodyText1 and bodyText2.
+///
+/// ![](https://storage.googleapis.com/spec-host-backup/mio-design%2Fassets%2F1W8kyGVruuG_O8psvyiOaCf1lLFIMzB-N%2Ftypesystem-typescale.png)
+///
+/// ## Migrating from the 2014 names
+///
+/// The Material Design typography scheme was significantly changed in the
+/// current (2018) version of the specification
+/// ([https://material.io/design/typography](https://material.io/design/typography)).
+///
+/// The 2018 spec has thirteen text styles:
+/// ```
+/// NAME         SIZE  WEIGHT  SPACING
+/// headline1    96.0  light   -1.5
+/// headline2    60.0  light   -0.5
+/// headline3    48.0  regular  0.0
+/// headline4    34.0  regular  0.25
+/// headline5    24.0  regular  0.0
+/// headline6    20.0  medium   0.15
+/// subtitle1    16.0  regular  0.15
+/// subtitle2    14.0  medium   0.1
+/// body1        16.0  regular  0.5   (bodyText1)
+/// body2        14.0  regular  0.25  (bodyText2)
+/// button       14.0  medium   1.25
+/// caption      12.0  regular  0.4
+/// overline     10.0  regular  1.5
+/// ```
+///
+/// ...where "light" is `FontWeight.w300`, "regular" is `FontWeight.w400` and
+/// "medium" is `FontWeight.w500`.
+///
+/// The [TextTheme] API was originally based on the original material (2014)
+/// design spec, which used different text style names. For backwards
+/// compatibility's sake, this API continues to expose the old names. The table
+/// below should help with understanding the mapping of the API's old names and
+/// the new names (those in terms of the 2018 material specification).
+///
+/// Each of the [TextTheme] text styles corresponds to one of the
+/// styles from 2018 spec. By default, the font sizes, font weights
+/// and letter spacings have not changed from their original,
+/// 2014, values.
+///
+/// ```
+/// NAME       SIZE   WEIGHT   SPACING  2018 NAME
+/// display4   112.0  thin     0.0      headline1
+/// display3   56.0   normal   0.0      headline2
+/// display2   45.0   normal   0.0      headline3
+/// display1   34.0   normal   0.0      headline4
+/// headline   24.0   normal   0.0      headline5
+/// title      20.0   medium   0.0      headline6
+/// subhead    16.0   normal   0.0      subtitle1
+/// body2      14.0   medium   0.0      body1 (bodyText1)
+/// body1      14.0   normal   0.0      body2 (bodyText2)
+/// caption    12.0   normal   0.0      caption
+/// button     14.0   medium   0.0      button
+/// subtitle   14.0   medium   0.0      subtitle2
+/// overline   10.0   normal   0.0      overline
+/// ```
+///
+/// Where "thin" is `FontWeight.w100`, "normal" is `FontWeight.w400` and
+/// "medium" is `FontWeight.w500`. Letter spacing for all of the original
+/// text styles was 0.0.
+///
+/// The old names are deprecated in this API.
+///
+/// Since the names `body1` and `body2` are used in both specifications but with
+/// different meanings, the API uses the terms `bodyText1` and `bodyText2` for
+/// the new API.
+///
+/// To configure a [Theme] for the new sizes, weights, and letter spacings,
+/// initialize its [ThemeData.typography] value using [Typography.material2018].
+///
+/// See also:
+///
+///  * [Typography], the class that generates [TextTheme]s appropriate for a platform.
+///  * [Theme], for other aspects of a material design application that can be
+///    globally adjusted, such as the color scheme.
+///  * <https://material.io/design/typography/>
+@immutable
+class TextTheme with Diagnosticable {
+  /// Creates a text theme that uses the given values.
+  ///
+  /// Rather than creating a new text theme, consider using [Typography.black]
+  /// or [Typography.white], which implement the typography styles in the
+  /// material design specification:
+  ///
+  /// <https://material.io/design/typography/#type-scale>
+  ///
+  /// If you do decide to create your own text theme, consider using one of
+  /// those predefined themes as a starting point for [copyWith] or [apply].
+  const TextTheme({
+    TextStyle? headline1,
+    TextStyle? headline2,
+    TextStyle? headline3,
+    TextStyle? headline4,
+    TextStyle? headline5,
+    TextStyle? headline6,
+    TextStyle? subtitle1,
+    TextStyle? subtitle2,
+    TextStyle? bodyText1,
+    TextStyle? bodyText2,
+    this.caption,
+    this.button,
+    this.overline,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is headline1. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? display4,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is headline2. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? display3,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is headline3. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? display2,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is headline4. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? display1,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is headline5. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? headline,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is headline6. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? title,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is subtitle1. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? subhead,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is subtitle2. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? subtitle,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is bodyText1. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? body2,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is bodyText2. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? body1,
+  }) : assert(
+         (headline1 == null && headline2 == null && headline3 == null && headline4 == null && headline5 == null && headline6 == null &&
+          subtitle1 == null && subtitle2 == null &&
+          bodyText1 == null && bodyText2 == null) ||
+         (display4 == null && display3 == null && display2 == null && display1 == null && headline == null && title == null &&
+          subhead == null && subtitle == null &&
+          body2 == null && body1 == null), 'Cannot mix 2014 and 2018 terms in call to TextTheme() constructor.'),
+       headline1 = headline1 ?? display4,
+       headline2 = headline2 ?? display3,
+       headline3 = headline3 ?? display2,
+       headline4 = headline4 ?? display1,
+       headline5 = headline5 ?? headline,
+       headline6 = headline6 ?? title,
+       subtitle1 = subtitle1 ?? subhead,
+       subtitle2 = subtitle2 ?? subtitle,
+       bodyText1 = bodyText1 ?? body2,
+       bodyText2 = bodyText2 ?? body1;
+
+  /// Extremely large text.
+  final TextStyle? headline1;
+
+  /// Very, very large text.
+  ///
+  /// Used for the date in the dialog shown by [showDatePicker].
+  final TextStyle? headline2;
+
+  /// Very large text.
+  final TextStyle? headline3;
+
+  /// Large text.
+  final TextStyle? headline4;
+
+  /// Used for large text in dialogs (e.g., the month and year in the dialog
+  /// shown by [showDatePicker]).
+  final TextStyle? headline5;
+
+  /// Used for the primary text in app bars and dialogs (e.g., [AppBar.title]
+  /// and [AlertDialog.title]).
+  final TextStyle? headline6;
+
+  /// Used for the primary text in lists (e.g., [ListTile.title]).
+  final TextStyle? subtitle1;
+
+  /// For medium emphasis text that's a little smaller than [subtitle1].
+  final TextStyle? subtitle2;
+
+  /// Used for emphasizing text that would otherwise be [bodyText2].
+  final TextStyle? bodyText1;
+
+  /// The default text style for [Material].
+  final TextStyle? bodyText2;
+
+  /// Used for auxiliary text associated with images.
+  final TextStyle? caption;
+
+  /// Used for text on [ElevatedButton], [TextButton] and [OutlinedButton].
+  final TextStyle? button;
+
+  /// The smallest style.
+  ///
+  /// Typically used for captions or to introduce a (larger) headline.
+  final TextStyle? overline;
+
+  /// Extremely large text.
+  ///
+  /// This was the name used in the material design 2014 specification. The new
+  /// specification calls this [headline1].
+  @Deprecated(
+    'This is the term used in the 2014 version of material design. The modern term is headline1. '
+    'This feature was deprecated after v1.13.8.'
+  )
+  TextStyle? get display4 => headline1;
+
+  /// Very, very large text.
+  ///
+  /// This was the name used in the material design 2014 specification. The new
+  /// specification calls this [headline2].
+  @Deprecated(
+    'This is the term used in the 2014 version of material design. The modern term is headline2. '
+    'This feature was deprecated after v1.13.8.'
+  )
+  TextStyle? get display3 => headline2;
+
+  /// Very large text.
+  ///
+  /// This was the name used in the material design 2014 specification. The new
+  /// specification calls this [headline3].
+  @Deprecated(
+    'This is the term used in the 2014 version of material design. The modern term is headline3. '
+    'This feature was deprecated after v1.13.8.'
+  )
+  TextStyle? get display2 => headline3;
+
+  /// Large text.
+  ///
+  /// This was the name used in the material design 2014 specification. The new
+  /// specification calls this [headline4].
+  @Deprecated(
+    'This is the term used in the 2014 version of material design. The modern term is headline4. '
+    'This feature was deprecated after v1.13.8.'
+  )
+  TextStyle? get display1 => headline4;
+
+  /// Used for large text in dialogs.
+  ///
+  /// This was the name used in the material design 2014 specification. The new
+  /// specification calls this [headline5].
+  @Deprecated(
+    'This is the term used in the 2014 version of material design. The modern term is headline5. '
+    'This feature was deprecated after v1.13.8.'
+  )
+  TextStyle? get headline => headline5;
+
+  /// Used for the primary text in app bars and dialogs.
+  ///
+  /// This was the name used in the material design 2014 specification. The new
+  /// specification calls this [headline6].
+  @Deprecated(
+    'This is the term used in the 2014 version of material design. The modern term is headline6. '
+    'This feature was deprecated after v1.13.8.'
+  )
+  TextStyle? get title => headline6;
+
+  /// Used for the primary text in lists (e.g., [ListTile.title]).
+  ///
+  /// This was the name used in the material design 2014 specification. The new
+  /// specification calls this [subtitle1].
+  @Deprecated(
+    'This is the term used in the 2014 version of material design. The modern term is subtitle1. '
+    'This feature was deprecated after v1.13.8.'
+  )
+  TextStyle? get subhead => subtitle1;
+
+  /// For medium emphasis text that's a little smaller than [subhead].
+  ///
+  /// This was the name used in the material design 2014 specification. The new
+  /// specification calls this [subtitle2].
+  @Deprecated(
+    'This is the term used in the 2014 version of material design. The modern term is subtitle2. '
+    'This feature was deprecated after v1.13.8.'
+  )
+  TextStyle? get subtitle => subtitle2;
+
+  /// Used for emphasizing text that would otherwise be [body1].
+  ///
+  /// This was the name used in the material design 2014 specification. The new
+  /// specification calls this `body1`, and it is exposed in this API as
+  /// [bodyText1].
+  @Deprecated(
+    'This is the term used in the 2014 version of material design. The modern term is bodyText1. '
+    'This feature was deprecated after v1.13.8.'
+  )
+  TextStyle? get body2 => bodyText1;
+
+  /// Used for the default text style for [Material].
+  ///
+  /// This was the name used in the material design 2014 specification. The new
+  /// specification calls this `body2`, and it is exposed in this API as
+  /// [bodyText2].
+  @Deprecated(
+    'This is the term used in the 2014 version of material design. The modern term is bodyText2. '
+    'This feature was deprecated after v1.13.8.'
+  )
+  TextStyle? get body1 => bodyText2;
+
+
+  /// Creates a copy of this text theme but with the given fields replaced with
+  /// the new values.
+  ///
+  /// Consider using [Typography.black] or [Typography.white], which implement
+  /// the typography styles in the material design specification, as a starting
+  /// point.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// /// A Widget that sets the ambient theme's title text color for its
+  /// /// descendants, while leaving other ambient theme attributes alone.
+  /// class TitleColorThemeCopy extends StatelessWidget {
+  ///   TitleColorThemeCopy({Key? key, required this.child, required this.titleColor}) : super(key: key);
+  ///
+  ///   final Color titleColor;
+  ///   final Widget child;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     final ThemeData theme = Theme.of(context);
+  ///     return Theme(
+  ///       data: theme.copyWith(
+  ///         textTheme: theme.textTheme.copyWith(
+  ///           headline6: theme.textTheme.headline6!.copyWith(
+  ///             color: titleColor,
+  ///           ),
+  ///         ),
+  ///       ),
+  ///       child: child,
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [merge] is used instead of [copyWith] when you want to merge all
+  ///    of the fields of a TextTheme instead of individual fields.
+  TextTheme copyWith({
+    TextStyle? headline1,
+    TextStyle? headline2,
+    TextStyle? headline3,
+    TextStyle? headline4,
+    TextStyle? headline5,
+    TextStyle? headline6,
+    TextStyle? subtitle1,
+    TextStyle? subtitle2,
+    TextStyle? bodyText1,
+    TextStyle? bodyText2,
+    TextStyle? caption,
+    TextStyle? button,
+    TextStyle? overline,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is headline1. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? display4,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is headline2. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? display3,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is headline3. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? display2,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is headline4. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? display1,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is headline5. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? headline,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is headline6. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? title,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is subtitle1. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? subhead,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is subtitle2. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? subtitle,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is bodyText1. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? body2,
+    @Deprecated(
+      'This is the term used in the 2014 version of material design. The modern term is bodyText2. '
+      'This feature was deprecated after v1.13.8.'
+    )
+    TextStyle? body1,
+  }) {
+    assert(
+      (headline1 == null && headline2 == null && headline3 == null && headline4 == null && headline5 == null && headline6 == null &&
+       subtitle1 == null && subtitle2 == null &&
+       bodyText1 == null && bodyText2 == null) ||
+      (display4 == null && display3 == null && display2 == null && display1 == null && headline == null && title == null &&
+       subhead == null && subtitle == null &&
+       body2 == null && body1 == null), 'Cannot mix 2014 and 2018 terms in call to TextTheme.copyWith().');
+    return TextTheme(
+      headline1: headline1 ?? display4 ?? this.headline1,
+      headline2: headline2 ?? display3 ?? this.headline2,
+      headline3: headline3 ?? display2 ?? this.headline3,
+      headline4: headline4 ?? display1 ?? this.headline4,
+      headline5: headline5 ?? headline ?? this.headline5,
+      headline6: headline6 ?? title ?? this.headline6,
+      subtitle1: subtitle1 ?? subhead ?? this.subtitle1,
+      subtitle2: subtitle2 ?? subtitle ?? this.subtitle2,
+      bodyText1: bodyText1 ?? body2 ?? this.bodyText1,
+      bodyText2: bodyText2 ?? body1 ?? this.bodyText2,
+      caption: caption ?? this.caption,
+      button: button ?? this.button,
+      overline: overline ?? this.overline,
+    );
+  }
+
+  /// Creates a new [TextTheme] where each text style from this object has been
+  /// merged with the matching text style from the `other` object.
+  ///
+  /// The merging is done by calling [TextStyle.merge] on each respective pair
+  /// of text styles from this and the [other] text themes and is subject to
+  /// the value of [TextStyle.inherit] flag. For more details, see the
+  /// documentation on [TextStyle.merge] and [TextStyle.inherit].
+  ///
+  /// If this theme, or the `other` theme has members that are null, then the
+  /// non-null one (if any) is used. If the `other` theme is itself null, then
+  /// this [TextTheme] is returned unchanged. If values in both are set, then
+  /// the values are merged using [TextStyle.merge].
+  ///
+  /// This is particularly useful if one [TextTheme] defines one set of
+  /// properties and another defines a different set, e.g. having colors
+  /// defined in one text theme and font sizes in another, or when one
+  /// [TextTheme] has only some fields defined, and you want to define the rest
+  /// by merging it with a default theme.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// /// A Widget that sets the ambient theme's title text color for its
+  /// /// descendants, while leaving other ambient theme attributes alone.
+  /// class TitleColorTheme extends StatelessWidget {
+  ///   TitleColorTheme({Key? key, required this.child, required this.titleColor}) : super(key: key);
+  ///
+  ///   final Color titleColor;
+  ///   final Widget child;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     ThemeData theme = Theme.of(context);
+  ///     // This partialTheme is incomplete: it only has the title style
+  ///     // defined. Just replacing theme.textTheme with partialTheme would
+  ///     // set the title, but everything else would be null. This isn't very
+  ///     // useful, so merge it with the existing theme to keep all of the
+  ///     // preexisting definitions for the other styles.
+  ///     TextTheme partialTheme = TextTheme(headline6: TextStyle(color: titleColor));
+  ///     theme = theme.copyWith(textTheme: theme.textTheme.merge(partialTheme));
+  ///     return Theme(data: theme, child: child);
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [copyWith] is used instead of [merge] when you wish to override
+  ///    individual fields in the [TextTheme] instead of merging all of the
+  ///    fields of two [TextTheme]s.
+  TextTheme merge(TextTheme? other) {
+    if (other == null)
+      return this;
+    return copyWith(
+      headline1: headline1?.merge(other.headline1) ?? other.headline1,
+      headline2: headline2?.merge(other.headline2) ?? other.headline2,
+      headline3: headline3?.merge(other.headline3) ?? other.headline3,
+      headline4: headline4?.merge(other.headline4) ?? other.headline4,
+      headline5: headline5?.merge(other.headline5) ?? other.headline5,
+      headline6: headline6?.merge(other.headline6) ?? other.headline6,
+      subtitle1: subtitle1?.merge(other.subtitle1) ?? other.subtitle1,
+      subtitle2: subtitle2?.merge(other.subtitle2) ?? other.subtitle2,
+      bodyText1: bodyText1?.merge(other.bodyText1) ?? other.bodyText1,
+      bodyText2: bodyText2?.merge(other.bodyText2) ?? other.bodyText2,
+      caption: caption?.merge(other.caption) ?? other.caption,
+      button: button?.merge(other.button) ?? other.button,
+      overline: overline?.merge(other.overline) ?? other.overline,
+    );
+  }
+
+  /// Creates a copy of this text theme but with the given field replaced in
+  /// each of the individual text styles.
+  ///
+  /// The `displayColor` is applied to [headline4], [headline3], [headline2],
+  /// [headline1], and [caption]. The `bodyColor` is applied to the remaining
+  /// text styles.
+  ///
+  /// Consider using [Typography.black] or [Typography.white], which implement
+  /// the typography styles in the material design specification, as a starting
+  /// point.
+  TextTheme apply({
+    String? fontFamily,
+    double fontSizeFactor = 1.0,
+    double fontSizeDelta = 0.0,
+    Color? displayColor,
+    Color? bodyColor,
+    TextDecoration? decoration,
+    Color? decorationColor,
+    TextDecorationStyle? decorationStyle,
+  }) {
+    return TextTheme(
+      headline1: headline1?.apply(
+        color: displayColor,
+        decoration: decoration,
+        decorationColor: decorationColor,
+        decorationStyle: decorationStyle,
+        fontFamily: fontFamily,
+        fontSizeFactor: fontSizeFactor,
+        fontSizeDelta: fontSizeDelta,
+      ),
+      headline2: headline2?.apply(
+        color: displayColor,
+        decoration: decoration,
+        decorationColor: decorationColor,
+        decorationStyle: decorationStyle,
+        fontFamily: fontFamily,
+        fontSizeFactor: fontSizeFactor,
+        fontSizeDelta: fontSizeDelta,
+      ),
+      headline3: headline3?.apply(
+        color: displayColor,
+        decoration: decoration,
+        decorationColor: decorationColor,
+        decorationStyle: decorationStyle,
+        fontFamily: fontFamily,
+        fontSizeFactor: fontSizeFactor,
+        fontSizeDelta: fontSizeDelta,
+      ),
+      headline4: headline4?.apply(
+        color: displayColor,
+        decoration: decoration,
+        decorationColor: decorationColor,
+        decorationStyle: decorationStyle,
+        fontFamily: fontFamily,
+        fontSizeFactor: fontSizeFactor,
+        fontSizeDelta: fontSizeDelta,
+      ),
+      headline5: headline5?.apply(
+        color: bodyColor,
+        decoration: decoration,
+        decorationColor: decorationColor,
+        decorationStyle: decorationStyle,
+        fontFamily: fontFamily,
+        fontSizeFactor: fontSizeFactor,
+        fontSizeDelta: fontSizeDelta,
+      ),
+      headline6: headline6?.apply(
+        color: bodyColor,
+        decoration: decoration,
+        decorationColor: decorationColor,
+        decorationStyle: decorationStyle,
+        fontFamily: fontFamily,
+        fontSizeFactor: fontSizeFactor,
+        fontSizeDelta: fontSizeDelta,
+      ),
+      subtitle1: subtitle1?.apply(
+        color: bodyColor,
+        decoration: decoration,
+        decorationColor: decorationColor,
+        decorationStyle: decorationStyle,
+        fontFamily: fontFamily,
+        fontSizeFactor: fontSizeFactor,
+        fontSizeDelta: fontSizeDelta,
+      ),
+      subtitle2: subtitle2?.apply(
+        color: bodyColor,
+        decoration: decoration,
+        decorationColor: decorationColor,
+        decorationStyle: decorationStyle,
+        fontFamily: fontFamily,
+        fontSizeFactor: fontSizeFactor,
+        fontSizeDelta: fontSizeDelta,
+      ),
+      bodyText1: bodyText1?.apply(
+        color: bodyColor,
+        decoration: decoration,
+        decorationColor: decorationColor,
+        decorationStyle: decorationStyle,
+        fontFamily: fontFamily,
+        fontSizeFactor: fontSizeFactor,
+        fontSizeDelta: fontSizeDelta,
+      ),
+      bodyText2: bodyText2?.apply(
+        color: bodyColor,
+        decoration: decoration,
+        decorationColor: decorationColor,
+        decorationStyle: decorationStyle,
+        fontFamily: fontFamily,
+        fontSizeFactor: fontSizeFactor,
+        fontSizeDelta: fontSizeDelta,
+      ),
+      caption: caption?.apply(
+        color: displayColor,
+        decoration: decoration,
+        decorationColor: decorationColor,
+        decorationStyle: decorationStyle,
+        fontFamily: fontFamily,
+        fontSizeFactor: fontSizeFactor,
+        fontSizeDelta: fontSizeDelta,
+      ),
+      button: button?.apply(
+        color: bodyColor,
+        decoration: decoration,
+        decorationColor: decorationColor,
+        decorationStyle: decorationStyle,
+        fontFamily: fontFamily,
+        fontSizeFactor: fontSizeFactor,
+        fontSizeDelta: fontSizeDelta,
+      ),
+      overline: overline?.apply(
+        color: bodyColor,
+        decoration: decoration,
+        decorationColor: decorationColor,
+        decorationStyle: decorationStyle,
+        fontFamily: fontFamily,
+        fontSizeFactor: fontSizeFactor,
+        fontSizeDelta: fontSizeDelta,
+      ),
+    );
+  }
+
+  /// Linearly interpolate between two text themes.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static TextTheme lerp(TextTheme? a, TextTheme? b, double t) {
+    assert(t != null);
+    return TextTheme(
+      headline1: TextStyle.lerp(a?.headline1, b?.headline1, t),
+      headline2: TextStyle.lerp(a?.headline2, b?.headline2, t),
+      headline3: TextStyle.lerp(a?.headline3, b?.headline3, t),
+      headline4: TextStyle.lerp(a?.headline4, b?.headline4, t),
+      headline5: TextStyle.lerp(a?.headline5, b?.headline5, t),
+      headline6: TextStyle.lerp(a?.headline6, b?.headline6, t),
+      subtitle1: TextStyle.lerp(a?.subtitle1, b?.subtitle1, t),
+      subtitle2: TextStyle.lerp(a?.subtitle2, b?.subtitle2, t),
+      bodyText1: TextStyle.lerp(a?.bodyText1, b?.bodyText1, t),
+      bodyText2: TextStyle.lerp(a?.bodyText2, b?.bodyText2, t),
+      caption: TextStyle.lerp(a?.caption, b?.caption, t),
+      button: TextStyle.lerp(a?.button, b?.button, t),
+      overline: TextStyle.lerp(a?.overline, b?.overline, t),
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is TextTheme
+      && headline1 == other.headline1
+      && headline2 == other.headline2
+      && headline3 == other.headline3
+      && headline4 == other.headline4
+      && headline5 == other.headline5
+      && headline6 == other.headline6
+      && subtitle1 == other.subtitle1
+      && subtitle2 == other.subtitle2
+      && bodyText1 == other.bodyText1
+      && bodyText2 == other.bodyText2
+      && caption == other.caption
+      && button == other.button
+      && overline == other.overline;
+  }
+
+  @override
+  int get hashCode {
+    // The hashValues() function supports up to 20 arguments.
+    return hashValues(
+      headline1,
+      headline2,
+      headline3,
+      headline4,
+      headline5,
+      headline6,
+      subtitle1,
+      subtitle2,
+      bodyText1,
+      bodyText2,
+      caption,
+      button,
+      overline,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    final TextTheme defaultTheme = Typography.material2018(platform: defaultTargetPlatform).black;
+    properties.add(DiagnosticsProperty<TextStyle>('headline1', headline1, defaultValue: defaultTheme.headline1));
+    properties.add(DiagnosticsProperty<TextStyle>('headline2', headline2, defaultValue: defaultTheme.headline2));
+    properties.add(DiagnosticsProperty<TextStyle>('headline3', headline3, defaultValue: defaultTheme.headline3));
+    properties.add(DiagnosticsProperty<TextStyle>('headline4', headline4, defaultValue: defaultTheme.headline4));
+    properties.add(DiagnosticsProperty<TextStyle>('headline5', headline5, defaultValue: defaultTheme.headline5));
+    properties.add(DiagnosticsProperty<TextStyle>('headline6', headline6, defaultValue: defaultTheme.headline6));
+    properties.add(DiagnosticsProperty<TextStyle>('subtitle1', subtitle1, defaultValue: defaultTheme.subtitle1));
+    properties.add(DiagnosticsProperty<TextStyle>('subtitle2', subtitle2, defaultValue: defaultTheme.subtitle2));
+    properties.add(DiagnosticsProperty<TextStyle>('bodyText1', bodyText1, defaultValue: defaultTheme.bodyText1));
+    properties.add(DiagnosticsProperty<TextStyle>('bodyText2', bodyText2, defaultValue: defaultTheme.bodyText2));
+    properties.add(DiagnosticsProperty<TextStyle>('caption', caption, defaultValue: defaultTheme.caption));
+    properties.add(DiagnosticsProperty<TextStyle>('button', button, defaultValue: defaultTheme.button));
+    properties.add(DiagnosticsProperty<TextStyle>('overline', overline, defaultValue: defaultTheme.overline));
+  }
+}
diff --git a/lib/src/material/theme.dart b/lib/src/material/theme.dart
new file mode 100644
index 0000000..57308dd
--- /dev/null
+++ b/lib/src/material/theme.dart
@@ -0,0 +1,242 @@
+// Copyright 2014 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.
+
+import 'package:flute/cupertino.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'material_localizations.dart';
+import 'theme_data.dart';
+import 'typography.dart';
+
+export 'theme_data.dart' show Brightness, ThemeData;
+
+/// The duration over which theme changes animate by default.
+const Duration kThemeAnimationDuration = Duration(milliseconds: 200);
+
+/// Applies a theme to descendant widgets.
+///
+/// A theme describes the colors and typographic choices of an application.
+///
+/// Descendant widgets obtain the current theme's [ThemeData] object using
+/// [Theme.of]. When a widget uses [Theme.of], it is automatically rebuilt if
+/// the theme later changes, so that the changes can be applied.
+///
+/// The [Theme] widget implies an [IconTheme] widget, set to the value of the
+/// [ThemeData.iconTheme] of the [data] for the [Theme].
+///
+/// See also:
+///
+///  * [ThemeData], which describes the actual configuration of a theme.
+///  * [AnimatedTheme], which animates the [ThemeData] when it changes rather
+///    than changing the theme all at once.
+///  * [MaterialApp], which includes an [AnimatedTheme] widget configured via
+///    the [MaterialApp.theme] argument.
+class Theme extends StatelessWidget {
+  /// Applies the given theme [data] to [child].
+  ///
+  /// The [data] and [child] arguments must not be null.
+  const Theme({
+    Key? key,
+    required this.data,
+    required this.child,
+  }) : assert(child != null),
+       assert(data != null),
+       super(key: key);
+
+  /// Specifies the color and typography values for descendant widgets.
+  final ThemeData data;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  static final ThemeData _kFallbackTheme = ThemeData.fallback();
+
+  /// The data from the closest [Theme] instance that encloses the given
+  /// context.
+  ///
+  /// If the given context is enclosed in a [Localizations] widget providing
+  /// [MaterialLocalizations], the returned data is localized according to the
+  /// nearest available [MaterialLocalizations].
+  ///
+  /// Defaults to [new ThemeData.fallback] if there is no [Theme] in the given
+  /// build context.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return Text(
+  ///     'Example',
+  ///     style: Theme.of(context).textTheme.headline6,
+  ///   );
+  /// }
+  /// ```
+  ///
+  /// When the [Theme] is actually created in the same `build` function
+  /// (possibly indirectly, e.g. as part of a [MaterialApp]), the `context`
+  /// argument to the `build` function can't be used to find the [Theme] (since
+  /// it's "above" the widget being returned). In such cases, the following
+  /// technique with a [Builder] can be used to provide a new scope with a
+  /// [BuildContext] that is "under" the [Theme]:
+  ///
+  /// ```dart
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return MaterialApp(
+  ///     theme: ThemeData.light(),
+  ///     body: Builder(
+  ///       // Create an inner BuildContext so that we can refer to
+  ///       // the Theme with Theme.of().
+  ///       builder: (BuildContext context) {
+  ///         return Center(
+  ///           child: Text(
+  ///             'Example',
+  ///             style: Theme.of(context).textTheme.headline6,
+  ///           ),
+  ///         );
+  ///       },
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  static ThemeData of(BuildContext context) {
+    final _InheritedTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedTheme>();
+    final MaterialLocalizations? localizations = Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
+    final ScriptCategory category = localizations?.scriptCategory ?? ScriptCategory.englishLike;
+    final ThemeData theme = inheritedTheme?.theme.data ?? _kFallbackTheme;
+    return ThemeData.localize(theme, theme.typography.geometryThemeFor(category));
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _InheritedTheme(
+      theme: this,
+      child: CupertinoTheme(
+        // We're using a MaterialBasedCupertinoThemeData here instead of a
+        // CupertinoThemeData because it defers some properties to the Material
+        // ThemeData.
+        data: MaterialBasedCupertinoThemeData(
+          materialTheme: data,
+        ),
+        child: IconTheme(
+          data: data.iconTheme,
+          child: child,
+        ),
+      ),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ThemeData>('data', data, showName: false));
+  }
+}
+
+class _InheritedTheme extends InheritedTheme {
+  const _InheritedTheme({
+    Key? key,
+    required this.theme,
+    required Widget child,
+  }) : assert(theme != null),
+       super(key: key, child: child);
+
+  final Theme theme;
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return Theme(data: theme.data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(_InheritedTheme old) => theme.data != old.theme.data;
+}
+
+/// An interpolation between two [ThemeData]s.
+///
+/// This class specializes the interpolation of [Tween<ThemeData>] to call the
+/// [ThemeData.lerp] method.
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+class ThemeDataTween extends Tween<ThemeData> {
+  /// Creates a [ThemeData] tween.
+  ///
+  /// The [begin] and [end] properties must be non-null before the tween is
+  /// first used, but the arguments can be null if the values are going to be
+  /// filled in later.
+  ThemeDataTween({ ThemeData? begin, ThemeData? end }) : super(begin: begin, end: end);
+
+  @override
+  ThemeData lerp(double t) => ThemeData.lerp(begin!, end!, t);
+}
+
+/// Animated version of [Theme] which automatically transitions the colors,
+/// etc, over a given duration whenever the given theme changes.
+///
+/// Here's an illustration of what using this widget looks like, using a [curve]
+/// of [Curves.elasticInOut].
+/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_theme.mp4}
+///
+/// See also:
+///
+///  * [Theme], which [AnimatedTheme] uses to actually apply the interpolated
+///    theme.
+///  * [ThemeData], which describes the actual configuration of a theme.
+///  * [MaterialApp], which includes an [AnimatedTheme] widget configured via
+///    the [MaterialApp.theme] argument.
+class AnimatedTheme extends ImplicitlyAnimatedWidget {
+  /// Creates an animated theme.
+  ///
+  /// By default, the theme transition uses a linear curve. The [data] and
+  /// [child] arguments must not be null.
+  const AnimatedTheme({
+    Key? key,
+    required this.data,
+    Curve curve = Curves.linear,
+    Duration duration = kThemeAnimationDuration,
+    VoidCallback? onEnd,
+    required this.child,
+  }) : assert(child != null),
+       assert(data != null),
+       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
+
+  /// Specifies the color and typography values for descendant widgets.
+  final ThemeData data;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  _AnimatedThemeState createState() => _AnimatedThemeState();
+}
+
+class _AnimatedThemeState extends AnimatedWidgetBaseState<AnimatedTheme> {
+  ThemeDataTween? _data;
+
+  @override
+  void forEachTween(TweenVisitor<dynamic> visitor) {
+    // TODO(ianh): Use constructor tear-offs when it becomes possible, https://github.com/dart-lang/sdk/issues/10659
+    _data = visitor(_data, widget.data, (dynamic value) => ThemeDataTween(begin: value as ThemeData))! as ThemeDataTween;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Theme(
+      child: widget.child,
+      data: _data!.evaluate(animation),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(DiagnosticsProperty<ThemeDataTween>('data', _data, showName: false, defaultValue: null));
+  }
+}
diff --git a/lib/src/material/theme_data.dart b/lib/src/material/theme_data.dart
new file mode 100644
index 0000000..66542a2
--- /dev/null
+++ b/lib/src/material/theme_data.dart
@@ -0,0 +1,2155 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show Color, hashList, lerpDouble;
+
+import 'package:flute/cupertino.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'app_bar_theme.dart';
+import 'banner_theme.dart';
+import 'bottom_app_bar_theme.dart';
+import 'bottom_navigation_bar_theme.dart';
+import 'bottom_sheet_theme.dart';
+import 'button_bar_theme.dart';
+import 'button_theme.dart';
+import 'card_theme.dart';
+import 'checkbox_theme.dart';
+import 'chip_theme.dart';
+import 'color_scheme.dart';
+import 'colors.dart';
+import 'data_table_theme.dart';
+import 'dialog_theme.dart';
+import 'divider_theme.dart';
+import 'elevated_button_theme.dart';
+import 'floating_action_button_theme.dart';
+import 'ink_splash.dart';
+import 'ink_well.dart' show InteractiveInkFeatureFactory;
+import 'input_decorator.dart';
+import 'navigation_rail_theme.dart';
+import 'outlined_button_theme.dart';
+import 'page_transitions_theme.dart';
+import 'popup_menu_theme.dart';
+import 'radio_theme.dart';
+import 'slider_theme.dart';
+import 'snack_bar_theme.dart';
+import 'switch_theme.dart';
+import 'tab_bar_theme.dart';
+import 'text_button_theme.dart';
+import 'text_selection_theme.dart';
+import 'text_theme.dart';
+import 'time_picker_theme.dart';
+import 'toggle_buttons_theme.dart';
+import 'tooltip_theme.dart';
+import 'typography.dart';
+
+export 'package:flute/services.dart' show Brightness;
+
+// Deriving these values is black magic. The spec claims that pressed buttons
+// have a highlight of 0x66999999, but that's clearly wrong. The videos in the
+// spec show that buttons have a composited highlight of #E1E1E1 on a background
+// of #FAFAFA. Assuming that the highlight really has an opacity of 0x66, we can
+// solve for the actual color of the highlight:
+const Color _kLightThemeHighlightColor = Color(0x66BCBCBC);
+
+// The same video shows the splash compositing to #D7D7D7 on a background of
+// #E1E1E1. Again, assuming the splash has an opacity of 0x66, we can solve for
+// the actual color of the splash:
+const Color _kLightThemeSplashColor = Color(0x66C8C8C8);
+
+// Unfortunately, a similar video isn't available for the dark theme, which
+// means we assume the values in the spec are actually correct.
+const Color _kDarkThemeHighlightColor = Color(0x40CCCCCC);
+const Color _kDarkThemeSplashColor = Color(0x40CCCCCC);
+
+/// Configures the tap target and layout size of certain Material widgets.
+///
+/// Changing the value in [ThemeData.materialTapTargetSize] will affect the
+/// accessibility experience.
+///
+/// Some of the impacted widgets include:
+///
+///   * [FloatingActionButton], only the mini tap target size is increased.
+///   * [MaterialButton]
+///   * [OutlinedButton]
+///   * [TextButton]
+///   * [ElevatedButton]
+///   * [OutlineButton]
+///   * [FlatButton]
+///   * [RaisedButton]
+///   * The time picker widget ([showTimePicker])
+///   * [SnackBar]
+///   * [Chip]
+///   * [RawChip]
+///   * [InputChip]
+///   * [ChoiceChip]
+///   * [FilterChip]
+///   * [ActionChip]
+///   * [Radio]
+///   * [Switch]
+///   * [Checkbox]
+enum MaterialTapTargetSize {
+  /// Expands the minimum tap target size to 48px by 48px.
+  ///
+  /// This is the default value of [ThemeData.materialTapTargetSize] and the
+  /// recommended size to conform to Android accessibility scanner
+  /// recommendations.
+  padded,
+
+  /// Shrinks the tap target size to the minimum provided by the Material
+  /// specification.
+  shrinkWrap,
+}
+
+/// Defines the configuration of the overall visual [Theme] for a [MaterialApp]
+/// or a widget subtree within the app.
+///
+/// The [MaterialApp] theme property can be used to configure the appearance
+/// of the entire app. Widget subtree's within an app can override the app's
+/// theme by including a [Theme] widget at the top of the subtree.
+///
+/// Widgets whose appearance should align with the overall theme can obtain the
+/// current theme's configuration with [Theme.of]. Material components typically
+/// depend exclusively on the [colorScheme] and [textTheme]. These properties
+/// are guaranteed to have non-null values.
+///
+/// The static [Theme.of] method finds the [ThemeData] value specified for the
+/// nearest [BuildContext] ancestor. This lookup is inexpensive, essentially
+/// just a single HashMap access. It can sometimes be a little confusing
+/// because [Theme.of] can not see a [Theme] widget that is defined in the
+/// current build method's context. To overcome that, create a new custom widget
+/// for the subtree that appears below the new [Theme], or insert a widget
+/// that creates a new BuildContext, like [Builder].
+///
+/// {@tool snippet}
+/// In this example, the [Container] widget uses [Theme.of] to retrieve the
+/// primary color from the theme's [colorScheme] to draw an amber square.
+/// The [Builder] widget separates the parent theme's [BuildContext] from the
+/// child's [BuildContext].
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/theme_data.png)
+///
+/// ```dart
+/// Theme(
+///   data: ThemeData.from(
+///     colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.amber),
+///   ),
+///   child: Builder(
+///     builder: (BuildContext context) {
+///       return Container(
+///         width: 100,
+///         height: 100,
+///         color: Theme.of(context).colorScheme.primary,
+///       );
+///     },
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+///
+/// This sample creates a [MaterialApp] widget that stores `ThemeData` and
+/// passes the `ThemeData` to descendant widgets. The [AppBar] widget uses the
+/// [primaryColor] to create a blue background. The [Text] widget uses the
+/// [TextTheme.bodyText2] to create purple text. The [FloatingActionButton] widget
+/// uses the [accentColor] to create a green background.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/material_app_theme_data.png)
+///
+/// ```dart
+/// MaterialApp(
+///   theme: ThemeData(
+///     primaryColor: Colors.blue,
+///     accentColor: Colors.green,
+///     textTheme: TextTheme(bodyText2: TextStyle(color: Colors.purple)),
+///   ),
+///   home: Scaffold(
+///     appBar: AppBar(
+///       title: const Text('ThemeData Demo'),
+///     ),
+///     floatingActionButton: FloatingActionButton(
+///       child: const Icon(Icons.add),
+///       onPressed: () {},
+///     ),
+///     body: Center(
+///       child: Text(
+///         'Button pressed 0 times',
+///       ),
+///     ),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See <https://material.io/design/color/> for
+/// more discussion on how to pick the right colors.
+
+@immutable
+class ThemeData with Diagnosticable {
+  /// Create a [ThemeData] that's used to configure a [Theme].
+  ///
+  /// Typically, only the [brightness], [primaryColor], or [primarySwatch] are
+  /// specified. That pair of values are used to construct the [colorScheme].
+  ///
+  /// The [colorScheme] and [textTheme] are used by the Material components to
+  /// compute default values for visual properties. The API documentation for
+  /// each component widget explains exactly how the defaults are computed.
+  ///
+  /// The [textTheme] [TextStyle] colors are black if the color scheme's
+  /// brightness is [Brightness.light], and white for [Brightness.dark].
+  ///
+  /// To override the appearance of specific components, provide
+  /// a component theme parameter like [sliderTheme], [toggleButtonsTheme],
+  /// or [bottomNavigationBarTheme].
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.from], which creates a ThemeData from a [ColorScheme].
+  ///  * [ThemeData.light], which creates a light blue theme.
+  ///  * [ThemeData.dark], which creates dark theme with a teal secondary [ColorScheme] color.
+  factory ThemeData({
+    Brightness? brightness,
+    VisualDensity? visualDensity,
+    MaterialColor? primarySwatch,
+    Color? primaryColor,
+    Brightness? primaryColorBrightness,
+    Color? primaryColorLight,
+    Color? primaryColorDark,
+    Color? accentColor,
+    Brightness? accentColorBrightness,
+    Color? canvasColor,
+    Color? shadowColor,
+    Color? scaffoldBackgroundColor,
+    Color? bottomAppBarColor,
+    Color? cardColor,
+    Color? dividerColor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    InteractiveInkFeatureFactory? splashFactory,
+    Color? selectedRowColor,
+    Color? unselectedWidgetColor,
+    Color? disabledColor,
+    Color? buttonColor,
+    ButtonThemeData? buttonTheme,
+    ToggleButtonsThemeData? toggleButtonsTheme,
+    Color? secondaryHeaderColor,
+    @Deprecated(
+      'Use TextSelectionThemeData.selectionColor instead. '
+      'This feature was deprecated after v1.23.0-4.0.pre.'
+    )
+    Color? textSelectionColor,
+    @Deprecated(
+      'Use TextSelectionThemeData.cursorColor instead. '
+      'This feature was deprecated after v1.23.0-4.0.pre.'
+    )
+    Color? cursorColor,
+    @Deprecated(
+      'Use TextSelectionThemeData.selectionHandleColor instead. '
+      'This feature was deprecated after v1.23.0-4.0.pre.'
+    )
+    Color? textSelectionHandleColor,
+    Color? backgroundColor,
+    Color? dialogBackgroundColor,
+    Color? indicatorColor,
+    Color? hintColor,
+    Color? errorColor,
+    Color? toggleableActiveColor,
+    String? fontFamily,
+    TextTheme? textTheme,
+    TextTheme? primaryTextTheme,
+    TextTheme? accentTextTheme,
+    InputDecorationTheme? inputDecorationTheme,
+    IconThemeData? iconTheme,
+    IconThemeData? primaryIconTheme,
+    IconThemeData? accentIconTheme,
+    SliderThemeData? sliderTheme,
+    TabBarTheme? tabBarTheme,
+    TooltipThemeData? tooltipTheme,
+    CardTheme? cardTheme,
+    ChipThemeData? chipTheme,
+    TargetPlatform? platform,
+    MaterialTapTargetSize? materialTapTargetSize,
+    bool? applyElevationOverlayColor,
+    PageTransitionsTheme? pageTransitionsTheme,
+    AppBarTheme? appBarTheme,
+    BottomAppBarTheme? bottomAppBarTheme,
+    ColorScheme? colorScheme,
+    DialogTheme? dialogTheme,
+    FloatingActionButtonThemeData? floatingActionButtonTheme,
+    NavigationRailThemeData? navigationRailTheme,
+    Typography? typography,
+    NoDefaultCupertinoThemeData? cupertinoOverrideTheme,
+    SnackBarThemeData? snackBarTheme,
+    BottomSheetThemeData? bottomSheetTheme,
+    PopupMenuThemeData? popupMenuTheme,
+    MaterialBannerThemeData? bannerTheme,
+    DividerThemeData? dividerTheme,
+    ButtonBarThemeData? buttonBarTheme,
+    BottomNavigationBarThemeData? bottomNavigationBarTheme,
+    TimePickerThemeData? timePickerTheme,
+    TextButtonThemeData? textButtonTheme,
+    ElevatedButtonThemeData? elevatedButtonTheme,
+    OutlinedButtonThemeData? outlinedButtonTheme,
+    TextSelectionThemeData? textSelectionTheme,
+    DataTableThemeData? dataTableTheme,
+    CheckboxThemeData? checkboxTheme,
+    RadioThemeData? radioTheme,
+    SwitchThemeData? switchTheme,
+    bool? fixTextFieldOutlineLabel,
+    @Deprecated(
+      'No longer used by the framework, please remove any reference to it. '
+      'This feature was deprecated after v1.23.0-4.0.pre.'
+    )
+    bool? useTextSelectionTheme,
+  }) {
+    assert(colorScheme?.brightness == null || brightness == null || colorScheme!.brightness == brightness);
+    final Brightness _brightness = brightness ?? colorScheme?.brightness ?? Brightness.light;
+    final bool isDark = _brightness == Brightness.dark;
+    visualDensity ??= VisualDensity.adaptivePlatformDensity;
+    primarySwatch ??= Colors.blue;
+    primaryColor ??= isDark ? Colors.grey[900]! : primarySwatch;
+    primaryColorBrightness ??= estimateBrightnessForColor(primaryColor);
+    primaryColorLight ??= isDark ? Colors.grey[500]! : primarySwatch[100]!;
+    primaryColorDark ??= isDark ? Colors.black : primarySwatch[700]!;
+    final bool primaryIsDark = primaryColorBrightness == Brightness.dark;
+    toggleableActiveColor ??= isDark ? Colors.tealAccent[200]! : (accentColor ?? primarySwatch[600]!);
+    accentColor ??= isDark ? Colors.tealAccent[200]! : primarySwatch[500]!;
+    accentColorBrightness ??= estimateBrightnessForColor(accentColor);
+    final bool accentIsDark = accentColorBrightness == Brightness.dark;
+    canvasColor ??= isDark ? Colors.grey[850]! : Colors.grey[50]!;
+    shadowColor ??= Colors.black;
+    scaffoldBackgroundColor ??= canvasColor;
+    bottomAppBarColor ??= isDark ? Colors.grey[800]! : Colors.white;
+    cardColor ??= isDark ? Colors.grey[800]! : Colors.white;
+    dividerColor ??= isDark ? const Color(0x1FFFFFFF) : const Color(0x1F000000);
+
+    // Create a ColorScheme that is backwards compatible as possible
+    // with the existing default ThemeData color values.
+    colorScheme ??= ColorScheme.fromSwatch(
+      primarySwatch: primarySwatch,
+      primaryColorDark: primaryColorDark,
+      accentColor: accentColor,
+      cardColor: cardColor,
+      backgroundColor: backgroundColor,
+      errorColor: errorColor,
+      brightness: _brightness,
+    );
+
+    splashFactory ??= InkSplash.splashFactory;
+    selectedRowColor ??= Colors.grey[100]!;
+    unselectedWidgetColor ??= isDark ? Colors.white70 : Colors.black54;
+    // Spec doesn't specify a dark theme secondaryHeaderColor, this is a guess.
+    secondaryHeaderColor ??= isDark ? Colors.grey[700]! : primarySwatch[50]!;
+    textSelectionColor ??= isDark ? accentColor : primarySwatch[200]!;
+    cursorColor = cursorColor ?? const Color.fromRGBO(66, 133, 244, 1.0);
+    textSelectionHandleColor ??= isDark ? Colors.tealAccent[400]! : primarySwatch[300]!;
+    backgroundColor ??= isDark ? Colors.grey[700]! : primarySwatch[200]!;
+    dialogBackgroundColor ??= isDark ? Colors.grey[800]! : Colors.white;
+    indicatorColor ??= accentColor == primaryColor ? Colors.white : accentColor;
+    hintColor ??= isDark ? Colors.white60 : Colors.black.withOpacity(0.6);
+    errorColor ??= Colors.red[700]!;
+    inputDecorationTheme ??= const InputDecorationTheme();
+    pageTransitionsTheme ??= const PageTransitionsTheme();
+    primaryIconTheme ??= primaryIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
+    accentIconTheme ??= accentIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
+    iconTheme ??= isDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black87);
+    platform ??= defaultTargetPlatform;
+    typography ??= Typography.material2014(platform: platform);
+    TextTheme defaultTextTheme = isDark ? typography.white : typography.black;
+    TextTheme defaultPrimaryTextTheme = primaryIsDark ? typography.white : typography.black;
+    TextTheme defaultAccentTextTheme = accentIsDark ? typography.white : typography.black;
+    if (fontFamily != null) {
+      defaultTextTheme = defaultTextTheme.apply(fontFamily: fontFamily);
+      defaultPrimaryTextTheme = defaultPrimaryTextTheme.apply(fontFamily: fontFamily);
+      defaultAccentTextTheme = defaultAccentTextTheme.apply(fontFamily: fontFamily);
+    }
+    textTheme = defaultTextTheme.merge(textTheme);
+    primaryTextTheme = defaultPrimaryTextTheme.merge(primaryTextTheme);
+    accentTextTheme = defaultAccentTextTheme.merge(accentTextTheme);
+    switch (platform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.iOS:
+        materialTapTargetSize ??= MaterialTapTargetSize.padded;
+        break;
+      case TargetPlatform.linux:
+      case TargetPlatform.macOS:
+      case TargetPlatform.windows:
+         materialTapTargetSize ??= MaterialTapTargetSize.shrinkWrap;
+        break;
+    }
+    applyElevationOverlayColor ??= false;
+
+    // Used as the default color (fill color) for RaisedButtons. Computing the
+    // default for ButtonThemeData for the sake of backwards compatibility.
+    buttonColor ??= isDark ? primarySwatch[600]! : Colors.grey[300]!;
+    focusColor ??= isDark ? Colors.white.withOpacity(0.12) : Colors.black.withOpacity(0.12);
+    hoverColor ??= isDark ? Colors.white.withOpacity(0.04) : Colors.black.withOpacity(0.04);
+    buttonTheme ??= ButtonThemeData(
+      colorScheme: colorScheme,
+      buttonColor: buttonColor,
+      disabledColor: disabledColor,
+      focusColor: focusColor,
+      hoverColor: hoverColor,
+      highlightColor: highlightColor,
+      splashColor: splashColor,
+      materialTapTargetSize: materialTapTargetSize,
+    );
+    toggleButtonsTheme ??= const ToggleButtonsThemeData();
+    disabledColor ??= isDark ? Colors.white38 : Colors.black38;
+    highlightColor ??= isDark ? _kDarkThemeHighlightColor : _kLightThemeHighlightColor;
+    splashColor ??= isDark ? _kDarkThemeSplashColor : _kLightThemeSplashColor;
+
+    sliderTheme ??= const SliderThemeData();
+    tabBarTheme ??= const TabBarTheme();
+    tooltipTheme ??= const TooltipThemeData();
+    appBarTheme ??= const AppBarTheme();
+    bottomAppBarTheme ??= const BottomAppBarTheme();
+    cardTheme ??= const CardTheme();
+    chipTheme ??= ChipThemeData.fromDefaults(
+      secondaryColor: primaryColor,
+      brightness: colorScheme.brightness,
+      labelStyle: textTheme.bodyText1!,
+    );
+    dialogTheme ??= const DialogTheme();
+    floatingActionButtonTheme ??= const FloatingActionButtonThemeData();
+    navigationRailTheme ??= const NavigationRailThemeData();
+    cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
+    snackBarTheme ??= const SnackBarThemeData();
+    bottomSheetTheme ??= const BottomSheetThemeData();
+    popupMenuTheme ??= const PopupMenuThemeData();
+    bannerTheme ??= const MaterialBannerThemeData();
+    dividerTheme ??= const DividerThemeData();
+    buttonBarTheme ??= const ButtonBarThemeData();
+    bottomNavigationBarTheme ??= const BottomNavigationBarThemeData();
+    timePickerTheme ??= const TimePickerThemeData();
+    textButtonTheme ??= const TextButtonThemeData();
+    elevatedButtonTheme ??= const ElevatedButtonThemeData();
+    outlinedButtonTheme ??= const OutlinedButtonThemeData();
+    textSelectionTheme ??= const TextSelectionThemeData();
+    dataTableTheme ??= const DataTableThemeData();
+    checkboxTheme ??= const CheckboxThemeData();
+    radioTheme ??= const RadioThemeData();
+    switchTheme ??= const SwitchThemeData();
+
+    fixTextFieldOutlineLabel ??= false;
+    useTextSelectionTheme ??= true;
+
+    return ThemeData.raw(
+      visualDensity: visualDensity,
+      primaryColor: primaryColor,
+      primaryColorBrightness: primaryColorBrightness,
+      primaryColorLight: primaryColorLight,
+      primaryColorDark: primaryColorDark,
+      accentColor: accentColor,
+      accentColorBrightness: accentColorBrightness,
+      canvasColor: canvasColor,
+      shadowColor: shadowColor,
+      scaffoldBackgroundColor: scaffoldBackgroundColor,
+      bottomAppBarColor: bottomAppBarColor,
+      cardColor: cardColor,
+      dividerColor: dividerColor,
+      focusColor: focusColor,
+      hoverColor: hoverColor,
+      highlightColor: highlightColor,
+      splashColor: splashColor,
+      splashFactory: splashFactory,
+      selectedRowColor: selectedRowColor,
+      unselectedWidgetColor: unselectedWidgetColor,
+      disabledColor: disabledColor,
+      buttonTheme: buttonTheme,
+      buttonColor: buttonColor,
+      toggleButtonsTheme: toggleButtonsTheme,
+      toggleableActiveColor: toggleableActiveColor,
+      secondaryHeaderColor: secondaryHeaderColor,
+      textSelectionColor: textSelectionColor,
+      cursorColor: cursorColor,
+      textSelectionHandleColor: textSelectionHandleColor,
+      backgroundColor: backgroundColor,
+      dialogBackgroundColor: dialogBackgroundColor,
+      indicatorColor: indicatorColor,
+      hintColor: hintColor,
+      errorColor: errorColor,
+      textTheme: textTheme,
+      primaryTextTheme: primaryTextTheme,
+      accentTextTheme: accentTextTheme,
+      inputDecorationTheme: inputDecorationTheme,
+      iconTheme: iconTheme,
+      primaryIconTheme: primaryIconTheme,
+      accentIconTheme: accentIconTheme,
+      sliderTheme: sliderTheme,
+      tabBarTheme: tabBarTheme,
+      tooltipTheme: tooltipTheme,
+      cardTheme: cardTheme,
+      chipTheme: chipTheme,
+      platform: platform,
+      materialTapTargetSize: materialTapTargetSize,
+      applyElevationOverlayColor: applyElevationOverlayColor,
+      pageTransitionsTheme: pageTransitionsTheme,
+      appBarTheme: appBarTheme,
+      bottomAppBarTheme: bottomAppBarTheme,
+      colorScheme: colorScheme,
+      dialogTheme: dialogTheme,
+      floatingActionButtonTheme: floatingActionButtonTheme,
+      navigationRailTheme: navigationRailTheme,
+      typography: typography,
+      cupertinoOverrideTheme: cupertinoOverrideTheme,
+      snackBarTheme: snackBarTheme,
+      bottomSheetTheme: bottomSheetTheme,
+      popupMenuTheme: popupMenuTheme,
+      bannerTheme: bannerTheme,
+      dividerTheme: dividerTheme,
+      buttonBarTheme: buttonBarTheme,
+      bottomNavigationBarTheme: bottomNavigationBarTheme,
+      timePickerTheme: timePickerTheme,
+      textButtonTheme: textButtonTheme,
+      elevatedButtonTheme: elevatedButtonTheme,
+      outlinedButtonTheme: outlinedButtonTheme,
+      textSelectionTheme: textSelectionTheme,
+      dataTableTheme: dataTableTheme,
+      checkboxTheme: checkboxTheme,
+      radioTheme: radioTheme,
+      switchTheme: switchTheme,
+      fixTextFieldOutlineLabel: fixTextFieldOutlineLabel,
+      useTextSelectionTheme: useTextSelectionTheme,
+    );
+  }
+
+  /// Create a [ThemeData] given a set of exact values. All the values must be
+  /// specified. They all must also be non-null except for
+  /// [cupertinoOverrideTheme].
+  ///
+  /// This will rarely be used directly. It is used by [lerp] to
+  /// create intermediate themes based on two themes created with the
+  /// [new ThemeData] constructor.
+  const ThemeData.raw({
+    // Warning: make sure these properties are in the exact same order as in
+    // operator == and in the hashValues method and in the order of fields
+    // in this class, and in the lerp() method.
+    required this.visualDensity,
+    required this.primaryColor,
+    required this.primaryColorBrightness,
+    required this.primaryColorLight,
+    required this.primaryColorDark,
+    required this.canvasColor,
+    required this.shadowColor,
+    required this.accentColor,
+    required this.accentColorBrightness,
+    required this.scaffoldBackgroundColor,
+    required this.bottomAppBarColor,
+    required this.cardColor,
+    required this.dividerColor,
+    required this.focusColor,
+    required this.hoverColor,
+    required this.highlightColor,
+    required this.splashColor,
+    required this.splashFactory,
+    required this.selectedRowColor,
+    required this.unselectedWidgetColor,
+    required this.disabledColor,
+    required this.buttonTheme,
+    required this.buttonColor,
+    required this.toggleButtonsTheme,
+    required this.secondaryHeaderColor,
+    required this.textSelectionColor,
+    required this.cursorColor,
+    required this.textSelectionHandleColor,
+    required this.backgroundColor,
+    required this.dialogBackgroundColor,
+    required this.indicatorColor,
+    required this.hintColor,
+    required this.errorColor,
+    required this.toggleableActiveColor,
+    required this.textTheme,
+    required this.primaryTextTheme,
+    required this.accentTextTheme,
+    required this.inputDecorationTheme,
+    required this.iconTheme,
+    required this.primaryIconTheme,
+    required this.accentIconTheme,
+    required this.sliderTheme,
+    required this.tabBarTheme,
+    required this.tooltipTheme,
+    required this.cardTheme,
+    required this.chipTheme,
+    required this.platform,
+    required this.materialTapTargetSize,
+    required this.applyElevationOverlayColor,
+    required this.pageTransitionsTheme,
+    required this.appBarTheme,
+    required this.bottomAppBarTheme,
+    required this.colorScheme,
+    required this.dialogTheme,
+    required this.floatingActionButtonTheme,
+    required this.navigationRailTheme,
+    required this.typography,
+    required this.cupertinoOverrideTheme,
+    required this.snackBarTheme,
+    required this.bottomSheetTheme,
+    required this.popupMenuTheme,
+    required this.bannerTheme,
+    required this.dividerTheme,
+    required this.buttonBarTheme,
+    required this.bottomNavigationBarTheme,
+    required this.timePickerTheme,
+    required this.textButtonTheme,
+    required this.elevatedButtonTheme,
+    required this.outlinedButtonTheme,
+    required this.textSelectionTheme,
+    required this.dataTableTheme,
+    required this.checkboxTheme,
+    required this.radioTheme,
+    required this.switchTheme,
+    required this.fixTextFieldOutlineLabel,
+    required this.useTextSelectionTheme,
+  }) : assert(visualDensity != null),
+       assert(primaryColor != null),
+       assert(primaryColorBrightness != null),
+       assert(primaryColorLight != null),
+       assert(primaryColorDark != null),
+       assert(accentColor != null),
+       assert(accentColorBrightness != null),
+       assert(canvasColor != null),
+       assert(shadowColor != null),
+       assert(scaffoldBackgroundColor != null),
+       assert(bottomAppBarColor != null),
+       assert(cardColor != null),
+       assert(dividerColor != null),
+       assert(focusColor != null),
+       assert(hoverColor != null),
+       assert(highlightColor != null),
+       assert(splashColor != null),
+       assert(splashFactory != null),
+       assert(selectedRowColor != null),
+       assert(unselectedWidgetColor != null),
+       assert(disabledColor != null),
+       assert(toggleableActiveColor != null),
+       assert(buttonTheme != null),
+       assert(toggleButtonsTheme != null),
+       assert(secondaryHeaderColor != null),
+       assert(textSelectionColor != null),
+       assert(cursorColor != null),
+       assert(textSelectionHandleColor != null),
+       assert(backgroundColor != null),
+       assert(dialogBackgroundColor != null),
+       assert(indicatorColor != null),
+       assert(hintColor != null),
+       assert(errorColor != null),
+       assert(textTheme != null),
+       assert(primaryTextTheme != null),
+       assert(accentTextTheme != null),
+       assert(inputDecorationTheme != null),
+       assert(iconTheme != null),
+       assert(primaryIconTheme != null),
+       assert(accentIconTheme != null),
+       assert(sliderTheme != null),
+       assert(tabBarTheme != null),
+       assert(tooltipTheme != null),
+       assert(cardTheme != null),
+       assert(chipTheme != null),
+       assert(platform != null),
+       assert(materialTapTargetSize != null),
+       assert(pageTransitionsTheme != null),
+       assert(appBarTheme != null),
+       assert(bottomAppBarTheme != null),
+       assert(colorScheme != null),
+       assert(dialogTheme != null),
+       assert(floatingActionButtonTheme != null),
+       assert(navigationRailTheme != null),
+       assert(typography != null),
+       assert(snackBarTheme != null),
+       assert(bottomSheetTheme != null),
+       assert(popupMenuTheme != null),
+       assert(bannerTheme != null),
+       assert(dividerTheme != null),
+       assert(buttonBarTheme != null),
+       assert(bottomNavigationBarTheme != null),
+       assert(timePickerTheme != null),
+       assert(textButtonTheme != null),
+       assert(elevatedButtonTheme != null),
+       assert(outlinedButtonTheme != null),
+       assert(textSelectionTheme != null),
+       assert(dataTableTheme != null),
+       assert(checkboxTheme != null),
+       assert(radioTheme != null),
+       assert(switchTheme != null),
+       assert(fixTextFieldOutlineLabel != null),
+       assert(useTextSelectionTheme != null);
+
+  /// Create a [ThemeData] based on the colors in the given [colorScheme] and
+  /// text styles of the optional [textTheme].
+  ///
+  /// The [colorScheme] can not be null.
+  ///
+  /// If [colorScheme.brightness] is [Brightness.dark] then
+  /// [ThemeData.applyElevationOverlayColor] will be set to true to support
+  /// the Material dark theme method for indicating elevation by applying
+  /// a semi-transparent onSurface color on top of the surface color.
+  ///
+  /// This is the recommended method to theme your application. As we move
+  /// forward we will be converting all the widget implementations to only use
+  /// colors or colors derived from those in [ColorScheme].
+  ///
+  /// {@tool snippet}
+  /// This example will set up an application to use the baseline Material
+  /// Design light and dark themes.
+  ///
+  /// ```dart
+  /// MaterialApp(
+  ///   theme: ThemeData.from(colorScheme: ColorScheme.light()),
+  ///   darkTheme: ThemeData.from(colorScheme: ColorScheme.dark()),
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See <https://material.io/design/color/> for
+  /// more discussion on how to pick the right colors.
+  factory ThemeData.from({
+    required ColorScheme colorScheme,
+    TextTheme? textTheme,
+  }) {
+    assert(colorScheme != null);
+
+    final bool isDark = colorScheme.brightness == Brightness.dark;
+
+    // For surfaces that use primary color in light themes and surface color in dark
+    final Color primarySurfaceColor = isDark ? colorScheme.surface : colorScheme.primary;
+    final Color onPrimarySurfaceColor = isDark ? colorScheme.onSurface : colorScheme.onPrimary;
+
+    return ThemeData(
+      brightness: colorScheme.brightness,
+      primaryColor: primarySurfaceColor,
+      primaryColorBrightness: ThemeData.estimateBrightnessForColor(primarySurfaceColor),
+      canvasColor: colorScheme.background,
+      accentColor: colorScheme.secondary,
+      accentColorBrightness: ThemeData.estimateBrightnessForColor(colorScheme.secondary),
+      scaffoldBackgroundColor: colorScheme.background,
+      bottomAppBarColor: colorScheme.surface,
+      cardColor: colorScheme.surface,
+      dividerColor: colorScheme.onSurface.withOpacity(0.12),
+      backgroundColor: colorScheme.background,
+      dialogBackgroundColor: colorScheme.background,
+      errorColor: colorScheme.error,
+      textTheme: textTheme,
+      indicatorColor: onPrimarySurfaceColor,
+      applyElevationOverlayColor: isDark,
+      colorScheme: colorScheme,
+    );
+  }
+
+  /// A default light blue theme.
+  ///
+  /// This theme does not contain text geometry. Instead, it is expected that
+  /// this theme is localized using text geometry using [ThemeData.localize].
+  factory ThemeData.light() => ThemeData(brightness: Brightness.light);
+
+  /// A default dark theme with a teal secondary [ColorScheme] color.
+  ///
+  /// This theme does not contain text geometry. Instead, it is expected that
+  /// this theme is localized using text geometry using [ThemeData.localize].
+  factory ThemeData.dark() => ThemeData(brightness: Brightness.dark);
+
+  /// The default color theme. Same as [new ThemeData.light].
+  ///
+  /// This is used by [Theme.of] when no theme has been specified.
+  ///
+  /// This theme does not contain text geometry. Instead, it is expected that
+  /// this theme is localized using text geometry using [ThemeData.localize].
+  ///
+  /// Most applications would use [Theme.of], which provides correct localized
+  /// text geometry.
+  factory ThemeData.fallback() => ThemeData.light();
+
+  // Warning: make sure these properties are in the exact same order as in
+  // hashValues() and in the raw constructor and in the order of fields in
+  // the class and in the lerp() method.
+
+  /// The overall theme brightness.
+  ///
+  /// The default [TextStyle] color for the [textTheme] is black if the
+  /// theme is constructed with [Brightness.light] and white if the
+  /// theme is constructed with [Brightness.dark].
+  Brightness get brightness => colorScheme.brightness;
+
+  /// The density value for specifying the compactness of various UI components.
+  ///
+  /// {@template flutter.material.themedata.visualDensity}
+  /// Density, in the context of a UI, is the vertical and horizontal
+  /// "compactness" of the elements in the UI. It is unitless, since it means
+  /// different things to different UI elements. For buttons, it affects the
+  /// spacing around the centered label of the button. For lists, it affects the
+  /// distance between baselines of entries in the list.
+  ///
+  /// Typically, density values are integral, but any value in range may be
+  /// used. The range includes values from [VisualDensity.minimumDensity] (which
+  /// is -4), to [VisualDensity.maximumDensity] (which is 4), inclusive, where
+  /// negative values indicate a denser, more compact, UI, and positive values
+  /// indicate a less dense, more expanded, UI. If a component doesn't support
+  /// the value given, it will clamp to the nearest supported value.
+  ///
+  /// The default for visual densities is zero for both vertical and horizontal
+  /// densities, which corresponds to the default visual density of components
+  /// in the Material Design specification.
+  ///
+  /// As a rule of thumb, a change of 1 or -1 in density corresponds to 4
+  /// logical pixels. However, this is not a strict relationship since
+  /// components interpret the density values appropriately for their needs.
+  ///
+  /// A larger value translates to a spacing increase (less dense), and a
+  /// smaller value translates to a spacing decrease (more dense).
+  /// {@endtemplate}
+  final VisualDensity visualDensity;
+
+  /// The background color for major parts of the app (toolbars, tab bars, etc)
+  ///
+  /// The theme's [colorScheme] property contains [ColorScheme.primary], as
+  /// well as a color that contrasts well with the primary color called
+  /// [ColorScheme.onPrimary]. It might be simpler to just configure an app's
+  /// visuals in terms of the theme's [colorScheme].
+  final Color primaryColor;
+
+  /// The brightness of the [primaryColor]. Used to determine the color of text and
+  /// icons placed on top of the primary color (e.g. toolbar text).
+  final Brightness primaryColorBrightness;
+
+  /// A lighter version of the [primaryColor].
+  final Color primaryColorLight;
+
+  /// A darker version of the [primaryColor].
+  final Color primaryColorDark;
+
+  /// The default color of [MaterialType.canvas] [Material].
+  final Color canvasColor;
+
+  /// The color that the [Material] widget uses to draw elevation shadows.
+  ///
+  /// Defaults to fully opaque black.
+  ///
+  /// Shadows can be difficult to see in a dark theme, so the elevation of a
+  /// surface should be rendered with an "overlay" in addition to the shadow.
+  /// As the elevation of the component increases, the overlay increases in
+  /// opacity. The [applyElevationOverlayColor] property turns the elevation
+  /// overlay on or off for dark themes.
+  final Color shadowColor;
+
+  /// The foreground color for widgets (knobs, text, overscroll edge effect, etc).
+  ///
+  /// Accent color is also known as the secondary color.
+  ///
+  /// The theme's [colorScheme] property contains [ColorScheme.secondary], as
+  /// well as a color that contrasts well with the secondary color called
+  /// [ColorScheme.onSecondary]. It might be simpler to just configure an app's
+  /// visuals in terms of the theme's [colorScheme].
+  final Color accentColor;
+
+  /// The brightness of the [accentColor]. Used to determine the color of text
+  /// and icons placed on top of the accent color (e.g. the icons on a floating
+  /// action button).
+  final Brightness accentColorBrightness;
+
+  /// The default color of the [Material] that underlies the [Scaffold]. The
+  /// background color for a typical material app or a page within the app.
+  final Color scaffoldBackgroundColor;
+
+  /// The default color of the [BottomAppBar].
+  ///
+  /// This can be overridden by specifying [BottomAppBar.color].
+  final Color bottomAppBarColor;
+
+  /// The color of [Material] when it is used as a [Card].
+  final Color cardColor;
+
+  /// The color of [Divider]s and [PopupMenuDivider]s, also used
+  /// between [ListTile]s, between rows in [DataTable]s, and so forth.
+  ///
+  /// To create an appropriate [BorderSide] that uses this color, consider
+  /// [Divider.createBorderSide].
+  final Color dividerColor;
+
+  /// The focus color used indicate that a component has the input focus.
+  final Color focusColor;
+
+  /// The hover color used to indicate when a pointer is hovering over a
+  /// component.
+  final Color hoverColor;
+
+  /// The highlight color used during ink splash animations or to
+  /// indicate an item in a menu is selected.
+  final Color highlightColor;
+
+  /// The color of ink splashes. See [InkWell].
+  final Color splashColor;
+
+  /// Defines the appearance of ink splashes produces by [InkWell]
+  /// and [InkResponse].
+  ///
+  /// See also:
+  ///
+  ///  * [InkSplash.splashFactory], which defines the default splash.
+  ///  * [InkRipple.splashFactory], which defines a splash that spreads out
+  ///    more aggressively than the default.
+  final InteractiveInkFeatureFactory splashFactory;
+
+  /// The color used to highlight selected rows.
+  final Color selectedRowColor;
+
+  /// The color used for widgets in their inactive (but enabled)
+  /// state. For example, an unchecked checkbox. Usually contrasted
+  /// with the [accentColor]. See also [disabledColor].
+  final Color unselectedWidgetColor;
+
+  /// The color used for widgets that are inoperative, regardless of
+  /// their state. For example, a disabled checkbox (which may be
+  /// checked or unchecked).
+  final Color disabledColor;
+
+  /// Defines the default configuration of button widgets, like [RaisedButton]
+  /// and [FlatButton].
+  final ButtonThemeData buttonTheme;
+
+  /// Defines the default configuration of [ToggleButtons] widgets.
+  final ToggleButtonsThemeData toggleButtonsTheme;
+
+  /// The default fill color of the [Material] used in [RaisedButton]s.
+  final Color buttonColor;
+
+  /// The color of the header of a [PaginatedDataTable] when there are selected rows.
+  // According to the spec for data tables:
+  // https://material.io/archive/guidelines/components/data-tables.html#data-tables-tables-within-cards
+  // ...this should be the "50-value of secondary app color".
+  final Color secondaryHeaderColor;
+
+  /// The color of text selections in text fields, such as [TextField].
+  @Deprecated(
+    'Use TextSelectionThemeData.selectionColor instead. '
+    'This feature was deprecated after v1.23.0-4.0.pre.'
+  )
+  final Color textSelectionColor;
+
+  /// The color of cursors in Material-style text fields, such as [TextField].
+  @Deprecated(
+    'Use TextSelectionThemeData.cursorColor instead. '
+    'This feature was deprecated after v1.23.0-4.0.pre.'
+  )
+  final Color cursorColor;
+
+  /// The color of the handles used to adjust what part of the text is currently selected.
+  @Deprecated(
+    'Use TextSelectionThemeData.selectionHandleColor instead. '
+    'This feature was deprecated after v1.23.0-4.0.pre.'
+  )
+  final Color textSelectionHandleColor;
+
+  /// A color that contrasts with the [primaryColor], e.g. used as the
+  /// remaining part of a progress bar.
+  final Color backgroundColor;
+
+  /// The background color of [Dialog] elements.
+  final Color dialogBackgroundColor;
+
+  /// The color of the selected tab indicator in a tab bar.
+  final Color indicatorColor;
+
+  /// The color to use for hint text or placeholder text, e.g. in
+  /// [TextField] fields.
+  final Color hintColor;
+
+  /// The color to use for input validation errors, e.g. in [TextField] fields.
+  final Color errorColor;
+
+  /// The color used to highlight the active states of toggleable widgets like
+  /// [Switch], [Radio], and [Checkbox].
+  final Color toggleableActiveColor;
+
+  /// Text with a color that contrasts with the card and canvas colors.
+  final TextTheme textTheme;
+
+  /// A text theme that contrasts with the primary color.
+  final TextTheme primaryTextTheme;
+
+  /// A text theme that contrasts with the accent color.
+  final TextTheme accentTextTheme;
+
+  /// The default [InputDecoration] values for [InputDecorator], [TextField],
+  /// and [TextFormField] are based on this theme.
+  ///
+  /// See [InputDecoration.applyDefaults].
+  final InputDecorationTheme inputDecorationTheme;
+
+  /// An icon theme that contrasts with the card and canvas colors.
+  final IconThemeData iconTheme;
+
+  /// An icon theme that contrasts with the primary color.
+  final IconThemeData primaryIconTheme;
+
+  /// An icon theme that contrasts with the accent color.
+  final IconThemeData accentIconTheme;
+
+  /// The colors and shapes used to render [Slider].
+  ///
+  /// This is the value returned from [SliderTheme.of].
+  final SliderThemeData sliderTheme;
+
+  /// A theme for customizing the size, shape, and color of the tab bar indicator.
+  final TabBarTheme tabBarTheme;
+
+  /// A theme for customizing the visual properties of [Tooltip]s.
+  ///
+  /// This is the value returned from [TooltipTheme.of].
+  final TooltipThemeData tooltipTheme;
+
+  /// The colors and styles used to render [Card].
+  ///
+  /// This is the value returned from [CardTheme.of].
+  final CardTheme cardTheme;
+
+  /// The colors and styles used to render [Chip]s.
+  ///
+  /// This is the value returned from [ChipTheme.of].
+  final ChipThemeData chipTheme;
+
+  /// The platform the material widgets should adapt to target.
+  ///
+  /// Defaults to the current platform, as exposed by [defaultTargetPlatform].
+  /// This should be used in order to style UI elements according to platform
+  /// conventions.
+  ///
+  /// Widgets from the material library should use this getter (via [Theme.of])
+  /// to determine the current platform for the purpose of emulating the
+  /// platform behavior (e.g. scrolling or haptic effects). Widgets and render
+  /// objects at lower layers that try to emulate the underlying platform
+  /// platform can depend on [defaultTargetPlatform] directly, or may require
+  /// that the target platform be provided as an argument. The
+  /// [dart:io.Platform] object should only be used directly when it's critical
+  /// to actually know the current platform, without any overrides possible (for
+  /// example, when a system API is about to be called).
+  ///
+  /// In a test environment, the platform returned is [TargetPlatform.android]
+  /// regardless of the host platform. (Android was chosen because the tests
+  /// were originally written assuming Android-like behavior, and we added
+  /// platform adaptations for other platforms later). Tests can check behavior
+  /// for other platforms by setting the [platform] of the [Theme] explicitly to
+  /// another [TargetPlatform] value, or by setting
+  /// [debugDefaultTargetPlatformOverride].
+  final TargetPlatform platform;
+
+  /// Configures the hit test size of certain Material widgets.
+  final MaterialTapTargetSize materialTapTargetSize;
+
+  /// Apply a semi-transparent overlay color on Material surfaces to indicate
+  /// elevation for dark themes.
+  ///
+  /// Material drop shadows can be difficult to see in a dark theme, so the
+  /// elevation of a surface should be portrayed with an "overlay" in addition
+  /// to the shadow. As the elevation of the component increases, the
+  /// overlay increases in opacity. [applyElevationOverlayColor] turns the
+  /// application of this overlay on or off for dark themes.
+  ///
+  /// If true and [brightness] is [Brightness.dark], a
+  /// semi-transparent version of [ColorScheme.onSurface] will be
+  /// applied on top of [Material] widgets that have a [ColorScheme.surface]
+  /// color. The level of transparency is based on [Material.elevation] as
+  /// per the Material Dark theme specification.
+  ///
+  /// If false the surface color will be used unmodified.
+  ///
+  /// Defaults to false in order to maintain backwards compatibility with
+  /// apps that were built before the Material Dark theme specification
+  /// was published. New apps should set this to true for any themes
+  /// where [brightness] is [Brightness.dark].
+  ///
+  /// See also:
+  ///
+  ///  * [Material.elevation], which effects the level of transparency of the
+  ///    overlay color.
+  ///  * [ElevationOverlay.applyOverlay], which is used by [Material] to apply
+  ///    the overlay color to its surface color.
+  ///  * <https://material.io/design/color/dark-theme.html>, which specifies how
+  ///    the overlay should be applied.
+  final bool applyElevationOverlayColor;
+
+  /// Default [MaterialPageRoute] transitions per [TargetPlatform].
+  ///
+  /// [MaterialPageRoute.buildTransitions] delegates to a [platform] specific
+  /// [PageTransitionsBuilder]. If a matching builder is not found, a builder
+  /// whose platform is null is used.
+  final PageTransitionsTheme pageTransitionsTheme;
+
+  /// A theme for customizing the color, elevation, brightness, iconTheme and
+  /// textTheme of [AppBar]s.
+  final AppBarTheme appBarTheme;
+
+  /// A theme for customizing the shape, elevation, and color of a [BottomAppBar].
+  final BottomAppBarTheme bottomAppBarTheme;
+
+  /// A set of thirteen colors that can be used to configure the
+  /// color properties of most components.
+  ///
+  /// This property was added much later than the theme's set of highly
+  /// specific colors, like [cardColor], [buttonColor], [canvasColor] etc.
+  /// New components can be defined exclusively in terms of [colorScheme].
+  /// Existing components will gradually migrate to it, to the extent
+  /// that is possible without significant backwards compatibility breaks.
+  final ColorScheme colorScheme;
+
+  /// A theme for customizing colors, shape, elevation, and behavior of a [SnackBar].
+  final SnackBarThemeData snackBarTheme;
+
+  /// A theme for customizing the shape of a dialog.
+  final DialogTheme dialogTheme;
+
+  /// A theme for customizing the shape, elevation, and color of a
+  /// [FloatingActionButton].
+  final FloatingActionButtonThemeData floatingActionButtonTheme;
+
+  /// A theme for customizing the background color, elevation, text style, and
+  /// icon themes of a [NavigationRail].
+  final NavigationRailThemeData navigationRailTheme;
+
+  /// The color and geometry [TextTheme] values used to configure [textTheme],
+  /// [primaryTextTheme], and [accentTextTheme].
+  final Typography typography;
+
+  /// Components of the [CupertinoThemeData] to override from the Material
+  /// [ThemeData] adaptation.
+  ///
+  /// By default, [cupertinoOverrideTheme] is null and Cupertino widgets
+  /// descendant to the Material [Theme] will adhere to a [CupertinoTheme]
+  /// derived from the Material [ThemeData]. e.g. [ThemeData]'s [ColorScheme]
+  /// will also inform the [CupertinoThemeData]'s `primaryColor` etc.
+  ///
+  /// This cascading effect for individual attributes of the [CupertinoThemeData]
+  /// can be overridden using attributes of this [cupertinoOverrideTheme].
+  final NoDefaultCupertinoThemeData? cupertinoOverrideTheme;
+
+  /// A theme for customizing the color, elevation, and shape of a bottom sheet.
+  final BottomSheetThemeData bottomSheetTheme;
+
+  /// A theme for customizing the color, shape, elevation, and text style of
+  /// popup menus.
+  final PopupMenuThemeData popupMenuTheme;
+
+  /// A theme for customizing the color and text style of a [MaterialBanner].
+  final MaterialBannerThemeData bannerTheme;
+
+  /// A theme for customizing the color, thickness, and indents of [Divider]s,
+  /// [VerticalDivider]s, etc.
+  final DividerThemeData dividerTheme;
+
+  /// A theme for customizing the appearance and layout of [ButtonBar] widgets.
+  final ButtonBarThemeData buttonBarTheme;
+
+  /// A theme for customizing the appearance and layout of [BottomNavigationBar]
+  /// widgets.
+  final BottomNavigationBarThemeData bottomNavigationBarTheme;
+
+  /// A theme for customizing the appearance and layout of time picker widgets.
+  final TimePickerThemeData timePickerTheme;
+
+  /// A theme for customizing the appearance and internal layout of
+  /// [TextButton]s.
+  final TextButtonThemeData textButtonTheme;
+
+  /// A theme for customizing the appearance and internal layout of
+  /// [ElevatedButton]s.
+  final ElevatedButtonThemeData elevatedButtonTheme;
+
+  /// A theme for customizing the appearance and internal layout of
+  /// [OutlinedButton]s.
+  final OutlinedButtonThemeData outlinedButtonTheme;
+
+  /// A theme for customizing the appearance and layout of [TextField] widgets.
+  final TextSelectionThemeData textSelectionTheme;
+
+  /// A theme for customizing the appearance and layout of [DataTable]
+  /// widgets.
+  final DataTableThemeData dataTableTheme;
+
+  /// A theme for customizing the appearance and layout of [Checkbox] widgets.
+  final CheckboxThemeData checkboxTheme;
+
+  /// A theme for customizing the appearance and layout of [Radio] widgets.
+  final RadioThemeData radioTheme;
+
+  /// A theme for customizing the appearance and layout of [Switch] widgets.
+  final SwitchThemeData switchTheme;
+
+  /// A temporary flag to allow apps to opt-in to a
+  /// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y
+  /// coordinate of the floating label in a [TextField] [OutlineInputBorder].
+  ///
+  /// Setting this flag to true causes the floating label to be more precisely
+  /// vertically centered relative to the border's outline.
+  ///
+  /// The flag is currently false by default. It will be default true and
+  /// deprecated before the next beta release (1.18), and removed before the next
+  /// stable release (1.19).
+  final bool fixTextFieldOutlineLabel;
+
+  /// A temporary flag that was used to opt-in to the new [TextSelectionTheme]
+  /// during the migration to this new theme. That migration is now complete
+  /// and this flag is not longer used by the framework. Please remove any
+  /// reference to this property, as it will be removed in future releases.
+  @Deprecated(
+    'No longer used by the framework, please remove any reference to it. '
+    'This feature was deprecated after v1.23.0-4.0.pre.'
+  )
+  final bool useTextSelectionTheme;
+
+  /// Creates a copy of this theme but with the given fields replaced with the new values.
+  ///
+  /// The [brightness] value is applied to the [colorScheme].
+  ThemeData copyWith({
+    Brightness? brightness,
+    VisualDensity? visualDensity,
+    Color? primaryColor,
+    Brightness? primaryColorBrightness,
+    Color? primaryColorLight,
+    Color? primaryColorDark,
+    Color? accentColor,
+    Brightness? accentColorBrightness,
+    Color? canvasColor,
+    Color? shadowColor,
+    Color? scaffoldBackgroundColor,
+    Color? bottomAppBarColor,
+    Color? cardColor,
+    Color? dividerColor,
+    Color? focusColor,
+    Color? hoverColor,
+    Color? highlightColor,
+    Color? splashColor,
+    InteractiveInkFeatureFactory? splashFactory,
+    Color? selectedRowColor,
+    Color? unselectedWidgetColor,
+    Color? disabledColor,
+    ButtonThemeData? buttonTheme,
+    ToggleButtonsThemeData? toggleButtonsTheme,
+    Color? buttonColor,
+    Color? secondaryHeaderColor,
+    @Deprecated(
+      'Use TextSelectionThemeData.selectionColor instead. '
+      'This feature was deprecated after v1.23.0-4.0.pre.'
+    )
+    Color? textSelectionColor,
+    @Deprecated(
+      'Use TextSelectionThemeData.cursorColor instead. '
+      'This feature was deprecated after v1.23.0-4.0.pre.'
+    )
+    Color? cursorColor,
+    @Deprecated(
+      'Use TextSelectionThemeData.selectionHandleColor instead. '
+      'This feature was deprecated after v1.23.0-4.0.pre.'
+    )
+    Color? textSelectionHandleColor,
+    Color? backgroundColor,
+    Color? dialogBackgroundColor,
+    Color? indicatorColor,
+    Color? hintColor,
+    Color? errorColor,
+    Color? toggleableActiveColor,
+    TextTheme? textTheme,
+    TextTheme? primaryTextTheme,
+    TextTheme? accentTextTheme,
+    InputDecorationTheme? inputDecorationTheme,
+    IconThemeData? iconTheme,
+    IconThemeData? primaryIconTheme,
+    IconThemeData? accentIconTheme,
+    SliderThemeData? sliderTheme,
+    TabBarTheme? tabBarTheme,
+    TooltipThemeData? tooltipTheme,
+    CardTheme? cardTheme,
+    ChipThemeData? chipTheme,
+    TargetPlatform? platform,
+    MaterialTapTargetSize? materialTapTargetSize,
+    bool? applyElevationOverlayColor,
+    PageTransitionsTheme? pageTransitionsTheme,
+    AppBarTheme? appBarTheme,
+    BottomAppBarTheme? bottomAppBarTheme,
+    ColorScheme? colorScheme,
+    DialogTheme? dialogTheme,
+    FloatingActionButtonThemeData? floatingActionButtonTheme,
+    NavigationRailThemeData? navigationRailTheme,
+    Typography? typography,
+    NoDefaultCupertinoThemeData? cupertinoOverrideTheme,
+    SnackBarThemeData? snackBarTheme,
+    BottomSheetThemeData? bottomSheetTheme,
+    PopupMenuThemeData? popupMenuTheme,
+    MaterialBannerThemeData? bannerTheme,
+    DividerThemeData? dividerTheme,
+    ButtonBarThemeData? buttonBarTheme,
+    BottomNavigationBarThemeData? bottomNavigationBarTheme,
+    TimePickerThemeData? timePickerTheme,
+    TextButtonThemeData? textButtonTheme,
+    ElevatedButtonThemeData? elevatedButtonTheme,
+    OutlinedButtonThemeData? outlinedButtonTheme,
+    TextSelectionThemeData? textSelectionTheme,
+    DataTableThemeData? dataTableTheme,
+    CheckboxThemeData? checkboxTheme,
+    RadioThemeData? radioTheme,
+    SwitchThemeData? switchTheme,
+    bool? fixTextFieldOutlineLabel,
+    @Deprecated(
+      'No longer used by the framework, please remove any reference to it. '
+      'This feature was deprecated after v1.23.0-4.0.pre.'
+    )
+    bool? useTextSelectionTheme,
+  }) {
+    cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
+    return ThemeData.raw(
+      visualDensity: visualDensity ?? this.visualDensity,
+      primaryColor: primaryColor ?? this.primaryColor,
+      primaryColorBrightness: primaryColorBrightness ?? this.primaryColorBrightness,
+      primaryColorLight: primaryColorLight ?? this.primaryColorLight,
+      primaryColorDark: primaryColorDark ?? this.primaryColorDark,
+      accentColor: accentColor ?? this.accentColor,
+      accentColorBrightness: accentColorBrightness ?? this.accentColorBrightness,
+      canvasColor: canvasColor ?? this.canvasColor,
+      shadowColor: shadowColor ?? this.shadowColor,
+      scaffoldBackgroundColor: scaffoldBackgroundColor ?? this.scaffoldBackgroundColor,
+      bottomAppBarColor: bottomAppBarColor ?? this.bottomAppBarColor,
+      cardColor: cardColor ?? this.cardColor,
+      dividerColor: dividerColor ?? this.dividerColor,
+      focusColor: focusColor ?? this.focusColor,
+      hoverColor: hoverColor ?? this.hoverColor,
+      highlightColor: highlightColor ?? this.highlightColor,
+      splashColor: splashColor ?? this.splashColor,
+      splashFactory: splashFactory ?? this.splashFactory,
+      selectedRowColor: selectedRowColor ?? this.selectedRowColor,
+      unselectedWidgetColor: unselectedWidgetColor ?? this.unselectedWidgetColor,
+      disabledColor: disabledColor ?? this.disabledColor,
+      buttonColor: buttonColor ?? this.buttonColor,
+      buttonTheme: buttonTheme ?? this.buttonTheme,
+      toggleButtonsTheme: toggleButtonsTheme ?? this.toggleButtonsTheme,
+      secondaryHeaderColor: secondaryHeaderColor ?? this.secondaryHeaderColor,
+      textSelectionColor: textSelectionColor ?? this.textSelectionColor,
+      cursorColor: cursorColor ?? this.cursorColor,
+      textSelectionHandleColor: textSelectionHandleColor ?? this.textSelectionHandleColor,
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      dialogBackgroundColor: dialogBackgroundColor ?? this.dialogBackgroundColor,
+      indicatorColor: indicatorColor ?? this.indicatorColor,
+      hintColor: hintColor ?? this.hintColor,
+      errorColor: errorColor ?? this.errorColor,
+      toggleableActiveColor: toggleableActiveColor ?? this.toggleableActiveColor,
+      textTheme: textTheme ?? this.textTheme,
+      primaryTextTheme: primaryTextTheme ?? this.primaryTextTheme,
+      accentTextTheme: accentTextTheme ?? this.accentTextTheme,
+      inputDecorationTheme: inputDecorationTheme ?? this.inputDecorationTheme,
+      iconTheme: iconTheme ?? this.iconTheme,
+      primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme,
+      accentIconTheme: accentIconTheme ?? this.accentIconTheme,
+      sliderTheme: sliderTheme ?? this.sliderTheme,
+      tabBarTheme: tabBarTheme ?? this.tabBarTheme,
+      tooltipTheme: tooltipTheme ?? this.tooltipTheme,
+      cardTheme: cardTheme ?? this.cardTheme,
+      chipTheme: chipTheme ?? this.chipTheme,
+      platform: platform ?? this.platform,
+      materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
+      applyElevationOverlayColor: applyElevationOverlayColor ?? this.applyElevationOverlayColor,
+      pageTransitionsTheme: pageTransitionsTheme ?? this.pageTransitionsTheme,
+      appBarTheme: appBarTheme ?? this.appBarTheme,
+      bottomAppBarTheme: bottomAppBarTheme ?? this.bottomAppBarTheme,
+      colorScheme: (colorScheme ?? this.colorScheme).copyWith(brightness: brightness),
+      dialogTheme: dialogTheme ?? this.dialogTheme,
+      floatingActionButtonTheme: floatingActionButtonTheme ?? this.floatingActionButtonTheme,
+      navigationRailTheme: navigationRailTheme ?? this.navigationRailTheme,
+      typography: typography ?? this.typography,
+      cupertinoOverrideTheme: cupertinoOverrideTheme ?? this.cupertinoOverrideTheme,
+      snackBarTheme: snackBarTheme ?? this.snackBarTheme,
+      bottomSheetTheme: bottomSheetTheme ?? this.bottomSheetTheme,
+      popupMenuTheme: popupMenuTheme ?? this.popupMenuTheme,
+      bannerTheme: bannerTheme ?? this.bannerTheme,
+      dividerTheme: dividerTheme ?? this.dividerTheme,
+      buttonBarTheme: buttonBarTheme ?? this.buttonBarTheme,
+      bottomNavigationBarTheme: bottomNavigationBarTheme ?? this.bottomNavigationBarTheme,
+      timePickerTheme: timePickerTheme ?? this.timePickerTheme,
+      textButtonTheme: textButtonTheme ?? this.textButtonTheme,
+      elevatedButtonTheme: elevatedButtonTheme ?? this.elevatedButtonTheme,
+      outlinedButtonTheme: outlinedButtonTheme ?? this.outlinedButtonTheme,
+      textSelectionTheme: textSelectionTheme ?? this.textSelectionTheme,
+      dataTableTheme: dataTableTheme ?? this.dataTableTheme,
+      checkboxTheme: checkboxTheme ?? this.checkboxTheme,
+      radioTheme: radioTheme ?? this.radioTheme,
+      switchTheme: switchTheme ?? this.switchTheme,
+      fixTextFieldOutlineLabel: fixTextFieldOutlineLabel ?? this.fixTextFieldOutlineLabel,
+      useTextSelectionTheme: useTextSelectionTheme ?? this.useTextSelectionTheme,
+    );
+  }
+
+  // The number 5 was chosen without any real science or research behind it. It
+  // just seemed like a number that's not too big (we should be able to fit 5
+  // copies of ThemeData in memory comfortably) and not too small (most apps
+  // shouldn't have more than 5 theme/localization pairs).
+  static const int _localizedThemeDataCacheSize = 5;
+
+  /// Caches localized themes to speed up the [localize] method.
+  static final _FifoCache<_IdentityThemeDataCacheKey, ThemeData> _localizedThemeDataCache =
+      _FifoCache<_IdentityThemeDataCacheKey, ThemeData>(_localizedThemeDataCacheSize);
+
+  /// Returns a new theme built by merging the text geometry provided by the
+  /// [localTextGeometry] theme with the [baseTheme].
+  ///
+  /// For those text styles in the [baseTheme] whose [TextStyle.inherit] is set
+  /// to true, the returned theme's text styles inherit the geometric properties
+  /// of [localTextGeometry]. The resulting text styles' [TextStyle.inherit] is
+  /// set to those provided by [localTextGeometry].
+  static ThemeData localize(ThemeData baseTheme, TextTheme localTextGeometry) {
+    // WARNING: this method memoizes the result in a cache based on the
+    // previously seen baseTheme and localTextGeometry. Memoization is safe
+    // because all inputs and outputs of this function are deeply immutable, and
+    // the computations are referentially transparent. It only short-circuits
+    // the computation if the new inputs are identical() to the previous ones.
+    // It does not use the == operator, which performs a costly deep comparison.
+    //
+    // When changing this method, make sure the memoization logic is correct.
+    // Remember:
+    //
+    // There are only two hard things in Computer Science: cache invalidation
+    // and naming things. -- Phil Karlton
+    assert(baseTheme != null);
+    assert(localTextGeometry != null);
+
+    return _localizedThemeDataCache.putIfAbsent(
+      _IdentityThemeDataCacheKey(baseTheme, localTextGeometry),
+      () {
+        return baseTheme.copyWith(
+          primaryTextTheme: localTextGeometry.merge(baseTheme.primaryTextTheme),
+          accentTextTheme: localTextGeometry.merge(baseTheme.accentTextTheme),
+          textTheme: localTextGeometry.merge(baseTheme.textTheme),
+        );
+      },
+    );
+  }
+
+  /// Determines whether the given [Color] is [Brightness.light] or
+  /// [Brightness.dark].
+  ///
+  /// This compares the luminosity of the given color to a threshold value that
+  /// matches the material design specification.
+  static Brightness estimateBrightnessForColor(Color color) {
+    final double relativeLuminance = color.computeLuminance();
+
+    // See <https://www.w3.org/TR/WCAG20/#contrast-ratiodef>
+    // The spec says to use kThreshold=0.0525, but Material Design appears to bias
+    // more towards using light text than WCAG20 recommends. Material Design spec
+    // doesn't say what value to use, but 0.15 seemed close to what the Material
+    // Design spec shows for its color palette on
+    // <https://material.io/go/design-theming#color-color-palette>.
+    const double kThreshold = 0.15;
+    if ((relativeLuminance + 0.05) * (relativeLuminance + 0.05) > kThreshold)
+      return Brightness.light;
+    return Brightness.dark;
+  }
+
+  /// Linearly interpolate between two themes.
+  ///
+  /// The arguments must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static ThemeData lerp(ThemeData a, ThemeData b, double t) {
+    assert(a != null);
+    assert(b != null);
+    assert(t != null);
+    // Warning: make sure these properties are in the exact same order as in
+    // hashValues() and in the raw constructor and in the order of fields in
+    // the class and in the lerp() method.
+    return ThemeData.raw(
+      visualDensity: VisualDensity.lerp(a.visualDensity, b.visualDensity, t),
+      primaryColor: Color.lerp(a.primaryColor, b.primaryColor, t)!,
+      primaryColorBrightness: t < 0.5 ? a.primaryColorBrightness : b.primaryColorBrightness,
+      primaryColorLight: Color.lerp(a.primaryColorLight, b.primaryColorLight, t)!,
+      primaryColorDark: Color.lerp(a.primaryColorDark, b.primaryColorDark, t)!,
+      canvasColor: Color.lerp(a.canvasColor, b.canvasColor, t)!,
+      shadowColor: Color.lerp(a.shadowColor, b.shadowColor, t)!,
+      accentColor: Color.lerp(a.accentColor, b.accentColor, t)!,
+      accentColorBrightness: t < 0.5 ? a.accentColorBrightness : b.accentColorBrightness,
+      scaffoldBackgroundColor: Color.lerp(a.scaffoldBackgroundColor, b.scaffoldBackgroundColor, t)!,
+      bottomAppBarColor: Color.lerp(a.bottomAppBarColor, b.bottomAppBarColor, t)!,
+      cardColor: Color.lerp(a.cardColor, b.cardColor, t)!,
+      dividerColor: Color.lerp(a.dividerColor, b.dividerColor, t)!,
+      focusColor: Color.lerp(a.focusColor, b.focusColor, t)!,
+      hoverColor: Color.lerp(a.hoverColor, b.hoverColor, t)!,
+      highlightColor: Color.lerp(a.highlightColor, b.highlightColor, t)!,
+      splashColor: Color.lerp(a.splashColor, b.splashColor, t)!,
+      splashFactory: t < 0.5 ? a.splashFactory : b.splashFactory,
+      selectedRowColor: Color.lerp(a.selectedRowColor, b.selectedRowColor, t)!,
+      unselectedWidgetColor: Color.lerp(a.unselectedWidgetColor, b.unselectedWidgetColor, t)!,
+      disabledColor: Color.lerp(a.disabledColor, b.disabledColor, t)!,
+      buttonTheme: t < 0.5 ? a.buttonTheme : b.buttonTheme,
+      toggleButtonsTheme: ToggleButtonsThemeData.lerp(a.toggleButtonsTheme, b.toggleButtonsTheme, t)!,
+      buttonColor: Color.lerp(a.buttonColor, b.buttonColor, t)!,
+      secondaryHeaderColor: Color.lerp(a.secondaryHeaderColor, b.secondaryHeaderColor, t)!,
+      textSelectionColor: Color.lerp(a.textSelectionColor, b.textSelectionColor, t)!,
+      cursorColor: Color.lerp(a.cursorColor, b.cursorColor, t)!,
+      textSelectionHandleColor: Color.lerp(a.textSelectionHandleColor, b.textSelectionHandleColor, t)!,
+      backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t)!,
+      dialogBackgroundColor: Color.lerp(a.dialogBackgroundColor, b.dialogBackgroundColor, t)!,
+      indicatorColor: Color.lerp(a.indicatorColor, b.indicatorColor, t)!,
+      hintColor: Color.lerp(a.hintColor, b.hintColor, t)!,
+      errorColor: Color.lerp(a.errorColor, b.errorColor, t)!,
+      toggleableActiveColor: Color.lerp(a.toggleableActiveColor, b.toggleableActiveColor, t)!,
+      textTheme: TextTheme.lerp(a.textTheme, b.textTheme, t),
+      primaryTextTheme: TextTheme.lerp(a.primaryTextTheme, b.primaryTextTheme, t),
+      accentTextTheme: TextTheme.lerp(a.accentTextTheme, b.accentTextTheme, t),
+      inputDecorationTheme: t < 0.5 ? a.inputDecorationTheme : b.inputDecorationTheme,
+      iconTheme: IconThemeData.lerp(a.iconTheme, b.iconTheme, t),
+      primaryIconTheme: IconThemeData.lerp(a.primaryIconTheme, b.primaryIconTheme, t),
+      accentIconTheme: IconThemeData.lerp(a.accentIconTheme, b.accentIconTheme, t),
+      sliderTheme: SliderThemeData.lerp(a.sliderTheme, b.sliderTheme, t),
+      tabBarTheme: TabBarTheme.lerp(a.tabBarTheme, b.tabBarTheme, t),
+      tooltipTheme: TooltipThemeData.lerp(a.tooltipTheme, b.tooltipTheme, t)!,
+      cardTheme: CardTheme.lerp(a.cardTheme, b.cardTheme, t),
+      chipTheme: ChipThemeData.lerp(a.chipTheme, b.chipTheme, t)!,
+      platform: t < 0.5 ? a.platform : b.platform,
+      materialTapTargetSize: t < 0.5 ? a.materialTapTargetSize : b.materialTapTargetSize,
+      applyElevationOverlayColor: t < 0.5 ? a.applyElevationOverlayColor : b.applyElevationOverlayColor,
+      pageTransitionsTheme: t < 0.5 ? a.pageTransitionsTheme : b.pageTransitionsTheme,
+      appBarTheme: AppBarTheme.lerp(a.appBarTheme, b.appBarTheme, t),
+      bottomAppBarTheme: BottomAppBarTheme.lerp(a.bottomAppBarTheme, b.bottomAppBarTheme, t),
+      colorScheme: ColorScheme.lerp(a.colorScheme, b.colorScheme, t),
+      dialogTheme: DialogTheme.lerp(a.dialogTheme, b.dialogTheme, t),
+      floatingActionButtonTheme: FloatingActionButtonThemeData.lerp(a.floatingActionButtonTheme, b.floatingActionButtonTheme, t)!,
+      navigationRailTheme: NavigationRailThemeData.lerp(a.navigationRailTheme, b.navigationRailTheme, t)!,
+      typography: Typography.lerp(a.typography, b.typography, t),
+      cupertinoOverrideTheme: t < 0.5 ? a.cupertinoOverrideTheme : b.cupertinoOverrideTheme,
+      snackBarTheme: SnackBarThemeData.lerp(a.snackBarTheme, b.snackBarTheme, t),
+      bottomSheetTheme: BottomSheetThemeData.lerp(a.bottomSheetTheme, b.bottomSheetTheme, t)!,
+      popupMenuTheme: PopupMenuThemeData.lerp(a.popupMenuTheme, b.popupMenuTheme, t)!,
+      bannerTheme: MaterialBannerThemeData.lerp(a.bannerTheme, b.bannerTheme, t),
+      dividerTheme: DividerThemeData.lerp(a.dividerTheme, b.dividerTheme, t),
+      buttonBarTheme: ButtonBarThemeData.lerp(a.buttonBarTheme, b.buttonBarTheme, t)!,
+      bottomNavigationBarTheme: BottomNavigationBarThemeData.lerp(a.bottomNavigationBarTheme, b.bottomNavigationBarTheme, t),
+      timePickerTheme: TimePickerThemeData.lerp(a.timePickerTheme, b.timePickerTheme, t),
+      textButtonTheme: TextButtonThemeData.lerp(a.textButtonTheme, b.textButtonTheme, t)!,
+      elevatedButtonTheme: ElevatedButtonThemeData.lerp(a.elevatedButtonTheme, b.elevatedButtonTheme, t)!,
+      outlinedButtonTheme: OutlinedButtonThemeData.lerp(a.outlinedButtonTheme, b.outlinedButtonTheme, t)!,
+      textSelectionTheme: TextSelectionThemeData .lerp(a.textSelectionTheme, b.textSelectionTheme, t)!,
+      dataTableTheme: DataTableThemeData.lerp(a.dataTableTheme, b.dataTableTheme, t),
+      checkboxTheme: CheckboxThemeData.lerp(a.checkboxTheme, b.checkboxTheme, t),
+      radioTheme: RadioThemeData.lerp(a.radioTheme, b.radioTheme, t),
+      switchTheme: SwitchThemeData.lerp(a.switchTheme, b.switchTheme, t),
+      fixTextFieldOutlineLabel: t < 0.5 ? a.fixTextFieldOutlineLabel : b.fixTextFieldOutlineLabel,
+      useTextSelectionTheme: t < 0.5 ? a.useTextSelectionTheme : b.useTextSelectionTheme,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    // Warning: make sure these properties are in the exact same order as in
+    // hashValues() and in the raw constructor and in the order of fields in
+    // the class and in the lerp() method.
+    return other is ThemeData
+        && other.visualDensity == visualDensity
+        && other.primaryColor == primaryColor
+        && other.primaryColorBrightness == primaryColorBrightness
+        && other.primaryColorLight == primaryColorLight
+        && other.primaryColorDark == primaryColorDark
+        && other.accentColor == accentColor
+        && other.accentColorBrightness == accentColorBrightness
+        && other.canvasColor == canvasColor
+        && other.scaffoldBackgroundColor == scaffoldBackgroundColor
+        && other.bottomAppBarColor == bottomAppBarColor
+        && other.cardColor == cardColor
+        && other.shadowColor == shadowColor
+        && other.dividerColor == dividerColor
+        && other.highlightColor == highlightColor
+        && other.splashColor == splashColor
+        && other.splashFactory == splashFactory
+        && other.selectedRowColor == selectedRowColor
+        && other.unselectedWidgetColor == unselectedWidgetColor
+        && other.disabledColor == disabledColor
+        && other.buttonTheme == buttonTheme
+        && other.buttonColor == buttonColor
+        && other.toggleButtonsTheme == toggleButtonsTheme
+        && other.secondaryHeaderColor == secondaryHeaderColor
+        && other.textSelectionColor == textSelectionColor
+        && other.cursorColor == cursorColor
+        && other.textSelectionHandleColor == textSelectionHandleColor
+        && other.backgroundColor == backgroundColor
+        && other.dialogBackgroundColor == dialogBackgroundColor
+        && other.indicatorColor == indicatorColor
+        && other.hintColor == hintColor
+        && other.errorColor == errorColor
+        && other.toggleableActiveColor == toggleableActiveColor
+        && other.textTheme == textTheme
+        && other.primaryTextTheme == primaryTextTheme
+        && other.accentTextTheme == accentTextTheme
+        && other.inputDecorationTheme == inputDecorationTheme
+        && other.iconTheme == iconTheme
+        && other.primaryIconTheme == primaryIconTheme
+        && other.accentIconTheme == accentIconTheme
+        && other.sliderTheme == sliderTheme
+        && other.tabBarTheme == tabBarTheme
+        && other.tooltipTheme == tooltipTheme
+        && other.cardTheme == cardTheme
+        && other.chipTheme == chipTheme
+        && other.platform == platform
+        && other.materialTapTargetSize == materialTapTargetSize
+        && other.applyElevationOverlayColor == applyElevationOverlayColor
+        && other.pageTransitionsTheme == pageTransitionsTheme
+        && other.appBarTheme == appBarTheme
+        && other.bottomAppBarTheme == bottomAppBarTheme
+        && other.colorScheme == colorScheme
+        && other.dialogTheme == dialogTheme
+        && other.floatingActionButtonTheme == floatingActionButtonTheme
+        && other.navigationRailTheme == navigationRailTheme
+        && other.typography == typography
+        && other.cupertinoOverrideTheme == cupertinoOverrideTheme
+        && other.snackBarTheme == snackBarTheme
+        && other.bottomSheetTheme == bottomSheetTheme
+        && other.popupMenuTheme == popupMenuTheme
+        && other.bannerTheme == bannerTheme
+        && other.dividerTheme == dividerTheme
+        && other.buttonBarTheme == buttonBarTheme
+        && other.bottomNavigationBarTheme == bottomNavigationBarTheme
+        && other.timePickerTheme == timePickerTheme
+        && other.textButtonTheme == textButtonTheme
+        && other.elevatedButtonTheme == elevatedButtonTheme
+        && other.outlinedButtonTheme == outlinedButtonTheme
+        && other.textSelectionTheme == textSelectionTheme
+        && other.dataTableTheme == dataTableTheme
+        && other.checkboxTheme == checkboxTheme
+        && other.radioTheme == radioTheme
+        && other.switchTheme == switchTheme
+        && other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel
+        && other.useTextSelectionTheme == useTextSelectionTheme;
+  }
+
+  @override
+  int get hashCode {
+    // Warning: For the sanity of the reader, please make sure these properties
+    // are in the exact same order as in operator == and in the raw constructor
+    // and in the order of fields in the class and in the lerp() method.
+    final List<Object?> values = <Object?>[
+      visualDensity,
+      primaryColor,
+      primaryColorBrightness,
+      primaryColorLight,
+      primaryColorDark,
+      accentColor,
+      accentColorBrightness,
+      canvasColor,
+      shadowColor,
+      scaffoldBackgroundColor,
+      bottomAppBarColor,
+      cardColor,
+      dividerColor,
+      focusColor,
+      hoverColor,
+      highlightColor,
+      splashColor,
+      splashFactory,
+      selectedRowColor,
+      unselectedWidgetColor,
+      disabledColor,
+      buttonTheme,
+      buttonColor,
+      toggleButtonsTheme,
+      toggleableActiveColor,
+      secondaryHeaderColor,
+      textSelectionColor,
+      cursorColor,
+      textSelectionHandleColor,
+      backgroundColor,
+      dialogBackgroundColor,
+      indicatorColor,
+      hintColor,
+      errorColor,
+      textTheme,
+      primaryTextTheme,
+      accentTextTheme,
+      inputDecorationTheme,
+      iconTheme,
+      primaryIconTheme,
+      accentIconTheme,
+      sliderTheme,
+      tabBarTheme,
+      tooltipTheme,
+      cardTheme,
+      chipTheme,
+      platform,
+      materialTapTargetSize,
+      applyElevationOverlayColor,
+      pageTransitionsTheme,
+      appBarTheme,
+      bottomAppBarTheme,
+      colorScheme,
+      dialogTheme,
+      floatingActionButtonTheme,
+      navigationRailTheme,
+      typography,
+      cupertinoOverrideTheme,
+      snackBarTheme,
+      bottomSheetTheme,
+      popupMenuTheme,
+      bannerTheme,
+      dividerTheme,
+      buttonBarTheme,
+      bottomNavigationBarTheme,
+      timePickerTheme,
+      textButtonTheme,
+      elevatedButtonTheme,
+      outlinedButtonTheme,
+      textSelectionTheme,
+      dataTableTheme,
+      checkboxTheme,
+      radioTheme,
+      switchTheme,
+      fixTextFieldOutlineLabel,
+      useTextSelectionTheme,
+    ];
+    return hashList(values);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    final ThemeData defaultData = ThemeData.fallback();
+    properties.add(EnumProperty<TargetPlatform>('platform', platform, defaultValue: defaultTargetPlatform, level: DiagnosticLevel.debug));
+    properties.add(EnumProperty<Brightness>('brightness', brightness, defaultValue: defaultData.brightness, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('primaryColor', primaryColor, defaultValue: defaultData.primaryColor, level: DiagnosticLevel.debug));
+    properties.add(EnumProperty<Brightness>('primaryColorBrightness', primaryColorBrightness, defaultValue: defaultData.primaryColorBrightness, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('accentColor', accentColor, defaultValue: defaultData.accentColor, level: DiagnosticLevel.debug));
+    properties.add(EnumProperty<Brightness>('accentColorBrightness', accentColorBrightness, defaultValue: defaultData.accentColorBrightness, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('canvasColor', canvasColor, defaultValue: defaultData.canvasColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: defaultData.shadowColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('scaffoldBackgroundColor', scaffoldBackgroundColor, defaultValue: defaultData.scaffoldBackgroundColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('bottomAppBarColor', bottomAppBarColor, defaultValue: defaultData.bottomAppBarColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('cardColor', cardColor, defaultValue: defaultData.cardColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('dividerColor', dividerColor, defaultValue: defaultData.dividerColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('focusColor', focusColor, defaultValue: defaultData.focusColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: defaultData.hoverColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('highlightColor', highlightColor, defaultValue: defaultData.highlightColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('splashColor', splashColor, defaultValue: defaultData.splashColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('selectedRowColor', selectedRowColor, defaultValue: defaultData.selectedRowColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('unselectedWidgetColor', unselectedWidgetColor, defaultValue: defaultData.unselectedWidgetColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('disabledColor', disabledColor, defaultValue: defaultData.disabledColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('buttonColor', buttonColor, defaultValue: defaultData.buttonColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('secondaryHeaderColor', secondaryHeaderColor, defaultValue: defaultData.secondaryHeaderColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('textSelectionColor', textSelectionColor, defaultValue: defaultData.textSelectionColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('cursorColor', cursorColor, defaultValue: defaultData.cursorColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('textSelectionHandleColor', textSelectionHandleColor, defaultValue: defaultData.textSelectionHandleColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: defaultData.backgroundColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('dialogBackgroundColor', dialogBackgroundColor, defaultValue: defaultData.dialogBackgroundColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('indicatorColor', indicatorColor, defaultValue: defaultData.indicatorColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('hintColor', hintColor, defaultValue: defaultData.hintColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('errorColor', errorColor, defaultValue: defaultData.errorColor, level: DiagnosticLevel.debug));
+    properties.add(ColorProperty('toggleableActiveColor', toggleableActiveColor, defaultValue: defaultData.toggleableActiveColor, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<ButtonThemeData>('buttonTheme', buttonTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<ToggleButtonsThemeData>('toggleButtonsTheme', toggleButtonsTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<TextTheme>('textTheme', textTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<TextTheme>('primaryTextTheme', primaryTextTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<TextTheme>('accentTextTheme', accentTextTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<InputDecorationTheme>('inputDecorationTheme', inputDecorationTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<IconThemeData>('iconTheme', iconTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<IconThemeData>('primaryIconTheme', primaryIconTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<IconThemeData>('accentIconTheme', accentIconTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<SliderThemeData>('sliderTheme', sliderTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<TabBarTheme>('tabBarTheme', tabBarTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<TooltipThemeData>('tooltipTheme', tooltipTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<CardTheme>('cardTheme', cardTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<bool>('applyElevationOverlayColor', applyElevationOverlayColor, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<PageTransitionsTheme>('pageTransitionsTheme', pageTransitionsTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<AppBarTheme>('appBarTheme', appBarTheme, defaultValue: defaultData.appBarTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<BottomAppBarTheme>('bottomAppBarTheme', bottomAppBarTheme, defaultValue: defaultData.bottomAppBarTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<ColorScheme>('colorScheme', colorScheme, defaultValue: defaultData.colorScheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<DialogTheme>('dialogTheme', dialogTheme, defaultValue: defaultData.dialogTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<FloatingActionButtonThemeData>('floatingActionButtonThemeData', floatingActionButtonTheme, defaultValue: defaultData.floatingActionButtonTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<NavigationRailThemeData>('navigationRailThemeData', navigationRailTheme, defaultValue: defaultData.navigationRailTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<Typography>('typography', typography, defaultValue: defaultData.typography, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<NoDefaultCupertinoThemeData>('cupertinoOverrideTheme', cupertinoOverrideTheme, defaultValue: defaultData.cupertinoOverrideTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<SnackBarThemeData>('snackBarTheme', snackBarTheme, defaultValue: defaultData.snackBarTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<BottomSheetThemeData>('bottomSheetTheme', bottomSheetTheme, defaultValue: defaultData.bottomSheetTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<PopupMenuThemeData>('popupMenuTheme', popupMenuTheme, defaultValue: defaultData.popupMenuTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<MaterialBannerThemeData>('bannerTheme', bannerTheme, defaultValue: defaultData.bannerTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<DividerThemeData>('dividerTheme', dividerTheme, defaultValue: defaultData.dividerTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<ButtonBarThemeData>('buttonBarTheme', buttonBarTheme, defaultValue: defaultData.buttonBarTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<TimePickerThemeData>('timePickerTheme', timePickerTheme, defaultValue: defaultData.timePickerTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<TextSelectionThemeData>('textSelectionTheme', textSelectionTheme, defaultValue: defaultData.textSelectionTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<TextSelectionThemeData>('textSelectionTheme', textSelectionTheme, defaultValue: defaultData.textSelectionTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<BottomNavigationBarThemeData>('bottomNavigationBarTheme', bottomNavigationBarTheme, defaultValue: defaultData.bottomNavigationBarTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<TextButtonThemeData>('textButtonTheme', textButtonTheme, defaultValue: defaultData.textButtonTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<ElevatedButtonThemeData>('elevatedButtonTheme', elevatedButtonTheme, defaultValue: defaultData.elevatedButtonTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<OutlinedButtonThemeData>('outlinedButtonTheme', outlinedButtonTheme, defaultValue: defaultData.outlinedButtonTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<DataTableThemeData>('dataTableTheme', dataTableTheme, defaultValue: defaultData.dataTableTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<CheckboxThemeData>('checkboxTheme', checkboxTheme, defaultValue: defaultData.checkboxTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<RadioThemeData>('radioTheme', radioTheme, defaultValue: defaultData.radioTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<SwitchThemeData>('switchTheme', switchTheme, defaultValue: defaultData.switchTheme, level: DiagnosticLevel.debug));
+  }
+}
+
+/// A [CupertinoThemeData] that defers unspecified theme attributes to an
+/// upstream Material [ThemeData].
+///
+/// This type of [CupertinoThemeData] is used by the Material [Theme] to
+/// harmonize the [CupertinoTheme] with the material theme's colors and text
+/// styles.
+///
+/// In the most basic case, [ThemeData]'s `cupertinoOverrideTheme` is null and
+/// and descendant Cupertino widgets' styling is derived from the Material theme.
+///
+/// To override individual parts of the Material-derived Cupertino styling,
+/// `cupertinoOverrideTheme`'s construction parameters can be used.
+///
+/// To completely decouple the Cupertino styling from Material theme derivation,
+/// another [CupertinoTheme] widget can be inserted as a descendant of the
+/// Material [Theme]. On a [MaterialApp], this can be done using the `builder`
+/// parameter on the constructor.
+///
+/// See also:
+///
+///  * [CupertinoThemeData], whose null constructor parameters default to
+///    reasonable iOS styling defaults rather than harmonizing with a Material
+///    theme.
+///  * [Theme], widget which inserts a [CupertinoTheme] with this
+///    [MaterialBasedCupertinoThemeData].
+// This class subclasses CupertinoThemeData rather than composes one because it
+// _is_ a CupertinoThemeData with partially altered behavior. e.g. its textTheme
+// is from the superclass and based on the primaryColor but the primaryColor
+// comes from the Material theme unless overridden.
+class MaterialBasedCupertinoThemeData extends CupertinoThemeData {
+  /// Create a [MaterialBasedCupertinoThemeData] based on a Material [ThemeData]
+  /// and its `cupertinoOverrideTheme`.
+  ///
+  /// The [materialTheme] parameter must not be null.
+  MaterialBasedCupertinoThemeData({
+    required ThemeData materialTheme,
+  }) : this._(
+    materialTheme,
+    (materialTheme.cupertinoOverrideTheme ?? const CupertinoThemeData()).noDefault(),
+  );
+
+  MaterialBasedCupertinoThemeData._(
+    this._materialTheme,
+    this._cupertinoOverrideTheme,
+  ) : assert(_materialTheme != null),
+      assert(_cupertinoOverrideTheme != null),
+      // Pass all values to the superclass so Material-agnostic properties
+      // like barBackgroundColor can still behave like a normal
+      // CupertinoThemeData.
+      super.raw(
+        _cupertinoOverrideTheme.brightness,
+        _cupertinoOverrideTheme.primaryColor,
+        _cupertinoOverrideTheme.primaryContrastingColor,
+        _cupertinoOverrideTheme.textTheme,
+        _cupertinoOverrideTheme.barBackgroundColor,
+        _cupertinoOverrideTheme.scaffoldBackgroundColor,
+      );
+
+  final ThemeData _materialTheme;
+  final NoDefaultCupertinoThemeData _cupertinoOverrideTheme;
+
+  @override
+  Brightness get brightness => _cupertinoOverrideTheme.brightness ?? _materialTheme.brightness;
+
+  @override
+  Color get primaryColor => _cupertinoOverrideTheme.primaryColor ?? _materialTheme.colorScheme.primary;
+
+  @override
+  Color get primaryContrastingColor => _cupertinoOverrideTheme.primaryContrastingColor ?? _materialTheme.colorScheme.onPrimary;
+
+  @override
+  Color get scaffoldBackgroundColor => _cupertinoOverrideTheme.scaffoldBackgroundColor ?? _materialTheme.scaffoldBackgroundColor;
+
+  /// Copies the [ThemeData]'s `cupertinoOverrideTheme`.
+  ///
+  /// Only the specified override attributes of the [ThemeData]'s
+  /// `cupertinoOverrideTheme` and the newly specified parameters are in the
+  /// returned [CupertinoThemeData]. No derived attributes from iOS defaults or
+  /// from cascaded Material theme attributes are copied.
+  ///
+  /// [MaterialBasedCupertinoThemeData.copyWith] cannot change the base
+  /// Material [ThemeData]. To change the base Material [ThemeData], create a
+  /// new Material [Theme] and use `copyWith` on the Material [ThemeData]
+  /// instead.
+  @override
+  MaterialBasedCupertinoThemeData copyWith({
+    Brightness? brightness,
+    Color? primaryColor,
+    Color? primaryContrastingColor,
+    CupertinoTextThemeData? textTheme,
+    Color? barBackgroundColor,
+    Color? scaffoldBackgroundColor,
+  }) {
+    return MaterialBasedCupertinoThemeData._(
+      _materialTheme,
+      _cupertinoOverrideTheme.copyWith(
+        brightness: brightness,
+        primaryColor: primaryColor,
+        primaryContrastingColor: primaryContrastingColor,
+        textTheme: textTheme,
+        barBackgroundColor: barBackgroundColor,
+        scaffoldBackgroundColor: scaffoldBackgroundColor,
+      ),
+    );
+  }
+
+  @override
+  CupertinoThemeData resolveFrom(BuildContext context) {
+    // Only the cupertino override theme part will be resolved.
+    // If the color comes from the material theme it's not resolved.
+    return MaterialBasedCupertinoThemeData._(
+      _materialTheme,
+      _cupertinoOverrideTheme.resolveFrom(context),
+    );
+  }
+}
+
+@immutable
+class _IdentityThemeDataCacheKey {
+  const _IdentityThemeDataCacheKey(this.baseTheme, this.localTextGeometry);
+
+  final ThemeData baseTheme;
+  final TextTheme localTextGeometry;
+
+  // Using XOR to make the hash function as fast as possible (e.g. Jenkins is
+  // noticeably slower).
+  @override
+  int get hashCode => identityHashCode(baseTheme) ^ identityHashCode(localTextGeometry);
+
+  @override
+  bool operator ==(Object other) {
+    // We are explicitly ignoring the possibility that the types might not
+    // match in the interests of speed.
+    return other is _IdentityThemeDataCacheKey
+        && identical(other.baseTheme, baseTheme)
+        && identical(other.localTextGeometry, localTextGeometry);
+  }
+}
+
+/// Cache of objects of limited size that uses the first in first out eviction
+/// strategy (a.k.a least recently inserted).
+///
+/// The key that was inserted before all other keys is evicted first, i.e. the
+/// one inserted least recently.
+class _FifoCache<K, V> {
+  _FifoCache(this._maximumSize) : assert(_maximumSize != null && _maximumSize > 0);
+
+  /// In Dart the map literal uses a linked hash-map implementation, whose keys
+  /// are stored such that [Map.keys] returns them in the order they were
+  /// inserted.
+  final Map<K, V> _cache = <K, V>{};
+
+  /// Maximum number of entries to store in the cache.
+  ///
+  /// Once this many entries have been cached, the entry inserted least recently
+  /// is evicted when adding a new entry.
+  final int _maximumSize;
+
+  /// Returns the previously cached value for the given key, if available;
+  /// if not, calls the given callback to obtain it first.
+  ///
+  /// The arguments must not be null.
+  V putIfAbsent(K key, V loader()) {
+    assert(key != null);
+    assert(loader != null);
+    final V? result = _cache[key];
+    if (result != null)
+      return result;
+    if (_cache.length == _maximumSize)
+      _cache.remove(_cache.keys.first);
+    return _cache[key] = loader();
+  }
+}
+
+/// Defines the visual density of user interface components.
+///
+/// Density, in the context of a UI, is the vertical and horizontal
+/// "compactness" of the components in the UI. It is unitless, since it means
+/// different things to different UI components.
+///
+/// The default for visual densities is zero for both vertical and horizontal
+/// densities, which corresponds to the default visual density of components in
+/// the Material Design specification. It does not affect text sizes, icon
+/// sizes, or padding values.
+///
+/// For example, for buttons, it affects the spacing around the child of the
+/// button. For lists, it affects the distance between baselines of entries in
+/// the list. For chips, it only affects the vertical size, not the horizontal
+/// size.
+///
+/// See also:
+///
+///  * [ThemeData.visualDensity], where this property is used to specify the base
+///    horizontal density of Material components.
+///  * [Material design guidance on density](https://material.io/design/layout/applying-density.html).
+@immutable
+class VisualDensity with Diagnosticable {
+  /// A const constructor for [VisualDensity].
+  ///
+  /// All of the arguments must be non-null, and [horizontal] and [vertical]
+  /// must be in the interval between [minimumDensity] and [maximumDensity],
+  /// inclusive.
+  const VisualDensity({
+    this.horizontal = 0.0,
+    this.vertical = 0.0,
+  }) : assert(horizontal != null),
+       assert(vertical != null),
+       assert(vertical <= maximumDensity),
+       assert(vertical >= minimumDensity),
+       assert(horizontal <= maximumDensity),
+       assert(horizontal >= minimumDensity);
+
+  /// The minimum allowed density.
+  static const double minimumDensity = -4.0;
+
+  /// The maximum allowed density.
+  static const double maximumDensity = 4.0;
+
+  /// The default profile for [VisualDensity] in [ThemeData].
+  ///
+  /// This default value represents a visual density that is less dense than
+  /// either [comfortable] or [compact], and corresponds to density values of
+  /// zero in both axes.
+  static const VisualDensity standard = VisualDensity();
+
+  /// The profile for a "comfortable" interpretation of [VisualDensity].
+  ///
+  /// Individual components will interpret the density value independently,
+  /// making themselves more visually dense than [standard] and less dense than
+  /// [compact] to different degrees based on the Material Design specification
+  /// of the "comfortable" setting for their particular use case.
+  ///
+  /// It corresponds to a density value of -1 in both axes.
+  static const VisualDensity comfortable = VisualDensity(horizontal: -1.0, vertical: -1.0);
+
+  /// The profile for a "compact" interpretation of [VisualDensity].
+  ///
+  /// Individual components will interpret the density value independently,
+  /// making themselves more visually dense than [standard] and [comfortable] to
+  /// different degrees based on the Material Design specification of the
+  /// "comfortable" setting for their particular use case.
+  ///
+  /// It corresponds to a density value of -2 in both axes.
+  static const VisualDensity compact = VisualDensity(horizontal: -2.0, vertical: -2.0);
+
+  /// Returns a visual density that is adaptive based on the [defaultTargetPlatform].
+  ///
+  /// For desktop platforms, this returns [compact], and for other platforms,
+  /// it returns a default-constructed [VisualDensity].
+  static VisualDensity get adaptivePlatformDensity {
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.android:
+      case TargetPlatform.iOS:
+      case TargetPlatform.fuchsia:
+        break;
+      case TargetPlatform.linux:
+      case TargetPlatform.macOS:
+      case TargetPlatform.windows:
+        return compact;
+    }
+    return const VisualDensity();
+  }
+
+  /// Copy the current [VisualDensity] with the given values replacing the
+  /// current values.
+  VisualDensity copyWith({
+    double? horizontal,
+    double? vertical,
+  }) {
+    return VisualDensity(
+      horizontal: horizontal ?? this.horizontal,
+      vertical: vertical ?? this.vertical,
+    );
+  }
+
+  /// The horizontal visual density of UI components.
+  ///
+  /// This property affects only the horizontal spacing between and within
+  /// components, to allow for different UI visual densities. It does not affect
+  /// text sizes, icon sizes, or padding values. The default value is 0.0,
+  /// corresponding to the metrics specified in the Material Design
+  /// specification. The value can range from [minimumDensity] to
+  /// [maximumDensity], inclusive.
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.visualDensity], where this property is used to specify the base
+  ///    horizontal density of Material components.
+  ///  * [Material design guidance on density](https://material.io/design/layout/applying-density.html).
+  final double horizontal;
+
+  /// The vertical visual density of UI components.
+  ///
+  /// This property affects only the vertical spacing between and within
+  /// components, to allow for different UI visual densities. It does not affect
+  /// text sizes, icon sizes, or padding values. The default value is 0.0,
+  /// corresponding to the metrics specified in the Material Design
+  /// specification. The value can range from [minimumDensity] to
+  /// [maximumDensity], inclusive.
+  ///
+  /// See also:
+  ///
+  ///  * [ThemeData.visualDensity], where this property is used to specify the base
+  ///    vertical density of Material components.
+  ///  * [Material design guidance on density](https://material.io/design/layout/applying-density.html).
+  final double vertical;
+
+  /// The base adjustment in logical pixels of the visual density of UI components.
+  ///
+  /// The input density values are multiplied by a constant to arrive at a base
+  /// size adjustment that fits material design guidelines.
+  ///
+  /// Individual components may adjust this value based upon their own
+  /// individual interpretation of density.
+  Offset get baseSizeAdjustment {
+    // The number of logical pixels represented by an increase or decrease in
+    // density by one. The Material Design guidelines say to increment/decrement
+    // sized in terms of four pixel increments.
+    const double interval = 4.0;
+
+    return Offset(horizontal, vertical) * interval;
+  }
+
+  /// Linearly interpolate between two densities.
+  static VisualDensity lerp(VisualDensity a, VisualDensity b, double t) {
+    return VisualDensity(
+      horizontal: lerpDouble(a.horizontal, b.horizontal, t)!,
+      vertical: lerpDouble(a.horizontal, b.horizontal, t)!,
+    );
+  }
+
+  /// Return a copy of [constraints] whose minimum width and height have been
+  /// updated with the [baseSizeAdjustment].
+  BoxConstraints effectiveConstraints(BoxConstraints constraints){
+    assert(constraints != null && constraints.debugAssertIsValid());
+    return constraints.copyWith(
+      minWidth: (constraints.minWidth + baseSizeAdjustment.dx).clamp(0.0, double.infinity).toDouble(),
+      minHeight: (constraints.minHeight + baseSizeAdjustment.dy).clamp(0.0, double.infinity).toDouble(),
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is VisualDensity
+        && other.horizontal == horizontal
+        && other.vertical == vertical;
+  }
+
+  @override
+  int get hashCode => hashValues(horizontal, vertical);
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('horizontal', horizontal, defaultValue: 0.0));
+    properties.add(DoubleProperty('vertical', vertical, defaultValue: 0.0));
+  }
+
+  @override
+  String toStringShort() {
+    return '${super.toStringShort()}(h: ${debugFormatDouble(horizontal)}, v: ${debugFormatDouble(vertical)})';
+  }
+}
diff --git a/lib/src/material/time.dart b/lib/src/material/time.dart
new file mode 100644
index 0000000..7e0e6f5
--- /dev/null
+++ b/lib/src/material/time.dart
@@ -0,0 +1,217 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show hashValues;
+
+import 'package:flute/widgets.dart';
+
+import 'debug.dart';
+import 'material_localizations.dart';
+
+
+/// Whether the [TimeOfDay] is before or after noon.
+enum DayPeriod {
+  /// Ante meridiem (before noon).
+  am,
+
+  /// Post meridiem (after noon).
+  pm,
+}
+
+/// A value representing a time during the day, independent of the date that
+/// day might fall on or the time zone.
+///
+/// The time is represented by [hour] and [minute] pair. Once created, both
+/// values cannot be changed.
+///
+/// You can create TimeOfDay using the constructor which requires both hour and
+/// minute or using [DateTime] object.
+/// Hours are specified between 0 and 23, as in a 24-hour clock.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// TimeOfDay now = TimeOfDay.now();
+/// TimeOfDay releaseTime = TimeOfDay(hour: 15, minute: 0); // 3:00pm
+/// TimeOfDay roomBooked = TimeOfDay.fromDateTime(DateTime.parse('2018-10-20 16:30:04Z')); // 4:30pm
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [showTimePicker], which returns this type.
+///  * [MaterialLocalizations], which provides methods for formatting values of
+///    this type according to the chosen [Locale].
+///  * [DateTime], which represents date and time, and is subject to eras and
+///    time zones.
+@immutable
+class TimeOfDay {
+  /// Creates a time of day.
+  ///
+  /// The [hour] argument must be between 0 and 23, inclusive. The [minute]
+  /// argument must be between 0 and 59, inclusive.
+  const TimeOfDay({ required this.hour, required this.minute });
+
+  /// Creates a time of day based on the given time.
+  ///
+  /// The [hour] is set to the time's hour and the [minute] is set to the time's
+  /// minute in the timezone of the given [DateTime].
+  TimeOfDay.fromDateTime(DateTime time)
+    : hour = time.hour,
+      minute = time.minute;
+
+  /// Creates a time of day based on the current time.
+  ///
+  /// The [hour] is set to the current hour and the [minute] is set to the
+  /// current minute in the local time zone.
+  factory TimeOfDay.now() { return TimeOfDay.fromDateTime(DateTime.now()); }
+
+  /// The number of hours in one day, i.e. 24.
+  static const int hoursPerDay = 24;
+
+  /// The number of hours in one day period (see also [DayPeriod]), i.e. 12.
+  static const int hoursPerPeriod = 12;
+
+  /// The number of minutes in one hour, i.e. 60.
+  static const int minutesPerHour = 60;
+
+  /// Returns a new TimeOfDay with the hour and/or minute replaced.
+  TimeOfDay replacing({ int? hour, int? minute }) {
+    assert(hour == null || (hour >= 0 && hour < hoursPerDay));
+    assert(minute == null || (minute >= 0 && minute < minutesPerHour));
+    return TimeOfDay(hour: hour ?? this.hour, minute: minute ?? this.minute);
+  }
+
+  /// The selected hour, in 24 hour time from 0..23.
+  final int hour;
+
+  /// The selected minute.
+  final int minute;
+
+  /// Whether this time of day is before or after noon.
+  DayPeriod get period => hour < hoursPerPeriod ? DayPeriod.am : DayPeriod.pm;
+
+  /// Which hour of the current period (e.g., am or pm) this time is.
+  int get hourOfPeriod => hour - periodOffset;
+
+  /// The hour at which the current period starts.
+  int get periodOffset => period == DayPeriod.am ? 0 : hoursPerPeriod;
+
+  /// Returns the localized string representation of this time of day.
+  ///
+  /// This is a shortcut for [MaterialLocalizations.formatTimeOfDay].
+  String format(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    return localizations.formatTimeOfDay(
+      this,
+      alwaysUse24HourFormat: MediaQuery.of(context).alwaysUse24HourFormat,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    return other is TimeOfDay
+        && other.hour == hour
+        && other.minute == minute;
+  }
+
+  @override
+  int get hashCode => hashValues(hour, minute);
+
+  @override
+  String toString() {
+    String _addLeadingZeroIfNeeded(int value) {
+      if (value < 10)
+        return '0$value';
+      return value.toString();
+    }
+
+    final String hourLabel = _addLeadingZeroIfNeeded(hour);
+    final String minuteLabel = _addLeadingZeroIfNeeded(minute);
+
+    return '$TimeOfDay($hourLabel:$minuteLabel)';
+  }
+}
+
+/// Determines how the time picker invoked using [showTimePicker] formats and
+/// lays out the time controls.
+///
+/// The time picker provides layout configurations optimized for each of the
+/// enum values.
+enum TimeOfDayFormat {
+  /// Corresponds to the ICU 'HH:mm' pattern.
+  ///
+  /// This format uses 24-hour two-digit zero-padded hours. Controls are always
+  /// laid out horizontally. Hours are separated from minutes by one colon
+  /// character.
+  HH_colon_mm,
+
+  /// Corresponds to the ICU 'HH.mm' pattern.
+  ///
+  /// This format uses 24-hour two-digit zero-padded hours. Controls are always
+  /// laid out horizontally. Hours are separated from minutes by one dot
+  /// character.
+  HH_dot_mm,
+
+  /// Corresponds to the ICU "HH 'h' mm" pattern used in Canadian French.
+  ///
+  /// This format uses 24-hour two-digit zero-padded hours. Controls are always
+  /// laid out horizontally. Hours are separated from minutes by letter 'h'.
+  frenchCanadian,
+
+  /// Corresponds to the ICU 'H:mm' pattern.
+  ///
+  /// This format uses 24-hour non-padded variable-length hours. Controls are
+  /// always laid out horizontally. Hours are separated from minutes by one
+  /// colon character.
+  H_colon_mm,
+
+  /// Corresponds to the ICU 'h:mm a' pattern.
+  ///
+  /// This format uses 12-hour non-padded variable-length hours with a day
+  /// period. Controls are laid out horizontally in portrait mode. In landscape
+  /// mode, the day period appears vertically after (consistent with the ambient
+  /// [TextDirection]) hour-minute indicator. Hours are separated from minutes
+  /// by one colon character.
+  h_colon_mm_space_a,
+
+  /// Corresponds to the ICU 'a h:mm' pattern.
+  ///
+  /// This format uses 12-hour non-padded variable-length hours with a day
+  /// period. Controls are laid out horizontally in portrait mode. In landscape
+  /// mode, the day period appears vertically before (consistent with the
+  /// ambient [TextDirection]) hour-minute indicator. Hours are separated from
+  /// minutes by one colon character.
+  a_space_h_colon_mm,
+}
+
+/// Describes how hours are formatted.
+enum HourFormat {
+  /// Zero-padded two-digit 24-hour format ranging from "00" to "23".
+  HH,
+
+  /// Non-padded variable-length 24-hour format ranging from "0" to "23".
+  H,
+
+  /// Non-padded variable-length hour in day period format ranging from "1" to
+  /// "12".
+  h,
+}
+
+/// The [HourFormat] used for the given [TimeOfDayFormat].
+HourFormat hourFormat({ required TimeOfDayFormat of }) {
+  switch (of) {
+    case TimeOfDayFormat.h_colon_mm_space_a:
+    case TimeOfDayFormat.a_space_h_colon_mm:
+      return HourFormat.h;
+    case TimeOfDayFormat.H_colon_mm:
+      return HourFormat.H;
+    case TimeOfDayFormat.HH_dot_mm:
+    case TimeOfDayFormat.HH_colon_mm:
+    case TimeOfDayFormat.frenchCanadian:
+      return HourFormat.HH;
+  }
+}
diff --git a/lib/src/material/time_picker.dart b/lib/src/material/time_picker.dart
new file mode 100644
index 0000000..cbc3e0f
--- /dev/null
+++ b/lib/src/material/time_picker.dart
@@ -0,0 +1,2230 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:math' as math;
+import 'package:flute/ui.dart' as ui;
+
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+import 'package:flute/widgets.dart';
+
+import 'color_scheme.dart';
+import 'colors.dart';
+import 'constants.dart';
+import 'curves.dart';
+import 'debug.dart';
+import 'dialog.dart';
+import 'feedback.dart';
+import 'icon_button.dart';
+import 'icons.dart';
+import 'ink_well.dart';
+import 'input_border.dart';
+import 'input_decorator.dart';
+import 'material.dart';
+import 'material_localizations.dart';
+import 'material_state.dart';
+import 'text_button.dart';
+import 'text_form_field.dart';
+import 'text_theme.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+import 'time.dart';
+import 'time_picker_theme.dart';
+
+// Examples can assume:
+// late BuildContext context;
+
+const Duration _kDialogSizeAnimationDuration = Duration(milliseconds: 200);
+const Duration _kDialAnimateDuration = Duration(milliseconds: 200);
+const double _kTwoPi = 2 * math.pi;
+const Duration _kVibrateCommitDelay = Duration(milliseconds: 100);
+
+enum _TimePickerMode { hour, minute }
+
+const double _kTimePickerHeaderLandscapeWidth = 264.0;
+const double _kTimePickerHeaderControlHeight = 80.0;
+
+const double _kTimePickerWidthPortrait = 328.0;
+const double _kTimePickerWidthLandscape = 528.0;
+
+const double _kTimePickerHeightInput = 226.0;
+const double _kTimePickerHeightPortrait = 496.0;
+const double _kTimePickerHeightLandscape = 316.0;
+
+const double _kTimePickerHeightPortraitCollapsed = 484.0;
+const double _kTimePickerHeightLandscapeCollapsed = 304.0;
+
+const BorderRadius _kDefaultBorderRadius = BorderRadius.all(Radius.circular(4.0));
+const ShapeBorder _kDefaultShape = RoundedRectangleBorder(borderRadius: _kDefaultBorderRadius);
+
+/// Interactive input mode of the time picker dialog.
+///
+/// In [TimePickerEntryMode.dial] mode, a clock dial is displayed and
+/// the user taps or drags the time they wish to select. In
+/// TimePickerEntryMode.input] mode, [TextField]s are displayed and the user
+/// types in the time they wish to select.
+enum TimePickerEntryMode {
+  /// Tapping/dragging on a clock dial.
+  dial,
+
+  /// Text input.
+  input,
+}
+
+/// Provides properties for rendering time picker header fragments.
+@immutable
+class _TimePickerFragmentContext {
+  const _TimePickerFragmentContext({
+    required this.selectedTime,
+    required this.mode,
+    required this.onTimeChange,
+    required this.onModeChange,
+    required this.onHourDoubleTapped,
+    required this.onMinuteDoubleTapped,
+    required this.use24HourDials,
+  }) : assert(selectedTime != null),
+       assert(mode != null),
+       assert(onTimeChange != null),
+       assert(onModeChange != null),
+       assert(use24HourDials != null);
+
+  final TimeOfDay selectedTime;
+  final _TimePickerMode mode;
+  final ValueChanged<TimeOfDay> onTimeChange;
+  final ValueChanged<_TimePickerMode> onModeChange;
+  final GestureTapCallback onHourDoubleTapped;
+  final GestureTapCallback onMinuteDoubleTapped;
+  final bool use24HourDials;
+}
+
+class _TimePickerHeader extends StatelessWidget {
+  const _TimePickerHeader({
+    required this.selectedTime,
+    required this.mode,
+    required this.orientation,
+    required this.onModeChanged,
+    required this.onChanged,
+    required this.onHourDoubleTapped,
+    required this.onMinuteDoubleTapped,
+    required this.use24HourDials,
+    required this.helpText,
+  }) : assert(selectedTime != null),
+       assert(mode != null),
+       assert(orientation != null),
+       assert(use24HourDials != null);
+
+  final TimeOfDay selectedTime;
+  final _TimePickerMode mode;
+  final Orientation orientation;
+  final ValueChanged<_TimePickerMode> onModeChanged;
+  final ValueChanged<TimeOfDay> onChanged;
+  final GestureTapCallback onHourDoubleTapped;
+  final GestureTapCallback onMinuteDoubleTapped;
+  final bool use24HourDials;
+  final String? helpText;
+
+  void _handleChangeMode(_TimePickerMode value) {
+    if (value != mode)
+      onModeChanged(value);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    final ThemeData themeData = Theme.of(context);
+    final TimeOfDayFormat timeOfDayFormat = MaterialLocalizations.of(context).timeOfDayFormat(
+      alwaysUse24HourFormat: MediaQuery.of(context).alwaysUse24HourFormat,
+    );
+
+    final _TimePickerFragmentContext fragmentContext = _TimePickerFragmentContext(
+      selectedTime: selectedTime,
+      mode: mode,
+      onTimeChange: onChanged,
+      onModeChange: _handleChangeMode,
+      onHourDoubleTapped: onHourDoubleTapped,
+      onMinuteDoubleTapped: onMinuteDoubleTapped,
+      use24HourDials: use24HourDials,
+    );
+
+    final EdgeInsets padding;
+    double? width;
+    final Widget controls;
+
+    switch (orientation) {
+      case Orientation.portrait:
+        // Keep width null because in portrait we don't cap the width.
+        padding = const EdgeInsets.symmetric(horizontal: 24.0);
+        controls = Column(
+          children: <Widget>[
+            const SizedBox(height: 16.0),
+            Container(
+              height: kMinInteractiveDimension * 2,
+              child: Row(
+                children: <Widget>[
+                  if (!use24HourDials && timeOfDayFormat == TimeOfDayFormat.a_space_h_colon_mm) ...<Widget>[
+                    _DayPeriodControl(
+                      selectedTime: selectedTime,
+                      orientation: orientation,
+                      onChanged: onChanged,
+                    ),
+                    const SizedBox(width: 12.0),
+                  ],
+                  Expanded(
+                    child: Row(
+                      // Hour/minutes should not change positions in RTL locales.
+                      textDirection: TextDirection.ltr,
+                      children: <Widget>[
+                        Expanded(child: _HourControl(fragmentContext: fragmentContext)),
+                        _StringFragment(timeOfDayFormat: timeOfDayFormat),
+                        Expanded(child: _MinuteControl(fragmentContext: fragmentContext)),
+                      ],
+                    ),
+                  ),
+                  if (!use24HourDials && timeOfDayFormat != TimeOfDayFormat.a_space_h_colon_mm) ...<Widget>[
+                    const SizedBox(width: 12.0),
+                    _DayPeriodControl(
+                      selectedTime: selectedTime,
+                      orientation: orientation,
+                      onChanged: onChanged,
+                    ),
+                  ]
+                ],
+              ),
+            ),
+          ],
+        );
+        break;
+      case Orientation.landscape:
+        width = _kTimePickerHeaderLandscapeWidth;
+        padding = const EdgeInsets.symmetric(horizontal: 24.0);
+        controls = Expanded(
+          child: Column(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: <Widget>[
+              if (!use24HourDials && timeOfDayFormat == TimeOfDayFormat.a_space_h_colon_mm)
+                _DayPeriodControl(
+                  selectedTime: selectedTime,
+                  orientation: orientation,
+                  onChanged: onChanged,
+                ),
+              Container(
+                height: kMinInteractiveDimension * 2,
+                child: Row(
+                  // Hour/minutes should not change positions in RTL locales.
+                  textDirection: TextDirection.ltr,
+                  children: <Widget>[
+                    Expanded(child: _HourControl(fragmentContext: fragmentContext)),
+                    _StringFragment(timeOfDayFormat: timeOfDayFormat),
+                    Expanded(child: _MinuteControl(fragmentContext: fragmentContext)),
+                  ],
+                ),
+              ),
+              if (!use24HourDials && timeOfDayFormat != TimeOfDayFormat.a_space_h_colon_mm)
+                _DayPeriodControl(
+                  selectedTime: selectedTime,
+                  orientation: orientation,
+                  onChanged: onChanged,
+                ),
+            ],
+          ),
+        );
+        break;
+    }
+
+    return Container(
+      width: width,
+      padding: padding,
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: <Widget>[
+          const SizedBox(height: 16.0),
+          Text(
+            helpText ?? MaterialLocalizations.of(context).timePickerDialHelpText,
+            style: TimePickerTheme.of(context).helpTextStyle ?? themeData.textTheme.overline,
+          ),
+          controls,
+        ],
+      ),
+    );
+  }
+}
+
+class _HourMinuteControl extends StatelessWidget {
+  const _HourMinuteControl({
+    required this.text,
+    required this.onTap,
+    required this.onDoubleTap,
+    required this.isSelected,
+  }) : assert(text != null),
+       assert(onTap != null),
+       assert(isSelected != null);
+
+  final String text;
+  final GestureTapCallback onTap;
+  final GestureTapCallback onDoubleTap;
+  final bool isSelected;
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData themeData = Theme.of(context);
+    final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context);
+    final bool isDark = themeData.colorScheme.brightness == Brightness.dark;
+    final Color textColor = timePickerTheme.hourMinuteTextColor
+        ?? MaterialStateColor.resolveWith((Set<MaterialState> states) {
+          return states.contains(MaterialState.selected)
+              ? themeData.colorScheme.primary
+              : themeData.colorScheme.onSurface;
+        });
+    final Color backgroundColor = timePickerTheme.hourMinuteColor
+        ?? MaterialStateColor.resolveWith((Set<MaterialState> states) {
+          return states.contains(MaterialState.selected)
+              ? themeData.colorScheme.primary.withOpacity(isDark ? 0.24 : 0.12)
+              : themeData.colorScheme.onSurface.withOpacity(0.12);
+        });
+    final TextStyle style = timePickerTheme.hourMinuteTextStyle ?? themeData.textTheme.headline2!;
+    final ShapeBorder shape = timePickerTheme.hourMinuteShape ?? _kDefaultShape;
+
+    final Set<MaterialState> states = isSelected ? <MaterialState>{MaterialState.selected} : <MaterialState>{};
+    return Container(
+      height: _kTimePickerHeaderControlHeight,
+      child: Material(
+        color: MaterialStateProperty.resolveAs(backgroundColor, states),
+        clipBehavior: Clip.antiAlias,
+        shape: shape,
+        child: InkWell(
+          onTap: onTap,
+          onDoubleTap: isSelected ? onDoubleTap : null,
+          child: Center(
+            child: Text(
+              text,
+              style: style.copyWith(color: MaterialStateProperty.resolveAs(textColor, states)),
+              textScaleFactor: 1.0,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+/// Displays the hour fragment.
+///
+/// When tapped changes time picker dial mode to [_TimePickerMode.hour].
+class _HourControl extends StatelessWidget {
+  const _HourControl({
+    required this.fragmentContext,
+  });
+
+  final _TimePickerFragmentContext fragmentContext;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    final bool alwaysUse24HourFormat = MediaQuery.of(context).alwaysUse24HourFormat;
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final String formattedHour = localizations.formatHour(
+      fragmentContext.selectedTime,
+      alwaysUse24HourFormat: alwaysUse24HourFormat,
+    );
+
+    TimeOfDay hoursFromSelected(int hoursToAdd) {
+      if (fragmentContext.use24HourDials) {
+        final int selectedHour = fragmentContext.selectedTime.hour;
+        return fragmentContext.selectedTime.replacing(
+          hour: (selectedHour + hoursToAdd) % TimeOfDay.hoursPerDay,
+        );
+      } else {
+        // Cycle 1 through 12 without changing day period.
+        final int periodOffset = fragmentContext.selectedTime.periodOffset;
+        final int hours = fragmentContext.selectedTime.hourOfPeriod;
+        return fragmentContext.selectedTime.replacing(
+          hour: periodOffset + (hours + hoursToAdd) % TimeOfDay.hoursPerPeriod,
+        );
+      }
+    }
+
+    final TimeOfDay nextHour = hoursFromSelected(1);
+    final String formattedNextHour = localizations.formatHour(
+      nextHour,
+      alwaysUse24HourFormat: alwaysUse24HourFormat,
+    );
+    final TimeOfDay previousHour = hoursFromSelected(-1);
+    final String formattedPreviousHour = localizations.formatHour(
+      previousHour,
+      alwaysUse24HourFormat: alwaysUse24HourFormat,
+    );
+
+    return Semantics(
+      value: '${localizations.timePickerHourModeAnnouncement} $formattedHour',
+      excludeSemantics: true,
+      increasedValue: formattedNextHour,
+      onIncrease: () {
+        fragmentContext.onTimeChange(nextHour);
+      },
+      decreasedValue: formattedPreviousHour,
+      onDecrease: () {
+        fragmentContext.onTimeChange(previousHour);
+      },
+      child: _HourMinuteControl(
+        isSelected: fragmentContext.mode == _TimePickerMode.hour,
+        text: formattedHour,
+        onTap: Feedback.wrapForTap(() => fragmentContext.onModeChange(_TimePickerMode.hour), context)!,
+        onDoubleTap: fragmentContext.onHourDoubleTapped,
+      ),
+    );
+  }
+}
+
+/// A passive fragment showing a string value.
+class _StringFragment extends StatelessWidget {
+  const _StringFragment({
+    required this.timeOfDayFormat,
+  });
+
+  final TimeOfDayFormat timeOfDayFormat;
+
+  String _stringFragmentValue(TimeOfDayFormat timeOfDayFormat) {
+    switch (timeOfDayFormat) {
+      case TimeOfDayFormat.h_colon_mm_space_a:
+      case TimeOfDayFormat.a_space_h_colon_mm:
+      case TimeOfDayFormat.H_colon_mm:
+      case TimeOfDayFormat.HH_colon_mm:
+        return ':';
+      case TimeOfDayFormat.HH_dot_mm:
+        return '.';
+      case TimeOfDayFormat.frenchCanadian:
+        return 'h';
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context);
+    final TextStyle hourMinuteStyle = timePickerTheme.hourMinuteTextStyle ?? theme.textTheme.headline2!;
+    final Color textColor = timePickerTheme.hourMinuteTextColor ?? theme.colorScheme.onSurface;
+
+    return ExcludeSemantics(
+      child: Padding(
+        padding: const EdgeInsets.symmetric(horizontal: 6.0),
+        child: Center(
+          child: Text(
+            _stringFragmentValue(timeOfDayFormat),
+            style: hourMinuteStyle.apply(color: MaterialStateProperty.resolveAs(textColor, <MaterialState>{})),
+            textScaleFactor: 1.0,
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+/// Displays the minute fragment.
+///
+/// When tapped changes time picker dial mode to [_TimePickerMode.minute].
+class _MinuteControl extends StatelessWidget {
+  const _MinuteControl({
+    required this.fragmentContext,
+  });
+
+  final _TimePickerFragmentContext fragmentContext;
+
+  @override
+  Widget build(BuildContext context) {
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final String formattedMinute = localizations.formatMinute(fragmentContext.selectedTime);
+    final TimeOfDay nextMinute = fragmentContext.selectedTime.replacing(
+      minute: (fragmentContext.selectedTime.minute + 1) % TimeOfDay.minutesPerHour,
+    );
+    final String formattedNextMinute = localizations.formatMinute(nextMinute);
+    final TimeOfDay previousMinute = fragmentContext.selectedTime.replacing(
+      minute: (fragmentContext.selectedTime.minute - 1) % TimeOfDay.minutesPerHour,
+    );
+    final String formattedPreviousMinute = localizations.formatMinute(previousMinute);
+
+    return Semantics(
+      excludeSemantics: true,
+      value: '${localizations.timePickerMinuteModeAnnouncement} $formattedMinute',
+      increasedValue: formattedNextMinute,
+      onIncrease: () {
+        fragmentContext.onTimeChange(nextMinute);
+      },
+      decreasedValue: formattedPreviousMinute,
+      onDecrease: () {
+        fragmentContext.onTimeChange(previousMinute);
+      },
+      child: _HourMinuteControl(
+        isSelected: fragmentContext.mode == _TimePickerMode.minute,
+        text: formattedMinute,
+        onTap: Feedback.wrapForTap(() => fragmentContext.onModeChange(_TimePickerMode.minute), context)!,
+        onDoubleTap: fragmentContext.onMinuteDoubleTapped,
+      ),
+    );
+  }
+}
+
+
+/// Displays the am/pm fragment and provides controls for switching between am
+/// and pm.
+class _DayPeriodControl extends StatelessWidget {
+  const _DayPeriodControl({
+    required this.selectedTime,
+    required this.onChanged,
+    required this.orientation,
+  });
+
+  final TimeOfDay selectedTime;
+  final Orientation orientation;
+  final ValueChanged<TimeOfDay> onChanged;
+
+  void _togglePeriod() {
+    final int newHour = (selectedTime.hour + TimeOfDay.hoursPerPeriod) % TimeOfDay.hoursPerDay;
+    final TimeOfDay newTime = selectedTime.replacing(hour: newHour);
+    onChanged(newTime);
+  }
+
+  void _setAm(BuildContext context) {
+    if (selectedTime.period == DayPeriod.am) {
+      return;
+    }
+    switch (Theme.of(context).platform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        _announceToAccessibility(context, MaterialLocalizations.of(context).anteMeridiemAbbreviation);
+        break;
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        break;
+    }
+    _togglePeriod();
+  }
+
+  void _setPm(BuildContext context) {
+    if (selectedTime.period == DayPeriod.pm) {
+      return;
+    }
+    switch (Theme.of(context).platform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        _announceToAccessibility(context, MaterialLocalizations.of(context).postMeridiemAbbreviation);
+        break;
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        break;
+    }
+    _togglePeriod();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(context);
+    final ColorScheme colorScheme = Theme.of(context).colorScheme;
+    final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context);
+    final bool isDark = colorScheme.brightness == Brightness.dark;
+    final Color textColor = timePickerTheme.dayPeriodTextColor
+        ?? MaterialStateColor.resolveWith((Set<MaterialState> states) {
+          return states.contains(MaterialState.selected)
+              ? colorScheme.primary
+              : colorScheme.onSurface.withOpacity(0.60);
+        });
+    final Color backgroundColor = timePickerTheme.dayPeriodColor
+        ?? MaterialStateColor.resolveWith((Set<MaterialState> states) {
+          // The unselected day period should match the overall picker dialog
+          // color. Making it transparent enables that without being redundant
+          // and allows the optional elevation overlay for dark mode to be
+          // visible.
+          return states.contains(MaterialState.selected)
+              ? colorScheme.primary.withOpacity(isDark ? 0.24 : 0.12)
+              : Colors.transparent;
+        });
+    final bool amSelected = selectedTime.period == DayPeriod.am;
+    final Set<MaterialState> amStates = amSelected ? <MaterialState>{MaterialState.selected} : <MaterialState>{};
+    final bool pmSelected = !amSelected;
+    final Set<MaterialState> pmStates = pmSelected ? <MaterialState>{MaterialState.selected} : <MaterialState>{};
+    final TextStyle textStyle = timePickerTheme.dayPeriodTextStyle ?? Theme.of(context).textTheme.subtitle1!;
+    final TextStyle amStyle = textStyle.copyWith(
+      color: MaterialStateProperty.resolveAs(textColor, amStates),
+    );
+    final TextStyle pmStyle = textStyle.copyWith(
+      color: MaterialStateProperty.resolveAs(textColor, pmStates),
+    );
+    OutlinedBorder shape = timePickerTheme.dayPeriodShape ??
+        const RoundedRectangleBorder(borderRadius: _kDefaultBorderRadius);
+    final BorderSide borderSide = timePickerTheme.dayPeriodBorderSide ?? BorderSide(
+      color: Color.alphaBlend(colorScheme.onBackground.withOpacity(0.38), colorScheme.surface),
+    );
+    // Apply the custom borderSide.
+    shape = shape.copyWith(
+      side: borderSide,
+    );
+
+    final double buttonTextScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 2.0);
+
+    final Widget amButton = Material(
+      color: MaterialStateProperty.resolveAs(backgroundColor, amStates),
+      child: InkWell(
+        onTap: Feedback.wrapForTap(() => _setAm(context), context),
+        child: Semantics(
+          checked: amSelected,
+          inMutuallyExclusiveGroup: true,
+          button: true,
+          child: Center(
+            child: Text(
+              materialLocalizations.anteMeridiemAbbreviation,
+              style: amStyle,
+              textScaleFactor: buttonTextScaleFactor,
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final Widget pmButton = Material(
+      color: MaterialStateProperty.resolveAs(backgroundColor, pmStates),
+      child: InkWell(
+        onTap: Feedback.wrapForTap(() => _setPm(context), context),
+        child: Semantics(
+          checked: pmSelected,
+          inMutuallyExclusiveGroup: true,
+          button: true,
+          child: Center(
+            child: Text(
+              materialLocalizations.postMeridiemAbbreviation,
+              style: pmStyle,
+              textScaleFactor: buttonTextScaleFactor,
+            ),
+          ),
+        ),
+      ),
+    );
+
+    final Widget result;
+    switch (orientation) {
+      case Orientation.portrait:
+        const double width = 52.0;
+        result = _DayPeriodInputPadding(
+          minSize: const Size(width, kMinInteractiveDimension * 2),
+          orientation: orientation,
+          child: Container(
+            width: width,
+            height: _kTimePickerHeaderControlHeight,
+            child: Material(
+              clipBehavior: Clip.antiAlias,
+              color: Colors.transparent,
+              shape: shape,
+              child: Column(
+                children: <Widget>[
+                  Expanded(child: amButton),
+                  Container(
+                    decoration: BoxDecoration(
+                      border: Border(top: borderSide),
+                    ),
+                    height: 1,
+                  ),
+                  Expanded(child: pmButton),
+                ],
+              ),
+            ),
+          ),
+        );
+        break;
+      case Orientation.landscape:
+        result = _DayPeriodInputPadding(
+          minSize: const Size(0.0, kMinInteractiveDimension),
+          orientation: orientation,
+          child: Container(
+            height: 40.0,
+            child: Material(
+              clipBehavior: Clip.antiAlias,
+              color: Colors.transparent,
+              shape: shape,
+              child: Row(
+                children: <Widget>[
+                  Expanded(child: amButton),
+                  Container(
+                    decoration: BoxDecoration(
+                      border: Border(left: borderSide),
+                    ),
+                    width: 1,
+                  ),
+                  Expanded(child: pmButton),
+                ],
+              ),
+            ),
+          ),
+        );
+        break;
+    }
+    return result;
+  }
+}
+
+/// A widget to pad the area around the [_DayPeriodControl]'s inner [Material].
+class _DayPeriodInputPadding extends SingleChildRenderObjectWidget {
+  const _DayPeriodInputPadding({
+    Key? key,
+    required Widget child,
+    required this.minSize,
+    required this.orientation,
+  }) : super(key: key, child: child);
+
+  final Size minSize;
+  final Orientation orientation;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _RenderInputPadding(minSize, orientation);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, covariant _RenderInputPadding renderObject) {
+    renderObject.minSize = minSize;
+  }
+}
+
+class _RenderInputPadding extends RenderShiftedBox {
+  _RenderInputPadding(this._minSize, this.orientation, [RenderBox? child]) : super(child);
+
+  final Orientation orientation;
+
+  Size get minSize => _minSize;
+  Size _minSize;
+  set minSize(Size value) {
+    if (_minSize == value)
+      return;
+    _minSize = value;
+    markNeedsLayout();
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    if (child != null) {
+      return math.max(child!.getMinIntrinsicWidth(height), minSize.width);
+    }
+    return 0.0;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    if (child != null) {
+      return math.max(child!.getMinIntrinsicHeight(width), minSize.height);
+    }
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    if (child != null) {
+      return math.max(child!.getMaxIntrinsicWidth(height), minSize.width);
+    }
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    if (child != null) {
+      return math.max(child!.getMaxIntrinsicHeight(width), minSize.height);
+    }
+    return 0.0;
+  }
+
+  Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
+    if (child != null) {
+      final Size childSize = layoutChild(child!, constraints);
+      final double width = math.max(childSize.width, minSize.width);
+      final double height = math.max(childSize.height, minSize.height);
+      return constraints.constrain(Size(width, height));
+    }
+    return Size.zero;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _computeSize(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.dryLayoutChild,
+    );
+  }
+
+  @override
+  void performLayout() {
+    size = _computeSize(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.layoutChild,
+    );
+    if (child != null) {
+      final BoxParentData childParentData = child!.parentData! as BoxParentData;
+      childParentData.offset = Alignment.center.alongOffset(size - child!.size as Offset);
+    }
+  }
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    if (super.hitTest(result, position: position)) {
+      return true;
+    }
+
+    if (position.dx < 0.0 ||
+        position.dx > math.max(child!.size.width, minSize.width) ||
+        position.dy < 0.0 ||
+        position.dy > math.max(child!.size.height, minSize.height)) {
+      return false;
+    }
+
+    Offset newPosition = child!.size.center(Offset.zero);
+    switch (orientation) {
+      case Orientation.portrait:
+        if (position.dy > newPosition.dy) {
+          newPosition += const Offset(0.0, 1.0);
+        } else {
+          newPosition += const Offset(0.0, -1.0);
+        }
+        break;
+      case Orientation.landscape:
+        if (position.dx > newPosition.dx) {
+          newPosition += const Offset(1.0, 0.0);
+        } else {
+          newPosition += const Offset(-1.0, 0.0);
+        }
+        break;
+    }
+
+
+    return result.addWithRawTransform(
+      transform: MatrixUtils.forceToPoint(newPosition),
+      position: newPosition,
+      hitTest: (BoxHitTestResult result, Offset position) {
+        assert(position == newPosition);
+        return child!.hitTest(result, position: newPosition);
+      },
+    );
+  }
+}
+
+class _TappableLabel {
+  _TappableLabel({
+    required this.value,
+    required this.painter,
+    required this.onTap,
+  });
+
+  /// The value this label is displaying.
+  final int value;
+
+  /// Paints the text of the label.
+  final TextPainter painter;
+
+  /// Called when a tap gesture is detected on the label.
+  final VoidCallback onTap;
+}
+
+class _DialPainter extends CustomPainter {
+  _DialPainter({
+    required this.primaryLabels,
+    required this.secondaryLabels,
+    required this.backgroundColor,
+    required this.accentColor,
+    required this.dotColor,
+    required this.theta,
+    required this.textDirection,
+    required this.selectedValue,
+  }) : super(repaint: PaintingBinding.instance!.systemFonts);
+
+  final List<_TappableLabel> primaryLabels;
+  final List<_TappableLabel> secondaryLabels;
+  final Color backgroundColor;
+  final Color accentColor;
+  final Color dotColor;
+  final double theta;
+  final TextDirection textDirection;
+  final int selectedValue;
+
+  static const double _labelPadding = 28.0;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final double radius = size.shortestSide / 2.0;
+    final Offset center = Offset(size.width / 2.0, size.height / 2.0);
+    final Offset centerPoint = center;
+    canvas.drawCircle(centerPoint, radius, Paint()..color = backgroundColor);
+
+    final double labelRadius = radius - _labelPadding;
+    Offset getOffsetForTheta(double theta) {
+      return center + Offset(labelRadius * math.cos(theta), -labelRadius * math.sin(theta));
+    }
+
+    void paintLabels(List<_TappableLabel>? labels) {
+      if (labels == null)
+        return;
+      final double labelThetaIncrement = -_kTwoPi / labels.length;
+      double labelTheta = math.pi / 2.0;
+
+      for (final _TappableLabel label in labels) {
+        final TextPainter labelPainter = label.painter;
+        final Offset labelOffset = Offset(-labelPainter.width / 2.0, -labelPainter.height / 2.0);
+        labelPainter.paint(canvas, getOffsetForTheta(labelTheta) + labelOffset);
+        labelTheta += labelThetaIncrement;
+      }
+    }
+
+    paintLabels(primaryLabels);
+
+    final Paint selectorPaint = Paint()
+      ..color = accentColor;
+    final Offset focusedPoint = getOffsetForTheta(theta);
+    const double focusedRadius = _labelPadding - 4.0;
+    canvas.drawCircle(centerPoint, 4.0, selectorPaint);
+    canvas.drawCircle(focusedPoint, focusedRadius, selectorPaint);
+    selectorPaint.strokeWidth = 2.0;
+    canvas.drawLine(centerPoint, focusedPoint, selectorPaint);
+
+    // Add a dot inside the selector but only when it isn't over the labels.
+    // This checks that the selector's theta is between two labels. A remainder
+    // between 0.1 and 0.45 indicates that the selector is roughly not above any
+    // labels. The values were derived by manually testing the dial.
+    final double labelThetaIncrement = -_kTwoPi / primaryLabels.length;
+    if (theta % labelThetaIncrement > 0.1 && theta % labelThetaIncrement < 0.45) {
+      canvas.drawCircle(focusedPoint, 2.0, selectorPaint..color = dotColor);
+    }
+
+    final Rect focusedRect = Rect.fromCircle(
+      center: focusedPoint, radius: focusedRadius,
+    );
+    canvas
+      ..save()
+      ..clipPath(Path()..addOval(focusedRect));
+    paintLabels(secondaryLabels);
+    canvas.restore();
+  }
+
+  @override
+  bool shouldRepaint(_DialPainter oldPainter) {
+    return oldPainter.primaryLabels != primaryLabels
+        || oldPainter.secondaryLabels != secondaryLabels
+        || oldPainter.backgroundColor != backgroundColor
+        || oldPainter.accentColor != accentColor
+        || oldPainter.theta != theta;
+  }
+}
+
+class _Dial extends StatefulWidget {
+  const _Dial({
+    required this.selectedTime,
+    required this.mode,
+    required this.use24HourDials,
+    required this.onChanged,
+    required this.onHourSelected,
+  }) : assert(selectedTime != null),
+       assert(mode != null),
+       assert(use24HourDials != null);
+
+  final TimeOfDay selectedTime;
+  final _TimePickerMode mode;
+  final bool use24HourDials;
+  final ValueChanged<TimeOfDay>? onChanged;
+  final VoidCallback? onHourSelected;
+
+  @override
+  _DialState createState() => _DialState();
+}
+
+class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
+  @override
+  void initState() {
+    super.initState();
+    _thetaController = AnimationController(
+      duration: _kDialAnimateDuration,
+      vsync: this,
+    );
+    _thetaTween = Tween<double>(begin: _getThetaForTime(widget.selectedTime));
+    _theta = _thetaController
+      .drive(CurveTween(curve: standardEasing))
+      .drive(_thetaTween)
+      ..addListener(() => setState(() { /* _theta.value has changed */ }));
+  }
+
+  late ThemeData themeData;
+  late MaterialLocalizations localizations;
+  late MediaQueryData media;
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    assert(debugCheckHasMediaQuery(context));
+    themeData = Theme.of(context);
+    localizations = MaterialLocalizations.of(context);
+    media = MediaQuery.of(context);
+  }
+
+  @override
+  void didUpdateWidget(_Dial oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.mode != oldWidget.mode || widget.selectedTime != oldWidget.selectedTime) {
+      if (!_dragging)
+        _animateTo(_getThetaForTime(widget.selectedTime));
+    }
+  }
+
+  @override
+  void dispose() {
+    _thetaController.dispose();
+    super.dispose();
+  }
+
+  late Tween<double> _thetaTween;
+  late Animation<double> _theta;
+  late AnimationController _thetaController;
+  bool _dragging = false;
+
+  static double _nearest(double target, double a, double b) {
+    return ((target - a).abs() < (target - b).abs()) ? a : b;
+  }
+
+  void _animateTo(double targetTheta) {
+    final double currentTheta = _theta.value;
+    double beginTheta = _nearest(targetTheta, currentTheta, currentTheta + _kTwoPi);
+    beginTheta = _nearest(targetTheta, beginTheta, currentTheta - _kTwoPi);
+    _thetaTween
+      ..begin = beginTheta
+      ..end = targetTheta;
+    _thetaController
+      ..value = 0.0
+      ..forward();
+  }
+
+  double _getThetaForTime(TimeOfDay time) {
+    final int hoursFactor = widget.use24HourDials ? TimeOfDay.hoursPerDay : TimeOfDay.hoursPerPeriod;
+    final double fraction = widget.mode == _TimePickerMode.hour
+      ? (time.hour / hoursFactor) % hoursFactor
+      : (time.minute / TimeOfDay.minutesPerHour) % TimeOfDay.minutesPerHour;
+    return (math.pi / 2.0 - fraction * _kTwoPi) % _kTwoPi;
+  }
+
+  TimeOfDay _getTimeForTheta(double theta, {bool roundMinutes = false}) {
+    final double fraction = (0.25 - (theta % _kTwoPi) / _kTwoPi) % 1.0;
+    if (widget.mode == _TimePickerMode.hour) {
+      int newHour;
+      if (widget.use24HourDials) {
+        newHour = (fraction * TimeOfDay.hoursPerDay).round() % TimeOfDay.hoursPerDay;
+      } else {
+        newHour = (fraction * TimeOfDay.hoursPerPeriod).round() % TimeOfDay.hoursPerPeriod;
+        newHour = newHour + widget.selectedTime.periodOffset;
+      }
+      return widget.selectedTime.replacing(hour: newHour);
+    } else {
+      int minute = (fraction * TimeOfDay.minutesPerHour).round() % TimeOfDay.minutesPerHour;
+      if (roundMinutes) {
+        // Round the minutes to nearest 5 minute interval.
+        minute = ((minute + 2) ~/ 5) * 5 % TimeOfDay.minutesPerHour;
+      }
+      return widget.selectedTime.replacing(minute: minute);
+    }
+  }
+
+  TimeOfDay _notifyOnChangedIfNeeded({ bool roundMinutes = false }) {
+    final TimeOfDay current = _getTimeForTheta(_theta.value, roundMinutes: roundMinutes);
+    if (widget.onChanged == null)
+      return current;
+    if (current != widget.selectedTime)
+      widget.onChanged!(current);
+    return current;
+  }
+
+  void _updateThetaForPan({ bool roundMinutes = false }) {
+    setState(() {
+      final Offset offset = _position! - _center!;
+      double angle = (math.atan2(offset.dx, offset.dy) - math.pi / 2.0) % _kTwoPi;
+      if (roundMinutes) {
+        angle = _getThetaForTime(_getTimeForTheta(angle, roundMinutes: roundMinutes));
+      }
+      _thetaTween
+        ..begin = angle
+        ..end = angle; // The controller doesn't animate during the pan gesture.
+    });
+  }
+
+  Offset? _position;
+  Offset? _center;
+
+  void _handlePanStart(DragStartDetails details) {
+    assert(!_dragging);
+    _dragging = true;
+    final RenderBox box = context.findRenderObject()! as RenderBox;
+    _position = box.globalToLocal(details.globalPosition);
+    _center = box.size.center(Offset.zero);
+    _updateThetaForPan();
+    _notifyOnChangedIfNeeded();
+  }
+
+  void _handlePanUpdate(DragUpdateDetails details) {
+    _position = _position! + details.delta;
+    _updateThetaForPan();
+    _notifyOnChangedIfNeeded();
+  }
+
+  void _handlePanEnd(DragEndDetails details) {
+    assert(_dragging);
+    _dragging = false;
+    _position = null;
+    _center = null;
+    _animateTo(_getThetaForTime(widget.selectedTime));
+    if (widget.mode == _TimePickerMode.hour) {
+      if (widget.onHourSelected != null) {
+        widget.onHourSelected!();
+      }
+    }
+  }
+
+  void _handleTapUp(TapUpDetails details) {
+    final RenderBox box = context.findRenderObject()! as RenderBox;
+    _position = box.globalToLocal(details.globalPosition);
+    _center = box.size.center(Offset.zero);
+    _updateThetaForPan(roundMinutes: true);
+    final TimeOfDay newTime = _notifyOnChangedIfNeeded(roundMinutes: true);
+    if (widget.mode == _TimePickerMode.hour) {
+      if (widget.use24HourDials) {
+        _announceToAccessibility(context, localizations.formatDecimal(newTime.hour));
+      } else {
+        _announceToAccessibility(context, localizations.formatDecimal(newTime.hourOfPeriod));
+      }
+      if (widget.onHourSelected != null) {
+        widget.onHourSelected!();
+      }
+    } else {
+      _announceToAccessibility(context, localizations.formatDecimal(newTime.minute));
+    }
+    _animateTo(_getThetaForTime(_getTimeForTheta(_theta.value, roundMinutes: true)));
+    _dragging = false;
+    _position = null;
+    _center = null;
+  }
+
+  void _selectHour(int hour) {
+    _announceToAccessibility(context, localizations.formatDecimal(hour));
+    final TimeOfDay time;
+    if (widget.mode == _TimePickerMode.hour && widget.use24HourDials) {
+      time = TimeOfDay(hour: hour, minute: widget.selectedTime.minute);
+    } else {
+      if (widget.selectedTime.period == DayPeriod.am) {
+        time = TimeOfDay(hour: hour, minute: widget.selectedTime.minute);
+      } else {
+        time = TimeOfDay(hour: hour + TimeOfDay.hoursPerPeriod, minute: widget.selectedTime.minute);
+      }
+    }
+    final double angle = _getThetaForTime(time);
+    _thetaTween
+      ..begin = angle
+      ..end = angle;
+    _notifyOnChangedIfNeeded();
+  }
+
+  void _selectMinute(int minute) {
+    _announceToAccessibility(context, localizations.formatDecimal(minute));
+    final TimeOfDay time = TimeOfDay(
+      hour: widget.selectedTime.hour,
+      minute: minute,
+    );
+    final double angle = _getThetaForTime(time);
+    _thetaTween
+      ..begin = angle
+      ..end = angle;
+    _notifyOnChangedIfNeeded();
+  }
+
+  static const List<TimeOfDay> _amHours = <TimeOfDay>[
+    TimeOfDay(hour: 12, minute: 0),
+    TimeOfDay(hour: 1, minute: 0),
+    TimeOfDay(hour: 2, minute: 0),
+    TimeOfDay(hour: 3, minute: 0),
+    TimeOfDay(hour: 4, minute: 0),
+    TimeOfDay(hour: 5, minute: 0),
+    TimeOfDay(hour: 6, minute: 0),
+    TimeOfDay(hour: 7, minute: 0),
+    TimeOfDay(hour: 8, minute: 0),
+    TimeOfDay(hour: 9, minute: 0),
+    TimeOfDay(hour: 10, minute: 0),
+    TimeOfDay(hour: 11, minute: 0),
+  ];
+
+  static const List<TimeOfDay> _twentyFourHours = <TimeOfDay>[
+    TimeOfDay(hour: 0, minute: 0),
+    TimeOfDay(hour: 2, minute: 0),
+    TimeOfDay(hour: 4, minute: 0),
+    TimeOfDay(hour: 6, minute: 0),
+    TimeOfDay(hour: 8, minute: 0),
+    TimeOfDay(hour: 10, minute: 0),
+    TimeOfDay(hour: 12, minute: 0),
+    TimeOfDay(hour: 14, minute: 0),
+    TimeOfDay(hour: 16, minute: 0),
+    TimeOfDay(hour: 18, minute: 0),
+    TimeOfDay(hour: 20, minute: 0),
+    TimeOfDay(hour: 22, minute: 0),
+  ];
+
+  _TappableLabel _buildTappableLabel(TextTheme textTheme, Color color, int value, String label, VoidCallback onTap) {
+    final TextStyle style = textTheme.bodyText1!.copyWith(color: color);
+    final double labelScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 2.0);
+    return _TappableLabel(
+      value: value,
+      painter: TextPainter(
+        text: TextSpan(style: style, text: label),
+        textDirection: TextDirection.ltr,
+        textScaleFactor: labelScaleFactor,
+      )..layout(),
+      onTap: onTap,
+    );
+  }
+
+  List<_TappableLabel> _build24HourRing(TextTheme textTheme, Color color) => <_TappableLabel>[
+    for (final TimeOfDay timeOfDay in _twentyFourHours)
+      _buildTappableLabel(
+        textTheme,
+        color,
+        timeOfDay.hour,
+        localizations.formatHour(timeOfDay, alwaysUse24HourFormat: media.alwaysUse24HourFormat),
+        () {
+          _selectHour(timeOfDay.hour);
+        },
+      ),
+  ];
+
+  List<_TappableLabel> _build12HourRing(TextTheme textTheme, Color color) => <_TappableLabel>[
+    for (final TimeOfDay timeOfDay in _amHours)
+      _buildTappableLabel(
+        textTheme,
+        color,
+        timeOfDay.hour,
+        localizations.formatHour(timeOfDay, alwaysUse24HourFormat: media.alwaysUse24HourFormat),
+        () {
+          _selectHour(timeOfDay.hour);
+        },
+      ),
+  ];
+
+  List<_TappableLabel> _buildMinutes(TextTheme textTheme, Color color) {
+    const List<TimeOfDay> _minuteMarkerValues = <TimeOfDay>[
+      TimeOfDay(hour: 0, minute: 0),
+      TimeOfDay(hour: 0, minute: 5),
+      TimeOfDay(hour: 0, minute: 10),
+      TimeOfDay(hour: 0, minute: 15),
+      TimeOfDay(hour: 0, minute: 20),
+      TimeOfDay(hour: 0, minute: 25),
+      TimeOfDay(hour: 0, minute: 30),
+      TimeOfDay(hour: 0, minute: 35),
+      TimeOfDay(hour: 0, minute: 40),
+      TimeOfDay(hour: 0, minute: 45),
+      TimeOfDay(hour: 0, minute: 50),
+      TimeOfDay(hour: 0, minute: 55),
+    ];
+
+    return <_TappableLabel>[
+      for (final TimeOfDay timeOfDay in _minuteMarkerValues)
+        _buildTappableLabel(
+          textTheme,
+          color,
+          timeOfDay.minute,
+          localizations.formatMinute(timeOfDay),
+          () {
+            _selectMinute(timeOfDay.minute);
+          },
+        ),
+    ];
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final TimePickerThemeData pickerTheme = TimePickerTheme.of(context);
+    final Color backgroundColor = pickerTheme.dialBackgroundColor ?? themeData.colorScheme.onBackground.withOpacity(0.12);
+    final Color accentColor = pickerTheme.dialHandColor ?? themeData.colorScheme.primary;
+    final Color primaryLabelColor = MaterialStateProperty.resolveAs(pickerTheme.dialTextColor, <MaterialState>{}) ?? themeData.colorScheme.onSurface;
+    final Color secondaryLabelColor = MaterialStateProperty.resolveAs(pickerTheme.dialTextColor, <MaterialState>{MaterialState.selected}) ?? themeData.colorScheme.onPrimary;
+    List<_TappableLabel> primaryLabels;
+    List<_TappableLabel> secondaryLabels;
+    final int selectedDialValue;
+    switch (widget.mode) {
+      case _TimePickerMode.hour:
+        if (widget.use24HourDials) {
+          selectedDialValue = widget.selectedTime.hour;
+          primaryLabels = _build24HourRing(theme.textTheme, primaryLabelColor);
+          secondaryLabels = _build24HourRing(theme.accentTextTheme, secondaryLabelColor);
+        } else {
+          selectedDialValue = widget.selectedTime.hourOfPeriod;
+          primaryLabels = _build12HourRing(theme.textTheme, primaryLabelColor);
+          secondaryLabels = _build12HourRing(theme.accentTextTheme, secondaryLabelColor);
+        }
+        break;
+      case _TimePickerMode.minute:
+        selectedDialValue = widget.selectedTime.minute;
+        primaryLabels = _buildMinutes(theme.textTheme, primaryLabelColor);
+        secondaryLabels = _buildMinutes(theme.accentTextTheme, secondaryLabelColor);
+        break;
+    }
+
+    return GestureDetector(
+      excludeFromSemantics: true,
+      onPanStart: _handlePanStart,
+      onPanUpdate: _handlePanUpdate,
+      onPanEnd: _handlePanEnd,
+      onTapUp: _handleTapUp,
+      child: CustomPaint(
+        key: const ValueKey<String>('time-picker-dial'),
+        painter: _DialPainter(
+          selectedValue: selectedDialValue,
+          primaryLabels: primaryLabels,
+          secondaryLabels: secondaryLabels,
+          backgroundColor: backgroundColor,
+          accentColor: accentColor,
+          dotColor: theme.colorScheme.surface,
+          theta: _theta.value,
+          textDirection: Directionality.of(context),
+        ),
+      ),
+    );
+  }
+}
+
+class _TimePickerInput extends StatefulWidget {
+  const _TimePickerInput({
+    Key? key,
+    required this.initialSelectedTime,
+    required this.helpText,
+    required this.autofocusHour,
+    required this.autofocusMinute,
+    required this.onChanged,
+  }) : assert(initialSelectedTime != null),
+       assert(onChanged != null),
+       super(key: key);
+
+  /// The time initially selected when the dialog is shown.
+  final TimeOfDay initialSelectedTime;
+
+  /// Optionally provide your own help text to the time picker.
+  final String? helpText;
+
+  final bool? autofocusHour;
+
+  final bool? autofocusMinute;
+
+  final ValueChanged<TimeOfDay> onChanged;
+
+  @override
+  _TimePickerInputState createState() => _TimePickerInputState();
+}
+
+class _TimePickerInputState extends State<_TimePickerInput> {
+  late TimeOfDay _selectedTime;
+  bool hourHasError = false;
+  bool minuteHasError = false;
+
+  @override
+  void initState() {
+    super.initState();
+    _selectedTime = widget.initialSelectedTime;
+  }
+
+  int? _parseHour(String? value) {
+    if (value == null) {
+      return null;
+    }
+
+    int? newHour = int.tryParse(value);
+    if (newHour == null) {
+      return null;
+    }
+
+    if (MediaQuery.of(context).alwaysUse24HourFormat) {
+      if (newHour >= 0 && newHour < 24) {
+        return newHour;
+      }
+    } else {
+      if (newHour > 0 && newHour < 13) {
+        if ((_selectedTime.period == DayPeriod.pm && newHour != 12)
+            || (_selectedTime.period == DayPeriod.am && newHour == 12)) {
+          newHour = (newHour + TimeOfDay.hoursPerPeriod) % TimeOfDay.hoursPerDay;
+        }
+        return newHour;
+      }
+    }
+    return null;
+  }
+
+  int? _parseMinute(String? value) {
+    if (value == null) {
+      return null;
+    }
+
+    final int? newMinute = int.tryParse(value);
+    if (newMinute == null) {
+      return null;
+    }
+
+    if (newMinute >= 0 && newMinute < 60) {
+      return newMinute;
+    }
+    return null;
+  }
+
+  void _handleHourSavedSubmitted(String? value) {
+    final int? newHour = _parseHour(value);
+    if (newHour != null) {
+      _selectedTime = TimeOfDay(hour: newHour, minute: _selectedTime.minute);
+      widget.onChanged(_selectedTime);
+    }
+  }
+
+  void _handleHourChanged(String value) {
+    final int? newHour = _parseHour(value);
+    if (newHour != null && value.length == 2) {
+      // If a valid hour is typed, move focus to the minute TextField.
+      FocusScope.of(context).nextFocus();
+    }
+  }
+
+  void _handleMinuteSavedSubmitted(String? value) {
+    final int? newMinute = _parseMinute(value);
+    if (newMinute != null) {
+      _selectedTime = TimeOfDay(hour: _selectedTime.hour, minute: int.parse(value!));
+      widget.onChanged(_selectedTime);
+    }
+  }
+
+  void _handleDayPeriodChanged(TimeOfDay value) {
+    _selectedTime = value;
+    widget.onChanged(_selectedTime);
+  }
+
+  String? _validateHour(String? value) {
+    final int? newHour = _parseHour(value);
+    setState(() {
+      hourHasError = newHour == null;
+    });
+    // This is used as the validator for the [TextFormField].
+    // Returning an empty string allows the field to go into an error state.
+    // Returning null means no error in the validation of the entered text.
+    return newHour == null ? '' : null;
+  }
+
+  String? _validateMinute(String? value) {
+    final int? newMinute = _parseMinute(value);
+    setState(() {
+      minuteHasError = newMinute == null;
+    });
+    // This is used as the validator for the [TextFormField].
+    // Returning an empty string allows the field to go into an error state.
+    // Returning null means no error in the validation of the entered text.
+    return newMinute == null ? '' : null;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    final MediaQueryData media = MediaQuery.of(context);
+    final TimeOfDayFormat timeOfDayFormat = MaterialLocalizations.of(context).timeOfDayFormat(alwaysUse24HourFormat: media.alwaysUse24HourFormat);
+    final bool use24HourDials = hourFormat(of: timeOfDayFormat) != HourFormat.h;
+    final ThemeData theme = Theme.of(context);
+    final TextStyle hourMinuteStyle = TimePickerTheme.of(context).hourMinuteTextStyle ?? theme.textTheme.headline2!;
+
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: <Widget>[
+          Text(
+            widget.helpText ?? MaterialLocalizations.of(context).timePickerInputHelpText,
+            style: TimePickerTheme.of(context).helpTextStyle ?? theme.textTheme.overline,
+          ),
+          const SizedBox(height: 16.0),
+          Row(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: <Widget>[
+              if (!use24HourDials && timeOfDayFormat == TimeOfDayFormat.a_space_h_colon_mm) ...<Widget>[
+                _DayPeriodControl(
+                  selectedTime: _selectedTime,
+                  orientation: Orientation.portrait,
+                  onChanged: _handleDayPeriodChanged,
+                ),
+                const SizedBox(width: 12.0),
+              ],
+              Expanded(
+                child: Row(
+                  crossAxisAlignment: CrossAxisAlignment.start,
+                  // Hour/minutes should not change positions in RTL locales.
+                  textDirection: TextDirection.ltr,
+                  children: <Widget>[
+                    Expanded(
+                      child: Column(
+                        crossAxisAlignment: CrossAxisAlignment.start,
+                        children: <Widget>[
+                          const SizedBox(height: 8.0),
+                          _HourTextField(
+                            selectedTime: _selectedTime,
+                            style: hourMinuteStyle,
+                            autofocus: widget.autofocusHour,
+                            validator: _validateHour,
+                            onSavedSubmitted: _handleHourSavedSubmitted,
+                            onChanged: _handleHourChanged,
+                          ),
+                          const SizedBox(height: 8.0),
+                          if (!hourHasError && !minuteHasError)
+                            ExcludeSemantics(
+                              child: Text(
+                                MaterialLocalizations.of(context).timePickerHourLabel,
+                                style: theme.textTheme.caption,
+                                maxLines: 1,
+                                overflow: TextOverflow.ellipsis,
+                              ),
+                            ),
+                        ],
+                      ),
+                    ),
+                    Container(
+                      margin: const EdgeInsets.only(top: 8.0),
+                      height: _kTimePickerHeaderControlHeight,
+                      child: _StringFragment(timeOfDayFormat: timeOfDayFormat),
+                    ),
+                    Expanded(
+                      child: Column(
+                        crossAxisAlignment: CrossAxisAlignment.start,
+                        children: <Widget>[
+                          const SizedBox(height: 8.0),
+                          _MinuteTextField(
+                            selectedTime: _selectedTime,
+                            style: hourMinuteStyle,
+                            autofocus: widget.autofocusMinute,
+                            validator: _validateMinute,
+                            onSavedSubmitted: _handleMinuteSavedSubmitted,
+                          ),
+                          const SizedBox(height: 8.0),
+                          if (!hourHasError && !minuteHasError)
+                            ExcludeSemantics(
+                              child: Text(
+                                MaterialLocalizations.of(context).timePickerMinuteLabel,
+                                style: theme.textTheme.caption,
+                                maxLines: 1,
+                                overflow: TextOverflow.ellipsis,
+                              ),
+                            ),
+                        ],
+                      ),
+                    ),
+                  ],
+                ),
+              ),
+              if (!use24HourDials && timeOfDayFormat != TimeOfDayFormat.a_space_h_colon_mm) ...<Widget>[
+                const SizedBox(width: 12.0),
+                _DayPeriodControl(
+                  selectedTime: _selectedTime,
+                  orientation: Orientation.portrait,
+                  onChanged: _handleDayPeriodChanged,
+                ),
+              ],
+            ],
+          ),
+          if (hourHasError || minuteHasError)
+            Text(
+              MaterialLocalizations.of(context).invalidTimeLabel,
+              style: theme.textTheme.bodyText2!.copyWith(color: theme.colorScheme.error),
+            )
+          else
+            const SizedBox(height: 2.0),
+        ],
+      ),
+    );
+  }
+}
+
+class _HourTextField extends StatelessWidget {
+  const _HourTextField({
+    Key? key,
+    required this.selectedTime,
+    required this.style,
+    required this.autofocus,
+    required this.validator,
+    required this.onSavedSubmitted,
+    required this.onChanged,
+  }) : super(key: key);
+
+  final TimeOfDay selectedTime;
+  final TextStyle style;
+  final bool? autofocus;
+  final FormFieldValidator<String> validator;
+  final ValueChanged<String?> onSavedSubmitted;
+  final ValueChanged<String> onChanged;
+
+  @override
+  Widget build(BuildContext context) {
+    return _HourMinuteTextField(
+      selectedTime: selectedTime,
+      isHour: true,
+      autofocus: autofocus,
+      style: style,
+      semanticHintText: MaterialLocalizations.of(context).timePickerHourLabel,
+      validator: validator,
+      onSavedSubmitted: onSavedSubmitted,
+      onChanged: onChanged,
+    );
+  }
+}
+
+class _MinuteTextField extends StatelessWidget {
+  const _MinuteTextField({
+    Key? key,
+    required this.selectedTime,
+    required this.style,
+    required this.autofocus,
+    required this.validator,
+    required this.onSavedSubmitted,
+  }) : super(key: key);
+
+  final TimeOfDay selectedTime;
+  final TextStyle style;
+  final bool? autofocus;
+  final FormFieldValidator<String> validator;
+  final ValueChanged<String?> onSavedSubmitted;
+
+  @override
+  Widget build(BuildContext context) {
+    return _HourMinuteTextField(
+      selectedTime: selectedTime,
+      isHour: false,
+      autofocus: autofocus,
+      style: style,
+      semanticHintText: MaterialLocalizations.of(context).timePickerMinuteLabel,
+      validator: validator,
+      onSavedSubmitted: onSavedSubmitted,
+    );
+  }
+}
+
+class _HourMinuteTextField extends StatefulWidget {
+  const _HourMinuteTextField({
+    Key? key,
+    required this.selectedTime,
+    required this.isHour,
+    required this.autofocus,
+    required this.style,
+    required this.semanticHintText,
+    required this.validator,
+    required this.onSavedSubmitted,
+    this.onChanged,
+  }) : super(key: key);
+
+  final TimeOfDay selectedTime;
+  final bool isHour;
+  final bool? autofocus;
+  final TextStyle style;
+  final String semanticHintText;
+  final FormFieldValidator<String> validator;
+  final ValueChanged<String?> onSavedSubmitted;
+  final ValueChanged<String>? onChanged;
+
+  @override
+  _HourMinuteTextFieldState createState() => _HourMinuteTextFieldState();
+}
+
+class _HourMinuteTextFieldState extends State<_HourMinuteTextField> {
+  TextEditingController? controller;
+  late FocusNode focusNode;
+
+  @override
+  void initState() {
+    super.initState();
+    focusNode = FocusNode()..addListener(() {
+      setState(() { }); // Rebuild.
+    });
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    controller ??= TextEditingController(text: _formattedValue);
+  }
+
+  String get _formattedValue {
+    final bool alwaysUse24HourFormat = MediaQuery.of(context).alwaysUse24HourFormat;
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    return !widget.isHour ? localizations.formatMinute(widget.selectedTime) : localizations.formatHour(
+      widget.selectedTime,
+      alwaysUse24HourFormat: alwaysUse24HourFormat,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context);
+    final ColorScheme colorScheme = theme.colorScheme;
+
+    final InputDecorationTheme? inputDecorationTheme = timePickerTheme.inputDecorationTheme;
+    InputDecoration inputDecoration;
+    if (inputDecorationTheme != null) {
+      inputDecoration = const InputDecoration().applyDefaults(inputDecorationTheme);
+    } else {
+      inputDecoration = InputDecoration(
+        contentPadding: EdgeInsets.zero,
+        filled: true,
+        enabledBorder: const OutlineInputBorder(
+          borderSide: BorderSide(color: Colors.transparent),
+        ),
+        errorBorder: OutlineInputBorder(
+          borderSide: BorderSide(color: colorScheme.error, width: 2.0),
+        ),
+        focusedBorder: OutlineInputBorder(
+          borderSide: BorderSide(color: colorScheme.primary, width: 2.0),
+        ),
+        focusedErrorBorder: OutlineInputBorder(
+          borderSide: BorderSide(color: colorScheme.error, width: 2.0),
+        ),
+        hintStyle: widget.style.copyWith(color: colorScheme.onSurface.withOpacity(0.36)),
+        // TODO(rami-a): Remove this logic once https://github.com/flutter/flutter/issues/54104 is fixed.
+        errorStyle: const TextStyle(fontSize: 0.0, height: 0.0), // Prevent the error text from appearing.
+      );
+    }
+    final Color unfocusedFillColor = timePickerTheme.hourMinuteColor ?? colorScheme.onSurface.withOpacity(0.12);
+    // If screen reader is in use, make the hint text say hours/minutes.
+    // Otherwise, remove the hint text when focused because the centered cursor
+    // appears odd above the hint text.
+    //
+    // TODO(rami-a): Once https://github.com/flutter/flutter/issues/67571 is
+    // resolved, remove the window check for semantics being enabled on web.
+    final String? hintText = MediaQuery.of(context).accessibleNavigation || ui.window.semanticsEnabled
+        ? widget.semanticHintText
+        : (focusNode.hasFocus ? null : _formattedValue);
+    inputDecoration = inputDecoration.copyWith(
+      hintText: hintText,
+      fillColor: focusNode.hasFocus ? Colors.transparent : inputDecorationTheme?.fillColor ?? unfocusedFillColor,
+    );
+
+    return SizedBox(
+      height: _kTimePickerHeaderControlHeight,
+      child: MediaQuery(
+        data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
+        child: TextFormField(
+          autofocus: widget.autofocus ?? false,
+          expands: true,
+          maxLines: null,
+          inputFormatters: <TextInputFormatter>[
+            LengthLimitingTextInputFormatter(2),
+          ],
+          focusNode: focusNode,
+          textAlign: TextAlign.center,
+          keyboardType: TextInputType.number,
+          style: widget.style.copyWith(color: timePickerTheme.hourMinuteTextColor ?? colorScheme.onSurface),
+          controller: controller,
+          decoration: inputDecoration,
+          validator: widget.validator,
+          onEditingComplete: () => widget.onSavedSubmitted(controller!.text),
+          onSaved: widget.onSavedSubmitted,
+          onFieldSubmitted: widget.onSavedSubmitted,
+          onChanged: widget.onChanged,
+        ),
+      ),
+    );
+  }
+}
+
+/// A material design time picker designed to appear inside a popup dialog.
+///
+/// Pass this widget to [showDialog]. The value returned by [showDialog] is the
+/// selected [TimeOfDay] if the user taps the "OK" button, or null if the user
+/// taps the "CANCEL" button. The selected time is reported by calling
+/// [Navigator.pop].
+class _TimePickerDialog extends StatefulWidget {
+  /// Creates a material time picker.
+  ///
+  /// [initialTime] must not be null.
+  const _TimePickerDialog({
+    Key? key,
+    required this.initialTime,
+    required this.cancelText,
+    required this.confirmText,
+    required this.helpText,
+    this.initialEntryMode = TimePickerEntryMode.dial,
+  }) : assert(initialTime != null),
+       super(key: key);
+
+  /// The time initially selected when the dialog is shown.
+  final TimeOfDay initialTime;
+
+  /// The entry mode for the picker. Whether it's text input or a dial.
+  final TimePickerEntryMode initialEntryMode;
+
+  /// Optionally provide your own text for the cancel button.
+  ///
+  /// If null, the button uses [MaterialLocalizations.cancelButtonLabel].
+  final String? cancelText;
+
+  /// Optionally provide your own text for the confirm button.
+  ///
+  /// If null, the button uses [MaterialLocalizations.okButtonLabel].
+  final String? confirmText;
+
+  /// Optionally provide your own help text to the header of the time picker.
+  final String? helpText;
+
+  @override
+  _TimePickerDialogState createState() => _TimePickerDialogState();
+}
+
+class _TimePickerDialogState extends State<_TimePickerDialog> {
+  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
+
+  @override
+  void initState() {
+    super.initState();
+    _selectedTime = widget.initialTime;
+    _entryMode = widget.initialEntryMode;
+    _autoValidate = false;
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    localizations = MaterialLocalizations.of(context);
+    _announceInitialTimeOnce();
+    _announceModeOnce();
+  }
+
+  late TimePickerEntryMode _entryMode;
+  _TimePickerMode _mode = _TimePickerMode.hour;
+  _TimePickerMode? _lastModeAnnounced;
+  late bool _autoValidate;
+  bool? _autofocusHour;
+  bool? _autofocusMinute;
+
+  TimeOfDay get selectedTime => _selectedTime;
+  late TimeOfDay _selectedTime;
+
+  Timer? _vibrateTimer;
+  late MaterialLocalizations localizations;
+
+  void _vibrate() {
+    switch (Theme.of(context).platform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        _vibrateTimer?.cancel();
+        _vibrateTimer = Timer(_kVibrateCommitDelay, () {
+          HapticFeedback.vibrate();
+          _vibrateTimer = null;
+        });
+        break;
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        break;
+    }
+  }
+
+  void _handleModeChanged(_TimePickerMode mode) {
+    _vibrate();
+    setState(() {
+      _mode = mode;
+      _announceModeOnce();
+    });
+  }
+
+  void _handleEntryModeToggle() {
+    setState(() {
+      switch (_entryMode) {
+        case TimePickerEntryMode.dial:
+          _autoValidate = false;
+          _entryMode = TimePickerEntryMode.input;
+          break;
+        case TimePickerEntryMode.input:
+          _formKey.currentState!.save();
+          _autofocusHour = false;
+          _autofocusMinute = false;
+          _entryMode = TimePickerEntryMode.dial;
+          break;
+      }
+    });
+  }
+
+  void _announceModeOnce() {
+    if (_lastModeAnnounced == _mode) {
+      // Already announced it.
+      return;
+    }
+
+    switch (_mode) {
+      case _TimePickerMode.hour:
+        _announceToAccessibility(context, localizations.timePickerHourModeAnnouncement);
+        break;
+      case _TimePickerMode.minute:
+        _announceToAccessibility(context, localizations.timePickerMinuteModeAnnouncement);
+        break;
+    }
+    _lastModeAnnounced = _mode;
+  }
+
+  bool _announcedInitialTime = false;
+
+  void _announceInitialTimeOnce() {
+    if (_announcedInitialTime)
+      return;
+
+    final MediaQueryData media = MediaQuery.of(context);
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    _announceToAccessibility(
+      context,
+      localizations.formatTimeOfDay(widget.initialTime, alwaysUse24HourFormat: media.alwaysUse24HourFormat),
+    );
+    _announcedInitialTime = true;
+  }
+
+  void _handleTimeChanged(TimeOfDay value) {
+    _vibrate();
+    setState(() {
+      _selectedTime = value;
+    });
+  }
+
+  void _handleHourDoubleTapped() {
+    _autofocusHour = true;
+    _handleEntryModeToggle();
+  }
+
+  void _handleMinuteDoubleTapped() {
+    _autofocusMinute = true;
+    _handleEntryModeToggle();
+  }
+
+  void _handleHourSelected() {
+    setState(() {
+      _mode = _TimePickerMode.minute;
+    });
+  }
+
+  void _handleCancel() {
+    Navigator.pop(context);
+  }
+
+  void _handleOk() {
+    if (_entryMode == TimePickerEntryMode.input) {
+      final FormState form = _formKey.currentState!;
+      if (!form.validate()) {
+        setState(() { _autoValidate = true; });
+        return;
+      }
+      form.save();
+    }
+    Navigator.pop(context, _selectedTime);
+  }
+
+  Size _dialogSize(BuildContext context) {
+    final Orientation orientation = MediaQuery.of(context).orientation;
+    final ThemeData theme = Theme.of(context);
+    // Constrain the textScaleFactor to prevent layout issues. Since only some
+    // parts of the time picker scale up with textScaleFactor, we cap the factor
+    // to 1.1 as that provides enough space to reasonably fit all the content.
+    final double textScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 1.1);
+
+    final double timePickerWidth;
+    final double timePickerHeight;
+    switch (_entryMode) {
+      case TimePickerEntryMode.dial:
+        switch (orientation) {
+          case Orientation.portrait:
+            timePickerWidth = _kTimePickerWidthPortrait;
+            timePickerHeight = theme.materialTapTargetSize == MaterialTapTargetSize.padded
+                ? _kTimePickerHeightPortrait
+                : _kTimePickerHeightPortraitCollapsed;
+            break;
+          case Orientation.landscape:
+            timePickerWidth = _kTimePickerWidthLandscape * textScaleFactor;
+            timePickerHeight = theme.materialTapTargetSize == MaterialTapTargetSize.padded
+                ? _kTimePickerHeightLandscape
+                : _kTimePickerHeightLandscapeCollapsed;
+            break;
+        }
+        break;
+      case TimePickerEntryMode.input:
+        timePickerWidth = _kTimePickerWidthPortrait;
+        timePickerHeight = _kTimePickerHeightInput;
+        break;
+    }
+    return Size(timePickerWidth, timePickerHeight * textScaleFactor);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    final MediaQueryData media = MediaQuery.of(context);
+    final TimeOfDayFormat timeOfDayFormat = localizations.timeOfDayFormat(alwaysUse24HourFormat: media.alwaysUse24HourFormat);
+    final bool use24HourDials = hourFormat(of: timeOfDayFormat) != HourFormat.h;
+    final ThemeData theme = Theme.of(context);
+    final ShapeBorder shape = TimePickerTheme.of(context).shape ?? _kDefaultShape;
+    final Orientation orientation = media.orientation;
+
+    final Widget actions = Row(
+      children: <Widget>[
+        const SizedBox(width: 10.0),
+        IconButton(
+          color: TimePickerTheme.of(context).entryModeIconColor ?? theme.colorScheme.onSurface.withOpacity(
+            theme.colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
+          ),
+          onPressed: _handleEntryModeToggle,
+          icon: Icon(_entryMode == TimePickerEntryMode.dial ? Icons.keyboard : Icons.access_time),
+          tooltip: _entryMode == TimePickerEntryMode.dial
+              ? MaterialLocalizations.of(context).inputTimeModeButtonLabel
+              : MaterialLocalizations.of(context).dialModeButtonLabel,
+        ),
+        Expanded(
+          child: Container(
+            alignment: AlignmentDirectional.centerEnd,
+            constraints: const BoxConstraints(minHeight: 52.0),
+            padding: const EdgeInsets.symmetric(horizontal: 8),
+            child: OverflowBar(
+              spacing: 8,
+              overflowAlignment: OverflowBarAlignment.end,
+              children: <Widget>[
+                TextButton(
+                  onPressed: _handleCancel,
+                  child: Text(widget.cancelText ?? localizations.cancelButtonLabel),
+                ),
+                TextButton(
+                  onPressed: _handleOk,
+                  child: Text(widget.confirmText ?? localizations.okButtonLabel),
+                ),
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+
+    final Widget picker;
+    switch (_entryMode) {
+      case TimePickerEntryMode.dial:
+        final Widget dial = Padding(
+          padding: orientation == Orientation.portrait ? const EdgeInsets.symmetric(horizontal: 36, vertical: 24) : const EdgeInsets.all(24),
+          child: ExcludeSemantics(
+            child: AspectRatio(
+              aspectRatio: 1.0,
+              child: _Dial(
+                mode: _mode,
+                use24HourDials: use24HourDials,
+                selectedTime: _selectedTime,
+                onChanged: _handleTimeChanged,
+                onHourSelected: _handleHourSelected,
+              ),
+            ),
+          ),
+        );
+
+        final Widget header = _TimePickerHeader(
+          selectedTime: _selectedTime,
+          mode: _mode,
+          orientation: orientation,
+          onModeChanged: _handleModeChanged,
+          onChanged: _handleTimeChanged,
+          onHourDoubleTapped: _handleHourDoubleTapped,
+          onMinuteDoubleTapped: _handleMinuteDoubleTapped,
+          use24HourDials: use24HourDials,
+          helpText: widget.helpText,
+        );
+
+        switch (orientation) {
+          case Orientation.portrait:
+            picker = Column(
+              mainAxisSize: MainAxisSize.min,
+              crossAxisAlignment: CrossAxisAlignment.stretch,
+              children: <Widget>[
+                header,
+                Expanded(
+                  child: Column(
+                    mainAxisSize: MainAxisSize.min,
+                    children: <Widget>[
+                      // Dial grows and shrinks with the available space.
+                      Expanded(child: dial),
+                      actions,
+                    ],
+                  ),
+                ),
+              ],
+            );
+            break;
+          case Orientation.landscape:
+            picker = Column(
+              children: <Widget>[
+                Expanded(
+                  child: Row(
+                    children: <Widget>[
+                      header,
+                      Expanded(child: dial),
+                    ],
+                  ),
+                ),
+                actions,
+              ],
+            );
+            break;
+        }
+        break;
+      case TimePickerEntryMode.input:
+        picker = Form(
+          key: _formKey,
+          autovalidate: _autoValidate,
+          child: SingleChildScrollView(
+            child: Column(
+              mainAxisSize: MainAxisSize.min,
+              children: <Widget>[
+                _TimePickerInput(
+                  initialSelectedTime: _selectedTime,
+                  helpText: widget.helpText,
+                  autofocusHour: _autofocusHour,
+                  autofocusMinute: _autofocusMinute,
+                  onChanged: _handleTimeChanged,
+                ),
+                actions,
+              ],
+            ),
+          ),
+        );
+        break;
+    }
+
+    final Size dialogSize = _dialogSize(context);
+    return Dialog(
+      shape: shape,
+      backgroundColor: TimePickerTheme.of(context).backgroundColor ?? theme.colorScheme.surface,
+      insetPadding: EdgeInsets.symmetric(
+        horizontal: 16.0,
+        vertical: _entryMode == TimePickerEntryMode.input ? 0.0 : 24.0,
+      ),
+      child: AnimatedContainer(
+        width: dialogSize.width,
+        height: dialogSize.height,
+        duration: _kDialogSizeAnimationDuration,
+        curve: Curves.easeIn,
+        child: picker,
+      ),
+    );
+  }
+
+  @override
+  void dispose() {
+    _vibrateTimer?.cancel();
+    _vibrateTimer = null;
+    super.dispose();
+  }
+}
+
+/// Shows a dialog containing a material design time picker.
+///
+/// The returned Future resolves to the time selected by the user when the user
+/// closes the dialog. If the user cancels the dialog, null is returned.
+///
+/// {@tool snippet}
+/// Show a dialog with [initialTime] equal to the current time.
+///
+/// ```dart
+/// Future<TimeOfDay?> selectedTime = showTimePicker(
+///   initialTime: TimeOfDay.now(),
+///   context: context,
+/// );
+/// ```
+/// {@end-tool}
+///
+/// The [context], [useRootNavigator] and [routeSettings] arguments are passed to
+/// [showDialog], the documentation for which discusses how it is used.
+///
+/// The [builder] parameter can be used to wrap the dialog widget
+/// to add inherited widgets like [Localizations.override],
+/// [Directionality], or [MediaQuery].
+///
+/// The `initialEntryMode` parameter can be used to
+/// determine the initial time entry selection of the picker (either a clock
+/// dial or text input).
+///
+/// Optional strings for the [helpText], [cancelText], and [confirmText] can be
+/// provided to override the default values.
+///
+/// By default, the time picker gets its colors from the overall theme's
+/// [ColorScheme]. The time picker can be further customized by providing a
+/// [TimePickerThemeData] to the overall theme.
+///
+/// {@tool snippet}
+/// Show a dialog with the text direction overridden to be [TextDirection.rtl].
+///
+/// ```dart
+/// Future<TimeOfDay?> selectedTimeRTL = showTimePicker(
+///   context: context,
+///   initialTime: TimeOfDay.now(),
+///   builder: (BuildContext context, Widget? child) {
+///     return Directionality(
+///       textDirection: TextDirection.rtl,
+///       child: child!,
+///     );
+///   },
+/// );
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+/// Show a dialog with time unconditionally displayed in 24 hour format.
+///
+/// ```dart
+/// Future<TimeOfDay?> selectedTime24Hour = showTimePicker(
+///   context: context,
+///   initialTime: TimeOfDay(hour: 10, minute: 47),
+///   builder: (BuildContext context, Widget? child) {
+///     return MediaQuery(
+///       data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true),
+///       child: child!,
+///     );
+///   },
+/// );
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [showDatePicker], which shows a dialog that contains a material design
+///    date picker.
+///  * [TimePickerThemeData], which allows you to customize the colors,
+///    typography, and shape of the time picker.
+Future<TimeOfDay?> showTimePicker({
+  required BuildContext context,
+  required TimeOfDay initialTime,
+  TransitionBuilder? builder,
+  bool useRootNavigator = true,
+  TimePickerEntryMode initialEntryMode = TimePickerEntryMode.dial,
+  String? cancelText,
+  String? confirmText,
+  String? helpText,
+  RouteSettings? routeSettings,
+}) async {
+  assert(context != null);
+  assert(initialTime != null);
+  assert(useRootNavigator != null);
+  assert(initialEntryMode != null);
+  assert(debugCheckHasMaterialLocalizations(context));
+
+  final Widget dialog = _TimePickerDialog(
+    initialTime: initialTime,
+    initialEntryMode: initialEntryMode,
+    cancelText: cancelText,
+    confirmText: confirmText,
+    helpText: helpText,
+  );
+  return await showDialog<TimeOfDay>(
+    context: context,
+    useRootNavigator: useRootNavigator,
+    builder: (BuildContext context) {
+      return builder == null ? dialog : builder(context, dialog);
+    },
+    routeSettings: routeSettings,
+  );
+}
+
+void _announceToAccessibility(BuildContext context, String message) {
+  SemanticsService.announce(message, Directionality.of(context));
+}
diff --git a/lib/src/material/time_picker_theme.dart b/lib/src/material/time_picker_theme.dart
new file mode 100644
index 0000000..da8465c
--- /dev/null
+++ b/lib/src/material/time_picker_theme.dart
@@ -0,0 +1,393 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'input_decorator.dart';
+import 'theme.dart';
+
+/// Defines the visual properties of the widget displayed with [showTimePicker].
+///
+/// Descendant widgets obtain the current [TimePickerThemeData] object using
+/// `TimePickerTheme.of(context)`. Instances of [TimePickerThemeData]
+/// can be customized with [TimePickerThemeData.copyWith].
+///
+/// Typically a [TimePickerThemeData] is specified as part of the overall
+/// [Theme] with [ThemeData.timePickerTheme].
+///
+/// All [TimePickerThemeData] properties are `null` by default. When null,
+/// [showTimePicker] will provide its own defaults.
+///
+/// See also:
+///
+///  * [ThemeData], which describes the overall theme information for the
+///    application.
+///  * [TimePickerTheme], which describes the actual configuration of a time
+///    picker theme.
+@immutable
+class TimePickerThemeData with Diagnosticable {
+
+  /// Creates a theme that can be used for [TimePickerTheme] or
+  /// [ThemeData.timePickerTheme].
+  const TimePickerThemeData({
+    this.backgroundColor,
+    this.hourMinuteTextColor,
+    this.hourMinuteColor,
+    this.dayPeriodTextColor,
+    this.dayPeriodColor,
+    this.dialHandColor,
+    this.dialBackgroundColor,
+    this.dialTextColor,
+    this.entryModeIconColor,
+    this.hourMinuteTextStyle,
+    this.dayPeriodTextStyle,
+    this.helpTextStyle,
+    this.shape,
+    this.hourMinuteShape,
+    this.dayPeriodShape,
+    this.dayPeriodBorderSide,
+    this.inputDecorationTheme,
+  });
+
+  /// The background color of a time picker.
+  ///
+  /// If this is null, the time picker defaults to the overall theme's
+  /// [ColorScheme.background].
+  final Color? backgroundColor;
+
+  /// The color of the header text that represents hours and minutes.
+  ///
+  /// If [hourMinuteTextColor] is a [MaterialStateColor], then the effective
+  /// text color can depend on the [MaterialState.selected] state, i.e. if the
+  /// text is selected or not.
+  ///
+  /// By default the overall theme's [ColorScheme.primary] color is used when
+  /// the text is selected and [ColorScheme.onSurface] when it's not selected.
+  final Color? hourMinuteTextColor;
+
+  /// The background color of the hour and minutes header segments.
+  ///
+  /// If [hourMinuteColor] is a [MaterialStateColor], then the effective
+  /// background color can depend on the [MaterialState.selected] state, i.e.
+  /// if the segment is selected or not.
+  ///
+  /// By default, if the segment is selected, the overall theme's
+  /// `ColorScheme.primary.withOpacity(0.12)` is used when the overall theme's
+  /// brightness is [Brightness.light] and
+  /// `ColorScheme.primary.withOpacity(0.24)` is used when the overall theme's
+  /// brightness is [Brightness.dark].
+  /// If the segment is not selected, the overall theme's
+  /// `ColorScheme.onSurface.withOpacity(0.12)` is used.
+  final Color? hourMinuteColor;
+
+  /// The color of the day period text that represents AM/PM.
+  ///
+  /// If [dayPeriodTextColor] is a [MaterialStateColor], then the effective
+  /// text color can depend on the [MaterialState.selected] state, i.e. if the
+  /// text is selected or not.
+  ///
+  /// By default the overall theme's [ColorScheme.primary] color is used when
+  /// the text is selected and `ColorScheme.onSurface.withOpacity(0.60)` when
+  /// it's not selected.
+  final Color? dayPeriodTextColor;
+
+  /// The background color of the AM/PM toggle.
+  ///
+  /// If [dayPeriodColor] is a [MaterialStateColor], then the effective
+  /// background color can depend on the [MaterialState.selected] state, i.e.
+  /// if the segment is selected or not.
+  ///
+  /// By default, if the segment is selected, the overall theme's
+  /// `ColorScheme.primary.withOpacity(0.12)` is used when the overall theme's
+  /// brightness is [Brightness.light] and
+  /// `ColorScheme.primary.withOpacity(0.24)` is used when the overall theme's
+  /// brightness is [Brightness.dark].
+  /// If the segment is not selected, [Colors.transparent] is used to allow the
+  /// [Dialog]'s color to be used.
+  final Color? dayPeriodColor;
+
+  /// The color of the time picker dial's hand.
+  ///
+  /// If this is null, the time picker defaults to the overall theme's
+  /// [ColorScheme.primary].
+  final Color? dialHandColor;
+
+  /// The background color of the time picker dial.
+  ///
+  /// If this is null, the time picker defaults to the overall theme's
+  /// [ColorScheme.primary].
+  final Color? dialBackgroundColor;
+
+  /// The color of the dial text that represents specific hours and minutes.
+  ///
+  /// If [dialTextColor] is a [MaterialStateColor], then the effective
+  /// text color can depend on the [MaterialState.selected] state, i.e. if the
+  /// text is selected or not.
+  ///
+  /// By default the overall theme's `textTheme` color is used when the text is
+  /// selected and `accentTextTheme` color is used when it's not selected.
+  final Color? dialTextColor;
+
+  /// The color of the entry mode [IconButton].
+  ///
+  /// If this is null, the time picker defaults to:
+  /// ```
+  /// Theme.of(context).colorScheme.onSurface.withOpacity(
+  ///   Theme.of(context).colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
+  /// )
+  /// ```
+  final Color? entryModeIconColor;
+
+  /// Used to configure the [TextStyle]s for the hour/minute controls.
+  ///
+  /// If this is null, the time picker defaults to the overall theme's
+  /// [TextTheme.headline3].
+  final TextStyle? hourMinuteTextStyle;
+
+  /// Used to configure the [TextStyle]s for the day period control.
+  ///
+  /// If this is null, the time picker defaults to the overall theme's
+  /// [TextTheme.subtitle1].
+  final TextStyle? dayPeriodTextStyle;
+
+  /// Used to configure the [TextStyle]s for the helper text in the header.
+  ///
+  /// If this is null, the time picker defaults to the overall theme's
+  /// [TextTheme.overline].
+  final TextStyle? helpTextStyle;
+
+  /// The shape of the [Dialog] that the time picker is presented in.
+  ///
+  /// If this is null, the time picker defaults to
+  /// `RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))`.
+  final ShapeBorder? shape;
+
+  /// The shape of the hour and minute controls that the time picker uses.
+  ///
+  /// If this is null, the time picker defaults to
+  /// `RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))`.
+  final ShapeBorder? hourMinuteShape;
+
+  /// The shape of the day period that the time picker uses.
+  ///
+  /// If this is null, the time picker defaults to:
+  /// ```
+  /// RoundedRectangleBorder(
+  ///   borderRadius: BorderRadius.all(Radius.circular(4.0)),
+  ///   side: BorderSide(),
+  /// )
+  /// ```
+  final OutlinedBorder? dayPeriodShape;
+
+  /// The color and weight of the day period's outline.
+  ///
+  /// If this is null, the time picker defaults to:
+  /// ```
+  /// BorderSide(
+  ///   color: Color.alphaBlend(colorScheme.onBackground.withOpacity(0.38), colorScheme.surface),
+  /// )
+  /// ```
+  final BorderSide? dayPeriodBorderSide;
+
+  /// The input decoration theme for the [TextField]s in the time picker.
+  ///
+  /// If this is null, the time picker provides its own defaults.
+  final InputDecorationTheme? inputDecorationTheme;
+
+  /// Creates a copy of this object with the given fields replaced with the
+  /// new values.
+  TimePickerThemeData copyWith({
+    Color? backgroundColor,
+    Color? hourMinuteTextColor,
+    Color? hourMinuteColor,
+    Color? dayPeriodTextColor,
+    Color? dayPeriodColor,
+    Color? dialHandColor,
+    Color? dialBackgroundColor,
+    Color? dialTextColor,
+    Color? entryModeIconColor,
+    TextStyle? hourMinuteTextStyle,
+    TextStyle? dayPeriodTextStyle,
+    TextStyle? helpTextStyle,
+    ShapeBorder? shape,
+    ShapeBorder? hourMinuteShape,
+    OutlinedBorder? dayPeriodShape,
+    BorderSide? dayPeriodBorderSide,
+    InputDecorationTheme? inputDecorationTheme,
+  }) {
+    return TimePickerThemeData(
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      hourMinuteTextColor: hourMinuteTextColor ?? this.hourMinuteTextColor,
+      hourMinuteColor: hourMinuteColor ?? this.hourMinuteColor,
+      dayPeriodTextColor: dayPeriodTextColor ?? this.dayPeriodTextColor,
+      dayPeriodColor: dayPeriodColor ?? this.dayPeriodColor,
+      dialHandColor: dialHandColor ?? this.dialHandColor,
+      dialBackgroundColor: dialBackgroundColor ?? this.dialBackgroundColor,
+      dialTextColor: dialTextColor ?? this.dialTextColor,
+      entryModeIconColor: entryModeIconColor ?? this.entryModeIconColor,
+      hourMinuteTextStyle: hourMinuteTextStyle ?? this.hourMinuteTextStyle,
+      dayPeriodTextStyle: dayPeriodTextStyle ?? this.dayPeriodTextStyle,
+      helpTextStyle: helpTextStyle ?? this.helpTextStyle,
+      shape: shape ?? this.shape,
+      hourMinuteShape: hourMinuteShape ?? this.hourMinuteShape,
+      dayPeriodShape: dayPeriodShape ?? this.dayPeriodShape,
+      dayPeriodBorderSide: dayPeriodBorderSide ?? this.dayPeriodBorderSide,
+      inputDecorationTheme: inputDecorationTheme ?? this.inputDecorationTheme,
+    );
+  }
+
+  /// Linearly interpolate between two time picker themes.
+  ///
+  /// The argument `t` must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static TimePickerThemeData lerp(TimePickerThemeData? a, TimePickerThemeData? b, double t) {
+    assert(t != null);
+
+    // Workaround since BorderSide's lerp does not allow for null arguments.
+    BorderSide? lerpedBorderSide;
+    if (a?.dayPeriodBorderSide == null && b?.dayPeriodBorderSide == null) {
+      lerpedBorderSide = null;
+    } else if (a?.dayPeriodBorderSide == null) {
+      lerpedBorderSide = b?.dayPeriodBorderSide;
+    } else if (b?.dayPeriodBorderSide == null) {
+      lerpedBorderSide = a?.dayPeriodBorderSide;
+    } else {
+      lerpedBorderSide = BorderSide.lerp(a!.dayPeriodBorderSide!, b!.dayPeriodBorderSide!, t);
+    }
+    return TimePickerThemeData(
+      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
+      hourMinuteTextColor: Color.lerp(a?.hourMinuteTextColor, b?.hourMinuteTextColor, t),
+      hourMinuteColor: Color.lerp(a?.hourMinuteColor, b?.hourMinuteColor, t),
+      dayPeriodTextColor: Color.lerp(a?.dayPeriodTextColor, b?.dayPeriodTextColor, t),
+      dayPeriodColor: Color.lerp(a?.dayPeriodColor, b?.dayPeriodColor, t),
+      dialHandColor: Color.lerp(a?.dialHandColor, b?.dialHandColor, t),
+      dialBackgroundColor: Color.lerp(a?.dialBackgroundColor, b?.dialBackgroundColor, t),
+      dialTextColor: Color.lerp(a?.dialTextColor, b?.dialTextColor, t),
+      entryModeIconColor: Color.lerp(a?.entryModeIconColor, b?.entryModeIconColor, t),
+      hourMinuteTextStyle: TextStyle.lerp(a?.hourMinuteTextStyle, b?.hourMinuteTextStyle, t),
+      dayPeriodTextStyle: TextStyle.lerp(a?.dayPeriodTextStyle, b?.dayPeriodTextStyle, t),
+      helpTextStyle: TextStyle.lerp(a?.helpTextStyle, b?.helpTextStyle, t),
+      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
+      hourMinuteShape: ShapeBorder.lerp(a?.hourMinuteShape, b?.hourMinuteShape, t),
+      dayPeriodShape: ShapeBorder.lerp(a?.dayPeriodShape, b?.dayPeriodShape, t) as OutlinedBorder?,
+      dayPeriodBorderSide: lerpedBorderSide,
+      inputDecorationTheme: t < 0.5 ? a?.inputDecorationTheme : b?.inputDecorationTheme,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      backgroundColor,
+      hourMinuteTextColor,
+      hourMinuteColor,
+      dayPeriodTextColor,
+      dayPeriodColor,
+      dialHandColor,
+      dialBackgroundColor,
+      dialTextColor,
+      entryModeIconColor,
+      hourMinuteTextStyle,
+      dayPeriodTextStyle,
+      helpTextStyle,
+      shape,
+      hourMinuteShape,
+      dayPeriodShape,
+      dayPeriodBorderSide,
+      inputDecorationTheme,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is TimePickerThemeData
+        && other.backgroundColor == backgroundColor
+        && other.hourMinuteTextColor == hourMinuteTextColor
+        && other.hourMinuteColor == hourMinuteColor
+        && other.dayPeriodTextColor == dayPeriodTextColor
+        && other.dayPeriodColor == dayPeriodColor
+        && other.dialHandColor == dialHandColor
+        && other.dialBackgroundColor == dialBackgroundColor
+        && other.dialTextColor == dialTextColor
+        && other.entryModeIconColor == entryModeIconColor
+        && other.hourMinuteTextStyle == hourMinuteTextStyle
+        && other.dayPeriodTextStyle == dayPeriodTextStyle
+        && other.helpTextStyle == helpTextStyle
+        && other.shape == shape
+        && other.hourMinuteShape == hourMinuteShape
+        && other.dayPeriodShape == dayPeriodShape
+        && other.dayPeriodBorderSide == dayPeriodBorderSide
+        && other.inputDecorationTheme == inputDecorationTheme;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
+    properties.add(ColorProperty('hourMinuteTextColor', hourMinuteTextColor, defaultValue: null));
+    properties.add(ColorProperty('hourMinuteColor', hourMinuteColor, defaultValue: null));
+    properties.add(ColorProperty('dayPeriodTextColor', dayPeriodTextColor, defaultValue: null));
+    properties.add(ColorProperty('dayPeriodColor', dayPeriodColor, defaultValue: null));
+    properties.add(ColorProperty('dialHandColor', dialHandColor, defaultValue: null));
+    properties.add(ColorProperty('dialBackgroundColor', dialBackgroundColor, defaultValue: null));
+    properties.add(ColorProperty('dialTextColor', dialTextColor, defaultValue: null));
+    properties.add(ColorProperty('entryModeIconColor', entryModeIconColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('hourMinuteTextStyle', hourMinuteTextStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('dayPeriodTextStyle', dayPeriodTextStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('helpTextStyle', helpTextStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('hourMinuteShape', hourMinuteShape, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('dayPeriodShape', dayPeriodShape, defaultValue: null));
+    properties.add(DiagnosticsProperty<BorderSide>('dayPeriodBorderSide', dayPeriodBorderSide, defaultValue: null));
+    properties.add(DiagnosticsProperty<InputDecorationTheme>('inputDecorationTheme', inputDecorationTheme, defaultValue: null));
+  }
+}
+
+/// An inherited widget that defines the configuration for time pickers
+/// displayed using [showTimePicker] in this widget's subtree.
+///
+/// Values specified here are used for time picker properties that are not
+/// given an explicit non-null value.
+class TimePickerTheme extends InheritedTheme {
+  /// Creates a time picker theme that controls the configurations for
+  /// time pickers displayed in its widget subtree.
+  const TimePickerTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null),
+       super(key: key, child: child);
+
+  /// The properties for descendant time picker widgets.
+  final TimePickerThemeData data;
+
+  /// The [data] value of the closest [TimePickerTheme] ancestor.
+  ///
+  /// If there is no ancestor, it returns [ThemeData.timePickerTheme].
+  /// Applications can assume that the returned value will not be null.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// TimePickerThemeData theme = TimePickerTheme.of(context);
+  /// ```
+  static TimePickerThemeData of(BuildContext context) {
+    final TimePickerTheme? timePickerTheme = context.dependOnInheritedWidgetOfExactType<TimePickerTheme>();
+    return timePickerTheme?.data ?? Theme.of(context).timePickerTheme;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return TimePickerTheme(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(TimePickerTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/toggle_buttons.dart b/lib/src/material/toggle_buttons.dart
new file mode 100644
index 0000000..97ac952
--- /dev/null
+++ b/lib/src/material/toggle_buttons.dart
@@ -0,0 +1,1477 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'button.dart';
+import 'constants.dart';
+import 'debug.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+import 'toggle_buttons_theme.dart';
+
+/// A set of toggle buttons.
+///
+/// The list of [children] are laid out along [direction]. The state of each button
+/// is controlled by [isSelected], which is a list of bools that determine
+/// if a button is in an unselected or selected state. They are both
+/// correlated by their index in the list. The length of [isSelected] has to
+/// match the length of the [children] list.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=kVEguaQWGAY}
+///
+/// ## Customizing toggle buttons
+/// Each toggle's behavior can be configured by the [onPressed] callback, which
+/// can update the [isSelected] list however it wants to.
+///
+/// {@animation 700 150 https://flutter.github.io/assets-for-api-docs/assets/material/toggle_buttons_simple.mp4}
+///
+/// Here is an implementation that allows for multiple buttons to be
+/// simultaneously selected, while requiring none of the buttons to be
+/// selected.
+/// ```dart
+/// ToggleButtons(
+///   children: <Widget>[
+///     Icon(Icons.ac_unit),
+///     Icon(Icons.call),
+///     Icon(Icons.cake),
+///   ],
+///   onPressed: (int index) {
+///     setState(() {
+///       isSelected[index] = !isSelected[index];
+///     });
+///   },
+///   isSelected: isSelected,
+/// ),
+/// ```
+///
+/// {@animation 700 150 https://flutter.github.io/assets-for-api-docs/assets/material/toggle_buttons_required_mutually_exclusive.mp4}
+///
+/// Here is an implementation that requires mutually exclusive selection
+/// while requiring at least one selection. Note that this assumes that
+/// [isSelected] was properly initialized with one selection.
+/// ```dart
+/// ToggleButtons(
+///   children: <Widget>[
+///     Icon(Icons.ac_unit),
+///     Icon(Icons.call),
+///     Icon(Icons.cake),
+///   ],
+///   onPressed: (int index) {
+///     setState(() {
+///       for (int buttonIndex = 0; buttonIndex < isSelected.length; buttonIndex++) {
+///         if (buttonIndex == index) {
+///           isSelected[buttonIndex] = true;
+///         } else {
+///           isSelected[buttonIndex] = false;
+///         }
+///       }
+///     });
+///   },
+///   isSelected: isSelected,
+/// ),
+/// ```
+///
+/// {@animation 700 150 https://flutter.github.io/assets-for-api-docs/assets/material/toggle_buttons_mutually_exclusive.mp4}
+///
+/// Here is an implementation that requires mutually exclusive selection,
+/// but allows for none of the buttons to be selected.
+/// ```dart
+/// ToggleButtons(
+///   children: <Widget>[
+///     Icon(Icons.ac_unit),
+///     Icon(Icons.call),
+///     Icon(Icons.cake),
+///   ],
+///   onPressed: (int index) {
+///     setState(() {
+///       for (int buttonIndex = 0; buttonIndex < isSelected.length; buttonIndex++) {
+///         if (buttonIndex == index) {
+///           isSelected[buttonIndex] = !isSelected[buttonIndex];
+///         } else {
+///           isSelected[buttonIndex] = false;
+///         }
+///       }
+///     });
+///   },
+///   isSelected: isSelected,
+/// ),
+/// ```
+///
+/// {@animation 700 150 https://flutter.github.io/assets-for-api-docs/assets/material/toggle_buttons_required.mp4}
+///
+/// Here is an implementation that allows for multiple buttons to be
+/// simultaneously selected, while requiring at least one selection. Note
+/// that this assumes that [isSelected] was properly initialized with one
+/// selection.
+/// ```dart
+/// ToggleButtons(
+///   children: <Widget>[
+///     Icon(Icons.ac_unit),
+///     Icon(Icons.call),
+///     Icon(Icons.cake),
+///   ],
+///   onPressed: (int index) {
+///     int count = 0;
+///     isSelected.forEach((bool val) {
+///       if (val) count++;
+///     });
+///
+///     if (isSelected[index] && count < 2)
+///       return;
+///
+///     setState(() {
+///       isSelected[index] = !isSelected[index];
+///     });
+///   },
+///   isSelected: isSelected,
+/// ),
+/// ```
+///
+/// ## ToggleButton Borders
+/// The toggle buttons, by default, have a solid, 1 logical pixel border
+/// surrounding itself and separating each button. The toggle button borders'
+/// color, width, and corner radii are configurable.
+///
+/// The [selectedBorderColor] determines the border's color when the button is
+/// selected, while [disabledBorderColor] determines the border's color when
+/// the button is disabled. [borderColor] is used when the button is enabled.
+///
+/// To remove the border, set [renderBorder] to false. Setting [borderWidth] to
+/// 0.0 results in a hairline border. For more information on hairline borders,
+/// see [BorderSide.width].
+///
+/// See also:
+///
+///  * <https://material.io/design/components/buttons.html#toggle-button>
+class ToggleButtons extends StatelessWidget {
+  /// Creates a set of toggle buttons.
+  ///
+  /// It displays its widgets provided in a [List] of [children] along [direction].
+  /// The state of each button is controlled by [isSelected], which is a list
+  /// of bools that determine if a button is in an active, disabled, or
+  /// selected state. They are both correlated by their index in the list.
+  /// The length of [isSelected] has to match the length of the [children]
+  /// list.
+  ///
+  /// Both [children] and [isSelected] properties arguments are required.
+  ///
+  /// [isSelected] values must be non-null. [focusNodes] must be null or a
+  /// list of non-null nodes. [renderBorder] and [direction] must not be null.
+  /// If [direction] is [Axis.vertical], [verticalDirection] must not be null.
+  const ToggleButtons({
+    Key? key,
+    required this.children,
+    required this.isSelected,
+    this.onPressed,
+    this.mouseCursor,
+    this.textStyle,
+    this.constraints,
+    this.color,
+    this.selectedColor,
+    this.disabledColor,
+    this.fillColor,
+    this.focusColor,
+    this.highlightColor,
+    this.hoverColor,
+    this.splashColor,
+    this.focusNodes,
+    this.renderBorder = true,
+    this.borderColor,
+    this.selectedBorderColor,
+    this.disabledBorderColor,
+    this.borderRadius,
+    this.borderWidth,
+    this.direction = Axis.horizontal,
+    this.verticalDirection = VerticalDirection.down,
+  }) :
+    assert(children != null),
+    assert(isSelected != null),
+    assert(children.length == isSelected.length),
+    assert(direction != null),
+    assert(direction == Axis.horizontal || verticalDirection != null),
+    super(key: key);
+
+  static const double _defaultBorderWidth = 1.0;
+
+  /// The toggle button widgets.
+  ///
+  /// These are typically [Icon] or [Text] widgets. The boolean selection
+  /// state of each widget is defined by the corresponding [isSelected]
+  /// list item.
+  ///
+  /// The length of children has to match the length of [isSelected]. If
+  /// [focusNodes] is not null, the length of children has to also match
+  /// the length of [focusNodes].
+  final List<Widget> children;
+
+  /// The corresponding selection state of each toggle button.
+  ///
+  /// Each value in this list represents the selection state of the [children]
+  /// widget at the same index.
+  ///
+  /// The length of [isSelected] has to match the length of [children].
+  final List<bool> isSelected;
+
+  /// The callback that is called when a button is tapped.
+  ///
+  /// The index parameter of the callback is the index of the button that is
+  /// tapped or otherwise activated.
+  ///
+  /// When the callback is null, all toggle buttons will be disabled.
+  final void Function(int index)? onPressed;
+
+  /// {@macro flutter.material.RawMaterialButton.mouseCursor}
+  final MouseCursor? mouseCursor;
+
+  /// The [TextStyle] to apply to any text in these toggle buttons.
+  ///
+  /// [TextStyle.color] will be ignored and substituted by [color],
+  /// [selectedColor] or [disabledColor] depending on whether the buttons
+  /// are active, selected, or disabled.
+  final TextStyle? textStyle;
+
+  /// Defines the button's size.
+  ///
+  /// Typically used to constrain the button's minimum size.
+  ///
+  /// If this property is null, then
+  /// BoxConstraints(minWidth: 48.0, minHeight: 48.0) is be used.
+  final BoxConstraints? constraints;
+
+  /// The color for descendant [Text] and [Icon] widgets if the button is
+  /// enabled and not selected.
+  ///
+  /// If [onPressed] is not null, this color will be used for values in
+  /// [isSelected] that are false.
+  ///
+  /// If this property is null, then ToggleButtonTheme.of(context).color
+  /// is used. If [ToggleButtonsThemeData.color] is also null, then
+  /// Theme.of(context).colorScheme.onSurface is used.
+  final Color? color;
+
+  /// The color for descendant [Text] and [Icon] widgets if the button is
+  /// selected.
+  ///
+  /// If [onPressed] is not null, this color will be used for values in
+  /// [isSelected] that are true.
+  ///
+  /// If this property is null, then
+  /// ToggleButtonTheme.of(context).selectedColor is used. If
+  /// [ToggleButtonsThemeData.selectedColor] is also null, then
+  /// Theme.of(context).colorScheme.primary is used.
+  final Color? selectedColor;
+
+  /// The color for descendant [Text] and [Icon] widgets if the button is
+  /// disabled.
+  ///
+  /// If [onPressed] is null, this color will be used.
+  ///
+  /// If this property is null, then
+  /// ToggleButtonTheme.of(context).disabledColor is used. If
+  /// [ToggleButtonsThemeData.disabledColor] is also null, then
+  /// Theme.of(context).colorScheme.onSurface.withOpacity(0.38) is used.
+  final Color? disabledColor;
+
+  /// The fill color for selected toggle buttons.
+  ///
+  /// If this property is null, then
+  /// ToggleButtonTheme.of(context).fillColor is used. If
+  /// [ToggleButtonsThemeData.fillColor] is also null, then
+  /// the fill color is null.
+  final Color? fillColor;
+
+  /// The color to use for filling the button when the button has input focus.
+  ///
+  /// If this property is null, then
+  /// ToggleButtonTheme.of(context).focusColor is used. If
+  /// [ToggleButtonsThemeData.focusColor] is also null, then
+  /// Theme.of(context).focusColor is used.
+  final Color? focusColor;
+
+  /// The highlight color for the button's [InkWell].
+  ///
+  /// If this property is null, then
+  /// ToggleButtonTheme.of(context).highlightColor is used. If
+  /// [ToggleButtonsThemeData.highlightColor] is also null, then
+  /// Theme.of(context).highlightColor is used.
+  final Color? highlightColor;
+
+  /// The splash color for the button's [InkWell].
+  ///
+  /// If this property is null, then
+  /// ToggleButtonTheme.of(context).splashColor is used. If
+  /// [ToggleButtonsThemeData.splashColor] is also null, then
+  /// Theme.of(context).splashColor is used.
+  final Color? splashColor;
+
+  /// The color to use for filling the button when the button has a pointer
+  /// hovering over it.
+  ///
+  /// If this property is null, then
+  /// ToggleButtonTheme.of(context).hoverColor is used. If
+  /// [ToggleButtonsThemeData.hoverColor] is also null, then
+  /// Theme.of(context).hoverColor is used.
+  final Color? hoverColor;
+
+  /// The list of [FocusNode]s, corresponding to each toggle button.
+  ///
+  /// Focus is used to determine which widget should be affected by keyboard
+  /// events. The focus tree keeps track of which widget is currently focused
+  /// on by the user.
+  ///
+  /// If not null, the length of focusNodes has to match the length of
+  /// [children].
+  ///
+  /// See [FocusNode] for more information about how focus nodes are used.
+  final List<FocusNode>? focusNodes;
+
+  /// Whether or not to render a border around each toggle button.
+  ///
+  /// When true, a border with [borderWidth], [borderRadius] and the
+  /// appropriate border color will render. Otherwise, no border will be
+  /// rendered.
+  final bool renderBorder;
+
+  /// The border color to display when the toggle button is enabled and not
+  /// selected.
+  ///
+  /// If this property is null, then
+  /// ToggleButtonTheme.of(context).borderColor is used. If
+  /// [ToggleButtonsThemeData.borderColor] is also null, then
+  /// Theme.of(context).colorScheme.onSurface is used.
+  final Color? borderColor;
+
+  /// The border color to display when the toggle button is selected.
+  ///
+  /// If this property is null, then
+  /// ToggleButtonTheme.of(context).selectedBorderColor is used. If
+  /// [ToggleButtonsThemeData.selectedBorderColor] is also null, then
+  /// Theme.of(context).colorScheme.primary is used.
+  final Color? selectedBorderColor;
+
+  /// The border color to display when the toggle button is disabled.
+  ///
+  /// If this property is null, then
+  /// ToggleButtonTheme.of(context).disabledBorderColor is used. If
+  /// [ToggleButtonsThemeData.disabledBorderColor] is also null, then
+  /// Theme.of(context).disabledBorderColor is used.
+  final Color? disabledBorderColor;
+
+  /// The width of the border surrounding each toggle button.
+  ///
+  /// This applies to both the greater surrounding border, as well as the
+  /// borders rendered between toggle buttons.
+  ///
+  /// To render a hairline border (one physical pixel), set borderWidth to 0.0.
+  /// See [BorderSide.width] for more details on hairline borders.
+  ///
+  /// To omit the border entirely, set [renderBorder] to false.
+  ///
+  /// If this property is null, then
+  /// ToggleButtonTheme.of(context).borderWidth is used. If
+  /// [ToggleButtonsThemeData.borderWidth] is also null, then
+  /// a width of 1.0 is used.
+  final double? borderWidth;
+
+  /// The radii of the border's corners.
+  ///
+  /// If this property is null, then
+  /// ToggleButtonTheme.of(context).borderRadius is used. If
+  /// [ToggleButtonsThemeData.borderRadius] is also null, then
+  /// the buttons default to non-rounded borders.
+  final BorderRadius? borderRadius;
+
+  /// The direction along which the buttons are rendered.
+  ///
+  /// Defaults to [Axis.horizontal].
+  final Axis direction;
+
+  /// If [direction] is [Axis.vertical], this parameter determines whether to lay out
+  /// the buttons starting from the first or last child from top to bottom.
+  final VerticalDirection verticalDirection;
+
+  // Determines if this is the first child that is being laid out
+  // by the render object, _not_ the order of the children in its list.
+  bool _isFirstButton(int index, int length, TextDirection textDirection) {
+    return index == 0 && ((direction == Axis.horizontal && textDirection == TextDirection.ltr) ||
+      (direction == Axis.vertical && verticalDirection == VerticalDirection.down))
+      || index == length - 1 && ((direction == Axis.horizontal && textDirection == TextDirection.rtl) ||
+      (direction == Axis.vertical && verticalDirection == VerticalDirection.up));
+  }
+
+  // Determines if this is the last child that is being laid out
+  // by the render object, _not_ the order of the children in its list.
+  bool _isLastButton(int index, int length, TextDirection textDirection) {
+    return index == length - 1 && ((direction == Axis.horizontal && textDirection == TextDirection.ltr) ||
+      (direction == Axis.vertical && verticalDirection == VerticalDirection.down))
+      || index == 0 && ((direction == Axis.horizontal && textDirection == TextDirection.rtl) ||
+      (direction == Axis.vertical && verticalDirection == VerticalDirection.up));
+  }
+
+  BorderRadius _getEdgeBorderRadius(
+    int index,
+    int length,
+    TextDirection textDirection,
+    ToggleButtonsThemeData toggleButtonsTheme,
+  ) {
+    final BorderRadius resultingBorderRadius = borderRadius
+      ?? toggleButtonsTheme.borderRadius
+      ?? BorderRadius.zero;
+
+    if (direction == Axis.horizontal) {
+      if (_isFirstButton(index, length, textDirection)) {
+        return BorderRadius.only(
+          topLeft: resultingBorderRadius.topLeft,
+          bottomLeft: resultingBorderRadius.bottomLeft,
+        );
+      } else if (_isLastButton(index, length, textDirection)) {
+        return BorderRadius.only(
+          topRight: resultingBorderRadius.topRight,
+          bottomRight: resultingBorderRadius.bottomRight,
+        );
+      }
+    } else {
+      if (_isFirstButton(index, length, textDirection)) {
+        return BorderRadius.only(
+          topLeft: resultingBorderRadius.topLeft,
+          topRight: resultingBorderRadius.topRight,
+        );
+      } else if (_isLastButton(index, length, textDirection)) {
+        return BorderRadius.only(
+          bottomLeft: resultingBorderRadius.bottomLeft,
+          bottomRight: resultingBorderRadius.bottomRight,
+        );
+      }
+    }
+
+    return BorderRadius.zero;
+  }
+
+  BorderRadius _getClipBorderRadius(
+    int index,
+    int length,
+    TextDirection textDirection,
+    ToggleButtonsThemeData toggleButtonsTheme,
+  ) {
+    final BorderRadius resultingBorderRadius = borderRadius
+      ?? toggleButtonsTheme.borderRadius
+      ?? BorderRadius.zero;
+    final double resultingBorderWidth = borderWidth
+      ?? toggleButtonsTheme.borderWidth
+      ?? _defaultBorderWidth;
+
+    if (direction == Axis.horizontal) {
+      if (_isFirstButton(index, length, textDirection)) {
+        return BorderRadius.only(
+          topLeft: resultingBorderRadius.topLeft - Radius.circular(resultingBorderWidth / 2.0),
+          bottomLeft: resultingBorderRadius.bottomLeft - Radius.circular(resultingBorderWidth / 2.0),
+        );
+      } else if (_isLastButton(index, length, textDirection)) {
+        return BorderRadius.only(
+          topRight: resultingBorderRadius.topRight - Radius.circular(resultingBorderWidth / 2.0),
+          bottomRight: resultingBorderRadius.bottomRight - Radius.circular(resultingBorderWidth / 2.0),
+        );
+      }
+    } else {
+      if (_isFirstButton(index, length, textDirection)) {
+        return BorderRadius.only(
+          topLeft: resultingBorderRadius.topLeft - Radius.circular(resultingBorderWidth / 2.0),
+          topRight: resultingBorderRadius.topRight - Radius.circular(resultingBorderWidth / 2.0),
+        );
+      } else if (_isLastButton(index, length, textDirection)) {
+        return BorderRadius.only(
+          bottomLeft: resultingBorderRadius.bottomLeft - Radius.circular(resultingBorderWidth / 2.0),
+          bottomRight: resultingBorderRadius.bottomRight - Radius.circular(resultingBorderWidth / 2.0),
+        );
+      }
+    }
+    return BorderRadius.zero;
+  }
+
+  BorderSide _getLeadingBorderSide(
+    int index,
+    ThemeData theme,
+    ToggleButtonsThemeData toggleButtonsTheme,
+  ) {
+    if (!renderBorder)
+      return BorderSide.none;
+
+    final double resultingBorderWidth = borderWidth
+      ?? toggleButtonsTheme.borderWidth
+      ?? _defaultBorderWidth;
+    if (onPressed != null && (isSelected[index] || (index != 0 && isSelected[index - 1]))) {
+      return BorderSide(
+        color: selectedBorderColor
+          ?? toggleButtonsTheme.selectedBorderColor
+          ?? theme.colorScheme.onSurface.withOpacity(0.12),
+        width: resultingBorderWidth,
+      );
+    } else if (onPressed != null && !isSelected[index]) {
+      return BorderSide(
+        color: borderColor
+          ?? toggleButtonsTheme.borderColor
+          ?? theme.colorScheme.onSurface.withOpacity(0.12),
+        width: resultingBorderWidth,
+      );
+    } else {
+      return BorderSide(
+        color: disabledBorderColor
+          ?? toggleButtonsTheme.disabledBorderColor
+          ?? theme.colorScheme.onSurface.withOpacity(0.12),
+        width: resultingBorderWidth,
+      );
+    }
+  }
+
+  BorderSide _getBorderSide(
+    int index,
+    ThemeData theme,
+    ToggleButtonsThemeData toggleButtonsTheme,
+  ) {
+    if (!renderBorder)
+      return BorderSide.none;
+
+    final double resultingBorderWidth = borderWidth
+      ?? toggleButtonsTheme.borderWidth
+      ?? _defaultBorderWidth;
+    if (onPressed != null && isSelected[index]) {
+      return BorderSide(
+        color: selectedBorderColor
+          ?? toggleButtonsTheme.selectedBorderColor
+          ?? theme.colorScheme.onSurface.withOpacity(0.12),
+        width: resultingBorderWidth,
+      );
+    } else if (onPressed != null && !isSelected[index]) {
+      return BorderSide(
+        color: borderColor
+          ?? toggleButtonsTheme.borderColor
+          ?? theme.colorScheme.onSurface.withOpacity(0.12),
+        width: resultingBorderWidth,
+      );
+    } else {
+      return BorderSide(
+        color: disabledBorderColor
+          ?? toggleButtonsTheme.disabledBorderColor
+          ?? theme.colorScheme.onSurface.withOpacity(0.12),
+        width: resultingBorderWidth,
+      );
+    }
+  }
+
+  BorderSide _getTrailingBorderSide(
+    int index,
+    ThemeData theme,
+    ToggleButtonsThemeData toggleButtonsTheme,
+  ) {
+    if (!renderBorder)
+      return BorderSide.none;
+
+    if (index != children.length - 1)
+      return BorderSide.none;
+
+    final double resultingBorderWidth = borderWidth
+      ?? toggleButtonsTheme.borderWidth
+      ?? _defaultBorderWidth;
+    if (onPressed != null && (isSelected[index])) {
+      return BorderSide(
+        color: selectedBorderColor
+          ?? toggleButtonsTheme.selectedBorderColor
+          ?? theme.colorScheme.onSurface.withOpacity(0.12),
+        width: resultingBorderWidth,
+      );
+    } else if (onPressed != null && !isSelected[index]) {
+      return BorderSide(
+        color: borderColor
+          ?? toggleButtonsTheme.borderColor
+          ?? theme.colorScheme.onSurface.withOpacity(0.12),
+        width: resultingBorderWidth,
+      );
+    } else {
+      return BorderSide(
+        color: disabledBorderColor
+          ?? toggleButtonsTheme.disabledBorderColor
+          ?? theme.colorScheme.onSurface.withOpacity(0.12),
+        width: resultingBorderWidth,
+      );
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(
+      !isSelected.any((bool val) => val == null),
+      'All elements of isSelected must be non-null.\n'
+      'The current list of isSelected values is as follows:\n'
+      '$isSelected'
+    );
+    assert(
+      focusNodes == null || !focusNodes!.any((FocusNode val) => val == null),
+      'All elements of focusNodes must be non-null.\n'
+      'The current list of focus node values is as follows:\n'
+      '$focusNodes'
+    );
+    assert(
+      () {
+        if (focusNodes != null)
+          return focusNodes!.length == children.length;
+        return true;
+      }(),
+      'focusNodes.length must match children.length.\n'
+      'There are ${focusNodes!.length} focus nodes, while '
+      'there are ${children.length} children.'
+    );
+    final ThemeData theme = Theme.of(context);
+    final ToggleButtonsThemeData toggleButtonsTheme = ToggleButtonsTheme.of(context);
+    final TextDirection textDirection = Directionality.of(context);
+
+    final List<Widget> buttons = List<Widget>.generate(children.length, (int index) {
+      final BorderRadius edgeBorderRadius = _getEdgeBorderRadius(index, children.length, textDirection, toggleButtonsTheme);
+      final BorderRadius clipBorderRadius = _getClipBorderRadius(index, children.length, textDirection, toggleButtonsTheme);
+
+      final BorderSide leadingBorderSide = _getLeadingBorderSide(index, theme, toggleButtonsTheme);
+      final BorderSide borderSide = _getBorderSide(index, theme, toggleButtonsTheme);
+      final BorderSide trailingBorderSide = _getTrailingBorderSide(index, theme, toggleButtonsTheme);
+
+      return _ToggleButton(
+        selected: isSelected[index],
+        textStyle: textStyle,
+        constraints: constraints,
+        color: color,
+        selectedColor: selectedColor,
+        disabledColor: disabledColor,
+        fillColor: fillColor ?? toggleButtonsTheme.fillColor,
+        focusColor: focusColor ?? toggleButtonsTheme.focusColor,
+        highlightColor: highlightColor ?? toggleButtonsTheme.highlightColor,
+        hoverColor: hoverColor ?? toggleButtonsTheme.hoverColor,
+        splashColor: splashColor ?? toggleButtonsTheme.splashColor,
+        focusNode: focusNodes != null ? focusNodes![index] : null,
+        onPressed: onPressed != null
+          ? () {onPressed!(index);}
+          : null,
+        mouseCursor: mouseCursor,
+        leadingBorderSide: leadingBorderSide,
+        borderSide: borderSide,
+        trailingBorderSide: trailingBorderSide,
+        borderRadius: edgeBorderRadius,
+        clipRadius: clipBorderRadius,
+        isFirstButton: index == 0,
+        isLastButton: index == children.length - 1,
+        direction: direction,
+        verticalDirection: verticalDirection,
+        child: children[index],
+      );
+    });
+
+    return direction == Axis.horizontal
+      ? IntrinsicHeight(
+        child: Row(
+          mainAxisSize: MainAxisSize.min,
+          crossAxisAlignment: CrossAxisAlignment.stretch,
+          children: buttons,
+        ),
+      )
+      : IntrinsicWidth(
+        child: Column(
+          mainAxisSize: MainAxisSize.min,
+          crossAxisAlignment: CrossAxisAlignment.stretch,
+          verticalDirection: verticalDirection,
+          children: buttons,
+        ),
+      );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('disabled',
+      value: onPressed == null,
+      ifTrue: 'Buttons are disabled',
+      ifFalse: 'Buttons are enabled',
+    ));
+    textStyle?.debugFillProperties(properties, prefix: 'textStyle.');
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(ColorProperty('selectedColor', selectedColor, defaultValue: null));
+    properties.add(ColorProperty('disabledColor', disabledColor, defaultValue: null));
+    properties.add(ColorProperty('fillColor', fillColor, defaultValue: null));
+    properties.add(ColorProperty('focusColor', focusColor, defaultValue: null));
+    properties.add(ColorProperty('highlightColor', highlightColor, defaultValue: null));
+    properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: null));
+    properties.add(ColorProperty('splashColor', splashColor, defaultValue: null));
+    properties.add(ColorProperty('borderColor', borderColor, defaultValue: null));
+    properties.add(ColorProperty('selectedBorderColor', selectedBorderColor, defaultValue: null));
+    properties.add(ColorProperty('disabledBorderColor', disabledBorderColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius, defaultValue: null));
+    properties.add(DoubleProperty('borderWidth', borderWidth, defaultValue: null));
+    properties.add(DiagnosticsProperty<Axis>('direction', direction, defaultValue: Axis.horizontal));
+    properties.add(DiagnosticsProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: VerticalDirection.down));
+  }
+}
+
+/// An individual toggle button, otherwise known as a segmented button.
+///
+/// This button is used by [ToggleButtons] to implement a set of segmented controls.
+class _ToggleButton extends StatelessWidget {
+  /// Creates a toggle button based on [RawMaterialButton].
+  ///
+  /// This class adds some logic to distinguish between enabled, active, and
+  /// disabled states, to determine the appropriate colors to use.
+  ///
+  /// It takes in a [shape] property to modify the borders of the button,
+  /// which is used by [ToggleButtons] to customize borders based on the
+  /// order in which this button appears in the list.
+  const _ToggleButton({
+    Key? key,
+    this.selected = false,
+    this.textStyle,
+    this.constraints,
+    this.color,
+    this.selectedColor,
+    this.disabledColor,
+    required this.fillColor,
+    required this.focusColor,
+    required this.highlightColor,
+    required this.hoverColor,
+    required this.splashColor,
+    this.focusNode,
+    this.onPressed,
+    this.mouseCursor,
+    required this.leadingBorderSide,
+    required this.borderSide,
+    required this.trailingBorderSide,
+    required this.borderRadius,
+    required this.clipRadius,
+    required this.isFirstButton,
+    required this.isLastButton,
+    required this.direction,
+    required this.verticalDirection,
+    required this.child,
+  }) : super(key: key);
+
+  /// Determines if the button is displayed as active/selected or enabled.
+  final bool selected;
+
+  /// The [TextStyle] to apply to any text that appears in this button.
+  final TextStyle? textStyle;
+
+  /// Defines the button's size.
+  ///
+  /// Typically used to constrain the button's minimum size.
+  final BoxConstraints? constraints;
+
+  /// The color for [Text] and [Icon] widgets if the button is enabled.
+  ///
+  /// If [selected] is false and [onPressed] is not null, this color will be used.
+  final Color? color;
+
+  /// The color for [Text] and [Icon] widgets if the button is selected.
+  ///
+  /// If [selected] is true and [onPressed] is not null, this color will be used.
+  final Color? selectedColor;
+
+  /// The color for [Text] and [Icon] widgets if the button is disabled.
+  ///
+  /// If [onPressed] is null, this color will be used.
+  final Color? disabledColor;
+
+  /// The color of the button's [Material].
+  final Color? fillColor;
+
+  /// The color for the button's [Material] when it has the input focus.
+  final Color? focusColor;
+
+  /// The color for the button's [Material] when a pointer is hovering over it.
+  final Color? hoverColor;
+
+  /// The highlight color for the button's [InkWell].
+  final Color? highlightColor;
+
+  /// The splash color for the button's [InkWell].
+  final Color? splashColor;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// Called when the button is tapped or otherwise activated.
+  ///
+  /// If this is null, the button will be disabled, see [enabled].
+  final VoidCallback? onPressed;
+
+  /// {@macro flutter.material.RawMaterialButton.mouseCursor}
+  final MouseCursor? mouseCursor;
+
+  /// The width and color of the button's leading side border.
+  final BorderSide leadingBorderSide;
+
+  /// If [direction] is [Axis.horizontal], this corresponds the width and color
+  /// of the button's top and bottom side borders.
+  ///
+  /// If [direction] is [Axis.vertical], this corresponds the width and color
+  /// of the button's left and right side borders.
+  final BorderSide borderSide;
+
+  /// The width and color of the button's trailing side border.
+  final BorderSide trailingBorderSide;
+
+  /// The border radii of each corner of the button.
+  final BorderRadius borderRadius;
+
+  /// The corner radii used to clip the button's contents.
+  ///
+  /// This is used to have the button's contents be properly clipped taking
+  /// the [borderRadius] and the border's width into account.
+  final BorderRadius clipRadius;
+
+  /// Whether or not this toggle button is the first button in the list.
+  final bool isFirstButton;
+
+  /// Whether or not this toggle button is the last button in the list.
+  final bool isLastButton;
+
+  /// The direction along which the buttons are rendered.
+  final Axis direction;
+
+  /// If [direction] is [Axis.vertical], this property defines whether or not this button in its list
+  /// of buttons is laid out starting from top to bottom or from bottom to top.
+  final VerticalDirection verticalDirection;
+
+  /// The button's label, which is usually an [Icon] or a [Text] widget.
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    final Color currentColor;
+    final Color currentFillColor;
+    Color? currentFocusColor;
+    Color? currentHoverColor;
+    Color? currentSplashColor;
+    final ThemeData theme = Theme.of(context);
+    final ToggleButtonsThemeData toggleButtonsTheme = ToggleButtonsTheme.of(context);
+
+    if (onPressed != null && selected) {
+      currentColor = selectedColor
+        ?? toggleButtonsTheme.selectedColor
+        ?? theme.colorScheme.primary;
+      currentFillColor = fillColor
+        ?? theme.colorScheme.primary.withOpacity(0.12);
+      currentFocusColor = focusColor
+        ?? toggleButtonsTheme.focusColor
+        ?? theme.colorScheme.primary.withOpacity(0.12);
+      currentHoverColor = hoverColor
+        ?? toggleButtonsTheme.hoverColor
+        ?? theme.colorScheme.primary.withOpacity(0.04);
+      currentSplashColor = splashColor
+        ?? toggleButtonsTheme.splashColor
+        ?? theme.colorScheme.primary.withOpacity(0.16);
+    } else if (onPressed != null && !selected) {
+      currentColor = color
+        ?? toggleButtonsTheme.color
+        ?? theme.colorScheme.onSurface.withOpacity(0.87);
+      currentFillColor = theme.colorScheme.surface.withOpacity(0.0);
+      currentFocusColor = focusColor
+        ?? toggleButtonsTheme.focusColor
+        ?? theme.colorScheme.onSurface.withOpacity(0.12);
+      currentHoverColor = hoverColor
+        ?? toggleButtonsTheme.hoverColor
+        ?? theme.colorScheme.onSurface.withOpacity(0.04);
+      currentSplashColor = splashColor
+        ?? toggleButtonsTheme.splashColor
+        ?? theme.colorScheme.onSurface.withOpacity(0.16);
+    } else {
+      currentColor = disabledColor
+        ?? toggleButtonsTheme.disabledColor
+        ?? theme.colorScheme.onSurface.withOpacity(0.38);
+      currentFillColor = theme.colorScheme.surface.withOpacity(0.0);
+    }
+
+    final TextStyle currentTextStyle = textStyle ?? toggleButtonsTheme.textStyle ?? theme.textTheme.bodyText2!;
+    final BoxConstraints currentConstraints = constraints ?? toggleButtonsTheme.constraints ?? const BoxConstraints(minWidth: kMinInteractiveDimension, minHeight: kMinInteractiveDimension);
+
+    final Widget result = ClipRRect(
+      borderRadius: clipRadius,
+      child: RawMaterialButton(
+        textStyle: currentTextStyle.copyWith(
+          color: currentColor,
+        ),
+        constraints: currentConstraints,
+        elevation: 0.0,
+        highlightElevation: 0.0,
+        fillColor: currentFillColor,
+        focusColor: currentFocusColor,
+        highlightColor: highlightColor
+          ?? theme.colorScheme.surface.withOpacity(0.0),
+        hoverColor: currentHoverColor,
+        splashColor: currentSplashColor,
+        focusNode: focusNode,
+        materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+        onPressed: onPressed,
+        mouseCursor: mouseCursor,
+        child: child,
+      ),
+    );
+
+    return _SelectToggleButton(
+      key: key,
+      leadingBorderSide: leadingBorderSide,
+      borderSide: borderSide,
+      trailingBorderSide: trailingBorderSide,
+      borderRadius: borderRadius,
+      isFirstButton: isFirstButton,
+      isLastButton: isLastButton,
+      direction: direction,
+      verticalDirection: verticalDirection,
+      child: result,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('selected',
+      value: selected,
+      ifTrue: 'Button is selected',
+      ifFalse: 'Button is unselected',
+    ));
+  }
+}
+
+class _SelectToggleButton extends SingleChildRenderObjectWidget {
+  const _SelectToggleButton({
+    Key? key,
+    required Widget child,
+    required this.leadingBorderSide,
+    required this.borderSide,
+    required this.trailingBorderSide,
+    required this.borderRadius,
+    required this.isFirstButton,
+    required this.isLastButton,
+    required this.direction,
+    required this.verticalDirection,
+  }) : super(
+    key: key,
+    child: child,
+  );
+
+  // The width and color of the button's leading side border.
+  final BorderSide leadingBorderSide;
+
+  // The width and color of the side borders.
+  //
+  // If [direction] is [Axis.horizontal], this corresponds to the width and color
+  // of the button's top and bottom side borders.
+  //
+  // If [direction] is [Axis.vertical], this corresponds to the width and color
+  // of the button's left and right side borders.
+  final BorderSide borderSide;
+
+  // The width and color of the button's trailing side border.
+  final BorderSide trailingBorderSide;
+
+  // The border radii of each corner of the button.
+  final BorderRadius borderRadius;
+
+  // Whether or not this toggle button is the first button in the list.
+  final bool isFirstButton;
+
+  // Whether or not this toggle button is the last button in the list.
+  final bool isLastButton;
+
+  // The direction along which the buttons are rendered.
+  final Axis direction;
+
+  // If [direction] is [Axis.vertical], this property defines whether or not this button in its list
+  // of buttons is laid out starting from top to bottom or from bottom to top.
+  final VerticalDirection verticalDirection;
+
+  @override
+  _SelectToggleButtonRenderObject createRenderObject(BuildContext context) => _SelectToggleButtonRenderObject(
+    leadingBorderSide,
+    borderSide,
+    trailingBorderSide,
+    borderRadius,
+    isFirstButton,
+    isLastButton,
+    direction,
+    verticalDirection,
+    Directionality.of(context),
+  );
+
+  @override
+  void updateRenderObject(BuildContext context, _SelectToggleButtonRenderObject renderObject) {
+    renderObject
+      ..leadingBorderSide = leadingBorderSide
+      ..borderSide  = borderSide
+      ..trailingBorderSide = trailingBorderSide
+      ..borderRadius = borderRadius
+      ..isFirstButton = isFirstButton
+      ..isLastButton = isLastButton
+      ..direction = direction
+      ..verticalDirection = verticalDirection
+      ..textDirection = Directionality.of(context);
+  }
+}
+
+class _SelectToggleButtonRenderObject extends RenderShiftedBox {
+  _SelectToggleButtonRenderObject(
+    this._leadingBorderSide,
+    this._borderSide,
+    this._trailingBorderSide,
+    this._borderRadius,
+    this._isFirstButton,
+    this._isLastButton,
+    this._direction,
+    this._verticalDirection,
+    this._textDirection, [
+    RenderBox? child,
+  ]) : super(child);
+
+  Axis get direction => _direction;
+  Axis _direction;
+  set direction(Axis value) {
+    if (_direction == value)
+      return;
+    _direction = value;
+    markNeedsLayout();
+  }
+
+  VerticalDirection get verticalDirection => _verticalDirection;
+  VerticalDirection _verticalDirection;
+  set verticalDirection(VerticalDirection value) {
+    if (_verticalDirection == value)
+      return;
+    _verticalDirection = value;
+    markNeedsLayout();
+  }
+
+  // The width and color of the button's leading side border.
+  BorderSide get leadingBorderSide => _leadingBorderSide;
+  BorderSide _leadingBorderSide;
+  set leadingBorderSide(BorderSide value) {
+    if (_leadingBorderSide == value)
+      return;
+    _leadingBorderSide = value;
+    markNeedsLayout();
+  }
+
+  // The width and color of the button's top and bottom side borders.
+  BorderSide get borderSide  => _borderSide;
+  BorderSide _borderSide;
+  set borderSide(BorderSide value) {
+    if (_borderSide == value)
+      return;
+    _borderSide = value;
+    markNeedsLayout();
+  }
+
+  // The width and color of the button's trailing side border.
+  BorderSide get trailingBorderSide => _trailingBorderSide;
+  BorderSide _trailingBorderSide;
+  set trailingBorderSide(BorderSide value) {
+    if (_trailingBorderSide == value)
+      return;
+    _trailingBorderSide = value;
+    markNeedsLayout();
+  }
+
+  // The border radii of each corner of the button.
+  BorderRadius get borderRadius => _borderRadius;
+  BorderRadius _borderRadius;
+  set borderRadius(BorderRadius value) {
+    if (_borderRadius == value)
+      return;
+    _borderRadius = value;
+    markNeedsLayout();
+  }
+
+  // Whether or not this toggle button is the first button in the list.
+  bool get isFirstButton => _isFirstButton;
+  bool _isFirstButton;
+  set isFirstButton(bool value) {
+    if (_isFirstButton == value)
+      return;
+    _isFirstButton = value;
+    markNeedsLayout();
+  }
+
+  // Whether or not this toggle button is the last button in the list.
+  bool get isLastButton => _isLastButton;
+  bool _isLastButton;
+  set isLastButton(bool value) {
+    if (_isLastButton == value)
+      return;
+    _isLastButton = value;
+    markNeedsLayout();
+  }
+
+  // The direction in which text flows for this application.
+  TextDirection get textDirection => _textDirection;
+  TextDirection _textDirection;
+  set textDirection(TextDirection value) {
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    markNeedsLayout();
+  }
+
+  static double _maxHeight(RenderBox? box, double width) {
+    return box == null ? 0.0 : box.getMaxIntrinsicHeight(width);
+  }
+
+  static double _minHeight(RenderBox? box, double width) {
+    return box == null ? 0.0 : box.getMinIntrinsicHeight(width);
+  }
+
+  static double _minWidth(RenderBox? box, double height) {
+    return box == null ? 0.0 : box.getMinIntrinsicWidth(height);
+  }
+
+  static double _maxWidth(RenderBox? box, double height) {
+    return box == null ? 0.0 : box.getMaxIntrinsicWidth(height);
+  }
+
+  @override
+  double computeDistanceToActualBaseline(TextBaseline baseline) {
+    // The baseline of this widget is the baseline of its child
+    return direction == Axis.horizontal
+      ? child!.computeDistanceToActualBaseline(baseline)! + borderSide.width
+      : child!.computeDistanceToActualBaseline(baseline)! + leadingBorderSide.width;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    return direction == Axis.horizontal
+      ? borderSide.width * 2.0 + _maxHeight(child, width)
+      : leadingBorderSide.width + _maxHeight(child, width) + trailingBorderSide.width;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    return direction == Axis.horizontal
+        ? borderSide.width * 2.0 + _minHeight(child, width)
+        : leadingBorderSide.width + _maxHeight(child, width) + trailingBorderSide.width;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    return direction == Axis.horizontal
+      ? leadingBorderSide.width + _maxWidth(child, height) + trailingBorderSide.width
+      : borderSide.width * 2.0 + _maxWidth(child, height);
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    return direction == Axis.horizontal
+      ? leadingBorderSide.width + _minWidth(child, height) + trailingBorderSide.width
+      : borderSide.width * 2.0 + _minWidth(child, height);
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _computeSize(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.dryLayoutChild,
+    );
+  }
+
+  @override
+  void performLayout() {
+    size = _computeSize(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.layoutChild,
+    );
+    if (child == null) {
+      return;
+    }
+    final BoxParentData childParentData = child!.parentData! as BoxParentData;
+    if (direction == Axis.horizontal) {
+      switch (textDirection) {
+        case TextDirection.ltr:
+          childParentData.offset = Offset(leadingBorderSide.width, borderSide.width);
+          break;
+        case TextDirection.rtl:
+          childParentData.offset = Offset(trailingBorderSide.width, borderSide.width);
+          break;
+      }
+    } else {
+      switch (verticalDirection) {
+        case VerticalDirection.down:
+          childParentData.offset = Offset(borderSide.width, leadingBorderSide.width);
+          break;
+        case VerticalDirection.up:
+          childParentData.offset = Offset(borderSide.width, trailingBorderSide.width);
+          break;
+      }
+    }
+  }
+
+  Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
+    if (child == null) {
+      if (direction == Axis.horizontal) {
+        return constraints.constrain(Size(
+          leadingBorderSide.width + trailingBorderSide.width,
+          borderSide.width * 2.0,
+        ));
+      } else {
+        return constraints.constrain(Size(
+          borderSide.width * 2.0,
+          leadingBorderSide.width + trailingBorderSide.width,
+        ));
+      }
+    }
+
+    final double leftConstraint;
+    final double rightConstraint;
+    final double topConstraint;
+    final double bottomConstraint;
+
+    // It does not matter what [textDirection] or [verticalDirection] is,
+    // since deflating the size constraints horizontally/vertically
+    // and the returned size accounts for the width of both sides.
+    if (direction == Axis.horizontal) {
+      rightConstraint = trailingBorderSide.width;
+      leftConstraint = leadingBorderSide.width;
+      topConstraint = borderSide.width;
+      bottomConstraint = borderSide.width;
+    } else {
+      rightConstraint = borderSide.width;
+      leftConstraint = borderSide.width;
+      topConstraint = leadingBorderSide.width;
+      bottomConstraint = trailingBorderSide.width;
+    }
+    final BoxConstraints innerConstraints = constraints.deflate(
+      EdgeInsets.only(
+        left: leftConstraint,
+        top: topConstraint,
+        right: rightConstraint,
+        bottom: bottomConstraint,
+      ),
+    );
+    final Size childSize = layoutChild(child!, innerConstraints);
+
+    return constraints.constrain(Size(
+      leftConstraint + childSize.width + rightConstraint,
+      topConstraint + childSize.height + bottomConstraint,
+    ));
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    super.paint(context, offset);
+    final Offset bottomRight = size.bottomRight(offset);
+    final Rect outer = Rect.fromLTRB(offset.dx, offset.dy, bottomRight.dx, bottomRight.dy);
+    final Rect center = outer.deflate(borderSide.width / 2.0);
+    const double sweepAngle = math.pi / 2.0;
+    final RRect rrect = RRect.fromRectAndCorners(
+      center,
+      topLeft: borderRadius.topLeft,
+      topRight: borderRadius.topRight,
+      bottomLeft: borderRadius.bottomLeft,
+      bottomRight: borderRadius.bottomRight,
+    ).scaleRadii();
+    final Rect tlCorner = Rect.fromLTWH(
+      rrect.left,
+      rrect.top,
+      rrect.tlRadiusX * 2.0,
+      rrect.tlRadiusY * 2.0,
+    );
+    final Rect blCorner = Rect.fromLTWH(
+      rrect.left,
+      rrect.bottom - (rrect.blRadiusY * 2.0),
+      rrect.blRadiusX * 2.0,
+      rrect.blRadiusY * 2.0,
+    );
+    final Rect trCorner = Rect.fromLTWH(
+      rrect.right - (rrect.trRadiusX * 2),
+      rrect.top,
+      rrect.trRadiusX * 2,
+      rrect.trRadiusY * 2,
+    );
+    final Rect brCorner = Rect.fromLTWH(
+      rrect.right - (rrect.brRadiusX * 2),
+      rrect.bottom - (rrect.brRadiusY * 2),
+      rrect.brRadiusX * 2,
+      rrect.brRadiusY * 2,
+    );
+
+    if (direction == Axis.horizontal) {
+      final Paint leadingPaint = leadingBorderSide.toPaint();
+      switch (textDirection) {
+        case TextDirection.ltr:
+          if (isLastButton) {
+            final Path leftPath = Path();
+            leftPath..moveTo(rrect.left, rrect.bottom + leadingBorderSide.width / 2)
+              ..lineTo(rrect.left, rrect.top - leadingBorderSide.width / 2);
+            context.canvas.drawPath(leftPath, leadingPaint);
+
+            final Paint endingPaint = trailingBorderSide.toPaint();
+            final Path endingPath = Path();
+            endingPath..moveTo(rrect.left + borderSide.width / 2.0, rrect.top)
+              ..lineTo(rrect.right - rrect.trRadiusX, rrect.top)
+              ..addArc(trCorner, math.pi * 3.0 / 2.0, sweepAngle)
+              ..lineTo(rrect.right, rrect.bottom - rrect.brRadiusY)
+              ..addArc(brCorner, 0, sweepAngle)
+              ..lineTo(rrect.left + borderSide.width / 2.0, rrect.bottom);
+            context.canvas.drawPath(endingPath, endingPaint);
+          } else if (isFirstButton) {
+            final Path leadingPath = Path();
+            leadingPath..moveTo(outer.right, rrect.bottom)
+              ..lineTo(rrect.left + rrect.blRadiusX, rrect.bottom)
+              ..addArc(blCorner, math.pi / 2.0, sweepAngle)
+              ..lineTo(rrect.left, rrect.top + rrect.tlRadiusY)
+              ..addArc(tlCorner, math.pi, sweepAngle)
+              ..lineTo(outer.right, rrect.top);
+            context.canvas.drawPath(leadingPath, leadingPaint);
+          } else {
+            final Path leadingPath = Path();
+            leadingPath..moveTo(rrect.left, rrect.bottom + leadingBorderSide.width / 2)
+              ..lineTo(rrect.left, rrect.top - leadingBorderSide.width / 2);
+            context.canvas.drawPath(leadingPath, leadingPaint);
+
+            final Paint horizontalPaint = borderSide.toPaint();
+            final Path horizontalPaths = Path();
+            horizontalPaths..moveTo(rrect.left + borderSide.width / 2.0, rrect.top)
+              ..lineTo(outer.right - rrect.trRadiusX, rrect.top)
+              ..moveTo(rrect.left + borderSide.width / 2.0 + rrect.tlRadiusX, rrect.bottom)
+              ..lineTo(outer.right - rrect.trRadiusX, rrect.bottom);
+
+            context.canvas.drawPath(horizontalPaths, horizontalPaint);
+          }
+          break;
+        case TextDirection.rtl:
+          if (isLastButton) {
+            final Path leadingPath = Path();
+            leadingPath..moveTo(rrect.right, rrect.bottom + leadingBorderSide.width / 2)
+              ..lineTo(rrect.right, rrect.top - leadingBorderSide.width / 2);
+            context.canvas.drawPath(leadingPath, leadingPaint);
+
+            final Paint endingPaint = trailingBorderSide.toPaint();
+            final Path endingPath = Path();
+            endingPath..moveTo(rrect.right - borderSide.width / 2.0, rrect.top)
+              ..lineTo(rrect.left + rrect.tlRadiusX, rrect.top)
+              ..addArc(tlCorner, math.pi * 3.0 / 2.0, -sweepAngle)
+              ..lineTo(rrect.left, rrect.bottom - rrect.blRadiusY)
+              ..addArc(blCorner, math.pi, -sweepAngle)
+              ..lineTo(rrect.right - borderSide.width / 2.0, rrect.bottom);
+            context.canvas.drawPath(endingPath, endingPaint);
+          } else if (isFirstButton) {
+            final Path leadingPath = Path();
+            leadingPath..moveTo(outer.left, rrect.bottom)
+              ..lineTo(rrect.right - rrect.brRadiusX, rrect.bottom)
+              ..addArc(brCorner, math.pi / 2.0, -sweepAngle)
+              ..lineTo(rrect.right, rrect.top + rrect.trRadiusY)
+              ..addArc(trCorner, 0, -sweepAngle)
+              ..lineTo(outer.left, rrect.top);
+            context.canvas.drawPath(leadingPath, leadingPaint);
+          } else {
+            final Path leadingPath = Path();
+            leadingPath..moveTo(rrect.right, rrect.bottom + leadingBorderSide.width / 2)
+              ..lineTo(rrect.right, rrect.top - leadingBorderSide.width / 2);
+            context.canvas.drawPath(leadingPath, leadingPaint);
+
+            final Paint horizontalPaint = borderSide.toPaint();
+            final Path horizontalPaths = Path();
+            horizontalPaths..moveTo(rrect.right - borderSide.width / 2.0, rrect.top)
+              ..lineTo(outer.left - rrect.tlRadiusX, rrect.top)
+              ..moveTo(rrect.right - borderSide.width / 2.0 + rrect.trRadiusX, rrect.bottom)
+              ..lineTo(outer.left - rrect.tlRadiusX, rrect.bottom);
+            context.canvas.drawPath(horizontalPaths, horizontalPaint);
+          }
+          break;
+      }
+    } else {
+      final Paint leadingPaint = leadingBorderSide.toPaint();
+      switch (verticalDirection) {
+        case VerticalDirection.down:
+          if (isLastButton) {
+            final Path topPath = Path();
+            topPath..moveTo(outer.left, outer.top + leadingBorderSide.width / 2)
+              ..lineTo(outer.right, outer.top + leadingBorderSide.width / 2);
+
+            context.canvas.drawPath(topPath, leadingPaint);
+
+            final Paint endingPaint = trailingBorderSide.toPaint();
+            final Path endingPath = Path();
+            endingPath..moveTo(rrect.left, rrect.top + leadingBorderSide.width / 2.0)
+              ..lineTo(rrect.left, rrect.bottom - rrect.blRadiusY)
+              ..addArc(blCorner, math.pi * 3.0, -sweepAngle)
+              ..lineTo(rrect.right - rrect.blRadiusX, rrect.bottom)
+              ..addArc(brCorner, math.pi / 2.0, -sweepAngle)
+              ..lineTo(rrect.right, rrect.top + leadingBorderSide.width / 2.0);
+
+            context.canvas.drawPath(endingPath, endingPaint);
+          } else if (isFirstButton) {
+            final Path leadingPath = Path();
+            leadingPath..moveTo(rrect.left, outer.bottom)
+              ..lineTo(rrect.left, rrect.top + rrect.tlRadiusX)
+              ..addArc(tlCorner, math.pi, sweepAngle)
+              ..lineTo(rrect.right - rrect.trRadiusX, rrect.top)
+              ..addArc(trCorner, math.pi * 3.0 / 2.0, sweepAngle)
+              ..lineTo(rrect.right, outer.bottom);
+
+            context.canvas.drawPath(leadingPath, leadingPaint);
+          } else {
+            final Path topPath = Path();
+            topPath..moveTo(outer.left, outer.top + leadingBorderSide.width / 2)
+              ..lineTo(outer.right, outer.top + leadingBorderSide.width / 2);
+            context.canvas.drawPath(topPath, leadingPaint);
+
+            final Paint paint = borderSide.toPaint();
+            final Path paths = Path(); // Left and right borders.
+            paths..moveTo(rrect.left, outer.top + leadingBorderSide.width)
+              ..lineTo(rrect.left, outer.bottom)
+              ..moveTo(rrect.right, outer.top + leadingBorderSide.width)
+              ..lineTo(rrect.right, outer.bottom);
+
+            context.canvas.drawPath(paths, paint);
+          }
+          break;
+        case VerticalDirection.up:
+          if (isLastButton) {
+            final Path bottomPath = Path();
+            bottomPath..moveTo(outer.left, outer.bottom - leadingBorderSide.width / 2.0)
+              ..lineTo(outer.right, outer.bottom - leadingBorderSide.width / 2.0);
+            context.canvas.drawPath(bottomPath, leadingPaint);
+
+            final Paint endingPaint = trailingBorderSide.toPaint();
+            final Path endingPath = Path();
+            endingPath..moveTo(rrect.left, rrect.bottom - leadingBorderSide.width / 2.0)
+              ..lineTo(rrect.left, rrect.top + rrect.tlRadiusY)
+              ..addArc(tlCorner, math.pi, sweepAngle)
+              ..lineTo(rrect.right - rrect.trRadiusX, rrect.top)
+              ..addArc(trCorner, math.pi * 3.0 / 2.0, sweepAngle)
+              ..lineTo(rrect.right, rrect.bottom - leadingBorderSide.width / 2.0);
+            context.canvas.drawPath(endingPath, endingPaint);
+          } else if (isFirstButton) {
+            final Path leadingPath = Path();
+            leadingPath..moveTo(rrect.left, outer.top)
+              ..lineTo(rrect.left, rrect.bottom - rrect.blRadiusY)
+              ..addArc(blCorner, math.pi, -sweepAngle)
+              ..lineTo(rrect.right - rrect.brRadiusX, rrect.bottom)
+              ..addArc(brCorner, math.pi / 2.0, -sweepAngle)
+              ..lineTo(rrect.right, outer.top);
+            context.canvas.drawPath(leadingPath, leadingPaint);
+          } else {
+            final Path bottomPath = Path();
+            bottomPath..moveTo(outer.left, outer.bottom - leadingBorderSide.width / 2.0)
+              ..lineTo(outer.right, outer.bottom - leadingBorderSide.width / 2.0);
+            context.canvas.drawPath(bottomPath, leadingPaint);
+
+            final Paint paint = borderSide.toPaint();
+            final Path paths = Path(); // Left and right borders.
+            paths..moveTo(rrect.left, outer.top)
+              ..lineTo(rrect.left, outer.bottom - leadingBorderSide.width)
+              ..moveTo(rrect.right, outer.top)
+              ..lineTo(rrect.right, outer.bottom - leadingBorderSide.width);
+            context.canvas.drawPath(paths, paint);
+          }
+          break;
+      }
+    }
+  }
+}
diff --git a/lib/src/material/toggle_buttons_theme.dart b/lib/src/material/toggle_buttons_theme.dart
new file mode 100644
index 0000000..ce587b1
--- /dev/null
+++ b/lib/src/material/toggle_buttons_theme.dart
@@ -0,0 +1,279 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'theme.dart';
+
+/// Defines the color and border properties of [ToggleButtons] widgets.
+///
+/// Used by [ToggleButtonsTheme] to control the color and border properties
+/// of toggle buttons in a widget subtree.
+///
+/// To obtain the current [ToggleButtonsTheme], use [ToggleButtonsTheme.of].
+///
+/// Values specified here are used for [ToggleButtons] properties that are not
+/// given an explicit non-null value.
+///
+/// See also:
+///
+///  * [ToggleButtonsTheme], which describes the actual configuration of a
+///    toggle buttons theme.
+@immutable
+class ToggleButtonsThemeData with Diagnosticable {
+  /// Creates the set of color and border properties used to configure
+  /// [ToggleButtons].
+  const ToggleButtonsThemeData({
+    this.textStyle,
+    this.constraints,
+    this.color,
+    this.selectedColor,
+    this.disabledColor,
+    this.fillColor,
+    this.focusColor,
+    this.highlightColor,
+    this.hoverColor,
+    this.splashColor,
+    this.borderColor,
+    this.selectedBorderColor,
+    this.disabledBorderColor,
+    this.borderRadius,
+    this.borderWidth,
+  });
+
+  /// The default text style for [ToggleButtons.children].
+  ///
+  /// [TextStyle.color] will be ignored and substituted by [color],
+  /// [selectedColor] or [disabledColor] depending on whether the buttons
+  /// are active, selected, or disabled.
+  final TextStyle? textStyle;
+
+  /// Defines the button's size.
+  ///
+  /// Typically used to constrain the button's minimum size.
+  final BoxConstraints? constraints;
+
+  /// The color for descendant [Text] and [Icon] widgets if the toggle button
+  /// is enabled.
+  final Color? color;
+
+  /// The color for descendant [Text] and [Icon] widgets if the toggle button
+  /// is selected.
+  final Color? selectedColor;
+
+  /// The color for descendant [Text] and [Icon] widgets if the toggle button
+  /// is disabled.
+  final Color? disabledColor;
+
+  /// The fill color for selected toggle buttons.
+  final Color? fillColor;
+
+  /// The color to use for filling the button when the button has input focus.
+  final Color? focusColor;
+
+  /// The highlight color for the toggle button's [InkWell].
+  final Color? highlightColor;
+
+  /// The splash color for the toggle button's [InkWell].
+  final Color? splashColor;
+
+  /// The color to use for filling the toggle button when the button has a
+  /// pointer hovering over it.
+  final Color? hoverColor;
+
+  /// The border color to display when the toggle button is enabled.
+  final Color? borderColor;
+
+  /// The border color to display when the toggle button is selected.
+  final Color? selectedBorderColor;
+
+  /// The border color to display when the toggle button is disabled.
+  final Color? disabledBorderColor;
+
+  /// The width of the border surrounding each toggle button.
+  ///
+  /// This applies to both the greater surrounding border, as well as the
+  /// borders dividing each toggle button.
+  ///
+  /// To render a hairline border (one physical pixel), set borderWidth to 0.0.
+  /// See [BorderSide.width] for more details on hairline borders.
+  final double? borderWidth;
+
+  /// The radii of the border's corners.
+  final BorderRadius? borderRadius;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  ToggleButtonsThemeData copyWith({
+    TextStyle? textStyle,
+    BoxConstraints? constraints,
+    Color? color,
+    Color? selectedColor,
+    Color? disabledColor,
+    Color? fillColor,
+    Color? focusColor,
+    Color? highlightColor,
+    Color? hoverColor,
+    Color? splashColor,
+    Color? borderColor,
+    Color? selectedBorderColor,
+    Color? disabledBorderColor,
+    BorderRadius? borderRadius,
+    double? borderWidth,
+  }) {
+    return ToggleButtonsThemeData(
+      textStyle: textStyle ?? this.textStyle,
+      constraints: constraints ?? this.constraints,
+      color: color ?? this.color,
+      selectedColor: selectedColor ?? this.selectedColor,
+      disabledColor: disabledColor ?? this.disabledColor,
+      fillColor: fillColor ?? this.fillColor,
+      focusColor: focusColor ?? this.focusColor,
+      highlightColor: highlightColor ?? this.highlightColor,
+      hoverColor: hoverColor ?? this.hoverColor,
+      splashColor: splashColor ?? this.splashColor,
+      borderColor: borderColor ?? this.borderColor,
+      selectedBorderColor: selectedBorderColor ?? this.selectedBorderColor,
+      disabledBorderColor: disabledBorderColor ?? this.disabledBorderColor,
+      borderRadius: borderRadius ?? this.borderRadius,
+      borderWidth: borderWidth ?? this.borderWidth,
+    );
+  }
+
+  /// Linearly interpolate between two toggle buttons themes.
+  static ToggleButtonsThemeData? lerp(ToggleButtonsThemeData? a, ToggleButtonsThemeData? b, double t) {
+    assert (t != null);
+    if (a == null && b == null)
+      return null;
+    return ToggleButtonsThemeData(
+      textStyle: TextStyle.lerp(a?.textStyle, b?.textStyle, t),
+      constraints: BoxConstraints.lerp(a?.constraints, b?.constraints, t),
+      color: Color.lerp(a?.color, b?.color, t),
+      selectedColor: Color.lerp(a?.selectedColor, b?.selectedColor, t),
+      disabledColor: Color.lerp(a?.disabledColor, b?.disabledColor, t),
+      fillColor: Color.lerp(a?.fillColor, b?.fillColor, t),
+      focusColor: Color.lerp(a?.focusColor, b?.focusColor, t),
+      highlightColor: Color.lerp(a?.highlightColor, b?.highlightColor, t),
+      hoverColor: Color.lerp(a?.hoverColor, b?.hoverColor, t),
+      splashColor: Color.lerp(a?.splashColor, b?.splashColor, t),
+      borderColor: Color.lerp(a?.borderColor, b?.borderColor, t),
+      selectedBorderColor: Color.lerp(a?.selectedBorderColor, b?.selectedBorderColor, t),
+      disabledBorderColor: Color.lerp(a?.disabledBorderColor, b?.disabledBorderColor, t),
+      borderRadius: BorderRadius.lerp(a?.borderRadius, b?.borderRadius, t),
+      borderWidth: lerpDouble(a?.borderWidth, b?.borderWidth, t),
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      textStyle,
+      constraints,
+      color,
+      selectedColor,
+      disabledColor,
+      fillColor,
+      focusColor,
+      highlightColor,
+      hoverColor,
+      splashColor,
+      borderColor,
+      selectedBorderColor,
+      disabledBorderColor,
+      borderRadius,
+      borderWidth,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ToggleButtonsThemeData
+        && other.textStyle == textStyle
+        && other.constraints == constraints
+        && other.color == color
+        && other.selectedColor == selectedColor
+        && other.disabledColor == disabledColor
+        && other.fillColor == fillColor
+        && other.focusColor == focusColor
+        && other.highlightColor == highlightColor
+        && other.hoverColor == hoverColor
+        && other.splashColor == splashColor
+        && other.borderColor == borderColor
+        && other.selectedBorderColor == selectedBorderColor
+        && other.disabledBorderColor == disabledBorderColor
+        && other.borderRadius == borderRadius
+        && other.borderWidth == borderWidth;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    textStyle?.debugFillProperties(properties, prefix: 'textStyle.');
+    properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(ColorProperty('selectedColor', selectedColor, defaultValue: null));
+    properties.add(ColorProperty('disabledColor', disabledColor, defaultValue: null));
+    properties.add(ColorProperty('fillColor', fillColor, defaultValue: null));
+    properties.add(ColorProperty('focusColor', focusColor, defaultValue: null));
+    properties.add(ColorProperty('highlightColor', highlightColor, defaultValue: null));
+    properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: null));
+    properties.add(ColorProperty('splashColor', splashColor, defaultValue: null));
+    properties.add(ColorProperty('borderColor', borderColor, defaultValue: null));
+    properties.add(ColorProperty('selectedBorderColor', selectedBorderColor, defaultValue: null));
+    properties.add(ColorProperty('disabledBorderColor', disabledBorderColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius, defaultValue: null));
+    properties.add(DoubleProperty('borderWidth', borderWidth, defaultValue: null));
+  }
+}
+
+/// An inherited widget that defines color and border parameters for
+/// [ToggleButtons] in this widget's subtree.
+///
+/// Values specified here are used for [ToggleButtons] properties that are not
+/// given an explicit non-null value.
+class ToggleButtonsTheme extends InheritedTheme {
+  /// Creates a toggle buttons theme that controls the color and border
+  /// parameters for [ToggleButtons].
+  ///
+  /// The data argument must not be null.
+  const ToggleButtonsTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null), super(key: key, child: child);
+
+  /// Specifies the color and border values for descendant [ToggleButtons] widgets.
+  final ToggleButtonsThemeData data;
+
+  /// The closest instance of this class that encloses the given context.
+  ///
+  /// If there is no enclosing [ToggleButtonsTheme] widget, then
+  /// [ThemeData.toggleButtonsTheme] is used.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// ToggleButtonsTheme theme = ToggleButtonsTheme.of(context);
+  /// ```
+  static ToggleButtonsThemeData of(BuildContext context) {
+    final ToggleButtonsTheme? toggleButtonsTheme = context.dependOnInheritedWidgetOfExactType<ToggleButtonsTheme>();
+    return toggleButtonsTheme?.data ?? Theme.of(context).toggleButtonsTheme;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return ToggleButtonsTheme(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(ToggleButtonsTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/toggleable.dart b/lib/src/material/toggleable.dart
new file mode 100644
index 0000000..7a1a86a
--- /dev/null
+++ b/lib/src/material/toggleable.dart
@@ -0,0 +1,521 @@
+// Copyright 2014 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.
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+
+import 'constants.dart';
+
+// Duration of the animation that moves the toggle from one state to another.
+const Duration _kToggleDuration = Duration(milliseconds: 200);
+
+// Duration of the fade animation for the reaction when focus and hover occur.
+const Duration _kReactionFadeDuration = Duration(milliseconds: 50);
+
+/// A base class for material style toggleable controls with toggle animations.
+///
+/// This class handles storing the current value, dispatching ValueChanged on a
+/// tap gesture and driving a changed animation. Subclasses are responsible for
+/// painting.
+abstract class RenderToggleable extends RenderConstrainedBox {
+  /// Creates a toggleable render object.
+  ///
+  /// The [activeColor], and [inactiveColor] arguments must not be
+  /// null. The [value] can only be null if tristate is true.
+  RenderToggleable({
+    required bool? value,
+    bool tristate = false,
+    required Color activeColor,
+    required Color inactiveColor,
+    Color? hoverColor,
+    Color? focusColor,
+    Color? reactionColor,
+    Color? inactiveReactionColor,
+    required double splashRadius,
+    ValueChanged<bool?>? onChanged,
+    required BoxConstraints additionalConstraints,
+    required TickerProvider vsync,
+    bool hasFocus = false,
+    bool hovering = false,
+  }) : assert(tristate != null),
+       assert(tristate || value != null),
+       assert(activeColor != null),
+       assert(inactiveColor != null),
+       assert(vsync != null),
+       _value = value,
+       _tristate = tristate,
+       _activeColor = activeColor,
+       _inactiveColor = inactiveColor,
+       _hoverColor = hoverColor ?? activeColor.withAlpha(kRadialReactionAlpha),
+       _focusColor = focusColor ?? activeColor.withAlpha(kRadialReactionAlpha),
+       _reactionColor = reactionColor ?? activeColor.withAlpha(kRadialReactionAlpha),
+       _inactiveReactionColor = inactiveReactionColor ?? activeColor.withAlpha(kRadialReactionAlpha),
+       _splashRadius = splashRadius,
+       _onChanged = onChanged,
+       _hasFocus = hasFocus,
+       _hovering = hovering,
+       _vsync = vsync,
+       super(additionalConstraints: additionalConstraints) {
+    _tap = TapGestureRecognizer()
+      ..onTapDown = _handleTapDown
+      ..onTap = _handleTap
+      ..onTapUp = _handleTapUp
+      ..onTapCancel = _handleTapCancel;
+    _positionController = AnimationController(
+      duration: _kToggleDuration,
+      value: value == false ? 0.0 : 1.0,
+      vsync: vsync,
+    );
+    _position = CurvedAnimation(
+      parent: _positionController,
+      curve: Curves.linear,
+    )..addListener(markNeedsPaint);
+    _reactionController = AnimationController(
+      duration: kRadialReactionDuration,
+      vsync: vsync,
+    );
+    _reaction = CurvedAnimation(
+      parent: _reactionController,
+      curve: Curves.fastOutSlowIn,
+    )..addListener(markNeedsPaint);
+    _reactionHoverFadeController = AnimationController(
+      duration: _kReactionFadeDuration,
+      value: hovering || hasFocus ? 1.0 : 0.0,
+      vsync: vsync,
+    );
+    _reactionHoverFade = CurvedAnimation(
+      parent: _reactionHoverFadeController,
+      curve: Curves.fastOutSlowIn,
+    )..addListener(markNeedsPaint);
+    _reactionFocusFadeController = AnimationController(
+      duration: _kReactionFadeDuration,
+      value: hovering || hasFocus ? 1.0 : 0.0,
+      vsync: vsync,
+    );
+    _reactionFocusFade = CurvedAnimation(
+      parent: _reactionFocusFadeController,
+      curve: Curves.fastOutSlowIn,
+    )..addListener(markNeedsPaint);
+  }
+
+  /// Used by subclasses to manipulate the visual value of the control.
+  ///
+  /// Some controls respond to user input by updating their visual value. For
+  /// example, the thumb of a switch moves from one position to another when
+  /// dragged. These controls manipulate this animation controller to update
+  /// their [position] and eventually trigger an [onChanged] callback when the
+  /// animation reaches either 0.0 or 1.0.
+  @protected
+  AnimationController get positionController => _positionController;
+  late AnimationController _positionController;
+
+  /// The visual value of the control.
+  ///
+  /// When the control is inactive, the [value] is false and this animation has
+  /// the value 0.0. When the control is active, the value either true or tristate
+  /// is true and the value is null. When the control is active the animation
+  /// has a value of 1.0. When the control is changing from inactive
+  /// to active (or vice versa), [value] is the target value and this animation
+  /// gradually updates from 0.0 to 1.0 (or vice versa).
+  CurvedAnimation get position => _position;
+  late CurvedAnimation _position;
+
+  /// Used by subclasses to control the radial reaction animation.
+  ///
+  /// Some controls have a radial ink reaction to user input. This animation
+  /// controller can be used to start or stop these ink reactions.
+  ///
+  /// Subclasses should call [paintRadialReaction] to actually paint the radial
+  /// reaction.
+  @protected
+  AnimationController get reactionController => _reactionController;
+  late AnimationController _reactionController;
+  late Animation<double> _reaction;
+
+  /// Used by subclasses to control the radial reaction's opacity animation for
+  /// [hasFocus] changes.
+  ///
+  /// Some controls have a radial ink reaction to focus. This animation
+  /// controller can be used to start or stop these ink reaction fade-ins and
+  /// fade-outs.
+  ///
+  /// Subclasses should call [paintRadialReaction] to actually paint the radial
+  /// reaction.
+  @protected
+  AnimationController get reactionFocusFadeController => _reactionFocusFadeController;
+  late AnimationController _reactionFocusFadeController;
+  late Animation<double> _reactionFocusFade;
+
+  /// Used by subclasses to control the radial reaction's opacity animation for
+  /// [hovering] changes.
+  ///
+  /// Some controls have a radial ink reaction to pointer hover. This animation
+  /// controller can be used to start or stop these ink reaction fade-ins and
+  /// fade-outs.
+  ///
+  /// Subclasses should call [paintRadialReaction] to actually paint the radial
+  /// reaction.
+  @protected
+  AnimationController get reactionHoverFadeController => _reactionHoverFadeController;
+  late AnimationController _reactionHoverFadeController;
+  late Animation<double> _reactionHoverFade;
+
+  /// True if this toggleable has the input focus.
+  bool get hasFocus => _hasFocus;
+  bool _hasFocus;
+  set hasFocus(bool value) {
+    assert(value != null);
+    if (value == _hasFocus)
+      return;
+    _hasFocus = value;
+    if (_hasFocus) {
+      _reactionFocusFadeController.forward();
+    } else {
+      _reactionFocusFadeController.reverse();
+    }
+    markNeedsPaint();
+  }
+
+  /// True if this toggleable is being hovered over by a pointer.
+  bool get hovering => _hovering;
+  bool _hovering;
+  set hovering(bool value) {
+    assert(value != null);
+    if (value == _hovering)
+      return;
+    _hovering = value;
+    if (_hovering) {
+      _reactionHoverFadeController.forward();
+    } else {
+      _reactionHoverFadeController.reverse();
+    }
+    markNeedsPaint();
+  }
+
+  /// The [TickerProvider] for the [AnimationController]s that run the animations.
+  TickerProvider get vsync => _vsync;
+  TickerProvider _vsync;
+  set vsync(TickerProvider value) {
+    assert(value != null);
+    if (value == _vsync)
+      return;
+    _vsync = value;
+    positionController.resync(vsync);
+    reactionController.resync(vsync);
+  }
+
+  /// False if this control is "inactive" (not checked, off, or unselected).
+  ///
+  /// If value is true then the control "active" (checked, on, or selected). If
+  /// tristate is true and value is null, then the control is considered to be
+  /// in its third or "indeterminate" state.
+  ///
+  /// When the value changes, this object starts the [positionController] and
+  /// [position] animations to animate the visual appearance of the control to
+  /// the new value.
+  bool? get value => _value;
+  bool? _value;
+  set value(bool? value) {
+    assert(tristate || value != null);
+    if (value == _value)
+      return;
+    _value = value;
+    markNeedsSemanticsUpdate();
+    _position
+      ..curve = Curves.easeIn
+      ..reverseCurve = Curves.easeOut;
+    if (tristate) {
+      if (value == null)
+        _positionController.value = 0.0;
+      if (value != false)
+        _positionController.forward();
+      else
+        _positionController.reverse();
+    } else {
+      if (value == true)
+        _positionController.forward();
+      else
+        _positionController.reverse();
+    }
+  }
+
+  /// If true, [value] can be true, false, or null, otherwise [value] must
+  /// be true or false.
+  ///
+  /// When [tristate] is true and [value] is null, then the control is
+  /// considered to be in its third or "indeterminate" state.
+  bool get tristate => _tristate;
+  bool _tristate;
+  set tristate(bool value) {
+    assert(tristate != null);
+    if (value == _tristate)
+      return;
+    _tristate = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// The color that should be used in the active state (i.e., when [value] is true).
+  ///
+  /// For example, a checkbox should use this color when checked.
+  Color get activeColor => _activeColor;
+  Color _activeColor;
+  set activeColor(Color value) {
+    assert(value != null);
+    if (value == _activeColor)
+      return;
+    _activeColor = value;
+    markNeedsPaint();
+  }
+
+  /// The color that should be used in the inactive state (i.e., when [value] is false).
+  ///
+  /// For example, a checkbox should use this color when unchecked.
+  Color get inactiveColor => _inactiveColor;
+  Color _inactiveColor;
+  set inactiveColor(Color value) {
+    assert(value != null);
+    if (value == _inactiveColor)
+      return;
+    _inactiveColor = value;
+    markNeedsPaint();
+  }
+
+  /// The color that should be used for the reaction when [hovering] is true.
+  ///
+  /// Used when the toggleable needs to change the reaction color/transparency,
+  /// when it is being hovered over.
+  ///
+  /// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
+  Color get hoverColor => _hoverColor;
+  Color _hoverColor;
+  set hoverColor(Color value) {
+    assert(value != null);
+    if (value == _hoverColor)
+      return;
+    _hoverColor = value;
+    markNeedsPaint();
+  }
+
+  /// The color that should be used for the reaction when [hasFocus] is true.
+  ///
+  /// Used when the toggleable needs to change the reaction color/transparency,
+  /// when it has focus.
+  ///
+  /// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
+  Color get focusColor => _focusColor;
+  Color _focusColor;
+  set focusColor(Color value) {
+    assert(value != null);
+    if (value == _focusColor)
+      return;
+    _focusColor = value;
+    markNeedsPaint();
+  }
+
+  /// The color that should be used for the reaction when the toggleable is
+  /// active.
+  ///
+  /// Used when the toggleable needs to change the reaction color/transparency
+  /// that is displayed when the toggleable is active and tapped.
+  ///
+  /// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
+  Color? get reactionColor => _reactionColor;
+  Color? _reactionColor;
+  set reactionColor(Color? value) {
+    assert(value != null);
+    if (value == _reactionColor)
+      return;
+    _reactionColor = value;
+    markNeedsPaint();
+  }
+
+  /// The color that should be used for the reaction when the toggleable is
+  /// inactive.
+  ///
+  /// Used when the toggleable needs to change the reaction color/transparency
+  /// that is displayed when the toggleable is inactive and tapped.
+  ///
+  /// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
+  Color? get inactiveReactionColor => _inactiveReactionColor;
+  Color? _inactiveReactionColor;
+  set inactiveReactionColor(Color? value) {
+    assert(value != null);
+    if (value == _inactiveReactionColor)
+      return;
+    _inactiveReactionColor = value;
+    markNeedsPaint();
+  }
+
+  /// The splash radius for the radial reaction.
+  double get splashRadius => _splashRadius;
+  double _splashRadius;
+  set splashRadius(double value) {
+    if (value == _splashRadius)
+      return;
+    _splashRadius = value;
+    markNeedsPaint();
+  }
+
+  /// Called when the control changes value.
+  ///
+  /// If the control is tapped, [onChanged] is called immediately with the new
+  /// value.
+  ///
+  /// The control is considered interactive (see [isInteractive]) if this
+  /// callback is non-null. If the callback is null, then the control is
+  /// disabled, and non-interactive. A disabled checkbox, for example, is
+  /// displayed using a grey color and its value cannot be changed.
+  ValueChanged<bool?>? get onChanged => _onChanged;
+  ValueChanged<bool?>? _onChanged;
+  set onChanged(ValueChanged<bool?>? value) {
+    if (value == _onChanged)
+      return;
+    final bool wasInteractive = isInteractive;
+    _onChanged = value;
+    if (wasInteractive != isInteractive) {
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  /// Whether [value] of this control can be changed by user interaction.
+  ///
+  /// The control is considered interactive if the [onChanged] callback is
+  /// non-null. If the callback is null, then the control is disabled, and
+  /// non-interactive. A disabled checkbox, for example, is displayed using a
+  /// grey color and its value cannot be changed.
+  bool get isInteractive => onChanged != null;
+
+  late TapGestureRecognizer _tap;
+  Offset? _downPosition;
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    if (value == false)
+      _positionController.reverse();
+    else
+      _positionController.forward();
+    if (isInteractive) {
+      switch (_reactionController.status) {
+        case AnimationStatus.forward:
+          _reactionController.forward();
+          break;
+        case AnimationStatus.reverse:
+          _reactionController.reverse();
+          break;
+        case AnimationStatus.dismissed:
+        case AnimationStatus.completed:
+          // nothing to do
+          break;
+      }
+    }
+  }
+
+  @override
+  void detach() {
+    _positionController.stop();
+    _reactionController.stop();
+    _reactionHoverFadeController.stop();
+    _reactionFocusFadeController.stop();
+    super.detach();
+  }
+
+  void _handleTapDown(TapDownDetails details) {
+    if (isInteractive) {
+      _downPosition = globalToLocal(details.globalPosition);
+      _reactionController.forward();
+    }
+  }
+
+  void _handleTap() {
+    if (!isInteractive)
+      return;
+    switch (value) {
+      case false:
+        onChanged!(true);
+        break;
+      case true:
+        onChanged!(tristate ? null : false);
+        break;
+      case null:
+        onChanged!(false);
+        break;
+    }
+    sendSemanticsEvent(const TapSemanticEvent());
+  }
+
+  void _handleTapUp(TapUpDetails details) {
+    _downPosition = null;
+    if (isInteractive)
+      _reactionController.reverse();
+  }
+
+  void _handleTapCancel() {
+    _downPosition = null;
+    if (isInteractive)
+      _reactionController.reverse();
+  }
+
+  @override
+  bool hitTestSelf(Offset position) => true;
+
+  @override
+  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
+    assert(debugHandleEvent(event, entry));
+    if (event is PointerDownEvent && isInteractive)
+      _tap.addPointer(event);
+  }
+
+  /// Used by subclasses to paint the radial ink reaction for this control.
+  ///
+  /// The reaction is painted on the given canvas at the given offset. The
+  /// origin is the center point of the reaction (usually distinct from the
+  /// point at which the user interacted with the control, which is handled
+  /// automatically).
+  void paintRadialReaction(Canvas canvas, Offset offset, Offset origin) {
+    if (!_reaction.isDismissed || !_reactionFocusFade.isDismissed || !_reactionHoverFade.isDismissed) {
+      final Paint reactionPaint = Paint()
+        ..color = Color.lerp(
+          Color.lerp(
+            Color.lerp(inactiveReactionColor, reactionColor, _position.value),
+            hoverColor,
+            _reactionHoverFade.value,
+          ),
+          focusColor,
+          _reactionFocusFade.value,
+        )!;
+      final Offset center = Offset.lerp(_downPosition ?? origin, origin, _reaction.value)!;
+      final Animatable<double> radialReactionRadiusTween = Tween<double>(
+        begin: 0.0,
+        end: splashRadius,
+      );
+      final double reactionRadius = hasFocus || hovering
+          ? splashRadius
+          : radialReactionRadiusTween.evaluate(_reaction);
+      if (reactionRadius > 0.0) {
+        canvas.drawCircle(center + offset, reactionRadius, reactionPaint);
+      }
+    }
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+
+    config.isEnabled = isInteractive;
+    if (isInteractive)
+      config.onTap = _handleTap;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('value', value: value, ifTrue: 'checked', ifFalse: 'unchecked', showName: true));
+    properties.add(FlagProperty('isInteractive', value: isInteractive, ifTrue: 'enabled', ifFalse: 'disabled', defaultValue: true));
+  }
+}
diff --git a/lib/src/material/tooltip.dart b/lib/src/material/tooltip.dart
new file mode 100644
index 0000000..3e7a5b1
--- /dev/null
+++ b/lib/src/material/tooltip.dart
@@ -0,0 +1,628 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/widgets.dart';
+
+import 'colors.dart';
+import 'feedback.dart';
+import 'theme.dart';
+import 'theme_data.dart';
+import 'tooltip_theme.dart';
+
+/// A material design tooltip.
+///
+/// Tooltips provide text labels which help explain the function of a button or
+/// other user interface action. Wrap the button in a [Tooltip] widget and provide
+/// a message which will be shown when the widget is long pressed.
+///
+/// Many widgets, such as [IconButton], [FloatingActionButton], and
+/// [PopupMenuButton] have a `tooltip` property that, when non-null, causes the
+/// widget to include a [Tooltip] in its build.
+///
+/// Tooltips improve the accessibility of visual widgets by proving a textual
+/// representation of the widget, which, for example, can be vocalized by a
+/// screen reader.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=EeEfD5fI-5Q}
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_center_no_null_safety}
+///
+/// This example show a basic [Tooltip] which has a [Text] as child.
+/// [message] contains your label to be shown by the tooltip when
+/// the child that Tooltip wraps is hovered over on web or desktop. On mobile,
+/// the tooltip is shown when the widget is long pressed.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Tooltip(
+///     message: "I am a Tooltip",
+///     child: Text("Hover over the text to show a tooltip."),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_center_no_null_safety}
+///
+/// This example covers most of the attributes available in Tooltip.
+/// `decoration` has been used to give a gradient and borderRadius to Tooltip.
+/// `height` has been used to set a specific height of the Tooltip.
+/// `preferBelow` is false, the tooltip will prefer showing above [Tooltip]'s child widget.
+/// However, it may show the tooltip below if there's not enough space
+/// above the widget.
+/// `textStyle` has been used to set the font size of the 'message'.
+/// `showDuration` accepts a Duration to continue showing the message after the long
+/// press has been released.
+/// `waitDuration` accepts a Duration for which a mouse pointer has to hover over the child
+/// widget before the tooltip is shown.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Tooltip(
+///     message: "I am a Tooltip",
+///     child: Text("Tap this text and hold down to show a tooltip."),
+///     decoration: BoxDecoration(
+///       borderRadius: BorderRadius.circular(25),
+///       gradient: LinearGradient(colors: [Colors.amber, Colors.red]),
+///     ),
+///     height: 50,
+///     padding: EdgeInsets.all(8.0),
+///     preferBelow: false,
+///     textStyle: TextStyle(
+///       fontSize: 24,
+///     ),
+///     showDuration: Duration(seconds: 2),
+///     waitDuration: Duration(seconds: 1),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * <https://material.io/design/components/tooltips.html>
+///  * [TooltipTheme] or [ThemeData.tooltipTheme]
+class Tooltip extends StatefulWidget {
+  /// Creates a tooltip.
+  ///
+  /// By default, tooltips should adhere to the
+  /// [Material specification](https://material.io/design/components/tooltips.html#spec).
+  /// If the optional constructor parameters are not defined, the values
+  /// provided by [TooltipTheme.of] will be used if a [TooltipTheme] is present
+  /// or specified in [ThemeData].
+  ///
+  /// All parameters that are defined in the constructor will
+  /// override the default values _and_ the values in [TooltipTheme.of].
+  const Tooltip({
+    Key? key,
+    required this.message,
+    this.height,
+    this.padding,
+    this.margin,
+    this.verticalOffset,
+    this.preferBelow,
+    this.excludeFromSemantics,
+    this.decoration,
+    this.textStyle,
+    this.waitDuration,
+    this.showDuration,
+    this.child,
+  }) : assert(message != null),
+       super(key: key);
+
+  /// The text to display in the tooltip.
+  final String message;
+
+  /// The height of the tooltip's [child].
+  ///
+  /// If the [child] is null, then this is the tooltip's intrinsic height.
+  final double? height;
+
+  /// The amount of space by which to inset the tooltip's [child].
+  ///
+  /// Defaults to 16.0 logical pixels in each direction.
+  final EdgeInsetsGeometry? padding;
+
+  /// The empty space that surrounds the tooltip.
+  ///
+  /// Defines the tooltip's outer [Container.margin]. By default, a
+  /// long tooltip will span the width of its window. If long enough,
+  /// a tooltip might also span the window's height. This property allows
+  /// one to define how much space the tooltip must be inset from the edges
+  /// of their display window.
+  ///
+  /// If this property is null, then [TooltipThemeData.margin] is used.
+  /// If [TooltipThemeData.margin] is also null, the default margin is
+  /// 0.0 logical pixels on all sides.
+  final EdgeInsetsGeometry? margin;
+
+  /// The vertical gap between the widget and the displayed tooltip.
+  ///
+  /// When [preferBelow] is set to true and tooltips have sufficient space to
+  /// display themselves, this property defines how much vertical space
+  /// tooltips will position themselves under their corresponding widgets.
+  /// Otherwise, tooltips will position themselves above their corresponding
+  /// widgets with the given offset.
+  final double? verticalOffset;
+
+  /// Whether the tooltip defaults to being displayed below the widget.
+  ///
+  /// Defaults to true. If there is insufficient space to display the tooltip in
+  /// the preferred direction, the tooltip will be displayed in the opposite
+  /// direction.
+  final bool? preferBelow;
+
+  /// Whether the tooltip's [message] should be excluded from the semantics
+  /// tree.
+  ///
+  /// Defaults to false. A tooltip will add a [Semantics] label that is set to
+  /// [Tooltip.message]. Set this property to true if the app is going to
+  /// provide its own custom semantics label.
+  final bool? excludeFromSemantics;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// Specifies the tooltip's shape and background color.
+  ///
+  /// The tooltip shape defaults to a rounded rectangle with a border radius of
+  /// 4.0. Tooltips will also default to an opacity of 90% and with the color
+  /// [Colors.grey[700]] if [ThemeData.brightness] is [Brightness.dark], and
+  /// [Colors.white] if it is [Brightness.light].
+  final Decoration? decoration;
+
+  /// The style to use for the message of the tooltip.
+  ///
+  /// If null, the message's [TextStyle] will be determined based on
+  /// [ThemeData]. If [ThemeData.brightness] is set to [Brightness.dark],
+  /// [TextTheme.bodyText2] of [ThemeData.textTheme] will be used with
+  /// [Colors.white]. Otherwise, if [ThemeData.brightness] is set to
+  /// [Brightness.light], [TextTheme.bodyText2] of [ThemeData.textTheme] will be
+  /// used with [Colors.black].
+  final TextStyle? textStyle;
+
+  /// The length of time that a pointer must hover over a tooltip's widget
+  /// before the tooltip will be shown.
+  ///
+  /// Once the pointer leaves the widget, the tooltip will immediately
+  /// disappear.
+  ///
+  /// Defaults to 0 milliseconds (tooltips are shown immediately upon hover).
+  final Duration? waitDuration;
+
+  /// The length of time that the tooltip will be shown after a long press
+  /// is released.
+  ///
+  /// Defaults to 1.5 seconds.
+  final Duration? showDuration;
+
+  @override
+  _TooltipState createState() => _TooltipState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(StringProperty('message', message, showName: false));
+    properties.add(DoubleProperty('height', height, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
+    properties.add(DoubleProperty('vertical offset', verticalOffset, defaultValue: null));
+    properties.add(FlagProperty('position', value: preferBelow, ifTrue: 'below', ifFalse: 'above', showName: true, defaultValue: null));
+    properties.add(FlagProperty('semantics', value: excludeFromSemantics, ifTrue: 'excluded', showName: true, defaultValue: null));
+    properties.add(DiagnosticsProperty<Duration>('wait duration', waitDuration, defaultValue: null));
+    properties.add(DiagnosticsProperty<Duration>('show duration', showDuration, defaultValue: null));
+  }
+}
+
+class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
+  static const double _defaultVerticalOffset = 24.0;
+  static const bool _defaultPreferBelow = true;
+  static const EdgeInsetsGeometry _defaultMargin = EdgeInsets.all(0.0);
+  static const Duration _fadeInDuration = Duration(milliseconds: 150);
+  static const Duration _fadeOutDuration = Duration(milliseconds: 75);
+  static const Duration _defaultShowDuration = Duration(milliseconds: 1500);
+  static const Duration _defaultWaitDuration = Duration(milliseconds: 0);
+  static const bool _defaultExcludeFromSemantics = false;
+
+  late double height;
+  late EdgeInsetsGeometry padding;
+  late EdgeInsetsGeometry margin;
+  late Decoration decoration;
+  late TextStyle textStyle;
+  late double verticalOffset;
+  late bool preferBelow;
+  late bool excludeFromSemantics;
+  late AnimationController _controller;
+  OverlayEntry? _entry;
+  Timer? _hideTimer;
+  Timer? _showTimer;
+  late Duration showDuration;
+  late Duration waitDuration;
+  late bool _mouseIsConnected;
+  bool _longPressActivated = false;
+
+  @override
+  void initState() {
+    super.initState();
+    _mouseIsConnected = RendererBinding.instance!.mouseTracker.mouseIsConnected;
+    _controller = AnimationController(
+      duration: _fadeInDuration,
+      reverseDuration: _fadeOutDuration,
+      vsync: this,
+    )
+      ..addStatusListener(_handleStatusChanged);
+    // Listen to see when a mouse is added.
+    RendererBinding.instance!.mouseTracker.addListener(_handleMouseTrackerChange);
+    // Listen to global pointer events so that we can hide a tooltip immediately
+    // if some other control is clicked on.
+    GestureBinding.instance!.pointerRouter.addGlobalRoute(_handlePointerEvent);
+  }
+
+  // https://material.io/components/tooltips#specs
+  double _getDefaultTooltipHeight() {
+    final ThemeData theme = Theme.of(context);
+    switch (theme.platform) {
+      case TargetPlatform.macOS:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        return 24.0;
+      default:
+        return 32.0;
+    }
+  }
+
+  EdgeInsets _getDefaultPadding() {
+    final ThemeData theme = Theme.of(context);
+    switch (theme.platform) {
+      case TargetPlatform.macOS:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        return const EdgeInsets.symmetric(horizontal: 8.0);
+      default:
+        return const EdgeInsets.symmetric(horizontal: 16.0);
+    }
+  }
+
+  double _getDefaultFontSize() {
+    final ThemeData theme = Theme.of(context);
+    switch (theme.platform) {
+      case TargetPlatform.macOS:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        return 10.0;
+      default:
+        return 14.0;
+    }
+  }
+
+  // Forces a rebuild if a mouse has been added or removed.
+  void _handleMouseTrackerChange() {
+    if (!mounted) {
+      return;
+    }
+    final bool mouseIsConnected = RendererBinding.instance!.mouseTracker.mouseIsConnected;
+    if (mouseIsConnected != _mouseIsConnected) {
+      setState((){
+        _mouseIsConnected = mouseIsConnected;
+      });
+    }
+  }
+
+  void _handleStatusChanged(AnimationStatus status) {
+    if (status == AnimationStatus.dismissed) {
+      _hideTooltip(immediately: true);
+    }
+  }
+
+  void _hideTooltip({ bool immediately = false }) {
+    _showTimer?.cancel();
+    _showTimer = null;
+    if (immediately) {
+      _removeEntry();
+      return;
+    }
+    if (_longPressActivated) {
+      // Tool tips activated by long press should stay around for the showDuration.
+      _hideTimer ??= Timer(showDuration, _controller.reverse);
+    } else {
+      // Tool tips activated by hover should disappear as soon as the mouse
+      // leaves the control.
+      _controller.reverse();
+    }
+    _longPressActivated = false;
+  }
+
+  void _showTooltip({ bool immediately = false }) {
+    _hideTimer?.cancel();
+    _hideTimer = null;
+    if (immediately) {
+      ensureTooltipVisible();
+      return;
+    }
+    _showTimer ??= Timer(waitDuration, ensureTooltipVisible);
+  }
+
+  /// Shows the tooltip if it is not already visible.
+  ///
+  /// Returns `false` when the tooltip was already visible or if the context has
+  /// become null.
+  bool ensureTooltipVisible() {
+    _showTimer?.cancel();
+    _showTimer = null;
+    if (_entry != null) {
+      // Stop trying to hide, if we were.
+      _hideTimer?.cancel();
+      _hideTimer = null;
+      _controller.forward();
+      return false; // Already visible.
+    }
+    _createNewEntry();
+    _controller.forward();
+    return true;
+  }
+
+  void _createNewEntry() {
+    final OverlayState overlayState = Overlay.of(
+      context,
+      debugRequiredFor: widget,
+    )!;
+
+    final RenderBox box = context.findRenderObject()! as RenderBox;
+    final Offset target = box.localToGlobal(
+      box.size.center(Offset.zero),
+      ancestor: overlayState.context.findRenderObject(),
+    );
+
+    // We create this widget outside of the overlay entry's builder to prevent
+    // updated values from happening to leak into the overlay when the overlay
+    // rebuilds.
+    final Widget overlay = Directionality(
+      textDirection: Directionality.of(context),
+      child: _TooltipOverlay(
+        message: widget.message,
+        height: height,
+        padding: padding,
+        margin: margin,
+        decoration: decoration,
+        textStyle: textStyle,
+        animation: CurvedAnimation(
+          parent: _controller,
+          curve: Curves.fastOutSlowIn,
+        ),
+        target: target,
+        verticalOffset: verticalOffset,
+        preferBelow: preferBelow,
+      ),
+    );
+    _entry = OverlayEntry(builder: (BuildContext context) => overlay);
+    overlayState.insert(_entry!);
+    SemanticsService.tooltip(widget.message);
+  }
+
+  void _removeEntry() {
+    _hideTimer?.cancel();
+    _hideTimer = null;
+    _showTimer?.cancel();
+    _showTimer = null;
+    _entry?.remove();
+    _entry = null;
+  }
+
+  void _handlePointerEvent(PointerEvent event) {
+    if (_entry == null) {
+      return;
+    }
+    if (event is PointerUpEvent || event is PointerCancelEvent) {
+      _hideTooltip();
+    } else if (event is PointerDownEvent) {
+      _hideTooltip(immediately: true);
+    }
+  }
+
+  @override
+  void deactivate() {
+    if (_entry != null) {
+      _hideTooltip(immediately: true);
+    }
+    _showTimer?.cancel();
+    super.deactivate();
+  }
+
+  @override
+  void dispose() {
+    GestureBinding.instance!.pointerRouter.removeGlobalRoute(_handlePointerEvent);
+    RendererBinding.instance!.mouseTracker.removeListener(_handleMouseTrackerChange);
+    if (_entry != null)
+      _removeEntry();
+    _controller.dispose();
+    super.dispose();
+  }
+
+  void _handleLongPress() {
+    _longPressActivated = true;
+    final bool tooltipCreated = ensureTooltipVisible();
+    if (tooltipCreated)
+      Feedback.forLongPress(context);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(Overlay.of(context, debugRequiredFor: widget) != null);
+    final ThemeData theme = Theme.of(context);
+    final TooltipThemeData tooltipTheme = TooltipTheme.of(context);
+    final TextStyle defaultTextStyle;
+    final BoxDecoration defaultDecoration;
+    if (theme.brightness == Brightness.dark) {
+      defaultTextStyle = theme.textTheme.bodyText2!.copyWith(
+        color: Colors.black,
+        fontSize: _getDefaultFontSize(),
+      );
+      defaultDecoration = BoxDecoration(
+        color: Colors.white.withOpacity(0.9),
+        borderRadius: const BorderRadius.all(Radius.circular(4)),
+      );
+    } else {
+      defaultTextStyle = theme.textTheme.bodyText2!.copyWith(
+        color: Colors.white,
+        fontSize: _getDefaultFontSize(),
+      );
+      defaultDecoration = BoxDecoration(
+        color: Colors.grey[700]!.withOpacity(0.9),
+        borderRadius: const BorderRadius.all(Radius.circular(4)),
+      );
+    }
+
+    height = widget.height ?? tooltipTheme.height ?? _getDefaultTooltipHeight();
+    padding = widget.padding ?? tooltipTheme.padding ?? _getDefaultPadding();
+    margin = widget.margin ?? tooltipTheme.margin ?? _defaultMargin;
+    verticalOffset = widget.verticalOffset ?? tooltipTheme.verticalOffset ?? _defaultVerticalOffset;
+    preferBelow = widget.preferBelow ?? tooltipTheme.preferBelow ?? _defaultPreferBelow;
+    excludeFromSemantics = widget.excludeFromSemantics ?? tooltipTheme.excludeFromSemantics ?? _defaultExcludeFromSemantics;
+    decoration = widget.decoration ?? tooltipTheme.decoration ?? defaultDecoration;
+    textStyle = widget.textStyle ?? tooltipTheme.textStyle ?? defaultTextStyle;
+    waitDuration = widget.waitDuration ?? tooltipTheme.waitDuration ?? _defaultWaitDuration;
+    showDuration = widget.showDuration ?? tooltipTheme.showDuration ?? _defaultShowDuration;
+
+    Widget result = GestureDetector(
+      behavior: HitTestBehavior.opaque,
+      onLongPress: _handleLongPress,
+      excludeFromSemantics: true,
+      child: Semantics(
+        label: excludeFromSemantics ? null : widget.message,
+        child: widget.child,
+      ),
+    );
+
+    // Only check for hovering if there is a mouse connected.
+    if (_mouseIsConnected) {
+      result = MouseRegion(
+        onEnter: (PointerEnterEvent event) => _showTooltip(),
+        onExit: (PointerExitEvent event) => _hideTooltip(),
+        child: result,
+      );
+    }
+
+    return result;
+  }
+}
+
+/// A delegate for computing the layout of a tooltip to be displayed above or
+/// bellow a target specified in the global coordinate system.
+class _TooltipPositionDelegate extends SingleChildLayoutDelegate {
+  /// Creates a delegate for computing the layout of a tooltip.
+  ///
+  /// The arguments must not be null.
+  _TooltipPositionDelegate({
+    required this.target,
+    required this.verticalOffset,
+    required this.preferBelow,
+  }) : assert(target != null),
+       assert(verticalOffset != null),
+       assert(preferBelow != null);
+
+  /// The offset of the target the tooltip is positioned near in the global
+  /// coordinate system.
+  final Offset target;
+
+  /// The amount of vertical distance between the target and the displayed
+  /// tooltip.
+  final double verticalOffset;
+
+  /// Whether the tooltip is displayed below its widget by default.
+  ///
+  /// If there is insufficient space to display the tooltip in the preferred
+  /// direction, the tooltip will be displayed in the opposite direction.
+  final bool preferBelow;
+
+  @override
+  BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints.loosen();
+
+  @override
+  Offset getPositionForChild(Size size, Size childSize) {
+    return positionDependentBox(
+      size: size,
+      childSize: childSize,
+      target: target,
+      verticalOffset: verticalOffset,
+      preferBelow: preferBelow,
+    );
+  }
+
+  @override
+  bool shouldRelayout(_TooltipPositionDelegate oldDelegate) {
+    return target != oldDelegate.target
+        || verticalOffset != oldDelegate.verticalOffset
+        || preferBelow != oldDelegate.preferBelow;
+  }
+}
+
+class _TooltipOverlay extends StatelessWidget {
+  const _TooltipOverlay({
+    Key? key,
+    required this.message,
+    required this.height,
+    this.padding,
+    this.margin,
+    this.decoration,
+    this.textStyle,
+    required this.animation,
+    required this.target,
+    required this.verticalOffset,
+    required this.preferBelow,
+  }) : super(key: key);
+
+  final String message;
+  final double height;
+  final EdgeInsetsGeometry? padding;
+  final EdgeInsetsGeometry? margin;
+  final Decoration? decoration;
+  final TextStyle? textStyle;
+  final Animation<double> animation;
+  final Offset target;
+  final double verticalOffset;
+  final bool preferBelow;
+
+  @override
+  Widget build(BuildContext context) {
+    return Positioned.fill(
+      child: IgnorePointer(
+        child: CustomSingleChildLayout(
+          delegate: _TooltipPositionDelegate(
+            target: target,
+            verticalOffset: verticalOffset,
+            preferBelow: preferBelow,
+          ),
+          child: FadeTransition(
+            opacity: animation,
+            child: ConstrainedBox(
+              constraints: BoxConstraints(minHeight: height),
+              child: DefaultTextStyle(
+                style: Theme.of(context).textTheme.bodyText2!,
+                child: Container(
+                  decoration: decoration,
+                  padding: padding,
+                  margin: margin,
+                  child: Center(
+                    widthFactor: 1.0,
+                    heightFactor: 1.0,
+                    child: Text(
+                      message,
+                      style: textStyle,
+                    ),
+                  ),
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/material/tooltip_theme.dart b/lib/src/material/tooltip_theme.dart
new file mode 100644
index 0000000..2f09232
--- /dev/null
+++ b/lib/src/material/tooltip_theme.dart
@@ -0,0 +1,252 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'theme.dart';
+
+/// Defines the visual properties of [Tooltip] widgets.
+///
+/// Used by [TooltipTheme] to control the visual properties of tooltips in a
+/// widget subtree.
+///
+/// To obtain this configuration, use [TooltipTheme.of] to access the closest
+/// ancestor [TooltipTheme] of the current [BuildContext].
+///
+/// See also:
+///
+///  * [TooltipTheme], an [InheritedWidget] that propagates the theme down its
+///    subtree.
+///  * [TooltipThemeData], which describes the actual configuration of a
+///    tooltip theme.
+@immutable
+class TooltipThemeData with Diagnosticable {
+  /// Creates the set of properties used to configure [Tooltip]s.
+  const TooltipThemeData({
+    this.height,
+    this.padding,
+    this.margin,
+    this.verticalOffset,
+    this.preferBelow,
+    this.excludeFromSemantics,
+    this.decoration,
+    this.textStyle,
+    this.waitDuration,
+    this.showDuration,
+  });
+
+  /// The height of [Tooltip.child].
+  final double? height;
+
+  /// If provided, the amount of space by which to inset [Tooltip.child].
+  final EdgeInsetsGeometry? padding;
+
+  /// If provided, the amount of empty space to surround the [Tooltip].
+  final EdgeInsetsGeometry? margin;
+
+  /// The vertical gap between the widget and the displayed tooltip.
+  ///
+  /// When [preferBelow] is set to true and tooltips have sufficient space to
+  /// display themselves, this property defines how much vertical space
+  /// tooltips will position themselves under their corresponding widgets.
+  /// Otherwise, tooltips will position themselves above their corresponding
+  /// widgets with the given offset.
+  final double? verticalOffset;
+
+  /// Whether the tooltip is displayed below its widget by default.
+  ///
+  /// If there is insufficient space to display the tooltip in the preferred
+  /// direction, the tooltip will be displayed in the opposite direction.
+  final bool? preferBelow;
+
+  /// Whether the [Tooltip.message] should be excluded from the semantics
+  /// tree.
+  ///
+  /// By default, [Tooltip]s will add a [Semantics] label that is set to
+  /// [Tooltip.message]. Set this property to true if the app is going to
+  /// provide its own custom semantics label.
+  final bool? excludeFromSemantics;
+
+  /// The [Tooltip]'s shape and background color.
+  final Decoration? decoration;
+
+  /// The style to use for the message of [Tooltip]s.
+  final TextStyle? textStyle;
+
+  /// The length of time that a pointer must hover over a tooltip's widget
+  /// before the tooltip will be shown.
+  final Duration? waitDuration;
+
+  /// The length of time that the tooltip will be shown once it has appeared.
+  final Duration? showDuration;
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  TooltipThemeData copyWith({
+    double? height,
+    EdgeInsetsGeometry? padding,
+    EdgeInsetsGeometry? margin,
+    double? verticalOffset,
+    bool? preferBelow,
+    bool? excludeFromSemantics,
+    Decoration? decoration,
+    TextStyle? textStyle,
+    Duration? waitDuration,
+    Duration? showDuration,
+  }) {
+    return TooltipThemeData(
+      height: height ?? this.height,
+      padding: padding ?? this.padding,
+      margin: margin ?? this.margin,
+      verticalOffset: verticalOffset ?? this.verticalOffset,
+      preferBelow: preferBelow ?? this.preferBelow,
+      excludeFromSemantics: excludeFromSemantics ?? this.excludeFromSemantics,
+      decoration: decoration ?? this.decoration,
+      textStyle: textStyle ?? this.textStyle,
+      waitDuration: waitDuration ?? this.waitDuration,
+      showDuration: showDuration ?? this.showDuration,
+    );
+  }
+
+  /// Linearly interpolate between two tooltip themes.
+  ///
+  /// If both arguments are null, then null is returned.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static TooltipThemeData? lerp(TooltipThemeData? a, TooltipThemeData? b, double t) {
+    if (a == null && b == null)
+      return null;
+    assert(t != null);
+    return TooltipThemeData(
+      height: lerpDouble(a?.height, b?.height, t),
+      padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
+      margin: EdgeInsetsGeometry.lerp(a?.margin, b?.margin, t),
+      verticalOffset: lerpDouble(a?.verticalOffset, b?.verticalOffset, t),
+      preferBelow: t < 0.5 ? a?.preferBelow: b?.preferBelow,
+      excludeFromSemantics: t < 0.5 ? a?.excludeFromSemantics : b?.excludeFromSemantics,
+      decoration: Decoration.lerp(a?.decoration, b?.decoration, t),
+      textStyle: TextStyle.lerp(a?.textStyle, b?.textStyle, t),
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      height,
+      padding,
+      margin,
+      verticalOffset,
+      preferBelow,
+      excludeFromSemantics,
+      decoration,
+      textStyle,
+      waitDuration,
+      showDuration,
+    );
+  }
+
+  @override
+  bool operator==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is TooltipThemeData
+        && other.height == height
+        && other.padding == padding
+        && other.margin == margin
+        && other.verticalOffset == verticalOffset
+        && other.preferBelow == preferBelow
+        && other.excludeFromSemantics == excludeFromSemantics
+        && other.decoration == decoration
+        && other.textStyle == textStyle
+        && other.waitDuration == waitDuration
+        && other.showDuration == showDuration;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('height', height, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
+    properties.add(DoubleProperty('vertical offset', verticalOffset, defaultValue: null));
+    properties.add(FlagProperty('position', value: preferBelow, ifTrue: 'below', ifFalse: 'above', showName: true, defaultValue: null));
+    properties.add(FlagProperty('semantics', value: excludeFromSemantics, ifTrue: 'excluded', showName: true, defaultValue: null));
+    properties.add(DiagnosticsProperty<Decoration>('decoration', decoration, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('textStyle', textStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<Duration>('wait duration', waitDuration, defaultValue: null));
+    properties.add(DiagnosticsProperty<Duration>('show duration', showDuration, defaultValue: null));
+  }
+}
+
+/// An inherited widget that defines the configuration for
+/// [Tooltip]s in this widget's subtree.
+///
+/// Values specified here are used for [Tooltip] properties that are not
+/// given an explicit non-null value.
+///
+/// {@tool snippet}
+///
+/// Here is an example of a tooltip theme that applies a blue foreground
+/// with non-rounded corners.
+///
+/// ```dart
+/// TooltipTheme(
+///   data: TooltipThemeData(
+///     decoration: BoxDecoration(
+///       color: Colors.blue.withOpacity(0.9),
+///       borderRadius: BorderRadius.zero,
+///     ),
+///   ),
+///   child: Tooltip(
+///     message: 'Example tooltip',
+///     child: IconButton(
+///       iconSize: 36.0,
+///       icon: Icon(Icons.touch_app),
+///       onPressed: () {},
+///     ),
+///   ),
+/// ),
+/// ```
+/// {@end-tool}
+class TooltipTheme extends InheritedTheme {
+  /// Creates a tooltip theme that controls the configurations for
+  /// [Tooltip].
+  ///
+  /// The data argument must not be null.
+  const TooltipTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null), super(key: key, child: child);
+
+  /// The properties for descendant [Tooltip] widgets.
+  final TooltipThemeData data;
+
+  /// Returns the [data] from the closest [TooltipTheme] ancestor. If there is
+  /// no ancestor, it returns [ThemeData.tooltipTheme]. Applications can assume
+  /// that the returned value will not be null.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// TooltipThemeData theme = TooltipTheme.of(context);
+  /// ```
+  static TooltipThemeData of(BuildContext context) {
+    final TooltipTheme? tooltipTheme = context.dependOnInheritedWidgetOfExactType<TooltipTheme>();
+    return tooltipTheme?.data ?? Theme.of(context).tooltipTheme;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return TooltipTheme(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(TooltipTheme oldWidget) => data != oldWidget.data;
+}
diff --git a/lib/src/material/typography.dart b/lib/src/material/typography.dart
new file mode 100644
index 0000000..ced7acc
--- /dev/null
+++ b/lib/src/material/typography.dart
@@ -0,0 +1,610 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart';
+
+import 'colors.dart';
+import 'text_theme.dart';
+
+/// A characterization of the of a [TextTheme]'s glyphs that is used to define
+/// its localized [TextStyle] geometry for [ThemeData.textTheme].
+///
+/// The script category defines the overall geometry of a [TextTheme] for
+/// the [Typography.geometryThemeFor] method in terms of the
+/// three language categories defined in <https://material.io/go/design-typography>.
+///
+/// Generally speaking, font sizes for [ScriptCategory.tall] and
+/// [ScriptCategory.dense] scripts - for text styles that are smaller than the
+/// title style - are one unit larger than they are for
+/// [ScriptCategory.englishLike] scripts.
+enum ScriptCategory {
+  /// The languages of Western, Central, and Eastern Europe and much of
+  /// Africa are typically written in the Latin alphabet. Vietnamese is a
+  /// notable exception in that, while it uses a localized form of the Latin
+  /// writing system, its accented glyphs can be much taller than those
+  /// found in Western European languages. The Greek and Cyrillic writing
+  /// systems are very similar to Latin.
+  englishLike,
+
+  /// Language scripts that require extra line height to accommodate larger
+  /// glyphs, including Chinese, Japanese, and Korean.
+  dense,
+
+  /// Language scripts that require extra line height to accommodate
+  /// larger glyphs, including South and Southeast Asian and
+  /// Middle-Eastern languages, like Arabic, Hindi, Telugu, Thai, and
+  /// Vietnamese.
+  tall,
+}
+
+/// The color and geometry [TextTheme]s for Material apps.
+///
+/// The text themes provided by the overall [Theme], like
+/// [ThemeData.textTheme], are based on the current locale's
+/// [MaterialLocalizations.scriptCategory] and are created
+/// by merging a color text theme, [black] or [white]
+/// and a geometry text theme, one of [englishLike], [dense],
+/// or [tall], depending on the locale.
+///
+/// To lookup a localized text theme use
+/// `Theme.of(context).textTheme` or
+/// `Theme.of(context).primaryTextTheme` or
+/// `Theme.of(context).accentTextTheme`.
+///
+/// The color text themes are [blackMountainView], [whiteMountainView],
+/// [blackCupertino], and [whiteCupertino]. The Mountain View theme [TextStyle]s
+/// are based on the Roboto fonts as used on Android. The Cupertino themes are
+/// based on the [San Francisco
+/// font](https://developer.apple.com/ios/human-interface-guidelines/visual-design/typography/)
+/// fonts as used by Apple on iOS.
+///
+/// Two sets of geometry themes are provided: 2014 and 2018. The 2014 themes
+/// correspond to the original version of the Material Design spec and are
+/// the defaults. The 2018 themes correspond the second iteration of the
+/// specification and feature different font sizes, font weights, and
+/// letter spacing values.
+///
+/// By default, [ThemeData.typography] is `Typography.material2014(platform:
+/// platform)` which uses [englishLike2014], [dense2014] and [tall2014]. To use
+/// the 2018 text theme geometries, specify a value using the [material2018]
+/// constructor:
+///
+/// ```dart
+/// typography: Typography.material2018(platform: platform)
+/// ```
+///
+/// See also:
+///
+///  * [ThemeData.typography], which can be used to configure the
+///    text themes used to create [ThemeData.textTheme],
+///    [ThemeData.primaryTextTheme], [ThemeData.accentTextTheme].
+///  * <https://material.io/design/typography/>
+@immutable
+class Typography with Diagnosticable {
+  /// Creates a typography instance.
+  ///
+  /// This constructor is identical to [Typography.material2014]. It is
+  /// deprecated because the 2014 material design defaults used by that
+  /// constructor are obsolete. The current material design specification
+  /// recommendations are those reflected by [Typography.material2018].
+  @Deprecated(
+    'The default Typography constructor defaults to the 2014 material design defaults. '
+    'Applications are urged to migrate to Typography.material2018(), or, if the 2014 defaults '
+    'are desired, to explicitly request them using Typography.material2014(). '
+    'This feature was deprecated after v1.13.8.'
+  )
+  factory Typography({
+    TargetPlatform? platform,
+    TextTheme? black,
+    TextTheme? white,
+    TextTheme? englishLike,
+    TextTheme? dense,
+    TextTheme? tall,
+  }) = Typography.material2014;
+
+  /// Creates a typography instance using material design's 2014 defaults.
+  ///
+  /// If [platform] is [TargetPlatform.iOS] or [TargetPlatform.macOS], the
+  /// default values for [black] and [white] are [blackCupertino] and
+  /// [whiteCupertino] respectively. Otherwise they are [blackMountainView] and
+  /// [whiteMountainView]. If [platform] is null then both [black] and [white]
+  /// must be specified.
+  ///
+  /// The default values for [englishLike], [dense], and [tall] are
+  /// [englishLike2014], [dense2014], and [tall2014].
+  factory Typography.material2014({
+    TargetPlatform? platform = TargetPlatform.android,
+    TextTheme? black,
+    TextTheme? white,
+    TextTheme? englishLike,
+    TextTheme? dense,
+    TextTheme? tall,
+  }) {
+    assert(platform != null || (black != null && white != null));
+    return Typography._withPlatform(
+      platform,
+      black, white,
+      englishLike ?? englishLike2014,
+      dense ?? dense2014,
+      tall ?? tall2014,
+    );
+  }
+
+  /// Creates a typography instance using material design's 2018 defaults.
+  ///
+  /// If [platform] is [TargetPlatform.iOS] or [TargetPlatform.macOS], the
+  /// default values for [black] and [white] are [blackCupertino] and
+  /// [whiteCupertino] respectively. Otherwise they are [blackMountainView] and
+  /// [whiteMountainView]. If [platform] is null then both [black] and [white]
+  /// must be specified.
+  ///
+  /// The default values for [englishLike], [dense], and [tall] are
+  /// [englishLike2018], [dense2018], and [tall2018].
+  factory Typography.material2018({
+    TargetPlatform? platform = TargetPlatform.android,
+    TextTheme? black,
+    TextTheme? white,
+    TextTheme? englishLike,
+    TextTheme? dense,
+    TextTheme? tall,
+  }) {
+    assert(platform != null || (black != null && white != null));
+    return Typography._withPlatform(
+      platform,
+      black, white,
+      englishLike ?? englishLike2018,
+      dense ?? dense2018,
+      tall ?? tall2018,
+    );
+  }
+
+  factory Typography._withPlatform(
+    TargetPlatform? platform,
+    TextTheme? black,
+    TextTheme? white,
+    TextTheme englishLike,
+    TextTheme dense,
+    TextTheme tall,
+  ) {
+    assert(platform != null || (black != null && white != null));
+    assert(englishLike != null);
+    assert(dense != null);
+    assert(tall != null);
+    switch (platform) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        black ??= blackCupertino;
+        white ??= whiteCupertino;
+        break;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+        black ??= blackMountainView;
+        white ??= whiteMountainView;
+        break;
+      case TargetPlatform.windows:
+        black ??= blackRedmond;
+        white ??= whiteRedmond;
+        break;
+      case TargetPlatform.linux:
+        black ??= blackHelsinki;
+        white ??= whiteHelsinki;
+        break;
+      case null:
+        break;
+    }
+    return Typography._(black!, white!, englishLike, dense, tall);
+  }
+
+  const Typography._(this.black, this.white, this.englishLike, this.dense, this.tall)
+    : assert(black != null),
+      assert(white != null),
+      assert(englishLike != null),
+      assert(dense != null),
+      assert(tall != null);
+
+  /// A material design text theme with dark glyphs.
+  ///
+  /// This [TextTheme] should provide color but not geometry (font size,
+  /// weight, etc). A text theme's geometry depends on the locale. To look
+  /// up a localized [TextTheme], use the overall [Theme], for example:
+  /// `Theme.of(context).textTheme`.
+  ///
+  /// The [englishLike], [dense], and [tall] text theme's provide locale-specific
+  /// geometry.
+  final TextTheme black;
+
+  /// A material design text theme with light glyphs.
+  ///
+  /// This [TextTheme] provides color but not geometry (font size, weight, etc).
+  /// A text theme's geometry depends on the locale. To look up a localized
+  /// [TextTheme], use the overall [Theme], for example:
+  /// `Theme.of(context).textTheme`.
+  ///
+  /// The [englishLike], [dense], and [tall] text theme's provide locale-specific
+  /// geometry.
+  final TextTheme white;
+
+  /// Defines text geometry for [ScriptCategory.englishLike] scripts, such as
+  /// English, French, Russian, etc.
+  ///
+  /// This text theme is merged with either [black] or [white], depending
+  /// on the overall [ThemeData.brightness], when the current locale's
+  /// [MaterialLocalizations.scriptCategory] is [ScriptCategory.englishLike].
+  ///
+  /// To look up a localized [TextTheme], use the overall [Theme], for
+  /// example: `Theme.of(context).textTheme`.
+  final TextTheme englishLike;
+
+  /// Defines text geometry for dense scripts, such as Chinese, Japanese
+  /// and Korean.
+  ///
+  /// This text theme is merged with either [black] or [white], depending
+  /// on the overall [ThemeData.brightness], when the current locale's
+  /// [MaterialLocalizations.scriptCategory] is [ScriptCategory.dense].
+  ///
+  /// To look up a localized [TextTheme], use the overall [Theme], for
+  /// example: `Theme.of(context).textTheme`.
+  final TextTheme dense;
+
+  /// Defines text geometry for tall scripts, such as Farsi, Hindi, and Thai.
+  ///
+  /// This text theme is merged with either [black] or [white], depending
+  /// on the overall [ThemeData.brightness], when the current locale's
+  /// [MaterialLocalizations.scriptCategory] is [ScriptCategory.tall].
+  ///
+  /// To look up a localized [TextTheme], use the overall [Theme], for
+  /// example: `Theme.of(context).textTheme`.
+  final TextTheme tall;
+
+  /// Returns one of [englishLike], [dense], or [tall].
+  TextTheme geometryThemeFor(ScriptCategory category) {
+    assert(category != null);
+    switch (category) {
+      case ScriptCategory.englishLike:
+        return englishLike;
+      case ScriptCategory.dense:
+        return dense;
+      case ScriptCategory.tall:
+        return tall;
+    }
+  }
+
+  /// Creates a copy of this [Typography] with the given fields
+  /// replaced by the non-null parameter values.
+  Typography copyWith({
+    TextTheme? black,
+    TextTheme? white,
+    TextTheme? englishLike,
+    TextTheme? dense,
+    TextTheme? tall,
+  }) {
+    return Typography._(
+      black ?? this.black,
+      white ?? this.white,
+      englishLike ?? this.englishLike,
+      dense ?? this.dense,
+      tall ?? this.tall,
+    );
+  }
+
+  /// Linearly interpolate between two [Typography] objects.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static Typography lerp(Typography a, Typography b, double t) {
+    return Typography._(
+      TextTheme.lerp(a.black, b.black, t),
+      TextTheme.lerp(a.white, b.white, t),
+      TextTheme.lerp(a.englishLike, b.englishLike, t),
+      TextTheme.lerp(a.dense, b.dense, t),
+      TextTheme.lerp(a.tall, b.tall, t),
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is Typography
+        && other.black == black
+        && other.white == white
+        && other.englishLike == englishLike
+        && other.dense == dense
+        && other.tall == tall;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      black,
+      white,
+      englishLike,
+      dense,
+      tall,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    final Typography defaultTypography = Typography.material2014();
+    properties.add(DiagnosticsProperty<TextTheme>('black', black, defaultValue: defaultTypography.black));
+    properties.add(DiagnosticsProperty<TextTheme>('white', white, defaultValue: defaultTypography.white));
+    properties.add(DiagnosticsProperty<TextTheme>('englishLike', englishLike, defaultValue: defaultTypography.englishLike));
+    properties.add(DiagnosticsProperty<TextTheme>('dense', dense, defaultValue: defaultTypography.dense));
+    properties.add(DiagnosticsProperty<TextTheme>('tall', tall, defaultValue: defaultTypography.tall));
+  }
+
+  /// A material design text theme with dark glyphs based on Roboto.
+  ///
+  /// This [TextTheme] provides color but not geometry (font size, weight, etc).
+  static const TextTheme blackMountainView = TextTheme(
+    headline1 : TextStyle(debugLabel: 'blackMountainView headline1', fontFamily: 'Roboto', inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline2 : TextStyle(debugLabel: 'blackMountainView headline2', fontFamily: 'Roboto', inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline3 : TextStyle(debugLabel: 'blackMountainView headline3', fontFamily: 'Roboto', inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline4 : TextStyle(debugLabel: 'blackMountainView headline4', fontFamily: 'Roboto', inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline5 : TextStyle(debugLabel: 'blackMountainView headline5', fontFamily: 'Roboto', inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    headline6 : TextStyle(debugLabel: 'blackMountainView headline6', fontFamily: 'Roboto', inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    bodyText1 : TextStyle(debugLabel: 'blackMountainView bodyText1', fontFamily: 'Roboto', inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    bodyText2 : TextStyle(debugLabel: 'blackMountainView bodyText2', fontFamily: 'Roboto', inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    subtitle1 : TextStyle(debugLabel: 'blackMountainView subtitle1', fontFamily: 'Roboto', inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    subtitle2 : TextStyle(debugLabel: 'blackMountainView subtitle2', fontFamily: 'Roboto', inherit: true, color: Colors.black,   decoration: TextDecoration.none),
+    caption   : TextStyle(debugLabel: 'blackMountainView caption',   fontFamily: 'Roboto', inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    button    : TextStyle(debugLabel: 'blackMountainView button',    fontFamily: 'Roboto', inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    overline  : TextStyle(debugLabel: 'blackMountainView overline',  fontFamily: 'Roboto', inherit: true, color: Colors.black,   decoration: TextDecoration.none),
+  );
+
+  /// A material design text theme with light glyphs based on Roboto.
+  ///
+  /// This [TextTheme] provides color but not geometry (font size, weight, etc).
+  static const TextTheme whiteMountainView = TextTheme(
+    headline1 : TextStyle(debugLabel: 'whiteMountainView headline1', fontFamily: 'Roboto', inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline2 : TextStyle(debugLabel: 'whiteMountainView headline2', fontFamily: 'Roboto', inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline3 : TextStyle(debugLabel: 'whiteMountainView headline3', fontFamily: 'Roboto', inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline4 : TextStyle(debugLabel: 'whiteMountainView headline4', fontFamily: 'Roboto', inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline5 : TextStyle(debugLabel: 'whiteMountainView headline5', fontFamily: 'Roboto', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    headline6 : TextStyle(debugLabel: 'whiteMountainView headline6', fontFamily: 'Roboto', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    bodyText1 : TextStyle(debugLabel: 'whiteMountainView bodyText1', fontFamily: 'Roboto', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    bodyText2 : TextStyle(debugLabel: 'whiteMountainView bodyText2', fontFamily: 'Roboto', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    subtitle1 : TextStyle(debugLabel: 'whiteMountainView subtitle1', fontFamily: 'Roboto', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    subtitle2 : TextStyle(debugLabel: 'whiteMountainView subtitle2', fontFamily: 'Roboto', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    caption   : TextStyle(debugLabel: 'whiteMountainView caption',   fontFamily: 'Roboto', inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    button    : TextStyle(debugLabel: 'whiteMountainView button',    fontFamily: 'Roboto', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    overline  : TextStyle(debugLabel: 'whiteMountainView overline',  fontFamily: 'Roboto', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+  );
+
+  /// A material design text theme with dark glyphs based on Segoe UI.
+  ///
+  /// This [TextTheme] provides color but not geometry (font size, weight, etc).
+  static const TextTheme blackRedmond = TextTheme(
+    headline1 : TextStyle(debugLabel: 'blackRedmond headline1', fontFamily: 'Segoe UI', inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline2 : TextStyle(debugLabel: 'blackRedmond headline2', fontFamily: 'Segoe UI', inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline3 : TextStyle(debugLabel: 'blackRedmond headline3', fontFamily: 'Segoe UI', inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline4 : TextStyle(debugLabel: 'blackRedmond headline4', fontFamily: 'Segoe UI', inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline5 : TextStyle(debugLabel: 'blackRedmond headline5', fontFamily: 'Segoe UI', inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    headline6 : TextStyle(debugLabel: 'blackRedmond headline6', fontFamily: 'Segoe UI', inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    bodyText1 : TextStyle(debugLabel: 'blackRedmond bodyText1', fontFamily: 'Segoe UI', inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    bodyText2 : TextStyle(debugLabel: 'blackRedmond bodyText2', fontFamily: 'Segoe UI', inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    subtitle1 : TextStyle(debugLabel: 'blackRedmond subtitle1', fontFamily: 'Segoe UI', inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    subtitle2 : TextStyle(debugLabel: 'blackRedmond subtitle2', fontFamily: 'Segoe UI', inherit: true, color: Colors.black,   decoration: TextDecoration.none),
+    caption   : TextStyle(debugLabel: 'blackRedmond caption',   fontFamily: 'Segoe UI', inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    button    : TextStyle(debugLabel: 'blackRedmond button',    fontFamily: 'Segoe UI', inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    overline  : TextStyle(debugLabel: 'blackRedmond overline',  fontFamily: 'Segoe UI', inherit: true, color: Colors.black,   decoration: TextDecoration.none),
+  );
+
+  /// A material design text theme with light glyphs based on Segoe UI.
+  ///
+  /// This [TextTheme] provides color but not geometry (font size, weight, etc).
+  static const TextTheme whiteRedmond = TextTheme(
+    headline1 : TextStyle(debugLabel: 'whiteRedmond headline1', fontFamily: 'Segoe UI', inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline2 : TextStyle(debugLabel: 'whiteRedmond headline2', fontFamily: 'Segoe UI', inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline3 : TextStyle(debugLabel: 'whiteRedmond headline3', fontFamily: 'Segoe UI', inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline4 : TextStyle(debugLabel: 'whiteRedmond headline4', fontFamily: 'Segoe UI', inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline5 : TextStyle(debugLabel: 'whiteRedmond headline5', fontFamily: 'Segoe UI', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    headline6 : TextStyle(debugLabel: 'whiteRedmond headline6', fontFamily: 'Segoe UI', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    bodyText1 : TextStyle(debugLabel: 'whiteRedmond bodyText1', fontFamily: 'Segoe UI', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    bodyText2 : TextStyle(debugLabel: 'whiteRedmond bodyText2', fontFamily: 'Segoe UI', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    subtitle1 : TextStyle(debugLabel: 'whiteRedmond subtitle1', fontFamily: 'Segoe UI', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    subtitle2 : TextStyle(debugLabel: 'whiteRedmond subtitle2', fontFamily: 'Segoe UI', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    caption   : TextStyle(debugLabel: 'whiteRedmond caption',   fontFamily: 'Segoe UI', inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    button    : TextStyle(debugLabel: 'whiteRedmond button',    fontFamily: 'Segoe UI', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    overline  : TextStyle(debugLabel: 'whiteRedmond overline',  fontFamily: 'Segoe UI', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+  );
+
+  static const List<String> _helsinkiFontFallbacks = <String>['Ubuntu', 'Cantarell', 'DejaVu Sans', 'Liberation Sans', 'Arial'];
+  /// A material design text theme with dark glyphs based on Roboto, with
+  /// fallback fonts that are likely (but not guaranteed) to be installed on
+  /// Linux.
+  ///
+  /// This [TextTheme] provides color but not geometry (font size, weight, etc).
+  static const TextTheme blackHelsinki = TextTheme(
+    headline1 : TextStyle(debugLabel: 'blackHelsinki headline1', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline2 : TextStyle(debugLabel: 'blackHelsinki headline2', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline3 : TextStyle(debugLabel: 'blackHelsinki headline3', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline4 : TextStyle(debugLabel: 'blackHelsinki headline4', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline5 : TextStyle(debugLabel: 'blackHelsinki headline5', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    headline6 : TextStyle(debugLabel: 'blackHelsinki headline6', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    bodyText1 : TextStyle(debugLabel: 'blackHelsinki bodyText1', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    bodyText2 : TextStyle(debugLabel: 'blackHelsinki bodyText2', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    subtitle1 : TextStyle(debugLabel: 'blackHelsinki subtitle1', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    subtitle2 : TextStyle(debugLabel: 'blackHelsinki subtitle2', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.black,   decoration: TextDecoration.none),
+    caption   : TextStyle(debugLabel: 'blackHelsinki caption',   fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    button    : TextStyle(debugLabel: 'blackHelsinki button',    fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    overline  : TextStyle(debugLabel: 'blackHelsinki overline',  fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.black,   decoration: TextDecoration.none),
+  );
+
+  /// A material design text theme with light glyphs based on Roboto, with fallbacks of DejaVu Sans, Liberation Sans and Arial.
+  ///
+  /// This [TextTheme] provides color but not geometry (font size, weight, etc).
+  static const TextTheme whiteHelsinki = TextTheme(
+    headline1 : TextStyle(debugLabel: 'whiteHelsinki headline1', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline2 : TextStyle(debugLabel: 'whiteHelsinki headline2', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline3 : TextStyle(debugLabel: 'whiteHelsinki headline3', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline4 : TextStyle(debugLabel: 'whiteHelsinki headline4', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline5 : TextStyle(debugLabel: 'whiteHelsinki headline5', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    headline6 : TextStyle(debugLabel: 'whiteHelsinki headline6', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    bodyText1 : TextStyle(debugLabel: 'whiteHelsinki bodyText1', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    bodyText2 : TextStyle(debugLabel: 'whiteHelsinki bodyText2', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    subtitle1 : TextStyle(debugLabel: 'whiteHelsinki subtitle1', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    subtitle2 : TextStyle(debugLabel: 'whiteHelsinki subtitle2', fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    caption   : TextStyle(debugLabel: 'whiteHelsinki caption',   fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    button    : TextStyle(debugLabel: 'whiteHelsinki button',    fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    overline  : TextStyle(debugLabel: 'whiteHelsinki overline',  fontFamily: 'Roboto', fontFamilyFallback: _helsinkiFontFallbacks, inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+  );
+
+  /// A material design text theme with dark glyphs based on San Francisco.
+  ///
+  /// This [TextTheme] provides color but not geometry (font size, weight, etc).
+  static const TextTheme blackCupertino = TextTheme(
+    headline1 : TextStyle(debugLabel: 'blackCupertino headline1', fontFamily: '.SF UI Display', inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline2 : TextStyle(debugLabel: 'blackCupertino headline2', fontFamily: '.SF UI Display', inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline3 : TextStyle(debugLabel: 'blackCupertino headline3', fontFamily: '.SF UI Display', inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline4 : TextStyle(debugLabel: 'blackCupertino headline4', fontFamily: '.SF UI Display', inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    headline5 : TextStyle(debugLabel: 'blackCupertino headline5', fontFamily: '.SF UI Display', inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    headline6 : TextStyle(debugLabel: 'blackCupertino headline6', fontFamily: '.SF UI Display', inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    bodyText1 : TextStyle(debugLabel: 'blackCupertino bodyText1', fontFamily: '.SF UI Text',    inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    bodyText2 : TextStyle(debugLabel: 'blackCupertino bodyText2', fontFamily: '.SF UI Text',    inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    subtitle1 : TextStyle(debugLabel: 'blackCupertino subtitle1', fontFamily: '.SF UI Text',    inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    subtitle2 : TextStyle(debugLabel: 'blackCupertino subtitle2', fontFamily: '.SF UI Text',    inherit: true, color: Colors.black,   decoration: TextDecoration.none),
+    caption   : TextStyle(debugLabel: 'blackCupertino caption',   fontFamily: '.SF UI Text',    inherit: true, color: Colors.black54, decoration: TextDecoration.none),
+    button    : TextStyle(debugLabel: 'blackCupertino button',    fontFamily: '.SF UI Text',    inherit: true, color: Colors.black87, decoration: TextDecoration.none),
+    overline  : TextStyle(debugLabel: 'blackCupertino overline',  fontFamily: '.SF UI Text',    inherit: true, color: Colors.black,   decoration: TextDecoration.none),
+  );
+
+  /// A material design text theme with light glyphs based on San Francisco.
+  ///
+  /// This [TextTheme] provides color but not geometry (font size, weight, etc).
+  static const TextTheme whiteCupertino = TextTheme(
+    headline1 : TextStyle(debugLabel: 'whiteCupertino headline1', fontFamily: '.SF UI Display', inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline2 : TextStyle(debugLabel: 'whiteCupertino headline2', fontFamily: '.SF UI Display', inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline3 : TextStyle(debugLabel: 'whiteCupertino headline3', fontFamily: '.SF UI Display', inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline4 : TextStyle(debugLabel: 'whiteCupertino headline4', fontFamily: '.SF UI Display', inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    headline5 : TextStyle(debugLabel: 'whiteCupertino headline5', fontFamily: '.SF UI Display', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    headline6 : TextStyle(debugLabel: 'whiteCupertino headline6', fontFamily: '.SF UI Display', inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    subtitle1 : TextStyle(debugLabel: 'whiteCupertino subtitle1', fontFamily: '.SF UI Text',    inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    bodyText1 : TextStyle(debugLabel: 'whiteCupertino bodyText1', fontFamily: '.SF UI Text',    inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    bodyText2 : TextStyle(debugLabel: 'whiteCupertino bodyText2', fontFamily: '.SF UI Text',    inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    caption   : TextStyle(debugLabel: 'whiteCupertino caption',   fontFamily: '.SF UI Text',    inherit: true, color: Colors.white70, decoration: TextDecoration.none),
+    button    : TextStyle(debugLabel: 'whiteCupertino button',    fontFamily: '.SF UI Text',    inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    subtitle2 : TextStyle(debugLabel: 'whiteCupertino subtitle2', fontFamily: '.SF UI Text',    inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+    overline  : TextStyle(debugLabel: 'whiteCupertino overline',  fontFamily: '.SF UI Text',    inherit: true, color: Colors.white,   decoration: TextDecoration.none),
+  );
+
+  /// Defines text geometry for [ScriptCategory.englishLike] scripts, such as
+  /// English, French, Russian, etc.
+  static const TextTheme englishLike2014 = TextTheme(
+    headline1 : TextStyle(debugLabel: 'englishLike display4 2014', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, textBaseline: TextBaseline.alphabetic),
+    headline2 : TextStyle(debugLabel: 'englishLike display3 2014', inherit: false, fontSize:  56.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    headline3 : TextStyle(debugLabel: 'englishLike display2 2014', inherit: false, fontSize:  45.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    headline4 : TextStyle(debugLabel: 'englishLike display1 2014', inherit: false, fontSize:  34.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    headline5 : TextStyle(debugLabel: 'englishLike headline 2014', inherit: false, fontSize:  24.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    headline6 : TextStyle(debugLabel: 'englishLike title 2014',    inherit: false, fontSize:  20.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.alphabetic),
+    bodyText1 : TextStyle(debugLabel: 'englishLike body2 2014',    inherit: false, fontSize:  14.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.alphabetic),
+    bodyText2 : TextStyle(debugLabel: 'englishLike body1 2014',    inherit: false, fontSize:  14.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    subtitle1 : TextStyle(debugLabel: 'englishLike subhead 2014',  inherit: false, fontSize:  16.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    subtitle2 : TextStyle(debugLabel: 'englishLike subtitle 2014', inherit: false, fontSize:  14.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.alphabetic, letterSpacing: 0.1),
+    caption   : TextStyle(debugLabel: 'englishLike caption 2014',  inherit: false, fontSize:  12.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    button    : TextStyle(debugLabel: 'englishLike button 2014',   inherit: false, fontSize:  14.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.alphabetic),
+    overline  : TextStyle(debugLabel: 'englishLike overline 2014', inherit: false, fontSize:  10.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic, letterSpacing: 1.5),
+  );
+
+  /// Defines text geometry for [ScriptCategory.englishLike] scripts, such as
+  /// English, French, Russian, etc.
+  ///
+  /// The font sizes, weights, and letter spacings in this version match the
+  /// [latest Material Design specification](https://material.io/go/design-typography#typography-styles).
+  static const TextTheme englishLike2018 = TextTheme(
+    headline1 : TextStyle(debugLabel: 'englishLike headline1 2018', fontSize: 96.0, fontWeight: FontWeight.w300, textBaseline: TextBaseline.alphabetic, letterSpacing: -1.5),
+    headline2 : TextStyle(debugLabel: 'englishLike headline2 2018', fontSize: 60.0, fontWeight: FontWeight.w300, textBaseline: TextBaseline.alphabetic, letterSpacing: -0.5),
+    headline3 : TextStyle(debugLabel: 'englishLike headline3 2018', fontSize: 48.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic, letterSpacing: 0.0),
+    headline4 : TextStyle(debugLabel: 'englishLike headline4 2018', fontSize: 34.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic, letterSpacing: 0.25),
+    headline5 : TextStyle(debugLabel: 'englishLike headline5 2018', fontSize: 24.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic, letterSpacing: 0.0),
+    headline6 : TextStyle(debugLabel: 'englishLike headline6 2018', fontSize: 20.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.alphabetic, letterSpacing: 0.15),
+    bodyText1 : TextStyle(debugLabel: 'englishLike bodyText1 2018', fontSize: 16.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic, letterSpacing: 0.5),
+    bodyText2 : TextStyle(debugLabel: 'englishLike bodyText2 2018', fontSize: 14.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic, letterSpacing: 0.25),
+    subtitle1 : TextStyle(debugLabel: 'englishLike subtitle1 2018', fontSize: 16.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic, letterSpacing: 0.15),
+    subtitle2 : TextStyle(debugLabel: 'englishLike subtitle2 2018', fontSize: 14.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.alphabetic, letterSpacing: 0.1),
+    button    : TextStyle(debugLabel: 'englishLike button 2018',    fontSize: 14.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.alphabetic, letterSpacing: 1.25),
+    caption   : TextStyle(debugLabel: 'englishLike caption 2018',   fontSize: 12.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic, letterSpacing: 0.4),
+    overline  : TextStyle(debugLabel: 'englishLike overline 2018',  fontSize: 10.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic, letterSpacing: 1.5),
+  );
+
+  /// Defines text geometry for dense scripts, such as Chinese, Japanese
+  /// and Korean.
+  static const TextTheme dense2014 = TextTheme(
+    headline1 : TextStyle(debugLabel: 'dense display4 2014', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, textBaseline: TextBaseline.ideographic),
+    headline2 : TextStyle(debugLabel: 'dense display3 2014', inherit: false, fontSize:  56.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+    headline3 : TextStyle(debugLabel: 'dense display2 2014', inherit: false, fontSize:  45.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+    headline4 : TextStyle(debugLabel: 'dense display1 2014', inherit: false, fontSize:  34.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+    headline5 : TextStyle(debugLabel: 'dense headline 2014', inherit: false, fontSize:  24.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+    headline6 : TextStyle(debugLabel: 'dense title 2014',    inherit: false, fontSize:  21.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.ideographic),
+    bodyText1 : TextStyle(debugLabel: 'dense body2 2014',    inherit: false, fontSize:  15.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.ideographic),
+    bodyText2 : TextStyle(debugLabel: 'dense body1 2014',    inherit: false, fontSize:  15.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+    subtitle1 : TextStyle(debugLabel: 'dense subhead 2014',  inherit: false, fontSize:  17.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+    subtitle2 : TextStyle(debugLabel: 'dense subtitle 2014', inherit: false, fontSize:  15.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.ideographic),
+    caption   : TextStyle(debugLabel: 'dense caption 2014',  inherit: false, fontSize:  13.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+    button    : TextStyle(debugLabel: 'dense button 2014',   inherit: false, fontSize:  15.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.ideographic),
+    overline  : TextStyle(debugLabel: 'dense overline 2014', inherit: false, fontSize:  11.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+  );
+
+  /// Defines text geometry for dense scripts, such as Chinese, Japanese
+  /// and Korean.
+  ///
+  /// The font sizes, weights, and letter spacings in this version match the
+  /// latest [Material Design specification](https://material.io/go/design-typography#typography-styles).
+  static const TextTheme dense2018 = TextTheme(
+    headline1 : TextStyle(debugLabel: 'dense headline1 2018', fontSize: 96.0, fontWeight: FontWeight.w100, textBaseline: TextBaseline.ideographic),
+    headline2 : TextStyle(debugLabel: 'dense headline2 2018', fontSize: 60.0, fontWeight: FontWeight.w100, textBaseline: TextBaseline.ideographic),
+    headline3 : TextStyle(debugLabel: 'dense headline3 2018', fontSize: 48.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+    headline4 : TextStyle(debugLabel: 'dense headline4 2018', fontSize: 34.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+    headline5 : TextStyle(debugLabel: 'dense headline5 2018', fontSize: 24.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+    headline6 : TextStyle(debugLabel: 'dense headline6 2018', fontSize: 21.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.ideographic),
+    bodyText1 : TextStyle(debugLabel: 'dense bodyText1 2018', fontSize: 17.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+    bodyText2 : TextStyle(debugLabel: 'dense bodyText2 2018', fontSize: 15.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+    subtitle1 : TextStyle(debugLabel: 'dense subtitle1 2018', fontSize: 17.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+    subtitle2 : TextStyle(debugLabel: 'dense subtitle2 2018', fontSize: 15.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.ideographic),
+    button    : TextStyle(debugLabel: 'dense button 2018',    fontSize: 15.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.ideographic),
+    caption   : TextStyle(debugLabel: 'dense caption 2018',   fontSize: 13.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+    overline  : TextStyle(debugLabel: 'dense overline 2018',  fontSize: 11.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.ideographic),
+  );
+
+  /// Defines text geometry for tall scripts, such as Farsi, Hindi, and Thai.
+  static const TextTheme tall2014 = TextTheme(
+    headline1 : TextStyle(debugLabel: 'tall display4 2014', inherit: false, fontSize: 112.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    headline2 : TextStyle(debugLabel: 'tall display3 2014', inherit: false, fontSize:  56.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    headline3 : TextStyle(debugLabel: 'tall display2 2014', inherit: false, fontSize:  45.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    headline4 : TextStyle(debugLabel: 'tall display1 2014', inherit: false, fontSize:  34.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    headline5 : TextStyle(debugLabel: 'tall headline 2014', inherit: false, fontSize:  24.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    headline6 : TextStyle(debugLabel: 'tall title 2014',    inherit: false, fontSize:  21.0, fontWeight: FontWeight.w700, textBaseline: TextBaseline.alphabetic),
+    bodyText1 : TextStyle(debugLabel: 'tall body2 2014',    inherit: false, fontSize:  15.0, fontWeight: FontWeight.w700, textBaseline: TextBaseline.alphabetic),
+    bodyText2 : TextStyle(debugLabel: 'tall body1 2014',    inherit: false, fontSize:  15.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    subtitle1 : TextStyle(debugLabel: 'tall subhead 2014',  inherit: false, fontSize:  17.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    subtitle2 : TextStyle(debugLabel: 'tall subtitle 2014', inherit: false, fontSize:  15.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.alphabetic),
+    caption   : TextStyle(debugLabel: 'tall caption 2014',  inherit: false, fontSize:  13.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    button    : TextStyle(debugLabel: 'tall button 2014',   inherit: false, fontSize:  15.0, fontWeight: FontWeight.w700, textBaseline: TextBaseline.alphabetic),
+    overline  : TextStyle(debugLabel: 'tall overline 2014', inherit: false, fontSize:  11.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+  );
+
+  /// Defines text geometry for tall scripts, such as Farsi, Hindi, and Thai.
+  ///
+  /// The font sizes, weights, and letter spacings in this version match the
+  /// latest [Material Design specification](https://material.io/go/design-typography#typography-styles).
+  static const TextTheme tall2018 = TextTheme(
+    headline1 : TextStyle(debugLabel: 'tall headline1 2018', fontSize: 96.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    headline2 : TextStyle(debugLabel: 'tall headline2 2018', fontSize: 60.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    headline3 : TextStyle(debugLabel: 'tall headline3 2018', fontSize: 48.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    headline4 : TextStyle(debugLabel: 'tall headline4 2018', fontSize: 34.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    headline5 : TextStyle(debugLabel: 'tall headline5 2018', fontSize: 24.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    headline6 : TextStyle(debugLabel: 'tall headline6 2018', fontSize: 21.0, fontWeight: FontWeight.w700, textBaseline: TextBaseline.alphabetic),
+    bodyText1 : TextStyle(debugLabel: 'tall bodyText1 2018', fontSize: 17.0, fontWeight: FontWeight.w700, textBaseline: TextBaseline.alphabetic),
+    bodyText2 : TextStyle(debugLabel: 'tall bodyText2 2018', fontSize: 15.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    subtitle1 : TextStyle(debugLabel: 'tall subtitle1 2018', fontSize: 17.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    subtitle2 : TextStyle(debugLabel: 'tall subtitle2 2018', fontSize: 15.0, fontWeight: FontWeight.w500, textBaseline: TextBaseline.alphabetic),
+    button    : TextStyle(debugLabel: 'tall button 2018',    fontSize: 15.0, fontWeight: FontWeight.w700, textBaseline: TextBaseline.alphabetic),
+    caption   : TextStyle(debugLabel: 'tall caption 2018',   fontSize: 13.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+    overline  : TextStyle(debugLabel: 'tall overline 2018',  fontSize: 11.0, fontWeight: FontWeight.w400, textBaseline: TextBaseline.alphabetic),
+  );
+}
diff --git a/lib/src/material/user_accounts_drawer_header.dart b/lib/src/material/user_accounts_drawer_header.dart
new file mode 100644
index 0000000..693bf33
--- /dev/null
+++ b/lib/src/material/user_accounts_drawer_header.dart
@@ -0,0 +1,393 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/widgets.dart';
+import 'package:flute/foundation.dart';
+
+import 'colors.dart';
+import 'debug.dart';
+import 'drawer_header.dart';
+import 'icons.dart';
+import 'ink_well.dart';
+import 'material_localizations.dart';
+import 'theme.dart';
+
+class _AccountPictures extends StatelessWidget {
+  const _AccountPictures({
+    Key? key,
+    this.currentAccountPicture,
+    this.otherAccountsPictures,
+  }) : super(key: key);
+
+  final Widget? currentAccountPicture;
+  final List<Widget>? otherAccountsPictures;
+
+  @override
+  Widget build(BuildContext context) {
+    return Stack(
+      children: <Widget>[
+        PositionedDirectional(
+          top: 0.0,
+          end: 0.0,
+          child: Row(
+            children: (otherAccountsPictures ?? <Widget>[]).take(3).map<Widget>((Widget picture) {
+              return Padding(
+                padding: const EdgeInsetsDirectional.only(start: 8.0),
+                child: Semantics(
+                  container: true,
+                  child: Container(
+                  padding: const EdgeInsets.only(left: 8.0, bottom: 8.0),
+                    width: 48.0,
+                    height: 48.0,
+                    child: picture,
+                 ),
+                ),
+              );
+            }).toList(),
+          ),
+        ),
+        Positioned(
+          top: 0.0,
+          child: Semantics(
+            explicitChildNodes: true,
+            child: SizedBox(
+              width: 72.0,
+              height: 72.0,
+              child: currentAccountPicture,
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+}
+
+class _AccountDetails extends StatefulWidget {
+  const _AccountDetails({
+    Key? key,
+    required this.accountName,
+    required this.accountEmail,
+    this.onTap,
+    required this.isOpen,
+    this.arrowColor,
+  }) : super(key: key);
+
+  final Widget? accountName;
+  final Widget? accountEmail;
+  final VoidCallback? onTap;
+  final bool isOpen;
+  final Color? arrowColor;
+
+  @override
+  _AccountDetailsState createState() => _AccountDetailsState();
+}
+
+class _AccountDetailsState extends State<_AccountDetails> with SingleTickerProviderStateMixin {
+  late Animation<double> _animation;
+  late AnimationController _controller;
+  @override
+  void initState () {
+    super.initState();
+    _controller = AnimationController(
+      value: widget.isOpen ? 1.0 : 0.0,
+      duration: const Duration(milliseconds: 200),
+      vsync: this,
+    );
+    _animation = CurvedAnimation(
+      parent: _controller,
+      curve: Curves.fastOutSlowIn,
+      reverseCurve: Curves.fastOutSlowIn.flipped,
+    )
+      ..addListener(() => setState(() {
+        // [animation]'s value has changed here.
+      }));
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget (_AccountDetails oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    // If the state of the arrow did not change, there is no need to trigger the animation
+    if (oldWidget.isOpen == widget.isOpen) {
+      return;
+    }
+
+    if (widget.isOpen) {
+      _controller.forward();
+    } else {
+      _controller.reverse();
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasDirectionality(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+
+    final ThemeData theme = Theme.of(context);
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+
+    Widget accountDetails = CustomMultiChildLayout(
+      delegate: _AccountDetailsLayout(
+        textDirection: Directionality.of(context),
+      ),
+      children: <Widget>[
+        if (widget.accountName != null)
+          LayoutId(
+            id: _AccountDetailsLayout.accountName,
+            child: Padding(
+              padding: const EdgeInsets.symmetric(vertical: 2.0),
+              child: DefaultTextStyle(
+                style: theme.primaryTextTheme.bodyText1!,
+                overflow: TextOverflow.ellipsis,
+                child: widget.accountName!,
+              ),
+            ),
+          ),
+        if (widget.accountEmail != null)
+          LayoutId(
+            id: _AccountDetailsLayout.accountEmail,
+            child: Padding(
+              padding: const EdgeInsets.symmetric(vertical: 2.0),
+              child: DefaultTextStyle(
+                style: theme.primaryTextTheme.bodyText2!,
+                overflow: TextOverflow.ellipsis,
+                child: widget.accountEmail!,
+              ),
+            ),
+          ),
+        if (widget.onTap != null)
+          LayoutId(
+            id: _AccountDetailsLayout.dropdownIcon,
+            child: Semantics(
+              container: true,
+              button: true,
+              onTap: widget.onTap,
+              child: SizedBox(
+                height: _kAccountDetailsHeight,
+                width: _kAccountDetailsHeight,
+                child: Center(
+                  child: Transform.rotate(
+                    angle: _animation.value * math.pi,
+                    child: Icon(
+                      Icons.arrow_drop_down,
+                      color: widget.arrowColor,
+                      semanticLabel: widget.isOpen
+                        ? localizations.hideAccountsLabel
+                        : localizations.showAccountsLabel,
+                    ),
+                  ),
+                ),
+              ),
+            ),
+          ),
+      ],
+    );
+
+    if (widget.onTap != null) {
+      accountDetails = InkWell(
+        onTap: widget.onTap,
+        child: accountDetails,
+        excludeFromSemantics: true,
+      );
+    }
+
+    return SizedBox(
+      height: _kAccountDetailsHeight,
+      child: accountDetails,
+    );
+  }
+}
+
+const double _kAccountDetailsHeight = 56.0;
+
+class _AccountDetailsLayout extends MultiChildLayoutDelegate {
+
+  _AccountDetailsLayout({ required this.textDirection });
+
+  static const String accountName = 'accountName';
+  static const String accountEmail = 'accountEmail';
+  static const String dropdownIcon = 'dropdownIcon';
+
+  final TextDirection textDirection;
+
+  @override
+  void performLayout(Size size) {
+    Size? iconSize;
+    if (hasChild(dropdownIcon)) {
+      // place the dropdown icon in bottom right (LTR) or bottom left (RTL)
+      iconSize = layoutChild(dropdownIcon, BoxConstraints.loose(size));
+      positionChild(dropdownIcon, _offsetForIcon(size, iconSize));
+    }
+
+    final String? bottomLine = hasChild(accountEmail) ? accountEmail : (hasChild(accountName) ? accountName : null);
+
+    if (bottomLine != null) {
+      final Size constraintSize = iconSize == null ? size : Size(size.width - iconSize.width, size.height);
+      iconSize ??= const Size(_kAccountDetailsHeight, _kAccountDetailsHeight);
+
+      // place bottom line center at same height as icon center
+      final Size bottomLineSize = layoutChild(bottomLine, BoxConstraints.loose(constraintSize));
+      final Offset bottomLineOffset = _offsetForBottomLine(size, iconSize, bottomLineSize);
+      positionChild(bottomLine, bottomLineOffset);
+
+      // place account name above account email
+      if (bottomLine == accountEmail && hasChild(accountName)) {
+        final Size nameSize = layoutChild(accountName, BoxConstraints.loose(constraintSize));
+        positionChild(accountName, _offsetForName(size, nameSize, bottomLineOffset));
+      }
+    }
+  }
+
+  @override
+  bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => true;
+
+  Offset _offsetForIcon(Size size, Size iconSize) {
+    switch (textDirection) {
+      case TextDirection.ltr:
+        return Offset(size.width - iconSize.width, size.height - iconSize.height);
+      case TextDirection.rtl:
+        return Offset(0.0, size.height - iconSize.height);
+    }
+  }
+
+  Offset _offsetForBottomLine(Size size, Size iconSize, Size bottomLineSize) {
+    final double y = size.height - 0.5 * iconSize.height - 0.5 * bottomLineSize.height;
+    switch (textDirection) {
+      case TextDirection.ltr:
+        return Offset(0.0, y);
+      case TextDirection.rtl:
+        return Offset(size.width - bottomLineSize.width, y);
+    }
+  }
+
+  Offset _offsetForName(Size size, Size nameSize, Offset bottomLineOffset) {
+    final double y = bottomLineOffset.dy - nameSize.height;
+    switch (textDirection) {
+      case TextDirection.ltr:
+        return Offset(0.0, y);
+      case TextDirection.rtl:
+        return Offset(size.width - nameSize.width, y);
+    }
+  }
+}
+
+/// A material design [Drawer] header that identifies the app's user.
+///
+/// Requires one of its ancestors to be a [Material] widget.
+///
+/// See also:
+///
+///  * [DrawerHeader], for a drawer header that doesn't show user accounts.
+///  * <https://material.io/design/components/navigation-drawer.html#anatomy>
+class UserAccountsDrawerHeader extends StatefulWidget {
+  /// Creates a material design drawer header.
+  ///
+  /// Requires one of its ancestors to be a [Material] widget.
+  const UserAccountsDrawerHeader({
+    Key? key,
+    this.decoration,
+    this.margin = const EdgeInsets.only(bottom: 8.0),
+    this.currentAccountPicture,
+    this.otherAccountsPictures,
+    required this.accountName,
+    required this.accountEmail,
+    this.onDetailsPressed,
+    this.arrowColor = Colors.white,
+  }) : super(key: key);
+
+  /// The header's background. If decoration is null then a [BoxDecoration]
+  /// with its background color set to the current theme's primaryColor is used.
+  final Decoration? decoration;
+
+  /// The margin around the drawer header.
+  final EdgeInsetsGeometry? margin;
+
+  /// A widget placed in the upper-left corner that represents the current
+  /// user's account. Normally a [CircleAvatar].
+  final Widget? currentAccountPicture;
+
+  /// A list of widgets that represent the current user's other accounts.
+  /// Up to three of these widgets will be arranged in a row in the header's
+  /// upper-right corner. Normally a list of [CircleAvatar] widgets.
+  final List<Widget>? otherAccountsPictures;
+
+  /// A widget that represents the user's current account name. It is
+  /// displayed on the left, below the [currentAccountPicture].
+  final Widget? accountName;
+
+  /// A widget that represents the email address of the user's current account.
+  /// It is displayed on the left, below the [accountName].
+  final Widget? accountEmail;
+
+  /// A callback that is called when the horizontal area which contains the
+  /// [accountName] and [accountEmail] is tapped.
+  final VoidCallback? onDetailsPressed;
+
+  /// The [Color] of the arrow icon.
+  final Color arrowColor;
+
+  @override
+  _UserAccountsDrawerHeaderState createState() => _UserAccountsDrawerHeaderState();
+}
+
+class _UserAccountsDrawerHeaderState extends State<UserAccountsDrawerHeader> {
+  bool _isOpen = false;
+
+  void _handleDetailsPressed() {
+    setState(() {
+      _isOpen = !_isOpen;
+    });
+    widget.onDetailsPressed!();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMaterial(context));
+    assert(debugCheckHasMaterialLocalizations(context));
+    return Semantics(
+      container: true,
+      label: MaterialLocalizations.of(context).signedInLabel,
+      child: DrawerHeader(
+        decoration: widget.decoration ?? BoxDecoration(
+          color: Theme.of(context).primaryColor,
+        ),
+        margin: widget.margin,
+        padding: const EdgeInsetsDirectional.only(top: 16.0, start: 16.0),
+        child: SafeArea(
+          bottom: false,
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.stretch,
+            children: <Widget>[
+              Expanded(
+                child: Padding(
+                  padding: const EdgeInsetsDirectional.only(end: 16.0),
+                  child: _AccountPictures(
+                    currentAccountPicture: widget.currentAccountPicture,
+                    otherAccountsPictures: widget.otherAccountsPictures,
+                  ),
+                ),
+              ),
+              _AccountDetails(
+                accountName: widget.accountName,
+                accountEmail: widget.accountEmail,
+                isOpen: _isOpen,
+                onTap: widget.onDetailsPressed == null ? null : _handleDetailsPressed,
+                arrowColor: widget.arrowColor,
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/painting/_network_image_io.dart b/lib/src/painting/_network_image_io.dart
new file mode 100644
index 0000000..2e3495a
--- /dev/null
+++ b/lib/src/painting/_network_image_io.dart
@@ -0,0 +1,142 @@
+// Copyright 2014 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.
+
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:typed_data';
+import 'package:flute/ui.dart' as ui;
+
+import 'package:flute/foundation.dart';
+
+import 'binding.dart';
+import 'debug.dart';
+import 'image_provider.dart' as image_provider;
+import 'image_stream.dart';
+
+/// The dart:io implementation of [image_provider.NetworkImage].
+@immutable
+class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkImage> implements image_provider.NetworkImage {
+  /// Creates an object that fetches the image at the given URL.
+  ///
+  /// The arguments [url] and [scale] must not be null.
+  const NetworkImage(this.url, { this.scale = 1.0, this.headers })
+    : assert(url != null),
+      assert(scale != null);
+
+  @override
+  final String url;
+
+  @override
+  final double scale;
+
+  @override
+  final Map<String, String>? headers;
+
+  @override
+  Future<NetworkImage> obtainKey(image_provider.ImageConfiguration configuration) {
+    return SynchronousFuture<NetworkImage>(this);
+  }
+
+  @override
+  ImageStreamCompleter load(image_provider.NetworkImage key, image_provider.DecoderCallback decode) {
+    // Ownership of this controller is handed off to [_loadAsync]; it is that
+    // method's responsibility to close the controller's stream when the image
+    // has been loaded or an error is thrown.
+    final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
+
+    return MultiFrameImageStreamCompleter(
+      codec: _loadAsync(key as NetworkImage, chunkEvents, decode),
+      chunkEvents: chunkEvents.stream,
+      scale: key.scale,
+      debugLabel: key.url,
+      informationCollector: () {
+        return <DiagnosticsNode>[
+          DiagnosticsProperty<image_provider.ImageProvider>('Image provider', this),
+          DiagnosticsProperty<image_provider.NetworkImage>('Image key', key),
+        ];
+      },
+    );
+  }
+
+  // Do not access this field directly; use [_httpClient] instead.
+  // We set `autoUncompress` to false to ensure that we can trust the value of
+  // the `Content-Length` HTTP header. We automatically uncompress the content
+  // in our call to [consolidateHttpClientResponseBytes].
+  static final HttpClient _sharedHttpClient = HttpClient()..autoUncompress = false;
+
+  static HttpClient get _httpClient {
+    HttpClient client = _sharedHttpClient;
+    assert(() {
+      if (debugNetworkImageHttpClientProvider != null)
+        client = debugNetworkImageHttpClientProvider!();
+      return true;
+    }());
+    return client;
+  }
+
+  Future<ui.Codec> _loadAsync(
+    NetworkImage key,
+    StreamController<ImageChunkEvent> chunkEvents,
+    image_provider.DecoderCallback decode,
+  ) async {
+    try {
+      assert(key == this);
+
+      final Uri resolved = Uri.base.resolve(key.url);
+
+      final HttpClientRequest request = await _httpClient.getUrl(resolved);
+
+      headers?.forEach((String name, String value) {
+        request.headers.add(name, value);
+      });
+      final HttpClientResponse response = await request.close();
+      if (response.statusCode != HttpStatus.ok) {
+        // The network may be only temporarily unavailable, or the file will be
+        // added on the server later. Avoid having future calls to resolve
+        // fail to check the network again.
+        throw image_provider.NetworkImageLoadException(statusCode: response.statusCode, uri: resolved);
+      }
+
+      final Uint8List bytes = await consolidateHttpClientResponseBytes(
+        response,
+        onBytesReceived: (int cumulative, int? total) {
+          chunkEvents.add(ImageChunkEvent(
+            cumulativeBytesLoaded: cumulative,
+            expectedTotalBytes: total,
+          ));
+        },
+      );
+      if (bytes.lengthInBytes == 0)
+        throw Exception('NetworkImage is an empty file: $resolved');
+
+      return decode(bytes);
+    } catch (e) {
+      // Depending on where the exception was thrown, the image cache may not
+      // have had a chance to track the key in the cache at all.
+      // Schedule a microtask to give the cache a chance to add the key.
+      scheduleMicrotask(() {
+        PaintingBinding.instance!.imageCache!.evict(key);
+      });
+      rethrow;
+    } finally {
+      chunkEvents.close();
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is NetworkImage
+        && other.url == url
+        && other.scale == scale;
+  }
+
+  @override
+  int get hashCode => ui.hashValues(url, scale);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'NetworkImage')}("$url", scale: $scale)';
+}
diff --git a/lib/src/painting/alignment.dart b/lib/src/painting/alignment.dart
new file mode 100644
index 0000000..eabda84
--- /dev/null
+++ b/lib/src/painting/alignment.dart
@@ -0,0 +1,673 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' as ui show lerpDouble;
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+
+/// Base class for [Alignment] that allows for text-direction aware
+/// resolution.
+///
+/// A property or argument of this type accepts classes created either with [new
+/// Alignment] and its variants, or [new AlignmentDirectional].
+///
+/// To convert an [AlignmentGeometry] object of indeterminate type into an
+/// [Alignment] object, call the [resolve] method.
+@immutable
+abstract class AlignmentGeometry {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const AlignmentGeometry();
+
+  double get _x;
+
+  double get _start;
+
+  double get _y;
+
+  /// Returns the sum of two [AlignmentGeometry] objects.
+  ///
+  /// If you know you are adding two [Alignment] or two [AlignmentDirectional]
+  /// objects, consider using the `+` operator instead, which always returns an
+  /// object of the same type as the operands, and is typed accordingly.
+  ///
+  /// If [add] is applied to two objects of the same type ([Alignment] or
+  /// [AlignmentDirectional]), an object of that type will be returned (though
+  /// this is not reflected in the type system). Otherwise, an object
+  /// representing a combination of both is returned. That object can be turned
+  /// into a concrete [Alignment] using [resolve].
+  AlignmentGeometry add(AlignmentGeometry other) {
+    return _MixedAlignment(
+      _x + other._x,
+      _start + other._start,
+      _y + other._y,
+    );
+  }
+
+  /// Returns the negation of the given [AlignmentGeometry] object.
+  ///
+  /// This is the same as multiplying the object by -1.0.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  AlignmentGeometry operator -();
+
+  /// Scales the [AlignmentGeometry] object in each dimension by the given factor.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  AlignmentGeometry operator *(double other);
+
+  /// Divides the [AlignmentGeometry] object in each dimension by the given factor.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  AlignmentGeometry operator /(double other);
+
+  /// Integer divides the [AlignmentGeometry] object in each dimension by the given factor.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  AlignmentGeometry operator ~/(double other);
+
+  /// Computes the remainder in each dimension by the given factor.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  AlignmentGeometry operator %(double other);
+
+  /// Linearly interpolate between two [AlignmentGeometry] objects.
+  ///
+  /// If either is null, this function interpolates from [Alignment.center], and
+  /// the result is an object of the same type as the non-null argument.
+  ///
+  /// If [lerp] is applied to two objects of the same type ([Alignment] or
+  /// [AlignmentDirectional]), an object of that type will be returned (though
+  /// this is not reflected in the type system). Otherwise, an object
+  /// representing a combination of both is returned. That object can be turned
+  /// into a concrete [Alignment] using [resolve].
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static AlignmentGeometry? lerp(AlignmentGeometry? a, AlignmentGeometry? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b! * t;
+    if (b == null)
+      return a * (1.0 - t);
+    if (a is Alignment && b is Alignment)
+      return Alignment.lerp(a, b, t);
+    if (a is AlignmentDirectional && b is AlignmentDirectional)
+      return AlignmentDirectional.lerp(a, b, t);
+    return _MixedAlignment(
+      ui.lerpDouble(a._x, b._x, t)!,
+      ui.lerpDouble(a._start, b._start, t)!,
+      ui.lerpDouble(a._y, b._y, t)!,
+    );
+  }
+
+  /// Convert this instance into an [Alignment], which uses literal
+  /// coordinates (the `x` coordinate being explicitly a distance from the
+  /// left).
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], for which this is a no-op (returns itself).
+  ///  * [AlignmentDirectional], which flips the horizontal direction
+  ///    based on the `direction` argument.
+  Alignment resolve(TextDirection? direction);
+
+  @override
+  String toString() {
+    if (_start == 0.0)
+      return Alignment._stringify(_x, _y);
+    if (_x == 0.0)
+      return AlignmentDirectional._stringify(_start, _y);
+    return Alignment._stringify(_x, _y) + ' + ' + AlignmentDirectional._stringify(_start, 0.0);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    return other is AlignmentGeometry
+        && other._x == _x
+        && other._start == _start
+        && other._y == _y;
+  }
+
+  @override
+  int get hashCode => hashValues(_x, _start, _y);
+}
+
+/// A point within a rectangle.
+///
+/// `Alignment(0.0, 0.0)` represents the center of the rectangle. The distance
+/// from -1.0 to +1.0 is the distance from one side of the rectangle to the
+/// other side of the rectangle. Therefore, 2.0 units horizontally (or
+/// vertically) is equivalent to the width (or height) of the rectangle.
+///
+/// `Alignment(-1.0, -1.0)` represents the top left of the rectangle.
+///
+/// `Alignment(1.0, 1.0)` represents the bottom right of the rectangle.
+///
+/// `Alignment(0.0, 3.0)` represents a point that is horizontally centered with
+/// respect to the rectangle and vertically below the bottom of the rectangle by
+/// the height of the rectangle.
+///
+/// `Alignment(0.0, -0.5)` represents a point that is horizontally centered with
+/// respect to the rectangle and vertically half way between the top edge and
+/// the center.
+///
+/// `Alignment(x, y)` in a rectangle with height h and width w describes
+/// the point (x * w/2 + w/2, y * h/2 + h/2) in the coordinate system of the
+/// rectangle.
+///
+/// [Alignment] uses visual coordinates, which means increasing [x] moves the
+/// point from left to right. To support layouts with a right-to-left
+/// [TextDirection], consider using [AlignmentDirectional], in which the
+/// direction the point moves when increasing the horizontal value depends on
+/// the [TextDirection].
+///
+/// A variety of widgets use [Alignment] in their configuration, most
+/// notably:
+///
+///  * [Align] positions a child according to an [Alignment].
+///
+/// See also:
+///
+///  * [AlignmentDirectional], which has a horizontal coordinate orientation
+///    that depends on the [TextDirection].
+///  * [AlignmentGeometry], which is an abstract type that is agnostic as to
+///    whether the horizontal direction depends on the [TextDirection].
+class Alignment extends AlignmentGeometry {
+  /// Creates an alignment.
+  ///
+  /// The [x] and [y] arguments must not be null.
+  const Alignment(this.x, this.y)
+    : assert(x != null),
+      assert(y != null);
+
+  /// The distance fraction in the horizontal direction.
+  ///
+  /// A value of -1.0 corresponds to the leftmost edge. A value of 1.0
+  /// corresponds to the rightmost edge. Values are not limited to that range;
+  /// values less than -1.0 represent positions to the left of the left edge,
+  /// and values greater than 1.0 represent positions to the right of the right
+  /// edge.
+  final double x;
+
+  /// The distance fraction in the vertical direction.
+  ///
+  /// A value of -1.0 corresponds to the topmost edge. A value of 1.0
+  /// corresponds to the bottommost edge. Values are not limited to that range;
+  /// values less than -1.0 represent positions above the top, and values
+  /// greater than 1.0 represent positions below the bottom.
+  final double y;
+
+  @override
+  double get _x => x;
+
+  @override
+  double get _start => 0.0;
+
+  @override
+  double get _y => y;
+
+  /// The top left corner.
+  static const Alignment topLeft = Alignment(-1.0, -1.0);
+
+  /// The center point along the top edge.
+  static const Alignment topCenter = Alignment(0.0, -1.0);
+
+  /// The top right corner.
+  static const Alignment topRight = Alignment(1.0, -1.0);
+
+  /// The center point along the left edge.
+  static const Alignment centerLeft = Alignment(-1.0, 0.0);
+
+  /// The center point, both horizontally and vertically.
+  static const Alignment center = Alignment(0.0, 0.0);
+
+  /// The center point along the right edge.
+  static const Alignment centerRight = Alignment(1.0, 0.0);
+
+  /// The bottom left corner.
+  static const Alignment bottomLeft = Alignment(-1.0, 1.0);
+
+  /// The center point along the bottom edge.
+  static const Alignment bottomCenter = Alignment(0.0, 1.0);
+
+  /// The bottom right corner.
+  static const Alignment bottomRight = Alignment(1.0, 1.0);
+
+  @override
+  AlignmentGeometry add(AlignmentGeometry other) {
+    if (other is Alignment)
+      return this + other;
+    return super.add(other);
+  }
+
+  /// Returns the difference between two [Alignment]s.
+  Alignment operator -(Alignment other) {
+    return Alignment(x - other.x, y - other.y);
+  }
+
+  /// Returns the sum of two [Alignment]s.
+  Alignment operator +(Alignment other) {
+    return Alignment(x + other.x, y + other.y);
+  }
+
+  /// Returns the negation of the given [Alignment].
+  @override
+  Alignment operator -() {
+    return Alignment(-x, -y);
+  }
+
+  /// Scales the [Alignment] in each dimension by the given factor.
+  @override
+  Alignment operator *(double other) {
+    return Alignment(x * other, y * other);
+  }
+
+  /// Divides the [Alignment] in each dimension by the given factor.
+  @override
+  Alignment operator /(double other) {
+    return Alignment(x / other, y / other);
+  }
+
+  /// Integer divides the [Alignment] in each dimension by the given factor.
+  @override
+  Alignment operator ~/(double other) {
+    return Alignment((x ~/ other).toDouble(), (y ~/ other).toDouble());
+  }
+
+  /// Computes the remainder in each dimension by the given factor.
+  @override
+  Alignment operator %(double other) {
+    return Alignment(x % other, y % other);
+  }
+
+  /// Returns the offset that is this fraction in the direction of the given offset.
+  Offset alongOffset(Offset other) {
+    final double centerX = other.dx / 2.0;
+    final double centerY = other.dy / 2.0;
+    return Offset(centerX + x * centerX, centerY + y * centerY);
+  }
+
+  /// Returns the offset that is this fraction within the given size.
+  Offset alongSize(Size other) {
+    final double centerX = other.width / 2.0;
+    final double centerY = other.height / 2.0;
+    return Offset(centerX + x * centerX, centerY + y * centerY);
+  }
+
+  /// Returns the point that is this fraction within the given rect.
+  Offset withinRect(Rect rect) {
+    final double halfWidth = rect.width / 2.0;
+    final double halfHeight = rect.height / 2.0;
+    return Offset(
+      rect.left + halfWidth + x * halfWidth,
+      rect.top + halfHeight + y * halfHeight,
+    );
+  }
+
+  /// Returns a rect of the given size, aligned within given rect as specified
+  /// by this alignment.
+  ///
+  /// For example, a 100×100 size inscribed on a 200×200 rect using
+  /// [Alignment.topLeft] would be the 100×100 rect at the top left of
+  /// the 200×200 rect.
+  Rect inscribe(Size size, Rect rect) {
+    final double halfWidthDelta = (rect.width - size.width) / 2.0;
+    final double halfHeightDelta = (rect.height - size.height) / 2.0;
+    return Rect.fromLTWH(
+      rect.left + halfWidthDelta + x * halfWidthDelta,
+      rect.top + halfHeightDelta + y * halfHeightDelta,
+      size.width,
+      size.height,
+    );
+  }
+
+  /// Linearly interpolate between two [Alignment]s.
+  ///
+  /// If either is null, this function interpolates from [Alignment.center].
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static Alignment? lerp(Alignment? a, Alignment? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return Alignment(ui.lerpDouble(0.0, b!.x, t)!, ui.lerpDouble(0.0, b.y, t)!);
+    if (b == null)
+      return Alignment(ui.lerpDouble(a.x, 0.0, t)!, ui.lerpDouble(a.y, 0.0, t)!);
+    return Alignment(ui.lerpDouble(a.x, b.x, t)!, ui.lerpDouble(a.y, b.y, t)!);
+  }
+
+  @override
+  Alignment resolve(TextDirection? direction) => this;
+
+  static String _stringify(double x, double y) {
+    if (x == -1.0 && y == -1.0)
+      return 'Alignment.topLeft';
+    if (x == 0.0 && y == -1.0)
+      return 'Alignment.topCenter';
+    if (x == 1.0 && y == -1.0)
+      return 'Alignment.topRight';
+    if (x == -1.0 && y == 0.0)
+      return 'Alignment.centerLeft';
+    if (x == 0.0 && y == 0.0)
+      return 'Alignment.center';
+    if (x == 1.0 && y == 0.0)
+      return 'Alignment.centerRight';
+    if (x == -1.0 && y == 1.0)
+      return 'Alignment.bottomLeft';
+    if (x == 0.0 && y == 1.0)
+      return 'Alignment.bottomCenter';
+    if (x == 1.0 && y == 1.0)
+      return 'Alignment.bottomRight';
+    return 'Alignment(${x.toStringAsFixed(1)}, '
+                     '${y.toStringAsFixed(1)})';
+  }
+
+  @override
+  String toString() => _stringify(x, y);
+}
+
+/// An offset that's expressed as a fraction of a [Size], but whose horizontal
+/// component is dependent on the writing direction.
+///
+/// This can be used to indicate an offset from the left in [TextDirection.ltr]
+/// text and an offset from the right in [TextDirection.rtl] text without having
+/// to be aware of the current text direction.
+///
+/// See also:
+///
+///  * [Alignment], a variant that is defined in physical terms (i.e.
+///    whose horizontal component does not depend on the text direction).
+class AlignmentDirectional extends AlignmentGeometry {
+  /// Creates a directional alignment.
+  ///
+  /// The [start] and [y] arguments must not be null.
+  const AlignmentDirectional(this.start, this.y)
+    : assert(start != null),
+      assert(y != null);
+
+  /// The distance fraction in the horizontal direction.
+  ///
+  /// A value of -1.0 corresponds to the edge on the "start" side, which is the
+  /// left side in [TextDirection.ltr] contexts and the right side in
+  /// [TextDirection.rtl] contexts. A value of 1.0 corresponds to the opposite
+  /// edge, the "end" side. Values are not limited to that range; values less
+  /// than -1.0 represent positions beyond the start edge, and values greater than
+  /// 1.0 represent positions beyond the end edge.
+  ///
+  /// This value is normalized into an [Alignment.x] value by the [resolve]
+  /// method.
+  final double start;
+
+  /// The distance fraction in the vertical direction.
+  ///
+  /// A value of -1.0 corresponds to the topmost edge. A value of 1.0
+  /// corresponds to the bottommost edge. Values are not limited to that range;
+  /// values less than -1.0 represent positions above the top, and values
+  /// greater than 1.0 represent positions below the bottom.
+  ///
+  /// This value is passed through to [Alignment.y] unmodified by the
+  /// [resolve] method.
+  final double y;
+
+  @override
+  double get _x => 0.0;
+
+  @override
+  double get _start => start;
+
+  @override
+  double get _y => y;
+
+  /// The top corner on the "start" side.
+  static const AlignmentDirectional topStart = AlignmentDirectional(-1.0, -1.0);
+
+  /// The center point along the top edge.
+  ///
+  /// Consider using [Alignment.topCenter] instead, as it does not need
+  /// to be [resolve]d to be used.
+  static const AlignmentDirectional topCenter = AlignmentDirectional(0.0, -1.0);
+
+  /// The top corner on the "end" side.
+  static const AlignmentDirectional topEnd = AlignmentDirectional(1.0, -1.0);
+
+  /// The center point along the "start" edge.
+  static const AlignmentDirectional centerStart = AlignmentDirectional(-1.0, 0.0);
+
+  /// The center point, both horizontally and vertically.
+  ///
+  /// Consider using [Alignment.center] instead, as it does not need to
+  /// be [resolve]d to be used.
+  static const AlignmentDirectional center = AlignmentDirectional(0.0, 0.0);
+
+  /// The center point along the "end" edge.
+  static const AlignmentDirectional centerEnd = AlignmentDirectional(1.0, 0.0);
+
+  /// The bottom corner on the "start" side.
+  static const AlignmentDirectional bottomStart = AlignmentDirectional(-1.0, 1.0);
+
+  /// The center point along the bottom edge.
+  ///
+  /// Consider using [Alignment.bottomCenter] instead, as it does not
+  /// need to be [resolve]d to be used.
+  static const AlignmentDirectional bottomCenter = AlignmentDirectional(0.0, 1.0);
+
+  /// The bottom corner on the "end" side.
+  static const AlignmentDirectional bottomEnd = AlignmentDirectional(1.0, 1.0);
+
+  @override
+  AlignmentGeometry add(AlignmentGeometry other) {
+    if (other is AlignmentDirectional)
+      return this + other;
+    return super.add(other);
+  }
+
+  /// Returns the difference between two [AlignmentDirectional]s.
+  AlignmentDirectional operator -(AlignmentDirectional other) {
+    return AlignmentDirectional(start - other.start, y - other.y);
+  }
+
+  /// Returns the sum of two [AlignmentDirectional]s.
+  AlignmentDirectional operator +(AlignmentDirectional other) {
+    return AlignmentDirectional(start + other.start, y + other.y);
+  }
+
+  /// Returns the negation of the given [AlignmentDirectional].
+  @override
+  AlignmentDirectional operator -() {
+    return AlignmentDirectional(-start, -y);
+  }
+
+  /// Scales the [AlignmentDirectional] in each dimension by the given factor.
+  @override
+  AlignmentDirectional operator *(double other) {
+    return AlignmentDirectional(start * other, y * other);
+  }
+
+  /// Divides the [AlignmentDirectional] in each dimension by the given factor.
+  @override
+  AlignmentDirectional operator /(double other) {
+    return AlignmentDirectional(start / other, y / other);
+  }
+
+  /// Integer divides the [AlignmentDirectional] in each dimension by the given factor.
+  @override
+  AlignmentDirectional operator ~/(double other) {
+    return AlignmentDirectional((start ~/ other).toDouble(), (y ~/ other).toDouble());
+  }
+
+  /// Computes the remainder in each dimension by the given factor.
+  @override
+  AlignmentDirectional operator %(double other) {
+    return AlignmentDirectional(start % other, y % other);
+  }
+
+  /// Linearly interpolate between two [AlignmentDirectional]s.
+  ///
+  /// If either is null, this function interpolates from [AlignmentDirectional.center].
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static AlignmentDirectional? lerp(AlignmentDirectional? a, AlignmentDirectional? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return AlignmentDirectional(ui.lerpDouble(0.0, b!.start, t)!, ui.lerpDouble(0.0, b.y, t)!);
+    if (b == null)
+      return AlignmentDirectional(ui.lerpDouble(a.start, 0.0, t)!, ui.lerpDouble(a.y, 0.0, t)!);
+    return AlignmentDirectional(ui.lerpDouble(a.start, b.start, t)!, ui.lerpDouble(a.y, b.y, t)!);
+  }
+
+  @override
+  Alignment resolve(TextDirection? direction) {
+    assert(direction != null, 'Cannot resolve $runtimeType without a TextDirection.');
+    switch (direction!) {
+      case TextDirection.rtl:
+        return Alignment(-start, y);
+      case TextDirection.ltr:
+        return Alignment(start, y);
+    }
+  }
+
+  static String _stringify(double start, double y) {
+    if (start == -1.0 && y == -1.0)
+      return 'AlignmentDirectional.topStart';
+    if (start == 0.0 && y == -1.0)
+      return 'AlignmentDirectional.topCenter';
+    if (start == 1.0 && y == -1.0)
+      return 'AlignmentDirectional.topEnd';
+    if (start == -1.0 && y == 0.0)
+      return 'AlignmentDirectional.centerStart';
+    if (start == 0.0 && y == 0.0)
+      return 'AlignmentDirectional.center';
+    if (start == 1.0 && y == 0.0)
+      return 'AlignmentDirectional.centerEnd';
+    if (start == -1.0 && y == 1.0)
+      return 'AlignmentDirectional.bottomStart';
+    if (start == 0.0 && y == 1.0)
+      return 'AlignmentDirectional.bottomCenter';
+    if (start == 1.0 && y == 1.0)
+      return 'AlignmentDirectional.bottomEnd';
+    return 'AlignmentDirectional(${start.toStringAsFixed(1)}, '
+                                '${y.toStringAsFixed(1)})';
+  }
+
+  @override
+  String toString() => _stringify(start, y);
+}
+
+class _MixedAlignment extends AlignmentGeometry {
+  const _MixedAlignment(this._x, this._start, this._y);
+
+  @override
+  final double _x;
+
+  @override
+  final double _start;
+
+  @override
+  final double _y;
+
+  @override
+  _MixedAlignment operator -() {
+    return _MixedAlignment(
+      -_x,
+      -_start,
+      -_y,
+    );
+  }
+
+  @override
+  _MixedAlignment operator *(double other) {
+    return _MixedAlignment(
+      _x * other,
+      _start * other,
+      _y * other,
+    );
+  }
+
+  @override
+  _MixedAlignment operator /(double other) {
+    return _MixedAlignment(
+      _x / other,
+      _start / other,
+      _y / other,
+    );
+  }
+
+  @override
+  _MixedAlignment operator ~/(double other) {
+    return _MixedAlignment(
+      (_x ~/ other).toDouble(),
+      (_start ~/ other).toDouble(),
+      (_y ~/ other).toDouble(),
+    );
+  }
+
+  @override
+  _MixedAlignment operator %(double other) {
+    return _MixedAlignment(
+      _x % other,
+      _start % other,
+      _y % other,
+    );
+  }
+
+  @override
+  Alignment resolve(TextDirection? direction) {
+    assert(direction != null, 'Cannot resolve $runtimeType without a TextDirection.');
+    switch (direction!) {
+      case TextDirection.rtl:
+        return Alignment(_x - _start, _y);
+      case TextDirection.ltr:
+        return Alignment(_x + _start, _y);
+    }
+  }
+}
+
+/// The vertical alignment of text within an input box.
+///
+/// A single [y] value that can range from -1.0 to 1.0. -1.0 aligns to the top
+/// of an input box so that the top of the first line of text fits within the
+/// box and its padding. 0.0 aligns to the center of the box. 1.0 aligns so that
+/// the bottom of the last line of text aligns with the bottom interior edge of
+/// the input box.
+///
+/// See also:
+///
+///  * [TextField.textAlignVertical], which is passed on to the [InputDecorator].
+///  * [CupertinoTextField.textAlignVertical], which behaves in the same way as
+///    the parameter in TextField.
+///  * [InputDecorator.textAlignVertical], which defines the alignment of
+///    prefix, input, and suffix within an [InputDecorator].
+class TextAlignVertical {
+  /// Creates a TextAlignVertical from any y value between -1.0 and 1.0.
+  const TextAlignVertical({
+    required this.y,
+  }) : assert(y != null),
+       assert(y >= -1.0 && y <= 1.0);
+
+  /// A value ranging from -1.0 to 1.0 that defines the topmost and bottommost
+  /// locations of the top and bottom of the input box.
+  final double y;
+
+  /// Aligns a TextField's input Text with the topmost location within a
+  /// TextField's input box.
+  static const TextAlignVertical top = TextAlignVertical(y: -1.0);
+  /// Aligns a TextField's input Text to the center of the TextField.
+  static const TextAlignVertical center = TextAlignVertical(y: 0.0);
+  /// Aligns a TextField's input Text with the bottommost location within a
+  /// TextField.
+  static const TextAlignVertical bottom = TextAlignVertical(y: 1.0);
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'TextAlignVertical')}(y: $y)';
+  }
+}
diff --git a/lib/src/painting/basic_types.dart b/lib/src/painting/basic_types.dart
new file mode 100644
index 0000000..7be6387
--- /dev/null
+++ b/lib/src/painting/basic_types.dart
@@ -0,0 +1,269 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' show TextDirection;
+
+export 'package:flute/ui.dart' show
+  BlendMode,
+  BlurStyle,
+  Canvas,
+  Clip,
+  Color,
+  ColorFilter,
+  FilterQuality,
+  FontStyle,
+  FontWeight,
+  ImageShader,
+  Locale,
+  MaskFilter,
+  Offset,
+  Paint,
+  PaintingStyle,
+  Path,
+  PathFillType,
+  PathOperation,
+  Radius,
+  RRect,
+  RSTransform,
+  Rect,
+  Shader,
+  Size,
+  StrokeCap,
+  StrokeJoin,
+  TextAffinity,
+  TextAlign,
+  TextBaseline,
+  TextBox,
+  TextDecoration,
+  TextDecorationStyle,
+  TextDirection,
+  TextPosition,
+  TileMode,
+  VertexMode,
+  hashValues,
+  hashList;
+
+export 'package:flute/foundation.dart' show VoidCallback;
+
+// Intentionally not exported:
+//  - Image, instantiateImageCodec, decodeImageFromList:
+//      We use ui.* to make it very explicit that these are low-level image APIs.
+//      Generally, higher layers provide more reasonable APIs around images.
+//  - lerpDouble:
+//      Hopefully this will eventually become Double.lerp.
+//  - Paragraph, ParagraphBuilder, ParagraphStyle, TextBox:
+//      These are low-level text primitives. Use this package's TextPainter API.
+//  - Picture, PictureRecorder, Scene, SceneBuilder:
+//      These are low-level primitives. Generally, the rendering layer makes these moot.
+//  - Gradient:
+//      Use this package's higher-level Gradient API instead.
+//  - window, WindowPadding
+//      These are generally wrapped by other APIs so we always refer to them directly
+//      as ui.* to avoid making them seem like high-level APIs.
+
+/// The description of the difference between two objects, in the context of how
+/// it will affect the rendering.
+///
+/// Used by [TextSpan.compareTo] and [TextStyle.compareTo].
+///
+/// The values in this enum are ordered such that they are in increasing order
+/// of cost. A value with index N implies all the values with index less than N.
+/// For example, [layout] (index 3) implies [paint] (2).
+enum RenderComparison {
+  /// The two objects are identical (meaning deeply equal, not necessarily
+  /// [dart:core.identical]).
+  identical,
+
+  /// The two objects are identical for the purpose of layout, but may be different
+  /// in other ways.
+  ///
+  /// For example, maybe some event handlers changed.
+  metadata,
+
+  /// The two objects are different but only in ways that affect paint, not layout.
+  ///
+  /// For example, only the color is changed.
+  ///
+  /// [RenderObject.markNeedsPaint] would be necessary to handle this kind of
+  /// change in a render object.
+  paint,
+
+  /// The two objects are different in ways that affect layout (and therefore paint).
+  ///
+  /// For example, the size is changed.
+  ///
+  /// This is the most drastic level of change possible.
+  ///
+  /// [RenderObject.markNeedsLayout] would be necessary to handle this kind of
+  /// change in a render object.
+  layout,
+}
+
+/// The two cardinal directions in two dimensions.
+///
+/// The axis is always relative to the current coordinate space. This means, for
+/// example, that a [horizontal] axis might actually be diagonally from top
+/// right to bottom left, due to some local [Transform] applied to the scene.
+///
+/// See also:
+///
+///  * [AxisDirection], which is a directional version of this enum (with values
+///    light left and right, rather than just horizontal).
+///  * [TextDirection], which disambiguates between left-to-right horizontal
+///    content and right-to-left horizontal content.
+enum Axis {
+  /// Left and right.
+  ///
+  /// See also:
+  ///
+  ///  * [TextDirection], which disambiguates between left-to-right horizontal
+  ///    content and right-to-left horizontal content.
+  horizontal,
+
+  /// Up and down.
+  vertical,
+}
+
+/// Returns the opposite of the given [Axis].
+///
+/// Specifically, returns [Axis.horizontal] for [Axis.vertical], and
+/// vice versa.
+///
+/// See also:
+///
+///  * [flipAxisDirection], which does the same thing for [AxisDirection] values.
+Axis flipAxis(Axis direction) {
+  assert(direction != null);
+  switch (direction) {
+    case Axis.horizontal:
+      return Axis.vertical;
+    case Axis.vertical:
+      return Axis.horizontal;
+  }
+}
+
+/// A direction in which boxes flow vertically.
+///
+/// This is used by the flex algorithm (e.g. [Column]) to decide in which
+/// direction to draw boxes.
+///
+/// This is also used to disambiguate `start` and `end` values (e.g.
+/// [MainAxisAlignment.start] or [CrossAxisAlignment.end]).
+///
+/// See also:
+///
+///  * [TextDirection], which controls the same thing but horizontally.
+enum VerticalDirection {
+  /// Boxes should start at the bottom and be stacked vertically towards the top.
+  ///
+  /// The "start" is at the bottom, the "end" is at the top.
+  up,
+
+  /// Boxes should start at the top and be stacked vertically towards the bottom.
+  ///
+  /// The "start" is at the top, the "end" is at the bottom.
+  down,
+}
+
+/// A direction along either the horizontal or vertical [Axis].
+enum AxisDirection {
+  /// Zero is at the bottom and positive values are above it: `⇈`
+  ///
+  /// Alphabetical content with a [GrowthDirection.forward] would have the A at
+  /// the bottom and the Z at the top. This is an unusual configuration.
+  up,
+
+  /// Zero is on the left and positive values are to the right of it: `⇉`
+  ///
+  /// Alphabetical content with a [GrowthDirection.forward] would have the A on
+  /// the left and the Z on the right. This is the ordinary reading order for a
+  /// horizontal set of tabs in an English application, for example.
+  right,
+
+  /// Zero is at the top and positive values are below it: `⇊`
+  ///
+  /// Alphabetical content with a [GrowthDirection.forward] would have the A at
+  /// the top and the Z at the bottom. This is the ordinary reading order for a
+  /// vertical list.
+  down,
+
+  /// Zero is to the right and positive values are to the left of it: `⇇`
+  ///
+  /// Alphabetical content with a [GrowthDirection.forward] would have the A at
+  /// the right and the Z at the left. This is the ordinary reading order for a
+  /// horizontal set of tabs in a Hebrew application, for example.
+  left,
+}
+
+/// Returns the [Axis] that contains the given [AxisDirection].
+///
+/// Specifically, returns [Axis.vertical] for [AxisDirection.up] and
+/// [AxisDirection.down] and returns [Axis.horizontal] for [AxisDirection.left]
+/// and [AxisDirection.right].
+Axis axisDirectionToAxis(AxisDirection axisDirection) {
+  assert(axisDirection != null);
+  switch (axisDirection) {
+    case AxisDirection.up:
+    case AxisDirection.down:
+      return Axis.vertical;
+    case AxisDirection.left:
+    case AxisDirection.right:
+      return Axis.horizontal;
+  }
+}
+
+/// Returns the [AxisDirection] in which reading occurs in the given [TextDirection].
+///
+/// Specifically, returns [AxisDirection.left] for [TextDirection.rtl] and
+/// [AxisDirection.right] for [TextDirection.ltr].
+AxisDirection textDirectionToAxisDirection(TextDirection textDirection) {
+  assert(textDirection != null);
+  switch (textDirection) {
+    case TextDirection.rtl:
+      return AxisDirection.left;
+    case TextDirection.ltr:
+      return AxisDirection.right;
+  }
+}
+
+/// Returns the opposite of the given [AxisDirection].
+///
+/// Specifically, returns [AxisDirection.up] for [AxisDirection.down] (and
+/// vice versa), as well as [AxisDirection.left] for [AxisDirection.right] (and
+/// vice versa).
+///
+/// See also:
+///
+///  * [flipAxis], which does the same thing for [Axis] values.
+AxisDirection flipAxisDirection(AxisDirection axisDirection) {
+  assert(axisDirection != null);
+  switch (axisDirection) {
+    case AxisDirection.up:
+      return AxisDirection.down;
+    case AxisDirection.right:
+      return AxisDirection.left;
+    case AxisDirection.down:
+      return AxisDirection.up;
+    case AxisDirection.left:
+      return AxisDirection.right;
+  }
+}
+
+/// Returns whether traveling along the given axis direction visits coordinates
+/// along that axis in numerically decreasing order.
+///
+/// Specifically, returns true for [AxisDirection.up] and [AxisDirection.left]
+/// and false for [AxisDirection.down] and [AxisDirection.right].
+bool axisDirectionIsReversed(AxisDirection axisDirection) {
+  assert(axisDirection != null);
+  switch (axisDirection) {
+    case AxisDirection.up:
+    case AxisDirection.left:
+      return true;
+    case AxisDirection.down:
+    case AxisDirection.right:
+      return false;
+  }
+}
diff --git a/lib/src/painting/beveled_rectangle_border.dart b/lib/src/painting/beveled_rectangle_border.dart
new file mode 100644
index 0000000..eb2d762
--- /dev/null
+++ b/lib/src/painting/beveled_rectangle_border.dart
@@ -0,0 +1,162 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'border_radius.dart';
+import 'borders.dart';
+import 'edge_insets.dart';
+
+/// A rectangular border with flattened or "beveled" corners.
+///
+/// The line segments that connect the rectangle's four sides will
+/// begin and at locations offset by the corresponding border radius,
+/// but not farther than the side's center. If all the border radii
+/// exceed the sides' half widths/heights the resulting shape is
+/// diamond made by connecting the centers of the sides.
+class BeveledRectangleBorder extends OutlinedBorder {
+  /// Creates a border like a [RoundedRectangleBorder] except that the corners
+  /// are joined by straight lines instead of arcs.
+  ///
+  /// The arguments must not be null.
+  const BeveledRectangleBorder({
+    BorderSide side = BorderSide.none,
+    this.borderRadius = BorderRadius.zero,
+  }) : assert(side != null),
+       assert(borderRadius != null),
+       super(side: side);
+
+  /// The radii for each corner.
+  ///
+  /// Each corner [Radius] defines the endpoints of a line segment that
+  /// spans the corner. The endpoints are located in the same place as
+  /// they would be for [RoundedRectangleBorder], but they're connected
+  /// by a straight line instead of an arc.
+  ///
+  /// Negative radius values are clamped to 0.0 by [getInnerPath] and
+  /// [getOuterPath].
+  final BorderRadiusGeometry borderRadius;
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    return EdgeInsets.all(side.width);
+  }
+
+  @override
+  ShapeBorder scale(double t) {
+    return BeveledRectangleBorder(
+      side: side.scale(t),
+      borderRadius: borderRadius * t,
+    );
+  }
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    assert(t != null);
+    if (a is BeveledRectangleBorder) {
+      return BeveledRectangleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
+      );
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    assert(t != null);
+    if (b is BeveledRectangleBorder) {
+      return BeveledRectangleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
+      );
+    }
+    return super.lerpTo(b, t);
+  }
+
+  /// Returns a copy of this RoundedRectangleBorder with the given fields
+  /// replaced with the new values.
+  @override
+  BeveledRectangleBorder copyWith({ BorderSide? side, BorderRadius? borderRadius }) {
+    return BeveledRectangleBorder(
+      side: side ?? this.side,
+      borderRadius: borderRadius ?? this.borderRadius,
+    );
+  }
+
+  Path _getPath(RRect rrect) {
+    final Offset centerLeft = Offset(rrect.left, rrect.center.dy);
+    final Offset centerRight = Offset(rrect.right, rrect.center.dy);
+    final Offset centerTop = Offset(rrect.center.dx, rrect.top);
+    final Offset centerBottom = Offset(rrect.center.dx, rrect.bottom);
+
+    final double tlRadiusX = math.max(0.0, rrect.tlRadiusX);
+    final double tlRadiusY = math.max(0.0, rrect.tlRadiusY);
+    final double trRadiusX = math.max(0.0, rrect.trRadiusX);
+    final double trRadiusY = math.max(0.0, rrect.trRadiusY);
+    final double blRadiusX = math.max(0.0, rrect.blRadiusX);
+    final double blRadiusY = math.max(0.0, rrect.blRadiusY);
+    final double brRadiusX = math.max(0.0, rrect.brRadiusX);
+    final double brRadiusY = math.max(0.0, rrect.brRadiusY);
+
+    final List<Offset> vertices = <Offset>[
+      Offset(rrect.left, math.min(centerLeft.dy, rrect.top + tlRadiusY)),
+      Offset(math.min(centerTop.dx, rrect.left + tlRadiusX), rrect.top),
+      Offset(math.max(centerTop.dx, rrect.right -trRadiusX), rrect.top),
+      Offset(rrect.right, math.min(centerRight.dy, rrect.top + trRadiusY)),
+      Offset(rrect.right, math.max(centerRight.dy, rrect.bottom - brRadiusY)),
+      Offset(math.max(centerBottom.dx, rrect.right - brRadiusX), rrect.bottom),
+      Offset(math.min(centerBottom.dx, rrect.left + blRadiusX), rrect.bottom),
+      Offset(rrect.left, math.max(centerLeft.dy, rrect.bottom  - blRadiusY)),
+    ];
+
+    return Path()..addPolygon(vertices, true);
+  }
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    return _getPath(borderRadius.resolve(textDirection).toRRect(rect).deflate(side.width));
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    return _getPath(borderRadius.resolve(textDirection).toRRect(rect));
+  }
+
+  @override
+  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
+    if (rect.isEmpty)
+      return;
+    switch (side.style) {
+      case BorderStyle.none:
+        break;
+      case BorderStyle.solid:
+        final Path path = getOuterPath(rect, textDirection: textDirection)
+          ..addPath(getInnerPath(rect, textDirection: textDirection), Offset.zero);
+        canvas.drawPath(path, side.toPaint());
+        break;
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is BeveledRectangleBorder
+        && other.side == side
+        && other.borderRadius == borderRadius;
+  }
+
+  @override
+  int get hashCode => hashValues(side, borderRadius);
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'BeveledRectangleBorder')}($side, $borderRadius)';
+  }
+}
diff --git a/lib/src/painting/binding.dart b/lib/src/painting/binding.dart
new file mode 100644
index 0000000..0500508
--- /dev/null
+++ b/lib/src/painting/binding.dart
@@ -0,0 +1,170 @@
+// Copyright 2014 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.
+
+
+import 'dart:typed_data' show Uint8List;
+import 'package:flute/ui.dart' as ui show instantiateImageCodec, Codec;
+import 'package:flute/foundation.dart';
+import 'package:flute/services.dart' show ServicesBinding;
+
+import 'image_cache.dart';
+import 'shader_warm_up.dart';
+
+/// Binding for the painting library.
+///
+/// Hooks into the cache eviction logic to clear the image cache.
+///
+/// Requires the [ServicesBinding] to be mixed in earlier.
+mixin PaintingBinding on BindingBase, ServicesBinding {
+  @override
+  void initInstances() {
+    super.initInstances();
+    _instance = this;
+    _imageCache = createImageCache();
+    shaderWarmUp?.execute();
+  }
+
+  /// The current [PaintingBinding], if one has been created.
+  static PaintingBinding? get instance => _instance;
+  static PaintingBinding? _instance;
+
+  /// [ShaderWarmUp] to be executed during [initInstances].
+  ///
+  /// If the application has scenes that require the compilation of complex
+  /// shaders that are not covered by [DefaultShaderWarmUp], it may cause jank
+  /// in the middle of an animation or interaction. In that case, set
+  /// [shaderWarmUp] to a custom [ShaderWarmUp] before calling [initInstances]
+  /// (usually before [runApp] for normal Flutter apps, and before
+  /// [enableFlutterDriverExtension] for Flutter driver tests). Paint the scene
+  /// in the custom [ShaderWarmUp] so Flutter can pre-compile and cache the
+  /// shaders during startup. The warm up is only costly (100ms-200ms,
+  /// depending on the shaders to compile) during the first run after the
+  /// installation or a data wipe. The warm up does not block the main thread
+  /// so there should be no "Application Not Responding" warning.
+  ///
+  /// Currently the warm-up happens synchronously on the raster thread which
+  /// means the rendering of the first frame on the raster thread will be
+  /// postponed until the warm-up is finished.
+  ///
+  /// See also:
+  ///
+  ///  * [ShaderWarmUp], the interface of how this warm up works.
+  static ShaderWarmUp? shaderWarmUp = const DefaultShaderWarmUp();
+
+  /// The singleton that implements the Flutter framework's image cache.
+  ///
+  /// The cache is used internally by [ImageProvider] and should generally not
+  /// be accessed directly.
+  ///
+  /// The image cache is created during startup by the [createImageCache]
+  /// method.
+  ImageCache? get imageCache => _imageCache;
+  ImageCache? _imageCache;
+
+  /// Creates the [ImageCache] singleton (accessible via [imageCache]).
+  ///
+  /// This method can be overridden to provide a custom image cache.
+  @protected
+  ImageCache createImageCache() => ImageCache();
+
+  /// Calls through to [dart:ui] from [ImageCache].
+  ///
+  /// The `cacheWidth` and `cacheHeight` parameters, when specified, indicate
+  /// the size to decode the image to.
+  ///
+  /// Both `cacheWidth` and `cacheHeight` must be positive values greater than
+  /// or equal to 1, or null. It is valid to specify only one of `cacheWidth`
+  /// and `cacheHeight` with the other remaining null, in which case the omitted
+  /// dimension will be scaled to maintain the aspect ratio of the original
+  /// dimensions. When both are null or omitted, the image will be decoded at
+  /// its native resolution.
+  ///
+  /// The `allowUpscaling` parameter determines whether the `cacheWidth` or
+  /// `cacheHeight` parameters are clamped to the intrinsic width and height of
+  /// the original image. By default, the dimensions are clamped to avoid
+  /// unnecessary memory usage for images. Callers that wish to display an image
+  /// above its native resolution should prefer scaling the canvas the image is
+  /// drawn into.
+  Future<ui.Codec> instantiateImageCodec(Uint8List bytes, {
+    int? cacheWidth,
+    int? cacheHeight,
+    bool allowUpscaling = false,
+  }) {
+    assert(cacheWidth == null || cacheWidth > 0);
+    assert(cacheHeight == null || cacheHeight > 0);
+    assert(allowUpscaling != null);
+    return ui.instantiateImageCodec(
+      bytes,
+      targetWidth: cacheWidth,
+      targetHeight: cacheHeight,
+      allowUpscaling: allowUpscaling,
+    );
+  }
+
+  @override
+  void evict(String asset) {
+    super.evict(asset);
+    imageCache!.clear();
+    imageCache!.clearLiveImages();
+  }
+
+  @override
+  void handleMemoryPressure() {
+    super.handleMemoryPressure();
+    imageCache?.clear();
+  }
+
+  /// Listenable that notifies when the available fonts on the system have
+  /// changed.
+  ///
+  /// System fonts can change when the system installs or removes new font. To
+  /// correctly reflect the change, it is important to relayout text related
+  /// widgets when this happens.
+  ///
+  /// Objects that show text and/or measure text (e.g. via [TextPainter] or
+  /// [Paragraph]) should listen to this and redraw/remeasure.
+  Listenable get systemFonts => _systemFonts;
+  final _SystemFontsNotifier _systemFonts = _SystemFontsNotifier();
+
+  @override
+  Future<void> handleSystemMessage(Object systemMessage) async {
+    await super.handleSystemMessage(systemMessage);
+    final Map<String, dynamic> message = systemMessage as Map<String, dynamic>;
+    final String type = message['type'] as String;
+    switch (type) {
+      case 'fontsChange':
+        _systemFonts.notifyListeners();
+        break;
+    }
+    return;
+  }
+}
+
+class _SystemFontsNotifier extends Listenable {
+  final Set<VoidCallback> _systemFontsCallbacks = <VoidCallback>{};
+
+  void notifyListeners () {
+    for (final VoidCallback callback in _systemFontsCallbacks) {
+      callback();
+    }
+  }
+
+  @override
+  void addListener(VoidCallback listener) {
+    _systemFontsCallbacks.add(listener);
+  }
+  @override
+  void removeListener(VoidCallback listener) {
+    _systemFontsCallbacks.remove(listener);
+  }
+}
+
+/// The singleton that implements the Flutter framework's image cache.
+///
+/// The cache is used internally by [ImageProvider] and should generally not be
+/// accessed directly.
+///
+/// The image cache is created during startup by the [PaintingBinding]'s
+/// [PaintingBinding.createImageCache] method.
+ImageCache? get imageCache => PaintingBinding.instance!.imageCache;
diff --git a/lib/src/painting/border_radius.dart b/lib/src/painting/border_radius.dart
new file mode 100644
index 0000000..33fd71c
--- /dev/null
+++ b/lib/src/painting/border_radius.dart
@@ -0,0 +1,858 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+
+/// Base class for [BorderRadius] that allows for text-direction aware resolution.
+///
+/// A property or argument of this type accepts classes created either with [new
+/// BorderRadius.only] and its variants, or [new BorderRadiusDirectional.only]
+/// and its variants.
+///
+/// To convert a [BorderRadiusGeometry] object of indeterminate type into a
+/// [BorderRadius] object, call the [resolve] method.
+@immutable
+abstract class BorderRadiusGeometry {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const BorderRadiusGeometry();
+
+  Radius get _topLeft;
+  Radius get _topRight;
+  Radius get _bottomLeft;
+  Radius get _bottomRight;
+  Radius get _topStart;
+  Radius get _topEnd;
+  Radius get _bottomStart;
+  Radius get _bottomEnd;
+
+  /// Returns the difference between two [BorderRadiusGeometry] objects.
+  ///
+  /// If you know you are applying this to two [BorderRadius] or two
+  /// [BorderRadiusDirectional] objects, consider using the binary infix `-`
+  /// operator instead, which always returns an object of the same type as the
+  /// operands, and is typed accordingly.
+  ///
+  /// If [subtract] is applied to two objects of the same type ([BorderRadius] or
+  /// [BorderRadiusDirectional]), an object of that type will be returned (though
+  /// this is not reflected in the type system). Otherwise, an object
+  /// representing a combination of both is returned. That object can be turned
+  /// into a concrete [BorderRadius] using [resolve].
+  ///
+  /// This method returns the same result as [add] applied to the result of
+  /// negating the argument (using the prefix unary `-` operator or multiplying
+  /// the argument by -1.0 using the `*` operator).
+  BorderRadiusGeometry subtract(BorderRadiusGeometry other) {
+    return _MixedBorderRadius(
+      _topLeft - other._topLeft,
+      _topRight - other._topRight,
+      _bottomLeft - other._bottomLeft,
+      _bottomRight - other._bottomRight,
+      _topStart - other._topStart,
+      _topEnd - other._topEnd,
+      _bottomStart - other._bottomStart,
+      _bottomEnd - other._bottomEnd,
+    );
+  }
+
+  /// Returns the sum of two [BorderRadiusGeometry] objects.
+  ///
+  /// If you know you are adding two [BorderRadius] or two [BorderRadiusDirectional]
+  /// objects, consider using the `+` operator instead, which always returns an
+  /// object of the same type as the operands, and is typed accordingly.
+  ///
+  /// If [add] is applied to two objects of the same type ([BorderRadius] or
+  /// [BorderRadiusDirectional]), an object of that type will be returned (though
+  /// this is not reflected in the type system). Otherwise, an object
+  /// representing a combination of both is returned. That object can be turned
+  /// into a concrete [BorderRadius] using [resolve].
+  BorderRadiusGeometry add(BorderRadiusGeometry other) {
+    return _MixedBorderRadius(
+      _topLeft + other._topLeft,
+      _topRight + other._topRight,
+      _bottomLeft + other._bottomLeft,
+      _bottomRight + other._bottomRight,
+      _topStart + other._topStart,
+      _topEnd + other._topEnd,
+      _bottomStart + other._bottomStart,
+      _bottomEnd + other._bottomEnd,
+    );
+  }
+
+  /// Returns the [BorderRadiusGeometry] object with each corner radius negated.
+  ///
+  /// This is the same as multiplying the object by -1.0.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  BorderRadiusGeometry operator -();
+
+  /// Scales the [BorderRadiusGeometry] object's corners by the given factor.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  BorderRadiusGeometry operator *(double other);
+
+  /// Divides the [BorderRadiusGeometry] object's corners by the given factor.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  BorderRadiusGeometry operator /(double other);
+
+  /// Integer divides the [BorderRadiusGeometry] object's corners by the given factor.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  ///
+  /// This operator may have unexpected results when applied to a mixture of
+  /// [BorderRadius] and [BorderRadiusDirectional] objects.
+  BorderRadiusGeometry operator ~/(double other);
+
+  /// Computes the remainder of each corner by the given factor.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  ///
+  /// This operator may have unexpected results when applied to a mixture of
+  /// [BorderRadius] and [BorderRadiusDirectional] objects.
+  BorderRadiusGeometry operator %(double other);
+
+  /// Linearly interpolate between two [BorderRadiusGeometry] objects.
+  ///
+  /// If either is null, this function interpolates from [BorderRadius.zero],
+  /// and the result is an object of the same type as the non-null argument. (If
+  /// both are null, this returns null.)
+  ///
+  /// If [lerp] is applied to two objects of the same type ([BorderRadius] or
+  /// [BorderRadiusDirectional]), an object of that type will be returned (though
+  /// this is not reflected in the type system). Otherwise, an object
+  /// representing a combination of both is returned. That object can be turned
+  /// into a concrete [BorderRadius] using [resolve].
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static BorderRadiusGeometry? lerp(BorderRadiusGeometry? a, BorderRadiusGeometry? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    a ??= BorderRadius.zero;
+    b ??= BorderRadius.zero;
+    return a.add((b.subtract(a)) * t);
+  }
+
+  /// Convert this instance into a [BorderRadius], so that the radii are
+  /// expressed for specific physical corners (top-left, top-right, etc) rather
+  /// than in a direction-dependent manner.
+  ///
+  /// See also:
+  ///
+  ///  * [BorderRadius], for which this is a no-op (returns itself).
+  ///  * [BorderRadiusDirectional], which flips the horizontal direction
+  ///    based on the `direction` argument.
+  BorderRadius resolve(TextDirection? direction);
+
+  @override
+  String toString() {
+    String? visual, logical;
+    if (_topLeft == _topRight &&
+        _topRight == _bottomLeft &&
+        _bottomLeft == _bottomRight) {
+      if (_topLeft != Radius.zero) {
+        if (_topLeft.x == _topLeft.y) {
+          visual = 'BorderRadius.circular(${_topLeft.x.toStringAsFixed(1)})';
+        } else {
+          visual = 'BorderRadius.all($_topLeft)';
+        }
+      }
+    } else {
+      // visuals aren't the same and at least one isn't zero
+      final StringBuffer result = StringBuffer();
+      result.write('BorderRadius.only(');
+      bool comma = false;
+      if (_topLeft != Radius.zero) {
+        result.write('topLeft: $_topLeft');
+        comma = true;
+      }
+      if (_topRight != Radius.zero) {
+        if (comma)
+          result.write(', ');
+        result.write('topRight: $_topRight');
+        comma = true;
+      }
+      if (_bottomLeft != Radius.zero) {
+        if (comma)
+          result.write(', ');
+        result.write('bottomLeft: $_bottomLeft');
+        comma = true;
+      }
+      if (_bottomRight != Radius.zero) {
+        if (comma)
+          result.write(', ');
+        result.write('bottomRight: $_bottomRight');
+      }
+      result.write(')');
+      visual = result.toString();
+    }
+    if (_topStart == _topEnd &&
+        _topEnd == _bottomEnd &&
+        _bottomEnd == _bottomStart) {
+      if (_topStart != Radius.zero) {
+        if (_topStart.x == _topStart.y) {
+          logical = 'BorderRadiusDirectional.circular(${_topStart.x.toStringAsFixed(1)})';
+        } else {
+          logical = 'BorderRadiusDirectional.all($_topStart)';
+        }
+      }
+    } else {
+      // logicals aren't the same and at least one isn't zero
+      final StringBuffer result = StringBuffer();
+      result.write('BorderRadiusDirectional.only(');
+      bool comma = false;
+      if (_topStart != Radius.zero) {
+        result.write('topStart: $_topStart');
+        comma = true;
+      }
+      if (_topEnd != Radius.zero) {
+        if (comma)
+          result.write(', ');
+        result.write('topEnd: $_topEnd');
+        comma = true;
+      }
+      if (_bottomStart != Radius.zero) {
+        if (comma)
+          result.write(', ');
+        result.write('bottomStart: $_bottomStart');
+        comma = true;
+      }
+      if (_bottomEnd != Radius.zero) {
+        if (comma)
+          result.write(', ');
+        result.write('bottomEnd: $_bottomEnd');
+      }
+      result.write(')');
+      logical = result.toString();
+    }
+    if (visual != null && logical != null)
+      return '$visual + $logical';
+    if (visual != null)
+      return visual;
+    if (logical != null)
+      return logical;
+    return 'BorderRadius.zero';
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is BorderRadiusGeometry
+        && other._topLeft == _topLeft
+        && other._topRight == _topRight
+        && other._bottomLeft == _bottomLeft
+        && other._bottomRight == _bottomRight
+        && other._topStart == _topStart
+        && other._topEnd == _topEnd
+        && other._bottomStart == _bottomStart
+        && other._bottomEnd == _bottomEnd;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      _topLeft,
+      _topRight,
+      _bottomLeft,
+      _bottomRight,
+      _topStart,
+      _topEnd,
+      _bottomStart,
+      _bottomEnd,
+    );
+  }
+}
+
+/// An immutable set of radii for each corner of a rectangle.
+///
+/// Used by [BoxDecoration] when the shape is a [BoxShape.rectangle].
+///
+/// The [BorderRadius] class specifies offsets in terms of visual corners, e.g.
+/// [topLeft]. These values are not affected by the [TextDirection]. To support
+/// both left-to-right and right-to-left layouts, consider using
+/// [BorderRadiusDirectional], which is expressed in terms that are relative to
+/// a [TextDirection] (typically obtained from the ambient [Directionality]).
+class BorderRadius extends BorderRadiusGeometry {
+  /// Creates a border radius where all radii are [radius].
+  const BorderRadius.all(Radius radius) : this.only(
+    topLeft: radius,
+    topRight: radius,
+    bottomLeft: radius,
+    bottomRight: radius,
+  );
+
+  /// Creates a border radius where all radii are [Radius.circular(radius)].
+  BorderRadius.circular(double radius) : this.all(
+    Radius.circular(radius),
+  );
+
+  /// Creates a vertically symmetric border radius where the top and bottom
+  /// sides of the rectangle have the same radii.
+  const BorderRadius.vertical({
+    Radius top = Radius.zero,
+    Radius bottom = Radius.zero,
+  }) : this.only(
+    topLeft: top,
+    topRight: top,
+    bottomLeft: bottom,
+    bottomRight: bottom,
+  );
+
+  /// Creates a horizontally symmetrical border radius where the left and right
+  /// sides of the rectangle have the same radii.
+  const BorderRadius.horizontal({
+    Radius left = Radius.zero,
+    Radius right = Radius.zero,
+  }) : this.only(
+    topLeft: left,
+    topRight: right,
+    bottomLeft: left,
+    bottomRight: right,
+  );
+
+  /// Creates a border radius with only the given non-zero values. The other
+  /// corners will be right angles.
+  const BorderRadius.only({
+    this.topLeft = Radius.zero,
+    this.topRight = Radius.zero,
+    this.bottomLeft = Radius.zero,
+    this.bottomRight = Radius.zero,
+  });
+
+  /// A border radius with all zero radii.
+  static const BorderRadius zero = BorderRadius.all(Radius.zero);
+
+  /// The top-left [Radius].
+  final Radius topLeft;
+
+  @override
+  Radius get _topLeft => topLeft;
+
+  /// The top-right [Radius].
+  final Radius topRight;
+
+  @override
+  Radius get _topRight => topRight;
+
+  /// The bottom-left [Radius].
+  final Radius bottomLeft;
+
+  @override
+  Radius get _bottomLeft => bottomLeft;
+
+  /// The bottom-right [Radius].
+  final Radius bottomRight;
+
+  @override
+  Radius get _bottomRight => bottomRight;
+
+  @override
+  Radius get _topStart => Radius.zero;
+
+  @override
+  Radius get _topEnd => Radius.zero;
+
+  @override
+  Radius get _bottomStart => Radius.zero;
+
+  @override
+  Radius get _bottomEnd => Radius.zero;
+
+  /// Creates an [RRect] from the current border radius and a [Rect].
+  RRect toRRect(Rect rect) {
+    return RRect.fromRectAndCorners(
+      rect,
+      topLeft: topLeft,
+      topRight: topRight,
+      bottomLeft: bottomLeft,
+      bottomRight: bottomRight,
+    );
+  }
+
+  @override
+  BorderRadiusGeometry subtract(BorderRadiusGeometry other) {
+    if (other is BorderRadius)
+      return this - other;
+    return super.subtract(other);
+  }
+
+  @override
+  BorderRadiusGeometry add(BorderRadiusGeometry other) {
+    if (other is BorderRadius)
+      return this + other;
+    return super.add(other);
+  }
+
+  /// Returns the difference between two [BorderRadius] objects.
+  BorderRadius operator -(BorderRadius other) {
+    return BorderRadius.only(
+      topLeft: topLeft - other.topLeft,
+      topRight: topRight - other.topRight,
+      bottomLeft: bottomLeft - other.bottomLeft,
+      bottomRight: bottomRight - other.bottomRight,
+    );
+  }
+
+  /// Returns the sum of two [BorderRadius] objects.
+  BorderRadius operator +(BorderRadius other) {
+    return BorderRadius.only(
+      topLeft: topLeft + other.topLeft,
+      topRight: topRight + other.topRight,
+      bottomLeft: bottomLeft + other.bottomLeft,
+      bottomRight: bottomRight + other.bottomRight,
+    );
+  }
+
+  /// Returns the [BorderRadius] object with each corner negated.
+  ///
+  /// This is the same as multiplying the object by -1.0.
+  @override
+  BorderRadius operator -() {
+    return BorderRadius.only(
+      topLeft: -topLeft,
+      topRight: -topRight,
+      bottomLeft: -bottomLeft,
+      bottomRight: -bottomRight,
+    );
+  }
+
+  /// Scales each corner of the [BorderRadius] by the given factor.
+  @override
+  BorderRadius operator *(double other) {
+    return BorderRadius.only(
+      topLeft: topLeft * other,
+      topRight: topRight * other,
+      bottomLeft: bottomLeft * other,
+      bottomRight: bottomRight * other,
+    );
+  }
+
+  /// Divides each corner of the [BorderRadius] by the given factor.
+  @override
+  BorderRadius operator /(double other) {
+    return BorderRadius.only(
+      topLeft: topLeft / other,
+      topRight: topRight / other,
+      bottomLeft: bottomLeft / other,
+      bottomRight: bottomRight / other,
+    );
+  }
+
+  /// Integer divides each corner of the [BorderRadius] by the given factor.
+  @override
+  BorderRadius operator ~/(double other) {
+    return BorderRadius.only(
+      topLeft: topLeft ~/ other,
+      topRight: topRight ~/ other,
+      bottomLeft: bottomLeft ~/ other,
+      bottomRight: bottomRight ~/ other,
+    );
+  }
+
+  /// Computes the remainder of each corner by the given factor.
+  @override
+  BorderRadius operator %(double other) {
+    return BorderRadius.only(
+      topLeft: topLeft % other,
+      topRight: topRight % other,
+      bottomLeft: bottomLeft % other,
+      bottomRight: bottomRight % other,
+    );
+  }
+
+  /// Linearly interpolate between two [BorderRadius] objects.
+  ///
+  /// If either is null, this function interpolates from [BorderRadius.zero].
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static BorderRadius? lerp(BorderRadius? a, BorderRadius? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b! * t;
+    if (b == null)
+      return a * (1.0 - t);
+    return BorderRadius.only(
+      topLeft: Radius.lerp(a.topLeft, b.topLeft, t)!,
+      topRight: Radius.lerp(a.topRight, b.topRight, t)!,
+      bottomLeft: Radius.lerp(a.bottomLeft, b.bottomLeft, t)!,
+      bottomRight: Radius.lerp(a.bottomRight, b.bottomRight, t)!,
+    );
+  }
+
+  @override
+  BorderRadius resolve(TextDirection? direction) => this;
+}
+
+/// An immutable set of radii for each corner of a rectangle, but with the
+/// corners specified in a manner dependent on the writing direction.
+///
+/// This can be used to specify a corner radius on the leading or trailing edge
+/// of a box, so that it flips to the other side when the text alignment flips
+/// (e.g. being on the top right in English text but the top left in Arabic
+/// text).
+///
+/// See also:
+///
+///  * [BorderRadius], a variant that uses physical labels (`topLeft` and
+///    `topRight` instead of `topStart` and `topEnd`).
+class BorderRadiusDirectional extends BorderRadiusGeometry {
+  /// Creates a border radius where all radii are [radius].
+  const BorderRadiusDirectional.all(Radius radius) : this.only(
+    topStart: radius,
+    topEnd: radius,
+    bottomStart: radius,
+    bottomEnd: radius,
+  );
+
+  /// Creates a border radius where all radii are [Radius.circular(radius)].
+  BorderRadiusDirectional.circular(double radius) : this.all(
+    Radius.circular(radius),
+  );
+
+  /// Creates a vertically symmetric border radius where the top and bottom
+  /// sides of the rectangle have the same radii.
+  const BorderRadiusDirectional.vertical({
+    Radius top = Radius.zero,
+    Radius bottom = Radius.zero,
+  }) : this.only(
+    topStart: top,
+    topEnd: top,
+    bottomStart: bottom,
+    bottomEnd: bottom,
+  );
+
+  /// Creates a horizontally symmetrical border radius where the start and end
+  /// sides of the rectangle have the same radii.
+  const BorderRadiusDirectional.horizontal({
+    Radius start = Radius.zero,
+    Radius end = Radius.zero,
+  }) : this.only(
+    topStart: start,
+    topEnd: end,
+    bottomStart: start,
+    bottomEnd: end,
+  );
+
+  /// Creates a border radius with only the given non-zero values. The other
+  /// corners will be right angles.
+  const BorderRadiusDirectional.only({
+    this.topStart = Radius.zero,
+    this.topEnd = Radius.zero,
+    this.bottomStart = Radius.zero,
+    this.bottomEnd = Radius.zero,
+  });
+
+  /// A border radius with all zero radii.
+  ///
+  /// Consider using [EdgeInsets.zero] instead, since that object has the same
+  /// effect, but will be cheaper to [resolve].
+  static const BorderRadiusDirectional zero = BorderRadiusDirectional.all(Radius.zero);
+
+  /// The top-start [Radius].
+  final Radius topStart;
+
+  @override
+  Radius get _topStart => topStart;
+
+  /// The top-end [Radius].
+  final Radius topEnd;
+
+  @override
+  Radius get _topEnd => topEnd;
+
+  /// The bottom-start [Radius].
+  final Radius bottomStart;
+
+  @override
+  Radius get _bottomStart => bottomStart;
+
+  /// The bottom-end [Radius].
+  final Radius bottomEnd;
+
+  @override
+  Radius get _bottomEnd => bottomEnd;
+
+  @override
+  Radius get _topLeft => Radius.zero;
+
+  @override
+  Radius get _topRight => Radius.zero;
+
+  @override
+  Radius get _bottomLeft => Radius.zero;
+
+  @override
+  Radius get _bottomRight => Radius.zero;
+
+  @override
+  BorderRadiusGeometry subtract(BorderRadiusGeometry other) {
+    if (other is BorderRadiusDirectional)
+      return this - other;
+    return super.subtract(other);
+  }
+
+  @override
+  BorderRadiusGeometry add(BorderRadiusGeometry other) {
+    if (other is BorderRadiusDirectional)
+      return this + other;
+    return super.add(other);
+  }
+
+  /// Returns the difference between two [BorderRadiusDirectional] objects.
+  BorderRadiusDirectional operator -(BorderRadiusDirectional other) {
+    return BorderRadiusDirectional.only(
+      topStart: topStart - other.topStart,
+      topEnd: topEnd - other.topEnd,
+      bottomStart: bottomStart - other.bottomStart,
+      bottomEnd: bottomEnd - other.bottomEnd,
+    );
+  }
+
+  /// Returns the sum of two [BorderRadiusDirectional] objects.
+  BorderRadiusDirectional operator +(BorderRadiusDirectional other) {
+    return BorderRadiusDirectional.only(
+      topStart: topStart + other.topStart,
+      topEnd: topEnd + other.topEnd,
+      bottomStart: bottomStart + other.bottomStart,
+      bottomEnd: bottomEnd + other.bottomEnd,
+    );
+  }
+
+  /// Returns the [BorderRadiusDirectional] object with each corner negated.
+  ///
+  /// This is the same as multiplying the object by -1.0.
+  @override
+  BorderRadiusDirectional operator -() {
+    return BorderRadiusDirectional.only(
+      topStart: -topStart,
+      topEnd: -topEnd,
+      bottomStart: -bottomStart,
+      bottomEnd: -bottomEnd,
+    );
+  }
+
+  /// Scales each corner of the [BorderRadiusDirectional] by the given factor.
+  @override
+  BorderRadiusDirectional operator *(double other) {
+    return BorderRadiusDirectional.only(
+      topStart: topStart * other,
+      topEnd: topEnd * other,
+      bottomStart: bottomStart * other,
+      bottomEnd: bottomEnd * other,
+    );
+  }
+
+  /// Divides each corner of the [BorderRadiusDirectional] by the given factor.
+  @override
+  BorderRadiusDirectional operator /(double other) {
+    return BorderRadiusDirectional.only(
+      topStart: topStart / other,
+      topEnd: topEnd / other,
+      bottomStart: bottomStart / other,
+      bottomEnd: bottomEnd / other,
+    );
+  }
+
+  /// Integer divides each corner of the [BorderRadiusDirectional] by the given factor.
+  @override
+  BorderRadiusDirectional operator ~/(double other) {
+    return BorderRadiusDirectional.only(
+      topStart: topStart ~/ other,
+      topEnd: topEnd ~/ other,
+      bottomStart: bottomStart ~/ other,
+      bottomEnd: bottomEnd ~/ other,
+    );
+  }
+
+  /// Computes the remainder of each corner by the given factor.
+  @override
+  BorderRadiusDirectional operator %(double other) {
+    return BorderRadiusDirectional.only(
+      topStart: topStart % other,
+      topEnd: topEnd % other,
+      bottomStart: bottomStart % other,
+      bottomEnd: bottomEnd % other,
+    );
+  }
+
+  /// Linearly interpolate between two [BorderRadiusDirectional] objects.
+  ///
+  /// If either is null, this function interpolates from [BorderRadiusDirectional.zero].
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static BorderRadiusDirectional? lerp(BorderRadiusDirectional? a, BorderRadiusDirectional? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b! * t;
+    if (b == null)
+      return a * (1.0 - t);
+    return BorderRadiusDirectional.only(
+      topStart: Radius.lerp(a.topStart, b.topStart, t)!,
+      topEnd: Radius.lerp(a.topEnd, b.topEnd, t)!,
+      bottomStart: Radius.lerp(a.bottomStart, b.bottomStart, t)!,
+      bottomEnd: Radius.lerp(a.bottomEnd, b.bottomEnd, t)!,
+    );
+  }
+
+  @override
+  BorderRadius resolve(TextDirection? direction) {
+    assert(direction != null);
+    switch (direction!) {
+      case TextDirection.rtl:
+        return BorderRadius.only(
+          topLeft: topEnd,
+          topRight: topStart,
+          bottomLeft: bottomEnd,
+          bottomRight: bottomStart,
+        );
+      case TextDirection.ltr:
+        return BorderRadius.only(
+          topLeft: topStart,
+          topRight: topEnd,
+          bottomLeft: bottomStart,
+          bottomRight: bottomEnd,
+        );
+    }
+  }
+}
+
+class _MixedBorderRadius extends BorderRadiusGeometry {
+  const _MixedBorderRadius(
+    this._topLeft,
+    this._topRight,
+    this._bottomLeft,
+    this._bottomRight,
+    this._topStart,
+    this._topEnd,
+    this._bottomStart,
+    this._bottomEnd,
+  );
+
+  @override
+  final Radius _topLeft;
+
+  @override
+  final Radius _topRight;
+
+  @override
+  final Radius _bottomLeft;
+
+  @override
+  final Radius _bottomRight;
+
+  @override
+  final Radius _topStart;
+
+  @override
+  final Radius _topEnd;
+
+  @override
+  final Radius _bottomStart;
+
+  @override
+  final Radius _bottomEnd;
+
+  @override
+  _MixedBorderRadius operator -() {
+    return _MixedBorderRadius(
+      -_topLeft,
+      -_topRight,
+      -_bottomLeft,
+      -_bottomRight,
+      -_topStart,
+      -_topEnd,
+      -_bottomStart,
+      -_bottomEnd,
+    );
+  }
+
+  /// Scales each corner of the [_MixedBorderRadius] by the given factor.
+  @override
+  _MixedBorderRadius operator *(double other) {
+    return _MixedBorderRadius(
+      _topLeft * other,
+      _topRight * other,
+      _bottomLeft * other,
+      _bottomRight * other,
+      _topStart * other,
+      _topEnd * other,
+      _bottomStart * other,
+      _bottomEnd * other,
+    );
+  }
+
+  @override
+  _MixedBorderRadius operator /(double other) {
+    return _MixedBorderRadius(
+      _topLeft / other,
+      _topRight / other,
+      _bottomLeft / other,
+      _bottomRight / other,
+      _topStart / other,
+      _topEnd / other,
+      _bottomStart / other,
+      _bottomEnd / other,
+    );
+  }
+
+  @override
+  _MixedBorderRadius operator ~/(double other) {
+    return _MixedBorderRadius(
+      _topLeft ~/ other,
+      _topRight ~/ other,
+      _bottomLeft ~/ other,
+      _bottomRight ~/ other,
+      _topStart ~/ other,
+      _topEnd ~/ other,
+      _bottomStart ~/ other,
+      _bottomEnd ~/ other,
+    );
+  }
+
+  @override
+  _MixedBorderRadius operator %(double other) {
+    return _MixedBorderRadius(
+      _topLeft % other,
+      _topRight % other,
+      _bottomLeft % other,
+      _bottomRight % other,
+      _topStart % other,
+      _topEnd % other,
+      _bottomStart % other,
+      _bottomEnd % other,
+    );
+  }
+
+  @override
+  BorderRadius resolve(TextDirection? direction) {
+    assert(direction != null);
+    switch (direction!) {
+      case TextDirection.rtl:
+        return BorderRadius.only(
+          topLeft: _topLeft + _topEnd,
+          topRight: _topRight + _topStart,
+          bottomLeft: _bottomLeft + _bottomEnd,
+          bottomRight: _bottomRight + _bottomStart,
+        );
+      case TextDirection.ltr:
+        return BorderRadius.only(
+          topLeft: _topLeft + _topStart,
+          topRight: _topRight + _topEnd,
+          bottomLeft: _bottomLeft + _bottomStart,
+          bottomRight: _bottomRight + _bottomEnd,
+        );
+    }
+  }
+}
diff --git a/lib/src/painting/borders.dart b/lib/src/painting/borders.dart
new file mode 100644
index 0000000..7bce882
--- /dev/null
+++ b/lib/src/painting/borders.dart
@@ -0,0 +1,770 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' as ui show lerpDouble;
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'edge_insets.dart';
+
+/// The style of line to draw for a [BorderSide] in a [Border].
+enum BorderStyle {
+  /// Skip the border.
+  none,
+
+  /// Draw the border as a solid line.
+  solid,
+
+  // if you add more, think about how they will lerp
+}
+
+/// A side of a border of a box.
+///
+/// A [Border] consists of four [BorderSide] objects: [Border.top],
+/// [Border.left], [Border.right], and [Border.bottom].
+///
+/// Note that setting [BorderSide.width] to 0.0 will result in hairline
+/// rendering. A more involved explanation is present in [BorderSide.width].
+///
+/// {@tool snippet}
+///
+/// This sample shows how [BorderSide] objects can be used in a [Container], via
+/// a [BoxDecoration] and a [Border], to decorate some [Text]. In this example,
+/// the text has a thick bar above it that is light blue, and a thick bar below
+/// it that is a darker shade of blue.
+///
+/// ```dart
+/// Container(
+///   padding: EdgeInsets.all(8.0),
+///   decoration: BoxDecoration(
+///     border: Border(
+///       top: BorderSide(width: 16.0, color: Colors.lightBlue.shade50),
+///       bottom: BorderSide(width: 16.0, color: Colors.lightBlue.shade900),
+///     ),
+///   ),
+///   child: Text('Flutter in the sky', textAlign: TextAlign.center),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Border], which uses [BorderSide] objects to represent its sides.
+///  * [BoxDecoration], which optionally takes a [Border] object.
+///  * [TableBorder], which is similar to [Border] but has two more sides
+///    ([TableBorder.horizontalInside] and [TableBorder.verticalInside]), both
+///    of which are also [BorderSide] objects.
+@immutable
+class BorderSide {
+  /// Creates the side of a border.
+  ///
+  /// By default, the border is 1.0 logical pixels wide and solid black.
+  const BorderSide({
+    this.color = const Color(0xFF000000),
+    this.width = 1.0,
+    this.style = BorderStyle.solid,
+  }) : assert(color != null),
+       assert(width != null),
+       assert(width >= 0.0),
+       assert(style != null);
+
+  /// Creates a [BorderSide] that represents the addition of the two given
+  /// [BorderSide]s.
+  ///
+  /// It is only valid to call this if [canMerge] returns true for the two
+  /// sides.
+  ///
+  /// If one of the sides is zero-width with [BorderStyle.none], then the other
+  /// side is return as-is. If both of the sides are zero-width with
+  /// [BorderStyle.none], then [BorderSide.none] is returned.
+  ///
+  /// The arguments must not be null.
+  static BorderSide merge(BorderSide a, BorderSide b) {
+    assert(a != null);
+    assert(b != null);
+    assert(canMerge(a, b));
+    final bool aIsNone = a.style == BorderStyle.none && a.width == 0.0;
+    final bool bIsNone = b.style == BorderStyle.none && b.width == 0.0;
+    if (aIsNone && bIsNone)
+      return BorderSide.none;
+    if (aIsNone)
+      return b;
+    if (bIsNone)
+      return a;
+    assert(a.color == b.color);
+    assert(a.style == b.style);
+    return BorderSide(
+      color: a.color, // == b.color
+      width: a.width + b.width,
+      style: a.style, // == b.style
+    );
+  }
+
+  /// The color of this side of the border.
+  final Color color;
+
+  /// The width of this side of the border, in logical pixels.
+  ///
+  /// Setting width to 0.0 will result in a hairline border. This means that
+  /// the border will have the width of one physical pixel. Also, hairline
+  /// rendering takes shortcuts when the path overlaps a pixel more than once.
+  /// This means that it will render faster than otherwise, but it might
+  /// double-hit pixels, giving it a slightly darker/lighter result.
+  ///
+  /// To omit the border entirely, set the [style] to [BorderStyle.none].
+  final double width;
+
+  /// The style of this side of the border.
+  ///
+  /// To omit a side, set [style] to [BorderStyle.none]. This skips
+  /// painting the border, but the border still has a [width].
+  final BorderStyle style;
+
+  /// A hairline black border that is not rendered.
+  static const BorderSide none = BorderSide(width: 0.0, style: BorderStyle.none);
+
+  /// Creates a copy of this border but with the given fields replaced with the new values.
+  BorderSide copyWith({
+    Color? color,
+    double? width,
+    BorderStyle? style,
+  }) {
+    assert(width == null || width >= 0.0);
+    return BorderSide(
+      color: color ?? this.color,
+      width: width ?? this.width,
+      style: style ?? this.style,
+    );
+  }
+
+  /// Creates a copy of this border side description but with the width scaled
+  /// by the factor `t`.
+  ///
+  /// The `t` argument represents the multiplicand, or the position on the
+  /// timeline for an interpolation from nothing to `this`, with 0.0 meaning
+  /// that the object returned should be the nil variant of this object, 1.0
+  /// meaning that no change should be applied, returning `this` (or something
+  /// equivalent to `this`), and other values meaning that the object should be
+  /// multiplied by `t`. Negative values are treated like zero.
+  ///
+  /// Since a zero width is normally painted as a hairline width rather than no
+  /// border at all, the zero factor is special-cased to instead change the
+  /// style to [BorderStyle.none].
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  BorderSide scale(double t) {
+    assert(t != null);
+    return BorderSide(
+      color: color,
+      width: math.max(0.0, width * t),
+      style: t <= 0.0 ? BorderStyle.none : style,
+    );
+  }
+
+  /// Create a [Paint] object that, if used to stroke a line, will draw the line
+  /// in this border's style.
+  ///
+  /// Not all borders use this method to paint their border sides. For example,
+  /// non-uniform rectangular [Border]s have beveled edges and so paint their
+  /// border sides as filled shapes rather than using a stroke.
+  Paint toPaint() {
+    switch (style) {
+      case BorderStyle.solid:
+        return Paint()
+          ..color = color
+          ..strokeWidth = width
+          ..style = PaintingStyle.stroke;
+      case BorderStyle.none:
+        return Paint()
+          ..color = const Color(0x00000000)
+          ..strokeWidth = 0.0
+          ..style = PaintingStyle.stroke;
+    }
+  }
+
+  /// Whether the two given [BorderSide]s can be merged using [new
+  /// BorderSide.merge].
+  ///
+  /// Two sides can be merged if one or both are zero-width with
+  /// [BorderStyle.none], or if they both have the same color and style.
+  ///
+  /// The arguments must not be null.
+  static bool canMerge(BorderSide a, BorderSide b) {
+    assert(a != null);
+    assert(b != null);
+    if ((a.style == BorderStyle.none && a.width == 0.0) ||
+        (b.style == BorderStyle.none && b.width == 0.0))
+      return true;
+    return a.style == b.style
+        && a.color == b.color;
+  }
+
+  /// Linearly interpolate between two border sides.
+  ///
+  /// The arguments must not be null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static BorderSide lerp(BorderSide a, BorderSide b, double t) {
+    assert(a != null);
+    assert(b != null);
+    assert(t != null);
+    if (t == 0.0)
+      return a;
+    if (t == 1.0)
+      return b;
+    final double width = ui.lerpDouble(a.width, b.width, t)!;
+    if (width < 0.0)
+      return BorderSide.none;
+    if (a.style == b.style) {
+      return BorderSide(
+        color: Color.lerp(a.color, b.color, t)!,
+        width: width,
+        style: a.style, // == b.style
+      );
+    }
+    Color colorA, colorB;
+    switch (a.style) {
+      case BorderStyle.solid:
+        colorA = a.color;
+        break;
+      case BorderStyle.none:
+        colorA = a.color.withAlpha(0x00);
+        break;
+    }
+    switch (b.style) {
+      case BorderStyle.solid:
+        colorB = b.color;
+        break;
+      case BorderStyle.none:
+        colorB = b.color.withAlpha(0x00);
+        break;
+    }
+    return BorderSide(
+      color: Color.lerp(colorA, colorB, t)!,
+      width: width,
+      style: BorderStyle.solid,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is BorderSide
+        && other.color == color
+        && other.width == width
+        && other.style == style;
+  }
+
+  @override
+  int get hashCode => hashValues(color, width, style);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'BorderSide')}($color, ${width.toStringAsFixed(1)}, $style)';
+}
+
+/// Base class for shape outlines.
+///
+/// This class handles how to add multiple borders together. Subclasses define
+/// various shapes, like circles ([CircleBorder]), rounded rectangles
+/// ([RoundedRectangleBorder]), continuous rectangles
+/// ([ContinuousRectangleBorder]), or beveled rectangles
+/// ([BeveledRectangleBorder]).
+///
+/// See also:
+///
+///  * [ShapeDecoration], which can be used with [DecoratedBox] to show a shape.
+///  * [Material] (and many other widgets in the Material library), which takes
+///    a [ShapeBorder] to define its shape.
+///  * [NotchedShape], which describes a shape with a hole in it.
+@immutable
+abstract class ShapeBorder {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const ShapeBorder();
+
+  /// The widths of the sides of this border represented as an [EdgeInsets].
+  ///
+  /// Specifically, this is the amount by which a rectangle should be inset so
+  /// as to avoid painting over any important part of the border. It is the
+  /// amount by which additional borders will be inset before they are drawn.
+  ///
+  /// This can be used, for example, with a [Padding] widget to inset a box by
+  /// the size of these borders.
+  ///
+  /// Shapes that have a fixed ratio regardless of the area on which they are
+  /// painted, or that change their rendering based on the size they are given
+  /// when painting (for instance [CircleBorder]), will not return valid
+  /// [dimensions] information because they cannot know their eventual size when
+  /// computing their [dimensions].
+  EdgeInsetsGeometry get dimensions;
+
+  /// Attempts to create a new object that represents the amalgamation of `this`
+  /// border and the `other` border.
+  ///
+  /// If the type of the other border isn't known, or the given instance cannot
+  /// be reasonably added to this instance, then this should return null.
+  ///
+  /// This method is used by the [operator +] implementation.
+  ///
+  /// The `reversed` argument is true if this object was the right operand of
+  /// the `+` operator, and false if it was the left operand.
+  @protected
+  ShapeBorder? add(ShapeBorder other, { bool reversed = false }) => null;
+
+  /// Creates a new border consisting of the two borders on either side of the
+  /// operator.
+  ///
+  /// If the borders belong to classes that know how to add themselves, then
+  /// this results in a new border that represents the intelligent addition of
+  /// those two borders (see [add]). Otherwise, an object is returned that
+  /// merely paints the two borders sequentially, with the left hand operand on
+  /// the inside and the right hand operand on the outside.
+  ShapeBorder operator +(ShapeBorder other) {
+    return add(other) ?? other.add(this, reversed: true) ?? _CompoundBorder(<ShapeBorder>[other, this]);
+  }
+
+  /// Creates a copy of this border, scaled by the factor `t`.
+  ///
+  /// Typically this means scaling the width of the border's side, but it can
+  /// also include scaling other artifacts of the border, e.g. the border radius
+  /// of a [RoundedRectangleBorder].
+  ///
+  /// The `t` argument represents the multiplicand, or the position on the
+  /// timeline for an interpolation from nothing to `this`, with 0.0 meaning
+  /// that the object returned should be the nil variant of this object, 1.0
+  /// meaning that no change should be applied, returning `this` (or something
+  /// equivalent to `this`), and other values meaning that the object should be
+  /// multiplied by `t`. Negative values are allowed but may be meaningless
+  /// (they correspond to extrapolating the interpolation from this object to
+  /// nothing, and going beyond nothing)
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  ///
+  /// See also:
+  ///
+  ///  * [BorderSide.scale], which most [ShapeBorder] subclasses defer to for
+  ///    the actual computation.
+  ShapeBorder scale(double t);
+
+  /// Linearly interpolates from another [ShapeBorder] (possibly of another
+  /// class) to `this`.
+  ///
+  /// When implementing this method in subclasses, return null if this class
+  /// cannot interpolate from `a`. In that case, [lerp] will try `a`'s [lerpTo]
+  /// method instead. If `a` is null, this must not return null.
+  ///
+  /// The base class implementation handles the case of `a` being null by
+  /// deferring to [scale].
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `this` (or something equivalent to `this`), and values in
+  /// between meaning that the interpolation is at the relevant point on the
+  /// timeline between `a` and `this`. The interpolation can be extrapolated
+  /// beyond 0.0 and 1.0, so negative values and values greater than 1.0 are
+  /// valid (and can easily be generated by curves such as
+  /// [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  ///
+  /// Instead of calling this directly, use [ShapeBorder.lerp].
+  @protected
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    if (a == null)
+      return scale(t);
+    return null;
+  }
+
+  /// Linearly interpolates from `this` to another [ShapeBorder] (possibly of
+  /// another class).
+  ///
+  /// This is called if `b`'s [lerpTo] did not know how to handle this class.
+  ///
+  /// When implementing this method in subclasses, return null if this class
+  /// cannot interpolate from `b`. In that case, [lerp] will apply a default
+  /// behavior instead. If `b` is null, this must not return null.
+  ///
+  /// The base class implementation handles the case of `b` being null by
+  /// deferring to [scale].
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `this` (or something
+  /// equivalent to `this`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `this` and `b`. The interpolation can be extrapolated beyond 0.0
+  /// and 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  ///
+  /// Instead of calling this directly, use [ShapeBorder.lerp].
+  @protected
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    if (b == null)
+      return scale(1.0 - t);
+    return null;
+  }
+
+  /// Linearly interpolates between two [ShapeBorder]s.
+  ///
+  /// This defers to `b`'s [lerpTo] function if `b` is not null. If `b` is
+  /// null or if its [lerpTo] returns null, it uses `a`'s [lerpFrom]
+  /// function instead. If both return null, it returns `a` before `t=0.5`
+  /// and `b` after `t=0.5`.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static ShapeBorder? lerp(ShapeBorder? a, ShapeBorder? b, double t) {
+    assert(t != null);
+    ShapeBorder? result;
+    if (b != null)
+      result = b.lerpFrom(a, t);
+    if (result == null && a != null)
+      result = a.lerpTo(b, t);
+    return result ?? (t < 0.5 ? a : b);
+  }
+
+  /// Create a [Path] that describes the outer edge of the border.
+  ///
+  /// This path must not cross the path given by [getInnerPath] for the same
+  /// [Rect].
+  ///
+  /// To obtain a [Path] that describes the area of the border itself, set the
+  /// [Path.fillType] of the returned object to [PathFillType.evenOdd], and add
+  /// to this object the path returned from [getInnerPath] (using
+  /// [Path.addPath]).
+  ///
+  /// The `textDirection` argument must be provided non-null if the border
+  /// has a text direction dependency (for example if it is expressed in terms
+  /// of "start" and "end" instead of "left" and "right"). It may be null if
+  /// the border will not need the text direction to paint itself.
+  ///
+  /// See also:
+  ///
+  ///  * [getInnerPath], which creates the path for the inner edge.
+  ///  * [Path.contains], which can tell if an [Offset] is within a [Path].
+  Path getOuterPath(Rect rect, { TextDirection? textDirection });
+
+  /// Create a [Path] that describes the inner edge of the border.
+  ///
+  /// This path must not cross the path given by [getOuterPath] for the same
+  /// [Rect].
+  ///
+  /// To obtain a [Path] that describes the area of the border itself, set the
+  /// [Path.fillType] of the returned object to [PathFillType.evenOdd], and add
+  /// to this object the path returned from [getOuterPath] (using
+  /// [Path.addPath]).
+  ///
+  /// The `textDirection` argument must be provided and non-null if the border
+  /// has a text direction dependency (for example if it is expressed in terms
+  /// of "start" and "end" instead of "left" and "right"). It may be null if
+  /// the border will not need the text direction to paint itself.
+  ///
+  /// See also:
+  ///
+  ///  * [getOuterPath], which creates the path for the outer edge.
+  ///  * [Path.contains], which can tell if an [Offset] is within a [Path].
+  Path getInnerPath(Rect rect, { TextDirection? textDirection });
+
+  /// Paints the border within the given [Rect] on the given [Canvas].
+  ///
+  /// The `textDirection` argument must be provided and non-null if the border
+  /// has a text direction dependency (for example if it is expressed in terms
+  /// of "start" and "end" instead of "left" and "right"). It may be null if
+  /// the border will not need the text direction to paint itself.
+  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection });
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'ShapeBorder')}()';
+  }
+}
+
+/// A ShapeBorder that draws an outline with the width and color specified
+/// by [side].
+@immutable
+abstract class OutlinedBorder extends ShapeBorder {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  ///
+  /// The value of [side] must not be null.
+  const OutlinedBorder({ this.side = BorderSide.none }) : assert(side != null);
+
+  /// The border outline's color and weight.
+  ///
+  /// If [side] is [BorderSide.none], which is the default, an outline is not drawn.
+  /// Otherwise the outline is centered over the shape's boundary.
+  final BorderSide side;
+
+  /// Returns a copy of this OutlinedBorder that draws its outline with the
+  /// specified [side], if [side] is non-null.
+  OutlinedBorder copyWith({ BorderSide? side });
+}
+
+/// Represents the addition of two otherwise-incompatible borders.
+///
+/// The borders are listed from the outside to the inside.
+class _CompoundBorder extends ShapeBorder {
+  _CompoundBorder(this.borders)
+    : assert(borders != null),
+      assert(borders.length >= 2),
+      assert(!borders.any((ShapeBorder border) => border is _CompoundBorder));
+
+  final List<ShapeBorder> borders;
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    return borders.fold<EdgeInsetsGeometry>(
+      EdgeInsets.zero,
+      (EdgeInsetsGeometry previousValue, ShapeBorder border) {
+        return previousValue.add(border.dimensions);
+      },
+    );
+  }
+
+  @override
+  ShapeBorder add(ShapeBorder other, { bool reversed = false }) {
+    // This wraps the list of borders with "other", or, if "reversed" is true,
+    // wraps "other" with the list of borders.
+    // If "reversed" is false, "other" should end up being at the start of the
+    // list, otherwise, if "reversed" is true, it should end up at the end.
+    // First, see if we can merge the new adjacent borders.
+    if (other is! _CompoundBorder) {
+      // Here, "ours" is the border at the side where we're adding the new
+      // border, and "merged" is the result of attempting to merge it with the
+      // new border. If it's null, it couldn't be merged.
+      final ShapeBorder ours = reversed ? borders.last : borders.first;
+      final ShapeBorder? merged = ours.add(other, reversed: reversed)
+                             ?? other.add(ours, reversed: !reversed);
+      if (merged != null) {
+        final List<ShapeBorder> result = <ShapeBorder>[...borders];
+        result[reversed ? result.length - 1 : 0] = merged;
+        return _CompoundBorder(result);
+      }
+    }
+    // We can't, so fall back to just adding the new border to the list.
+    final List<ShapeBorder> mergedBorders = <ShapeBorder>[
+      if (reversed) ...borders,
+      if (other is _CompoundBorder) ...other.borders
+      else other,
+      if (!reversed) ...borders,
+    ];
+    return _CompoundBorder(mergedBorders);
+  }
+
+  @override
+  ShapeBorder scale(double t) {
+    return _CompoundBorder(
+      borders.map<ShapeBorder>((ShapeBorder border) => border.scale(t)).toList()
+    );
+  }
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    return _CompoundBorder.lerp(a, this, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    return _CompoundBorder.lerp(this, b, t);
+  }
+
+  static _CompoundBorder lerp(ShapeBorder? a, ShapeBorder? b, double t) {
+    assert(t != null);
+    assert(a is _CompoundBorder || b is _CompoundBorder); // Not really necessary, but all call sites currently intend this.
+    final List<ShapeBorder?> aList = a is _CompoundBorder ? a.borders : <ShapeBorder?>[a];
+    final List<ShapeBorder?> bList = b is _CompoundBorder ? b.borders : <ShapeBorder?>[b];
+    final List<ShapeBorder> results = <ShapeBorder>[];
+    final int length = math.max(aList.length, bList.length);
+    for (int index = 0; index < length; index += 1) {
+      final ShapeBorder? localA = index < aList.length ? aList[index] : null;
+      final ShapeBorder? localB = index < bList.length ? bList[index] : null;
+      if (localA != null && localB != null) {
+        final ShapeBorder? localResult = localA.lerpTo(localB, t) ?? localB.lerpFrom(localA, t);
+        if (localResult != null) {
+          results.add(localResult);
+          continue;
+        }
+      }
+      // If we're changing from one shape to another, make sure the shape that is coming in
+      // is inserted before the shape that is going away, so that the outer path changes to
+      // the new border earlier rather than later. (This affects, among other things, where
+      // the ShapeDecoration class puts its background.)
+      if (localB != null)
+        results.add(localB.scale(t));
+      if (localA != null)
+        results.add(localA.scale(1.0 - t));
+    }
+    return _CompoundBorder(results);
+  }
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    for (int index = 0; index < borders.length - 1; index += 1)
+      rect = borders[index].dimensions.resolve(textDirection).deflateRect(rect);
+    return borders.last.getInnerPath(rect, textDirection: textDirection);
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    return borders.first.getOuterPath(rect, textDirection: textDirection);
+  }
+
+  @override
+  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
+    for (final ShapeBorder border in borders) {
+      border.paint(canvas, rect, textDirection: textDirection);
+      rect = border.dimensions.resolve(textDirection).deflateRect(rect);
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _CompoundBorder
+        && listEquals<ShapeBorder>(other.borders, borders);
+  }
+
+  @override
+  int get hashCode => hashList(borders);
+
+  @override
+  String toString() {
+    // We list them in reverse order because when adding two borders they end up
+    // in the list in the opposite order of what the source looks like: a + b =>
+    // [b, a]. We do this to make the painting code more optimal, and most of
+    // the rest of the code doesn't care, except toString() (for debugging).
+    return borders.reversed.map<String>((ShapeBorder border) => border.toString()).join(' + ');
+  }
+}
+
+/// Paints a border around the given rectangle on the canvas.
+///
+/// The four sides can be independently specified. They are painted in the order
+/// top, right, bottom, left. This is only notable if the widths of the borders
+/// and the size of the given rectangle are such that the border sides will
+/// overlap each other. No effort is made to optimize the rendering of uniform
+/// borders (where all the borders have the same configuration); to render a
+/// uniform border, consider using [Canvas.drawRect] directly.
+///
+/// The arguments must not be null.
+///
+/// See also:
+///
+///  * [paintImage], which paints an image in a rectangle on a canvas.
+///  * [Border], which uses this function to paint its border when the border is
+///    not uniform.
+///  * [BoxDecoration], which describes its border using the [Border] class.
+void paintBorder(
+  Canvas canvas,
+  Rect rect, {
+  BorderSide top = BorderSide.none,
+  BorderSide right = BorderSide.none,
+  BorderSide bottom = BorderSide.none,
+  BorderSide left = BorderSide.none,
+}) {
+  assert(canvas != null);
+  assert(rect != null);
+  assert(top != null);
+  assert(right != null);
+  assert(bottom != null);
+  assert(left != null);
+
+  // We draw the borders as filled shapes, unless the borders are hairline
+  // borders, in which case we use PaintingStyle.stroke, with the stroke width
+  // specified here.
+  final Paint paint = Paint()
+    ..strokeWidth = 0.0;
+
+  final Path path = Path();
+
+  switch (top.style) {
+    case BorderStyle.solid:
+      paint.color = top.color;
+      path.reset();
+      path.moveTo(rect.left, rect.top);
+      path.lineTo(rect.right, rect.top);
+      if (top.width == 0.0) {
+        paint.style = PaintingStyle.stroke;
+      } else {
+        paint.style = PaintingStyle.fill;
+        path.lineTo(rect.right - right.width, rect.top + top.width);
+        path.lineTo(rect.left + left.width, rect.top + top.width);
+      }
+      canvas.drawPath(path, paint);
+      break;
+    case BorderStyle.none:
+      break;
+  }
+
+  switch (right.style) {
+    case BorderStyle.solid:
+      paint.color = right.color;
+      path.reset();
+      path.moveTo(rect.right, rect.top);
+      path.lineTo(rect.right, rect.bottom);
+      if (right.width == 0.0) {
+        paint.style = PaintingStyle.stroke;
+      } else {
+        paint.style = PaintingStyle.fill;
+        path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
+        path.lineTo(rect.right - right.width, rect.top + top.width);
+      }
+      canvas.drawPath(path, paint);
+      break;
+    case BorderStyle.none:
+      break;
+  }
+
+  switch (bottom.style) {
+    case BorderStyle.solid:
+      paint.color = bottom.color;
+      path.reset();
+      path.moveTo(rect.right, rect.bottom);
+      path.lineTo(rect.left, rect.bottom);
+      if (bottom.width == 0.0) {
+        paint.style = PaintingStyle.stroke;
+      } else {
+        paint.style = PaintingStyle.fill;
+        path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
+        path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
+      }
+      canvas.drawPath(path, paint);
+      break;
+    case BorderStyle.none:
+      break;
+  }
+
+  switch (left.style) {
+    case BorderStyle.solid:
+      paint.color = left.color;
+      path.reset();
+      path.moveTo(rect.left, rect.bottom);
+      path.lineTo(rect.left, rect.top);
+      if (left.width == 0.0) {
+        paint.style = PaintingStyle.stroke;
+      } else {
+        paint.style = PaintingStyle.fill;
+        path.lineTo(rect.left + left.width, rect.top + top.width);
+        path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
+      }
+      canvas.drawPath(path, paint);
+      break;
+    case BorderStyle.none:
+      break;
+  }
+}
diff --git a/lib/src/painting/box_border.dart b/lib/src/painting/box_border.dart
new file mode 100644
index 0000000..1d47988
--- /dev/null
+++ b/lib/src/painting/box_border.dart
@@ -0,0 +1,879 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'border_radius.dart';
+import 'borders.dart';
+import 'edge_insets.dart';
+
+// Examples can assume:
+// late BuildContext context;
+
+/// The shape to use when rendering a [Border] or [BoxDecoration].
+///
+/// Consider using [ShapeBorder] subclasses directly (with [ShapeDecoration]),
+/// instead of using [BoxShape] and [Border], if the shapes will need to be
+/// interpolated or animated. The [Border] class cannot interpolate between
+/// different shapes.
+enum BoxShape {
+  /// An axis-aligned, 2D rectangle. May have rounded corners (described by a
+  /// [BorderRadius]). The edges of the rectangle will match the edges of the box
+  /// into which the [Border] or [BoxDecoration] is painted.
+  ///
+  /// See also:
+  ///
+  ///  * [RoundedRectangleBorder], the equivalent [ShapeBorder].
+  rectangle,
+
+  /// A circle centered in the middle of the box into which the [Border] or
+  /// [BoxDecoration] is painted. The diameter of the circle is the shortest
+  /// dimension of the box, either the width or the height, such that the circle
+  /// touches the edges of the box.
+  ///
+  /// See also:
+  ///
+  ///  * [CircleBorder], the equivalent [ShapeBorder].
+  circle,
+
+  // Don't add more, instead create a new ShapeBorder.
+}
+
+/// Base class for box borders that can paint as rectangles, circles, or rounded
+/// rectangles.
+///
+/// This class is extended by [Border] and [BorderDirectional] to provide
+/// concrete versions of four-sided borders using different conventions for
+/// specifying the sides.
+///
+/// The only API difference that this class introduces over [ShapeBorder] is
+/// that its [paint] method takes additional arguments.
+///
+/// See also:
+///
+///  * [BorderSide], which is used to describe each side of the box.
+///  * [RoundedRectangleBorder], another way of describing a box's border.
+///  * [CircleBorder], another way of describing a circle border.
+///  * [BoxDecoration], which uses a [BoxBorder] to describe its borders.
+abstract class BoxBorder extends ShapeBorder {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const BoxBorder();
+
+  /// The top side of this border.
+  ///
+  /// This getter is available on both [Border] and [BorderDirectional]. If
+  /// [isUniform] is true, then this is the same style as all the other sides.
+  BorderSide get top;
+
+  /// The bottom side of this border.
+  BorderSide get bottom;
+
+  /// Whether all four sides of the border are identical. Uniform borders are
+  /// typically more efficient to paint.
+  ///
+  /// A uniform border by definition has no text direction dependency and
+  /// therefore could be expressed as a [Border], even if it is currently a
+  /// [BorderDirectional]. A uniform border can also be expressed as a
+  /// [RoundedRectangleBorder].
+  bool get isUniform;
+
+  // We override this to tighten the return value, so that callers can assume
+  // that we'll return a [BoxBorder].
+  @override
+  BoxBorder? add(ShapeBorder other, { bool reversed = false }) => null;
+
+  /// Linearly interpolate between two borders.
+  ///
+  /// If a border is null, it is treated as having four [BorderSide.none]
+  /// borders.
+  ///
+  /// This supports interpolating between [Border] and [BorderDirectional]
+  /// objects. If both objects are different types but both have sides on one or
+  /// both of their lateral edges (the two sides that aren't the top and bottom)
+  /// other than [BorderSide.none], then the sides are interpolated by reducing
+  /// `a`'s lateral edges to [BorderSide.none] over the first half of the
+  /// animation, and then bringing `b`'s lateral edges _from_ [BorderSide.none]
+  /// over the second half of the animation.
+  ///
+  /// For a more flexible approach, consider [ShapeBorder.lerp], which would
+  /// instead [add] the two sets of sides and interpolate them simultaneously.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static BoxBorder? lerp(BoxBorder? a, BoxBorder? b, double t) {
+    assert(t != null);
+    if ((a is Border?) && (b is Border?))
+      return Border.lerp(a, b, t);
+    if ((a is BorderDirectional?) && (b is BorderDirectional?))
+      return BorderDirectional.lerp(a, b, t);
+    if (b is Border && a is BorderDirectional) {
+      final BoxBorder c = b;
+      b = a;
+      a = c;
+      t = 1.0 - t;
+      // fall through to next case
+    }
+    if (a is Border && b is BorderDirectional) {
+      if (b.start == BorderSide.none && b.end == BorderSide.none) {
+        // The fact that b is a BorderDirectional really doesn't matter, it turns out.
+        return Border(
+          top: BorderSide.lerp(a.top, b.top, t),
+          right: BorderSide.lerp(a.right, BorderSide.none, t),
+          bottom: BorderSide.lerp(a.bottom, b.bottom, t),
+          left: BorderSide.lerp(a.left, BorderSide.none, t),
+        );
+      }
+      if (a.left == BorderSide.none && a.right == BorderSide.none) {
+        // The fact that a is a Border really doesn't matter, it turns out.
+        return BorderDirectional(
+          top: BorderSide.lerp(a.top, b.top, t),
+          start: BorderSide.lerp(BorderSide.none, b.start, t),
+          end: BorderSide.lerp(BorderSide.none, b.end, t),
+          bottom: BorderSide.lerp(a.bottom, b.bottom, t),
+        );
+      }
+      // Since we have to swap a visual border for a directional one,
+      // we speed up the horizontal sides' transitions and switch from
+      // one mode to the other at t=0.5.
+      if (t < 0.5) {
+        return Border(
+          top: BorderSide.lerp(a.top, b.top, t),
+          right: BorderSide.lerp(a.right, BorderSide.none, t * 2.0),
+          bottom: BorderSide.lerp(a.bottom, b.bottom, t),
+          left: BorderSide.lerp(a.left, BorderSide.none, t * 2.0),
+        );
+      }
+      return BorderDirectional(
+        top: BorderSide.lerp(a.top, b.top, t),
+        start: BorderSide.lerp(BorderSide.none, b.start, (t - 0.5) * 2.0),
+        end: BorderSide.lerp(BorderSide.none, b.end, (t - 0.5) * 2.0),
+        bottom: BorderSide.lerp(a.bottom, b.bottom, t),
+      );
+    }
+    throw FlutterError.fromParts(<DiagnosticsNode>[
+      ErrorSummary('BoxBorder.lerp can only interpolate Border and BorderDirectional classes.'),
+      ErrorDescription(
+        'BoxBorder.lerp() was called with two objects of type ${a.runtimeType} and ${b.runtimeType}:\n'
+        '  $a\n'
+        '  $b\n'
+        'However, only Border and BorderDirectional classes are supported by this method.'
+      ),
+      ErrorHint('For a more general interpolation method, consider using ShapeBorder.lerp instead.'),
+    ]);
+  }
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    assert(textDirection != null, 'The textDirection argument to $runtimeType.getInnerPath must not be null.');
+    return Path()
+      ..addRect(dimensions.resolve(textDirection).deflateRect(rect));
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    assert(textDirection != null, 'The textDirection argument to $runtimeType.getOuterPath must not be null.');
+    return Path()
+      ..addRect(rect);
+  }
+
+  /// Paints the border within the given [Rect] on the given [Canvas].
+  ///
+  /// This is an extension of the [ShapeBorder.paint] method. It allows
+  /// [BoxBorder] borders to be applied to different [BoxShape]s and with
+  /// different [borderRadius] parameters, without changing the [BoxBorder]
+  /// object itself.
+  ///
+  /// The `shape` argument specifies the [BoxShape] to draw the border on.
+  ///
+  /// If the `shape` is specifies a rectangular box shape
+  /// ([BoxShape.rectangle]), then the `borderRadius` argument describes the
+  /// corners of the rectangle.
+  ///
+  /// The [getInnerPath] and [getOuterPath] methods do not know about the
+  /// `shape` and `borderRadius` arguments.
+  ///
+  /// See also:
+  ///
+  ///  * [paintBorder], which is used if the border is not uniform.
+  @override
+  void paint(
+    Canvas canvas,
+    Rect rect, {
+    TextDirection? textDirection,
+    BoxShape shape = BoxShape.rectangle,
+    BorderRadius? borderRadius,
+  });
+
+  static void _paintUniformBorderWithRadius(Canvas canvas, Rect rect, BorderSide side, BorderRadius borderRadius) {
+    assert(side.style != BorderStyle.none);
+    final Paint paint = Paint()
+      ..color = side.color;
+    final RRect outer = borderRadius.toRRect(rect);
+    final double width = side.width;
+    if (width == 0.0) {
+      paint
+        ..style = PaintingStyle.stroke
+        ..strokeWidth = 0.0;
+      canvas.drawRRect(outer, paint);
+    } else {
+      final RRect inner = outer.deflate(width);
+      canvas.drawDRRect(outer, inner, paint);
+    }
+  }
+
+  static void _paintUniformBorderWithCircle(Canvas canvas, Rect rect, BorderSide side) {
+    assert(side.style != BorderStyle.none);
+    final double width = side.width;
+    final Paint paint = side.toPaint();
+    final double radius = (rect.shortestSide - width) / 2.0;
+    canvas.drawCircle(rect.center, radius, paint);
+  }
+
+  static void _paintUniformBorderWithRectangle(Canvas canvas, Rect rect, BorderSide side) {
+    assert(side.style != BorderStyle.none);
+    final double width = side.width;
+    final Paint paint = side.toPaint();
+    canvas.drawRect(rect.deflate(width / 2.0), paint);
+  }
+}
+
+/// A border of a box, comprised of four sides: top, right, bottom, left.
+///
+/// The sides are represented by [BorderSide] objects.
+///
+/// {@tool snippet}
+///
+/// All four borders the same, two-pixel wide solid white:
+///
+/// ```dart
+/// Border.all(width: 2.0, color: const Color(0xFFFFFFFF))
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// The border for a material design divider:
+///
+/// ```dart
+/// Border(bottom: BorderSide(color: Theme.of(context).dividerColor))
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// A 1990s-era "OK" button:
+///
+/// ```dart
+/// Container(
+///   decoration: const BoxDecoration(
+///     border: Border(
+///       top: BorderSide(width: 1.0, color: Color(0xFFFFFFFFFF)),
+///       left: BorderSide(width: 1.0, color: Color(0xFFFFFFFFFF)),
+///       right: BorderSide(width: 1.0, color: Color(0xFFFF000000)),
+///       bottom: BorderSide(width: 1.0, color: Color(0xFFFF000000)),
+///     ),
+///   ),
+///   child: Container(
+///     padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 2.0),
+///     decoration: const BoxDecoration(
+///       border: Border(
+///         top: BorderSide(width: 1.0, color: Color(0xFFFFDFDFDF)),
+///         left: BorderSide(width: 1.0, color: Color(0xFFFFDFDFDF)),
+///         right: BorderSide(width: 1.0, color: Color(0xFFFF7F7F7F)),
+///         bottom: BorderSide(width: 1.0, color: Color(0xFFFF7F7F7F)),
+///       ),
+///       color: Color(0xFFBFBFBF),
+///     ),
+///     child: const Text(
+///       'OK',
+///       textAlign: TextAlign.center,
+///       style: TextStyle(color: Color(0xFF000000))
+///     ),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [BoxDecoration], which uses this class to describe its edge decoration.
+///  * [BorderSide], which is used to describe each side of the box.
+///  * [Theme], from the material layer, which can be queried to obtain appropriate colors
+///    to use for borders in a material app, as shown in the "divider" sample above.
+class Border extends BoxBorder {
+  /// Creates a border.
+  ///
+  /// All the sides of the border default to [BorderSide.none].
+  ///
+  /// The arguments must not be null.
+  const Border({
+    this.top = BorderSide.none,
+    this.right = BorderSide.none,
+    this.bottom = BorderSide.none,
+    this.left = BorderSide.none,
+  }) : assert(top != null),
+       assert(right != null),
+       assert(bottom != null),
+       assert(left != null);
+
+  /// Creates a border whose sides are all the same.
+  ///
+  /// The `side` argument must not be null.
+  const Border.fromBorderSide(BorderSide side)
+      : assert(side != null),
+        top = side,
+        right = side,
+        bottom = side,
+        left = side;
+
+  /// Creates a border with symmetrical vertical and horizontal sides.
+  ///
+  /// The `vertical` argument applies to the [left] and [right] sides, and the
+  /// `horizontal` argument applies to the [top] and [bottom] sides.
+  ///
+  /// All arguments default to [BorderSide.none] and must not be null.
+  const Border.symmetric({
+    BorderSide vertical = BorderSide.none,
+    BorderSide horizontal = BorderSide.none,
+  }) : assert(vertical != null),
+       assert(horizontal != null),
+       left = vertical,
+       top = horizontal,
+       right = vertical,
+       bottom = horizontal;
+
+  /// A uniform border with all sides the same color and width.
+  ///
+  /// The sides default to black solid borders, one logical pixel wide.
+  factory Border.all({
+    Color color = const Color(0xFF000000),
+    double width = 1.0,
+    BorderStyle style = BorderStyle.solid,
+  }) {
+    final BorderSide side = BorderSide(color: color, width: width, style: style);
+    return Border.fromBorderSide(side);
+  }
+
+  /// Creates a [Border] that represents the addition of the two given
+  /// [Border]s.
+  ///
+  /// It is only valid to call this if [BorderSide.canMerge] returns true for
+  /// the pairwise combination of each side on both [Border]s.
+  ///
+  /// The arguments must not be null.
+  static Border merge(Border a, Border b) {
+    assert(a != null);
+    assert(b != null);
+    assert(BorderSide.canMerge(a.top, b.top));
+    assert(BorderSide.canMerge(a.right, b.right));
+    assert(BorderSide.canMerge(a.bottom, b.bottom));
+    assert(BorderSide.canMerge(a.left, b.left));
+    return Border(
+      top: BorderSide.merge(a.top, b.top),
+      right: BorderSide.merge(a.right, b.right),
+      bottom: BorderSide.merge(a.bottom, b.bottom),
+      left: BorderSide.merge(a.left, b.left),
+    );
+  }
+
+  @override
+  final BorderSide top;
+
+  /// The right side of this border.
+  final BorderSide right;
+
+  @override
+  final BorderSide bottom;
+
+  /// The left side of this border.
+  final BorderSide left;
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    return EdgeInsets.fromLTRB(left.width, top.width, right.width, bottom.width);
+  }
+
+  @override
+  bool get isUniform => _colorIsUniform && _widthIsUniform && _styleIsUniform;
+
+  bool get _colorIsUniform {
+    final Color topColor = top.color;
+    return right.color == topColor && bottom.color == topColor && left.color == topColor;
+  }
+
+  bool get _widthIsUniform {
+    final double topWidth = top.width;
+    return right.width == topWidth && bottom.width == topWidth && left.width == topWidth;
+  }
+
+  bool get _styleIsUniform {
+    final BorderStyle topStyle = top.style;
+    return right.style == topStyle && bottom.style == topStyle && left.style == topStyle;
+  }
+
+  @override
+  Border? add(ShapeBorder other, { bool reversed = false }) {
+    if (other is Border &&
+        BorderSide.canMerge(top, other.top) &&
+        BorderSide.canMerge(right, other.right) &&
+        BorderSide.canMerge(bottom, other.bottom) &&
+        BorderSide.canMerge(left, other.left)) {
+      return Border.merge(this, other);
+    }
+    return null;
+  }
+
+  @override
+  Border scale(double t) {
+    return Border(
+      top: top.scale(t),
+      right: right.scale(t),
+      bottom: bottom.scale(t),
+      left: left.scale(t),
+    );
+  }
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    if (a is Border)
+      return Border.lerp(a, this, t);
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    if (b is Border)
+      return Border.lerp(this, b, t);
+    return super.lerpTo(b, t);
+  }
+
+  /// Linearly interpolate between two borders.
+  ///
+  /// If a border is null, it is treated as having four [BorderSide.none]
+  /// borders.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static Border? lerp(Border? a, Border? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b!.scale(t);
+    if (b == null)
+      return a.scale(1.0 - t);
+    return Border(
+      top: BorderSide.lerp(a.top, b.top, t),
+      right: BorderSide.lerp(a.right, b.right, t),
+      bottom: BorderSide.lerp(a.bottom, b.bottom, t),
+      left: BorderSide.lerp(a.left, b.left, t),
+    );
+  }
+
+  /// Paints the border within the given [Rect] on the given [Canvas].
+  ///
+  /// Uniform borders are more efficient to paint than more complex borders.
+  ///
+  /// You can provide a [BoxShape] to draw the border on. If the `shape` in
+  /// [BoxShape.circle], there is the requirement that the border [isUniform].
+  ///
+  /// If you specify a rectangular box shape ([BoxShape.rectangle]), then you
+  /// may specify a [BorderRadius]. If a `borderRadius` is specified, there is
+  /// the requirement that the border [isUniform].
+  ///
+  /// The [getInnerPath] and [getOuterPath] methods do not know about the
+  /// `shape` and `borderRadius` arguments.
+  ///
+  /// The `textDirection` argument is not used by this paint method.
+  ///
+  /// See also:
+  ///
+  ///  * [paintBorder], which is used if the border is not uniform.
+  @override
+  void paint(
+    Canvas canvas,
+    Rect rect, {
+    TextDirection? textDirection,
+    BoxShape shape = BoxShape.rectangle,
+    BorderRadius? borderRadius,
+  }) {
+    if (isUniform) {
+      switch (top.style) {
+        case BorderStyle.none:
+          return;
+        case BorderStyle.solid:
+          switch (shape) {
+            case BoxShape.circle:
+              assert(borderRadius == null, 'A borderRadius can only be given for rectangular boxes.');
+              BoxBorder._paintUniformBorderWithCircle(canvas, rect, top);
+              break;
+            case BoxShape.rectangle:
+              if (borderRadius != null) {
+                BoxBorder._paintUniformBorderWithRadius(canvas, rect, top, borderRadius);
+                return;
+              }
+              BoxBorder._paintUniformBorderWithRectangle(canvas, rect, top);
+              break;
+          }
+          return;
+      }
+    }
+
+    assert(() {
+      if (borderRadius != null) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('A borderRadius can only be given for a uniform Border.'),
+          ErrorDescription('The following is not uniform:'),
+          if (!_colorIsUniform) ErrorDescription('BorderSide.color'),
+          if (!_widthIsUniform) ErrorDescription('BorderSide.width'),
+          if (!_styleIsUniform) ErrorDescription('BorderSide.style'),
+        ]);
+      }
+      return true;
+    }());
+    assert(() {
+      if (shape != BoxShape.rectangle) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('A Border can only be drawn as a circle if it is uniform'),
+          ErrorDescription('The following is not uniform:'),
+          if (!_colorIsUniform) ErrorDescription('BorderSide.color'),
+          if (!_widthIsUniform) ErrorDescription('BorderSide.width'),
+          if (!_styleIsUniform) ErrorDescription('BorderSide.style'),
+        ]);
+      }
+      return true;
+    }());
+
+    paintBorder(canvas, rect, top: top, right: right, bottom: bottom, left: left);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is Border
+        && other.top == top
+        && other.right == right
+        && other.bottom == bottom
+        && other.left == left;
+  }
+
+  @override
+  int get hashCode => hashValues(top, right, bottom, left);
+
+  @override
+  String toString() {
+    if (isUniform)
+      return '${objectRuntimeType(this, 'Border')}.all($top)';
+    final List<String> arguments = <String>[
+      if (top != BorderSide.none) 'top: $top',
+      if (right != BorderSide.none) 'right: $right',
+      if (bottom != BorderSide.none) 'bottom: $bottom',
+      if (left != BorderSide.none) 'left: $left',
+    ];
+    return '${objectRuntimeType(this, 'Border')}(${arguments.join(", ")})';
+  }
+}
+
+/// A border of a box, comprised of four sides, the lateral sides of which
+/// flip over based on the reading direction.
+///
+/// The lateral sides are called [start] and [end]. When painted in
+/// left-to-right environments, the [start] side will be painted on the left and
+/// the [end] side on the right; in right-to-left environments, it is the
+/// reverse. The other two sides are [top] and [bottom].
+///
+/// The sides are represented by [BorderSide] objects.
+///
+/// If the [start] and [end] sides are the same, then it is slightly more
+/// efficient to use a [Border] object rather than a [BorderDirectional] object.
+///
+/// See also:
+///
+///  * [BoxDecoration], which uses this class to describe its edge decoration.
+///  * [BorderSide], which is used to describe each side of the box.
+///  * [Theme], from the material layer, which can be queried to obtain appropriate colors
+///    to use for borders in a material app, as shown in the "divider" sample above.
+class BorderDirectional extends BoxBorder {
+  /// Creates a border.
+  ///
+  /// The [start] and [end] sides represent the horizontal sides; the start side
+  /// is on the leading edge given the reading direction, and the end side is on
+  /// the trailing edge. They are resolved during [paint].
+  ///
+  /// All the sides of the border default to [BorderSide.none].
+  ///
+  /// The arguments must not be null.
+  const BorderDirectional({
+    this.top = BorderSide.none,
+    this.start = BorderSide.none,
+    this.end = BorderSide.none,
+    this.bottom = BorderSide.none,
+  }) : assert(top != null),
+       assert(start != null),
+       assert(end != null),
+       assert(bottom != null);
+
+  /// Creates a [BorderDirectional] that represents the addition of the two
+  /// given [BorderDirectional]s.
+  ///
+  /// It is only valid to call this if [BorderSide.canMerge] returns true for
+  /// the pairwise combination of each side on both [BorderDirectional]s.
+  ///
+  /// The arguments must not be null.
+  static BorderDirectional merge(BorderDirectional a, BorderDirectional b) {
+    assert(a != null);
+    assert(b != null);
+    assert(BorderSide.canMerge(a.top, b.top));
+    assert(BorderSide.canMerge(a.start, b.start));
+    assert(BorderSide.canMerge(a.end, b.end));
+    assert(BorderSide.canMerge(a.bottom, b.bottom));
+    return BorderDirectional(
+      top: BorderSide.merge(a.top, b.top),
+      start: BorderSide.merge(a.start, b.start),
+      end: BorderSide.merge(a.end, b.end),
+      bottom: BorderSide.merge(a.bottom, b.bottom),
+    );
+  }
+
+  @override
+  final BorderSide top;
+
+  /// The start side of this border.
+  ///
+  /// This is the side on the left in left-to-right text and on the right in
+  /// right-to-left text.
+  ///
+  /// See also:
+  ///
+  ///  * [TextDirection], which is used to describe the reading direction.
+  final BorderSide start;
+
+  /// The end side of this border.
+  ///
+  /// This is the side on the right in left-to-right text and on the left in
+  /// right-to-left text.
+  ///
+  /// See also:
+  ///
+  ///  * [TextDirection], which is used to describe the reading direction.
+  final BorderSide end;
+
+  @override
+  final BorderSide bottom;
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    return EdgeInsetsDirectional.fromSTEB(start.width, top.width, end.width, bottom.width);
+  }
+
+  @override
+  bool get isUniform {
+    final Color topColor = top.color;
+    if (start.color != topColor ||
+        end.color != topColor ||
+        bottom.color != topColor)
+      return false;
+
+    final double topWidth = top.width;
+    if (start.width != topWidth ||
+        end.width != topWidth ||
+        bottom.width != topWidth)
+      return false;
+
+    final BorderStyle topStyle = top.style;
+    if (start.style != topStyle ||
+        end.style != topStyle ||
+        bottom.style != topStyle)
+      return false;
+
+    return true;
+  }
+
+  @override
+  BoxBorder? add(ShapeBorder other, { bool reversed = false }) {
+    if (other is BorderDirectional) {
+      final BorderDirectional typedOther = other;
+      if (BorderSide.canMerge(top, typedOther.top) &&
+          BorderSide.canMerge(start, typedOther.start) &&
+          BorderSide.canMerge(end, typedOther.end) &&
+          BorderSide.canMerge(bottom, typedOther.bottom)) {
+        return BorderDirectional.merge(this, typedOther);
+      }
+      return null;
+    }
+    if (other is Border) {
+      final Border typedOther = other;
+      if (!BorderSide.canMerge(typedOther.top, top) ||
+          !BorderSide.canMerge(typedOther.bottom, bottom))
+        return null;
+      if (start != BorderSide.none ||
+          end != BorderSide.none) {
+        if (typedOther.left != BorderSide.none ||
+            typedOther.right != BorderSide.none)
+          return null;
+        assert(typedOther.left == BorderSide.none);
+        assert(typedOther.right == BorderSide.none);
+        return BorderDirectional(
+          top: BorderSide.merge(typedOther.top, top),
+          start: start,
+          end: end,
+          bottom: BorderSide.merge(typedOther.bottom, bottom),
+        );
+      }
+      assert(start == BorderSide.none);
+      assert(end == BorderSide.none);
+      return Border(
+        top: BorderSide.merge(typedOther.top, top),
+        right: typedOther.right,
+        bottom: BorderSide.merge(typedOther.bottom, bottom),
+        left: typedOther.left,
+      );
+    }
+    return null;
+  }
+
+  @override
+  BorderDirectional scale(double t) {
+    return BorderDirectional(
+      top: top.scale(t),
+      start: start.scale(t),
+      end: end.scale(t),
+      bottom: bottom.scale(t),
+    );
+  }
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    if (a is BorderDirectional)
+      return BorderDirectional.lerp(a, this, t);
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    if (b is BorderDirectional)
+      return BorderDirectional.lerp(this, b, t);
+    return super.lerpTo(b, t);
+  }
+
+  /// Linearly interpolate between two borders.
+  ///
+  /// If a border is null, it is treated as having four [BorderSide.none]
+  /// borders.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static BorderDirectional? lerp(BorderDirectional? a, BorderDirectional? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b!.scale(t);
+    if (b == null)
+      return a.scale(1.0 - t);
+    return BorderDirectional(
+      top: BorderSide.lerp(a.top, b.top, t),
+      end: BorderSide.lerp(a.end, b.end, t),
+      bottom: BorderSide.lerp(a.bottom, b.bottom, t),
+      start: BorderSide.lerp(a.start, b.start, t),
+    );
+  }
+
+  /// Paints the border within the given [Rect] on the given [Canvas].
+  ///
+  /// Uniform borders are more efficient to paint than more complex borders.
+  ///
+  /// You can provide a [BoxShape] to draw the border on. If the `shape` in
+  /// [BoxShape.circle], there is the requirement that the border [isUniform].
+  ///
+  /// If you specify a rectangular box shape ([BoxShape.rectangle]), then you
+  /// may specify a [BorderRadius]. If a `borderRadius` is specified, there is
+  /// the requirement that the border [isUniform].
+  ///
+  /// The [getInnerPath] and [getOuterPath] methods do not know about the
+  /// `shape` and `borderRadius` arguments.
+  ///
+  /// The `textDirection` argument is used to determine which of [start] and
+  /// [end] map to the left and right. For [TextDirection.ltr], the [start] is
+  /// the left and the [end] is the right; for [TextDirection.rtl], it is the
+  /// reverse.
+  ///
+  /// See also:
+  ///
+  ///  * [paintBorder], which is used if the border is not uniform.
+  @override
+  void paint(
+    Canvas canvas,
+    Rect rect, {
+    TextDirection? textDirection,
+    BoxShape shape = BoxShape.rectangle,
+    BorderRadius? borderRadius,
+  }) {
+    if (isUniform) {
+      switch (top.style) {
+        case BorderStyle.none:
+          return;
+        case BorderStyle.solid:
+          switch (shape) {
+            case BoxShape.circle:
+              assert(borderRadius == null, 'A borderRadius can only be given for rectangular boxes.');
+              BoxBorder._paintUniformBorderWithCircle(canvas, rect, top);
+              break;
+            case BoxShape.rectangle:
+              if (borderRadius != null) {
+                BoxBorder._paintUniformBorderWithRadius(canvas, rect, top, borderRadius);
+                return;
+              }
+              BoxBorder._paintUniformBorderWithRectangle(canvas, rect, top);
+              break;
+          }
+          return;
+      }
+    }
+
+    assert(borderRadius == null, 'A borderRadius can only be given for uniform borders.');
+    assert(shape == BoxShape.rectangle, 'A border can only be drawn as a circle if it is uniform.');
+
+    BorderSide left, right;
+    assert(textDirection != null, 'Non-uniform BorderDirectional objects require a TextDirection when painting.');
+    switch (textDirection!) {
+      case TextDirection.rtl:
+        left = end;
+        right = start;
+        break;
+      case TextDirection.ltr:
+        left = start;
+        right = end;
+        break;
+    }
+    paintBorder(canvas, rect, top: top, left: left, bottom: bottom, right: right);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is BorderDirectional
+        && other.top == top
+        && other.start == start
+        && other.end == end
+        && other.bottom == bottom;
+  }
+
+  @override
+  int get hashCode => hashValues(top, start, end, bottom);
+
+  @override
+  String toString() {
+    final List<String> arguments = <String>[
+      if (top != BorderSide.none) 'top: $top',
+      if (start != BorderSide.none) 'start: $start',
+      if (end != BorderSide.none) 'end: $end',
+      if (bottom != BorderSide.none) 'bottom: $bottom',
+    ];
+    return '${objectRuntimeType(this, 'BorderDirectional')}(${arguments.join(", ")})';
+  }
+}
diff --git a/lib/src/painting/box_decoration.dart b/lib/src/painting/box_decoration.dart
new file mode 100644
index 0000000..e513246
--- /dev/null
+++ b/lib/src/painting/box_decoration.dart
@@ -0,0 +1,498 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'border_radius.dart';
+import 'box_border.dart';
+import 'box_shadow.dart';
+import 'colors.dart';
+import 'decoration.dart';
+import 'decoration_image.dart';
+import 'edge_insets.dart';
+import 'gradient.dart';
+import 'image_provider.dart';
+
+/// An immutable description of how to paint a box.
+///
+/// The [BoxDecoration] class provides a variety of ways to draw a box.
+///
+/// The box has a [border], a body, and may cast a [boxShadow].
+///
+/// The [shape] of the box can be a circle or a rectangle. If it is a rectangle,
+/// then the [borderRadius] property controls the roundness of the corners.
+///
+/// The body of the box is painted in layers. The bottom-most layer is the
+/// [color], which fills the box. Above that is the [gradient], which also fills
+/// the box. Finally there is the [image], the precise alignment of which is
+/// controlled by the [DecorationImage] class.
+///
+/// The [border] paints over the body; the [boxShadow], naturally, paints below it.
+///
+/// {@tool snippet}
+///
+/// The following applies a [BoxDecoration] to a [Container] widget to draw an
+/// [image] of an owl with a thick black [border] and rounded corners.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_decoration.png)
+///
+/// ```dart
+/// Container(
+///   decoration: BoxDecoration(
+///     color: const Color(0xff7c94b6),
+///     image: const DecorationImage(
+///       image: NetworkImage('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'),
+///       fit: BoxFit.cover,
+///     ),
+///     border: Border.all(
+///       color: Colors.black,
+///       width: 8,
+///     ),
+///     borderRadius: BorderRadius.circular(12),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@template flutter.painting.BoxDecoration.clip}
+/// The [shape] or the [borderRadius] won't clip the children of the
+/// decorated [Container]. If the clip is required, insert a clip widget
+/// (e.g., [ClipRect], [ClipRRect], [ClipPath]) as the child of the [Container].
+/// Be aware that clipping may be costly in terms of performance.
+/// {@endtemplate}
+///
+/// See also:
+///
+///  * [DecoratedBox] and [Container], widgets that can be configured with
+///    [BoxDecoration] objects.
+///  * [CustomPaint], a widget that lets you draw arbitrary graphics.
+///  * [Decoration], the base class which lets you define other decorations.
+class BoxDecoration extends Decoration {
+  /// Creates a box decoration.
+  ///
+  /// * If [color] is null, this decoration does not paint a background color.
+  /// * If [image] is null, this decoration does not paint a background image.
+  /// * If [border] is null, this decoration does not paint a border.
+  /// * If [borderRadius] is null, this decoration uses more efficient background
+  ///   painting commands. The [borderRadius] argument must be null if [shape] is
+  ///   [BoxShape.circle].
+  /// * If [boxShadow] is null, this decoration does not paint a shadow.
+  /// * If [gradient] is null, this decoration does not paint gradients.
+  /// * If [backgroundBlendMode] is null, this decoration paints with [BlendMode.srcOver]
+  ///
+  /// The [shape] argument must not be null.
+  const BoxDecoration({
+    this.color,
+    this.image,
+    this.border,
+    this.borderRadius,
+    this.boxShadow,
+    this.gradient,
+    this.backgroundBlendMode,
+    this.shape = BoxShape.rectangle,
+  }) : assert(shape != null),
+       assert(
+         backgroundBlendMode == null || color != null || gradient != null,
+         "backgroundBlendMode applies to BoxDecoration's background color or "
+         'gradient, but no color or gradient was provided.'
+       );
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  BoxDecoration copyWith({
+    Color? color,
+    DecorationImage? image,
+    BoxBorder? border,
+    BorderRadiusGeometry? borderRadius,
+    List<BoxShadow>? boxShadow,
+    Gradient? gradient,
+    BlendMode? backgroundBlendMode,
+    BoxShape? shape,
+  }) {
+    return BoxDecoration(
+      color: color ?? this.color,
+      image: image ?? this.image,
+      border: border ?? this.border,
+      borderRadius: borderRadius ?? this.borderRadius,
+      boxShadow: boxShadow ?? this.boxShadow,
+      gradient: gradient ?? this.gradient,
+      backgroundBlendMode: backgroundBlendMode ?? this.backgroundBlendMode,
+      shape: shape ?? this.shape,
+    );
+  }
+
+  @override
+  bool debugAssertIsValid() {
+    assert(shape != BoxShape.circle ||
+          borderRadius == null); // Can't have a border radius if you're a circle.
+    return super.debugAssertIsValid();
+  }
+
+  /// The color to fill in the background of the box.
+  ///
+  /// The color is filled into the [shape] of the box (e.g., either a rectangle,
+  /// potentially with a [borderRadius], or a circle).
+  ///
+  /// This is ignored if [gradient] is non-null.
+  ///
+  /// The [color] is drawn under the [image].
+  final Color? color;
+
+  /// An image to paint above the background [color] or [gradient].
+  ///
+  /// If [shape] is [BoxShape.circle] then the image is clipped to the circle's
+  /// boundary; if [borderRadius] is non-null then the image is clipped to the
+  /// given radii.
+  final DecorationImage? image;
+
+  /// A border to draw above the background [color], [gradient], or [image].
+  ///
+  /// Follows the [shape] and [borderRadius].
+  ///
+  /// Use [Border] objects to describe borders that do not depend on the reading
+  /// direction.
+  ///
+  /// Use [BoxBorder] objects to describe borders that should flip their left
+  /// and right edges based on whether the text is being read left-to-right or
+  /// right-to-left.
+  final BoxBorder? border;
+
+  /// If non-null, the corners of this box are rounded by this [BorderRadius].
+  ///
+  /// Applies only to boxes with rectangular shapes; ignored if [shape] is not
+  /// [BoxShape.rectangle].
+  ///
+  /// {@macro flutter.painting.BoxDecoration.clip}
+  final BorderRadiusGeometry? borderRadius;
+
+  /// A list of shadows cast by this box behind the box.
+  ///
+  /// The shadow follows the [shape] of the box.
+  ///
+  /// See also:
+  ///
+  ///  * [kElevationToShadow], for some predefined shadows used in Material
+  ///    Design.
+  ///  * [PhysicalModel], a widget for showing shadows.
+  final List<BoxShadow>? boxShadow;
+
+  /// A gradient to use when filling the box.
+  ///
+  /// If this is specified, [color] has no effect.
+  ///
+  /// The [gradient] is drawn under the [image].
+  final Gradient? gradient;
+
+  /// The blend mode applied to the [color] or [gradient] background of the box.
+  ///
+  /// If no [backgroundBlendMode] is provided then the default painting blend
+  /// mode is used.
+  ///
+  /// If no [color] or [gradient] is provided then the blend mode has no impact.
+  final BlendMode? backgroundBlendMode;
+
+  /// The shape to fill the background [color], [gradient], and [image] into and
+  /// to cast as the [boxShadow].
+  ///
+  /// If this is [BoxShape.circle] then [borderRadius] is ignored.
+  ///
+  /// The [shape] cannot be interpolated; animating between two [BoxDecoration]s
+  /// with different [shape]s will result in a discontinuity in the rendering.
+  /// To interpolate between two shapes, consider using [ShapeDecoration] and
+  /// different [ShapeBorder]s; in particular, [CircleBorder] instead of
+  /// [BoxShape.circle] and [RoundedRectangleBorder] instead of
+  /// [BoxShape.rectangle].
+  ///
+  /// {@macro flutter.painting.BoxDecoration.clip}
+  final BoxShape shape;
+
+  @override
+  EdgeInsetsGeometry? get padding => border?.dimensions;
+
+  @override
+  Path getClipPath(Rect rect, TextDirection textDirection) {
+    switch (shape) {
+      case BoxShape.circle:
+        final Offset center = rect.center;
+        final double radius = rect.shortestSide / 2.0;
+        final Rect square = Rect.fromCircle(center: center, radius: radius);
+        return Path()..addOval(square);
+      case BoxShape.rectangle:
+        if (borderRadius != null)
+          return Path()..addRRect(borderRadius!.resolve(textDirection).toRRect(rect));
+        return Path()..addRect(rect);
+    }
+  }
+
+  /// Returns a new box decoration that is scaled by the given factor.
+  BoxDecoration scale(double factor) {
+    return BoxDecoration(
+      color: Color.lerp(null, color, factor),
+      image: image, // TODO(ianh): fade the image from transparent
+      border: BoxBorder.lerp(null, border, factor),
+      borderRadius: BorderRadiusGeometry.lerp(null, borderRadius, factor),
+      boxShadow: BoxShadow.lerpList(null, boxShadow, factor),
+      gradient: gradient?.scale(factor),
+      shape: shape,
+    );
+  }
+
+  @override
+  bool get isComplex => boxShadow != null;
+
+  @override
+  BoxDecoration? lerpFrom(Decoration? a, double t) {
+    if (a == null)
+      return scale(t);
+    if (a is BoxDecoration)
+      return BoxDecoration.lerp(a, this, t);
+    return super.lerpFrom(a, t) as BoxDecoration?;
+  }
+
+  @override
+  BoxDecoration? lerpTo(Decoration? b, double t) {
+    if (b == null)
+      return scale(1.0 - t);
+    if (b is BoxDecoration)
+      return BoxDecoration.lerp(this, b, t);
+    return super.lerpTo(b, t) as BoxDecoration?;
+  }
+
+  /// Linearly interpolate between two box decorations.
+  ///
+  /// Interpolates each parameter of the box decoration separately.
+  ///
+  /// The [shape] is not interpolated. To interpolate the shape, consider using
+  /// a [ShapeDecoration] with different border shapes.
+  ///
+  /// If both values are null, this returns null. Otherwise, it returns a
+  /// non-null value. If one of the values is null, then the result is obtained
+  /// by applying [scale] to the other value. If neither value is null and `t ==
+  /// 0.0`, then `a` is returned unmodified; if `t == 1.0` then `b` is returned
+  /// unmodified. Otherwise, the values are computed by interpolating the
+  /// properties appropriately.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  ///
+  /// See also:
+  ///
+  ///  * [Decoration.lerp], which can interpolate between any two types of
+  ///    [Decoration]s, not just [BoxDecoration]s.
+  ///  * [lerpFrom] and [lerpTo], which are used to implement [Decoration.lerp]
+  ///    and which use [BoxDecoration.lerp] when interpolating two
+  ///    [BoxDecoration]s or a [BoxDecoration] to or from null.
+  static BoxDecoration? lerp(BoxDecoration? a, BoxDecoration? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b!.scale(t);
+    if (b == null)
+      return a.scale(1.0 - t);
+    if (t == 0.0)
+      return a;
+    if (t == 1.0)
+      return b;
+    return BoxDecoration(
+      color: Color.lerp(a.color, b.color, t),
+      image: t < 0.5 ? a.image : b.image, // TODO(ianh): cross-fade the image
+      border: BoxBorder.lerp(a.border, b.border, t),
+      borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, b.borderRadius, t),
+      boxShadow: BoxShadow.lerpList(a.boxShadow, b.boxShadow, t),
+      gradient: Gradient.lerp(a.gradient, b.gradient, t),
+      shape: t < 0.5 ? a.shape : b.shape,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is BoxDecoration
+        && other.color == color
+        && other.image == image
+        && other.border == border
+        && other.borderRadius == borderRadius
+        && listEquals<BoxShadow>(other.boxShadow, boxShadow)
+        && other.gradient == gradient
+        && other.shape == shape;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      color,
+      image,
+      border,
+      borderRadius,
+      hashList(boxShadow),
+      gradient,
+      shape,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties
+      ..defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.whitespace
+      ..emptyBodyDescription = '<no decorations specified>';
+
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(DiagnosticsProperty<DecorationImage>('image', image, defaultValue: null));
+    properties.add(DiagnosticsProperty<BoxBorder>('border', border, defaultValue: null));
+    properties.add(DiagnosticsProperty<BorderRadiusGeometry>('borderRadius', borderRadius, defaultValue: null));
+    properties.add(IterableProperty<BoxShadow>('boxShadow', boxShadow, defaultValue: null, style: DiagnosticsTreeStyle.whitespace));
+    properties.add(DiagnosticsProperty<Gradient>('gradient', gradient, defaultValue: null));
+    properties.add(EnumProperty<BoxShape>('shape', shape, defaultValue: BoxShape.rectangle));
+  }
+
+  @override
+  bool hitTest(Size size, Offset position, { TextDirection? textDirection }) {
+    assert(shape != null);
+    assert((Offset.zero & size).contains(position));
+    switch (shape) {
+      case BoxShape.rectangle:
+        if (borderRadius != null) {
+          final RRect bounds = borderRadius!.resolve(textDirection).toRRect(Offset.zero & size);
+          return bounds.contains(position);
+        }
+        return true;
+      case BoxShape.circle:
+        // Circles are inscribed into our smallest dimension.
+        final Offset center = size.center(Offset.zero);
+        final double distance = (position - center).distance;
+        return distance <= math.min(size.width, size.height) / 2.0;
+    }
+  }
+
+  @override
+  _BoxDecorationPainter createBoxPainter([ VoidCallback? onChanged ]) {
+    assert(onChanged != null || image == null);
+    return _BoxDecorationPainter(this, onChanged);
+  }
+}
+
+/// An object that paints a [BoxDecoration] into a canvas.
+class _BoxDecorationPainter extends BoxPainter {
+  _BoxDecorationPainter(this._decoration, VoidCallback? onChanged)
+    : assert(_decoration != null),
+      super(onChanged);
+
+  final BoxDecoration _decoration;
+
+  Paint? _cachedBackgroundPaint;
+  Rect? _rectForCachedBackgroundPaint;
+  Paint _getBackgroundPaint(Rect rect, TextDirection? textDirection) {
+    assert(rect != null);
+    assert(_decoration.gradient != null || _rectForCachedBackgroundPaint == null);
+
+    if (_cachedBackgroundPaint == null ||
+        (_decoration.gradient != null && _rectForCachedBackgroundPaint != rect)) {
+      final Paint paint = Paint();
+      if (_decoration.backgroundBlendMode != null)
+        paint.blendMode = _decoration.backgroundBlendMode!;
+      if (_decoration.color != null)
+        paint.color = _decoration.color!;
+      if (_decoration.gradient != null) {
+        paint.shader = _decoration.gradient!.createShader(rect, textDirection: textDirection);
+        _rectForCachedBackgroundPaint = rect;
+      }
+      _cachedBackgroundPaint = paint;
+    }
+
+    return _cachedBackgroundPaint!;
+  }
+
+  void _paintBox(Canvas canvas, Rect rect, Paint paint, TextDirection? textDirection) {
+    switch (_decoration.shape) {
+      case BoxShape.circle:
+        assert(_decoration.borderRadius == null);
+        final Offset center = rect.center;
+        final double radius = rect.shortestSide / 2.0;
+        canvas.drawCircle(center, radius, paint);
+        break;
+      case BoxShape.rectangle:
+        if (_decoration.borderRadius == null) {
+          canvas.drawRect(rect, paint);
+        } else {
+          canvas.drawRRect(_decoration.borderRadius!.resolve(textDirection).toRRect(rect), paint);
+        }
+        break;
+    }
+  }
+
+  void _paintShadows(Canvas canvas, Rect rect, TextDirection? textDirection) {
+    if (_decoration.boxShadow == null)
+      return;
+    for (final BoxShadow boxShadow in _decoration.boxShadow!) {
+      final Paint paint = boxShadow.toPaint();
+      final Rect bounds = rect.shift(boxShadow.offset).inflate(boxShadow.spreadRadius);
+      _paintBox(canvas, bounds, paint, textDirection);
+    }
+  }
+
+  void _paintBackgroundColor(Canvas canvas, Rect rect, TextDirection? textDirection) {
+    if (_decoration.color != null || _decoration.gradient != null)
+      _paintBox(canvas, rect, _getBackgroundPaint(rect, textDirection), textDirection);
+  }
+
+  DecorationImagePainter? _imagePainter;
+  void _paintBackgroundImage(Canvas canvas, Rect rect, ImageConfiguration configuration) {
+    if (_decoration.image == null)
+      return;
+    _imagePainter ??= _decoration.image!.createPainter(onChanged!);
+    Path? clipPath;
+    switch (_decoration.shape) {
+      case BoxShape.circle:
+        assert(_decoration.borderRadius == null);
+        final Offset center = rect.center;
+        final double radius = rect.shortestSide / 2.0;
+        final Rect square = Rect.fromCircle(center: center, radius: radius);
+        clipPath = Path()..addOval(square);
+        break;
+      case BoxShape.rectangle:
+        if (_decoration.borderRadius != null)
+          clipPath = Path()..addRRect(_decoration.borderRadius!.resolve(configuration.textDirection).toRRect(rect));
+        break;
+    }
+    _imagePainter!.paint(canvas, rect, clipPath, configuration);
+  }
+
+  @override
+  void dispose() {
+    _imagePainter?.dispose();
+    super.dispose();
+  }
+
+  /// Paint the box decoration into the given location on the given canvas.
+  @override
+  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
+    assert(configuration != null);
+    assert(configuration.size != null);
+    final Rect rect = offset & configuration.size!;
+    final TextDirection? textDirection = configuration.textDirection;
+    _paintShadows(canvas, rect, textDirection);
+    _paintBackgroundColor(canvas, rect, textDirection);
+    _paintBackgroundImage(canvas, rect, configuration);
+    _decoration.border?.paint(
+      canvas,
+      rect,
+      shape: _decoration.shape,
+      borderRadius: _decoration.borderRadius as BorderRadius?,
+      textDirection: configuration.textDirection,
+    );
+  }
+
+  @override
+  String toString() {
+    return 'BoxPainter for $_decoration';
+  }
+}
diff --git a/lib/src/painting/box_fit.dart b/lib/src/painting/box_fit.dart
new file mode 100644
index 0000000..3d86cac
--- /dev/null
+++ b/lib/src/painting/box_fit.dart
@@ -0,0 +1,191 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+
+/// How a box should be inscribed into another box.
+///
+/// See also:
+///
+///  * [applyBoxFit], which applies the sizing semantics of these values (though
+///    not the alignment semantics).
+enum BoxFit {
+  /// Fill the target box by distorting the source's aspect ratio.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_fill.png)
+  fill,
+
+  /// As large as possible while still containing the source entirely within the
+  /// target box.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_contain.png)
+  contain,
+
+  /// As small as possible while still covering the entire target box.
+  ///
+  /// {@template flutter.painting.BoxFit.cover}
+  /// To actually clip the content, use `clipBehavior: Clip.hardEdge` alongside
+  /// this in a [FittedBox].
+  /// {@endtemplate}
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_cover.png)
+  cover,
+
+  /// Make sure the full width of the source is shown, regardless of
+  /// whether this means the source overflows the target box vertically.
+  ///
+  /// {@macro flutter.painting.BoxFit.cover}
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_fitWidth.png)
+  fitWidth,
+
+  /// Make sure the full height of the source is shown, regardless of
+  /// whether this means the source overflows the target box horizontally.
+  ///
+  /// {@macro flutter.painting.BoxFit.cover}
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_fitHeight.png)
+  fitHeight,
+
+  /// Align the source within the target box (by default, centering) and discard
+  /// any portions of the source that lie outside the box.
+  ///
+  /// The source image is not resized.
+  ///
+  /// {@macro flutter.painting.BoxFit.cover}
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_none.png)
+  none,
+
+  /// Align the source within the target box (by default, centering) and, if
+  /// necessary, scale the source down to ensure that the source fits within the
+  /// box.
+  ///
+  /// This is the same as `contain` if that would shrink the image, otherwise it
+  /// is the same as `none`.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_scaleDown.png)
+  scaleDown,
+}
+
+/// The pair of sizes returned by [applyBoxFit].
+@immutable
+class FittedSizes {
+  /// Creates an object to store a pair of sizes,
+  /// as would be returned by [applyBoxFit].
+  const FittedSizes(this.source, this.destination);
+
+  /// The size of the part of the input to show on the output.
+  final Size source;
+
+  /// The size of the part of the output on which to show the input.
+  final Size destination;
+}
+
+/// Apply a [BoxFit] value.
+///
+/// The arguments to this method, in addition to the [BoxFit] value to apply,
+/// are two sizes, ostensibly the sizes of an input box and an output box.
+/// Specifically, the `inputSize` argument gives the size of the complete source
+/// that is being fitted, and the `outputSize` gives the size of the rectangle
+/// into which the source is to be drawn.
+///
+/// This function then returns two sizes, combined into a single [FittedSizes]
+/// object.
+///
+/// The [FittedSizes.source] size is the subpart of the `inputSize` that is to
+/// be shown. If the entire input source is shown, then this will equal the
+/// `inputSize`, but if the input source is to be cropped down, this may be
+/// smaller.
+///
+/// The [FittedSizes.destination] size is the subpart of the `outputSize` in
+/// which to paint the (possibly cropped) source. If the
+/// [FittedSizes.destination] size is smaller than the `outputSize` then the
+/// source is being letterboxed (or pillarboxed).
+///
+/// This method does not express an opinion regarding the alignment of the
+/// source and destination sizes within the input and output rectangles.
+/// Typically they are centered (this is what [BoxDecoration] does, for
+/// instance, and is how [BoxFit] is defined). The [Alignment] class provides a
+/// convenience function, [Alignment.inscribe], for resolving the sizes to
+/// rects, as shown in the example below.
+///
+/// {@tool snippet}
+///
+/// This function paints a [dart:ui.Image] `image` onto the [Rect] `outputRect` on a
+/// [Canvas] `canvas`, using a [Paint] `paint`, applying the [BoxFit] algorithm
+/// `fit`:
+///
+/// ```dart
+/// void paintImage(ui.Image image, Rect outputRect, Canvas canvas, Paint paint, BoxFit fit) {
+///   final Size imageSize = Size(image.width.toDouble(), image.height.toDouble());
+///   final FittedSizes sizes = applyBoxFit(fit, imageSize, outputRect.size);
+///   final Rect inputSubrect = Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
+///   final Rect outputSubrect = Alignment.center.inscribe(sizes.destination, outputRect);
+///   canvas.drawImageRect(image, inputSubrect, outputSubrect, paint);
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [FittedBox], a widget that applies this algorithm to another widget.
+///  * [paintImage], a function that applies this algorithm to images for painting.
+///  * [DecoratedBox], [BoxDecoration], and [DecorationImage], which together
+///    provide access to [paintImage] at the widgets layer.
+FittedSizes applyBoxFit(BoxFit fit, Size inputSize, Size outputSize) {
+  if (inputSize.height <= 0.0 || inputSize.width <= 0.0 || outputSize.height <= 0.0 || outputSize.width <= 0.0)
+    return const FittedSizes(Size.zero, Size.zero);
+
+  Size sourceSize, destinationSize;
+  switch (fit) {
+    case BoxFit.fill:
+      sourceSize = inputSize;
+      destinationSize = outputSize;
+      break;
+    case BoxFit.contain:
+      sourceSize = inputSize;
+      if (outputSize.width / outputSize.height > sourceSize.width / sourceSize.height)
+        destinationSize = Size(sourceSize.width * outputSize.height / sourceSize.height, outputSize.height);
+      else
+        destinationSize = Size(outputSize.width, sourceSize.height * outputSize.width / sourceSize.width);
+      break;
+    case BoxFit.cover:
+      if (outputSize.width / outputSize.height > inputSize.width / inputSize.height) {
+        sourceSize = Size(inputSize.width, inputSize.width * outputSize.height / outputSize.width);
+      } else {
+        sourceSize = Size(inputSize.height * outputSize.width / outputSize.height, inputSize.height);
+      }
+      destinationSize = outputSize;
+      break;
+    case BoxFit.fitWidth:
+      sourceSize = Size(inputSize.width, inputSize.width * outputSize.height / outputSize.width);
+      destinationSize = Size(outputSize.width, sourceSize.height * outputSize.width / sourceSize.width);
+      break;
+    case BoxFit.fitHeight:
+      sourceSize = Size(inputSize.height * outputSize.width / outputSize.height, inputSize.height);
+      destinationSize = Size(sourceSize.width * outputSize.height / sourceSize.height, outputSize.height);
+      break;
+    case BoxFit.none:
+      sourceSize = Size(math.min(inputSize.width, outputSize.width),
+                            math.min(inputSize.height, outputSize.height));
+      destinationSize = sourceSize;
+      break;
+    case BoxFit.scaleDown:
+      sourceSize = inputSize;
+      destinationSize = inputSize;
+      final double aspectRatio = inputSize.width / inputSize.height;
+      if (destinationSize.height > outputSize.height)
+        destinationSize = Size(outputSize.height * aspectRatio, outputSize.height);
+      if (destinationSize.width > outputSize.width)
+        destinationSize = Size(outputSize.width, outputSize.width / aspectRatio);
+      break;
+  }
+  return FittedSizes(sourceSize, destinationSize);
+}
diff --git a/lib/src/painting/box_shadow.dart b/lib/src/painting/box_shadow.dart
new file mode 100644
index 0000000..4552e29
--- /dev/null
+++ b/lib/src/painting/box_shadow.dart
@@ -0,0 +1,134 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' as ui show Shadow, lerpDouble;
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'debug.dart';
+
+/// A shadow cast by a box.
+///
+/// [BoxShadow] can cast non-rectangular shadows if the box is non-rectangular
+/// (e.g., has a border radius or a circular shape).
+///
+/// This class is similar to CSS box-shadow.
+///
+/// See also:
+///
+///  * [Canvas.drawShadow], which is a more efficient way to draw shadows.
+///  * [PhysicalModel], a widget for showing shadows.
+///  * [kElevationToShadow], for some predefined shadows used in Material
+///    Design.
+///  * [Shadow], which is the parent class that lacks [spreadRadius].
+@immutable
+class BoxShadow extends ui.Shadow {
+  /// Creates a box shadow.
+  ///
+  /// By default, the shadow is solid black with zero [offset], [blurRadius],
+  /// and [spreadRadius].
+  const BoxShadow({
+    Color color = const Color(0xFF000000),
+    Offset offset = Offset.zero,
+    double blurRadius = 0.0,
+    this.spreadRadius = 0.0,
+  }) : super(color: color, offset: offset, blurRadius: blurRadius);
+
+  /// The amount the box should be inflated prior to applying the blur.
+  final double spreadRadius;
+
+  /// Create the [Paint] object that corresponds to this shadow description.
+  ///
+  /// The [offset] and [spreadRadius] are not represented in the [Paint] object.
+  /// To honor those as well, the shape should be inflated by [spreadRadius] pixels
+  /// in every direction and then translated by [offset] before being filled using
+  /// this [Paint].
+  @override
+  Paint toPaint() {
+    final Paint result = Paint()
+      ..color = color
+      ..maskFilter = MaskFilter.blur(BlurStyle.normal, blurSigma);
+    assert(() {
+      if (debugDisableShadows)
+        result.maskFilter = null;
+      return true;
+    }());
+    return result;
+  }
+
+  /// Returns a new box shadow with its offset, blurRadius, and spreadRadius scaled by the given factor.
+  @override
+  BoxShadow scale(double factor) {
+    return BoxShadow(
+      color: color,
+      offset: offset * factor,
+      blurRadius: blurRadius * factor,
+      spreadRadius: spreadRadius * factor,
+    );
+  }
+
+  /// Linearly interpolate between two box shadows.
+  ///
+  /// If either box shadow is null, this function linearly interpolates from a
+  /// a box shadow that matches the other box shadow in color but has a zero
+  /// offset and a zero blurRadius.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static BoxShadow? lerp(BoxShadow? a, BoxShadow? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b!.scale(t);
+    if (b == null)
+      return a.scale(1.0 - t);
+    return BoxShadow(
+      color: Color.lerp(a.color, b.color, t)!,
+      offset: Offset.lerp(a.offset, b.offset, t)!,
+      blurRadius: ui.lerpDouble(a.blurRadius, b.blurRadius, t)!,
+      spreadRadius: ui.lerpDouble(a.spreadRadius, b.spreadRadius, t)!,
+    );
+  }
+
+  /// Linearly interpolate between two lists of box shadows.
+  ///
+  /// If the lists differ in length, excess items are lerped with null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static List<BoxShadow>? lerpList(List<BoxShadow>? a, List<BoxShadow>? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    a ??= <BoxShadow>[];
+    b ??= <BoxShadow>[];
+    final int commonLength = math.min(a.length, b.length);
+    return <BoxShadow>[
+      for (int i = 0; i < commonLength; i += 1) BoxShadow.lerp(a[i], b[i], t)!,
+      for (int i = commonLength; i < a.length; i += 1) a[i].scale(1.0 - t),
+      for (int i = commonLength; i < b.length; i += 1) b[i].scale(t),
+    ];
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is BoxShadow
+        && other.color == color
+        && other.offset == offset
+        && other.blurRadius == blurRadius
+        && other.spreadRadius == spreadRadius;
+  }
+
+  @override
+  int get hashCode => hashValues(color, offset, blurRadius, spreadRadius);
+
+  @override
+  String toString() => 'BoxShadow($color, $offset, ${debugFormatDouble(blurRadius)}, ${debugFormatDouble(spreadRadius)})';
+}
diff --git a/lib/src/painting/circle_border.dart b/lib/src/painting/circle_border.dart
new file mode 100644
index 0000000..25c02cb
--- /dev/null
+++ b/lib/src/painting/circle_border.dart
@@ -0,0 +1,103 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'borders.dart';
+import 'edge_insets.dart';
+
+/// A border that fits a circle within the available space.
+///
+/// Typically used with [ShapeDecoration] to draw a circle.
+///
+/// The [dimensions] assume that the border is being used in a square space.
+/// When applied to a rectangular space, the border paints in the center of the
+/// rectangle.
+///
+/// See also:
+///
+///  * [BorderSide], which is used to describe each side of the box.
+///  * [Border], which, when used with [BoxDecoration], can also
+///    describe a circle.
+class CircleBorder extends OutlinedBorder {
+  /// Create a circle border.
+  ///
+  /// The [side] argument must not be null.
+  const CircleBorder({ BorderSide side = BorderSide.none }) : assert(side != null), super(side: side);
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    return EdgeInsets.all(side.width);
+  }
+
+  @override
+  ShapeBorder scale(double t) => CircleBorder(side: side.scale(t));
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    if (a is CircleBorder)
+      return CircleBorder(side: BorderSide.lerp(a.side, side, t));
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    if (b is CircleBorder)
+      return CircleBorder(side: BorderSide.lerp(side, b.side, t));
+    return super.lerpTo(b, t);
+  }
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()
+      ..addOval(Rect.fromCircle(
+        center: rect.center,
+        radius: math.max(0.0, rect.shortestSide / 2.0 - side.width),
+      ));
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()
+      ..addOval(Rect.fromCircle(
+        center: rect.center,
+        radius: rect.shortestSide / 2.0,
+      ));
+  }
+
+  @override
+  CircleBorder copyWith({ BorderSide? side }) {
+    return CircleBorder(side: side ?? this.side);
+  }
+
+  @override
+  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
+    switch (side.style) {
+      case BorderStyle.none:
+        break;
+      case BorderStyle.solid:
+        canvas.drawCircle(rect.center, (rect.shortestSide - side.width) / 2.0, side.toPaint());
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is CircleBorder
+        && other.side == side;
+  }
+
+  @override
+  int get hashCode => side.hashCode;
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'CircleBorder')}($side)';
+  }
+}
diff --git a/lib/src/painting/clip.dart b/lib/src/painting/clip.dart
new file mode 100644
index 0000000..d52d9c3
--- /dev/null
+++ b/lib/src/painting/clip.dart
@@ -0,0 +1,60 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' show Canvas, Clip, Path, Paint, Rect, RRect;
+
+/// Clip utilities used by [PaintingContext].
+abstract class ClipContext {
+  /// The canvas on which to paint.
+  Canvas get canvas;
+
+  void _clipAndPaint(void canvasClipCall(bool doAntiAlias), Clip clipBehavior, Rect bounds, void painter()) {
+    assert(canvasClipCall != null);
+    canvas.save();
+    switch (clipBehavior) {
+      case Clip.none:
+        break;
+      case Clip.hardEdge:
+        canvasClipCall(false);
+        break;
+      case Clip.antiAlias:
+        canvasClipCall(true);
+        break;
+      case Clip.antiAliasWithSaveLayer:
+        canvasClipCall(true);
+        canvas.saveLayer(bounds, Paint());
+        break;
+    }
+    painter();
+    if (clipBehavior == Clip.antiAliasWithSaveLayer) {
+      canvas.restore();
+    }
+    canvas.restore();
+  }
+
+  /// Clip [canvas] with [Path] according to [Clip] and then paint. [canvas] is
+  /// restored to the pre-clip status afterwards.
+  ///
+  /// `bounds` is the saveLayer bounds used for [Clip.antiAliasWithSaveLayer].
+  void clipPathAndPaint(Path path, Clip clipBehavior, Rect bounds, void painter()) {
+    _clipAndPaint((bool doAntiAias) => canvas.clipPath(path, doAntiAlias: doAntiAias), clipBehavior, bounds, painter);
+  }
+
+  /// Clip [canvas] with [Path] according to `rrect` and then paint. [canvas] is
+  /// restored to the pre-clip status afterwards.
+  ///
+  /// `bounds` is the saveLayer bounds used for [Clip.antiAliasWithSaveLayer].
+  void clipRRectAndPaint(RRect rrect, Clip clipBehavior, Rect bounds, void painter()) {
+    _clipAndPaint((bool doAntiAias) => canvas.clipRRect(rrect, doAntiAlias: doAntiAias), clipBehavior, bounds, painter);
+  }
+
+  /// Clip [canvas] with [Path] according to `rect` and then paint. [canvas] is
+  /// restored to the pre-clip status afterwards.
+  ///
+  /// `bounds` is the saveLayer bounds used for [Clip.antiAliasWithSaveLayer].
+  void clipRectAndPaint(Rect rect, Clip clipBehavior, Rect bounds, void painter()) {
+    _clipAndPaint((bool doAntiAias) => canvas.clipRect(rect, doAntiAlias: doAntiAias), clipBehavior, bounds, painter);
+  }
+}
diff --git a/lib/src/painting/colors.dart b/lib/src/painting/colors.dart
new file mode 100644
index 0000000..22ba3f8
--- /dev/null
+++ b/lib/src/painting/colors.dart
@@ -0,0 +1,498 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show Color, lerpDouble, hashValues;
+
+import 'package:flute/foundation.dart';
+
+double _getHue(double red, double green, double blue, double max, double delta) {
+  late double hue;
+  if (max == 0.0) {
+    hue = 0.0;
+  } else if (max == red) {
+    hue = 60.0 * (((green - blue) / delta) % 6);
+  } else if (max == green) {
+    hue = 60.0 * (((blue - red) / delta) + 2);
+  } else if (max == blue) {
+    hue = 60.0 * (((red - green) / delta) + 4);
+  }
+
+  /// Set hue to 0.0 when red == green == blue.
+  hue = hue.isNaN ? 0.0 : hue;
+  return hue;
+}
+
+Color _colorFromHue(
+  double alpha,
+  double hue,
+  double chroma,
+  double secondary,
+  double match,
+) {
+  double red;
+  double green;
+  double blue;
+  if (hue < 60.0) {
+    red = chroma;
+    green = secondary;
+    blue = 0.0;
+  } else if (hue < 120.0) {
+    red = secondary;
+    green = chroma;
+    blue = 0.0;
+  } else if (hue < 180.0) {
+    red = 0.0;
+    green = chroma;
+    blue = secondary;
+  } else if (hue < 240.0) {
+    red = 0.0;
+    green = secondary;
+    blue = chroma;
+  } else if (hue < 300.0) {
+    red = secondary;
+    green = 0.0;
+    blue = chroma;
+  } else {
+    red = chroma;
+    green = 0.0;
+    blue = secondary;
+  }
+  return Color.fromARGB((alpha * 0xFF).round(), ((red + match) * 0xFF).round(), ((green + match) * 0xFF).round(), ((blue + match) * 0xFF).round());
+}
+
+/// A color represented using [alpha], [hue], [saturation], and [value].
+///
+/// An [HSVColor] is represented in a parameter space that's based on human
+/// perception of color in pigments (e.g. paint and printer's ink). The
+/// representation is useful for some color computations (e.g. rotating the hue
+/// through the colors), because interpolation and picking of
+/// colors as red, green, and blue channels doesn't always produce intuitive
+/// results.
+///
+/// The HSV color space models the way that different pigments are perceived
+/// when mixed. The hue describes which pigment is used, the saturation
+/// describes which shade of the pigment, and the value resembles mixing the
+/// pigment with different amounts of black or white pigment.
+///
+/// See also:
+///
+///  * [HSLColor], a color that uses a color space based on human perception of
+///    colored light.
+///  * [HSV and HSL](https://en.wikipedia.org/wiki/HSL_and_HSV) Wikipedia
+///    article, which this implementation is based upon.
+@immutable
+class HSVColor {
+  /// Creates a color.
+  ///
+  /// All the arguments must not be null and be in their respective ranges. See
+  /// the fields for each parameter for a description of their ranges.
+  const HSVColor.fromAHSV(this.alpha, this.hue, this.saturation, this.value)
+    : assert(alpha != null),
+      assert(hue != null),
+      assert(saturation != null),
+      assert(value != null),
+      assert(alpha >= 0.0),
+      assert(alpha <= 1.0),
+      assert(hue >= 0.0),
+      assert(hue <= 360.0),
+      assert(saturation >= 0.0),
+      assert(saturation <= 1.0),
+      assert(value >= 0.0),
+      assert(value <= 1.0);
+
+  /// Creates an [HSVColor] from an RGB [Color].
+  ///
+  /// This constructor does not necessarily round-trip with [toColor] because
+  /// of floating point imprecision.
+  factory HSVColor.fromColor(Color color) {
+    final double red = color.red / 0xFF;
+    final double green = color.green / 0xFF;
+    final double blue = color.blue / 0xFF;
+
+    final double max = math.max(red, math.max(green, blue));
+    final double min = math.min(red, math.min(green, blue));
+    final double delta = max - min;
+
+    final double alpha = color.alpha / 0xFF;
+    final double hue = _getHue(red, green, blue, max, delta);
+    final double saturation = max == 0.0 ? 0.0 : delta / max;
+
+    return HSVColor.fromAHSV(alpha, hue, saturation, max);
+  }
+
+  /// Alpha, from 0.0 to 1.0. The describes the transparency of the color.
+  /// A value of 0.0 is fully transparent, and 1.0 is fully opaque.
+  final double alpha;
+
+  /// Hue, from 0.0 to 360.0. Describes which color of the spectrum is
+  /// represented. A value of 0.0 represents red, as does 360.0. Values in
+  /// between go through all the hues representable in RGB. You can think of
+  /// this as selecting which pigment will be added to a color.
+  final double hue;
+
+  /// Saturation, from 0.0 to 1.0. This describes how colorful the color is.
+  /// 0.0 implies a shade of grey (i.e. no pigment), and 1.0 implies a color as
+  /// vibrant as that hue gets. You can think of this as the equivalent of
+  /// how much of a pigment is added.
+  final double saturation;
+
+  /// Value, from 0.0 to 1.0. The "value" of a color that, in this context,
+  /// describes how bright a color is. A value of 0.0 indicates black, and 1.0
+  /// indicates full intensity color. You can think of this as the equivalent of
+  /// removing black from the color as value increases.
+  final double value;
+
+  /// Returns a copy of this color with the [alpha] parameter replaced with the
+  /// given value.
+  HSVColor withAlpha(double alpha) {
+    return HSVColor.fromAHSV(alpha, hue, saturation, value);
+  }
+
+  /// Returns a copy of this color with the [hue] parameter replaced with the
+  /// given value.
+  HSVColor withHue(double hue) {
+    return HSVColor.fromAHSV(alpha, hue, saturation, value);
+  }
+
+  /// Returns a copy of this color with the [saturation] parameter replaced with
+  /// the given value.
+  HSVColor withSaturation(double saturation) {
+    return HSVColor.fromAHSV(alpha, hue, saturation, value);
+  }
+
+  /// Returns a copy of this color with the [value] parameter replaced with the
+  /// given value.
+  HSVColor withValue(double value) {
+    return HSVColor.fromAHSV(alpha, hue, saturation, value);
+  }
+
+  /// Returns this color in RGB.
+  Color toColor() {
+    final double chroma = saturation * value;
+    final double secondary = chroma * (1.0 - (((hue / 60.0) % 2.0) - 1.0).abs());
+    final double match = value - chroma;
+
+    return _colorFromHue(alpha, hue, chroma, secondary, match);
+  }
+
+  HSVColor _scaleAlpha(double factor) {
+    return withAlpha(alpha * factor);
+  }
+
+  /// Linearly interpolate between two HSVColors.
+  ///
+  /// The colors are interpolated by interpolating the [alpha], [hue],
+  /// [saturation], and [value] channels separately, which usually leads to a
+  /// more pleasing effect than [Color.lerp] (which interpolates the red, green,
+  /// and blue channels separately).
+  ///
+  /// If either color is null, this function linearly interpolates from a
+  /// transparent instance of the other color. This is usually preferable to
+  /// interpolating from [Colors.transparent] (`const Color(0x00000000)`) since
+  /// that will interpolate from a transparent red and cycle through the hues to
+  /// match the target color, regardless of what that color's hue is.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  ///
+  /// Values outside of the valid range for each channel will be clamped.
+  static HSVColor? lerp(HSVColor? a, HSVColor? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b!._scaleAlpha(t);
+    if (b == null)
+      return a._scaleAlpha(1.0 - t);
+    return HSVColor.fromAHSV(
+      lerpDouble(a.alpha, b.alpha, t)!.clamp(0.0, 1.0),
+      lerpDouble(a.hue, b.hue, t)! % 360.0,
+      lerpDouble(a.saturation, b.saturation, t)!.clamp(0.0, 1.0),
+      lerpDouble(a.value, b.value, t)!.clamp(0.0, 1.0),
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    return other is HSVColor
+        && other.alpha == alpha
+        && other.hue == hue
+        && other.saturation == saturation
+        && other.value == value;
+  }
+
+  @override
+  int get hashCode => hashValues(alpha, hue, saturation, value);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'HSVColor')}($alpha, $hue, $saturation, $value)';
+}
+
+/// A color represented using [alpha], [hue], [saturation], and [lightness].
+///
+/// An [HSLColor] is represented in a parameter space that's based up human
+/// perception of colored light. The representation is useful for some color
+/// computations (e.g., combining colors of light), because interpolation and
+/// picking of colors as red, green, and blue channels doesn't always produce
+/// intuitive results.
+///
+/// HSL is a perceptual color model, placing fully saturated colors around a
+/// circle (conceptually) at a lightness of ​0.5, with a lightness of 0.0 being
+/// completely black, and a lightness of 1.0 being completely white. As the
+/// lightness increases or decreases from 0.5, the apparent saturation decreases
+/// proportionally (even though the [saturation] parameter hasn't changed).
+///
+/// See also:
+///
+///  * [HSVColor], a color that uses a color space based on human perception of
+///    pigments (e.g. paint and printer's ink).
+///  * [HSV and HSL](https://en.wikipedia.org/wiki/HSL_and_HSV) Wikipedia
+///    article, which this implementation is based upon.
+@immutable
+class HSLColor {
+  /// Creates a color.
+  ///
+  /// All the arguments must not be null and be in their respective ranges. See
+  /// the fields for each parameter for a description of their ranges.
+  const HSLColor.fromAHSL(this.alpha, this.hue, this.saturation, this.lightness)
+    : assert(alpha != null),
+      assert(hue != null),
+      assert(saturation != null),
+      assert(lightness != null),
+      assert(alpha >= 0.0),
+      assert(alpha <= 1.0),
+      assert(hue >= 0.0),
+      assert(hue <= 360.0),
+      assert(saturation >= 0.0),
+      assert(saturation <= 1.0),
+      assert(lightness >= 0.0),
+      assert(lightness <= 1.0);
+
+  /// Creates an [HSLColor] from an RGB [Color].
+  ///
+  /// This constructor does not necessarily round-trip with [toColor] because
+  /// of floating point imprecision.
+  factory HSLColor.fromColor(Color color) {
+    final double red = color.red / 0xFF;
+    final double green = color.green / 0xFF;
+    final double blue = color.blue / 0xFF;
+
+    final double max = math.max(red, math.max(green, blue));
+    final double min = math.min(red, math.min(green, blue));
+    final double delta = max - min;
+
+    final double alpha = color.alpha / 0xFF;
+    final double hue = _getHue(red, green, blue, max, delta);
+    final double lightness = (max + min) / 2.0;
+    // Saturation can exceed 1.0 with rounding errors, so clamp it.
+    final double saturation = lightness == 1.0
+      ? 0.0
+      : ((delta / (1.0 - (2.0 * lightness - 1.0).abs())).clamp(0.0, 1.0));
+    return HSLColor.fromAHSL(alpha, hue, saturation, lightness);
+  }
+
+  /// Alpha, from 0.0 to 1.0. The describes the transparency of the color.
+  /// A value of 0.0 is fully transparent, and 1.0 is fully opaque.
+  final double alpha;
+
+  /// Hue, from 0.0 to 360.0. Describes which color of the spectrum is
+  /// represented. A value of 0.0 represents red, as does 360.0. Values in
+  /// between go through all the hues representable in RGB. You can think of
+  /// this as selecting which color filter is placed over a light.
+  final double hue;
+
+  /// Saturation, from 0.0 to 1.0. This describes how colorful the color is.
+  /// 0.0 implies a shade of grey (i.e. no pigment), and 1.0 implies a color as
+  /// vibrant as that hue gets. You can think of this as the purity of the
+  /// color filter over the light.
+  final double saturation;
+
+  /// Lightness, from 0.0 to 1.0. The lightness of a color describes how bright
+  /// a color is. A value of 0.0 indicates black, and 1.0 indicates white. You
+  /// can think of this as the intensity of the light behind the filter. As the
+  /// lightness approaches 0.5, the colors get brighter and appear more
+  /// saturated, and over 0.5, the colors start to become less saturated and
+  /// approach white at 1.0.
+  final double lightness;
+
+  /// Returns a copy of this color with the alpha parameter replaced with the
+  /// given value.
+  HSLColor withAlpha(double alpha) {
+    return HSLColor.fromAHSL(alpha, hue, saturation, lightness);
+  }
+
+  /// Returns a copy of this color with the [hue] parameter replaced with the
+  /// given value.
+  HSLColor withHue(double hue) {
+    return HSLColor.fromAHSL(alpha, hue, saturation, lightness);
+  }
+
+  /// Returns a copy of this color with the [saturation] parameter replaced with
+  /// the given value.
+  HSLColor withSaturation(double saturation) {
+    return HSLColor.fromAHSL(alpha, hue, saturation, lightness);
+  }
+
+  /// Returns a copy of this color with the [lightness] parameter replaced with
+  /// the given value.
+  HSLColor withLightness(double lightness) {
+    return HSLColor.fromAHSL(alpha, hue, saturation, lightness);
+  }
+
+  /// Returns this HSL color in RGB.
+  Color toColor() {
+    final double chroma = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation;
+    final double secondary = chroma * (1.0 - (((hue / 60.0) % 2.0) - 1.0).abs());
+    final double match = lightness - chroma / 2.0;
+
+    return _colorFromHue(alpha, hue, chroma, secondary, match);
+  }
+
+  HSLColor _scaleAlpha(double factor) {
+    return withAlpha(alpha * factor);
+  }
+
+  /// Linearly interpolate between two HSLColors.
+  ///
+  /// The colors are interpolated by interpolating the [alpha], [hue],
+  /// [saturation], and [lightness] channels separately, which usually leads to
+  /// a more pleasing effect than [Color.lerp] (which interpolates the red,
+  /// green, and blue channels separately).
+  ///
+  /// If either color is null, this function linearly interpolates from a
+  /// transparent instance of the other color. This is usually preferable to
+  /// interpolating from [Colors.transparent] (`const Color(0x00000000)`) since
+  /// that will interpolate from a transparent red and cycle through the hues to
+  /// match the target color, regardless of what that color's hue is.
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values between them
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+  /// 1.0, so negative values and values greater than 1.0 are valid
+  /// (and can easily be generated by curves such as [Curves.elasticInOut]).
+  ///
+  /// Values outside of the valid range for each channel will be clamped.
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  static HSLColor? lerp(HSLColor? a, HSLColor? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b!._scaleAlpha(t);
+    if (b == null)
+      return a._scaleAlpha(1.0 - t);
+    return HSLColor.fromAHSL(
+      lerpDouble(a.alpha, b.alpha, t)!.clamp(0.0, 1.0),
+      lerpDouble(a.hue, b.hue, t)! % 360.0,
+      lerpDouble(a.saturation, b.saturation, t)!.clamp(0.0, 1.0),
+      lerpDouble(a.lightness, b.lightness, t)!.clamp(0.0, 1.0),
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    return other is HSLColor
+        && other.alpha == alpha
+        && other.hue == hue
+        && other.saturation == saturation
+        && other.lightness == lightness;
+  }
+
+  @override
+  int get hashCode => hashValues(alpha, hue, saturation, lightness);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'HSLColor')}($alpha, $hue, $saturation, $lightness)';
+}
+
+/// A color that has a small table of related colors called a "swatch".
+///
+/// The table is indexed by values of type `T`.
+///
+/// See also:
+///
+///  * [MaterialColor] and [MaterialAccentColor], which define material design
+///    primary and accent color swatches.
+///  * [material.Colors], which defines all of the standard material design
+///    colors.
+@immutable
+class ColorSwatch<T> extends Color {
+  /// Creates a color that has a small table of related colors called a "swatch".
+  ///
+  /// The `primary` argument should be the 32 bit ARGB value of one of the
+  /// values in the swatch, as would be passed to the [new Color] constructor
+  /// for that same color, and as is exposed by [value]. (This is distinct from
+  /// the specific index of the color in the swatch.)
+  const ColorSwatch(int primary, this._swatch) : super(primary);
+
+  @protected
+  final Map<T, Color> _swatch;
+
+  /// Returns an element of the swatch table.
+  Color? operator [](T index) => _swatch[index];
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return super == other
+        && other is ColorSwatch<T>
+        && mapEquals<T, Color>(other._swatch, _swatch);
+  }
+
+  @override
+  int get hashCode => hashValues(runtimeType, value, _swatch);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'ColorSwatch')}(primary value: ${super.toString()})';
+}
+
+/// [DiagnosticsProperty] that has an [Color] as value.
+class ColorProperty extends DiagnosticsProperty<Color> {
+  /// Create a diagnostics property for [Color].
+  ///
+  /// The [showName], [style], and [level] arguments must not be null.
+  ColorProperty(
+    String name,
+    Color? value, {
+    bool showName = true,
+    Object? defaultValue = kNoDefaultValue,
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(showName != null),
+       assert(style != null),
+       assert(level != null),
+       super(name, value,
+         defaultValue: defaultValue,
+         showName: showName,
+         style: style,
+         level: level,
+       );
+
+  @override
+  Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
+    final Map<String, Object?> json = super.toJsonMap(delegate);
+    if (value != null) {
+      json['valueProperties'] = <String, Object>{
+        'red': value!.red,
+        'green': value!.green,
+        'blue': value!.blue,
+        'alpha': value!.alpha,
+      };
+    }
+    return json;
+  }
+}
diff --git a/lib/src/painting/continuous_rectangle_border.dart b/lib/src/painting/continuous_rectangle_border.dart
new file mode 100644
index 0000000..6b645b3
--- /dev/null
+++ b/lib/src/painting/continuous_rectangle_border.dart
@@ -0,0 +1,175 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'border_radius.dart';
+import 'borders.dart';
+import 'edge_insets.dart';
+
+/// A rectangular border with smooth continuous transitions between the straight
+/// sides and the rounded corners.
+///
+/// {@tool snippet}
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Material(
+///     shape: ContinuousRectangleBorder(
+///       borderRadius: BorderRadius.circular(28.0),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [RoundedRectangleBorder] Which creates rectangles with rounded corners,
+///    however its straight sides change into a rounded corner with a circular
+///    radius in a step function instead of gradually like the
+///    [ContinuousRectangleBorder].
+class ContinuousRectangleBorder extends OutlinedBorder {
+  /// The arguments must not be null.
+  const ContinuousRectangleBorder({
+    BorderSide side = BorderSide.none,
+    this.borderRadius = BorderRadius.zero,
+  }) : assert(side != null),
+       assert(borderRadius != null),
+       super(side: side);
+
+  /// The radius for each corner.
+  ///
+  /// Negative radius values are clamped to 0.0 by [getInnerPath] and
+  /// [getOuterPath].
+  final BorderRadiusGeometry borderRadius;
+
+   @override
+  EdgeInsetsGeometry get dimensions => EdgeInsets.all(side.width);
+
+  @override
+  ShapeBorder scale(double t) {
+    return ContinuousRectangleBorder(
+      side: side.scale(t),
+      borderRadius: borderRadius * t,
+    );
+  }
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    assert(t != null);
+    if (a is ContinuousRectangleBorder) {
+      return ContinuousRectangleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
+      );
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    assert(t != null);
+    if (b is ContinuousRectangleBorder) {
+      return ContinuousRectangleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
+      );
+    }
+    return super.lerpTo(b, t);
+  }
+
+  double _clampToShortest(RRect rrect, double value) {
+    return value > rrect.shortestSide ? rrect.shortestSide : value;
+  }
+
+  Path _getPath(RRect rrect) {
+    final double left = rrect.left;
+    final double right = rrect.right;
+    final double top = rrect.top;
+    final double bottom = rrect.bottom;
+    //  Radii will be clamped to the value of the shortest side
+    /// of [rrect] to avoid strange tie-fighter shapes.
+    final double tlRadiusX =
+      math.max(0.0, _clampToShortest(rrect, rrect.tlRadiusX));
+    final double tlRadiusY =
+      math.max(0.0, _clampToShortest(rrect, rrect.tlRadiusY));
+    final double trRadiusX =
+      math.max(0.0, _clampToShortest(rrect, rrect.trRadiusX));
+    final double trRadiusY =
+      math.max(0.0, _clampToShortest(rrect, rrect.trRadiusY));
+    final double blRadiusX =
+      math.max(0.0, _clampToShortest(rrect, rrect.blRadiusX));
+    final double blRadiusY =
+      math.max(0.0, _clampToShortest(rrect, rrect.blRadiusY));
+    final double brRadiusX =
+      math.max(0.0, _clampToShortest(rrect, rrect.brRadiusX));
+    final double brRadiusY =
+      math.max(0.0, _clampToShortest(rrect, rrect.brRadiusY));
+
+    return Path()
+      ..moveTo(left, top + tlRadiusX)
+      ..cubicTo(left, top, left, top, left + tlRadiusY, top)
+      ..lineTo(right - trRadiusX, top)
+      ..cubicTo(right, top, right, top, right, top + trRadiusY)
+      ..lineTo(right, bottom - brRadiusX)
+      ..cubicTo(right, bottom, right, bottom, right - brRadiusY, bottom)
+      ..lineTo(left + blRadiusX, bottom)
+      ..cubicTo(left, bottom, left, bottom, left, bottom - blRadiusY)
+      ..close();
+  }
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    return _getPath(borderRadius.resolve(textDirection).toRRect(rect).deflate(side.width));
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    return _getPath(borderRadius.resolve(textDirection).toRRect(rect));
+  }
+
+  @override
+  ContinuousRectangleBorder copyWith({ BorderSide? side, BorderRadius? borderRadius }) {
+    return ContinuousRectangleBorder(
+      side: side ?? this.side,
+      borderRadius: borderRadius ?? this.borderRadius,
+    );
+  }
+
+  @override
+  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
+    if (rect.isEmpty)
+      return;
+    switch (side.style) {
+      case BorderStyle.none:
+        break;
+      case BorderStyle.solid:
+        final Path path = getOuterPath(rect, textDirection: textDirection);
+        final Paint paint = side.toPaint();
+        canvas.drawPath(path, paint);
+        break;
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ContinuousRectangleBorder
+        && other.side == side
+        && other.borderRadius == borderRadius;
+  }
+
+  @override
+  int get hashCode => hashValues(side, borderRadius);
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'ContinuousRectangleBorder')}($side, $borderRadius)';
+  }
+}
diff --git a/lib/src/painting/debug.dart b/lib/src/painting/debug.dart
new file mode 100644
index 0000000..76058de
--- /dev/null
+++ b/lib/src/painting/debug.dart
@@ -0,0 +1,189 @@
+// Copyright 2014 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.
+
+
+import 'dart:io';
+import 'package:flute/ui.dart' show Size, hashValues;
+
+import 'package:flute/foundation.dart';
+
+/// Whether to replace all shadows with solid color blocks.
+///
+/// This is useful when writing golden file tests (see [matchesGoldenFile]) since
+/// the rendering of shadows is not guaranteed to be pixel-for-pixel identical from
+/// version to version (or even from run to run).
+bool debugDisableShadows = false;
+
+/// Signature for a method that returns an [HttpClient].
+///
+/// Used by [debugNetworkImageHttpClientProvider].
+typedef HttpClientProvider = HttpClient Function();
+
+/// Provider from which [NetworkImage] will get its [HttpClient] in debug builds.
+///
+/// If this value is unset, [NetworkImage] will use its own internally-managed
+/// [HttpClient].
+///
+/// This setting can be overridden for testing to ensure that each test receives
+/// a mock client that hasn't been affected by other tests.
+///
+/// This value is ignored in non-debug builds.
+HttpClientProvider? debugNetworkImageHttpClientProvider;
+
+/// Called when the framework is about to paint an [Image] to a [Canvas] with an
+/// [ImageSizeInfo] that contains the decoded size of the image as well as its
+/// output size.
+///
+/// See: [debugOnPaintImage].
+typedef PaintImageCallback = void Function(ImageSizeInfo);
+
+/// Tracks the bytes used by a [dart:ui.Image] compared to the bytes needed to
+/// paint that image without scaling it.
+@immutable
+class ImageSizeInfo {
+  /// Creates an object to track the backing size of a [dart:ui.Image] compared
+  /// to its display size on a [Canvas].
+  ///
+  /// This class is used by the framework when it paints an image to a canvas
+  /// to report to `dart:developer`'s [postEvent], as well as to the
+  /// [debugOnPaintImage] callback if it is set.
+  const ImageSizeInfo({this.source, this.displaySize, required this.imageSize});
+
+  /// A unique identifier for this image, for example its asset path or network
+  /// URL.
+  final String? source;
+
+  /// The size of the area the image will be rendered in.
+  final Size? displaySize;
+
+  /// The size the image has been decoded to.
+  final Size imageSize;
+
+  /// The number of bytes needed to render the image without scaling it.
+  int get displaySizeInBytes => _sizeToBytes(displaySize!);
+
+  /// The number of bytes used by the image in memory.
+  int get decodedSizeInBytes => _sizeToBytes(imageSize);
+
+  int _sizeToBytes(Size size) {
+    // Assume 4 bytes per pixel and that mipmapping will be used, which adds
+    // 4/3.
+    return (size.width * size.height * 4 * (4/3)).toInt();
+  }
+
+  /// Returns a JSON encodable representation of this object.
+  Map<String, Object?> toJson() {
+    return <String, Object?>{
+      'source': source,
+      if (displaySize != null)
+        'displaySize': <String, Object?>{
+          'width': displaySize!.width,
+          'height': displaySize!.height,
+        },
+      'imageSize': <String, Object?>{
+        'width': imageSize.width,
+        'height': imageSize.height,
+      },
+      'displaySizeInBytes': displaySizeInBytes,
+      'decodedSizeInBytes': decodedSizeInBytes,
+    };
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is ImageSizeInfo
+        && other.source == source
+        && other.imageSize == imageSize
+        && other.displaySize == displaySize;
+  }
+
+  @override
+  int get hashCode => hashValues(source, displaySize, imageSize);
+
+  @override
+  String toString() => 'ImageSizeInfo($source, imageSize: $imageSize, displaySize: $displaySize)';
+}
+
+/// If not null, called when the framework is about to paint an [Image] to a
+/// [Canvas] with an [ImageSizeInfo] that contains the decoded size of the
+/// image as well as its output size.
+///
+/// A test can use this callback to detect if images under test are being
+/// rendered with the appropriate cache dimensions.
+///
+/// For example, if a 100x100 image is decoded it takes roughly 53kb in memory
+/// (including mipmapping overhead). If it is only ever displayed at 50x50, it
+/// would take only 13kb if the cacheHeight/cacheWidth parameters had been
+/// specified at that size. This problem becomes more serious for larger
+/// images, such as a high resolution image from a 12MP camera, which would be
+/// 64mb when decoded.
+///
+/// When using this callback, developers should consider whether the image will
+/// be panned or scaled up in the application, how many images are being
+/// displayed, and whether the application will run on multiple devices with
+/// different resolutions and memory capacities. For example, it should be fine
+/// to have an image that animates from thumbnail size to full screen be at
+/// a higher resolution while animating, but it would be problematic to have
+/// a grid or list of such thumbnails all be at the full resolution at the same
+/// time.
+PaintImageCallback? debugOnPaintImage;
+
+/// If true, the framework will color invert and horizontally flip images that
+/// have been decoded to a size taking at least [debugImageOverheadAllowance]
+/// bytes more than necessary.
+///
+/// It will also call [FlutterError.reportError] with information about the
+/// image's decoded size and its display size, which can be used resize the
+/// asset before shipping it, apply `cacheHeight` or `cacheWidth` parameters, or
+/// directly use a [ResizeImage]. Whenever possible, resizing the image asset
+/// itself should be preferred, to avoid unnecessary network traffic, disk space
+/// usage, and other memory overhead incurred during decoding.
+///
+/// Developers using this flag should test their application on appropriate
+/// devices and display sizes for their expected deployment targets when using
+/// these parameters. For example, an application that responsively resizes
+/// images for a desktop and mobile layout should avoid decoding all images at
+/// sizes appropriate for mobile when on desktop. Applications should also avoid
+/// animating these parameters, as each change will result in a newly decoded
+/// image. For example, an image that always grows into view should decode only
+/// at its largest size, whereas an image that normally is a thumbnail and then
+/// pops into view should be decoded at its smallest size for the thumbnail and
+/// the largest size when needed.
+///
+/// This has no effect unless asserts are enabled.
+bool debugInvertOversizedImages = false;
+
+/// The number of bytes an image must use before it triggers inversion when
+/// [debugInvertOversizedImages] is true.
+///
+/// Default is 1024 (1kb).
+int debugImageOverheadAllowance = 1024;
+
+/// Returns true if none of the painting library debug variables have been changed.
+///
+/// This function is used by the test framework to ensure that debug variables
+/// haven't been inadvertently changed.
+///
+/// See [the painting library](painting/painting-library.html) for a complete
+/// list.
+///
+/// The `debugDisableShadowsOverride` argument can be provided to override
+/// the expected value for [debugDisableShadows]. (This exists because the
+/// test framework itself overrides this value in some cases.)
+bool debugAssertAllPaintingVarsUnset(String reason, { bool debugDisableShadowsOverride = false }) {
+  assert(() {
+    if (debugDisableShadows != debugDisableShadowsOverride ||
+        debugNetworkImageHttpClientProvider != null ||
+        debugOnPaintImage != null ||
+        debugInvertOversizedImages == true ||
+        debugImageOverheadAllowance != 1024) {
+      throw FlutterError(reason);
+    }
+    return true;
+  }());
+  return true;
+}
diff --git a/lib/src/painting/decoration.dart b/lib/src/painting/decoration.dart
new file mode 100644
index 0000000..d6dc4f2
--- /dev/null
+++ b/lib/src/painting/decoration.dart
@@ -0,0 +1,241 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'edge_insets.dart';
+import 'image_provider.dart';
+
+// This group of classes is intended for painting in cartesian coordinates.
+
+/// A description of a box decoration (a decoration applied to a [Rect]).
+///
+/// This class presents the abstract interface for all decorations.
+/// See [BoxDecoration] for a concrete example.
+///
+/// To actually paint a [Decoration], use the [createBoxPainter]
+/// method to obtain a [BoxPainter]. [Decoration] objects can be
+/// shared between boxes; [BoxPainter] objects can cache resources to
+/// make painting on a particular surface faster.
+@immutable
+abstract class Decoration with Diagnosticable {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const Decoration();
+
+  @override
+  String toStringShort() => objectRuntimeType(this, 'Decoration');
+
+  /// In checked mode, throws an exception if the object is not in a
+  /// valid configuration. Otherwise, returns true.
+  ///
+  /// This is intended to be used as follows:
+  /// ```dart
+  /// assert(myDecoration.debugAssertIsValid());
+  /// ```
+  bool debugAssertIsValid() => true;
+
+  /// Returns the insets to apply when using this decoration on a box
+  /// that has contents, so that the contents do not overlap the edges
+  /// of the decoration. For example, if the decoration draws a frame
+  /// around its edge, the padding would return the distance by which
+  /// to inset the children so as to not overlap the frame.
+  ///
+  /// This only works for decorations that have absolute sizes. If the padding
+  /// needed would change based on the size at which the decoration is drawn,
+  /// then this will return incorrect padding values.
+  ///
+  /// For example, when a [BoxDecoration] has [BoxShape.circle], the padding
+  /// does not take into account that the circle is drawn in the center of the
+  /// box regardless of the ratio of the box; it does not provide the extra
+  /// padding that is implied by changing the ratio.
+  ///
+  /// The value returned by this getter must be resolved (using
+  /// [EdgeInsetsGeometry.resolve] to obtain an absolute [EdgeInsets]. (For
+  /// example, [BorderDirectional] will return an [EdgeInsetsDirectional] for
+  /// its [padding].)
+  EdgeInsetsGeometry? get padding => EdgeInsets.zero;
+
+  /// Whether this decoration is complex enough to benefit from caching its painting.
+  bool get isComplex => false;
+
+  /// Linearly interpolates from another [Decoration] (which may be of a
+  /// different class) to `this`.
+  ///
+  /// When implementing this method in subclasses, return null if this class
+  /// cannot interpolate from `a`. In that case, [lerp] will try `a`'s [lerpTo]
+  /// method instead.
+  ///
+  /// Supporting interpolating from null is recommended as the [Decoration.lerp]
+  /// method uses this as a fallback when two classes can't interpolate between
+  /// each other.
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `this` (or something equivalent to `this`), and values in
+  /// between meaning that the interpolation is at the relevant point on the
+  /// timeline between `a` and `this`. The interpolation can be extrapolated
+  /// beyond 0.0 and 1.0, so negative values and values greater than 1.0 are
+  /// valid (and can easily be generated by curves such as
+  /// [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  ///
+  /// Instead of calling this directly, use [Decoration.lerp].
+  @protected
+  Decoration? lerpFrom(Decoration? a, double t) => null;
+
+  /// Linearly interpolates from `this` to another [Decoration] (which may be of
+  /// a different class).
+  ///
+  /// This is called if `b`'s [lerpTo] did not know how to handle this class.
+  ///
+  /// When implementing this method in subclasses, return null if this class
+  /// cannot interpolate from `b`. In that case, [lerp] will apply a default
+  /// behavior instead.
+  ///
+  /// Supporting interpolating to null is recommended as the [Decoration.lerp]
+  /// method uses this as a fallback when two classes can't interpolate between
+  /// each other.
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `this` (or something
+  /// equivalent to `this`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `this` and `b`. The interpolation can be extrapolated beyond 0.0
+  /// and 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  ///
+  /// Instead of calling this directly, use [Decoration.lerp].
+  @protected
+  Decoration? lerpTo(Decoration? b, double t) => null;
+
+  /// Linearly interpolates between two [Decoration]s.
+  ///
+  /// This attempts to use [lerpFrom] and [lerpTo] on `b` and `a`
+  /// respectively to find a solution. If the two values can't directly be
+  /// interpolated, then the interpolation is done via null (at `t == 0.5`).
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static Decoration? lerp(Decoration? a, Decoration? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b!.lerpFrom(null, t) ?? b;
+    if (b == null)
+      return a.lerpTo(null, t) ?? a;
+    if (t == 0.0)
+      return a;
+    if (t == 1.0)
+      return b;
+    return b.lerpFrom(a, t)
+        ?? a.lerpTo(b, t)
+        ?? (t < 0.5 ? (a.lerpTo(null, t * 2.0) ?? a) : (b.lerpFrom(null, (t - 0.5) * 2.0) ?? b));
+  }
+
+  /// Tests whether the given point, on a rectangle of a given size,
+  /// would be considered to hit the decoration or not. For example,
+  /// if the decoration only draws a circle, this function might
+  /// return true if the point was inside the circle and false
+  /// otherwise.
+  ///
+  /// The decoration may be sensitive to the [TextDirection]. The
+  /// `textDirection` argument should therefore be provided. If it is known that
+  /// the decoration is not affected by the text direction, then the argument
+  /// may be omitted or set to null.
+  ///
+  /// When a [Decoration] is painted in a [Container] or [DecoratedBox] (which
+  /// is what [Container] uses), the `textDirection` parameter will be populated
+  /// based on the ambient [Directionality] (by way of the [RenderDecoratedBox]
+  /// renderer).
+  bool hitTest(Size size, Offset position, { TextDirection? textDirection }) => true;
+
+  /// Returns a [BoxPainter] that will paint this decoration.
+  ///
+  /// The `onChanged` argument configures [BoxPainter.onChanged]. It can be
+  /// omitted if there is no chance that the painter will change (for example,
+  /// if it is a [BoxDecoration] with definitely no [DecorationImage]).
+  @factory
+  BoxPainter createBoxPainter([ VoidCallback onChanged ]);
+
+  /// Returns a closed [Path] that describes the outer edge of this decoration.
+  ///
+  /// The default implementation throws. Subclasses must override this implementation
+  /// to describe the clip path that should be applied to the decoration when it is
+  /// used in a [Container] with an explicit [Clip] behavior.
+  ///
+  /// See also:
+  ///
+  ///  * [Container.clipBehavior], which, if set, uses this method to determine
+  ///    the clip path to use.
+  Path getClipPath(Rect rect, TextDirection textDirection) {
+    throw UnsupportedError('${objectRuntimeType(this, 'This Decoration subclass')} does not expect to be used for clipping.');
+  }
+}
+
+/// A stateful class that can paint a particular [Decoration].
+///
+/// [BoxPainter] objects can cache resources so that they can be used
+/// multiple times.
+///
+/// Some resources used by [BoxPainter] may load asynchronously. When this
+/// happens, the [onChanged] callback will be invoked. To stop this callback
+/// from being called after the painter has been discarded, call [dispose].
+abstract class BoxPainter {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const BoxPainter([this.onChanged]);
+
+  /// Paints the [Decoration] for which this object was created on the
+  /// given canvas using the given configuration.
+  ///
+  /// The [ImageConfiguration] object passed as the third argument must, at a
+  /// minimum, have a non-null [Size].
+  ///
+  /// If this object caches resources for painting (e.g. [Paint] objects), the
+  /// cache may be flushed when [paint] is called with a new configuration. For
+  /// this reason, it may be more efficient to call
+  /// [Decoration.createBoxPainter] for each different rectangle that is being
+  /// painted in a particular frame.
+  ///
+  /// For example, if a decoration's owner wants to paint a particular
+  /// decoration once for its whole size, and once just in the bottom
+  /// right, it might get two [BoxPainter] instances, one for each.
+  /// However, when its size changes, it could continue using those
+  /// same instances, since the previous resources would no longer be
+  /// relevant and thus losing them would not be an issue.
+  ///
+  /// Implementations should paint their decorations on the canvas in a
+  /// rectangle whose top left corner is at the given `offset` and whose size is
+  /// given by `configuration.size`.
+  ///
+  /// When a [Decoration] is painted in a [Container] or [DecoratedBox] (which
+  /// is what [Container] uses), the [ImageConfiguration.textDirection] property
+  /// will be populated based on the ambient [Directionality].
+  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration);
+
+  /// Callback that is invoked if an asynchronously-loading resource used by the
+  /// decoration finishes loading. For example, an image. When this is invoked,
+  /// the [paint] method should be called again.
+  ///
+  /// Resources might not start to load until after [paint] has been called,
+  /// because they might depend on the configuration.
+  final VoidCallback? onChanged;
+
+  /// Discard any resources being held by the object.
+  ///
+  /// The [onChanged] callback will not be invoked after this method has been
+  /// called.
+  @mustCallSuper
+  void dispose() { }
+}
diff --git a/lib/src/painting/decoration_image.dart b/lib/src/painting/decoration_image.dart
new file mode 100644
index 0000000..d0bb85c
--- /dev/null
+++ b/lib/src/painting/decoration_image.dart
@@ -0,0 +1,621 @@
+// Copyright 2014 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.
+
+
+import 'dart:developer' as developer;
+import 'package:flute/ui.dart' as ui show Image;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+
+import 'alignment.dart';
+import 'basic_types.dart';
+import 'borders.dart';
+import 'box_fit.dart';
+import 'debug.dart';
+import 'image_provider.dart';
+import 'image_stream.dart';
+
+/// How to paint any portions of a box not covered by an image.
+enum ImageRepeat {
+  /// Repeat the image in both the x and y directions until the box is filled.
+  repeat,
+
+  /// Repeat the image in the x direction until the box is filled horizontally.
+  repeatX,
+
+  /// Repeat the image in the y direction until the box is filled vertically.
+  repeatY,
+
+  /// Leave uncovered portions of the box transparent.
+  noRepeat,
+}
+
+/// An image for a box decoration.
+///
+/// The image is painted using [paintImage], which describes the meanings of the
+/// various fields on this class in more detail.
+@immutable
+class DecorationImage {
+  /// Creates an image to show in a [BoxDecoration].
+  ///
+  /// The [image], [alignment], [repeat], and [matchTextDirection] arguments
+  /// must not be null.
+  const DecorationImage({
+    required this.image,
+    this.onError,
+    this.colorFilter,
+    this.fit,
+    this.alignment = Alignment.center,
+    this.centerSlice,
+    this.repeat = ImageRepeat.noRepeat,
+    this.matchTextDirection = false,
+    this.scale = 1.0
+  }) : assert(image != null),
+       assert(alignment != null),
+       assert(repeat != null),
+       assert(matchTextDirection != null),
+       assert(scale != null);
+
+  /// The image to be painted into the decoration.
+  ///
+  /// Typically this will be an [AssetImage] (for an image shipped with the
+  /// application) or a [NetworkImage] (for an image obtained from the network).
+  final ImageProvider image;
+
+  /// An optional error callback for errors emitted when loading [image].
+  final ImageErrorListener? onError;
+
+  /// A color filter to apply to the image before painting it.
+  final ColorFilter? colorFilter;
+
+  /// How the image should be inscribed into the box.
+  ///
+  /// The default is [BoxFit.scaleDown] if [centerSlice] is null, and
+  /// [BoxFit.fill] if [centerSlice] is not null.
+  ///
+  /// See the discussion at [paintImage] for more details.
+  final BoxFit? fit;
+
+  /// How to align the image within its bounds.
+  ///
+  /// The alignment aligns the given position in the image to the given position
+  /// in the layout bounds. For example, an [Alignment] alignment of (-1.0,
+  /// -1.0) aligns the image to the top-left corner of its layout bounds, while a
+  /// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
+  /// image with the bottom right corner of its layout bounds. Similarly, an
+  /// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
+  /// middle of the bottom edge of its layout bounds.
+  ///
+  /// To display a subpart of an image, consider using a [CustomPainter] and
+  /// [Canvas.drawImageRect].
+  ///
+  /// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
+  /// [AlignmentDirectional]), then a [TextDirection] must be available
+  /// when the image is painted.
+  ///
+  /// Defaults to [Alignment.center].
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], a class with convenient constants typically used to
+  ///    specify an [AlignmentGeometry].
+  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
+  ///    relative to text direction.
+  final AlignmentGeometry alignment;
+
+  /// The center slice for a nine-patch image.
+  ///
+  /// The region of the image inside the center slice will be stretched both
+  /// horizontally and vertically to fit the image into its destination. The
+  /// region of the image above and below the center slice will be stretched
+  /// only horizontally and the region of the image to the left and right of
+  /// the center slice will be stretched only vertically.
+  ///
+  /// The stretching will be applied in order to make the image fit into the box
+  /// specified by [fit]. When [centerSlice] is not null, [fit] defaults to
+  /// [BoxFit.fill], which distorts the destination image size relative to the
+  /// image's original aspect ratio. Values of [BoxFit] which do not distort the
+  /// destination image size will result in [centerSlice] having no effect
+  /// (since the nine regions of the image will be rendered with the same
+  /// scaling, as if it wasn't specified).
+  final Rect? centerSlice;
+
+  /// How to paint any portions of the box that would not otherwise be covered
+  /// by the image.
+  final ImageRepeat repeat;
+
+  /// Whether to paint the image in the direction of the [TextDirection].
+  ///
+  /// If this is true, then in [TextDirection.ltr] contexts, the image will be
+  /// drawn with its origin in the top left (the "normal" painting direction for
+  /// images); and in [TextDirection.rtl] contexts, the image will be drawn with
+  /// a scaling factor of -1 in the horizontal direction so that the origin is
+  /// in the top right.
+  final bool matchTextDirection;
+
+  /// Defines image pixels to be shown per logical pixels.
+  ///
+  /// By default the value of scale is 1.0. The scale for the image is
+  /// calculated by multiplying [scale] with `scale` of the given [ImageProvider].
+  final double scale;
+
+  /// Creates a [DecorationImagePainter] for this [DecorationImage].
+  ///
+  /// The `onChanged` argument must not be null. It will be called whenever the
+  /// image needs to be repainted, e.g. because it is loading incrementally or
+  /// because it is animated.
+  DecorationImagePainter createPainter(VoidCallback onChanged) {
+    assert(onChanged != null);
+    return DecorationImagePainter._(this, onChanged);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is DecorationImage
+        && other.image == image
+        && other.colorFilter == colorFilter
+        && other.fit == fit
+        && other.alignment == alignment
+        && other.centerSlice == centerSlice
+        && other.repeat == repeat
+        && other.matchTextDirection == matchTextDirection
+        && other.scale == scale;
+  }
+
+  @override
+  int get hashCode => hashValues(image, colorFilter, fit, alignment, centerSlice, repeat, matchTextDirection, scale);
+
+  @override
+  String toString() {
+    final List<String> properties = <String>[
+      '$image',
+      if (colorFilter != null)
+        '$colorFilter',
+      if (fit != null &&
+          !(fit == BoxFit.fill && centerSlice != null) &&
+          !(fit == BoxFit.scaleDown && centerSlice == null))
+        '$fit',
+      '$alignment',
+      if (centerSlice != null)
+        'centerSlice: $centerSlice',
+      if (repeat != ImageRepeat.noRepeat)
+        '$repeat',
+      if (matchTextDirection)
+        'match text direction',
+      'scale: $scale'
+    ];
+    return '${objectRuntimeType(this, 'DecorationImage')}(${properties.join(", ")})';
+  }
+}
+
+/// The painter for a [DecorationImage].
+///
+/// To obtain a painter, call [DecorationImage.createPainter].
+///
+/// To paint, call [paint]. The `onChanged` callback passed to
+/// [DecorationImage.createPainter] will be called if the image needs to paint
+/// again (e.g. because it is animated or because it had not yet loaded the
+/// first time the [paint] method was called).
+///
+/// This object should be disposed using the [dispose] method when it is no
+/// longer needed.
+class DecorationImagePainter {
+  DecorationImagePainter._(this._details, this._onChanged) : assert(_details != null);
+
+  final DecorationImage _details;
+  final VoidCallback _onChanged;
+
+  ImageStream? _imageStream;
+  ImageInfo? _image;
+
+  /// Draw the image onto the given canvas.
+  ///
+  /// The image is drawn at the position and size given by the `rect` argument.
+  ///
+  /// The image is clipped to the given `clipPath`, if any.
+  ///
+  /// The `configuration` object is used to resolve the image (e.g. to pick
+  /// resolution-specific assets), and to implement the
+  /// [DecorationImage.matchTextDirection] feature.
+  ///
+  /// If the image needs to be painted again, e.g. because it is animated or
+  /// because it had not yet been loaded the first time this method was called,
+  /// then the `onChanged` callback passed to [DecorationImage.createPainter]
+  /// will be called.
+  void paint(Canvas canvas, Rect rect, Path? clipPath, ImageConfiguration configuration) {
+    assert(canvas != null);
+    assert(rect != null);
+    assert(configuration != null);
+
+    bool flipHorizontally = false;
+    if (_details.matchTextDirection) {
+      assert(() {
+        // We check this first so that the assert will fire immediately, not just
+        // when the image is ready.
+        if (configuration.textDirection == null) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('DecorationImage.matchTextDirection can only be used when a TextDirection is available.'),
+            ErrorDescription(
+              'When DecorationImagePainter.paint() was called, there was no text direction provided '
+              'in the ImageConfiguration object to match.'
+            ),
+            DiagnosticsProperty<DecorationImage>('The DecorationImage was', _details, style: DiagnosticsTreeStyle.errorProperty),
+            DiagnosticsProperty<ImageConfiguration>('The ImageConfiguration was', configuration, style: DiagnosticsTreeStyle.errorProperty),
+          ]);
+        }
+        return true;
+      }());
+      if (configuration.textDirection == TextDirection.rtl)
+        flipHorizontally = true;
+    }
+
+    final ImageStream newImageStream = _details.image.resolve(configuration);
+    if (newImageStream.key != _imageStream?.key) {
+      final ImageStreamListener listener = ImageStreamListener(
+        _handleImage,
+        onError: _details.onError,
+      );
+      _imageStream?.removeListener(listener);
+      _imageStream = newImageStream;
+      _imageStream!.addListener(listener);
+    }
+    if (_image == null)
+      return;
+
+    if (clipPath != null) {
+      canvas.save();
+      canvas.clipPath(clipPath);
+    }
+
+    paintImage(
+      canvas: canvas,
+      rect: rect,
+      image: _image!.image,
+      debugImageLabel: _image!.debugLabel,
+      scale: _details.scale * _image!.scale,
+      colorFilter: _details.colorFilter,
+      fit: _details.fit,
+      alignment: _details.alignment.resolve(configuration.textDirection),
+      centerSlice: _details.centerSlice,
+      repeat: _details.repeat,
+      flipHorizontally: flipHorizontally,
+      filterQuality: FilterQuality.low,
+    );
+
+    if (clipPath != null)
+      canvas.restore();
+  }
+
+  void _handleImage(ImageInfo value, bool synchronousCall) {
+    if (_image == value)
+      return;
+    if (_image != null && _image!.isCloneOf(value)) {
+      value.dispose();
+      return;
+    }
+    _image?.dispose();
+    _image = value;
+    assert(_onChanged != null);
+    if (!synchronousCall)
+      _onChanged();
+  }
+
+  /// Releases the resources used by this painter.
+  ///
+  /// This should be called whenever the painter is no longer needed.
+  ///
+  /// After this method has been called, the object is no longer usable.
+  @mustCallSuper
+  void dispose() {
+    _imageStream?.removeListener(ImageStreamListener(
+      _handleImage,
+      onError: _details.onError,
+    ));
+    _image?.dispose();
+    _image = null;
+  }
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'DecorationImagePainter')}(stream: $_imageStream, image: $_image) for $_details';
+  }
+}
+
+/// Used by [paintImage] to report image sizes drawn at the end of the frame.
+Map<String, ImageSizeInfo> _pendingImageSizeInfo = <String, ImageSizeInfo>{};
+
+/// [ImageSizeInfo]s that were reported on the last frame.
+///
+/// Used to prevent duplicative reports from frame to frame.
+Set<ImageSizeInfo> _lastFrameImageSizeInfo = <ImageSizeInfo>{};
+
+/// Flushes inter-frame tracking of image size information from [paintImage].
+///
+/// Has no effect if asserts are disabled.
+@visibleForTesting
+void debugFlushLastFrameImageSizeInfo() {
+  assert(() {
+    _lastFrameImageSizeInfo = <ImageSizeInfo>{};
+    return true;
+  }());
+}
+
+/// Paints an image into the given rectangle on the canvas.
+///
+/// The arguments have the following meanings:
+///
+///  * `canvas`: The canvas onto which the image will be painted.
+///
+///  * `rect`: The region of the canvas into which the image will be painted.
+///    The image might not fill the entire rectangle (e.g., depending on the
+///    `fit`). If `rect` is empty, nothing is painted.
+///
+///  * `image`: The image to paint onto the canvas.
+///
+///  * `scale`: The number of image pixels for each logical pixel.
+///
+///  * `colorFilter`: If non-null, the color filter to apply when painting the
+///    image.
+///
+///  * `fit`: How the image should be inscribed into `rect`. If null, the
+///    default behavior depends on `centerSlice`. If `centerSlice` is also null,
+///    the default behavior is [BoxFit.scaleDown]. If `centerSlice` is
+///    non-null, the default behavior is [BoxFit.fill]. See [BoxFit] for
+///    details.
+///
+///  * `alignment`: How the destination rectangle defined by applying `fit` is
+///    aligned within `rect`. For example, if `fit` is [BoxFit.contain] and
+///    `alignment` is [Alignment.bottomRight], the image will be as large
+///    as possible within `rect` and placed with its bottom right corner at the
+///    bottom right corner of `rect`. Defaults to [Alignment.center].
+///
+///  * `centerSlice`: The image is drawn in nine portions described by splitting
+///    the image by drawing two horizontal lines and two vertical lines, where
+///    `centerSlice` describes the rectangle formed by the four points where
+///    these four lines intersect each other. (This forms a 3-by-3 grid
+///    of regions, the center region being described by `centerSlice`.)
+///    The four regions in the corners are drawn, without scaling, in the four
+///    corners of the destination rectangle defined by applying `fit`. The
+///    remaining five regions are drawn by stretching them to fit such that they
+///    exactly cover the destination rectangle while maintaining their relative
+///    positions.
+///
+///  * `repeat`: If the image does not fill `rect`, whether and how the image
+///    should be repeated to fill `rect`. By default, the image is not repeated.
+///    See [ImageRepeat] for details.
+///
+///  * `flipHorizontally`: Whether to flip the image horizontally. This is
+///    occasionally used with images in right-to-left environments, for images
+///    that were designed for left-to-right locales (or vice versa). Be careful,
+///    when using this, to not flip images with integral shadows, text, or other
+///    effects that will look incorrect when flipped.
+///
+///  * `invertColors`: Inverting the colors of an image applies a new color
+///    filter to the paint. If there is another specified color filter, the
+///    invert will be applied after it. This is primarily used for implementing
+///    smart invert on iOS.
+///
+///  * `filterQuality`: Use this to change the quality when scaling an image.
+///     Use the [FilterQuality.low] quality setting to scale the image, which corresponds to
+///     bilinear interpolation, rather than the default [FilterQuality.none] which corresponds
+///     to nearest-neighbor.
+///
+/// The `canvas`, `rect`, `image`, `scale`, `alignment`, `repeat`, `flipHorizontally` and `filterQuality`
+/// arguments must not be null.
+///
+/// See also:
+///
+///  * [paintBorder], which paints a border around a rectangle on a canvas.
+///  * [DecorationImage], which holds a configuration for calling this function.
+///  * [BoxDecoration], which uses this function to paint a [DecorationImage].
+void paintImage({
+  required Canvas canvas,
+  required Rect rect,
+  required ui.Image image,
+  String? debugImageLabel,
+  double scale = 1.0,
+  ColorFilter? colorFilter,
+  BoxFit? fit,
+  Alignment alignment = Alignment.center,
+  Rect? centerSlice,
+  ImageRepeat repeat = ImageRepeat.noRepeat,
+  bool flipHorizontally = false,
+  bool invertColors = false,
+  FilterQuality filterQuality = FilterQuality.low,
+  bool isAntiAlias = false,
+}) {
+  assert(canvas != null);
+  assert(image != null);
+  assert(alignment != null);
+  assert(repeat != null);
+  assert(flipHorizontally != null);
+  assert(isAntiAlias != null);
+  assert(
+    image.debugGetOpenHandleStackTraces()?.isNotEmpty ?? true,
+    'Cannot paint an image that is disposed.\n'
+    'The caller of paintImage is expected to wait to dispose the image until '
+    'after painting has completed.'
+  );
+  if (rect.isEmpty)
+    return;
+  Size outputSize = rect.size;
+  Size inputSize = Size(image.width.toDouble(), image.height.toDouble());
+  Offset? sliceBorder;
+  if (centerSlice != null) {
+    sliceBorder = inputSize / scale - centerSlice.size as Offset;
+    outputSize = outputSize - sliceBorder as Size;
+    inputSize = inputSize - sliceBorder * scale as Size;
+  }
+  fit ??= centerSlice == null ? BoxFit.scaleDown : BoxFit.fill;
+  assert(centerSlice == null || (fit != BoxFit.none && fit != BoxFit.cover));
+  final FittedSizes fittedSizes = applyBoxFit(fit, inputSize / scale, outputSize);
+  final Size sourceSize = fittedSizes.source * scale;
+  Size destinationSize = fittedSizes.destination;
+  if (centerSlice != null) {
+    outputSize += sliceBorder!;
+    destinationSize += sliceBorder;
+    // We don't have the ability to draw a subset of the image at the same time
+    // as we apply a nine-patch stretch.
+    assert(sourceSize == inputSize, 'centerSlice was used with a BoxFit that does not guarantee that the image is fully visible.');
+  }
+
+  if (repeat != ImageRepeat.noRepeat && destinationSize == outputSize) {
+    // There's no need to repeat the image because we're exactly filling the
+    // output rect with the image.
+    repeat = ImageRepeat.noRepeat;
+  }
+  final Paint paint = Paint()..isAntiAlias = isAntiAlias;
+  if (colorFilter != null)
+    paint.colorFilter = colorFilter;
+  if (sourceSize != destinationSize) {
+    paint.filterQuality = filterQuality;
+  }
+  paint.invertColors = invertColors;
+  final double halfWidthDelta = (outputSize.width - destinationSize.width) / 2.0;
+  final double halfHeightDelta = (outputSize.height - destinationSize.height) / 2.0;
+  final double dx = halfWidthDelta + (flipHorizontally ? -alignment.x : alignment.x) * halfWidthDelta;
+  final double dy = halfHeightDelta + alignment.y * halfHeightDelta;
+  final Offset destinationPosition = rect.topLeft.translate(dx, dy);
+  final Rect destinationRect = destinationPosition & destinationSize;
+
+  // Set to true if we added a saveLayer to the canvas to invert/flip the image.
+  bool invertedCanvas = false;
+  // Output size and destination rect are fully calculated.
+  if (!kReleaseMode) {
+    final ImageSizeInfo sizeInfo = ImageSizeInfo(
+      // Some ImageProvider implementations may not have given this.
+      source: debugImageLabel ?? '<Unknown Image(${image.width}×${image.height})>',
+      imageSize: Size(image.width.toDouble(), image.height.toDouble()),
+      displaySize: outputSize,
+    );
+    assert(() {
+      if (debugInvertOversizedImages &&
+          sizeInfo.decodedSizeInBytes > sizeInfo.displaySizeInBytes + debugImageOverheadAllowance) {
+        final int overheadInKilobytes = (sizeInfo.decodedSizeInBytes - sizeInfo.displaySizeInBytes) ~/ 1024;
+        final int outputWidth = outputSize.width.toInt();
+        final int outputHeight = outputSize.height.toInt();
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: 'Image $debugImageLabel has a display size of '
+            '$outputWidth×$outputHeight but a decode size of '
+            '${image.width}×${image.height}, which uses an additional '
+            '${overheadInKilobytes}kb.\n\n'
+            'Consider resizing the asset ahead of time, supplying a cacheWidth '
+            'parameter of $outputWidth, a cacheHeight parameter of '
+            '$outputHeight, or using a ResizeImage.',
+          library: 'painting library',
+          context: ErrorDescription('while painting an image'),
+        ));
+        // Invert the colors of the canvas.
+        canvas.saveLayer(
+          destinationRect,
+          Paint()..colorFilter = const ColorFilter.matrix(<double>[
+            -1,  0,  0, 0, 255,
+             0, -1,  0, 0, 255,
+             0,  0, -1, 0, 255,
+             0,  0,  0, 1,   0,
+          ]),
+        );
+        // Flip the canvas vertically.
+        final double dy = -(rect.top + rect.height / 2.0);
+        canvas.translate(0.0, -dy);
+        canvas.scale(1.0, -1.0);
+        canvas.translate(0.0, dy);
+        invertedCanvas = true;
+      }
+      return true;
+    }());
+    // Avoid emitting events that are the same as those emitted in the last frame.
+    if (!_lastFrameImageSizeInfo.contains(sizeInfo)) {
+      final ImageSizeInfo? existingSizeInfo = _pendingImageSizeInfo[sizeInfo.source];
+      if (existingSizeInfo == null || existingSizeInfo.displaySizeInBytes < sizeInfo.displaySizeInBytes) {
+        _pendingImageSizeInfo[sizeInfo.source!] = sizeInfo;
+      }
+      if (debugOnPaintImage != null) {
+        debugOnPaintImage!(sizeInfo);
+      }
+      SchedulerBinding.instance!.addPostFrameCallback((Duration timeStamp) {
+        _lastFrameImageSizeInfo = _pendingImageSizeInfo.values.toSet();
+        if (_pendingImageSizeInfo.isEmpty) {
+          return;
+        }
+        developer.postEvent(
+          'Flutter.ImageSizesForFrame',
+          <String, Object>{
+            for (ImageSizeInfo imageSizeInfo in _pendingImageSizeInfo.values)
+              imageSizeInfo.source!: imageSizeInfo.toJson()
+          },
+        );
+        _pendingImageSizeInfo = <String, ImageSizeInfo>{};
+      });
+    }
+  }
+
+  final bool needSave = centerSlice != null || repeat != ImageRepeat.noRepeat || flipHorizontally;
+  if (needSave)
+    canvas.save();
+  if (repeat != ImageRepeat.noRepeat)
+    canvas.clipRect(rect);
+  if (flipHorizontally) {
+    final double dx = -(rect.left + rect.width / 2.0);
+    canvas.translate(-dx, 0.0);
+    canvas.scale(-1.0, 1.0);
+    canvas.translate(dx, 0.0);
+  }
+  if (centerSlice == null) {
+    final Rect sourceRect = alignment.inscribe(
+      sourceSize, Offset.zero & inputSize,
+    );
+    if (repeat == ImageRepeat.noRepeat) {
+      canvas.drawImageRect(image, sourceRect, destinationRect, paint);
+    } else {
+      for (final Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
+        canvas.drawImageRect(image, sourceRect, tileRect, paint);
+    }
+  } else {
+    canvas.scale(1 / scale);
+    if (repeat == ImageRepeat.noRepeat) {
+      canvas.drawImageNine(image, _scaleRect(centerSlice, scale), _scaleRect(destinationRect, scale), paint);
+    } else {
+      for (final Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
+        canvas.drawImageNine(image, _scaleRect(centerSlice, scale), _scaleRect(tileRect, scale), paint);
+    }
+  }
+  if (needSave)
+    canvas.restore();
+
+  if (invertedCanvas) {
+    canvas.restore();
+  }
+}
+
+Iterable<Rect> _generateImageTileRects(Rect outputRect, Rect fundamentalRect, ImageRepeat repeat) sync* {
+  int startX = 0;
+  int startY = 0;
+  int stopX = 0;
+  int stopY = 0;
+  final double strideX = fundamentalRect.width;
+  final double strideY = fundamentalRect.height;
+
+  if (repeat == ImageRepeat.repeat || repeat == ImageRepeat.repeatX) {
+    startX = ((outputRect.left - fundamentalRect.left) / strideX).floor();
+    stopX = ((outputRect.right - fundamentalRect.right) / strideX).ceil();
+  }
+
+  if (repeat == ImageRepeat.repeat || repeat == ImageRepeat.repeatY) {
+    startY = ((outputRect.top - fundamentalRect.top) / strideY).floor();
+    stopY = ((outputRect.bottom - fundamentalRect.bottom) / strideY).ceil();
+  }
+
+  for (int i = startX; i <= stopX; ++i) {
+    for (int j = startY; j <= stopY; ++j)
+      yield fundamentalRect.shift(Offset(i * strideX, j * strideY));
+  }
+}
+
+Rect _scaleRect(Rect rect, double scale) => Rect.fromLTRB(rect.left * scale, rect.top * scale, rect.right * scale, rect.bottom * scale);
diff --git a/lib/src/painting/edge_insets.dart b/lib/src/painting/edge_insets.dart
new file mode 100644
index 0000000..53a9f61
--- /dev/null
+++ b/lib/src/painting/edge_insets.dart
@@ -0,0 +1,952 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' as ui show lerpDouble, WindowPadding;
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+
+/// Base class for [EdgeInsets] that allows for text-direction aware
+/// resolution.
+///
+/// A property or argument of this type accepts classes created either with [new
+/// EdgeInsets.fromLTRB] and its variants, or [new
+/// EdgeInsetsDirectional.fromSTEB] and its variants.
+///
+/// To convert an [EdgeInsetsGeometry] object of indeterminate type into a
+/// [EdgeInsets] object, call the [resolve] method.
+///
+/// See also:
+///
+///  * [Padding], a widget that describes margins using [EdgeInsetsGeometry].
+@immutable
+abstract class EdgeInsetsGeometry {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const EdgeInsetsGeometry();
+
+  double get _bottom;
+  double get _end;
+  double get _left;
+  double get _right;
+  double get _start;
+  double get _top;
+
+  /// An [EdgeInsetsGeometry] with infinite offsets in each direction.
+  ///
+  /// Can be used as an infinite upper bound for [clamp].
+  static const EdgeInsetsGeometry infinity = _MixedEdgeInsets.fromLRSETB(
+    double.infinity,
+    double.infinity,
+    double.infinity,
+    double.infinity,
+    double.infinity,
+    double.infinity,
+  );
+
+  /// Whether every dimension is non-negative.
+  bool get isNonNegative {
+    return _left >= 0.0
+        && _right >= 0.0
+        && _start >= 0.0
+        && _end >= 0.0
+        && _top >= 0.0
+        && _bottom >= 0.0;
+  }
+
+  /// The total offset in the horizontal direction.
+  double get horizontal => _left + _right + _start + _end;
+
+  /// The total offset in the vertical direction.
+  double get vertical => _top + _bottom;
+
+  /// The total offset in the given direction.
+  double along(Axis axis) {
+    assert(axis != null);
+    switch (axis) {
+      case Axis.horizontal:
+        return horizontal;
+      case Axis.vertical:
+        return vertical;
+    }
+  }
+
+  /// The size that this [EdgeInsets] would occupy with an empty interior.
+  Size get collapsedSize => Size(horizontal, vertical);
+
+  /// An [EdgeInsetsGeometry] with top and bottom, left and right, and start and end flipped.
+  EdgeInsetsGeometry get flipped => _MixedEdgeInsets.fromLRSETB(_right, _left, _end, _start, _bottom, _top);
+
+  /// Returns a new size that is bigger than the given size by the amount of
+  /// inset in the horizontal and vertical directions.
+  ///
+  /// See also:
+  ///
+  ///  * [EdgeInsets.inflateRect], to inflate a [Rect] rather than a [Size] (for
+  ///    [EdgeInsetsDirectional], requires first calling [resolve] to establish
+  ///    how the start and end map to the left or right).
+  ///  * [deflateSize], to deflate a [Size] rather than inflating it.
+  Size inflateSize(Size size) {
+    return Size(size.width + horizontal, size.height + vertical);
+  }
+
+  /// Returns a new size that is smaller than the given size by the amount of
+  /// inset in the horizontal and vertical directions.
+  ///
+  /// If the argument is smaller than [collapsedSize], then the resulting size
+  /// will have negative dimensions.
+  ///
+  /// See also:
+  ///
+  ///  * [EdgeInsets.deflateRect], to deflate a [Rect] rather than a [Size]. (for
+  ///    [EdgeInsetsDirectional], requires first calling [resolve] to establish
+  ///    how the start and end map to the left or right).
+  ///  * [inflateSize], to inflate a [Size] rather than deflating it.
+  Size deflateSize(Size size) {
+    return Size(size.width - horizontal, size.height - vertical);
+  }
+
+  /// Returns the difference between two [EdgeInsetsGeometry] objects.
+  ///
+  /// If you know you are applying this to two [EdgeInsets] or two
+  /// [EdgeInsetsDirectional] objects, consider using the binary infix `-`
+  /// operator instead, which always returns an object of the same type as the
+  /// operands, and is typed accordingly.
+  ///
+  /// If [subtract] is applied to two objects of the same type ([EdgeInsets] or
+  /// [EdgeInsetsDirectional]), an object of that type will be returned (though
+  /// this is not reflected in the type system). Otherwise, an object
+  /// representing a combination of both is returned. That object can be turned
+  /// into a concrete [EdgeInsets] using [resolve].
+  ///
+  /// This method returns the same result as [add] applied to the result of
+  /// negating the argument (using the prefix unary `-` operator or multiplying
+  /// the argument by -1.0 using the `*` operator).
+  EdgeInsetsGeometry subtract(EdgeInsetsGeometry other) {
+    return _MixedEdgeInsets.fromLRSETB(
+      _left - other._left,
+      _right - other._right,
+      _start - other._start,
+      _end - other._end,
+      _top - other._top,
+      _bottom - other._bottom,
+    );
+  }
+
+  /// Returns the sum of two [EdgeInsetsGeometry] objects.
+  ///
+  /// If you know you are adding two [EdgeInsets] or two [EdgeInsetsDirectional]
+  /// objects, consider using the `+` operator instead, which always returns an
+  /// object of the same type as the operands, and is typed accordingly.
+  ///
+  /// If [add] is applied to two objects of the same type ([EdgeInsets] or
+  /// [EdgeInsetsDirectional]), an object of that type will be returned (though
+  /// this is not reflected in the type system). Otherwise, an object
+  /// representing a combination of both is returned. That object can be turned
+  /// into a concrete [EdgeInsets] using [resolve].
+  EdgeInsetsGeometry add(EdgeInsetsGeometry other) {
+    return _MixedEdgeInsets.fromLRSETB(
+      _left + other._left,
+      _right + other._right,
+      _start + other._start,
+      _end + other._end,
+      _top + other._top,
+      _bottom + other._bottom,
+    );
+  }
+
+  /// Returns the a new [EdgeInsetsGeometry] object with all values greater than
+  /// or equal to `min`, and less than or equal to `max`.
+  EdgeInsetsGeometry clamp(EdgeInsetsGeometry min, EdgeInsetsGeometry max) {
+    return _MixedEdgeInsets.fromLRSETB(
+      _left.clamp(min._left, max._left),
+      _right.clamp(min._right, max._right),
+      _start.clamp(min._start, max._start),
+      _end.clamp(min._end, max._end),
+      _top.clamp(min._top, max._top),
+      _bottom.clamp(min._bottom, max._bottom),
+    );
+  }
+
+  /// Returns the [EdgeInsetsGeometry] object with each dimension negated.
+  ///
+  /// This is the same as multiplying the object by -1.0.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  EdgeInsetsGeometry operator -();
+
+  /// Scales the [EdgeInsetsGeometry] object in each dimension by the given factor.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  EdgeInsetsGeometry operator *(double other);
+
+  /// Divides the [EdgeInsetsGeometry] object in each dimension by the given factor.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  EdgeInsetsGeometry operator /(double other);
+
+  /// Integer divides the [EdgeInsetsGeometry] object in each dimension by the given factor.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  ///
+  /// This operator may have unexpected results when applied to a mixture of
+  /// [EdgeInsets] and [EdgeInsetsDirectional] objects.
+  EdgeInsetsGeometry operator ~/(double other);
+
+  /// Computes the remainder in each dimension by the given factor.
+  ///
+  /// This operator returns an object of the same type as the operand.
+  ///
+  /// This operator may have unexpected results when applied to a mixture of
+  /// [EdgeInsets] and [EdgeInsetsDirectional] objects.
+  EdgeInsetsGeometry operator %(double other);
+
+  /// Linearly interpolate between two [EdgeInsetsGeometry] objects.
+  ///
+  /// If either is null, this function interpolates from [EdgeInsets.zero], and
+  /// the result is an object of the same type as the non-null argument.
+  ///
+  /// If [lerp] is applied to two objects of the same type ([EdgeInsets] or
+  /// [EdgeInsetsDirectional]), an object of that type will be returned (though
+  /// this is not reflected in the type system). Otherwise, an object
+  /// representing a combination of both is returned. That object can be turned
+  /// into a concrete [EdgeInsets] using [resolve].
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static EdgeInsetsGeometry? lerp(EdgeInsetsGeometry? a, EdgeInsetsGeometry? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b! * t;
+    if (b == null)
+      return a * (1.0 - t);
+    if (a is EdgeInsets && b is EdgeInsets)
+      return EdgeInsets.lerp(a, b, t);
+    if (a is EdgeInsetsDirectional && b is EdgeInsetsDirectional)
+      return EdgeInsetsDirectional.lerp(a, b, t);
+    return _MixedEdgeInsets.fromLRSETB(
+      ui.lerpDouble(a._left, b._left, t)!,
+      ui.lerpDouble(a._right, b._right, t)!,
+      ui.lerpDouble(a._start, b._start, t)!,
+      ui.lerpDouble(a._end, b._end, t)!,
+      ui.lerpDouble(a._top, b._top, t)!,
+      ui.lerpDouble(a._bottom, b._bottom, t)!,
+    );
+  }
+
+  /// Convert this instance into an [EdgeInsets], which uses literal coordinates
+  /// (i.e. the `left` coordinate being explicitly a distance from the left, and
+  /// the `right` coordinate being explicitly a distance from the right).
+  ///
+  /// See also:
+  ///
+  ///  * [EdgeInsets], for which this is a no-op (returns itself).
+  ///  * [EdgeInsetsDirectional], which flips the horizontal direction
+  ///    based on the `direction` argument.
+  EdgeInsets resolve(TextDirection? direction);
+
+  @override
+  String toString() {
+    if (_start == 0.0 && _end == 0.0) {
+      if (_left == 0.0 && _right == 0.0 && _top == 0.0 && _bottom == 0.0)
+        return 'EdgeInsets.zero';
+      if (_left == _right && _right == _top && _top == _bottom)
+        return 'EdgeInsets.all(${_left.toStringAsFixed(1)})';
+      return 'EdgeInsets(${_left.toStringAsFixed(1)}, '
+                        '${_top.toStringAsFixed(1)}, '
+                        '${_right.toStringAsFixed(1)}, '
+                        '${_bottom.toStringAsFixed(1)})';
+    }
+    if (_left == 0.0 && _right == 0.0) {
+      return 'EdgeInsetsDirectional(${_start.toStringAsFixed(1)}, '
+                                   '${_top.toStringAsFixed(1)}, '
+                                   '${_end.toStringAsFixed(1)}, '
+                                   '${_bottom.toStringAsFixed(1)})';
+    }
+    return 'EdgeInsets(${_left.toStringAsFixed(1)}, '
+                      '${_top.toStringAsFixed(1)}, '
+                      '${_right.toStringAsFixed(1)}, '
+                      '${_bottom.toStringAsFixed(1)})'
+           ' + '
+           'EdgeInsetsDirectional(${_start.toStringAsFixed(1)}, '
+                                 '0.0, '
+                                 '${_end.toStringAsFixed(1)}, '
+                                 '0.0)';
+  }
+
+  @override
+  bool operator ==(Object other) {
+    return other is EdgeInsetsGeometry
+        && other._left == _left
+        && other._right == _right
+        && other._start == _start
+        && other._end == _end
+        && other._top == _top
+        && other._bottom == _bottom;
+  }
+
+  @override
+  int get hashCode => hashValues(_left, _right, _start, _end, _top, _bottom);
+}
+
+/// An immutable set of offsets in each of the four cardinal directions.
+///
+/// Typically used for an offset from each of the four sides of a box. For
+/// example, the padding inside a box can be represented using this class.
+///
+/// The [EdgeInsets] class specifies offsets in terms of visual edges, left,
+/// top, right, and bottom. These values are not affected by the
+/// [TextDirection]. To support both left-to-right and right-to-left layouts,
+/// consider using [EdgeInsetsDirectional], which is expressed in terms of
+/// _start_, top, _end_, and bottom, where start and end are resolved in terms
+/// of a [TextDirection] (typically obtained from the ambient [Directionality]).
+///
+/// {@tool snippet}
+///
+/// Here are some examples of how to create [EdgeInsets] instances:
+///
+/// Typical eight-pixel margin on all sides:
+///
+/// ```dart
+/// const EdgeInsets.all(8.0)
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// Eight pixel margin above and below, no horizontal margins:
+///
+/// ```dart
+/// const EdgeInsets.symmetric(vertical: 8.0)
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// Left margin indent of 40 pixels:
+///
+/// ```dart
+/// const EdgeInsets.only(left: 40.0)
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Padding], a widget that accepts [EdgeInsets] to describe its margins.
+///  * [EdgeInsetsDirectional], which (for properties and arguments that accept
+///    the type [EdgeInsetsGeometry]) allows the horizontal insets to be
+///    specified in a [TextDirection]-aware manner.
+class EdgeInsets extends EdgeInsetsGeometry {
+  /// Creates insets from offsets from the left, top, right, and bottom.
+  const EdgeInsets.fromLTRB(this.left, this.top, this.right, this.bottom);
+
+  /// Creates insets where all the offsets are `value`.
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical eight-pixel margin on all sides:
+  ///
+  /// ```dart
+  /// const EdgeInsets.all(8.0)
+  /// ```
+  /// {@end-tool}
+  const EdgeInsets.all(double value)
+    : left = value,
+      top = value,
+      right = value,
+      bottom = value;
+
+  /// Creates insets with only the given values non-zero.
+  ///
+  /// {@tool snippet}
+  ///
+  /// Left margin indent of 40 pixels:
+  ///
+  /// ```dart
+  /// const EdgeInsets.only(left: 40.0)
+  /// ```
+  /// {@end-tool}
+  const EdgeInsets.only({
+    this.left = 0.0,
+    this.top = 0.0,
+    this.right = 0.0,
+    this.bottom = 0.0,
+  });
+
+  /// Creates insets with symmetrical vertical and horizontal offsets.
+  ///
+  /// {@tool snippet}
+  ///
+  /// Eight pixel margin above and below, no horizontal margins:
+  ///
+  /// ```dart
+  /// const EdgeInsets.symmetric(vertical: 8.0)
+  /// ```
+  /// {@end-tool}
+  const EdgeInsets.symmetric({
+    double vertical = 0.0,
+    double horizontal = 0.0,
+  }) : left = horizontal,
+       top = vertical,
+       right = horizontal,
+       bottom = vertical;
+
+  /// Creates insets that match the given window padding.
+  ///
+  /// If you need the current system padding or view insets in the context of a
+  /// widget, consider using [MediaQuery.of] to obtain these values rather than
+  /// using the value from [dart:ui.window], so that you get notified of
+  /// changes.
+  EdgeInsets.fromWindowPadding(ui.WindowPadding padding, double devicePixelRatio)
+    : left = padding.left / devicePixelRatio,
+      top = padding.top / devicePixelRatio,
+      right = padding.right / devicePixelRatio,
+      bottom = padding.bottom / devicePixelRatio;
+
+  /// An [EdgeInsets] with zero offsets in each direction.
+  static const EdgeInsets zero = EdgeInsets.only();
+
+  /// The offset from the left.
+  final double left;
+
+  @override
+  double get _left => left;
+
+  /// The offset from the top.
+  final double top;
+
+  @override
+  double get _top => top;
+
+  /// The offset from the right.
+  final double right;
+
+  @override
+  double get _right => right;
+
+  /// The offset from the bottom.
+  final double bottom;
+
+  @override
+  double get _bottom => bottom;
+
+  @override
+  double get _start => 0.0;
+
+  @override
+  double get _end => 0.0;
+
+  /// An Offset describing the vector from the top left of a rectangle to the
+  /// top left of that rectangle inset by this object.
+  Offset get topLeft => Offset(left, top);
+
+  /// An Offset describing the vector from the top right of a rectangle to the
+  /// top right of that rectangle inset by this object.
+  Offset get topRight => Offset(-right, top);
+
+  /// An Offset describing the vector from the bottom left of a rectangle to the
+  /// bottom left of that rectangle inset by this object.
+  Offset get bottomLeft => Offset(left, -bottom);
+
+  /// An Offset describing the vector from the bottom right of a rectangle to the
+  /// bottom right of that rectangle inset by this object.
+  Offset get bottomRight => Offset(-right, -bottom);
+
+  /// An [EdgeInsets] with top and bottom as well as left and right flipped.
+  @override
+  EdgeInsets get flipped => EdgeInsets.fromLTRB(right, bottom, left, top);
+
+  /// Returns a new rect that is bigger than the given rect in each direction by
+  /// the amount of inset in each direction. Specifically, the left edge of the
+  /// rect is moved left by [left], the top edge of the rect is moved up by
+  /// [top], the right edge of the rect is moved right by [right], and the
+  /// bottom edge of the rect is moved down by [bottom].
+  ///
+  /// See also:
+  ///
+  ///  * [inflateSize], to inflate a [Size] rather than a [Rect].
+  ///  * [deflateRect], to deflate a [Rect] rather than inflating it.
+  Rect inflateRect(Rect rect) {
+    return Rect.fromLTRB(rect.left - left, rect.top - top, rect.right + right, rect.bottom + bottom);
+  }
+
+  /// Returns a new rect that is smaller than the given rect in each direction by
+  /// the amount of inset in each direction. Specifically, the left edge of the
+  /// rect is moved right by [left], the top edge of the rect is moved down by
+  /// [top], the right edge of the rect is moved left by [right], and the
+  /// bottom edge of the rect is moved up by [bottom].
+  ///
+  /// If the argument's [Rect.size] is smaller than [collapsedSize], then the
+  /// resulting rectangle will have negative dimensions.
+  ///
+  /// See also:
+  ///
+  ///  * [deflateSize], to deflate a [Size] rather than a [Rect].
+  ///  * [inflateRect], to inflate a [Rect] rather than deflating it.
+  Rect deflateRect(Rect rect) {
+    return Rect.fromLTRB(rect.left + left, rect.top + top, rect.right - right, rect.bottom - bottom);
+  }
+
+  @override
+  EdgeInsetsGeometry subtract(EdgeInsetsGeometry other) {
+    if (other is EdgeInsets)
+      return this - other;
+    return super.subtract(other);
+  }
+
+  @override
+  EdgeInsetsGeometry add(EdgeInsetsGeometry other) {
+    if (other is EdgeInsets)
+      return this + other;
+    return super.add(other);
+  }
+
+  @override
+  EdgeInsetsGeometry clamp(EdgeInsetsGeometry min, EdgeInsetsGeometry max) {
+    return EdgeInsets.fromLTRB(
+      _left.clamp(min._left, max._left),
+      _top.clamp(min._top, max._top),
+      _right.clamp(min._right, max._right),
+      _bottom.clamp(min._bottom, max._bottom),
+    );
+  }
+
+  /// Returns the difference between two [EdgeInsets].
+  EdgeInsets operator -(EdgeInsets other) {
+    return EdgeInsets.fromLTRB(
+      left - other.left,
+      top - other.top,
+      right - other.right,
+      bottom - other.bottom,
+    );
+  }
+
+  /// Returns the sum of two [EdgeInsets].
+  EdgeInsets operator +(EdgeInsets other) {
+    return EdgeInsets.fromLTRB(
+      left + other.left,
+      top + other.top,
+      right + other.right,
+      bottom + other.bottom,
+    );
+  }
+
+  /// Returns the [EdgeInsets] object with each dimension negated.
+  ///
+  /// This is the same as multiplying the object by -1.0.
+  @override
+  EdgeInsets operator -() {
+    return EdgeInsets.fromLTRB(
+      -left,
+      -top,
+      -right,
+      -bottom,
+    );
+  }
+
+  /// Scales the [EdgeInsets] in each dimension by the given factor.
+  @override
+  EdgeInsets operator *(double other) {
+    return EdgeInsets.fromLTRB(
+      left * other,
+      top * other,
+      right * other,
+      bottom * other,
+    );
+  }
+
+  /// Divides the [EdgeInsets] in each dimension by the given factor.
+  @override
+  EdgeInsets operator /(double other) {
+    return EdgeInsets.fromLTRB(
+      left / other,
+      top / other,
+      right / other,
+      bottom / other,
+    );
+  }
+
+  /// Integer divides the [EdgeInsets] in each dimension by the given factor.
+  @override
+  EdgeInsets operator ~/(double other) {
+    return EdgeInsets.fromLTRB(
+      (left ~/ other).toDouble(),
+      (top ~/ other).toDouble(),
+      (right ~/ other).toDouble(),
+      (bottom ~/ other).toDouble(),
+    );
+  }
+
+  /// Computes the remainder in each dimension by the given factor.
+  @override
+  EdgeInsets operator %(double other) {
+    return EdgeInsets.fromLTRB(
+      left % other,
+      top % other,
+      right % other,
+      bottom % other,
+    );
+  }
+
+  /// Linearly interpolate between two [EdgeInsets].
+  ///
+  /// If either is null, this function interpolates from [EdgeInsets.zero].
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static EdgeInsets? lerp(EdgeInsets? a, EdgeInsets? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b! * t;
+    if (b == null)
+      return a * (1.0 - t);
+    return EdgeInsets.fromLTRB(
+      ui.lerpDouble(a.left, b.left, t)!,
+      ui.lerpDouble(a.top, b.top, t)!,
+      ui.lerpDouble(a.right, b.right, t)!,
+      ui.lerpDouble(a.bottom, b.bottom, t)!,
+    );
+  }
+
+  @override
+  EdgeInsets resolve(TextDirection? direction) => this;
+
+  /// Creates a copy of this EdgeInsets but with the given fields replaced
+  /// with the new values.
+  EdgeInsets copyWith({
+    double? left,
+    double? top,
+    double? right,
+    double? bottom,
+  }) {
+    return EdgeInsets.only(
+      left: left ?? this.left,
+      top: top ?? this.top,
+      right: right ?? this.right,
+      bottom: bottom ?? this.bottom,
+    );
+  }
+}
+
+/// An immutable set of offsets in each of the four cardinal directions, but
+/// whose horizontal components are dependent on the writing direction.
+///
+/// This can be used to indicate padding from the left in [TextDirection.ltr]
+/// text and padding from the right in [TextDirection.rtl] text without having
+/// to be aware of the current text direction.
+///
+/// See also:
+///
+///  * [EdgeInsets], a variant that uses physical labels (left and right instead
+///    of start and end).
+class EdgeInsetsDirectional extends EdgeInsetsGeometry {
+  /// Creates insets from offsets from the start, top, end, and bottom.
+  const EdgeInsetsDirectional.fromSTEB(this.start, this.top, this.end, this.bottom);
+
+  /// Creates insets with only the given values non-zero.
+  ///
+  /// {@tool snippet}
+  ///
+  /// A margin indent of 40 pixels on the leading side:
+  ///
+  /// ```dart
+  /// const EdgeInsetsDirectional.only(start: 40.0)
+  /// ```
+  /// {@end-tool}
+  const EdgeInsetsDirectional.only({
+    this.start = 0.0,
+    this.top = 0.0,
+    this.end = 0.0,
+    this.bottom = 0.0,
+  });
+
+  /// An [EdgeInsetsDirectional] with zero offsets in each direction.
+  ///
+  /// Consider using [EdgeInsets.zero] instead, since that object has the same
+  /// effect, but will be cheaper to [resolve].
+  static const EdgeInsetsDirectional zero = EdgeInsetsDirectional.only();
+
+  /// The offset from the start side, the side from which the user will start
+  /// reading text.
+  ///
+  /// This value is normalized into an [EdgeInsets.left] or [EdgeInsets.right]
+  /// value by the [resolve] method.
+  final double start;
+
+  @override
+  double get _start => start;
+
+  /// The offset from the top.
+  ///
+  /// This value is passed through to [EdgeInsets.top] unmodified by the
+  /// [resolve] method.
+  final double top;
+
+  @override
+  double get _top => top;
+
+  /// The offset from the end side, the side on which the user ends reading
+  /// text.
+  ///
+  /// This value is normalized into an [EdgeInsets.left] or [EdgeInsets.right]
+  /// value by the [resolve] method.
+  final double end;
+
+  @override
+  double get _end => end;
+
+  /// The offset from the bottom.
+  ///
+  /// This value is passed through to [EdgeInsets.bottom] unmodified by the
+  /// [resolve] method.
+  final double bottom;
+
+  @override
+  double get _bottom => bottom;
+
+  @override
+  double get _left => 0.0;
+
+  @override
+  double get _right => 0.0;
+
+  @override
+  bool get isNonNegative => start >= 0.0 && top >= 0.0 && end >= 0.0 && bottom >= 0.0;
+
+  /// An [EdgeInsetsDirectional] with [top] and [bottom] as well as [start] and [end] flipped.
+  @override
+  EdgeInsetsDirectional get flipped => EdgeInsetsDirectional.fromSTEB(end, bottom, start, top);
+
+  @override
+  EdgeInsetsGeometry subtract(EdgeInsetsGeometry other) {
+    if (other is EdgeInsetsDirectional)
+      return this - other;
+    return super.subtract(other);
+  }
+
+  @override
+  EdgeInsetsGeometry add(EdgeInsetsGeometry other) {
+    if (other is EdgeInsetsDirectional)
+      return this + other;
+    return super.add(other);
+  }
+
+  /// Returns the difference between two [EdgeInsetsDirectional] objects.
+  EdgeInsetsDirectional operator -(EdgeInsetsDirectional other) {
+    return EdgeInsetsDirectional.fromSTEB(
+      start - other.start,
+      top - other.top,
+      end - other.end,
+      bottom - other.bottom,
+    );
+  }
+
+  /// Returns the sum of two [EdgeInsetsDirectional] objects.
+  EdgeInsetsDirectional operator +(EdgeInsetsDirectional other) {
+    return EdgeInsetsDirectional.fromSTEB(
+      start + other.start,
+      top + other.top,
+      end + other.end,
+      bottom + other.bottom,
+    );
+  }
+
+  /// Returns the [EdgeInsetsDirectional] object with each dimension negated.
+  ///
+  /// This is the same as multiplying the object by -1.0.
+  @override
+  EdgeInsetsDirectional operator -() {
+    return EdgeInsetsDirectional.fromSTEB(
+      -start,
+      -top,
+      -end,
+      -bottom,
+    );
+  }
+
+  /// Scales the [EdgeInsetsDirectional] object in each dimension by the given factor.
+  @override
+  EdgeInsetsDirectional operator *(double other) {
+    return EdgeInsetsDirectional.fromSTEB(
+      start * other,
+      top * other,
+      end * other,
+      bottom * other,
+    );
+  }
+
+  /// Divides the [EdgeInsetsDirectional] object in each dimension by the given factor.
+  @override
+  EdgeInsetsDirectional operator /(double other) {
+    return EdgeInsetsDirectional.fromSTEB(
+      start / other,
+      top / other,
+      end / other,
+      bottom / other,
+    );
+  }
+
+  /// Integer divides the [EdgeInsetsDirectional] object in each dimension by the given factor.
+  @override
+  EdgeInsetsDirectional operator ~/(double other) {
+    return EdgeInsetsDirectional.fromSTEB(
+      (start ~/ other).toDouble(),
+      (top ~/ other).toDouble(),
+      (end ~/ other).toDouble(),
+      (bottom ~/ other).toDouble(),
+    );
+  }
+
+  /// Computes the remainder in each dimension by the given factor.
+  @override
+  EdgeInsetsDirectional operator %(double other) {
+    return EdgeInsetsDirectional.fromSTEB(
+      start % other,
+      top % other,
+      end % other,
+      bottom % other,
+    );
+  }
+
+  /// Linearly interpolate between two [EdgeInsetsDirectional].
+  ///
+  /// If either is null, this function interpolates from [EdgeInsetsDirectional.zero].
+  ///
+  /// To interpolate between two [EdgeInsetsGeometry] objects of arbitrary type
+  /// (either [EdgeInsets] or [EdgeInsetsDirectional]), consider the
+  /// [EdgeInsetsGeometry.lerp] static method.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static EdgeInsetsDirectional? lerp(EdgeInsetsDirectional? a, EdgeInsetsDirectional? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b! * t;
+    if (b == null)
+      return a * (1.0 - t);
+    return EdgeInsetsDirectional.fromSTEB(
+      ui.lerpDouble(a.start, b.start, t)!,
+      ui.lerpDouble(a.top, b.top, t)!,
+      ui.lerpDouble(a.end, b.end, t)!,
+      ui.lerpDouble(a.bottom, b.bottom, t)!,
+    );
+  }
+
+  @override
+  EdgeInsets resolve(TextDirection? direction) {
+    assert(direction != null);
+    switch (direction!) {
+      case TextDirection.rtl:
+        return EdgeInsets.fromLTRB(end, top, start, bottom);
+      case TextDirection.ltr:
+        return EdgeInsets.fromLTRB(start, top, end, bottom);
+    }
+  }
+}
+
+class _MixedEdgeInsets extends EdgeInsetsGeometry {
+  const _MixedEdgeInsets.fromLRSETB(this._left, this._right, this._start, this._end, this._top, this._bottom);
+
+  @override
+  final double _left;
+
+  @override
+  final double _right;
+
+  @override
+  final double _start;
+
+  @override
+  final double _end;
+
+  @override
+  final double _top;
+
+  @override
+  final double _bottom;
+
+  @override
+  bool get isNonNegative {
+    return _left >= 0.0
+        && _right >= 0.0
+        && _start >= 0.0
+        && _end >= 0.0
+        && _top >= 0.0
+        && _bottom >= 0.0;
+  }
+
+  @override
+  _MixedEdgeInsets operator -() {
+    return _MixedEdgeInsets.fromLRSETB(
+      -_left,
+      -_right,
+      -_start,
+      -_end,
+      -_top,
+      -_bottom,
+    );
+  }
+
+  @override
+  _MixedEdgeInsets operator *(double other) {
+    return _MixedEdgeInsets.fromLRSETB(
+      _left * other,
+      _right * other,
+      _start * other,
+      _end * other,
+      _top * other,
+      _bottom * other,
+    );
+  }
+
+  @override
+  _MixedEdgeInsets operator /(double other) {
+    return _MixedEdgeInsets.fromLRSETB(
+      _left / other,
+      _right / other,
+      _start / other,
+      _end / other,
+      _top / other,
+      _bottom / other,
+    );
+  }
+
+  @override
+  _MixedEdgeInsets operator ~/(double other) {
+    return _MixedEdgeInsets.fromLRSETB(
+      (_left ~/ other).toDouble(),
+      (_right ~/ other).toDouble(),
+      (_start ~/ other).toDouble(),
+      (_end ~/ other).toDouble(),
+      (_top ~/ other).toDouble(),
+      (_bottom ~/ other).toDouble(),
+    );
+  }
+
+  @override
+  _MixedEdgeInsets operator %(double other) {
+    return _MixedEdgeInsets.fromLRSETB(
+      _left % other,
+      _right % other,
+      _start % other,
+      _end % other,
+      _top % other,
+      _bottom % other,
+    );
+  }
+
+  @override
+  EdgeInsets resolve(TextDirection? direction) {
+    assert(direction != null);
+    switch (direction!) {
+      case TextDirection.rtl:
+        return EdgeInsets.fromLTRB(_end + _left, _top, _start + _right, _bottom);
+      case TextDirection.ltr:
+        return EdgeInsets.fromLTRB(_start + _left, _top, _end + _right, _bottom);
+    }
+  }
+}
diff --git a/lib/src/painting/flutter_logo.dart b/lib/src/painting/flutter_logo.dart
new file mode 100644
index 0000000..9652678
--- /dev/null
+++ b/lib/src/painting/flutter_logo.dart
@@ -0,0 +1,446 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+import 'dart:typed_data';
+import 'package:flute/ui.dart' as ui show Gradient, TextBox, lerpDouble;
+
+import 'package:flute/foundation.dart';
+
+import 'alignment.dart';
+import 'basic_types.dart';
+import 'box_fit.dart';
+import 'colors.dart';
+import 'decoration.dart';
+import 'edge_insets.dart';
+import 'image_provider.dart';
+import 'text_painter.dart';
+import 'text_span.dart';
+import 'text_style.dart';
+
+/// Possible ways to draw Flutter's logo.
+enum FlutterLogoStyle {
+  /// Show only Flutter's logo, not the "Flutter" label.
+  ///
+  /// This is the default behavior for [FlutterLogoDecoration] objects.
+  markOnly,
+
+  /// Show Flutter's logo on the left, and the "Flutter" label to its right.
+  horizontal,
+
+  /// Show Flutter's logo above the "Flutter" label.
+  stacked,
+}
+
+/// An immutable description of how to paint Flutter's logo.
+class FlutterLogoDecoration extends Decoration {
+  /// Creates a decoration that knows how to paint Flutter's logo.
+  ///
+  /// The [style] controls whether and where to draw the "Flutter" label. If one
+  /// is shown, the [textColor] controls the color of the label.
+  ///
+  /// The [textColor], [style], and [margin] arguments must not be null.
+  const FlutterLogoDecoration({
+    this.textColor = const Color(0xFF757575),
+    this.style = FlutterLogoStyle.markOnly,
+    this.margin = EdgeInsets.zero,
+  }) : assert(textColor != null),
+       assert(style != null),
+       assert(margin != null),
+       _position = identical(style, FlutterLogoStyle.markOnly) ? 0.0 : identical(style, FlutterLogoStyle.horizontal) ? 1.0 : -1.0,
+       _opacity = 1.0;
+
+  const FlutterLogoDecoration._(this.textColor, this.style, this.margin, this._position, this._opacity);
+
+  /// The color used to paint the "Flutter" text on the logo, if [style] is
+  /// [FlutterLogoStyle.horizontal] or [FlutterLogoStyle.stacked].
+  ///
+  /// If possible, the default (a medium grey) should be used against a white
+  /// background.
+  final Color textColor;
+
+  /// Whether and where to draw the "Flutter" text. By default, only the logo
+  /// itself is drawn.
+  // This property isn't actually used when painting. It's only really used to
+  // set the internal _position property.
+  final FlutterLogoStyle style;
+
+  /// How far to inset the logo from the edge of the container.
+  final EdgeInsets margin;
+
+  // The following are set when lerping, to represent states that can't be
+  // represented by the constructor.
+  final double _position; // -1.0 for stacked, 1.0 for horizontal, 0.0 for no logo
+  final double _opacity; // 0.0 .. 1.0
+
+  bool get _inTransition => _opacity != 1.0 || (_position != -1.0 && _position != 0.0 && _position != 1.0);
+
+  @override
+  bool debugAssertIsValid() {
+    assert(textColor != null
+        && style != null
+        && margin != null
+        && _position != null
+        && _position.isFinite
+        && _opacity != null
+        && _opacity >= 0.0
+        && _opacity <= 1.0);
+    return true;
+  }
+
+  @override
+  bool get isComplex => !_inTransition;
+
+  /// Linearly interpolate between two Flutter logo descriptions.
+  ///
+  /// Interpolates both the color and the style in a continuous fashion.
+  ///
+  /// If both values are null, this returns null. Otherwise, it returns a
+  /// non-null value. If one of the values is null, then the result is obtained
+  /// by scaling the other value's opacity and [margin].
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  ///
+  /// See also:
+  ///
+  ///  * [Decoration.lerp], which interpolates between arbitrary decorations.
+  static FlutterLogoDecoration? lerp(FlutterLogoDecoration? a, FlutterLogoDecoration? b, double t) {
+    assert(t != null);
+    assert(a == null || a.debugAssertIsValid());
+    assert(b == null || b.debugAssertIsValid());
+    if (a == null && b == null)
+      return null;
+    if (a == null) {
+      return FlutterLogoDecoration._(
+        b!.textColor,
+        b.style,
+        b.margin * t,
+        b._position,
+        b._opacity * t.clamp(0.0, 1.0),
+      );
+    }
+    if (b == null) {
+      return FlutterLogoDecoration._(
+        a.textColor,
+        a.style,
+        a.margin * t,
+        a._position,
+        a._opacity * (1.0 - t).clamp(0.0, 1.0),
+      );
+    }
+    if (t == 0.0)
+      return a;
+    if (t == 1.0)
+      return b;
+    return FlutterLogoDecoration._(
+      Color.lerp(a.textColor, b.textColor, t)!,
+      t < 0.5 ? a.style : b.style,
+      EdgeInsets.lerp(a.margin, b.margin, t)!,
+      a._position + (b._position - a._position) * t,
+      (a._opacity + (b._opacity - a._opacity) * t).clamp(0.0, 1.0),
+    );
+  }
+
+  @override
+  FlutterLogoDecoration? lerpFrom(Decoration? a, double t) {
+    assert(debugAssertIsValid());
+    if (a == null || a is FlutterLogoDecoration) {
+      assert(a == null || a.debugAssertIsValid());
+      return FlutterLogoDecoration.lerp(a as FlutterLogoDecoration?, this, t);
+    }
+    return super.lerpFrom(a, t) as FlutterLogoDecoration?;
+  }
+
+  @override
+  FlutterLogoDecoration? lerpTo(Decoration? b, double t) {
+    assert(debugAssertIsValid());
+    if (b == null || b is FlutterLogoDecoration) {
+      assert(b == null || b.debugAssertIsValid());
+      return FlutterLogoDecoration.lerp(this, b as FlutterLogoDecoration?, t);
+    }
+    return super.lerpTo(b, t) as FlutterLogoDecoration?;
+  }
+
+  @override
+  // TODO(ianh): better hit testing
+  bool hitTest(Size size, Offset position, { TextDirection? textDirection }) => true;
+
+  @override
+  BoxPainter createBoxPainter([ VoidCallback? onChanged ]) {
+    assert(debugAssertIsValid());
+    return _FlutterLogoPainter(this);
+  }
+
+  @override
+  Path getClipPath(Rect rect, TextDirection textDirection) {
+    return Path()..addRect(rect);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    assert(debugAssertIsValid());
+    if (identical(this, other))
+      return true;
+    return other is FlutterLogoDecoration
+        && other.textColor == textColor
+        && other._position == _position
+        && other._opacity == _opacity;
+  }
+
+  @override
+  int get hashCode {
+    assert(debugAssertIsValid());
+    return hashValues(
+      textColor,
+      _position,
+      _opacity,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ColorProperty('textColor', textColor));
+    properties.add(EnumProperty<FlutterLogoStyle>('style', style));
+    if (_inTransition)
+      properties.add(DiagnosticsNode.message('transition ${debugFormatDouble(_position)}:${debugFormatDouble(_opacity)}'));
+  }
+}
+
+
+/// An object that paints a [BoxDecoration] into a canvas.
+class _FlutterLogoPainter extends BoxPainter {
+  _FlutterLogoPainter(this._config)
+      : assert(_config != null),
+        assert(_config.debugAssertIsValid()),
+        super(null) {
+    _prepareText();
+  }
+
+  final FlutterLogoDecoration _config;
+
+  // these are configured assuming a font size of 100.0.
+  late TextPainter _textPainter;
+  late Rect _textBoundingRect;
+
+  void _prepareText() {
+    const String kLabel = 'Flutter';
+    _textPainter = TextPainter(
+      text: TextSpan(
+        text: kLabel,
+        style: TextStyle(
+          color: _config.textColor,
+          fontFamily: 'Roboto',
+          fontSize: 100.0 * 350.0 / 247.0, // 247 is the height of the F when the fontSize is 350, assuming device pixel ratio 1.0
+          fontWeight: FontWeight.w300,
+          textBaseline: TextBaseline.alphabetic,
+        ),
+      ),
+      textDirection: TextDirection.ltr,
+    );
+    _textPainter.layout();
+    final ui.TextBox textSize = _textPainter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: kLabel.length)).single;
+    _textBoundingRect = Rect.fromLTRB(textSize.left, textSize.top, textSize.right, textSize.bottom);
+  }
+
+  // This class contains a lot of magic numbers. They were derived from the
+  // values in the SVG files exported from the original artwork source.
+
+  void _paintLogo(Canvas canvas, Rect rect) {
+    // Our points are in a coordinate space that's 166 pixels wide and 202 pixels high.
+    // First, transform the rectangle so that our coordinate space is a square 202 pixels
+    // to a side, with the top left at the origin.
+    canvas.save();
+    canvas.translate(rect.left, rect.top);
+    canvas.scale(rect.width / 202.0, rect.height / 202.0);
+    // Next, offset it some more so that the 166 horizontal pixels are centered
+    // in that square (as opposed to being on the left side of it). This means
+    // that if we draw in the rectangle from 0,0 to 166,202, we are drawing in
+    // the center of the given rect.
+    canvas.translate((202.0 - 166.0) / 2.0, 0.0);
+
+    // Set up the styles.
+    final Paint lightPaint = Paint()
+      ..color = const Color(0xFF54C5F8);
+    final Paint mediumPaint = Paint()
+      ..color = const Color(0xFF29B6F6);
+    final Paint darkPaint = Paint()
+      ..color = const Color(0xFF01579B);
+
+    final ui.Gradient triangleGradient = ui.Gradient.linear(
+      const Offset(87.2623 + 37.9092, 28.8384 + 123.4389),
+      const Offset(42.9205 + 37.9092, 35.0952 + 123.4389),
+      <Color>[
+        const Color(0x001A237E),
+        const Color(0x661A237E),
+      ],
+    );
+    final Paint trianglePaint = Paint()
+      ..shader = triangleGradient;
+
+    // Draw the basic shape.
+    final Path topBeam = Path()
+      ..moveTo(37.7, 128.9)
+      ..lineTo(9.8, 101.0)
+      ..lineTo(100.4, 10.4)
+      ..lineTo(156.2, 10.4);
+    canvas.drawPath(topBeam, lightPaint);
+
+    final Path middleBeam = Path()
+      ..moveTo(156.2, 94.0)
+      ..lineTo(100.4, 94.0)
+      ..lineTo(78.5, 115.9)
+      ..lineTo(106.4, 143.8);
+    canvas.drawPath(middleBeam, lightPaint);
+
+    final Path bottomBeam = Path()
+      ..moveTo(79.5, 170.7)
+      ..lineTo(100.4, 191.6)
+      ..lineTo(156.2, 191.6)
+      ..lineTo(107.4, 142.8);
+    canvas.drawPath(bottomBeam, darkPaint);
+
+    // The overlap between middle and bottom beam.
+    canvas.save();
+    canvas.transform(Float64List.fromList(const <double>[
+      // careful, this is in _column_-major order
+      0.7071, -0.7071, 0.0, 0.0,
+      0.7071, 0.7071, 0.0, 0.0,
+      0.0, 0.0, 1.0, 0.0,
+      -77.697, 98.057, 0.0, 1.0,
+    ]));
+    canvas.drawRect(const Rect.fromLTWH(59.8, 123.1, 39.4, 39.4), mediumPaint);
+    canvas.restore();
+
+    // The gradients below the middle beam on top of the bottom beam.
+    final Path triangle = Path()
+      ..moveTo(79.5, 170.7)
+      ..lineTo(120.9, 156.4)
+      ..lineTo(107.4, 142.8);
+    canvas.drawPath(triangle, trianglePaint);
+
+    canvas.restore();
+  }
+
+  @override
+  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
+    offset += _config.margin.topLeft;
+    final Size canvasSize = _config.margin.deflateSize(configuration.size!);
+    if (canvasSize.isEmpty)
+      return;
+    final Size logoSize;
+    if (_config._position > 0.0) {
+      // horizontal style
+      logoSize = const Size(820.0, 232.0);
+    } else if (_config._position < 0.0) {
+      // stacked style
+      logoSize = const Size(252.0, 306.0);
+    } else {
+      // only the mark
+      logoSize = const Size(202.0, 202.0);
+    }
+    final FittedSizes fittedSize = applyBoxFit(BoxFit.contain, logoSize, canvasSize);
+    assert(fittedSize.source == logoSize);
+    final Rect rect = Alignment.center.inscribe(fittedSize.destination, offset & canvasSize);
+    final double centerSquareHeight = canvasSize.shortestSide;
+    final Rect centerSquare = Rect.fromLTWH(
+      offset.dx + (canvasSize.width - centerSquareHeight) / 2.0,
+      offset.dy + (canvasSize.height - centerSquareHeight) / 2.0,
+      centerSquareHeight,
+      centerSquareHeight,
+    );
+
+    final Rect logoTargetSquare;
+    if (_config._position > 0.0) {
+      // horizontal style
+      logoTargetSquare = Rect.fromLTWH(rect.left, rect.top, rect.height, rect.height);
+    } else if (_config._position < 0.0) {
+      // stacked style
+      final double logoHeight = rect.height * 191.0 / 306.0;
+      logoTargetSquare = Rect.fromLTWH(
+        rect.left + (rect.width - logoHeight) / 2.0,
+        rect.top,
+        logoHeight,
+        logoHeight,
+      );
+    } else {
+      // only the mark
+      logoTargetSquare = centerSquare;
+    }
+    final Rect logoSquare = Rect.lerp(centerSquare, logoTargetSquare, _config._position.abs())!;
+
+    if (_config._opacity < 1.0) {
+      canvas.saveLayer(
+        offset & canvasSize,
+        Paint()
+          ..colorFilter = ColorFilter.mode(
+            const Color(0xFFFFFFFF).withOpacity(_config._opacity),
+            BlendMode.modulate,
+          ),
+      );
+    }
+    if (_config._position != 0.0) {
+      if (_config._position > 0.0) {
+        // horizontal style
+        final double fontSize = 2.0 / 3.0 * logoSquare.height * (1 - (10.4 * 2.0) / 202.0);
+        final double scale = fontSize / 100.0;
+        final double finalLeftTextPosition = // position of text in rest position
+          (256.4 / 820.0) * rect.width - // 256.4 is the distance from the left edge to the left of the F when the whole logo is 820.0 wide
+          (32.0 / 350.0) * fontSize; // 32 is the distance from the text bounding box edge to the left edge of the F when the font size is 350
+        final double initialLeftTextPosition = // position of text when just starting the animation
+          rect.width / 2.0 - _textBoundingRect.width * scale;
+        final Offset textOffset = Offset(
+          rect.left + ui.lerpDouble(initialLeftTextPosition, finalLeftTextPosition, _config._position)!,
+          rect.top + (rect.height - _textBoundingRect.height * scale) / 2.0,
+        );
+        canvas.save();
+        if (_config._position < 1.0) {
+          final Offset center = logoSquare.center;
+          final Path path = Path()
+            ..moveTo(center.dx, center.dy)
+            ..lineTo(center.dx + rect.width, center.dy - rect.width)
+            ..lineTo(center.dx + rect.width, center.dy + rect.width)
+            ..close();
+          canvas.clipPath(path);
+        }
+        canvas.translate(textOffset.dx, textOffset.dy);
+        canvas.scale(scale, scale);
+        _textPainter.paint(canvas, Offset.zero);
+        canvas.restore();
+      } else if (_config._position < 0.0) {
+        // stacked style
+        final double fontSize = 0.35 * logoTargetSquare.height * (1 - (10.4 * 2.0) / 202.0);
+        final double scale = fontSize / 100.0;
+        if (_config._position > -1.0) {
+          // This limits what the drawRect call below is going to blend with.
+          canvas.saveLayer(_textBoundingRect, Paint());
+        } else {
+          canvas.save();
+        }
+        canvas.translate(
+          logoTargetSquare.center.dx - (_textBoundingRect.width * scale / 2.0),
+          logoTargetSquare.bottom,
+        );
+        canvas.scale(scale, scale);
+        _textPainter.paint(canvas, Offset.zero);
+        if (_config._position > -1.0) {
+          canvas.drawRect(_textBoundingRect.inflate(_textBoundingRect.width * 0.5), Paint()
+            ..blendMode = BlendMode.modulate
+            ..shader = ui.Gradient.linear(
+              Offset(_textBoundingRect.width * -0.5, 0.0),
+              Offset(_textBoundingRect.width * 1.5, 0.0),
+              <Color>[const Color(0xFFFFFFFF), const Color(0xFFFFFFFF), const Color(0x00FFFFFF), const Color(0x00FFFFFF)],
+              <double>[ 0.0, math.max(0.0, _config._position.abs() - 0.1), math.min(_config._position.abs() + 0.1, 1.0), 1.0 ],
+            ),
+          );
+        }
+        canvas.restore();
+      }
+    }
+    _paintLogo(canvas, logoSquare);
+    if (_config._opacity < 1.0)
+      canvas.restore();
+  }
+}
diff --git a/lib/src/painting/fractional_offset.dart b/lib/src/painting/fractional_offset.dart
new file mode 100644
index 0000000..61d90b0
--- /dev/null
+++ b/lib/src/painting/fractional_offset.dart
@@ -0,0 +1,197 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' as ui show lerpDouble;
+
+import 'package:flute/foundation.dart';
+
+import 'alignment.dart';
+import 'basic_types.dart';
+
+/// An offset that's expressed as a fraction of a [Size].
+///
+/// `FractionalOffset(1.0, 0.0)` represents the top right of the [Size].
+///
+/// `FractionalOffset(0.0, 1.0)` represents the bottom left of the [Size].
+///
+/// `FractionalOffset(0.5, 2.0)` represents a point half way across the [Size],
+/// below the bottom of the rectangle by the height of the [Size].
+///
+/// The [FractionalOffset] class specifies offsets in terms of a distance from
+/// the top left, regardless of the [TextDirection].
+///
+/// ## Design discussion
+///
+/// [FractionalOffset] and [Alignment] are two different representations of the
+/// same information: the location within a rectangle relative to the size of
+/// the rectangle. The difference between the two classes is in the coordinate
+/// system they use to represent the location.
+///
+/// [FractionalOffset] uses a coordinate system with an origin in the top-left
+/// corner of the rectangle whereas [Alignment] uses a coordinate system with an
+/// origin in the center of the rectangle.
+///
+/// Historically, [FractionalOffset] predates [Alignment]. When we attempted to
+/// make a version of [FractionalOffset] that adapted to the [TextDirection], we
+/// ran into difficulty because placing the origin in the top-left corner
+/// introduced a left-to-right bias that was hard to remove.
+///
+/// By placing the origin in the center, [Alignment] and [AlignmentDirectional]
+/// are able to use the same origin, which means we can use a linear function to
+/// resolve an [AlignmentDirectional] into an [Alignment] in both
+/// [TextDirection.rtl] and [TextDirection.ltr].
+///
+/// [Alignment] is better for most purposes than [FractionalOffset] and should
+/// be used instead of [FractionalOffset]. We continue to implement
+/// [FractionalOffset] to support code that predates [Alignment].
+///
+/// See also:
+///
+///  * [Alignment], which uses a coordinate system based on the center of the
+///    rectangle instead of the top left corner of the rectangle.
+@immutable
+class FractionalOffset extends Alignment {
+  /// Creates a fractional offset.
+  ///
+  /// The [dx] and [dy] arguments must not be null.
+  const FractionalOffset(double dx, double dy)
+    : assert(dx != null),
+      assert(dy != null),
+      super(dx * 2.0 - 1.0, dy * 2.0 - 1.0);
+
+  /// Creates a fractional offset from a specific offset and size.
+  ///
+  /// The returned [FractionalOffset] describes the position of the
+  /// [Offset] in the [Size], as a fraction of the [Size].
+  factory FractionalOffset.fromOffsetAndSize(Offset offset, Size size) {
+    assert(size != null);
+    assert(offset != null);
+    return FractionalOffset(
+      offset.dx / size.width,
+      offset.dy / size.height,
+    );
+  }
+
+  /// Creates a fractional offset from a specific offset and rectangle.
+  ///
+  /// The offset is assumed to be relative to the same origin as the rectangle.
+  ///
+  /// If the offset is relative to the top left of the rectangle, use [new
+  /// FractionalOffset.fromOffsetAndSize] instead, passing `rect.size`.
+  ///
+  /// The returned [FractionalOffset] describes the position of the
+  /// [Offset] in the [Rect], as a fraction of the [Rect].
+  factory FractionalOffset.fromOffsetAndRect(Offset offset, Rect rect) {
+    return FractionalOffset.fromOffsetAndSize(
+      offset - rect.topLeft,
+      rect.size,
+    );
+  }
+
+  /// The distance fraction in the horizontal direction.
+  ///
+  /// A value of 0.0 corresponds to the leftmost edge. A value of 1.0
+  /// corresponds to the rightmost edge. Values are not limited to that range;
+  /// negative values represent positions to the left of the left edge, and
+  /// values greater than 1.0 represent positions to the right of the right
+  /// edge.
+  double get dx => (x + 1.0) / 2.0;
+
+  /// The distance fraction in the vertical direction.
+  ///
+  /// A value of 0.0 corresponds to the topmost edge. A value of 1.0 corresponds
+  /// to the bottommost edge. Values are not limited to that range; negative
+  /// values represent positions above the top, and values greater than 1.0
+  /// represent positions below the bottom.
+  double get dy => (y + 1.0) / 2.0;
+
+  /// The top left corner.
+  static const FractionalOffset topLeft = FractionalOffset(0.0, 0.0);
+
+  /// The center point along the top edge.
+  static const FractionalOffset topCenter = FractionalOffset(0.5, 0.0);
+
+  /// The top right corner.
+  static const FractionalOffset topRight = FractionalOffset(1.0, 0.0);
+
+  /// The center point along the left edge.
+  static const FractionalOffset centerLeft = FractionalOffset(0.0, 0.5);
+
+  /// The center point, both horizontally and vertically.
+  static const FractionalOffset center = FractionalOffset(0.5, 0.5);
+
+  /// The center point along the right edge.
+  static const FractionalOffset centerRight = FractionalOffset(1.0, 0.5);
+
+  /// The bottom left corner.
+  static const FractionalOffset bottomLeft = FractionalOffset(0.0, 1.0);
+
+  /// The center point along the bottom edge.
+  static const FractionalOffset bottomCenter = FractionalOffset(0.5, 1.0);
+
+  /// The bottom right corner.
+  static const FractionalOffset bottomRight = FractionalOffset(1.0, 1.0);
+
+  @override
+  Alignment operator -(Alignment other) {
+    if (other is! FractionalOffset)
+      return super - other;
+    return FractionalOffset(dx - other.dx, dy - other.dy);
+  }
+
+  @override
+  Alignment operator +(Alignment other) {
+    if (other is! FractionalOffset)
+      return super + other;
+    return FractionalOffset(dx + other.dx, dy + other.dy);
+  }
+
+  @override
+  FractionalOffset operator -() {
+    return FractionalOffset(-dx, -dy);
+  }
+
+  @override
+  FractionalOffset operator *(double other) {
+    return FractionalOffset(dx * other, dy * other);
+  }
+
+  @override
+  FractionalOffset operator /(double other) {
+    return FractionalOffset(dx / other, dy / other);
+  }
+
+  @override
+  FractionalOffset operator ~/(double other) {
+    return FractionalOffset((dx ~/ other).toDouble(), (dy ~/ other).toDouble());
+  }
+
+  @override
+  FractionalOffset operator %(double other) {
+    return FractionalOffset(dx % other, dy % other);
+  }
+
+  /// Linearly interpolate between two [FractionalOffset]s.
+  ///
+  /// If either is null, this function interpolates from [FractionalOffset.center].
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static FractionalOffset? lerp(FractionalOffset? a, FractionalOffset? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return FractionalOffset(ui.lerpDouble(0.5, b!.dx, t)!, ui.lerpDouble(0.5, b.dy, t)!);
+    if (b == null)
+      return FractionalOffset(ui.lerpDouble(a.dx, 0.5, t)!, ui.lerpDouble(a.dy, 0.5, t)!);
+    return FractionalOffset(ui.lerpDouble(a.dx, b.dx, t)!, ui.lerpDouble(a.dy, b.dy, t)!);
+  }
+
+  @override
+  String toString() {
+    return 'FractionalOffset(${dx.toStringAsFixed(1)}, '
+                            '${dy.toStringAsFixed(1)})';
+  }
+}
diff --git a/lib/src/painting/geometry.dart b/lib/src/painting/geometry.dart
new file mode 100644
index 0000000..bd6a7e8
--- /dev/null
+++ b/lib/src/painting/geometry.dart
@@ -0,0 +1,79 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+
+import 'basic_types.dart';
+
+/// Position a child box within a container box, either above or below a target
+/// point.
+///
+/// The container's size is described by `size`.
+///
+/// The target point is specified by `target`, as an offset from the top left of
+/// the container.
+///
+/// The child box's size is given by `childSize`.
+///
+/// The return value is the suggested distance from the top left of the
+/// container box to the top left of the child box.
+///
+/// The suggested position will be above the target point if `preferBelow` is
+/// false, and below the target point if it is true, unless it wouldn't fit on
+/// the preferred side but would fit on the other side.
+///
+/// The suggested position will place the nearest side of the child to the
+/// target point `verticalOffset` from the target point (even if it cannot fit
+/// given that constraint).
+///
+/// The suggested position will be at least `margin` away from the edge of the
+/// container. If possible, the child will be positioned so that its center is
+/// aligned with the target point. If the child cannot fit horizontally within
+/// the container given the margin, then the child will be centered in the
+/// container.
+///
+/// Used by [Tooltip] to position a tooltip relative to its parent.
+///
+/// The arguments must not be null.
+Offset positionDependentBox({
+  required Size size,
+  required Size childSize,
+  required Offset target,
+  required bool preferBelow,
+  double verticalOffset = 0.0,
+  double margin = 10.0,
+}) {
+  assert(size != null);
+  assert(childSize != null);
+  assert(target != null);
+  assert(verticalOffset != null);
+  assert(preferBelow != null);
+  assert(margin != null);
+  // VERTICAL DIRECTION
+  final bool fitsBelow = target.dy + verticalOffset + childSize.height <= size.height - margin;
+  final bool fitsAbove = target.dy - verticalOffset - childSize.height >= margin;
+  final bool tooltipBelow = preferBelow ? fitsBelow || !fitsAbove : !(fitsAbove || !fitsBelow);
+  double y;
+  if (tooltipBelow)
+    y = math.min(target.dy + verticalOffset, size.height - margin);
+  else
+    y = math.max(target.dy - verticalOffset - childSize.height, margin);
+  // HORIZONTAL DIRECTION
+  double x;
+  if (size.width - margin * 2.0 < childSize.width) {
+    x = (size.width - childSize.width) / 2.0;
+  } else {
+    final double normalizedTargetX = target.dx.clamp(margin, size.width - margin);
+    final double edge = margin + childSize.width / 2.0;
+    if (normalizedTargetX < edge) {
+      x = margin;
+    } else if (normalizedTargetX > size.width - edge) {
+      x = size.width - margin - childSize.width;
+    } else {
+      x = normalizedTargetX - childSize.width / 2.0;
+    }
+  }
+  return Offset(x, y);
+}
diff --git a/lib/src/painting/gradient.dart b/lib/src/painting/gradient.dart
new file mode 100644
index 0000000..1d7fe07
--- /dev/null
+++ b/lib/src/painting/gradient.dart
@@ -0,0 +1,1027 @@
+// Copyright 2014 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.
+
+
+import 'dart:collection';
+import 'dart:math' as math;
+import 'dart:typed_data';
+import 'package:flute/ui.dart' as ui show Gradient, lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'alignment.dart';
+import 'basic_types.dart';
+
+class _ColorsAndStops {
+  _ColorsAndStops(this.colors, this.stops);
+  final List<Color> colors;
+  final List<double> stops;
+}
+
+/// Calculate the color at position [t] of the gradient defined by [colors] and [stops].
+Color _sample(List<Color> colors, List<double> stops, double t) {
+  assert(colors != null);
+  assert(colors.isNotEmpty);
+  assert(stops != null);
+  assert(stops.isNotEmpty);
+  assert(t != null);
+  if (t <= stops.first)
+    return colors.first;
+  if (t >= stops.last)
+    return colors.last;
+  final int index = stops.lastIndexWhere((double s) => s <= t);
+  assert(index != -1);
+  return Color.lerp(
+      colors[index], colors[index + 1],
+      (t - stops[index]) / (stops[index + 1] - stops[index]),
+  )!;
+}
+
+_ColorsAndStops _interpolateColorsAndStops(
+  List<Color> aColors,
+  List<double> aStops,
+  List<Color> bColors,
+  List<double> bStops,
+  double t,
+) {
+  assert(aColors.length >= 2);
+  assert(bColors.length >= 2);
+  assert(aStops.length == aColors.length);
+  assert(bStops.length == bColors.length);
+  final SplayTreeSet<double> stops = SplayTreeSet<double>()
+    ..addAll(aStops)
+    ..addAll(bStops);
+  final List<double> interpolatedStops = stops.toList(growable: false);
+  final List<Color> interpolatedColors = interpolatedStops.map<Color>(
+          (double stop) => Color.lerp(_sample(aColors, aStops, stop), _sample(bColors, bStops, stop), t)!
+  ).toList(growable: false);
+  return _ColorsAndStops(interpolatedColors, interpolatedStops);
+}
+
+/// Base class for transforming gradient shaders without applying the same
+/// transform to the entire canvas.
+///
+/// For example, a [SweepGradient] normally starts its gradation at 3 o'clock
+/// and draws clockwise. To have the sweep appear to start at 6 o'clock, supply
+/// a [GradientRotation] of `pi/4` radians (i.e. 45 degrees).
+@immutable
+abstract class GradientTransform {
+  /// A const constructor so that subclasses may be const.
+  const GradientTransform();
+
+  /// When a [Gradient] creates its [Shader], it will call this method to
+  /// determine what transform to apply to the shader for the given [Rect] and
+  /// [TextDirection].
+  ///
+  /// Implementers may return null from this method, which achieves the same
+  /// final effect as returning [Matrix4.identity].
+  Matrix4? transform(Rect bounds, {TextDirection? textDirection});
+}
+
+/// A [GradientTransform] that rotates the gradient around the center-point of
+/// its bounding box.
+///
+/// {@tool snippet}
+///
+/// This sample would rotate a sweep gradient by a quarter turn clockwise:
+///
+/// ```dart
+/// const SweepGradient gradient = SweepGradient(
+///   colors: <Color>[Color(0xFFFFFFFF), Color(0xFF009900)],
+///   transform: GradientRotation(math.pi/4),
+/// );
+/// ```
+/// {@end-tool}
+@immutable
+class GradientRotation extends GradientTransform {
+  /// Constructs a [GradientRotation] for the specified angle.
+  ///
+  /// The angle is in radians in the clockwise direction.
+  const GradientRotation(this.radians);
+
+  /// The angle of rotation in radians in the clockwise direction.
+  final double radians;
+
+  @override
+  Matrix4 transform(Rect bounds, {TextDirection? textDirection}) {
+    assert(bounds != null);
+    final double sinRadians = math.sin(radians);
+    final double oneMinusCosRadians = 1 - math.cos(radians);
+    final Offset center = bounds.center;
+    final double originX = sinRadians * center.dy + oneMinusCosRadians * center.dx;
+    final double originY = -sinRadians * center.dx + oneMinusCosRadians * center.dy;
+
+    return Matrix4.identity()
+      ..translate(originX, originY)
+      ..rotateZ(radians);
+  }
+}
+
+/// A 2D gradient.
+///
+/// This is an interface that allows [LinearGradient], [RadialGradient], and
+/// [SweepGradient] classes to be used interchangeably in [BoxDecoration]s.
+///
+/// See also:
+///
+///  * [Gradient](dart-ui/Gradient-class.html), the class in the [dart:ui] library.
+///
+@immutable
+abstract class Gradient {
+  /// Initialize the gradient's colors and stops.
+  ///
+  /// The [colors] argument must not be null, and must have at least two colors
+  /// (the length is not verified until the [createShader] method is called).
+  ///
+  /// If specified, the [stops] argument must have the same number of entries as
+  /// [colors] (this is also not verified until the [createShader] method is
+  /// called).
+  ///
+  /// The [transform] argument can be applied to transform _only_ the gradient,
+  /// without rotating the canvas itself or other geometry on the canvas. For
+  /// example, a `GradientRotation(math.pi/4)` will result in a [SweepGradient]
+  /// that starts from a position of 6 o'clock instead of 3 o'clock, assuming
+  /// no other rotation or perspective transformations have been applied to the
+  /// [Canvas]. If null, no transformation is applied.
+  const Gradient({
+    required this.colors,
+    this.stops,
+    this.transform,
+  }) : assert(colors != null);
+
+  /// The colors the gradient should obtain at each of the stops.
+  ///
+  /// If [stops] is non-null, this list must have the same length as [stops].
+  ///
+  /// This list must have at least two colors in it (otherwise, it's not a
+  /// gradient!).
+  final List<Color> colors;
+
+  /// A list of values from 0.0 to 1.0 that denote fractions along the gradient.
+  ///
+  /// If non-null, this list must have the same length as [colors].
+  ///
+  /// If the first value is not 0.0, then a stop with position 0.0 and a color
+  /// equal to the first color in [colors] is implied.
+  ///
+  /// If the last value is not 1.0, then a stop with position 1.0 and a color
+  /// equal to the last color in [colors] is implied.
+  ///
+  /// The values in the [stops] list must be in ascending order. If a value in
+  /// the [stops] list is less than an earlier value in the list, then its value
+  /// is assumed to equal the previous value.
+  ///
+  /// If stops is null, then a set of uniformly distributed stops is implied,
+  /// with the first stop at 0.0 and the last stop at 1.0.
+  final List<double>? stops;
+
+  /// The transform, if any, to apply to the gradient.
+  ///
+  /// This transform is in addition to any other transformations applied to the
+  /// canvas, but does not add any transformations to the canvas.
+  final GradientTransform? transform;
+
+  List<double> _impliedStops() {
+    if (stops != null)
+      return stops!;
+    assert(colors.length >= 2, 'colors list must have at least two colors');
+    final double separation = 1.0 / (colors.length - 1);
+    return List<double>.generate(
+      colors.length,
+      (int index) => index * separation,
+      growable: false,
+    );
+  }
+
+  /// Creates a [Shader] for this gradient to fill the given rect.
+  ///
+  /// If the gradient's configuration is text-direction-dependent, for example
+  /// it uses [AlignmentDirectional] objects instead of [Alignment]
+  /// objects, then the `textDirection` argument must not be null.
+  ///
+  /// The shader's transform will be resolved from the [transform] of this
+  /// gradient.
+  @factory
+  Shader createShader(Rect rect, { TextDirection? textDirection });
+
+  /// Returns a new gradient with its properties scaled by the given factor.
+  ///
+  /// A factor of 0.0 (or less) should result in a variant of the gradient that
+  /// is invisible; any two factors epsilon apart should be unnoticeably
+  /// different from each other at first glance. From this it follows that
+  /// scaling a gradient with values from 1.0 to 0.0 over time should cause the
+  /// gradient to smoothly disappear.
+  ///
+  /// Typically this is the same as interpolating from null (with [lerp]).
+  Gradient scale(double factor);
+
+  /// Linearly interpolates from another [Gradient] to `this`.
+  ///
+  /// When implementing this method in subclasses, return null if this class
+  /// cannot interpolate from `a`. In that case, [lerp] will try `a`'s [lerpTo]
+  /// method instead.
+  ///
+  /// If `a` is null, this must not return null. The base class implements this
+  /// by deferring to [scale].
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `this` (or something equivalent to `this`), and values in
+  /// between meaning that the interpolation is at the relevant point on the
+  /// timeline between `a` and `this`. The interpolation can be extrapolated
+  /// beyond 0.0 and 1.0, so negative values and values greater than 1.0 are
+  /// valid (and can easily be generated by curves such as
+  /// [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  ///
+  /// Instead of calling this directly, use [Gradient.lerp].
+  @protected
+  Gradient? lerpFrom(Gradient? a, double t) {
+    if (a == null)
+      return scale(t);
+    return null;
+  }
+
+  /// Linearly interpolates from `this` to another [Gradient].
+  ///
+  /// This is called if `b`'s [lerpTo] did not know how to handle this class.
+  ///
+  /// When implementing this method in subclasses, return null if this class
+  /// cannot interpolate from `b`. In that case, [lerp] will apply a default
+  /// behavior instead.
+  ///
+  /// If `b` is null, this must not return null. The base class implements this
+  /// by deferring to [scale].
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `this` (or something
+  /// equivalent to `this`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `this` and `b`. The interpolation can be extrapolated beyond 0.0
+  /// and 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  ///
+  /// Instead of calling this directly, use [Gradient.lerp].
+  @protected
+  Gradient? lerpTo(Gradient? b, double t) {
+    if (b == null)
+      return scale(1.0 - t);
+    return null;
+  }
+
+  /// Linearly interpolates between two [Gradient]s.
+  ///
+  /// This defers to `b`'s [lerpTo] function if `b` is not null. If `b` is
+  /// null or if its [lerpTo] returns null, it uses `a`'s [lerpFrom]
+  /// function instead. If both return null, it returns `a` before `t == 0.5`
+  /// and `b` after `t == 0.5`.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static Gradient? lerp(Gradient? a, Gradient? b, double t) {
+    assert(t != null);
+    Gradient? result;
+    if (b != null)
+      result = b.lerpFrom(a, t); // if a is null, this must return non-null
+    if (result == null && a != null)
+      result = a.lerpTo(b, t); // if b is null, this must return non-null
+    if (result != null)
+      return result;
+    if (a == null && b == null)
+      return null;
+    assert(a != null && b != null);
+    return t < 0.5 ? a!.scale(1.0 - (t * 2.0)) : b!.scale((t - 0.5) * 2.0);
+  }
+
+  Float64List? _resolveTransform(Rect bounds, TextDirection? textDirection) {
+    return transform?.transform(bounds, textDirection: textDirection)?.storage;
+  }
+}
+
+/// A 2D linear gradient.
+///
+/// This class is used by [BoxDecoration] to represent linear gradients. This
+/// abstracts out the arguments to the [new ui.Gradient.linear] constructor from
+/// the `dart:ui` library.
+///
+/// A gradient has two anchor points, [begin] and [end]. The [begin] point
+/// corresponds to 0.0, and the [end] point corresponds to 1.0. These points are
+/// expressed in fractions, so that the same gradient can be reused with varying
+/// sized boxes without changing the parameters. (This contrasts with [new
+/// ui.Gradient.linear], whose arguments are expressed in logical pixels.)
+///
+/// The [colors] are described by a list of [Color] objects. There must be at
+/// least two colors. The [stops] list, if specified, must have the same length
+/// as [colors]. It specifies fractions of the vector from start to end, between
+/// 0.0 and 1.0, for each color. If it is null, a uniform distribution is
+/// assumed.
+///
+/// The region of the canvas before [begin] and after [end] is colored according
+/// to [tileMode].
+///
+/// Typically this class is used with [BoxDecoration], which does the painting.
+/// To use a [LinearGradient] to paint on a canvas directly, see [createShader].
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+/// This sample draws a picture that looks like vertical window shades by having
+/// a [Container] display a [BoxDecoration] with a [LinearGradient].
+///
+/// ```dart
+///  Widget build(BuildContext context) {
+///    return Container(
+///      decoration: BoxDecoration(
+///        gradient: LinearGradient(
+///          begin: Alignment.topLeft,
+///          end: Alignment(0.8, 0.0), // 10% of the width, so there are ten blinds.
+///          colors: [const Color(0xffee0000), const Color(0xffeeee00)], // red to yellow
+///          tileMode: TileMode.repeated, // repeats the gradient over the canvas
+///        ),
+///      ),
+///    );
+///  }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [RadialGradient], which displays a gradient in concentric circles, and
+///    has an example which shows a different way to use [Gradient] objects.
+///  * [SweepGradient], which displays a gradient in a sweeping arc around a
+///    center point.
+///  * [BoxDecoration], which can take a [LinearGradient] in its
+///    [BoxDecoration.gradient] property.
+class LinearGradient extends Gradient {
+  /// Creates a linear gradient.
+  ///
+  /// The [colors] argument must not be null. If [stops] is non-null, it must
+  /// have the same length as [colors].
+  const LinearGradient({
+    this.begin = Alignment.centerLeft,
+    this.end = Alignment.centerRight,
+    required List<Color> colors,
+    List<double>? stops,
+    this.tileMode = TileMode.clamp,
+    GradientTransform? transform,
+  }) : assert(begin != null),
+       assert(end != null),
+       assert(tileMode != null),
+       super(colors: colors, stops: stops, transform: transform);
+
+  /// The offset at which stop 0.0 of the gradient is placed.
+  ///
+  /// If this is an [Alignment], then it is expressed as a vector from
+  /// coordinate (0.0, 0.0), in a coordinate space that maps the center of the
+  /// paint box at (0.0, 0.0) and the bottom right at (1.0, 1.0).
+  ///
+  /// For example, a begin offset of (-1.0, 0.0) is half way down the
+  /// left side of the box.
+  ///
+  /// It can also be an [AlignmentDirectional], where the start is the
+  /// left in left-to-right contexts and the right in right-to-left contexts. If
+  /// a text-direction-dependent value is provided here, then the [createShader]
+  /// method will need to be given a [TextDirection].
+  final AlignmentGeometry begin;
+
+  /// The offset at which stop 1.0 of the gradient is placed.
+  ///
+  /// If this is an [Alignment], then it is expressed as a vector from
+  /// coordinate (0.0, 0.0), in a coordinate space that maps the center of the
+  /// paint box at (0.0, 0.0) and the bottom right at (1.0, 1.0).
+  ///
+  /// For example, a begin offset of (1.0, 0.0) is half way down the
+  /// right side of the box.
+  ///
+  /// It can also be an [AlignmentDirectional], where the start is the left in
+  /// left-to-right contexts and the right in right-to-left contexts. If a
+  /// text-direction-dependent value is provided here, then the [createShader]
+  /// method will need to be given a [TextDirection].
+  final AlignmentGeometry end;
+
+  /// How this gradient should tile the plane beyond in the region before
+  /// [begin] and after [end].
+  ///
+  /// For details, see [TileMode].
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_linear.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_linear.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_linear.png)
+  final TileMode tileMode;
+
+  @override
+  Shader createShader(Rect rect, { TextDirection? textDirection }) {
+    return ui.Gradient.linear(
+      begin.resolve(textDirection).withinRect(rect),
+      end.resolve(textDirection).withinRect(rect),
+      colors, _impliedStops(), tileMode, _resolveTransform(rect, textDirection),
+    );
+  }
+
+  /// Returns a new [LinearGradient] with its colors scaled by the given factor.
+  ///
+  /// Since the alpha component of the Color is what is scaled, a factor
+  /// of 0.0 or less results in a gradient that is fully transparent.
+  @override
+  LinearGradient scale(double factor) {
+    return LinearGradient(
+      begin: begin,
+      end: end,
+      colors: colors.map<Color>((Color color) => Color.lerp(null, color, factor)!).toList(),
+      stops: stops,
+      tileMode: tileMode,
+    );
+  }
+
+  @override
+  Gradient? lerpFrom(Gradient? a, double t) {
+    if (a == null || (a is LinearGradient))
+      return LinearGradient.lerp(a as LinearGradient?, this, t);
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  Gradient? lerpTo(Gradient? b, double t) {
+    if (b == null || (b is LinearGradient))
+      return LinearGradient.lerp(this, b as LinearGradient?, t);
+    return super.lerpTo(b, t);
+  }
+
+  /// Linearly interpolate between two [LinearGradient]s.
+  ///
+  /// If either gradient is null, this function linearly interpolates from a
+  /// a gradient that matches the other gradient in [begin], [end], [stops] and
+  /// [tileMode] and with the same [colors] but transparent (using [scale]).
+  ///
+  /// If neither gradient is null, they must have the same number of [colors].
+  ///
+  /// The `t` argument represents a position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+  /// 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  static LinearGradient? lerp(LinearGradient? a, LinearGradient? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b!.scale(t);
+    if (b == null)
+      return a.scale(1.0 - t);
+    final _ColorsAndStops interpolated = _interpolateColorsAndStops(
+        a.colors,
+        a._impliedStops(),
+        b.colors,
+        b._impliedStops(),
+        t,
+    );
+    return LinearGradient(
+      begin: AlignmentGeometry.lerp(a.begin, b.begin, t)!,
+      end: AlignmentGeometry.lerp(a.end, b.end, t)!,
+      colors: interpolated.colors,
+      stops: interpolated.stops,
+      tileMode: t < 0.5 ? a.tileMode : b.tileMode, // TODO(ianh): interpolate tile mode
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is LinearGradient
+        && other.begin == begin
+        && other.end == end
+        && other.tileMode == tileMode
+        && listEquals<Color>(other.colors, colors)
+        && listEquals<double>(other.stops, stops);
+  }
+
+  @override
+  int get hashCode => hashValues(begin, end, tileMode, hashList(colors), hashList(stops));
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'LinearGradient')}($begin, $end, $colors, $stops, $tileMode)';
+  }
+}
+
+/// A 2D radial gradient.
+///
+/// This class is used by [BoxDecoration] to represent radial gradients. This
+/// abstracts out the arguments to the [new ui.Gradient.radial] constructor from
+/// the `dart:ui` library.
+///
+/// A normal radial gradient has a [center] and a [radius]. The [center] point
+/// corresponds to 0.0, and the ring at [radius] from the center corresponds
+/// to 1.0. These lengths are expressed in fractions, so that the same gradient
+/// can be reused with varying sized boxes without changing the parameters.
+/// (This contrasts with [new ui.Gradient.radial], whose arguments are expressed
+/// in logical pixels.)
+///
+/// It is also possible to create a two-point (or focal pointed) radial gradient
+/// (which is sometimes referred to as a two point conic gradient, but is not the
+/// same as a CSS conic gradient which corresponds to a [SweepGradient]). A [focal]
+/// point and [focalRadius] can be specified similarly to [center] and [radius],
+/// which will make the rendered gradient appear to be pointed or directed in the
+/// direction of the [focal] point. This is only important if [focal] and [center]
+/// are not equal or [focalRadius] > 0.0 (as this case is visually identical to a
+/// normal radial gradient).  One important case to avoid is having [focal] and
+/// [center] both resolve to [Offset.zero] when [focalRadius] > 0.0. In such a case,
+/// a valid shader cannot be created by the framework.
+///
+/// The [colors] are described by a list of [Color] objects. There must be at
+/// least two colors. The [stops] list, if specified, must have the same length
+/// as [colors]. It specifies fractions of the radius between 0.0 and 1.0,
+/// giving concentric rings for each color stop. If it is null, a uniform
+/// distribution is assumed.
+///
+/// The region of the canvas beyond [radius] from the [center] is colored
+/// according to [tileMode].
+///
+/// Typically this class is used with [BoxDecoration], which does the painting.
+/// To use a [RadialGradient] to paint on a canvas directly, see [createShader].
+///
+/// {@tool snippet}
+///
+/// This function draws a gradient that looks like a sun in a blue sky.
+///
+/// ```dart
+/// void paintSky(Canvas canvas, Rect rect) {
+///   var gradient = RadialGradient(
+///     center: const Alignment(0.7, -0.6), // near the top right
+///     radius: 0.2,
+///     colors: [
+///       const Color(0xFFFFFF00), // yellow sun
+///       const Color(0xFF0099FF), // blue sky
+///     ],
+///     stops: [0.4, 1.0],
+///   );
+///   // rect is the area we are painting over
+///   var paint = Paint()
+///     ..shader = gradient.createShader(rect);
+///   canvas.drawRect(rect, paint);
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [LinearGradient], which displays a gradient in parallel lines, and has an
+///    example which shows a different way to use [Gradient] objects.
+///  * [SweepGradient], which displays a gradient in a sweeping arc around a
+///    center point.
+///  * [BoxDecoration], which can take a [RadialGradient] in its
+///    [BoxDecoration.gradient] property.
+///  * [CustomPainter], which shows how to use the above sample code in a custom
+///    painter.
+class RadialGradient extends Gradient {
+  /// Creates a radial gradient.
+  ///
+  /// The [colors] argument must not be null. If [stops] is non-null, it must
+  /// have the same length as [colors].
+  const RadialGradient({
+    this.center = Alignment.center,
+    this.radius = 0.5,
+    required List<Color> colors,
+    List<double>? stops,
+    this.tileMode = TileMode.clamp,
+    this.focal,
+    this.focalRadius = 0.0,
+    GradientTransform? transform,
+  }) : assert(center != null),
+       assert(radius != null),
+       assert(tileMode != null),
+       assert(focalRadius != null),
+       super(colors: colors, stops: stops, transform: transform);
+
+  /// The center of the gradient, as an offset into the (-1.0, -1.0) x (1.0, 1.0)
+  /// square describing the gradient which will be mapped onto the paint box.
+  ///
+  /// For example, an alignment of (0.0, 0.0) will place the radial
+  /// gradient in the center of the box.
+  ///
+  /// If this is an [Alignment], then it is expressed as a vector from
+  /// coordinate (0.0, 0.0), in a coordinate space that maps the center of the
+  /// paint box at (0.0, 0.0) and the bottom right at (1.0, 1.0).
+  ///
+  /// It can also be an [AlignmentDirectional], where the start is the left in
+  /// left-to-right contexts and the right in right-to-left contexts. If a
+  /// text-direction-dependent value is provided here, then the [createShader]
+  /// method will need to be given a [TextDirection].
+  final AlignmentGeometry center;
+
+  /// The radius of the gradient, as a fraction of the shortest side
+  /// of the paint box.
+  ///
+  /// For example, if a radial gradient is painted on a box that is
+  /// 100.0 pixels wide and 200.0 pixels tall, then a radius of 1.0
+  /// will place the 1.0 stop at 100.0 pixels from the [center].
+  final double radius;
+
+  /// How this gradient should tile the plane beyond the outer ring at [radius]
+  /// pixels from the [center].
+  ///
+  /// For details, see [TileMode].
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_radial.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_radial.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_radial.png)
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_radialWithFocal.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_radialWithFocal.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_radialWithFocal.png)
+  final TileMode tileMode;
+
+  /// The focal point of the gradient.  If specified, the gradient will appear
+  /// to be focused along the vector from [center] to focal.
+  ///
+  /// See [center] for a description of how the coordinates are mapped.
+  ///
+  /// If this value is specified and [focalRadius] > 0.0, care should be taken
+  /// to ensure that either this value or [center] will not both resolve to
+  /// [Offset.zero], which would fail to create a valid gradient.
+  final AlignmentGeometry? focal;
+
+  /// The radius of the focal point of gradient, as a fraction of the shortest
+  /// side of the paint box.
+  ///
+  /// For example, if a radial gradient is painted on a box that is
+  /// 100.0 pixels wide and 200.0 pixels tall, then a radius of 1.0
+  /// will place the 1.0 stop at 100.0 pixels from the [focal] point.
+  ///
+  /// If this value is specified and is greater than 0.0, either [focal] or
+  /// [center] must not resolve to [Offset.zero], which would fail to create
+  /// a valid gradient.
+  final double focalRadius;
+
+  @override
+  Shader createShader(Rect rect, { TextDirection? textDirection }) {
+    return ui.Gradient.radial(
+      center.resolve(textDirection).withinRect(rect),
+      radius * rect.shortestSide,
+      colors, _impliedStops(), tileMode,
+      _resolveTransform(rect, textDirection),
+      focal == null  ? null : focal!.resolve(textDirection).withinRect(rect),
+      focalRadius * rect.shortestSide,
+    );
+  }
+
+  /// Returns a new [RadialGradient] with its colors scaled by the given factor.
+  ///
+  /// Since the alpha component of the Color is what is scaled, a factor
+  /// of 0.0 or less results in a gradient that is fully transparent.
+  @override
+  RadialGradient scale(double factor) {
+    return RadialGradient(
+      center: center,
+      radius: radius,
+      colors: colors.map<Color>((Color color) => Color.lerp(null, color, factor)!).toList(),
+      stops: stops,
+      tileMode: tileMode,
+      focal: focal,
+      focalRadius: focalRadius,
+    );
+  }
+
+  @override
+  Gradient? lerpFrom(Gradient? a, double t) {
+    if (a == null || (a is RadialGradient))
+      return RadialGradient.lerp(a as RadialGradient?, this, t);
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  Gradient? lerpTo(Gradient? b, double t) {
+    if (b == null || (b is RadialGradient))
+      return RadialGradient.lerp(this, b as RadialGradient?, t);
+    return super.lerpTo(b, t);
+  }
+
+  /// Linearly interpolate between two [RadialGradient]s.
+  ///
+  /// If either gradient is null, this function linearly interpolates from a
+  /// a gradient that matches the other gradient in [center], [radius], [stops] and
+  /// [tileMode] and with the same [colors] but transparent (using [scale]).
+  ///
+  /// If neither gradient is null, they must have the same number of [colors].
+  ///
+  /// The `t` argument represents a position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+  /// 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  static RadialGradient? lerp(RadialGradient? a, RadialGradient? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b!.scale(t);
+    if (b == null)
+      return a.scale(1.0 - t);
+    final _ColorsAndStops interpolated = _interpolateColorsAndStops(
+        a.colors,
+        a._impliedStops(),
+        b.colors,
+        b._impliedStops(),
+        t,
+    );
+    return RadialGradient(
+      center: AlignmentGeometry.lerp(a.center, b.center, t)!,
+      radius: math.max(0.0, ui.lerpDouble(a.radius, b.radius, t)!),
+      colors: interpolated.colors,
+      stops: interpolated.stops,
+      tileMode: t < 0.5 ? a.tileMode : b.tileMode, // TODO(ianh): interpolate tile mode
+      focal: AlignmentGeometry.lerp(a.focal, b.focal, t),
+      focalRadius: math.max(0.0, ui.lerpDouble(a.focalRadius, b.focalRadius, t)!),
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is RadialGradient
+        && other.center == center
+        && other.radius == radius
+        && other.tileMode == tileMode
+        && listEquals<Color>(other.colors, colors)
+        && listEquals<double>(other.stops, stops)
+        && other.focal == focal
+        && other.focalRadius == focalRadius;
+  }
+
+  @override
+  int get hashCode => hashValues(center, radius, tileMode, hashList(colors), hashList(stops), focal, focalRadius);
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'RadialGradient')}($center, $radius, $colors, $stops, $tileMode, $focal, $focalRadius)';
+  }
+}
+
+/// A 2D sweep gradient.
+///
+/// This class is used by [BoxDecoration] to represent sweep gradients. This
+/// abstracts out the arguments to the [new ui.Gradient.sweep] constructor from
+/// the `dart:ui` library.
+///
+/// A gradient has a [center], a [startAngle], and an [endAngle]. The [startAngle]
+/// corresponds to 0.0, and the [endAngle] corresponds to 1.0. These angles are
+/// expressed in radians.
+///
+/// The [colors] are described by a list of [Color] objects. There must be at
+/// least two colors. The [stops] list, if specified, must have the same length
+/// as [colors]. It specifies fractions of the vector from start to end, between
+/// 0.0 and 1.0, for each color. If it is null, a uniform distribution is
+/// assumed.
+///
+/// The region of the canvas before [startAngle] and after [endAngle] is colored
+/// according to [tileMode].
+///
+/// Typically this class is used with [BoxDecoration], which does the painting.
+/// To use a [SweepGradient] to paint on a canvas directly, see [createShader].
+///
+/// {@tool snippet}
+///
+/// This sample draws a different color in each quadrant.
+///
+/// ```dart
+/// Container(
+///   decoration: BoxDecoration(
+///     gradient: SweepGradient(
+///       center: FractionalOffset.center,
+///       startAngle: 0.0,
+///       endAngle: math.pi * 2,
+///       colors: const <Color>[
+///         Color(0xFF4285F4), // blue
+///         Color(0xFF34A853), // green
+///         Color(0xFFFBBC05), // yellow
+///         Color(0xFFEA4335), // red
+///         Color(0xFF4285F4), // blue again to seamlessly transition to the start
+///       ],
+///       stops: const <double>[0.0, 0.25, 0.5, 0.75, 1.0],
+///     ),
+///   )
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+///
+/// This sample takes the above gradient and rotates it by `math.pi/4` radians,
+/// i.e. 45 degrees.
+///
+/// ```dart
+/// Container(
+///   decoration: BoxDecoration(
+///     gradient: SweepGradient(
+///       center: FractionalOffset.center,
+///       startAngle: 0.0,
+///       endAngle: math.pi * 2,
+///       colors: const <Color>[
+///         Color(0xFF4285F4), // blue
+///         Color(0xFF34A853), // green
+///         Color(0xFFFBBC05), // yellow
+///         Color(0xFFEA4335), // red
+///         Color(0xFF4285F4), // blue again to seamlessly transition to the start
+///       ],
+///       stops: const <double>[0.0, 0.25, 0.5, 0.75, 1.0],
+///       transform: GradientRotation(math.pi/4),
+///     ),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [LinearGradient], which displays a gradient in parallel lines, and has an
+///    example which shows a different way to use [Gradient] objects.
+///  * [RadialGradient], which displays a gradient in concentric circles, and
+///    has an example which shows a different way to use [Gradient] objects.
+///  * [BoxDecoration], which can take a [SweepGradient] in its
+///    [BoxDecoration.gradient] property.
+class SweepGradient extends Gradient {
+  /// Creates a sweep gradient.
+  ///
+  /// The [colors] argument must not be null. If [stops] is non-null, it must
+  /// have the same length as [colors].
+  const SweepGradient({
+    this.center = Alignment.center,
+    this.startAngle = 0.0,
+    this.endAngle = math.pi * 2,
+    required List<Color> colors,
+    List<double>? stops,
+    this.tileMode = TileMode.clamp,
+    GradientTransform? transform,
+  }) : assert(center != null),
+       assert(startAngle != null),
+       assert(endAngle != null),
+       assert(tileMode != null),
+       super(colors: colors, stops: stops, transform: transform);
+
+  /// The center of the gradient, as an offset into the (-1.0, -1.0) x (1.0, 1.0)
+  /// square describing the gradient which will be mapped onto the paint box.
+  ///
+  /// For example, an alignment of (0.0, 0.0) will place the sweep
+  /// gradient in the center of the box.
+  ///
+  /// If this is an [Alignment], then it is expressed as a vector from
+  /// coordinate (0.0, 0.0), in a coordinate space that maps the center of the
+  /// paint box at (0.0, 0.0) and the bottom right at (1.0, 1.0).
+  ///
+  /// It can also be an [AlignmentDirectional], where the start is the left in
+  /// left-to-right contexts and the right in right-to-left contexts. If a
+  /// text-direction-dependent value is provided here, then the [createShader]
+  /// method will need to be given a [TextDirection].
+  final AlignmentGeometry center;
+
+  /// The angle in radians at which stop 0.0 of the gradient is placed.
+  ///
+  /// Defaults to 0.0.
+  final double startAngle;
+
+  /// The angle in radians at which stop 1.0 of the gradient is placed.
+  ///
+  /// Defaults to math.pi * 2.
+  final double endAngle;
+
+  /// How this gradient should tile the plane beyond in the region before
+  /// [startAngle] and after [endAngle].
+  ///
+  /// For details, see [TileMode].
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_sweep.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_sweep.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_sweep.png)
+  final TileMode tileMode;
+
+  @override
+  Shader createShader(Rect rect, { TextDirection? textDirection }) {
+    return ui.Gradient.sweep(
+      center.resolve(textDirection).withinRect(rect),
+      colors, _impliedStops(), tileMode,
+      startAngle,
+      endAngle,
+      _resolveTransform(rect, textDirection),
+    );
+  }
+
+  /// Returns a new [SweepGradient] with its colors scaled by the given factor.
+  ///
+  /// Since the alpha component of the Color is what is scaled, a factor
+  /// of 0.0 or less results in a gradient that is fully transparent.
+  @override
+  SweepGradient scale(double factor) {
+    return SweepGradient(
+      center: center,
+      startAngle: startAngle,
+      endAngle: endAngle,
+      colors: colors.map<Color>((Color color) => Color.lerp(null, color, factor)!).toList(),
+      stops: stops,
+      tileMode: tileMode,
+    );
+  }
+
+  @override
+  Gradient? lerpFrom(Gradient? a, double t) {
+    if (a == null || (a is SweepGradient))
+      return SweepGradient.lerp(a as SweepGradient?, this, t);
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  Gradient? lerpTo(Gradient? b, double t) {
+    if (b == null || (b is SweepGradient))
+      return SweepGradient.lerp(this, b as SweepGradient?, t);
+    return super.lerpTo(b, t);
+  }
+
+  /// Linearly interpolate between two [SweepGradient]s.
+  ///
+  /// If either gradient is null, then the non-null gradient is returned with
+  /// its color scaled in the same way as the [scale] function.
+  ///
+  /// If neither gradient is null, they must have the same number of [colors].
+  ///
+  /// The `t` argument represents a position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+  /// 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  static SweepGradient? lerp(SweepGradient? a, SweepGradient? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b!.scale(t);
+    if (b == null)
+      return a.scale(1.0 - t);
+    final _ColorsAndStops interpolated = _interpolateColorsAndStops(
+        a.colors,
+        a._impliedStops(),
+        b.colors,
+        b._impliedStops(),
+        t,
+    );
+    return SweepGradient(
+      center: AlignmentGeometry.lerp(a.center, b.center, t)!,
+      startAngle: math.max(0.0, ui.lerpDouble(a.startAngle, b.startAngle, t)!),
+      endAngle: math.max(0.0, ui.lerpDouble(a.endAngle, b.endAngle, t)!),
+      colors: interpolated.colors,
+      stops: interpolated.stops,
+      tileMode: t < 0.5 ? a.tileMode : b.tileMode, // TODO(ianh): interpolate tile mode
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is SweepGradient
+        && other.center == center
+        && other.startAngle == startAngle
+        && other.endAngle == endAngle
+        && other.tileMode == tileMode
+        && listEquals<Color>(other.colors, colors)
+        && listEquals<double>(other.stops, stops);
+  }
+
+  @override
+  int get hashCode => hashValues(center, startAngle, endAngle, tileMode, hashList(colors), hashList(stops));
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'SweepGradient')}($center, $startAngle, $endAngle, $colors, $stops, $tileMode)';
+  }
+}
diff --git a/lib/src/painting/image_cache.dart b/lib/src/painting/image_cache.dart
new file mode 100644
index 0000000..df235d9
--- /dev/null
+++ b/lib/src/painting/image_cache.dart
@@ -0,0 +1,664 @@
+// Copyright 2014 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.
+
+import 'dart:developer';
+import 'package:flute/ui.dart' show hashValues;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+
+import 'image_stream.dart';
+
+const int _kDefaultSize = 1000;
+const int _kDefaultSizeBytes = 100 << 20; // 100 MiB
+
+/// Class for caching images.
+///
+/// Implements a least-recently-used cache of up to 1000 images, and up to 100
+/// MB. The maximum size can be adjusted using [maximumSize] and
+/// [maximumSizeBytes].
+///
+/// The cache also holds a list of "live" references. An image is considered
+/// live if its [ImageStreamCompleter]'s listener count has never dropped to
+/// zero after adding at least one listener. The cache uses
+/// [ImageStreamCompleter.addOnLastListenerRemovedCallback] to determine when
+/// this has happened.
+///
+/// The [putIfAbsent] method is the main entry-point to the cache API. It
+/// returns the previously cached [ImageStreamCompleter] for the given key, if
+/// available; if not, it calls the given callback to obtain it first. In either
+/// case, the key is moved to the "most recently used" position.
+///
+/// A caller can determine whether an image is already in the cache by using
+/// [containsKey], which will return true if the image is tracked by the cache
+/// in a pending or completed state. More fine grained information is available
+/// by using the [statusForKey] method.
+///
+/// Generally this class is not used directly. The [ImageProvider] class and its
+/// subclasses automatically handle the caching of images.
+///
+/// A shared instance of this cache is retained by [PaintingBinding] and can be
+/// obtained via the [imageCache] top-level property in the [painting] library.
+///
+/// {@tool snippet}
+///
+/// This sample shows how to supply your own caching logic and replace the
+/// global [imageCache] variable.
+///
+/// ```dart
+/// /// This is the custom implementation of [ImageCache] where we can override
+/// /// the logic.
+/// class MyImageCache extends ImageCache {
+///   @override
+///   void clear() {
+///     print("Clearing cache!");
+///     super.clear();
+///   }
+/// }
+///
+/// class MyWidgetsBinding extends WidgetsFlutterBinding {
+///   @override
+///   ImageCache createImageCache() => MyImageCache();
+/// }
+///
+/// void main() {
+///   // The constructor sets global variables.
+///   MyWidgetsBinding();
+///   runApp(MyApp());
+/// }
+///
+/// class MyApp extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return Container();
+///   }
+/// }
+/// ```
+/// {@end-tool}
+class ImageCache {
+  final Map<Object, _PendingImage> _pendingImages = <Object, _PendingImage>{};
+  final Map<Object, _CachedImage> _cache = <Object, _CachedImage>{};
+  /// ImageStreamCompleters with at least one listener. These images may or may
+  /// not fit into the _pendingImages or _cache objects.
+  ///
+  /// Unlike _cache, the [_CachedImage] for this may have a null byte size.
+  final Map<Object, _LiveImage> _liveImages = <Object, _LiveImage>{};
+
+  /// Maximum number of entries to store in the cache.
+  ///
+  /// Once this many entries have been cached, the least-recently-used entry is
+  /// evicted when adding a new entry.
+  int get maximumSize => _maximumSize;
+  int _maximumSize = _kDefaultSize;
+  /// Changes the maximum cache size.
+  ///
+  /// If the new size is smaller than the current number of elements, the
+  /// extraneous elements are evicted immediately. Setting this to zero and then
+  /// returning it to its original value will therefore immediately clear the
+  /// cache.
+  set maximumSize(int value) {
+    assert(value != null);
+    assert(value >= 0);
+    if (value == maximumSize)
+      return;
+    TimelineTask? timelineTask;
+    if (!kReleaseMode) {
+       timelineTask = TimelineTask()..start(
+        'ImageCache.setMaximumSize',
+        arguments: <String, dynamic>{'value': value},
+      );
+    }
+    _maximumSize = value;
+    if (maximumSize == 0) {
+      clear();
+    } else {
+      _checkCacheSize(timelineTask);
+    }
+    if (!kReleaseMode) {
+      timelineTask!.finish();
+    }
+  }
+
+  /// The current number of cached entries.
+  int get currentSize => _cache.length;
+
+  /// Maximum size of entries to store in the cache in bytes.
+  ///
+  /// Once more than this amount of bytes have been cached, the
+  /// least-recently-used entry is evicted until there are fewer than the
+  /// maximum bytes.
+  int get maximumSizeBytes => _maximumSizeBytes;
+  int _maximumSizeBytes = _kDefaultSizeBytes;
+  /// Changes the maximum cache bytes.
+  ///
+  /// If the new size is smaller than the current size in bytes, the
+  /// extraneous elements are evicted immediately. Setting this to zero and then
+  /// returning it to its original value will therefore immediately clear the
+  /// cache.
+  set maximumSizeBytes(int value) {
+    assert(value != null);
+    assert(value >= 0);
+    if (value == _maximumSizeBytes)
+      return;
+    TimelineTask? timelineTask;
+    if (!kReleaseMode) {
+      timelineTask = TimelineTask()..start(
+        'ImageCache.setMaximumSizeBytes',
+        arguments: <String, dynamic>{'value': value},
+      );
+    }
+    _maximumSizeBytes = value;
+    if (_maximumSizeBytes == 0) {
+      clear();
+    } else {
+      _checkCacheSize(timelineTask);
+    }
+    if (!kReleaseMode) {
+      timelineTask!.finish();
+    }
+  }
+
+  /// The current size of cached entries in bytes.
+  int get currentSizeBytes => _currentSizeBytes;
+  int _currentSizeBytes = 0;
+
+  /// Evicts all pending and keepAlive entries from the cache.
+  ///
+  /// This is useful if, for instance, the root asset bundle has been updated
+  /// and therefore new images must be obtained.
+  ///
+  /// Images which have not finished loading yet will not be removed from the
+  /// cache, and when they complete they will be inserted as normal.
+  ///
+  /// This method does not clear live references to images, since clearing those
+  /// would not reduce memory pressure. Such images still have listeners in the
+  /// application code, and will still remain resident in memory.
+  ///
+  /// To clear live references, use [clearLiveImages].
+  void clear() {
+    if (!kReleaseMode) {
+      Timeline.instantSync(
+        'ImageCache.clear',
+        arguments: <String, dynamic>{
+          'pendingImages': _pendingImages.length,
+          'keepAliveImages': _cache.length,
+          'liveImages': _liveImages.length,
+          'currentSizeInBytes': _currentSizeBytes,
+        },
+      );
+    }
+    _cache.clear();
+    _pendingImages.clear();
+    _currentSizeBytes = 0;
+  }
+
+  /// Evicts a single entry from the cache, returning true if successful.
+  ///
+  /// Pending images waiting for completion are removed as well, returning true
+  /// if successful. When a pending image is removed the listener on it is
+  /// removed as well to prevent it from adding itself to the cache if it
+  /// eventually completes.
+  ///
+  /// If this method removes a pending image, it will also remove
+  /// the corresponding live tracking of the image, since it is no longer clear
+  /// if the image will ever complete or have any listeners, and failing to
+  /// remove the live reference could leave the cache in a state where all
+  /// subsequent calls to [putIfAbsent] will return an [ImageStreamCompleter]
+  /// that will never complete.
+  ///
+  /// If this method removes a completed image, it will _not_ remove the live
+  /// reference to the image, which will only be cleared when the listener
+  /// count on the completer drops to zero. To clear live image references,
+  /// whether completed or not, use [clearLiveImages].
+  ///
+  /// The `key` must be equal to an object used to cache an image in
+  /// [ImageCache.putIfAbsent].
+  ///
+  /// If the key is not immediately available, as is common, consider using
+  /// [ImageProvider.evict] to call this method indirectly instead.
+  ///
+  /// The `includeLive` argument determines whether images that still have
+  /// listeners in the tree should be evicted as well. This parameter should be
+  /// set to true in cases where the image may be corrupted and needs to be
+  /// completely discarded by the cache. It should be set to false when calls
+  /// to evict are trying to relieve memory pressure, since an image with a
+  /// listener will not actually be evicted from memory, and subsequent attempts
+  /// to load it will end up allocating more memory for the image again. The
+  /// argument must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [ImageProvider], for providing images to the [Image] widget.
+  bool evict(Object key, { bool includeLive = true }) {
+    assert(includeLive != null);
+    if (includeLive) {
+      // Remove from live images - the cache will not be able to mark
+      // it as complete, and it might be getting evicted because it
+      // will never complete, e.g. it was loaded in a FakeAsync zone.
+      // In such a case, we need to make sure subsequent calls to
+      // putIfAbsent don't return this image that may never complete.
+      final _LiveImage? image = _liveImages.remove(key);
+      image?.dispose();
+    }
+    final _PendingImage? pendingImage = _pendingImages.remove(key);
+    if (pendingImage != null) {
+      if (!kReleaseMode) {
+        Timeline.instantSync('ImageCache.evict', arguments: <String, dynamic>{
+          'type': 'pending'
+        });
+      }
+      pendingImage.removeListener();
+      return true;
+    }
+    final _CachedImage? image = _cache.remove(key);
+    if (image != null) {
+      if (!kReleaseMode) {
+        Timeline.instantSync('ImageCache.evict', arguments: <String, dynamic>{
+          'type': 'keepAlive',
+          'sizeInBytes': image.sizeBytes,
+        });
+      }
+      _currentSizeBytes -= image.sizeBytes!;
+      image.dispose();
+      return true;
+    }
+    if (!kReleaseMode) {
+      Timeline.instantSync('ImageCache.evict', arguments: <String, dynamic>{
+        'type': 'miss',
+      });
+    }
+    return false;
+  }
+
+  /// Updates the least recently used image cache with this image, if it is
+  /// less than the [maximumSizeBytes] of this cache.
+  ///
+  /// Resizes the cache as appropriate to maintain the constraints of
+  /// [maximumSize] and [maximumSizeBytes].
+  void _touch(Object key, _CachedImage image, TimelineTask? timelineTask) {
+    assert(timelineTask != null);
+    if (image.sizeBytes != null && image.sizeBytes! <= maximumSizeBytes && maximumSize > 0) {
+      _currentSizeBytes += image.sizeBytes!;
+      _cache[key] = image;
+      _checkCacheSize(timelineTask);
+    } else {
+      image.dispose();
+    }
+  }
+
+  void _trackLiveImage(Object key, ImageStreamCompleter completer, int? sizeBytes) {
+    // Avoid adding unnecessary callbacks to the completer.
+    _liveImages.putIfAbsent(key, () {
+      // Even if no callers to ImageProvider.resolve have listened to the stream,
+      // the cache is listening to the stream and will remove itself once the
+      // image completes to move it from pending to keepAlive.
+      // Even if the cache size is 0, we still add this tracker, which will add
+      // a keep alive handle to the stream.
+      return _LiveImage(
+        completer,
+        () {
+          _liveImages.remove(key);
+        },
+      );
+    }).sizeBytes ??= sizeBytes;
+  }
+
+  /// Returns the previously cached [ImageStream] for the given key, if available;
+  /// if not, calls the given callback to obtain it first. In either case, the
+  /// key is moved to the "most recently used" position.
+  ///
+  /// The arguments must not be null. The `loader` cannot return null.
+  ///
+  /// In the event that the loader throws an exception, it will be caught only if
+  /// `onError` is also provided. When an exception is caught resolving an image,
+  /// no completers are cached and `null` is returned instead of a new
+  /// completer.
+  ImageStreamCompleter? putIfAbsent(Object key, ImageStreamCompleter loader(), { ImageErrorListener? onError }) {
+    assert(key != null);
+    assert(loader != null);
+    TimelineTask? timelineTask;
+    TimelineTask? listenerTask;
+    if (!kReleaseMode) {
+      timelineTask = TimelineTask()..start(
+        'ImageCache.putIfAbsent',
+        arguments: <String, dynamic>{
+          'key': key.toString(),
+        },
+      );
+    }
+    ImageStreamCompleter? result = _pendingImages[key]?.completer;
+    // Nothing needs to be done because the image hasn't loaded yet.
+    if (result != null) {
+      if (!kReleaseMode) {
+        timelineTask!.finish(arguments: <String, dynamic>{'result': 'pending'});
+      }
+      return result;
+    }
+    // Remove the provider from the list so that we can move it to the
+    // recently used position below.
+    // Don't use _touch here, which would trigger a check on cache size that is
+    // not needed since this is just moving an existing cache entry to the head.
+    final _CachedImage? image = _cache.remove(key);
+    if (image != null) {
+      if (!kReleaseMode) {
+        timelineTask!.finish(arguments: <String, dynamic>{'result': 'keepAlive'});
+      }
+      // The image might have been keptAlive but had no listeners (so not live).
+      // Make sure the cache starts tracking it as live again.
+      _trackLiveImage(
+        key,
+        image.completer,
+        image.sizeBytes,
+      );
+      _cache[key] = image;
+      return image.completer;
+    }
+
+    final _LiveImage? liveImage = _liveImages[key];
+    if (liveImage != null) {
+      _touch(
+        key,
+        _CachedImage(
+          liveImage.completer,
+          sizeBytes: liveImage.sizeBytes,
+        ),
+        timelineTask,
+      );
+      if (!kReleaseMode) {
+        timelineTask!.finish(arguments: <String, dynamic>{'result': 'keepAlive'});
+      }
+      return liveImage.completer;
+    }
+
+    try {
+      result = loader();
+      _trackLiveImage(key, result, null);
+    } catch (error, stackTrace) {
+      if (!kReleaseMode) {
+        timelineTask!.finish(arguments: <String, dynamic>{
+          'result': 'error',
+          'error': error.toString(),
+          'stackTrace': stackTrace.toString(),
+        });
+      }
+      if (onError != null) {
+        onError(error, stackTrace);
+        return null;
+      } else {
+        rethrow;
+      }
+    }
+
+    if (!kReleaseMode) {
+      listenerTask = TimelineTask(parent: timelineTask)..start('listener');
+    }
+    // If we're doing tracing, we need to make sure that we don't try to finish
+    // the trace entry multiple times if we get re-entrant calls from a multi-
+    // frame provider here.
+    bool listenedOnce = false;
+
+    // We shouldn't use the _pendingImages map if the cache is disabled, but we
+    // will have to listen to the image at least once so we don't leak it in
+    // the live image tracking.
+    // If the cache is disabled, this variable will be set.
+    _PendingImage? untrackedPendingImage;
+    void listener(ImageInfo? info, bool syncCall) {
+      int? sizeBytes;
+      if (info != null) {
+        sizeBytes = info.image.height * info.image.width * 4;
+        info.dispose();
+      }
+      final _CachedImage image = _CachedImage(
+        result!,
+        sizeBytes: sizeBytes,
+      );
+
+      _trackLiveImage(key, result, sizeBytes);
+
+      // Only touch if the cache was enabled when resolve was initially called.
+      if (untrackedPendingImage == null) {
+        _touch(key, image, listenerTask);
+      } else {
+        image.dispose();
+      }
+
+      final _PendingImage? pendingImage = untrackedPendingImage ?? _pendingImages.remove(key);
+      if (pendingImage != null) {
+        pendingImage.removeListener();
+      }
+      if (!kReleaseMode && !listenedOnce) {
+        listenerTask!.finish(arguments: <String, dynamic>{
+          'syncCall': syncCall,
+          'sizeInBytes': sizeBytes,
+        });
+        timelineTask!.finish(arguments: <String, dynamic>{
+          'currentSizeBytes': currentSizeBytes,
+          'currentSize': currentSize,
+        });
+      }
+      listenedOnce = true;
+    }
+
+    final ImageStreamListener streamListener = ImageStreamListener(listener);
+    if (maximumSize > 0 && maximumSizeBytes > 0) {
+      _pendingImages[key] = _PendingImage(result, streamListener);
+    } else {
+      untrackedPendingImage = _PendingImage(result, streamListener);
+    }
+    // Listener is removed in [_PendingImage.removeListener].
+    result.addListener(streamListener);
+
+    return result;
+  }
+
+  /// The [ImageCacheStatus] information for the given `key`.
+  ImageCacheStatus statusForKey(Object key) {
+    return ImageCacheStatus._(
+      pending: _pendingImages.containsKey(key),
+      keepAlive: _cache.containsKey(key),
+      live: _liveImages.containsKey(key),
+    );
+  }
+
+  /// Returns whether this `key` has been previously added by [putIfAbsent].
+  bool containsKey(Object key) {
+    return _pendingImages[key] != null || _cache[key] != null;
+  }
+
+  /// The number of live images being held by the [ImageCache].
+  ///
+  /// Compare with [ImageCache.currentSize] for keepAlive images.
+  int get liveImageCount => _liveImages.length;
+
+  /// The number of images being tracked as pending in the [ImageCache].
+  ///
+  /// Compare with [ImageCache.currentSize] for keepAlive images.
+  int get pendingImageCount => _pendingImages.length;
+
+  /// Clears any live references to images in this cache.
+  ///
+  /// An image is considered live if its [ImageStreamCompleter] has never hit
+  /// zero listeners after adding at least one listener. The
+  /// [ImageStreamCompleter.addOnLastListenerRemovedCallback] is used to
+  /// determine when this has happened.
+  ///
+  /// This is called after a hot reload to evict any stale references to image
+  /// data for assets that have changed. Calling this method does not relieve
+  /// memory pressure, since the live image caching only tracks image instances
+  /// that are also being held by at least one other object.
+  void clearLiveImages() {
+    for (final _LiveImage image in _liveImages.values) {
+      image.dispose();
+    }
+    _liveImages.clear();
+  }
+
+  // Remove images from the cache until both the length and bytes are below
+  // maximum, or the cache is empty.
+  void _checkCacheSize(TimelineTask? timelineTask) {
+    final Map<String, dynamic> finishArgs = <String, dynamic>{};
+    TimelineTask? checkCacheTask;
+    if (!kReleaseMode) {
+      checkCacheTask = TimelineTask(parent: timelineTask)..start('checkCacheSize');
+      finishArgs['evictedKeys'] = <String>[];
+      finishArgs['currentSize'] = currentSize;
+      finishArgs['currentSizeBytes'] = currentSizeBytes;
+    }
+    while (_currentSizeBytes > _maximumSizeBytes || _cache.length > _maximumSize) {
+      final Object key = _cache.keys.first;
+      final _CachedImage image = _cache[key]!;
+      _currentSizeBytes -= image.sizeBytes!;
+      image.dispose();
+      _cache.remove(key);
+      if (!kReleaseMode) {
+        finishArgs['evictedKeys'].add(key.toString());
+      }
+    }
+    if (!kReleaseMode) {
+      finishArgs['endSize'] = currentSize;
+      finishArgs['endSizeBytes'] = currentSizeBytes;
+      checkCacheTask!.finish(arguments: finishArgs);
+    }
+    assert(_currentSizeBytes >= 0);
+    assert(_cache.length <= maximumSize);
+    assert(_currentSizeBytes <= maximumSizeBytes);
+  }
+}
+
+/// Information about how the [ImageCache] is tracking an image.
+///
+/// A [pending] image is one that has not completed yet. It may also be tracked
+/// as [live] because something is listening to it.
+///
+/// A [keepAlive] image is being held in the cache, which uses Least Recently
+/// Used semantics to determine when to evict an image. These images are subject
+/// to eviction based on [ImageCache.maximumSizeBytes] and
+/// [ImageCache.maximumSize]. It may be [live], but not [pending].
+///
+/// A [live] image is being held until its [ImageStreamCompleter] has no more
+/// listeners. It may also be [pending] or [keepAlive].
+///
+/// An [untracked] image is not being cached.
+///
+/// To obtain an [ImageCacheStatus], use [ImageCache.statusForKey] or
+/// [ImageProvider.obtainCacheStatus].
+@immutable
+class ImageCacheStatus {
+  const ImageCacheStatus._({
+    this.pending = false,
+    this.keepAlive = false,
+    this.live = false,
+  }) : assert(!pending || !keepAlive);
+
+  /// An image that has been submitted to [ImageCache.putIfAbsent], but
+  /// not yet completed.
+  final bool pending;
+
+  /// An image that has been submitted to [ImageCache.putIfAbsent], has
+  /// completed, fits based on the sizing rules of the cache, and has not been
+  /// evicted.
+  ///
+  /// Such images will be kept alive even if [live] is false, as long
+  /// as they have not been evicted from the cache based on its sizing rules.
+  final bool keepAlive;
+
+  /// An image that has been submitted to [ImageCache.putIfAbsent] and has at
+  /// least one listener on its [ImageStreamCompleter].
+  ///
+  /// Such images may also be [keepAlive] if they fit in the cache based on its
+  /// sizing rules. They may also be [pending] if they have not yet resolved.
+  final bool live;
+
+  /// An image that is tracked in some way by the [ImageCache], whether
+  /// [pending], [keepAlive], or [live].
+  bool get tracked => pending || keepAlive || live;
+
+  /// An image that either has not been submitted to
+  /// [ImageCache.putIfAbsent] or has otherwise been evicted from the
+  /// [keepAlive] and [live] caches.
+  bool get untracked => !pending && !keepAlive && !live;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is ImageCacheStatus
+        && other.pending == pending
+        && other.keepAlive == keepAlive
+        && other.live == live;
+  }
+
+  @override
+  int get hashCode => hashValues(pending, keepAlive, live);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'ImageCacheStatus')}(pending: $pending, live: $live, keepAlive: $keepAlive)';
+}
+
+/// Base class for [_CachedImage] and [_LiveImage].
+///
+/// Exists primarily so that a [_LiveImage] cannot be added to the
+/// [ImageCache._cache].
+abstract class _CachedImageBase {
+  _CachedImageBase(
+    this.completer, {
+    this.sizeBytes,
+  }) : assert(completer != null),
+       handle = completer.keepAlive();
+
+  final ImageStreamCompleter completer;
+  int? sizeBytes;
+  ImageStreamCompleterHandle? handle;
+
+  @mustCallSuper
+  void dispose() {
+    assert(handle != null);
+    // Give any interested parties a chance to listen to the stream before we
+    // potentially dispose it.
+    SchedulerBinding.instance!.addPostFrameCallback((Duration timeStamp) {
+      assert(handle != null);
+      handle?.dispose();
+      handle = null;
+    });
+  }
+}
+
+class _CachedImage extends _CachedImageBase {
+  _CachedImage(ImageStreamCompleter completer, {int? sizeBytes})
+      : super(completer, sizeBytes: sizeBytes);
+}
+
+class _LiveImage extends _CachedImageBase {
+  _LiveImage(ImageStreamCompleter completer, VoidCallback handleRemove, {int? sizeBytes})
+      : super(completer, sizeBytes: sizeBytes) {
+    _handleRemove = () {
+      handleRemove();
+      dispose();
+    };
+    completer.addOnLastListenerRemovedCallback(_handleRemove);
+  }
+
+  late VoidCallback _handleRemove;
+
+  @override
+  void dispose() {
+    completer.removeOnLastListenerRemovedCallback(_handleRemove);
+    super.dispose();
+  }
+
+  @override
+  String toString() => describeIdentity(this);
+}
+
+class _PendingImage {
+  _PendingImage(this.completer, this.listener);
+
+  final ImageStreamCompleter completer;
+  final ImageStreamListener listener;
+
+  void removeListener() {
+    completer.removeListener(listener);
+  }
+}
diff --git a/lib/src/painting/image_decoder.dart b/lib/src/painting/image_decoder.dart
new file mode 100644
index 0000000..26603a0
--- /dev/null
+++ b/lib/src/painting/image_decoder.dart
@@ -0,0 +1,27 @@
+// Copyright 2014 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.
+
+
+import 'dart:typed_data';
+import 'package:flute/ui.dart' as ui show Codec, FrameInfo, Image;
+
+import 'binding.dart';
+
+/// Creates an image from a list of bytes.
+///
+/// This function attempts to interpret the given bytes an image. If successful,
+/// the returned [Future] resolves to the decoded image. Otherwise, the [Future]
+/// resolves to null.
+///
+/// If the image is animated, this returns the first frame. Consider
+/// [instantiateImageCodec] if support for animated images is necessary.
+///
+/// This function differs from [ui.decodeImageFromList] in that it defers to
+/// [PaintingBinding.instantiateImageCodec], and therefore can be mocked in
+/// tests.
+Future<ui.Image> decodeImageFromList(Uint8List bytes) async {
+  final ui.Codec codec = await PaintingBinding.instance!.instantiateImageCodec(bytes);
+  final ui.FrameInfo frameInfo = await codec.getNextFrame();
+  return frameInfo.image;
+}
diff --git a/lib/src/painting/image_provider.dart b/lib/src/painting/image_provider.dart
new file mode 100644
index 0000000..bbdfbda
--- /dev/null
+++ b/lib/src/painting/image_provider.dart
@@ -0,0 +1,1153 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:typed_data';
+import 'package:flute/ui.dart' as ui show Codec;
+import 'package:flute/ui.dart' show Size, Locale, TextDirection, hashValues;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/services.dart';
+
+import '_network_image_io.dart' as network_image;
+import 'binding.dart';
+import 'image_cache.dart';
+import 'image_stream.dart';
+
+/// Signature for the callback taken by [_createErrorHandlerAndKey].
+typedef _KeyAndErrorHandlerCallback<T> = void Function(T key, ImageErrorListener handleError);
+
+/// Signature used for error handling by [_createErrorHandlerAndKey].
+typedef _AsyncKeyErrorHandler<T> = Future<void> Function(T key, Object exception, StackTrace? stack);
+
+/// Configuration information passed to the [ImageProvider.resolve] method to
+/// select a specific image.
+///
+/// See also:
+///
+///  * [createLocalImageConfiguration], which creates an [ImageConfiguration]
+///    based on ambient configuration in a [Widget] environment.
+///  * [ImageProvider], which uses [ImageConfiguration] objects to determine
+///    which image to obtain.
+@immutable
+class ImageConfiguration {
+  /// Creates an object holding the configuration information for an [ImageProvider].
+  ///
+  /// All the arguments are optional. Configuration information is merely
+  /// advisory and best-effort.
+  const ImageConfiguration({
+    this.bundle,
+    this.devicePixelRatio,
+    this.locale,
+    this.textDirection,
+    this.size,
+    this.platform,
+  });
+
+  /// Creates an object holding the configuration information for an [ImageProvider].
+  ///
+  /// All the arguments are optional. Configuration information is merely
+  /// advisory and best-effort.
+  ImageConfiguration copyWith({
+    AssetBundle? bundle,
+    double? devicePixelRatio,
+    Locale? locale,
+    TextDirection? textDirection,
+    Size? size,
+    TargetPlatform? platform,
+  }) {
+    return ImageConfiguration(
+      bundle: bundle ?? this.bundle,
+      devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
+      locale: locale ?? this.locale,
+      textDirection: textDirection ?? this.textDirection,
+      size: size ?? this.size,
+      platform: platform ?? this.platform,
+    );
+  }
+
+  /// The preferred [AssetBundle] to use if the [ImageProvider] needs one and
+  /// does not have one already selected.
+  final AssetBundle? bundle;
+
+  /// The device pixel ratio where the image will be shown.
+  final double? devicePixelRatio;
+
+  /// The language and region for which to select the image.
+  final Locale? locale;
+
+  /// The reading direction of the language for which to select the image.
+  final TextDirection? textDirection;
+
+  /// The size at which the image will be rendered.
+  final Size? size;
+
+  /// The [TargetPlatform] for which assets should be used. This allows images
+  /// to be specified in a platform-neutral fashion yet use different assets on
+  /// different platforms, to match local conventions e.g. for color matching or
+  /// shadows.
+  final TargetPlatform? platform;
+
+  /// An image configuration that provides no additional information.
+  ///
+  /// Useful when resolving an [ImageProvider] without any context.
+  static const ImageConfiguration empty = ImageConfiguration();
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ImageConfiguration
+        && other.bundle == bundle
+        && other.devicePixelRatio == devicePixelRatio
+        && other.locale == locale
+        && other.textDirection == textDirection
+        && other.size == size
+        && other.platform == platform;
+  }
+
+  @override
+  int get hashCode => hashValues(bundle, devicePixelRatio, locale, size, platform);
+
+  @override
+  String toString() {
+    final StringBuffer result = StringBuffer();
+    result.write('ImageConfiguration(');
+    bool hasArguments = false;
+    if (bundle != null) {
+      if (hasArguments)
+        result.write(', ');
+      result.write('bundle: $bundle');
+      hasArguments = true;
+    }
+    if (devicePixelRatio != null) {
+      if (hasArguments)
+        result.write(', ');
+      result.write('devicePixelRatio: ${devicePixelRatio!.toStringAsFixed(1)}');
+      hasArguments = true;
+    }
+    if (locale != null) {
+      if (hasArguments)
+        result.write(', ');
+      result.write('locale: $locale');
+      hasArguments = true;
+    }
+    if (textDirection != null) {
+      if (hasArguments)
+        result.write(', ');
+      result.write('textDirection: $textDirection');
+      hasArguments = true;
+    }
+    if (size != null) {
+      if (hasArguments)
+        result.write(', ');
+      result.write('size: $size');
+      hasArguments = true;
+    }
+    if (platform != null) {
+      if (hasArguments)
+        result.write(', ');
+      result.write('platform: ${describeEnum(platform!)}');
+      hasArguments = true;
+    }
+    result.write(')');
+    return result.toString();
+  }
+}
+
+/// Performs the decode process for use in [ImageProvider.load].
+///
+/// This callback allows decoupling of the `cacheWidth`, `cacheHeight`, and
+/// `allowUpscaling` parameters from implementations of [ImageProvider] that do
+/// not expose them.
+///
+/// See also:
+///
+///  * [ResizeImage], which uses this to override the `cacheWidth`,
+///    `cacheHeight`, and `allowUpscaling` parameters.
+typedef DecoderCallback = Future<ui.Codec> Function(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool allowUpscaling});
+
+/// Identifies an image without committing to the precise final asset. This
+/// allows a set of images to be identified and for the precise image to later
+/// be resolved based on the environment, e.g. the device pixel ratio.
+///
+/// To obtain an [ImageStream] from an [ImageProvider], call [resolve],
+/// passing it an [ImageConfiguration] object.
+///
+/// [ImageProvider] uses the global [imageCache] to cache images.
+///
+/// The type argument `T` is the type of the object used to represent a resolved
+/// configuration. This is also the type used for the key in the image cache. It
+/// should be immutable and implement the [==] operator and the [hashCode]
+/// getter. Subclasses should subclass a variant of [ImageProvider] with an
+/// explicit `T` type argument.
+///
+/// The type argument does not have to be specified when using the type as an
+/// argument (where any image provider is acceptable).
+///
+/// The following image formats are supported: {@macro flutter.dart:ui.imageFormats}
+///
+/// ## Lifecycle of resolving an image
+///
+/// The [ImageProvider] goes through the following lifecycle to resolve an
+/// image, once the [resolve] method is called:
+///
+///   1. Create an [ImageStream] using [createStream] to return to the caller.
+///      This stream will be used to communicate back to the caller when the
+///      image is decoded and ready to display, or when an error occurs.
+///   2. Obtain the key for the image using [obtainKey].
+///      Calling this method can throw exceptions into the zone asynchronously
+///      or into the callstack synchronously. To handle that, an error handler
+///      is created that catches both synchronous and asynchronous errors, to
+///      make sure errors can be routed to the correct consumers.
+///      The error handler is passed on to [resolveStreamForKey] and the
+///      [ImageCache].
+///   3. If the key is successfully obtained, schedule resolution of the image
+///      using that key. This is handled by [resolveStreamForKey]. That method
+///      may fizzle if it determines the image is no longer necessary, use the
+///      provided [ImageErrorListener] to report an error, set the completer
+///      from the cache if possible, or call [load] to fetch the encoded image
+///      bytes and schedule decoding.
+///   4. The [load] method is responsible for both fetching the encoded bytes
+///      and decoding them using the provided [DecoderCallback]. It is called
+///      in a context that uses the [ImageErrorListener] to report errors back.
+///
+/// Subclasses normally only have to implement the [load] and [obtainKey]
+/// methods. A subclass that needs finer grained control over the [ImageStream]
+/// type must override [createStream]. A subclass that needs finer grained
+/// control over the resolution, such as delaying calling [load], must override
+/// [resolveStreamForKey].
+///
+/// The [resolve] method is marked as [nonVirtual] so that [ImageProvider]s can
+/// be properly composed, and so that the base class can properly set up error
+/// handling for subsequent methods.
+///
+/// ## Using an [ImageProvider]
+///
+/// {@tool snippet}
+///
+/// The following shows the code required to write a widget that fully conforms
+/// to the [ImageProvider] and [Widget] protocols. (It is essentially a
+/// bare-bones version of the [widgets.Image] widget.)
+///
+/// ```dart
+/// class MyImage extends StatefulWidget {
+///   const MyImage({
+///     Key? key,
+///     required this.imageProvider,
+///   }) : super(key: key);
+///
+///   final ImageProvider imageProvider;
+///
+///   @override
+///   _MyImageState createState() => _MyImageState();
+/// }
+///
+/// class _MyImageState extends State<MyImage> {
+///   ImageStream? _imageStream;
+///   ImageInfo? _imageInfo;
+///
+///   @override
+///   void didChangeDependencies() {
+///     super.didChangeDependencies();
+///     // We call _getImage here because createLocalImageConfiguration() needs to
+///     // be called again if the dependencies changed, in case the changes relate
+///     // to the DefaultAssetBundle, MediaQuery, etc, which that method uses.
+///     _getImage();
+///   }
+///
+///   @override
+///   void didUpdateWidget(MyImage oldWidget) {
+///     super.didUpdateWidget(oldWidget);
+///     if (widget.imageProvider != oldWidget.imageProvider)
+///       _getImage();
+///   }
+///
+///   void _getImage() {
+///     final ImageStream? oldImageStream = _imageStream;
+///     _imageStream = widget.imageProvider.resolve(createLocalImageConfiguration(context));
+///     if (_imageStream!.key != oldImageStream?.key) {
+///       // If the keys are the same, then we got the same image back, and so we don't
+///       // need to update the listeners. If the key changed, though, we must make sure
+///       // to switch our listeners to the new image stream.
+///       final ImageStreamListener listener = ImageStreamListener(_updateImage);
+///       oldImageStream?.removeListener(listener);
+///       _imageStream!.addListener(listener);
+///     }
+///   }
+///
+///   void _updateImage(ImageInfo imageInfo, bool synchronousCall) {
+///     setState(() {
+///       // Trigger a build whenever the image changes.
+///       _imageInfo?.dispose();
+///       _imageInfo = imageInfo;
+///     });
+///   }
+///
+///   @override
+///   void dispose() {
+///     _imageStream?.removeListener(ImageStreamListener(_updateImage));
+///     _imageInfo?.dispose();
+///     _imageInfo = null;
+///     super.dispose();
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return RawImage(
+///       image: _imageInfo?.image, // this is a dart:ui Image object
+///       scale: _imageInfo?.scale ?? 1.0,
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+@optionalTypeArgs
+abstract class ImageProvider<T extends Object> {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const ImageProvider();
+
+  /// Resolves this image provider using the given `configuration`, returning
+  /// an [ImageStream].
+  ///
+  /// This is the public entry-point of the [ImageProvider] class hierarchy.
+  ///
+  /// Subclasses should implement [obtainKey] and [load], which are used by this
+  /// method. If they need to change the implementation of [ImageStream] used,
+  /// they should override [createStream]. If they need to manage the actual
+  /// resolution of the image, they should override [resolveStreamForKey].
+  ///
+  /// See the Lifecycle documentation on [ImageProvider] for more information.
+  @nonVirtual
+  ImageStream resolve(ImageConfiguration configuration) {
+    assert(configuration != null);
+    final ImageStream stream = createStream(configuration);
+    // Load the key (potentially asynchronously), set up an error handling zone,
+    // and call resolveStreamForKey.
+    _createErrorHandlerAndKey(
+      configuration,
+      (T key, ImageErrorListener errorHandler) {
+        resolveStreamForKey(configuration, stream, key, errorHandler);
+      },
+      (T? key, Object exception, StackTrace? stack) async {
+        await null; // wait an event turn in case a listener has been added to the image stream.
+        final _ErrorImageCompleter imageCompleter = _ErrorImageCompleter();
+        stream.setCompleter(imageCompleter);
+        InformationCollector? collector;
+        assert(() {
+          collector = () sync* {
+            yield DiagnosticsProperty<ImageProvider>('Image provider', this);
+            yield DiagnosticsProperty<ImageConfiguration>('Image configuration', configuration);
+            yield DiagnosticsProperty<T>('Image key', key, defaultValue: null);
+          };
+          return true;
+        }());
+        imageCompleter.setError(
+          exception: exception,
+          stack: stack,
+          context: ErrorDescription('while resolving an image'),
+          silent: true, // could be a network error or whatnot
+          informationCollector: collector,
+        );
+      },
+    );
+    return stream;
+  }
+
+  /// Called by [resolve] to create the [ImageStream] it returns.
+  ///
+  /// Subclasses should override this instead of [resolve] if they need to
+  /// return some subclass of [ImageStream]. The stream created here will be
+  /// passed to [resolveStreamForKey].
+  @protected
+  ImageStream createStream(ImageConfiguration configuration) {
+    return ImageStream();
+  }
+
+  /// Returns the cache location for the key that this [ImageProvider] creates.
+  ///
+  /// The location may be [ImageCacheStatus.untracked], indicating that this
+  /// image provider's key is not available in the [ImageCache].
+  ///
+  /// The `cache` and `configuration` parameters must not be null. If the
+  /// `handleError` parameter is null, errors will be reported to
+  /// [FlutterError.onError], and the method will return null.
+  ///
+  /// A completed return value of null indicates that an error has occurred.
+  Future<ImageCacheStatus?> obtainCacheStatus({
+    required ImageConfiguration configuration,
+    ImageErrorListener? handleError,
+  }) {
+    assert(configuration != null);
+    final Completer<ImageCacheStatus?> completer = Completer<ImageCacheStatus?>();
+    _createErrorHandlerAndKey(
+      configuration,
+      (T key, ImageErrorListener innerHandleError) {
+        completer.complete(PaintingBinding.instance!.imageCache!.statusForKey(key));
+      },
+      (T? key, Object exception, StackTrace? stack) async {
+        if (handleError != null) {
+          handleError(exception, stack);
+        } else {
+          InformationCollector? collector;
+          assert(() {
+            collector = () sync* {
+              yield DiagnosticsProperty<ImageProvider>('Image provider', this);
+              yield DiagnosticsProperty<ImageConfiguration>('Image configuration', configuration);
+              yield DiagnosticsProperty<T>('Image key', key, defaultValue: null);
+            };
+            return true;
+          }());
+          FlutterError.reportError(FlutterErrorDetails(
+            context: ErrorDescription('while checking the cache location of an image'),
+            informationCollector: collector,
+            exception: exception,
+            stack: stack,
+          ));
+          completer.complete(null);
+        }
+      },
+    );
+    return completer.future;
+  }
+
+  /// This method is used by both [resolve] and [obtainCacheStatus] to ensure
+  /// that errors thrown during key creation are handled whether synchronous or
+  /// asynchronous.
+  void _createErrorHandlerAndKey(
+    ImageConfiguration configuration,
+    _KeyAndErrorHandlerCallback<T> successCallback,
+    _AsyncKeyErrorHandler<T?> errorCallback,
+  ) {
+    T? obtainedKey;
+    bool didError = false;
+    Future<void> handleError(Object exception, StackTrace? stack) async {
+      if (didError) {
+        return;
+      }
+      if (!didError) {
+        errorCallback(obtainedKey, exception, stack);
+      }
+      didError = true;
+    }
+
+    // If an error is added to a synchronous completer before a listener has been
+    // added, it can throw an error both into the zone and up the stack. Thus, it
+    // looks like the error has been caught, but it is in fact also bubbling to the
+    // zone. Since we cannot prevent all usage of Completer.sync here, or rather
+    // that changing them would be too breaking, we instead hook into the same
+    // zone mechanism to intercept the uncaught error and deliver it to the
+    // image stream's error handler. Note that these errors may be duplicated,
+    // hence the need for the `didError` flag.
+    final Zone dangerZone = Zone.current.fork(
+      specification: ZoneSpecification(
+        handleUncaughtError: (Zone zone, ZoneDelegate delegate, Zone parent, Object error, StackTrace stackTrace) {
+          handleError(error, stackTrace);
+        }
+      )
+    );
+    dangerZone.runGuarded(() {
+      Future<T> key;
+      try {
+        key = obtainKey(configuration);
+      } catch (error, stackTrace) {
+        handleError(error, stackTrace);
+        return;
+      }
+      key.then<void>((T key) {
+        obtainedKey = key;
+        try {
+          successCallback(key, handleError);
+        } catch (error, stackTrace) {
+          handleError(error, stackTrace);
+        }
+      }).catchError(handleError);
+    });
+  }
+
+  /// Called by [resolve] with the key returned by [obtainKey].
+  ///
+  /// Subclasses should override this method rather than calling [obtainKey] if
+  /// they need to use a key directly. The [resolve] method installs appropriate
+  /// error handling guards so that errors will bubble up to the right places in
+  /// the framework, and passes those guards along to this method via the
+  /// [handleError] parameter.
+  ///
+  /// It is safe for the implementation of this method to call [handleError]
+  /// multiple times if multiple errors occur, or if an error is thrown both
+  /// synchronously into the current part of the stack and thrown into the
+  /// enclosing [Zone].
+  ///
+  /// The default implementation uses the key to interact with the [ImageCache],
+  /// calling [ImageCache.putIfAbsent] and notifying listeners of the [stream].
+  /// Implementers that do not call super are expected to correctly use the
+  /// [ImageCache].
+  @protected
+  void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) {
+    // This is an unusual edge case where someone has told us that they found
+    // the image we want before getting to this method. We should avoid calling
+    // load again, but still update the image cache with LRU information.
+    if (stream.completer != null) {
+      final ImageStreamCompleter? completer = PaintingBinding.instance!.imageCache!.putIfAbsent(
+        key,
+        () => stream.completer!,
+        onError: handleError,
+      );
+      assert(identical(completer, stream.completer));
+      return;
+    }
+    final ImageStreamCompleter? completer = PaintingBinding.instance!.imageCache!.putIfAbsent(
+      key,
+      () => load(key, PaintingBinding.instance!.instantiateImageCodec),
+      onError: handleError,
+    );
+    if (completer != null) {
+      stream.setCompleter(completer);
+    }
+  }
+
+  /// Evicts an entry from the image cache.
+  ///
+  /// Returns a [Future] which indicates whether the value was successfully
+  /// removed.
+  ///
+  /// The [ImageProvider] used does not need to be the same instance that was
+  /// passed to an [Image] widget, but it does need to create a key which is
+  /// equal to one.
+  ///
+  /// The [cache] is optional and defaults to the global image cache.
+  ///
+  /// The [configuration] is optional and defaults to
+  /// [ImageConfiguration.empty].
+  ///
+  /// {@tool snippet}
+  ///
+  /// The following sample code shows how an image loaded using the [Image]
+  /// widget can be evicted using a [NetworkImage] with a matching URL.
+  ///
+  /// ```dart
+  /// class MyWidget extends StatelessWidget {
+  ///   final String url = '...';
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Image.network(url);
+  ///   }
+  ///
+  ///   void evictImage() {
+  ///     final NetworkImage provider = NetworkImage(url);
+  ///     provider.evict().then<void>((bool success) {
+  ///       if (success)
+  ///         debugPrint('removed image!');
+  ///     });
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  Future<bool> evict({ ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty }) async {
+    cache ??= imageCache;
+    final T key = await obtainKey(configuration);
+    return cache!.evict(key);
+  }
+
+  /// Converts an ImageProvider's settings plus an ImageConfiguration to a key
+  /// that describes the precise image to load.
+  ///
+  /// The type of the key is determined by the subclass. It is a value that
+  /// unambiguously identifies the image (_including its scale_) that the [load]
+  /// method will fetch. Different [ImageProvider]s given the same constructor
+  /// arguments and [ImageConfiguration] objects should return keys that are
+  /// '==' to each other (possibly by using a class for the key that itself
+  /// implements [==]).
+  Future<T> obtainKey(ImageConfiguration configuration);
+
+  /// Converts a key into an [ImageStreamCompleter], and begins fetching the
+  /// image.
+  ///
+  /// The [decode] callback provides the logic to obtain the codec for the
+  /// image.
+  ///
+  /// See also:
+  ///
+  ///  * [ResizeImage], for modifying the key to account for cache dimensions.
+  @protected
+  ImageStreamCompleter load(T key, DecoderCallback decode);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'ImageConfiguration')}()';
+}
+
+/// Key for the image obtained by an [AssetImage] or [ExactAssetImage].
+///
+/// This is used to identify the precise resource in the [imageCache].
+@immutable
+class AssetBundleImageKey {
+  /// Creates the key for an [AssetImage] or [AssetBundleImageProvider].
+  ///
+  /// The arguments must not be null.
+  const AssetBundleImageKey({
+    required this.bundle,
+    required this.name,
+    required this.scale,
+  }) : assert(bundle != null),
+       assert(name != null),
+       assert(scale != null);
+
+  /// The bundle from which the image will be obtained.
+  ///
+  /// The image is obtained by calling [AssetBundle.load] on the given [bundle]
+  /// using the key given by [name].
+  final AssetBundle bundle;
+
+  /// The key to use to obtain the resource from the [bundle]. This is the
+  /// argument passed to [AssetBundle.load].
+  final String name;
+
+  /// The scale to place in the [ImageInfo] object of the image.
+  final double scale;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is AssetBundleImageKey
+        && other.bundle == bundle
+        && other.name == name
+        && other.scale == scale;
+  }
+
+  @override
+  int get hashCode => hashValues(bundle, name, scale);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'AssetBundleImageKey')}(bundle: $bundle, name: "$name", scale: $scale)';
+}
+
+/// A subclass of [ImageProvider] that knows about [AssetBundle]s.
+///
+/// This factors out the common logic of [AssetBundle]-based [ImageProvider]
+/// classes, simplifying what subclasses must implement to just [obtainKey].
+abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKey> {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const AssetBundleImageProvider();
+
+  /// Converts a key into an [ImageStreamCompleter], and begins fetching the
+  /// image.
+  @override
+  ImageStreamCompleter load(AssetBundleImageKey key, DecoderCallback decode) {
+    InformationCollector? collector;
+    assert(() {
+      collector = () sync* {
+        yield DiagnosticsProperty<ImageProvider>('Image provider', this);
+        yield DiagnosticsProperty<AssetBundleImageKey>('Image key', key);
+      };
+      return true;
+    }());
+    return MultiFrameImageStreamCompleter(
+      codec: _loadAsync(key, decode),
+      scale: key.scale,
+      debugLabel: key.name,
+      informationCollector: collector
+    );
+  }
+
+  /// Fetches the image from the asset bundle, decodes it, and returns a
+  /// corresponding [ImageInfo] object.
+  ///
+  /// This function is used by [load].
+  @protected
+  Future<ui.Codec> _loadAsync(AssetBundleImageKey key, DecoderCallback decode) async {
+    ByteData? data;
+    // Hot reload/restart could change whether an asset bundle or key in a
+    // bundle are available, or if it is a network backed bundle.
+    try {
+      data = await key.bundle.load(key.name);
+    } on FlutterError {
+      PaintingBinding.instance!.imageCache!.evict(key);
+      rethrow;
+    }
+    // `key.bundle.load` has a non-nullable return type, but might be null when
+    // running with weak checking, so we need to null check it anyway (and
+    // ignore the warning that the null-handling logic is dead code).
+    if (data == null) { // ignore: dead_code
+      PaintingBinding.instance!.imageCache!.evict(key);
+      throw StateError('Unable to read data');
+    }
+    return await decode(data.buffer.asUint8List());
+  }
+}
+
+@immutable
+class _SizeAwareCacheKey {
+  const _SizeAwareCacheKey(this.providerCacheKey, this.width, this.height);
+
+  final Object providerCacheKey;
+
+  final int? width;
+
+  final int? height;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _SizeAwareCacheKey
+        && other.providerCacheKey == providerCacheKey
+        && other.width == width
+        && other.height == height;
+  }
+
+  @override
+  int get hashCode => hashValues(providerCacheKey, width, height);
+}
+
+/// Instructs Flutter to decode the image at the specified dimensions
+/// instead of at its native size.
+///
+/// This allows finer control of the size of the image in [ImageCache] and is
+/// generally used to reduce the memory footprint of [ImageCache].
+///
+/// The decoded image may still be displayed at sizes other than the
+/// cached size provided here.
+class ResizeImage extends ImageProvider<_SizeAwareCacheKey> {
+  /// Creates an ImageProvider that decodes the image to the specified size.
+  ///
+  /// The cached image will be directly decoded and stored at the resolution
+  /// defined by `width` and `height`. The image will lose detail and
+  /// use less memory if resized to a size smaller than the native size.
+  const ResizeImage(
+    this.imageProvider, {
+    this.width,
+    this.height,
+    this.allowUpscaling = false,
+  }) : assert(width != null || height != null),
+       assert(allowUpscaling != null);
+
+  /// The [ImageProvider] that this class wraps.
+  final ImageProvider imageProvider;
+
+  /// The width the image should decode to and cache.
+  final int? width;
+
+  /// The height the image should decode to and cache.
+  final int? height;
+
+  /// Whether the [width] and [height] parameters should be clamped to the
+  /// intrinsic width and height of the image.
+  ///
+  /// In general, it is better for memory usage to avoid scaling the image
+  /// beyond its intrinsic dimensions when decoding it. If there is a need to
+  /// scale an image larger, it is better to apply a scale to the canvas, or
+  /// to use an appropriate [Image.fit].
+  final bool allowUpscaling;
+
+  /// Composes the `provider` in a [ResizeImage] only when `cacheWidth` and
+  /// `cacheHeight` are not both null.
+  ///
+  /// When `cacheWidth` and `cacheHeight` are both null, this will return the
+  /// `provider` directly.
+  static ImageProvider<Object> resizeIfNeeded(int? cacheWidth, int? cacheHeight, ImageProvider<Object> provider) {
+    if (cacheWidth != null || cacheHeight != null) {
+      return ResizeImage(provider, width: cacheWidth, height: cacheHeight);
+    }
+    return provider;
+  }
+
+  @override
+  ImageStreamCompleter load(_SizeAwareCacheKey key, DecoderCallback decode) {
+    final DecoderCallback decodeResize = (Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
+      assert(
+        cacheWidth == null && cacheHeight == null && allowUpscaling == null,
+        'ResizeImage cannot be composed with another ImageProvider that applies '
+        'cacheWidth, cacheHeight, or allowUpscaling.'
+      );
+      return decode(bytes, cacheWidth: width, cacheHeight: height, allowUpscaling: this.allowUpscaling);
+    };
+    final ImageStreamCompleter completer = imageProvider.load(key.providerCacheKey, decodeResize);
+    if (!kReleaseMode) {
+      completer.debugLabel = '${completer.debugLabel} - Resized(${key.width}×${key.height})';
+    }
+    return completer;
+  }
+
+  @override
+  Future<_SizeAwareCacheKey> obtainKey(ImageConfiguration configuration) {
+    Completer<_SizeAwareCacheKey>? completer;
+    // If the imageProvider.obtainKey future is synchronous, then we will be able to fill in result with
+    // a value before completer is initialized below.
+    SynchronousFuture<_SizeAwareCacheKey>? result;
+    imageProvider.obtainKey(configuration).then((Object key) {
+      if (completer == null) {
+        // This future has completed synchronously (completer was never assigned),
+        // so we can directly create the synchronous result to return.
+        result = SynchronousFuture<_SizeAwareCacheKey>(_SizeAwareCacheKey(key, width, height));
+      } else {
+        // This future did not synchronously complete.
+        completer.complete(_SizeAwareCacheKey(key, width, height));
+      }
+    });
+    if (result != null) {
+      return result!;
+    }
+    // If the code reaches here, it means the imageProvider.obtainKey was not
+    // completed sync, so we initialize the completer for completion later.
+    completer = Completer<_SizeAwareCacheKey>();
+    return completer.future;
+  }
+}
+
+/// Fetches the given URL from the network, associating it with the given scale.
+///
+/// The image will be cached regardless of cache headers from the server.
+///
+/// When a network image is used on the Web platform, the `cacheWidth` and
+/// `cacheHeight` parameters of the [DecoderCallback] are ignored as the Web
+/// engine delegates image decoding of network images to the Web, which does
+/// not support custom decode sizes.
+///
+/// See also:
+///
+///  * [Image.network] for a shorthand of an [Image] widget backed by [NetworkImage].
+// TODO(ianh): Find some way to honor cache headers to the extent that when the
+// last reference to an image is released, we proactively evict the image from
+// our cache if the headers describe the image as having expired at that point.
+abstract class NetworkImage extends ImageProvider<NetworkImage> {
+  /// Creates an object that fetches the image at the given URL.
+  ///
+  /// The arguments [url] and [scale] must not be null.
+  const factory NetworkImage(String url, { double scale, Map<String, String>? headers }) = network_image.NetworkImage;
+
+  /// The URL from which the image will be fetched.
+  String get url;
+
+  /// The scale to place in the [ImageInfo] object of the image.
+  double get scale;
+
+  /// The HTTP headers that will be used with [HttpClient.get] to fetch image from network.
+  ///
+  /// When running flutter on the web, headers are not used.
+  Map<String, String>? get headers;
+
+  @override
+  ImageStreamCompleter load(NetworkImage key, DecoderCallback decode);
+}
+
+/// Decodes the given [File] object as an image, associating it with the given
+/// scale.
+///
+/// The provider does not monitor the file for changes. If you expect the
+/// underlying data to change, you should call the [evict] method.
+///
+/// See also:
+///
+///  * [Image.file] for a shorthand of an [Image] widget backed by [FileImage].
+@immutable
+class FileImage extends ImageProvider<FileImage> {
+  /// Creates an object that decodes a [File] as an image.
+  ///
+  /// The arguments must not be null.
+  const FileImage(this.file, { this.scale = 1.0 })
+    : assert(file != null),
+      assert(scale != null);
+
+  /// The file to decode into an image.
+  final File file;
+
+  /// The scale to place in the [ImageInfo] object of the image.
+  final double scale;
+
+  @override
+  Future<FileImage> obtainKey(ImageConfiguration configuration) {
+    return SynchronousFuture<FileImage>(this);
+  }
+
+  @override
+  ImageStreamCompleter load(FileImage key, DecoderCallback decode) {
+    return MultiFrameImageStreamCompleter(
+      codec: _loadAsync(key, decode),
+      scale: key.scale,
+      debugLabel: key.file.path,
+      informationCollector: () sync* {
+        yield ErrorDescription('Path: ${file.path}');
+      },
+    );
+  }
+
+  Future<ui.Codec> _loadAsync(FileImage key, DecoderCallback decode) async {
+    assert(key == this);
+
+    final Uint8List bytes = await file.readAsBytes();
+
+    if (bytes.lengthInBytes == 0) {
+      // The file may become available later.
+      PaintingBinding.instance!.imageCache!.evict(key);
+      throw StateError('$file is empty and cannot be loaded as an image.');
+    }
+
+    return await decode(bytes);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is FileImage
+        && other.file.path == file.path
+        && other.scale == scale;
+  }
+
+  @override
+  int get hashCode => hashValues(file.path, scale);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'FileImage')}("${file.path}", scale: $scale)';
+}
+
+/// Decodes the given [Uint8List] buffer as an image, associating it with the
+/// given scale.
+///
+/// The provided [bytes] buffer should not be changed after it is provided
+/// to a [MemoryImage]. To provide an [ImageStream] that represents an image
+/// that changes over time, consider creating a new subclass of [ImageProvider]
+/// whose [load] method returns a subclass of [ImageStreamCompleter] that can
+/// handle providing multiple images.
+///
+/// See also:
+///
+///  * [Image.memory] for a shorthand of an [Image] widget backed by [MemoryImage].
+@immutable
+class MemoryImage extends ImageProvider<MemoryImage> {
+  /// Creates an object that decodes a [Uint8List] buffer as an image.
+  ///
+  /// The arguments must not be null.
+  const MemoryImage(this.bytes, { this.scale = 1.0 })
+    : assert(bytes != null),
+      assert(scale != null);
+
+  /// The bytes to decode into an image.
+  final Uint8List bytes;
+
+  /// The scale to place in the [ImageInfo] object of the image.
+  final double scale;
+
+  @override
+  Future<MemoryImage> obtainKey(ImageConfiguration configuration) {
+    return SynchronousFuture<MemoryImage>(this);
+  }
+
+  @override
+  ImageStreamCompleter load(MemoryImage key, DecoderCallback decode) {
+    return MultiFrameImageStreamCompleter(
+      codec: _loadAsync(key, decode),
+      scale: key.scale,
+      debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})',
+    );
+  }
+
+  Future<ui.Codec> _loadAsync(MemoryImage key, DecoderCallback decode) {
+    assert(key == this);
+
+    return decode(bytes);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is MemoryImage
+        && other.bytes == bytes
+        && other.scale == scale;
+  }
+
+  @override
+  int get hashCode => hashValues(bytes.hashCode, scale);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'MemoryImage')}(${describeIdentity(bytes)}, scale: $scale)';
+}
+
+/// Fetches an image from an [AssetBundle], associating it with the given scale.
+///
+/// This implementation requires an explicit final [assetName] and [scale] on
+/// construction, and ignores the device pixel ratio and size in the
+/// configuration passed into [resolve]. For a resolution-aware variant that
+/// uses the configuration to pick an appropriate image based on the device
+/// pixel ratio and size, see [AssetImage].
+///
+/// ## Fetching assets
+///
+/// When fetching an image provided by the app itself, use the [assetName]
+/// argument to name the asset to choose. For instance, consider a directory
+/// `icons` with an image `heart.png`. First, the `pubspec.yaml` of the project
+/// should specify its assets in the `flutter` section:
+///
+/// ```yaml
+/// flutter:
+///   assets:
+///     - icons/heart.png
+/// ```
+///
+/// Then, to fetch the image and associate it with scale `1.5`, use:
+///
+/// ```dart
+/// AssetImage('icons/heart.png', scale: 1.5)
+/// ```
+///
+/// ## Assets in packages
+///
+/// To fetch an asset from a package, the [package] argument must be provided.
+/// For instance, suppose the structure above is inside a package called
+/// `my_icons`. Then to fetch the image, use:
+///
+/// ```dart
+/// AssetImage('icons/heart.png', scale: 1.5, package: 'my_icons')
+/// ```
+///
+/// Assets used by the package itself should also be fetched using the [package]
+/// argument as above.
+///
+/// If the desired asset is specified in the `pubspec.yaml` of the package, it
+/// is bundled automatically with the app. In particular, assets used by the
+/// package itself must be specified in its `pubspec.yaml`.
+///
+/// A package can also choose to have assets in its 'lib/' folder that are not
+/// specified in its `pubspec.yaml`. In this case for those images to be
+/// bundled, the app has to specify which ones to include. For instance a
+/// package named `fancy_backgrounds` could have:
+///
+/// ```
+/// lib/backgrounds/background1.png
+/// lib/backgrounds/background2.png
+/// lib/backgrounds/background3.png
+/// ```
+///
+/// To include, say the first image, the `pubspec.yaml` of the app should specify
+/// it in the `assets` section:
+///
+/// ```yaml
+///   assets:
+///     - packages/fancy_backgrounds/backgrounds/background1.png
+/// ```
+///
+/// The `lib/` is implied, so it should not be included in the asset path.
+///
+/// See also:
+///
+///  * [Image.asset] for a shorthand of an [Image] widget backed by
+///    [ExactAssetImage] when using a scale.
+@immutable
+class ExactAssetImage extends AssetBundleImageProvider {
+  /// Creates an object that fetches the given image from an asset bundle.
+  ///
+  /// The [assetName] and [scale] arguments must not be null. The [scale] arguments
+  /// defaults to 1.0. The [bundle] argument may be null, in which case the
+  /// bundle provided in the [ImageConfiguration] passed to the [resolve] call
+  /// will be used instead.
+  ///
+  /// The [package] argument must be non-null when fetching an asset that is
+  /// included in a package. See the documentation for the [ExactAssetImage] class
+  /// itself for details.
+  const ExactAssetImage(
+    this.assetName, {
+    this.scale = 1.0,
+    this.bundle,
+    this.package,
+  }) : assert(assetName != null),
+       assert(scale != null);
+
+  /// The name of the asset.
+  final String assetName;
+
+  /// The key to use to obtain the resource from the [bundle]. This is the
+  /// argument passed to [AssetBundle.load].
+  String get keyName => package == null ? assetName : 'packages/$package/$assetName';
+
+  /// The scale to place in the [ImageInfo] object of the image.
+  final double scale;
+
+  /// The bundle from which the image will be obtained.
+  ///
+  /// If the provided [bundle] is null, the bundle provided in the
+  /// [ImageConfiguration] passed to the [resolve] call will be used instead. If
+  /// that is also null, the [rootBundle] is used.
+  ///
+  /// The image is obtained by calling [AssetBundle.load] on the given [bundle]
+  /// using the key given by [keyName].
+  final AssetBundle? bundle;
+
+  /// The name of the package from which the image is included. See the
+  /// documentation for the [ExactAssetImage] class itself for details.
+  final String? package;
+
+  @override
+  Future<AssetBundleImageKey> obtainKey(ImageConfiguration configuration) {
+    return SynchronousFuture<AssetBundleImageKey>(AssetBundleImageKey(
+      bundle: bundle ?? configuration.bundle ?? rootBundle,
+      name: keyName,
+      scale: scale,
+    ));
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ExactAssetImage
+        && other.keyName == keyName
+        && other.scale == scale
+        && other.bundle == bundle;
+  }
+
+  @override
+  int get hashCode => hashValues(keyName, scale, bundle);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'ExactAssetImage')}(name: "$keyName", scale: $scale, bundle: $bundle)';
+}
+
+// A completer used when resolving an image fails sync.
+class _ErrorImageCompleter extends ImageStreamCompleter {
+  _ErrorImageCompleter();
+
+  void setError({
+    DiagnosticsNode? context,
+    required Object exception,
+    StackTrace? stack,
+    InformationCollector? informationCollector,
+    bool silent = false,
+  }) {
+    reportError(
+      context: context,
+      exception: exception,
+      stack: stack,
+      informationCollector: informationCollector,
+      silent: silent,
+    );
+  }
+}
+
+/// The exception thrown when the HTTP request to load a network image fails.
+class NetworkImageLoadException implements Exception {
+  /// Creates a [NetworkImageLoadException] with the specified http [statusCode]
+  /// and [uri].
+  NetworkImageLoadException({required this.statusCode, required this.uri})
+      : assert(uri != null),
+        assert(statusCode != null),
+        _message = 'HTTP request failed, statusCode: $statusCode, $uri';
+
+  /// The HTTP status code from the server.
+  final int statusCode;
+
+  /// A human-readable error message.
+  final String _message;
+
+  /// Resolved URL of the requested image.
+  final Uri uri;
+
+  @override
+  String toString() => _message;
+}
diff --git a/lib/src/painting/image_resolution.dart b/lib/src/painting/image_resolution.dart
new file mode 100644
index 0000000..8507d4a
--- /dev/null
+++ b/lib/src/painting/image_resolution.dart
@@ -0,0 +1,349 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:convert';
+import 'package:flute/ui.dart' show hashValues;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/services.dart';
+
+import 'image_provider.dart';
+
+const String _kAssetManifestFileName = 'AssetManifest.json';
+
+/// A screen with a device-pixel ratio strictly less than this value is
+/// considered a low-resolution screen (typically entry-level to mid-range
+/// laptops, desktop screens up to QHD, low-end tablets such as Kindle Fire).
+const double _kLowDprLimit = 2.0;
+
+/// Fetches an image from an [AssetBundle], having determined the exact image to
+/// use based on the context.
+///
+/// Given a main asset and a set of variants, AssetImage chooses the most
+/// appropriate asset for the current context, based on the device pixel ratio
+/// and size given in the configuration passed to [resolve].
+///
+/// To show a specific image from a bundle without any asset resolution, use an
+/// [AssetBundleImageProvider].
+///
+/// ## Naming assets for matching with different pixel densities
+///
+/// Main assets are presumed to match a nominal pixel ratio of 1.0. To specify
+/// assets targeting different pixel ratios, place the variant assets in
+/// the application bundle under subdirectories named in the form "Nx", where
+/// N is the nominal device pixel ratio for that asset.
+///
+/// For example, suppose an application wants to use an icon named
+/// "heart.png". This icon has representations at 1.0 (the main icon), as well
+/// as 2.0 and 4.0 pixel ratios (variants). The asset bundle should then
+/// contain the following assets:
+///
+/// ```
+/// heart.png
+/// 2.0x/heart.png
+/// 4.0x/heart.png
+/// ```
+///
+/// On a device with a 1.0 device pixel ratio, the image chosen would be
+/// heart.png; on a device with a 2.0 device pixel ratio, the image chosen
+/// would be 2.0x/heart.png; on a device with a 4.0 device pixel ratio, the
+/// image chosen would be 4.0x/heart.png.
+///
+/// On a device with a device pixel ratio that does not exactly match an
+/// available asset the "best match" is chosen. Which asset is the best depends
+/// on the screen. Low-resolution screens (those with device pixel ratio
+/// strictly less than 2.0) use a different matching algorithm from the
+/// high-resolution screen. Because in low-resolution screens the physical
+/// pixels are visible to the user upscaling artifacts (e.g. blurred edges) are
+/// more pronounced. Therefore, a higher resolution asset is chosen, if
+/// available. For higher-resolution screens, where individual physical pixels
+/// are not visible to the user, the asset variant with the pixel ratio that's
+/// the closest to the screen's device pixel ratio is chosen.
+///
+/// For example, for a screen with device pixel ratio 1.25 the image chosen
+/// would be 2.0x/heart.png, even though heart.png (i.e. 1.0) is closer. This
+/// is because the screen is considered low-resolution. For a screen with
+/// device pixel ratio of 2.25 the image chosen would also be 2.0x/heart.png.
+/// This is because the screen is considered to be a high-resolution screen,
+/// and therefore upscaling a 2.0x image to 2.25 won't result in visible
+/// upscaling artifacts. However, for a screen with device-pixel ratio 3.25 the
+/// image chosen would be 4.0x/heart.png because it's closer to 4.0 than it is
+/// to 2.0.
+///
+/// Choosing a higher-resolution image than necessary may waste significantly
+/// more memory if the difference between the screen device pixel ratio and
+/// the device pixel ratio of the image is high. To reduce memory usage,
+/// consider providing more variants of the image. In the example above adding
+/// a 3.0x/heart.png variant would improve memory usage for screens with device
+/// pixel ratios between 3.0 and 3.5.
+///
+/// [ImageConfiguration] can be used to customize the selection of the image
+/// variant by setting [ImageConfiguration.devicePixelRatio] to value different
+/// from the default. The default value is derived from
+/// [MediaQueryData.devicePixelRatio] by [createLocalImageConfiguration].
+///
+/// The directory level of the asset does not matter as long as the variants are
+/// at the equivalent level; that is, the following is also a valid bundle
+/// structure:
+///
+/// ```
+/// icons/heart.png
+/// icons/1.5x/heart.png
+/// icons/2.0x/heart.png
+/// ```
+///
+/// assets/icons/3.0x/heart.png would be a valid variant of
+/// assets/icons/heart.png.
+///
+///
+/// ## Fetching assets
+///
+/// When fetching an image provided by the app itself, use the [assetName]
+/// argument to name the asset to choose. For instance, consider the structure
+/// above. First, the `pubspec.yaml` of the project should specify its assets in
+/// the `flutter` section:
+///
+/// ```yaml
+/// flutter:
+///   assets:
+///     - icons/heart.png
+/// ```
+///
+/// Then, to fetch the image, use:
+/// ```dart
+/// AssetImage('icons/heart.png')
+/// ```
+///
+/// ## Assets in packages
+///
+/// To fetch an asset from a package, the [package] argument must be provided.
+/// For instance, suppose the structure above is inside a package called
+/// `my_icons`. Then to fetch the image, use:
+///
+/// ```dart
+/// AssetImage('icons/heart.png', package: 'my_icons')
+/// ```
+///
+/// Assets used by the package itself should also be fetched using the [package]
+/// argument as above.
+///
+/// If the desired asset is specified in the `pubspec.yaml` of the package, it
+/// is bundled automatically with the app. In particular, assets used by the
+/// package itself must be specified in its `pubspec.yaml`.
+///
+/// A package can also choose to have assets in its 'lib/' folder that are not
+/// specified in its `pubspec.yaml`. In this case for those images to be
+/// bundled, the app has to specify which ones to include. For instance a
+/// package named `fancy_backgrounds` could have:
+///
+/// ```
+/// lib/backgrounds/background1.png
+/// lib/backgrounds/background2.png
+/// lib/backgrounds/background3.png
+/// ```
+///
+/// To include, say the first image, the `pubspec.yaml` of the app should specify
+/// it in the `assets` section:
+///
+/// ```yaml
+///   assets:
+///     - packages/fancy_backgrounds/backgrounds/background1.png
+/// ```
+///
+/// The `lib/` is implied, so it should not be included in the asset path.
+///
+/// See also:
+///
+///  * [Image.asset] for a shorthand of an [Image] widget backed by [AssetImage]
+///    when used without a scale.
+@immutable
+class AssetImage extends AssetBundleImageProvider {
+  /// Creates an object that fetches an image from an asset bundle.
+  ///
+  /// The [assetName] argument must not be null. It should name the main asset
+  /// from the set of images to choose from. The [package] argument must be
+  /// non-null when fetching an asset that is included in package. See the
+  /// documentation for the [AssetImage] class itself for details.
+  const AssetImage(
+    this.assetName, {
+    this.bundle,
+    this.package,
+  }) : assert(assetName != null);
+
+  /// The name of the main asset from the set of images to choose from. See the
+  /// documentation for the [AssetImage] class itself for details.
+  final String assetName;
+
+  /// The name used to generate the key to obtain the asset. For local assets
+  /// this is [assetName], and for assets from packages the [assetName] is
+  /// prefixed 'packages/<package_name>/'.
+  String get keyName => package == null ? assetName : 'packages/$package/$assetName';
+
+  /// The bundle from which the image will be obtained.
+  ///
+  /// If the provided [bundle] is null, the bundle provided in the
+  /// [ImageConfiguration] passed to the [resolve] call will be used instead. If
+  /// that is also null, the [rootBundle] is used.
+  ///
+  /// The image is obtained by calling [AssetBundle.load] on the given [bundle]
+  /// using the key given by [keyName].
+  final AssetBundle? bundle;
+
+  /// The name of the package from which the image is included. See the
+  /// documentation for the [AssetImage] class itself for details.
+  final String? package;
+
+  // We assume the main asset is designed for a device pixel ratio of 1.0
+  static const double _naturalResolution = 1.0;
+
+  @override
+  Future<AssetBundleImageKey> obtainKey(ImageConfiguration configuration) {
+    // This function tries to return a SynchronousFuture if possible. We do this
+    // because otherwise showing an image would always take at least one frame,
+    // which would be sad. (This code is called from inside build/layout/paint,
+    // which all happens in one call frame; using native Futures would guarantee
+    // that we resolve each future in a new call frame, and thus not in this
+    // build/layout/paint sequence.)
+    final AssetBundle chosenBundle = bundle ?? configuration.bundle ?? rootBundle;
+    Completer<AssetBundleImageKey>? completer;
+    Future<AssetBundleImageKey>? result;
+
+    chosenBundle.loadStructuredData<Map<String, List<String>>?>(_kAssetManifestFileName, _manifestParser).then<void>(
+      (Map<String, List<String>>? manifest) {
+        final String chosenName = _chooseVariant(
+          keyName,
+          configuration,
+          manifest == null ? null : manifest[keyName],
+        )!;
+        final double chosenScale = _parseScale(chosenName);
+        final AssetBundleImageKey key = AssetBundleImageKey(
+          bundle: chosenBundle,
+          name: chosenName,
+          scale: chosenScale,
+        );
+        if (completer != null) {
+          // We already returned from this function, which means we are in the
+          // asynchronous mode. Pass the value to the completer. The completer's
+          // future is what we returned.
+          completer.complete(key);
+        } else {
+          // We haven't yet returned, so we must have been called synchronously
+          // just after loadStructuredData returned (which means it provided us
+          // with a SynchronousFuture). Let's return a SynchronousFuture
+          // ourselves.
+          result = SynchronousFuture<AssetBundleImageKey>(key);
+        }
+      }
+    ).catchError((Object error, StackTrace stack) {
+      // We had an error. (This guarantees we weren't called synchronously.)
+      // Forward the error to the caller.
+      assert(completer != null);
+      assert(result == null);
+      completer!.completeError(error, stack);
+    });
+    if (result != null) {
+      // The code above ran synchronously, and came up with an answer.
+      // Return the SynchronousFuture that we created above.
+      return result!;
+    }
+    // The code above hasn't yet run its "then" handler yet. Let's prepare a
+    // completer for it to use when it does run.
+    completer = Completer<AssetBundleImageKey>();
+    return completer.future;
+  }
+
+  static Future<Map<String, List<String>>?> _manifestParser(String? jsonData) {
+    if (jsonData == null)
+      return SynchronousFuture<Map<String, List<String>>?>(null);
+    // TODO(ianh): JSON decoding really shouldn't be on the main thread.
+    final Map<String, dynamic> parsedJson = json.decode(jsonData) as Map<String, dynamic>;
+    final Iterable<String> keys = parsedJson.keys;
+    final Map<String, List<String>> parsedManifest =
+        Map<String, List<String>>.fromIterables(keys,
+          keys.map<List<String>>((String key) => List<String>.from(parsedJson[key] as List<dynamic>)));
+    // TODO(ianh): convert that data structure to the right types.
+    return SynchronousFuture<Map<String, List<String>>?>(parsedManifest);
+  }
+
+  String? _chooseVariant(String main, ImageConfiguration config, List<String>? candidates) {
+    if (config.devicePixelRatio == null || candidates == null || candidates.isEmpty)
+      return main;
+    // TODO(ianh): Consider moving this parsing logic into _manifestParser.
+    final SplayTreeMap<double, String> mapping = SplayTreeMap<double, String>();
+    for (final String candidate in candidates)
+      mapping[_parseScale(candidate)] = candidate;
+    // TODO(ianh): implement support for config.locale, config.textDirection,
+    // config.size, config.platform (then document this over in the Image.asset
+    // docs)
+    return _findBestVariant(mapping, config.devicePixelRatio!);
+  }
+
+  // Returns the "best" asset variant amongst the availabe `candidates`.
+  //
+  // The best variant is chosen as follows:
+  // - Choose a variant whose key matches `value` exactly, if available.
+  // - If `value` is less than the lowest key, choose the variant with the
+  //   lowest key.
+  // - If `value` is greater than the highest key, choose the variant with
+  //   the highest key.
+  // - If the screen has low device pixel ratio, chosse the variant with the
+  //   lowest key higher than `value`.
+  // - If the screen has high device pixel ratio, choose the variant with the
+  //   key nearest to `value`.
+  String? _findBestVariant(SplayTreeMap<double, String> candidates, double value) {
+    if (candidates.containsKey(value))
+      return candidates[value]!;
+    final double? lower = candidates.lastKeyBefore(value);
+    final double? upper = candidates.firstKeyAfter(value);
+    if (lower == null)
+      return candidates[upper];
+    if (upper == null)
+      return candidates[lower];
+
+    // On screens with low device-pixel ratios the artifacts from upscaling
+    // images are more visible than on screens with a higher device-pixel
+    // ratios because the physical pixels are larger. Choose the higher
+    // resolution image in that case instead of the nearest one.
+    if (value < _kLowDprLimit || value > (lower + upper) / 2)
+      return candidates[upper];
+    else
+      return candidates[lower];
+  }
+
+  static final RegExp _extractRatioRegExp = RegExp(r'/?(\d+(\.\d*)?)x$');
+
+  double _parseScale(String key) {
+    if (key == assetName) {
+      return _naturalResolution;
+    }
+
+    final Uri assetUri = Uri.parse(key);
+    String directoryPath = '';
+    if (assetUri.pathSegments.length > 1) {
+      directoryPath = assetUri.pathSegments[assetUri.pathSegments.length - 2];
+    }
+
+    final Match? match = _extractRatioRegExp.firstMatch(directoryPath);
+    if (match != null && match.groupCount > 0)
+      return double.parse(match.group(1)!);
+    return _naturalResolution; // i.e. default to 1.0x
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is AssetImage
+        && other.keyName == keyName
+        && other.bundle == bundle;
+  }
+
+  @override
+  int get hashCode => hashValues(keyName, bundle);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'AssetImage')}(bundle: $bundle, name: "$keyName")';
+}
diff --git a/lib/src/painting/image_stream.dart b/lib/src/painting/image_stream.dart
new file mode 100644
index 0000000..d2997a2
--- /dev/null
+++ b/lib/src/painting/image_stream.dart
@@ -0,0 +1,974 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'package:flute/ui.dart' as ui show Image, Codec, FrameInfo;
+import 'package:flute/ui.dart' show hashValues;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+
+/// A [dart:ui.Image] object with its corresponding scale.
+///
+/// ImageInfo objects are used by [ImageStream] objects to represent the
+/// actual data of the image once it has been obtained.
+///
+/// The receiver of an [ImageInfo] object must call [dispose]. To safely share
+/// the object with other clients, use the [clone] method before calling
+/// dispose.
+@immutable
+class ImageInfo {
+  /// Creates an [ImageInfo] object for the given [image] and [scale].
+  ///
+  /// Both the [image] and the [scale] must not be null.
+  ///
+  /// The [debugLabel] may be used to identify the source of this image.
+  const ImageInfo({ required this.image, this.scale = 1.0, this.debugLabel })
+    : assert(image != null),
+      assert(scale != null);
+
+  /// Creates an [ImageInfo] with a cloned [image].
+  ///
+  /// Once all outstanding references to the [image] are disposed, it is no
+  /// longer safe to access properties of it or attempt to draw it. Clones serve
+  /// to create new references to the underlying image data that can safely be
+  /// disposed without knowledge of whether some other reference holder will
+  /// still need access to the underlying image. Once a client disposes of its
+  /// own image reference, it can no longer access the image, but other clients
+  /// will be able to access their own references.
+  ///
+  /// This method must be used in cases where a client holding an [ImageInfo]
+  /// needs to share the image info object with another client and will still
+  /// need to access the underlying image data at some later point, e.g. to
+  /// share it again with another client.
+  ///
+  /// See also:
+  ///
+  ///  * [Image.clone], which describes how and why to clone images.
+  ImageInfo clone() {
+    return ImageInfo(
+      image: image.clone(),
+      scale: scale,
+      debugLabel: debugLabel,
+    );
+  }
+
+  /// Whether this [ImageInfo] is a [clone] of the `other`.
+  ///
+  /// This method is a convenience wrapper for [Image.isCloneOf], and is useful
+  /// for clients that are trying to determine whether new layout or painting
+  /// logic is required when receiving a new image reference.
+  ///
+  /// {@tool snippet}
+  ///
+  /// The following sample shows how to appropriately check whether the
+  /// [ImageInfo] reference refers to new image data or not.
+  ///
+  /// ```dart
+  /// ImageInfo _imageInfo;
+  /// set imageInfo (ImageInfo value) {
+  ///   // If the image reference is exactly the same, do nothing.
+  ///   if (value == _imageInfo) {
+  ///     return;
+  ///   }
+  ///   // If it is a clone of the current reference, we must dispose of it and
+  ///   // can do so immediately. Since the underlying image has not changed,
+  ///   // We don't have any additional work to do here.
+  ///   if (value != null && _imageInfo != null && value.isCloneOf(_imageInfo)) {
+  ///     value.dispose();
+  ///     return;
+  ///   }
+  ///   _imageInfo?.dispose();
+  ///   _imageInfo = value;
+  ///   // Perform work to determine size, or paint the image.
+  /// }
+  /// ```
+  /// {@end-tool}
+  bool isCloneOf(ImageInfo other) {
+    return other.image.isCloneOf(image)
+        && scale == scale
+        && other.debugLabel == debugLabel;
+  }
+
+  /// The raw image pixels.
+  ///
+  /// This is the object to pass to the [Canvas.drawImage],
+  /// [Canvas.drawImageRect], or [Canvas.drawImageNine] methods when painting
+  /// the image.
+  final ui.Image image;
+
+  /// The linear scale factor for drawing this image at its intended size.
+  ///
+  /// The scale factor applies to the width and the height.
+  ///
+  /// For example, if this is 2.0 it means that there are four image pixels for
+  /// every one logical pixel, and the image's actual width and height (as given
+  /// by the [dart:ui.Image.width] and [dart:ui.Image.height] properties) are
+  /// double the height and width that should be used when painting the image
+  /// (e.g. in the arguments given to [Canvas.drawImage]).
+  final double scale;
+
+  /// A string used for debugging purposes to identify the source of this image.
+  final String? debugLabel;
+
+  /// Disposes of this object.
+  ///
+  /// Once this method has been called, the object should not be used anymore,
+  /// and no clones of it or the image it contains can be made.
+  void dispose() {
+    assert((image.debugGetOpenHandleStackTraces()?.length ?? 1) > 0);
+    image.dispose();
+  }
+
+  @override
+  String toString() => '${debugLabel != null ? '$debugLabel ' : ''}$image @ ${debugFormatDouble(scale)}x';
+
+  @override
+  int get hashCode => hashValues(image, scale, debugLabel);
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ImageInfo
+        && other.image == image
+        && other.scale == scale
+        && other.debugLabel == debugLabel;
+  }
+}
+
+/// Interface for receiving notifications about the loading of an image.
+///
+/// This class overrides [operator ==] and [hashCode] to compare the individual
+/// callbacks in the listener, meaning that if you add an instance of this class
+/// as a listener (e.g. via [ImageStream.addListener]), you can instantiate a
+/// _different_ instance of this class when you remove the listener, and the
+/// listener will be properly removed as long as all associated callbacks are
+/// equal.
+///
+/// Used by [ImageStream] and [ImageStreamCompleter].
+@immutable
+class ImageStreamListener {
+  /// Creates a new [ImageStreamListener].
+  ///
+  /// The [onImage] parameter must not be null.
+  const ImageStreamListener(
+    this.onImage, {
+    this.onChunk,
+    this.onError,
+  }) : assert(onImage != null);
+
+  /// Callback for getting notified that an image is available.
+  ///
+  /// This callback may fire multiple times (e.g. if the [ImageStreamCompleter]
+  /// that drives the notifications fires multiple times). An example of such a
+  /// case would be an image with multiple frames within it (such as an animated
+  /// GIF).
+  ///
+  /// For more information on how to interpret the parameters to the callback,
+  /// see the documentation on [ImageListener].
+  ///
+  /// See also:
+  ///
+  ///  * [onError], which will be called instead of [onImage] if an error occurs
+  ///    during loading.
+  final ImageListener onImage;
+
+  /// Callback for getting notified when a chunk of bytes has been received
+  /// during the loading of the image.
+  ///
+  /// This callback may fire many times (e.g. when used with a [NetworkImage],
+  /// where the image bytes are loaded incrementally over the wire) or not at
+  /// all (e.g. when used with a [MemoryImage], where the image bytes are
+  /// already available in memory).
+  ///
+  /// This callback may also continue to fire after the [onImage] callback has
+  /// fired (e.g. for multi-frame images that continue to load after the first
+  /// frame is available).
+  final ImageChunkListener? onChunk;
+
+  /// Callback for getting notified when an error occurs while loading an image.
+  ///
+  /// If an error occurs during loading, [onError] will be called instead of
+  /// [onImage].
+  final ImageErrorListener? onError;
+
+  @override
+  int get hashCode => hashValues(onImage, onChunk, onError);
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ImageStreamListener
+        && other.onImage == onImage
+        && other.onChunk == onChunk
+        && other.onError == onError;
+  }
+}
+
+/// Signature for callbacks reporting that an image is available.
+///
+/// Used in [ImageStreamListener].
+///
+/// The `image` argument contains information about the image to be rendered.
+/// The implementer of [ImageStreamListener.onImage] is expected to call dispose
+/// on the [ui.Image] it receives.
+///
+/// The `synchronousCall` argument is true if the listener is being invoked
+/// during the call to `addListener`. This can be useful if, for example,
+/// [ImageStream.addListener] is invoked during a frame, so that a new rendering
+/// frame is requested if the call was asynchronous (after the current frame)
+/// and no rendering frame is requested if the call was synchronous (within the
+/// same stack frame as the call to [ImageStream.addListener]).
+typedef ImageListener = void Function(ImageInfo image, bool synchronousCall);
+
+/// Signature for listening to [ImageChunkEvent] events.
+///
+/// Used in [ImageStreamListener].
+typedef ImageChunkListener = void Function(ImageChunkEvent event);
+
+/// Signature for reporting errors when resolving images.
+///
+/// Used in [ImageStreamListener], as well as by [ImageCache.putIfAbsent] and
+/// [precacheImage], to report errors.
+typedef ImageErrorListener = void Function(Object exception, StackTrace? stackTrace);
+
+/// An immutable notification of image bytes that have been incrementally loaded.
+///
+/// Chunk events represent progress notifications while an image is being
+/// loaded (e.g. from disk or over the network).
+///
+/// See also:
+///
+///  * [ImageChunkListener], the means by which callers get notified of
+///    these events.
+@immutable
+class ImageChunkEvent with Diagnosticable {
+  /// Creates a new chunk event.
+  const ImageChunkEvent({
+    required this.cumulativeBytesLoaded,
+    required this.expectedTotalBytes,
+  }) : assert(cumulativeBytesLoaded >= 0),
+       assert(expectedTotalBytes == null || expectedTotalBytes >= 0);
+
+  /// The number of bytes that have been received across the wire thus far.
+  final int cumulativeBytesLoaded;
+
+  /// The expected number of bytes that need to be received to finish loading
+  /// the image.
+  ///
+  /// This value is not necessarily equal to the expected _size_ of the image
+  /// in bytes, as the bytes required to load the image may be compressed.
+  ///
+  /// This value will be null if the number is not known in advance.
+  ///
+  /// When this value is null, the chunk event may still be useful as an
+  /// indication that data is loading (and how much), but it cannot represent a
+  /// loading completion percentage.
+  final int? expectedTotalBytes;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(IntProperty('cumulativeBytesLoaded', cumulativeBytesLoaded));
+    properties.add(IntProperty('expectedTotalBytes', expectedTotalBytes));
+  }
+}
+
+/// A handle to an image resource.
+///
+/// ImageStream represents a handle to a [dart:ui.Image] object and its scale
+/// (together represented by an [ImageInfo] object). The underlying image object
+/// might change over time, either because the image is animating or because the
+/// underlying image resource was mutated.
+///
+/// ImageStream objects can also represent an image that hasn't finished
+/// loading.
+///
+/// ImageStream objects are backed by [ImageStreamCompleter] objects.
+///
+/// The [ImageCache] will consider an image to be live until the listener count
+/// drops to zero after adding at least one listener. The
+/// [ImageStreamCompleter.addOnLastListenerRemovedCallback] method is used for
+/// tracking this information.
+///
+/// See also:
+///
+///  * [ImageProvider], which has an example that includes the use of an
+///    [ImageStream] in a [Widget].
+class ImageStream with Diagnosticable {
+  /// Create an initially unbound image stream.
+  ///
+  /// Once an [ImageStreamCompleter] is available, call [setCompleter].
+  ImageStream();
+
+  /// The completer that has been assigned to this image stream.
+  ///
+  /// Generally there is no need to deal with the completer directly.
+  ImageStreamCompleter? get completer => _completer;
+  ImageStreamCompleter? _completer;
+
+  List<ImageStreamListener>? _listeners;
+
+  /// Assigns a particular [ImageStreamCompleter] to this [ImageStream].
+  ///
+  /// This is usually done automatically by the [ImageProvider] that created the
+  /// [ImageStream].
+  ///
+  /// This method can only be called once per stream. To have an [ImageStream]
+  /// represent multiple images over time, assign it a completer that
+  /// completes several images in succession.
+  void setCompleter(ImageStreamCompleter value) {
+    assert(_completer == null);
+    _completer = value;
+    if (_listeners != null) {
+      final List<ImageStreamListener> initialListeners = _listeners!;
+      _listeners = null;
+      initialListeners.forEach(_completer!.addListener);
+    }
+  }
+
+  /// Adds a listener callback that is called whenever a new concrete [ImageInfo]
+  /// object is available. If a concrete image is already available, this object
+  /// will call the listener synchronously.
+  ///
+  /// If the assigned [completer] completes multiple images over its lifetime,
+  /// this listener will fire multiple times.
+  ///
+  /// {@template flutter.painting.imageStream.addListener}
+  /// The listener will be passed a flag indicating whether a synchronous call
+  /// occurred. If the listener is added within a render object paint function,
+  /// then use this flag to avoid calling [RenderObject.markNeedsPaint] during
+  /// a paint.
+  ///
+  /// If a duplicate `listener` is registered N times, then it will be called N
+  /// times when the image stream completes (whether because a new image is
+  /// available or because an error occurs). Likewise, to remove all instances
+  /// of the listener, [removeListener] would need to called N times as well.
+  ///
+  /// When a `listener` receives an [ImageInfo] object, the `listener` is
+  /// responsible for disposing of the [ImageInfo.image].
+  /// {@endtemplate}
+  void addListener(ImageStreamListener listener) {
+    if (_completer != null)
+      return _completer!.addListener(listener);
+    _listeners ??= <ImageStreamListener>[];
+    _listeners!.add(listener);
+  }
+
+  /// Stops listening for events from this stream's [ImageStreamCompleter].
+  ///
+  /// If [listener] has been added multiple times, this removes the _first_
+  /// instance of the listener.
+  void removeListener(ImageStreamListener listener) {
+    if (_completer != null)
+      return _completer!.removeListener(listener);
+    assert(_listeners != null);
+    for (int i = 0; i < _listeners!.length; i += 1) {
+      if (_listeners![i] == listener) {
+        _listeners!.removeAt(i);
+        break;
+      }
+    }
+  }
+
+  /// Returns an object which can be used with `==` to determine if this
+  /// [ImageStream] shares the same listeners list as another [ImageStream].
+  ///
+  /// This can be used to avoid un-registering and re-registering listeners
+  /// after calling [ImageProvider.resolve] on a new, but possibly equivalent,
+  /// [ImageProvider].
+  ///
+  /// The key may change once in the lifetime of the object. When it changes, it
+  /// will go from being different than other [ImageStream]'s keys to
+  /// potentially being the same as others'. No notification is sent when this
+  /// happens.
+  Object get key => _completer ?? this;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ObjectFlagProperty<ImageStreamCompleter>(
+      'completer',
+      _completer,
+      ifPresent: _completer?.toStringShort(),
+      ifNull: 'unresolved',
+    ));
+    properties.add(ObjectFlagProperty<List<ImageStreamListener>>(
+      'listeners',
+      _listeners,
+      ifPresent: '${_listeners?.length} listener${_listeners?.length == 1 ? "" : "s" }',
+      ifNull: 'no listeners',
+      level: _completer != null ? DiagnosticLevel.hidden : DiagnosticLevel.info,
+    ));
+    _completer?.debugFillProperties(properties);
+  }
+}
+
+/// An opaque handle that keeps an [ImageStreamCompleter] alive even if it has
+/// lost its last listener.
+///
+/// To create a handle, use [ImageStreamCompleter.keepAlive].
+///
+/// Such handles are useful when an image cache needs to keep a completer alive
+/// but does not actually have a listener subscribed, or when a widget that
+/// displays an image needs to temporarily unsubscribe from the completer but
+/// may re-subscribe in the future, for example when the [TickerMode] changes.
+class ImageStreamCompleterHandle {
+  ImageStreamCompleterHandle._(ImageStreamCompleter this._completer) {
+    _completer!._keepAliveHandles += 1;
+  }
+
+  ImageStreamCompleter? _completer;
+
+  /// Call this method to signal the [ImageStreamCompleter] that it can now be
+  /// disposed when its last listener drops.
+  ///
+  /// This method must only be called once per object.
+  void dispose() {
+    assert(_completer != null);
+    assert(_completer!._keepAliveHandles > 0);
+    assert(!_completer!._disposed);
+
+    _completer!._keepAliveHandles -= 1;
+    _completer!._maybeDispose();
+    _completer = null;
+  }
+}
+
+/// Base class for those that manage the loading of [dart:ui.Image] objects for
+/// [ImageStream]s.
+///
+/// [ImageStreamListener] objects are rarely constructed directly. Generally, an
+/// [ImageProvider] subclass will return an [ImageStream] and automatically
+/// configure it with the right [ImageStreamCompleter] when possible.
+abstract class ImageStreamCompleter with Diagnosticable {
+  final List<ImageStreamListener> _listeners = <ImageStreamListener>[];
+  ImageInfo? _currentImage;
+  FlutterErrorDetails? _currentError;
+
+  /// A string identifying the source of the underlying image.
+  String? debugLabel;
+
+  /// Whether any listeners are currently registered.
+  ///
+  /// Clients should not depend on this value for their behavior, because having
+  /// one listener's logic change when another listener happens to start or stop
+  /// listening will lead to extremely hard-to-track bugs. Subclasses might use
+  /// this information to determine whether to do any work when there are no
+  /// listeners, however; for example, [MultiFrameImageStreamCompleter] uses it
+  /// to determine when to iterate through frames of an animated image.
+  ///
+  /// Typically this is used by overriding [addListener], checking if
+  /// [hasListeners] is false before calling `super.addListener()`, and if so,
+  /// starting whatever work is needed to determine when to notify listeners;
+  /// and similarly, by overriding [removeListener], checking if [hasListeners]
+  /// is false after calling `super.removeListener()`, and if so, stopping that
+  /// same work.
+  @protected
+  @visibleForTesting
+  bool get hasListeners => _listeners.isNotEmpty;
+
+  /// We must avoid disposing a completer if it has never had a listener, even
+  /// if all [keepAlive] handles get disposed.
+  bool _hadAtLeastOneListener = false;
+
+  /// Adds a listener callback that is called whenever a new concrete [ImageInfo]
+  /// object is available or an error is reported. If a concrete image is
+  /// already available, or if an error has been already reported, this object
+  /// will notify the listener synchronously.
+  ///
+  /// If the [ImageStreamCompleter] completes multiple images over its lifetime,
+  /// this listener's [ImageStreamListener.onImage] will fire multiple times.
+  ///
+  /// {@macro flutter.painting.imageStream.addListener}
+  void addListener(ImageStreamListener listener) {
+    _checkDisposed();
+    _hadAtLeastOneListener = true;
+    _listeners.add(listener);
+    if (_currentImage != null) {
+      try {
+        listener.onImage(_currentImage!.clone(), true);
+      } catch (exception, stack) {
+        reportError(
+          context: ErrorDescription('by a synchronously-called image listener'),
+          exception: exception,
+          stack: stack,
+        );
+      }
+    }
+    if (_currentError != null && listener.onError != null) {
+      try {
+        listener.onError!(_currentError!.exception, _currentError!.stack);
+      } catch (exception, stack) {
+        FlutterError.reportError(
+          FlutterErrorDetails(
+            exception: exception,
+            library: 'image resource service',
+            context: ErrorDescription('by a synchronously-called image error listener'),
+            stack: stack,
+          ),
+        );
+      }
+    }
+  }
+
+  int _keepAliveHandles = 0;
+  /// Creates an [ImageStreamCompleterHandle] that will prevent this stream from
+  /// being disposed at least until the handle is disposed.
+  ///
+  /// Such handles are useful when an image cache needs to keep a completer
+  /// alive but does not itself have a listener subscribed, or when a widget
+  /// that displays an image needs to temporarily unsubscribe from the completer
+  /// but may re-subscribe in the future, for example when the [TickerMode]
+  /// changes.
+  ImageStreamCompleterHandle keepAlive() {
+    _checkDisposed();
+    return ImageStreamCompleterHandle._(this);
+  }
+
+  /// Stops the specified [listener] from receiving image stream events.
+  ///
+  /// If [listener] has been added multiple times, this removes the _first_
+  /// instance of the listener.
+  ///
+  /// Once all listeners have been removed and all [keepAlive] handles have been
+  /// disposed, this image stream is no longer usable.
+  void removeListener(ImageStreamListener listener) {
+    _checkDisposed();
+    for (int i = 0; i < _listeners.length; i += 1) {
+      if (_listeners[i] == listener) {
+        _listeners.removeAt(i);
+        break;
+      }
+    }
+    if (_listeners.isEmpty) {
+      final List<VoidCallback> callbacks = _onLastListenerRemovedCallbacks.toList();
+      for (final VoidCallback callback in callbacks) {
+        callback();
+      }
+      _onLastListenerRemovedCallbacks.clear();
+      _maybeDispose();
+    }
+  }
+
+  bool _disposed = false;
+  void _maybeDispose() {
+    if (!_hadAtLeastOneListener || _disposed || _listeners.isNotEmpty || _keepAliveHandles != 0) {
+      return;
+    }
+
+    _currentImage?.dispose();
+    _currentImage = null;
+    _disposed = true;
+  }
+
+  void _checkDisposed() {
+    if (_disposed) {
+      throw StateError(
+        'Stream has been disposed.\n'
+        'An ImageStream is considered disposed once at least one listener has '
+        'been added and subsequently all listeners have been removed and no '
+        'handles are outstanding from the keepAlive method.\n'
+        'To resolve this error, maintain at least one listener on the stream, '
+        'or create an ImageStreamCompleterHandle from the keepAlive '
+        'method, or create a new stream for the image.',
+      );
+    }
+  }
+
+  final List<VoidCallback> _onLastListenerRemovedCallbacks = <VoidCallback>[];
+
+  /// Adds a callback to call when [removeListener] results in an empty
+  /// list of listeners and there are no [keepAlive] handles outstanding.
+  ///
+  /// This callback will never fire if [removeListener] is never called.
+  void addOnLastListenerRemovedCallback(VoidCallback callback) {
+    assert(callback != null);
+    _checkDisposed();
+    _onLastListenerRemovedCallbacks.add(callback);
+  }
+
+  /// Removes a callback previously supplied to
+  /// [addOnLastListenerRemovedCallback].
+  void removeOnLastListenerRemovedCallback(VoidCallback callback) {
+    assert(callback != null);
+    _checkDisposed();
+    _onLastListenerRemovedCallbacks.remove(callback);
+  }
+
+  /// Calls all the registered listeners to notify them of a new image.
+  @protected
+  void setImage(ImageInfo image) {
+    _checkDisposed();
+    _currentImage?.dispose();
+    _currentImage = image;
+
+    if (_listeners.isEmpty)
+      return;
+    // Make a copy to allow for concurrent modification.
+    final List<ImageStreamListener> localListeners =
+        List<ImageStreamListener>.from(_listeners);
+    for (final ImageStreamListener listener in localListeners) {
+      try {
+        listener.onImage(image.clone(), false);
+      } catch (exception, stack) {
+        reportError(
+          context: ErrorDescription('by an image listener'),
+          exception: exception,
+          stack: stack,
+        );
+      }
+    }
+  }
+
+  /// Calls all the registered error listeners to notify them of an error that
+  /// occurred while resolving the image.
+  ///
+  /// If no error listeners (listeners with an [ImageStreamListener.onError]
+  /// specified) are attached, a [FlutterError] will be reported instead.
+  ///
+  /// The `context` should be a string describing where the error was caught, in
+  /// a form that will make sense in English when following the word "thrown",
+  /// as in "thrown while obtaining the image from the network" (for the context
+  /// "while obtaining the image from the network").
+  ///
+  /// The `exception` is the error being reported; the `stack` is the
+  /// [StackTrace] associated with the exception.
+  ///
+  /// The `informationCollector` is a callback (of type [InformationCollector])
+  /// that is called when the exception is used by [FlutterError.reportError].
+  /// It is used to obtain further details to include in the logs, which may be
+  /// expensive to collect, and thus should only be collected if the error is to
+  /// be logged in the first place.
+  ///
+  /// The `silent` argument causes the exception to not be reported to the logs
+  /// in release builds, if passed to [FlutterError.reportError]. (It is still
+  /// sent to error handlers.) It should be set to true if the error is one that
+  /// is expected to be encountered in release builds, for example network
+  /// errors. That way, logs on end-user devices will not have spurious
+  /// messages, but errors during development will still be reported.
+  ///
+  /// See [FlutterErrorDetails] for further details on these values.
+  @protected
+  void reportError({
+    DiagnosticsNode? context,
+    required Object exception,
+    StackTrace? stack,
+    InformationCollector? informationCollector,
+    bool silent = false,
+  }) {
+    _currentError = FlutterErrorDetails(
+      exception: exception,
+      stack: stack,
+      library: 'image resource service',
+      context: context,
+      informationCollector: informationCollector,
+      silent: silent,
+    );
+
+    // Make a copy to allow for concurrent modification.
+    final List<ImageErrorListener> localErrorListeners = _listeners
+        .map<ImageErrorListener?>((ImageStreamListener listener) => listener.onError)
+        .whereType<ImageErrorListener>()
+        .toList();
+
+    if (localErrorListeners.isEmpty) {
+      FlutterError.reportError(_currentError!);
+    } else {
+      for (final ImageErrorListener errorListener in localErrorListeners) {
+        try {
+          errorListener(exception, stack);
+        } catch (exception, stack) {
+          FlutterError.reportError(
+            FlutterErrorDetails(
+              context: ErrorDescription('when reporting an error to an image listener'),
+              library: 'image resource service',
+              exception: exception,
+              stack: stack,
+            ),
+          );
+        }
+      }
+    }
+  }
+
+  /// Calls all the registered [ImageChunkListener]s (listeners with an
+  /// [ImageStreamListener.onChunk] specified) to notify them of a new
+  /// [ImageChunkEvent].
+  @protected
+  void reportImageChunkEvent(ImageChunkEvent event){
+    _checkDisposed();
+    if (hasListeners) {
+      // Make a copy to allow for concurrent modification.
+      final List<ImageChunkListener> localListeners = _listeners
+          .map<ImageChunkListener?>((ImageStreamListener listener) => listener.onChunk)
+          .whereType<ImageChunkListener>()
+          .toList();
+      for (final ImageChunkListener listener in localListeners) {
+        listener(event);
+      }
+    }
+  }
+
+  /// Accumulates a list of strings describing the object's state. Subclasses
+  /// should override this to have their information included in [toString].
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(DiagnosticsProperty<ImageInfo>('current', _currentImage, ifNull: 'unresolved', showName: false));
+    description.add(ObjectFlagProperty<List<ImageStreamListener>>(
+      'listeners',
+      _listeners,
+      ifPresent: '${_listeners.length} listener${_listeners.length == 1 ? "" : "s" }',
+    ));
+    description.add(FlagProperty('disposed', value: _disposed, ifTrue: '<disposed>'));
+  }
+}
+
+/// Manages the loading of [dart:ui.Image] objects for static [ImageStream]s (those
+/// with only one frame).
+class OneFrameImageStreamCompleter extends ImageStreamCompleter {
+  /// Creates a manager for one-frame [ImageStream]s.
+  ///
+  /// The image resource awaits the given [Future]. When the future resolves,
+  /// it notifies the [ImageListener]s that have been registered with
+  /// [addListener].
+  ///
+  /// The [InformationCollector], if provided, is invoked if the given [Future]
+  /// resolves with an error, and can be used to supplement the reported error
+  /// message (for example, giving the image's URL).
+  ///
+  /// Errors are reported using [FlutterError.reportError] with the `silent`
+  /// argument on [FlutterErrorDetails] set to true, meaning that by default the
+  /// message is only dumped to the console in debug mode (see [new
+  /// FlutterErrorDetails]).
+  OneFrameImageStreamCompleter(Future<ImageInfo> image, { InformationCollector? informationCollector })
+      : assert(image != null) {
+    image.then<void>(setImage, onError: (Object error, StackTrace stack) {
+      reportError(
+        context: ErrorDescription('resolving a single-frame image stream'),
+        exception: error,
+        stack: stack,
+        informationCollector: informationCollector,
+        silent: true,
+      );
+    });
+  }
+}
+
+/// Manages the decoding and scheduling of image frames.
+///
+/// New frames will only be emitted while there are registered listeners to the
+/// stream (registered with [addListener]).
+///
+/// This class deals with 2 types of frames:
+///
+///  * image frames - image frames of an animated image.
+///  * app frames - frames that the flutter engine is drawing to the screen to
+///    show the app GUI.
+///
+/// For single frame images the stream will only complete once.
+///
+/// For animated images, this class eagerly decodes the next image frame,
+/// and notifies the listeners that a new frame is ready on the first app frame
+/// that is scheduled after the image frame duration has passed.
+///
+/// Scheduling new timers only from scheduled app frames, makes sure we pause
+/// the animation when the app is not visible (as new app frames will not be
+/// scheduled).
+///
+/// See the following timeline example:
+///
+///     | Time | Event                                      | Comment                   |
+///     |------|--------------------------------------------|---------------------------|
+///     | t1   | App frame scheduled (image frame A posted) |                           |
+///     | t2   | App frame scheduled                        |                           |
+///     | t3   | App frame scheduled                        |                           |
+///     | t4   | Image frame B decoded                      |                           |
+///     | t5   | App frame scheduled                        | t5 - t1 < frameB_duration |
+///     | t6   | App frame scheduled (image frame B posted) | t6 - t1 > frameB_duration |
+///
+class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
+  /// Creates a image stream completer.
+  ///
+  /// Immediately starts decoding the first image frame when the codec is ready.
+  ///
+  /// The `codec` parameter is a future for an initialized [ui.Codec] that will
+  /// be used to decode the image.
+  ///
+  /// The `scale` parameter is the linear scale factor for drawing this frames
+  /// of this image at their intended size.
+  ///
+  /// The `tag` parameter is passed on to created [ImageInfo] objects to
+  /// help identify the source of the image.
+  ///
+  /// The `chunkEvents` parameter is an optional stream of notifications about
+  /// the loading progress of the image. If this stream is provided, the events
+  /// produced by the stream will be delivered to registered [ImageChunkListener]s
+  /// (see [addListener]).
+  MultiFrameImageStreamCompleter({
+    required Future<ui.Codec> codec,
+    required double scale,
+    String? debugLabel,
+    Stream<ImageChunkEvent>? chunkEvents,
+    InformationCollector? informationCollector,
+  }) : assert(codec != null),
+       _informationCollector = informationCollector,
+       _scale = scale {
+    this.debugLabel = debugLabel;
+    codec.then<void>(_handleCodecReady, onError: (Object error, StackTrace stack) {
+      reportError(
+        context: ErrorDescription('resolving an image codec'),
+        exception: error,
+        stack: stack,
+        informationCollector: informationCollector,
+        silent: true,
+      );
+    });
+    if (chunkEvents != null) {
+      chunkEvents.listen(reportImageChunkEvent,
+        onError: (Object error, StackTrace stack) {
+          reportError(
+            context: ErrorDescription('loading an image'),
+            exception: error,
+            stack: stack,
+            informationCollector: informationCollector,
+            silent: true,
+          );
+        },
+      );
+    }
+  }
+
+  ui.Codec? _codec;
+  final double _scale;
+  final InformationCollector? _informationCollector;
+  ui.FrameInfo? _nextFrame;
+  // When the current was first shown.
+  late Duration _shownTimestamp;
+  // The requested duration for the current frame;
+  Duration? _frameDuration;
+  // How many frames have been emitted so far.
+  int _framesEmitted = 0;
+  Timer? _timer;
+
+  // Used to guard against registering multiple _handleAppFrame callbacks for the same frame.
+  bool _frameCallbackScheduled = false;
+
+  void _handleCodecReady(ui.Codec codec) {
+    _codec = codec;
+    assert(_codec != null);
+
+    if (hasListeners) {
+      _decodeNextFrameAndSchedule();
+    }
+  }
+
+  void _handleAppFrame(Duration timestamp) {
+    _frameCallbackScheduled = false;
+    if (!hasListeners)
+      return;
+    assert(_nextFrame != null);
+    if (_isFirstFrame() || _hasFrameDurationPassed(timestamp)) {
+      _emitFrame(ImageInfo(
+        image: _nextFrame!.image.clone(),
+        scale: _scale,
+        debugLabel: debugLabel,
+      ));
+      _shownTimestamp = timestamp;
+      _frameDuration = _nextFrame!.duration;
+      _nextFrame!.image.dispose();
+      _nextFrame = null;
+      final int completedCycles = _framesEmitted ~/ _codec!.frameCount;
+      if (_codec!.repetitionCount == -1 || completedCycles <= _codec!.repetitionCount) {
+        _decodeNextFrameAndSchedule();
+      }
+      return;
+    }
+    final Duration delay = _frameDuration! - (timestamp - _shownTimestamp);
+    _timer = Timer(delay * timeDilation, () {
+      _scheduleAppFrame();
+    });
+  }
+
+  bool _isFirstFrame() {
+    return _frameDuration == null;
+  }
+
+  bool _hasFrameDurationPassed(Duration timestamp) {
+    return timestamp - _shownTimestamp >= _frameDuration!;
+  }
+
+  Future<void> _decodeNextFrameAndSchedule() async {
+    // This will be null if we gave it away. If not, it's still ours and it
+    // must be disposed of.
+    _nextFrame?.image.dispose();
+    _nextFrame = null;
+    try {
+      _nextFrame = await _codec!.getNextFrame();
+    } catch (exception, stack) {
+      reportError(
+        context: ErrorDescription('resolving an image frame'),
+        exception: exception,
+        stack: stack,
+        informationCollector: _informationCollector,
+        silent: true,
+      );
+      return;
+    }
+    if (_codec!.frameCount == 1) {
+      // ImageStreamCompleter listeners removed while waiting for next frame to
+      // be decoded.
+      // There's no reason to emit the frame without active listeners.
+      if (!hasListeners) {
+        return;
+      }
+      // This is not an animated image, just return it and don't schedule more
+      // frames.
+      _emitFrame(ImageInfo(
+        image: _nextFrame!.image.clone(),
+        scale: _scale,
+        debugLabel: debugLabel,
+      ));
+      _nextFrame!.image.dispose();
+      _nextFrame = null;
+      return;
+    }
+    _scheduleAppFrame();
+  }
+
+  void _scheduleAppFrame() {
+    if (_frameCallbackScheduled) {
+      return;
+    }
+    _frameCallbackScheduled = true;
+    SchedulerBinding.instance!.scheduleFrameCallback(_handleAppFrame);
+  }
+
+  void _emitFrame(ImageInfo imageInfo) {
+    setImage(imageInfo);
+    _framesEmitted += 1;
+  }
+
+  @override
+  void addListener(ImageStreamListener listener) {
+    if (!hasListeners && _codec != null)
+      _decodeNextFrameAndSchedule();
+    super.addListener(listener);
+  }
+
+  @override
+  void removeListener(ImageStreamListener listener) {
+    super.removeListener(listener);
+    if (!hasListeners) {
+      _timer?.cancel();
+      _timer = null;
+    }
+  }
+}
diff --git a/lib/src/painting/inline_span.dart b/lib/src/painting/inline_span.dart
new file mode 100644
index 0000000..713749f
--- /dev/null
+++ b/lib/src/painting/inline_span.dart
@@ -0,0 +1,380 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' as ui show ParagraphBuilder;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+
+import 'basic_types.dart';
+import 'text_painter.dart';
+import 'text_span.dart';
+import 'text_style.dart';
+
+/// Mutable wrapper of an integer that can be passed by reference to track a
+/// value across a recursive stack.
+class Accumulator {
+  /// [Accumulator] may be initialized with a specified value, otherwise, it will
+  /// initialize to zero.
+  Accumulator([this._value = 0]);
+
+  /// The integer stored in this [Accumulator].
+  int get value => _value;
+  int _value;
+
+  /// Increases the [value] by the `addend`.
+  void increment(int addend) {
+    assert(addend >= 0);
+    _value += addend;
+  }
+}
+/// Called on each span as [InlineSpan.visitChildren] walks the [InlineSpan] tree.
+///
+/// Returns true when the walk should continue, and false to stop visiting further
+/// [InlineSpan]s.
+typedef InlineSpanVisitor = bool Function(InlineSpan span);
+
+/// The textual and semantic label information for an [InlineSpan].
+///
+/// For [PlaceholderSpan]s, [InlineSpanSemanticsInformation.placeholder] is used by default.
+///
+/// See also:
+///
+///  * [InlineSpan.getSemanticsInformation]
+@immutable
+class InlineSpanSemanticsInformation {
+  /// Constructs an object that holds the text and semantics label values of an
+  /// [InlineSpan].
+  ///
+  /// The text parameter must not be null.
+  ///
+  /// Use [InlineSpanSemanticsInformation.placeholder] instead of directly setting
+  /// [isPlaceholder].
+  const InlineSpanSemanticsInformation(
+    this.text, {
+    this.isPlaceholder = false,
+    this.semanticsLabel,
+    this.recognizer,
+  }) : assert(text != null),
+       assert(isPlaceholder != null),
+       assert(isPlaceholder == false || (text == '\uFFFC' && semanticsLabel == null && recognizer == null)),
+       requiresOwnNode = isPlaceholder || recognizer != null;
+
+  /// The text info for a [PlaceholderSpan].
+  static const InlineSpanSemanticsInformation placeholder = InlineSpanSemanticsInformation('\uFFFC', isPlaceholder: true);
+
+  /// The text value, if any.  For [PlaceholderSpan]s, this will be the unicode
+  /// placeholder value.
+  final String text;
+
+  /// The semanticsLabel, if any.
+  final String? semanticsLabel;
+
+  /// The gesture recognizer, if any, for this span.
+  final GestureRecognizer? recognizer;
+
+  /// Whether this is for a placeholder span.
+  final bool isPlaceholder;
+
+  /// True if this configuration should get its own semantics node.
+  ///
+  /// This will be the case of the [recognizer] is not null, of if
+  /// [isPlaceholder] is true.
+  final bool requiresOwnNode;
+
+  @override
+  bool operator ==(Object other) {
+    return other is InlineSpanSemanticsInformation
+        && other.text == text
+        && other.semanticsLabel == semanticsLabel
+        && other.recognizer == recognizer
+        && other.isPlaceholder == isPlaceholder;
+  }
+
+  @override
+  int get hashCode => hashValues(text, semanticsLabel, recognizer, isPlaceholder);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'InlineSpanSemanticsInformation')}{text: $text, semanticsLabel: $semanticsLabel, recognizer: $recognizer}';
+}
+
+/// An immutable span of inline content which forms part of a paragraph.
+///
+///  * The subclass [TextSpan] specifies text and may contain child [InlineSpan]s.
+///  * The subclass [PlaceholderSpan] represents a placeholder that may be
+///    filled with non-text content. [PlaceholderSpan] itself defines a
+///    [ui.PlaceholderAlignment] and a [TextBaseline]. To be useful,
+///    [PlaceholderSpan] must be extended to define content. An instance of
+///    this is the [WidgetSpan] class in the widgets library.
+///  * The subclass [WidgetSpan] specifies embedded inline widgets.
+///
+/// {@tool snippet}
+///
+/// This example shows a tree of [InlineSpan]s that make a query asking for a
+/// name with a [TextField] embedded inline.
+///
+/// ```dart
+/// Text.rich(
+///   TextSpan(
+///     text: 'My name is ',
+///     style: TextStyle(color: Colors.black),
+///     children: <InlineSpan>[
+///       WidgetSpan(
+///         alignment: PlaceholderAlignment.baseline,
+///         baseline: TextBaseline.alphabetic,
+///         child: ConstrainedBox(
+///           constraints: BoxConstraints(maxWidth: 100),
+///           child: TextField(),
+///         )
+///       ),
+///       TextSpan(
+///         text: '.',
+///       ),
+///     ],
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Text], a widget for showing uniformly-styled text.
+///  * [RichText], a widget for finer control of text rendering.
+///  * [TextPainter], a class for painting [InlineSpan] objects on a [Canvas].
+@immutable
+abstract class InlineSpan extends DiagnosticableTree {
+  /// Creates an [InlineSpan] with the given values.
+  const InlineSpan({
+    this.style,
+  });
+
+  /// The [TextStyle] to apply to this span.
+  ///
+  /// The [style] is also applied to any child spans when this is an instance
+  /// of [TextSpan].
+  final TextStyle? style;
+
+  // TODO(garyq): Remove the deprecated visitTextSpan, text, and children.
+  /// Returns the text associated with this span if this is an instance of [TextSpan],
+  /// otherwise returns null.
+  @Deprecated(
+    'InlineSpan does not innately have text. Use TextSpan.text instead. '
+    'This feature was deprecated after v1.7.3.'
+  )
+  String? get text => null;
+
+  // TODO(garyq): Remove the deprecated visitTextSpan, text, and children.
+  /// Returns the [InlineSpan] children list associated with this span if this is an
+  /// instance of [TextSpan], otherwise returns null.
+  @Deprecated(
+    'InlineSpan does not innately have children. Use TextSpan.children instead. '
+    'This feature was deprecated after v1.7.3.'
+  )
+  List<InlineSpan>? get children => null;
+
+  /// Returns the [GestureRecognizer] associated with this span if this is an
+  /// instance of [TextSpan], otherwise returns null.
+  @Deprecated(
+    'InlineSpan does not innately have a recognizer. Use TextSpan.recognizer instead. '
+    'This feature was deprecated after v1.7.3.'
+  )
+  GestureRecognizer? get recognizer => null;
+
+  /// Apply the properties of this object to the given [ParagraphBuilder], from
+  /// which a [Paragraph] can be obtained.
+  ///
+  /// The `textScaleFactor` parameter specifies a scale that the text and
+  /// placeholders will be scaled by. The scaling is performed before layout,
+  /// so the text will be laid out with the scaled glyphs and placeholders.
+  ///
+  /// The `dimensions` parameter specifies the sizes of the placeholders.
+  /// Each [PlaceholderSpan] must be paired with a [PlaceholderDimensions]
+  /// in the same order as defined in the [InlineSpan] tree.
+  ///
+  /// [Paragraph] objects can be drawn on [Canvas] objects.
+  void build(ui.ParagraphBuilder builder, { double textScaleFactor = 1.0, List<PlaceholderDimensions>? dimensions });
+
+  // TODO(garyq): Remove the deprecated visitTextSpan, text, and children.
+  /// Walks this [TextSpan] and any descendants in pre-order and calls `visitor`
+  /// for each span that has content.
+  ///
+  /// When `visitor` returns true, the walk will continue. When `visitor` returns
+  /// false, then the walk will end.
+  @Deprecated(
+    'Use visitChildren instead. '
+    'This feature was deprecated after v1.7.3.'
+  )
+  bool visitTextSpan(bool visitor(TextSpan span));
+
+  /// Walks this [InlineSpan] and any descendants in pre-order and calls `visitor`
+  /// for each span that has content.
+  ///
+  /// When `visitor` returns true, the walk will continue. When `visitor` returns
+  /// false, then the walk will end.
+  bool visitChildren(InlineSpanVisitor visitor);
+
+  /// Returns the [InlineSpan] that contains the given position in the text.
+  InlineSpan? getSpanForPosition(TextPosition position) {
+    assert(debugAssertIsValid());
+    final Accumulator offset = Accumulator();
+    InlineSpan? result;
+    visitChildren((InlineSpan span) {
+      result = span.getSpanForPositionVisitor(position, offset);
+      return result == null;
+    });
+    return result;
+  }
+
+  /// Performs the check at each [InlineSpan] for if the `position` falls within the range
+  /// of the span and returns the span if it does.
+  ///
+  /// The `offset` parameter tracks the current index offset in the text buffer formed
+  /// if the contents of the [InlineSpan] tree were concatenated together starting
+  /// from the root [InlineSpan].
+  ///
+  /// This method should not be directly called. Use [getSpanForPosition] instead.
+  @protected
+  InlineSpan? getSpanForPositionVisitor(TextPosition position, Accumulator offset);
+
+  /// Flattens the [InlineSpan] tree into a single string.
+  ///
+  /// Styles are not honored in this process. If `includeSemanticsLabels` is
+  /// true, then the text returned will include the [TextSpan.semanticsLabel]s
+  /// instead of the text contents for [TextSpan]s.
+  ///
+  /// When `includePlaceholders` is true, [PlaceholderSpan]s in the tree will be
+  /// represented as a 0xFFFC 'object replacement character'.
+  String toPlainText({bool includeSemanticsLabels = true, bool includePlaceholders = true}) {
+    final StringBuffer buffer = StringBuffer();
+    computeToPlainText(buffer, includeSemanticsLabels: includeSemanticsLabels, includePlaceholders: includePlaceholders);
+    return buffer.toString();
+  }
+
+  /// Flattens the [InlineSpan] tree to a list of
+  /// [InlineSpanSemanticsInformation] objects.
+  ///
+  /// [PlaceholderSpan]s in the tree will be represented with a
+  /// [InlineSpanSemanticsInformation.placeholder] value.
+  List<InlineSpanSemanticsInformation> getSemanticsInformation() {
+    final List<InlineSpanSemanticsInformation> collector = <InlineSpanSemanticsInformation>[];
+    computeSemanticsInformation(collector);
+    return collector;
+  }
+
+  /// Walks the [InlineSpan] tree and accumulates a list of
+  /// [InlineSpanSemanticsInformation] objects.
+  ///
+  /// This method should not be directly called.  Use
+  /// [getSemanticsInformation] instead.
+  ///
+  /// [PlaceholderSpan]s in the tree will be represented with a
+  /// [InlineSpanSemanticsInformation.placeholder] value.
+  @protected
+  void computeSemanticsInformation(List<InlineSpanSemanticsInformation> collector);
+
+  /// Walks the [InlineSpan] tree and writes the plain text representation to `buffer`.
+  ///
+  /// This method should not be directly called. Use [toPlainText] instead.
+  ///
+  /// Styles are not honored in this process. If `includeSemanticsLabels` is
+  /// true, then the text returned will include the [TextSpan.semanticsLabel]s
+  /// instead of the text contents for [TextSpan]s.
+  ///
+  /// When `includePlaceholders` is true, [PlaceholderSpan]s in the tree will be
+  /// represented as a 0xFFFC 'object replacement character'.
+  ///
+  /// The plain-text representation of this [InlineSpan] is written into the `buffer`.
+  /// This method will then recursively call [computeToPlainText] on its children
+  /// [InlineSpan]s if available.
+  @protected
+  void computeToPlainText(StringBuffer buffer, {bool includeSemanticsLabels = true, bool includePlaceholders = true});
+
+  /// Returns the UTF-16 code unit at the given `index` in the flattened string.
+  ///
+  /// This only accounts for the [TextSpan.text] values and ignores [PlaceholderSpan]s.
+  ///
+  /// Returns null if the `index` is out of bounds.
+  int? codeUnitAt(int index) {
+    if (index < 0)
+      return null;
+    final Accumulator offset = Accumulator();
+    int? result;
+    visitChildren((InlineSpan span) {
+      result = span.codeUnitAtVisitor(index, offset);
+      return result == null;
+    });
+    return result;
+  }
+
+  /// Performs the check at each [InlineSpan] for if the `index` falls within the range
+  /// of the span and returns the corresponding code unit. Returns null otherwise.
+  ///
+  /// The `offset` parameter tracks the current index offset in the text buffer formed
+  /// if the contents of the [InlineSpan] tree were concatenated together starting
+  /// from the root [InlineSpan].
+  ///
+  /// This method should not be directly called. Use [codeUnitAt] instead.
+  @protected
+  int? codeUnitAtVisitor(int index, Accumulator offset);
+
+  /// Populates the `semanticsOffsets` and `semanticsElements` with the appropriate data
+  /// to be able to construct a [SemanticsNode].
+  ///
+  /// If applicable, the beginning and end text offset are added to [semanticsOffsets].
+  /// [PlaceholderSpan]s have a text length of 1, which corresponds to the object
+  /// replacement character (0xFFFC) that is inserted to represent it.
+  ///
+  /// Any [GestureRecognizer]s are added to `semanticsElements`. Null is added to
+  /// `semanticsElements` for [PlaceholderSpan]s.
+  @Deprecated(
+    'Implement computeSemanticsInformation instead. '
+    'This feature was deprecated after v1.7.3.'
+  )
+  void describeSemantics(Accumulator offset, List<int> semanticsOffsets, List<dynamic> semanticsElements);
+
+  /// In checked mode, throws an exception if the object is not in a
+  /// valid configuration. Otherwise, returns true.
+  ///
+  /// This is intended to be used as follows:
+  ///
+  /// ```dart
+  /// assert(myInlineSpan.debugAssertIsValid());
+  /// ```
+  bool debugAssertIsValid() => true;
+
+  /// Describe the difference between this span and another, in terms of
+  /// how much damage it will make to the rendering. The comparison is deep.
+  ///
+  /// Comparing [InlineSpan] objects of different types, for example, comparing
+  /// a [TextSpan] to a [WidgetSpan], always results in [RenderComparison.layout].
+  ///
+  /// See also:
+  ///
+  ///  * [TextStyle.compareTo], which does the same thing for [TextStyle]s.
+  RenderComparison compareTo(InlineSpan other);
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is InlineSpan
+        && other.style == style;
+  }
+
+  @override
+  int get hashCode => style.hashCode;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.whitespace;
+
+    if (style != null) {
+      style!.debugFillProperties(properties);
+    }
+  }
+}
diff --git a/lib/src/painting/matrix_utils.dart b/lib/src/painting/matrix_utils.dart
new file mode 100644
index 0000000..80abad7
--- /dev/null
+++ b/lib/src/painting/matrix_utils.dart
@@ -0,0 +1,588 @@
+// Copyright 2014 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.
+
+
+import 'dart:typed_data';
+
+import 'package:flute/foundation.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'basic_types.dart';
+
+/// Utility functions for working with matrices.
+class MatrixUtils {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  MatrixUtils._();
+
+  /// Returns the given [transform] matrix as an [Offset], if the matrix is
+  /// nothing but a 2D translation.
+  ///
+  /// Otherwise, returns null.
+  static Offset? getAsTranslation(Matrix4 transform) {
+    assert(transform != null);
+    final Float64List values = transform.storage;
+    // Values are stored in column-major order.
+    if (values[0] == 1.0 && // col 1
+        values[1] == 0.0 &&
+        values[2] == 0.0 &&
+        values[3] == 0.0 &&
+        values[4] == 0.0 && // col 2
+        values[5] == 1.0 &&
+        values[6] == 0.0 &&
+        values[7] == 0.0 &&
+        values[8] == 0.0 && // col 3
+        values[9] == 0.0 &&
+        values[10] == 1.0 &&
+        values[11] == 0.0 &&
+        values[14] == 0.0 && // bottom of col 4 (values 12 and 13 are the x and y offsets)
+        values[15] == 1.0) {
+      return Offset(values[12], values[13]);
+    }
+    return null;
+  }
+
+  /// Returns the given [transform] matrix as a [double] describing a uniform
+  /// scale, if the matrix is nothing but a symmetric 2D scale transform.
+  ///
+  /// Otherwise, returns null.
+  static double? getAsScale(Matrix4 transform) {
+    assert(transform != null);
+    final Float64List values = transform.storage;
+    // Values are stored in column-major order.
+    if (values[1] == 0.0 && // col 1 (value 0 is the scale)
+        values[2] == 0.0 &&
+        values[3] == 0.0 &&
+        values[4] == 0.0 && // col 2 (value 5 is the scale)
+        values[6] == 0.0 &&
+        values[7] == 0.0 &&
+        values[8] == 0.0 && // col 3
+        values[9] == 0.0 &&
+        values[10] == 1.0 &&
+        values[11] == 0.0 &&
+        values[12] == 0.0 && // col 4
+        values[13] == 0.0 &&
+        values[14] == 0.0 &&
+        values[15] == 1.0 &&
+        values[0] == values[5]) { // uniform scale
+      return values[0];
+    }
+    return null;
+  }
+
+  /// Returns true if the given matrices are exactly equal, and false
+  /// otherwise. Null values are assumed to be the identity matrix.
+  static bool matrixEquals(Matrix4? a, Matrix4? b) {
+    if (identical(a, b))
+      return true;
+    assert(a != null || b != null);
+    if (a == null)
+      return isIdentity(b!);
+    if (b == null)
+      return isIdentity(a);
+    assert(a != null && b != null);
+    return a.storage[0] == b.storage[0]
+        && a.storage[1] == b.storage[1]
+        && a.storage[2] == b.storage[2]
+        && a.storage[3] == b.storage[3]
+        && a.storage[4] == b.storage[4]
+        && a.storage[5] == b.storage[5]
+        && a.storage[6] == b.storage[6]
+        && a.storage[7] == b.storage[7]
+        && a.storage[8] == b.storage[8]
+        && a.storage[9] == b.storage[9]
+        && a.storage[10] == b.storage[10]
+        && a.storage[11] == b.storage[11]
+        && a.storage[12] == b.storage[12]
+        && a.storage[13] == b.storage[13]
+        && a.storage[14] == b.storage[14]
+        && a.storage[15] == b.storage[15];
+  }
+
+  /// Whether the given matrix is the identity matrix.
+  static bool isIdentity(Matrix4 a) {
+    assert(a != null);
+    return a.storage[0] == 1.0 // col 1
+        && a.storage[1] == 0.0
+        && a.storage[2] == 0.0
+        && a.storage[3] == 0.0
+        && a.storage[4] == 0.0 // col 2
+        && a.storage[5] == 1.0
+        && a.storage[6] == 0.0
+        && a.storage[7] == 0.0
+        && a.storage[8] == 0.0 // col 3
+        && a.storage[9] == 0.0
+        && a.storage[10] == 1.0
+        && a.storage[11] == 0.0
+        && a.storage[12] == 0.0 // col 4
+        && a.storage[13] == 0.0
+        && a.storage[14] == 0.0
+        && a.storage[15] == 1.0;
+  }
+
+  /// Applies the given matrix as a perspective transform to the given point.
+  ///
+  /// This function assumes the given point has a z-coordinate of 0.0. The
+  /// z-coordinate of the result is ignored.
+  ///
+  /// While not common, this method may return (NaN, NaN), iff the given `point`
+  /// results in a "point at infinity" in homogeneous coordinates after applying
+  /// the `transform`. For example, a [RenderObject] may set its transform to
+  /// the zero matrix to indicate its content is currently not visible. Trying
+  /// to convert an `Offset` to its coordinate space always results in
+  /// (NaN, NaN).
+  static Offset transformPoint(Matrix4 transform, Offset point) {
+    final Float64List storage = transform.storage;
+    final double x = point.dx;
+    final double y = point.dy;
+
+    // Directly simulate the transform of the vector (x, y, 0, 1),
+    // dropping the resulting Z coordinate, and normalizing only
+    // if needed.
+
+    final double rx = storage[0] * x + storage[4] * y + storage[12];
+    final double ry = storage[1] * x + storage[5] * y + storage[13];
+    final double rw = storage[3] * x + storage[7] * y + storage[15];
+    if (rw == 1.0) {
+      return Offset(rx, ry);
+    } else {
+      return Offset(rx / rw, ry / rw);
+    }
+  }
+
+  /// Returns a rect that bounds the result of applying the given matrix as a
+  /// perspective transform to the given rect.
+  ///
+  /// This version of the operation is slower than the regular transformRect
+  /// method, but it avoids creating infinite values from large finite values
+  /// if it can.
+  static Rect _safeTransformRect(Matrix4 transform, Rect rect) {
+    final Float64List storage = transform.storage;
+    final bool isAffine = storage[3] == 0.0 &&
+        storage[7] == 0.0 &&
+        storage[15] == 1.0;
+
+    _accumulate(storage, rect.left,  rect.top,    true,  isAffine);
+    _accumulate(storage, rect.right, rect.top,    false, isAffine);
+    _accumulate(storage, rect.left,  rect.bottom, false, isAffine);
+    _accumulate(storage, rect.right, rect.bottom, false, isAffine);
+
+    return Rect.fromLTRB(_minMax[0], _minMax[1], _minMax[2], _minMax[3]);
+  }
+
+  static late final Float64List _minMax = Float64List(4);
+  static void _accumulate(Float64List m, double x, double y, bool first, bool isAffine) {
+    final double w = isAffine ? 1.0 : 1.0 / (m[3] * x + m[7] * y + m[15]);
+    final double tx = (m[0] * x + m[4] * y + m[12]) * w;
+    final double ty = (m[1] * x + m[5] * y + m[13]) * w;
+    if (first) {
+      _minMax[0] = _minMax[2] = tx;
+      _minMax[1] = _minMax[3] = ty;
+    } else {
+      if (tx < _minMax[0]) {
+        _minMax[0] = tx;
+      }
+      if (ty < _minMax[1]) {
+        _minMax[1] = ty;
+      }
+      if (tx > _minMax[2]) {
+        _minMax[2] = tx;
+      }
+      if (ty > _minMax[3]) {
+        _minMax[3] = ty;
+      }
+    }
+  }
+
+  /// Returns a rect that bounds the result of applying the given matrix as a
+  /// perspective transform to the given rect.
+  ///
+  /// This function assumes the given rect is in the plane with z equals 0.0.
+  /// The transformed rect is then projected back into the plane with z equals
+  /// 0.0 before computing its bounding rect.
+  static Rect transformRect(Matrix4 transform, Rect rect) {
+    final Float64List storage = transform.storage;
+    final double x = rect.left;
+    final double y = rect.top;
+    final double w = rect.right - x;
+    final double h = rect.bottom - y;
+
+    // We want to avoid turning a finite rect into an infinite one if we can.
+    if (!w.isFinite || !h.isFinite) {
+      return _safeTransformRect(transform, rect);
+    }
+
+    // Transforming the 4 corners of a rectangle the straightforward way
+    // incurs the cost of transforming 4 points using vector math which
+    // involves 48 multiplications and 48 adds and then normalizing
+    // the points using 4 inversions of the homogeneous weight factor
+    // and then 12 multiplies. Once we have transformed all of the points
+    // we then need to turn them into a bounding box using 4 min/max
+    // operations each on 4 values yielding 12 total comparisons.
+    //
+    // On top of all of those operations, using the vector_math package to
+    // do the work for us involves allocating several objects in order to
+    // communicate the values back and forth - 4 allocating getters to extract
+    // the [Offset] objects for the corners of the [Rect], 4 conversions to
+    // a [Vector3] to use [Matrix4.perspectiveTransform()], and then 4 new
+    // [Offset] objects allocated to hold those results, yielding 8 [Offset]
+    // and 4 [Vector3] object allocations per rectangle transformed.
+    //
+    // But the math we really need to get our answer is actually much less
+    // than that.
+    //
+    // First, consider that a full point transform using the vector math
+    // package involves expanding it out into a vector3 with a Z coordinate
+    // of 0.0 and then performing 3 multiplies and 3 adds per coordinate:
+    // ```
+    // xt = x*m00 + y*m10 + z*m20 + m30;
+    // yt = x*m01 + y*m11 + z*m21 + m31;
+    // zt = x*m02 + y*m12 + z*m22 + m32;
+    // wt = x*m03 + y*m13 + z*m23 + m33;
+    // ```
+    // Immediately we see that we can get rid of the 3rd column of multiplies
+    // since we know that Z=0.0. We can also get rid of the 3rd row because
+    // we ignore the resulting Z coordinate. Finally we can get rid of the
+    // last row if we don't have a perspective transform since we can verify
+    // that the results are 1.0 for all points.  This gets us down to 16
+    // multiplies and 16 adds in the non-perspective case and 24 of each for
+    // the perspective case. (Plus the 12 comparisons to turn them back into
+    // a bounding box.)
+    //
+    // But we can do even better than that.
+    //
+    // Under optimal conditions of no perspective transformation,
+    // which is actually a very common condition, we can transform
+    // a rectangle in as little as 3 operations:
+    //
+    // (rx,ry) = transform of upper left corner of rectangle
+    // (wx,wy) = delta transform of the (w, 0) width relative vector
+    // (hx,hy) = delta transform of the (0, h) height relative vector
+    //
+    // A delta transform is a transform of all elements of the matrix except
+    // for the translation components. The translation components are added
+    // in at the end of each transform computation so they represent a
+    // constant offset for each point transformed. A delta transform of
+    // a horizontal or vertical vector involves a single multiplication due
+    // to the fact that it only has one non-zero coordinate and no addition
+    // of the translation component.
+    //
+    // In the absence of a perspective transform, the transformed
+    // rectangle will be mapped into a parallelogram with corners at:
+    // corner1 = (rx, ry)
+    // corner2 = corner1 + dTransformed width vector = (rx+wx, ry+wy)
+    // corner3 = corner1 + dTransformed height vector = (rx+hx, ry+hy)
+    // corner4 = corner1 + both dTransformed vectors = (rx+wx+hx, ry+wy+hy)
+    // In all, this method of transforming the rectangle requires only
+    // 8 multiplies and 12 additions (which we can reduce to 8 additions if
+    // we only need a bounding box, see below).
+    //
+    // In the presence of a perspective transform, the above conditions
+    // continue to hold with respect to the non-normalized coordinates so
+    // we can still save a lot of multiplications by computing the 4
+    // non-normalized coordinates using relative additions before we normalize
+    // them and they lose their "pseudo-parallelogram" relationships.  We still
+    // have to do the normalization divisions and min/max all 4 points to
+    // get the resulting transformed bounding box, but we save a lot of
+    // calculations over blindly transforming all 4 coordinates independently.
+    // In all, we need 12 multiplies and 22 additions to construct the
+    // non-normalized vectors and then 8 divisions (or 4 inversions and 8
+    // multiplies) for normalization (plus the standard set of 12 comparisons
+    // for the min/max bounds operations).
+    //
+    // Back to the non-perspective case, the optimization that lets us get
+    // away with fewer additions if we only need a bounding box comes from
+    // analyzing the impact of the relative vectors on expanding the
+    // bounding box of the parallelogram. First, the bounding box always
+    // contains the transformed upper-left corner of the rectangle. Next,
+    // each relative vector either pushes on the left or right side of the
+    // bounding box and also either the top or bottom side, depending on
+    // whether it is positive or negative. Finally, you can consider the
+    // impact of each vector on the bounding box independently. If, say,
+    // wx and hx have the same sign, then the limiting point in the bounding
+    // box will be the one that involves adding both of them to the origin
+    // point. If they have opposite signs, then one will push one wall one
+    // way and the other will push the opposite wall the other way and when
+    // you combine both of them, the resulting "opposite corner" will
+    // actually be between the limits they established by pushing the walls
+    // away from each other, as below:
+    // ```
+    //         +---------(originx,originy)--------------+
+    //         |            -----^----                  |
+    //         |       -----          ----              |
+    //         |  -----                   ----          |
+    // (+hx,+hy)<                             ----      |
+    //         |  ----                            ----  |
+    //         |      ----                             >(+wx,+wy)
+    //         |          ----                   -----  |
+    //         |              ----          -----       |
+    //         |                  ---- -----            |
+    //         |                      v                 |
+    //         +---------------(+wx+hx,+wy+hy)----------+
+    // ```
+    // In this diagram, consider that:
+    // ```
+    // wx would be a positive number
+    // hx would be a negative number
+    // wy and hy would both be positive numbers
+    // ```
+    // As a result, wx pushes out the right wall, hx pushes out the left wall,
+    // and both wy and hy push down the bottom wall of the bounding box. The
+    // wx,hx pair (of opposite signs) worked on opposite walls and the final
+    // opposite corner had an X coordinate between the limits they established.
+    // The wy,hy pair (of the same sign) both worked together to push the
+    // bottom wall down by their sum.
+    //
+    // This relationship allows us to simply start with the point computed by
+    // transforming the upper left corner of the rectangle, and then
+    // conditionally adding wx, wy, hx, and hy to either the left or top
+    // or right or bottom of the bounding box independently depending on sign.
+    // In that case we only need 4 comparisons and 4 additions total to
+    // compute the bounding box, combined with the 8 multiplications and
+    // 4 additions to compute the transformed point and relative vectors
+    // for a total of 8 multiplies, 8 adds, and 4 comparisons.
+    //
+    // An astute observer will note that we do need to do 2 subtractions at
+    // the top of the method to compute the width and height.  Add those to
+    // all of the relative solutions listed above.  The test for perspective
+    // also adds 3 compares to the affine case and up to 3 compares to the
+    // perspective case (depending on which test fails, the rest are omitted).
+    //
+    // The final tally:
+    // basic method          = 60 mul + 48 add + 12 compare
+    // optimized perspective = 12 mul + 22 add + 15 compare + 2 sub
+    // optimized affine      =  8 mul +  8 add +  7 compare + 2 sub
+    //
+    // Since compares are essentially subtractions and subtractions are
+    // the same cost as adds, we end up with:
+    // basic method          = 60 mul + 60 add/sub/compare
+    // optimized perspective = 12 mul + 39 add/sub/compare
+    // optimized affine      =  8 mul + 17 add/sub/compare
+
+    final double wx = storage[0] * w;
+    final double hx = storage[4] * h;
+    final double rx = storage[0] * x + storage[4] * y + storage[12];
+
+    final double wy = storage[1] * w;
+    final double hy = storage[5] * h;
+    final double ry = storage[1] * x + storage[5] * y + storage[13];
+
+    if (storage[3] == 0.0 && storage[7] == 0.0 && storage[15] == 1.0) {
+      double left  = rx;
+      double right = rx;
+      if (wx < 0) {
+        left  += wx;
+      } else {
+        right += wx;
+      }
+      if (hx < 0) {
+        left  += hx;
+      } else {
+        right += hx;
+      }
+
+      double top    = ry;
+      double bottom = ry;
+      if (wy < 0) {
+        top    += wy;
+      } else {
+        bottom += wy;
+      }
+      if (hy < 0) {
+        top    += hy;
+      } else {
+        bottom += hy;
+      }
+
+      return Rect.fromLTRB(left, top, right, bottom);
+    } else {
+      final double ww = storage[3] * w;
+      final double hw = storage[7] * h;
+      final double rw = storage[3] * x + storage[7] * y + storage[15];
+
+      final double ulx =  rx            /  rw;
+      final double uly =  ry            /  rw;
+      final double urx = (rx + wx)      / (rw + ww);
+      final double ury = (ry + wy)      / (rw + ww);
+      final double llx = (rx      + hx) / (rw      + hw);
+      final double lly = (ry      + hy) / (rw      + hw);
+      final double lrx = (rx + wx + hx) / (rw + ww + hw);
+      final double lry = (ry + wy + hy) / (rw + ww + hw);
+
+      return Rect.fromLTRB(
+        _min4(ulx, urx, llx, lrx),
+        _min4(uly, ury, lly, lry),
+        _max4(ulx, urx, llx, lrx),
+        _max4(uly, ury, lly, lry),
+      );
+    }
+  }
+
+  static double _min4(double a, double b, double c, double d) {
+    final double e = (a < b) ? a : b;
+    final double f = (c < d) ? c : d;
+    return (e < f) ? e : f;
+  }
+  static double _max4(double a, double b, double c, double d) {
+    final double e = (a > b) ? a : b;
+    final double f = (c > d) ? c : d;
+    return (e > f) ? e : f;
+  }
+
+  /// Returns a rect that bounds the result of applying the inverse of the given
+  /// matrix as a perspective transform to the given rect.
+  ///
+  /// This function assumes the given rect is in the plane with z equals 0.0.
+  /// The transformed rect is then projected back into the plane with z equals
+  /// 0.0 before computing its bounding rect.
+  static Rect inverseTransformRect(Matrix4 transform, Rect rect) {
+    assert(rect != null);
+    // As exposed by `unrelated_type_equality_checks`, this assert was a no-op.
+    // Fixing it introduces a bunch of runtime failures; for more context see:
+    // https://github.com/flutter/flutter/pull/31568
+    // assert(transform.determinant != 0.0);
+    if (isIdentity(transform))
+      return rect;
+    transform = Matrix4.copy(transform)..invert();
+    return transformRect(transform, rect);
+  }
+
+  /// Create a transformation matrix which mimics the effects of tangentially
+  /// wrapping the plane on which this transform is applied around a cylinder
+  /// and then looking at the cylinder from a point outside the cylinder.
+  ///
+  /// The `radius` simulates the radius of the cylinder the plane is being
+  /// wrapped onto. If the transformation is applied to a 0-dimensional dot
+  /// instead of a plane, the dot would simply translate by +/- `radius` pixels
+  /// along the `orientation` [Axis] when rotating from 0 to +/- 90 degrees.
+  ///
+  /// A positive radius means the object is closest at 0 `angle` and a negative
+  /// radius means the object is closest at π `angle` or 180 degrees.
+  ///
+  /// The `angle` argument is the difference in angle in radians between the
+  /// object and the viewing point. A positive `angle` on a positive `radius`
+  /// moves the object up when `orientation` is vertical and right when
+  /// horizontal.
+  ///
+  /// The transformation is always done such that a 0 `angle` keeps the
+  /// transformed object at exactly the same size as before regardless of
+  /// `radius` and `perspective` when `radius` is positive.
+  ///
+  /// The `perspective` argument is a number between 0 and 1 where 0 means
+  /// looking at the object from infinitely far with an infinitely narrow field
+  /// of view and 1 means looking at the object from infinitely close with an
+  /// infinitely wide field of view. Defaults to a sane but arbitrary 0.001.
+  ///
+  /// The `orientation` is the direction of the rotation axis.
+  ///
+  /// Because the viewing position is a point, it's never possible to see the
+  /// outer side of the cylinder at or past +/- π / 2 or 90 degrees and it's
+  /// almost always possible to end up seeing the inner side of the cylinder
+  /// or the back side of the transformed plane before π / 2 when perspective > 0.
+  static Matrix4 createCylindricalProjectionTransform({
+    required double radius,
+    required double angle,
+    double perspective = 0.001,
+    Axis orientation = Axis.vertical,
+  }) {
+    assert(radius != null);
+    assert(angle != null);
+    assert(perspective >= 0 && perspective <= 1.0);
+    assert(orientation != null);
+
+    // Pre-multiplied matrix of a projection matrix and a view matrix.
+    //
+    // Projection matrix is a simplified perspective matrix
+    // http://web.iitd.ac.in/~hegde/cad/lecture/L9_persproj.pdf
+    // in the form of
+    // [[1.0, 0.0, 0.0, 0.0],
+    //  [0.0, 1.0, 0.0, 0.0],
+    //  [0.0, 0.0, 1.0, 0.0],
+    //  [0.0, 0.0, -perspective, 1.0]]
+    //
+    // View matrix is a simplified camera view matrix.
+    // Basically re-scales to keep object at original size at angle = 0 at
+    // any radius in the form of
+    // [[1.0, 0.0, 0.0, 0.0],
+    //  [0.0, 1.0, 0.0, 0.0],
+    //  [0.0, 0.0, 1.0, -radius],
+    //  [0.0, 0.0, 0.0, 1.0]]
+    Matrix4 result = Matrix4.identity()
+        ..setEntry(3, 2, -perspective)
+        ..setEntry(2, 3, -radius)
+        ..setEntry(3, 3, perspective * radius + 1.0);
+
+    // Model matrix by first translating the object from the origin of the world
+    // by radius in the z axis and then rotating against the world.
+    result = result * ((
+        orientation == Axis.horizontal
+            ? Matrix4.rotationY(angle)
+            : Matrix4.rotationX(angle)
+    ) * Matrix4.translationValues(0.0, 0.0, radius)) as Matrix4;
+
+    // Essentially perspective * view * model.
+    return result;
+  }
+
+  /// Returns a matrix that transforms every point to [offset].
+  static Matrix4 forceToPoint(Offset offset) {
+    return Matrix4.identity()
+      ..setRow(0, Vector4(0, 0, 0, offset.dx))
+      ..setRow(1, Vector4(0, 0, 0, offset.dy));
+  }
+}
+
+/// Returns a list of strings representing the given transform in a format
+/// useful for [TransformProperty].
+///
+/// If the argument is null, returns a list with the single string "null".
+List<String> debugDescribeTransform(Matrix4? transform) {
+  if (transform == null)
+    return const <String>['null'];
+  return <String>[
+    '[0] ${debugFormatDouble(transform.entry(0, 0))},${debugFormatDouble(transform.entry(0, 1))},${debugFormatDouble(transform.entry(0, 2))},${debugFormatDouble(transform.entry(0, 3))}',
+    '[1] ${debugFormatDouble(transform.entry(1, 0))},${debugFormatDouble(transform.entry(1, 1))},${debugFormatDouble(transform.entry(1, 2))},${debugFormatDouble(transform.entry(1, 3))}',
+    '[2] ${debugFormatDouble(transform.entry(2, 0))},${debugFormatDouble(transform.entry(2, 1))},${debugFormatDouble(transform.entry(2, 2))},${debugFormatDouble(transform.entry(2, 3))}',
+    '[3] ${debugFormatDouble(transform.entry(3, 0))},${debugFormatDouble(transform.entry(3, 1))},${debugFormatDouble(transform.entry(3, 2))},${debugFormatDouble(transform.entry(3, 3))}',
+  ];
+}
+
+/// Property which handles [Matrix4] that represent transforms.
+class TransformProperty extends DiagnosticsProperty<Matrix4> {
+  /// Create a diagnostics property for [Matrix4] objects.
+  ///
+  /// The [showName] and [level] arguments must not be null.
+  TransformProperty(
+    String name,
+    Matrix4? value, {
+    bool showName = true,
+    Object? defaultValue = kNoDefaultValue,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(showName != null),
+       assert(level != null),
+       super(
+         name,
+         value,
+         showName: showName,
+         defaultValue: defaultValue,
+         level: level,
+       );
+
+  @override
+  String valueToString({ TextTreeConfiguration? parentConfiguration }) {
+    if (parentConfiguration != null && !parentConfiguration.lineBreakProperties) {
+      // Format the value on a single line to be compatible with the parent's
+      // style.
+      final List<String> values = <String>[
+        '${debugFormatDouble(value!.entry(0, 0))},${debugFormatDouble(value!.entry(0, 1))},${debugFormatDouble(value!.entry(0, 2))},${debugFormatDouble(value!.entry(0, 3))}',
+        '${debugFormatDouble(value!.entry(1, 0))},${debugFormatDouble(value!.entry(1, 1))},${debugFormatDouble(value!.entry(1, 2))},${debugFormatDouble(value!.entry(1, 3))}',
+        '${debugFormatDouble(value!.entry(2, 0))},${debugFormatDouble(value!.entry(2, 1))},${debugFormatDouble(value!.entry(2, 2))},${debugFormatDouble(value!.entry(2, 3))}',
+        '${debugFormatDouble(value!.entry(3, 0))},${debugFormatDouble(value!.entry(3, 1))},${debugFormatDouble(value!.entry(3, 2))},${debugFormatDouble(value!.entry(3, 3))}',
+      ];
+      return '[${values.join('; ')}]';
+    }
+    return debugDescribeTransform(value).join('\n');
+  }
+}
diff --git a/lib/src/painting/notched_shapes.dart b/lib/src/painting/notched_shapes.dart
new file mode 100644
index 0000000..d92659a
--- /dev/null
+++ b/lib/src/painting/notched_shapes.dart
@@ -0,0 +1,165 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+
+import 'basic_types.dart';
+import 'borders.dart';
+
+/// A shape with a notch in its outline.
+///
+/// Typically used as the outline of a 'host' widget to make a notch that
+/// accommodates a 'guest' widget. e.g the [BottomAppBar] may have a notch to
+/// accommodate the [FloatingActionButton].
+///
+/// See also:
+///
+///  * [ShapeBorder], which defines a shaped border without a dynamic notch.
+///  * [AutomaticNotchedShape], an adapter from [ShapeBorder] to [NotchedShape].
+abstract class NotchedShape {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const NotchedShape();
+
+  /// Creates a [Path] that describes the outline of the shape.
+  ///
+  /// The `host` is the bounding rectangle of the shape.
+  ///
+  /// The `guest` is the bounding rectangle of the shape for which a notch will
+  /// be made. It is null when there is no guest.
+  Path getOuterPath(Rect host, Rect? guest);
+}
+
+/// A rectangle with a smooth circular notch.
+///
+/// See also:
+///
+///  * [CircleBorder], a [ShapeBorder] that describes a circle.
+class CircularNotchedRectangle extends NotchedShape {
+  /// Creates a [CircularNotchedRectangle].
+  ///
+  /// The same object can be used to create multiple shapes.
+  const CircularNotchedRectangle();
+
+  /// Creates a [Path] that describes a rectangle with a smooth circular notch.
+  ///
+  /// `host` is the bounding box for the returned shape. Conceptually this is
+  /// the rectangle to which the notch will be applied.
+  ///
+  /// `guest` is the bounding box of a circle that the notch accommodates. All
+  /// points in the circle bounded by `guest` will be outside of the returned
+  /// path.
+  ///
+  /// The notch is curve that smoothly connects the host's top edge and
+  /// the guest circle.
+  // TODO(amirh): add an example diagram here.
+  @override
+  Path getOuterPath(Rect host, Rect? guest) {
+    if (guest == null || !host.overlaps(guest))
+      return Path()..addRect(host);
+
+    // The guest's shape is a circle bounded by the guest rectangle.
+    // So the guest's radius is half the guest width.
+    final double notchRadius = guest.width / 2.0;
+
+    // We build a path for the notch from 3 segments:
+    // Segment A - a Bezier curve from the host's top edge to segment B.
+    // Segment B - an arc with radius notchRadius.
+    // Segment C - a Bezier curve from segment B back to the host's top edge.
+    //
+    // A detailed explanation and the derivation of the formulas below is
+    // available at: https://goo.gl/Ufzrqn
+
+    const double s1 = 15.0;
+    const double s2 = 1.0;
+
+    final double r = notchRadius;
+    final double a = -1.0 * r - s2;
+    final double b = host.top - guest.center.dy;
+
+    final double n2 = math.sqrt(b * b * r * r * (a * a + b * b - r * r));
+    final double p2xA = ((a * r * r) - n2) / (a * a + b * b);
+    final double p2xB = ((a * r * r) + n2) / (a * a + b * b);
+    final double p2yA = math.sqrt(r * r - p2xA * p2xA);
+    final double p2yB = math.sqrt(r * r - p2xB * p2xB);
+
+    final List<Offset?> p = List<Offset?>.filled(6, null, growable: false);
+
+    // p0, p1, and p2 are the control points for segment A.
+    p[0] = Offset(a - s1, b);
+    p[1] = Offset(a, b);
+    final double cmp = b < 0 ? -1.0 : 1.0;
+    p[2] = cmp * p2yA > cmp * p2yB ? Offset(p2xA, p2yA) : Offset(p2xB, p2yB);
+
+    // p3, p4, and p5 are the control points for segment B, which is a mirror
+    // of segment A around the y axis.
+    p[3] = Offset(-1.0 * p[2]!.dx, p[2]!.dy);
+    p[4] = Offset(-1.0 * p[1]!.dx, p[1]!.dy);
+    p[5] = Offset(-1.0 * p[0]!.dx, p[0]!.dy);
+
+    // translate all points back to the absolute coordinate system.
+    for (int i = 0; i < p.length; i += 1)
+      p[i] = p[i]! + guest.center;
+
+    return Path()
+      ..moveTo(host.left, host.top)
+      ..lineTo(p[0]!.dx, p[0]!.dy)
+      ..quadraticBezierTo(p[1]!.dx, p[1]!.dy, p[2]!.dx, p[2]!.dy)
+      ..arcToPoint(
+        p[3]!,
+        radius: Radius.circular(notchRadius),
+        clockwise: false,
+      )
+      ..quadraticBezierTo(p[4]!.dx, p[4]!.dy, p[5]!.dx, p[5]!.dy)
+      ..lineTo(host.right, host.top)
+      ..lineTo(host.right, host.bottom)
+      ..lineTo(host.left, host.bottom)
+      ..close();
+  }
+}
+
+/// A [NotchedShape] created from [ShapeBorder]s.
+///
+/// Two shapes can be provided. The [host] is the shape of the widget that
+/// uses the [NotchedShape] (typically a [BottomAppBar]). The [guest] is
+/// subtracted from the [host] to create the notch (typically to make room
+/// for a [FloatingActionButton]).
+class AutomaticNotchedShape extends NotchedShape {
+  /// Creates a [NotchedShape] that is defined by two [ShapeBorder]s.
+  ///
+  /// The [host] must not be null.
+  ///
+  /// The [guest] may be null, in which case no notch is created even
+  /// if a guest rectangle is provided to [getOuterPath].
+  const AutomaticNotchedShape(this.host, [ this.guest ]);
+
+  /// The shape of the widget that uses the [NotchedShape] (typically a
+  /// [BottomAppBar]).
+  ///
+  /// This shape cannot depend on the [TextDirection], as no text direction
+  /// is available to [NotchedShape]s.
+  final ShapeBorder host;
+
+  /// The shape to subtract from the [host] to make the notch.
+  ///
+  /// This shape cannot depend on the [TextDirection], as no text direction
+  /// is available to [NotchedShape]s.
+  ///
+  /// If this is null, [getOuterPath] ignores the guest rectangle.
+  final ShapeBorder? guest;
+
+  @override
+  Path getOuterPath(Rect hostRect, Rect? guestRect) { // ignore: avoid_renaming_method_parameters, the
+    // parameters are renamed over the baseclass because they would clash
+    // with properties of this object, and the use of all four of them in
+    // the code below is really confusing if they have the same names.
+    final Path hostPath = host.getOuterPath(hostRect);
+    if (guest != null && guestRect != null) {
+      final Path guestPath = guest!.getOuterPath(guestRect);
+      return Path.combine(PathOperation.difference, hostPath, guestPath);
+    }
+    return hostPath;
+  }
+}
diff --git a/lib/src/painting/paint_utilities.dart b/lib/src/painting/paint_utilities.dart
new file mode 100644
index 0000000..d68da30
--- /dev/null
+++ b/lib/src/painting/paint_utilities.dart
@@ -0,0 +1,43 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+
+import 'basic_types.dart';
+
+/// Draw a line between two points, which cuts diagonally back and forth across
+/// the line that connects the two points.
+///
+/// The line will cross the line `zigs - 1` times.
+///
+/// If `zigs` is 1, then this will draw two sides of a triangle from `start` to
+/// `end`, with the third point being `width` away from the line, as measured
+/// perpendicular to that line.
+///
+/// If `width` is positive, the first `zig` will be to the left of the `start`
+/// point when facing the `end` point. To reverse the zigging polarity, provide
+/// a negative `width`.
+///
+/// The line is drawn using the provided `paint` on the provided `canvas`.
+void paintZigZag(Canvas canvas, Paint paint, Offset start, Offset end, int zigs, double width) {
+  assert(zigs.isFinite);
+  assert(zigs > 0);
+  canvas.save();
+  canvas.translate(start.dx, start.dy);
+  end = end - start;
+  canvas.rotate(math.atan2(end.dy, end.dx));
+  final double length = end.distance;
+  final double spacing = length / (zigs * 2.0);
+  final Path path = Path()
+    ..moveTo(0.0, 0.0);
+  for (int index = 0; index < zigs; index += 1) {
+    final double x = (index * 2.0 + 1.0) * spacing;
+    final double y = width * ((index % 2.0) * 2.0 - 1.0);
+    path.lineTo(x, y);
+  }
+  path.lineTo(length, 0.0);
+  canvas.drawPath(path, paint);
+  canvas.restore();
+}
diff --git a/lib/src/painting/placeholder_span.dart b/lib/src/painting/placeholder_span.dart
new file mode 100644
index 0000000..61a0481
--- /dev/null
+++ b/lib/src/painting/placeholder_span.dart
@@ -0,0 +1,103 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' as ui show PlaceholderAlignment;
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'inline_span.dart';
+import 'text_painter.dart';
+import 'text_span.dart';
+import 'text_style.dart';
+
+/// An immutable placeholder that is embedded inline within text.
+///
+/// [PlaceholderSpan] represents a placeholder that acts as a stand-in for other
+/// content. A [PlaceholderSpan] by itself does not contain useful
+/// information to change a [TextSpan]. Instead, this class must be extended
+/// to define contents.
+///
+/// [WidgetSpan] from the widgets library extends [PlaceholderSpan] and may be
+/// used instead to specify a widget as the contents of the placeholder.
+///
+/// See also:
+///
+///  * [WidgetSpan], a leaf node that represents an embedded inline widget.
+///  * [TextSpan], a node that represents text in a [TextSpan] tree.
+///  * [Text], a widget for showing uniformly-styled text.
+///  * [RichText], a widget for finer control of text rendering.
+///  * [TextPainter], a class for painting [TextSpan] objects on a [Canvas].
+abstract class PlaceholderSpan extends InlineSpan {
+  /// Creates a [PlaceholderSpan] with the given values.
+  ///
+  /// A [TextStyle] may be provided with the [style] property, but only the
+  /// decoration, foreground, background, and spacing options will be used.
+  const PlaceholderSpan({
+    this.alignment = ui.PlaceholderAlignment.bottom,
+    this.baseline,
+    TextStyle? style,
+  }) : super(style: style,);
+
+  /// How the placeholder aligns vertically with the text.
+  ///
+  /// See [ui.PlaceholderAlignment] for details on each mode.
+  final ui.PlaceholderAlignment alignment;
+
+  /// The [TextBaseline] to align against when using [ui.PlaceholderAlignment.baseline],
+  /// [ui.PlaceholderAlignment.aboveBaseline], and [ui.PlaceholderAlignment.belowBaseline].
+  ///
+  /// This is ignored when using other alignment modes.
+  final TextBaseline? baseline;
+
+  /// [PlaceholderSpan]s are flattened to a `0xFFFC` object replacement character in the
+  /// plain text representation when `includePlaceholders` is true.
+  @override
+  void computeToPlainText(StringBuffer buffer, {bool includeSemanticsLabels = true, bool includePlaceholders = true}) {
+    if (includePlaceholders) {
+      buffer.write('\uFFFC');
+    }
+  }
+
+  @override
+  void computeSemanticsInformation(List<InlineSpanSemanticsInformation> collector) {
+    collector.add(InlineSpanSemanticsInformation.placeholder);
+  }
+
+  // TODO(garyq): Remove this after next stable release.
+  /// The [visitTextSpan] method is invalid on [PlaceholderSpan]s.
+  @override
+  @Deprecated(
+    'Use to visitChildren instead. '
+    'This feature was deprecated after v1.7.3.'
+  )
+  bool visitTextSpan(bool visitor(TextSpan span)) {
+    assert(false, 'visitTextSpan is deprecated. Use visitChildren to support InlineSpans');
+    return false;
+  }
+
+  /// Populates the `semanticsOffsets` and `semanticsElements` with the appropriate data
+  /// to be able to construct a [SemanticsNode].
+  ///
+  /// [PlaceholderSpan]s have a text length of 1, which corresponds to the object
+  /// replacement character (0xFFFC) that is inserted to represent it.
+  ///
+  /// Null is added to `semanticsElements` for [PlaceholderSpan]s.
+  @override
+  void describeSemantics(Accumulator offset, List<int> semanticsOffsets, List<dynamic> semanticsElements) {
+    semanticsOffsets.add(offset.value);
+    semanticsOffsets.add(offset.value + 1);
+    semanticsElements.add(null); // null indicates this is a placeholder.
+    offset.increment(1);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+
+    properties.add(EnumProperty<ui.PlaceholderAlignment>('alignment', alignment, defaultValue: null));
+    properties.add(EnumProperty<TextBaseline>('baseline', baseline, defaultValue: null));
+  }
+}
diff --git a/lib/src/painting/rounded_rectangle_border.dart b/lib/src/painting/rounded_rectangle_border.dart
new file mode 100644
index 0000000..e9e935a
--- /dev/null
+++ b/lib/src/painting/rounded_rectangle_border.dart
@@ -0,0 +1,319 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' as ui show lerpDouble;
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'border_radius.dart';
+import 'borders.dart';
+import 'circle_border.dart';
+import 'edge_insets.dart';
+
+/// A rectangular border with rounded corners.
+///
+/// Typically used with [ShapeDecoration] to draw a box with a rounded
+/// rectangle.
+///
+/// This shape can interpolate to and from [CircleBorder].
+///
+/// See also:
+///
+///  * [BorderSide], which is used to describe each side of the box.
+///  * [Border], which, when used with [BoxDecoration], can also
+///    describe a rounded rectangle.
+class RoundedRectangleBorder extends OutlinedBorder {
+  /// Creates a rounded rectangle border.
+  ///
+  /// The arguments must not be null.
+  const RoundedRectangleBorder({
+    BorderSide side = BorderSide.none,
+    this.borderRadius = BorderRadius.zero,
+  }) : assert(side != null),
+       assert(borderRadius != null),
+       super(side: side);
+
+  /// The radii for each corner.
+  final BorderRadiusGeometry borderRadius;
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    return EdgeInsets.all(side.width);
+  }
+
+  @override
+  ShapeBorder scale(double t) {
+    return RoundedRectangleBorder(
+      side: side.scale(t),
+      borderRadius: borderRadius * t,
+    );
+  }
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    assert(t != null);
+    if (a is RoundedRectangleBorder) {
+      return RoundedRectangleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
+      );
+    }
+    if (a is CircleBorder) {
+      return _RoundedRectangleToCircleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        borderRadius: borderRadius,
+        circleness: 1.0 - t,
+      );
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    assert(t != null);
+    if (b is RoundedRectangleBorder) {
+      return RoundedRectangleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
+      );
+    }
+    if (b is CircleBorder) {
+      return _RoundedRectangleToCircleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        borderRadius: borderRadius,
+        circleness: t,
+      );
+    }
+    return super.lerpTo(b, t);
+  }
+
+  /// Returns a copy of this RoundedRectangleBorder with the given fields
+  /// replaced with the new values.
+  @override
+  RoundedRectangleBorder copyWith({ BorderSide? side, BorderRadius? borderRadius }) {
+    return RoundedRectangleBorder(
+      side: side ?? this.side,
+      borderRadius: borderRadius ?? this.borderRadius,
+    );
+  }
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()
+      ..addRRect(borderRadius.resolve(textDirection).toRRect(rect).deflate(side.width));
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()
+      ..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
+  }
+
+  @override
+  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
+    switch (side.style) {
+      case BorderStyle.none:
+        break;
+      case BorderStyle.solid:
+        final double width = side.width;
+        if (width == 0.0) {
+          canvas.drawRRect(borderRadius.resolve(textDirection).toRRect(rect), side.toPaint());
+        } else {
+          final RRect outer = borderRadius.resolve(textDirection).toRRect(rect);
+          final RRect inner = outer.deflate(width);
+          final Paint paint = Paint()
+            ..color = side.color;
+          canvas.drawDRRect(outer, inner, paint);
+        }
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is RoundedRectangleBorder
+        && other.side == side
+        && other.borderRadius == borderRadius;
+  }
+
+  @override
+  int get hashCode => hashValues(side, borderRadius);
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'RoundedRectangleBorder')}($side, $borderRadius)';
+  }
+}
+
+class _RoundedRectangleToCircleBorder extends OutlinedBorder {
+  const _RoundedRectangleToCircleBorder({
+    BorderSide side = BorderSide.none,
+    this.borderRadius = BorderRadius.zero,
+    required this.circleness,
+  }) : assert(side != null),
+       assert(borderRadius != null),
+       assert(circleness != null),
+       super(side: side);
+
+  final BorderRadiusGeometry borderRadius;
+
+  final double circleness;
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    return EdgeInsets.all(side.width);
+  }
+
+  @override
+  ShapeBorder scale(double t) {
+    return _RoundedRectangleToCircleBorder(
+      side: side.scale(t),
+      borderRadius: borderRadius * t,
+      circleness: t,
+    );
+  }
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    assert(t != null);
+    if (a is RoundedRectangleBorder) {
+      return _RoundedRectangleToCircleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
+        circleness: circleness * t,
+      );
+    }
+    if (a is CircleBorder) {
+      return _RoundedRectangleToCircleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        borderRadius: borderRadius,
+        circleness: circleness + (1.0 - circleness) * (1.0 - t),
+      );
+    }
+    if (a is _RoundedRectangleToCircleBorder) {
+      return _RoundedRectangleToCircleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
+        circleness: ui.lerpDouble(a.circleness, circleness, t)!,
+      );
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    if (b is RoundedRectangleBorder) {
+      return _RoundedRectangleToCircleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
+        circleness: circleness * (1.0 - t),
+      );
+    }
+    if (b is CircleBorder) {
+      return _RoundedRectangleToCircleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        borderRadius: borderRadius,
+        circleness: circleness + (1.0 - circleness) * t,
+      );
+    }
+    if (b is _RoundedRectangleToCircleBorder) {
+      return _RoundedRectangleToCircleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
+        circleness: ui.lerpDouble(circleness, b.circleness, t)!,
+      );
+    }
+    return super.lerpTo(b, t);
+  }
+
+  Rect _adjustRect(Rect rect) {
+    if (circleness == 0.0 || rect.width == rect.height)
+      return rect;
+    if (rect.width < rect.height) {
+      final double delta = circleness * (rect.height - rect.width) / 2.0;
+      return Rect.fromLTRB(
+        rect.left,
+        rect.top + delta,
+        rect.right,
+        rect.bottom - delta,
+      );
+    } else {
+      final double delta = circleness * (rect.width - rect.height) / 2.0;
+      return Rect.fromLTRB(
+        rect.left + delta,
+        rect.top,
+        rect.right - delta,
+        rect.bottom,
+      );
+    }
+  }
+
+  BorderRadius? _adjustBorderRadius(Rect rect, TextDirection? textDirection) {
+    final BorderRadius resolvedRadius = borderRadius.resolve(textDirection);
+    if (circleness == 0.0)
+      return resolvedRadius;
+    return BorderRadius.lerp(resolvedRadius, BorderRadius.circular(rect.shortestSide / 2.0), circleness);
+  }
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()
+      ..addRRect(_adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect)).deflate(side.width));
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()
+      ..addRRect(_adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect)));
+  }
+
+  @override
+  _RoundedRectangleToCircleBorder copyWith({ BorderSide? side, BorderRadius? borderRadius, double? circleness }) {
+    return _RoundedRectangleToCircleBorder(
+      side: side ?? this.side,
+      borderRadius: borderRadius ?? this.borderRadius,
+      circleness: circleness ?? this.circleness,
+    );
+  }
+
+  @override
+  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
+    switch (side.style) {
+      case BorderStyle.none:
+        break;
+      case BorderStyle.solid:
+        final double width = side.width;
+        if (width == 0.0) {
+          canvas.drawRRect(_adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect)), side.toPaint());
+        } else {
+          final RRect outer = _adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect));
+          final RRect inner = outer.deflate(width);
+          final Paint paint = Paint()
+            ..color = side.color;
+          canvas.drawDRRect(outer, inner, paint);
+        }
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _RoundedRectangleToCircleBorder
+        && other.side == side
+        && other.borderRadius == borderRadius
+        && other.circleness == circleness;
+  }
+
+  @override
+  int get hashCode => hashValues(side, borderRadius, circleness);
+
+  @override
+  String toString() {
+    return 'RoundedRectangleBorder($side, $borderRadius, ${(circleness * 100).toStringAsFixed(1)}% of the way to being a CircleBorder)';
+  }
+}
diff --git a/lib/src/painting/shader_warm_up.dart b/lib/src/painting/shader_warm_up.dart
new file mode 100644
index 0000000..9c8e5e3
--- /dev/null
+++ b/lib/src/painting/shader_warm_up.dart
@@ -0,0 +1,219 @@
+// Copyright 2014 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.
+
+
+import 'dart:developer';
+import 'package:flute/ui.dart' as ui;
+
+import 'package:flute/foundation.dart';
+
+/// Interface for drawing an image to warm up Skia shader compilations.
+///
+/// When Skia first sees a certain type of draw operation on the GPU, it needs
+/// to compile the corresponding shader. The compilation can be slow (20ms-
+/// 200ms). Having that time as startup latency is often better than having
+/// jank in the middle of an animation.
+///
+/// Therefore, we use this during the [PaintingBinding.initInstances] call to
+/// move common shader compilations from animation time to startup time. By
+/// default, a [DefaultShaderWarmUp] is used. If needed, app developers can
+/// create a custom [ShaderWarmUp] subclass and hand it to
+/// [PaintingBinding.shaderWarmUp] (so it replaces [DefaultShaderWarmUp])
+/// before [PaintingBinding.initInstances] is called. Usually, that can be
+/// done before calling [runApp].
+///
+/// To determine whether a draw operation is useful for warming up shaders,
+/// check whether it improves the slowest frame rasterization time. Also,
+/// tracing with `flutter run --profile --trace-skia` may reveal whether
+/// there is shader-compilation-related jank. If there is such jank, some long
+/// `GrGLProgramBuilder::finalize` calls would appear in the middle of an
+/// animation. Their parent calls, which look like `XyzOp` (e.g., `FillRecOp`,
+/// `CircularRRectOp`) would suggest Xyz draw operations are causing the
+/// shaders to be compiled. A useful shader warm-up draw operation would
+/// eliminate such long compilation calls in the animation. To double-check
+/// the warm-up, trace with
+/// `flutter run --profile --trace-skia --start-paused`.
+/// The `GrGLProgramBuilder` with the associated `XyzOp` should
+/// appear during startup rather than in the middle of a later animation.
+
+///
+/// This warm-up needs to be run on each individual device because the shader
+/// compilation depends on the specific GPU hardware and driver a device has. It
+/// can't be pre-computed during the Flutter engine compilation as the engine is
+/// device-agnostic.
+///
+/// If no warm-up is desired (e.g., when the startup latency is crucial), set
+/// [PaintingBinding.shaderWarmUp] either to a custom ShaderWarmUp with an empty
+/// [warmUpOnCanvas] or null.
+///
+/// See also:
+///
+///  * [PaintingBinding.shaderWarmUp], the actual instance of [ShaderWarmUp]
+///    that's used to warm up the shaders.
+abstract class ShaderWarmUp {
+  /// Allow const constructors for subclasses.
+  const ShaderWarmUp();
+
+  /// The size of the warm up image.
+  ///
+  /// The exact size shouldn't matter much as long as all draws are onscreen.
+  /// 100x100 is an arbitrary small size that's easy to fit significant draw
+  /// calls onto.
+  ///
+  /// A custom shader warm up can override this based on targeted devices.
+  ui.Size get size => const ui.Size(100.0, 100.0);
+
+  /// Trigger draw operations on a given canvas to warm up GPU shader
+  /// compilation cache.
+  ///
+  /// To decide which draw operations to be added to your custom warm up
+  /// process, try capture an skp using
+  /// `flutter screenshot --observatory-uri=<uri> --type=skia`
+  /// and analyze it with https://debugger.skia.org.
+  /// Alternatively, one may run the app with `flutter run --trace-skia` and
+  /// then examine the raster thread in the observatory timeline to see which
+  /// Skia draw operations are commonly used, and which shader compilations
+  /// are causing jank.
+  @protected
+  Future<void> warmUpOnCanvas(ui.Canvas canvas);
+
+  /// Construct an offscreen image of [size], and execute [warmUpOnCanvas] on a
+  /// canvas associated with that image.
+  Future<void> execute() async {
+    final ui.PictureRecorder recorder = ui.PictureRecorder();
+    final ui.Canvas canvas = ui.Canvas(recorder);
+
+    await warmUpOnCanvas(canvas);
+
+    final ui.Picture picture = recorder.endRecording();
+    final TimelineTask shaderWarmUpTask = TimelineTask();
+    shaderWarmUpTask.start('Warm-up shader');
+    // Picture.toImage is not yet implemented on the web.
+    if (!kIsWeb) {
+      await picture.toImage(size.width.ceil(), size.height.ceil());
+    }
+    shaderWarmUpTask.finish();
+  }
+}
+
+/// Default way of warming up Skia shader compilations.
+///
+/// The draw operations being warmed up here are decided according to Flutter
+/// engineers' observation and experience based on the apps and the performance
+/// issues seen so far.
+class DefaultShaderWarmUp extends ShaderWarmUp {
+  /// Allow [DefaultShaderWarmUp] to be used as the default value of parameters.
+  const DefaultShaderWarmUp({
+    this.drawCallSpacing = 0.0,
+    this.canvasSize = const ui.Size(100.0, 100.0),
+  });
+
+  /// Constant that can be used to space out draw calls for visualizing the draws
+  /// for debugging purposes (example: 80.0).  Be sure to also change your canvas
+  /// size.
+  final double drawCallSpacing;
+
+  /// Value that returned by this.size to control canvas size where draws happen.
+  final ui.Size canvasSize;
+
+  @override
+  ui.Size get size => canvasSize;
+
+  /// Trigger common draw operations on a canvas to warm up GPU shader
+  /// compilation cache.
+  @override
+  Future<void> warmUpOnCanvas(ui.Canvas canvas) async {
+    const ui.RRect rrect = ui.RRect.fromLTRBXY(20.0, 20.0, 60.0, 60.0, 10.0, 10.0);
+    final ui.Path rrectPath = ui.Path()..addRRect(rrect);
+
+    final ui.Path circlePath = ui.Path()..addOval(
+        ui.Rect.fromCircle(center: const ui.Offset(40.0, 40.0), radius: 20.0)
+    );
+
+    // The following path is based on
+    // https://skia.org/user/api/SkCanvas_Reference#SkCanvas_drawPath
+    final ui.Path path = ui.Path();
+    path.moveTo(20.0, 60.0);
+    path.quadraticBezierTo(60.0, 20.0, 60.0, 60.0);
+    path.close();
+    path.moveTo(60.0, 20.0);
+    path.quadraticBezierTo(60.0, 60.0, 20.0, 60.0);
+
+    final ui.Path convexPath = ui.Path();
+    convexPath.moveTo(20.0, 30.0);
+    convexPath.lineTo(40.0, 20.0);
+    convexPath.lineTo(60.0, 30.0);
+    convexPath.lineTo(60.0, 60.0);
+    convexPath.lineTo(20.0, 60.0);
+    convexPath.close();
+
+    // Skia uses different shaders based on the kinds of paths being drawn and
+    // the associated paint configurations. According to our experience and
+    // tracing, drawing the following paths/paints generates various of
+    // shaders that are commonly used.
+    final List<ui.Path> paths = <ui.Path>[rrectPath, circlePath, path, convexPath];
+
+    final List<ui.Paint> paints = <ui.Paint>[
+      ui.Paint()
+        ..isAntiAlias = true
+        ..style = ui.PaintingStyle.fill,
+      ui.Paint()
+        ..isAntiAlias = false
+        ..style = ui.PaintingStyle.fill,
+      ui.Paint()
+        ..isAntiAlias = true
+        ..style = ui.PaintingStyle.stroke
+        ..strokeWidth = 10,
+      ui.Paint()
+        ..isAntiAlias = true
+        ..style = ui.PaintingStyle.stroke
+        ..strokeWidth = 0.1,  // hairline
+    ];
+
+    // Warm up path stroke and fill shaders.
+    for (int i = 0; i < paths.length; i += 1) {
+      canvas.save();
+      for (final ui.Paint paint in paints) {
+        canvas.drawPath(paths[i], paint);
+        canvas.translate(drawCallSpacing, 0.0);
+      }
+      canvas.restore();
+      canvas.translate(0.0, drawCallSpacing);
+    }
+
+    // Warm up shadow shaders.
+    const ui.Color black = ui.Color(0xFF000000);
+    canvas.save();
+    canvas.drawShadow(rrectPath, black, 10.0, true);
+    canvas.translate(drawCallSpacing, 0.0);
+    canvas.drawShadow(rrectPath, black, 10.0, false);
+    canvas.restore();
+
+    // Warm up text shaders.
+    canvas.translate(0.0, drawCallSpacing);
+    final ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(
+      ui.ParagraphStyle(textDirection: ui.TextDirection.ltr),
+    )..pushStyle(ui.TextStyle(color: black))..addText('_');
+    final ui.Paragraph paragraph = paragraphBuilder.build()
+      ..layout(const ui.ParagraphConstraints(width: 60.0));
+    canvas.drawParagraph(paragraph, const ui.Offset(20.0, 20.0));
+
+    // Draw a rect inside a rrect with a non-trivial intersection. If the
+    // intersection is trivial (e.g., equals the rrect clip), Skia will optimize
+    // the clip out.
+    //
+    // Add an integral or fractional translation to trigger Skia's non-AA or AA
+    // optimizations (as did before in normal FillRectOp in rrect clip cases).
+    for (final double fraction in <double>[0.0, 0.5]) {
+      canvas
+        ..save()
+        ..translate(fraction, fraction)
+        ..clipRRect(ui.RRect.fromLTRBR(8, 8, 328, 248, const ui.Radius.circular(16)))
+        ..drawRect(const ui.Rect.fromLTRB(10, 10, 320, 240), ui.Paint())
+        ..restore();
+      canvas.translate(drawCallSpacing, 0.0);
+    }
+    canvas.translate(0.0, drawCallSpacing);
+  }
+}
diff --git a/lib/src/painting/shape_decoration.dart b/lib/src/painting/shape_decoration.dart
new file mode 100644
index 0000000..c6e91ec
--- /dev/null
+++ b/lib/src/painting/shape_decoration.dart
@@ -0,0 +1,393 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'borders.dart';
+import 'box_border.dart';
+import 'box_decoration.dart';
+import 'box_shadow.dart';
+import 'circle_border.dart';
+import 'colors.dart';
+import 'decoration.dart';
+import 'decoration_image.dart';
+import 'edge_insets.dart';
+import 'gradient.dart';
+import 'image_provider.dart';
+import 'rounded_rectangle_border.dart';
+
+/// An immutable description of how to paint an arbitrary shape.
+///
+/// The [ShapeDecoration] class provides a way to draw a [ShapeBorder],
+/// optionally filling it with a color or a gradient, optionally painting an
+/// image into it, and optionally casting a shadow.
+///
+/// {@tool snippet}
+///
+/// The following example uses the [Container] widget from the widgets layer to
+/// draw a white rectangle with a 24-pixel multicolor outline, with the text
+/// "RGB" inside it:
+///
+/// ```dart
+/// Container(
+///   decoration: ShapeDecoration(
+///     color: Colors.white,
+///     shape: Border.all(
+///       color: Colors.red,
+///       width: 8.0,
+///     ) + Border.all(
+///       color: Colors.green,
+///       width: 8.0,
+///     ) + Border.all(
+///       color: Colors.blue,
+///       width: 8.0,
+///     ),
+///   ),
+///   child: const Text('RGB', textAlign: TextAlign.center),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [DecoratedBox] and [Container], widgets that can be configured with
+///    [ShapeDecoration] objects.
+///  * [BoxDecoration], a similar [Decoration] that is optimized for rectangles
+///    specifically.
+///  * [ShapeBorder], the base class for the objects that are used in the
+///    [shape] property.
+class ShapeDecoration extends Decoration {
+  /// Creates a shape decoration.
+  ///
+  /// * If [color] is null, this decoration does not paint a background color.
+  /// * If [gradient] is null, this decoration does not paint gradients.
+  /// * If [image] is null, this decoration does not paint a background image.
+  /// * If [shadows] is null, this decoration does not paint a shadow.
+  ///
+  /// The [color] and [gradient] properties are mutually exclusive, one (or
+  /// both) of them must be null.
+  ///
+  /// The [shape] must not be null.
+  const ShapeDecoration({
+    this.color,
+    this.image,
+    this.gradient,
+    this.shadows,
+    required this.shape,
+  }) : assert(!(color != null && gradient != null)),
+       assert(shape != null);
+
+  /// Creates a shape decoration configured to match a [BoxDecoration].
+  ///
+  /// The [BoxDecoration] class is more efficient for shapes that it can
+  /// describe than the [ShapeDecoration] class is for those same shapes,
+  /// because [ShapeDecoration] has to be more general as it can support any
+  /// shape. However, having a [ShapeDecoration] is sometimes necessary, for
+  /// example when calling [ShapeDecoration.lerp] to transition between
+  /// different shapes (e.g. from a [CircleBorder] to a
+  /// [RoundedRectangleBorder]; the [BoxDecoration] class cannot animate the
+  /// transition from a [BoxShape.circle] to [BoxShape.rectangle]).
+  factory ShapeDecoration.fromBoxDecoration(BoxDecoration source) {
+    final ShapeBorder shape;
+    assert(source.shape != null);
+    switch (source.shape) {
+      case BoxShape.circle:
+        if (source.border != null) {
+          assert(source.border!.isUniform);
+          shape = CircleBorder(side: source.border!.top);
+        } else {
+          shape = const CircleBorder();
+        }
+        break;
+      case BoxShape.rectangle:
+        if (source.borderRadius != null) {
+          assert(source.border == null || source.border!.isUniform);
+          shape = RoundedRectangleBorder(
+            side: source.border?.top ?? BorderSide.none,
+            borderRadius: source.borderRadius!,
+          );
+        } else {
+          shape = source.border ?? const Border();
+        }
+        break;
+    }
+    return ShapeDecoration(
+      color: source.color,
+      image: source.image,
+      gradient: source.gradient,
+      shadows: source.boxShadow,
+      shape: shape,
+    );
+  }
+
+  @override
+  Path getClipPath(Rect rect, TextDirection textDirection) {
+    return shape.getOuterPath(rect, textDirection: textDirection);
+  }
+
+  /// The color to fill in the background of the shape.
+  ///
+  /// The color is under the [image].
+  ///
+  /// If a [gradient] is specified, [color] must be null.
+  final Color? color;
+
+  /// A gradient to use when filling the shape.
+  ///
+  /// The gradient is under the [image].
+  ///
+  /// If a [color] is specified, [gradient] must be null.
+  final Gradient? gradient;
+
+  /// An image to paint inside the shape (clipped to its outline).
+  ///
+  /// The image is drawn over the [color] or [gradient].
+  final DecorationImage? image;
+
+  /// A list of shadows cast by the [shape].
+  ///
+  /// See also:
+  ///
+  ///  * [kElevationToShadow], for some predefined shadows used in Material
+  ///    Design.
+  ///  * [PhysicalModel], a widget for showing shadows.
+  final List<BoxShadow>? shadows;
+
+  /// The shape to fill the [color], [gradient], and [image] into and to cast as
+  /// the [shadows].
+  ///
+  /// Shapes can be stacked (using the `+` operator). The color, gradient, and
+  /// image are drawn into the inner-most shape specified.
+  ///
+  /// The [shape] property specifies the outline (border) of the decoration. The
+  /// shape must not be null.
+  ///
+  /// ## Directionality-dependent shapes
+  ///
+  /// Some [ShapeBorder] subclasses are sensitive to the [TextDirection]. The
+  /// direction that is provided to the border (e.g. for its [ShapeBorder.paint]
+  /// method) is the one specified in the [ImageConfiguration]
+  /// ([ImageConfiguration.textDirection]) provided to the [BoxPainter] (via its
+  /// [BoxPainter.paint method). The [BoxPainter] is obtained when
+  /// [createBoxPainter] is called.
+  ///
+  /// When a [ShapeDecoration] is used with a [Container] widget or a
+  /// [DecoratedBox] widget (which is what [Container] uses), the
+  /// [TextDirection] specified in the [ImageConfiguration] is obtained from the
+  /// ambient [Directionality], using [createLocalImageConfiguration].
+  final ShapeBorder shape;
+
+  /// The inset space occupied by the [shape]'s border.
+  ///
+  /// This value may be misleading. See the discussion at [ShapeBorder.dimensions].
+  @override
+  EdgeInsetsGeometry get padding => shape.dimensions;
+
+  @override
+  bool get isComplex => shadows != null;
+
+  @override
+  ShapeDecoration? lerpFrom(Decoration? a, double t) {
+    if (a is BoxDecoration) {
+      return ShapeDecoration.lerp(ShapeDecoration.fromBoxDecoration(a), this, t);
+    } else if (a == null || a is ShapeDecoration) {
+      return ShapeDecoration.lerp(a as ShapeDecoration?, this, t);
+    }
+    return super.lerpFrom(a, t) as ShapeDecoration?;
+  }
+
+  @override
+  ShapeDecoration? lerpTo(Decoration? b, double t) {
+    if (b is BoxDecoration) {
+      return ShapeDecoration.lerp(this, ShapeDecoration.fromBoxDecoration(b), t);
+    } else if (b == null || b is ShapeDecoration) {
+      return ShapeDecoration.lerp(this, b as ShapeDecoration?, t);
+    }
+    return super.lerpTo(b, t) as ShapeDecoration?;
+  }
+
+  /// Linearly interpolate between two shapes.
+  ///
+  /// Interpolates each parameter of the decoration separately.
+  ///
+  /// If both values are null, this returns null. Otherwise, it returns a
+  /// non-null value, with null arguments treated like a [ShapeDecoration] whose
+  /// fields are all null (including the [shape], which cannot normally be
+  /// null).
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  ///
+  /// See also:
+  ///
+  ///  * [Decoration.lerp], which can interpolate between any two types of
+  ///    [Decoration]s, not just [ShapeDecoration]s.
+  ///  * [lerpFrom] and [lerpTo], which are used to implement [Decoration.lerp]
+  ///    and which use [ShapeDecoration.lerp] when interpolating two
+  ///    [ShapeDecoration]s or a [ShapeDecoration] to or from null.
+  static ShapeDecoration? lerp(ShapeDecoration? a, ShapeDecoration? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a != null && b != null) {
+      if (t == 0.0)
+        return a;
+      if (t == 1.0)
+        return b;
+    }
+    return ShapeDecoration(
+      color: Color.lerp(a?.color, b?.color, t),
+      gradient: Gradient.lerp(a?.gradient, b?.gradient, t),
+      image: t < 0.5 ? a!.image : b!.image, // TODO(ianh): cross-fade the image
+      shadows: BoxShadow.lerpList(a?.shadows, b?.shadows, t),
+      shape: ShapeBorder.lerp(a?.shape, b?.shape, t)!,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ShapeDecoration
+        && other.color == color
+        && other.gradient == gradient
+        && other.image == image
+        && listEquals<BoxShadow>(other.shadows, shadows)
+        && other.shape == shape;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      color,
+      gradient,
+      image,
+      shape,
+      hashList(shadows),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.whitespace;
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(DiagnosticsProperty<Gradient>('gradient', gradient, defaultValue: null));
+    properties.add(DiagnosticsProperty<DecorationImage>('image', image, defaultValue: null));
+    properties.add(IterableProperty<BoxShadow>('shadows', shadows, defaultValue: null, style: DiagnosticsTreeStyle.whitespace));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape));
+  }
+
+  @override
+  bool hitTest(Size size, Offset position, { TextDirection? textDirection }) {
+    return shape.getOuterPath(Offset.zero & size, textDirection: textDirection).contains(position);
+  }
+
+  @override
+  _ShapeDecorationPainter createBoxPainter([ VoidCallback? onChanged ]) {
+    assert(onChanged != null || image == null);
+    return _ShapeDecorationPainter(this, onChanged!);
+  }
+}
+
+/// An object that paints a [ShapeDecoration] into a canvas.
+class _ShapeDecorationPainter extends BoxPainter {
+  _ShapeDecorationPainter(this._decoration, VoidCallback onChanged)
+    : assert(_decoration != null),
+      super(onChanged);
+
+  final ShapeDecoration _decoration;
+
+  Rect? _lastRect;
+  TextDirection? _lastTextDirection;
+  late Path _outerPath;
+  Path? _innerPath;
+  Paint? _interiorPaint;
+  int? _shadowCount;
+  late List<Path> _shadowPaths;
+  late List<Paint> _shadowPaints;
+
+  @override
+  VoidCallback get onChanged => super.onChanged!;
+
+  void _precache(Rect rect, TextDirection? textDirection) {
+    assert(rect != null);
+    if (rect == _lastRect && textDirection == _lastTextDirection)
+      return;
+
+    // We reach here in two cases:
+    //  - the very first time we paint, in which case everything except _decoration is null
+    //  - subsequent times, if the rect has changed, in which case we only need to update
+    //    the features that depend on the actual rect.
+    if (_interiorPaint == null && (_decoration.color != null || _decoration.gradient != null)) {
+      _interiorPaint = Paint();
+      if (_decoration.color != null)
+        _interiorPaint!.color = _decoration.color!;
+    }
+    if (_decoration.gradient != null)
+      _interiorPaint!.shader = _decoration.gradient!.createShader(rect);
+    if (_decoration.shadows != null) {
+      if (_shadowCount == null) {
+        _shadowCount = _decoration.shadows!.length;
+        _shadowPaints = <Paint>[
+          ..._decoration.shadows!.map((BoxShadow shadow) => shadow.toPaint()),
+        ];
+      }
+      _shadowPaths = <Path>[
+        ..._decoration.shadows!.map((BoxShadow shadow) {
+          return _decoration.shape.getOuterPath(rect.shift(shadow.offset).inflate(shadow.spreadRadius), textDirection: textDirection);
+        }),
+      ];
+    }
+    if (_interiorPaint != null || _shadowCount != null)
+      _outerPath = _decoration.shape.getOuterPath(rect, textDirection: textDirection);
+    if (_decoration.image != null)
+      _innerPath = _decoration.shape.getInnerPath(rect, textDirection: textDirection);
+
+    _lastRect = rect;
+    _lastTextDirection = textDirection;
+  }
+
+  void _paintShadows(Canvas canvas) {
+    if (_shadowCount != null) {
+      for (int index = 0; index < _shadowCount!; index += 1)
+        canvas.drawPath(_shadowPaths[index], _shadowPaints[index]);
+    }
+  }
+
+  void _paintInterior(Canvas canvas) {
+    if (_interiorPaint != null)
+      canvas.drawPath(_outerPath, _interiorPaint!);
+  }
+
+  DecorationImagePainter? _imagePainter;
+  void _paintImage(Canvas canvas, ImageConfiguration configuration) {
+    if (_decoration.image == null)
+      return;
+    _imagePainter ??= _decoration.image!.createPainter(onChanged);
+    _imagePainter!.paint(canvas, _lastRect!, _innerPath, configuration);
+  }
+
+  @override
+  void dispose() {
+    _imagePainter?.dispose();
+    super.dispose();
+  }
+
+  @override
+  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
+    assert(configuration != null);
+    assert(configuration.size != null);
+    final Rect rect = offset & configuration.size!;
+    final TextDirection? textDirection = configuration.textDirection;
+    _precache(rect, textDirection);
+    _paintShadows(canvas);
+    _paintInterior(canvas);
+    _paintImage(canvas, configuration);
+    _decoration.shape.paint(canvas, rect, textDirection: textDirection);
+  }
+}
diff --git a/lib/src/painting/stadium_border.dart b/lib/src/painting/stadium_border.dart
new file mode 100644
index 0000000..deb1b65
--- /dev/null
+++ b/lib/src/painting/stadium_border.dart
@@ -0,0 +1,440 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' as ui show lerpDouble;
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'border_radius.dart';
+import 'borders.dart';
+import 'circle_border.dart';
+import 'edge_insets.dart';
+import 'rounded_rectangle_border.dart';
+
+/// A border that fits a stadium-shaped border (a box with semicircles on the ends)
+/// within the rectangle of the widget it is applied to.
+///
+/// Typically used with [ShapeDecoration] to draw a stadium border.
+///
+/// If the rectangle is taller than it is wide, then the semicircles will be on the
+/// top and bottom, and on the left and right otherwise.
+///
+/// See also:
+///
+///  * [BorderSide], which is used to describe the border of the stadium.
+class StadiumBorder extends OutlinedBorder {
+  /// Create a stadium border.
+  ///
+  /// The [side] argument must not be null.
+  const StadiumBorder({ BorderSide side = BorderSide.none }) : assert(side != null), super(side: side);
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    return EdgeInsets.all(side.width);
+  }
+
+  @override
+  ShapeBorder scale(double t) => StadiumBorder(side: side.scale(t));
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    assert(t != null);
+    if (a is StadiumBorder)
+      return StadiumBorder(side: BorderSide.lerp(a.side, side, t));
+    if (a is CircleBorder) {
+      return _StadiumToCircleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        circleness: 1.0 - t,
+      );
+    }
+    if (a is RoundedRectangleBorder) {
+      return _StadiumToRoundedRectangleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        borderRadius: a.borderRadius as BorderRadius,
+        rectness: 1.0 - t,
+      );
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    assert(t != null);
+    if (b is StadiumBorder)
+      return StadiumBorder(side: BorderSide.lerp(side, b.side, t));
+    if (b is CircleBorder) {
+      return _StadiumToCircleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        circleness: t,
+      );
+    }
+    if (b is RoundedRectangleBorder) {
+      return _StadiumToRoundedRectangleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        borderRadius: b.borderRadius as BorderRadius,
+        rectness: t,
+      );
+    }
+    return super.lerpTo(b, t);
+  }
+
+  @override
+  StadiumBorder copyWith({ BorderSide? side }) {
+    return StadiumBorder(side: side ?? this.side);
+  }
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    final Radius radius = Radius.circular(rect.shortestSide / 2.0);
+    return Path()
+      ..addRRect(RRect.fromRectAndRadius(rect, radius).deflate(side.width));
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    final Radius radius = Radius.circular(rect.shortestSide / 2.0);
+    return Path()
+      ..addRRect(RRect.fromRectAndRadius(rect, radius));
+  }
+
+  @override
+  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
+    switch (side.style) {
+      case BorderStyle.none:
+        break;
+      case BorderStyle.solid:
+        final Radius radius = Radius.circular(rect.shortestSide / 2.0);
+        canvas.drawRRect(
+          RRect.fromRectAndRadius(rect, radius).deflate(side.width / 2.0),
+          side.toPaint(),
+        );
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is StadiumBorder
+        && other.side == side;
+  }
+
+  @override
+  int get hashCode => side.hashCode;
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'StadiumBorder')}($side)';
+  }
+}
+
+// Class to help with transitioning to/from a CircleBorder.
+class _StadiumToCircleBorder extends OutlinedBorder {
+  const _StadiumToCircleBorder({
+    BorderSide side = BorderSide.none,
+    this.circleness = 0.0,
+  }) : assert(side != null),
+       assert(circleness != null),
+       super(side: side);
+
+  final double circleness;
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    return EdgeInsets.all(side.width);
+  }
+
+  @override
+  ShapeBorder scale(double t) {
+    return _StadiumToCircleBorder(
+      side: side.scale(t),
+      circleness: t,
+    );
+  }
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    assert(t != null);
+    if (a is StadiumBorder) {
+      return _StadiumToCircleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        circleness: circleness * t,
+      );
+    }
+    if (a is CircleBorder) {
+      return _StadiumToCircleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        circleness: circleness + (1.0 - circleness) * (1.0 - t),
+      );
+    }
+    if (a is _StadiumToCircleBorder) {
+      return _StadiumToCircleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        circleness: ui.lerpDouble(a.circleness, circleness, t)!,
+      );
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    assert(t != null);
+    if (b is StadiumBorder) {
+      return _StadiumToCircleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        circleness: circleness * (1.0 - t),
+      );
+    }
+    if (b is CircleBorder) {
+      return _StadiumToCircleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        circleness: circleness + (1.0 - circleness) * t,
+      );
+    }
+    if (b is _StadiumToCircleBorder) {
+      return _StadiumToCircleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        circleness: ui.lerpDouble(circleness, b.circleness, t)!,
+      );
+    }
+    return super.lerpTo(b, t);
+  }
+
+  Rect _adjustRect(Rect rect) {
+    if (circleness == 0.0 || rect.width == rect.height)
+      return rect;
+    if (rect.width < rect.height) {
+      final double delta = circleness * (rect.height - rect.width) / 2.0;
+      return Rect.fromLTRB(
+        rect.left,
+        rect.top + delta,
+        rect.right,
+        rect.bottom - delta,
+      );
+    } else {
+      final double delta = circleness * (rect.width - rect.height) / 2.0;
+      return Rect.fromLTRB(
+        rect.left + delta,
+        rect.top,
+        rect.right - delta,
+        rect.bottom,
+      );
+    }
+  }
+
+  BorderRadius _adjustBorderRadius(Rect rect) {
+    return BorderRadius.circular(rect.shortestSide / 2.0);
+  }
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()
+      ..addRRect(_adjustBorderRadius(rect).toRRect(_adjustRect(rect)).deflate(side.width));
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()
+      ..addRRect(_adjustBorderRadius(rect).toRRect(_adjustRect(rect)));
+  }
+
+  @override
+  _StadiumToCircleBorder copyWith({ BorderSide? side, double? circleness }) {
+    return _StadiumToCircleBorder(
+      side: side ?? this.side,
+      circleness: circleness ?? this.circleness,
+    );
+  }
+
+  @override
+  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
+    switch (side.style) {
+      case BorderStyle.none:
+        break;
+      case BorderStyle.solid:
+        final double width = side.width;
+        if (width == 0.0) {
+          canvas.drawRRect(_adjustBorderRadius(rect).toRRect(_adjustRect(rect)), side.toPaint());
+        } else {
+          final RRect outer = _adjustBorderRadius(rect).toRRect(_adjustRect(rect));
+          final RRect inner = outer.deflate(width);
+          final Paint paint = Paint()
+            ..color = side.color;
+          canvas.drawDRRect(outer, inner, paint);
+        }
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _StadiumToCircleBorder
+        && other.side == side
+        && other.circleness == circleness;
+  }
+
+  @override
+  int get hashCode => hashValues(side, circleness);
+
+  @override
+  String toString() {
+    return 'StadiumBorder($side, ${(circleness * 100).toStringAsFixed(1)}% '
+           'of the way to being a CircleBorder)';
+  }
+}
+
+// Class to help with transitioning to/from a RoundedRectBorder.
+class _StadiumToRoundedRectangleBorder extends OutlinedBorder {
+  const _StadiumToRoundedRectangleBorder({
+    BorderSide side = BorderSide.none,
+    this.borderRadius = BorderRadius.zero,
+    this.rectness = 0.0,
+  }) : assert(side != null),
+       assert(borderRadius != null),
+       assert(rectness != null),
+       super(side: side);
+
+  final BorderRadius borderRadius;
+
+  final double rectness;
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    return EdgeInsets.all(side.width);
+  }
+
+  @override
+  ShapeBorder scale(double t) {
+    return _StadiumToRoundedRectangleBorder(
+      side: side.scale(t),
+      borderRadius: borderRadius * t,
+      rectness: t,
+    );
+  }
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    assert(t != null);
+    if (a is StadiumBorder) {
+      return _StadiumToRoundedRectangleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        borderRadius: borderRadius,
+        rectness: rectness * t,
+      );
+    }
+    if (a is RoundedRectangleBorder) {
+      return _StadiumToRoundedRectangleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        borderRadius: borderRadius,
+        rectness: rectness + (1.0 - rectness) * (1.0 - t),
+      );
+    }
+    if (a is _StadiumToRoundedRectangleBorder) {
+      return _StadiumToRoundedRectangleBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        borderRadius: BorderRadius.lerp(a.borderRadius, borderRadius, t)!,
+        rectness: ui.lerpDouble(a.rectness, rectness, t)!,
+      );
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    assert(t != null);
+    if (b is StadiumBorder) {
+      return _StadiumToRoundedRectangleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        borderRadius: borderRadius,
+        rectness: rectness * (1.0 - t),
+      );
+    }
+    if (b is RoundedRectangleBorder) {
+      return _StadiumToRoundedRectangleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        borderRadius: borderRadius,
+        rectness: rectness + (1.0 - rectness) * t,
+      );
+    }
+    if (b is _StadiumToRoundedRectangleBorder) {
+      return _StadiumToRoundedRectangleBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        borderRadius: BorderRadius.lerp(borderRadius, b.borderRadius, t)!,
+        rectness: ui.lerpDouble(rectness, b.rectness, t)!,
+      );
+    }
+    return super.lerpTo(b, t);
+  }
+
+  BorderRadius _adjustBorderRadius(Rect rect) {
+    return BorderRadius.lerp(
+      borderRadius,
+      BorderRadius.all(Radius.circular(rect.shortestSide / 2.0)),
+      1.0 - rectness,
+    )!;
+  }
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()
+      ..addRRect(_adjustBorderRadius(rect).toRRect(rect).deflate(side.width));
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()
+      ..addRRect(_adjustBorderRadius(rect).toRRect(rect));
+  }
+
+  @override
+  _StadiumToRoundedRectangleBorder copyWith({ BorderSide? side, BorderRadius? borderRadius, double? rectness }) {
+    return _StadiumToRoundedRectangleBorder(
+      side: side ?? this.side,
+      borderRadius: borderRadius ?? this.borderRadius,
+      rectness: rectness ?? this.rectness
+    );
+  }
+
+  @override
+  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
+    switch (side.style) {
+      case BorderStyle.none:
+        break;
+      case BorderStyle.solid:
+        final double width = side.width;
+        if (width == 0.0) {
+          canvas.drawRRect(_adjustBorderRadius(rect).toRRect(rect), side.toPaint());
+        } else {
+          final RRect outer = _adjustBorderRadius(rect).toRRect(rect);
+          final RRect inner = outer.deflate(width);
+          final Paint paint = Paint()
+            ..color = side.color;
+          canvas.drawDRRect(outer, inner, paint);
+        }
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _StadiumToRoundedRectangleBorder
+        && other.side == side
+        && other.borderRadius == borderRadius
+        && other.rectness == rectness;
+  }
+
+  @override
+  int get hashCode => hashValues(side, borderRadius, rectness);
+
+  @override
+  String toString() {
+    return 'StadiumBorder($side, $borderRadius, '
+           '${(rectness * 100).toStringAsFixed(1)}% of the way to being a '
+           'RoundedRectangleBorder)';
+  }
+}
diff --git a/lib/src/painting/strut_style.dart b/lib/src/painting/strut_style.dart
new file mode 100644
index 0000000..bbd1f8c
--- /dev/null
+++ b/lib/src/painting/strut_style.dart
@@ -0,0 +1,611 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'text_style.dart';
+
+/// Defines the strut, which sets the minimum height a line can be
+/// relative to the baseline.
+///
+/// Strut applies to all lines in the paragraph. Strut is a feature that
+/// allows minimum line heights to be set. The effect is as if a zero
+/// width space was included at the beginning of each line in the
+/// paragraph. This imaginary space is 'shaped' according the properties
+/// defined in this class. Flutter's strut is based on
+/// [typesetting strut](https://en.wikipedia.org/wiki/Strut_(typesetting))
+/// and CSS's [line-height](https://www.w3.org/TR/CSS2/visudet.html#line-height).
+///
+/// No lines may be shorter than the strut. The ascent and descent of the
+/// strut are calculated, and any laid out text that has a shorter ascent or
+/// descent than the strut's ascent or descent will take the ascent and
+/// descent of the strut. Text with ascents or descents larger than the
+/// strut's ascent or descent will layout as normal and extend past the strut.
+///
+/// Strut is defined independently from any text content or [TextStyle]s.
+///
+/// The vertical components of strut are as follows:
+///
+///  * Half the font-defined leading
+///  * `ascent * height`
+///  * `descent * height`
+///  * Half the font-defined leading
+///
+/// The sum of these four values is the total height of the line.
+///
+/// Ascent is the font's spacing above the baseline without leading and
+/// descent is the spacing below the baseline without leading. Leading is
+/// split evenly between the top and bottom. The values for `ascent` and
+/// `descent` are provided by the font named by [fontFamily]. If no
+/// [fontFamily] or [fontFamilyFallback] is provided, then the platform's
+/// default family will be used. Many fonts will have leading values of
+/// zero, so in practice, the leading component is often irrelevant.
+///
+/// When [height] is omitted or null, then the font defined ascent and descent
+/// will be used. The font's combined ascent and descent may be taller or
+/// shorter than the [fontSize]. When [height] is provided, the line's EM-square
+/// ascent and descent (which sums to [fontSize]) will be scaled by [height] to
+/// achieve a final line height of `fontSize * height + fontSize * leading`
+/// logical pixels. The proportion of ascent:descent with [height] specified
+/// is the same as the font metrics defined ascent:descent ratio.
+///
+/// ![Text height diagram](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_diagram.png)
+///
+/// Each line's spacing above the baseline will be at least as tall as the
+/// half leading plus ascent. Each line's spacing below the baseline will
+/// be at least as tall as the half leading plus descent.
+///
+/// See also:
+///
+///  * [StrutStyle](dart-ui/StrutStyle-class.html), the class in the [dart:ui] library.
+///
+/// ### Fields and their default values.
+
+// ///////////////////////////////////////////////////////////////////////////
+// The defaults are noted here for convenience. The actual place where they //
+// are defined is in the engine paragraph_style.h of LibTxt. The values here//
+// should be updated should it change in the engine. The engine specifies   //
+// the defaults in order to reduce the amount of data we pass to native as  //
+// strut will usually be unspecified.                                       //
+// ///////////////////////////////////////////////////////////////////////////
+
+///
+/// Omitted or null properties will take the default values specified below:
+///
+///  * [fontFamily]: the name of the font to use when calculating the strut
+///    (e.g., Roboto). No glyphs from the font will be drawn and the font will
+///    be used purely for metrics.
+///
+///  * [fontFamilyFallback]: an ordered list of font family names that will
+///    be searched for when the font in [fontFamily] cannot be found. When
+///    all specified font families have been exhausted an no match was found,
+///    the default platform font will be used.
+///
+///  * [fontSize]: the size of the ascent plus descent in logical pixels. This
+///    is also used as the basis of the custom leading calculation. This value
+///    cannot be negative.
+///    Default is 14 logical pixels.
+///
+///  * [height]: the multiple of [fontSize] the line's height should be.
+///    The line's height will take the font's ascent and descent values if
+///    [height] is omitted or null. If provided, the EM-square ascent and
+///    descent (which sum to [fontSize]) is scaled by [height].
+///    The [height] will impact the spacing above and below the baseline
+///    differently depending on the ratios between the font's ascent and
+///    descent. This property is separate from the leading multiplier, which
+///    is controlled through [leading].
+///    Default is null.
+///
+///  * [leading]: the custom leading to apply to the strut as a multiple of
+///    [fontSize]. Leading is additional spacing between lines. Half of the
+///    leading is added to the top and the other half to the bottom of the
+///    line height. This differs from [height] since the spacing is equally
+///    distributed above and below the baseline.
+///    Default is null, which will use the font-specified leading.
+///
+///  * [fontWeight]: the typeface thickness to use when calculating the strut
+///    (e.g., bold).
+///    Default is [FontWeight.w400].
+///
+///  * [fontStyle]: the typeface variant to use when calculating the strut
+///    (e.g., italic).
+///    Default is [FontStyle.normal].
+///
+///  * [forceStrutHeight]: when true, all lines will be laid out with the
+///    height of the strut. All line and run-specific metrics will be
+///    ignored/overridden and only strut metrics will be used instead.
+///    This property guarantees uniform line spacing, however text in
+///    adjacent lines may overlap. This property should be enabled with
+///    caution as it bypasses a large portion of the vertical layout system.
+///    The default value is false.
+///
+/// ### Examples
+///
+/// {@tool snippet}
+/// In this simple case, the text will be rendered at font size 10, however,
+/// the vertical height of each line will be the strut height (Roboto in
+/// font size 30 * 1.5) as the text itself is shorter than the strut.
+///
+/// ```dart
+/// const Text(
+///   'Hello, world!\nSecond line!',
+///   style: TextStyle(
+///     fontSize: 10,
+///     fontFamily: 'Raleway',
+///   ),
+///   strutStyle: StrutStyle(
+///     fontFamily: 'Roboto',
+///     fontSize: 30,
+///     height: 1.5,
+///   ),
+/// ),
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+/// Here, strut is used to absorb the additional line height in the second line.
+/// The strut [height] was defined as 1.5 (the default font size is 14), which
+/// caused all lines to be laid out taller than without strut. This extra space
+/// was able to accommodate the larger font size of `Second line!` without
+/// causing the line height to change for the second line only. All lines in
+/// this example are thus the same height (`14 * 1.5`).
+///
+/// ```dart
+/// const Text.rich(
+///   TextSpan(
+///     text: 'First line!\n',
+///     style: TextStyle(
+///       fontSize: 14,
+///       fontFamily: 'Roboto'
+///     ),
+///     children: <TextSpan>[
+///       TextSpan(
+///         text: 'Second line!\n',
+///         style: TextStyle(
+///           fontSize: 16,
+///           fontFamily: 'Roboto',
+///         ),
+///       ),
+///       TextSpan(
+///         text: 'Third line!\n',
+///         style: TextStyle(
+///           fontSize: 14,
+///           fontFamily: 'Roboto',
+///         ),
+///       ),
+///     ],
+///   ),
+///   strutStyle: StrutStyle(
+///     fontFamily: 'Roboto',
+///     height: 1.5,
+///   ),
+/// ),
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+/// Here, strut is used to enable strange and overlapping text to achieve unique
+/// effects. The `M`s in lines 2 and 3 are able to extend above their lines and
+/// fill empty space in lines above. The [forceStrutHeight] is enabled and functions
+/// as a 'grid' for the glyphs to draw on.
+///
+/// ![The result of the example below.](https://flutter.github.io/assets-for-api-docs/assets/painting/strut_force_example.png)
+///
+/// ```dart
+/// const Text.rich(
+///   TextSpan(
+///     text: '---------         ---------\n',
+///     style: TextStyle(
+///       fontSize: 14,
+///       fontFamily: 'Roboto',
+///     ),
+///     children: <TextSpan>[
+///       TextSpan(
+///         text: '^^^M^^^\n',
+///         style: TextStyle(
+///           fontSize: 30,
+///           fontFamily: 'Roboto',
+///         ),
+///       ),
+///       TextSpan(
+///         text: 'M------M\n',
+///         style: TextStyle(
+///           fontSize: 30,
+///           fontFamily: 'Roboto',
+///         ),
+///       ),
+///     ],
+///   ),
+///   strutStyle: StrutStyle(
+///     fontFamily: 'Roboto',
+///     fontSize: 14,
+///     height: 1,
+///     forceStrutHeight: true,
+///   ),
+/// ),
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+/// This example uses forceStrutHeight to create a 'drop cap' for the 'T' in 'The'.
+/// By locking the line heights to the metrics of the 14pt serif font, we are able
+/// to lay out a large 37pt 'T' on the second line to take up space on both the first
+/// and second lines.
+///
+/// ![The result of the example below.](https://flutter.github.io/assets-for-api-docs/assets/painting/strut_force_example_2.png)
+///
+/// ```dart
+/// Text.rich(
+///   TextSpan(
+///     text: '       he candle flickered\n',
+///     style: TextStyle(
+///       fontSize: 14,
+///       fontFamily: 'Serif'
+///     ),
+///     children: <TextSpan>[
+///       TextSpan(
+///         text: 'T',
+///         style: TextStyle(
+///           fontSize: 37,
+///           fontFamily: 'Serif'
+///         ),
+///       ),
+///       TextSpan(
+///         text: 'in the moonlight as\n',
+///         style: TextStyle(
+///           fontSize: 14,
+///           fontFamily: 'Serif'
+///         ),
+///       ),
+///       TextSpan(
+///         text: 'Dash the bird fluttered\n',
+///         style: TextStyle(
+///           fontSize: 14,
+///           fontFamily: 'Serif'
+///         ),
+///       ),
+///       TextSpan(
+///         text: 'off into the distance.',
+///         style: TextStyle(
+///           fontSize: 14,
+///           fontFamily: 'Serif'
+///         ),
+///       ),
+///     ],
+///   ),
+///   strutStyle: StrutStyle(
+///     fontFamily: 'Serif',
+///     fontSize: 14,
+///     forceStrutHeight: true,
+///   ),
+/// ),
+/// ```
+/// {@end-tool}
+///
+@immutable
+class StrutStyle with Diagnosticable {
+  /// Creates a strut style.
+  ///
+  /// The `package` argument must be non-null if the font family is defined in a
+  /// package. It is combined with the `fontFamily` argument to set the
+  /// [fontFamily] property.
+  ///
+  /// If provided, fontSize must be positive and non-zero, leading must be
+  /// zero or positive.
+  const StrutStyle({
+    String? fontFamily,
+    List<String>? fontFamilyFallback,
+    this.fontSize,
+    this.height,
+    this.leading,
+    this.fontWeight,
+    this.fontStyle,
+    this.forceStrutHeight,
+    this.debugLabel,
+    String? package,
+  }) : fontFamily = package == null ? fontFamily : 'packages/$package/$fontFamily',
+       _fontFamilyFallback = fontFamilyFallback,
+       _package = package,
+       assert(fontSize == null || fontSize > 0),
+       assert(leading == null || leading >= 0),
+       assert(package == null || (fontFamily != null || fontFamilyFallback != null));
+
+  /// Builds a StrutStyle that contains values of the equivalent properties in
+  /// the provided [textStyle].
+  ///
+  /// The [textStyle] parameter must not be null.
+  ///
+  /// The named parameters override the [textStyle]'s argument's properties.
+  /// Since TextStyle does not contain [leading] or [forceStrutHeight], these
+  /// values will take on default values (null and false) unless otherwise
+  /// specified.
+  ///
+  /// If provided, fontSize must be positive and non-zero, leading must be
+  /// zero or positive.
+  ///
+  /// When [textStyle] has a package and a new [package] is also specified,
+  /// the entire font family fallback list should be redefined since the
+  /// [textStyle]'s package data is inherited by being prepended onto the
+  /// font family names. If [fontFamilyFallback] is meant to be empty, pass
+  /// an empty list instead of null. This prevents the previous package name
+  /// from being prepended twice.
+  StrutStyle.fromTextStyle(
+    TextStyle textStyle, {
+    String? fontFamily,
+    List<String>? fontFamilyFallback,
+    double? fontSize,
+    double? height,
+    this.leading, // TextStyle does not have an equivalent (yet).
+    FontWeight? fontWeight,
+    FontStyle? fontStyle,
+    this.forceStrutHeight,
+    String? debugLabel,
+    String? package,
+  }) : assert(textStyle != null),
+       assert(fontSize == null || fontSize > 0),
+       assert(leading == null || leading >= 0),
+       assert(package == null || fontFamily != null || fontFamilyFallback != null),
+       fontFamily = fontFamily != null ? (package == null ? fontFamily : 'packages/$package/$fontFamily') : textStyle.fontFamily,
+       _fontFamilyFallback = fontFamilyFallback ?? textStyle.fontFamilyFallback,
+       height = height ?? textStyle.height,
+       fontSize = fontSize ?? textStyle.fontSize,
+       fontWeight = fontWeight ?? textStyle.fontWeight,
+       fontStyle = fontStyle ?? textStyle.fontStyle,
+       debugLabel = debugLabel ?? textStyle.debugLabel,
+       _package = package; // the textStyle._package data is embedded in the
+                           // fontFamily names, so we no longer need it.
+
+  /// A [StrutStyle] that will have no impact on the text layout.
+  ///
+  /// Equivalent to having no strut at all. All lines will be laid out according to
+  /// the properties defined in [TextStyle].
+  static const StrutStyle disabled = StrutStyle(
+    height: 0.0,
+    leading: 0.0,
+  );
+
+  /// The name of the font to use when calculating the strut (e.g., Roboto). If
+  /// the font is defined in a package, this will be prefixed with
+  /// 'packages/package_name/' (e.g. 'packages/cool_fonts/Roboto'). The
+  /// prefixing is done by the constructor when the `package` argument is
+  /// provided.
+  ///
+  /// The value provided in [fontFamily] will act as the preferred/first font
+  /// family that will be searched for, followed in order by the font families
+  /// in [fontFamilyFallback]. If all font families are exhausted and no match
+  /// was found, the default platform font family will be used instead. Unlike
+  /// [TextStyle.fontFamilyFallback], the font does not need to contain the
+  /// desired glyphs to match.
+  final String? fontFamily;
+
+  /// The ordered list of font families to fall back on when a higher priority
+  /// font family cannot be found.
+  ///
+  /// The value provided in [fontFamily] will act as the preferred/first font
+  /// family that will be searched for, followed in order by the font families
+  /// in [fontFamilyFallback]. If all font families are exhausted and no match
+  /// was found, the default platform font family will be used instead. Unlike
+  /// [TextStyle.fontFamilyFallback], the font does not need to contain the
+  /// desired glyphs to match.
+  ///
+  /// When [fontFamily] is null or not provided, the first value in [fontFamilyFallback]
+  /// acts as the preferred/first font family. When neither is provided, then
+  /// the default platform font will be used. Providing and empty list or null
+  /// for this property is the same as omitting it.
+  ///
+  /// If the font is defined in a package, each font family in the list will be
+  /// prefixed with 'packages/package_name/' (e.g. 'packages/cool_fonts/Roboto').
+  /// The package name should be provided by the `package` argument in the
+  /// constructor.
+  List<String>? get fontFamilyFallback {
+    if (_package != null && _fontFamilyFallback != null)
+      return _fontFamilyFallback!.map((String family) => 'packages/$_package/$family').toList();
+    return _fontFamilyFallback;
+  }
+  final List<String>? _fontFamilyFallback;
+
+  // This is stored in order to prefix the fontFamilies in _fontFamilyFallback
+  // in the [fontFamilyFallback] getter.
+  final String? _package;
+
+  /// The size of text (in logical pixels) to use when obtaining metrics from the font.
+  ///
+  /// The [fontSize] is used to get the base set of metrics that are then used to calculated
+  /// the metrics of strut. The height and leading are expressed as a multiple of
+  /// [fontSize].
+  ///
+  /// The default fontSize is 14 logical pixels.
+  final double? fontSize;
+
+  /// The multiple of [fontSize] to multiply the ascent and descent by where
+  /// `ascent + descent = fontSize`.
+  ///
+  /// Ascent is the spacing above the baseline and descent is the spacing below
+  /// the baseline.
+  ///
+  /// When [height] is omitted or null, then the font defined ascent and descent
+  /// will be used. The font's combined ascent and descent may be taller or
+  /// shorter than the [fontSize]. When [height] is provided, the line's EM-square
+  /// ascent and descent (which sums to [fontSize]) will be scaled by [height] to
+  /// achieve a final line height of `fontSize * height + fontSize * leading`
+  /// logical pixels. The following diagram illustrates the differences between
+  /// the font metrics defined height and the EM-square height:
+  ///
+  /// ![Text height diagram](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_diagram.png)
+  ///
+  /// The [height] will impact the spacing above and below the baseline differently
+  /// depending on the ratios between the font's ascent and descent. This property is
+  /// separate from the leading multiplier, which is controlled through [leading].
+  ///
+  /// The ratio of ascent:descent with [height] specified is the same as the
+  /// font metrics defined ascent:descent ratio when [height] is null or omitted.
+  ///
+  /// See [TextStyle.height], which works in a similar manner.
+  ///
+  /// The default height is null.
+  final double? height;
+
+  /// The typeface thickness to use when calculating the strut (e.g., bold).
+  ///
+  /// The default fontWeight is [FontWeight.w400].
+  final FontWeight? fontWeight;
+
+  /// The typeface variant to use when calculating the strut (e.g., italics).
+  ///
+  /// The default fontStyle is [FontStyle.normal].
+  final FontStyle? fontStyle;
+
+  /// The custom leading to apply to the strut as a multiple of [fontSize].
+  ///
+  /// Leading is additional spacing between lines. Half of the leading is added
+  /// to the top and the other half to the bottom of the line. This differs
+  /// from [height] since the spacing is equally distributed above and below the
+  /// baseline.
+  ///
+  /// The default leading is null, which will use the font-specified leading.
+  final double? leading;
+
+  /// Whether the strut height should be forced.
+  ///
+  /// When true, all lines will be laid out with the height of the
+  /// strut. All line and run-specific metrics will be ignored/overridden
+  /// and only strut metrics will be used instead. This property guarantees
+  /// uniform line spacing, however text in adjacent lines may overlap.
+  ///
+  /// This property should be enabled with caution as
+  /// it bypasses a large portion of the vertical layout system.
+  ///
+  /// This is equivalent to setting [TextStyle.height] to zero for all [TextStyle]s
+  /// in the paragraph. Since the height of each line is calculated as a max of the
+  /// metrics of each run of text, zero height [TextStyle]s cause the minimums
+  /// defined by strut to always manifest, resulting in all lines having the height
+  /// of the strut.
+  ///
+  /// The default is false.
+  final bool? forceStrutHeight;
+
+  /// A human-readable description of this strut style.
+  ///
+  /// This property is maintained only in debug builds.
+  ///
+  /// This property is not considered when comparing strut styles using `==` or
+  /// [compareTo], and it does not affect [hashCode].
+  final String? debugLabel;
+
+  /// Describe the difference between this style and another, in terms of how
+  /// much damage it will make to the rendering.
+  ///
+  /// See also:
+  ///
+  ///  * [TextSpan.compareTo], which does the same thing for entire [TextSpan]s.
+  RenderComparison compareTo(StrutStyle other) {
+    if (identical(this, other))
+      return RenderComparison.identical;
+    if (fontFamily != other.fontFamily ||
+        fontSize != other.fontSize ||
+        fontWeight != other.fontWeight ||
+        fontStyle != other.fontStyle ||
+        height != other.height ||
+        leading != other.leading ||
+        forceStrutHeight != other.forceStrutHeight ||
+        !listEquals(fontFamilyFallback, other.fontFamilyFallback))
+      return RenderComparison.layout;
+    return RenderComparison.identical;
+  }
+
+  /// Returns a new strut style that inherits its null values from
+  /// corresponding properties in the [other] [TextStyle].
+  ///
+  /// The "missing" properties of the this strut style are _filled_ by
+  /// the properties of the provided [TextStyle]. This is possible because
+  /// [StrutStyle] shares many of the same basic properties as [TextStyle].
+  ///
+  /// If the given text style is null, returns this strut style.
+  StrutStyle inheritFromTextStyle(TextStyle? other) {
+    if (other == null)
+      return this;
+
+    return StrutStyle(
+      fontFamily: fontFamily ?? other.fontFamily,
+      fontFamilyFallback: fontFamilyFallback ?? other.fontFamilyFallback,
+      fontSize: fontSize ?? other.fontSize,
+      height: height ?? other.height,
+      leading: leading, // No equivalent property in TextStyle yet.
+      fontWeight: fontWeight ?? other.fontWeight,
+      fontStyle: fontStyle ?? other.fontStyle,
+      forceStrutHeight: forceStrutHeight, // StrutStyle-unique property.
+      debugLabel: debugLabel ?? other.debugLabel,
+      // Package is embedded within the getters for fontFamilyFallback.
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is StrutStyle
+        && other.fontFamily == fontFamily
+        && other.fontSize == fontSize
+        && other.fontWeight == fontWeight
+        && other.fontStyle == fontStyle
+        && other.height == height
+        && other.leading == leading
+        && other.forceStrutHeight == forceStrutHeight;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      fontFamily,
+      fontSize,
+      fontWeight,
+      fontStyle,
+      height,
+      leading,
+      forceStrutHeight,
+    );
+  }
+
+  @override
+  String toStringShort() => objectRuntimeType(this, 'StrutStyle');
+
+  /// Adds all properties prefixing property names with the optional `prefix`.
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties, { String prefix = '' }) {
+    super.debugFillProperties(properties);
+    if (debugLabel != null)
+      properties.add(MessageProperty('${prefix}debugLabel', debugLabel!));
+    final List<DiagnosticsNode> styles = <DiagnosticsNode>[
+      StringProperty('${prefix}family', fontFamily, defaultValue: null, quoted: false),
+      IterableProperty<String>('${prefix}familyFallback', fontFamilyFallback, defaultValue: null),
+      DoubleProperty('${prefix}size', fontSize, defaultValue: null),
+    ];
+    String? weightDescription;
+    if (fontWeight != null) {
+      weightDescription = 'w${fontWeight!.index + 1}00';
+    }
+    // TODO(jacobr): switch this to use enumProperty which will either cause the
+    // weight description to change to w600 from 600 or require existing
+    // enumProperty to handle this special case.
+    styles.add(DiagnosticsProperty<FontWeight>(
+      '${prefix}weight',
+      fontWeight,
+      description: weightDescription,
+      defaultValue: null,
+    ));
+    styles.add(EnumProperty<FontStyle>('${prefix}style', fontStyle, defaultValue: null));
+    styles.add(DoubleProperty('${prefix}height', height, unit: 'x', defaultValue: null));
+    styles.add(FlagProperty('${prefix}forceStrutHeight', value: forceStrutHeight, defaultValue: null, ifTrue: '$prefix<strut height forced>', ifFalse: '$prefix<strut height normal>'));
+
+    final bool styleSpecified = styles.any((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info));
+    styles.forEach(properties.add);
+
+    if (!styleSpecified)
+      properties.add(FlagProperty('forceStrutHeight', value: forceStrutHeight, ifTrue: '$prefix<strut height forced>', ifFalse: '$prefix<strut height normal>'));
+  }
+}
diff --git a/lib/src/painting/text_painter.dart b/lib/src/painting/text_painter.dart
new file mode 100644
index 0000000..8559d5e
--- /dev/null
+++ b/lib/src/painting/text_painter.dart
@@ -0,0 +1,917 @@
+// Copyright 2014 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.
+
+import 'dart:math' show min, max;
+import 'package:flute/ui.dart' as ui show Paragraph, ParagraphBuilder, ParagraphConstraints, ParagraphStyle, PlaceholderAlignment, LineMetrics, TextHeightBehavior, BoxHeightStyle, BoxWidthStyle;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/services.dart';
+
+import 'basic_types.dart';
+import 'inline_span.dart';
+import 'placeholder_span.dart';
+import 'strut_style.dart';
+import 'text_span.dart';
+
+export 'package:flute/services.dart' show TextRange, TextSelection;
+
+// The default font size if none is specified. This should be kept in
+// sync with the default values in text_style.dart, as well as the
+// defaults set in the engine (eg, LibTxt's text_style.h, paragraph_style.h).
+const double _kDefaultFontSize = 14.0;
+
+/// Holds the [Size] and baseline required to represent the dimensions of
+/// a placeholder in text.
+///
+/// Placeholders specify an empty space in the text layout, which is used
+/// to later render arbitrary inline widgets into defined by a [WidgetSpan].
+///
+/// The [size] and [alignment] properties are required and cannot be null.
+///
+/// See also:
+///
+///  * [WidgetSpan], a subclass of [InlineSpan] and [PlaceholderSpan] that
+///    represents an inline widget embedded within text. The space this
+///    widget takes is indicated by a placeholder.
+///  * [RichText], a text widget that supports text inline widgets.
+@immutable
+class PlaceholderDimensions {
+  /// Constructs a [PlaceholderDimensions] with the specified parameters.
+  ///
+  /// The `size` and `alignment` are required as a placeholder's dimensions
+  /// require at least `size` and `alignment` to be fully defined.
+  const PlaceholderDimensions({
+    required this.size,
+    required this.alignment,
+    this.baseline,
+    this.baselineOffset,
+  }) : assert(size != null),
+       assert(alignment != null);
+
+  /// A constant representing an empty placeholder.
+  static const PlaceholderDimensions empty = PlaceholderDimensions(size: Size.zero, alignment: ui.PlaceholderAlignment.bottom);
+
+  /// Width and height dimensions of the placeholder.
+  final Size size;
+
+  /// How to align the placeholder with the text.
+  ///
+  /// See also:
+  ///
+  ///  * [baseline], the baseline to align to when using
+  ///    [dart:ui.PlaceholderAlignment.baseline],
+  ///    [dart:ui.PlaceholderAlignment.aboveBaseline],
+  ///    or [dart:ui.PlaceholderAlignment.belowBaseline].
+  ///  * [baselineOffset], the distance of the alphabetic baseline from the upper
+  ///    edge of the placeholder.
+  final ui.PlaceholderAlignment alignment;
+
+  /// Distance of the [baseline] from the upper edge of the placeholder.
+  ///
+  /// Only used when [alignment] is [ui.PlaceholderAlignment.baseline].
+  final double? baselineOffset;
+
+  /// The [TextBaseline] to align to. Used with:
+  ///
+  ///  * [ui.PlaceholderAlignment.baseline]
+  ///  * [ui.PlaceholderAlignment.aboveBaseline]
+  ///  * [ui.PlaceholderAlignment.belowBaseline]
+  ///  * [ui.PlaceholderAlignment.middle]
+  final TextBaseline? baseline;
+
+  @override
+  String toString() {
+    return 'PlaceholderDimensions($size, $baseline)';
+  }
+}
+
+/// The different ways of measuring the width of one or more lines of text.
+///
+/// See [Text.textWidthBasis], for example.
+enum TextWidthBasis {
+  /// multiline text will take up the full width given by the parent. For single
+  /// line text, only the minimum amount of width needed to contain the text
+  /// will be used. A common use case for this is a standard series of
+  /// paragraphs.
+  parent,
+
+  /// The width will be exactly enough to contain the longest line and no
+  /// longer. A common use case for this is chat bubbles.
+  longestLine,
+}
+
+/// This is used to cache and pass the computed metrics regarding the
+/// caret's size and position. This is preferred due to the expensive
+/// nature of the calculation.
+class _CaretMetrics {
+  const _CaretMetrics({required this.offset, this.fullHeight});
+  /// The offset of the top left corner of the caret from the top left
+  /// corner of the paragraph.
+  final Offset offset;
+
+  /// The full height of the glyph at the caret position.
+  final double? fullHeight;
+}
+
+/// An object that paints a [TextSpan] tree into a [Canvas].
+///
+/// To use a [TextPainter], follow these steps:
+///
+/// 1. Create a [TextSpan] tree and pass it to the [TextPainter]
+///    constructor.
+///
+/// 2. Call [layout] to prepare the paragraph.
+///
+/// 3. Call [paint] as often as desired to paint the paragraph.
+///
+/// If the width of the area into which the text is being painted
+/// changes, return to step 2. If the text to be painted changes,
+/// return to step 1.
+///
+/// The default text style is white. To change the color of the text,
+/// pass a [TextStyle] object to the [TextSpan] in `text`.
+class TextPainter {
+  /// Creates a text painter that paints the given text.
+  ///
+  /// The `text` and `textDirection` arguments are optional but [text] and
+  /// [textDirection] must be non-null before calling [layout].
+  ///
+  /// The [textAlign] property must not be null.
+  ///
+  /// The [maxLines] property, if non-null, must be greater than zero.
+  TextPainter({
+    InlineSpan? text,
+    TextAlign textAlign = TextAlign.start,
+    TextDirection? textDirection,
+    double textScaleFactor = 1.0,
+    int? maxLines,
+    String? ellipsis,
+    Locale? locale,
+    StrutStyle? strutStyle,
+    TextWidthBasis textWidthBasis = TextWidthBasis.parent,
+    ui.TextHeightBehavior? textHeightBehavior,
+  }) : assert(text == null || text.debugAssertIsValid()),
+       assert(textAlign != null),
+       assert(textScaleFactor != null),
+       assert(maxLines == null || maxLines > 0),
+       assert(textWidthBasis != null),
+       _text = text,
+       _textAlign = textAlign,
+       _textDirection = textDirection,
+       _textScaleFactor = textScaleFactor,
+       _maxLines = maxLines,
+       _ellipsis = ellipsis,
+       _locale = locale,
+       _strutStyle = strutStyle,
+       _textWidthBasis = textWidthBasis,
+       _textHeightBehavior = textHeightBehavior;
+
+  ui.Paragraph? _paragraph;
+  bool _needsLayout = true;
+
+  /// Marks this text painter's layout information as dirty and removes cached
+  /// information.
+  ///
+  /// Uses this method to notify text painter to relayout in the case of
+  /// layout changes in engine. In most cases, updating text painter properties
+  /// in framework will automatically invoke this method.
+  void markNeedsLayout() {
+    _paragraph = null;
+    _needsLayout = true;
+    _previousCaretPosition = null;
+    _previousCaretPrototype = null;
+  }
+
+  /// The (potentially styled) text to paint.
+  ///
+  /// After this is set, you must call [layout] before the next call to [paint].
+  /// This and [textDirection] must be non-null before you call [layout].
+  ///
+  /// The [InlineSpan] this provides is in the form of a tree that may contain
+  /// multiple instances of [TextSpan]s and [WidgetSpan]s. To obtain a plain text
+  /// representation of the contents of this [TextPainter], use [InlineSpan.toPlainText]
+  /// to get the full contents of all nodes in the tree. [TextSpan.text] will
+  /// only provide the contents of the first node in the tree.
+  InlineSpan? get text => _text;
+  InlineSpan? _text;
+  set text(InlineSpan? value) {
+    assert(value == null || value.debugAssertIsValid());
+    if (_text == value)
+      return;
+    if (_text?.style != value?.style)
+      _layoutTemplate = null;
+    _text = value;
+    markNeedsLayout();
+  }
+
+  /// How the text should be aligned horizontally.
+  ///
+  /// After this is set, you must call [layout] before the next call to [paint].
+  ///
+  /// The [textAlign] property must not be null. It defaults to [TextAlign.start].
+  TextAlign get textAlign => _textAlign;
+  TextAlign _textAlign;
+  set textAlign(TextAlign value) {
+    assert(value != null);
+    if (_textAlign == value)
+      return;
+    _textAlign = value;
+    markNeedsLayout();
+  }
+
+  /// The default directionality of the text.
+  ///
+  /// This controls how the [TextAlign.start], [TextAlign.end], and
+  /// [TextAlign.justify] values of [textAlign] are resolved.
+  ///
+  /// This is also used to disambiguate how to render bidirectional text. For
+  /// example, if the [text] is an English phrase followed by a Hebrew phrase,
+  /// in a [TextDirection.ltr] context the English phrase will be on the left
+  /// and the Hebrew phrase to its right, while in a [TextDirection.rtl]
+  /// context, the English phrase will be on the right and the Hebrew phrase on
+  /// its left.
+  ///
+  /// After this is set, you must call [layout] before the next call to [paint].
+  ///
+  /// This and [text] must be non-null before you call [layout].
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection;
+  set textDirection(TextDirection? value) {
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    markNeedsLayout();
+    _layoutTemplate = null; // Shouldn't really matter, but for strict correctness...
+  }
+
+  /// The number of font pixels for each logical pixel.
+  ///
+  /// For example, if the text scale factor is 1.5, text will be 50% larger than
+  /// the specified font size.
+  ///
+  /// After this is set, you must call [layout] before the next call to [paint].
+  double get textScaleFactor => _textScaleFactor;
+  double _textScaleFactor;
+  set textScaleFactor(double value) {
+    assert(value != null);
+    if (_textScaleFactor == value)
+      return;
+    _textScaleFactor = value;
+    markNeedsLayout();
+    _layoutTemplate = null;
+  }
+
+  /// The string used to ellipsize overflowing text. Setting this to a non-empty
+  /// string will cause this string to be substituted for the remaining text
+  /// if the text can not fit within the specified maximum width.
+  ///
+  /// Specifically, the ellipsis is applied to the last line before the line
+  /// truncated by [maxLines], if [maxLines] is non-null and that line overflows
+  /// the width constraint, or to the first line that is wider than the width
+  /// constraint, if [maxLines] is null. The width constraint is the `maxWidth`
+  /// passed to [layout].
+  ///
+  /// After this is set, you must call [layout] before the next call to [paint].
+  ///
+  /// The higher layers of the system, such as the [Text] widget, represent
+  /// overflow effects using the [TextOverflow] enum. The
+  /// [TextOverflow.ellipsis] value corresponds to setting this property to
+  /// U+2026 HORIZONTAL ELLIPSIS (…).
+  String? get ellipsis => _ellipsis;
+  String? _ellipsis;
+  set ellipsis(String? value) {
+    assert(value == null || value.isNotEmpty);
+    if (_ellipsis == value)
+      return;
+    _ellipsis = value;
+    markNeedsLayout();
+  }
+
+  /// The locale used to select region-specific glyphs.
+  Locale? get locale => _locale;
+  Locale? _locale;
+  set locale(Locale? value) {
+    if (_locale == value)
+      return;
+    _locale = value;
+    markNeedsLayout();
+  }
+
+  /// An optional maximum number of lines for the text to span, wrapping if
+  /// necessary.
+  ///
+  /// If the text exceeds the given number of lines, it is truncated such that
+  /// subsequent lines are dropped.
+  ///
+  /// After this is set, you must call [layout] before the next call to [paint].
+  int? get maxLines => _maxLines;
+  int? _maxLines;
+  /// The value may be null. If it is not null, then it must be greater than zero.
+  set maxLines(int? value) {
+    assert(value == null || value > 0);
+    if (_maxLines == value)
+      return;
+    _maxLines = value;
+    markNeedsLayout();
+  }
+
+  /// {@template flutter.painting.textPainter.strutStyle}
+  /// The strut style to use. Strut style defines the strut, which sets minimum
+  /// vertical layout metrics.
+  ///
+  /// Omitting or providing null will disable strut.
+  ///
+  /// Omitting or providing null for any properties of [StrutStyle] will result in
+  /// default values being used. It is highly recommended to at least specify a
+  /// [StrutStyle.fontSize].
+  ///
+  /// See [StrutStyle] for details.
+  /// {@endtemplate}
+  StrutStyle? get strutStyle => _strutStyle;
+  StrutStyle? _strutStyle;
+  set strutStyle(StrutStyle? value) {
+    if (_strutStyle == value)
+      return;
+    _strutStyle = value;
+    markNeedsLayout();
+  }
+
+  /// {@template flutter.painting.textPainter.textWidthBasis}
+  /// Defines how to measure the width of the rendered text.
+  /// {@endtemplate}
+  TextWidthBasis get textWidthBasis => _textWidthBasis;
+  TextWidthBasis _textWidthBasis;
+  set textWidthBasis(TextWidthBasis value) {
+    assert(value != null);
+    if (_textWidthBasis == value)
+      return;
+    _textWidthBasis = value;
+    markNeedsLayout();
+  }
+
+  /// {@macro flutter.dart:ui.textHeightBehavior}
+  ui.TextHeightBehavior? get textHeightBehavior => _textHeightBehavior;
+  ui.TextHeightBehavior? _textHeightBehavior;
+  set textHeightBehavior(ui.TextHeightBehavior? value) {
+    if (_textHeightBehavior == value)
+      return;
+    _textHeightBehavior = value;
+    markNeedsLayout();
+  }
+
+  ui.Paragraph? _layoutTemplate;
+
+  /// An ordered list of [TextBox]es that bound the positions of the placeholders
+  /// in the paragraph.
+  ///
+  /// Each box corresponds to a [PlaceholderSpan] in the order they were defined
+  /// in the [InlineSpan] tree.
+  List<TextBox>? get inlinePlaceholderBoxes => _inlinePlaceholderBoxes;
+  List<TextBox>? _inlinePlaceholderBoxes;
+
+  /// An ordered list of scales for each placeholder in the paragraph.
+  ///
+  /// The scale is used as a multiplier on the height, width and baselineOffset of
+  /// the placeholder. Scale is primarily used to handle accessibility scaling.
+  ///
+  /// Each scale corresponds to a [PlaceholderSpan] in the order they were defined
+  /// in the [InlineSpan] tree.
+  List<double>? get inlinePlaceholderScales => _inlinePlaceholderScales;
+  List<double>? _inlinePlaceholderScales;
+
+  /// Sets the dimensions of each placeholder in [text].
+  ///
+  /// The number of [PlaceholderDimensions] provided should be the same as the
+  /// number of [PlaceholderSpan]s in text. Passing in an empty or null `value`
+  /// will do nothing.
+  ///
+  /// If [layout] is attempted without setting the placeholder dimensions, the
+  /// placeholders will be ignored in the text layout and no valid
+  /// [inlinePlaceholderBoxes] will be returned.
+  void setPlaceholderDimensions(List<PlaceholderDimensions>? value) {
+    if (value == null || value.isEmpty || listEquals(value, _placeholderDimensions)) {
+      return;
+    }
+    assert(() {
+      int placeholderCount = 0;
+      text!.visitChildren((InlineSpan span) {
+        if (span is PlaceholderSpan) {
+          placeholderCount += 1;
+        }
+        return true;
+      });
+      return placeholderCount;
+    }() == value.length);
+    _placeholderDimensions = value;
+    markNeedsLayout();
+  }
+  List<PlaceholderDimensions>? _placeholderDimensions;
+
+  ui.ParagraphStyle _createParagraphStyle([ TextDirection? defaultTextDirection ]) {
+    // The defaultTextDirection argument is used for preferredLineHeight in case
+    // textDirection hasn't yet been set.
+    assert(textAlign != null);
+    assert(textDirection != null || defaultTextDirection != null, 'TextPainter.textDirection must be set to a non-null value before using the TextPainter.');
+    return _text!.style?.getParagraphStyle(
+      textAlign: textAlign,
+      textDirection: textDirection ?? defaultTextDirection,
+      textScaleFactor: textScaleFactor,
+      maxLines: _maxLines,
+      textHeightBehavior: _textHeightBehavior,
+      ellipsis: _ellipsis,
+      locale: _locale,
+      strutStyle: _strutStyle,
+    ) ?? ui.ParagraphStyle(
+      textAlign: textAlign,
+      textDirection: textDirection ?? defaultTextDirection,
+      // Use the default font size to multiply by as RichText does not
+      // perform inheriting [TextStyle]s and would otherwise
+      // fail to apply textScaleFactor.
+      fontSize: _kDefaultFontSize * textScaleFactor,
+      maxLines: maxLines,
+      textHeightBehavior: _textHeightBehavior,
+      ellipsis: ellipsis,
+      locale: locale,
+    );
+  }
+
+  /// The height of a space in [text] in logical pixels.
+  ///
+  /// Not every line of text in [text] will have this height, but this height
+  /// is "typical" for text in [text] and useful for sizing other objects
+  /// relative a typical line of text.
+  ///
+  /// Obtaining this value does not require calling [layout].
+  ///
+  /// The style of the [text] property is used to determine the font settings
+  /// that contribute to the [preferredLineHeight]. If [text] is null or if it
+  /// specifies no styles, the default [TextStyle] values are used (a 10 pixel
+  /// sans-serif font).
+  double get preferredLineHeight {
+    if (_layoutTemplate == null) {
+      final ui.ParagraphBuilder builder = ui.ParagraphBuilder(
+        _createParagraphStyle(TextDirection.rtl),
+      ); // direction doesn't matter, text is just a space
+      if (text?.style != null)
+        builder.pushStyle(text!.style!.getTextStyle(textScaleFactor: textScaleFactor));
+      builder.addText(' ');
+      _layoutTemplate = builder.build()
+        ..layout(const ui.ParagraphConstraints(width: double.infinity));
+    }
+    return _layoutTemplate!.height;
+  }
+
+  // Unfortunately, using full precision floating point here causes bad layouts
+  // because floating point math isn't associative. If we add and subtract
+  // padding, for example, we'll get different values when we estimate sizes and
+  // when we actually compute layout because the operations will end up associated
+  // differently. To work around this problem for now, we round fractional pixel
+  // values up to the nearest whole pixel value. The right long-term fix is to do
+  // layout using fixed precision arithmetic.
+  double _applyFloatingPointHack(double layoutValue) {
+    return layoutValue.ceilToDouble();
+  }
+
+  /// The width at which decreasing the width of the text would prevent it from
+  /// painting itself completely within its bounds.
+  ///
+  /// Valid only after [layout] has been called.
+  double get minIntrinsicWidth {
+    assert(!_needsLayout);
+    return _applyFloatingPointHack(_paragraph!.minIntrinsicWidth);
+  }
+
+  /// The width at which increasing the width of the text no longer decreases the height.
+  ///
+  /// Valid only after [layout] has been called.
+  double get maxIntrinsicWidth {
+    assert(!_needsLayout);
+    return _applyFloatingPointHack(_paragraph!.maxIntrinsicWidth);
+  }
+
+  /// The horizontal space required to paint this text.
+  ///
+  /// Valid only after [layout] has been called.
+  double get width {
+    assert(!_needsLayout);
+    return _applyFloatingPointHack(
+      textWidthBasis == TextWidthBasis.longestLine ? _paragraph!.longestLine : _paragraph!.width,
+    );
+  }
+
+  /// The vertical space required to paint this text.
+  ///
+  /// Valid only after [layout] has been called.
+  double get height {
+    assert(!_needsLayout);
+    return _applyFloatingPointHack(_paragraph!.height);
+  }
+
+  /// The amount of space required to paint this text.
+  ///
+  /// Valid only after [layout] has been called.
+  Size get size {
+    assert(!_needsLayout);
+    return Size(width, height);
+  }
+
+  /// Returns the distance from the top of the text to the first baseline of the
+  /// given type.
+  ///
+  /// Valid only after [layout] has been called.
+  double computeDistanceToActualBaseline(TextBaseline baseline) {
+    assert(!_needsLayout);
+    assert(baseline != null);
+    switch (baseline) {
+      case TextBaseline.alphabetic:
+        return _paragraph!.alphabeticBaseline;
+      case TextBaseline.ideographic:
+        return _paragraph!.ideographicBaseline;
+    }
+  }
+
+  /// Whether any text was truncated or ellipsized.
+  ///
+  /// If [maxLines] is not null, this is true if there were more lines to be
+  /// drawn than the given [maxLines], and thus at least one line was omitted in
+  /// the output; otherwise it is false.
+  ///
+  /// If [maxLines] is null, this is true if [ellipsis] is not the empty string
+  /// and there was a line that overflowed the `maxWidth` argument passed to
+  /// [layout]; otherwise it is false.
+  ///
+  /// Valid only after [layout] has been called.
+  bool get didExceedMaxLines {
+    assert(!_needsLayout);
+    return _paragraph!.didExceedMaxLines;
+  }
+
+  double? _lastMinWidth;
+  double? _lastMaxWidth;
+
+  /// Computes the visual position of the glyphs for painting the text.
+  ///
+  /// The text will layout with a width that's as close to its max intrinsic
+  /// width as possible while still being greater than or equal to `minWidth` and
+  /// less than or equal to `maxWidth`.
+  ///
+  /// The [text] and [textDirection] properties must be non-null before this is
+  /// called.
+  void layout({ double minWidth = 0.0, double maxWidth = double.infinity }) {
+    assert(text != null, 'TextPainter.text must be set to a non-null value before using the TextPainter.');
+    assert(textDirection != null, 'TextPainter.textDirection must be set to a non-null value before using the TextPainter.');
+    if (!_needsLayout && minWidth == _lastMinWidth && maxWidth == _lastMaxWidth)
+      return;
+    _needsLayout = false;
+    if (_paragraph == null) {
+      final ui.ParagraphBuilder builder = ui.ParagraphBuilder(_createParagraphStyle());
+      _text!.build(builder, textScaleFactor: textScaleFactor, dimensions: _placeholderDimensions);
+      _inlinePlaceholderScales = builder.placeholderScales;
+      _paragraph = builder.build();
+    }
+    _lastMinWidth = minWidth;
+    _lastMaxWidth = maxWidth;
+    // A change in layout invalidates the cached caret metrics as well.
+    _previousCaretPosition = null;
+    _previousCaretPrototype = null;
+    _paragraph!.layout(ui.ParagraphConstraints(width: maxWidth));
+    if (minWidth != maxWidth) {
+      double newWidth;
+      switch (textWidthBasis) {
+        case TextWidthBasis.longestLine:
+          // The parent widget expects the paragraph to be exactly
+          // `TextPainter.width` wide, if that value satisfies the constraints
+          // it gave to the TextPainter. So when `textWidthBasis` is longestLine,
+          // the paragraph's width needs to be as close to the width of its
+          // longest line as possible.
+          newWidth = _applyFloatingPointHack(_paragraph!.longestLine);
+          break;
+        case TextWidthBasis.parent:
+          newWidth = maxIntrinsicWidth;
+          break;
+      }
+      newWidth = newWidth.clamp(minWidth, maxWidth);
+      if (newWidth != _applyFloatingPointHack(_paragraph!.width)) {
+        _paragraph!.layout(ui.ParagraphConstraints(width: newWidth));
+      }
+    }
+    _inlinePlaceholderBoxes = _paragraph!.getBoxesForPlaceholders();
+  }
+
+  /// Paints the text onto the given canvas at the given offset.
+  ///
+  /// Valid only after [layout] has been called.
+  ///
+  /// If you cannot see the text being painted, check that your text color does
+  /// not conflict with the background on which you are drawing. The default
+  /// text color is white (to contrast with the default black background color),
+  /// so if you are writing an application with a white background, the text
+  /// will not be visible by default.
+  ///
+  /// To set the text style, specify a [TextStyle] when creating the [TextSpan]
+  /// that you pass to the [TextPainter] constructor or to the [text] property.
+  void paint(Canvas canvas, Offset offset) {
+    assert(() {
+      if (_needsLayout) {
+        throw FlutterError(
+          'TextPainter.paint called when text geometry was not yet calculated.\n'
+          'Please call layout() before paint() to position the text before painting it.'
+        );
+      }
+      return true;
+    }());
+    canvas.drawParagraph(_paragraph!, offset);
+  }
+
+  // Returns true iff the given value is a valid UTF-16 surrogate. The value
+  // must be a UTF-16 code unit, meaning it must be in the range 0x0000-0xFFFF.
+  //
+  // See also:
+  //   * https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF
+  static bool _isUtf16Surrogate(int value) {
+    return value & 0xF800 == 0xD800;
+  }
+
+  // Checks if the glyph is either [Unicode.RLM] or [Unicode.LRM]. These values take
+  // up zero space and do not have valid bounding boxes around them.
+  //
+  // We do not directly use the [Unicode] constants since they are strings.
+  static bool _isUnicodeDirectionality(int value) {
+    return value == 0x200F || value == 0x200E;
+  }
+
+  /// Returns the closest offset after `offset` at which the input cursor can be
+  /// positioned.
+  int? getOffsetAfter(int offset) {
+    final int? nextCodeUnit = _text!.codeUnitAt(offset);
+    if (nextCodeUnit == null)
+      return null;
+    // TODO(goderbauer): doesn't handle extended grapheme clusters with more than one Unicode scalar value (https://github.com/flutter/flutter/issues/13404).
+    return _isUtf16Surrogate(nextCodeUnit) ? offset + 2 : offset + 1;
+  }
+
+  /// Returns the closest offset before `offset` at which the input cursor can
+  /// be positioned.
+  int? getOffsetBefore(int offset) {
+    final int? prevCodeUnit = _text!.codeUnitAt(offset - 1);
+    if (prevCodeUnit == null)
+      return null;
+    // TODO(goderbauer): doesn't handle extended grapheme clusters with more than one Unicode scalar value (https://github.com/flutter/flutter/issues/13404).
+    return _isUtf16Surrogate(prevCodeUnit) ? offset - 2 : offset - 1;
+  }
+
+  // Unicode value for a zero width joiner character.
+  static const int _zwjUtf16 = 0x200d;
+
+  // Get the Rect of the cursor (in logical pixels) based off the near edge
+  // of the character upstream from the given string offset.
+  Rect? _getRectFromUpstream(int offset, Rect caretPrototype) {
+    final String flattenedText = _text!.toPlainText(includePlaceholders: false);
+    final int? prevCodeUnit = _text!.codeUnitAt(max(0, offset - 1));
+    if (prevCodeUnit == null)
+      return null;
+
+    // Check for multi-code-unit glyphs such as emojis or zero width joiner.
+    final bool needsSearch = _isUtf16Surrogate(prevCodeUnit) || _text!.codeUnitAt(offset) == _zwjUtf16 || _isUnicodeDirectionality(prevCodeUnit);
+    int graphemeClusterLength = needsSearch ? 2 : 1;
+    List<TextBox> boxes = <TextBox>[];
+    while (boxes.isEmpty) {
+      final int prevRuneOffset = offset - graphemeClusterLength;
+      // Use BoxHeightStyle.strut to ensure that the caret's height fits within
+      // the line's height and is consistent throughout the line.
+      boxes = _paragraph!.getBoxesForRange(prevRuneOffset, offset, boxHeightStyle: ui.BoxHeightStyle.strut);
+      // When the range does not include a full cluster, no boxes will be returned.
+      if (boxes.isEmpty) {
+        // When we are at the beginning of the line, a non-surrogate position will
+        // return empty boxes. We break and try from downstream instead.
+        if (!needsSearch) {
+          break; // Only perform one iteration if no search is required.
+        }
+        if (prevRuneOffset < -flattenedText.length) {
+          break; // Stop iterating when beyond the max length of the text.
+        }
+        // Multiply by two to log(n) time cover the entire text span. This allows
+        // faster discovery of very long clusters and reduces the possibility
+        // of certain large clusters taking much longer than others, which can
+        // cause jank.
+        graphemeClusterLength *= 2;
+        continue;
+      }
+      final TextBox box = boxes.first;
+
+      // If the upstream character is a newline, cursor is at start of next line
+      const int NEWLINE_CODE_UNIT = 10;
+      if (prevCodeUnit == NEWLINE_CODE_UNIT) {
+        return Rect.fromLTRB(_emptyOffset.dx, box.bottom, _emptyOffset.dx, box.bottom + box.bottom - box.top);
+      }
+
+      final double caretEnd = box.end;
+      final double dx = box.direction == TextDirection.rtl ? caretEnd - caretPrototype.width : caretEnd;
+      return Rect.fromLTRB(min(dx, _paragraph!.width), box.top, min(dx, _paragraph!.width), box.bottom);
+    }
+    return null;
+  }
+
+  // Get the Rect of the cursor (in logical pixels) based off the near edge
+  // of the character downstream from the given string offset.
+  Rect? _getRectFromDownstream(int offset, Rect caretPrototype) {
+    final String flattenedText = _text!.toPlainText(includePlaceholders: false);
+    // We cap the offset at the final index of the _text.
+    final int? nextCodeUnit = _text!.codeUnitAt(min(offset, flattenedText.length - 1));
+    if (nextCodeUnit == null)
+      return null;
+    // Check for multi-code-unit glyphs such as emojis or zero width joiner
+    final bool needsSearch = _isUtf16Surrogate(nextCodeUnit) || nextCodeUnit == _zwjUtf16 || _isUnicodeDirectionality(nextCodeUnit);
+    int graphemeClusterLength = needsSearch ? 2 : 1;
+    List<TextBox> boxes = <TextBox>[];
+    while (boxes.isEmpty) {
+      final int nextRuneOffset = offset + graphemeClusterLength;
+      // Use BoxHeightStyle.strut to ensure that the caret's height fits within
+      // the line's height and is consistent throughout the line.
+      boxes = _paragraph!.getBoxesForRange(offset, nextRuneOffset, boxHeightStyle: ui.BoxHeightStyle.strut);
+      // When the range does not include a full cluster, no boxes will be returned.
+      if (boxes.isEmpty) {
+        // When we are at the end of the line, a non-surrogate position will
+        // return empty boxes. We break and try from upstream instead.
+        if (!needsSearch) {
+          break; // Only perform one iteration if no search is required.
+        }
+        if (nextRuneOffset >= flattenedText.length << 1) {
+          break; // Stop iterating when beyond the max length of the text.
+        }
+        // Multiply by two to log(n) time cover the entire text span. This allows
+        // faster discovery of very long clusters and reduces the possibility
+        // of certain large clusters taking much longer than others, which can
+        // cause jank.
+        graphemeClusterLength *= 2;
+        continue;
+      }
+      final TextBox box = boxes.last;
+      final double caretStart = box.start;
+      final double dx = box.direction == TextDirection.rtl ? caretStart - caretPrototype.width : caretStart;
+      return Rect.fromLTRB(min(dx, _paragraph!.width), box.top, min(dx, _paragraph!.width), box.bottom);
+    }
+    return null;
+  }
+
+  Offset get _emptyOffset {
+    assert(!_needsLayout); // implies textDirection is non-null
+    assert(textAlign != null);
+    switch (textAlign) {
+      case TextAlign.left:
+        return Offset.zero;
+      case TextAlign.right:
+        return Offset(width, 0.0);
+      case TextAlign.center:
+        return Offset(width / 2.0, 0.0);
+      case TextAlign.justify:
+      case TextAlign.start:
+        assert(textDirection != null);
+        switch (textDirection!) {
+          case TextDirection.rtl:
+            return Offset(width, 0.0);
+          case TextDirection.ltr:
+            return Offset.zero;
+        }
+      case TextAlign.end:
+        assert(textDirection != null);
+        switch (textDirection!) {
+          case TextDirection.rtl:
+            return Offset.zero;
+          case TextDirection.ltr:
+            return Offset(width, 0.0);
+        }
+    }
+  }
+
+  /// Returns the offset at which to paint the caret.
+  ///
+  /// Valid only after [layout] has been called.
+  Offset getOffsetForCaret(TextPosition position, Rect caretPrototype) {
+    _computeCaretMetrics(position, caretPrototype);
+    return _caretMetrics.offset;
+  }
+
+  /// {@template flutter.painting.textPainter.getFullHeightForCaret}
+  /// Returns the strut bounded height of the glyph at the given `position`.
+  /// {@endtemplate}
+  ///
+  /// Valid only after [layout] has been called.
+  double? getFullHeightForCaret(TextPosition position, Rect caretPrototype) {
+    _computeCaretMetrics(position, caretPrototype);
+    return _caretMetrics.fullHeight;
+  }
+
+  // Cached caret metrics. This allows multiple invokes of [getOffsetForCaret] and
+  // [getFullHeightForCaret] in a row without performing redundant and expensive
+  // get rect calls to the paragraph.
+  late _CaretMetrics _caretMetrics;
+
+  // Holds the TextPosition and caretPrototype the last caret metrics were
+  // computed with. When new values are passed in, we recompute the caret metrics.
+  // only as necessary.
+  TextPosition? _previousCaretPosition;
+  Rect? _previousCaretPrototype;
+
+  // Checks if the [position] and [caretPrototype] have changed from the cached
+  // version and recomputes the metrics required to position the caret.
+  void _computeCaretMetrics(TextPosition position, Rect caretPrototype) {
+    assert(!_needsLayout);
+    if (position == _previousCaretPosition && caretPrototype == _previousCaretPrototype)
+      return;
+    final int offset = position.offset;
+    assert(position.affinity != null);
+    Rect? rect;
+    switch (position.affinity) {
+      case TextAffinity.upstream: {
+        rect = _getRectFromUpstream(offset, caretPrototype) ?? _getRectFromDownstream(offset, caretPrototype);
+        break;
+      }
+      case TextAffinity.downstream: {
+        rect = _getRectFromDownstream(offset, caretPrototype) ??  _getRectFromUpstream(offset, caretPrototype);
+        break;
+      }
+    }
+    _caretMetrics = _CaretMetrics(
+      offset: rect != null ? Offset(rect.left, rect.top) : _emptyOffset,
+      fullHeight: rect != null ? rect.bottom - rect.top : null,
+    );
+
+    // Cache the input parameters to prevent repeat work later.
+    _previousCaretPosition = position;
+    _previousCaretPrototype = caretPrototype;
+  }
+
+  /// Returns a list of rects that bound the given selection.
+  ///
+  /// The [boxHeightStyle] and [boxWidthStyle] arguments may be used to select
+  /// the shape of the [TextBox]s. These properties default to
+  /// [ui.BoxHeightStyle.tight] and [ui.BoxWidthStyle.tight] respectively and
+  /// must not be null.
+  ///
+  /// A given selection might have more than one rect if this text painter
+  /// contains bidirectional text because logically contiguous text might not be
+  /// visually contiguous.
+  List<TextBox> getBoxesForSelection(
+    TextSelection selection, {
+    ui.BoxHeightStyle boxHeightStyle = ui.BoxHeightStyle.tight,
+    ui.BoxWidthStyle boxWidthStyle = ui.BoxWidthStyle.tight,
+  }) {
+    assert(!_needsLayout);
+    assert(boxHeightStyle != null);
+    assert(boxWidthStyle != null);
+    return _paragraph!.getBoxesForRange(
+      selection.start,
+      selection.end,
+      boxHeightStyle: boxHeightStyle,
+      boxWidthStyle: boxWidthStyle
+    );
+  }
+
+  /// Returns the position within the text for the given pixel offset.
+  TextPosition getPositionForOffset(Offset offset) {
+    assert(!_needsLayout);
+    return _paragraph!.getPositionForOffset(offset);
+  }
+
+  /// Returns the text range of the word at the given offset. Characters not
+  /// part of a word, such as spaces, symbols, and punctuation, have word breaks
+  /// on both sides. In such cases, this method will return a text range that
+  /// contains the given text position.
+  ///
+  /// Word boundaries are defined more precisely in Unicode Standard Annex #29
+  /// <http://www.unicode.org/reports/tr29/#Word_Boundaries>.
+  TextRange getWordBoundary(TextPosition position) {
+    assert(!_needsLayout);
+    return _paragraph!.getWordBoundary(position);
+  }
+
+  /// Returns the text range of the line at the given offset.
+  ///
+  /// The newline, if any, is included in the range.
+  TextRange getLineBoundary(TextPosition position) {
+    assert(!_needsLayout);
+    return _paragraph!.getLineBoundary(position);
+  }
+
+  /// Returns the full list of [LineMetrics] that describe in detail the various
+  /// metrics of each laid out line.
+  ///
+  /// The [LineMetrics] list is presented in the order of the lines they represent.
+  /// For example, the first line is in the zeroth index.
+  ///
+  /// [LineMetrics] contains measurements such as ascent, descent, baseline, and
+  /// width for the line as a whole, and may be useful for aligning additional
+  /// widgets to a particular line.
+  ///
+  /// Valid only after [layout] has been called.
+  ///
+  /// This can potentially return a large amount of data, so it is not recommended
+  /// to repeatedly call this. Instead, cache the results. The cached results
+  /// should be invalidated upon the next successful [layout].
+  List<ui.LineMetrics> computeLineMetrics() {
+    assert(!_needsLayout);
+    return _paragraph!.computeLineMetrics();
+  }
+}
diff --git a/lib/src/painting/text_span.dart b/lib/src/painting/text_span.dart
new file mode 100644
index 0000000..c097b2f
--- /dev/null
+++ b/lib/src/painting/text_span.dart
@@ -0,0 +1,492 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui show ParagraphBuilder;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/services.dart';
+
+import 'basic_types.dart';
+import 'inline_span.dart';
+import 'text_painter.dart';
+import 'text_style.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+
+/// An immutable span of text.
+///
+/// A [TextSpan] object can be styled using its [style] property. The style will
+/// be applied to the [text] and the [children].
+///
+/// A [TextSpan] object can just have plain text, or it can have children
+/// [TextSpan] objects with their own styles that (possibly only partially)
+/// override the [style] of this object. If a [TextSpan] has both [text] and
+/// [children], then the [text] is treated as if it was an un-styled [TextSpan]
+/// at the start of the [children] list. Leaving the [TextSpan.text] field null
+/// results in the [TextSpan] acting as an empty node in the [InlineSpan] tree
+/// with a list of children.
+///
+/// To paint a [TextSpan] on a [Canvas], use a [TextPainter]. To display a text
+/// span in a widget, use a [RichText]. For text with a single style, consider
+/// using the [Text] widget.
+///
+/// {@tool snippet}
+///
+/// The text "Hello world!", in black:
+///
+/// ```dart
+/// TextSpan(
+///   text: 'Hello world!',
+///   style: TextStyle(color: Colors.black),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// _There is some more detailed sample code in the documentation for the
+/// [recognizer] property._
+///
+/// The [TextSpan.text] will be used as the semantics label unless overridden
+/// by the [TextSpan.semanticsLabel] property. Any [PlaceholderSpan]s in the
+/// [TextSpan.children] list will separate the text before and after it into two
+/// semantics nodes.
+///
+/// See also:
+///
+///  * [WidgetSpan], a leaf node that represents an embedded inline widget in an
+///    [InlineSpan] tree. Specify a widget within the [children] list by
+///    wrapping the widget with a [WidgetSpan]. The widget will be laid out
+///    inline within the paragraph.
+///  * [Text], a widget for showing uniformly-styled text.
+///  * [RichText], a widget for finer control of text rendering.
+///  * [TextPainter], a class for painting [TextSpan] objects on a [Canvas].
+@immutable
+class TextSpan extends InlineSpan {
+  /// Creates a [TextSpan] with the given values.
+  ///
+  /// For the object to be useful, at least one of [text] or
+  /// [children] should be set.
+  const TextSpan({
+    this.text,
+    this.children,
+    TextStyle? style,
+    this.recognizer,
+    this.semanticsLabel,
+  }) : super(style: style);
+
+  /// The text contained in this span.
+  ///
+  /// If both [text] and [children] are non-null, the text will precede the
+  /// children.
+  ///
+  /// This getter does not include the contents of its children.
+  @override
+  final String? text;
+
+
+  /// Additional spans to include as children.
+  ///
+  /// If both [text] and [children] are non-null, the text will precede the
+  /// children.
+  ///
+  /// Modifying the list after the [TextSpan] has been created is not supported
+  /// and may have unexpected results.
+  ///
+  /// The list must not contain any nulls.
+  @override
+  final List<InlineSpan>? children;
+
+  /// A gesture recognizer that will receive events that hit this span.
+  ///
+  /// [InlineSpan] itself does not implement hit testing or event dispatch. The
+  /// object that manages the [InlineSpan] painting is also responsible for
+  /// dispatching events. In the rendering library, that is the
+  /// [RenderParagraph] object, which corresponds to the [RichText] widget in
+  /// the widgets layer; these objects do not bubble events in [InlineSpan]s,
+  /// so a [recognizer] is only effective for events that directly hit the
+  /// [text] of that [InlineSpan], not any of its [children].
+  ///
+  /// [InlineSpan] also does not manage the lifetime of the gesture recognizer.
+  /// The code that owns the [GestureRecognizer] object must call
+  /// [GestureRecognizer.dispose] when the [InlineSpan] object is no longer
+  /// used.
+  ///
+  /// {@tool snippet}
+  ///
+  /// This example shows how to manage the lifetime of a gesture recognizer
+  /// provided to an [InlineSpan] object. It defines a `BuzzingText` widget
+  /// which uses the [HapticFeedback] class to vibrate the device when the user
+  /// long-presses the "find the" span, which is underlined in wavy green. The
+  /// hit-testing is handled by the [RichText] widget.
+  ///
+  /// ```dart
+  /// class BuzzingText extends StatefulWidget {
+  ///   @override
+  ///   _BuzzingTextState createState() => _BuzzingTextState();
+  /// }
+  ///
+  /// class _BuzzingTextState extends State<BuzzingText> {
+  ///   LongPressGestureRecognizer _longPressRecognizer;
+  ///
+  ///   @override
+  ///   void initState() {
+  ///     super.initState();
+  ///     _longPressRecognizer = LongPressGestureRecognizer()
+  ///       ..onLongPress = _handlePress;
+  ///   }
+  ///
+  ///   @override
+  ///   void dispose() {
+  ///     _longPressRecognizer.dispose();
+  ///     super.dispose();
+  ///   }
+  ///
+  ///   void _handlePress() {
+  ///     HapticFeedback.vibrate();
+  ///   }
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Text.rich(
+  ///       TextSpan(
+  ///         text: 'Can you ',
+  ///         style: TextStyle(color: Colors.black),
+  ///         children: <InlineSpan>[
+  ///           TextSpan(
+  ///             text: 'find the',
+  ///             style: TextStyle(
+  ///               color: Colors.green,
+  ///               decoration: TextDecoration.underline,
+  ///               decorationStyle: TextDecorationStyle.wavy,
+  ///             ),
+  ///             recognizer: _longPressRecognizer,
+  ///           ),
+  ///           TextSpan(
+  ///             text: ' secret?',
+  ///           ),
+  ///         ],
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  @override
+  final GestureRecognizer? recognizer;
+
+  /// An alternative semantics label for this [TextSpan].
+  ///
+  /// If present, the semantics of this span will contain this value instead
+  /// of the actual text.
+  ///
+  /// This is useful for replacing abbreviations or shorthands with the full
+  /// text value:
+  ///
+  /// ```dart
+  /// TextSpan(text: r'$$', semanticsLabel: 'Double dollars')
+  /// ```
+  final String? semanticsLabel;
+
+  /// Apply the [style], [text], and [children] of this object to the
+  /// given [ParagraphBuilder], from which a [Paragraph] can be obtained.
+  /// [Paragraph] objects can be drawn on [Canvas] objects.
+  ///
+  /// Rather than using this directly, it's simpler to use the
+  /// [TextPainter] class to paint [TextSpan] objects onto [Canvas]
+  /// objects.
+  @override
+  void build(
+    ui.ParagraphBuilder builder, {
+    double textScaleFactor = 1.0,
+    List<PlaceholderDimensions>? dimensions,
+  }) {
+    assert(debugAssertIsValid());
+    final bool hasStyle = style != null;
+    if (hasStyle)
+      builder.pushStyle(style!.getTextStyle(textScaleFactor: textScaleFactor));
+    if (text != null)
+      builder.addText(text!);
+    if (children != null) {
+      for (final InlineSpan child in children!) {
+        assert(child != null);
+        child.build(
+          builder,
+          textScaleFactor: textScaleFactor,
+          dimensions: dimensions,
+        );
+      }
+    }
+    if (hasStyle)
+      builder.pop();
+  }
+
+  /// Walks this [TextSpan] and its descendants in pre-order and calls [visitor]
+  /// for each span that has text.
+  ///
+  /// When `visitor` returns true, the walk will continue. When `visitor`
+  /// returns false, then the walk will end.
+  @override
+  bool visitChildren(InlineSpanVisitor visitor) {
+    if (text != null) {
+      if (!visitor(this))
+        return false;
+    }
+    if (children != null) {
+      for (final InlineSpan child in children!) {
+        if (!child.visitChildren(visitor))
+          return false;
+      }
+    }
+    return true;
+  }
+
+  // TODO(garyq): Remove this after next stable release.
+  /// Walks this [TextSpan] and any descendants in pre-order and calls `visitor`
+  /// for each span that has content.
+  ///
+  /// When `visitor` returns true, the walk will continue. When `visitor`
+  /// returns false, then the walk will end.
+  @override
+  @Deprecated(
+    'Use to visitChildren instead. '
+    'This feature was deprecated after v1.7.3.'
+  )
+  bool visitTextSpan(bool visitor(TextSpan span)) {
+    if (text != null) {
+      if (!visitor(this))
+        return false;
+    }
+    if (children != null) {
+      for (final InlineSpan child in children!) {
+        assert(
+          child is TextSpan,
+          'visitTextSpan is deprecated. Use visitChildren to support InlineSpans',
+        );
+        final TextSpan textSpanChild = child as TextSpan;
+        if (!textSpanChild.visitTextSpan(visitor))
+          return false;
+      }
+    }
+    return true;
+  }
+
+  /// Returns the text span that contains the given position in the text.
+  @override
+  InlineSpan? getSpanForPositionVisitor(TextPosition position, Accumulator offset) {
+    if (text == null) {
+      return null;
+    }
+    final TextAffinity affinity = position.affinity;
+    final int targetOffset = position.offset;
+    final int endOffset = offset.value + text!.length;
+    if (offset.value == targetOffset && affinity == TextAffinity.downstream ||
+        offset.value < targetOffset && targetOffset < endOffset ||
+        endOffset == targetOffset && affinity == TextAffinity.upstream) {
+      return this;
+    }
+    offset.increment(text!.length);
+    return null;
+  }
+
+  @override
+  void computeToPlainText(
+    StringBuffer buffer, {
+    bool includeSemanticsLabels = true,
+    bool includePlaceholders = true
+  }) {
+    assert(debugAssertIsValid());
+    if (semanticsLabel != null && includeSemanticsLabels) {
+      buffer.write(semanticsLabel);
+    } else if (text != null) {
+      buffer.write(text);
+    }
+    if (children != null) {
+      for (final InlineSpan child in children!) {
+        child.computeToPlainText(buffer,
+          includeSemanticsLabels: includeSemanticsLabels,
+          includePlaceholders: includePlaceholders,
+        );
+      }
+    }
+  }
+
+  @override
+  void computeSemanticsInformation(List<InlineSpanSemanticsInformation> collector) {
+    assert(debugAssertIsValid());
+    if (text != null || semanticsLabel != null) {
+      collector.add(InlineSpanSemanticsInformation(
+        text!,
+        semanticsLabel: semanticsLabel,
+        recognizer: recognizer,
+      ));
+    }
+    if (children != null) {
+      for (final InlineSpan child in children!) {
+        child.computeSemanticsInformation(collector);
+      }
+    }
+  }
+
+  @override
+  int? codeUnitAtVisitor(int index, Accumulator offset) {
+    if (text == null) {
+      return null;
+    }
+    if (index - offset.value < text!.length) {
+      return text!.codeUnitAt(index - offset.value);
+    }
+    offset.increment(text!.length);
+    return null;
+  }
+
+  @override
+  void describeSemantics(Accumulator offset, List<int> semanticsOffsets, List<dynamic> semanticsElements) {
+    if (
+      recognizer != null &&
+      (recognizer is TapGestureRecognizer || recognizer is LongPressGestureRecognizer)
+    ) {
+      final int length = semanticsLabel?.length ?? text!.length;
+      semanticsOffsets.add(offset.value);
+      semanticsOffsets.add(offset.value + length);
+      semanticsElements.add(recognizer);
+    }
+    offset.increment(text != null ? text!.length : 0);
+  }
+
+  /// In checked mode, throws an exception if the object is not in a valid
+  /// configuration. Otherwise, returns true.
+  ///
+  /// This is intended to be used as follows:
+  ///
+  /// ```dart
+  /// assert(myTextSpan.debugAssertIsValid());
+  /// ```
+  @override
+  bool debugAssertIsValid() {
+    assert(() {
+      if (children != null) {
+        for (final InlineSpan child in children!) {
+          // `child` has a non-nullable return type, but might be null when
+          // running with weak checking, so we need to null check it anyway (and
+          // ignore the warning that the null-handling logic is dead code).
+          if (child == null) { // ignore: dead_code
+            throw FlutterError.fromParts(<DiagnosticsNode>[
+              ErrorSummary('TextSpan contains a null child.'),
+              ErrorDescription(
+                  'A TextSpan object with a non-null child list should not have any nulls in its child list.'),
+              toDiagnosticsNode(name: 'The full text in question was',
+                  style: DiagnosticsTreeStyle.errorProperty),
+            ]);
+          }
+          assert(child.debugAssertIsValid());
+        }
+      }
+      return true;
+    }());
+    return super.debugAssertIsValid();
+  }
+
+  @override
+  RenderComparison compareTo(InlineSpan other) {
+    if (identical(this, other))
+      return RenderComparison.identical;
+    if (other.runtimeType != runtimeType)
+      return RenderComparison.layout;
+    final TextSpan textSpan = other as TextSpan;
+    if (textSpan.text != text ||
+        children?.length != textSpan.children?.length ||
+        (style == null) != (textSpan.style == null))
+      return RenderComparison.layout;
+    RenderComparison result = recognizer == textSpan.recognizer ?
+      RenderComparison.identical :
+      RenderComparison.metadata;
+    if (style != null) {
+      final RenderComparison candidate = style!.compareTo(textSpan.style!);
+      if (candidate.index > result.index)
+        result = candidate;
+      if (result == RenderComparison.layout)
+        return result;
+    }
+    if (children != null) {
+      for (int index = 0; index < children!.length; index += 1) {
+        final RenderComparison candidate = children![index].compareTo(textSpan.children![index]);
+        if (candidate.index > result.index)
+          result = candidate;
+        if (result == RenderComparison.layout)
+          return result;
+      }
+    }
+    return result;
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    if (super != other)
+      return false;
+    return other is TextSpan
+        && other.text == text
+        && other.recognizer == recognizer
+        && other.semanticsLabel == semanticsLabel
+        && listEquals<InlineSpan>(other.children, children);
+  }
+
+  @override
+  int get hashCode => hashValues(
+    super.hashCode,
+    text,
+    recognizer,
+    semanticsLabel,
+    hashList(children),
+  );
+
+  @override
+  String toStringShort() => objectRuntimeType(this, 'TextSpan');
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+
+    properties.add(
+      StringProperty(
+        'text',
+        text,
+        showName: false,
+        defaultValue: null,
+      )
+    );
+    if (style == null && text == null && children == null)
+      properties.add(DiagnosticsNode.message('(empty)'));
+
+    properties.add(DiagnosticsProperty<GestureRecognizer>(
+      'recognizer', recognizer,
+      description: recognizer?.runtimeType.toString(),
+      defaultValue: null,
+    ));
+
+    if (semanticsLabel != null) {
+      properties.add(StringProperty('semanticsLabel', semanticsLabel));
+    }
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    if (children == null)
+      return const <DiagnosticsNode>[];
+    return children!.map<DiagnosticsNode>((InlineSpan child) {
+      // `child` has a non-nullable return type, but might be null when running
+      // with weak checking, so we need to null check it anyway (and ignore the
+      // warning that the null-handling logic is dead code).
+      if (child != null) {
+        return child.toDiagnosticsNode();
+      } else { // ignore: dead_code
+        return DiagnosticsNode.message('<null child>');
+      }
+    }).toList();
+  }
+}
diff --git a/lib/src/painting/text_style.dart b/lib/src/painting/text_style.dart
new file mode 100644
index 0000000..e5b22fa
--- /dev/null
+++ b/lib/src/painting/text_style.dart
@@ -0,0 +1,1275 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' as ui show ParagraphStyle, TextStyle, StrutStyle, lerpDouble, Shadow, FontFeature, TextHeightBehavior;
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'colors.dart';
+import 'strut_style.dart';
+
+const String _kDefaultDebugLabel = 'unknown';
+
+const String _kColorForegroundWarning = 'Cannot provide both a color and a foreground\n'
+    'The color argument is just a shorthand for "foreground: new Paint()..color = color".';
+
+const String _kColorBackgroundWarning = 'Cannot provide both a backgroundColor and a background\n'
+    'The backgroundColor argument is just a shorthand for "background: new Paint()..color = color".';
+
+// The default font size if none is specified. This should be kept in
+// sync with the default values in text_painter.dart, as well as the
+// defaults set in the engine (eg, LibTxt's text_style.h, paragraph_style.h).
+const double _kDefaultFontSize = 14.0;
+
+// Examples can assume:
+// // @dart = 2.9
+// BuildContext context;
+
+/// An immutable style describing how to format and paint text.
+///
+/// ### Bold
+///
+/// {@tool snippet}
+/// Here, a single line of text in a [Text] widget is given a specific style
+/// override. The style is mixed with the ambient [DefaultTextStyle] by the
+/// [Text] widget.
+///
+/// ![Applying the style in this way creates bold text.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_bold.png)
+///
+/// ```dart
+/// Text(
+///   'No, we need bold strokes. We need this plan.',
+///   style: TextStyle(fontWeight: FontWeight.bold),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ### Italics
+///
+/// {@tool snippet}
+/// As in the previous example, the [Text] widget is given a specific style
+/// override which is implicitly mixed with the ambient [DefaultTextStyle].
+///
+/// ![This results in italicized text.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_italics.png)
+///
+/// ```dart
+/// Text(
+///   "Welcome to the present, we're running a real nation.",
+///   style: TextStyle(fontStyle: FontStyle.italic),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ### Opacity and Color
+///
+/// Each line here is progressively more opaque. The base color is
+/// [material.Colors.black], and [Color.withOpacity] is used to create a
+/// derivative color with the desired opacity. The root [TextSpan] for this
+/// [RichText] widget is explicitly given the ambient [DefaultTextStyle], since
+/// [RichText] does not do that automatically. The inner [TextStyle] objects are
+/// implicitly mixed with the parent [TextSpan]'s [TextSpan.style].
+///
+/// If [color] is specified, [foreground] must be null and vice versa. [color] is
+/// treated as a shorthand for `Paint()..color = color`.
+///
+/// If [backgroundColor] is specified, [background] must be null and vice versa.
+/// The [backgroundColor] is treated as a shorthand for
+/// `background: Paint()..color = backgroundColor`.
+///
+/// ![This results in three lines of text that go from lighter to darker in color.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_opacity_and_color.png)
+///
+/// ```dart
+/// RichText(
+///   text: TextSpan(
+///     style: DefaultTextStyle.of(context).style,
+///     children: <TextSpan>[
+///       TextSpan(
+///         text: "You don't have the votes.\n",
+///         style: TextStyle(color: Colors.black.withOpacity(0.6)),
+///       ),
+///       TextSpan(
+///         text: "You don't have the votes!\n",
+///         style: TextStyle(color: Colors.black.withOpacity(0.8)),
+///       ),
+///       TextSpan(
+///         text: "You're gonna need congressional approval and you don't have the votes!\n",
+///         style: TextStyle(color: Colors.black.withOpacity(1.0)),
+///       ),
+///     ],
+///   ),
+/// )
+/// ```
+///
+/// ### Size
+///
+/// {@tool snippet}
+/// In this example, the ambient [DefaultTextStyle] is explicitly manipulated to
+/// obtain a [TextStyle] that doubles the default font size.
+///
+/// ![This results in text that is twice as large as normal.](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_size.png)
+///
+/// ```dart
+/// Text(
+///   "These are wise words, enterprising men quote 'em.",
+///   style: DefaultTextStyle.of(context).style.apply(fontSizeFactor: 2.0),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ### Line height
+///
+/// By default, text will layout with line height as defined by the font.
+/// Font-metrics defined line height may be taller or shorter than the font size.
+/// The [height] property allows manual adjustment of the height of the line as
+/// a multiple of [fontSize]. For most fonts, setting [height] to 1.0 is not
+/// the same as omitting or setting height to null. The following diagram
+/// illustrates the difference between the font-metrics defined line height and
+/// the line height produced with `height: 1.0` (also known as the EM-square):
+///
+/// ![Text height diagram](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_diagram.png)
+///
+/// {@tool snippet}
+/// The [height] property can be used to change the line height. Here, the line
+/// height is set to 5 times the font size, so that the text is very spaced out.
+/// Since the `fontSize` is set to 10, the final height of the line is
+/// 50 pixels.
+///
+/// ```dart
+/// Text(
+///   'Ladies and gentlemen, you coulda been anywhere in the world tonight, but you’re here with us in New York City.',
+///   style: TextStyle(height: 5, fontSize: 10),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// Examples of the resulting heights from different values of `TextStyle.height`:
+///
+/// ![Text height comparison diagram](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_comparison_diagram.png)
+///
+/// See [StrutStyle] for further control of line height at the paragraph level.
+///
+/// ### Wavy red underline with black text
+///
+/// {@tool snippet}
+/// Styles can be combined. In this example, the misspelled word is drawn in
+/// black text and underlined with a wavy red line to indicate a spelling error.
+/// (The remainder is styled according to the Flutter default text styles, not
+/// the ambient [DefaultTextStyle], since no explicit style is given and
+/// [RichText] does not automatically use the ambient [DefaultTextStyle].)
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_wavy_red_underline.png)
+///
+/// ```dart
+/// RichText(
+///   text: TextSpan(
+///     text: "Don't tax the South ",
+///     children: <TextSpan>[
+///       TextSpan(
+///         text: 'cuz',
+///         style: TextStyle(
+///           color: Colors.black,
+///           decoration: TextDecoration.underline,
+///           decorationColor: Colors.red,
+///           decorationStyle: TextDecorationStyle.wavy,
+///         ),
+///       ),
+///       TextSpan(
+///         text: ' we got it made in the shade',
+///       ),
+///     ],
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ### Borders and stroke (Foreground)
+///
+/// {@tool snippet}
+/// To create bordered text, a [Paint] with [Paint.style] set to [PaintingStyle.stroke]
+/// should be provided as a [foreground] paint. The following example uses a [Stack]
+/// to produce a stroke and fill effect.
+///
+/// ![Text border](https://flutter.github.io/assets-for-api-docs/assets/widgets/text_border.png)
+///
+/// ```dart
+/// Stack(
+///   children: <Widget>[
+///     // Stroked text as border.
+///     Text(
+///       'Greetings, planet!',
+///       style: TextStyle(
+///         fontSize: 40,
+///         foreground: Paint()
+///           ..style = PaintingStyle.stroke
+///           ..strokeWidth = 6
+///           ..color = Colors.blue[700],
+///       ),
+///     ),
+///     // Solid text as fill.
+///     Text(
+///       'Greetings, planet!',
+///       style: TextStyle(
+///         fontSize: 40,
+///         color: Colors.grey[300],
+///       ),
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ### Gradients (Foreground)
+///
+/// {@tool snippet}
+/// The [foreground] property also allows effects such as gradients to be
+/// applied to the text. Here we provide a [Paint] with a [ui.Gradient]
+/// shader.
+///
+/// ![Text gradient](https://flutter.github.io/assets-for-api-docs/assets/widgets/text_gradient.png)
+///
+/// ```dart
+/// Text(
+///   'Greetings, planet!',
+///   style: TextStyle(
+///     fontSize: 40,
+///     foreground: Paint()
+///       ..shader = ui.Gradient.linear(
+///         const Offset(0, 20),
+///         const Offset(150, 20),
+///         <Color>[
+///           Colors.red,
+///           Colors.yellow,
+///         ],
+///       )
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ### Custom Fonts
+///
+/// Custom fonts can be declared in the `pubspec.yaml` file as shown below:
+///
+/// ```yaml
+/// flutter:
+///   fonts:
+///     - family: Raleway
+///       fonts:
+///         - asset: fonts/Raleway-Regular.ttf
+///         - asset: fonts/Raleway-Medium.ttf
+///           weight: 500
+///         - asset: assets/fonts/Raleway-SemiBold.ttf
+///           weight: 600
+///      - family: Schyler
+///        fonts:
+///          - asset: fonts/Schyler-Regular.ttf
+///          - asset: fonts/Schyler-Italic.ttf
+///            style: italic
+/// ```
+///
+/// The `family` property determines the name of the font, which you can use in
+/// the [fontFamily] argument. The `asset` property is a path to the font file,
+/// relative to the `pubspec.yaml` file. The `weight` property specifies the
+/// weight of the glyph outlines in the file as an integer multiple of 100
+/// between 100 and 900. This corresponds to the [FontWeight] class and can be
+/// used in the [fontWeight] argument. The `style` property specifies whether the
+/// outlines in the file are `italic` or `normal`. These values correspond to
+/// the [FontStyle] class and can be used in the [fontStyle] argument.
+///
+/// To select a custom font, create [TextStyle] using the [fontFamily]
+/// argument as shown in the example below:
+///
+/// {@tool snippet}
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/text_style_custom_fonts.png)
+///
+/// ```dart
+/// const TextStyle(fontFamily: 'Raleway')
+/// ```
+/// {@end-tool}
+///
+/// To use a font family defined in a package, the `package` argument must be
+/// provided. For instance, suppose the font declaration above is in the
+/// `pubspec.yaml` of a package named `my_package` which the app depends on.
+/// Then creating the TextStyle is done as follows:
+///
+/// ```dart
+/// const TextStyle(fontFamily: 'Raleway', package: 'my_package')
+/// ```
+///
+/// If the package internally uses the font it defines, it should still specify
+/// the `package` argument when creating the text style as in the example above.
+///
+/// A package can also provide font files without declaring a font in its
+/// `pubspec.yaml`. These files should then be in the `lib/` folder of the
+/// package. The font files will not automatically be bundled in the app, instead
+/// the app can use these selectively when declaring a font. Suppose a package
+/// named `my_package` has:
+///
+/// ```
+/// lib/fonts/Raleway-Medium.ttf
+/// ```
+///
+/// Then the app can declare a font like in the example below:
+///
+/// ```yaml
+/// flutter:
+///   fonts:
+///     - family: Raleway
+///       fonts:
+///         - asset: assets/fonts/Raleway-Regular.ttf
+///         - asset: packages/my_package/fonts/Raleway-Medium.ttf
+///           weight: 500
+/// ```
+///
+/// The `lib/` is implied, so it should not be included in the asset path.
+///
+/// In this case, since the app locally defines the font, the TextStyle is
+/// created without the `package` argument:
+///
+/// {@tool snippet}
+/// ```dart
+/// const TextStyle(fontFamily: 'Raleway')
+/// ```
+/// {@end-tool}
+///
+/// ### Custom Font Fallback
+///
+/// A custom [fontFamilyFallback] list can be provided. The list should be an
+/// ordered list of strings of font family names in the order they will be attempted.
+///
+/// The fonts in [fontFamilyFallback] will be used only if the requested glyph is
+/// not present in the [fontFamily].
+///
+/// The fallback order is:
+///
+///  * [fontFamily]
+///  * [fontFamilyFallback] in order of first to last.
+///  * System fallback fonts which will vary depending on platform.
+///
+/// The glyph used will always be the first matching version in fallback order.
+///
+/// The [fontFamilyFallback] property is commonly used to specify different font
+/// families for multilingual text spans as well as separate fonts for glyphs such
+/// as emojis.
+///
+/// {@tool snippet}
+/// In the following example, any glyphs not present in the font `Raleway` will be attempted
+/// to be resolved with `Noto Sans CJK SC`, and then with `Noto Color Emoji`:
+///
+/// ```dart
+/// const TextStyle(
+///   fontFamily: 'Raleway',
+///   fontFamilyFallback: <String>[
+///     'Noto Sans CJK SC',
+///     'Noto Color Emoji',
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// If all custom fallback font families are exhausted and no match was found
+/// or no custom fallback was provided, the platform font fallback will be used.
+///
+/// ### Inconsistent platform fonts
+///
+/// Since Flutter's font discovery for default fonts depends on the fonts present
+/// on the device, it is not safe to assume all default fonts will be available or
+/// consistent across devices.
+///
+/// A known example of this is that Samsung devices ship with a CJK font that has
+/// smaller line spacing than the Android default. This results in Samsung devices
+/// displaying more tightly spaced text than on other Android devices when no
+/// custom font is specified.
+///
+/// To avoid this, a custom font should be specified if absolute font consistency
+/// is required for your application.
+///
+/// See also:
+///
+///  * [Text], the widget for showing text in a single style.
+///  * [DefaultTextStyle], the widget that specifies the default text styles for
+///    [Text] widgets, configured using a [TextStyle].
+///  * [RichText], the widget for showing a paragraph of mix-style text.
+///  * [TextSpan], the class that wraps a [TextStyle] for the purposes of
+///    passing it to a [RichText].
+///  * [TextStyle](https://api.flutter.dev/flutter/dart-ui/TextStyle-class.html), the class in the [dart:ui] library.
+///  * Cookbook: [Use a custom font](https://flutter.dev/docs/cookbook/design/fonts)
+///  * Cookbook: [Use themes to share colors and font styles](https://flutter.dev/docs/cookbook/design/themes)
+@immutable
+class TextStyle with Diagnosticable {
+  /// Creates a text style.
+  ///
+  /// The `package` argument must be non-null if the font family is defined in a
+  /// package. It is combined with the `fontFamily` argument to set the
+  /// [fontFamily] property.
+  const TextStyle({
+    this.inherit = true,
+    this.color,
+    this.backgroundColor,
+    this.fontSize,
+    this.fontWeight,
+    this.fontStyle,
+    this.letterSpacing,
+    this.wordSpacing,
+    this.textBaseline,
+    this.height,
+    this.locale,
+    this.foreground,
+    this.background,
+    this.shadows,
+    this.fontFeatures,
+    this.decoration,
+    this.decorationColor,
+    this.decorationStyle,
+    this.decorationThickness,
+    this.debugLabel,
+    String? fontFamily,
+    List<String>? fontFamilyFallback,
+    String? package,
+  }) : fontFamily = package == null ? fontFamily : 'packages/$package/$fontFamily',
+       _fontFamilyFallback = fontFamilyFallback,
+       _package = package,
+       assert(inherit != null),
+       assert(color == null || foreground == null, _kColorForegroundWarning),
+       assert(backgroundColor == null || background == null, _kColorBackgroundWarning);
+
+
+  /// Whether null values are replaced with their value in an ancestor text
+  /// style (e.g., in a [TextSpan] tree).
+  ///
+  /// If this is false, properties that don't have explicit values will revert
+  /// to the defaults: white in color, a font size of 10 pixels, in a sans-serif
+  /// font face.
+  final bool inherit;
+
+  /// The color to use when painting the text.
+  ///
+  /// If [foreground] is specified, this value must be null. The [color] property
+  /// is shorthand for `Paint()..color = color`.
+  ///
+  /// In [merge], [apply], and [lerp], conflicts between [color] and [foreground]
+  /// specification are resolved in [foreground]'s favor - i.e. if [foreground] is
+  /// specified in one place, it will dominate [color] in another.
+  final Color? color;
+
+  /// The color to use as the background for the text.
+  ///
+  /// If [background] is specified, this value must be null. The
+  /// [backgroundColor] property is shorthand for
+  /// `background: Paint()..color = backgroundColor`.
+  ///
+  /// In [merge], [apply], and [lerp], conflicts between [backgroundColor] and [background]
+  /// specification are resolved in [background]'s favor - i.e. if [background] is
+  /// specified in one place, it will dominate [color] in another.
+  final Color? backgroundColor;
+
+  /// The name of the font to use when painting the text (e.g., Roboto). If the
+  /// font is defined in a package, this will be prefixed with
+  /// 'packages/package_name/' (e.g. 'packages/cool_fonts/Roboto'). The
+  /// prefixing is done by the constructor when the `package` argument is
+  /// provided.
+  ///
+  /// The value provided in [fontFamily] will act as the preferred/first font
+  /// family that glyphs are looked for in, followed in order by the font families
+  /// in [fontFamilyFallback]. When [fontFamily] is null or not provided, the
+  /// first value in [fontFamilyFallback] acts as the preferred/first font
+  /// family. When neither is provided, then the default platform font will
+  /// be used.
+  final String? fontFamily;
+
+  /// The ordered list of font families to fall back on when a glyph cannot be
+  /// found in a higher priority font family.
+  ///
+  /// The value provided in [fontFamily] will act as the preferred/first font
+  /// family that glyphs are looked for in, followed in order by the font families
+  /// in [fontFamilyFallback]. If all font families are exhausted and no match
+  /// was found, the default platform font family will be used instead.
+  ///
+  /// When [fontFamily] is null or not provided, the first value in [fontFamilyFallback]
+  /// acts as the preferred/first font family. When neither is provided, then
+  /// the default platform font will be used. Providing an empty list or null
+  /// for this property is the same as omitting it.
+  ///
+  /// For example, if a glyph is not found in [fontFamily], then each font family
+  /// in [fontFamilyFallback] will be searched in order until it is found. If it
+  /// is not found, then a box will be drawn in its place.
+  ///
+  /// If the font is defined in a package, each font family in the list will be
+  /// prefixed with 'packages/package_name/' (e.g. 'packages/cool_fonts/Roboto').
+  /// The package name should be provided by the `package` argument in the
+  /// constructor.
+  List<String>? get fontFamilyFallback => _package != null && _fontFamilyFallback != null ? _fontFamilyFallback!.map((String str) => 'packages/$_package/$str').toList() : _fontFamilyFallback;
+  final List<String>? _fontFamilyFallback;
+
+  // This is stored in order to prefix the fontFamilies in _fontFamilyFallback
+  // in the [fontFamilyFallback] getter.
+  final String? _package;
+
+  /// The size of glyphs (in logical pixels) to use when painting the text.
+  ///
+  /// During painting, the [fontSize] is multiplied by the current
+  /// `textScaleFactor` to let users make it easier to read text by increasing
+  /// its size.
+  ///
+  /// [getParagraphStyle] will default to 14 logical pixels if the font size
+  /// isn't specified here.
+  final double? fontSize;
+
+  /// The typeface thickness to use when painting the text (e.g., bold).
+  final FontWeight? fontWeight;
+
+  /// The typeface variant to use when drawing the letters (e.g., italics).
+  final FontStyle? fontStyle;
+
+  /// The amount of space (in logical pixels) to add between each letter.
+  /// A negative value can be used to bring the letters closer.
+  final double? letterSpacing;
+
+  /// The amount of space (in logical pixels) to add at each sequence of
+  /// white-space (i.e. between each word). A negative value can be used to
+  /// bring the words closer.
+  final double? wordSpacing;
+
+  /// The common baseline that should be aligned between this text span and its
+  /// parent text span, or, for the root text spans, with the line box.
+  final TextBaseline? textBaseline;
+
+  /// The height of this text span, as a multiple of the font size.
+  ///
+  /// When [height] is null or omitted, the line height will be determined
+  /// by the font's metrics directly, which may differ from the fontSize.
+  /// When [height] is non-null, the line height of the span of text will be a
+  /// multiple of [fontSize] and be exactly `fontSize * height` logical pixels
+  /// tall.
+  ///
+  /// For most fonts, setting [height] to 1.0 is not the same as omitting or
+  /// setting height to null because the [fontSize] sets the height of the EM-square,
+  /// which is different than the font provided metrics for line height. The
+  /// following diagram illustrates the difference between the font-metrics
+  /// defined line height and the line height produced with `height: 1.0`
+  /// (which forms the upper and lower edges of the EM-square):
+  ///
+  /// ![Text height diagram](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_diagram.png)
+  ///
+  /// Examples of the resulting line heights from different values of `TextStyle.height`:
+  ///
+  /// ![Text height comparison diagram](https://flutter.github.io/assets-for-api-docs/assets/painting/text_height_comparison_diagram.png)
+  ///
+  /// See [StrutStyle] for further control of line height at the paragraph level.
+  final double? height;
+
+  /// The locale used to select region-specific glyphs.
+  ///
+  /// This property is rarely set. Typically the locale used to select
+  /// region-specific glyphs is defined by the text widget's [BuildContext]
+  /// using `Localizations.localeOf(context)`. For example [RichText] defines
+  /// its locale this way. However, a rich text widget's [TextSpan]s could
+  /// specify text styles with different explicit locales in order to select
+  /// different region-specific glyphs for each text span.
+  final Locale? locale;
+
+  /// The paint drawn as a foreground for the text.
+  ///
+  /// The value should ideally be cached and reused each time if multiple text
+  /// styles are created with the same paint settings. Otherwise, each time it
+  /// will appear like the style changed, which will result in unnecessary
+  /// updates all the way through the framework.
+  ///
+  /// If [color] is specified, this value must be null. The [color] property
+  /// is shorthand for `Paint()..color = color`.
+  ///
+  /// In [merge], [apply], and [lerp], conflicts between [color] and [foreground]
+  /// specification are resolved in [foreground]'s favor - i.e. if [foreground] is
+  /// specified in one place, it will dominate [color] in another.
+  final Paint? foreground;
+
+  /// The paint drawn as a background for the text.
+  ///
+  /// The value should ideally be cached and reused each time if multiple text
+  /// styles are created with the same paint settings. Otherwise, each time it
+  /// will appear like the style changed, which will result in unnecessary
+  /// updates all the way through the framework.
+  ///
+  /// If [backgroundColor] is specified, this value must be null. The
+  /// [backgroundColor] property is shorthand for
+  /// `background: Paint()..color = backgroundColor`.
+  ///
+  /// In [merge], [apply], and [lerp], conflicts between [backgroundColor] and
+  /// [background] specification are resolved in [background]'s favor - i.e. if
+  /// [background] is specified in one place, it will dominate [backgroundColor]
+  /// in another.
+  final Paint? background;
+
+  /// The decorations to paint near the text (e.g., an underline).
+  ///
+  /// Multiple decorations can be applied using [TextDecoration.combine].
+  final TextDecoration? decoration;
+
+  /// The color in which to paint the text decorations.
+  final Color? decorationColor;
+
+  /// The style in which to paint the text decorations (e.g., dashed).
+  final TextDecorationStyle? decorationStyle;
+
+  /// The thickness of the decoration stroke as a multiplier of the thickness
+  /// defined by the font.
+  ///
+  /// The font provides a base stroke width for [decoration]s which scales off
+  /// of the [fontSize]. This property may be used to achieve a thinner or
+  /// thicker decoration stroke, without changing the [fontSize]. For example,
+  /// a [decorationThickness] of 2.0 will draw a decoration twice as thick as
+  /// the font defined decoration thickness.
+  ///
+  /// {@tool snippet}
+  /// To achieve a bolded strike-through, we can apply a thicker stroke for the
+  /// decoration.
+  ///
+  /// ```dart
+  /// Text(
+  ///   'This has a very BOLD strike through!',
+  ///   style: TextStyle(
+  ///     decoration: TextDecoration.lineThrough,
+  ///     decorationThickness: 2.85,
+  ///   ),
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@tool snippet}
+  /// We can apply a very thin and subtle wavy underline (perhaps, when words
+  /// are misspelled) by using a [decorationThickness] < 1.0.
+  ///
+  /// ```dart
+  /// Text(
+  ///   'oopsIforgottousespaces!',
+  ///   style: TextStyle(
+  ///     decoration: TextDecoration.underline,
+  ///     decorationStyle: TextDecorationStyle.wavy,
+  ///     decorationColor: Colors.red,
+  ///     decorationThickness: 0.5,
+  ///   ),
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// The default [decorationThickness] is 1.0, which will use the font's base
+  /// stroke thickness/width.
+  final double? decorationThickness;
+
+  /// A human-readable description of this text style.
+  ///
+  /// This property is maintained only in debug builds.
+  ///
+  /// When merging ([merge]), copying ([copyWith]), modifying using [apply], or
+  /// interpolating ([lerp]), the label of the resulting style is marked with
+  /// the debug labels of the original styles. This helps figuring out where a
+  /// particular text style came from.
+  ///
+  /// This property is not considered when comparing text styles using `==` or
+  /// [compareTo], and it does not affect [hashCode].
+  final String? debugLabel;
+
+  /// A list of [Shadow]s that will be painted underneath the text.
+  ///
+  /// Multiple shadows are supported to replicate lighting from multiple light
+  /// sources.
+  ///
+  /// Shadows must be in the same order for [TextStyle] to be considered as
+  /// equivalent as order produces differing transparency.
+  final List<ui.Shadow>? shadows;
+
+  /// A list of [FontFeature]s that affect how the font selects glyphs.
+  ///
+  /// Some fonts support multiple variants of how a given character can be
+  /// rendered.  For example, a font might provide both proportional and
+  /// tabular numbers, or it might offer versions of the zero digit with
+  /// and without slashes.  [FontFeature]s can be used to select which of
+  /// these variants will be used for rendering.
+  final List<ui.FontFeature>? fontFeatures;
+
+  /// Creates a copy of this text style but with the given fields replaced with
+  /// the new values.
+  ///
+  /// One of [color] or [foreground] must be null, and if this has [foreground]
+  /// specified it will be given preference over any color parameter.
+  ///
+  /// One of [backgroundColor] or [background] must be null, and if this has
+  /// [background] specified it will be given preference over any
+  /// backgroundColor parameter.
+  TextStyle copyWith({
+    bool? inherit,
+    Color? color,
+    Color? backgroundColor,
+    String? fontFamily,
+    List<String>? fontFamilyFallback,
+    double? fontSize,
+    FontWeight? fontWeight,
+    FontStyle? fontStyle,
+    double? letterSpacing,
+    double? wordSpacing,
+    TextBaseline? textBaseline,
+    double? height,
+    Locale? locale,
+    Paint? foreground,
+    Paint? background,
+    List<ui.Shadow>? shadows,
+    List<ui.FontFeature>? fontFeatures,
+    TextDecoration? decoration,
+    Color? decorationColor,
+    TextDecorationStyle? decorationStyle,
+    double? decorationThickness,
+    String? debugLabel,
+  }) {
+    assert(color == null || foreground == null, _kColorForegroundWarning);
+    assert(backgroundColor == null || background == null, _kColorBackgroundWarning);
+    String? newDebugLabel;
+    assert(() {
+      if (this.debugLabel != null)
+        newDebugLabel = debugLabel ?? '(${this.debugLabel}).copyWith';
+      return true;
+    }());
+    return TextStyle(
+      inherit: inherit ?? this.inherit,
+      color: this.foreground == null && foreground == null ? color ?? this.color : null,
+      backgroundColor: this.background == null && background == null ? backgroundColor ?? this.backgroundColor : null,
+      fontFamily: fontFamily ?? this.fontFamily,
+      fontFamilyFallback: fontFamilyFallback ?? this.fontFamilyFallback,
+      fontSize: fontSize ?? this.fontSize,
+      fontWeight: fontWeight ?? this.fontWeight,
+      fontStyle: fontStyle ?? this.fontStyle,
+      letterSpacing: letterSpacing ?? this.letterSpacing,
+      wordSpacing: wordSpacing ?? this.wordSpacing,
+      textBaseline: textBaseline ?? this.textBaseline,
+      height: height ?? this.height,
+      locale: locale ?? this.locale,
+      foreground: foreground ?? this.foreground,
+      background: background ?? this.background,
+      shadows: shadows ?? this.shadows,
+      fontFeatures: fontFeatures ?? this.fontFeatures,
+      decoration: decoration ?? this.decoration,
+      decorationColor: decorationColor ?? this.decorationColor,
+      decorationStyle: decorationStyle ?? this.decorationStyle,
+      decorationThickness: decorationThickness ?? this.decorationThickness,
+      debugLabel: newDebugLabel,
+    );
+  }
+
+  /// Creates a copy of this text style replacing or altering the specified
+  /// properties.
+  ///
+  /// The non-numeric properties [color], [fontFamily], [decoration],
+  /// [decorationColor] and [decorationStyle] are replaced with the new values.
+  ///
+  /// [foreground] will be given preference over [color] if it is not null and
+  /// [background] will be given preference over [backgroundColor] if it is not
+  /// null.
+  ///
+  /// The numeric properties are multiplied by the given factors and then
+  /// incremented by the given deltas.
+  ///
+  /// For example, `style.apply(fontSizeFactor: 2.0, fontSizeDelta: 1.0)` would
+  /// return a [TextStyle] whose [fontSize] is `style.fontSize * 2.0 + 1.0`.
+  ///
+  /// For the [fontWeight], the delta is applied to the [FontWeight] enum index
+  /// values, so that for instance `style.apply(fontWeightDelta: -2)` when
+  /// applied to a `style` whose [fontWeight] is [FontWeight.w500] will return a
+  /// [TextStyle] with a [FontWeight.w300].
+  ///
+  /// The numeric arguments must not be null.
+  ///
+  /// If the underlying values are null, then the corresponding factors and/or
+  /// deltas must not be specified.
+  ///
+  /// If [foreground] is specified on this object, then applying [color] here
+  /// will have no effect and if [background] is specified on this object, then
+  /// applying [backgroundColor] here will have no effect either.
+  TextStyle apply({
+    Color? color,
+    Color? backgroundColor,
+    TextDecoration? decoration,
+    Color? decorationColor,
+    TextDecorationStyle? decorationStyle,
+    double decorationThicknessFactor = 1.0,
+    double decorationThicknessDelta = 0.0,
+    String? fontFamily,
+    List<String>? fontFamilyFallback,
+    double fontSizeFactor = 1.0,
+    double fontSizeDelta = 0.0,
+    int fontWeightDelta = 0,
+    FontStyle? fontStyle,
+    double letterSpacingFactor = 1.0,
+    double letterSpacingDelta = 0.0,
+    double wordSpacingFactor = 1.0,
+    double wordSpacingDelta = 0.0,
+    double heightFactor = 1.0,
+    double heightDelta = 0.0,
+    TextBaseline? textBaseline,
+    Locale? locale,
+    List<ui.Shadow>? shadows,
+    List<ui.FontFeature>? fontFeatures,
+  }) {
+    assert(fontSizeFactor != null);
+    assert(fontSizeDelta != null);
+    assert(fontSize != null || (fontSizeFactor == 1.0 && fontSizeDelta == 0.0));
+    assert(fontWeightDelta != null);
+    assert(fontWeight != null || fontWeightDelta == 0.0);
+    assert(letterSpacingFactor != null);
+    assert(letterSpacingDelta != null);
+    assert(letterSpacing != null || (letterSpacingFactor == 1.0 && letterSpacingDelta == 0.0));
+    assert(wordSpacingFactor != null);
+    assert(wordSpacingDelta != null);
+    assert(wordSpacing != null || (wordSpacingFactor == 1.0 && wordSpacingDelta == 0.0));
+    assert(heightFactor != null);
+    assert(heightDelta != null);
+    assert(decorationThicknessFactor != null);
+    assert(decorationThicknessDelta != null);
+    assert(decorationThickness != null || (decorationThicknessFactor == 1.0 && decorationThicknessDelta == 0.0));
+
+    String? modifiedDebugLabel;
+    assert(() {
+      if (debugLabel != null)
+        modifiedDebugLabel = '($debugLabel).apply';
+      return true;
+    }());
+
+    return TextStyle(
+      inherit: inherit,
+      color: foreground == null ? color ?? this.color : null,
+      backgroundColor: background == null ? backgroundColor ?? this.backgroundColor : null,
+      fontFamily: fontFamily ?? this.fontFamily,
+      fontFamilyFallback: fontFamilyFallback ?? this.fontFamilyFallback,
+      fontSize: fontSize == null ? null : fontSize! * fontSizeFactor + fontSizeDelta,
+      fontWeight: fontWeight == null ? null : FontWeight.values[(fontWeight!.index + fontWeightDelta).clamp(0, FontWeight.values.length - 1)],
+      fontStyle: fontStyle ?? this.fontStyle,
+      letterSpacing: letterSpacing == null ? null : letterSpacing! * letterSpacingFactor + letterSpacingDelta,
+      wordSpacing: wordSpacing == null ? null : wordSpacing! * wordSpacingFactor + wordSpacingDelta,
+      textBaseline: textBaseline ?? this.textBaseline,
+      height: height == null ? null : height! * heightFactor + heightDelta,
+      locale: locale ?? this.locale,
+      foreground: foreground,
+      background: background,
+      shadows: shadows ?? this.shadows,
+      fontFeatures: fontFeatures ?? this.fontFeatures,
+      decoration: decoration ?? this.decoration,
+      decorationColor: decorationColor ?? this.decorationColor,
+      decorationStyle: decorationStyle ?? this.decorationStyle,
+      decorationThickness: decorationThickness == null ? null : decorationThickness! * decorationThicknessFactor + decorationThicknessDelta,
+      debugLabel: modifiedDebugLabel,
+    );
+  }
+
+  /// Returns a new text style that is a combination of this style and the given
+  /// [other] style.
+  ///
+  /// If the given [other] text style has its [TextStyle.inherit] set to true,
+  /// its null properties are replaced with the non-null properties of this text
+  /// style. The [other] style _inherits_ the properties of this style. Another
+  /// way to think of it is that the "missing" properties of the [other] style
+  /// are _filled_ by the properties of this style.
+  ///
+  /// If the given [other] text style has its [TextStyle.inherit] set to false,
+  /// returns the given [other] style unchanged. The [other] style does not
+  /// inherit properties of this style.
+  ///
+  /// If the given text style is null, returns this text style.
+  ///
+  /// One of [color] or [foreground] must be null, and if this or `other` has
+  /// [foreground] specified it will be given preference over any color parameter.
+  ///
+  /// Similarly, One of [backgroundColor] or [background] must be null, and if
+  /// this or `other` has [background] specified it will be given preference
+  /// over any backgroundColor parameter.
+  TextStyle merge(TextStyle? other) {
+    if (other == null)
+      return this;
+    if (!other.inherit)
+      return other;
+
+    String? mergedDebugLabel;
+    assert(() {
+      if (other.debugLabel != null || debugLabel != null)
+        mergedDebugLabel = '(${debugLabel ?? _kDefaultDebugLabel}).merge(${other.debugLabel ?? _kDefaultDebugLabel})';
+      return true;
+    }());
+
+    return copyWith(
+      color: other.color,
+      backgroundColor: other.backgroundColor,
+      fontFamily: other.fontFamily,
+      fontFamilyFallback: other.fontFamilyFallback,
+      fontSize: other.fontSize,
+      fontWeight: other.fontWeight,
+      fontStyle: other.fontStyle,
+      letterSpacing: other.letterSpacing,
+      wordSpacing: other.wordSpacing,
+      textBaseline: other.textBaseline,
+      height: other.height,
+      locale: other.locale,
+      foreground: other.foreground,
+      background: other.background,
+      shadows: other.shadows,
+      fontFeatures: other.fontFeatures,
+      decoration: other.decoration,
+      decorationColor: other.decorationColor,
+      decorationStyle: other.decorationStyle,
+      decorationThickness: other.decorationThickness,
+      debugLabel: mergedDebugLabel,
+    );
+  }
+
+  /// Interpolate between two text styles.
+  ///
+  /// This will not work well if the styles don't set the same fields.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  ///
+  /// If [foreground] is specified on either of `a` or `b`, both will be treated
+  /// as if they have a [foreground] paint (creating a new [Paint] if necessary
+  /// based on the [color] property).
+  ///
+  /// If [background] is specified on either of `a` or `b`, both will be treated
+  /// as if they have a [background] paint (creating a new [Paint] if necessary
+  /// based on the [backgroundColor] property).
+  static TextStyle? lerp(TextStyle? a, TextStyle? b, double t) {
+    assert(t != null);
+    assert(a == null || b == null || a.inherit == b.inherit);
+    if (a == null && b == null) {
+      return null;
+    }
+
+    String? lerpDebugLabel;
+    assert(() {
+      lerpDebugLabel = 'lerp(${a?.debugLabel ?? _kDefaultDebugLabel} ⎯${t.toStringAsFixed(1)}→ ${b?.debugLabel ?? _kDefaultDebugLabel})';
+      return true;
+    }());
+
+    if (a == null) {
+      return TextStyle(
+        inherit: b!.inherit,
+        color: Color.lerp(null, b.color, t),
+        backgroundColor: Color.lerp(null, b.backgroundColor, t),
+        fontFamily: t < 0.5 ? null : b.fontFamily,
+        fontFamilyFallback: t < 0.5 ? null : b.fontFamilyFallback,
+        fontSize: t < 0.5 ? null : b.fontSize,
+        fontWeight: FontWeight.lerp(null, b.fontWeight, t),
+        fontStyle: t < 0.5 ? null : b.fontStyle,
+        letterSpacing: t < 0.5 ? null : b.letterSpacing,
+        wordSpacing: t < 0.5 ? null : b.wordSpacing,
+        textBaseline: t < 0.5 ? null : b.textBaseline,
+        height: t < 0.5 ? null : b.height,
+        locale: t < 0.5 ? null : b.locale,
+        foreground: t < 0.5 ? null : b.foreground,
+        background: t < 0.5 ? null : b.background,
+        decoration: t < 0.5 ? null : b.decoration,
+        shadows: t < 0.5 ? null : b.shadows,
+        fontFeatures: t < 0.5 ? null : b.fontFeatures,
+        decorationColor: Color.lerp(null, b.decorationColor, t),
+        decorationStyle: t < 0.5 ? null : b.decorationStyle,
+        decorationThickness: t < 0.5 ? null : b.decorationThickness,
+        debugLabel: lerpDebugLabel,
+      );
+    }
+
+    if (b == null) {
+      return TextStyle(
+        inherit: a.inherit,
+        color: Color.lerp(a.color, null, t),
+        backgroundColor: Color.lerp(null, a.backgroundColor, t),
+        fontFamily: t < 0.5 ? a.fontFamily : null,
+        fontFamilyFallback: t < 0.5 ? a.fontFamilyFallback : null,
+        fontSize: t < 0.5 ? a.fontSize : null,
+        fontWeight: FontWeight.lerp(a.fontWeight, null, t),
+        fontStyle: t < 0.5 ? a.fontStyle : null,
+        letterSpacing: t < 0.5 ? a.letterSpacing : null,
+        wordSpacing: t < 0.5 ? a.wordSpacing : null,
+        textBaseline: t < 0.5 ? a.textBaseline : null,
+        height: t < 0.5 ? a.height : null,
+        locale: t < 0.5 ? a.locale : null,
+        foreground: t < 0.5 ? a.foreground : null,
+        background: t < 0.5 ? a.background : null,
+        shadows: t < 0.5 ? a.shadows : null,
+        fontFeatures: t < 0.5 ? a.fontFeatures : null,
+        decoration: t < 0.5 ? a.decoration : null,
+        decorationColor: Color.lerp(a.decorationColor, null, t),
+        decorationStyle: t < 0.5 ? a.decorationStyle : null,
+        decorationThickness: t < 0.5 ? a.decorationThickness : null,
+        debugLabel: lerpDebugLabel,
+      );
+    }
+
+    return TextStyle(
+      inherit: b.inherit,
+      color: a.foreground == null && b.foreground == null ? Color.lerp(a.color, b.color, t) : null,
+      backgroundColor: a.background == null && b.background == null ? Color.lerp(a.backgroundColor, b.backgroundColor, t) : null,
+      fontFamily: t < 0.5 ? a.fontFamily : b.fontFamily,
+      fontFamilyFallback: t < 0.5 ? a.fontFamilyFallback : b.fontFamilyFallback,
+      fontSize: ui.lerpDouble(a.fontSize ?? b.fontSize, b.fontSize ?? a.fontSize, t),
+      fontWeight: FontWeight.lerp(a.fontWeight, b.fontWeight, t),
+      fontStyle: t < 0.5 ? a.fontStyle : b.fontStyle,
+      letterSpacing: ui.lerpDouble(a.letterSpacing ?? b.letterSpacing, b.letterSpacing ?? a.letterSpacing, t),
+      wordSpacing: ui.lerpDouble(a.wordSpacing ?? b.wordSpacing, b.wordSpacing ?? a.wordSpacing, t),
+      textBaseline: t < 0.5 ? a.textBaseline : b.textBaseline,
+      height: ui.lerpDouble(a.height ?? b.height, b.height ?? a.height, t),
+      locale: t < 0.5 ? a.locale : b.locale,
+      foreground: (a.foreground != null || b.foreground != null)
+        ? t < 0.5
+          ? a.foreground ?? (Paint()..color = a.color!)
+          : b.foreground ?? (Paint()..color = b.color!)
+        : null,
+      background: (a.background != null || b.background != null)
+        ? t < 0.5
+          ? a.background ?? (Paint()..color = a.backgroundColor!)
+          : b.background ?? (Paint()..color = b.backgroundColor!)
+        : null,
+      shadows: t < 0.5 ? a.shadows : b.shadows,
+      fontFeatures: t < 0.5 ? a.fontFeatures : b.fontFeatures,
+      decoration: t < 0.5 ? a.decoration : b.decoration,
+      decorationColor: Color.lerp(a.decorationColor, b.decorationColor, t),
+      decorationStyle: t < 0.5 ? a.decorationStyle : b.decorationStyle,
+      decorationThickness: ui.lerpDouble(a.decorationThickness ?? b.decorationThickness, b.decorationThickness ?? a.decorationThickness, t),
+      debugLabel: lerpDebugLabel,
+    );
+  }
+
+  /// The style information for text runs, encoded for use by `dart:ui`.
+  ui.TextStyle getTextStyle({ double textScaleFactor = 1.0 }) {
+    return ui.TextStyle(
+      color: color,
+      decoration: decoration,
+      decorationColor: decorationColor,
+      decorationStyle: decorationStyle,
+      decorationThickness: decorationThickness,
+      fontWeight: fontWeight,
+      fontStyle: fontStyle,
+      textBaseline: textBaseline,
+      fontFamily: fontFamily,
+      fontFamilyFallback: fontFamilyFallback,
+      fontSize: fontSize == null ? null : fontSize! * textScaleFactor,
+      letterSpacing: letterSpacing,
+      wordSpacing: wordSpacing,
+      height: height,
+      locale: locale,
+      foreground: foreground,
+      background: background ?? (backgroundColor != null
+        ? (Paint()..color = backgroundColor!)
+        : null
+      ),
+      shadows: shadows,
+      fontFeatures: fontFeatures,
+    );
+  }
+
+  /// The style information for paragraphs, encoded for use by `dart:ui`.
+  ///
+  /// The `textScaleFactor` argument must not be null. If omitted, it defaults
+  /// to 1.0. The other arguments may be null. The `maxLines` argument, if
+  /// specified and non-null, must be greater than zero.
+  ///
+  /// If the font size on this style isn't set, it will default to 14 logical
+  /// pixels.
+  ui.ParagraphStyle getParagraphStyle({
+    TextAlign? textAlign,
+    TextDirection? textDirection,
+    double textScaleFactor = 1.0,
+    String? ellipsis,
+    int? maxLines,
+    ui.TextHeightBehavior? textHeightBehavior,
+    Locale? locale,
+    String? fontFamily,
+    double? fontSize,
+    FontWeight? fontWeight,
+    FontStyle? fontStyle,
+    double? height,
+    StrutStyle? strutStyle,
+  }) {
+    assert(textScaleFactor != null);
+    assert(maxLines == null || maxLines > 0);
+    return ui.ParagraphStyle(
+      textAlign: textAlign,
+      textDirection: textDirection,
+      // Here, we establish the contents of this TextStyle as the paragraph's default font
+      // unless an override is passed in.
+      fontWeight: fontWeight ?? this.fontWeight,
+      fontStyle: fontStyle ?? this.fontStyle,
+      fontFamily: fontFamily ?? this.fontFamily,
+      fontSize: (fontSize ?? this.fontSize ?? _kDefaultFontSize) * textScaleFactor,
+      height: height ?? this.height,
+      textHeightBehavior: textHeightBehavior,
+      strutStyle: strutStyle == null ? null : ui.StrutStyle(
+        fontFamily: strutStyle.fontFamily,
+        fontFamilyFallback: strutStyle.fontFamilyFallback,
+        fontSize: strutStyle.fontSize == null ? null : strutStyle.fontSize! * textScaleFactor,
+        height: strutStyle.height,
+        leading: strutStyle.leading,
+        fontWeight: strutStyle.fontWeight,
+        fontStyle: strutStyle.fontStyle,
+        forceStrutHeight: strutStyle.forceStrutHeight,
+      ),
+      maxLines: maxLines,
+      ellipsis: ellipsis,
+      locale: locale,
+    );
+  }
+
+  /// Describe the difference between this style and another, in terms of how
+  /// much damage it will make to the rendering.
+  ///
+  /// See also:
+  ///
+  ///  * [TextSpan.compareTo], which does the same thing for entire [TextSpan]s.
+  RenderComparison compareTo(TextStyle other) {
+    if (identical(this, other))
+      return RenderComparison.identical;
+    if (inherit != other.inherit ||
+        fontFamily != other.fontFamily ||
+        fontSize != other.fontSize ||
+        fontWeight != other.fontWeight ||
+        fontStyle != other.fontStyle ||
+        letterSpacing != other.letterSpacing ||
+        wordSpacing != other.wordSpacing ||
+        textBaseline != other.textBaseline ||
+        height != other.height ||
+        locale != other.locale ||
+        foreground != other.foreground ||
+        background != other.background ||
+        !listEquals(shadows, other.shadows) ||
+        !listEquals(fontFeatures, other.fontFeatures) ||
+        !listEquals(fontFamilyFallback, other.fontFamilyFallback))
+      return RenderComparison.layout;
+    if (color != other.color ||
+        backgroundColor != other.backgroundColor ||
+        decoration != other.decoration ||
+        decorationColor != other.decorationColor ||
+        decorationStyle != other.decorationStyle ||
+        decorationThickness != other.decorationThickness)
+      return RenderComparison.paint;
+    return RenderComparison.identical;
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is TextStyle
+        && other.inherit == inherit
+        && other.color == color
+        && other.backgroundColor == backgroundColor
+        && other.fontFamily == fontFamily
+        && other.fontSize == fontSize
+        && other.fontWeight == fontWeight
+        && other.fontStyle == fontStyle
+        && other.letterSpacing == letterSpacing
+        && other.wordSpacing == wordSpacing
+        && other.textBaseline == textBaseline
+        && other.height == height
+        && other.locale == locale
+        && other.foreground == foreground
+        && other.background == background
+        && other.decoration == decoration
+        && other.decorationColor == decorationColor
+        && other.decorationStyle == decorationStyle
+        && other.decorationThickness == decorationThickness
+        && listEquals(other.shadows, shadows)
+        && listEquals(other.fontFeatures, fontFeatures)
+        && listEquals(other.fontFamilyFallback, fontFamilyFallback);
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      inherit,
+      color,
+      backgroundColor,
+      fontFamily,
+      fontSize,
+      fontWeight,
+      fontStyle,
+      letterSpacing,
+      wordSpacing,
+      textBaseline,
+      height,
+      locale,
+      foreground,
+      background,
+      decoration,
+      decorationColor,
+      decorationStyle,
+      hashList(shadows),
+      hashList(fontFeatures),
+      hashList(fontFamilyFallback),
+    );
+  }
+
+  @override
+  String toStringShort() => objectRuntimeType(this, 'TextStyle');
+
+  /// Adds all properties prefixing property names with the optional `prefix`.
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties, { String prefix = '' }) {
+    super.debugFillProperties(properties);
+    if (debugLabel != null)
+      properties.add(MessageProperty('${prefix}debugLabel', debugLabel!));
+    final List<DiagnosticsNode> styles = <DiagnosticsNode>[
+      ColorProperty('${prefix}color', color, defaultValue: null),
+      ColorProperty('${prefix}backgroundColor', backgroundColor, defaultValue: null),
+      StringProperty('${prefix}family', fontFamily, defaultValue: null, quoted: false),
+      IterableProperty<String>('${prefix}familyFallback', fontFamilyFallback, defaultValue: null),
+      DoubleProperty('${prefix}size', fontSize, defaultValue: null),
+    ];
+    String? weightDescription;
+    if (fontWeight != null) {
+      weightDescription = '${fontWeight!.index + 1}00';
+    }
+    // TODO(jacobr): switch this to use enumProperty which will either cause the
+    // weight description to change to w600 from 600 or require existing
+    // enumProperty to handle this special case.
+    styles.add(DiagnosticsProperty<FontWeight>(
+      '${prefix}weight',
+      fontWeight,
+      description: weightDescription,
+      defaultValue: null,
+    ));
+    styles.add(EnumProperty<FontStyle>('${prefix}style', fontStyle, defaultValue: null));
+    styles.add(DoubleProperty('${prefix}letterSpacing', letterSpacing, defaultValue: null));
+    styles.add(DoubleProperty('${prefix}wordSpacing', wordSpacing, defaultValue: null));
+    styles.add(EnumProperty<TextBaseline>('${prefix}baseline', textBaseline, defaultValue: null));
+    styles.add(DoubleProperty('${prefix}height', height, unit: 'x', defaultValue: null));
+    styles.add(DiagnosticsProperty<Locale>('${prefix}locale', locale, defaultValue: null));
+    styles.add(DiagnosticsProperty<Paint>('${prefix}foreground', foreground, defaultValue: null));
+    styles.add(DiagnosticsProperty<Paint>('${prefix}background', background, defaultValue: null));
+    if (decoration != null || decorationColor != null || decorationStyle != null || decorationThickness != null) {
+      final List<String> decorationDescription = <String>[];
+      if (decorationStyle != null)
+        decorationDescription.add(describeEnum(decorationStyle!));
+
+      // Hide decorationColor from the default text view as it is shown in the
+      // terse decoration summary as well.
+      styles.add(ColorProperty('${prefix}decorationColor', decorationColor, defaultValue: null, level: DiagnosticLevel.fine));
+
+      if (decorationColor != null)
+        decorationDescription.add('$decorationColor');
+
+      // Intentionally collide with the property 'decoration' added below.
+      // Tools that show hidden properties could choose the first property
+      // matching the name to disambiguate.
+      styles.add(DiagnosticsProperty<TextDecoration>('${prefix}decoration', decoration, defaultValue: null, level: DiagnosticLevel.hidden));
+      if (decoration != null)
+        decorationDescription.add('$decoration');
+      assert(decorationDescription.isNotEmpty);
+      styles.add(MessageProperty('${prefix}decoration', decorationDescription.join(' ')));
+      styles.add(DoubleProperty('${prefix}decorationThickness', decorationThickness, unit: 'x', defaultValue: null));
+    }
+
+    final bool styleSpecified = styles.any((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info));
+    properties.add(DiagnosticsProperty<bool>('${prefix}inherit', inherit, level: (!styleSpecified && inherit) ? DiagnosticLevel.fine : DiagnosticLevel.info));
+    styles.forEach(properties.add);
+
+    if (!styleSpecified)
+      properties.add(FlagProperty('inherit', value: inherit, ifTrue: '$prefix<all styles inherited>', ifFalse: '$prefix<no style specified>'));
+  }
+}
diff --git a/lib/src/physics/clamped_simulation.dart b/lib/src/physics/clamped_simulation.dart
new file mode 100644
index 0000000..c50dbc7
--- /dev/null
+++ b/lib/src/physics/clamped_simulation.dart
@@ -0,0 +1,58 @@
+// Copyright 2014 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.
+
+import 'simulation.dart';
+
+/// A simulation that applies limits to another simulation.
+///
+/// The limits are only applied to the other simulation's outputs. For example,
+/// if a maximum position was applied to a gravity simulation with the
+/// particle's initial velocity being up, and the acceleration being down, and
+/// the maximum position being between the initial position and the curve's
+/// apogee, then the particle would return to its initial position in the same
+/// amount of time as it would have if the maximum had not been applied; the
+/// difference would just be that the position would be reported as pinned to
+/// the maximum value for the times that it would otherwise have been reported
+/// as higher.
+class ClampedSimulation extends Simulation {
+
+  /// Creates a [ClampedSimulation] that clamps the given simulation.
+  ///
+  /// The named arguments specify the ranges for the clamping behavior, as
+  /// applied to [x] and [dx].
+  ClampedSimulation(
+    this.simulation, {
+    this.xMin = double.negativeInfinity,
+    this.xMax = double.infinity,
+    this.dxMin = double.negativeInfinity,
+    this.dxMax = double.infinity,
+  }) : assert(simulation != null),
+       assert(xMax >= xMin),
+       assert(dxMax >= dxMin);
+
+  /// The simulation being clamped. Calls to [x], [dx], and [isDone] are
+  /// forwarded to the simulation.
+  final Simulation simulation;
+
+  /// The minimum to apply to [x].
+  final double xMin;
+
+  /// The maximum to apply to [x].
+  final double xMax;
+
+  /// The minimum to apply to [dx].
+  final double dxMin;
+
+  /// The maximum to apply to [dx].
+  final double dxMax;
+
+  @override
+  double x(double time) => simulation.x(time).clamp(xMin, xMax);
+
+  @override
+  double dx(double time) => simulation.dx(time).clamp(dxMin, dxMax);
+
+  @override
+  bool isDone(double time) => simulation.isDone(time);
+}
diff --git a/lib/src/physics/friction_simulation.dart b/lib/src/physics/friction_simulation.dart
new file mode 100644
index 0000000..1879d79
--- /dev/null
+++ b/lib/src/physics/friction_simulation.dart
@@ -0,0 +1,128 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'simulation.dart';
+import 'tolerance.dart';
+
+/// A simulation that applies a drag to slow a particle down.
+///
+/// Models a particle affected by fluid drag, e.g. air resistance.
+///
+/// The simulation ends when the velocity of the particle drops to zero (within
+/// the current velocity [tolerance]).
+class FrictionSimulation extends Simulation {
+  /// Creates a [FrictionSimulation] with the given arguments, namely: the fluid
+  /// drag coefficient, a unitless value; the initial position, in the same
+  /// length units as used for [x]; and the initial velocity, in the same
+  /// velocity units as used for [dx].
+  FrictionSimulation(
+    double drag,
+    double position,
+    double velocity, {
+    Tolerance tolerance = Tolerance.defaultTolerance,
+  }) : _drag = drag,
+       _dragLog = math.log(drag),
+       _x = position,
+       _v = velocity,
+       super(tolerance: tolerance);
+
+  /// Creates a new friction simulation with its fluid drag coefficient set so
+  /// as to ensure that the simulation starts and ends at the specified
+  /// positions and velocities.
+  ///
+  /// The positions must use the same units as expected from [x], and the
+  /// velocities must use the same units as expected from [dx].
+  ///
+  /// The sign of the start and end velocities must be the same, the magnitude
+  /// of the start velocity must be greater than the magnitude of the end
+  /// velocity, and the velocities must be in the direction appropriate for the
+  /// particle to start from the start position and reach the end position.
+  factory FrictionSimulation.through(double startPosition, double endPosition, double startVelocity, double endVelocity) {
+    assert(startVelocity == 0.0 || endVelocity == 0.0 || startVelocity.sign == endVelocity.sign);
+    assert(startVelocity.abs() >= endVelocity.abs());
+    assert((endPosition - startPosition).sign == startVelocity.sign);
+    return FrictionSimulation(
+      _dragFor(startPosition, endPosition, startVelocity, endVelocity),
+      startPosition,
+      startVelocity,
+      tolerance: Tolerance(velocity: endVelocity.abs()),
+    );
+  }
+
+  final double _drag;
+  final double _dragLog;
+  final double _x;
+  final double _v;
+
+  // Return the drag value for a FrictionSimulation whose x() and dx() values pass
+  // through the specified start and end position/velocity values.
+  //
+  // Total time to reach endVelocity is just: (log(endVelocity) / log(startVelocity)) / log(_drag)
+  // or (log(v1) - log(v0)) / log(D), given v = v0 * D^t per the dx() function below.
+  // Solving for D given x(time) is trickier. Algebra courtesy of Wolfram Alpha:
+  // x1 = x0 + (v0 * D^((log(v1) - log(v0)) / log(D))) / log(D) - v0 / log(D), find D
+  static double _dragFor(double startPosition, double endPosition, double startVelocity, double endVelocity) {
+    return math.pow(math.e, (startVelocity - endVelocity) / (startPosition - endPosition)) as double;
+  }
+
+  @override
+  double x(double time) => _x + _v * math.pow(_drag, time) / _dragLog - _v / _dragLog;
+
+  @override
+  double dx(double time) => _v * math.pow(_drag, time);
+
+  /// The value of [x] at `double.infinity`.
+  double get finalX => _x - _v / _dragLog;
+
+  /// The time at which the value of `x(time)` will equal [x].
+  ///
+  /// Returns `double.infinity` if the simulation will never reach [x].
+  double timeAtX(double x) {
+    if (x == _x)
+      return 0.0;
+    if (_v == 0.0 || (_v > 0 ? (x < _x || x > finalX) : (x > _x || x < finalX)))
+      return double.infinity;
+    return math.log(_dragLog * (x - _x) / _v + 1.0) / _dragLog;
+  }
+
+  @override
+  bool isDone(double time) => dx(time).abs() < tolerance.velocity;
+}
+
+/// A [FrictionSimulation] that clamps the modeled particle to a specific range
+/// of values.
+class BoundedFrictionSimulation extends FrictionSimulation {
+  /// Creates a [BoundedFrictionSimulation] with the given arguments, namely:
+  /// the fluid drag coefficient, a unitless value; the initial position, in the
+  /// same length units as used for [x]; the initial velocity, in the same
+  /// velocity units as used for [dx], the minimum value for the position, and
+  /// the maximum value for the position. The minimum and maximum values must be
+  /// in the same units as the initial position, and the initial position must
+  /// be within the given range.
+  BoundedFrictionSimulation(
+    double drag,
+    double position,
+    double velocity,
+    this._minX,
+    this._maxX,
+  ) : assert(position.clamp(_minX, _maxX) == position),
+      super(drag, position, velocity);
+
+  final double _minX;
+  final double _maxX;
+
+  @override
+  double x(double time) {
+    return super.x(time).clamp(_minX, _maxX);
+  }
+
+  @override
+  bool isDone(double time) {
+    return super.isDone(time) ||
+      (x(time) - _minX).abs() < tolerance.distance ||
+      (x(time) - _maxX).abs() < tolerance.distance;
+  }
+}
diff --git a/lib/src/physics/gravity_simulation.dart b/lib/src/physics/gravity_simulation.dart
new file mode 100644
index 0000000..0688579
--- /dev/null
+++ b/lib/src/physics/gravity_simulation.dart
@@ -0,0 +1,83 @@
+// Copyright 2014 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.
+
+import 'simulation.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// AnimationController _controller;
+
+/// A simulation that applies a constant accelerating force.
+///
+/// Models a particle that follows Newton's second law of motion. The simulation
+/// ends when the position reaches a defined point.
+///
+/// {@tool snippet}
+///
+/// This method triggers an [AnimationController] (a previously constructed
+/// `_controller` field) to simulate a fall of 300 pixels.
+///
+/// ```dart
+/// void _startFall() {
+///   _controller.animateWith(GravitySimulation(
+///     10.0, // acceleration, pixels per second per second
+///     0.0, // starting position, pixels
+///     300.0, // ending position, pixels
+///     0.0, // starting velocity, pixels per second
+///   ));
+/// }
+/// ```
+/// {@end-tool}
+///
+/// This [AnimationController] could be used with an [AnimatedBuilder] to
+/// animate the position of a child as if it was falling.
+///
+/// See also:
+///
+///  * [Curves.bounceOut], a [Curve] that has a similar aesthetics but includes
+///    a bouncing effect.
+class GravitySimulation extends Simulation {
+  /// Creates a [GravitySimulation] using the given arguments, which are,
+  /// respectively: an acceleration that is to be applied continually over time;
+  /// an initial position relative to an origin; the magnitude of the distance
+  /// from that origin beyond which (in either direction) to consider the
+  /// simulation to be "done", which must be positive; and an initial velocity.
+  ///
+  /// The initial position and maximum distance are measured in arbitrary length
+  /// units L from an arbitrary origin. The units will match those used for [x].
+  ///
+  /// The time unit T used for the arguments to [x], [dx], and [isDone],
+  /// combined with the aforementioned length unit, together determine the units
+  /// that must be used for the velocity and acceleration arguments: L/T and
+  /// L/T² respectively. The same units of velocity are used for the velocity
+  /// obtained from [dx].
+  GravitySimulation(
+    double acceleration,
+    double distance,
+    double endDistance,
+    double velocity,
+  ) : assert(acceleration != null),
+      assert(distance != null),
+      assert(velocity != null),
+      assert(endDistance != null),
+      assert(endDistance >= 0),
+      _a = acceleration,
+      _x = distance,
+      _v = velocity,
+      _end = endDistance;
+
+  final double _x;
+  final double _v;
+  final double _a;
+  final double _end;
+
+  @override
+  double x(double time) => _x + _v * time + 0.5 * _a * time * time;
+
+  @override
+  double dx(double time) => _v + time * _a;
+
+  @override
+  bool isDone(double time) => x(time).abs() >= _end;
+}
diff --git a/lib/src/physics/simulation.dart b/lib/src/physics/simulation.dart
new file mode 100644
index 0000000..8933132
--- /dev/null
+++ b/lib/src/physics/simulation.dart
@@ -0,0 +1,58 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'tolerance.dart';
+
+/// The base class for all simulations.
+///
+/// A simulation models an object, in a one-dimensional space, on which particular
+/// forces are being applied, and exposes:
+///
+///  * The object's position, [x]
+///  * The object's velocity, [dx]
+///  * Whether the simulation is "done", [isDone]
+///
+/// A simulation is generally "done" if the object has, to a given [tolerance],
+/// come to a complete rest.
+///
+/// The [x], [dx], and [isDone] functions take a time argument which specifies
+/// the time for which they are to be evaluated. In principle, simulations can
+/// be stateless, and thus can be queried with arbitrary times. In practice,
+/// however, some simulations are not, and calling any of these functions will
+/// advance the simulation to the given time.
+///
+/// As a general rule, therefore, a simulation should only be queried using
+/// times that are equal to or greater than all times previously used for that
+/// simulation.
+///
+/// Simulations do not specify units for distance, velocity, and time. Client
+/// should establish a convention and use that convention consistently with all
+/// related objects.
+abstract class Simulation {
+  /// Initializes the [tolerance] field for subclasses.
+  Simulation({ this.tolerance = Tolerance.defaultTolerance });
+
+  /// The position of the object in the simulation at the given time.
+  double x(double time);
+
+  /// The velocity of the object in the simulation at the given time.
+  double dx(double time);
+
+  /// Whether the simulation is "done" at the given time.
+  bool isDone(double time);
+
+  /// How close to the actual end of the simulation a value at a particular time
+  /// must be before [isDone] considers the simulation to be "done".
+  ///
+  /// A simulation with an asymptotic curve would never technically be "done",
+  /// but once the difference from the value at a particular time and the
+  /// asymptote itself could not be seen, it would be pointless to continue. The
+  /// tolerance defines how to determine if the difference could not be seen.
+  Tolerance tolerance;
+
+  @override
+  String toString() => objectRuntimeType(this, 'Simulation');
+}
diff --git a/lib/src/physics/spring_simulation.dart b/lib/src/physics/spring_simulation.dart
new file mode 100644
index 0000000..58dc954
--- /dev/null
+++ b/lib/src/physics/spring_simulation.dart
@@ -0,0 +1,286 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+
+import 'simulation.dart';
+import 'tolerance.dart';
+import 'utils.dart';
+
+/// Structure that describes a spring's constants.
+///
+/// Used to configure a [SpringSimulation].
+class SpringDescription {
+  /// Creates a spring given the mass, stiffness, and the damping coefficient.
+  ///
+  /// See [mass], [stiffness], and [damping] for the units of the arguments.
+  const SpringDescription({
+    required this.mass,
+    required this.stiffness,
+    required this.damping,
+  });
+
+  /// Creates a spring given the mass (m), stiffness (k), and damping ratio (ζ).
+  /// The damping ratio is especially useful trying to determining the type of
+  /// spring to create. A ratio of 1.0 creates a critically damped spring, > 1.0
+  /// creates an overdamped spring and < 1.0 an underdamped one.
+  ///
+  /// See [mass] and [stiffness] for the units for those arguments. The damping
+  /// ratio is unitless.
+  SpringDescription.withDampingRatio({
+    required this.mass,
+    required this.stiffness,
+    double ratio = 1.0,
+  }) : damping = ratio * 2.0 * math.sqrt(mass * stiffness);
+
+  /// The mass of the spring (m). The units are arbitrary, but all springs
+  /// within a system should use the same mass units.
+  final double mass;
+
+  /// The spring constant (k). The units of stiffness are M/T², where M is the
+  /// mass unit used for the value of the [mass] property, and T is the time
+  /// unit used for driving the [SpringSimulation].
+  final double stiffness;
+
+  /// The damping coefficient (c).
+  ///
+  /// Do not confuse the damping _coefficient_ (c) with the damping _ratio_ (ζ).
+  /// To create a [SpringDescription] with a damping ratio, use the [new
+  /// SpringDescription.withDampingRatio] constructor.
+  ///
+  /// The units of the damping coefficient are M/T, where M is the mass unit
+  /// used for the value of the [mass] property, and T is the time unit used for
+  /// driving the [SpringSimulation].
+  final double damping;
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'SpringDescription')}(mass: ${mass.toStringAsFixed(1)}, stiffness: ${stiffness.toStringAsFixed(1)}, damping: ${damping.toStringAsFixed(1)})';
+}
+
+/// The kind of spring solution that the [SpringSimulation] is using to simulate the spring.
+///
+/// See [SpringSimulation.type].
+enum SpringType {
+  /// A spring that does not bounce and returns to its rest position in the
+  /// shortest possible time.
+  criticallyDamped,
+
+  /// A spring that bounces.
+  underDamped,
+
+  /// A spring that does not bounce but takes longer to return to its rest
+  /// position than a [criticallyDamped] one.
+  overDamped,
+}
+
+/// A spring simulation.
+///
+/// Models a particle attached to a spring that follows Hooke's law.
+class SpringSimulation extends Simulation {
+  /// Creates a spring simulation from the provided spring description, start
+  /// distance, end distance, and initial velocity.
+  ///
+  /// The units for the start and end distance arguments are arbitrary, but must
+  /// be consistent with the units used for other lengths in the system.
+  ///
+  /// The units for the velocity are L/T, where L is the aforementioned
+  /// arbitrary unit of length, and T is the time unit used for driving the
+  /// [SpringSimulation].
+  SpringSimulation(
+    SpringDescription spring,
+    double start,
+    double end,
+    double velocity, {
+    Tolerance tolerance = Tolerance.defaultTolerance,
+  }) : _endPosition = end,
+       _solution = _SpringSolution(spring, start - end, velocity),
+       super(tolerance: tolerance);
+
+  final double _endPosition;
+  final _SpringSolution _solution;
+
+  /// The kind of spring being simulated, for debugging purposes.
+  ///
+  /// This is derived from the [SpringDescription] provided to the [new
+  /// SpringSimulation] constructor.
+  SpringType get type => _solution.type;
+
+  @override
+  double x(double time) => _endPosition + _solution.x(time);
+
+  @override
+  double dx(double time) => _solution.dx(time);
+
+  @override
+  bool isDone(double time) {
+    return nearZero(_solution.x(time), tolerance.distance) &&
+           nearZero(_solution.dx(time), tolerance.velocity);
+  }
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'SpringSimulation')}(end: $_endPosition, $type)';
+}
+
+/// A [SpringSimulation] where the value of [x] is guaranteed to have exactly the
+/// end value when the simulation [isDone].
+class ScrollSpringSimulation extends SpringSimulation {
+  /// Creates a spring simulation from the provided spring description, start
+  /// distance, end distance, and initial velocity.
+  ///
+  /// See the [new SpringSimulation] constructor on the superclass for a
+  /// discussion of the arguments' units.
+  ScrollSpringSimulation(
+    SpringDescription spring,
+    double start,
+    double end,
+    double velocity, {
+    Tolerance tolerance = Tolerance.defaultTolerance,
+  }) : super(spring, start, end, velocity, tolerance: tolerance);
+
+  @override
+  double x(double time) => isDone(time) ? _endPosition : super.x(time);
+}
+
+
+// SPRING IMPLEMENTATIONS
+
+abstract class _SpringSolution {
+  factory _SpringSolution(
+    SpringDescription spring,
+    double initialPosition,
+    double initialVelocity,
+  ) {
+    assert(spring != null);
+    assert(spring.mass != null);
+    assert(spring.stiffness != null);
+    assert(spring.damping != null);
+    assert(initialPosition != null);
+    assert(initialVelocity != null);
+    final double cmk = spring.damping * spring.damping - 4 * spring.mass * spring.stiffness;
+    if (cmk == 0.0)
+      return _CriticalSolution(spring, initialPosition, initialVelocity);
+    if (cmk > 0.0)
+      return _OverdampedSolution(spring, initialPosition, initialVelocity);
+    return _UnderdampedSolution(spring, initialPosition, initialVelocity);
+  }
+
+  double x(double time);
+  double dx(double time);
+  SpringType get type;
+}
+
+class _CriticalSolution implements _SpringSolution {
+  factory _CriticalSolution(
+    SpringDescription spring,
+    double distance,
+    double velocity,
+  ) {
+    final double r = -spring.damping / (2.0 * spring.mass);
+    final double c1 = distance;
+    final double c2 = velocity / (r * distance);
+    return _CriticalSolution.withArgs(r, c1, c2);
+  }
+
+  _CriticalSolution.withArgs(double r, double c1, double c2)
+    : _r = r,
+      _c1 = c1,
+      _c2 = c2;
+
+  final double _r, _c1, _c2;
+
+  @override
+  double x(double time) {
+    return (_c1 + _c2 * time) * math.pow(math.e, _r * time);
+  }
+
+  @override
+  double dx(double time) {
+    final double power = math.pow(math.e, _r * time) as double;
+    return _r * (_c1 + _c2 * time) * power + _c2 * power;
+  }
+
+  @override
+  SpringType get type => SpringType.criticallyDamped;
+}
+
+class _OverdampedSolution implements _SpringSolution {
+  factory _OverdampedSolution(
+    SpringDescription spring,
+    double distance,
+    double velocity,
+  ) {
+    final double cmk = spring.damping * spring.damping - 4 * spring.mass * spring.stiffness;
+    final double r1 = (-spring.damping - math.sqrt(cmk)) / (2.0 * spring.mass);
+    final double r2 = (-spring.damping + math.sqrt(cmk)) / (2.0 * spring.mass);
+    final double c2 = (velocity - r1 * distance) / (r2 - r1);
+    final double c1 = distance - c2;
+    return _OverdampedSolution.withArgs(r1, r2, c1, c2);
+  }
+
+  _OverdampedSolution.withArgs(double r1, double r2, double c1, double c2)
+    : _r1 = r1,
+      _r2 = r2,
+      _c1 = c1,
+      _c2 = c2;
+
+  final double _r1, _r2, _c1, _c2;
+
+  @override
+  double x(double time) {
+    return _c1 * math.pow(math.e, _r1 * time) +
+           _c2 * math.pow(math.e, _r2 * time);
+  }
+
+  @override
+  double dx(double time) {
+    return _c1 * _r1 * math.pow(math.e, _r1 * time) +
+           _c2 * _r2 * math.pow(math.e, _r2 * time);
+  }
+
+  @override
+  SpringType get type => SpringType.overDamped;
+}
+
+class _UnderdampedSolution implements _SpringSolution {
+  factory _UnderdampedSolution(
+    SpringDescription spring,
+    double distance,
+    double velocity,
+  ) {
+    final double w = math.sqrt(4.0 * spring.mass * spring.stiffness -
+                     spring.damping * spring.damping) / (2.0 * spring.mass);
+    final double r = -(spring.damping / 2.0 * spring.mass);
+    final double c1 = distance;
+    final double c2 = (velocity - r * distance) / w;
+    return _UnderdampedSolution.withArgs(w, r, c1, c2);
+  }
+
+  _UnderdampedSolution.withArgs(double w, double r, double c1, double c2)
+    : _w = w,
+      _r = r,
+      _c1 = c1,
+      _c2 = c2;
+
+  final double _w, _r, _c1, _c2;
+
+  @override
+  double x(double time) {
+    return (math.pow(math.e, _r * time) as double) *
+           (_c1 * math.cos(_w * time) + _c2 * math.sin(_w * time));
+  }
+
+  @override
+  double dx(double time) {
+    final double power = math.pow(math.e, _r * time) as double;
+    final double cosine = math.cos(_w * time);
+    final double sine = math.sin(_w * time);
+    return power * (_c2 * _w * cosine - _c1 * _w * sine) +
+           _r * power * (_c2 *      sine   + _c1 *      cosine);
+  }
+
+  @override
+  SpringType get type => SpringType.underDamped;
+}
diff --git a/lib/src/physics/tolerance.dart b/lib/src/physics/tolerance.dart
new file mode 100644
index 0000000..6351a2d
--- /dev/null
+++ b/lib/src/physics/tolerance.dart
@@ -0,0 +1,46 @@
+// Copyright 2014 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.
+
+/// Structure that specifies maximum allowable magnitudes for distances,
+/// durations, and velocity differences to be considered equal.
+class Tolerance {
+  /// Creates a [Tolerance] object. By default, the distance, time, and velocity
+  /// tolerances are all ±0.001; the constructor arguments override this.
+  ///
+  /// The arguments should all be positive values.
+  const Tolerance({
+    this.distance = _epsilonDefault,
+    this.time = _epsilonDefault,
+    this.velocity = _epsilonDefault,
+  });
+
+  static const double _epsilonDefault = 1e-3;
+
+  /// A default tolerance of 0.001 for all three values.
+  static const Tolerance defaultTolerance = Tolerance();
+
+  /// The magnitude of the maximum distance between two points for them to be
+  /// considered within tolerance.
+  ///
+  /// The units for the distance tolerance must be the same as the units used
+  /// for the distances that are to be compared to this tolerance.
+  final double distance;
+
+  /// The magnitude of the maximum duration between two times for them to be
+  /// considered within tolerance.
+  ///
+  /// The units for the time tolerance must be the same as the units used
+  /// for the times that are to be compared to this tolerance.
+  final double time;
+
+  /// The magnitude of the maximum difference between two velocities for them to
+  /// be considered within tolerance.
+  ///
+  /// The units for the velocity tolerance must be the same as the units used
+  /// for the velocities that are to be compared to this tolerance.
+  final double velocity;
+
+  @override
+  String toString() => 'Tolerance(distance: ±$distance, time: ±$time, velocity: ±$velocity)';
+}
diff --git a/lib/src/physics/utils.dart b/lib/src/physics/utils.dart
new file mode 100644
index 0000000..252bd17
--- /dev/null
+++ b/lib/src/physics/utils.dart
@@ -0,0 +1,21 @@
+// Copyright 2014 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.
+
+/// Whether two doubles are within a given distance of each other.
+///
+/// The `epsilon` argument must be positive and not null.
+/// The `a` and `b` arguments may be null. A null value is only considered
+/// near-equal to another null value.
+bool nearEqual(double? a, double? b, double epsilon) {
+  assert(epsilon != null);
+  assert(epsilon >= 0.0);
+  if (a == null || b == null)
+    return a == b;
+  return (a > (b - epsilon)) && (a < (b + epsilon)) || a == b;
+}
+
+/// Whether a double is within a given distance of zero.
+///
+/// The epsilon argument must be positive.
+bool nearZero(double a, double epsilon) => nearEqual(a, 0.0, epsilon);
diff --git a/lib/src/rendering/animated_size.dart b/lib/src/rendering/animated_size.dart
new file mode 100644
index 0000000..cf92ac0
--- /dev/null
+++ b/lib/src/rendering/animated_size.dart
@@ -0,0 +1,339 @@
+// Copyright 2014 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.
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+
+import 'box.dart';
+import 'layer.dart';
+import 'object.dart';
+import 'shifted_box.dart';
+
+/// A [RenderAnimatedSize] can be in exactly one of these states.
+@visibleForTesting
+enum RenderAnimatedSizeState {
+  /// The initial state, when we do not yet know what the starting and target
+  /// sizes are to animate.
+  ///
+  /// The next state is [stable].
+  start,
+
+  /// At this state the child's size is assumed to be stable and we are either
+  /// animating, or waiting for the child's size to change.
+  ///
+  /// If the child's size changes, the state will become [changed]. Otherwise,
+  /// it remains [stable].
+  stable,
+
+  /// At this state we know that the child has changed once after being assumed
+  /// [stable].
+  ///
+  /// The next state will be one of:
+  ///
+  /// * [stable] if the child's size stabilized immediately. This is a signal
+  ///   for the render object to begin animating the size towards the child's new
+  ///   size.
+  ///
+  /// * [unstable] if the child's size continues to change.
+  changed,
+
+  /// At this state the child's size is assumed to be unstable (changing each
+  /// frame).
+  ///
+  /// Instead of chasing the child's size in this state, the render object
+  /// tightly tracks the child's size until it stabilizes.
+  ///
+  /// The render object remains in this state until a frame where the child's
+  /// size remains the same as the previous frame. At that time, the next state
+  /// is [stable].
+  unstable,
+}
+
+/// A render object that animates its size to its child's size over a given
+/// [duration] and with a given [curve]. If the child's size itself animates
+/// (i.e. if it changes size two frames in a row, as opposed to abruptly
+/// changing size in one frame then remaining that size in subsequent frames),
+/// this render object sizes itself to fit the child instead of animating
+/// itself.
+///
+/// When the child overflows the current animated size of this render object, it
+/// is clipped.
+class RenderAnimatedSize extends RenderAligningShiftedBox {
+  /// Creates a render object that animates its size to match its child.
+  /// The [duration] and [curve] arguments define the animation.
+  ///
+  /// The [alignment] argument is used to align the child when the parent is not
+  /// (yet) the same size as the child.
+  ///
+  /// The [duration] is required.
+  ///
+  /// The [vsync] should specify a [TickerProvider] for the animation
+  /// controller.
+  ///
+  /// The arguments [duration], [curve], [alignment], and [vsync] must
+  /// not be null.
+  RenderAnimatedSize({
+    required TickerProvider vsync,
+    required Duration duration,
+    Duration? reverseDuration,
+    Curve curve = Curves.linear,
+    AlignmentGeometry alignment = Alignment.center,
+    TextDirection? textDirection,
+    RenderBox? child,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : assert(vsync != null),
+       assert(duration != null),
+       assert(curve != null),
+       assert(clipBehavior != null),
+       _vsync = vsync,
+       _clipBehavior = clipBehavior,
+       super(child: child, alignment: alignment, textDirection: textDirection) {
+    _controller = AnimationController(
+      vsync: vsync,
+      duration: duration,
+      reverseDuration: reverseDuration,
+    )..addListener(() {
+      if (_controller.value != _lastValue)
+        markNeedsLayout();
+    });
+    _animation = CurvedAnimation(
+      parent: _controller,
+      curve: curve,
+    );
+  }
+
+  late final AnimationController _controller;
+  late final CurvedAnimation _animation;
+  final SizeTween _sizeTween = SizeTween();
+  late bool _hasVisualOverflow;
+  double? _lastValue;
+
+  /// The state this size animation is in.
+  ///
+  /// See [RenderAnimatedSizeState] for possible states.
+  @visibleForTesting
+  RenderAnimatedSizeState get state => _state;
+  RenderAnimatedSizeState _state = RenderAnimatedSizeState.start;
+
+  /// The duration of the animation.
+  Duration get duration => _controller.duration!;
+  set duration(Duration value) {
+    assert(value != null);
+    if (value == _controller.duration)
+      return;
+    _controller.duration = value;
+  }
+
+  /// The duration of the animation when running in reverse.
+  Duration? get reverseDuration => _controller.reverseDuration;
+  set reverseDuration(Duration? value) {
+    if (value == _controller.reverseDuration)
+      return;
+    _controller.reverseDuration = value;
+  }
+
+  /// The curve of the animation.
+  Curve get curve => _animation.curve;
+  set curve(Curve value) {
+    assert(value != null);
+    if (value == _animation.curve)
+      return;
+    _animation.curve = value;
+  }
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge], and must not be null.
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior = Clip.hardEdge;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  /// Whether the size is being currently animated towards the child's size.
+  ///
+  /// See [RenderAnimatedSizeState] for situations when we may not be animating
+  /// the size.
+  bool get isAnimating => _controller.isAnimating;
+
+  /// The [TickerProvider] for the [AnimationController] that runs the animation.
+  TickerProvider get vsync => _vsync;
+  TickerProvider _vsync;
+  set vsync(TickerProvider value) {
+    assert(value != null);
+    if (value == _vsync)
+      return;
+    _vsync = value;
+    _controller.resync(vsync);
+  }
+
+  @override
+  void detach() {
+    _controller.stop();
+    super.detach();
+  }
+
+  Size? get _animatedSize {
+    return _sizeTween.evaluate(_animation);
+  }
+
+  @override
+  void performLayout() {
+    _lastValue = _controller.value;
+    _hasVisualOverflow = false;
+    final BoxConstraints constraints = this.constraints;
+    if (child == null || constraints.isTight) {
+      _controller.stop();
+      size = _sizeTween.begin = _sizeTween.end = constraints.smallest;
+      _state = RenderAnimatedSizeState.start;
+      child?.layout(constraints);
+      return;
+    }
+
+    child!.layout(constraints, parentUsesSize: true);
+
+    assert(_state != null);
+    switch (_state) {
+      case RenderAnimatedSizeState.start:
+        _layoutStart();
+        break;
+      case RenderAnimatedSizeState.stable:
+        _layoutStable();
+        break;
+      case RenderAnimatedSizeState.changed:
+        _layoutChanged();
+        break;
+      case RenderAnimatedSizeState.unstable:
+        _layoutUnstable();
+        break;
+    }
+
+    size = constraints.constrain(_animatedSize!);
+    alignChild();
+
+    if (size.width < _sizeTween.end!.width ||
+        size.height < _sizeTween.end!.height)
+      _hasVisualOverflow = true;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (child == null || constraints.isTight) {
+      return constraints.smallest;
+    }
+
+    // This simplified version of performLayout only calculates the current
+    // size without modifying global state. See performLayout for comments
+    // explaining the rational behind the implementation.
+    final Size childSize = child!.getDryLayout(constraints);
+    assert(_state != null);
+    switch (_state) {
+      case RenderAnimatedSizeState.start:
+        return constraints.constrain(childSize);
+      case RenderAnimatedSizeState.stable:
+        if (_sizeTween.end != childSize) {
+          return constraints.constrain(size);
+        } else if (_controller.value == _controller.upperBound) {
+          return constraints.constrain(childSize);
+        }
+        break;
+      case RenderAnimatedSizeState.unstable:
+      case RenderAnimatedSizeState.changed:
+        if (_sizeTween.end != childSize) {
+          return constraints.constrain(childSize);
+        }
+        break;
+    }
+
+    return constraints.constrain(_animatedSize!);
+  }
+
+  void _restartAnimation() {
+    _lastValue = 0.0;
+    _controller.forward(from: 0.0);
+  }
+
+  /// Laying out the child for the first time.
+  ///
+  /// We have the initial size to animate from, but we do not have the target
+  /// size to animate to, so we set both ends to child's size.
+  void _layoutStart() {
+    _sizeTween.begin = _sizeTween.end = debugAdoptSize(child!.size);
+    _state = RenderAnimatedSizeState.stable;
+  }
+
+  /// At this state we're assuming the child size is stable and letting the
+  /// animation run its course.
+  ///
+  /// If during animation the size of the child changes we restart the
+  /// animation.
+  void _layoutStable() {
+    if (_sizeTween.end != child!.size) {
+      _sizeTween.begin = size;
+      _sizeTween.end = debugAdoptSize(child!.size);
+      _restartAnimation();
+      _state = RenderAnimatedSizeState.changed;
+    } else if (_controller.value == _controller.upperBound) {
+      // Animation finished. Reset target sizes.
+      _sizeTween.begin = _sizeTween.end = debugAdoptSize(child!.size);
+    } else if (!_controller.isAnimating) {
+      _controller.forward(); // resume the animation after being detached
+    }
+  }
+
+  /// This state indicates that the size of the child changed once after being
+  /// considered stable.
+  ///
+  /// If the child stabilizes immediately, we go back to stable state. If it
+  /// changes again, we match the child's size, restart animation and go to
+  /// unstable state.
+  void _layoutChanged() {
+    if (_sizeTween.end != child!.size) {
+      // Child size changed again. Match the child's size and restart animation.
+      _sizeTween.begin = _sizeTween.end = debugAdoptSize(child!.size);
+      _restartAnimation();
+      _state = RenderAnimatedSizeState.unstable;
+    } else {
+      // Child size stabilized.
+      _state = RenderAnimatedSizeState.stable;
+      if (!_controller.isAnimating)
+        _controller.forward(); // resume the animation after being detached
+    }
+  }
+
+  /// The child's size is not stable.
+  ///
+  /// Continue tracking the child's size until is stabilizes.
+  void _layoutUnstable() {
+    if (_sizeTween.end != child!.size) {
+      // Still unstable. Continue tracking the child.
+      _sizeTween.begin = _sizeTween.end = debugAdoptSize(child!.size);
+      _restartAnimation();
+    } else {
+      // Child size stabilized.
+      _controller.stop();
+      _state = RenderAnimatedSizeState.stable;
+    }
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null && _hasVisualOverflow && clipBehavior != Clip.none) {
+      final Rect rect = Offset.zero & size;
+      _clipRectLayer = context.pushClipRect(needsCompositing, offset, rect, super.paint,
+          clipBehavior: clipBehavior, oldLayer: _clipRectLayer);
+    } else {
+      _clipRectLayer = null;
+      super.paint(context, offset);
+    }
+  }
+
+  ClipRectLayer? _clipRectLayer;
+}
diff --git a/lib/src/rendering/binding.dart b/lib/src/rendering/binding.dart
new file mode 100644
index 0000000..63da55e
--- /dev/null
+++ b/lib/src/rendering/binding.dart
@@ -0,0 +1,534 @@
+// Copyright 2014 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.
+
+import 'dart:developer';
+import 'dart:typed_data';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/semantics.dart';
+import 'package:flute/services.dart';
+
+import 'box.dart';
+import 'debug.dart';
+import 'mouse_tracking.dart';
+import 'object.dart';
+import 'view.dart';
+
+export 'package:flute/gestures.dart' show HitTestResult;
+
+// Examples can assume:
+// // @dart = 2.9
+// dynamic context;
+
+/// The glue between the render tree and the Flutter engine.
+mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
+  @override
+  void initInstances() {
+    super.initInstances();
+    _instance = this;
+    _pipelineOwner = PipelineOwner(
+      onNeedVisualUpdate: ensureVisualUpdate,
+      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
+      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
+    );
+    window
+      ..onMetricsChanged = handleMetricsChanged
+      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
+      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
+      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
+      ..onSemanticsAction = _handleSemanticsAction;
+    initRenderView();
+    _handleSemanticsEnabledChanged();
+    assert(renderView != null);
+    addPersistentFrameCallback(_handlePersistentFrameCallback);
+    initMouseTracker();
+    if (kIsWeb) {
+      addPostFrameCallback(_handleWebFirstFrame);
+    }
+  }
+
+  /// The current [RendererBinding], if one has been created.
+  static RendererBinding? get instance => _instance;
+  static RendererBinding? _instance;
+
+  @override
+  void initServiceExtensions() {
+    super.initServiceExtensions();
+
+    assert(() {
+      // these service extensions only work in debug mode
+      registerBoolServiceExtension(
+        name: 'invertOversizedImages',
+        getter: () async => debugInvertOversizedImages,
+        setter: (bool value) async {
+          if (debugInvertOversizedImages != value) {
+            debugInvertOversizedImages = value;
+            return _forceRepaint();
+          }
+          return Future<void>.value();
+        },
+      );
+      registerBoolServiceExtension(
+        name: 'debugPaint',
+        getter: () async => debugPaintSizeEnabled,
+        setter: (bool value) {
+          if (debugPaintSizeEnabled == value)
+            return Future<void>.value();
+          debugPaintSizeEnabled = value;
+          return _forceRepaint();
+        },
+      );
+      registerBoolServiceExtension(
+        name: 'debugPaintBaselinesEnabled',
+        getter: () async => debugPaintBaselinesEnabled,
+        setter: (bool value) {
+          if (debugPaintBaselinesEnabled == value)
+            return Future<void>.value();
+          debugPaintBaselinesEnabled = value;
+          return _forceRepaint();
+        },
+      );
+      registerBoolServiceExtension(
+        name: 'repaintRainbow',
+        getter: () async => debugRepaintRainbowEnabled,
+        setter: (bool value) {
+          final bool repaint = debugRepaintRainbowEnabled && !value;
+          debugRepaintRainbowEnabled = value;
+          if (repaint)
+            return _forceRepaint();
+          return Future<void>.value();
+        },
+      );
+      registerBoolServiceExtension(
+        name: 'debugCheckElevationsEnabled',
+        getter: () async => debugCheckElevationsEnabled,
+        setter: (bool value) {
+          if (debugCheckElevationsEnabled == value) {
+            return Future<void>.value();
+          }
+          debugCheckElevationsEnabled = value;
+          return _forceRepaint();
+        },
+      );
+      registerSignalServiceExtension(
+        name: 'debugDumpLayerTree',
+        callback: () {
+          debugDumpLayerTree();
+          return debugPrintDone;
+        },
+      );
+      return true;
+    }());
+
+    if (!kReleaseMode) {
+      // these service extensions work in debug or profile mode
+      registerSignalServiceExtension(
+        name: 'debugDumpRenderTree',
+        callback: () {
+          debugDumpRenderTree();
+          return debugPrintDone;
+        },
+      );
+
+      registerSignalServiceExtension(
+        name: 'debugDumpSemanticsTreeInTraversalOrder',
+        callback: () {
+          debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversalOrder);
+          return debugPrintDone;
+        },
+      );
+
+      registerSignalServiceExtension(
+        name: 'debugDumpSemanticsTreeInInverseHitTestOrder',
+        callback: () {
+          debugDumpSemanticsTree(DebugSemanticsDumpOrder.inverseHitTest);
+          return debugPrintDone;
+        },
+      );
+    }
+  }
+
+  /// Creates a [RenderView] object to be the root of the
+  /// [RenderObject] rendering tree, and initializes it so that it
+  /// will be rendered when the next frame is requested.
+  ///
+  /// Called automatically when the binding is created.
+  void initRenderView() {
+    assert(!_debugIsRenderViewInitialized);
+    assert(() {
+      _debugIsRenderViewInitialized = true;
+      return true;
+    }());
+    renderView = RenderView(configuration: createViewConfiguration(), window: window);
+    renderView.prepareInitialFrame();
+  }
+  bool _debugIsRenderViewInitialized = false;
+
+  /// The object that manages state about currently connected mice, for hover
+  /// notification.
+  MouseTracker get mouseTracker => _mouseTracker!;
+  MouseTracker? _mouseTracker;
+
+  /// The render tree's owner, which maintains dirty state for layout,
+  /// composite, paint, and accessibility semantics.
+  PipelineOwner get pipelineOwner => _pipelineOwner;
+  late PipelineOwner _pipelineOwner;
+
+  /// The render tree that's attached to the output surface.
+  RenderView get renderView => _pipelineOwner.rootNode! as RenderView;
+  /// Sets the given [RenderView] object (which must not be null), and its tree, to
+  /// be the new render tree to display. The previous tree, if any, is detached.
+  set renderView(RenderView value) {
+    assert(value != null);
+    _pipelineOwner.rootNode = value;
+  }
+
+  /// Called when the system metrics change.
+  ///
+  /// See [dart:ui.PlatformDispatcher.onMetricsChanged].
+  @protected
+  void handleMetricsChanged() {
+    assert(renderView != null);
+    renderView.configuration = createViewConfiguration();
+    scheduleForcedFrame();
+  }
+
+  /// Called when the platform text scale factor changes.
+  ///
+  /// See [dart:ui.PlatformDispatcher.onTextScaleFactorChanged].
+  @protected
+  void handleTextScaleFactorChanged() { }
+
+  /// Called when the platform brightness changes.
+  ///
+  /// The current platform brightness can be queried from a Flutter binding or
+  /// from a [MediaQuery] widget. The latter is preferred from widgets because
+  /// it causes the widget to be automatically rebuilt when the brightness
+  /// changes.
+  ///
+  /// {@tool snippet}
+  /// Querying [MediaQuery] directly. Preferred.
+  ///
+  /// ```dart
+  /// final Brightness brightness = MediaQuery.platformBrightnessOf(context);
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@tool snippet}
+  /// Querying [PlatformDispatcher.platformBrightness].
+  ///
+  /// ```dart
+  /// final Brightness brightness = WidgetsBinding.instance.platformDispatcher.platformBrightness;
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@tool snippet}
+  /// Querying [MediaQueryData].
+  ///
+  /// ```dart
+  /// final MediaQueryData mediaQueryData = MediaQuery.of(context);
+  /// final Brightness brightness = mediaQueryData.platformBrightness;
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See [dart:ui.PlatformDispatcher.onPlatformBrightnessChanged].
+  @protected
+  void handlePlatformBrightnessChanged() { }
+
+  /// Returns a [ViewConfiguration] configured for the [RenderView] based on the
+  /// current environment.
+  ///
+  /// This is called during construction and also in response to changes to the
+  /// system metrics.
+  ///
+  /// Bindings can override this method to change what size or device pixel
+  /// ratio the [RenderView] will use. For example, the testing framework uses
+  /// this to force the display into 800x600 when a test is run on the device
+  /// using `flutter run`.
+  ViewConfiguration createViewConfiguration() {
+    final double devicePixelRatio = window.devicePixelRatio;
+    return ViewConfiguration(
+      size: window.physicalSize / devicePixelRatio,
+      devicePixelRatio: devicePixelRatio,
+    );
+  }
+
+  SemanticsHandle? _semanticsHandle;
+
+  /// Creates a [MouseTracker] which manages state about currently connected
+  /// mice, for hover notification.
+  ///
+  /// Used by testing framework to reinitialize the mouse tracker between tests.
+  @visibleForTesting
+  void initMouseTracker([MouseTracker? tracker]) {
+    _mouseTracker?.dispose();
+    _mouseTracker = tracker ?? MouseTracker();
+  }
+
+  @override // from GestureBinding
+  void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
+    if (hitTestResult != null ||
+        event is PointerAddedEvent ||
+        event is PointerRemovedEvent) {
+      assert(event.position != null);
+      _mouseTracker!.updateWithEvent(event,
+          () => hitTestResult ?? renderView.hitTestMouseTrackers(event.position));
+    }
+    super.dispatchEvent(event, hitTestResult);
+  }
+
+  void _handleSemanticsEnabledChanged() {
+    setSemanticsEnabled(window.semanticsEnabled);
+  }
+
+  /// Whether the render tree associated with this binding should produce a tree
+  /// of [SemanticsNode] objects.
+  void setSemanticsEnabled(bool enabled) {
+    if (enabled) {
+      _semanticsHandle ??= _pipelineOwner.ensureSemantics();
+    } else {
+      _semanticsHandle?.dispose();
+      _semanticsHandle = null;
+    }
+  }
+
+  void _handleWebFirstFrame(Duration _) {
+    assert(kIsWeb);
+    const MethodChannel methodChannel = MethodChannel('flutter/service_worker');
+    methodChannel.invokeMethod<void>('first-frame');
+  }
+
+  void _handleSemanticsAction(int id, SemanticsAction action, ByteData? args) {
+    _pipelineOwner.semanticsOwner?.performAction(
+      id,
+      action,
+      args != null ? const StandardMessageCodec().decodeMessage(args) : null,
+    );
+  }
+
+  void _handleSemanticsOwnerCreated() {
+    renderView.scheduleInitialSemantics();
+  }
+
+  void _handleSemanticsOwnerDisposed() {
+    renderView.clearSemantics();
+  }
+
+  void _handlePersistentFrameCallback(Duration timeStamp) {
+    drawFrame();
+    _scheduleMouseTrackerUpdate();
+  }
+
+  bool _debugMouseTrackerUpdateScheduled = false;
+  void _scheduleMouseTrackerUpdate() {
+    assert(!_debugMouseTrackerUpdateScheduled);
+    assert(() {
+      _debugMouseTrackerUpdateScheduled = true;
+      return true;
+    }());
+    SchedulerBinding.instance!.addPostFrameCallback((Duration duration) {
+      assert(_debugMouseTrackerUpdateScheduled);
+      assert(() {
+        _debugMouseTrackerUpdateScheduled = false;
+        return true;
+      }());
+      _mouseTracker!.updateAllDevices(renderView.hitTestMouseTrackers);
+    });
+  }
+
+  int _firstFrameDeferredCount = 0;
+  bool _firstFrameSent = false;
+
+  /// Whether frames produced by [drawFrame] are sent to the engine.
+  ///
+  /// If false the framework will do all the work to produce a frame,
+  /// but the frame is never sent to the engine to actually appear on screen.
+  ///
+  /// See also:
+  ///
+  ///  * [deferFirstFrame], which defers when the first frame is sent to the
+  ///    engine.
+  bool get sendFramesToEngine => _firstFrameSent || _firstFrameDeferredCount == 0;
+
+  /// Tell the framework to not send the first frames to the engine until there
+  /// is a corresponding call to [allowFirstFrame].
+  ///
+  /// Call this to perform asynchronous initialization work before the first
+  /// frame is rendered (which takes down the splash screen). The framework
+  /// will still do all the work to produce frames, but those frames are never
+  /// sent to the engine and will not appear on screen.
+  ///
+  /// Calling this has no effect after the first frame has been sent to the
+  /// engine.
+  void deferFirstFrame() {
+    assert(_firstFrameDeferredCount >= 0);
+    _firstFrameDeferredCount += 1;
+  }
+
+  /// Called after [deferFirstFrame] to tell the framework that it is ok to
+  /// send the first frame to the engine now.
+  ///
+  /// For best performance, this method should only be called while the
+  /// [schedulerPhase] is [SchedulerPhase.idle].
+  ///
+  /// This method may only be called once for each corresponding call
+  /// to [deferFirstFrame].
+  void allowFirstFrame() {
+    assert(_firstFrameDeferredCount > 0);
+    _firstFrameDeferredCount -= 1;
+    // Always schedule a warm up frame even if the deferral count is not down to
+    // zero yet since the removal of a deferral may uncover new deferrals that
+    // are lower in the widget tree.
+    if (!_firstFrameSent)
+      scheduleWarmUpFrame();
+  }
+
+  /// Call this to pretend that no frames have been sent to the engine yet.
+  ///
+  /// This is useful for tests that want to call [deferFirstFrame] and
+  /// [allowFirstFrame] since those methods only have an effect if no frames
+  /// have been sent to the engine yet.
+  void resetFirstFrameSent() {
+    _firstFrameSent = false;
+  }
+
+  /// Pump the rendering pipeline to generate a frame.
+  ///
+  /// This method is called by [handleDrawFrame], which itself is called
+  /// automatically by the engine when it is time to lay out and paint a frame.
+  ///
+  /// Each frame consists of the following phases:
+  ///
+  /// 1. The animation phase: The [handleBeginFrame] method, which is registered
+  /// with [PlatformDispatcher.onBeginFrame], invokes all the transient frame
+  /// callbacks registered with [scheduleFrameCallback], in registration order.
+  /// This includes all the [Ticker] instances that are driving
+  /// [AnimationController] objects, which means all of the active [Animation]
+  /// objects tick at this point.
+  ///
+  /// 2. Microtasks: After [handleBeginFrame] returns, any microtasks that got
+  /// scheduled by transient frame callbacks get to run. This typically includes
+  /// callbacks for futures from [Ticker]s and [AnimationController]s that
+  /// completed this frame.
+  ///
+  /// After [handleBeginFrame], [handleDrawFrame], which is registered with
+  /// [dart:ui.PlatformDispatcher.onDrawFrame], is called, which invokes all the
+  /// persistent frame callbacks, of which the most notable is this method,
+  /// [drawFrame], which proceeds as follows:
+  ///
+  /// 3. The layout phase: All the dirty [RenderObject]s in the system are laid
+  /// out (see [RenderObject.performLayout]). See [RenderObject.markNeedsLayout]
+  /// for further details on marking an object dirty for layout.
+  ///
+  /// 4. The compositing bits phase: The compositing bits on any dirty
+  /// [RenderObject] objects are updated. See
+  /// [RenderObject.markNeedsCompositingBitsUpdate].
+  ///
+  /// 5. The paint phase: All the dirty [RenderObject]s in the system are
+  /// repainted (see [RenderObject.paint]). This generates the [Layer] tree. See
+  /// [RenderObject.markNeedsPaint] for further details on marking an object
+  /// dirty for paint.
+  ///
+  /// 6. The compositing phase: The layer tree is turned into a [Scene] and
+  /// sent to the GPU.
+  ///
+  /// 7. The semantics phase: All the dirty [RenderObject]s in the system have
+  /// their semantics updated. This generates the [SemanticsNode] tree. See
+  /// [RenderObject.markNeedsSemanticsUpdate] for further details on marking an
+  /// object dirty for semantics.
+  ///
+  /// For more details on steps 3-7, see [PipelineOwner].
+  ///
+  /// 8. The finalization phase: After [drawFrame] returns, [handleDrawFrame]
+  /// then invokes post-frame callbacks (registered with [addPostFrameCallback]).
+  ///
+  /// Some bindings (for example, the [WidgetsBinding]) add extra steps to this
+  /// list (for example, see [WidgetsBinding.drawFrame]).
+  //
+  // When editing the above, also update widgets/binding.dart's copy.
+  @protected
+  void drawFrame() {
+    assert(renderView != null);
+    pipelineOwner.flushLayout();
+    pipelineOwner.flushCompositingBits();
+    pipelineOwner.flushPaint();
+    if (sendFramesToEngine) {
+      renderView.compositeFrame(); // this sends the bits to the GPU
+      pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
+      _firstFrameSent = true;
+    }
+  }
+
+  @override
+  Future<void> performReassemble() async {
+    await super.performReassemble();
+    Timeline.startSync('Dirty Render Tree', arguments: timelineArgumentsIndicatingLandmarkEvent);
+    try {
+      renderView.reassemble();
+    } finally {
+      Timeline.finishSync();
+    }
+    scheduleWarmUpFrame();
+    await endOfFrame;
+  }
+
+  @override
+  void hitTest(HitTestResult result, Offset position) {
+    assert(renderView != null);
+    assert(result != null);
+    assert(position != null);
+    renderView.hitTest(result, position: position);
+    super.hitTest(result, position);
+  }
+
+  Future<void> _forceRepaint() {
+    late RenderObjectVisitor visitor;
+    visitor = (RenderObject child) {
+      child.markNeedsPaint();
+      child.visitChildren(visitor);
+    };
+    instance?.renderView.visitChildren(visitor);
+    return endOfFrame;
+  }
+}
+
+/// Prints a textual representation of the entire render tree.
+void debugDumpRenderTree() {
+  debugPrint(RendererBinding.instance?.renderView.toStringDeep() ?? 'Render tree unavailable.');
+}
+
+/// Prints a textual representation of the entire layer tree.
+void debugDumpLayerTree() {
+  debugPrint(RendererBinding.instance?.renderView.debugLayer?.toStringDeep() ?? 'Layer tree unavailable.');
+}
+
+/// Prints a textual representation of the entire semantics tree.
+/// This will only work if there is a semantics client attached.
+/// Otherwise, a notice that no semantics are available will be printed.
+///
+/// The order in which the children of a [SemanticsNode] will be printed is
+/// controlled by the [childOrder] parameter.
+void debugDumpSemanticsTree(DebugSemanticsDumpOrder childOrder) {
+  debugPrint(RendererBinding.instance?.renderView.debugSemantics?.toStringDeep(childOrder: childOrder) ?? 'Semantics not collected.');
+}
+
+/// A concrete binding for applications that use the Rendering framework
+/// directly. This is the glue that binds the framework to the Flutter engine.
+///
+/// You would only use this binding if you are writing to the
+/// rendering layer directly. If you are writing to a higher-level
+/// library, such as the Flutter Widgets library, then you would use
+/// that layer's binding.
+class RenderingFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, SemanticsBinding, PaintingBinding, RendererBinding {
+  /// Creates a binding for the rendering layer.
+  ///
+  /// The `root` render box is attached directly to the [renderView] and is
+  /// given constraints that require it to fill the window.
+  RenderingFlutterBinding({ RenderBox? root }) {
+    assert(renderView != null);
+    renderView.child = root;
+  }
+}
diff --git a/lib/src/rendering/box.dart b/lib/src/rendering/box.dart
new file mode 100644
index 0000000..39b7edf
--- /dev/null
+++ b/lib/src/rendering/box.dart
@@ -0,0 +1,2807 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' as ui show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+
+import 'package:vector_math/vector_math_64.dart';
+
+import 'debug.dart';
+import 'object.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+
+// This class should only be used in debug builds.
+class _DebugSize extends Size {
+  _DebugSize(Size source, this._owner, this._canBeUsedByParent) : super.copy(source);
+  final RenderBox _owner;
+  final bool _canBeUsedByParent;
+}
+
+/// Immutable layout constraints for [RenderBox] layout.
+///
+/// A [Size] respects a [BoxConstraints] if, and only if, all of the following
+/// relations hold:
+///
+/// * [minWidth] <= [Size.width] <= [maxWidth]
+/// * [minHeight] <= [Size.height] <= [maxHeight]
+///
+/// The constraints themselves must satisfy these relations:
+///
+/// * 0.0 <= [minWidth] <= [maxWidth] <= [double.infinity]
+/// * 0.0 <= [minHeight] <= [maxHeight] <= [double.infinity]
+///
+/// [double.infinity] is a legal value for each constraint.
+///
+/// ## The box layout model
+///
+/// Render objects in the Flutter framework are laid out by a one-pass layout
+/// model which walks down the render tree passing constraints, then walks back
+/// up the render tree passing concrete geometry.
+///
+/// For boxes, the constraints are [BoxConstraints], which, as described herein,
+/// consist of four numbers: a minimum width [minWidth], a maximum width
+/// [maxWidth], a minimum height [minHeight], and a maximum height [maxHeight].
+///
+/// The geometry for boxes consists of a [Size], which must satisfy the
+/// constraints described above.
+///
+/// Each [RenderBox] (the objects that provide the layout models for box
+/// widgets) receives [BoxConstraints] from its parent, then lays out each of
+/// its children, then picks a [Size] that satisfies the [BoxConstraints].
+///
+/// Render objects position their children independently of laying them out.
+/// Frequently, the parent will use the children's sizes to determine their
+/// position. A child does not know its position and will not necessarily be
+/// laid out again, or repainted, if its position changes.
+///
+/// ## Terminology
+///
+/// When the minimum constraints and the maximum constraint in an axis are the
+/// same, that axis is _tightly_ constrained. See: [new
+/// BoxConstraints.tightFor], [new BoxConstraints.tightForFinite], [tighten],
+/// [hasTightWidth], [hasTightHeight], [isTight].
+///
+/// An axis with a minimum constraint of 0.0 is _loose_ (regardless of the
+/// maximum constraint; if it is also 0.0, then the axis is simultaneously tight
+/// and loose!). See: [new BoxConstraints.loose], [loosen].
+///
+/// An axis whose maximum constraint is not infinite is _bounded_. See:
+/// [hasBoundedWidth], [hasBoundedHeight].
+///
+/// An axis whose maximum constraint is infinite is _unbounded_. An axis is
+/// _expanding_ if it is tightly infinite (its minimum and maximum constraints
+/// are both infinite). See: [new BoxConstraints.expand].
+///
+/// An axis whose _minimum_ constraint is infinite is just said to be _infinite_
+/// (since by definition the maximum constraint must also be infinite in that
+/// case). See: [hasInfiniteWidth], [hasInfiniteHeight].
+///
+/// A size is _constrained_ when it satisfies a [BoxConstraints] description.
+/// See: [constrain], [constrainWidth], [constrainHeight],
+/// [constrainDimensions], [constrainSizeAndAttemptToPreserveAspectRatio],
+/// [isSatisfiedBy].
+class BoxConstraints extends Constraints {
+  /// Creates box constraints with the given constraints.
+  const BoxConstraints({
+    this.minWidth = 0.0,
+    this.maxWidth = double.infinity,
+    this.minHeight = 0.0,
+    this.maxHeight = double.infinity,
+  }) : assert(minWidth != null),
+       assert(maxWidth != null),
+       assert(minHeight != null),
+       assert(maxHeight != null);
+
+  /// Creates box constraints that is respected only by the given size.
+  BoxConstraints.tight(Size size)
+    : minWidth = size.width,
+      maxWidth = size.width,
+      minHeight = size.height,
+      maxHeight = size.height;
+
+  /// Creates box constraints that require the given width or height.
+  ///
+  /// See also:
+  ///
+  ///  * [new BoxConstraints.tightForFinite], which is similar but instead of
+  ///    being tight if the value is non-null, is tight if the value is not
+  ///    infinite.
+  const BoxConstraints.tightFor({
+    double? width,
+    double? height,
+  }) : minWidth = width ?? 0.0,
+       maxWidth = width ?? double.infinity,
+       minHeight = height ?? 0.0,
+       maxHeight = height ?? double.infinity;
+
+  /// Creates box constraints that require the given width or height, except if
+  /// they are infinite.
+  ///
+  /// See also:
+  ///
+  ///  * [new BoxConstraints.tightFor], which is similar but instead of being
+  ///    tight if the value is not infinite, is tight if the value is non-null.
+  const BoxConstraints.tightForFinite({
+    double width = double.infinity,
+    double height = double.infinity,
+  }) : minWidth = width != double.infinity ? width : 0.0,
+       maxWidth = width != double.infinity ? width : double.infinity,
+       minHeight = height != double.infinity ? height : 0.0,
+       maxHeight = height != double.infinity ? height : double.infinity;
+
+  /// Creates box constraints that forbid sizes larger than the given size.
+  BoxConstraints.loose(Size size)
+    : minWidth = 0.0,
+      maxWidth = size.width,
+      minHeight = 0.0,
+      maxHeight = size.height;
+
+  /// Creates box constraints that expand to fill another box constraints.
+  ///
+  /// If width or height is given, the constraints will require exactly the
+  /// given value in the given dimension.
+  const BoxConstraints.expand({
+    double? width,
+    double? height,
+  }) : minWidth = width ?? double.infinity,
+       maxWidth = width ?? double.infinity,
+       minHeight = height ?? double.infinity,
+       maxHeight = height ?? double.infinity;
+
+  /// The minimum width that satisfies the constraints.
+  final double minWidth;
+
+  /// The maximum width that satisfies the constraints.
+  ///
+  /// Might be [double.infinity].
+  final double maxWidth;
+
+  /// The minimum height that satisfies the constraints.
+  final double minHeight;
+
+  /// The maximum height that satisfies the constraints.
+  ///
+  /// Might be [double.infinity].
+  final double maxHeight;
+
+  /// Creates a copy of this box constraints but with the given fields replaced with the new values.
+  BoxConstraints copyWith({
+    double? minWidth,
+    double? maxWidth,
+    double? minHeight,
+    double? maxHeight,
+  }) {
+    return BoxConstraints(
+      minWidth: minWidth ?? this.minWidth,
+      maxWidth: maxWidth ?? this.maxWidth,
+      minHeight: minHeight ?? this.minHeight,
+      maxHeight: maxHeight ?? this.maxHeight,
+    );
+  }
+
+  /// Returns new box constraints that are smaller by the given edge dimensions.
+  BoxConstraints deflate(EdgeInsets edges) {
+    assert(edges != null);
+    assert(debugAssertIsValid());
+    final double horizontal = edges.horizontal;
+    final double vertical = edges.vertical;
+    final double deflatedMinWidth = math.max(0.0, minWidth - horizontal);
+    final double deflatedMinHeight = math.max(0.0, minHeight - vertical);
+    return BoxConstraints(
+      minWidth: deflatedMinWidth,
+      maxWidth: math.max(deflatedMinWidth, maxWidth - horizontal),
+      minHeight: deflatedMinHeight,
+      maxHeight: math.max(deflatedMinHeight, maxHeight - vertical),
+    );
+  }
+
+  /// Returns new box constraints that remove the minimum width and height requirements.
+  BoxConstraints loosen() {
+    assert(debugAssertIsValid());
+    return BoxConstraints(
+      minWidth: 0.0,
+      maxWidth: maxWidth,
+      minHeight: 0.0,
+      maxHeight: maxHeight,
+    );
+  }
+
+  /// Returns new box constraints that respect the given constraints while being
+  /// as close as possible to the original constraints.
+  BoxConstraints enforce(BoxConstraints constraints) {
+    return BoxConstraints(
+      minWidth: minWidth.clamp(constraints.minWidth, constraints.maxWidth),
+      maxWidth: maxWidth.clamp(constraints.minWidth, constraints.maxWidth),
+      minHeight: minHeight.clamp(constraints.minHeight, constraints.maxHeight),
+      maxHeight: maxHeight.clamp(constraints.minHeight, constraints.maxHeight),
+    );
+  }
+
+  /// Returns new box constraints with a tight width and/or height as close to
+  /// the given width and height as possible while still respecting the original
+  /// box constraints.
+  BoxConstraints tighten({ double? width, double? height }) {
+    return BoxConstraints(
+      minWidth: width == null ? minWidth : width.clamp(minWidth, maxWidth),
+      maxWidth: width == null ? maxWidth : width.clamp(minWidth, maxWidth),
+      minHeight: height == null ? minHeight : height.clamp(minHeight, maxHeight),
+      maxHeight: height == null ? maxHeight : height.clamp(minHeight, maxHeight),
+    );
+  }
+
+  /// A box constraints with the width and height constraints flipped.
+  BoxConstraints get flipped {
+    return BoxConstraints(
+      minWidth: minHeight,
+      maxWidth: maxHeight,
+      minHeight: minWidth,
+      maxHeight: maxWidth,
+    );
+  }
+
+  /// Returns box constraints with the same width constraints but with
+  /// unconstrained height.
+  BoxConstraints widthConstraints() => BoxConstraints(minWidth: minWidth, maxWidth: maxWidth);
+
+  /// Returns box constraints with the same height constraints but with
+  /// unconstrained width.
+  BoxConstraints heightConstraints() => BoxConstraints(minHeight: minHeight, maxHeight: maxHeight);
+
+  /// Returns the width that both satisfies the constraints and is as close as
+  /// possible to the given width.
+  double constrainWidth([ double width = double.infinity ]) {
+    assert(debugAssertIsValid());
+    return width.clamp(minWidth, maxWidth);
+  }
+
+  /// Returns the height that both satisfies the constraints and is as close as
+  /// possible to the given height.
+  double constrainHeight([ double height = double.infinity ]) {
+    assert(debugAssertIsValid());
+    return height.clamp(minHeight, maxHeight);
+  }
+
+  Size _debugPropagateDebugSize(Size size, Size result) {
+    assert(() {
+      if (size is _DebugSize)
+        result = _DebugSize(result, size._owner, size._canBeUsedByParent);
+      return true;
+    }());
+    return result;
+  }
+
+  /// Returns the size that both satisfies the constraints and is as close as
+  /// possible to the given size.
+  ///
+  /// See also:
+  ///
+  ///  * [constrainDimensions], which applies the same algorithm to
+  ///    separately provided widths and heights.
+  Size constrain(Size size) {
+    Size result = Size(constrainWidth(size.width), constrainHeight(size.height));
+    assert(() {
+      result = _debugPropagateDebugSize(size, result);
+      return true;
+    }());
+    return result;
+  }
+
+  /// Returns the size that both satisfies the constraints and is as close as
+  /// possible to the given width and height.
+  ///
+  /// When you already have a [Size], prefer [constrain], which applies the same
+  /// algorithm to a [Size] directly.
+  Size constrainDimensions(double width, double height) {
+    return Size(constrainWidth(width), constrainHeight(height));
+  }
+
+  /// Returns a size that attempts to meet the following conditions, in order:
+  ///
+  ///  * The size must satisfy these constraints.
+  ///  * The aspect ratio of the returned size matches the aspect ratio of the
+  ///    given size.
+  ///  * The returned size as big as possible while still being equal to or
+  ///    smaller than the given size.
+  Size constrainSizeAndAttemptToPreserveAspectRatio(Size size) {
+    if (isTight) {
+      Size result = smallest;
+      assert(() {
+        result = _debugPropagateDebugSize(size, result);
+        return true;
+      }());
+      return result;
+    }
+
+    double width = size.width;
+    double height = size.height;
+    assert(width > 0.0);
+    assert(height > 0.0);
+    final double aspectRatio = width / height;
+
+    if (width > maxWidth) {
+      width = maxWidth;
+      height = width / aspectRatio;
+    }
+
+    if (height > maxHeight) {
+      height = maxHeight;
+      width = height * aspectRatio;
+    }
+
+    if (width < minWidth) {
+      width = minWidth;
+      height = width / aspectRatio;
+    }
+
+    if (height < minHeight) {
+      height = minHeight;
+      width = height * aspectRatio;
+    }
+
+    Size result = Size(constrainWidth(width), constrainHeight(height));
+    assert(() {
+      result = _debugPropagateDebugSize(size, result);
+      return true;
+    }());
+    return result;
+  }
+
+  /// The biggest size that satisfies the constraints.
+  Size get biggest => Size(constrainWidth(), constrainHeight());
+
+  /// The smallest size that satisfies the constraints.
+  Size get smallest => Size(constrainWidth(0.0), constrainHeight(0.0));
+
+  /// Whether there is exactly one width value that satisfies the constraints.
+  bool get hasTightWidth => minWidth >= maxWidth;
+
+  /// Whether there is exactly one height value that satisfies the constraints.
+  bool get hasTightHeight => minHeight >= maxHeight;
+
+  /// Whether there is exactly one size that satisfies the constraints.
+  @override
+  bool get isTight => hasTightWidth && hasTightHeight;
+
+  /// Whether there is an upper bound on the maximum width.
+  ///
+  /// See also:
+  ///
+  ///  * [hasBoundedHeight], the equivalent for the vertical axis.
+  ///  * [hasInfiniteWidth], which describes whether the minimum width
+  ///    constraint is infinite.
+  bool get hasBoundedWidth => maxWidth < double.infinity;
+
+  /// Whether there is an upper bound on the maximum height.
+  ///
+  /// See also:
+  ///
+  ///  * [hasBoundedWidth], the equivalent for the horizontal axis.
+  ///  * [hasInfiniteHeight], which describes whether the minimum height
+  ///    constraint is infinite.
+  bool get hasBoundedHeight => maxHeight < double.infinity;
+
+  /// Whether the width constraint is infinite.
+  ///
+  /// Such a constraint is used to indicate that a box should grow as large as
+  /// some other constraint (in this case, horizontally). If constraints are
+  /// infinite, then they must have other (non-infinite) constraints [enforce]d
+  /// upon them, or must be [tighten]ed, before they can be used to derive a
+  /// [Size] for a [RenderBox.size].
+  ///
+  /// See also:
+  ///
+  ///  * [hasInfiniteHeight], the equivalent for the vertical axis.
+  ///  * [hasBoundedWidth], which describes whether the maximum width
+  ///    constraint is finite.
+  bool get hasInfiniteWidth => minWidth >= double.infinity;
+
+  /// Whether the height constraint is infinite.
+  ///
+  /// Such a constraint is used to indicate that a box should grow as large as
+  /// some other constraint (in this case, vertically). If constraints are
+  /// infinite, then they must have other (non-infinite) constraints [enforce]d
+  /// upon them, or must be [tighten]ed, before they can be used to derive a
+  /// [Size] for a [RenderBox.size].
+  ///
+  /// See also:
+  ///
+  ///  * [hasInfiniteWidth], the equivalent for the horizontal axis.
+  ///  * [hasBoundedHeight], which describes whether the maximum height
+  ///    constraint is finite.
+  bool get hasInfiniteHeight => minHeight >= double.infinity;
+
+  /// Whether the given size satisfies the constraints.
+  bool isSatisfiedBy(Size size) {
+    assert(debugAssertIsValid());
+    return (minWidth <= size.width) && (size.width <= maxWidth) &&
+           (minHeight <= size.height) && (size.height <= maxHeight);
+  }
+
+  /// Scales each constraint parameter by the given factor.
+  BoxConstraints operator*(double factor) {
+    return BoxConstraints(
+      minWidth: minWidth * factor,
+      maxWidth: maxWidth * factor,
+      minHeight: minHeight * factor,
+      maxHeight: maxHeight * factor,
+    );
+  }
+
+  /// Scales each constraint parameter by the inverse of the given factor.
+  BoxConstraints operator/(double factor) {
+    return BoxConstraints(
+      minWidth: minWidth / factor,
+      maxWidth: maxWidth / factor,
+      minHeight: minHeight / factor,
+      maxHeight: maxHeight / factor,
+    );
+  }
+
+  /// Scales each constraint parameter by the inverse of the given factor, rounded to the nearest integer.
+  BoxConstraints operator~/(double factor) {
+    return BoxConstraints(
+      minWidth: (minWidth ~/ factor).toDouble(),
+      maxWidth: (maxWidth ~/ factor).toDouble(),
+      minHeight: (minHeight ~/ factor).toDouble(),
+      maxHeight: (maxHeight ~/ factor).toDouble(),
+    );
+  }
+
+  /// Computes the remainder of each constraint parameter by the given value.
+  BoxConstraints operator%(double value) {
+    return BoxConstraints(
+      minWidth: minWidth % value,
+      maxWidth: maxWidth % value,
+      minHeight: minHeight % value,
+      maxHeight: maxHeight % value,
+    );
+  }
+
+  /// Linearly interpolate between two BoxConstraints.
+  ///
+  /// If either is null, this function interpolates from a [BoxConstraints]
+  /// object whose fields are all set to 0.0.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static BoxConstraints? lerp(BoxConstraints? a, BoxConstraints? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b! * t;
+    if (b == null)
+      return a * (1.0 - t);
+    assert(a.debugAssertIsValid());
+    assert(b.debugAssertIsValid());
+    assert((a.minWidth.isFinite && b.minWidth.isFinite) || (a.minWidth == double.infinity && b.minWidth == double.infinity), 'Cannot interpolate between finite constraints and unbounded constraints.');
+    assert((a.maxWidth.isFinite && b.maxWidth.isFinite) || (a.maxWidth == double.infinity && b.maxWidth == double.infinity), 'Cannot interpolate between finite constraints and unbounded constraints.');
+    assert((a.minHeight.isFinite && b.minHeight.isFinite) || (a.minHeight == double.infinity && b.minHeight == double.infinity), 'Cannot interpolate between finite constraints and unbounded constraints.');
+    assert((a.maxHeight.isFinite && b.maxHeight.isFinite) || (a.maxHeight == double.infinity && b.maxHeight == double.infinity), 'Cannot interpolate between finite constraints and unbounded constraints.');
+    return BoxConstraints(
+      minWidth: a.minWidth.isFinite ? ui.lerpDouble(a.minWidth, b.minWidth, t)! : double.infinity,
+      maxWidth: a.maxWidth.isFinite ? ui.lerpDouble(a.maxWidth, b.maxWidth, t)! : double.infinity,
+      minHeight: a.minHeight.isFinite ? ui.lerpDouble(a.minHeight, b.minHeight, t)! : double.infinity,
+      maxHeight: a.maxHeight.isFinite ? ui.lerpDouble(a.maxHeight, b.maxHeight, t)! : double.infinity,
+    );
+  }
+
+  /// Returns whether the object's constraints are normalized.
+  /// Constraints are normalized if the minimums are less than or
+  /// equal to the corresponding maximums.
+  ///
+  /// For example, a BoxConstraints object with a minWidth of 100.0
+  /// and a maxWidth of 90.0 is not normalized.
+  ///
+  /// Most of the APIs on BoxConstraints expect the constraints to be
+  /// normalized and have undefined behavior when they are not. In
+  /// checked mode, many of these APIs will assert if the constraints
+  /// are not normalized.
+  @override
+  bool get isNormalized {
+    return minWidth >= 0.0 &&
+           minWidth <= maxWidth &&
+           minHeight >= 0.0 &&
+           minHeight <= maxHeight;
+  }
+
+  @override
+  bool debugAssertIsValid({
+    bool isAppliedConstraint = false,
+    InformationCollector? informationCollector,
+  }) {
+    assert(() {
+      void throwError(DiagnosticsNode message) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          message,
+          if (informationCollector != null) ...informationCollector(),
+          DiagnosticsProperty<BoxConstraints>('The offending constraints were', this, style: DiagnosticsTreeStyle.errorProperty),
+        ]);
+      }
+      if (minWidth.isNaN || maxWidth.isNaN || minHeight.isNaN || maxHeight.isNaN) {
+        final List<String> affectedFieldsList = <String>[
+          if (minWidth.isNaN) 'minWidth',
+          if (maxWidth.isNaN) 'maxWidth',
+          if (minHeight.isNaN) 'minHeight',
+          if (maxHeight.isNaN) 'maxHeight',
+        ];
+        assert(affectedFieldsList.isNotEmpty);
+        if (affectedFieldsList.length > 1)
+          affectedFieldsList.add('and ${affectedFieldsList.removeLast()}');
+        String whichFields = '';
+        if (affectedFieldsList.length > 2) {
+          whichFields = affectedFieldsList.join(', ');
+        } else if (affectedFieldsList.length == 2) {
+          whichFields = affectedFieldsList.join(' ');
+        } else {
+          whichFields = affectedFieldsList.single;
+        }
+        throwError(ErrorSummary('BoxConstraints has ${affectedFieldsList.length == 1 ? 'a NaN value' : 'NaN values' } in $whichFields.'));
+      }
+      if (minWidth < 0.0 && minHeight < 0.0)
+        throwError(ErrorSummary('BoxConstraints has both a negative minimum width and a negative minimum height.'));
+      if (minWidth < 0.0)
+        throwError(ErrorSummary('BoxConstraints has a negative minimum width.'));
+      if (minHeight < 0.0)
+        throwError(ErrorSummary('BoxConstraints has a negative minimum height.'));
+      if (maxWidth < minWidth && maxHeight < minHeight)
+        throwError(ErrorSummary('BoxConstraints has both width and height constraints non-normalized.'));
+      if (maxWidth < minWidth)
+        throwError(ErrorSummary('BoxConstraints has non-normalized width constraints.'));
+      if (maxHeight < minHeight)
+        throwError(ErrorSummary('BoxConstraints has non-normalized height constraints.'));
+      if (isAppliedConstraint) {
+        if (minWidth.isInfinite && minHeight.isInfinite)
+          throwError(ErrorSummary('BoxConstraints forces an infinite width and infinite height.'));
+        if (minWidth.isInfinite)
+          throwError(ErrorSummary('BoxConstraints forces an infinite width.'));
+        if (minHeight.isInfinite)
+          throwError(ErrorSummary('BoxConstraints forces an infinite height.'));
+      }
+      assert(isNormalized);
+      return true;
+    }());
+    return isNormalized;
+  }
+
+  /// Returns a box constraints that [isNormalized].
+  ///
+  /// The returned [maxWidth] is at least as large as the [minWidth]. Similarly,
+  /// the returned [maxHeight] is at least as large as the [minHeight].
+  BoxConstraints normalize() {
+    if (isNormalized)
+      return this;
+    final double minWidth = this.minWidth >= 0.0 ? this.minWidth : 0.0;
+    final double minHeight = this.minHeight >= 0.0 ? this.minHeight : 0.0;
+    return BoxConstraints(
+      minWidth: minWidth,
+      maxWidth: minWidth > maxWidth ? minWidth : maxWidth,
+      minHeight: minHeight,
+      maxHeight: minHeight > maxHeight ? minHeight : maxHeight,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    assert(debugAssertIsValid());
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    assert(other is BoxConstraints && other.debugAssertIsValid());
+    return other is BoxConstraints
+        && other.minWidth == minWidth
+        && other.maxWidth == maxWidth
+        && other.minHeight == minHeight
+        && other.maxHeight == maxHeight;
+  }
+
+  @override
+  int get hashCode {
+    assert(debugAssertIsValid());
+    return hashValues(minWidth, maxWidth, minHeight, maxHeight);
+  }
+
+  @override
+  String toString() {
+    final String annotation = isNormalized ? '' : '; NOT NORMALIZED';
+    if (minWidth == double.infinity && minHeight == double.infinity)
+      return 'BoxConstraints(biggest$annotation)';
+    if (minWidth == 0 && maxWidth == double.infinity &&
+        minHeight == 0 && maxHeight == double.infinity)
+      return 'BoxConstraints(unconstrained$annotation)';
+    String describe(double min, double max, String dim) {
+      if (min == max)
+        return '$dim=${min.toStringAsFixed(1)}';
+      return '${min.toStringAsFixed(1)}<=$dim<=${max.toStringAsFixed(1)}';
+    }
+    final String width = describe(minWidth, maxWidth, 'w');
+    final String height = describe(minHeight, maxHeight, 'h');
+    return 'BoxConstraints($width, $height$annotation)';
+  }
+}
+
+/// Method signature for hit testing a [RenderBox].
+///
+/// Used by [BoxHitTestResult.addWithPaintTransform] to hit test children
+/// of a [RenderBox].
+///
+/// See also:
+///
+///  * [RenderBox.hitTest], which documents more details around hit testing
+///    [RenderBox]es.
+typedef BoxHitTest = bool Function(BoxHitTestResult result, Offset position);
+
+/// Method signature for hit testing a [RenderBox] with a manually
+/// managed position (one that is passed out-of-band).
+///
+/// Used by [RenderSliverSingleBoxAdapter.hitTestBoxChild] to hit test
+/// [RenderBox] children of a [RenderSliver].
+///
+/// See also:
+///
+///  * [RenderBox.hitTest], which documents more details around hit testing
+///    [RenderBox]es.
+typedef BoxHitTestWithOutOfBandPosition = bool Function(BoxHitTestResult result);
+
+/// The result of performing a hit test on [RenderBox]es.
+///
+/// An instance of this class is provided to [RenderBox.hitTest] to record the
+/// result of the hit test.
+class BoxHitTestResult extends HitTestResult {
+  /// Creates an empty hit test result for hit testing on [RenderBox].
+  BoxHitTestResult() : super();
+
+  /// Wraps `result` to create a [HitTestResult] that implements the
+  /// [BoxHitTestResult] protocol for hit testing on [RenderBox]es.
+  ///
+  /// This method is used by [RenderObject]s that adapt between the
+  /// [RenderBox]-world and the non-[RenderBox]-world to convert a (subtype of)
+  /// [HitTestResult] to a [BoxHitTestResult] for hit testing on [RenderBox]es.
+  ///
+  /// The [HitTestEntry] instances added to the returned [BoxHitTestResult] are
+  /// also added to the wrapped `result` (both share the same underlying data
+  /// structure to store [HitTestEntry] instances).
+  ///
+  /// See also:
+  ///
+  ///  * [HitTestResult.wrap], which turns a [BoxHitTestResult] back into a
+  ///    generic [HitTestResult].
+  ///  * [SliverHitTestResult.wrap], which turns a [BoxHitTestResult] into a
+  ///    [SliverHitTestResult] for hit testing on [RenderSliver] children.
+  BoxHitTestResult.wrap(HitTestResult result) : super.wrap(result);
+
+  /// Transforms `position` to the local coordinate system of a child for
+  /// hit-testing the child.
+  ///
+  /// The actual hit testing of the child needs to be implemented in the
+  /// provided `hitTest` callback, which is invoked with the transformed
+  /// `position` as argument.
+  ///
+  /// The provided paint `transform` (which describes the transform from the
+  /// child to the parent in 3D) is processed by
+  /// [PointerEvent.removePerspectiveTransform] to remove the
+  /// perspective component and inverted before it is used to transform
+  /// `position` from the coordinate system of the parent to the system of the
+  /// child.
+  ///
+  /// If `transform` is null it will be treated as the identity transform and
+  /// `position` is provided to the `hitTest` callback as-is. If `transform`
+  /// cannot be inverted, the `hitTest` callback is not invoked and false is
+  /// returned. Otherwise, the return value of the `hitTest` callback is
+  /// returned.
+  ///
+  /// The `position` argument may be null, which will be forwarded to the
+  /// `hitTest` callback as-is. Using null as the position can be useful if
+  /// the child speaks a different hit test protocol then the parent and the
+  /// position is not required to do the actual hit testing in that protocol.
+  ///
+  /// The function returns the return value of the `hitTest` callback.
+  ///
+  /// {@tool snippet}
+  /// This method is used in [RenderBox.hitTestChildren] when the child and
+  /// parent don't share the same origin.
+  ///
+  /// ```dart
+  /// abstract class RenderFoo extends RenderBox {
+  ///
+  ///   final Matrix4 _effectiveTransform = Matrix4.rotationZ(50);
+  ///
+  ///   @override
+  ///   void applyPaintTransform(RenderBox child, Matrix4 transform) {
+  ///     transform.multiply(_effectiveTransform);
+  ///   }
+  ///
+  ///   @override
+  ///   bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
+  ///     return result.addWithPaintTransform(
+  ///       transform: _effectiveTransform,
+  ///       position: position,
+  ///       hitTest: (BoxHitTestResult result, Offset position) {
+  ///         return super.hitTestChildren(result, position: position);
+  ///       },
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [addWithPaintOffset], which can be used for `transform`s that are just
+  ///    simple matrix translations by an [Offset].
+  ///  * [addWithRawTransform], which takes a transform matrix that is directly
+  ///    used to transform the position without any pre-processing.
+  bool addWithPaintTransform({
+    required Matrix4? transform,
+    required Offset position,
+    required BoxHitTest hitTest,
+  }) {
+    assert(position != null);
+    assert(hitTest != null);
+    if (transform != null) {
+      transform = Matrix4.tryInvert(PointerEvent.removePerspectiveTransform(transform));
+      if (transform == null) {
+        // Objects are not visible on screen and cannot be hit-tested.
+        return false;
+      }
+    }
+    return addWithRawTransform(
+      transform: transform,
+      position: position,
+      hitTest: hitTest,
+    );
+  }
+
+  /// Convenience method for hit testing children, that are translated by
+  /// an [Offset].
+  ///
+  /// The actual hit testing of the child needs to be implemented in the
+  /// provided `hitTest` callback, which is invoked with the transformed
+  /// `position` as argument.
+  ///
+  /// This method can be used as a convenience over [addWithPaintTransform] if
+  /// a parent paints a child at an `offset`.
+  ///
+  /// A null value for `offset` is treated as if [Offset.zero] was provided.
+  ///
+  /// The function returns the return value of the `hitTest` callback.
+  ///
+  /// See also:
+  ///
+  ///  * [addWithPaintTransform], which takes a generic paint transform matrix and
+  ///    documents the intended usage of this API in more detail.
+  bool addWithPaintOffset({
+    required Offset? offset,
+    required Offset position,
+    required BoxHitTest hitTest,
+  }) {
+    assert(position != null);
+    assert(hitTest != null);
+    final Offset transformedPosition = offset == null ? position : position - offset;
+    if (offset != null) {
+      pushOffset(-offset);
+    }
+    final bool isHit = hitTest(this, transformedPosition);
+    if (offset != null) {
+      popTransform();
+    }
+    return isHit;
+  }
+
+  /// Transforms `position` to the local coordinate system of a child for
+  /// hit-testing the child.
+  ///
+  /// The actual hit testing of the child needs to be implemented in the
+  /// provided `hitTest` callback, which is invoked with the transformed
+  /// `position` as argument.
+  ///
+  /// Unlike [addWithPaintTransform], the provided `transform` matrix is used
+  /// directly to transform `position` without any pre-processing.
+  ///
+  /// If `transform` is null it will be treated as the identity transform ad
+  /// `position` is provided to the `hitTest` callback as-is.
+  ///
+  /// The function returns the return value of the `hitTest` callback.
+  ///
+  /// See also:
+  ///
+  ///  * [addWithPaintTransform], which accomplishes the same thing, but takes a
+  ///    _paint_ transform matrix.
+  bool addWithRawTransform({
+    required Matrix4? transform,
+    required Offset position,
+    required BoxHitTest hitTest,
+  }) {
+    assert(position != null);
+    assert(hitTest != null);
+    assert(position != null);
+    final Offset transformedPosition = transform == null ?
+        position : MatrixUtils.transformPoint(transform, position);
+    if (transform != null) {
+      pushTransform(transform);
+    }
+    final bool isHit = hitTest(this, transformedPosition);
+    if (transform != null) {
+      popTransform();
+    }
+    return isHit;
+  }
+
+  /// Pass-through method for adding a hit test while manually managing
+  /// the position transformation logic.
+  ///
+  /// The actual hit testing of the child needs to be implemented in the
+  /// provided `hitTest` callback. The position needs to be handled by
+  /// the caller.
+  ///
+  /// The function returns the return value of the `hitTest` callback.
+  ///
+  /// A `paintOffset`, `paintTransform`, or `rawTransform` should be
+  /// passed to the method to update the hit test stack.
+  ///
+  ///  * `paintOffset` has the semantics of the `offset` passed to
+  ///    [addWithPaintOffset].
+  ///
+  ///  * `paintTransform` has the semantics of the `transform` passed to
+  ///    [addWithPaintTransform], except that it must be invertible; it
+  ///    is the responsibility of the caller to ensure this.
+  ///
+  ///  * `rawTransform` has the semantics of the `transform` passed to
+  ///    [addWithRawTransform].
+  ///
+  /// Exactly one of these must be non-null.
+  ///
+  /// See also:
+  ///
+  ///  * [addWithPaintTransform], which takes a generic paint transform matrix and
+  ///    documents the intended usage of this API in more detail.
+  bool addWithOutOfBandPosition({
+    Offset? paintOffset,
+    Matrix4? paintTransform,
+    Matrix4? rawTransform,
+    required BoxHitTestWithOutOfBandPosition hitTest,
+  }) {
+    assert(hitTest != null);
+    assert((paintOffset == null && paintTransform == null && rawTransform != null) ||
+           (paintOffset == null && paintTransform != null && rawTransform == null) ||
+           (paintOffset != null && paintTransform == null && rawTransform == null),
+           'Exactly one transform or offset argument must be provided.');
+    if (paintOffset != null) {
+      pushOffset(-paintOffset);
+    } else if (rawTransform != null) {
+      pushTransform(rawTransform);
+    } else {
+      assert(paintTransform != null);
+      paintTransform = Matrix4.tryInvert(PointerEvent.removePerspectiveTransform(paintTransform!));
+      assert(paintTransform != null, 'paintTransform must be invertible.');
+      pushTransform(paintTransform!);
+    }
+    final bool isHit = hitTest(this);
+    popTransform();
+    return isHit;
+  }
+}
+
+/// A hit test entry used by [RenderBox].
+class BoxHitTestEntry extends HitTestEntry {
+  /// Creates a box hit test entry.
+  ///
+  /// The [localPosition] argument must not be null.
+  BoxHitTestEntry(RenderBox target, this.localPosition)
+    : assert(localPosition != null),
+      super(target);
+
+  @override
+  RenderBox get target => super.target as RenderBox;
+
+  /// The position of the hit test in the local coordinates of [target].
+  final Offset localPosition;
+
+  @override
+  String toString() => '${describeIdentity(target)}@$localPosition';
+}
+
+/// Parent data used by [RenderBox] and its subclasses.
+class BoxParentData extends ParentData {
+  /// The offset at which to paint the child in the parent's coordinate system.
+  Offset offset = Offset.zero;
+
+  @override
+  String toString() => 'offset=$offset';
+}
+
+/// Abstract [ParentData] subclass for [RenderBox] subclasses that want the
+/// [ContainerRenderObjectMixin].
+///
+/// This is a convenience class that mixes in the relevant classes with
+/// the relevant type arguments.
+abstract class ContainerBoxParentData<ChildType extends RenderObject> extends BoxParentData with ContainerParentDataMixin<ChildType> { }
+
+enum _IntrinsicDimension { minWidth, maxWidth, minHeight, maxHeight }
+
+@immutable
+class _IntrinsicDimensionsCacheEntry {
+  const _IntrinsicDimensionsCacheEntry(this.dimension, this.argument);
+
+  final _IntrinsicDimension dimension;
+  final double argument;
+
+  @override
+  bool operator ==(Object other) {
+    return other is _IntrinsicDimensionsCacheEntry
+        && other.dimension == dimension
+        && other.argument == argument;
+  }
+
+  @override
+  int get hashCode => hashValues(dimension, argument);
+}
+
+/// A render object in a 2D Cartesian coordinate system.
+///
+/// The [size] of each box is expressed as a width and a height. Each box has
+/// its own coordinate system in which its upper left corner is placed at (0,
+/// 0). The lower right corner of the box is therefore at (width, height). The
+/// box contains all the points including the upper left corner and extending
+/// to, but not including, the lower right corner.
+///
+/// Box layout is performed by passing a [BoxConstraints] object down the tree.
+/// The box constraints establish a min and max value for the child's width and
+/// height. In determining its size, the child must respect the constraints
+/// given to it by its parent.
+///
+/// This protocol is sufficient for expressing a number of common box layout
+/// data flows. For example, to implement a width-in-height-out data flow, call
+/// your child's [layout] function with a set of box constraints with a tight
+/// width value (and pass true for parentUsesSize). After the child determines
+/// its height, use the child's height to determine your size.
+///
+/// ## Writing a RenderBox subclass
+///
+/// One would implement a new [RenderBox] subclass to describe a new layout
+/// model, new paint model, new hit-testing model, or new semantics model, while
+/// remaining in the Cartesian space defined by the [RenderBox] protocol.
+///
+/// To create a new protocol, consider subclassing [RenderObject] instead.
+///
+/// ### Constructors and properties of a new RenderBox subclass
+///
+/// The constructor will typically take a named argument for each property of
+/// the class. The value is then passed to a private field of the class and the
+/// constructor asserts its correctness (e.g. if it should not be null, it
+/// asserts it's not null).
+///
+/// Properties have the form of a getter/setter/field group like the following:
+///
+/// ```dart
+/// AxisDirection get axis => _axis;
+/// AxisDirection _axis;
+/// set axis(AxisDirection value) {
+///   assert(value != null); // same check as in the constructor
+///   if (value == _axis)
+///     return;
+///   _axis = value;
+///   markNeedsLayout();
+/// }
+/// ```
+///
+/// The setter will typically finish with either a call to [markNeedsLayout], if
+/// the layout uses this property, or [markNeedsPaint], if only the painter
+/// function does. (No need to call both, [markNeedsLayout] implies
+/// [markNeedsPaint].)
+///
+/// Consider layout and paint to be expensive; be conservative about calling
+/// [markNeedsLayout] or [markNeedsPaint]. They should only be called if the
+/// layout (or paint, respectively) has actually changed.
+///
+/// ### Children
+///
+/// If a render object is a leaf, that is, it cannot have any children, then
+/// ignore this section. (Examples of leaf render objects are [RenderImage] and
+/// [RenderParagraph].)
+///
+/// For render objects with children, there are four possible scenarios:
+///
+/// * A single [RenderBox] child. In this scenario, consider inheriting from
+///   [RenderProxyBox] (if the render object sizes itself to match the child) or
+///   [RenderShiftedBox] (if the child will be smaller than the box and the box
+///   will align the child inside itself).
+///
+/// * A single child, but it isn't a [RenderBox]. Use the
+///   [RenderObjectWithChildMixin] mixin.
+///
+/// * A single list of children. Use the [ContainerRenderObjectMixin] mixin.
+///
+/// * A more complicated child model.
+///
+/// #### Using RenderProxyBox
+///
+/// By default, a [RenderProxyBox] render object sizes itself to fit its child, or
+/// to be as small as possible if there is no child; it passes all hit testing
+/// and painting on to the child, and intrinsic dimensions and baseline
+/// measurements similarly are proxied to the child.
+///
+/// A subclass of [RenderProxyBox] just needs to override the parts of the
+/// [RenderBox] protocol that matter. For example, [RenderOpacity] just
+/// overrides the paint method (and [alwaysNeedsCompositing] to reflect what the
+/// paint method does, and the [visitChildrenForSemantics] method so that the
+/// child is hidden from accessibility tools when it's invisible), and adds an
+/// [RenderOpacity.opacity] field.
+///
+/// [RenderProxyBox] assumes that the child is the size of the parent and
+/// positioned at 0,0. If this is not true, then use [RenderShiftedBox] instead.
+///
+/// See
+/// [proxy_box.dart](https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/proxy_box.dart)
+/// for examples of inheriting from [RenderProxyBox].
+///
+/// #### Using RenderShiftedBox
+///
+/// By default, a [RenderShiftedBox] acts much like a [RenderProxyBox] but
+/// without assuming that the child is positioned at 0,0 (the actual position
+/// recorded in the child's [parentData] field is used), and without providing a
+/// default layout algorithm.
+///
+/// See
+/// [shifted_box.dart](https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/shifted_box.dart)
+/// for examples of inheriting from [RenderShiftedBox].
+///
+/// #### Kinds of children and child-specific data
+///
+/// A [RenderBox] doesn't have to have [RenderBox] children. One can use another
+/// subclass of [RenderObject] for a [RenderBox]'s children. See the discussion
+/// at [RenderObject].
+///
+/// Children can have additional data owned by the parent but stored on the
+/// child using the [parentData] field. The class used for that data must
+/// inherit from [ParentData]. The [setupParentData] method is used to
+/// initialize the [parentData] field of a child when the child is attached.
+///
+/// By convention, [RenderBox] objects that have [RenderBox] children use the
+/// [BoxParentData] class, which has a [BoxParentData.offset] field to store the
+/// position of the child relative to the parent. ([RenderProxyBox] does not
+/// need this offset and therefore is an exception to this rule.)
+///
+/// #### Using RenderObjectWithChildMixin
+///
+/// If a render object has a single child but it isn't a [RenderBox], then the
+/// [RenderObjectWithChildMixin] class, which is a mixin that will handle the
+/// boilerplate of managing a child, will be useful.
+///
+/// It's a generic class with one type argument, the type of the child. For
+/// example, if you are building a `RenderFoo` class which takes a single
+/// `RenderBar` child, you would use the mixin as follows:
+///
+/// ```dart
+/// class RenderFoo extends RenderBox
+///   with RenderObjectWithChildMixin<RenderBar> {
+///   // ...
+/// }
+/// ```
+///
+/// Since the `RenderFoo` class itself is still a [RenderBox] in this case, you
+/// still have to implement the [RenderBox] layout algorithm, as well as
+/// features like intrinsics and baselines, painting, and hit testing.
+///
+/// #### Using ContainerRenderObjectMixin
+///
+/// If a render box can have multiple children, then the
+/// [ContainerRenderObjectMixin] mixin can be used to handle the boilerplate. It
+/// uses a linked list to model the children in a manner that is easy to mutate
+/// dynamically and that can be walked efficiently. Random access is not
+/// efficient in this model; if you need random access to the children consider
+/// the next section on more complicated child models.
+///
+/// The [ContainerRenderObjectMixin] class has two type arguments. The first is
+/// the type of the child objects. The second is the type for their
+/// [parentData]. The class used for [parentData] must itself have the
+/// [ContainerParentDataMixin] class mixed into it; this is where
+/// [ContainerRenderObjectMixin] stores the linked list. A [ParentData] class
+/// can extend [ContainerBoxParentData]; this is essentially
+/// [BoxParentData] mixed with [ContainerParentDataMixin]. For example, if a
+/// `RenderFoo` class wanted to have a linked list of [RenderBox] children, one
+/// might create a `FooParentData` class as follows:
+///
+/// ```dart
+/// class FooParentData extends ContainerBoxParentData<RenderBox> {
+///   // (any fields you might need for these children)
+/// }
+/// ```
+///
+/// When using [ContainerRenderObjectMixin] in a [RenderBox], consider mixing in
+/// [RenderBoxContainerDefaultsMixin], which provides a collection of utility
+/// methods that implement common parts of the [RenderBox] protocol (such as
+/// painting the children).
+///
+/// The declaration of the `RenderFoo` class itself would thus look like this:
+///
+/// ```dart
+/// class RenderFoo extends RenderBox with
+///   ContainerRenderObjectMixin<RenderBox, FooParentData>,
+///   RenderBoxContainerDefaultsMixin<RenderBox, FooParentData> {
+///   // ...
+/// }
+/// ```
+///
+/// When walking the children (e.g. during layout), the following pattern is
+/// commonly used (in this case assuming that the children are all [RenderBox]
+/// objects and that this render object uses `FooParentData` objects for its
+/// children's [parentData] fields):
+///
+/// ```dart
+/// RenderBox child = firstChild;
+/// while (child != null) {
+///   final FooParentData childParentData = child.parentData;
+///   // ...operate on child and childParentData...
+///   assert(child.parentData == childParentData);
+///   child = childParentData.nextSibling;
+/// }
+/// ```
+///
+/// #### More complicated child models
+///
+/// Render objects can have more complicated models, for example a map of
+/// children keyed on an enum, or a 2D grid of efficiently randomly-accessible
+/// children, or multiple lists of children, etc. If a render object has a model
+/// that can't be handled by the mixins above, it must implement the
+/// [RenderObject] child protocol, as follows:
+///
+/// * Any time a child is removed, call [dropChild] with the child.
+///
+/// * Any time a child is added, call [adoptChild] with the child.
+///
+/// * Implement the [attach] method such that it calls [attach] on each child.
+///
+/// * Implement the [detach] method such that it calls [detach] on each child.
+///
+/// * Implement the [redepthChildren] method such that it calls [redepthChild]
+///   on each child.
+///
+/// * Implement the [visitChildren] method such that it calls its argument for
+///   each child, typically in paint order (back-most to front-most).
+///
+/// * Implement [debugDescribeChildren] such that it outputs a [DiagnosticsNode]
+///   for each child.
+///
+/// Implementing these seven bullet points is essentially all that the two
+/// aforementioned mixins do.
+///
+/// ### Layout
+///
+/// [RenderBox] classes implement a layout algorithm. They have a set of
+/// constraints provided to them, and they size themselves based on those
+/// constraints and whatever other inputs they may have (for example, their
+/// children or properties).
+///
+/// When implementing a [RenderBox] subclass, one must make a choice. Does it
+/// size itself exclusively based on the constraints, or does it use any other
+/// information in sizing itself? An example of sizing purely based on the
+/// constraints would be growing to fit the parent.
+///
+/// Sizing purely based on the constraints allows the system to make some
+/// significant optimizations. Classes that use this approach should override
+/// [sizedByParent] to return true, and then override [computeDryLayout] to
+/// compute the [Size] using nothing but the constraints, e.g.:
+///
+/// ```dart
+/// @override
+/// bool get sizedByParent => true;
+///
+/// @override
+/// Size computeDryLayout(BoxConstraints constraints) {
+///   return constraints.smallest;
+/// }
+/// ```
+///
+/// Otherwise, the size is set in the [performLayout] function.
+///
+/// The [performLayout] function is where render boxes decide, if they are not
+/// [sizedByParent], what [size] they should be, and also where they decide
+/// where their children should be.
+///
+/// #### Layout of RenderBox children
+///
+/// The [performLayout] function should call the [layout] function of each (box)
+/// child, passing it a [BoxConstraints] object describing the constraints
+/// within which the child can render. Passing tight constraints (see
+/// [BoxConstraints.isTight]) to the child will allow the rendering library to
+/// apply some optimizations, as it knows that if the constraints are tight, the
+/// child's dimensions cannot change even if the layout of the child itself
+/// changes.
+///
+/// If the [performLayout] function will use the child's size to affect other
+/// aspects of the layout, for example if the render box sizes itself around the
+/// child, or positions several children based on the size of those children,
+/// then it must specify the `parentUsesSize` argument to the child's [layout]
+/// function, setting it to true.
+///
+/// This flag turns off some optimizations; algorithms that do not rely on the
+/// children's sizes will be more efficient. (In particular, relying on the
+/// child's [size] means that if the child is marked dirty for layout, the
+/// parent will probably also be marked dirty for layout, unless the
+/// [constraints] given by the parent to the child were tight constraints.)
+///
+/// For [RenderBox] classes that do not inherit from [RenderProxyBox], once they
+/// have laid out their children, they should also position them, by setting the
+/// [BoxParentData.offset] field of each child's [parentData] object.
+///
+/// #### Layout of non-RenderBox children
+///
+/// The children of a [RenderBox] do not have to be [RenderBox]es themselves. If
+/// they use another protocol (as discussed at [RenderObject]), then instead of
+/// [BoxConstraints], the parent would pass in the appropriate [Constraints]
+/// subclass, and instead of reading the child's size, the parent would read
+/// whatever the output of [layout] is for that layout protocol. The
+/// `parentUsesSize` flag is still used to indicate whether the parent is going
+/// to read that output, and optimizations still kick in if the child has tight
+/// constraints (as defined by [Constraints.isTight]).
+///
+/// ### Painting
+///
+/// To describe how a render box paints, implement the [paint] method. It is
+/// given a [PaintingContext] object and an [Offset]. The painting context
+/// provides methods to affect the layer tree as well as a
+/// [PaintingContext.canvas] which can be used to add drawing commands. The
+/// canvas object should not be cached across calls to the [PaintingContext]'s
+/// methods; every time a method on [PaintingContext] is called, there is a
+/// chance that the canvas will change identity. The offset specifies the
+/// position of the top left corner of the box in the coordinate system of the
+/// [PaintingContext.canvas].
+///
+/// To draw text on a canvas, use a [TextPainter].
+///
+/// To draw an image to a canvas, use the [paintImage] method.
+///
+/// A [RenderBox] that uses methods on [PaintingContext] that introduce new
+/// layers should override the [alwaysNeedsCompositing] getter and set it to
+/// true. If the object sometimes does and sometimes does not, it can have that
+/// getter return true in some cases and false in others. In that case, whenever
+/// the return value would change, call [markNeedsCompositingBitsUpdate]. (This
+/// is done automatically when a child is added or removed, so you don't have to
+/// call it explicitly if the [alwaysNeedsCompositing] getter only changes value
+/// based on the presence or absence of children.)
+///
+/// Anytime anything changes on the object that would cause the [paint] method
+/// to paint something different (but would not cause the layout to change),
+/// the object should call [markNeedsPaint].
+///
+/// #### Painting children
+///
+/// The [paint] method's `context` argument has a [PaintingContext.paintChild]
+/// method, which should be called for each child that is to be painted. It
+/// should be given a reference to the child, and an [Offset] giving the
+/// position of the child relative to the parent.
+///
+/// If the [paint] method applies a transform to the painting context before
+/// painting children (or generally applies an additional offset beyond the
+/// offset it was itself given as an argument), then the [applyPaintTransform]
+/// method should also be overridden. That method must adjust the matrix that it
+/// is given in the same manner as it transformed the painting context and
+/// offset before painting the given child. This is used by the [globalToLocal]
+/// and [localToGlobal] methods.
+///
+/// #### Hit Tests
+///
+/// Hit testing for render boxes is implemented by the [hitTest] method. The
+/// default implementation of this method defers to [hitTestSelf] and
+/// [hitTestChildren]. When implementing hit testing, you can either override
+/// these latter two methods, or ignore them and just override [hitTest].
+///
+/// The [hitTest] method itself is given an [Offset], and must return true if the
+/// object or one of its children has absorbed the hit (preventing objects below
+/// this one from being hit), or false if the hit can continue to other objects
+/// below this one.
+///
+/// For each child [RenderBox], the [hitTest] method on the child should be
+/// called with the same [HitTestResult] argument and with the point transformed
+/// into the child's coordinate space (in the same manner that the
+/// [applyPaintTransform] method would). The default implementation defers to
+/// [hitTestChildren] to call the children. [RenderBoxContainerDefaultsMixin]
+/// provides a [RenderBoxContainerDefaultsMixin.defaultHitTestChildren] method
+/// that does this assuming that the children are axis-aligned, not transformed,
+/// and positioned according to the [BoxParentData.offset] field of the
+/// [parentData]; more elaborate boxes can override [hitTestChildren]
+/// accordingly.
+///
+/// If the object is hit, then it should also add itself to the [HitTestResult]
+/// object that is given as an argument to the [hitTest] method, using
+/// [HitTestResult.add]. The default implementation defers to [hitTestSelf] to
+/// determine if the box is hit. If the object adds itself before the children
+/// can add themselves, then it will be as if the object was above the children.
+/// If it adds itself after the children, then it will be as if it was below the
+/// children. Entries added to the [HitTestResult] object should use the
+/// [BoxHitTestEntry] class. The entries are subsequently walked by the system
+/// in the order they were added, and for each entry, the target's [handleEvent]
+/// method is called, passing in the [HitTestEntry] object.
+///
+/// Hit testing cannot rely on painting having happened.
+///
+/// ### Semantics
+///
+/// For a render box to be accessible, implement the
+/// [describeApproximatePaintClip], [visitChildrenForSemantics], and
+/// [describeSemanticsConfiguration] methods. The default implementations are
+/// sufficient for objects that only affect layout, but nodes that represent
+/// interactive components or information (diagrams, text, images, etc) should
+/// provide more complete implementations. For more information, see the
+/// documentation for these members.
+///
+/// ### Intrinsics and Baselines
+///
+/// The layout, painting, hit testing, and semantics protocols are common to all
+/// render objects. [RenderBox] objects must implement two additional protocols:
+/// intrinsic sizing and baseline measurements.
+///
+/// There are four methods to implement for intrinsic sizing, to compute the
+/// minimum and maximum intrinsic width and height of the box. The documentation
+/// for these methods discusses the protocol in detail:
+/// [computeMinIntrinsicWidth], [computeMaxIntrinsicWidth],
+/// [computeMinIntrinsicHeight], [computeMaxIntrinsicHeight].
+///
+/// In addition, if the box has any children, it must implement
+/// [computeDistanceToActualBaseline]. [RenderProxyBox] provides a simple
+/// implementation that forwards to the child; [RenderShiftedBox] provides an
+/// implementation that offsets the child's baseline information by the position
+/// of the child relative to the parent. If you do not inherited from either of
+/// these classes, however, you must implement the algorithm yourself.
+abstract class RenderBox extends RenderObject {
+  @override
+  void setupParentData(covariant RenderObject child) {
+    if (child.parentData is! BoxParentData)
+      child.parentData = BoxParentData();
+  }
+
+  Map<_IntrinsicDimensionsCacheEntry, double>? _cachedIntrinsicDimensions;
+
+  double _computeIntrinsicDimension(_IntrinsicDimension dimension, double argument, double computer(double argument)) {
+    assert(RenderObject.debugCheckingIntrinsics || !debugDoingThisResize); // performResize should not depend on anything except the incoming constraints
+    bool shouldCache = true;
+    assert(() {
+      // we don't want the checked-mode intrinsic tests to affect
+      // who gets marked dirty, etc.
+      if (RenderObject.debugCheckingIntrinsics)
+        shouldCache = false;
+      return true;
+    }());
+    if (shouldCache) {
+      _cachedIntrinsicDimensions ??= <_IntrinsicDimensionsCacheEntry, double>{};
+      return _cachedIntrinsicDimensions!.putIfAbsent(
+        _IntrinsicDimensionsCacheEntry(dimension, argument),
+        () => computer(argument),
+      );
+    }
+    return computer(argument);
+  }
+
+  /// Returns the minimum width that this box could be without failing to
+  /// correctly paint its contents within itself, without clipping.
+  ///
+  /// The height argument may give a specific height to assume. The given height
+  /// can be infinite, meaning that the intrinsic width in an unconstrained
+  /// environment is being requested. The given height should never be negative
+  /// or null.
+  ///
+  /// This function should only be called on one's children. Calling this
+  /// function couples the child with the parent so that when the child's layout
+  /// changes, the parent is notified (via [markNeedsLayout]).
+  ///
+  /// Calling this function is expensive as it can result in O(N^2) behavior.
+  ///
+  /// Do not override this method. Instead, implement [computeMinIntrinsicWidth].
+  @mustCallSuper
+  double getMinIntrinsicWidth(double height) {
+    assert(() {
+      // `height` has a non-nullable return type, but might be null when
+      // running with weak checking, so we need to null check it anyway (and
+      // ignore the warning that the null-handling logic is dead code).
+      if (height == null) { // ignore: dead_code
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('The height argument to getMinIntrinsicWidth was null.'),
+          ErrorDescription('The argument to getMinIntrinsicWidth must not be negative or null.'),
+          ErrorHint('If you do not have a specific height in mind, then pass double.infinity instead.'),
+        ]);
+      }
+      if (height < 0.0) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('The height argument to getMinIntrinsicWidth was negative.'),
+          ErrorDescription('The argument to getMinIntrinsicWidth must not be negative or null.'),
+          ErrorHint(
+            'If you perform computations on another height before passing it to '
+            'getMinIntrinsicWidth, consider using math.max() or double.clamp() '
+            'to force the value into the valid range.'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    return _computeIntrinsicDimension(_IntrinsicDimension.minWidth, height, computeMinIntrinsicWidth);
+  }
+
+  /// Computes the value returned by [getMinIntrinsicWidth]. Do not call this
+  /// function directly, instead, call [getMinIntrinsicWidth].
+  ///
+  /// Override in subclasses that implement [performLayout]. This method should
+  /// return the minimum width that this box could be without failing to
+  /// correctly paint its contents within itself, without clipping.
+  ///
+  /// If the layout algorithm is independent of the context (e.g. it always
+  /// tries to be a particular size), or if the layout algorithm is
+  /// width-in-height-out, or if the layout algorithm uses both the incoming
+  /// width and height constraints (e.g. it always sizes itself to
+  /// [BoxConstraints.biggest]), then the `height` argument should be ignored.
+  ///
+  /// If the layout algorithm is strictly height-in-width-out, or is
+  /// height-in-width-out when the width is unconstrained, then the height
+  /// argument is the height to use.
+  ///
+  /// The `height` argument will never be negative or null. It may be infinite.
+  ///
+  /// If this algorithm depends on the intrinsic dimensions of a child, the
+  /// intrinsic dimensions of that child should be obtained using the functions
+  /// whose names start with `get`, not `compute`.
+  ///
+  /// This function should never return a negative or infinite value.
+  ///
+  /// ## Examples
+  ///
+  /// ### Text
+  ///
+  /// Text is the canonical example of a width-in-height-out algorithm. The
+  /// `height` argument is therefore ignored.
+  ///
+  /// Consider the string "Hello World" The _maximum_ intrinsic width (as
+  /// returned from [computeMaxIntrinsicWidth]) would be the width of the string
+  /// with no line breaks.
+  ///
+  /// The minimum intrinsic width would be the width of the widest word, "Hello"
+  /// or "World". If the text is rendered in an even narrower width, however, it
+  /// might still not overflow. For example, maybe the rendering would put a
+  /// line-break half-way through the words, as in "Hel⁞lo⁞Wor⁞ld". However,
+  /// this wouldn't be a _correct_ rendering, and [computeMinIntrinsicWidth] is
+  /// supposed to render the minimum width that the box could be without failing
+  /// to _correctly_ paint the contents within itself.
+  ///
+  /// The minimum intrinsic _height_ for a given width smaller than the minimum
+  /// intrinsic width could therefore be greater than the minimum intrinsic
+  /// height for the minimum intrinsic width.
+  ///
+  /// ### Viewports (e.g. scrolling lists)
+  ///
+  /// Some render boxes are intended to clip their children. For example, the
+  /// render box for a scrolling list might always size itself to its parents'
+  /// size (or rather, to the maximum incoming constraints), regardless of the
+  /// children's sizes, and then clip the children and position them based on
+  /// the current scroll offset.
+  ///
+  /// The intrinsic dimensions in these cases still depend on the children, even
+  /// though the layout algorithm sizes the box in a way independent of the
+  /// children. It is the size that is needed to paint the box's contents (in
+  /// this case, the children) _without clipping_ that matters.
+  ///
+  /// ### When the intrinsic dimensions cannot be known
+  ///
+  /// There are cases where render objects do not have an efficient way to
+  /// compute their intrinsic dimensions. For example, it may be prohibitively
+  /// expensive to reify and measure every child of a lazy viewport (viewports
+  /// generally only instantiate the actually visible children), or the
+  /// dimensions may be computed by a callback about which the render object
+  /// cannot reason.
+  ///
+  /// In such cases, it may be impossible (or at least impractical) to actually
+  /// return a valid answer. In such cases, the intrinsic functions should throw
+  /// when [RenderObject.debugCheckingIntrinsics] is false and asserts are
+  /// enabled, and return 0.0 otherwise.
+  ///
+  /// See the implementations of [LayoutBuilder] or [RenderViewportBase] for
+  /// examples (in particular,
+  /// [RenderViewportBase.debugThrowIfNotCheckingIntrinsics]).
+  ///
+  /// ### Aspect-ratio-driven boxes
+  ///
+  /// Some boxes always return a fixed size based on the constraints. For these
+  /// boxes, the intrinsic functions should return the appropriate size when the
+  /// incoming `height` or `width` argument is finite, treating that as a tight
+  /// constraint in the respective direction and treating the other direction's
+  /// constraints as unbounded. This is because the definitions of
+  /// [computeMinIntrinsicWidth] and [computeMinIntrinsicHeight] are in terms of
+  /// what the dimensions _could be_, and such boxes can only be one size in
+  /// such cases.
+  ///
+  /// When the incoming argument is not finite, then they should return the
+  /// actual intrinsic dimensions based on the contents, as any other box would.
+  ///
+  /// See also:
+  ///
+  ///  * [computeMaxIntrinsicWidth], which computes the smallest width beyond
+  ///    which increasing the width never decreases the preferred height.
+  @protected
+  double computeMinIntrinsicWidth(double height) {
+    return 0.0;
+  }
+
+  /// Returns the smallest width beyond which increasing the width never
+  /// decreases the preferred height. The preferred height is the value that
+  /// would be returned by [getMinIntrinsicHeight] for that width.
+  ///
+  /// The height argument may give a specific height to assume. The given height
+  /// can be infinite, meaning that the intrinsic width in an unconstrained
+  /// environment is being requested. The given height should never be negative
+  /// or null.
+  ///
+  /// This function should only be called on one's children. Calling this
+  /// function couples the child with the parent so that when the child's layout
+  /// changes, the parent is notified (via [markNeedsLayout]).
+  ///
+  /// Calling this function is expensive as it can result in O(N^2) behavior.
+  ///
+  /// Do not override this method. Instead, implement
+  /// [computeMaxIntrinsicWidth].
+  @mustCallSuper
+  double getMaxIntrinsicWidth(double height) {
+    assert(() {
+      // `height` has a non-nullable return type, but might be null when
+      // running with weak checking, so we need to null check it anyway (and
+      // ignore the warning that the null-handling logic is dead code).
+      if (height == null) { // ignore: dead_code
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('The height argument to getMaxIntrinsicWidth was null.'),
+          ErrorDescription('The argument to getMaxIntrinsicWidth must not be negative or null.'),
+          ErrorHint('If you do not have a specific height in mind, then pass double.infinity instead.'),
+        ]);
+      }
+      if (height < 0.0) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('The height argument to getMaxIntrinsicWidth was negative.'),
+          ErrorDescription('The argument to getMaxIntrinsicWidth must not be negative or null.'),
+          ErrorHint(
+            'If you perform computations on another height before passing it to '
+            'getMaxIntrinsicWidth, consider using math.max() or double.clamp() '
+            'to force the value into the valid range.'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    return _computeIntrinsicDimension(_IntrinsicDimension.maxWidth, height, computeMaxIntrinsicWidth);
+  }
+
+  /// Computes the value returned by [getMaxIntrinsicWidth]. Do not call this
+  /// function directly, instead, call [getMaxIntrinsicWidth].
+  ///
+  /// Override in subclasses that implement [performLayout]. This should return
+  /// the smallest width beyond which increasing the width never decreases the
+  /// preferred height. The preferred height is the value that would be returned
+  /// by [computeMinIntrinsicHeight] for that width.
+  ///
+  /// If the layout algorithm is strictly height-in-width-out, or is
+  /// height-in-width-out when the width is unconstrained, then this should
+  /// return the same value as [computeMinIntrinsicWidth] for the same height.
+  ///
+  /// Otherwise, the height argument should be ignored, and the returned value
+  /// should be equal to or bigger than the value returned by
+  /// [computeMinIntrinsicWidth].
+  ///
+  /// The `height` argument will never be negative or null. It may be infinite.
+  ///
+  /// The value returned by this method might not match the size that the object
+  /// would actually take. For example, a [RenderBox] subclass that always
+  /// exactly sizes itself using [BoxConstraints.biggest] might well size itself
+  /// bigger than its max intrinsic size.
+  ///
+  /// If this algorithm depends on the intrinsic dimensions of a child, the
+  /// intrinsic dimensions of that child should be obtained using the functions
+  /// whose names start with `get`, not `compute`.
+  ///
+  /// This function should never return a negative or infinite value.
+  ///
+  /// See also:
+  ///
+  ///  * [computeMinIntrinsicWidth], which has usage examples.
+  @protected
+  double computeMaxIntrinsicWidth(double height) {
+    return 0.0;
+  }
+
+  /// Returns the minimum height that this box could be without failing to
+  /// correctly paint its contents within itself, without clipping.
+  ///
+  /// The width argument may give a specific width to assume. The given width
+  /// can be infinite, meaning that the intrinsic height in an unconstrained
+  /// environment is being requested. The given width should never be negative
+  /// or null.
+  ///
+  /// This function should only be called on one's children. Calling this
+  /// function couples the child with the parent so that when the child's layout
+  /// changes, the parent is notified (via [markNeedsLayout]).
+  ///
+  /// Calling this function is expensive as it can result in O(N^2) behavior.
+  ///
+  /// Do not override this method. Instead, implement
+  /// [computeMinIntrinsicHeight].
+  @mustCallSuper
+  double getMinIntrinsicHeight(double width) {
+    assert(() {
+      // `width` has a non-nullable return type, but might be null when
+      // running with weak checking, so we need to null check it anyway (and
+      // ignore the warning that the null-handling logic is dead code).
+      if (width == null) { // ignore: dead_code
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('The width argument to getMinIntrinsicHeight was null.'),
+          ErrorDescription('The argument to getMinIntrinsicHeight must not be negative or null.'),
+          ErrorHint('If you do not have a specific width in mind, then pass double.infinity instead.'),
+        ]);
+      }
+      if (width < 0.0) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('The width argument to getMinIntrinsicHeight was negative.'),
+          ErrorDescription('The argument to getMinIntrinsicHeight must not be negative or null.'),
+          ErrorHint(
+            'If you perform computations on another width before passing it to '
+            'getMinIntrinsicHeight, consider using math.max() or double.clamp() '
+            'to force the value into the valid range.'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    return _computeIntrinsicDimension(_IntrinsicDimension.minHeight, width, computeMinIntrinsicHeight);
+  }
+
+  /// Computes the value returned by [getMinIntrinsicHeight]. Do not call this
+  /// function directly, instead, call [getMinIntrinsicHeight].
+  ///
+  /// Override in subclasses that implement [performLayout]. Should return the
+  /// minimum height that this box could be without failing to correctly paint
+  /// its contents within itself, without clipping.
+  ///
+  /// If the layout algorithm is independent of the context (e.g. it always
+  /// tries to be a particular size), or if the layout algorithm is
+  /// height-in-width-out, or if the layout algorithm uses both the incoming
+  /// height and width constraints (e.g. it always sizes itself to
+  /// [BoxConstraints.biggest]), then the `width` argument should be ignored.
+  ///
+  /// If the layout algorithm is strictly width-in-height-out, or is
+  /// width-in-height-out when the height is unconstrained, then the width
+  /// argument is the width to use.
+  ///
+  /// The `width` argument will never be negative or null. It may be infinite.
+  ///
+  /// If this algorithm depends on the intrinsic dimensions of a child, the
+  /// intrinsic dimensions of that child should be obtained using the functions
+  /// whose names start with `get`, not `compute`.
+  ///
+  /// This function should never return a negative or infinite value.
+  ///
+  /// See also:
+  ///
+  ///  * [computeMinIntrinsicWidth], which has usage examples.
+  ///  * [computeMaxIntrinsicHeight], which computes the smallest height beyond
+  ///    which increasing the height never decreases the preferred width.
+  @protected
+  double computeMinIntrinsicHeight(double width) {
+    return 0.0;
+  }
+
+  /// Returns the smallest height beyond which increasing the height never
+  /// decreases the preferred width. The preferred width is the value that
+  /// would be returned by [getMinIntrinsicWidth] for that height.
+  ///
+  /// The width argument may give a specific width to assume. The given width
+  /// can be infinite, meaning that the intrinsic height in an unconstrained
+  /// environment is being requested. The given width should never be negative
+  /// or null.
+  ///
+  /// This function should only be called on one's children. Calling this
+  /// function couples the child with the parent so that when the child's layout
+  /// changes, the parent is notified (via [markNeedsLayout]).
+  ///
+  /// Calling this function is expensive as it can result in O(N^2) behavior.
+  ///
+  /// Do not override this method. Instead, implement
+  /// [computeMaxIntrinsicHeight].
+  @mustCallSuper
+  double getMaxIntrinsicHeight(double width) {
+    assert(() {
+      // `width` has a non-nullable return type, but might be null when
+      // running with weak checking, so we need to null check it anyway (and
+      // ignore the warning that the null-handling logic is dead code).
+      if (width == null) { // ignore: dead_code
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('The width argument to getMaxIntrinsicHeight was null.'),
+          ErrorDescription('The argument to getMaxIntrinsicHeight must not be negative or null.'),
+          ErrorHint('If you do not have a specific width in mind, then pass double.infinity instead.'),
+        ]);
+      }
+      if (width < 0.0) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('The width argument to getMaxIntrinsicHeight was negative.'),
+          ErrorDescription('The argument to getMaxIntrinsicHeight must not be negative or null.'),
+          ErrorHint(
+            'If you perform computations on another width before passing it to '
+            'getMaxIntrinsicHeight, consider using math.max() or double.clamp() '
+            'to force the value into the valid range.'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    return _computeIntrinsicDimension(_IntrinsicDimension.maxHeight, width, computeMaxIntrinsicHeight);
+  }
+
+  /// Computes the value returned by [getMaxIntrinsicHeight]. Do not call this
+  /// function directly, instead, call [getMaxIntrinsicHeight].
+  ///
+  /// Override in subclasses that implement [performLayout]. Should return the
+  /// smallest height beyond which increasing the height never decreases the
+  /// preferred width. The preferred width is the value that would be returned
+  /// by [computeMinIntrinsicWidth] for that height.
+  ///
+  /// If the layout algorithm is strictly width-in-height-out, or is
+  /// width-in-height-out when the height is unconstrained, then this should
+  /// return the same value as [computeMinIntrinsicHeight] for the same width.
+  ///
+  /// Otherwise, the width argument should be ignored, and the returned value
+  /// should be equal to or bigger than the value returned by
+  /// [computeMinIntrinsicHeight].
+  ///
+  /// The `width` argument will never be negative or null. It may be infinite.
+  ///
+  /// The value returned by this method might not match the size that the object
+  /// would actually take. For example, a [RenderBox] subclass that always
+  /// exactly sizes itself using [BoxConstraints.biggest] might well size itself
+  /// bigger than its max intrinsic size.
+  ///
+  /// If this algorithm depends on the intrinsic dimensions of a child, the
+  /// intrinsic dimensions of that child should be obtained using the functions
+  /// whose names start with `get`, not `compute`.
+  ///
+  /// This function should never return a negative or infinite value.
+  ///
+  /// See also:
+  ///
+  ///  * [computeMinIntrinsicWidth], which has usage examples.
+  @protected
+  double computeMaxIntrinsicHeight(double width) {
+    return 0.0;
+  }
+
+  Map<BoxConstraints, Size>? _cachedDryLayoutSizes;
+  bool _computingThisDryLayout = false;
+
+  /// Returns the [Size] that this [RenderBox] would like to be given the
+  /// provided [BoxConstraints].
+  ///
+  /// The size returned by this method is guaranteed to be the same size that
+  /// this [RenderBox] computes for itself during layout given the same
+  /// constraints.
+  ///
+  /// This function should only be called on one's children. Calling this
+  /// function couples the child with the parent so that when the child's layout
+  /// changes, the parent is notified (via [markNeedsLayout]).
+  ///
+  /// This layout is called "dry" layout as opposed to the regular "wet" layout
+  /// run performed by [performLayout] because it computes the desired size for
+  /// the given constraints without changing any internal state.
+  ///
+  /// Calling this function is expensive as it can result in O(N^2) behavior.
+  ///
+  /// Do not override this method. Instead, implement [computeDryLayout].
+  @mustCallSuper
+  Size getDryLayout(BoxConstraints constraints) {
+    bool shouldCache = true;
+    assert(() {
+      // we don't want the checked-mode intrinsic tests to affect
+      // who gets marked dirty, etc.
+      if (RenderObject.debugCheckingIntrinsics)
+        shouldCache = false;
+      return true;
+    }());
+    if (shouldCache) {
+      _cachedDryLayoutSizes ??= <BoxConstraints, Size>{};
+      return _cachedDryLayoutSizes!.putIfAbsent(constraints, () => _computeDryLayout(constraints));
+    }
+    return _computeDryLayout(constraints);
+  }
+
+  Size _computeDryLayout(BoxConstraints constraints) {
+    assert(() {
+      assert(!_computingThisDryLayout);
+      _computingThisDryLayout = true;
+      return true;
+    }());
+    final Size result =  computeDryLayout(constraints);
+    assert(() {
+      assert(_computingThisDryLayout);
+      _computingThisDryLayout = false;
+      return true;
+    }());
+    return result;
+  }
+
+  /// Computes the value returned by [getDryLayout]. Do not call this
+  /// function directly, instead, call [getDryLayout].
+  ///
+  /// Override in subclasses that implement [performLayout] or [performResize]
+  /// or when setting [sizedByParent] to true without overriding
+  /// [performResize]. This method should return the [Size] that this
+  /// [RenderBox] would like to be given the provided [BoxConstraints].
+  ///
+  /// The size returned by this method must match the [size] that the
+  /// [RenderBox] will compute for itself in [performLayout] (or
+  /// [performResize], if [sizedByParent] is true).
+  ///
+  /// If this algorithm depends on the size of a child, the size of that child
+  /// should be obtained using its [getDryLayout] method.
+  ///
+  /// This layout is called "dry" layout as opposed to the regular "wet" layout
+  /// run performed by [performLayout] because it computes the desired size for
+  /// the given constraints without changing any internal state.
+  ///
+  /// ### When the size cannot be known
+  ///
+  /// There are cases where render objects do not have an efficient way to
+  /// compute their size without doing a full layout. For example, the size
+  /// may depend on the baseline of a child (which is not available without
+  /// doing a full layout), it may be computed by a callback about which the
+  /// render object cannot reason, or the layout is so complex that it
+  /// is simply impractical to calculate the size in an efficient way.
+  ///
+  /// In such cases, it may be impossible (or at least impractical) to actually
+  /// return a valid answer. In such cases, the function should call
+  /// [debugCannotComputeDryLayout] from within an assert and and return a dummy
+  /// value of `const Size(0, 0)`.
+  @protected
+  Size computeDryLayout(BoxConstraints constraints) {
+    assert(debugCannotComputeDryLayout(
+      error: FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('The ${objectRuntimeType(this, 'RenderBox')} class does not implement "computeDryLayout".'),
+        ErrorHint(
+          'If you are not writing your own RenderBox subclass, then this is not\n'
+          'your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=2_bug.md'
+        ),
+      ]),
+    ));
+    return const Size(0, 0);
+  }
+
+  static bool _dryLayoutCalculationValid = true;
+
+  /// Called from [computeDryLayout] within an assert if the given [RenderBox]
+  /// subclass does not support calculating a dry layout.
+  ///
+  /// When asserts are enabled and [debugCheckingIntrinsics] is not true, this
+  /// method will either throw the provided [FlutterError] or it will create and
+  /// throw a [FlutterError] with the provided `reason`. Otherwise, it will
+  /// simply return true.
+  ///
+  /// One of the arguments has to be provided.
+  ///
+  /// See also:
+  ///
+  ///  * [computeDryLayout], which lists some reasons why it may not be feasible
+  ///    to compute the dry layout.
+  bool debugCannotComputeDryLayout({String? reason, FlutterError? error}) {
+    assert((reason == null) != (error == null));
+    assert(() {
+      if (!RenderObject.debugCheckingIntrinsics) {
+        if (reason != null) {
+          assert(error ==null);
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('The ${objectRuntimeType(this, 'RenderBox')} class does not support dry layout.'),
+            if (reason.isNotEmpty) ErrorDescription(reason),
+          ]);
+        }
+        assert(error != null);
+        throw error!;
+      }
+      _dryLayoutCalculationValid = false;
+      return true;
+    }());
+    return true;
+  }
+
+  /// Whether this render object has undergone layout and has a [size].
+  bool get hasSize => _size != null;
+
+  /// The size of this render box computed during layout.
+  ///
+  /// This value is stale whenever this object is marked as needing layout.
+  /// During [performLayout], do not read the size of a child unless you pass
+  /// true for parentUsesSize when calling the child's [layout] function.
+  ///
+  /// The size of a box should be set only during the box's [performLayout] or
+  /// [performResize] functions. If you wish to change the size of a box outside
+  /// of those functions, call [markNeedsLayout] instead to schedule a layout of
+  /// the box.
+  Size get size {
+    assert(hasSize, 'RenderBox was not laid out: ${toString()}');
+    assert(() {
+      final Size? _size = this._size;
+      if (_size is _DebugSize) {
+        assert(_size._owner == this);
+        if (RenderObject.debugActiveLayout != null) {
+          assert(
+            debugDoingThisResize || debugDoingThisLayout || _computingThisDryLayout ||
+              (RenderObject.debugActiveLayout == parent && _size._canBeUsedByParent),
+            'RenderBox.size accessed beyond the scope of resize, layout, or '
+            'permitted parent access. RenderBox can always access its own size, '
+            'otherwise, the only object that is allowed to read RenderBox.size '
+            'is its parent, if they have said they will. It you hit this assert '
+            'trying to access a child\'s size, pass "parentUsesSize: true" to '
+            'that child\'s layout().'
+          );
+        }
+        assert(_size == this._size);
+      }
+      return true;
+    }());
+    return _size!;
+  }
+  Size? _size;
+  /// Setting the size, in checked mode, triggers some analysis of the render box,
+  /// as implemented by [debugAssertDoesMeetConstraints], including calling the intrinsic
+  /// sizing methods and checking that they meet certain invariants.
+  @protected
+  set size(Size value) {
+    assert(!(debugDoingThisResize && debugDoingThisLayout));
+    assert(sizedByParent || !debugDoingThisResize);
+    assert(() {
+      if ((sizedByParent && debugDoingThisResize) ||
+          (!sizedByParent && debugDoingThisLayout))
+        return true;
+      assert(!debugDoingThisResize);
+      final List<DiagnosticsNode> information = <DiagnosticsNode>[
+        ErrorSummary('RenderBox size setter called incorrectly.'),
+      ];
+      if (debugDoingThisLayout) {
+        assert(sizedByParent);
+        information.add(ErrorDescription('It appears that the size setter was called from performLayout().'));
+      } else {
+        information.add(ErrorDescription(
+          'The size setter was called from outside layout (neither performResize() nor performLayout() were being run for this object).'
+        ));
+        if (owner != null && owner!.debugDoingLayout)
+          information.add(ErrorDescription('Only the object itself can set its size. It is a contract violation for other objects to set it.'));
+      }
+      if (sizedByParent)
+        information.add(ErrorDescription('Because this RenderBox has sizedByParent set to true, it must set its size in performResize().'));
+      else
+        information.add(ErrorDescription('Because this RenderBox has sizedByParent set to false, it must set its size in performLayout().'));
+      throw FlutterError.fromParts(information);
+    }());
+    assert(() {
+      value = debugAdoptSize(value);
+      return true;
+    }());
+    _size = value;
+    assert(() {
+      debugAssertDoesMeetConstraints();
+      return true;
+    }());
+  }
+
+  /// Claims ownership of the given [Size].
+  ///
+  /// In debug mode, the [RenderBox] class verifies that [Size] objects obtained
+  /// from other [RenderBox] objects are only used according to the semantics of
+  /// the [RenderBox] protocol, namely that a [Size] from a [RenderBox] can only
+  /// be used by its parent, and then only if `parentUsesSize` was set.
+  ///
+  /// Sometimes, a [Size] that can validly be used ends up no longer being valid
+  /// over time. The common example is a [Size] taken from a child that is later
+  /// removed from the parent. In such cases, this method can be called to first
+  /// check whether the size can legitimately be used, and if so, to then create
+  /// a new [Size] that can be used going forward, regardless of what happens to
+  /// the original owner.
+  Size debugAdoptSize(Size value) {
+    Size result = value;
+    assert(() {
+      if (value is _DebugSize) {
+        if (value._owner != this) {
+          if (value._owner.parent != this) {
+            throw FlutterError.fromParts(<DiagnosticsNode>[
+              ErrorSummary('The size property was assigned a size inappropriately.'),
+              describeForError('The following render object'),
+              value._owner.describeForError('...was assigned a size obtained from'),
+              ErrorDescription(
+                'However, this second render object is not, or is no longer, a '
+                'child of the first, and it is therefore a violation of the '
+                'RenderBox layout protocol to use that size in the layout of the '
+                'first render object.'
+              ),
+              ErrorHint(
+                'If the size was obtained at a time where it was valid to read '
+                'the size (because the second render object above was a child '
+                'of the first at the time), then it should be adopted using '
+                'debugAdoptSize at that time.'
+              ),
+              ErrorHint(
+                'If the size comes from a grandchild or a render object from an '
+                'entirely different part of the render tree, then there is no '
+                'way to be notified when the size changes and therefore attempts '
+                'to read that size are almost certainly a source of bugs. A different '
+                'approach should be used.'
+              ),
+            ]);
+          }
+          if (!value._canBeUsedByParent) {
+            throw FlutterError.fromParts(<DiagnosticsNode>[
+              ErrorSummary("A child's size was used without setting parentUsesSize."),
+              describeForError('The following render object'),
+              value._owner.describeForError('...was assigned a size obtained from its child'),
+              ErrorDescription(
+                'However, when the child was laid out, the parentUsesSize argument '
+                'was not set or set to false. Subsequently this transpired to be '
+                'inaccurate: the size was nonetheless used by the parent.\n'
+                'It is important to tell the framework if the size will be used or not '
+                'as several important performance optimizations can be made if the '
+                'size will not be used by the parent.'
+              ),
+            ]);
+          }
+        }
+      }
+      result = _DebugSize(value, this, debugCanParentUseSize);
+      return true;
+    }());
+    return result;
+  }
+
+  @override
+  Rect get semanticBounds => Offset.zero & size;
+
+  @override
+  void debugResetSize() {
+    // updates the value of size._canBeUsedByParent if necessary
+    size = size;
+  }
+
+  Map<TextBaseline, double?>? _cachedBaselines;
+  static bool _debugDoingBaseline = false;
+  static bool _debugSetDoingBaseline(bool value) {
+    _debugDoingBaseline = value;
+    return true;
+  }
+
+  /// Returns the distance from the y-coordinate of the position of the box to
+  /// the y-coordinate of the first given baseline in the box's contents.
+  ///
+  /// Used by certain layout models to align adjacent boxes on a common
+  /// baseline, regardless of padding, font size differences, etc. If there is
+  /// no baseline, this function returns the distance from the y-coordinate of
+  /// the position of the box to the y-coordinate of the bottom of the box
+  /// (i.e., the height of the box) unless the caller passes true
+  /// for `onlyReal`, in which case the function returns null.
+  ///
+  /// Only call this function after calling [layout] on this box. You
+  /// are only allowed to call this from the parent of this box during
+  /// that parent's [performLayout] or [paint] functions.
+  ///
+  /// When implementing a [RenderBox] subclass, to override the baseline
+  /// computation, override [computeDistanceToActualBaseline].
+  double? getDistanceToBaseline(TextBaseline baseline, { bool onlyReal = false }) {
+    assert(!_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
+    assert(!debugNeedsLayout);
+    assert(() {
+      final RenderObject? parent = this.parent as RenderObject?;
+      if (owner!.debugDoingLayout)
+        return (RenderObject.debugActiveLayout == parent) && parent!.debugDoingThisLayout;
+      if (owner!.debugDoingPaint)
+        return ((RenderObject.debugActivePaint == parent) && parent!.debugDoingThisPaint) ||
+               ((RenderObject.debugActivePaint == this) && debugDoingThisPaint);
+      assert(parent == this.parent);
+      return false;
+    }());
+    assert(_debugSetDoingBaseline(true));
+    final double? result = getDistanceToActualBaseline(baseline);
+    assert(_debugSetDoingBaseline(false));
+    if (result == null && !onlyReal)
+      return size.height;
+    return result;
+  }
+
+  /// Calls [computeDistanceToActualBaseline] and caches the result.
+  ///
+  /// This function must only be called from [getDistanceToBaseline] and
+  /// [computeDistanceToActualBaseline]. Do not call this function directly from
+  /// outside those two methods.
+  @protected
+  @mustCallSuper
+  double? getDistanceToActualBaseline(TextBaseline baseline) {
+    assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
+    _cachedBaselines ??= <TextBaseline, double?>{};
+    _cachedBaselines!.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline));
+    return _cachedBaselines![baseline];
+  }
+
+  /// Returns the distance from the y-coordinate of the position of the box to
+  /// the y-coordinate of the first given baseline in the box's contents, if
+  /// any, or null otherwise.
+  ///
+  /// Do not call this function directly. If you need to know the baseline of a
+  /// child from an invocation of [performLayout] or [paint], call
+  /// [getDistanceToBaseline].
+  ///
+  /// Subclasses should override this method to supply the distances to their
+  /// baselines. When implementing this method, there are generally three
+  /// strategies:
+  ///
+  ///  * For classes that use the [ContainerRenderObjectMixin] child model,
+  ///    consider mixing in the [RenderBoxContainerDefaultsMixin] class and
+  ///    using
+  ///    [RenderBoxContainerDefaultsMixin.defaultComputeDistanceToFirstActualBaseline].
+  ///
+  ///  * For classes that define a particular baseline themselves, return that
+  ///    value directly.
+  ///
+  ///  * For classes that have a child to which they wish to defer the
+  ///    computation, call [getDistanceToActualBaseline] on the child (not
+  ///    [computeDistanceToActualBaseline], the internal implementation, and not
+  ///    [getDistanceToBaseline], the public entry point for this API).
+  @protected
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
+    return null;
+  }
+
+  /// The box constraints most recently received from the parent.
+  @override
+  BoxConstraints get constraints => super.constraints as BoxConstraints;
+
+  @override
+  void debugAssertDoesMeetConstraints() {
+    assert(constraints != null);
+    assert(() {
+      if (!hasSize) {
+        final DiagnosticsNode contract;
+        if (sizedByParent)
+          contract = ErrorDescription('Because this RenderBox has sizedByParent set to true, it must set its size in performResize().');
+        else
+          contract = ErrorDescription('Because this RenderBox has sizedByParent set to false, it must set its size in performLayout().');
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('RenderBox did not set its size during layout.'),
+          contract,
+          ErrorDescription('It appears that this did not happen; layout completed, but the size property is still null.'),
+          DiagnosticsProperty<RenderBox>('The RenderBox in question is', this, style: DiagnosticsTreeStyle.errorProperty),
+        ]);
+      }
+      // verify that the size is not infinite
+      if (!_size!.isFinite) {
+        final List<DiagnosticsNode> information = <DiagnosticsNode>[
+          ErrorSummary('$runtimeType object was given an infinite size during layout.'),
+          ErrorDescription(
+            'This probably means that it is a render object that tries to be '
+            'as big as possible, but it was put inside another render object '
+            'that allows its children to pick their own size.'
+          ),
+        ];
+        if (!constraints.hasBoundedWidth) {
+          RenderBox node = this;
+          while (!node.constraints.hasBoundedWidth && node.parent is RenderBox)
+            node = node.parent! as RenderBox;
+
+          information.add(node.describeForError('The nearest ancestor providing an unbounded width constraint is'));
+        }
+        if (!constraints.hasBoundedHeight) {
+          RenderBox node = this;
+          while (!node.constraints.hasBoundedHeight && node.parent is RenderBox)
+            node = node.parent! as RenderBox;
+
+          information.add(node.describeForError('The nearest ancestor providing an unbounded height constraint is'));
+        }
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ...information,
+          DiagnosticsProperty<BoxConstraints>('The constraints that applied to the $runtimeType were', constraints, style: DiagnosticsTreeStyle.errorProperty),
+          DiagnosticsProperty<Size>('The exact size it was given was', _size, style: DiagnosticsTreeStyle.errorProperty),
+          ErrorHint('See https://flutter.dev/docs/development/ui/layout/box-constraints for more information.'),
+        ]);
+     }
+      // verify that the size is within the constraints
+      if (!constraints.isSatisfiedBy(_size!)) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('$runtimeType does not meet its constraints.'),
+          DiagnosticsProperty<BoxConstraints>('Constraints', constraints, style: DiagnosticsTreeStyle.errorProperty),
+          DiagnosticsProperty<Size>('Size', _size, style: DiagnosticsTreeStyle.errorProperty),
+          ErrorHint(
+            'If you are not writing your own RenderBox subclass, then this is not '
+            'your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=2_bug.md'
+          ),
+        ]);
+      }
+      if (debugCheckIntrinsicSizes) {
+        // verify that the intrinsics are sane
+        assert(!RenderObject.debugCheckingIntrinsics);
+        RenderObject.debugCheckingIntrinsics = true;
+        final List<DiagnosticsNode> failures = <DiagnosticsNode>[];
+
+        double testIntrinsic(double function(double extent), String name, double constraint) {
+          final double result = function(constraint);
+          if (result < 0) {
+            failures.add(ErrorDescription(' * $name($constraint) returned a negative value: $result'));
+          }
+          if (!result.isFinite) {
+            failures.add(ErrorDescription(' * $name($constraint) returned a non-finite value: $result'));
+          }
+          return result;
+        }
+
+        void testIntrinsicsForValues(double getMin(double extent), double getMax(double extent), String name, double constraint) {
+          final double min = testIntrinsic(getMin, 'getMinIntrinsic$name', constraint);
+          final double max = testIntrinsic(getMax, 'getMaxIntrinsic$name', constraint);
+          if (min > max) {
+            failures.add(ErrorDescription(' * getMinIntrinsic$name($constraint) returned a larger value ($min) than getMaxIntrinsic$name($constraint) ($max)'));
+          }
+        }
+
+        testIntrinsicsForValues(getMinIntrinsicWidth, getMaxIntrinsicWidth, 'Width', double.infinity);
+        testIntrinsicsForValues(getMinIntrinsicHeight, getMaxIntrinsicHeight, 'Height', double.infinity);
+        if (constraints.hasBoundedWidth)
+          testIntrinsicsForValues(getMinIntrinsicWidth, getMaxIntrinsicWidth, 'Width', constraints.maxHeight);
+        if (constraints.hasBoundedHeight)
+          testIntrinsicsForValues(getMinIntrinsicHeight, getMaxIntrinsicHeight, 'Height', constraints.maxWidth);
+
+        // TODO(ianh): Test that values are internally consistent in more ways than the above.
+
+        RenderObject.debugCheckingIntrinsics = false;
+        if (failures.isNotEmpty) {
+          // TODO(jacobr): consider nesting the failures object so it is collapsible.
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('The intrinsic dimension methods of the $runtimeType class returned values that violate the intrinsic protocol contract.'),
+            ErrorDescription('The following ${failures.length > 1 ? "failures" : "failure"} was detected:'), // should this be tagged as an error or not?
+            ...failures,
+            ErrorHint(
+              'If you are not writing your own RenderBox subclass, then this is not\n'
+              'your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=2_bug.md'
+            ),
+          ]);
+        }
+
+        // Checking that getDryLayout computes the same size.
+        _dryLayoutCalculationValid = true;
+        RenderObject.debugCheckingIntrinsics = true;
+        late Size dryLayoutSize;
+        try {
+          dryLayoutSize = getDryLayout(constraints);
+        } finally {
+          RenderObject.debugCheckingIntrinsics = false;
+        }
+        if (_dryLayoutCalculationValid && dryLayoutSize != size) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('The size given to the ${objectRuntimeType(this, 'RenderBox')} class differs from the size computed by computeDryLayout.'),
+            ErrorDescription(
+              'The size computed in ${sizedByParent ? 'performResize' : 'performLayout'} '
+              'is $size, which is different from $dryLayoutSize, which was computed by computeDryLayout.'
+            ),
+            ErrorDescription(
+              'The constraints used were $constraints.',
+            ),
+            ErrorHint(
+              'If you are not writing your own RenderBox subclass, then this is not\n'
+              'your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=2_bug.md'
+            ),
+          ]);
+        }
+      }
+      return true;
+    }());
+  }
+
+  @override
+  void markNeedsLayout() {
+    if ((_cachedBaselines != null && _cachedBaselines!.isNotEmpty) ||
+        (_cachedIntrinsicDimensions != null && _cachedIntrinsicDimensions!.isNotEmpty) ||
+        (_cachedDryLayoutSizes != null && _cachedDryLayoutSizes!.isNotEmpty)) {
+      // If we have cached data, then someone must have used our data.
+      // Since the parent will shortly be marked dirty, we can forget that they
+      // used the baseline and/or intrinsic dimensions. If they use them again,
+      // then we'll fill the cache again, and if we get dirty again, we'll
+      // notify them again.
+      _cachedBaselines?.clear();
+      _cachedIntrinsicDimensions?.clear();
+      _cachedDryLayoutSizes?.clear();
+      if (parent is RenderObject) {
+        markParentNeedsLayout();
+        return;
+      }
+    }
+    super.markNeedsLayout();
+  }
+
+  /// {@macro flutter.rendering.RenderObject.performResize}
+  ///
+  /// By default this method calls [getDryLayout] with the current
+  /// [constraints]. Instead of overriding this method, consider overriding
+  /// [computeDryLayout] (the backend implementation of [getDryLayout]).
+  @override
+  void performResize() {
+    // default behavior for subclasses that have sizedByParent = true
+    size = computeDryLayout(constraints);
+    assert(size.isFinite);
+  }
+
+  @override
+  void performLayout() {
+    assert(() {
+      if (!sizedByParent) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('$runtimeType did not implement performLayout().'),
+          ErrorHint(
+            'RenderBox subclasses need to either override performLayout() to '
+            'set a size and lay out any children, or, set sizedByParent to true '
+            'so that performResize() sizes the render object.'
+          ),
+        ]);
+      }
+      return true;
+    }());
+  }
+
+  /// Determines the set of render objects located at the given position.
+  ///
+  /// Returns true, and adds any render objects that contain the point to the
+  /// given hit test result, if this render object or one of its descendants
+  /// absorbs the hit (preventing objects below this one from being hit).
+  /// Returns false if the hit can continue to other objects below this one.
+  ///
+  /// The caller is responsible for transforming [position] from global
+  /// coordinates to its location relative to the origin of this [RenderBox].
+  /// This [RenderBox] is responsible for checking whether the given position is
+  /// within its bounds.
+  ///
+  /// If transforming is necessary, [BoxHitTestResult.addWithPaintTransform],
+  /// [BoxHitTestResult.addWithPaintOffset], or
+  /// [BoxHitTestResult.addWithRawTransform] need to be invoked by the caller
+  /// to record the required transform operations in the [HitTestResult]. These
+  /// methods will also help with applying the transform to `position`.
+  ///
+  /// Hit testing requires layout to be up-to-date but does not require painting
+  /// to be up-to-date. That means a render object can rely upon [performLayout]
+  /// having been called in [hitTest] but cannot rely upon [paint] having been
+  /// called. For example, a render object might be a child of a [RenderOpacity]
+  /// object, which calls [hitTest] on its children when its opacity is zero
+  /// even through it does not [paint] its children.
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    assert(() {
+      if (!hasSize) {
+        if (debugNeedsLayout) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('Cannot hit test a render box that has never been laid out.'),
+            describeForError('The hitTest() method was called on this RenderBox'),
+            ErrorDescription(
+              "Unfortunately, this object's geometry is not known at this time, "
+              'probably because it has never been laid out. '
+              'This means it cannot be accurately hit-tested.'
+            ),
+            ErrorHint(
+              'If you are trying '
+              'to perform a hit test during the layout phase itself, make sure '
+              "you only hit test nodes that have completed layout (e.g. the node's "
+              'children, after their layout() method has been called).'
+            ),
+          ]);
+        }
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('Cannot hit test a render box with no size.'),
+          describeForError('The hitTest() method was called on this RenderBox'),
+          ErrorDescription(
+            'Although this node is not marked as needing layout, '
+            'its size is not set.'
+          ),
+          ErrorHint(
+            'A RenderBox object must have an '
+            'explicit size before it can be hit-tested. Make sure '
+            'that the RenderBox in question sets its size during layout.'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    if (_size!.contains(position)) {
+      if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
+        result.add(BoxHitTestEntry(this, position));
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /// Override this method if this render object can be hit even if its
+  /// children were not hit.
+  ///
+  /// Returns true if the specified `position` should be considered a hit
+  /// on this render object.
+  ///
+  /// The caller is responsible for transforming [position] from global
+  /// coordinates to its location relative to the origin of this [RenderBox].
+  /// This [RenderBox] is responsible for checking whether the given position is
+  /// within its bounds.
+  ///
+  /// Used by [hitTest]. If you override [hitTest] and do not call this
+  /// function, then you don't need to implement this function.
+  @protected
+  bool hitTestSelf(Offset position) => false;
+
+  /// Override this method to check whether any children are located at the
+  /// given position.
+  ///
+  /// Subclasses should return true if at least one child reported a hit at the
+  /// specified position.
+  ///
+  /// Typically children should be hit-tested in reverse paint order so that
+  /// hit tests at locations where children overlap hit the child that is
+  /// visually "on top" (i.e., paints later).
+  ///
+  /// The caller is responsible for transforming [position] from global
+  /// coordinates to its location relative to the origin of this [RenderBox].
+  /// Likewise, this [RenderBox] is responsible for transforming the position
+  /// that it passes to its children when it calls [hitTest] on each child.
+  ///
+  /// If transforming is necessary, [BoxHitTestResult.addWithPaintTransform],
+  /// [BoxHitTestResult.addWithPaintOffset], or
+  /// [BoxHitTestResult.addWithRawTransform] need to be invoked by subclasses to
+  /// record the required transform operations in the [BoxHitTestResult]. These
+  /// methods will also help with applying the transform to `position`.
+  ///
+  /// Used by [hitTest]. If you override [hitTest] and do not call this
+  /// function, then you don't need to implement this function.
+  @protected
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) => false;
+
+  /// Multiply the transform from the parent's coordinate system to this box's
+  /// coordinate system into the given transform.
+  ///
+  /// This function is used to convert coordinate systems between boxes.
+  /// Subclasses that apply transforms during painting should override this
+  /// function to factor those transforms into the calculation.
+  ///
+  /// The [RenderBox] implementation takes care of adjusting the matrix for the
+  /// position of the given child as determined during layout and stored on the
+  /// child's [parentData] in the [BoxParentData.offset] field.
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) {
+    assert(child != null);
+    assert(child.parent == this);
+    assert(() {
+      if (child.parentData is! BoxParentData) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('$runtimeType does not implement applyPaintTransform.'),
+          describeForError('The following $runtimeType object'),
+          child.describeForError('...did not use a BoxParentData class for the parentData field of the following child'),
+          ErrorDescription('The $runtimeType class inherits from RenderBox.'),
+          ErrorHint(
+            'The default applyPaintTransform implementation provided by RenderBox assumes that the '
+            'children all use BoxParentData objects for their parentData field. '
+            'Since $runtimeType does not in fact use that ParentData class for its children, it must '
+            'provide an implementation of applyPaintTransform that supports the specific ParentData '
+            'subclass used by its children (which apparently is ${child.parentData.runtimeType}).'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    final BoxParentData childParentData = child.parentData! as BoxParentData;
+    final Offset offset = childParentData.offset;
+    transform.translate(offset.dx, offset.dy);
+  }
+
+  /// Convert the given point from the global coordinate system in logical pixels
+  /// to the local coordinate system for this box.
+  ///
+  /// This method will un-project the point from the screen onto the widget,
+  /// which makes it different from [MatrixUtils.transformPoint].
+  ///
+  /// If the transform from global coordinates to local coordinates is
+  /// degenerate, this function returns [Offset.zero].
+  ///
+  /// If `ancestor` is non-null, this function converts the given point from the
+  /// coordinate system of `ancestor` (which must be an ancestor of this render
+  /// object) instead of from the global coordinate system.
+  ///
+  /// This method is implemented in terms of [getTransformTo].
+  Offset globalToLocal(Offset point, { RenderObject? ancestor }) {
+    // We want to find point (p) that corresponds to a given point on the
+    // screen (s), but that also physically resides on the local render plane,
+    // so that it is useful for visually accurate gesture processing in the
+    // local space. For that, we can't simply transform 2D screen point to
+    // the 3D local space since the screen space lacks the depth component |z|,
+    // and so there are many 3D points that correspond to the screen point.
+    // We must first unproject the screen point onto the render plane to find
+    // the true 3D point that corresponds to the screen point.
+    // We do orthogonal unprojection after undoing perspective, in local space.
+    // The render plane is specified by renderBox offset (o) and Z axis (n).
+    // Unprojection is done by finding the intersection of the view vector (d)
+    // with the local X-Y plane: (o-s).dot(n) == (p-s).dot(n), (p-s) == |z|*d.
+    final Matrix4 transform = getTransformTo(ancestor);
+    final double det = transform.invert();
+    if (det == 0.0)
+      return Offset.zero;
+    final Vector3 n = Vector3(0.0, 0.0, 1.0);
+    final Vector3 i = transform.perspectiveTransform(Vector3(0.0, 0.0, 0.0));
+    final Vector3 d = transform.perspectiveTransform(Vector3(0.0, 0.0, 1.0)) - i;
+    final Vector3 s = transform.perspectiveTransform(Vector3(point.dx, point.dy, 0.0));
+    final Vector3 p = s - d * (n.dot(s) / n.dot(d));
+    return Offset(p.x, p.y);
+  }
+
+  /// Convert the given point from the local coordinate system for this box to
+  /// the global coordinate system in logical pixels.
+  ///
+  /// If `ancestor` is non-null, this function converts the given point to the
+  /// coordinate system of `ancestor` (which must be an ancestor of this render
+  /// object) instead of to the global coordinate system.
+  ///
+  /// This method is implemented in terms of [getTransformTo]. If the transform
+  /// matrix puts the given `point` on the line at infinity (for instance, when
+  /// the transform matrix is the zero matrix), this method returns (NaN, NaN).
+  Offset localToGlobal(Offset point, { RenderObject? ancestor }) {
+    return MatrixUtils.transformPoint(getTransformTo(ancestor), point);
+  }
+
+  /// Returns a rectangle that contains all the pixels painted by this box.
+  ///
+  /// The paint bounds can be larger or smaller than [size], which is the amount
+  /// of space this box takes up during layout. For example, if this box casts a
+  /// shadow, that shadow might extend beyond the space allocated to this box
+  /// during layout.
+  ///
+  /// The paint bounds are used to size the buffers into which this box paints.
+  /// If the box attempts to paints outside its paint bounds, there might not be
+  /// enough memory allocated to represent the box's visual appearance, which
+  /// can lead to undefined behavior.
+  ///
+  /// The returned paint bounds are in the local coordinate system of this box.
+  @override
+  Rect get paintBounds => Offset.zero & size;
+
+  /// Override this method to handle pointer events that hit this render object.
+  ///
+  /// For [RenderBox] objects, the `entry` argument is a [BoxHitTestEntry]. From this
+  /// object you can determine the [PointerDownEvent]'s position in local coordinates.
+  /// (This is useful because [PointerEvent.position] is in global coordinates.)
+  ///
+  /// If you override this, consider calling [debugHandleEvent] as follows, so
+  /// that you can support [debugPaintPointersEnabled]:
+  ///
+  /// ```dart
+  /// @override
+  /// void handleEvent(PointerEvent event, HitTestEntry entry) {
+  ///   assert(debugHandleEvent(event, entry));
+  ///   // ... handle the event ...
+  /// }
+  /// ```
+  @override
+  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
+    super.handleEvent(event, entry);
+  }
+
+  int _debugActivePointers = 0;
+
+  /// Implements the [debugPaintPointersEnabled] debugging feature.
+  ///
+  /// [RenderBox] subclasses that implement [handleEvent] should call
+  /// [debugHandleEvent] from their [handleEvent] method, as follows:
+  ///
+  /// ```dart
+  /// @override
+  /// void handleEvent(PointerEvent event, HitTestEntry entry) {
+  ///   assert(debugHandleEvent(event, entry));
+  ///   // ... handle the event ...
+  /// }
+  /// ```
+  ///
+  /// If you call this for a [PointerDownEvent], make sure you also call it for
+  /// the corresponding [PointerUpEvent] or [PointerCancelEvent].
+  bool debugHandleEvent(PointerEvent event, HitTestEntry entry) {
+    assert(() {
+      if (debugPaintPointersEnabled) {
+        if (event is PointerDownEvent) {
+          _debugActivePointers += 1;
+        } else if (event is PointerUpEvent || event is PointerCancelEvent) {
+          _debugActivePointers -= 1;
+        }
+        markNeedsPaint();
+      }
+      return true;
+    }());
+    return true;
+  }
+
+  @override
+  void debugPaint(PaintingContext context, Offset offset) {
+    assert(() {
+      if (debugPaintSizeEnabled)
+        debugPaintSize(context, offset);
+      if (debugPaintBaselinesEnabled)
+        debugPaintBaselines(context, offset);
+      if (debugPaintPointersEnabled)
+        debugPaintPointers(context, offset);
+      return true;
+    }());
+  }
+
+  /// In debug mode, paints a border around this render box.
+  ///
+  /// Called for every [RenderBox] when [debugPaintSizeEnabled] is true.
+  @protected
+  void debugPaintSize(PaintingContext context, Offset offset) {
+    assert(() {
+      final Paint paint = Paint()
+       ..style = PaintingStyle.stroke
+       ..strokeWidth = 1.0
+       ..color = const Color(0xFF00FFFF);
+      context.canvas.drawRect((offset & size).deflate(0.5), paint);
+      return true;
+    }());
+  }
+
+  /// In debug mode, paints a line for each baseline.
+  ///
+  /// Called for every [RenderBox] when [debugPaintBaselinesEnabled] is true.
+  @protected
+  void debugPaintBaselines(PaintingContext context, Offset offset) {
+    assert(() {
+      final Paint paint = Paint()
+       ..style = PaintingStyle.stroke
+       ..strokeWidth = 0.25;
+      Path path;
+      // ideographic baseline
+      final double? baselineI = getDistanceToBaseline(TextBaseline.ideographic, onlyReal: true);
+      if (baselineI != null) {
+        paint.color = const Color(0xFFFFD000);
+        path = Path();
+        path.moveTo(offset.dx, offset.dy + baselineI);
+        path.lineTo(offset.dx + size.width, offset.dy + baselineI);
+        context.canvas.drawPath(path, paint);
+      }
+      // alphabetic baseline
+      final double? baselineA = getDistanceToBaseline(TextBaseline.alphabetic, onlyReal: true);
+      if (baselineA != null) {
+        paint.color = const Color(0xFF00FF00);
+        path = Path();
+        path.moveTo(offset.dx, offset.dy + baselineA);
+        path.lineTo(offset.dx + size.width, offset.dy + baselineA);
+        context.canvas.drawPath(path, paint);
+      }
+      return true;
+    }());
+  }
+
+  /// In debug mode, paints a rectangle if this render box has counted more
+  /// pointer downs than pointer up events.
+  ///
+  /// Called for every [RenderBox] when [debugPaintPointersEnabled] is true.
+  ///
+  /// By default, events are not counted. For details on how to ensure that
+  /// events are counted for your class, see [debugHandleEvent].
+  @protected
+  void debugPaintPointers(PaintingContext context, Offset offset) {
+    assert(() {
+      if (_debugActivePointers > 0) {
+        final Paint paint = Paint()
+         ..color = Color(0x00BBBB | ((0x04000000 * depth) & 0xFF000000));
+        context.canvas.drawRect(offset & size, paint);
+      }
+      return true;
+    }());
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Size>('size', _size, missingIfNull: true));
+  }
+}
+
+/// A mixin that provides useful default behaviors for boxes with children
+/// managed by the [ContainerRenderObjectMixin] mixin.
+///
+/// By convention, this class doesn't override any members of the superclass.
+/// Instead, it provides helpful functions that subclasses can call as
+/// appropriate.
+mixin RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, ParentDataType extends ContainerBoxParentData<ChildType>> implements ContainerRenderObjectMixin<ChildType, ParentDataType> {
+  /// Returns the baseline of the first child with a baseline.
+  ///
+  /// Useful when the children are displayed vertically in the same order they
+  /// appear in the child list.
+  double? defaultComputeDistanceToFirstActualBaseline(TextBaseline baseline) {
+    assert(!debugNeedsLayout);
+    ChildType? child = firstChild;
+    while (child != null) {
+      final ParentDataType? childParentData = child.parentData as ParentDataType?;
+      final double? result = child.getDistanceToActualBaseline(baseline);
+      if (result != null)
+        return result + childParentData!.offset.dy;
+      child = childParentData!.nextSibling;
+    }
+    return null;
+  }
+
+  /// Returns the minimum baseline value among every child.
+  ///
+  /// Useful when the vertical position of the children isn't determined by the
+  /// order in the child list.
+  double? defaultComputeDistanceToHighestActualBaseline(TextBaseline baseline) {
+    assert(!debugNeedsLayout);
+    double? result;
+    ChildType? child = firstChild;
+    while (child != null) {
+      final ParentDataType childParentData = child.parentData! as ParentDataType;
+      double? candidate = child.getDistanceToActualBaseline(baseline);
+      if (candidate != null) {
+        candidate += childParentData.offset.dy;
+        if (result != null)
+          result = math.min(result, candidate);
+        else
+          result = candidate;
+      }
+      child = childParentData.nextSibling;
+    }
+    return result;
+  }
+
+  /// Performs a hit test on each child by walking the child list backwards.
+  ///
+  /// Stops walking once after the first child reports that it contains the
+  /// given point. Returns whether any children contain the given point.
+  ///
+  /// See also:
+  ///
+  ///  * [defaultPaint], which paints the children appropriate for this
+  ///    hit-testing strategy.
+  bool defaultHitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    // The x, y parameters have the top left of the node's box as the origin.
+    ChildType? child = lastChild;
+    while (child != null) {
+      final ParentDataType childParentData = child.parentData! as ParentDataType;
+      final bool isHit = result.addWithPaintOffset(
+        offset: childParentData.offset,
+        position: position,
+        hitTest: (BoxHitTestResult result, Offset? transformed) {
+          assert(transformed == position - childParentData.offset);
+          return child!.hitTest(result, position: transformed!);
+        },
+      );
+      if (isHit)
+        return true;
+      child = childParentData.previousSibling;
+    }
+    return false;
+  }
+
+  /// Paints each child by walking the child list forwards.
+  ///
+  /// See also:
+  ///
+  ///  * [defaultHitTestChildren], which implements hit-testing of the children
+  ///    in a manner appropriate for this painting strategy.
+  void defaultPaint(PaintingContext context, Offset offset) {
+    ChildType? child = firstChild;
+    while (child != null) {
+      final ParentDataType childParentData = child.parentData! as ParentDataType;
+      context.paintChild(child, childParentData.offset + offset);
+      child = childParentData.nextSibling;
+    }
+  }
+
+  /// Returns a list containing the children of this render object.
+  ///
+  /// This function is useful when you need random-access to the children of
+  /// this render object. If you're accessing the children in order, consider
+  /// walking the child list directly.
+  List<ChildType> getChildrenAsList() {
+    final List<ChildType> result = <ChildType>[];
+    RenderBox? child = firstChild;
+    while (child != null) {
+      final ParentDataType childParentData = child.parentData! as ParentDataType;
+      result.add(child as ChildType);
+      child = childParentData.nextSibling;
+    }
+    return result;
+  }
+}
diff --git a/lib/src/rendering/custom_layout.dart b/lib/src/rendering/custom_layout.dart
new file mode 100644
index 0000000..88c64d9
--- /dev/null
+++ b/lib/src/rendering/custom_layout.dart
@@ -0,0 +1,419 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'box.dart';
+import 'object.dart';
+
+// For SingleChildLayoutDelegate and RenderCustomSingleChildLayoutBox, see shifted_box.dart
+
+/// [ParentData] used by [RenderCustomMultiChildLayoutBox].
+class MultiChildLayoutParentData extends ContainerBoxParentData<RenderBox> {
+  /// An object representing the identity of this child.
+  Object? id;
+
+  @override
+  String toString() => '${super.toString()}; id=$id';
+}
+
+/// A delegate that controls the layout of multiple children.
+///
+/// Used with [CustomMultiChildLayout] (in the widgets library) and
+/// [RenderCustomMultiChildLayoutBox] (in the rendering library).
+///
+/// Delegates must be idempotent. Specifically, if two delegates are equal, then
+/// they must produce the same layout. To change the layout, replace the
+/// delegate with a different instance whose [shouldRelayout] returns true when
+/// given the previous instance.
+///
+/// Override [getSize] to control the overall size of the layout. The size of
+/// the layout cannot depend on layout properties of the children. This was
+/// a design decision to simplify the delegate implementations: This way,
+/// the delegate implementations do not have to also handle various intrinsic
+/// sizing functions if the parent's size depended on the children.
+/// If you want to build a custom layout where you define the size of that widget
+/// based on its children, then you will have to create a custom render object.
+/// See [MultiChildRenderObjectWidget] with [ContainerRenderObjectMixin] and
+/// [RenderBoxContainerDefaultsMixin] to get started or [RenderStack] for an
+/// example implementation.
+///
+/// Override [performLayout] to size and position the children. An
+/// implementation of [performLayout] must call [layoutChild] exactly once for
+/// each child, but it may call [layoutChild] on children in an arbitrary order.
+/// Typically a delegate will use the size returned from [layoutChild] on one
+/// child to determine the constraints for [performLayout] on another child or
+/// to determine the offset for [positionChild] for that child or another child.
+///
+/// Override [shouldRelayout] to determine when the layout of the children needs
+/// to be recomputed when the delegate changes.
+///
+/// The most efficient way to trigger a relayout is to supply a `relayout`
+/// argument to the constructor of the [MultiChildLayoutDelegate]. The custom
+/// layout will listen to this value and relayout whenever the Listenable
+/// notifies its listeners, such as when an [Animation] ticks. This allows
+/// the custom layout to avoid the build phase of the pipeline.
+///
+/// Each child must be wrapped in a [LayoutId] widget to assign the id that
+/// identifies it to the delegate. The [LayoutId.id] needs to be unique among
+/// the children that the [CustomMultiChildLayout] manages.
+///
+/// {@tool snippet}
+///
+/// Below is an example implementation of [performLayout] that causes one widget
+/// (the follower) to be the same size as another (the leader):
+///
+/// ```dart
+/// // Define your own slot numbers, depending upon the id assigned by LayoutId.
+/// // Typical usage is to define an enum like the one below, and use those
+/// // values as the ids.
+/// enum _Slot {
+///   leader,
+///   follower,
+/// }
+///
+/// class FollowTheLeader extends MultiChildLayoutDelegate {
+///   @override
+///   void performLayout(Size size) {
+///     Size leaderSize = Size.zero;
+///
+///     if (hasChild(_Slot.leader)) {
+///       leaderSize = layoutChild(_Slot.leader, BoxConstraints.loose(size));
+///       positionChild(_Slot.leader, Offset.zero);
+///     }
+///
+///     if (hasChild(_Slot.follower)) {
+///       layoutChild(_Slot.follower, BoxConstraints.tight(leaderSize));
+///       positionChild(_Slot.follower, Offset(size.width - leaderSize.width,
+///           size.height - leaderSize.height));
+///     }
+///   }
+///
+///   @override
+///   bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => false;
+/// }
+/// ```
+/// {@end-tool}
+///
+/// The delegate gives the leader widget loose constraints, which means the
+/// child determines what size to be (subject to fitting within the given size).
+/// The delegate then remembers the size of that child and places it in the
+/// upper left corner.
+///
+/// The delegate then gives the follower widget tight constraints, forcing it to
+/// match the size of the leader widget. The delegate then places the follower
+/// widget in the bottom right corner.
+///
+/// The leader and follower widget will paint in the order they appear in the
+/// child list, regardless of the order in which [layoutChild] is called on
+/// them.
+///
+/// See also:
+///
+///  * [CustomMultiChildLayout], the widget that uses this delegate.
+///  * [RenderCustomMultiChildLayoutBox], render object that uses this
+///    delegate.
+abstract class MultiChildLayoutDelegate {
+  /// Creates a layout delegate.
+  ///
+  /// The layout will update whenever [relayout] notifies its listeners.
+  MultiChildLayoutDelegate({ Listenable? relayout }) : _relayout = relayout;
+
+  final Listenable? _relayout;
+
+  Map<Object, RenderBox>? _idToChild;
+  Set<RenderBox>? _debugChildrenNeedingLayout;
+
+  /// True if a non-null LayoutChild was provided for the specified id.
+  ///
+  /// Call this from the [performLayout] or [getSize] methods to
+  /// determine which children are available, if the child list might
+  /// vary.
+  bool hasChild(Object childId) => _idToChild![childId] != null;
+
+  /// Ask the child to update its layout within the limits specified by
+  /// the constraints parameter. The child's size is returned.
+  ///
+  /// Call this from your [performLayout] function to lay out each
+  /// child. Every child must be laid out using this function exactly
+  /// once each time the [performLayout] function is called.
+  Size layoutChild(Object childId, BoxConstraints constraints) {
+    final RenderBox? child = _idToChild![childId];
+    assert(() {
+      if (child == null) {
+        throw FlutterError(
+          'The $this custom multichild layout delegate tried to lay out a non-existent child.\n'
+          'There is no child with the id "$childId".'
+        );
+      }
+      if (!_debugChildrenNeedingLayout!.remove(child)) {
+        throw FlutterError(
+          'The $this custom multichild layout delegate tried to lay out the child with id "$childId" more than once.\n'
+          'Each child must be laid out exactly once.'
+        );
+      }
+      try {
+        assert(constraints.debugAssertIsValid(isAppliedConstraint: true));
+      } on AssertionError catch (exception) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('The $this custom multichild layout delegate provided invalid box constraints for the child with id "$childId".'),
+          DiagnosticsProperty<AssertionError>('Exception', exception, showName: false),
+          ErrorDescription(
+            'The minimum width and height must be greater than or equal to zero.\n'
+            'The maximum width must be greater than or equal to the minimum width.\n'
+            'The maximum height must be greater than or equal to the minimum height.'
+          )
+        ]);
+      }
+      return true;
+    }());
+    child!.layout(constraints, parentUsesSize: true);
+    return child.size;
+  }
+
+  /// Specify the child's origin relative to this origin.
+  ///
+  /// Call this from your [performLayout] function to position each
+  /// child. If you do not call this for a child, its position will
+  /// remain unchanged. Children initially have their position set to
+  /// (0,0), i.e. the top left of the [RenderCustomMultiChildLayoutBox].
+  void positionChild(Object childId, Offset offset) {
+    final RenderBox? child = _idToChild![childId];
+    assert(() {
+      if (child == null) {
+        throw FlutterError(
+          'The $this custom multichild layout delegate tried to position out a non-existent child:\n'
+          'There is no child with the id "$childId".'
+        );
+      }
+      // `offset` has a non-nullable return type, but might be null when
+      // running with weak checking, so we need to null check it anyway (and
+      // ignore the warning that the null-handling logic is dead code).
+      if (offset == null) { // ignore: dead_code
+        throw FlutterError(
+          'The $this custom multichild layout delegate provided a null position for the child with id "$childId".'
+        );
+      }
+      return true;
+    }());
+    final MultiChildLayoutParentData childParentData = child!.parentData! as MultiChildLayoutParentData;
+    childParentData.offset = offset;
+  }
+
+  DiagnosticsNode _debugDescribeChild(RenderBox child) {
+    final MultiChildLayoutParentData childParentData = child.parentData! as MultiChildLayoutParentData;
+    return DiagnosticsProperty<RenderBox>('${childParentData.id}', child);
+  }
+
+  void _callPerformLayout(Size size, RenderBox? firstChild) {
+    // A particular layout delegate could be called reentrantly, e.g. if it used
+    // by both a parent and a child. So, we must restore the _idToChild map when
+    // we return.
+    final Map<Object, RenderBox>? previousIdToChild = _idToChild;
+
+    Set<RenderBox>? debugPreviousChildrenNeedingLayout;
+    assert(() {
+      debugPreviousChildrenNeedingLayout = _debugChildrenNeedingLayout;
+      _debugChildrenNeedingLayout = <RenderBox>{};
+      return true;
+    }());
+
+    try {
+      _idToChild = <Object, RenderBox>{};
+      RenderBox? child = firstChild;
+      while (child != null) {
+        final MultiChildLayoutParentData childParentData = child.parentData! as MultiChildLayoutParentData;
+        assert(() {
+          if (childParentData.id == null) {
+            throw FlutterError.fromParts(<DiagnosticsNode>[
+              ErrorSummary('Every child of a RenderCustomMultiChildLayoutBox must have an ID in its parent data.'),
+              child!.describeForError('The following child has no ID'),
+            ]);
+          }
+          return true;
+        }());
+        _idToChild![childParentData.id!] = child;
+        assert(() {
+          _debugChildrenNeedingLayout!.add(child!);
+          return true;
+        }());
+        child = childParentData.nextSibling;
+      }
+      performLayout(size);
+      assert(() {
+        if (_debugChildrenNeedingLayout!.isNotEmpty) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('Each child must be laid out exactly once.'),
+            DiagnosticsBlock(
+              name:
+                'The $this custom multichild layout delegate forgot '
+                'to lay out the following '
+                '${_debugChildrenNeedingLayout!.length > 1 ? 'children' : 'child'}',
+              properties: _debugChildrenNeedingLayout!.map<DiagnosticsNode>(_debugDescribeChild).toList(),
+              style: DiagnosticsTreeStyle.whitespace,
+            ),
+          ]);
+        }
+        return true;
+      }());
+    } finally {
+      _idToChild = previousIdToChild;
+      assert(() {
+        _debugChildrenNeedingLayout = debugPreviousChildrenNeedingLayout;
+        return true;
+      }());
+    }
+  }
+
+  /// Override this method to return the size of this object given the
+  /// incoming constraints.
+  ///
+  /// The size cannot reflect the sizes of the children. If this layout has a
+  /// fixed width or height the returned size can reflect that; the size will be
+  /// constrained to the given constraints.
+  ///
+  /// By default, attempts to size the box to the biggest size
+  /// possible given the constraints.
+  Size getSize(BoxConstraints constraints) => constraints.biggest;
+
+  /// Override this method to lay out and position all children given this
+  /// widget's size.
+  ///
+  /// This method must call [layoutChild] for each child. It should also specify
+  /// the final position of each child with [positionChild].
+  void performLayout(Size size);
+
+  /// Override this method to return true when the children need to be
+  /// laid out.
+  ///
+  /// This should compare the fields of the current delegate and the given
+  /// `oldDelegate` and return true if the fields are such that the layout would
+  /// be different.
+  bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate);
+
+  /// Override this method to include additional information in the
+  /// debugging data printed by [debugDumpRenderTree] and friends.
+  ///
+  /// By default, returns the [runtimeType] of the class.
+  @override
+  String toString() => objectRuntimeType(this, 'MultiChildLayoutDelegate');
+}
+
+/// Defers the layout of multiple children to a delegate.
+///
+/// The delegate can determine the layout constraints for each child and can
+/// decide where to position each child. The delegate can also determine the
+/// size of the parent, but the size of the parent cannot depend on the sizes of
+/// the children.
+class RenderCustomMultiChildLayoutBox extends RenderBox
+  with ContainerRenderObjectMixin<RenderBox, MultiChildLayoutParentData>,
+       RenderBoxContainerDefaultsMixin<RenderBox, MultiChildLayoutParentData> {
+  /// Creates a render object that customizes the layout of multiple children.
+  ///
+  /// The [delegate] argument must not be null.
+  RenderCustomMultiChildLayoutBox({
+    List<RenderBox>? children,
+    required MultiChildLayoutDelegate delegate,
+  }) : assert(delegate != null),
+       _delegate = delegate {
+    addAll(children);
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! MultiChildLayoutParentData)
+      child.parentData = MultiChildLayoutParentData();
+  }
+
+  /// The delegate that controls the layout of the children.
+  MultiChildLayoutDelegate get delegate => _delegate;
+  MultiChildLayoutDelegate _delegate;
+  set delegate(MultiChildLayoutDelegate newDelegate) {
+    assert(newDelegate != null);
+    if (_delegate == newDelegate)
+      return;
+    final MultiChildLayoutDelegate oldDelegate = _delegate;
+    if (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRelayout(oldDelegate))
+      markNeedsLayout();
+    _delegate = newDelegate;
+    if (attached) {
+      oldDelegate._relayout?.removeListener(markNeedsLayout);
+      newDelegate._relayout?.addListener(markNeedsLayout);
+    }
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    _delegate._relayout?.addListener(markNeedsLayout);
+  }
+
+  @override
+  void detach() {
+    _delegate._relayout?.removeListener(markNeedsLayout);
+    super.detach();
+  }
+
+  Size _getSize(BoxConstraints constraints) {
+    assert(constraints.debugAssertIsValid());
+    return constraints.constrain(_delegate.getSize(constraints));
+  }
+
+  // TODO(ianh): It's a bit dubious to be using the getSize function from the delegate to
+  // figure out the intrinsic dimensions. We really should either not support intrinsics,
+  // or we should expose intrinsic delegate callbacks and throw if they're not implemented.
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
+    if (width.isFinite)
+      return width;
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
+    if (width.isFinite)
+      return width;
+    return 0.0;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
+    if (height.isFinite)
+      return height;
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
+    if (height.isFinite)
+      return height;
+    return 0.0;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _getSize(constraints);
+  }
+
+  @override
+  void performLayout() {
+    size = _getSize(constraints);
+    delegate._callPerformLayout(size, firstChild);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    defaultPaint(context, offset);
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    return defaultHitTestChildren(result, position: position);
+  }
+}
diff --git a/lib/src/rendering/custom_paint.dart b/lib/src/rendering/custom_paint.dart
new file mode 100644
index 0000000..22d124d
--- /dev/null
+++ b/lib/src/rendering/custom_paint.dart
@@ -0,0 +1,986 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/semantics.dart';
+
+import 'package:vector_math/vector_math_64.dart';
+
+import 'box.dart';
+import 'object.dart';
+import 'proxy_box.dart';
+
+/// Signature of the function returned by [CustomPainter.semanticsBuilder].
+///
+/// Builds semantics information describing the picture drawn by a
+/// [CustomPainter]. Each [CustomPainterSemantics] in the returned list is
+/// converted into a [SemanticsNode] by copying its properties.
+///
+/// The returned list must not be mutated after this function completes. To
+/// change the semantic information, the function must return a new list
+/// instead.
+typedef SemanticsBuilderCallback = List<CustomPainterSemantics> Function(Size size);
+
+/// The interface used by [CustomPaint] (in the widgets library) and
+/// [RenderCustomPaint] (in the rendering library).
+///
+/// To implement a custom painter, either subclass or implement this interface
+/// to define your custom paint delegate. [CustomPaint] subclasses must
+/// implement the [paint] and [shouldRepaint] methods, and may optionally also
+/// implement the [hitTest] and [shouldRebuildSemantics] methods, and the
+/// [semanticsBuilder] getter.
+///
+/// The [paint] method is called whenever the custom object needs to be repainted.
+///
+/// The [shouldRepaint] method is called when a new instance of the class
+/// is provided, to check if the new instance actually represents different
+/// information.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=vvI_NUXK00s}
+///
+/// The most efficient way to trigger a repaint is to either:
+///
+/// * Extend this class and supply a `repaint` argument to the constructor of
+///   the [CustomPainter], where that object notifies its listeners when it is
+///   time to repaint.
+/// * Extend [Listenable] (e.g. via [ChangeNotifier]) and implement
+///   [CustomPainter], so that the object itself provides the notifications
+///   directly.
+///
+/// In either case, the [CustomPaint] widget or [RenderCustomPaint]
+/// render object will listen to the [Listenable] and repaint whenever the
+/// animation ticks, avoiding both the build and layout phases of the pipeline.
+///
+/// The [hitTest] method is called when the user interacts with the underlying
+/// render object, to determine if the user hit the object or missed it.
+///
+/// The [semanticsBuilder] is called whenever the custom object needs to rebuild
+/// its semantics information.
+///
+/// The [shouldRebuildSemantics] method is called when a new instance of the
+/// class is provided, to check if the new instance contains different
+/// information that affects the semantics tree.
+///
+/// {@tool snippet}
+///
+/// This sample extends the same code shown for [RadialGradient] to create a
+/// custom painter that paints a sky.
+///
+/// ```dart
+/// class Sky extends CustomPainter {
+///   @override
+///   void paint(Canvas canvas, Size size) {
+///     var rect = Offset.zero & size;
+///     var gradient = RadialGradient(
+///       center: const Alignment(0.7, -0.6),
+///       radius: 0.2,
+///       colors: [const Color(0xFFFFFF00), const Color(0xFF0099FF)],
+///       stops: [0.4, 1.0],
+///     );
+///     canvas.drawRect(
+///       rect,
+///       Paint()..shader = gradient.createShader(rect),
+///     );
+///   }
+///
+///   @override
+///   SemanticsBuilderCallback get semanticsBuilder {
+///     return (Size size) {
+///       // Annotate a rectangle containing the picture of the sun
+///       // with the label "Sun". When text to speech feature is enabled on the
+///       // device, a user will be able to locate the sun on this picture by
+///       // touch.
+///       var rect = Offset.zero & size;
+///       var width = size.shortestSide * 0.4;
+///       rect = const Alignment(0.8, -0.9).inscribe(Size(width, width), rect);
+///       return [
+///         CustomPainterSemantics(
+///           rect: rect,
+///           properties: SemanticsProperties(
+///             label: 'Sun',
+///             textDirection: TextDirection.ltr,
+///           ),
+///         ),
+///       ];
+///     };
+///   }
+///
+///   // Since this Sky painter has no fields, it always paints
+///   // the same thing and semantics information is the same.
+///   // Therefore we return false here. If we had fields (set
+///   // from the constructor) then we would return true if any
+///   // of them differed from the same fields on the oldDelegate.
+///   @override
+///   bool shouldRepaint(Sky oldDelegate) => false;
+///   @override
+///   bool shouldRebuildSemantics(Sky oldDelegate) => false;
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Canvas], the class that a custom painter uses to paint.
+///  * [CustomPaint], the widget that uses [CustomPainter], and whose sample
+///    code shows how to use the above `Sky` class.
+///  * [RadialGradient], whose sample code section shows a different take
+///    on the sample code above.
+abstract class CustomPainter extends Listenable {
+  /// Creates a custom painter.
+  ///
+  /// The painter will repaint whenever `repaint` notifies its listeners.
+  const CustomPainter({ Listenable? repaint }) : _repaint = repaint;
+
+  final Listenable? _repaint;
+
+  /// Register a closure to be notified when it is time to repaint.
+  ///
+  /// The [CustomPainter] implementation merely forwards to the same method on
+  /// the [Listenable] provided to the constructor in the `repaint` argument, if
+  /// it was not null.
+  @override
+  void addListener(VoidCallback listener) => _repaint?.addListener(listener);
+
+  /// Remove a previously registered closure from the list of closures that the
+  /// object notifies when it is time to repaint.
+  ///
+  /// The [CustomPainter] implementation merely forwards to the same method on
+  /// the [Listenable] provided to the constructor in the `repaint` argument, if
+  /// it was not null.
+  @override
+  void removeListener(VoidCallback listener) => _repaint?.removeListener(listener);
+
+  /// Called whenever the object needs to paint. The given [Canvas] has its
+  /// coordinate space configured such that the origin is at the top left of the
+  /// box. The area of the box is the size of the [size] argument.
+  ///
+  /// Paint operations should remain inside the given area. Graphical
+  /// operations outside the bounds may be silently ignored, clipped, or not
+  /// clipped. It may sometimes be difficult to guarantee that a certain
+  /// operation is inside the bounds (e.g., drawing a rectangle whose size is
+  /// determined by user inputs). In that case, consider calling
+  /// [Canvas.clipRect] at the beginning of [paint] so everything that follows
+  /// will be guaranteed to only draw within the clipped area.
+  ///
+  /// Implementations should be wary of correctly pairing any calls to
+  /// [Canvas.save]/[Canvas.saveLayer] and [Canvas.restore], otherwise all
+  /// subsequent painting on this canvas may be affected, with potentially
+  /// hilarious but confusing results.
+  ///
+  /// To paint text on a [Canvas], use a [TextPainter].
+  ///
+  /// To paint an image on a [Canvas]:
+  ///
+  /// 1. Obtain an [ImageStream], for example by calling [ImageProvider.resolve]
+  ///    on an [AssetImage] or [NetworkImage] object.
+  ///
+  /// 2. Whenever the [ImageStream]'s underlying [ImageInfo] object changes
+  ///    (see [ImageStream.addListener]), create a new instance of your custom
+  ///    paint delegate, giving it the new [ImageInfo] object.
+  ///
+  /// 3. In your delegate's [paint] method, call the [Canvas.drawImage],
+  ///    [Canvas.drawImageRect], or [Canvas.drawImageNine] methods to paint the
+  ///    [ImageInfo.image] object, applying the [ImageInfo.scale] value to
+  ///    obtain the correct rendering size.
+  void paint(Canvas canvas, Size size);
+
+  /// Returns a function that builds semantic information for the picture drawn
+  /// by this painter.
+  ///
+  /// If the returned function is null, this painter will not contribute new
+  /// [SemanticsNode]s to the semantics tree and the [CustomPaint] corresponding
+  /// to this painter will not create a semantics boundary. However, if the
+  /// child of a [CustomPaint] is not null, the child may contribute
+  /// [SemanticsNode]s to the tree.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsConfiguration.isSemanticBoundary], which causes new
+  ///    [SemanticsNode]s to be added to the semantics tree.
+  ///  * [RenderCustomPaint], which uses this getter to build semantics.
+  SemanticsBuilderCallback? get semanticsBuilder => null;
+
+  /// Called whenever a new instance of the custom painter delegate class is
+  /// provided to the [RenderCustomPaint] object, or any time that a new
+  /// [CustomPaint] object is created with a new instance of the custom painter
+  /// delegate class (which amounts to the same thing, because the latter is
+  /// implemented in terms of the former).
+  ///
+  /// If the new instance would cause [semanticsBuilder] to create different
+  /// semantics information, then this method should return true, otherwise it
+  /// should return false.
+  ///
+  /// If the method returns false, then the [semanticsBuilder] call might be
+  /// optimized away.
+  ///
+  /// It's possible that the [semanticsBuilder] will get called even if
+  /// [shouldRebuildSemantics] would return false. For example, it is called
+  /// when the [CustomPaint] is rendered for the very first time, or when the
+  /// box changes its size.
+  ///
+  /// By default this method delegates to [shouldRepaint] under the assumption
+  /// that in most cases semantics change when something new is drawn.
+  bool shouldRebuildSemantics(covariant CustomPainter oldDelegate) => shouldRepaint(oldDelegate);
+
+  /// Called whenever a new instance of the custom painter delegate class is
+  /// provided to the [RenderCustomPaint] object, or any time that a new
+  /// [CustomPaint] object is created with a new instance of the custom painter
+  /// delegate class (which amounts to the same thing, because the latter is
+  /// implemented in terms of the former).
+  ///
+  /// If the new instance represents different information than the old
+  /// instance, then the method should return true, otherwise it should return
+  /// false.
+  ///
+  /// If the method returns false, then the [paint] call might be optimized
+  /// away.
+  ///
+  /// It's possible that the [paint] method will get called even if
+  /// [shouldRepaint] returns false (e.g. if an ancestor or descendant needed to
+  /// be repainted). It's also possible that the [paint] method will get called
+  /// without [shouldRepaint] being called at all (e.g. if the box changes
+  /// size).
+  ///
+  /// If a custom delegate has a particularly expensive paint function such that
+  /// repaints should be avoided as much as possible, a [RepaintBoundary] or
+  /// [RenderRepaintBoundary] (or other render object with
+  /// [RenderObject.isRepaintBoundary] set to true) might be helpful.
+  ///
+  /// The `oldDelegate` argument will never be null.
+  bool shouldRepaint(covariant CustomPainter oldDelegate);
+
+  /// Called whenever a hit test is being performed on an object that is using
+  /// this custom paint delegate.
+  ///
+  /// The given point is relative to the same coordinate space as the last
+  /// [paint] call.
+  ///
+  /// The default behavior is to consider all points to be hits for
+  /// background painters, and no points to be hits for foreground painters.
+  ///
+  /// Return true if the given position corresponds to a point on the drawn
+  /// image that should be considered a "hit", false if it corresponds to a
+  /// point that should be considered outside the painted image, and null to use
+  /// the default behavior.
+  bool? hitTest(Offset position) => null;
+
+  @override
+  String toString() => '${describeIdentity(this)}(${ _repaint?.toString() ?? "" })';
+}
+
+/// Contains properties describing information drawn in a rectangle contained by
+/// the [Canvas] used by a [CustomPaint].
+///
+/// This information is used, for example, by assistive technologies to improve
+/// the accessibility of applications.
+///
+/// Implement [CustomPainter.semanticsBuilder] to build the semantic
+/// description of the whole picture drawn by a [CustomPaint], rather that one
+/// particular rectangle.
+///
+/// See also:
+///
+///  * [SemanticsNode], which is created using the properties of this class.
+///  * [CustomPainter], which creates instances of this class.
+@immutable
+class CustomPainterSemantics {
+
+  /// Creates semantics information describing a rectangle on a canvas.
+  ///
+  /// Arguments `rect` and `properties` must not be null.
+  const CustomPainterSemantics({
+    this.key,
+    required this.rect,
+    required this.properties,
+    this.transform,
+    this.tags,
+  }) : assert(rect != null),
+       assert(properties != null);
+
+  /// Identifies this object in a list of siblings.
+  ///
+  /// [SemanticsNode] inherits this key, so that when the list of nodes is
+  /// updated, its nodes are updated from [CustomPainterSemantics] with matching
+  /// keys.
+  ///
+  /// If this is null, the update algorithm does not guarantee which
+  /// [SemanticsNode] will be updated using this instance.
+  ///
+  /// This value is assigned to [SemanticsNode.key] during update.
+  final Key? key;
+
+  /// The location and size of the box on the canvas where this piece of semantic
+  /// information applies.
+  ///
+  /// This value is assigned to [SemanticsNode.rect] during update.
+  final Rect rect;
+
+  /// The transform from the canvas' coordinate system to its parent's
+  /// coordinate system.
+  ///
+  /// This value is assigned to [SemanticsNode.transform] during update.
+  final Matrix4? transform;
+
+  /// Contains properties that are assigned to the [SemanticsNode] created or
+  /// updated from this object.
+  ///
+  /// See also:
+  ///
+  ///  * [Semantics], which is a widget that also uses [SemanticsProperties] to
+  ///    annotate.
+  final SemanticsProperties properties;
+
+  /// Tags used by the parent [SemanticsNode] to determine the layout of the
+  /// semantics tree.
+  ///
+  /// This value is assigned to [SemanticsNode.tags] during update.
+  final Set<SemanticsTag>? tags;
+}
+
+/// Provides a canvas on which to draw during the paint phase.
+///
+/// When asked to paint, [RenderCustomPaint] first asks its [painter] to paint
+/// on the current canvas, then it paints its child, and then, after painting
+/// its child, it asks its [foregroundPainter] to paint. The coordinate system of
+/// the canvas matches the coordinate system of the [CustomPaint] object. The
+/// painters are expected to paint within a rectangle starting at the origin and
+/// encompassing a region of the given size. (If the painters paint outside
+/// those bounds, there might be insufficient memory allocated to rasterize the
+/// painting commands and the resulting behavior is undefined.)
+///
+/// Painters are implemented by subclassing or implementing [CustomPainter].
+///
+/// Because custom paint calls its painters during paint, you cannot mark the
+/// tree as needing a new layout during the callback (the layout for this frame
+/// has already happened).
+///
+/// Custom painters normally size themselves to their child. If they do not have
+/// a child, they attempt to size themselves to the [preferredSize], which
+/// defaults to [Size.zero].
+///
+/// See also:
+///
+///  * [CustomPainter], the class that custom painter delegates should extend.
+///  * [Canvas], the API provided to custom painter delegates.
+class RenderCustomPaint extends RenderProxyBox {
+  /// Creates a render object that delegates its painting.
+  RenderCustomPaint({
+    CustomPainter? painter,
+    CustomPainter? foregroundPainter,
+    Size preferredSize = Size.zero,
+    this.isComplex = false,
+    this.willChange = false,
+    RenderBox? child,
+  }) : assert(preferredSize != null),
+       _painter = painter,
+       _foregroundPainter = foregroundPainter,
+       _preferredSize = preferredSize,
+       super(child);
+
+  /// The background custom paint delegate.
+  ///
+  /// This painter, if non-null, is called to paint behind the children.
+  CustomPainter? get painter => _painter;
+  CustomPainter? _painter;
+  /// Set a new background custom paint delegate.
+  ///
+  /// If the new delegate is the same as the previous one, this does nothing.
+  ///
+  /// If the new delegate is the same class as the previous one, then the new
+  /// delegate has its [CustomPainter.shouldRepaint] called; if the result is
+  /// true, then the delegate will be called.
+  ///
+  /// If the new delegate is a different class than the previous one, then the
+  /// delegate will be called.
+  ///
+  /// If the new value is null, then there is no background custom painter.
+  set painter(CustomPainter? value) {
+    if (_painter == value)
+      return;
+    final CustomPainter? oldPainter = _painter;
+    _painter = value;
+    _didUpdatePainter(_painter, oldPainter);
+  }
+
+  /// The foreground custom paint delegate.
+  ///
+  /// This painter, if non-null, is called to paint in front of the children.
+  CustomPainter? get foregroundPainter => _foregroundPainter;
+  CustomPainter? _foregroundPainter;
+  /// Set a new foreground custom paint delegate.
+  ///
+  /// If the new delegate is the same as the previous one, this does nothing.
+  ///
+  /// If the new delegate is the same class as the previous one, then the new
+  /// delegate has its [CustomPainter.shouldRepaint] called; if the result is
+  /// true, then the delegate will be called.
+  ///
+  /// If the new delegate is a different class than the previous one, then the
+  /// delegate will be called.
+  ///
+  /// If the new value is null, then there is no foreground custom painter.
+  set foregroundPainter(CustomPainter? value) {
+    if (_foregroundPainter == value)
+      return;
+    final CustomPainter? oldPainter = _foregroundPainter;
+    _foregroundPainter = value;
+    _didUpdatePainter(_foregroundPainter, oldPainter);
+  }
+
+  void _didUpdatePainter(CustomPainter? newPainter, CustomPainter? oldPainter) {
+    // Check if we need to repaint.
+    if (newPainter == null) {
+      assert(oldPainter != null); // We should be called only for changes.
+      markNeedsPaint();
+    } else if (oldPainter == null ||
+        newPainter.runtimeType != oldPainter.runtimeType ||
+        newPainter.shouldRepaint(oldPainter)) {
+      markNeedsPaint();
+    }
+    if (attached) {
+      oldPainter?.removeListener(markNeedsPaint);
+      newPainter?.addListener(markNeedsPaint);
+    }
+
+    // Check if we need to rebuild semantics.
+    if (newPainter == null) {
+      assert(oldPainter != null); // We should be called only for changes.
+      if (attached)
+        markNeedsSemanticsUpdate();
+    } else if (oldPainter == null ||
+        newPainter.runtimeType != oldPainter.runtimeType ||
+        newPainter.shouldRebuildSemantics(oldPainter)) {
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  /// The size that this [RenderCustomPaint] should aim for, given the layout
+  /// constraints, if there is no child.
+  ///
+  /// Defaults to [Size.zero].
+  ///
+  /// If there's a child, this is ignored, and the size of the child is used
+  /// instead.
+  Size get preferredSize => _preferredSize;
+  Size _preferredSize;
+  set preferredSize(Size value) {
+    assert(value != null);
+    if (preferredSize == value)
+      return;
+    _preferredSize = value;
+    markNeedsLayout();
+  }
+
+  /// Whether to hint that this layer's painting should be cached.
+  ///
+  /// The compositor contains a raster cache that holds bitmaps of layers in
+  /// order to avoid the cost of repeatedly rendering those layers on each
+  /// frame. If this flag is not set, then the compositor will apply its own
+  /// heuristics to decide whether the this layer is complex enough to benefit
+  /// from caching.
+  bool isComplex;
+
+  /// Whether the raster cache should be told that this painting is likely
+  /// to change in the next frame.
+  bool willChange;
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    _painter?.addListener(markNeedsPaint);
+    _foregroundPainter?.addListener(markNeedsPaint);
+  }
+
+  @override
+  void detach() {
+    _painter?.removeListener(markNeedsPaint);
+    _foregroundPainter?.removeListener(markNeedsPaint);
+    super.detach();
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    if (_foregroundPainter != null && (_foregroundPainter!.hitTest(position) ?? false))
+      return true;
+    return super.hitTestChildren(result, position: position);
+  }
+
+  @override
+  bool hitTestSelf(Offset position) {
+    return _painter != null && (_painter!.hitTest(position) ?? true);
+  }
+
+  @override
+  void performLayout() {
+    super.performLayout();
+    markNeedsSemanticsUpdate();
+  }
+
+  @override
+  Size computeSizeForNoChild(BoxConstraints constraints) {
+    return constraints.constrain(preferredSize);
+  }
+
+  void _paintWithPainter(Canvas canvas, Offset offset, CustomPainter painter) {
+    late int debugPreviousCanvasSaveCount;
+    canvas.save();
+    assert(() {
+      debugPreviousCanvasSaveCount = canvas.getSaveCount();
+      return true;
+    }());
+    if (offset != Offset.zero)
+      canvas.translate(offset.dx, offset.dy);
+    painter.paint(canvas, size);
+    assert(() {
+      // This isn't perfect. For example, we can't catch the case of
+      // someone first restoring, then setting a transform or whatnot,
+      // then saving.
+      // If this becomes a real problem, we could add logic to the
+      // Canvas class to lock the canvas at a particular save count
+      // such that restore() fails if it would take the lock count
+      // below that number.
+      final int debugNewCanvasSaveCount = canvas.getSaveCount();
+      if (debugNewCanvasSaveCount > debugPreviousCanvasSaveCount) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary(
+            'The $painter custom painter called canvas.save() or canvas.saveLayer() at least '
+            '${debugNewCanvasSaveCount - debugPreviousCanvasSaveCount} more '
+            'time${debugNewCanvasSaveCount - debugPreviousCanvasSaveCount == 1 ? '' : 's' } '
+            'than it called canvas.restore().'
+          ),
+          ErrorDescription('This leaves the canvas in an inconsistent state and will probably result in a broken display.'),
+          ErrorHint('You must pair each call to save()/saveLayer() with a later matching call to restore().')
+        ]);
+      }
+      if (debugNewCanvasSaveCount < debugPreviousCanvasSaveCount) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('The $painter custom painter called canvas.restore() '
+            '${debugPreviousCanvasSaveCount - debugNewCanvasSaveCount} more '
+            'time${debugPreviousCanvasSaveCount - debugNewCanvasSaveCount == 1 ? '' : 's' } '
+            'than it called canvas.save() or canvas.saveLayer().'
+          ),
+          ErrorDescription('This leaves the canvas in an inconsistent state and will result in a broken display.'),
+          ErrorHint('You should only call restore() if you first called save() or saveLayer().')
+        ]);
+      }
+      return debugNewCanvasSaveCount == debugPreviousCanvasSaveCount;
+    }());
+    canvas.restore();
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (_painter != null) {
+      _paintWithPainter(context.canvas, offset, _painter!);
+      _setRasterCacheHints(context);
+    }
+    super.paint(context, offset);
+    if (_foregroundPainter != null) {
+      _paintWithPainter(context.canvas, offset, _foregroundPainter!);
+      _setRasterCacheHints(context);
+    }
+  }
+
+  void _setRasterCacheHints(PaintingContext context) {
+    if (isComplex)
+      context.setIsComplexHint();
+    if (willChange)
+      context.setWillChangeHint();
+  }
+
+  /// Builds semantics for the picture drawn by [painter].
+  SemanticsBuilderCallback? _backgroundSemanticsBuilder;
+
+  /// Builds semantics for the picture drawn by [foregroundPainter].
+  SemanticsBuilderCallback? _foregroundSemanticsBuilder;
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    _backgroundSemanticsBuilder = painter?.semanticsBuilder;
+    _foregroundSemanticsBuilder = foregroundPainter?.semanticsBuilder;
+    config.isSemanticBoundary = _backgroundSemanticsBuilder != null || _foregroundSemanticsBuilder != null;
+  }
+
+  /// Describe the semantics of the picture painted by the [painter].
+  List<SemanticsNode>? _backgroundSemanticsNodes;
+
+  /// Describe the semantics of the picture painted by the [foregroundPainter].
+  List<SemanticsNode>? _foregroundSemanticsNodes;
+
+  @override
+  void assembleSemanticsNode(
+    SemanticsNode node,
+    SemanticsConfiguration config,
+    Iterable<SemanticsNode> children,
+  ) {
+    assert(() {
+      if (child == null && children.isNotEmpty) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary(
+            '$runtimeType does not have a child widget but received a non-empty list of child SemanticsNode:\n'
+            '${children.join('\n')}'
+          )
+        ]);
+      }
+      return true;
+    }());
+
+    final List<CustomPainterSemantics> backgroundSemantics = _backgroundSemanticsBuilder != null
+      ? _backgroundSemanticsBuilder!(size)
+      : const <CustomPainterSemantics>[];
+    _backgroundSemanticsNodes = _updateSemanticsChildren(_backgroundSemanticsNodes, backgroundSemantics);
+
+    final List<CustomPainterSemantics> foregroundSemantics = _foregroundSemanticsBuilder != null
+      ? _foregroundSemanticsBuilder!(size)
+      : const <CustomPainterSemantics>[];
+    _foregroundSemanticsNodes = _updateSemanticsChildren(_foregroundSemanticsNodes, foregroundSemantics);
+
+    final bool hasBackgroundSemantics = _backgroundSemanticsNodes != null && _backgroundSemanticsNodes!.isNotEmpty;
+    final bool hasForegroundSemantics = _foregroundSemanticsNodes != null && _foregroundSemanticsNodes!.isNotEmpty;
+    final List<SemanticsNode> finalChildren = <SemanticsNode>[
+      if (hasBackgroundSemantics) ..._backgroundSemanticsNodes!,
+      ...children,
+      if (hasForegroundSemantics) ..._foregroundSemanticsNodes!,
+    ];
+    super.assembleSemanticsNode(node, config, finalChildren);
+  }
+
+  @override
+  void clearSemantics() {
+    super.clearSemantics();
+    _backgroundSemanticsNodes = null;
+    _foregroundSemanticsNodes = null;
+  }
+
+  /// Updates the nodes of `oldSemantics` using data in `newChildSemantics`, and
+  /// returns a new list containing child nodes sorted according to the order
+  /// specified by `newChildSemantics`.
+  ///
+  /// [SemanticsNode]s that match [CustomPainterSemantics] by [Key]s preserve
+  /// their [SemanticsNode.key] field. If a node with the same key appears in
+  /// a different position in the list, it is moved to the new position, but the
+  /// same object is reused.
+  ///
+  /// [SemanticsNode]s whose `key` is null may be updated from
+  /// [CustomPainterSemantics] whose `key` is also null. However, the algorithm
+  /// does not guarantee it. If your semantics require that specific nodes are
+  /// updated from specific [CustomPainterSemantics], it is recommended to match
+  /// them by specifying non-null keys.
+  ///
+  /// The algorithm tries to be as close to [RenderObjectElement.updateChildren]
+  /// as possible, deviating only where the concepts diverge between widgets and
+  /// semantics. For example, a [SemanticsNode] can be updated from a
+  /// [CustomPainterSemantics] based on `Key` alone; their types are not
+  /// considered because there is only one type of [SemanticsNode]. There is no
+  /// concept of a "forgotten" node in semantics, deactivated nodes, or global
+  /// keys.
+  static List<SemanticsNode> _updateSemanticsChildren(
+    List<SemanticsNode>? oldSemantics,
+    List<CustomPainterSemantics>? newChildSemantics,
+  ) {
+    oldSemantics = oldSemantics ?? const <SemanticsNode>[];
+    newChildSemantics = newChildSemantics ?? const <CustomPainterSemantics>[];
+
+    assert(() {
+      final Map<Key, int> keys = HashMap<Key, int>();
+      final List<DiagnosticsNode> information = <DiagnosticsNode>[];
+      for (int i = 0; i < newChildSemantics!.length; i += 1) {
+        final CustomPainterSemantics child = newChildSemantics[i];
+        if (child.key != null) {
+          if (keys.containsKey(child.key)) {
+            information.add(ErrorDescription('- duplicate key ${child.key} found at position $i'));
+          }
+          keys[child.key!] = i;
+        }
+      }
+
+      if (information.isNotEmpty) {
+        information.insert(0, ErrorSummary('Failed to update the list of CustomPainterSemantics:'));
+        throw FlutterError.fromParts(information);
+      }
+
+      return true;
+    }());
+
+    int newChildrenTop = 0;
+    int oldChildrenTop = 0;
+    int newChildrenBottom = newChildSemantics.length - 1;
+    int oldChildrenBottom = oldSemantics.length - 1;
+
+    final List<SemanticsNode?> newChildren = List<SemanticsNode?>.filled(newChildSemantics.length, null, growable: false);
+
+    // Update the top of the list.
+    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
+      final SemanticsNode oldChild = oldSemantics[oldChildrenTop];
+      final CustomPainterSemantics newSemantics = newChildSemantics[newChildrenTop];
+      if (!_canUpdateSemanticsChild(oldChild, newSemantics))
+        break;
+      final SemanticsNode newChild = _updateSemanticsChild(oldChild, newSemantics);
+      newChildren[newChildrenTop] = newChild;
+      newChildrenTop += 1;
+      oldChildrenTop += 1;
+    }
+
+    // Scan the bottom of the list.
+    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
+      final SemanticsNode oldChild = oldSemantics[oldChildrenBottom];
+      final CustomPainterSemantics newChild = newChildSemantics[newChildrenBottom];
+      if (!_canUpdateSemanticsChild(oldChild, newChild))
+        break;
+      oldChildrenBottom -= 1;
+      newChildrenBottom -= 1;
+    }
+
+    // Scan the old children in the middle of the list.
+    final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
+    late final Map<Key, SemanticsNode> oldKeyedChildren;
+    if (haveOldChildren) {
+      oldKeyedChildren = <Key, SemanticsNode>{};
+      while (oldChildrenTop <= oldChildrenBottom) {
+        final SemanticsNode oldChild = oldSemantics[oldChildrenTop];
+        if (oldChild.key != null)
+          oldKeyedChildren[oldChild.key!] = oldChild;
+        oldChildrenTop += 1;
+      }
+    }
+
+    // Update the middle of the list.
+    while (newChildrenTop <= newChildrenBottom) {
+      SemanticsNode? oldChild;
+      final CustomPainterSemantics newSemantics = newChildSemantics[newChildrenTop];
+      if (haveOldChildren) {
+        final Key? key = newSemantics.key;
+        if (key != null) {
+          oldChild = oldKeyedChildren[key];
+          if (oldChild != null) {
+            if (_canUpdateSemanticsChild(oldChild, newSemantics)) {
+              // we found a match!
+              // remove it from oldKeyedChildren so we don't unsync it later
+              oldKeyedChildren.remove(key);
+            } else {
+              // Not a match, let's pretend we didn't see it for now.
+              oldChild = null;
+            }
+          }
+        }
+      }
+      assert(oldChild == null || _canUpdateSemanticsChild(oldChild, newSemantics));
+      final SemanticsNode newChild = _updateSemanticsChild(oldChild, newSemantics);
+      assert(oldChild == newChild || oldChild == null);
+      newChildren[newChildrenTop] = newChild;
+      newChildrenTop += 1;
+    }
+
+    // We've scanned the whole list.
+    assert(oldChildrenTop == oldChildrenBottom + 1);
+    assert(newChildrenTop == newChildrenBottom + 1);
+    assert(newChildSemantics.length - newChildrenTop == oldSemantics.length - oldChildrenTop);
+    newChildrenBottom = newChildSemantics.length - 1;
+    oldChildrenBottom = oldSemantics.length - 1;
+
+    // Update the bottom of the list.
+    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
+      final SemanticsNode oldChild = oldSemantics[oldChildrenTop];
+      final CustomPainterSemantics newSemantics = newChildSemantics[newChildrenTop];
+      assert(_canUpdateSemanticsChild(oldChild, newSemantics));
+      final SemanticsNode newChild = _updateSemanticsChild(oldChild, newSemantics);
+      assert(oldChild == newChild);
+      newChildren[newChildrenTop] = newChild;
+      newChildrenTop += 1;
+      oldChildrenTop += 1;
+    }
+
+    assert(() {
+      for (final SemanticsNode? node in newChildren) {
+        assert(node != null);
+      }
+      return true;
+    }());
+
+    return newChildren.cast<SemanticsNode>();
+  }
+
+  /// Whether `oldChild` can be updated with properties from `newSemantics`.
+  ///
+  /// If `oldChild` can be updated, it is updated using [_updateSemanticsChild].
+  /// Otherwise, the node is replaced by a new instance of [SemanticsNode].
+  static bool _canUpdateSemanticsChild(SemanticsNode oldChild, CustomPainterSemantics newSemantics) {
+    return oldChild.key == newSemantics.key;
+  }
+
+  /// Updates `oldChild` using the properties of `newSemantics`.
+  ///
+  /// This method requires that `_canUpdateSemanticsChild(oldChild, newSemantics)`
+  /// is true prior to calling it.
+  static SemanticsNode _updateSemanticsChild(SemanticsNode? oldChild, CustomPainterSemantics newSemantics) {
+    assert(oldChild == null || _canUpdateSemanticsChild(oldChild, newSemantics));
+
+    final SemanticsNode newChild = oldChild ?? SemanticsNode(
+      key: newSemantics.key,
+    );
+
+    final SemanticsProperties properties = newSemantics.properties;
+    final SemanticsConfiguration config = SemanticsConfiguration();
+    if (properties.sortKey != null) {
+      config.sortKey = properties.sortKey;
+    }
+    if (properties.checked != null) {
+      config.isChecked = properties.checked;
+    }
+    if (properties.selected != null) {
+      config.isSelected = properties.selected!;
+    }
+    if (properties.button != null) {
+      config.isButton = properties.button!;
+    }
+    if (properties.link != null) {
+      config.isLink = properties.link!;
+    }
+    if (properties.textField != null) {
+      config.isTextField = properties.textField!;
+    }
+    if (properties.slider != null) {
+      config.isSlider = properties.slider!;
+    }
+    if (properties.readOnly != null) {
+      config.isReadOnly = properties.readOnly!;
+    }
+    if (properties.focusable != null) {
+      config.isFocusable = properties.focusable!;
+    }
+    if (properties.focused != null) {
+      config.isFocused = properties.focused!;
+    }
+    if (properties.enabled != null) {
+      config.isEnabled = properties.enabled;
+    }
+    if (properties.inMutuallyExclusiveGroup != null) {
+      config.isInMutuallyExclusiveGroup = properties.inMutuallyExclusiveGroup!;
+    }
+    if (properties.obscured != null) {
+      config.isObscured = properties.obscured!;
+    }
+    if (properties.multiline != null) {
+      config.isMultiline = properties.multiline!;
+    }
+    if (properties.hidden != null) {
+      config.isHidden = properties.hidden!;
+    }
+    if (properties.header != null) {
+      config.isHeader = properties.header!;
+    }
+    if (properties.scopesRoute != null) {
+      config.scopesRoute = properties.scopesRoute!;
+    }
+    if (properties.namesRoute != null) {
+      config.namesRoute = properties.namesRoute!;
+    }
+    if (properties.liveRegion != null) {
+      config.liveRegion = properties.liveRegion!;
+    }
+    if (properties.maxValueLength != null) {
+      config.maxValueLength = properties.maxValueLength;
+    }
+    if (properties.currentValueLength != null) {
+      config.currentValueLength = properties.currentValueLength;
+    }
+    if (properties.toggled != null) {
+      config.isToggled = properties.toggled;
+    }
+    if (properties.image != null) {
+      config.isImage = properties.image!;
+    }
+    if (properties.label != null) {
+      config.label = properties.label!;
+    }
+    if (properties.value != null) {
+      config.value = properties.value!;
+    }
+    if (properties.increasedValue != null) {
+      config.increasedValue = properties.increasedValue!;
+    }
+    if (properties.decreasedValue != null) {
+      config.decreasedValue = properties.decreasedValue!;
+    }
+    if (properties.hint != null) {
+      config.hint = properties.hint!;
+    }
+    if (properties.textDirection != null) {
+      config.textDirection = properties.textDirection;
+    }
+    if (properties.onTap != null) {
+      config.onTap = properties.onTap;
+    }
+    if (properties.onLongPress != null) {
+      config.onLongPress = properties.onLongPress;
+    }
+    if (properties.onScrollLeft != null) {
+      config.onScrollLeft = properties.onScrollLeft;
+    }
+    if (properties.onScrollRight != null) {
+      config.onScrollRight = properties.onScrollRight;
+    }
+    if (properties.onScrollUp != null) {
+      config.onScrollUp = properties.onScrollUp;
+    }
+    if (properties.onScrollDown != null) {
+      config.onScrollDown = properties.onScrollDown;
+    }
+    if (properties.onIncrease != null) {
+      config.onIncrease = properties.onIncrease;
+    }
+    if (properties.onDecrease != null) {
+      config.onDecrease = properties.onDecrease;
+    }
+    if (properties.onCopy != null) {
+      config.onCopy = properties.onCopy;
+    }
+    if (properties.onCut != null) {
+      config.onCut = properties.onCut;
+    }
+    if (properties.onPaste != null) {
+      config.onPaste = properties.onPaste;
+    }
+    if (properties.onMoveCursorForwardByCharacter != null) {
+      config.onMoveCursorForwardByCharacter = properties.onMoveCursorForwardByCharacter;
+    }
+    if (properties.onMoveCursorBackwardByCharacter != null) {
+      config.onMoveCursorBackwardByCharacter = properties.onMoveCursorBackwardByCharacter;
+    }
+    if (properties.onMoveCursorForwardByWord != null) {
+      config.onMoveCursorForwardByWord = properties.onMoveCursorForwardByWord;
+    }
+    if (properties.onMoveCursorBackwardByWord != null) {
+      config.onMoveCursorBackwardByWord = properties.onMoveCursorBackwardByWord;
+    }
+    if (properties.onSetSelection != null) {
+      config.onSetSelection = properties.onSetSelection;
+    }
+    if (properties.onDidGainAccessibilityFocus != null) {
+      config.onDidGainAccessibilityFocus = properties.onDidGainAccessibilityFocus;
+    }
+    if (properties.onDidLoseAccessibilityFocus != null) {
+      config.onDidLoseAccessibilityFocus = properties.onDidLoseAccessibilityFocus;
+    }
+    if (properties.onDismiss != null) {
+      config.onDismiss = properties.onDismiss;
+    }
+
+    newChild.updateWith(
+      config: config,
+      // As of now CustomPainter does not support multiple tree levels.
+      childrenInInversePaintOrder: const <SemanticsNode>[],
+    );
+
+    newChild
+      ..rect = newSemantics.rect
+      ..transform = newSemantics.transform
+      ..tags = newSemantics.tags;
+
+    return newChild;
+  }
+}
diff --git a/lib/src/rendering/debug.dart b/lib/src/rendering/debug.dart
new file mode 100644
index 0000000..b6fda0b
--- /dev/null
+++ b/lib/src/rendering/debug.dart
@@ -0,0 +1,294 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart';
+
+import 'object.dart';
+
+export 'package:flute/foundation.dart' show debugPrint;
+
+// Any changes to this file should be reflected in the debugAssertAllRenderVarsUnset()
+// function below.
+
+const HSVColor _kDebugDefaultRepaintColor = HSVColor.fromAHSV(0.4, 60.0, 1.0, 1.0);
+
+/// Causes each RenderBox to paint a box around its bounds, and some extra
+/// boxes, such as [RenderPadding], to draw construction lines.
+///
+/// The edges of the boxes are painted as a one-pixel-thick `const Color(0xFF00FFFF)` outline.
+///
+/// Spacing is painted as a solid `const Color(0x90909090)` area.
+///
+/// Padding is filled in solid `const Color(0x900090FF)`, with the inner edge
+/// outlined in `const Color(0xFF0090FF)`, using [debugPaintPadding].
+bool debugPaintSizeEnabled = false;
+
+/// Causes each RenderBox to paint a line at each of its baselines.
+bool debugPaintBaselinesEnabled = false;
+
+/// Causes each Layer to paint a box around its bounds.
+bool debugPaintLayerBordersEnabled = false;
+
+/// Causes objects like [RenderPointerListener] to flash while they are being
+/// tapped. This can be useful to see how large the hit box is, e.g. when
+/// debugging buttons that are harder to hit than expected.
+///
+/// For details on how to support this in your [RenderBox] subclass, see
+/// [RenderBox.debugHandleEvent].
+bool debugPaintPointersEnabled = false;
+
+/// Overlay a rotating set of colors when repainting layers in checked mode.
+///
+/// See also:
+///
+///  * [RepaintBoundary], which can be used to contain repaints when unchanged
+///    areas are being excessively repainted.
+bool debugRepaintRainbowEnabled = false;
+
+/// Overlay a rotating set of colors when repainting text in checked mode.
+bool debugRepaintTextRainbowEnabled = false;
+
+/// Causes [PhysicalModelLayer]s to paint a red rectangle around themselves if
+/// they are overlapping and painted out of order with regard to their elevation.
+///
+/// Android and iOS will show the last painted layer on top, whereas Fuchsia
+/// will show the layer with the highest elevation on top.
+///
+/// For example, a rectangular elevation at 3.0 that is painted before an
+/// overlapping rectangular elevation at 2.0 would render this way on Android
+/// and iOS (with fake shadows):
+/// ```
+/// ┌───────────────────┐
+/// │                   │
+/// │      3.0          │
+/// │            ┌───────────────────┐
+/// │            │                   │
+/// └────────────│                   │
+///              │        2.0        │
+///              │                   │
+///              └───────────────────┘
+/// ```
+///
+/// But this way on Fuchsia (with real shadows):
+/// ```
+/// ┌───────────────────┐
+/// │                   │
+/// │      3.0          │
+/// │                   │────────────┐
+/// │                   │            │
+/// └───────────────────┘            │
+///              │         2.0       │
+///              │                   │
+///              └───────────────────┘
+/// ```
+///
+/// This check helps developers that want a consistent look and feel detect
+/// where this inconsistency would occur.
+///
+/// This check assumes that elevations on [PhysicalModelLayer] objects have
+/// been set to non-null values before the scene is built. If this assumption
+/// is violated, the check will throw exceptions. (The scene building would
+/// also fail in that case, however.)
+bool debugCheckElevationsEnabled = false;
+
+/// The current color to overlay when repainting a layer.
+///
+/// This is used by painting debug code that implements
+/// [debugRepaintRainbowEnabled] or [debugRepaintTextRainbowEnabled].
+///
+/// The value is incremented by [RenderView.compositeFrame] if either of those
+/// flags is enabled.
+HSVColor debugCurrentRepaintColor = _kDebugDefaultRepaintColor;
+
+/// Log the call stacks that mark render objects as needing layout.
+///
+/// For sanity, this only logs the stack traces of cases where an object is
+/// added to the list of nodes needing layout. This avoids printing multiple
+/// redundant stack traces as a single [RenderObject.markNeedsLayout] call walks
+/// up the tree.
+bool debugPrintMarkNeedsLayoutStacks = false;
+
+/// Log the call stacks that mark render objects as needing paint.
+bool debugPrintMarkNeedsPaintStacks = false;
+
+/// Log the dirty render objects that are laid out each frame.
+///
+/// Combined with [debugPrintBeginFrameBanner], this allows you to distinguish
+/// layouts triggered by the initial mounting of a render tree (e.g. in a call
+/// to [runApp]) from the regular layouts triggered by the pipeline.
+///
+/// Combined with [debugPrintMarkNeedsLayoutStacks], this lets you watch a
+/// render object's dirty/clean lifecycle.
+///
+/// See also:
+///
+///  * [debugProfileLayoutsEnabled], which does something similar for layout
+///    but using the timeline view.
+///  * [debugProfilePaintsEnabled], which does something similar for painting
+///    but using the timeline view.
+///  * [debugPrintRebuildDirtyWidgets], which does something similar for widgets
+///    being rebuilt.
+///  * The discussion at [RendererBinding.drawFrame].
+bool debugPrintLayouts = false;
+
+/// Check the intrinsic sizes of each [RenderBox] during layout.
+///
+/// By default this is turned off since these checks are expensive, but it is
+/// enabled by the test framework.
+bool debugCheckIntrinsicSizes = false;
+
+/// Adds [dart:developer.Timeline] events for every [RenderObject] layout.
+///
+/// For details on how to use [dart:developer.Timeline] events in the Dart
+/// Observatory to optimize your app, see:
+/// <https://fuchsia.googlesource.com/topaz/+/master/shell/docs/performance.md>
+///
+/// See also:
+///
+///  * [debugPrintLayouts], which does something similar for layout but using
+///    console output.
+///  * [debugProfileBuildsEnabled], which does something similar for widgets
+///    being rebuilt.
+///  * [debugProfilePaintsEnabled], which does something similar for painting.
+bool debugProfileLayoutsEnabled = false;
+
+/// Adds [dart:developer.Timeline] events for every [RenderObject] painted.
+///
+/// This is only enabled in debug builds. The timing information this exposes is
+/// not representative of actual paints. However, it can expose unexpected
+/// painting in the timeline.
+///
+/// For details on how to use [dart:developer.Timeline] events in the Dart
+/// Observatory to optimize your app, see:
+/// <https://fuchsia.googlesource.com/topaz/+/master/shell/docs/performance.md>
+///
+/// See also:
+///
+///  * [debugProfileBuildsEnabled], which does something similar for widgets
+///    being rebuilt, and [debugPrintRebuildDirtyWidgets], its console
+///    equivalent.
+///  * [debugProfileLayoutsEnabled], which does something similar for layout,
+///    and [debugPrintLayouts], its console equivalent.
+///  * The discussion at [RendererBinding.drawFrame].
+///  * [RepaintBoundary], which can be used to contain repaints when unchanged
+///    areas are being excessively repainted.
+bool debugProfilePaintsEnabled = false;
+
+/// Signature for [debugOnProfilePaint] implementations.
+typedef ProfilePaintCallback = void Function(RenderObject renderObject);
+
+/// Callback invoked for every [RenderObject] painted each frame.
+///
+/// This callback is only invoked in debug builds.
+///
+/// See also:
+///
+///  * [debugProfilePaintsEnabled], which does something similar but adds
+///    [dart:developer.Timeline] events instead of invoking a callback.
+///  * [debugOnRebuildDirtyWidget], which does something similar for widgets
+///    being built.
+///  * [WidgetInspectorService], which uses the [debugOnProfilePaint]
+///    callback to generate aggregate profile statistics describing what paints
+///    occurred when the `ext.flutter.inspector.trackRepaintWidgets` service
+///    extension is enabled.
+ProfilePaintCallback? debugOnProfilePaint;
+
+/// Setting to true will cause all clipping effects from the layer tree to be
+/// ignored.
+///
+/// Can be used to debug whether objects being clipped are painting excessively
+/// in clipped areas. Can also be used to check whether excessive use of
+/// clipping is affecting performance.
+///
+/// This will not reduce the number of [Layer] objects created; the compositing
+/// strategy is unaffected. It merely causes the clipping layers to be skipped
+/// when building the scene.
+bool debugDisableClipLayers = false;
+
+/// Setting to true will cause all physical modeling effects from the layer
+/// tree, such as shadows from elevations, to be ignored.
+///
+/// Can be used to check whether excessive use of physical models is affecting
+/// performance.
+///
+/// This will not reduce the number of [Layer] objects created; the compositing
+/// strategy is unaffected. It merely causes the physical shape layers to be
+/// skipped when building the scene.
+bool debugDisablePhysicalShapeLayers = false;
+
+/// Setting to true will cause all opacity effects from the layer tree to be
+/// ignored.
+///
+/// An optimization to not paint the child at all when opacity is 0 will still
+/// remain.
+///
+/// Can be used to check whether excessive use of opacity effects is affecting
+/// performance.
+///
+/// This will not reduce the number of [Layer] objects created; the compositing
+/// strategy is unaffected. It merely causes the opacity layers to be skipped
+/// when building the scene.
+bool debugDisableOpacityLayers = false;
+
+void _debugDrawDoubleRect(Canvas canvas, Rect outerRect, Rect innerRect, Color color) {
+  final Path path = Path()
+    ..fillType = PathFillType.evenOdd
+    ..addRect(outerRect)
+    ..addRect(innerRect);
+  final Paint paint = Paint()
+    ..color = color;
+  canvas.drawPath(path, paint);
+}
+
+/// Paint a diagram showing the given area as padding.
+///
+/// Called by [RenderPadding.debugPaintSize] when [debugPaintSizeEnabled] is
+/// true.
+void debugPaintPadding(Canvas canvas, Rect outerRect, Rect? innerRect, { double outlineWidth = 2.0 }) {
+  assert(() {
+    if (innerRect != null && !innerRect.isEmpty) {
+      _debugDrawDoubleRect(canvas, outerRect, innerRect, const Color(0x900090FF));
+      _debugDrawDoubleRect(canvas, innerRect.inflate(outlineWidth).intersect(outerRect), innerRect, const Color(0xFF0090FF));
+    } else {
+      final Paint paint = Paint()
+        ..color = const Color(0x90909090);
+      canvas.drawRect(outerRect, paint);
+    }
+    return true;
+  }());
+}
+
+/// Returns true if none of the rendering library debug variables have been changed.
+///
+/// This function is used by the test framework to ensure that debug variables
+/// haven't been inadvertently changed.
+///
+/// See [the rendering library](rendering/rendering-library.html) for a complete
+/// list.
+///
+/// The `debugCheckIntrinsicSizesOverride` argument can be provided to override
+/// the expected value for [debugCheckIntrinsicSizes]. (This exists because the
+/// test framework itself overrides this value in some cases.)
+bool debugAssertAllRenderVarsUnset(String reason, { bool debugCheckIntrinsicSizesOverride = false }) {
+  assert(() {
+    if (debugPaintSizeEnabled ||
+        debugPaintBaselinesEnabled ||
+        debugPaintLayerBordersEnabled ||
+        debugPaintPointersEnabled ||
+        debugRepaintRainbowEnabled ||
+        debugRepaintTextRainbowEnabled ||
+        debugCurrentRepaintColor != _kDebugDefaultRepaintColor ||
+        debugPrintMarkNeedsLayoutStacks ||
+        debugPrintMarkNeedsPaintStacks ||
+        debugPrintLayouts ||
+        debugCheckIntrinsicSizes != debugCheckIntrinsicSizesOverride ||
+        debugProfilePaintsEnabled ||
+        debugOnProfilePaint != null) {
+      throw FlutterError(reason);
+    }
+    return true;
+  }());
+  return true;
+}
diff --git a/lib/src/rendering/debug_overflow_indicator.dart b/lib/src/rendering/debug_overflow_indicator.dart
new file mode 100644
index 0000000..4531d09
--- /dev/null
+++ b/lib/src/rendering/debug_overflow_indicator.dart
@@ -0,0 +1,324 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' as ui;
+
+import 'package:flute/painting.dart';
+import 'package:flute/foundation.dart';
+
+import 'object.dart';
+import 'stack.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+
+// Describes which side the region data overflows on.
+enum _OverflowSide {
+  left,
+  top,
+  bottom,
+  right,
+}
+
+// Data used by the DebugOverflowIndicator to manage the regions and labels for
+// the indicators.
+class _OverflowRegionData {
+  const _OverflowRegionData({
+    required this.rect,
+    this.label = '',
+    this.labelOffset = Offset.zero,
+    this.rotation = 0.0,
+    required this.side,
+  });
+
+  final Rect rect;
+  final String label;
+  final Offset labelOffset;
+  final double rotation;
+  final _OverflowSide side;
+}
+
+/// An mixin indicator that is drawn when a [RenderObject] overflows its
+/// container.
+///
+/// This is used by some RenderObjects that are containers to show where, and by
+/// how much, their children overflow their containers. These indicators are
+/// typically only shown in a debug build (where the call to
+/// [paintOverflowIndicator] is surrounded by an assert).
+///
+/// This class will also print a debug message to the console when the container
+/// overflows. It will print on the first occurrence, and once after each time that
+/// [reassemble] is called.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// class MyRenderObject extends RenderAligningShiftedBox with DebugOverflowIndicatorMixin {
+///   MyRenderObject({
+///     AlignmentGeometry alignment,
+///     TextDirection textDirection,
+///     RenderBox child,
+///   }) : super.mixin(alignment, textDirection, child);
+///
+///   Rect _containerRect;
+///   Rect _childRect;
+///
+///   @override
+///   void performLayout() {
+///     // ...
+///     final BoxParentData childParentData = child.parentData;
+///     _containerRect = Offset.zero & size;
+///     _childRect = childParentData.offset & child.size;
+///   }
+///
+///   @override
+///   void paint(PaintingContext context, Offset offset) {
+///     // Do normal painting here...
+///     // ...
+///
+///     assert(() {
+///       paintOverflowIndicator(context, offset, _containerRect, _childRect);
+///       return true;
+///     }());
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [RenderUnconstrainedBox] and [RenderFlex] for examples of classes that use this indicator mixin.
+mixin DebugOverflowIndicatorMixin on RenderObject {
+  static const Color _black = Color(0xBF000000);
+  static const Color _yellow = Color(0xBFFFFF00);
+  // The fraction of the container that the indicator covers.
+  static const double _indicatorFraction = 0.1;
+  static const double _indicatorFontSizePixels = 7.5;
+  static const double _indicatorLabelPaddingPixels = 1.0;
+  static const TextStyle _indicatorTextStyle = TextStyle(
+    color: Color(0xFF900000),
+    fontSize: _indicatorFontSizePixels,
+    fontWeight: FontWeight.w800,
+  );
+  static final Paint _indicatorPaint = Paint()
+    ..shader = ui.Gradient.linear(
+      const Offset(0.0, 0.0),
+      const Offset(10.0, 10.0),
+      <Color>[_black, _yellow, _yellow, _black],
+      <double>[0.25, 0.25, 0.75, 0.75],
+      TileMode.repeated,
+    );
+  static final Paint _labelBackgroundPaint = Paint()..color = const Color(0xFFFFFFFF);
+
+  final List<TextPainter> _indicatorLabel = List<TextPainter>.filled(
+    _OverflowSide.values.length,
+    TextPainter(textDirection: TextDirection.ltr), // This label is in English.
+  );
+
+  // Set to true to trigger a debug message in the console upon
+  // the next paint call. Will be reset after each paint.
+  bool _overflowReportNeeded = true;
+
+  String _formatPixels(double value) {
+    assert(value > 0.0);
+    final String pixels;
+    if (value > 10.0) {
+      pixels = value.toStringAsFixed(0);
+    } else if (value > 1.0) {
+      pixels = value.toStringAsFixed(1);
+    } else {
+      pixels = value.toStringAsPrecision(3);
+    }
+    return pixels;
+  }
+
+  List<_OverflowRegionData> _calculateOverflowRegions(RelativeRect overflow, Rect containerRect) {
+    final List<_OverflowRegionData> regions = <_OverflowRegionData>[];
+    if (overflow.left > 0.0) {
+      final Rect markerRect = Rect.fromLTWH(
+        0.0,
+        0.0,
+        containerRect.width * _indicatorFraction,
+        containerRect.height,
+      );
+      regions.add(_OverflowRegionData(
+        rect: markerRect,
+        label: 'LEFT OVERFLOWED BY ${_formatPixels(overflow.left)} PIXELS',
+        labelOffset: markerRect.centerLeft +
+            const Offset(_indicatorFontSizePixels + _indicatorLabelPaddingPixels, 0.0),
+        rotation: math.pi / 2.0,
+        side: _OverflowSide.left,
+      ));
+    }
+    if (overflow.right > 0.0) {
+      final Rect markerRect = Rect.fromLTWH(
+        containerRect.width * (1.0 - _indicatorFraction),
+        0.0,
+        containerRect.width * _indicatorFraction,
+        containerRect.height,
+      );
+      regions.add(_OverflowRegionData(
+        rect: markerRect,
+        label: 'RIGHT OVERFLOWED BY ${_formatPixels(overflow.right)} PIXELS',
+        labelOffset: markerRect.centerRight -
+            const Offset(_indicatorFontSizePixels + _indicatorLabelPaddingPixels, 0.0),
+        rotation: -math.pi / 2.0,
+        side: _OverflowSide.right,
+      ));
+    }
+    if (overflow.top > 0.0) {
+      final Rect markerRect = Rect.fromLTWH(
+        0.0,
+        0.0,
+        containerRect.width,
+        containerRect.height * _indicatorFraction,
+      );
+      regions.add(_OverflowRegionData(
+        rect: markerRect,
+        label: 'TOP OVERFLOWED BY ${_formatPixels(overflow.top)} PIXELS',
+        labelOffset: markerRect.topCenter + const Offset(0.0, _indicatorLabelPaddingPixels),
+        rotation: 0.0,
+        side: _OverflowSide.top,
+      ));
+    }
+    if (overflow.bottom > 0.0) {
+      final Rect markerRect = Rect.fromLTWH(
+        0.0,
+        containerRect.height * (1.0 - _indicatorFraction),
+        containerRect.width,
+        containerRect.height * _indicatorFraction,
+      );
+      regions.add(_OverflowRegionData(
+        rect: markerRect,
+        label: 'BOTTOM OVERFLOWED BY ${_formatPixels(overflow.bottom)} PIXELS',
+        labelOffset: markerRect.bottomCenter -
+            const Offset(0.0, _indicatorFontSizePixels + _indicatorLabelPaddingPixels),
+        rotation: 0.0,
+        side: _OverflowSide.bottom,
+      ));
+    }
+    return regions;
+  }
+
+  void _reportOverflow(RelativeRect overflow, List<DiagnosticsNode>? overflowHints) {
+    overflowHints ??= <DiagnosticsNode>[];
+    if (overflowHints.isEmpty) {
+      overflowHints.add(ErrorDescription(
+        'The edge of the $runtimeType that is '
+        'overflowing has been marked in the rendering with a yellow and black '
+        'striped pattern. This is usually caused by the contents being too big '
+        'for the $runtimeType.'
+      ));
+      overflowHints.add(ErrorHint(
+        'This is considered an error condition because it indicates that there '
+        'is content that cannot be seen. If the content is legitimately bigger '
+        'than the available space, consider clipping it with a ClipRect widget '
+        'before putting it in the $runtimeType, or using a scrollable '
+        'container, like a ListView.'
+      ));
+    }
+
+    final List<String> overflows = <String>[
+      if (overflow.left > 0.0) '${_formatPixels(overflow.left)} pixels on the left',
+      if (overflow.top > 0.0) '${_formatPixels(overflow.top)} pixels on the top',
+      if (overflow.bottom > 0.0) '${_formatPixels(overflow.bottom)} pixels on the bottom',
+      if (overflow.right > 0.0) '${_formatPixels(overflow.right)} pixels on the right',
+    ];
+    String overflowText = '';
+    assert(overflows.isNotEmpty,
+        "Somehow $runtimeType didn't actually overflow like it thought it did.");
+    switch (overflows.length) {
+      case 1:
+        overflowText = overflows.first;
+        break;
+      case 2:
+        overflowText = '${overflows.first} and ${overflows.last}';
+        break;
+      default:
+        overflows[overflows.length - 1] = 'and ${overflows[overflows.length - 1]}';
+        overflowText = overflows.join(', ');
+    }
+    // TODO(jacobr): add the overflows in pixels as structured data so they can
+    // be visualized in debugging tools.
+    FlutterError.reportError(
+      FlutterErrorDetails(
+        exception: FlutterError('A $runtimeType overflowed by $overflowText.'),
+        library: 'rendering library',
+        context: ErrorDescription('during layout'),
+        informationCollector: () sync* {
+          if (debugCreator != null)
+            yield DiagnosticsDebugCreator(debugCreator!);
+          yield* overflowHints!;
+          yield describeForError('The specific $runtimeType in question is');
+          // TODO(jacobr): this line is ascii art that it would be nice to
+          // handle a little more generically in GUI debugging clients in the
+          // future.
+          yield DiagnosticsNode.message('◢◤' * (FlutterError.wrapWidth ~/ 2), allowWrap: false);
+        },
+      ),
+    );
+  }
+
+  /// To be called when the overflow indicators should be painted.
+  ///
+  /// Typically only called if there is an overflow, and only from within a
+  /// debug build.
+  ///
+  /// See example code in [DebugOverflowIndicatorMixin] documentation.
+  void paintOverflowIndicator(
+    PaintingContext context,
+    Offset offset,
+    Rect containerRect,
+    Rect childRect, {
+    List<DiagnosticsNode>? overflowHints,
+  }) {
+    final RelativeRect overflow = RelativeRect.fromRect(containerRect, childRect);
+
+    if (overflow.left <= 0.0 &&
+        overflow.right <= 0.0 &&
+        overflow.top <= 0.0 &&
+        overflow.bottom <= 0.0) {
+      return;
+    }
+
+    final List<_OverflowRegionData> overflowRegions = _calculateOverflowRegions(overflow, containerRect);
+    for (final _OverflowRegionData region in overflowRegions) {
+      context.canvas.drawRect(region.rect.shift(offset), _indicatorPaint);
+      final TextSpan? textSpan = _indicatorLabel[region.side.index].text as TextSpan?;
+      if (textSpan?.text != region.label) {
+        _indicatorLabel[region.side.index].text = TextSpan(
+          text: region.label,
+          style: _indicatorTextStyle,
+        );
+        _indicatorLabel[region.side.index].layout();
+      }
+
+      final Offset labelOffset = region.labelOffset + offset;
+      final Offset centerOffset = Offset(-_indicatorLabel[region.side.index].width / 2.0, 0.0);
+      final Rect textBackgroundRect = centerOffset & _indicatorLabel[region.side.index].size;
+      context.canvas.save();
+      context.canvas.translate(labelOffset.dx, labelOffset.dy);
+      context.canvas.rotate(region.rotation);
+      context.canvas.drawRect(textBackgroundRect, _labelBackgroundPaint);
+      _indicatorLabel[region.side.index].paint(context.canvas, centerOffset);
+      context.canvas.restore();
+    }
+
+    if (_overflowReportNeeded) {
+      _overflowReportNeeded = false;
+      _reportOverflow(overflow, overflowHints);
+    }
+  }
+
+  @override
+  void reassemble() {
+    super.reassemble();
+    // Users expect error messages to be shown again after hot reload.
+    assert(() {
+      _overflowReportNeeded = true;
+      return true;
+    }());
+  }
+}
diff --git a/lib/src/rendering/editable.dart b/lib/src/rendering/editable.dart
new file mode 100644
index 0000000..7253e6f
--- /dev/null
+++ b/lib/src/rendering/editable.dart
@@ -0,0 +1,2409 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' as ui show TextBox, lerpDouble, BoxHeightStyle, BoxWidthStyle;
+
+import 'package:characters/characters.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/semantics.dart';
+import 'package:flute/services.dart';
+
+import 'box.dart';
+import 'layer.dart';
+import 'object.dart';
+import 'viewport_offset.dart';
+
+const double _kCaretGap = 1.0; // pixels
+const double _kCaretHeightOffset = 2.0; // pixels
+
+// The additional size on the x and y axis with which to expand the prototype
+// cursor to render the floating cursor in pixels.
+const Offset _kFloatingCaretSizeIncrease = Offset(0.5, 1.0);
+
+// The corner radius of the floating cursor in pixels.
+const double _kFloatingCaretRadius = 1.0;
+
+/// Signature for the callback that reports when the user changes the selection
+/// (including the cursor location).
+///
+/// Used by [RenderEditable.onSelectionChanged].
+typedef SelectionChangedHandler = void Function(TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause);
+
+/// Indicates what triggered the change in selected text (including changes to
+/// the cursor location).
+enum SelectionChangedCause {
+  /// The user tapped on the text and that caused the selection (or the location
+  /// of the cursor) to change.
+  tap,
+
+  /// The user tapped twice in quick succession on the text and that caused
+  /// the selection (or the location of the cursor) to change.
+  doubleTap,
+
+  /// The user long-pressed the text and that caused the selection (or the
+  /// location of the cursor) to change.
+  longPress,
+
+  /// The user force-pressed the text and that caused the selection (or the
+  /// location of the cursor) to change.
+  forcePress,
+
+  /// The user used the keyboard to change the selection or the location of the
+  /// cursor.
+  ///
+  /// Keyboard-triggered selection changes may be caused by the IME as well as
+  /// by accessibility tools (e.g. TalkBack on Android).
+  keyboard,
+
+  /// The user used the mouse to change the selection by dragging over a piece
+  /// of text.
+  drag,
+}
+
+/// Signature for the callback that reports when the caret location changes.
+///
+/// Used by [RenderEditable.onCaretChanged].
+typedef CaretChangedHandler = void Function(Rect caretRect);
+
+/// Represents the coordinates of the point in a selection, and the text
+/// direction at that point, relative to top left of the [RenderEditable] that
+/// holds the selection.
+@immutable
+class TextSelectionPoint {
+  /// Creates a description of a point in a text selection.
+  ///
+  /// The [point] argument must not be null.
+  const TextSelectionPoint(this.point, this.direction)
+    : assert(point != null);
+
+  /// Coordinates of the lower left or lower right corner of the selection,
+  /// relative to the top left of the [RenderEditable] object.
+  final Offset point;
+
+  /// Direction of the text at this edge of the selection.
+  final TextDirection? direction;
+
+  @override
+  String toString() {
+    switch (direction) {
+      case TextDirection.ltr:
+        return '$point-ltr';
+      case TextDirection.rtl:
+        return '$point-rtl';
+      case null:
+        return '$point';
+    }
+  }
+}
+
+// Check if the given code unit is a white space or separator
+// character.
+//
+// Includes newline characters from ASCII and separators from the
+// [unicode separator category](https://www.compart.com/en/unicode/category/Zs)
+// TODO(gspencergoog): replace when we expose this ICU information.
+bool _isWhitespace(int codeUnit) {
+  switch (codeUnit) {
+    case 0x9: // horizontal tab
+    case 0xA: // line feed
+    case 0xB: // vertical tab
+    case 0xC: // form feed
+    case 0xD: // carriage return
+    case 0x1C: // file separator
+    case 0x1D: // group separator
+    case 0x1E: // record separator
+    case 0x1F: // unit separator
+    case 0x20: // space
+    case 0xA0: // no-break space
+    case 0x1680: // ogham space mark
+    case 0x2000: // en quad
+    case 0x2001: // em quad
+    case 0x2002: // en space
+    case 0x2003: // em space
+    case 0x2004: // three-per-em space
+    case 0x2005: // four-er-em space
+    case 0x2006: // six-per-em space
+    case 0x2007: // figure space
+    case 0x2008: // punctuation space
+    case 0x2009: // thin space
+    case 0x200A: // hair space
+    case 0x202F: // narrow no-break space
+    case 0x205F: // medium mathematical space
+    case 0x3000: // ideographic space
+      break;
+    default:
+      return false;
+  }
+  return true;
+}
+
+/// Displays some text in a scrollable container with a potentially blinking
+/// cursor and with gesture recognizers.
+///
+/// This is the renderer for an editable text field. It does not directly
+/// provide affordances for editing the text, but it does handle text selection
+/// and manipulation of the text cursor.
+///
+/// The [text] is displayed, scrolled by the given [offset], aligned according
+/// to [textAlign]. The [maxLines] property controls whether the text displays
+/// on one line or many. The [selection], if it is not collapsed, is painted in
+/// the [selectionColor]. If it _is_ collapsed, then it represents the cursor
+/// position. The cursor is shown while [showCursor] is true. It is painted in
+/// the [cursorColor].
+///
+/// If, when the render object paints, the caret is found to have changed
+/// location, [onCaretChanged] is called.
+///
+/// The user may interact with the render object by tapping or long-pressing.
+/// When the user does so, the selection is updated, and [onSelectionChanged] is
+/// called.
+///
+/// Keyboard handling, IME handling, scrolling, toggling the [showCursor] value
+/// to actually blink the cursor, and other features not mentioned above are the
+/// responsibility of higher layers and not handled by this object.
+class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
+  /// Creates a render object that implements the visual aspects of a text field.
+  ///
+  /// The [textAlign] argument must not be null. It defaults to [TextAlign.start].
+  ///
+  /// The [textDirection] argument must not be null.
+  ///
+  /// If [showCursor] is not specified, then it defaults to hiding the cursor.
+  ///
+  /// The [maxLines] property can be set to null to remove the restriction on
+  /// the number of lines. By default, it is 1, meaning this is a single-line
+  /// text field. If it is not null, it must be greater than zero.
+  ///
+  /// The [offset] is required and must not be null. You can use [new
+  /// ViewportOffset.zero] if you have no need for scrolling.
+  RenderEditable({
+    TextSpan? text,
+    required TextDirection textDirection,
+    TextAlign textAlign = TextAlign.start,
+    Color? cursorColor,
+    Color? backgroundCursorColor,
+    ValueNotifier<bool>? showCursor,
+    bool? hasFocus,
+    required LayerLink startHandleLayerLink,
+    required LayerLink endHandleLayerLink,
+    int? maxLines = 1,
+    int? minLines,
+    bool expands = false,
+    StrutStyle? strutStyle,
+    Color? selectionColor,
+    double textScaleFactor = 1.0,
+    TextSelection? selection,
+    required ViewportOffset offset,
+    this.onSelectionChanged,
+    this.onCaretChanged,
+    this.ignorePointer = false,
+    bool readOnly = false,
+    bool forceLine = true,
+    TextHeightBehavior? textHeightBehavior,
+    TextWidthBasis textWidthBasis = TextWidthBasis.parent,
+    String obscuringCharacter = '•',
+    bool obscureText = false,
+    Locale? locale,
+    double cursorWidth = 1.0,
+    double? cursorHeight,
+    Radius? cursorRadius,
+    bool paintCursorAboveText = false,
+    Offset? cursorOffset,
+    double devicePixelRatio = 1.0,
+    ui.BoxHeightStyle selectionHeightStyle = ui.BoxHeightStyle.tight,
+    ui.BoxWidthStyle selectionWidthStyle = ui.BoxWidthStyle.tight,
+    bool? enableInteractiveSelection,
+    EdgeInsets floatingCursorAddedMargin = const EdgeInsets.fromLTRB(4, 4, 4, 5),
+    TextRange? promptRectRange,
+    Color? promptRectColor,
+    Clip clipBehavior = Clip.hardEdge,
+    required this.textSelectionDelegate,
+  }) : assert(textAlign != null),
+       assert(textDirection != null, 'RenderEditable created without a textDirection.'),
+       assert(maxLines == null || maxLines > 0),
+       assert(minLines == null || minLines > 0),
+       assert(startHandleLayerLink != null),
+       assert(endHandleLayerLink != null),
+       assert(
+         (maxLines == null) || (minLines == null) || (maxLines >= minLines),
+         "minLines can't be greater than maxLines",
+       ),
+       assert(expands != null),
+       assert(
+         !expands || (maxLines == null && minLines == null),
+         'minLines and maxLines must be null when expands is true.',
+       ),
+       assert(textScaleFactor != null),
+       assert(offset != null),
+       assert(ignorePointer != null),
+       assert(textWidthBasis != null),
+       assert(paintCursorAboveText != null),
+       assert(obscuringCharacter != null && obscuringCharacter.characters.length == 1),
+       assert(obscureText != null),
+       assert(textSelectionDelegate != null),
+       assert(cursorWidth != null && cursorWidth >= 0.0),
+       assert(cursorHeight == null || cursorHeight >= 0.0),
+       assert(readOnly != null),
+       assert(forceLine != null),
+       assert(devicePixelRatio != null),
+       assert(selectionHeightStyle != null),
+       assert(selectionWidthStyle != null),
+       assert(clipBehavior != null),
+       _textPainter = TextPainter(
+         text: text,
+         textAlign: textAlign,
+         textDirection: textDirection,
+         textScaleFactor: textScaleFactor,
+         locale: locale,
+         strutStyle: strutStyle,
+         textHeightBehavior: textHeightBehavior,
+         textWidthBasis: textWidthBasis,
+       ),
+       _cursorColor = cursorColor,
+       _backgroundCursorColor = backgroundCursorColor,
+       _showCursor = showCursor ?? ValueNotifier<bool>(false),
+       _maxLines = maxLines,
+       _minLines = minLines,
+       _expands = expands,
+       _selectionColor = selectionColor,
+       _selection = selection,
+       _offset = offset,
+       _cursorWidth = cursorWidth,
+       _cursorHeight = cursorHeight,
+       _cursorRadius = cursorRadius,
+       _paintCursorOnTop = paintCursorAboveText,
+       _cursorOffset = cursorOffset,
+       _floatingCursorAddedMargin = floatingCursorAddedMargin,
+       _enableInteractiveSelection = enableInteractiveSelection,
+       _devicePixelRatio = devicePixelRatio,
+       _selectionHeightStyle = selectionHeightStyle,
+       _selectionWidthStyle = selectionWidthStyle,
+       _startHandleLayerLink = startHandleLayerLink,
+       _endHandleLayerLink = endHandleLayerLink,
+       _obscuringCharacter = obscuringCharacter,
+       _obscureText = obscureText,
+       _readOnly = readOnly,
+       _forceLine = forceLine,
+       _promptRectRange = promptRectRange,
+       _clipBehavior = clipBehavior {
+    assert(_showCursor != null);
+    assert(!_showCursor.value || cursorColor != null);
+    this.hasFocus = hasFocus ?? false;
+    if (promptRectColor != null)
+      _promptRectPaint.color = promptRectColor;
+  }
+
+  /// Called when the selection changes.
+  ///
+  /// If this is null, then selection changes will be ignored.
+  SelectionChangedHandler? onSelectionChanged;
+
+  double? _textLayoutLastMaxWidth;
+  double? _textLayoutLastMinWidth;
+
+  /// Called during the paint phase when the caret location changes.
+  CaretChangedHandler? onCaretChanged;
+
+  /// Whether the [handleEvent] will propagate pointer events to selection
+  /// handlers.
+  ///
+  /// If this property is true, the [handleEvent] assumes that this renderer
+  /// will be notified of input gestures via [handleTapDown], [handleTap],
+  /// [handleDoubleTap], and [handleLongPress].
+  ///
+  /// If there are any gesture recognizers in the text span, the [handleEvent]
+  /// will still propagate pointer events to those recognizers.
+  ///
+  /// The default value of this property is false.
+  bool ignorePointer;
+
+  /// {@macro flutter.dart:ui.textHeightBehavior}
+  TextHeightBehavior? get textHeightBehavior => _textPainter.textHeightBehavior;
+  set textHeightBehavior(TextHeightBehavior? value) {
+    if (_textPainter.textHeightBehavior == value)
+      return;
+    _textPainter.textHeightBehavior = value;
+    markNeedsTextLayout();
+  }
+
+  /// {@macro flutter.painting.textPainter.textWidthBasis}
+  TextWidthBasis get textWidthBasis => _textPainter.textWidthBasis;
+  set textWidthBasis(TextWidthBasis value) {
+    assert(value != null);
+    if (_textPainter.textWidthBasis == value)
+      return;
+    _textPainter.textWidthBasis = value;
+    markNeedsTextLayout();
+  }
+
+  /// The pixel ratio of the current device.
+  ///
+  /// Should be obtained by querying MediaQuery for the devicePixelRatio.
+  double get devicePixelRatio => _devicePixelRatio;
+  double _devicePixelRatio;
+  set devicePixelRatio(double value) {
+    if (devicePixelRatio == value)
+      return;
+    _devicePixelRatio = value;
+    markNeedsTextLayout();
+  }
+
+  /// Character used for obscuring text if [obscureText] is true.
+  ///
+  /// Cannot be null, and must have a length of exactly one.
+  String get obscuringCharacter => _obscuringCharacter;
+  String _obscuringCharacter;
+  set obscuringCharacter(String value) {
+    if (_obscuringCharacter == value) {
+      return;
+    }
+    assert(value != null && value.characters.length == 1);
+    _obscuringCharacter = value;
+    markNeedsLayout();
+  }
+
+  /// Whether to hide the text being edited (e.g., for passwords).
+  bool get obscureText => _obscureText;
+  bool _obscureText;
+  set obscureText(bool value) {
+    if (_obscureText == value)
+      return;
+    _obscureText = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// The object that controls the text selection, used by this render object
+  /// for implementing cut, copy, and paste keyboard shortcuts.
+  ///
+  /// It must not be null. It will make cut, copy and paste functionality work
+  /// with the most recently set [TextSelectionDelegate].
+  TextSelectionDelegate textSelectionDelegate;
+
+  Rect? _lastCaretRect;
+
+  /// Track whether position of the start of the selected text is within the viewport.
+  ///
+  /// For example, if the text contains "Hello World", and the user selects
+  /// "Hello", then scrolls so only "World" is visible, this will become false.
+  /// If the user scrolls back so that the "H" is visible again, this will
+  /// become true.
+  ///
+  /// This bool indicates whether the text is scrolled so that the handle is
+  /// inside the text field viewport, as opposed to whether it is actually
+  /// visible on the screen.
+  ValueListenable<bool> get selectionStartInViewport => _selectionStartInViewport;
+  final ValueNotifier<bool> _selectionStartInViewport = ValueNotifier<bool>(true);
+
+  /// Track whether position of the end of the selected text is within the viewport.
+  ///
+  /// For example, if the text contains "Hello World", and the user selects
+  /// "World", then scrolls so only "Hello" is visible, this will become
+  /// 'false'. If the user scrolls back so that the "d" is visible again, this
+  /// will become 'true'.
+  ///
+  /// This bool indicates whether the text is scrolled so that the handle is
+  /// inside the text field viewport, as opposed to whether it is actually
+  /// visible on the screen.
+  ValueListenable<bool> get selectionEndInViewport => _selectionEndInViewport;
+  final ValueNotifier<bool> _selectionEndInViewport = ValueNotifier<bool>(true);
+
+  void _updateSelectionExtentsVisibility(Offset effectiveOffset) {
+    assert(selection != null);
+    final Rect visibleRegion = Offset.zero & size;
+
+    final Offset startOffset = _textPainter.getOffsetForCaret(
+      TextPosition(offset: selection!.start, affinity: selection!.affinity),
+      _caretPrototype,
+    );
+    // TODO(justinmc): https://github.com/flutter/flutter/issues/31495
+    // Check if the selection is visible with an approximation because a
+    // difference between rounded and unrounded values causes the caret to be
+    // reported as having a slightly (< 0.5) negative y offset. This rounding
+    // happens in paragraph.cc's layout and TextPainer's
+    // _applyFloatingPointHack. Ideally, the rounding mismatch will be fixed and
+    // this can be changed to be a strict check instead of an approximation.
+    const double visibleRegionSlop = 0.5;
+    _selectionStartInViewport.value = visibleRegion
+      .inflate(visibleRegionSlop)
+      .contains(startOffset + effectiveOffset);
+
+    final Offset endOffset =  _textPainter.getOffsetForCaret(
+      TextPosition(offset: selection!.end, affinity: selection!.affinity),
+      _caretPrototype,
+    );
+    _selectionEndInViewport.value = visibleRegion
+      .inflate(visibleRegionSlop)
+      .contains(endOffset + effectiveOffset);
+  }
+
+  // Holds the last cursor location the user selected in the case the user tries
+  // to select vertically past the end or beginning of the field. If they do,
+  // then we need to keep the old cursor location so that we can go back to it
+  // if they change their minds. Only used for moving selection up and down in a
+  // multiline text field when selecting using the keyboard.
+  int _cursorResetLocation = -1;
+
+  // Whether we should reset the location of the cursor in the case the user
+  // tries to select vertically past the end or beginning of the field. If they
+  // do, then we need to keep the old cursor location so that we can go back to
+  // it if they change their minds. Only used for resetting selection up and
+  // down in a multiline text field when selecting using the keyboard.
+  bool _wasSelectingVerticallyWithKeyboard = false;
+
+  // Call through to onSelectionChanged.
+  void _handleSelectionChange(
+    TextSelection nextSelection,
+    SelectionChangedCause cause,
+  ) {
+    // Changes made by the keyboard can sometimes be "out of band" for listening
+    // components, so always send those events, even if we didn't think it
+    // changed. Also, focusing an empty field is sent as a selection change even
+    // if the selection offset didn't change.
+    final bool focusingEmpty = nextSelection.baseOffset == 0 && nextSelection.extentOffset == 0 && !hasFocus;
+    if (nextSelection == selection && cause != SelectionChangedCause.keyboard && !focusingEmpty) {
+      return;
+    }
+    if (onSelectionChanged != null) {
+      onSelectionChanged!(nextSelection, this, cause);
+    }
+  }
+
+  static final Set<LogicalKeyboardKey> _movementKeys = <LogicalKeyboardKey>{
+    LogicalKeyboardKey.arrowRight,
+    LogicalKeyboardKey.arrowLeft,
+    LogicalKeyboardKey.arrowUp,
+    LogicalKeyboardKey.arrowDown,
+  };
+
+  static final Set<LogicalKeyboardKey> _shortcutKeys = <LogicalKeyboardKey>{
+    LogicalKeyboardKey.keyA,
+    LogicalKeyboardKey.keyC,
+    LogicalKeyboardKey.keyV,
+    LogicalKeyboardKey.keyX,
+    LogicalKeyboardKey.delete,
+    LogicalKeyboardKey.backspace,
+  };
+
+  static final Set<LogicalKeyboardKey> _nonModifierKeys = <LogicalKeyboardKey>{
+    ..._shortcutKeys,
+    ..._movementKeys,
+  };
+
+  static final Set<LogicalKeyboardKey> _modifierKeys = <LogicalKeyboardKey>{
+    LogicalKeyboardKey.shift,
+    LogicalKeyboardKey.control,
+    LogicalKeyboardKey.alt,
+  };
+
+  static final Set<LogicalKeyboardKey> _macOsModifierKeys = <LogicalKeyboardKey>{
+    LogicalKeyboardKey.shift,
+    LogicalKeyboardKey.meta,
+    LogicalKeyboardKey.alt,
+  };
+
+  static final Set<LogicalKeyboardKey> _interestingKeys = <LogicalKeyboardKey>{
+    ..._modifierKeys,
+    ..._macOsModifierKeys,
+    ..._nonModifierKeys,
+  };
+
+  void _handleKeyEvent(RawKeyEvent keyEvent) {
+    if (kIsWeb) {
+      // On web platform, we should ignore the key because it's processed already.
+      return;
+    }
+
+    if (keyEvent is! RawKeyDownEvent || onSelectionChanged == null)
+      return;
+    final Set<LogicalKeyboardKey> keysPressed = LogicalKeyboardKey.collapseSynonyms(RawKeyboard.instance.keysPressed);
+    final LogicalKeyboardKey key = keyEvent.logicalKey;
+
+    final bool isMacOS = keyEvent.data is RawKeyEventDataMacOs;
+    if (!_nonModifierKeys.contains(key) ||
+        keysPressed.difference(isMacOS ? _macOsModifierKeys : _modifierKeys).length > 1 ||
+        keysPressed.difference(_interestingKeys).isNotEmpty) {
+      // If the most recently pressed key isn't a non-modifier key, or more than
+      // one non-modifier key is down, or keys other than the ones we're interested in
+      // are pressed, just ignore the keypress.
+      return;
+    }
+
+    // TODO(ianh): It seems to be entirely possible for the selection to be null here, but
+    // all the keyboard handling functions assume it is not.
+    assert(selection != null);
+
+    final bool isWordModifierPressed = isMacOS ? keyEvent.isAltPressed : keyEvent.isControlPressed;
+    final bool isLineModifierPressed = isMacOS ? keyEvent.isMetaPressed : keyEvent.isAltPressed;
+    final bool isShortcutModifierPressed = isMacOS ? keyEvent.isMetaPressed : keyEvent.isControlPressed;
+    if (_movementKeys.contains(key)) {
+      _handleMovement(key, wordModifier: isWordModifierPressed, lineModifier: isLineModifierPressed, shift: keyEvent.isShiftPressed);
+    } else if (isShortcutModifierPressed && _shortcutKeys.contains(key)) {
+      // _handleShortcuts depends on being started in the same stack invocation
+      // as the _handleKeyEvent method
+      _handleShortcuts(key);
+    } else if (key == LogicalKeyboardKey.delete) {
+      _handleDelete(forward: true);
+    } else if (key == LogicalKeyboardKey.backspace) {
+      _handleDelete(forward: false);
+    }
+  }
+
+  /// Returns the index into the string of the next character boundary after the
+  /// given index.
+  ///
+  /// The character boundary is determined by the characters package, so
+  /// surrogate pairs and extended grapheme clusters are considered.
+  ///
+  /// The index must be between 0 and string.length, inclusive. If given
+  /// string.length, string.length is returned.
+  ///
+  /// Setting includeWhitespace to false will only return the index of non-space
+  /// characters.
+  @visibleForTesting
+  static int nextCharacter(int index, String string, [bool includeWhitespace = true]) {
+    assert(index >= 0 && index <= string.length);
+    if (index == string.length) {
+      return string.length;
+    }
+
+    int count = 0;
+    final Characters remaining = string.characters.skipWhile((String currentString) {
+      if (count <= index) {
+        count += currentString.length;
+        return true;
+      }
+      if (includeWhitespace) {
+        return false;
+      }
+      return _isWhitespace(currentString.codeUnitAt(0));
+    });
+    return string.length - remaining.toString().length;
+  }
+
+  /// Returns the index into the string of the previous character boundary
+  /// before the given index.
+  ///
+  /// The character boundary is determined by the characters package, so
+  /// surrogate pairs and extended grapheme clusters are considered.
+  ///
+  /// The index must be between 0 and string.length, inclusive. If index is 0,
+  /// 0 will be returned.
+  ///
+  /// Setting includeWhitespace to false will only return the index of non-space
+  /// characters.
+  @visibleForTesting
+  static int previousCharacter(int index, String string, [bool includeWhitespace = true]) {
+    assert(index >= 0 && index <= string.length);
+    if (index == 0) {
+      return 0;
+    }
+
+    int count = 0;
+    int? lastNonWhitespace;
+    for (final String currentString in string.characters) {
+      if (!includeWhitespace &&
+          !_isWhitespace(currentString.characters.first.toString().codeUnitAt(0))) {
+        lastNonWhitespace = count;
+      }
+      if (count + currentString.length >= index) {
+        return includeWhitespace ? count : lastNonWhitespace ?? 0;
+      }
+      count += currentString.length;
+    }
+    return 0;
+  }
+
+  void _handleMovement(
+    LogicalKeyboardKey key, {
+    required bool wordModifier,
+    required bool lineModifier,
+    required bool shift,
+  }){
+    if (wordModifier && lineModifier) {
+      // If both modifiers are down, nothing happens on any of the platforms.
+      return;
+    }
+    assert(selection != null);
+
+    TextSelection newSelection = selection!;
+
+    final bool rightArrow = key == LogicalKeyboardKey.arrowRight;
+    final bool leftArrow = key == LogicalKeyboardKey.arrowLeft;
+    final bool upArrow = key == LogicalKeyboardKey.arrowUp;
+    final bool downArrow = key == LogicalKeyboardKey.arrowDown;
+
+    if ((rightArrow || leftArrow) && !(rightArrow && leftArrow)) {
+      // Jump to begin/end of word.
+      if (wordModifier) {
+        // If control/option is pressed, we will decide which way to look for a
+        // word based on which arrow is pressed.
+        if (leftArrow) {
+          // When going left, we want to skip over any whitespace before the word,
+          // so we go back to the first non-whitespace before asking for the word
+          // boundary, since _selectWordAtOffset finds the word boundaries without
+          // including whitespace.
+          final int startPoint = previousCharacter(newSelection.extentOffset, _plainText, false);
+          final TextSelection textSelection = _selectWordAtOffset(TextPosition(offset: startPoint));
+          newSelection = newSelection.copyWith(extentOffset: textSelection.baseOffset);
+        } else {
+          // When going right, we want to skip over any whitespace after the word,
+          // so we go forward to the first non-whitespace character before asking
+          // for the word bounds, since _selectWordAtOffset finds the word
+          // boundaries without including whitespace.
+          final int startPoint = nextCharacter(newSelection.extentOffset, _plainText, false);
+          final TextSelection textSelection = _selectWordAtOffset(TextPosition(offset: startPoint));
+          newSelection = newSelection.copyWith(extentOffset: textSelection.extentOffset);
+        }
+      } else if (lineModifier) {
+        // If control/command is pressed, we will decide which way to expand to
+        // the beginning/end of the line based on which arrow is pressed.
+        if (leftArrow) {
+          // When going left, we want to skip over any whitespace before the line,
+          // so we go back to the first non-whitespace before asking for the line
+          // bounds, since _selectLineAtOffset finds the line boundaries without
+          // including whitespace (like the newline).
+          final int startPoint = previousCharacter(newSelection.extentOffset, _plainText, false);
+          final TextSelection textSelection = _selectLineAtOffset(TextPosition(offset: startPoint));
+          newSelection = newSelection.copyWith(extentOffset: textSelection.baseOffset);
+        } else {
+          // When going right, we want to skip over any whitespace after the line,
+          // so we go forward to the first non-whitespace character before asking
+          // for the line bounds, since _selectLineAtOffset finds the line
+          // boundaries without including whitespace (like the newline).
+          final int startPoint = nextCharacter(newSelection.extentOffset, _plainText, false);
+          final TextSelection textSelection = _selectLineAtOffset(TextPosition(offset: startPoint));
+          newSelection = newSelection.copyWith(extentOffset: textSelection.extentOffset);
+        }
+      } else {
+        // The directional arrows move the TextSelection.extentOffset, while the
+        // base remains fixed.
+        if (rightArrow && newSelection.extentOffset < _plainText.length) {
+          int nextExtent;
+          if (!shift && !wordModifier && !lineModifier && newSelection.start != newSelection.end) {
+            nextExtent = newSelection.end;
+          } else {
+            nextExtent = nextCharacter(newSelection.extentOffset, _plainText);
+          }
+          final int distance = nextExtent - newSelection.extentOffset;
+          newSelection = newSelection.copyWith(extentOffset: nextExtent);
+          if (shift) {
+            _cursorResetLocation += distance;
+          }
+        } else if (leftArrow && newSelection.extentOffset > 0) {
+          int previousExtent;
+          if (!shift && !wordModifier && !lineModifier && newSelection.start != newSelection.end) {
+            previousExtent = newSelection.start;
+          } else {
+            previousExtent = previousCharacter(newSelection.extentOffset, _plainText);
+          }
+          final int distance = newSelection.extentOffset - previousExtent;
+          newSelection = newSelection.copyWith(extentOffset: previousExtent);
+          if (shift) {
+            _cursorResetLocation -= distance;
+          }
+        }
+      }
+    }
+
+    // Handles moving the cursor vertically as well as taking care of the
+    // case where the user moves the cursor to the end or beginning of the text
+    // and then back up or down.
+    if (downArrow || upArrow) {
+      // The caret offset gives a location in the upper left hand corner of
+      // the caret so the middle of the line above is a half line above that
+      // point and the line below is 1.5 lines below that point.
+      final double preferredLineHeight = _textPainter.preferredLineHeight;
+      final double verticalOffset = upArrow ? -0.5 * preferredLineHeight : 1.5 * preferredLineHeight;
+
+      final Offset caretOffset = _textPainter.getOffsetForCaret(TextPosition(offset: newSelection.extentOffset), _caretPrototype);
+      final Offset caretOffsetTranslated = caretOffset.translate(0.0, verticalOffset);
+      final TextPosition position = _textPainter.getPositionForOffset(caretOffsetTranslated);
+
+      // To account for the possibility where the user vertically highlights
+      // all the way to the top or bottom of the text, we hold the previous
+      // cursor location. This allows us to restore to this position in the
+      // case that the user wants to unhighlight some text.
+      if (position.offset == newSelection.extentOffset) {
+        if (downArrow) {
+          newSelection = newSelection.copyWith(extentOffset: _plainText.length);
+        } else if (upArrow) {
+          newSelection = newSelection.copyWith(extentOffset: 0);
+        }
+        _wasSelectingVerticallyWithKeyboard = shift;
+      } else if (_wasSelectingVerticallyWithKeyboard && shift) {
+        newSelection = newSelection.copyWith(extentOffset: _cursorResetLocation);
+        _wasSelectingVerticallyWithKeyboard = false;
+      } else {
+        newSelection = newSelection.copyWith(extentOffset: position.offset);
+        _cursorResetLocation = newSelection.extentOffset;
+      }
+    }
+
+    // Just place the collapsed selection at the end or beginning of the region
+    // if shift isn't down.
+    if (!shift) {
+      // We want to put the cursor at the correct location depending on which
+      // arrow is used while there is a selection.
+      int newOffset = newSelection.extentOffset;
+      if (!selection!.isCollapsed) {
+        if (leftArrow) {
+          newOffset = newSelection.baseOffset < newSelection.extentOffset ? newSelection.baseOffset : newSelection.extentOffset;
+        } else if (rightArrow) {
+          newOffset = newSelection.baseOffset > newSelection.extentOffset ? newSelection.baseOffset : newSelection.extentOffset;
+        }
+      }
+      newSelection = TextSelection.fromPosition(TextPosition(offset: newOffset));
+    }
+
+    _handleSelectionChange(
+      newSelection,
+      SelectionChangedCause.keyboard,
+    );
+    // Update the text selection delegate so that the engine knows what we did.
+    textSelectionDelegate.textEditingValue = textSelectionDelegate.textEditingValue.copyWith(selection: newSelection);
+  }
+
+  // Handles shortcut functionality including cut, copy, paste and select all
+  // using control/command + (X, C, V, A).
+  Future<void> _handleShortcuts(LogicalKeyboardKey key) async {
+    final TextSelection selection = textSelectionDelegate.textEditingValue.selection;
+    final String text = textSelectionDelegate.textEditingValue.text;
+    assert(selection != null);
+    assert(_shortcutKeys.contains(key), 'shortcut key $key not recognized.');
+    if (key == LogicalKeyboardKey.keyC) {
+      if (!selection.isCollapsed) {
+        Clipboard.setData(
+            ClipboardData(text: selection.textInside(text)));
+      }
+      return;
+    }
+    TextEditingValue? value;
+    if (key == LogicalKeyboardKey.keyX && !_readOnly) {
+      if (!selection.isCollapsed) {
+        Clipboard.setData(ClipboardData(text: selection.textInside(text)));
+        value = TextEditingValue(
+          text: selection.textBefore(text) + selection.textAfter(text),
+          selection: TextSelection.collapsed(offset: math.min(selection.start, selection.end)),
+        );
+      }
+    } else if (key == LogicalKeyboardKey.keyV && !_readOnly) {
+      // Snapshot the input before using `await`.
+      // See https://github.com/flutter/flutter/issues/11427
+      final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
+      if (data != null) {
+        value = TextEditingValue(
+          text: selection.textBefore(text) + data.text! + selection.textAfter(text),
+          selection: TextSelection.collapsed(
+            offset: math.min(selection.start, selection.end) + data.text!.length,
+          ),
+        );
+      }
+    } else if (key == LogicalKeyboardKey.keyA) {
+      value = TextEditingValue(
+        text: text,
+        selection: selection.copyWith(
+          baseOffset: 0,
+          extentOffset: textSelectionDelegate.textEditingValue.text.length,
+        ),
+      );
+    }
+    if (value != null) {
+      if (textSelectionDelegate.textEditingValue.selection != value.selection) {
+        _handleSelectionChange(
+          value.selection,
+          SelectionChangedCause.keyboard,
+        );
+      }
+      textSelectionDelegate.textEditingValue = value;
+    }
+  }
+
+  void _handleDelete({ required bool forward }) {
+    final TextSelection selection = textSelectionDelegate.textEditingValue.selection;
+    final String text = textSelectionDelegate.textEditingValue.text;
+    assert(_selection != null);
+    if (_readOnly) {
+      return;
+    }
+    String textBefore = selection.textBefore(text);
+    String textAfter = selection.textAfter(text);
+    int cursorPosition = math.min(selection.start, selection.end);
+    // If not deleting a selection, delete the next/previous character.
+    if (selection.isCollapsed) {
+      if (!forward && textBefore.isNotEmpty) {
+        final int characterBoundary = previousCharacter(textBefore.length, textBefore);
+        textBefore = textBefore.substring(0, characterBoundary);
+        cursorPosition = characterBoundary;
+      }
+      if (forward && textAfter.isNotEmpty) {
+        final int deleteCount = nextCharacter(0, textAfter);
+        textAfter = textAfter.substring(deleteCount);
+      }
+    }
+    final TextSelection newSelection = TextSelection.collapsed(offset: cursorPosition);
+    if (selection != newSelection) {
+      _handleSelectionChange(
+        newSelection,
+        SelectionChangedCause.keyboard,
+      );
+    }
+    textSelectionDelegate.textEditingValue = TextEditingValue(
+      text: textBefore + textAfter,
+      selection: newSelection,
+    );
+  }
+
+  /// Marks the render object as needing to be laid out again and have its text
+  /// metrics recomputed.
+  ///
+  /// Implies [markNeedsLayout].
+  @protected
+  void markNeedsTextLayout() {
+    _textLayoutLastMaxWidth = null;
+    _textLayoutLastMinWidth = null;
+    markNeedsLayout();
+  }
+
+  @override
+  void systemFontsDidChange() {
+    super.systemFontsDidChange();
+    _textPainter.markNeedsLayout();
+    _textLayoutLastMaxWidth = null;
+    _textLayoutLastMinWidth = null;
+  }
+
+  String? _cachedPlainText;
+  // Returns a plain text version of the text in the painter.
+  //
+  // Returns the obscured text when [obscureText] is true. See
+  // [obscureText] and [obscuringCharacter].
+  String get _plainText {
+    _cachedPlainText ??= _textPainter.text!.toPlainText();
+    return _cachedPlainText!;
+  }
+
+  /// The text to display.
+  TextSpan? get text => _textPainter.text as TextSpan?;
+  final TextPainter _textPainter;
+  set text(TextSpan? value) {
+    if (_textPainter.text == value)
+      return;
+    _textPainter.text = value;
+    _cachedPlainText = null;
+    markNeedsTextLayout();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// How the text should be aligned horizontally.
+  ///
+  /// This must not be null.
+  TextAlign get textAlign => _textPainter.textAlign;
+  set textAlign(TextAlign value) {
+    assert(value != null);
+    if (_textPainter.textAlign == value)
+      return;
+    _textPainter.textAlign = value;
+    markNeedsTextLayout();
+  }
+
+  /// The directionality of the text.
+  ///
+  /// This decides how the [TextAlign.start], [TextAlign.end], and
+  /// [TextAlign.justify] values of [textAlign] are interpreted.
+  ///
+  /// This is also used to disambiguate how to render bidirectional text. For
+  /// example, if the [text] is an English phrase followed by a Hebrew phrase,
+  /// in a [TextDirection.ltr] context the English phrase will be on the left
+  /// and the Hebrew phrase to its right, while in a [TextDirection.rtl]
+  /// context, the English phrase will be on the right and the Hebrew phrase on
+  /// its left.
+  ///
+  /// This must not be null.
+  // TextPainter.textDirection is nullable, but it is set to a
+  // non-null value in the RenderEditable constructor and we refuse to
+  // set it to null here, so _textPainter.textDirection cannot be null.
+  TextDirection get textDirection => _textPainter.textDirection!;
+  set textDirection(TextDirection value) {
+    assert(value != null);
+    if (_textPainter.textDirection == value)
+      return;
+    _textPainter.textDirection = value;
+    markNeedsTextLayout();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Used by this renderer's internal [TextPainter] to select a locale-specific
+  /// font.
+  ///
+  /// In some cases the same Unicode character may be rendered differently depending
+  /// on the locale. For example the '骨' character is rendered differently in
+  /// the Chinese and Japanese locales. In these cases the [locale] may be used
+  /// to select a locale-specific font.
+  ///
+  /// If this value is null, a system-dependent algorithm is used to select
+  /// the font.
+  Locale? get locale => _textPainter.locale;
+  set locale(Locale? value) {
+    if (_textPainter.locale == value)
+      return;
+    _textPainter.locale = value;
+    markNeedsTextLayout();
+  }
+
+  /// The [StrutStyle] used by the renderer's internal [TextPainter] to
+  /// determine the strut to use.
+  StrutStyle? get strutStyle => _textPainter.strutStyle;
+  set strutStyle(StrutStyle? value) {
+    if (_textPainter.strutStyle == value)
+      return;
+    _textPainter.strutStyle = value;
+    markNeedsTextLayout();
+  }
+
+  /// The color to use when painting the cursor.
+  Color? get cursorColor => _cursorColor;
+  Color? _cursorColor;
+  set cursorColor(Color? value) {
+    if (_cursorColor == value)
+      return;
+    _cursorColor = value;
+    markNeedsPaint();
+  }
+
+  /// The color to use when painting the cursor aligned to the text while
+  /// rendering the floating cursor.
+  ///
+  /// The default is light grey.
+  Color? get backgroundCursorColor => _backgroundCursorColor;
+  Color? _backgroundCursorColor;
+  set backgroundCursorColor(Color? value) {
+    if (backgroundCursorColor == value)
+      return;
+    _backgroundCursorColor = value;
+    markNeedsPaint();
+  }
+
+  /// Whether to paint the cursor.
+  ValueNotifier<bool> get showCursor => _showCursor;
+  ValueNotifier<bool> _showCursor;
+  set showCursor(ValueNotifier<bool> value) {
+    assert(value != null);
+    if (_showCursor == value)
+      return;
+    if (attached)
+      _showCursor.removeListener(markNeedsPaint);
+    _showCursor = value;
+    if (attached)
+      _showCursor.addListener(markNeedsPaint);
+    markNeedsPaint();
+  }
+
+  /// Whether the editable is currently focused.
+  bool get hasFocus => _hasFocus;
+  bool _hasFocus = false;
+  bool _listenerAttached = false;
+  set hasFocus(bool value) {
+    assert(value != null);
+    if (_hasFocus == value)
+      return;
+    _hasFocus = value;
+    if (_hasFocus) {
+      assert(!_listenerAttached);
+      RawKeyboard.instance.addListener(_handleKeyEvent);
+      _listenerAttached = true;
+    } else {
+      assert(_listenerAttached);
+      RawKeyboard.instance.removeListener(_handleKeyEvent);
+      _listenerAttached = false;
+    }
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Whether this rendering object will take a full line regardless the text width.
+  bool get forceLine => _forceLine;
+  bool _forceLine = false;
+  set forceLine(bool value) {
+    assert(value != null);
+    if (_forceLine == value)
+      return;
+    _forceLine = value;
+    markNeedsLayout();
+  }
+
+  /// Whether this rendering object is read only.
+  bool get readOnly => _readOnly;
+  bool _readOnly = false;
+  set readOnly(bool value) {
+    assert(value != null);
+    if (_readOnly == value)
+      return;
+    _readOnly = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// The maximum number of lines for the text to span, wrapping if necessary.
+  ///
+  /// If this is 1 (the default), the text will not wrap, but will extend
+  /// indefinitely instead.
+  ///
+  /// If this is null, there is no limit to the number of lines.
+  ///
+  /// When this is not null, the intrinsic height of the render object is the
+  /// height of one line of text multiplied by this value. In other words, this
+  /// also controls the height of the actual editing widget.
+  int? get maxLines => _maxLines;
+  int? _maxLines;
+  /// The value may be null. If it is not null, then it must be greater than zero.
+  set maxLines(int? value) {
+    assert(value == null || value > 0);
+    if (maxLines == value)
+      return;
+    _maxLines = value;
+    markNeedsTextLayout();
+  }
+
+  /// {@macro flutter.widgets.editableText.minLines}
+  int? get minLines => _minLines;
+  int? _minLines;
+  /// The value may be null. If it is not null, then it must be greater than zero.
+  set minLines(int? value) {
+    assert(value == null || value > 0);
+    if (minLines == value)
+      return;
+    _minLines = value;
+    markNeedsTextLayout();
+  }
+
+  /// {@macro flutter.widgets.editableText.expands}
+  bool get expands => _expands;
+  bool _expands;
+  set expands(bool value) {
+    assert(value != null);
+    if (expands == value)
+      return;
+    _expands = value;
+    markNeedsTextLayout();
+  }
+
+  /// The color to use when painting the selection.
+  Color? get selectionColor => _selectionColor;
+  Color? _selectionColor;
+  set selectionColor(Color? value) {
+    if (_selectionColor == value)
+      return;
+    _selectionColor = value;
+    markNeedsPaint();
+  }
+
+  /// The number of font pixels for each logical pixel.
+  ///
+  /// For example, if the text scale factor is 1.5, text will be 50% larger than
+  /// the specified font size.
+  double get textScaleFactor => _textPainter.textScaleFactor;
+  set textScaleFactor(double value) {
+    assert(value != null);
+    if (_textPainter.textScaleFactor == value)
+      return;
+    _textPainter.textScaleFactor = value;
+    markNeedsTextLayout();
+  }
+
+  List<ui.TextBox>? _selectionRects;
+
+  /// The region of text that is selected, if any.
+  ///
+  /// The caret position is represented by a collapsed selection.
+  ///
+  /// If [selection] is null, there is no selection and attempts to
+  /// manipulate the selection will throw.
+  TextSelection? get selection => _selection;
+  TextSelection? _selection;
+  set selection(TextSelection? value) {
+    if (_selection == value)
+      return;
+    _selection = value;
+    _selectionRects = null;
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// The offset at which the text should be painted.
+  ///
+  /// If the text content is larger than the editable line itself, the editable
+  /// line clips the text. This property controls which part of the text is
+  /// visible by shifting the text by the given offset before clipping.
+  ViewportOffset get offset => _offset;
+  ViewportOffset _offset;
+  set offset(ViewportOffset value) {
+    assert(value != null);
+    if (_offset == value)
+      return;
+    if (attached)
+      _offset.removeListener(markNeedsPaint);
+    _offset = value;
+    if (attached)
+      _offset.addListener(markNeedsPaint);
+    markNeedsLayout();
+  }
+
+  /// How thick the cursor will be.
+  double get cursorWidth => _cursorWidth;
+  double _cursorWidth = 1.0;
+  set cursorWidth(double value) {
+    if (_cursorWidth == value)
+      return;
+    _cursorWidth = value;
+    markNeedsLayout();
+  }
+
+  /// How tall the cursor will be.
+  ///
+  /// This can be null, in which case the getter will actually return [preferredLineHeight].
+  ///
+  /// Setting this to itself fixes the value to the current [preferredLineHeight]. Setting
+  /// this to null returns the behavior of deferring to [preferredLineHeight].
+  // TODO(ianh): This is a confusing API. We should have a separate getter for the effective cursor height.
+  double get cursorHeight => _cursorHeight ?? preferredLineHeight;
+  double? _cursorHeight;
+  set cursorHeight(double? value) {
+    if (_cursorHeight == value)
+      return;
+    _cursorHeight = value;
+    markNeedsLayout();
+  }
+
+  /// {@template flutter.rendering.RenderEditable.paintCursorAboveText}
+  /// If the cursor should be painted on top of the text or underneath it.
+  ///
+  /// By default, the cursor should be painted on top for iOS platforms and
+  /// underneath for Android platforms.
+  /// {@endtemplate}
+  bool get paintCursorAboveText => _paintCursorOnTop;
+  bool _paintCursorOnTop;
+  set paintCursorAboveText(bool value) {
+    if (_paintCursorOnTop == value)
+      return;
+    _paintCursorOnTop = value;
+    markNeedsLayout();
+  }
+
+  /// {@template flutter.rendering.RenderEditable.cursorOffset}
+  /// The offset that is used, in pixels, when painting the cursor on screen.
+  ///
+  /// By default, the cursor position should be set to an offset of
+  /// (-[cursorWidth] * 0.5, 0.0) on iOS platforms and (0, 0) on Android
+  /// platforms. The origin from where the offset is applied to is the arbitrary
+  /// location where the cursor ends up being rendered from by default.
+  /// {@endtemplate}
+  Offset? get cursorOffset => _cursorOffset;
+  Offset? _cursorOffset;
+  set cursorOffset(Offset? value) {
+    if (_cursorOffset == value)
+      return;
+    _cursorOffset = value;
+    markNeedsLayout();
+  }
+
+  /// How rounded the corners of the cursor should be.
+  ///
+  /// A null value is the same as [Radius.zero].
+  Radius? get cursorRadius => _cursorRadius;
+  Radius? _cursorRadius;
+  set cursorRadius(Radius? value) {
+    if (_cursorRadius == value)
+      return;
+    _cursorRadius = value;
+    markNeedsPaint();
+  }
+
+  /// The [LayerLink] of start selection handle.
+  ///
+  /// [RenderEditable] is responsible for calculating the [Offset] of this
+  /// [LayerLink], which will be used as [CompositedTransformTarget] of start handle.
+  LayerLink get startHandleLayerLink => _startHandleLayerLink;
+  LayerLink _startHandleLayerLink;
+  set startHandleLayerLink(LayerLink value) {
+    if (_startHandleLayerLink == value)
+      return;
+    _startHandleLayerLink = value;
+    markNeedsPaint();
+  }
+
+  /// The [LayerLink] of end selection handle.
+  ///
+  /// [RenderEditable] is responsible for calculating the [Offset] of this
+  /// [LayerLink], which will be used as [CompositedTransformTarget] of end handle.
+  LayerLink get endHandleLayerLink => _endHandleLayerLink;
+  LayerLink _endHandleLayerLink;
+  set endHandleLayerLink(LayerLink value) {
+    if (_endHandleLayerLink == value)
+      return;
+    _endHandleLayerLink = value;
+    markNeedsPaint();
+  }
+
+  /// The padding applied to text field. Used to determine the bounds when
+  /// moving the floating cursor.
+  ///
+  /// Defaults to a padding with left, top and right set to 4, bottom to 5.
+  EdgeInsets get floatingCursorAddedMargin => _floatingCursorAddedMargin;
+  EdgeInsets _floatingCursorAddedMargin;
+  set floatingCursorAddedMargin(EdgeInsets value) {
+    if (_floatingCursorAddedMargin == value)
+      return;
+    _floatingCursorAddedMargin = value;
+    markNeedsPaint();
+  }
+
+  bool _floatingCursorOn = false;
+  late Offset _floatingCursorOffset;
+  late TextPosition _floatingCursorTextPosition;
+
+  /// Controls how tall the selection highlight boxes are computed to be.
+  ///
+  /// See [ui.BoxHeightStyle] for details on available styles.
+  ui.BoxHeightStyle get selectionHeightStyle => _selectionHeightStyle;
+  ui.BoxHeightStyle _selectionHeightStyle;
+  set selectionHeightStyle(ui.BoxHeightStyle value) {
+    assert(value != null);
+    if (_selectionHeightStyle == value)
+      return;
+    _selectionHeightStyle = value;
+    markNeedsPaint();
+  }
+
+  /// Controls how wide the selection highlight boxes are computed to be.
+  ///
+  /// See [ui.BoxWidthStyle] for details on available styles.
+  ui.BoxWidthStyle get selectionWidthStyle => _selectionWidthStyle;
+  ui.BoxWidthStyle _selectionWidthStyle;
+  set selectionWidthStyle(ui.BoxWidthStyle value) {
+    assert(value != null);
+    if (_selectionWidthStyle == value)
+      return;
+    _selectionWidthStyle = value;
+    markNeedsPaint();
+  }
+
+  /// Whether to allow the user to change the selection.
+  ///
+  /// Since [RenderEditable] does not handle selection manipulation
+  /// itself, this actually only affects whether the accessibility
+  /// hints provided to the system (via
+  /// [describeSemanticsConfiguration]) will enable selection
+  /// manipulation. It's the responsibility of this object's owner
+  /// to provide selection manipulation affordances.
+  ///
+  /// This field is used by [selectionEnabled] (which then controls
+  /// the accessibility hints mentioned above). When null,
+  /// [obscureText] is used to determine the value of
+  /// [selectionEnabled] instead.
+  bool? get enableInteractiveSelection => _enableInteractiveSelection;
+  bool? _enableInteractiveSelection;
+  set enableInteractiveSelection(bool? value) {
+    if (_enableInteractiveSelection == value)
+      return;
+    _enableInteractiveSelection = value;
+    markNeedsTextLayout();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Whether interactive selection are enabled based on the values of
+  /// [enableInteractiveSelection] and [obscureText].
+  ///
+  /// Since [RenderEditable] does not handle selection manipulation
+  /// itself, this actually only affects whether the accessibility
+  /// hints provided to the system (via
+  /// [describeSemanticsConfiguration]) will enable selection
+  /// manipulation. It's the responsibility of this object's owner
+  /// to provide selection manipulation affordances.
+  ///
+  /// By default, [enableInteractiveSelection] is null, [obscureText] is false,
+  /// and this getter returns true.
+  ///
+  /// If [enableInteractiveSelection] is null and [obscureText] is true, then this
+  /// getter returns false. This is the common case for password fields.
+  ///
+  /// If [enableInteractiveSelection] is non-null then its value is
+  /// returned. An application might [enableInteractiveSelection] to
+  /// true to enable interactive selection for a password field, or to
+  /// false to unconditionally disable interactive selection.
+  bool get selectionEnabled {
+    return enableInteractiveSelection ?? !obscureText;
+  }
+
+  /// The color used to paint the prompt rectangle.
+  ///
+  /// The prompt rectangle will only be requested on non-web iOS applications.
+  // TODO(ianh): We should change the getter to return null when _promptRectRange is null
+  // (otherwise, if you set it to null and then get it, you get back non-null).
+  // Alternatively, we could stop supporting setting this to null.
+  Color? get promptRectColor => _promptRectPaint.color;
+  set promptRectColor(Color? newValue) {
+    // Painter.color cannot be null.
+    if (newValue == null) {
+      setPromptRectRange(null);
+      return;
+    }
+
+    if (promptRectColor == newValue)
+      return;
+
+    _promptRectPaint.color = newValue;
+    if (_promptRectRange != null)
+      markNeedsPaint();
+  }
+
+  TextRange? _promptRectRange;
+  /// Dismisses the currently displayed prompt rectangle and displays a new prompt rectangle
+  /// over [newRange] in the given color [promptRectColor].
+  ///
+  /// The prompt rectangle will only be requested on non-web iOS applications.
+  ///
+  /// When set to null, the currently displayed prompt rectangle (if any) will be dismissed.
+  void setPromptRectRange(TextRange? newRange) {
+    if (_promptRectRange == newRange)
+      return;
+
+    _promptRectRange = newRange;
+    markNeedsPaint();
+  }
+
+  /// The maximum amount the text is allowed to scroll.
+  ///
+  /// This value is only valid after layout and can change as additional
+  /// text is entered or removed in order to accommodate expanding when
+  /// [expands] is set to true.
+  double get maxScrollExtent => _maxScrollExtent;
+  double _maxScrollExtent = 0;
+
+  double get _caretMargin => _kCaretGap + cursorWidth;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge], and must not be null.
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior = Clip.hardEdge;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+
+    config
+      ..value = obscureText
+          ? obscuringCharacter * _plainText.length
+          : _plainText
+      ..isObscured = obscureText
+      ..isMultiline = _isMultiline
+      ..textDirection = textDirection
+      ..isFocused = hasFocus
+      ..isTextField = true
+      ..isReadOnly = readOnly;
+
+    if (hasFocus && selectionEnabled)
+      config.onSetSelection = _handleSetSelection;
+
+    if (selectionEnabled && selection?.isValid == true) {
+      config.textSelection = selection;
+      if (_textPainter.getOffsetBefore(selection!.extentOffset) != null) {
+        config
+          ..onMoveCursorBackwardByWord = _handleMoveCursorBackwardByWord
+          ..onMoveCursorBackwardByCharacter = _handleMoveCursorBackwardByCharacter;
+      }
+      if (_textPainter.getOffsetAfter(selection!.extentOffset) != null) {
+        config
+          ..onMoveCursorForwardByWord = _handleMoveCursorForwardByWord
+          ..onMoveCursorForwardByCharacter = _handleMoveCursorForwardByCharacter;
+      }
+    }
+  }
+
+  // TODO(ianh): in theory, [selection] could become null between when
+  // we last called describeSemanticsConfiguration and when the
+  // callbacks are invoked, in which case the callbacks will crash...
+
+  void _handleSetSelection(TextSelection selection) {
+    _handleSelectionChange(selection, SelectionChangedCause.keyboard);
+  }
+
+  void _handleMoveCursorForwardByCharacter(bool extentSelection) {
+    assert(selection != null);
+    final int? extentOffset = _textPainter.getOffsetAfter(selection!.extentOffset);
+    if (extentOffset == null)
+      return;
+    final int baseOffset = !extentSelection ? extentOffset : selection!.baseOffset;
+    _handleSelectionChange(
+      TextSelection(baseOffset: baseOffset, extentOffset: extentOffset), SelectionChangedCause.keyboard,
+    );
+  }
+
+  void _handleMoveCursorBackwardByCharacter(bool extentSelection) {
+    assert(selection != null);
+    final int? extentOffset = _textPainter.getOffsetBefore(selection!.extentOffset);
+    if (extentOffset == null)
+      return;
+    final int baseOffset = !extentSelection ? extentOffset : selection!.baseOffset;
+    _handleSelectionChange(
+      TextSelection(baseOffset: baseOffset, extentOffset: extentOffset), SelectionChangedCause.keyboard,
+    );
+  }
+
+  void _handleMoveCursorForwardByWord(bool extentSelection) {
+    assert(selection != null);
+    final TextRange currentWord = _textPainter.getWordBoundary(selection!.extent);
+    final TextRange? nextWord = _getNextWord(currentWord.end);
+    if (nextWord == null)
+      return;
+    final int baseOffset = extentSelection ? selection!.baseOffset : nextWord.start;
+    _handleSelectionChange(
+      TextSelection(
+        baseOffset: baseOffset,
+        extentOffset: nextWord.start,
+      ),
+      SelectionChangedCause.keyboard,
+    );
+  }
+
+  void _handleMoveCursorBackwardByWord(bool extentSelection) {
+    assert(selection != null);
+    final TextRange currentWord = _textPainter.getWordBoundary(selection!.extent);
+    final TextRange? previousWord = _getPreviousWord(currentWord.start - 1);
+    if (previousWord == null)
+      return;
+    final int baseOffset = extentSelection ?  selection!.baseOffset : previousWord.start;
+    _handleSelectionChange(
+      TextSelection(
+        baseOffset: baseOffset,
+        extentOffset: previousWord.start,
+      ),
+      SelectionChangedCause.keyboard,
+    );
+  }
+
+  TextRange? _getNextWord(int offset) {
+    while (true) {
+      final TextRange range = _textPainter.getWordBoundary(TextPosition(offset: offset));
+      if (range == null || !range.isValid || range.isCollapsed)
+        return null;
+      if (!_onlyWhitespace(range))
+        return range;
+      offset = range.end;
+    }
+  }
+
+  TextRange? _getPreviousWord(int offset) {
+    while (offset >= 0) {
+      final TextRange range = _textPainter.getWordBoundary(TextPosition(offset: offset));
+      if (range == null || !range.isValid || range.isCollapsed)
+        return null;
+      if (!_onlyWhitespace(range))
+        return range;
+      offset = range.start - 1;
+    }
+    return null;
+  }
+
+  // Check if the given text range only contains white space or separator
+  // characters.
+  //
+  // Includes newline characters from ASCII and separators from the
+  // [unicode separator category](https://www.compart.com/en/unicode/category/Zs)
+  // TODO(jonahwilliams): replace when we expose this ICU information.
+  bool _onlyWhitespace(TextRange range) {
+    for (int i = range.start; i < range.end; i++) {
+      final int codeUnit = text!.codeUnitAt(i)!;
+      if (!_isWhitespace(codeUnit)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    _tap = TapGestureRecognizer(debugOwner: this)
+      ..onTapDown = _handleTapDown
+      ..onTap = _handleTap;
+    _longPress = LongPressGestureRecognizer(debugOwner: this)..onLongPress = _handleLongPress;
+    _offset.addListener(markNeedsPaint);
+    _showCursor.addListener(markNeedsPaint);
+  }
+
+  @override
+  void detach() {
+    _tap.dispose();
+    _longPress.dispose();
+    _offset.removeListener(markNeedsPaint);
+    _showCursor.removeListener(markNeedsPaint);
+    if (_listenerAttached)
+      RawKeyboard.instance.removeListener(_handleKeyEvent);
+    super.detach();
+  }
+
+  bool get _isMultiline => maxLines != 1;
+
+  Axis get _viewportAxis => _isMultiline ? Axis.vertical : Axis.horizontal;
+
+  Offset get _paintOffset {
+    switch (_viewportAxis) {
+      case Axis.horizontal:
+        return Offset(-offset.pixels, 0.0);
+      case Axis.vertical:
+        return Offset(0.0, -offset.pixels);
+    }
+  }
+
+  double get _viewportExtent {
+    assert(hasSize);
+    switch (_viewportAxis) {
+      case Axis.horizontal:
+        return size.width;
+      case Axis.vertical:
+        return size.height;
+    }
+  }
+
+  double _getMaxScrollExtent(Size contentSize) {
+    assert(hasSize);
+    switch (_viewportAxis) {
+      case Axis.horizontal:
+        return math.max(0.0, contentSize.width - size.width);
+      case Axis.vertical:
+        return math.max(0.0, contentSize.height - size.height);
+    }
+  }
+
+  // We need to check the paint offset here because during animation, the start of
+  // the text may position outside the visible region even when the text fits.
+  bool get _hasVisualOverflow => _maxScrollExtent > 0 || _paintOffset != Offset.zero;
+
+  /// Returns the local coordinates of the endpoints of the given selection.
+  ///
+  /// If the selection is collapsed (and therefore occupies a single point), the
+  /// returned list is of length one. Otherwise, the selection is not collapsed
+  /// and the returned list is of length two. In this case, however, the two
+  /// points might actually be co-located (e.g., because of a bidirectional
+  /// selection that contains some text but whose ends meet in the middle).
+  ///
+  /// See also:
+  ///
+  ///  * [getLocalRectForCaret], which is the equivalent but for
+  ///    a [TextPosition] rather than a [TextSelection].
+  List<TextSelectionPoint> getEndpointsForSelection(TextSelection selection) {
+    assert(constraints != null);
+    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
+
+    final Offset paintOffset = _paintOffset;
+
+
+    final List<ui.TextBox> boxes = selection.isCollapsed ?
+        <ui.TextBox>[] : _textPainter.getBoxesForSelection(selection);
+    if (boxes.isEmpty) {
+      // TODO(mpcomplete): This doesn't work well at an RTL/LTR boundary.
+      final Offset caretOffset = _textPainter.getOffsetForCaret(selection.extent, _caretPrototype);
+      final Offset start = Offset(0.0, preferredLineHeight) + caretOffset + paintOffset;
+      return <TextSelectionPoint>[TextSelectionPoint(start, null)];
+    } else {
+      final Offset start = Offset(boxes.first.start, boxes.first.bottom) + paintOffset;
+      final Offset end = Offset(boxes.last.end, boxes.last.bottom) + paintOffset;
+      return <TextSelectionPoint>[
+        TextSelectionPoint(start, boxes.first.direction),
+        TextSelectionPoint(end, boxes.last.direction),
+      ];
+    }
+  }
+
+  /// Returns the smallest [Rect], in the local coordinate system, that covers
+  /// the text within the [TextRange] specified.
+  ///
+  /// This method is used to calculate the approximate position of the IME bar
+  /// on iOS.
+  ///
+  /// Returns null if [TextRange.isValid] is false for the given `range`, or the
+  /// given `range` is collapsed.
+  Rect? getRectForComposingRange(TextRange range) {
+    assert(constraints != null);
+    if (!range.isValid || range.isCollapsed)
+      return null;
+    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
+
+    final List<ui.TextBox> boxes = _textPainter.getBoxesForSelection(
+      TextSelection(baseOffset: range.start, extentOffset: range.end),
+    );
+
+    return boxes.fold(
+      null,
+      (Rect? accum, TextBox incoming) => accum?.expandToInclude(incoming.toRect()) ?? incoming.toRect(),
+    )?.shift(_paintOffset);
+  }
+
+  /// Returns the position in the text for the given global coordinate.
+  ///
+  /// See also:
+  ///
+  ///  * [getLocalRectForCaret], which is the reverse operation, taking
+  ///    a [TextPosition] and returning a [Rect].
+  ///  * [TextPainter.getPositionForOffset], which is the equivalent method
+  ///    for a [TextPainter] object.
+  TextPosition getPositionForPoint(Offset globalPosition) {
+    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
+    globalPosition += -_paintOffset;
+    return _textPainter.getPositionForOffset(globalToLocal(globalPosition));
+  }
+
+  /// Returns the [Rect] in local coordinates for the caret at the given text
+  /// position.
+  ///
+  /// See also:
+  ///
+  ///  * [getPositionForPoint], which is the reverse operation, taking
+  ///    an [Offset] in global coordinates and returning a [TextPosition].
+  ///  * [getEndpointsForSelection], which is the equivalent but for
+  ///    a selection rather than a particular text position.
+  ///  * [TextPainter.getOffsetForCaret], the equivalent method for a
+  ///    [TextPainter] object.
+  Rect getLocalRectForCaret(TextPosition caretPosition) {
+    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
+    final Offset caretOffset = _textPainter.getOffsetForCaret(caretPosition, _caretPrototype);
+    // This rect is the same as _caretPrototype but without the vertical padding.
+    Rect rect = Rect.fromLTWH(0.0, 0.0, cursorWidth, cursorHeight).shift(caretOffset + _paintOffset);
+    // Add additional cursor offset (generally only if on iOS).
+    if (_cursorOffset != null)
+      rect = rect.shift(_cursorOffset!);
+
+    return rect.shift(_getPixelPerfectCursorOffset(rect));
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    _layoutText(maxWidth: double.infinity);
+    return _textPainter.minIntrinsicWidth;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    _layoutText(maxWidth: double.infinity);
+    return _textPainter.maxIntrinsicWidth + cursorWidth;
+  }
+
+  /// An estimate of the height of a line in the text. See [TextPainter.preferredLineHeight].
+  /// This does not required the layout to be updated.
+  double get preferredLineHeight => _textPainter.preferredLineHeight;
+
+  double _preferredHeight(double width) {
+    // Lock height to maxLines if needed.
+    final bool lockedMax = maxLines != null && minLines == null;
+    final bool lockedBoth = minLines != null && minLines == maxLines;
+    final bool singleLine = maxLines == 1;
+    if (singleLine || lockedMax || lockedBoth) {
+      return preferredLineHeight * maxLines!;
+    }
+
+    // Clamp height to minLines or maxLines if needed.
+    final bool minLimited = minLines != null && minLines! > 1;
+    final bool maxLimited = maxLines != null;
+    if (minLimited || maxLimited) {
+      _layoutText(maxWidth: width);
+      if (minLimited && _textPainter.height < preferredLineHeight * minLines!) {
+        return preferredLineHeight * minLines!;
+      }
+      if (maxLimited && _textPainter.height > preferredLineHeight * maxLines!) {
+        return preferredLineHeight * maxLines!;
+      }
+    }
+
+    // Set the height based on the content.
+    if (width == double.infinity) {
+      final String text = _plainText;
+      int lines = 1;
+      for (int index = 0; index < text.length; index += 1) {
+        if (text.codeUnitAt(index) == 0x0A) // count explicit line breaks
+          lines += 1;
+      }
+      return preferredLineHeight * lines;
+    }
+    _layoutText(maxWidth: width);
+    return math.max(preferredLineHeight, _textPainter.height);
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    return _preferredHeight(width);
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    return _preferredHeight(width);
+  }
+
+  @override
+  double computeDistanceToActualBaseline(TextBaseline baseline) {
+    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
+    return _textPainter.computeDistanceToActualBaseline(baseline);
+  }
+
+  @override
+  bool hitTestSelf(Offset position) => true;
+
+  late TapGestureRecognizer _tap;
+  late LongPressGestureRecognizer _longPress;
+
+  @override
+  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
+    assert(debugHandleEvent(event, entry));
+    if (event is PointerDownEvent) {
+      assert(!debugNeedsLayout);
+      // Checks if there is any gesture recognizer in the text span.
+      final Offset offset = entry.localPosition;
+      final TextPosition position = _textPainter.getPositionForOffset(offset);
+      final InlineSpan? span = _textPainter.text!.getSpanForPosition(position);
+      if (span != null && span is TextSpan) {
+        final TextSpan textSpan = span;
+        textSpan.recognizer?.addPointer(event);
+      }
+
+      if (!ignorePointer && onSelectionChanged != null) {
+        // Propagates the pointer event to selection handlers.
+        _tap.addPointer(event);
+        _longPress.addPointer(event);
+      }
+    }
+  }
+
+  Offset? _lastTapDownPosition;
+
+  /// If [ignorePointer] is false (the default) then this method is called by
+  /// the internal gesture recognizer's [TapGestureRecognizer.onTapDown]
+  /// callback.
+  ///
+  /// When [ignorePointer] is true, an ancestor widget must respond to tap
+  /// down events by calling this method.
+  void handleTapDown(TapDownDetails details) {
+    _lastTapDownPosition = details.globalPosition;
+  }
+  void _handleTapDown(TapDownDetails details) {
+    assert(!ignorePointer);
+    handleTapDown(details);
+  }
+
+  /// If [ignorePointer] is false (the default) then this method is called by
+  /// the internal gesture recognizer's [TapGestureRecognizer.onTap]
+  /// callback.
+  ///
+  /// When [ignorePointer] is true, an ancestor widget must respond to tap
+  /// events by calling this method.
+  void handleTap() {
+    selectPosition(cause: SelectionChangedCause.tap);
+  }
+  void _handleTap() {
+    assert(!ignorePointer);
+    handleTap();
+  }
+
+  /// If [ignorePointer] is false (the default) then this method is called by
+  /// the internal gesture recognizer's [DoubleTapGestureRecognizer.onDoubleTap]
+  /// callback.
+  ///
+  /// When [ignorePointer] is true, an ancestor widget must respond to double
+  /// tap events by calling this method.
+  void handleDoubleTap() {
+    selectWord(cause: SelectionChangedCause.doubleTap);
+  }
+
+  /// If [ignorePointer] is false (the default) then this method is called by
+  /// the internal gesture recognizer's [LongPressGestureRecognizer.onLongPress]
+  /// callback.
+  ///
+  /// When [ignorePointer] is true, an ancestor widget must respond to long
+  /// press events by calling this method.
+  void handleLongPress() {
+    selectWord(cause: SelectionChangedCause.longPress);
+  }
+  void _handleLongPress() {
+    assert(!ignorePointer);
+    handleLongPress();
+  }
+
+  /// Move selection to the location of the last tap down.
+  ///
+  /// {@template flutter.rendering.RenderEditable.selectPosition}
+  /// This method is mainly used to translate user inputs in global positions
+  /// into a [TextSelection]. When used in conjunction with a [EditableText],
+  /// the selection change is fed back into [TextEditingController.selection].
+  ///
+  /// If you have a [TextEditingController], it's generally easier to
+  /// programmatically manipulate its `value` or `selection` directly.
+  /// {@endtemplate}
+  void selectPosition({ required SelectionChangedCause cause }) {
+    selectPositionAt(from: _lastTapDownPosition!, cause: cause);
+  }
+
+  /// Select text between the global positions [from] and [to].
+  ///
+  /// [from] corresponds to the [TextSelection.baseOffset], and [to] corresponds
+  /// to the [TextSelection.extentOffset].
+  void selectPositionAt({ required Offset from, Offset? to, required SelectionChangedCause cause }) {
+    assert(cause != null);
+    assert(from != null);
+    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
+    if (onSelectionChanged == null) {
+      return;
+    }
+    final TextPosition fromPosition = _textPainter.getPositionForOffset(globalToLocal(from - _paintOffset));
+    final TextPosition? toPosition = to == null
+      ? null
+      : _textPainter.getPositionForOffset(globalToLocal(to - _paintOffset));
+
+    final int baseOffset = fromPosition.offset;
+    final int extentOffset = toPosition?.offset ?? fromPosition.offset;
+
+    final TextSelection newSelection = TextSelection(
+      baseOffset: baseOffset,
+      extentOffset: extentOffset,
+      affinity: fromPosition.affinity,
+    );
+    // Call [onSelectionChanged] only when the selection actually changed.
+    _handleSelectionChange(newSelection, cause);
+  }
+
+  /// Select a word around the location of the last tap down.
+  ///
+  /// {@macro flutter.rendering.RenderEditable.selectPosition}
+  void selectWord({ required SelectionChangedCause cause }) {
+    selectWordsInRange(from: _lastTapDownPosition!, cause: cause);
+  }
+
+  /// Selects the set words of a paragraph in a given range of global positions.
+  ///
+  /// The first and last endpoints of the selection will always be at the
+  /// beginning and end of a word respectively.
+  ///
+  /// {@macro flutter.rendering.RenderEditable.selectPosition}
+  void selectWordsInRange({ required Offset from, Offset? to, required SelectionChangedCause cause }) {
+    assert(cause != null);
+    assert(from != null);
+    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
+    if (onSelectionChanged == null) {
+      return;
+    }
+    final TextPosition firstPosition = _textPainter.getPositionForOffset(globalToLocal(from - _paintOffset));
+    final TextSelection firstWord = _selectWordAtOffset(firstPosition);
+    final TextSelection lastWord = to == null ?
+      firstWord : _selectWordAtOffset(_textPainter.getPositionForOffset(globalToLocal(to - _paintOffset)));
+
+    _handleSelectionChange(
+      TextSelection(
+        baseOffset: firstWord.base.offset,
+        extentOffset: lastWord.extent.offset,
+        affinity: firstWord.affinity,
+      ),
+      cause,
+    );
+  }
+
+  /// Move the selection to the beginning or end of a word.
+  ///
+  /// {@macro flutter.rendering.RenderEditable.selectPosition}
+  void selectWordEdge({ required SelectionChangedCause cause }) {
+    assert(cause != null);
+    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
+    assert(_lastTapDownPosition != null);
+    if (onSelectionChanged == null) {
+      return;
+    }
+    final TextPosition position = _textPainter.getPositionForOffset(globalToLocal(_lastTapDownPosition! - _paintOffset));
+    final TextRange word = _textPainter.getWordBoundary(position);
+    if (position.offset - word.start <= 1) {
+      _handleSelectionChange(
+        TextSelection.collapsed(offset: word.start, affinity: TextAffinity.downstream),
+        cause,
+      );
+    } else {
+      _handleSelectionChange(
+        TextSelection.collapsed(offset: word.end, affinity: TextAffinity.upstream),
+        cause,
+      );
+    }
+  }
+
+  TextSelection _selectWordAtOffset(TextPosition position) {
+    assert(_textLayoutLastMaxWidth == constraints.maxWidth &&
+           _textLayoutLastMinWidth == constraints.minWidth,
+      'Last width ($_textLayoutLastMinWidth, $_textLayoutLastMaxWidth) not the same as max width constraint (${constraints.minWidth}, ${constraints.maxWidth}).');
+    final TextRange word = _textPainter.getWordBoundary(position);
+    // When long-pressing past the end of the text, we want a collapsed cursor.
+    if (position.offset >= word.end)
+      return TextSelection.fromPosition(position);
+    // If text is obscured, the entire sentence should be treated as one word.
+    if (obscureText) {
+      return TextSelection(baseOffset: 0, extentOffset: _plainText.length);
+    // If the word is a space, on iOS try to select the previous word instead.
+    } else if (text?.text != null
+        && _isWhitespace(text!.text!.codeUnitAt(position.offset))
+        && position.offset > 0) {
+      assert(defaultTargetPlatform != null);
+      switch (defaultTargetPlatform) {
+        case TargetPlatform.iOS:
+          int startIndex = position.offset - 1;
+          while (startIndex > 0
+              && (_isWhitespace(text!.text!.codeUnitAt(startIndex))
+              || text!.text! == '\u200e' || text!.text! == '\u200f')) {
+            startIndex--;
+          }
+          if (startIndex > 0) {
+            final TextPosition positionBeforeSpace = TextPosition(
+              offset: startIndex,
+              affinity: position.affinity,
+            );
+            final TextRange wordBeforeSpace = _textPainter.getWordBoundary(
+              positionBeforeSpace,
+            );
+            startIndex = wordBeforeSpace.start;
+          }
+          return TextSelection(
+            baseOffset: startIndex,
+            extentOffset: position.offset,
+          );
+        case TargetPlatform.android:
+        case TargetPlatform.fuchsia:
+        case TargetPlatform.macOS:
+        case TargetPlatform.linux:
+        case TargetPlatform.windows:
+          break;
+      }
+    }
+    return TextSelection(baseOffset: word.start, extentOffset: word.end);
+  }
+
+  TextSelection _selectLineAtOffset(TextPosition position) {
+    assert(_textLayoutLastMaxWidth == constraints.maxWidth &&
+        _textLayoutLastMinWidth == constraints.minWidth,
+    'Last width ($_textLayoutLastMinWidth, $_textLayoutLastMaxWidth) not the same as max width constraint (${constraints.minWidth}, ${constraints.maxWidth}).');
+    final TextRange line = _textPainter.getLineBoundary(position);
+    if (position.offset >= line.end)
+      return TextSelection.fromPosition(position);
+    // If text is obscured, the entire string should be treated as one line.
+    if (obscureText) {
+      return TextSelection(baseOffset: 0, extentOffset: _plainText.length);
+    }
+    return TextSelection(baseOffset: line.start, extentOffset: line.end);
+  }
+
+  void _layoutText({ double minWidth = 0.0, double maxWidth = double.infinity }) {
+    assert(maxWidth != null && minWidth != null);
+    if (_textLayoutLastMaxWidth == maxWidth && _textLayoutLastMinWidth == minWidth)
+      return;
+    final double availableMaxWidth = math.max(0.0, maxWidth - _caretMargin);
+    final double availableMinWidth = math.min(minWidth, availableMaxWidth);
+    final double textMaxWidth = _isMultiline ? availableMaxWidth : double.infinity;
+    final double textMinWidth = forceLine ? availableMaxWidth : availableMinWidth;
+    _textPainter.layout(
+        minWidth: textMinWidth,
+        maxWidth: textMaxWidth,
+    );
+    _textLayoutLastMinWidth = minWidth;
+    _textLayoutLastMaxWidth = maxWidth;
+  }
+
+  late Rect _caretPrototype;
+
+  // TODO(garyq): This is no longer producing the highest-fidelity caret
+  // heights for Android, especially when non-alphabetic languages
+  // are involved. The current implementation overrides the height set
+  // here with the full measured height of the text on Android which looks
+  // superior (subjectively and in terms of fidelity) in _paintCaret. We
+  // should rework this properly to once again match the platform. The constant
+  // _kCaretHeightOffset scales poorly for small font sizes.
+  //
+  /// On iOS, the cursor is taller than the cursor on Android. The height
+  /// of the cursor for iOS is approximate and obtained through an eyeball
+  /// comparison.
+  void _computeCaretPrototype() {
+    assert(defaultTargetPlatform != null);
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        _caretPrototype = Rect.fromLTWH(0.0, 0.0, cursorWidth, cursorHeight + 2);
+        break;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        _caretPrototype = Rect.fromLTWH(0.0, _kCaretHeightOffset, cursorWidth, cursorHeight - 2.0 * _kCaretHeightOffset);
+        break;
+    }
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
+    final double width = forceLine ? constraints.maxWidth : constraints
+        .constrainWidth(_textPainter.size.width + _caretMargin);
+    return Size(width, constraints.constrainHeight(_preferredHeight(constraints.maxWidth)));
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
+    _computeCaretPrototype();
+    _selectionRects = null;
+    // We grab _textPainter.size here because assigning to `size` on the next
+    // line will trigger us to validate our intrinsic sizes, which will change
+    // _textPainter's layout because the intrinsic size calculations are
+    // destructive, which would mean we would get different results if we later
+    // used properties on _textPainter in this method.
+    // Other _textPainter state like didExceedMaxLines will also be affected,
+    // though we currently don't use those here.
+    // See also RenderParagraph which has a similar issue.
+    final Size textPainterSize = _textPainter.size;
+    final double width = forceLine ? constraints.maxWidth : constraints
+        .constrainWidth(_textPainter.size.width + _caretMargin);
+    size = Size(width, constraints.constrainHeight(_preferredHeight(constraints.maxWidth)));
+    final Size contentSize = Size(textPainterSize.width + _caretMargin, textPainterSize.height);
+    _maxScrollExtent = _getMaxScrollExtent(contentSize);
+    offset.applyViewportDimension(_viewportExtent);
+    offset.applyContentDimensions(0.0, _maxScrollExtent);
+  }
+
+  /// Computes the offset to apply to the given [caretRect] so it perfectly
+  /// snaps to physical pixels.
+  Offset _getPixelPerfectCursorOffset(Rect caretRect) {
+    final Offset caretPosition = localToGlobal(caretRect.topLeft);
+    final double pixelMultiple = 1.0 / _devicePixelRatio;
+    final double pixelPerfectOffsetX = caretPosition.dx.isFinite
+      ? (caretPosition.dx / pixelMultiple).round() * pixelMultiple - caretPosition.dx
+      : 0;
+    final double pixelPerfectOffsetY = caretPosition.dy.isFinite
+      ? (caretPosition.dy / pixelMultiple).round() * pixelMultiple - caretPosition.dy
+      : 0;
+    return Offset(pixelPerfectOffsetX, pixelPerfectOffsetY);
+  }
+
+  void _paintCaret(Canvas canvas, Offset effectiveOffset, TextPosition textPosition) {
+    assert(_textLayoutLastMaxWidth == constraints.maxWidth &&
+           _textLayoutLastMinWidth == constraints.minWidth,
+      'Last width ($_textLayoutLastMinWidth, $_textLayoutLastMaxWidth) not the same as max width constraint (${constraints.minWidth}, ${constraints.maxWidth}).');
+    assert(_caretPrototype != null);
+
+    // If the floating cursor is enabled, the text cursor's color is [backgroundCursorColor] while
+    // the floating cursor's color is _cursorColor;
+    final Paint paint = Paint()
+      ..color = (_floatingCursorOn ? backgroundCursorColor : _cursorColor)!;
+    final Offset caretOffset = _textPainter.getOffsetForCaret(textPosition, _caretPrototype) + effectiveOffset;
+    Rect caretRect = _caretPrototype.shift(caretOffset);
+    if (_cursorOffset != null)
+      caretRect = caretRect.shift(_cursorOffset!);
+
+    final double? caretHeight = _textPainter.getFullHeightForCaret(textPosition, _caretPrototype);
+    if (caretHeight != null) {
+      switch (defaultTargetPlatform) {
+        case TargetPlatform.iOS:
+        case TargetPlatform.macOS:
+          final double heightDiff = caretHeight - caretRect.height;
+          // Center the caret vertically along the text.
+          caretRect = Rect.fromLTWH(
+            caretRect.left,
+            caretRect.top + heightDiff / 2,
+            caretRect.width,
+            caretRect.height,
+          );
+          break;
+        case TargetPlatform.android:
+        case TargetPlatform.fuchsia:
+        case TargetPlatform.linux:
+        case TargetPlatform.windows:
+          // Override the height to take the full height of the glyph at the TextPosition
+          // when not on iOS. iOS has special handling that creates a taller caret.
+          // TODO(garyq): See the TODO for _computeCaretPrototype().
+          caretRect = Rect.fromLTWH(
+            caretRect.left,
+            caretRect.top - _kCaretHeightOffset,
+            caretRect.width,
+            caretHeight,
+          );
+          break;
+      }
+    }
+
+    caretRect = caretRect.shift(_getPixelPerfectCursorOffset(caretRect));
+
+    if (cursorRadius == null) {
+      canvas.drawRect(caretRect, paint);
+    } else {
+      final RRect caretRRect = RRect.fromRectAndRadius(caretRect, cursorRadius!);
+      canvas.drawRRect(caretRRect, paint);
+    }
+
+    if (caretRect != _lastCaretRect) {
+      _lastCaretRect = caretRect;
+      if (onCaretChanged != null)
+        onCaretChanged!(caretRect);
+    }
+  }
+
+  /// Sets the screen position of the floating cursor and the text position
+  /// closest to the cursor.
+  void setFloatingCursor(FloatingCursorDragState state, Offset boundedOffset, TextPosition lastTextPosition, { double? resetLerpValue }) {
+    assert(state != null);
+    assert(boundedOffset != null);
+    assert(lastTextPosition != null);
+    if (state == FloatingCursorDragState.Start) {
+      _relativeOrigin = const Offset(0, 0);
+      _previousOffset = null;
+      _resetOriginOnBottom = false;
+      _resetOriginOnTop = false;
+      _resetOriginOnRight = false;
+      _resetOriginOnBottom = false;
+    }
+    _floatingCursorOn = state != FloatingCursorDragState.End;
+    _resetFloatingCursorAnimationValue = resetLerpValue;
+    if (_floatingCursorOn) {
+      _floatingCursorOffset = boundedOffset;
+      _floatingCursorTextPosition = lastTextPosition;
+    }
+    markNeedsPaint();
+  }
+
+  void _paintFloatingCaret(Canvas canvas, Offset effectiveOffset) {
+    assert(_textLayoutLastMaxWidth == constraints.maxWidth &&
+           _textLayoutLastMinWidth == constraints.minWidth,
+      'Last width ($_textLayoutLastMinWidth, $_textLayoutLastMaxWidth) not the same as max width constraint (${constraints.minWidth}, ${constraints.maxWidth}).');
+    assert(_floatingCursorOn);
+
+    // We always want the floating cursor to render at full opacity.
+    final Paint paint = Paint()..color = _cursorColor!.withOpacity(0.75);
+
+    double sizeAdjustmentX = _kFloatingCaretSizeIncrease.dx;
+    double sizeAdjustmentY = _kFloatingCaretSizeIncrease.dy;
+
+    if (_resetFloatingCursorAnimationValue != null) {
+      sizeAdjustmentX = ui.lerpDouble(sizeAdjustmentX, 0, _resetFloatingCursorAnimationValue!)!;
+      sizeAdjustmentY = ui.lerpDouble(sizeAdjustmentY, 0, _resetFloatingCursorAnimationValue!)!;
+    }
+
+    final Rect floatingCaretPrototype = Rect.fromLTRB(
+      _caretPrototype.left - sizeAdjustmentX,
+      _caretPrototype.top - sizeAdjustmentY,
+      _caretPrototype.right + sizeAdjustmentX,
+      _caretPrototype.bottom + sizeAdjustmentY,
+    );
+
+    final Rect caretRect = floatingCaretPrototype.shift(effectiveOffset);
+    const Radius floatingCursorRadius = Radius.circular(_kFloatingCaretRadius);
+    final RRect caretRRect = RRect.fromRectAndRadius(caretRect, floatingCursorRadius);
+    canvas.drawRRect(caretRRect, paint);
+  }
+
+  // The relative origin in relation to the distance the user has theoretically
+  // dragged the floating cursor offscreen. This value is used to account for the
+  // difference in the rendering position and the raw offset value.
+  Offset _relativeOrigin = const Offset(0, 0);
+  Offset? _previousOffset;
+  bool _resetOriginOnLeft = false;
+  bool _resetOriginOnRight = false;
+  bool _resetOriginOnTop = false;
+  bool _resetOriginOnBottom = false;
+  double? _resetFloatingCursorAnimationValue;
+
+  /// Returns the position within the text field closest to the raw cursor offset.
+  Offset calculateBoundedFloatingCursorOffset(Offset rawCursorOffset) {
+    Offset deltaPosition = const Offset(0, 0);
+    final double topBound = -floatingCursorAddedMargin.top;
+    final double bottomBound = _textPainter.height - preferredLineHeight + floatingCursorAddedMargin.bottom;
+    final double leftBound = -floatingCursorAddedMargin.left;
+    final double rightBound = _textPainter.width + floatingCursorAddedMargin.right;
+
+    if (_previousOffset != null)
+      deltaPosition = rawCursorOffset - _previousOffset!;
+
+    // If the raw cursor offset has gone off an edge, we want to reset the relative
+    // origin of the dragging when the user drags back into the field.
+    if (_resetOriginOnLeft && deltaPosition.dx > 0) {
+      _relativeOrigin = Offset(rawCursorOffset.dx - leftBound, _relativeOrigin.dy);
+      _resetOriginOnLeft = false;
+    } else if (_resetOriginOnRight && deltaPosition.dx < 0) {
+      _relativeOrigin = Offset(rawCursorOffset.dx - rightBound, _relativeOrigin.dy);
+      _resetOriginOnRight = false;
+    }
+    if (_resetOriginOnTop && deltaPosition.dy > 0) {
+      _relativeOrigin = Offset(_relativeOrigin.dx, rawCursorOffset.dy - topBound);
+      _resetOriginOnTop = false;
+    } else if (_resetOriginOnBottom && deltaPosition.dy < 0) {
+      _relativeOrigin = Offset(_relativeOrigin.dx, rawCursorOffset.dy - bottomBound);
+      _resetOriginOnBottom = false;
+    }
+
+    final double currentX = rawCursorOffset.dx - _relativeOrigin.dx;
+    final double currentY = rawCursorOffset.dy - _relativeOrigin.dy;
+    final double adjustedX = math.min(math.max(currentX, leftBound), rightBound);
+    final double adjustedY = math.min(math.max(currentY, topBound), bottomBound);
+    final Offset adjustedOffset = Offset(adjustedX, adjustedY);
+
+    if (currentX < leftBound && deltaPosition.dx < 0)
+      _resetOriginOnLeft = true;
+    else if (currentX > rightBound && deltaPosition.dx > 0)
+      _resetOriginOnRight = true;
+    if (currentY < topBound && deltaPosition.dy < 0)
+      _resetOriginOnTop = true;
+    else if (currentY > bottomBound && deltaPosition.dy > 0)
+      _resetOriginOnBottom = true;
+
+    _previousOffset = rawCursorOffset;
+
+    return adjustedOffset;
+  }
+
+  void _paintSelection(Canvas canvas, Offset effectiveOffset) {
+    assert(_textLayoutLastMaxWidth == constraints.maxWidth &&
+           _textLayoutLastMinWidth == constraints.minWidth,
+      'Last width ($_textLayoutLastMinWidth, $_textLayoutLastMaxWidth) not the same as max width constraint (${constraints.minWidth}, ${constraints.maxWidth}).');
+    assert(_selectionRects != null);
+    final Paint paint = Paint()..color = _selectionColor!;
+    for (final ui.TextBox box in _selectionRects!)
+      canvas.drawRect(box.toRect().shift(effectiveOffset), paint);
+  }
+
+  final Paint _promptRectPaint = Paint();
+  void _paintPromptRectIfNeeded(Canvas canvas, Offset effectiveOffset) {
+    if (_promptRectRange == null || promptRectColor == null) {
+      return;
+    }
+
+    final List<TextBox> boxes = _textPainter.getBoxesForSelection(
+      TextSelection(
+        baseOffset: _promptRectRange!.start,
+        extentOffset: _promptRectRange!.end,
+      ),
+    );
+
+    for (final TextBox box in boxes) {
+      canvas.drawRect(box.toRect().shift(effectiveOffset), _promptRectPaint);
+    }
+  }
+
+  void _paintContents(PaintingContext context, Offset offset) {
+    assert(_textLayoutLastMaxWidth == constraints.maxWidth &&
+           _textLayoutLastMinWidth == constraints.minWidth,
+      'Last width ($_textLayoutLastMinWidth, $_textLayoutLastMaxWidth) not the same as max width constraint (${constraints.minWidth}, ${constraints.maxWidth}).');
+    final Offset effectiveOffset = offset + _paintOffset;
+
+    bool showSelection = false;
+    bool showCaret = false;
+
+    if (selection != null && !_floatingCursorOn) {
+      if (selection!.isCollapsed && _showCursor.value && cursorColor != null)
+        showCaret = true;
+      else if (!selection!.isCollapsed && _selectionColor != null)
+        showSelection = true;
+      _updateSelectionExtentsVisibility(effectiveOffset);
+    }
+
+    if (showSelection) {
+      assert(selection != null);
+      _selectionRects ??= _textPainter.getBoxesForSelection(selection!, boxHeightStyle: _selectionHeightStyle, boxWidthStyle: _selectionWidthStyle);
+      _paintSelection(context.canvas, effectiveOffset);
+    }
+
+    _paintPromptRectIfNeeded(context.canvas, effectiveOffset);
+
+    // On iOS, the cursor is painted over the text, on Android, it's painted
+    // under it.
+    if (paintCursorAboveText)
+      _textPainter.paint(context.canvas, effectiveOffset);
+
+    if (showCaret) {
+      assert(selection != null);
+      _paintCaret(context.canvas, effectiveOffset, selection!.extent);
+    }
+
+    if (!paintCursorAboveText)
+      _textPainter.paint(context.canvas, effectiveOffset);
+
+    if (_floatingCursorOn) {
+      if (_resetFloatingCursorAnimationValue == null)
+        _paintCaret(context.canvas, effectiveOffset, _floatingCursorTextPosition);
+      _paintFloatingCaret(context.canvas, _floatingCursorOffset);
+    }
+  }
+
+  void _paintHandleLayers(PaintingContext context, List<TextSelectionPoint> endpoints) {
+    Offset startPoint = endpoints[0].point;
+    startPoint = Offset(
+      startPoint.dx.clamp(0.0, size.width),
+      startPoint.dy.clamp(0.0, size.height),
+    );
+    context.pushLayer(
+      LeaderLayer(link: startHandleLayerLink, offset: startPoint),
+      super.paint,
+      Offset.zero,
+    );
+    if (endpoints.length == 2) {
+      Offset endPoint = endpoints[1].point;
+      endPoint = Offset(
+        endPoint.dx.clamp(0.0, size.width),
+        endPoint.dy.clamp(0.0, size.height),
+      );
+      context.pushLayer(
+        LeaderLayer(link: endHandleLayerLink, offset: endPoint),
+        super.paint,
+        Offset.zero,
+      );
+    }
+  }
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
+    if (_hasVisualOverflow && clipBehavior != Clip.none) {
+      _clipRectLayer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents,
+          clipBehavior: clipBehavior, oldLayer: _clipRectLayer);
+    } else {
+      _clipRectLayer = null;
+      _paintContents(context, offset);
+    }
+    _paintHandleLayers(context, getEndpointsForSelection(selection!));
+  }
+
+  ClipRectLayer? _clipRectLayer;
+
+  @override
+  Rect? describeApproximatePaintClip(RenderObject child) => _hasVisualOverflow ? Offset.zero & size : null;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ColorProperty('cursorColor', cursorColor));
+    properties.add(DiagnosticsProperty<ValueNotifier<bool>>('showCursor', showCursor));
+    properties.add(IntProperty('maxLines', maxLines));
+    properties.add(IntProperty('minLines', minLines));
+    properties.add(DiagnosticsProperty<bool>('expands', expands, defaultValue: false));
+    properties.add(ColorProperty('selectionColor', selectionColor));
+    properties.add(DoubleProperty('textScaleFactor', textScaleFactor));
+    properties.add(DiagnosticsProperty<Locale>('locale', locale, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextSelection>('selection', selection));
+    properties.add(DiagnosticsProperty<ViewportOffset>('offset', offset));
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    return <DiagnosticsNode>[
+      if (text != null)
+        text!.toDiagnosticsNode(
+          name: 'text',
+          style: DiagnosticsTreeStyle.transition,
+        ),
+    ];
+  }
+}
diff --git a/lib/src/rendering/error.dart b/lib/src/rendering/error.dart
new file mode 100644
index 0000000..3edcd9a
--- /dev/null
+++ b/lib/src/rendering/error.dart
@@ -0,0 +1,169 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui show Paragraph, ParagraphBuilder, ParagraphConstraints, ParagraphStyle, TextStyle;
+
+import 'box.dart';
+import 'object.dart';
+
+const double _kMaxWidth = 100000.0;
+const double _kMaxHeight = 100000.0;
+
+/// A render object used as a placeholder when an error occurs.
+///
+/// The box will be painted in the color given by the
+/// [RenderErrorBox.backgroundColor] static property.
+///
+/// A message can be provided. To simplify the class and thus help reduce the
+/// likelihood of this class itself being the source of errors, the message
+/// cannot be changed once the object has been created. If provided, the text
+/// will be painted on top of the background, using the styles given by the
+/// [RenderErrorBox.textStyle] and [RenderErrorBox.paragraphStyle] static
+/// properties.
+///
+/// Again to help simplify the class, if the parent has left the constraints
+/// unbounded, this box tries to be 100000.0 pixels wide and high, to
+/// approximate being infinitely high but without using infinities.
+class RenderErrorBox extends RenderBox {
+  /// Creates a RenderErrorBox render object.
+  ///
+  /// A message can optionally be provided. If a message is provided, an attempt
+  /// will be made to render the message when the box paints.
+  RenderErrorBox([ this.message = '' ]) {
+    try {
+      if (message != '') {
+        // This class is intentionally doing things using the low-level
+        // primitives to avoid depending on any subsystems that may have ended
+        // up in an unstable state -- after all, this class is mainly used when
+        // things have gone wrong.
+        //
+        // Generally, the much better way to draw text in a RenderObject is to
+        // use the TextPainter class. If you're looking for code to crib from,
+        // see the paragraph.dart file and the RenderParagraph class.
+        final ui.ParagraphBuilder builder = ui.ParagraphBuilder(paragraphStyle);
+        builder.pushStyle(textStyle);
+        builder.addText(message);
+        _paragraph = builder.build();
+      } else {
+        _paragraph = null;
+      }
+    } catch (error) {
+      // Intentionally left empty.
+    }
+  }
+
+  /// The message to attempt to display at paint time.
+  final String message;
+
+  // TODO(ianh): should be final
+  ui.Paragraph? _paragraph;
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    return _kMaxWidth;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    return _kMaxHeight;
+  }
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  bool hitTestSelf(Offset position) => true;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return constraints.constrain(const Size(_kMaxWidth, _kMaxHeight));
+  }
+
+  /// The distance to place around the text.
+  ///
+  /// This is intended to ensure that if the [RenderErrorBox] is placed at the top left
+  /// of the screen, under the system's status bar, the error text is still visible in
+  /// the area below the status bar.
+  ///
+  /// The padding is ignored if the error box is smaller than the padding.
+  ///
+  /// See also:
+  ///
+  ///  * [minimumWidth], which controls how wide the box must be before the
+  ///    horizontal padding is applied.
+  static EdgeInsets padding = const EdgeInsets.fromLTRB(64.0, 96.0, 64.0, 12.0);
+
+  /// The width below which the horizontal padding is not applied.
+  ///
+  /// If the left and right padding would reduce the available width to less than
+  /// this value, then the text is rendered flush with the left edge.
+  static double minimumWidth = 200.0;
+
+  /// The color to use when painting the background of [RenderErrorBox] objects.
+  ///
+  /// Defaults to red in debug mode, a light gray otherwise.
+  static Color backgroundColor = _initBackgroundColor();
+
+  static Color _initBackgroundColor() {
+    Color result = const Color(0xF0C0C0C0);
+    assert(() {
+      result = const Color(0xF0900000);
+      return true;
+    }());
+    return result;
+  }
+
+  /// The text style to use when painting [RenderErrorBox] objects.
+  ///
+  /// Defaults to a yellow monospace font in debug mode, and a dark gray
+  /// sans-serif font otherwise.
+  static ui.TextStyle textStyle = _initTextStyle();
+
+  static ui.TextStyle _initTextStyle() {
+    ui.TextStyle result = ui.TextStyle(
+      color: const Color(0xFF303030),
+      fontFamily: 'sans-serif',
+      fontSize: 18.0,
+    );
+    assert(() {
+      result = ui.TextStyle(
+        color: const Color(0xFFFFFF66),
+        fontFamily: 'monospace',
+        fontSize: 14.0,
+        fontWeight: FontWeight.bold,
+      );
+      return true;
+    }());
+    return result;
+  }
+
+  /// The paragraph style to use when painting [RenderErrorBox] objects.
+  static ui.ParagraphStyle paragraphStyle = ui.ParagraphStyle(
+    textDirection: TextDirection.ltr,
+    textAlign: TextAlign.left,
+  );
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    try {
+      context.canvas.drawRect(offset & size, Paint() .. color = backgroundColor);
+      if (_paragraph != null) {
+        double width = size.width;
+        double left = 0.0;
+        double top = 0.0;
+        if (width > padding.left + minimumWidth + padding.right) {
+          width -= padding.left + padding.right;
+          left += padding.left;
+        }
+        _paragraph!.layout(ui.ParagraphConstraints(width: width));
+        if (size.height > padding.top + _paragraph!.height + padding.bottom) {
+          top += padding.top;
+        }
+        context.canvas.drawParagraph(_paragraph!, offset + Offset(left, top));
+      }
+    } catch (e) {
+      // Intentionally left empty.
+    }
+  }
+}
diff --git a/lib/src/rendering/flex.dart b/lib/src/rendering/flex.dart
new file mode 100644
index 0000000..dc93958
--- /dev/null
+++ b/lib/src/rendering/flex.dart
@@ -0,0 +1,1175 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+
+import 'box.dart';
+import 'debug_overflow_indicator.dart';
+import 'layer.dart';
+import 'layout_helper.dart';
+import 'object.dart';
+
+/// How the child is inscribed into the available space.
+///
+/// See also:
+///
+///  * [RenderFlex], the flex render object.
+///  * [Column], [Row], and [Flex], the flex widgets.
+///  * [Expanded], the widget equivalent of [tight].
+///  * [Flexible], the widget equivalent of [loose].
+enum FlexFit {
+  /// The child is forced to fill the available space.
+  ///
+  /// The [Expanded] widget assigns this kind of [FlexFit] to its child.
+  tight,
+
+  /// The child can be at most as large as the available space (but is
+  /// allowed to be smaller).
+  ///
+  /// The [Flexible] widget assigns this kind of [FlexFit] to its child.
+  loose,
+}
+
+/// Parent data for use with [RenderFlex].
+class FlexParentData extends ContainerBoxParentData<RenderBox> {
+  /// The flex factor to use for this child.
+  ///
+  /// If null or zero, the child is inflexible and determines its own size. If
+  /// non-zero, the amount of space the child's can occupy in the main axis is
+  /// determined by dividing the free space (after placing the inflexible
+  /// children) according to the flex factors of the flexible children.
+  int? flex;
+
+  /// How a flexible child is inscribed into the available space.
+  ///
+  /// If [flex] is non-zero, the [fit] determines whether the child fills the
+  /// space the parent makes available during layout. If the fit is
+  /// [FlexFit.tight], the child is required to fill the available space. If the
+  /// fit is [FlexFit.loose], the child can be at most as large as the available
+  /// space (but is allowed to be smaller).
+  FlexFit? fit;
+
+  @override
+  String toString() => '${super.toString()}; flex=$flex; fit=$fit';
+}
+
+/// How much space should be occupied in the main axis.
+///
+/// During a flex layout, available space along the main axis is allocated to
+/// children. After allocating space, there might be some remaining free space.
+/// This value controls whether to maximize or minimize the amount of free
+/// space, subject to the incoming layout constraints.
+///
+/// See also:
+///
+///  * [Column], [Row], and [Flex], the flex widgets.
+///  * [Expanded] and [Flexible], the widgets that controls a flex widgets'
+///    children's flex.
+///  * [RenderFlex], the flex render object.
+///  * [MainAxisAlignment], which controls how the free space is distributed.
+enum MainAxisSize {
+  /// Minimize the amount of free space along the main axis, subject to the
+  /// incoming layout constraints.
+  ///
+  /// If the incoming layout constraints have a large enough
+  /// [BoxConstraints.minWidth] or [BoxConstraints.minHeight], there might still
+  /// be a non-zero amount of free space.
+  ///
+  /// If the incoming layout constraints are unbounded, and any children have a
+  /// non-zero [FlexParentData.flex] and a [FlexFit.tight] fit (as applied by
+  /// [Expanded]), the [RenderFlex] will assert, because there would be infinite
+  /// remaining free space and boxes cannot be given infinite size.
+  min,
+
+  /// Maximize the amount of free space along the main axis, subject to the
+  /// incoming layout constraints.
+  ///
+  /// If the incoming layout constraints have a small enough
+  /// [BoxConstraints.maxWidth] or [BoxConstraints.maxHeight], there might still
+  /// be no free space.
+  ///
+  /// If the incoming layout constraints are unbounded, the [RenderFlex] will
+  /// assert, because there would be infinite remaining free space and boxes
+  /// cannot be given infinite size.
+  max,
+}
+
+/// How the children should be placed along the main axis in a flex layout.
+///
+/// See also:
+///
+///  * [Column], [Row], and [Flex], the flex widgets.
+///  * [RenderFlex], the flex render object.
+enum MainAxisAlignment {
+  /// Place the children as close to the start of the main axis as possible.
+  ///
+  /// If this value is used in a horizontal direction, a [TextDirection] must be
+  /// available to determine if the start is the left or the right.
+  ///
+  /// If this value is used in a vertical direction, a [VerticalDirection] must be
+  /// available to determine if the start is the top or the bottom.
+  start,
+
+  /// Place the children as close to the end of the main axis as possible.
+  ///
+  /// If this value is used in a horizontal direction, a [TextDirection] must be
+  /// available to determine if the end is the left or the right.
+  ///
+  /// If this value is used in a vertical direction, a [VerticalDirection] must be
+  /// available to determine if the end is the top or the bottom.
+  end,
+
+  /// Place the children as close to the middle of the main axis as possible.
+  center,
+
+  /// Place the free space evenly between the children.
+  spaceBetween,
+
+  /// Place the free space evenly between the children as well as half of that
+  /// space before and after the first and last child.
+  spaceAround,
+
+  /// Place the free space evenly between the children as well as before and
+  /// after the first and last child.
+  spaceEvenly,
+}
+
+/// How the children should be placed along the cross axis in a flex layout.
+///
+/// See also:
+///
+///  * [Column], [Row], and [Flex], the flex widgets.
+///  * [RenderFlex], the flex render object.
+enum CrossAxisAlignment {
+  /// Place the children with their start edge aligned with the start side of
+  /// the cross axis.
+  ///
+  /// For example, in a column (a flex with a vertical axis) whose
+  /// [TextDirection] is [TextDirection.ltr], this aligns the left edge of the
+  /// children along the left edge of the column.
+  ///
+  /// If this value is used in a horizontal direction, a [TextDirection] must be
+  /// available to determine if the start is the left or the right.
+  ///
+  /// If this value is used in a vertical direction, a [VerticalDirection] must be
+  /// available to determine if the start is the top or the bottom.
+  start,
+
+  /// Place the children as close to the end of the cross axis as possible.
+  ///
+  /// For example, in a column (a flex with a vertical axis) whose
+  /// [TextDirection] is [TextDirection.ltr], this aligns the right edge of the
+  /// children along the right edge of the column.
+  ///
+  /// If this value is used in a horizontal direction, a [TextDirection] must be
+  /// available to determine if the end is the left or the right.
+  ///
+  /// If this value is used in a vertical direction, a [VerticalDirection] must be
+  /// available to determine if the end is the top or the bottom.
+  end,
+
+  /// Place the children so that their centers align with the middle of the
+  /// cross axis.
+  ///
+  /// This is the default cross-axis alignment.
+  center,
+
+  /// Require the children to fill the cross axis.
+  ///
+  /// This causes the constraints passed to the children to be tight in the
+  /// cross axis.
+  stretch,
+
+  /// Place the children along the cross axis such that their baselines match.
+  ///
+  /// Because baselines are always horizontal, this alignment is intended for
+  /// horizontal main axes. If the main axis is vertical, then this value is
+  /// treated like [start].
+  ///
+  /// For horizontal main axes, if the minimum height constraint passed to the
+  /// flex layout exceeds the intrinsic height of the cross axis, children will
+  /// be aligned as close to the top as they can be while honoring the baseline
+  /// alignment. In other words, the extra space will be below all the children.
+  ///
+  /// Children who report no baseline will be top-aligned.
+  baseline,
+}
+
+bool? _startIsTopLeft(Axis direction, TextDirection? textDirection, VerticalDirection? verticalDirection) {
+  assert(direction != null);
+  // If the relevant value of textDirection or verticalDirection is null, this returns null too.
+  switch (direction) {
+    case Axis.horizontal:
+      switch (textDirection) {
+        case TextDirection.ltr:
+          return true;
+        case TextDirection.rtl:
+          return false;
+        case null:
+          return null;
+      }
+    case Axis.vertical:
+      switch (verticalDirection) {
+        case VerticalDirection.down:
+          return true;
+        case VerticalDirection.up:
+          return false;
+        case null:
+          return null;
+      }
+  }
+}
+
+typedef _ChildSizingFunction = double Function(RenderBox child, double extent);
+
+/// Displays its children in a one-dimensional array.
+///
+/// ## Layout algorithm
+///
+/// _This section describes how the framework causes [RenderFlex] to position
+/// its children._
+/// _See [BoxConstraints] for an introduction to box layout models._
+///
+/// Layout for a [RenderFlex] proceeds in six steps:
+///
+/// 1. Layout each child a null or zero flex factor with unbounded main axis
+///    constraints and the incoming cross axis constraints. If the
+///    [crossAxisAlignment] is [CrossAxisAlignment.stretch], instead use tight
+///    cross axis constraints that match the incoming max extent in the cross
+///    axis.
+/// 2. Divide the remaining main axis space among the children with non-zero
+///    flex factors according to their flex factor. For example, a child with a
+///    flex factor of 2.0 will receive twice the amount of main axis space as a
+///    child with a flex factor of 1.0.
+/// 3. Layout each of the remaining children with the same cross axis
+///    constraints as in step 1, but instead of using unbounded main axis
+///    constraints, use max axis constraints based on the amount of space
+///    allocated in step 2. Children with [Flexible.fit] properties that are
+///    [FlexFit.tight] are given tight constraints (i.e., forced to fill the
+///    allocated space), and children with [Flexible.fit] properties that are
+///    [FlexFit.loose] are given loose constraints (i.e., not forced to fill the
+///    allocated space).
+/// 4. The cross axis extent of the [RenderFlex] is the maximum cross axis
+///    extent of the children (which will always satisfy the incoming
+///    constraints).
+/// 5. The main axis extent of the [RenderFlex] is determined by the
+///    [mainAxisSize] property. If the [mainAxisSize] property is
+///    [MainAxisSize.max], then the main axis extent of the [RenderFlex] is the
+///    max extent of the incoming main axis constraints. If the [mainAxisSize]
+///    property is [MainAxisSize.min], then the main axis extent of the [Flex]
+///    is the sum of the main axis extents of the children (subject to the
+///    incoming constraints).
+/// 6. Determine the position for each child according to the
+///    [mainAxisAlignment] and the [crossAxisAlignment]. For example, if the
+///    [mainAxisAlignment] is [MainAxisAlignment.spaceBetween], any main axis
+///    space that has not been allocated to children is divided evenly and
+///    placed between the children.
+///
+/// See also:
+///
+///  * [Flex], the widget equivalent.
+///  * [Row] and [Column], direction-specific variants of [Flex].
+class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, FlexParentData>,
+                                        RenderBoxContainerDefaultsMixin<RenderBox, FlexParentData>,
+                                        DebugOverflowIndicatorMixin {
+  /// Creates a flex render object.
+  ///
+  /// By default, the flex layout is horizontal and children are aligned to the
+  /// start of the main axis and the center of the cross axis.
+  RenderFlex({
+    List<RenderBox>? children,
+    Axis direction = Axis.horizontal,
+    MainAxisSize mainAxisSize = MainAxisSize.max,
+    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
+    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
+    TextDirection? textDirection,
+    VerticalDirection verticalDirection = VerticalDirection.down,
+    TextBaseline? textBaseline,
+    Clip clipBehavior = Clip.none,
+  }) : assert(direction != null),
+       assert(mainAxisAlignment != null),
+       assert(mainAxisSize != null),
+       assert(crossAxisAlignment != null),
+       assert(clipBehavior != null),
+       _direction = direction,
+       _mainAxisAlignment = mainAxisAlignment,
+       _mainAxisSize = mainAxisSize,
+       _crossAxisAlignment = crossAxisAlignment,
+       _textDirection = textDirection,
+       _verticalDirection = verticalDirection,
+       _textBaseline = textBaseline,
+       _clipBehavior = clipBehavior {
+    addAll(children);
+  }
+
+  /// The direction to use as the main axis.
+  Axis get direction => _direction;
+  Axis _direction;
+  set direction(Axis value) {
+    assert(value != null);
+    if (_direction != value) {
+      _direction = value;
+      markNeedsLayout();
+    }
+  }
+
+  /// How the children should be placed along the main axis.
+  ///
+  /// If the [direction] is [Axis.horizontal], and the [mainAxisAlignment] is
+  /// either [MainAxisAlignment.start] or [MainAxisAlignment.end], then the
+  /// [textDirection] must not be null.
+  ///
+  /// If the [direction] is [Axis.vertical], and the [mainAxisAlignment] is
+  /// either [MainAxisAlignment.start] or [MainAxisAlignment.end], then the
+  /// [verticalDirection] must not be null.
+  MainAxisAlignment get mainAxisAlignment => _mainAxisAlignment;
+  MainAxisAlignment _mainAxisAlignment;
+  set mainAxisAlignment(MainAxisAlignment value) {
+    assert(value != null);
+    if (_mainAxisAlignment != value) {
+      _mainAxisAlignment = value;
+      markNeedsLayout();
+    }
+  }
+
+  /// How much space should be occupied in the main axis.
+  ///
+  /// After allocating space to children, there might be some remaining free
+  /// space. This value controls whether to maximize or minimize the amount of
+  /// free space, subject to the incoming layout constraints.
+  ///
+  /// If some children have a non-zero flex factors (and none have a fit of
+  /// [FlexFit.loose]), they will expand to consume all the available space and
+  /// there will be no remaining free space to maximize or minimize, making this
+  /// value irrelevant to the final layout.
+  MainAxisSize get mainAxisSize => _mainAxisSize;
+  MainAxisSize _mainAxisSize;
+  set mainAxisSize(MainAxisSize value) {
+    assert(value != null);
+    if (_mainAxisSize != value) {
+      _mainAxisSize = value;
+      markNeedsLayout();
+    }
+  }
+
+  /// How the children should be placed along the cross axis.
+  ///
+  /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is
+  /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
+  /// [verticalDirection] must not be null.
+  ///
+  /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is
+  /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
+  /// [textDirection] must not be null.
+  CrossAxisAlignment get crossAxisAlignment => _crossAxisAlignment;
+  CrossAxisAlignment _crossAxisAlignment;
+  set crossAxisAlignment(CrossAxisAlignment value) {
+    assert(value != null);
+    if (_crossAxisAlignment != value) {
+      _crossAxisAlignment = value;
+      markNeedsLayout();
+    }
+  }
+
+  /// Determines the order to lay children out horizontally and how to interpret
+  /// `start` and `end` in the horizontal direction.
+  ///
+  /// If the [direction] is [Axis.horizontal], this controls the order in which
+  /// children are positioned (left-to-right or right-to-left), and the meaning
+  /// of the [mainAxisAlignment] property's [MainAxisAlignment.start] and
+  /// [MainAxisAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.horizontal], and either the
+  /// [mainAxisAlignment] is either [MainAxisAlignment.start] or
+  /// [MainAxisAlignment.end], or there's more than one child, then the
+  /// [textDirection] must not be null.
+  ///
+  /// If the [direction] is [Axis.vertical], this controls the meaning of the
+  /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and
+  /// [CrossAxisAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is
+  /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
+  /// [textDirection] must not be null.
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection;
+  set textDirection(TextDirection? value) {
+    if (_textDirection != value) {
+      _textDirection = value;
+      markNeedsLayout();
+    }
+  }
+
+  /// Determines the order to lay children out vertically and how to interpret
+  /// `start` and `end` in the vertical direction.
+  ///
+  /// If the [direction] is [Axis.vertical], this controls which order children
+  /// are painted in (down or up), the meaning of the [mainAxisAlignment]
+  /// property's [MainAxisAlignment.start] and [MainAxisAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.vertical], and either the [mainAxisAlignment]
+  /// is either [MainAxisAlignment.start] or [MainAxisAlignment.end], or there's
+  /// more than one child, then the [verticalDirection] must not be null.
+  ///
+  /// If the [direction] is [Axis.horizontal], this controls the meaning of the
+  /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and
+  /// [CrossAxisAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is
+  /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
+  /// [verticalDirection] must not be null.
+  VerticalDirection get verticalDirection => _verticalDirection;
+  VerticalDirection _verticalDirection;
+  set verticalDirection(VerticalDirection value) {
+    if (_verticalDirection != value) {
+      _verticalDirection = value;
+      markNeedsLayout();
+    }
+  }
+
+  /// If aligning items according to their baseline, which baseline to use.
+  ///
+  /// Must not be null if [crossAxisAlignment] is [CrossAxisAlignment.baseline].
+  TextBaseline? get textBaseline => _textBaseline;
+  TextBaseline? _textBaseline;
+  set textBaseline(TextBaseline? value) {
+    assert(_crossAxisAlignment != CrossAxisAlignment.baseline || value != null);
+    if (_textBaseline != value) {
+      _textBaseline = value;
+      markNeedsLayout();
+    }
+  }
+
+  bool get _debugHasNecessaryDirections {
+    assert(direction != null);
+    assert(crossAxisAlignment != null);
+    if (firstChild != null && lastChild != firstChild) {
+      // i.e. there's more than one child
+      switch (direction) {
+        case Axis.horizontal:
+          assert(textDirection != null, 'Horizontal $runtimeType with multiple children has a null textDirection, so the layout order is undefined.');
+          break;
+        case Axis.vertical:
+          assert(verticalDirection != null, 'Vertical $runtimeType with multiple children has a null verticalDirection, so the layout order is undefined.');
+          break;
+      }
+    }
+    if (mainAxisAlignment == MainAxisAlignment.start ||
+        mainAxisAlignment == MainAxisAlignment.end) {
+      switch (direction) {
+        case Axis.horizontal:
+          assert(textDirection != null, 'Horizontal $runtimeType with $mainAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
+          break;
+        case Axis.vertical:
+          assert(verticalDirection != null, 'Vertical $runtimeType with $mainAxisAlignment has a null verticalDirection, so the alignment cannot be resolved.');
+          break;
+      }
+    }
+    if (crossAxisAlignment == CrossAxisAlignment.start ||
+        crossAxisAlignment == CrossAxisAlignment.end) {
+      switch (direction) {
+        case Axis.horizontal:
+          assert(verticalDirection != null, 'Horizontal $runtimeType with $crossAxisAlignment has a null verticalDirection, so the alignment cannot be resolved.');
+          break;
+        case Axis.vertical:
+          assert(textDirection != null, 'Vertical $runtimeType with $crossAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
+          break;
+      }
+    }
+    return true;
+  }
+
+  // Set during layout if overflow occurred on the main axis.
+  double? _overflow;
+  // Check whether any meaningful overflow is present. Values below an epsilon
+  // are treated as not overflowing.
+  bool get _hasOverflow => _overflow! > precisionErrorTolerance;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior = Clip.none;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! FlexParentData)
+      child.parentData = FlexParentData();
+  }
+
+  bool get _canComputeIntrinsics => crossAxisAlignment != CrossAxisAlignment.baseline;
+
+  double _getIntrinsicSize({
+    required Axis sizingDirection,
+    required double extent, // the extent in the direction that isn't the sizing direction
+    required _ChildSizingFunction childSize, // a method to find the size in the sizing direction
+  }) {
+    if (!_canComputeIntrinsics) {
+      // Intrinsics cannot be calculated without a full layout for
+      // baseline alignment. Throw an assertion and return 0.0 as documented
+      // on [RenderBox.computeMinIntrinsicWidth].
+      assert(
+        RenderObject.debugCheckingIntrinsics,
+        'Intrinsics are not available for CrossAxisAlignment.baseline.'
+      );
+      return 0.0;
+    }
+    if (_direction == sizingDirection) {
+      // INTRINSIC MAIN SIZE
+      // Intrinsic main size is the smallest size the flex container can take
+      // while maintaining the min/max-content contributions of its flex items.
+      double totalFlex = 0.0;
+      double inflexibleSpace = 0.0;
+      double maxFlexFractionSoFar = 0.0;
+      RenderBox? child = firstChild;
+      while (child != null) {
+        final int flex = _getFlex(child);
+        totalFlex += flex;
+        if (flex > 0) {
+          final double flexFraction = childSize(child, extent) / _getFlex(child);
+          maxFlexFractionSoFar = math.max(maxFlexFractionSoFar, flexFraction);
+        } else {
+          inflexibleSpace += childSize(child, extent);
+        }
+        final FlexParentData childParentData = child.parentData! as FlexParentData;
+        child = childParentData.nextSibling;
+      }
+      return maxFlexFractionSoFar * totalFlex + inflexibleSpace;
+    } else {
+      // INTRINSIC CROSS SIZE
+      // Intrinsic cross size is the max of the intrinsic cross sizes of the
+      // children, after the flexible children are fit into the available space,
+      // with the children sized using their max intrinsic dimensions.
+
+      // Get inflexible space using the max intrinsic dimensions of fixed children in the main direction.
+      final double availableMainSpace = extent;
+      int totalFlex = 0;
+      double inflexibleSpace = 0.0;
+      double maxCrossSize = 0.0;
+      RenderBox? child = firstChild;
+      while (child != null) {
+        final int flex = _getFlex(child);
+        totalFlex += flex;
+        late final double mainSize;
+        late final double crossSize;
+        if (flex == 0) {
+          switch (_direction) {
+            case Axis.horizontal:
+              mainSize = child.getMaxIntrinsicWidth(double.infinity);
+              crossSize = childSize(child, mainSize);
+              break;
+            case Axis.vertical:
+              mainSize = child.getMaxIntrinsicHeight(double.infinity);
+              crossSize = childSize(child, mainSize);
+              break;
+          }
+          inflexibleSpace += mainSize;
+          maxCrossSize = math.max(maxCrossSize, crossSize);
+        }
+        final FlexParentData childParentData = child.parentData! as FlexParentData;
+        child = childParentData.nextSibling;
+      }
+
+      // Determine the spacePerFlex by allocating the remaining available space.
+      // When you're overconstrained spacePerFlex can be negative.
+      final double spacePerFlex = math.max(0.0,
+          (availableMainSpace - inflexibleSpace) / totalFlex);
+
+      // Size remaining (flexible) items, find the maximum cross size.
+      child = firstChild;
+      while (child != null) {
+        final int flex = _getFlex(child);
+        if (flex > 0)
+          maxCrossSize = math.max(maxCrossSize, childSize(child, spacePerFlex * flex));
+        final FlexParentData childParentData = child.parentData! as FlexParentData;
+        child = childParentData.nextSibling;
+      }
+
+      return maxCrossSize;
+    }
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    return _getIntrinsicSize(
+      sizingDirection: Axis.horizontal,
+      extent: height,
+      childSize: (RenderBox child, double extent) => child.getMinIntrinsicWidth(extent),
+    );
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    return _getIntrinsicSize(
+      sizingDirection: Axis.horizontal,
+      extent: height,
+      childSize: (RenderBox child, double extent) => child.getMaxIntrinsicWidth(extent),
+    );
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    return _getIntrinsicSize(
+      sizingDirection: Axis.vertical,
+      extent: width,
+      childSize: (RenderBox child, double extent) => child.getMinIntrinsicHeight(extent),
+    );
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    return _getIntrinsicSize(
+      sizingDirection: Axis.vertical,
+      extent: width,
+      childSize: (RenderBox child, double extent) => child.getMaxIntrinsicHeight(extent),
+    );
+  }
+
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    if (_direction == Axis.horizontal)
+      return defaultComputeDistanceToHighestActualBaseline(baseline);
+    return defaultComputeDistanceToFirstActualBaseline(baseline);
+  }
+
+  int _getFlex(RenderBox child) {
+    final FlexParentData childParentData = child.parentData! as FlexParentData;
+    return childParentData.flex ?? 0;
+  }
+
+  FlexFit _getFit(RenderBox child) {
+    final FlexParentData childParentData = child.parentData! as FlexParentData;
+    return childParentData.fit ?? FlexFit.tight;
+  }
+
+  double _getCrossSize(Size size) {
+    switch (_direction) {
+      case Axis.horizontal:
+        return size.height;
+      case Axis.vertical:
+        return size.width;
+    }
+  }
+
+  double _getMainSize(Size size) {
+    switch (_direction) {
+      case Axis.horizontal:
+        return size.width;
+      case Axis.vertical:
+        return size.height;
+    }
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (!_canComputeIntrinsics) {
+      assert(debugCannotComputeDryLayout(
+        reason: 'Dry layout cannot be computed for CrossAxisAlignment.baseline, which requires a full layout.'
+      ));
+      return const Size(0, 0);
+    }
+    FlutterError? constraintsError;
+    assert(() {
+      constraintsError = _debugCheckConstraints(
+        constraints: constraints,
+        reportParentConstraints: false,
+      );
+      return true;
+    }());
+    if (constraintsError != null) {
+      assert(debugCannotComputeDryLayout(error: constraintsError));
+      return const Size(0, 0);
+    }
+
+    final _LayoutSizes sizes = _computeSizes(
+      layoutChild: ChildLayoutHelper.dryLayoutChild,
+      constraints: constraints,
+    );
+
+    switch (_direction) {
+      case Axis.horizontal:
+        return constraints.constrain(Size(sizes.mainSize, sizes.crossSize));
+      case Axis.vertical:
+        return constraints.constrain(Size(sizes.crossSize, sizes.mainSize));
+    }
+  }
+
+  FlutterError? _debugCheckConstraints({required BoxConstraints constraints, required bool reportParentConstraints}) {
+    FlutterError? result;
+    assert(() {
+      final double maxMainSize = _direction == Axis.horizontal ? constraints.maxWidth : constraints.maxHeight;
+      final bool canFlex = maxMainSize < double.infinity;
+      RenderBox? child = firstChild;
+      while (child != null) {
+        final int flex = _getFlex(child);
+        if (flex > 0) {
+          final String identity = _direction == Axis.horizontal ? 'row' : 'column';
+          final String axis = _direction == Axis.horizontal ? 'horizontal' : 'vertical';
+          final String dimension = _direction == Axis.horizontal ? 'width' : 'height';
+          DiagnosticsNode error, message;
+          final List<DiagnosticsNode> addendum = <DiagnosticsNode>[];
+          if (!canFlex && (mainAxisSize == MainAxisSize.max || _getFit(child) == FlexFit.tight)) {
+            error = ErrorSummary('RenderFlex children have non-zero flex but incoming $dimension constraints are unbounded.');
+            message = ErrorDescription(
+              'When a $identity is in a parent that does not provide a finite $dimension constraint, for example '
+              'if it is in a $axis scrollable, it will try to shrink-wrap its children along the $axis '
+              'axis. Setting a flex on a child (e.g. using Expanded) indicates that the child is to '
+              'expand to fill the remaining space in the $axis direction.'
+            );
+            if (reportParentConstraints) { // Constraints of parents are unavailable in dry layout.
+              RenderBox? node = this;
+              switch (_direction) {
+                case Axis.horizontal:
+                  while (!node!.constraints.hasBoundedWidth && node.parent is RenderBox)
+                    node = node.parent! as RenderBox;
+                  if (!node.constraints.hasBoundedWidth)
+                    node = null;
+                  break;
+                case Axis.vertical:
+                  while (!node!.constraints.hasBoundedHeight && node.parent is RenderBox)
+                    node = node.parent! as RenderBox;
+                  if (!node.constraints.hasBoundedHeight)
+                    node = null;
+                  break;
+              }
+              if (node != null) {
+                addendum.add(node.describeForError('The nearest ancestor providing an unbounded width constraint is'));
+              }
+            }
+            addendum.add(ErrorHint('See also: https://flutter.dev/layout/'));
+          } else {
+            return true;
+          }
+          result = FlutterError.fromParts(<DiagnosticsNode>[
+            error,
+            message,
+            ErrorDescription(
+              'These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child '
+              'cannot simultaneously expand to fit its parent.'
+            ),
+            ErrorHint(
+              'Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose fits for the flexible '
+              'children (using Flexible rather than Expanded). This will allow the flexible children '
+              'to size themselves to less than the infinite remaining space they would otherwise be '
+              'forced to take, and then will cause the RenderFlex to shrink-wrap the children '
+              'rather than expanding to fit the maximum constraints provided by the parent.'
+            ),
+            ErrorDescription(
+              'If this message did not help you determine the problem, consider using debugDumpRenderTree():\n'
+              '  https://flutter.dev/debugging/#rendering-layer\n'
+              '  http://api.flutter.dev/flutter/rendering/debugDumpRenderTree.html'
+            ),
+            describeForError('The affected RenderFlex is', style: DiagnosticsTreeStyle.errorProperty),
+            DiagnosticsProperty<dynamic>('The creator information is set to', debugCreator, style: DiagnosticsTreeStyle.errorProperty),
+            ...addendum,
+            ErrorDescription(
+              "If none of the above helps enough to fix this problem, please don't hesitate to file a bug:\n"
+              '  https://github.com/flutter/flutter/issues/new?template=2_bug.md'
+            ),
+          ]);
+          return true;
+        }
+        child = childAfter(child);
+      }
+      return true;
+    }());
+    return result;
+  }
+
+  _LayoutSizes _computeSizes({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
+    assert(_debugHasNecessaryDirections);
+    assert(constraints != null);
+
+    // Determine used flex factor, size inflexible items, calculate free space.
+    int totalFlex = 0;
+    final double maxMainSize = _direction == Axis.horizontal ? constraints.maxWidth : constraints.maxHeight;
+    final bool canFlex = maxMainSize < double.infinity;
+
+    double crossSize = 0.0;
+    double allocatedSize = 0.0; // Sum of the sizes of the non-flexible children.
+    RenderBox? child = firstChild;
+    RenderBox? lastFlexChild;
+    while (child != null) {
+      final FlexParentData childParentData = child.parentData! as FlexParentData;
+      final int flex = _getFlex(child);
+      if (flex > 0) {
+        totalFlex += flex;
+        lastFlexChild = child;
+      } else {
+        final BoxConstraints innerConstraints;
+        if (crossAxisAlignment == CrossAxisAlignment.stretch) {
+          switch (_direction) {
+            case Axis.horizontal:
+              innerConstraints = BoxConstraints.tightFor(height: constraints.maxHeight);
+              break;
+            case Axis.vertical:
+              innerConstraints = BoxConstraints.tightFor(width: constraints.maxWidth);
+              break;
+          }
+        } else {
+          switch (_direction) {
+            case Axis.horizontal:
+              innerConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
+              break;
+            case Axis.vertical:
+              innerConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
+              break;
+          }
+        }
+        final Size childSize = layoutChild(child, innerConstraints);
+        allocatedSize += _getMainSize(childSize);
+        crossSize = math.max(crossSize, _getCrossSize(childSize));
+      }
+      assert(child.parentData == childParentData);
+      child = childParentData.nextSibling;
+    }
+
+    // Distribute free space to flexible children.
+    final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
+    double allocatedFlexSpace = 0.0;
+    if (totalFlex > 0) {
+      final double spacePerFlex = canFlex ? (freeSpace / totalFlex) : double.nan;
+      child = firstChild;
+      while (child != null) {
+        final int flex = _getFlex(child);
+        if (flex > 0) {
+          final double maxChildExtent = canFlex ? (child == lastFlexChild ? (freeSpace - allocatedFlexSpace) : spacePerFlex * flex) : double.infinity;
+          late final double minChildExtent;
+          switch (_getFit(child)) {
+            case FlexFit.tight:
+              assert(maxChildExtent < double.infinity);
+              minChildExtent = maxChildExtent;
+              break;
+            case FlexFit.loose:
+              minChildExtent = 0.0;
+              break;
+          }
+          assert(minChildExtent != null);
+          final BoxConstraints innerConstraints;
+          if (crossAxisAlignment == CrossAxisAlignment.stretch) {
+            switch (_direction) {
+              case Axis.horizontal:
+                innerConstraints = BoxConstraints(
+                  minWidth: minChildExtent,
+                  maxWidth: maxChildExtent,
+                  minHeight: constraints.maxHeight,
+                  maxHeight: constraints.maxHeight,
+                );
+                break;
+              case Axis.vertical:
+                innerConstraints = BoxConstraints(
+                  minWidth: constraints.maxWidth,
+                  maxWidth: constraints.maxWidth,
+                  minHeight: minChildExtent,
+                  maxHeight: maxChildExtent,
+                );
+                break;
+            }
+          } else {
+            switch (_direction) {
+              case Axis.horizontal:
+                innerConstraints = BoxConstraints(
+                  minWidth: minChildExtent,
+                  maxWidth: maxChildExtent,
+                  maxHeight: constraints.maxHeight,
+                );
+                break;
+              case Axis.vertical:
+                innerConstraints = BoxConstraints(
+                  maxWidth: constraints.maxWidth,
+                  minHeight: minChildExtent,
+                  maxHeight: maxChildExtent,
+                );
+                break;
+            }
+          }
+          final Size childSize = layoutChild(child, innerConstraints);
+          final double childMainSize = _getMainSize(childSize);
+          assert(childMainSize <= maxChildExtent);
+          allocatedSize += childMainSize;
+          allocatedFlexSpace += maxChildExtent;
+          crossSize = math.max(crossSize, _getCrossSize(childSize));
+        }
+        final FlexParentData childParentData = child.parentData! as FlexParentData;
+        child = childParentData.nextSibling;
+      }
+    }
+
+    final double idealSize = canFlex && mainAxisSize == MainAxisSize.max ? maxMainSize : allocatedSize;
+    return _LayoutSizes(
+      mainSize: idealSize,
+      crossSize: crossSize,
+      allocatedSize: allocatedSize,
+    );
+  }
+
+  @override
+  void performLayout() {
+    assert(_debugHasNecessaryDirections);
+    final BoxConstraints constraints = this.constraints;
+    assert(() {
+      final FlutterError? constraintsError = _debugCheckConstraints(
+        constraints: constraints,
+        reportParentConstraints: true,
+      );
+      if (constraintsError != null) {
+        throw constraintsError;
+      }
+      return true;
+    }());
+
+    final _LayoutSizes sizes = _computeSizes(
+      layoutChild: ChildLayoutHelper.layoutChild,
+      constraints: constraints,
+    );
+
+    final double allocatedSize = sizes.allocatedSize;
+    double actualSize = sizes.mainSize;
+    double crossSize = sizes.crossSize;
+    double maxBaselineDistance = 0.0;
+    if (crossAxisAlignment == CrossAxisAlignment.baseline) {
+      RenderBox? child = firstChild;
+      double maxSizeAboveBaseline = 0;
+      double maxSizeBelowBaseline = 0;
+      while (child != null) {
+        assert(() {
+          if (textBaseline == null)
+            throw FlutterError('To use FlexAlignItems.baseline, you must also specify which baseline to use using the "baseline" argument.');
+          return true;
+        }());
+        final double? distance = child.getDistanceToBaseline(textBaseline!, onlyReal: true);
+        if (distance != null) {
+          maxBaselineDistance = math.max(maxBaselineDistance, distance);
+          maxSizeAboveBaseline = math.max(
+            distance,
+            maxSizeAboveBaseline,
+          );
+          maxSizeBelowBaseline = math.max(
+            child.size.height - distance,
+            maxSizeBelowBaseline,
+          );
+          crossSize = math.max(maxSizeAboveBaseline + maxSizeBelowBaseline, crossSize);
+        }
+        final FlexParentData childParentData = child.parentData! as FlexParentData;
+        child = childParentData.nextSibling;
+      }
+    }
+
+    // Align items along the main axis.
+    switch (_direction) {
+      case Axis.horizontal:
+        size = constraints.constrain(Size(actualSize, crossSize));
+        actualSize = size.width;
+        crossSize = size.height;
+        break;
+      case Axis.vertical:
+        size = constraints.constrain(Size(crossSize, actualSize));
+        actualSize = size.height;
+        crossSize = size.width;
+        break;
+    }
+    final double actualSizeDelta = actualSize - allocatedSize;
+    _overflow = math.max(0.0, -actualSizeDelta);
+    final double remainingSpace = math.max(0.0, actualSizeDelta);
+    late final double leadingSpace;
+    late final double betweenSpace;
+    // flipMainAxis is used to decide whether to lay out left-to-right/top-to-bottom (false), or
+    // right-to-left/bottom-to-top (true). The _startIsTopLeft will return null if there's only
+    // one child and the relevant direction is null, in which case we arbitrarily decide not to
+    // flip, but that doesn't have any detectable effect.
+    final bool flipMainAxis = !(_startIsTopLeft(direction, textDirection, verticalDirection) ?? true);
+    switch (_mainAxisAlignment) {
+      case MainAxisAlignment.start:
+        leadingSpace = 0.0;
+        betweenSpace = 0.0;
+        break;
+      case MainAxisAlignment.end:
+        leadingSpace = remainingSpace;
+        betweenSpace = 0.0;
+        break;
+      case MainAxisAlignment.center:
+        leadingSpace = remainingSpace / 2.0;
+        betweenSpace = 0.0;
+        break;
+      case MainAxisAlignment.spaceBetween:
+        leadingSpace = 0.0;
+        betweenSpace = childCount > 1 ? remainingSpace / (childCount - 1) : 0.0;
+        break;
+      case MainAxisAlignment.spaceAround:
+        betweenSpace = childCount > 0 ? remainingSpace / childCount : 0.0;
+        leadingSpace = betweenSpace / 2.0;
+        break;
+      case MainAxisAlignment.spaceEvenly:
+        betweenSpace = childCount > 0 ? remainingSpace / (childCount + 1) : 0.0;
+        leadingSpace = betweenSpace;
+        break;
+    }
+
+    // Position elements
+    double childMainPosition = flipMainAxis ? actualSize - leadingSpace : leadingSpace;
+    RenderBox? child = firstChild;
+    while (child != null) {
+      final FlexParentData childParentData = child.parentData! as FlexParentData;
+      final double childCrossPosition;
+      switch (_crossAxisAlignment) {
+        case CrossAxisAlignment.start:
+        case CrossAxisAlignment.end:
+          childCrossPosition = _startIsTopLeft(flipAxis(direction), textDirection, verticalDirection)
+                               == (_crossAxisAlignment == CrossAxisAlignment.start)
+                             ? 0.0
+                             : crossSize - _getCrossSize(child.size);
+          break;
+        case CrossAxisAlignment.center:
+          childCrossPosition = crossSize / 2.0 - _getCrossSize(child.size) / 2.0;
+          break;
+        case CrossAxisAlignment.stretch:
+          childCrossPosition = 0.0;
+          break;
+        case CrossAxisAlignment.baseline:
+          if (_direction == Axis.horizontal) {
+            assert(textBaseline != null);
+            final double? distance = child.getDistanceToBaseline(textBaseline!, onlyReal: true);
+            if (distance != null)
+              childCrossPosition = maxBaselineDistance - distance;
+            else
+              childCrossPosition = 0.0;
+          } else {
+            childCrossPosition = 0.0;
+          }
+          break;
+      }
+      if (flipMainAxis)
+        childMainPosition -= _getMainSize(child.size);
+      switch (_direction) {
+        case Axis.horizontal:
+          childParentData.offset = Offset(childMainPosition, childCrossPosition);
+          break;
+        case Axis.vertical:
+          childParentData.offset = Offset(childCrossPosition, childMainPosition);
+          break;
+      }
+      if (flipMainAxis) {
+        childMainPosition -= betweenSpace;
+      } else {
+        childMainPosition += _getMainSize(child.size) + betweenSpace;
+      }
+      child = childParentData.nextSibling;
+    }
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    return defaultHitTestChildren(result, position: position);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (!_hasOverflow) {
+      defaultPaint(context, offset);
+      return;
+    }
+
+    // There's no point in drawing the children if we're empty.
+    if (size.isEmpty)
+      return;
+
+    if (clipBehavior == Clip.none) {
+      _clipRectLayer = null;
+      defaultPaint(context, offset);
+    } else {
+      // We have overflow and the clipBehavior isn't none. Clip it.
+      _clipRectLayer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint,
+          clipBehavior: clipBehavior, oldLayer: _clipRectLayer);
+    }
+
+    assert(() {
+      // Only set this if it's null to save work. It gets reset to null if the
+      // _direction changes.
+      final List<DiagnosticsNode> debugOverflowHints = <DiagnosticsNode>[
+        ErrorDescription(
+          'The overflowing $runtimeType has an orientation of $_direction.'
+        ),
+        ErrorDescription(
+          'The edge of the $runtimeType that is overflowing has been marked '
+          'in the rendering with a yellow and black striped pattern. This is '
+          'usually caused by the contents being too big for the $runtimeType.'
+        ),
+        ErrorHint(
+          'Consider applying a flex factor (e.g. using an Expanded widget) to '
+          'force the children of the $runtimeType to fit within the available '
+          'space instead of being sized to their natural size.'
+        ),
+        ErrorHint(
+          'This is considered an error condition because it indicates that there '
+          'is content that cannot be seen. If the content is legitimately bigger '
+          'than the available space, consider clipping it with a ClipRect widget '
+          'before putting it in the flex, or using a scrollable container rather '
+          'than a Flex, like a ListView.'
+        ),
+      ];
+
+      // Simulate a child rect that overflows by the right amount. This child
+      // rect is never used for drawing, just for determining the overflow
+      // location and amount.
+      final Rect overflowChildRect;
+      switch (_direction) {
+        case Axis.horizontal:
+          overflowChildRect = Rect.fromLTWH(0.0, 0.0, size.width + _overflow!, 0.0);
+          break;
+        case Axis.vertical:
+          overflowChildRect = Rect.fromLTWH(0.0, 0.0, 0.0, size.height + _overflow!);
+          break;
+      }
+      paintOverflowIndicator(context, offset, Offset.zero & size, overflowChildRect, overflowHints: debugOverflowHints);
+      return true;
+    }());
+  }
+
+  ClipRectLayer? _clipRectLayer;
+
+  @override
+  Rect? describeApproximatePaintClip(RenderObject child) => _hasOverflow ? Offset.zero & size : null;
+
+  @override
+  String toStringShort() {
+    String header = super.toStringShort();
+    if (_overflow != null && _hasOverflow)
+      header += ' OVERFLOWING';
+    return header;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<Axis>('direction', direction));
+    properties.add(EnumProperty<MainAxisAlignment>('mainAxisAlignment', mainAxisAlignment));
+    properties.add(EnumProperty<MainAxisSize>('mainAxisSize', mainAxisSize));
+    properties.add(EnumProperty<CrossAxisAlignment>('crossAxisAlignment', crossAxisAlignment));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    properties.add(EnumProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: null));
+    properties.add(EnumProperty<TextBaseline>('textBaseline', textBaseline, defaultValue: null));
+  }
+}
+
+class _LayoutSizes {
+  const _LayoutSizes({
+    required this.mainSize,
+    required this.crossSize,
+    required this.allocatedSize,
+  });
+
+  final double mainSize;
+  final double crossSize;
+  final double allocatedSize;
+}
diff --git a/lib/src/rendering/flow.dart b/lib/src/rendering/flow.dart
new file mode 100644
index 0000000..423075b
--- /dev/null
+++ b/lib/src/rendering/flow.dart
@@ -0,0 +1,434 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui show Color;
+
+import 'package:flute/foundation.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'box.dart';
+import 'layer.dart';
+import 'object.dart';
+
+/// A context in which a [FlowDelegate] paints.
+///
+/// Provides information about the current size of the container and the
+/// children and a mechanism for painting children.
+///
+/// See also:
+///
+///  * [FlowDelegate]
+///  * [Flow]
+///  * [RenderFlow]
+abstract class FlowPaintingContext {
+  /// The size of the container in which the children can be painted.
+  Size get size;
+
+  /// The number of children available to paint.
+  int get childCount;
+
+  /// The size of the [i]th child.
+  ///
+  /// If [i] is negative or exceeds [childCount], returns null.
+  Size? getChildSize(int i);
+
+  /// Paint the [i]th child using the given transform.
+  ///
+  /// The child will be painted in a coordinate system that concatenates the
+  /// container's coordinate system with the given transform. The origin of the
+  /// parent's coordinate system is the upper left corner of the parent, with
+  /// x increasing rightward and y increasing downward.
+  ///
+  /// The container will clip the children to its bounds.
+  void paintChild(int i, { Matrix4 transform, double opacity = 1.0 });
+}
+
+/// A delegate that controls the appearance of a flow layout.
+///
+/// Flow layouts are optimized for moving children around the screen using
+/// transformation matrices. For optimal performance, construct the
+/// [FlowDelegate] with an [Animation] that ticks whenever the delegate wishes
+/// to change the transformation matrices for the children and avoid rebuilding
+/// the [Flow] widget itself every animation frame.
+///
+/// See also:
+///
+///  * [Flow]
+///  * [RenderFlow]
+abstract class FlowDelegate {
+  /// The flow will repaint whenever [repaint] notifies its listeners.
+  const FlowDelegate({ Listenable? repaint }) : _repaint = repaint;
+
+  final Listenable? _repaint;
+
+  /// Override to control the size of the container for the children.
+  ///
+  /// By default, the flow will be as large as possible. If this function
+  /// returns a size that does not respect the given constraints, the size will
+  /// be adjusted to be as close to the returned size as possible while still
+  /// respecting the constraints.
+  ///
+  /// If this function depends on information other than the given constraints,
+  /// override [shouldRelayout] to indicate when when the container should
+  /// relayout.
+  Size getSize(BoxConstraints constraints) => constraints.biggest;
+
+  /// Override to control the layout constraints given to each child.
+  ///
+  /// By default, the children will receive the given constraints, which are the
+  /// constraints used to size the container. The children need
+  /// not respect the given constraints, but they are required to respect the
+  /// returned constraints. For example, the incoming constraints might require
+  /// the container to have a width of exactly 100.0 and a height of exactly
+  /// 100.0, but this function might give the children looser constraints that
+  /// let them be larger or smaller than 100.0 by 100.0.
+  ///
+  /// If this function depends on information other than the given constraints,
+  /// override [shouldRelayout] to indicate when when the container should
+  /// relayout.
+  BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) => constraints;
+
+  /// Override to paint the children of the flow.
+  ///
+  /// Children can be painted in any order, but each child can be painted at
+  /// most once. Although the container clips the children to its own bounds, it
+  /// is more efficient to skip painting a child altogether rather than having
+  /// it paint entirely outside the container's clip.
+  ///
+  /// To paint a child, call [FlowPaintingContext.paintChild] on the given
+  /// [FlowPaintingContext] (the `context` argument). The given context is valid
+  /// only within the scope of this function call and contains information (such
+  /// as the size of the container) that is useful for picking transformation
+  /// matrices for the children.
+  ///
+  /// If this function depends on information other than the given context,
+  /// override [shouldRepaint] to indicate when when the container should
+  /// relayout.
+  void paintChildren(FlowPaintingContext context);
+
+  /// Override this method to return true when the children need to be laid out.
+  /// This should compare the fields of the current delegate and the given
+  /// oldDelegate and return true if the fields are such that the layout would
+  /// be different.
+  bool shouldRelayout(covariant FlowDelegate oldDelegate) => false;
+
+  /// Override this method to return true when the children need to be
+  /// repainted. This should compare the fields of the current delegate and the
+  /// given oldDelegate and return true if the fields are such that
+  /// paintChildren would act differently.
+  ///
+  /// The delegate can also trigger a repaint if the delegate provides the
+  /// repaint animation argument to this object's constructor and that animation
+  /// ticks. Triggering a repaint using this animation-based mechanism is more
+  /// efficient than rebuilding the [Flow] widget to change its delegate.
+  ///
+  /// The flow container might repaint even if this function returns false, for
+  /// example if layout triggers painting (e.g., if [shouldRelayout] returns
+  /// true).
+  bool shouldRepaint(covariant FlowDelegate oldDelegate);
+
+  /// Override this method to include additional information in the
+  /// debugging data printed by [debugDumpRenderTree] and friends.
+  ///
+  /// By default, returns the [runtimeType] of the class.
+  @override
+  String toString() => objectRuntimeType(this, 'FlowDelegate');
+}
+
+/// Parent data for use with [RenderFlow].
+///
+/// The [offset] property is ignored by [RenderFlow] and is always set to
+/// [Offset.zero]. Children of a [RenderFlow] are positioned using a
+/// transformation matrix, which is private to the [RenderFlow]. To set the
+/// matrix, use the [FlowPaintingContext.paintChild] function from an override
+/// of the [FlowDelegate.paintChildren] function.
+class FlowParentData extends ContainerBoxParentData<RenderBox> {
+  Matrix4? _transform;
+}
+
+/// Implements the flow layout algorithm.
+///
+/// Flow layouts are optimized for repositioning children using transformation
+/// matrices.
+///
+/// The flow container is sized independently from the children by the
+/// [FlowDelegate.getSize] function of the delegate. The children are then sized
+/// independently given the constraints from the
+/// [FlowDelegate.getConstraintsForChild] function.
+///
+/// Rather than positioning the children during layout, the children are
+/// positioned using transformation matrices during the paint phase using the
+/// matrices from the [FlowDelegate.paintChildren] function. The children can be
+/// repositioned efficiently by simply repainting the flow.
+///
+/// The most efficient way to trigger a repaint of the flow is to supply a
+/// repaint argument to the constructor of the [FlowDelegate]. The flow will
+/// listen to this animation and repaint whenever the animation ticks, avoiding
+/// both the build and layout phases of the pipeline.
+///
+/// See also:
+///
+///  * [FlowDelegate]
+///  * [RenderStack]
+class RenderFlow extends RenderBox
+    with ContainerRenderObjectMixin<RenderBox, FlowParentData>,
+         RenderBoxContainerDefaultsMixin<RenderBox, FlowParentData>
+    implements FlowPaintingContext {
+  /// Creates a render object for a flow layout.
+  ///
+  /// For optimal performance, consider using children that return true from
+  /// [isRepaintBoundary].
+  RenderFlow({
+    List<RenderBox>? children,
+    required FlowDelegate delegate,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : assert(delegate != null),
+       assert(clipBehavior != null),
+       _delegate = delegate,
+       _clipBehavior = clipBehavior {
+    addAll(children);
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    final ParentData? childParentData = child.parentData;
+    if (childParentData is FlowParentData)
+      childParentData._transform = null;
+    else
+      child.parentData = FlowParentData();
+  }
+
+  /// The delegate that controls the transformation matrices of the children.
+  FlowDelegate get delegate => _delegate;
+  FlowDelegate _delegate;
+  /// When the delegate is changed to a new delegate with the same runtimeType
+  /// as the old delegate, this object will call the delegate's
+  /// [FlowDelegate.shouldRelayout] and [FlowDelegate.shouldRepaint] functions
+  /// to determine whether the new delegate requires this object to update its
+  /// layout or painting.
+  set delegate(FlowDelegate newDelegate) {
+    assert(newDelegate != null);
+    if (_delegate == newDelegate)
+      return;
+    final FlowDelegate oldDelegate = _delegate;
+    _delegate = newDelegate;
+
+    if (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRelayout(oldDelegate))
+      markNeedsLayout();
+    else if (newDelegate.shouldRepaint(oldDelegate))
+      markNeedsPaint();
+
+    if (attached) {
+      oldDelegate._repaint?.removeListener(markNeedsPaint);
+      newDelegate._repaint?.addListener(markNeedsPaint);
+    }
+  }
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge], and must not be null.
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior = Clip.hardEdge;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    _delegate._repaint?.addListener(markNeedsPaint);
+  }
+
+  @override
+  void detach() {
+    _delegate._repaint?.removeListener(markNeedsPaint);
+    super.detach();
+  }
+
+  Size _getSize(BoxConstraints constraints) {
+    assert(constraints.debugAssertIsValid());
+    return constraints.constrain(_delegate.getSize(constraints));
+  }
+
+  @override
+  bool get isRepaintBoundary => true;
+
+  // TODO(ianh): It's a bit dubious to be using the getSize function from the delegate to
+  // figure out the intrinsic dimensions. We really should either not support intrinsics,
+  // or we should expose intrinsic delegate callbacks and throw if they're not implemented.
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
+    if (width.isFinite)
+      return width;
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
+    if (width.isFinite)
+      return width;
+    return 0.0;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
+    if (height.isFinite)
+      return height;
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
+    if (height.isFinite)
+      return height;
+    return 0.0;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _getSize(constraints);
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    size = _getSize(constraints);
+    int i = 0;
+    _randomAccessChildren.clear();
+    RenderBox? child = firstChild;
+    while (child != null) {
+      _randomAccessChildren.add(child);
+      final BoxConstraints innerConstraints = _delegate.getConstraintsForChild(i, constraints);
+      child.layout(innerConstraints, parentUsesSize: true);
+      final FlowParentData childParentData = child.parentData! as FlowParentData;
+      childParentData.offset = Offset.zero;
+      child = childParentData.nextSibling;
+      i += 1;
+    }
+  }
+
+  // Updated during layout. Only valid if layout is not dirty.
+  final List<RenderBox> _randomAccessChildren = <RenderBox>[];
+
+  // Updated during paint.
+  final List<int> _lastPaintOrder = <int>[];
+
+  // Only valid during paint.
+  PaintingContext? _paintingContext;
+  Offset? _paintingOffset;
+
+  @override
+  Size? getChildSize(int i) {
+    if (i < 0 || i >= _randomAccessChildren.length)
+      return null;
+    return _randomAccessChildren[i].size;
+  }
+
+  @override
+  void paintChild(int i, { Matrix4? transform, double opacity = 1.0 }) {
+    transform ??= Matrix4.identity();
+    final RenderBox child = _randomAccessChildren[i];
+    final FlowParentData childParentData = child.parentData! as FlowParentData;
+    assert(() {
+      if (childParentData._transform != null) {
+        throw FlutterError(
+          'Cannot call paintChild twice for the same child.\n'
+          'The flow delegate of type ${_delegate.runtimeType} attempted to '
+          'paint child $i multiple times, which is not permitted.'
+        );
+      }
+      return true;
+    }());
+    _lastPaintOrder.add(i);
+    childParentData._transform = transform;
+
+    // We return after assigning _transform so that the transparent child can
+    // still be hit tested at the correct location.
+    if (opacity == 0.0)
+      return;
+
+    void painter(PaintingContext context, Offset offset) {
+      context.paintChild(child, offset);
+    }
+    if (opacity == 1.0) {
+      _paintingContext!.pushTransform(needsCompositing, _paintingOffset!, transform, painter);
+    } else {
+      _paintingContext!.pushOpacity(_paintingOffset!, ui.Color.getAlphaFromOpacity(opacity), (PaintingContext context, Offset offset) {
+        context.pushTransform(needsCompositing, offset, transform!, painter);
+      });
+    }
+  }
+
+  void _paintWithDelegate(PaintingContext context, Offset offset) {
+    _lastPaintOrder.clear();
+    _paintingContext = context;
+    _paintingOffset = offset;
+    for (final RenderBox child in _randomAccessChildren) {
+      final FlowParentData childParentData = child.parentData! as FlowParentData;
+      childParentData._transform = null;
+    }
+    try {
+      _delegate.paintChildren(this);
+    } finally {
+      _paintingContext = null;
+      _paintingOffset = null;
+    }
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (clipBehavior == Clip.none) {
+      _clipRectLayer = null;
+      _paintWithDelegate(context, offset);
+    } else {
+      _clipRectLayer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintWithDelegate,
+          clipBehavior: clipBehavior, oldLayer: _clipRectLayer);
+    }
+  }
+
+  ClipRectLayer? _clipRectLayer;
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    final List<RenderBox> children = getChildrenAsList();
+    for (int i = _lastPaintOrder.length - 1; i >= 0; --i) {
+      final int childIndex = _lastPaintOrder[i];
+      if (childIndex >= children.length)
+        continue;
+      final RenderBox child = children[childIndex];
+      final FlowParentData childParentData = child.parentData! as FlowParentData;
+      final Matrix4? transform = childParentData._transform;
+      if (transform == null)
+        continue;
+      final bool absorbed = result.addWithPaintTransform(
+        transform: transform,
+        position: position,
+        hitTest: (BoxHitTestResult result, Offset? position) {
+          return child.hitTest(result, position: position!);
+        },
+      );
+      if (absorbed)
+        return true;
+    }
+    return false;
+  }
+
+  @override
+  void applyPaintTransform(RenderBox child, Matrix4 transform) {
+    final FlowParentData childParentData = child.parentData! as FlowParentData;
+    if (childParentData._transform != null)
+      transform.multiply(childParentData._transform!);
+    super.applyPaintTransform(child, transform);
+  }
+}
diff --git a/lib/src/rendering/image.dart b/lib/src/rendering/image.dart
new file mode 100644
index 0000000..0c0bb79
--- /dev/null
+++ b/lib/src/rendering/image.dart
@@ -0,0 +1,427 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui show Image;
+
+import 'box.dart';
+import 'object.dart';
+
+export 'package:flute/painting.dart' show
+  BoxFit,
+  ImageRepeat;
+
+/// An image in the render tree.
+///
+/// The render image attempts to find a size for itself that fits in the given
+/// constraints and preserves the image's intrinsic aspect ratio.
+///
+/// The image is painted using [paintImage], which describes the meanings of the
+/// various fields on this class in more detail.
+class RenderImage extends RenderBox {
+  /// Creates a render box that displays an image.
+  ///
+  /// The [scale], [alignment], [repeat], [matchTextDirection] and [filterQuality] arguments
+  /// must not be null. The [textDirection] argument must not be null if
+  /// [alignment] will need resolving or if [matchTextDirection] is true.
+  RenderImage({
+    ui.Image? image,
+    this.debugImageLabel,
+    double? width,
+    double? height,
+    double scale = 1.0,
+    Color? color,
+    BlendMode? colorBlendMode,
+    BoxFit? fit,
+    AlignmentGeometry alignment = Alignment.center,
+    ImageRepeat repeat = ImageRepeat.noRepeat,
+    Rect? centerSlice,
+    bool matchTextDirection = false,
+    TextDirection? textDirection,
+    bool invertColors = false,
+    bool isAntiAlias = false,
+    FilterQuality filterQuality = FilterQuality.low,
+  }) : assert(scale != null),
+       assert(repeat != null),
+       assert(alignment != null),
+       assert(filterQuality != null),
+       assert(matchTextDirection != null),
+       assert(isAntiAlias != null),
+       _image = image,
+       _width = width,
+       _height = height,
+       _scale = scale,
+       _color = color,
+       _colorBlendMode = colorBlendMode,
+       _fit = fit,
+       _alignment = alignment,
+       _repeat = repeat,
+       _centerSlice = centerSlice,
+       _matchTextDirection = matchTextDirection,
+       _invertColors = invertColors,
+       _textDirection = textDirection,
+       _isAntiAlias = isAntiAlias,
+       _filterQuality = filterQuality {
+    _updateColorFilter();
+  }
+
+  Alignment? _resolvedAlignment;
+  bool? _flipHorizontally;
+
+  void _resolve() {
+    if (_resolvedAlignment != null)
+      return;
+    _resolvedAlignment = alignment.resolve(textDirection);
+    _flipHorizontally = matchTextDirection && textDirection == TextDirection.rtl;
+  }
+
+  void _markNeedResolution() {
+    _resolvedAlignment = null;
+    _flipHorizontally = null;
+    markNeedsPaint();
+  }
+
+  /// The image to display.
+  ui.Image? get image => _image;
+  ui.Image? _image;
+  set image(ui.Image? value) {
+    if (value == _image) {
+      return;
+    }
+    // If we get a clone of our image, it's the same underlying native data -
+    // dispose of the new clone and return early.
+    if (value != null && _image != null && value.isCloneOf(_image!)) {
+      value.dispose();
+      return;
+    }
+    _image?.dispose();
+    _image = value;
+    markNeedsPaint();
+    if (_width == null || _height == null)
+      markNeedsLayout();
+  }
+
+  /// A string used to identify the source of the image.
+  String? debugImageLabel;
+
+  /// If non-null, requires the image to have this width.
+  ///
+  /// If null, the image will pick a size that best preserves its intrinsic
+  /// aspect ratio.
+  double? get width => _width;
+  double? _width;
+  set width(double? value) {
+    if (value == _width)
+      return;
+    _width = value;
+    markNeedsLayout();
+  }
+
+  /// If non-null, require the image to have this height.
+  ///
+  /// If null, the image will pick a size that best preserves its intrinsic
+  /// aspect ratio.
+  double? get height => _height;
+  double? _height;
+  set height(double? value) {
+    if (value == _height)
+      return;
+    _height = value;
+    markNeedsLayout();
+  }
+
+  /// Specifies the image's scale.
+  ///
+  /// Used when determining the best display size for the image.
+  double get scale => _scale;
+  double _scale;
+  set scale(double value) {
+    assert(value != null);
+    if (value == _scale)
+      return;
+    _scale = value;
+    markNeedsLayout();
+  }
+
+  ColorFilter? _colorFilter;
+
+  void _updateColorFilter() {
+    if (_color == null)
+      _colorFilter = null;
+    else
+      _colorFilter = ColorFilter.mode(_color!, _colorBlendMode ?? BlendMode.srcIn);
+  }
+
+  /// If non-null, this color is blended with each image pixel using [colorBlendMode].
+  Color? get color => _color;
+  Color? _color;
+  set color(Color? value) {
+    if (value == _color)
+      return;
+    _color = value;
+    _updateColorFilter();
+    markNeedsPaint();
+  }
+
+  /// Used to set the filterQuality of the image
+  /// Use the [FilterQuality.low] quality setting to scale the image, which corresponds to
+  /// bilinear interpolation, rather than the default [FilterQuality.none] which corresponds
+  /// to nearest-neighbor.
+  FilterQuality get filterQuality => _filterQuality;
+  FilterQuality _filterQuality;
+  set filterQuality(FilterQuality value) {
+    assert(value != null);
+    if (value == _filterQuality)
+      return;
+    _filterQuality = value;
+    markNeedsPaint();
+  }
+
+
+  /// Used to combine [color] with this image.
+  ///
+  /// The default is [BlendMode.srcIn]. In terms of the blend mode, [color] is
+  /// the source and this image is the destination.
+  ///
+  /// See also:
+  ///
+  ///  * [BlendMode], which includes an illustration of the effect of each blend mode.
+  BlendMode? get colorBlendMode => _colorBlendMode;
+  BlendMode? _colorBlendMode;
+  set colorBlendMode(BlendMode? value) {
+    if (value == _colorBlendMode)
+      return;
+    _colorBlendMode = value;
+    _updateColorFilter();
+    markNeedsPaint();
+  }
+
+  /// How to inscribe the image into the space allocated during layout.
+  ///
+  /// The default varies based on the other fields. See the discussion at
+  /// [paintImage].
+  BoxFit? get fit => _fit;
+  BoxFit? _fit;
+  set fit(BoxFit? value) {
+    if (value == _fit)
+      return;
+    _fit = value;
+    markNeedsPaint();
+  }
+
+  /// How to align the image within its bounds.
+  ///
+  /// If this is set to a text-direction-dependent value, [textDirection] must
+  /// not be null.
+  AlignmentGeometry get alignment => _alignment;
+  AlignmentGeometry _alignment;
+  set alignment(AlignmentGeometry value) {
+    assert(value != null);
+    if (value == _alignment)
+      return;
+    _alignment = value;
+    _markNeedResolution();
+  }
+
+  /// How to repeat this image if it doesn't fill its layout bounds.
+  ImageRepeat get repeat => _repeat;
+  ImageRepeat _repeat;
+  set repeat(ImageRepeat value) {
+    assert(value != null);
+    if (value == _repeat)
+      return;
+    _repeat = value;
+    markNeedsPaint();
+  }
+
+  /// The center slice for a nine-patch image.
+  ///
+  /// The region of the image inside the center slice will be stretched both
+  /// horizontally and vertically to fit the image into its destination. The
+  /// region of the image above and below the center slice will be stretched
+  /// only horizontally and the region of the image to the left and right of
+  /// the center slice will be stretched only vertically.
+  Rect? get centerSlice => _centerSlice;
+  Rect? _centerSlice;
+  set centerSlice(Rect? value) {
+    if (value == _centerSlice)
+      return;
+    _centerSlice = value;
+    markNeedsPaint();
+  }
+
+  /// Whether to invert the colors of the image.
+  ///
+  /// inverting the colors of an image applies a new color filter to the paint.
+  /// If there is another specified color filter, the invert will be applied
+  /// after it. This is primarily used for implementing smart invert on iOS.
+  bool get invertColors => _invertColors;
+  bool _invertColors;
+  set invertColors(bool value) {
+    if (value == _invertColors)
+      return;
+    _invertColors = value;
+    markNeedsPaint();
+  }
+
+  /// Whether to paint the image in the direction of the [TextDirection].
+  ///
+  /// If this is true, then in [TextDirection.ltr] contexts, the image will be
+  /// drawn with its origin in the top left (the "normal" painting direction for
+  /// images); and in [TextDirection.rtl] contexts, the image will be drawn with
+  /// a scaling factor of -1 in the horizontal direction so that the origin is
+  /// in the top right.
+  ///
+  /// This is occasionally used with images in right-to-left environments, for
+  /// images that were designed for left-to-right locales. Be careful, when
+  /// using this, to not flip images with integral shadows, text, or other
+  /// effects that will look incorrect when flipped.
+  ///
+  /// If this is set to true, [textDirection] must not be null.
+  bool get matchTextDirection => _matchTextDirection;
+  bool _matchTextDirection;
+  set matchTextDirection(bool value) {
+    assert(value != null);
+    if (value == _matchTextDirection)
+      return;
+    _matchTextDirection = value;
+    _markNeedResolution();
+  }
+
+  /// The text direction with which to resolve [alignment].
+  ///
+  /// This may be changed to null, but only after the [alignment] and
+  /// [matchTextDirection] properties have been changed to values that do not
+  /// depend on the direction.
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection;
+  set textDirection(TextDirection? value) {
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    _markNeedResolution();
+  }
+
+  /// Whether to paint the image with anti-aliasing.
+  ///
+  /// Anti-aliasing alleviates the sawtooth artifact when the image is rotated.
+  bool get isAntiAlias => _isAntiAlias;
+  bool _isAntiAlias;
+  set isAntiAlias(bool value) {
+    if (_isAntiAlias == value) {
+      return;
+    }
+    assert(value != null);
+    _isAntiAlias = value;
+    markNeedsPaint();
+  }
+
+  /// Find a size for the render image within the given constraints.
+  ///
+  ///  - The dimensions of the RenderImage must fit within the constraints.
+  ///  - The aspect ratio of the RenderImage matches the intrinsic aspect
+  ///    ratio of the image.
+  ///  - The RenderImage's dimension are maximal subject to being smaller than
+  ///    the intrinsic size of the image.
+  Size _sizeForConstraints(BoxConstraints constraints) {
+    // Folds the given |width| and |height| into |constraints| so they can all
+    // be treated uniformly.
+    constraints = BoxConstraints.tightFor(
+      width: _width,
+      height: _height,
+    ).enforce(constraints);
+
+    if (_image == null)
+      return constraints.smallest;
+
+    return constraints.constrainSizeAndAttemptToPreserveAspectRatio(Size(
+      _image!.width.toDouble() / _scale,
+      _image!.height.toDouble() / _scale,
+    ));
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    assert(height >= 0.0);
+    if (_width == null && _height == null)
+      return 0.0;
+    return _sizeForConstraints(BoxConstraints.tightForFinite(height: height)).width;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    assert(height >= 0.0);
+    return _sizeForConstraints(BoxConstraints.tightForFinite(height: height)).width;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    assert(width >= 0.0);
+    if (_width == null && _height == null)
+      return 0.0;
+    return _sizeForConstraints(BoxConstraints.tightForFinite(width: width)).height;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    assert(width >= 0.0);
+    return _sizeForConstraints(BoxConstraints.tightForFinite(width: width)).height;
+  }
+
+  @override
+  bool hitTestSelf(Offset position) => true;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _sizeForConstraints(constraints);
+  }
+
+  @override
+  void performLayout() {
+    size = _sizeForConstraints(constraints);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (_image == null)
+      return;
+    _resolve();
+    assert(_resolvedAlignment != null);
+    assert(_flipHorizontally != null);
+    paintImage(
+      canvas: context.canvas,
+      rect: offset & size,
+      image: _image!,
+      debugImageLabel: debugImageLabel,
+      scale: _scale,
+      colorFilter: _colorFilter,
+      fit: _fit,
+      alignment: _resolvedAlignment!,
+      centerSlice: _centerSlice,
+      repeat: _repeat,
+      flipHorizontally: _flipHorizontally!,
+      invertColors: invertColors,
+      filterQuality: _filterQuality,
+      isAntiAlias: _isAntiAlias,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ui.Image>('image', image));
+    properties.add(DoubleProperty('width', width, defaultValue: null));
+    properties.add(DoubleProperty('height', height, defaultValue: null));
+    properties.add(DoubleProperty('scale', scale, defaultValue: 1.0));
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null));
+    properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null));
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
+    properties.add(EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
+    properties.add(DiagnosticsProperty<Rect>('centerSlice', centerSlice, defaultValue: null));
+    properties.add(FlagProperty('matchTextDirection', value: matchTextDirection, ifTrue: 'match text direction'));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('invertColors', invertColors));
+    properties.add(EnumProperty<FilterQuality>('filterQuality', filterQuality));
+  }
+}
diff --git a/lib/src/rendering/layer.dart b/lib/src/rendering/layer.dart
new file mode 100644
index 0000000..05b2d3f
--- /dev/null
+++ b/lib/src/rendering/layer.dart
@@ -0,0 +1,2558 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/painting.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'debug.dart';
+
+/// Information collected for an annotation that is found in the layer tree.
+///
+/// See also:
+///
+///  * [Layer.findAnnotations], which create and use objects of this class.
+@immutable
+class AnnotationEntry<T> {
+  /// Create an entry of found annotation by providing the object and related
+  /// information.
+  const AnnotationEntry({
+    required this.annotation,
+    required this.localPosition,
+  }) : assert(localPosition != null);
+
+  /// The annotation object that is found.
+  final T annotation;
+
+  /// The target location described by the local coordinate space of the
+  /// annotation object.
+  final Offset localPosition;
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'AnnotationEntry')}(annotation: $annotation, localPosition: $localPosition)';
+  }
+}
+
+/// Information collected about a list of annotations that are found in the
+/// layer tree.
+///
+/// See also:
+///
+///  * [AnnotationEntry], which are members of this class.
+///  * [Layer.findAllAnnotations], and [Layer.findAnnotations], which create and
+///    use an object of this class.
+class AnnotationResult<T> {
+  final List<AnnotationEntry<T>> _entries = <AnnotationEntry<T>>[];
+
+  /// Add a new entry to the end of the result.
+  ///
+  /// Usually, entries should be added in order from most specific to least
+  /// specific, typically during an upward walk of the tree.
+  void add(AnnotationEntry<T> entry) => _entries.add(entry);
+
+  /// An unmodifiable list of [AnnotationEntry] objects recorded.
+  ///
+  /// The first entry is the most specific, typically the one at the leaf of
+  /// tree.
+  Iterable<AnnotationEntry<T>> get entries => _entries;
+
+  /// An unmodifiable list of annotations recorded.
+  ///
+  /// The first entry is the most specific, typically the one at the leaf of
+  /// tree.
+  ///
+  /// It is similar to [entries] but does not contain other information.
+  Iterable<T> get annotations sync* {
+    for (final AnnotationEntry<T> entry in _entries)
+      yield entry.annotation;
+  }
+}
+
+/// A composited layer.
+///
+/// During painting, the render tree generates a tree of composited layers that
+/// are uploaded into the engine and displayed by the compositor. This class is
+/// the base class for all composited layers.
+///
+/// Most layers can have their properties mutated, and layers can be moved to
+/// different parents. The scene must be explicitly recomposited after such
+/// changes are made; the layer tree does not maintain its own dirty state.
+///
+/// To composite the tree, create a [SceneBuilder] object, pass it to the
+/// root [Layer] object's [addToScene] method, and then call
+/// [SceneBuilder.build] to obtain a [Scene]. A [Scene] can then be painted
+/// using [dart:ui.FlutterView.render].
+///
+/// See also:
+///
+///  * [RenderView.compositeFrame], which implements this recomposition protocol
+///    for painting [RenderObject] trees on the display.
+abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
+  /// This layer's parent in the layer tree.
+  ///
+  /// The [parent] of the root node in the layer tree is null.
+  ///
+  /// Only subclasses of [ContainerLayer] can have children in the layer tree.
+  /// All other layer classes are used for leaves in the layer tree.
+  @override
+  ContainerLayer? get parent => super.parent as ContainerLayer?;
+
+  // Whether this layer has any changes since its last call to [addToScene].
+  //
+  // Initialized to true as a new layer has never called [addToScene], and is
+  // set to false after calling [addToScene]. The value can become true again
+  // if [markNeedsAddToScene] is called, or when [updateSubtreeNeedsAddToScene]
+  // is called on this layer or on an ancestor layer.
+  //
+  // The values of [_needsAddToScene] in a tree of layers are said to be
+  // _consistent_ if every layer in the tree satisfies the following:
+  //
+  // - If [alwaysNeedsAddToScene] is true, then [_needsAddToScene] is also true.
+  // - If [_needsAddToScene] is true and [parent] is not null, then
+  //   `parent._needsAddToScene` is true.
+  //
+  // Typically, this value is set during the paint phase and during compositing.
+  // During the paint phase render objects create new layers and call
+  // [markNeedsAddToScene] on existing layers, causing this value to become
+  // true. After the paint phase the tree may be in an inconsistent state.
+  // During compositing [ContainerLayer.buildScene] first calls
+  // [updateSubtreeNeedsAddToScene] to bring this tree to a consistent state,
+  // then it calls [addToScene], and finally sets this field to false.
+  bool _needsAddToScene = true;
+
+  /// Mark that this layer has changed and [addToScene] needs to be called.
+  @protected
+  @visibleForTesting
+  void markNeedsAddToScene() {
+    assert(
+      !alwaysNeedsAddToScene,
+      '$runtimeType with alwaysNeedsAddToScene set called markNeedsAddToScene.\n'
+      "The layer's alwaysNeedsAddToScene is set to true, and therefore it should not call markNeedsAddToScene.",
+    );
+
+    // Already marked. Short-circuit.
+    if (_needsAddToScene) {
+      return;
+    }
+
+    _needsAddToScene = true;
+  }
+
+  /// Mark that this layer is in sync with engine.
+  ///
+  /// This is for debugging and testing purposes only. In release builds
+  /// this method has no effect.
+  @visibleForTesting
+  void debugMarkClean() {
+    assert(() {
+      _needsAddToScene = false;
+      return true;
+    }());
+  }
+
+  /// Subclasses may override this to true to disable retained rendering.
+  @protected
+  bool get alwaysNeedsAddToScene => false;
+
+  /// Whether this or any descendant layer in the subtree needs [addToScene].
+  ///
+  /// This is for debug and test purpose only. It only becomes valid after
+  /// calling [updateSubtreeNeedsAddToScene].
+  @visibleForTesting
+  bool? get debugSubtreeNeedsAddToScene {
+    bool? result;
+    assert(() {
+      result = _needsAddToScene;
+      return true;
+    }());
+    return result;
+  }
+
+  /// Stores the engine layer created for this layer in order to reuse engine
+  /// resources across frames for better app performance.
+  ///
+  /// This value may be passed to [ui.SceneBuilder.addRetained] to communicate
+  /// to the engine that nothing in this layer or any of its descendants
+  /// changed. The native engine could, for example, reuse the texture rendered
+  /// in a previous frame. The web engine could, for example, reuse the HTML
+  /// DOM nodes created for a previous frame.
+  ///
+  /// This value may be passed as `oldLayer` argument to a "push" method to
+  /// communicate to the engine that a layer is updating a previously rendered
+  /// layer. The web engine could, for example, update the properties of
+  /// previously rendered HTML DOM nodes rather than creating new nodes.
+  @protected
+  ui.EngineLayer? get engineLayer => _engineLayer;
+
+  /// Sets the engine layer used to render this layer.
+  ///
+  /// Typically this field is set to the value returned by [addToScene], which
+  /// in turn returns the engine layer produced by one of [ui.SceneBuilder]'s
+  /// "push" methods, such as [ui.SceneBuilder.pushOpacity].
+  @protected
+  set engineLayer(ui.EngineLayer? value) {
+    _engineLayer = value;
+    if (!alwaysNeedsAddToScene) {
+      // The parent must construct a new engine layer to add this layer to, and
+      // so we mark it as needing [addToScene].
+      //
+      // This is designed to handle two situations:
+      //
+      // 1. When rendering the complete layer tree as normal. In this case we
+      // call child `addToScene` methods first, then we call `set engineLayer`
+      // for the parent. The children will call `markNeedsAddToScene` on the
+      // parent to signal that they produced new engine layers and therefore
+      // the parent needs to update. In this case, the parent is already adding
+      // itself to the scene via [addToScene], and so after it's done, its
+      // `set engineLayer` is called and it clears the `_needsAddToScene` flag.
+      //
+      // 2. When rendering an interior layer (e.g. `OffsetLayer.toImage`). In
+      // this case we call `addToScene` for one of the children but not the
+      // parent, i.e. we produce new engine layers for children but not for the
+      // parent. Here the children will mark the parent as needing
+      // `addToScene`, but the parent does not clear the flag until some future
+      // frame decides to render it, at which point the parent knows that it
+      // cannot retain its engine layer and will call `addToScene` again.
+      if (parent != null && !parent!.alwaysNeedsAddToScene) {
+        parent!.markNeedsAddToScene();
+      }
+    }
+  }
+  ui.EngineLayer? _engineLayer;
+
+  /// Traverses the layer subtree starting from this layer and determines whether it needs [addToScene].
+  ///
+  /// A layer needs [addToScene] if any of the following is true:
+  ///
+  /// - [alwaysNeedsAddToScene] is true.
+  /// - [markNeedsAddToScene] has been called.
+  /// - Any of its descendants need [addToScene].
+  ///
+  /// [ContainerLayer] overrides this method to recursively call it on its children.
+  @protected
+  @visibleForTesting
+  void updateSubtreeNeedsAddToScene() {
+    _needsAddToScene = _needsAddToScene || alwaysNeedsAddToScene;
+  }
+
+  /// This layer's next sibling in the parent layer's child list.
+  Layer? get nextSibling => _nextSibling;
+  Layer? _nextSibling;
+
+  /// This layer's previous sibling in the parent layer's child list.
+  Layer? get previousSibling => _previousSibling;
+  Layer? _previousSibling;
+
+  @override
+  void dropChild(AbstractNode child) {
+    if (!alwaysNeedsAddToScene) {
+      markNeedsAddToScene();
+    }
+    super.dropChild(child);
+  }
+
+  @override
+  void adoptChild(AbstractNode child) {
+    if (!alwaysNeedsAddToScene) {
+      markNeedsAddToScene();
+    }
+    super.adoptChild(child);
+  }
+
+  /// Removes this layer from its parent layer's child list.
+  ///
+  /// This has no effect if the layer's parent is already null.
+  @mustCallSuper
+  void remove() {
+    parent?._removeChild(this);
+  }
+
+  /// Search this layer and its subtree for annotations of type `S` at the
+  /// location described by `localPosition`.
+  ///
+  /// This method is called by the default implementation of [find] and
+  /// [findAllAnnotations]. Override this method to customize how the layer
+  /// should search for annotations, or if the layer has its own annotations to
+  /// add.
+  ///
+  /// The default implementation simply returns `false`, which means neither
+  /// the layer nor its children has annotations, and the annotation search
+  /// is not absorbed either (see below for explanation).
+  ///
+  /// ## About layer annotations
+  ///
+  /// {@template flutter.rendering.Layer.findAnnotations.aboutAnnotations}
+  /// An annotation is an optional object of any type that can be carried with a
+  /// layer. An annotation can be found at a location as long as the owner layer
+  /// contains the location and is walked to.
+  ///
+  /// The annotations are searched by first visiting each child recursively,
+  /// then this layer, resulting in an order from visually front to back.
+  /// Annotations must meet the given restrictions, such as type and position.
+  ///
+  /// The common way for a value to be found here is by pushing an
+  /// [AnnotatedRegionLayer] into the layer tree, or by adding the desired
+  /// annotation by overriding [findAnnotations].
+  /// {@endtemplate}
+  ///
+  /// ## Parameters and return value
+  ///
+  /// The [result] parameter is where the method outputs the resulting
+  /// annotations. New annotations found during the walk are added to the tail.
+  ///
+  /// The [onlyFirst] parameter indicates that, if true, the search will stop
+  /// when it finds the first qualified annotation; otherwise, it will walk the
+  /// entire subtree.
+  ///
+  /// The return value indicates the opacity of this layer and its subtree at
+  /// this position. If it returns true, then this layer's parent should skip
+  /// the children behind this layer. In other words, it is opaque to this type
+  /// of annotation and has absorbed the search so that its siblings behind it
+  /// are not aware of the search. If the return value is false, then the parent
+  /// might continue with other siblings.
+  ///
+  /// The return value does not affect whether the parent adds its own
+  /// annotations; in other words, if a layer is supposed to add an annotation,
+  /// it will always add it even if its children are opaque to this type of
+  /// annotation. However, the opacity that the parents return might be affected
+  /// by their children, hence making all of its ancestors opaque to this type
+  /// of annotation.
+  @protected
+  bool findAnnotations<S extends Object>(
+    AnnotationResult<S> result,
+    Offset localPosition, {
+    required bool onlyFirst,
+  }) {
+    return false;
+  }
+
+  /// Search this layer and its subtree for the first annotation of type `S`
+  /// under the point described by `localPosition`.
+  ///
+  /// Returns null if no matching annotations are found.
+  ///
+  /// By default this method simply calls [findAnnotations] with `onlyFirst:
+  /// true` and returns the annotation of the first result. Prefer overriding
+  /// [findAnnotations] instead of this method, because during an annotation
+  /// search, only [findAnnotations] is recursively called, while custom
+  /// behavior in this method is ignored.
+  ///
+  /// ## About layer annotations
+  ///
+  /// {@macro flutter.rendering.Layer.findAnnotations.aboutAnnotations}
+  ///
+  /// See also:
+  ///
+  ///  * [findAllAnnotations], which is similar but returns all annotations found
+  ///    at the given position.
+  ///  * [AnnotatedRegionLayer], for placing values in the layer tree.
+  S? find<S extends Object>(Offset localPosition) {
+    final AnnotationResult<S> result = AnnotationResult<S>();
+    findAnnotations<S>(result, localPosition, onlyFirst: true);
+    return result.entries.isEmpty ? null : result.entries.first.annotation;
+  }
+
+  /// Search this layer and its subtree for all annotations of type `S` under
+  /// the point described by `localPosition`.
+  ///
+  /// Returns a result with empty entries if no matching annotations are found.
+  ///
+  /// By default this method simply calls [findAnnotations] with `onlyFirst:
+  /// false` and returns the annotations of its result. Prefer overriding
+  /// [findAnnotations] instead of this method, because during an annotation
+  /// search, only [findAnnotations] is recursively called, while custom
+  /// behavior in this method is ignored.
+  ///
+  /// ## About layer annotations
+  ///
+  /// {@macro flutter.rendering.Layer.findAnnotations.aboutAnnotations}
+  ///
+  /// See also:
+  ///
+  ///  * [find], which is similar but returns the first annotation found at the
+  ///    given position.
+  ///  * [findAllAnnotations], which is similar but returns an
+  ///    [AnnotationResult], which contains more information, such as the local
+  ///    position of the event related to each annotation, and is equally fast,
+  ///    hence is preferred over [findAll].
+  ///  * [AnnotatedRegionLayer], for placing values in the layer tree.
+  @Deprecated(
+    'Use findAllAnnotations(...).annotations instead. '
+    'This feature was deprecated after v1.10.14.'
+  )
+  Iterable<S> findAll<S extends Object>(Offset localPosition) {
+    final AnnotationResult<S> result = findAllAnnotations(localPosition);
+    return result.entries.map((AnnotationEntry<S> entry) => entry.annotation);
+  }
+
+  /// Search this layer and its subtree for all annotations of type `S` under
+  /// the point described by `localPosition`.
+  ///
+  /// Returns a result with empty entries if no matching annotations are found.
+  ///
+  /// By default this method simply calls [findAnnotations] with `onlyFirst:
+  /// false` and returns the annotations of its result. Prefer overriding
+  /// [findAnnotations] instead of this method, because during an annotation
+  /// search, only [findAnnotations] is recursively called, while custom
+  /// behavior in this method is ignored.
+  ///
+  /// ## About layer annotations
+  ///
+  /// {@macro flutter.rendering.Layer.findAnnotations.aboutAnnotations}
+  ///
+  /// See also:
+  ///
+  ///  * [find], which is similar but returns the first annotation found at the
+  ///    given position.
+  ///  * [AnnotatedRegionLayer], for placing values in the layer tree.
+  AnnotationResult<S> findAllAnnotations<S extends Object>(Offset localPosition) {
+    final AnnotationResult<S> result = AnnotationResult<S>();
+    findAnnotations<S>(result, localPosition, onlyFirst: false);
+    return result;
+  }
+
+  /// Override this method to upload this layer to the engine.
+  ///
+  /// Return the engine layer for retained rendering. When there's no
+  /// corresponding engine layer, null is returned.
+  @protected
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]);
+
+  void _addToSceneWithRetainedRendering(ui.SceneBuilder builder) {
+    // There can't be a loop by adding a retained layer subtree whose
+    // _needsAddToScene is false.
+    //
+    // Proof by contradiction:
+    //
+    // If we introduce a loop, this retained layer must be appended to one of
+    // its descendant layers, say A. That means the child structure of A has
+    // changed so A's _needsAddToScene is true. This contradicts
+    // _needsAddToScene being false.
+    if (!_needsAddToScene && _engineLayer != null) {
+      builder.addRetained(_engineLayer!);
+      return;
+    }
+    addToScene(builder);
+    // Clearing the flag _after_ calling `addToScene`, not _before_. This is
+    // because `addToScene` calls children's `addToScene` methods, which may
+    // mark this layer as dirty.
+    _needsAddToScene = false;
+  }
+
+  /// The object responsible for creating this layer.
+  ///
+  /// Defaults to the value of [RenderObject.debugCreator] for the render object
+  /// that created this layer. Used in debug messages.
+  dynamic debugCreator;
+
+  @override
+  String toStringShort() => '${super.toStringShort()}${ owner == null ? " DETACHED" : ""}';
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Object>('owner', owner, level: parent != null ? DiagnosticLevel.hidden : DiagnosticLevel.info, defaultValue: null));
+    properties.add(DiagnosticsProperty<dynamic>('creator', debugCreator, defaultValue: null, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<String>('engine layer', describeIdentity(_engineLayer)));
+  }
+}
+
+/// A composited layer containing a [Picture].
+///
+/// Picture layers are always leaves in the layer tree.
+class PictureLayer extends Layer {
+  /// Creates a leaf layer for the layer tree.
+  PictureLayer(this.canvasBounds);
+
+  /// The bounds that were used for the canvas that drew this layer's [picture].
+  ///
+  /// This is purely advisory. It is included in the information dumped with
+  /// [debugDumpLayerTree] (which can be triggered by pressing "L" when using
+  /// "flutter run" at the console), which can help debug why certain drawing
+  /// commands are being culled.
+  final Rect canvasBounds;
+
+  /// The picture recorded for this layer.
+  ///
+  /// The picture's coordinate system matches this layer's coordinate system.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  ui.Picture? get picture => _picture;
+  ui.Picture? _picture;
+  set picture(ui.Picture? picture) {
+    markNeedsAddToScene();
+    _picture = picture;
+  }
+
+  /// Hints that the painting in this layer is complex and would benefit from
+  /// caching.
+  ///
+  /// If this hint is not set, the compositor will apply its own heuristics to
+  /// decide whether the this layer is complex enough to benefit from caching.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  bool get isComplexHint => _isComplexHint;
+  bool _isComplexHint = false;
+  set isComplexHint(bool value) {
+    if (value != _isComplexHint) {
+      _isComplexHint = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  /// Hints that the painting in this layer is likely to change next frame.
+  ///
+  /// This hint tells the compositor not to cache this layer because the cache
+  /// will not be used in the future. If this hint is not set, the compositor
+  /// will apply its own heuristics to decide whether this layer is likely to be
+  /// reused in the future.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  bool get willChangeHint => _willChangeHint;
+  bool _willChangeHint = false;
+  set willChangeHint(bool value) {
+    if (value != _willChangeHint) {
+      _willChangeHint = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    assert(picture != null);
+    builder.addPicture(layerOffset, picture!, isComplexHint: isComplexHint, willChangeHint: willChangeHint);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Rect>('paint bounds', canvasBounds));
+    properties.add(DiagnosticsProperty<String>('picture', describeIdentity(_picture)));
+    properties.add(DiagnosticsProperty<String>(
+      'raster cache hints',
+      'isComplex = $isComplexHint, willChange = $willChangeHint'),
+    );
+  }
+
+  @override
+  bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
+    return false;
+  }
+}
+
+/// A composited layer that maps a backend texture to a rectangle.
+///
+/// Backend textures are images that can be applied (mapped) to an area of the
+/// Flutter view. They are created, managed, and updated using a
+/// platform-specific texture registry. This is typically done by a plugin
+/// that integrates with host platform video player, camera, or OpenGL APIs,
+/// or similar image sources.
+///
+/// A texture layer refers to its backend texture using an integer ID. Texture
+/// IDs are obtained from the texture registry and are scoped to the Flutter
+/// view. Texture IDs may be reused after deregistration, at the discretion
+/// of the registry. The use of texture IDs currently unknown to the registry
+/// will silently result in a blank rectangle.
+///
+/// Once inserted into the layer tree, texture layers are repainted autonomously
+/// as dictated by the backend (e.g. on arrival of a video frame). Such
+/// repainting generally does not involve executing Dart code.
+///
+/// Texture layers are always leaves in the layer tree.
+///
+/// See also:
+///
+///  * <https://api.flutter.dev/javadoc/io/flutter/view/TextureRegistry.html>
+///    for how to create and manage backend textures on Android.
+///  * <https://api.flutter.dev/objcdoc/Protocols/FlutterTextureRegistry.html>
+///    for how to create and manage backend textures on iOS.
+class TextureLayer extends Layer {
+  /// Creates a texture layer bounded by [rect] and with backend texture
+  /// identified by [textureId], if [freeze] is true new texture frames will not be
+  /// populated to the texture, and use [filterQuality] to set layer's [FilterQuality].
+  TextureLayer({
+    required this.rect,
+    required this.textureId,
+    this.freeze = false,
+    this.filterQuality = ui.FilterQuality.low,
+  }) : assert(rect != null),
+       assert(textureId != null);
+
+  /// Bounding rectangle of this layer.
+  final Rect rect;
+
+  /// The identity of the backend texture.
+  final int textureId;
+
+  /// When true the texture that will not be updated with new frames.
+  ///
+  /// This is used when resizing an embedded  Android views: When resizing there
+  /// is a short period during which the framework cannot tell if the newest
+  /// texture frame has the previous or new size, to workaround this the
+  /// framework "freezes" the texture just before resizing the Android view and
+  /// un-freezes it when it is certain that a frame with the new size is ready.
+  final bool freeze;
+
+  /// {@macro flutter.widgets.Texture.filterQuality}
+  final ui.FilterQuality filterQuality;
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    final Rect shiftedRect = layerOffset == Offset.zero ? rect : rect.shift(layerOffset);
+    builder.addTexture(
+      textureId,
+      offset: shiftedRect.topLeft,
+      width: shiftedRect.width,
+      height: shiftedRect.height,
+      freeze: freeze,
+      filterQuality: filterQuality,
+    );
+  }
+
+  @override
+  bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
+    return false;
+  }
+}
+
+/// A layer that shows an embedded [UIView](https://developer.apple.com/documentation/uikit/uiview)
+/// on iOS.
+class PlatformViewLayer extends Layer {
+  /// Creates a platform view layer.
+  ///
+  /// The `rect` and `viewId` parameters must not be null.
+  PlatformViewLayer({
+    required this.rect,
+    required this.viewId,
+  }) : assert(rect != null),
+       assert(viewId != null);
+
+  /// Bounding rectangle of this layer in the global coordinate space.
+  final Rect rect;
+
+  /// The unique identifier of the UIView displayed on this layer.
+  ///
+  /// A UIView with this identifier must have been created by [PlatformViewsService.initUiKitView].
+  final int viewId;
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    final Rect shiftedRect = layerOffset == Offset.zero ? rect : rect.shift(layerOffset);
+    builder.addPlatformView(
+      viewId,
+      offset: shiftedRect.topLeft,
+      width: shiftedRect.width,
+      height: shiftedRect.height,
+    );
+  }
+}
+
+/// A layer that indicates to the compositor that it should display
+/// certain performance statistics within it.
+///
+/// Performance overlay layers are always leaves in the layer tree.
+class PerformanceOverlayLayer extends Layer {
+  /// Creates a layer that displays a performance overlay.
+  PerformanceOverlayLayer({
+    required Rect overlayRect,
+    required this.optionsMask,
+    required this.rasterizerThreshold,
+    required this.checkerboardRasterCacheImages,
+    required this.checkerboardOffscreenLayers,
+  }) : _overlayRect = overlayRect;
+
+  /// The rectangle in this layer's coordinate system that the overlay should occupy.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  Rect get overlayRect => _overlayRect;
+  Rect _overlayRect;
+  set overlayRect(Rect value) {
+    if (value != _overlayRect) {
+      _overlayRect = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  /// The mask is created by shifting 1 by the index of the specific
+  /// [PerformanceOverlayOption] to enable.
+  final int optionsMask;
+
+  /// The rasterizer threshold is an integer specifying the number of frame
+  /// intervals that the rasterizer must miss before it decides that the frame
+  /// is suitable for capturing an SkPicture trace for further analysis.
+  final int rasterizerThreshold;
+
+  /// Whether the raster cache should checkerboard cached entries.
+  ///
+  /// The compositor can sometimes decide to cache certain portions of the
+  /// widget hierarchy. Such portions typically don't change often from frame to
+  /// frame and are expensive to render. This can speed up overall rendering. However,
+  /// there is certain upfront cost to constructing these cache entries. And, if
+  /// the cache entries are not used very often, this cost may not be worth the
+  /// speedup in rendering of subsequent frames. If the developer wants to be certain
+  /// that populating the raster cache is not causing stutters, this option can be
+  /// set. Depending on the observations made, hints can be provided to the compositor
+  /// that aid it in making better decisions about caching.
+  final bool checkerboardRasterCacheImages;
+
+  /// Whether the compositor should checkerboard layers that are rendered to offscreen
+  /// bitmaps. This can be useful for debugging rendering performance.
+  ///
+  /// Render target switches are caused by using opacity layers (via a [FadeTransition] or
+  /// [Opacity] widget), clips, shader mask layers, etc. Selecting a new render target
+  /// and merging it with the rest of the scene has a performance cost. This can sometimes
+  /// be avoided by using equivalent widgets that do not require these layers (for example,
+  /// replacing an [Opacity] widget with an [widgets.Image] using a [BlendMode]).
+  final bool checkerboardOffscreenLayers;
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    assert(optionsMask != null);
+    final Rect shiftedOverlayRect = layerOffset == Offset.zero ? overlayRect : overlayRect.shift(layerOffset);
+    builder.addPerformanceOverlay(optionsMask, shiftedOverlayRect);
+    builder.setRasterizerTracingThreshold(rasterizerThreshold);
+    builder.setCheckerboardRasterCacheImages(checkerboardRasterCacheImages);
+    builder.setCheckerboardOffscreenLayers(checkerboardOffscreenLayers);
+  }
+
+  @override
+  bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
+    return false;
+  }
+}
+
+/// A composited layer that has a list of children.
+///
+/// A [ContainerLayer] instance merely takes a list of children and inserts them
+/// into the composited rendering in order. There are subclasses of
+/// [ContainerLayer] which apply more elaborate effects in the process.
+class ContainerLayer extends Layer {
+  /// The first composited layer in this layer's child list.
+  Layer? get firstChild => _firstChild;
+  Layer? _firstChild;
+
+  /// The last composited layer in this layer's child list.
+  Layer? get lastChild => _lastChild;
+  Layer? _lastChild;
+
+  /// Returns whether this layer has at least one child layer.
+  bool get hasChildren => _firstChild != null;
+
+  /// Consider this layer as the root and build a scene (a tree of layers)
+  /// in the engine.
+  // The reason this method is in the `ContainerLayer` class rather than
+  // `PipelineOwner` or other singleton level is because this method can be used
+  // both to render the whole layer tree (e.g. a normal application frame) and
+  // to render a subtree (e.g. `OffsetLayer.toImage`).
+  ui.Scene buildScene(ui.SceneBuilder builder) {
+    List<PictureLayer>? temporaryLayers;
+    assert(() {
+      if (debugCheckElevationsEnabled) {
+        temporaryLayers = _debugCheckElevations();
+      }
+      return true;
+    }());
+    updateSubtreeNeedsAddToScene();
+    addToScene(builder);
+    // Clearing the flag _after_ calling `addToScene`, not _before_. This is
+    // because `addToScene` calls children's `addToScene` methods, which may
+    // mark this layer as dirty.
+    _needsAddToScene = false;
+    final ui.Scene scene = builder.build();
+    assert(() {
+      // We should remove any layers that got added to highlight the incorrect
+      // PhysicalModelLayers. If we don't, we'll end up adding duplicate layers
+      // or continuing to render stale outlines.
+      if (temporaryLayers != null) {
+        for (final PictureLayer temporaryLayer in temporaryLayers!) {
+          temporaryLayer.remove();
+        }
+      }
+      return true;
+    }());
+    return scene;
+  }
+
+  bool _debugUltimatePreviousSiblingOf(Layer child, { Layer? equals }) {
+    assert(child.attached == attached);
+    while (child.previousSibling != null) {
+      assert(child.previousSibling != child);
+      child = child.previousSibling!;
+      assert(child.attached == attached);
+    }
+    return child == equals;
+  }
+
+  bool _debugUltimateNextSiblingOf(Layer child, { Layer? equals }) {
+    assert(child.attached == attached);
+    while (child._nextSibling != null) {
+      assert(child._nextSibling != child);
+      child = child._nextSibling!;
+      assert(child.attached == attached);
+    }
+    return child == equals;
+  }
+
+  PictureLayer _highlightConflictingLayer(PhysicalModelLayer child) {
+    final ui.PictureRecorder recorder = ui.PictureRecorder();
+    final Canvas canvas = Canvas(recorder);
+    canvas.drawPath(
+      child.clipPath!,
+      Paint()
+        ..color = const Color(0xFFAA0000)
+        ..style = PaintingStyle.stroke
+        // The elevation may be 0 or otherwise too small to notice.
+        // Adding 10 to it makes it more visually obvious.
+        ..strokeWidth = child.elevation! + 10.0,
+    );
+    final PictureLayer pictureLayer = PictureLayer(child.clipPath!.getBounds())
+      ..picture = recorder.endRecording()
+      ..debugCreator = child;
+    child.append(pictureLayer);
+    return pictureLayer;
+  }
+
+  List<PictureLayer> _processConflictingPhysicalLayers(PhysicalModelLayer predecessor, PhysicalModelLayer child) {
+    FlutterError.reportError(FlutterErrorDetails(
+      exception: FlutterError('Painting order is out of order with respect to elevation.\n'
+                              'See https://api.flutter.dev/flutter/rendering/debugCheckElevationsEnabled.html '
+                              'for more details.'),
+      library: 'rendering library',
+      context: ErrorDescription('during compositing'),
+      informationCollector: () {
+        return <DiagnosticsNode>[
+          child.toDiagnosticsNode(name: 'Attempted to composite layer', style: DiagnosticsTreeStyle.errorProperty),
+          predecessor.toDiagnosticsNode(name: 'after layer', style: DiagnosticsTreeStyle.errorProperty),
+          ErrorDescription('which occupies the same area at a higher elevation.'),
+        ];
+      },
+    ));
+    return <PictureLayer>[
+      _highlightConflictingLayer(predecessor),
+      _highlightConflictingLayer(child),
+    ];
+  }
+
+  /// Checks that no [PhysicalModelLayer] would paint after another overlapping
+  /// [PhysicalModelLayer] that has a higher elevation.
+  ///
+  /// Returns a list of [PictureLayer] objects it added to the tree to highlight
+  /// bad nodes. These layers should be removed from the tree after building the
+  /// [Scene].
+  List<PictureLayer> _debugCheckElevations() {
+    final List<PhysicalModelLayer> physicalModelLayers = depthFirstIterateChildren().whereType<PhysicalModelLayer>().toList();
+    final List<PictureLayer> addedLayers = <PictureLayer>[];
+
+    for (int i = 0; i < physicalModelLayers.length; i++) {
+      final PhysicalModelLayer physicalModelLayer = physicalModelLayers[i];
+      assert(
+        physicalModelLayer.lastChild?.debugCreator != physicalModelLayer,
+        'debugCheckElevations has either already visited this layer or failed '
+        'to remove the added picture from it.',
+      );
+      double accumulatedElevation = physicalModelLayer.elevation!;
+      Layer? ancestor = physicalModelLayer.parent;
+      while (ancestor != null) {
+        if (ancestor is PhysicalModelLayer) {
+          accumulatedElevation += ancestor.elevation!;
+        }
+        ancestor = ancestor.parent;
+      }
+      for (int j = 0; j <= i; j++) {
+        final PhysicalModelLayer predecessor = physicalModelLayers[j];
+        double predecessorAccumulatedElevation = predecessor.elevation!;
+        ancestor = predecessor.parent;
+        while (ancestor != null) {
+          if (ancestor == predecessor) {
+            continue;
+          }
+          if (ancestor is PhysicalModelLayer) {
+            predecessorAccumulatedElevation += ancestor.elevation!;
+          }
+          ancestor = ancestor.parent;
+        }
+        if (predecessorAccumulatedElevation <= accumulatedElevation) {
+          continue;
+        }
+        final Path intersection = Path.combine(
+          PathOperation.intersect,
+          predecessor._debugTransformedClipPath,
+          physicalModelLayer._debugTransformedClipPath,
+        );
+        if (intersection != null && intersection.computeMetrics().any((ui.PathMetric metric) => metric.length > 0)) {
+          addedLayers.addAll(_processConflictingPhysicalLayers(predecessor, physicalModelLayer));
+        }
+      }
+    }
+    return addedLayers;
+  }
+
+  @override
+  void updateSubtreeNeedsAddToScene() {
+    super.updateSubtreeNeedsAddToScene();
+    Layer? child = firstChild;
+    while (child != null) {
+      child.updateSubtreeNeedsAddToScene();
+      _needsAddToScene = _needsAddToScene || child._needsAddToScene;
+      child = child.nextSibling;
+    }
+  }
+
+  @override
+  bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
+    for (Layer? child = lastChild; child != null; child = child.previousSibling) {
+      final bool isAbsorbed = child.findAnnotations<S>(result, localPosition, onlyFirst: onlyFirst);
+      if (isAbsorbed)
+        return true;
+      if (onlyFirst && result.entries.isNotEmpty)
+        return isAbsorbed;
+    }
+    return false;
+  }
+
+  @override
+  void attach(Object owner) {
+    super.attach(owner);
+    Layer? child = firstChild;
+    while (child != null) {
+      child.attach(owner);
+      child = child.nextSibling;
+    }
+  }
+
+  @override
+  void detach() {
+    super.detach();
+    Layer? child = firstChild;
+    while (child != null) {
+      child.detach();
+      child = child.nextSibling;
+    }
+  }
+
+  /// Adds the given layer to the end of this layer's child list.
+  void append(Layer child) {
+    assert(child != this);
+    assert(child != firstChild);
+    assert(child != lastChild);
+    assert(child.parent == null);
+    assert(!child.attached);
+    assert(child.nextSibling == null);
+    assert(child.previousSibling == null);
+    assert(() {
+      Layer node = this;
+      while (node.parent != null)
+        node = node.parent!;
+      assert(node != child); // indicates we are about to create a cycle
+      return true;
+    }());
+    adoptChild(child);
+    child._previousSibling = lastChild;
+    if (lastChild != null)
+      lastChild!._nextSibling = child;
+    _lastChild = child;
+    _firstChild ??= child;
+    assert(child.attached == attached);
+  }
+
+  // Implementation of [Layer.remove].
+  void _removeChild(Layer child) {
+    assert(child.parent == this);
+    assert(child.attached == attached);
+    assert(_debugUltimatePreviousSiblingOf(child, equals: firstChild));
+    assert(_debugUltimateNextSiblingOf(child, equals: lastChild));
+    if (child._previousSibling == null) {
+      assert(_firstChild == child);
+      _firstChild = child._nextSibling;
+    } else {
+      child._previousSibling!._nextSibling = child.nextSibling;
+    }
+    if (child._nextSibling == null) {
+      assert(lastChild == child);
+      _lastChild = child.previousSibling;
+    } else {
+      child.nextSibling!._previousSibling = child.previousSibling;
+    }
+    assert((firstChild == null) == (lastChild == null));
+    assert(firstChild == null || firstChild!.attached == attached);
+    assert(lastChild == null || lastChild!.attached == attached);
+    assert(firstChild == null || _debugUltimateNextSiblingOf(firstChild!, equals: lastChild));
+    assert(lastChild == null || _debugUltimatePreviousSiblingOf(lastChild!, equals: firstChild));
+    child._previousSibling = null;
+    child._nextSibling = null;
+    dropChild(child);
+    assert(!child.attached);
+  }
+
+  /// Removes all of this layer's children from its child list.
+  void removeAllChildren() {
+    Layer? child = firstChild;
+    while (child != null) {
+      final Layer? next = child.nextSibling;
+      child._previousSibling = null;
+      child._nextSibling = null;
+      assert(child.attached == attached);
+      dropChild(child);
+      child = next;
+    }
+    _firstChild = null;
+    _lastChild = null;
+  }
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    addChildrenToScene(builder, layerOffset);
+  }
+
+  /// Uploads all of this layer's children to the engine.
+  ///
+  /// This method is typically used by [addToScene] to insert the children into
+  /// the scene. Subclasses of [ContainerLayer] typically override [addToScene]
+  /// to apply effects to the scene using the [SceneBuilder] API, then insert
+  /// their children using [addChildrenToScene], then reverse the aforementioned
+  /// effects before returning from [addToScene].
+  void addChildrenToScene(ui.SceneBuilder builder, [ Offset childOffset = Offset.zero ]) {
+    Layer? child = firstChild;
+    while (child != null) {
+      if (childOffset == Offset.zero) {
+        child._addToSceneWithRetainedRendering(builder);
+      } else {
+        child.addToScene(builder, childOffset);
+      }
+      child = child.nextSibling;
+    }
+  }
+
+  /// Applies the transform that would be applied when compositing the given
+  /// child to the given matrix.
+  ///
+  /// Specifically, this should apply the transform that is applied to child's
+  /// _origin_. When using [applyTransform] with a chain of layers, results will
+  /// be unreliable unless the deepest layer in the chain collapses the
+  /// `layerOffset` in [addToScene] to zero, meaning that it passes
+  /// [Offset.zero] to its children, and bakes any incoming `layerOffset` into
+  /// the [SceneBuilder] as (for instance) a transform (which is then also
+  /// included in the transformation applied by [applyTransform]).
+  ///
+  /// For example, if [addToScene] applies the `layerOffset` and then
+  /// passes [Offset.zero] to the children, then it should be included in the
+  /// transform applied here, whereas if [addToScene] just passes the
+  /// `layerOffset` to the child, then it should not be included in the
+  /// transform applied here.
+  ///
+  /// This method is only valid immediately after [addToScene] has been called,
+  /// before any of the properties have been changed.
+  ///
+  /// The default implementation does nothing, since [ContainerLayer], by
+  /// default, composites its children at the origin of the [ContainerLayer]
+  /// itself.
+  ///
+  /// The `child` argument should generally not be null, since in principle a
+  /// layer could transform each child independently. However, certain layers
+  /// may explicitly allow null as a value, for example if they know that they
+  /// transform all their children identically.
+  ///
+  /// The `transform` argument must not be null.
+  ///
+  /// Used by [FollowerLayer] to transform its child to a [LeaderLayer]'s
+  /// position.
+  void applyTransform(Layer? child, Matrix4 transform) {
+    assert(child != null);
+    assert(transform != null);
+  }
+
+  /// Returns the descendants of this layer in depth first order.
+  @visibleForTesting
+  List<Layer> depthFirstIterateChildren() {
+    if (firstChild == null)
+      return <Layer>[];
+    final List<Layer> children = <Layer>[];
+    Layer? child = firstChild;
+    while(child != null) {
+      children.add(child);
+      if (child is ContainerLayer) {
+        children.addAll(child.depthFirstIterateChildren());
+      }
+      child = child.nextSibling;
+    }
+    return children;
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    final List<DiagnosticsNode> children = <DiagnosticsNode>[];
+    if (firstChild == null)
+      return children;
+    Layer? child = firstChild;
+    int count = 1;
+    while (true) {
+      children.add(child!.toDiagnosticsNode(name: 'child $count'));
+      if (child == lastChild)
+        break;
+      count += 1;
+      child = child.nextSibling;
+    }
+    return children;
+  }
+}
+
+/// A layer that is displayed at an offset from its parent layer.
+///
+/// Offset layers are key to efficient repainting because they are created by
+/// repaint boundaries in the [RenderObject] tree (see
+/// [RenderObject.isRepaintBoundary]). When a render object that is a repaint
+/// boundary is asked to paint at given offset in a [PaintingContext], the
+/// render object first checks whether it needs to repaint itself. If not, it
+/// reuses its existing [OffsetLayer] (and its entire subtree) by mutating its
+/// [offset] property, cutting off the paint walk.
+class OffsetLayer extends ContainerLayer {
+  /// Creates an offset layer.
+  ///
+  /// By default, [offset] is zero. It must be non-null before the compositing
+  /// phase of the pipeline.
+  OffsetLayer({ Offset offset = Offset.zero }) : _offset = offset;
+
+  /// Offset from parent in the parent's coordinate system.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  ///
+  /// The [offset] property must be non-null before the compositing phase of the
+  /// pipeline.
+  Offset get offset => _offset;
+  Offset _offset;
+  set offset(Offset value) {
+    if (value != _offset) {
+      markNeedsAddToScene();
+    }
+    _offset = value;
+  }
+
+  @override
+  bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
+    return super.findAnnotations<S>(result, localPosition - offset, onlyFirst: onlyFirst);
+  }
+
+  @override
+  void applyTransform(Layer? child, Matrix4 transform) {
+    assert(child != null);
+    assert(transform != null);
+    transform.multiply(Matrix4.translationValues(offset.dx, offset.dy, 0.0));
+  }
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    // Skia has a fast path for concatenating scale/translation only matrices.
+    // Hence pushing a translation-only transform layer should be fast. For
+    // retained rendering, we don't want to push the offset down to each leaf
+    // node. Otherwise, changing an offset layer on the very high level could
+    // cascade the change to too many leaves.
+    engineLayer = builder.pushOffset(
+      layerOffset.dx + offset.dx,
+      layerOffset.dy + offset.dy,
+      oldLayer: _engineLayer as ui.OffsetEngineLayer?,
+    );
+    addChildrenToScene(builder);
+    builder.pop();
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Offset>('offset', offset));
+  }
+
+  /// Capture an image of the current state of this layer and its children.
+  ///
+  /// The returned [ui.Image] has uncompressed raw RGBA bytes, will be offset
+  /// by the top-left corner of [bounds], and have dimensions equal to the size
+  /// of [bounds] multiplied by [pixelRatio].
+  ///
+  /// The [pixelRatio] describes the scale between the logical pixels and the
+  /// size of the output image. It is independent of the
+  /// [dart:ui.FlutterView.devicePixelRatio] for the device, so specifying 1.0
+  /// (the default) will give you a 1:1 mapping between logical pixels and the
+  /// output pixels in the image.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderRepaintBoundary.toImage] for a similar API at the render object level.
+  ///  * [dart:ui.Scene.toImage] for more information about the image returned.
+  Future<ui.Image> toImage(Rect bounds, { double pixelRatio = 1.0 }) async {
+    assert(bounds != null);
+    assert(pixelRatio != null);
+    final ui.SceneBuilder builder = ui.SceneBuilder();
+    final Matrix4 transform = Matrix4.translationValues(
+      (-bounds.left  - offset.dx) * pixelRatio,
+      (-bounds.top - offset.dy) * pixelRatio,
+      0.0,
+    );
+    transform.scale(pixelRatio, pixelRatio);
+    builder.pushTransform(transform.storage);
+    final ui.Scene scene = buildScene(builder);
+
+    try {
+      // Size is rounded up to the next pixel to make sure we don't clip off
+      // anything.
+      return await scene.toImage(
+        (pixelRatio * bounds.width).ceil(),
+        (pixelRatio * bounds.height).ceil(),
+      );
+    } finally {
+      scene.dispose();
+    }
+  }
+}
+
+/// A composite layer that clips its children using a rectangle.
+///
+/// When debugging, setting [debugDisableClipLayers] to true will cause this
+/// layer to be skipped (directly replaced by its children). This can be helpful
+/// to track down the cause of performance problems.
+class ClipRectLayer extends ContainerLayer {
+  /// Creates a layer with a rectangular clip.
+  ///
+  /// The [clipRect] argument must not be null before the compositing phase of
+  /// the pipeline.
+  ///
+  /// The [clipBehavior] argument must not be null, and must not be [Clip.none].
+  ClipRectLayer({
+    Rect? clipRect,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : _clipRect = clipRect,
+       _clipBehavior = clipBehavior,
+       assert(clipBehavior != null),
+       assert(clipBehavior != Clip.none);
+
+  /// The rectangle to clip in the parent's coordinate system.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  Rect? get clipRect => _clipRect;
+  Rect? _clipRect;
+  set clipRect(Rect? value) {
+    if (value != _clipRect) {
+      _clipRect = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  /// {@template flutter.rendering.ClipRectLayer.clipBehavior}
+  /// Controls how to clip.
+  ///
+  /// Must not be set to null or [Clip.none].
+  /// {@endtemplate}
+  ///
+  /// Defaults to [Clip.hardEdge].
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    assert(value != Clip.none);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  @override
+  bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
+    if (!clipRect!.contains(localPosition))
+      return false;
+    return super.findAnnotations<S>(result, localPosition, onlyFirst: onlyFirst);
+  }
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    assert(clipRect != null);
+    assert(clipBehavior != null);
+    bool enabled = true;
+    assert(() {
+      enabled = !debugDisableClipLayers;
+      return true;
+    }());
+    if (enabled) {
+      final Rect shiftedClipRect = layerOffset == Offset.zero ? clipRect! : clipRect!.shift(layerOffset);
+      engineLayer = builder.pushClipRect(
+        shiftedClipRect,
+        clipBehavior: clipBehavior,
+        oldLayer: _engineLayer as ui.ClipRectEngineLayer?,
+      );
+    } else {
+      engineLayer = null;
+    }
+    addChildrenToScene(builder, layerOffset);
+    if (enabled)
+      builder.pop();
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Rect>('clipRect', clipRect));
+    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior));
+  }
+}
+
+/// A composite layer that clips its children using a rounded rectangle.
+///
+/// When debugging, setting [debugDisableClipLayers] to true will cause this
+/// layer to be skipped (directly replaced by its children). This can be helpful
+/// to track down the cause of performance problems.
+class ClipRRectLayer extends ContainerLayer {
+  /// Creates a layer with a rounded-rectangular clip.
+  ///
+  /// The [clipRRect] and [clipBehavior] properties must be non-null before the
+  /// compositing phase of the pipeline.
+  ClipRRectLayer({
+    RRect? clipRRect,
+    Clip clipBehavior = Clip.antiAlias,
+  }) : _clipRRect = clipRRect,
+       _clipBehavior = clipBehavior,
+       assert(clipBehavior != null),
+       assert(clipBehavior != Clip.none);
+
+  /// The rounded-rect to clip in the parent's coordinate system.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  RRect? get clipRRect => _clipRRect;
+  RRect? _clipRRect;
+  set clipRRect(RRect? value) {
+    if (value != _clipRRect) {
+      _clipRRect = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  /// {@macro flutter.rendering.ClipRectLayer.clipBehavior}
+  ///
+  /// Defaults to [Clip.antiAlias].
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    assert(value != Clip.none);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  @override
+  bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
+    if (!clipRRect!.contains(localPosition))
+      return false;
+    return super.findAnnotations<S>(result, localPosition, onlyFirst: onlyFirst);
+  }
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    assert(clipRRect != null);
+    assert(clipBehavior != null);
+    bool enabled = true;
+    assert(() {
+      enabled = !debugDisableClipLayers;
+      return true;
+    }());
+    if (enabled) {
+      final RRect shiftedClipRRect = layerOffset == Offset.zero ? clipRRect! : clipRRect!.shift(layerOffset);
+      engineLayer = builder.pushClipRRect(
+        shiftedClipRRect,
+        clipBehavior: clipBehavior,
+        oldLayer: _engineLayer as ui.ClipRRectEngineLayer?,
+      );
+    } else {
+      engineLayer = null;
+    }
+    addChildrenToScene(builder, layerOffset);
+    if (enabled)
+      builder.pop();
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<RRect>('clipRRect', clipRRect));
+    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior));
+  }
+}
+
+/// A composite layer that clips its children using a path.
+///
+/// When debugging, setting [debugDisableClipLayers] to true will cause this
+/// layer to be skipped (directly replaced by its children). This can be helpful
+/// to track down the cause of performance problems.
+class ClipPathLayer extends ContainerLayer {
+  /// Creates a layer with a path-based clip.
+  ///
+  /// The [clipPath] and [clipBehavior] properties must be non-null before the
+  /// compositing phase of the pipeline.
+  ClipPathLayer({
+    Path? clipPath,
+    Clip clipBehavior = Clip.antiAlias,
+  }) : _clipPath = clipPath,
+       _clipBehavior = clipBehavior,
+       assert(clipBehavior != null),
+       assert(clipBehavior != Clip.none);
+
+  /// The path to clip in the parent's coordinate system.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  Path? get clipPath => _clipPath;
+  Path? _clipPath;
+  set clipPath(Path? value) {
+    if (value != _clipPath) {
+      _clipPath = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  /// {@macro flutter.rendering.ClipRectLayer.clipBehavior}
+  ///
+  /// Defaults to [Clip.antiAlias].
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    assert(value != Clip.none);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  @override
+  bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
+    if (!clipPath!.contains(localPosition))
+      return false;
+    return super.findAnnotations<S>(result, localPosition, onlyFirst: onlyFirst);
+  }
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    assert(clipPath != null);
+    assert(clipBehavior != null);
+    bool enabled = true;
+    assert(() {
+      enabled = !debugDisableClipLayers;
+      return true;
+    }());
+    if (enabled) {
+      final Path shiftedPath = layerOffset == Offset.zero ? clipPath! : clipPath!.shift(layerOffset);
+      engineLayer = builder.pushClipPath(
+        shiftedPath,
+        clipBehavior: clipBehavior,
+        oldLayer: _engineLayer as ui.ClipPathEngineLayer?,
+      );
+    } else {
+      engineLayer = null;
+    }
+    addChildrenToScene(builder, layerOffset);
+    if (enabled)
+      builder.pop();
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior));
+  }
+}
+
+/// A composite layer that applies a [ColorFilter] to its children.
+class ColorFilterLayer extends ContainerLayer {
+  /// Creates a layer that applies a [ColorFilter] to its children.
+  ///
+  /// The [colorFilter] property must be non-null before the compositing phase
+  /// of the pipeline.
+  ColorFilterLayer({
+    ColorFilter? colorFilter,
+  }) : _colorFilter = colorFilter;
+
+  /// The color filter to apply to children.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  ColorFilter? get colorFilter => _colorFilter;
+  ColorFilter? _colorFilter;
+  set colorFilter(ColorFilter? value) {
+    assert(value != null);
+    if (value != _colorFilter) {
+      _colorFilter = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    assert(colorFilter != null);
+    engineLayer = builder.pushColorFilter(
+      colorFilter!,
+      oldLayer: _engineLayer as ui.ColorFilterEngineLayer?,
+    );
+    addChildrenToScene(builder, layerOffset);
+    builder.pop();
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ColorFilter>('colorFilter', colorFilter));
+  }
+}
+
+/// A composite layer that applies an [ImageFilter] to its children.
+class ImageFilterLayer extends ContainerLayer {
+  /// Creates a layer that applies an [ImageFilter] to its children.
+  ///
+  /// The [imageFilter] property must be non-null before the compositing phase
+  /// of the pipeline.
+  ImageFilterLayer({
+    ui.ImageFilter? imageFilter,
+  }) : _imageFilter = imageFilter;
+
+  /// The image filter to apply to children.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  ui.ImageFilter? get imageFilter => _imageFilter;
+  ui.ImageFilter? _imageFilter;
+  set imageFilter(ui.ImageFilter? value) {
+    assert(value != null);
+    if (value != _imageFilter) {
+      _imageFilter = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    assert(imageFilter != null);
+    engineLayer = builder.pushImageFilter(
+      imageFilter!,
+      oldLayer: _engineLayer as ui.ImageFilterEngineLayer?,
+    );
+    addChildrenToScene(builder, layerOffset);
+    builder.pop();
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ui.ImageFilter>('imageFilter', imageFilter));
+  }
+}
+
+/// A composited layer that applies a given transformation matrix to its
+/// children.
+///
+/// This class inherits from [OffsetLayer] to make it one of the layers that
+/// can be used at the root of a [RenderObject] hierarchy.
+class TransformLayer extends OffsetLayer {
+  /// Creates a transform layer.
+  ///
+  /// The [transform] and [offset] properties must be non-null before the
+  /// compositing phase of the pipeline.
+  TransformLayer({ Matrix4? transform, Offset offset = Offset.zero })
+    : _transform = transform,
+      super(offset: offset);
+
+  /// The matrix to apply.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  ///
+  /// This transform is applied before [offset], if both are set.
+  ///
+  /// The [transform] property must be non-null before the compositing phase of
+  /// the pipeline.
+  Matrix4? get transform => _transform;
+  Matrix4? _transform;
+  set transform(Matrix4? value) {
+    assert(value != null);
+    assert(value!.storage.every((double component) => component.isFinite));
+    if (value == _transform)
+      return;
+    _transform = value;
+    _inverseDirty = true;
+    markNeedsAddToScene();
+  }
+
+  Matrix4? _lastEffectiveTransform;
+  Matrix4? _invertedTransform;
+  bool _inverseDirty = true;
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    assert(transform != null);
+    _lastEffectiveTransform = transform;
+    final Offset totalOffset = offset + layerOffset;
+    if (totalOffset != Offset.zero) {
+      _lastEffectiveTransform = Matrix4.translationValues(totalOffset.dx, totalOffset.dy, 0.0)
+        ..multiply(_lastEffectiveTransform!);
+    }
+    engineLayer = builder.pushTransform(
+      _lastEffectiveTransform!.storage,
+      oldLayer: _engineLayer as ui.TransformEngineLayer?,
+    );
+    addChildrenToScene(builder);
+    builder.pop();
+  }
+
+  Offset? _transformOffset(Offset localPosition) {
+    if (_inverseDirty) {
+      _invertedTransform = Matrix4.tryInvert(
+        PointerEvent.removePerspectiveTransform(transform!)
+      );
+      _inverseDirty = false;
+    }
+    if (_invertedTransform == null)
+      return null;
+
+    return MatrixUtils.transformPoint(_invertedTransform!, localPosition);
+  }
+
+  @override
+  bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
+    final Offset? transformedOffset = _transformOffset(localPosition);
+    if (transformedOffset == null)
+      return false;
+    return super.findAnnotations<S>(result, transformedOffset, onlyFirst: onlyFirst);
+  }
+
+  @override
+  void applyTransform(Layer? child, Matrix4 transform) {
+    assert(child != null);
+    assert(transform != null);
+    assert(_lastEffectiveTransform != null || this.transform != null);
+    if (_lastEffectiveTransform == null) {
+      transform.multiply(this.transform!);
+    } else {
+      transform.multiply(_lastEffectiveTransform!);
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(TransformProperty('transform', transform));
+  }
+}
+
+/// A composited layer that makes its children partially transparent.
+///
+/// When debugging, setting [debugDisableOpacityLayers] to true will cause this
+/// layer to be skipped (directly replaced by its children). This can be helpful
+/// to track down the cause of performance problems.
+///
+/// Try to avoid an [OpacityLayer] with no children. Remove that layer if
+/// possible to save some tree walks.
+class OpacityLayer extends ContainerLayer {
+  /// Creates an opacity layer.
+  ///
+  /// The [alpha] property must be non-null before the compositing phase of
+  /// the pipeline.
+  OpacityLayer({
+    int? alpha,
+    Offset offset = Offset.zero,
+  }) : _alpha = alpha,
+       _offset = offset;
+
+  /// The amount to multiply into the alpha channel.
+  ///
+  /// The opacity is expressed as an integer from 0 to 255, where 0 is fully
+  /// transparent and 255 is fully opaque.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  int? get alpha => _alpha;
+  int? _alpha;
+  set alpha(int? value) {
+    assert(value != null);
+    if (value != _alpha) {
+      _alpha = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  /// Offset from parent in the parent's coordinate system.
+  Offset? get offset => _offset;
+  Offset? _offset;
+  set offset(Offset? value) {
+    if (value != _offset) {
+      _offset = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  @override
+  void applyTransform(Layer? child, Matrix4 transform) {
+    assert(child != null);
+    assert(transform != null);
+    transform.translate(offset!.dx, offset!.dy);
+  }
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    assert(alpha != null);
+    bool enabled = firstChild != null;  // don't add this layer if there's no child
+    assert(() {
+      enabled = enabled && !debugDisableOpacityLayers;
+      return true;
+    }());
+
+    if (enabled)
+      engineLayer = builder.pushOpacity(
+        alpha!,
+        offset: offset! + layerOffset,
+        oldLayer: _engineLayer as ui.OpacityEngineLayer?,
+      );
+    else
+      engineLayer = null;
+    addChildrenToScene(builder);
+    if (enabled)
+      builder.pop();
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(IntProperty('alpha', alpha));
+    properties.add(DiagnosticsProperty<Offset>('offset', offset));
+  }
+}
+
+/// A composited layer that applies a shader to its children.
+///
+/// The shader is only applied inside the given [maskRect]. The shader itself
+/// uses the top left of the [maskRect] as its origin.
+///
+/// The [maskRect] does not affect the positions of any child layers.
+class ShaderMaskLayer extends ContainerLayer {
+  /// Creates a shader mask layer.
+  ///
+  /// The [shader], [maskRect], and [blendMode] properties must be non-null
+  /// before the compositing phase of the pipeline.
+  ShaderMaskLayer({
+    Shader? shader,
+    Rect? maskRect,
+    BlendMode? blendMode,
+  }) : _shader = shader,
+       _maskRect = maskRect,
+       _blendMode = blendMode;
+
+  /// The shader to apply to the children.
+  ///
+  /// The origin of the shader (e.g. of the coordinate system used by the `from`
+  /// and `to` arguments to [ui.Gradient.linear]) is at the top left of the
+  /// [maskRect].
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  ///
+  /// See also:
+  ///
+  ///  * [ui.Gradient] and [ui.ImageShader], two shader types that can be used.
+  Shader? get shader => _shader;
+  Shader? _shader;
+  set shader(Shader? value) {
+    if (value != _shader) {
+      _shader = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  /// The position and size of the shader.
+  ///
+  /// The [shader] is only rendered inside this rectangle, using the top left of
+  /// the rectangle as its origin.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  Rect? get maskRect => _maskRect;
+  Rect? _maskRect;
+  set maskRect(Rect? value) {
+    if (value != _maskRect) {
+      _maskRect = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  /// The blend mode to apply when blending the shader with the children.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  BlendMode? get blendMode => _blendMode;
+  BlendMode? _blendMode;
+  set blendMode(BlendMode? value) {
+    if (value != _blendMode) {
+      _blendMode = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    assert(shader != null);
+    assert(maskRect != null);
+    assert(blendMode != null);
+    assert(layerOffset != null);
+    final Rect shiftedMaskRect = layerOffset == Offset.zero ? maskRect! : maskRect!.shift(layerOffset);
+    engineLayer = builder.pushShaderMask(
+      shader!,
+      shiftedMaskRect,
+      blendMode!,
+      oldLayer: _engineLayer as ui.ShaderMaskEngineLayer?,
+    );
+    addChildrenToScene(builder, layerOffset);
+    builder.pop();
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Shader>('shader', shader));
+    properties.add(DiagnosticsProperty<Rect>('maskRect', maskRect));
+    properties.add(DiagnosticsProperty<BlendMode>('blendMode', blendMode));
+  }
+}
+
+/// A composited layer that applies a filter to the existing contents of the scene.
+class BackdropFilterLayer extends ContainerLayer {
+  /// Creates a backdrop filter layer.
+  ///
+  /// The [filter] property must be non-null before the compositing phase of the
+  /// pipeline.
+  BackdropFilterLayer({ ui.ImageFilter? filter }) : _filter = filter;
+
+  /// The filter to apply to the existing contents of the scene.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  ui.ImageFilter? get filter => _filter;
+  ui.ImageFilter? _filter;
+  set filter(ui.ImageFilter? value) {
+    if (value != _filter) {
+      _filter = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    assert(filter != null);
+    engineLayer = builder.pushBackdropFilter(
+      filter!,
+      oldLayer: _engineLayer as ui.BackdropFilterEngineLayer?,
+    );
+    addChildrenToScene(builder, layerOffset);
+    builder.pop();
+  }
+}
+
+/// A composited layer that uses a physical model to producing lighting effects.
+///
+/// For example, the layer casts a shadow according to its geometry and the
+/// relative position of lights and other physically modeled objects in the
+/// scene.
+///
+/// When debugging, setting [debugDisablePhysicalShapeLayers] to true will cause this
+/// layer to be skipped (directly replaced by its children). This can be helpful
+/// to track down the cause of performance problems.
+class PhysicalModelLayer extends ContainerLayer {
+  /// Creates a composited layer that uses a physical model to producing
+  /// lighting effects.
+  ///
+  /// The [clipPath], [clipBehavior], [elevation], [color], and [shadowColor]
+  /// arguments must be non-null before the compositing phase of the pipeline.
+  PhysicalModelLayer({
+    Path? clipPath,
+    Clip clipBehavior = Clip.none,
+    double? elevation,
+    Color? color,
+    Color? shadowColor,
+  }) : _clipPath = clipPath,
+       _clipBehavior = clipBehavior,
+       _elevation = elevation,
+       _color = color,
+       _shadowColor = shadowColor;
+
+  /// The path to clip in the parent's coordinate system.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  Path? get clipPath => _clipPath;
+  Path? _clipPath;
+  set clipPath(Path? value) {
+    if (value != _clipPath) {
+      _clipPath = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  Path get _debugTransformedClipPath {
+    ContainerLayer? ancestor = parent;
+    final Matrix4 matrix = Matrix4.identity();
+    while (ancestor != null && ancestor.parent != null) {
+      ancestor.applyTransform(this, matrix);
+      ancestor = ancestor.parent;
+    }
+    return clipPath!.transform(matrix.storage);
+  }
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  /// The z-coordinate at which to place this physical object.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  ///
+  /// In tests, the [debugDisableShadows] flag is set to true by default.
+  /// Several widgets and render objects force all elevations to zero when this
+  /// flag is set. For this reason, this property will often be set to zero in
+  /// tests even if the layer should be raised. To verify the actual value,
+  /// consider setting [debugDisableShadows] to false in your test.
+  double? get elevation => _elevation;
+  double? _elevation;
+  set elevation(double? value) {
+    if (value != _elevation) {
+      _elevation = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  /// The background color.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  Color? get color => _color;
+  Color? _color;
+  set color(Color? value) {
+    if (value != _color) {
+      _color = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  /// The shadow color.
+  Color? get shadowColor => _shadowColor;
+  Color? _shadowColor;
+  set shadowColor(Color? value) {
+    if (value != _shadowColor) {
+      _shadowColor = value;
+      markNeedsAddToScene();
+    }
+  }
+
+  @override
+  bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
+    if (!clipPath!.contains(localPosition))
+      return false;
+    return super.findAnnotations<S>(result, localPosition, onlyFirst: onlyFirst);
+  }
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    assert(clipPath != null);
+    assert(clipBehavior != null);
+    assert(elevation != null);
+    assert(color != null);
+    assert(shadowColor != null);
+
+    bool enabled = true;
+    assert(() {
+      enabled = !debugDisablePhysicalShapeLayers;
+      return true;
+    }());
+    if (enabled) {
+      engineLayer = builder.pushPhysicalShape(
+        path: layerOffset == Offset.zero ? clipPath! : clipPath!.shift(layerOffset),
+        elevation: elevation!,
+        color: color!,
+        shadowColor: shadowColor,
+        clipBehavior: clipBehavior,
+        oldLayer: _engineLayer as ui.PhysicalShapeEngineLayer?,
+      );
+    } else {
+      engineLayer = null;
+    }
+    addChildrenToScene(builder, layerOffset);
+    if (enabled)
+      builder.pop();
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('elevation', elevation));
+    properties.add(ColorProperty('color', color));
+  }
+}
+
+/// An object that a [LeaderLayer] can register with.
+///
+/// An instance of this class should be provided as the [LeaderLayer.link] and
+/// the [FollowerLayer.link] properties to cause the [FollowerLayer] to follow
+/// the [LeaderLayer].
+///
+/// See also:
+///
+///  * [CompositedTransformTarget], the widget that creates a [LeaderLayer].
+///  * [CompositedTransformFollower], the widget that creates a [FollowerLayer].
+///  * [RenderLeaderLayer] and [RenderFollowerLayer], the corresponding
+///    render objects.
+class LayerLink {
+  /// The currently-registered [LeaderLayer], if any.
+  LeaderLayer? get leader => _leader;
+  LeaderLayer? _leader;
+
+  /// The total size of [leader]'s contents.
+  ///
+  /// Generally this should be set by the [RenderObject] that paints on the
+  /// registered [leader] layer (for instance a [RenderLeaderLayer] that shares
+  /// this link with its followers). This size may be outdated before and during
+  /// layout.
+  Size? leaderSize;
+
+  @override
+  String toString() => '${describeIdentity(this)}(${ _leader != null ? "<linked>" : "<dangling>" })';
+}
+
+/// A composited layer that can be followed by a [FollowerLayer].
+///
+/// This layer collapses the accumulated offset into a transform and passes
+/// [Offset.zero] to its child layers in the [addToScene]/[addChildrenToScene]
+/// methods, so that [applyTransform] will work reliably.
+class LeaderLayer extends ContainerLayer {
+  /// Creates a leader layer.
+  ///
+  /// The [link] property must not be null, and must not have been provided to
+  /// any other [LeaderLayer] layers that are [attached] to the layer tree at
+  /// the same time.
+  ///
+  /// The [offset] property must be non-null before the compositing phase of the
+  /// pipeline.
+  LeaderLayer({ required LayerLink link, this.offset = Offset.zero }) : assert(link != null), _link = link;
+
+  /// The object with which this layer should register.
+  ///
+  /// The link will be established when this layer is [attach]ed, and will be
+  /// cleared when this layer is [detach]ed.
+  LayerLink get link => _link;
+  set link(LayerLink value) {
+    assert(value != null);
+    _link = value;
+  }
+  LayerLink _link;
+
+  /// Offset from parent in the parent's coordinate system.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  ///
+  /// The [offset] property must be non-null before the compositing phase of the
+  /// pipeline.
+  Offset offset;
+
+  /// {@macro flutter.rendering.FollowerLayer.alwaysNeedsAddToScene}
+  @override
+  bool get alwaysNeedsAddToScene => true;
+
+  @override
+  void attach(Object owner) {
+    super.attach(owner);
+    assert(link.leader == null);
+    _lastOffset = null;
+    link._leader = this;
+  }
+
+  @override
+  void detach() {
+    assert(link.leader == this);
+    link._leader = null;
+    _lastOffset = null;
+    super.detach();
+  }
+
+  /// The offset the last time this layer was composited.
+  ///
+  /// This is reset to null when the layer is attached or detached, to help
+  /// catch cases where the follower layer ends up before the leader layer, but
+  /// not every case can be detected.
+  Offset? _lastOffset;
+
+  @override
+  bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
+    return super.findAnnotations<S>(result, localPosition - offset, onlyFirst: onlyFirst);
+  }
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    assert(offset != null);
+    _lastOffset = offset + layerOffset;
+    if (_lastOffset != Offset.zero)
+      engineLayer = builder.pushTransform(
+        Matrix4.translationValues(_lastOffset!.dx, _lastOffset!.dy, 0.0).storage,
+        oldLayer: _engineLayer as ui.TransformEngineLayer?,
+      );
+    addChildrenToScene(builder);
+    if (_lastOffset != Offset.zero)
+      builder.pop();
+  }
+
+  /// Applies the transform that would be applied when compositing the given
+  /// child to the given matrix.
+  ///
+  /// See [ContainerLayer.applyTransform] for details.
+  ///
+  /// The `child` argument may be null, as the same transform is applied to all
+  /// children.
+  @override
+  void applyTransform(Layer? child, Matrix4 transform) {
+    assert(_lastOffset != null);
+    if (_lastOffset != Offset.zero)
+      transform.translate(_lastOffset!.dx, _lastOffset!.dy);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Offset>('offset', offset));
+    properties.add(DiagnosticsProperty<LayerLink>('link', link));
+  }
+}
+
+/// A composited layer that applies a transformation matrix to its children such
+/// that they are positioned to match a [LeaderLayer].
+///
+/// If any of the ancestors of this layer have a degenerate matrix (e.g. scaling
+/// by zero), then the [FollowerLayer] will not be able to transform its child
+/// to the coordinate space of the [LeaderLayer].
+///
+/// A [linkedOffset] property can be provided to further offset the child layer
+/// from the leader layer, for example if the child is to follow the linked
+/// layer at a distance rather than directly overlapping it.
+class FollowerLayer extends ContainerLayer {
+  /// Creates a follower layer.
+  ///
+  /// The [link] property must not be null.
+  ///
+  /// The [unlinkedOffset], [linkedOffset], and [showWhenUnlinked] properties
+  /// must be non-null before the compositing phase of the pipeline.
+  FollowerLayer({
+    required LayerLink link,
+    this.showWhenUnlinked = true,
+    this.unlinkedOffset = Offset.zero,
+    this.linkedOffset = Offset.zero,
+  }) : assert(link != null), _link = link;
+
+  /// The link to the [LeaderLayer].
+  ///
+  /// The same object should be provided to a [LeaderLayer] that is earlier in
+  /// the layer tree. When this layer is composited, it will apply a transform
+  /// that moves its children to match the position of the [LeaderLayer].
+  LayerLink get link => _link;
+  set link(LayerLink value) {
+    assert(value != null);
+    _link = value;
+  }
+  LayerLink _link;
+
+  /// Whether to show the layer's contents when the [link] does not point to a
+  /// [LeaderLayer].
+  ///
+  /// When the layer is linked, children layers are positioned such that they
+  /// have the same global position as the linked [LeaderLayer].
+  ///
+  /// When the layer is not linked, then: if [showWhenUnlinked] is true,
+  /// children are positioned as if the [FollowerLayer] was a [ContainerLayer];
+  /// if it is false, then children are hidden.
+  ///
+  /// The [showWhenUnlinked] property must be non-null before the compositing
+  /// phase of the pipeline.
+  bool? showWhenUnlinked;
+
+  /// Offset from parent in the parent's coordinate system, used when the layer
+  /// is not linked to a [LeaderLayer].
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  ///
+  /// The [unlinkedOffset] property must be non-null before the compositing
+  /// phase of the pipeline.
+  ///
+  /// See also:
+  ///
+  ///  * [linkedOffset], for when the layers are linked.
+  Offset? unlinkedOffset;
+
+  /// Offset from the origin of the leader layer to the origin of the child
+  /// layers, used when the layer is linked to a [LeaderLayer].
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  ///
+  /// The [linkedOffset] property must be non-null before the compositing phase
+  /// of the pipeline.
+  ///
+  /// See also:
+  ///
+  ///  * [unlinkedOffset], for when the layer is not linked.
+  Offset? linkedOffset;
+
+  Offset? _lastOffset;
+  Matrix4? _lastTransform;
+  Matrix4? _invertedTransform;
+  bool _inverseDirty = true;
+
+  Offset? _transformOffset(Offset localPosition) {
+    if (_inverseDirty) {
+      _invertedTransform = Matrix4.tryInvert(getLastTransform()!);
+      _inverseDirty = false;
+    }
+    if (_invertedTransform == null)
+      return null;
+    final Vector4 vector = Vector4(localPosition.dx, localPosition.dy, 0.0, 1.0);
+    final Vector4 result = _invertedTransform!.transform(vector);
+    return Offset(result[0] - linkedOffset!.dx, result[1] - linkedOffset!.dy);
+  }
+
+  @override
+  bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
+    if (link.leader == null) {
+      if (showWhenUnlinked!) {
+        return super.findAnnotations(result, localPosition - unlinkedOffset!, onlyFirst: onlyFirst);
+      }
+      return false;
+    }
+    final Offset? transformedOffset = _transformOffset(localPosition);
+    if (transformedOffset == null) {
+      return false;
+    }
+    return super.findAnnotations<S>(result, transformedOffset, onlyFirst: onlyFirst);
+  }
+
+  /// The transform that was used during the last composition phase.
+  ///
+  /// If the [link] was not linked to a [LeaderLayer], or if this layer has
+  /// a degenerate matrix applied, then this will be null.
+  ///
+  /// This method returns a new [Matrix4] instance each time it is invoked.
+  Matrix4? getLastTransform() {
+    if (_lastTransform == null)
+      return null;
+    final Matrix4 result = Matrix4.translationValues(-_lastOffset!.dx, -_lastOffset!.dy, 0.0);
+    result.multiply(_lastTransform!);
+    return result;
+  }
+
+  /// Call [applyTransform] for each layer in the provided list.
+  ///
+  /// The list is in reverse order (deepest first). The first layer will be
+  /// treated as the child of the second, and so forth. The first layer in the
+  /// list won't have [applyTransform] called on it. The first layer may be
+  /// null.
+  static Matrix4 _collectTransformForLayerChain(List<ContainerLayer?> layers) {
+    // Initialize our result matrix.
+    final Matrix4 result = Matrix4.identity();
+    // Apply each layer to the matrix in turn, starting from the last layer,
+    // and providing the previous layer as the child.
+    for (int index = layers.length - 1; index > 0; index -= 1)
+      layers[index]?.applyTransform(layers[index - 1], result);
+    return result;
+  }
+
+  /// Find the common ancestor of two layers [a] and [b] by searching towards
+  /// the root of the tree, and append each ancestor of [a] or [b] visited along
+  /// the path to [ancestorsA] and [ancestorsB] respectively.
+  ///
+  /// Returns null if [a] [b] do not share a common ancestor, in which case the
+  /// results in [ancestorsA] and [ancestorsB] are undefined.
+  static Layer? _pathsToCommonAncestor(
+    Layer? a, Layer? b,
+    List<ContainerLayer?> ancestorsA, List<ContainerLayer?> ancestorsB,
+  ) {
+    // No common ancestor found.
+    if (a == null || b == null)
+      return null;
+
+    if (identical(a, b))
+      return a;
+
+    if (a.depth < b.depth) {
+      ancestorsB.add(b.parent);
+      return _pathsToCommonAncestor(a, b.parent, ancestorsA, ancestorsB);
+    } else if (a.depth > b.depth){
+      ancestorsA.add(a.parent);
+      return _pathsToCommonAncestor(a.parent, b, ancestorsA, ancestorsB);
+    }
+
+    ancestorsA.add(a.parent);
+    ancestorsB.add(b.parent);
+    return _pathsToCommonAncestor(a.parent, b.parent, ancestorsA, ancestorsB);
+  }
+
+  /// Populate [_lastTransform] given the current state of the tree.
+  void _establishTransform() {
+    assert(link != null);
+    _lastTransform = null;
+    final LeaderLayer? leader = link.leader;
+    // Check to see if we are linked.
+    if (leader == null)
+      return;
+    // If we're linked, check the link is valid.
+    assert(
+      leader.owner == owner,
+      'Linked LeaderLayer anchor is not in the same layer tree as the FollowerLayer.',
+    );
+    assert(
+      leader._lastOffset != null,
+      'LeaderLayer anchor must come before FollowerLayer in paint order, but the reverse was true.',
+    );
+
+    // Stores [leader, ..., commonAncestor] after calling _pathsToCommonAncestor.
+    final List<ContainerLayer?> forwardLayers = <ContainerLayer>[leader];
+    // Stores [this (follower), ..., commonAncestor] after calling
+    // _pathsToCommonAncestor.
+    final List<ContainerLayer?> inverseLayers = <ContainerLayer>[this];
+
+    final Layer? ancestor = _pathsToCommonAncestor(
+      leader, this,
+      forwardLayers, inverseLayers,
+    );
+    assert(ancestor != null);
+
+    final Matrix4 forwardTransform = _collectTransformForLayerChain(forwardLayers);
+    // Further transforms the coordinate system to a hypothetical child (null)
+    // of the leader layer, to account for the leader's additional paint offset
+    // and layer offset (LeaderLayer._lastOffset).
+    leader.applyTransform(null, forwardTransform);
+    forwardTransform.translate(linkedOffset!.dx, linkedOffset!.dy);
+
+    final Matrix4 inverseTransform = _collectTransformForLayerChain(inverseLayers);
+
+    if (inverseTransform.invert() == 0.0) {
+      // We are in a degenerate transform, so there's not much we can do.
+      return;
+    }
+    // Combine the matrices and store the result.
+    inverseTransform.multiply(forwardTransform);
+    _lastTransform = inverseTransform;
+    _inverseDirty = true;
+  }
+
+  /// {@template flutter.rendering.FollowerLayer.alwaysNeedsAddToScene}
+  /// This disables retained rendering.
+  ///
+  /// A [FollowerLayer] copies changes from a [LeaderLayer] that could be anywhere
+  /// in the Layer tree, and that leader layer could change without notifying the
+  /// follower layer. Therefore we have to always call a follower layer's
+  /// [addToScene]. In order to call follower layer's [addToScene], leader layer's
+  /// [addToScene] must be called first so leader layer must also be considered
+  /// as [alwaysNeedsAddToScene].
+  /// {@endtemplate}
+  @override
+  bool get alwaysNeedsAddToScene => true;
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    assert(link != null);
+    assert(showWhenUnlinked != null);
+    if (link.leader == null && !showWhenUnlinked!) {
+      _lastTransform = null;
+      _lastOffset = null;
+      _inverseDirty = true;
+      engineLayer = null;
+      return;
+    }
+    _establishTransform();
+    if (_lastTransform != null) {
+      engineLayer = builder.pushTransform(
+        _lastTransform!.storage,
+        oldLayer: _engineLayer as ui.TransformEngineLayer?,
+      );
+      addChildrenToScene(builder);
+      builder.pop();
+      _lastOffset = unlinkedOffset! + layerOffset;
+    } else {
+      _lastOffset = null;
+      final Matrix4 matrix = Matrix4.translationValues(unlinkedOffset!.dx, unlinkedOffset!.dy, .0);
+      engineLayer = builder.pushTransform(
+        matrix.storage,
+        oldLayer: _engineLayer as ui.TransformEngineLayer?,
+      );
+      addChildrenToScene(builder);
+      builder.pop();
+    }
+    _inverseDirty = true;
+  }
+
+  @override
+  void applyTransform(Layer? child, Matrix4 transform) {
+    assert(child != null);
+    assert(transform != null);
+    if (_lastTransform != null) {
+      transform.multiply(_lastTransform!);
+    } else {
+      transform.multiply(Matrix4.translationValues(unlinkedOffset!.dx, unlinkedOffset!.dy, 0));
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<LayerLink>('link', link));
+    properties.add(TransformProperty('transform', getLastTransform(), defaultValue: null));
+  }
+}
+
+/// A composited layer which annotates its children with a value. Pushing this
+/// layer to the tree is the common way of adding an annotation.
+///
+/// An annotation is an optional object of any type that, when attached with a
+/// layer, can be retrieved using [Layer.find] or [Layer.findAllAnnotations]
+/// with a position. The search process is done recursively, controlled by a
+/// concept of being opaque to a type of annotation, explained in the document
+/// of [Layer.findAnnotations].
+///
+/// When an annotation search arrives, this layer defers the same search to each
+/// of this layer's children, respecting their opacity. Then it adds this
+/// layer's annotation if all of the following restrictions are met:
+///
+/// {@template flutter.rendering.AnnotatedRegionLayer.restrictions}
+/// * The target type must be identical to the annotated type `T`.
+/// * If [size] is provided, the target position must be contained within the
+///   rectangle formed by [size] and [offset].
+/// {@endtemplate}
+///
+/// This layer is opaque to a type of annotation if any child is also opaque, or
+/// if [opaque] is true and the layer's annotation is added.
+class AnnotatedRegionLayer<T extends Object> extends ContainerLayer {
+  /// Creates a new layer that annotates its children with [value].
+  ///
+  /// The [value] provided cannot be null.
+  AnnotatedRegionLayer(
+    this.value, {
+    this.size,
+    Offset? offset,
+    this.opaque = false,
+  }) : assert(value != null),
+       assert(opaque != null),
+       offset = offset ?? Offset.zero;
+
+  /// The annotated object, which is added to the result if all restrictions are
+  /// met.
+  final T value;
+
+  /// The size of the annotated object.
+  ///
+  /// If [size] is provided, then the annotation is found only if the target
+  /// position is contained by the rectangle formed by [size] and [offset].
+  /// Otherwise no such restriction is applied, and clipping can only be done by
+  /// the ancestor layers.
+  final Size? size;
+
+  /// The position of the annotated object.
+  ///
+  /// The [offset] defaults to [Offset.zero] if not provided, and is ignored if
+  /// [size] is not set.
+  ///
+  /// The [offset] only offsets the clipping rectangle, and does not affect
+  /// how the painting or annotation search is propagated to its children.
+  final Offset offset;
+
+  /// Whether the annotation of this layer should be opaque during an annotation
+  /// search of type `T`, preventing siblings visually behind it from being
+  /// searched.
+  ///
+  /// If [opaque] is true, and this layer does add its annotation [value],
+  /// then the layer will always be opaque during the search.
+  ///
+  /// If [opaque] is false, or if this layer does not add its annotation,
+  /// then the opacity of this layer will be the one returned by the children,
+  /// meaning that it will be opaque if any child is opaque.
+  ///
+  /// The [opaque] defaults to false.
+  ///
+  /// The [opaque] is effectively useless during [Layer.find] (more
+  /// specifically, [Layer.findAnnotations] with `onlyFirst: true`), since the
+  /// search process then skips the remaining tree after finding the first
+  /// annotation.
+  ///
+  /// See also:
+  ///
+  ///  * [Layer.findAnnotations], which explains the concept of being opaque
+  ///    to a type of annotation as the return value.
+  ///  * [HitTestBehavior], which controls similar logic when hit-testing in the
+  ///    render tree.
+  final bool opaque;
+
+  /// Searches the subtree for annotations of type `S` at the location
+  /// `localPosition`, then adds the annotation [value] if applicable.
+  ///
+  /// This method always searches its children, and if any child returns `true`,
+  /// the remaining children are skipped. Regardless of what the children
+  /// return, this method then adds this layer's annotation if all of the
+  /// following restrictions are met:
+  ///
+  /// {@macro flutter.rendering.AnnotatedRegionLayer.restrictions}
+  ///
+  /// This search process respects `onlyFirst`, meaning that when `onlyFirst` is
+  /// true, the search will stop when it finds the first annotation from the
+  /// children, and the layer's own annotation is checked only when none is
+  /// given by the children.
+  ///
+  /// The return value is true if any child returns `true`, or if [opaque] is
+  /// true and the layer's annotation is added.
+  ///
+  /// For explanation of layer annotations, parameters and return value, refer
+  /// to [Layer.findAnnotations].
+  @override
+  bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
+    bool isAbsorbed = super.findAnnotations(result, localPosition, onlyFirst: onlyFirst);
+    if (result.entries.isNotEmpty && onlyFirst)
+      return isAbsorbed;
+    if (size != null && !(offset & size!).contains(localPosition)) {
+      return isAbsorbed;
+    }
+    if (T == S) {
+      isAbsorbed = isAbsorbed || opaque;
+      final Object untypedValue = value;
+      final S typedValue = untypedValue as S;
+      result.add(AnnotationEntry<S>(
+        annotation: typedValue,
+        localPosition: localPosition - offset,
+      ));
+    }
+    return isAbsorbed;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<T>('value', value));
+    properties.add(DiagnosticsProperty<Size>('size', size, defaultValue: null));
+    properties.add(DiagnosticsProperty<Offset>('offset', offset, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('opaque', opaque, defaultValue: false));
+  }
+}
diff --git a/lib/src/rendering/layout_helper.dart b/lib/src/rendering/layout_helper.dart
new file mode 100644
index 0000000..1f96115
--- /dev/null
+++ b/lib/src/rendering/layout_helper.dart
@@ -0,0 +1,57 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart';
+
+import 'box.dart';
+
+/// Signature for a function that takes a [RenderBox] and returns the [Size]
+/// that the [RenderBox] would have if it were laid out with the given
+/// [BoxConstraints].
+///
+/// The methods of [ChildLayoutHelper] adhere to this signature.
+typedef ChildLayouter = Size Function(RenderBox child, BoxConstraints constraints);
+
+/// A collection of static functions to layout a [RenderBox] child with the
+/// given set of [BoxConstraints].
+///
+/// All of the functions adhere to the [ChildLayouter] signature.
+class ChildLayoutHelper {
+  const ChildLayoutHelper._();
+
+  /// Returns the [Size] that the [RenderBox] would have if it were to
+  /// be layed out with the given [BoxConstraints].
+  ///
+  /// This method calls [RenderBox.getDryLayout] on the given [RenderBox].
+  ///
+  /// This method should only be called by the parent of the provided
+  /// [RenderBox] child as it bounds parent and child together (if the child
+  /// is marked as dirty, the child will also be marked as dirty).
+  ///
+  /// See also:
+  ///
+  ///  * [layoutChild], which actually lays out the child with the given
+  ///    constraints.
+  static Size dryLayoutChild(RenderBox child, BoxConstraints constrains) {
+    return child.getDryLayout(constrains);
+  }
+
+  /// Lays out the [RenderBox] with the given constraints and returns its
+  /// [Size].
+  ///
+  /// This method calls [RenderBox.layout] on the given [RenderBox] with
+  /// `parentUsesSize` set to true to receive its [Size].
+  ///
+  /// This method should only be called by the parent of the provided
+  /// [RenderBox] child as it bounds parent and child together (if the child
+  /// is marked as dirty, the child will also be marked as dirty).
+  ///
+  /// See also:
+  ///
+  ///  * [dryLayoutChild], which does not perform a real layout of the child.
+  static Size layoutChild(RenderBox child, BoxConstraints constraints) {
+    child.layout(constraints, parentUsesSize: true);
+    return child.size;
+  }
+}
diff --git a/lib/src/rendering/list_body.dart b/lib/src/rendering/list_body.dart
new file mode 100644
index 0000000..76d06a7
--- /dev/null
+++ b/lib/src/rendering/list_body.dart
@@ -0,0 +1,313 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'box.dart';
+import 'object.dart';
+
+/// Parent data for use with [RenderListBody].
+class ListBodyParentData extends ContainerBoxParentData<RenderBox> { }
+
+typedef _ChildSizingFunction = double Function(RenderBox child);
+
+/// Displays its children sequentially along a given axis, forcing them to the
+/// dimensions of the parent in the other axis.
+///
+/// This layout algorithm arranges its children linearly along the main axis
+/// (either horizontally or vertically). In the cross axis, children are
+/// stretched to match the box's cross-axis extent. In the main axis, children
+/// are given unlimited space and the box expands its main axis to contain all
+/// its children. Because [RenderListBody] boxes expand in the main axis, they
+/// must be given unlimited space in the main axis, typically by being contained
+/// in a viewport with a scrolling direction that matches the box's main axis.
+class RenderListBody extends RenderBox
+    with ContainerRenderObjectMixin<RenderBox, ListBodyParentData>,
+         RenderBoxContainerDefaultsMixin<RenderBox, ListBodyParentData> {
+  /// Creates a render object that arranges its children sequentially along a
+  /// given axis.
+  ///
+  /// By default, children are arranged along the vertical axis.
+  RenderListBody({
+    List<RenderBox>? children,
+    AxisDirection axisDirection = AxisDirection.down,
+  }) : assert(axisDirection != null),
+       _axisDirection = axisDirection {
+    addAll(children);
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! ListBodyParentData)
+      child.parentData = ListBodyParentData();
+  }
+
+  /// The direction in which the children are laid out.
+  ///
+  /// For example, if the [axisDirection] is [AxisDirection.down], each child
+  /// will be laid out below the next, vertically.
+  AxisDirection get axisDirection => _axisDirection;
+  AxisDirection _axisDirection;
+  set axisDirection(AxisDirection value) {
+    assert(value != null);
+    if (_axisDirection == value)
+      return;
+    _axisDirection = value;
+    markNeedsLayout();
+  }
+
+  /// The axis (horizontal or vertical) corresponding to the current
+  /// [axisDirection].
+  Axis get mainAxis => axisDirectionToAxis(axisDirection);
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    assert(_debugCheckConstraints(constraints));
+    double mainAxisExtent = 0.0;
+    RenderBox? child = firstChild;
+    switch (axisDirection) {
+      case AxisDirection.right:
+      case AxisDirection.left:
+        final BoxConstraints innerConstraints = BoxConstraints.tightFor(height: constraints.maxHeight);
+        while (child != null) {
+          final Size childSize = child.getDryLayout(innerConstraints);
+          mainAxisExtent += childSize.width;
+          child = childAfter(child);
+        }
+        return constraints.constrain(Size(mainAxisExtent, constraints.maxHeight));
+      case AxisDirection.up:
+      case AxisDirection.down:
+        final BoxConstraints innerConstraints = BoxConstraints.tightFor(width: constraints.maxWidth);
+        while (child != null) {
+          final Size childSize = child.getDryLayout(innerConstraints);
+          mainAxisExtent += childSize.height;
+          child = childAfter(child);
+        }
+        return constraints.constrain(Size(constraints.maxWidth, mainAxisExtent));
+    }
+  }
+
+  bool _debugCheckConstraints(BoxConstraints constraints) {
+    assert(() {
+      switch (mainAxis) {
+        case Axis.horizontal:
+          if (!constraints.hasBoundedWidth)
+            return true;
+          break;
+        case Axis.vertical:
+          if (!constraints.hasBoundedHeight)
+            return true;
+          break;
+      }
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('RenderListBody must have unlimited space along its main axis.'),
+        ErrorDescription(
+          'RenderListBody does not clip or resize its children, so it must be '
+          'placed in a parent that does not constrain the main '
+          'axis.'
+        ),
+        ErrorHint(
+          'You probably want to put the RenderListBody inside a '
+          'RenderViewport with a matching main axis.'
+        )
+      ]);
+    }());
+    assert(() {
+      switch (mainAxis) {
+        case Axis.horizontal:
+          if (constraints.hasBoundedHeight)
+            return true;
+          break;
+        case Axis.vertical:
+          if (constraints.hasBoundedWidth)
+            return true;
+          break;
+      }
+      // TODO(ianh): Detect if we're actually nested blocks and say something
+      // more specific to the exact situation in that case, and don't mention
+      // nesting blocks in the negative case.
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('RenderListBody must have a bounded constraint for its cross axis.'),
+        ErrorDescription(
+          "RenderListBody forces its children to expand to fit the RenderListBody's container, "
+          'so it must be placed in a parent that constrains the cross '
+          'axis to a finite dimension.'
+        ),
+        // TODO(jacobr): this hint is a great candidate to promote to being an
+        // automated quick fix in the future.
+        ErrorHint(
+          'If you are attempting to nest a RenderListBody with '
+          'one direction inside one of another direction, you will want to '
+          'wrap the inner one inside a box that fixes the dimension in that direction, '
+          'for example, a RenderIntrinsicWidth or RenderIntrinsicHeight object. '
+          'This is relatively expensive, however.' // (that's why we don't do it automatically)
+        )
+      ]);
+    }());
+    return true;
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    assert(_debugCheckConstraints(constraints));
+    double mainAxisExtent = 0.0;
+    RenderBox? child = firstChild;
+    switch (axisDirection) {
+      case AxisDirection.right:
+        final BoxConstraints innerConstraints = BoxConstraints.tightFor(height: constraints.maxHeight);
+        while (child != null) {
+          child.layout(innerConstraints, parentUsesSize: true);
+          final ListBodyParentData childParentData = child.parentData! as ListBodyParentData;
+          childParentData.offset = Offset(mainAxisExtent, 0.0);
+          mainAxisExtent += child.size.width;
+          assert(child.parentData == childParentData);
+          child = childParentData.nextSibling;
+        }
+        size = constraints.constrain(Size(mainAxisExtent, constraints.maxHeight));
+        break;
+      case AxisDirection.left:
+        final BoxConstraints innerConstraints = BoxConstraints.tightFor(height: constraints.maxHeight);
+        while (child != null) {
+          child.layout(innerConstraints, parentUsesSize: true);
+          final ListBodyParentData childParentData = child.parentData! as ListBodyParentData;
+          mainAxisExtent += child.size.width;
+          assert(child.parentData == childParentData);
+          child = childParentData.nextSibling;
+        }
+        double position = 0.0;
+        child = firstChild;
+        while (child != null) {
+          final ListBodyParentData childParentData = child.parentData! as ListBodyParentData;
+          position += child.size.width;
+          childParentData.offset = Offset(mainAxisExtent - position, 0.0);
+          assert(child.parentData == childParentData);
+          child = childParentData.nextSibling;
+        }
+        size = constraints.constrain(Size(mainAxisExtent, constraints.maxHeight));
+        break;
+      case AxisDirection.down:
+        final BoxConstraints innerConstraints = BoxConstraints.tightFor(width: constraints.maxWidth);
+        while (child != null) {
+          child.layout(innerConstraints, parentUsesSize: true);
+          final ListBodyParentData childParentData = child.parentData! as ListBodyParentData;
+          childParentData.offset = Offset(0.0, mainAxisExtent);
+          mainAxisExtent += child.size.height;
+          assert(child.parentData == childParentData);
+          child = childParentData.nextSibling;
+        }
+        size = constraints.constrain(Size(constraints.maxWidth, mainAxisExtent));
+        break;
+      case AxisDirection.up:
+        final BoxConstraints innerConstraints = BoxConstraints.tightFor(width: constraints.maxWidth);
+        while (child != null) {
+          child.layout(innerConstraints, parentUsesSize: true);
+          final ListBodyParentData childParentData = child.parentData! as ListBodyParentData;
+          mainAxisExtent += child.size.height;
+          assert(child.parentData == childParentData);
+          child = childParentData.nextSibling;
+        }
+        double position = 0.0;
+        child = firstChild;
+        while (child != null) {
+          final ListBodyParentData childParentData = child.parentData! as ListBodyParentData;
+          position += child.size.height;
+          childParentData.offset = Offset(0.0, mainAxisExtent - position);
+          assert(child.parentData == childParentData);
+          child = childParentData.nextSibling;
+        }
+        size = constraints.constrain(Size(constraints.maxWidth, mainAxisExtent));
+        break;
+    }
+    assert(size.isFinite);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<AxisDirection>('axisDirection', axisDirection));
+  }
+
+  double _getIntrinsicCrossAxis(_ChildSizingFunction childSize) {
+    double extent = 0.0;
+    RenderBox? child = firstChild;
+    while (child != null) {
+      extent = math.max(extent, childSize(child));
+      final ListBodyParentData childParentData = child.parentData! as ListBodyParentData;
+      child = childParentData.nextSibling;
+    }
+    return extent;
+  }
+
+  double _getIntrinsicMainAxis(_ChildSizingFunction childSize) {
+    double extent = 0.0;
+    RenderBox? child = firstChild;
+    while (child != null) {
+      extent += childSize(child);
+      final ListBodyParentData childParentData = child.parentData! as ListBodyParentData;
+      child = childParentData.nextSibling;
+    }
+    return extent;
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    assert(mainAxis != null);
+    switch (mainAxis) {
+      case Axis.horizontal:
+        return _getIntrinsicMainAxis((RenderBox child) => child.getMinIntrinsicWidth(height));
+      case Axis.vertical:
+        return _getIntrinsicCrossAxis((RenderBox child) => child.getMinIntrinsicWidth(height));
+    }
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    assert(mainAxis != null);
+    switch (mainAxis) {
+      case Axis.horizontal:
+        return _getIntrinsicMainAxis((RenderBox child) => child.getMaxIntrinsicWidth(height));
+      case Axis.vertical:
+        return _getIntrinsicCrossAxis((RenderBox child) => child.getMaxIntrinsicWidth(height));
+    }
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    assert(mainAxis != null);
+    switch (mainAxis) {
+      case Axis.horizontal:
+        return _getIntrinsicMainAxis((RenderBox child) => child.getMinIntrinsicHeight(width));
+      case Axis.vertical:
+        return _getIntrinsicCrossAxis((RenderBox child) => child.getMinIntrinsicHeight(width));
+    }
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    assert(mainAxis != null);
+    switch (mainAxis) {
+      case Axis.horizontal:
+        return _getIntrinsicMainAxis((RenderBox child) => child.getMaxIntrinsicHeight(width));
+      case Axis.vertical:
+        return _getIntrinsicCrossAxis((RenderBox child) => child.getMaxIntrinsicHeight(width));
+    }
+  }
+
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    return defaultComputeDistanceToFirstActualBaseline(baseline);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    defaultPaint(context, offset);
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    return defaultHitTestChildren(result, position: position);
+  }
+
+}
diff --git a/lib/src/rendering/list_wheel_viewport.dart b/lib/src/rendering/list_wheel_viewport.dart
new file mode 100644
index 0000000..0061d3b
--- /dev/null
+++ b/lib/src/rendering/list_wheel_viewport.dart
@@ -0,0 +1,1059 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/animation.dart';
+import 'package:vector_math/vector_math_64.dart' show Matrix4;
+
+import 'box.dart';
+import 'layer.dart';
+import 'object.dart';
+import 'viewport.dart';
+import 'viewport_offset.dart';
+
+typedef _ChildSizingFunction = double Function(RenderBox child);
+
+/// A delegate used by [RenderListWheelViewport] to manage its children.
+///
+/// [RenderListWheelViewport] during layout will ask the delegate to create
+/// children that are visible in the viewport and remove those that are not.
+abstract class ListWheelChildManager {
+  /// The maximum number of children that can be provided to
+  /// [RenderListWheelViewport].
+  ///
+  /// If non-null, the children will have index in the range [0, childCount - 1].
+  ///
+  /// If null, then there's no explicit limits to the range of the children
+  /// except that it has to be contiguous. If [childExistsAt] for a certain
+  /// index returns false, that index is already past the limit.
+  int? get childCount;
+
+  /// Checks whether the delegate is able to provide a child widget at the given
+  /// index.
+  ///
+  /// This function is not about whether the child at the given index is
+  /// attached to the [RenderListWheelViewport] or not.
+  bool childExistsAt(int index);
+
+  /// Creates a new child at the given index and updates it to the child list
+  /// of [RenderListWheelViewport]. If no child corresponds to `index`, then do
+  /// nothing.
+  ///
+  /// It is possible to create children with negative indices.
+  void createChild(int index, { required RenderBox? after });
+
+  /// Removes the child element corresponding with the given RenderBox.
+  void removeChild(RenderBox child);
+}
+
+/// [ParentData] for use with [RenderListWheelViewport].
+class ListWheelParentData extends ContainerBoxParentData<RenderBox> {
+  /// Index of this child in its parent's child list.
+  ///
+  /// This must be maintained by the [ListWheelChildManager].
+  int? index;
+}
+
+/// Render, onto a wheel, a bigger sequential set of objects inside this viewport.
+///
+/// Takes a scrollable set of fixed sized [RenderBox]es and renders them
+/// sequentially from top down on a vertical scrolling axis.
+///
+/// It starts with the first scrollable item in the center of the main axis
+/// and ends with the last scrollable item in the center of the main axis. This
+/// is in contrast to typical lists that start with the first scrollable item
+/// at the start of the main axis and ends with the last scrollable item at the
+/// end of the main axis.
+///
+/// Instead of rendering its children on a flat plane, it renders them
+/// as if each child is broken into its own plane and that plane is
+/// perpendicularly fixed onto a cylinder which rotates along the scrolling
+/// axis.
+///
+/// This class works in 3 coordinate systems:
+///
+/// 1. The **scrollable layout coordinates**. This coordinate system is used to
+///    communicate with [ViewportOffset] and describes its children's abstract
+///    offset from the beginning of the scrollable list at (0.0, 0.0).
+///
+///    The list is scrollable from the start of the first child item to the
+///    start of the last child item.
+///
+///    Children's layout coordinates don't change as the viewport scrolls.
+///
+/// 2. The **untransformed plane's viewport painting coordinates**. Children are
+///    not painted in this coordinate system. It's an abstract intermediary used
+///    before transforming into the next cylindrical coordinate system.
+///
+///    This system is the **scrollable layout coordinates** translated by the
+///    scroll offset such that (0.0, 0.0) is the top left corner of the
+///    viewport.
+///
+///    Because the viewport is centered at the scrollable list's scroll offset
+///    instead of starting at the scroll offset, there are paintable children
+///    ~1/2 viewport length before and after the scroll offset instead of ~1
+///    viewport length after the scroll offset.
+///
+///    Children's visibility inclusion in the viewport is determined in this
+///    system regardless of the cylinder's properties such as [diameterRatio]
+///    or [perspective]. In other words, a 100px long viewport will always
+///    paint 10-11 visible 10px children if there are enough children in the
+///    viewport.
+///
+/// 3. The **transformed cylindrical space viewport painting coordinates**.
+///    Children from system 2 get their positions transformed into a cylindrical
+///    projection matrix instead of its Cartesian offset with respect to the
+///    scroll offset.
+///
+///    Children in this coordinate system are painted.
+///
+///    The wheel's size and the maximum and minimum visible angles are both
+///    controlled by [diameterRatio]. Children visible in the **untransformed
+///    plane's viewport painting coordinates**'s viewport will be radially
+///    evenly laid out between the maximum and minimum angles determined by
+///    intersecting the viewport's main axis length with a cylinder whose
+///    diameter is [diameterRatio] times longer, as long as those angles are
+///    between -pi/2 and pi/2.
+///
+///    For example, if [diameterRatio] is 2.0 and this [RenderListWheelViewport]
+///    is 100.0px in the main axis, then the diameter is 200.0. And children
+///    will be evenly laid out between that cylinder's -arcsin(1/2) and
+///    arcsin(1/2) angles.
+///
+///    The cylinder's 0 degree side is always centered in the
+///    [RenderListWheelViewport]. The transformation from **untransformed
+///    plane's viewport painting coordinates** is also done such that the child
+///    in the center of that plane will be mostly untransformed with children
+///    above and below it being transformed more as the angle increases.
+class RenderListWheelViewport
+    extends RenderBox
+    with ContainerRenderObjectMixin<RenderBox, ListWheelParentData>
+    implements RenderAbstractViewport {
+  /// Creates a [RenderListWheelViewport] which renders children on a wheel.
+  ///
+  /// All arguments must not be null. Optional arguments have reasonable defaults.
+  RenderListWheelViewport({
+    required this.childManager,
+    required ViewportOffset offset,
+    double diameterRatio = defaultDiameterRatio,
+    double perspective = defaultPerspective,
+    double offAxisFraction = 0,
+    bool useMagnifier = false,
+    double magnification = 1,
+    double overAndUnderCenterOpacity = 1,
+    required double itemExtent,
+    double squeeze = 1,
+    bool renderChildrenOutsideViewport = false,
+    Clip clipBehavior = Clip.none,
+    List<RenderBox>? children,
+  }) : assert(childManager != null),
+       assert(offset != null),
+       assert(diameterRatio != null),
+       assert(diameterRatio > 0, diameterRatioZeroMessage),
+       assert(perspective != null),
+       assert(perspective > 0),
+       assert(perspective <= 0.01, perspectiveTooHighMessage),
+       assert(offAxisFraction != null),
+       assert(useMagnifier != null),
+       assert(magnification != null),
+       assert(magnification > 0),
+       assert(overAndUnderCenterOpacity != null),
+       assert(overAndUnderCenterOpacity >= 0 && overAndUnderCenterOpacity <= 1),
+       assert(itemExtent != null),
+       assert(squeeze != null),
+       assert(squeeze > 0),
+       assert(itemExtent > 0),
+       assert(renderChildrenOutsideViewport != null),
+       assert(clipBehavior != null),
+       assert(
+         !renderChildrenOutsideViewport || clipBehavior == Clip.none,
+         clipBehaviorAndRenderChildrenOutsideViewportConflict,
+       ),
+       _offset = offset,
+       _diameterRatio = diameterRatio,
+       _perspective = perspective,
+       _offAxisFraction = offAxisFraction,
+       _useMagnifier = useMagnifier,
+       _magnification = magnification,
+       _overAndUnderCenterOpacity = overAndUnderCenterOpacity,
+       _itemExtent = itemExtent,
+       _squeeze = squeeze,
+       _renderChildrenOutsideViewport = renderChildrenOutsideViewport,
+       _clipBehavior = clipBehavior {
+    addAll(children);
+  }
+
+  /// An arbitrary but aesthetically reasonable default value for [diameterRatio].
+  static const double defaultDiameterRatio = 2.0;
+
+  /// An arbitrary but aesthetically reasonable default value for [perspective].
+  static const double defaultPerspective = 0.003;
+
+  /// An error message to show when the provided [diameterRatio] is zero.
+  static const String diameterRatioZeroMessage = "You can't set a diameterRatio "
+      'of 0 or of a negative number. It would imply a cylinder of 0 in diameter '
+      'in which case nothing will be drawn.';
+
+  /// An error message to show when the [perspective] value is too high.
+  static const String perspectiveTooHighMessage = 'A perspective too high will '
+      'be clipped in the z-axis and therefore not renderable. Value must be '
+      'between 0 and 0.01.';
+
+  /// An error message to show when [clipBehavior] and [renderChildrenOutsideViewport]
+  /// are set to conflicting values.
+  static const String clipBehaviorAndRenderChildrenOutsideViewportConflict =
+      'Cannot renderChildrenOutsideViewport and clip since children '
+      'rendered outside will be clipped anyway.';
+
+  /// The delegate that manages the children of this object.
+  ///
+  /// This delegate must maintain the [ListWheelParentData.index] value.
+  final ListWheelChildManager childManager;
+
+  /// The associated ViewportOffset object for the viewport describing the part
+  /// of the content inside that's visible.
+  ///
+  /// The [ViewportOffset.pixels] value determines the scroll offset that the
+  /// viewport uses to select which part of its content to display. As the user
+  /// scrolls the viewport, this value changes, which changes the content that
+  /// is displayed.
+  ///
+  /// Must not be null.
+  ViewportOffset get offset => _offset;
+  ViewportOffset _offset;
+  set offset(ViewportOffset value) {
+    assert(value != null);
+    if (value == _offset)
+      return;
+    if (attached)
+      _offset.removeListener(_hasScrolled);
+    _offset = value;
+    if (attached)
+      _offset.addListener(_hasScrolled);
+    markNeedsLayout();
+  }
+
+  /// {@template flutter.rendering.RenderListWheelViewport.diameterRatio}
+  /// A ratio between the diameter of the cylinder and the viewport's size
+  /// in the main axis.
+  ///
+  /// A value of 1 means the cylinder has the same diameter as the viewport's
+  /// size.
+  ///
+  /// A value smaller than 1 means items at the edges of the cylinder are
+  /// entirely contained inside the viewport.
+  ///
+  /// A value larger than 1 means angles less than ±[pi] / 2 from the
+  /// center of the cylinder are visible.
+  ///
+  /// The same number of children will be visible in the viewport regardless of
+  /// the [diameterRatio]. The number of children visible is based on the
+  /// viewport's length along the main axis divided by the children's
+  /// [itemExtent]. Then the children are evenly distributed along the visible
+  /// angles up to ±[pi] / 2.
+  ///
+  /// Just as it's impossible to stretch a paper to cover the an entire
+  /// half of a cylinder's surface where the cylinder has the same diameter
+  /// as the paper's length, choosing a [diameterRatio] smaller than [pi]
+  /// will leave same gaps between the children.
+  ///
+  /// Defaults to an arbitrary but aesthetically reasonable number of 2.0.
+  ///
+  /// Must not be null and must be positive.
+  /// {@endtemplate}
+  double get diameterRatio => _diameterRatio;
+  double _diameterRatio;
+  set diameterRatio(double value) {
+    assert(value != null);
+    assert(
+      value > 0,
+      diameterRatioZeroMessage,
+    );
+    if (value == _diameterRatio)
+      return;
+    _diameterRatio = value;
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// {@template flutter.rendering.RenderListWheelViewport.perspective}
+  /// Perspective of the cylindrical projection.
+  ///
+  /// A number between 0 and 0.01 where 0 means looking at the cylinder from
+  /// infinitely far with an infinitely small field of view and 1 means looking
+  /// at the cylinder from infinitely close with an infinitely large field of
+  /// view (which cannot be rendered).
+  ///
+  /// Defaults to an arbitrary but aesthetically reasonable number of 0.003.
+  /// A larger number brings the vanishing point closer and a smaller number
+  /// pushes the vanishing point further.
+  ///
+  /// Must not be null and must be positive.
+  /// {@endtemplate}
+  double get perspective => _perspective;
+  double _perspective;
+  set perspective(double value) {
+    assert(value != null);
+    assert(value > 0);
+    assert(
+      value <= 0.01,
+      perspectiveTooHighMessage,
+    );
+    if (value == _perspective)
+      return;
+    _perspective = value;
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// {@template flutter.rendering.RenderListWheelViewport.offAxisFraction}
+  /// How much the wheel is horizontally off-center, as a fraction of its width.
+
+  /// This property creates the visual effect of looking at a vertical wheel from
+  /// its side where its vanishing points at the edge curves to one side instead
+  /// of looking at the wheel head-on.
+  ///
+  /// The value is horizontal distance between the wheel's center and the vertical
+  /// vanishing line at the edges of the wheel, represented as a fraction of the
+  /// wheel's width.
+  ///
+  /// The value `0.0` means the wheel is looked at head-on and its vanishing
+  /// line runs through the center of the wheel. Negative values means moving
+  /// the wheel to the left of the observer, thus the edges curve to the right.
+  /// Positive values means moving the wheel to the right of the observer,
+  /// thus the edges curve to the left.
+  ///
+  /// The visual effect causes the wheel's edges to curve rather than moving
+  /// the center. So a value of `0.5` means the edges' vanishing line will touch
+  /// the wheel's size's left edge.
+  ///
+  /// Defaults to 0.0, which means looking at the wheel head-on.
+  /// The visual effect can be unaesthetic if this value is too far from the
+  /// range [-0.5, 0.5].
+  /// {@endtemplate}
+  double get offAxisFraction => _offAxisFraction;
+  double _offAxisFraction = 0.0;
+  set offAxisFraction(double value) {
+    assert(value != null);
+    if (value == _offAxisFraction)
+      return;
+    _offAxisFraction = value;
+    markNeedsPaint();
+  }
+
+  /// {@template flutter.rendering.RenderListWheelViewport.useMagnifier}
+  /// Whether to use the magnifier for the center item of the wheel.
+  /// {@endtemplate}
+  bool get useMagnifier => _useMagnifier;
+  bool _useMagnifier = false;
+  set useMagnifier(bool value) {
+    assert(value != null);
+    if (value == _useMagnifier)
+      return;
+    _useMagnifier = value;
+    markNeedsPaint();
+  }
+  /// {@template flutter.rendering.RenderListWheelViewport.magnification}
+  /// The zoomed-in rate of the magnifier, if it is used.
+  ///
+  /// The default value is 1.0, which will not change anything.
+  /// If the value is > 1.0, the center item will be zoomed in by that rate, and
+  /// it will also be rendered as flat, not cylindrical like the rest of the list.
+  /// The item will be zoomed out if magnification < 1.0.
+  ///
+  /// Must be positive.
+  /// {@endtemplate}
+  double get magnification => _magnification;
+  double _magnification = 1.0;
+  set magnification(double value) {
+    assert(value != null);
+    assert(value > 0);
+    if (value == _magnification)
+      return;
+    _magnification = value;
+    markNeedsPaint();
+  }
+
+  /// {@template flutter.rendering.RenderListWheelViewport.overAndUnderCenterOpacity}
+  /// The opacity value that will be applied to the wheel that appears below and
+  /// above the magnifier.
+  ///
+  /// The default value is 1.0, which will not change anything.
+  ///
+  /// Must be greater than or equal to 0, and less than or equal to 1.
+  /// {@endtemplate}
+  double get overAndUnderCenterOpacity => _overAndUnderCenterOpacity;
+  double _overAndUnderCenterOpacity = 1.0;
+  set overAndUnderCenterOpacity(double value) {
+    assert(value != null);
+    assert(value >= 0 && value <= 1);
+    if (value == _overAndUnderCenterOpacity)
+      return;
+    _overAndUnderCenterOpacity = value;
+    markNeedsPaint();
+  }
+
+  /// {@template flutter.rendering.RenderListWheelViewport.itemExtent}
+  /// The size of the children along the main axis. Children [RenderBox]es will
+  /// be given the [BoxConstraints] of this exact size.
+  ///
+  /// Must not be null and must be positive.
+  /// {@endtemplate}
+  double get itemExtent => _itemExtent;
+  double _itemExtent;
+  set itemExtent(double value) {
+    assert(value != null);
+    assert(value > 0);
+    if (value == _itemExtent)
+      return;
+    _itemExtent = value;
+    markNeedsLayout();
+  }
+
+
+  /// {@template flutter.rendering.RenderListWheelViewport.squeeze}
+  /// The angular compactness of the children on the wheel.
+  ///
+  /// This denotes a ratio of the number of children on the wheel vs the number
+  /// of children that would fit on a flat list of equivalent size, assuming
+  /// [diameterRatio] of 1.
+  ///
+  /// For instance, if this RenderListWheelViewport has a height of 100px and
+  /// [itemExtent] is 20px, 5 items would fit on an equivalent flat list.
+  /// With a [squeeze] of 1, 5 items would also be shown in the
+  /// RenderListWheelViewport. With a [squeeze] of 2, 10 items would be shown
+  /// in the RenderListWheelViewport.
+  ///
+  /// Changing this value will change the number of children built and shown
+  /// inside the wheel.
+  ///
+  /// Must not be null and must be positive.
+  /// {@endtemplate}
+  ///
+  /// Defaults to 1.
+  double get squeeze => _squeeze;
+  double _squeeze;
+  set squeeze(double value) {
+    assert(value != null);
+    assert(value > 0);
+    if (value == _squeeze)
+      return;
+    _squeeze = value;
+    markNeedsLayout();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// {@template flutter.rendering.RenderListWheelViewport.renderChildrenOutsideViewport}
+  /// Whether to paint children inside the viewport only.
+  ///
+  /// If false, every child will be painted. However the [Scrollable] is still
+  /// the size of the viewport and detects gestures inside only.
+  ///
+  /// Defaults to false. Must not be null. Cannot be true if [clipBehavior]
+  /// is not [Clip.none] since children outside the viewport will be clipped, and
+  /// therefore cannot render children outside the viewport.
+  /// {@endtemplate}
+  bool get renderChildrenOutsideViewport => _renderChildrenOutsideViewport;
+  bool _renderChildrenOutsideViewport;
+  set renderChildrenOutsideViewport(bool value) {
+    assert(value != null);
+    assert(
+      !renderChildrenOutsideViewport || clipBehavior == Clip.none,
+      clipBehaviorAndRenderChildrenOutsideViewportConflict,
+    );
+    if (value == _renderChildrenOutsideViewport)
+      return;
+    _renderChildrenOutsideViewport = value;
+    markNeedsLayout();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge], and must not be null.
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior = Clip.hardEdge;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  void _hasScrolled() {
+    markNeedsLayout();
+    markNeedsSemanticsUpdate();
+  }
+
+  @override
+  void setupParentData(RenderObject child) {
+    if (child.parentData is! ListWheelParentData)
+      child.parentData = ListWheelParentData();
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    _offset.addListener(_hasScrolled);
+  }
+
+  @override
+  void detach() {
+    _offset.removeListener(_hasScrolled);
+    super.detach();
+  }
+
+  @override
+  bool get isRepaintBoundary => true;
+
+  /// Main axis length in the untransformed plane.
+  double get _viewportExtent {
+    assert(hasSize);
+    return size.height;
+  }
+
+  /// Main axis scroll extent in the **scrollable layout coordinates** that puts
+  /// the first item in the center.
+  double get _minEstimatedScrollExtent {
+    assert(hasSize);
+    if (childManager.childCount == null)
+      return double.negativeInfinity;
+    return 0.0;
+  }
+
+  /// Main axis scroll extent in the **scrollable layout coordinates** that puts
+  /// the last item in the center.
+  double get _maxEstimatedScrollExtent {
+    assert(hasSize);
+    if (childManager.childCount == null)
+      return double.infinity;
+
+    return math.max(0.0, (childManager.childCount! - 1) * _itemExtent);
+  }
+
+  /// Scroll extent distance in the untransformed plane between the center
+  /// position in the viewport and the top position in the viewport.
+  ///
+  /// It's also the distance in the untransformed plane that children's painting
+  /// is offset by with respect to those children's [BoxParentData.offset].
+  double get _topScrollMarginExtent {
+    assert(hasSize);
+    // Consider adding alignment options other than center.
+    return -size.height / 2.0 + _itemExtent / 2.0;
+  }
+
+  /// Transforms a **scrollable layout coordinates**' y position to the
+  /// **untransformed plane's viewport painting coordinates**' y position given
+  /// the current scroll offset.
+  double _getUntransformedPaintingCoordinateY(double layoutCoordinateY) {
+    return layoutCoordinateY - _topScrollMarginExtent - offset.pixels;
+  }
+
+  /// Given the _diameterRatio, return the largest absolute angle of the item
+  /// at the edge of the portion of the visible cylinder.
+  ///
+  /// For a _diameterRatio of 1 or less than 1 (i.e. the viewport is bigger
+  /// than the cylinder diameter), this value reaches and clips at pi / 2.
+  ///
+  /// When the center of children passes this angle, they are no longer painted
+  /// if [renderChildrenOutsideViewport] is false.
+  double get _maxVisibleRadian {
+    if (_diameterRatio < 1.0)
+      return math.pi / 2.0;
+    return math.asin(1.0 / _diameterRatio);
+  }
+
+  double _getIntrinsicCrossAxis(_ChildSizingFunction childSize) {
+    double extent = 0.0;
+    RenderBox? child = firstChild;
+    while (child != null) {
+      extent = math.max(extent, childSize(child));
+      child = childAfter(child);
+    }
+    return extent;
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    return _getIntrinsicCrossAxis(
+      (RenderBox child) => child.getMinIntrinsicWidth(height)
+    );
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    return _getIntrinsicCrossAxis(
+      (RenderBox child) => child.getMaxIntrinsicWidth(height)
+    );
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    if (childManager.childCount == null)
+      return 0.0;
+    return childManager.childCount! * _itemExtent;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    if (childManager.childCount == null)
+      return 0.0;
+    return childManager.childCount! * _itemExtent;
+  }
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return constraints.biggest;
+  }
+
+  /// Gets the index of a child by looking at its [parentData].
+  ///
+  /// This relies on the [childManager] maintaining [ListWheelParentData.index].
+  int indexOf(RenderBox child) {
+    assert(child != null);
+    final ListWheelParentData childParentData = child.parentData! as ListWheelParentData;
+    assert(childParentData.index != null);
+    return childParentData.index!;
+  }
+
+  /// Returns the index of the child at the given offset.
+  int scrollOffsetToIndex(double scrollOffset) => (scrollOffset / itemExtent).floor();
+
+  /// Returns the scroll offset of the child with the given index.
+  double indexToScrollOffset(int index) => index * itemExtent;
+
+  void _createChild(int index, { RenderBox? after }) {
+    invokeLayoutCallback<BoxConstraints>((BoxConstraints constraints) {
+      assert(constraints == this.constraints);
+      childManager.createChild(index, after: after);
+    });
+  }
+
+  void _destroyChild(RenderBox child) {
+    invokeLayoutCallback<BoxConstraints>((BoxConstraints constraints) {
+      assert(constraints == this.constraints);
+      childManager.removeChild(child);
+    });
+  }
+
+  void _layoutChild(RenderBox child, BoxConstraints constraints, int index) {
+    child.layout(constraints, parentUsesSize: true);
+    final ListWheelParentData childParentData = child.parentData! as ListWheelParentData;
+    // Centers the child horizontally.
+    final double crossPosition = size.width / 2.0 - child.size.width / 2.0;
+    childParentData.offset = Offset(crossPosition, indexToScrollOffset(index));
+  }
+
+  /// Performs layout based on how [childManager] provides children.
+  ///
+  /// From the current scroll offset, the minimum index and maximum index that
+  /// is visible in the viewport can be calculated. The index range of the
+  /// currently active children can also be acquired by looking directly at
+  /// the current child list. This function has to modify the current index
+  /// range to match the target index range by removing children that are no
+  /// longer visible and creating those that are visible but not yet provided
+  /// by [childManager].
+  @override
+  void performLayout() {
+    final BoxConstraints childConstraints =
+      constraints.copyWith(
+        minHeight: _itemExtent,
+        maxHeight: _itemExtent,
+        minWidth: 0.0,
+      );
+
+    // The height, in pixel, that children will be visible and might be laid out
+    // and painted.
+    double visibleHeight = size.height * _squeeze;
+    // If renderChildrenOutsideViewport is true, we spawn extra children by
+    // doubling the visibility range, those that are in the backside of the
+    // cylinder won't be painted anyway.
+    if (renderChildrenOutsideViewport)
+      visibleHeight *= 2;
+
+    final double firstVisibleOffset =
+        offset.pixels + _itemExtent / 2 - visibleHeight / 2;
+    final double lastVisibleOffset = firstVisibleOffset + visibleHeight;
+
+    // The index range that we want to spawn children. We find indexes that
+    // are in the interval [firstVisibleOffset, lastVisibleOffset).
+    int targetFirstIndex = scrollOffsetToIndex(firstVisibleOffset);
+    int targetLastIndex = scrollOffsetToIndex(lastVisibleOffset);
+    // Because we exclude lastVisibleOffset, if there's a new child starting at
+    // that offset, it is removed.
+    if (targetLastIndex * _itemExtent == lastVisibleOffset)
+      targetLastIndex--;
+
+    // Validates the target index range.
+    while (!childManager.childExistsAt(targetFirstIndex) && targetFirstIndex <= targetLastIndex)
+      targetFirstIndex++;
+    while (!childManager.childExistsAt(targetLastIndex) && targetFirstIndex <= targetLastIndex)
+      targetLastIndex--;
+
+    // If it turns out there's no children to layout, we remove old children and
+    // return.
+    if (targetFirstIndex > targetLastIndex) {
+      while (firstChild != null)
+        _destroyChild(firstChild!);
+      return;
+    }
+
+    // Now there are 2 cases:
+    //  - The target index range and our current index range have intersection:
+    //    We shorten and extend our current child list so that the two lists
+    //    match. Most of the time we are in this case.
+    //  - The target list and our current child list have no intersection:
+    //    We first remove all children and then add one child from the target
+    //    list => this case becomes the other case.
+
+    // Case when there is no intersection.
+    if (childCount > 0 &&
+        (indexOf(firstChild!) > targetLastIndex || indexOf(lastChild!) < targetFirstIndex)) {
+      while (firstChild != null)
+        _destroyChild(firstChild!);
+    }
+
+    // If there is no child at this stage, we add the first one that is in
+    // target range.
+    if (childCount == 0) {
+      _createChild(targetFirstIndex);
+      _layoutChild(firstChild!, childConstraints, targetFirstIndex);
+    }
+
+    int currentFirstIndex = indexOf(firstChild!);
+    int currentLastIndex = indexOf(lastChild!);
+
+    // Remove all unnecessary children by shortening the current child list, in
+    // both directions.
+    while (currentFirstIndex < targetFirstIndex) {
+      _destroyChild(firstChild!);
+      currentFirstIndex++;
+    }
+    while (currentLastIndex > targetLastIndex) {
+      _destroyChild(lastChild!);
+      currentLastIndex--;
+    }
+
+    // Relayout all active children.
+    RenderBox? child = firstChild;
+    while (child != null) {
+      child.layout(childConstraints, parentUsesSize: true);
+      child = childAfter(child);
+    }
+
+    // Spawning new children that are actually visible but not in child list yet.
+    while (currentFirstIndex > targetFirstIndex) {
+      _createChild(currentFirstIndex - 1);
+      _layoutChild(firstChild!, childConstraints, --currentFirstIndex);
+    }
+    while (currentLastIndex < targetLastIndex) {
+      _createChild(currentLastIndex + 1, after: lastChild);
+      _layoutChild(lastChild!, childConstraints, ++currentLastIndex);
+    }
+
+    offset.applyViewportDimension(_viewportExtent);
+
+    // Applying content dimensions bases on how the childManager builds widgets:
+    // if it is available to provide a child just out of target range, then
+    // we don't know whether there's a limit yet, and set the dimension to the
+    // estimated value. Otherwise, we set the dimension limited to our target
+    // range.
+    final double minScrollExtent = childManager.childExistsAt(targetFirstIndex - 1)
+      ? _minEstimatedScrollExtent
+      : indexToScrollOffset(targetFirstIndex);
+    final double maxScrollExtent = childManager.childExistsAt(targetLastIndex + 1)
+        ? _maxEstimatedScrollExtent
+        : indexToScrollOffset(targetLastIndex);
+    offset.applyContentDimensions(minScrollExtent, maxScrollExtent);
+  }
+
+  bool _shouldClipAtCurrentOffset() {
+    final double highestUntransformedPaintY =
+        _getUntransformedPaintingCoordinateY(0.0);
+    return highestUntransformedPaintY < 0.0
+        || size.height < highestUntransformedPaintY + _maxEstimatedScrollExtent + _itemExtent;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (childCount > 0) {
+      if (_shouldClipAtCurrentOffset() && clipBehavior != Clip.none) {
+        _clipRectLayer = context.pushClipRect(
+          needsCompositing,
+          offset,
+          Offset.zero & size,
+          _paintVisibleChildren,
+          clipBehavior: clipBehavior,
+          oldLayer: _clipRectLayer,
+        );
+      } else {
+        _clipRectLayer = null;
+        _paintVisibleChildren(context, offset);
+      }
+    }
+  }
+
+  ClipRectLayer? _clipRectLayer;
+
+  /// Paints all children visible in the current viewport.
+  void _paintVisibleChildren(PaintingContext context, Offset offset) {
+    RenderBox? childToPaint = firstChild;
+    while (childToPaint != null) {
+      final ListWheelParentData childParentData = childToPaint.parentData! as ListWheelParentData;
+      _paintTransformedChild(childToPaint, context, offset, childParentData.offset);
+      childToPaint = childAfter(childToPaint);
+    }
+  }
+
+  /// Takes in a child with a **scrollable layout offset** and paints it in the
+  /// **transformed cylindrical space viewport painting coordinates**.
+  void _paintTransformedChild(
+    RenderBox child,
+    PaintingContext context,
+    Offset offset,
+    Offset layoutOffset,
+  ) {
+    final Offset untransformedPaintingCoordinates = offset
+        + Offset(
+            layoutOffset.dx,
+            _getUntransformedPaintingCoordinateY(layoutOffset.dy),
+        );
+
+    // Get child's center as a fraction of the viewport's height.
+    final double fractionalY =
+        (untransformedPaintingCoordinates.dy + _itemExtent / 2.0) / size.height;
+    final double angle = -(fractionalY - 0.5) * 2.0 * _maxVisibleRadian / squeeze;
+    // Don't paint the backside of the cylinder when
+    // renderChildrenOutsideViewport is true. Otherwise, only children within
+    // suitable angles (via _first/lastVisibleLayoutOffset) reach the paint
+    // phase.
+    if (angle > math.pi / 2.0 || angle < -math.pi / 2.0)
+      return;
+
+    final Matrix4 transform = MatrixUtils.createCylindricalProjectionTransform(
+      radius: size.height * _diameterRatio / 2.0,
+      angle: angle,
+      perspective: _perspective,
+    );
+
+    // Offset that helps painting everything in the center (e.g. angle = 0).
+    final Offset offsetToCenter = Offset(
+      untransformedPaintingCoordinates.dx,
+      -_topScrollMarginExtent,
+    );
+
+    final bool shouldApplyOffCenterDim = overAndUnderCenterOpacity < 1;
+    if (useMagnifier || shouldApplyOffCenterDim) {
+      _paintChildWithMagnifier(context, offset, child, transform, offsetToCenter, untransformedPaintingCoordinates);
+    } else {
+      _paintChildCylindrically(context, offset, child, transform, offsetToCenter);
+    }
+  }
+
+  /// Paint child with the magnifier active - the child will be rendered
+  /// differently if it intersects with the magnifier.
+  void _paintChildWithMagnifier(
+    PaintingContext context,
+    Offset offset,
+    RenderBox child,
+    Matrix4 cylindricalTransform,
+    Offset offsetToCenter,
+    Offset untransformedPaintingCoordinates,
+  ) {
+    final double magnifierTopLinePosition =
+        size.height / 2 - _itemExtent * _magnification / 2;
+    final double magnifierBottomLinePosition =
+        size.height / 2 + _itemExtent * _magnification / 2;
+
+    final bool isAfterMagnifierTopLine = untransformedPaintingCoordinates.dy
+        >= magnifierTopLinePosition - _itemExtent * _magnification;
+    final bool isBeforeMagnifierBottomLine = untransformedPaintingCoordinates.dy
+        <= magnifierBottomLinePosition;
+
+    // Some part of the child is in the center magnifier.
+    if (isAfterMagnifierTopLine && isBeforeMagnifierBottomLine) {
+      final Rect centerRect = Rect.fromLTWH(
+          0.0,
+          magnifierTopLinePosition,
+          size.width,
+          _itemExtent * _magnification);
+      final Rect topHalfRect = Rect.fromLTWH(
+          0.0,
+          0.0,
+          size.width,
+          magnifierTopLinePosition);
+      final Rect bottomHalfRect = Rect.fromLTWH(
+          0.0,
+          magnifierBottomLinePosition,
+          size.width,
+          magnifierTopLinePosition);
+
+      // Clipping the part in the center.
+      context.pushClipRect(
+        needsCompositing,
+        offset,
+        centerRect,
+        (PaintingContext context, Offset offset) {
+          context.pushTransform(
+            needsCompositing,
+            offset,
+            _magnifyTransform(),
+            (PaintingContext context, Offset offset) {
+              context.paintChild(child, offset + untransformedPaintingCoordinates);
+          });
+      });
+
+      // Clipping the part in either the top-half or bottom-half of the wheel.
+      context.pushClipRect(
+        needsCompositing,
+        offset,
+        untransformedPaintingCoordinates.dy <= magnifierTopLinePosition
+          ? topHalfRect
+          : bottomHalfRect,
+        (PaintingContext context, Offset offset) {
+          _paintChildCylindrically(
+            context,
+            offset,
+            child,
+            cylindricalTransform,
+            offsetToCenter);
+        },
+      );
+    } else {
+      _paintChildCylindrically(
+        context,
+        offset,
+        child,
+        cylindricalTransform,
+        offsetToCenter);
+    }
+  }
+
+  // / Paint the child cylindrically at given offset.
+  void _paintChildCylindrically(
+    PaintingContext context,
+    Offset offset,
+    RenderBox child,
+    Matrix4 cylindricalTransform,
+    Offset offsetToCenter,
+  ) {
+    // Paint child cylindrically, without [overAndUnderCenterOpacity].
+    final PaintingContextCallback painter = (PaintingContext context, Offset offset) {
+      context.paintChild(
+        child,
+        // Paint everything in the center (e.g. angle = 0), then transform.
+        offset + offsetToCenter,
+      );
+    };
+
+    // Paint child cylindrically, with [overAndUnderCenterOpacity].
+    final PaintingContextCallback opacityPainter = (PaintingContext context, Offset offset) {
+      context.pushOpacity(offset, (overAndUnderCenterOpacity * 255).round(), painter);
+    };
+
+    context.pushTransform(
+      needsCompositing,
+      offset,
+      _centerOriginTransform(cylindricalTransform),
+      // Pre-transform painting function.
+      overAndUnderCenterOpacity == 1 ? painter : opacityPainter,
+    );
+  }
+
+  /// Return the Matrix4 transformation that would zoom in content in the
+  /// magnified area.
+  Matrix4 _magnifyTransform() {
+    final Matrix4 magnify = Matrix4.identity();
+    magnify.translate(size.width * (-_offAxisFraction + 0.5), size.height / 2);
+    magnify.scale(_magnification, _magnification, _magnification);
+    magnify.translate(-size.width * (-_offAxisFraction + 0.5), -size.height / 2);
+    return magnify;
+  }
+
+  /// Apply incoming transformation with the transformation's origin at the
+  /// viewport's center or horizontally off to the side based on offAxisFraction.
+  Matrix4 _centerOriginTransform(Matrix4 originalMatrix) {
+    final Matrix4 result = Matrix4.identity();
+    final Offset centerOriginTranslation = Alignment.center.alongSize(size);
+    result.translate(centerOriginTranslation.dx * (-_offAxisFraction * 2 + 1),
+                     centerOriginTranslation.dy);
+    result.multiply(originalMatrix);
+    result.translate(-centerOriginTranslation.dx * (-_offAxisFraction * 2 + 1),
+                     -centerOriginTranslation.dy);
+    return result;
+  }
+
+  /// This returns the matrices relative to the **untransformed plane's viewport
+  /// painting coordinates** system.
+  @override
+  void applyPaintTransform(RenderBox child, Matrix4 transform) {
+    final ListWheelParentData parentData = child.parentData! as ListWheelParentData;
+    transform.translate(0.0, _getUntransformedPaintingCoordinateY(parentData.offset.dy));
+  }
+
+  @override
+  Rect? describeApproximatePaintClip(RenderObject child) {
+    if (_shouldClipAtCurrentOffset()) {
+      return Offset.zero & size;
+    }
+    return null;
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) => false;
+
+  @override
+  RevealedOffset getOffsetToReveal(RenderObject target, double alignment, { Rect? rect }) {
+    // `target` is only fully revealed when in the selected/center position. Therefore,
+    // this method always returns the offset that shows `target` in the center position,
+    // which is the same offset for all `alignment` values.
+
+    rect ??= target.paintBounds;
+
+    // `child` will be the last RenderObject before the viewport when walking up from `target`.
+    RenderObject child = target;
+    while (child.parent != this)
+      child = child.parent! as RenderObject;
+
+    final ListWheelParentData parentData = child.parentData! as ListWheelParentData;
+    final double targetOffset = parentData.offset.dy; // the so-called "centerPosition"
+
+    final Matrix4 transform = target.getTransformTo(child);
+    final Rect bounds = MatrixUtils.transformRect(transform, rect);
+    final Rect targetRect = bounds.translate(0.0, (size.height - itemExtent) / 2);
+
+    return RevealedOffset(offset: targetOffset, rect: targetRect);
+  }
+
+  @override
+  void showOnScreen({
+    RenderObject? descendant,
+    Rect? rect,
+    Duration duration = Duration.zero,
+    Curve curve = Curves.ease,
+  }) {
+    if (descendant != null) {
+      // Shows the descendant in the selected/center position.
+      final RevealedOffset revealedOffset = getOffsetToReveal(descendant, 0.5, rect: rect);
+      if (duration == Duration.zero) {
+        offset.jumpTo(revealedOffset.offset);
+      } else {
+        offset.animateTo(revealedOffset.offset, duration: duration, curve: curve);
+      }
+      rect = revealedOffset.rect;
+    }
+
+    super.showOnScreen(
+      rect: rect,
+      duration: duration,
+      curve: curve,
+    );
+  }
+}
diff --git a/lib/src/rendering/mouse_cursor.dart b/lib/src/rendering/mouse_cursor.dart
new file mode 100644
index 0000000..4233302
--- /dev/null
+++ b/lib/src/rendering/mouse_cursor.dart
@@ -0,0 +1,919 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/services.dart';
+
+import 'mouse_tracking.dart';
+
+/// A mixin for [BaseMouseTracker] that sets the mouse pointer's cursors
+/// on device update.
+///
+/// See also:
+///
+///  * [MouseTracker], which uses this mixin.
+mixin MouseTrackerCursorMixin on BaseMouseTracker {
+  /// Returns the active mouse cursor of a device.
+  ///
+  /// The return value is the last [MouseCursor] activated onto this
+  /// device, even if the activation failed.
+  ///
+  /// Only valid when asserts are enabled. In release builds, always returns
+  /// null.
+  @visibleForTesting
+  MouseCursor? debugDeviceActiveCursor(int device) {
+    MouseCursor? result;
+    assert(() {
+      result = _lastSession[device]?.cursor;
+      return true;
+    }());
+    return result;
+  }
+
+  @protected
+  @override
+  void handleDeviceUpdate(MouseTrackerUpdateDetails details) {
+    super.handleDeviceUpdate(details);
+    _handleDeviceUpdateMouseCursor(details);
+  }
+
+  final Map<int, MouseCursorSession> _lastSession = <int, MouseCursorSession>{};
+
+  // Find the first non-deferred mouse cursor, which fallbacks to
+  // [SystemMouseCursors.basic].
+  //
+  // The `annotations` is the current annotations that the device is hovering in
+  // visual order from front the back.
+  // The return value is never null.
+  MouseCursor _findFirstCursor(Iterable<MouseTrackerAnnotation> annotations) {
+    return _DeferringMouseCursor.firstNonDeferred(
+      annotations.map((MouseTrackerAnnotation annotation) => annotation.cursor),
+    ) ?? SystemMouseCursors.basic;
+  }
+
+  // Handles device update and changes mouse cursors.
+  void _handleDeviceUpdateMouseCursor(MouseTrackerUpdateDetails details) {
+    final int device = details.device;
+
+    if (details.triggeringEvent is PointerRemovedEvent) {
+      _lastSession.remove(device);
+      return;
+    }
+
+    final MouseCursorSession? lastSession = _lastSession[device];
+    final MouseCursor nextCursor = _findFirstCursor(details.nextAnnotations.keys);
+    if (lastSession?.cursor == nextCursor)
+      return;
+
+    final MouseCursorSession nextSession = nextCursor.createSession(device);
+    _lastSession[device] = nextSession;
+
+    lastSession?.dispose();
+    nextSession.activate();
+  }
+}
+
+/// Manages the duration that a pointing device should display a specific mouse
+/// cursor.
+///
+/// While [MouseCursor] classes describe the kind of cursors, [MouseCursorSession]
+/// classes represents a continuous use of the cursor on a pointing device. The
+/// [MouseCursorSession] classes can be stateful. For example, a cursor that
+/// needs to load resources might want to set a temporary cursor first, then
+/// switch to the correct cursor after the load is completed.
+///
+/// A [MouseCursorSession] has the following lifecycle:
+///
+///  * When a pointing device should start displaying a cursor, [MouseTracker]
+///    creates a session by calling [MouseCursor.createSession] on the target
+///    cursor, and stores it in a table associated with the device.
+///  * [MouseTracker] then immediately calls the session's [activate], where the
+///    session should fetch resources and make system calls.
+///  * When the pointing device should start displaying a different cursor,
+///    [MouseTracker] calls [dispose] on this session. After [dispose], this session
+///    will no longer be used in the future.
+abstract class MouseCursorSession {
+  /// Create a session.
+  ///
+  /// All arguments must be non-null.
+  MouseCursorSession(this.cursor, this.device)
+    : assert(cursor != null),
+      assert(device != null);
+
+  /// The cursor that created this session.
+  final MouseCursor cursor;
+
+  /// The device ID of the pointing device.
+  final int device;
+
+  /// Override this method to do the work of changing the cursor of the device.
+  ///
+  /// Called right after this session is created.
+  ///
+  /// This method has full control over the cursor until the [dispose] call, and
+  /// can make system calls to change the pointer cursor as many times as
+  /// necessary (usually through [SystemChannels.mouseCursor]). It can also
+  /// collect resources, and store the result in this object.
+  @protected
+  Future<void> activate();
+
+  /// Called when device stops displaying the cursor.
+  ///
+  /// After this call, this session instance will no longer be used in the
+  /// future.
+  ///
+  /// When implementing this method in subclasses, you should release resources
+  /// and prevent [activate] from causing side effects after disposal.
+  @protected
+  void dispose();
+}
+
+/// An interface for mouse cursor definitions.
+///
+/// A mouse cursor is a graphical image on the screen that echoes the movement
+/// of a pointing device, such as a mouse or a stylus. A [MouseCursor] object
+/// defines a kind of mouse cursor, such as an arrow, a pointing hand, or an
+/// I-beam.
+///
+/// During the painting phase, [MouseCursor] objects are assigned to regions on
+/// the screen via annotations. Later during a device update (e.g. when a mouse
+/// moves), [MouseTracker] finds the _active cursor_ of each mouse device, which
+/// is the front-most region associated with the position of each mouse cursor,
+/// or defaults to [SystemMouseCursors.basic] if no cursors are associated with
+/// the position. [MouseTracker] changes the cursor of the pointer if the new
+/// active cursor is different from the previous active cursor, whose effect is
+/// defined by the session created by [createSession].
+///
+/// ## Cursor classes
+///
+/// A [SystemMouseCursor] is a cursor that is natively supported by the
+/// platform that the program is running on. All supported system mouse cursors
+/// are enumerated in [SystemMouseCursors].
+///
+/// ## Using cursors
+///
+/// A [MouseCursor] object is used by being assigned to a [MouseRegion] or
+/// another widget that exposes the [MouseRegion] API, such as
+/// [InkResponse.mouseCursor].
+///
+/// {@tool snippet --template=stateless_widget_material}
+/// This sample creates a rectangular region that is wrapped by a [MouseRegion]
+/// with a system mouse cursor. The mouse pointer becomes an I-beam when
+/// hovering over the region.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Center(
+///     child: MouseRegion(
+///       cursor: SystemMouseCursors.text,
+///       child: Container(
+///         width: 200,
+///         height: 100,
+///         decoration: BoxDecoration(
+///           color: Colors.blue,
+///           border: Border.all(color: Colors.yellow),
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// Assigning regions with mouse cursors on platforms that do not support mouse
+/// cursors, or when there are no mice connected, will have no effect.
+///
+/// ## Related classes
+///
+/// [MouseCursorSession] represents the duration when a pointing device displays
+/// a cursor, and defines the states and behaviors of the cursor. Every mouse
+/// cursor class usually has a corresponding [MouseCursorSession] class.
+///
+/// [MouseTrackerCursorMixin] is a mixin that adds the feature of changing
+/// cursors to [BaseMouseTracker], which tracks the relationship between mouse
+/// devices and annotations. [MouseTrackerCursorMixin] is usually used as a part
+/// of [MouseTracker].
+@immutable
+abstract class MouseCursor with Diagnosticable {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const MouseCursor();
+
+  /// Associate a pointing device to this cursor.
+  ///
+  /// A mouse cursor class usually has a corresponding [MouseCursorSession]
+  /// class, and instantiates such class in this method.
+  ///
+  /// This method is called each time a pointing device starts displaying this
+  /// cursor. A given cursor can be displayed by multiple devices at the same
+  /// time, in which case this method will be called separately for each device.
+  @protected
+  @factory
+  MouseCursorSession createSession(int device);
+
+  /// A very short description of the mouse cursor.
+  ///
+  /// The [debugDescription] should be a few words that can describe this cursor
+  /// to make debug information more readable. It is returned as the [toString]
+  /// when the diagnostic level is at or above [DiagnosticLevel.info].
+  ///
+  /// The [debugDescription] must not be null or empty string.
+  String get debugDescription;
+
+  @override
+  String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
+    final String debugDescription = this.debugDescription;
+    if (minLevel.index >= DiagnosticLevel.info.index && debugDescription != null)
+      return debugDescription;
+    return super.toString(minLevel: minLevel);
+  }
+
+  /// A special class that indicates that the region with this cursor defers the
+  /// choice of cursor to the next region behind it.
+  ///
+  /// When an event occurs, [MouseTracker] will update each pointer's cursor by
+  /// finding the list of regions that contain the pointer's location, from front
+  /// to back in hit-test order. The pointer's cursor will be the first cursor in
+  /// the list that is not a [MouseCursor.defer].
+  static const MouseCursor defer = _DeferringMouseCursor._();
+
+  /// A special value that doesn't change cursor by itself, but make a region
+  /// that blocks other regions behind it from changing the cursor.
+  ///
+  /// When a pointer enters a region with a cursor of [uncontrolled], the pointer
+  /// retains its previous cursor and keeps so until it moves out of the region.
+  /// Technically, this region absorb the mouse cursor hit test without changing
+  /// the pointer's cursor.
+  ///
+  /// This is useful in a region that displays a platform view, which let the
+  /// operating system handle pointer events and change cursors accordingly. To
+  /// achieve this, the region's cursor must not be any Flutter cursor, since
+  /// that might overwrite the system request upon pointer entering; the cursor
+  /// must not be null either, since that allows the widgets behind the region to
+  /// change cursors.
+  static const MouseCursor uncontrolled = _NoopMouseCursor._();
+}
+
+class _DeferringMouseCursor extends MouseCursor {
+  const _DeferringMouseCursor._();
+
+  @override
+  MouseCursorSession createSession(int device) {
+    assert(false, '_DeferringMouseCursor can not create a session');
+    throw UnimplementedError();
+  }
+
+  @override
+  String get debugDescription => 'defer';
+
+  /// Returns the first cursor that is not a [MouseCursor.defer].
+  static MouseCursor? firstNonDeferred(Iterable<MouseCursor> cursors) {
+    for (final MouseCursor cursor in cursors) {
+      assert(cursor != null);
+      if (cursor != MouseCursor.defer)
+        return cursor;
+    }
+    return null;
+  }
+}
+
+class _NoopMouseCursorSession extends MouseCursorSession {
+  _NoopMouseCursorSession(_NoopMouseCursor cursor, int device)
+    : super(cursor, device);
+
+  @override
+  Future<void> activate() async { /* Nothing */ }
+
+  @override
+  void dispose() { /* Nothing */ }
+}
+
+/// A mouse cursor that doesn't change the cursor when activated.
+///
+/// Although setting a region's cursor to [NoopMouseCursor] doesn't change the
+/// cursor, it blocks regions behind it from changing the cursor, in contrast to
+/// setting the cursor to null. More information about the usage of this class
+/// can be found at [MouseCursors.uncontrolled].
+///
+/// To use this class, use [MouseCursors.uncontrolled]. Directly
+/// instantiating this class is not allowed.
+class _NoopMouseCursor extends MouseCursor {
+  // Application code shouldn't directly instantiate this class, since its only
+  // instance is accessible at [SystemMouseCursors.releaseControl].
+  const _NoopMouseCursor._();
+
+  @override
+  @protected
+  _NoopMouseCursorSession createSession(int device) => _NoopMouseCursorSession(this, device);
+
+  @override
+  String get debugDescription => 'uncontrolled';
+}
+
+class _SystemMouseCursorSession extends MouseCursorSession {
+  _SystemMouseCursorSession(SystemMouseCursor cursor, int device)
+    : super(cursor, device);
+
+  @override
+  SystemMouseCursor get cursor => super.cursor as SystemMouseCursor;
+
+  @override
+  Future<void> activate() {
+    return SystemChannels.mouseCursor.invokeMethod<void>(
+      'activateSystemCursor',
+      <String, dynamic>{
+        'device': device,
+        'kind': cursor.kind,
+      },
+    );
+  }
+
+  @override
+  void dispose() { /* Nothing */ }
+}
+
+/// A mouse cursor that is natively supported on the platform that the
+/// application is running on.
+///
+/// System cursors can be used without external resources, and their appearances
+/// match the experience of native apps. Examples of system cursors are a
+/// pointing arrow, a pointing hand, a double arrow for resizing, or a text
+/// I-beam, etc.
+///
+/// An instance of [SystemMouseCursor] refers to one cursor from each platform
+/// that represents the same concept, such as being text text, being clickable,
+/// or being a forbidden operation. Since the set of system cursors supported by
+/// each platform varies, multiple instances can correspond to the same system
+/// cursor.
+///
+/// Each cursor is noted with its corresponding native cursors on each platform:
+///
+///  * Android: API name in Java
+///  * Web: CSS cursor
+///  * Windows: Win32 API
+///  * Linux: GDK, `gdk_cursor_new_from_name`
+///  * macOS: API name in Objective C
+///
+/// If the platform that the application is running on is not listed for a cursor,
+/// using this cursor falls back to [SystemMouseCursors.basic].
+///
+/// [SystemMouseCursors] enumerates the complete set of system cursors supported
+/// by Flutter, which are hard-coded in the engine. Therefore, manually
+/// instantiating this class is not supported.
+class SystemMouseCursor extends MouseCursor {
+  // Application code shouldn't directly instantiate system mouse cursors, since
+  // the supported system cursors are enumerated in [SystemMouseCursors].
+  const SystemMouseCursor._({
+    required this.kind,
+  }) : assert(kind != null);
+
+  /// A string that identifies the kind of the cursor.
+  ///
+  /// The interpretation of [kind] is platform-dependent.
+  final String kind;
+
+  @override
+  String get debugDescription => '${objectRuntimeType(this, 'SystemMouseCursor')}($kind)';
+
+  @override
+  @protected
+  _SystemMouseCursorSession createSession(int device) => _SystemMouseCursorSession(this, device);
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is SystemMouseCursor
+        && other.kind == kind;
+  }
+
+  @override
+  int get hashCode => kind.hashCode;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<String>('kind', kind, level: DiagnosticLevel.debug));
+  }
+}
+
+/// A collection of system [MouseCursor]s.
+///
+/// System cursors are standard mouse cursors that are provided by the current
+/// platform. They don't require external resources.
+///
+/// [SystemMouseCursors] is a superset of the system cursors of every platform
+/// that Flutter supports, therefore some of these objects might map to the same
+/// result, or fallback to the [basic] arrow. This mapping is defined by the
+/// Flutter engine.
+///
+/// The cursors should be named based on the cursors' use cases instead of their
+/// appearance, because different platforms might (although not commonly) use
+/// different shapes for the same use case.
+class SystemMouseCursors {
+  // This class only contains static members, and should not be instantiated or
+  // extended.
+  factory SystemMouseCursors._() => throw Error();
+
+  // The mapping in this class must be kept in sync with the following files in
+  // the engine:
+  //
+  // * Android: shell/platform/android/io/flutter/plugin/mouse/MouseCursorPlugin.java
+  // * Web: lib/web_ui/lib/src/engine/mouse_cursor.dart
+  // * Windows: shell/platform/windows/win32_flutter_window.cc
+  // * Linux: shell/platform/linux/fl_mouse_cursor_plugin.cc
+  // * macOS: shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm
+
+
+  /// Hide the cursor.
+  ///
+  /// Any cursor other than [none] or [MouseCursor.uncontrolled] unhides the
+  /// cursor.
+  static const SystemMouseCursor none = SystemMouseCursor._(kind: 'none');
+
+
+  // STATUS
+
+  /// The platform-dependent basic cursor.
+  ///
+  /// Typically the shape of an arrow.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_DEFAULT, TYPE_ARROW
+  ///  * Web: default
+  ///  * Windows: IDC_ARROW
+  ///  * Linux: default
+  ///  * macOS: arrowCursor
+  static const SystemMouseCursor basic = SystemMouseCursor._(kind: 'basic');
+
+  /// A cursor that emphasizes an element being clickable, such as a hyperlink.
+  ///
+  /// Typically the shape of a pointing hand.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_HAND
+  ///  * Web: pointer
+  ///  * Windows: IDC_HAND
+  ///  * Linux: pointer
+  ///  * macOS: pointingHandCursor
+  static const SystemMouseCursor click = SystemMouseCursor._(kind: 'click');
+
+  /// A cursor indicating an operation that will not be carried out.
+  ///
+  /// Typically the shape of a circle with a diagonal line. May fall back to
+  /// [noDrop].
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_NO_DROP
+  ///  * Web: not-allowed
+  ///  * Windows: IDC_NO
+  ///  * Linux: not-allowed
+  ///  * macOS: operationNotAllowedCursor
+  ///
+  /// See also:
+  ///
+  ///  * [noDrop], which indicates somewhere that the current item may not be
+  ///    dropped.
+  static const SystemMouseCursor forbidden = SystemMouseCursor._(kind: 'forbidden');
+
+  /// A cursor indicating the status that the program is busy and therefore
+  /// can not be interacted with.
+  ///
+  /// Typically the shape of an hourglass or a watch.
+  ///
+  /// This cursor is not available as a system cursor on macOS. Although macOS
+  /// displays a "spinning ball" cursor when busy, it's handled by the OS and not
+  /// exposed for applications to choose.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_WAIT
+  ///  * Windows: IDC_WAIT
+  ///  * Web: wait
+  ///  * Linux: wait
+  ///
+  /// See also:
+  ///
+  ///  * [progress], which is similar to [wait] but the program can still be
+  ///    interacted with.
+  static const SystemMouseCursor wait = SystemMouseCursor._(kind: 'wait');
+
+  /// A cursor indicating the status that the program is busy but can still be
+  /// interacted with.
+  ///
+  /// Typically the shape of an arrow with an hourglass or a watch at the corner.
+  /// Does *not* fall back to [wait] if unavailable.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Web: progress
+  ///  * Windows: IDC_APPSTARTING
+  ///  * Linux: progress
+  ///
+  /// See also:
+  ///
+  ///  * [wait], which is similar to [progress] but the program can not be
+  ///    interacted with.
+  static const SystemMouseCursor progress = SystemMouseCursor._(kind: 'progress');
+
+  /// A cursor indicating somewhere the user can trigger a context menu.
+  ///
+  /// Typically the shape of an arrow with a small menu at the corner.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_CONTEXT_MENU
+  ///  * Web: context-menu
+  ///  * Linux: context-menu
+  ///  * macOS: contextualMenuCursor
+  static const SystemMouseCursor contextMenu = SystemMouseCursor._(kind: 'contextMenu');
+
+  /// A cursor indicating help information.
+  ///
+  /// Typically the shape of a question mark, or an arrow therewith.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_HELP
+  ///  * Windows: IDC_HELP
+  ///  * Web: help
+  ///  * Linux: help
+  static const SystemMouseCursor help = SystemMouseCursor._(kind: 'help');
+
+
+  // SELECTION
+
+  /// A cursor indicating selectable text.
+  ///
+  /// Typically the shape of a capital I.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_TEXT
+  ///  * Web: text
+  ///  * Windows: IDC_IBEAM
+  ///  * Linux: text
+  ///  * macOS: IBeamCursor
+  static const SystemMouseCursor text = SystemMouseCursor._(kind: 'text');
+
+  /// A cursor indicating selectable vertical text.
+  ///
+  /// Typically the shape of a capital I rotated to be horizontal. May fall back
+  /// to [text].
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_VERTICAL_TEXT
+  ///  * Web: vertical-text
+  ///  * Linux: vertical-text
+  ///  * macOS: IBeamCursorForVerticalLayout
+  static const SystemMouseCursor verticalText = SystemMouseCursor._(kind: 'verticalText');
+
+  /// A cursor indicating selectable table cells.
+  ///
+  /// Typically the shape of a hollow plus sign.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_CELL
+  ///  * Web: cell
+  ///  * Linux: cell
+  static const SystemMouseCursor cell = SystemMouseCursor._(kind: 'cell');
+
+  /// A cursor indicating precise selection, such as selecting a pixel in a
+  /// bitmap.
+  ///
+  /// Typically the shape of a crosshair.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_CROSSHAIR
+  ///  * Web: crosshair
+  ///  * Windows: IDC_CROSS
+  ///  * Linux: crosshair
+  ///  * macOS: crosshairCursor
+  static const SystemMouseCursor precise = SystemMouseCursor._(kind: 'precise');
+
+
+  // DRAG-AND-DROP
+
+  /// A cursor indicating moving something.
+  ///
+  /// Typically the shape of four-way arrow. May fall back to [allScroll].
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_ALL_SCROLL
+  ///  * Windows: IDC_SIZEALL
+  ///  * Web: move
+  ///  * Linux: move
+  static const SystemMouseCursor move = SystemMouseCursor._(kind: 'move');
+
+  /// A cursor indicating something that can be dragged.
+  ///
+  /// Typically the shape of an open hand.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_GRAB
+  ///  * Web: grab
+  ///  * Linux: grab
+  ///  * macOS: openHandCursor
+  static const SystemMouseCursor grab = SystemMouseCursor._(kind: 'grab');
+
+  /// A cursor indicating something that is being dragged.
+  ///
+  /// Typically the shape of a closed hand.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_GRABBING
+  ///  * Web: grabbing
+  ///  * Linux: grabbing
+  ///  * macOS: closedHandCursor
+  static const SystemMouseCursor grabbing = SystemMouseCursor._(kind: 'grabbing');
+
+  /// A cursor indicating somewhere that the current item may not be dropped.
+  ///
+  /// Typically the shape of a hand with a [forbidden] sign at the corner. May
+  /// fall back to [forbidden].
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_NO_DROP
+  ///  * Web: no-drop
+  ///  * Windows: IDC_NO
+  ///  * Linux: no-drop
+  ///  * macOS: operationNotAllowedCursor
+  ///
+  /// See also:
+  ///
+  ///  * [forbidden], which indicates an action that will not be carried out.
+  static const SystemMouseCursor noDrop = SystemMouseCursor._(kind: 'noDrop');
+
+  /// A cursor indicating that the current operation will create an alias of, or
+  /// a shortcut of the item.
+  ///
+  /// Typically the shape of an arrow with a shortcut icon at the corner.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_ALIAS
+  ///  * Web: alias
+  ///  * Linux: alias
+  ///  * macOS: dragLinkCursor
+  static const SystemMouseCursor alias = SystemMouseCursor._(kind: 'alias');
+
+  /// A cursor indicating that the current operation will copy the item.
+  ///
+  /// Typically the shape of an arrow with a boxed plus sign at the corner.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_COPY
+  ///  * Web: copy
+  ///  * Linux: copy
+  ///  * macOS: dragCopyCursor
+  static const SystemMouseCursor copy = SystemMouseCursor._(kind: 'copy');
+
+  /// A cursor indicating that the current operation will result in the
+  /// disappearance of the item.
+  ///
+  /// Typically the shape of an arrow with a cloud of smoke at the corner.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * macOS: disappearingItemCursor
+  static const SystemMouseCursor disappearing = SystemMouseCursor._(kind: 'disappearing');
+
+
+  // RESIZING AND SCROLLING
+
+  /// A cursor indicating scrolling in any direction.
+  ///
+  /// Typically the shape of a dot surrounded by 4 arrows.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_ALL_SCROLL
+  ///  * Windows: IDC_SIZEALL
+  ///  * Web: all-scroll
+  ///  * Linux: all-scroll
+  ///
+  /// See also:
+  ///
+  ///  * [move], which indicates moving in any direction.
+  static const SystemMouseCursor allScroll = SystemMouseCursor._(kind: 'allScroll');
+
+  /// A cursor indicating resizing an object bidirectionally from its left or
+  /// right edge.
+  ///
+  /// Typically the shape of a bidirectional arrow pointing left and right.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_HORIZONTAL_DOUBLE_ARROW
+  ///  * Web: ew-resize
+  ///  * Windows: IDC_SIZEWE
+  ///  * Linux: ew-resize
+  ///  * macOS: resizeLeftRightCursor
+  static const SystemMouseCursor resizeLeftRight = SystemMouseCursor._(kind: 'resizeLeftRight');
+
+  /// A cursor indicating resizing an object bidirectionally from its top or
+  /// bottom edge.
+  ///
+  /// Typically the shape of a bidirectional arrow pointing up and down.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_VERTICAL_DOUBLE_ARROW
+  ///  * Web: ns-resize
+  ///  * Windows: IDC_SIZENS
+  ///  * Linux: ns-resize
+  ///  * macOS: resizeUpDownCursor
+  static const SystemMouseCursor resizeUpDown = SystemMouseCursor._(kind: 'resizeUpDown');
+
+  /// A cursor indicating resizing an object bidirectionally from its top left or
+  /// bottom right corner.
+  ///
+  /// Typically the shape of a bidirectional arrow pointing upper left and lower right.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW
+  ///  * Web: nwse-resize
+  ///  * Windows: IDC_SIZENWSE
+  ///  * Linux: nwse-resize
+  static const SystemMouseCursor resizeUpLeftDownRight = SystemMouseCursor._(kind: 'resizeUpLeftDownRight');
+
+  /// A cursor indicating resizing an object bidirectionally from its top right or
+  /// bottom left corner.
+  ///
+  /// Typically the shape of a bidirectional arrow pointing upper right and lower left.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW
+  ///  * Windows: IDC_SIZENESW
+  ///  * Web: nesw-resize
+  ///  * Linux: nesw-resize
+  static const SystemMouseCursor resizeUpRightDownLeft = SystemMouseCursor._(kind: 'resizeUpRightDownLeft');
+
+  /// A cursor indicating resizing an object from its top edge.
+  ///
+  /// Typically the shape of an arrow pointing up. May fallback to [resizeUpDown].
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_VERTICAL_DOUBLE_ARROW
+  ///  * Web: n-resize
+  ///  * Windows: IDC_SIZENS
+  ///  * Linux: n-resize
+  ///  * macOS: resizeUpCursor
+  static const SystemMouseCursor resizeUp = SystemMouseCursor._(kind: 'resizeUp');
+
+  /// A cursor indicating resizing an object from its bottom edge.
+  ///
+  /// Typically the shape of an arrow pointing down. May fallback to [resizeUpDown].
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_VERTICAL_DOUBLE_ARROW
+  ///  * Web: s-resize
+  ///  * Windows: IDC_SIZENS
+  ///  * Linux: s-resize
+  ///  * macOS: resizeDownCursor
+  static const SystemMouseCursor resizeDown = SystemMouseCursor._(kind: 'resizeDown');
+
+  /// A cursor indicating resizing an object from its left edge.
+  ///
+  /// Typically the shape of an arrow pointing left. May fallback to [resizeLeftRight].
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_HORIZONTAL_DOUBLE_ARROW
+  ///  * Web: w-resize
+  ///  * Windows: IDC_SIZEWE
+  ///  * Linux: w-resize
+  ///  * macOS: resizeLeftCursor
+  static const SystemMouseCursor resizeLeft = SystemMouseCursor._(kind: 'resizeLeft');
+
+  /// A cursor indicating resizing an object from its right edge.
+  ///
+  /// Typically the shape of an arrow pointing right. May fallback to [resizeLeftRight].
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_HORIZONTAL_DOUBLE_ARROW
+  ///  * Web: e-resize
+  ///  * Windows: IDC_SIZEWE
+  ///  * Linux: e-resize
+  ///  * macOS: resizeRightCursor
+  static const SystemMouseCursor resizeRight = SystemMouseCursor._(kind: 'resizeRight');
+
+  /// A cursor indicating resizing an object from its top-left corner.
+  ///
+  /// Typically the shape of an arrow pointing upper left. May fallback to [resizeUpLeftDownRight].
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW
+  ///  * Web: nw-resize
+  ///  * Windows: IDC_SIZENWSE
+  ///  * Linux: nw-resize
+  static const SystemMouseCursor resizeUpLeft = SystemMouseCursor._(kind: 'resizeUpLeft');
+
+  /// A cursor indicating resizing an object from its top-right corner.
+  ///
+  /// Typically the shape of an arrow pointing upper right. May fallback to [resizeUpRightDownLeft].
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW
+  ///  * Web: ne-resize
+  ///  * Windows: IDC_SIZENESW
+  ///  * Linux: ne-resize
+  static const SystemMouseCursor resizeUpRight = SystemMouseCursor._(kind: 'resizeUpRight');
+
+  /// A cursor indicating resizing an object from its bottom-left corner.
+  ///
+  /// Typically the shape of an arrow pointing lower left. May fallback to [resizeUpRightDownLeft].
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW
+  ///  * Web: sw-resize
+  ///  * Windows: IDC_SIZENESW
+  ///  * Linux: sw-resize
+  static const SystemMouseCursor resizeDownLeft = SystemMouseCursor._(kind: 'resizeDownLeft');
+
+  /// A cursor indicating resizing an object from its bottom-right corner.
+  ///
+  /// Typically the shape of an arrow pointing lower right. May fallback to [resizeUpLeftDownRight].
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW
+  ///  * Web: se-resize
+  ///  * Windows: IDC_SIZENWSE
+  ///  * Linux: se-resize
+  static const SystemMouseCursor resizeDownRight = SystemMouseCursor._(kind: 'resizeDownRight');
+
+  /// A cursor indicating resizing a column, or an item horizontally.
+  ///
+  /// Typically the shape of arrows pointing left and right with a vertical bar
+  /// separating them. May fallback to [resizeLeftRight].
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_HORIZONTAL_DOUBLE_ARROW
+  ///  * Web: col-resize
+  ///  * Windows: IDC_SIZEWE
+  ///  * Linux: col-resize
+  ///  * macOS: resizeLeftRightCursor
+  static const SystemMouseCursor resizeColumn = SystemMouseCursor._(kind: 'resizeColumn');
+
+  /// A cursor indicating resizing a row, or an item vertically.
+  ///
+  /// Typically the shape of arrows pointing up and down with a horizontal bar
+  /// separating them. May fallback to [resizeUpDown].
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_VERTICAL_DOUBLE_ARROW
+  ///  * Web: row-resize
+  ///  * Windows: IDC_SIZENS
+  ///  * Linux: row-resize
+  ///  * macOS: resizeUpDownCursor
+  static const SystemMouseCursor resizeRow = SystemMouseCursor._(kind: 'resizeRow');
+
+
+  // OTHER OPERATIONS
+
+  /// A cursor indicating zooming in.
+  ///
+  /// Typically a magnifying glass with a plus sign.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_ZOOM_IN
+  ///  * Web: zoom-in
+  ///  * Linux: zoom-in
+  static const SystemMouseCursor zoomIn = SystemMouseCursor._(kind: 'zoomIn');
+
+  /// A cursor indicating zooming out.
+  ///
+  /// Typically a magnifying glass with a minus sign.
+  ///
+  /// Corresponds to:
+  ///
+  ///  * Android: TYPE_ZOOM_OUT
+  ///  * Web: zoom-out
+  ///  * Linux: zoom-out
+  static const SystemMouseCursor zoomOut = SystemMouseCursor._(kind: 'zoomOut');
+}
diff --git a/lib/src/rendering/mouse_tracking.dart b/lib/src/rendering/mouse_tracking.dart
new file mode 100644
index 0000000..534b76d
--- /dev/null
+++ b/lib/src/rendering/mouse_tracking.dart
@@ -0,0 +1,546 @@
+// Copyright 2014 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.
+
+import 'dart:collection' show LinkedHashMap;
+import 'package:flute/ui.dart';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+
+import 'package:vector_math/vector_math_64.dart' show Matrix4;
+
+import 'mouse_cursor.dart';
+import 'object.dart';
+
+/// Signature for listening to [PointerEnterEvent] events.
+///
+/// Used by [MouseTrackerAnnotation], [MouseRegion] and [RenderMouseRegion].
+typedef PointerEnterEventListener = void Function(PointerEnterEvent event);
+
+/// Signature for listening to [PointerExitEvent] events.
+///
+/// Used by [MouseTrackerAnnotation], [MouseRegion] and [RenderMouseRegion].
+typedef PointerExitEventListener = void Function(PointerExitEvent event);
+
+/// Signature for listening to [PointerHoverEvent] events.
+///
+/// Used by [MouseTrackerAnnotation], [MouseRegion] and [RenderMouseRegion].
+typedef PointerHoverEventListener = void Function(PointerHoverEvent event);
+
+/// The annotation object used to annotate regions that are interested in mouse
+/// movements.
+///
+/// To use an annotation, push it with [AnnotatedRegionLayer] during painting.
+/// The annotation's callbacks or configurations will be used depending on the
+/// relationship between annotations and mouse pointers.
+///
+/// A [RenderObject] who uses this class must not dispose this class in its
+/// `detach`, even if it recreates a new one in `attach`, because the object
+/// might be detached and attached during the same frame during a reparent, and
+/// replacing the `MouseTrackerAnnotation` will cause an unnecessary `onExit` and
+/// `onEnter`.
+///
+/// This class is also the type parameter of the annotation search started by
+/// [BaseMouseTracker].
+///
+/// See also:
+///
+///  * [BaseMouseTracker], which uses [MouseTrackerAnnotation].
+class MouseTrackerAnnotation with Diagnosticable {
+  /// Creates an immutable [MouseTrackerAnnotation].
+  ///
+  /// All arguments are optional. The [cursor] must not be null.
+  const MouseTrackerAnnotation({
+    this.onEnter,
+    this.onExit,
+    this.cursor = MouseCursor.defer,
+    this.validForMouseTracker = true,
+  }) : assert(cursor != null);
+
+  /// Triggered when a mouse pointer, with or without buttons pressed, has
+  /// entered the region and [validForMouseTracker] is true.
+  ///
+  /// This callback is triggered when the pointer has started to be contained by
+  /// the region, either due to a pointer event, or due to the movement or
+  /// disappearance of the region. This method is always matched by a later
+  /// [onExit].
+  ///
+  /// See also:
+  ///
+  ///  * [onExit], which is triggered when a mouse pointer exits the region.
+  ///  * [MouseRegion.onEnter], which uses this callback.
+  final PointerEnterEventListener? onEnter;
+
+  /// Triggered when a mouse pointer, with or without buttons pressed, has
+  /// exited the region and [validForMouseTracker] is true.
+  ///
+  /// This callback is triggered when the pointer has stopped being contained
+  /// by the region, either due to a pointer event, or due to the movement or
+  /// disappearance of the region. This method always matches an earlier
+  /// [onEnter].
+  ///
+  /// See also:
+  ///
+  ///  * [onEnter], which is triggered when a mouse pointer enters the region.
+  ///  * [MouseRegion.onExit], which uses this callback, but is not triggered in
+  ///    certain cases and does not always match its earlier [MouseRegion.onEnter].
+  final PointerExitEventListener? onExit;
+
+  /// The mouse cursor for mouse pointers that are hovering over the region.
+  ///
+  /// When a mouse enters the region, its cursor will be changed to the [cursor].
+  /// When the mouse leaves the region, the cursor will be set by the region
+  /// found at the new location.
+  ///
+  /// Defaults to [MouseCursor.defer], deferring the choice of cursor to the next
+  /// region behind it in hit-test order.
+  ///
+  /// See also:
+  ///
+  ///  * [MouseRegion.cursor], which provide values to this field.
+  final MouseCursor cursor;
+
+  /// Whether this is included when [MouseTracker] collects the list of annotations.
+  ///
+  /// If [validForMouseTracker] is false, this object is excluded from the current annotation list
+  /// even if it's included in the hit test, affecting mouse-related behavior such as enter events,
+  /// exit events, and mouse cursors. The [validForMouseTracker] does not affect hit testing.
+  ///
+  /// The [validForMouseTracker] is true for [MouseTrackerAnnotation]s built by the constructor.
+  final bool validForMouseTracker;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagsSummary<Function?>(
+      'callbacks',
+      <String, Function?> {
+        'enter': onEnter,
+        'exit': onExit,
+      },
+      ifEmpty: '<none>',
+    ));
+    properties.add(DiagnosticsProperty<MouseCursor>('cursor', cursor, defaultValue: MouseCursor.defer));
+  }
+}
+
+/// Signature for searching for [MouseTrackerAnnotation]s at the given offset.
+///
+/// It is used by the [BaseMouseTracker] to fetch annotations for the mouse
+/// position.
+typedef MouseDetectorAnnotationFinder = HitTestResult Function(Offset offset);
+
+// Various states of a connected mouse device used by [BaseMouseTracker].
+class _MouseState {
+  _MouseState({
+    required PointerEvent initialEvent,
+  }) : assert(initialEvent != null),
+       _latestEvent = initialEvent;
+
+  // The list of annotations that contains this device.
+  //
+  // It uses [LinkedHashMap] to keep the insertion order.
+  LinkedHashMap<MouseTrackerAnnotation, Matrix4> get annotations => _annotations;
+  LinkedHashMap<MouseTrackerAnnotation, Matrix4> _annotations = LinkedHashMap<MouseTrackerAnnotation, Matrix4>();
+
+  LinkedHashMap<MouseTrackerAnnotation, Matrix4> replaceAnnotations(LinkedHashMap<MouseTrackerAnnotation, Matrix4> value) {
+    assert(value != null);
+    final LinkedHashMap<MouseTrackerAnnotation, Matrix4> previous = _annotations;
+    _annotations = value;
+    return previous;
+  }
+
+  // The most recently processed mouse event observed from this device.
+  PointerEvent get latestEvent => _latestEvent;
+  PointerEvent _latestEvent;
+
+  PointerEvent replaceLatestEvent(PointerEvent value) {
+    assert(value != null);
+    assert(value.device == _latestEvent.device);
+    final PointerEvent previous = _latestEvent;
+    _latestEvent = value;
+    return previous;
+  }
+
+  int get device => latestEvent.device;
+
+  @override
+  String toString() {
+    final String describeLatestEvent = 'latestEvent: ${describeIdentity(latestEvent)}';
+    final String describeAnnotations = 'annotations: [list of ${annotations.length}]';
+    return '${describeIdentity(this)}($describeLatestEvent, $describeAnnotations)';
+  }
+}
+
+/// Used by [BaseMouseTracker] to provide the details of an update of a mouse
+/// device.
+///
+/// This class contains the information needed to handle the update that might
+/// change the state of a mouse device, or the [MouseTrackerAnnotation]s that
+/// the mouse device is hovering.
+@immutable
+class MouseTrackerUpdateDetails with Diagnosticable {
+  /// When device update is triggered by a new frame.
+  ///
+  /// All parameters are required.
+  const MouseTrackerUpdateDetails.byNewFrame({
+    required this.lastAnnotations,
+    required this.nextAnnotations,
+    required PointerEvent this.previousEvent,
+  }) : assert(previousEvent != null),
+       assert(lastAnnotations != null),
+       assert(nextAnnotations != null),
+       triggeringEvent = null;
+
+  /// When device update is triggered by a pointer event.
+  ///
+  /// The [lastAnnotations], [nextAnnotations], and [triggeringEvent] are
+  /// required.
+  const MouseTrackerUpdateDetails.byPointerEvent({
+    required this.lastAnnotations,
+    required this.nextAnnotations,
+    this.previousEvent,
+    required PointerEvent this.triggeringEvent,
+  }) : assert(triggeringEvent != null),
+       assert(lastAnnotations != null),
+       assert(nextAnnotations != null);
+
+  /// The annotations that the device is hovering before the update.
+  ///
+  /// It is never null.
+  final LinkedHashMap<MouseTrackerAnnotation, Matrix4> lastAnnotations;
+
+  /// The annotations that the device is hovering after the update.
+  ///
+  /// It is never null.
+  final LinkedHashMap<MouseTrackerAnnotation, Matrix4> nextAnnotations;
+
+  /// The last event that the device observed before the update.
+  ///
+  /// If the update is triggered by a frame, the [previousEvent] is never null,
+  /// since the pointer must have been added before.
+  ///
+  /// If the update is triggered by a pointer event, the [previousEvent] is not
+  /// null except for cases where the event is the first event observed by the
+  /// pointer (which is not necessarily a [PointerAddedEvent]).
+  final PointerEvent? previousEvent;
+
+  /// The event that triggered this update.
+  ///
+  /// It is non-null if and only if the update is triggered by a pointer event.
+  final PointerEvent? triggeringEvent;
+
+  /// The pointing device of this update.
+  int get device {
+    final int result = (previousEvent ?? triggeringEvent)!.device;
+    assert(result != null);
+    return result;
+  }
+
+  /// The last event that the device observed after the update.
+  ///
+  /// The [latestEvent] is never null.
+  PointerEvent get latestEvent {
+    final PointerEvent result = triggeringEvent ?? previousEvent!;
+    assert(result != null);
+    return result;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(IntProperty('device', device));
+    properties.add(DiagnosticsProperty<PointerEvent>('previousEvent', previousEvent));
+    properties.add(DiagnosticsProperty<PointerEvent>('triggeringEvent', triggeringEvent));
+    properties.add(DiagnosticsProperty<Map<MouseTrackerAnnotation, Matrix4>>('lastAnnotations', lastAnnotations));
+    properties.add(DiagnosticsProperty<Map<MouseTrackerAnnotation, Matrix4>>('nextAnnotations', nextAnnotations));
+  }
+}
+
+/// A base class that tracks the relationship between mouse devices and
+/// [MouseTrackerAnnotation]s.
+///
+/// An event (not necessarily a pointer event) that might change the relationship
+/// between mouse devices and [MouseTrackerAnnotation]s is called a _device
+/// update_.
+///
+/// [MouseTracker] is notified of device updates by [updateWithEvent] or
+/// [updateAllDevices], and processes effects as defined in [handleDeviceUpdate]
+/// by subclasses.
+///
+/// This class is a [ChangeNotifier] that notifies its listeners if the value of
+/// [mouseIsConnected] changes.
+///
+/// See also:
+///
+///   * [MouseTracker], which is a subclass of [BaseMouseTracker] with definition
+///     of how to process mouse event callbacks and mouse cursors.
+///   * [MouseTrackerCursorMixin], which is a mixin for [BaseMouseTracker] that
+///     defines how to process mouse cursors.
+abstract class BaseMouseTracker extends ChangeNotifier {
+  /// Whether or not at least one mouse is connected and has produced events.
+  bool get mouseIsConnected => _mouseStates.isNotEmpty;
+
+  // Tracks the state of connected mouse devices.
+  //
+  // It is the source of truth for the list of connected mouse devices, and is
+  // consists of two parts:
+  //
+  //  * The mouse devices that are connected.
+  //  * In which annotations each device is contained.
+  final Map<int, _MouseState> _mouseStates = <int, _MouseState>{};
+
+  // Used to wrap any procedure that might change `mouseIsConnected`.
+  //
+  // This method records `mouseIsConnected`, runs `task`, and calls
+  // [notifyListeners] at the end if the `mouseIsConnected` has changed.
+  void _monitorMouseConnection(VoidCallback task) {
+    final bool mouseWasConnected = mouseIsConnected;
+    task();
+    if (mouseWasConnected != mouseIsConnected)
+      notifyListeners();
+  }
+
+  bool _debugDuringDeviceUpdate = false;
+  // Used to wrap any procedure that might call [handleDeviceUpdate].
+  //
+  // In debug mode, this method uses `_debugDuringDeviceUpdate` to prevent
+  // `_deviceUpdatePhase` being recursively called.
+  void _deviceUpdatePhase(VoidCallback task) {
+    assert(!_debugDuringDeviceUpdate);
+    assert(() {
+      _debugDuringDeviceUpdate = true;
+      return true;
+    }());
+    task();
+    assert(() {
+      _debugDuringDeviceUpdate = false;
+      return true;
+    }());
+  }
+
+  // Whether an observed event might update a device.
+  static bool _shouldMarkStateDirty(_MouseState? state, PointerEvent event) {
+    if (state == null)
+      return true;
+    assert(event != null);
+    final PointerEvent lastEvent = state.latestEvent;
+    assert(event.device == lastEvent.device);
+    // An Added can only follow a Removed, and a Removed can only be followed
+    // by an Added.
+    assert((event is PointerAddedEvent) == (lastEvent is PointerRemovedEvent));
+
+    // Ignore events that are unrelated to mouse tracking.
+    if (event is PointerSignalEvent)
+      return false;
+    return lastEvent is PointerAddedEvent
+      || event is PointerRemovedEvent
+      || lastEvent.position != event.position;
+  }
+
+  LinkedHashMap<MouseTrackerAnnotation, Matrix4> _hitTestResultToAnnotations(HitTestResult result) {
+    assert(result != null);
+    final LinkedHashMap<MouseTrackerAnnotation, Matrix4> annotations = <MouseTrackerAnnotation, Matrix4>{}
+        as LinkedHashMap<MouseTrackerAnnotation, Matrix4>;
+    for (final HitTestEntry entry in result.path) {
+      if (entry.target is MouseTrackerAnnotation) {
+        annotations[entry.target as MouseTrackerAnnotation] = entry.transform!;
+      }
+    }
+    return annotations;
+  }
+
+  // Find the annotations that is hovered by the device of the `state`, and
+  // their respective global transform matrices.
+  //
+  // If the device is not connected or not a mouse, an empty map is returned
+  // without calling `hitTest`.
+  LinkedHashMap<MouseTrackerAnnotation, Matrix4> _findAnnotations(_MouseState state, MouseDetectorAnnotationFinder hitTest) {
+    assert(state != null);
+    assert(hitTest != null);
+    final Offset globalPosition = state.latestEvent.position;
+    final int device = state.device;
+    if (!_mouseStates.containsKey(device))
+      return <MouseTrackerAnnotation, Matrix4>{} as LinkedHashMap<MouseTrackerAnnotation, Matrix4>;
+
+    return _hitTestResultToAnnotations(hitTest(globalPosition));
+  }
+
+  /// A callback that is called on the update of a device.
+  ///
+  /// This method should be called only by [BaseMouseTracker], each time when the
+  /// relationship between a device and annotations has changed.
+  ///
+  /// By default the [handleDeviceUpdate] does nothing effective. Subclasses
+  /// should override this method to first call to their inherited
+  /// [handleDeviceUpdate] method, and then process the update as desired.
+  ///
+  /// The update can be caused by two kinds of triggers:
+  ///
+  ///   * Triggered by the addition, movement, or removal of a pointer. Such
+  ///     calls occur during the handler of the event, indicated by
+  ///     `details.triggeringEvent` being non-null.
+  ///   * Triggered by the appearance, movement, or disappearance of an annotation.
+  ///     Such calls occur after each new frame, during the post-frame callbacks,
+  ///     indicated by `details.triggeringEvent` being null.
+  ///
+  /// Calling of this method must be wrapped in `_deviceUpdatePhase`.
+  @protected
+  @mustCallSuper
+  void handleDeviceUpdate(MouseTrackerUpdateDetails details) {
+    assert(_debugDuringDeviceUpdate);
+  }
+
+  /// Trigger a device update with a new event and its corresponding hit test
+  /// result.
+  ///
+  /// The [updateWithEvent] indicates that an event has been observed, and
+  /// is called during the handler of the event. The `getResult` should return
+  /// the hit test result at the position of the event.
+  ///
+  /// The [updateWithEvent] will generate the new state for the pointer based on
+  /// given information, and call [handleDeviceUpdate] based on the state changes.
+  void updateWithEvent(PointerEvent event, ValueGetter<HitTestResult> getResult) {
+    assert(event != null);
+    final HitTestResult result = event is PointerRemovedEvent ? HitTestResult() : getResult();
+    assert(result != null);
+    if (event.kind != PointerDeviceKind.mouse)
+      return;
+    if (event is PointerSignalEvent)
+      return;
+    final int device = event.device;
+    final _MouseState? existingState = _mouseStates[device];
+    if (!_shouldMarkStateDirty(existingState, event))
+      return;
+
+    _monitorMouseConnection(() {
+      _deviceUpdatePhase(() {
+        // Update mouseState to the latest devices that have not been removed,
+        // so that [mouseIsConnected], which is decided by `_mouseStates`, is
+        // correct during the callbacks.
+        if (existingState == null) {
+          assert(event is! PointerRemovedEvent);
+          _mouseStates[device] = _MouseState(initialEvent: event);
+        } else {
+          assert(event is! PointerAddedEvent);
+          if (event is PointerRemovedEvent)
+            _mouseStates.remove(event.device);
+        }
+        final _MouseState targetState = _mouseStates[device] ?? existingState!;
+
+        final PointerEvent lastEvent = targetState.replaceLatestEvent(event);
+        final LinkedHashMap<MouseTrackerAnnotation, Matrix4> nextAnnotations = event is PointerRemovedEvent ?
+            <MouseTrackerAnnotation, Matrix4>{} as LinkedHashMap<MouseTrackerAnnotation, Matrix4> :
+            _hitTestResultToAnnotations(result);
+        final LinkedHashMap<MouseTrackerAnnotation, Matrix4> lastAnnotations = targetState.replaceAnnotations(nextAnnotations);
+
+        handleDeviceUpdate(MouseTrackerUpdateDetails.byPointerEvent(
+          lastAnnotations: lastAnnotations,
+          nextAnnotations: nextAnnotations,
+          previousEvent: lastEvent,
+          triggeringEvent: event,
+        ));
+      });
+    });
+  }
+
+  /// Trigger a device update for all detected devices.
+  ///
+  /// The [updateAllDevices] is typically called during the post frame phase,
+  /// indicating a frame has passed and all objects have potentially moved. The
+  /// `hitTest` is a function that can acquire the hit test result at a given
+  /// position, and must not be empty.
+  ///
+  /// For each connected device, the [updateAllDevices] will make a hit test on
+  /// the device's last seen position, generate the new state for the pointer
+  /// based on given information, and call [handleDeviceUpdate] based on the
+  /// state changes.
+  void updateAllDevices(MouseDetectorAnnotationFinder hitTest) {
+    _deviceUpdatePhase(() {
+      for (final _MouseState dirtyState in _mouseStates.values) {
+        final PointerEvent lastEvent = dirtyState.latestEvent;
+        final LinkedHashMap<MouseTrackerAnnotation, Matrix4> nextAnnotations = _findAnnotations(dirtyState, hitTest);
+        final LinkedHashMap<MouseTrackerAnnotation, Matrix4> lastAnnotations = dirtyState.replaceAnnotations(nextAnnotations);
+
+        handleDeviceUpdate(MouseTrackerUpdateDetails.byNewFrame(
+          lastAnnotations: lastAnnotations,
+          nextAnnotations: nextAnnotations,
+          previousEvent: lastEvent,
+        ));
+      }
+    });
+  }
+}
+
+// A mixin for [BaseMouseTracker] that dispatches mouse events on device update.
+//
+// See also:
+//
+//  * [MouseTracker], which uses this mixin.
+mixin _MouseTrackerEventMixin on BaseMouseTracker {
+  // Handles device update and dispatches mouse event callbacks.
+  static void _handleDeviceUpdateMouseEvents(MouseTrackerUpdateDetails details) {
+    final PointerEvent latestEvent = details.latestEvent;
+
+    final LinkedHashMap<MouseTrackerAnnotation, Matrix4> lastAnnotations = details.lastAnnotations;
+    final LinkedHashMap<MouseTrackerAnnotation, Matrix4> nextAnnotations = details.nextAnnotations;
+
+    // Order is important for mouse event callbacks. The `findAnnotations`
+    // returns annotations in the visual order from front to back. We call
+    // it the "visual order", and the opposite one "reverse visual order".
+    // The algorithm here is explained in
+    // https://github.com/flutter/flutter/issues/41420
+
+    // Send exit events to annotations that are in last but not in next, in
+    // visual order.
+    final PointerExitEvent baseExitEvent = PointerExitEvent.fromMouseEvent(latestEvent);
+    lastAnnotations.forEach((MouseTrackerAnnotation annotation, Matrix4 transform) {
+      if (!nextAnnotations.containsKey(annotation))
+        if (annotation.validForMouseTracker && annotation.onExit != null)
+          annotation.onExit!(baseExitEvent.transformed(lastAnnotations[annotation]));
+    });
+
+    // Send enter events to annotations that are not in last but in next, in
+    // reverse visual order.
+    final List<MouseTrackerAnnotation> enteringAnnotations = nextAnnotations.keys.where(
+      (MouseTrackerAnnotation annotation) => !lastAnnotations.containsKey(annotation),
+    ).toList();
+    final PointerEnterEvent baseEnterEvent = PointerEnterEvent.fromMouseEvent(latestEvent);
+    for (final MouseTrackerAnnotation annotation in enteringAnnotations.reversed) {
+      if (annotation.validForMouseTracker && annotation.onEnter != null)
+        annotation.onEnter!(baseEnterEvent.transformed(nextAnnotations[annotation]));
+    }
+  }
+
+  @protected
+  @override
+  void handleDeviceUpdate(MouseTrackerUpdateDetails details) {
+    super.handleDeviceUpdate(details);
+    _handleDeviceUpdateMouseEvents(details);
+  }
+}
+
+/// Tracks the relationship between mouse devices and annotations, and
+/// triggers mouse events and cursor changes accordingly.
+///
+/// The [MouseTracker] tracks the relationship between mouse devices and
+/// [MouseTrackerAnnotation]s, and when such relationship changes, triggers
+/// the following changes if applicable:
+///
+///  * Dispatches mouse-related pointer events (pointer enter, hover, and exit).
+///  * Notifies changes of [mouseIsConnected].
+///  * Changes mouse cursors.
+///
+/// An instance of [MouseTracker] is owned by the global singleton of
+/// [RendererBinding].
+///
+/// This class is a [ChangeNotifier] that notifies its listeners if the value of
+/// [mouseIsConnected] changes.
+///
+/// See also:
+///
+///   * [BaseMouseTracker], which introduces more details about the timing of
+///     device updates.
+class MouseTracker extends BaseMouseTracker with MouseTrackerCursorMixin, _MouseTrackerEventMixin {
+}
diff --git a/lib/src/rendering/object.dart b/lib/src/rendering/object.dart
new file mode 100644
index 0000000..b0f89d6
--- /dev/null
+++ b/lib/src/rendering/object.dart
@@ -0,0 +1,3960 @@
+// Copyright 2014 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.
+
+import 'dart:developer';
+import 'package:flute/ui.dart' as ui show PictureRecorder;
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/painting.dart';
+import 'package:flute/semantics.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'binding.dart';
+import 'debug.dart';
+import 'layer.dart';
+
+export 'package:flute/foundation.dart' show FlutterError, InformationCollector, DiagnosticsNode, ErrorSummary, ErrorDescription, ErrorHint, DiagnosticsProperty, StringProperty, DoubleProperty, EnumProperty, FlagProperty, IntProperty, DiagnosticPropertiesBuilder;
+export 'package:flute/gestures.dart' show HitTestEntry, HitTestResult;
+export 'package:flute/painting.dart';
+
+/// Base class for data associated with a [RenderObject] by its parent.
+///
+/// Some render objects wish to store data on their children, such as the
+/// children's input parameters to the parent's layout algorithm or the
+/// children's position relative to other children.
+///
+/// See also:
+///
+///  * [RenderObject.setupParentData], which [RenderObject] subclasses may
+///    override to attach specific types of parent data to children.
+class ParentData {
+  /// Called when the RenderObject is removed from the tree.
+  @protected
+  @mustCallSuper
+  void detach() { }
+
+  @override
+  String toString() => '<none>';
+}
+
+/// Signature for painting into a [PaintingContext].
+///
+/// The `offset` argument is the offset from the origin of the coordinate system
+/// of the [PaintingContext.canvas] to the coordinate system of the callee.
+///
+/// Used by many of the methods of [PaintingContext].
+typedef PaintingContextCallback = void Function(PaintingContext context, Offset offset);
+
+/// A place to paint.
+///
+/// Rather than holding a canvas directly, [RenderObject]s paint using a painting
+/// context. The painting context has a [Canvas], which receives the
+/// individual draw operations, and also has functions for painting child
+/// render objects.
+///
+/// When painting a child render object, the canvas held by the painting context
+/// can change because the draw operations issued before and after painting the
+/// child might be recorded in separate compositing layers. For this reason, do
+/// not hold a reference to the canvas across operations that might paint
+/// child render objects.
+///
+/// New [PaintingContext] objects are created automatically when using
+/// [PaintingContext.repaintCompositedChild] and [pushLayer].
+class PaintingContext extends ClipContext {
+
+  /// Creates a painting context.
+  ///
+  /// Typically only called by [PaintingContext.repaintCompositedChild]
+  /// and [pushLayer].
+  @protected
+  PaintingContext(this._containerLayer, this.estimatedBounds)
+    : assert(_containerLayer != null),
+      assert(estimatedBounds != null);
+
+  final ContainerLayer _containerLayer;
+
+  /// An estimate of the bounds within which the painting context's [canvas]
+  /// will record painting commands. This can be useful for debugging.
+  ///
+  /// The canvas will allow painting outside these bounds.
+  ///
+  /// The [estimatedBounds] rectangle is in the [canvas] coordinate system.
+  final Rect estimatedBounds;
+
+  /// Repaint the given render object.
+  ///
+  /// The render object must be attached to a [PipelineOwner], must have a
+  /// composited layer, and must be in need of painting. The render object's
+  /// layer, if any, is re-used, along with any layers in the subtree that don't
+  /// need to be repainted.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderObject.isRepaintBoundary], which determines if a [RenderObject]
+  ///    has a composited layer.
+  static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent = false }) {
+    assert(child._needsPaint);
+    _repaintCompositedChild(
+      child,
+      debugAlsoPaintedParent: debugAlsoPaintedParent,
+    );
+  }
+
+  static void _repaintCompositedChild(
+    RenderObject child, {
+    bool debugAlsoPaintedParent = false,
+    PaintingContext? childContext,
+  }) {
+    assert(child.isRepaintBoundary);
+    assert(() {
+      // register the call for RepaintBoundary metrics
+      child.debugRegisterRepaintBoundaryPaint(
+        includedParent: debugAlsoPaintedParent,
+        includedChild: true,
+      );
+      return true;
+    }());
+    OffsetLayer? childLayer = child._layer as OffsetLayer?;
+    if (childLayer == null) {
+      assert(debugAlsoPaintedParent);
+      // Not using the `layer` setter because the setter asserts that we not
+      // replace the layer for repaint boundaries. That assertion does not
+      // apply here because this is exactly the place designed to create a
+      // layer for repaint boundaries.
+      child._layer = childLayer = OffsetLayer();
+    } else {
+      assert(childLayer is OffsetLayer);
+      assert(debugAlsoPaintedParent || childLayer.attached);
+      childLayer.removeAllChildren();
+    }
+    assert(identical(childLayer, child._layer));
+    assert(child._layer is OffsetLayer);
+    assert(() {
+      child._layer!.debugCreator = child.debugCreator ?? child.runtimeType;
+      return true;
+    }());
+    childContext ??= PaintingContext(child._layer!, child.paintBounds);
+    child._paintWithContext(childContext, Offset.zero);
+
+    // Double-check that the paint method did not replace the layer (the first
+    // check is done in the [layer] setter itself).
+    assert(identical(childLayer, child._layer));
+    childContext.stopRecordingIfNeeded();
+  }
+
+  /// In debug mode, repaint the given render object using a custom painting
+  /// context that can record the results of the painting operation in addition
+  /// to performing the regular paint of the child.
+  ///
+  /// See also:
+  ///
+  ///  * [repaintCompositedChild], for repainting a composited child without
+  ///    instrumentation.
+  static void debugInstrumentRepaintCompositedChild(
+    RenderObject child, {
+    bool debugAlsoPaintedParent = false,
+    required PaintingContext customContext,
+  }) {
+    assert(() {
+      _repaintCompositedChild(
+        child,
+        debugAlsoPaintedParent: debugAlsoPaintedParent,
+        childContext: customContext,
+      );
+      return true;
+    }());
+  }
+
+  /// Paint a child [RenderObject].
+  ///
+  /// If the child has its own composited layer, the child will be composited
+  /// into the layer subtree associated with this painting context. Otherwise,
+  /// the child will be painted into the current PictureLayer for this context.
+  void paintChild(RenderObject child, Offset offset) {
+    assert(() {
+      if (debugProfilePaintsEnabled)
+        Timeline.startSync('${child.runtimeType}', arguments: timelineArgumentsIndicatingLandmarkEvent);
+      if (debugOnProfilePaint != null)
+        debugOnProfilePaint!(child);
+      return true;
+    }());
+
+    if (child.isRepaintBoundary) {
+      stopRecordingIfNeeded();
+      _compositeChild(child, offset);
+    } else {
+      child._paintWithContext(this, offset);
+    }
+
+    assert(() {
+      if (debugProfilePaintsEnabled)
+        Timeline.finishSync();
+      return true;
+    }());
+  }
+
+  void _compositeChild(RenderObject child, Offset offset) {
+    assert(!_isRecording);
+    assert(child.isRepaintBoundary);
+    assert(_canvas == null || _canvas!.getSaveCount() == 1);
+
+    // Create a layer for our child, and paint the child into it.
+    if (child._needsPaint) {
+      repaintCompositedChild(child, debugAlsoPaintedParent: true);
+    } else {
+      assert(() {
+        // register the call for RepaintBoundary metrics
+        child.debugRegisterRepaintBoundaryPaint(
+          includedParent: true,
+          includedChild: false,
+        );
+        child._layer!.debugCreator = child.debugCreator ?? child;
+        return true;
+      }());
+    }
+    assert(child._layer is OffsetLayer);
+    final OffsetLayer childOffsetLayer = child._layer! as OffsetLayer;
+    childOffsetLayer.offset = offset;
+    appendLayer(child._layer!);
+  }
+
+  /// Adds a layer to the recording requiring that the recording is already
+  /// stopped.
+  ///
+  /// Do not call this function directly: call [addLayer] or [pushLayer]
+  /// instead. This function is called internally when all layers not
+  /// generated from the [canvas] are added.
+  ///
+  /// Subclasses that need to customize how layers are added should override
+  /// this method.
+  @protected
+  void appendLayer(Layer layer) {
+    assert(!_isRecording);
+    layer.remove();
+    _containerLayer.append(layer);
+  }
+
+  bool get _isRecording {
+    final bool hasCanvas = _canvas != null;
+    assert(() {
+      if (hasCanvas) {
+        assert(_currentLayer != null);
+        assert(_recorder != null);
+        assert(_canvas != null);
+      } else {
+        assert(_currentLayer == null);
+        assert(_recorder == null);
+        assert(_canvas == null);
+      }
+      return true;
+    }());
+    return hasCanvas;
+  }
+
+  // Recording state
+  PictureLayer? _currentLayer;
+  ui.PictureRecorder? _recorder;
+  Canvas? _canvas;
+
+  /// The canvas on which to paint.
+  ///
+  /// The current canvas can change whenever you paint a child using this
+  /// context, which means it's fragile to hold a reference to the canvas
+  /// returned by this getter.
+  @override
+  Canvas get canvas {
+    if (_canvas == null)
+      _startRecording();
+    return _canvas!;
+  }
+
+  void _startRecording() {
+    assert(!_isRecording);
+    _currentLayer = PictureLayer(estimatedBounds);
+    _recorder = ui.PictureRecorder();
+    _canvas = Canvas(_recorder!);
+    _containerLayer.append(_currentLayer!);
+  }
+
+  /// Stop recording to a canvas if recording has started.
+  ///
+  /// Do not call this function directly: functions in this class will call
+  /// this method as needed. This function is called internally to ensure that
+  /// recording is stopped before adding layers or finalizing the results of a
+  /// paint.
+  ///
+  /// Subclasses that need to customize how recording to a canvas is performed
+  /// should override this method to save the results of the custom canvas
+  /// recordings.
+  @protected
+  @mustCallSuper
+  void stopRecordingIfNeeded() {
+    if (!_isRecording)
+      return;
+    assert(() {
+      if (debugRepaintRainbowEnabled) {
+        final Paint paint = Paint()
+          ..style = PaintingStyle.stroke
+          ..strokeWidth = 6.0
+          ..color = debugCurrentRepaintColor.toColor();
+        canvas.drawRect(estimatedBounds.deflate(3.0), paint);
+      }
+      if (debugPaintLayerBordersEnabled) {
+        final Paint paint = Paint()
+          ..style = PaintingStyle.stroke
+          ..strokeWidth = 1.0
+          ..color = const Color(0xFFFF9800);
+        canvas.drawRect(estimatedBounds, paint);
+      }
+      return true;
+    }());
+    _currentLayer!.picture = _recorder!.endRecording();
+    _currentLayer = null;
+    _recorder = null;
+    _canvas = null;
+  }
+
+  /// Hints that the painting in the current layer is complex and would benefit
+  /// from caching.
+  ///
+  /// If this hint is not set, the compositor will apply its own heuristics to
+  /// decide whether the current layer is complex enough to benefit from
+  /// caching.
+  void setIsComplexHint() {
+    _currentLayer?.isComplexHint = true;
+  }
+
+  /// Hints that the painting in the current layer is likely to change next frame.
+  ///
+  /// This hint tells the compositor not to cache the current layer because the
+  /// cache will not be used in the future. If this hint is not set, the
+  /// compositor will apply its own heuristics to decide whether the current
+  /// layer is likely to be reused in the future.
+  void setWillChangeHint() {
+    _currentLayer?.willChangeHint = true;
+  }
+
+  /// Adds a composited leaf layer to the recording.
+  ///
+  /// After calling this function, the [canvas] property will change to refer to
+  /// a new [Canvas] that draws on top of the given layer.
+  ///
+  /// A [RenderObject] that uses this function is very likely to require its
+  /// [RenderObject.alwaysNeedsCompositing] property to return true. That informs
+  /// ancestor render objects that this render object will include a composited
+  /// layer, which, for example, causes them to use composited clips.
+  ///
+  /// See also:
+  ///
+  ///  * [pushLayer], for adding a layer and painting further contents within
+  ///    it.
+  void addLayer(Layer layer) {
+    stopRecordingIfNeeded();
+    appendLayer(layer);
+  }
+
+  /// Appends the given layer to the recording, and calls the `painter` callback
+  /// with that layer, providing the `childPaintBounds` as the estimated paint
+  /// bounds of the child. The `childPaintBounds` can be used for debugging but
+  /// have no effect on painting.
+  ///
+  /// The given layer must be an unattached orphan. (Providing a newly created
+  /// object, rather than reusing an existing layer, satisfies that
+  /// requirement.)
+  ///
+  /// {@template flutter.rendering.PaintingContext.pushLayer.offset}
+  /// The `offset` is the offset to pass to the `painter`. In particular, it is
+  /// not an offset applied to the layer itself. Layers conceptually by default
+  /// have no position or size, though they can transform their contents. For
+  /// example, an [OffsetLayer] applies an offset to its children.
+  /// {@endtemplate}
+  ///
+  /// If the `childPaintBounds` are not specified then the current layer's paint
+  /// bounds are used. This is appropriate if the child layer does not apply any
+  /// transformation or clipping to its contents. The `childPaintBounds`, if
+  /// specified, must be in the coordinate system of the new layer (i.e. as seen
+  /// by its children after it applies whatever transform to its contents), and
+  /// should not go outside the current layer's paint bounds.
+  ///
+  /// See also:
+  ///
+  ///  * [addLayer], for pushing a layer without painting further contents
+  ///    within it.
+  void pushLayer(ContainerLayer childLayer, PaintingContextCallback painter, Offset offset, { Rect? childPaintBounds }) {
+    assert(painter != null);
+    // If a layer is being reused, it may already contain children. We remove
+    // them so that `painter` can add children that are relevant for this frame.
+    if (childLayer.hasChildren) {
+      childLayer.removeAllChildren();
+    }
+    stopRecordingIfNeeded();
+    appendLayer(childLayer);
+    final PaintingContext childContext = createChildContext(childLayer, childPaintBounds ?? estimatedBounds);
+    painter(childContext, offset);
+    childContext.stopRecordingIfNeeded();
+  }
+
+  /// Creates a painting context configured to paint into [childLayer].
+  ///
+  /// The `bounds` are estimated paint bounds for debugging purposes.
+  @protected
+  PaintingContext createChildContext(ContainerLayer childLayer, Rect bounds) {
+    return PaintingContext(childLayer, bounds);
+  }
+
+  /// Clip further painting using a rectangle.
+  ///
+  /// {@template flutter.rendering.PaintingContext.pushClipRect.needsCompositing}
+  /// The `needsCompositing` argument specifies whether the child needs
+  /// compositing. Typically this matches the value of
+  /// [RenderObject.needsCompositing] for the caller. If false, this method
+  /// returns null, indicating that a layer is no longer necessary. If a render
+  /// object calling this method stores the `oldLayer` in its
+  /// [RenderObject.layer] field, it should set that field to null.
+  ///
+  /// When `needsCompositing` is false, this method will use a more efficient
+  /// way to apply the layer effect than actually creating a layer.
+  /// {@endtemplate}
+  ///
+  /// {@template flutter.rendering.PaintingContext.pushClipRect.offset}
+  /// The `offset` argument is the offset from the origin of the canvas'
+  /// coordinate system to the origin of the caller's coordinate system.
+  /// {@endtemplate}
+  ///
+  /// The `clipRect` is the rectangle (in the caller's coordinate system) to use
+  /// to clip the painting done by [painter]. It should not include the
+  /// `offset`.
+  ///
+  /// The `painter` callback will be called while the `clipRect` is applied. It
+  /// is called synchronously during the call to [pushClipRect].
+  ///
+  /// The `clipBehavior` argument controls how the rectangle is clipped.
+  ///
+  /// {@template flutter.rendering.PaintingContext.pushClipRect.oldLayer}
+  /// For the `oldLayer` argument, specify the layer created in the previous
+  /// frame. This gives the engine more information for performance
+  /// optimizations. Typically this is the value of [RenderObject.layer] that a
+  /// render object creates once, then reuses for all subsequent frames until a
+  /// layer is no longer needed (e.g. the render object no longer needs
+  /// compositing) or until the render object changes the type of the layer
+  /// (e.g. from opacity layer to a clip rect layer).
+  /// {@endtemplate}
+  ClipRectLayer? pushClipRect(bool needsCompositing, Offset offset, Rect clipRect, PaintingContextCallback painter, { Clip clipBehavior = Clip.hardEdge, ClipRectLayer? oldLayer }) {
+    final Rect offsetClipRect = clipRect.shift(offset);
+    if (needsCompositing) {
+      final ClipRectLayer layer = oldLayer ?? ClipRectLayer();
+      layer
+        ..clipRect = offsetClipRect
+        ..clipBehavior = clipBehavior;
+      pushLayer(layer, painter, offset, childPaintBounds: offsetClipRect);
+      return layer;
+    } else {
+      clipRectAndPaint(offsetClipRect, clipBehavior, offsetClipRect, () => painter(this, offset));
+      return null;
+    }
+  }
+
+  /// Clip further painting using a rounded rectangle.
+  ///
+  /// {@macro flutter.rendering.PaintingContext.pushClipRect.needsCompositing}
+  ///
+  /// {@macro flutter.rendering.PaintingContext.pushClipRect.offset}
+  ///
+  /// The `bounds` argument is used to specify the region of the canvas (in the
+  /// caller's coordinate system) into which `painter` will paint.
+  ///
+  /// The `clipRRect` argument specifies the rounded-rectangle (in the caller's
+  /// coordinate system) to use to clip the painting done by `painter`. It
+  /// should not include the `offset`.
+  ///
+  /// The `painter` callback will be called while the `clipRRect` is applied. It
+  /// is called synchronously during the call to [pushClipRRect].
+  ///
+  /// The `clipBehavior` argument controls how the rounded rectangle is clipped.
+  ///
+  /// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
+  ClipRRectLayer? pushClipRRect(bool needsCompositing, Offset offset, Rect bounds, RRect clipRRect, PaintingContextCallback painter, { Clip clipBehavior = Clip.antiAlias, ClipRRectLayer? oldLayer }) {
+    assert(clipBehavior != null);
+    final Rect offsetBounds = bounds.shift(offset);
+    final RRect offsetClipRRect = clipRRect.shift(offset);
+    if (needsCompositing) {
+      final ClipRRectLayer layer = oldLayer ?? ClipRRectLayer();
+      layer
+        ..clipRRect = offsetClipRRect
+        ..clipBehavior = clipBehavior;
+      pushLayer(layer, painter, offset, childPaintBounds: offsetBounds);
+      return layer;
+    } else {
+      clipRRectAndPaint(offsetClipRRect, clipBehavior, offsetBounds, () => painter(this, offset));
+      return null;
+    }
+  }
+
+  /// Clip further painting using a path.
+  ///
+  /// {@macro flutter.rendering.PaintingContext.pushClipRect.needsCompositing}
+  ///
+  /// {@macro flutter.rendering.PaintingContext.pushClipRect.offset}
+  ///
+  /// The `bounds` argument is used to specify the region of the canvas (in the
+  /// caller's coordinate system) into which `painter` will paint.
+  ///
+  /// The `clipPath` argument specifies the [Path] (in the caller's coordinate
+  /// system) to use to clip the painting done by `painter`. It should not
+  /// include the `offset`.
+  ///
+  /// The `painter` callback will be called while the `clipPath` is applied. It
+  /// is called synchronously during the call to [pushClipPath].
+  ///
+  /// The `clipBehavior` argument controls how the path is clipped.
+  ///
+  /// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
+  ClipPathLayer? pushClipPath(bool needsCompositing, Offset offset, Rect bounds, Path clipPath, PaintingContextCallback painter, { Clip clipBehavior = Clip.antiAlias, ClipPathLayer? oldLayer }) {
+    assert(clipBehavior != null);
+    final Rect offsetBounds = bounds.shift(offset);
+    final Path offsetClipPath = clipPath.shift(offset);
+    if (needsCompositing) {
+      final ClipPathLayer layer = oldLayer ?? ClipPathLayer();
+      layer
+        ..clipPath = offsetClipPath
+        ..clipBehavior = clipBehavior;
+      pushLayer(layer, painter, offset, childPaintBounds: offsetBounds);
+      return layer;
+    } else {
+      clipPathAndPaint(offsetClipPath, clipBehavior, offsetBounds, () => painter(this, offset));
+      return null;
+    }
+  }
+
+  /// Blend further painting with a color filter.
+  ///
+  /// {@macro flutter.rendering.PaintingContext.pushLayer.offset}
+  ///
+  /// The `colorFilter` argument is the [ColorFilter] value to use when blending
+  /// the painting done by `painter`.
+  ///
+  /// The `painter` callback will be called while the `colorFilter` is applied.
+  /// It is called synchronously during the call to [pushColorFilter].
+  ///
+  /// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
+  ///
+  /// A [RenderObject] that uses this function is very likely to require its
+  /// [RenderObject.alwaysNeedsCompositing] property to return true. That informs
+  /// ancestor render objects that this render object will include a composited
+  /// layer, which, for example, causes them to use composited clips.
+  ColorFilterLayer pushColorFilter(Offset offset, ColorFilter colorFilter, PaintingContextCallback painter, { ColorFilterLayer? oldLayer }) {
+    assert(colorFilter != null);
+    final ColorFilterLayer layer = oldLayer ?? ColorFilterLayer();
+    layer.colorFilter = colorFilter;
+    pushLayer(layer, painter, offset);
+    return layer;
+  }
+
+  /// Transform further painting using a matrix.
+  ///
+  /// {@macro flutter.rendering.PaintingContext.pushClipRect.needsCompositing}
+  ///
+  /// The `offset` argument is the offset to pass to `painter` and the offset to
+  /// the origin used by `transform`.
+  ///
+  /// The `transform` argument is the [Matrix4] with which to transform the
+  /// coordinate system while calling `painter`. It should not include `offset`.
+  /// It is applied effectively after applying `offset`.
+  ///
+  /// The `painter` callback will be called while the `transform` is applied. It
+  /// is called synchronously during the call to [pushTransform].
+  ///
+  /// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
+  TransformLayer? pushTransform(bool needsCompositing, Offset offset, Matrix4 transform, PaintingContextCallback painter, { TransformLayer? oldLayer }) {
+    final Matrix4 effectiveTransform = Matrix4.translationValues(offset.dx, offset.dy, 0.0)
+      ..multiply(transform)..translate(-offset.dx, -offset.dy);
+    if (needsCompositing) {
+      final TransformLayer layer = oldLayer ?? TransformLayer();
+      layer.transform = effectiveTransform;
+      pushLayer(
+        layer,
+        painter,
+        offset,
+        childPaintBounds: MatrixUtils.inverseTransformRect(effectiveTransform, estimatedBounds),
+      );
+      return layer;
+    } else {
+      canvas
+        ..save()
+        ..transform(effectiveTransform.storage);
+      painter(this, offset);
+      canvas.restore();
+      return null;
+    }
+  }
+
+  /// Blend further painting with an alpha value.
+  ///
+  /// The `offset` argument indicates an offset to apply to all the children
+  /// (the rendering created by `painter`).
+  ///
+  /// The `alpha` argument is the alpha value to use when blending the painting
+  /// done by `painter`. An alpha value of 0 means the painting is fully
+  /// transparent and an alpha value of 255 means the painting is fully opaque.
+  ///
+  /// The `painter` callback will be called while the `alpha` is applied. It
+  /// is called synchronously during the call to [pushOpacity].
+  ///
+  /// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
+  ///
+  /// A [RenderObject] that uses this function is very likely to require its
+  /// [RenderObject.alwaysNeedsCompositing] property to return true. That informs
+  /// ancestor render objects that this render object will include a composited
+  /// layer, which, for example, causes them to use composited clips.
+  OpacityLayer pushOpacity(Offset offset, int alpha, PaintingContextCallback painter, { OpacityLayer? oldLayer }) {
+    final OpacityLayer layer = oldLayer ?? OpacityLayer();
+    layer
+      ..alpha = alpha
+      ..offset = offset;
+    pushLayer(layer, painter, Offset.zero);
+    return layer;
+  }
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'PaintingContext')}#$hashCode(layer: $_containerLayer, canvas bounds: $estimatedBounds)';
+}
+
+/// An abstract set of layout constraints.
+///
+/// Concrete layout models (such as box) will create concrete subclasses to
+/// communicate layout constraints between parents and children.
+///
+/// ## Writing a Constraints subclass
+///
+/// When creating a new [RenderObject] subclass with a new layout protocol, one
+/// will usually need to create a new [Constraints] subclass to express the
+/// input to the layout algorithms.
+///
+/// A [Constraints] subclass should be immutable (all fields final). There are
+/// several members to implement, in addition to whatever fields, constructors,
+/// and helper methods one may find useful for a particular layout protocol:
+///
+/// * The [isTight] getter, which should return true if the object represents a
+///   case where the [RenderObject] class has no choice for how to lay itself
+///   out. For example, [BoxConstraints] returns true for [isTight] when both
+///   the minimum and maximum widths and the minimum and maximum heights are
+///   equal.
+///
+/// * The [isNormalized] getter, which should return true if the object
+///   represents its data in its canonical form. Sometimes, it is possible for
+///   fields to be redundant with each other, such that several different
+///   representations have the same implications. For example, a
+///   [BoxConstraints] instance with its minimum width greater than its maximum
+///   width is equivalent to one where the maximum width is set to that minimum
+///   width (`2<w<1` is equivalent to `2<w<2`, since minimum constraints have
+///   priority). This getter is used by the default implementation of
+///   [debugAssertIsValid].
+///
+/// * The [debugAssertIsValid] method, which should assert if there's anything
+///   wrong with the constraints object. (We use this approach rather than
+///   asserting in constructors so that our constructors can be `const` and so
+///   that it is possible to create invalid constraints temporarily while
+///   building valid ones.) See the implementation of
+///   [BoxConstraints.debugAssertIsValid] for an example of the detailed checks
+///   that can be made.
+///
+/// * The [==] operator and the [hashCode] getter, so that constraints can be
+///   compared for equality. If a render object is given constraints that are
+///   equal, then the rendering library will avoid laying the object out again
+///   if it is not dirty.
+///
+/// * The [toString] method, which should describe the constraints so that they
+///   appear in a usefully readable form in the output of [debugDumpRenderTree].
+@immutable
+abstract class Constraints {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const Constraints();
+
+  /// Whether there is exactly one size possible given these constraints.
+  bool get isTight;
+
+  /// Whether the constraint is expressed in a consistent manner.
+  bool get isNormalized;
+
+  /// Asserts that the constraints are valid.
+  ///
+  /// This might involve checks more detailed than [isNormalized].
+  ///
+  /// For example, the [BoxConstraints] subclass verifies that the constraints
+  /// are not [double.nan].
+  ///
+  /// If the `isAppliedConstraint` argument is true, then even stricter rules
+  /// are enforced. This argument is set to true when checking constraints that
+  /// are about to be applied to a [RenderObject] during layout, as opposed to
+  /// constraints that may be further affected by other constraints. For
+  /// example, the asserts for verifying the validity of
+  /// [RenderConstrainedBox.additionalConstraints] do not set this argument, but
+  /// the asserts for verifying the argument passed to the [RenderObject.layout]
+  /// method do.
+  ///
+  /// The `informationCollector` argument takes an optional callback which is
+  /// called when an exception is to be thrown. The collected information is
+  /// then included in the message after the error line.
+  ///
+  /// Returns the same as [isNormalized] if asserts are disabled.
+  bool debugAssertIsValid({
+    bool isAppliedConstraint = false,
+    InformationCollector? informationCollector,
+  }) {
+    assert(isNormalized);
+    return isNormalized;
+  }
+}
+
+/// Signature for a function that is called for each [RenderObject].
+///
+/// Used by [RenderObject.visitChildren] and [RenderObject.visitChildrenForSemantics].
+///
+/// The `child` argument must not be null.
+typedef RenderObjectVisitor = void Function(RenderObject child);
+
+/// Signature for a function that is called during layout.
+///
+/// Used by [RenderObject.invokeLayoutCallback].
+typedef LayoutCallback<T extends Constraints> = void Function(T constraints);
+
+/// A reference to the semantics tree.
+///
+/// The framework maintains the semantics tree (used for accessibility and
+/// indexing) only when there is at least one client holding an open
+/// [SemanticsHandle].
+///
+/// The framework notifies the client that it has updated the semantics tree by
+/// calling the [listener] callback. When the client no longer needs the
+/// semantics tree, the client can call [dispose] on the [SemanticsHandle],
+/// which stops these callbacks and closes the [SemanticsHandle]. When all the
+/// outstanding [SemanticsHandle] objects are closed, the framework stops
+/// updating the semantics tree.
+///
+/// To obtain a [SemanticsHandle], call [PipelineOwner.ensureSemantics] on the
+/// [PipelineOwner] for the render tree from which you wish to read semantics.
+/// You can obtain the [PipelineOwner] using the [RenderObject.owner] property.
+class SemanticsHandle {
+  SemanticsHandle._(PipelineOwner owner, this.listener)
+      : assert(owner != null),
+        _owner = owner {
+    if (listener != null)
+      _owner.semanticsOwner!.addListener(listener!);
+  }
+
+  final PipelineOwner _owner;
+
+  /// The callback that will be notified when the semantics tree updates.
+  final VoidCallback? listener;
+
+  /// Closes the semantics handle and stops calling [listener] when the
+  /// semantics updates.
+  ///
+  /// When all the outstanding [SemanticsHandle] objects for a given
+  /// [PipelineOwner] are closed, the [PipelineOwner] will stop updating the
+  /// semantics tree.
+  @mustCallSuper
+  void dispose() {
+    if (listener != null)
+      _owner.semanticsOwner!.removeListener(listener!);
+    _owner._didDisposeSemanticsHandle();
+  }
+}
+
+/// The pipeline owner manages the rendering pipeline.
+///
+/// The pipeline owner provides an interface for driving the rendering pipeline
+/// and stores the state about which render objects have requested to be visited
+/// in each stage of the pipeline. To flush the pipeline, call the following
+/// functions in order:
+///
+/// 1. [flushLayout] updates any render objects that need to compute their
+///    layout. During this phase, the size and position of each render
+///    object is calculated. Render objects might dirty their painting or
+///    compositing state during this phase.
+/// 2. [flushCompositingBits] updates any render objects that have dirty
+///    compositing bits. During this phase, each render object learns whether
+///    any of its children require compositing. This information is used during
+///    the painting phase when selecting how to implement visual effects such as
+///    clipping. If a render object has a composited child, its needs to use a
+///    [Layer] to create the clip in order for the clip to apply to the
+///    composited child (which will be painted into its own [Layer]).
+/// 3. [flushPaint] visits any render objects that need to paint. During this
+///    phase, render objects get a chance to record painting commands into
+///    [PictureLayer]s and construct other composited [Layer]s.
+/// 4. Finally, if semantics are enabled, [flushSemantics] will compile the
+///    semantics for the render objects. This semantic information is used by
+///    assistive technology to improve the accessibility of the render tree.
+///
+/// The [RendererBinding] holds the pipeline owner for the render objects that
+/// are visible on screen. You can create other pipeline owners to manage
+/// off-screen objects, which can flush their pipelines independently of the
+/// on-screen render objects.
+class PipelineOwner {
+  /// Creates a pipeline owner.
+  ///
+  /// Typically created by the binding (e.g., [RendererBinding]), but can be
+  /// created separately from the binding to drive off-screen render objects
+  /// through the rendering pipeline.
+  PipelineOwner({
+    this.onNeedVisualUpdate,
+    this.onSemanticsOwnerCreated,
+    this.onSemanticsOwnerDisposed,
+  });
+
+  /// Called when a render object associated with this pipeline owner wishes to
+  /// update its visual appearance.
+  ///
+  /// Typical implementations of this function will schedule a task to flush the
+  /// various stages of the pipeline. This function might be called multiple
+  /// times in quick succession. Implementations should take care to discard
+  /// duplicate calls quickly.
+  final VoidCallback? onNeedVisualUpdate;
+
+  /// Called whenever this pipeline owner creates a semantics object.
+  ///
+  /// Typical implementations will schedule the creation of the initial
+  /// semantics tree.
+  final VoidCallback? onSemanticsOwnerCreated;
+
+  /// Called whenever this pipeline owner disposes its semantics owner.
+  ///
+  /// Typical implementations will tear down the semantics tree.
+  final VoidCallback? onSemanticsOwnerDisposed;
+
+  /// Calls [onNeedVisualUpdate] if [onNeedVisualUpdate] is not null.
+  ///
+  /// Used to notify the pipeline owner that an associated render object wishes
+  /// to update its visual appearance.
+  void requestVisualUpdate() {
+    if (onNeedVisualUpdate != null)
+      onNeedVisualUpdate!();
+  }
+
+  /// The unique object managed by this pipeline that has no parent.
+  ///
+  /// This object does not have to be a [RenderObject].
+  AbstractNode? get rootNode => _rootNode;
+  AbstractNode? _rootNode;
+  set rootNode(AbstractNode? value) {
+    if (_rootNode == value)
+      return;
+    _rootNode?.detach();
+    _rootNode = value;
+    _rootNode?.attach(this);
+  }
+
+  List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
+
+  /// Whether this pipeline is currently in the layout phase.
+  ///
+  /// Specifically, whether [flushLayout] is currently running.
+  ///
+  /// Only valid when asserts are enabled; in release builds, this
+  /// always returns false.
+  bool get debugDoingLayout => _debugDoingLayout;
+  bool _debugDoingLayout = false;
+
+  /// Update the layout information for all dirty render objects.
+  ///
+  /// This function is one of the core stages of the rendering pipeline. Layout
+  /// information is cleaned prior to painting so that render objects will
+  /// appear on screen in their up-to-date locations.
+  ///
+  /// See [RendererBinding] for an example of how this function is used.
+  void flushLayout() {
+    if (!kReleaseMode) {
+      Timeline.startSync('Layout', arguments: timelineArgumentsIndicatingLandmarkEvent);
+    }
+    assert(() {
+      _debugDoingLayout = true;
+      return true;
+    }());
+    try {
+      // TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themselves
+      while (_nodesNeedingLayout.isNotEmpty) {
+        final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
+        _nodesNeedingLayout = <RenderObject>[];
+        for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
+          if (node._needsLayout && node.owner == this)
+            node._layoutWithoutResize();
+        }
+      }
+    } finally {
+      assert(() {
+        _debugDoingLayout = false;
+        return true;
+      }());
+      if (!kReleaseMode) {
+        Timeline.finishSync();
+      }
+    }
+  }
+
+  // This flag is used to allow the kinds of mutations performed by GlobalKey
+  // reparenting while a LayoutBuilder is being rebuilt and in so doing tries to
+  // move a node from another LayoutBuilder subtree that hasn't been updated
+  // yet. To set this, call [_enableMutationsToDirtySubtrees], which is called
+  // by [RenderObject.invokeLayoutCallback].
+  bool _debugAllowMutationsToDirtySubtrees = false;
+
+  // See [RenderObject.invokeLayoutCallback].
+  void _enableMutationsToDirtySubtrees(VoidCallback callback) {
+    assert(_debugDoingLayout);
+    bool? oldState;
+    assert(() {
+      oldState = _debugAllowMutationsToDirtySubtrees;
+      _debugAllowMutationsToDirtySubtrees = true;
+      return true;
+    }());
+    try {
+      callback();
+    } finally {
+      assert(() {
+        _debugAllowMutationsToDirtySubtrees = oldState!;
+        return true;
+      }());
+    }
+  }
+
+  final List<RenderObject> _nodesNeedingCompositingBitsUpdate = <RenderObject>[];
+  /// Updates the [RenderObject.needsCompositing] bits.
+  ///
+  /// Called as part of the rendering pipeline after [flushLayout] and before
+  /// [flushPaint].
+  void flushCompositingBits() {
+    if (!kReleaseMode) {
+      Timeline.startSync('Compositing bits');
+    }
+    _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
+    for (final RenderObject node in _nodesNeedingCompositingBitsUpdate) {
+      if (node._needsCompositingBitsUpdate && node.owner == this)
+        node._updateCompositingBits();
+    }
+    _nodesNeedingCompositingBitsUpdate.clear();
+    if (!kReleaseMode) {
+      Timeline.finishSync();
+    }
+  }
+
+  List<RenderObject> _nodesNeedingPaint = <RenderObject>[];
+
+  /// Whether this pipeline is currently in the paint phase.
+  ///
+  /// Specifically, whether [flushPaint] is currently running.
+  ///
+  /// Only valid when asserts are enabled. In release builds,
+  /// this always returns false.
+  bool get debugDoingPaint => _debugDoingPaint;
+  bool _debugDoingPaint = false;
+
+  /// Update the display lists for all render objects.
+  ///
+  /// This function is one of the core stages of the rendering pipeline.
+  /// Painting occurs after layout and before the scene is recomposited so that
+  /// scene is composited with up-to-date display lists for every render object.
+  ///
+  /// See [RendererBinding] for an example of how this function is used.
+  void flushPaint() {
+    if (!kReleaseMode) {
+      Timeline.startSync('Paint', arguments: timelineArgumentsIndicatingLandmarkEvent);
+    }
+    assert(() {
+      _debugDoingPaint = true;
+      return true;
+    }());
+    try {
+      final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
+      _nodesNeedingPaint = <RenderObject>[];
+      // Sort the dirty nodes in reverse order (deepest first).
+      for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
+        assert(node._layer != null);
+        if (node._needsPaint && node.owner == this) {
+          if (node._layer!.attached) {
+            PaintingContext.repaintCompositedChild(node);
+          } else {
+            node._skippedPaintingOnLayer();
+          }
+        }
+      }
+      assert(_nodesNeedingPaint.isEmpty);
+    } finally {
+      assert(() {
+        _debugDoingPaint = false;
+        return true;
+      }());
+      if (!kReleaseMode) {
+        Timeline.finishSync();
+      }
+    }
+  }
+
+  /// The object that is managing semantics for this pipeline owner, if any.
+  ///
+  /// An owner is created by [ensureSemantics]. The owner is valid for as long
+  /// there are [SemanticsHandle]s returned by [ensureSemantics] that have not
+  /// yet been disposed. Once the last handle has been disposed, the
+  /// [semanticsOwner] field will revert to null, and the previous owner will be
+  /// disposed.
+  ///
+  /// When [semanticsOwner] is null, the [PipelineOwner] skips all steps
+  /// relating to semantics.
+  SemanticsOwner? get semanticsOwner => _semanticsOwner;
+  SemanticsOwner? _semanticsOwner;
+
+  /// The number of clients registered to listen for semantics.
+  ///
+  /// The number is increased whenever [ensureSemantics] is called and decreased
+  /// when [SemanticsHandle.dispose] is called.
+  int get debugOutstandingSemanticsHandles => _outstandingSemanticsHandles;
+  int _outstandingSemanticsHandles = 0;
+
+  /// Opens a [SemanticsHandle] and calls [listener] whenever the semantics tree
+  /// updates.
+  ///
+  /// The [PipelineOwner] updates the semantics tree only when there are clients
+  /// that wish to use the semantics tree. These clients express their interest
+  /// by holding [SemanticsHandle] objects that notify them whenever the
+  /// semantics tree updates.
+  ///
+  /// Clients can close their [SemanticsHandle] by calling
+  /// [SemanticsHandle.dispose]. Once all the outstanding [SemanticsHandle]
+  /// objects for a given [PipelineOwner] are closed, the [PipelineOwner] stops
+  /// maintaining the semantics tree.
+  SemanticsHandle ensureSemantics({ VoidCallback? listener }) {
+    _outstandingSemanticsHandles += 1;
+    if (_outstandingSemanticsHandles == 1) {
+      assert(_semanticsOwner == null);
+      _semanticsOwner = SemanticsOwner();
+      if (onSemanticsOwnerCreated != null)
+        onSemanticsOwnerCreated!();
+    }
+    return SemanticsHandle._(this, listener);
+  }
+
+  void _didDisposeSemanticsHandle() {
+    assert(_semanticsOwner != null);
+    _outstandingSemanticsHandles -= 1;
+    if (_outstandingSemanticsHandles == 0) {
+      _semanticsOwner!.dispose();
+      _semanticsOwner = null;
+      if (onSemanticsOwnerDisposed != null)
+        onSemanticsOwnerDisposed!();
+    }
+  }
+
+  bool _debugDoingSemantics = false;
+  final Set<RenderObject> _nodesNeedingSemantics = <RenderObject>{};
+
+  /// Update the semantics for render objects marked as needing a semantics
+  /// update.
+  ///
+  /// Initially, only the root node, as scheduled by
+  /// [RenderObject.scheduleInitialSemantics], needs a semantics update.
+  ///
+  /// This function is one of the core stages of the rendering pipeline. The
+  /// semantics are compiled after painting and only after
+  /// [RenderObject.scheduleInitialSemantics] has been called.
+  ///
+  /// See [RendererBinding] for an example of how this function is used.
+  void flushSemantics() {
+    if (_semanticsOwner == null)
+      return;
+    if (!kReleaseMode) {
+      Timeline.startSync('Semantics');
+    }
+    assert(_semanticsOwner != null);
+    assert(() {
+      _debugDoingSemantics = true;
+      return true;
+    }());
+    try {
+      final List<RenderObject> nodesToProcess = _nodesNeedingSemantics.toList()
+        ..sort((RenderObject a, RenderObject b) => a.depth - b.depth);
+      _nodesNeedingSemantics.clear();
+      for (final RenderObject node in nodesToProcess) {
+        if (node._needsSemanticsUpdate && node.owner == this)
+          node._updateSemantics();
+      }
+      _semanticsOwner!.sendSemanticsUpdate();
+    } finally {
+      assert(_nodesNeedingSemantics.isEmpty);
+      assert(() {
+        _debugDoingSemantics = false;
+        return true;
+      }());
+      if (!kReleaseMode) {
+        Timeline.finishSync();
+      }
+    }
+  }
+}
+
+/// An object in the render tree.
+///
+/// The [RenderObject] class hierarchy is the core of the rendering
+/// library's reason for being.
+///
+/// [RenderObject]s have a [parent], and have a slot called [parentData] in
+/// which the parent [RenderObject] can store child-specific data, for example,
+/// the child position. The [RenderObject] class also implements the basic
+/// layout and paint protocols.
+///
+/// The [RenderObject] class, however, does not define a child model (e.g.
+/// whether a node has zero, one, or more children). It also doesn't define a
+/// coordinate system (e.g. whether children are positioned in Cartesian
+/// coordinates, in polar coordinates, etc) or a specific layout protocol (e.g.
+/// whether the layout is width-in-height-out, or constraint-in-size-out, or
+/// whether the parent sets the size and position of the child before or after
+/// the child lays out, etc; or indeed whether the children are allowed to read
+/// their parent's [parentData] slot).
+///
+/// The [RenderBox] subclass introduces the opinion that the layout
+/// system uses Cartesian coordinates.
+///
+/// ## Writing a RenderObject subclass
+///
+/// In most cases, subclassing [RenderObject] itself is overkill, and
+/// [RenderBox] would be a better starting point. However, if a render object
+/// doesn't want to use a Cartesian coordinate system, then it should indeed
+/// inherit from [RenderObject] directly. This allows it to define its own
+/// layout protocol by using a new subclass of [Constraints] rather than using
+/// [BoxConstraints], and by potentially using an entirely new set of objects
+/// and values to represent the result of the output rather than just a [Size].
+/// This increased flexibility comes at the cost of not being able to rely on
+/// the features of [RenderBox]. For example, [RenderBox] implements an
+/// intrinsic sizing protocol that allows you to measure a child without fully
+/// laying it out, in such a way that if that child changes size, the parent
+/// will be laid out again (to take into account the new dimensions of the
+/// child). This is a subtle and bug-prone feature to get right.
+///
+/// Most aspects of writing a [RenderBox] apply to writing a [RenderObject] as
+/// well, and therefore the discussion at [RenderBox] is recommended background
+/// reading. The main differences are around layout and hit testing, since those
+/// are the aspects that [RenderBox] primarily specializes.
+///
+/// ### Layout
+///
+/// A layout protocol begins with a subclass of [Constraints]. See the
+/// discussion at [Constraints] for more information on how to write a
+/// [Constraints] subclass.
+///
+/// The [performLayout] method should take the [constraints], and apply them.
+/// The output of the layout algorithm is fields set on the object that describe
+/// the geometry of the object for the purposes of the parent's layout. For
+/// example, with [RenderBox] the output is the [RenderBox.size] field. This
+/// output should only be read by the parent if the parent specified
+/// `parentUsesSize` as true when calling [layout] on the child.
+///
+/// Anytime anything changes on a render object that would affect the layout of
+/// that object, it should call [markNeedsLayout].
+///
+/// ### Hit Testing
+///
+/// Hit testing is even more open-ended than layout. There is no method to
+/// override, you are expected to provide one.
+///
+/// The general behavior of your hit-testing method should be similar to the
+/// behavior described for [RenderBox]. The main difference is that the input
+/// need not be an [Offset]. You are also allowed to use a different subclass of
+/// [HitTestEntry] when adding entries to the [HitTestResult]. When the
+/// [handleEvent] method is called, the same object that was added to the
+/// [HitTestResult] will be passed in, so it can be used to track information
+/// like the precise coordinate of the hit, in whatever coordinate system is
+/// used by the new layout protocol.
+///
+/// ### Adapting from one protocol to another
+///
+/// In general, the root of a Flutter render object tree is a [RenderView]. This
+/// object has a single child, which must be a [RenderBox]. Thus, if you want to
+/// have a custom [RenderObject] subclass in the render tree, you have two
+/// choices: you either need to replace the [RenderView] itself, or you need to
+/// have a [RenderBox] that has your class as its child. (The latter is the much
+/// more common case.)
+///
+/// This [RenderBox] subclass converts from the box protocol to the protocol of
+/// your class.
+///
+/// In particular, this means that for hit testing it overrides
+/// [RenderBox.hitTest], and calls whatever method you have in your class for
+/// hit testing.
+///
+/// Similarly, it overrides [performLayout] to create a [Constraints] object
+/// appropriate for your class and passes that to the child's [layout] method.
+///
+/// ### Layout interactions between render objects
+///
+/// In general, the layout of a render object should only depend on the output of
+/// its child's layout, and then only if `parentUsesSize` is set to true in the
+/// [layout] call. Furthermore, if it is set to true, the parent must call the
+/// child's [layout] if the child is to be rendered, because otherwise the
+/// parent will not be notified when the child changes its layout outputs.
+///
+/// It is possible to set up render object protocols that transfer additional
+/// information. For example, in the [RenderBox] protocol you can query your
+/// children's intrinsic dimensions and baseline geometry. However, if this is
+/// done then it is imperative that the child call [markNeedsLayout] on the
+/// parent any time that additional information changes, if the parent used it
+/// in the last layout phase. For an example of how to implement this, see the
+/// [RenderBox.markNeedsLayout] method. It overrides
+/// [RenderObject.markNeedsLayout] so that if a parent has queried the intrinsic
+/// or baseline information, it gets marked dirty whenever the child's geometry
+/// changes.
+abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
+  /// Initializes internal fields for subclasses.
+  RenderObject() {
+    _needsCompositing = isRepaintBoundary || alwaysNeedsCompositing;
+  }
+
+  /// Cause the entire subtree rooted at the given [RenderObject] to be marked
+  /// dirty for layout, paint, etc, so that the effects of a hot reload can be
+  /// seen, or so that the effect of changing a global debug flag (such as
+  /// [debugPaintSizeEnabled]) can be applied.
+  ///
+  /// This is called by the [RendererBinding] in response to the
+  /// `ext.flutter.reassemble` hook, which is used by development tools when the
+  /// application code has changed, to cause the widget tree to pick up any
+  /// changed implementations.
+  ///
+  /// This is expensive and should not be called except during development.
+  ///
+  /// See also:
+  ///
+  ///  * [BindingBase.reassembleApplication]
+  void reassemble() {
+    markNeedsLayout();
+    markNeedsCompositingBitsUpdate();
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+    visitChildren((RenderObject child) {
+      child.reassemble();
+    });
+  }
+
+  // LAYOUT
+
+  /// Data for use by the parent render object.
+  ///
+  /// The parent data is used by the render object that lays out this object
+  /// (typically this object's parent in the render tree) to store information
+  /// relevant to itself and to any other nodes who happen to know exactly what
+  /// the data means. The parent data is opaque to the child.
+  ///
+  ///  * The parent data field must not be directly set, except by calling
+  ///    [setupParentData] on the parent node.
+  ///  * The parent data can be set before the child is added to the parent, by
+  ///    calling [setupParentData] on the future parent node.
+  ///  * The conventions for using the parent data depend on the layout protocol
+  ///    used between the parent and child. For example, in box layout, the
+  ///    parent data is completely opaque but in sector layout the child is
+  ///    permitted to read some fields of the parent data.
+  ParentData? parentData;
+
+  /// Override to setup parent data correctly for your children.
+  ///
+  /// You can call this function to set up the parent data for child before the
+  /// child is added to the parent's child list.
+  void setupParentData(covariant RenderObject child) {
+    assert(_debugCanPerformMutations);
+    if (child.parentData is! ParentData)
+      child.parentData = ParentData();
+  }
+
+  /// Called by subclasses when they decide a render object is a child.
+  ///
+  /// Only for use by subclasses when changing their child lists. Calling this
+  /// in other cases will lead to an inconsistent tree and probably cause crashes.
+  @override
+  void adoptChild(RenderObject child) {
+    assert(_debugCanPerformMutations);
+    assert(child != null);
+    setupParentData(child);
+    markNeedsLayout();
+    markNeedsCompositingBitsUpdate();
+    markNeedsSemanticsUpdate();
+    super.adoptChild(child);
+  }
+
+  /// Called by subclasses when they decide a render object is no longer a child.
+  ///
+  /// Only for use by subclasses when changing their child lists. Calling this
+  /// in other cases will lead to an inconsistent tree and probably cause crashes.
+  @override
+  void dropChild(RenderObject child) {
+    assert(_debugCanPerformMutations);
+    assert(child != null);
+    assert(child.parentData != null);
+    child._cleanRelayoutBoundary();
+    child.parentData!.detach();
+    child.parentData = null;
+    super.dropChild(child);
+    markNeedsLayout();
+    markNeedsCompositingBitsUpdate();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Calls visitor for each immediate child of this render object.
+  ///
+  /// Override in subclasses with children and call the visitor for each child.
+  void visitChildren(RenderObjectVisitor visitor) { }
+
+  /// The object responsible for creating this render object.
+  ///
+  /// Used in debug messages.
+  Object? debugCreator;
+
+  void _debugReportException(String method, Object exception, StackTrace stack) {
+    FlutterError.reportError(FlutterErrorDetails(
+      exception: exception,
+      stack: stack,
+      library: 'rendering library',
+      context: ErrorDescription('during $method()'),
+      informationCollector: () sync* {
+        if (debugCreator != null)
+          yield DiagnosticsDebugCreator(debugCreator!);
+        yield describeForError('The following RenderObject was being processed when the exception was fired');
+        // TODO(jacobr): this error message has a code smell. Consider whether
+        // displaying the truncated children is really useful for command line
+        // users. Inspector users can see the full tree by clicking on the
+        // render object so this may not be that useful.
+        yield describeForError('RenderObject', style: DiagnosticsTreeStyle.truncateChildren);
+      },
+    ));
+  }
+
+  /// Whether [performResize] for this render object is currently running.
+  ///
+  /// Only valid when asserts are enabled. In release builds, always returns
+  /// false.
+  bool get debugDoingThisResize => _debugDoingThisResize;
+  bool _debugDoingThisResize = false;
+
+  /// Whether [performLayout] for this render object is currently running.
+  ///
+  /// Only valid when asserts are enabled. In release builds, always returns
+  /// false.
+  bool get debugDoingThisLayout => _debugDoingThisLayout;
+  bool _debugDoingThisLayout = false;
+
+  /// The render object that is actively computing layout.
+  ///
+  /// Only valid when asserts are enabled. In release builds, always returns
+  /// null.
+  static RenderObject? get debugActiveLayout => _debugActiveLayout;
+  static RenderObject? _debugActiveLayout;
+
+  /// Whether the parent render object is permitted to use this render object's
+  /// size.
+  ///
+  /// Determined by the `parentUsesSize` parameter to [layout].
+  ///
+  /// Only valid when asserts are enabled. In release builds, throws.
+  bool get debugCanParentUseSize => _debugCanParentUseSize!;
+  bool? _debugCanParentUseSize;
+
+  bool _debugMutationsLocked = false;
+
+  /// Whether tree mutations are currently permitted.
+  ///
+  /// Only valid when asserts are enabled. In release builds, always returns
+  /// null.
+  bool get _debugCanPerformMutations {
+    late bool result;
+    assert(() {
+      RenderObject node = this;
+      while (true) {
+        if (node._doingThisLayoutWithCallback) {
+          result = true;
+          break;
+        }
+        if (owner != null && owner!._debugAllowMutationsToDirtySubtrees && node._needsLayout) {
+          result = true;
+          break;
+        }
+        if (node._debugMutationsLocked) {
+          result = false;
+          break;
+        }
+        if (node.parent is! RenderObject) {
+          result = true;
+          break;
+        }
+        node = node.parent! as RenderObject;
+      }
+      return true;
+    }());
+    return result;
+  }
+
+  @override
+  PipelineOwner? get owner => super.owner as PipelineOwner?;
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    // If the node was dirtied in some way while unattached, make sure to add
+    // it to the appropriate dirty list now that an owner is available
+    if (_needsLayout && _relayoutBoundary != null) {
+      // Don't enter this block if we've never laid out at all;
+      // scheduleInitialLayout() will handle it
+      _needsLayout = false;
+      markNeedsLayout();
+    }
+    if (_needsCompositingBitsUpdate) {
+      _needsCompositingBitsUpdate = false;
+      markNeedsCompositingBitsUpdate();
+    }
+    if (_needsPaint && _layer != null) {
+      // Don't enter this block if we've never painted at all;
+      // scheduleInitialPaint() will handle it
+      _needsPaint = false;
+      markNeedsPaint();
+    }
+    if (_needsSemanticsUpdate && _semanticsConfiguration.isSemanticBoundary) {
+      // Don't enter this block if we've never updated semantics at all;
+      // scheduleInitialSemantics() will handle it
+      _needsSemanticsUpdate = false;
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  /// Whether this render object's layout information is dirty.
+  ///
+  /// This is only set in debug mode. In general, render objects should not need
+  /// to condition their runtime behavior on whether they are dirty or not,
+  /// since they should only be marked dirty immediately prior to being laid
+  /// out and painted. In release builds, this throws.
+  ///
+  /// It is intended to be used by tests and asserts.
+  bool get debugNeedsLayout {
+    late bool result;
+    assert(() {
+      result = _needsLayout;
+      return true;
+    }());
+    return result;
+  }
+  bool _needsLayout = true;
+
+  RenderObject? _relayoutBoundary;
+  bool _doingThisLayoutWithCallback = false;
+
+  /// The layout constraints most recently supplied by the parent.
+  ///
+  /// If layout has not yet happened, accessing this getter will
+  /// throw a [StateError] exception.
+  @protected
+  Constraints get constraints {
+    if (_constraints == null)
+      throw StateError('A RenderObject does not have any constraints before it has been laid out.');
+    return _constraints!;
+  }
+  Constraints? _constraints;
+
+  /// Verify that the object's constraints are being met. Override
+  /// this function in a subclass to verify that your state matches
+  /// the constraints object. This function is only called in checked
+  /// mode and only when needsLayout is false. If the constraints are
+  /// not met, it should assert or throw an exception.
+  @protected
+  void debugAssertDoesMeetConstraints();
+
+  /// When true, debugAssertDoesMeetConstraints() is currently
+  /// executing asserts for verifying the consistent behavior of
+  /// intrinsic dimensions methods.
+  ///
+  /// This should only be set by debugAssertDoesMeetConstraints()
+  /// implementations. It is used by tests to selectively ignore
+  /// custom layout callbacks. It should not be set outside of
+  /// debugAssertDoesMeetConstraints(), and should not be checked in
+  /// release mode (where it will always be false).
+  static bool debugCheckingIntrinsics = false;
+  bool _debugSubtreeRelayoutRootAlreadyMarkedNeedsLayout() {
+    if (_relayoutBoundary == null)
+      return true; // we don't know where our relayout boundary is yet
+    RenderObject node = this;
+    while (node != _relayoutBoundary) {
+      assert(node._relayoutBoundary == _relayoutBoundary);
+      assert(node.parent != null);
+      node = node.parent! as RenderObject;
+      if ((!node._needsLayout) && (!node._debugDoingThisLayout))
+        return false;
+    }
+    assert(node._relayoutBoundary == node);
+    return true;
+  }
+
+  /// Mark this render object's layout information as dirty, and either register
+  /// this object with its [PipelineOwner], or defer to the parent, depending on
+  /// whether this object is a relayout boundary or not respectively.
+  ///
+  /// ## Background
+  ///
+  /// Rather than eagerly updating layout information in response to writes into
+  /// a render object, we instead mark the layout information as dirty, which
+  /// schedules a visual update. As part of the visual update, the rendering
+  /// pipeline updates the render object's layout information.
+  ///
+  /// This mechanism batches the layout work so that multiple sequential writes
+  /// are coalesced, removing redundant computation.
+  ///
+  /// If a render object's parent indicates that it uses the size of one of its
+  /// render object children when computing its layout information, this
+  /// function, when called for the child, will also mark the parent as needing
+  /// layout. In that case, since both the parent and the child need to have
+  /// their layout recomputed, the pipeline owner is only notified about the
+  /// parent; when the parent is laid out, it will call the child's [layout]
+  /// method and thus the child will be laid out as well.
+  ///
+  /// Once [markNeedsLayout] has been called on a render object,
+  /// [debugNeedsLayout] returns true for that render object until just after
+  /// the pipeline owner has called [layout] on the render object.
+  ///
+  /// ## Special cases
+  ///
+  /// Some subclasses of [RenderObject], notably [RenderBox], have other
+  /// situations in which the parent needs to be notified if the child is
+  /// dirtied (e.g., if the child's intrinsic dimensions or baseline changes).
+  /// Such subclasses override markNeedsLayout and either call
+  /// `super.markNeedsLayout()`, in the normal case, or call
+  /// [markParentNeedsLayout], in the case where the parent needs to be laid out
+  /// as well as the child.
+  ///
+  /// If [sizedByParent] has changed, calls
+  /// [markNeedsLayoutForSizedByParentChange] instead of [markNeedsLayout].
+  void markNeedsLayout() {
+    assert(_debugCanPerformMutations);
+    if (_needsLayout) {
+      assert(_debugSubtreeRelayoutRootAlreadyMarkedNeedsLayout());
+      return;
+    }
+    assert(_relayoutBoundary != null);
+    if (_relayoutBoundary != this) {
+      markParentNeedsLayout();
+    } else {
+      _needsLayout = true;
+      if (owner != null) {
+        assert(() {
+          if (debugPrintMarkNeedsLayoutStacks)
+            debugPrintStack(label: 'markNeedsLayout() called for $this');
+          return true;
+        }());
+        owner!._nodesNeedingLayout.add(this);
+        owner!.requestVisualUpdate();
+      }
+    }
+  }
+
+  /// Mark this render object's layout information as dirty, and then defer to
+  /// the parent.
+  ///
+  /// This function should only be called from [markNeedsLayout] or
+  /// [markNeedsLayoutForSizedByParentChange] implementations of subclasses that
+  /// introduce more reasons for deferring the handling of dirty layout to the
+  /// parent. See [markNeedsLayout] for details.
+  ///
+  /// Only call this if [parent] is not null.
+  @protected
+  void markParentNeedsLayout() {
+    _needsLayout = true;
+    assert(this.parent != null);
+    final RenderObject parent = this.parent! as RenderObject;
+    if (!_doingThisLayoutWithCallback) {
+      parent.markNeedsLayout();
+    } else {
+      assert(parent._debugDoingThisLayout);
+    }
+    assert(parent == this.parent);
+  }
+
+  /// Mark this render object's layout information as dirty (like
+  /// [markNeedsLayout]), and additionally also handle any necessary work to
+  /// handle the case where [sizedByParent] has changed value.
+  ///
+  /// This should be called whenever [sizedByParent] might have changed.
+  ///
+  /// Only call this if [parent] is not null.
+  void markNeedsLayoutForSizedByParentChange() {
+    markNeedsLayout();
+    markParentNeedsLayout();
+  }
+
+  void _cleanRelayoutBoundary() {
+    if (_relayoutBoundary != this) {
+      _relayoutBoundary = null;
+      _needsLayout = true;
+      visitChildren(_cleanChildRelayoutBoundary);
+    }
+  }
+
+  // Reduces closure allocation for visitChildren use cases.
+  static void _cleanChildRelayoutBoundary(RenderObject child) {
+    child._cleanRelayoutBoundary();
+  }
+
+  /// Bootstrap the rendering pipeline by scheduling the very first layout.
+  ///
+  /// Requires this render object to be attached and that this render object
+  /// is the root of the render tree.
+  ///
+  /// See [RenderView] for an example of how this function is used.
+  void scheduleInitialLayout() {
+    assert(attached);
+    assert(parent is! RenderObject);
+    assert(!owner!._debugDoingLayout);
+    assert(_relayoutBoundary == null);
+    _relayoutBoundary = this;
+    assert(() {
+      _debugCanParentUseSize = false;
+      return true;
+    }());
+    owner!._nodesNeedingLayout.add(this);
+  }
+
+  void _layoutWithoutResize() {
+    assert(_relayoutBoundary == this);
+    RenderObject? debugPreviousActiveLayout;
+    assert(!_debugMutationsLocked);
+    assert(!_doingThisLayoutWithCallback);
+    assert(_debugCanParentUseSize != null);
+    assert(() {
+      _debugMutationsLocked = true;
+      _debugDoingThisLayout = true;
+      debugPreviousActiveLayout = _debugActiveLayout;
+      _debugActiveLayout = this;
+      if (debugPrintLayouts)
+        debugPrint('Laying out (without resize) $this');
+      return true;
+    }());
+    try {
+      performLayout();
+      markNeedsSemanticsUpdate();
+    } catch (e, stack) {
+      _debugReportException('performLayout', e, stack);
+    }
+    assert(() {
+      _debugActiveLayout = debugPreviousActiveLayout;
+      _debugDoingThisLayout = false;
+      _debugMutationsLocked = false;
+      return true;
+    }());
+    _needsLayout = false;
+    markNeedsPaint();
+  }
+
+  /// Compute the layout for this render object.
+  ///
+  /// This method is the main entry point for parents to ask their children to
+  /// update their layout information. The parent passes a constraints object,
+  /// which informs the child as to which layouts are permissible. The child is
+  /// required to obey the given constraints.
+  ///
+  /// If the parent reads information computed during the child's layout, the
+  /// parent must pass true for `parentUsesSize`. In that case, the parent will
+  /// be marked as needing layout whenever the child is marked as needing layout
+  /// because the parent's layout information depends on the child's layout
+  /// information. If the parent uses the default value (false) for
+  /// `parentUsesSize`, the child can change its layout information (subject to
+  /// the given constraints) without informing the parent.
+  ///
+  /// Subclasses should not override [layout] directly. Instead, they should
+  /// override [performResize] and/or [performLayout]. The [layout] method
+  /// delegates the actual work to [performResize] and [performLayout].
+  ///
+  /// The parent's [performLayout] method should call the [layout] of all its
+  /// children unconditionally. It is the [layout] method's responsibility (as
+  /// implemented here) to return early if the child does not need to do any
+  /// work to update its layout information.
+  void layout(Constraints constraints, { bool parentUsesSize = false }) {
+    if (!kReleaseMode && debugProfileLayoutsEnabled)
+      Timeline.startSync('$runtimeType',  arguments: timelineArgumentsIndicatingLandmarkEvent);
+
+    assert(constraints != null);
+    assert(constraints.debugAssertIsValid(
+      isAppliedConstraint: true,
+      informationCollector: () sync* {
+        final List<String> stack = StackTrace.current.toString().split('\n');
+        int? targetFrame;
+        final Pattern layoutFramePattern = RegExp(r'^#[0-9]+ +RenderObject.layout \(');
+        for (int i = 0; i < stack.length; i += 1) {
+          if (layoutFramePattern.matchAsPrefix(stack[i]) != null) {
+            targetFrame = i + 1;
+            break;
+          }
+        }
+        if (targetFrame != null && targetFrame < stack.length) {
+          final Pattern targetFramePattern = RegExp(r'^#[0-9]+ +(.+)$');
+          final Match? targetFrameMatch = targetFramePattern.matchAsPrefix(stack[targetFrame]);
+          final String? problemFunction = (targetFrameMatch != null && targetFrameMatch.groupCount > 0) ? targetFrameMatch.group(1) : stack[targetFrame].trim();
+          // TODO(jacobr): this case is similar to displaying a single stack frame.
+          yield ErrorDescription(
+            "These invalid constraints were provided to $runtimeType's layout() "
+            'function by the following function, which probably computed the '
+            'invalid constraints in question:\n'
+            '  $problemFunction'
+          );
+        }
+      },
+    ));
+    assert(!_debugDoingThisResize);
+    assert(!_debugDoingThisLayout);
+    RenderObject? relayoutBoundary;
+    if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
+      relayoutBoundary = this;
+    } else {
+      relayoutBoundary = (parent! as RenderObject)._relayoutBoundary;
+    }
+    assert(() {
+      _debugCanParentUseSize = parentUsesSize;
+      return true;
+    }());
+    if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
+      assert(() {
+        // in case parentUsesSize changed since the last invocation, set size
+        // to itself, so it has the right internal debug values.
+        _debugDoingThisResize = sizedByParent;
+        _debugDoingThisLayout = !sizedByParent;
+        final RenderObject? debugPreviousActiveLayout = _debugActiveLayout;
+        _debugActiveLayout = this;
+        debugResetSize();
+        _debugActiveLayout = debugPreviousActiveLayout;
+        _debugDoingThisLayout = false;
+        _debugDoingThisResize = false;
+        return true;
+      }());
+
+      if (!kReleaseMode && debugProfileLayoutsEnabled)
+        Timeline.finishSync();
+      return;
+    }
+    _constraints = constraints;
+    if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
+      // The local relayout boundary has changed, must notify children in case
+      // they also need updating. Otherwise, they will be confused about what
+      // their actual relayout boundary is later.
+      visitChildren(_cleanChildRelayoutBoundary);
+    }
+    _relayoutBoundary = relayoutBoundary;
+    assert(!_debugMutationsLocked);
+    assert(!_doingThisLayoutWithCallback);
+    assert(() {
+      _debugMutationsLocked = true;
+      if (debugPrintLayouts)
+        debugPrint('Laying out (${sizedByParent ? "with separate resize" : "with resize allowed"}) $this');
+      return true;
+    }());
+    if (sizedByParent) {
+      assert(() {
+        _debugDoingThisResize = true;
+        return true;
+      }());
+      try {
+        performResize();
+        assert(() {
+          debugAssertDoesMeetConstraints();
+          return true;
+        }());
+      } catch (e, stack) {
+        _debugReportException('performResize', e, stack);
+      }
+      assert(() {
+        _debugDoingThisResize = false;
+        return true;
+      }());
+    }
+    RenderObject? debugPreviousActiveLayout;
+    assert(() {
+      _debugDoingThisLayout = true;
+      debugPreviousActiveLayout = _debugActiveLayout;
+      _debugActiveLayout = this;
+      return true;
+    }());
+    try {
+      performLayout();
+      markNeedsSemanticsUpdate();
+      assert(() {
+        debugAssertDoesMeetConstraints();
+        return true;
+      }());
+    } catch (e, stack) {
+      _debugReportException('performLayout', e, stack);
+    }
+    assert(() {
+      _debugActiveLayout = debugPreviousActiveLayout;
+      _debugDoingThisLayout = false;
+      _debugMutationsLocked = false;
+      return true;
+    }());
+    _needsLayout = false;
+    markNeedsPaint();
+
+    if (!kReleaseMode && debugProfileLayoutsEnabled)
+      Timeline.finishSync();
+  }
+
+  /// If a subclass has a "size" (the state controlled by `parentUsesSize`,
+  /// whatever it is in the subclass, e.g. the actual `size` property of
+  /// [RenderBox]), and the subclass verifies that in checked mode this "size"
+  /// property isn't used when [debugCanParentUseSize] isn't set, then that
+  /// subclass should override [debugResetSize] to reapply the current values of
+  /// [debugCanParentUseSize] to that state.
+  @protected
+  void debugResetSize() { }
+
+  /// Whether the constraints are the only input to the sizing algorithm (in
+  /// particular, child nodes have no impact).
+  ///
+  /// Returning false is always correct, but returning true can be more
+  /// efficient when computing the size of this render object because we don't
+  /// need to recompute the size if the constraints don't change.
+  ///
+  /// Typically, subclasses will always return the same value. If the value can
+  /// change, then, when it does change, the subclass should make sure to call
+  /// [markNeedsLayoutForSizedByParentChange].
+  ///
+  /// Subclasses that return true must not change the dimensions of this render
+  /// object in [performLayout]. Instead, that work should be done by
+  /// [performResize] or - for subclasses of [RenderBox] - in
+  /// [RenderBox.computeDryLayout].
+  @protected
+  bool get sizedByParent => false;
+
+  /// {@template flutter.rendering.RenderObject.performResize}
+  /// Updates the render objects size using only the constraints.
+  ///
+  /// Do not call this function directly: call [layout] instead. This function
+  /// is called by [layout] when there is actually work to be done by this
+  /// render object during layout. The layout constraints provided by your
+  /// parent are available via the [constraints] getter.
+  ///
+  /// This function is called only if [sizedByParent] is true.
+  /// {@endtemplate}
+  ///
+  /// Subclasses that set [sizedByParent] to true should override this method to
+  /// compute their size. Subclasses of [RenderBox] should consider overriding
+  /// [RenderBox.computeDryLayout] instead.
+  @protected
+  void performResize();
+
+  /// Do the work of computing the layout for this render object.
+  ///
+  /// Do not call this function directly: call [layout] instead. This function
+  /// is called by [layout] when there is actually work to be done by this
+  /// render object during layout. The layout constraints provided by your
+  /// parent are available via the [constraints] getter.
+  ///
+  /// If [sizedByParent] is true, then this function should not actually change
+  /// the dimensions of this render object. Instead, that work should be done by
+  /// [performResize]. If [sizedByParent] is false, then this function should
+  /// both change the dimensions of this render object and instruct its children
+  /// to layout.
+  ///
+  /// In implementing this function, you must call [layout] on each of your
+  /// children, passing true for parentUsesSize if your layout information is
+  /// dependent on your child's layout information. Passing true for
+  /// parentUsesSize ensures that this render object will undergo layout if the
+  /// child undergoes layout. Otherwise, the child can change its layout
+  /// information without informing this render object.
+  @protected
+  void performLayout();
+
+  /// Allows mutations to be made to this object's child list (and any
+  /// descendants) as well as to any other dirty nodes in the render tree owned
+  /// by the same [PipelineOwner] as this object. The `callback` argument is
+  /// invoked synchronously, and the mutations are allowed only during that
+  /// callback's execution.
+  ///
+  /// This exists to allow child lists to be built on-demand during layout (e.g.
+  /// based on the object's size), and to enable nodes to be moved around the
+  /// tree as this happens (e.g. to handle [GlobalKey] reparenting), while still
+  /// ensuring that any particular node is only laid out once per frame.
+  ///
+  /// Calling this function disables a number of assertions that are intended to
+  /// catch likely bugs. As such, using this function is generally discouraged.
+  ///
+  /// This function can only be called during layout.
+  @protected
+  void invokeLayoutCallback<T extends Constraints>(LayoutCallback<T> callback) {
+    assert(_debugMutationsLocked);
+    assert(_debugDoingThisLayout);
+    assert(!_doingThisLayoutWithCallback);
+    _doingThisLayoutWithCallback = true;
+    try {
+      owner!._enableMutationsToDirtySubtrees(() { callback(constraints as T); });
+    } finally {
+      _doingThisLayoutWithCallback = false;
+    }
+  }
+
+  /// Rotate this render object (not yet implemented).
+  void rotate({
+    int? oldAngle, // 0..3
+    int? newAngle, // 0..3
+    Duration? time,
+  }) { }
+
+  // when the parent has rotated (e.g. when the screen has been turned
+  // 90 degrees), immediately prior to layout() being called for the
+  // new dimensions, rotate() is called with the old and new angles.
+  // The next time paint() is called, the coordinate space will have
+  // been rotated N quarter-turns clockwise, where:
+  //    N = newAngle-oldAngle
+  // ...but the rendering is expected to remain the same, pixel for
+  // pixel, on the output device. Then, the layout() method or
+  // equivalent will be called.
+
+
+  // PAINTING
+
+  /// Whether [paint] for this render object is currently running.
+  ///
+  /// Only valid when asserts are enabled. In release builds, always returns
+  /// false.
+  bool get debugDoingThisPaint => _debugDoingThisPaint;
+  bool _debugDoingThisPaint = false;
+
+  /// The render object that is actively painting.
+  ///
+  /// Only valid when asserts are enabled. In release builds, always returns
+  /// null.
+  static RenderObject? get debugActivePaint => _debugActivePaint;
+  static RenderObject? _debugActivePaint;
+
+  /// Whether this render object repaints separately from its parent.
+  ///
+  /// Override this in subclasses to indicate that instances of your class ought
+  /// to repaint independently. For example, render objects that repaint
+  /// frequently might want to repaint themselves without requiring their parent
+  /// to repaint.
+  ///
+  /// If this getter returns true, the [paintBounds] are applied to this object
+  /// and all descendants. The framework automatically creates an [OffsetLayer]
+  /// and assigns it to the [layer] field. Render objects that declare
+  /// themselves as repaint boundaries must not replace the layer created by
+  /// the framework.
+  ///
+  /// Warning: This getter must not change value over the lifetime of this object.
+  ///
+  /// See [RepaintBoundary] for more information about how repaint boundaries function.
+  bool get isRepaintBoundary => false;
+
+  /// Called, in checked mode, if [isRepaintBoundary] is true, when either the
+  /// this render object or its parent attempt to paint.
+  ///
+  /// This can be used to record metrics about whether the node should actually
+  /// be a repaint boundary.
+  void debugRegisterRepaintBoundaryPaint({ bool includedParent = true, bool includedChild = false }) { }
+
+  /// Whether this render object always needs compositing.
+  ///
+  /// Override this in subclasses to indicate that your paint function always
+  /// creates at least one composited layer. For example, videos should return
+  /// true if they use hardware decoders.
+  ///
+  /// You must call [markNeedsCompositingBitsUpdate] if the value of this getter
+  /// changes. (This is implied when [adoptChild] or [dropChild] are called.)
+  @protected
+  bool get alwaysNeedsCompositing => false;
+
+  /// The compositing layer that this render object uses to repaint.
+  ///
+  /// If this render object is not a repaint boundary, it is the responsibility
+  /// of the [paint] method to populate this field. If [needsCompositing] is
+  /// true, this field may be populated with the root-most layer used by the
+  /// render object implementation. When repainting, instead of creating a new
+  /// layer the render object may update the layer stored in this field for better
+  /// performance. It is also OK to leave this field as null and create a new
+  /// layer on every repaint, but without the performance benefit. If
+  /// [needsCompositing] is false, this field must be set to null either by
+  /// never populating this field, or by setting it to null when the value of
+  /// [needsCompositing] changes from true to false.
+  ///
+  /// If this render object is a repaint boundary, the framework automatically
+  /// creates an [OffsetLayer] and populates this field prior to calling the
+  /// [paint] method. The [paint] method must not replace the value of this
+  /// field.
+  @protected
+  ContainerLayer? get layer {
+    assert(!isRepaintBoundary || (_layer == null || _layer is OffsetLayer));
+    return _layer;
+  }
+
+  @protected
+  set layer(ContainerLayer? newLayer) {
+    assert(
+      !isRepaintBoundary,
+      'Attempted to set a layer to a repaint boundary render object.\n'
+      'The framework creates and assigns an OffsetLayer to a repaint '
+      'boundary automatically.',
+    );
+    _layer = newLayer;
+  }
+  ContainerLayer? _layer;
+
+  /// In debug mode, the compositing layer that this render object uses to repaint.
+  ///
+  /// This getter is intended for debugging purposes only. In release builds, it
+  /// always returns null. In debug builds, it returns the layer even if the layer
+  /// is dirty.
+  ///
+  /// For production code, consider [layer].
+  ContainerLayer? get debugLayer {
+    ContainerLayer? result;
+    assert(() {
+      result = _layer;
+      return true;
+    }());
+    return result;
+  }
+
+  bool _needsCompositingBitsUpdate = false; // set to true when a child is added
+  /// Mark the compositing state for this render object as dirty.
+  ///
+  /// This is called to indicate that the value for [needsCompositing] needs to
+  /// be recomputed during the next [PipelineOwner.flushCompositingBits] engine
+  /// phase.
+  ///
+  /// When the subtree is mutated, we need to recompute our
+  /// [needsCompositing] bit, and some of our ancestors need to do the
+  /// same (in case ours changed in a way that will change theirs). To
+  /// this end, [adoptChild] and [dropChild] call this method, and, as
+  /// necessary, this method calls the parent's, etc, walking up the
+  /// tree to mark all the nodes that need updating.
+  ///
+  /// This method does not schedule a rendering frame, because since
+  /// it cannot be the case that _only_ the compositing bits changed,
+  /// something else will have scheduled a frame for us.
+  void markNeedsCompositingBitsUpdate() {
+    if (_needsCompositingBitsUpdate)
+      return;
+    _needsCompositingBitsUpdate = true;
+    if (parent is RenderObject) {
+      final RenderObject parent = this.parent! as RenderObject;
+      if (parent._needsCompositingBitsUpdate)
+        return;
+      if (!isRepaintBoundary && !parent.isRepaintBoundary) {
+        parent.markNeedsCompositingBitsUpdate();
+        return;
+      }
+    }
+    assert(() {
+      final AbstractNode? parent = this.parent;
+      if (parent is RenderObject)
+        return parent._needsCompositing;
+      return true;
+    }());
+    // parent is fine (or there isn't one), but we are dirty
+    if (owner != null)
+      owner!._nodesNeedingCompositingBitsUpdate.add(this);
+  }
+
+  late bool _needsCompositing; // initialized in the constructor
+  /// Whether we or one of our descendants has a compositing layer.
+  ///
+  /// If this node needs compositing as indicated by this bit, then all ancestor
+  /// nodes will also need compositing.
+  ///
+  /// Only legal to call after [PipelineOwner.flushLayout] and
+  /// [PipelineOwner.flushCompositingBits] have been called.
+  bool get needsCompositing {
+    assert(!_needsCompositingBitsUpdate); // make sure we don't use this bit when it is dirty
+    return _needsCompositing;
+  }
+
+  void _updateCompositingBits() {
+    if (!_needsCompositingBitsUpdate)
+      return;
+    final bool oldNeedsCompositing = _needsCompositing;
+    _needsCompositing = false;
+    visitChildren((RenderObject child) {
+      child._updateCompositingBits();
+      if (child.needsCompositing)
+        _needsCompositing = true;
+    });
+    if (isRepaintBoundary || alwaysNeedsCompositing)
+      _needsCompositing = true;
+    if (oldNeedsCompositing != _needsCompositing)
+      markNeedsPaint();
+    _needsCompositingBitsUpdate = false;
+  }
+
+  /// Whether this render object's paint information is dirty.
+  ///
+  /// This is only set in debug mode. In general, render objects should not need
+  /// to condition their runtime behavior on whether they are dirty or not,
+  /// since they should only be marked dirty immediately prior to being laid
+  /// out and painted. (In release builds, this throws.)
+  ///
+  /// It is intended to be used by tests and asserts.
+  ///
+  /// It is possible (and indeed, quite common) for [debugNeedsPaint] to be
+  /// false and [debugNeedsLayout] to be true. The render object will still be
+  /// repainted in the next frame when this is the case, because the
+  /// [markNeedsPaint] method is implicitly called by the framework after a
+  /// render object is laid out, prior to the paint phase.
+  bool get debugNeedsPaint {
+    late bool result;
+    assert(() {
+      result = _needsPaint;
+      return true;
+    }());
+    return result;
+  }
+  bool _needsPaint = true;
+
+  /// Mark this render object as having changed its visual appearance.
+  ///
+  /// Rather than eagerly updating this render object's display list
+  /// in response to writes, we instead mark the render object as needing to
+  /// paint, which schedules a visual update. As part of the visual update, the
+  /// rendering pipeline will give this render object an opportunity to update
+  /// its display list.
+  ///
+  /// This mechanism batches the painting work so that multiple sequential
+  /// writes are coalesced, removing redundant computation.
+  ///
+  /// Once [markNeedsPaint] has been called on a render object,
+  /// [debugNeedsPaint] returns true for that render object until just after
+  /// the pipeline owner has called [paint] on the render object.
+  ///
+  /// See also:
+  ///
+  ///  * [RepaintBoundary], to scope a subtree of render objects to their own
+  ///    layer, thus limiting the number of nodes that [markNeedsPaint] must mark
+  ///    dirty.
+  void markNeedsPaint() {
+    assert(owner == null || !owner!.debugDoingPaint);
+    if (_needsPaint)
+      return;
+    _needsPaint = true;
+    if (isRepaintBoundary) {
+      assert(() {
+        if (debugPrintMarkNeedsPaintStacks)
+          debugPrintStack(label: 'markNeedsPaint() called for $this');
+        return true;
+      }());
+      // If we always have our own layer, then we can just repaint
+      // ourselves without involving any other nodes.
+      assert(_layer is OffsetLayer);
+      if (owner != null) {
+        owner!._nodesNeedingPaint.add(this);
+        owner!.requestVisualUpdate();
+      }
+    } else if (parent is RenderObject) {
+      final RenderObject parent = this.parent! as RenderObject;
+      parent.markNeedsPaint();
+      assert(parent == this.parent);
+    } else {
+      assert(() {
+        if (debugPrintMarkNeedsPaintStacks)
+          debugPrintStack(label: 'markNeedsPaint() called for $this (root of render tree)');
+        return true;
+      }());
+      // If we're the root of the render tree (probably a RenderView),
+      // then we have to paint ourselves, since nobody else can paint
+      // us. We don't add ourselves to _nodesNeedingPaint in this
+      // case, because the root is always told to paint regardless.
+      if (owner != null)
+        owner!.requestVisualUpdate();
+    }
+  }
+
+  // Called when flushPaint() tries to make us paint but our layer is detached.
+  // To make sure that our subtree is repainted when it's finally reattached,
+  // even in the case where some ancestor layer is itself never marked dirty, we
+  // have to mark our entire detached subtree as dirty and needing to be
+  // repainted. That way, we'll eventually be repainted.
+  void _skippedPaintingOnLayer() {
+    assert(attached);
+    assert(isRepaintBoundary);
+    assert(_needsPaint);
+    assert(_layer != null);
+    assert(!_layer!.attached);
+    AbstractNode? node = parent;
+    while (node is RenderObject) {
+      if (node.isRepaintBoundary) {
+        if (node._layer == null)
+          break; // looks like the subtree here has never been painted. let it handle itself.
+        if (node._layer!.attached)
+          break; // it's the one that detached us, so it's the one that will decide to repaint us.
+        node._needsPaint = true;
+      }
+      node = node.parent;
+    }
+  }
+
+  /// Bootstrap the rendering pipeline by scheduling the very first paint.
+  ///
+  /// Requires that this render object is attached, is the root of the render
+  /// tree, and has a composited layer.
+  ///
+  /// See [RenderView] for an example of how this function is used.
+  void scheduleInitialPaint(ContainerLayer rootLayer) {
+    assert(rootLayer.attached);
+    assert(attached);
+    assert(parent is! RenderObject);
+    assert(!owner!._debugDoingPaint);
+    assert(isRepaintBoundary);
+    assert(_layer == null);
+    _layer = rootLayer;
+    assert(_needsPaint);
+    owner!._nodesNeedingPaint.add(this);
+  }
+
+  /// Replace the layer. This is only valid for the root of a render
+  /// object subtree (whatever object [scheduleInitialPaint] was
+  /// called on).
+  ///
+  /// This might be called if, e.g., the device pixel ratio changed.
+  void replaceRootLayer(OffsetLayer rootLayer) {
+    assert(rootLayer.attached);
+    assert(attached);
+    assert(parent is! RenderObject);
+    assert(!owner!._debugDoingPaint);
+    assert(isRepaintBoundary);
+    assert(_layer != null); // use scheduleInitialPaint the first time
+    _layer!.detach();
+    _layer = rootLayer;
+    markNeedsPaint();
+  }
+
+  void _paintWithContext(PaintingContext context, Offset offset) {
+    assert(() {
+      if (_debugDoingThisPaint) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('Tried to paint a RenderObject reentrantly.'),
+          describeForError(
+            'The following RenderObject was already being painted when it was '
+            'painted again'
+          ),
+          ErrorDescription(
+            'Since this typically indicates an infinite recursion, it is '
+            'disallowed.'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    // If we still need layout, then that means that we were skipped in the
+    // layout phase and therefore don't need painting. We might not know that
+    // yet (that is, our layer might not have been detached yet), because the
+    // same node that skipped us in layout is above us in the tree (obviously)
+    // and therefore may not have had a chance to paint yet (since the tree
+    // paints in reverse order). In particular this will happen if they have
+    // a different layer, because there's a repaint boundary between us.
+    if (_needsLayout)
+      return;
+    assert(() {
+      if (_needsCompositingBitsUpdate) {
+        if (parent is RenderObject) {
+          final RenderObject parent = this.parent! as RenderObject;
+          bool visitedByParent = false;
+          parent.visitChildren((RenderObject child) {
+            if (child == this) {
+              visitedByParent = true;
+            }
+          });
+          if (!visitedByParent) {
+            throw FlutterError.fromParts(<DiagnosticsNode>[
+              ErrorSummary(
+                "A RenderObject was not visited by the parent's visitChildren "
+                'during paint.',
+              ),
+              parent.describeForError(
+                'The parent was',
+              ),
+              describeForError(
+                'The child that was not visited was'
+              ),
+              ErrorDescription(
+                'A RenderObject with children must implement visitChildren and '
+                'call the visitor exactly once for each child; it also should not '
+                'paint children that were removed with dropChild.'
+              ),
+              ErrorHint(
+                'This usually indicates an error in the Flutter framework itself.'
+              ),
+            ]);
+          }
+        }
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary(
+            'Tried to paint a RenderObject before its compositing bits were '
+            'updated.'
+          ),
+          describeForError(
+            'The following RenderObject was marked as having dirty compositing '
+            'bits at the time that it was painted',
+          ),
+          ErrorDescription(
+            'A RenderObject that still has dirty compositing bits cannot be '
+            'painted because this indicates that the tree has not yet been '
+            'properly configured for creating the layer tree.'
+          ),
+          ErrorHint(
+            'This usually indicates an error in the Flutter framework itself.'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    RenderObject? debugLastActivePaint;
+    assert(() {
+      _debugDoingThisPaint = true;
+      debugLastActivePaint = _debugActivePaint;
+      _debugActivePaint = this;
+      assert(!isRepaintBoundary || _layer != null);
+      return true;
+    }());
+    _needsPaint = false;
+    try {
+      paint(context, offset);
+      assert(!_needsLayout); // check that the paint() method didn't mark us dirty again
+      assert(!_needsPaint); // check that the paint() method didn't mark us dirty again
+    } catch (e, stack) {
+      _debugReportException('paint', e, stack);
+    }
+    assert(() {
+      debugPaint(context, offset);
+      _debugActivePaint = debugLastActivePaint;
+      _debugDoingThisPaint = false;
+      return true;
+    }());
+  }
+
+  /// An estimate of the bounds within which this render object will paint.
+  /// Useful for debugging flags such as [debugPaintLayerBordersEnabled].
+  ///
+  /// These are also the bounds used by [showOnScreen] to make a [RenderObject]
+  /// visible on screen.
+  Rect get paintBounds;
+
+  /// Override this method to paint debugging information.
+  void debugPaint(PaintingContext context, Offset offset) { }
+
+  /// Paint this render object into the given context at the given offset.
+  ///
+  /// Subclasses should override this method to provide a visual appearance
+  /// for themselves. The render object's local coordinate system is
+  /// axis-aligned with the coordinate system of the context's canvas and the
+  /// render object's local origin (i.e, x=0 and y=0) is placed at the given
+  /// offset in the context's canvas.
+  ///
+  /// Do not call this function directly. If you wish to paint yourself, call
+  /// [markNeedsPaint] instead to schedule a call to this function. If you wish
+  /// to paint one of your children, call [PaintingContext.paintChild] on the
+  /// given `context`.
+  ///
+  /// When painting one of your children (via a paint child function on the
+  /// given context), the current canvas held by the context might change
+  /// because draw operations before and after painting children might need to
+  /// be recorded on separate compositing layers.
+  void paint(PaintingContext context, Offset offset) { }
+
+  /// Applies the transform that would be applied when painting the given child
+  /// to the given matrix.
+  ///
+  /// Used by coordinate conversion functions to translate coordinates local to
+  /// one render object into coordinates local to another render object.
+  void applyPaintTransform(covariant RenderObject child, Matrix4 transform) {
+    assert(child.parent == this);
+  }
+
+  /// Applies the paint transform up the tree to `ancestor`.
+  ///
+  /// Returns a matrix that maps the local paint coordinate system to the
+  /// coordinate system of `ancestor`.
+  ///
+  /// If `ancestor` is null, this method returns a matrix that maps from the
+  /// local paint coordinate system to the coordinate system of the
+  /// [PipelineOwner.rootNode]. For the render tree owner by the
+  /// [RendererBinding] (i.e. for the main render tree displayed on the device)
+  /// this means that this method maps to the global coordinate system in
+  /// logical pixels. To get physical pixels, use [applyPaintTransform] from the
+  /// [RenderView] to further transform the coordinate.
+  Matrix4 getTransformTo(RenderObject? ancestor) {
+    final bool ancestorSpecified = ancestor != null;
+    assert(attached);
+    if (ancestor == null) {
+      final AbstractNode? rootNode = owner!.rootNode;
+      if (rootNode is RenderObject)
+        ancestor = rootNode;
+    }
+    final List<RenderObject> renderers = <RenderObject>[];
+    for (RenderObject renderer = this; renderer != ancestor; renderer = renderer.parent! as RenderObject) {
+      assert(renderer != null); // Failed to find ancestor in parent chain.
+      renderers.add(renderer);
+    }
+    if (ancestorSpecified)
+      renderers.add(ancestor!);
+    final Matrix4 transform = Matrix4.identity();
+    for (int index = renderers.length - 1; index > 0; index -= 1) {
+      renderers[index].applyPaintTransform(renderers[index - 1], transform);
+    }
+    return transform;
+  }
+
+
+  /// Returns a rect in this object's coordinate system that describes
+  /// the approximate bounding box of the clip rect that would be
+  /// applied to the given child during the paint phase, if any.
+  ///
+  /// Returns null if the child would not be clipped.
+  ///
+  /// This is used in the semantics phase to avoid including children
+  /// that are not physically visible.
+  Rect? describeApproximatePaintClip(covariant RenderObject child) => null;
+
+  /// Returns a rect in this object's coordinate system that describes
+  /// which [SemanticsNode]s produced by the `child` should be included in the
+  /// semantics tree. [SemanticsNode]s from the `child` that are positioned
+  /// outside of this rect will be dropped. Child [SemanticsNode]s that are
+  /// positioned inside this rect, but outside of [describeApproximatePaintClip]
+  /// will be included in the tree marked as hidden. Child [SemanticsNode]s
+  /// that are inside of both rect will be included in the tree as regular
+  /// nodes.
+  ///
+  /// This method only returns a non-null value if the semantics clip rect
+  /// is different from the rect returned by [describeApproximatePaintClip].
+  /// If the semantics clip rect and the paint clip rect are the same, this
+  /// method returns null.
+  ///
+  /// A viewport would typically implement this method to include semantic nodes
+  /// in the semantics tree that are currently hidden just before the leading
+  /// or just after the trailing edge. These nodes have to be included in the
+  /// semantics tree to implement implicit accessibility scrolling on iOS where
+  /// the viewport scrolls implicitly when moving the accessibility focus from
+  /// a the last visible node in the viewport to the first hidden one.
+  ///
+  /// See also:
+  ///
+  /// * [RenderViewportBase.cacheExtent], used by viewports to extend their
+  ///   semantics clip beyond their approximate paint clip.
+  Rect? describeSemanticsClip(covariant RenderObject? child) => null;
+
+  // SEMANTICS
+
+  /// Bootstrap the semantics reporting mechanism by marking this node
+  /// as needing a semantics update.
+  ///
+  /// Requires that this render object is attached, and is the root of
+  /// the render tree.
+  ///
+  /// See [RendererBinding] for an example of how this function is used.
+  void scheduleInitialSemantics() {
+    assert(attached);
+    assert(parent is! RenderObject);
+    assert(!owner!._debugDoingSemantics);
+    assert(_semantics == null);
+    assert(_needsSemanticsUpdate);
+    assert(owner!._semanticsOwner != null);
+    owner!._nodesNeedingSemantics.add(this);
+    owner!.requestVisualUpdate();
+  }
+
+  /// Report the semantics of this node, for example for accessibility purposes.
+  ///
+  /// This method should be overridden by subclasses that have interesting
+  /// semantic information.
+  ///
+  /// The given [SemanticsConfiguration] object is mutable and should be
+  /// annotated in a manner that describes the current state. No reference
+  /// should be kept to that object; mutating it outside of the context of the
+  /// [describeSemanticsConfiguration] call (for example as a result of
+  /// asynchronous computation) will at best have no useful effect and at worse
+  /// will cause crashes as the data will be in an inconsistent state.
+  ///
+  /// {@tool snippet}
+  ///
+  /// The following snippet will describe the node as a button that responds to
+  /// tap actions.
+  ///
+  /// ```dart
+  /// abstract class SemanticButtonRenderObject extends RenderObject {
+  ///   @override
+  ///   void describeSemanticsConfiguration(SemanticsConfiguration config) {
+  ///     super.describeSemanticsConfiguration(config);
+  ///     config
+  ///       ..onTap = _handleTap
+  ///       ..label = 'I am a button'
+  ///       ..isButton = true;
+  ///   }
+  ///
+  ///   void _handleTap() {
+  ///     // Do something.
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  @protected
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    // Nothing to do by default.
+  }
+
+  /// Sends a [SemanticsEvent] associated with this render object's [SemanticsNode].
+  ///
+  /// If this render object has no semantics information, the first parent
+  /// render object with a non-null semantic node is used.
+  ///
+  /// If semantics are disabled, no events are dispatched.
+  ///
+  /// See [SemanticsNode.sendEvent] for a full description of the behavior.
+  void sendSemanticsEvent(SemanticsEvent semanticsEvent) {
+    if (owner!.semanticsOwner == null)
+      return;
+    if (_semantics != null && !_semantics!.isMergedIntoParent) {
+      _semantics!.sendEvent(semanticsEvent);
+    } else if (parent != null) {
+      final RenderObject renderParent = parent! as RenderObject;
+      renderParent.sendSemanticsEvent(semanticsEvent);
+    }
+  }
+
+  // Use [_semanticsConfiguration] to access.
+  SemanticsConfiguration? _cachedSemanticsConfiguration;
+
+  SemanticsConfiguration get _semanticsConfiguration {
+    if (_cachedSemanticsConfiguration == null) {
+      _cachedSemanticsConfiguration = SemanticsConfiguration();
+      describeSemanticsConfiguration(_cachedSemanticsConfiguration!);
+    }
+    return _cachedSemanticsConfiguration!;
+  }
+
+  /// The bounding box, in the local coordinate system, of this
+  /// object, for accessibility purposes.
+  Rect get semanticBounds;
+
+  bool _needsSemanticsUpdate = true;
+  SemanticsNode? _semantics;
+
+  /// The semantics of this render object.
+  ///
+  /// Exposed only for testing and debugging. To learn about the semantics of
+  /// render objects in production, obtain a [SemanticsHandle] from
+  /// [PipelineOwner.ensureSemantics].
+  ///
+  /// Only valid in debug and profile mode. In release builds, always returns
+  /// null.
+  SemanticsNode? get debugSemantics {
+    if (!kReleaseMode) {
+      return _semantics;
+    }
+    return null;
+  }
+
+  /// Removes all semantics from this render object and its descendants.
+  ///
+  /// Should only be called on objects whose [parent] is not a [RenderObject].
+  ///
+  /// Override this method if you instantiate new [SemanticsNode]s in an
+  /// overridden [assembleSemanticsNode] method, to dispose of those nodes.
+  @mustCallSuper
+  void clearSemantics() {
+    _needsSemanticsUpdate = true;
+    _semantics = null;
+    visitChildren((RenderObject child) {
+      child.clearSemantics();
+    });
+  }
+
+  /// Mark this node as needing an update to its semantics description.
+  ///
+  /// This must be called whenever the semantics configuration of this
+  /// [RenderObject] as annotated by [describeSemanticsConfiguration] changes in
+  /// any way to update the semantics tree.
+  void markNeedsSemanticsUpdate() {
+    assert(!attached || !owner!._debugDoingSemantics);
+    if (!attached || owner!._semanticsOwner == null) {
+      _cachedSemanticsConfiguration = null;
+      return;
+    }
+
+    // Dirty the semantics tree starting at `this` until we have reached a
+    // RenderObject that is a semantics boundary. All semantics past this
+    // RenderObject are still up-to date. Therefore, we will later only rebuild
+    // the semantics subtree starting at the identified semantics boundary.
+
+    final bool wasSemanticsBoundary = _semantics != null && _cachedSemanticsConfiguration?.isSemanticBoundary == true;
+    _cachedSemanticsConfiguration = null;
+    bool isEffectiveSemanticsBoundary = _semanticsConfiguration.isSemanticBoundary && wasSemanticsBoundary;
+    RenderObject node = this;
+
+    while (!isEffectiveSemanticsBoundary && node.parent is RenderObject) {
+      if (node != this && node._needsSemanticsUpdate)
+        break;
+      node._needsSemanticsUpdate = true;
+
+      node = node.parent! as RenderObject;
+      isEffectiveSemanticsBoundary = node._semanticsConfiguration.isSemanticBoundary;
+      if (isEffectiveSemanticsBoundary && node._semantics == null) {
+        // We have reached a semantics boundary that doesn't own a semantics node.
+        // That means the semantics of this branch are currently blocked and will
+        // not appear in the semantics tree. We can abort the walk here.
+        return;
+      }
+    }
+    if (node != this && _semantics != null && _needsSemanticsUpdate) {
+      // If `this` node has already been added to [owner._nodesNeedingSemantics]
+      // remove it as it is no longer guaranteed that its semantics
+      // node will continue to be in the tree. If it still is in the tree, the
+      // ancestor `node` added to [owner._nodesNeedingSemantics] at the end of
+      // this block will ensure that the semantics of `this` node actually gets
+      // updated.
+      // (See semantics_10_test.dart for an example why this is required).
+      owner!._nodesNeedingSemantics.remove(this);
+    }
+    if (!node._needsSemanticsUpdate) {
+      node._needsSemanticsUpdate = true;
+      if (owner != null) {
+        assert(node._semanticsConfiguration.isSemanticBoundary || node.parent is! RenderObject);
+        owner!._nodesNeedingSemantics.add(node);
+        owner!.requestVisualUpdate();
+      }
+    }
+  }
+
+  /// Updates the semantic information of the render object.
+  void _updateSemantics() {
+    assert(_semanticsConfiguration.isSemanticBoundary || parent is! RenderObject);
+    if (_needsLayout) {
+      // There's not enough information in this subtree to compute semantics.
+      // The subtree is probably being kept alive by a viewport but not laid out.
+      return;
+    }
+    final _SemanticsFragment fragment = _getSemanticsForParent(
+      mergeIntoParent: _semantics?.parent?.isPartOfNodeMerging ?? false,
+    );
+    assert(fragment is _InterestingSemanticsFragment);
+    final _InterestingSemanticsFragment interestingFragment = fragment as _InterestingSemanticsFragment;
+    final List<SemanticsNode> result = <SemanticsNode>[];
+    interestingFragment.compileChildren(
+      parentSemanticsClipRect: _semantics?.parentSemanticsClipRect,
+      parentPaintClipRect: _semantics?.parentPaintClipRect,
+      elevationAdjustment: _semantics?.elevationAdjustment ?? 0.0,
+      result: result,
+    );
+    final SemanticsNode node = result.single;
+    // Fragment only wants to add this node's SemanticsNode to the parent.
+    assert(interestingFragment.config == null && node == _semantics);
+  }
+
+  /// Returns the semantics that this node would like to add to its parent.
+  _SemanticsFragment _getSemanticsForParent({
+    required bool mergeIntoParent,
+  }) {
+    assert(mergeIntoParent != null);
+    assert(!_needsLayout, 'Updated layout information required for $this to calculate semantics.');
+
+    final SemanticsConfiguration config = _semanticsConfiguration;
+    bool dropSemanticsOfPreviousSiblings = config.isBlockingSemanticsOfPreviouslyPaintedNodes;
+
+    final bool producesForkingFragment = !config.hasBeenAnnotated && !config.isSemanticBoundary;
+    final List<_InterestingSemanticsFragment> fragments = <_InterestingSemanticsFragment>[];
+    final Set<_InterestingSemanticsFragment> toBeMarkedExplicit = <_InterestingSemanticsFragment>{};
+    final bool childrenMergeIntoParent = mergeIntoParent || config.isMergingSemanticsOfDescendants;
+
+    // When set to true there's currently not enough information in this subtree
+    // to compute semantics. In this case the walk needs to be aborted and no
+    // SemanticsNodes in the subtree should be updated.
+    // This will be true for subtrees that are currently kept alive by a
+    // viewport but not laid out.
+    bool abortWalk = false;
+
+    visitChildrenForSemantics((RenderObject renderChild) {
+      if (abortWalk || _needsLayout) {
+        abortWalk = true;
+        return;
+      }
+      final _SemanticsFragment parentFragment = renderChild._getSemanticsForParent(
+        mergeIntoParent: childrenMergeIntoParent,
+      );
+      if (parentFragment.abortsWalk) {
+        abortWalk = true;
+        return;
+      }
+      if (parentFragment.dropsSemanticsOfPreviousSiblings) {
+        fragments.clear();
+        toBeMarkedExplicit.clear();
+        if (!config.isSemanticBoundary)
+          dropSemanticsOfPreviousSiblings = true;
+      }
+      // Figure out which child fragments are to be made explicit.
+      for (final _InterestingSemanticsFragment fragment in parentFragment.interestingFragments) {
+        fragments.add(fragment);
+        fragment.addAncestor(this);
+        fragment.addTags(config.tagsForChildren);
+        if (config.explicitChildNodes || parent is! RenderObject) {
+          fragment.markAsExplicit();
+          continue;
+        }
+        if (!fragment.hasConfigForParent || producesForkingFragment)
+          continue;
+        if (!config.isCompatibleWith(fragment.config))
+          toBeMarkedExplicit.add(fragment);
+        final int siblingLength = fragments.length - 1;
+        for (int i = 0; i < siblingLength; i += 1) {
+          final _InterestingSemanticsFragment siblingFragment = fragments[i];
+          if (!fragment.config!.isCompatibleWith(siblingFragment.config)) {
+            toBeMarkedExplicit.add(fragment);
+            toBeMarkedExplicit.add(siblingFragment);
+          }
+        }
+      }
+    });
+
+    if (abortWalk) {
+      return _AbortingSemanticsFragment(owner: this);
+    }
+
+    for (final _InterestingSemanticsFragment fragment in toBeMarkedExplicit)
+      fragment.markAsExplicit();
+
+    _needsSemanticsUpdate = false;
+
+    _SemanticsFragment result;
+    if (parent is! RenderObject) {
+      assert(!config.hasBeenAnnotated);
+      assert(!mergeIntoParent);
+      result = _RootSemanticsFragment(
+        owner: this,
+        dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings,
+      );
+    } else if (producesForkingFragment) {
+      result = _ContainerSemanticsFragment(
+        dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings,
+      );
+    } else {
+      result = _SwitchableSemanticsFragment(
+        config: config,
+        mergeIntoParent: mergeIntoParent,
+        owner: this,
+        dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings,
+      );
+      if (config.isSemanticBoundary) {
+        final _SwitchableSemanticsFragment fragment = result as _SwitchableSemanticsFragment;
+        fragment.markAsExplicit();
+      }
+    }
+
+    result.addAll(fragments);
+
+    return result;
+  }
+
+  /// Called when collecting the semantics of this node.
+  ///
+  /// The implementation has to return the children in paint order skipping all
+  /// children that are not semantically relevant (e.g. because they are
+  /// invisible).
+  ///
+  /// The default implementation mirrors the behavior of
+  /// [visitChildren] (which is supposed to walk all the children).
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    visitChildren(visitor);
+  }
+
+  /// Assemble the [SemanticsNode] for this [RenderObject].
+  ///
+  /// If [describeSemanticsConfiguration] sets
+  /// [SemanticsConfiguration.isSemanticBoundary] to true, this method is called
+  /// with the `node` created for this [RenderObject], the `config` to be
+  /// applied to that node and the `children` [SemanticsNode]s that descendants
+  /// of this RenderObject have generated.
+  ///
+  /// By default, the method will annotate `node` with `config` and add the
+  /// `children` to it.
+  ///
+  /// Subclasses can override this method to add additional [SemanticsNode]s
+  /// to the tree. If new [SemanticsNode]s are instantiated in this method
+  /// they must be disposed in [clearSemantics].
+  void assembleSemanticsNode(
+    SemanticsNode node,
+    SemanticsConfiguration config,
+    Iterable<SemanticsNode> children,
+  ) {
+    assert(node == _semantics);
+    // TODO(a14n): remove the following cast by updating type of parameter in either updateWith or assembleSemanticsNode
+    node.updateWith(config: config, childrenInInversePaintOrder: children as List<SemanticsNode>);
+  }
+
+  // EVENTS
+
+  /// Override this method to handle pointer events that hit this render object.
+  @override
+  void handleEvent(PointerEvent event, covariant HitTestEntry entry) { }
+
+
+  // HIT TESTING
+
+  // RenderObject subclasses are expected to have a method like the following
+  // (with the signature being whatever passes for coordinates for this
+  // particular class):
+  //
+  // bool hitTest(HitTestResult result, { Offset position }) {
+  //   // If the given position is not inside this node, then return false.
+  //   // Otherwise:
+  //   // For each child that intersects the position, in z-order starting from
+  //   // the top, call hitTest() for that child, passing it /result/, and the
+  //   // coordinates converted to the child's coordinate origin, and stop at
+  //   // the first child that returns true.
+  //   // Then, add yourself to /result/, and return true.
+  // }
+  //
+  // If you add yourself to /result/ and still return false, then that means you
+  // will see events but so will objects below you.
+
+
+  /// Returns a human understandable name.
+  @override
+  String toStringShort() {
+    String header = describeIdentity(this);
+    if (_relayoutBoundary != null && _relayoutBoundary != this) {
+      int count = 1;
+      RenderObject? target = parent as RenderObject?;
+      while (target != null && target != _relayoutBoundary) {
+        target = target.parent as RenderObject?;
+        count += 1;
+      }
+      header += ' relayoutBoundary=up$count';
+    }
+    if (_needsLayout)
+      header += ' NEEDS-LAYOUT';
+    if (_needsPaint)
+      header += ' NEEDS-PAINT';
+    if (_needsCompositingBitsUpdate)
+      header += ' NEEDS-COMPOSITING-BITS-UPDATE';
+    if (!attached)
+      header += ' DETACHED';
+    return header;
+  }
+
+  @override
+  String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) => toStringShort();
+
+  /// Returns a description of the tree rooted at this node.
+  /// If the prefix argument is provided, then every line in the output
+  /// will be prefixed by that string.
+  @override
+  String toStringDeep({
+    String prefixLineOne = '',
+    String? prefixOtherLines = '',
+    DiagnosticLevel minLevel = DiagnosticLevel.debug,
+  }) {
+    RenderObject? debugPreviousActiveLayout;
+    assert(() {
+      debugPreviousActiveLayout = _debugActiveLayout;
+      _debugActiveLayout = null;
+      return true;
+    }());
+    final String result = super.toStringDeep(
+      prefixLineOne: prefixLineOne,
+      prefixOtherLines: prefixOtherLines,
+      minLevel: minLevel,
+    );
+    assert(() {
+      _debugActiveLayout = debugPreviousActiveLayout;
+      return true;
+    }());
+    return result;
+  }
+
+  /// Returns a one-line detailed description of the render object.
+  /// This description is often somewhat long.
+  ///
+  /// This includes the same information for this RenderObject as given by
+  /// [toStringDeep], but does not recurse to any children.
+  @override
+  String toStringShallow({
+    String joiner = ', ',
+    DiagnosticLevel minLevel = DiagnosticLevel.debug,
+  }) {
+    RenderObject? debugPreviousActiveLayout;
+    assert(() {
+      debugPreviousActiveLayout = _debugActiveLayout;
+      _debugActiveLayout = null;
+      return true;
+    }());
+    final String result = super.toStringShallow(joiner: joiner, minLevel: minLevel);
+    assert(() {
+      _debugActiveLayout = debugPreviousActiveLayout;
+      return true;
+    }());
+    return result;
+  }
+
+  @protected
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('needsCompositing', value: _needsCompositing, ifTrue: 'needs compositing'));
+    properties.add(DiagnosticsProperty<Object?>('creator', debugCreator, defaultValue: null, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<ParentData>('parentData', parentData, tooltip: _debugCanParentUseSize == true ? 'can use size' : null, missingIfNull: true));
+    properties.add(DiagnosticsProperty<Constraints>('constraints', _constraints, missingIfNull: true));
+    // don't access it via the "layer" getter since that's only valid when we don't need paint
+    properties.add(DiagnosticsProperty<ContainerLayer>('layer', _layer, defaultValue: null));
+    properties.add(DiagnosticsProperty<SemanticsNode>('semantics node', _semantics, defaultValue: null));
+    properties.add(FlagProperty(
+      'isBlockingSemanticsOfPreviouslyPaintedNodes',
+      value: _semanticsConfiguration.isBlockingSemanticsOfPreviouslyPaintedNodes,
+      ifTrue: 'blocks semantics of earlier render objects below the common boundary',
+    ));
+    properties.add(FlagProperty('isSemanticBoundary', value: _semanticsConfiguration.isSemanticBoundary, ifTrue: 'semantic boundary'));
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() => <DiagnosticsNode>[];
+
+  /// Attempt to make (a portion of) this or a descendant [RenderObject] visible
+  /// on screen.
+  ///
+  /// If `descendant` is provided, that [RenderObject] is made visible. If
+  /// `descendant` is omitted, this [RenderObject] is made visible.
+  ///
+  /// The optional `rect` parameter describes which area of that [RenderObject]
+  /// should be shown on screen. If `rect` is null, the entire
+  /// [RenderObject] (as defined by its [paintBounds]) will be revealed. The
+  /// `rect` parameter is interpreted relative to the coordinate system of
+  /// `descendant` if that argument is provided and relative to this
+  /// [RenderObject] otherwise.
+  ///
+  /// The `duration` parameter can be set to a non-zero value to bring the
+  /// target object on screen in an animation defined by `curve`.
+  ///
+  /// See also:
+  ///
+  /// * [RenderViewportBase.showInViewport], which [RenderViewportBase] and
+  ///   [SingleChildScrollView] delegate this method to.
+  void showOnScreen({
+    RenderObject? descendant,
+    Rect? rect,
+    Duration duration = Duration.zero,
+    Curve curve = Curves.ease,
+  }) {
+    if (parent is RenderObject) {
+      final RenderObject renderParent = parent! as RenderObject;
+      renderParent.showOnScreen(
+        descendant: descendant ?? this,
+        rect: rect,
+        duration: duration,
+        curve: curve,
+      );
+    }
+  }
+
+  /// Adds a debug representation of a [RenderObject] optimized for including in
+  /// error messages.
+  ///
+  /// The default [style] of [DiagnosticsTreeStyle.shallow] ensures that all of
+  /// the properties of the render object are included in the error output but
+  /// none of the children of the object are.
+  ///
+  /// You should always include a RenderObject in an error message if it is the
+  /// [RenderObject] causing the failure or contract violation of the error.
+  DiagnosticsNode describeForError(String name, { DiagnosticsTreeStyle style = DiagnosticsTreeStyle.shallow }) {
+    return toDiagnosticsNode(name: name, style: style);
+  }
+}
+
+/// Generic mixin for render objects with one child.
+///
+/// Provides a child model for a render object subclass that has
+/// a unique child, which is accessible via the [child] getter.
+///
+/// This mixin is typically used to implement render objects created
+/// in a [SingleChildRenderObjectWidget].
+mixin RenderObjectWithChildMixin<ChildType extends RenderObject> on RenderObject {
+
+  /// Checks whether the given render object has the correct [runtimeType] to be
+  /// a child of this render object.
+  ///
+  /// Does nothing if assertions are disabled.
+  ///
+  /// Always returns true.
+  bool debugValidateChild(RenderObject child) {
+    assert(() {
+      if (child is! ChildType) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary(
+            'A $runtimeType expected a child of type $ChildType but received a '
+            'child of type ${child.runtimeType}.'
+          ),
+          ErrorDescription(
+            'RenderObjects expect specific types of children because they '
+            'coordinate with their children during layout and paint. For '
+            'example, a RenderSliver cannot be the child of a RenderBox because '
+            'a RenderSliver does not understand the RenderBox layout protocol.',
+          ),
+          ErrorSpacer(),
+          DiagnosticsProperty<Object?>(
+            'The $runtimeType that expected a $ChildType child was created by',
+            debugCreator,
+            style: DiagnosticsTreeStyle.errorProperty,
+          ),
+          ErrorSpacer(),
+          DiagnosticsProperty<Object?>(
+            'The ${child.runtimeType} that did not match the expected child type '
+            'was created by',
+            child.debugCreator,
+            style: DiagnosticsTreeStyle.errorProperty,
+          ),
+        ]);
+      }
+      return true;
+    }());
+    return true;
+  }
+
+  ChildType? _child;
+  /// The render object's unique child.
+  ChildType? get child => _child;
+  set child(ChildType? value) {
+    if (_child != null)
+      dropChild(_child!);
+    _child = value;
+    if (_child != null)
+      adoptChild(_child!);
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    if (_child != null)
+      _child!.attach(owner);
+  }
+
+  @override
+  void detach() {
+    super.detach();
+    if (_child != null)
+      _child!.detach();
+  }
+
+  @override
+  void redepthChildren() {
+    if (_child != null)
+      redepthChild(_child!);
+  }
+
+  @override
+  void visitChildren(RenderObjectVisitor visitor) {
+    if (_child != null)
+      visitor(_child!);
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    return child != null ? <DiagnosticsNode>[child!.toDiagnosticsNode(name: 'child')] : <DiagnosticsNode>[];
+  }
+}
+
+/// Parent data to support a doubly-linked list of children.
+///
+/// The children can be traversed using [nextSibling] or [previousSibling],
+/// which can be called on the parent data of the render objects
+/// obtained via [ContainerRenderObjectMixin.firstChild] or
+/// [ContainerRenderObjectMixin.lastChild].
+mixin ContainerParentDataMixin<ChildType extends RenderObject> on ParentData {
+  /// The previous sibling in the parent's child list.
+  ChildType? previousSibling;
+  /// The next sibling in the parent's child list.
+  ChildType? nextSibling;
+
+  /// Clear the sibling pointers.
+  @override
+  void detach() {
+    assert(previousSibling == null, 'Pointers to siblings must be nulled before detaching ParentData.');
+    assert(nextSibling == null, 'Pointers to siblings must be nulled before detaching ParentData.');
+    super.detach();
+  }
+}
+
+/// Generic mixin for render objects with a list of children.
+///
+/// Provides a child model for a render object subclass that has a doubly-linked
+/// list of children.
+///
+/// The [ChildType] specifies the type of the children (extending [RenderObject]),
+/// e.g. [RenderBox].
+///
+/// [ParentDataType] stores parent container data on its child render objects.
+/// It must extend [ContainerParentDataMixin], which provides the interface
+/// for visiting children. This data is populated by
+/// [RenderObject.setupParentData] implemented by the class using this mixin.
+///
+/// When using [RenderBox] as the child type, you will usually want to make use of
+/// [RenderBoxContainerDefaultsMixin] and extend [ContainerBoxParentData] for the
+/// parent data.
+///
+/// Moreover, this is a required mixin for render objects returned to [MultiChildRenderObjectWidget].
+mixin ContainerRenderObjectMixin<ChildType extends RenderObject, ParentDataType extends ContainerParentDataMixin<ChildType>> on RenderObject {
+  bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType? equals }) {
+    ParentDataType childParentData = child.parentData! as ParentDataType;
+    while (childParentData.previousSibling != null) {
+      assert(childParentData.previousSibling != child);
+      child = childParentData.previousSibling!;
+      childParentData = child.parentData! as ParentDataType;
+    }
+    return child == equals;
+  }
+  bool _debugUltimateNextSiblingOf(ChildType child, { ChildType? equals }) {
+    ParentDataType childParentData = child.parentData! as ParentDataType;
+    while (childParentData.nextSibling != null) {
+      assert(childParentData.nextSibling != child);
+      child = childParentData.nextSibling!;
+      childParentData = child.parentData! as ParentDataType;
+    }
+    return child == equals;
+  }
+
+  int _childCount = 0;
+  /// The number of children.
+  int get childCount => _childCount;
+
+  /// Checks whether the given render object has the correct [runtimeType] to be
+  /// a child of this render object.
+  ///
+  /// Does nothing if assertions are disabled.
+  ///
+  /// Always returns true.
+  bool debugValidateChild(RenderObject child) {
+    assert(() {
+      if (child is! ChildType) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary(
+            'A $runtimeType expected a child of type $ChildType but received a '
+            'child of type ${child.runtimeType}.'
+          ),
+          ErrorDescription(
+            'RenderObjects expect specific types of children because they '
+            'coordinate with their children during layout and paint. For '
+            'example, a RenderSliver cannot be the child of a RenderBox because '
+            'a RenderSliver does not understand the RenderBox layout protocol.'
+          ),
+          ErrorSpacer(),
+          DiagnosticsProperty<Object?>(
+            'The $runtimeType that expected a $ChildType child was created by',
+            debugCreator,
+            style: DiagnosticsTreeStyle.errorProperty,
+          ),
+          ErrorSpacer(),
+          DiagnosticsProperty<Object?>(
+            'The ${child.runtimeType} that did not match the expected child type '
+            'was created by',
+            child.debugCreator,
+            style: DiagnosticsTreeStyle.errorProperty,
+          ),
+        ]);
+      }
+      return true;
+    }());
+    return true;
+  }
+
+  ChildType? _firstChild;
+  ChildType? _lastChild;
+  void _insertIntoChildList(ChildType child, { ChildType? after }) {
+    final ParentDataType childParentData = child.parentData! as ParentDataType;
+    assert(childParentData.nextSibling == null);
+    assert(childParentData.previousSibling == null);
+    _childCount += 1;
+    assert(_childCount > 0);
+    if (after == null) {
+      // insert at the start (_firstChild)
+      childParentData.nextSibling = _firstChild;
+      if (_firstChild != null) {
+        final ParentDataType _firstChildParentData = _firstChild!.parentData! as ParentDataType;
+        _firstChildParentData.previousSibling = child;
+      }
+      _firstChild = child;
+      _lastChild ??= child;
+    } else {
+      assert(_firstChild != null);
+      assert(_lastChild != null);
+      assert(_debugUltimatePreviousSiblingOf(after, equals: _firstChild));
+      assert(_debugUltimateNextSiblingOf(after, equals: _lastChild));
+      final ParentDataType afterParentData = after.parentData! as ParentDataType;
+      if (afterParentData.nextSibling == null) {
+        // insert at the end (_lastChild); we'll end up with two or more children
+        assert(after == _lastChild);
+        childParentData.previousSibling = after;
+        afterParentData.nextSibling = child;
+        _lastChild = child;
+      } else {
+        // insert in the middle; we'll end up with three or more children
+        // set up links from child to siblings
+        childParentData.nextSibling = afterParentData.nextSibling;
+        childParentData.previousSibling = after;
+        // set up links from siblings to child
+        final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling!.parentData! as ParentDataType;
+        final ParentDataType childNextSiblingParentData = childParentData.nextSibling!.parentData! as ParentDataType;
+        childPreviousSiblingParentData.nextSibling = child;
+        childNextSiblingParentData.previousSibling = child;
+        assert(afterParentData.nextSibling == child);
+      }
+    }
+  }
+  /// Insert child into this render object's child list after the given child.
+  ///
+  /// If `after` is null, then this inserts the child at the start of the list,
+  /// and the child becomes the new [firstChild].
+  void insert(ChildType child, { ChildType? after }) {
+    assert(child != this, 'A RenderObject cannot be inserted into itself.');
+    assert(after != this, 'A RenderObject cannot simultaneously be both the parent and the sibling of another RenderObject.');
+    assert(child != after, 'A RenderObject cannot be inserted after itself.');
+    assert(child != _firstChild);
+    assert(child != _lastChild);
+    adoptChild(child);
+    _insertIntoChildList(child, after: after);
+  }
+
+  /// Append child to the end of this render object's child list.
+  void add(ChildType child) {
+    insert(child, after: _lastChild);
+  }
+
+  /// Add all the children to the end of this render object's child list.
+  void addAll(List<ChildType>? children) {
+    children?.forEach(add);
+  }
+
+  void _removeFromChildList(ChildType child) {
+    final ParentDataType childParentData = child.parentData! as ParentDataType;
+    assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild));
+    assert(_debugUltimateNextSiblingOf(child, equals: _lastChild));
+    assert(_childCount >= 0);
+    if (childParentData.previousSibling == null) {
+      assert(_firstChild == child);
+      _firstChild = childParentData.nextSibling;
+    } else {
+      final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling!.parentData! as ParentDataType;
+      childPreviousSiblingParentData.nextSibling = childParentData.nextSibling;
+    }
+    if (childParentData.nextSibling == null) {
+      assert(_lastChild == child);
+      _lastChild = childParentData.previousSibling;
+    } else {
+      final ParentDataType childNextSiblingParentData = childParentData.nextSibling!.parentData! as ParentDataType;
+      childNextSiblingParentData.previousSibling = childParentData.previousSibling;
+    }
+    childParentData.previousSibling = null;
+    childParentData.nextSibling = null;
+    _childCount -= 1;
+  }
+
+  /// Remove this child from the child list.
+  ///
+  /// Requires the child to be present in the child list.
+  void remove(ChildType child) {
+    _removeFromChildList(child);
+    dropChild(child);
+  }
+
+  /// Remove all their children from this render object's child list.
+  ///
+  /// More efficient than removing them individually.
+  void removeAll() {
+    ChildType? child = _firstChild;
+    while (child != null) {
+      final ParentDataType childParentData = child.parentData! as ParentDataType;
+      final ChildType? next = childParentData.nextSibling;
+      childParentData.previousSibling = null;
+      childParentData.nextSibling = null;
+      dropChild(child);
+      child = next;
+    }
+    _firstChild = null;
+    _lastChild = null;
+    _childCount = 0;
+  }
+
+  /// Move the given `child` in the child list to be after another child.
+  ///
+  /// More efficient than removing and re-adding the child. Requires the child
+  /// to already be in the child list at some position. Pass null for `after` to
+  /// move the child to the start of the child list.
+  void move(ChildType child, { ChildType? after }) {
+    assert(child != this);
+    assert(after != this);
+    assert(child != after);
+    assert(child.parent == this);
+    final ParentDataType childParentData = child.parentData! as ParentDataType;
+    if (childParentData.previousSibling == after)
+      return;
+    _removeFromChildList(child);
+    _insertIntoChildList(child, after: after);
+    markNeedsLayout();
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    ChildType? child = _firstChild;
+    while (child != null) {
+      child.attach(owner);
+      final ParentDataType childParentData = child.parentData! as ParentDataType;
+      child = childParentData.nextSibling;
+    }
+  }
+
+  @override
+  void detach() {
+    super.detach();
+    ChildType? child = _firstChild;
+    while (child != null) {
+      child.detach();
+      final ParentDataType childParentData = child.parentData! as ParentDataType;
+      child = childParentData.nextSibling;
+    }
+  }
+
+  @override
+  void redepthChildren() {
+    ChildType? child = _firstChild;
+    while (child != null) {
+      redepthChild(child);
+      final ParentDataType childParentData = child.parentData! as ParentDataType;
+      child = childParentData.nextSibling;
+    }
+  }
+
+  @override
+  void visitChildren(RenderObjectVisitor visitor) {
+    ChildType? child = _firstChild;
+    while (child != null) {
+      visitor(child);
+      final ParentDataType childParentData = child.parentData! as ParentDataType;
+      child = childParentData.nextSibling;
+    }
+  }
+
+  /// The first child in the child list.
+  ChildType? get firstChild => _firstChild;
+
+  /// The last child in the child list.
+  ChildType? get lastChild => _lastChild;
+
+  /// The previous child before the given child in the child list.
+  ChildType? childBefore(ChildType child) {
+    assert(child != null);
+    assert(child.parent == this);
+    final ParentDataType childParentData = child.parentData! as ParentDataType;
+    return childParentData.previousSibling;
+  }
+
+  /// The next child after the given child in the child list.
+  ChildType? childAfter(ChildType child) {
+    assert(child != null);
+    assert(child.parent == this);
+    final ParentDataType childParentData = child.parentData! as ParentDataType;
+    return childParentData.nextSibling;
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    final List<DiagnosticsNode> children = <DiagnosticsNode>[];
+    if (firstChild != null) {
+      ChildType child = firstChild!;
+      int count = 1;
+      while (true) {
+        children.add(child.toDiagnosticsNode(name: 'child $count'));
+        if (child == lastChild)
+          break;
+        count += 1;
+        final ParentDataType childParentData = child.parentData! as ParentDataType;
+        child = childParentData.nextSibling!;
+      }
+    }
+    return children;
+  }
+}
+
+/// Mixin for [RenderObject] that will call [systemFontsDidChange] whenever the
+/// system fonts change.
+///
+/// System fonts can change when the OS install or remove a font. Use this mixin if
+/// the [RenderObject] uses [TextPainter] or [Paragraph] to correctly update the
+/// text when it happens.
+mixin RelayoutWhenSystemFontsChangeMixin on RenderObject {
+
+  /// A callback that is called when system fonts have changed.
+  ///
+  /// By default, [markNeedsLayout] is called on the [RenderObject]
+  /// implementing this mixin.
+  ///
+  /// Subclass should override this method to clear any extra cache that depend
+  /// on font-related metrics.
+  @protected
+  @mustCallSuper
+  void systemFontsDidChange() {
+    markNeedsLayout();
+  }
+
+  @override
+  void attach(covariant PipelineOwner owner) {
+    super.attach(owner);
+    PaintingBinding.instance!.systemFonts.addListener(systemFontsDidChange);
+  }
+
+  @override
+  void detach() {
+    PaintingBinding.instance!.systemFonts.removeListener(systemFontsDidChange);
+    super.detach();
+  }
+}
+
+/// Describes the semantics information a [RenderObject] wants to add to its
+/// parent.
+///
+/// It has two notable subclasses:
+///  * [_InterestingSemanticsFragment] describing actual semantic information to
+///    be added to the parent.
+///  * [_ContainerSemanticsFragment]: a container class to transport the semantic
+///    information of multiple [_InterestingSemanticsFragment] to a parent.
+abstract class _SemanticsFragment {
+  _SemanticsFragment({ required this.dropsSemanticsOfPreviousSiblings })
+    : assert (dropsSemanticsOfPreviousSiblings != null);
+
+  /// Incorporate the fragments of children into this fragment.
+  void addAll(Iterable<_InterestingSemanticsFragment> fragments);
+
+  /// Whether this fragment wants to make the semantics information of
+  /// previously painted [RenderObject]s unreachable for accessibility purposes.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsConfiguration.isBlockingSemanticsOfPreviouslyPaintedNodes]
+  ///    describes what semantics are dropped in more detail.
+  final bool dropsSemanticsOfPreviousSiblings;
+
+  /// Returns [_InterestingSemanticsFragment] describing the actual semantic
+  /// information that this fragment wants to add to the parent.
+  List<_InterestingSemanticsFragment> get interestingFragments;
+
+  /// Whether this fragment wants to abort the semantics walk because the
+  /// information in the tree are not sufficient to calculate semantics.
+  ///
+  /// This happens for subtrees that are currently kept alive by a viewport but
+  /// not laid out.
+  ///
+  /// See also:
+  ///
+  ///  * [_AbortingSemanticsFragment], which sets this to true.
+  bool get abortsWalk => false;
+}
+
+/// A container used when a [RenderObject] wants to add multiple independent
+/// [_InterestingSemanticsFragment] to its parent.
+///
+/// The [_InterestingSemanticsFragment] to be added to the parent can be
+/// obtained via [interestingFragments].
+class _ContainerSemanticsFragment extends _SemanticsFragment {
+
+  _ContainerSemanticsFragment({ required bool dropsSemanticsOfPreviousSiblings })
+    : super(dropsSemanticsOfPreviousSiblings: dropsSemanticsOfPreviousSiblings);
+
+  @override
+  void addAll(Iterable<_InterestingSemanticsFragment> fragments) {
+    interestingFragments.addAll(fragments);
+  }
+
+  @override
+  final List<_InterestingSemanticsFragment> interestingFragments = <_InterestingSemanticsFragment>[];
+}
+
+/// A [_SemanticsFragment] that describes which concrete semantic information
+/// a [RenderObject] wants to add to the [SemanticsNode] of its parent.
+///
+/// Specifically, it describes which children (as returned by [compileChildren])
+/// should be added to the parent's [SemanticsNode] and which [config] should be
+/// merged into the parent's [SemanticsNode].
+abstract class _InterestingSemanticsFragment extends _SemanticsFragment {
+  _InterestingSemanticsFragment({
+    required RenderObject owner,
+    required bool dropsSemanticsOfPreviousSiblings,
+  }) : assert(owner != null),
+       _ancestorChain = <RenderObject>[owner],
+       super(dropsSemanticsOfPreviousSiblings: dropsSemanticsOfPreviousSiblings);
+
+  /// The [RenderObject] that owns this fragment (and any new [SemanticsNode]
+  /// introduced by it).
+  RenderObject get owner => _ancestorChain.first;
+
+  final List<RenderObject> _ancestorChain;
+
+  /// The children to be added to the parent.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsNode.parentSemanticsClipRect] for the source and definition
+  ///    of the `parentSemanticsClipRect` argument.
+  ///  * [SemanticsNode.parentPaintClipRect] for the source and definition
+  ///    of the `parentPaintClipRect` argument.
+  ///  * [SemanticsNode.elevationAdjustment] for the source and definition
+  ///    of the `elevationAdjustment` argument.
+  void compileChildren({
+    required Rect? parentSemanticsClipRect,
+    required Rect? parentPaintClipRect,
+    required double elevationAdjustment,
+    required List<SemanticsNode> result,
+  });
+
+  /// The [SemanticsConfiguration] the child wants to merge into the parent's
+  /// [SemanticsNode] or null if it doesn't want to merge anything.
+  SemanticsConfiguration? get config;
+
+  /// Disallows this fragment to merge any configuration into its parent's
+  /// [SemanticsNode].
+  ///
+  /// After calling this the fragment will only produce children to be added
+  /// to the parent and it will return null for [config].
+  void markAsExplicit();
+
+  /// Consume the fragments of children.
+  ///
+  /// For each provided fragment it will add that fragment's children to
+  /// this fragment's children (as returned by [compileChildren]) and merge that
+  /// fragment's [config] into this fragment's [config].
+  ///
+  /// If a provided fragment should not merge anything into [config] call
+  /// [markAsExplicit] before passing the fragment to this method.
+  @override
+  void addAll(Iterable<_InterestingSemanticsFragment> fragments);
+
+  /// Whether this fragment wants to add any semantic information to the parent
+  /// [SemanticsNode].
+  bool get hasConfigForParent => config != null;
+
+  @override
+  List<_InterestingSemanticsFragment> get interestingFragments => <_InterestingSemanticsFragment>[this];
+
+  Set<SemanticsTag>? _tagsForChildren;
+
+  /// Tag all children produced by [compileChildren] with `tags`.
+  void addTags(Iterable<SemanticsTag>? tags) {
+    if (tags == null || tags.isEmpty)
+      return;
+    _tagsForChildren ??= <SemanticsTag>{};
+    _tagsForChildren!.addAll(tags);
+  }
+
+  /// Adds the geometric information of `ancestor` to this object.
+  ///
+  /// Those information are required to properly compute the value for
+  /// [SemanticsNode.transform], [SemanticsNode.clipRect], and
+  /// [SemanticsNode.rect].
+  ///
+  /// Ancestors have to be added in order from [owner] up until the next
+  /// [RenderObject] that owns a [SemanticsNode] is reached.
+  void addAncestor(RenderObject ancestor) {
+    _ancestorChain.add(ancestor);
+  }
+}
+
+/// An [_InterestingSemanticsFragment] that produces the root [SemanticsNode] of
+/// the semantics tree.
+///
+/// The root node is available as the only element in the Iterable returned by
+/// [children].
+class _RootSemanticsFragment extends _InterestingSemanticsFragment {
+  _RootSemanticsFragment({
+    required RenderObject owner,
+    required bool dropsSemanticsOfPreviousSiblings,
+  }) : super(owner: owner, dropsSemanticsOfPreviousSiblings: dropsSemanticsOfPreviousSiblings);
+
+  @override
+  void compileChildren({ Rect? parentSemanticsClipRect, Rect? parentPaintClipRect, required double elevationAdjustment, required List<SemanticsNode> result }) {
+    assert(_tagsForChildren == null || _tagsForChildren!.isEmpty);
+    assert(parentSemanticsClipRect == null);
+    assert(parentPaintClipRect == null);
+    assert(_ancestorChain.length == 1);
+    assert(elevationAdjustment == 0.0);
+
+    owner._semantics ??= SemanticsNode.root(
+      showOnScreen: owner.showOnScreen,
+      owner: owner.owner!.semanticsOwner!,
+    );
+    final SemanticsNode node = owner._semantics!;
+    assert(MatrixUtils.matrixEquals(node.transform, Matrix4.identity()));
+    assert(node.parentSemanticsClipRect == null);
+    assert(node.parentPaintClipRect == null);
+
+    node.rect = owner.semanticBounds;
+
+    final List<SemanticsNode> children = <SemanticsNode>[];
+    for (final _InterestingSemanticsFragment fragment in _children) {
+      assert(fragment.config == null);
+      fragment.compileChildren(
+        parentSemanticsClipRect: parentSemanticsClipRect,
+        parentPaintClipRect: parentPaintClipRect,
+        elevationAdjustment: 0.0,
+        result: children,
+      );
+    }
+    node.updateWith(config: null, childrenInInversePaintOrder: children);
+
+    // The root node is the only semantics node allowed to be invisible. This
+    // can happen when the canvas the app is drawn on has a size of 0 by 0
+    // pixel. If this happens, the root node must not have any children (because
+    // these would be invisible as well and are therefore excluded from the
+    // tree).
+    assert(!node.isInvisible || children.isEmpty);
+    result.add(node);
+  }
+
+  @override
+  SemanticsConfiguration? get config => null;
+
+  final List<_InterestingSemanticsFragment> _children = <_InterestingSemanticsFragment>[];
+
+  @override
+  void markAsExplicit() {
+    // nothing to do, we are always explicit.
+  }
+
+  @override
+  void addAll(Iterable<_InterestingSemanticsFragment> fragments) {
+    _children.addAll(fragments);
+  }
+}
+
+/// An [_InterestingSemanticsFragment] that can be told to only add explicit
+/// [SemanticsNode]s to the parent.
+///
+/// If [markAsExplicit] was not called before this fragment is added to
+/// another fragment it will merge [config] into the parent's [SemanticsNode]
+/// and add its [children] to it.
+///
+/// If [markAsExplicit] was called before adding this fragment to another
+/// fragment it will create a new [SemanticsNode]. The newly created node will
+/// be annotated with the [SemanticsConfiguration] that - without the call to
+/// [markAsExplicit] - would have been merged into the parent's [SemanticsNode].
+/// Similarly, the new node will also take over the children that otherwise
+/// would have been added to the parent's [SemanticsNode].
+///
+/// After a call to [markAsExplicit] the only element returned by [children]
+/// is the newly created node and [config] will return null as the fragment
+/// no longer wants to merge any semantic information into the parent's
+/// [SemanticsNode].
+class _SwitchableSemanticsFragment extends _InterestingSemanticsFragment {
+  _SwitchableSemanticsFragment({
+    required bool mergeIntoParent,
+    required SemanticsConfiguration config,
+    required RenderObject owner,
+    required bool dropsSemanticsOfPreviousSiblings,
+  }) : _mergeIntoParent = mergeIntoParent,
+       _config = config,
+       assert(mergeIntoParent != null),
+       assert(config != null),
+       super(owner: owner, dropsSemanticsOfPreviousSiblings: dropsSemanticsOfPreviousSiblings);
+
+  final bool _mergeIntoParent;
+  SemanticsConfiguration _config;
+  bool _isConfigWritable = false;
+  final List<_InterestingSemanticsFragment> _children = <_InterestingSemanticsFragment>[];
+
+  @override
+  void compileChildren({ Rect? parentSemanticsClipRect, Rect? parentPaintClipRect, required double elevationAdjustment, required List<SemanticsNode> result }) {
+    if (!_isExplicit) {
+      owner._semantics = null;
+      for (final _InterestingSemanticsFragment fragment in _children) {
+        assert(_ancestorChain.first == fragment._ancestorChain.last);
+        fragment._ancestorChain.addAll(_ancestorChain.skip(1));
+        fragment.compileChildren(
+          parentSemanticsClipRect: parentSemanticsClipRect,
+          parentPaintClipRect: parentPaintClipRect,
+          // The fragment is not explicit, its elevation has been absorbed by
+          // the parent config (as thickness). We still need to make sure that
+          // its children are placed at the elevation dictated by this config.
+          elevationAdjustment: elevationAdjustment + _config.elevation,
+          result: result,
+        );
+      }
+      return;
+    }
+
+    final _SemanticsGeometry? geometry = _needsGeometryUpdate
+        ? _SemanticsGeometry(parentSemanticsClipRect: parentSemanticsClipRect, parentPaintClipRect: parentPaintClipRect, ancestors: _ancestorChain)
+        : null;
+
+    if (!_mergeIntoParent && (geometry?.dropFromTree == true))
+      return;  // Drop the node, it's not going to be visible.
+
+    owner._semantics ??= SemanticsNode(showOnScreen: owner.showOnScreen);
+    final SemanticsNode node = owner._semantics!
+      ..isMergedIntoParent = _mergeIntoParent
+      ..tags = _tagsForChildren;
+
+    node.elevationAdjustment = elevationAdjustment;
+    if (elevationAdjustment != 0.0) {
+      _ensureConfigIsWritable();
+      _config.elevation += elevationAdjustment;
+    }
+
+    if (geometry != null) {
+      assert(_needsGeometryUpdate);
+      node
+        ..rect = geometry.rect
+        ..transform = geometry.transform
+        ..parentSemanticsClipRect = geometry.semanticsClipRect
+        ..parentPaintClipRect = geometry.paintClipRect;
+      if (!_mergeIntoParent && geometry.markAsHidden) {
+        _ensureConfigIsWritable();
+        _config.isHidden = true;
+      }
+    }
+
+    final List<SemanticsNode> children = <SemanticsNode>[];
+    for (final _InterestingSemanticsFragment fragment in _children) {
+      fragment.compileChildren(
+        parentSemanticsClipRect: node.parentSemanticsClipRect,
+        parentPaintClipRect: node.parentPaintClipRect,
+        elevationAdjustment: 0.0,
+        result: children,
+      );
+    }
+    if (_config.isSemanticBoundary) {
+      owner.assembleSemanticsNode(node, _config, children);
+    } else {
+      node.updateWith(config: _config, childrenInInversePaintOrder: children);
+    }
+    result.add(node);
+  }
+
+  @override
+  SemanticsConfiguration? get config {
+    return _isExplicit ? null : _config;
+  }
+
+  @override
+  void addAll(Iterable<_InterestingSemanticsFragment> fragments) {
+    for (final _InterestingSemanticsFragment fragment in fragments) {
+      _children.add(fragment);
+      if (fragment.config == null)
+        continue;
+      _ensureConfigIsWritable();
+      _config.absorb(fragment.config!);
+    }
+  }
+
+  void _ensureConfigIsWritable() {
+    if (!_isConfigWritable) {
+      _config = _config.copy();
+      _isConfigWritable = true;
+    }
+  }
+
+  bool _isExplicit = false;
+
+  @override
+  void markAsExplicit() {
+    _isExplicit = true;
+  }
+
+  bool get _needsGeometryUpdate => _ancestorChain.length > 1;
+}
+
+
+/// [_SemanticsFragment] used to indicate that the current information in this
+/// subtree is not sufficient to update semantics.
+///
+/// Anybody processing this [_SemanticsFragment] should abort the walk of the
+/// current subtree without updating any [SemanticsNode]s as there is no semantic
+/// information to compute. As a result, this fragment also doesn't carry any
+/// semantics information either.
+class _AbortingSemanticsFragment extends _InterestingSemanticsFragment {
+  _AbortingSemanticsFragment({required RenderObject owner}) : super(owner: owner, dropsSemanticsOfPreviousSiblings: false);
+
+  @override
+  bool get abortsWalk => true;
+
+  @override
+  SemanticsConfiguration? get config => null;
+
+  @override
+  void addAll(Iterable<_InterestingSemanticsFragment> fragments) {
+    assert(false);
+  }
+
+  @override
+  void compileChildren({ Rect? parentSemanticsClipRect, Rect? parentPaintClipRect, required double elevationAdjustment, required List<SemanticsNode> result }) {
+    result.add(owner._semantics!);
+  }
+
+  @override
+  void markAsExplicit() {
+    // Is never explicit.
+  }
+}
+
+/// Helper class that keeps track of the geometry of a [SemanticsNode].
+///
+/// It is used to annotate a [SemanticsNode] with the current information for
+/// [SemanticsNode.rect] and [SemanticsNode.transform].
+class _SemanticsGeometry {
+
+  /// The `parentClippingRect` may be null if no clip is to be applied.
+  ///
+  /// The `ancestors` list has to include all [RenderObject] in order that are
+  /// located between the [SemanticsNode] whose geometry is represented here
+  /// (first [RenderObject] in the list) and its closest ancestor [RenderObject]
+  /// that also owns its own [SemanticsNode] (last [RenderObject] in the list).
+  _SemanticsGeometry({
+    required Rect? parentSemanticsClipRect,
+    required Rect? parentPaintClipRect,
+    required List<RenderObject> ancestors,
+  }) {
+    _computeValues(parentSemanticsClipRect, parentPaintClipRect, ancestors);
+  }
+
+  Rect? _paintClipRect;
+  Rect? _semanticsClipRect;
+  late Matrix4 _transform;
+  late Rect _rect;
+
+  /// Value for [SemanticsNode.transform].
+  Matrix4 get transform => _transform;
+
+  /// Value for [SemanticsNode.parentSemanticsClipRect].
+  Rect? get semanticsClipRect => _semanticsClipRect;
+
+  /// Value for [SemanticsNode.parentPaintClipRect].
+  Rect? get paintClipRect => _paintClipRect;
+
+  /// Value for [SemanticsNode.rect].
+  Rect get rect => _rect;
+
+  /// Computes values, ensuring `rect` is properly bounded by ancestor clipping rects.
+  ///
+  /// See also:
+  ///
+  /// * [RenderObject.describeSemanticsClip], typically used to determine `parentSemanticsClipRect`.
+  /// * [RenderObject.describeApproximatePaintClip], typically used to determine `parentPaintClipRect`.
+  void _computeValues(Rect? parentSemanticsClipRect, Rect? parentPaintClipRect, List<RenderObject> ancestors) {
+    assert(ancestors.length > 1);
+
+    _transform = Matrix4.identity();
+    _semanticsClipRect = parentSemanticsClipRect;
+    _paintClipRect = parentPaintClipRect;
+    for (int index = ancestors.length-1; index > 0; index -= 1) {
+      final RenderObject parent = ancestors[index];
+      final RenderObject child = ancestors[index-1];
+      final Rect? parentSemanticsClipRect = parent.describeSemanticsClip(child);
+      if (parentSemanticsClipRect != null) {
+        _semanticsClipRect = parentSemanticsClipRect;
+        _paintClipRect = _intersectRects(_paintClipRect, parent.describeApproximatePaintClip(child));
+      } else {
+        _semanticsClipRect = _intersectRects(_semanticsClipRect, parent.describeApproximatePaintClip(child));
+      }
+      _temporaryTransformHolder.setIdentity(); // clears data from previous call(s)
+      _applyIntermediatePaintTransforms(parent, child, _transform, _temporaryTransformHolder);
+      _semanticsClipRect = _transformRect(_semanticsClipRect, _temporaryTransformHolder);
+      _paintClipRect = _transformRect(_paintClipRect, _temporaryTransformHolder);
+    }
+
+    final RenderObject owner = ancestors.first;
+    _rect = _semanticsClipRect == null ? owner.semanticBounds : _semanticsClipRect!.intersect(owner.semanticBounds);
+    if (_paintClipRect != null) {
+      final Rect paintRect = _paintClipRect!.intersect(_rect);
+      _markAsHidden = paintRect.isEmpty && !_rect.isEmpty;
+      if (!_markAsHidden)
+        _rect = paintRect;
+    }
+  }
+
+  // A matrix used to store transient transform data.
+  //
+  // Reusing this matrix avoids allocating a new matrix every time a temporary
+  // matrix is needed.
+  //
+  // This instance should never be returned to the caller. Otherwise, the data
+  // stored in it will be overwritten unpredictably by subsequent reuses.
+  static final Matrix4 _temporaryTransformHolder = Matrix4.zero();
+
+  /// From parent to child coordinate system.
+  static Rect? _transformRect(Rect? rect, Matrix4 transform) {
+    assert(transform != null);
+    if (rect == null)
+      return null;
+    if (rect.isEmpty || transform.isZero())
+      return Rect.zero;
+    return MatrixUtils.inverseTransformRect(transform, rect);
+  }
+
+  // Calls applyPaintTransform on all of the render objects between [child] and
+  // [ancestor]. This method handles cases where the immediate semantic parent
+  // is not the immediate render object parent of the child.
+  //
+  // It will mutate both transform and clipRectTransform.
+  static void _applyIntermediatePaintTransforms(
+    RenderObject ancestor,
+    RenderObject child,
+    Matrix4 transform,
+    Matrix4 clipRectTransform,
+  ) {
+    assert(ancestor != null);
+    assert(child != null);
+    assert(transform != null);
+    assert(clipRectTransform != null);
+    assert(clipRectTransform.isIdentity());
+    RenderObject intermediateParent = child.parent! as RenderObject;
+    assert(intermediateParent != null);
+    while (intermediateParent != ancestor) {
+      intermediateParent.applyPaintTransform(child, transform);
+      intermediateParent = intermediateParent.parent! as RenderObject;
+      child = child.parent! as RenderObject;
+      assert(intermediateParent != null);
+    }
+    ancestor.applyPaintTransform(child, transform);
+    ancestor.applyPaintTransform(child, clipRectTransform);
+  }
+
+  static Rect? _intersectRects(Rect? a, Rect? b) {
+    if (a == null)
+      return b;
+    if (b == null)
+      return a;
+    return a.intersect(b);
+  }
+
+  /// Whether the [SemanticsNode] annotated with the geometric information tracked
+  /// by this object can be dropped from the semantics tree without losing
+  /// semantics information.
+  bool get dropFromTree {
+    return _rect.isEmpty;
+  }
+
+  /// Whether the [SemanticsNode] annotated with the geometric information
+  /// tracked by this object should be marked as hidden because it is not
+  /// visible on screen.
+  ///
+  /// Hidden elements should still be included in the tree to work around
+  /// platform limitations (e.g. accessibility scrolling on iOS).
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsFlag.isHidden] for the purpose of marking a node as hidden.
+  bool get markAsHidden => _markAsHidden;
+  bool _markAsHidden = false;
+}
+
+/// A class that creates [DiagnosticsNode] by wrapping [RenderObject.debugCreator].
+///
+/// Attach a [DiagnosticsDebugCreator] into [FlutterErrorDetails.informationCollector]
+/// when a [RenderObject.debugCreator] is available. This will lead to improved
+/// error message.
+class DiagnosticsDebugCreator extends DiagnosticsProperty<Object> {
+  /// Create a [DiagnosticsProperty] with its [value] initialized to input
+  /// [RenderObject.debugCreator].
+  DiagnosticsDebugCreator(Object value)
+    : assert(value != null),
+      super(
+        'debugCreator',
+        value,
+        level: DiagnosticLevel.hidden,
+      );
+}
diff --git a/lib/src/rendering/paragraph.dart b/lib/src/rendering/paragraph.dart
new file mode 100644
index 0000000..7fc541c
--- /dev/null
+++ b/lib/src/rendering/paragraph.dart
@@ -0,0 +1,1086 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+import 'dart:math' as math;
+import 'package:flute/ui.dart' as ui show Gradient, Shader, TextBox, PlaceholderAlignment, TextHeightBehavior;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/painting.dart';
+import 'package:flute/semantics.dart';
+import 'package:flute/services.dart';
+
+import 'package:vector_math/vector_math_64.dart';
+
+import 'box.dart';
+import 'debug.dart';
+import 'object.dart';
+
+/// How overflowing text should be handled.
+///
+/// A [TextOverflow] can be passed to [Text] and [RichText] via their
+/// [Text.overflow] and [RichText.overflow] properties respectively.
+enum TextOverflow {
+  /// Clip the overflowing text to fix its container.
+  clip,
+
+  /// Fade the overflowing text to transparent.
+  fade,
+
+  /// Use an ellipsis to indicate that the text has overflowed.
+  ellipsis,
+
+  /// Render overflowing text outside of its container.
+  visible,
+}
+
+const String _kEllipsis = '\u2026';
+
+/// Parent data for use with [RenderParagraph].
+class TextParentData extends ContainerBoxParentData<RenderBox> {
+  /// The scaling of the text.
+  double? scale;
+
+  @override
+  String toString() {
+    final List<String> values = <String>[
+      'offset=$offset',
+      if (scale != null) 'scale=$scale',
+      super.toString(),
+    ];
+    return values.join('; ');
+  }
+}
+
+/// Used by the [RenderParagraph] to map its rendering children to their
+/// corresponding semantics nodes.
+///
+/// The [RichText] uses this to tag the relation between its placeholder spans
+/// and their semantics nodes.
+@immutable
+class PlaceholderSpanIndexSemanticsTag extends SemanticsTag {
+  /// Creates a semantics tag with the input `index`.
+  ///
+  /// Different [PlaceholderSpanIndexSemanticsTag]s with the same `index` are
+  /// consider the same.
+  const PlaceholderSpanIndexSemanticsTag(this.index) : super('PlaceholderSpanIndexSemanticsTag($index)');
+
+  /// The index of this tag.
+  final int index;
+
+  @override
+  bool operator ==(Object other) {
+    return other is PlaceholderSpanIndexSemanticsTag
+        && other.index == index;
+  }
+
+  @override
+  int get hashCode => hashValues(PlaceholderSpanIndexSemanticsTag, index);
+}
+
+/// A render object that displays a paragraph of text.
+class RenderParagraph extends RenderBox
+    with ContainerRenderObjectMixin<RenderBox, TextParentData>,
+             RenderBoxContainerDefaultsMixin<RenderBox, TextParentData>,
+                  RelayoutWhenSystemFontsChangeMixin {
+  /// Creates a paragraph render object.
+  ///
+  /// The [text], [textAlign], [textDirection], [overflow], [softWrap], and
+  /// [textScaleFactor] arguments must not be null.
+  ///
+  /// The [maxLines] property may be null (and indeed defaults to null), but if
+  /// it is not null, it must be greater than zero.
+  RenderParagraph(InlineSpan text, {
+    TextAlign textAlign = TextAlign.start,
+    required TextDirection textDirection,
+    bool softWrap = true,
+    TextOverflow overflow = TextOverflow.clip,
+    double textScaleFactor = 1.0,
+    int? maxLines,
+    Locale? locale,
+    StrutStyle? strutStyle,
+    TextWidthBasis textWidthBasis = TextWidthBasis.parent,
+    ui.TextHeightBehavior? textHeightBehavior,
+    List<RenderBox>? children,
+  }) : assert(text != null),
+       assert(text.debugAssertIsValid()),
+       assert(textAlign != null),
+       assert(textDirection != null),
+       assert(softWrap != null),
+       assert(overflow != null),
+       assert(textScaleFactor != null),
+       assert(maxLines == null || maxLines > 0),
+       assert(textWidthBasis != null),
+       _softWrap = softWrap,
+       _overflow = overflow,
+       _textPainter = TextPainter(
+         text: text,
+         textAlign: textAlign,
+         textDirection: textDirection,
+         textScaleFactor: textScaleFactor,
+         maxLines: maxLines,
+         ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null,
+         locale: locale,
+         strutStyle: strutStyle,
+         textWidthBasis: textWidthBasis,
+         textHeightBehavior: textHeightBehavior
+       ) {
+    addAll(children);
+    _extractPlaceholderSpans(text);
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! TextParentData)
+      child.parentData = TextParentData();
+  }
+
+  final TextPainter _textPainter;
+
+  /// The text to display.
+  InlineSpan get text => _textPainter.text!;
+  set text(InlineSpan value) {
+    assert(value != null);
+    switch (_textPainter.text!.compareTo(value)) {
+      case RenderComparison.identical:
+      case RenderComparison.metadata:
+        return;
+      case RenderComparison.paint:
+        _textPainter.text = value;
+        _extractPlaceholderSpans(value);
+        markNeedsPaint();
+        markNeedsSemanticsUpdate();
+        break;
+      case RenderComparison.layout:
+        _textPainter.text = value;
+        _overflowShader = null;
+        _extractPlaceholderSpans(value);
+        markNeedsLayout();
+        break;
+    }
+  }
+
+  late List<PlaceholderSpan> _placeholderSpans;
+  void _extractPlaceholderSpans(InlineSpan span) {
+    _placeholderSpans = <PlaceholderSpan>[];
+    span.visitChildren((InlineSpan span) {
+      if (span is PlaceholderSpan) {
+        _placeholderSpans.add(span);
+      }
+      return true;
+    });
+  }
+
+  /// How the text should be aligned horizontally.
+  TextAlign get textAlign => _textPainter.textAlign;
+  set textAlign(TextAlign value) {
+    assert(value != null);
+    if (_textPainter.textAlign == value)
+      return;
+    _textPainter.textAlign = value;
+    markNeedsPaint();
+  }
+
+  /// The directionality of the text.
+  ///
+  /// This decides how the [TextAlign.start], [TextAlign.end], and
+  /// [TextAlign.justify] values of [textAlign] are interpreted.
+  ///
+  /// This is also used to disambiguate how to render bidirectional text. For
+  /// example, if the [text] is an English phrase followed by a Hebrew phrase,
+  /// in a [TextDirection.ltr] context the English phrase will be on the left
+  /// and the Hebrew phrase to its right, while in a [TextDirection.rtl]
+  /// context, the English phrase will be on the right and the Hebrew phrase on
+  /// its left.
+  ///
+  /// This must not be null.
+  TextDirection get textDirection => _textPainter.textDirection!;
+  set textDirection(TextDirection value) {
+    assert(value != null);
+    if (_textPainter.textDirection == value)
+      return;
+    _textPainter.textDirection = value;
+    markNeedsLayout();
+  }
+
+  /// Whether the text should break at soft line breaks.
+  ///
+  /// If false, the glyphs in the text will be positioned as if there was
+  /// unlimited horizontal space.
+  ///
+  /// If [softWrap] is false, [overflow] and [textAlign] may have unexpected
+  /// effects.
+  bool get softWrap => _softWrap;
+  bool _softWrap;
+  set softWrap(bool value) {
+    assert(value != null);
+    if (_softWrap == value)
+      return;
+    _softWrap = value;
+    markNeedsLayout();
+  }
+
+  /// How visual overflow should be handled.
+  TextOverflow get overflow => _overflow;
+  TextOverflow _overflow;
+  set overflow(TextOverflow value) {
+    assert(value != null);
+    if (_overflow == value)
+      return;
+    _overflow = value;
+    _textPainter.ellipsis = value == TextOverflow.ellipsis ? _kEllipsis : null;
+    markNeedsLayout();
+  }
+
+  /// The number of font pixels for each logical pixel.
+  ///
+  /// For example, if the text scale factor is 1.5, text will be 50% larger than
+  /// the specified font size.
+  double get textScaleFactor => _textPainter.textScaleFactor;
+  set textScaleFactor(double value) {
+    assert(value != null);
+    if (_textPainter.textScaleFactor == value)
+      return;
+    _textPainter.textScaleFactor = value;
+    _overflowShader = null;
+    markNeedsLayout();
+  }
+
+  /// An optional maximum number of lines for the text to span, wrapping if
+  /// necessary. If the text exceeds the given number of lines, it will be
+  /// truncated according to [overflow] and [softWrap].
+  int? get maxLines => _textPainter.maxLines;
+  /// The value may be null. If it is not null, then it must be greater than
+  /// zero.
+  set maxLines(int? value) {
+    assert(value == null || value > 0);
+    if (_textPainter.maxLines == value)
+      return;
+    _textPainter.maxLines = value;
+    _overflowShader = null;
+    markNeedsLayout();
+  }
+
+  /// Used by this paragraph's internal [TextPainter] to select a
+  /// locale-specific font.
+  ///
+  /// In some cases the same Unicode character may be rendered differently
+  /// depending
+  /// on the locale. For example the '骨' character is rendered differently in
+  /// the Chinese and Japanese locales. In these cases the [locale] may be used
+  /// to select a locale-specific font.
+  Locale? get locale => _textPainter.locale;
+  /// The value may be null.
+  set locale(Locale? value) {
+    if (_textPainter.locale == value)
+      return;
+    _textPainter.locale = value;
+    _overflowShader = null;
+    markNeedsLayout();
+  }
+
+  /// {@macro flutter.painting.textPainter.strutStyle}
+  StrutStyle? get strutStyle => _textPainter.strutStyle;
+  /// The value may be null.
+  set strutStyle(StrutStyle? value) {
+    if (_textPainter.strutStyle == value)
+      return;
+    _textPainter.strutStyle = value;
+    _overflowShader = null;
+    markNeedsLayout();
+  }
+
+  /// {@macro flutter.painting.textPainter.textWidthBasis}
+  TextWidthBasis get textWidthBasis => _textPainter.textWidthBasis;
+  set textWidthBasis(TextWidthBasis value) {
+    assert(value != null);
+    if (_textPainter.textWidthBasis == value)
+      return;
+    _textPainter.textWidthBasis = value;
+    _overflowShader = null;
+    markNeedsLayout();
+  }
+
+  /// {@macro flutter.dart:ui.textHeightBehavior}
+  ui.TextHeightBehavior? get textHeightBehavior => _textPainter.textHeightBehavior;
+  set textHeightBehavior(ui.TextHeightBehavior? value) {
+    if (_textPainter.textHeightBehavior == value)
+      return;
+    _textPainter.textHeightBehavior = value;
+    _overflowShader = null;
+    markNeedsLayout();
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    if (!_canComputeIntrinsics()) {
+      return 0.0;
+    }
+    _computeChildrenWidthWithMinIntrinsics(height);
+    _layoutText(); // layout with infinite width.
+    return _textPainter.minIntrinsicWidth;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    if (!_canComputeIntrinsics()) {
+      return 0.0;
+    }
+    _computeChildrenWidthWithMaxIntrinsics(height);
+    _layoutText(); // layout with infinite width.
+    return _textPainter.maxIntrinsicWidth;
+  }
+
+  double _computeIntrinsicHeight(double width) {
+    if (!_canComputeIntrinsics()) {
+      return 0.0;
+    }
+    _computeChildrenHeightWithMinIntrinsics(width);
+    _layoutText(minWidth: width, maxWidth: width);
+    return _textPainter.height;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    return _computeIntrinsicHeight(width);
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    return _computeIntrinsicHeight(width);
+  }
+
+  @override
+  double computeDistanceToActualBaseline(TextBaseline baseline) {
+    assert(!debugNeedsLayout);
+    assert(constraints != null);
+    assert(constraints.debugAssertIsValid());
+    _layoutTextWithConstraints(constraints);
+    // TODO(garyq): Since our metric for ideographic baseline is currently
+    // inaccurate and the non-alphabetic baselines are based off of the
+    // alphabetic baseline, we use the alphabetic for now to produce correct
+    // layouts. We should eventually change this back to pass the `baseline`
+    // property when the ideographic baseline is properly implemented
+    // (https://github.com/flutter/flutter/issues/22625).
+    return _textPainter.computeDistanceToActualBaseline(TextBaseline.alphabetic);
+  }
+
+  // Intrinsics cannot be calculated without a full layout for
+  // alignments that require the baseline (baseline, aboveBaseline,
+  // belowBaseline).
+  bool _canComputeIntrinsics() {
+    for (final PlaceholderSpan span in _placeholderSpans) {
+      switch (span.alignment) {
+        case ui.PlaceholderAlignment.baseline:
+        case ui.PlaceholderAlignment.aboveBaseline:
+        case ui.PlaceholderAlignment.belowBaseline: {
+          assert(RenderObject.debugCheckingIntrinsics,
+            'Intrinsics are not available for PlaceholderAlignment.baseline, '
+            'PlaceholderAlignment.aboveBaseline, or PlaceholderAlignment.belowBaseline,');
+          return false;
+        }
+        case ui.PlaceholderAlignment.top:
+        case ui.PlaceholderAlignment.middle:
+        case ui.PlaceholderAlignment.bottom: {
+          continue;
+        }
+      }
+    }
+    return true;
+  }
+
+  void _computeChildrenWidthWithMaxIntrinsics(double height) {
+    RenderBox? child = firstChild;
+    final List<PlaceholderDimensions> placeholderDimensions = List<PlaceholderDimensions>.filled(childCount, PlaceholderDimensions.empty, growable: false);
+    int childIndex = 0;
+    while (child != null) {
+      // Height and baseline is irrelevant as all text will be laid
+      // out in a single line. Therefore, using 0.0 as a dummy for the height.
+      placeholderDimensions[childIndex] = PlaceholderDimensions(
+        size: Size(child.getMaxIntrinsicWidth(double.infinity), 0.0),
+        alignment: _placeholderSpans[childIndex].alignment,
+        baseline: _placeholderSpans[childIndex].baseline,
+      );
+      child = childAfter(child);
+      childIndex += 1;
+    }
+    _textPainter.setPlaceholderDimensions(placeholderDimensions);
+  }
+
+  void _computeChildrenWidthWithMinIntrinsics(double height) {
+    RenderBox? child = firstChild;
+    final List<PlaceholderDimensions> placeholderDimensions = List<PlaceholderDimensions>.filled(childCount, PlaceholderDimensions.empty, growable: false);
+    int childIndex = 0;
+    while (child != null) {
+      // Height and baseline is irrelevant; only looking for the widest word or
+      // placeholder. Therefore, using 0.0 as a dummy for height.
+      placeholderDimensions[childIndex] = PlaceholderDimensions(
+        size: Size(child.getMinIntrinsicWidth(double.infinity), 0.0),
+        alignment: _placeholderSpans[childIndex].alignment,
+        baseline: _placeholderSpans[childIndex].baseline,
+      );
+      child = childAfter(child);
+      childIndex += 1;
+    }
+    _textPainter.setPlaceholderDimensions(placeholderDimensions);
+  }
+
+  void _computeChildrenHeightWithMinIntrinsics(double width) {
+    RenderBox? child = firstChild;
+    final List<PlaceholderDimensions> placeholderDimensions = List<PlaceholderDimensions>.filled(childCount, PlaceholderDimensions.empty, growable: false);
+    int childIndex = 0;
+    // Takes textScaleFactor into account because the content of the placeholder
+    // span will be scale up when it paints.
+    width = width / textScaleFactor;
+    while (child != null) {
+      final Size size = child.getDryLayout(BoxConstraints(maxWidth: width));
+      placeholderDimensions[childIndex] = PlaceholderDimensions(
+        size: size,
+        alignment: _placeholderSpans[childIndex].alignment,
+        baseline: _placeholderSpans[childIndex].baseline,
+      );
+      child = childAfter(child);
+      childIndex += 1;
+    }
+    _textPainter.setPlaceholderDimensions(placeholderDimensions);
+  }
+
+  @override
+  bool hitTestSelf(Offset position) => true;
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    RenderBox? child = firstChild;
+    int childIndex = 0;
+    while (child != null && childIndex < _textPainter.inlinePlaceholderBoxes!.length) {
+      final TextParentData textParentData = child.parentData! as TextParentData;
+      final Matrix4 transform = Matrix4.translationValues(
+        textParentData.offset.dx,
+        textParentData.offset.dy,
+        0.0,
+      )..scale(
+        textParentData.scale,
+        textParentData.scale,
+        textParentData.scale,
+      );
+      final bool isHit = result.addWithPaintTransform(
+        transform: transform,
+        position: position,
+        hitTest: (BoxHitTestResult result, Offset? transformed) {
+          assert(() {
+            final Offset manualPosition = (position - textParentData.offset) / textParentData.scale!;
+            return (transformed!.dx - manualPosition.dx).abs() < precisionErrorTolerance
+              && (transformed.dy - manualPosition.dy).abs() < precisionErrorTolerance;
+          }());
+          return child!.hitTest(result, position: transformed!);
+        },
+      );
+      if (isHit) {
+        return true;
+      }
+      child = childAfter(child);
+      childIndex += 1;
+    }
+    return false;
+  }
+
+  @override
+  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
+    assert(debugHandleEvent(event, entry));
+    if (event is! PointerDownEvent)
+      return;
+    _layoutTextWithConstraints(constraints);
+    final Offset offset = entry.localPosition;
+    final TextPosition position = _textPainter.getPositionForOffset(offset);
+    final InlineSpan? span = _textPainter.text!.getSpanForPosition(position);
+    if (span == null) {
+      return;
+    }
+    if (span is TextSpan) {
+      span.recognizer?.addPointer(event);
+    }
+  }
+
+  bool _needsClipping = false;
+  ui.Shader? _overflowShader;
+
+  /// Whether this paragraph currently has a [dart:ui.Shader] for its overflow
+  /// effect.
+  ///
+  /// Used to test this object. Not for use in production.
+  @visibleForTesting
+  bool get debugHasOverflowShader => _overflowShader != null;
+
+  void _layoutText({ double minWidth = 0.0, double maxWidth = double.infinity }) {
+    final bool widthMatters = softWrap || overflow == TextOverflow.ellipsis;
+    _textPainter.layout(
+      minWidth: minWidth,
+      maxWidth: widthMatters ?
+        maxWidth :
+        double.infinity,
+    );
+  }
+
+  @override
+  void systemFontsDidChange() {
+    super.systemFontsDidChange();
+    _textPainter.markNeedsLayout();
+  }
+
+  // Placeholder dimensions representing the sizes of child inline widgets.
+  //
+  // These need to be cached because the text painter's placeholder dimensions
+  // will be overwritten during intrinsic width/height calculations and must be
+  // restored to the original values before final layout and painting.
+  List<PlaceholderDimensions>? _placeholderDimensions;
+
+  void _layoutTextWithConstraints(BoxConstraints constraints) {
+    _textPainter.setPlaceholderDimensions(_placeholderDimensions);
+    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
+  }
+
+  // Layout the child inline widgets. We then pass the dimensions of the
+  // children to _textPainter so that appropriate placeholders can be inserted
+  // into the LibTxt layout. This does not do anything if no inline widgets were
+  // specified.
+  List<PlaceholderDimensions> _layoutChildren(BoxConstraints constraints, {bool dry = false}) {
+    if (childCount == 0) {
+      return <PlaceholderDimensions>[];
+    }
+    RenderBox? child = firstChild;
+    final List<PlaceholderDimensions> placeholderDimensions = List<PlaceholderDimensions>.filled(childCount, PlaceholderDimensions.empty, growable: false);
+    int childIndex = 0;
+    // Only constrain the width to the maximum width of the paragraph.
+    // Leave height unconstrained, which will overflow if expanded past.
+    BoxConstraints boxConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
+    // The content will be enlarged by textScaleFactor during painting phase.
+    // We reduce constraint by textScaleFactor so that the content will fit
+    // into the box once it is enlarged.
+    boxConstraints = boxConstraints / textScaleFactor;
+    while (child != null) {
+      double? baselineOffset;
+      final Size childSize;
+      if (!dry) {
+        child.layout(
+          boxConstraints,
+          parentUsesSize: true,
+        );
+        childSize = child.size;
+        switch (_placeholderSpans[childIndex].alignment) {
+          case ui.PlaceholderAlignment.baseline: {
+            baselineOffset = child.getDistanceToBaseline(
+              _placeholderSpans[childIndex].baseline!
+            );
+            break;
+          }
+          default: {
+            baselineOffset = null;
+            break;
+          }
+        }
+      } else {
+        assert(_placeholderSpans[childIndex].alignment != ui.PlaceholderAlignment.baseline);
+        childSize = child.getDryLayout(boxConstraints);
+      }
+      placeholderDimensions[childIndex] = PlaceholderDimensions(
+        size: childSize,
+        alignment: _placeholderSpans[childIndex].alignment,
+        baseline: _placeholderSpans[childIndex].baseline,
+        baselineOffset: baselineOffset,
+      );
+      child = childAfter(child);
+      childIndex += 1;
+    }
+    return placeholderDimensions;
+  }
+
+  // Iterate through the laid-out children and set the parentData offsets based
+  // off of the placeholders inserted for each child.
+  void _setParentData() {
+    RenderBox? child = firstChild;
+    int childIndex = 0;
+    while (child != null && childIndex < _textPainter.inlinePlaceholderBoxes!.length) {
+      final TextParentData textParentData = child.parentData! as TextParentData;
+      textParentData.offset = Offset(
+        _textPainter.inlinePlaceholderBoxes![childIndex].left,
+        _textPainter.inlinePlaceholderBoxes![childIndex].top,
+      );
+      textParentData.scale = _textPainter.inlinePlaceholderScales![childIndex];
+      child = childAfter(child);
+      childIndex += 1;
+    }
+  }
+
+  bool _canComputeDryLayout() {
+    // Dry layout cannot be calculated without a full layout for
+    // alignments that require the baseline (baseline, aboveBaseline,
+    // belowBaseline).
+    for (final PlaceholderSpan span in _placeholderSpans) {
+      switch (span.alignment) {
+        case ui.PlaceholderAlignment.baseline:
+        case ui.PlaceholderAlignment.aboveBaseline:
+        case ui.PlaceholderAlignment.belowBaseline: {
+          return false;
+        }
+        case ui.PlaceholderAlignment.top:
+        case ui.PlaceholderAlignment.middle:
+        case ui.PlaceholderAlignment.bottom: {
+          continue;
+        }
+      }
+    }
+    return true;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (!_canComputeDryLayout()) {
+      assert(debugCannotComputeDryLayout(
+        reason: 'Dry layout not available for alignments that require baseline.',
+      ));
+      return const Size(0, 0);
+    }
+    _textPainter.setPlaceholderDimensions(_layoutChildren(constraints, dry: true));
+    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
+    return constraints.constrain(_textPainter.size);
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    _placeholderDimensions = _layoutChildren(constraints);
+    _layoutTextWithConstraints(constraints);
+    _setParentData();
+
+    // We grab _textPainter.size and _textPainter.didExceedMaxLines here because
+    // assigning to `size` will trigger us to validate our intrinsic sizes,
+    // which will change _textPainter's layout because the intrinsic size
+    // calculations are destructive. Other _textPainter state will also be
+    // affected. See also RenderEditable which has a similar issue.
+    final Size textSize = _textPainter.size;
+    final bool textDidExceedMaxLines = _textPainter.didExceedMaxLines;
+    size = constraints.constrain(textSize);
+
+    final bool didOverflowHeight = size.height < textSize.height || textDidExceedMaxLines;
+    final bool didOverflowWidth = size.width < textSize.width;
+    // TODO(abarth): We're only measuring the sizes of the line boxes here. If
+    // the glyphs draw outside the line boxes, we might think that there isn't
+    // visual overflow when there actually is visual overflow. This can become
+    // a problem if we start having horizontal overflow and introduce a clip
+    // that affects the actual (but undetected) vertical overflow.
+    final bool hasVisualOverflow = didOverflowWidth || didOverflowHeight;
+    if (hasVisualOverflow) {
+      switch (_overflow) {
+        case TextOverflow.visible:
+          _needsClipping = false;
+          _overflowShader = null;
+          break;
+        case TextOverflow.clip:
+        case TextOverflow.ellipsis:
+          _needsClipping = true;
+          _overflowShader = null;
+          break;
+        case TextOverflow.fade:
+          assert(textDirection != null);
+          _needsClipping = true;
+          final TextPainter fadeSizePainter = TextPainter(
+            text: TextSpan(style: _textPainter.text!.style, text: '\u2026'),
+            textDirection: textDirection,
+            textScaleFactor: textScaleFactor,
+            locale: locale,
+          )..layout();
+          if (didOverflowWidth) {
+            double fadeEnd, fadeStart;
+            switch (textDirection) {
+              case TextDirection.rtl:
+                fadeEnd = 0.0;
+                fadeStart = fadeSizePainter.width;
+                break;
+              case TextDirection.ltr:
+                fadeEnd = size.width;
+                fadeStart = fadeEnd - fadeSizePainter.width;
+                break;
+            }
+            _overflowShader = ui.Gradient.linear(
+              Offset(fadeStart, 0.0),
+              Offset(fadeEnd, 0.0),
+              <Color>[const Color(0xFFFFFFFF), const Color(0x00FFFFFF)],
+            );
+          } else {
+            final double fadeEnd = size.height;
+            final double fadeStart = fadeEnd - fadeSizePainter.height / 2.0;
+            _overflowShader = ui.Gradient.linear(
+              Offset(0.0, fadeStart),
+              Offset(0.0, fadeEnd),
+              <Color>[const Color(0xFFFFFFFF), const Color(0x00FFFFFF)],
+            );
+          }
+          break;
+      }
+    } else {
+      _needsClipping = false;
+      _overflowShader = null;
+    }
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    // Ideally we could compute the min/max intrinsic width/height with a
+    // non-destructive operation. However, currently, computing these values
+    // will destroy state inside the painter. If that happens, we need to get
+    // back the correct state by calling _layout again.
+    //
+    // TODO(abarth): Make computing the min/max intrinsic width/height a
+    //  non-destructive operation.
+    //
+    // If you remove this call, make sure that changing the textAlign still
+    // works properly.
+    _layoutTextWithConstraints(constraints);
+
+    assert(() {
+      if (debugRepaintTextRainbowEnabled) {
+        final Paint paint = Paint()
+          ..color = debugCurrentRepaintColor.toColor();
+        context.canvas.drawRect(offset & size, paint);
+      }
+      return true;
+    }());
+
+    if (_needsClipping) {
+      final Rect bounds = offset & size;
+      if (_overflowShader != null) {
+        // This layer limits what the shader below blends with to be just the
+        // text (as opposed to the text and its background).
+        context.canvas.saveLayer(bounds, Paint());
+      } else {
+        context.canvas.save();
+      }
+      context.canvas.clipRect(bounds);
+    }
+    _textPainter.paint(context.canvas, offset);
+
+    RenderBox? child = firstChild;
+    int childIndex = 0;
+    // childIndex might be out of index of placeholder boxes. This can happen
+    // if engine truncates children due to ellipsis. Sadly, we would not know
+    // it until we finish layout, and RenderObject is in immutable state at
+    // this point.
+    while (child != null && childIndex < _textPainter.inlinePlaceholderBoxes!.length) {
+      final TextParentData textParentData = child.parentData! as TextParentData;
+
+      final double scale = textParentData.scale!;
+      context.pushTransform(
+        needsCompositing,
+        offset + textParentData.offset,
+        Matrix4.diagonal3Values(scale, scale, scale),
+        (PaintingContext context, Offset offset) {
+          context.paintChild(
+            child!,
+            offset,
+          );
+        },
+      );
+      child = childAfter(child);
+      childIndex += 1;
+    }
+    if (_needsClipping) {
+      if (_overflowShader != null) {
+        context.canvas.translate(offset.dx, offset.dy);
+        final Paint paint = Paint()
+          ..blendMode = BlendMode.modulate
+          ..shader = _overflowShader;
+        context.canvas.drawRect(Offset.zero & size, paint);
+      }
+      context.canvas.restore();
+    }
+  }
+
+  /// Returns the offset at which to paint the caret.
+  ///
+  /// Valid only after [layout].
+  Offset getOffsetForCaret(TextPosition position, Rect caretPrototype) {
+    assert(!debugNeedsLayout);
+    _layoutTextWithConstraints(constraints);
+    return _textPainter.getOffsetForCaret(position, caretPrototype);
+  }
+
+  /// {@macro flutter.painting.textPainter.getFullHeightForCaret}
+  ///
+  /// Valid only after [layout].
+  double? getFullHeightForCaret(TextPosition position) {
+    assert(!debugNeedsLayout);
+    _layoutTextWithConstraints(constraints);
+    return _textPainter.getFullHeightForCaret(position, Rect.zero);
+  }
+
+  /// Returns a list of rects that bound the given selection.
+  ///
+  /// A given selection might have more than one rect if this text painter
+  /// contains bidirectional text because logically contiguous text might not be
+  /// visually contiguous.
+  ///
+  /// Valid only after [layout].
+  List<ui.TextBox> getBoxesForSelection(TextSelection selection) {
+    assert(!debugNeedsLayout);
+    _layoutTextWithConstraints(constraints);
+    return _textPainter.getBoxesForSelection(selection);
+  }
+
+  /// Returns the position within the text for the given pixel offset.
+  ///
+  /// Valid only after [layout].
+  TextPosition getPositionForOffset(Offset offset) {
+    assert(!debugNeedsLayout);
+    _layoutTextWithConstraints(constraints);
+    return _textPainter.getPositionForOffset(offset);
+  }
+
+  /// Returns the text range of the word at the given offset. Characters not
+  /// part of a word, such as spaces, symbols, and punctuation, have word breaks
+  /// on both sides. In such cases, this method will return a text range that
+  /// contains the given text position.
+  ///
+  /// Word boundaries are defined more precisely in Unicode Standard Annex #29
+  /// <http://www.unicode.org/reports/tr29/#Word_Boundaries>.
+  ///
+  /// Valid only after [layout].
+  TextRange getWordBoundary(TextPosition position) {
+    assert(!debugNeedsLayout);
+    _layoutTextWithConstraints(constraints);
+    return _textPainter.getWordBoundary(position);
+  }
+
+  /// Returns the size of the text as laid out.
+  ///
+  /// This can differ from [size] if the text overflowed or if the [constraints]
+  /// provided by the parent [RenderObject] forced the layout to be bigger than
+  /// necessary for the given [text].
+  ///
+  /// This returns the [TextPainter.size] of the underlying [TextPainter].
+  ///
+  /// Valid only after [layout].
+  Size get textSize {
+    assert(!debugNeedsLayout);
+    return _textPainter.size;
+  }
+
+  /// Collected during [describeSemanticsConfiguration], used by
+  /// [assembleSemanticsNode] and [_combineSemanticsInfo].
+  List<InlineSpanSemanticsInformation>? _semanticsInfo;
+
+  /// Combines _semanticsInfo entries where permissible, determined by
+  /// [InlineSpanSemanticsInformation.requiresOwnNode].
+  List<InlineSpanSemanticsInformation> _combineSemanticsInfo() {
+    assert(_semanticsInfo != null);
+    final List<InlineSpanSemanticsInformation> combined = <InlineSpanSemanticsInformation>[];
+    String workingText = '';
+    // TODO(ianh): this algorithm is internally inconsistent. workingText
+    // never becomes null, but we check for it being so below.
+    String? workingLabel;
+    for (final InlineSpanSemanticsInformation info in _semanticsInfo!) {
+      if (info.requiresOwnNode) {
+        combined.add(InlineSpanSemanticsInformation(
+          workingText,
+          semanticsLabel: workingLabel ?? workingText,
+        ));
+        workingText = '';
+        workingLabel = null;
+        combined.add(info);
+      } else {
+        workingText += info.text;
+        workingLabel ??= '';
+        if (info.semanticsLabel != null) {
+          workingLabel += info.semanticsLabel!;
+        } else {
+          workingLabel += info.text;
+        }
+      }
+    }
+    combined.add(InlineSpanSemanticsInformation(
+      workingText,
+      semanticsLabel: workingLabel,
+    ));
+    return combined;
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    _semanticsInfo = text.getSemanticsInformation();
+
+    if (_semanticsInfo!.any((InlineSpanSemanticsInformation info) => info.recognizer != null)) {
+      config.explicitChildNodes = true;
+      config.isSemanticBoundary = true;
+    } else {
+      final StringBuffer buffer = StringBuffer();
+      for (final InlineSpanSemanticsInformation info in _semanticsInfo!) {
+        buffer.write(info.semanticsLabel ?? info.text);
+      }
+      config.label = buffer.toString();
+      config.textDirection = textDirection;
+    }
+  }
+
+  // Caches [SemanticsNode]s created during [assembleSemanticsNode] so they
+  // can be re-used when [assembleSemanticsNode] is called again. This ensures
+  // stable ids for the [SemanticsNode]s of [TextSpan]s across
+  // [assembleSemanticsNode] invocations.
+  Queue<SemanticsNode>? _cachedChildNodes;
+
+  @override
+  void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, Iterable<SemanticsNode> children) {
+    assert(_semanticsInfo != null && _semanticsInfo!.isNotEmpty);
+    final List<SemanticsNode> newChildren = <SemanticsNode>[];
+    TextDirection currentDirection = textDirection;
+    Rect currentRect;
+    double ordinal = 0.0;
+    int start = 0;
+    int placeholderIndex = 0;
+    int childIndex = 0;
+    RenderBox? child = firstChild;
+    final Queue<SemanticsNode> newChildCache = Queue<SemanticsNode>();
+    for (final InlineSpanSemanticsInformation info in _combineSemanticsInfo()) {
+      final TextSelection selection = TextSelection(
+        baseOffset: start,
+        extentOffset: start + info.text.length,
+      );
+      start += info.text.length;
+
+      if (info.isPlaceholder) {
+        // A placeholder span may have 0 to multple semantics nodes, we need
+        // to annotate all of the semantics nodes belong to this span.
+        while (children.length > childIndex &&
+               children.elementAt(childIndex).isTagged(PlaceholderSpanIndexSemanticsTag(placeholderIndex))) {
+          final SemanticsNode childNode = children.elementAt(childIndex);
+          final TextParentData parentData = child!.parentData! as TextParentData;
+          childNode.rect = Rect.fromLTWH(
+            childNode.rect.left,
+            childNode.rect.top,
+            childNode.rect.width * parentData.scale!,
+            childNode.rect.height * parentData.scale!,
+          );
+          newChildren.add(childNode);
+          childIndex += 1;
+        }
+        child = childAfter(child!);
+        placeholderIndex += 1;
+      } else {
+        final TextDirection initialDirection = currentDirection;
+        final List<ui.TextBox> rects = getBoxesForSelection(selection);
+        if (rects.isEmpty) {
+          continue;
+        }
+        Rect rect = rects.first.toRect();
+        currentDirection = rects.first.direction;
+        for (final ui.TextBox textBox in rects.skip(1)) {
+          rect = rect.expandToInclude(textBox.toRect());
+          currentDirection = textBox.direction;
+        }
+        // Any of the text boxes may have had infinite dimensions.
+        // We shouldn't pass infinite dimensions up to the bridges.
+        rect = Rect.fromLTWH(
+          math.max(0.0, rect.left),
+          math.max(0.0, rect.top),
+          math.min(rect.width, constraints.maxWidth),
+          math.min(rect.height, constraints.maxHeight),
+        );
+        // round the current rectangle to make this API testable and add some
+        // padding so that the accessibility rects do not overlap with the text.
+        currentRect = Rect.fromLTRB(
+          rect.left.floorToDouble() - 4.0,
+          rect.top.floorToDouble() - 4.0,
+          rect.right.ceilToDouble() + 4.0,
+          rect.bottom.ceilToDouble() + 4.0,
+        );
+        final SemanticsConfiguration configuration = SemanticsConfiguration()
+          ..sortKey = OrdinalSortKey(ordinal++)
+          ..textDirection = initialDirection
+          ..label = info.semanticsLabel ?? info.text;
+        final GestureRecognizer? recognizer = info.recognizer;
+        if (recognizer != null) {
+          if (recognizer is TapGestureRecognizer) {
+            if (recognizer.onTap != null) {
+              configuration.onTap = recognizer.onTap;
+              configuration.isLink = true;
+            }
+          } else if (recognizer is DoubleTapGestureRecognizer) {
+            if (recognizer.onDoubleTap != null) {
+              configuration.onTap = recognizer.onDoubleTap;
+              configuration.isLink = true;
+            }
+          } else if (recognizer is LongPressGestureRecognizer) {
+            if (recognizer.onLongPress != null) {
+              configuration.onLongPress = recognizer.onLongPress;
+            }
+          } else {
+            assert(false, '${recognizer.runtimeType} is not supported.');
+          }
+        }
+        final SemanticsNode newChild = (_cachedChildNodes?.isNotEmpty == true)
+            ? _cachedChildNodes!.removeFirst()
+            : SemanticsNode();
+        newChild
+          ..updateWith(config: configuration)
+          ..rect = currentRect;
+        newChildCache.addLast(newChild);
+        newChildren.add(newChild);
+      }
+    }
+    // Makes sure we annotated all of the semantics children.
+    assert(childIndex == children.length);
+    assert(child == null);
+
+    _cachedChildNodes = newChildCache;
+    node.updateWith(config: config, childrenInInversePaintOrder: newChildren);
+  }
+
+  @override
+  void clearSemantics() {
+    super.clearSemantics();
+    _cachedChildNodes = null;
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    return <DiagnosticsNode>[
+      text.toDiagnosticsNode(
+        name: 'text',
+        style: DiagnosticsTreeStyle.transition,
+      )
+    ];
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<TextAlign>('textAlign', textAlign));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection));
+    properties.add(
+      FlagProperty(
+        'softWrap',
+        value: softWrap,
+        ifTrue: 'wrapping at box width',
+        ifFalse: 'no wrapping except at line break characters',
+        showName: true,
+      )
+    );
+    properties.add(EnumProperty<TextOverflow>('overflow', overflow));
+    properties.add(
+      DoubleProperty(
+        'textScaleFactor',
+        textScaleFactor,
+        defaultValue: 1.0,
+      )
+    );
+    properties.add(
+      DiagnosticsProperty<Locale>(
+        'locale',
+        locale,
+        defaultValue: null,
+      )
+    );
+    properties.add(IntProperty('maxLines', maxLines, ifNull: 'unlimited'));
+  }
+}
diff --git a/lib/src/rendering/performance_overlay.dart b/lib/src/rendering/performance_overlay.dart
new file mode 100644
index 0000000..a329399
--- /dev/null
+++ b/lib/src/rendering/performance_overlay.dart
@@ -0,0 +1,180 @@
+// Copyright 2014 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.
+
+import 'box.dart';
+import 'layer.dart';
+import 'object.dart';
+
+/// The options that control whether the performance overlay displays certain
+/// aspects of the compositor.
+enum PerformanceOverlayOption {
+  // these must be in the order needed for their index values to match the
+  // constants in //engine/src/sky/compositor/performance_overlay_layer.h
+
+  /// Display the frame time and FPS of the last frame rendered. This field is
+  /// updated every frame.
+  ///
+  /// This is the time spent by the rasterizer as it tries
+  /// to convert the layer tree obtained from the widgets into OpenGL commands
+  /// and tries to flush them onto the screen. When the total time taken by this
+  /// step exceeds the frame slice, a frame is lost.
+  displayRasterizerStatistics,
+
+  /// Display the rasterizer frame times as they change over a set period of
+  /// time in the form of a graph. The y axis of the graph denotes the total
+  /// time spent by the rasterizer as a fraction of the total frame slice. When
+  /// the bar turns red, a frame is lost.
+  visualizeRasterizerStatistics,
+
+  /// Display the frame time and FPS at which the interface can construct a
+  /// layer tree for the rasterizer (whose behavior is described above) to
+  /// consume.
+  ///
+  /// This involves all layout, animations, etc. When the total time taken by
+  /// this step exceeds the frame slice, a frame is lost.
+  displayEngineStatistics,
+
+  /// Display the engine frame times as they change over a set period of time
+  /// in the form of a graph. The y axis of the graph denotes the total time
+  /// spent by the engine as a fraction of the total frame slice. When the bar
+  /// turns red, a frame is lost.
+  visualizeEngineStatistics,
+}
+
+/// Displays performance statistics.
+///
+/// The overlay shows two time series. The first shows how much time was
+/// required on this thread to produce each frame. The second shows how much
+/// time was required on the raster thread (formerly known as the GPU thread)
+/// to produce each frame. Ideally, both these values would be less than
+/// the total frame budget for the hardware on which the app is running.
+/// For example, if the hardware has a screen that updates at 60 Hz, each
+/// thread should ideally spend less than 16ms producing each frame.
+/// This ideal condition is indicated by a green vertical line for each thread.
+/// Otherwise, the performance overlay shows a red vertical line.
+///
+/// The simplest way to show the performance overlay is to set
+/// [MaterialApp.showPerformanceOverlay] or [WidgetsApp.showPerformanceOverlay]
+/// to true.
+class RenderPerformanceOverlay extends RenderBox {
+  /// Creates a performance overlay render object.
+  ///
+  /// The [optionsMask], [rasterizerThreshold], [checkerboardRasterCacheImages],
+  /// and [checkerboardOffscreenLayers] arguments must not be null.
+  RenderPerformanceOverlay({
+    int optionsMask = 0,
+    int rasterizerThreshold = 0,
+    bool checkerboardRasterCacheImages = false,
+    bool checkerboardOffscreenLayers = false,
+  }) : assert(optionsMask != null),
+       assert(rasterizerThreshold != null),
+       assert(checkerboardRasterCacheImages != null),
+       assert(checkerboardOffscreenLayers != null),
+       _optionsMask = optionsMask,
+       _rasterizerThreshold = rasterizerThreshold,
+       _checkerboardRasterCacheImages = checkerboardRasterCacheImages,
+       _checkerboardOffscreenLayers = checkerboardOffscreenLayers;
+
+  /// The mask is created by shifting 1 by the index of the specific
+  /// [PerformanceOverlayOption] to enable.
+  int get optionsMask => _optionsMask;
+  int _optionsMask;
+  set optionsMask(int value) {
+    assert(value != null);
+    if (value == _optionsMask)
+      return;
+    _optionsMask = value;
+    markNeedsPaint();
+  }
+
+  /// The rasterizer threshold is an integer specifying the number of frame
+  /// intervals that the rasterizer must miss before it decides that the frame
+  /// is suitable for capturing an SkPicture trace for further analysis.
+  int get rasterizerThreshold => _rasterizerThreshold;
+  int _rasterizerThreshold;
+  set rasterizerThreshold(int value) {
+    assert(value != null);
+    if (value == _rasterizerThreshold)
+      return;
+    _rasterizerThreshold = value;
+    markNeedsPaint();
+  }
+
+  /// Whether the raster cache should checkerboard cached entries.
+  bool get checkerboardRasterCacheImages => _checkerboardRasterCacheImages;
+  bool _checkerboardRasterCacheImages;
+  set checkerboardRasterCacheImages(bool value) {
+    assert(value != null);
+    if (value == _checkerboardRasterCacheImages)
+      return;
+    _checkerboardRasterCacheImages = value;
+    markNeedsPaint();
+  }
+
+  /// Whether the compositor should checkerboard layers rendered to offscreen bitmaps.
+  bool get checkerboardOffscreenLayers => _checkerboardOffscreenLayers;
+  bool _checkerboardOffscreenLayers;
+  set checkerboardOffscreenLayers(bool value) {
+    assert(value != null);
+    if (value == _checkerboardOffscreenLayers)
+      return;
+    _checkerboardOffscreenLayers = value;
+    markNeedsPaint();
+  }
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  bool get alwaysNeedsCompositing => true;
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    return 0.0;
+  }
+
+  double get _intrinsicHeight {
+    const double kDefaultGraphHeight = 80.0;
+    double result = 0.0;
+    if ((optionsMask | (1 << PerformanceOverlayOption.displayRasterizerStatistics.index) > 0) ||
+        (optionsMask | (1 << PerformanceOverlayOption.visualizeRasterizerStatistics.index) > 0))
+      result += kDefaultGraphHeight;
+    if ((optionsMask | (1 << PerformanceOverlayOption.displayEngineStatistics.index) > 0) ||
+        (optionsMask | (1 << PerformanceOverlayOption.visualizeEngineStatistics.index) > 0))
+      result += kDefaultGraphHeight;
+    return result;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    return _intrinsicHeight;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    return _intrinsicHeight;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return constraints.constrain(Size(double.infinity, _intrinsicHeight));
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    assert(needsCompositing);
+    context.addLayer(PerformanceOverlayLayer(
+      overlayRect: Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height),
+      optionsMask: optionsMask,
+      rasterizerThreshold: rasterizerThreshold,
+      checkerboardRasterCacheImages: checkerboardRasterCacheImages,
+      checkerboardOffscreenLayers: checkerboardOffscreenLayers,
+    ));
+  }
+}
diff --git a/lib/src/rendering/platform_view.dart b/lib/src/rendering/platform_view.dart
new file mode 100644
index 0000000..3c0ee9b
--- /dev/null
+++ b/lib/src/rendering/platform_view.dart
@@ -0,0 +1,758 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/semantics.dart';
+import 'package:flute/services.dart';
+
+import 'box.dart';
+import 'layer.dart';
+import 'mouse_cursor.dart';
+import 'mouse_tracking.dart';
+import 'object.dart';
+
+
+/// How an embedded platform view behave during hit tests.
+enum PlatformViewHitTestBehavior {
+  /// Opaque targets can be hit by hit tests, causing them to both receive
+  /// events within their bounds and prevent targets visually behind them from
+  /// also receiving events.
+  opaque,
+
+  /// Translucent targets both receive events within their bounds and permit
+  /// targets visually behind them to also receive events.
+  translucent,
+
+  /// Transparent targets don't receive events within their bounds and permit
+  /// targets visually behind them to receive events.
+  transparent,
+}
+
+enum _PlatformViewState {
+  uninitialized,
+  resizing,
+  ready,
+}
+
+bool _factoryTypesSetEquals<T>(Set<Factory<T>>? a, Set<Factory<T>>? b) {
+  if (a == b) {
+    return true;
+  }
+  if (a == null ||  b == null) {
+    return false;
+  }
+  return setEquals(_factoriesTypeSet(a), _factoriesTypeSet(b));
+}
+
+Set<Type> _factoriesTypeSet<T>(Set<Factory<T>> factories) {
+  return factories.map<Type>((Factory<T> factory) => factory.type).toSet();
+}
+
+/// A render object for an Android view.
+///
+/// Requires Android API level 20 or greater.
+///
+/// [RenderAndroidView] is responsible for sizing, displaying and passing touch events to an
+/// Android [View](https://developer.android.com/reference/android/view/View).
+///
+/// {@template flutter.rendering.RenderAndroidView.layout}
+/// The render object's layout behavior is to fill all available space, the parent of this object must
+/// provide bounded layout constraints.
+/// {@endtemplate}
+///
+/// {@template flutter.rendering.RenderAndroidView.gestures}
+/// The render object participates in Flutter's gesture arenas, and dispatches touch events to the
+/// platform view iff it won the arena. Specific gestures that should be dispatched to the platform
+/// view can be specified with factories in the `gestureRecognizers` constructor parameter or
+/// by calling `updateGestureRecognizers`. If the set of gesture recognizers is empty, the gesture
+/// will be dispatched to the platform view iff it was not claimed by any other gesture recognizer.
+/// {@endtemplate}
+///
+/// See also:
+///
+///  * [AndroidView] which is a widget that is used to show an Android view.
+///  * [PlatformViewsService] which is a service for controlling platform views.
+class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
+
+  /// Creates a render object for an Android view.
+  RenderAndroidView({
+    required AndroidViewController viewController,
+    required PlatformViewHitTestBehavior hitTestBehavior,
+    required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : assert(viewController != null),
+       assert(hitTestBehavior != null),
+       assert(gestureRecognizers != null),
+       assert(clipBehavior != null),
+       _viewController = viewController,
+       _clipBehavior = clipBehavior {
+    _viewController.pointTransformer = (Offset offset) => globalToLocal(offset);
+    updateGestureRecognizers(gestureRecognizers);
+    _viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
+    this.hitTestBehavior = hitTestBehavior;
+  }
+
+  _PlatformViewState _state = _PlatformViewState.uninitialized;
+
+  /// The Android view controller for the Android view associated with this render object.
+  AndroidViewController get viewcontroller => _viewController;
+  AndroidViewController _viewController;
+  /// Sets a new Android view controller.
+  ///
+  /// `viewController` must not be null.
+  set viewController(AndroidViewController viewController) {
+    assert(_viewController != null);
+    assert(viewController != null);
+    if (_viewController == viewController)
+      return;
+    _viewController.removeOnPlatformViewCreatedListener(_onPlatformViewCreated);
+    _viewController = viewController;
+    _sizePlatformView();
+    if (_viewController.isCreated) {
+      markNeedsSemanticsUpdate();
+    }
+    _viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
+  }
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge], and must not be null.
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior = Clip.hardEdge;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  void _onPlatformViewCreated(int id) {
+    markNeedsSemanticsUpdate();
+  }
+
+  /// {@template flutter.rendering.RenderAndroidView.updateGestureRecognizers}
+  /// Updates which gestures should be forwarded to the platform view.
+  ///
+  /// Gesture recognizers created by factories in this set participate in the gesture arena for each
+  /// pointer that was put down on the render box. If any of the recognizers on this list wins the
+  /// gesture arena, the entire pointer event sequence starting from the pointer down event
+  /// will be dispatched to the Android view.
+  ///
+  /// The `gestureRecognizers` property must not contain more than one factory with the same [Factory.type].
+  ///
+  /// Setting a new set of gesture recognizer factories with the same [Factory.type]s as the current
+  /// set has no effect, because the factories' constructors would have already been called with the previous set.
+  /// {@endtemplate}
+  ///
+  /// Any active gesture arena the Android view participates in is rejected when the
+  /// set of gesture recognizers is changed.
+  void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) {
+    _updateGestureRecognizersWithCallBack(gestureRecognizers, _viewController.dispatchPointerEvent);
+  }
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  bool get alwaysNeedsCompositing => true;
+
+  @override
+  bool get isRepaintBoundary => true;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return constraints.biggest;
+  }
+
+  @override
+  void performResize() {
+    super.performResize();
+    _sizePlatformView();
+  }
+
+  late Size _currentAndroidViewSize;
+
+  Future<void> _sizePlatformView() async {
+    // Android virtual displays cannot have a zero size.
+    // Trying to size it to 0 crashes the app, which was happening when starting the app
+    // with a locked screen (see: https://github.com/flutter/flutter/issues/20456).
+    if (_state == _PlatformViewState.resizing || size.isEmpty) {
+      return;
+    }
+
+    _state = _PlatformViewState.resizing;
+    markNeedsPaint();
+
+    Size targetSize;
+    do {
+      targetSize = size;
+      await _viewController.setSize(targetSize);
+      _currentAndroidViewSize = targetSize;
+      // We've resized the platform view to targetSize, but it is possible that
+      // while we were resizing the render object's size was changed again.
+      // In that case we will resize the platform view again.
+    } while (size != targetSize);
+
+    _state = _PlatformViewState.ready;
+    markNeedsPaint();
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (_viewController.textureId == null)
+      return;
+
+    // Clip the texture if it's going to paint out of the bounds of the renter box
+    // (see comment in _paintTexture for an explanation of when this happens).
+    if ((size.width < _currentAndroidViewSize.width || size.height < _currentAndroidViewSize.height) && clipBehavior != Clip.none) {
+      _clipRectLayer = context.pushClipRect(true, offset, offset & size, _paintTexture, clipBehavior: clipBehavior,
+          oldLayer: _clipRectLayer);
+      return;
+    }
+    _clipRectLayer = null;
+    _paintTexture(context, offset);
+  }
+
+  ClipRectLayer? _clipRectLayer;
+
+  void _paintTexture(PaintingContext context, Offset offset) {
+    // As resizing the Android view happens asynchronously we don't know exactly when is a
+    // texture frame with the new size is ready for consumption.
+    // TextureLayer is unaware of the texture frame's size and always maps it to the
+    // specified rect. If the rect we provide has a different size from the current texture frame's
+    // size the texture frame will be scaled.
+    // To prevent unwanted scaling artifacts while resizing we freeze the texture frame, until
+    // we know that a frame with the new size is in the buffer.
+    // This guarantees that the size of the texture frame we're painting is always
+    // _currentAndroidViewSize.
+    context.addLayer(TextureLayer(
+      rect: offset & _currentAndroidViewSize,
+      textureId: _viewController.textureId!,
+      freeze: _state == _PlatformViewState.resizing,
+    ));
+  }
+
+  @override
+  void describeSemanticsConfiguration (SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+
+    config.isSemanticBoundary = true;
+
+    if (_viewController.isCreated) {
+      config.platformViewId = _viewController.viewId;
+    }
+  }
+}
+
+/// A render object for an iOS UIKit UIView.
+///
+/// {@template flutter.rendering.RenderUiKitView}
+/// Embedding UIViews is still preview-quality. To enable the preview for an iOS app add a boolean
+/// field with the key 'io.flutter.embedded_views_preview' and the value set to 'YES' to the
+/// application's Info.plist file. A list of open issued with embedding UIViews is available on
+/// [Github](https://github.com/flutter/flutter/issues?q=is%3Aopen+is%3Aissue+label%3A%22a%3A+platform-views%22+label%3Aplatform-ios+sort%3Acreated-asc)
+/// {@endtemplate}
+///
+/// [RenderUiKitView] is responsible for sizing and displaying an iOS
+/// [UIView](https://developer.apple.com/documentation/uikit/uiview).
+///
+/// UIViews are added as sub views of the FlutterView and are composited by Quartz.
+///
+/// {@macro flutter.rendering.RenderAndroidView.layout}
+///
+/// {@macro flutter.rendering.RenderAndroidView.gestures}
+///
+/// See also:
+///
+///  * [UiKitView] which is a widget that is used to show a UIView.
+///  * [PlatformViewsService] which is a service for controlling platform views.
+class RenderUiKitView extends RenderBox {
+  /// Creates a render object for an iOS UIView.
+  ///
+  /// The `viewId`, `hitTestBehavior`, and `gestureRecognizers` parameters must not be null.
+  RenderUiKitView({
+    required UiKitViewController viewController,
+    required this.hitTestBehavior,
+    required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
+  }) : assert(viewController != null),
+       assert(hitTestBehavior != null),
+       assert(gestureRecognizers != null),
+       _viewController = viewController {
+    updateGestureRecognizers(gestureRecognizers);
+  }
+
+
+  /// The unique identifier of the UIView controlled by this controller.
+  ///
+  /// Typically generated by [PlatformViewsRegistry.getNextPlatformViewId], the UIView
+  /// must have been created by calling [PlatformViewsService.initUiKitView].
+  UiKitViewController get viewController => _viewController;
+  UiKitViewController _viewController;
+  set viewController(UiKitViewController viewController) {
+    assert(viewController != null);
+    final bool needsSemanticsUpdate = _viewController.id != viewController.id;
+    _viewController = viewController;
+    markNeedsPaint();
+    if (needsSemanticsUpdate) {
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  /// How to behave during hit testing.
+  // The implicit setter is enough here as changing this value will just affect
+  // any newly arriving events there's nothing we need to invalidate.
+  PlatformViewHitTestBehavior hitTestBehavior;
+
+  /// {@macro flutter.rendering.RenderAndroidView.updateGestureRecognizers}
+  void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) {
+    assert(gestureRecognizers != null);
+    assert(
+    _factoriesTypeSet(gestureRecognizers).length == gestureRecognizers.length,
+    'There were multiple gesture recognizer factories for the same type, there must only be a single '
+        'gesture recognizer factory for each gesture recognizer type.',);
+    if (_factoryTypesSetEquals(gestureRecognizers, _gestureRecognizer?.gestureRecognizerFactories)) {
+      return;
+    }
+    _gestureRecognizer?.dispose();
+    _gestureRecognizer = _UiKitViewGestureRecognizer(viewController, gestureRecognizers);
+  }
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  bool get alwaysNeedsCompositing => true;
+
+  @override
+  bool get isRepaintBoundary => true;
+
+  _UiKitViewGestureRecognizer? _gestureRecognizer;
+
+  PointerEvent? _lastPointerDownEvent;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return constraints.biggest;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    context.addLayer(PlatformViewLayer(
+      rect: offset & size,
+      viewId: _viewController.id,
+    ));
+  }
+
+  @override
+  bool hitTest(BoxHitTestResult result, { Offset? position }) {
+    if (hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position!))
+      return false;
+    result.add(BoxHitTestEntry(this, position));
+    return hitTestBehavior == PlatformViewHitTestBehavior.opaque;
+  }
+
+  @override
+  bool hitTestSelf(Offset position) => hitTestBehavior != PlatformViewHitTestBehavior.transparent;
+
+  @override
+  void handleEvent(PointerEvent event, HitTestEntry entry) {
+    if (event is! PointerDownEvent) {
+      return;
+    }
+    _gestureRecognizer!.addPointer(event);
+    _lastPointerDownEvent = event.original ?? event;
+  }
+
+  // This is registered as a global PointerRoute while the render object is attached.
+  void _handleGlobalPointerEvent(PointerEvent event) {
+    if (event is! PointerDownEvent) {
+      return;
+    }
+    if (!(Offset.zero & size).contains(globalToLocal(event.position))) {
+      return;
+    }
+    if ((event.original ?? event) != _lastPointerDownEvent) {
+      // The pointer event is in the bounds of this render box, but we didn't get it in handleEvent.
+      // This means that the pointer event was absorbed by a different render object.
+      // Since on the platform side the FlutterTouchIntercepting view is seeing all events that are
+      // within its bounds we need to tell it to reject the current touch sequence.
+      _viewController.rejectGesture();
+    }
+    _lastPointerDownEvent = null;
+  }
+
+  @override
+  void describeSemanticsConfiguration (SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    config.isSemanticBoundary = true;
+    config.platformViewId = _viewController.id;
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    GestureBinding.instance!.pointerRouter.addGlobalRoute(_handleGlobalPointerEvent);
+  }
+
+  @override
+  void detach() {
+    GestureBinding.instance!.pointerRouter.removeGlobalRoute(_handleGlobalPointerEvent);
+    _gestureRecognizer!.reset();
+    super.detach();
+  }
+}
+
+// This recognizer constructs gesture recognizers from a set of gesture recognizer factories
+// it was give, adds all of them to a gesture arena team with the _UiKitViewGesturrRecognizer
+// as the team captain.
+// When the team wins a gesture the recognizer notifies the engine that it should release
+// the touch sequence to the embedded UIView.
+class _UiKitViewGestureRecognizer extends OneSequenceGestureRecognizer {
+  _UiKitViewGestureRecognizer(
+    this.controller,
+    this.gestureRecognizerFactories, {
+    PointerDeviceKind? kind,
+  }) : super(kind: kind) {
+    team = GestureArenaTeam()
+      ..captain = this;
+    _gestureRecognizers = gestureRecognizerFactories.map(
+      (Factory<OneSequenceGestureRecognizer> recognizerFactory) {
+        final OneSequenceGestureRecognizer gestureRecognizer = recognizerFactory.constructor();
+        gestureRecognizer.team = team;
+        // The below gesture recognizers requires at least one non-empty callback to
+        // compete in the gesture arena.
+        // https://github.com/flutter/flutter/issues/35394#issuecomment-562285087
+        if (gestureRecognizer is LongPressGestureRecognizer) {
+          gestureRecognizer.onLongPress ??= (){};
+        } else if (gestureRecognizer is DragGestureRecognizer) {
+          gestureRecognizer.onDown ??= (_){};
+        } else if (gestureRecognizer is TapGestureRecognizer) {
+          gestureRecognizer.onTapDown ??= (_){};
+        }
+        return gestureRecognizer;
+      },
+    ).toSet();
+  }
+
+
+  // We use OneSequenceGestureRecognizers as they support gesture arena teams.
+  // TODO(amirh): get a list of GestureRecognizers here.
+  // https://github.com/flutter/flutter/issues/20953
+  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizerFactories;
+  late Set<OneSequenceGestureRecognizer> _gestureRecognizers;
+
+  final UiKitViewController controller;
+
+  @override
+  void addAllowedPointer(PointerDownEvent event) {
+    startTrackingPointer(event.pointer, event.transform);
+    for (final OneSequenceGestureRecognizer recognizer in _gestureRecognizers) {
+      recognizer.addPointer(event);
+    }
+  }
+
+  @override
+  String get debugDescription => 'UIKit view';
+
+  @override
+  void didStopTrackingLastPointer(int pointer) { }
+
+  @override
+  void handleEvent(PointerEvent event) {
+    stopTrackingIfPointerNoLongerDown(event);
+  }
+
+  @override
+  void acceptGesture(int pointer) {
+    controller.acceptGesture();
+  }
+
+  @override
+  void rejectGesture(int pointer) {
+    controller.rejectGesture();
+  }
+
+  void reset() {
+    resolve(GestureDisposition.rejected);
+  }
+}
+
+typedef _HandlePointerEvent = Future<void> Function(PointerEvent event);
+
+// This recognizer constructs gesture recognizers from a set of gesture recognizer factories
+// it was give, adds all of them to a gesture arena team with the _PlatformViewGestureRecognizer
+// as the team captain.
+// As long as the gesture arena is unresolved, the recognizer caches all pointer events.
+// When the team wins, the recognizer sends all the cached pointer events to `_handlePointerEvent`, and
+// sets itself to a "forwarding mode" where it will forward any new pointer event to `_handlePointerEvent`.
+class _PlatformViewGestureRecognizer extends OneSequenceGestureRecognizer {
+  _PlatformViewGestureRecognizer(
+    _HandlePointerEvent handlePointerEvent,
+    this.gestureRecognizerFactories, {
+    PointerDeviceKind? kind,
+  }) : super(kind: kind) {
+    team = GestureArenaTeam()
+      ..captain = this;
+    _gestureRecognizers = gestureRecognizerFactories.map(
+      (Factory<OneSequenceGestureRecognizer> recognizerFactory) {
+        final OneSequenceGestureRecognizer gestureRecognizer = recognizerFactory.constructor();
+        gestureRecognizer.team = team;
+        // The below gesture recognizers requires at least one non-empty callback to
+        // compete in the gesture arena.
+        // https://github.com/flutter/flutter/issues/35394#issuecomment-562285087
+        if (gestureRecognizer is LongPressGestureRecognizer) {
+          gestureRecognizer.onLongPress ??= (){};
+        } else if (gestureRecognizer is DragGestureRecognizer) {
+          gestureRecognizer.onDown ??= (_){};
+        } else if (gestureRecognizer is TapGestureRecognizer) {
+          gestureRecognizer.onTapDown ??= (_){};
+        }
+        return gestureRecognizer;
+      },
+    ).toSet();
+    _handlePointerEvent = handlePointerEvent;
+  }
+
+  late _HandlePointerEvent _handlePointerEvent;
+
+  // Maps a pointer to a list of its cached pointer events.
+  // Before the arena for a pointer is resolved all events are cached here, if we win the arena
+  // the cached events are dispatched to `_handlePointerEvent`, if we lose the arena we clear the cache for
+  // the pointer.
+  final Map<int, List<PointerEvent>> cachedEvents = <int, List<PointerEvent>>{};
+
+  // Pointer for which we have already won the arena, events for pointers in this set are
+  // immediately dispatched to `_handlePointerEvent`.
+  final Set<int> forwardedPointers = <int>{};
+
+  // We use OneSequenceGestureRecognizers as they support gesture arena teams.
+  // TODO(amirh): get a list of GestureRecognizers here.
+  // https://github.com/flutter/flutter/issues/20953
+  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizerFactories;
+  late Set<OneSequenceGestureRecognizer> _gestureRecognizers;
+
+  @override
+  void addAllowedPointer(PointerDownEvent event) {
+    startTrackingPointer(event.pointer, event.transform);
+    for (final OneSequenceGestureRecognizer recognizer in _gestureRecognizers) {
+      recognizer.addPointer(event);
+    }
+  }
+
+  @override
+  String get debugDescription => 'Platform view';
+
+  @override
+  void didStopTrackingLastPointer(int pointer) { }
+
+  @override
+  void handleEvent(PointerEvent event) {
+    if (!forwardedPointers.contains(event.pointer)) {
+      _cacheEvent(event);
+    } else {
+      _handlePointerEvent(event);
+    }
+    stopTrackingIfPointerNoLongerDown(event);
+  }
+
+  @override
+  void acceptGesture(int pointer) {
+    _flushPointerCache(pointer);
+    forwardedPointers.add(pointer);
+  }
+
+  @override
+  void rejectGesture(int pointer) {
+    stopTrackingPointer(pointer);
+    cachedEvents.remove(pointer);
+  }
+
+  void _cacheEvent(PointerEvent event) {
+    if (!cachedEvents.containsKey(event.pointer)) {
+      cachedEvents[event.pointer] = <PointerEvent> [];
+    }
+    cachedEvents[event.pointer]!.add(event);
+  }
+
+  void _flushPointerCache(int pointer) {
+    cachedEvents.remove(pointer)?.forEach(_handlePointerEvent);
+  }
+
+  @override
+  void stopTrackingPointer(int pointer) {
+    super.stopTrackingPointer(pointer);
+    forwardedPointers.remove(pointer);
+  }
+
+  void reset() {
+    forwardedPointers.forEach(super.stopTrackingPointer);
+    forwardedPointers.clear();
+    cachedEvents.keys.forEach(super.stopTrackingPointer);
+    cachedEvents.clear();
+    resolve(GestureDisposition.rejected);
+  }
+}
+
+/// A render object for embedding a platform view.
+///
+/// [PlatformViewRenderBox] presents a platform view by adding a [PlatformViewLayer] layer,
+/// integrates it with the gesture arenas system and adds relevant semantic nodes to the semantics tree.
+class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin {
+
+  /// Creating a render object for a [PlatformViewSurface].
+  ///
+  /// The `controller` parameter must not be null.
+  PlatformViewRenderBox({
+    required PlatformViewController controller,
+    required PlatformViewHitTestBehavior hitTestBehavior,
+    required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
+  }) :  assert(controller != null && controller.viewId != null && controller.viewId > -1),
+        assert(hitTestBehavior != null),
+        assert(gestureRecognizers != null),
+        _controller = controller {
+    this.hitTestBehavior = hitTestBehavior;
+    updateGestureRecognizers(gestureRecognizers);
+  }
+
+  /// Sets the [controller] for this render object.
+  ///
+  /// This value must not be null, and setting it to a new value will result in a repaint.
+  set controller(PlatformViewController controller) {
+    assert(controller != null);
+    assert(controller.viewId != null && controller.viewId > -1);
+
+    if ( _controller == controller) {
+      return;
+    }
+    final bool needsSemanticsUpdate = _controller.viewId != controller.viewId;
+    _controller = controller;
+    markNeedsPaint();
+    if (needsSemanticsUpdate) {
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  /// {@macro  flutter.rendering.RenderAndroidView.updateGestureRecognizers}
+  ///
+  /// Any active gesture arena the `PlatformView` participates in is rejected when the
+  /// set of gesture recognizers is changed.
+  void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) {
+    _updateGestureRecognizersWithCallBack(gestureRecognizers, _controller.dispatchPointerEvent);
+  }
+
+  PlatformViewController _controller;
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  bool get alwaysNeedsCompositing => true;
+
+  @override
+  bool get isRepaintBoundary => true;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return constraints.biggest;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    assert(_controller.viewId != null);
+    context.addLayer(PlatformViewLayer(
+      rect: offset & size,
+      viewId: _controller.viewId,
+    ));
+  }
+
+  @override
+  void describeSemanticsConfiguration (SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    assert(_controller.viewId != null);
+    config.isSemanticBoundary = true;
+    config.platformViewId = _controller.viewId;
+  }
+}
+
+/// The Mixin handling the pointer events and gestures of a platform view render box.
+mixin _PlatformViewGestureMixin on RenderBox implements MouseTrackerAnnotation {
+
+  /// How to behave during hit testing.
+  // Changing _hitTestBehavior might affect which objects are considered hovered over.
+  set hitTestBehavior(PlatformViewHitTestBehavior value) {
+    if (value != _hitTestBehavior) {
+      _hitTestBehavior = value;
+      if (owner != null)
+        markNeedsPaint();
+    }
+  }
+  PlatformViewHitTestBehavior? _hitTestBehavior;
+
+  _HandlePointerEvent? _handlePointerEvent;
+
+  /// {@macro  flutter.rendering.RenderAndroidView.updateGestureRecognizers}
+  ///
+  /// Any active gesture arena the `PlatformView` participates in is rejected when the
+  /// set of gesture recognizers is changed.
+  void _updateGestureRecognizersWithCallBack(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers, _HandlePointerEvent handlePointerEvent) {
+    assert(gestureRecognizers != null);
+    assert(
+    _factoriesTypeSet(gestureRecognizers).length == gestureRecognizers.length,
+    'There were multiple gesture recognizer factories for the same type, there must only be a single '
+        'gesture recognizer factory for each gesture recognizer type.',);
+    if (_factoryTypesSetEquals(gestureRecognizers, _gestureRecognizer?.gestureRecognizerFactories)) {
+      return;
+    }
+    _gestureRecognizer?.dispose();
+    _gestureRecognizer = _PlatformViewGestureRecognizer(handlePointerEvent, gestureRecognizers);
+    _handlePointerEvent = handlePointerEvent;
+  }
+
+  _PlatformViewGestureRecognizer? _gestureRecognizer;
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    if (_hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position)) {
+      return false;
+    }
+    result.add(BoxHitTestEntry(this, position));
+    return _hitTestBehavior == PlatformViewHitTestBehavior.opaque;
+  }
+
+  @override
+  bool hitTestSelf(Offset position) => _hitTestBehavior != PlatformViewHitTestBehavior.transparent;
+
+  @override
+  PointerEnterEventListener? get onEnter => null;
+
+  @override
+  PointerExitEventListener? get onExit => null;
+
+  @override
+  MouseCursor get cursor => MouseCursor.uncontrolled;
+
+  @override
+  bool get validForMouseTracker => true;
+
+  @override
+  void handleEvent(PointerEvent event, HitTestEntry entry) {
+    if (event is PointerDownEvent) {
+      _gestureRecognizer!.addPointer(event);
+    }
+    if (event is PointerHoverEvent) {
+      _handlePointerEvent?.call(event);
+    }
+  }
+
+  @override
+  void detach() {
+    _gestureRecognizer!.reset();
+    super.detach();
+  }
+}
diff --git a/lib/src/rendering/proxy_box.dart b/lib/src/rendering/proxy_box.dart
new file mode 100644
index 0000000..b73fff1
--- /dev/null
+++ b/lib/src/rendering/proxy_box.dart
@@ -0,0 +1,5337 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui show ImageFilter, Gradient, Image, Color;
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/painting.dart';
+import 'package:flute/semantics.dart';
+import 'package:flute/services.dart';
+
+import 'package:vector_math/vector_math_64.dart';
+
+import 'binding.dart';
+import 'box.dart';
+import 'layer.dart';
+import 'layout_helper.dart';
+import 'mouse_cursor.dart';
+import 'mouse_tracking.dart';
+import 'object.dart';
+
+export 'package:flute/gestures.dart' show
+  PointerEvent,
+  PointerDownEvent,
+  PointerMoveEvent,
+  PointerUpEvent,
+  PointerCancelEvent;
+
+// Examples can assume:
+// // @dart = 2.9
+
+/// A base class for render boxes that resemble their children.
+///
+/// A proxy box has a single child and simply mimics all the properties of that
+/// child by calling through to the child for each function in the render box
+/// protocol. For example, a proxy box determines its size by asking its child
+/// to layout with the same constraints and then matching the size.
+///
+/// A proxy box isn't useful on its own because you might as well just replace
+/// the proxy box with its child. However, RenderProxyBox is a useful base class
+/// for render objects that wish to mimic most, but not all, of the properties
+/// of their child.
+///
+/// See also:
+///
+///  * [RenderProxySliver], a base class for render slivers that resemble their
+///    children.
+class RenderProxyBox extends RenderBox with RenderObjectWithChildMixin<RenderBox>, RenderProxyBoxMixin<RenderBox> {
+  /// Creates a proxy render box.
+  ///
+  /// Proxy render boxes are rarely created directly because they simply proxy
+  /// the render box protocol to [child]. Instead, consider using one of the
+  /// subclasses.
+  RenderProxyBox([RenderBox? child]) {
+    this.child = child;
+  }
+}
+
+/// Implementation of [RenderProxyBox].
+///
+/// Use this mixin in situations where the proxying behavior
+/// of [RenderProxyBox] is desired but inheriting from [RenderProxyBox] is
+/// impractical (e.g. because you want to mix in other classes as well).
+// TODO(ianh): Remove this class once https://github.com/dart-lang/sdk/issues/31543 is fixed
+@optionalTypeArgs
+mixin RenderProxyBoxMixin<T extends RenderBox> on RenderBox, RenderObjectWithChildMixin<T> {
+  @override
+  void setupParentData(RenderObject child) {
+    // We don't actually use the offset argument in BoxParentData, so let's
+    // avoid allocating it at all.
+    if (child.parentData is! ParentData)
+      child.parentData = ParentData();
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    if (child != null)
+      return child!.getMinIntrinsicWidth(height);
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    if (child != null)
+      return child!.getMaxIntrinsicWidth(height);
+    return 0.0;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    if (child != null)
+      return child!.getMinIntrinsicHeight(width);
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    if (child != null)
+      return child!.getMaxIntrinsicHeight(width);
+    return 0.0;
+  }
+
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    if (child != null)
+      return child!.getDistanceToActualBaseline(baseline);
+    return super.computeDistanceToActualBaseline(baseline);
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (child != null) {
+      return child!.getDryLayout(constraints);
+    }
+    return computeSizeForNoChild(constraints);
+  }
+
+  @override
+  void performLayout() {
+    if (child != null) {
+      child!.layout(constraints, parentUsesSize: true);
+      size = child!.size;
+    } else {
+      size = computeSizeForNoChild(constraints);
+    }
+  }
+
+  /// Calculate the size the [RenderProxyBox] would have under the given
+  /// [BoxConstraints] for the case where it does not have a child.
+  Size computeSizeForNoChild(BoxConstraints constraints) {
+    return constraints.smallest;
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    return child?.hitTest(result, position: position) ?? false;
+  }
+
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) { }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null)
+      context.paintChild(child!, offset);
+  }
+}
+
+/// How to behave during hit tests.
+enum HitTestBehavior {
+  /// Targets that defer to their children receive events within their bounds
+  /// only if one of their children is hit by the hit test.
+  deferToChild,
+
+  /// Opaque targets can be hit by hit tests, causing them to both receive
+  /// events within their bounds and prevent targets visually behind them from
+  /// also receiving events.
+  opaque,
+
+  /// Translucent targets both receive events within their bounds and permit
+  /// targets visually behind them to also receive events.
+  translucent,
+}
+
+/// A RenderProxyBox subclass that allows you to customize the
+/// hit-testing behavior.
+abstract class RenderProxyBoxWithHitTestBehavior extends RenderProxyBox {
+  /// Initializes member variables for subclasses.
+  ///
+  /// By default, the [behavior] is [HitTestBehavior.deferToChild].
+  RenderProxyBoxWithHitTestBehavior({
+    this.behavior = HitTestBehavior.deferToChild,
+    RenderBox? child,
+  }) : super(child);
+
+  /// How to behave during hit testing.
+  HitTestBehavior behavior;
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    bool hitTarget = false;
+    if (size.contains(position)) {
+      hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
+      if (hitTarget || behavior == HitTestBehavior.translucent)
+        result.add(BoxHitTestEntry(this, position));
+    }
+    return hitTarget;
+  }
+
+  @override
+  bool hitTestSelf(Offset position) => behavior == HitTestBehavior.opaque;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<HitTestBehavior>('behavior', behavior, defaultValue: null));
+  }
+}
+
+/// Imposes additional constraints on its child.
+///
+/// A render constrained box proxies most functions in the render box protocol
+/// to its child, except that when laying out its child, it tightens the
+/// constraints provided by its parent by enforcing the [additionalConstraints]
+/// as well.
+///
+/// For example, if you wanted [child] to have a minimum height of 50.0 logical
+/// pixels, you could use `const BoxConstraints(minHeight: 50.0)` as the
+/// [additionalConstraints].
+class RenderConstrainedBox extends RenderProxyBox {
+  /// Creates a render box that constrains its child.
+  ///
+  /// The [additionalConstraints] argument must not be null and must be valid.
+  RenderConstrainedBox({
+    RenderBox? child,
+    required BoxConstraints additionalConstraints,
+  }) : assert(additionalConstraints != null),
+       assert(additionalConstraints.debugAssertIsValid()),
+       _additionalConstraints = additionalConstraints,
+       super(child);
+
+  /// Additional constraints to apply to [child] during layout.
+  BoxConstraints get additionalConstraints => _additionalConstraints;
+  BoxConstraints _additionalConstraints;
+  set additionalConstraints(BoxConstraints value) {
+    assert(value != null);
+    assert(value.debugAssertIsValid());
+    if (_additionalConstraints == value)
+      return;
+    _additionalConstraints = value;
+    markNeedsLayout();
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    if (_additionalConstraints.hasBoundedWidth && _additionalConstraints.hasTightWidth)
+      return _additionalConstraints.minWidth;
+    final double width = super.computeMinIntrinsicWidth(height);
+    assert(width.isFinite);
+    if (!_additionalConstraints.hasInfiniteWidth)
+      return _additionalConstraints.constrainWidth(width);
+    return width;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    if (_additionalConstraints.hasBoundedWidth && _additionalConstraints.hasTightWidth)
+      return _additionalConstraints.minWidth;
+    final double width = super.computeMaxIntrinsicWidth(height);
+    assert(width.isFinite);
+    if (!_additionalConstraints.hasInfiniteWidth)
+      return _additionalConstraints.constrainWidth(width);
+    return width;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    if (_additionalConstraints.hasBoundedHeight && _additionalConstraints.hasTightHeight)
+      return _additionalConstraints.minHeight;
+    final double height = super.computeMinIntrinsicHeight(width);
+    assert(height.isFinite);
+    if (!_additionalConstraints.hasInfiniteHeight)
+      return _additionalConstraints.constrainHeight(height);
+    return height;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    if (_additionalConstraints.hasBoundedHeight && _additionalConstraints.hasTightHeight)
+      return _additionalConstraints.minHeight;
+    final double height = super.computeMaxIntrinsicHeight(width);
+    assert(height.isFinite);
+    if (!_additionalConstraints.hasInfiniteHeight)
+      return _additionalConstraints.constrainHeight(height);
+    return height;
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    if (child != null) {
+      child!.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true);
+      size = child!.size;
+    } else {
+      size = _additionalConstraints.enforce(constraints).constrain(Size.zero);
+    }
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (child != null) {
+      return child!.getDryLayout(_additionalConstraints.enforce(constraints));
+    } else {
+      return _additionalConstraints.enforce(constraints).constrain(Size.zero);
+    }
+  }
+
+  @override
+  void debugPaintSize(PaintingContext context, Offset offset) {
+    super.debugPaintSize(context, offset);
+    assert(() {
+      final Paint paint;
+      if (child == null || child!.size.isEmpty) {
+        paint = Paint()
+          ..color = const Color(0x90909090);
+        context.canvas.drawRect(offset & size, paint);
+      }
+      return true;
+    }());
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<BoxConstraints>('additionalConstraints', additionalConstraints));
+  }
+}
+
+/// Constrains the child's [BoxConstraints.maxWidth] and
+/// [BoxConstraints.maxHeight] if they're otherwise unconstrained.
+///
+/// This has the effect of giving the child a natural dimension in unbounded
+/// environments. For example, by providing a [maxHeight] to a widget that
+/// normally tries to be as big as possible, the widget will normally size
+/// itself to fit its parent, but when placed in a vertical list, it will take
+/// on the given height.
+///
+/// This is useful when composing widgets that normally try to match their
+/// parents' size, so that they behave reasonably in lists (which are
+/// unbounded).
+class RenderLimitedBox extends RenderProxyBox {
+  /// Creates a render box that imposes a maximum width or maximum height on its
+  /// child if the child is otherwise unconstrained.
+  ///
+  /// The [maxWidth] and [maxHeight] arguments not be null and must be
+  /// non-negative.
+  RenderLimitedBox({
+    RenderBox? child,
+    double maxWidth = double.infinity,
+    double maxHeight = double.infinity,
+  }) : assert(maxWidth != null && maxWidth >= 0.0),
+       assert(maxHeight != null && maxHeight >= 0.0),
+       _maxWidth = maxWidth,
+       _maxHeight = maxHeight,
+       super(child);
+
+  /// The value to use for maxWidth if the incoming maxWidth constraint is infinite.
+  double get maxWidth => _maxWidth;
+  double _maxWidth;
+  set maxWidth(double value) {
+    assert(value != null && value >= 0.0);
+    if (_maxWidth == value)
+      return;
+    _maxWidth = value;
+    markNeedsLayout();
+  }
+
+  /// The value to use for maxHeight if the incoming maxHeight constraint is infinite.
+  double get maxHeight => _maxHeight;
+  double _maxHeight;
+  set maxHeight(double value) {
+    assert(value != null && value >= 0.0);
+    if (_maxHeight == value)
+      return;
+    _maxHeight = value;
+    markNeedsLayout();
+  }
+
+  BoxConstraints _limitConstraints(BoxConstraints constraints) {
+    return BoxConstraints(
+      minWidth: constraints.minWidth,
+      maxWidth: constraints.hasBoundedWidth ? constraints.maxWidth : constraints.constrainWidth(maxWidth),
+      minHeight: constraints.minHeight,
+      maxHeight: constraints.hasBoundedHeight ? constraints.maxHeight : constraints.constrainHeight(maxHeight),
+    );
+  }
+
+  Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild }) {
+    if (child != null) {
+      final Size childSize = layoutChild(child!, _limitConstraints(constraints));
+      return constraints.constrain(childSize);
+    }
+    return _limitConstraints(constraints).constrain(Size.zero);
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _computeSize(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.dryLayoutChild,
+    );
+  }
+
+  @override
+  void performLayout() {
+    size = _computeSize(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.layoutChild,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('maxWidth', maxWidth, defaultValue: double.infinity));
+    properties.add(DoubleProperty('maxHeight', maxHeight, defaultValue: double.infinity));
+  }
+}
+
+/// Attempts to size the child to a specific aspect ratio.
+///
+/// The render object first tries the largest width permitted by the layout
+/// constraints. The height of the render object is determined by applying the
+/// given aspect ratio to the width, expressed as a ratio of width to height.
+///
+/// For example, a 16:9 width:height aspect ratio would have a value of
+/// 16.0/9.0. If the maximum width is infinite, the initial width is determined
+/// by applying the aspect ratio to the maximum height.
+///
+/// Now consider a second example, this time with an aspect ratio of 2.0 and
+/// layout constraints that require the width to be between 0.0 and 100.0 and
+/// the height to be between 0.0 and 100.0. We'll select a width of 100.0 (the
+/// biggest allowed) and a height of 50.0 (to match the aspect ratio).
+///
+/// In that same situation, if the aspect ratio is 0.5, we'll also select a
+/// width of 100.0 (still the biggest allowed) and we'll attempt to use a height
+/// of 200.0. Unfortunately, that violates the constraints because the child can
+/// be at most 100.0 pixels tall. The render object will then take that value
+/// and apply the aspect ratio again to obtain a width of 50.0. That width is
+/// permitted by the constraints and the child receives a width of 50.0 and a
+/// height of 100.0. If the width were not permitted, the render object would
+/// continue iterating through the constraints. If the render object does not
+/// find a feasible size after consulting each constraint, the render object
+/// will eventually select a size for the child that meets the layout
+/// constraints but fails to meet the aspect ratio constraints.
+class RenderAspectRatio extends RenderProxyBox {
+  /// Creates as render object with a specific aspect ratio.
+  ///
+  /// The [aspectRatio] argument must be a finite, positive value.
+  RenderAspectRatio({
+    RenderBox? child,
+    required double aspectRatio,
+  }) : assert(aspectRatio != null),
+       assert(aspectRatio > 0.0),
+       assert(aspectRatio.isFinite),
+       _aspectRatio = aspectRatio,
+       super(child);
+
+  /// The aspect ratio to attempt to use.
+  ///
+  /// The aspect ratio is expressed as a ratio of width to height. For example,
+  /// a 16:9 width:height aspect ratio would have a value of 16.0/9.0.
+  double get aspectRatio => _aspectRatio;
+  double _aspectRatio;
+  set aspectRatio(double value) {
+    assert(value != null);
+    assert(value > 0.0);
+    assert(value.isFinite);
+    if (_aspectRatio == value)
+      return;
+    _aspectRatio = value;
+    markNeedsLayout();
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    if (height.isFinite)
+      return height * _aspectRatio;
+    if (child != null)
+      return child!.getMinIntrinsicWidth(height);
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    if (height.isFinite)
+      return height * _aspectRatio;
+    if (child != null)
+      return child!.getMaxIntrinsicWidth(height);
+    return 0.0;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    if (width.isFinite)
+      return width / _aspectRatio;
+    if (child != null)
+      return child!.getMinIntrinsicHeight(width);
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    if (width.isFinite)
+      return width / _aspectRatio;
+    if (child != null)
+      return child!.getMaxIntrinsicHeight(width);
+    return 0.0;
+  }
+
+  Size _applyAspectRatio(BoxConstraints constraints) {
+    assert(constraints.debugAssertIsValid());
+    assert(() {
+      if (!constraints.hasBoundedWidth && !constraints.hasBoundedHeight) {
+        throw FlutterError(
+          '$runtimeType has unbounded constraints.\n'
+          'This $runtimeType was given an aspect ratio of $aspectRatio but was given '
+          'both unbounded width and unbounded height constraints. Because both '
+          'constraints were unbounded, this render object doesn\'t know how much '
+          'size to consume.'
+        );
+      }
+      return true;
+    }());
+
+    if (constraints.isTight)
+      return constraints.smallest;
+
+    double width = constraints.maxWidth;
+    double height;
+
+    // We default to picking the height based on the width, but if the width
+    // would be infinite, that's not sensible so we try to infer the height
+    // from the width.
+    if (width.isFinite) {
+      height = width / _aspectRatio;
+    } else {
+      height = constraints.maxHeight;
+      width = height * _aspectRatio;
+    }
+
+    // Similar to RenderImage, we iteratively attempt to fit within the given
+    // constraints while maintaining the given aspect ratio. The order of
+    // applying the constraints is also biased towards inferring the height
+    // from the width.
+
+    if (width > constraints.maxWidth) {
+      width = constraints.maxWidth;
+      height = width / _aspectRatio;
+    }
+
+    if (height > constraints.maxHeight) {
+      height = constraints.maxHeight;
+      width = height * _aspectRatio;
+    }
+
+    if (width < constraints.minWidth) {
+      width = constraints.minWidth;
+      height = width / _aspectRatio;
+    }
+
+    if (height < constraints.minHeight) {
+      height = constraints.minHeight;
+      width = height * _aspectRatio;
+    }
+
+    return constraints.constrain(Size(width, height));
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _applyAspectRatio(constraints);
+  }
+
+  @override
+  void performLayout() {
+    size = computeDryLayout(constraints);
+    if (child != null)
+      child!.layout(BoxConstraints.tight(size));
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('aspectRatio', aspectRatio));
+  }
+}
+
+/// Sizes its child to the child's maximum intrinsic width.
+///
+/// This class is useful, for example, when unlimited width is available and
+/// you would like a child that would otherwise attempt to expand infinitely to
+/// instead size itself to a more reasonable width.
+///
+/// The constraints that this object passes to its child will adhere to the
+/// parent's constraints, so if the constraints are not large enough to satisfy
+/// the child's maximum intrinsic width, then the child will get less width
+/// than it otherwise would. Likewise, if the minimum width constraint is
+/// larger than the child's maximum intrinsic width, the child will be given
+/// more width than it otherwise would.
+///
+/// If [stepWidth] is non-null, the child's width will be snapped to a multiple
+/// of the [stepWidth]. Similarly, if [stepHeight] is non-null, the child's
+/// height will be snapped to a multiple of the [stepHeight].
+///
+/// This class is relatively expensive, because it adds a speculative layout
+/// pass before the final layout phase. Avoid using it where possible. In the
+/// worst case, this render object can result in a layout that is O(N²) in the
+/// depth of the tree.
+///
+/// See also:
+///
+///  * [Align], a widget that aligns its child within itself. This can be used
+///    to loosen the constraints passed to the [RenderIntrinsicWidth],
+///    allowing the [RenderIntrinsicWidth]'s child to be smaller than that of
+///    its parent.
+///  * [Row], which when used with [CrossAxisAlignment.stretch] can be used
+///    to loosen just the width constraints that are passed to the
+///    [RenderIntrinsicWidth], allowing the [RenderIntrinsicWidth]'s child's
+///    width to be smaller than that of its parent.
+class RenderIntrinsicWidth extends RenderProxyBox {
+  /// Creates a render object that sizes itself to its child's intrinsic width.
+  ///
+  /// If [stepWidth] is non-null it must be > 0.0. Similarly If [stepHeight] is
+  /// non-null it must be > 0.0.
+  RenderIntrinsicWidth({
+    double? stepWidth,
+    double? stepHeight,
+    RenderBox? child,
+  }) : assert(stepWidth == null || stepWidth > 0.0),
+       assert(stepHeight == null || stepHeight > 0.0),
+       _stepWidth = stepWidth,
+       _stepHeight = stepHeight,
+       super(child);
+
+  /// If non-null, force the child's width to be a multiple of this value.
+  ///
+  /// This value must be null or > 0.0.
+  double? get stepWidth => _stepWidth;
+  double? _stepWidth;
+  set stepWidth(double? value) {
+    assert(value == null || value > 0.0);
+    if (value == _stepWidth)
+      return;
+    _stepWidth = value;
+    markNeedsLayout();
+  }
+
+  /// If non-null, force the child's height to be a multiple of this value.
+  ///
+  /// This value must be null or > 0.0.
+  double? get stepHeight => _stepHeight;
+  double? _stepHeight;
+  set stepHeight(double? value) {
+    assert(value == null || value > 0.0);
+    if (value == _stepHeight)
+      return;
+    _stepHeight = value;
+    markNeedsLayout();
+  }
+
+  static double _applyStep(double input, double? step) {
+    assert(input.isFinite);
+    if (step == null)
+      return input;
+    return (input / step).ceil() * step;
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    return computeMaxIntrinsicWidth(height);
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    if (child == null)
+      return 0.0;
+    final double width = child!.getMaxIntrinsicWidth(height);
+    return _applyStep(width, _stepWidth);
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    if (child == null)
+      return 0.0;
+    if (!width.isFinite)
+      width = computeMaxIntrinsicWidth(double.infinity);
+    assert(width.isFinite);
+    final double height = child!.getMinIntrinsicHeight(width);
+    return _applyStep(height, _stepHeight);
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    if (child == null)
+      return 0.0;
+    if (!width.isFinite)
+      width = computeMaxIntrinsicWidth(double.infinity);
+    assert(width.isFinite);
+    final double height = child!.getMaxIntrinsicHeight(width);
+    return _applyStep(height, _stepHeight);
+  }
+
+  Size _computeSize({required ChildLayouter layoutChild, required BoxConstraints constraints}) {
+    if (child != null) {
+      if (!constraints.hasTightWidth) {
+        final double width = child!.getMaxIntrinsicWidth(constraints.maxHeight);
+        assert(width.isFinite);
+        constraints = constraints.tighten(width: _applyStep(width, _stepWidth));
+      }
+      if (_stepHeight != null) {
+        final double height = child!.getMaxIntrinsicHeight(constraints.maxWidth);
+        assert(height.isFinite);
+        constraints = constraints.tighten(height: _applyStep(height, _stepHeight));
+      }
+      return layoutChild(child!, constraints);
+    } else {
+      return constraints.smallest;
+    }
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _computeSize(
+      layoutChild: ChildLayoutHelper.dryLayoutChild,
+      constraints: constraints,
+    );
+  }
+
+  @override
+  void performLayout() {
+    size = _computeSize(
+      layoutChild: ChildLayoutHelper.layoutChild,
+      constraints: constraints,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('stepWidth', stepWidth));
+    properties.add(DoubleProperty('stepHeight', stepHeight));
+  }
+}
+
+/// Sizes its child to the child's intrinsic height.
+///
+/// This class is useful, for example, when unlimited height is available and
+/// you would like a child that would otherwise attempt to expand infinitely to
+/// instead size itself to a more reasonable height.
+///
+/// The constraints that this object passes to its child will adhere to the
+/// parent's constraints, so if the constraints are not large enough to satisfy
+/// the child's maximum intrinsic height, then the child will get less height
+/// than it otherwise would. Likewise, if the minimum height constraint is
+/// larger than the child's maximum intrinsic height, the child will be given
+/// more height than it otherwise would.
+///
+/// This class is relatively expensive, because it adds a speculative layout
+/// pass before the final layout phase. Avoid using it where possible. In the
+/// worst case, this render object can result in a layout that is O(N²) in the
+/// depth of the tree.
+///
+/// See also:
+///
+///  * [Align], a widget that aligns its child within itself. This can be used
+///    to loosen the constraints passed to the [RenderIntrinsicHeight],
+///    allowing the [RenderIntrinsicHeight]'s child to be smaller than that of
+///    its parent.
+///  * [Column], which when used with [CrossAxisAlignment.stretch] can be used
+///    to loosen just the height constraints that are passed to the
+///    [RenderIntrinsicHeight], allowing the [RenderIntrinsicHeight]'s child's
+///    height to be smaller than that of its parent.
+class RenderIntrinsicHeight extends RenderProxyBox {
+  /// Creates a render object that sizes itself to its child's intrinsic height.
+  RenderIntrinsicHeight({
+    RenderBox? child,
+  }) : super(child);
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    if (child == null)
+      return 0.0;
+    if (!height.isFinite)
+      height = child!.getMaxIntrinsicHeight(double.infinity);
+    assert(height.isFinite);
+    return child!.getMinIntrinsicWidth(height);
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    if (child == null)
+      return 0.0;
+    if (!height.isFinite)
+      height = child!.getMaxIntrinsicHeight(double.infinity);
+    assert(height.isFinite);
+    return child!.getMaxIntrinsicWidth(height);
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    return computeMaxIntrinsicHeight(width);
+  }
+
+  Size _computeSize({required ChildLayouter layoutChild, required BoxConstraints constraints}) {
+    if (child != null) {
+      if (!constraints.hasTightHeight) {
+        final double height = child!.getMaxIntrinsicHeight(constraints.maxWidth);
+        assert(height.isFinite);
+        constraints = constraints.tighten(height: height);
+      }
+      return layoutChild(child!, constraints);
+    } else {
+      return constraints.smallest;
+    }
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _computeSize(
+      layoutChild: ChildLayoutHelper.dryLayoutChild,
+      constraints: constraints,
+    );
+  }
+
+  @override
+  void performLayout() {
+    size = _computeSize(
+      layoutChild: ChildLayoutHelper.layoutChild,
+      constraints: constraints,
+    );
+  }
+}
+
+/// Makes its child partially transparent.
+///
+/// This class paints its child into an intermediate buffer and then blends the
+/// child back into the scene partially transparent.
+///
+/// For values of opacity other than 0.0 and 1.0, this class is relatively
+/// expensive because it requires painting the child into an intermediate
+/// buffer. For the value 0.0, the child is simply not painted at all. For the
+/// value 1.0, the child is painted immediately without an intermediate buffer.
+class RenderOpacity extends RenderProxyBox {
+  /// Creates a partially transparent render object.
+  ///
+  /// The [opacity] argument must be between 0.0 and 1.0, inclusive.
+  RenderOpacity({
+    double opacity = 1.0,
+    bool alwaysIncludeSemantics = false,
+    RenderBox? child,
+  }) : assert(opacity != null),
+       assert(opacity >= 0.0 && opacity <= 1.0),
+       assert(alwaysIncludeSemantics != null),
+       _opacity = opacity,
+       _alwaysIncludeSemantics = alwaysIncludeSemantics,
+       _alpha = ui.Color.getAlphaFromOpacity(opacity),
+       super(child);
+
+  @override
+  bool get alwaysNeedsCompositing => child != null && (_alpha != 0 && _alpha != 255);
+
+  int _alpha;
+
+  /// The fraction to scale the child's alpha value.
+  ///
+  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
+  /// (i.e., invisible).
+  ///
+  /// The opacity must not be null.
+  ///
+  /// Values 1.0 and 0.0 are painted with a fast path. Other values
+  /// require painting the child into an intermediate buffer, which is
+  /// expensive.
+  double get opacity => _opacity;
+  double _opacity;
+  set opacity(double value) {
+    assert(value != null);
+    assert(value >= 0.0 && value <= 1.0);
+    if (_opacity == value)
+      return;
+    final bool didNeedCompositing = alwaysNeedsCompositing;
+    final bool wasVisible = _alpha != 0;
+    _opacity = value;
+    _alpha = ui.Color.getAlphaFromOpacity(_opacity);
+    if (didNeedCompositing != alwaysNeedsCompositing)
+      markNeedsCompositingBitsUpdate();
+    markNeedsPaint();
+    if (wasVisible != (_alpha != 0) && !alwaysIncludeSemantics)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// Whether child semantics are included regardless of the opacity.
+  ///
+  /// If false, semantics are excluded when [opacity] is 0.0.
+  ///
+  /// Defaults to false.
+  bool get alwaysIncludeSemantics => _alwaysIncludeSemantics;
+  bool _alwaysIncludeSemantics;
+  set alwaysIncludeSemantics(bool value) {
+    if (value == _alwaysIncludeSemantics)
+      return;
+    _alwaysIncludeSemantics = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null) {
+      if (_alpha == 0) {
+        // No need to keep the layer. We'll create a new one if necessary.
+        layer = null;
+        return;
+      }
+      if (_alpha == 255) {
+        // No need to keep the layer. We'll create a new one if necessary.
+        layer = null;
+        context.paintChild(child!, offset);
+        return;
+      }
+      assert(needsCompositing);
+      layer = context.pushOpacity(offset, _alpha, super.paint, oldLayer: layer as OpacityLayer?);
+    }
+  }
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    if (child != null && (_alpha != 0 || alwaysIncludeSemantics))
+      visitor(child!);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('opacity', opacity));
+    properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
+  }
+}
+
+/// Implementation of [RenderAnimatedOpacity] and [RenderSliverAnimatedOpacity].
+///
+/// This mixin allows the logic of animating opacity to be used with different
+/// layout models, e.g. the way that [RenderAnimatedOpacity] uses it for [RenderBox]
+/// and [RenderSliverAnimatedOpacity] uses it for [RenderSliver].
+mixin RenderAnimatedOpacityMixin<T extends RenderObject> on RenderObjectWithChildMixin<T> {
+  int? _alpha;
+
+  @override
+  bool get alwaysNeedsCompositing => child != null && _currentlyNeedsCompositing!;
+  bool? _currentlyNeedsCompositing;
+
+  /// The animation that drives this render object's opacity.
+  ///
+  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
+  /// (i.e., invisible).
+  ///
+  /// To change the opacity of a child in a static manner, not animated,
+  /// consider [RenderOpacity] instead.
+  ///
+  /// This getter cannot be read until the value has been set. It should be set
+  /// by the constructor of the class in which this mixin is included.
+  Animation<double> get opacity => _opacity!;
+  Animation<double>? _opacity;
+  set opacity(Animation<double> value) {
+    assert(value != null);
+    if (_opacity == value)
+      return;
+    if (attached && _opacity != null)
+      opacity.removeListener(_updateOpacity);
+    _opacity = value;
+    if (attached)
+      opacity.addListener(_updateOpacity);
+    _updateOpacity();
+  }
+
+  /// Whether child semantics are included regardless of the opacity.
+  ///
+  /// If false, semantics are excluded when [opacity] is 0.0.
+  ///
+  /// Defaults to false.
+  ///
+  /// This getter cannot be read until the value has been set. It should be set
+  /// by the constructor of the class in which this mixin is included.
+  bool get alwaysIncludeSemantics => _alwaysIncludeSemantics!;
+  bool? _alwaysIncludeSemantics;
+  set alwaysIncludeSemantics(bool value) {
+    if (value == _alwaysIncludeSemantics)
+      return;
+    _alwaysIncludeSemantics = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    opacity.addListener(_updateOpacity);
+    _updateOpacity(); // in case it changed while we weren't listening
+  }
+
+  @override
+  void detach() {
+    opacity.removeListener(_updateOpacity);
+    super.detach();
+  }
+
+  void _updateOpacity() {
+    final int? oldAlpha = _alpha;
+    _alpha = ui.Color.getAlphaFromOpacity(opacity.value);
+    if (oldAlpha != _alpha) {
+      final bool? didNeedCompositing = _currentlyNeedsCompositing;
+      _currentlyNeedsCompositing = _alpha! > 0 && _alpha! < 255;
+      if (child != null && didNeedCompositing != _currentlyNeedsCompositing)
+        markNeedsCompositingBitsUpdate();
+      markNeedsPaint();
+      if (oldAlpha == 0 || _alpha == 0)
+        markNeedsSemanticsUpdate();
+    }
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null) {
+      if (_alpha == 0) {
+        // No need to keep the layer. We'll create a new one if necessary.
+        layer = null;
+        return;
+      }
+      if (_alpha == 255) {
+        // No need to keep the layer. We'll create a new one if necessary.
+        layer = null;
+        context.paintChild(child!, offset);
+        return;
+      }
+      assert(needsCompositing);
+      layer = context.pushOpacity(offset, _alpha!, super.paint, oldLayer: layer as OpacityLayer?);
+    }
+  }
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    if (child != null && (_alpha != 0 || alwaysIncludeSemantics))
+      visitor(child!);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Animation<double>>('opacity', opacity));
+    properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
+  }
+}
+
+/// Makes its child partially transparent, driven from an [Animation].
+///
+/// This is a variant of [RenderOpacity] that uses an [Animation<double>] rather
+/// than a [double] to control the opacity.
+class RenderAnimatedOpacity extends RenderProxyBox with RenderProxyBoxMixin, RenderAnimatedOpacityMixin<RenderBox> {
+  /// Creates a partially transparent render object.
+  ///
+  /// The [opacity] argument must not be null.
+  RenderAnimatedOpacity({
+    required Animation<double> opacity,
+    bool alwaysIncludeSemantics = false,
+    RenderBox? child,
+  }) : assert(opacity != null),
+       assert(alwaysIncludeSemantics != null),
+       super(child) {
+    this.opacity = opacity;
+    this.alwaysIncludeSemantics = alwaysIncludeSemantics;
+  }
+}
+
+/// Signature for a function that creates a [Shader] for a given [Rect].
+///
+/// Used by [RenderShaderMask] and the [ShaderMask] widget.
+typedef ShaderCallback = Shader Function(Rect bounds);
+
+/// Applies a mask generated by a [Shader] to its child.
+///
+/// For example, [RenderShaderMask] can be used to gradually fade out the edge
+/// of a child by using a [new ui.Gradient.linear] mask.
+class RenderShaderMask extends RenderProxyBox {
+  /// Creates a render object that applies a mask generated by a [Shader] to its child.
+  ///
+  /// The [shaderCallback] and [blendMode] arguments must not be null.
+  RenderShaderMask({
+    RenderBox? child,
+    required ShaderCallback shaderCallback,
+    BlendMode blendMode = BlendMode.modulate,
+  }) : assert(shaderCallback != null),
+       assert(blendMode != null),
+       _shaderCallback = shaderCallback,
+       _blendMode = blendMode,
+       super(child);
+
+  @override
+  ShaderMaskLayer? get layer => super.layer as ShaderMaskLayer?;
+
+  /// Called to creates the [Shader] that generates the mask.
+  ///
+  /// The shader callback is called with the current size of the child so that
+  /// it can customize the shader to the size and location of the child.
+  ///
+  /// The rectangle will always be at the origin when called by
+  /// [RenderShaderMask].
+  // TODO(abarth): Use the delegate pattern here to avoid generating spurious
+  // repaints when the ShaderCallback changes identity.
+  ShaderCallback get shaderCallback => _shaderCallback;
+  ShaderCallback _shaderCallback;
+  set shaderCallback(ShaderCallback value) {
+    assert(value != null);
+    if (_shaderCallback == value)
+      return;
+    _shaderCallback = value;
+    markNeedsPaint();
+  }
+
+  /// The [BlendMode] to use when applying the shader to the child.
+  ///
+  /// The default, [BlendMode.modulate], is useful for applying an alpha blend
+  /// to the child. Other blend modes can be used to create other effects.
+  BlendMode get blendMode => _blendMode;
+  BlendMode _blendMode;
+  set blendMode(BlendMode value) {
+    assert(value != null);
+    if (_blendMode == value)
+      return;
+    _blendMode = value;
+    markNeedsPaint();
+  }
+
+  @override
+  bool get alwaysNeedsCompositing => child != null;
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null) {
+      assert(needsCompositing);
+      layer ??= ShaderMaskLayer();
+      layer!
+        ..shader = _shaderCallback(Offset.zero & size)
+        ..maskRect = offset & size
+        ..blendMode = _blendMode;
+      context.pushLayer(layer!, super.paint, offset);
+    } else {
+      layer = null;
+    }
+  }
+}
+
+/// Applies a filter to the existing painted content and then paints [child].
+///
+/// This effect is relatively expensive, especially if the filter is non-local,
+/// such as a blur.
+class RenderBackdropFilter extends RenderProxyBox {
+  /// Creates a backdrop filter.
+  ///
+  /// The [filter] argument must not be null.
+  RenderBackdropFilter({ RenderBox? child, required ui.ImageFilter filter })
+    : assert(filter != null),
+      _filter = filter,
+      super(child);
+
+  @override
+  BackdropFilterLayer? get layer => super.layer as BackdropFilterLayer?;
+
+  /// The image filter to apply to the existing painted content before painting
+  /// the child.
+  ///
+  /// For example, consider using [new ui.ImageFilter.blur] to create a backdrop
+  /// blur effect.
+  ui.ImageFilter get filter => _filter;
+  ui.ImageFilter _filter;
+  set filter(ui.ImageFilter value) {
+    assert(value != null);
+    if (_filter == value)
+      return;
+    _filter = value;
+    markNeedsPaint();
+  }
+
+  @override
+  bool get alwaysNeedsCompositing => child != null;
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null) {
+      assert(needsCompositing);
+      layer ??= BackdropFilterLayer();
+      layer!.filter = _filter;
+      context.pushLayer(layer!, super.paint, offset);
+    } else {
+      layer = null;
+    }
+  }
+}
+
+/// An interface for providing custom clips.
+///
+/// This class is used by a number of clip widgets (e.g., [ClipRect] and
+/// [ClipPath]).
+///
+/// The [getClip] method is called whenever the custom clip needs to be updated.
+///
+/// The [shouldReclip] method is called when a new instance of the class
+/// is provided, to check if the new instance actually represents different
+/// information.
+///
+/// The most efficient way to update the clip provided by this class is to
+/// supply a `reclip` argument to the constructor of the [CustomClipper]. The
+/// custom object will listen to this animation and update the clip whenever the
+/// animation ticks, avoiding both the build and layout phases of the pipeline.
+///
+/// See also:
+///
+///  * [ClipRect], which can be customized with a [CustomClipper<Rect>].
+///  * [ClipRRect], which can be customized with a [CustomClipper<RRect>].
+///  * [ClipOval], which can be customized with a [CustomClipper<Rect>].
+///  * [ClipPath], which can be customized with a [CustomClipper<Path>].
+///  * [ShapeBorderClipper], for specifying a clip path using a [ShapeBorder].
+abstract class CustomClipper<T> extends Listenable {
+  /// Creates a custom clipper.
+  ///
+  /// The clipper will update its clip whenever [reclip] notifies its listeners.
+  const CustomClipper({ Listenable? reclip }) : _reclip = reclip;
+
+  final Listenable? _reclip;
+
+  /// Register a closure to be notified when it is time to reclip.
+  ///
+  /// The [CustomClipper] implementation merely forwards to the same method on
+  /// the [Listenable] provided to the constructor in the `reclip` argument, if
+  /// it was not null.
+  @override
+  void addListener(VoidCallback listener) => _reclip?.addListener(listener);
+
+  /// Remove a previously registered closure from the list of closures that the
+  /// object notifies when it is time to reclip.
+  ///
+  /// The [CustomClipper] implementation merely forwards to the same method on
+  /// the [Listenable] provided to the constructor in the `reclip` argument, if
+  /// it was not null.
+  @override
+  void removeListener(VoidCallback listener) => _reclip?.removeListener(listener);
+
+  /// Returns a description of the clip given that the render object being
+  /// clipped is of the given size.
+  T getClip(Size size);
+
+  /// Returns an approximation of the clip returned by [getClip], as
+  /// an axis-aligned Rect. This is used by the semantics layer to
+  /// determine whether widgets should be excluded.
+  ///
+  /// By default, this returns a rectangle that is the same size as
+  /// the RenderObject. If getClip returns a shape that is roughly the
+  /// same size as the RenderObject (e.g. it's a rounded rectangle
+  /// with very small arcs in the corners), then this may be adequate.
+  Rect getApproximateClipRect(Size size) => Offset.zero & size;
+
+  /// Called whenever a new instance of the custom clipper delegate class is
+  /// provided to the clip object, or any time that a new clip object is created
+  /// with a new instance of the custom clipper delegate class (which amounts to
+  /// the same thing, because the latter is implemented in terms of the former).
+  ///
+  /// If the new instance represents different information than the old
+  /// instance, then the method should return true, otherwise it should return
+  /// false.
+  ///
+  /// If the method returns false, then the [getClip] call might be optimized
+  /// away.
+  ///
+  /// It's possible that the [getClip] method will get called even if
+  /// [shouldReclip] returns false or if the [shouldReclip] method is never
+  /// called at all (e.g. if the box changes size).
+  bool shouldReclip(covariant CustomClipper<T> oldClipper);
+
+  @override
+  String toString() => objectRuntimeType(this, 'CustomClipper');
+}
+
+/// A [CustomClipper] that clips to the outer path of a [ShapeBorder].
+class ShapeBorderClipper extends CustomClipper<Path> {
+  /// Creates a [ShapeBorder] clipper.
+  ///
+  /// The [shape] argument must not be null.
+  ///
+  /// The [textDirection] argument must be provided non-null if [shape]
+  /// has a text direction dependency (for example if it is expressed in terms
+  /// of "start" and "end" instead of "left" and "right"). It may be null if
+  /// the border will not need the text direction to paint itself.
+  const ShapeBorderClipper({
+    required this.shape,
+    this.textDirection,
+  }) : assert(shape != null);
+
+  /// The shape border whose outer path this clipper clips to.
+  final ShapeBorder shape;
+
+  /// The text direction to use for getting the outer path for [shape].
+  ///
+  /// [ShapeBorder]s can depend on the text direction (e.g having a "dent"
+  /// towards the start of the shape).
+  final TextDirection? textDirection;
+
+  /// Returns the outer path of [shape] as the clip.
+  @override
+  Path getClip(Size size) {
+    return shape.getOuterPath(Offset.zero & size, textDirection: textDirection);
+  }
+
+  @override
+  bool shouldReclip(CustomClipper<Path> oldClipper) {
+    if (oldClipper.runtimeType != ShapeBorderClipper)
+      return true;
+    final ShapeBorderClipper typedOldClipper = oldClipper as ShapeBorderClipper;
+    return typedOldClipper.shape != shape
+        || typedOldClipper.textDirection != textDirection;
+  }
+}
+
+abstract class _RenderCustomClip<T> extends RenderProxyBox {
+  _RenderCustomClip({
+    RenderBox? child,
+    CustomClipper<T>? clipper,
+    Clip clipBehavior = Clip.antiAlias,
+  }) : assert(clipBehavior != null),
+       _clipper = clipper,
+       _clipBehavior = clipBehavior,
+       super(child);
+
+  /// If non-null, determines which clip to use on the child.
+  CustomClipper<T>? get clipper => _clipper;
+  CustomClipper<T>? _clipper;
+  set clipper(CustomClipper<T>? newClipper) {
+    if (_clipper == newClipper)
+      return;
+    final CustomClipper<T>? oldClipper = _clipper;
+    _clipper = newClipper;
+    assert(newClipper != null || oldClipper != null);
+    if (newClipper == null || oldClipper == null ||
+        newClipper.runtimeType != oldClipper.runtimeType ||
+        newClipper.shouldReclip(oldClipper)) {
+      _markNeedsClip();
+    }
+    if (attached) {
+      oldClipper?.removeListener(_markNeedsClip);
+      newClipper?.addListener(_markNeedsClip);
+    }
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    _clipper?.addListener(_markNeedsClip);
+  }
+
+  @override
+  void detach() {
+    _clipper?.removeListener(_markNeedsClip);
+    super.detach();
+  }
+
+  void _markNeedsClip() {
+    _clip = null;
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  T get _defaultClip;
+  T? _clip;
+
+  Clip get clipBehavior => _clipBehavior;
+  set clipBehavior(Clip value) {
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsPaint();
+    }
+  }
+  Clip _clipBehavior;
+
+  @override
+  void performLayout() {
+    final Size? oldSize = hasSize ? size : null;
+    super.performLayout();
+    if (oldSize != size)
+      _clip = null;
+  }
+
+  void _updateClip() {
+    _clip ??= _clipper?.getClip(size) ?? _defaultClip;
+  }
+
+  @override
+  Rect describeApproximatePaintClip(RenderObject child) {
+    return _clipper?.getApproximateClipRect(size) ?? Offset.zero & size;
+  }
+
+  Paint? _debugPaint;
+  TextPainter? _debugText;
+  @override
+  void debugPaintSize(PaintingContext context, Offset offset) {
+    assert(() {
+      _debugPaint ??= Paint()
+        ..shader = ui.Gradient.linear(
+          const Offset(0.0, 0.0),
+          const Offset(10.0, 10.0),
+          <Color>[const Color(0x00000000), const Color(0xFFFF00FF), const Color(0xFFFF00FF), const Color(0x00000000)],
+          <double>[0.25, 0.25, 0.75, 0.75],
+          TileMode.repeated,
+        )
+        ..strokeWidth = 2.0
+        ..style = PaintingStyle.stroke;
+      _debugText ??= TextPainter(
+        text: const TextSpan(
+          text: '✂',
+          style: TextStyle(
+            color: Color(0xFFFF00FF),
+              fontSize: 14.0,
+            ),
+          ),
+          textDirection: TextDirection.rtl, // doesn't matter, it's one character
+        )
+        ..layout();
+      return true;
+    }());
+  }
+}
+
+/// Clips its child using a rectangle.
+///
+/// By default, [RenderClipRect] prevents its child from painting outside its
+/// bounds, but the size and location of the clip rect can be customized using a
+/// custom [clipper].
+class RenderClipRect extends _RenderCustomClip<Rect> {
+  /// Creates a rectangular clip.
+  ///
+  /// If [clipper] is null, the clip will match the layout size and position of
+  /// the child.
+  ///
+  /// The [clipBehavior] must not be null or [Clip.none].
+  RenderClipRect({
+    RenderBox? child,
+    CustomClipper<Rect>? clipper,
+    Clip clipBehavior = Clip.antiAlias,
+  }) : assert(clipBehavior != null),
+       assert(clipBehavior != Clip.none),
+       super(child: child, clipper: clipper, clipBehavior: clipBehavior);
+
+  @override
+  Rect get _defaultClip => Offset.zero & size;
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    if (_clipper != null) {
+      _updateClip();
+      assert(_clip != null);
+      if (!_clip!.contains(position))
+        return false;
+    }
+    return super.hitTest(result, position: position);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null) {
+      _updateClip();
+      layer = context.pushClipRect(
+        needsCompositing,
+        offset,
+        _clip!,
+        super.paint,
+        clipBehavior: clipBehavior,
+        oldLayer: layer as ClipRectLayer?,
+      );
+    } else {
+      layer = null;
+    }
+  }
+
+  @override
+  void debugPaintSize(PaintingContext context, Offset offset) {
+    assert(() {
+      if (child != null) {
+        super.debugPaintSize(context, offset);
+        context.canvas.drawRect(_clip!.shift(offset), _debugPaint!);
+        _debugText!.paint(context.canvas, offset + Offset(_clip!.width / 8.0, -_debugText!.text!.style!.fontSize! * 1.1));
+      }
+      return true;
+    }());
+  }
+}
+
+/// Clips its child using a rounded rectangle.
+///
+/// By default, [RenderClipRRect] uses its own bounds as the base rectangle for
+/// the clip, but the size and location of the clip can be customized using a
+/// custom [clipper].
+class RenderClipRRect extends _RenderCustomClip<RRect> {
+  /// Creates a rounded-rectangular clip.
+  ///
+  /// The [borderRadius] defaults to [BorderRadius.zero], i.e. a rectangle with
+  /// right-angled corners.
+  ///
+  /// If [clipper] is non-null, then [borderRadius] is ignored.
+  ///
+  /// The [clipBehavior] argument must not be null or [Clip.none].
+  RenderClipRRect({
+    RenderBox? child,
+    BorderRadius borderRadius = BorderRadius.zero,
+    CustomClipper<RRect>? clipper,
+    Clip clipBehavior = Clip.antiAlias,
+  }) : assert(clipBehavior != null),
+       assert(clipBehavior != Clip.none),
+       _borderRadius = borderRadius,
+       super(child: child, clipper: clipper, clipBehavior: clipBehavior) {
+    // `_borderRadius` has a non-nullable return type, but might be null when
+    // running with weak checking, so we need to null check it anyway (and
+    // ignore the warning that the null-handling logic is dead code).
+    assert(_borderRadius != null || clipper != null);// ignore: dead_code
+  }
+
+  /// The border radius of the rounded corners.
+  ///
+  /// Values are clamped so that horizontal and vertical radii sums do not
+  /// exceed width/height.
+  ///
+  /// This value is ignored if [clipper] is non-null.
+  BorderRadius get borderRadius => _borderRadius;
+  BorderRadius _borderRadius;
+  set borderRadius(BorderRadius value) {
+    assert(value != null);
+    if (_borderRadius == value)
+      return;
+    _borderRadius = value;
+    _markNeedsClip();
+  }
+
+  @override
+  RRect get _defaultClip => _borderRadius.toRRect(Offset.zero & size);
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    if (_clipper != null) {
+      _updateClip();
+      assert(_clip != null);
+      if (!_clip!.contains(position))
+        return false;
+    }
+    return super.hitTest(result, position: position);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null) {
+      _updateClip();
+      layer = context.pushClipRRect(
+        needsCompositing,
+        offset,
+        _clip!.outerRect,
+        _clip!,
+        super.paint, clipBehavior: clipBehavior, oldLayer: layer as ClipRRectLayer?,
+      );
+    } else {
+      layer = null;
+    }
+  }
+
+  @override
+  void debugPaintSize(PaintingContext context, Offset offset) {
+    assert(() {
+      if (child != null) {
+        super.debugPaintSize(context, offset);
+        context.canvas.drawRRect(_clip!.shift(offset), _debugPaint!);
+        _debugText!.paint(context.canvas, offset + Offset(_clip!.tlRadiusX, -_debugText!.text!.style!.fontSize! * 1.1));
+      }
+      return true;
+    }());
+  }
+}
+
+/// Clips its child using an oval.
+///
+/// By default, inscribes an axis-aligned oval into its layout dimensions and
+/// prevents its child from painting outside that oval, but the size and
+/// location of the clip oval can be customized using a custom [clipper].
+class RenderClipOval extends _RenderCustomClip<Rect> {
+  /// Creates an oval-shaped clip.
+  ///
+  /// If [clipper] is null, the oval will be inscribed into the layout size and
+  /// position of the child.
+  ///
+  /// The [clipBehavior] argument must not be null or [Clip.none].
+  RenderClipOval({
+    RenderBox? child,
+    CustomClipper<Rect>? clipper,
+    Clip clipBehavior = Clip.antiAlias,
+  }) : assert(clipBehavior != null),
+       assert(clipBehavior != Clip.none),
+       super(child: child, clipper: clipper, clipBehavior: clipBehavior);
+
+  Rect? _cachedRect;
+  late Path _cachedPath;
+
+  Path _getClipPath(Rect rect) {
+    if (rect != _cachedRect) {
+      _cachedRect = rect;
+      _cachedPath = Path()..addOval(_cachedRect!);
+    }
+    return _cachedPath;
+  }
+
+  @override
+  Rect get _defaultClip => Offset.zero & size;
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    _updateClip();
+    assert(_clip != null);
+    final Offset center = _clip!.center;
+    // convert the position to an offset from the center of the unit circle
+    final Offset offset = Offset((position.dx - center.dx) / _clip!.width,
+                                     (position.dy - center.dy) / _clip!.height);
+    // check if the point is outside the unit circle
+    if (offset.distanceSquared > 0.25) // x^2 + y^2 > r^2
+      return false;
+    return super.hitTest(result, position: position);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null) {
+      _updateClip();
+      layer = context.pushClipPath(
+        needsCompositing,
+        offset,
+        _clip!,
+        _getClipPath(_clip!),
+        super.paint,
+        clipBehavior: clipBehavior,
+        oldLayer: layer as ClipPathLayer?,
+      );
+    } else {
+      layer = null;
+    }
+  }
+
+  @override
+  void debugPaintSize(PaintingContext context, Offset offset) {
+    assert(() {
+      if (child != null) {
+        super.debugPaintSize(context, offset);
+        context.canvas.drawPath(_getClipPath(_clip!).shift(offset), _debugPaint!);
+        _debugText!.paint(context.canvas, offset + Offset((_clip!.width - _debugText!.width) / 2.0, -_debugText!.text!.style!.fontSize! * 1.1));
+      }
+      return true;
+    }());
+  }
+}
+
+/// Clips its child using a path.
+///
+/// Takes a delegate whose primary method returns a path that should
+/// be used to prevent the child from painting outside the path.
+///
+/// Clipping to a path is expensive. Certain shapes have more
+/// optimized render objects:
+///
+///  * To clip to a rectangle, consider [RenderClipRect].
+///  * To clip to an oval or circle, consider [RenderClipOval].
+///  * To clip to a rounded rectangle, consider [RenderClipRRect].
+class RenderClipPath extends _RenderCustomClip<Path> {
+  /// Creates a path clip.
+  ///
+  /// If [clipper] is null, the clip will be a rectangle that matches the layout
+  /// size and location of the child. However, rather than use this default,
+  /// consider using a [RenderClipRect], which can achieve the same effect more
+  /// efficiently.
+  ///
+  /// The [clipBehavior] argument must not be null or [Clip.none].
+  RenderClipPath({
+    RenderBox? child,
+    CustomClipper<Path>? clipper,
+    Clip clipBehavior = Clip.antiAlias,
+  }) : assert(clipBehavior != null),
+       assert(clipBehavior != Clip.none),
+       super(child: child, clipper: clipper, clipBehavior: clipBehavior);
+
+  @override
+  Path get _defaultClip => Path()..addRect(Offset.zero & size);
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    if (_clipper != null) {
+      _updateClip();
+      assert(_clip != null);
+      if (!_clip!.contains(position))
+        return false;
+    }
+    return super.hitTest(result, position: position);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null) {
+      _updateClip();
+      layer = context.pushClipPath(
+        needsCompositing,
+        offset,
+        Offset.zero & size,
+        _clip!,
+        super.paint,
+        clipBehavior: clipBehavior,
+        oldLayer: layer as ClipPathLayer?,
+      );
+    } else {
+      layer = null;
+    }
+  }
+
+  @override
+  void debugPaintSize(PaintingContext context, Offset offset) {
+    assert(() {
+      if (child != null) {
+        super.debugPaintSize(context, offset);
+        context.canvas.drawPath(_clip!.shift(offset), _debugPaint!);
+        _debugText!.paint(context.canvas, offset);
+      }
+      return true;
+    }());
+  }
+}
+
+/// A physical model layer casts a shadow based on its [elevation].
+///
+/// The concrete implementations [RenderPhysicalModel] and [RenderPhysicalShape]
+/// determine the actual shape of the physical model.
+abstract class _RenderPhysicalModelBase<T> extends _RenderCustomClip<T> {
+  /// The [shape], [elevation], [color], and [shadowColor] must not be null.
+  /// Additionally, the [elevation] must be non-negative.
+  _RenderPhysicalModelBase({
+    required RenderBox? child,
+    required double elevation,
+    required Color color,
+    required Color shadowColor,
+    Clip clipBehavior = Clip.none,
+    CustomClipper<T>? clipper,
+  }) : assert(elevation != null && elevation >= 0.0),
+       assert(color != null),
+       assert(shadowColor != null),
+       assert(clipBehavior != null),
+       _elevation = elevation,
+       _color = color,
+       _shadowColor = shadowColor,
+       super(child: child, clipBehavior: clipBehavior, clipper: clipper);
+
+  /// The z-coordinate relative to the parent at which to place this material.
+  ///
+  /// The value is non-negative.
+  ///
+  /// If [debugDisableShadows] is set, this value is ignored and no shadow is
+  /// drawn (an outline is rendered instead).
+  double get elevation => _elevation;
+  double _elevation;
+  set elevation(double value) {
+    assert(value != null && value >= 0.0);
+    if (elevation == value)
+      return;
+    final bool didNeedCompositing = alwaysNeedsCompositing;
+    _elevation = value;
+    if (didNeedCompositing != alwaysNeedsCompositing)
+      markNeedsCompositingBitsUpdate();
+    markNeedsPaint();
+  }
+
+  /// The shadow color.
+  Color get shadowColor => _shadowColor;
+  Color _shadowColor;
+  set shadowColor(Color value) {
+    assert(value != null);
+    if (shadowColor == value)
+      return;
+    _shadowColor = value;
+    markNeedsPaint();
+  }
+
+  /// The background color.
+  Color get color => _color;
+  Color _color;
+  set color(Color value) {
+    assert(value != null);
+    if (color == value)
+      return;
+    _color = value;
+    markNeedsPaint();
+  }
+
+  @override
+  bool get alwaysNeedsCompositing => true;
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    config.elevation = elevation;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(DoubleProperty('elevation', elevation));
+    description.add(ColorProperty('color', color));
+    description.add(ColorProperty('shadowColor', color));
+  }
+}
+
+/// Creates a physical model layer that clips its child to a rounded
+/// rectangle.
+///
+/// A physical model layer casts a shadow based on its [elevation].
+class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> {
+  /// Creates a rounded-rectangular clip.
+  ///
+  /// The [color] is required.
+  ///
+  /// The [shape], [elevation], [color], [clipBehavior], and [shadowColor]
+  /// arguments must not be null. Additionally, the [elevation] must be
+  /// non-negative.
+  RenderPhysicalModel({
+    RenderBox? child,
+    BoxShape shape = BoxShape.rectangle,
+    Clip clipBehavior = Clip.none,
+    BorderRadius? borderRadius,
+    double elevation = 0.0,
+    required Color color,
+    Color shadowColor = const Color(0xFF000000),
+  }) : assert(shape != null),
+       assert(clipBehavior != null),
+       assert(elevation != null && elevation >= 0.0),
+       assert(color != null),
+       assert(shadowColor != null),
+       _shape = shape,
+       _borderRadius = borderRadius,
+       super(
+         clipBehavior: clipBehavior,
+         child: child,
+         elevation: elevation,
+         color: color,
+         shadowColor: shadowColor,
+       );
+
+  @override
+  PhysicalModelLayer? get layer => super.layer as PhysicalModelLayer?;
+
+  /// The shape of the layer.
+  ///
+  /// Defaults to [BoxShape.rectangle]. The [borderRadius] affects the corners
+  /// of the rectangle.
+  BoxShape get shape => _shape;
+  BoxShape _shape;
+  set shape(BoxShape value) {
+    assert(value != null);
+    if (shape == value)
+      return;
+    _shape = value;
+    _markNeedsClip();
+  }
+
+  /// The border radius of the rounded corners.
+  ///
+  /// Values are clamped so that horizontal and vertical radii sums do not
+  /// exceed width/height.
+  ///
+  /// This property is ignored if the [shape] is not [BoxShape.rectangle].
+  ///
+  /// The value null is treated like [BorderRadius.zero].
+  BorderRadius? get borderRadius => _borderRadius;
+  BorderRadius? _borderRadius;
+  set borderRadius(BorderRadius? value) {
+    if (borderRadius == value)
+      return;
+    _borderRadius = value;
+    _markNeedsClip();
+  }
+
+  @override
+  RRect get _defaultClip {
+    assert(hasSize);
+    assert(_shape != null);
+    switch (_shape) {
+      case BoxShape.rectangle:
+        return (borderRadius ?? BorderRadius.zero).toRRect(Offset.zero & size);
+      case BoxShape.circle:
+        final Rect rect = Offset.zero & size;
+        return RRect.fromRectXY(rect, rect.width / 2, rect.height / 2);
+    }
+  }
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    if (_clipper != null) {
+      _updateClip();
+      assert(_clip != null);
+      if (!_clip!.contains(position))
+        return false;
+    }
+    return super.hitTest(result, position: position);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null) {
+      _updateClip();
+      final RRect offsetRRect = _clip!.shift(offset);
+      final Rect offsetBounds = offsetRRect.outerRect;
+      final Path offsetRRectAsPath = Path()..addRRect(offsetRRect);
+      bool paintShadows = true;
+      assert(() {
+        if (debugDisableShadows) {
+          if (elevation > 0.0) {
+            context.canvas.drawRRect(
+              offsetRRect,
+              Paint()
+                ..color = shadowColor
+                ..style = PaintingStyle.stroke
+                ..strokeWidth = elevation * 2.0,
+            );
+          }
+          paintShadows = false;
+        }
+        return true;
+      }());
+      layer ??= PhysicalModelLayer();
+      layer!
+        ..clipPath = offsetRRectAsPath
+        ..clipBehavior = clipBehavior
+        ..elevation = paintShadows ? elevation : 0.0
+        ..color = color
+        ..shadowColor = shadowColor;
+      context.pushLayer(layer!, super.paint, offset, childPaintBounds: offsetBounds);
+      assert(() {
+        layer!.debugCreator = debugCreator;
+        return true;
+      }());
+    } else {
+      layer = null;
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(DiagnosticsProperty<BoxShape>('shape', shape));
+    description.add(DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius));
+  }
+}
+
+/// Creates a physical shape layer that clips its child to a [Path].
+///
+/// A physical shape layer casts a shadow based on its [elevation].
+///
+/// See also:
+///
+///  * [RenderPhysicalModel], which is optimized for rounded rectangles and
+///    circles.
+class RenderPhysicalShape extends _RenderPhysicalModelBase<Path> {
+  /// Creates an arbitrary shape clip.
+  ///
+  /// The [color] and [clipper] parameters are required.
+  ///
+  /// The [clipper], [elevation], [color] and [shadowColor] must not be null.
+  /// Additionally, the [elevation] must be non-negative.
+  RenderPhysicalShape({
+    RenderBox? child,
+    required CustomClipper<Path> clipper,
+    Clip clipBehavior = Clip.none,
+    double elevation = 0.0,
+    required Color color,
+    Color shadowColor = const Color(0xFF000000),
+  }) : assert(clipper != null),
+       assert(elevation != null && elevation >= 0.0),
+       assert(color != null),
+       assert(shadowColor != null),
+       super(
+         child: child,
+         elevation: elevation,
+         color: color,
+         shadowColor: shadowColor,
+         clipper: clipper,
+         clipBehavior: clipBehavior,
+       );
+
+  @override
+  PhysicalModelLayer? get layer => super.layer as PhysicalModelLayer?;
+
+  @override
+  Path get _defaultClip => Path()..addRect(Offset.zero & size);
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    if (_clipper != null) {
+      _updateClip();
+      assert(_clip != null);
+      if (!_clip!.contains(position))
+        return false;
+    }
+    return super.hitTest(result, position: position);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null) {
+      _updateClip();
+      final Rect offsetBounds = offset & size;
+      final Path offsetPath = _clip!.shift(offset);
+      bool paintShadows = true;
+      assert(() {
+        if (debugDisableShadows) {
+          if (elevation > 0.0) {
+            context.canvas.drawPath(
+              offsetPath,
+              Paint()
+                ..color = shadowColor
+                ..style = PaintingStyle.stroke
+                ..strokeWidth = elevation * 2.0,
+            );
+          }
+          paintShadows = false;
+        }
+        return true;
+      }());
+      layer ??= PhysicalModelLayer();
+      layer!
+        ..clipPath = offsetPath
+        ..clipBehavior = clipBehavior
+        ..elevation = paintShadows ? elevation : 0.0
+        ..color = color
+        ..shadowColor = shadowColor;
+      context.pushLayer(layer!, super.paint, offset, childPaintBounds: offsetBounds);
+      assert(() {
+        layer!.debugCreator = debugCreator;
+        return true;
+      }());
+    } else {
+      layer = null;
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(DiagnosticsProperty<CustomClipper<Path>>('clipper', clipper));
+  }
+}
+
+/// Where to paint a box decoration.
+enum DecorationPosition {
+  /// Paint the box decoration behind the children.
+  background,
+
+  /// Paint the box decoration in front of the children.
+  foreground,
+}
+
+/// Paints a [Decoration] either before or after its child paints.
+class RenderDecoratedBox extends RenderProxyBox {
+  /// Creates a decorated box.
+  ///
+  /// The [decoration], [position], and [configuration] arguments must not be
+  /// null. By default the decoration paints behind the child.
+  ///
+  /// The [ImageConfiguration] will be passed to the decoration (with the size
+  /// filled in) to let it resolve images.
+  RenderDecoratedBox({
+    required Decoration decoration,
+    DecorationPosition position = DecorationPosition.background,
+    ImageConfiguration configuration = ImageConfiguration.empty,
+    RenderBox? child,
+  }) : assert(decoration != null),
+       assert(position != null),
+       assert(configuration != null),
+       _decoration = decoration,
+       _position = position,
+       _configuration = configuration,
+       super(child);
+
+  BoxPainter? _painter;
+
+  /// What decoration to paint.
+  ///
+  /// Commonly a [BoxDecoration].
+  Decoration get decoration => _decoration;
+  Decoration _decoration;
+  set decoration(Decoration value) {
+    assert(value != null);
+    if (value == _decoration)
+      return;
+    _painter?.dispose();
+    _painter = null;
+    _decoration = value;
+    markNeedsPaint();
+  }
+
+  /// Whether to paint the box decoration behind or in front of the child.
+  DecorationPosition get position => _position;
+  DecorationPosition _position;
+  set position(DecorationPosition value) {
+    assert(value != null);
+    if (value == _position)
+      return;
+    _position = value;
+    markNeedsPaint();
+  }
+
+  /// The settings to pass to the decoration when painting, so that it can
+  /// resolve images appropriately. See [ImageProvider.resolve] and
+  /// [BoxPainter.paint].
+  ///
+  /// The [ImageConfiguration.textDirection] field is also used by
+  /// direction-sensitive [Decoration]s for painting and hit-testing.
+  ImageConfiguration get configuration => _configuration;
+  ImageConfiguration _configuration;
+  set configuration(ImageConfiguration value) {
+    assert(value != null);
+    if (value == _configuration)
+      return;
+    _configuration = value;
+    markNeedsPaint();
+  }
+
+  @override
+  void detach() {
+    _painter?.dispose();
+    _painter = null;
+    super.detach();
+    // Since we're disposing of our painter, we won't receive change
+    // notifications. We mark ourselves as needing paint so that we will
+    // resubscribe to change notifications. If we didn't do this, then, for
+    // example, animated GIFs would stop animating when a DecoratedBox gets
+    // moved around the tree due to GlobalKey reparenting.
+    markNeedsPaint();
+  }
+
+  @override
+  bool hitTestSelf(Offset position) {
+    return _decoration.hitTest(size, position, textDirection: configuration.textDirection);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    assert(size.width != null);
+    assert(size.height != null);
+    _painter ??= _decoration.createBoxPainter(markNeedsPaint);
+    final ImageConfiguration filledConfiguration = configuration.copyWith(size: size);
+    if (position == DecorationPosition.background) {
+      int? debugSaveCount;
+      assert(() {
+        debugSaveCount = context.canvas.getSaveCount();
+        return true;
+      }());
+      _painter!.paint(context.canvas, offset, filledConfiguration);
+      assert(() {
+        if (debugSaveCount != context.canvas.getSaveCount()) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('${_decoration.runtimeType} painter had mismatching save and restore calls.'),
+            ErrorDescription(
+              'Before painting the decoration, the canvas save count was $debugSaveCount. '
+              'After painting it, the canvas save count was ${context.canvas.getSaveCount()}. '
+              'Every call to save() or saveLayer() must be matched by a call to restore().'
+            ),
+            DiagnosticsProperty<Decoration>('The decoration was', decoration, style: DiagnosticsTreeStyle.errorProperty),
+            DiagnosticsProperty<BoxPainter>('The painter was', _painter, style: DiagnosticsTreeStyle.errorProperty),
+          ]);
+        }
+        return true;
+      }());
+      if (decoration.isComplex)
+        context.setIsComplexHint();
+    }
+    super.paint(context, offset);
+    if (position == DecorationPosition.foreground) {
+      _painter!.paint(context.canvas, offset, filledConfiguration);
+      if (decoration.isComplex)
+        context.setIsComplexHint();
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(_decoration.toDiagnosticsNode(name: 'decoration'));
+    properties.add(DiagnosticsProperty<ImageConfiguration>('configuration', configuration));
+  }
+}
+
+/// Applies a transformation before painting its child.
+class RenderTransform extends RenderProxyBox {
+  /// Creates a render object that transforms its child.
+  ///
+  /// The [transform] argument must not be null.
+  RenderTransform({
+    required Matrix4 transform,
+    Offset? origin,
+    AlignmentGeometry? alignment,
+    TextDirection? textDirection,
+    this.transformHitTests = true,
+    RenderBox? child,
+  }) : assert(transform != null),
+       super(child) {
+    this.transform = transform;
+    this.alignment = alignment;
+    this.textDirection = textDirection;
+    this.origin = origin;
+  }
+
+  /// The origin of the coordinate system (relative to the upper left corner of
+  /// this render object) in which to apply the matrix.
+  ///
+  /// Setting an origin is equivalent to conjugating the transform matrix by a
+  /// translation. This property is provided just for convenience.
+  Offset? get origin => _origin;
+  Offset? _origin;
+  set origin(Offset? value) {
+    if (_origin == value)
+      return;
+    _origin = value;
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// The alignment of the origin, relative to the size of the box.
+  ///
+  /// This is equivalent to setting an origin based on the size of the box.
+  /// If it is specified at the same time as an offset, both are applied.
+  ///
+  /// An [AlignmentDirectional.centerStart] value is the same as an [Alignment]
+  /// whose [Alignment.x] value is `-1.0` if [textDirection] is
+  /// [TextDirection.ltr], and `1.0` if [textDirection] is [TextDirection.rtl].
+  /// Similarly [AlignmentDirectional.centerEnd] is the same as an [Alignment]
+  /// whose [Alignment.x] value is `1.0` if [textDirection] is
+  /// [TextDirection.ltr], and `-1.0` if [textDirection] is [TextDirection.rtl].
+  AlignmentGeometry? get alignment => _alignment;
+  AlignmentGeometry? _alignment;
+  set alignment(AlignmentGeometry? value) {
+    if (_alignment == value)
+      return;
+    _alignment = value;
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// The text direction with which to resolve [alignment].
+  ///
+  /// This may be changed to null, but only after [alignment] has been changed
+  /// to a value that does not depend on the direction.
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection;
+  set textDirection(TextDirection? value) {
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// When set to true, hit tests are performed based on the position of the
+  /// child as it is painted. When set to false, hit tests are performed
+  /// ignoring the transformation.
+  ///
+  /// [applyPaintTransform], and therefore [localToGlobal] and [globalToLocal],
+  /// always honor the transformation, regardless of the value of this property.
+  bool transformHitTests;
+
+  // Note the lack of a getter for transform because Matrix4 is not immutable
+  Matrix4? _transform;
+
+  /// The matrix to transform the child by during painting.
+  set transform(Matrix4 value) {
+    assert(value != null);
+    if (_transform == value)
+      return;
+    _transform = Matrix4.copy(value);
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Sets the transform to the identity matrix.
+  void setIdentity() {
+    _transform!.setIdentity();
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Concatenates a rotation about the x axis into the transform.
+  void rotateX(double radians) {
+    _transform!.rotateX(radians);
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Concatenates a rotation about the y axis into the transform.
+  void rotateY(double radians) {
+    _transform!.rotateY(radians);
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Concatenates a rotation about the z axis into the transform.
+  void rotateZ(double radians) {
+    _transform!.rotateZ(radians);
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Concatenates a translation by (x, y, z) into the transform.
+  void translate(double x, [ double y = 0.0, double z = 0.0 ]) {
+    _transform!.translate(x, y, z);
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Concatenates a scale into the transform.
+  void scale(double x, [ double? y, double? z ]) {
+    _transform!.scale(x, y, z);
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  Matrix4? get _effectiveTransform {
+    final Alignment? resolvedAlignment = alignment?.resolve(textDirection);
+    if (_origin == null && resolvedAlignment == null)
+      return _transform;
+    final Matrix4 result = Matrix4.identity();
+    if (_origin != null)
+      result.translate(_origin!.dx, _origin!.dy);
+    Offset? translation;
+    if (resolvedAlignment != null) {
+      translation = resolvedAlignment.alongSize(size);
+      result.translate(translation.dx, translation.dy);
+    }
+    result.multiply(_transform!);
+    if (resolvedAlignment != null)
+      result.translate(-translation!.dx, -translation.dy);
+    if (_origin != null)
+      result.translate(-_origin!.dx, -_origin!.dy);
+    return result;
+  }
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    // RenderTransform objects don't check if they are
+    // themselves hit, because it's confusing to think about
+    // how the untransformed size and the child's transformed
+    // position interact.
+    return hitTestChildren(result, position: position);
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    assert(!transformHitTests || _effectiveTransform != null);
+    return result.addWithPaintTransform(
+      transform: transformHitTests ? _effectiveTransform : null,
+      position: position,
+      hitTest: (BoxHitTestResult result, Offset? position) {
+        return super.hitTestChildren(result, position: position!);
+      },
+    );
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null) {
+      final Matrix4 transform = _effectiveTransform!;
+      final Offset? childOffset = MatrixUtils.getAsTranslation(transform);
+      if (childOffset == null) {
+        layer = context.pushTransform(
+          needsCompositing,
+          offset,
+          transform,
+          super.paint,
+          oldLayer: layer as TransformLayer?,
+        );
+      } else {
+        super.paint(context, offset + childOffset);
+        layer = null;
+      }
+    }
+  }
+
+  @override
+  void applyPaintTransform(RenderBox child, Matrix4 transform) {
+    transform.multiply(_effectiveTransform!);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(TransformProperty('transform matrix', _transform));
+    properties.add(DiagnosticsProperty<Offset>('origin', origin));
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('transformHitTests', transformHitTests));
+  }
+}
+
+/// Scales and positions its child within itself according to [fit].
+class RenderFittedBox extends RenderProxyBox {
+  /// Scales and positions its child within itself.
+  ///
+  /// The [fit] and [alignment] arguments must not be null.
+  RenderFittedBox({
+    BoxFit fit = BoxFit.contain,
+    AlignmentGeometry alignment = Alignment.center,
+    TextDirection? textDirection,
+    RenderBox? child,
+    Clip clipBehavior = Clip.none,
+  }) : assert(fit != null),
+       assert(alignment != null),
+       assert(clipBehavior != null),
+       _fit = fit,
+       _alignment = alignment,
+       _textDirection = textDirection,
+       _clipBehavior = clipBehavior,
+       super(child);
+
+  Alignment? _resolvedAlignment;
+
+  void _resolve() {
+    if (_resolvedAlignment != null)
+      return;
+    _resolvedAlignment = alignment.resolve(textDirection);
+  }
+
+  void _markNeedResolution() {
+    _resolvedAlignment = null;
+    markNeedsPaint();
+  }
+
+  bool _fitAffectsLayout(BoxFit fit) {
+    switch (fit) {
+      case BoxFit.scaleDown:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  /// How to inscribe the child into the space allocated during layout.
+  BoxFit get fit => _fit;
+  BoxFit _fit;
+  set fit(BoxFit value) {
+    assert(value != null);
+    if (_fit == value)
+      return;
+    final BoxFit lastFit = _fit;
+    _fit = value;
+    if (_fitAffectsLayout(lastFit) || _fitAffectsLayout(value)) {
+      markNeedsLayout();
+    } else {
+      _clearPaintData();
+      markNeedsPaint();
+    }
+  }
+
+  /// How to align the child within its parent's bounds.
+  ///
+  /// An alignment of (0.0, 0.0) aligns the child to the top-left corner of its
+  /// parent's bounds. An alignment of (1.0, 0.5) aligns the child to the middle
+  /// of the right edge of its parent's bounds.
+  ///
+  /// If this is set to an [AlignmentDirectional] object, then
+  /// [textDirection] must not be null.
+  AlignmentGeometry get alignment => _alignment;
+  AlignmentGeometry _alignment;
+  set alignment(AlignmentGeometry value) {
+    assert(value != null);
+    if (_alignment == value)
+      return;
+    _alignment = value;
+    _clearPaintData();
+    _markNeedResolution();
+  }
+
+  /// The text direction with which to resolve [alignment].
+  ///
+  /// This may be changed to null, but only after [alignment] has been changed
+  /// to a value that does not depend on the direction.
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection;
+  set textDirection(TextDirection? value) {
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    _clearPaintData();
+    _markNeedResolution();
+  }
+
+  // TODO(ianh): The intrinsic dimensions of this box are wrong.
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (child != null) {
+      final Size childSize = child!.getDryLayout(const BoxConstraints());
+
+      // During [RenderObject.debugCheckingIntrinsics] a child that doesn't
+      // support dry layout may provide us with an invalid size that triggers
+      // assertions if we try to work with it. Instead of throwing, we bail
+      // out early in that case.
+      bool invalidChildSize = false;
+      assert(() {
+        if (RenderObject.debugCheckingIntrinsics && childSize.width * childSize.height == 0.0) {
+          invalidChildSize = true;
+        }
+        return true;
+      }());
+      if (invalidChildSize) {
+        assert(debugCannotComputeDryLayout(
+          reason: 'Child provided invalid size of $childSize.',
+        ));
+        return const Size(0, 0);
+      }
+
+      switch (fit) {
+        case BoxFit.scaleDown:
+          final BoxConstraints sizeConstraints = constraints.loosen();
+          final Size unconstrainedSize = sizeConstraints.constrainSizeAndAttemptToPreserveAspectRatio(childSize);
+          return constraints.constrain(unconstrainedSize);
+        default:
+          return constraints.constrainSizeAndAttemptToPreserveAspectRatio(childSize);
+      }
+    } else {
+      return constraints.smallest;
+    }
+  }
+
+  @override
+  void performLayout() {
+    if (child != null) {
+      child!.layout(const BoxConstraints(), parentUsesSize: true);
+      switch (fit) {
+        case BoxFit.scaleDown:
+          final BoxConstraints sizeConstraints = constraints.loosen();
+          final Size unconstrainedSize = sizeConstraints.constrainSizeAndAttemptToPreserveAspectRatio(child!.size);
+          size = constraints.constrain(unconstrainedSize);
+          break;
+        default:
+          size = constraints.constrainSizeAndAttemptToPreserveAspectRatio(child!.size);
+          break;
+      }
+      _clearPaintData();
+    } else {
+      size = constraints.smallest;
+    }
+  }
+
+  bool? _hasVisualOverflow;
+  Matrix4? _transform;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior = Clip.none;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  void _clearPaintData() {
+    _hasVisualOverflow = null;
+    _transform = null;
+  }
+
+  void _updatePaintData() {
+    if (_transform != null)
+      return;
+
+    if (child == null) {
+      _hasVisualOverflow = false;
+      _transform = Matrix4.identity();
+    } else {
+      _resolve();
+      final Size childSize = child!.size;
+      final FittedSizes sizes = applyBoxFit(_fit, childSize, size);
+      final double scaleX = sizes.destination.width / sizes.source.width;
+      final double scaleY = sizes.destination.height / sizes.source.height;
+      final Rect sourceRect = _resolvedAlignment!.inscribe(sizes.source, Offset.zero & childSize);
+      final Rect destinationRect = _resolvedAlignment!.inscribe(sizes.destination, Offset.zero & size);
+      _hasVisualOverflow = sourceRect.width < childSize.width || sourceRect.height < childSize.height;
+      assert(scaleX.isFinite && scaleY.isFinite);
+      _transform = Matrix4.translationValues(destinationRect.left, destinationRect.top, 0.0)
+        ..scale(scaleX, scaleY, 1.0)
+        ..translate(-sourceRect.left, -sourceRect.top);
+      assert(_transform!.storage.every((double value) => value.isFinite));
+    }
+  }
+
+  TransformLayer? _paintChildWithTransform(PaintingContext context, Offset offset) {
+    final Offset? childOffset = MatrixUtils.getAsTranslation(_transform!);
+    if (childOffset == null)
+      return context.pushTransform(needsCompositing, offset, _transform!, super.paint,
+          oldLayer: layer is TransformLayer ? layer! as TransformLayer : null);
+    else
+      super.paint(context, offset + childOffset);
+    return null;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (size.isEmpty || child!.size.isEmpty)
+      return;
+    _updatePaintData();
+    if (child != null) {
+      if (_hasVisualOverflow! && clipBehavior != Clip.none)
+        layer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintChildWithTransform,
+            oldLayer: layer is ClipRectLayer ? layer! as ClipRectLayer : null, clipBehavior: clipBehavior);
+      else
+        layer = _paintChildWithTransform(context, offset);
+    }
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    if (size.isEmpty || child?.size.isEmpty == true)
+      return false;
+    _updatePaintData();
+    return result.addWithPaintTransform(
+      transform: _transform,
+      position: position,
+      hitTest: (BoxHitTestResult result, Offset? position) {
+        return super.hitTestChildren(result, position: position!);
+      },
+    );
+  }
+
+  @override
+  void applyPaintTransform(RenderBox child, Matrix4 transform) {
+    if (size.isEmpty || child.size.isEmpty) {
+      transform.setZero();
+    } else {
+      _updatePaintData();
+      transform.multiply(_transform!);
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<BoxFit>('fit', fit));
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+  }
+}
+
+/// Applies a translation transformation before painting its child.
+///
+/// The translation is expressed as an [Offset] scaled to the child's size. For
+/// example, an [Offset] with a `dx` of 0.25 will result in a horizontal
+/// translation of one quarter the width of the child.
+///
+/// Hit tests will only be detected inside the bounds of the
+/// [RenderFractionalTranslation], even if the contents are offset such that
+/// they overflow.
+class RenderFractionalTranslation extends RenderProxyBox {
+  /// Creates a render object that translates its child's painting.
+  ///
+  /// The [translation] argument must not be null.
+  RenderFractionalTranslation({
+    required Offset translation,
+    this.transformHitTests = true,
+    RenderBox? child,
+  }) : assert(translation != null),
+       _translation = translation,
+       super(child);
+
+  /// The translation to apply to the child, scaled to the child's size.
+  ///
+  /// For example, an [Offset] with a `dx` of 0.25 will result in a horizontal
+  /// translation of one quarter the width of the child.
+  Offset get translation => _translation;
+  Offset _translation;
+  set translation(Offset value) {
+    assert(value != null);
+    if (_translation == value)
+      return;
+    _translation = value;
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    // RenderFractionalTranslation objects don't check if they are
+    // themselves hit, because it's confusing to think about
+    // how the untransformed size and the child's transformed
+    // position interact.
+    return hitTestChildren(result, position: position);
+  }
+
+  /// When set to true, hit tests are performed based on the position of the
+  /// child as it is painted. When set to false, hit tests are performed
+  /// ignoring the transformation.
+  ///
+  /// applyPaintTransform(), and therefore localToGlobal() and globalToLocal(),
+  /// always honor the transformation, regardless of the value of this property.
+  bool transformHitTests;
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    assert(!debugNeedsLayout);
+    return result.addWithPaintOffset(
+      offset: transformHitTests
+          ? Offset(translation.dx * size.width, translation.dy * size.height)
+          : null,
+      position: position,
+      hitTest: (BoxHitTestResult result, Offset? position) {
+        return super.hitTestChildren(result, position: position!);
+      },
+    );
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    assert(!debugNeedsLayout);
+    if (child != null) {
+      super.paint(context, Offset(
+        offset.dx + translation.dx * size.width,
+        offset.dy + translation.dy * size.height,
+      ));
+    }
+  }
+
+  @override
+  void applyPaintTransform(RenderBox child, Matrix4 transform) {
+    transform.translate(
+      translation.dx * size.width,
+      translation.dy * size.height,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Offset>('translation', translation));
+    properties.add(DiagnosticsProperty<bool>('transformHitTests', transformHitTests));
+  }
+}
+
+/// Signature for listening to [PointerDownEvent] events.
+///
+/// Used by [Listener] and [RenderPointerListener].
+typedef PointerDownEventListener = void Function(PointerDownEvent event);
+
+/// Signature for listening to [PointerMoveEvent] events.
+///
+/// Used by [Listener] and [RenderPointerListener].
+typedef PointerMoveEventListener = void Function(PointerMoveEvent event);
+
+/// Signature for listening to [PointerUpEvent] events.
+///
+/// Used by [Listener] and [RenderPointerListener].
+typedef PointerUpEventListener = void Function(PointerUpEvent event);
+
+/// Signature for listening to [PointerCancelEvent] events.
+///
+/// Used by [Listener] and [RenderPointerListener].
+typedef PointerCancelEventListener = void Function(PointerCancelEvent event);
+
+/// Signature for listening to [PointerSignalEvent] events.
+///
+/// Used by [Listener] and [RenderPointerListener].
+typedef PointerSignalEventListener = void Function(PointerSignalEvent event);
+
+/// Calls callbacks in response to common pointer events.
+///
+/// It responds to events that can construct gestures, such as when the
+/// pointer is pointer is pressed and moved, and then released or canceled.
+///
+/// It does not respond to events that are exclusive to mouse, such as when the
+/// mouse enters and exits a region without pressing any buttons. For
+/// these events, use [RenderMouseRegion].
+///
+/// If it has a child, defers to the child for sizing behavior.
+///
+/// If it does not have a child, grows to fit the parent-provided constraints.
+class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
+  /// Creates a render object that forwards pointer events to callbacks.
+  ///
+  /// The [behavior] argument defaults to [HitTestBehavior.deferToChild].
+  RenderPointerListener({
+    this.onPointerDown,
+    this.onPointerMove,
+    this.onPointerUp,
+    this.onPointerHover,
+    this.onPointerCancel,
+    this.onPointerSignal,
+    HitTestBehavior behavior = HitTestBehavior.deferToChild,
+    RenderBox? child,
+  }) : super(behavior: behavior, child: child);
+
+  /// Called when a pointer comes into contact with the screen (for touch
+  /// pointers), or has its button pressed (for mouse pointers) at this widget's
+  /// location.
+  PointerDownEventListener? onPointerDown;
+
+  /// Called when a pointer that triggered an [onPointerDown] changes position.
+  PointerMoveEventListener? onPointerMove;
+
+  /// Called when a pointer that triggered an [onPointerDown] is no longer in
+  /// contact with the screen.
+  PointerUpEventListener? onPointerUp;
+
+  /// Called when a pointer that has not an [onPointerDown] changes position.
+  PointerHoverEventListener? onPointerHover;
+
+  /// Called when the input from a pointer that triggered an [onPointerDown] is
+  /// no longer directed towards this receiver.
+  PointerCancelEventListener? onPointerCancel;
+
+  /// Called when a pointer signal occurs over this object.
+  PointerSignalEventListener? onPointerSignal;
+
+  @override
+  Size computeSizeForNoChild(BoxConstraints constraints) {
+    return constraints.biggest;
+  }
+
+  @override
+  void handleEvent(PointerEvent event, HitTestEntry entry) {
+    assert(debugHandleEvent(event, entry));
+    if (event is PointerDownEvent)
+      return onPointerDown?.call(event);
+    if (event is PointerMoveEvent)
+      return onPointerMove?.call(event);
+    if (event is PointerUpEvent)
+      return onPointerUp?.call(event);
+    if (event is PointerHoverEvent)
+      return onPointerHover?.call(event);
+    if (event is PointerCancelEvent)
+      return onPointerCancel?.call(event);
+    if (event is PointerSignalEvent)
+      return onPointerSignal?.call(event);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagsSummary<Function?>(
+      'listeners',
+      <String, Function?>{
+        'down': onPointerDown,
+        'move': onPointerMove,
+        'up': onPointerUp,
+        'hover': onPointerHover,
+        'cancel': onPointerCancel,
+        'signal': onPointerSignal,
+      },
+      ifEmpty: '<none>',
+    ));
+  }
+}
+
+/// Calls callbacks in response to pointer events that are exclusive to mice.
+///
+/// It responds to events that are related to hovering, i.e. when the mouse
+/// enters, exits (with or without pressing buttons), or moves over a region
+/// without pressing buttons.
+///
+/// It does not respond to common events that construct gestures, such as when
+/// the pointer is pressed, moved, then released or canceled. For these events,
+/// use [RenderPointerListener].
+///
+/// If it has a child, it defers to the child for sizing behavior.
+///
+/// If it does not have a child, it grows to fit the parent-provided constraints.
+///
+/// See also:
+///
+///  * [MouseRegion], a widget that listens to hover events using
+///    [RenderMouseRegion].
+class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation {
+  /// Creates a render object that forwards pointer events to callbacks.
+  ///
+  /// All parameters are optional. By default this method creates an opaque
+  /// mouse region with no callbacks and cursor being [MouseCursor.defer]. The
+  /// [cursor] must not be null.
+  RenderMouseRegion({
+    this.onEnter,
+    this.onHover,
+    this.onExit,
+    MouseCursor cursor = MouseCursor.defer,
+    bool validForMouseTracker = true,
+    bool opaque = true,
+    RenderBox? child,
+  }) : assert(opaque != null),
+       assert(cursor != null),
+       _cursor = cursor,
+       _validForMouseTracker = validForMouseTracker,
+       _opaque = opaque,
+       super(child);
+
+  @protected
+  @override
+  bool hitTestSelf(Offset position) => true;
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    return super.hitTest(result, position: position) && _opaque;
+  }
+
+  @override
+  void handleEvent(PointerEvent event, HitTestEntry entry) {
+    assert(debugHandleEvent(event, entry));
+    if (onHover != null && event is PointerHoverEvent)
+      return onHover!(event);
+  }
+
+  /// Whether this object should prevent [RenderMouseRegion]s visually behind it
+  /// from detecting the pointer, thus affecting how their [onHover], [onEnter],
+  /// and [onExit] behave.
+  ///
+  /// If [opaque] is true, this object will absorb the mouse pointer and
+  /// prevent this object's siblings (or any other objects that are not
+  /// ancestors or descendants of this object) from detecting the mouse
+  /// pointer even when the pointer is within their areas.
+  ///
+  /// If [opaque] is false, this object will not affect how [RenderMouseRegion]s
+  /// behind it behave, which will detect the mouse pointer as long as the
+  /// pointer is within their areas.
+  ///
+  /// This defaults to true.
+  bool get opaque => _opaque;
+  bool _opaque;
+  set opaque(bool value) {
+    if (_opaque != value) {
+      _opaque = value;
+      // Trigger [MouseTracker]'s device update to recalculate mouse states.
+      markNeedsPaint();
+    }
+  }
+
+  @override
+  PointerEnterEventListener? onEnter;
+
+  /// Triggered when a pointer has moved onto or within the region without
+  /// buttons pressed.
+  ///
+  /// This callback is not triggered by the movement of the object.
+  PointerHoverEventListener? onHover;
+
+  @override
+  PointerExitEventListener? onExit;
+
+  @override
+  MouseCursor get cursor => _cursor;
+  MouseCursor _cursor;
+  set cursor(MouseCursor value) {
+    if (_cursor != value) {
+      _cursor = value;
+      // A repaint is needed in order to trigger a device update of
+      // [MouseTracker] so that this new value can be found.
+      markNeedsPaint();
+    }
+  }
+
+  @override
+  bool get validForMouseTracker => _validForMouseTracker;
+  bool _validForMouseTracker;
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    _validForMouseTracker = true;
+  }
+
+  @override
+  void detach() {
+    // It's possible that the renderObject be detached during mouse events
+    // dispatching, set the [MouseTrackerAnnotation.validForMouseTracker] false to prevent
+    // the callbacks from being called.
+    _validForMouseTracker = false;
+    super.detach();
+  }
+
+  @override
+  Size computeSizeForNoChild(BoxConstraints constraints) {
+    return constraints.biggest;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagsSummary<Function?>(
+      'listeners',
+      <String, Function?>{
+        'enter': onEnter,
+        'hover': onHover,
+        'exit': onExit,
+      },
+      ifEmpty: '<none>',
+    ));
+    properties.add(DiagnosticsProperty<MouseCursor>('cursor', cursor, defaultValue: MouseCursor.defer));
+    properties.add(DiagnosticsProperty<bool>('opaque', opaque, defaultValue: true));
+    properties.add(FlagProperty('validForMouseTracker', value: validForMouseTracker, defaultValue: true, ifFalse: 'invalid for MouseTracker'));
+  }
+}
+
+/// Creates a separate display list for its child.
+///
+/// This render object creates a separate display list for its child, which
+/// can improve performance if the subtree repaints at different times than
+/// the surrounding parts of the tree. Specifically, when the child does not
+/// repaint but its parent does, we can re-use the display list we recorded
+/// previously. Similarly, when the child repaints but the surround tree does
+/// not, we can re-record its display list without re-recording the display list
+/// for the surround tree.
+///
+/// In some cases, it is necessary to place _two_ (or more) repaint boundaries
+/// to get a useful effect. Consider, for example, an e-mail application that
+/// shows an unread count and a list of e-mails. Whenever a new e-mail comes in,
+/// the list would update, but so would the unread count. If only one of these
+/// two parts of the application was behind a repaint boundary, the entire
+/// application would repaint each time. On the other hand, if both were behind
+/// a repaint boundary, a new e-mail would only change those two parts of the
+/// application and the rest of the application would not repaint.
+///
+/// To tell if a particular RenderRepaintBoundary is useful, run your
+/// application in checked mode, interacting with it in typical ways, and then
+/// call [debugDumpRenderTree]. Each RenderRepaintBoundary will include the
+/// ratio of cases where the repaint boundary was useful vs the cases where it
+/// was not. These counts can also be inspected programmatically using
+/// [debugAsymmetricPaintCount] and [debugSymmetricPaintCount] respectively.
+class RenderRepaintBoundary extends RenderProxyBox {
+  /// Creates a repaint boundary around [child].
+  RenderRepaintBoundary({ RenderBox? child }) : super(child);
+
+  @override
+  bool get isRepaintBoundary => true;
+
+  /// Capture an image of the current state of this render object and its
+  /// children.
+  ///
+  /// The returned [ui.Image] has uncompressed raw RGBA bytes in the dimensions
+  /// of the render object, multiplied by the [pixelRatio].
+  ///
+  /// To use [toImage], the render object must have gone through the paint phase
+  /// (i.e. [debugNeedsPaint] must be false).
+  ///
+  /// The [pixelRatio] describes the scale between the logical pixels and the
+  /// size of the output image. It is independent of the
+  /// [dart:ui.FlutterView.devicePixelRatio] for the device, so specifying 1.0
+  /// (the default) will give you a 1:1 mapping between logical pixels and the
+  /// output pixels in the image.
+  ///
+  /// {@tool snippet}
+  ///
+  /// The following is an example of how to go from a `GlobalKey` on a
+  /// `RepaintBoundary` to a PNG:
+  ///
+  /// ```dart
+  /// class PngHome extends StatefulWidget {
+  ///   PngHome({Key key}) : super(key: key);
+  ///
+  ///   @override
+  ///   _PngHomeState createState() => _PngHomeState();
+  /// }
+  ///
+  /// class _PngHomeState extends State<PngHome> {
+  ///   GlobalKey globalKey = GlobalKey();
+  ///
+  ///   Future<void> _capturePng() async {
+  ///     RenderRepaintBoundary boundary = globalKey.currentContext.findRenderObject();
+  ///     ui.Image image = await boundary.toImage();
+  ///     ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
+  ///     Uint8List pngBytes = byteData.buffer.asUint8List();
+  ///     print(pngBytes);
+  ///   }
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return RepaintBoundary(
+  ///       key: globalKey,
+  ///       child: Center(
+  ///         child: TextButton(
+  ///           child: Text('Hello World', textDirection: TextDirection.ltr),
+  ///           onPressed: _capturePng,
+  ///         ),
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [OffsetLayer.toImage] for a similar API at the layer level.
+  ///  * [dart:ui.Scene.toImage] for more information about the image returned.
+  Future<ui.Image> toImage({ double pixelRatio = 1.0 }) {
+    assert(!debugNeedsPaint);
+    final OffsetLayer offsetLayer = layer! as OffsetLayer;
+    return offsetLayer.toImage(Offset.zero & size, pixelRatio: pixelRatio);
+  }
+
+
+  /// The number of times that this render object repainted at the same time as
+  /// its parent. Repaint boundaries are only useful when the parent and child
+  /// paint at different times. When both paint at the same time, the repaint
+  /// boundary is redundant, and may be actually making performance worse.
+  ///
+  /// Only valid when asserts are enabled. In release builds, always returns
+  /// zero.
+  ///
+  /// Can be reset using [debugResetMetrics]. See [debugAsymmetricPaintCount]
+  /// for the corresponding count of times where only the parent or only the
+  /// child painted.
+  int get debugSymmetricPaintCount => _debugSymmetricPaintCount;
+  int _debugSymmetricPaintCount = 0;
+
+  /// The number of times that either this render object repainted without the
+  /// parent being painted, or the parent repainted without this object being
+  /// painted. When a repaint boundary is used at a seam in the render tree
+  /// where the parent tends to repaint at entirely different times than the
+  /// child, it can improve performance by reducing the number of paint
+  /// operations that have to be recorded each frame.
+  ///
+  /// Only valid when asserts are enabled. In release builds, always returns
+  /// zero.
+  ///
+  /// Can be reset using [debugResetMetrics]. See [debugSymmetricPaintCount] for
+  /// the corresponding count of times where both the parent and the child
+  /// painted together.
+  int get debugAsymmetricPaintCount => _debugAsymmetricPaintCount;
+  int _debugAsymmetricPaintCount = 0;
+
+  /// Resets the [debugSymmetricPaintCount] and [debugAsymmetricPaintCount]
+  /// counts to zero.
+  ///
+  /// Only valid when asserts are enabled. Does nothing in release builds.
+  void debugResetMetrics() {
+    assert(() {
+      _debugSymmetricPaintCount = 0;
+      _debugAsymmetricPaintCount = 0;
+      return true;
+    }());
+  }
+
+  @override
+  void debugRegisterRepaintBoundaryPaint({ bool includedParent = true, bool includedChild = false }) {
+    assert(() {
+      if (includedParent && includedChild)
+        _debugSymmetricPaintCount += 1;
+      else
+        _debugAsymmetricPaintCount += 1;
+      return true;
+    }());
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    bool inReleaseMode = true;
+    assert(() {
+      inReleaseMode = false;
+      if (debugSymmetricPaintCount + debugAsymmetricPaintCount == 0) {
+        properties.add(MessageProperty('usefulness ratio', 'no metrics collected yet (never painted)'));
+      } else {
+        final double fraction = debugAsymmetricPaintCount / (debugSymmetricPaintCount + debugAsymmetricPaintCount);
+        final String diagnosis;
+        if (debugSymmetricPaintCount + debugAsymmetricPaintCount < 5) {
+          diagnosis = 'insufficient data to draw conclusion (less than five repaints)';
+        } else if (fraction > 0.9) {
+          diagnosis = 'this is an outstandingly useful repaint boundary and should definitely be kept';
+        } else if (fraction > 0.5) {
+          diagnosis = 'this is a useful repaint boundary and should be kept';
+        } else if (fraction > 0.30) {
+          diagnosis = 'this repaint boundary is probably useful, but maybe it would be more useful in tandem with adding more repaint boundaries elsewhere';
+        } else if (fraction > 0.1) {
+          diagnosis = 'this repaint boundary does sometimes show value, though currently not that often';
+        } else if (debugAsymmetricPaintCount == 0) {
+          diagnosis = 'this repaint boundary is astoundingly ineffectual and should be removed';
+        } else {
+          diagnosis = 'this repaint boundary is not very effective and should probably be removed';
+        }
+        properties.add(PercentProperty('metrics', fraction, unit: 'useful', tooltip: '$debugSymmetricPaintCount bad vs $debugAsymmetricPaintCount good'));
+        properties.add(MessageProperty('diagnosis', diagnosis));
+      }
+      return true;
+    }());
+    if (inReleaseMode)
+      properties.add(DiagnosticsNode.message('(run in checked mode to collect repaint boundary statistics)'));
+  }
+}
+
+/// A render object that is invisible during hit testing.
+///
+/// When [ignoring] is true, this render object (and its subtree) is invisible
+/// to hit testing. It still consumes space during layout and paints its child
+/// as usual. It just cannot be the target of located events, because its render
+/// object returns false from [hitTest].
+///
+/// When [ignoringSemantics] is true, the subtree will be invisible to
+/// the semantics layer (and thus e.g. accessibility tools). If
+/// [ignoringSemantics] is null, it uses the value of [ignoring].
+///
+/// See also:
+///
+///  * [RenderAbsorbPointer], which takes the pointer events but prevents any
+///    nodes in the subtree from seeing them.
+class RenderIgnorePointer extends RenderProxyBox {
+  /// Creates a render object that is invisible to hit testing.
+  ///
+  /// The [ignoring] argument must not be null. If [ignoringSemantics] is null,
+  /// this render object will be ignored for semantics if [ignoring] is true.
+  RenderIgnorePointer({
+    RenderBox? child,
+    bool ignoring = true,
+    bool? ignoringSemantics,
+  }) : _ignoring = ignoring,
+       _ignoringSemantics = ignoringSemantics,
+       super(child) {
+    assert(_ignoring != null);
+  }
+
+  /// Whether this render object is ignored during hit testing.
+  ///
+  /// Regardless of whether this render object is ignored during hit testing, it
+  /// will still consume space during layout and be visible during painting.
+  bool get ignoring => _ignoring;
+  bool _ignoring;
+  set ignoring(bool value) {
+    assert(value != null);
+    if (value == _ignoring)
+      return;
+    _ignoring = value;
+    if (_ignoringSemantics == null || !_ignoringSemantics!)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// Whether the semantics of this render object is ignored when compiling the semantics tree.
+  ///
+  /// If null, defaults to value of [ignoring].
+  ///
+  /// See [SemanticsNode] for additional information about the semantics tree.
+  bool? get ignoringSemantics => _ignoringSemantics;
+  bool? _ignoringSemantics;
+  set ignoringSemantics(bool? value) {
+    if (value == _ignoringSemantics)
+      return;
+    final bool oldEffectiveValue = _effectiveIgnoringSemantics;
+    _ignoringSemantics = value;
+    if (oldEffectiveValue != _effectiveIgnoringSemantics)
+      markNeedsSemanticsUpdate();
+  }
+
+  bool get _effectiveIgnoringSemantics => ignoringSemantics ?? ignoring;
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    return !ignoring && super.hitTest(result, position: position);
+  }
+
+  // TODO(ianh): figure out a way to still include labels and flags in
+  // descendants, just make them non-interactive, even when
+  // _effectiveIgnoringSemantics is true
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    if (child != null && !_effectiveIgnoringSemantics)
+      visitor(child!);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('ignoring', ignoring));
+    properties.add(
+      DiagnosticsProperty<bool>(
+        'ignoringSemantics',
+        _effectiveIgnoringSemantics,
+        description: ignoringSemantics == null ? 'implicitly $_effectiveIgnoringSemantics' : null,
+      ),
+    );
+  }
+}
+
+/// Lays the child out as if it was in the tree, but without painting anything,
+/// without making the child available for hit testing, and without taking any
+/// room in the parent.
+class RenderOffstage extends RenderProxyBox {
+  /// Creates an offstage render object.
+  RenderOffstage({
+    bool offstage = true,
+    RenderBox? child,
+  }) : assert(offstage != null),
+       _offstage = offstage,
+       super(child);
+
+  /// Whether the child is hidden from the rest of the tree.
+  ///
+  /// If true, the child is laid out as if it was in the tree, but without
+  /// painting anything, without making the child available for hit testing, and
+  /// without taking any room in the parent.
+  ///
+  /// If false, the child is included in the tree as normal.
+  bool get offstage => _offstage;
+  bool _offstage;
+  set offstage(bool value) {
+    assert(value != null);
+    if (value == _offstage)
+      return;
+    _offstage = value;
+    markNeedsLayoutForSizedByParentChange();
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    if (offstage)
+      return 0.0;
+    return super.computeMinIntrinsicWidth(height);
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    if (offstage)
+      return 0.0;
+    return super.computeMaxIntrinsicWidth(height);
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    if (offstage)
+      return 0.0;
+    return super.computeMinIntrinsicHeight(width);
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    if (offstage)
+      return 0.0;
+    return super.computeMaxIntrinsicHeight(width);
+  }
+
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    if (offstage)
+      return null;
+    return super.computeDistanceToActualBaseline(baseline);
+  }
+
+  @override
+  bool get sizedByParent => offstage;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (offstage) {
+      return constraints.smallest;
+    }
+    return super.computeDryLayout(constraints);
+  }
+
+
+  @override
+  void performResize() {
+    assert(offstage);
+    super.performResize();
+  }
+
+  @override
+  void performLayout() {
+    if (offstage) {
+      child?.layout(constraints);
+    } else {
+      super.performLayout();
+    }
+  }
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    return !offstage && super.hitTest(result, position: position);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (offstage)
+      return;
+    super.paint(context, offset);
+  }
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    if (offstage)
+      return;
+    super.visitChildrenForSemantics(visitor);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('offstage', offstage));
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    if (child == null)
+      return <DiagnosticsNode>[];
+    return <DiagnosticsNode>[
+      child!.toDiagnosticsNode(
+        name: 'child',
+        style: offstage ? DiagnosticsTreeStyle.offstage : DiagnosticsTreeStyle.sparse,
+      ),
+    ];
+  }
+}
+
+/// A render object that absorbs pointers during hit testing.
+///
+/// When [absorbing] is true, this render object prevents its subtree from
+/// receiving pointer events by terminating hit testing at itself. It still
+/// consumes space during layout and paints its child as usual. It just prevents
+/// its children from being the target of located events, because its render
+/// object returns true from [hitTest].
+///
+/// See also:
+///
+///  * [RenderIgnorePointer], which has the opposite effect: removing the
+///    subtree from considering entirely for the purposes of hit testing.
+class RenderAbsorbPointer extends RenderProxyBox {
+  /// Creates a render object that absorbs pointers during hit testing.
+  ///
+  /// The [absorbing] argument must not be null.
+  RenderAbsorbPointer({
+    RenderBox? child,
+    bool absorbing = true,
+    bool? ignoringSemantics,
+  }) : assert(absorbing != null),
+       _absorbing = absorbing,
+       _ignoringSemantics = ignoringSemantics,
+       super(child);
+
+  /// Whether this render object absorbs pointers during hit testing.
+  ///
+  /// Regardless of whether this render object absorbs pointers during hit
+  /// testing, it will still consume space during layout and be visible during
+  /// painting.
+  bool get absorbing => _absorbing;
+  bool _absorbing;
+  set absorbing(bool value) {
+    if (_absorbing == value)
+      return;
+    _absorbing = value;
+    if (ignoringSemantics == null)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// Whether the semantics of this render object is ignored when compiling the semantics tree.
+  ///
+  /// If null, defaults to value of [absorbing].
+  ///
+  /// See [SemanticsNode] for additional information about the semantics tree.
+  bool? get ignoringSemantics => _ignoringSemantics;
+  bool? _ignoringSemantics;
+  set ignoringSemantics(bool? value) {
+    if (value == _ignoringSemantics)
+      return;
+    final bool oldEffectiveValue = _effectiveIgnoringSemantics;
+    _ignoringSemantics = value;
+    if (oldEffectiveValue != _effectiveIgnoringSemantics)
+      markNeedsSemanticsUpdate();
+  }
+
+  bool get _effectiveIgnoringSemantics => ignoringSemantics ?? absorbing;
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    return absorbing
+        ? size.contains(position)
+        : super.hitTest(result, position: position);
+  }
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    if (child != null && !_effectiveIgnoringSemantics)
+      visitor(child!);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('absorbing', absorbing));
+    properties.add(
+      DiagnosticsProperty<bool>(
+        'ignoringSemantics',
+        _effectiveIgnoringSemantics,
+        description: ignoringSemantics == null ? 'implicitly $_effectiveIgnoringSemantics' : null,
+      ),
+    );
+  }
+}
+
+/// Holds opaque meta data in the render tree.
+///
+/// Useful for decorating the render tree with information that will be consumed
+/// later. For example, you could store information in the render tree that will
+/// be used when the user interacts with the render tree but has no visual
+/// impact prior to the interaction.
+class RenderMetaData extends RenderProxyBoxWithHitTestBehavior {
+  /// Creates a render object that hold opaque meta data.
+  ///
+  /// The [behavior] argument defaults to [HitTestBehavior.deferToChild].
+  RenderMetaData({
+    this.metaData,
+    HitTestBehavior behavior = HitTestBehavior.deferToChild,
+    RenderBox? child,
+  }) : super(behavior: behavior, child: child);
+
+  /// Opaque meta data ignored by the render tree.
+  dynamic metaData;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<dynamic>('metaData', metaData));
+  }
+}
+
+/// Listens for the specified gestures from the semantics server (e.g.
+/// an accessibility tool).
+class RenderSemanticsGestureHandler extends RenderProxyBox {
+  /// Creates a render object that listens for specific semantic gestures.
+  ///
+  /// The [scrollFactor] argument must not be null.
+  RenderSemanticsGestureHandler({
+    RenderBox? child,
+    GestureTapCallback? onTap,
+    GestureLongPressCallback? onLongPress,
+    GestureDragUpdateCallback? onHorizontalDragUpdate,
+    GestureDragUpdateCallback? onVerticalDragUpdate,
+    this.scrollFactor = 0.8,
+  }) : assert(scrollFactor != null),
+       _onTap = onTap,
+       _onLongPress = onLongPress,
+       _onHorizontalDragUpdate = onHorizontalDragUpdate,
+       _onVerticalDragUpdate = onVerticalDragUpdate,
+       super(child);
+
+  /// If non-null, the set of actions to allow. Other actions will be omitted,
+  /// even if their callback is provided.
+  ///
+  /// For example, if [onTap] is non-null but [validActions] does not contain
+  /// [SemanticsAction.tap], then the semantic description of this node will
+  /// not claim to support taps.
+  ///
+  /// This is normally used to filter the actions made available by
+  /// [onHorizontalDragUpdate] and [onVerticalDragUpdate]. Normally, these make
+  /// both the right and left, or up and down, actions available. For example,
+  /// if [onHorizontalDragUpdate] is set but [validActions] only contains
+  /// [SemanticsAction.scrollLeft], then the [SemanticsAction.scrollRight]
+  /// action will be omitted.
+  Set<SemanticsAction>? get validActions => _validActions;
+  Set<SemanticsAction>? _validActions;
+  set validActions(Set<SemanticsAction>? value) {
+    if (setEquals<SemanticsAction>(value, _validActions))
+      return;
+    _validActions = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Called when the user taps on the render object.
+  GestureTapCallback? get onTap => _onTap;
+  GestureTapCallback? _onTap;
+  set onTap(GestureTapCallback? value) {
+    if (_onTap == value)
+      return;
+    final bool hadHandler = _onTap != null;
+    _onTap = value;
+    if ((value != null) != hadHandler)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// Called when the user presses on the render object for a long period of time.
+  GestureLongPressCallback? get onLongPress => _onLongPress;
+  GestureLongPressCallback? _onLongPress;
+  set onLongPress(GestureLongPressCallback? value) {
+    if (_onLongPress == value)
+      return;
+    final bool hadHandler = _onLongPress != null;
+    _onLongPress = value;
+    if ((value != null) != hadHandler)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// Called when the user scrolls to the left or to the right.
+  GestureDragUpdateCallback? get onHorizontalDragUpdate => _onHorizontalDragUpdate;
+  GestureDragUpdateCallback? _onHorizontalDragUpdate;
+  set onHorizontalDragUpdate(GestureDragUpdateCallback? value) {
+    if (_onHorizontalDragUpdate == value)
+      return;
+    final bool hadHandler = _onHorizontalDragUpdate != null;
+    _onHorizontalDragUpdate = value;
+    if ((value != null) != hadHandler)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// Called when the user scrolls up or down.
+  GestureDragUpdateCallback? get onVerticalDragUpdate => _onVerticalDragUpdate;
+  GestureDragUpdateCallback? _onVerticalDragUpdate;
+  set onVerticalDragUpdate(GestureDragUpdateCallback? value) {
+    if (_onVerticalDragUpdate == value)
+      return;
+    final bool hadHandler = _onVerticalDragUpdate != null;
+    _onVerticalDragUpdate = value;
+    if ((value != null) != hadHandler)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The fraction of the dimension of this render box to use when
+  /// scrolling. For example, if this is 0.8 and the box is 200 pixels
+  /// wide, then when a left-scroll action is received from the
+  /// accessibility system, it will translate into a 160 pixel
+  /// leftwards drag.
+  double scrollFactor;
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+
+    if (onTap != null && _isValidAction(SemanticsAction.tap))
+      config.onTap = onTap;
+    if (onLongPress != null && _isValidAction(SemanticsAction.longPress))
+      config.onLongPress = onLongPress;
+    if (onHorizontalDragUpdate != null) {
+      if (_isValidAction(SemanticsAction.scrollRight))
+        config.onScrollRight = _performSemanticScrollRight;
+      if (_isValidAction(SemanticsAction.scrollLeft))
+        config.onScrollLeft = _performSemanticScrollLeft;
+    }
+    if (onVerticalDragUpdate != null) {
+      if (_isValidAction(SemanticsAction.scrollUp))
+        config.onScrollUp = _performSemanticScrollUp;
+      if (_isValidAction(SemanticsAction.scrollDown))
+        config.onScrollDown = _performSemanticScrollDown;
+    }
+  }
+
+  bool _isValidAction(SemanticsAction action) {
+    return validActions == null || validActions!.contains(action);
+  }
+
+  void _performSemanticScrollLeft() {
+    if (onHorizontalDragUpdate != null) {
+      final double primaryDelta = size.width * -scrollFactor;
+      onHorizontalDragUpdate!(DragUpdateDetails(
+        delta: Offset(primaryDelta, 0.0), primaryDelta: primaryDelta,
+        globalPosition: localToGlobal(size.center(Offset.zero)),
+      ));
+    }
+  }
+
+  void _performSemanticScrollRight() {
+    if (onHorizontalDragUpdate != null) {
+      final double primaryDelta = size.width * scrollFactor;
+      onHorizontalDragUpdate!(DragUpdateDetails(
+        delta: Offset(primaryDelta, 0.0), primaryDelta: primaryDelta,
+        globalPosition: localToGlobal(size.center(Offset.zero)),
+      ));
+    }
+  }
+
+  void _performSemanticScrollUp() {
+    if (onVerticalDragUpdate != null) {
+      final double primaryDelta = size.height * -scrollFactor;
+      onVerticalDragUpdate!(DragUpdateDetails(
+        delta: Offset(0.0, primaryDelta), primaryDelta: primaryDelta,
+        globalPosition: localToGlobal(size.center(Offset.zero)),
+      ));
+    }
+  }
+
+  void _performSemanticScrollDown() {
+    if (onVerticalDragUpdate != null) {
+      final double primaryDelta = size.height * scrollFactor;
+      onVerticalDragUpdate!(DragUpdateDetails(
+        delta: Offset(0.0, primaryDelta), primaryDelta: primaryDelta,
+        globalPosition: localToGlobal(size.center(Offset.zero)),
+      ));
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    final List<String> gestures = <String>[
+      if (onTap != null) 'tap',
+      if (onLongPress != null) 'long press',
+      if (onHorizontalDragUpdate != null) 'horizontal scroll',
+      if (onVerticalDragUpdate != null) 'vertical scroll',
+    ];
+    if (gestures.isEmpty)
+      gestures.add('<none>');
+    properties.add(IterableProperty<String>('gestures', gestures));
+  }
+}
+
+/// Add annotations to the [SemanticsNode] for this subtree.
+class RenderSemanticsAnnotations extends RenderProxyBox {
+  /// Creates a render object that attaches a semantic annotation.
+  ///
+  /// The [container] argument must not be null.
+  ///
+  /// If the [label] is not null, the [textDirection] must also not be null.
+  RenderSemanticsAnnotations({
+    RenderBox? child,
+    bool container = false,
+    bool explicitChildNodes = false,
+    bool excludeSemantics = false,
+    bool? enabled,
+    bool? checked,
+    bool? toggled,
+    bool? selected,
+    bool? button,
+    bool? slider,
+    bool? link,
+    bool? header,
+    bool? textField,
+    bool? readOnly,
+    bool? focusable,
+    bool? focused,
+    bool? inMutuallyExclusiveGroup,
+    bool? obscured,
+    bool? multiline,
+    bool? scopesRoute,
+    bool? namesRoute,
+    bool? hidden,
+    bool? image,
+    bool? liveRegion,
+    int? maxValueLength,
+    int? currentValueLength,
+    String? label,
+    String? value,
+    String? increasedValue,
+    String? decreasedValue,
+    String? hint,
+    SemanticsHintOverrides? hintOverrides,
+    TextDirection? textDirection,
+    SemanticsSortKey? sortKey,
+    SemanticsTag? tagForChildren,
+    VoidCallback? onTap,
+    VoidCallback? onDismiss,
+    VoidCallback? onLongPress,
+    VoidCallback? onScrollLeft,
+    VoidCallback? onScrollRight,
+    VoidCallback? onScrollUp,
+    VoidCallback? onScrollDown,
+    VoidCallback? onIncrease,
+    VoidCallback? onDecrease,
+    VoidCallback? onCopy,
+    VoidCallback? onCut,
+    VoidCallback? onPaste,
+    MoveCursorHandler? onMoveCursorForwardByCharacter,
+    MoveCursorHandler? onMoveCursorBackwardByCharacter,
+    MoveCursorHandler? onMoveCursorForwardByWord,
+    MoveCursorHandler? onMoveCursorBackwardByWord,
+    SetSelectionHandler? onSetSelection,
+    VoidCallback? onDidGainAccessibilityFocus,
+    VoidCallback? onDidLoseAccessibilityFocus,
+    Map<CustomSemanticsAction, VoidCallback>? customSemanticsActions,
+  }) : assert(container != null),
+       _container = container,
+       _explicitChildNodes = explicitChildNodes,
+       _excludeSemantics = excludeSemantics,
+       _enabled = enabled,
+       _checked = checked,
+       _toggled = toggled,
+       _selected = selected,
+       _button = button,
+       _slider = slider,
+       _link = link,
+       _header = header,
+       _textField = textField,
+       _readOnly = readOnly,
+       _focusable = focusable,
+       _focused = focused,
+       _inMutuallyExclusiveGroup = inMutuallyExclusiveGroup,
+       _obscured = obscured,
+       _multiline = multiline,
+       _scopesRoute = scopesRoute,
+       _namesRoute = namesRoute,
+       _liveRegion = liveRegion,
+       _maxValueLength = maxValueLength,
+       _currentValueLength = currentValueLength,
+       _hidden = hidden,
+       _image = image,
+       _onDismiss = onDismiss,
+       _label = label,
+       _value = value,
+       _increasedValue = increasedValue,
+       _decreasedValue = decreasedValue,
+       _hint = hint,
+       _hintOverrides = hintOverrides,
+       _textDirection = textDirection,
+       _sortKey = sortKey,
+       _tagForChildren = tagForChildren,
+       _onTap = onTap,
+       _onLongPress = onLongPress,
+       _onScrollLeft = onScrollLeft,
+       _onScrollRight = onScrollRight,
+       _onScrollUp = onScrollUp,
+       _onScrollDown = onScrollDown,
+       _onIncrease = onIncrease,
+       _onDecrease = onDecrease,
+       _onCopy = onCopy,
+       _onCut = onCut,
+       _onPaste = onPaste,
+       _onMoveCursorForwardByCharacter = onMoveCursorForwardByCharacter,
+       _onMoveCursorBackwardByCharacter = onMoveCursorBackwardByCharacter,
+       _onMoveCursorForwardByWord = onMoveCursorForwardByWord,
+       _onMoveCursorBackwardByWord = onMoveCursorBackwardByWord,
+       _onSetSelection = onSetSelection,
+       _onDidGainAccessibilityFocus = onDidGainAccessibilityFocus,
+       _onDidLoseAccessibilityFocus = onDidLoseAccessibilityFocus,
+       _customSemanticsActions = customSemanticsActions,
+       super(child);
+
+  /// If 'container' is true, this [RenderObject] will introduce a new
+  /// node in the semantics tree. Otherwise, the semantics will be
+  /// merged with the semantics of any ancestors.
+  ///
+  /// Whether descendants of this [RenderObject] can add their semantic information
+  /// to the [SemanticsNode] introduced by this configuration is controlled by
+  /// [explicitChildNodes].
+  bool get container => _container;
+  bool _container;
+  set container(bool value) {
+    assert(value != null);
+    if (container == value)
+      return;
+    _container = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Whether descendants of this [RenderObject] are allowed to add semantic
+  /// information to the [SemanticsNode] annotated by this widget.
+  ///
+  /// When set to false descendants are allowed to annotate [SemanticsNode]s of
+  /// their parent with the semantic information they want to contribute to the
+  /// semantic tree.
+  /// When set to true the only way for descendants to contribute semantic
+  /// information to the semantic tree is to introduce new explicit
+  /// [SemanticsNode]s to the tree.
+  ///
+  /// This setting is often used in combination with
+  /// [SemanticsConfiguration.isSemanticBoundary] to create semantic boundaries
+  /// that are either writable or not for children.
+  bool get explicitChildNodes => _explicitChildNodes;
+  bool _explicitChildNodes;
+  set explicitChildNodes(bool value) {
+    assert(value != null);
+    if (_explicitChildNodes == value)
+      return;
+    _explicitChildNodes = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Whether descendants of this [RenderObject] should have their semantic
+  /// information ignored.
+  ///
+  /// When this flag is set to true, all child semantics nodes are ignored.
+  /// This can be used as a convenience for cases where a child is wrapped in
+  /// an [ExcludeSemantics] widget and then another [Semantics] widget.
+  bool get excludeSemantics => _excludeSemantics;
+  bool _excludeSemantics;
+  set excludeSemantics(bool value) {
+    assert(value != null);
+    if (_excludeSemantics == value)
+      return;
+    _excludeSemantics = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsFlag.hasCheckedState] semantic to true and
+  /// the [SemanticsConfiguration.isChecked] semantic to the given value.
+  bool? get checked => _checked;
+  bool? _checked;
+  set checked(bool? value) {
+    if (checked == value)
+      return;
+    _checked = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsFlag.hasEnabledState] semantic to true and
+  /// the [SemanticsConfiguration.isEnabled] semantic to the given value.
+  bool? get enabled => _enabled;
+  bool? _enabled;
+  set enabled(bool? value) {
+    if (enabled == value)
+      return;
+    _enabled = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.isSelected] semantic to the
+  /// given value.
+  bool? get selected => _selected;
+  bool? _selected;
+  set selected(bool? value) {
+    if (selected == value)
+      return;
+    _selected = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.isButton] semantic to the
+  /// given value.
+  bool? get button => _button;
+  bool? _button;
+  set button(bool? value) {
+    if (button == value)
+      return;
+    _button = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.isSlider] semantic to the
+  /// given value.
+  bool? get slider => _slider;
+  bool? _slider;
+  set slider(bool? value) {
+    if (slider == value)
+      return;
+    _slider = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.isLink] semantic to the
+  /// given value.
+  bool? get link => _link;
+  bool? _link;
+  set link(bool? value) {
+    if (link == value)
+      return;
+    _link = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.isHeader] semantic to the
+  /// given value.
+  bool? get header => _header;
+  bool? _header;
+  set header(bool? value) {
+    if (header == value)
+      return;
+    _header = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.isTextField] semantic to the
+  /// given value.
+  bool? get textField => _textField;
+  bool? _textField;
+  set textField(bool? value) {
+    if (textField == value)
+      return;
+    _textField = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.isReadOnly] semantic to the
+  /// given value.
+  bool? get readOnly => _readOnly;
+  bool? _readOnly;
+  set readOnly(bool? value) {
+    if (readOnly == value)
+      return;
+    _readOnly = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.isFocusable] semantic to the
+  /// given value.
+  bool? get focusable => _focusable;
+  bool? _focusable;
+  set focusable(bool? value) {
+    if (focusable == value)
+      return;
+    _focusable = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.isFocused] semantic to the
+  /// given value.
+  bool? get focused => _focused;
+  bool? _focused;
+  set focused(bool? value) {
+    if (focused == value)
+      return;
+    _focused = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.isInMutuallyExclusiveGroup]
+  /// semantic to the given value.
+  bool? get inMutuallyExclusiveGroup => _inMutuallyExclusiveGroup;
+  bool? _inMutuallyExclusiveGroup;
+  set inMutuallyExclusiveGroup(bool? value) {
+    if (inMutuallyExclusiveGroup == value)
+      return;
+    _inMutuallyExclusiveGroup = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.isObscured] semantic to the
+  /// given value.
+  bool? get obscured => _obscured;
+  bool? _obscured;
+  set obscured(bool? value) {
+    if (obscured == value)
+      return;
+    _obscured = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsNode.isMultiline] semantic to the given
+  /// value.
+  bool? get multiline => _multiline;
+  bool? _multiline;
+  set multiline(bool? value) {
+    if (multiline == value)
+      return;
+    _multiline = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.scopesRoute] semantic to the
+  /// give value.
+  bool? get scopesRoute => _scopesRoute;
+  bool? _scopesRoute;
+  set scopesRoute(bool? value) {
+    if (scopesRoute == value)
+      return;
+    _scopesRoute = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.namesRoute] semantic to the
+  /// give value.
+  bool? get namesRoute => _namesRoute;
+  bool? _namesRoute;
+  set namesRoute(bool? value) {
+    if (_namesRoute == value)
+      return;
+    _namesRoute = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.isHidden] semantic to the
+  /// given value.
+  bool? get hidden => _hidden;
+  bool? _hidden;
+  set hidden(bool? value) {
+    if (hidden == value)
+      return;
+    _hidden = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.isImage] semantic to the
+  /// given value.
+  bool? get image => _image;
+  bool? _image;
+  set image(bool? value) {
+    if (_image == value)
+      return;
+    _image = value;
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.liveRegion] semantic to
+  /// the given value.
+  bool? get liveRegion => _liveRegion;
+  bool? _liveRegion;
+  set liveRegion(bool? value) {
+    if (_liveRegion == value)
+      return;
+    _liveRegion = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsNode.maxValueLength] semantic to the given
+  /// value.
+  int? get maxValueLength => _maxValueLength;
+  int? _maxValueLength;
+  set maxValueLength(int? value) {
+    if (_maxValueLength == value)
+      return;
+    _maxValueLength = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsNode.currentValueLength] semantic to the
+  /// given value.
+  int? get currentValueLength => _currentValueLength;
+  int? _currentValueLength;
+  set currentValueLength(int? value) {
+    if (_currentValueLength == value)
+      return;
+    _currentValueLength = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.isToggled] semantic to the given
+  /// value.
+  bool? get toggled => _toggled;
+  bool? _toggled;
+  set toggled(bool? value) {
+    if (_toggled == value)
+      return;
+    _toggled = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsNode.label] semantic to the given value.
+  ///
+  /// The reading direction is given by [textDirection].
+  String? get label => _label;
+  String? _label;
+  set label(String? value) {
+    if (_label == value)
+      return;
+    _label = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsNode.value] semantic to the given value.
+  ///
+  /// The reading direction is given by [textDirection].
+  String? get value => _value;
+  String? _value;
+  set value(String? value) {
+    if (_value == value)
+      return;
+    _value = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsNode.increasedValue] semantic to the given
+  /// value.
+  ///
+  /// The reading direction is given by [textDirection].
+  String? get increasedValue => _increasedValue;
+  String? _increasedValue;
+  set increasedValue(String? value) {
+    if (_increasedValue == value)
+      return;
+    _increasedValue = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsNode.decreasedValue] semantic to the given
+  /// value.
+  ///
+  /// The reading direction is given by [textDirection].
+  String? get decreasedValue => _decreasedValue;
+  String? _decreasedValue;
+  set decreasedValue(String? value) {
+    if (_decreasedValue == value)
+      return;
+    _decreasedValue = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsNode.hint] semantic to the given value.
+  ///
+  /// The reading direction is given by [textDirection].
+  String? get hint => _hint;
+  String? _hint;
+  set hint(String? value) {
+    if (_hint == value)
+      return;
+    _hint = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsConfiguration.hintOverrides] to the given value.
+  SemanticsHintOverrides? get hintOverrides => _hintOverrides;
+  SemanticsHintOverrides? _hintOverrides;
+  set hintOverrides(SemanticsHintOverrides? value) {
+    if (_hintOverrides == value)
+      return;
+    _hintOverrides = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsNode.textDirection] semantic to the given value.
+  ///
+  /// This must not be null if [label], [hint], [value], [increasedValue], or
+  /// [decreasedValue] are not null.
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection;
+  set textDirection(TextDirection? value) {
+    if (textDirection == value)
+      return;
+    _textDirection = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Sets the [SemanticsNode.sortKey] to the given value.
+  ///
+  /// This defines how this node is sorted among the sibling semantics nodes
+  /// to determine the order in which they are traversed by the accessibility
+  /// services on the platform (e.g. VoiceOver on iOS and TalkBack on Android).
+  SemanticsSortKey? get sortKey => _sortKey;
+  SemanticsSortKey? _sortKey;
+  set sortKey(SemanticsSortKey? value) {
+    if (sortKey == value)
+      return;
+    _sortKey = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Adds a semenatics tag to the semantics subtree.
+  SemanticsTag? get tagForChildren => _tagForChildren;
+  SemanticsTag? _tagForChildren;
+  set tagForChildren(SemanticsTag? value) {
+    if (_tagForChildren == value)
+      return;
+    markNeedsSemanticsUpdate();
+    _tagForChildren = value;
+  }
+
+  /// The handler for [SemanticsAction.tap].
+  ///
+  /// This is the semantic equivalent of a user briefly tapping the screen with
+  /// the finger without moving it. For example, a button should implement this
+  /// action.
+  ///
+  /// VoiceOver users on iOS and TalkBack users on Android can trigger this
+  /// action by double-tapping the screen while an element is focused.
+  VoidCallback? get onTap => _onTap;
+  VoidCallback? _onTap;
+  set onTap(VoidCallback? handler) {
+    if (_onTap == handler)
+      return;
+    final bool hadValue = _onTap != null;
+    _onTap = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.dismiss].
+  ///
+  /// This is a request to dismiss the currently focused node.
+  ///
+  /// TalkBack users on Android can trigger this action in the local context
+  /// menu, and VoiceOver users on iOS can trigger this action with a standard
+  /// gesture or menu option.
+  VoidCallback? get onDismiss => _onDismiss;
+  VoidCallback? _onDismiss;
+  set onDismiss(VoidCallback? handler) {
+    if (_onDismiss == handler)
+      return;
+    final bool hadValue = _onDismiss != null;
+    _onDismiss = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.longPress].
+  ///
+  /// This is the semantic equivalent of a user pressing and holding the screen
+  /// with the finger for a few seconds without moving it.
+  ///
+  /// VoiceOver users on iOS and TalkBack users on Android can trigger this
+  /// action by double-tapping the screen without lifting the finger after the
+  /// second tap.
+  VoidCallback? get onLongPress => _onLongPress;
+  VoidCallback? _onLongPress;
+  set onLongPress(VoidCallback? handler) {
+    if (_onLongPress == handler)
+      return;
+    final bool hadValue = _onLongPress != null;
+    _onLongPress = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.scrollLeft].
+  ///
+  /// This is the semantic equivalent of a user moving their finger across the
+  /// screen from right to left. It should be recognized by controls that are
+  /// horizontally scrollable.
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping left with three
+  /// fingers. TalkBack users on Android can trigger this action by swiping
+  /// right and then left in one motion path. On Android, [onScrollUp] and
+  /// [onScrollLeft] share the same gesture. Therefore, only on of them should
+  /// be provided.
+  VoidCallback? get onScrollLeft => _onScrollLeft;
+  VoidCallback? _onScrollLeft;
+  set onScrollLeft(VoidCallback? handler) {
+    if (_onScrollLeft == handler)
+      return;
+    final bool hadValue = _onScrollLeft != null;
+    _onScrollLeft = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.scrollRight].
+  ///
+  /// This is the semantic equivalent of a user moving their finger across the
+  /// screen from left to right. It should be recognized by controls that are
+  /// horizontally scrollable.
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping right with three
+  /// fingers. TalkBack users on Android can trigger this action by swiping
+  /// left and then right in one motion path. On Android, [onScrollDown] and
+  /// [onScrollRight] share the same gesture. Therefore, only on of them should
+  /// be provided.
+  VoidCallback? get onScrollRight => _onScrollRight;
+  VoidCallback? _onScrollRight;
+  set onScrollRight(VoidCallback? handler) {
+    if (_onScrollRight == handler)
+      return;
+    final bool hadValue = _onScrollRight != null;
+    _onScrollRight = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.scrollUp].
+  ///
+  /// This is the semantic equivalent of a user moving their finger across the
+  /// screen from bottom to top. It should be recognized by controls that are
+  /// vertically scrollable.
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping up with three
+  /// fingers. TalkBack users on Android can trigger this action by swiping
+  /// right and then left in one motion path. On Android, [onScrollUp] and
+  /// [onScrollLeft] share the same gesture. Therefore, only on of them should
+  /// be provided.
+  VoidCallback? get onScrollUp => _onScrollUp;
+  VoidCallback? _onScrollUp;
+  set onScrollUp(VoidCallback? handler) {
+    if (_onScrollUp == handler)
+      return;
+    final bool hadValue = _onScrollUp != null;
+    _onScrollUp = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.scrollDown].
+  ///
+  /// This is the semantic equivalent of a user moving their finger across the
+  /// screen from top to bottom. It should be recognized by controls that are
+  /// vertically scrollable.
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping down with three
+  /// fingers. TalkBack users on Android can trigger this action by swiping
+  /// left and then right in one motion path. On Android, [onScrollDown] and
+  /// [onScrollRight] share the same gesture. Therefore, only on of them should
+  /// be provided.
+  VoidCallback? get onScrollDown => _onScrollDown;
+  VoidCallback? _onScrollDown;
+  set onScrollDown(VoidCallback? handler) {
+    if (_onScrollDown == handler)
+      return;
+    final bool hadValue = _onScrollDown != null;
+    _onScrollDown = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.increase].
+  ///
+  /// This is a request to increase the value represented by the widget. For
+  /// example, this action might be recognized by a slider control.
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping up with one
+  /// finger. TalkBack users on Android can trigger this action by pressing the
+  /// volume up button.
+  VoidCallback? get onIncrease => _onIncrease;
+  VoidCallback? _onIncrease;
+  set onIncrease(VoidCallback? handler) {
+    if (_onIncrease == handler)
+      return;
+    final bool hadValue = _onIncrease != null;
+    _onIncrease = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.decrease].
+  ///
+  /// This is a request to decrease the value represented by the widget. For
+  /// example, this action might be recognized by a slider control.
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping down with one
+  /// finger. TalkBack users on Android can trigger this action by pressing the
+  /// volume down button.
+  VoidCallback? get onDecrease => _onDecrease;
+  VoidCallback? _onDecrease;
+  set onDecrease(VoidCallback? handler) {
+    if (_onDecrease == handler)
+      return;
+    final bool hadValue = _onDecrease != null;
+    _onDecrease = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.copy].
+  ///
+  /// This is a request to copy the current selection to the clipboard.
+  ///
+  /// TalkBack users on Android can trigger this action from the local context
+  /// menu of a text field, for example.
+  VoidCallback? get onCopy => _onCopy;
+  VoidCallback? _onCopy;
+  set onCopy(VoidCallback? handler) {
+    if (_onCopy == handler)
+      return;
+    final bool hadValue = _onCopy != null;
+    _onCopy = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.cut].
+  ///
+  /// This is a request to cut the current selection and place it in the
+  /// clipboard.
+  ///
+  /// TalkBack users on Android can trigger this action from the local context
+  /// menu of a text field, for example.
+  VoidCallback? get onCut => _onCut;
+  VoidCallback? _onCut;
+  set onCut(VoidCallback? handler) {
+    if (_onCut == handler)
+      return;
+    final bool hadValue = _onCut != null;
+    _onCut = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.paste].
+  ///
+  /// This is a request to paste the current content of the clipboard.
+  ///
+  /// TalkBack users on Android can trigger this action from the local context
+  /// menu of a text field, for example.
+  VoidCallback? get onPaste => _onPaste;
+  VoidCallback? _onPaste;
+  set onPaste(VoidCallback? handler) {
+    if (_onPaste == handler)
+      return;
+    final bool hadValue = _onPaste != null;
+    _onPaste = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.moveCursorForwardByCharacter].
+  ///
+  /// This handler is invoked when the user wants to move the cursor in a
+  /// text field forward by one character.
+  ///
+  /// TalkBack users can trigger this by pressing the volume up key while the
+  /// input focus is in a text field.
+  MoveCursorHandler? get onMoveCursorForwardByCharacter => _onMoveCursorForwardByCharacter;
+  MoveCursorHandler? _onMoveCursorForwardByCharacter;
+  set onMoveCursorForwardByCharacter(MoveCursorHandler? handler) {
+    if (_onMoveCursorForwardByCharacter == handler)
+      return;
+    final bool hadValue = _onMoveCursorForwardByCharacter != null;
+    _onMoveCursorForwardByCharacter = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.moveCursorBackwardByCharacter].
+  ///
+  /// This handler is invoked when the user wants to move the cursor in a
+  /// text field backward by one character.
+  ///
+  /// TalkBack users can trigger this by pressing the volume down key while the
+  /// input focus is in a text field.
+  MoveCursorHandler? get onMoveCursorBackwardByCharacter => _onMoveCursorBackwardByCharacter;
+  MoveCursorHandler? _onMoveCursorBackwardByCharacter;
+  set onMoveCursorBackwardByCharacter(MoveCursorHandler? handler) {
+    if (_onMoveCursorBackwardByCharacter == handler)
+      return;
+    final bool hadValue = _onMoveCursorBackwardByCharacter != null;
+    _onMoveCursorBackwardByCharacter = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.moveCursorForwardByWord].
+  ///
+  /// This handler is invoked when the user wants to move the cursor in a
+  /// text field backward by one character.
+  ///
+  /// TalkBack users can trigger this by pressing the volume down key while the
+  /// input focus is in a text field.
+  MoveCursorHandler? get onMoveCursorForwardByWord => _onMoveCursorForwardByWord;
+  MoveCursorHandler? _onMoveCursorForwardByWord;
+  set onMoveCursorForwardByWord(MoveCursorHandler? handler) {
+    if (_onMoveCursorForwardByWord == handler)
+      return;
+    final bool hadValue = _onMoveCursorForwardByWord != null;
+    _onMoveCursorForwardByWord = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.moveCursorBackwardByWord].
+  ///
+  /// This handler is invoked when the user wants to move the cursor in a
+  /// text field backward by one character.
+  ///
+  /// TalkBack users can trigger this by pressing the volume down key while the
+  /// input focus is in a text field.
+  MoveCursorHandler? get onMoveCursorBackwardByWord => _onMoveCursorBackwardByWord;
+  MoveCursorHandler? _onMoveCursorBackwardByWord;
+  set onMoveCursorBackwardByWord(MoveCursorHandler? handler) {
+    if (_onMoveCursorBackwardByWord == handler)
+      return;
+    final bool hadValue = _onMoveCursorBackwardByWord != null;
+    _onMoveCursorBackwardByWord = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.setSelection].
+  ///
+  /// This handler is invoked when the user either wants to change the currently
+  /// selected text in a text field or change the position of the cursor.
+  ///
+  /// TalkBack users can trigger this handler by selecting "Move cursor to
+  /// beginning/end" or "Select all" from the local context menu.
+  SetSelectionHandler? get onSetSelection => _onSetSelection;
+  SetSelectionHandler? _onSetSelection;
+  set onSetSelection(SetSelectionHandler? handler) {
+    if (_onSetSelection == handler)
+      return;
+    final bool hadValue = _onSetSelection != null;
+    _onSetSelection = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.didGainAccessibilityFocus].
+  ///
+  /// This handler is invoked when the node annotated with this handler gains
+  /// the accessibility focus. The accessibility focus is the
+  /// green (on Android with TalkBack) or black (on iOS with VoiceOver)
+  /// rectangle shown on screen to indicate what element an accessibility
+  /// user is currently interacting with.
+  ///
+  /// The accessibility focus is different from the input focus. The input focus
+  /// is usually held by the element that currently responds to keyboard inputs.
+  /// Accessibility focus and input focus can be held by two different nodes!
+  ///
+  /// See also:
+  ///
+  ///  * [onDidLoseAccessibilityFocus], which is invoked when the accessibility
+  ///    focus is removed from the node.
+  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
+  VoidCallback? get onDidGainAccessibilityFocus => _onDidGainAccessibilityFocus;
+  VoidCallback? _onDidGainAccessibilityFocus;
+  set onDidGainAccessibilityFocus(VoidCallback? handler) {
+    if (_onDidGainAccessibilityFocus == handler)
+      return;
+    final bool hadValue = _onDidGainAccessibilityFocus != null;
+    _onDidGainAccessibilityFocus = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handler for [SemanticsAction.didLoseAccessibilityFocus].
+  ///
+  /// This handler is invoked when the node annotated with this handler
+  /// loses the accessibility focus. The accessibility focus is
+  /// the green (on Android with TalkBack) or black (on iOS with VoiceOver)
+  /// rectangle shown on screen to indicate what element an accessibility
+  /// user is currently interacting with.
+  ///
+  /// The accessibility focus is different from the input focus. The input focus
+  /// is usually held by the element that currently responds to keyboard inputs.
+  /// Accessibility focus and input focus can be held by two different nodes!
+  ///
+  /// See also:
+  ///
+  ///  * [onDidGainAccessibilityFocus], which is invoked when the node gains
+  ///    accessibility focus.
+  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
+  VoidCallback? get onDidLoseAccessibilityFocus => _onDidLoseAccessibilityFocus;
+  VoidCallback? _onDidLoseAccessibilityFocus;
+  set onDidLoseAccessibilityFocus(VoidCallback? handler) {
+    if (_onDidLoseAccessibilityFocus == handler)
+      return;
+    final bool hadValue = _onDidLoseAccessibilityFocus != null;
+    _onDidLoseAccessibilityFocus = handler;
+    if ((handler != null) != hadValue)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// The handlers and supported [CustomSemanticsAction]s for this node.
+  ///
+  /// These handlers are called whenever the user performs the associated
+  /// custom accessibility action from a special platform menu. Providing any
+  /// custom actions here also adds [SemanticsAction.customAction] to the node.
+  ///
+  /// See also:
+  ///
+  ///  * [CustomSemanticsAction], for an explanation of custom actions.
+  Map<CustomSemanticsAction, VoidCallback>? get customSemanticsActions => _customSemanticsActions;
+  Map<CustomSemanticsAction, VoidCallback>? _customSemanticsActions;
+  set customSemanticsActions(Map<CustomSemanticsAction, VoidCallback>? value) {
+    if (_customSemanticsActions == value)
+      return;
+    _customSemanticsActions = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    if (excludeSemantics)
+      return;
+    super.visitChildrenForSemantics(visitor);
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    config.isSemanticBoundary = container;
+    config.explicitChildNodes = explicitChildNodes;
+    assert((scopesRoute == true && explicitChildNodes == true) || scopesRoute != true,
+      'explicitChildNodes must be set to true if scopes route is true');
+    assert(!(toggled == true && checked == true),
+      'A semantics node cannot be toggled and checked at the same time');
+
+    if (enabled != null)
+      config.isEnabled = enabled;
+    if (checked != null)
+      config.isChecked = checked;
+    if (toggled != null)
+      config.isToggled = toggled;
+    if (selected != null)
+      config.isSelected = selected!;
+    if (button != null)
+      config.isButton = button!;
+    if (link != null)
+      config.isLink = link!;
+    if (slider != null)
+      config.isSlider = slider!;
+    if (header != null)
+      config.isHeader = header!;
+    if (textField != null)
+      config.isTextField = textField!;
+    if (readOnly != null)
+      config.isReadOnly = readOnly!;
+    if (focusable != null)
+      config.isFocusable = focusable!;
+    if (focused != null)
+      config.isFocused = focused!;
+    if (inMutuallyExclusiveGroup != null)
+      config.isInMutuallyExclusiveGroup = inMutuallyExclusiveGroup!;
+    if (obscured != null)
+      config.isObscured = obscured!;
+    if (multiline != null)
+      config.isMultiline = multiline!;
+    if (hidden != null)
+      config.isHidden = hidden!;
+    if (image != null)
+      config.isImage = image!;
+    if (label != null)
+      config.label = label!;
+    if (value != null)
+      config.value = value!;
+    if (increasedValue != null)
+      config.increasedValue = increasedValue!;
+    if (decreasedValue != null)
+      config.decreasedValue = decreasedValue!;
+    if (hint != null)
+      config.hint = hint!;
+    if (hintOverrides != null && hintOverrides!.isNotEmpty)
+      config.hintOverrides = hintOverrides;
+    if (scopesRoute != null)
+      config.scopesRoute = scopesRoute!;
+    if (namesRoute != null)
+      config.namesRoute = namesRoute!;
+    if (liveRegion != null)
+      config.liveRegion = liveRegion!;
+    if (maxValueLength != null) {
+      config.maxValueLength = maxValueLength;
+    }
+    if (currentValueLength != null) {
+      config.currentValueLength = currentValueLength;
+    }
+    if (textDirection != null)
+      config.textDirection = textDirection;
+    if (sortKey != null)
+      config.sortKey = sortKey;
+    if (tagForChildren != null)
+      config.addTagForChildren(tagForChildren!);
+    // Registering _perform* as action handlers instead of the user provided
+    // ones to ensure that changing a user provided handler from a non-null to
+    // another non-null value doesn't require a semantics update.
+    if (onTap != null)
+      config.onTap = _performTap;
+    if (onLongPress != null)
+      config.onLongPress = _performLongPress;
+    if (onDismiss != null)
+      config.onDismiss = _performDismiss;
+    if (onScrollLeft != null)
+      config.onScrollLeft = _performScrollLeft;
+    if (onScrollRight != null)
+      config.onScrollRight = _performScrollRight;
+    if (onScrollUp != null)
+      config.onScrollUp = _performScrollUp;
+    if (onScrollDown != null)
+      config.onScrollDown = _performScrollDown;
+    if (onIncrease != null)
+      config.onIncrease = _performIncrease;
+    if (onDecrease != null)
+      config.onDecrease = _performDecrease;
+    if (onCopy != null)
+      config.onCopy = _performCopy;
+    if (onCut != null)
+      config.onCut = _performCut;
+    if (onPaste != null)
+      config.onPaste = _performPaste;
+    if (onMoveCursorForwardByCharacter != null)
+      config.onMoveCursorForwardByCharacter = _performMoveCursorForwardByCharacter;
+    if (onMoveCursorBackwardByCharacter != null)
+      config.onMoveCursorBackwardByCharacter = _performMoveCursorBackwardByCharacter;
+    if (onMoveCursorForwardByWord != null)
+      config.onMoveCursorForwardByWord = _performMoveCursorForwardByWord;
+    if (onMoveCursorBackwardByWord != null)
+      config.onMoveCursorBackwardByWord = _performMoveCursorBackwardByWord;
+    if (onSetSelection != null)
+      config.onSetSelection = _performSetSelection;
+    if (onDidGainAccessibilityFocus != null)
+      config.onDidGainAccessibilityFocus = _performDidGainAccessibilityFocus;
+    if (onDidLoseAccessibilityFocus != null)
+      config.onDidLoseAccessibilityFocus = _performDidLoseAccessibilityFocus;
+    if (customSemanticsActions != null)
+      config.customSemanticsActions = _customSemanticsActions!;
+  }
+
+  void _performTap() {
+    if (onTap != null)
+      onTap!();
+  }
+
+  void _performLongPress() {
+    if (onLongPress != null)
+      onLongPress!();
+  }
+
+  void _performDismiss() {
+    if (onDismiss != null)
+      onDismiss!();
+  }
+
+  void _performScrollLeft() {
+    if (onScrollLeft != null)
+      onScrollLeft!();
+  }
+
+  void _performScrollRight() {
+    if (onScrollRight != null)
+      onScrollRight!();
+  }
+
+  void _performScrollUp() {
+    if (onScrollUp != null)
+      onScrollUp!();
+  }
+
+  void _performScrollDown() {
+    if (onScrollDown != null)
+      onScrollDown!();
+  }
+
+  void _performIncrease() {
+    if (onIncrease != null)
+      onIncrease!();
+  }
+
+  void _performDecrease() {
+    if (onDecrease != null)
+      onDecrease!();
+  }
+
+  void _performCopy() {
+    if (onCopy != null)
+      onCopy!();
+  }
+
+  void _performCut() {
+    if (onCut != null)
+      onCut!();
+  }
+
+  void _performPaste() {
+    if (onPaste != null)
+      onPaste!();
+  }
+
+  void _performMoveCursorForwardByCharacter(bool extendSelection) {
+    if (onMoveCursorForwardByCharacter != null)
+      onMoveCursorForwardByCharacter!(extendSelection);
+  }
+
+  void _performMoveCursorBackwardByCharacter(bool extendSelection) {
+    if (onMoveCursorBackwardByCharacter != null)
+      onMoveCursorBackwardByCharacter!(extendSelection);
+  }
+
+  void _performMoveCursorForwardByWord(bool extendSelection) {
+    if (onMoveCursorForwardByWord != null)
+      onMoveCursorForwardByWord!(extendSelection);
+  }
+
+  void _performMoveCursorBackwardByWord(bool extendSelection) {
+    if (onMoveCursorBackwardByWord != null)
+      onMoveCursorBackwardByWord!(extendSelection);
+  }
+
+  void _performSetSelection(TextSelection selection) {
+    if (onSetSelection != null)
+      onSetSelection!(selection);
+  }
+
+  void _performDidGainAccessibilityFocus() {
+    if (onDidGainAccessibilityFocus != null)
+      onDidGainAccessibilityFocus!();
+  }
+
+  void _performDidLoseAccessibilityFocus() {
+    if (onDidLoseAccessibilityFocus != null)
+      onDidLoseAccessibilityFocus!();
+  }
+}
+
+/// Causes the semantics of all earlier render objects below the same semantic
+/// boundary to be dropped.
+///
+/// This is useful in a stack where an opaque mask should prevent interactions
+/// with the render objects painted below the mask.
+class RenderBlockSemantics extends RenderProxyBox {
+  /// Create a render object that blocks semantics for nodes below it in paint
+  /// order.
+  RenderBlockSemantics({
+    RenderBox? child,
+    bool blocking = true,
+  }) : _blocking = blocking,
+       super(child);
+
+  /// Whether this render object is blocking semantics of previously painted
+  /// [RenderObject]s below a common semantics boundary from the semantic tree.
+  bool get blocking => _blocking;
+  bool _blocking;
+  set blocking(bool value) {
+    assert(value != null);
+    if (value == _blocking)
+      return;
+    _blocking = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    config.isBlockingSemanticsOfPreviouslyPaintedNodes = blocking;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('blocking', blocking));
+  }
+}
+
+/// Causes the semantics of all descendants to be merged into this
+/// node such that the entire subtree becomes a single leaf in the
+/// semantics tree.
+///
+/// Useful for combining the semantics of multiple render objects that
+/// form part of a single conceptual widget, e.g. a checkbox, a label,
+/// and the gesture detector that goes with them.
+class RenderMergeSemantics extends RenderProxyBox {
+  /// Creates a render object that merges the semantics from its descendants.
+  RenderMergeSemantics({ RenderBox? child }) : super(child);
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    config
+      ..isSemanticBoundary = true
+      ..isMergingSemanticsOfDescendants = true;
+  }
+}
+
+/// Excludes this subtree from the semantic tree.
+///
+/// When [excluding] is true, this render object (and its subtree) is excluded
+/// from the semantic tree.
+///
+/// Useful e.g. for hiding text that is redundant with other text next
+/// to it (e.g. text included only for the visual effect).
+class RenderExcludeSemantics extends RenderProxyBox {
+  /// Creates a render object that ignores the semantics of its subtree.
+  RenderExcludeSemantics({
+    RenderBox? child,
+    bool excluding = true,
+  }) : _excluding = excluding,
+       super(child) {
+    assert(_excluding != null);
+  }
+
+  /// Whether this render object is excluded from the semantic tree.
+  bool get excluding => _excluding;
+  bool _excluding;
+  set excluding(bool value) {
+    assert(value != null);
+    if (value == _excluding)
+      return;
+    _excluding = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    if (excluding)
+      return;
+    super.visitChildrenForSemantics(visitor);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('excluding', excluding));
+  }
+}
+
+/// A render objects that annotates semantics with an index.
+///
+/// Certain widgets will automatically provide a child index for building
+/// semantics. For example, the [ScrollView] uses the index of the first
+/// visible child semantics node to determine the
+/// [SemanticsConfiguration.scrollIndex].
+///
+/// See also:
+///
+///  * [CustomScrollView], for an explanation of scroll semantics.
+class RenderIndexedSemantics extends RenderProxyBox {
+  /// Creates a render object that annotates the child semantics with an index.
+  RenderIndexedSemantics({
+    RenderBox? child,
+    required int index,
+  }) : assert(index != null),
+       _index = index,
+       super(child);
+
+  /// The index used to annotated child semantics.
+  int get index => _index;
+  int _index;
+  set index(int value) {
+    if (value == index)
+      return;
+    _index = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    config.isSemanticBoundary = true;
+    config.indexInParent = index;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<int>('index', index));
+  }
+}
+
+/// Provides an anchor for a [RenderFollowerLayer].
+///
+/// See also:
+///
+///  * [CompositedTransformTarget], the corresponding widget.
+///  * [LeaderLayer], the layer that this render object creates.
+class RenderLeaderLayer extends RenderProxyBox {
+  /// Creates a render object that uses a [LeaderLayer].
+  ///
+  /// The [link] must not be null.
+  RenderLeaderLayer({
+    required LayerLink link,
+    RenderBox? child,
+  }) : assert(link != null),
+       _link = link,
+       super(child);
+
+  /// The link object that connects this [RenderLeaderLayer] with one or more
+  /// [RenderFollowerLayer]s.
+  ///
+  /// This property must not be null. The object must not be associated with
+  /// another [RenderLeaderLayer] that is also being painted.
+  LayerLink get link => _link;
+  LayerLink _link;
+  set link(LayerLink value) {
+    assert(value != null);
+    if (_link == value)
+      return;
+    _link.leaderSize = null;
+    _link = value;
+    if (_previousLayoutSize != null) {
+      _link.leaderSize = _previousLayoutSize;
+    }
+    markNeedsPaint();
+  }
+
+  @override
+  bool get alwaysNeedsCompositing => true;
+
+  // The latest size of this [RenderBox], computed during the previous layout
+  // pass. It should always be equal to [size], but can be accessed even when
+  // [debugDoingThisResize] and [debugDoingThisLayout] are false.
+  Size? _previousLayoutSize;
+
+  @override
+  void performLayout() {
+    super.performLayout();
+    _previousLayoutSize = size;
+    link.leaderSize = size;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (layer == null) {
+      layer = LeaderLayer(link: link, offset: offset);
+    } else {
+      final LeaderLayer leaderLayer = layer! as LeaderLayer;
+      leaderLayer
+        ..link = link
+        ..offset = offset;
+    }
+    context.pushLayer(layer!, super.paint, Offset.zero);
+    assert(layer != null);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<LayerLink>('link', link));
+  }
+}
+
+/// Transform the child so that its origin is [offset] from the origin of the
+/// [RenderLeaderLayer] with the same [LayerLink].
+///
+/// The [RenderLeaderLayer] in question must be earlier in the paint order.
+///
+/// Hit testing on descendants of this render object will only work if the
+/// target position is within the box that this render object's parent considers
+/// to be hittable.
+///
+/// See also:
+///
+///  * [CompositedTransformFollower], the corresponding widget.
+///  * [FollowerLayer], the layer that this render object creates.
+class RenderFollowerLayer extends RenderProxyBox {
+  /// Creates a render object that uses a [FollowerLayer].
+  ///
+  /// The [link] and [offset] arguments must not be null.
+  RenderFollowerLayer({
+    required LayerLink link,
+    bool showWhenUnlinked = true,
+    Offset offset = Offset.zero,
+    Alignment leaderAnchor = Alignment.topLeft,
+    Alignment followerAnchor = Alignment.topLeft,
+    RenderBox? child,
+  }) : assert(link != null),
+       assert(showWhenUnlinked != null),
+       assert(offset != null),
+       _link = link,
+       _showWhenUnlinked = showWhenUnlinked,
+       _offset = offset,
+       _leaderAnchor = leaderAnchor,
+       _followerAnchor = followerAnchor,
+       super(child);
+
+  /// The link object that connects this [RenderFollowerLayer] with a
+  /// [RenderLeaderLayer] earlier in the paint order.
+  LayerLink get link => _link;
+  LayerLink _link;
+  set link(LayerLink value) {
+    assert(value != null);
+    if (_link == value)
+      return;
+    _link = value;
+    markNeedsPaint();
+  }
+
+  /// Whether to show the render object's contents when there is no
+  /// corresponding [RenderLeaderLayer] with the same [link].
+  ///
+  /// When the render object is linked, the child is positioned such that it has
+  /// the same global position as the linked [RenderLeaderLayer].
+  ///
+  /// When the render object is not linked, then: if [showWhenUnlinked] is true,
+  /// the child is visible and not repositioned; if it is false, then child is
+  /// hidden, and its hit testing is also disabled.
+  bool get showWhenUnlinked => _showWhenUnlinked;
+  bool _showWhenUnlinked;
+  set showWhenUnlinked(bool value) {
+    assert(value != null);
+    if (_showWhenUnlinked == value)
+      return;
+    _showWhenUnlinked = value;
+    markNeedsPaint();
+  }
+
+  /// The offset to apply to the origin of the linked [RenderLeaderLayer] to
+  /// obtain this render object's origin.
+  Offset get offset => _offset;
+  Offset _offset;
+  set offset(Offset value) {
+    assert(value != null);
+    if (_offset == value)
+      return;
+    _offset = value;
+    markNeedsPaint();
+  }
+
+  /// The anchor point on the linked [RenderLeaderLayer] that [followerAnchor]
+  /// will line up with.
+  ///
+  /// {@template flutter.rendering.RenderFollowerLayer.leaderAnchor}
+  /// For example, when [leaderAnchor] and [followerAnchor] are both
+  /// [Alignment.topLeft], this [RenderFollowerLayer] will be top left aligned
+  /// with the linked [RenderLeaderLayer]. When [leaderAnchor] is
+  /// [Alignment.bottomLeft] and [followerAnchor] is [Alignment.topLeft], this
+  /// [RenderFollowerLayer] will be left aligned with the linked
+  /// [RenderLeaderLayer], and its top edge will line up with the
+  /// [RenderLeaderLayer]'s bottom edge.
+  /// {@endtemplate}
+  ///
+  /// Defaults to [Alignment.topLeft].
+  Alignment get leaderAnchor => _leaderAnchor;
+  Alignment _leaderAnchor;
+  set leaderAnchor(Alignment value) {
+    assert(value != null);
+    if (_leaderAnchor == value)
+      return;
+    _leaderAnchor = value;
+    markNeedsPaint();
+  }
+
+  /// The anchor point on this [RenderFollowerLayer] that will line up with
+  /// [followerAnchor] on the linked [RenderLeaderLayer].
+  ///
+  /// {@macro flutter.rendering.RenderFollowerLayer.leaderAnchor}
+  ///
+  /// Defaults to [Alignment.topLeft].
+  Alignment get followerAnchor => _followerAnchor;
+  Alignment _followerAnchor;
+  set followerAnchor(Alignment value) {
+    assert(value != null);
+    if (_followerAnchor == value)
+      return;
+    _followerAnchor = value;
+    markNeedsPaint();
+  }
+
+  @override
+  void detach() {
+    layer = null;
+    super.detach();
+  }
+
+  @override
+  bool get alwaysNeedsCompositing => true;
+
+  /// The layer we created when we were last painted.
+  @override
+  FollowerLayer? get layer => super.layer as FollowerLayer?;
+
+  /// Return the transform that was used in the last composition phase, if any.
+  ///
+  /// If the [FollowerLayer] has not yet been created, was never composited, or
+  /// was unable to determine the transform (see
+  /// [FollowerLayer.getLastTransform]), this returns the identity matrix (see
+  /// [new Matrix4.identity].
+  Matrix4 getCurrentTransform() {
+    return layer?.getLastTransform() ?? Matrix4.identity();
+  }
+
+  @override
+  bool hitTest(BoxHitTestResult result, { required Offset position }) {
+    // Disables the hit testing if this render object is hidden.
+    if (link.leader == null && !showWhenUnlinked)
+      return false;
+    // RenderFollowerLayer objects don't check if they are
+    // themselves hit, because it's confusing to think about
+    // how the untransformed size and the child's transformed
+    // position interact.
+    return hitTestChildren(result, position: position);
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    return result.addWithPaintTransform(
+      transform: getCurrentTransform(),
+      position: position,
+      hitTest: (BoxHitTestResult result, Offset? position) {
+        return super.hitTestChildren(result, position: position!);
+      },
+    );
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final Size? leaderSize = link.leaderSize;
+    assert(
+      link.leaderSize != null || (link.leader == null || leaderAnchor == Alignment.topLeft),
+      '$link: layer is linked to ${link.leader} but a valid leaderSize is not set. '
+      'leaderSize is required when leaderAnchor is not Alignment.topLeft'
+      '(current value is $leaderAnchor).',
+    );
+    final Offset effectiveLinkedOffset = leaderSize == null
+      ? this.offset
+      : leaderAnchor.alongSize(leaderSize) - followerAnchor.alongSize(size) + this.offset;
+    assert(showWhenUnlinked != null);
+    if (layer == null) {
+      layer = FollowerLayer(
+        link: link,
+        showWhenUnlinked: showWhenUnlinked,
+        linkedOffset: effectiveLinkedOffset,
+        unlinkedOffset: offset,
+      );
+    } else {
+      layer
+        ?..link = link
+        ..showWhenUnlinked = showWhenUnlinked
+        ..linkedOffset = effectiveLinkedOffset
+        ..unlinkedOffset = offset;
+    }
+    context.pushLayer(
+      layer!,
+      super.paint,
+      Offset.zero,
+      childPaintBounds: const Rect.fromLTRB(
+        // We don't know where we'll end up, so we have no idea what our cull rect should be.
+        double.negativeInfinity,
+        double.negativeInfinity,
+        double.infinity,
+        double.infinity,
+      ),
+    );
+  }
+
+  @override
+  void applyPaintTransform(RenderBox child, Matrix4 transform) {
+    transform.multiply(getCurrentTransform());
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<LayerLink>('link', link));
+    properties.add(DiagnosticsProperty<bool>('showWhenUnlinked', showWhenUnlinked));
+    properties.add(DiagnosticsProperty<Offset>('offset', offset));
+    properties.add(TransformProperty('current transform matrix', getCurrentTransform()));
+  }
+}
+
+/// Render object which inserts an [AnnotatedRegionLayer] into the layer tree.
+///
+/// See also:
+///
+///  * [Layer.find], for an example of how this value is retrieved.
+///  * [AnnotatedRegionLayer], the layer this render object creates.
+class RenderAnnotatedRegion<T extends Object> extends RenderProxyBox {
+
+  /// Creates a new [RenderAnnotatedRegion] to insert [value] into the
+  /// layer tree.
+  ///
+  /// If [sized] is true, the layer is provided with the size of this render
+  /// object to clip the results of [Layer.find].
+  ///
+  /// Neither [value] nor [sized] can be null.
+  RenderAnnotatedRegion({
+    required T value,
+    required bool sized,
+    RenderBox? child,
+  }) : assert(value != null),
+       assert(sized != null),
+       _value = value,
+       _sized = sized,
+       super(child);
+
+  /// A value which can be retrieved using [Layer.find].
+  T get value => _value;
+  T _value;
+  set value (T newValue) {
+    if (_value == newValue)
+      return;
+    _value = newValue;
+    markNeedsPaint();
+  }
+
+  /// Whether the render object will pass its [size] to the [AnnotatedRegionLayer].
+  bool get sized => _sized;
+  bool _sized;
+  set sized(bool value) {
+    if (_sized == value)
+      return;
+    _sized = value;
+    markNeedsPaint();
+  }
+
+  @override
+  final bool alwaysNeedsCompositing = true;
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    // Annotated region layers are not retained because they do not create engine layers.
+    final AnnotatedRegionLayer<T> layer = AnnotatedRegionLayer<T>(
+      value,
+      size: sized ? size : null,
+      offset: sized ? offset : null,
+    );
+    context.pushLayer(layer, super.paint, offset);
+  }
+}
diff --git a/lib/src/rendering/proxy_sliver.dart b/lib/src/rendering/proxy_sliver.dart
new file mode 100644
index 0000000..f6fb2f5
--- /dev/null
+++ b/lib/src/rendering/proxy_sliver.dart
@@ -0,0 +1,399 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui show Color;
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'layer.dart';
+import 'object.dart';
+import 'proxy_box.dart';
+import 'sliver.dart';
+
+/// A base class for sliver render objects that resemble their children.
+///
+/// A proxy sliver has a single child and simply mimics all the properties of
+/// that child by calling through to the child for each function in the render
+/// sliver protocol. For example, a proxy sliver determines its geometry by
+/// asking its sliver child to layout with the same constraints and then
+/// matching the geometry.
+///
+/// A proxy sliver isn't useful on its own because you might as well just
+/// replace the proxy sliver with its child. However, RenderProxySliver is a
+/// useful base class for render objects that wish to mimic most, but not all,
+/// of the properties of their sliver child.
+///
+/// See also:
+///
+///  * [RenderProxyBox], a base class for render boxes that resemble their
+///    children.
+abstract class RenderProxySliver extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
+  /// Creates a proxy render sliver.
+  ///
+  /// Proxy render slivers aren't created directly because they simply proxy
+  /// the render sliver protocol to their sliver [child]. Instead, use one of
+  /// the subclasses.
+  RenderProxySliver([RenderSliver? child]) {
+    this.child = child;
+  }
+
+  @override
+  void setupParentData(RenderObject child) {
+    if (child.parentData is! SliverPhysicalParentData)
+      child.parentData = SliverPhysicalParentData();
+  }
+
+  @override
+  void performLayout() {
+    assert(child != null);
+    child!.layout(constraints, parentUsesSize: true);
+    geometry = child!.geometry;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null)
+      context.paintChild(child!, offset);
+  }
+
+  @override
+  bool hitTestChildren(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) {
+    return child != null
+      && child!.geometry!.hitTestExtent > 0
+      && child!.hitTest(
+        result,
+        mainAxisPosition: mainAxisPosition,
+        crossAxisPosition: crossAxisPosition,
+      );
+  }
+
+  @override
+  double childMainAxisPosition(RenderSliver child) {
+    assert(child != null);
+    assert(child == this.child);
+    return 0.0;
+  }
+
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) {
+    assert(child != null);
+    final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
+    childParentData.applyPaintTransform(transform);
+  }
+}
+
+/// Makes its sliver child partially transparent.
+///
+/// This class paints its sliver child into an intermediate buffer and then
+/// blends the sliver child back into the scene, partially transparent.
+///
+/// For values of opacity other than 0.0 and 1.0, this class is relatively
+/// expensive, because it requires painting the sliver child into an intermediate
+/// buffer. For the value 0.0, the sliver child is simply not painted at all.
+/// For the value 1.0, the sliver child is painted immediately without an
+/// intermediate buffer.
+class RenderSliverOpacity extends RenderProxySliver {
+  /// Creates a partially transparent render object.
+  ///
+  /// The [opacity] argument must be between 0.0 and 1.0, inclusive.
+  RenderSliverOpacity({
+    double opacity = 1.0,
+    bool alwaysIncludeSemantics = false,
+    RenderSliver? sliver,
+  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
+       assert(alwaysIncludeSemantics != null),
+       _opacity = opacity,
+       _alwaysIncludeSemantics = alwaysIncludeSemantics,
+       _alpha = ui.Color.getAlphaFromOpacity(opacity) {
+    child = sliver;
+  }
+
+  @override
+  bool get alwaysNeedsCompositing => child != null && (_alpha != 0 && _alpha != 255);
+
+  int _alpha;
+
+  /// The fraction to scale the child's alpha value.
+  ///
+  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
+  /// (i.e. invisible).
+  ///
+  /// The opacity must not be null.
+  ///
+  /// Values 1.0 and 0.0 are painted with a fast path. Other values
+  /// require painting the child into an intermediate buffer, which is
+  /// expensive.
+  double get opacity => _opacity;
+  double _opacity;
+  set opacity(double value) {
+    assert(value != null);
+    assert(value >= 0.0 && value <= 1.0);
+    if (_opacity == value)
+      return;
+    final bool didNeedCompositing = alwaysNeedsCompositing;
+    final bool wasVisible = _alpha != 0;
+    _opacity = value;
+    _alpha = ui.Color.getAlphaFromOpacity(_opacity);
+    if (didNeedCompositing != alwaysNeedsCompositing)
+      markNeedsCompositingBitsUpdate();
+    markNeedsPaint();
+    if (wasVisible != (_alpha != 0) && !alwaysIncludeSemantics)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// Whether child semantics are included regardless of the opacity.
+  ///
+  /// If false, semantics are excluded when [opacity] is 0.0.
+  ///
+  /// Defaults to false.
+  bool get alwaysIncludeSemantics => _alwaysIncludeSemantics;
+  bool _alwaysIncludeSemantics;
+  set alwaysIncludeSemantics(bool value) {
+    if (value == _alwaysIncludeSemantics)
+      return;
+    _alwaysIncludeSemantics = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null && child!.geometry!.visible) {
+      if (_alpha == 0) {
+        // No need to keep the layer. We'll create a new one if necessary.
+        layer = null;
+        return;
+      }
+      if (_alpha == 255) {
+        // No need to keep the layer. We'll create a new one if necessary.
+        layer = null;
+        context.paintChild(child!, offset);
+        return;
+      }
+      assert(needsCompositing);
+      layer = context.pushOpacity(
+        offset,
+        _alpha,
+        super.paint,
+        oldLayer: layer as OpacityLayer?,
+      );
+    }
+  }
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    if (child != null && (_alpha != 0 || alwaysIncludeSemantics))
+      visitor(child!);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('opacity', opacity));
+    properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
+  }
+}
+
+/// A render object that is invisible during hit testing.
+///
+/// When [ignoring] is true, this render object (and its subtree) is invisible
+/// to hit testing. It still consumes space during layout and paints its sliver
+/// child as usual. It just cannot be the target of located events, because its
+/// render object returns false from [hitTest].
+///
+/// When [ignoringSemantics] is true, the subtree will be invisible to the
+/// semantics layer (and thus e.g. accessibility tools). If [ignoringSemantics]
+/// is null, it uses the value of [ignoring].
+class RenderSliverIgnorePointer extends RenderProxySliver {
+  /// Creates a render object that is invisible to hit testing.
+  ///
+  /// The [ignoring] argument must not be null. If [ignoringSemantics] is null,
+  /// this render object will be ignored for semantics if [ignoring] is true.
+  RenderSliverIgnorePointer({
+    RenderSliver? sliver,
+    bool ignoring = true,
+    bool? ignoringSemantics,
+  }) : assert(ignoring != null),
+       _ignoring = ignoring,
+       _ignoringSemantics = ignoringSemantics {
+    child = sliver;
+  }
+
+  /// Whether this render object is ignored during hit testing.
+  ///
+  /// Regardless of whether this render object is ignored during hit testing, it
+  /// will still consume space during layout and be visible during painting.
+  bool get ignoring => _ignoring;
+  bool _ignoring;
+  set ignoring(bool value) {
+    assert(value != null);
+    if (value == _ignoring)
+      return;
+    _ignoring = value;
+    if (_ignoringSemantics == null || !_ignoringSemantics!)
+      markNeedsSemanticsUpdate();
+  }
+
+  /// Whether the semantics of this render object is ignored when compiling the
+  /// semantics tree.
+  ///
+  /// If null, defaults to value of [ignoring].
+  ///
+  /// See [SemanticsNode] for additional information about the semantics tree.
+  bool? get ignoringSemantics => _ignoringSemantics;
+  bool? _ignoringSemantics;
+  set ignoringSemantics(bool? value) {
+    if (value == _ignoringSemantics)
+      return;
+    final bool oldEffectiveValue = _effectiveIgnoringSemantics;
+    _ignoringSemantics = value;
+    if (oldEffectiveValue != _effectiveIgnoringSemantics)
+      markNeedsSemanticsUpdate();
+  }
+
+  bool get _effectiveIgnoringSemantics => ignoringSemantics ?? ignoring;
+
+  @override
+  bool hitTest(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) {
+    return !ignoring
+      && super.hitTest(
+        result,
+        mainAxisPosition: mainAxisPosition,
+        crossAxisPosition: crossAxisPosition,
+      );
+  }
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    if (child != null && !_effectiveIgnoringSemantics)
+      visitor(child!);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('ignoring', ignoring));
+    properties.add(DiagnosticsProperty<bool>('ignoringSemantics', _effectiveIgnoringSemantics, description: ignoringSemantics == null ? 'implicitly $_effectiveIgnoringSemantics' : null));
+  }
+}
+
+/// Lays the sliver child out as if it was in the tree, but without painting
+/// anything, without making the sliver child available for hit testing, and
+/// without taking any room in the parent.
+class RenderSliverOffstage extends RenderProxySliver {
+  /// Creates an offstage render object.
+  RenderSliverOffstage({
+    bool offstage = true,
+    RenderSliver? sliver,
+  }) : assert(offstage != null),
+       _offstage = offstage {
+    child = sliver;
+  }
+
+  /// Whether the sliver child is hidden from the rest of the tree.
+  ///
+  /// If true, the sliver child is laid out as if it was in the tree, but
+  /// without painting anything, without making the sliver child available for
+  /// hit testing, and without taking any room in the parent.
+  ///
+  /// If false, the sliver child is included in the tree as normal.
+  bool get offstage => _offstage;
+  bool _offstage;
+
+  set offstage(bool value) {
+    assert(value != null);
+    if (value == _offstage)
+      return;
+    _offstage = value;
+    markNeedsLayoutForSizedByParentChange();
+  }
+
+  @override
+  void performLayout() {
+    assert(child != null);
+    child!.layout(constraints, parentUsesSize: true);
+    if (!offstage)
+      geometry = child!.geometry;
+    else
+      geometry = const SliverGeometry(
+        scrollExtent: 0.0,
+        visible: false,
+        maxPaintExtent: 0.0,
+      );
+  }
+
+  @override
+  bool hitTest(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) {
+    return !offstage && super.hitTest(
+      result,
+      mainAxisPosition: mainAxisPosition,
+      crossAxisPosition: crossAxisPosition,
+    );
+  }
+
+  @override
+  bool hitTestChildren(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) {
+    return !offstage
+      && child != null
+      && child!.geometry!.hitTestExtent > 0
+      && child!.hitTest(
+        result,
+        mainAxisPosition: mainAxisPosition,
+        crossAxisPosition: crossAxisPosition,
+      );
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (offstage)
+      return;
+    context.paintChild(child!, offset);
+  }
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    if (offstage)
+      return;
+    super.visitChildrenForSemantics(visitor);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('offstage', offstage));
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    if (child == null)
+      return <DiagnosticsNode>[];
+    return <DiagnosticsNode>[
+      child!.toDiagnosticsNode(
+        name: 'child',
+        style: offstage ? DiagnosticsTreeStyle.offstage : DiagnosticsTreeStyle.sparse,
+      ),
+    ];
+  }
+}
+
+/// Makes its sliver child partially transparent, driven from an [Animation].
+///
+/// This is a variant of [RenderSliverOpacity] that uses an [Animation<double>]
+/// rather than a [double] to control the opacity.
+class RenderSliverAnimatedOpacity extends RenderProxySliver with RenderAnimatedOpacityMixin<RenderSliver>{
+  /// Creates a partially transparent render object.
+  ///
+  /// The [opacity] argument must not be null.
+  RenderSliverAnimatedOpacity({
+    required Animation<double> opacity,
+    bool alwaysIncludeSemantics = false,
+    RenderSliver? sliver,
+  }) : assert(opacity != null),
+       assert(alwaysIncludeSemantics != null) {
+    this.opacity = opacity;
+    this.alwaysIncludeSemantics = alwaysIncludeSemantics;
+    child = sliver;
+  }
+}
diff --git a/lib/src/rendering/rotated_box.dart b/lib/src/rendering/rotated_box.dart
new file mode 100644
index 0000000..c39cc79
--- /dev/null
+++ b/lib/src/rendering/rotated_box.dart
@@ -0,0 +1,137 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/gestures.dart';
+import 'package:flute/painting.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'box.dart';
+import 'layer.dart';
+import 'object.dart';
+
+const double _kQuarterTurnsInRadians = math.pi / 2.0;
+
+/// Rotates its child by a integral number of quarter turns.
+///
+/// Unlike [RenderTransform], which applies a transform just prior to painting,
+/// this object applies its rotation prior to layout, which means the entire
+/// rotated box consumes only as much space as required by the rotated child.
+class RenderRotatedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
+  /// Creates a rotated render box.
+  ///
+  /// The [quarterTurns] argument must not be null.
+  RenderRotatedBox({
+    required int quarterTurns,
+    RenderBox? child,
+  }) : assert(quarterTurns != null),
+       _quarterTurns = quarterTurns {
+    this.child = child;
+  }
+
+  /// The number of clockwise quarter turns the child should be rotated.
+  int get quarterTurns => _quarterTurns;
+  int _quarterTurns;
+  set quarterTurns(int value) {
+    assert(value != null);
+    if (_quarterTurns == value)
+      return;
+    _quarterTurns = value;
+    markNeedsLayout();
+  }
+
+  bool get _isVertical => quarterTurns.isOdd;
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    if (child == null)
+      return 0.0;
+    return _isVertical ? child!.getMinIntrinsicHeight(height) : child!.getMinIntrinsicWidth(height);
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    if (child == null)
+      return 0.0;
+    return _isVertical ? child!.getMaxIntrinsicHeight(height) : child!.getMaxIntrinsicWidth(height);
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    if (child == null)
+      return 0.0;
+    return _isVertical ? child!.getMinIntrinsicWidth(width) : child!.getMinIntrinsicHeight(width);
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    if (child == null)
+      return 0.0;
+    return _isVertical ? child!.getMaxIntrinsicWidth(width) : child!.getMaxIntrinsicHeight(width);
+  }
+
+  Matrix4? _paintTransform;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (child == null) {
+      return constraints.smallest;
+    }
+    final Size childSize = child!.getDryLayout(_isVertical ? constraints.flipped : constraints);
+    return _isVertical ? Size(childSize.height, childSize.width) : childSize;
+  }
+
+  @override
+  void performLayout() {
+    _paintTransform = null;
+    if (child != null) {
+      child!.layout(_isVertical ? constraints.flipped : constraints, parentUsesSize: true);
+      size = _isVertical ? Size(child!.size.height, child!.size.width) : child!.size;
+      _paintTransform = Matrix4.identity()
+        ..translate(size.width / 2.0, size.height / 2.0)
+        ..rotateZ(_kQuarterTurnsInRadians * (quarterTurns % 4))
+        ..translate(-child!.size.width / 2.0, -child!.size.height / 2.0);
+    } else {
+      size = constraints.smallest;
+    }
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    assert(_paintTransform != null || debugNeedsLayout || child == null);
+    if (child == null || _paintTransform == null)
+      return false;
+    return result.addWithPaintTransform(
+      transform: _paintTransform,
+      position: position,
+      hitTest: (BoxHitTestResult result, Offset? position) {
+        return child!.hitTest(result, position: position!);
+      },
+    );
+  }
+
+  void _paintChild(PaintingContext context, Offset offset) {
+    context.paintChild(child!, offset);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null) {
+      _transformLayer = context.pushTransform(needsCompositing, offset, _paintTransform!, _paintChild,
+          oldLayer: _transformLayer);
+    } else {
+      _transformLayer = null;
+    }
+  }
+
+  TransformLayer? _transformLayer;
+
+  @override
+  void applyPaintTransform(RenderBox child, Matrix4 transform) {
+    if (_paintTransform != null)
+      transform.multiply(_paintTransform!);
+    super.applyPaintTransform(child, transform);
+  }
+}
diff --git a/lib/src/rendering/shifted_box.dart b/lib/src/rendering/shifted_box.dart
new file mode 100644
index 0000000..64f2835
--- /dev/null
+++ b/lib/src/rendering/shifted_box.dart
@@ -0,0 +1,1318 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+
+import 'box.dart';
+import 'debug.dart';
+import 'debug_overflow_indicator.dart';
+import 'layer.dart';
+import 'layout_helper.dart';
+import 'object.dart';
+import 'stack.dart' show RelativeRect;
+
+/// Abstract class for one-child-layout render boxes that provide control over
+/// the child's position.
+abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
+  /// Initializes the [child] property for subclasses.
+  RenderShiftedBox(RenderBox? child) {
+    this.child = child;
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    if (child != null)
+      return child!.getMinIntrinsicWidth(height);
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    if (child != null)
+      return child!.getMaxIntrinsicWidth(height);
+    return 0.0;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    if (child != null)
+      return child!.getMinIntrinsicHeight(width);
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    if (child != null)
+      return child!.getMaxIntrinsicHeight(width);
+    return 0.0;
+  }
+
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    double? result;
+    if (child != null) {
+      assert(!debugNeedsLayout);
+      result = child!.getDistanceToActualBaseline(baseline);
+      final BoxParentData childParentData = child!.parentData! as BoxParentData;
+      if (result != null)
+        result += childParentData.offset.dy;
+    } else {
+      result = super.computeDistanceToActualBaseline(baseline);
+    }
+    return result;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null) {
+      final BoxParentData childParentData = child!.parentData! as BoxParentData;
+      context.paintChild(child!, childParentData.offset + offset);
+    }
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    if (child != null) {
+      final BoxParentData childParentData = child!.parentData! as BoxParentData;
+      return result.addWithPaintOffset(
+        offset: childParentData.offset,
+        position: position,
+        hitTest: (BoxHitTestResult result, Offset? transformed) {
+          assert(transformed == position - childParentData.offset);
+          return child!.hitTest(result, position: transformed!);
+        },
+      );
+    }
+    return false;
+  }
+}
+
+/// Insets its child by the given padding.
+///
+/// When passing layout constraints to its child, padding shrinks the
+/// constraints by the given padding, causing the child to layout at a smaller
+/// size. Padding then sizes itself to its child's size, inflated by the
+/// padding, effectively creating empty space around the child.
+class RenderPadding extends RenderShiftedBox {
+  /// Creates a render object that insets its child.
+  ///
+  /// The [padding] argument must not be null and must have non-negative insets.
+  RenderPadding({
+    required EdgeInsetsGeometry padding,
+    TextDirection? textDirection,
+    RenderBox? child,
+  }) : assert(padding != null),
+       assert(padding.isNonNegative),
+       _textDirection = textDirection,
+       _padding = padding,
+       super(child);
+
+  EdgeInsets? _resolvedPadding;
+
+  void _resolve() {
+    if (_resolvedPadding != null)
+      return;
+    _resolvedPadding = padding.resolve(textDirection);
+    assert(_resolvedPadding!.isNonNegative);
+  }
+
+  void _markNeedResolution() {
+    _resolvedPadding = null;
+    markNeedsLayout();
+  }
+
+  /// The amount to pad the child in each dimension.
+  ///
+  /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
+  /// must not be null.
+  EdgeInsetsGeometry get padding => _padding;
+  EdgeInsetsGeometry _padding;
+  set padding(EdgeInsetsGeometry value) {
+    assert(value != null);
+    assert(value.isNonNegative);
+    if (_padding == value)
+      return;
+    _padding = value;
+    _markNeedResolution();
+  }
+
+  /// The text direction with which to resolve [padding].
+  ///
+  /// This may be changed to null, but only after the [padding] has been changed
+  /// to a value that does not depend on the direction.
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection;
+  set textDirection(TextDirection? value) {
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    _markNeedResolution();
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    _resolve();
+    final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
+    final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
+    if (child != null) // next line relies on double.infinity absorption
+      return child!.getMinIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
+    return totalHorizontalPadding;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    _resolve();
+    final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
+    final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
+    if (child != null) // next line relies on double.infinity absorption
+      return child!.getMaxIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
+    return totalHorizontalPadding;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    _resolve();
+    final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
+    final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
+    if (child != null) // next line relies on double.infinity absorption
+      return child!.getMinIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
+    return totalVerticalPadding;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    _resolve();
+    final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
+    final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
+    if (child != null) // next line relies on double.infinity absorption
+      return child!.getMaxIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
+    return totalVerticalPadding;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    _resolve();
+    assert(_resolvedPadding != null);
+    if (child == null) {
+      return constraints.constrain(Size(
+        _resolvedPadding!.left + _resolvedPadding!.right,
+        _resolvedPadding!.top + _resolvedPadding!.bottom,
+      ));
+    }
+    final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding!);
+    final Size childSize = child!.getDryLayout(innerConstraints);
+    return constraints.constrain(Size(
+      _resolvedPadding!.left + childSize.width + _resolvedPadding!.right,
+      _resolvedPadding!.top + childSize.height + _resolvedPadding!.bottom,
+    ));
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    _resolve();
+    assert(_resolvedPadding != null);
+    if (child == null) {
+      size = constraints.constrain(Size(
+        _resolvedPadding!.left + _resolvedPadding!.right,
+        _resolvedPadding!.top + _resolvedPadding!.bottom,
+      ));
+      return;
+    }
+    final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding!);
+    child!.layout(innerConstraints, parentUsesSize: true);
+    final BoxParentData childParentData = child!.parentData! as BoxParentData;
+    childParentData.offset = Offset(_resolvedPadding!.left, _resolvedPadding!.top);
+    size = constraints.constrain(Size(
+      _resolvedPadding!.left + child!.size.width + _resolvedPadding!.right,
+      _resolvedPadding!.top + child!.size.height + _resolvedPadding!.bottom,
+    ));
+  }
+
+  @override
+  void debugPaintSize(PaintingContext context, Offset offset) {
+    super.debugPaintSize(context, offset);
+    assert(() {
+      final Rect outerRect = offset & size;
+      debugPaintPadding(context.canvas, outerRect, child != null ? _resolvedPadding!.deflateRect(outerRect) : null);
+      return true;
+    }());
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+  }
+}
+
+/// Abstract class for one-child-layout render boxes that use a
+/// [AlignmentGeometry] to align their children.
+abstract class RenderAligningShiftedBox extends RenderShiftedBox {
+  /// Initializes member variables for subclasses.
+  ///
+  /// The [alignment] argument must not be null.
+  ///
+  /// The [textDirection] must be non-null if the [alignment] is
+  /// direction-sensitive.
+  RenderAligningShiftedBox({
+    AlignmentGeometry alignment = Alignment.center,
+    required TextDirection? textDirection,
+    RenderBox? child,
+  }) : assert(alignment != null),
+       _alignment = alignment,
+       _textDirection = textDirection,
+       super(child);
+
+  /// A constructor to be used only when the extending class also has a mixin.
+  // TODO(gspencer): Remove this constructor once https://github.com/dart-lang/sdk/issues/31543 is fixed.
+  @protected
+  RenderAligningShiftedBox.mixin(AlignmentGeometry alignment, TextDirection? textDirection, RenderBox? child)
+    : this(alignment: alignment, textDirection: textDirection, child: child);
+
+  Alignment? _resolvedAlignment;
+
+  void _resolve() {
+    if (_resolvedAlignment != null)
+      return;
+    _resolvedAlignment = alignment.resolve(textDirection);
+  }
+
+  void _markNeedResolution() {
+    _resolvedAlignment = null;
+    markNeedsLayout();
+  }
+
+  /// How to align the child.
+  ///
+  /// The x and y values of the alignment control the horizontal and vertical
+  /// alignment, respectively. An x value of -1.0 means that the left edge of
+  /// the child is aligned with the left edge of the parent whereas an x value
+  /// of 1.0 means that the right edge of the child is aligned with the right
+  /// edge of the parent. Other values interpolate (and extrapolate) linearly.
+  /// For example, a value of 0.0 means that the center of the child is aligned
+  /// with the center of the parent.
+  ///
+  /// If this is set to an [AlignmentDirectional] object, then
+  /// [textDirection] must not be null.
+  AlignmentGeometry get alignment => _alignment;
+  AlignmentGeometry _alignment;
+  /// Sets the alignment to a new value, and triggers a layout update.
+  ///
+  /// The new alignment must not be null.
+  set alignment(AlignmentGeometry value) {
+    assert(value != null);
+    if (_alignment == value)
+      return;
+    _alignment = value;
+    _markNeedResolution();
+  }
+
+  /// The text direction with which to resolve [alignment].
+  ///
+  /// This may be changed to null, but only after [alignment] has been changed
+  /// to a value that does not depend on the direction.
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection;
+  set textDirection(TextDirection? value) {
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    _markNeedResolution();
+  }
+
+  /// Apply the current [alignment] to the [child].
+  ///
+  /// Subclasses should call this method if they have a child, to have
+  /// this class perform the actual alignment. If there is no child,
+  /// do not call this method.
+  ///
+  /// This method must be called after the child has been laid out and
+  /// this object's own size has been set.
+  @protected
+  void alignChild() {
+    _resolve();
+    assert(child != null);
+    assert(!child!.debugNeedsLayout);
+    assert(child!.hasSize);
+    assert(hasSize);
+    assert(_resolvedAlignment != null);
+    final BoxParentData childParentData = child!.parentData! as BoxParentData;
+    childParentData.offset = _resolvedAlignment!.alongOffset(size - child!.size as Offset);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+  }
+}
+
+/// Positions its child using an [AlignmentGeometry].
+///
+/// For example, to align a box at the bottom right, you would pass this box a
+/// tight constraint that is bigger than the child's natural size,
+/// with an alignment of [Alignment.bottomRight].
+///
+/// By default, sizes to be as big as possible in both axes. If either axis is
+/// unconstrained, then in that direction it will be sized to fit the child's
+/// dimensions. Using widthFactor and heightFactor you can force this latter
+/// behavior in all cases.
+class RenderPositionedBox extends RenderAligningShiftedBox {
+  /// Creates a render object that positions its child.
+  RenderPositionedBox({
+    RenderBox? child,
+    double? widthFactor,
+    double? heightFactor,
+    AlignmentGeometry alignment = Alignment.center,
+    TextDirection? textDirection,
+  }) : assert(widthFactor == null || widthFactor >= 0.0),
+       assert(heightFactor == null || heightFactor >= 0.0),
+       _widthFactor = widthFactor,
+       _heightFactor = heightFactor,
+       super(child: child, alignment: alignment, textDirection: textDirection);
+
+  /// If non-null, sets its width to the child's width multiplied by this factor.
+  ///
+  /// Can be both greater and less than 1.0 but must be positive.
+  double? get widthFactor => _widthFactor;
+  double? _widthFactor;
+  set widthFactor(double? value) {
+    assert(value == null || value >= 0.0);
+    if (_widthFactor == value)
+      return;
+    _widthFactor = value;
+    markNeedsLayout();
+  }
+
+  /// If non-null, sets its height to the child's height multiplied by this factor.
+  ///
+  /// Can be both greater and less than 1.0 but must be positive.
+  double? get heightFactor => _heightFactor;
+  double? _heightFactor;
+  set heightFactor(double? value) {
+    assert(value == null || value >= 0.0);
+    if (_heightFactor == value)
+      return;
+    _heightFactor = value;
+    markNeedsLayout();
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
+    final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;
+    if (child != null) {
+      final Size childSize = child!.getDryLayout(constraints.loosen());
+      return constraints.constrain(Size(
+        shrinkWrapWidth ? childSize.width * (_widthFactor ?? 1.0) : double.infinity,
+        shrinkWrapHeight ? childSize.height * (_heightFactor ?? 1.0) : double.infinity),
+      );
+    }
+    return constraints.constrain(Size(
+      shrinkWrapWidth ? 0.0 : double.infinity,
+      shrinkWrapHeight ? 0.0 : double.infinity,
+    ));
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
+    final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;
+
+    if (child != null) {
+      child!.layout(constraints.loosen(), parentUsesSize: true);
+      size = constraints.constrain(Size(shrinkWrapWidth ? child!.size.width * (_widthFactor ?? 1.0) : double.infinity,
+                                        shrinkWrapHeight ? child!.size.height * (_heightFactor ?? 1.0) : double.infinity));
+      alignChild();
+    } else {
+      size = constraints.constrain(Size(shrinkWrapWidth ? 0.0 : double.infinity,
+                                        shrinkWrapHeight ? 0.0 : double.infinity));
+    }
+  }
+
+  @override
+  void debugPaintSize(PaintingContext context, Offset offset) {
+    super.debugPaintSize(context, offset);
+    assert(() {
+      final Paint paint;
+      if (child != null && !child!.size.isEmpty) {
+        final Path path;
+        paint = Paint()
+          ..style = PaintingStyle.stroke
+          ..strokeWidth = 1.0
+          ..color = const Color(0xFFFFFF00);
+        path = Path();
+        final BoxParentData childParentData = child!.parentData! as BoxParentData;
+        if (childParentData.offset.dy > 0.0) {
+          // vertical alignment arrows
+          final double headSize = math.min(childParentData.offset.dy * 0.2, 10.0);
+          path
+            ..moveTo(offset.dx + size.width / 2.0, offset.dy)
+            ..relativeLineTo(0.0, childParentData.offset.dy - headSize)
+            ..relativeLineTo(headSize, 0.0)
+            ..relativeLineTo(-headSize, headSize)
+            ..relativeLineTo(-headSize, -headSize)
+            ..relativeLineTo(headSize, 0.0)
+            ..moveTo(offset.dx + size.width / 2.0, offset.dy + size.height)
+            ..relativeLineTo(0.0, -childParentData.offset.dy + headSize)
+            ..relativeLineTo(headSize, 0.0)
+            ..relativeLineTo(-headSize, -headSize)
+            ..relativeLineTo(-headSize, headSize)
+            ..relativeLineTo(headSize, 0.0);
+          context.canvas.drawPath(path, paint);
+        }
+        if (childParentData.offset.dx > 0.0) {
+          // horizontal alignment arrows
+          final double headSize = math.min(childParentData.offset.dx * 0.2, 10.0);
+          path
+            ..moveTo(offset.dx, offset.dy + size.height / 2.0)
+            ..relativeLineTo(childParentData.offset.dx - headSize, 0.0)
+            ..relativeLineTo(0.0, headSize)
+            ..relativeLineTo(headSize, -headSize)
+            ..relativeLineTo(-headSize, -headSize)
+            ..relativeLineTo(0.0, headSize)
+            ..moveTo(offset.dx + size.width, offset.dy + size.height / 2.0)
+            ..relativeLineTo(-childParentData.offset.dx + headSize, 0.0)
+            ..relativeLineTo(0.0, headSize)
+            ..relativeLineTo(-headSize, -headSize)
+            ..relativeLineTo(headSize, -headSize)
+            ..relativeLineTo(0.0, headSize);
+          context.canvas.drawPath(path, paint);
+        }
+      } else {
+        paint = Paint()
+          ..color = const Color(0x90909090);
+        context.canvas.drawRect(offset & size, paint);
+      }
+      return true;
+    }());
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'expand'));
+    properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'expand'));
+  }
+}
+
+/// A render object that imposes different constraints on its child than it gets
+/// from its parent, possibly allowing the child to overflow the parent.
+///
+/// A render overflow box proxies most functions in the render box protocol to
+/// its child, except that when laying out its child, it passes constraints
+/// based on the minWidth, maxWidth, minHeight, and maxHeight fields instead of
+/// just passing the parent's constraints in. Specifically, it overrides any of
+/// the equivalent fields on the constraints given by the parent with the
+/// constraints given by these fields for each such field that is not null. It
+/// then sizes itself based on the parent's constraints' maxWidth and maxHeight,
+/// ignoring the child's dimensions.
+///
+/// For example, if you wanted a box to always render 50 pixels high, regardless
+/// of where it was rendered, you would wrap it in a
+/// RenderConstrainedOverflowBox with minHeight and maxHeight set to 50.0.
+/// Generally speaking, to avoid confusing behavior around hit testing, a
+/// RenderConstrainedOverflowBox should usually be wrapped in a RenderClipRect.
+///
+/// The child is positioned according to [alignment]. To position a smaller
+/// child inside a larger parent, use [RenderPositionedBox] and
+/// [RenderConstrainedBox] rather than RenderConstrainedOverflowBox.
+///
+/// See also:
+///
+///  * [RenderUnconstrainedBox] for a render object that allows its children
+///    to render themselves unconstrained, expands to fit them, and considers
+///    overflow to be an error.
+///  * [RenderSizedOverflowBox], a render object that is a specific size but
+///    passes its original constraints through to its child, which it allows to
+///    overflow.
+class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
+  /// Creates a render object that lets its child overflow itself.
+  RenderConstrainedOverflowBox({
+    RenderBox? child,
+    double? minWidth,
+    double? maxWidth,
+    double? minHeight,
+    double? maxHeight,
+    AlignmentGeometry alignment = Alignment.center,
+    TextDirection? textDirection,
+  }) : _minWidth = minWidth,
+       _maxWidth = maxWidth,
+       _minHeight = minHeight,
+       _maxHeight = maxHeight,
+       super(child: child, alignment: alignment, textDirection: textDirection);
+
+  /// The minimum width constraint to give the child. Set this to null (the
+  /// default) to use the constraint from the parent instead.
+  double? get minWidth => _minWidth;
+  double? _minWidth;
+  set minWidth(double? value) {
+    if (_minWidth == value)
+      return;
+    _minWidth = value;
+    markNeedsLayout();
+  }
+
+  /// The maximum width constraint to give the child. Set this to null (the
+  /// default) to use the constraint from the parent instead.
+  double? get maxWidth => _maxWidth;
+  double? _maxWidth;
+  set maxWidth(double? value) {
+    if (_maxWidth == value)
+      return;
+    _maxWidth = value;
+    markNeedsLayout();
+  }
+
+  /// The minimum height constraint to give the child. Set this to null (the
+  /// default) to use the constraint from the parent instead.
+  double? get minHeight => _minHeight;
+  double? _minHeight;
+  set minHeight(double? value) {
+    if (_minHeight == value)
+      return;
+    _minHeight = value;
+    markNeedsLayout();
+  }
+
+  /// The maximum height constraint to give the child. Set this to null (the
+  /// default) to use the constraint from the parent instead.
+  double? get maxHeight => _maxHeight;
+  double? _maxHeight;
+  set maxHeight(double? value) {
+    if (_maxHeight == value)
+      return;
+    _maxHeight = value;
+    markNeedsLayout();
+  }
+
+  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
+    return BoxConstraints(
+      minWidth: _minWidth ?? constraints.minWidth,
+      maxWidth: _maxWidth ?? constraints.maxWidth,
+      minHeight: _minHeight ?? constraints.minHeight,
+      maxHeight: _maxHeight ?? constraints.maxHeight,
+    );
+  }
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return constraints.biggest;
+  }
+
+  @override
+  void performLayout() {
+    if (child != null) {
+      child?.layout(_getInnerConstraints(constraints), parentUsesSize: true);
+      alignChild();
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('minWidth', minWidth, ifNull: 'use parent minWidth constraint'));
+    properties.add(DoubleProperty('maxWidth', maxWidth, ifNull: 'use parent maxWidth constraint'));
+    properties.add(DoubleProperty('minHeight', minHeight, ifNull: 'use parent minHeight constraint'));
+    properties.add(DoubleProperty('maxHeight', maxHeight, ifNull: 'use parent maxHeight constraint'));
+  }
+}
+
+/// Renders a box, imposing no constraints on its child, allowing the child to
+/// render at its "natural" size.
+///
+/// This allows a child to render at the size it would render if it were alone
+/// on an infinite canvas with no constraints. This box will then attempt to
+/// adopt the same size, within the limits of its own constraints. If it ends
+/// up with a different size, it will align the child based on [alignment].
+/// If the box cannot expand enough to accommodate the entire child, the
+/// child will be clipped.
+///
+/// In debug mode, if the child overflows the box, a warning will be printed on
+/// the console, and black and yellow striped areas will appear where the
+/// overflow occurs.
+///
+/// See also:
+///
+///  * [RenderConstrainedBox], which renders a box which imposes constraints
+///    on its child.
+///  * [RenderConstrainedOverflowBox], which renders a box that imposes different
+///    constraints on its child than it gets from its parent, possibly allowing
+///    the child to overflow the parent.
+///  * [RenderSizedOverflowBox], a render object that is a specific size but
+///    passes its original constraints through to its child, which it allows to
+///    overflow.
+class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflowIndicatorMixin {
+  /// Create a render object that sizes itself to the child but does not
+  /// pass the [constraints] down to that child.
+  ///
+  /// The [alignment] must not be null.
+  RenderUnconstrainedBox({
+    required AlignmentGeometry alignment,
+    required TextDirection? textDirection,
+    Axis? constrainedAxis,
+    RenderBox? child,
+    Clip clipBehavior = Clip.none,
+  }) : assert(alignment != null),
+       assert(clipBehavior != null),
+       _constrainedAxis = constrainedAxis,
+       _clipBehavior = clipBehavior,
+       super.mixin(alignment, textDirection, child);
+
+  /// The axis to retain constraints on, if any.
+  ///
+  /// If not set, or set to null (the default), neither axis will retain its
+  /// constraints. If set to [Axis.vertical], then vertical constraints will
+  /// be retained, and if set to [Axis.horizontal], then horizontal constraints
+  /// will be retained.
+  Axis? get constrainedAxis => _constrainedAxis;
+  Axis? _constrainedAxis;
+  set constrainedAxis(Axis? value) {
+    if (_constrainedAxis == value)
+      return;
+    _constrainedAxis = value;
+    markNeedsLayout();
+  }
+
+  Rect _overflowContainerRect = Rect.zero;
+  Rect _overflowChildRect = Rect.zero;
+  bool _isOverflowing = false;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior = Clip.none;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  Size _calculateSizeWithChild({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
+    assert(child != null);
+    // Let the child lay itself out at it's "natural" size, but if
+    // constrainedAxis is non-null, keep any constraints on that axis.
+    final BoxConstraints childConstraints;
+    if (constrainedAxis != null) {
+      switch (constrainedAxis!) {
+        case Axis.horizontal:
+          childConstraints = BoxConstraints(maxWidth: constraints.maxWidth, minWidth: constraints.minWidth);
+          break;
+        case Axis.vertical:
+          childConstraints = BoxConstraints(maxHeight: constraints.maxHeight, minHeight: constraints.minHeight);
+          break;
+      }
+    } else {
+      childConstraints = const BoxConstraints();
+    }
+    return constraints.constrain(layoutChild(child!, childConstraints));
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (child == null) {
+      return constraints.smallest;
+    }
+    return _calculateSizeWithChild(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.dryLayoutChild,
+    );
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    if (child != null) {
+      size = _calculateSizeWithChild(
+        constraints: constraints,
+        layoutChild: ChildLayoutHelper.layoutChild,
+      );
+      alignChild();
+      final BoxParentData childParentData = child!.parentData! as BoxParentData;
+      _overflowContainerRect = Offset.zero & size;
+      _overflowChildRect = childParentData.offset & child!.size;
+    } else {
+      size = constraints.smallest;
+      _overflowContainerRect = Rect.zero;
+      _overflowChildRect = Rect.zero;
+    }
+    _isOverflowing = RelativeRect.fromRect(_overflowContainerRect, _overflowChildRect).hasInsets;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    // There's no point in drawing the child if we're empty, or there is no
+    // child.
+    if (child == null || size.isEmpty)
+      return;
+
+    if (!_isOverflowing) {
+      super.paint(context, offset);
+      return;
+    }
+
+    if (clipBehavior == Clip.none) {
+      _clipRectLayer = null;
+      super.paint(context, offset);
+    } else {
+      // We have overflow and the clipBehavior isn't none. Clip it.
+      _clipRectLayer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, super.paint,
+          clipBehavior: clipBehavior, oldLayer:_clipRectLayer);
+    }
+
+    // Display the overflow indicator.
+    assert(() {
+      paintOverflowIndicator(context, offset, _overflowContainerRect, _overflowChildRect);
+      return true;
+    }());
+  }
+
+  ClipRectLayer? _clipRectLayer;
+
+  @override
+  Rect? describeApproximatePaintClip(RenderObject child) {
+    return _isOverflowing ? Offset.zero & size : null;
+  }
+
+  @override
+  String toStringShort() {
+    String header = super.toStringShort();
+    if (_isOverflowing)
+      header += ' OVERFLOWING';
+    return header;
+  }
+}
+
+/// A render object that is a specific size but passes its original constraints
+/// through to its child, which it allows to overflow.
+///
+/// If the child's resulting size differs from this render object's size, then
+/// the child is aligned according to the [alignment] property.
+///
+/// See also:
+///
+///  * [RenderUnconstrainedBox] for a render object that allows its children
+///    to render themselves unconstrained, expands to fit them, and considers
+///    overflow to be an error.
+///  * [RenderConstrainedOverflowBox] for a render object that imposes
+///    different constraints on its child than it gets from its parent,
+///    possibly allowing the child to overflow the parent.
+class RenderSizedOverflowBox extends RenderAligningShiftedBox {
+  /// Creates a render box of a given size that lets its child overflow.
+  ///
+  /// The [requestedSize] and [alignment] arguments must not be null.
+  ///
+  /// The [textDirection] argument must not be null if the [alignment] is
+  /// direction-sensitive.
+  RenderSizedOverflowBox({
+    RenderBox? child,
+    required Size requestedSize,
+    AlignmentGeometry alignment = Alignment.center,
+    TextDirection? textDirection,
+  }) : assert(requestedSize != null),
+       _requestedSize = requestedSize,
+       super(child: child, alignment: alignment, textDirection: textDirection);
+
+  /// The size this render box should attempt to be.
+  Size get requestedSize => _requestedSize;
+  Size _requestedSize;
+  set requestedSize(Size value) {
+    assert(value != null);
+    if (_requestedSize == value)
+      return;
+    _requestedSize = value;
+    markNeedsLayout();
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    return _requestedSize.width;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    return _requestedSize.width;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    return _requestedSize.height;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    return _requestedSize.height;
+  }
+
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    if (child != null)
+      return child!.getDistanceToActualBaseline(baseline);
+    return super.computeDistanceToActualBaseline(baseline);
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return constraints.constrain(_requestedSize);
+  }
+
+  @override
+  void performLayout() {
+    size = constraints.constrain(_requestedSize);
+    if (child != null) {
+      child!.layout(constraints, parentUsesSize: true);
+      alignChild();
+    }
+  }
+}
+
+/// Sizes its child to a fraction of the total available space.
+///
+/// For both its width and height, this render object imposes a tight
+/// constraint on its child that is a multiple (typically less than 1.0) of the
+/// maximum constraint it received from its parent on that axis. If the factor
+/// for a given axis is null, then the constraints from the parent are just
+/// passed through instead.
+///
+/// It then tries to size itself to the size of its child. Where this is not
+/// possible (e.g. if the constraints from the parent are themselves tight), the
+/// child is aligned according to [alignment].
+class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox {
+  /// Creates a render box that sizes its child to a fraction of the total available space.
+  ///
+  /// If non-null, the [widthFactor] and [heightFactor] arguments must be
+  /// non-negative.
+  ///
+  /// The [alignment] must not be null.
+  ///
+  /// The [textDirection] must be non-null if the [alignment] is
+  /// direction-sensitive.
+  RenderFractionallySizedOverflowBox({
+    RenderBox? child,
+    double? widthFactor,
+    double? heightFactor,
+    AlignmentGeometry alignment = Alignment.center,
+    TextDirection? textDirection,
+  }) : _widthFactor = widthFactor,
+       _heightFactor = heightFactor,
+       super(child: child, alignment: alignment, textDirection: textDirection) {
+    assert(_widthFactor == null || _widthFactor! >= 0.0);
+    assert(_heightFactor == null || _heightFactor! >= 0.0);
+  }
+
+  /// If non-null, the factor of the incoming width to use.
+  ///
+  /// If non-null, the child is given a tight width constraint that is the max
+  /// incoming width constraint multiplied by this factor. If null, the child is
+  /// given the incoming width constraints.
+  double? get widthFactor => _widthFactor;
+  double? _widthFactor;
+  set widthFactor(double? value) {
+    assert(value == null || value >= 0.0);
+    if (_widthFactor == value)
+      return;
+    _widthFactor = value;
+    markNeedsLayout();
+  }
+
+  /// If non-null, the factor of the incoming height to use.
+  ///
+  /// If non-null, the child is given a tight height constraint that is the max
+  /// incoming width constraint multiplied by this factor. If null, the child is
+  /// given the incoming width constraints.
+  double? get heightFactor => _heightFactor;
+  double? _heightFactor;
+  set heightFactor(double? value) {
+    assert(value == null || value >= 0.0);
+    if (_heightFactor == value)
+      return;
+    _heightFactor = value;
+    markNeedsLayout();
+  }
+
+  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
+    double minWidth = constraints.minWidth;
+    double maxWidth = constraints.maxWidth;
+    if (_widthFactor != null) {
+      final double width = maxWidth * _widthFactor!;
+      minWidth = width;
+      maxWidth = width;
+    }
+    double minHeight = constraints.minHeight;
+    double maxHeight = constraints.maxHeight;
+    if (_heightFactor != null) {
+      final double height = maxHeight * _heightFactor!;
+      minHeight = height;
+      maxHeight = height;
+    }
+    return BoxConstraints(
+      minWidth: minWidth,
+      maxWidth: maxWidth,
+      minHeight: minHeight,
+      maxHeight: maxHeight,
+    );
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    final double result;
+    if (child == null) {
+      result = super.computeMinIntrinsicWidth(height);
+    } else { // the following line relies on double.infinity absorption
+      result = child!.getMinIntrinsicWidth(height * (_heightFactor ?? 1.0));
+    }
+    assert(result.isFinite);
+    return result / (_widthFactor ?? 1.0);
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    final double result;
+    if (child == null) {
+      result = super.computeMaxIntrinsicWidth(height);
+    } else { // the following line relies on double.infinity absorption
+      result = child!.getMaxIntrinsicWidth(height * (_heightFactor ?? 1.0));
+    }
+    assert(result.isFinite);
+    return result / (_widthFactor ?? 1.0);
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    final double result;
+    if (child == null) {
+      result = super.computeMinIntrinsicHeight(width);
+    } else { // the following line relies on double.infinity absorption
+      result = child!.getMinIntrinsicHeight(width * (_widthFactor ?? 1.0));
+    }
+    assert(result.isFinite);
+    return result / (_heightFactor ?? 1.0);
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    final double result;
+    if (child == null) {
+      result = super.computeMaxIntrinsicHeight(width);
+    } else { // the following line relies on double.infinity absorption
+      result = child!.getMaxIntrinsicHeight(width * (_widthFactor ?? 1.0));
+    }
+    assert(result.isFinite);
+    return result / (_heightFactor ?? 1.0);
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (child != null) {
+      final Size childSize = child!.getDryLayout(_getInnerConstraints(constraints));
+      return constraints.constrain(childSize);
+    }
+    return constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero));
+  }
+
+  @override
+  void performLayout() {
+    if (child != null) {
+      child!.layout(_getInnerConstraints(constraints), parentUsesSize: true);
+      size = constraints.constrain(child!.size);
+      alignChild();
+    } else {
+      size = constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero));
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'pass-through'));
+    properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'pass-through'));
+  }
+}
+
+/// A delegate for computing the layout of a render object with a single child.
+///
+/// Used by [CustomSingleChildLayout] (in the widgets library) and
+/// [RenderCustomSingleChildLayoutBox] (in the rendering library).
+///
+/// When asked to layout, [CustomSingleChildLayout] first calls [getSize] with
+/// its incoming constraints to determine its size. It then calls
+/// [getConstraintsForChild] to determine the constraints to apply to the child.
+/// After the child completes its layout, [RenderCustomSingleChildLayoutBox]
+/// calls [getPositionForChild] to determine the child's position.
+///
+/// The [shouldRelayout] method is called when a new instance of the class
+/// is provided, to check if the new instance actually represents different
+/// information.
+///
+/// The most efficient way to trigger a relayout is to supply a `relayout`
+/// argument to the constructor of the [SingleChildLayoutDelegate]. The custom
+/// layout will listen to this value and relayout whenever the Listenable
+/// notifies its listeners, such as when an [Animation] ticks. This allows
+/// the custom layout to avoid the build phase of the pipeline.
+///
+/// See also:
+///
+///  * [CustomSingleChildLayout], the widget that uses this delegate.
+///  * [RenderCustomSingleChildLayoutBox], render object that uses this
+///    delegate.
+abstract class SingleChildLayoutDelegate {
+  /// Creates a layout delegate.
+  ///
+  /// The layout will update whenever [relayout] notifies its listeners.
+  const SingleChildLayoutDelegate({ Listenable? relayout }) : _relayout = relayout;
+
+  final Listenable? _relayout;
+
+  /// The size of this object given the incoming constraints.
+  ///
+  /// Defaults to the biggest size that satisfies the given constraints.
+  Size getSize(BoxConstraints constraints) => constraints.biggest;
+
+  /// The constraints for the child given the incoming constraints.
+  ///
+  /// During layout, the child is given the layout constraints returned by this
+  /// function. The child is required to pick a size for itself that satisfies
+  /// these constraints.
+  ///
+  /// Defaults to the given constraints.
+  BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints;
+
+  /// The position where the child should be placed.
+  ///
+  /// The `size` argument is the size of the parent, which might be different
+  /// from the value returned by [getSize] if that size doesn't satisfy the
+  /// constraints passed to [getSize]. The `childSize` argument is the size of
+  /// the child, which will satisfy the constraints returned by
+  /// [getConstraintsForChild].
+  ///
+  /// Defaults to positioning the child in the upper left corner of the parent.
+  Offset getPositionForChild(Size size, Size childSize) => Offset.zero;
+
+  /// Called whenever a new instance of the custom layout delegate class is
+  /// provided to the [RenderCustomSingleChildLayoutBox] object, or any time
+  /// that a new [CustomSingleChildLayout] object is created with a new instance
+  /// of the custom layout delegate class (which amounts to the same thing,
+  /// because the latter is implemented in terms of the former).
+  ///
+  /// If the new instance represents different information than the old
+  /// instance, then the method should return true, otherwise it should return
+  /// false.
+  ///
+  /// If the method returns false, then the [getSize],
+  /// [getConstraintsForChild], and [getPositionForChild] calls might be
+  /// optimized away.
+  ///
+  /// It's possible that the layout methods will get called even if
+  /// [shouldRelayout] returns false (e.g. if an ancestor changed its layout).
+  /// It's also possible that the layout method will get called
+  /// without [shouldRelayout] being called at all (e.g. if the parent changes
+  /// size).
+  bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate);
+}
+
+/// Defers the layout of its single child to a delegate.
+///
+/// The delegate can determine the layout constraints for the child and can
+/// decide where to position the child. The delegate can also determine the size
+/// of the parent, but the size of the parent cannot depend on the size of the
+/// child.
+class RenderCustomSingleChildLayoutBox extends RenderShiftedBox {
+  /// Creates a render box that defers its layout to a delegate.
+  ///
+  /// The [delegate] argument must not be null.
+  RenderCustomSingleChildLayoutBox({
+    RenderBox? child,
+    required SingleChildLayoutDelegate delegate,
+  }) : assert(delegate != null),
+       _delegate = delegate,
+       super(child);
+
+  /// A delegate that controls this object's layout.
+  SingleChildLayoutDelegate get delegate => _delegate;
+  SingleChildLayoutDelegate _delegate;
+  set delegate(SingleChildLayoutDelegate newDelegate) {
+    assert(newDelegate != null);
+    if (_delegate == newDelegate)
+      return;
+    final SingleChildLayoutDelegate oldDelegate = _delegate;
+    if (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRelayout(oldDelegate))
+      markNeedsLayout();
+    _delegate = newDelegate;
+    if (attached) {
+      oldDelegate._relayout?.removeListener(markNeedsLayout);
+      newDelegate._relayout?.addListener(markNeedsLayout);
+    }
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    _delegate._relayout?.addListener(markNeedsLayout);
+  }
+
+  @override
+  void detach() {
+    _delegate._relayout?.removeListener(markNeedsLayout);
+    super.detach();
+  }
+
+  Size _getSize(BoxConstraints constraints) {
+    return constraints.constrain(_delegate.getSize(constraints));
+  }
+
+  // TODO(ianh): It's a bit dubious to be using the getSize function from the delegate to
+  // figure out the intrinsic dimensions. We really should either not support intrinsics,
+  // or we should expose intrinsic delegate callbacks and throw if they're not implemented.
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
+    if (width.isFinite)
+      return width;
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
+    if (width.isFinite)
+      return width;
+    return 0.0;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
+    if (height.isFinite)
+      return height;
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
+    if (height.isFinite)
+      return height;
+    return 0.0;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _getSize(constraints);
+  }
+
+  @override
+  void performLayout() {
+    size = _getSize(constraints);
+    if (child != null) {
+      final BoxConstraints childConstraints = delegate.getConstraintsForChild(constraints);
+      assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
+      child!.layout(childConstraints, parentUsesSize: !childConstraints.isTight);
+      final BoxParentData childParentData = child!.parentData! as BoxParentData;
+      childParentData.offset = delegate.getPositionForChild(size, childConstraints.isTight ? childConstraints.smallest : child!.size);
+    }
+  }
+}
+
+/// Shifts the child down such that the child's baseline (or the
+/// bottom of the child, if the child has no baseline) is [baseline]
+/// logical pixels below the top of this box, then sizes this box to
+/// contain the child.
+///
+/// If [baseline] is less than the distance from the top of the child
+/// to the baseline of the child, then the child will overflow the top
+/// of the box. This is typically not desirable, in particular, that
+/// part of the child will not be found when doing hit tests, so the
+/// user cannot interact with that part of the child.
+///
+/// This box will be sized so that its bottom is coincident with the
+/// bottom of the child. This means if this box shifts the child down,
+/// there will be space between the top of this box and the top of the
+/// child, but there is never space between the bottom of the child
+/// and the bottom of the box.
+class RenderBaseline extends RenderShiftedBox {
+  /// Creates a [RenderBaseline] object.
+  ///
+  /// The [baseline] and [baselineType] arguments must not be null.
+  RenderBaseline({
+    RenderBox? child,
+    required double baseline,
+    required TextBaseline baselineType,
+  }) : assert(baseline != null),
+       assert(baselineType != null),
+       _baseline = baseline,
+       _baselineType = baselineType,
+       super(child);
+
+  /// The number of logical pixels from the top of this box at which to position
+  /// the child's baseline.
+  double get baseline => _baseline;
+  double _baseline;
+  set baseline(double value) {
+    assert(value != null);
+    if (_baseline == value)
+      return;
+    _baseline = value;
+    markNeedsLayout();
+  }
+
+  /// The type of baseline to use for positioning the child.
+  TextBaseline get baselineType => _baselineType;
+  TextBaseline _baselineType;
+  set baselineType(TextBaseline value) {
+    assert(value != null);
+    if (_baselineType == value)
+      return;
+    _baselineType = value;
+    markNeedsLayout();
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (child != null) {
+      assert(debugCannotComputeDryLayout(
+        reason: 'Baseline metrics are only available after a full layout.',
+      ));
+      return const Size(0, 0);
+    }
+    return constraints.smallest;
+  }
+
+  @override
+  void performLayout() {
+    if (child != null) {
+      final BoxConstraints constraints = this.constraints;
+      child!.layout(constraints.loosen(), parentUsesSize: true);
+      final double childBaseline = child!.getDistanceToBaseline(baselineType)!;
+      final double actualBaseline = baseline;
+      final double top = actualBaseline - childBaseline;
+      final BoxParentData childParentData = child!.parentData! as BoxParentData;
+      childParentData.offset = Offset(0.0, top);
+      final Size childSize = child!.size;
+      size = constraints.constrain(Size(childSize.width, top + childSize.height));
+    } else {
+      size = constraints.smallest;
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('baseline', baseline));
+    properties.add(EnumProperty<TextBaseline>('baselineType', baselineType));
+  }
+}
diff --git a/lib/src/rendering/sliver.dart b/lib/src/rendering/sliver.dart
new file mode 100644
index 0000000..66864f7
--- /dev/null
+++ b/lib/src/rendering/sliver.dart
@@ -0,0 +1,1838 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'binding.dart';
+import 'box.dart';
+import 'debug.dart';
+import 'object.dart';
+import 'viewport.dart';
+import 'viewport_offset.dart';
+
+// CORE TYPES FOR SLIVERS
+// The RenderSliver base class and its helper types.
+
+/// The direction in which a sliver's contents are ordered, relative to the
+/// scroll offset axis.
+///
+/// For example, a vertical alphabetical list that is going [AxisDirection.down]
+/// with a [GrowthDirection.forward] would have the A at the top and the Z at
+/// the bottom, with the A adjacent to the origin, as would such a list going
+/// [AxisDirection.up] with a [GrowthDirection.reverse]. On the other hand, a
+/// vertical alphabetical list that is going [AxisDirection.down] with a
+/// [GrowthDirection.reverse] would have the Z at the top (at scroll offset
+/// zero) and the A below it.
+///
+/// The direction in which the scroll offset increases is given by
+/// [applyGrowthDirectionToAxisDirection].
+enum GrowthDirection {
+  /// This sliver's contents are ordered in the same direction as the
+  /// [AxisDirection].
+  forward,
+
+  /// This sliver's contents are ordered in the opposite direction of the
+  /// [AxisDirection].
+  reverse,
+}
+
+/// Flips the [AxisDirection] if the [GrowthDirection] is [GrowthDirection.reverse].
+///
+/// Specifically, returns `axisDirection` if `growthDirection` is
+/// [GrowthDirection.forward], otherwise returns [flipAxisDirection] applied to
+/// `axisDirection`.
+///
+/// This function is useful in [RenderSliver] subclasses that are given both an
+/// [AxisDirection] and a [GrowthDirection] and wish to compute the
+/// [AxisDirection] in which growth will occur.
+AxisDirection applyGrowthDirectionToAxisDirection(AxisDirection axisDirection, GrowthDirection growthDirection) {
+  assert(axisDirection != null);
+  assert(growthDirection != null);
+  switch (growthDirection) {
+    case GrowthDirection.forward:
+      return axisDirection;
+    case GrowthDirection.reverse:
+      return flipAxisDirection(axisDirection);
+  }
+}
+
+/// Flips the [ScrollDirection] if the [GrowthDirection] is [GrowthDirection.reverse].
+///
+/// Specifically, returns `scrollDirection` if `scrollDirection` is
+/// [GrowthDirection.forward], otherwise returns [flipScrollDirection] applied to
+/// `scrollDirection`.
+///
+/// This function is useful in [RenderSliver] subclasses that are given both an
+/// [ScrollDirection] and a [GrowthDirection] and wish to compute the
+/// [ScrollDirection] in which growth will occur.
+ScrollDirection applyGrowthDirectionToScrollDirection(ScrollDirection scrollDirection, GrowthDirection growthDirection) {
+  assert(scrollDirection != null);
+  assert(growthDirection != null);
+  switch (growthDirection) {
+    case GrowthDirection.forward:
+      return scrollDirection;
+    case GrowthDirection.reverse:
+      return flipScrollDirection(scrollDirection);
+  }
+}
+
+/// Immutable layout constraints for [RenderSliver] layout.
+///
+/// The [SliverConstraints] describe the current scroll state of the viewport
+/// from the point of view of the sliver receiving the constraints. For example,
+/// a [scrollOffset] of zero means that the leading edge of the sliver is
+/// visible in the viewport, not that the viewport itself has a zero scroll
+/// offset.
+class SliverConstraints extends Constraints {
+  /// Creates sliver constraints with the given information.
+  ///
+  /// All of the argument must not be null.
+  const SliverConstraints({
+    required this.axisDirection,
+    required this.growthDirection,
+    required this.userScrollDirection,
+    required this.scrollOffset,
+    required this.precedingScrollExtent,
+    required this.overlap,
+    required this.remainingPaintExtent,
+    required this.crossAxisExtent,
+    required this.crossAxisDirection,
+    required this.viewportMainAxisExtent,
+    required this.remainingCacheExtent,
+    required this.cacheOrigin,
+  }) : assert(axisDirection != null),
+       assert(growthDirection != null),
+       assert(userScrollDirection != null),
+       assert(scrollOffset != null),
+       assert(precedingScrollExtent != null),
+       assert(overlap != null),
+       assert(remainingPaintExtent != null),
+       assert(crossAxisExtent != null),
+       assert(crossAxisDirection != null),
+       assert(viewportMainAxisExtent != null),
+       assert(remainingCacheExtent != null),
+       assert(cacheOrigin != null);
+
+  /// Creates a copy of this object but with the given fields replaced with the
+  /// new values.
+  SliverConstraints copyWith({
+    AxisDirection? axisDirection,
+    GrowthDirection? growthDirection,
+    ScrollDirection? userScrollDirection,
+    double? scrollOffset,
+    double? precedingScrollExtent,
+    double? overlap,
+    double? remainingPaintExtent,
+    double? crossAxisExtent,
+    AxisDirection? crossAxisDirection,
+    double? viewportMainAxisExtent,
+    double? remainingCacheExtent,
+    double? cacheOrigin,
+  }) {
+    return SliverConstraints(
+      axisDirection: axisDirection ?? this.axisDirection,
+      growthDirection: growthDirection ?? this.growthDirection,
+      userScrollDirection: userScrollDirection ?? this.userScrollDirection,
+      scrollOffset: scrollOffset ?? this.scrollOffset,
+      precedingScrollExtent: precedingScrollExtent ?? this.precedingScrollExtent,
+      overlap: overlap ?? this.overlap,
+      remainingPaintExtent: remainingPaintExtent ?? this.remainingPaintExtent,
+      crossAxisExtent: crossAxisExtent ?? this.crossAxisExtent,
+      crossAxisDirection: crossAxisDirection ?? this.crossAxisDirection,
+      viewportMainAxisExtent: viewportMainAxisExtent ?? this.viewportMainAxisExtent,
+      remainingCacheExtent: remainingCacheExtent ?? this.remainingCacheExtent,
+      cacheOrigin: cacheOrigin ?? this.cacheOrigin,
+    );
+  }
+
+  /// The direction in which the [scrollOffset] and [remainingPaintExtent]
+  /// increase.
+  final AxisDirection axisDirection;
+
+  /// The direction in which the contents of slivers are ordered, relative to
+  /// the [axisDirection].
+  ///
+  /// For example, if the [axisDirection] is [AxisDirection.up], and the
+  /// [growthDirection] is [GrowthDirection.forward], then an alphabetical list
+  /// will have A at the bottom, then B, then C, and so forth, with Z at the
+  /// top, with the bottom of the A at scroll offset zero, and the top of the Z
+  /// at the highest scroll offset.
+  ///
+  /// If a viewport has an overall [AxisDirection] of [AxisDirection.down], then
+  /// slivers above the absolute zero offset will have an axis of
+  /// [AxisDirection.up] and a growth direction of [GrowthDirection.reverse],
+  /// while slivers below the absolute zero offset will have the same axis
+  /// direction as the viewport and a growth direction of
+  /// [GrowthDirection.forward]. (The slivers with a reverse growth direction
+  /// still see only positive scroll offsets; the scroll offsets are reversed as
+  /// well, with zero at the absolute zero point, and positive numbers going
+  /// away from there.)
+  ///
+  /// Normally, the absolute zero offset is determined by the viewport's
+  /// [RenderViewport.center] and [RenderViewport.anchor] properties.
+  final GrowthDirection growthDirection;
+
+  /// The direction in which the user is attempting to scroll, relative to the
+  /// [axisDirection] and [growthDirection].
+  ///
+  /// For example, if [growthDirection] is [GrowthDirection.reverse] and
+  /// [axisDirection] is [AxisDirection.down], then a
+  /// [ScrollDirection.forward] means that the user is scrolling up, in the
+  /// positive [scrollOffset] direction.
+  ///
+  /// If the _user_ is not scrolling, this will return [ScrollDirection.idle]
+  /// even if there is (for example) a [ScrollActivity] currently animating the
+  /// position.
+  ///
+  /// This is used by some slivers to determine how to react to a change in
+  /// scroll offset. For example, [RenderSliverFloatingPersistentHeader] will
+  /// only expand a floating app bar when the [userScrollDirection] is in the
+  /// positive scroll offset direction.
+  final ScrollDirection userScrollDirection;
+
+  /// The scroll offset, in this sliver's coordinate system, that corresponds to
+  /// the earliest visible part of this sliver in the [AxisDirection] if
+  /// [growthDirection] is [GrowthDirection.forward] or in the opposite
+  /// [AxisDirection] direction if [growthDirection] is [GrowthDirection.reverse].
+  ///
+  /// For example, if [AxisDirection] is [AxisDirection.down] and [growthDirection]
+  /// is [GrowthDirection.forward], then scroll offset is the amount the top of
+  /// the sliver has been scrolled past the top of the viewport.
+  ///
+  /// This value is typically used to compute whether this sliver should still
+  /// protrude into the viewport via [SliverGeometry.paintExtent] and
+  /// [SliverGeometry.layoutExtent] considering how far the beginning of the
+  /// sliver is above the beginning of the viewport.
+  ///
+  /// For slivers whose top is not past the top of the viewport, the
+  /// [scrollOffset] is `0` when [AxisDirection] is [AxisDirection.down] and
+  /// [growthDirection] is [GrowthDirection.forward]. The set of slivers with
+  /// [scrollOffset] `0` includes all the slivers that are below the bottom of the
+  /// viewport.
+  ///
+  /// [SliverConstraints.remainingPaintExtent] is typically used to accomplish
+  /// the same goal of computing whether scrolled out slivers should still
+  /// partially 'protrude in' from the bottom of the viewport.
+  ///
+  /// Whether this corresponds to the beginning or the end of the sliver's
+  /// contents depends on the [growthDirection].
+  final double scrollOffset;
+
+  /// The scroll distance that has been consumed by all [RenderSliver]s that
+  /// came before this [RenderSliver].
+  ///
+  /// # Edge Cases
+  ///
+  /// [RenderSliver]s often lazily create their internal content as layout
+  /// occurs, e.g., [SliverList]. In this case, when [RenderSliver]s exceed the
+  /// viewport, their children are built lazily, and the [RenderSliver] does not
+  /// have enough information to estimate its total extent,
+  /// [precedingScrollExtent] will be [double.infinity] for all [RenderSliver]s
+  /// that appear after the lazily constructed child. This is because a total
+  /// [SliverGeometry.scrollExtent] cannot be calculated unless all inner
+  /// children have been created and sized, or the number of children and
+  /// estimated extents are provided. The infinite [SliverGeometry.scrollExtent]
+  /// will become finite as soon as enough information is available to estimate
+  /// the overall extent of all children within the given [RenderSliver].
+  ///
+  /// [RenderSliver]s may legitimately be infinite, meaning that they can scroll
+  /// content forever without reaching the end. For any [RenderSliver]s that
+  /// appear after the infinite [RenderSliver], the [precedingScrollExtent] will
+  /// be [double.infinity].
+  final double precedingScrollExtent;
+
+  /// The number of pixels from where the pixels corresponding to the
+  /// [scrollOffset] will be painted up to the first pixel that has not yet been
+  /// painted on by an earlier sliver, in the [axisDirection].
+  ///
+  /// For example, if the previous sliver had a [SliverGeometry.paintExtent] of
+  /// 100.0 pixels but a [SliverGeometry.layoutExtent] of only 50.0 pixels,
+  /// then the [overlap] of this sliver will be 50.0.
+  ///
+  /// This is typically ignored unless the sliver is itself going to be pinned
+  /// or floating and wants to avoid doing so under the previous sliver.
+  final double overlap;
+
+  /// The number of pixels of content that the sliver should consider providing.
+  /// (Providing more pixels than this is inefficient.)
+  ///
+  /// The actual number of pixels provided should be specified in the
+  /// [RenderSliver.geometry] as [SliverGeometry.paintExtent].
+  ///
+  /// This value may be infinite, for example if the viewport is an
+  /// unconstrained [RenderShrinkWrappingViewport].
+  ///
+  /// This value may be 0.0, for example if the sliver is scrolled off the
+  /// bottom of a downwards vertical viewport.
+  final double remainingPaintExtent;
+
+  /// The number of pixels in the cross-axis.
+  ///
+  /// For a vertical list, this is the width of the sliver.
+  final double crossAxisExtent;
+
+  /// The direction in which children should be placed in the cross axis.
+  ///
+  /// Typically used in vertical lists to describe whether the ambient
+  /// [TextDirection] is [TextDirection.rtl] or [TextDirection.ltr].
+  final AxisDirection crossAxisDirection;
+
+  /// The number of pixels the viewport can display in the main axis.
+  ///
+  /// For a vertical list, this is the height of the viewport.
+  final double viewportMainAxisExtent;
+
+  /// Where the cache area starts relative to the [scrollOffset].
+  ///
+  /// Slivers that fall into the cache area located before the leading edge and
+  /// after the trailing edge of the viewport should still render content
+  /// because they are about to become visible when the user scrolls.
+  ///
+  /// The [cacheOrigin] describes where the [remainingCacheExtent] starts relative
+  /// to the [scrollOffset]. A cache origin of 0 means that the sliver does not
+  /// have to provide any content before the current [scrollOffset]. A
+  /// [cacheOrigin] of -250.0 means that even though the first visible part of
+  /// the sliver will be at the provided [scrollOffset], the sliver should
+  /// render content starting 250.0 before the [scrollOffset] to fill the
+  /// cache area of the viewport.
+  ///
+  /// The [cacheOrigin] is always negative or zero and will never exceed
+  /// -[scrollOffset]. In other words, a sliver is never asked to provide
+  /// content before its zero [scrollOffset].
+  ///
+  /// See also:
+  ///
+  ///  * [RenderViewport.cacheExtent] for a description of a viewport's cache area.
+  final double cacheOrigin;
+
+
+  /// Describes how much content the sliver should provide starting from the
+  /// [cacheOrigin].
+  ///
+  /// Not all content in the [remainingCacheExtent] will be visible as some
+  /// of it might fall into the cache area of the viewport.
+  ///
+  /// Each sliver should start laying out content at the [cacheOrigin] and
+  /// try to provide as much content as the [remainingCacheExtent] allows.
+  ///
+  /// The [remainingCacheExtent] is always larger or equal to the
+  /// [remainingPaintExtent]. Content, that falls in the [remainingCacheExtent],
+  /// but is outside of the [remainingPaintExtent] is currently not visible
+  /// in the viewport.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderViewport.cacheExtent] for a description of a viewport's cache area.
+  final double remainingCacheExtent;
+
+  /// The axis along which the [scrollOffset] and [remainingPaintExtent] are measured.
+  Axis get axis => axisDirectionToAxis(axisDirection);
+
+  /// Return what the [growthDirection] would be if the [axisDirection] was
+  /// either [AxisDirection.down] or [AxisDirection.right].
+  ///
+  /// This is the same as [growthDirection] unless the [axisDirection] is either
+  /// [AxisDirection.up] or [AxisDirection.left], in which case it is the
+  /// opposite growth direction.
+  ///
+  /// This can be useful in combination with [axis] to view the [axisDirection]
+  /// and [growthDirection] in different terms.
+  GrowthDirection get normalizedGrowthDirection {
+    assert(axisDirection != null);
+    switch (axisDirection) {
+      case AxisDirection.down:
+      case AxisDirection.right:
+        return growthDirection;
+      case AxisDirection.up:
+      case AxisDirection.left:
+        switch (growthDirection) {
+          case GrowthDirection.forward:
+            return GrowthDirection.reverse;
+          case GrowthDirection.reverse:
+            return GrowthDirection.forward;
+        }
+    }
+  }
+
+  @override
+  bool get isTight => false;
+
+  @override
+  bool get isNormalized {
+    return scrollOffset >= 0.0
+        && crossAxisExtent >= 0.0
+        && axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection)
+        && viewportMainAxisExtent >= 0.0
+        && remainingPaintExtent >= 0.0;
+  }
+
+  /// Returns [BoxConstraints] that reflects the sliver constraints.
+  ///
+  /// The `minExtent` and `maxExtent` are used as the constraints in the main
+  /// axis. If non-null, the given `crossAxisExtent` is used as a tight
+  /// constraint in the cross axis. Otherwise, the [crossAxisExtent] from this
+  /// object is used as a constraint in the cross axis.
+  ///
+  /// Useful for slivers that have [RenderBox] children.
+  BoxConstraints asBoxConstraints({
+    double minExtent = 0.0,
+    double maxExtent = double.infinity,
+    double? crossAxisExtent,
+  }) {
+    crossAxisExtent ??= this.crossAxisExtent;
+    switch (axis) {
+      case Axis.horizontal:
+        return BoxConstraints(
+          minHeight: crossAxisExtent,
+          maxHeight: crossAxisExtent,
+          minWidth: minExtent,
+          maxWidth: maxExtent,
+        );
+      case Axis.vertical:
+        return BoxConstraints(
+          minWidth: crossAxisExtent,
+          maxWidth: crossAxisExtent,
+          minHeight: minExtent,
+          maxHeight: maxExtent,
+        );
+    }
+  }
+
+  @override
+  bool debugAssertIsValid({
+    bool isAppliedConstraint = false,
+    InformationCollector? informationCollector,
+  }) {
+    assert(() {
+      bool hasErrors = false;
+      final StringBuffer errorMessage = StringBuffer('\n');
+      void verify(bool check, String message) {
+        if (check)
+          return;
+        hasErrors = true;
+        errorMessage.writeln('  $message');
+      }
+      void verifyDouble(double property, String name, {bool mustBePositive = false, bool mustBeNegative = false}) {
+        verify(property != null, 'The "$name" is null.');
+        if (property.isNaN) {
+          String additional = '.';
+          if (mustBePositive) {
+            additional = ', expected greater than or equal to zero.';
+          } else if (mustBeNegative) {
+            additional = ', expected less than or equal to zero.';
+          }
+          verify(false, 'The "$name" is NaN$additional');
+        } else if (mustBePositive) {
+          verify(property >= 0.0, 'The "$name" is negative.');
+        } else if (mustBeNegative) {
+          verify(property <= 0.0, 'The "$name" is positive.');
+        }
+      }
+      verify(axis != null, 'The "axis" is null.');
+      verify(growthDirection != null, 'The "growthDirection" is null.');
+      verifyDouble(scrollOffset, 'scrollOffset');
+      verifyDouble(overlap, 'overlap');
+      verifyDouble(crossAxisExtent, 'crossAxisExtent');
+      verifyDouble(scrollOffset, 'scrollOffset', mustBePositive: true);
+      verify(crossAxisDirection != null, 'The "crossAxisDirection" is null.');
+      verify(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection), 'The "axisDirection" and the "crossAxisDirection" are along the same axis.');
+      verifyDouble(viewportMainAxisExtent, 'viewportMainAxisExtent', mustBePositive: true);
+      verifyDouble(remainingPaintExtent, 'remainingPaintExtent', mustBePositive: true);
+      verifyDouble(remainingCacheExtent, 'remainingCacheExtent', mustBePositive: true);
+      verifyDouble(cacheOrigin, 'cacheOrigin', mustBeNegative: true);
+      verifyDouble(precedingScrollExtent, 'precedingScrollExtent', mustBePositive: true);
+      verify(isNormalized, 'The constraints are not normalized.'); // should be redundant with earlier checks
+      if (hasErrors) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('$runtimeType is not valid: $errorMessage'),
+          if (informationCollector != null)
+            ...informationCollector(),
+          DiagnosticsProperty<SliverConstraints>('The offending constraints were', this, style: DiagnosticsTreeStyle.errorProperty),
+        ]);
+      }
+      return true;
+    }());
+    return true;
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other is! SliverConstraints)
+      return false;
+    assert(other is SliverConstraints && other.debugAssertIsValid());
+    return other is SliverConstraints
+        && other.axisDirection == axisDirection
+        && other.growthDirection == growthDirection
+        && other.scrollOffset == scrollOffset
+        && other.overlap == overlap
+        && other.remainingPaintExtent == remainingPaintExtent
+        && other.crossAxisExtent == crossAxisExtent
+        && other.crossAxisDirection == crossAxisDirection
+        && other.viewportMainAxisExtent == viewportMainAxisExtent
+        && other.remainingCacheExtent == remainingCacheExtent
+        && other.cacheOrigin == cacheOrigin;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      axisDirection,
+      growthDirection,
+      scrollOffset,
+      overlap,
+      remainingPaintExtent,
+      crossAxisExtent,
+      crossAxisDirection,
+      viewportMainAxisExtent,
+      remainingCacheExtent,
+      cacheOrigin,
+    );
+  }
+
+  @override
+  String toString() {
+    final List<String> properties = <String>[
+      '$axisDirection',
+      '$growthDirection',
+      '$userScrollDirection',
+      'scrollOffset: ${scrollOffset.toStringAsFixed(1)}',
+      'remainingPaintExtent: ${remainingPaintExtent.toStringAsFixed(1)}',
+      if (overlap != 0.0) 'overlap: ${overlap.toStringAsFixed(1)}',
+      'crossAxisExtent: ${crossAxisExtent.toStringAsFixed(1)}',
+      'crossAxisDirection: $crossAxisDirection',
+      'viewportMainAxisExtent: ${viewportMainAxisExtent.toStringAsFixed(1)}',
+      'remainingCacheExtent: ${remainingCacheExtent.toStringAsFixed(1)}',
+      'cacheOrigin: ${cacheOrigin.toStringAsFixed(1)}',
+    ];
+    return 'SliverConstraints(${properties.join(', ')})';
+  }
+}
+
+/// Describes the amount of space occupied by a [RenderSliver].
+///
+/// A sliver can occupy space in several different ways, which is why this class
+/// contains multiple values.
+@immutable
+class SliverGeometry with Diagnosticable {
+  /// Creates an object that describes the amount of space occupied by a sliver.
+  ///
+  /// If the [layoutExtent] argument is null, [layoutExtent] defaults to the
+  /// [paintExtent]. If the [hitTestExtent] argument is null, [hitTestExtent]
+  /// defaults to the [paintExtent]. If [visible] is null, [visible] defaults to
+  /// whether [paintExtent] is greater than zero.
+  ///
+  /// The other arguments must not be null.
+  const SliverGeometry({
+    this.scrollExtent = 0.0,
+    this.paintExtent = 0.0,
+    this.paintOrigin = 0.0,
+    double? layoutExtent,
+    this.maxPaintExtent = 0.0,
+    this.maxScrollObstructionExtent = 0.0,
+    double? hitTestExtent,
+    bool? visible,
+    this.hasVisualOverflow = false,
+    this.scrollOffsetCorrection,
+    double? cacheExtent,
+  }) : assert(scrollExtent != null),
+       assert(paintExtent != null),
+       assert(paintOrigin != null),
+       assert(maxPaintExtent != null),
+       assert(hasVisualOverflow != null),
+       assert(scrollOffsetCorrection != 0.0),
+       layoutExtent = layoutExtent ?? paintExtent,
+       hitTestExtent = hitTestExtent ?? paintExtent,
+       cacheExtent = cacheExtent ?? layoutExtent ?? paintExtent,
+       visible = visible ?? paintExtent > 0.0;
+
+  /// A sliver that occupies no space at all.
+  static const SliverGeometry zero = SliverGeometry();
+
+  /// The (estimated) total scrollable extent that this sliver has content for.
+  ///
+  /// This is the amount of scrolling the user needs to do to get from the
+  /// beginning of this sliver to the end of this sliver.
+  ///
+  /// The value is used to calculate the [SliverConstraints.scrollOffset] of
+  /// all slivers in the scrollable and thus should be provided whether the
+  /// sliver is currently in the viewport or not.
+  ///
+  /// In a typical scrolling scenario, the [scrollExtent] is constant for a
+  /// sliver throughout the scrolling while [paintExtent] and [layoutExtent]
+  /// will progress from `0` when offscreen to between `0` and [scrollExtent]
+  /// as the sliver scrolls partially into and out of the screen and is
+  /// equal to [scrollExtent] while the sliver is entirely on screen. However,
+  /// these relationships can be customized to achieve more special effects.
+  ///
+  /// This value must be accurate if the [paintExtent] is less than the
+  /// [SliverConstraints.remainingPaintExtent] provided during layout.
+  final double scrollExtent;
+
+  /// The visual location of the first visible part of this sliver relative to
+  /// its layout position.
+  ///
+  /// For example, if the sliver wishes to paint visually before its layout
+  /// position, the [paintOrigin] is negative. The coordinate system this sliver
+  /// uses for painting is relative to this [paintOrigin]. In other words,
+  /// when [RenderSliver.paint] is called, the (0, 0) position of the [Offset]
+  /// given to it is at this [paintOrigin].
+  ///
+  /// The coordinate system used for the [paintOrigin] itself is relative
+  /// to the start of this sliver's layout position rather than relative to
+  /// its current position on the viewport. In other words, in a typical
+  /// scrolling scenario, [paintOrigin] remains constant at 0.0 rather than
+  /// tracking from 0.0 to [SliverConstraints.viewportMainAxisExtent] as the
+  /// sliver scrolls past the viewport.
+  ///
+  /// This value does not affect the layout of subsequent slivers. The next
+  /// sliver is still placed at [layoutExtent] after this sliver's layout
+  /// position. This value does affect where the [paintExtent] extent is
+  /// measured from when computing the [SliverConstraints.overlap] for the next
+  /// sliver.
+  ///
+  /// Defaults to 0.0, which means slivers start painting at their layout
+  /// position by default.
+  final double paintOrigin;
+
+  /// The amount of currently visible visual space that was taken by the sliver
+  /// to render the subset of the sliver that covers all or part of the
+  /// [SliverConstraints.remainingPaintExtent] in the current viewport.
+  ///
+  /// This value does not affect how the next sliver is positioned. In other
+  /// words, if this value was 100 and [layoutExtent] was 0, typical slivers
+  /// placed after it would end up drawing in the same 100 pixel space while
+  /// painting.
+  ///
+  /// This must be between zero and [SliverConstraints.remainingPaintExtent].
+  ///
+  /// This value is typically 0 when outside of the viewport and grows or
+  /// shrinks from 0 or to 0 as the sliver is being scrolled into and out of the
+  /// viewport unless the sliver wants to achieve a special effect and paint
+  /// even when scrolled away.
+  ///
+  /// This contributes to the calculation for the next sliver's
+  /// [SliverConstraints.overlap].
+  final double paintExtent;
+
+  /// The distance from the first visible part of this sliver to the first
+  /// visible part of the next sliver, assuming the next sliver's
+  /// [SliverConstraints.scrollOffset] is zero.
+  ///
+  /// This must be between zero and [paintExtent]. It defaults to [paintExtent].
+  ///
+  /// This value is typically 0 when outside of the viewport and grows or
+  /// shrinks from 0 or to 0 as the sliver is being scrolled into and out of the
+  /// viewport unless the sliver wants to achieve a special effect and push
+  /// down the layout start position of subsequent slivers before the sliver is
+  /// even scrolled into the viewport.
+  final double layoutExtent;
+
+  /// The (estimated) total paint extent that this sliver would be able to
+  /// provide if the [SliverConstraints.remainingPaintExtent] was infinite.
+  ///
+  /// This is used by viewports that implement shrink-wrapping.
+  ///
+  /// By definition, this cannot be less than [paintExtent].
+  final double maxPaintExtent;
+
+  /// The maximum extent by which this sliver can reduce the area in which
+  /// content can scroll if the sliver were pinned at the edge.
+  ///
+  /// Slivers that never get pinned at the edge, should return zero.
+  ///
+  /// A pinned app bar is an example for a sliver that would use this setting:
+  /// When the app bar is pinned to the top, the area in which content can
+  /// actually scroll is reduced by the height of the app bar.
+  final double maxScrollObstructionExtent;
+
+  /// The distance from where this sliver started painting to the bottom of
+  /// where it should accept hits.
+  ///
+  /// This must be between zero and [paintExtent]. It defaults to [paintExtent].
+  final double hitTestExtent;
+
+  /// Whether this sliver should be painted.
+  ///
+  /// By default, this is true if [paintExtent] is greater than zero, and
+  /// false if [paintExtent] is zero.
+  final bool visible;
+
+  /// Whether this sliver has visual overflow.
+  ///
+  /// By default, this is false, which means the viewport does not need to clip
+  /// its children. If any slivers have visual overflow, the viewport will apply
+  /// a clip to its children.
+  final bool hasVisualOverflow;
+
+  /// If this is non-zero after [RenderSliver.performLayout] returns, the scroll
+  /// offset will be adjusted by the parent and then the entire layout of the
+  /// parent will be rerun.
+  ///
+  /// When the value is non-zero, the [RenderSliver] does not need to compute
+  /// the rest of the values when constructing the [SliverGeometry] or call
+  /// [RenderObject.layout] on its children since [RenderSliver.performLayout]
+  /// will be called again on this sliver in the same frame after the
+  /// [SliverConstraints.scrollOffset] correction has been applied, when the
+  /// proper [SliverGeometry] and layout of its children can be computed.
+  ///
+  /// If the parent is also a [RenderSliver], it must propagate this value
+  /// in its own [RenderSliver.geometry] property until a viewport which adjusts
+  /// its offset based on this value.
+  final double? scrollOffsetCorrection;
+
+  /// How many pixels the sliver has consumed in the
+  /// [SliverConstraints.remainingCacheExtent].
+  ///
+  /// This value should be equal to or larger than the [layoutExtent] because
+  /// the sliver always consumes at least the [layoutExtent] from the
+  /// [SliverConstraints.remainingCacheExtent] and possibly more if it falls
+  /// into the cache area of the viewport.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderViewport.cacheExtent] for a description of a viewport's cache area.
+  final double cacheExtent;
+
+  /// Asserts that this geometry is internally consistent.
+  ///
+  /// Does nothing if asserts are disabled. Always returns true.
+  bool debugAssertIsValid({
+    InformationCollector? informationCollector,
+  }) {
+    assert(() {
+      void verify(bool check, String summary, {List<DiagnosticsNode>? details}) {
+        if (check)
+          return;
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('${objectRuntimeType(this, 'SliverGeometry')} is not valid: $summary'),
+          ...?details,
+          if (informationCollector != null)
+            ...informationCollector(),
+        ]);
+      }
+
+      verify(scrollExtent != null, 'The "scrollExtent" is null.');
+      verify(scrollExtent >= 0.0, 'The "scrollExtent" is negative.');
+      verify(paintExtent != null, 'The "paintExtent" is null.');
+      verify(paintExtent >= 0.0, 'The "paintExtent" is negative.');
+      verify(paintOrigin != null, 'The "paintOrigin" is null.');
+      verify(layoutExtent != null, 'The "layoutExtent" is null.');
+      verify(layoutExtent >= 0.0, 'The "layoutExtent" is negative.');
+      verify(cacheExtent >= 0.0, 'The "cacheExtent" is negative.');
+      if (layoutExtent > paintExtent) {
+        verify(false,
+          'The "layoutExtent" exceeds the "paintExtent".',
+          details: _debugCompareFloats('paintExtent', paintExtent, 'layoutExtent', layoutExtent),
+        );
+      }
+      verify(maxPaintExtent != null, 'The "maxPaintExtent" is null.');
+      // If the paintExtent is slightly more than the maxPaintExtent, but the difference is still less
+      // than precisionErrorTolerance, we will not throw the assert below.
+      if (paintExtent - maxPaintExtent > precisionErrorTolerance) {
+        verify(false,
+          'The "maxPaintExtent" is less than the "paintExtent".',
+          details:
+            _debugCompareFloats('maxPaintExtent', maxPaintExtent, 'paintExtent', paintExtent)
+              ..add(ErrorDescription("By definition, a sliver can't paint more than the maximum that it can paint!")),
+        );
+      }
+      verify(hitTestExtent != null, 'The "hitTestExtent" is null.');
+      verify(hitTestExtent >= 0.0, 'The "hitTestExtent" is negative.');
+      verify(visible != null, 'The "visible" property is null.');
+      verify(hasVisualOverflow != null, 'The "hasVisualOverflow" is null.');
+      verify(scrollOffsetCorrection != 0.0, 'The "scrollOffsetCorrection" is zero.');
+      return true;
+    }());
+    return true;
+  }
+
+  @override
+  String toStringShort() => objectRuntimeType(this, 'SliverGeometry');
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('scrollExtent', scrollExtent));
+    if (paintExtent > 0.0) {
+      properties.add(DoubleProperty('paintExtent', paintExtent, unit : visible ? null : ' but not painting'));
+    } else if (paintExtent == 0.0) {
+      if (visible) {
+        properties.add(DoubleProperty('paintExtent', paintExtent, unit: visible ? null : ' but visible'));
+      }
+      properties.add(FlagProperty('visible', value: visible, ifFalse: 'hidden'));
+    } else {
+      // Negative paintExtent!
+      properties.add(DoubleProperty('paintExtent', paintExtent, tooltip: '!'));
+    }
+    properties.add(DoubleProperty('paintOrigin', paintOrigin, defaultValue: 0.0));
+    properties.add(DoubleProperty('layoutExtent', layoutExtent, defaultValue: paintExtent));
+    properties.add(DoubleProperty('maxPaintExtent', maxPaintExtent));
+    properties.add(DoubleProperty('hitTestExtent', hitTestExtent, defaultValue: paintExtent));
+    properties.add(DiagnosticsProperty<bool>('hasVisualOverflow', hasVisualOverflow, defaultValue: false));
+    properties.add(DoubleProperty('scrollOffsetCorrection', scrollOffsetCorrection, defaultValue: null));
+    properties.add(DoubleProperty('cacheExtent', cacheExtent, defaultValue: 0.0));
+  }
+}
+
+/// Method signature for hit testing a [RenderSliver].
+///
+/// Used by [SliverHitTestResult.addWithAxisOffset] to hit test [RenderSliver]
+/// children.
+///
+/// See also:
+///
+///  * [RenderSliver.hitTest], which documents more details around hit testing
+///    [RenderSliver]s.
+typedef SliverHitTest = bool Function(SliverHitTestResult result, { required double mainAxisPosition, required double crossAxisPosition });
+
+/// The result of performing a hit test on [RenderSliver]s.
+///
+/// An instance of this class is provided to [RenderSliver.hitTest] to record
+/// the result of the hit test.
+class SliverHitTestResult extends HitTestResult {
+  /// Creates an empty hit test result for hit testing on [RenderSliver].
+  SliverHitTestResult() : super();
+
+  /// Wraps `result` to create a [HitTestResult] that implements the
+  /// [SliverHitTestResult] protocol for hit testing on [RenderSliver]s.
+  ///
+  /// This method is used by [RenderObject]s that adapt between the
+  /// [RenderSliver]-world and the non-[RenderSliver]-world to convert a
+  /// (subtype of) [HitTestResult] to a [SliverHitTestResult] for hit testing on
+  /// [RenderSliver]s.
+  ///
+  /// The [HitTestEntry] instances added to the returned [SliverHitTestResult]
+  /// are also added to the wrapped `result` (both share the same underlying
+  /// data structure to store [HitTestEntry] instances).
+  ///
+  /// See also:
+  ///
+  ///  * [HitTestResult.wrap], which turns a [SliverHitTestResult] back into a
+  ///    generic [HitTestResult].
+  ///  * [BoxHitTestResult.wrap], which turns a [SliverHitTestResult] into a
+  ///    [BoxHitTestResult] for hit testing on [RenderBox] children.
+  SliverHitTestResult.wrap(HitTestResult result) : super.wrap(result);
+
+  /// Transforms `mainAxisPosition` and `crossAxisPosition` to the local
+  /// coordinate system of a child for hit-testing the child.
+  ///
+  /// The actual hit testing of the child needs to be implemented in the
+  /// provided `hitTest` callback, which is invoked with the transformed
+  /// `position` as argument.
+  ///
+  /// For the transform `mainAxisOffset` is subtracted from `mainAxisPosition`
+  /// and `crossAxisOffset` is subtracted from `crossAxisPosition`.
+  ///
+  /// The `paintOffset` describes how the paint position of a point painted at
+  /// the provided `mainAxisPosition` and `crossAxisPosition` would change after
+  /// `mainAxisOffset` and `crossAxisOffset` have been applied. This
+  /// `paintOffset` is used to properly convert [PointerEvent]s to the local
+  /// coordinate system of the event receiver.
+  ///
+  /// The `paintOffset` may be null if `mainAxisOffset` and `crossAxisOffset` are
+  /// both zero.
+  ///
+  /// The function returns the return value of `hitTest`.
+  bool addWithAxisOffset({
+    required Offset? paintOffset,
+    required double mainAxisOffset,
+    required double crossAxisOffset,
+    required double mainAxisPosition,
+    required double crossAxisPosition,
+    required SliverHitTest hitTest,
+  }) {
+    assert(mainAxisOffset != null);
+    assert(crossAxisOffset != null);
+    assert(mainAxisPosition != null);
+    assert(crossAxisPosition != null);
+    assert(hitTest != null);
+    if (paintOffset != null) {
+      pushOffset(-paintOffset);
+    }
+    final bool isHit = hitTest(
+      this,
+      mainAxisPosition: mainAxisPosition - mainAxisOffset,
+      crossAxisPosition: crossAxisPosition - crossAxisOffset,
+    );
+    if (paintOffset != null) {
+      popTransform();
+    }
+    return isHit;
+  }
+}
+
+/// A hit test entry used by [RenderSliver].
+///
+/// The coordinate system used by this hit test entry is relative to the
+/// [AxisDirection] of the target sliver.
+class SliverHitTestEntry extends HitTestEntry {
+  /// Creates a sliver hit test entry.
+  ///
+  /// The [mainAxisPosition] and [crossAxisPosition] arguments must not be null.
+  SliverHitTestEntry(
+    RenderSliver target, {
+    required this.mainAxisPosition,
+    required this.crossAxisPosition,
+  }) : assert(mainAxisPosition != null),
+       assert(crossAxisPosition != null),
+       super(target);
+
+  @override
+  RenderSliver get target => super.target as RenderSliver;
+
+  /// The distance in the [AxisDirection] from the edge of the sliver's painted
+  /// area (as given by the [SliverConstraints.scrollOffset]) to the hit point.
+  /// This can be an unusual direction, for example in the [AxisDirection.up]
+  /// case this is a distance from the _bottom_ of the sliver's painted area.
+  final double mainAxisPosition;
+
+  /// The distance to the hit point in the axis opposite the
+  /// [SliverConstraints.axis].
+  ///
+  /// If the cross axis is horizontal (i.e. the
+  /// [SliverConstraints.axisDirection] is either [AxisDirection.down] or
+  /// [AxisDirection.up]), then the `crossAxisPosition` is a distance from the
+  /// left edge of the sliver. If the cross axis is vertical (i.e. the
+  /// [SliverConstraints.axisDirection] is either [AxisDirection.right] or
+  /// [AxisDirection.left]), then the `crossAxisPosition` is a distance from the
+  /// top edge of the sliver.
+  ///
+  /// This is always a distance from the left or top of the parent, never a
+  /// distance from the right or bottom.
+  final double crossAxisPosition;
+
+  @override
+  String toString() => '${target.runtimeType}@(mainAxis: $mainAxisPosition, crossAxis: $crossAxisPosition)';
+}
+
+/// Parent data structure used by parents of slivers that position their
+/// children using layout offsets.
+///
+/// This data structure is optimized for fast layout. It is best used by parents
+/// that expect to have many children whose relative positions don't change even
+/// when the scroll offset does.
+class SliverLogicalParentData extends ParentData {
+  /// The position of the child relative to the zero scroll offset.
+  ///
+  /// The number of pixels from from the zero scroll offset of the parent sliver
+  /// (the line at which its [SliverConstraints.scrollOffset] is zero) to the
+  /// side of the child closest to that offset. A [layoutOffset] can be null
+  /// when it cannot be determined. The value will be set after layout.
+  ///
+  /// In a typical list, this does not change as the parent is scrolled.
+  ///
+  /// Defaults to null.
+  double? layoutOffset;
+
+  @override
+  String toString() => 'layoutOffset=${layoutOffset == null ? 'None': layoutOffset!.toStringAsFixed(1)}';
+}
+
+/// Parent data for slivers that have multiple children and that position their
+/// children using layout offsets.
+class SliverLogicalContainerParentData extends SliverLogicalParentData with ContainerParentDataMixin<RenderSliver> { }
+
+/// Parent data structure used by parents of slivers that position their
+/// children using absolute coordinates.
+///
+/// For example, used by [RenderViewport].
+///
+/// This data structure is optimized for fast painting, at the cost of requiring
+/// additional work during layout when the children change their offsets. It is
+/// best used by parents that expect to have few children, especially if those
+/// children will themselves be very tall relative to the parent.
+class SliverPhysicalParentData extends ParentData {
+  /// The position of the child relative to the parent.
+  ///
+  /// This is the distance from the top left visible corner of the parent to the
+  /// top left visible corner of the sliver.
+  Offset paintOffset = Offset.zero;
+
+  /// Apply the [paintOffset] to the given [transform].
+  ///
+  /// Used to implement [RenderObject.applyPaintTransform] by slivers that use
+  /// [SliverPhysicalParentData].
+  void applyPaintTransform(Matrix4 transform) {
+    // Hit test logic relies on this always providing an invertible matrix.
+    transform.translate(paintOffset.dx, paintOffset.dy);
+  }
+
+  @override
+  String toString() => 'paintOffset=$paintOffset';
+}
+
+/// Parent data for slivers that have multiple children and that position their
+/// children using absolute coordinates.
+class SliverPhysicalContainerParentData extends SliverPhysicalParentData with ContainerParentDataMixin<RenderSliver> { }
+
+List<DiagnosticsNode> _debugCompareFloats(String labelA, double valueA, String labelB, double valueB) {
+  return <DiagnosticsNode>[
+    if (valueA.toStringAsFixed(1) != valueB.toStringAsFixed(1))
+      ErrorDescription(
+        'The $labelA is ${valueA.toStringAsFixed(1)}, but '
+        'the $labelB is ${valueB.toStringAsFixed(1)}.'
+      )
+    else ...<DiagnosticsNode>[
+      ErrorDescription('The $labelA is $valueA, but the $labelB is $valueB.'),
+      ErrorHint(
+        'Maybe you have fallen prey to floating point rounding errors, and should explicitly '
+        'apply the min() or max() functions, or the clamp() method, to the $labelB?'
+      ),
+    ],
+  ];
+}
+
+/// Base class for the render objects that implement scroll effects in viewports.
+///
+/// A [RenderViewport] has a list of child slivers. Each sliver — literally a
+/// slice of the viewport's contents — is laid out in turn, covering the
+/// viewport in the process. (Every sliver is laid out each time, including
+/// those that have zero extent because they are "scrolled off" or are beyond
+/// the end of the viewport.)
+///
+/// Slivers participate in the _sliver protocol_, wherein during [layout] each
+/// sliver receives a [SliverConstraints] object and computes a corresponding
+/// [SliverGeometry] that describes where it fits in the viewport. This is
+/// analogous to the box protocol used by [RenderBox], which gets a
+/// [BoxConstraints] as input and computes a [Size].
+///
+/// Slivers have a leading edge, which is where the position described by
+/// [SliverConstraints.scrollOffset] for this sliver begins. Slivers have
+/// several dimensions, the primary of which is [SliverGeometry.paintExtent],
+/// which describes the extent of the sliver along the main axis, starting from
+/// the leading edge, reaching either the end of the viewport or the end of the
+/// sliver, whichever comes first.
+///
+/// Slivers can change dimensions based on the changing constraints in a
+/// non-linear fashion, to achieve various scroll effects. For example, the
+/// various [RenderSliverPersistentHeader] subclasses, on which [SliverAppBar]
+/// is based, achieve effects such as staying visible despite the scroll offset,
+/// or reappearing at different offsets based on the user's scroll direction
+/// ([SliverConstraints.userScrollDirection]).
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=Mz3kHQxBjGg}
+///
+/// ## Writing a RenderSliver subclass
+///
+/// Slivers can have sliver children, or children from another coordinate
+/// system, typically box children. (For details on the box protocol, see
+/// [RenderBox].) Slivers can also have different child models, typically having
+/// either one child, or a list of children.
+///
+/// ### Examples of slivers
+///
+/// A good example of a sliver with a single child that is also itself a sliver
+/// is [RenderSliverPadding], which indents its child. A sliver-to-sliver render
+/// object such as this must construct a [SliverConstraints] object for its
+/// child, then must take its child's [SliverGeometry] and use it to form its
+/// own [geometry].
+///
+/// The other common kind of one-child sliver is a sliver that has a single
+/// [RenderBox] child. An example of that would be [RenderSliverToBoxAdapter],
+/// which lays out a single box and sizes itself around the box. Such a sliver
+/// must use its [SliverConstraints] to create a [BoxConstraints] for the
+/// child, lay the child out (using the child's [layout] method), and then use
+/// the child's [RenderBox.size] to generate the sliver's [SliverGeometry].
+///
+/// The most common kind of sliver though is one with multiple children. The
+/// most straight-forward example of this is [RenderSliverList], which arranges
+/// its children one after the other in the main axis direction. As with the
+/// one-box-child sliver case, it uses its [constraints] to create a
+/// [BoxConstraints] for the children, and then it uses the aggregate
+/// information from all its children to generate its [geometry]. Unlike the
+/// one-child cases, however, it is judicious in which children it actually lays
+/// out (and later paints). If the scroll offset is 1000 pixels, and it
+/// previously determined that the first three children are each 400 pixels
+/// tall, then it will skip the first two and start the layout with its third
+/// child.
+///
+/// ### Layout
+///
+/// As they are laid out, slivers decide their [geometry], which includes their
+/// size ([SliverGeometry.paintExtent]) and the position of the next sliver
+/// ([SliverGeometry.layoutExtent]), as well as the position of each of their
+/// children, based on the input [constraints] from the viewport such as the
+/// scroll offset ([SliverConstraints.scrollOffset]).
+///
+/// For example, a sliver that just paints a box 100 pixels high would say its
+/// [SliverGeometry.paintExtent] was 100 pixels when the scroll offset was zero,
+/// but would say its [SliverGeometry.paintExtent] was 25 pixels when the scroll
+/// offset was 75 pixels, and would say it was zero when the scroll offset was
+/// 100 pixels or more. (This is assuming that
+/// [SliverConstraints.remainingPaintExtent] was more than 100 pixels.)
+///
+/// The various dimensions that are provided as input to this system are in the
+/// [constraints]. They are described in detail in the documentation for the
+/// [SliverConstraints] class.
+///
+/// The [performLayout] function must take these [constraints] and create a
+/// [SliverGeometry] object that it must then assign to the [geometry] property.
+/// The different dimensions of the geometry that can be configured are
+/// described in detail in the documentation for the [SliverGeometry] class.
+///
+/// ### Painting
+///
+/// In addition to implementing layout, a sliver must also implement painting.
+/// This is achieved by overriding the [paint] method.
+///
+/// The [paint] method is called with an [Offset] from the [Canvas] origin to
+/// the top-left corner of the sliver, _regardless of the axis direction_.
+///
+/// Subclasses should also override [applyPaintTransform] to provide the
+/// [Matrix4] describing the position of each child relative to the sliver.
+/// (This is used by, among other things, the accessibility layer, to determine
+/// the bounds of the child.)
+///
+/// ### Hit testing
+///
+/// To implement hit testing, either override the [hitTestSelf] and
+/// [hitTestChildren] methods, or, for more complex cases, instead override the
+/// [hitTest] method directly.
+///
+/// To actually react to pointer events, the [handleEvent] method may be
+/// implemented. By default it does nothing. (Typically gestures are handled by
+/// widgets in the box protocol, not by slivers directly.)
+///
+/// ### Helper methods
+///
+/// There are a number of methods that a sliver should implement which will make
+/// the other methods easier to implement. Each method listed below has detailed
+/// documentation. In addition, the [RenderSliverHelpers] class can be used to
+/// mix in some helpful methods.
+///
+/// #### childScrollOffset
+///
+/// If the subclass positions children anywhere other than at scroll offset
+/// zero, it should override [childScrollOffset]. For example,
+/// [RenderSliverList] and [RenderSliverGrid] override this method, but
+/// [RenderSliverToBoxAdapter] does not.
+///
+/// This is used by, among other things, [Scrollable.ensureVisible].
+///
+/// #### childMainAxisPosition
+///
+/// Subclasses should implement [childMainAxisPosition] to describe where their
+/// children are positioned.
+///
+/// #### childCrossAxisPosition
+///
+/// If the subclass positions children in the cross-axis at a position other
+/// than zero, then it should override [childCrossAxisPosition]. For example
+/// [RenderSliverGrid] overrides this method.
+abstract class RenderSliver extends RenderObject {
+  // layout input
+  @override
+  SliverConstraints get constraints => super.constraints as SliverConstraints;
+
+  /// The amount of space this sliver occupies.
+  ///
+  /// This value is stale whenever this object is marked as needing layout.
+  /// During [performLayout], do not read the [geometry] of a child unless you
+  /// pass true for parentUsesSize when calling the child's [layout] function.
+  ///
+  /// The geometry of a sliver should be set only during the sliver's
+  /// [performLayout] or [performResize] functions. If you wish to change the
+  /// geometry of a sliver outside of those functions, call [markNeedsLayout]
+  /// instead to schedule a layout of the sliver.
+  SliverGeometry? get geometry => _geometry;
+  SliverGeometry? _geometry;
+  set geometry(SliverGeometry? value) {
+    assert(!(debugDoingThisResize && debugDoingThisLayout));
+    assert(sizedByParent || !debugDoingThisResize);
+    assert(() {
+      if ((sizedByParent && debugDoingThisResize) ||
+          (!sizedByParent && debugDoingThisLayout))
+        return true;
+      assert(!debugDoingThisResize);
+      DiagnosticsNode? contract, violation, hint;
+      if (debugDoingThisLayout) {
+        assert(sizedByParent);
+        violation = ErrorDescription('It appears that the geometry setter was called from performLayout().');
+      } else {
+        violation = ErrorDescription('The geometry setter was called from outside layout (neither performResize() nor performLayout() were being run for this object).');
+        if (owner != null && owner!.debugDoingLayout)
+          hint = ErrorDescription('Only the object itself can set its geometry. It is a contract violation for other objects to set it.');
+      }
+      if (sizedByParent)
+        contract = ErrorDescription('Because this RenderSliver has sizedByParent set to true, it must set its geometry in performResize().');
+      else
+        contract = ErrorDescription('Because this RenderSliver has sizedByParent set to false, it must set its geometry in performLayout().');
+
+      final List<DiagnosticsNode> information = <DiagnosticsNode>[
+        ErrorSummary('RenderSliver geometry setter called incorrectly.'),
+        violation,
+        if (hint != null) hint,
+        contract,
+        describeForError('The RenderSliver in question is'),
+      ];
+      throw FlutterError.fromParts(information);
+    }());
+    _geometry = value;
+  }
+
+  @override
+  Rect get semanticBounds => paintBounds;
+
+  @override
+  Rect get paintBounds {
+    assert(constraints.axis != null);
+    switch (constraints.axis) {
+      case Axis.horizontal:
+        return Rect.fromLTWH(
+          0.0, 0.0,
+          geometry!.paintExtent,
+          constraints.crossAxisExtent,
+        );
+      case Axis.vertical:
+        return Rect.fromLTWH(
+          0.0, 0.0,
+          constraints.crossAxisExtent,
+          geometry!.paintExtent,
+        );
+    }
+  }
+
+  @override
+  void debugResetSize() { }
+
+  @override
+  void debugAssertDoesMeetConstraints() {
+    assert(geometry!.debugAssertIsValid(
+      informationCollector: () sync* {
+        yield describeForError('The RenderSliver that returned the offending geometry was');
+      }
+    ));
+    assert(() {
+      if (geometry!.paintOrigin + geometry!.paintExtent > constraints.remainingPaintExtent) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('SliverGeometry has a paintOffset that exceeds the remainingPaintExtent from the constraints.'),
+          describeForError('The render object whose geometry violates the constraints is the following'),
+          ..._debugCompareFloats(
+            'remainingPaintExtent', constraints.remainingPaintExtent,
+            'paintOrigin + paintExtent', geometry!.paintOrigin + geometry!.paintExtent,
+          ),
+          ErrorDescription(
+            'The paintOrigin and paintExtent must cause the child sliver to paint '
+            'within the viewport, and so cannot exceed the remainingPaintExtent.',
+          ),
+        ]);
+      }
+      return true;
+    }());
+  }
+
+  @override
+  void performResize() {
+    assert(false);
+  }
+
+  /// For a center sliver, the distance before the absolute zero scroll offset
+  /// that this sliver can cover.
+  ///
+  /// For example, if an [AxisDirection.down] viewport with an
+  /// [RenderViewport.anchor] of 0.5 has a single sliver with a height of 100.0
+  /// and its [centerOffsetAdjustment] returns 50.0, then the sliver will be
+  /// centered in the viewport when the scroll offset is 0.0.
+  ///
+  /// The distance here is in the opposite direction of the
+  /// [RenderViewport.axisDirection], so values will typically be positive.
+  double get centerOffsetAdjustment => 0.0;
+
+  /// Determines the set of render objects located at the given position.
+  ///
+  /// Returns true if the given point is contained in this render object or one
+  /// of its descendants. Adds any render objects that contain the point to the
+  /// given hit test result.
+  ///
+  /// The caller is responsible for providing the position in the local
+  /// coordinate space of the callee. The callee is responsible for checking
+  /// whether the given position is within its bounds.
+  ///
+  /// Hit testing requires layout to be up-to-date but does not require painting
+  /// to be up-to-date. That means a render object can rely upon [performLayout]
+  /// having been called in [hitTest] but cannot rely upon [paint] having been
+  /// called. For example, a render object might be a child of a [RenderOpacity]
+  /// object, which calls [hitTest] on its children when its opacity is zero
+  /// even through it does not [paint] its children.
+  ///
+  /// ## Coordinates for RenderSliver objects
+  ///
+  /// The `mainAxisPosition` is the distance in the [AxisDirection] (after
+  /// applying the [GrowthDirection]) from the edge of the sliver's painted
+  /// area. This can be an unusual direction, for example in the
+  /// [AxisDirection.up] case this is a distance from the _bottom_ of the
+  /// sliver's painted area.
+  ///
+  /// The `crossAxisPosition` is the distance in the other axis. If the cross
+  /// axis is horizontal (i.e. the [SliverConstraints.axisDirection] is either
+  /// [AxisDirection.down] or [AxisDirection.up]), then the `crossAxisPosition`
+  /// is a distance from the left edge of the sliver. If the cross axis is
+  /// vertical (i.e. the [SliverConstraints.axisDirection] is either
+  /// [AxisDirection.right] or [AxisDirection.left]), then the
+  /// `crossAxisPosition` is a distance from the top edge of the sliver.
+  ///
+  /// ## Implementing hit testing for slivers
+  ///
+  /// The most straight-forward way to implement hit testing for a new sliver
+  /// render object is to override its [hitTestSelf] and [hitTestChildren]
+  /// methods.
+  bool hitTest(SliverHitTestResult result, { required double mainAxisPosition, required double crossAxisPosition }) {
+    if (mainAxisPosition >= 0.0 && mainAxisPosition < geometry!.hitTestExtent &&
+        crossAxisPosition >= 0.0 && crossAxisPosition < constraints.crossAxisExtent) {
+      if (hitTestChildren(result, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition) ||
+          hitTestSelf(mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition)) {
+        result.add(SliverHitTestEntry(
+          this,
+          mainAxisPosition: mainAxisPosition,
+          crossAxisPosition: crossAxisPosition,
+        ));
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /// Override this method if this render object can be hit even if its
+  /// children were not hit.
+  ///
+  /// Used by [hitTest]. If you override [hitTest] and do not call this
+  /// function, then you don't need to implement this function.
+  ///
+  /// For a discussion of the semantics of the arguments, see [hitTest].
+  @protected
+  bool hitTestSelf({ required double mainAxisPosition, required double crossAxisPosition }) => false;
+
+  /// Override this method to check whether any children are located at the
+  /// given position.
+  ///
+  /// Typically children should be hit-tested in reverse paint order so that
+  /// hit tests at locations where children overlap hit the child that is
+  /// visually "on top" (i.e., paints later).
+  ///
+  /// Used by [hitTest]. If you override [hitTest] and do not call this
+  /// function, then you don't need to implement this function.
+  ///
+  /// For a discussion of the semantics of the arguments, see [hitTest].
+  @protected
+  bool hitTestChildren(SliverHitTestResult result, { required double mainAxisPosition, required double crossAxisPosition }) => false;
+
+  /// Computes the portion of the region from `from` to `to` that is visible,
+  /// assuming that only the region from the [SliverConstraints.scrollOffset]
+  /// that is [SliverConstraints.remainingPaintExtent] high is visible, and that
+  /// the relationship between scroll offsets and paint offsets is linear.
+  ///
+  /// For example, if the constraints have a scroll offset of 100 and a
+  /// remaining paint extent of 100, and the arguments to this method describe
+  /// the region 50..150, then the returned value would be 50 (from scroll
+  /// offset 100 to scroll offset 150).
+  ///
+  /// This method is not useful if there is not a 1:1 relationship between
+  /// consumed scroll offset and consumed paint extent. For example, if the
+  /// sliver always paints the same amount but consumes a scroll offset extent
+  /// that is proportional to the [SliverConstraints.scrollOffset], then this
+  /// function's results will not be consistent.
+  // This could be a static method but isn't, because it would be less convenient
+  // to call it from subclasses if it was.
+  double calculatePaintOffset(SliverConstraints constraints, { required double from, required double to }) {
+    assert(from <= to);
+    final double a = constraints.scrollOffset;
+    final double b = constraints.scrollOffset + constraints.remainingPaintExtent;
+    // the clamp on the next line is to avoid floating point rounding errors
+    return (to.clamp(a, b) - from.clamp(a, b)).clamp(0.0, constraints.remainingPaintExtent);
+  }
+
+  /// Computes the portion of the region from `from` to `to` that is within
+  /// the cache extent of the viewport, assuming that only the region from the
+  /// [SliverConstraints.cacheOrigin] that is
+  /// [SliverConstraints.remainingCacheExtent] high is visible, and that
+  /// the relationship between scroll offsets and paint offsets is linear.
+  ///
+  /// This method is not useful if there is not a 1:1 relationship between
+  /// consumed scroll offset and consumed cache extent.
+  double calculateCacheOffset(SliverConstraints constraints, { required double from, required double to }) {
+    assert(from <= to);
+    final double a = constraints.scrollOffset + constraints.cacheOrigin;
+    final double b = constraints.scrollOffset + constraints.remainingCacheExtent;
+    // the clamp on the next line is to avoid floating point rounding errors
+    return (to.clamp(a, b) - from.clamp(a, b)).clamp(0.0, constraints.remainingCacheExtent);
+  }
+
+  /// Returns the distance from the leading _visible_ edge of the sliver to the
+  /// side of the given child closest to that edge.
+  ///
+  /// For example, if the [constraints] describe this sliver as having an axis
+  /// direction of [AxisDirection.down], then this is the distance from the top
+  /// of the visible portion of the sliver to the top of the child. On the other
+  /// hand, if the [constraints] describe this sliver as having an axis
+  /// direction of [AxisDirection.up], then this is the distance from the bottom
+  /// of the visible portion of the sliver to the bottom of the child. In both
+  /// cases, this is the direction of increasing
+  /// [SliverConstraints.scrollOffset] and
+  /// [SliverLogicalParentData.layoutOffset].
+  ///
+  /// For children that are [RenderSliver]s, the leading edge of the _child_
+  /// will be the leading _visible_ edge of the child, not the part of the child
+  /// that would locally be a scroll offset 0.0. For children that are not
+  /// [RenderSliver]s, for example a [RenderBox] child, it's the actual distance
+  /// to the edge of the box, since those boxes do not know how to handle being
+  /// scrolled.
+  ///
+  /// This method differs from [childScrollOffset] in that
+  /// [childMainAxisPosition] gives the distance from the leading _visible_ edge
+  /// of the sliver whereas [childScrollOffset] gives the distance from the
+  /// sliver's zero scroll offset.
+  ///
+  /// Calling this for a child that is not visible is not valid.
+  @protected
+  double childMainAxisPosition(covariant RenderObject child) {
+    assert(() {
+      throw FlutterError('${objectRuntimeType(this, 'RenderSliver')} does not implement childPosition.');
+    }());
+    return 0.0;
+  }
+
+  /// Returns the distance along the cross axis from the zero of the cross axis
+  /// in this sliver's [paint] coordinate space to the nearest side of the given
+  /// child.
+  ///
+  /// For example, if the [constraints] describe this sliver as having an axis
+  /// direction of [AxisDirection.down], then this is the distance from the left
+  /// of the sliver to the left of the child. Similarly, if the [constraints]
+  /// describe this sliver as having an axis direction of [AxisDirection.up],
+  /// then this is value is the same. If the axis direction is
+  /// [AxisDirection.left] or [AxisDirection.right], then it is the distance
+  /// from the top of the sliver to the top of the child.
+  ///
+  /// Calling this for a child that is not visible is not valid.
+  @protected
+  double childCrossAxisPosition(covariant RenderObject child) => 0.0;
+
+  /// Returns the scroll offset for the leading edge of the given child.
+  ///
+  /// The `child` must be a child of this sliver.
+  ///
+  /// This method differs from [childMainAxisPosition] in that
+  /// [childMainAxisPosition] gives the distance from the leading _visible_ edge
+  /// of the sliver whereas [childScrollOffset] gives the distance from sliver's
+  /// zero scroll offset.
+  double? childScrollOffset(covariant RenderObject child) {
+    assert(child.parent == this);
+    return 0.0;
+  }
+
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) {
+    assert(() {
+      throw FlutterError('${objectRuntimeType(this, 'RenderSliver')} does not implement applyPaintTransform.');
+    }());
+  }
+
+  /// This returns a [Size] with dimensions relative to the leading edge of the
+  /// sliver, specifically the same offset that is given to the [paint] method.
+  /// This means that the dimensions may be negative.
+  ///
+  /// This is only valid after [layout] has completed.
+  ///
+  /// See also:
+  ///
+  ///  * [getAbsoluteSize], which returns absolute size.
+  @protected
+  Size getAbsoluteSizeRelativeToOrigin() {
+    assert(geometry != null);
+    assert(!debugNeedsLayout);
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+        return Size(constraints.crossAxisExtent, -geometry!.paintExtent);
+      case AxisDirection.right:
+        return Size(geometry!.paintExtent, constraints.crossAxisExtent);
+      case AxisDirection.down:
+        return Size(constraints.crossAxisExtent, geometry!.paintExtent);
+      case AxisDirection.left:
+        return Size(-geometry!.paintExtent, constraints.crossAxisExtent);
+    }
+  }
+
+  /// This returns the absolute [Size] of the sliver.
+  ///
+  /// The dimensions are always positive and calling this is only valid after
+  /// [layout] has completed.
+  ///
+  /// See also:
+  ///
+  ///  * [getAbsoluteSizeRelativeToOrigin], which returns the size relative to
+  ///    the leading edge of the sliver.
+  @protected
+  Size getAbsoluteSize() {
+    assert(geometry != null);
+    assert(!debugNeedsLayout);
+    switch (constraints.axisDirection) {
+      case AxisDirection.up:
+      case AxisDirection.down:
+        return Size(constraints.crossAxisExtent, geometry!.paintExtent);
+      case AxisDirection.right:
+      case AxisDirection.left:
+        return Size(geometry!.paintExtent, constraints.crossAxisExtent);
+    }
+  }
+
+  void _debugDrawArrow(Canvas canvas, Paint paint, Offset p0, Offset p1, GrowthDirection direction) {
+    assert(() {
+      if (p0 == p1)
+        return true;
+      assert(p0.dx == p1.dx || p0.dy == p1.dy); // must be axis-aligned
+      final double d = (p1 - p0).distance * 0.2;
+      final Offset temp;
+      double dx1, dx2, dy1, dy2;
+      switch (direction) {
+        case GrowthDirection.forward:
+          dx1 = dx2 = dy1 = dy2 = d;
+          break;
+        case GrowthDirection.reverse:
+          temp = p0;
+          p0 = p1;
+          p1 = temp;
+          dx1 = dx2 = dy1 = dy2 = -d;
+          break;
+      }
+      if (p0.dx == p1.dx) {
+        dx2 = -dx2;
+      } else {
+        dy2 = -dy2;
+      }
+      canvas.drawPath(
+        Path()
+          ..moveTo(p0.dx, p0.dy)
+          ..lineTo(p1.dx, p1.dy)
+          ..moveTo(p1.dx - dx1, p1.dy - dy1)
+          ..lineTo(p1.dx, p1.dy)
+          ..lineTo(p1.dx - dx2, p1.dy - dy2),
+        paint,
+      );
+      return true;
+    }());
+  }
+
+  @override
+  void debugPaint(PaintingContext context, Offset offset) {
+    assert(() {
+      if (debugPaintSizeEnabled) {
+        final double strokeWidth = math.min(4.0, geometry!.paintExtent / 30.0);
+        final Paint paint = Paint()
+          ..color = const Color(0xFF33CC33)
+          ..strokeWidth = strokeWidth
+          ..style = PaintingStyle.stroke
+          ..maskFilter = MaskFilter.blur(BlurStyle.solid, strokeWidth);
+        final double arrowExtent = geometry!.paintExtent;
+        final double padding = math.max(2.0, strokeWidth);
+        final Canvas canvas = context.canvas;
+        canvas.drawCircle(
+          offset.translate(padding, padding),
+          padding * 0.5,
+          paint,
+        );
+        switch (constraints.axis) {
+          case Axis.vertical:
+            canvas.drawLine(
+              offset,
+              offset.translate(constraints.crossAxisExtent, 0.0),
+              paint,
+            );
+            _debugDrawArrow(
+              canvas,
+              paint,
+              offset.translate(constraints.crossAxisExtent * 1.0 / 4.0, padding),
+              offset.translate(constraints.crossAxisExtent * 1.0 / 4.0, arrowExtent - padding),
+              constraints.normalizedGrowthDirection,
+            );
+            _debugDrawArrow(
+              canvas,
+              paint,
+              offset.translate(constraints.crossAxisExtent * 3.0 / 4.0, padding),
+              offset.translate(constraints.crossAxisExtent * 3.0 / 4.0, arrowExtent - padding),
+              constraints.normalizedGrowthDirection,
+            );
+            break;
+          case Axis.horizontal:
+            canvas.drawLine(
+              offset,
+              offset.translate(0.0, constraints.crossAxisExtent),
+              paint,
+            );
+            _debugDrawArrow(
+              canvas,
+              paint,
+              offset.translate(padding, constraints.crossAxisExtent * 1.0 / 4.0),
+              offset.translate(arrowExtent - padding, constraints.crossAxisExtent * 1.0 / 4.0),
+              constraints.normalizedGrowthDirection,
+            );
+            _debugDrawArrow(
+              canvas,
+              paint,
+              offset.translate(padding, constraints.crossAxisExtent * 3.0 / 4.0),
+              offset.translate(arrowExtent - padding, constraints.crossAxisExtent * 3.0 / 4.0),
+              constraints.normalizedGrowthDirection,
+            );
+            break;
+        }
+      }
+      return true;
+    }());
+  }
+
+  // This override exists only to change the type of the second argument.
+  @override
+  void handleEvent(PointerEvent event, SliverHitTestEntry entry) { }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<SliverGeometry>('geometry', geometry));
+  }
+}
+
+/// Mixin for [RenderSliver] subclasses that provides some utility functions.
+abstract class RenderSliverHelpers implements RenderSliver {
+
+  bool _getRightWayUp(SliverConstraints constraints) {
+    assert(constraints != null);
+    assert(constraints.axisDirection != null);
+    bool rightWayUp;
+    switch (constraints.axisDirection) {
+      case AxisDirection.up:
+      case AxisDirection.left:
+        rightWayUp = false;
+        break;
+      case AxisDirection.down:
+      case AxisDirection.right:
+        rightWayUp = true;
+        break;
+    }
+    assert(constraints.growthDirection != null);
+    switch (constraints.growthDirection) {
+      case GrowthDirection.forward:
+        break;
+      case GrowthDirection.reverse:
+        rightWayUp = !rightWayUp;
+        break;
+    }
+    assert(rightWayUp != null);
+    return rightWayUp;
+  }
+
+  /// Utility function for [hitTestChildren] for use when the children are
+  /// [RenderBox] widgets.
+  ///
+  /// This function takes care of converting the position from the sliver
+  /// coordinate system to the Cartesian coordinate system used by [RenderBox].
+  ///
+  /// This function relies on [childMainAxisPosition] to determine the position of
+  /// child in question.
+  ///
+  /// Calling this for a child that is not visible is not valid.
+  @protected
+  bool hitTestBoxChild(BoxHitTestResult result, RenderBox child, { required double mainAxisPosition, required double crossAxisPosition }) {
+    final bool rightWayUp = _getRightWayUp(constraints);
+    double delta = childMainAxisPosition(child);
+    final double crossAxisDelta = childCrossAxisPosition(child);
+    double absolutePosition = mainAxisPosition - delta;
+    final double absoluteCrossAxisPosition = crossAxisPosition - crossAxisDelta;
+    Offset paintOffset, transformedPosition;
+    assert(constraints.axis != null);
+    switch (constraints.axis) {
+      case Axis.horizontal:
+        if (!rightWayUp) {
+          absolutePosition = child.size.width - absolutePosition;
+          delta = geometry!.paintExtent - child.size.width - delta;
+        }
+        paintOffset = Offset(delta, crossAxisDelta);
+        transformedPosition = Offset(absolutePosition, absoluteCrossAxisPosition);
+        break;
+      case Axis.vertical:
+        if (!rightWayUp) {
+          absolutePosition = child.size.height - absolutePosition;
+          delta = geometry!.paintExtent - child.size.height - delta;
+        }
+        paintOffset = Offset(crossAxisDelta, delta);
+        transformedPosition = Offset(absoluteCrossAxisPosition, absolutePosition);
+        break;
+    }
+    assert(paintOffset != null);
+    assert(transformedPosition != null);
+    return result.addWithOutOfBandPosition(
+      paintOffset: paintOffset,
+      hitTest: (BoxHitTestResult result) {
+        return child.hitTest(result, position: transformedPosition);
+      },
+    );
+  }
+
+  /// Utility function for [applyPaintTransform] for use when the children are
+  /// [RenderBox] widgets.
+  ///
+  /// This function turns the value returned by [childMainAxisPosition] and
+  /// [childCrossAxisPosition]for the child in question into a translation that
+  /// it then applies to the given matrix.
+  ///
+  /// Calling this for a child that is not visible is not valid.
+  @protected
+  void applyPaintTransformForBoxChild(RenderBox child, Matrix4 transform) {
+    final bool rightWayUp = _getRightWayUp(constraints);
+    double delta = childMainAxisPosition(child);
+    final double crossAxisDelta = childCrossAxisPosition(child);
+    assert(constraints.axis != null);
+    switch (constraints.axis) {
+      case Axis.horizontal:
+        if (!rightWayUp)
+          delta = geometry!.paintExtent - child.size.width - delta;
+        transform.translate(delta, crossAxisDelta);
+        break;
+      case Axis.vertical:
+        if (!rightWayUp)
+          delta = geometry!.paintExtent - child.size.height - delta;
+        transform.translate(crossAxisDelta, delta);
+        break;
+    }
+  }
+}
+
+// ADAPTER FOR RENDER BOXES INSIDE SLIVERS
+// Transitions from the RenderSliver world to the RenderBox world.
+
+/// An abstract class for [RenderSliver]s that contains a single [RenderBox].
+///
+/// See also:
+///
+///  * [RenderSliver], which explains more about the Sliver protocol.
+///  * [RenderBox], which explains more about the Box protocol.
+///  * [RenderSliverToBoxAdapter], which extends this class to size the child
+///    according to its preferred size.
+///  * [RenderSliverFillRemaining], which extends this class to size the child
+///    to fill the remaining space in the viewport.
+abstract class RenderSliverSingleBoxAdapter extends RenderSliver with RenderObjectWithChildMixin<RenderBox>, RenderSliverHelpers {
+  /// Creates a [RenderSliver] that wraps a [RenderBox].
+  RenderSliverSingleBoxAdapter({
+    RenderBox? child,
+  }) {
+    this.child = child;
+  }
+
+  @override
+  void setupParentData(RenderObject child) {
+    if (child.parentData is! SliverPhysicalParentData)
+      child.parentData = SliverPhysicalParentData();
+  }
+
+  /// Sets the [SliverPhysicalParentData.paintOffset] for the given child
+  /// according to the [SliverConstraints.axisDirection] and
+  /// [SliverConstraints.growthDirection] and the given geometry.
+  @protected
+  void setChildParentData(RenderObject child, SliverConstraints constraints, SliverGeometry geometry) {
+    final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
+    assert(constraints.axisDirection != null);
+    assert(constraints.growthDirection != null);
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+        childParentData.paintOffset = Offset(0.0, -(geometry.scrollExtent - (geometry.paintExtent + constraints.scrollOffset)));
+        break;
+      case AxisDirection.right:
+        childParentData.paintOffset = Offset(-constraints.scrollOffset, 0.0);
+        break;
+      case AxisDirection.down:
+        childParentData.paintOffset = Offset(0.0, -constraints.scrollOffset);
+        break;
+      case AxisDirection.left:
+        childParentData.paintOffset = Offset(-(geometry.scrollExtent - (geometry.paintExtent + constraints.scrollOffset)), 0.0);
+        break;
+    }
+    assert(childParentData.paintOffset != null);
+  }
+
+  @override
+  bool hitTestChildren(SliverHitTestResult result, { required double mainAxisPosition, required double crossAxisPosition }) {
+    assert(geometry!.hitTestExtent > 0.0);
+    if (child != null)
+      return hitTestBoxChild(BoxHitTestResult.wrap(result), child!, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition);
+    return false;
+  }
+
+  @override
+  double childMainAxisPosition(RenderBox child) {
+    return -constraints.scrollOffset;
+  }
+
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) {
+    assert(child != null);
+    assert(child == this.child);
+    final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
+    childParentData.applyPaintTransform(transform);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null && geometry!.visible) {
+      final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
+      context.paintChild(child!, offset + childParentData.paintOffset);
+    }
+  }
+}
+
+/// A [RenderSliver] that contains a single [RenderBox].
+///
+/// The child will not be laid out if it is not visible. It is sized according
+/// to the child's preferences in the main axis, and with a tight constraint
+/// forcing it to the dimensions of the viewport in the cross axis.
+///
+/// See also:
+///
+///  * [RenderSliver], which explains more about the Sliver protocol.
+///  * [RenderBox], which explains more about the Box protocol.
+///  * [RenderViewport], which allows [RenderSliver] objects to be placed inside
+///    a [RenderBox] (the opposite of this class).
+class RenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter {
+  /// Creates a [RenderSliver] that wraps a [RenderBox].
+  RenderSliverToBoxAdapter({
+    RenderBox? child,
+  }) : super(child: child);
+
+  @override
+  void performLayout() {
+    if (child == null) {
+      geometry = SliverGeometry.zero;
+      return;
+    }
+    final SliverConstraints constraints = this.constraints;
+    child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);
+    final double childExtent;
+    switch (constraints.axis) {
+      case Axis.horizontal:
+        childExtent = child!.size.width;
+        break;
+      case Axis.vertical:
+        childExtent = child!.size.height;
+        break;
+    }
+    assert(childExtent != null);
+    final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent);
+    final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: childExtent);
+
+    assert(paintedChildSize.isFinite);
+    assert(paintedChildSize >= 0.0);
+    geometry = SliverGeometry(
+      scrollExtent: childExtent,
+      paintExtent: paintedChildSize,
+      cacheExtent: cacheExtent,
+      maxPaintExtent: childExtent,
+      hitTestExtent: paintedChildSize,
+      hasVisualOverflow: childExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
+    );
+    setChildParentData(child!, constraints, geometry!);
+  }
+}
diff --git a/lib/src/rendering/sliver_fill.dart b/lib/src/rendering/sliver_fill.dart
new file mode 100644
index 0000000..84da72b
--- /dev/null
+++ b/lib/src/rendering/sliver_fill.dart
@@ -0,0 +1,259 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'box.dart';
+import 'object.dart';
+import 'sliver.dart';
+import 'sliver_fixed_extent_list.dart';
+import 'sliver_multi_box_adaptor.dart';
+
+/// A sliver that contains multiple box children that each fill the viewport.
+///
+/// [RenderSliverFillViewport] places its children in a linear array along the
+/// main axis. Each child is sized to fill the viewport, both in the main and
+/// cross axis. A [viewportFraction] factor can be provided to size the children
+/// to a multiple of the viewport's main axis dimension (typically a fraction
+/// less than 1.0).
+///
+/// See also:
+///
+///  * [RenderSliverFillRemaining], which sizes the children based on the
+///    remaining space rather than the viewport itself.
+///  * [RenderSliverFixedExtentList], which has a configurable [itemExtent].
+///  * [RenderSliverList], which does not require its children to have the same
+///    extent in the main axis.
+class RenderSliverFillViewport extends RenderSliverFixedExtentBoxAdaptor {
+  /// Creates a sliver that contains multiple box children that each fill the
+  /// viewport.
+  ///
+  /// The [childManager] argument must not be null.
+  RenderSliverFillViewport({
+    required RenderSliverBoxChildManager childManager,
+    double viewportFraction = 1.0,
+  }) : assert(viewportFraction != null),
+       assert(viewportFraction > 0.0),
+       _viewportFraction = viewportFraction,
+       super(childManager: childManager);
+
+  @override
+  double get itemExtent => constraints.viewportMainAxisExtent * viewportFraction;
+
+  /// The fraction of the viewport that each child should fill in the main axis.
+  ///
+  /// If this fraction is less than 1.0, more than one child will be visible at
+  /// once. If this fraction is greater than 1.0, each child will be larger than
+  /// the viewport in the main axis.
+  double get viewportFraction => _viewportFraction;
+  double _viewportFraction;
+  set viewportFraction(double value) {
+    assert(value != null);
+    if (_viewportFraction == value)
+      return;
+    _viewportFraction = value;
+    markNeedsLayout();
+  }
+}
+
+/// A sliver that contains a single box child that contains a scrollable and
+/// fills the viewport.
+///
+/// [RenderSliverFillRemainingWithScrollable] sizes its child to fill the
+/// viewport in the cross axis and to fill the remaining space in the viewport
+/// in the main axis.
+///
+/// Typically this will be the last sliver in a viewport, since (by definition)
+/// there is never any room for anything beyond this sliver.
+///
+/// See also:
+///
+///  * [NestedScrollView], which uses this sliver for the inner scrollable.
+///  * [RenderSliverFillRemaining], which lays out its
+///    non-scrollable child slightly different than this widget.
+///  * [RenderSliverFillRemainingAndOverscroll], which incorporates the
+///    overscroll into the remaining space to fill.
+///  * [RenderSliverFillViewport], which sizes its children based on the
+///    size of the viewport, regardless of what else is in the scroll view.
+///  * [RenderSliverList], which shows a list of variable-sized children in a
+///    viewport.
+class RenderSliverFillRemainingWithScrollable extends RenderSliverSingleBoxAdapter {
+  /// Creates a [RenderSliver] that wraps a scrollable [RenderBox] which is
+  /// sized to fit the remaining space in the viewport.
+  RenderSliverFillRemainingWithScrollable({ RenderBox? child }) : super(child: child);
+
+  @override
+  void performLayout() {
+    final SliverConstraints constraints = this.constraints;
+    final double extent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);
+
+    if (child != null)
+      child!.layout(constraints.asBoxConstraints(
+        minExtent: extent,
+        maxExtent: extent,
+      ));
+
+    final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
+    assert(paintedChildSize.isFinite);
+    assert(paintedChildSize >= 0.0);
+    geometry = SliverGeometry(
+      scrollExtent: constraints.viewportMainAxisExtent,
+      paintExtent: paintedChildSize,
+      maxPaintExtent: paintedChildSize,
+      hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
+    );
+    if (child != null)
+      setChildParentData(child!, constraints, geometry!);
+  }
+}
+
+/// A sliver that contains a single box child that is non-scrollable and fills
+/// the remaining space in the viewport.
+///
+/// [RenderSliverFillRemaining] sizes its child to fill the
+/// viewport in the cross axis and to fill the remaining space in the viewport
+/// in the main axis.
+///
+/// Typically this will be the last sliver in a viewport, since (by definition)
+/// there is never any room for anything beyond this sliver.
+///
+/// See also:
+///
+///  * [RenderSliverFillRemainingWithScrollable], which lays out its scrollable
+///    child slightly different than this widget.
+///  * [RenderSliverFillRemainingAndOverscroll], which incorporates the
+///    overscroll into the remaining space to fill.
+///  * [RenderSliverFillViewport], which sizes its children based on the
+///    size of the viewport, regardless of what else is in the scroll view.
+///  * [RenderSliverList], which shows a list of variable-sized children in a
+///    viewport.
+class RenderSliverFillRemaining extends RenderSliverSingleBoxAdapter {
+  /// Creates a [RenderSliver] that wraps a non-scrollable [RenderBox] which is
+  /// sized to fit the remaining space in the viewport.
+  RenderSliverFillRemaining({ RenderBox? child }) : super(child: child);
+
+  @override
+  void performLayout() {
+    final SliverConstraints constraints = this.constraints;
+    // The remaining space in the viewportMainAxisExtent. Can be <= 0 if we have
+    // scrolled beyond the extent of the screen.
+    double extent = constraints.viewportMainAxisExtent - constraints.precedingScrollExtent;
+
+    if (child != null) {
+      final double childExtent;
+      switch (constraints.axis) {
+        case Axis.horizontal:
+          childExtent = child!.getMaxIntrinsicWidth(constraints.crossAxisExtent);
+          break;
+        case Axis.vertical:
+          childExtent = child!.getMaxIntrinsicHeight(constraints.crossAxisExtent);
+          break;
+      }
+
+      // If the childExtent is greater than the computed extent, we want to use
+      // that instead of potentially cutting off the child. This allows us to
+      // safely specify a maxExtent.
+      extent = math.max(extent, childExtent);
+      child!.layout(constraints.asBoxConstraints(
+        minExtent: extent,
+        maxExtent: extent,
+      ));
+    }
+
+    assert(extent.isFinite,
+      'The calculated extent for the child of SliverFillRemaining is not finite. '
+      'This can happen if the child is a scrollable, in which case, the '
+      'hasScrollBody property of SliverFillRemaining should not be set to '
+      'false.',
+    );
+    final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
+    assert(paintedChildSize.isFinite);
+    assert(paintedChildSize >= 0.0);
+    geometry = SliverGeometry(
+      scrollExtent: extent,
+      paintExtent: paintedChildSize,
+      maxPaintExtent: paintedChildSize,
+      hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
+    );
+    if (child != null)
+      setChildParentData(child!, constraints, geometry!);
+  }
+}
+
+/// A sliver that contains a single box child that is non-scrollable and fills
+/// the remaining space in the viewport including any overscrolled area.
+///
+/// [RenderSliverFillRemainingAndOverscroll] sizes its child to fill the
+/// viewport in the cross axis and to fill the remaining space in the viewport
+/// in the main axis with the overscroll area included.
+///
+/// Typically this will be the last sliver in a viewport, since (by definition)
+/// there is never any room for anything beyond this sliver.
+///
+/// See also:
+///
+///  * [RenderSliverFillRemainingWithScrollable], which lays out its scrollable
+///    child without overscroll.
+///  * [RenderSliverFillRemaining], which lays out its
+///    non-scrollable child without overscroll.
+///  * [RenderSliverFillViewport], which sizes its children based on the
+///    size of the viewport, regardless of what else is in the scroll view.
+///  * [RenderSliverList], which shows a list of variable-sized children in a
+///    viewport.
+class RenderSliverFillRemainingAndOverscroll extends RenderSliverSingleBoxAdapter {
+  /// Creates a [RenderSliver] that wraps a non-scrollable [RenderBox] which is
+  /// sized to fit the remaining space plus any overscroll in the viewport.
+  RenderSliverFillRemainingAndOverscroll({ RenderBox? child }) : super(child: child);
+
+  @override
+  void performLayout() {
+    final SliverConstraints constraints = this.constraints;
+    // The remaining space in the viewportMainAxisExtent. Can be <= 0 if we have
+    // scrolled beyond the extent of the screen.
+    double extent = constraints.viewportMainAxisExtent - constraints.precedingScrollExtent;
+    // The maxExtent includes any overscrolled area. Can be < 0 if we have
+    // overscroll in the opposite direction, away from the end of the list.
+    double maxExtent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);
+
+    if (child != null) {
+      final double childExtent;
+      switch (constraints.axis) {
+        case Axis.horizontal:
+          childExtent = child!.getMaxIntrinsicWidth(constraints.crossAxisExtent);
+          break;
+        case Axis.vertical:
+          childExtent = child!.getMaxIntrinsicHeight(constraints.crossAxisExtent);
+          break;
+      }
+
+      // If the childExtent is greater than the computed extent, we want to use
+      // that instead of potentially cutting off the child. This allows us to
+      // safely specify a maxExtent.
+      extent = math.max(extent, childExtent);
+      // The extent could be larger than the maxExtent due to a larger child
+      // size or overscrolling at the top of the scrollable (rather than at the
+      // end where this sliver is).
+      maxExtent = math.max(extent, maxExtent);
+      child!.layout(constraints.asBoxConstraints(minExtent: extent, maxExtent: maxExtent));
+    }
+
+    assert(extent.isFinite,
+      'The calculated extent for the child of SliverFillRemaining is not finite. '
+      'This can happen if the child is a scrollable, in which case, the '
+      'hasScrollBody property of SliverFillRemaining should not be set to '
+      'false.',
+    );
+    final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
+    assert(paintedChildSize.isFinite);
+    assert(paintedChildSize >= 0.0);
+    geometry = SliverGeometry(
+      scrollExtent: extent,
+      paintExtent: math.min(maxExtent, constraints.remainingPaintExtent),
+      maxPaintExtent: maxExtent,
+      hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
+    );
+    if (child != null)
+      setChildParentData(child!, constraints, geometry!);
+  }
+}
diff --git a/lib/src/rendering/sliver_fixed_extent_list.dart b/lib/src/rendering/sliver_fixed_extent_list.dart
new file mode 100644
index 0000000..acb0b25
--- /dev/null
+++ b/lib/src/rendering/sliver_fixed_extent_list.dart
@@ -0,0 +1,364 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+
+import 'box.dart';
+import 'sliver.dart';
+import 'sliver_multi_box_adaptor.dart';
+
+/// A sliver that contains multiple box children that have the same extent in
+/// the main axis.
+///
+/// [RenderSliverFixedExtentBoxAdaptor] places its children in a linear array
+/// along the main axis. Each child is forced to have the [itemExtent] in the
+/// main axis and the [SliverConstraints.crossAxisExtent] in the cross axis.
+///
+/// Subclasses should override [itemExtent] to control the size of the children
+/// in the main axis. For a concrete subclass with a configurable [itemExtent],
+/// see [RenderSliverFixedExtentList].
+///
+/// [RenderSliverFixedExtentBoxAdaptor] is more efficient than
+/// [RenderSliverList] because [RenderSliverFixedExtentBoxAdaptor] does not need
+/// to perform layout on its children to obtain their extent in the main axis.
+///
+/// See also:
+///
+///  * [RenderSliverFixedExtentList], which has a configurable [itemExtent].
+///  * [RenderSliverFillViewport], which determines the [itemExtent] based on
+///    [SliverConstraints.viewportMainAxisExtent].
+///  * [RenderSliverFillRemaining], which determines the [itemExtent] based on
+///    [SliverConstraints.remainingPaintExtent].
+///  * [RenderSliverList], which does not require its children to have the same
+///    extent in the main axis.
+abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAdaptor {
+  /// Creates a sliver that contains multiple box children that have the same
+  /// extent in the main axis.
+  ///
+  /// The [childManager] argument must not be null.
+  RenderSliverFixedExtentBoxAdaptor({
+    required RenderSliverBoxChildManager childManager,
+  }) : super(childManager: childManager);
+
+  /// The main-axis extent of each item.
+  double get itemExtent;
+
+  /// The layout offset for the child with the given index.
+  ///
+  /// This function is given the [itemExtent] as an argument to avoid
+  /// recomputing [itemExtent] repeatedly during layout.
+  ///
+  /// By default, places the children in order, without gaps, starting from
+  /// layout offset zero.
+  @protected
+  double indexToLayoutOffset(double itemExtent, int index) => itemExtent * index;
+
+  /// The minimum child index that is visible at the given scroll offset.
+  ///
+  /// This function is given the [itemExtent] as an argument to avoid
+  /// recomputing [itemExtent] repeatedly during layout.
+  ///
+  /// By default, returns a value consistent with the children being placed in
+  /// order, without gaps, starting from layout offset zero.
+  @protected
+  int getMinChildIndexForScrollOffset(double scrollOffset, double itemExtent) {
+    if (itemExtent > 0.0) {
+      final double actual = scrollOffset / itemExtent;
+      final int round = actual.round();
+      if ((actual - round).abs() < precisionErrorTolerance) {
+        return round;
+      }
+      return actual.floor();
+    }
+    return 0;
+  }
+
+  /// The maximum child index that is visible at the given scroll offset.
+  ///
+  /// This function is given the [itemExtent] as an argument to avoid
+  /// recomputing [itemExtent] repeatedly during layout.
+  ///
+  /// By default, returns a value consistent with the children being placed in
+  /// order, without gaps, starting from layout offset zero.
+  @protected
+  int getMaxChildIndexForScrollOffset(double scrollOffset, double itemExtent) {
+    if (itemExtent > 0.0) {
+      final double actual = scrollOffset / itemExtent - 1;
+      final int round = actual.round();
+      if (_isWithinPrecisionErrorTolerance(actual, round)) {
+        return math.max(0, round);
+      }
+      return math.max(0, actual.ceil());
+    }
+    return 0;
+  }
+
+  /// Called to estimate the total scrollable extents of this object.
+  ///
+  /// Must return the total distance from the start of the child with the
+  /// earliest possible index to the end of the child with the last possible
+  /// index.
+  ///
+  /// By default, defers to [RenderSliverBoxChildManager.estimateMaxScrollOffset].
+  ///
+  /// See also:
+  ///
+  ///  * [computeMaxScrollOffset], which is similar but must provide a precise
+  ///    value.
+  @protected
+  double estimateMaxScrollOffset(
+    SliverConstraints constraints, {
+    int? firstIndex,
+    int? lastIndex,
+    double? leadingScrollOffset,
+    double? trailingScrollOffset,
+  }) {
+    return childManager.estimateMaxScrollOffset(
+      constraints,
+      firstIndex: firstIndex,
+      lastIndex: lastIndex,
+      leadingScrollOffset: leadingScrollOffset,
+      trailingScrollOffset: trailingScrollOffset,
+    );
+  }
+
+  /// Called to obtain a precise measure of the total scrollable extents of this
+  /// object.
+  ///
+  /// Must return the precise total distance from the start of the child with
+  /// the earliest possible index to the end of the child with the last possible
+  /// index.
+  ///
+  /// This is used when no child is available for the index corresponding to the
+  /// current scroll offset, to determine the precise dimensions of the sliver.
+  /// It must return a precise value. It will not be called if the
+  /// [childManager] returns an infinite number of children for positive
+  /// indices.
+  ///
+  /// By default, multiplies the [itemExtent] by the number of children reported
+  /// by [RenderSliverBoxChildManager.childCount].
+  ///
+  /// See also:
+  ///
+  ///  * [estimateMaxScrollOffset], which is similar but may provide inaccurate
+  ///    values.
+  @protected
+  double computeMaxScrollOffset(SliverConstraints constraints, double itemExtent) {
+    return childManager.childCount * itemExtent;
+  }
+
+  int _calculateLeadingGarbage(int firstIndex) {
+    RenderBox? walker = firstChild;
+    int leadingGarbage = 0;
+    while(walker != null && indexOf(walker) < firstIndex){
+      leadingGarbage += 1;
+      walker = childAfter(walker);
+    }
+    return leadingGarbage;
+  }
+
+  int _calculateTrailingGarbage(int targetLastIndex) {
+    RenderBox? walker = lastChild;
+    int trailingGarbage = 0;
+    while(walker != null && indexOf(walker) > targetLastIndex){
+      trailingGarbage += 1;
+      walker = childBefore(walker);
+    }
+    return trailingGarbage;
+  }
+
+  @override
+  void performLayout() {
+    final SliverConstraints constraints = this.constraints;
+    childManager.didStartLayout();
+    childManager.setDidUnderflow(false);
+
+    final double itemExtent = this.itemExtent;
+
+    final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
+    assert(scrollOffset >= 0.0);
+    final double remainingExtent = constraints.remainingCacheExtent;
+    assert(remainingExtent >= 0.0);
+    final double targetEndScrollOffset = scrollOffset + remainingExtent;
+
+    final BoxConstraints childConstraints = constraints.asBoxConstraints(
+      minExtent: itemExtent,
+      maxExtent: itemExtent,
+    );
+
+    final int firstIndex = getMinChildIndexForScrollOffset(scrollOffset, itemExtent);
+    final int? targetLastIndex = targetEndScrollOffset.isFinite ?
+        getMaxChildIndexForScrollOffset(targetEndScrollOffset, itemExtent) : null;
+
+    if (firstChild != null) {
+      final int leadingGarbage = _calculateLeadingGarbage(firstIndex);
+      final int trailingGarbage = _calculateTrailingGarbage(targetLastIndex!);
+      collectGarbage(leadingGarbage, trailingGarbage);
+    } else {
+      collectGarbage(0, 0);
+    }
+
+    if (firstChild == null) {
+      if (!addInitialChild(index: firstIndex, layoutOffset: indexToLayoutOffset(itemExtent, firstIndex))) {
+        // There are either no children, or we are past the end of all our children.
+        final double max;
+        if (firstIndex <= 0) {
+          max = 0.0;
+        } else {
+          max = computeMaxScrollOffset(constraints, itemExtent);
+        }
+        geometry = SliverGeometry(
+          scrollExtent: max,
+          maxPaintExtent: max,
+        );
+        childManager.didFinishLayout();
+        return;
+      }
+    }
+
+    RenderBox? trailingChildWithLayout;
+
+    for (int index = indexOf(firstChild!) - 1; index >= firstIndex; --index) {
+      final RenderBox? child = insertAndLayoutLeadingChild(childConstraints);
+      if (child == null) {
+        // Items before the previously first child are no longer present.
+        // Reset the scroll offset to offset all items prior and up to the
+        // missing item. Let parent re-layout everything.
+        geometry = SliverGeometry(scrollOffsetCorrection: index * itemExtent);
+        return;
+      }
+      final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
+      childParentData.layoutOffset = indexToLayoutOffset(itemExtent, index);
+      assert(childParentData.index == index);
+      trailingChildWithLayout ??= child;
+    }
+
+    if (trailingChildWithLayout == null) {
+      firstChild!.layout(childConstraints);
+      final SliverMultiBoxAdaptorParentData childParentData = firstChild!.parentData! as SliverMultiBoxAdaptorParentData;
+      childParentData.layoutOffset = indexToLayoutOffset(itemExtent, firstIndex);
+      trailingChildWithLayout = firstChild;
+    }
+
+    double estimatedMaxScrollOffset = double.infinity;
+    for (int index = indexOf(trailingChildWithLayout!) + 1; targetLastIndex == null || index <= targetLastIndex; ++index) {
+      RenderBox? child = childAfter(trailingChildWithLayout!);
+      if (child == null || indexOf(child) != index) {
+        child = insertAndLayoutChild(childConstraints, after: trailingChildWithLayout);
+        if (child == null) {
+          // We have run out of children.
+          estimatedMaxScrollOffset = index * itemExtent;
+          break;
+        }
+      } else {
+        child.layout(childConstraints);
+      }
+      trailingChildWithLayout = child;
+      assert(child != null);
+      final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
+      assert(childParentData.index == index);
+      childParentData.layoutOffset = indexToLayoutOffset(itemExtent, childParentData.index!);
+    }
+
+    final int lastIndex = indexOf(lastChild!);
+    final double leadingScrollOffset = indexToLayoutOffset(itemExtent, firstIndex);
+    final double trailingScrollOffset = indexToLayoutOffset(itemExtent, lastIndex + 1);
+
+    assert(firstIndex == 0 || childScrollOffset(firstChild!)! - scrollOffset <= precisionErrorTolerance);
+    assert(debugAssertChildListIsNonEmptyAndContiguous());
+    assert(indexOf(firstChild!) == firstIndex);
+    assert(targetLastIndex == null || lastIndex <= targetLastIndex);
+
+    estimatedMaxScrollOffset = math.min(
+      estimatedMaxScrollOffset,
+      estimateMaxScrollOffset(
+        constraints,
+        firstIndex: firstIndex,
+        lastIndex: lastIndex,
+        leadingScrollOffset: leadingScrollOffset,
+        trailingScrollOffset: trailingScrollOffset,
+      ),
+    );
+
+    final double paintExtent = calculatePaintOffset(
+      constraints,
+      from: leadingScrollOffset,
+      to: trailingScrollOffset,
+    );
+
+    final double cacheExtent = calculateCacheOffset(
+      constraints,
+      from: leadingScrollOffset,
+      to: trailingScrollOffset,
+    );
+
+    final double targetEndScrollOffsetForPaint = constraints.scrollOffset + constraints.remainingPaintExtent;
+    final int? targetLastIndexForPaint = targetEndScrollOffsetForPaint.isFinite ?
+        getMaxChildIndexForScrollOffset(targetEndScrollOffsetForPaint, itemExtent) : null;
+    geometry = SliverGeometry(
+      scrollExtent: estimatedMaxScrollOffset,
+      paintExtent: paintExtent,
+      cacheExtent: cacheExtent,
+      maxPaintExtent: estimatedMaxScrollOffset,
+      // Conservative to avoid flickering away the clip during scroll.
+      hasVisualOverflow: (targetLastIndexForPaint != null && lastIndex >= targetLastIndexForPaint)
+        || constraints.scrollOffset > 0.0,
+    );
+
+    // We may have started the layout while scrolled to the end, which would not
+    // expose a new child.
+    if (estimatedMaxScrollOffset == trailingScrollOffset)
+      childManager.setDidUnderflow(true);
+    childManager.didFinishLayout();
+  }
+}
+
+/// A sliver that places multiple box children with the same main axis extent in
+/// a linear array.
+///
+/// [RenderSliverFixedExtentList] places its children in a linear array along
+/// the main axis starting at offset zero and without gaps. Each child is forced
+/// to have the [itemExtent] in the main axis and the
+/// [SliverConstraints.crossAxisExtent] in the cross axis.
+///
+/// [RenderSliverFixedExtentList] is more efficient than [RenderSliverList]
+/// because [RenderSliverFixedExtentList] does not need to perform layout on its
+/// children to obtain their extent in the main axis.
+///
+/// See also:
+///
+///  * [RenderSliverList], which does not require its children to have the same
+///    extent in the main axis.
+///  * [RenderSliverFillViewport], which determines the [itemExtent] based on
+///    [SliverConstraints.viewportMainAxisExtent].
+///  * [RenderSliverFillRemaining], which determines the [itemExtent] based on
+///    [SliverConstraints.remainingPaintExtent].
+class RenderSliverFixedExtentList extends RenderSliverFixedExtentBoxAdaptor {
+  /// Creates a sliver that contains multiple box children that have a given
+  /// extent in the main axis.
+  ///
+  /// The [childManager] argument must not be null.
+  RenderSliverFixedExtentList({
+    required RenderSliverBoxChildManager childManager,
+    required double itemExtent,
+  }) : _itemExtent = itemExtent,
+       super(childManager: childManager);
+
+  @override
+  double get itemExtent => _itemExtent;
+  double _itemExtent;
+  set itemExtent(double value) {
+    assert(value != null);
+    if (_itemExtent == value)
+      return;
+    _itemExtent = value;
+    markNeedsLayout();
+  }
+}
+
+bool _isWithinPrecisionErrorTolerance(double actual, int round) {
+  return (actual - round).abs() < precisionErrorTolerance;
+}
diff --git a/lib/src/rendering/sliver_grid.dart b/lib/src/rendering/sliver_grid.dart
new file mode 100644
index 0000000..0e0ceb0
--- /dev/null
+++ b/lib/src/rendering/sliver_grid.dart
@@ -0,0 +1,647 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+
+import 'box.dart';
+import 'object.dart';
+import 'sliver.dart';
+import 'sliver_multi_box_adaptor.dart';
+
+/// Describes the placement of a child in a [RenderSliverGrid].
+///
+/// See also:
+///
+///  * [SliverGridLayout], which represents the geometry of all the tiles in a
+///    grid.
+///  * [SliverGridLayout.getGeometryForChildIndex], which returns this object
+///    to describe the child's placement.
+///  * [RenderSliverGrid], which uses this class during its
+///    [RenderSliverGrid.performLayout] method.
+@immutable
+class SliverGridGeometry {
+  /// Creates an object that describes the placement of a child in a [RenderSliverGrid].
+  const SliverGridGeometry({
+    required this.scrollOffset,
+    required this.crossAxisOffset,
+    required this.mainAxisExtent,
+    required this.crossAxisExtent,
+  });
+
+  /// The scroll offset of the leading edge of the child relative to the leading
+  /// edge of the parent.
+  final double scrollOffset;
+
+  /// The offset of the child in the non-scrolling axis.
+  ///
+  /// If the scroll axis is vertical, this offset is from the left-most edge of
+  /// the parent to the left-most edge of the child. If the scroll axis is
+  /// horizontal, this offset is from the top-most edge of the parent to the
+  /// top-most edge of the child.
+  final double crossAxisOffset;
+
+  /// The extent of the child in the scrolling axis.
+  ///
+  /// If the scroll axis is vertical, this extent is the child's height. If the
+  /// scroll axis is horizontal, this extent is the child's width.
+  final double mainAxisExtent;
+
+  /// The extent of the child in the non-scrolling axis.
+  ///
+  /// If the scroll axis is vertical, this extent is the child's width. If the
+  /// scroll axis is horizontal, this extent is the child's height.
+  final double crossAxisExtent;
+
+  /// The scroll offset of the trailing edge of the child relative to the
+  /// leading edge of the parent.
+  double get trailingScrollOffset => scrollOffset + mainAxisExtent;
+
+  /// Returns a tight [BoxConstraints] that forces the child to have the
+  /// required size.
+  BoxConstraints getBoxConstraints(SliverConstraints constraints) {
+    return constraints.asBoxConstraints(
+      minExtent: mainAxisExtent,
+      maxExtent: mainAxisExtent,
+      crossAxisExtent: crossAxisExtent,
+    );
+  }
+
+  @override
+  String toString() {
+    final List<String> properties = <String>[
+      'scrollOffset: $scrollOffset',
+      'crossAxisOffset: $crossAxisOffset',
+      'mainAxisExtent: $mainAxisExtent',
+      'crossAxisExtent: $crossAxisExtent',
+    ];
+    return 'SliverGridGeometry(${properties.join(', ')})';
+  }
+}
+
+/// The size and position of all the tiles in a [RenderSliverGrid].
+///
+/// Rather that providing a grid with a [SliverGridLayout] directly, you instead
+/// provide the grid a [SliverGridDelegate], which can compute a
+/// [SliverGridLayout] given the current [SliverConstraints].
+///
+/// The tiles can be placed arbitrarily, but it is more efficient to place tiles
+/// in roughly in order by scroll offset because grids reify a contiguous
+/// sequence of children.
+///
+/// See also:
+///
+///  * [SliverGridRegularTileLayout], which represents a layout that uses
+///    equally sized and spaced tiles.
+///  * [SliverGridGeometry], which represents the size and position of a single
+///    tile in a grid.
+///  * [SliverGridDelegate.getLayout], which returns this object to describe the
+///    delegate's layout.
+///  * [RenderSliverGrid], which uses this class during its
+///    [RenderSliverGrid.performLayout] method.
+@immutable
+abstract class SliverGridLayout {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const SliverGridLayout();
+
+  /// The minimum child index that intersects with (or is after) this scroll offset.
+  int getMinChildIndexForScrollOffset(double scrollOffset);
+
+  /// The maximum child index that intersects with (or is before) this scroll offset.
+  int getMaxChildIndexForScrollOffset(double scrollOffset);
+
+  /// The size and position of the child with the given index.
+  SliverGridGeometry getGeometryForChildIndex(int index);
+
+  /// The scroll extent needed to fully display all the tiles if there are
+  /// `childCount` children in total.
+  ///
+  /// The child count will never be null.
+  double computeMaxScrollOffset(int childCount);
+}
+
+/// A [SliverGridLayout] that uses equally sized and spaced tiles.
+///
+/// Rather that providing a grid with a [SliverGridLayout] directly, you instead
+/// provide the grid a [SliverGridDelegate], which can compute a
+/// [SliverGridLayout] given the current [SliverConstraints].
+///
+/// This layout is used by [SliverGridDelegateWithFixedCrossAxisCount] and
+/// [SliverGridDelegateWithMaxCrossAxisExtent].
+///
+/// See also:
+///
+///  * [SliverGridDelegateWithFixedCrossAxisCount], which uses this layout.
+///  * [SliverGridDelegateWithMaxCrossAxisExtent], which uses this layout.
+///  * [SliverGridLayout], which represents an arbitrary tile layout.
+///  * [SliverGridGeometry], which represents the size and position of a single
+///    tile in a grid.
+///  * [SliverGridDelegate.getLayout], which returns this object to describe the
+///    delegate's layout.
+///  * [RenderSliverGrid], which uses this class during its
+///    [RenderSliverGrid.performLayout] method.
+class SliverGridRegularTileLayout extends SliverGridLayout {
+  /// Creates a layout that uses equally sized and spaced tiles.
+  ///
+  /// All of the arguments must not be null and must not be negative. The
+  /// `crossAxisCount` argument must be greater than zero.
+  const SliverGridRegularTileLayout({
+    required this.crossAxisCount,
+    required this.mainAxisStride,
+    required this.crossAxisStride,
+    required this.childMainAxisExtent,
+    required this.childCrossAxisExtent,
+    required this.reverseCrossAxis,
+  }) : assert(crossAxisCount != null && crossAxisCount > 0),
+       assert(mainAxisStride != null && mainAxisStride >= 0),
+       assert(crossAxisStride != null && crossAxisStride >= 0),
+       assert(childMainAxisExtent != null && childMainAxisExtent >= 0),
+       assert(childCrossAxisExtent != null && childCrossAxisExtent >= 0),
+       assert(reverseCrossAxis != null);
+
+  /// The number of children in the cross axis.
+  final int crossAxisCount;
+
+  /// The number of pixels from the leading edge of one tile to the leading edge
+  /// of the next tile in the main axis.
+  final double mainAxisStride;
+
+  /// The number of pixels from the leading edge of one tile to the leading edge
+  /// of the next tile in the cross axis.
+  final double crossAxisStride;
+
+  /// The number of pixels from the leading edge of one tile to the trailing
+  /// edge of the same tile in the main axis.
+  final double childMainAxisExtent;
+
+  /// The number of pixels from the leading edge of one tile to the trailing
+  /// edge of the same tile in the cross axis.
+  final double childCrossAxisExtent;
+
+  /// Whether the children should be placed in the opposite order of increasing
+  /// coordinates in the cross axis.
+  ///
+  /// For example, if the cross axis is horizontal, the children are placed from
+  /// left to right when [reverseCrossAxis] is false and from right to left when
+  /// [reverseCrossAxis] is true.
+  ///
+  /// Typically set to the return value of [axisDirectionIsReversed] applied to
+  /// the [SliverConstraints.crossAxisDirection].
+  final bool reverseCrossAxis;
+
+  @override
+  int getMinChildIndexForScrollOffset(double scrollOffset) {
+    return mainAxisStride > 0.0 ? crossAxisCount * (scrollOffset ~/ mainAxisStride) : 0;
+  }
+
+  @override
+  int getMaxChildIndexForScrollOffset(double scrollOffset) {
+    if (mainAxisStride > 0.0) {
+      final int mainAxisCount = (scrollOffset / mainAxisStride).ceil();
+      return math.max(0, crossAxisCount * mainAxisCount - 1);
+    }
+    return 0;
+  }
+
+  double _getOffsetFromStartInCrossAxis(double crossAxisStart) {
+    if (reverseCrossAxis)
+      return crossAxisCount * crossAxisStride - crossAxisStart - childCrossAxisExtent - (crossAxisStride - childCrossAxisExtent);
+    return crossAxisStart;
+  }
+
+  @override
+  SliverGridGeometry getGeometryForChildIndex(int index) {
+    final double crossAxisStart = (index % crossAxisCount) * crossAxisStride;
+    return SliverGridGeometry(
+      scrollOffset: (index ~/ crossAxisCount) * mainAxisStride,
+      crossAxisOffset: _getOffsetFromStartInCrossAxis(crossAxisStart),
+      mainAxisExtent: childMainAxisExtent,
+      crossAxisExtent: childCrossAxisExtent,
+    );
+  }
+
+  @override
+  double computeMaxScrollOffset(int childCount) {
+    assert(childCount != null);
+    final int mainAxisCount = ((childCount - 1) ~/ crossAxisCount) + 1;
+    final double mainAxisSpacing = mainAxisStride - childMainAxisExtent;
+    return mainAxisStride * mainAxisCount - mainAxisSpacing;
+  }
+}
+
+/// Controls the layout of tiles in a grid.
+///
+/// Given the current constraints on the grid, a [SliverGridDelegate] computes
+/// the layout for the tiles in the grid. The tiles can be placed arbitrarily,
+/// but it is more efficient to place tiles in roughly in order by scroll offset
+/// because grids reify a contiguous sequence of children.
+///
+/// See also:
+///
+///  * [SliverGridDelegateWithFixedCrossAxisCount], which creates a layout with
+///    a fixed number of tiles in the cross axis.
+///  * [SliverGridDelegateWithMaxCrossAxisExtent], which creates a layout with
+///    tiles that have a maximum cross-axis extent.
+///  * [GridView], which uses this delegate to control the layout of its tiles.
+///  * [SliverGrid], which uses this delegate to control the layout of its
+///    tiles.
+///  * [RenderSliverGrid], which uses this delegate to control the layout of its
+///    tiles.
+abstract class SliverGridDelegate {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const SliverGridDelegate();
+
+  /// Returns information about the size and position of the tiles in the grid.
+  SliverGridLayout getLayout(SliverConstraints constraints);
+
+  /// Override this method to return true when the children need to be
+  /// laid out.
+  ///
+  /// This should compare the fields of the current delegate and the given
+  /// `oldDelegate` and return true if the fields are such that the layout would
+  /// be different.
+  bool shouldRelayout(covariant SliverGridDelegate oldDelegate);
+}
+
+/// Creates grid layouts with a fixed number of tiles in the cross axis.
+///
+/// For example, if the grid is vertical, this delegate will create a layout
+/// with a fixed number of columns. If the grid is horizontal, this delegate
+/// will create a layout with a fixed number of rows.
+///
+/// This delegate creates grids with equally sized and spaced tiles.
+///
+/// See also:
+///
+///  * [SliverGridDelegateWithMaxCrossAxisExtent], which creates a layout with
+///    tiles that have a maximum cross-axis extent.
+///  * [SliverGridDelegate], which creates arbitrary layouts.
+///  * [GridView], which can use this delegate to control the layout of its
+///    tiles.
+///  * [SliverGrid], which can use this delegate to control the layout of its
+///    tiles.
+///  * [RenderSliverGrid], which can use this delegate to control the layout of
+///    its tiles.
+class SliverGridDelegateWithFixedCrossAxisCount extends SliverGridDelegate {
+  /// Creates a delegate that makes grid layouts with a fixed number of tiles in
+  /// the cross axis.
+  ///
+  /// All of the arguments must not be null. The `mainAxisSpacing` and
+  /// `crossAxisSpacing` arguments must not be negative. The `crossAxisCount`
+  /// and `childAspectRatio` arguments must be greater than zero.
+  const SliverGridDelegateWithFixedCrossAxisCount({
+    required this.crossAxisCount,
+    this.mainAxisSpacing = 0.0,
+    this.crossAxisSpacing = 0.0,
+    this.childAspectRatio = 1.0,
+  }) : assert(crossAxisCount != null && crossAxisCount > 0),
+       assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
+       assert(crossAxisSpacing != null && crossAxisSpacing >= 0),
+       assert(childAspectRatio != null && childAspectRatio > 0);
+
+  /// The number of children in the cross axis.
+  final int crossAxisCount;
+
+  /// The number of logical pixels between each child along the main axis.
+  final double mainAxisSpacing;
+
+  /// The number of logical pixels between each child along the cross axis.
+  final double crossAxisSpacing;
+
+  /// The ratio of the cross-axis to the main-axis extent of each child.
+  final double childAspectRatio;
+
+  bool _debugAssertIsValid() {
+    assert(crossAxisCount > 0);
+    assert(mainAxisSpacing >= 0.0);
+    assert(crossAxisSpacing >= 0.0);
+    assert(childAspectRatio > 0.0);
+    return true;
+  }
+
+  @override
+  SliverGridLayout getLayout(SliverConstraints constraints) {
+    assert(_debugAssertIsValid());
+    final double usableCrossAxisExtent = math.max(0.0,
+        constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1));
+    final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
+    final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio;
+    return SliverGridRegularTileLayout(
+      crossAxisCount: crossAxisCount,
+      mainAxisStride: childMainAxisExtent + mainAxisSpacing,
+      crossAxisStride: childCrossAxisExtent + crossAxisSpacing,
+      childMainAxisExtent: childMainAxisExtent,
+      childCrossAxisExtent: childCrossAxisExtent,
+      reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
+    );
+  }
+
+  @override
+  bool shouldRelayout(SliverGridDelegateWithFixedCrossAxisCount oldDelegate) {
+    return oldDelegate.crossAxisCount != crossAxisCount
+        || oldDelegate.mainAxisSpacing != mainAxisSpacing
+        || oldDelegate.crossAxisSpacing != crossAxisSpacing
+        || oldDelegate.childAspectRatio != childAspectRatio;
+  }
+}
+
+/// Creates grid layouts with tiles that each have a maximum cross-axis extent.
+///
+/// This delegate will select a cross-axis extent for the tiles that is as
+/// large as possible subject to the following conditions:
+///
+///  - The extent evenly divides the cross-axis extent of the grid.
+///  - The extent is at most [maxCrossAxisExtent].
+///
+/// For example, if the grid is vertical, the grid is 500.0 pixels wide, and
+/// [maxCrossAxisExtent] is 150.0, this delegate will create a grid with 4
+/// columns that are 125.0 pixels wide.
+///
+/// This delegate creates grids with equally sized and spaced tiles.
+///
+/// See also:
+///
+///  * [SliverGridDelegateWithFixedCrossAxisCount], which creates a layout with
+///    a fixed number of tiles in the cross axis.
+///  * [SliverGridDelegate], which creates arbitrary layouts.
+///  * [GridView], which can use this delegate to control the layout of its
+///    tiles.
+///  * [SliverGrid], which can use this delegate to control the layout of its
+///    tiles.
+///  * [RenderSliverGrid], which can use this delegate to control the layout of
+///    its tiles.
+class SliverGridDelegateWithMaxCrossAxisExtent extends SliverGridDelegate {
+  /// Creates a delegate that makes grid layouts with tiles that have a maximum
+  /// cross-axis extent.
+  ///
+  /// All of the arguments must not be null. The [maxCrossAxisExtent] and
+  /// [mainAxisSpacing], and [crossAxisSpacing] arguments must not be negative.
+  /// The [childAspectRatio] argument must be greater than zero.
+  const SliverGridDelegateWithMaxCrossAxisExtent({
+    required this.maxCrossAxisExtent,
+    this.mainAxisSpacing = 0.0,
+    this.crossAxisSpacing = 0.0,
+    this.childAspectRatio = 1.0,
+  }) : assert(maxCrossAxisExtent != null && maxCrossAxisExtent >= 0),
+       assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
+       assert(crossAxisSpacing != null && crossAxisSpacing >= 0),
+       assert(childAspectRatio != null && childAspectRatio > 0);
+
+  /// The maximum extent of tiles in the cross axis.
+  ///
+  /// This delegate will select a cross-axis extent for the tiles that is as
+  /// large as possible subject to the following conditions:
+  ///
+  ///  - The extent evenly divides the cross-axis extent of the grid.
+  ///  - The extent is at most [maxCrossAxisExtent].
+  ///
+  /// For example, if the grid is vertical, the grid is 500.0 pixels wide, and
+  /// [maxCrossAxisExtent] is 150.0, this delegate will create a grid with 4
+  /// columns that are 125.0 pixels wide.
+  final double maxCrossAxisExtent;
+
+  /// The number of logical pixels between each child along the main axis.
+  final double mainAxisSpacing;
+
+  /// The number of logical pixels between each child along the cross axis.
+  final double crossAxisSpacing;
+
+  /// The ratio of the cross-axis to the main-axis extent of each child.
+  final double childAspectRatio;
+
+  bool _debugAssertIsValid(double crossAxisExtent) {
+    assert(crossAxisExtent > 0.0);
+    assert(maxCrossAxisExtent > 0.0);
+    assert(mainAxisSpacing >= 0.0);
+    assert(crossAxisSpacing >= 0.0);
+    assert(childAspectRatio > 0.0);
+    return true;
+  }
+
+  @override
+  SliverGridLayout getLayout(SliverConstraints constraints) {
+    assert(_debugAssertIsValid(constraints.crossAxisExtent));
+    final int crossAxisCount = (constraints.crossAxisExtent / (maxCrossAxisExtent + crossAxisSpacing)).ceil();
+    final double usableCrossAxisExtent = math.max(0.0,
+        constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1));
+    final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
+    final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio;
+    return SliverGridRegularTileLayout(
+      crossAxisCount: crossAxisCount,
+      mainAxisStride: childMainAxisExtent + mainAxisSpacing,
+      crossAxisStride: childCrossAxisExtent + crossAxisSpacing,
+      childMainAxisExtent: childMainAxisExtent,
+      childCrossAxisExtent: childCrossAxisExtent,
+      reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
+    );
+  }
+
+  @override
+  bool shouldRelayout(SliverGridDelegateWithMaxCrossAxisExtent oldDelegate) {
+    return oldDelegate.maxCrossAxisExtent != maxCrossAxisExtent
+        || oldDelegate.mainAxisSpacing != mainAxisSpacing
+        || oldDelegate.crossAxisSpacing != crossAxisSpacing
+        || oldDelegate.childAspectRatio != childAspectRatio;
+  }
+}
+
+/// Parent data structure used by [RenderSliverGrid].
+class SliverGridParentData extends SliverMultiBoxAdaptorParentData {
+  /// The offset of the child in the non-scrolling axis.
+  ///
+  /// If the scroll axis is vertical, this offset is from the left-most edge of
+  /// the parent to the left-most edge of the child. If the scroll axis is
+  /// horizontal, this offset is from the top-most edge of the parent to the
+  /// top-most edge of the child.
+  double? crossAxisOffset;
+
+  @override
+  String toString() => 'crossAxisOffset=$crossAxisOffset; ${super.toString()}';
+}
+
+/// A sliver that places multiple box children in a two dimensional arrangement.
+///
+/// [RenderSliverGrid] places its children in arbitrary positions determined by
+/// [gridDelegate]. Each child is forced to have the size specified by the
+/// [gridDelegate].
+///
+/// See also:
+///
+///  * [RenderSliverList], which places its children in a linear
+///    array.
+///  * [RenderSliverFixedExtentList], which places its children in a linear
+///    array with a fixed extent in the main axis.
+class RenderSliverGrid extends RenderSliverMultiBoxAdaptor {
+  /// Creates a sliver that contains multiple box children that whose size and
+  /// position are determined by a delegate.
+  ///
+  /// The [childManager] and [gridDelegate] arguments must not be null.
+  RenderSliverGrid({
+    required RenderSliverBoxChildManager childManager,
+    required SliverGridDelegate gridDelegate,
+  }) : assert(gridDelegate != null),
+       _gridDelegate = gridDelegate,
+       super(childManager: childManager);
+
+  @override
+  void setupParentData(RenderObject child) {
+    if (child.parentData is! SliverGridParentData)
+      child.parentData = SliverGridParentData();
+  }
+
+  /// The delegate that controls the size and position of the children.
+  SliverGridDelegate get gridDelegate => _gridDelegate;
+  SliverGridDelegate _gridDelegate;
+  set gridDelegate(SliverGridDelegate value) {
+    assert(value != null);
+    if (_gridDelegate == value)
+      return;
+    if (value.runtimeType != _gridDelegate.runtimeType ||
+        value.shouldRelayout(_gridDelegate))
+      markNeedsLayout();
+    _gridDelegate = value;
+  }
+
+  @override
+  double childCrossAxisPosition(RenderBox child) {
+    final SliverGridParentData childParentData = child.parentData! as SliverGridParentData;
+    return childParentData.crossAxisOffset!;
+  }
+
+  @override
+  void performLayout() {
+    final SliverConstraints constraints = this.constraints;
+    childManager.didStartLayout();
+    childManager.setDidUnderflow(false);
+
+    final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
+    assert(scrollOffset >= 0.0);
+    final double remainingExtent = constraints.remainingCacheExtent;
+    assert(remainingExtent >= 0.0);
+    final double targetEndScrollOffset = scrollOffset + remainingExtent;
+
+    final SliverGridLayout layout = _gridDelegate.getLayout(constraints);
+
+    final int firstIndex = layout.getMinChildIndexForScrollOffset(scrollOffset);
+    final int? targetLastIndex = targetEndScrollOffset.isFinite ?
+      layout.getMaxChildIndexForScrollOffset(targetEndScrollOffset) : null;
+
+    if (firstChild != null) {
+      final int oldFirstIndex = indexOf(firstChild!);
+      final int oldLastIndex = indexOf(lastChild!);
+      final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount);
+      final int trailingGarbage = targetLastIndex == null
+        ? 0
+        : (oldLastIndex - targetLastIndex).clamp(0, childCount);
+      collectGarbage(leadingGarbage, trailingGarbage);
+    } else {
+      collectGarbage(0, 0);
+    }
+
+    final SliverGridGeometry firstChildGridGeometry = layout.getGeometryForChildIndex(firstIndex);
+    final double leadingScrollOffset = firstChildGridGeometry.scrollOffset;
+    double trailingScrollOffset = firstChildGridGeometry.trailingScrollOffset;
+
+    if (firstChild == null) {
+      if (!addInitialChild(index: firstIndex, layoutOffset: firstChildGridGeometry.scrollOffset)) {
+        // There are either no children, or we are past the end of all our children.
+        final double max = layout.computeMaxScrollOffset(childManager.childCount);
+        geometry = SliverGeometry(
+          scrollExtent: max,
+          maxPaintExtent: max,
+        );
+        childManager.didFinishLayout();
+        return;
+      }
+    }
+
+    RenderBox? trailingChildWithLayout;
+
+    for (int index = indexOf(firstChild!) - 1; index >= firstIndex; --index) {
+      final SliverGridGeometry gridGeometry = layout.getGeometryForChildIndex(index);
+      final RenderBox child = insertAndLayoutLeadingChild(
+        gridGeometry.getBoxConstraints(constraints),
+      )!;
+      final SliverGridParentData childParentData = child.parentData! as SliverGridParentData;
+      childParentData.layoutOffset = gridGeometry.scrollOffset;
+      childParentData.crossAxisOffset = gridGeometry.crossAxisOffset;
+      assert(childParentData.index == index);
+      trailingChildWithLayout ??= child;
+      trailingScrollOffset = math.max(trailingScrollOffset, gridGeometry.trailingScrollOffset);
+    }
+
+    if (trailingChildWithLayout == null) {
+      firstChild!.layout(firstChildGridGeometry.getBoxConstraints(constraints));
+      final SliverGridParentData childParentData = firstChild!.parentData! as SliverGridParentData;
+      childParentData.layoutOffset = firstChildGridGeometry.scrollOffset;
+      childParentData.crossAxisOffset = firstChildGridGeometry.crossAxisOffset;
+      trailingChildWithLayout = firstChild;
+    }
+
+    for (int index = indexOf(trailingChildWithLayout!) + 1; targetLastIndex == null || index <= targetLastIndex; ++index) {
+      final SliverGridGeometry gridGeometry = layout.getGeometryForChildIndex(index);
+      final BoxConstraints childConstraints = gridGeometry.getBoxConstraints(constraints);
+      RenderBox? child = childAfter(trailingChildWithLayout!);
+      if (child == null || indexOf(child) != index) {
+        child = insertAndLayoutChild(childConstraints, after: trailingChildWithLayout);
+        if (child == null) {
+          // We have run out of children.
+          break;
+        }
+      } else {
+        child.layout(childConstraints);
+      }
+      trailingChildWithLayout = child;
+      assert(child != null);
+      final SliverGridParentData childParentData = child.parentData! as SliverGridParentData;
+      childParentData.layoutOffset = gridGeometry.scrollOffset;
+      childParentData.crossAxisOffset = gridGeometry.crossAxisOffset;
+      assert(childParentData.index == index);
+      trailingScrollOffset = math.max(trailingScrollOffset, gridGeometry.trailingScrollOffset);
+    }
+
+    final int lastIndex = indexOf(lastChild!);
+
+    assert(debugAssertChildListIsNonEmptyAndContiguous());
+    assert(indexOf(firstChild!) == firstIndex);
+    assert(targetLastIndex == null || lastIndex <= targetLastIndex);
+
+    final double estimatedTotalExtent = childManager.estimateMaxScrollOffset(
+      constraints,
+      firstIndex: firstIndex,
+      lastIndex: lastIndex,
+      leadingScrollOffset: leadingScrollOffset,
+      trailingScrollOffset: trailingScrollOffset,
+    );
+
+    final double paintExtent = calculatePaintOffset(
+      constraints,
+      from: math.min(constraints.scrollOffset, leadingScrollOffset),
+      to: trailingScrollOffset,
+    );
+    final double cacheExtent = calculateCacheOffset(
+      constraints,
+      from: leadingScrollOffset,
+      to: trailingScrollOffset,
+    );
+
+    geometry = SliverGeometry(
+      scrollExtent: estimatedTotalExtent,
+      paintExtent: paintExtent,
+      maxPaintExtent: estimatedTotalExtent,
+      cacheExtent: cacheExtent,
+      // Conservative to avoid complexity.
+      hasVisualOverflow: true,
+    );
+
+    // We may have started the layout while scrolled to the end, which
+    // would not expose a new child.
+    if (estimatedTotalExtent == trailingScrollOffset)
+      childManager.setDidUnderflow(true);
+    childManager.didFinishLayout();
+  }
+}
diff --git a/lib/src/rendering/sliver_list.dart b/lib/src/rendering/sliver_list.dart
new file mode 100644
index 0000000..afc5218
--- /dev/null
+++ b/lib/src/rendering/sliver_list.dart
@@ -0,0 +1,342 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'box.dart';
+import 'sliver.dart';
+import 'sliver_multi_box_adaptor.dart';
+
+/// A sliver that places multiple box children in a linear array along the main
+/// axis.
+///
+/// Each child is forced to have the [SliverConstraints.crossAxisExtent] in the
+/// cross axis but determines its own main axis extent.
+///
+/// [RenderSliverList] determines its scroll offset by "dead reckoning" because
+/// children outside the visible part of the sliver are not materialized, which
+/// means [RenderSliverList] cannot learn their main axis extent. Instead, newly
+/// materialized children are placed adjacent to existing children. If this dead
+/// reckoning results in a logical inconsistency (e.g., attempting to place the
+/// zeroth child at a scroll offset other than zero), the [RenderSliverList]
+/// generates a [SliverGeometry.scrollOffsetCorrection] to restore consistency.
+///
+/// If the children have a fixed extent in the main axis, consider using
+/// [RenderSliverFixedExtentList] rather than [RenderSliverList] because
+/// [RenderSliverFixedExtentList] does not need to perform layout on its
+/// children to obtain their extent in the main axis and is therefore more
+/// efficient.
+///
+/// See also:
+///
+///  * [RenderSliverFixedExtentList], which is more efficient for children with
+///    the same extent in the main axis.
+///  * [RenderSliverGrid], which places its children in arbitrary positions.
+class RenderSliverList extends RenderSliverMultiBoxAdaptor {
+  /// Creates a sliver that places multiple box children in a linear array along
+  /// the main axis.
+  ///
+  /// The [childManager] argument must not be null.
+  RenderSliverList({
+    required RenderSliverBoxChildManager childManager,
+  }) : super(childManager: childManager);
+
+  @override
+  void performLayout() {
+    final SliverConstraints constraints = this.constraints;
+    childManager.didStartLayout();
+    childManager.setDidUnderflow(false);
+
+    final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
+    assert(scrollOffset >= 0.0);
+    final double remainingExtent = constraints.remainingCacheExtent;
+    assert(remainingExtent >= 0.0);
+    final double targetEndScrollOffset = scrollOffset + remainingExtent;
+    final BoxConstraints childConstraints = constraints.asBoxConstraints();
+    int leadingGarbage = 0;
+    int trailingGarbage = 0;
+    bool reachedEnd = false;
+
+    // This algorithm in principle is straight-forward: find the first child
+    // that overlaps the given scrollOffset, creating more children at the top
+    // of the list if necessary, then walk down the list updating and laying out
+    // each child and adding more at the end if necessary until we have enough
+    // children to cover the entire viewport.
+    //
+    // It is complicated by one minor issue, which is that any time you update
+    // or create a child, it's possible that the some of the children that
+    // haven't yet been laid out will be removed, leaving the list in an
+    // inconsistent state, and requiring that missing nodes be recreated.
+    //
+    // To keep this mess tractable, this algorithm starts from what is currently
+    // the first child, if any, and then walks up and/or down from there, so
+    // that the nodes that might get removed are always at the edges of what has
+    // already been laid out.
+
+    // Make sure we have at least one child to start from.
+    if (firstChild == null) {
+      if (!addInitialChild()) {
+        // There are no children.
+        geometry = SliverGeometry.zero;
+        childManager.didFinishLayout();
+        return;
+      }
+    }
+
+    // We have at least one child.
+
+    // These variables track the range of children that we have laid out. Within
+    // this range, the children have consecutive indices. Outside this range,
+    // it's possible for a child to get removed without notice.
+    RenderBox? leadingChildWithLayout, trailingChildWithLayout;
+
+    RenderBox? earliestUsefulChild = firstChild;
+
+    // A firstChild with null layout offset is likely a result of children
+    // reordering.
+    //
+    // We rely on firstChild to have accurate layout offset. In the case of null
+    // layout offset, we have to find the first child that has valid layout
+    // offset.
+    if (childScrollOffset(firstChild!) == null) {
+      int leadingChildrenWithoutLayoutOffset = 0;
+      while (earliestUsefulChild != null && childScrollOffset(earliestUsefulChild) == null) {
+        earliestUsefulChild = childAfter(earliestUsefulChild);
+        leadingChildrenWithoutLayoutOffset += 1;
+      }
+      // We should be able to destroy children with null layout offset safely,
+      // because they are likely outside of viewport
+      collectGarbage(leadingChildrenWithoutLayoutOffset, 0);
+      // If can not find a valid layout offset, start from the initial child.
+      if (firstChild == null) {
+        if (!addInitialChild()) {
+          // There are no children.
+          geometry = SliverGeometry.zero;
+          childManager.didFinishLayout();
+          return;
+        }
+      }
+    }
+
+    // Find the last child that is at or before the scrollOffset.
+    earliestUsefulChild = firstChild;
+    for (double earliestScrollOffset = childScrollOffset(earliestUsefulChild!)!;
+        earliestScrollOffset > scrollOffset;
+        earliestScrollOffset = childScrollOffset(earliestUsefulChild)!) {
+      // We have to add children before the earliestUsefulChild.
+      earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true);
+      if (earliestUsefulChild == null) {
+        final SliverMultiBoxAdaptorParentData childParentData = firstChild!.parentData! as SliverMultiBoxAdaptorParentData;
+        childParentData.layoutOffset = 0.0;
+
+        if (scrollOffset == 0.0) {
+          // insertAndLayoutLeadingChild only lays out the children before
+          // firstChild. In this case, nothing has been laid out. We have
+          // to lay out firstChild manually.
+          firstChild!.layout(childConstraints, parentUsesSize: true);
+          earliestUsefulChild = firstChild;
+          leadingChildWithLayout = earliestUsefulChild;
+          trailingChildWithLayout ??= earliestUsefulChild;
+          break;
+        } else {
+          // We ran out of children before reaching the scroll offset.
+          // We must inform our parent that this sliver cannot fulfill
+          // its contract and that we need a scroll offset correction.
+          geometry = SliverGeometry(
+            scrollOffsetCorrection: -scrollOffset,
+          );
+          return;
+        }
+      }
+
+      final double firstChildScrollOffset = earliestScrollOffset - paintExtentOf(firstChild!);
+      // firstChildScrollOffset may contain double precision error
+      if (firstChildScrollOffset < -precisionErrorTolerance) {
+        // Let's assume there is no child before the first child. We will
+        // correct it on the next layout if it is not.
+        geometry = SliverGeometry(
+          scrollOffsetCorrection: -firstChildScrollOffset,
+        );
+        final SliverMultiBoxAdaptorParentData childParentData = firstChild!.parentData! as SliverMultiBoxAdaptorParentData;
+        childParentData.layoutOffset = 0.0;
+        return;
+      }
+
+      final SliverMultiBoxAdaptorParentData childParentData = earliestUsefulChild.parentData! as SliverMultiBoxAdaptorParentData;
+      childParentData.layoutOffset = firstChildScrollOffset;
+      assert(earliestUsefulChild == firstChild);
+      leadingChildWithLayout = earliestUsefulChild;
+      trailingChildWithLayout ??= earliestUsefulChild;
+    }
+
+    assert(childScrollOffset(firstChild!)! > -precisionErrorTolerance);
+
+    // If the scroll offset is at zero, we should make sure we are
+    // actually at the beginning of the list.
+    if (scrollOffset < precisionErrorTolerance) {
+      // We iterate from the firstChild in case the leading child has a 0 paint
+      // extent.
+      while (indexOf(firstChild!) > 0) {
+        final double earliestScrollOffset = childScrollOffset(firstChild!)!;
+        // We correct one child at a time. If there are more children before
+        // the earliestUsefulChild, we will correct it once the scroll offset
+        // reaches zero again.
+        earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true);
+        assert(earliestUsefulChild != null);
+        final double firstChildScrollOffset = earliestScrollOffset - paintExtentOf(firstChild!);
+        final SliverMultiBoxAdaptorParentData childParentData = firstChild!.parentData! as SliverMultiBoxAdaptorParentData;
+        childParentData.layoutOffset = 0.0;
+        // We only need to correct if the leading child actually has a
+        // paint extent.
+        if (firstChildScrollOffset < -precisionErrorTolerance) {
+          geometry = SliverGeometry(
+            scrollOffsetCorrection: -firstChildScrollOffset,
+          );
+          return;
+        }
+      }
+    }
+
+    // At this point, earliestUsefulChild is the first child, and is a child
+    // whose scrollOffset is at or before the scrollOffset, and
+    // leadingChildWithLayout and trailingChildWithLayout are either null or
+    // cover a range of render boxes that we have laid out with the first being
+    // the same as earliestUsefulChild and the last being either at or after the
+    // scroll offset.
+
+    assert(earliestUsefulChild == firstChild);
+    assert(childScrollOffset(earliestUsefulChild!)! <= scrollOffset);
+
+    // Make sure we've laid out at least one child.
+    if (leadingChildWithLayout == null) {
+      earliestUsefulChild!.layout(childConstraints, parentUsesSize: true);
+      leadingChildWithLayout = earliestUsefulChild;
+      trailingChildWithLayout = earliestUsefulChild;
+    }
+
+    // Here, earliestUsefulChild is still the first child, it's got a
+    // scrollOffset that is at or before our actual scrollOffset, and it has
+    // been laid out, and is in fact our leadingChildWithLayout. It's possible
+    // that some children beyond that one have also been laid out.
+
+    bool inLayoutRange = true;
+    RenderBox? child = earliestUsefulChild;
+    int index = indexOf(child!);
+    double endScrollOffset = childScrollOffset(child)! + paintExtentOf(child);
+    bool advance() { // returns true if we advanced, false if we have no more children
+      // This function is used in two different places below, to avoid code duplication.
+      assert(child != null);
+      if (child == trailingChildWithLayout)
+        inLayoutRange = false;
+      child = childAfter(child!);
+      if (child == null)
+        inLayoutRange = false;
+      index += 1;
+      if (!inLayoutRange) {
+        if (child == null || indexOf(child!) != index) {
+          // We are missing a child. Insert it (and lay it out) if possible.
+          child = insertAndLayoutChild(childConstraints,
+            after: trailingChildWithLayout,
+            parentUsesSize: true,
+          );
+          if (child == null) {
+            // We have run out of children.
+            return false;
+          }
+        } else {
+          // Lay out the child.
+          child!.layout(childConstraints, parentUsesSize: true);
+        }
+        trailingChildWithLayout = child;
+      }
+      assert(child != null);
+      final SliverMultiBoxAdaptorParentData childParentData = child!.parentData! as SliverMultiBoxAdaptorParentData;
+      childParentData.layoutOffset = endScrollOffset;
+      assert(childParentData.index == index);
+      endScrollOffset = childScrollOffset(child!)! + paintExtentOf(child!);
+      return true;
+    }
+
+    // Find the first child that ends after the scroll offset.
+    while (endScrollOffset < scrollOffset) {
+      leadingGarbage += 1;
+      if (!advance()) {
+        assert(leadingGarbage == childCount);
+        assert(child == null);
+        // we want to make sure we keep the last child around so we know the end scroll offset
+        collectGarbage(leadingGarbage - 1, 0);
+        assert(firstChild == lastChild);
+        final double extent = childScrollOffset(lastChild!)! + paintExtentOf(lastChild!);
+        geometry = SliverGeometry(
+          scrollExtent: extent,
+          paintExtent: 0.0,
+          maxPaintExtent: extent,
+        );
+        return;
+      }
+    }
+
+    // Now find the first child that ends after our end.
+    while (endScrollOffset < targetEndScrollOffset) {
+      if (!advance()) {
+        reachedEnd = true;
+        break;
+      }
+    }
+
+    // Finally count up all the remaining children and label them as garbage.
+    if (child != null) {
+      child = childAfter(child!);
+      while (child != null) {
+        trailingGarbage += 1;
+        child = childAfter(child!);
+      }
+    }
+
+    // At this point everything should be good to go, we just have to clean up
+    // the garbage and report the geometry.
+
+    collectGarbage(leadingGarbage, trailingGarbage);
+
+    assert(debugAssertChildListIsNonEmptyAndContiguous());
+    final double estimatedMaxScrollOffset;
+    if (reachedEnd) {
+      estimatedMaxScrollOffset = endScrollOffset;
+    } else {
+      estimatedMaxScrollOffset = childManager.estimateMaxScrollOffset(
+        constraints,
+        firstIndex: indexOf(firstChild!),
+        lastIndex: indexOf(lastChild!),
+        leadingScrollOffset: childScrollOffset(firstChild!),
+        trailingScrollOffset: endScrollOffset,
+      );
+      assert(estimatedMaxScrollOffset >= endScrollOffset - childScrollOffset(firstChild!)!);
+    }
+    final double paintExtent = calculatePaintOffset(
+      constraints,
+      from: childScrollOffset(firstChild!)!,
+      to: endScrollOffset,
+    );
+    final double cacheExtent = calculateCacheOffset(
+      constraints,
+      from: childScrollOffset(firstChild!)!,
+      to: endScrollOffset,
+    );
+    final double targetEndScrollOffsetForPaint = constraints.scrollOffset + constraints.remainingPaintExtent;
+    geometry = SliverGeometry(
+      scrollExtent: estimatedMaxScrollOffset,
+      paintExtent: paintExtent,
+      cacheExtent: cacheExtent,
+      maxPaintExtent: estimatedMaxScrollOffset,
+      // Conservative to avoid flickering away the clip during scroll.
+      hasVisualOverflow: endScrollOffset > targetEndScrollOffsetForPaint || constraints.scrollOffset > 0.0,
+    );
+
+    // We may have started the layout while scrolled to the end, which would not
+    // expose a new child.
+    if (estimatedMaxScrollOffset == endScrollOffset)
+      childManager.setDidUnderflow(true);
+    childManager.didFinishLayout();
+  }
+}
diff --git a/lib/src/rendering/sliver_multi_box_adaptor.dart b/lib/src/rendering/sliver_multi_box_adaptor.dart
new file mode 100644
index 0000000..d72a6dc
--- /dev/null
+++ b/lib/src/rendering/sliver_multi_box_adaptor.dart
@@ -0,0 +1,697 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'box.dart';
+import 'object.dart';
+import 'sliver.dart';
+
+/// A delegate used by [RenderSliverMultiBoxAdaptor] to manage its children.
+///
+/// [RenderSliverMultiBoxAdaptor] objects reify their children lazily to avoid
+/// spending resources on children that are not visible in the viewport. This
+/// delegate lets these objects create and remove children as well as estimate
+/// the total scroll offset extent occupied by the full child list.
+abstract class RenderSliverBoxChildManager {
+  /// Called during layout when a new child is needed. The child should be
+  /// inserted into the child list in the appropriate position, after the
+  /// `after` child (at the start of the list if `after` is null). Its index and
+  /// scroll offsets will automatically be set appropriately.
+  ///
+  /// The `index` argument gives the index of the child to show. It is possible
+  /// for negative indices to be requested. For example: if the user scrolls
+  /// from child 0 to child 10, and then those children get much smaller, and
+  /// then the user scrolls back up again, this method will eventually be asked
+  /// to produce a child for index -1.
+  ///
+  /// If no child corresponds to `index`, then do nothing.
+  ///
+  /// Which child is indicated by index zero depends on the [GrowthDirection]
+  /// specified in the `constraints` of the [RenderSliverMultiBoxAdaptor]. For
+  /// example if the children are the alphabet, then if
+  /// [SliverConstraints.growthDirection] is [GrowthDirection.forward] then
+  /// index zero is A, and index 25 is Z. On the other hand if
+  /// [SliverConstraints.growthDirection] is [GrowthDirection.reverse] then
+  /// index zero is Z, and index 25 is A.
+  ///
+  /// During a call to [createChild] it is valid to remove other children from
+  /// the [RenderSliverMultiBoxAdaptor] object if they were not created during
+  /// this frame and have not yet been updated during this frame. It is not
+  /// valid to add any other children to this render object.
+  void createChild(int index, { required RenderBox? after });
+
+  /// Remove the given child from the child list.
+  ///
+  /// Called by [RenderSliverMultiBoxAdaptor.collectGarbage], which itself is
+  /// called from [RenderSliverMultiBoxAdaptor]'s `performLayout`.
+  ///
+  /// The index of the given child can be obtained using the
+  /// [RenderSliverMultiBoxAdaptor.indexOf] method, which reads it from the
+  /// [SliverMultiBoxAdaptorParentData.index] field of the child's
+  /// [RenderObject.parentData].
+  void removeChild(RenderBox child);
+
+  /// Called to estimate the total scrollable extents of this object.
+  ///
+  /// Must return the total distance from the start of the child with the
+  /// earliest possible index to the end of the child with the last possible
+  /// index.
+  double estimateMaxScrollOffset(
+    SliverConstraints constraints, {
+    int? firstIndex,
+    int? lastIndex,
+    double? leadingScrollOffset,
+    double? trailingScrollOffset,
+  });
+
+  /// Called to obtain a precise measure of the total number of children.
+  ///
+  /// Must return the number that is one greater than the greatest `index` for
+  /// which `createChild` will actually create a child.
+  ///
+  /// This is used when [createChild] cannot add a child for a positive `index`,
+  /// to determine the precise dimensions of the sliver. It must return an
+  /// accurate and precise non-null value. It will not be called if
+  /// [createChild] is always able to create a child (e.g. for an infinite
+  /// list).
+  int get childCount;
+
+  /// Called during [RenderSliverMultiBoxAdaptor.adoptChild] or
+  /// [RenderSliverMultiBoxAdaptor.move].
+  ///
+  /// Subclasses must ensure that the [SliverMultiBoxAdaptorParentData.index]
+  /// field of the child's [RenderObject.parentData] accurately reflects the
+  /// child's index in the child list after this function returns.
+  void didAdoptChild(RenderBox child);
+
+  /// Called during layout to indicate whether this object provided insufficient
+  /// children for the [RenderSliverMultiBoxAdaptor] to fill the
+  /// [SliverConstraints.remainingPaintExtent].
+  ///
+  /// Typically called unconditionally at the start of layout with false and
+  /// then later called with true when the [RenderSliverMultiBoxAdaptor]
+  /// fails to create a child required to fill the
+  /// [SliverConstraints.remainingPaintExtent].
+  ///
+  /// Useful for subclasses to determine whether newly added children could
+  /// affect the visible contents of the [RenderSliverMultiBoxAdaptor].
+  void setDidUnderflow(bool value);
+
+  /// Called at the beginning of layout to indicate that layout is about to
+  /// occur.
+  void didStartLayout() { }
+
+  /// Called at the end of layout to indicate that layout is now complete.
+  void didFinishLayout() { }
+
+  /// In debug mode, asserts that this manager is not expecting any
+  /// modifications to the [RenderSliverMultiBoxAdaptor]'s child list.
+  ///
+  /// This function always returns true.
+  ///
+  /// The manager is not required to track whether it is expecting modifications
+  /// to the [RenderSliverMultiBoxAdaptor]'s child list and can simply return
+  /// true without making any assertions.
+  bool debugAssertChildListLocked() => true;
+}
+/// Parent data structure used by [RenderSliverWithKeepAliveMixin].
+mixin KeepAliveParentDataMixin implements ParentData {
+  /// Whether to keep the child alive even when it is no longer visible.
+  bool keepAlive = false;
+
+  /// Whether the widget is currently being kept alive, i.e. has [keepAlive] set
+  /// to true and is offscreen.
+  bool get keptAlive;
+}
+
+/// This class exists to dissociate [KeepAlive] from [RenderSliverMultiBoxAdaptor].
+///
+/// [RenderSliverWithKeepAliveMixin.setupParentData] must be implemented to use
+/// a parentData class that uses the right mixin or whatever is appropriate.
+mixin RenderSliverWithKeepAliveMixin implements RenderSliver {
+  /// Alerts the developer that the child's parentData needs to be of type
+  /// [KeepAliveParentDataMixin].
+  @override
+  void setupParentData(RenderObject child) {
+    assert(child.parentData is KeepAliveParentDataMixin);
+  }
+}
+
+/// Parent data structure used by [RenderSliverMultiBoxAdaptor].
+class SliverMultiBoxAdaptorParentData extends SliverLogicalParentData with ContainerParentDataMixin<RenderBox>, KeepAliveParentDataMixin {
+  /// The index of this child according to the [RenderSliverBoxChildManager].
+  int? index;
+
+  @override
+  bool get keptAlive => _keptAlive;
+  bool _keptAlive = false;
+
+  @override
+  String toString() => 'index=$index; ${keepAlive == true ? "keepAlive; " : ""}${super.toString()}';
+}
+
+/// A sliver with multiple box children.
+///
+/// [RenderSliverMultiBoxAdaptor] is a base class for slivers that have multiple
+/// box children. The children are managed by a [RenderSliverBoxChildManager],
+/// which lets subclasses create children lazily during layout. Typically
+/// subclasses will create only those children that are actually needed to fill
+/// the [SliverConstraints.remainingPaintExtent].
+///
+/// The contract for adding and removing children from this render object is
+/// more strict than for normal render objects:
+///
+/// * Children can be removed except during a layout pass if they have already
+///   been laid out during that layout pass.
+/// * Children cannot be added except during a call to [childManager], and
+///   then only if there is no child corresponding to that index (or the child
+///   child corresponding to that index was first removed).
+///
+/// See also:
+///
+///  * [RenderSliverToBoxAdapter], which has a single box child.
+///  * [RenderSliverList], which places its children in a linear
+///    array.
+///  * [RenderSliverFixedExtentList], which places its children in a linear
+///    array with a fixed extent in the main axis.
+///  * [RenderSliverGrid], which places its children in arbitrary positions.
+abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
+  with ContainerRenderObjectMixin<RenderBox, SliverMultiBoxAdaptorParentData>,
+       RenderSliverHelpers, RenderSliverWithKeepAliveMixin {
+
+  /// Creates a sliver with multiple box children.
+  ///
+  /// The [childManager] argument must not be null.
+  RenderSliverMultiBoxAdaptor({
+    required RenderSliverBoxChildManager childManager,
+  }) : assert(childManager != null),
+       _childManager = childManager {
+    assert(() {
+      _debugDanglingKeepAlives = <RenderBox>[];
+      return true;
+    }());
+  }
+
+  @override
+  void setupParentData(RenderObject child) {
+    if (child.parentData is! SliverMultiBoxAdaptorParentData)
+      child.parentData = SliverMultiBoxAdaptorParentData();
+  }
+
+  /// The delegate that manages the children of this object.
+  ///
+  /// Rather than having a concrete list of children, a
+  /// [RenderSliverMultiBoxAdaptor] uses a [RenderSliverBoxChildManager] to
+  /// create children during layout in order to fill the
+  /// [SliverConstraints.remainingPaintExtent].
+  @protected
+  RenderSliverBoxChildManager get childManager => _childManager;
+  final RenderSliverBoxChildManager _childManager;
+
+  /// The nodes being kept alive despite not being visible.
+  final Map<int, RenderBox> _keepAliveBucket = <int, RenderBox>{};
+
+  late List<RenderBox> _debugDanglingKeepAlives;
+
+  /// Indicates whether integrity check is enabled.
+  ///
+  /// Setting this property to true will immediately perform an integrity check.
+  ///
+  /// The integrity check consists of:
+  ///
+  /// 1. Verify that the children index in childList is in ascending order.
+  /// 2. Verify that there is no dangling keepalive child as the result of [move].
+  bool get debugChildIntegrityEnabled => _debugChildIntegrityEnabled;
+  bool _debugChildIntegrityEnabled = true;
+  set debugChildIntegrityEnabled(bool enabled) {
+    assert(enabled != null);
+    assert(() {
+      _debugChildIntegrityEnabled = enabled;
+      return _debugVerifyChildOrder() &&
+        (!_debugChildIntegrityEnabled || _debugDanglingKeepAlives.isEmpty);
+    }());
+  }
+
+  @override
+  void adoptChild(RenderObject child) {
+    super.adoptChild(child);
+    final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
+    if (!childParentData._keptAlive)
+      childManager.didAdoptChild(child as RenderBox);
+  }
+
+  bool _debugAssertChildListLocked() => childManager.debugAssertChildListLocked();
+
+  /// Verify that the child list index is in strictly increasing order.
+  ///
+  /// This has no effect in release builds.
+  bool _debugVerifyChildOrder(){
+    if (_debugChildIntegrityEnabled) {
+      RenderBox? child = firstChild;
+      int index;
+      while (child != null) {
+        index = indexOf(child);
+        child = childAfter(child);
+        assert(child == null || indexOf(child) > index);
+      }
+    }
+    return true;
+  }
+
+  @override
+  void insert(RenderBox child, { RenderBox? after }) {
+    assert(!_keepAliveBucket.containsValue(child));
+    super.insert(child, after: after);
+    assert(firstChild != null);
+    assert(_debugVerifyChildOrder());
+  }
+
+  @override
+  void move(RenderBox child, { RenderBox? after }) {
+    // There are two scenarios:
+    //
+    // 1. The child is not keptAlive.
+    // The child is in the childList maintained by ContainerRenderObjectMixin.
+    // We can call super.move and update parentData with the new slot.
+    //
+    // 2. The child is keptAlive.
+    // In this case, the child is no longer in the childList but might be stored in
+    // [_keepAliveBucket]. We need to update the location of the child in the bucket.
+    final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
+    if (!childParentData.keptAlive) {
+      super.move(child, after: after);
+      childManager.didAdoptChild(child); // updates the slot in the parentData
+      // Its slot may change even if super.move does not change the position.
+      // In this case, we still want to mark as needs layout.
+      markNeedsLayout();
+    } else {
+      // If the child in the bucket is not current child, that means someone has
+      // already moved and replaced current child, and we cannot remove this child.
+      if (_keepAliveBucket[childParentData.index] == child) {
+        _keepAliveBucket.remove(childParentData.index);
+      }
+      assert(() {
+        _debugDanglingKeepAlives.remove(child);
+        return true;
+      }());
+      // Update the slot and reinsert back to _keepAliveBucket in the new slot.
+      childManager.didAdoptChild(child);
+      // If there is an existing child in the new slot, that mean that child will
+      // be moved to other index. In other cases, the existing child should have been
+      // removed by updateChild. Thus, it is ok to overwrite it.
+      assert(() {
+        if (_keepAliveBucket.containsKey(childParentData.index))
+          _debugDanglingKeepAlives.add(_keepAliveBucket[childParentData.index]!);
+        return true;
+      }());
+      _keepAliveBucket[childParentData.index!] = child;
+    }
+  }
+
+  @override
+  void remove(RenderBox child) {
+    final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
+    if (!childParentData._keptAlive) {
+      super.remove(child);
+      return;
+    }
+    assert(_keepAliveBucket[childParentData.index] == child);
+    assert(() {
+      _debugDanglingKeepAlives.remove(child);
+      return true;
+    }());
+    _keepAliveBucket.remove(childParentData.index);
+    dropChild(child);
+  }
+
+  @override
+  void removeAll() {
+    super.removeAll();
+    _keepAliveBucket.values.forEach(dropChild);
+    _keepAliveBucket.clear();
+  }
+
+  void _createOrObtainChild(int index, { required RenderBox? after }) {
+    invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
+      assert(constraints == this.constraints);
+      if (_keepAliveBucket.containsKey(index)) {
+        final RenderBox child = _keepAliveBucket.remove(index)!;
+        final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
+        assert(childParentData._keptAlive);
+        dropChild(child);
+        child.parentData = childParentData;
+        insert(child, after: after);
+        childParentData._keptAlive = false;
+      } else {
+        _childManager.createChild(index, after: after);
+      }
+    });
+  }
+
+  void _destroyOrCacheChild(RenderBox child) {
+    final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
+    if (childParentData.keepAlive) {
+      assert(!childParentData._keptAlive);
+      remove(child);
+      _keepAliveBucket[childParentData.index!] = child;
+      child.parentData = childParentData;
+      super.adoptChild(child);
+      childParentData._keptAlive = true;
+    } else {
+      assert(child.parent == this);
+      _childManager.removeChild(child);
+      assert(child.parent == null);
+    }
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    for (final RenderBox child in _keepAliveBucket.values)
+      child.attach(owner);
+  }
+
+  @override
+  void detach() {
+    super.detach();
+    for (final RenderBox child in _keepAliveBucket.values)
+      child.detach();
+  }
+
+  @override
+  void redepthChildren() {
+    super.redepthChildren();
+    _keepAliveBucket.values.forEach(redepthChild);
+  }
+
+  @override
+  void visitChildren(RenderObjectVisitor visitor) {
+    super.visitChildren(visitor);
+    _keepAliveBucket.values.forEach(visitor);
+  }
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    super.visitChildren(visitor);
+    // Do not visit children in [_keepAliveBucket].
+  }
+
+  /// Called during layout to create and add the child with the given index and
+  /// scroll offset.
+  ///
+  /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
+  /// the child if necessary. The child may instead be obtained from a cache;
+  /// see [SliverMultiBoxAdaptorParentData.keepAlive].
+  ///
+  /// Returns false if there was no cached child and `createChild` did not add
+  /// any child, otherwise returns true.
+  ///
+  /// Does not layout the new child.
+  ///
+  /// When this is called, there are no visible children, so no children can be
+  /// removed during the call to `createChild`. No child should be added during
+  /// that call either, except for the one that is created and returned by
+  /// `createChild`.
+  @protected
+  bool addInitialChild({ int index = 0, double layoutOffset = 0.0 }) {
+    assert(_debugAssertChildListLocked());
+    assert(firstChild == null);
+    _createOrObtainChild(index, after: null);
+    if (firstChild != null) {
+      assert(firstChild == lastChild);
+      assert(indexOf(firstChild!) == index);
+      final SliverMultiBoxAdaptorParentData firstChildParentData = firstChild!.parentData! as SliverMultiBoxAdaptorParentData;
+      firstChildParentData.layoutOffset = layoutOffset;
+      return true;
+    }
+    childManager.setDidUnderflow(true);
+    return false;
+  }
+
+  /// Called during layout to create, add, and layout the child before
+  /// [firstChild].
+  ///
+  /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
+  /// the child if necessary. The child may instead be obtained from a cache;
+  /// see [SliverMultiBoxAdaptorParentData.keepAlive].
+  ///
+  /// Returns the new child or null if no child was obtained.
+  ///
+  /// The child that was previously the first child, as well as any subsequent
+  /// children, may be removed by this call if they have not yet been laid out
+  /// during this layout pass. No child should be added during that call except
+  /// for the one that is created and returned by `createChild`.
+  @protected
+  RenderBox? insertAndLayoutLeadingChild(
+    BoxConstraints childConstraints, {
+    bool parentUsesSize = false,
+  }) {
+    assert(_debugAssertChildListLocked());
+    final int index = indexOf(firstChild!) - 1;
+    _createOrObtainChild(index, after: null);
+    if (indexOf(firstChild!) == index) {
+      firstChild!.layout(childConstraints, parentUsesSize: parentUsesSize);
+      return firstChild;
+    }
+    childManager.setDidUnderflow(true);
+    return null;
+  }
+
+  /// Called during layout to create, add, and layout the child after
+  /// the given child.
+  ///
+  /// Calls [RenderSliverBoxChildManager.createChild] to actually create and add
+  /// the child if necessary. The child may instead be obtained from a cache;
+  /// see [SliverMultiBoxAdaptorParentData.keepAlive].
+  ///
+  /// Returns the new child. It is the responsibility of the caller to configure
+  /// the child's scroll offset.
+  ///
+  /// Children after the `after` child may be removed in the process. Only the
+  /// new child may be added.
+  @protected
+  RenderBox? insertAndLayoutChild(
+    BoxConstraints childConstraints, {
+    required RenderBox? after,
+    bool parentUsesSize = false,
+  }) {
+    assert(_debugAssertChildListLocked());
+    assert(after != null);
+    final int index = indexOf(after!) + 1;
+    _createOrObtainChild(index, after: after);
+    final RenderBox? child = childAfter(after);
+    if (child != null && indexOf(child) == index) {
+      child.layout(childConstraints, parentUsesSize: parentUsesSize);
+      return child;
+    }
+    childManager.setDidUnderflow(true);
+    return null;
+  }
+
+  /// Called after layout with the number of children that can be garbage
+  /// collected at the head and tail of the child list.
+  ///
+  /// Children whose [SliverMultiBoxAdaptorParentData.keepAlive] property is
+  /// set to true will be removed to a cache instead of being dropped.
+  ///
+  /// This method also collects any children that were previously kept alive but
+  /// are now no longer necessary. As such, it should be called every time
+  /// [performLayout] is run, even if the arguments are both zero.
+  @protected
+  void collectGarbage(int leadingGarbage, int trailingGarbage) {
+    assert(_debugAssertChildListLocked());
+    assert(childCount >= leadingGarbage + trailingGarbage);
+    invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
+      while (leadingGarbage > 0) {
+        _destroyOrCacheChild(firstChild!);
+        leadingGarbage -= 1;
+      }
+      while (trailingGarbage > 0) {
+        _destroyOrCacheChild(lastChild!);
+        trailingGarbage -= 1;
+      }
+      // Ask the child manager to remove the children that are no longer being
+      // kept alive. (This should cause _keepAliveBucket to change, so we have
+      // to prepare our list ahead of time.)
+      _keepAliveBucket.values.where((RenderBox child) {
+        final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
+        return !childParentData.keepAlive;
+      }).toList().forEach(_childManager.removeChild);
+      assert(_keepAliveBucket.values.where((RenderBox child) {
+        final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
+        return !childParentData.keepAlive;
+      }).isEmpty);
+    });
+  }
+
+  /// Returns the index of the given child, as given by the
+  /// [SliverMultiBoxAdaptorParentData.index] field of the child's [parentData].
+  int indexOf(RenderBox child) {
+    assert(child != null);
+    final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
+    assert(childParentData.index != null);
+    return childParentData.index!;
+  }
+
+  /// Returns the dimension of the given child in the main axis, as given by the
+  /// child's [RenderBox.size] property. This is only valid after layout.
+  @protected
+  double paintExtentOf(RenderBox child) {
+    assert(child != null);
+    assert(child.hasSize);
+    switch (constraints.axis) {
+      case Axis.horizontal:
+        return child.size.width;
+      case Axis.vertical:
+        return child.size.height;
+    }
+  }
+
+  @override
+  bool hitTestChildren(SliverHitTestResult result, { required double mainAxisPosition, required double crossAxisPosition }) {
+    RenderBox? child = lastChild;
+    final BoxHitTestResult boxResult = BoxHitTestResult.wrap(result);
+    while (child != null) {
+      if (hitTestBoxChild(boxResult, child, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition))
+        return true;
+      child = childBefore(child);
+    }
+    return false;
+  }
+
+  @override
+  double childMainAxisPosition(RenderBox child) {
+    return childScrollOffset(child)! - constraints.scrollOffset;
+  }
+
+  @override
+  double? childScrollOffset(RenderObject child) {
+    assert(child != null);
+    assert(child.parent == this);
+    final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
+    return childParentData.layoutOffset;
+  }
+
+  @override
+  void applyPaintTransform(RenderBox child, Matrix4 transform) {
+    if (_keepAliveBucket.containsKey(indexOf(child))) {
+      // It is possible that widgets under kept alive children want to paint
+      // themselves. For example, the Material widget tries to paint all
+      // InkFeatures under its subtree as long as they are not disposed. In
+      // such case, we give it a zero transform to prevent them from painting.
+      transform.setZero();
+    } else {
+      applyPaintTransformForBoxChild(child, transform);
+    }
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (firstChild == null)
+      return;
+    // offset is to the top-left corner, regardless of our axis direction.
+    // originOffset gives us the delta from the real origin to the origin in the axis direction.
+    final Offset mainAxisUnit, crossAxisUnit, originOffset;
+    final bool addExtent;
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+        mainAxisUnit = const Offset(0.0, -1.0);
+        crossAxisUnit = const Offset(1.0, 0.0);
+        originOffset = offset + Offset(0.0, geometry!.paintExtent);
+        addExtent = true;
+        break;
+      case AxisDirection.right:
+        mainAxisUnit = const Offset(1.0, 0.0);
+        crossAxisUnit = const Offset(0.0, 1.0);
+        originOffset = offset;
+        addExtent = false;
+        break;
+      case AxisDirection.down:
+        mainAxisUnit = const Offset(0.0, 1.0);
+        crossAxisUnit = const Offset(1.0, 0.0);
+        originOffset = offset;
+        addExtent = false;
+        break;
+      case AxisDirection.left:
+        mainAxisUnit = const Offset(-1.0, 0.0);
+        crossAxisUnit = const Offset(0.0, 1.0);
+        originOffset = offset + Offset(geometry!.paintExtent, 0.0);
+        addExtent = true;
+        break;
+    }
+    assert(mainAxisUnit != null);
+    assert(addExtent != null);
+    RenderBox? child = firstChild;
+    while (child != null) {
+      final double mainAxisDelta = childMainAxisPosition(child);
+      final double crossAxisDelta = childCrossAxisPosition(child);
+      Offset childOffset = Offset(
+        originOffset.dx + mainAxisUnit.dx * mainAxisDelta + crossAxisUnit.dx * crossAxisDelta,
+        originOffset.dy + mainAxisUnit.dy * mainAxisDelta + crossAxisUnit.dy * crossAxisDelta,
+      );
+      if (addExtent)
+        childOffset += mainAxisUnit * paintExtentOf(child);
+
+      // If the child's visible interval (mainAxisDelta, mainAxisDelta + paintExtentOf(child))
+      // does not intersect the paint extent interval (0, constraints.remainingPaintExtent), it's hidden.
+      if (mainAxisDelta < constraints.remainingPaintExtent && mainAxisDelta + paintExtentOf(child) > 0)
+        context.paintChild(child, childOffset);
+
+      child = childAfter(child);
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsNode.message(firstChild != null ? 'currently live children: ${indexOf(firstChild!)} to ${indexOf(lastChild!)}' : 'no children current live'));
+  }
+
+  /// Asserts that the reified child list is not empty and has a contiguous
+  /// sequence of indices.
+  ///
+  /// Always returns true.
+  bool debugAssertChildListIsNonEmptyAndContiguous() {
+    assert(() {
+      assert(firstChild != null);
+      int index = indexOf(firstChild!);
+      RenderBox? child = childAfter(firstChild!);
+      while (child != null) {
+        index += 1;
+        assert(indexOf(child) == index);
+        child = childAfter(child);
+      }
+      return true;
+    }());
+    return true;
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    final List<DiagnosticsNode> children = <DiagnosticsNode>[];
+    if (firstChild != null) {
+      RenderBox? child = firstChild;
+      while (true) {
+        final SliverMultiBoxAdaptorParentData childParentData = child!.parentData! as SliverMultiBoxAdaptorParentData;
+        children.add(child.toDiagnosticsNode(name: 'child with index ${childParentData.index}'));
+        if (child == lastChild)
+          break;
+        child = childParentData.nextSibling;
+      }
+    }
+    if (_keepAliveBucket.isNotEmpty) {
+      final List<int> indices = _keepAliveBucket.keys.toList()..sort();
+      for (final int index in indices) {
+        children.add(_keepAliveBucket[index]!.toDiagnosticsNode(
+          name: 'child with index $index (kept alive but not laid out)',
+          style: DiagnosticsTreeStyle.offstage,
+        ));
+      }
+    }
+    return children;
+  }
+}
diff --git a/lib/src/rendering/sliver_padding.dart b/lib/src/rendering/sliver_padding.dart
new file mode 100644
index 0000000..ffced9d
--- /dev/null
+++ b/lib/src/rendering/sliver_padding.dart
@@ -0,0 +1,382 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'debug.dart';
+import 'object.dart';
+import 'sliver.dart';
+
+/// Insets a [RenderSliver] by applying [resolvedPadding] on each side.
+///
+/// A [RenderSliverEdgeInsetsPadding] subclass wraps the [SliverGeometry.layoutExtent]
+/// of its child. Any incoming [SliverConstraints.overlap] is ignored and not
+/// passed on to the child.
+///
+/// {@template flutter.rendering.RenderSliverEdgeInsetsPadding}
+/// Applying padding in the main extent of the viewport to slivers that have scroll effects is likely to have
+/// undesired effects. For example, For example, wrapping a [SliverPersistentHeader] with
+/// `pinned:true` will cause only the appbar to stay pinned while the padding will scroll away.
+/// {@endtemplate}
+abstract class RenderSliverEdgeInsetsPadding extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
+  /// The amount to pad the child in each dimension.
+  ///
+  /// The offsets are specified in terms of visual edges, left, top, right, and
+  /// bottom. These values are not affected by the [TextDirection].
+  ///
+  /// Must not be null or contain negative values when [performLayout] is called.
+  EdgeInsets? get resolvedPadding;
+
+  /// The padding in the scroll direction on the side nearest the 0.0 scroll direction.
+  ///
+  /// Only valid after layout has started, since before layout the render object
+  /// doesn't know what direction it will be laid out in.
+  double get beforePadding {
+    assert(constraints != null);
+    assert(constraints.axisDirection != null);
+    assert(constraints.growthDirection != null);
+    assert(resolvedPadding != null);
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+        return resolvedPadding!.bottom;
+      case AxisDirection.right:
+        return resolvedPadding!.left;
+      case AxisDirection.down:
+        return resolvedPadding!.top;
+      case AxisDirection.left:
+        return resolvedPadding!.right;
+    }
+  }
+
+  /// The padding in the scroll direction on the side furthest from the 0.0 scroll offset.
+  ///
+  /// Only valid after layout has started, since before layout the render object
+  /// doesn't know what direction it will be laid out in.
+  double get afterPadding {
+    assert(constraints != null);
+    assert(constraints.axisDirection != null);
+    assert(constraints.growthDirection != null);
+    assert(resolvedPadding != null);
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+        return resolvedPadding!.top;
+      case AxisDirection.right:
+        return resolvedPadding!.right;
+      case AxisDirection.down:
+        return resolvedPadding!.bottom;
+      case AxisDirection.left:
+        return resolvedPadding!.left;
+    }
+  }
+
+  /// The total padding in the [SliverConstraints.axisDirection]. (In other
+  /// words, for a vertical downwards-growing list, the sum of the padding on
+  /// the top and bottom.)
+  ///
+  /// Only valid after layout has started, since before layout the render object
+  /// doesn't know what direction it will be laid out in.
+  double get mainAxisPadding {
+    assert(constraints != null);
+    assert(constraints.axis != null);
+    assert(resolvedPadding != null);
+    return resolvedPadding!.along(constraints.axis);
+  }
+
+  /// The total padding in the cross-axis direction. (In other words, for a
+  /// vertical downwards-growing list, the sum of the padding on the left and
+  /// right.)
+  ///
+  /// Only valid after layout has started, since before layout the render object
+  /// doesn't know what direction it will be laid out in.
+  double get crossAxisPadding {
+    assert(constraints != null);
+    assert(constraints.axis != null);
+    assert(resolvedPadding != null);
+    switch (constraints.axis) {
+      case Axis.horizontal:
+        return resolvedPadding!.vertical;
+      case Axis.vertical:
+        return resolvedPadding!.horizontal;
+    }
+  }
+
+  @override
+  void setupParentData(RenderObject child) {
+    if (child.parentData is! SliverPhysicalParentData)
+      child.parentData = SliverPhysicalParentData();
+  }
+
+  @override
+  void performLayout() {
+    final SliverConstraints constraints = this.constraints;
+    assert(resolvedPadding != null);
+    final double beforePadding = this.beforePadding;
+    final double afterPadding = this.afterPadding;
+    final double mainAxisPadding = this.mainAxisPadding;
+    final double crossAxisPadding = this.crossAxisPadding;
+    if (child == null) {
+      geometry = SliverGeometry(
+        scrollExtent: mainAxisPadding,
+        paintExtent: math.min(mainAxisPadding, constraints.remainingPaintExtent),
+        maxPaintExtent: mainAxisPadding,
+      );
+      return;
+    }
+    final double beforePaddingPaintExtent = calculatePaintOffset(
+      constraints,
+      from: 0.0,
+      to: beforePadding,
+    );
+    double overlap = constraints.overlap;
+    if (overlap > 0) {
+      overlap = math.max(0.0, constraints.overlap - beforePaddingPaintExtent);
+    }
+    child!.layout(
+      constraints.copyWith(
+        scrollOffset: math.max(0.0, constraints.scrollOffset - beforePadding),
+        cacheOrigin: math.min(0.0, constraints.cacheOrigin + beforePadding),
+        overlap: overlap,
+        remainingPaintExtent: constraints.remainingPaintExtent - calculatePaintOffset(constraints, from: 0.0, to: beforePadding),
+        remainingCacheExtent: constraints.remainingCacheExtent - calculateCacheOffset(constraints, from: 0.0, to: beforePadding),
+        crossAxisExtent: math.max(0.0, constraints.crossAxisExtent - crossAxisPadding),
+        precedingScrollExtent: beforePadding + constraints.precedingScrollExtent,
+      ),
+      parentUsesSize: true,
+    );
+    final SliverGeometry childLayoutGeometry = child!.geometry!;
+    if (childLayoutGeometry.scrollOffsetCorrection != null) {
+      geometry = SliverGeometry(
+        scrollOffsetCorrection: childLayoutGeometry.scrollOffsetCorrection,
+      );
+      return;
+    }
+    final double afterPaddingPaintExtent = calculatePaintOffset(
+      constraints,
+      from: beforePadding + childLayoutGeometry.scrollExtent,
+      to: mainAxisPadding + childLayoutGeometry.scrollExtent,
+    );
+    final double mainAxisPaddingPaintExtent = beforePaddingPaintExtent + afterPaddingPaintExtent;
+    final double beforePaddingCacheExtent = calculateCacheOffset(
+      constraints,
+      from: 0.0,
+      to: beforePadding,
+    );
+    final double afterPaddingCacheExtent = calculateCacheOffset(
+      constraints,
+      from: beforePadding + childLayoutGeometry.scrollExtent,
+      to: mainAxisPadding + childLayoutGeometry.scrollExtent,
+    );
+    final double mainAxisPaddingCacheExtent = afterPaddingCacheExtent + beforePaddingCacheExtent;
+    final double paintExtent = math.min(
+      beforePaddingPaintExtent + math.max(childLayoutGeometry.paintExtent, childLayoutGeometry.layoutExtent + afterPaddingPaintExtent),
+      constraints.remainingPaintExtent,
+    );
+    geometry = SliverGeometry(
+      paintOrigin: childLayoutGeometry.paintOrigin,
+      scrollExtent: mainAxisPadding + childLayoutGeometry.scrollExtent,
+      paintExtent: paintExtent,
+      layoutExtent: math.min(mainAxisPaddingPaintExtent + childLayoutGeometry.layoutExtent, paintExtent),
+      cacheExtent: math.min(mainAxisPaddingCacheExtent + childLayoutGeometry.cacheExtent, constraints.remainingCacheExtent),
+      maxPaintExtent: mainAxisPadding + childLayoutGeometry.maxPaintExtent,
+      hitTestExtent: math.max(
+        mainAxisPaddingPaintExtent + childLayoutGeometry.paintExtent,
+        beforePaddingPaintExtent + childLayoutGeometry.hitTestExtent,
+      ),
+      hasVisualOverflow: childLayoutGeometry.hasVisualOverflow,
+    );
+
+    final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
+    assert(constraints.axisDirection != null);
+    assert(constraints.growthDirection != null);
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+        childParentData.paintOffset = Offset(resolvedPadding!.left, calculatePaintOffset(constraints, from: resolvedPadding!.bottom + childLayoutGeometry.scrollExtent, to: resolvedPadding!.bottom + childLayoutGeometry.scrollExtent + resolvedPadding!.top));
+        break;
+      case AxisDirection.right:
+        childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: 0.0, to: resolvedPadding!.left), resolvedPadding!.top);
+        break;
+      case AxisDirection.down:
+        childParentData.paintOffset = Offset(resolvedPadding!.left, calculatePaintOffset(constraints, from: 0.0, to: resolvedPadding!.top));
+        break;
+      case AxisDirection.left:
+        childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: resolvedPadding!.right + childLayoutGeometry.scrollExtent, to: resolvedPadding!.right + childLayoutGeometry.scrollExtent + resolvedPadding!.left), resolvedPadding!.top);
+        break;
+    }
+    assert(childParentData.paintOffset != null);
+    assert(beforePadding == this.beforePadding);
+    assert(afterPadding == this.afterPadding);
+    assert(mainAxisPadding == this.mainAxisPadding);
+    assert(crossAxisPadding == this.crossAxisPadding);
+  }
+
+  @override
+  bool hitTestChildren(SliverHitTestResult result, { required double mainAxisPosition, required double crossAxisPosition }) {
+    if (child != null && child!.geometry!.hitTestExtent > 0.0) {
+      final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
+      result.addWithAxisOffset(
+        mainAxisPosition: mainAxisPosition,
+        crossAxisPosition: crossAxisPosition,
+        mainAxisOffset: childMainAxisPosition(child!),
+        crossAxisOffset: childCrossAxisPosition(child!),
+        paintOffset: childParentData.paintOffset,
+        hitTest: child!.hitTest,
+      );
+    }
+    return false;
+  }
+
+  @override
+  double childMainAxisPosition(RenderSliver child) {
+    assert(child != null);
+    assert(child == this.child);
+    return calculatePaintOffset(constraints, from: 0.0, to: beforePadding);
+  }
+
+  @override
+  double childCrossAxisPosition(RenderSliver child) {
+    assert(child != null);
+    assert(child == this.child);
+    assert(constraints != null);
+    assert(constraints.axisDirection != null);
+    assert(constraints.growthDirection != null);
+    assert(resolvedPadding != null);
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+      case AxisDirection.down:
+        return resolvedPadding!.left;
+      case AxisDirection.left:
+      case AxisDirection.right:
+        return resolvedPadding!.top;
+    }
+  }
+
+  @override
+  double? childScrollOffset(RenderObject child) {
+    assert(child.parent == this);
+    return beforePadding;
+  }
+
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) {
+    assert(child != null);
+    assert(child == this.child);
+    final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
+    childParentData.applyPaintTransform(transform);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null && child!.geometry!.visible) {
+      final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
+      context.paintChild(child!, offset + childParentData.paintOffset);
+    }
+  }
+
+  @override
+  void debugPaint(PaintingContext context, Offset offset) {
+    super.debugPaint(context, offset);
+    assert(() {
+      if (debugPaintSizeEnabled) {
+        final Size parentSize = getAbsoluteSize();
+        final Rect outerRect = offset & parentSize;
+        Rect? innerRect;
+        if (child != null) {
+          final Size childSize = child!.getAbsoluteSize();
+          final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
+          innerRect = (offset + childParentData.paintOffset) & childSize;
+          assert(innerRect.top >= outerRect.top);
+          assert(innerRect.left >= outerRect.left);
+          assert(innerRect.right <= outerRect.right);
+          assert(innerRect.bottom <= outerRect.bottom);
+        }
+        debugPaintPadding(context.canvas, outerRect, innerRect);
+      }
+      return true;
+    }());
+  }
+}
+
+/// Insets a [RenderSliver], applying padding on each side.
+///
+/// A [RenderSliverPadding] object wraps the [SliverGeometry.layoutExtent] of
+/// its child. Any incoming [SliverConstraints.overlap] is ignored and not
+/// passed on to the child.
+///
+/// {@macro flutter.rendering.RenderSliverEdgeInsetsPadding}
+class RenderSliverPadding extends RenderSliverEdgeInsetsPadding {
+  /// Creates a render object that insets its child in a viewport.
+  ///
+  /// The [padding] argument must not be null and must have non-negative insets.
+  RenderSliverPadding({
+    required EdgeInsetsGeometry padding,
+    TextDirection? textDirection,
+    RenderSliver? child,
+  }) : assert(padding != null),
+       assert(padding.isNonNegative),
+       _padding = padding,
+       _textDirection = textDirection {
+    this.child = child;
+  }
+
+  @override
+  EdgeInsets? get resolvedPadding => _resolvedPadding;
+  EdgeInsets? _resolvedPadding;
+
+  void _resolve() {
+    if (resolvedPadding != null)
+      return;
+    _resolvedPadding = padding.resolve(textDirection);
+    assert(resolvedPadding!.isNonNegative);
+  }
+
+  void _markNeedsResolution() {
+    _resolvedPadding = null;
+    markNeedsLayout();
+  }
+
+  /// The amount to pad the child in each dimension.
+  ///
+  /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
+  /// must not be null.
+  EdgeInsetsGeometry get padding => _padding;
+  EdgeInsetsGeometry _padding;
+  set padding(EdgeInsetsGeometry value) {
+    assert(value != null);
+    assert(padding.isNonNegative);
+    if (_padding == value)
+      return;
+    _padding = value;
+    _markNeedsResolution();
+  }
+
+  /// The text direction with which to resolve [padding].
+  ///
+  /// This may be changed to null, but only after the [padding] has been changed
+  /// to a value that does not depend on the direction.
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection;
+  set textDirection(TextDirection? value) {
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    _markNeedsResolution();
+  }
+
+  @override
+  void performLayout() {
+    _resolve();
+    super.performLayout();
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+  }
+}
diff --git a/lib/src/rendering/sliver_persistent_header.dart b/lib/src/rendering/sliver_persistent_header.dart
new file mode 100644
index 0000000..bd74371
--- /dev/null
+++ b/lib/src/rendering/sliver_persistent_header.dart
@@ -0,0 +1,846 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/semantics.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'box.dart';
+import 'object.dart';
+import 'sliver.dart';
+import 'viewport.dart';
+import 'viewport_offset.dart';
+
+// Trims the specified edges of the given `Rect` [original], so that they do not
+// exceed the given values.
+Rect? _trim(Rect? original, {
+  double top = -double.infinity,
+  double right = double.infinity,
+  double bottom = double.infinity,
+  double left = -double.infinity,
+}) => original?.intersect(Rect.fromLTRB(left, top, right, bottom));
+
+/// Specifies how a stretched header is to trigger an [AsyncCallback].
+///
+/// See also:
+///
+///  * [SliverAppBar], which creates a header that can be stretched into an
+///    overscroll area and trigger a callback function.
+class OverScrollHeaderStretchConfiguration {
+  /// Creates an object that specifies how a stretched header may activate an
+  /// [AsyncCallback].
+  OverScrollHeaderStretchConfiguration({
+    this.stretchTriggerOffset = 100.0,
+    this.onStretchTrigger,
+  }) : assert(stretchTriggerOffset != null);
+
+  /// The offset of overscroll required to trigger the [onStretchTrigger].
+  final double stretchTriggerOffset;
+
+  /// The callback function to be executed when a user over-scrolls to the
+  /// offset specified by [stretchTriggerOffset].
+  final AsyncCallback? onStretchTrigger;
+}
+
+/// {@template flutter.rendering.PersistentHeaderShowOnScreenConfiguration}
+/// Specifies how a pinned header or a floating header should react to
+/// [RenderObject.showOnScreen] calls.
+/// {@endtemplate}
+@immutable
+class PersistentHeaderShowOnScreenConfiguration {
+  /// Creates an object that specifies how a pinned or floating persistent header
+  /// should behave in response to [RenderObject.showOnScreen] calls.
+  const PersistentHeaderShowOnScreenConfiguration({
+    this.minShowOnScreenExtent = double.negativeInfinity,
+    this.maxShowOnScreenExtent = double.infinity,
+  }) : assert(minShowOnScreenExtent <= maxShowOnScreenExtent);
+
+  /// The smallest the floating header can expand to in the main axis direction,
+  /// in response to a [RenderObject.showOnScreen] call, in addition to its
+  /// [RenderSliverPersistentHeader.minExtent].
+  ///
+  /// When a floating persistent header is told to show a [Rect] on screen, it
+  /// may expand itself to accommodate the [Rect]. The minimum extent that is
+  /// allowed for such expansion is either
+  /// [RenderSliverPersistentHeader.minExtent] or [minShowOnScreenExtent],
+  /// whichever is larger. If the persistent header's current extent is already
+  /// larger than that maximum extent, it will remain unchanged.
+  ///
+  /// This parameter can be set to the persistent header's `maxExtent` (or
+  /// `double.infinity`) so the persistent header will always try to expand when
+  /// [RenderObject.showOnScreen] is called on it.
+  ///
+  /// Defaults to [double.negativeInfinity], must be less than or equal to
+  /// [maxShowOnScreenExtent]. Has no effect unless the persistent header is a
+  /// floating header.
+  final double minShowOnScreenExtent;
+
+  /// The biggest the floating header can expand to in the main axis direction,
+  /// in response to a [RenderObject.showOnScreen] call, in addition to its
+  /// [RenderSliverPersistentHeader.maxExtent].
+  ///
+  /// When a floating persistent header is told to show a [Rect] on screen, it
+  /// may expand itself to accommodate the [Rect]. The maximum extent that is
+  /// allowed for such expansion is either
+  /// [RenderSliverPersistentHeader.maxExtent] or [maxShowOnScreenExtent],
+  /// whichever is smaller. If the persistent header's current extent is already
+  /// larger than that maximum extent, it will remain unchanged.
+  ///
+  /// This parameter can be set to the persistent header's `minExtent` (or
+  /// `double.negativeInfinity`) so the persistent header will never try to
+  /// expand when [RenderObject.showOnScreen] is called on it.
+  ///
+  /// Defaults to [double.infinity], must be greater than or equal to
+  /// [minShowOnScreenExtent]. Has no effect unless the persistent header is a
+  /// floating header.
+  final double maxShowOnScreenExtent;
+}
+
+/// A base class for slivers that have a [RenderBox] child which scrolls
+/// normally, except that when it hits the leading edge (typically the top) of
+/// the viewport, it shrinks to a minimum size ([minExtent]).
+///
+/// This class primarily provides helpers for managing the child, in particular:
+///
+///  * [layoutChild], which applies min and max extents and a scroll offset to
+///    lay out the child. This is normally called from [performLayout].
+///
+///  * [childExtent], to convert the child's box layout dimensions to the sliver
+///    geometry model.
+///
+///  * hit testing, painting, and other details of the sliver protocol.
+///
+/// Subclasses must implement [performLayout], [minExtent], and [maxExtent], and
+/// typically also will implement [updateChild].
+abstract class RenderSliverPersistentHeader extends RenderSliver with RenderObjectWithChildMixin<RenderBox>, RenderSliverHelpers {
+  /// Creates a sliver that changes its size when scrolled to the start of the
+  /// viewport.
+  ///
+  /// This is an abstract class; this constructor only initializes the [child].
+  RenderSliverPersistentHeader({
+    RenderBox? child,
+    this.stretchConfiguration,
+  }) {
+    this.child = child;
+  }
+
+  late double _lastStretchOffset;
+
+  /// The biggest that this render object can become, in the main axis direction.
+  ///
+  /// This value should not be based on the child. If it changes, call
+  /// [markNeedsLayout].
+  double get maxExtent;
+
+  /// The smallest that this render object can become, in the main axis direction.
+  ///
+  /// If this is based on the intrinsic dimensions of the child, the child
+  /// should be measured during [updateChild] and the value cached and returned
+  /// here. The [updateChild] method will automatically be invoked any time the
+  /// child changes its intrinsic dimensions.
+  double get minExtent;
+
+  /// The dimension of the child in the main axis.
+  @protected
+  double get childExtent {
+    if (child == null)
+      return 0.0;
+    assert(child!.hasSize);
+    assert(constraints.axis != null);
+    switch (constraints.axis) {
+      case Axis.vertical:
+        return child!.size.height;
+      case Axis.horizontal:
+        return child!.size.width;
+    }
+  }
+
+  bool _needsUpdateChild = true;
+  double _lastShrinkOffset = 0.0;
+  bool _lastOverlapsContent = false;
+
+  /// Defines the parameters used to execute an [AsyncCallback] when a
+  /// stretching header over-scrolls.
+  ///
+  /// If [stretchConfiguration] is null then callback is not triggered.
+  ///
+  /// See also:
+  ///
+  ///  * [SliverAppBar], which creates a header that can stretched into an
+  ///    overscroll area and trigger a callback function.
+  OverScrollHeaderStretchConfiguration? stretchConfiguration;
+
+  /// Update the child render object if necessary.
+  ///
+  /// Called before the first layout, any time [markNeedsLayout] is called, and
+  /// any time the scroll offset changes. The `shrinkOffset` is the difference
+  /// between the [maxExtent] and the current size. Zero means the header is
+  /// fully expanded, any greater number up to [maxExtent] means that the header
+  /// has been scrolled by that much. The `overlapsContent` argument is true if
+  /// the sliver's leading edge is beyond its normal place in the viewport
+  /// contents, and false otherwise. It may still paint beyond its normal place
+  /// if the [minExtent] after this call is greater than the amount of space that
+  /// would normally be left.
+  ///
+  /// The render object will size itself to the larger of (a) the [maxExtent]
+  /// minus the child's intrinsic height and (b) the [maxExtent] minus the
+  /// shrink offset.
+  ///
+  /// When this method is called by [layoutChild], the [child] can be set,
+  /// mutated, or replaced. (It should not be called outside [layoutChild].)
+  ///
+  /// Any time this method would mutate the child, call [markNeedsLayout].
+  @protected
+  void updateChild(double shrinkOffset, bool overlapsContent) { }
+
+  @override
+  void markNeedsLayout() {
+    // This is automatically called whenever the child's intrinsic dimensions
+    // change, at which point we should remeasure them during the next layout.
+    _needsUpdateChild = true;
+    super.markNeedsLayout();
+  }
+
+  /// Lays out the [child].
+  ///
+  /// This is called by [performLayout]. It applies the given `scrollOffset`
+  /// (which need not match the offset given by the [constraints]) and the
+  /// `maxExtent` (which need not match the value returned by the [maxExtent]
+  /// getter).
+  ///
+  /// The `overlapsContent` argument is passed to [updateChild].
+  @protected
+  void layoutChild(double scrollOffset, double maxExtent, { bool overlapsContent = false }) {
+    assert(maxExtent != null);
+    final double shrinkOffset = math.min(scrollOffset, maxExtent);
+    if (_needsUpdateChild || _lastShrinkOffset != shrinkOffset || _lastOverlapsContent != overlapsContent) {
+      invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
+        assert(constraints == this.constraints);
+        updateChild(shrinkOffset, overlapsContent);
+      });
+      _lastShrinkOffset = shrinkOffset;
+      _lastOverlapsContent = overlapsContent;
+      _needsUpdateChild = false;
+    }
+    assert(minExtent != null);
+    assert(() {
+      if (minExtent <= maxExtent)
+        return true;
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('The maxExtent for this $runtimeType is less than its minExtent.'),
+        DoubleProperty('The specified maxExtent was', maxExtent),
+        DoubleProperty('The specified minExtent was', minExtent),
+      ]);
+    }());
+    double stretchOffset = 0.0;
+    if (stretchConfiguration != null && constraints.scrollOffset == 0.0) {
+      stretchOffset += constraints.overlap.abs();
+    }
+
+    child?.layout(
+      constraints.asBoxConstraints(
+        maxExtent: math.max(minExtent, maxExtent - shrinkOffset) + stretchOffset,
+      ),
+      parentUsesSize: true,
+    );
+
+    if (stretchConfiguration != null &&
+      stretchConfiguration!.onStretchTrigger != null &&
+      stretchOffset >= stretchConfiguration!.stretchTriggerOffset &&
+      _lastStretchOffset <= stretchConfiguration!.stretchTriggerOffset) {
+      stretchConfiguration!.onStretchTrigger!();
+    }
+    _lastStretchOffset = stretchOffset;
+  }
+
+  /// Returns the distance from the leading _visible_ edge of the sliver to the
+  /// side of the child closest to that edge, in the scroll axis direction.
+  ///
+  /// For example, if the [constraints] describe this sliver as having an axis
+  /// direction of [AxisDirection.down], then this is the distance from the top
+  /// of the visible portion of the sliver to the top of the child. If the child
+  /// is scrolled partially off the top of the viewport, then this will be
+  /// negative. On the other hand, if the [constraints] describe this sliver as
+  /// having an axis direction of [AxisDirection.up], then this is the distance
+  /// from the bottom of the visible portion of the sliver to the bottom of the
+  /// child. In both cases, this is the direction of increasing
+  /// [SliverConstraints.scrollOffset].
+  ///
+  /// Calling this when the child is not visible is not valid.
+  ///
+  /// The argument must be the value of the [child] property.
+  ///
+  /// This must be implemented by [RenderSliverPersistentHeader] subclasses.
+  ///
+  /// If there is no child, this should return 0.0.
+  @override
+  double childMainAxisPosition(covariant RenderObject child) => super.childMainAxisPosition(child);
+
+  @override
+  bool hitTestChildren(SliverHitTestResult result, { required double mainAxisPosition, required double crossAxisPosition }) {
+    assert(geometry!.hitTestExtent > 0.0);
+    if (child != null)
+      return hitTestBoxChild(BoxHitTestResult.wrap(result), child!, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition);
+    return false;
+  }
+
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) {
+    assert(child != null);
+    assert(child == this.child);
+    applyPaintTransformForBoxChild(child as RenderBox, transform);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null && geometry!.visible) {
+      assert(constraints.axisDirection != null);
+      switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+        case AxisDirection.up:
+          offset += Offset(0.0, geometry!.paintExtent - childMainAxisPosition(child!) - childExtent);
+          break;
+        case AxisDirection.down:
+          offset += Offset(0.0, childMainAxisPosition(child!));
+          break;
+        case AxisDirection.left:
+          offset += Offset(geometry!.paintExtent - childMainAxisPosition(child!) - childExtent, 0.0);
+          break;
+        case AxisDirection.right:
+          offset += Offset(childMainAxisPosition(child!), 0.0);
+          break;
+      }
+      context.paintChild(child!, offset);
+    }
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    config.addTagForChildren(RenderViewport.excludeFromScrolling);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty.lazy('maxExtent', () => maxExtent));
+    properties.add(DoubleProperty.lazy('child position', () => childMainAxisPosition(child!)));
+  }
+}
+
+/// A sliver with a [RenderBox] child which scrolls normally, except that when
+/// it hits the leading edge (typically the top) of the viewport, it shrinks to
+/// a minimum size before continuing to scroll.
+///
+/// This sliver makes no effort to avoid overlapping other content.
+abstract class RenderSliverScrollingPersistentHeader extends RenderSliverPersistentHeader {
+  /// Creates a sliver that shrinks when it hits the start of the viewport, then
+  /// scrolls off.
+  RenderSliverScrollingPersistentHeader({
+    RenderBox? child,
+    OverScrollHeaderStretchConfiguration? stretchConfiguration,
+  }) : super(
+    child: child,
+    stretchConfiguration: stretchConfiguration,
+  );
+
+  // Distance from our leading edge to the child's leading edge, in the axis
+  // direction. Negative if we're scrolled off the top.
+  double? _childPosition;
+
+  /// Updates [geometry], and returns the new value for [childMainAxisPosition].
+  ///
+  /// This is used by [performLayout].
+  @protected
+  double updateGeometry() {
+    double stretchOffset = 0.0;
+    if (stretchConfiguration != null && _childPosition == 0.0) {
+      stretchOffset += constraints.overlap.abs();
+    }
+    final double maxExtent = this.maxExtent;
+    final double paintExtent = maxExtent - constraints.scrollOffset;
+    geometry = SliverGeometry(
+      scrollExtent: maxExtent,
+      paintOrigin: math.min(constraints.overlap, 0.0),
+      paintExtent: paintExtent.clamp(0.0, constraints.remainingPaintExtent),
+      maxPaintExtent: maxExtent + stretchOffset,
+      hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
+    );
+    return stretchOffset > 0 ? 0.0 : math.min(0.0, paintExtent - childExtent);
+  }
+
+
+  @override
+  void performLayout() {
+    final SliverConstraints constraints = this.constraints;
+    final double maxExtent = this.maxExtent;
+    layoutChild(constraints.scrollOffset, maxExtent);
+    final double paintExtent = maxExtent - constraints.scrollOffset;
+    geometry = SliverGeometry(
+      scrollExtent: maxExtent,
+      paintOrigin: math.min(constraints.overlap, 0.0),
+      paintExtent: paintExtent.clamp(0.0, constraints.remainingPaintExtent),
+      maxPaintExtent: maxExtent,
+      hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
+    );
+    _childPosition = updateGeometry();
+  }
+
+  @override
+  double childMainAxisPosition(RenderBox child) {
+    assert(child == this.child);
+    assert(_childPosition != null);
+    return _childPosition!;
+  }
+}
+
+/// A sliver with a [RenderBox] child which never scrolls off the viewport in
+/// the positive scroll direction, and which first scrolls on at a full size but
+/// then shrinks as the viewport continues to scroll.
+///
+/// This sliver avoids overlapping other earlier slivers where possible.
+abstract class RenderSliverPinnedPersistentHeader extends RenderSliverPersistentHeader {
+  /// Creates a sliver that shrinks when it hits the start of the viewport, then
+  /// stays pinned there.
+  RenderSliverPinnedPersistentHeader({
+    RenderBox? child,
+    OverScrollHeaderStretchConfiguration? stretchConfiguration,
+    this.showOnScreenConfiguration = const PersistentHeaderShowOnScreenConfiguration(),
+  }) : super(
+    child: child,
+    stretchConfiguration: stretchConfiguration,
+  );
+
+  /// Specifies the persistent header's behavior when `showOnScreen` is called.
+  ///
+  /// If set to null, the persistent header will delegate the `showOnScreen` call
+  /// to it's parent [RenderObject].
+  PersistentHeaderShowOnScreenConfiguration? showOnScreenConfiguration;
+
+  @override
+  void performLayout() {
+    final SliverConstraints constraints = this.constraints;
+    final double maxExtent = this.maxExtent;
+    final bool overlapsContent = constraints.overlap > 0.0;
+    layoutChild(constraints.scrollOffset, maxExtent, overlapsContent: overlapsContent);
+    final double effectiveRemainingPaintExtent = math.max(0, constraints.remainingPaintExtent - constraints.overlap);
+    final double layoutExtent = (maxExtent - constraints.scrollOffset).clamp(0.0, effectiveRemainingPaintExtent);
+    final double stretchOffset = stretchConfiguration != null ?
+      constraints.overlap.abs() :
+      0.0;
+    geometry = SliverGeometry(
+      scrollExtent: maxExtent,
+      paintOrigin: constraints.overlap,
+      paintExtent: math.min(childExtent, effectiveRemainingPaintExtent),
+      layoutExtent: layoutExtent,
+      maxPaintExtent: maxExtent + stretchOffset,
+      maxScrollObstructionExtent: minExtent,
+      cacheExtent: layoutExtent > 0.0 ? -constraints.cacheOrigin + layoutExtent : layoutExtent,
+      hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
+    );
+  }
+
+  @override
+  double childMainAxisPosition(RenderBox child) => 0.0;
+
+  @override
+  void showOnScreen({
+    RenderObject? descendant,
+    Rect? rect,
+    Duration duration = Duration.zero,
+    Curve curve = Curves.ease,
+  }) {
+    final Rect? localBounds = descendant != null
+      ? MatrixUtils.transformRect(descendant.getTransformTo(this), rect ?? descendant.paintBounds)
+      : rect;
+
+    Rect? newRect;
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+        newRect = _trim(localBounds, bottom: childExtent);
+        break;
+      case AxisDirection.right:
+        newRect = _trim(localBounds, left: 0);
+        break;
+      case AxisDirection.down:
+        newRect = _trim(localBounds, top: 0);
+        break;
+      case AxisDirection.left:
+        newRect = _trim(localBounds, right: childExtent);
+        break;
+    }
+
+    super.showOnScreen(
+      descendant: this,
+      rect: newRect,
+      duration: duration,
+      curve: curve,
+    );
+  }
+}
+
+/// Specifies how a floating header is to be "snapped" (animated) into or out
+/// of view.
+///
+/// See also:
+///
+///  * [RenderSliverFloatingPersistentHeader.maybeStartSnapAnimation] and
+///    [RenderSliverFloatingPersistentHeader.maybeStopSnapAnimation], which
+///    start or stop the floating header's animation.
+///  * [SliverAppBar], which creates a header that can be pinned, floating,
+///    and snapped into view via the corresponding parameters.
+class FloatingHeaderSnapConfiguration {
+  /// Creates an object that specifies how a floating header is to be "snapped"
+  /// (animated) into or out of view.
+  FloatingHeaderSnapConfiguration({
+    @Deprecated(
+      'Specify SliverPersistentHeaderDelegate.vsync instead. '
+      'This feature was deprecated after v1.19.0.'
+    )
+    this.vsync,
+    this.curve = Curves.ease,
+    this.duration = const Duration(milliseconds: 300),
+  }) : assert(curve != null),
+       assert(duration != null);
+
+  /// The [TickerProvider] for the [AnimationController] that causes a floating
+  /// header to snap in or out of view.
+  @Deprecated(
+    'Specify SliverPersistentHeaderDelegate.vsync instead. '
+    'This feature was deprecated after v1.19.0.'
+  )
+  final TickerProvider? vsync;
+
+  /// The snap animation curve.
+  final Curve curve;
+
+  /// The snap animation's duration.
+  final Duration duration;
+}
+
+/// A sliver with a [RenderBox] child which shrinks and scrolls like a
+/// [RenderSliverScrollingPersistentHeader], but immediately comes back when the
+/// user scrolls in the reverse direction.
+///
+/// See also:
+///
+///  * [RenderSliverFloatingPinnedPersistentHeader], which is similar but sticks
+///    to the start of the viewport rather than scrolling off.
+abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersistentHeader {
+  /// Creates a sliver that shrinks when it hits the start of the viewport, then
+  /// scrolls off, and comes back immediately when the user reverses the scroll
+  /// direction.
+  RenderSliverFloatingPersistentHeader({
+    RenderBox? child,
+    TickerProvider? vsync,
+    this.snapConfiguration,
+    OverScrollHeaderStretchConfiguration? stretchConfiguration,
+    required this.showOnScreenConfiguration,
+  }) : _vsync = vsync,
+       super(
+    child: child,
+    stretchConfiguration: stretchConfiguration,
+  );
+
+  AnimationController? _controller;
+  late Animation<double> _animation;
+  double? _lastActualScrollOffset;
+  double? _effectiveScrollOffset;
+
+  // Distance from our leading edge to the child's leading edge, in the axis
+  // direction. Negative if we're scrolled off the top.
+  double? _childPosition;
+
+  @override
+  void detach() {
+    _controller?.dispose();
+    _controller = null; // lazily recreated if we're reattached.
+    super.detach();
+  }
+
+
+  /// A [TickerProvider] to use when animating the scroll position.
+  TickerProvider? get vsync => _vsync;
+  TickerProvider? _vsync;
+  set vsync(TickerProvider? value) {
+    if (value == _vsync)
+      return;
+    _vsync = value;
+    if (value == null) {
+      _controller?.dispose();
+      _controller = null;
+    } else {
+      _controller?.resync(value);
+    }
+  }
+
+  /// Defines the parameters used to snap (animate) the floating header in and
+  /// out of view.
+  ///
+  /// If [snapConfiguration] is null then the floating header does not snap.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderSliverFloatingPersistentHeader.maybeStartSnapAnimation] and
+  ///    [RenderSliverFloatingPersistentHeader.maybeStopSnapAnimation], which
+  ///    start or stop the floating header's animation.
+  ///  * [SliverAppBar], which creates a header that can be pinned, floating,
+  ///    and snapped into view via the corresponding parameters.
+  FloatingHeaderSnapConfiguration? snapConfiguration;
+
+  /// {@macro flutter.rendering.PersistentHeaderShowOnScreenConfiguration}
+  ///
+  /// If set to null, the persistent header will delegate the `showOnScreen` call
+  /// to it's parent [RenderObject].
+  PersistentHeaderShowOnScreenConfiguration? showOnScreenConfiguration;
+
+  /// Updates [geometry], and returns the new value for [childMainAxisPosition].
+  ///
+  /// This is used by [performLayout].
+  @protected
+  double updateGeometry() {
+    double stretchOffset = 0.0;
+    if (stretchConfiguration != null && _childPosition == 0.0) {
+      stretchOffset += constraints.overlap.abs();
+    }
+    final double maxExtent = this.maxExtent;
+    final double paintExtent = maxExtent - _effectiveScrollOffset!;
+    final double layoutExtent = maxExtent - constraints.scrollOffset;
+    geometry = SliverGeometry(
+      scrollExtent: maxExtent,
+      paintOrigin: math.min(constraints.overlap, 0.0),
+      paintExtent: paintExtent.clamp(0.0, constraints.remainingPaintExtent),
+      layoutExtent: layoutExtent.clamp(0.0, constraints.remainingPaintExtent),
+      maxPaintExtent: maxExtent + stretchOffset,
+      hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
+    );
+    return stretchOffset > 0 ? 0.0 : math.min(0.0, paintExtent - childExtent);
+  }
+
+  void _updateAnimation(Duration duration, double endValue, Curve curve) {
+    assert(duration != null);
+    assert(endValue != null);
+    assert(curve != null);
+    assert(
+      vsync != null,
+      'vsync must not be null if the floating header changes size animatedly.',
+    );
+
+    final AnimationController effectiveController =
+      _controller ??= AnimationController(vsync: vsync!, duration: duration)
+                        ..addListener(() {
+      if (_effectiveScrollOffset == _animation.value)
+        return;
+      _effectiveScrollOffset = _animation.value;
+      markNeedsLayout();
+    });
+
+    _animation = effectiveController.drive(
+      Tween<double>(
+        begin: _effectiveScrollOffset,
+        end: endValue,
+      ).chain(CurveTween(curve: curve)),
+    );
+  }
+
+  /// If the header isn't already fully exposed, then scroll it into view.
+  void maybeStartSnapAnimation(ScrollDirection direction) {
+    final FloatingHeaderSnapConfiguration? snap = snapConfiguration;
+    if (snap == null)
+      return;
+    if (direction == ScrollDirection.forward && _effectiveScrollOffset! <= 0.0)
+      return;
+    if (direction == ScrollDirection.reverse && _effectiveScrollOffset! >= maxExtent)
+      return;
+
+    _updateAnimation(
+      snap.duration,
+      direction == ScrollDirection.forward ? 0.0 : maxExtent,
+      snap.curve,
+    );
+    _controller?.forward(from: 0.0);
+  }
+
+  /// If a header snap animation or a [showOnScreen] expand animation is underway
+  /// then stop it.
+  void maybeStopSnapAnimation(ScrollDirection direction) {
+    _controller?.stop();
+  }
+
+  @override
+  void performLayout() {
+    final SliverConstraints constraints = this.constraints;
+    final double maxExtent = this.maxExtent;
+    if (_lastActualScrollOffset != null && // We've laid out at least once to get an initial position, and either
+        ((constraints.scrollOffset < _lastActualScrollOffset!) || // we are scrolling back, so should reveal, or
+         (_effectiveScrollOffset! < maxExtent))) { // some part of it is visible, so should shrink or reveal as appropriate.
+      double delta = _lastActualScrollOffset! - constraints.scrollOffset;
+
+      final bool allowFloatingExpansion = constraints.userScrollDirection == ScrollDirection.forward;
+      if (allowFloatingExpansion) {
+        if (_effectiveScrollOffset! > maxExtent) // We're scrolled off-screen, but should reveal, so
+          _effectiveScrollOffset = maxExtent; // pretend we're just at the limit.
+      } else {
+        if (delta > 0.0) // If we are trying to expand when allowFloatingExpansion is false,
+          delta = 0.0; // disallow the expansion. (But allow shrinking, i.e. delta < 0.0 is fine.)
+      }
+      _effectiveScrollOffset = (_effectiveScrollOffset! - delta).clamp(0.0, constraints.scrollOffset);
+    } else {
+      _effectiveScrollOffset = constraints.scrollOffset;
+    }
+    final bool overlapsContent = _effectiveScrollOffset! < constraints.scrollOffset;
+
+    layoutChild(
+      _effectiveScrollOffset!,
+      maxExtent,
+      overlapsContent: overlapsContent,
+    );
+    _childPosition = updateGeometry();
+    _lastActualScrollOffset = constraints.scrollOffset;
+  }
+
+  @override
+  void showOnScreen({
+    RenderObject? descendant,
+    Rect? rect,
+    Duration duration = Duration.zero,
+    Curve curve = Curves.ease,
+  }) {
+    final PersistentHeaderShowOnScreenConfiguration? showOnScreen = showOnScreenConfiguration;
+    if (showOnScreen == null)
+      return super.showOnScreen(descendant: descendant, rect: rect, duration: duration, curve: curve);
+
+    assert(child != null || descendant == null);
+    // We prefer the child's coordinate space (instead of the sliver's) because
+    // it's easier for us to convert the target rect into target extents: when
+    // the sliver is sitting above the leading edge (not possible with pinned
+    // headers), the leading edge of the sliver and the leading edge of the child
+    // will not be aligned. The only exception is when child is null (and thus
+    // descendant == null).
+    final Rect? childBounds = descendant != null
+      ? MatrixUtils.transformRect(descendant.getTransformTo(child), rect ?? descendant.paintBounds)
+      : rect;
+
+    double targetExtent;
+    Rect? targetRect;
+    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
+      case AxisDirection.up:
+        targetExtent = childExtent - (childBounds?.top ?? 0);
+        targetRect = _trim(childBounds, bottom: childExtent);
+        break;
+      case AxisDirection.right:
+        targetExtent = childBounds?.right ?? childExtent;
+        targetRect = _trim(childBounds, left: 0);
+        break;
+      case AxisDirection.down:
+        targetExtent = childBounds?.bottom ?? childExtent;
+        targetRect = _trim(childBounds, top: 0);
+        break;
+      case AxisDirection.left:
+        targetExtent = childExtent - (childBounds?.left ?? 0);
+        targetRect = _trim(childBounds, right: childExtent);
+        break;
+    }
+
+    // A stretch header can have a bigger childExtent than maxExtent.
+    final double effectiveMaxExtent = math.max(childExtent, maxExtent);
+
+    targetExtent = targetExtent.clamp(
+        showOnScreen.minShowOnScreenExtent,
+        showOnScreen.maxShowOnScreenExtent,
+      )
+      // Clamp the value back to the valid range after applying additional
+      // constriants. Contracting is not allowed.
+      .clamp(childExtent, effectiveMaxExtent);
+
+    // Expands the header if needed, with animation.
+    if (targetExtent > childExtent) {
+      final double targetScrollOffset = maxExtent - targetExtent;
+      assert(
+        vsync != null,
+        'vsync must not be null if the floating header changes size animatedly.',
+      );
+      _updateAnimation(duration, targetScrollOffset, curve);
+      _controller?.forward(from: 0.0);
+    }
+
+    super.showOnScreen(
+      descendant: descendant == null ? this : child,
+      rect: targetRect,
+      duration: duration,
+      curve: curve,
+    );
+  }
+
+  @override
+  double childMainAxisPosition(RenderBox child) {
+    assert(child == this.child);
+    return _childPosition ?? 0.0;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('effective scroll offset', _effectiveScrollOffset));
+  }
+}
+
+/// A sliver with a [RenderBox] child which shrinks and then remains pinned to
+/// the start of the viewport like a [RenderSliverPinnedPersistentHeader], but
+/// immediately grows when the user scrolls in the reverse direction.
+///
+/// See also:
+///
+///  * [RenderSliverFloatingPersistentHeader], which is similar but scrolls off
+///    the top rather than sticking to it.
+abstract class RenderSliverFloatingPinnedPersistentHeader extends RenderSliverFloatingPersistentHeader {
+  /// Creates a sliver that shrinks when it hits the start of the viewport, then
+  /// stays pinned there, and grows immediately when the user reverses the
+  /// scroll direction.
+  RenderSliverFloatingPinnedPersistentHeader({
+    RenderBox? child,
+    TickerProvider? vsync,
+    FloatingHeaderSnapConfiguration? snapConfiguration,
+    OverScrollHeaderStretchConfiguration? stretchConfiguration,
+    PersistentHeaderShowOnScreenConfiguration? showOnScreenConfiguration,
+  }) : super(
+    child: child,
+    vsync: vsync,
+    snapConfiguration: snapConfiguration,
+    stretchConfiguration: stretchConfiguration,
+    showOnScreenConfiguration: showOnScreenConfiguration,
+  );
+
+  @override
+  double updateGeometry() {
+    final double minExtent = this.minExtent;
+    final double minAllowedExtent = constraints.remainingPaintExtent > minExtent ?
+      minExtent :
+      constraints.remainingPaintExtent;
+    final double maxExtent = this.maxExtent;
+    final double paintExtent = maxExtent - _effectiveScrollOffset!;
+    final double clampedPaintExtent = paintExtent.clamp(
+      minAllowedExtent,
+      constraints.remainingPaintExtent,
+    );
+    final double layoutExtent = maxExtent - constraints.scrollOffset;
+    final double stretchOffset = stretchConfiguration != null ?
+      constraints.overlap.abs() :
+      0.0;
+    geometry = SliverGeometry(
+      scrollExtent: maxExtent,
+      paintOrigin: math.min(constraints.overlap, 0.0),
+      paintExtent: clampedPaintExtent,
+      layoutExtent: layoutExtent.clamp(0.0, clampedPaintExtent),
+      maxPaintExtent: maxExtent + stretchOffset,
+      maxScrollObstructionExtent: minExtent,
+      hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
+    );
+    return 0.0;
+  }
+}
diff --git a/lib/src/rendering/stack.dart b/lib/src/rendering/stack.dart
new file mode 100644
index 0000000..1a36cf2
--- /dev/null
+++ b/lib/src/rendering/stack.dart
@@ -0,0 +1,740 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show lerpDouble, hashValues;
+
+import 'package:flute/foundation.dart';
+
+import 'box.dart';
+import 'layer.dart';
+import 'layout_helper.dart';
+import 'object.dart';
+
+/// An immutable 2D, axis-aligned, floating-point rectangle whose coordinates
+/// are given relative to another rectangle's edges, known as the container.
+/// Since the dimensions of the rectangle are relative to those of the
+/// container, this class has no width and height members. To determine the
+/// width or height of the rectangle, convert it to a [Rect] using [toRect()]
+/// (passing the container's own Rect), and then examine that object.
+///
+/// The fields [left], [right], [bottom], and [top] must not be null.
+@immutable
+class RelativeRect {
+  /// Creates a RelativeRect with the given values.
+  ///
+  /// The arguments must not be null.
+  const RelativeRect.fromLTRB(this.left, this.top, this.right, this.bottom)
+    : assert(left != null && top != null && right != null && bottom != null);
+
+  /// Creates a RelativeRect from a Rect and a Size. The Rect (first argument)
+  /// and the RelativeRect (the output) are in the coordinate space of the
+  /// rectangle described by the Size, with 0,0 being at the top left.
+  factory RelativeRect.fromSize(Rect rect, Size container) {
+    return RelativeRect.fromLTRB(rect.left, rect.top, container.width - rect.right, container.height - rect.bottom);
+  }
+
+  /// Creates a RelativeRect from two Rects. The second Rect provides the
+  /// container, the first provides the rectangle, in the same coordinate space,
+  /// that is to be converted to a RelativeRect. The output will be in the
+  /// container's coordinate space.
+  ///
+  /// For example, if the top left of the rect is at 0,0, and the top left of
+  /// the container is at 100,100, then the top left of the output will be at
+  /// -100,-100.
+  ///
+  /// If the first rect is actually in the container's coordinate space, then
+  /// use [RelativeRect.fromSize] and pass the container's size as the second
+  /// argument instead.
+  factory RelativeRect.fromRect(Rect rect, Rect container) {
+    return RelativeRect.fromLTRB(
+      rect.left - container.left,
+      rect.top - container.top,
+      container.right - rect.right,
+      container.bottom - rect.bottom,
+    );
+  }
+
+  /// A rect that covers the entire container.
+  static const RelativeRect fill = RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0);
+
+  /// Distance from the left side of the container to the left side of this rectangle.
+  ///
+  /// May be negative if the left side of the rectangle is outside of the container.
+  final double left;
+
+  /// Distance from the top side of the container to the top side of this rectangle.
+  ///
+  /// May be negative if the top side of the rectangle is outside of the container.
+  final double top;
+
+  /// Distance from the right side of the container to the right side of this rectangle.
+  ///
+  /// May be positive if the right side of the rectangle is outside of the container.
+  final double right;
+
+  /// Distance from the bottom side of the container to the bottom side of this rectangle.
+  ///
+  /// May be positive if the bottom side of the rectangle is outside of the container.
+  final double bottom;
+
+  /// Returns whether any of the values are greater than zero.
+  ///
+  /// This corresponds to one of the sides ([left], [top], [right], or [bottom]) having
+  /// some positive inset towards the center.
+  bool get hasInsets => left > 0.0 || top > 0.0 || right > 0.0 || bottom > 0.0;
+
+  /// Returns a new rectangle object translated by the given offset.
+  RelativeRect shift(Offset offset) {
+    return RelativeRect.fromLTRB(left + offset.dx, top + offset.dy, right - offset.dx, bottom - offset.dy);
+  }
+
+  /// Returns a new rectangle with edges moved outwards by the given delta.
+  RelativeRect inflate(double delta) {
+    return RelativeRect.fromLTRB(left - delta, top - delta, right - delta, bottom - delta);
+  }
+
+  /// Returns a new rectangle with edges moved inwards by the given delta.
+  RelativeRect deflate(double delta) {
+    return inflate(-delta);
+  }
+
+  /// Returns a new rectangle that is the intersection of the given rectangle and this rectangle.
+  RelativeRect intersect(RelativeRect other) {
+    return RelativeRect.fromLTRB(
+      math.max(left, other.left),
+      math.max(top, other.top),
+      math.max(right, other.right),
+      math.max(bottom, other.bottom),
+    );
+  }
+
+  /// Convert this [RelativeRect] to a [Rect], in the coordinate space of the container.
+  ///
+  /// See also:
+  ///
+  ///  * [toSize], which returns the size part of the rect, based on the size of
+  ///    the container.
+  Rect toRect(Rect container) {
+    return Rect.fromLTRB(left, top, container.width - right, container.height - bottom);
+  }
+
+  /// Convert this [RelativeRect] to a [Size], assuming a container with the given size.
+  ///
+  /// See also:
+  ///
+  ///  * [toRect], which also computes the position relative to the container.
+  Size toSize(Size container) {
+    return Size(container.width - left - right, container.height - top - bottom);
+  }
+
+  /// Linearly interpolate between two RelativeRects.
+  ///
+  /// If either rect is null, this function interpolates from [RelativeRect.fill].
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static RelativeRect? lerp(RelativeRect? a, RelativeRect? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return RelativeRect.fromLTRB(b!.left * t, b.top * t, b.right * t, b.bottom * t);
+    if (b == null) {
+      final double k = 1.0 - t;
+      return RelativeRect.fromLTRB(b!.left * k, b.top * k, b.right * k, b.bottom * k);
+    }
+    return RelativeRect.fromLTRB(
+      lerpDouble(a.left, b.left, t)!,
+      lerpDouble(a.top, b.top, t)!,
+      lerpDouble(a.right, b.right, t)!,
+      lerpDouble(a.bottom, b.bottom, t)!,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    return other is RelativeRect
+        && other.left == left
+        && other.top == top
+        && other.right == right
+        && other.bottom == bottom;
+  }
+
+  @override
+  int get hashCode => hashValues(left, top, right, bottom);
+
+  @override
+  String toString() => 'RelativeRect.fromLTRB(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)})';
+}
+
+/// Parent data for use with [RenderStack].
+class StackParentData extends ContainerBoxParentData<RenderBox> {
+  /// The distance by which the child's top edge is inset from the top of the stack.
+  double? top;
+
+  /// The distance by which the child's right edge is inset from the right of the stack.
+  double? right;
+
+  /// The distance by which the child's bottom edge is inset from the bottom of the stack.
+  double? bottom;
+
+  /// The distance by which the child's left edge is inset from the left of the stack.
+  double? left;
+
+  /// The child's width.
+  ///
+  /// Ignored if both left and right are non-null.
+  double? width;
+
+  /// The child's height.
+  ///
+  /// Ignored if both top and bottom are non-null.
+  double? height;
+
+  /// Get or set the current values in terms of a RelativeRect object.
+  RelativeRect get rect => RelativeRect.fromLTRB(left!, top!, right!, bottom!);
+  set rect(RelativeRect value) {
+    top = value.top;
+    right = value.right;
+    bottom = value.bottom;
+    left = value.left;
+  }
+
+  /// Whether this child is considered positioned.
+  ///
+  /// A child is positioned if any of the top, right, bottom, or left properties
+  /// are non-null. Positioned children do not factor into determining the size
+  /// of the stack but are instead placed relative to the non-positioned
+  /// children in the stack.
+  bool get isPositioned => top != null || right != null || bottom != null || left != null || width != null || height != null;
+
+  @override
+  String toString() {
+    final List<String> values = <String>[
+      if (top != null) 'top=${debugFormatDouble(top)}',
+      if (right != null) 'right=${debugFormatDouble(right)}',
+      if (bottom != null) 'bottom=${debugFormatDouble(bottom)}',
+      if (left != null) 'left=${debugFormatDouble(left)}',
+      if (width != null) 'width=${debugFormatDouble(width)}',
+      if (height != null) 'height=${debugFormatDouble(height)}',
+    ];
+    if (values.isEmpty)
+      values.add('not positioned');
+    values.add(super.toString());
+    return values.join('; ');
+  }
+}
+
+/// How to size the non-positioned children of a [Stack].
+///
+/// This enum is used with [Stack.fit] and [RenderStack.fit] to control
+/// how the [BoxConstraints] passed from the stack's parent to the stack's child
+/// are adjusted.
+///
+/// See also:
+///
+///  * [Stack], the widget that uses this.
+///  * [RenderStack], the render object that implements the stack algorithm.
+enum StackFit {
+  /// The constraints passed to the stack from its parent are loosened.
+  ///
+  /// For example, if the stack has constraints that force it to 350x600, then
+  /// this would allow the non-positioned children of the stack to have any
+  /// width from zero to 350 and any height from zero to 600.
+  ///
+  /// See also:
+  ///
+  ///  * [Center], which loosens the constraints passed to its child and then
+  ///    centers the child in itself.
+  ///  * [BoxConstraints.loosen], which implements the loosening of box
+  ///    constraints.
+  loose,
+
+  /// The constraints passed to the stack from its parent are tightened to the
+  /// biggest size allowed.
+  ///
+  /// For example, if the stack has loose constraints with a width in the range
+  /// 10 to 100 and a height in the range 0 to 600, then the non-positioned
+  /// children of the stack would all be sized as 100 pixels wide and 600 high.
+  expand,
+
+  /// The constraints passed to the stack from its parent are passed unmodified
+  /// to the non-positioned children.
+  ///
+  /// For example, if a [Stack] is an [Expanded] child of a [Row], the
+  /// horizontal constraints will be tight and the vertical constraints will be
+  /// loose.
+  passthrough,
+}
+
+/// Whether overflowing children should be clipped, or their overflow be
+/// visible.
+///
+/// Deprecated. Use [Stack.clipBehavior] instead.
+@Deprecated(
+  'Use clipBehavior instead. See the migration guide in flutter.dev/go/clip-behavior. '
+  'This feature was deprecated after v1.22.0-12.0.pre.'
+)
+enum Overflow {
+  /// Overflowing children will be visible.
+  ///
+  /// The visible overflow area will not accept hit testing.
+  visible,
+
+  /// Overflowing children will be clipped to the bounds of their parent.
+  clip,
+}
+
+/// Implements the stack layout algorithm.
+///
+/// In a stack layout, the children are positioned on top of each other in the
+/// order in which they appear in the child list. First, the non-positioned
+/// children (those with null values for top, right, bottom, and left) are
+/// laid out and initially placed in the upper-left corner of the stack. The
+/// stack is then sized to enclose all of the non-positioned children. If there
+/// are no non-positioned children, the stack becomes as large as possible.
+///
+/// The final location of non-positioned children is determined by the alignment
+/// parameter. The left of each non-positioned child becomes the
+/// difference between the child's width and the stack's width scaled by
+/// alignment.x. The top of each non-positioned child is computed
+/// similarly and scaled by alignment.y. So if the alignment x and y properties
+/// are 0.0 (the default) then the non-positioned children remain in the
+/// upper-left corner. If the alignment x and y properties are 0.5 then the
+/// non-positioned children are centered within the stack.
+///
+/// Next, the positioned children are laid out. If a child has top and bottom
+/// values that are both non-null, the child is given a fixed height determined
+/// by subtracting the sum of the top and bottom values from the height of the stack.
+/// Similarly, if the child has right and left values that are both non-null,
+/// the child is given a fixed width derived from the stack's width.
+/// Otherwise, the child is given unbounded constraints in the non-fixed dimensions.
+///
+/// Once the child is laid out, the stack positions the child
+/// according to the top, right, bottom, and left properties of their
+/// [StackParentData]. For example, if the bottom value is 10.0, the
+/// bottom edge of the child will be inset 10.0 pixels from the bottom
+/// edge of the stack. If the child extends beyond the bounds of the
+/// stack, the stack will clip the child's painting to the bounds of
+/// the stack.
+///
+/// See also:
+///
+///  * [RenderFlow]
+class RenderStack extends RenderBox
+    with ContainerRenderObjectMixin<RenderBox, StackParentData>,
+         RenderBoxContainerDefaultsMixin<RenderBox, StackParentData> {
+  /// Creates a stack render object.
+  ///
+  /// By default, the non-positioned children of the stack are aligned by their
+  /// top left corners.
+  RenderStack({
+    List<RenderBox>? children,
+    AlignmentGeometry alignment = AlignmentDirectional.topStart,
+    TextDirection? textDirection,
+    StackFit fit = StackFit.loose,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : assert(alignment != null),
+       assert(fit != null),
+       assert(clipBehavior != null),
+       _alignment = alignment,
+       _textDirection = textDirection,
+       _fit = fit,
+       _clipBehavior = clipBehavior {
+    addAll(children);
+  }
+
+  bool _hasVisualOverflow = false;
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! StackParentData)
+      child.parentData = StackParentData();
+  }
+
+  Alignment? _resolvedAlignment;
+
+  void _resolve() {
+    if (_resolvedAlignment != null)
+      return;
+    _resolvedAlignment = alignment.resolve(textDirection);
+  }
+
+  void _markNeedResolution() {
+    _resolvedAlignment = null;
+    markNeedsLayout();
+  }
+
+  /// How to align the non-positioned or partially-positioned children in the
+  /// stack.
+  ///
+  /// The non-positioned children are placed relative to each other such that
+  /// the points determined by [alignment] are co-located. For example, if the
+  /// [alignment] is [Alignment.topLeft], then the top left corner of
+  /// each non-positioned child will be located at the same global coordinate.
+  ///
+  /// Partially-positioned children, those that do not specify an alignment in a
+  /// particular axis (e.g. that have neither `top` nor `bottom` set), use the
+  /// alignment to determine how they should be positioned in that
+  /// under-specified axis.
+  ///
+  /// If this is set to an [AlignmentDirectional] object, then [textDirection]
+  /// must not be null.
+  AlignmentGeometry get alignment => _alignment;
+  AlignmentGeometry _alignment;
+  set alignment(AlignmentGeometry value) {
+    assert(value != null);
+    if (_alignment == value)
+      return;
+    _alignment = value;
+    _markNeedResolution();
+  }
+
+  /// The text direction with which to resolve [alignment].
+  ///
+  /// This may be changed to null, but only after the [alignment] has been changed
+  /// to a value that does not depend on the direction.
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection;
+  set textDirection(TextDirection? value) {
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    _markNeedResolution();
+  }
+
+  /// How to size the non-positioned children in the stack.
+  ///
+  /// The constraints passed into the [RenderStack] from its parent are either
+  /// loosened ([StackFit.loose]) or tightened to their biggest size
+  /// ([StackFit.expand]).
+  StackFit get fit => _fit;
+  StackFit _fit;
+  set fit(StackFit value) {
+    assert(value != null);
+    if (_fit != value) {
+      _fit = value;
+      markNeedsLayout();
+    }
+  }
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge], and must not be null.
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior = Clip.hardEdge;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  /// Helper function for calculating the intrinsics metrics of a Stack.
+  static double getIntrinsicDimension(RenderBox? firstChild, double mainChildSizeGetter(RenderBox child)) {
+    double extent = 0.0;
+    RenderBox? child = firstChild;
+    while (child != null) {
+      final StackParentData childParentData = child.parentData! as StackParentData;
+      if (!childParentData.isPositioned)
+        extent = math.max(extent, mainChildSizeGetter(child));
+      assert(child.parentData == childParentData);
+      child = childParentData.nextSibling;
+    }
+    return extent;
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMinIntrinsicWidth(height));
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMaxIntrinsicWidth(height));
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMinIntrinsicHeight(width));
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    return getIntrinsicDimension(firstChild, (RenderBox child) => child.getMaxIntrinsicHeight(width));
+  }
+
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    return defaultComputeDistanceToHighestActualBaseline(baseline);
+  }
+
+  /// Lays out the positioned `child` according to `alignment` within a Stack of `size`.
+  ///
+  /// Returns true when the child has visual overflow.
+  static bool layoutPositionedChild(RenderBox child, StackParentData childParentData, Size size, Alignment alignment) {
+    assert(childParentData.isPositioned);
+    assert(child.parentData == childParentData);
+
+    bool hasVisualOverflow = false;
+    BoxConstraints childConstraints = const BoxConstraints();
+
+    if (childParentData.left != null && childParentData.right != null)
+      childConstraints = childConstraints.tighten(width: size.width - childParentData.right! - childParentData.left!);
+    else if (childParentData.width != null)
+      childConstraints = childConstraints.tighten(width: childParentData.width);
+
+    if (childParentData.top != null && childParentData.bottom != null)
+      childConstraints = childConstraints.tighten(height: size.height - childParentData.bottom! - childParentData.top!);
+    else if (childParentData.height != null)
+      childConstraints = childConstraints.tighten(height: childParentData.height);
+
+    child.layout(childConstraints, parentUsesSize: true);
+
+    late final double x;
+    if (childParentData.left != null) {
+      x = childParentData.left!;
+    } else if (childParentData.right != null) {
+      x = size.width - childParentData.right! - child.size.width;
+    } else {
+      x = alignment.alongOffset(size - child.size as Offset).dx;
+    }
+
+    if (x < 0.0 || x + child.size.width > size.width)
+      hasVisualOverflow = true;
+
+    late final double y;
+    if (childParentData.top != null) {
+      y = childParentData.top!;
+    } else if (childParentData.bottom != null) {
+      y = size.height - childParentData.bottom! - child.size.height;
+    } else {
+      y = alignment.alongOffset(size - child.size as Offset).dy;
+    }
+
+    if (y < 0.0 || y + child.size.height > size.height)
+      hasVisualOverflow = true;
+
+    childParentData.offset = Offset(x, y);
+
+    return hasVisualOverflow;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _computeSize(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.dryLayoutChild,
+    );
+  }
+
+  Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
+    _resolve();
+    assert(_resolvedAlignment != null);
+    bool hasNonPositionedChildren = false;
+    if (childCount == 0) {
+      assert(constraints.biggest.isFinite);
+      return constraints.biggest;
+    }
+
+    double width = constraints.minWidth;
+    double height = constraints.minHeight;
+
+    final BoxConstraints nonPositionedConstraints;
+    assert(fit != null);
+    switch (fit) {
+      case StackFit.loose:
+        nonPositionedConstraints = constraints.loosen();
+        break;
+      case StackFit.expand:
+        nonPositionedConstraints = BoxConstraints.tight(constraints.biggest);
+        break;
+      case StackFit.passthrough:
+        nonPositionedConstraints = constraints;
+        break;
+    }
+    assert(nonPositionedConstraints != null);
+
+    RenderBox? child = firstChild;
+    while (child != null) {
+      final StackParentData childParentData = child.parentData! as StackParentData;
+
+      if (!childParentData.isPositioned) {
+        hasNonPositionedChildren = true;
+
+        final Size childSize = layoutChild(child, nonPositionedConstraints);
+
+        width = math.max(width, childSize.width);
+        height = math.max(height, childSize.height);
+      }
+
+      child = childParentData.nextSibling;
+    }
+
+    final Size size;
+    if (hasNonPositionedChildren) {
+      size = Size(width, height);
+      assert(size.width == constraints.constrainWidth(width));
+      assert(size.height == constraints.constrainHeight(height));
+    } else {
+      size = constraints.biggest;
+    }
+
+    assert(size.isFinite);
+    return size;
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    _hasVisualOverflow = false;
+
+    size = _computeSize(
+      constraints: constraints,
+      layoutChild: ChildLayoutHelper.layoutChild,
+    );
+
+    assert(_resolvedAlignment != null);
+    RenderBox? child = firstChild;
+    while (child != null) {
+      final StackParentData childParentData = child.parentData! as StackParentData;
+
+      if (!childParentData.isPositioned) {
+        childParentData.offset = _resolvedAlignment!.alongOffset(size - child.size as Offset);
+      } else {
+        _hasVisualOverflow = layoutPositionedChild(child, childParentData, size, _resolvedAlignment!) || _hasVisualOverflow;
+      }
+
+      assert(child.parentData == childParentData);
+      child = childParentData.nextSibling;
+    }
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    return defaultHitTestChildren(result, position: position);
+  }
+
+  /// Override in subclasses to customize how the stack paints.
+  ///
+  /// By default, the stack uses [defaultPaint]. This function is called by
+  /// [paint] after potentially applying a clip to contain visual overflow.
+  @protected
+  void paintStack(PaintingContext context, Offset offset) {
+    defaultPaint(context, offset);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (clipBehavior != Clip.none && _hasVisualOverflow) {
+      _clipRectLayer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintStack,
+          clipBehavior: clipBehavior, oldLayer: _clipRectLayer);
+    } else {
+      _clipRectLayer = null;
+      paintStack(context, offset);
+    }
+  }
+
+  ClipRectLayer? _clipRectLayer;
+
+  @override
+  Rect? describeApproximatePaintClip(RenderObject child) => _hasVisualOverflow ? Offset.zero & size : null;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection));
+    properties.add(EnumProperty<StackFit>('fit', fit));
+    properties.add(EnumProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge));
+  }
+}
+
+/// Implements the same layout algorithm as RenderStack but only paints the child
+/// specified by index.
+///
+/// Although only one child is displayed, the cost of the layout algorithm is
+/// still O(N), like an ordinary stack.
+class RenderIndexedStack extends RenderStack {
+  /// Creates a stack render object that paints a single child.
+  ///
+  /// If the [index] parameter is null, nothing is displayed.
+  RenderIndexedStack({
+    List<RenderBox>? children,
+    AlignmentGeometry alignment = AlignmentDirectional.topStart,
+    TextDirection? textDirection,
+    int? index = 0,
+  }) : _index = index,
+       super(
+         children: children,
+         alignment: alignment,
+         textDirection: textDirection,
+       );
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    if (index != null && firstChild != null)
+      visitor(_childAtIndex());
+  }
+
+  /// The index of the child to show, null if nothing is to be displayed.
+  int? get index => _index;
+  int? _index;
+  set index(int? value) {
+    if (_index != value) {
+      _index = value;
+      markNeedsLayout();
+    }
+  }
+
+  RenderBox _childAtIndex() {
+    assert(index != null);
+    RenderBox? child = firstChild;
+    int i = 0;
+    while (child != null && i < index!) {
+      final StackParentData childParentData = child.parentData! as StackParentData;
+      child = childParentData.nextSibling;
+      i += 1;
+    }
+    assert(i == index);
+    assert(child != null);
+    return child!;
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    if (firstChild == null || index == null)
+      return false;
+    assert(position != null);
+    final RenderBox child = _childAtIndex();
+    final StackParentData childParentData = child.parentData! as StackParentData;
+    return result.addWithPaintOffset(
+      offset: childParentData.offset,
+      position: position,
+      hitTest: (BoxHitTestResult result, Offset? transformed) {
+        assert(transformed == position - childParentData.offset);
+        return child.hitTest(result, position: transformed!);
+      },
+    );
+  }
+
+  @override
+  void paintStack(PaintingContext context, Offset offset) {
+    if (firstChild == null || index == null)
+      return;
+    final RenderBox child = _childAtIndex();
+    final StackParentData childParentData = child.parentData! as StackParentData;
+    context.paintChild(child, childParentData.offset + offset);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(IntProperty('index', index));
+  }
+}
diff --git a/lib/src/rendering/table.dart b/lib/src/rendering/table.dart
new file mode 100644
index 0000000..b263efb
--- /dev/null
+++ b/lib/src/rendering/table.dart
@@ -0,0 +1,1253 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+
+import 'box.dart';
+import 'object.dart';
+import 'table_border.dart';
+
+/// Parent data used by [RenderTable] for its children.
+class TableCellParentData extends BoxParentData {
+  /// Where this cell should be placed vertically.
+  ///
+  /// When using [TableCellVerticalAlignment.baseline], the text baseline must be set as well.
+  TableCellVerticalAlignment? verticalAlignment;
+
+  /// The column that the child was in the last time it was laid out.
+  int? x;
+
+  /// The row that the child was in the last time it was laid out.
+  int? y;
+
+  @override
+  String toString() => '${super.toString()}; ${verticalAlignment == null ? "default vertical alignment" : "$verticalAlignment"}';
+}
+
+/// Base class to describe how wide a column in a [RenderTable] should be.
+///
+/// To size a column to a specific number of pixels, use a [FixedColumnWidth].
+/// This is the cheapest way to size a column.
+///
+/// Other algorithms that are relatively cheap include [FlexColumnWidth], which
+/// distributes the space equally among the flexible columns,
+/// [FractionColumnWidth], which sizes a column based on the size of the
+/// table's container.
+@immutable
+abstract class TableColumnWidth {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const TableColumnWidth();
+
+  /// The smallest width that the column can have.
+  ///
+  /// The `cells` argument is an iterable that provides all the cells
+  /// in the table for this column. Walking the cells is by definition
+  /// O(N), so algorithms that do that should be considered expensive.
+  ///
+  /// The `containerWidth` argument is the `maxWidth` of the incoming
+  /// constraints for the table, and might be infinite.
+  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth);
+
+  /// The ideal width that the column should have. This must be equal
+  /// to or greater than the [minIntrinsicWidth]. The column might be
+  /// bigger than this width, e.g. if the column is flexible or if the
+  /// table's width ends up being forced to be bigger than the sum of
+  /// all the maxIntrinsicWidth values.
+  ///
+  /// The `cells` argument is an iterable that provides all the cells
+  /// in the table for this column. Walking the cells is by definition
+  /// O(N), so algorithms that do that should be considered expensive.
+  ///
+  /// The `containerWidth` argument is the `maxWidth` of the incoming
+  /// constraints for the table, and might be infinite.
+  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth);
+
+  /// The flex factor to apply to the cell if there is any room left
+  /// over when laying out the table. The remaining space is
+  /// distributed to any columns with flex in proportion to their flex
+  /// value (higher values get more space).
+  ///
+  /// The `cells` argument is an iterable that provides all the cells
+  /// in the table for this column. Walking the cells is by definition
+  /// O(N), so algorithms that do that should be considered expensive.
+  double? flex(Iterable<RenderBox> cells) => null;
+
+  @override
+  String toString() => objectRuntimeType(this, 'TableColumnWidth');
+}
+
+/// Sizes the column according to the intrinsic dimensions of all the
+/// cells in that column.
+///
+/// This is a very expensive way to size a column.
+///
+/// A flex value can be provided. If specified (and non-null), the
+/// column will participate in the distribution of remaining space
+/// once all the non-flexible columns have been sized.
+class IntrinsicColumnWidth extends TableColumnWidth {
+  /// Creates a column width based on intrinsic sizing.
+  ///
+  /// This sizing algorithm is very expensive.
+  ///
+  /// The `flex` argument specifies the flex factor to apply to the column if
+  /// there is any room left over when laying out the table. If `flex` is
+  /// null (the default), the table will not distribute any extra space to the
+  /// column.
+  const IntrinsicColumnWidth({ double? flex }) : _flex = flex;
+
+  @override
+  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
+    double result = 0.0;
+    for (final RenderBox cell in cells)
+      result = math.max(result, cell.getMinIntrinsicWidth(double.infinity));
+    return result;
+  }
+
+  @override
+  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
+    double result = 0.0;
+    for (final RenderBox cell in cells)
+      result = math.max(result, cell.getMaxIntrinsicWidth(double.infinity));
+    return result;
+  }
+
+  final double? _flex;
+
+  @override
+  double? flex(Iterable<RenderBox> cells) => _flex;
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'IntrinsicColumnWidth')}(flex: ${_flex?.toStringAsFixed(1)})';
+}
+
+/// Sizes the column to a specific number of pixels.
+///
+/// This is the cheapest way to size a column.
+class FixedColumnWidth extends TableColumnWidth {
+  /// Creates a column width based on a fixed number of logical pixels.
+  ///
+  /// The [value] argument must not be null.
+  const FixedColumnWidth(this.value) : assert(value != null);
+
+  /// The width the column should occupy in logical pixels.
+  final double value;
+
+  @override
+  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
+    return value;
+  }
+
+  @override
+  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
+    return value;
+  }
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'FixedColumnWidth')}(${debugFormatDouble(value)})';
+}
+
+/// Sizes the column to a fraction of the table's constraints' maxWidth.
+///
+/// This is a cheap way to size a column.
+class FractionColumnWidth extends TableColumnWidth {
+  /// Creates a column width based on a fraction of the table's constraints'
+  /// maxWidth.
+  ///
+  /// The [value] argument must not be null.
+  const FractionColumnWidth(this.value) : assert(value != null);
+
+  /// The fraction of the table's constraints' maxWidth that this column should
+  /// occupy.
+  final double value;
+
+  @override
+  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
+    if (!containerWidth.isFinite)
+      return 0.0;
+    return value * containerWidth;
+  }
+
+  @override
+  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
+    if (!containerWidth.isFinite)
+      return 0.0;
+    return value * containerWidth;
+  }
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'FractionColumnWidth')}($value)';
+}
+
+/// Sizes the column by taking a part of the remaining space once all
+/// the other columns have been laid out.
+///
+/// For example, if two columns have a [FlexColumnWidth], then half the
+/// space will go to one and half the space will go to the other.
+///
+/// This is a cheap way to size a column.
+class FlexColumnWidth extends TableColumnWidth {
+  /// Creates a column width based on a fraction of the remaining space once all
+  /// the other columns have been laid out.
+  ///
+  /// The [value] argument must not be null.
+  const FlexColumnWidth([this.value = 1.0]) : assert(value != null);
+
+  /// The reaction of the of the remaining space once all the other columns have
+  /// been laid out that this column should occupy.
+  final double value;
+
+  @override
+  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
+    return 0.0;
+  }
+
+  @override
+  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
+    return 0.0;
+  }
+
+  @override
+  double flex(Iterable<RenderBox> cells) {
+    return value;
+  }
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'FlexColumnWidth')}(${debugFormatDouble(value)})';
+}
+
+/// Sizes the column such that it is the size that is the maximum of
+/// two column width specifications.
+///
+/// For example, to have a column be 10% of the container width or
+/// 100px, whichever is bigger, you could use:
+///
+///     const MaxColumnWidth(const FixedColumnWidth(100.0), FractionColumnWidth(0.1))
+///
+/// Both specifications are evaluated, so if either specification is
+/// expensive, so is this.
+class MaxColumnWidth extends TableColumnWidth {
+  /// Creates a column width that is the maximum of two other column widths.
+  const MaxColumnWidth(this.a, this.b);
+
+  /// A lower bound for the width of this column.
+  final TableColumnWidth a;
+
+  /// Another lower bound for the width of this column.
+  final TableColumnWidth b;
+
+  @override
+  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
+    return math.max(
+      a.minIntrinsicWidth(cells, containerWidth),
+      b.minIntrinsicWidth(cells, containerWidth),
+    );
+  }
+
+  @override
+  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
+    return math.max(
+      a.maxIntrinsicWidth(cells, containerWidth),
+      b.maxIntrinsicWidth(cells, containerWidth),
+    );
+  }
+
+  @override
+  double? flex(Iterable<RenderBox> cells) {
+    final double? aFlex = a.flex(cells);
+    if (aFlex == null)
+      return b.flex(cells);
+    final double? bFlex = b.flex(cells);
+    if (bFlex == null)
+      return null;
+    return math.max(aFlex, bFlex);
+  }
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'MaxColumnWidth')}($a, $b)';
+}
+
+/// Sizes the column such that it is the size that is the minimum of
+/// two column width specifications.
+///
+/// For example, to have a column be 10% of the container width but
+/// never bigger than 100px, you could use:
+///
+///     const MinColumnWidth(const FixedColumnWidth(100.0), FractionColumnWidth(0.1))
+///
+/// Both specifications are evaluated, so if either specification is
+/// expensive, so is this.
+class MinColumnWidth extends TableColumnWidth {
+  /// Creates a column width that is the minimum of two other column widths.
+  const MinColumnWidth(this.a, this.b);
+
+  /// An upper bound for the width of this column.
+  final TableColumnWidth a;
+
+  /// Another upper bound for the width of this column.
+  final TableColumnWidth b;
+
+  @override
+  double minIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
+    return math.min(
+      a.minIntrinsicWidth(cells, containerWidth),
+      b.minIntrinsicWidth(cells, containerWidth),
+    );
+  }
+
+  @override
+  double maxIntrinsicWidth(Iterable<RenderBox> cells, double containerWidth) {
+    return math.min(
+      a.maxIntrinsicWidth(cells, containerWidth),
+      b.maxIntrinsicWidth(cells, containerWidth),
+    );
+  }
+
+  @override
+  double? flex(Iterable<RenderBox> cells) {
+    final double? aFlex = a.flex(cells);
+    if (aFlex == null)
+      return b.flex(cells);
+    final double? bFlex = b.flex(cells);
+    if (bFlex == null)
+      return null;
+    return math.min(aFlex, bFlex);
+  }
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'MinColumnWidth')}($a, $b)';
+}
+
+/// Vertical alignment options for cells in [RenderTable] objects.
+///
+/// This is specified using [TableCellParentData] objects on the
+/// [RenderObject.parentData] of the children of the [RenderTable].
+enum TableCellVerticalAlignment {
+  /// Cells with this alignment are placed with their top at the top of the row.
+  top,
+
+  /// Cells with this alignment are vertically centered in the row.
+  middle,
+
+  /// Cells with this alignment are placed with their bottom at the bottom of the row.
+  bottom,
+
+  /// Cells with this alignment are aligned such that they all share the same
+  /// baseline. Cells with no baseline are top-aligned instead. The baseline
+  /// used is specified by [RenderTable.textBaseline]. It is not valid to use
+  /// the baseline value if [RenderTable.textBaseline] is not specified.
+  ///
+  /// This vertical alignment is relatively expensive because it causes the table
+  /// to compute the baseline for each cell in the row.
+  baseline,
+
+  /// Cells with this alignment are sized to be as tall as the row, then made to fit the row.
+  /// If all the cells have this alignment, then the row will have zero height.
+  fill
+}
+
+/// A table where the columns and rows are sized to fit the contents of the cells.
+class RenderTable extends RenderBox {
+  /// Creates a table render object.
+  ///
+  ///  * `columns` must either be null or non-negative. If `columns` is null,
+  ///    the number of columns will be inferred from length of the first sublist
+  ///    of `children`.
+  ///  * `rows` must either be null or non-negative. If `rows` is null, the
+  ///    number of rows will be inferred from the `children`. If `rows` is not
+  ///    null, then `children` must be null.
+  ///  * `children` must either be null or contain lists of all the same length.
+  ///    if `children` is not null, then `rows` must be null.
+  ///  * [columnWidths] may be null, in which case it defaults to an empty map.
+  ///  * [defaultColumnWidth] must not be null.
+  ///  * [configuration] must not be null (but has a default value).
+  RenderTable({
+    int? columns,
+    int? rows,
+    Map<int, TableColumnWidth>? columnWidths,
+    TableColumnWidth defaultColumnWidth = const FlexColumnWidth(1.0),
+    required TextDirection textDirection,
+    TableBorder? border,
+    List<Decoration?>? rowDecorations,
+    ImageConfiguration configuration = ImageConfiguration.empty,
+    TableCellVerticalAlignment defaultVerticalAlignment = TableCellVerticalAlignment.top,
+    TextBaseline? textBaseline,
+    List<List<RenderBox>>? children,
+  }) : assert(columns == null || columns >= 0),
+       assert(rows == null || rows >= 0),
+       assert(rows == null || children == null),
+       assert(defaultColumnWidth != null),
+       assert(textDirection != null),
+       assert(configuration != null),
+       _textDirection = textDirection,
+       _columns = columns ?? (children != null && children.isNotEmpty ? children.first.length : 0),
+       _rows = rows ?? 0,
+       _columnWidths = columnWidths ?? HashMap<int, TableColumnWidth>(),
+       _defaultColumnWidth = defaultColumnWidth,
+       _border = border,
+       _textBaseline = textBaseline,
+       _defaultVerticalAlignment = defaultVerticalAlignment,
+       _configuration = configuration {
+    _children = <RenderBox?>[]..length = _columns * _rows;
+    this.rowDecorations = rowDecorations; // must use setter to initialize box painters array
+    children?.forEach(addRow);
+  }
+
+  // Children are stored in row-major order.
+  // _children.length must be rows * columns
+  List<RenderBox?> _children = const <RenderBox?>[];
+
+  /// The number of vertical alignment lines in this table.
+  ///
+  /// Changing the number of columns will remove any children that no longer fit
+  /// in the table.
+  ///
+  /// Changing the number of columns is an expensive operation because the table
+  /// needs to rearrange its internal representation.
+  int get columns => _columns;
+  int _columns;
+  set columns(int value) {
+    assert(value != null);
+    assert(value >= 0);
+    if (value == columns)
+      return;
+    final int oldColumns = columns;
+    final List<RenderBox?> oldChildren = _children;
+    _columns = value;
+    _children = List<RenderBox?>.filled(columns * rows, null, growable: false);
+    final int columnsToCopy = math.min(columns, oldColumns);
+    for (int y = 0; y < rows; y += 1) {
+      for (int x = 0; x < columnsToCopy; x += 1)
+        _children[x + y * columns] = oldChildren[x + y * oldColumns];
+    }
+    if (oldColumns > columns) {
+      for (int y = 0; y < rows; y += 1) {
+        for (int x = columns; x < oldColumns; x += 1) {
+          final int xy = x + y * oldColumns;
+          if (oldChildren[xy] != null)
+            dropChild(oldChildren[xy]!);
+        }
+      }
+    }
+    markNeedsLayout();
+  }
+
+  /// The number of horizontal alignment lines in this table.
+  ///
+  /// Changing the number of rows will remove any children that no longer fit
+  /// in the table.
+  int get rows => _rows;
+  int _rows;
+  set rows(int value) {
+    assert(value != null);
+    assert(value >= 0);
+    if (value == rows)
+      return;
+    if (_rows > value) {
+      for (int xy = columns * value; xy < _children.length; xy += 1) {
+        if (_children[xy] != null)
+          dropChild(_children[xy]!);
+      }
+    }
+    _rows = value;
+    _children.length = columns * rows;
+    markNeedsLayout();
+  }
+
+  /// How the horizontal extents of the columns of this table should be determined.
+  ///
+  /// If the [Map] has a null entry for a given column, the table uses the
+  /// [defaultColumnWidth] instead.
+  ///
+  /// The layout performance of the table depends critically on which column
+  /// sizing algorithms are used here. In particular, [IntrinsicColumnWidth] is
+  /// quite expensive because it needs to measure each cell in the column to
+  /// determine the intrinsic size of the column.
+  ///
+  /// This property can never return null. If it is set to null, and the existing
+  /// map is not empty, then the value is replaced by an empty map. (If it is set
+  /// to null while the current value is an empty map, the value is not changed.)
+  Map<int, TableColumnWidth>? get columnWidths => Map<int, TableColumnWidth>.unmodifiable(_columnWidths);
+  Map<int, TableColumnWidth> _columnWidths;
+  set columnWidths(Map<int, TableColumnWidth>? value) {
+    if (_columnWidths == value)
+      return;
+    if (_columnWidths.isEmpty && value == null)
+      return;
+    _columnWidths = value ?? HashMap<int, TableColumnWidth>();
+    markNeedsLayout();
+  }
+
+  /// Determines how the width of column with the given index is determined.
+  void setColumnWidth(int column, TableColumnWidth value) {
+    if (_columnWidths[column] == value)
+      return;
+    _columnWidths[column] = value;
+    markNeedsLayout();
+  }
+
+  /// How to determine with widths of columns that don't have an explicit sizing algorithm.
+  ///
+  /// Specifically, the [defaultColumnWidth] is used for column `i` if
+  /// `columnWidths[i]` is null.
+  TableColumnWidth get defaultColumnWidth => _defaultColumnWidth;
+  TableColumnWidth _defaultColumnWidth;
+  set defaultColumnWidth(TableColumnWidth value) {
+    assert(value != null);
+    if (defaultColumnWidth == value)
+      return;
+    _defaultColumnWidth = value;
+    markNeedsLayout();
+  }
+
+  /// The direction in which the columns are ordered.
+  TextDirection get textDirection => _textDirection;
+  TextDirection _textDirection;
+  set textDirection(TextDirection value) {
+    assert(value != null);
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    markNeedsLayout();
+  }
+
+  /// The style to use when painting the boundary and interior divisions of the table.
+  TableBorder? get border => _border;
+  TableBorder? _border;
+  set border(TableBorder? value) {
+    if (border == value)
+      return;
+    _border = value;
+    markNeedsPaint();
+  }
+
+  /// The decorations to use for each row of the table.
+  ///
+  /// Row decorations fill the horizontal and vertical extent of each row in
+  /// the table, unlike decorations for individual cells, which might not fill
+  /// either.
+  List<Decoration> get rowDecorations => List<Decoration>.unmodifiable(_rowDecorations ?? const <Decoration>[]);
+  // _rowDecorations and _rowDecorationPainters need to be in sync. They have to
+  // either both be null or have same length.
+  List<Decoration?>? _rowDecorations;
+  List<BoxPainter?>? _rowDecorationPainters;
+  set rowDecorations(List<Decoration?>? value) {
+    if (_rowDecorations == value)
+      return;
+    _rowDecorations = value;
+    if (_rowDecorationPainters != null) {
+      for (final BoxPainter? painter in _rowDecorationPainters!)
+        painter?.dispose();
+    }
+    _rowDecorationPainters = _rowDecorations != null ? List<BoxPainter?>.filled(_rowDecorations!.length, null, growable: false) : null;
+  }
+
+  /// The settings to pass to the [rowDecorations] when painting, so that they
+  /// can resolve images appropriately. See [ImageProvider.resolve] and
+  /// [BoxPainter.paint].
+  ImageConfiguration get configuration => _configuration;
+  ImageConfiguration _configuration;
+  set configuration(ImageConfiguration value) {
+    assert(value != null);
+    if (value == _configuration)
+      return;
+    _configuration = value;
+    markNeedsPaint();
+  }
+
+  /// How cells that do not explicitly specify a vertical alignment are aligned vertically.
+  TableCellVerticalAlignment get defaultVerticalAlignment => _defaultVerticalAlignment;
+  TableCellVerticalAlignment _defaultVerticalAlignment;
+  set defaultVerticalAlignment(TableCellVerticalAlignment value) {
+    assert(value != null);
+    if (_defaultVerticalAlignment == value)
+      return;
+    _defaultVerticalAlignment = value;
+    markNeedsLayout();
+  }
+
+  /// The text baseline to use when aligning rows using [TableCellVerticalAlignment.baseline].
+  TextBaseline? get textBaseline => _textBaseline;
+  TextBaseline? _textBaseline;
+  set textBaseline(TextBaseline? value) {
+    if (_textBaseline == value)
+      return;
+    _textBaseline = value;
+    markNeedsLayout();
+  }
+
+  @override
+  void setupParentData(RenderObject child) {
+    if (child.parentData is! TableCellParentData)
+      child.parentData = TableCellParentData();
+  }
+
+  /// Replaces the children of this table with the given cells.
+  ///
+  /// The cells are divided into the specified number of columns before
+  /// replacing the existing children.
+  ///
+  /// If the new cells contain any existing children of the table, those
+  /// children are simply moved to their new location in the table rather than
+  /// removed from the table and re-added.
+  void setFlatChildren(int columns, List<RenderBox?> cells) {
+    if (cells == _children && columns == _columns)
+      return;
+    assert(columns >= 0);
+    // consider the case of a newly empty table
+    if (columns == 0 || cells.isEmpty) {
+      assert(cells == null || cells.isEmpty);
+      _columns = columns;
+      if (_children.isEmpty) {
+        assert(_rows == 0);
+        return;
+      }
+      for (final RenderBox? oldChild in _children) {
+        if (oldChild != null)
+          dropChild(oldChild);
+      }
+      _rows = 0;
+      _children.clear();
+      markNeedsLayout();
+      return;
+    }
+    assert(cells != null);
+    assert(cells.length % columns == 0);
+    // fill a set with the cells that are moving (it's important not
+    // to dropChild a child that's remaining with us, because that
+    // would clear their parentData field)
+    final Set<RenderBox> lostChildren = HashSet<RenderBox>();
+    for (int y = 0; y < _rows; y += 1) {
+      for (int x = 0; x < _columns; x += 1) {
+        final int xyOld = x + y * _columns;
+        final int xyNew = x + y * columns;
+        if (_children[xyOld] != null && (x >= columns || xyNew >= cells.length || _children[xyOld] != cells[xyNew]))
+          lostChildren.add(_children[xyOld]!);
+      }
+    }
+    // adopt cells that are arriving, and cross cells that are just moving off our list of lostChildren
+    int y = 0;
+    while (y * columns < cells.length) {
+      for (int x = 0; x < columns; x += 1) {
+        final int xyNew = x + y * columns;
+        final int xyOld = x + y * _columns;
+        if (cells[xyNew] != null && (x >= _columns || y >= _rows || _children[xyOld] != cells[xyNew])) {
+          if (!lostChildren.remove(cells[xyNew]))
+            adoptChild(cells[xyNew]!);
+        }
+      }
+      y += 1;
+    }
+    // drop all the lost children
+    lostChildren.forEach(dropChild);
+    // update our internal values
+    _columns = columns;
+    _rows = cells.length ~/ columns;
+    _children = List<RenderBox?>.from(cells);
+    assert(_children.length == rows * columns);
+    markNeedsLayout();
+  }
+
+  /// Replaces the children of this table with the given cells.
+  void setChildren(List<List<RenderBox>>? cells) {
+    // TODO(ianh): Make this smarter, like setFlatChildren
+    if (cells == null) {
+      setFlatChildren(0, const <RenderBox?>[]);
+      return;
+    }
+    for (final RenderBox? oldChild in _children) {
+      if (oldChild != null)
+        dropChild(oldChild);
+    }
+    _children.clear();
+    _columns = cells.isNotEmpty ? cells.first.length : 0;
+    _rows = 0;
+    cells.forEach(addRow);
+    assert(_children.length == rows * columns);
+  }
+
+  /// Adds a row to the end of the table.
+  ///
+  /// The newly added children must not already have parents.
+  void addRow(List<RenderBox?> cells) {
+    assert(cells.length == columns);
+    assert(_children.length == rows * columns);
+    _rows += 1;
+    _children.addAll(cells);
+    for (final RenderBox? cell in cells) {
+      if (cell != null)
+        adoptChild(cell);
+    }
+    markNeedsLayout();
+  }
+
+  /// Replaces the child at the given position with the given child.
+  ///
+  /// If the given child is already located at the given position, this function
+  /// does not modify the table. Otherwise, the given child must not already
+  /// have a parent.
+  void setChild(int x, int y, RenderBox? value) {
+    assert(x != null);
+    assert(y != null);
+    assert(x >= 0 && x < columns && y >= 0 && y < rows);
+    assert(_children.length == rows * columns);
+    final int xy = x + y * columns;
+    final RenderBox? oldChild = _children[xy];
+    if (oldChild == value)
+      return;
+    if (oldChild != null)
+      dropChild(oldChild);
+    _children[xy] = value;
+    if (value != null)
+      adoptChild(value);
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    for (final RenderBox? child in _children)
+      child?.attach(owner);
+  }
+
+  @override
+  void detach() {
+    super.detach();
+    if (_rowDecorationPainters != null) {
+      for (final BoxPainter? painter in _rowDecorationPainters!)
+        painter?.dispose();
+      _rowDecorationPainters = List<BoxPainter?>.filled(_rowDecorations!.length, null, growable: false);
+    }
+    for (final RenderBox? child in _children)
+      child?.detach();
+  }
+
+  @override
+  void visitChildren(RenderObjectVisitor visitor) {
+    assert(_children.length == rows * columns);
+    for (final RenderBox? child in _children) {
+      if (child != null)
+        visitor(child);
+    }
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    assert(_children.length == rows * columns);
+    double totalMinWidth = 0.0;
+    for (int x = 0; x < columns; x += 1) {
+      final TableColumnWidth columnWidth = _columnWidths[x] ?? defaultColumnWidth;
+      final Iterable<RenderBox> columnCells = column(x);
+      totalMinWidth += columnWidth.minIntrinsicWidth(columnCells, double.infinity);
+    }
+    return totalMinWidth;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    assert(_children.length == rows * columns);
+    double totalMaxWidth = 0.0;
+    for (int x = 0; x < columns; x += 1) {
+      final TableColumnWidth columnWidth = _columnWidths[x] ?? defaultColumnWidth;
+      final Iterable<RenderBox> columnCells = column(x);
+      totalMaxWidth += columnWidth.maxIntrinsicWidth(columnCells, double.infinity);
+    }
+    return totalMaxWidth;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    // winner of the 2016 world's most expensive intrinsic dimension function award
+    // honorable mention, most likely to improve if taught about memoization award
+    assert(_children.length == rows * columns);
+    final List<double> widths = _computeColumnWidths(BoxConstraints.tightForFinite(width: width));
+    double rowTop = 0.0;
+    for (int y = 0; y < rows; y += 1) {
+      double rowHeight = 0.0;
+      for (int x = 0; x < columns; x += 1) {
+        final int xy = x + y * columns;
+        final RenderBox? child = _children[xy];
+        if (child != null)
+          rowHeight = math.max(rowHeight, child.getMaxIntrinsicHeight(widths[x]));
+      }
+      rowTop += rowHeight;
+    }
+    return rowTop;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    return computeMinIntrinsicHeight(width);
+  }
+
+  double? _baselineDistance;
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    // returns the baseline of the first cell that has a baseline in the first row
+    assert(!debugNeedsLayout);
+    return _baselineDistance;
+  }
+
+  /// Returns the list of [RenderBox] objects that are in the given
+  /// column, in row order, starting from the first row.
+  ///
+  /// This is a lazily-evaluated iterable.
+  Iterable<RenderBox> column(int x) sync* {
+    for (int y = 0; y < rows; y += 1) {
+      final int xy = x + y * columns;
+      final RenderBox? child = _children[xy];
+      if (child != null)
+        yield child;
+    }
+  }
+
+  /// Returns the list of [RenderBox] objects that are on the given
+  /// row, in column order, starting with the first column.
+  ///
+  /// This is a lazily-evaluated iterable.
+  Iterable<RenderBox> row(int y) sync* {
+    final int start = y * columns;
+    final int end = (y + 1) * columns;
+    for (int xy = start; xy < end; xy += 1) {
+      final RenderBox? child = _children[xy];
+      if (child != null)
+        yield child;
+    }
+  }
+
+  List<double> _computeColumnWidths(BoxConstraints constraints) {
+    assert(constraints != null);
+    assert(_children.length == rows * columns);
+    // We apply the constraints to the column widths in the order of
+    // least important to most important:
+    // 1. apply the ideal widths (maxIntrinsicWidth)
+    // 2. grow the flex columns so that the table has the maxWidth (if
+    //    finite) or the minWidth (if not)
+    // 3. if there were no flex columns, then grow the table to the
+    //    minWidth.
+    // 4. apply the maximum width of the table, shrinking columns as
+    //    necessary, applying minimum column widths as we go
+
+    // 1. apply ideal widths, and collect information we'll need later
+    final List<double> widths = List<double>.filled(columns, 0.0, growable: false);
+    final List<double> minWidths = List<double>.filled(columns, 0.0, growable: false);
+    final List<double?> flexes = List<double?>.filled(columns, null, growable: false);
+    double tableWidth = 0.0; // running tally of the sum of widths[x] for all x
+    double unflexedTableWidth = 0.0; // sum of the maxIntrinsicWidths of any column that has null flex
+    double totalFlex = 0.0;
+    for (int x = 0; x < columns; x += 1) {
+      final TableColumnWidth columnWidth = _columnWidths[x] ?? defaultColumnWidth;
+      final Iterable<RenderBox> columnCells = column(x);
+      // apply ideal width (maxIntrinsicWidth)
+      final double maxIntrinsicWidth = columnWidth.maxIntrinsicWidth(columnCells, constraints.maxWidth);
+      assert(maxIntrinsicWidth.isFinite);
+      assert(maxIntrinsicWidth >= 0.0);
+      widths[x] = maxIntrinsicWidth;
+      tableWidth += maxIntrinsicWidth;
+      // collect min width information while we're at it
+      final double minIntrinsicWidth = columnWidth.minIntrinsicWidth(columnCells, constraints.maxWidth);
+      assert(minIntrinsicWidth.isFinite);
+      assert(minIntrinsicWidth >= 0.0);
+      minWidths[x] = minIntrinsicWidth;
+      assert(maxIntrinsicWidth >= minIntrinsicWidth);
+      // collect flex information while we're at it
+      final double? flex = columnWidth.flex(columnCells);
+      if (flex != null) {
+        assert(flex.isFinite);
+        assert(flex > 0.0);
+        flexes[x] = flex;
+        totalFlex += flex;
+      } else {
+        unflexedTableWidth = unflexedTableWidth + maxIntrinsicWidth;
+      }
+    }
+    final double maxWidthConstraint = constraints.maxWidth;
+    final double minWidthConstraint = constraints.minWidth;
+
+    // 2. grow the flex columns so that the table has the maxWidth (if
+    //    finite) or the minWidth (if not)
+    if (totalFlex > 0.0) {
+      // this can only grow the table, but it _will_ grow the table at
+      // least as big as the target width.
+      final double targetWidth;
+      if (maxWidthConstraint.isFinite) {
+        targetWidth = maxWidthConstraint;
+      } else {
+        targetWidth = minWidthConstraint;
+      }
+      if (tableWidth < targetWidth) {
+        final double remainingWidth = targetWidth - unflexedTableWidth;
+        assert(remainingWidth.isFinite);
+        assert(remainingWidth >= 0.0);
+        for (int x = 0; x < columns; x += 1) {
+          if (flexes[x] != null) {
+            final double flexedWidth = remainingWidth * flexes[x]! / totalFlex;
+            assert(flexedWidth.isFinite);
+            assert(flexedWidth >= 0.0);
+            if (widths[x] < flexedWidth) {
+              final double delta = flexedWidth - widths[x];
+              tableWidth += delta;
+              widths[x] = flexedWidth;
+            }
+          }
+        }
+        assert(tableWidth + precisionErrorTolerance >= targetWidth);
+      }
+    } // step 2 and 3 are mutually exclusive
+
+    // 3. if there were no flex columns, then grow the table to the
+    //    minWidth.
+    else if (tableWidth < minWidthConstraint) {
+      final double delta = (minWidthConstraint - tableWidth) / columns;
+      for (int x = 0; x < columns; x += 1)
+        widths[x] = widths[x] + delta;
+      tableWidth = minWidthConstraint;
+    }
+
+    // beyond this point, unflexedTableWidth is no longer valid
+
+    // 4. apply the maximum width of the table, shrinking columns as
+    //    necessary, applying minimum column widths as we go
+    if (tableWidth > maxWidthConstraint) {
+      double deficit = tableWidth - maxWidthConstraint;
+      // Some columns may have low flex but have all the free space.
+      // (Consider a case with a 1px wide column of flex 1000.0 and
+      // a 1000px wide column of flex 1.0; the sizes coming from the
+      // maxIntrinsicWidths. If the maximum table width is 2px, then
+      // just applying the flexes to the deficit would result in a
+      // table with one column at -998px and one column at 990px,
+      // which is wildly unhelpful.)
+      // Similarly, some columns may be flexible, but not actually
+      // be shrinkable due to a large minimum width. (Consider a
+      // case with two columns, one is flex and one isn't, both have
+      // 1000px maxIntrinsicWidths, but the flex one has 1000px
+      // minIntrinsicWidth also. The whole deficit will have to come
+      // from the non-flex column.)
+      // So what we do is we repeatedly iterate through the flexible
+      // columns shrinking them proportionally until we have no
+      // available columns, then do the same to the non-flexible ones.
+      int availableColumns = columns;
+      while (deficit > precisionErrorTolerance && totalFlex > precisionErrorTolerance) {
+        double newTotalFlex = 0.0;
+        for (int x = 0; x < columns; x += 1) {
+          if (flexes[x] != null) {
+            final double newWidth = widths[x] - deficit * flexes[x]! / totalFlex;
+            assert(newWidth.isFinite);
+            if (newWidth <= minWidths[x]) {
+              // shrank to minimum
+              deficit -= widths[x] - minWidths[x];
+              widths[x] = minWidths[x];
+              flexes[x] = null;
+              availableColumns -= 1;
+            } else {
+              deficit -= widths[x] - newWidth;
+              widths[x] = newWidth;
+              newTotalFlex += flexes[x]!;
+            }
+            assert(widths[x] >= 0.0);
+          }
+        }
+        totalFlex = newTotalFlex;
+      }
+      while (deficit > precisionErrorTolerance && availableColumns > 0) {
+        // Now we have to take out the remaining space from the
+        // columns that aren't minimum sized.
+        // To make this fair, we repeatedly remove equal amounts from
+        // each column, clamped to the minimum width, until we run out
+        // of columns that aren't at their minWidth.
+        final double delta = deficit / availableColumns;
+        assert(delta != 0);
+        int newAvailableColumns = 0;
+        for (int x = 0; x < columns; x += 1) {
+          final double availableDelta = widths[x] - minWidths[x];
+          if (availableDelta > 0.0) {
+            if (availableDelta <= delta) {
+              // shrank to minimum
+              deficit -= widths[x] - minWidths[x];
+              widths[x] = minWidths[x];
+            } else {
+              deficit -= delta;
+              widths[x] = widths[x] - delta;
+              newAvailableColumns += 1;
+            }
+          }
+        }
+        availableColumns = newAvailableColumns;
+      }
+    }
+    return widths;
+  }
+
+  // cache the table geometry for painting purposes
+  final List<double> _rowTops = <double>[];
+  Iterable<double>? _columnLefts;
+
+  /// Returns the position and dimensions of the box that the given
+  /// row covers, in this render object's coordinate space (so the
+  /// left coordinate is always 0.0).
+  ///
+  /// The row being queried must exist.
+  ///
+  /// This is only valid after layout.
+  Rect getRowBox(int row) {
+    assert(row >= 0);
+    assert(row < rows);
+    assert(!debugNeedsLayout);
+    return Rect.fromLTRB(0.0, _rowTops[row], size.width, _rowTops[row + 1]);
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (rows * columns == 0) {
+      return constraints.constrain(const Size(0.0, 0.0));
+    }
+    final List<double> widths = _computeColumnWidths(constraints);
+    final double tableWidth = widths.fold(0.0, (double a, double b) => a + b);
+    double rowTop = 0.0;
+    for (int y = 0; y < rows; y += 1) {
+      double rowHeight = 0.0;
+      for (int x = 0; x < columns; x += 1) {
+        final int xy = x + y * columns;
+        final RenderBox? child = _children[xy];
+        if (child != null) {
+          final TableCellParentData childParentData = child.parentData! as TableCellParentData;
+          assert(childParentData != null);
+          switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
+            case TableCellVerticalAlignment.baseline:
+              assert(debugCannotComputeDryLayout(
+                reason: 'TableCellVerticalAlignment.baseline requires a full layout for baseline metrics to be available.'
+              ));
+              return const Size(0 ,0);
+            case TableCellVerticalAlignment.top:
+            case TableCellVerticalAlignment.middle:
+            case TableCellVerticalAlignment.bottom:
+              final Size childSize = child.getDryLayout(BoxConstraints.tightFor(width: widths[x]));
+              rowHeight = math.max(rowHeight, childSize.height);
+              break;
+            case TableCellVerticalAlignment.fill:
+              break;
+          }
+        }
+      }
+      rowTop += rowHeight;
+    }
+    return constraints.constrain(Size(tableWidth, rowTop));
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    final int rows = this.rows;
+    final int columns = this.columns;
+    assert(_children.length == rows * columns);
+    if (rows * columns == 0) {
+      // TODO(ianh): if columns is zero, this should be zero width
+      // TODO(ianh): if columns is not zero, this should be based on the column width specifications
+      size = constraints.constrain(const Size(0.0, 0.0));
+      return;
+    }
+    final List<double> widths = _computeColumnWidths(constraints);
+    final List<double> positions = List<double>.filled(columns, 0.0, growable: false);
+    final double tableWidth;
+    switch (textDirection) {
+      case TextDirection.rtl:
+        positions[columns - 1] = 0.0;
+        for (int x = columns - 2; x >= 0; x -= 1)
+          positions[x] = positions[x+1] + widths[x+1];
+        _columnLefts = positions.reversed;
+        tableWidth = positions.first + widths.first;
+        break;
+      case TextDirection.ltr:
+        positions[0] = 0.0;
+        for (int x = 1; x < columns; x += 1)
+          positions[x] = positions[x-1] + widths[x-1];
+        _columnLefts = positions;
+        tableWidth = positions.last + widths.last;
+        break;
+    }
+    _rowTops.clear();
+    _baselineDistance = null;
+    // then, lay out each row
+    double rowTop = 0.0;
+    for (int y = 0; y < rows; y += 1) {
+      _rowTops.add(rowTop);
+      double rowHeight = 0.0;
+      bool haveBaseline = false;
+      double beforeBaselineDistance = 0.0;
+      double afterBaselineDistance = 0.0;
+      final List<double> baselines = List<double>.filled(columns, 0.0, growable: false);
+      for (int x = 0; x < columns; x += 1) {
+        final int xy = x + y * columns;
+        final RenderBox? child = _children[xy];
+        if (child != null) {
+          final TableCellParentData childParentData = child.parentData! as TableCellParentData;
+          assert(childParentData != null);
+          childParentData.x = x;
+          childParentData.y = y;
+          switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
+            case TableCellVerticalAlignment.baseline:
+              assert(textBaseline != null, 'An explicit textBaseline is required when using baseline alignment.');
+              child.layout(BoxConstraints.tightFor(width: widths[x]), parentUsesSize: true);
+              final double? childBaseline = child.getDistanceToBaseline(textBaseline!, onlyReal: true);
+              if (childBaseline != null) {
+                beforeBaselineDistance = math.max(beforeBaselineDistance, childBaseline);
+                afterBaselineDistance = math.max(afterBaselineDistance, child.size.height - childBaseline);
+                baselines[x] = childBaseline;
+                haveBaseline = true;
+              } else {
+                rowHeight = math.max(rowHeight, child.size.height);
+                childParentData.offset = Offset(positions[x], rowTop);
+              }
+              break;
+            case TableCellVerticalAlignment.top:
+            case TableCellVerticalAlignment.middle:
+            case TableCellVerticalAlignment.bottom:
+              child.layout(BoxConstraints.tightFor(width: widths[x]), parentUsesSize: true);
+              rowHeight = math.max(rowHeight, child.size.height);
+              break;
+            case TableCellVerticalAlignment.fill:
+              break;
+          }
+        }
+      }
+      if (haveBaseline) {
+        if (y == 0)
+          _baselineDistance = beforeBaselineDistance;
+        rowHeight = math.max(rowHeight, beforeBaselineDistance + afterBaselineDistance);
+      }
+      for (int x = 0; x < columns; x += 1) {
+        final int xy = x + y * columns;
+        final RenderBox? child = _children[xy];
+        if (child != null) {
+          final TableCellParentData childParentData = child.parentData! as TableCellParentData;
+          switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
+            case TableCellVerticalAlignment.baseline:
+              childParentData.offset = Offset(positions[x], rowTop + beforeBaselineDistance - baselines[x]);
+              break;
+            case TableCellVerticalAlignment.top:
+              childParentData.offset = Offset(positions[x], rowTop);
+              break;
+            case TableCellVerticalAlignment.middle:
+              childParentData.offset = Offset(positions[x], rowTop + (rowHeight - child.size.height) / 2.0);
+              break;
+            case TableCellVerticalAlignment.bottom:
+              childParentData.offset = Offset(positions[x], rowTop + rowHeight - child.size.height);
+              break;
+            case TableCellVerticalAlignment.fill:
+              child.layout(BoxConstraints.tightFor(width: widths[x], height: rowHeight));
+              childParentData.offset = Offset(positions[x], rowTop);
+              break;
+          }
+        }
+      }
+      rowTop += rowHeight;
+    }
+    _rowTops.add(rowTop);
+    size = constraints.constrain(Size(tableWidth, rowTop));
+    assert(_rowTops.length == rows + 1);
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    assert(_children.length == rows * columns);
+    for (int index = _children.length - 1; index >= 0; index -= 1) {
+      final RenderBox? child = _children[index];
+      if (child != null) {
+        final BoxParentData childParentData = child.parentData! as BoxParentData;
+        final bool isHit = result.addWithPaintOffset(
+          offset: childParentData.offset,
+          position: position,
+          hitTest: (BoxHitTestResult result, Offset? transformed) {
+            assert(transformed == position - childParentData.offset);
+            return child.hitTest(result, position: transformed!);
+          },
+        );
+        if (isHit)
+          return true;
+      }
+    }
+    return false;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    assert(_children.length == rows * columns);
+    if (rows * columns == 0) {
+      if (border != null) {
+        final Rect borderRect = Rect.fromLTWH(offset.dx, offset.dy, size.width, 0.0);
+        border!.paint(context.canvas, borderRect, rows: const <double>[], columns: const <double>[]);
+      }
+      return;
+    }
+    assert(_rowTops.length == rows + 1);
+    if (_rowDecorations != null) {
+      assert(_rowDecorations!.length == _rowDecorationPainters!.length);
+      final Canvas canvas = context.canvas;
+      for (int y = 0; y < rows; y += 1) {
+        if (_rowDecorations!.length <= y)
+          break;
+        if (_rowDecorations![y] != null) {
+          _rowDecorationPainters![y] ??= _rowDecorations![y]!.createBoxPainter(markNeedsPaint);
+          _rowDecorationPainters![y]!.paint(
+            canvas,
+            Offset(offset.dx, offset.dy + _rowTops[y]),
+            configuration.copyWith(size: Size(size.width, _rowTops[y+1] - _rowTops[y])),
+          );
+        }
+      }
+    }
+    for (int index = 0; index < _children.length; index += 1) {
+      final RenderBox? child = _children[index];
+      if (child != null) {
+        final BoxParentData childParentData = child.parentData! as BoxParentData;
+        context.paintChild(child, childParentData.offset + offset);
+      }
+    }
+    assert(_rows == _rowTops.length - 1);
+    assert(_columns == _columnLefts!.length);
+    if (border != null) {
+      // The border rect might not fill the entire height of this render object
+      // if the rows underflow. We always force the columns to fill the width of
+      // the render object, which means the columns cannot underflow.
+      final Rect borderRect = Rect.fromLTWH(offset.dx, offset.dy, size.width, _rowTops.last);
+      final Iterable<double> rows = _rowTops.getRange(1, _rowTops.length - 1);
+      final Iterable<double> columns = _columnLefts!.skip(1);
+      border!.paint(context.canvas, borderRect, rows: rows, columns: columns);
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<TableBorder>('border', border, defaultValue: null));
+    properties.add(DiagnosticsProperty<Map<int, TableColumnWidth>>('specified column widths', _columnWidths, level: _columnWidths.isEmpty ? DiagnosticLevel.hidden : DiagnosticLevel.info));
+    properties.add(DiagnosticsProperty<TableColumnWidth>('default column width', defaultColumnWidth));
+    properties.add(MessageProperty('table size', '$columns\u00D7$rows'));
+    properties.add(IterableProperty<String>('column offsets', _columnLefts?.map(debugFormatDouble), ifNull: 'unknown'));
+    properties.add(IterableProperty<String>('row offsets', _rowTops.map(debugFormatDouble), ifNull: 'unknown'));
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    if (_children.isEmpty) {
+      return <DiagnosticsNode>[DiagnosticsNode.message('table is empty')];
+    }
+
+    final List<DiagnosticsNode> children = <DiagnosticsNode>[];
+    for (int y = 0; y < rows; y += 1) {
+      for (int x = 0; x < columns; x += 1) {
+        final int xy = x + y * columns;
+        final RenderBox? child = _children[xy];
+        final String name = 'child ($x, $y)';
+        if (child != null)
+          children.add(child.toDiagnosticsNode(name: name));
+        else
+          children.add(DiagnosticsProperty<Object>(name, null, ifNull: 'is null', showSeparator: false));
+      }
+    }
+    return children;
+  }
+}
diff --git a/lib/src/rendering/table_border.dart b/lib/src/rendering/table_border.dart
new file mode 100644
index 0000000..4758181
--- /dev/null
+++ b/lib/src/rendering/table_border.dart
@@ -0,0 +1,282 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart' hide Border;
+
+/// Border specification for [Table] widgets.
+///
+/// This is like [Border], with the addition of two sides: the inner horizontal
+/// borders between rows and the inner vertical borders between columns.
+///
+/// The sides are represented by [BorderSide] objects.
+@immutable
+class TableBorder {
+  /// Creates a border for a table.
+  ///
+  /// All the sides of the border default to [BorderSide.none].
+  const TableBorder({
+    this.top = BorderSide.none,
+    this.right = BorderSide.none,
+    this.bottom = BorderSide.none,
+    this.left = BorderSide.none,
+    this.horizontalInside = BorderSide.none,
+    this.verticalInside = BorderSide.none,
+  });
+
+  /// A uniform border with all sides the same color and width.
+  ///
+  /// The sides default to black solid borders, one logical pixel wide.
+  factory TableBorder.all({
+    Color color = const Color(0xFF000000),
+    double width = 1.0,
+    BorderStyle style = BorderStyle.solid,
+  }) {
+    final BorderSide side = BorderSide(color: color, width: width, style: style);
+    return TableBorder(top: side, right: side, bottom: side, left: side, horizontalInside: side, verticalInside: side);
+  }
+
+  /// Creates a border for a table where all the interior sides use the same
+  /// styling and all the exterior sides use the same styling.
+  factory TableBorder.symmetric({
+    BorderSide inside = BorderSide.none,
+    BorderSide outside = BorderSide.none,
+  }) {
+    return TableBorder(
+      top: outside,
+      right: outside,
+      bottom: outside,
+      left: outside,
+      horizontalInside: inside,
+      verticalInside: inside,
+    );
+  }
+
+  /// The top side of this border.
+  final BorderSide top;
+
+  /// The right side of this border.
+  final BorderSide right;
+
+  /// The bottom side of this border.
+  final BorderSide bottom;
+
+  /// The left side of this border.
+  final BorderSide left;
+
+  /// The horizontal interior sides of this border.
+  final BorderSide horizontalInside;
+
+  /// The vertical interior sides of this border.
+  final BorderSide verticalInside;
+
+  /// The widths of the sides of this border represented as an [EdgeInsets].
+  ///
+  /// This can be used, for example, with a [Padding] widget to inset a box by
+  /// the size of these borders.
+  EdgeInsets get dimensions {
+    return EdgeInsets.fromLTRB(left.width, top.width, right.width, bottom.width);
+  }
+
+  /// Whether all the sides of the border (outside and inside) are identical.
+  /// Uniform borders are typically more efficient to paint.
+  bool get isUniform {
+    assert(top != null);
+    assert(right != null);
+    assert(bottom != null);
+    assert(left != null);
+    assert(horizontalInside != null);
+    assert(verticalInside != null);
+
+    final Color topColor = top.color;
+    if (right.color != topColor ||
+        bottom.color != topColor ||
+        left.color != topColor ||
+        horizontalInside.color != topColor ||
+        verticalInside.color != topColor)
+      return false;
+
+    final double topWidth = top.width;
+    if (right.width != topWidth ||
+        bottom.width != topWidth ||
+        left.width != topWidth ||
+        horizontalInside.width != topWidth ||
+        verticalInside.width != topWidth)
+      return false;
+
+    final BorderStyle topStyle = top.style;
+    if (right.style != topStyle ||
+        bottom.style != topStyle ||
+        left.style != topStyle ||
+        horizontalInside.style != topStyle ||
+        verticalInside.style != topStyle)
+      return false;
+
+    return true;
+  }
+
+  /// Creates a copy of this border but with the widths scaled by the factor `t`.
+  ///
+  /// The `t` argument represents the multiplicand, or the position on the
+  /// timeline for an interpolation from nothing to `this`, with 0.0 meaning
+  /// that the object returned should be the nil variant of this object, 1.0
+  /// meaning that no change should be applied, returning `this` (or something
+  /// equivalent to `this`), and other values meaning that the object should be
+  /// multiplied by `t`. Negative values are treated like zero.
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  ///
+  /// See also:
+  ///
+  ///  * [BorderSide.scale], which is used to implement this method.
+  TableBorder scale(double t) {
+    return TableBorder(
+      top: top.scale(t),
+      right: right.scale(t),
+      bottom: bottom.scale(t),
+      left: left.scale(t),
+      horizontalInside: horizontalInside.scale(t),
+      verticalInside: verticalInside.scale(t),
+    );
+  }
+
+  /// Linearly interpolate between two table borders.
+  ///
+  /// If a border is null, it is treated as having only [BorderSide.none]
+  /// borders.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static TableBorder? lerp(TableBorder? a, TableBorder? b, double t) {
+    assert(t != null);
+    if (a == null && b == null)
+      return null;
+    if (a == null)
+      return b!.scale(t);
+    if (b == null)
+      return a.scale(1.0 - t);
+    return TableBorder(
+      top: BorderSide.lerp(a.top, b.top, t),
+      right: BorderSide.lerp(a.right, b.right, t),
+      bottom: BorderSide.lerp(a.bottom, b.bottom, t),
+      left: BorderSide.lerp(a.left, b.left, t),
+      horizontalInside: BorderSide.lerp(a.horizontalInside, b.horizontalInside, t),
+      verticalInside: BorderSide.lerp(a.verticalInside, b.verticalInside, t),
+    );
+  }
+
+  /// Paints the border around the given [Rect] on the given [Canvas], with the
+  /// given rows and columns.
+  ///
+  /// Uniform borders are more efficient to paint than more complex borders.
+  ///
+  /// The `rows` argument specifies the vertical positions between the rows,
+  /// relative to the given rectangle. For example, if the table contained two
+  /// rows of height 100.0 each, then `rows` would contain a single value,
+  /// 100.0, which is the vertical position between the two rows (relative to
+  /// the top edge of `rect`).
+  ///
+  /// The `columns` argument specifies the horizontal positions between the
+  /// columns, relative to the given rectangle. For example, if the table
+  /// contained two columns of height 100.0 each, then `columns` would contain a
+  /// single value, 100.0, which is the vertical position between the two
+  /// columns (relative to the left edge of `rect`).
+  ///
+  /// The [verticalInside] border is only drawn if there are at least two
+  /// columns. The [horizontalInside] border is only drawn if there are at least
+  /// two rows. The horizontal borders are drawn after the vertical borders.
+  ///
+  /// The outer borders (in the order [top], [right], [bottom], [left], with
+  /// [left] above the others) are painted after the inner borders.
+  ///
+  /// The paint order is particularly notable in the case of
+  /// partially-transparent borders.
+  void paint(
+    Canvas canvas,
+    Rect rect, {
+    required Iterable<double> rows,
+    required Iterable<double> columns,
+  }) {
+    // properties can't be null
+    assert(top != null);
+    assert(right != null);
+    assert(bottom != null);
+    assert(left != null);
+    assert(horizontalInside != null);
+    assert(verticalInside != null);
+
+    // arguments can't be null
+    assert(canvas != null);
+    assert(rect != null);
+    assert(rows != null);
+    assert(rows.isEmpty || (rows.first >= 0.0 && rows.last <= rect.height));
+    assert(columns != null);
+    assert(columns.isEmpty || (columns.first >= 0.0 && columns.last <= rect.width));
+
+    if (columns.isNotEmpty || rows.isNotEmpty) {
+      final Paint paint = Paint();
+      final Path path = Path();
+
+      if (columns.isNotEmpty) {
+        switch (verticalInside.style) {
+          case BorderStyle.solid:
+            paint
+              ..color = verticalInside.color
+              ..strokeWidth = verticalInside.width
+              ..style = PaintingStyle.stroke;
+            path.reset();
+            for (final double x in columns) {
+              path.moveTo(rect.left + x, rect.top);
+              path.lineTo(rect.left + x, rect.bottom);
+            }
+            canvas.drawPath(path, paint);
+            break;
+          case BorderStyle.none:
+            break;
+        }
+      }
+
+      if (rows.isNotEmpty) {
+        switch (horizontalInside.style) {
+          case BorderStyle.solid:
+            paint
+              ..color = horizontalInside.color
+              ..strokeWidth = horizontalInside.width
+              ..style = PaintingStyle.stroke;
+            path.reset();
+            for (final double y in rows) {
+              path.moveTo(rect.left, rect.top + y);
+              path.lineTo(rect.right, rect.top + y);
+            }
+            canvas.drawPath(path, paint);
+            break;
+          case BorderStyle.none:
+            break;
+        }
+      }
+    }
+    paintBorder(canvas, rect, top: top, right: right, bottom: bottom, left: left);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is TableBorder
+        && other.top == top
+        && other.right == right
+        && other.bottom == bottom
+        && other.left == left
+        && other.horizontalInside == horizontalInside
+        && other.verticalInside == verticalInside;
+  }
+
+  @override
+  int get hashCode => hashValues(top, right, bottom, left, horizontalInside, verticalInside);
+
+  @override
+  String toString() => 'TableBorder($top, $right, $bottom, $left, $horizontalInside, $verticalInside)';
+}
diff --git a/lib/src/rendering/texture.dart b/lib/src/rendering/texture.dart
new file mode 100644
index 0000000..df9807d
--- /dev/null
+++ b/lib/src/rendering/texture.dart
@@ -0,0 +1,93 @@
+// Copyright 2014 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.
+
+import 'box.dart';
+import 'layer.dart';
+import 'object.dart';
+
+/// A rectangle upon which a backend texture is mapped.
+///
+/// Backend textures are images that can be applied (mapped) to an area of the
+/// Flutter view. They are created, managed, and updated using a
+/// platform-specific texture registry. This is typically done by a plugin
+/// that integrates with host platform video player, camera, or OpenGL APIs,
+/// or similar image sources.
+///
+/// A texture box refers to its backend texture using an integer ID. Texture
+/// IDs are obtained from the texture registry and are scoped to the Flutter
+/// view. Texture IDs may be reused after deregistration, at the discretion
+/// of the registry. The use of texture IDs currently unknown to the registry
+/// will silently result in a blank rectangle.
+///
+/// Texture boxes are repainted autonomously as dictated by the backend (e.g. on
+/// arrival of a video frame). Such repainting generally does not involve
+/// executing Dart code.
+///
+/// The size of the rectangle is determined by the parent, and the texture is
+/// automatically scaled to fit.
+///
+/// See also:
+///
+///  * <https://api.flutter.dev/javadoc/io/flutter/view/TextureRegistry.html>
+///    for how to create and manage backend textures on Android.
+///  * <https://api.flutter.dev/objcdoc/Protocols/FlutterTextureRegistry.html>
+///    for how to create and manage backend textures on iOS.
+class TextureBox extends RenderBox {
+  /// Creates a box backed by the texture identified by [textureId], and use
+  /// [filterQuality] to set texture's [FilterQuality].
+  TextureBox({
+    required int textureId,
+    FilterQuality filterQuality = FilterQuality.low,
+  }) : assert(textureId != null),
+      _textureId = textureId,
+      _filterQuality = filterQuality;
+
+  /// The identity of the backend texture.
+  int get textureId => _textureId;
+  int _textureId;
+  set textureId(int value) {
+    assert(value != null);
+    if (value != _textureId) {
+      _textureId = value;
+      markNeedsPaint();
+    }
+  }
+
+  /// {@macro flutter.widgets.Texture.filterQuality}
+  FilterQuality get filterQuality => _filterQuality;
+  FilterQuality _filterQuality;
+  set filterQuality(FilterQuality value) {
+    assert(value != null);
+    if (value == _filterQuality)
+      return;
+    _filterQuality = value;
+    markNeedsPaint();
+  }
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  bool get alwaysNeedsCompositing => true;
+
+  @override
+  bool get isRepaintBoundary => true;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return constraints.biggest;
+  }
+
+  @override
+  bool hitTestSelf(Offset position) => true;
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    context.addLayer(TextureLayer(
+      rect: Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height),
+      textureId: _textureId,
+      filterQuality: _filterQuality,
+    ));
+  }
+}
diff --git a/lib/src/rendering/tweens.dart b/lib/src/rendering/tweens.dart
new file mode 100644
index 0000000..9fee4f6
--- /dev/null
+++ b/lib/src/rendering/tweens.dart
@@ -0,0 +1,78 @@
+// Copyright 2014 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.
+
+import 'package:flute/animation.dart';
+import 'package:flute/painting.dart';
+
+/// An interpolation between two fractional offsets.
+///
+/// This class specializes the interpolation of [Tween<FractionalOffset>] to be
+/// appropriate for fractional offsets.
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+///
+/// See also:
+///
+///  * [AlignmentTween], which interpolates between to [Alignment] objects.
+class FractionalOffsetTween extends Tween<FractionalOffset?> {
+  /// Creates a fractional offset tween.
+  ///
+  /// The [begin] and [end] properties may be null; the null value
+  /// is treated as meaning the center.
+  FractionalOffsetTween({ FractionalOffset? begin, FractionalOffset? end })
+    : super(begin: begin, end: end);
+
+  /// Returns the value this variable has at the given animation clock value.
+  @override
+  FractionalOffset? lerp(double t) => FractionalOffset.lerp(begin, end, t);
+}
+
+/// An interpolation between two alignments.
+///
+/// This class specializes the interpolation of [Tween<Alignment>] to be
+/// appropriate for alignments.
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+///
+/// See also:
+///
+///  * [AlignmentGeometryTween], which interpolates between two
+///    [AlignmentGeometry] objects.
+class AlignmentTween extends Tween<Alignment> {
+  /// Creates a fractional offset tween.
+  ///
+  /// The [begin] and [end] properties may be null; the null value
+  /// is treated as meaning the center.
+  AlignmentTween({ Alignment? begin, Alignment? end })
+    : super(begin: begin, end: end);
+
+  /// Returns the value this variable has at the given animation clock value.
+  @override
+  Alignment lerp(double t) => Alignment.lerp(begin, end, t)!;
+}
+
+/// An interpolation between two [AlignmentGeometry].
+///
+/// This class specializes the interpolation of [Tween<AlignmentGeometry>]
+/// to be appropriate for alignments.
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+///
+/// See also:
+///
+///  * [AlignmentTween], which interpolates between two [Alignment] objects.
+class AlignmentGeometryTween extends Tween<AlignmentGeometry?> {
+  /// Creates a fractional offset geometry tween.
+  ///
+  /// The [begin] and [end] properties may be null; the null value
+  /// is treated as meaning the center.
+  AlignmentGeometryTween({
+    AlignmentGeometry? begin,
+    AlignmentGeometry? end,
+  }) : super(begin: begin, end: end);
+
+  /// Returns the value this variable has at the given animation clock value.
+  @override
+  AlignmentGeometry? lerp(double t) => AlignmentGeometry.lerp(begin, end, t);
+}
diff --git a/lib/src/rendering/view.dart b/lib/src/rendering/view.dart
new file mode 100644
index 0000000..85c40ae
--- /dev/null
+++ b/lib/src/rendering/view.dart
@@ -0,0 +1,340 @@
+// Copyright 2014 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.
+
+import 'dart:developer';
+import 'dart:io' show Platform;
+import 'package:flute/ui.dart' as ui show Scene, SceneBuilder, FlutterView;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/services.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'binding.dart';
+import 'box.dart';
+import 'debug.dart';
+import 'layer.dart';
+import 'object.dart';
+
+/// The layout constraints for the root render object.
+@immutable
+class ViewConfiguration {
+  /// Creates a view configuration.
+  ///
+  /// By default, the view has zero [size] and a [devicePixelRatio] of 1.0.
+  const ViewConfiguration({
+    this.size = Size.zero,
+    this.devicePixelRatio = 1.0,
+  });
+
+  /// The size of the output surface.
+  final Size size;
+
+  /// The pixel density of the output surface.
+  final double devicePixelRatio;
+
+  /// Creates a transformation matrix that applies the [devicePixelRatio].
+  Matrix4 toMatrix() {
+    return Matrix4.diagonal3Values(devicePixelRatio, devicePixelRatio, 1.0);
+  }
+
+  @override
+  String toString() => '$size at ${debugFormatDouble(devicePixelRatio)}x';
+}
+
+/// The root of the render tree.
+///
+/// The view represents the total output surface of the render tree and handles
+/// bootstrapping the rendering pipeline. The view has a unique child
+/// [RenderBox], which is required to fill the entire output surface.
+class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
+  /// Creates the root of the render tree.
+  ///
+  /// Typically created by the binding (e.g., [RendererBinding]).
+  ///
+  /// The [configuration] must not be null.
+  RenderView({
+    RenderBox? child,
+    required ViewConfiguration configuration,
+    required ui.FlutterView window,
+  }) : assert(configuration != null),
+       _configuration = configuration,
+       _window = window {
+    this.child = child;
+  }
+
+  /// The current layout size of the view.
+  Size get size => _size;
+  Size _size = Size.zero;
+
+  /// The constraints used for the root layout.
+  ViewConfiguration get configuration => _configuration;
+  ViewConfiguration _configuration;
+  /// The configuration is initially set by the `configuration` argument
+  /// passed to the constructor.
+  ///
+  /// Always call [prepareInitialFrame] before changing the configuration.
+  set configuration(ViewConfiguration value) {
+    assert(value != null);
+    if (configuration == value)
+      return;
+    _configuration = value;
+    replaceRootLayer(_updateMatricesAndCreateNewRootLayer());
+    assert(_rootTransform != null);
+    markNeedsLayout();
+  }
+
+  final ui.FlutterView _window;
+
+  /// Whether Flutter should automatically compute the desired system UI.
+  ///
+  /// When this setting is enabled, Flutter will hit-test the layer tree at the
+  /// top and bottom of the screen on each frame looking for an
+  /// [AnnotatedRegionLayer] with an instance of a [SystemUiOverlayStyle]. The
+  /// hit-test result from the top of the screen provides the status bar settings
+  /// and the hit-test result from the bottom of the screen provides the system
+  /// nav bar settings.
+  ///
+  /// Setting this to false does not cause previous automatic adjustments to be
+  /// reset, nor does setting it to true cause the app to update immediately.
+  ///
+  /// If you want to imperatively set the system ui style instead, it is
+  /// recommended that [automaticSystemUiAdjustment] is set to false.
+  ///
+  /// See also:
+  ///
+  ///  * [AnnotatedRegion], for placing [SystemUiOverlayStyle] in the layer tree.
+  ///  * [SystemChrome.setSystemUIOverlayStyle], for imperatively setting the system ui style.
+  bool automaticSystemUiAdjustment = true;
+
+  /// Bootstrap the rendering pipeline by scheduling the first frame.
+  ///
+  /// Deprecated. Call [prepareInitialFrame] followed by a call to
+  /// [PipelineOwner.requestVisualUpdate] on [owner] instead.
+  @Deprecated(
+    'Call prepareInitialFrame followed by owner.requestVisualUpdate() instead. '
+    'This feature was deprecated after v1.10.0.'
+  )
+  void scheduleInitialFrame() {
+    prepareInitialFrame();
+    owner!.requestVisualUpdate();
+  }
+
+  /// Bootstrap the rendering pipeline by preparing the first frame.
+  ///
+  /// This should only be called once, and must be called before changing
+  /// [configuration]. It is typically called immediately after calling the
+  /// constructor.
+  ///
+  /// This does not actually schedule the first frame. Call
+  /// [PipelineOwner.requestVisualUpdate] on [owner] to do that.
+  void prepareInitialFrame() {
+    assert(owner != null);
+    assert(_rootTransform == null);
+    scheduleInitialLayout();
+    scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
+    assert(_rootTransform != null);
+  }
+
+  Matrix4? _rootTransform;
+
+  TransformLayer _updateMatricesAndCreateNewRootLayer() {
+    _rootTransform = configuration.toMatrix();
+    final TransformLayer rootLayer = TransformLayer(transform: _rootTransform);
+    rootLayer.attach(this);
+    assert(_rootTransform != null);
+    return rootLayer;
+  }
+
+  // We never call layout() on this class, so this should never get
+  // checked. (This class is laid out using scheduleInitialLayout().)
+  @override
+  void debugAssertDoesMeetConstraints() { assert(false); }
+
+  @override
+  void performResize() {
+    assert(false);
+  }
+
+  @override
+  void performLayout() {
+    assert(_rootTransform != null);
+    _size = configuration.size;
+    assert(_size.isFinite);
+
+    if (child != null)
+      child!.layout(BoxConstraints.tight(_size));
+  }
+
+  @override
+  void rotate({ int? oldAngle, int? newAngle, Duration? time }) {
+    assert(false); // nobody tells the screen to rotate, the whole rotate() dance is started from our performResize()
+  }
+
+  /// Determines the set of render objects located at the given position.
+  ///
+  /// Returns true if the given point is contained in this render object or one
+  /// of its descendants. Adds any render objects that contain the point to the
+  /// given hit test result.
+  ///
+  /// The [position] argument is in the coordinate system of the render view,
+  /// which is to say, in logical pixels. This is not necessarily the same
+  /// coordinate system as that expected by the root [Layer], which will
+  /// normally be in physical (device) pixels.
+  bool hitTest(HitTestResult result, { required Offset position }) {
+    if (child != null)
+      child!.hitTest(BoxHitTestResult.wrap(result), position: position);
+    result.add(HitTestEntry(this));
+    return true;
+  }
+
+  /// Determines the set of mouse tracker annotations at the given position.
+  ///
+  /// See also:
+  ///
+  ///  * [Layer.findAllAnnotations], which is used by this method to find all
+  ///    [AnnotatedRegionLayer]s annotated for mouse tracking.
+  HitTestResult hitTestMouseTrackers(Offset position) {
+    assert(position != null);
+    // Layer hit testing is done using device pixels, so we have to convert
+    // the logical coordinates of the event location back to device pixels
+    // here.
+    final BoxHitTestResult result = BoxHitTestResult();
+    hitTest(result, position: position);
+    return result;
+  }
+
+  @override
+  bool get isRepaintBoundary => true;
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null)
+      context.paintChild(child!, offset);
+  }
+
+  @override
+  void applyPaintTransform(RenderBox child, Matrix4 transform) {
+    assert(_rootTransform != null);
+    transform.multiply(_rootTransform!);
+    super.applyPaintTransform(child, transform);
+  }
+
+  /// Uploads the composited layer tree to the engine.
+  ///
+  /// Actually causes the output of the rendering pipeline to appear on screen.
+  void compositeFrame() {
+    Timeline.startSync('Compositing', arguments: timelineArgumentsIndicatingLandmarkEvent);
+    try {
+      final ui.SceneBuilder builder = ui.SceneBuilder();
+      final ui.Scene scene = layer!.buildScene(builder);
+      if (automaticSystemUiAdjustment)
+        _updateSystemChrome();
+      _window.render(scene);
+      scene.dispose();
+      assert(() {
+        if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
+          debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0);
+        return true;
+      }());
+    } finally {
+      Timeline.finishSync();
+    }
+  }
+
+  void _updateSystemChrome() {
+    // Take overlay style from the place where a system status bar and system
+    // navigation bar are placed to update system style overlay.
+    // The center of the system navigation bar and the center of the status bar
+    // are used to get SystemUiOverlayStyle's to update system overlay appearance.
+    //
+    //         Horizontal center of the screen
+    //                 V
+    //    ++++++++++++++++++++++++++
+    //    |                        |
+    //    |    System status bar   |  <- Vertical center of the status bar
+    //    |                        |
+    //    ++++++++++++++++++++++++++
+    //    |                        |
+    //    |        Content         |
+    //    ~                        ~
+    //    |                        |
+    //    ++++++++++++++++++++++++++
+    //    |                        |
+    //    |  System navigation bar | <- Vertical center of the navigation bar
+    //    |                        |
+    //    ++++++++++++++++++++++++++ <- bounds.bottom
+    final Rect bounds = paintBounds;
+    // Center of the status bar
+    final Offset top = Offset(
+      // Horizontal center of the screen
+      bounds.center.dx,
+      // The vertical center of the system status bar. The system status bar
+      // height is kept as top window padding.
+      _window.padding.top / 2.0,
+    );
+    // Center of the navigation bar
+    final Offset bottom = Offset(
+      // Horizontal center of the screen
+      bounds.center.dx,
+      // Vertical center of the system navigation bar. The system navigation bar
+      // height is kept as bottom window padding. The "1" needs to be subtracted
+      // from the bottom because available pixels are in (0..bottom) range.
+      // I.e. for a device with 1920 height, bound.bottom is 1920, but the most
+      // bottom drawn pixel is at 1919 position.
+      bounds.bottom - 1.0 - _window.padding.bottom / 2.0,
+    );
+    final SystemUiOverlayStyle? upperOverlayStyle = layer!.find<SystemUiOverlayStyle>(top);
+    // Only android has a customizable system navigation bar.
+    SystemUiOverlayStyle? lowerOverlayStyle;
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.android:
+        lowerOverlayStyle = layer!.find<SystemUiOverlayStyle>(bottom);
+        break;
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.iOS:
+      case TargetPlatform.linux:
+      case TargetPlatform.macOS:
+      case TargetPlatform.windows:
+        break;
+    }
+    // If there are no overlay styles in the UI don't bother updating.
+    if (upperOverlayStyle != null || lowerOverlayStyle != null) {
+      final SystemUiOverlayStyle overlayStyle = SystemUiOverlayStyle(
+        statusBarBrightness: upperOverlayStyle?.statusBarBrightness,
+        statusBarIconBrightness: upperOverlayStyle?.statusBarIconBrightness,
+        statusBarColor: upperOverlayStyle?.statusBarColor,
+        systemNavigationBarColor: lowerOverlayStyle?.systemNavigationBarColor,
+        systemNavigationBarDividerColor: lowerOverlayStyle?.systemNavigationBarDividerColor,
+        systemNavigationBarIconBrightness: lowerOverlayStyle?.systemNavigationBarIconBrightness,
+      );
+      SystemChrome.setSystemUIOverlayStyle(overlayStyle);
+    }
+  }
+
+  @override
+  Rect get paintBounds => Offset.zero & (size * configuration.devicePixelRatio);
+
+  @override
+  Rect get semanticBounds {
+    assert(_rootTransform != null);
+    return MatrixUtils.transformRect(_rootTransform!, Offset.zero & size);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    // call to ${super.debugFillProperties(description)} is omitted because the
+    // root superclasses don't include any interesting information for this
+    // class
+    assert(() {
+      properties.add(DiagnosticsNode.message('debug mode enabled - ${kIsWeb ? 'Web' :  Platform.operatingSystem}'));
+      return true;
+    }());
+    properties.add(DiagnosticsProperty<Size>('window size', _window.physicalSize, tooltip: 'in physical pixels'));
+    properties.add(DoubleProperty('device pixel ratio', _window.devicePixelRatio, tooltip: 'physical pixels per logical pixel'));
+    properties.add(DiagnosticsProperty<ViewConfiguration>('configuration', configuration, tooltip: 'in logical pixels'));
+    if (_window.platformDispatcher.semanticsEnabled)
+      properties.add(DiagnosticsNode.message('semantics enabled'));
+  }
+}
diff --git a/lib/src/rendering/viewport.dart b/lib/src/rendering/viewport.dart
new file mode 100644
index 0000000..2cf8799
--- /dev/null
+++ b/lib/src/rendering/viewport.dart
@@ -0,0 +1,2028 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/semantics.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'box.dart';
+import 'layer.dart';
+import 'object.dart';
+import 'sliver.dart';
+import 'viewport_offset.dart';
+
+/// The unit of measurement for a [Viewport.cacheExtent].
+enum CacheExtentStyle {
+  /// Treat the [Viewport.cacheExtent] as logical pixels.
+  pixel,
+  /// Treat the [Viewport.cacheExtent] as a multiplier of the main axis extent.
+  viewport,
+}
+
+/// An interface for render objects that are bigger on the inside.
+///
+/// Some render objects, such as [RenderViewport], present a portion of their
+/// content, which can be controlled by a [ViewportOffset]. This interface lets
+/// the framework recognize such render objects and interact with them without
+/// having specific knowledge of all the various types of viewports.
+abstract class RenderAbstractViewport extends RenderObject {
+  // This class is intended to be used as an interface, and should not be
+  // extended directly; this constructor prevents instantiation and extension.
+  // ignore: unused_element
+  factory RenderAbstractViewport._() => throw Error();
+
+  /// Returns the [RenderAbstractViewport] that most tightly encloses the given
+  /// render object.
+  ///
+  /// If the object does not have a [RenderAbstractViewport] as an ancestor,
+  /// this function returns null.
+  static RenderAbstractViewport? of(RenderObject? object) {
+    while (object != null) {
+      if (object is RenderAbstractViewport)
+        return object;
+      object = object.parent as RenderObject?;
+    }
+    return null;
+  }
+
+  /// Returns the offset that would be needed to reveal the `target`
+  /// [RenderObject].
+  ///
+  /// This is used by [RenderViewportBase.showInViewport], which is
+  /// itself used by [RenderObject.showOnScreen] for
+  /// [RenderViewportBase], which is in turn used by the semantics
+  /// system to implement scrolling for accessibility tools.
+  ///
+  /// The optional `rect` parameter describes which area of that `target` object
+  /// should be revealed in the viewport. If `rect` is null, the entire
+  /// `target` [RenderObject] (as defined by its [RenderObject.paintBounds])
+  /// will be revealed. If `rect` is provided it has to be given in the
+  /// coordinate system of the `target` object.
+  ///
+  /// The `alignment` argument describes where the target should be positioned
+  /// after applying the returned offset. If `alignment` is 0.0, the child must
+  /// be positioned as close to the leading edge of the viewport as possible. If
+  /// `alignment` is 1.0, the child must be positioned as close to the trailing
+  /// edge of the viewport as possible. If `alignment` is 0.5, the child must be
+  /// positioned as close to the center of the viewport as possible.
+  ///
+  /// The `target` might not be a direct child of this viewport but it must be a
+  /// descendant of the viewport. Other viewports in between this viewport and
+  /// the `target` will not be adjusted.
+  ///
+  /// This method assumes that the content of the viewport moves linearly, i.e.
+  /// when the offset of the viewport is changed by x then `target` also moves
+  /// by x within the viewport.
+  ///
+  /// See also:
+  ///
+  ///  * [RevealedOffset], which describes the return value of this method.
+  RevealedOffset getOffsetToReveal(RenderObject target, double alignment, { Rect? rect });
+
+  /// The default value for the cache extent of the viewport.
+  ///
+  /// This default assumes [CacheExtentStyle.pixel].
+  ///
+  /// See also:
+  ///
+  ///  * [RenderViewportBase.cacheExtent] for a definition of the cache extent.
+  static const double defaultCacheExtent = 250.0;
+}
+
+/// Return value for [RenderAbstractViewport.getOffsetToReveal].
+///
+/// It indicates the [offset] required to reveal an element in a viewport and
+/// the [rect] position said element would have in the viewport at that
+/// [offset].
+class RevealedOffset {
+
+  /// Instantiates a return value for [RenderAbstractViewport.getOffsetToReveal].
+  const RevealedOffset({
+    required this.offset,
+    required this.rect,
+  }) : assert(offset != null),
+       assert(rect != null);
+
+  /// Offset for the viewport to reveal a specific element in the viewport.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderAbstractViewport.getOffsetToReveal], which calculates this
+  ///    value for a specific element.
+  final double offset;
+
+  /// The [Rect] in the outer coordinate system of the viewport at which the
+  /// to-be-revealed element would be located if the viewport's offset is set
+  /// to [offset].
+  ///
+  /// A viewport usually has two coordinate systems and works as an adapter
+  /// between the two:
+  ///
+  /// The inner coordinate system has its origin at the top left corner of the
+  /// content that moves inside the viewport. The origin of this coordinate
+  /// system usually moves around relative to the leading edge of the viewport
+  /// when the viewport offset changes.
+  ///
+  /// The outer coordinate system has its origin at the top left corner of the
+  /// visible part of the viewport. This origin stays at the same position
+  /// regardless of the current viewport offset.
+  ///
+  /// In other words: [rect] describes where the revealed element would be
+  /// located relative to the top left corner of the visible part of the
+  /// viewport if the viewport's offset is set to [offset].
+  ///
+  /// See also:
+  ///
+  ///  * [RenderAbstractViewport.getOffsetToReveal], which calculates this
+  ///    value for a specific element.
+  final Rect rect;
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'RevealedOffset')}(offset: $offset, rect: $rect)';
+  }
+}
+
+/// A base class for render objects that are bigger on the inside.
+///
+/// This render object provides the shared code for render objects that host
+/// [RenderSliver] render objects inside a [RenderBox]. The viewport establishes
+/// an [axisDirection], which orients the sliver's coordinate system, which is
+/// based on scroll offsets rather than Cartesian coordinates.
+///
+/// The viewport also listens to an [offset], which determines the
+/// [SliverConstraints.scrollOffset] input to the sliver layout protocol.
+///
+/// Subclasses typically override [performLayout] and call
+/// [layoutChildSequence], perhaps multiple times.
+///
+/// See also:
+///
+///  * [RenderSliver], which explains more about the Sliver protocol.
+///  * [RenderBox], which explains more about the Box protocol.
+///  * [RenderSliverToBoxAdapter], which allows a [RenderBox] object to be
+///    placed inside a [RenderSliver] (the opposite of this class).
+abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMixin<RenderSliver>>
+    extends RenderBox with ContainerRenderObjectMixin<RenderSliver, ParentDataClass>
+    implements RenderAbstractViewport {
+  /// Initializes fields for subclasses.
+  ///
+  /// The [cacheExtent], if null, defaults to [RenderAbstractViewport.defaultCacheExtent].
+  ///
+  /// The [cacheExtent] must be specified if [cacheExtentStyle] is not [CacheExtentStyle.pixel].
+  RenderViewportBase({
+    AxisDirection axisDirection = AxisDirection.down,
+    required AxisDirection crossAxisDirection,
+    required ViewportOffset offset,
+    double? cacheExtent,
+    CacheExtentStyle cacheExtentStyle = CacheExtentStyle.pixel,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : assert(axisDirection != null),
+       assert(crossAxisDirection != null),
+       assert(offset != null),
+       assert(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection)),
+       assert(cacheExtentStyle != null),
+       assert(cacheExtent != null || cacheExtentStyle == CacheExtentStyle.pixel),
+       assert(clipBehavior != null),
+       _axisDirection = axisDirection,
+       _crossAxisDirection = crossAxisDirection,
+       _offset = offset,
+       _cacheExtent = cacheExtent ?? RenderAbstractViewport.defaultCacheExtent,
+       _cacheExtentStyle = cacheExtentStyle,
+       _clipBehavior = clipBehavior;
+
+  /// Report the semantics of this node, for example for accessibility purposes.
+  ///
+  /// [RenderViewportBase] adds [RenderViewport.useTwoPaneSemantics] to the
+  /// provided [SemanticsConfiguration] to support children using
+  /// [RenderViewport.excludeFromScrolling].
+  ///
+  /// This method should be overridden by subclasses that have interesting
+  /// semantic information. Overriding subclasses should call
+  /// `super.describeSemanticsConfiguration(config)` to ensure
+  /// [RenderViewport.useTwoPaneSemantics] is still added to `config`.
+  ///
+  /// See also:
+  ///
+  /// * [RenderObject.describeSemanticsConfiguration], for important
+  ///   details about not mutating a [SemanticsConfiguration] out of context.
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+
+    config.addTagForChildren(RenderViewport.useTwoPaneSemantics);
+  }
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    childrenInPaintOrder
+        .where((RenderSliver sliver) => sliver.geometry!.visible || sliver.geometry!.cacheExtent > 0.0)
+        .forEach(visitor);
+  }
+
+  /// The direction in which the [SliverConstraints.scrollOffset] increases.
+  ///
+  /// For example, if the [axisDirection] is [AxisDirection.down], a scroll
+  /// offset of zero is at the top of the viewport and increases towards the
+  /// bottom of the viewport.
+  AxisDirection get axisDirection => _axisDirection;
+  AxisDirection _axisDirection;
+  set axisDirection(AxisDirection value) {
+    assert(value != null);
+    if (value == _axisDirection)
+      return;
+    _axisDirection = value;
+    markNeedsLayout();
+  }
+
+  /// The direction in which child should be laid out in the cross axis.
+  ///
+  /// For example, if the [axisDirection] is [AxisDirection.down], this property
+  /// is typically [AxisDirection.left] if the ambient [TextDirection] is
+  /// [TextDirection.rtl] and [AxisDirection.right] if the ambient
+  /// [TextDirection] is [TextDirection.ltr].
+  AxisDirection get crossAxisDirection => _crossAxisDirection;
+  AxisDirection _crossAxisDirection;
+  set crossAxisDirection(AxisDirection value) {
+    assert(value != null);
+    if (value == _crossAxisDirection)
+      return;
+    _crossAxisDirection = value;
+    markNeedsLayout();
+  }
+
+  /// The axis along which the viewport scrolls.
+  ///
+  /// For example, if the [axisDirection] is [AxisDirection.down], then the
+  /// [axis] is [Axis.vertical] and the viewport scrolls vertically.
+  Axis get axis => axisDirectionToAxis(axisDirection);
+
+  /// Which part of the content inside the viewport should be visible.
+  ///
+  /// The [ViewportOffset.pixels] value determines the scroll offset that the
+  /// viewport uses to select which part of its content to display. As the user
+  /// scrolls the viewport, this value changes, which changes the content that
+  /// is displayed.
+  ViewportOffset get offset => _offset;
+  ViewportOffset _offset;
+  set offset(ViewportOffset value) {
+    assert(value != null);
+    if (value == _offset)
+      return;
+    if (attached)
+      _offset.removeListener(markNeedsLayout);
+    _offset = value;
+    if (attached)
+      _offset.addListener(markNeedsLayout);
+    // We need to go through layout even if the new offset has the same pixels
+    // value as the old offset so that we will apply our viewport and content
+    // dimensions.
+    markNeedsLayout();
+  }
+
+  // TODO(ianh): cacheExtent/cacheExtentStyle should be a single
+  // object that specifies both the scalar value and the unit, not a
+  // pair of independent setters. Changing that would allow a more
+  // rational API and would let us make the getter non-nullable.
+
+  /// {@template flutter.rendering.RenderViewportBase.cacheExtent}
+  /// The viewport has an area before and after the visible area to cache items
+  /// that are about to become visible when the user scrolls.
+  ///
+  /// Items that fall in this cache area are laid out even though they are not
+  /// (yet) visible on screen. The [cacheExtent] describes how many pixels
+  /// the cache area extends before the leading edge and after the trailing edge
+  /// of the viewport.
+  ///
+  /// The total extent, which the viewport will try to cover with children, is
+  /// [cacheExtent] before the leading edge + extent of the main axis +
+  /// [cacheExtent] after the trailing edge.
+  ///
+  /// The cache area is also used to implement implicit accessibility scrolling
+  /// on iOS: When the accessibility focus moves from an item in the visible
+  /// viewport to an invisible item in the cache area, the framework will bring
+  /// that item into view with an (implicit) scroll action.
+  /// {@endtemplate}
+  ///
+  /// The getter can never return null, but the field is nullable
+  /// because the setter can be set to null to reset the value to
+  /// [RenderAbstractViewport.defaultCacheExtent] (in which case
+  /// [cacheExtentStyle] must be [CacheExtentStyle.pixel]).
+  ///
+  /// See also:
+  ///
+  ///  * [cacheExtentStyle], which controls the units of the [cacheExtent].
+  double? get cacheExtent => _cacheExtent;
+  double _cacheExtent;
+  set cacheExtent(double? value) {
+    value ??= RenderAbstractViewport.defaultCacheExtent;
+    assert(value != null);
+    if (value == _cacheExtent)
+      return;
+    _cacheExtent = value;
+    markNeedsLayout();
+  }
+
+  /// This value is set during layout based on the [CacheExtentStyle].
+  ///
+  /// When the style is [CacheExtentStyle.viewport], it is the main axis extent
+  /// of the viewport multiplied by the requested cache extent, which is still
+  /// expressed in pixels.
+  double? _calculatedCacheExtent;
+
+  /// {@template flutter.rendering.RenderViewportBase.cacheExtentStyle}
+  /// Controls how the [cacheExtent] is interpreted.
+  ///
+  /// If set to [CacheExtentStyle.pixel], the [cacheExtent] will be
+  /// treated as a logical pixels, and the default [cacheExtent] is
+  /// [RenderAbstractViewport.defaultCacheExtent].
+  ///
+  /// If set to [CacheExtentStyle.viewport], the [cacheExtent] will be
+  /// treated as a multiplier for the main axis extent of the
+  /// viewport. In this case there is no default [cacheExtent]; it
+  /// must be explicitly specified.
+  /// {@endtemplate}
+  ///
+  /// Changing the [cacheExtentStyle] without also changing the [cacheExtent]
+  /// is rarely the correct choice.
+  CacheExtentStyle get cacheExtentStyle => _cacheExtentStyle;
+  CacheExtentStyle _cacheExtentStyle;
+  set cacheExtentStyle(CacheExtentStyle value) {
+    assert(value != null);
+    if (value == _cacheExtentStyle) {
+      return;
+    }
+    _cacheExtentStyle = value;
+    markNeedsLayout();
+  }
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge], and must not be null.
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior = Clip.hardEdge;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    _offset.addListener(markNeedsLayout);
+  }
+
+  @override
+  void detach() {
+    _offset.removeListener(markNeedsLayout);
+    super.detach();
+  }
+
+  /// Throws an exception saying that the object does not support returning
+  /// intrinsic dimensions if, in checked mode, we are not in the
+  /// [RenderObject.debugCheckingIntrinsics] mode.
+  ///
+  /// This is used by [computeMinIntrinsicWidth] et al because viewports do not
+  /// generally support returning intrinsic dimensions. See the discussion at
+  /// [computeMinIntrinsicWidth].
+  @protected
+  bool debugThrowIfNotCheckingIntrinsics() {
+    assert(() {
+      if (!RenderObject.debugCheckingIntrinsics) {
+        assert(this is! RenderShrinkWrappingViewport); // it has its own message
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('$runtimeType does not support returning intrinsic dimensions.'),
+          ErrorDescription(
+            'Calculating the intrinsic dimensions would require instantiating every child of '
+            'the viewport, which defeats the point of viewports being lazy.',
+          ),
+          ErrorHint(
+            'If you are merely trying to shrink-wrap the viewport in the main axis direction, '
+            'consider a RenderShrinkWrappingViewport render object (ShrinkWrappingViewport widget), '
+            'which achieves that effect without implementing the intrinsic dimension API.'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    return true;
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    assert(debugThrowIfNotCheckingIntrinsics());
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    assert(debugThrowIfNotCheckingIntrinsics());
+    return 0.0;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    assert(debugThrowIfNotCheckingIntrinsics());
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    assert(debugThrowIfNotCheckingIntrinsics());
+    return 0.0;
+  }
+
+  @override
+  bool get isRepaintBoundary => true;
+
+  /// Determines the size and position of some of the children of the viewport.
+  ///
+  /// This function is the workhorse of `performLayout` implementations in
+  /// subclasses.
+  ///
+  /// Layout starts with `child`, proceeds according to the `advance` callback,
+  /// and stops once `advance` returns null.
+  ///
+  ///  * `scrollOffset` is the [SliverConstraints.scrollOffset] to pass the
+  ///    first child. The scroll offset is adjusted by
+  ///    [SliverGeometry.scrollExtent] for subsequent children.
+  ///  * `overlap` is the [SliverConstraints.overlap] to pass the first child.
+  ///    The overlay is adjusted by the [SliverGeometry.paintOrigin] and
+  ///    [SliverGeometry.paintExtent] for subsequent children.
+  ///  * `layoutOffset` is the layout offset at which to place the first child.
+  ///    The layout offset is updated by the [SliverGeometry.layoutExtent] for
+  ///    subsequent children.
+  ///  * `remainingPaintExtent` is [SliverConstraints.remainingPaintExtent] to
+  ///    pass the first child. The remaining paint extent is updated by the
+  ///    [SliverGeometry.layoutExtent] for subsequent children.
+  ///  * `mainAxisExtent` is the [SliverConstraints.viewportMainAxisExtent] to
+  ///    pass to each child.
+  ///  * `crossAxisExtent` is the [SliverConstraints.crossAxisExtent] to pass to
+  ///    each child.
+  ///  * `growthDirection` is the [SliverConstraints.growthDirection] to pass to
+  ///    each child.
+  ///
+  /// Returns the first non-zero [SliverGeometry.scrollOffsetCorrection]
+  /// encountered, if any. Otherwise returns 0.0. Typical callers will call this
+  /// function repeatedly until it returns 0.0.
+  @protected
+  double layoutChildSequence({
+    required RenderSliver? child,
+    required double scrollOffset,
+    required double overlap,
+    required double layoutOffset,
+    required double remainingPaintExtent,
+    required double mainAxisExtent,
+    required double crossAxisExtent,
+    required GrowthDirection growthDirection,
+    required RenderSliver? Function(RenderSliver child) advance,
+    required double remainingCacheExtent,
+    required double cacheOrigin,
+  }) {
+    assert(scrollOffset.isFinite);
+    assert(scrollOffset >= 0.0);
+    final double initialLayoutOffset = layoutOffset;
+    final ScrollDirection adjustedUserScrollDirection =
+        applyGrowthDirectionToScrollDirection(offset.userScrollDirection, growthDirection);
+    assert(adjustedUserScrollDirection != null);
+    double maxPaintOffset = layoutOffset + overlap;
+    double precedingScrollExtent = 0.0;
+
+    while (child != null) {
+      final double sliverScrollOffset = scrollOffset <= 0.0 ? 0.0 : scrollOffset;
+      // If the scrollOffset is too small we adjust the paddedOrigin because it
+      // doesn't make sense to ask a sliver for content before its scroll
+      // offset.
+      final double correctedCacheOrigin = math.max(cacheOrigin, -sliverScrollOffset);
+      final double cacheExtentCorrection = cacheOrigin - correctedCacheOrigin;
+
+      assert(sliverScrollOffset >= correctedCacheOrigin.abs());
+      assert(correctedCacheOrigin <= 0.0);
+      assert(sliverScrollOffset >= 0.0);
+      assert(cacheExtentCorrection <= 0.0);
+
+      child.layout(SliverConstraints(
+        axisDirection: axisDirection,
+        growthDirection: growthDirection,
+        userScrollDirection: adjustedUserScrollDirection,
+        scrollOffset: sliverScrollOffset,
+        precedingScrollExtent: precedingScrollExtent,
+        overlap: maxPaintOffset - layoutOffset,
+        remainingPaintExtent: math.max(0.0, remainingPaintExtent - layoutOffset + initialLayoutOffset),
+        crossAxisExtent: crossAxisExtent,
+        crossAxisDirection: crossAxisDirection,
+        viewportMainAxisExtent: mainAxisExtent,
+        remainingCacheExtent: math.max(0.0, remainingCacheExtent + cacheExtentCorrection),
+        cacheOrigin: correctedCacheOrigin,
+      ), parentUsesSize: true);
+
+      final SliverGeometry childLayoutGeometry = child.geometry!;
+      assert(childLayoutGeometry.debugAssertIsValid());
+
+      // If there is a correction to apply, we'll have to start over.
+      if (childLayoutGeometry.scrollOffsetCorrection != null)
+        return childLayoutGeometry.scrollOffsetCorrection!;
+
+      // We use the child's paint origin in our coordinate system as the
+      // layoutOffset we store in the child's parent data.
+      final double effectiveLayoutOffset = layoutOffset + childLayoutGeometry.paintOrigin;
+
+      // `effectiveLayoutOffset` becomes meaningless once we moved past the trailing edge
+      // because `childLayoutGeometry.layoutExtent` is zero. Using the still increasing
+      // 'scrollOffset` to roughly position these invisible slivers in the right order.
+      if (childLayoutGeometry.visible || scrollOffset > 0) {
+        updateChildLayoutOffset(child, effectiveLayoutOffset, growthDirection);
+      } else {
+        updateChildLayoutOffset(child, -scrollOffset + initialLayoutOffset, growthDirection);
+      }
+
+      maxPaintOffset = math.max(effectiveLayoutOffset + childLayoutGeometry.paintExtent, maxPaintOffset);
+      scrollOffset -= childLayoutGeometry.scrollExtent;
+      precedingScrollExtent += childLayoutGeometry.scrollExtent;
+      layoutOffset += childLayoutGeometry.layoutExtent;
+      if (childLayoutGeometry.cacheExtent != 0.0) {
+        remainingCacheExtent -= childLayoutGeometry.cacheExtent - cacheExtentCorrection;
+        cacheOrigin = math.min(correctedCacheOrigin + childLayoutGeometry.cacheExtent, 0.0);
+      }
+
+      updateOutOfBandData(growthDirection, childLayoutGeometry);
+
+      // move on to the next child
+      child = advance(child);
+    }
+
+    // we made it without a correction, whee!
+    return 0.0;
+  }
+
+  @override
+  Rect describeApproximatePaintClip(RenderSliver child) {
+    final Rect viewportClip = Offset.zero & size;
+    // The child's viewportMainAxisExtent can be infinite when a
+    // RenderShrinkWrappingViewport is given infinite constraints, such as when
+    // it is the child of a Row or Column (depending on orientation).
+    //
+    // For example, a shrink wrapping render sliver may have infinite
+    // constraints along the viewport's main axis but may also have bouncing
+    // scroll physics, which will allow for some scrolling effect to occur.
+    // We should just use the viewportClip - the start of the overlap is at
+    // double.infinity and so it is effectively meaningless.
+    if (child.constraints.overlap == 0 || !child.constraints.viewportMainAxisExtent.isFinite) {
+      return viewportClip;
+    }
+
+    // Adjust the clip rect for this sliver by the overlap from the previous sliver.
+    double left = viewportClip.left;
+    double right = viewportClip.right;
+    double top = viewportClip.top;
+    double bottom = viewportClip.bottom;
+    final double startOfOverlap = child.constraints.viewportMainAxisExtent - child.constraints.remainingPaintExtent;
+    final double overlapCorrection = startOfOverlap + child.constraints.overlap;
+    switch (applyGrowthDirectionToAxisDirection(axisDirection, child.constraints.growthDirection)) {
+      case AxisDirection.down:
+        top += overlapCorrection;
+        break;
+      case AxisDirection.up:
+        bottom -= overlapCorrection;
+        break;
+      case AxisDirection.right:
+        left += overlapCorrection;
+        break;
+      case AxisDirection.left:
+        right -= overlapCorrection;
+        break;
+    }
+    return Rect.fromLTRB(left, top, right, bottom);
+  }
+
+  @override
+  Rect describeSemanticsClip(RenderSliver? child) {
+    assert(axis != null);
+
+    if (_calculatedCacheExtent == null) {
+      return semanticBounds;
+    }
+
+    switch (axis) {
+      case Axis.vertical:
+        return Rect.fromLTRB(
+          semanticBounds.left,
+          semanticBounds.top - _calculatedCacheExtent!,
+          semanticBounds.right,
+          semanticBounds.bottom + _calculatedCacheExtent!,
+        );
+      case Axis.horizontal:
+        return Rect.fromLTRB(
+          semanticBounds.left - _calculatedCacheExtent!,
+          semanticBounds.top,
+          semanticBounds.right + _calculatedCacheExtent!,
+          semanticBounds.bottom,
+        );
+    }
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (firstChild == null)
+      return;
+    if (hasVisualOverflow && clipBehavior != Clip.none) {
+      _clipRectLayer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents,
+          clipBehavior: clipBehavior, oldLayer: _clipRectLayer);
+    } else {
+      _clipRectLayer = null;
+      _paintContents(context, offset);
+    }
+  }
+
+  ClipRectLayer? _clipRectLayer;
+
+  void _paintContents(PaintingContext context, Offset offset) {
+    for (final RenderSliver child in childrenInPaintOrder) {
+      if (child.geometry!.visible)
+        context.paintChild(child, offset + paintOffsetOf(child));
+    }
+  }
+
+  @override
+  void debugPaintSize(PaintingContext context, Offset offset) {
+    assert(() {
+      super.debugPaintSize(context, offset);
+      final Paint paint = Paint()
+        ..style = PaintingStyle.stroke
+        ..strokeWidth = 1.0
+        ..color = const Color(0xFF00FF00);
+      final Canvas canvas = context.canvas;
+      RenderSliver? child = firstChild;
+      while (child != null) {
+        final Size size;
+        switch (axis) {
+          case Axis.vertical:
+            size = Size(child.constraints.crossAxisExtent, child.geometry!.layoutExtent);
+            break;
+          case Axis.horizontal:
+            size = Size(child.geometry!.layoutExtent, child.constraints.crossAxisExtent);
+            break;
+        }
+        assert(size != null);
+        canvas.drawRect(((offset + paintOffsetOf(child)) & size).deflate(0.5), paint);
+        child = childAfter(child);
+      }
+      return true;
+    }());
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    double mainAxisPosition, crossAxisPosition;
+    switch (axis) {
+      case Axis.vertical:
+        mainAxisPosition = position.dy;
+        crossAxisPosition = position.dx;
+        break;
+      case Axis.horizontal:
+        mainAxisPosition = position.dx;
+        crossAxisPosition = position.dy;
+        break;
+    }
+    assert(mainAxisPosition != null);
+    assert(crossAxisPosition != null);
+    final SliverHitTestResult sliverResult = SliverHitTestResult.wrap(result);
+    for (final RenderSliver child in childrenInHitTestOrder) {
+      if (!child.geometry!.visible) {
+        continue;
+      }
+      final Matrix4 transform = Matrix4.identity();
+      applyPaintTransform(child, transform); // must be invertible
+      final bool isHit = result.addWithOutOfBandPosition(
+        paintTransform: transform,
+        hitTest: (BoxHitTestResult result) {
+          return child.hitTest(
+            sliverResult,
+            mainAxisPosition: computeChildMainAxisPosition(child, mainAxisPosition),
+            crossAxisPosition: crossAxisPosition,
+          );
+        },
+      );
+      if (isHit) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @override
+  RevealedOffset getOffsetToReveal(RenderObject target, double alignment, { Rect? rect }) {
+    // Steps to convert `rect` (from a RenderBox coordinate system) to its
+    // scroll offset within this viewport (not in the exact order):
+    //
+    // 1. Pick the outmost RenderBox (between which, and the viewport, there is
+    // nothing but RenderSlivers) as an intermediate reference frame
+    // (the `pivot`), convert `rect` to that coordinate space.
+    //
+    // 2. Convert `rect` from the `pivot` coordinate space to its sliver
+    // parent's sliver coordinate system (i.e., to a scroll offset), based on
+    // the axis direction and growth direction of the parent.
+    //
+    // 3. Convert the scroll offset to its sliver parent's coordinate space
+    // using `childScrollOffset`, until we reach the viewport.
+    //
+    // 4. Make the final conversion from the outmost sliver to the viewport
+    // using `scrollOffsetOf`.
+
+    double leadingScrollOffset = 0.0;
+    // Starting at `target` and walking towards the root:
+    //  - `child` will be the last object before we reach this viewport, and
+    //  - `pivot` will be the last RenderBox before we reach this viewport.
+    RenderObject child = target;
+    RenderBox? pivot;
+    bool onlySlivers = target is RenderSliver; // ... between viewport and `target` (`target` included).
+    while (child.parent != this) {
+      final RenderObject parent = child.parent! as RenderObject;
+      assert(parent != null, '$target must be a descendant of $this');
+      if (child is RenderBox) {
+        pivot = child;
+      }
+      if (parent is RenderSliver) {
+        leadingScrollOffset += parent.childScrollOffset(child)!;
+      } else {
+        onlySlivers = false;
+        leadingScrollOffset = 0.0;
+      }
+      child = parent;
+    }
+
+    // `rect` in the new intermediate coordinate system.
+    final Rect rectLocal;
+    // Our new reference frame render object's main axis extent.
+    final double pivotExtent;
+    final GrowthDirection growthDirection;
+
+    // `leadingScrollOffset` is currently the scrollOffset of our new reference
+    // frame (`pivot` or `target`), within `child`.
+    if (pivot != null) {
+      assert(pivot.parent != null);
+      assert(pivot.parent != this);
+      assert(pivot != this);
+      assert(pivot.parent is RenderSliver);  // TODO(abarth): Support other kinds of render objects besides slivers.
+      final RenderSliver pivotParent = pivot.parent! as RenderSliver;
+      growthDirection = pivotParent.constraints.growthDirection;
+      switch (axis) {
+        case Axis.horizontal:
+          pivotExtent = pivot.size.width;
+          break;
+        case Axis.vertical:
+          pivotExtent = pivot.size.height;
+          break;
+      }
+      rect ??= target.paintBounds;
+      rectLocal = MatrixUtils.transformRect(target.getTransformTo(pivot), rect);
+    } else if (onlySlivers) {
+      // `pivot` does not exist. We'll have to make up one from `target`, the
+      // innermost sliver.
+      final RenderSliver targetSliver = target as RenderSliver;
+      growthDirection = targetSliver.constraints.growthDirection;
+      // TODO(LongCatIsLooong): make sure this works if `targetSliver` is a
+      // persistent header, when #56413 relands.
+      pivotExtent = targetSliver.geometry!.scrollExtent;
+      if (rect == null) {
+        switch (axis) {
+          case Axis.horizontal:
+            rect = Rect.fromLTWH(
+              0, 0,
+              targetSliver.geometry!.scrollExtent,
+              targetSliver.constraints.crossAxisExtent,
+            );
+            break;
+          case Axis.vertical:
+            rect = Rect.fromLTWH(
+              0, 0,
+              targetSliver.constraints.crossAxisExtent,
+              targetSliver.geometry!.scrollExtent,
+            );
+            break;
+        }
+      }
+      rectLocal = rect;
+    } else {
+      assert(rect != null);
+      return RevealedOffset(offset: offset.pixels, rect: rect!);
+    }
+
+    assert(pivotExtent != null);
+    assert(rect != null);
+    assert(rectLocal != null);
+    assert(growthDirection != null);
+    assert(child.parent == this);
+    assert(child is RenderSliver);
+    final RenderSliver sliver = child as RenderSliver;
+
+    final double targetMainAxisExtent;
+    // The scroll offset of `rect` within `child`.
+    switch (applyGrowthDirectionToAxisDirection(axisDirection, growthDirection)) {
+      case AxisDirection.up:
+        leadingScrollOffset += pivotExtent - rectLocal.bottom;
+        targetMainAxisExtent = rectLocal.height;
+        break;
+      case AxisDirection.right:
+        leadingScrollOffset += rectLocal.left;
+        targetMainAxisExtent = rectLocal.width;
+        break;
+      case AxisDirection.down:
+        leadingScrollOffset += rectLocal.top;
+        targetMainAxisExtent = rectLocal.height;
+        break;
+      case AxisDirection.left:
+        leadingScrollOffset += pivotExtent - rectLocal.right;
+        targetMainAxisExtent = rectLocal.width;
+        break;
+    }
+
+    // So far leadingScrollOffset is the scroll offset of `rect` in the `child`
+    // sliver's sliver coordinate system. The sign of this value indicates
+    // whether the `rect` protrudes the leading edge of the `child` sliver. When
+    // this value is non-negative and `child`'s `maxScrollObstructionExtent` is
+    // greater than 0, we assume `rect` can't be obstructed by the leading edge
+    // of the viewport (i.e. its pinned to the leading edge).
+    final bool isPinned = sliver.geometry!.maxScrollObstructionExtent > 0 && leadingScrollOffset >= 0;
+
+    // The scroll offset in the viewport to `rect`.
+    leadingScrollOffset = scrollOffsetOf(sliver, leadingScrollOffset);
+
+    // This step assumes the viewport's layout is up-to-date, i.e., if
+    // offset.pixels is changed after the last performLayout, the new scroll
+    // position will not be accounted for.
+    final Matrix4 transform = target.getTransformTo(this);
+    Rect targetRect = MatrixUtils.transformRect(transform, rect);
+    final double extentOfPinnedSlivers = maxScrollObstructionExtentBefore(sliver);
+
+    switch (sliver.constraints.growthDirection) {
+      case GrowthDirection.forward:
+        if (isPinned && alignment <= 0)
+          return RevealedOffset(offset: double.infinity, rect: targetRect);
+        leadingScrollOffset -= extentOfPinnedSlivers;
+        break;
+      case GrowthDirection.reverse:
+        if (isPinned && alignment >= 1)
+          return RevealedOffset(offset: double.negativeInfinity, rect: targetRect);
+        // If child's growth direction is reverse, when viewport.offset is
+        // `leadingScrollOffset`, it is positioned just outside of the leading
+        // edge of the viewport.
+        switch (axis) {
+          case Axis.vertical:
+            leadingScrollOffset -= targetRect.height;
+            break;
+          case Axis.horizontal:
+            leadingScrollOffset -= targetRect.width;
+            break;
+        }
+        break;
+    }
+
+    final double mainAxisExtent;
+    switch (axis) {
+      case Axis.horizontal:
+        mainAxisExtent = size.width - extentOfPinnedSlivers;
+        break;
+      case Axis.vertical:
+        mainAxisExtent = size.height - extentOfPinnedSlivers;
+        break;
+    }
+
+    final double targetOffset = leadingScrollOffset - (mainAxisExtent - targetMainAxisExtent) * alignment;
+    final double offsetDifference = offset.pixels - targetOffset;
+
+    switch (axisDirection) {
+      case AxisDirection.down:
+        targetRect = targetRect.translate(0.0, offsetDifference);
+        break;
+      case AxisDirection.right:
+        targetRect = targetRect.translate(offsetDifference, 0.0);
+        break;
+      case AxisDirection.up:
+        targetRect = targetRect.translate(0.0, -offsetDifference);
+        break;
+      case AxisDirection.left:
+        targetRect = targetRect.translate(-offsetDifference, 0.0);
+        break;
+    }
+
+    return RevealedOffset(offset: targetOffset, rect: targetRect);
+  }
+
+  /// The offset at which the given `child` should be painted.
+  ///
+  /// The returned offset is from the top left corner of the inside of the
+  /// viewport to the top left corner of the paint coordinate system of the
+  /// `child`.
+  ///
+  /// See also:
+  ///
+  ///  * [paintOffsetOf], which uses the layout offset and growth direction
+  ///    computed for the child during layout.
+  @protected
+  Offset computeAbsolutePaintOffset(RenderSliver child, double layoutOffset, GrowthDirection growthDirection) {
+    assert(hasSize); // this is only usable once we have a size
+    assert(axisDirection != null);
+    assert(growthDirection != null);
+    assert(child != null);
+    assert(child.geometry != null);
+    switch (applyGrowthDirectionToAxisDirection(axisDirection, growthDirection)) {
+      case AxisDirection.up:
+        return Offset(0.0, size.height - (layoutOffset + child.geometry!.paintExtent));
+      case AxisDirection.right:
+        return Offset(layoutOffset, 0.0);
+      case AxisDirection.down:
+        return Offset(0.0, layoutOffset);
+      case AxisDirection.left:
+        return Offset(size.width - (layoutOffset + child.geometry!.paintExtent), 0.0);
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<AxisDirection>('axisDirection', axisDirection));
+    properties.add(EnumProperty<AxisDirection>('crossAxisDirection', crossAxisDirection));
+    properties.add(DiagnosticsProperty<ViewportOffset>('offset', offset));
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    final List<DiagnosticsNode> children = <DiagnosticsNode>[];
+    RenderSliver? child = firstChild;
+    if (child == null)
+      return children;
+
+    int count = indexOfFirstChild;
+    while (true) {
+      children.add(child!.toDiagnosticsNode(name: labelForChild(count)));
+      if (child == lastChild)
+        break;
+      count += 1;
+      child = childAfter(child);
+    }
+    return children;
+  }
+
+  // API TO BE IMPLEMENTED BY SUBCLASSES
+
+  // setupParentData
+
+  // performLayout (and optionally sizedByParent and performResize)
+
+  /// Whether the contents of this viewport would paint outside the bounds of
+  /// the viewport if [paint] did not clip.
+  ///
+  /// This property enables an optimization whereby [paint] can skip apply a
+  /// clip of the contents of the viewport are known to paint entirely within
+  /// the bounds of the viewport.
+  @protected
+  bool get hasVisualOverflow;
+
+  /// Called during [layoutChildSequence] for each child.
+  ///
+  /// Typically used by subclasses to update any out-of-band data, such as the
+  /// max scroll extent, for each child.
+  @protected
+  void updateOutOfBandData(GrowthDirection growthDirection, SliverGeometry childLayoutGeometry);
+
+  /// Called during [layoutChildSequence] to store the layout offset for the
+  /// given child.
+  ///
+  /// Different subclasses using different representations for their children's
+  /// layout offset (e.g., logical or physical coordinates). This function lets
+  /// subclasses transform the child's layout offset before storing it in the
+  /// child's parent data.
+  @protected
+  void updateChildLayoutOffset(RenderSliver child, double layoutOffset, GrowthDirection growthDirection);
+
+  /// The offset at which the given `child` should be painted.
+  ///
+  /// The returned offset is from the top left corner of the inside of the
+  /// viewport to the top left corner of the paint coordinate system of the
+  /// `child`.
+  ///
+  /// See also:
+  ///
+  ///  * [computeAbsolutePaintOffset], which computes the paint offset from an
+  ///    explicit layout offset and growth direction instead of using the values
+  ///    computed for the child during layout.
+  @protected
+  Offset paintOffsetOf(RenderSliver child);
+
+  /// Returns the scroll offset within the viewport for the given
+  /// `scrollOffsetWithinChild` within the given `child`.
+  ///
+  /// The returned value is an estimate that assumes the slivers within the
+  /// viewport do not change the layout extent in response to changes in their
+  /// scroll offset.
+  @protected
+  double scrollOffsetOf(RenderSliver child, double scrollOffsetWithinChild);
+
+  /// Returns the total scroll obstruction extent of all slivers in the viewport
+  /// before [child].
+  ///
+  /// This is the extent by which the actual area in which content can scroll
+  /// is reduced. For example, an app bar that is pinned at the top will reduce
+  /// the area in which content can actually scroll by the height of the app bar.
+  @protected
+  double maxScrollObstructionExtentBefore(RenderSliver child);
+
+  /// Converts the `parentMainAxisPosition` into the child's coordinate system.
+  ///
+  /// The `parentMainAxisPosition` is a distance from the top edge (for vertical
+  /// viewports) or left edge (for horizontal viewports) of the viewport bounds.
+  /// This describes a line, perpendicular to the viewport's main axis, heretofore
+  /// known as the target line.
+  ///
+  /// The child's coordinate system's origin in the main axis is at the leading
+  /// edge of the given child, as given by the child's
+  /// [SliverConstraints.axisDirection] and [SliverConstraints.growthDirection].
+  ///
+  /// This method returns the distance from the leading edge of the given child to
+  /// the target line described above.
+  ///
+  /// (The `parentMainAxisPosition` is not from the leading edge of the
+  /// viewport, it's always the top or left edge.)
+  @protected
+  double computeChildMainAxisPosition(RenderSliver child, double parentMainAxisPosition);
+
+  /// The index of the first child of the viewport relative to the center child.
+  ///
+  /// For example, the center child has index zero and the first child in the
+  /// reverse growth direction has index -1.
+  @protected
+  int get indexOfFirstChild;
+
+  /// A short string to identify the child with the given index.
+  ///
+  /// Used by [debugDescribeChildren] to label the children.
+  @protected
+  String labelForChild(int index);
+
+  /// Provides an iterable that walks the children of the viewport, in the order
+  /// that they should be painted.
+  ///
+  /// This should be the reverse order of [childrenInHitTestOrder].
+  @protected
+  Iterable<RenderSliver> get childrenInPaintOrder;
+
+  /// Provides an iterable that walks the children of the viewport, in the order
+  /// that hit-testing should use.
+  ///
+  /// This should be the reverse order of [childrenInPaintOrder].
+  @protected
+  Iterable<RenderSliver> get childrenInHitTestOrder;
+
+  @override
+  void showOnScreen({
+    RenderObject? descendant,
+    Rect? rect,
+    Duration duration = Duration.zero,
+    Curve curve = Curves.ease,
+  }) {
+    if (!offset.allowImplicitScrolling) {
+      return super.showOnScreen(
+        descendant: descendant,
+        rect: rect,
+        duration: duration,
+        curve: curve,
+      );
+    }
+
+    final Rect? newRect = RenderViewportBase.showInViewport(
+      descendant: descendant,
+      viewport: this,
+      offset: offset,
+      rect: rect,
+      duration: duration,
+      curve: curve,
+    );
+    super.showOnScreen(
+      rect: newRect,
+      duration: duration,
+      curve: curve,
+    );
+  }
+
+  /// Make (a portion of) the given `descendant` of the given `viewport` fully
+  /// visible in the `viewport` by manipulating the provided [ViewportOffset]
+  /// `offset`.
+  ///
+  /// The optional `rect` parameter describes which area of the `descendant`
+  /// should be shown in the viewport. If `rect` is null, the entire
+  /// `descendant` will be revealed. The `rect` parameter is interpreted
+  /// relative to the coordinate system of `descendant`.
+  ///
+  /// The returned [Rect] describes the new location of `descendant` or `rect`
+  /// in the viewport after it has been revealed. See [RevealedOffset.rect]
+  /// for a full definition of this [Rect].
+  ///
+  /// The parameters `viewport` and `offset` are required and cannot be null.
+  /// If `descendant` is null, this is a no-op and `rect` is returned.
+  ///
+  /// If both `descendant` and `rect` are null, null is returned because there is
+  /// nothing to be shown in the viewport.
+  ///
+  /// The `duration` parameter can be set to a non-zero value to animate the
+  /// target object into the viewport with an animation defined by `curve`.
+  ///
+  /// See also:
+  ///
+  /// * [RenderObject.showOnScreen], overridden by [RenderViewportBase] and the
+  ///   renderer for [SingleChildScrollView] to delegate to this method.
+  static Rect? showInViewport({
+    RenderObject? descendant,
+    Rect? rect,
+    required RenderAbstractViewport viewport,
+    required ViewportOffset offset,
+    Duration duration = Duration.zero,
+    Curve curve = Curves.ease,
+  }) {
+    assert(viewport != null);
+    assert(offset != null);
+    if (descendant == null) {
+      return rect;
+    }
+    final RevealedOffset leadingEdgeOffset = viewport.getOffsetToReveal(descendant, 0.0, rect: rect);
+    final RevealedOffset trailingEdgeOffset = viewport.getOffsetToReveal(descendant, 1.0, rect: rect);
+    final double currentOffset = offset.pixels;
+
+    //           scrollOffset
+    //                       0 +---------+
+    //                         |         |
+    //                       _ |         |
+    //    viewport position |  |         |
+    // with `descendant` at |  |         | _
+    //        trailing edge |_ | xxxxxxx |  | viewport position
+    //                         |         |  | with `descendant` at
+    //                         |         | _| leading edge
+    //                         |         |
+    //                     800 +---------+
+    //
+    // `trailingEdgeOffset`: Distance from scrollOffset 0 to the start of the
+    //                       viewport on the left in image above.
+    // `leadingEdgeOffset`: Distance from scrollOffset 0 to the start of the
+    //                      viewport on the right in image above.
+    //
+    // The viewport position on the left is achieved by setting `offset.pixels`
+    // to `trailingEdgeOffset`, the one on the right by setting it to
+    // `leadingEdgeOffset`.
+
+    final RevealedOffset targetOffset;
+    if (leadingEdgeOffset.offset < trailingEdgeOffset.offset) {
+      // `descendant` is too big to be visible on screen in its entirety. Let's
+      // align it with the edge that requires the least amount of scrolling.
+      final double leadingEdgeDiff = (offset.pixels - leadingEdgeOffset.offset).abs();
+      final double trailingEdgeDiff = (offset.pixels - trailingEdgeOffset.offset).abs();
+      targetOffset = leadingEdgeDiff < trailingEdgeDiff ? leadingEdgeOffset : trailingEdgeOffset;
+    } else if (currentOffset > leadingEdgeOffset.offset) {
+      // `descendant` currently starts above the leading edge and can be shown
+      // fully on screen by scrolling down (which means: moving viewport up).
+      targetOffset = leadingEdgeOffset;
+    } else if (currentOffset < trailingEdgeOffset.offset) {
+      // `descendant currently ends below the trailing edge and can be shown
+      // fully on screen by scrolling up (which means: moving viewport down)
+      targetOffset = trailingEdgeOffset;
+    } else {
+      // `descendant` is between leading and trailing edge and hence already
+      //  fully shown on screen. No action necessary.
+      final Matrix4 transform = descendant.getTransformTo(viewport.parent! as RenderObject);
+      return MatrixUtils.transformRect(transform, rect ?? descendant.paintBounds);
+    }
+
+    assert(targetOffset != null);
+
+    offset.moveTo(targetOffset.offset, duration: duration, curve: curve);
+    return targetOffset.rect;
+  }
+}
+
+/// A render object that is bigger on the inside.
+///
+/// [RenderViewport] is the visual workhorse of the scrolling machinery. It
+/// displays a subset of its children according to its own dimensions and the
+/// given [offset]. As the offset varies, different children are visible through
+/// the viewport.
+///
+/// [RenderViewport] hosts a bidirectional list of slivers, anchored on a
+/// [center] sliver, which is placed at the zero scroll offset. The center
+/// widget is displayed in the viewport according to the [anchor] property.
+///
+/// Slivers that are earlier in the child list than [center] are displayed in
+/// reverse order in the reverse [axisDirection] starting from the [center]. For
+/// example, if the [axisDirection] is [AxisDirection.down], the first sliver
+/// before [center] is placed above the [center]. The slivers that are later in
+/// the child list than [center] are placed in order in the [axisDirection]. For
+/// example, in the preceding scenario, the first sliver after [center] is
+/// placed below the [center].
+///
+/// [RenderViewport] cannot contain [RenderBox] children directly. Instead, use
+/// a [RenderSliverList], [RenderSliverFixedExtentList], [RenderSliverGrid], or
+/// a [RenderSliverToBoxAdapter], for example.
+///
+/// See also:
+///
+///  * [RenderSliver], which explains more about the Sliver protocol.
+///  * [RenderBox], which explains more about the Box protocol.
+///  * [RenderSliverToBoxAdapter], which allows a [RenderBox] object to be
+///    placed inside a [RenderSliver] (the opposite of this class).
+///  * [RenderShrinkWrappingViewport], a variant of [RenderViewport] that
+///    shrink-wraps its contents along the main axis.
+class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentData> {
+  /// Creates a viewport for [RenderSliver] objects.
+  ///
+  /// If the [center] is not specified, then the first child in the `children`
+  /// list, if any, is used.
+  ///
+  /// The [offset] must be specified. For testing purposes, consider passing a
+  /// [new ViewportOffset.zero] or [new ViewportOffset.fixed].
+  RenderViewport({
+    AxisDirection axisDirection = AxisDirection.down,
+    required AxisDirection crossAxisDirection,
+    required ViewportOffset offset,
+    double anchor = 0.0,
+    List<RenderSliver>? children,
+    RenderSliver? center,
+    double? cacheExtent,
+    CacheExtentStyle cacheExtentStyle = CacheExtentStyle.pixel,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : assert(anchor != null),
+       assert(anchor >= 0.0 && anchor <= 1.0),
+       assert(cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null),
+       assert(clipBehavior != null),
+       _anchor = anchor,
+       _center = center,
+       super(
+         axisDirection: axisDirection,
+         crossAxisDirection: crossAxisDirection,
+         offset: offset,
+         cacheExtent: cacheExtent,
+         cacheExtentStyle: cacheExtentStyle,
+         clipBehavior: clipBehavior,
+       ) {
+    addAll(children);
+    if (center == null && firstChild != null)
+      _center = firstChild;
+  }
+
+  /// If a [RenderAbstractViewport] overrides
+  /// [RenderObject.describeSemanticsConfiguration] to add the [SemanticsTag]
+  /// [useTwoPaneSemantics] to its [SemanticsConfiguration], two semantics nodes
+  /// will be used to represent the viewport with its associated scrolling
+  /// actions in the semantics tree.
+  ///
+  /// Two semantics nodes (an inner and an outer node) are necessary to exclude
+  /// certain child nodes (via the [excludeFromScrolling] tag) from the
+  /// scrollable area for semantic purposes: The [SemanticsNode]s of children
+  /// that should be excluded from scrolling will be attached to the outer node.
+  /// The semantic scrolling actions and the [SemanticsNode]s of scrollable
+  /// children will be attached to the inner node, which itself is a child of
+  /// the outer node.
+  ///
+  /// See also:
+  ///
+  /// * [RenderViewportBase.describeSemanticsConfiguration], which adds this
+  ///   tag to its [SemanticsConfiguration].
+  static const SemanticsTag useTwoPaneSemantics = SemanticsTag('RenderViewport.twoPane');
+
+  /// When a top-level [SemanticsNode] below a [RenderAbstractViewport] is
+  /// tagged with [excludeFromScrolling] it will not be part of the scrolling
+  /// area for semantic purposes.
+  ///
+  /// This behavior is only active if the [RenderAbstractViewport]
+  /// tagged its [SemanticsConfiguration] with [useTwoPaneSemantics].
+  /// Otherwise, the [excludeFromScrolling] tag is ignored.
+  ///
+  /// As an example, a [RenderSliver] that stays on the screen within a
+  /// [Scrollable] even though the user has scrolled past it (e.g. a pinned app
+  /// bar) can tag its [SemanticsNode] with [excludeFromScrolling] to indicate
+  /// that it should no longer be considered for semantic actions related to
+  /// scrolling.
+  static const SemanticsTag excludeFromScrolling = SemanticsTag('RenderViewport.excludeFromScrolling');
+
+  @override
+  void setupParentData(RenderObject child) {
+    if (child.parentData is! SliverPhysicalContainerParentData)
+      child.parentData = SliverPhysicalContainerParentData();
+  }
+
+  /// The relative position of the zero scroll offset.
+  ///
+  /// For example, if [anchor] is 0.5 and the [axisDirection] is
+  /// [AxisDirection.down] or [AxisDirection.up], then the zero scroll offset is
+  /// vertically centered within the viewport. If the [anchor] is 1.0, and the
+  /// [axisDirection] is [AxisDirection.right], then the zero scroll offset is
+  /// on the left edge of the viewport.
+  double get anchor => _anchor;
+  double _anchor;
+  set anchor(double value) {
+    assert(value != null);
+    assert(value >= 0.0 && value <= 1.0);
+    if (value == _anchor)
+      return;
+    _anchor = value;
+    markNeedsLayout();
+  }
+
+  /// The first child in the [GrowthDirection.forward] growth direction.
+  ///
+  /// This child that will be at the position defined by [anchor] when the
+  /// [ViewportOffset.pixels] of [offset] is `0`.
+  ///
+  /// Children after [center] will be placed in the [axisDirection] relative to
+  /// the [center]. Children before [center] will be placed in the opposite of
+  /// the [axisDirection] relative to the [center].
+  ///
+  /// The [center] must be a child of the viewport.
+  RenderSliver? get center => _center;
+  RenderSliver? _center;
+  set center(RenderSliver? value) {
+    if (value == _center)
+      return;
+    _center = value;
+    markNeedsLayout();
+  }
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    assert(() {
+      if (!constraints.hasBoundedHeight || !constraints.hasBoundedWidth) {
+        switch (axis) {
+          case Axis.vertical:
+            if (!constraints.hasBoundedHeight) {
+              throw FlutterError.fromParts(<DiagnosticsNode>[
+                ErrorSummary('Vertical viewport was given unbounded height.'),
+                ErrorDescription(
+                  'Viewports expand in the scrolling direction to fill their container. '
+                  'In this case, a vertical viewport was given an unlimited amount of '
+                  'vertical space in which to expand. This situation typically happens '
+                  'when a scrollable widget is nested inside another scrollable widget.'
+                ),
+                ErrorHint(
+                  'If this widget is always nested in a scrollable widget there '
+                  'is no need to use a viewport because there will always be enough '
+                  'vertical space for the children. In this case, consider using a '
+                  'Column instead. Otherwise, consider using the "shrinkWrap" property '
+                  '(or a ShrinkWrappingViewport) to size the height of the viewport '
+                  'to the sum of the heights of its children.'
+                )
+              ]);
+            }
+            if (!constraints.hasBoundedWidth) {
+              throw FlutterError(
+                'Vertical viewport was given unbounded width.\n'
+                'Viewports expand in the cross axis to fill their container and '
+                'constrain their children to match their extent in the cross axis. '
+                'In this case, a vertical viewport was given an unlimited amount of '
+                'horizontal space in which to expand.'
+              );
+            }
+            break;
+          case Axis.horizontal:
+            if (!constraints.hasBoundedWidth) {
+              throw FlutterError.fromParts(<DiagnosticsNode>[
+                ErrorSummary('Horizontal viewport was given unbounded width.'),
+                ErrorDescription(
+                  'Viewports expand in the scrolling direction to fill their container. '
+                  'In this case, a horizontal viewport was given an unlimited amount of '
+                  'horizontal space in which to expand. This situation typically happens '
+                  'when a scrollable widget is nested inside another scrollable widget.'
+                ),
+                ErrorHint(
+                  'If this widget is always nested in a scrollable widget there '
+                  'is no need to use a viewport because there will always be enough '
+                  'horizontal space for the children. In this case, consider using a '
+                  'Row instead. Otherwise, consider using the "shrinkWrap" property '
+                  '(or a ShrinkWrappingViewport) to size the width of the viewport '
+                  'to the sum of the widths of its children.'
+                )
+              ]);
+            }
+            if (!constraints.hasBoundedHeight) {
+              throw FlutterError(
+                'Horizontal viewport was given unbounded height.\n'
+                'Viewports expand in the cross axis to fill their container and '
+                'constrain their children to match their extent in the cross axis. '
+                'In this case, a horizontal viewport was given an unlimited amount of '
+                'vertical space in which to expand.'
+              );
+            }
+            break;
+        }
+      }
+      return true;
+    }());
+    return constraints.biggest;
+  }
+
+  static const int _maxLayoutCycles = 10;
+
+  // Out-of-band data computed during layout.
+  late double _minScrollExtent;
+  late double _maxScrollExtent;
+  bool _hasVisualOverflow = false;
+
+  @override
+  void performLayout() {
+    // Ignore the return value of applyViewportDimension because we are
+    // doing a layout regardless.
+    switch (axis) {
+      case Axis.vertical:
+        offset.applyViewportDimension(size.height);
+        break;
+      case Axis.horizontal:
+        offset.applyViewportDimension(size.width);
+        break;
+    }
+
+    if (center == null) {
+      assert(firstChild == null);
+      _minScrollExtent = 0.0;
+      _maxScrollExtent = 0.0;
+      _hasVisualOverflow = false;
+      offset.applyContentDimensions(0.0, 0.0);
+      return;
+    }
+    assert(center!.parent == this);
+
+    final double mainAxisExtent;
+    final double crossAxisExtent;
+    switch (axis) {
+      case Axis.vertical:
+        mainAxisExtent = size.height;
+        crossAxisExtent = size.width;
+        break;
+      case Axis.horizontal:
+        mainAxisExtent = size.width;
+        crossAxisExtent = size.height;
+        break;
+    }
+
+    final double centerOffsetAdjustment = center!.centerOffsetAdjustment;
+
+    double correction;
+    int count = 0;
+    do {
+      assert(offset.pixels != null);
+      correction = _attemptLayout(mainAxisExtent, crossAxisExtent, offset.pixels + centerOffsetAdjustment);
+      if (correction != 0.0) {
+        offset.correctBy(correction);
+      } else {
+        if (offset.applyContentDimensions(
+              math.min(0.0, _minScrollExtent + mainAxisExtent * anchor),
+              math.max(0.0, _maxScrollExtent - mainAxisExtent * (1.0 - anchor)),
+           ))
+          break;
+      }
+      count += 1;
+    } while (count < _maxLayoutCycles);
+    assert(() {
+      if (count >= _maxLayoutCycles) {
+        assert(count != 1);
+        throw FlutterError(
+          'A RenderViewport exceeded its maximum number of layout cycles.\n'
+          'RenderViewport render objects, during layout, can retry if either their '
+          'slivers or their ViewportOffset decide that the offset should be corrected '
+          'to take into account information collected during that layout.\n'
+          'In the case of this RenderViewport object, however, this happened $count '
+          'times and still there was no consensus on the scroll offset. This usually '
+          'indicates a bug. Specifically, it means that one of the following three '
+          'problems is being experienced by the RenderViewport object:\n'
+          ' * One of the RenderSliver children or the ViewportOffset have a bug such'
+          ' that they always think that they need to correct the offset regardless.\n'
+          ' * Some combination of the RenderSliver children and the ViewportOffset'
+          ' have a bad interaction such that one applies a correction then another'
+          ' applies a reverse correction, leading to an infinite loop of corrections.\n'
+          ' * There is a pathological case that would eventually resolve, but it is'
+          ' so complicated that it cannot be resolved in any reasonable number of'
+          ' layout passes.'
+        );
+      }
+      return true;
+    }());
+  }
+
+  double _attemptLayout(double mainAxisExtent, double crossAxisExtent, double correctedOffset) {
+    assert(!mainAxisExtent.isNaN);
+    assert(mainAxisExtent >= 0.0);
+    assert(crossAxisExtent.isFinite);
+    assert(crossAxisExtent >= 0.0);
+    assert(correctedOffset.isFinite);
+    _minScrollExtent = 0.0;
+    _maxScrollExtent = 0.0;
+    _hasVisualOverflow = false;
+
+    // centerOffset is the offset from the leading edge of the RenderViewport
+    // to the zero scroll offset (the line between the forward slivers and the
+    // reverse slivers).
+    final double centerOffset = mainAxisExtent * anchor - correctedOffset;
+    final double reverseDirectionRemainingPaintExtent = centerOffset.clamp(0.0, mainAxisExtent);
+    final double forwardDirectionRemainingPaintExtent = (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent);
+
+    switch (cacheExtentStyle) {
+      case CacheExtentStyle.pixel:
+        _calculatedCacheExtent = cacheExtent;
+        break;
+      case CacheExtentStyle.viewport:
+        _calculatedCacheExtent = mainAxisExtent * _cacheExtent;
+        break;
+    }
+
+    final double fullCacheExtent = mainAxisExtent + 2 * _calculatedCacheExtent!;
+    final double centerCacheOffset = centerOffset + _calculatedCacheExtent!;
+    final double reverseDirectionRemainingCacheExtent = centerCacheOffset.clamp(0.0, fullCacheExtent);
+    final double forwardDirectionRemainingCacheExtent = (fullCacheExtent - centerCacheOffset).clamp(0.0, fullCacheExtent);
+
+    final RenderSliver? leadingNegativeChild = childBefore(center!);
+
+    if (leadingNegativeChild != null) {
+      // negative scroll offsets
+      final double result = layoutChildSequence(
+        child: leadingNegativeChild,
+        scrollOffset: math.max(mainAxisExtent, centerOffset) - mainAxisExtent,
+        overlap: 0.0,
+        layoutOffset: forwardDirectionRemainingPaintExtent,
+        remainingPaintExtent: reverseDirectionRemainingPaintExtent,
+        mainAxisExtent: mainAxisExtent,
+        crossAxisExtent: crossAxisExtent,
+        growthDirection: GrowthDirection.reverse,
+        advance: childBefore,
+        remainingCacheExtent: reverseDirectionRemainingCacheExtent,
+        cacheOrigin: (mainAxisExtent - centerOffset).clamp(-_calculatedCacheExtent!, 0.0),
+      );
+      if (result != 0.0)
+        return -result;
+    }
+
+    // positive scroll offsets
+    return layoutChildSequence(
+      child: center,
+      scrollOffset: math.max(0.0, -centerOffset),
+      overlap: leadingNegativeChild == null ? math.min(0.0, -centerOffset) : 0.0,
+      layoutOffset: centerOffset >= mainAxisExtent ? centerOffset: reverseDirectionRemainingPaintExtent,
+      remainingPaintExtent: forwardDirectionRemainingPaintExtent,
+      mainAxisExtent: mainAxisExtent,
+      crossAxisExtent: crossAxisExtent,
+      growthDirection: GrowthDirection.forward,
+      advance: childAfter,
+      remainingCacheExtent: forwardDirectionRemainingCacheExtent,
+      cacheOrigin: centerOffset.clamp(-_calculatedCacheExtent!, 0.0),
+    );
+  }
+
+  @override
+  bool get hasVisualOverflow => _hasVisualOverflow;
+
+  @override
+  void updateOutOfBandData(GrowthDirection growthDirection, SliverGeometry childLayoutGeometry) {
+    switch (growthDirection) {
+      case GrowthDirection.forward:
+        _maxScrollExtent += childLayoutGeometry.scrollExtent;
+        break;
+      case GrowthDirection.reverse:
+        _minScrollExtent -= childLayoutGeometry.scrollExtent;
+        break;
+    }
+    if (childLayoutGeometry.hasVisualOverflow)
+      _hasVisualOverflow = true;
+  }
+
+  @override
+  void updateChildLayoutOffset(RenderSliver child, double layoutOffset, GrowthDirection growthDirection) {
+    final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
+    childParentData.paintOffset = computeAbsolutePaintOffset(child, layoutOffset, growthDirection);
+  }
+
+  @override
+  Offset paintOffsetOf(RenderSliver child) {
+    final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
+    return childParentData.paintOffset;
+  }
+
+  @override
+  double scrollOffsetOf(RenderSliver child, double scrollOffsetWithinChild) {
+    assert(child.parent == this);
+    final GrowthDirection growthDirection = child.constraints.growthDirection;
+    assert(growthDirection != null);
+    switch (growthDirection) {
+      case GrowthDirection.forward:
+        double scrollOffsetToChild = 0.0;
+        RenderSliver? current = center;
+        while (current != child) {
+          scrollOffsetToChild += current!.geometry!.scrollExtent;
+          current = childAfter(current);
+        }
+        return scrollOffsetToChild + scrollOffsetWithinChild;
+      case GrowthDirection.reverse:
+        double scrollOffsetToChild = 0.0;
+        RenderSliver? current = childBefore(center!);
+        while (current != child) {
+          scrollOffsetToChild -= current!.geometry!.scrollExtent;
+          current = childBefore(current);
+        }
+        return scrollOffsetToChild - scrollOffsetWithinChild;
+    }
+  }
+
+  @override
+  double maxScrollObstructionExtentBefore(RenderSliver child) {
+    assert(child.parent == this);
+    final GrowthDirection growthDirection = child.constraints.growthDirection;
+    assert(growthDirection != null);
+    switch (growthDirection) {
+      case GrowthDirection.forward:
+        double pinnedExtent = 0.0;
+        RenderSliver? current = center;
+        while (current != child) {
+          pinnedExtent += current!.geometry!.maxScrollObstructionExtent;
+          current = childAfter(current);
+        }
+        return pinnedExtent;
+      case GrowthDirection.reverse:
+        double pinnedExtent = 0.0;
+        RenderSliver? current = childBefore(center!);
+        while (current != child) {
+          pinnedExtent += current!.geometry!.maxScrollObstructionExtent;
+          current = childBefore(current);
+        }
+        return pinnedExtent;
+    }
+  }
+
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) {
+    // Hit test logic relies on this always providing an invertible matrix.
+    assert(child != null);
+    final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
+    childParentData.applyPaintTransform(transform);
+  }
+
+  @override
+  double computeChildMainAxisPosition(RenderSliver child, double parentMainAxisPosition) {
+    assert(child != null);
+    assert(child.constraints != null);
+    final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
+    switch (applyGrowthDirectionToAxisDirection(child.constraints.axisDirection, child.constraints.growthDirection)) {
+      case AxisDirection.down:
+        return parentMainAxisPosition - childParentData.paintOffset.dy;
+      case AxisDirection.right:
+        return parentMainAxisPosition - childParentData.paintOffset.dx;
+      case AxisDirection.up:
+        return child.geometry!.paintExtent - (parentMainAxisPosition - childParentData.paintOffset.dy);
+      case AxisDirection.left:
+        return child.geometry!.paintExtent - (parentMainAxisPosition - childParentData.paintOffset.dx);
+    }
+  }
+
+  @override
+  int get indexOfFirstChild {
+    assert(center != null);
+    assert(center!.parent == this);
+    assert(firstChild != null);
+    int count = 0;
+    RenderSliver? child = center;
+    while (child != firstChild) {
+      count -= 1;
+      child = childBefore(child!);
+    }
+    return count;
+  }
+
+  @override
+  String labelForChild(int index) {
+    if (index == 0)
+      return 'center child';
+    return 'child $index';
+  }
+
+  @override
+  Iterable<RenderSliver> get childrenInPaintOrder sync* {
+    if (firstChild == null)
+      return;
+    RenderSliver? child = firstChild;
+    while (child != center) {
+      yield child!;
+      child = childAfter(child);
+    }
+    child = lastChild;
+    while (true) {
+      yield child!;
+      if (child == center)
+        return;
+      child = childBefore(child);
+    }
+  }
+
+  @override
+  Iterable<RenderSliver> get childrenInHitTestOrder sync* {
+    if (firstChild == null)
+      return;
+    RenderSliver? child = center;
+    while (child != null) {
+      yield child;
+      child = childAfter(child);
+    }
+    child = childBefore(center!);
+    while (child != null) {
+      yield child;
+      child = childBefore(child);
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('anchor', anchor));
+  }
+}
+
+/// A render object that is bigger on the inside and shrink wraps its children
+/// in the main axis.
+///
+/// [RenderShrinkWrappingViewport] displays a subset of its children according
+/// to its own dimensions and the given [offset]. As the offset varies, different
+/// children are visible through the viewport.
+///
+/// [RenderShrinkWrappingViewport] differs from [RenderViewport] in that
+/// [RenderViewport] expands to fill the main axis whereas
+/// [RenderShrinkWrappingViewport] sizes itself to match its children in the
+/// main axis. This shrink wrapping behavior is expensive because the children,
+/// and hence the viewport, could potentially change size whenever the [offset]
+/// changes (e.g., because of a collapsing header).
+///
+/// [RenderShrinkWrappingViewport] cannot contain [RenderBox] children directly.
+/// Instead, use a [RenderSliverList], [RenderSliverFixedExtentList],
+/// [RenderSliverGrid], or a [RenderSliverToBoxAdapter], for example.
+///
+/// See also:
+///
+///  * [RenderViewport], a viewport that does not shrink-wrap its contents.
+///  * [RenderSliver], which explains more about the Sliver protocol.
+///  * [RenderBox], which explains more about the Box protocol.
+///  * [RenderSliverToBoxAdapter], which allows a [RenderBox] object to be
+///    placed inside a [RenderSliver] (the opposite of this class).
+class RenderShrinkWrappingViewport extends RenderViewportBase<SliverLogicalContainerParentData> {
+  /// Creates a viewport (for [RenderSliver] objects) that shrink-wraps its
+  /// contents.
+  ///
+  /// The [offset] must be specified. For testing purposes, consider passing a
+  /// [new ViewportOffset.zero] or [new ViewportOffset.fixed].
+  RenderShrinkWrappingViewport({
+    AxisDirection axisDirection = AxisDirection.down,
+    required AxisDirection crossAxisDirection,
+    required ViewportOffset offset,
+    Clip clipBehavior = Clip.hardEdge,
+    List<RenderSliver>? children,
+  }) : super(
+        axisDirection: axisDirection,
+        crossAxisDirection: crossAxisDirection,
+        offset: offset,
+        clipBehavior: clipBehavior,
+       ) {
+    addAll(children);
+  }
+
+  @override
+  void setupParentData(RenderObject child) {
+    if (child.parentData is! SliverLogicalContainerParentData)
+      child.parentData = SliverLogicalContainerParentData();
+  }
+
+  @override
+  bool debugThrowIfNotCheckingIntrinsics() {
+    assert(() {
+      if (!RenderObject.debugCheckingIntrinsics) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('$runtimeType does not support returning intrinsic dimensions.'),
+          ErrorDescription(
+           'Calculating the intrinsic dimensions would require instantiating every child of '
+           'the viewport, which defeats the point of viewports being lazy.'
+          ),
+          ErrorHint(
+            'If you are merely trying to shrink-wrap the viewport in the main axis direction, '
+            'you should be able to achieve that effect by just giving the viewport loose '
+            'constraints, without needing to measure its intrinsic dimensions.'
+          )
+        ]);
+      }
+      return true;
+    }());
+    return true;
+  }
+
+  // Out-of-band data computed during layout.
+  late double _maxScrollExtent;
+  late double _shrinkWrapExtent;
+  bool _hasVisualOverflow = false;
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    if (firstChild == null) {
+      switch (axis) {
+        case Axis.vertical:
+          assert(constraints.hasBoundedWidth);
+          size = Size(constraints.maxWidth, constraints.minHeight);
+          break;
+        case Axis.horizontal:
+          assert(constraints.hasBoundedHeight);
+          size = Size(constraints.minWidth, constraints.maxHeight);
+          break;
+      }
+      offset.applyViewportDimension(0.0);
+      _maxScrollExtent = 0.0;
+      _shrinkWrapExtent = 0.0;
+      _hasVisualOverflow = false;
+      offset.applyContentDimensions(0.0, 0.0);
+      return;
+    }
+
+    final double mainAxisExtent;
+    final double crossAxisExtent;
+    switch (axis) {
+      case Axis.vertical:
+        assert(constraints.hasBoundedWidth);
+        mainAxisExtent = constraints.maxHeight;
+        crossAxisExtent = constraints.maxWidth;
+        break;
+      case Axis.horizontal:
+        assert(constraints.hasBoundedHeight);
+        mainAxisExtent = constraints.maxWidth;
+        crossAxisExtent = constraints.maxHeight;
+        break;
+    }
+
+    double correction;
+    double effectiveExtent;
+    do {
+      assert(offset.pixels != null);
+      correction = _attemptLayout(mainAxisExtent, crossAxisExtent, offset.pixels);
+      if (correction != 0.0) {
+        offset.correctBy(correction);
+      } else {
+        switch (axis) {
+          case Axis.vertical:
+            effectiveExtent = constraints.constrainHeight(_shrinkWrapExtent);
+            break;
+          case Axis.horizontal:
+            effectiveExtent = constraints.constrainWidth(_shrinkWrapExtent);
+            break;
+        }
+        final bool didAcceptViewportDimension = offset.applyViewportDimension(effectiveExtent);
+        final bool didAcceptContentDimension = offset.applyContentDimensions(0.0, math.max(0.0, _maxScrollExtent - effectiveExtent));
+        if (didAcceptViewportDimension && didAcceptContentDimension)
+          break;
+      }
+    } while (true);
+    switch (axis) {
+      case Axis.vertical:
+        size = constraints.constrainDimensions(crossAxisExtent, effectiveExtent);
+        break;
+      case Axis.horizontal:
+        size = constraints.constrainDimensions(effectiveExtent, crossAxisExtent);
+        break;
+    }
+  }
+
+  double _attemptLayout(double mainAxisExtent, double crossAxisExtent, double correctedOffset) {
+    // We can't assert mainAxisExtent is finite, because it could be infinite if
+    // it is within a column or row for example. In such a case, there's not
+    // even any scrolling to do, although some scroll physics (i.e.
+    // BouncingScrollPhysics) could still temporarily scroll the content in a
+    // simulation.
+    assert(!mainAxisExtent.isNaN);
+    assert(mainAxisExtent >= 0.0);
+    assert(crossAxisExtent.isFinite);
+    assert(crossAxisExtent >= 0.0);
+    assert(correctedOffset.isFinite);
+    _maxScrollExtent = 0.0;
+    _shrinkWrapExtent = 0.0;
+    _hasVisualOverflow = false;
+    return layoutChildSequence(
+      child: firstChild,
+      scrollOffset: math.max(0.0, correctedOffset),
+      overlap: math.min(0.0, correctedOffset),
+      layoutOffset: 0.0,
+      remainingPaintExtent: mainAxisExtent,
+      mainAxisExtent: mainAxisExtent,
+      crossAxisExtent: crossAxisExtent,
+      growthDirection: GrowthDirection.forward,
+      advance: childAfter,
+      remainingCacheExtent: mainAxisExtent + 2 * _cacheExtent,
+      cacheOrigin: -_cacheExtent,
+    );
+  }
+
+  @override
+  bool get hasVisualOverflow => _hasVisualOverflow;
+
+  @override
+  void updateOutOfBandData(GrowthDirection growthDirection, SliverGeometry childLayoutGeometry) {
+    assert(growthDirection == GrowthDirection.forward);
+    _maxScrollExtent += childLayoutGeometry.scrollExtent;
+    if (childLayoutGeometry.hasVisualOverflow)
+      _hasVisualOverflow = true;
+    _shrinkWrapExtent += childLayoutGeometry.maxPaintExtent;
+  }
+
+  @override
+  void updateChildLayoutOffset(RenderSliver child, double layoutOffset, GrowthDirection growthDirection) {
+    assert(growthDirection == GrowthDirection.forward);
+    final SliverLogicalParentData childParentData = child.parentData! as SliverLogicalParentData;
+    childParentData.layoutOffset = layoutOffset;
+  }
+
+  @override
+  Offset paintOffsetOf(RenderSliver child) {
+    final SliverLogicalParentData childParentData = child.parentData! as SliverLogicalParentData;
+    return computeAbsolutePaintOffset(child, childParentData.layoutOffset!, GrowthDirection.forward);
+  }
+
+  @override
+  double scrollOffsetOf(RenderSliver child, double scrollOffsetWithinChild) {
+    assert(child.parent == this);
+    assert(child.constraints.growthDirection == GrowthDirection.forward);
+    double scrollOffsetToChild = 0.0;
+    RenderSliver? current = firstChild;
+    while (current != child) {
+      scrollOffsetToChild += current!.geometry!.scrollExtent;
+      current = childAfter(current);
+    }
+    return scrollOffsetToChild + scrollOffsetWithinChild;
+  }
+
+  @override
+  double maxScrollObstructionExtentBefore(RenderSliver child) {
+    assert(child.parent == this);
+    assert(child.constraints.growthDirection == GrowthDirection.forward);
+    double pinnedExtent = 0.0;
+    RenderSliver? current = firstChild;
+    while (current != child) {
+      pinnedExtent += current!.geometry!.maxScrollObstructionExtent;
+      current = childAfter(current);
+    }
+    return pinnedExtent;
+  }
+
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) {
+    // Hit test logic relies on this always providing an invertible matrix.
+    assert(child != null);
+    final Offset offset = paintOffsetOf(child as RenderSliver);
+    transform.translate(offset.dx, offset.dy);
+  }
+
+  @override
+  double computeChildMainAxisPosition(RenderSliver child, double parentMainAxisPosition) {
+    assert(child != null);
+    assert(child.constraints != null);
+    assert(hasSize);
+    final SliverLogicalParentData childParentData = child.parentData! as SliverLogicalParentData;
+    switch (applyGrowthDirectionToAxisDirection(child.constraints.axisDirection, child.constraints.growthDirection)) {
+      case AxisDirection.down:
+      case AxisDirection.right:
+        return parentMainAxisPosition - childParentData.layoutOffset!;
+      case AxisDirection.up:
+        return (size.height - parentMainAxisPosition) - childParentData.layoutOffset!;
+      case AxisDirection.left:
+        return (size.width - parentMainAxisPosition) - childParentData.layoutOffset!;
+    }
+  }
+
+  @override
+  int get indexOfFirstChild => 0;
+
+  @override
+  String labelForChild(int index) => 'child $index';
+
+  @override
+  Iterable<RenderSliver> get childrenInPaintOrder sync* {
+    RenderSliver? child = firstChild;
+    while (child != null) {
+      yield child;
+      child = childAfter(child);
+    }
+  }
+
+  @override
+  Iterable<RenderSliver> get childrenInHitTestOrder sync* {
+    RenderSliver? child = lastChild;
+    while (child != null) {
+      yield child;
+      child = childBefore(child);
+    }
+  }
+}
diff --git a/lib/src/rendering/viewport_offset.dart b/lib/src/rendering/viewport_offset.dart
new file mode 100644
index 0000000..e87265c
--- /dev/null
+++ b/lib/src/rendering/viewport_offset.dart
@@ -0,0 +1,295 @@
+// Copyright 2014 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.
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+
+/// The direction of a scroll, relative to the positive scroll offset axis given
+/// by an [AxisDirection] and a [GrowthDirection].
+///
+/// This contrasts to [GrowthDirection] in that it has a third value, [idle],
+/// for the case where no scroll is occurring.
+///
+/// This is used by [RenderSliverFloatingPersistentHeader] to only expand when
+/// the user is scrolling in the same direction as the detected scroll offset
+/// change.
+enum ScrollDirection {
+  /// No scrolling is underway.
+  idle,
+
+  /// Scrolling is happening in the positive scroll offset direction.
+  ///
+  /// For example, for the [GrowthDirection.forward] part of a vertical
+  /// [AxisDirection.down] list, this means the content is moving up, exposing
+  /// lower content.
+  forward,
+
+  /// Scrolling is happening in the negative scroll offset direction.
+  ///
+  /// For example, for the [GrowthDirection.forward] part of a vertical
+  /// [AxisDirection.down] list, this means the content is moving down, exposing
+  /// earlier content.
+  reverse,
+}
+
+/// Returns the opposite of the given [ScrollDirection].
+///
+/// Specifically, returns [ScrollDirection.reverse] for [ScrollDirection.forward]
+/// (and vice versa) and returns [ScrollDirection.idle] for
+/// [ScrollDirection.idle].
+ScrollDirection flipScrollDirection(ScrollDirection direction) {
+  switch (direction) {
+    case ScrollDirection.idle:
+      return ScrollDirection.idle;
+    case ScrollDirection.forward:
+      return ScrollDirection.reverse;
+    case ScrollDirection.reverse:
+      return ScrollDirection.forward;
+  }
+}
+
+/// Which part of the content inside the viewport should be visible.
+///
+/// The [pixels] value determines the scroll offset that the viewport uses to
+/// select which part of its content to display. As the user scrolls the
+/// viewport, this value changes, which changes the content that is displayed.
+///
+/// This object is a [Listenable] that notifies its listeners when [pixels]
+/// changes.
+///
+/// See also:
+///
+///  * [ScrollPosition], which is a commonly used concrete subclass.
+///  * [RenderViewportBase], which is a render object that uses viewport
+///    offsets.
+abstract class ViewportOffset extends ChangeNotifier {
+  /// Default constructor.
+  ///
+  /// Allows subclasses to construct this object directly.
+  ViewportOffset();
+
+  /// Creates a viewport offset with the given [pixels] value.
+  ///
+  /// The [pixels] value does not change unless the viewport issues a
+  /// correction.
+  factory ViewportOffset.fixed(double value) = _FixedViewportOffset;
+
+  /// Creates a viewport offset with a [pixels] value of 0.0.
+  ///
+  /// The [pixels] value does not change unless the viewport issues a
+  /// correction.
+  factory ViewportOffset.zero() = _FixedViewportOffset.zero;
+
+  /// The number of pixels to offset the children in the opposite of the axis direction.
+  ///
+  /// For example, if the axis direction is down, then the pixel value
+  /// represents the number of logical pixels to move the children _up_ the
+  /// screen. Similarly, if the axis direction is left, then the pixels value
+  /// represents the number of logical pixels to move the children to _right_.
+  ///
+  /// This object notifies its listeners when this value changes (except when
+  /// the value changes due to [correctBy]).
+  double get pixels;
+
+  /// Whether the [pixels] property is available.
+  bool get hasPixels;
+
+  /// Called when the viewport's extents are established.
+  ///
+  /// The argument is the dimension of the [RenderViewport] in the main axis
+  /// (e.g. the height, for a vertical viewport).
+  ///
+  /// This may be called redundantly, with the same value, each frame. This is
+  /// called during layout for the [RenderViewport]. If the viewport is
+  /// configured to shrink-wrap its contents, it may be called several times,
+  /// since the layout is repeated each time the scroll offset is corrected.
+  ///
+  /// If this is called, it is called before [applyContentDimensions]. If this
+  /// is called, [applyContentDimensions] will be called soon afterwards in the
+  /// same layout phase. If the viewport is not configured to shrink-wrap its
+  /// contents, then this will only be called when the viewport recomputes its
+  /// size (i.e. when its parent lays out), and not during normal scrolling.
+  ///
+  /// If applying the viewport dimensions changes the scroll offset, return
+  /// false. Otherwise, return true. If you return false, the [RenderViewport]
+  /// will be laid out again with the new scroll offset. This is expensive. (The
+  /// return value is answering the question "did you accept these viewport
+  /// dimensions unconditionally?"; if the new dimensions change the
+  /// [ViewportOffset]'s actual [pixels] value, then the viewport will need to
+  /// be laid out again.)
+  bool applyViewportDimension(double viewportDimension);
+
+  /// Called when the viewport's content extents are established.
+  ///
+  /// The arguments are the minimum and maximum scroll extents respectively. The
+  /// minimum will be equal to or less than the maximum. In the case of slivers,
+  /// the minimum will be equal to or less than zero, the maximum will be equal
+  /// to or greater than zero.
+  ///
+  /// The maximum scroll extent has the viewport dimension subtracted from it.
+  /// For instance, if there is 100.0 pixels of scrollable content, and the
+  /// viewport is 80.0 pixels high, then the minimum scroll extent will
+  /// typically be 0.0 and the maximum scroll extent will typically be 20.0,
+  /// because there's only 20.0 pixels of actual scroll slack.
+  ///
+  /// If applying the content dimensions changes the scroll offset, return
+  /// false. Otherwise, return true. If you return false, the [RenderViewport]
+  /// will be laid out again with the new scroll offset. This is expensive. (The
+  /// return value is answering the question "did you accept these content
+  /// dimensions unconditionally?"; if the new dimensions change the
+  /// [ViewportOffset]'s actual [pixels] value, then the viewport will need to
+  /// be laid out again.)
+  ///
+  /// This is called at least once each time the [RenderViewport] is laid out,
+  /// even if the values have not changed. It may be called many times if the
+  /// scroll offset is corrected (if this returns false). This is always called
+  /// after [applyViewportDimension], if that method is called.
+  bool applyContentDimensions(double minScrollExtent, double maxScrollExtent);
+
+  /// Apply a layout-time correction to the scroll offset.
+  ///
+  /// This method should change the [pixels] value by `correction`, but without
+  /// calling [notifyListeners]. It is called during layout by the
+  /// [RenderViewport], before [applyContentDimensions]. After this method is
+  /// called, the layout will be recomputed and that may result in this method
+  /// being called again, though this should be very rare.
+  ///
+  /// See also:
+  ///
+  ///  * [jumpTo], for also changing the scroll position when not in layout.
+  ///    [jumpTo] applies the change immediately and notifies its listeners.
+  void correctBy(double correction);
+
+  /// Jumps [pixels] from its current value to the given value,
+  /// without animation, and without checking if the new value is in range.
+  ///
+  /// See also:
+  ///
+  ///  * [correctBy], for changing the current offset in the middle of layout
+  ///    and that defers the notification of its listeners until after layout.
+  void jumpTo(double pixels);
+
+  /// Animates [pixels] from its current value to the given value.
+  ///
+  /// The returned [Future] will complete when the animation ends, whether it
+  /// completed successfully or whether it was interrupted prematurely.
+  ///
+  /// The duration must not be zero. To jump to a particular value without an
+  /// animation, use [jumpTo].
+  Future<void> animateTo(
+    double to, {
+    required Duration duration,
+    required Curve curve,
+  });
+
+  /// Calls [jumpTo] if duration is null or [Duration.zero], otherwise
+  /// [animateTo] is called.
+  ///
+  /// If [animateTo] is called then [curve] defaults to [Curves.ease]. The
+  /// [clamp] parameter is ignored by this stub implementation but subclasses
+  /// like [ScrollPosition] handle it by adjusting [to] to prevent over or
+  /// underscroll.
+  Future<void> moveTo(
+    double to, {
+    Duration? duration,
+    Curve? curve,
+    bool? clamp,
+  }) {
+    assert(to != null);
+    if (duration == null || duration == Duration.zero) {
+      jumpTo(to);
+      return Future<void>.value();
+    } else {
+      return animateTo(to, duration: duration, curve: curve ?? Curves.ease);
+    }
+  }
+
+  /// The direction in which the user is trying to change [pixels], relative to
+  /// the viewport's [RenderViewportBase.axisDirection].
+  ///
+  /// If the _user_ is not scrolling, this will return [ScrollDirection.idle]
+  /// even if there is (for example) a [ScrollActivity] currently animating the
+  /// position.
+  ///
+  /// This is exposed in [SliverConstraints.userScrollDirection], which is used
+  /// by some slivers to determine how to react to a change in scroll offset.
+  /// For example, [RenderSliverFloatingPersistentHeader] will only expand a
+  /// floating app bar when the [userScrollDirection] is in the positive scroll
+  /// offset direction.
+  ScrollDirection get userScrollDirection;
+
+  /// Whether a viewport is allowed to change [pixels] implicitly to respond to
+  /// a call to [RenderObject.showOnScreen].
+  ///
+  /// [RenderObject.showOnScreen] is for example used to bring a text field
+  /// fully on screen after it has received focus. This property controls
+  /// whether the viewport associated with this offset is allowed to change the
+  /// offset's [pixels] value to fulfill such a request.
+  bool get allowImplicitScrolling;
+
+  @override
+  String toString() {
+    final List<String> description = <String>[];
+    debugFillDescription(description);
+    return '${describeIdentity(this)}(${description.join(", ")})';
+  }
+
+  /// Add additional information to the given description for use by [toString].
+  ///
+  /// This method makes it easier for subclasses to coordinate to provide a
+  /// high-quality [toString] implementation. The [toString] implementation on
+  /// the [State] base class calls [debugFillDescription] to collect useful
+  /// information from subclasses to incorporate into its return value.
+  ///
+  /// If you override this, make sure to start your method with a call to
+  /// `super.debugFillDescription(description)`.
+  @mustCallSuper
+  void debugFillDescription(List<String> description) {
+    if (hasPixels) {
+      description.add('offset: ${pixels.toStringAsFixed(1)}');
+    }
+  }
+}
+
+class _FixedViewportOffset extends ViewportOffset {
+  _FixedViewportOffset(this._pixels);
+  _FixedViewportOffset.zero() : _pixels = 0.0;
+
+  double _pixels;
+
+  @override
+  double get pixels => _pixels;
+
+  @override
+  bool get hasPixels => true;
+
+  @override
+  bool applyViewportDimension(double viewportDimension) => true;
+
+  @override
+  bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) => true;
+
+  @override
+  void correctBy(double correction) {
+    _pixels += correction;
+  }
+
+  @override
+  void jumpTo(double pixels) {
+    // Do nothing, viewport is fixed.
+  }
+
+  @override
+  Future<void> animateTo(
+    double to, {
+    required Duration duration,
+    required Curve curve,
+  }) async { }
+
+  @override
+  ScrollDirection get userScrollDirection => ScrollDirection.idle;
+
+  @override
+  bool get allowImplicitScrolling => false;
+}
diff --git a/lib/src/rendering/wrap.dart b/lib/src/rendering/wrap.dart
new file mode 100644
index 0000000..7206a8a
--- /dev/null
+++ b/lib/src/rendering/wrap.dart
@@ -0,0 +1,786 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'box.dart';
+import 'layer.dart';
+import 'layout_helper.dart';
+import 'object.dart';
+
+/// How [Wrap] should align objects.
+///
+/// Used both to align children within a run in the main axis as well as to
+/// align the runs themselves in the cross axis.
+enum WrapAlignment {
+  /// Place the objects as close to the start of the axis as possible.
+  ///
+  /// If this value is used in a horizontal direction, a [TextDirection] must be
+  /// available to determine if the start is the left or the right.
+  ///
+  /// If this value is used in a vertical direction, a [VerticalDirection] must be
+  /// available to determine if the start is the top or the bottom.
+  start,
+
+  /// Place the objects as close to the end of the axis as possible.
+  ///
+  /// If this value is used in a horizontal direction, a [TextDirection] must be
+  /// available to determine if the end is the left or the right.
+  ///
+  /// If this value is used in a vertical direction, a [VerticalDirection] must be
+  /// available to determine if the end is the top or the bottom.
+  end,
+
+  /// Place the objects as close to the middle of the axis as possible.
+  center,
+
+  /// Place the free space evenly between the objects.
+  spaceBetween,
+
+  /// Place the free space evenly between the objects as well as half of that
+  /// space before and after the first and last objects.
+  spaceAround,
+
+  /// Place the free space evenly between the objects as well as before and
+  /// after the first and last objects.
+  spaceEvenly,
+}
+
+/// Who [Wrap] should align children within a run in the cross axis.
+enum WrapCrossAlignment {
+  /// Place the children as close to the start of the run in the cross axis as
+  /// possible.
+  ///
+  /// If this value is used in a horizontal direction, a [TextDirection] must be
+  /// available to determine if the start is the left or the right.
+  ///
+  /// If this value is used in a vertical direction, a [VerticalDirection] must be
+  /// available to determine if the start is the top or the bottom.
+  start,
+
+  /// Place the children as close to the end of the run in the cross axis as
+  /// possible.
+  ///
+  /// If this value is used in a horizontal direction, a [TextDirection] must be
+  /// available to determine if the end is the left or the right.
+  ///
+  /// If this value is used in a vertical direction, a [VerticalDirection] must be
+  /// available to determine if the end is the top or the bottom.
+  end,
+
+  /// Place the children as close to the middle of the run in the cross axis as
+  /// possible.
+  center,
+
+  // TODO(ianh): baseline.
+}
+
+class _RunMetrics {
+  _RunMetrics(this.mainAxisExtent, this.crossAxisExtent, this.childCount);
+
+  final double mainAxisExtent;
+  final double crossAxisExtent;
+  final int childCount;
+}
+
+/// Parent data for use with [RenderWrap].
+class WrapParentData extends ContainerBoxParentData<RenderBox> {
+  int _runIndex = 0;
+}
+
+/// Displays its children in multiple horizontal or vertical runs.
+///
+/// A [RenderWrap] lays out each child and attempts to place the child adjacent
+/// to the previous child in the main axis, given by [direction], leaving
+/// [spacing] space in between. If there is not enough space to fit the child,
+/// [RenderWrap] creates a new _run_ adjacent to the existing children in the
+/// cross axis.
+///
+/// After all the children have been allocated to runs, the children within the
+/// runs are positioned according to the [alignment] in the main axis and
+/// according to the [crossAxisAlignment] in the cross axis.
+///
+/// The runs themselves are then positioned in the cross axis according to the
+/// [runSpacing] and [runAlignment].
+class RenderWrap extends RenderBox
+    with ContainerRenderObjectMixin<RenderBox, WrapParentData>,
+         RenderBoxContainerDefaultsMixin<RenderBox, WrapParentData> {
+  /// Creates a wrap render object.
+  ///
+  /// By default, the wrap layout is horizontal and both the children and the
+  /// runs are aligned to the start.
+  RenderWrap({
+    List<RenderBox>? children,
+    Axis direction = Axis.horizontal,
+    WrapAlignment alignment = WrapAlignment.start,
+    double spacing = 0.0,
+    WrapAlignment runAlignment = WrapAlignment.start,
+    double runSpacing = 0.0,
+    WrapCrossAlignment crossAxisAlignment = WrapCrossAlignment.start,
+    TextDirection? textDirection,
+    VerticalDirection verticalDirection = VerticalDirection.down,
+    Clip clipBehavior = Clip.none,
+  }) : assert(direction != null),
+       assert(alignment != null),
+       assert(spacing != null),
+       assert(runAlignment != null),
+       assert(runSpacing != null),
+       assert(crossAxisAlignment != null),
+       assert(clipBehavior != null),
+       _direction = direction,
+       _alignment = alignment,
+       _spacing = spacing,
+       _runAlignment = runAlignment,
+       _runSpacing = runSpacing,
+       _crossAxisAlignment = crossAxisAlignment,
+       _textDirection = textDirection,
+       _verticalDirection = verticalDirection,
+       _clipBehavior = clipBehavior {
+    addAll(children);
+  }
+
+  /// The direction to use as the main axis.
+  ///
+  /// For example, if [direction] is [Axis.horizontal], the default, the
+  /// children are placed adjacent to one another in a horizontal run until the
+  /// available horizontal space is consumed, at which point a subsequent
+  /// children are placed in a new run vertically adjacent to the previous run.
+  Axis get direction => _direction;
+  Axis _direction;
+  set direction (Axis value) {
+    assert(value != null);
+    if (_direction == value)
+      return;
+    _direction = value;
+    markNeedsLayout();
+  }
+
+  /// How the children within a run should be placed in the main axis.
+  ///
+  /// For example, if [alignment] is [WrapAlignment.center], the children in
+  /// each run are grouped together in the center of their run in the main axis.
+  ///
+  /// Defaults to [WrapAlignment.start].
+  ///
+  /// See also:
+  ///
+  ///  * [runAlignment], which controls how the runs are placed relative to each
+  ///    other in the cross axis.
+  ///  * [crossAxisAlignment], which controls how the children within each run
+  ///    are placed relative to each other in the cross axis.
+  WrapAlignment get alignment => _alignment;
+  WrapAlignment _alignment;
+  set alignment (WrapAlignment value) {
+    assert(value != null);
+    if (_alignment == value)
+      return;
+    _alignment = value;
+    markNeedsLayout();
+  }
+
+  /// How much space to place between children in a run in the main axis.
+  ///
+  /// For example, if [spacing] is 10.0, the children will be spaced at least
+  /// 10.0 logical pixels apart in the main axis.
+  ///
+  /// If there is additional free space in a run (e.g., because the wrap has a
+  /// minimum size that is not filled or because some runs are longer than
+  /// others), the additional free space will be allocated according to the
+  /// [alignment].
+  ///
+  /// Defaults to 0.0.
+  double get spacing => _spacing;
+  double _spacing;
+  set spacing (double value) {
+    assert(value != null);
+    if (_spacing == value)
+      return;
+    _spacing = value;
+    markNeedsLayout();
+  }
+
+  /// How the runs themselves should be placed in the cross axis.
+  ///
+  /// For example, if [runAlignment] is [WrapAlignment.center], the runs are
+  /// grouped together in the center of the overall [RenderWrap] in the cross
+  /// axis.
+  ///
+  /// Defaults to [WrapAlignment.start].
+  ///
+  /// See also:
+  ///
+  ///  * [alignment], which controls how the children within each run are placed
+  ///    relative to each other in the main axis.
+  ///  * [crossAxisAlignment], which controls how the children within each run
+  ///    are placed relative to each other in the cross axis.
+  WrapAlignment get runAlignment => _runAlignment;
+  WrapAlignment _runAlignment;
+  set runAlignment (WrapAlignment value) {
+    assert(value != null);
+    if (_runAlignment == value)
+      return;
+    _runAlignment = value;
+    markNeedsLayout();
+  }
+
+  /// How much space to place between the runs themselves in the cross axis.
+  ///
+  /// For example, if [runSpacing] is 10.0, the runs will be spaced at least
+  /// 10.0 logical pixels apart in the cross axis.
+  ///
+  /// If there is additional free space in the overall [RenderWrap] (e.g.,
+  /// because the wrap has a minimum size that is not filled), the additional
+  /// free space will be allocated according to the [runAlignment].
+  ///
+  /// Defaults to 0.0.
+  double get runSpacing => _runSpacing;
+  double _runSpacing;
+  set runSpacing (double value) {
+    assert(value != null);
+    if (_runSpacing == value)
+      return;
+    _runSpacing = value;
+    markNeedsLayout();
+  }
+
+  /// How the children within a run should be aligned relative to each other in
+  /// the cross axis.
+  ///
+  /// For example, if this is set to [WrapCrossAlignment.end], and the
+  /// [direction] is [Axis.horizontal], then the children within each
+  /// run will have their bottom edges aligned to the bottom edge of the run.
+  ///
+  /// Defaults to [WrapCrossAlignment.start].
+  ///
+  /// See also:
+  ///
+  ///  * [alignment], which controls how the children within each run are placed
+  ///    relative to each other in the main axis.
+  ///  * [runAlignment], which controls how the runs are placed relative to each
+  ///    other in the cross axis.
+  WrapCrossAlignment get crossAxisAlignment => _crossAxisAlignment;
+  WrapCrossAlignment _crossAxisAlignment;
+  set crossAxisAlignment (WrapCrossAlignment value) {
+    assert(value != null);
+    if (_crossAxisAlignment == value)
+      return;
+    _crossAxisAlignment = value;
+    markNeedsLayout();
+  }
+
+  /// Determines the order to lay children out horizontally and how to interpret
+  /// `start` and `end` in the horizontal direction.
+  ///
+  /// If the [direction] is [Axis.horizontal], this controls the order in which
+  /// children are positioned (left-to-right or right-to-left), and the meaning
+  /// of the [alignment] property's [WrapAlignment.start] and
+  /// [WrapAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.horizontal], and either the
+  /// [alignment] is either [WrapAlignment.start] or [WrapAlignment.end], or
+  /// there's more than one child, then the [textDirection] must not be null.
+  ///
+  /// If the [direction] is [Axis.vertical], this controls the order in
+  /// which runs are positioned, the meaning of the [runAlignment] property's
+  /// [WrapAlignment.start] and [WrapAlignment.end] values, as well as the
+  /// [crossAxisAlignment] property's [WrapCrossAlignment.start] and
+  /// [WrapCrossAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.vertical], and either the
+  /// [runAlignment] is either [WrapAlignment.start] or [WrapAlignment.end], the
+  /// [crossAxisAlignment] is either [WrapCrossAlignment.start] or
+  /// [WrapCrossAlignment.end], or there's more than one child, then the
+  /// [textDirection] must not be null.
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection;
+  set textDirection(TextDirection? value) {
+    if (_textDirection != value) {
+      _textDirection = value;
+      markNeedsLayout();
+    }
+  }
+
+  /// Determines the order to lay children out vertically and how to interpret
+  /// `start` and `end` in the vertical direction.
+  ///
+  /// If the [direction] is [Axis.vertical], this controls which order children
+  /// are painted in (down or up), the meaning of the [alignment] property's
+  /// [WrapAlignment.start] and [WrapAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.vertical], and either the [alignment]
+  /// is either [WrapAlignment.start] or [WrapAlignment.end], or there's
+  /// more than one child, then the [verticalDirection] must not be null.
+  ///
+  /// If the [direction] is [Axis.horizontal], this controls the order in which
+  /// runs are positioned, the meaning of the [runAlignment] property's
+  /// [WrapAlignment.start] and [WrapAlignment.end] values, as well as the
+  /// [crossAxisAlignment] property's [WrapCrossAlignment.start] and
+  /// [WrapCrossAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.horizontal], and either the
+  /// [runAlignment] is either [WrapAlignment.start] or [WrapAlignment.end], the
+  /// [crossAxisAlignment] is either [WrapCrossAlignment.start] or
+  /// [WrapCrossAlignment.end], or there's more than one child, then the
+  /// [verticalDirection] must not be null.
+  VerticalDirection get verticalDirection => _verticalDirection;
+  VerticalDirection _verticalDirection;
+  set verticalDirection(VerticalDirection value) {
+    if (_verticalDirection != value) {
+      _verticalDirection = value;
+      markNeedsLayout();
+    }
+  }
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior = Clip.none;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  bool get _debugHasNecessaryDirections {
+    assert(direction != null);
+    assert(alignment != null);
+    assert(runAlignment != null);
+    assert(crossAxisAlignment != null);
+    if (firstChild != null && lastChild != firstChild) {
+      // i.e. there's more than one child
+      switch (direction) {
+        case Axis.horizontal:
+          assert(textDirection != null, 'Horizontal $runtimeType with multiple children has a null textDirection, so the layout order is undefined.');
+          break;
+        case Axis.vertical:
+          assert(verticalDirection != null, 'Vertical $runtimeType with multiple children has a null verticalDirection, so the layout order is undefined.');
+          break;
+      }
+    }
+    if (alignment == WrapAlignment.start || alignment == WrapAlignment.end) {
+      switch (direction) {
+        case Axis.horizontal:
+          assert(textDirection != null, 'Horizontal $runtimeType with alignment $alignment has a null textDirection, so the alignment cannot be resolved.');
+          break;
+        case Axis.vertical:
+          assert(verticalDirection != null, 'Vertical $runtimeType with alignment $alignment has a null verticalDirection, so the alignment cannot be resolved.');
+          break;
+      }
+    }
+    if (runAlignment == WrapAlignment.start || runAlignment == WrapAlignment.end) {
+      switch (direction) {
+        case Axis.horizontal:
+          assert(verticalDirection != null, 'Horizontal $runtimeType with runAlignment $runAlignment has a null verticalDirection, so the alignment cannot be resolved.');
+          break;
+        case Axis.vertical:
+          assert(textDirection != null, 'Vertical $runtimeType with runAlignment $runAlignment has a null textDirection, so the alignment cannot be resolved.');
+          break;
+      }
+    }
+    if (crossAxisAlignment == WrapCrossAlignment.start || crossAxisAlignment == WrapCrossAlignment.end) {
+      switch (direction) {
+        case Axis.horizontal:
+          assert(verticalDirection != null, 'Horizontal $runtimeType with crossAxisAlignment $crossAxisAlignment has a null verticalDirection, so the alignment cannot be resolved.');
+          break;
+        case Axis.vertical:
+          assert(textDirection != null, 'Vertical $runtimeType with crossAxisAlignment $crossAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
+          break;
+      }
+    }
+    return true;
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! WrapParentData)
+      child.parentData = WrapParentData();
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    switch (direction) {
+      case Axis.horizontal:
+        double width = 0.0;
+        RenderBox? child = firstChild;
+        while (child != null) {
+          width = math.max(width, child.getMinIntrinsicWidth(double.infinity));
+          child = childAfter(child);
+        }
+        return width;
+      case Axis.vertical:
+        return computeDryLayout(BoxConstraints(maxHeight: height)).width;
+    }
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    switch (direction) {
+      case Axis.horizontal:
+        double width = 0.0;
+        RenderBox? child = firstChild;
+        while (child != null) {
+          width += child.getMaxIntrinsicWidth(double.infinity);
+          child = childAfter(child);
+        }
+        return width;
+      case Axis.vertical:
+        return computeDryLayout(BoxConstraints(maxHeight: height)).width;
+    }
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    switch (direction) {
+      case Axis.horizontal:
+        return computeDryLayout(BoxConstraints(maxWidth: width)).height;
+      case Axis.vertical:
+        double height = 0.0;
+        RenderBox? child = firstChild;
+        while (child != null) {
+          height = math.max(height, child.getMinIntrinsicHeight(double.infinity));
+          child = childAfter(child);
+        }
+        return height;
+    }
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    switch (direction) {
+      case Axis.horizontal:
+        return computeDryLayout(BoxConstraints(maxWidth: width)).height;
+      case Axis.vertical:
+        double height = 0.0;
+        RenderBox? child = firstChild;
+        while (child != null) {
+          height += child.getMaxIntrinsicHeight(double.infinity);
+          child = childAfter(child);
+        }
+        return height;
+    }
+  }
+
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    return defaultComputeDistanceToHighestActualBaseline(baseline);
+  }
+
+  double _getMainAxisExtent(Size childSize) {
+    switch (direction) {
+      case Axis.horizontal:
+        return childSize.width;
+      case Axis.vertical:
+        return childSize.height;
+    }
+  }
+
+  double _getCrossAxisExtent(Size childSize) {
+    switch (direction) {
+      case Axis.horizontal:
+        return childSize.height;
+      case Axis.vertical:
+        return childSize.width;
+    }
+  }
+
+  Offset _getOffset(double mainAxisOffset, double crossAxisOffset) {
+    switch (direction) {
+      case Axis.horizontal:
+        return Offset(mainAxisOffset, crossAxisOffset);
+      case Axis.vertical:
+        return Offset(crossAxisOffset, mainAxisOffset);
+    }
+  }
+
+  double _getChildCrossAxisOffset(bool flipCrossAxis, double runCrossAxisExtent, double childCrossAxisExtent) {
+    final double freeSpace = runCrossAxisExtent - childCrossAxisExtent;
+    switch (crossAxisAlignment) {
+      case WrapCrossAlignment.start:
+        return flipCrossAxis ? freeSpace : 0.0;
+      case WrapCrossAlignment.end:
+        return flipCrossAxis ? 0.0 : freeSpace;
+      case WrapCrossAlignment.center:
+        return freeSpace / 2.0;
+    }
+  }
+
+  bool _hasVisualOverflow = false;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _computeDryLayout(constraints);
+  }
+
+  Size _computeDryLayout(BoxConstraints constraints, [ChildLayouter layoutChild = ChildLayoutHelper.dryLayoutChild]) {
+    final BoxConstraints childConstraints;
+    double mainAxisLimit = 0.0;
+    switch (direction) {
+      case Axis.horizontal:
+        childConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
+        mainAxisLimit = constraints.maxWidth;
+        break;
+      case Axis.vertical:
+        childConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
+        mainAxisLimit = constraints.maxHeight;
+        break;
+    }
+
+    double mainAxisExtent = 0.0;
+    double crossAxisExtent = 0.0;
+    double runMainAxisExtent = 0.0;
+    double runCrossAxisExtent = 0.0;
+    int childCount = 0;
+    RenderBox? child = firstChild;
+    while (child != null) {
+      final Size childSize = layoutChild(child, childConstraints);
+      final double childMainAxisExtent = _getMainAxisExtent(childSize);
+      final double childCrossAxisExtent = _getCrossAxisExtent(childSize);
+      // There must be at least one child before we move on to the next run.
+      if (childCount > 0 && runMainAxisExtent + childMainAxisExtent + spacing > mainAxisLimit) {
+        mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
+        crossAxisExtent += runCrossAxisExtent + runSpacing;
+        runMainAxisExtent = 0.0;
+        runCrossAxisExtent = 0.0;
+        childCount = 0;
+      }
+      runMainAxisExtent += childMainAxisExtent;
+      runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
+      if (childCount > 0)
+        runMainAxisExtent += spacing;
+      childCount += 1;
+      child = childAfter(child);
+    }
+    crossAxisExtent += runCrossAxisExtent;
+    mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
+
+    switch (direction) {
+      case Axis.horizontal:
+        return constraints.constrain(Size(mainAxisExtent, crossAxisExtent));
+      case Axis.vertical:
+        return constraints.constrain(Size(crossAxisExtent, mainAxisExtent));
+    }
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    assert(_debugHasNecessaryDirections);
+    _hasVisualOverflow = false;
+    RenderBox? child = firstChild;
+    if (child == null) {
+      size = constraints.smallest;
+      return;
+    }
+    final BoxConstraints childConstraints;
+    double mainAxisLimit = 0.0;
+    bool flipMainAxis = false;
+    bool flipCrossAxis = false;
+    switch (direction) {
+      case Axis.horizontal:
+        childConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
+        mainAxisLimit = constraints.maxWidth;
+        if (textDirection == TextDirection.rtl)
+          flipMainAxis = true;
+        if (verticalDirection == VerticalDirection.up)
+          flipCrossAxis = true;
+        break;
+      case Axis.vertical:
+        childConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
+        mainAxisLimit = constraints.maxHeight;
+        if (verticalDirection == VerticalDirection.up)
+          flipMainAxis = true;
+        if (textDirection == TextDirection.rtl)
+          flipCrossAxis = true;
+        break;
+    }
+    assert(childConstraints != null);
+    assert(mainAxisLimit != null);
+    final double spacing = this.spacing;
+    final double runSpacing = this.runSpacing;
+    final List<_RunMetrics> runMetrics = <_RunMetrics>[];
+    double mainAxisExtent = 0.0;
+    double crossAxisExtent = 0.0;
+    double runMainAxisExtent = 0.0;
+    double runCrossAxisExtent = 0.0;
+    int childCount = 0;
+    while (child != null) {
+      child.layout(childConstraints, parentUsesSize: true);
+      final double childMainAxisExtent = _getMainAxisExtent(child.size);
+      final double childCrossAxisExtent = _getCrossAxisExtent(child.size);
+      if (childCount > 0 && runMainAxisExtent + spacing + childMainAxisExtent > mainAxisLimit) {
+        mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
+        crossAxisExtent += runCrossAxisExtent;
+        if (runMetrics.isNotEmpty)
+          crossAxisExtent += runSpacing;
+        runMetrics.add(_RunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount));
+        runMainAxisExtent = 0.0;
+        runCrossAxisExtent = 0.0;
+        childCount = 0;
+      }
+      runMainAxisExtent += childMainAxisExtent;
+      if (childCount > 0)
+        runMainAxisExtent += spacing;
+      runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
+      childCount += 1;
+      final WrapParentData childParentData = child.parentData! as WrapParentData;
+      childParentData._runIndex = runMetrics.length;
+      child = childParentData.nextSibling;
+    }
+    if (childCount > 0) {
+      mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
+      crossAxisExtent += runCrossAxisExtent;
+      if (runMetrics.isNotEmpty)
+        crossAxisExtent += runSpacing;
+      runMetrics.add(_RunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount));
+    }
+
+    final int runCount = runMetrics.length;
+    assert(runCount > 0);
+
+    double containerMainAxisExtent = 0.0;
+    double containerCrossAxisExtent = 0.0;
+
+    switch (direction) {
+      case Axis.horizontal:
+        size = constraints.constrain(Size(mainAxisExtent, crossAxisExtent));
+        containerMainAxisExtent = size.width;
+        containerCrossAxisExtent = size.height;
+        break;
+      case Axis.vertical:
+        size = constraints.constrain(Size(crossAxisExtent, mainAxisExtent));
+        containerMainAxisExtent = size.height;
+        containerCrossAxisExtent = size.width;
+        break;
+    }
+
+    _hasVisualOverflow = containerMainAxisExtent < mainAxisExtent || containerCrossAxisExtent < crossAxisExtent;
+
+    final double crossAxisFreeSpace = math.max(0.0, containerCrossAxisExtent - crossAxisExtent);
+    double runLeadingSpace = 0.0;
+    double runBetweenSpace = 0.0;
+    switch (runAlignment) {
+      case WrapAlignment.start:
+        break;
+      case WrapAlignment.end:
+        runLeadingSpace = crossAxisFreeSpace;
+        break;
+      case WrapAlignment.center:
+        runLeadingSpace = crossAxisFreeSpace / 2.0;
+        break;
+      case WrapAlignment.spaceBetween:
+        runBetweenSpace = runCount > 1 ? crossAxisFreeSpace / (runCount - 1) : 0.0;
+        break;
+      case WrapAlignment.spaceAround:
+        runBetweenSpace = crossAxisFreeSpace / runCount;
+        runLeadingSpace = runBetweenSpace / 2.0;
+        break;
+      case WrapAlignment.spaceEvenly:
+        runBetweenSpace = crossAxisFreeSpace / (runCount + 1);
+        runLeadingSpace = runBetweenSpace;
+        break;
+    }
+
+    runBetweenSpace += runSpacing;
+    double crossAxisOffset = flipCrossAxis ? containerCrossAxisExtent - runLeadingSpace : runLeadingSpace;
+
+    child = firstChild;
+    for (int i = 0; i < runCount; ++i) {
+      final _RunMetrics metrics = runMetrics[i];
+      final double runMainAxisExtent = metrics.mainAxisExtent;
+      final double runCrossAxisExtent = metrics.crossAxisExtent;
+      final int childCount = metrics.childCount;
+
+      final double mainAxisFreeSpace = math.max(0.0, containerMainAxisExtent - runMainAxisExtent);
+      double childLeadingSpace = 0.0;
+      double childBetweenSpace = 0.0;
+
+      switch (alignment) {
+        case WrapAlignment.start:
+          break;
+        case WrapAlignment.end:
+          childLeadingSpace = mainAxisFreeSpace;
+          break;
+        case WrapAlignment.center:
+          childLeadingSpace = mainAxisFreeSpace / 2.0;
+          break;
+        case WrapAlignment.spaceBetween:
+          childBetweenSpace = childCount > 1 ? mainAxisFreeSpace / (childCount - 1) : 0.0;
+          break;
+        case WrapAlignment.spaceAround:
+          childBetweenSpace = mainAxisFreeSpace / childCount;
+          childLeadingSpace = childBetweenSpace / 2.0;
+          break;
+        case WrapAlignment.spaceEvenly:
+          childBetweenSpace = mainAxisFreeSpace / (childCount + 1);
+          childLeadingSpace = childBetweenSpace;
+          break;
+      }
+
+      childBetweenSpace += spacing;
+      double childMainPosition = flipMainAxis ? containerMainAxisExtent - childLeadingSpace : childLeadingSpace;
+
+      if (flipCrossAxis)
+        crossAxisOffset -= runCrossAxisExtent;
+
+      while (child != null) {
+        final WrapParentData childParentData = child.parentData! as WrapParentData;
+        if (childParentData._runIndex != i)
+          break;
+        final double childMainAxisExtent = _getMainAxisExtent(child.size);
+        final double childCrossAxisExtent = _getCrossAxisExtent(child.size);
+        final double childCrossAxisOffset = _getChildCrossAxisOffset(flipCrossAxis, runCrossAxisExtent, childCrossAxisExtent);
+        if (flipMainAxis)
+          childMainPosition -= childMainAxisExtent;
+        childParentData.offset = _getOffset(childMainPosition, crossAxisOffset + childCrossAxisOffset);
+        if (flipMainAxis)
+          childMainPosition -= childBetweenSpace;
+        else
+          childMainPosition += childMainAxisExtent + childBetweenSpace;
+        child = childParentData.nextSibling;
+      }
+
+      if (flipCrossAxis)
+        crossAxisOffset -= runBetweenSpace;
+      else
+        crossAxisOffset += runCrossAxisExtent + runBetweenSpace;
+    }
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    return defaultHitTestChildren(result, position: position);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    // TODO(ianh): move the debug flex overflow paint logic somewhere common so
+    // it can be reused here
+    if (_hasVisualOverflow && clipBehavior != Clip.none) {
+      _clipRectLayer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint,
+          clipBehavior: clipBehavior, oldLayer: _clipRectLayer);
+    } else {
+      _clipRectLayer = null;
+      defaultPaint(context, offset);
+    }
+  }
+
+  ClipRectLayer? _clipRectLayer;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<Axis>('direction', direction));
+    properties.add(EnumProperty<WrapAlignment>('alignment', alignment));
+    properties.add(DoubleProperty('spacing', spacing));
+    properties.add(EnumProperty<WrapAlignment>('runAlignment', runAlignment));
+    properties.add(DoubleProperty('runSpacing', runSpacing));
+    properties.add(DoubleProperty('crossAxisAlignment', runSpacing));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    properties.add(EnumProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: VerticalDirection.down));
+  }
+}
diff --git a/lib/src/scheduler/binding.dart b/lib/src/scheduler/binding.dart
new file mode 100644
index 0000000..660511a
--- /dev/null
+++ b/lib/src/scheduler/binding.dart
@@ -0,0 +1,1177 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:developer' show Flow, Timeline;
+import 'package:flute/ui.dart' show AppLifecycleState, FramePhase, FrameTiming, TimingsCallback, PlatformDispatcher;
+
+import 'package:collection/collection.dart' show PriorityQueue, HeapPriorityQueue;
+import 'package:flute/foundation.dart';
+
+import 'debug.dart';
+import 'priority.dart';
+
+export 'package:flute/ui.dart' show AppLifecycleState, VoidCallback, FrameTiming;
+
+/// Slows down animations by this factor to help in development.
+double get timeDilation => _timeDilation;
+double _timeDilation = 1.0;
+/// Setting the time dilation automatically calls [SchedulerBinding.resetEpoch]
+/// to ensure that time stamps seen by consumers of the scheduler binding are
+/// always increasing.
+set timeDilation(double value) {
+  assert(value > 0.0);
+  if (_timeDilation == value)
+    return;
+  // We need to resetEpoch first so that we capture start of the epoch with the
+  // current time dilation.
+  SchedulerBinding.instance?.resetEpoch();
+  _timeDilation = value;
+}
+
+/// Signature for frame-related callbacks from the scheduler.
+///
+/// The `timeStamp` is the number of milliseconds since the beginning of the
+/// scheduler's epoch. Use timeStamp to determine how far to advance animation
+/// timelines so that all the animations in the system are synchronized to a
+/// common time base.
+typedef FrameCallback = void Function(Duration timeStamp);
+
+/// Signature for [SchedulerBinding.scheduleTask] callbacks.
+///
+/// The type argument `T` is the task's return value. Consider `void` if the
+/// task does not return a value.
+typedef TaskCallback<T> = T Function();
+
+/// Signature for the [SchedulerBinding.schedulingStrategy] callback. Called
+/// whenever the system needs to decide whether a task at a given
+/// priority needs to be run.
+///
+/// Return true if a task with the given priority should be executed at this
+/// time, false otherwise.
+///
+/// See also:
+///
+///  * [defaultSchedulingStrategy], the default [SchedulingStrategy] for [SchedulerBinding.schedulingStrategy].
+typedef SchedulingStrategy = bool Function({ required int priority, required SchedulerBinding scheduler });
+
+class _TaskEntry<T> {
+  _TaskEntry(this.task, this.priority, this.debugLabel, this.flow) {
+    assert(() {
+      debugStack = StackTrace.current;
+      return true;
+    }());
+  }
+  final TaskCallback<T> task;
+  final int priority;
+  final String? debugLabel;
+  final Flow? flow;
+
+  late StackTrace debugStack;
+  final Completer<T> completer = Completer<T>();
+
+  void run() {
+    if (!kReleaseMode) {
+      Timeline.timeSync(
+        debugLabel ?? 'Scheduled Task',
+        () {
+          completer.complete(task());
+        },
+        flow: flow != null ? Flow.step(flow!.id) : null,
+      );
+    } else {
+      completer.complete(task());
+    }
+  }
+}
+
+class _FrameCallbackEntry {
+  _FrameCallbackEntry(this.callback, { bool rescheduling = false }) {
+    assert(() {
+      if (rescheduling) {
+        assert(() {
+          if (debugCurrentCallbackStack == null) {
+            throw FlutterError.fromParts(<DiagnosticsNode>[
+              ErrorSummary('scheduleFrameCallback called with rescheduling true, but no callback is in scope.'),
+              ErrorDescription(
+                'The "rescheduling" argument should only be set to true if the '
+                'callback is being reregistered from within the callback itself, '
+                'and only then if the callback itself is entirely synchronous.'
+              ),
+              ErrorHint(
+                'If this is the initial registration of the callback, or if the '
+                'callback is asynchronous, then do not use the "rescheduling" '
+                'argument.'
+              )
+            ]);
+          }
+          return true;
+        }());
+        debugStack = debugCurrentCallbackStack;
+      } else {
+        // TODO(ianh): trim the frames from this library, so that the call to scheduleFrameCallback is the top one
+        debugStack = StackTrace.current;
+      }
+      return true;
+    }());
+  }
+
+  final FrameCallback callback;
+
+  static StackTrace? debugCurrentCallbackStack;
+  StackTrace? debugStack;
+}
+
+/// The various phases that a [SchedulerBinding] goes through during
+/// [SchedulerBinding.handleBeginFrame].
+///
+/// This is exposed by [SchedulerBinding.schedulerPhase].
+///
+/// The values of this enum are ordered in the same order as the phases occur,
+/// so their relative index values can be compared to each other.
+///
+/// See also:
+///
+///  * [WidgetsBinding.drawFrame], which pumps the build and rendering pipeline
+///    to generate a frame.
+enum SchedulerPhase {
+  /// No frame is being processed. Tasks (scheduled by
+  /// [SchedulerBinding.scheduleTask]), microtasks (scheduled by
+  /// [scheduleMicrotask]), [Timer] callbacks, event handlers (e.g. from user
+  /// input), and other callbacks (e.g. from [Future]s, [Stream]s, and the like)
+  /// may be executing.
+  idle,
+
+  /// The transient callbacks (scheduled by
+  /// [SchedulerBinding.scheduleFrameCallback]) are currently executing.
+  ///
+  /// Typically, these callbacks handle updating objects to new animation
+  /// states.
+  ///
+  /// See [SchedulerBinding.handleBeginFrame].
+  transientCallbacks,
+
+  /// Microtasks scheduled during the processing of transient callbacks are
+  /// current executing.
+  ///
+  /// This may include, for instance, callbacks from futures resolved during the
+  /// [transientCallbacks] phase.
+  midFrameMicrotasks,
+
+  /// The persistent callbacks (scheduled by
+  /// [SchedulerBinding.addPersistentFrameCallback]) are currently executing.
+  ///
+  /// Typically, this is the build/layout/paint pipeline. See
+  /// [WidgetsBinding.drawFrame] and [SchedulerBinding.handleDrawFrame].
+  persistentCallbacks,
+
+  /// The post-frame callbacks (scheduled by
+  /// [SchedulerBinding.addPostFrameCallback]) are currently executing.
+  ///
+  /// Typically, these callbacks handle cleanup and scheduling of work for the
+  /// next frame.
+  ///
+  /// See [SchedulerBinding.handleDrawFrame].
+  postFrameCallbacks,
+}
+
+/// Scheduler for running the following:
+///
+/// * _Transient callbacks_, triggered by the system's
+///   [dart:ui.PlatformDispatcher.onBeginFrame] callback, for synchronizing the
+///   application's behavior to the system's display. For example, [Ticker]s and
+///   [AnimationController]s trigger from these.
+///
+/// * _Persistent callbacks_, triggered by the system's
+///   [dart:ui.PlatformDispatcher.onDrawFrame] callback, for updating the
+///   system's display after transient callbacks have executed. For example, the
+///   rendering layer uses this to drive its rendering pipeline.
+///
+/// * _Post-frame callbacks_, which are run after persistent callbacks, just
+///   before returning from the [dart:ui.PlatformDispatcher.onDrawFrame] callback.
+///
+/// * Non-rendering tasks, to be run between frames. These are given a
+///   priority and are executed in priority order according to a
+///   [schedulingStrategy].
+mixin SchedulerBinding on BindingBase {
+  @override
+  void initInstances() {
+    super.initInstances();
+    _instance = this;
+
+    if (!kReleaseMode) {
+      int frameNumber = 0;
+      addTimingsCallback((List<FrameTiming> timings) {
+        for (final FrameTiming frameTiming in timings) {
+          frameNumber += 1;
+          _profileFramePostEvent(frameNumber, frameTiming);
+        }
+      });
+    }
+  }
+
+  final List<TimingsCallback> _timingsCallbacks = <TimingsCallback>[];
+
+  /// Add a [TimingsCallback] that receives [FrameTiming] sent from
+  /// the engine.
+  ///
+  /// This API enables applications to monitor their graphics
+  /// performance. Data from the engine is batched into lists of
+  /// [FrameTiming] objects which are reported approximately once a
+  /// second in release mode and approximately once every 100ms in
+  /// debug and profile builds. The list is sorted in ascending
+  /// chronological order (earliest frame first). The timing of the
+  /// first frame is sent immediately without batching.
+  ///
+  /// The data returned can be used to catch missed frames (by seeing
+  /// if [FrameTiming.buildDuration] or [FrameTiming.rasterDuration]
+  /// exceed the frame budget, e.g. 16ms at 60Hz), and to catch high
+  /// latency (by seeing if [FrameTiming.totalSpan] exceeds the frame
+  /// budget). It is possible for no frames to be missed but for the
+  /// latency to be more than one frame in the case where the Flutter
+  /// engine is pipelining the graphics updates, e.g. because the sum
+  /// of the [FrameTiming.buildDuration] and the
+  /// [FrameTiming.rasterDuration] together exceed the frame budget.
+  /// In those cases, animations will be smooth but touch input will
+  /// feel more sluggish.
+  ///
+  /// Using [addTimingsCallback] is preferred over using
+  /// [dart:ui.PlatformDispatcher.onReportTimings] directly because the
+  /// [dart:ui.PlatformDispatcher.onReportTimings] API only allows one callback,
+  /// which prevents multiple libraries from registering listeners
+  /// simultaneously, while this API allows multiple callbacks to be registered
+  /// independently.
+  ///
+  /// This API is implemented in terms of
+  /// [dart:ui.PlatformDispatcher.onReportTimings]. In release builds, when no
+  /// libraries have registered with this API, the
+  /// [dart:ui.PlatformDispatcher.onReportTimings] callback is not set, which
+  /// disables the performance tracking and reduces the runtime overhead to
+  /// approximately zero. The performance overhead of the performance tracking
+  /// when one or more callbacks are registered (i.e. when it is enabled) is
+  /// very approximately 0.01% CPU usage per second (measured on an iPhone 6s).
+  ///
+  /// In debug and profile builds, the [SchedulerBinding] itself
+  /// registers a timings callback to update the [Timeline].
+  ///
+  /// If the same callback is added twice, it will be executed twice.
+  ///
+  /// See also:
+  ///
+  ///  * [removeTimingsCallback], which can be used to remove a callback
+  ///    added using this method.
+  void addTimingsCallback(TimingsCallback callback) {
+    _timingsCallbacks.add(callback);
+    if (_timingsCallbacks.length == 1) {
+      assert(window.onReportTimings == null);
+      window.onReportTimings = _executeTimingsCallbacks;
+    }
+    assert(window.onReportTimings == _executeTimingsCallbacks);
+  }
+
+  /// Removes a callback that was earlier added by [addTimingsCallback].
+  void removeTimingsCallback(TimingsCallback callback) {
+    assert(_timingsCallbacks.contains(callback));
+    _timingsCallbacks.remove(callback);
+    if (_timingsCallbacks.isEmpty) {
+      window.onReportTimings = null;
+    }
+  }
+
+  void _executeTimingsCallbacks(List<FrameTiming> timings) {
+    final List<TimingsCallback> clonedCallbacks =
+        List<TimingsCallback>.from(_timingsCallbacks);
+    for (final TimingsCallback callback in clonedCallbacks) {
+      try {
+        if (_timingsCallbacks.contains(callback)) {
+          callback(timings);
+        }
+      } catch (exception, stack) {
+        InformationCollector? collector;
+        assert(() {
+          collector = () sync* {
+            yield DiagnosticsProperty<TimingsCallback>(
+              'The TimingsCallback that gets executed was',
+              callback,
+              style: DiagnosticsTreeStyle.errorProperty,
+            );
+          };
+          return true;
+        }());
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          context: ErrorDescription('while executing callbacks for FrameTiming'),
+          informationCollector: collector
+        ));
+      }
+    }
+  }
+
+  /// The current [SchedulerBinding], if one has been created.
+  static SchedulerBinding? get instance => _instance;
+  static SchedulerBinding? _instance;
+
+  @override
+  void initServiceExtensions() {
+    super.initServiceExtensions();
+
+    if (!kReleaseMode) {
+      registerNumericServiceExtension(
+        name: 'timeDilation',
+        getter: () async => timeDilation,
+        setter: (double value) async {
+          timeDilation = value;
+        },
+      );
+    }
+  }
+
+  /// Whether the application is visible, and if so, whether it is currently
+  /// interactive.
+  ///
+  /// This is set by [handleAppLifecycleStateChanged] when the
+  /// [SystemChannels.lifecycle] notification is dispatched.
+  ///
+  /// The preferred way to watch for changes to this value is using
+  /// [WidgetsBindingObserver.didChangeAppLifecycleState].
+  AppLifecycleState? get lifecycleState => _lifecycleState;
+  AppLifecycleState? _lifecycleState;
+
+  /// Called when the application lifecycle state changes.
+  ///
+  /// Notifies all the observers using
+  /// [WidgetsBindingObserver.didChangeAppLifecycleState].
+  ///
+  /// This method exposes notifications from [SystemChannels.lifecycle].
+  @protected
+  @mustCallSuper
+  void handleAppLifecycleStateChanged(AppLifecycleState state) {
+    assert(state != null);
+    _lifecycleState = state;
+    switch (state) {
+      case AppLifecycleState.resumed:
+      case AppLifecycleState.inactive:
+        _setFramesEnabledState(true);
+        break;
+      case AppLifecycleState.paused:
+      case AppLifecycleState.detached:
+        _setFramesEnabledState(false);
+        break;
+    }
+  }
+
+  /// The strategy to use when deciding whether to run a task or not.
+  ///
+  /// Defaults to [defaultSchedulingStrategy].
+  SchedulingStrategy schedulingStrategy = defaultSchedulingStrategy;
+
+  static int _taskSorter (_TaskEntry<dynamic> e1, _TaskEntry<dynamic> e2) {
+    return -e1.priority.compareTo(e2.priority);
+  }
+  final PriorityQueue<_TaskEntry<dynamic>> _taskQueue = HeapPriorityQueue<_TaskEntry<dynamic>>(_taskSorter);
+
+  /// Schedules the given `task` with the given `priority` and returns a
+  /// [Future] that completes to the `task`'s eventual return value.
+  ///
+  /// The `debugLabel` and `flow` are used to report the task to the [Timeline],
+  /// for use when profiling.
+  ///
+  /// ## Processing model
+  ///
+  /// Tasks will be executed between frames, in priority order,
+  /// excluding tasks that are skipped by the current
+  /// [schedulingStrategy]. Tasks should be short (as in, up to a
+  /// millisecond), so as to not cause the regular frame callbacks to
+  /// get delayed.
+  ///
+  /// If an animation is running, including, for instance, a [ProgressIndicator]
+  /// indicating that there are pending tasks, then tasks with a priority below
+  /// [Priority.animation] won't run (at least, not with the
+  /// [defaultSchedulingStrategy]; this can be configured using
+  /// [schedulingStrategy]).
+  Future<T> scheduleTask<T>(
+    TaskCallback<T> task,
+    Priority priority, {
+    String? debugLabel,
+    Flow? flow,
+  }) {
+    final bool isFirstTask = _taskQueue.isEmpty;
+    final _TaskEntry<T> entry = _TaskEntry<T>(
+      task,
+      priority.value,
+      debugLabel,
+      flow,
+    );
+    _taskQueue.add(entry);
+    if (isFirstTask && !locked)
+      _ensureEventLoopCallback();
+    return entry.completer.future;
+  }
+
+  @override
+  void unlocked() {
+    super.unlocked();
+    if (_taskQueue.isNotEmpty)
+      _ensureEventLoopCallback();
+  }
+
+  // Whether this scheduler already requested to be called from the event loop.
+  bool _hasRequestedAnEventLoopCallback = false;
+
+  // Ensures that the scheduler services a task scheduled by
+  // [SchedulerBinding.scheduleTask].
+  void _ensureEventLoopCallback() {
+    assert(!locked);
+    assert(_taskQueue.isNotEmpty);
+    if (_hasRequestedAnEventLoopCallback)
+      return;
+    _hasRequestedAnEventLoopCallback = true;
+    Timer.run(_runTasks);
+  }
+
+  // Scheduled by _ensureEventLoopCallback.
+  void _runTasks() {
+    _hasRequestedAnEventLoopCallback = false;
+    if (handleEventLoopCallback())
+      _ensureEventLoopCallback(); // runs next task when there's time
+  }
+
+  /// Execute the highest-priority task, if it is of a high enough priority.
+  ///
+  /// Returns true if a task was executed and there are other tasks remaining
+  /// (even if they are not high-enough priority).
+  ///
+  /// Returns false if no task was executed, which can occur if there are no
+  /// tasks scheduled, if the scheduler is [locked], or if the highest-priority
+  /// task is of too low a priority given the current [schedulingStrategy].
+  ///
+  /// Also returns false if there are no tasks remaining.
+  @visibleForTesting
+  bool handleEventLoopCallback() {
+    if (_taskQueue.isEmpty || locked)
+      return false;
+    final _TaskEntry<dynamic> entry = _taskQueue.first;
+    if (schedulingStrategy(priority: entry.priority, scheduler: this)) {
+      try {
+        _taskQueue.removeFirst();
+        entry.run();
+      } catch (exception, exceptionStack) {
+        StackTrace? callbackStack;
+        assert(() {
+          callbackStack = entry.debugStack;
+          return true;
+        }());
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: exceptionStack,
+          library: 'scheduler library',
+          context: ErrorDescription('during a task callback'),
+          informationCollector: (callbackStack == null) ? null : () sync* {
+            yield DiagnosticsStackTrace(
+              '\nThis exception was thrown in the context of a scheduler callback. '
+              'When the scheduler callback was _registered_ (as opposed to when the '
+              'exception was thrown), this was the stack',
+              callbackStack,
+            );
+          },
+        ));
+      }
+      return _taskQueue.isNotEmpty;
+    }
+    return false;
+  }
+
+  int _nextFrameCallbackId = 0; // positive
+  Map<int, _FrameCallbackEntry> _transientCallbacks = <int, _FrameCallbackEntry>{};
+  final Set<int> _removedIds = HashSet<int>();
+
+  /// The current number of transient frame callbacks scheduled.
+  ///
+  /// This is reset to zero just before all the currently scheduled
+  /// transient callbacks are called, at the start of a frame.
+  ///
+  /// This number is primarily exposed so that tests can verify that
+  /// there are no unexpected transient callbacks still registered
+  /// after a test's resources have been gracefully disposed.
+  int get transientCallbackCount => _transientCallbacks.length;
+
+  /// Schedules the given transient frame callback.
+  ///
+  /// Adds the given callback to the list of frame callbacks and ensures that a
+  /// frame is scheduled.
+  ///
+  /// If this is a one-off registration, ignore the `rescheduling` argument.
+  ///
+  /// If this is a callback that will be re-registered each time it fires, then
+  /// when you re-register the callback, set the `rescheduling` argument to
+  /// true. This has no effect in release builds, but in debug builds, it
+  /// ensures that the stack trace that is stored for this callback is the
+  /// original stack trace for when the callback was _first_ registered, rather
+  /// than the stack trace for when the callback is re-registered. This makes it
+  /// easier to track down the original reason that a particular callback was
+  /// called. If `rescheduling` is true, the call must be in the context of a
+  /// frame callback.
+  ///
+  /// Callbacks registered with this method can be canceled using
+  /// [cancelFrameCallbackWithId].
+  int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
+    scheduleFrame();
+    _nextFrameCallbackId += 1;
+    _transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
+    return _nextFrameCallbackId;
+  }
+
+  /// Cancels the transient frame callback with the given [id].
+  ///
+  /// Removes the given callback from the list of frame callbacks. If a frame
+  /// has been requested, this does not also cancel that request.
+  ///
+  /// Transient frame callbacks are those registered using
+  /// [scheduleFrameCallback].
+  void cancelFrameCallbackWithId(int id) {
+    assert(id > 0);
+    _transientCallbacks.remove(id);
+    _removedIds.add(id);
+  }
+
+  /// Asserts that there are no registered transient callbacks; if
+  /// there are, prints their locations and throws an exception.
+  ///
+  /// A transient frame callback is one that was registered with
+  /// [scheduleFrameCallback].
+  ///
+  /// This is expected to be called at the end of tests (the
+  /// flutter_test framework does it automatically in normal cases).
+  ///
+  /// Call this method when you expect there to be no transient
+  /// callbacks registered, in an assert statement with a message that
+  /// you want printed when a transient callback is registered:
+  ///
+  /// ```dart
+  /// assert(SchedulerBinding.instance.debugAssertNoTransientCallbacks(
+  ///   'A leak of transient callbacks was detected while doing foo.'
+  /// ));
+  /// ```
+  ///
+  /// Does nothing if asserts are disabled. Always returns true.
+  bool debugAssertNoTransientCallbacks(String reason) {
+    assert(() {
+      if (transientCallbackCount > 0) {
+        // We cache the values so that we can produce them later
+        // even if the information collector is called after
+        // the problem has been resolved.
+        final int count = transientCallbackCount;
+        final Map<int, _FrameCallbackEntry> callbacks = Map<int, _FrameCallbackEntry>.from(_transientCallbacks);
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: reason,
+          library: 'scheduler library',
+          informationCollector: () sync* {
+            if (count == 1) {
+              // TODO(jacobr): I have added an extra line break in this case.
+              yield ErrorDescription(
+                'There was one transient callback left. '
+                'The stack trace for when it was registered is as follows:'
+              );
+            } else {
+              yield ErrorDescription(
+                'There were $count transient callbacks left. '
+                'The stack traces for when they were registered are as follows:'
+              );
+            }
+            for (final int id in callbacks.keys) {
+              final _FrameCallbackEntry entry = callbacks[id]!;
+              yield DiagnosticsStackTrace('── callback $id ──', entry.debugStack, showSeparator: false);
+            }
+          },
+        ));
+      }
+      return true;
+    }());
+    return true;
+  }
+
+  /// Prints the stack for where the current transient callback was registered.
+  ///
+  /// A transient frame callback is one that was registered with
+  /// [scheduleFrameCallback].
+  ///
+  /// When called in debug more and in the context of a transient callback, this
+  /// function prints the stack trace from where the current transient callback
+  /// was registered (i.e. where it first called [scheduleFrameCallback]).
+  ///
+  /// When called in debug mode in other contexts, it prints a message saying
+  /// that this function was not called in the context a transient callback.
+  ///
+  /// In release mode, this function does nothing.
+  ///
+  /// To call this function, use the following code:
+  ///
+  /// ```dart
+  /// SchedulerBinding.debugPrintTransientCallbackRegistrationStack();
+  /// ```
+  static void debugPrintTransientCallbackRegistrationStack() {
+    assert(() {
+      if (_FrameCallbackEntry.debugCurrentCallbackStack != null) {
+        debugPrint('When the current transient callback was registered, this was the stack:');
+        debugPrint(
+          FlutterError.defaultStackFilter(
+            FlutterError.demangleStackTrace(
+              _FrameCallbackEntry.debugCurrentCallbackStack!,
+            ).toString().trimRight().split('\n')
+          ).join('\n')
+        );
+      } else {
+        debugPrint('No transient callback is currently executing.');
+      }
+      return true;
+    }());
+  }
+
+  final List<FrameCallback> _persistentCallbacks = <FrameCallback>[];
+
+  /// Adds a persistent frame callback.
+  ///
+  /// Persistent callbacks are called after transient
+  /// (non-persistent) frame callbacks.
+  ///
+  /// Does *not* request a new frame. Conceptually, persistent frame
+  /// callbacks are observers of "begin frame" events. Since they are
+  /// executed after the transient frame callbacks they can drive the
+  /// rendering pipeline.
+  ///
+  /// Persistent frame callbacks cannot be unregistered. Once registered, they
+  /// are called for every frame for the lifetime of the application.
+  void addPersistentFrameCallback(FrameCallback callback) {
+    _persistentCallbacks.add(callback);
+  }
+
+  final List<FrameCallback> _postFrameCallbacks = <FrameCallback>[];
+
+  /// Schedule a callback for the end of this frame.
+  ///
+  /// Does *not* request a new frame.
+  ///
+  /// This callback is run during a frame, just after the persistent
+  /// frame callbacks (which is when the main rendering pipeline has
+  /// been flushed). If a frame is in progress and post-frame
+  /// callbacks haven't been executed yet, then the registered
+  /// callback is still executed during the frame. Otherwise, the
+  /// registered callback is executed during the next frame.
+  ///
+  /// The callbacks are executed in the order in which they have been
+  /// added.
+  ///
+  /// Post-frame callbacks cannot be unregistered. They are called exactly once.
+  ///
+  /// See also:
+  ///
+  ///  * [scheduleFrameCallback], which registers a callback for the start of
+  ///    the next frame.
+  void addPostFrameCallback(FrameCallback callback) {
+    _postFrameCallbacks.add(callback);
+  }
+
+  Completer<void>? _nextFrameCompleter;
+
+  /// Returns a Future that completes after the frame completes.
+  ///
+  /// If this is called between frames, a frame is immediately scheduled if
+  /// necessary. If this is called during a frame, the Future completes after
+  /// the current frame.
+  ///
+  /// If the device's screen is currently turned off, this may wait a very long
+  /// time, since frames are not scheduled while the device's screen is turned
+  /// off.
+  Future<void> get endOfFrame {
+    if (_nextFrameCompleter == null) {
+      if (schedulerPhase == SchedulerPhase.idle)
+        scheduleFrame();
+      _nextFrameCompleter = Completer<void>();
+      addPostFrameCallback((Duration timeStamp) {
+        _nextFrameCompleter!.complete();
+        _nextFrameCompleter = null;
+      });
+    }
+    return _nextFrameCompleter!.future;
+  }
+
+  /// Whether this scheduler has requested that [handleBeginFrame] be called soon.
+  bool get hasScheduledFrame => _hasScheduledFrame;
+  bool _hasScheduledFrame = false;
+
+  /// The phase that the scheduler is currently operating under.
+  SchedulerPhase get schedulerPhase => _schedulerPhase;
+  SchedulerPhase _schedulerPhase = SchedulerPhase.idle;
+
+  /// Whether frames are currently being scheduled when [scheduleFrame] is called.
+  ///
+  /// This value depends on the value of the [lifecycleState].
+  bool get framesEnabled => _framesEnabled;
+
+  bool _framesEnabled = true;
+  void _setFramesEnabledState(bool enabled) {
+    if (_framesEnabled == enabled)
+      return;
+    _framesEnabled = enabled;
+    if (enabled)
+      scheduleFrame();
+  }
+
+  /// Ensures callbacks for [PlatformDispatcher.onBeginFrame] and
+  /// [PlatformDispatcher.onDrawFrame] are registered.
+  @protected
+  void ensureFrameCallbacksRegistered() {
+    window.onBeginFrame ??= _handleBeginFrame;
+    window.onDrawFrame ??= _handleDrawFrame;
+  }
+
+  /// Schedules a new frame using [scheduleFrame] if this object is not
+  /// currently producing a frame.
+  ///
+  /// Calling this method ensures that [handleDrawFrame] will eventually be
+  /// called, unless it's already in progress.
+  ///
+  /// This has no effect if [schedulerPhase] is
+  /// [SchedulerPhase.transientCallbacks] or [SchedulerPhase.midFrameMicrotasks]
+  /// (because a frame is already being prepared in that case), or
+  /// [SchedulerPhase.persistentCallbacks] (because a frame is actively being
+  /// rendered in that case). It will schedule a frame if the [schedulerPhase]
+  /// is [SchedulerPhase.idle] (in between frames) or
+  /// [SchedulerPhase.postFrameCallbacks] (after a frame).
+  void ensureVisualUpdate() {
+    switch (schedulerPhase) {
+      case SchedulerPhase.idle:
+      case SchedulerPhase.postFrameCallbacks:
+        scheduleFrame();
+        return;
+      case SchedulerPhase.transientCallbacks:
+      case SchedulerPhase.midFrameMicrotasks:
+      case SchedulerPhase.persistentCallbacks:
+        return;
+    }
+  }
+
+  /// If necessary, schedules a new frame by calling
+  /// [dart:ui.PlatformDispatcher.scheduleFrame].
+  ///
+  /// After this is called, the engine will (eventually) call
+  /// [handleBeginFrame]. (This call might be delayed, e.g. if the device's
+  /// screen is turned off it will typically be delayed until the screen is on
+  /// and the application is visible.) Calling this during a frame forces
+  /// another frame to be scheduled, even if the current frame has not yet
+  /// completed.
+  ///
+  /// Scheduled frames are serviced when triggered by a "Vsync" signal provided
+  /// by the operating system. The "Vsync" signal, or vertical synchronization
+  /// signal, was historically related to the display refresh, at a time when
+  /// hardware physically moved a beam of electrons vertically between updates
+  /// of the display. The operation of contemporary hardware is somewhat more
+  /// subtle and complicated, but the conceptual "Vsync" refresh signal continue
+  /// to be used to indicate when applications should update their rendering.
+  ///
+  /// To have a stack trace printed to the console any time this function
+  /// schedules a frame, set [debugPrintScheduleFrameStacks] to true.
+  ///
+  /// See also:
+  ///
+  ///  * [scheduleForcedFrame], which ignores the [lifecycleState] when
+  ///    scheduling a frame.
+  ///  * [scheduleWarmUpFrame], which ignores the "Vsync" signal entirely and
+  ///    triggers a frame immediately.
+  void scheduleFrame() {
+    if (_hasScheduledFrame || !framesEnabled)
+      return;
+    assert(() {
+      if (debugPrintScheduleFrameStacks)
+        debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
+      return true;
+    }());
+    ensureFrameCallbacksRegistered();
+    window.scheduleFrame();
+    _hasScheduledFrame = true;
+  }
+
+  /// Schedules a new frame by calling
+  /// [dart:ui.PlatformDispatcher.scheduleFrame].
+  ///
+  /// After this is called, the engine will call [handleBeginFrame], even if
+  /// frames would normally not be scheduled by [scheduleFrame] (e.g. even if
+  /// the device's screen is turned off).
+  ///
+  /// The framework uses this to force a frame to be rendered at the correct
+  /// size when the phone is rotated, so that a correctly-sized rendering is
+  /// available when the screen is turned back on.
+  ///
+  /// To have a stack trace printed to the console any time this function
+  /// schedules a frame, set [debugPrintScheduleFrameStacks] to true.
+  ///
+  /// Prefer using [scheduleFrame] unless it is imperative that a frame be
+  /// scheduled immediately, since using [scheduleForcedFrame] will cause
+  /// significantly higher battery usage when the device should be idle.
+  ///
+  /// Consider using [scheduleWarmUpFrame] instead if the goal is to update the
+  /// rendering as soon as possible (e.g. at application startup).
+  void scheduleForcedFrame() {
+    // TODO(chunhtai): Removes the if case once the issue is fixed
+    // https://github.com/flutter/flutter/issues/45131
+    if (!framesEnabled)
+      return;
+
+    if (_hasScheduledFrame)
+      return;
+    assert(() {
+      if (debugPrintScheduleFrameStacks)
+        debugPrintStack(label: 'scheduleForcedFrame() called. Current phase is $schedulerPhase.');
+      return true;
+    }());
+    window.scheduleFrame();
+    _hasScheduledFrame = true;
+  }
+
+  bool _warmUpFrame = false;
+
+  /// Schedule a frame to run as soon as possible, rather than waiting for
+  /// the engine to request a frame in response to a system "Vsync" signal.
+  ///
+  /// This is used during application startup so that the first frame (which is
+  /// likely to be quite expensive) gets a few extra milliseconds to run.
+  ///
+  /// Locks events dispatching until the scheduled frame has completed.
+  ///
+  /// If a frame has already been scheduled with [scheduleFrame] or
+  /// [scheduleForcedFrame], this call may delay that frame.
+  ///
+  /// If any scheduled frame has already begun or if another
+  /// [scheduleWarmUpFrame] was already called, this call will be ignored.
+  ///
+  /// Prefer [scheduleFrame] to update the display in normal operation.
+  void scheduleWarmUpFrame() {
+    if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
+      return;
+
+    _warmUpFrame = true;
+    Timeline.startSync('Warm-up frame');
+    final bool hadScheduledFrame = _hasScheduledFrame;
+    // We use timers here to ensure that microtasks flush in between.
+    Timer.run(() {
+      assert(_warmUpFrame);
+      handleBeginFrame(null);
+    });
+    Timer.run(() {
+      assert(_warmUpFrame);
+      handleDrawFrame();
+      // We call resetEpoch after this frame so that, in the hot reload case,
+      // the very next frame pretends to have occurred immediately after this
+      // warm-up frame. The warm-up frame's timestamp will typically be far in
+      // the past (the time of the last real frame), so if we didn't reset the
+      // epoch we would see a sudden jump from the old time in the warm-up frame
+      // to the new time in the "real" frame. The biggest problem with this is
+      // that implicit animations end up being triggered at the old time and
+      // then skipping every frame and finishing in the new time.
+      resetEpoch();
+      _warmUpFrame = false;
+      if (hadScheduledFrame)
+        scheduleFrame();
+    });
+
+    // Lock events so touch events etc don't insert themselves until the
+    // scheduled frame has finished.
+    lockEvents(() async {
+      await endOfFrame;
+      Timeline.finishSync();
+    });
+  }
+
+  Duration? _firstRawTimeStampInEpoch;
+  Duration _epochStart = Duration.zero;
+  Duration _lastRawTimeStamp = Duration.zero;
+
+  /// Prepares the scheduler for a non-monotonic change to how time stamps are
+  /// calculated.
+  ///
+  /// Callbacks received from the scheduler assume that their time stamps are
+  /// monotonically increasing. The raw time stamp passed to [handleBeginFrame]
+  /// is monotonic, but the scheduler might adjust those time stamps to provide
+  /// [timeDilation]. Without careful handling, these adjusts could cause time
+  /// to appear to run backwards.
+  ///
+  /// The [resetEpoch] function ensures that the time stamps are monotonic by
+  /// resetting the base time stamp used for future time stamp adjustments to the
+  /// current value. For example, if the [timeDilation] decreases, rather than
+  /// scaling down the [Duration] since the beginning of time, [resetEpoch] will
+  /// ensure that we only scale down the duration since [resetEpoch] was called.
+  ///
+  /// Setting [timeDilation] calls [resetEpoch] automatically. You don't need to
+  /// call [resetEpoch] yourself.
+  void resetEpoch() {
+    _epochStart = _adjustForEpoch(_lastRawTimeStamp);
+    _firstRawTimeStampInEpoch = null;
+  }
+
+  /// Adjusts the given time stamp into the current epoch.
+  ///
+  /// This both offsets the time stamp to account for when the epoch started
+  /// (both in raw time and in the epoch's own time line) and scales the time
+  /// stamp to reflect the time dilation in the current epoch.
+  ///
+  /// These mechanisms together combine to ensure that the durations we give
+  /// during frame callbacks are monotonically increasing.
+  Duration _adjustForEpoch(Duration rawTimeStamp) {
+    final Duration rawDurationSinceEpoch = _firstRawTimeStampInEpoch == null ? Duration.zero : rawTimeStamp - _firstRawTimeStampInEpoch!;
+    return Duration(microseconds: (rawDurationSinceEpoch.inMicroseconds / timeDilation).round() + _epochStart.inMicroseconds);
+  }
+
+  /// The time stamp for the frame currently being processed.
+  ///
+  /// This is only valid while between the start of [handleBeginFrame] and the
+  /// end of the corresponding [handleDrawFrame], i.e. while a frame is being
+  /// produced.
+  Duration get currentFrameTimeStamp {
+    assert(_currentFrameTimeStamp != null);
+    return _currentFrameTimeStamp!;
+  }
+  Duration? _currentFrameTimeStamp;
+
+  /// The raw time stamp as provided by the engine to
+  /// [dart:ui.PlatformDispatcher.onBeginFrame] for the frame currently being
+  /// processed.
+  ///
+  /// Unlike [currentFrameTimeStamp], this time stamp is neither adjusted to
+  /// offset when the epoch started nor scaled to reflect the [timeDilation] in
+  /// the current epoch.
+  ///
+  /// On most platforms, this is a more or less arbitrary value, and should
+  /// generally be ignored. On Fuchsia, this corresponds to the system-provided
+  /// presentation time, and can be used to ensure that animations running in
+  /// different processes are synchronized.
+  Duration get currentSystemFrameTimeStamp {
+    assert(_lastRawTimeStamp != null);
+    return _lastRawTimeStamp;
+  }
+
+  int _debugFrameNumber = 0;
+  String? _debugBanner;
+
+  // Whether the current engine frame needs to be postponed till after the
+  // warm-up frame.
+  //
+  // Engine may begin a frame in the middle of the warm-up frame because the
+  // warm-up frame is scheduled by timers while the engine frame is scheduled
+  // by platform specific frame scheduler (e.g. `requestAnimationFrame` on the
+  // web). When this happens, we let the warm-up frame finish, and postpone the
+  // engine frame.
+  bool _rescheduleAfterWarmUpFrame = false;
+
+  void _handleBeginFrame(Duration rawTimeStamp) {
+    if (_warmUpFrame) {
+      // "begin frame" and "draw frame" must strictly alternate. Therefore
+      // _rescheduleAfterWarmUpFrame cannot possibly be true here as it is
+      // reset by _handleDrawFrame.
+      assert(!_rescheduleAfterWarmUpFrame);
+      _rescheduleAfterWarmUpFrame = true;
+      return;
+    }
+    handleBeginFrame(rawTimeStamp);
+  }
+
+  void _handleDrawFrame() {
+    if (_rescheduleAfterWarmUpFrame) {
+      _rescheduleAfterWarmUpFrame = false;
+      // Reschedule in a post-frame callback to allow the draw-frame phase of
+      // the warm-up frame to finish.
+      addPostFrameCallback((Duration timeStamp) {
+        // Force an engine frame.
+        //
+        // We need to reset _hasScheduledFrame here because we cancelled the
+        // original engine frame, and therefore did not run handleBeginFrame
+        // who is responsible for resetting it. So if a frame callback set this
+        // to true in the "begin frame" part of the warm-up frame, it will
+        // still be true here and cause us to skip scheduling an engine frame.
+        _hasScheduledFrame = false;
+        scheduleFrame();
+      });
+      return;
+    }
+    handleDrawFrame();
+  }
+
+  /// Called by the engine to prepare the framework to produce a new frame.
+  ///
+  /// This function calls all the transient frame callbacks registered by
+  /// [scheduleFrameCallback]. It then returns, any scheduled microtasks are run
+  /// (e.g. handlers for any [Future]s resolved by transient frame callbacks),
+  /// and [handleDrawFrame] is called to continue the frame.
+  ///
+  /// If the given time stamp is null, the time stamp from the last frame is
+  /// reused.
+  ///
+  /// To have a banner shown at the start of every frame in debug mode, set
+  /// [debugPrintBeginFrameBanner] to true. The banner will be printed to the
+  /// console using [debugPrint] and will contain the frame number (which
+  /// increments by one for each frame), and the time stamp of the frame. If the
+  /// given time stamp was null, then the string "warm-up frame" is shown
+  /// instead of the time stamp. This allows frames eagerly pushed by the
+  /// framework to be distinguished from those requested by the engine in
+  /// response to the "Vsync" signal from the operating system.
+  ///
+  /// You can also show a banner at the end of every frame by setting
+  /// [debugPrintEndFrameBanner] to true. This allows you to distinguish log
+  /// statements printed during a frame from those printed between frames (e.g.
+  /// in response to events or timers).
+  void handleBeginFrame(Duration? rawTimeStamp) {
+    Timeline.startSync('Frame', arguments: timelineArgumentsIndicatingLandmarkEvent);
+    _firstRawTimeStampInEpoch ??= rawTimeStamp;
+    _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
+    if (rawTimeStamp != null)
+      _lastRawTimeStamp = rawTimeStamp;
+
+    assert(() {
+      _debugFrameNumber += 1;
+
+      if (debugPrintBeginFrameBanner || debugPrintEndFrameBanner) {
+        final StringBuffer frameTimeStampDescription = StringBuffer();
+        if (rawTimeStamp != null) {
+          _debugDescribeTimeStamp(_currentFrameTimeStamp!, frameTimeStampDescription);
+        } else {
+          frameTimeStampDescription.write('(warm-up frame)');
+        }
+        _debugBanner = '▄▄▄▄▄▄▄▄ Frame ${_debugFrameNumber.toString().padRight(7)}   ${frameTimeStampDescription.toString().padLeft(18)} ▄▄▄▄▄▄▄▄';
+        if (debugPrintBeginFrameBanner)
+          debugPrint(_debugBanner);
+      }
+      return true;
+    }());
+
+    assert(schedulerPhase == SchedulerPhase.idle);
+    _hasScheduledFrame = false;
+    try {
+      // TRANSIENT FRAME CALLBACKS
+      Timeline.startSync('Animate', arguments: timelineArgumentsIndicatingLandmarkEvent);
+      _schedulerPhase = SchedulerPhase.transientCallbacks;
+      final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
+      _transientCallbacks = <int, _FrameCallbackEntry>{};
+      callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
+        if (!_removedIds.contains(id))
+          _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp!, callbackEntry.debugStack);
+      });
+      _removedIds.clear();
+    } finally {
+      _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
+    }
+  }
+
+  /// Called by the engine to produce a new frame.
+  ///
+  /// This method is called immediately after [handleBeginFrame]. It calls all
+  /// the callbacks registered by [addPersistentFrameCallback], which typically
+  /// drive the rendering pipeline, and then calls the callbacks registered by
+  /// [addPostFrameCallback].
+  ///
+  /// See [handleBeginFrame] for a discussion about debugging hooks that may be
+  /// useful when working with frame callbacks.
+  void handleDrawFrame() {
+    assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
+    Timeline.finishSync(); // end the "Animate" phase
+    try {
+      // PERSISTENT FRAME CALLBACKS
+      _schedulerPhase = SchedulerPhase.persistentCallbacks;
+      for (final FrameCallback callback in _persistentCallbacks)
+        _invokeFrameCallback(callback, _currentFrameTimeStamp!);
+
+      // POST-FRAME CALLBACKS
+      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
+      final List<FrameCallback> localPostFrameCallbacks =
+          List<FrameCallback>.from(_postFrameCallbacks);
+      _postFrameCallbacks.clear();
+      for (final FrameCallback callback in localPostFrameCallbacks)
+        _invokeFrameCallback(callback, _currentFrameTimeStamp!);
+    } finally {
+      _schedulerPhase = SchedulerPhase.idle;
+      Timeline.finishSync(); // end the Frame
+      assert(() {
+        if (debugPrintEndFrameBanner)
+          debugPrint('▀' * _debugBanner!.length);
+        _debugBanner = null;
+        return true;
+      }());
+      _currentFrameTimeStamp = null;
+    }
+  }
+
+  void _profileFramePostEvent(int frameNumber, FrameTiming frameTiming) {
+    postEvent('Flutter.Frame', <String, dynamic>{
+      'number': frameNumber,
+      'startTime': frameTiming.timestampInMicroseconds(FramePhase.buildStart),
+      'elapsed': frameTiming.totalSpan.inMicroseconds,
+      'build': frameTiming.buildDuration.inMicroseconds,
+      'raster': frameTiming.rasterDuration.inMicroseconds,
+      'vsyncOverhead': frameTiming.vsyncOverhead.inMicroseconds,
+    });
+  }
+
+  static void _debugDescribeTimeStamp(Duration timeStamp, StringBuffer buffer) {
+    if (timeStamp.inDays > 0)
+      buffer.write('${timeStamp.inDays}d ');
+    if (timeStamp.inHours > 0)
+      buffer.write('${timeStamp.inHours - timeStamp.inDays * Duration.hoursPerDay}h ');
+    if (timeStamp.inMinutes > 0)
+      buffer.write('${timeStamp.inMinutes - timeStamp.inHours * Duration.minutesPerHour}m ');
+    if (timeStamp.inSeconds > 0)
+      buffer.write('${timeStamp.inSeconds - timeStamp.inMinutes * Duration.secondsPerMinute}s ');
+    buffer.write('${timeStamp.inMilliseconds - timeStamp.inSeconds * Duration.millisecondsPerSecond}');
+    final int microseconds = timeStamp.inMicroseconds - timeStamp.inMilliseconds * Duration.microsecondsPerMillisecond;
+    if (microseconds > 0)
+      buffer.write('.${microseconds.toString().padLeft(3, "0")}');
+    buffer.write('ms');
+  }
+
+  // Calls the given [callback] with [timestamp] as argument.
+  //
+  // Wraps the callback in a try/catch and forwards any error to
+  // [debugSchedulerExceptionHandler], if set. If not set, then simply prints
+  // the error.
+  void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace? callbackStack ]) {
+    assert(callback != null);
+    assert(_FrameCallbackEntry.debugCurrentCallbackStack == null);
+    assert(() {
+      _FrameCallbackEntry.debugCurrentCallbackStack = callbackStack;
+      return true;
+    }());
+    try {
+      callback(timeStamp);
+    } catch (exception, exceptionStack) {
+      FlutterError.reportError(FlutterErrorDetails(
+        exception: exception,
+        stack: exceptionStack,
+        library: 'scheduler library',
+        context: ErrorDescription('during a scheduler callback'),
+        informationCollector: (callbackStack == null) ? null : () sync* {
+          yield DiagnosticsStackTrace(
+            '\nThis exception was thrown in the context of a scheduler callback. '
+            'When the scheduler callback was _registered_ (as opposed to when the '
+            'exception was thrown), this was the stack',
+            callbackStack,
+          );
+        },
+      ));
+    }
+    assert(() {
+      _FrameCallbackEntry.debugCurrentCallbackStack = null;
+      return true;
+    }());
+  }
+}
+
+/// The default [SchedulingStrategy] for [SchedulerBinding.schedulingStrategy].
+///
+/// If there are any frame callbacks registered, only runs tasks with
+/// a [Priority] of [Priority.animation] or higher. Otherwise, runs
+/// all tasks.
+bool defaultSchedulingStrategy({ required int priority, required SchedulerBinding scheduler }) {
+  if (scheduler.transientCallbackCount > 0)
+    return priority >= Priority.animation.value;
+  return true;
+}
diff --git a/lib/src/scheduler/debug.dart b/lib/src/scheduler/debug.dart
new file mode 100644
index 0000000..43dae6b
--- /dev/null
+++ b/lib/src/scheduler/debug.dart
@@ -0,0 +1,67 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+// Any changes to this file should be reflected in the debugAssertAllSchedulerVarsUnset()
+// function below.
+
+/// Print a banner at the beginning of each frame.
+///
+/// Frames triggered by the engine and handler by the scheduler binding will
+/// have a banner giving the frame number and the time stamp of the frame.
+///
+/// Frames triggered eagerly by the widget framework (e.g. when calling
+/// [runApp]) will have a label saying "warm-up frame" instead of the time stamp
+/// (the time stamp sent to frame callbacks in that case is the time of the last
+/// frame, or 0:00 if it is the first frame).
+///
+/// To include a banner at the end of each frame as well, to distinguish
+/// intra-frame output from inter-frame output, set [debugPrintEndFrameBanner]
+/// to true as well.
+///
+/// See also:
+///
+///  * [debugProfilePaintsEnabled], which does something similar for
+///    painting but using the timeline view.
+///  * [debugPrintLayouts], which does something similar for layout but using
+///    console output.
+///  * The discussions at [WidgetsBinding.drawFrame] and at
+///    [SchedulerBinding.handleBeginFrame].
+bool debugPrintBeginFrameBanner = false;
+
+/// Print a banner at the end of each frame.
+///
+/// Combined with [debugPrintBeginFrameBanner], this can be helpful for
+/// determining if code is running during a frame or between frames.
+bool debugPrintEndFrameBanner = false;
+
+/// Log the call stacks that cause a frame to be scheduled.
+///
+/// This is called whenever [SchedulerBinding.scheduleFrame] schedules a frame. This
+/// can happen for various reasons, e.g. when a [Ticker] or
+/// [AnimationController] is started, or when [RenderObject.markNeedsLayout] is
+/// called, or when [State.setState] is called.
+///
+/// To get a stack specifically when widgets are scheduled to be built, see
+/// [debugPrintScheduleBuildForStacks].
+bool debugPrintScheduleFrameStacks = false;
+
+/// Returns true if none of the scheduler library debug variables have been changed.
+///
+/// This function is used by the test framework to ensure that debug variables
+/// haven't been inadvertently changed.
+///
+/// See [the scheduler library](scheduler/scheduler-library.html) for a complete
+/// list.
+bool debugAssertAllSchedulerVarsUnset(String reason) {
+  assert(() {
+    if (debugPrintBeginFrameBanner ||
+        debugPrintEndFrameBanner) {
+      throw FlutterError(reason);
+    }
+    return true;
+  }());
+  return true;
+}
diff --git a/lib/src/scheduler/priority.dart b/lib/src/scheduler/priority.dart
new file mode 100644
index 0000000..cd26950
--- /dev/null
+++ b/lib/src/scheduler/priority.dart
@@ -0,0 +1,51 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+/// A task priority, as passed to [SchedulerBinding.scheduleTask].
+@immutable
+class Priority {
+  const Priority._(this._value);
+
+  /// Integer that describes this Priority value.
+  int get value => _value;
+  final int _value;
+
+  /// A task to run after all other tasks, when no animations are running.
+  static const Priority idle = Priority._(0);
+
+  /// A task to run even when animations are running.
+  static const Priority animation = Priority._(100000);
+
+  /// A task to run even when the user is interacting with the device.
+  static const Priority touch = Priority._(200000);
+
+  /// Maximum offset by which to clamp relative priorities.
+  ///
+  /// It is still possible to have priorities that are offset by more
+  /// than this amount by repeatedly taking relative offsets, but that
+  /// is generally discouraged.
+  static const int kMaxOffset = 10000;
+
+  /// Returns a priority relative to this priority.
+  ///
+  /// A positive [offset] indicates a higher priority.
+  ///
+  /// The parameter [offset] is clamped to ±[kMaxOffset].
+  Priority operator +(int offset) {
+    if (offset.abs() > kMaxOffset) {
+      // Clamp the input offset.
+      offset = kMaxOffset * offset.sign;
+    }
+    return Priority._(_value + offset);
+  }
+
+  /// Returns a priority relative to this priority.
+  ///
+  /// A positive offset indicates a lower priority.
+  ///
+  /// The parameter [offset] is clamped to ±[kMaxOffset].
+  Priority operator -(int offset) => this + (-offset);
+}
diff --git a/lib/src/scheduler/ticker.dart b/lib/src/scheduler/ticker.dart
new file mode 100644
index 0000000..d0d9963
--- /dev/null
+++ b/lib/src/scheduler/ticker.dart
@@ -0,0 +1,473 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+
+import 'package:flute/foundation.dart';
+
+import 'binding.dart';
+
+/// Signature for the callback passed to the [Ticker] class's constructor.
+///
+/// The argument is the time that the object had spent enabled so far
+/// at the time of the callback being called.
+typedef TickerCallback = void Function(Duration elapsed);
+
+/// An interface implemented by classes that can vend [Ticker] objects.
+///
+/// Tickers can be used by any object that wants to be notified whenever a frame
+/// triggers, but are most commonly used indirectly via an
+/// [AnimationController]. [AnimationController]s need a [TickerProvider] to
+/// obtain their [Ticker]. If you are creating an [AnimationController] from a
+/// [State], then you can use the [TickerProviderStateMixin] and
+/// [SingleTickerProviderStateMixin] classes to obtain a suitable
+/// [TickerProvider]. The widget test framework [WidgetTester] object can be
+/// used as a ticker provider in the context of tests. In other contexts, you
+/// will have to either pass a [TickerProvider] from a higher level (e.g.
+/// indirectly from a [State] that mixes in [TickerProviderStateMixin]), or
+/// create a custom [TickerProvider] subclass.
+abstract class TickerProvider {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const TickerProvider();
+
+  /// Creates a ticker with the given callback.
+  ///
+  /// The kind of ticker provided depends on the kind of ticker provider.
+  @factory
+  Ticker createTicker(TickerCallback onTick);
+}
+
+// TODO(jacobr): make Ticker use Diagnosticable to simplify reporting errors
+// related to a ticker.
+/// Calls its callback once per animation frame.
+///
+/// When created, a ticker is initially disabled. Call [start] to
+/// enable the ticker.
+///
+/// A [Ticker] can be silenced by setting [muted] to true. While silenced, time
+/// still elapses, and [start] and [stop] can still be called, but no callbacks
+/// are called.
+///
+/// By convention, the [start] and [stop] methods are used by the ticker's
+/// consumer, and the [muted] property is controlled by the [TickerProvider]
+/// that created the ticker.
+///
+/// Tickers are driven by the [SchedulerBinding]. See
+/// [SchedulerBinding.scheduleFrameCallback].
+class Ticker {
+  /// Creates a ticker that will call the provided callback once per frame while
+  /// running.
+  ///
+  /// An optional label can be provided for debugging purposes. That label
+  /// will appear in the [toString] output in debug builds.
+  Ticker(this._onTick, { this.debugLabel }) {
+    assert(() {
+      _debugCreationStack = StackTrace.current;
+      return true;
+    }());
+  }
+
+  TickerFuture? _future;
+
+  /// Whether this ticker has been silenced.
+  ///
+  /// While silenced, a ticker's clock can still run, but the callback will not
+  /// be called.
+  bool get muted => _muted;
+  bool _muted = false;
+  /// When set to true, silences the ticker, so that it is no longer ticking. If
+  /// a tick is already scheduled, it will unschedule it. This will not
+  /// unschedule the next frame, though.
+  ///
+  /// When set to false, unsilences the ticker, potentially scheduling a frame
+  /// to handle the next tick.
+  ///
+  /// By convention, the [muted] property is controlled by the object that
+  /// created the [Ticker] (typically a [TickerProvider]), not the object that
+  /// listens to the ticker's ticks.
+  set muted(bool value) {
+    if (value == muted)
+      return;
+    _muted = value;
+    if (value) {
+      unscheduleTick();
+    } else if (shouldScheduleTick) {
+      scheduleTick();
+    }
+  }
+
+  /// Whether this [Ticker] has scheduled a call to call its callback
+  /// on the next frame.
+  ///
+  /// A ticker that is [muted] can be active (see [isActive]) yet not be
+  /// ticking. In that case, the ticker will not call its callback, and
+  /// [isTicking] will be false, but time will still be progressing.
+  ///
+  /// This will return false if the [SchedulerBinding.lifecycleState] is one
+  /// that indicates the application is not currently visible (e.g. if the
+  /// device's screen is turned off).
+  bool get isTicking {
+    if (_future == null)
+      return false;
+    if (muted)
+      return false;
+    if (SchedulerBinding.instance!.framesEnabled)
+      return true;
+    if (SchedulerBinding.instance!.schedulerPhase != SchedulerPhase.idle)
+      return true; // for example, we might be in a warm-up frame or forced frame
+    return false;
+  }
+
+  /// Whether time is elapsing for this [Ticker]. Becomes true when [start] is
+  /// called and false when [stop] is called.
+  ///
+  /// A ticker can be active yet not be actually ticking (i.e. not be calling
+  /// the callback). To determine if a ticker is actually ticking, use
+  /// [isTicking].
+  bool get isActive => _future != null;
+
+  Duration? _startTime;
+
+  /// Starts the clock for this [Ticker]. If the ticker is not [muted], then this
+  /// also starts calling the ticker's callback once per animation frame.
+  ///
+  /// The returned future resolves once the ticker [stop]s ticking. If the
+  /// ticker is disposed, the future does not resolve. A derivative future is
+  /// available from the returned [TickerFuture] object that resolves with an
+  /// error in that case, via [TickerFuture.orCancel].
+  ///
+  /// Calling this sets [isActive] to true.
+  ///
+  /// This method cannot be called while the ticker is active. To restart the
+  /// ticker, first [stop] it.
+  ///
+  /// By convention, this method is used by the object that receives the ticks
+  /// (as opposed to the [TickerProvider] which created the ticker).
+  TickerFuture start() {
+    assert(() {
+      if (isActive) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('A ticker was started twice.'),
+          ErrorDescription('A ticker that is already active cannot be started again without first stopping it.'),
+          describeForError('The affected ticker was'),
+        ]);
+      }
+      return true;
+    }());
+    assert(_startTime == null);
+    _future = TickerFuture._();
+    if (shouldScheduleTick) {
+      scheduleTick();
+    }
+    if (SchedulerBinding.instance!.schedulerPhase.index > SchedulerPhase.idle.index &&
+        SchedulerBinding.instance!.schedulerPhase.index < SchedulerPhase.postFrameCallbacks.index)
+      _startTime = SchedulerBinding.instance!.currentFrameTimeStamp;
+    return _future!;
+  }
+
+  /// Adds a debug representation of a [Ticker] optimized for including in error
+  /// messages.
+  DiagnosticsNode describeForError(String name) {
+    // TODO(jacobr): make this more structured.
+    return DiagnosticsProperty<Ticker>(name, this, description: toString(debugIncludeStack: true));
+  }
+
+  /// Stops calling this [Ticker]'s callback.
+  ///
+  /// If called with the `canceled` argument set to false (the default), causes
+  /// the future returned by [start] to resolve. If called with the `canceled`
+  /// argument set to true, the future does not resolve, and the future obtained
+  /// from [TickerFuture.orCancel], if any, resolves with a [TickerCanceled]
+  /// error.
+  ///
+  /// Calling this sets [isActive] to false.
+  ///
+  /// This method does nothing if called when the ticker is inactive.
+  ///
+  /// By convention, this method is used by the object that receives the ticks
+  /// (as opposed to the [TickerProvider] which created the ticker).
+  void stop({ bool canceled = false }) {
+    if (!isActive)
+      return;
+
+    // We take the _future into a local variable so that isTicking is false
+    // when we actually complete the future (isTicking uses _future to
+    // determine its state).
+    final TickerFuture localFuture = _future!;
+    _future = null;
+    _startTime = null;
+    assert(!isActive);
+
+    unscheduleTick();
+    if (canceled) {
+      localFuture._cancel(this);
+    } else {
+      localFuture._complete();
+    }
+  }
+
+
+  final TickerCallback _onTick;
+
+  int? _animationId;
+
+  /// Whether this [Ticker] has already scheduled a frame callback.
+  @protected
+  bool get scheduled => _animationId != null;
+
+  /// Whether a tick should be scheduled.
+  ///
+  /// If this is true, then calling [scheduleTick] should succeed.
+  ///
+  /// Reasons why a tick should not be scheduled include:
+  ///
+  /// * A tick has already been scheduled for the coming frame.
+  /// * The ticker is not active ([start] has not been called).
+  /// * The ticker is not ticking, e.g. because it is [muted] (see [isTicking]).
+  @protected
+  bool get shouldScheduleTick => !muted && isActive && !scheduled;
+
+  void _tick(Duration timeStamp) {
+    assert(isTicking);
+    assert(scheduled);
+    _animationId = null;
+
+    _startTime ??= timeStamp;
+    _onTick(timeStamp - _startTime!);
+
+    // The onTick callback may have scheduled another tick already, for
+    // example by calling stop then start again.
+    if (shouldScheduleTick)
+      scheduleTick(rescheduling: true);
+  }
+
+  /// Schedules a tick for the next frame.
+  ///
+  /// This should only be called if [shouldScheduleTick] is true.
+  @protected
+  void scheduleTick({ bool rescheduling = false }) {
+    assert(!scheduled);
+    assert(shouldScheduleTick);
+    _animationId = SchedulerBinding.instance!.scheduleFrameCallback(_tick, rescheduling: rescheduling);
+  }
+
+  /// Cancels the frame callback that was requested by [scheduleTick], if any.
+  ///
+  /// Calling this method when no tick is [scheduled] is harmless.
+  ///
+  /// This method should not be called when [shouldScheduleTick] would return
+  /// true if no tick was scheduled.
+  @protected
+  void unscheduleTick() {
+    if (scheduled) {
+      SchedulerBinding.instance!.cancelFrameCallbackWithId(_animationId!);
+      _animationId = null;
+    }
+    assert(!shouldScheduleTick);
+  }
+
+  /// Makes this [Ticker] take the state of another ticker, and disposes the
+  /// other ticker.
+  ///
+  /// This is useful if an object with a [Ticker] is given a new
+  /// [TickerProvider] but needs to maintain continuity. In particular, this
+  /// maintains the identity of the [TickerFuture] returned by the [start]
+  /// function of the original [Ticker] if the original ticker is active.
+  ///
+  /// This ticker must not be active when this method is called.
+  void absorbTicker(Ticker originalTicker) {
+    assert(!isActive);
+    assert(_future == null);
+    assert(_startTime == null);
+    assert(_animationId == null);
+    assert((originalTicker._future == null) == (originalTicker._startTime == null), 'Cannot absorb Ticker after it has been disposed.');
+    if (originalTicker._future != null) {
+      _future = originalTicker._future;
+      _startTime = originalTicker._startTime;
+      if (shouldScheduleTick)
+        scheduleTick();
+      originalTicker._future = null; // so that it doesn't get disposed when we dispose of originalTicker
+      originalTicker.unscheduleTick();
+    }
+    originalTicker.dispose();
+  }
+
+  /// Release the resources used by this object. The object is no longer usable
+  /// after this method is called.
+  @mustCallSuper
+  void dispose() {
+    if (_future != null) {
+      final TickerFuture localFuture = _future!;
+      _future = null;
+      assert(!isActive);
+      unscheduleTick();
+      localFuture._cancel(this);
+    }
+    assert(() {
+      // We intentionally don't null out _startTime. This means that if start()
+      // was ever called, the object is now in a bogus state. This weakly helps
+      // catch cases of use-after-dispose.
+      _startTime = Duration.zero;
+      return true;
+    }());
+  }
+
+  /// An optional label can be provided for debugging purposes.
+  ///
+  /// This label will appear in the [toString] output in debug builds.
+  final String? debugLabel;
+  late StackTrace _debugCreationStack;
+
+  @override
+  String toString({ bool debugIncludeStack = false }) {
+    final StringBuffer buffer = StringBuffer();
+    buffer.write('${objectRuntimeType(this, 'Ticker')}(');
+    assert(() {
+      buffer.write(debugLabel ?? '');
+      return true;
+    }());
+    buffer.write(')');
+    assert(() {
+      if (debugIncludeStack) {
+        buffer.writeln();
+        buffer.writeln('The stack trace when the $runtimeType was actually created was:');
+        FlutterError.defaultStackFilter(_debugCreationStack.toString().trimRight().split('\n')).forEach(buffer.writeln);
+      }
+      return true;
+    }());
+    return buffer.toString();
+  }
+}
+
+/// An object representing an ongoing [Ticker] sequence.
+///
+/// The [Ticker.start] method returns a [TickerFuture]. The [TickerFuture] will
+/// complete successfully if the [Ticker] is stopped using [Ticker.stop] with
+/// the `canceled` argument set to false (the default).
+///
+/// If the [Ticker] is disposed without being stopped, or if it is stopped with
+/// `canceled` set to true, then this Future will never complete.
+///
+/// This class works like a normal [Future], but has an additional property,
+/// [orCancel], which returns a derivative [Future] that completes with an error
+/// if the [Ticker] that returned the [TickerFuture] was stopped with `canceled`
+/// set to true, or if it was disposed without being stopped.
+///
+/// To run a callback when either this future resolves or when the ticker is
+/// canceled, use [whenCompleteOrCancel].
+class TickerFuture implements Future<void> {
+  TickerFuture._();
+
+  /// Creates a [TickerFuture] instance that represents an already-complete
+  /// [Ticker] sequence.
+  ///
+  /// This is useful for implementing objects that normally defer to a [Ticker]
+  /// but sometimes can skip the ticker because the animation is of zero
+  /// duration, but which still need to represent the completed animation in the
+  /// form of a [TickerFuture].
+  TickerFuture.complete() {
+    _complete();
+  }
+
+  final Completer<void> _primaryCompleter = Completer<void>();
+  Completer<void>? _secondaryCompleter;
+  bool? _completed; // null means unresolved, true means complete, false means canceled
+
+  void _complete() {
+    assert(_completed == null);
+    _completed = true;
+    _primaryCompleter.complete();
+    _secondaryCompleter?.complete();
+  }
+
+  void _cancel(Ticker ticker) {
+    assert(_completed == null);
+    _completed = false;
+    _secondaryCompleter?.completeError(TickerCanceled(ticker));
+  }
+
+  /// Calls `callback` either when this future resolves or when the ticker is
+  /// canceled.
+  ///
+  /// Calling this method registers an exception handler for the [orCancel]
+  /// future, so even if the [orCancel] property is accessed, canceling the
+  /// ticker will not cause an uncaught exception in the current zone.
+  void whenCompleteOrCancel(VoidCallback callback) {
+    void thunk(dynamic value) {
+      callback();
+    }
+    orCancel.then<void>(thunk, onError: thunk);
+  }
+
+  /// A future that resolves when this future resolves or throws when the ticker
+  /// is canceled.
+  ///
+  /// If this property is never accessed, then canceling the ticker does not
+  /// throw any exceptions. Once this property is accessed, though, if the
+  /// corresponding ticker is canceled, then the [Future] returned by this
+  /// getter will complete with an error, and if that error is not caught, there
+  /// will be an uncaught exception in the current zone.
+  Future<void> get orCancel {
+    if (_secondaryCompleter == null) {
+      _secondaryCompleter = Completer<void>();
+      if (_completed != null) {
+        if (_completed!) {
+          _secondaryCompleter!.complete();
+        } else {
+          _secondaryCompleter!.completeError(const TickerCanceled());
+        }
+      }
+    }
+    return _secondaryCompleter!.future;
+  }
+
+  @override
+  Stream<void> asStream() {
+    return _primaryCompleter.future.asStream();
+  }
+
+  @override
+  Future<void> catchError(Function onError, { bool Function(Object)? test }) {
+    return _primaryCompleter.future.catchError(onError, test: test);
+  }
+
+  @override
+  Future<R> then<R>(FutureOr<R> onValue(void value), { Function? onError }) {
+    return _primaryCompleter.future.then<R>(onValue, onError: onError);
+  }
+
+  @override
+  Future<void> timeout(Duration timeLimit, { FutureOr<void> Function()? onTimeout }) {
+    return _primaryCompleter.future.timeout(timeLimit, onTimeout: onTimeout);
+  }
+
+  @override
+  Future<void> whenComplete(dynamic action()) {
+    return _primaryCompleter.future.whenComplete(action);
+  }
+
+  @override
+  String toString() => '${describeIdentity(this)}(${ _completed == null ? "active" : _completed! ? "complete" : "canceled" })';
+}
+
+/// Exception thrown by [Ticker] objects on the [TickerFuture.orCancel] future
+/// when the ticker is canceled.
+class TickerCanceled implements Exception {
+  /// Creates a canceled-ticker exception.
+  const TickerCanceled([this.ticker]);
+
+  /// Reference to the [Ticker] object that was canceled.
+  ///
+  /// This may be null in the case that the [Future] created for
+  /// [TickerFuture.orCancel] was created after the ticker was canceled.
+  final Ticker? ticker;
+
+  @override
+  String toString() {
+    if (ticker != null)
+      return 'This ticker was canceled: $ticker';
+    return 'The ticker was canceled before the "orCancel" property was first used.';
+  }
+}
diff --git a/lib/src/semantics/binding.dart b/lib/src/semantics/binding.dart
new file mode 100644
index 0000000..a7c13e3
--- /dev/null
+++ b/lib/src/semantics/binding.dart
@@ -0,0 +1,69 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui show AccessibilityFeatures, SemanticsUpdateBuilder;
+
+import 'package:flute/foundation.dart';
+
+import 'debug.dart';
+
+export 'package:flute/ui.dart' show AccessibilityFeatures;
+
+/// The glue between the semantics layer and the Flutter engine.
+// TODO(jonahwilliams): move the remaining semantic related bindings here.
+mixin SemanticsBinding on BindingBase {
+  /// The current [SemanticsBinding], if one has been created.
+  static SemanticsBinding? get instance => _instance;
+  static SemanticsBinding? _instance;
+
+  @override
+  void initInstances() {
+    super.initInstances();
+    _instance = this;
+    _accessibilityFeatures = window.accessibilityFeatures;
+  }
+
+  /// Called when the platform accessibility features change.
+  ///
+  /// See [dart:ui.PlatformDispatcher.onAccessibilityFeaturesChanged].
+  @protected
+  void handleAccessibilityFeaturesChanged() {
+    _accessibilityFeatures = window.accessibilityFeatures;
+  }
+
+  /// Creates an empty semantics update builder.
+  ///
+  /// The caller is responsible for filling out the semantics node updates.
+  ///
+  /// This method is used by the [SemanticsOwner] to create builder for all its
+  /// semantics updates.
+  ui.SemanticsUpdateBuilder createSemanticsUpdateBuilder() {
+    return ui.SemanticsUpdateBuilder();
+  }
+
+  /// The currently active set of [AccessibilityFeatures].
+  ///
+  /// This is initialized the first time [runApp] is called and updated whenever
+  /// a flag is changed.
+  ///
+  /// To listen to changes to accessibility features, create a
+  /// [WidgetsBindingObserver] and listen to
+  /// [WidgetsBindingObserver.didChangeAccessibilityFeatures].
+  ui.AccessibilityFeatures get accessibilityFeatures => _accessibilityFeatures;
+  late ui.AccessibilityFeatures _accessibilityFeatures;
+
+  /// The platform is requesting that animations be disabled or simplified.
+  ///
+  /// This setting can be overridden for testing or debugging by setting
+  /// [debugSemanticsDisableAnimations].
+  bool get disableAnimations {
+    bool value = _accessibilityFeatures.disableAnimations;
+    assert(() {
+      if (debugSemanticsDisableAnimations != null)
+        value = debugSemanticsDisableAnimations!;
+      return true;
+    }());
+    return value;
+  }
+}
diff --git a/lib/src/semantics/debug.dart b/lib/src/semantics/debug.dart
new file mode 100644
index 0000000..dca4177
--- /dev/null
+++ b/lib/src/semantics/debug.dart
@@ -0,0 +1,10 @@
+// Copyright 2014 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.
+
+
+/// Overrides the setting of [SemanticsBinding.disableAnimations] for debugging
+/// and testing.
+///
+/// This value is ignored in non-debug builds.
+bool? debugSemanticsDisableAnimations;
diff --git a/lib/src/semantics/semantics.dart b/lib/src/semantics/semantics.dart
new file mode 100644
index 0000000..c44d11a
--- /dev/null
+++ b/lib/src/semantics/semantics.dart
@@ -0,0 +1,4116 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'dart:typed_data';
+import 'package:flute/ui.dart' as ui;
+import 'package:flute/ui.dart' show Offset, Rect, SemanticsAction, SemanticsFlag,
+       TextDirection;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart' show MatrixUtils, TransformProperty;
+import 'package:flute/services.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'binding.dart' show SemanticsBinding;
+import 'semantics_event.dart';
+
+export 'package:flute/ui.dart' show SemanticsAction;
+export 'semantics_event.dart';
+
+/// Signature for a function that is called for each [SemanticsNode].
+///
+/// Return false to stop visiting nodes.
+///
+/// Used by [SemanticsNode.visitChildren].
+typedef SemanticsNodeVisitor = bool Function(SemanticsNode node);
+
+/// Signature for [SemanticsAction]s that move the cursor.
+///
+/// If `extendSelection` is set to true the cursor movement should extend the
+/// current selection or (if nothing is currently selected) start a selection.
+typedef MoveCursorHandler = void Function(bool extendSelection);
+
+/// Signature for the [SemanticsAction.setSelection] handlers to change the
+/// text selection (or re-position the cursor) to `selection`.
+typedef SetSelectionHandler = void Function(TextSelection selection);
+
+typedef _SemanticsActionHandler = void Function(dynamic args);
+
+/// A tag for a [SemanticsNode].
+///
+/// Tags can be interpreted by the parent of a [SemanticsNode]
+/// and depending on the presence of a tag the parent can for example decide
+/// how to add the tagged node as a child. Tags are not sent to the engine.
+///
+/// As an example, the [RenderSemanticsGestureHandler] uses tags to determine
+/// if a child node should be excluded from the scrollable area for semantic
+/// purposes.
+///
+/// The provided [name] is only used for debugging. Two tags created with the
+/// same [name] and the `new` operator are not considered identical. However,
+/// two tags created with the same [name] and the `const` operator are always
+/// identical.
+class SemanticsTag {
+  /// Creates a [SemanticsTag].
+  ///
+  /// The provided [name] is only used for debugging. Two tags created with the
+  /// same [name] and the `new` operator are not considered identical. However,
+  /// two tags created with the same [name] and the `const` operator are always
+  /// identical.
+  const SemanticsTag(this.name);
+
+  /// A human-readable name for this tag used for debugging.
+  ///
+  /// This string is not used to determine if two tags are identical.
+  final String name;
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'SemanticsTag')}($name)';
+}
+
+/// An identifier of a custom semantics action.
+///
+/// Custom semantics actions can be provided to make complex user
+/// interactions more accessible. For instance, if an application has a
+/// drag-and-drop list that requires the user to press and hold an item
+/// to move it, users interacting with the application using a hardware
+/// switch may have difficulty. This can be made accessible by creating custom
+/// actions and pairing them with handlers that move a list item up or down in
+/// the list.
+///
+/// In Android, these actions are presented in the local context menu. In iOS,
+/// these are presented in the radial context menu.
+///
+/// Localization and text direction do not automatically apply to the provided
+/// label or hint.
+///
+/// Instances of this class should either be instantiated with const or
+/// new instances cached in static fields.
+///
+/// See also:
+///
+///  * [SemanticsProperties], where the handler for a custom action is provided.
+@immutable
+class CustomSemanticsAction {
+  /// Creates a new [CustomSemanticsAction].
+  ///
+  /// The [label] must not be null or the empty string.
+  const CustomSemanticsAction({required String this.label})
+    : assert(label != null),
+      assert(label != ''),
+      hint = null,
+      action = null;
+
+  /// Creates a new [CustomSemanticsAction] that overrides a standard semantics
+  /// action.
+  ///
+  /// The [hint] must not be null or the empty string.
+  const CustomSemanticsAction.overridingAction({required String this.hint, required SemanticsAction this.action})
+    : assert(hint != null),
+      assert(hint != ''),
+      assert(action != null),
+      label = null;
+
+  /// The user readable name of this custom semantics action.
+  final String? label;
+
+  /// The hint description of this custom semantics action.
+  final String? hint;
+
+  /// The standard semantics action this action replaces.
+  final SemanticsAction? action;
+
+  @override
+  int get hashCode => ui.hashValues(label, hint, action);
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is CustomSemanticsAction
+        && other.label == label
+        && other.hint == hint
+        && other.action == action;
+  }
+
+  @override
+  String toString() {
+    return 'CustomSemanticsAction(${_ids[this]}, label:$label, hint:$hint, action:$action)';
+  }
+
+  // Logic to assign a unique id to each custom action without requiring
+  // user specification.
+  static int _nextId = 0;
+  static final Map<int, CustomSemanticsAction> _actions = <int, CustomSemanticsAction>{};
+  static final Map<CustomSemanticsAction, int> _ids = <CustomSemanticsAction, int>{};
+
+  /// Get the identifier for a given `action`.
+  static int getIdentifier(CustomSemanticsAction action) {
+    int? result = _ids[action];
+    if (result == null) {
+      result = _nextId++;
+      _ids[action] = result;
+      _actions[result] = action;
+    }
+    return result;
+  }
+
+  /// Get the `action` for a given identifier.
+  static CustomSemanticsAction? getAction(int id) {
+    return _actions[id];
+  }
+}
+
+/// Summary information about a [SemanticsNode] object.
+///
+/// A semantics node might [SemanticsNode.mergeAllDescendantsIntoThisNode],
+/// which means the individual fields on the semantics node don't fully describe
+/// the semantics at that node. This data structure contains the full semantics
+/// for the node.
+///
+/// Typically obtained from [SemanticsNode.getSemanticsData].
+@immutable
+class SemanticsData with Diagnosticable {
+  /// Creates a semantics data object.
+  ///
+  /// The [flags], [actions], [label], and [Rect] arguments must not be null.
+  ///
+  /// If [label] is not empty, then [textDirection] must also not be null.
+  const SemanticsData({
+    required this.flags,
+    required this.actions,
+    required this.label,
+    required this.increasedValue,
+    required this.value,
+    required this.decreasedValue,
+    required this.hint,
+    required this.textDirection,
+    required this.rect,
+    required this.elevation,
+    required this.thickness,
+    required this.textSelection,
+    required this.scrollIndex,
+    required this.scrollChildCount,
+    required this.scrollPosition,
+    required this.scrollExtentMax,
+    required this.scrollExtentMin,
+    required this.platformViewId,
+    required this.maxValueLength,
+    required this.currentValueLength,
+    this.tags,
+    this.transform,
+    this.customSemanticsActionIds,
+  }) : assert(flags != null),
+       assert(actions != null),
+       assert(label != null),
+       assert(value != null),
+       assert(decreasedValue != null),
+       assert(increasedValue != null),
+       assert(hint != null),
+       assert(label == '' || textDirection != null, 'A SemanticsData object with label "$label" had a null textDirection.'),
+       assert(value == '' || textDirection != null, 'A SemanticsData object with value "$value" had a null textDirection.'),
+       assert(hint == '' || textDirection != null, 'A SemanticsData object with hint "$hint" had a null textDirection.'),
+       assert(decreasedValue == '' || textDirection != null, 'A SemanticsData object with decreasedValue "$decreasedValue" had a null textDirection.'),
+       assert(increasedValue == '' || textDirection != null, 'A SemanticsData object with increasedValue "$increasedValue" had a null textDirection.'),
+       assert(rect != null);
+
+  /// A bit field of [SemanticsFlag]s that apply to this node.
+  final int flags;
+
+  /// A bit field of [SemanticsAction]s that apply to this node.
+  final int actions;
+
+  /// A textual description of this node.
+  ///
+  /// The reading direction is given by [textDirection].
+  final String label;
+
+  /// A textual description for the current value of the node.
+  ///
+  /// The reading direction is given by [textDirection].
+  final String value;
+
+  /// The value that [value] will become after performing a
+  /// [SemanticsAction.increase] action.
+  ///
+  /// The reading direction is given by [textDirection].
+  final String increasedValue;
+
+  /// The value that [value] will become after performing a
+  /// [SemanticsAction.decrease] action.
+  ///
+  /// The reading direction is given by [textDirection].
+  final String decreasedValue;
+
+  /// A brief description of the result of performing an action on this node.
+  ///
+  /// The reading direction is given by [textDirection].
+  final String hint;
+
+  /// The reading direction for the text in [label], [value], [hint],
+  /// [increasedValue], and [decreasedValue].
+  final TextDirection? textDirection;
+
+  /// The currently selected text (or the position of the cursor) within [value]
+  /// if this node represents a text field.
+  final TextSelection? textSelection;
+
+  /// The total number of scrollable children that contribute to semantics.
+  ///
+  /// If the number of children are unknown or unbounded, this value will be
+  /// null.
+  final int? scrollChildCount;
+
+  /// The index of the first visible semantic child of a scroll node.
+  final int? scrollIndex;
+
+  /// Indicates the current scrolling position in logical pixels if the node is
+  /// scrollable.
+  ///
+  /// The properties [scrollExtentMin] and [scrollExtentMax] indicate the valid
+  /// in-range values for this property. The value for [scrollPosition] may
+  /// (temporarily) be outside that range, e.g. during an overscroll.
+  ///
+  /// See also:
+  ///
+  ///  * [ScrollPosition.pixels], from where this value is usually taken.
+  final double? scrollPosition;
+
+  /// Indicates the maximum in-range value for [scrollPosition] if the node is
+  /// scrollable.
+  ///
+  /// This value may be infinity if the scroll is unbound.
+  ///
+  /// See also:
+  ///
+  ///  * [ScrollPosition.maxScrollExtent], from where this value is usually taken.
+  final double? scrollExtentMax;
+
+  /// Indicates the minimum in-range value for [scrollPosition] if the node is
+  /// scrollable.
+  ///
+  /// This value may be infinity if the scroll is unbound.
+  ///
+  /// See also:
+  ///
+  ///  * [ScrollPosition.minScrollExtent], from where this value is usually taken.
+  final double? scrollExtentMin;
+
+  /// The id of the platform view, whose semantics nodes will be added as
+  /// children to this node.
+  ///
+  /// If this value is non-null, the SemanticsNode must not have any children
+  /// as those would be replaced by the semantics nodes of the referenced
+  /// platform view.
+  ///
+  /// See also:
+  ///
+  ///  * [AndroidView], which is the platform view for Android.
+  ///  * [UiKitView], which is the platform view for iOS.
+  final int? platformViewId;
+
+  /// The maximum number of characters that can be entered into an editable
+  /// text field.
+  ///
+  /// For the purpose of this function a character is defined as one Unicode
+  /// scalar value.
+  ///
+  /// This should only be set when [SemanticsFlag.isTextField] is set. Defaults
+  /// to null, which means no limit is imposed on the text field.
+  final int? maxValueLength;
+
+  /// The current number of characters that have been entered into an editable
+  /// text field.
+  ///
+  /// For the purpose of this function a character is defined as one Unicode
+  /// scalar value.
+  ///
+  /// This should only be set when [SemanticsFlag.isTextField] is set. This must
+  /// be set when [maxValueLength] is set.
+  final int? currentValueLength;
+
+  /// The bounding box for this node in its coordinate system.
+  final Rect rect;
+
+  /// The set of [SemanticsTag]s associated with this node.
+  final Set<SemanticsTag>? tags;
+
+  /// The transform from this node's coordinate system to its parent's coordinate system.
+  ///
+  /// By default, the transform is null, which represents the identity
+  /// transformation (i.e., that this node has the same coordinate system as its
+  /// parent).
+  final Matrix4? transform;
+
+  /// The elevation of this node relative to the parent semantics node.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsConfiguration.elevation] for a detailed discussion regarding
+  ///    elevation and semantics.
+  final double elevation;
+
+  /// The extent of this node along the z-axis beyond its [elevation]
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsConfiguration.thickness] for a more detailed definition.
+  final double thickness;
+
+  /// The identifiers for the custom semantics actions and standard action
+  /// overrides for this node.
+  ///
+  /// The list must be sorted in increasing order.
+  ///
+  /// See also:
+  ///
+  ///  * [CustomSemanticsAction], for an explanation of custom actions.
+  final List<int>? customSemanticsActionIds;
+
+  /// Whether [flags] contains the given flag.
+  bool hasFlag(SemanticsFlag flag) => (flags & flag.index) != 0;
+
+  /// Whether [actions] contains the given action.
+  bool hasAction(SemanticsAction action) => (actions & action.index) != 0;
+
+  @override
+  String toStringShort() => objectRuntimeType(this, 'SemanticsData');
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Rect>('rect', rect, showName: false));
+    properties.add(TransformProperty('transform', transform, showName: false, defaultValue: null));
+    properties.add(DoubleProperty('elevation', elevation, defaultValue: 0.0));
+    properties.add(DoubleProperty('thickness', thickness, defaultValue: 0.0));
+    final List<String> actionSummary = <String>[
+      for (final SemanticsAction action in SemanticsAction.values.values)
+        if ((actions & action.index) != 0)
+          describeEnum(action),
+    ];
+    final List<String?> customSemanticsActionSummary = customSemanticsActionIds!
+      .map<String?>((int actionId) => CustomSemanticsAction.getAction(actionId)!.label)
+      .toList();
+    properties.add(IterableProperty<String>('actions', actionSummary, ifEmpty: null));
+    properties.add(IterableProperty<String?>('customActions', customSemanticsActionSummary, ifEmpty: null));
+
+    final List<String> flagSummary = <String>[
+      for (final SemanticsFlag flag in SemanticsFlag.values.values)
+        if ((flags & flag.index) != 0)
+          describeEnum(flag),
+    ];
+    properties.add(IterableProperty<String>('flags', flagSummary, ifEmpty: null));
+    properties.add(StringProperty('label', label, defaultValue: ''));
+    properties.add(StringProperty('value', value, defaultValue: ''));
+    properties.add(StringProperty('increasedValue', increasedValue, defaultValue: ''));
+    properties.add(StringProperty('decreasedValue', decreasedValue, defaultValue: ''));
+    properties.add(StringProperty('hint', hint, defaultValue: ''));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    if (textSelection?.isValid == true)
+      properties.add(MessageProperty('textSelection', '[${textSelection!.start}, ${textSelection!.end}]'));
+    properties.add(IntProperty('platformViewId', platformViewId, defaultValue: null));
+    properties.add(IntProperty('maxValueLength', maxValueLength, defaultValue: null));
+    properties.add(IntProperty('currentValueLength', currentValueLength, defaultValue: null));
+    properties.add(IntProperty('scrollChildren', scrollChildCount, defaultValue: null));
+    properties.add(IntProperty('scrollIndex', scrollIndex, defaultValue: null));
+    properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
+    properties.add(DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
+    properties.add(DoubleProperty('scrollExtentMax', scrollExtentMax, defaultValue: null));
+  }
+
+  @override
+  bool operator ==(Object other) {
+    return other is SemanticsData
+        && other.flags == flags
+        && other.actions == actions
+        && other.label == label
+        && other.value == value
+        && other.increasedValue == increasedValue
+        && other.decreasedValue == decreasedValue
+        && other.hint == hint
+        && other.textDirection == textDirection
+        && other.rect == rect
+        && setEquals(other.tags, tags)
+        && other.scrollChildCount == scrollChildCount
+        && other.scrollIndex == scrollIndex
+        && other.textSelection == textSelection
+        && other.scrollPosition == scrollPosition
+        && other.scrollExtentMax == scrollExtentMax
+        && other.scrollExtentMin == scrollExtentMin
+        && other.platformViewId == platformViewId
+        && other.maxValueLength == maxValueLength
+        && other.currentValueLength == currentValueLength
+        && other.transform == transform
+        && other.elevation == elevation
+        && other.thickness == thickness
+        && _sortedListsEqual(other.customSemanticsActionIds, customSemanticsActionIds);
+  }
+
+  @override
+  int get hashCode {
+    return ui.hashValues(
+      ui.hashValues(
+        flags,
+        actions,
+        label,
+        value,
+        increasedValue,
+        decreasedValue,
+        hint,
+        textDirection,
+        rect,
+        tags,
+        textSelection,
+        scrollChildCount,
+        scrollIndex,
+        scrollPosition,
+        scrollExtentMax,
+        scrollExtentMin,
+        platformViewId,
+        maxValueLength,
+        currentValueLength,
+        transform,
+      ),
+      elevation,
+      thickness,
+      ui.hashList(customSemanticsActionIds),
+    );
+  }
+
+  static bool _sortedListsEqual(List<int>? left, List<int>? right) {
+    if (left == null && right == null)
+      return true;
+    if (left != null && right != null) {
+      if (left.length != right.length)
+        return false;
+      for (int i = 0; i < left.length; i++)
+        if (left[i] != right[i])
+          return false;
+      return true;
+    }
+    return false;
+  }
+}
+
+class _SemanticsDiagnosticableNode extends DiagnosticableNode<SemanticsNode> {
+  _SemanticsDiagnosticableNode({
+    String? name,
+    required SemanticsNode value,
+    required DiagnosticsTreeStyle? style,
+    required this.childOrder,
+  }) : super(
+    name: name,
+    value: value,
+    style: style,
+  );
+
+  final DebugSemanticsDumpOrder childOrder;
+
+  @override
+  List<DiagnosticsNode> getChildren() => value.debugDescribeChildren(childOrder: childOrder);
+}
+
+/// Provides hint values which override the default hints on supported
+/// platforms.
+///
+/// On iOS, these values are always ignored.
+@immutable
+class SemanticsHintOverrides extends DiagnosticableTree {
+  /// Creates a semantics hint overrides.
+  const SemanticsHintOverrides({
+    this.onTapHint,
+    this.onLongPressHint,
+  }) : assert(onTapHint != ''),
+       assert(onLongPressHint != '');
+
+  /// The hint text for a tap action.
+  ///
+  /// If null, the standard hint is used instead.
+  ///
+  /// The hint should describe what happens when a tap occurs, not the
+  /// manner in which a tap is accomplished.
+  ///
+  /// Bad: 'Double tap to show movies'.
+  /// Good: 'show movies'.
+  final String? onTapHint;
+
+  /// The hint text for a long press action.
+  ///
+  /// If null, the standard hint is used instead.
+  ///
+  /// The hint should describe what happens when a long press occurs, not
+  /// the manner in which the long press is accomplished.
+  ///
+  /// Bad: 'Double tap and hold to show tooltip'.
+  /// Good: 'show tooltip'.
+  final String? onLongPressHint;
+
+  /// Whether there are any non-null hint values.
+  bool get isNotEmpty => onTapHint != null || onLongPressHint != null;
+
+  @override
+  int get hashCode => ui.hashValues(onTapHint, onLongPressHint);
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is SemanticsHintOverrides
+        && other.onTapHint == onTapHint
+        && other.onLongPressHint == onLongPressHint;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(StringProperty('onTapHint', onTapHint, defaultValue: null));
+    properties.add(StringProperty('onLongPressHint', onLongPressHint, defaultValue: null));
+  }
+}
+
+/// Contains properties used by assistive technologies to make the application
+/// more accessible.
+///
+/// The properties of this class are used to generate a [SemanticsNode]s in the
+/// semantics tree.
+@immutable
+class SemanticsProperties extends DiagnosticableTree {
+  /// Creates a semantic annotation.
+  const SemanticsProperties({
+    this.enabled,
+    this.checked,
+    this.selected,
+    this.toggled,
+    this.button,
+    this.link,
+    this.header,
+    this.textField,
+    this.slider,
+    this.readOnly,
+    this.focusable,
+    this.focused,
+    this.inMutuallyExclusiveGroup,
+    this.hidden,
+    this.obscured,
+    this.multiline,
+    this.scopesRoute,
+    this.namesRoute,
+    this.image,
+    this.liveRegion,
+    this.maxValueLength,
+    this.currentValueLength,
+    this.label,
+    this.value,
+    this.increasedValue,
+    this.decreasedValue,
+    this.hint,
+    this.hintOverrides,
+    this.textDirection,
+    this.sortKey,
+    this.tagForChildren,
+    this.onTap,
+    this.onLongPress,
+    this.onScrollLeft,
+    this.onScrollRight,
+    this.onScrollUp,
+    this.onScrollDown,
+    this.onIncrease,
+    this.onDecrease,
+    this.onCopy,
+    this.onCut,
+    this.onPaste,
+    this.onMoveCursorForwardByCharacter,
+    this.onMoveCursorBackwardByCharacter,
+    this.onMoveCursorForwardByWord,
+    this.onMoveCursorBackwardByWord,
+    this.onSetSelection,
+    this.onDidGainAccessibilityFocus,
+    this.onDidLoseAccessibilityFocus,
+    this.onDismiss,
+    this.customSemanticsActions,
+  });
+
+  /// If non-null, indicates that this subtree represents something that can be
+  /// in an enabled or disabled state.
+  ///
+  /// For example, a button that a user can currently interact with would set
+  /// this field to true. A button that currently does not respond to user
+  /// interactions would set this field to false.
+  final bool? enabled;
+
+  /// If non-null, indicates that this subtree represents a checkbox
+  /// or similar widget with a "checked" state, and what its current
+  /// state is.
+  ///
+  /// This is mutually exclusive with [toggled].
+  final bool? checked;
+
+  /// If non-null, indicates that this subtree represents a toggle switch
+  /// or similar widget with an "on" state, and what its current
+  /// state is.
+  ///
+  /// This is mutually exclusive with [checked].
+  final bool? toggled;
+
+  /// If non-null indicates that this subtree represents something that can be
+  /// in a selected or unselected state, and what its current state is.
+  ///
+  /// The active tab in a tab bar for example is considered "selected", whereas
+  /// all other tabs are unselected.
+  final bool? selected;
+
+  /// If non-null, indicates that this subtree represents a button.
+  ///
+  /// TalkBack/VoiceOver provides users with the hint "button" when a button
+  /// is focused.
+  final bool? button;
+
+  /// If non-null, indicates that this subtree represents a link.
+  ///
+  /// iOS's VoiceOver provides users with a unique hint when a link is focused.
+  /// Android's Talkback will announce a link hint the same way it does a
+  /// button.
+  final bool? link;
+
+  /// If non-null, indicates that this subtree represents a header.
+  ///
+  /// A header divides into sections. For example, an address book application
+  /// might define headers A, B, C, etc. to divide the list of alphabetically
+  /// sorted contacts into sections.
+  final bool? header;
+
+  /// If non-null, indicates that this subtree represents a text field.
+  ///
+  /// TalkBack/VoiceOver provide special affordances to enter text into a
+  /// text field.
+  final bool? textField;
+
+  /// If non-null, indicates that this subtree represents a slider.
+  ///
+  /// Talkback/\VoiceOver provides users with the hint "slider" when a
+  /// slider is focused.
+  final bool? slider;
+
+  /// If non-null, indicates that this subtree is read only.
+  ///
+  /// Only applicable when [textField] is true.
+  ///
+  /// TalkBack/VoiceOver will treat it as non-editable text field.
+  final bool? readOnly;
+
+  /// If non-null, whether the node is able to hold input focus.
+  ///
+  /// If [focusable] is set to false, then [focused] must not be true.
+  ///
+  /// Input focus indicates that the node will receive keyboard events. It is not
+  /// to be confused with accessibility focus. Accessibility focus is the
+  /// green/black rectangular highlight that TalkBack/VoiceOver draws around the
+  /// element it is reading, and is separate from input focus.
+  final bool? focusable;
+
+  /// If non-null, whether the node currently holds input focus.
+  ///
+  /// At most one node in the tree should hold input focus at any point in time,
+  /// and it should not be set to true if [focusable] is false.
+  ///
+  /// Input focus indicates that the node will receive keyboard events. It is not
+  /// to be confused with accessibility focus. Accessibility focus is the
+  /// green/black rectangular highlight that TalkBack/VoiceOver draws around the
+  /// element it is reading, and is separate from input focus.
+  final bool? focused;
+
+  /// If non-null, whether a semantic node is in a mutually exclusive group.
+  ///
+  /// For example, a radio button is in a mutually exclusive group because only
+  /// one radio button in that group can be marked as [checked].
+  final bool? inMutuallyExclusiveGroup;
+
+  /// If non-null, whether the node is considered hidden.
+  ///
+  /// Hidden elements are currently not visible on screen. They may be covered
+  /// by other elements or positioned outside of the visible area of a viewport.
+  ///
+  /// Hidden elements cannot gain accessibility focus though regular touch. The
+  /// only way they can be focused is by moving the focus to them via linear
+  /// navigation.
+  ///
+  /// Platforms are free to completely ignore hidden elements and new platforms
+  /// are encouraged to do so.
+  ///
+  /// Instead of marking an element as hidden it should usually be excluded from
+  /// the semantics tree altogether. Hidden elements are only included in the
+  /// semantics tree to work around platform limitations and they are mainly
+  /// used to implement accessibility scrolling on iOS.
+  final bool? hidden;
+
+  /// If non-null, whether [value] should be obscured.
+  ///
+  /// This option is usually set in combination with [textField] to indicate
+  /// that the text field contains a password (or other sensitive information).
+  /// Doing so instructs screen readers to not read out the [value].
+  final bool? obscured;
+
+  /// Whether the [value] is coming from a field that supports multiline text
+  /// editing.
+  ///
+  /// This option is only meaningful when [textField] is true to indicate
+  /// whether it's a single-line or multiline text field.
+  ///
+  /// This option is null when [textField] is false.
+  final bool? multiline;
+
+  /// If non-null, whether the node corresponds to the root of a subtree for
+  /// which a route name should be announced.
+  ///
+  /// Generally, this is set in combination with
+  /// [SemanticsConfiguration.explicitChildNodes], since nodes with this flag
+  /// are not considered focusable by Android or iOS.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsFlag.scopesRoute] for a description of how the announced
+  ///    value is selected.
+  final bool? scopesRoute;
+
+  /// If non-null, whether the node contains the semantic label for a route.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsFlag.namesRoute] for a description of how the name is used.
+  final bool? namesRoute;
+
+  /// If non-null, whether the node represents an image.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsFlag.isImage], for the flag this setting controls.
+  final bool? image;
+
+  /// If non-null, whether the node should be considered a live region.
+  ///
+  /// On Android, when the label changes on a live region semantics node,
+  /// TalkBack will make a polite announcement of the current label. This
+  /// announcement occurs even if the node is not focused, but only if the label
+  /// has changed since the last update.
+  ///
+  /// On iOS, no announcements are made but the node is marked as
+  /// `UIAccessibilityTraitUpdatesFrequently`.
+  ///
+  /// An example of a live region is the [SnackBar] widget. When it appears
+  /// on the screen it may be difficult to focus to read the label. A live
+  /// region causes an initial polite announcement to be generated
+  /// automatically.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsFlag.isLiveRegion], the semantics flag this setting controls.
+  ///  * [SemanticsConfiguration.liveRegion], for a full description of a live region.
+  final bool? liveRegion;
+
+  /// The maximum number of characters that can be entered into an editable
+  /// text field.
+  ///
+  /// For the purpose of this function a character is defined as one Unicode
+  /// scalar value.
+  ///
+  /// This should only be set when [textField] is true. Defaults to null,
+  /// which means no limit is imposed on the text field.
+  final int? maxValueLength;
+
+  /// The current number of characters that have been entered into an editable
+  /// text field.
+  ///
+  /// For the purpose of this function a character is defined as one Unicode
+  /// scalar value.
+  ///
+  /// This should only be set when [textField] is true. Must be set when
+  /// [maxValueLength] is set.
+  final int? currentValueLength;
+
+  /// Provides a textual description of the widget.
+  ///
+  /// If a label is provided, there must either by an ambient [Directionality]
+  /// or an explicit [textDirection] should be provided.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsConfiguration.label] for a description of how this is exposed
+  ///    in TalkBack and VoiceOver.
+  final String? label;
+
+  /// Provides a textual description of the value of the widget.
+  ///
+  /// If a value is provided, there must either by an ambient [Directionality]
+  /// or an explicit [textDirection] should be provided.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsConfiguration.value] for a description of how this is exposed
+  ///    in TalkBack and VoiceOver.
+  final String? value;
+
+  /// The value that [value] will become after a [SemanticsAction.increase]
+  /// action has been performed on this widget.
+  ///
+  /// If a value is provided, [onIncrease] must also be set and there must
+  /// either be an ambient [Directionality] or an explicit [textDirection]
+  /// must be provided.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsConfiguration.increasedValue] for a description of how this
+  ///    is exposed in TalkBack and VoiceOver.
+  final String? increasedValue;
+
+  /// The value that [value] will become after a [SemanticsAction.decrease]
+  /// action has been performed on this widget.
+  ///
+  /// If a value is provided, [onDecrease] must also be set and there must
+  /// either be an ambient [Directionality] or an explicit [textDirection]
+  /// must be provided.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsConfiguration.decreasedValue] for a description of how this
+  ///    is exposed in TalkBack and VoiceOver.
+  final String? decreasedValue;
+
+  /// Provides a brief textual description of the result of an action performed
+  /// on the widget.
+  ///
+  /// If a hint is provided, there must either be an ambient [Directionality]
+  /// or an explicit [textDirection] should be provided.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsConfiguration.hint] for a description of how this is exposed
+  ///    in TalkBack and VoiceOver.
+  final String? hint;
+
+  /// Provides hint values which override the default hints on supported
+  /// platforms.
+  ///
+  /// On Android, If no hint overrides are used then default [hint] will be
+  /// combined with the [label]. Otherwise, the [hint] will be ignored as long
+  /// as there as at least one non-null hint override.
+  ///
+  /// On iOS, these are always ignored and the default [hint] is used instead.
+  final SemanticsHintOverrides? hintOverrides;
+
+  /// The reading direction of the [label], [value], [hint], [increasedValue],
+  /// and [decreasedValue].
+  ///
+  /// Defaults to the ambient [Directionality].
+  final TextDirection? textDirection;
+
+  /// Determines the position of this node among its siblings in the traversal
+  /// sort order.
+  ///
+  /// This is used to describe the order in which the semantic node should be
+  /// traversed by the accessibility services on the platform (e.g. VoiceOver
+  /// on iOS and TalkBack on Android).
+  final SemanticsSortKey? sortKey;
+
+  /// A tag to be applied to the child [SemanticsNode]s of this widget.
+  ///
+  /// The tag is added to all child [SemanticsNode]s that pass through the
+  /// [RenderObject] corresponding to this widget while looking to be attached
+  /// to a parent SemanticsNode.
+  ///
+  /// Tags are used to communicate to a parent SemanticsNode that a child
+  /// SemanticsNode was passed through a particular RenderObject. The parent can
+  /// use this information to determine the shape of the semantics tree.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsConfiguration.addTagForChildren], to which the tags provided
+  ///    here will be passed.
+  final SemanticsTag? tagForChildren;
+
+  /// The handler for [SemanticsAction.tap].
+  ///
+  /// This is the semantic equivalent of a user briefly tapping the screen with
+  /// the finger without moving it. For example, a button should implement this
+  /// action.
+  ///
+  /// VoiceOver users on iOS and TalkBack users on Android can trigger this
+  /// action by double-tapping the screen while an element is focused.
+  final VoidCallback? onTap;
+
+  /// The handler for [SemanticsAction.longPress].
+  ///
+  /// This is the semantic equivalent of a user pressing and holding the screen
+  /// with the finger for a few seconds without moving it.
+  ///
+  /// VoiceOver users on iOS and TalkBack users on Android can trigger this
+  /// action by double-tapping the screen without lifting the finger after the
+  /// second tap.
+  final VoidCallback? onLongPress;
+
+  /// The handler for [SemanticsAction.scrollLeft].
+  ///
+  /// This is the semantic equivalent of a user moving their finger across the
+  /// screen from right to left. It should be recognized by controls that are
+  /// horizontally scrollable.
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping left with three
+  /// fingers. TalkBack users on Android can trigger this action by swiping
+  /// right and then left in one motion path. On Android, [onScrollUp] and
+  /// [onScrollLeft] share the same gesture. Therefore, only on of them should
+  /// be provided.
+  final VoidCallback? onScrollLeft;
+
+  /// The handler for [SemanticsAction.scrollRight].
+  ///
+  /// This is the semantic equivalent of a user moving their finger across the
+  /// screen from left to right. It should be recognized by controls that are
+  /// horizontally scrollable.
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping right with three
+  /// fingers. TalkBack users on Android can trigger this action by swiping
+  /// left and then right in one motion path. On Android, [onScrollDown] and
+  /// [onScrollRight] share the same gesture. Therefore, only on of them should
+  /// be provided.
+  final VoidCallback? onScrollRight;
+
+  /// The handler for [SemanticsAction.scrollUp].
+  ///
+  /// This is the semantic equivalent of a user moving their finger across the
+  /// screen from bottom to top. It should be recognized by controls that are
+  /// vertically scrollable.
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping up with three
+  /// fingers. TalkBack users on Android can trigger this action by swiping
+  /// right and then left in one motion path. On Android, [onScrollUp] and
+  /// [onScrollLeft] share the same gesture. Therefore, only on of them should
+  /// be provided.
+  final VoidCallback? onScrollUp;
+
+  /// The handler for [SemanticsAction.scrollDown].
+  ///
+  /// This is the semantic equivalent of a user moving their finger across the
+  /// screen from top to bottom. It should be recognized by controls that are
+  /// vertically scrollable.
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping down with three
+  /// fingers. TalkBack users on Android can trigger this action by swiping
+  /// left and then right in one motion path. On Android, [onScrollDown] and
+  /// [onScrollRight] share the same gesture. Therefore, only on of them should
+  /// be provided.
+  final VoidCallback? onScrollDown;
+
+  /// The handler for [SemanticsAction.increase].
+  ///
+  /// This is a request to increase the value represented by the widget. For
+  /// example, this action might be recognized by a slider control.
+  ///
+  /// If a [value] is set, [increasedValue] must also be provided and
+  /// [onIncrease] must ensure that [value] will be set to [increasedValue].
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping up with one
+  /// finger. TalkBack users on Android can trigger this action by pressing the
+  /// volume up button.
+  final VoidCallback? onIncrease;
+
+  /// The handler for [SemanticsAction.decrease].
+  ///
+  /// This is a request to decrease the value represented by the widget. For
+  /// example, this action might be recognized by a slider control.
+  ///
+  /// If a [value] is set, [decreasedValue] must also be provided and
+  /// [onDecrease] must ensure that [value] will be set to [decreasedValue].
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping down with one
+  /// finger. TalkBack users on Android can trigger this action by pressing the
+  /// volume down button.
+  final VoidCallback? onDecrease;
+
+  /// The handler for [SemanticsAction.copy].
+  ///
+  /// This is a request to copy the current selection to the clipboard.
+  ///
+  /// TalkBack users on Android can trigger this action from the local context
+  /// menu of a text field, for example.
+  final VoidCallback? onCopy;
+
+  /// The handler for [SemanticsAction.cut].
+  ///
+  /// This is a request to cut the current selection and place it in the
+  /// clipboard.
+  ///
+  /// TalkBack users on Android can trigger this action from the local context
+  /// menu of a text field, for example.
+  final VoidCallback? onCut;
+
+  /// The handler for [SemanticsAction.paste].
+  ///
+  /// This is a request to paste the current content of the clipboard.
+  ///
+  /// TalkBack users on Android can trigger this action from the local context
+  /// menu of a text field, for example.
+  final VoidCallback? onPaste;
+
+  /// The handler for [SemanticsAction.moveCursorForwardByCharacter].
+  ///
+  /// This handler is invoked when the user wants to move the cursor in a
+  /// text field forward by one character.
+  ///
+  /// TalkBack users can trigger this by pressing the volume up key while the
+  /// input focus is in a text field.
+  final MoveCursorHandler? onMoveCursorForwardByCharacter;
+
+  /// The handler for [SemanticsAction.moveCursorBackwardByCharacter].
+  ///
+  /// This handler is invoked when the user wants to move the cursor in a
+  /// text field backward by one character.
+  ///
+  /// TalkBack users can trigger this by pressing the volume down key while the
+  /// input focus is in a text field.
+  final MoveCursorHandler? onMoveCursorBackwardByCharacter;
+
+  /// The handler for [SemanticsAction.moveCursorForwardByWord].
+  ///
+  /// This handler is invoked when the user wants to move the cursor in a
+  /// text field backward by one word.
+  ///
+  /// TalkBack users can trigger this by pressing the volume down key while the
+  /// input focus is in a text field.
+  final MoveCursorHandler? onMoveCursorForwardByWord;
+
+  /// The handler for [SemanticsAction.moveCursorBackwardByWord].
+  ///
+  /// This handler is invoked when the user wants to move the cursor in a
+  /// text field backward by one word.
+  ///
+  /// TalkBack users can trigger this by pressing the volume down key while the
+  /// input focus is in a text field.
+  final MoveCursorHandler? onMoveCursorBackwardByWord;
+
+  /// The handler for [SemanticsAction.setSelection].
+  ///
+  /// This handler is invoked when the user either wants to change the currently
+  /// selected text in a text field or change the position of the cursor.
+  ///
+  /// TalkBack users can trigger this handler by selecting "Move cursor to
+  /// beginning/end" or "Select all" from the local context menu.
+  final SetSelectionHandler? onSetSelection;
+
+  /// The handler for [SemanticsAction.didGainAccessibilityFocus].
+  ///
+  /// This handler is invoked when the node annotated with this handler gains
+  /// the accessibility focus. The accessibility focus is the
+  /// green (on Android with TalkBack) or black (on iOS with VoiceOver)
+  /// rectangle shown on screen to indicate what element an accessibility
+  /// user is currently interacting with.
+  ///
+  /// The accessibility focus is different from the input focus. The input focus
+  /// is usually held by the element that currently responds to keyboard inputs.
+  /// Accessibility focus and input focus can be held by two different nodes!
+  ///
+  /// See also:
+  ///
+  ///  * [onDidLoseAccessibilityFocus], which is invoked when the accessibility
+  ///    focus is removed from the node.
+  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
+  final VoidCallback? onDidGainAccessibilityFocus;
+
+  /// The handler for [SemanticsAction.didLoseAccessibilityFocus].
+  ///
+  /// This handler is invoked when the node annotated with this handler
+  /// loses the accessibility focus. The accessibility focus is
+  /// the green (on Android with TalkBack) or black (on iOS with VoiceOver)
+  /// rectangle shown on screen to indicate what element an accessibility
+  /// user is currently interacting with.
+  ///
+  /// The accessibility focus is different from the input focus. The input focus
+  /// is usually held by the element that currently responds to keyboard inputs.
+  /// Accessibility focus and input focus can be held by two different nodes!
+  ///
+  /// See also:
+  ///
+  ///  * [onDidGainAccessibilityFocus], which is invoked when the node gains
+  ///    accessibility focus.
+  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
+  final VoidCallback? onDidLoseAccessibilityFocus;
+
+  /// The handler for [SemanticsAction.dismiss].
+  ///
+  /// This is a request to dismiss the currently focused node.
+  ///
+  /// TalkBack users on Android can trigger this action in the local context
+  /// menu, and VoiceOver users on iOS can trigger this action with a standard
+  /// gesture or menu option.
+  final VoidCallback? onDismiss;
+
+  /// A map from each supported [CustomSemanticsAction] to a provided handler.
+  ///
+  /// The handler associated with each custom action is called whenever a
+  /// semantics action of type [SemanticsAction.customAction] is received. The
+  /// provided argument will be an identifier used to retrieve an instance of
+  /// a custom action which can then retrieve the correct handler from this map.
+  ///
+  /// See also:
+  ///
+  ///  * [CustomSemanticsAction], for an explanation of custom actions.
+  final Map<CustomSemanticsAction, VoidCallback>? customSemanticsActions;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('checked', checked, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('selected', selected, defaultValue: null));
+    properties.add(StringProperty('label', label, defaultValue: ''));
+    properties.add(StringProperty('value', value));
+    properties.add(StringProperty('hint', hint));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    properties.add(DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
+    properties.add(DiagnosticsProperty<SemanticsHintOverrides>('hintOverrides', hintOverrides));
+  }
+
+  @override
+  String toStringShort() => objectRuntimeType(this, 'SemanticsProperties'); // the hashCode isn't important since we're immutable
+}
+
+/// In tests use this function to reset the counter used to generate
+/// [SemanticsNode.id].
+void debugResetSemanticsIdCounter() {
+  SemanticsNode._lastIdentifier = 0;
+}
+
+/// A node that represents some semantic data.
+///
+/// The semantics tree is maintained during the semantics phase of the pipeline
+/// (i.e., during [PipelineOwner.flushSemantics]), which happens after
+/// compositing. The semantics tree is then uploaded into the engine for use
+/// by assistive technology.
+class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
+  /// Creates a semantic node.
+  ///
+  /// Each semantic node has a unique identifier that is assigned when the node
+  /// is created.
+  SemanticsNode({
+    this.key,
+    VoidCallback? showOnScreen,
+  }) : id = _generateNewId(),
+       _showOnScreen = showOnScreen;
+
+  /// Creates a semantic node to represent the root of the semantics tree.
+  ///
+  /// The root node is assigned an identifier of zero.
+  SemanticsNode.root({
+    this.key,
+    VoidCallback? showOnScreen,
+    required SemanticsOwner owner,
+  }) : id = 0,
+       _showOnScreen = showOnScreen {
+    attach(owner);
+  }
+
+
+  // The maximal semantic node identifier generated by the framework.
+  //
+  // The identifier range for semantic node IDs is split into 2, the least significant 16 bits are
+  // reserved for framework generated IDs(generated with _generateNewId), and most significant 32
+  // bits are reserved for engine generated IDs.
+  static const int _maxFrameworkAccessibilityIdentifier = (1<<16) - 1;
+
+  static int _lastIdentifier = 0;
+  static int _generateNewId() {
+    _lastIdentifier = (_lastIdentifier + 1) % _maxFrameworkAccessibilityIdentifier;
+    return _lastIdentifier;
+  }
+
+  /// Uniquely identifies this node in the list of sibling nodes.
+  ///
+  /// Keys are used during the construction of the semantics tree. They are not
+  /// transferred to the engine.
+  final Key? key;
+
+  /// The unique identifier for this node.
+  ///
+  /// The root node has an id of zero. Other nodes are given a unique id when
+  /// they are created.
+  final int id;
+
+  final VoidCallback? _showOnScreen;
+
+  // GEOMETRY
+
+  /// The transform from this node's coordinate system to its parent's coordinate system.
+  ///
+  /// By default, the transform is null, which represents the identity
+  /// transformation (i.e., that this node has the same coordinate system as its
+  /// parent).
+  Matrix4? get transform => _transform;
+  Matrix4? _transform;
+  set transform(Matrix4? value) {
+    if (!MatrixUtils.matrixEquals(_transform, value)) {
+      _transform = value == null || MatrixUtils.isIdentity(value) ? null : value;
+      _markDirty();
+    }
+  }
+
+  /// The bounding box for this node in its coordinate system.
+  Rect get rect => _rect;
+  Rect _rect = Rect.zero;
+  set rect(Rect value) {
+    assert(value != null);
+    assert(value.isFinite, '$this (with $owner) tried to set a non-finite rect.');
+    if (_rect != value) {
+      _rect = value;
+      _markDirty();
+    }
+  }
+
+  /// The semantic clip from an ancestor that was applied to this node.
+  ///
+  /// Expressed in the coordinate system of the node. May be null if no clip has
+  /// been applied.
+  ///
+  /// Descendant [SemanticsNode]s that are positioned outside of this rect will
+  /// be excluded from the semantics tree. Descendant [SemanticsNode]s that are
+  /// overlapping with this rect, but are outside of [parentPaintClipRect] will
+  /// be included in the tree, but they will be marked as hidden because they
+  /// are assumed to be not visible on screen.
+  ///
+  /// If this rect is null, all descendant [SemanticsNode]s outside of
+  /// [parentPaintClipRect] will be excluded from the tree.
+  ///
+  /// If this rect is non-null it has to completely enclose
+  /// [parentPaintClipRect]. If [parentPaintClipRect] is null this property is
+  /// also null.
+  Rect? parentSemanticsClipRect;
+
+  /// The paint clip from an ancestor that was applied to this node.
+  ///
+  /// Expressed in the coordinate system of the node. May be null if no clip has
+  /// been applied.
+  ///
+  /// Descendant [SemanticsNode]s that are positioned outside of this rect will
+  /// either be excluded from the semantics tree (if they have no overlap with
+  /// [parentSemanticsClipRect]) or they will be included and marked as hidden
+  /// (if they are overlapping with [parentSemanticsClipRect]).
+  ///
+  /// This rect is completely enclosed by [parentSemanticsClipRect].
+  ///
+  /// If this rect is null [parentSemanticsClipRect] also has to be null.
+  Rect? parentPaintClipRect;
+
+  /// The elevation adjustment that the parent imposes on this node.
+  ///
+  /// The [elevation] property is relative to the elevation of the parent
+  /// [SemanticsNode]. However, as [SemanticsConfiguration]s from various
+  /// ascending [RenderObject]s are merged into each other to form that
+  /// [SemanticsNode] the parent’s elevation may change. This requires an
+  /// adjustment of the child’s relative elevation which is represented by this
+  /// value.
+  ///
+  /// The value is rarely accessed directly. Instead, for most use cases the
+  /// [elevation] value should be used, which includes this adjustment.
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], the actual elevation of this [SemanticsNode].
+  double? elevationAdjustment;
+
+  /// The index of this node within the parent's list of semantic children.
+  ///
+  /// This includes all semantic nodes, not just those currently in the
+  /// child list. For example, if a scrollable has five children but the first
+  /// two are not visible (and thus not included in the list of children), then
+  /// the index of the last node will still be 4.
+  int? indexInParent;
+
+  /// Whether the node is invisible.
+  ///
+  /// A node whose [rect] is outside of the bounds of the screen and hence not
+  /// reachable for users is considered invisible if its semantic information
+  /// is not merged into a (partially) visible parent as indicated by
+  /// [isMergedIntoParent].
+  ///
+  /// An invisible node can be safely dropped from the semantic tree without
+  /// loosing semantic information that is relevant for describing the content
+  /// currently shown on screen.
+  bool get isInvisible => !isMergedIntoParent && rect.isEmpty;
+
+  // MERGING
+
+  /// Whether this node merges its semantic information into an ancestor node.
+  bool get isMergedIntoParent => _isMergedIntoParent;
+  bool _isMergedIntoParent = false;
+  set isMergedIntoParent(bool value) {
+    assert(value != null);
+    if (_isMergedIntoParent == value)
+      return;
+    _isMergedIntoParent = value;
+    _markDirty();
+  }
+
+  /// Whether this node is taking part in a merge of semantic information.
+  ///
+  /// This returns true if the node is either merged into an ancestor node or if
+  /// decedent nodes are merged into this node.
+  ///
+  /// See also:
+  ///
+  ///  * [isMergedIntoParent]
+  ///  * [mergeAllDescendantsIntoThisNode]
+  bool get isPartOfNodeMerging => mergeAllDescendantsIntoThisNode || isMergedIntoParent;
+
+  /// Whether this node and all of its descendants should be treated as one logical entity.
+  bool get mergeAllDescendantsIntoThisNode => _mergeAllDescendantsIntoThisNode;
+  bool _mergeAllDescendantsIntoThisNode = _kEmptyConfig.isMergingSemanticsOfDescendants;
+
+
+  // CHILDREN
+
+  /// Contains the children in inverse hit test order (i.e. paint order).
+  List<SemanticsNode>? _children;
+
+  /// A snapshot of `newChildren` passed to [_replaceChildren] that we keep in
+  /// debug mode. It supports the assertion that user does not mutate the list
+  /// of children.
+  late List<SemanticsNode> _debugPreviousSnapshot;
+
+  void _replaceChildren(List<SemanticsNode> newChildren) {
+    assert(!newChildren.any((SemanticsNode child) => child == this));
+    assert(() {
+      if (identical(newChildren, _children)) {
+        final List<DiagnosticsNode> mutationErrors = <DiagnosticsNode>[];
+        if (newChildren.length != _debugPreviousSnapshot.length) {
+          mutationErrors.add(ErrorDescription(
+            "The list's length has changed from ${_debugPreviousSnapshot.length} "
+            'to ${newChildren.length}.'
+          ));
+        } else {
+          for (int i = 0; i < newChildren.length; i++) {
+            if (!identical(newChildren[i], _debugPreviousSnapshot[i])) {
+              if (mutationErrors.isNotEmpty) {
+                mutationErrors.add(ErrorSpacer());
+              }
+              mutationErrors.add(ErrorDescription('Child node at position $i was replaced:'));
+              mutationErrors.add(newChildren[i].toDiagnosticsNode(name: 'Previous child', style: DiagnosticsTreeStyle.singleLine));
+              mutationErrors.add(_debugPreviousSnapshot[i].toDiagnosticsNode(name: 'New child', style: DiagnosticsTreeStyle.singleLine));
+            }
+          }
+        }
+        if (mutationErrors.isNotEmpty) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('Failed to replace child semantics nodes because the list of `SemanticsNode`s was mutated.'),
+            ErrorHint('Instead of mutating the existing list, create a new list containing the desired `SemanticsNode`s.'),
+            ErrorDescription('Error details:'),
+            ...mutationErrors,
+          ]);
+        }
+      }
+      assert(!newChildren.any((SemanticsNode node) => node.isMergedIntoParent) || isPartOfNodeMerging);
+
+      _debugPreviousSnapshot = List<SemanticsNode>.from(newChildren);
+
+      SemanticsNode ancestor = this;
+      while (ancestor.parent is SemanticsNode)
+        ancestor = ancestor.parent!;
+      assert(!newChildren.any((SemanticsNode child) => child == ancestor));
+      return true;
+    }());
+    assert(() {
+      final Set<SemanticsNode> seenChildren = <SemanticsNode>{};
+      for (final SemanticsNode child in newChildren)
+        assert(seenChildren.add(child)); // check for duplicate adds
+      return true;
+    }());
+
+    // The goal of this function is updating sawChange.
+    if (_children != null) {
+      for (final SemanticsNode child in _children!)
+        child._dead = true;
+    }
+    for (final SemanticsNode child in newChildren) {
+      assert(!child.isInvisible, 'Child $child is invisible and should not be added as a child of $this.');
+      child._dead = false;
+    }
+    bool sawChange = false;
+    if (_children != null) {
+      for (final SemanticsNode child in _children!) {
+        if (child._dead) {
+          if (child.parent == this) {
+            // we might have already had our child stolen from us by
+            // another node that is deeper in the tree.
+            dropChild(child);
+          }
+          sawChange = true;
+        }
+      }
+    }
+    for (final SemanticsNode child in newChildren) {
+      if (child.parent != this) {
+        if (child.parent != null) {
+          // we're rebuilding the tree from the bottom up, so it's possible
+          // that our child was, in the last pass, a child of one of our
+          // ancestors. In that case, we drop the child eagerly here.
+          // TODO(ianh): Find a way to assert that the same node didn't
+          // actually appear in the tree in two places.
+          child.parent?.dropChild(child);
+        }
+        assert(!child.attached);
+        adoptChild(child);
+        sawChange = true;
+      }
+    }
+    if (!sawChange && _children != null) {
+      assert(newChildren != null);
+      assert(newChildren.length == _children!.length);
+      // Did the order change?
+      for (int i = 0; i < _children!.length; i++) {
+        if (_children![i].id != newChildren[i].id) {
+          sawChange = true;
+          break;
+        }
+      }
+    }
+    _children = newChildren;
+    if (sawChange)
+      _markDirty();
+  }
+
+  /// Whether this node has a non-zero number of children.
+  bool get hasChildren => _children?.isNotEmpty ?? false;
+  bool _dead = false;
+
+  /// The number of children this node has.
+  int get childrenCount => hasChildren ? _children!.length : 0;
+
+  /// Visits the immediate children of this node.
+  ///
+  /// This function calls visitor for each immediate child until visitor returns
+  /// false. Returns true if all the visitor calls returned true, otherwise
+  /// returns false.
+  void visitChildren(SemanticsNodeVisitor visitor) {
+    if (_children != null) {
+      for (final SemanticsNode child in _children!) {
+        if (!visitor(child))
+          return;
+      }
+    }
+  }
+
+  /// Visit all the descendants of this node.
+  ///
+  /// This function calls visitor for each descendant in a pre-order traversal
+  /// until visitor returns false. Returns true if all the visitor calls
+  /// returned true, otherwise returns false.
+  bool _visitDescendants(SemanticsNodeVisitor visitor) {
+    if (_children != null) {
+      for (final SemanticsNode child in _children!) {
+        if (!visitor(child) || !child._visitDescendants(visitor))
+          return false;
+      }
+    }
+    return true;
+  }
+
+  // AbstractNode OVERRIDES
+
+  @override
+  SemanticsOwner? get owner => super.owner as SemanticsOwner?;
+
+  @override
+  SemanticsNode? get parent => super.parent as SemanticsNode?;
+
+  @override
+  void redepthChildren() {
+    _children?.forEach(redepthChild);
+  }
+
+  @override
+  void attach(SemanticsOwner owner) {
+    super.attach(owner);
+    assert(!owner._nodes.containsKey(id));
+    owner._nodes[id] = this;
+    owner._detachedNodes.remove(this);
+    if (_dirty) {
+      _dirty = false;
+      _markDirty();
+    }
+    if (_children != null) {
+      for (final SemanticsNode child in _children!)
+        child.attach(owner);
+    }
+  }
+
+  @override
+  void detach() {
+    assert(owner!._nodes.containsKey(id));
+    assert(!owner!._detachedNodes.contains(this));
+    owner!._nodes.remove(id);
+    owner!._detachedNodes.add(this);
+    super.detach();
+    assert(owner == null);
+    if (_children != null) {
+      for (final SemanticsNode child in _children!) {
+        // The list of children may be stale and may contain nodes that have
+        // been assigned to a different parent.
+        if (child.parent == this)
+          child.detach();
+      }
+    }
+    // The other side will have forgotten this node if we ever send
+    // it again, so make sure to mark it dirty so that it'll get
+    // sent if it is resurrected.
+    _markDirty();
+  }
+
+  // DIRTY MANAGEMENT
+
+  bool _dirty = false;
+  void _markDirty() {
+    if (_dirty)
+      return;
+    _dirty = true;
+    if (attached) {
+      assert(!owner!._detachedNodes.contains(this));
+      owner!._dirtyNodes.add(this);
+    }
+  }
+
+  bool _isDifferentFromCurrentSemanticAnnotation(SemanticsConfiguration config) {
+    return _label != config.label ||
+        _hint != config.hint ||
+        _elevation != config.elevation ||
+        _thickness != config.thickness ||
+        _decreasedValue != config.decreasedValue ||
+        _value != config.value ||
+        _increasedValue != config.increasedValue ||
+        _flags != config._flags ||
+        _textDirection != config.textDirection ||
+        _sortKey != config._sortKey ||
+        _textSelection != config._textSelection ||
+        _scrollPosition != config._scrollPosition ||
+        _scrollExtentMax != config._scrollExtentMax ||
+        _scrollExtentMin != config._scrollExtentMin ||
+        _actionsAsBits != config._actionsAsBits ||
+        indexInParent != config.indexInParent ||
+        platformViewId != config.platformViewId ||
+        _maxValueLength != config._maxValueLength ||
+        _currentValueLength != config._currentValueLength ||
+        _mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants;
+  }
+
+  // TAGS, LABELS, ACTIONS
+
+  Map<SemanticsAction, _SemanticsActionHandler> _actions = _kEmptyConfig._actions;
+  Map<CustomSemanticsAction, VoidCallback> _customSemanticsActions = _kEmptyConfig._customSemanticsActions;
+
+  int _actionsAsBits = _kEmptyConfig._actionsAsBits;
+
+  /// The [SemanticsTag]s this node is tagged with.
+  ///
+  /// Tags are used during the construction of the semantics tree. They are not
+  /// transferred to the engine.
+  Set<SemanticsTag>? tags;
+
+  /// Whether this node is tagged with `tag`.
+  bool isTagged(SemanticsTag tag) => tags != null && tags!.contains(tag);
+
+  int _flags = _kEmptyConfig._flags;
+
+  /// Whether this node currently has a given [SemanticsFlag].
+  bool hasFlag(SemanticsFlag flag) => _flags & flag.index != 0;
+
+  /// A textual description of this node.
+  ///
+  /// The reading direction is given by [textDirection].
+  String get label => _label;
+  String _label = _kEmptyConfig.label;
+
+  /// A textual description for the current value of the node.
+  ///
+  /// The reading direction is given by [textDirection].
+  String get value => _value;
+  String _value = _kEmptyConfig.value;
+
+  /// The value that [value] will have after a [SemanticsAction.decrease] action
+  /// has been performed.
+  ///
+  /// This property is only valid if the [SemanticsAction.decrease] action is
+  /// available on this node.
+  ///
+  /// The reading direction is given by [textDirection].
+  String get decreasedValue => _decreasedValue;
+  String _decreasedValue = _kEmptyConfig.decreasedValue;
+
+  /// The value that [value] will have after a [SemanticsAction.increase] action
+  /// has been performed.
+  ///
+  /// This property is only valid if the [SemanticsAction.increase] action is
+  /// available on this node.
+  ///
+  /// The reading direction is given by [textDirection].
+  String get increasedValue => _increasedValue;
+  String _increasedValue = _kEmptyConfig.increasedValue;
+
+  /// A brief description of the result of performing an action on this node.
+  ///
+  /// The reading direction is given by [textDirection].
+  String get hint => _hint;
+  String _hint = _kEmptyConfig.hint;
+
+  /// The elevation along the z-axis at which the [rect] of this [SemanticsNode]
+  /// is located above its parent.
+  ///
+  /// The value is relative to the parent's [elevation]. The sum of the
+  /// [elevation]s of all ancestor node plus this value determines the absolute
+  /// elevation of this [SemanticsNode].
+  ///
+  /// See also:
+  ///
+  ///  * [thickness], which describes how much space in z-direction this
+  ///    [SemanticsNode] occupies starting at this [elevation].
+  ///  * [elevationAdjustment], which has been used to calculate this value.
+  double get elevation => _elevation;
+  double _elevation = _kEmptyConfig.elevation;
+
+  /// Describes how much space the [SemanticsNode] takes up along the z-axis.
+  ///
+  /// A [SemanticsNode] represents multiple [RenderObject]s, which can be
+  /// located at various elevations in 3D. The [thickness] is the difference
+  /// between the absolute elevations of the lowest and highest [RenderObject]
+  /// represented by this [SemanticsNode]. In other words, the thickness
+  /// describes how high the box is that this [SemanticsNode] occupies in three
+  /// dimensional space. The two other dimensions are defined by [rect].
+  ///
+  /// {@tool snippet}
+  /// The following code stacks three [PhysicalModel]s on top of each other
+  /// separated by non-zero elevations.
+  ///
+  /// [PhysicalModel] C is elevated 10.0 above [PhysicalModel] B, which in turn
+  /// is elevated 5.0 above [PhysicalModel] A. The side view of this
+  /// constellation looks as follows:
+  ///
+  /// ![A diagram illustrating the elevations of three PhysicalModels and their
+  /// corresponding SemanticsNodes.](https://flutter.github.io/assets-for-api-docs/assets/semantics/SemanticsNode.thickness.png)
+  ///
+  /// In this example the [RenderObject]s for [PhysicalModel] C and B share one
+  /// [SemanticsNode] Y. Given the elevations of those [RenderObject]s, this
+  /// [SemanticsNode] has a [thickness] of 10.0 and an elevation of 5.0 over
+  /// its parent [SemanticsNode] X.
+  /// ```dart
+  /// PhysicalModel( // A
+  ///   color: Colors.amber,
+  ///   elevation: 0.0,
+  ///   child: Semantics(
+  ///     explicitChildNodes: true,
+  ///     child: PhysicalModel( // B
+  ///       color: Colors.brown,
+  ///       elevation: 5.0,
+  ///       child: PhysicalModel( // C
+  ///         color: Colors.cyan,
+  ///         elevation: 10.0,
+  ///         child: Placeholder(),
+  ///       ),
+  ///     ),
+  ///   ),
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [elevation], which describes the elevation of the box defined by
+  ///    [thickness] and [rect] relative to the parent of this [SemanticsNode].
+  double get thickness => _thickness;
+  double _thickness = _kEmptyConfig.thickness;
+
+  /// Provides hint values which override the default hints on supported
+  /// platforms.
+  SemanticsHintOverrides? get hintOverrides => _hintOverrides;
+  SemanticsHintOverrides? _hintOverrides;
+
+  /// The reading direction for [label], [value], [hint], [increasedValue], and
+  /// [decreasedValue].
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection = _kEmptyConfig.textDirection;
+
+  /// Determines the position of this node among its siblings in the traversal
+  /// sort order.
+  ///
+  /// This is used to describe the order in which the semantic node should be
+  /// traversed by the accessibility services on the platform (e.g. VoiceOver
+  /// on iOS and TalkBack on Android).
+  SemanticsSortKey? get sortKey => _sortKey;
+  SemanticsSortKey? _sortKey;
+
+  /// The currently selected text (or the position of the cursor) within [value]
+  /// if this node represents a text field.
+  TextSelection? get textSelection => _textSelection;
+  TextSelection? _textSelection;
+
+  /// If this node represents a text field, this indicates whether or not it's
+  /// a multiline text field.
+  bool? get isMultiline => _isMultiline;
+  bool? _isMultiline;
+
+  /// The total number of scrollable children that contribute to semantics.
+  ///
+  /// If the number of children are unknown or unbounded, this value will be
+  /// null.
+  int? get scrollChildCount => _scrollChildCount;
+  int? _scrollChildCount;
+
+  /// The index of the first visible semantic child of a scroll node.
+  int? get scrollIndex => _scrollIndex;
+  int? _scrollIndex;
+
+  /// Indicates the current scrolling position in logical pixels if the node is
+  /// scrollable.
+  ///
+  /// The properties [scrollExtentMin] and [scrollExtentMax] indicate the valid
+  /// in-range values for this property. The value for [scrollPosition] may
+  /// (temporarily) be outside that range, e.g. during an overscroll.
+  ///
+  /// See also:
+  ///
+  ///  * [ScrollPosition.pixels], from where this value is usually taken.
+  double? get scrollPosition => _scrollPosition;
+  double? _scrollPosition;
+
+  /// Indicates the maximum in-range value for [scrollPosition] if the node is
+  /// scrollable.
+  ///
+  /// This value may be infinity if the scroll is unbound.
+  ///
+  /// See also:
+  ///
+  ///  * [ScrollPosition.maxScrollExtent], from where this value is usually taken.
+  double? get scrollExtentMax => _scrollExtentMax;
+  double? _scrollExtentMax;
+
+  /// Indicates the minimum in-range value for [scrollPosition] if the node is
+  /// scrollable.
+  ///
+  /// This value may be infinity if the scroll is unbound.
+  ///
+  /// See also:
+  ///
+  ///  * [ScrollPosition.minScrollExtent] from where this value is usually taken.
+  double? get scrollExtentMin => _scrollExtentMin;
+  double? _scrollExtentMin;
+
+  /// The id of the platform view, whose semantics nodes will be added as
+  /// children to this node.
+  ///
+  /// If this value is non-null, the SemanticsNode must not have any children
+  /// as those would be replaced by the semantics nodes of the referenced
+  /// platform view.
+  ///
+  /// See also:
+  ///
+  ///  * [AndroidView], which is the platform view for Android.
+  ///  * [UiKitView], which is the platform view for iOS.
+  int? get platformViewId => _platformViewId;
+  int? _platformViewId;
+
+  /// The maximum number of characters that can be entered into an editable
+  /// text field.
+  ///
+  /// For the purpose of this function a character is defined as one Unicode
+  /// scalar value.
+  ///
+  /// This should only be set when [SemanticsFlag.isTextField] is set. Defaults
+  /// to null, which means no limit is imposed on the text field.
+  int? get maxValueLength => _maxValueLength;
+  int? _maxValueLength;
+
+  /// The current number of characters that have been entered into an editable
+  /// text field.
+  ///
+  /// For the purpose of this function a character is defined as one Unicode
+  /// scalar value.
+  ///
+  /// This should only be set when [SemanticsFlag.isTextField] is set. Must be
+  /// set when [maxValueLength] is set.
+  int? get currentValueLength => _currentValueLength;
+  int? _currentValueLength;
+
+  bool _canPerformAction(SemanticsAction action) => _actions.containsKey(action);
+
+  static final SemanticsConfiguration _kEmptyConfig = SemanticsConfiguration();
+
+  /// Reconfigures the properties of this object to describe the configuration
+  /// provided in the `config` argument and the children listed in the
+  /// `childrenInInversePaintOrder` argument.
+  ///
+  /// The arguments may be null; this represents an empty configuration (all
+  /// values at their defaults, no children).
+  ///
+  /// No reference is kept to the [SemanticsConfiguration] object, but the child
+  /// list is used as-is and should therefore not be changed after this call.
+  void updateWith({
+    required SemanticsConfiguration? config,
+    List<SemanticsNode>? childrenInInversePaintOrder,
+  }) {
+    config ??= _kEmptyConfig;
+    if (_isDifferentFromCurrentSemanticAnnotation(config))
+      _markDirty();
+
+    assert(
+      config.platformViewId == null || childrenInInversePaintOrder == null || childrenInInversePaintOrder.isEmpty,
+      'SemanticsNodes with children must not specify a platformViewId.'
+    );
+
+    _label = config.label;
+    _decreasedValue = config.decreasedValue;
+    _value = config.value;
+    _increasedValue = config.increasedValue;
+    _hint = config.hint;
+    _hintOverrides = config.hintOverrides;
+    _elevation = config.elevation;
+    _thickness = config.thickness;
+    _flags = config._flags;
+    _textDirection = config.textDirection;
+    _sortKey = config.sortKey;
+    _actions = Map<SemanticsAction, _SemanticsActionHandler>.from(config._actions);
+    _customSemanticsActions = Map<CustomSemanticsAction, VoidCallback>.from(config._customSemanticsActions);
+    _actionsAsBits = config._actionsAsBits;
+    _textSelection = config._textSelection;
+    _isMultiline = config.isMultiline;
+    _scrollPosition = config._scrollPosition;
+    _scrollExtentMax = config._scrollExtentMax;
+    _scrollExtentMin = config._scrollExtentMin;
+    _mergeAllDescendantsIntoThisNode = config.isMergingSemanticsOfDescendants;
+    _scrollChildCount = config.scrollChildCount;
+    _scrollIndex = config.scrollIndex;
+    indexInParent = config.indexInParent;
+    _platformViewId = config._platformViewId;
+    _maxValueLength = config._maxValueLength;
+    _currentValueLength = config._currentValueLength;
+    _replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
+
+    assert(
+      !_canPerformAction(SemanticsAction.increase) || (_value == '') == (_increasedValue == ''),
+      'A SemanticsNode with action "increase" needs to be annotated with either both "value" and "increasedValue" or neither',
+    );
+    assert(
+      !_canPerformAction(SemanticsAction.decrease) || (_value == '') == (_decreasedValue == ''),
+      'A SemanticsNode with action "increase" needs to be annotated with either both "value" and "decreasedValue" or neither',
+    );
+  }
+
+
+  /// Returns a summary of the semantics for this node.
+  ///
+  /// If this node has [mergeAllDescendantsIntoThisNode], then the returned data
+  /// includes the information from this node's descendants. Otherwise, the
+  /// returned data matches the data on this node.
+  SemanticsData getSemanticsData() {
+    int flags = _flags;
+    int actions = _actionsAsBits;
+    String label = _label;
+    String hint = _hint;
+    String value = _value;
+    String increasedValue = _increasedValue;
+    String decreasedValue = _decreasedValue;
+    TextDirection? textDirection = _textDirection;
+    Set<SemanticsTag>? mergedTags = tags == null ? null : Set<SemanticsTag>.from(tags!);
+    TextSelection? textSelection = _textSelection;
+    int? scrollChildCount = _scrollChildCount;
+    int? scrollIndex = _scrollIndex;
+    double? scrollPosition = _scrollPosition;
+    double? scrollExtentMax = _scrollExtentMax;
+    double? scrollExtentMin = _scrollExtentMin;
+    int? platformViewId = _platformViewId;
+    int? maxValueLength = _maxValueLength;
+    int? currentValueLength = _currentValueLength;
+    final double elevation = _elevation;
+    double thickness = _thickness;
+    final Set<int> customSemanticsActionIds = <int>{};
+    for (final CustomSemanticsAction action in _customSemanticsActions.keys)
+      customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
+    if (hintOverrides != null) {
+      if (hintOverrides!.onTapHint != null) {
+        final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
+          hint: hintOverrides!.onTapHint!,
+          action: SemanticsAction.tap,
+        );
+        customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
+      }
+      if (hintOverrides!.onLongPressHint != null) {
+        final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
+          hint: hintOverrides!.onLongPressHint!,
+          action: SemanticsAction.longPress,
+        );
+        customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
+      }
+    }
+
+    if (mergeAllDescendantsIntoThisNode) {
+      _visitDescendants((SemanticsNode node) {
+        assert(node.isMergedIntoParent);
+        flags |= node._flags;
+        actions |= node._actionsAsBits;
+        textDirection ??= node._textDirection;
+        textSelection ??= node._textSelection;
+        scrollChildCount ??= node._scrollChildCount;
+        scrollIndex ??= node._scrollIndex;
+        scrollPosition ??= node._scrollPosition;
+        scrollExtentMax ??= node._scrollExtentMax;
+        scrollExtentMin ??= node._scrollExtentMin;
+        platformViewId ??= node._platformViewId;
+        maxValueLength ??= node._maxValueLength;
+        currentValueLength ??= node._currentValueLength;
+        if (value == '' || value == null)
+          value = node._value;
+        if (increasedValue == '' || increasedValue == null)
+          increasedValue = node._increasedValue;
+        if (decreasedValue == '' || decreasedValue == null)
+          decreasedValue = node._decreasedValue;
+        if (node.tags != null) {
+          mergedTags ??= <SemanticsTag>{};
+          mergedTags!.addAll(node.tags!);
+        }
+        for (final CustomSemanticsAction action in _customSemanticsActions.keys)
+          customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
+        if (node.hintOverrides != null) {
+          if (node.hintOverrides!.onTapHint != null) {
+            final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
+              hint: node.hintOverrides!.onTapHint!,
+              action: SemanticsAction.tap,
+            );
+            customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
+          }
+          if (node.hintOverrides!.onLongPressHint != null) {
+            final CustomSemanticsAction action = CustomSemanticsAction.overridingAction(
+              hint: node.hintOverrides!.onLongPressHint!,
+              action: SemanticsAction.longPress,
+            );
+            customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
+          }
+        }
+        label = _concatStrings(
+          thisString: label,
+          thisTextDirection: textDirection,
+          otherString: node._label,
+          otherTextDirection: node._textDirection,
+        );
+        hint = _concatStrings(
+          thisString: hint,
+          thisTextDirection: textDirection,
+          otherString: node._hint,
+          otherTextDirection: node._textDirection,
+        );
+
+        thickness = math.max(thickness, node._thickness + node._elevation);
+
+        return true;
+      });
+    }
+
+    return SemanticsData(
+      flags: flags,
+      actions: actions,
+      label: label,
+      value: value,
+      increasedValue: increasedValue,
+      decreasedValue: decreasedValue,
+      hint: hint,
+      textDirection: textDirection,
+      rect: rect,
+      transform: transform,
+      elevation: elevation,
+      thickness: thickness,
+      tags: mergedTags,
+      textSelection: textSelection,
+      scrollChildCount: scrollChildCount,
+      scrollIndex: scrollIndex,
+      scrollPosition: scrollPosition,
+      scrollExtentMax: scrollExtentMax,
+      scrollExtentMin: scrollExtentMin,
+      platformViewId: platformViewId,
+      maxValueLength: maxValueLength,
+      currentValueLength: currentValueLength,
+      customSemanticsActionIds: customSemanticsActionIds.toList()..sort(),
+    );
+  }
+
+  static Float64List _initIdentityTransform() {
+    return Matrix4.identity().storage;
+  }
+
+  static final Int32List _kEmptyChildList = Int32List(0);
+  static final Int32List _kEmptyCustomSemanticsActionsList = Int32List(0);
+  static final Float64List _kIdentityTransform = _initIdentityTransform();
+
+  void _addToUpdate(ui.SemanticsUpdateBuilder builder, Set<int> customSemanticsActionIdsUpdate) {
+    assert(_dirty);
+    final SemanticsData data = getSemanticsData();
+    final Int32List childrenInTraversalOrder;
+    final Int32List childrenInHitTestOrder;
+    if (!hasChildren || mergeAllDescendantsIntoThisNode) {
+      childrenInTraversalOrder = _kEmptyChildList;
+      childrenInHitTestOrder = _kEmptyChildList;
+    } else {
+      final int childCount = _children!.length;
+      final List<SemanticsNode> sortedChildren = _childrenInTraversalOrder();
+      childrenInTraversalOrder = Int32List(childCount);
+      for (int i = 0; i < childCount; i += 1) {
+        childrenInTraversalOrder[i] = sortedChildren[i].id;
+      }
+      // _children is sorted in paint order, so we invert it to get the hit test
+      // order.
+      childrenInHitTestOrder = Int32List(childCount);
+      for (int i = childCount - 1; i >= 0; i -= 1) {
+        childrenInHitTestOrder[i] = _children![childCount - i - 1].id;
+      }
+    }
+    Int32List? customSemanticsActionIds;
+    if (data.customSemanticsActionIds?.isNotEmpty == true) {
+      customSemanticsActionIds = Int32List(data.customSemanticsActionIds!.length);
+      for (int i = 0; i < data.customSemanticsActionIds!.length; i++) {
+        customSemanticsActionIds[i] = data.customSemanticsActionIds![i];
+        customSemanticsActionIdsUpdate.add(data.customSemanticsActionIds![i]);
+      }
+    }
+    builder.updateNode(
+      id: id,
+      flags: data.flags,
+      actions: data.actions,
+      rect: data.rect,
+      label: data.label,
+      value: data.value,
+      decreasedValue: data.decreasedValue,
+      increasedValue: data.increasedValue,
+      hint: data.hint,
+      textDirection: data.textDirection,
+      textSelectionBase: data.textSelection != null ? data.textSelection!.baseOffset : -1,
+      textSelectionExtent: data.textSelection != null ? data.textSelection!.extentOffset : -1,
+      platformViewId: data.platformViewId ?? -1,
+      maxValueLength: data.maxValueLength ?? -1,
+      currentValueLength: data.currentValueLength ?? -1,
+      scrollChildren: data.scrollChildCount ?? 0,
+      scrollIndex: data.scrollIndex ?? 0 ,
+      scrollPosition: data.scrollPosition ?? double.nan,
+      scrollExtentMax: data.scrollExtentMax ?? double.nan,
+      scrollExtentMin: data.scrollExtentMin ?? double.nan,
+      transform: data.transform?.storage ?? _kIdentityTransform,
+      elevation: data.elevation,
+      thickness: data.thickness,
+      childrenInTraversalOrder: childrenInTraversalOrder,
+      childrenInHitTestOrder: childrenInHitTestOrder,
+      additionalActions: customSemanticsActionIds ?? _kEmptyCustomSemanticsActionsList,
+    );
+    _dirty = false;
+  }
+
+  /// Builds a new list made of [_children] sorted in semantic traversal order.
+  List<SemanticsNode> _childrenInTraversalOrder() {
+    TextDirection? inheritedTextDirection = textDirection;
+    SemanticsNode? ancestor = parent;
+    while (inheritedTextDirection == null && ancestor != null) {
+      inheritedTextDirection = ancestor.textDirection;
+      ancestor = ancestor.parent;
+    }
+
+    List<SemanticsNode>? childrenInDefaultOrder;
+    if (inheritedTextDirection != null) {
+      childrenInDefaultOrder = _childrenInDefaultOrder(_children!, inheritedTextDirection);
+    } else {
+      // In the absence of text direction default to paint order.
+      childrenInDefaultOrder = _children;
+    }
+
+    // List.sort does not guarantee stable sort order. Therefore, children are
+    // first partitioned into groups that have compatible sort keys, i.e. keys
+    // in the same group can be compared to each other. These groups stay in
+    // the same place. Only children within the same group are sorted.
+    final List<_TraversalSortNode> everythingSorted = <_TraversalSortNode>[];
+    final List<_TraversalSortNode> sortNodes = <_TraversalSortNode>[];
+    SemanticsSortKey? lastSortKey;
+    for (int position = 0; position < childrenInDefaultOrder!.length; position += 1) {
+      final SemanticsNode child = childrenInDefaultOrder[position];
+      final SemanticsSortKey? sortKey = child.sortKey;
+      lastSortKey = position > 0
+          ? childrenInDefaultOrder[position - 1].sortKey
+          : null;
+      final bool isCompatibleWithPreviousSortKey = position == 0 ||
+          sortKey.runtimeType == lastSortKey.runtimeType &&
+          (sortKey == null || sortKey.name == lastSortKey!.name);
+      if (!isCompatibleWithPreviousSortKey && sortNodes.isNotEmpty) {
+        // Do not sort groups with null sort keys. List.sort does not guarantee
+        // a stable sort order.
+        if (lastSortKey != null) {
+          sortNodes.sort();
+        }
+        everythingSorted.addAll(sortNodes);
+        sortNodes.clear();
+      }
+
+      sortNodes.add(_TraversalSortNode(
+        node: child,
+        sortKey: sortKey,
+        position: position,
+      ));
+    }
+
+    // Do not sort groups with null sort keys. List.sort does not guarantee
+    // a stable sort order.
+    if (lastSortKey != null) {
+      sortNodes.sort();
+    }
+    everythingSorted.addAll(sortNodes);
+
+    return everythingSorted
+      .map<SemanticsNode>((_TraversalSortNode sortNode) => sortNode.node)
+      .toList();
+  }
+
+  /// Sends a [SemanticsEvent] associated with this [SemanticsNode].
+  ///
+  /// Semantics events should be sent to inform interested parties (like
+  /// the accessibility system of the operating system) about changes to the UI.
+  void sendEvent(SemanticsEvent event) {
+    if (!attached)
+      return;
+    SystemChannels.accessibility.send(event.toMap(nodeId: id));
+  }
+
+  @override
+  String toStringShort() => '${objectRuntimeType(this, 'SemanticsNode')}#$id';
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    bool hideOwner = true;
+    if (_dirty) {
+      final bool inDirtyNodes = owner != null && owner!._dirtyNodes.contains(this);
+      properties.add(FlagProperty('inDirtyNodes', value: inDirtyNodes, ifTrue: 'dirty', ifFalse: 'STALE'));
+      hideOwner = inDirtyNodes;
+    }
+    properties.add(DiagnosticsProperty<SemanticsOwner>('owner', owner, level: hideOwner ? DiagnosticLevel.hidden : DiagnosticLevel.info));
+    properties.add(FlagProperty('isMergedIntoParent', value: isMergedIntoParent, ifTrue: 'merged up ⬆️'));
+    properties.add(FlagProperty('mergeAllDescendantsIntoThisNode', value: mergeAllDescendantsIntoThisNode, ifTrue: 'merge boundary ⛔️'));
+    final Offset? offset = transform != null ? MatrixUtils.getAsTranslation(transform!) : null;
+    if (offset != null) {
+      properties.add(DiagnosticsProperty<Rect>('rect', rect.shift(offset), showName: false));
+    } else {
+      final double? scale = transform != null ? MatrixUtils.getAsScale(transform!) : null;
+      String? description;
+      if (scale != null) {
+        description = '$rect scaled by ${scale.toStringAsFixed(1)}x';
+      } else if (transform != null && !MatrixUtils.isIdentity(transform!)) {
+        final String matrix = transform.toString().split('\n').take(4).map<String>((String line) => line.substring(4)).join('; ');
+        description = '$rect with transform [$matrix]';
+      }
+      properties.add(DiagnosticsProperty<Rect>('rect', rect, description: description, showName: false));
+    }
+    properties.add(IterableProperty<String>('tags', tags?.map((SemanticsTag tag) => tag.name), defaultValue: null));
+    final List<String> actions = _actions.keys.map<String>((SemanticsAction action) => describeEnum(action)).toList()..sort();
+    final List<String?> customSemanticsActions = _customSemanticsActions.keys
+      .map<String?>((CustomSemanticsAction action) => action.label)
+      .toList();
+    properties.add(IterableProperty<String>('actions', actions, ifEmpty: null));
+    properties.add(IterableProperty<String?>('customActions', customSemanticsActions, ifEmpty: null));
+    final List<String> flags = SemanticsFlag.values.values.where((SemanticsFlag flag) => hasFlag(flag)).map((SemanticsFlag flag) => flag.toString().substring('SemanticsFlag.'.length)).toList();
+    properties.add(IterableProperty<String>('flags', flags, ifEmpty: null));
+    properties.add(FlagProperty('isInvisible', value: isInvisible, ifTrue: 'invisible'));
+    properties.add(FlagProperty('isHidden', value: hasFlag(SemanticsFlag.isHidden), ifTrue: 'HIDDEN'));
+    properties.add(StringProperty('label', _label, defaultValue: ''));
+    properties.add(StringProperty('value', _value, defaultValue: ''));
+    properties.add(StringProperty('increasedValue', _increasedValue, defaultValue: ''));
+    properties.add(StringProperty('decreasedValue', _decreasedValue, defaultValue: ''));
+    properties.add(StringProperty('hint', _hint, defaultValue: ''));
+    properties.add(EnumProperty<TextDirection>('textDirection', _textDirection, defaultValue: null));
+    properties.add(DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
+    if (_textSelection?.isValid == true)
+      properties.add(MessageProperty('text selection', '[${_textSelection!.start}, ${_textSelection!.end}]'));
+    properties.add(IntProperty('platformViewId', platformViewId, defaultValue: null));
+    properties.add(IntProperty('maxValueLength', maxValueLength, defaultValue: null));
+    properties.add(IntProperty('currentValueLength', currentValueLength, defaultValue: null));
+    properties.add(IntProperty('scrollChildren', scrollChildCount, defaultValue: null));
+    properties.add(IntProperty('scrollIndex', scrollIndex, defaultValue: null));
+    properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
+    properties.add(DoubleProperty('scrollPosition', scrollPosition, defaultValue: null));
+    properties.add(DoubleProperty('scrollExtentMax', scrollExtentMax, defaultValue: null));
+    properties.add(DoubleProperty('elevation', elevation, defaultValue: 0.0));
+    properties.add(DoubleProperty('thickness', thickness, defaultValue: 0.0));
+  }
+
+  /// Returns a string representation of this node and its descendants.
+  ///
+  /// The order in which the children of the [SemanticsNode] will be printed is
+  /// controlled by the [childOrder] parameter.
+  @override
+  String toStringDeep({
+    String prefixLineOne = '',
+    String? prefixOtherLines,
+    DiagnosticLevel minLevel = DiagnosticLevel.debug,
+    DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.traversalOrder,
+  }) {
+    assert(childOrder != null);
+    return toDiagnosticsNode(childOrder: childOrder).toStringDeep(prefixLineOne: prefixLineOne, prefixOtherLines: prefixOtherLines, minLevel: minLevel);
+  }
+
+  @override
+  DiagnosticsNode toDiagnosticsNode({
+    String? name,
+    DiagnosticsTreeStyle? style = DiagnosticsTreeStyle.sparse,
+    DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.traversalOrder,
+  }) {
+    return _SemanticsDiagnosticableNode(
+      name: name,
+      value: this,
+      style: style,
+      childOrder: childOrder,
+    );
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren({ DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.inverseHitTest }) {
+    return debugListChildrenInOrder(childOrder)
+      .map<DiagnosticsNode>((SemanticsNode node) => node.toDiagnosticsNode(childOrder: childOrder))
+      .toList();
+  }
+
+  /// Returns the list of direct children of this node in the specified order.
+  List<SemanticsNode> debugListChildrenInOrder(DebugSemanticsDumpOrder childOrder) {
+    assert(childOrder != null);
+    if (_children == null)
+      return const <SemanticsNode>[];
+
+    switch (childOrder) {
+      case DebugSemanticsDumpOrder.inverseHitTest:
+        return _children!;
+      case DebugSemanticsDumpOrder.traversalOrder:
+        return _childrenInTraversalOrder();
+    }
+  }
+}
+
+/// An edge of a box, such as top, bottom, left or right, used to compute
+/// [SemanticsNode]s that overlap vertically or horizontally.
+///
+/// For computing horizontal overlap in an LTR setting we create two [_BoxEdge]
+/// objects for each [SemanticsNode]: one representing the left edge (marked
+/// with [isLeadingEdge] equal to true) and one for the right edge (with [isLeadingEdge]
+/// equal to false). Similarly, for vertical overlap we also create two objects
+/// for each [SemanticsNode], one for the top and one for the bottom edge.
+class _BoxEdge implements Comparable<_BoxEdge> {
+  _BoxEdge({
+    required this.isLeadingEdge,
+    required this.offset,
+    required this.node,
+  }) : assert(isLeadingEdge != null),
+       assert(offset != null),
+       assert(offset.isFinite),
+       assert(node != null);
+
+  /// True if the edge comes before the seconds edge along the traversal
+  /// direction, and false otherwise.
+  ///
+  /// This field is never null.
+  ///
+  /// For example, in LTR traversal the left edge's [isLeadingEdge] is set to true,
+  /// the right edge's [isLeadingEdge] is set to false. When considering vertical
+  /// ordering of boxes, the top edge is the start edge, and the bottom edge is
+  /// the end edge.
+  final bool isLeadingEdge;
+
+  /// The offset from the start edge of the parent [SemanticsNode] in the
+  /// direction of the traversal.
+  final double offset;
+
+  /// The node whom this edge belongs.
+  final SemanticsNode node;
+
+  @override
+  int compareTo(_BoxEdge other) {
+    return (offset - other.offset).sign.toInt();
+  }
+}
+
+/// A group of [nodes] that are disjoint vertically or horizontally from other
+/// nodes that share the same [SemanticsNode] parent.
+///
+/// The [nodes] are sorted among each other separately from other nodes.
+class _SemanticsSortGroup extends Comparable<_SemanticsSortGroup> {
+  _SemanticsSortGroup({
+    required this.startOffset,
+    required this.textDirection,
+  }) : assert(startOffset != null);
+
+  /// The offset from the start edge of the parent [SemanticsNode] in the
+  /// direction of the traversal.
+  ///
+  /// This value is equal to the [_BoxEdge.offset] of the first node in the
+  /// [nodes] list being considered.
+  final double startOffset;
+
+  final TextDirection textDirection;
+
+  /// The nodes that are sorted among each other.
+  final List<SemanticsNode> nodes = <SemanticsNode>[];
+
+  @override
+  int compareTo(_SemanticsSortGroup other) {
+    return (startOffset - other.startOffset).sign.toInt();
+  }
+
+  /// Sorts this group assuming that [nodes] belong to the same vertical group.
+  ///
+  /// This method breaks up this group into horizontal [_SemanticsSortGroup]s
+  /// then sorts them using [sortedWithinKnot].
+  List<SemanticsNode> sortedWithinVerticalGroup() {
+    final List<_BoxEdge> edges = <_BoxEdge>[];
+    for (final SemanticsNode child in nodes) {
+      // Using a small delta to shrink child rects removes overlapping cases.
+      final Rect childRect = child.rect.deflate(0.1);
+      edges.add(_BoxEdge(
+        isLeadingEdge: true,
+        offset: _pointInParentCoordinates(child, childRect.topLeft).dx,
+        node: child,
+      ));
+      edges.add(_BoxEdge(
+        isLeadingEdge: false,
+        offset: _pointInParentCoordinates(child, childRect.bottomRight).dx,
+        node: child,
+      ));
+    }
+    edges.sort();
+
+    List<_SemanticsSortGroup> horizontalGroups = <_SemanticsSortGroup>[];
+    _SemanticsSortGroup? group;
+    int depth = 0;
+    for (final _BoxEdge edge in edges) {
+      if (edge.isLeadingEdge) {
+        depth += 1;
+        group ??= _SemanticsSortGroup(
+          startOffset: edge.offset,
+          textDirection: textDirection,
+        );
+        group.nodes.add(edge.node);
+      } else {
+        depth -= 1;
+      }
+      if (depth == 0) {
+        horizontalGroups.add(group!);
+        group = null;
+      }
+    }
+    horizontalGroups.sort();
+
+    if (textDirection == TextDirection.rtl) {
+      horizontalGroups = horizontalGroups.reversed.toList();
+    }
+
+    return horizontalGroups
+      .expand((_SemanticsSortGroup group) => group.sortedWithinKnot())
+      .toList();
+  }
+
+  /// Sorts [nodes] where nodes intersect both vertically and horizontally.
+  ///
+  /// In the special case when [nodes] contains one or less nodes, this method
+  /// returns [nodes] unchanged.
+  ///
+  /// This method constructs a graph, where vertices are [SemanticsNode]s and
+  /// edges are "traversed before" relation between pairs of nodes. The sort
+  /// order is the topological sorting of the graph, with the original order of
+  /// [nodes] used as the tie breaker.
+  ///
+  /// Whether a node is traversed before another node is determined by the
+  /// vector that connects the two nodes' centers. If the vector "points to the
+  /// right or down", defined as the [Offset.direction] being between `-pi/4`
+  /// and `3*pi/4`), then the semantics node whose center is at the end of the
+  /// vector is said to be traversed after.
+  List<SemanticsNode> sortedWithinKnot() {
+    if (nodes.length <= 1) {
+      // Trivial knot. Nothing to do.
+      return nodes;
+    }
+    final Map<int, SemanticsNode> nodeMap = <int, SemanticsNode>{};
+    final Map<int, int> edges = <int, int>{};
+    for (final SemanticsNode node in nodes) {
+      nodeMap[node.id] = node;
+      final Offset center = _pointInParentCoordinates(node, node.rect.center);
+      for (final SemanticsNode nextNode in nodes) {
+        if (identical(node, nextNode) || edges[nextNode.id] == node.id) {
+          // Skip self or when we've already established that the next node
+          // points to current node.
+          continue;
+        }
+
+        final Offset nextCenter = _pointInParentCoordinates(nextNode, nextNode.rect.center);
+        final Offset centerDelta = nextCenter - center;
+        // When centers coincide, direction is 0.0.
+        final double direction = centerDelta.direction;
+        final bool isLtrAndForward = textDirection == TextDirection.ltr &&
+            -math.pi / 4 < direction && direction < 3 * math.pi / 4;
+        final bool isRtlAndForward = textDirection == TextDirection.rtl &&
+            (direction < -3 * math.pi / 4 || direction > 3 * math.pi / 4);
+        if (isLtrAndForward || isRtlAndForward) {
+          edges[node.id] = nextNode.id;
+        }
+      }
+    }
+
+    final List<int> sortedIds = <int>[];
+    final Set<int> visitedIds = <int>{};
+    final List<SemanticsNode> startNodes = nodes.toList()..sort((SemanticsNode a, SemanticsNode b) {
+      final Offset aTopLeft = _pointInParentCoordinates(a, a.rect.topLeft);
+      final Offset bTopLeft = _pointInParentCoordinates(b, b.rect.topLeft);
+      final int verticalDiff = aTopLeft.dy.compareTo(bTopLeft.dy);
+      if (verticalDiff != 0) {
+        return -verticalDiff;
+      }
+      return -aTopLeft.dx.compareTo(bTopLeft.dx);
+    });
+
+    void search(int id) {
+      if (visitedIds.contains(id)) {
+        return;
+      }
+      visitedIds.add(id);
+      if (edges.containsKey(id)) {
+        search(edges[id]!);
+      }
+      sortedIds.add(id);
+    }
+
+    startNodes.map<int>((SemanticsNode node) => node.id).forEach(search);
+    return sortedIds.map<SemanticsNode>((int id) => nodeMap[id]!).toList().reversed.toList();
+  }
+}
+
+/// Converts `point` to the `node`'s parent's coordinate system.
+Offset _pointInParentCoordinates(SemanticsNode node, Offset point) {
+  if (node.transform == null) {
+    return point;
+  }
+  final Vector3 vector = Vector3(point.dx, point.dy, 0.0);
+  node.transform!.transform3(vector);
+  return Offset(vector.x, vector.y);
+}
+
+/// Sorts `children` using the default sorting algorithm, and returns them as a
+/// new list.
+///
+/// The algorithm first breaks up children into groups such that no two nodes
+/// from different groups overlap vertically. These groups are sorted vertically
+/// according to their [_SemanticsSortGroup.startOffset].
+///
+/// Within each group, the nodes are sorted using
+/// [_SemanticsSortGroup.sortedWithinVerticalGroup].
+///
+/// For an illustration of the algorithm see http://bit.ly/flutter-default-traversal.
+List<SemanticsNode> _childrenInDefaultOrder(List<SemanticsNode> children, TextDirection textDirection) {
+  final List<_BoxEdge> edges = <_BoxEdge>[];
+  for (final SemanticsNode child in children) {
+    assert(child.rect.isFinite);
+    // Using a small delta to shrink child rects removes overlapping cases.
+    final Rect childRect = child.rect.deflate(0.1);
+    edges.add(_BoxEdge(
+      isLeadingEdge: true,
+      offset: _pointInParentCoordinates(child, childRect.topLeft).dy,
+      node: child,
+    ));
+    edges.add(_BoxEdge(
+      isLeadingEdge: false,
+      offset: _pointInParentCoordinates(child, childRect.bottomRight).dy,
+      node: child,
+    ));
+  }
+  edges.sort();
+
+  final List<_SemanticsSortGroup> verticalGroups = <_SemanticsSortGroup>[];
+  _SemanticsSortGroup? group;
+  int depth = 0;
+  for (final _BoxEdge edge in edges) {
+    if (edge.isLeadingEdge) {
+      depth += 1;
+      group ??= _SemanticsSortGroup(
+        startOffset: edge.offset,
+        textDirection: textDirection,
+      );
+      group.nodes.add(edge.node);
+    } else {
+      depth -= 1;
+    }
+    if (depth == 0) {
+      verticalGroups.add(group!);
+      group = null;
+    }
+  }
+  verticalGroups.sort();
+
+  return verticalGroups
+    .expand((_SemanticsSortGroup group) => group.sortedWithinVerticalGroup())
+    .toList();
+}
+
+/// The implementation of [Comparable] that implements the ordering of
+/// [SemanticsNode]s in the accessibility traversal.
+///
+/// [SemanticsNode]s are sorted prior to sending them to the engine side.
+///
+/// This implementation considers a [node]'s [sortKey] and its position within
+/// the list of its siblings. [sortKey] takes precedence over position.
+class _TraversalSortNode implements Comparable<_TraversalSortNode> {
+  _TraversalSortNode({
+    required this.node,
+    this.sortKey,
+    required this.position,
+  })
+    : assert(node != null),
+      assert(position != null);
+
+  /// The node whose position this sort node determines.
+  final SemanticsNode node;
+
+  /// Determines the position of this node among its siblings.
+  ///
+  /// Sort keys take precedence over other attributes, such as
+  /// [position].
+  final SemanticsSortKey? sortKey;
+
+  /// Position within the list of siblings as determined by the default sort
+  /// order.
+  final int position;
+
+  @override
+  int compareTo(_TraversalSortNode other) {
+    if (sortKey == null || other.sortKey == null) {
+      return position - other.position;
+    }
+    return sortKey!.compareTo(other.sortKey!);
+  }
+}
+
+/// Owns [SemanticsNode] objects and notifies listeners of changes to the
+/// render tree semantics.
+///
+/// To listen for semantic updates, call [PipelineOwner.ensureSemantics] to
+/// obtain a [SemanticsHandle]. This will create a [SemanticsOwner] if
+/// necessary.
+class SemanticsOwner extends ChangeNotifier {
+  final Set<SemanticsNode> _dirtyNodes = <SemanticsNode>{};
+  final Map<int, SemanticsNode> _nodes = <int, SemanticsNode>{};
+  final Set<SemanticsNode> _detachedNodes = <SemanticsNode>{};
+
+  /// The root node of the semantics tree, if any.
+  ///
+  /// If the semantics tree is empty, returns null.
+  SemanticsNode? get rootSemanticsNode => _nodes[0];
+
+  @override
+  void dispose() {
+    _dirtyNodes.clear();
+    _nodes.clear();
+    _detachedNodes.clear();
+    super.dispose();
+  }
+
+  /// Update the semantics using [dart:ui.PlatformDispatcher.updateSemantics].
+  void sendSemanticsUpdate() {
+    if (_dirtyNodes.isEmpty)
+      return;
+    final Set<int> customSemanticsActionIds = <int>{};
+    final List<SemanticsNode> visitedNodes = <SemanticsNode>[];
+    while (_dirtyNodes.isNotEmpty) {
+      final List<SemanticsNode> localDirtyNodes = _dirtyNodes.where((SemanticsNode node) => !_detachedNodes.contains(node)).toList();
+      _dirtyNodes.clear();
+      _detachedNodes.clear();
+      localDirtyNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
+      visitedNodes.addAll(localDirtyNodes);
+      for (final SemanticsNode node in localDirtyNodes) {
+        assert(node._dirty);
+        assert(node.parent == null || !node.parent!.isPartOfNodeMerging || node.isMergedIntoParent);
+        if (node.isPartOfNodeMerging) {
+          assert(node.mergeAllDescendantsIntoThisNode || node.parent != null);
+          // if we're merged into our parent, make sure our parent is added to the dirty list
+          if (node.parent != null && node.parent!.isPartOfNodeMerging) {
+            node.parent!._markDirty(); // this can add the node to the dirty list
+            node._dirty = false; // We don't want to send update for this node.
+          }
+        }
+      }
+    }
+    visitedNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
+    final ui.SemanticsUpdateBuilder builder = SemanticsBinding.instance!.createSemanticsUpdateBuilder();
+    for (final SemanticsNode node in visitedNodes) {
+      assert(node.parent?._dirty != true); // could be null (no parent) or false (not dirty)
+      // The _serialize() method marks the node as not dirty, and
+      // recurses through the tree to do a deep serialization of all
+      // contiguous dirty nodes. This means that when we return here,
+      // it's quite possible that subsequent nodes are no longer
+      // dirty. We skip these here.
+      // We also skip any nodes that were reset and subsequently
+      // dropped entirely (RenderObject.markNeedsSemanticsUpdate()
+      // calls reset() on its SemanticsNode if onlyChanges isn't set,
+      // which happens e.g. when the node is no longer contributing
+      // semantics).
+      if (node._dirty && node.attached)
+        node._addToUpdate(builder, customSemanticsActionIds);
+    }
+    _dirtyNodes.clear();
+    for (final int actionId in customSemanticsActionIds) {
+      final CustomSemanticsAction action = CustomSemanticsAction.getAction(actionId)!;
+      builder.updateCustomAction(id: actionId, label: action.label, hint: action.hint, overrideId: action.action?.index ?? -1);
+    }
+    SemanticsBinding.instance!.window.updateSemantics(builder.build());
+    notifyListeners();
+  }
+
+  _SemanticsActionHandler? _getSemanticsActionHandlerForId(int id, SemanticsAction action) {
+    SemanticsNode? result = _nodes[id];
+    if (result != null && result.isPartOfNodeMerging && !result._canPerformAction(action)) {
+      result._visitDescendants((SemanticsNode node) {
+        if (node._canPerformAction(action)) {
+          result = node;
+          return false; // found node, abort walk
+        }
+        return true; // continue walk
+      });
+    }
+    if (result == null || !result!._canPerformAction(action))
+      return null;
+    return result!._actions[action];
+  }
+
+  /// Asks the [SemanticsNode] with the given id to perform the given action.
+  ///
+  /// If the [SemanticsNode] has not indicated that it can perform the action,
+  /// this function does nothing.
+  ///
+  /// If the given `action` requires arguments they need to be passed in via
+  /// the `args` parameter.
+  void performAction(int id, SemanticsAction action, [ dynamic args ]) {
+    assert(action != null);
+    final _SemanticsActionHandler? handler = _getSemanticsActionHandlerForId(id, action);
+    if (handler != null) {
+      handler(args);
+      return;
+    }
+
+    // Default actions if no [handler] was provided.
+    if (action == SemanticsAction.showOnScreen && _nodes[id]!._showOnScreen != null)
+      _nodes[id]!._showOnScreen!();
+  }
+
+  _SemanticsActionHandler? _getSemanticsActionHandlerForPosition(SemanticsNode node, Offset position, SemanticsAction action) {
+    if (node.transform != null) {
+      final Matrix4 inverse = Matrix4.identity();
+      if (inverse.copyInverse(node.transform!) == 0.0)
+        return null;
+      position = MatrixUtils.transformPoint(inverse, position);
+    }
+    if (!node.rect.contains(position))
+      return null;
+    if (node.mergeAllDescendantsIntoThisNode) {
+      SemanticsNode? result;
+      node._visitDescendants((SemanticsNode child) {
+        if (child._canPerformAction(action)) {
+          result = child;
+          return false;
+        }
+        return true;
+      });
+      return result?._actions[action];
+    }
+    if (node.hasChildren) {
+      for (final SemanticsNode child in node._children!.reversed) {
+        final _SemanticsActionHandler? handler = _getSemanticsActionHandlerForPosition(child, position, action);
+        if (handler != null)
+          return handler;
+      }
+    }
+    return node._actions[action];
+  }
+
+  /// Asks the [SemanticsNode] at the given position to perform the given action.
+  ///
+  /// If the [SemanticsNode] has not indicated that it can perform the action,
+  /// this function does nothing.
+  ///
+  /// If the given `action` requires arguments they need to be passed in via
+  /// the `args` parameter.
+  void performActionAt(Offset position, SemanticsAction action, [ dynamic args ]) {
+    assert(action != null);
+    final SemanticsNode? node = rootSemanticsNode;
+    if (node == null)
+      return;
+    final _SemanticsActionHandler? handler = _getSemanticsActionHandlerForPosition(node, position, action);
+    if (handler != null)
+      handler(args);
+  }
+
+  @override
+  String toString() => describeIdentity(this);
+}
+
+/// Describes the semantic information associated with the owning
+/// [RenderObject].
+///
+/// The information provided in the configuration is used to generate the
+/// semantics tree.
+class SemanticsConfiguration {
+
+  // SEMANTIC BOUNDARY BEHAVIOR
+
+  /// Whether the [RenderObject] owner of this configuration wants to own its
+  /// own [SemanticsNode].
+  ///
+  /// When set to true semantic information associated with the [RenderObject]
+  /// owner of this configuration or any of its descendants will not leak into
+  /// parents. The [SemanticsNode] generated out of this configuration will
+  /// act as a boundary.
+  ///
+  /// Whether descendants of the owning [RenderObject] can add their semantic
+  /// information to the [SemanticsNode] introduced by this configuration
+  /// is controlled by [explicitChildNodes].
+  ///
+  /// This has to be true if [isMergingSemanticsOfDescendants] is also true.
+  bool get isSemanticBoundary => _isSemanticBoundary;
+  bool _isSemanticBoundary = false;
+  set isSemanticBoundary(bool value) {
+    assert(!isMergingSemanticsOfDescendants || value);
+    _isSemanticBoundary = value;
+  }
+
+  /// Whether the configuration forces all children of the owning [RenderObject]
+  /// that want to contribute semantic information to the semantics tree to do
+  /// so in the form of explicit [SemanticsNode]s.
+  ///
+  /// When set to false children of the owning [RenderObject] are allowed to
+  /// annotate [SemanticsNode]s of their parent with the semantic information
+  /// they want to contribute to the semantic tree.
+  /// When set to true the only way for children of the owning [RenderObject]
+  /// to contribute semantic information to the semantic tree is to introduce
+  /// new explicit [SemanticsNode]s to the tree.
+  ///
+  /// This setting is often used in combination with [isSemanticBoundary] to
+  /// create semantic boundaries that are either writable or not for children.
+  bool explicitChildNodes = false;
+
+  /// Whether the owning [RenderObject] makes other [RenderObject]s previously
+  /// painted within the same semantic boundary unreachable for accessibility
+  /// purposes.
+  ///
+  /// If set to true, the semantic information for all siblings and cousins of
+  /// this node, that are earlier in a depth-first pre-order traversal, are
+  /// dropped from the semantics tree up until a semantic boundary (as defined
+  /// by [isSemanticBoundary]) is reached.
+  ///
+  /// If [isSemanticBoundary] and [isBlockingSemanticsOfPreviouslyPaintedNodes]
+  /// is set on the same node, all previously painted siblings and cousins up
+  /// until the next ancestor that is a semantic boundary are dropped.
+  ///
+  /// Paint order as established by [RenderObject.visitChildrenForSemantics] is
+  /// used to determine if a node is previous to this one.
+  bool isBlockingSemanticsOfPreviouslyPaintedNodes = false;
+
+  // SEMANTIC ANNOTATIONS
+  // These will end up on [SemanticsNode]s generated from
+  // [SemanticsConfiguration]s.
+
+  /// Whether this configuration is empty.
+  ///
+  /// An empty configuration doesn't contain any semantic information that it
+  /// wants to contribute to the semantics tree.
+  bool get hasBeenAnnotated => _hasBeenAnnotated;
+  bool _hasBeenAnnotated = false;
+
+  /// The actions (with associated action handlers) that this configuration
+  /// would like to contribute to the semantics tree.
+  ///
+  /// See also:
+  ///
+  ///  * [addAction] to add an action.
+  final Map<SemanticsAction, _SemanticsActionHandler> _actions = <SemanticsAction, _SemanticsActionHandler>{};
+
+  int _actionsAsBits = 0;
+
+  /// Adds an `action` to the semantics tree.
+  ///
+  /// The provided `handler` is called to respond to the user triggered
+  /// `action`.
+  void _addAction(SemanticsAction action, _SemanticsActionHandler handler) {
+    assert(handler != null);
+    _actions[action] = handler;
+    _actionsAsBits |= action.index;
+    _hasBeenAnnotated = true;
+  }
+
+  /// Adds an `action` to the semantics tree, whose `handler` does not expect
+  /// any arguments.
+  ///
+  /// The provided `handler` is called to respond to the user triggered
+  /// `action`.
+  void _addArgumentlessAction(SemanticsAction action, VoidCallback handler) {
+    assert(handler != null);
+    _addAction(action, (dynamic args) {
+      assert(args == null);
+      handler();
+    });
+  }
+
+  /// The handler for [SemanticsAction.tap].
+  ///
+  /// This is the semantic equivalent of a user briefly tapping the screen with
+  /// the finger without moving it. For example, a button should implement this
+  /// action.
+  ///
+  /// VoiceOver users on iOS and TalkBack users on Android can trigger this
+  /// action by double-tapping the screen while an element is focused.
+  ///
+  /// On Android prior to Android Oreo a double-tap on the screen while an
+  /// element with an [onTap] handler is focused will not call the registered
+  /// handler. Instead, Android will simulate a pointer down and up event at the
+  /// center of the focused element. Those pointer events will get dispatched
+  /// just like a regular tap with TalkBack disabled would: The events will get
+  /// processed by any [GestureDetector] listening for gestures in the center of
+  /// the focused element. Therefore, to ensure that [onTap] handlers work
+  /// properly on Android versions prior to Oreo, a [GestureDetector] with an
+  /// onTap handler should always be wrapping an element that defines a
+  /// semantic [onTap] handler. By default a [GestureDetector] will register its
+  /// own semantic [onTap] handler that follows this principle.
+  VoidCallback? get onTap => _onTap;
+  VoidCallback? _onTap;
+  set onTap(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.tap, value!);
+    _onTap = value;
+  }
+
+  /// The handler for [SemanticsAction.longPress].
+  ///
+  /// This is the semantic equivalent of a user pressing and holding the screen
+  /// with the finger for a few seconds without moving it.
+  ///
+  /// VoiceOver users on iOS and TalkBack users on Android can trigger this
+  /// action by double-tapping the screen without lifting the finger after the
+  /// second tap.
+  VoidCallback? get onLongPress => _onLongPress;
+  VoidCallback? _onLongPress;
+  set onLongPress(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.longPress, value!);
+    _onLongPress = value;
+  }
+
+  /// The handler for [SemanticsAction.scrollLeft].
+  ///
+  /// This is the semantic equivalent of a user moving their finger across the
+  /// screen from right to left. It should be recognized by controls that are
+  /// horizontally scrollable.
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping left with three
+  /// fingers. TalkBack users on Android can trigger this action by swiping
+  /// right and then left in one motion path. On Android, [onScrollUp] and
+  /// [onScrollLeft] share the same gesture. Therefore, only on of them should
+  /// be provided.
+  VoidCallback? get onScrollLeft => _onScrollLeft;
+  VoidCallback? _onScrollLeft;
+  set onScrollLeft(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.scrollLeft, value!);
+    _onScrollLeft = value;
+  }
+
+  /// The handler for [SemanticsAction.dismiss].
+  ///
+  /// This is a request to dismiss the currently focused node.
+  ///
+  /// TalkBack users on Android can trigger this action in the local context
+  /// menu, and VoiceOver users on iOS can trigger this action with a standard
+  /// gesture or menu option.
+  VoidCallback? get onDismiss => _onDismiss;
+  VoidCallback? _onDismiss;
+  set onDismiss(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.dismiss, value!);
+    _onDismiss = value;
+  }
+
+  /// The handler for [SemanticsAction.scrollRight].
+  ///
+  /// This is the semantic equivalent of a user moving their finger across the
+  /// screen from left to right. It should be recognized by controls that are
+  /// horizontally scrollable.
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping right with three
+  /// fingers. TalkBack users on Android can trigger this action by swiping
+  /// left and then right in one motion path. On Android, [onScrollDown] and
+  /// [onScrollRight] share the same gesture. Therefore, only on of them should
+  /// be provided.
+  VoidCallback? get onScrollRight => _onScrollRight;
+  VoidCallback? _onScrollRight;
+  set onScrollRight(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.scrollRight, value!);
+    _onScrollRight = value;
+  }
+
+  /// The handler for [SemanticsAction.scrollUp].
+  ///
+  /// This is the semantic equivalent of a user moving their finger across the
+  /// screen from bottom to top. It should be recognized by controls that are
+  /// vertically scrollable.
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping up with three
+  /// fingers. TalkBack users on Android can trigger this action by swiping
+  /// right and then left in one motion path. On Android, [onScrollUp] and
+  /// [onScrollLeft] share the same gesture. Therefore, only on of them should
+  /// be provided.
+  VoidCallback? get onScrollUp => _onScrollUp;
+  VoidCallback? _onScrollUp;
+  set onScrollUp(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.scrollUp, value!);
+    _onScrollUp = value;
+  }
+
+  /// The handler for [SemanticsAction.scrollDown].
+  ///
+  /// This is the semantic equivalent of a user moving their finger across the
+  /// screen from top to bottom. It should be recognized by controls that are
+  /// vertically scrollable.
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping down with three
+  /// fingers. TalkBack users on Android can trigger this action by swiping
+  /// left and then right in one motion path. On Android, [onScrollDown] and
+  /// [onScrollRight] share the same gesture. Therefore, only on of them should
+  /// be provided.
+  VoidCallback? get onScrollDown => _onScrollDown;
+  VoidCallback? _onScrollDown;
+  set onScrollDown(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.scrollDown, value!);
+    _onScrollDown = value;
+  }
+
+  /// The handler for [SemanticsAction.increase].
+  ///
+  /// This is a request to increase the value represented by the widget. For
+  /// example, this action might be recognized by a slider control.
+  ///
+  /// If a [value] is set, [increasedValue] must also be provided and
+  /// [onIncrease] must ensure that [value] will be set to [increasedValue].
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping up with one
+  /// finger. TalkBack users on Android can trigger this action by pressing the
+  /// volume up button.
+  VoidCallback? get onIncrease => _onIncrease;
+  VoidCallback? _onIncrease;
+  set onIncrease(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.increase, value!);
+    _onIncrease = value;
+  }
+
+  /// The handler for [SemanticsAction.decrease].
+  ///
+  /// This is a request to decrease the value represented by the widget. For
+  /// example, this action might be recognized by a slider control.
+  ///
+  /// If a [value] is set, [decreasedValue] must also be provided and
+  /// [onDecrease] must ensure that [value] will be set to [decreasedValue].
+  ///
+  /// VoiceOver users on iOS can trigger this action by swiping down with one
+  /// finger. TalkBack users on Android can trigger this action by pressing the
+  /// volume down button.
+  VoidCallback? get onDecrease => _onDecrease;
+  VoidCallback? _onDecrease;
+  set onDecrease(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.decrease, value!);
+    _onDecrease = value;
+  }
+
+  /// The handler for [SemanticsAction.copy].
+  ///
+  /// This is a request to copy the current selection to the clipboard.
+  ///
+  /// TalkBack users on Android can trigger this action from the local context
+  /// menu of a text field, for example.
+  VoidCallback? get onCopy => _onCopy;
+  VoidCallback? _onCopy;
+  set onCopy(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.copy, value!);
+    _onCopy = value;
+  }
+
+  /// The handler for [SemanticsAction.cut].
+  ///
+  /// This is a request to cut the current selection and place it in the
+  /// clipboard.
+  ///
+  /// TalkBack users on Android can trigger this action from the local context
+  /// menu of a text field, for example.
+  VoidCallback? get onCut => _onCut;
+  VoidCallback? _onCut;
+  set onCut(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.cut, value!);
+    _onCut = value;
+  }
+
+  /// The handler for [SemanticsAction.paste].
+  ///
+  /// This is a request to paste the current content of the clipboard.
+  ///
+  /// TalkBack users on Android can trigger this action from the local context
+  /// menu of a text field, for example.
+  VoidCallback? get onPaste => _onPaste;
+  VoidCallback? _onPaste;
+  set onPaste(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.paste, value!);
+    _onPaste = value;
+  }
+
+  /// The handler for [SemanticsAction.showOnScreen].
+  ///
+  /// A request to fully show the semantics node on screen. For example, this
+  /// action might be send to a node in a scrollable list that is partially off
+  /// screen to bring it on screen.
+  ///
+  /// For elements in a scrollable list the framework provides a default
+  /// implementation for this action and it is not advised to provide a
+  /// custom one via this setter.
+  VoidCallback? get onShowOnScreen => _onShowOnScreen;
+  VoidCallback? _onShowOnScreen;
+  set onShowOnScreen(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.showOnScreen, value!);
+    _onShowOnScreen = value;
+  }
+
+  /// The handler for [SemanticsAction.moveCursorForwardByCharacter].
+  ///
+  /// This handler is invoked when the user wants to move the cursor in a
+  /// text field forward by one character.
+  ///
+  /// TalkBack users can trigger this by pressing the volume up key while the
+  /// input focus is in a text field.
+  MoveCursorHandler? get onMoveCursorForwardByCharacter => _onMoveCursorForwardByCharacter;
+  MoveCursorHandler? _onMoveCursorForwardByCharacter;
+  set onMoveCursorForwardByCharacter(MoveCursorHandler? value) {
+    assert(value != null);
+    _addAction(SemanticsAction.moveCursorForwardByCharacter, (dynamic args) {
+      final bool extentSelection = args as bool;
+      assert(extentSelection != null);
+      value!(extentSelection);
+    });
+    _onMoveCursorForwardByCharacter = value;
+  }
+
+  /// The handler for [SemanticsAction.moveCursorBackwardByCharacter].
+  ///
+  /// This handler is invoked when the user wants to move the cursor in a
+  /// text field backward by one character.
+  ///
+  /// TalkBack users can trigger this by pressing the volume down key while the
+  /// input focus is in a text field.
+  MoveCursorHandler? get onMoveCursorBackwardByCharacter => _onMoveCursorBackwardByCharacter;
+  MoveCursorHandler? _onMoveCursorBackwardByCharacter;
+  set onMoveCursorBackwardByCharacter(MoveCursorHandler? value) {
+    assert(value != null);
+    _addAction(SemanticsAction.moveCursorBackwardByCharacter, (dynamic args) {
+      final bool extentSelection = args as bool;
+      assert(extentSelection != null);
+      value!(extentSelection);
+    });
+    _onMoveCursorBackwardByCharacter = value;
+  }
+
+  /// The handler for [SemanticsAction.moveCursorForwardByWord].
+  ///
+  /// This handler is invoked when the user wants to move the cursor in a
+  /// text field backward by one word.
+  ///
+  /// TalkBack users can trigger this by pressing the volume down key while the
+  /// input focus is in a text field.
+  MoveCursorHandler? get onMoveCursorForwardByWord => _onMoveCursorForwardByWord;
+  MoveCursorHandler? _onMoveCursorForwardByWord;
+  set onMoveCursorForwardByWord(MoveCursorHandler? value) {
+    assert(value != null);
+    _addAction(SemanticsAction.moveCursorForwardByWord, (dynamic args) {
+      final bool extentSelection = args as bool;
+      assert(extentSelection != null);
+      value!(extentSelection);
+    });
+    _onMoveCursorForwardByCharacter = value;
+  }
+
+  /// The handler for [SemanticsAction.moveCursorBackwardByWord].
+  ///
+  /// This handler is invoked when the user wants to move the cursor in a
+  /// text field backward by one word.
+  ///
+  /// TalkBack users can trigger this by pressing the volume down key while the
+  /// input focus is in a text field.
+  MoveCursorHandler? get onMoveCursorBackwardByWord => _onMoveCursorBackwardByWord;
+  MoveCursorHandler? _onMoveCursorBackwardByWord;
+  set onMoveCursorBackwardByWord(MoveCursorHandler? value) {
+    assert(value != null);
+    _addAction(SemanticsAction.moveCursorBackwardByWord, (dynamic args) {
+      final bool extentSelection = args as bool;
+      assert(extentSelection != null);
+      value!(extentSelection);
+    });
+    _onMoveCursorBackwardByCharacter = value;
+  }
+
+  /// The handler for [SemanticsAction.setSelection].
+  ///
+  /// This handler is invoked when the user either wants to change the currently
+  /// selected text in a text field or change the position of the cursor.
+  ///
+  /// TalkBack users can trigger this handler by selecting "Move cursor to
+  /// beginning/end" or "Select all" from the local context menu.
+  SetSelectionHandler? get onSetSelection => _onSetSelection;
+  SetSelectionHandler? _onSetSelection;
+  set onSetSelection(SetSelectionHandler? value) {
+    assert(value != null);
+    _addAction(SemanticsAction.setSelection, (dynamic args) {
+      assert(args != null && args is Map);
+      final Map<String, int> selection = (args as Map<dynamic, dynamic>).cast<String, int>();
+      assert(selection != null && selection['base'] != null && selection['extent'] != null);
+      value!(TextSelection(
+        baseOffset: selection['base']!,
+        extentOffset: selection['extent']!,
+      ));
+    });
+    _onSetSelection = value;
+  }
+
+  /// The handler for [SemanticsAction.didGainAccessibilityFocus].
+  ///
+  /// This handler is invoked when the node annotated with this handler gains
+  /// the accessibility focus. The accessibility focus is the
+  /// green (on Android with TalkBack) or black (on iOS with VoiceOver)
+  /// rectangle shown on screen to indicate what element an accessibility
+  /// user is currently interacting with.
+  ///
+  /// The accessibility focus is different from the input focus. The input focus
+  /// is usually held by the element that currently responds to keyboard inputs.
+  /// Accessibility focus and input focus can be held by two different nodes!
+  ///
+  /// See also:
+  ///
+  ///  * [onDidLoseAccessibilityFocus], which is invoked when the accessibility
+  ///    focus is removed from the node.
+  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
+  VoidCallback? get onDidGainAccessibilityFocus => _onDidGainAccessibilityFocus;
+  VoidCallback? _onDidGainAccessibilityFocus;
+  set onDidGainAccessibilityFocus(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.didGainAccessibilityFocus, value!);
+    _onDidGainAccessibilityFocus = value;
+  }
+
+  /// The handler for [SemanticsAction.didLoseAccessibilityFocus].
+  ///
+  /// This handler is invoked when the node annotated with this handler
+  /// loses the accessibility focus. The accessibility focus is
+  /// the green (on Android with TalkBack) or black (on iOS with VoiceOver)
+  /// rectangle shown on screen to indicate what element an accessibility
+  /// user is currently interacting with.
+  ///
+  /// The accessibility focus is different from the input focus. The input focus
+  /// is usually held by the element that currently responds to keyboard inputs.
+  /// Accessibility focus and input focus can be held by two different nodes!
+  ///
+  /// See also:
+  ///
+  ///  * [onDidGainAccessibilityFocus], which is invoked when the node gains
+  ///    accessibility focus.
+  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
+  VoidCallback? get onDidLoseAccessibilityFocus => _onDidLoseAccessibilityFocus;
+  VoidCallback? _onDidLoseAccessibilityFocus;
+  set onDidLoseAccessibilityFocus(VoidCallback? value) {
+    _addArgumentlessAction(SemanticsAction.didLoseAccessibilityFocus, value!);
+    _onDidLoseAccessibilityFocus = value;
+  }
+
+  /// Returns the action handler registered for [action] or null if none was
+  /// registered.
+  _SemanticsActionHandler? getActionHandler(SemanticsAction action) => _actions[action];
+
+  /// Determines the position of this node among its siblings in the traversal
+  /// sort order.
+  ///
+  /// This is used to describe the order in which the semantic node should be
+  /// traversed by the accessibility services on the platform (e.g. VoiceOver
+  /// on iOS and TalkBack on Android).
+  ///
+  /// Whether this sort key has an effect on the [SemanticsNode] sort order is
+  /// subject to how this configuration is used. For example, the [absorb]
+  /// method may decide to not use this key when it combines multiple
+  /// [SemanticsConfiguration] objects.
+  SemanticsSortKey? get sortKey => _sortKey;
+  SemanticsSortKey? _sortKey;
+  set sortKey(SemanticsSortKey? value) {
+    assert(value != null);
+    _sortKey = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// The index of this node within the parent's list of semantic children.
+  ///
+  /// This includes all semantic nodes, not just those currently in the
+  /// child list. For example, if a scrollable has five children but the first
+  /// two are not visible (and thus not included in the list of children), then
+  /// the index of the last node will still be 4.
+  int? get indexInParent => _indexInParent;
+  int? _indexInParent;
+  set indexInParent(int? value) {
+    _indexInParent = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// The total number of scrollable children that contribute to semantics.
+  ///
+  /// If the number of children are unknown or unbounded, this value will be
+  /// null.
+  int? get scrollChildCount => _scrollChildCount;
+  int? _scrollChildCount;
+  set scrollChildCount(int? value) {
+    if (value == scrollChildCount)
+      return;
+    _scrollChildCount = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// The index of the first visible scrollable child that contributes to
+  /// semantics.
+  int? get scrollIndex => _scrollIndex;
+  int? _scrollIndex;
+  set scrollIndex(int? value) {
+    if (value == scrollIndex)
+      return;
+    _scrollIndex = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// The id of the platform view, whose semantics nodes will be added as
+  /// children to this node.
+  int? get platformViewId => _platformViewId;
+  int? _platformViewId;
+  set platformViewId(int? value) {
+    if (value == platformViewId)
+      return;
+    _platformViewId = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// The maximum number of characters that can be entered into an editable
+  /// text field.
+  ///
+  /// For the purpose of this function a character is defined as one Unicode
+  /// scalar value.
+  ///
+  /// This should only be set when [isTextField] is true. Defaults to null,
+  /// which means no limit is imposed on the text field.
+  int? get maxValueLength => _maxValueLength;
+  int? _maxValueLength;
+  set maxValueLength(int? value) {
+    if (value == maxValueLength)
+      return;
+    _maxValueLength = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// The current number of characters that have been entered into an editable
+  /// text field.
+  ///
+  /// For the purpose of this function a character is defined as one Unicode
+  /// scalar value.
+  ///
+  /// This should only be set when [isTextField] is true. Must be set when
+  /// [maxValueLength] is set.
+  int? get currentValueLength => _currentValueLength;
+  int? _currentValueLength;
+  set currentValueLength(int? value) {
+    if (value == currentValueLength)
+      return;
+    _currentValueLength = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// Whether the semantic information provided by the owning [RenderObject] and
+  /// all of its descendants should be treated as one logical entity.
+  ///
+  /// If set to true, the descendants of the owning [RenderObject]'s
+  /// [SemanticsNode] will merge their semantic information into the
+  /// [SemanticsNode] representing the owning [RenderObject].
+  ///
+  /// Setting this to true requires that [isSemanticBoundary] is also true.
+  bool get isMergingSemanticsOfDescendants => _isMergingSemanticsOfDescendants;
+  bool _isMergingSemanticsOfDescendants = false;
+  set isMergingSemanticsOfDescendants(bool value) {
+    assert(isSemanticBoundary);
+    _isMergingSemanticsOfDescendants = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// The handlers for each supported [CustomSemanticsAction].
+  ///
+  /// Whenever a custom accessibility action is added to a node, the action
+  /// [SemanticsAction.customAction] is automatically added. A handler is
+  /// created which uses the passed argument to lookup the custom action
+  /// handler from this map and invoke it, if present.
+  Map<CustomSemanticsAction, VoidCallback> get customSemanticsActions => _customSemanticsActions;
+  Map<CustomSemanticsAction, VoidCallback> _customSemanticsActions = <CustomSemanticsAction, VoidCallback>{};
+  set customSemanticsActions(Map<CustomSemanticsAction, VoidCallback> value) {
+    _hasBeenAnnotated = true;
+    _actionsAsBits |= SemanticsAction.customAction.index;
+    _customSemanticsActions = value;
+    _actions[SemanticsAction.customAction] = _onCustomSemanticsAction;
+  }
+
+  void _onCustomSemanticsAction(dynamic args) {
+    final CustomSemanticsAction? action = CustomSemanticsAction.getAction(args as int);
+    if (action == null)
+      return;
+    final VoidCallback? callback = _customSemanticsActions[action];
+    if (callback != null)
+      callback();
+  }
+
+  /// A textual description of the owning [RenderObject].
+  ///
+  /// On iOS this is used for the `accessibilityLabel` property defined in the
+  /// `UIAccessibility` Protocol. On Android it is concatenated together with
+  /// [value] and [hint] in the following order: [value], [label], [hint].
+  /// The concatenated value is then used as the `Text` description.
+  ///
+  /// The reading direction is given by [textDirection].
+  String get label => _label;
+  String _label = '';
+  set label(String label) {
+    assert(label != null);
+    _label = label;
+    _hasBeenAnnotated = true;
+  }
+
+  /// A textual description for the current value of the owning [RenderObject].
+  ///
+  /// On iOS this is used for the `accessibilityValue` property defined in the
+  /// `UIAccessibility` Protocol. On Android it is concatenated together with
+  /// [label] and [hint] in the following order: [value], [label], [hint].
+  /// The concatenated value is then used as the `Text` description.
+  ///
+  /// The reading direction is given by [textDirection].
+  ///
+  /// See also:
+  ///
+  ///  * [decreasedValue], describes what [value] will be after performing
+  ///    [SemanticsAction.decrease].
+  ///  * [increasedValue], describes what [value] will be after performing
+  ///    [SemanticsAction.increase].
+  String get value => _value;
+  String _value = '';
+  set value(String value) {
+    assert(value != null);
+    _value = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// The value that [value] will have after performing a
+  /// [SemanticsAction.decrease] action.
+  ///
+  /// This must be set if a handler for [SemanticsAction.decrease] is provided
+  /// and [value] is set.
+  ///
+  /// The reading direction is given by [textDirection].
+  String get decreasedValue => _decreasedValue;
+  String _decreasedValue = '';
+  set decreasedValue(String decreasedValue) {
+    assert(decreasedValue != null);
+    _decreasedValue = decreasedValue;
+    _hasBeenAnnotated = true;
+  }
+
+  /// The value that [value] will have after performing a
+  /// [SemanticsAction.increase] action.
+  ///
+  /// This must be set if a handler for [SemanticsAction.increase] is provided
+  /// and [value] is set.
+  ///
+  /// The reading direction is given by [textDirection].
+  String get increasedValue => _increasedValue;
+  String _increasedValue = '';
+  set increasedValue(String increasedValue) {
+    assert(increasedValue != null);
+    _increasedValue = increasedValue;
+    _hasBeenAnnotated = true;
+  }
+
+  /// A brief description of the result of performing an action on this node.
+  ///
+  /// On iOS this is used for the `accessibilityHint` property defined in the
+  /// `UIAccessibility` Protocol. On Android it is concatenated together with
+  /// [label] and [value] in the following order: [value], [label], [hint].
+  /// The concatenated value is then used as the `Text` description.
+  ///
+  /// The reading direction is given by [textDirection].
+  String get hint => _hint;
+  String _hint = '';
+  set hint(String hint) {
+    assert(hint != null);
+    _hint = hint;
+    _hasBeenAnnotated = true;
+  }
+
+  /// Provides hint values which override the default hints on supported
+  /// platforms.
+  SemanticsHintOverrides? get hintOverrides => _hintOverrides;
+  SemanticsHintOverrides? _hintOverrides;
+  set hintOverrides(SemanticsHintOverrides? value) {
+    if (value == null)
+      return;
+    _hintOverrides = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// The elevation in z-direction at which the owning [RenderObject] is
+  /// located relative to its parent.
+  double get elevation => _elevation;
+  double _elevation = 0.0;
+  set elevation(double value) {
+    assert(value != null && value >= 0.0);
+    if (value == _elevation) {
+      return;
+    }
+    _elevation = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// The extend that the owning [RenderObject] occupies in z-direction starting
+  /// at [elevation].
+  ///
+  /// It's extremely rare to set this value directly. Instead, it is calculated
+  /// implicitly when other [SemanticsConfiguration]s are merged into this one
+  /// via [absorb].
+  double get thickness => _thickness;
+  double _thickness = 0.0;
+  set thickness(double value) {
+    assert(value != null && value >= 0.0);
+    if (value == _thickness) {
+      return;
+    }
+    _thickness = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// Whether the semantics node is the root of a subtree for which values
+  /// should be announced.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsFlag.scopesRoute], for a full description of route scoping.
+  bool get scopesRoute => _hasFlag(SemanticsFlag.scopesRoute);
+  set scopesRoute(bool value) {
+    _setFlag(SemanticsFlag.scopesRoute, value);
+  }
+
+  /// Whether the semantics node contains the label of a route.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsFlag.namesRoute], for a full description of route naming.
+  bool get namesRoute => _hasFlag(SemanticsFlag.namesRoute);
+  set namesRoute(bool value) {
+    _setFlag(SemanticsFlag.namesRoute, value);
+  }
+
+  /// Whether the semantics node represents an image.
+  bool get isImage => _hasFlag(SemanticsFlag.isImage);
+  set isImage(bool value) {
+    _setFlag(SemanticsFlag.isImage, value);
+  }
+
+  /// Whether the semantics node is a live region.
+  ///
+  /// On Android, when the label changes on a live region semantics node,
+  /// TalkBack will make a polite announcement of the current label. This
+  /// announcement occurs even if the node is not focused, but only if the label
+  /// has changed since the last update.
+  ///
+  /// An example of a live region is the [SnackBar] widget. When it appears
+  /// on the screen it may be difficult to focus to read the label. A live
+  /// region causes an initial polite announcement to be generated
+  /// automatically.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsFlag.isLiveRegion], the semantics flag that this setting controls.
+  bool get liveRegion => _hasFlag(SemanticsFlag.isLiveRegion);
+  set liveRegion(bool value) {
+    _setFlag(SemanticsFlag.isLiveRegion, value);
+  }
+
+  /// The reading direction for the text in [label], [value], [hint],
+  /// [increasedValue], and [decreasedValue].
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection;
+  set textDirection(TextDirection? textDirection) {
+    _textDirection = textDirection;
+    _hasBeenAnnotated = true;
+  }
+
+  /// Whether the owning [RenderObject] is selected (true) or not (false).
+  ///
+  /// This is different from having accessibility focus. The element that is
+  /// accessibility focused may or may not be selected; e.g. a [ListTile] can have
+  /// accessibility focus but have its [ListTile.selected] property set to false,
+  /// in which case it will not be flagged as selected.
+  bool get isSelected => _hasFlag(SemanticsFlag.isSelected);
+  set isSelected(bool value) {
+    _setFlag(SemanticsFlag.isSelected, value);
+  }
+
+  /// Whether the owning [RenderObject] is currently enabled.
+  ///
+  /// A disabled object does not respond to user interactions. Only objects that
+  /// usually respond to user interactions, but which currently do not (like a
+  /// disabled button) should be marked as disabled.
+  ///
+  /// The setter should not be called for objects (like static text) that never
+  /// respond to user interactions.
+  ///
+  /// The getter will return null if the owning [RenderObject] doesn't support
+  /// the concept of being enabled/disabled.
+  ///
+  /// This property does not control whether semantics are enabled. If you wish to
+  /// disable semantics for a particular widget, you should use an [ExcludeSemantics]
+  /// widget.
+  bool? get isEnabled => _hasFlag(SemanticsFlag.hasEnabledState) ? _hasFlag(SemanticsFlag.isEnabled) : null;
+  set isEnabled(bool? value) {
+    _setFlag(SemanticsFlag.hasEnabledState, true);
+    _setFlag(SemanticsFlag.isEnabled, value!);
+  }
+
+  /// If this node has Boolean state that can be controlled by the user, whether
+  /// that state is checked or unchecked, corresponding to true and false,
+  /// respectively.
+  ///
+  /// Do not call the setter for this field if the owning [RenderObject] doesn't
+  /// have checked/unchecked state that can be controlled by the user.
+  ///
+  /// The getter returns null if the owning [RenderObject] does not have
+  /// checked/unchecked state.
+  bool? get isChecked => _hasFlag(SemanticsFlag.hasCheckedState) ? _hasFlag(SemanticsFlag.isChecked) : null;
+  set isChecked(bool? value) {
+    _setFlag(SemanticsFlag.hasCheckedState, true);
+    _setFlag(SemanticsFlag.isChecked, value!);
+  }
+
+  /// If this node has Boolean state that can be controlled by the user, whether
+  /// that state is on or off, corresponding to true and false, respectively.
+  ///
+  /// Do not call the setter for this field if the owning [RenderObject] doesn't
+  /// have on/off state that can be controlled by the user.
+  ///
+  /// The getter returns null if the owning [RenderObject] does not have
+  /// on/off state.
+  bool? get isToggled => _hasFlag(SemanticsFlag.hasToggledState) ? _hasFlag(SemanticsFlag.isToggled) : null;
+  set isToggled(bool? value) {
+    _setFlag(SemanticsFlag.hasToggledState, true);
+    _setFlag(SemanticsFlag.isToggled, value!);
+  }
+
+  /// Whether the owning RenderObject corresponds to UI that allows the user to
+  /// pick one of several mutually exclusive options.
+  ///
+  /// For example, a [Radio] button is in a mutually exclusive group because
+  /// only one radio button in that group can be marked as [isChecked].
+  bool get isInMutuallyExclusiveGroup => _hasFlag(SemanticsFlag.isInMutuallyExclusiveGroup);
+  set isInMutuallyExclusiveGroup(bool value) {
+    _setFlag(SemanticsFlag.isInMutuallyExclusiveGroup, value);
+  }
+
+  /// Whether the owning [RenderObject] can hold the input focus.
+  bool get isFocusable => _hasFlag(SemanticsFlag.isFocusable);
+  set isFocusable(bool value) {
+    _setFlag(SemanticsFlag.isFocusable, value);
+  }
+
+  /// Whether the owning [RenderObject] currently holds the input focus.
+  bool get isFocused => _hasFlag(SemanticsFlag.isFocused);
+  set isFocused(bool value) {
+    _setFlag(SemanticsFlag.isFocused, value);
+  }
+
+  /// Whether the owning [RenderObject] is a button (true) or not (false).
+  bool get isButton => _hasFlag(SemanticsFlag.isButton);
+  set isButton(bool value) {
+    _setFlag(SemanticsFlag.isButton, value);
+  }
+
+  /// Whether the owning [RenderObject] is a link (true) or not (false).
+  bool get isLink => _hasFlag(SemanticsFlag.isLink);
+  set isLink(bool value) {
+    _setFlag(SemanticsFlag.isLink, value);
+  }
+
+  /// Whether the owning [RenderObject] is a header (true) or not (false).
+  bool get isHeader => _hasFlag(SemanticsFlag.isHeader);
+  set isHeader(bool value) {
+    _setFlag(SemanticsFlag.isHeader, value);
+  }
+
+  /// Whether the owning [RenderObject] is a slider (true) or not (false).
+  bool get isSlider => _hasFlag(SemanticsFlag.isSlider);
+  set isSlider(bool value) {
+    _setFlag(SemanticsFlag.isSlider, value);
+  }
+
+  /// Whether the owning [RenderObject] is considered hidden.
+  ///
+  /// Hidden elements are currently not visible on screen. They may be covered
+  /// by other elements or positioned outside of the visible area of a viewport.
+  ///
+  /// Hidden elements cannot gain accessibility focus though regular touch. The
+  /// only way they can be focused is by moving the focus to them via linear
+  /// navigation.
+  ///
+  /// Platforms are free to completely ignore hidden elements and new platforms
+  /// are encouraged to do so.
+  ///
+  /// Instead of marking an element as hidden it should usually be excluded from
+  /// the semantics tree altogether. Hidden elements are only included in the
+  /// semantics tree to work around platform limitations and they are mainly
+  /// used to implement accessibility scrolling on iOS.
+  bool get isHidden => _hasFlag(SemanticsFlag.isHidden);
+  set isHidden(bool value) {
+    _setFlag(SemanticsFlag.isHidden, value);
+  }
+
+  /// Whether the owning [RenderObject] is a text field.
+  bool get isTextField => _hasFlag(SemanticsFlag.isTextField);
+  set isTextField(bool value) {
+    _setFlag(SemanticsFlag.isTextField, value);
+  }
+
+  /// Whether the owning [RenderObject] is read only.
+  ///
+  /// Only applicable when [isTextField] is true.
+  bool get isReadOnly => _hasFlag(SemanticsFlag.isReadOnly);
+  set isReadOnly(bool value) {
+    _setFlag(SemanticsFlag.isReadOnly, value);
+  }
+
+  /// Whether the [value] should be obscured.
+  ///
+  /// This option is usually set in combination with [isTextField] to indicate
+  /// that the text field contains a password (or other sensitive information).
+  /// Doing so instructs screen readers to not read out the [value].
+  bool get isObscured => _hasFlag(SemanticsFlag.isObscured);
+  set isObscured(bool value) {
+    _setFlag(SemanticsFlag.isObscured, value);
+  }
+
+  /// Whether the text field is multiline.
+  ///
+  /// This option is usually set in combination with [isTextField] to indicate
+  /// that the text field is configured to be multiline.
+  bool get isMultiline => _hasFlag(SemanticsFlag.isMultiline);
+  set isMultiline(bool value) {
+    _setFlag(SemanticsFlag.isMultiline, value);
+  }
+
+  /// Whether the platform can scroll the semantics node when the user attempts
+  /// to move focus to an offscreen child.
+  ///
+  /// For example, a [ListView] widget has implicit scrolling so that users can
+  /// easily move to the next visible set of children. A [TabBar] widget does
+  /// not have implicit scrolling, so that users can navigate into the tab
+  /// body when reaching the end of the tab bar.
+  bool get hasImplicitScrolling => _hasFlag(SemanticsFlag.hasImplicitScrolling);
+  set hasImplicitScrolling(bool value) {
+    _setFlag(SemanticsFlag.hasImplicitScrolling, value);
+  }
+
+  /// The currently selected text (or the position of the cursor) within [value]
+  /// if this node represents a text field.
+  TextSelection? get textSelection => _textSelection;
+  TextSelection? _textSelection;
+  set textSelection(TextSelection? value) {
+    assert(value != null);
+    _textSelection = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// Indicates the current scrolling position in logical pixels if the node is
+  /// scrollable.
+  ///
+  /// The properties [scrollExtentMin] and [scrollExtentMax] indicate the valid
+  /// in-range values for this property. The value for [scrollPosition] may
+  /// (temporarily) be outside that range, e.g. during an overscroll.
+  ///
+  /// See also:
+  ///
+  ///  * [ScrollPosition.pixels], from where this value is usually taken.
+  double? get scrollPosition => _scrollPosition;
+  double? _scrollPosition;
+  set scrollPosition(double? value) {
+    assert(value != null);
+    _scrollPosition = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// Indicates the maximum in-range value for [scrollPosition] if the node is
+  /// scrollable.
+  ///
+  /// This value may be infinity if the scroll is unbound.
+  ///
+  /// See also:
+  ///
+  ///  * [ScrollPosition.maxScrollExtent], from where this value is usually taken.
+  double? get scrollExtentMax => _scrollExtentMax;
+  double? _scrollExtentMax;
+  set scrollExtentMax(double? value) {
+    assert(value != null);
+    _scrollExtentMax = value;
+    _hasBeenAnnotated = true;
+  }
+
+  /// Indicates the minimum in-range value for [scrollPosition] if the node is
+  /// scrollable.
+  ///
+  /// This value may be infinity if the scroll is unbound.
+  ///
+  /// See also:
+  ///
+  ///  * [ScrollPosition.minScrollExtent], from where this value is usually taken.
+  double? get scrollExtentMin => _scrollExtentMin;
+  double? _scrollExtentMin;
+  set scrollExtentMin(double? value) {
+    assert(value != null);
+    _scrollExtentMin = value;
+    _hasBeenAnnotated = true;
+  }
+
+  // TAGS
+
+  /// The set of tags that this configuration wants to add to all child
+  /// [SemanticsNode]s.
+  ///
+  /// See also:
+  ///
+  ///  * [addTagForChildren] to add a tag and for more information about their
+  ///    usage.
+  Iterable<SemanticsTag>? get tagsForChildren => _tagsForChildren;
+  Set<SemanticsTag>? _tagsForChildren;
+
+  /// Specifies a [SemanticsTag] that this configuration wants to apply to all
+  /// child [SemanticsNode]s.
+  ///
+  /// The tag is added to all [SemanticsNode] that pass through the
+  /// [RenderObject] owning this configuration while looking to be attached to a
+  /// parent [SemanticsNode].
+  ///
+  /// Tags are used to communicate to a parent [SemanticsNode] that a child
+  /// [SemanticsNode] was passed through a particular [RenderObject]. The parent
+  /// can use this information to determine the shape of the semantics tree.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderViewport.excludeFromScrolling] for an example of
+  ///    how tags are used.
+  void addTagForChildren(SemanticsTag tag) {
+    _tagsForChildren ??= <SemanticsTag>{};
+    _tagsForChildren!.add(tag);
+  }
+
+  // INTERNAL FLAG MANAGEMENT
+
+  int _flags = 0;
+  void _setFlag(SemanticsFlag flag, bool value) {
+    if (value) {
+      _flags |= flag.index;
+    } else {
+      _flags &= ~flag.index;
+    }
+    _hasBeenAnnotated = true;
+  }
+
+  bool _hasFlag(SemanticsFlag flag) => (_flags & flag.index) != 0;
+
+  // CONFIGURATION COMBINATION LOGIC
+
+  /// Whether this configuration is compatible with the provided `other`
+  /// configuration.
+  ///
+  /// Two configurations are said to be compatible if they can be added to the
+  /// same [SemanticsNode] without losing any semantics information.
+  bool isCompatibleWith(SemanticsConfiguration? other) {
+    if (other == null || !other.hasBeenAnnotated || !hasBeenAnnotated)
+      return true;
+    if (_actionsAsBits & other._actionsAsBits != 0)
+      return false;
+    if ((_flags & other._flags) != 0)
+      return false;
+    if (_platformViewId != null && other._platformViewId != null) {
+      return false;
+    }
+    if (_maxValueLength != null && other._maxValueLength != null) {
+      return false;
+    }
+    if (_currentValueLength != null && other._currentValueLength != null) {
+      return false;
+    }
+    if (_value != null && _value.isNotEmpty && other._value != null && other._value.isNotEmpty)
+      return false;
+    return true;
+  }
+
+  /// Absorb the semantic information from `child` into this configuration.
+  ///
+  /// This adds the semantic information of both configurations and saves the
+  /// result in this configuration.
+  ///
+  /// The [RenderObject] owning the `child` configuration must be a descendant
+  /// of the [RenderObject] that owns this configuration.
+  ///
+  /// Only configurations that have [explicitChildNodes] set to false can
+  /// absorb other configurations and it is recommended to only absorb compatible
+  /// configurations as determined by [isCompatibleWith].
+  void absorb(SemanticsConfiguration child) {
+    assert(!explicitChildNodes);
+
+    if (!child.hasBeenAnnotated)
+      return;
+
+    _actions.addAll(child._actions);
+    _customSemanticsActions.addAll(child._customSemanticsActions);
+    _actionsAsBits |= child._actionsAsBits;
+    _flags |= child._flags;
+    _textSelection ??= child._textSelection;
+    _scrollPosition ??= child._scrollPosition;
+    _scrollExtentMax ??= child._scrollExtentMax;
+    _scrollExtentMin ??= child._scrollExtentMin;
+    _hintOverrides ??= child._hintOverrides;
+    _indexInParent ??= child.indexInParent;
+    _scrollIndex ??= child._scrollIndex;
+    _scrollChildCount ??= child._scrollChildCount;
+    _platformViewId ??= child._platformViewId;
+    _maxValueLength ??= child._maxValueLength;
+    _currentValueLength ??= child._currentValueLength;
+
+    textDirection ??= child.textDirection;
+    _sortKey ??= child._sortKey;
+    _label = _concatStrings(
+      thisString: _label,
+      thisTextDirection: textDirection,
+      otherString: child._label,
+      otherTextDirection: child.textDirection,
+    );
+    if (_decreasedValue == '' || _decreasedValue == null)
+      _decreasedValue = child._decreasedValue;
+    if (_value == '' || _value == null)
+      _value = child._value;
+    if (_increasedValue == '' || _increasedValue == null)
+      _increasedValue = child._increasedValue;
+    _hint = _concatStrings(
+      thisString: _hint,
+      thisTextDirection: textDirection,
+      otherString: child._hint,
+      otherTextDirection: child.textDirection,
+    );
+
+    _thickness = math.max(_thickness, child._thickness + child._elevation);
+
+    _hasBeenAnnotated = _hasBeenAnnotated || child._hasBeenAnnotated;
+  }
+
+  /// Returns an exact copy of this configuration.
+  SemanticsConfiguration copy() {
+    return SemanticsConfiguration()
+      .._isSemanticBoundary = _isSemanticBoundary
+      ..explicitChildNodes = explicitChildNodes
+      ..isBlockingSemanticsOfPreviouslyPaintedNodes = isBlockingSemanticsOfPreviouslyPaintedNodes
+      .._hasBeenAnnotated = _hasBeenAnnotated
+      .._isMergingSemanticsOfDescendants = _isMergingSemanticsOfDescendants
+      .._textDirection = _textDirection
+      .._sortKey = _sortKey
+      .._label = _label
+      .._increasedValue = _increasedValue
+      .._value = _value
+      .._decreasedValue = _decreasedValue
+      .._hint = _hint
+      .._hintOverrides = _hintOverrides
+      .._elevation = _elevation
+      .._thickness = _thickness
+      .._flags = _flags
+      .._tagsForChildren = _tagsForChildren
+      .._textSelection = _textSelection
+      .._scrollPosition = _scrollPosition
+      .._scrollExtentMax = _scrollExtentMax
+      .._scrollExtentMin = _scrollExtentMin
+      .._actionsAsBits = _actionsAsBits
+      .._indexInParent = indexInParent
+      .._scrollIndex = _scrollIndex
+      .._scrollChildCount = _scrollChildCount
+      .._platformViewId = _platformViewId
+      .._maxValueLength = _maxValueLength
+      .._currentValueLength = _currentValueLength
+      .._actions.addAll(_actions)
+      .._customSemanticsActions.addAll(_customSemanticsActions);
+  }
+}
+
+/// Used by [debugDumpSemanticsTree] to specify the order in which child nodes
+/// are printed.
+enum DebugSemanticsDumpOrder {
+  /// Print nodes in inverse hit test order.
+  ///
+  /// In inverse hit test order, the last child of a [SemanticsNode] will be
+  /// asked first if it wants to respond to a user's interaction, followed by
+  /// the second last, etc. until a taker is found.
+  inverseHitTest,
+
+  /// Print nodes in semantic traversal order.
+  ///
+  /// This is the order in which a user would navigate the UI using the "next"
+  /// and "previous" gestures.
+  traversalOrder,
+}
+
+String _concatStrings({
+  required String thisString,
+  required String otherString,
+  required TextDirection? thisTextDirection,
+  required TextDirection? otherTextDirection,
+}) {
+  if (otherString.isEmpty)
+    return thisString;
+  String nestedLabel = otherString;
+  if (thisTextDirection != otherTextDirection && otherTextDirection != null) {
+    switch (otherTextDirection) {
+      case TextDirection.rtl:
+        nestedLabel = '${Unicode.RLE}$nestedLabel${Unicode.PDF}';
+        break;
+      case TextDirection.ltr:
+        nestedLabel = '${Unicode.LRE}$nestedLabel${Unicode.PDF}';
+        break;
+    }
+  }
+  if (thisString.isEmpty)
+    return nestedLabel;
+  return '$thisString\n$nestedLabel';
+}
+
+/// Base class for all sort keys for [SemanticsProperties.sortKey] accessibility
+/// traversal order sorting.
+///
+/// Sort keys are sorted by [name], then by the comparison that the subclass
+/// implements. If [SemanticsProperties.sortKey] is specified, sort keys within
+/// the same semantic group must all be of the same type.
+///
+/// Keys with no [name] are compared to other keys with no [name], and will
+/// be traversed before those with a [name].
+///
+/// If no sort key is applied to a semantics node, then it will be ordered using
+/// a platform dependent default algorithm.
+///
+/// See also:
+///
+///  * [OrdinalSortKey] for a sort key that sorts using an ordinal.
+abstract class SemanticsSortKey with Diagnosticable implements Comparable<SemanticsSortKey> {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const SemanticsSortKey({this.name});
+
+  /// An optional name that will group this sort key with other sort keys of the
+  /// same [name].
+  ///
+  /// Sort keys must have the same `runtimeType` when compared.
+  ///
+  /// Keys with no [name] are compared to other keys with no [name], and will
+  /// be traversed before those with a [name].
+  final String? name;
+
+  @override
+  int compareTo(SemanticsSortKey other) {
+    // Sort by name first and then subclass ordering.
+    assert(runtimeType == other.runtimeType, 'Semantics sort keys can only be compared to other sort keys of the same type.');
+
+    // Defer to the subclass implementation for ordering only if the names are
+    // identical (or both null).
+    if (name == other.name) {
+      return doCompare(other);
+    }
+
+    // Keys that don't have a name are sorted together and come before those with
+    // a name.
+    if (name == null && other.name != null) {
+      return -1;
+    } else if (name != null && other.name == null) {
+      return 1;
+    }
+
+    return name!.compareTo(other.name!);
+  }
+
+  /// The implementation of [compareTo].
+  ///
+  /// The argument is guaranteed to be of the same type as this object and have
+  /// the same [name].
+  ///
+  /// The method should return a negative number if this object comes earlier in
+  /// the sort order than the argument; and a positive number if it comes later
+  /// in the sort order. Returning zero causes the system to use default sort
+  /// order.
+  @protected
+  int doCompare(covariant SemanticsSortKey other);
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(StringProperty('name', name, defaultValue: null));
+  }
+}
+
+/// A [SemanticsSortKey] that sorts simply based on the `double` value it is
+/// given.
+///
+/// The [OrdinalSortKey] compares itself with other [OrdinalSortKey]s
+/// to sort based on the order it is given.
+///
+/// [OrdinalSortKey]s are sorted by the optional [name], then by their [order].
+/// If [SemanticsProperties.sortKey] is a [OrdinalSortKey], then all the other
+/// specified sort keys in the same semantics group must also be
+/// [OrdinalSortKey]s.
+///
+/// Keys with no [name] are compared to other keys with no [name], and will
+/// be traversed before those with a [name].
+///
+/// The ordinal value [order] is typically a whole number, though it can be
+/// fractional, e.g. in order to fit between two other consecutive whole
+/// numbers. The value must be finite (it cannot be [double.nan],
+/// [double.infinity], or [double.negativeInfinity]).
+class OrdinalSortKey extends SemanticsSortKey {
+  /// Creates a const semantics sort key that uses a [double] as its key value.
+  ///
+  /// The [order] must be a finite number, and must not be null.
+  const OrdinalSortKey(
+    this.order, {
+    String? name,
+  }) : assert(order != null),
+       assert(order != double.nan),
+       assert(order > double.negativeInfinity),
+       assert(order < double.infinity),
+       super(name: name);
+
+  /// Determines the placement of this key in a sequence of keys that defines
+  /// the order in which this node is traversed by the platform's accessibility
+  /// services.
+  ///
+  /// Lower values will be traversed first. Keys with the same [name] will be
+  /// grouped together and sorted by name first, and then sorted by [order].
+  final double order;
+
+  @override
+  int doCompare(OrdinalSortKey other) {
+    if (other.order == null || order == null || other.order == order)
+      return 0;
+    return order.compareTo(other.order);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('order', order, defaultValue: null));
+  }
+}
diff --git a/lib/src/semantics/semantics_event.dart b/lib/src/semantics/semantics_event.dart
new file mode 100644
index 0000000..88c1eee
--- /dev/null
+++ b/lib/src/semantics/semantics_event.dart
@@ -0,0 +1,163 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart';
+
+/// An event sent by the application to notify interested listeners that
+/// something happened to the user interface (e.g. a view scrolled).
+///
+/// These events are usually interpreted by assistive technologies to give the
+/// user additional clues about the current state of the UI.
+abstract class SemanticsEvent {
+  /// Initializes internal fields.
+  ///
+  /// [type] is a string that identifies this class of [SemanticsEvent]s.
+  const SemanticsEvent(this.type);
+
+  /// The type of this event.
+  ///
+  /// The type is used by the engine to translate this event into the
+  /// appropriate native event (`UIAccessibility*Notification` on iOS and
+  /// `AccessibilityEvent` on Android).
+  final String type;
+
+  /// Converts this event to a Map that can be encoded with
+  /// [StandardMessageCodec].
+  ///
+  /// [nodeId] is the unique identifier of the semantics node associated with
+  /// the event, or null if the event is not associated with a semantics node.
+  Map<String, dynamic> toMap({ int? nodeId }) {
+    final Map<String, dynamic> event = <String, dynamic>{
+      'type': type,
+      'data': getDataMap(),
+    };
+    if (nodeId != null)
+      event['nodeId'] = nodeId;
+
+    return event;
+  }
+
+  /// Returns the event's data object.
+  Map<String, dynamic> getDataMap();
+
+  @override
+  String toString() {
+    final List<String> pairs = <String>[];
+    final Map<String, dynamic> dataMap = getDataMap();
+    final List<String> sortedKeys = dataMap.keys.toList()..sort();
+    for (final String key in sortedKeys)
+      pairs.add('$key: ${dataMap[key]}');
+    return '${objectRuntimeType(this, 'SemanticsEvent')}(${pairs.join(', ')})';
+  }
+}
+
+/// An event for a semantic announcement.
+///
+/// This should be used for announcement that are not seamlessly announced by
+/// the system as a result of a UI state change.
+///
+/// For example a camera application can use this method to make accessibility
+/// announcements regarding objects in the viewfinder.
+///
+/// When possible, prefer using mechanisms like [Semantics] to implicitly
+/// trigger announcements over using this event.
+class AnnounceSemanticsEvent extends SemanticsEvent {
+
+  /// Constructs an event that triggers an announcement by the platform.
+  const AnnounceSemanticsEvent(this.message, this.textDirection)
+    : assert(message != null),
+      assert(textDirection != null),
+      super('announce');
+
+  /// The message to announce.
+  ///
+  /// This property must not be null.
+  final String message;
+
+  /// Text direction for [message].
+  ///
+  /// This property must not be null.
+  final TextDirection textDirection;
+
+  @override
+  Map<String, dynamic> getDataMap() {
+    return <String, dynamic>{
+      'message': message,
+      'textDirection': textDirection.index,
+    };
+  }
+}
+
+/// An event for a semantic announcement of a tooltip.
+///
+/// This is only used by Android to announce tooltip values.
+class TooltipSemanticsEvent extends SemanticsEvent {
+
+  /// Constructs an event that triggers a tooltip announcement by the platform.
+  const TooltipSemanticsEvent(this.message) : super('tooltip');
+
+  /// The text content of the tooltip.
+  final String message;
+
+  @override
+  Map<String, dynamic> getDataMap() {
+    return <String, dynamic>{
+      'message': message,
+    };
+  }
+}
+
+/// An event which triggers long press semantic feedback.
+///
+/// Currently only honored on Android. Triggers a long-press specific sound
+/// when TalkBack is enabled.
+class LongPressSemanticsEvent extends SemanticsEvent {
+
+  /// Constructs an event that triggers a long-press semantic feedback by the platform.
+  const LongPressSemanticsEvent() : super('longPress');
+
+  @override
+  Map<String, dynamic> getDataMap() => const <String, dynamic>{};
+}
+
+/// An event which triggers tap semantic feedback.
+///
+/// Currently only honored on Android. Triggers a tap specific sound when
+/// TalkBack is enabled.
+class TapSemanticEvent extends SemanticsEvent {
+
+  /// Constructs an event that triggers a long-press semantic feedback by the platform.
+  const TapSemanticEvent() : super('tap');
+
+  @override
+  Map<String, dynamic> getDataMap() => const <String, dynamic>{};
+}
+
+/// An event which triggers a polite announcement of a live region.
+///
+/// This requires that the semantics node has already been marked as a live
+/// region. On Android, TalkBack will make a verbal announcement, as long as
+/// the label of the semantics node has changed since the last live region
+/// update. iOS does not currently support this event.
+///
+/// Deprecated. This message was never implemented, and references to it should
+/// be removed.
+///
+/// See also:
+///
+///  * [SemanticsFlag.isLiveRegion], for a description of live regions.
+///
+@Deprecated(
+  'This event has never been implemented and will be removed in a future version of Flutter. References to it should be removed. '
+  'This feature was deprecated after v1.12.16.'
+)
+class UpdateLiveRegionEvent extends SemanticsEvent {
+  /// Creates a new [UpdateLiveRegionEvent].
+  const UpdateLiveRegionEvent() : super('updateLiveRegion');
+
+  @override
+  Map<String, dynamic> getDataMap() => const <String, dynamic>{};
+}
diff --git a/lib/src/semantics/semantics_service.dart b/lib/src/semantics/semantics_service.dart
new file mode 100644
index 0000000..3b866ad
--- /dev/null
+++ b/lib/src/semantics/semantics_service.dart
@@ -0,0 +1,46 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' show TextDirection;
+
+import 'package:flute/services.dart' show SystemChannels;
+
+import 'semantics_event.dart' show AnnounceSemanticsEvent, TooltipSemanticsEvent;
+
+
+/// Allows access to the platform's accessibility services.
+///
+/// Events sent by this service are handled by the platform-specific
+/// accessibility bridge in Flutter's engine.
+///
+/// When possible, prefer using mechanisms like [Semantics] to implicitly
+/// trigger announcements over using this event.
+class SemanticsService {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  SemanticsService._();
+
+  /// Sends a semantic announcement.
+  ///
+  /// This should be used for announcement that are not seamlessly announced by
+  /// the system as a result of a UI state change.
+  ///
+  /// For example a camera application can use this method to make accessibility
+  /// announcements regarding objects in the viewfinder.
+  static Future<void> announce(String message, TextDirection textDirection) async {
+    final AnnounceSemanticsEvent event = AnnounceSemanticsEvent(message, textDirection);
+    await SystemChannels.accessibility.send(event.toMap());
+  }
+
+  /// Sends a semantic announcement of a tooltip.
+  ///
+  /// Currently only honored on Android. The contents of [message] will be
+  /// read by TalkBack.
+  static Future<void> tooltip(String message) async {
+    final TooltipSemanticsEvent event = TooltipSemanticsEvent(message);
+    await SystemChannels.accessibility.send(event.toMap());
+  }
+}
diff --git a/lib/src/services/asset_bundle.dart b/lib/src/services/asset_bundle.dart
new file mode 100644
index 0000000..ed6b12c
--- /dev/null
+++ b/lib/src/services/asset_bundle.dart
@@ -0,0 +1,285 @@
+// Copyright 2014 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.
+
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:flute/foundation.dart';
+
+import 'binary_messenger.dart';
+
+/// A collection of resources used by the application.
+///
+/// Asset bundles contain resources, such as images and strings, that can be
+/// used by an application. Access to these resources is asynchronous so that
+/// they can be transparently loaded over a network (e.g., from a
+/// [NetworkAssetBundle]) or from the local file system without blocking the
+/// application's user interface.
+///
+/// Applications have a [rootBundle], which contains the resources that were
+/// packaged with the application when it was built. To add resources to the
+/// [rootBundle] for your application, add them to the `assets` subsection of
+/// the `flutter` section of your application's `pubspec.yaml` manifest.
+///
+/// For example:
+///
+/// ```yaml
+/// name: my_awesome_application
+/// flutter:
+///   assets:
+///    - images/hamilton.jpeg
+///    - images/lafayette.jpeg
+/// ```
+///
+/// Rather than accessing the [rootBundle] global static directly, consider
+/// obtaining the [AssetBundle] for the current [BuildContext] using
+/// [DefaultAssetBundle.of]. This layer of indirection lets ancestor widgets
+/// substitute a different [AssetBundle] (e.g., for testing or localization) at
+/// runtime rather than directly replying upon the [rootBundle] created at build
+/// time. For convenience, the [WidgetsApp] or [MaterialApp] widget at the top
+/// of the widget hierarchy configures the [DefaultAssetBundle] to be the
+/// [rootBundle].
+///
+/// See also:
+///
+///  * [DefaultAssetBundle]
+///  * [NetworkAssetBundle]
+///  * [rootBundle]
+abstract class AssetBundle {
+  /// Retrieve a binary resource from the asset bundle as a data stream.
+  ///
+  /// Throws an exception if the asset is not found.
+  Future<ByteData> load(String key);
+
+  /// Retrieve a string from the asset bundle.
+  ///
+  /// Throws an exception if the asset is not found.
+  ///
+  /// If the `cache` argument is set to false, then the data will not be
+  /// cached, and reading the data may bypass the cache. This is useful if the
+  /// caller is going to be doing its own caching. (It might not be cached if
+  /// it's set to true either, that depends on the asset bundle
+  /// implementation.)
+  ///
+  /// If the `unzip` argument is set to true, it would first unzip file at the
+  /// specified location before retrieving the string content.
+  Future<String> loadString(
+    String key,
+    {
+      bool cache = true,
+      bool unzip = false,
+    }
+  ) async {
+    final ByteData data = await load(key);
+    // Note: data has a non-nullable type, but might be null when running with
+    // weak checking, so we need to null check it anyway (and ignore the warning
+    // that the null-handling logic is dead code).
+    if (data == null)
+      throw FlutterError('Unable to load asset: $key'); // ignore: dead_code
+    // 50 KB of data should take 2-3 ms to parse on a Moto G4, and about 400 μs
+    // on a Pixel 4.
+    if (data.lengthInBytes < 50 * 1024 && !unzip) {
+      return _utf8Decode(data);
+    }
+
+    // For strings larger than 50 KB, run the computation in an isolate to
+    // avoid causing main thread jank.
+    return compute(
+      unzip ? _utf8ZipDecode : _utf8Decode,
+      data,
+      debugLabel: '${unzip ? "Unzip and ": ""}UTF8 decode for "$key"',
+    );
+  }
+
+  static String _utf8ZipDecode(ByteData data) {
+    List<int> bytes = data.buffer.asUint8List();
+    bytes = gzip.decode(bytes);
+    return utf8.decode(bytes);
+  }
+
+  static String _utf8Decode(ByteData data) {
+    return utf8.decode(data.buffer.asUint8List());
+  }
+
+  /// Retrieve a string from the asset bundle, parse it with the given function,
+  /// and return the function's result.
+  ///
+  /// Implementations may cache the result, so a particular key should only be
+  /// used with one parser for the lifetime of the asset bundle.
+  Future<T> loadStructuredData<T>(String key, Future<T> parser(String value));
+
+  /// If this is a caching asset bundle, and the given key describes a cached
+  /// asset, then evict the asset from the cache so that the next time it is
+  /// loaded, the cache will be reread from the asset bundle.
+  void evict(String key) { }
+
+  @override
+  String toString() => '${describeIdentity(this)}()';
+}
+
+/// An [AssetBundle] that loads resources over the network.
+///
+/// This asset bundle does not cache any resources, though the underlying
+/// network stack may implement some level of caching itself.
+class NetworkAssetBundle extends AssetBundle {
+  /// Creates an network asset bundle that resolves asset keys as URLs relative
+  /// to the given base URL.
+  NetworkAssetBundle(Uri baseUrl)
+    : _baseUrl = baseUrl,
+      _httpClient = HttpClient();
+
+  final Uri _baseUrl;
+  final HttpClient _httpClient;
+
+  Uri _urlFromKey(String key) => _baseUrl.resolve(key);
+
+  @override
+  Future<ByteData> load(String key) async {
+    final HttpClientRequest request = await _httpClient.getUrl(_urlFromKey(key));
+    final HttpClientResponse response = await request.close();
+    if (response.statusCode != HttpStatus.ok)
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('Unable to load asset: $key'),
+        IntProperty('HTTP status code', response.statusCode),
+      ]);
+    final Uint8List bytes = await consolidateHttpClientResponseBytes(response);
+    return bytes.buffer.asByteData();
+  }
+
+  /// Retrieve a string from the asset bundle, parse it with the given function,
+  /// and return the function's result.
+  ///
+  /// The result is not cached. The parser is run each time the resource is
+  /// fetched.
+  @override
+  Future<T> loadStructuredData<T>(String key, Future<T> parser(String value)) async {
+    assert(key != null);
+    assert(parser != null);
+    return parser(await loadString(key));
+  }
+
+  // TODO(ianh): Once the underlying network logic learns about caching, we
+  // should implement evict().
+
+  @override
+  String toString() => '${describeIdentity(this)}($_baseUrl)';
+}
+
+/// An [AssetBundle] that permanently caches string and structured resources
+/// that have been fetched.
+///
+/// Strings (for [loadString] and [loadStructuredData]) are decoded as UTF-8.
+/// Data that is cached is cached for the lifetime of the asset bundle
+/// (typically the lifetime of the application).
+///
+/// Binary resources (from [load]) are not cached.
+abstract class CachingAssetBundle extends AssetBundle {
+  // TODO(ianh): Replace this with an intelligent cache, see https://github.com/flutter/flutter/issues/3568
+  final Map<String, Future<String>> _stringCache = <String, Future<String>>{};
+  final Map<String, Future<dynamic>> _structuredDataCache = <String, Future<dynamic>>{};
+
+  @override
+  Future<String> loadString(String key, { bool cache = true, bool unzip = false }) {
+    if (cache)
+      return _stringCache.putIfAbsent(key, () => super.loadString(key, unzip: unzip));
+    return super.loadString(key, unzip: unzip);
+  }
+
+  /// Retrieve a string from the asset bundle, parse it with the given function,
+  /// and return the function's result.
+  ///
+  /// The result of parsing the string is cached (the string itself is not,
+  /// unless you also fetch it with [loadString]). For any given `key`, the
+  /// `parser` is only run the first time.
+  ///
+  /// Once the value has been parsed, the future returned by this function for
+  /// subsequent calls will be a [SynchronousFuture], which resolves its
+  /// callback synchronously.
+  @override
+  Future<T> loadStructuredData<T>(String key, Future<T> parser(String value)) {
+    assert(key != null);
+    assert(parser != null);
+    if (_structuredDataCache.containsKey(key))
+      return _structuredDataCache[key]! as Future<T>;
+    Completer<T>? completer;
+    Future<T>? result;
+    loadString(key, cache: false).then<T>(parser).then<void>((T value) {
+      result = SynchronousFuture<T>(value);
+      _structuredDataCache[key] = result!;
+      if (completer != null) {
+        // We already returned from the loadStructuredData function, which means
+        // we are in the asynchronous mode. Pass the value to the completer. The
+        // completer's future is what we returned.
+        completer.complete(value);
+      }
+    });
+    if (result != null) {
+      // The code above ran synchronously, and came up with an answer.
+      // Return the SynchronousFuture that we created above.
+      return result!;
+    }
+    // The code above hasn't yet run its "then" handler yet. Let's prepare a
+    // completer for it to use when it does run.
+    completer = Completer<T>();
+    _structuredDataCache[key] = completer.future;
+    return completer.future;
+  }
+
+  @override
+  void evict(String key) {
+    _stringCache.remove(key);
+    _structuredDataCache.remove(key);
+  }
+}
+
+/// An [AssetBundle] that loads resources using platform messages.
+class PlatformAssetBundle extends CachingAssetBundle {
+  @override
+  Future<ByteData> load(String key) async {
+    final Uint8List encoded = utf8.encoder.convert(Uri(path: Uri.encodeFull(key)).path);
+    final ByteData? asset =
+        await defaultBinaryMessenger.send('flutter/assets', encoded.buffer.asByteData());
+    if (asset == null)
+      throw FlutterError('Unable to load asset: $key');
+    return asset;
+  }
+}
+
+AssetBundle _initRootBundle() {
+  return PlatformAssetBundle();
+}
+
+/// The [AssetBundle] from which this application was loaded.
+///
+/// The [rootBundle] contains the resources that were packaged with the
+/// application when it was built. To add resources to the [rootBundle] for your
+/// application, add them to the `assets` subsection of the `flutter` section of
+/// your application's `pubspec.yaml` manifest.
+///
+/// For example:
+///
+/// ```yaml
+/// name: my_awesome_application
+/// flutter:
+///   assets:
+///    - images/hamilton.jpeg
+///    - images/lafayette.jpeg
+/// ```
+///
+/// Rather than using [rootBundle] directly, consider obtaining the
+/// [AssetBundle] for the current [BuildContext] using [DefaultAssetBundle.of].
+/// This layer of indirection lets ancestor widgets substitute a different
+/// [AssetBundle] at runtime (e.g., for testing or localization) rather than
+/// directly replying upon the [rootBundle] created at build time. For
+/// convenience, the [WidgetsApp] or [MaterialApp] widget at the top of the
+/// widget hierarchy configures the [DefaultAssetBundle] to be the [rootBundle].
+///
+/// See also:
+///
+///  * [DefaultAssetBundle]
+///  * [NetworkAssetBundle]
+final AssetBundle rootBundle = _initRootBundle();
diff --git a/lib/src/services/autofill.dart b/lib/src/services/autofill.dart
new file mode 100644
index 0000000..596cc26
--- /dev/null
+++ b/lib/src/services/autofill.dart
@@ -0,0 +1,816 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+import 'text_input.dart';
+
+/// A collection of commonly used autofill hint strings on different platforms.
+///
+/// Each hint is pre-defined on at least one supported platform. See their
+/// documentation for their availability on each platform, and the platform
+/// values each autofill hint corresponds to.
+class AutofillHints {
+  AutofillHints._();
+
+  /// The input field expects an address locality (city/town).
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_LOCALITY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_LOCALITY).
+  /// * iOS: [addressCity](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String addressCity = 'addressCity';
+
+  /// The input field expects a city name combined with a state name.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [addressCityAndState](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String addressCityAndState = 'addressCityAndState';
+
+  /// The input field expects a region/state.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_REGION](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_REGION).
+  /// * iOS: [addressState](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String addressState = 'addressState';
+
+  /// The input field expects a person's full birth date.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_BIRTH_DATE_FULL](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_FULL).
+  /// * web: ["bday"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String birthday = 'birthday';
+
+  /// The input field expects a person's birth day(of the month).
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_BIRTH_DATE_DAY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_DAY).
+  /// * web: ["bday-day"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String birthdayDay = 'birthdayDay';
+
+  /// The input field expects a person's birth month.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_BIRTH_DATE_MONTH](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_MONTH).
+  /// * web: ["bday-month"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String birthdayMonth = 'birthdayMonth';
+
+  /// The input field expects a person's birth year.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_BIRTH_DATE_YEAR](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_YEAR).
+  /// * web: ["bday-year"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String birthdayYear = 'birthdayYear';
+
+  /// The input field expects an
+  /// [ISO 3166-1-alpha-2](https://www.iso.org/standard/63545.html) country code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["country"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String countryCode = 'countryCode';
+
+  /// The input field expects a country name.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_COUNTRY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_COUNTRY).
+  /// * iOS: [countryName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["country-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String countryName = 'countryName';
+
+  /// The input field expects a credit card expiration date.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_CREDIT_CARD_NUMBER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_NUMBER).
+  /// * web: ["cc-exp"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardExpirationDate = 'creditCardExpirationDate';
+
+  /// The input field expects a credit card expiration day.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardExpirationDay = 'creditCardExpirationDay';
+
+  /// The input field expects a credit card expiration month.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH).
+  /// * web: ["cc-exp-month"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardExpirationMonth = 'creditCardExpirationMonth';
+
+  /// The input field expects a credit card expiration year.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR).
+  /// * web: ["cc-exp-year"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardExpirationYear = 'creditCardExpirationYear';
+
+  /// The input field expects the holder's last/family name as given on a credit
+  /// card.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["cc-family-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardFamilyName = 'creditCardFamilyName';
+
+  /// The input field expects the holder's first/given name as given on a credit
+  /// card.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["cc-given-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardGivenName = 'creditCardGivenName';
+
+  /// The input field expects the holder's middle name as given on a credit
+  /// card.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["cc-additional-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardMiddleName = 'creditCardMiddleName';
+
+  /// The input field expects the holder's full name as given on a credit card.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["cc-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardName = 'creditCardName';
+
+  /// The input field expects a credit card number.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_CREDIT_CARD_NUMBER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_NUMBER).
+  /// * iOS: [creditCardNumber](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["cc-number"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardNumber = 'creditCardNumber';
+
+  /// The input field expects a credit card security code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE).
+  /// * web: ["cc-csc"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardSecurityCode = 'creditCardSecurityCode';
+
+  /// The input field expects the type of a credit card, for example "Visa".
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["cc-type"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String creditCardType = 'creditCardType';
+
+  /// The input field expects an email address.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_EMAIL_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_EMAIL_ADDRESS).
+  /// * iOS: [emailAddress](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["email"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String email = 'email';
+
+  /// The input field expects a person's last/family name.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PERSON_NAME_FAMILY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_FAMILY).
+  /// * iOS: [familyName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["family-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String familyName = 'familyName';
+
+  /// The input field expects a street address that fully identifies a location.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS).
+  /// * iOS: [fullStreetAddress](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["street-address"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String fullStreetAddress = 'fullStreetAddress';
+
+  /// The input field expects a gender.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_GENDER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_GENDER).
+  /// * web: ["sex"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String gender = 'gender';
+
+  /// The input field expects a person's first/given name.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PERSON_NAME_GIVEN](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_GIVEN).
+  /// * iOS: [givenName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["given-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String givenName = 'givenName';
+
+  /// The input field expects a URL representing an instant messaging protocol
+  /// endpoint.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["impp"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String impp = 'impp';
+
+  /// The input field expects a job title.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [jobTitle](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["organization-title"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String jobTitle = 'jobTitle';
+
+  /// The input field expects the preferred language of the user.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["language"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String language = 'language';
+
+  /// The input field expects a location, such as a point of interest, an
+  /// address,or another way to identify a location.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [location](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String location = 'location';
+
+  /// The input field expects a person's middle initial.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PERSON_NAME_MIDDLE_INITIAL](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_MIDDLE_INITIAL).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String middleInitial = 'middleInitial';
+
+  /// The input field expects a person's middle name.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PERSON_NAME_MIDDLE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_MIDDLE).
+  /// * iOS: [middleName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["additional-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String middleName = 'middleName';
+
+  /// The input field expects a person's full name.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PERSON_NAME](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME).
+  /// * iOS: [name](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String name = 'name';
+
+  /// The input field expects a person's name prefix or title, such as "Dr.".
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PERSON_NAME_PREFIX](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_PREFIX).
+  /// * iOS: [namePrefix](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["honorific-prefix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String namePrefix = 'namePrefix';
+
+  /// The input field expects a person's name suffix, such as "Jr.".
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PERSON_NAME_SUFFIX](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_SUFFIX).
+  /// * iOS: [nameSuffix](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["honorific-suffix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String nameSuffix = 'nameSuffix';
+
+  /// The input field expects a newly created password for save/update.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_NEW_PASSWORD](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_NEW_PASSWORD).
+  /// * iOS: [newPassword](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["new-password"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String newPassword = 'newPassword';
+
+  /// The input field expects a newly created username for save/update.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_NEW_USERNAME](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_NEW_USERNAME).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String newUsername = 'newUsername';
+
+  /// The input field expects a nickname.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [nickname](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["nickname"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String nickname = 'nickname';
+
+  /// The input field expects a SMS one-time code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_SMS_OTP](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_SMS_OTP).
+  /// * iOS: [oneTimeCode](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["one-time-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String oneTimeCode = 'oneTimeCode';
+
+  /// The input field expects an organization name corresponding to the person,
+  /// address, or contact information in the other fields associated with this
+  /// field.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [organizationName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["organization"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String organizationName = 'organizationName';
+
+  /// The input field expects a password.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PASSWORD](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PASSWORD).
+  /// * iOS: [password](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["current-password"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String password = 'password';
+
+  /// The input field expects a photograph, icon, or other image corresponding
+  /// to the company, person, address, or contact information in the other
+  /// fields associated with this field.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["photo"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String photo = 'photo';
+
+  /// The input field expects a postal address.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String postalAddress = 'postalAddress';
+
+  /// The input field expects an auxiliary address details.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_ADDRESS).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String postalAddressExtended = 'postalAddressExtended';
+
+  /// The input field expects an extended ZIP/POSTAL code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_POSTAL_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_POSTAL_CODE).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String postalAddressExtendedPostalCode = 'postalAddressExtendedPostalCode';
+
+  /// The input field expects a postal code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_POSTAL_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_CODE).
+  /// * iOS: [postalCode](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["postal-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String postalCode = 'postalCode';
+
+  /// The first administrative level in the address. This is typically the
+  /// province in which the address is located. In the United States, this would
+  /// be the state. In Switzerland, the canton. In the United Kingdom, the post
+  /// town.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["address-level1"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String streetAddressLevel1 = 'streetAddressLevel1';
+
+  /// The second administrative level, in addresses with at least two of them.
+  /// In countries with two administrative levels, this would typically be the
+  /// city, town, village, or other locality in which the address is located.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["address-level2"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String streetAddressLevel2 = 'streetAddressLevel2';
+
+  /// The third administrative level, in addresses with at least three
+  /// administrative levels.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["address-level3"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String streetAddressLevel3 = 'streetAddressLevel3';
+
+  /// The finest-grained administrative level, in addresses which have four
+  /// levels.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["address-level4"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String streetAddressLevel4 = 'streetAddressLevel4';
+
+  /// The input field expects the first line of a street address.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [streetAddressLine1](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["address-line1"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String streetAddressLine1 = 'streetAddressLine1';
+
+  /// The input field expects the second line of a street address.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [streetAddressLine2](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["address-line2"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String streetAddressLine2 = 'streetAddressLine2';
+
+  /// The input field expects the third line of a street address.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["address-line3"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String streetAddressLine3 = 'streetAddressLine3';
+
+  /// The input field expects a sublocality.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [sublocality](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String sublocality = 'sublocality';
+
+  /// The input field expects a telephone number.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PHONE_NUMBER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_NUMBER).
+  /// * iOS: [telephoneNumber](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["tel"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumber = 'telephoneNumber';
+
+  /// The input field expects a phone number's area code, with a country
+  /// -internal prefix applied if applicable.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["tel-area-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberAreaCode = 'telephoneNumberAreaCode';
+
+  /// The input field expects a phone number's country code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PHONE_COUNTRY_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_COUNTRY_CODE).
+  /// * web: ["tel-country-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberCountryCode = 'telephoneNumberCountryCode';
+
+  /// The input field expects the current device's phone number, usually for
+  /// Sign Up / OTP flows.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PHONE_NUMBER_DEVICE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_NUMBER_DEVICE).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberDevice = 'telephoneNumberDevice';
+
+  /// The input field expects a phone number's internal extension code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["tel-extension"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberExtension = 'telephoneNumberExtension';
+
+  /// The input field expects a phone number without the country code and area
+  /// code components.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["tel-local"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberLocal = 'telephoneNumberLocal';
+
+  /// The input field expects the first part of the component of the telephone
+  /// number that follows the area code, when that component is split into two
+  /// components.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["tel-local-prefix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberLocalPrefix = 'telephoneNumberLocalPrefix';
+
+  /// The input field expects the second part of the component of the telephone
+  /// number that follows the area code, when that component is split into two
+  /// components.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["tel-local-suffix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberLocalSuffix = 'telephoneNumberLocalSuffix';
+
+  /// The input field expects a phone number without country code.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_PHONE_NATIONAL](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_NATIONAL).
+  /// * web: ["tel-national"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String telephoneNumberNational = 'telephoneNumberNational';
+
+  /// The amount that the user would like for the transaction (e.g. when
+  /// entering a bid or sale price).
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["transaction-amount"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String transactionAmount = 'transactionAmount';
+
+  /// The currency that the user would prefer the transaction to use, in [ISO
+  /// 4217 currency code](https://www.iso.org/iso-4217-currency-codes.html).
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * web: ["transaction-currency"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String transactionCurrency = 'transactionCurrency';
+
+  /// The input field expects a URL.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * iOS: [URL](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["url"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String url = 'url';
+
+  /// The input field expects a username or an account name.
+  ///
+  /// This hint will be translated to the below values on different platforms:
+  ///
+  /// * Android: [AUTOFILL_HINT_USERNAME](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_USERNAME).
+  /// * iOS: [username](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  /// * web: ["username"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
+  /// * Otherwise, the hint string will be used as-is.
+  static const String username = 'username';
+}
+
+/// A collection of autofill related information that represents an [AutofillClient].
+///
+/// Typically used in [TextInputConfiguration.autofillConfiguration].
+@immutable
+class AutofillConfiguration {
+  /// Creates autofill related configuration information that can be sent to the
+  /// platform.
+  const AutofillConfiguration({
+    required this.uniqueIdentifier,
+    required this.autofillHints,
+    required this.currentEditingValue,
+  }) : assert(uniqueIdentifier != null),
+       assert(autofillHints != null);
+
+  /// A string that uniquely identifies the current [AutofillClient].
+  ///
+  /// The identifier needs to be unique within the [AutofillScope] for the
+  /// [AutofillClient] to receive the correct autofill value.
+  ///
+  /// Must not be null.
+  final String uniqueIdentifier;
+
+  /// A list of strings that helps the autofill service identify the type of the
+  /// [AutofillClient].
+  ///
+  /// Must not be null or empty.
+  ///
+  /// {@template flutter.services.AutofillConfiguration.autofillHints}
+  /// For the best results, hint strings need to be understood by the platform's
+  /// autofill service. The common values of hint strings can be found in
+  /// [AutofillHints], as well as their availability on different platforms.
+  ///
+  /// If an autofillable input field needs to use a custom hint that translates to
+  /// different strings on different platforms, the easiest way to achieve that
+  /// is to return different hint strings based on the value of
+  /// [defaultTargetPlatform].
+  ///
+  /// Each hint in the list, if not ignored, will be translated to the platform's
+  /// autofill hint type understood by its autofill services:
+  ///
+  /// * On iOS, only the first hint in the list is accounted for. The hint will
+  ///   be translated to a
+  ///   [UITextContentType](https://developer.apple.com/documentation/uikit/uitextcontenttype).
+  ///
+  /// * On Android, all hints in the list are translated to Android hint strings.
+  ///
+  /// * On web, only the first hint is accounted for and will be translated to
+  ///   an "autocomplete" string.
+  ///
+  /// Providing an autofill hint that is predefined on the platform does not
+  /// automatically grant the input field eligibility for autofill. Ultimately,
+  /// it comes down to the autofill service currently in charge to determine
+  /// whether an input field is suitable for autofill and what the autofill
+  /// candidates are.
+  ///
+  /// See also:
+  ///
+  /// * [AutofillHints], a list of autofill hint strings that is predefined on at
+  ///   least one platform.
+  ///
+  /// * [UITextContentType](https://developer.apple.com/documentation/uikit/uitextcontenttype),
+  ///   the iOS equivalent.
+  ///
+  /// * Android [autofillHints](https://developer.android.com/reference/android/view/View#setAutofillHints(java.lang.String...)),
+  ///   the Android equivalent.
+  ///
+  /// * The [autocomplete](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) attribute,
+  ///   the web equivalent.
+  /// {@endtemplate}
+  final List<String> autofillHints;
+
+  /// The current [TextEditingValue] of the [AutofillClient].
+  final TextEditingValue currentEditingValue;
+
+  /// Returns a representation of this object as a JSON object.
+  Map<String, dynamic> toJson() {
+    assert(autofillHints.isNotEmpty);
+    return <String, dynamic>{
+      'uniqueIdentifier': uniqueIdentifier,
+      'hints': autofillHints,
+      'editingValue': currentEditingValue.toJSON(),
+    };
+  }
+}
+
+/// An object that represents an autofillable input field in the autofill workflow.
+///
+/// An [AutofillClient] provides autofill-related information of the input field
+/// it represents to the platform, and consumes autofill inputs from the platform.
+abstract class AutofillClient {
+  /// The unique identifier of this [AutofillClient].
+  ///
+  /// Must not be null.
+  String get autofillId;
+
+  /// The [TextInputConfiguration] that describes this [AutofillClient].
+  ///
+  /// In order to participate in autofill, its
+  /// [TextInputConfiguration.autofillConfiguration] must not be null.
+  TextInputConfiguration get textInputConfiguration;
+
+  /// Requests this [AutofillClient] update its [TextEditingValue] to the given
+  /// value.
+  void updateEditingValue(TextEditingValue newEditingValue);
+}
+
+/// An ordered group within which [AutofillClient]s are logically connected.
+///
+/// {@template flutter.services.AutofillScope}
+/// [AutofillClient]s within the same [AutofillScope] are isolated from other
+/// input fields during autofill. That is, when an autofillable [TextInputClient]
+/// gains focus, only the [AutofillClient]s within the same [AutofillScope] will
+/// be visible to the autofill service, in the same order as they appear in
+/// [AutofillScope.autofillClients].
+///
+/// [AutofillScope] also allows [TextInput] to redirect autofill values from the
+/// platform to the [AutofillClient] with the given identifier, by calling
+/// [AutofillScope.getAutofillClient].
+///
+/// An [AutofillClient] that's not tied to any [AutofillScope] will only
+/// participate in autofill if the autofill is directly triggered by its own
+/// [TextInputClient].
+/// {@endtemplate}
+abstract class AutofillScope {
+  /// Gets the [AutofillScope] associated with the given [autofillId], in
+  /// this [AutofillScope].
+  ///
+  /// Returns null if there's no matching [AutofillClient].
+  AutofillClient? getAutofillClient(String autofillId);
+
+  /// The collection of [AutofillClient]s currently tied to this [AutofillScope].
+  ///
+  /// Every [AutofillClient] in this list must have autofill enabled (i.e. its
+  /// [AutofillClient.textInputConfiguration] must have a non-null
+  /// [AutofillConfiguration].)
+  Iterable<AutofillClient> get autofillClients;
+
+  /// Allows a [TextInputClient] to attach to this scope. This method should be
+  /// called in lieu of [TextInput.attach], when the [TextInputClient] wishes to
+  /// participate in autofill.
+  TextInputConnection attach(TextInputClient trigger, TextInputConfiguration configuration);
+}
+
+@immutable
+class _AutofillScopeTextInputConfiguration extends TextInputConfiguration {
+  _AutofillScopeTextInputConfiguration({
+    required this.allConfigurations,
+    required TextInputConfiguration currentClientConfiguration,
+  }) : assert(allConfigurations != null),
+       assert(currentClientConfiguration != null),
+       super(inputType: currentClientConfiguration.inputType,
+         obscureText: currentClientConfiguration.obscureText,
+         autocorrect: currentClientConfiguration.autocorrect,
+         smartDashesType: currentClientConfiguration.smartDashesType,
+         smartQuotesType: currentClientConfiguration.smartQuotesType,
+         enableSuggestions: currentClientConfiguration.enableSuggestions,
+         inputAction: currentClientConfiguration.inputAction,
+         textCapitalization: currentClientConfiguration.textCapitalization,
+         keyboardAppearance: currentClientConfiguration.keyboardAppearance,
+         actionLabel: currentClientConfiguration.actionLabel,
+         autofillConfiguration: currentClientConfiguration.autofillConfiguration,
+       );
+
+  final Iterable<TextInputConfiguration> allConfigurations;
+
+  @override
+  Map<String, dynamic> toJson() {
+    final Map<String, dynamic> result = super.toJson();
+    result['fields'] = allConfigurations
+      .map((TextInputConfiguration configuration) => configuration.toJson())
+      .toList(growable: false);
+    return result;
+  }
+}
+
+/// A partial implementation of [AutofillScope].
+///
+/// The mixin provides a default implementation for [AutofillScope.attach].
+mixin AutofillScopeMixin implements AutofillScope {
+  @override
+  TextInputConnection attach(TextInputClient trigger, TextInputConfiguration configuration) {
+    assert(trigger != null);
+    assert(
+      !autofillClients.any((AutofillClient client) => client.textInputConfiguration.autofillConfiguration == null),
+      'Every client in AutofillScope.autofillClients must enable autofill',
+    );
+
+    final TextInputConfiguration inputConfiguration = _AutofillScopeTextInputConfiguration(
+      allConfigurations: autofillClients.map((AutofillClient client) => client.textInputConfiguration),
+      currentClientConfiguration: configuration,
+    );
+    return TextInput.attach(trigger, inputConfiguration);
+  }
+}
diff --git a/lib/src/services/binary_messenger.dart b/lib/src/services/binary_messenger.dart
new file mode 100644
index 0000000..e3813f6
--- /dev/null
+++ b/lib/src/services/binary_messenger.dart
@@ -0,0 +1,112 @@
+// Copyright 2014 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.
+
+
+import 'dart:typed_data';
+import 'package:flute/ui.dart' as ui;
+
+import 'package:flute/foundation.dart';
+import 'binding.dart';
+
+/// A function which takes a platform message and asynchronously returns an encoded response.
+typedef MessageHandler = Future<ByteData?>? Function(ByteData? message);
+
+/// A messenger which sends binary data across the Flutter platform barrier.
+///
+/// This class also registers handlers for incoming messages.
+abstract class BinaryMessenger {
+  /// A const constructor to allow subclasses to be const.
+  const BinaryMessenger();
+
+  /// Calls the handler registered for the given channel.
+  ///
+  /// Typically called by [ServicesBinding] to handle platform messages received
+  /// from [dart:ui.PlatformDispatcher.onPlatformMessage].
+  ///
+  /// To register a handler for a given message channel, see [setMessageHandler].
+  Future<void> handlePlatformMessage(String channel, ByteData? data, ui.PlatformMessageResponseCallback? callback);
+
+  /// Send a binary message to the platform plugins on the given channel.
+  ///
+  /// Returns a [Future] which completes to the received response, undecoded,
+  /// in binary form.
+  Future<ByteData?>? send(String channel, ByteData? message);
+
+  /// Set a callback for receiving messages from the platform plugins on the
+  /// given channel, without decoding them.
+  ///
+  /// The given callback will replace the currently registered callback for that
+  /// channel, if any. To remove the handler, pass null as the [handler]
+  /// argument.
+  ///
+  /// The handler's return value, if non-null, is sent as a response, unencoded.
+  void setMessageHandler(String channel, MessageHandler? handler);
+
+  /// Returns true if the `handler` argument matches the `handler` previously
+  /// passed to [setMessageHandler].
+  ///
+  /// This method is useful for tests or test harnesses that want to assert the
+  /// handler for the specified channel has not been altered by a previous test.
+  ///
+  /// Passing null for the `handler` returns true if the handler for the
+  /// `channel` is not set.
+  bool checkMessageHandler(String channel, MessageHandler? handler);
+
+  /// Set a mock callback for intercepting messages from the [send] method on
+  /// this class, on the given channel, without decoding them.
+  ///
+  /// The given callback will replace the currently registered mock callback for
+  /// that channel, if any. To remove the mock handler, pass null as the
+  /// `handler` argument.
+  ///
+  /// The handler's return value, if non-null, is used as a response, unencoded.
+  ///
+  /// This is intended for testing. Messages intercepted in this manner are not
+  /// sent to platform plugins.
+  void setMockMessageHandler(String channel, MessageHandler? handler);
+
+  /// Returns true if the `handler` argument matches the `handler` previously
+  /// passed to [setMockMessageHandler].
+  ///
+  /// This method is useful for tests or test harnesses that want to assert the
+  /// mock handler for the specified channel has not been altered by a previous
+  /// test.
+  ///
+  /// Passing null for the `handler` returns true if the handler for the
+  /// `channel` is not set.
+  bool checkMockMessageHandler(String channel, MessageHandler? handler);
+}
+
+/// The default instance of [BinaryMessenger].
+///
+/// This API has been deprecated in favor of [ServicesBinding.defaultBinaryMessenger].
+/// Please use [ServicesBinding.defaultBinaryMessenger] as the default
+/// instance of [BinaryMessenger].
+///
+/// This is used to send messages from the application to the platform, and
+/// keeps track of which handlers have been registered on each channel so
+/// it may dispatch incoming messages to the registered handler.
+@Deprecated(
+  'Use ServicesBinding.instance.defaultBinaryMessenger instead. '
+  'This feature was deprecated after v1.6.5.'
+)
+BinaryMessenger get defaultBinaryMessenger {
+  assert(() {
+    if (ServicesBinding.instance == null) {
+      throw FlutterError(
+        'ServicesBinding.defaultBinaryMessenger was accessed before the '
+        'binding was initialized.\n'
+        "If you're running an application and need to access the binary "
+        'messenger before `runApp()` has been called (for example, during '
+        'plugin initialization), then you need to explicitly call the '
+        '`WidgetsFlutterBinding.ensureInitialized()` first.\n'
+        "If you're running a test, you can call the "
+        '`TestWidgetsFlutterBinding.ensureInitialized()` as the first line in '
+        "your test's `main()` method to initialize the binding."
+      );
+    }
+    return true;
+  }());
+  return ServicesBinding.instance!.defaultBinaryMessenger;
+}
diff --git a/lib/src/services/binding.dart b/lib/src/services/binding.dart
new file mode 100644
index 0000000..f1b53e7
--- /dev/null
+++ b/lib/src/services/binding.dart
@@ -0,0 +1,348 @@
+// Copyright 2014 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.
+
+
+import 'dart:async';
+import 'dart:typed_data';
+import 'package:flute/ui.dart' as ui;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+
+import 'asset_bundle.dart';
+import 'binary_messenger.dart';
+import 'restoration.dart';
+import 'system_channels.dart';
+
+/// Listens for platform messages and directs them to the [defaultBinaryMessenger].
+///
+/// The [ServicesBinding] also registers a [LicenseEntryCollector] that exposes
+/// the licenses found in the `LICENSE` file stored at the root of the asset
+/// bundle, and implements the `ext.flutter.evict` service extension (see
+/// [evict]).
+mixin ServicesBinding on BindingBase, SchedulerBinding {
+  @override
+  void initInstances() {
+    super.initInstances();
+    _instance = this;
+    _defaultBinaryMessenger = createBinaryMessenger();
+    _restorationManager = createRestorationManager();
+    window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
+    initLicenses();
+    SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object));
+    SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
+    readInitialLifecycleStateFromNativeWindow();
+  }
+
+  /// The current [ServicesBinding], if one has been created.
+  static ServicesBinding? get instance => _instance;
+  static ServicesBinding? _instance;
+
+  /// The default instance of [BinaryMessenger].
+  ///
+  /// This is used to send messages from the application to the platform, and
+  /// keeps track of which handlers have been registered on each channel so
+  /// it may dispatch incoming messages to the registered handler.
+  BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger;
+  late BinaryMessenger _defaultBinaryMessenger;
+
+  /// Creates a default [BinaryMessenger] instance that can be used for sending
+  /// platform messages.
+  @protected
+  BinaryMessenger createBinaryMessenger() {
+    return const _DefaultBinaryMessenger._();
+  }
+
+
+  /// Called when the operating system notifies the application of a memory
+  /// pressure situation.
+  ///
+  /// This method exposes the `memoryPressure` notification from
+  /// [SystemChannels.system].
+  @protected
+  @mustCallSuper
+  void handleMemoryPressure() { }
+
+  /// Handler called for messages received on the [SystemChannels.system]
+  /// message channel.
+  ///
+  /// Other bindings may override this to respond to incoming system messages.
+  @protected
+  @mustCallSuper
+  Future<void> handleSystemMessage(Object systemMessage) async {
+    final Map<String, dynamic> message = systemMessage as Map<String, dynamic>;
+    final String type = message['type'] as String;
+    switch (type) {
+      case 'memoryPressure':
+        handleMemoryPressure();
+        break;
+    }
+    return;
+  }
+
+  /// Adds relevant licenses to the [LicenseRegistry].
+  ///
+  /// By default, the [ServicesBinding]'s implementation of [initLicenses] adds
+  /// all the licenses collected by the `flutter` tool during compilation.
+  @protected
+  @mustCallSuper
+  void initLicenses() {
+    LicenseRegistry.addLicense(_addLicenses);
+  }
+
+  Stream<LicenseEntry> _addLicenses() async* {
+    // Using _something_ here to break
+    // this into two parts is important because isolates take a while to copy
+    // data at the moment, and if we receive the data in the same event loop
+    // iteration as we send the data to the next isolate, we are definitely
+    // going to miss frames. Another solution would be to have the work all
+    // happen in one isolate, and we may go there eventually, but first we are
+    // going to see if isolate communication can be made cheaper.
+    // See: https://github.com/dart-lang/sdk/issues/31959
+    //      https://github.com/dart-lang/sdk/issues/31960
+    // TODO(ianh): Remove this complexity once these bugs are fixed.
+    final Completer<String> rawLicenses = Completer<String>();
+    scheduleTask(() async {
+      rawLicenses.complete(
+        await rootBundle.loadString(
+          // NOTICES for web isn't compressed since we don't have access to
+          // dart:io on the client side and it's already compressed between
+          // the server and client.
+          //
+          // The compressed version doesn't have a more common .gz extension
+          // because gradle for Android non-transparently manipulates .gz files.
+          kIsWeb ? 'NOTICES' : 'NOTICES.Z',
+          cache: false,
+          unzip: !kIsWeb,
+        )
+      );
+    }, Priority.animation);
+    await rawLicenses.future;
+    final Completer<List<LicenseEntry>> parsedLicenses = Completer<List<LicenseEntry>>();
+    scheduleTask(() async {
+      parsedLicenses.complete(compute<String, List<LicenseEntry>>(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses'));
+    }, Priority.animation);
+    await parsedLicenses.future;
+    yield* Stream<LicenseEntry>.fromIterable(await parsedLicenses.future);
+  }
+
+  // This is run in another isolate created by _addLicenses above.
+  static List<LicenseEntry> _parseLicenses(String rawLicenses) {
+    final String _licenseSeparator = '\n' + ('-' * 80) + '\n';
+    final List<LicenseEntry> result = <LicenseEntry>[];
+    final List<String> licenses = rawLicenses.split(_licenseSeparator);
+    for (final String license in licenses) {
+      final int split = license.indexOf('\n\n');
+      if (split >= 0) {
+        result.add(LicenseEntryWithLineBreaks(
+          license.substring(0, split).split('\n'),
+          license.substring(split + 2),
+        ));
+      } else {
+        result.add(LicenseEntryWithLineBreaks(const <String>[], license));
+      }
+    }
+    return result;
+  }
+
+  @override
+  void initServiceExtensions() {
+    super.initServiceExtensions();
+
+    assert(() {
+      registerStringServiceExtension(
+        // ext.flutter.evict value=foo.png will cause foo.png to be evicted from
+        // the rootBundle cache and cause the entire image cache to be cleared.
+        // This is used by hot reload mode to clear out the cache of resources
+        // that have changed.
+        name: 'evict',
+        getter: () async => '',
+        setter: (String value) async {
+          evict(value);
+        },
+      );
+      return true;
+    }());
+  }
+
+  /// Called in response to the `ext.flutter.evict` service extension.
+  ///
+  /// This is used by the `flutter` tool during hot reload so that any images
+  /// that have changed on disk get cleared from caches.
+  @protected
+  @mustCallSuper
+  void evict(String asset) {
+    rootBundle.evict(asset);
+  }
+
+  // App life cycle
+
+  /// Initializes the [lifecycleState] with the
+  /// [dart:ui.SingletonFlutterWindow.initialLifecycleState].
+  ///
+  /// Once the [lifecycleState] is populated through any means (including this
+  /// method), this method will do nothing. This is because the
+  /// [dart:ui.SingletonFlutterWindow.initialLifecycleState] may already be
+  /// stale and it no longer makes sense to use the initial state at dart vm
+  /// startup as the current state anymore.
+  ///
+  /// The latest state should be obtained by subscribing to
+  /// [WidgetsBindingObserver.didChangeAppLifecycleState].
+  @protected
+  void readInitialLifecycleStateFromNativeWindow() {
+    if (lifecycleState != null) {
+      return;
+    }
+    final AppLifecycleState? state = _parseAppLifecycleMessage(window.initialLifecycleState);
+    if (state != null) {
+      handleAppLifecycleStateChanged(state);
+    }
+  }
+
+  Future<String?> _handleLifecycleMessage(String? message) async {
+    handleAppLifecycleStateChanged(_parseAppLifecycleMessage(message!)!);
+    return null;
+  }
+
+  static AppLifecycleState? _parseAppLifecycleMessage(String message) {
+    switch (message) {
+      case 'AppLifecycleState.paused':
+        return AppLifecycleState.paused;
+      case 'AppLifecycleState.resumed':
+        return AppLifecycleState.resumed;
+      case 'AppLifecycleState.inactive':
+        return AppLifecycleState.inactive;
+      case 'AppLifecycleState.detached':
+        return AppLifecycleState.detached;
+    }
+    return null;
+  }
+
+  /// The [RestorationManager] synchronizes the restoration data between
+  /// engine and framework.
+  ///
+  /// See the docs for [RestorationManager] for a discussion of restoration
+  /// state and how it is organized in Flutter.
+  ///
+  /// To use a different [RestorationManager] subclasses can override
+  /// [createRestorationManager], which is called to create the instance
+  /// returned by this getter.
+  RestorationManager get restorationManager => _restorationManager;
+  late RestorationManager _restorationManager;
+
+  /// Creates the [RestorationManager] instance available via
+  /// [restorationManager].
+  ///
+  /// Can be overridden in subclasses to create a different [RestorationManager].
+  @protected
+  RestorationManager createRestorationManager() {
+    return RestorationManager();
+  }
+}
+
+/// The default implementation of [BinaryMessenger].
+///
+/// This messenger sends messages from the app-side to the platform-side and
+/// dispatches incoming messages from the platform-side to the appropriate
+/// handler.
+class _DefaultBinaryMessenger extends BinaryMessenger {
+  const _DefaultBinaryMessenger._();
+
+  // Handlers for incoming messages from platform plugins.
+  // This is static so that this class can have a const constructor.
+  static final Map<String, MessageHandler> _handlers =
+      <String, MessageHandler>{};
+
+  // Mock handlers that intercept and respond to outgoing messages.
+  // This is static so that this class can have a const constructor.
+  static final Map<String, MessageHandler> _mockHandlers =
+      <String, MessageHandler>{};
+
+  Future<ByteData?> _sendPlatformMessage(String channel, ByteData? message) {
+    final Completer<ByteData?> completer = Completer<ByteData?>();
+    // ui.PlatformDispatcher.instance is accessed directly instead of using
+    // ServicesBinding.instance.platformDispatcher because this method might be
+    // invoked before any binding is initialized. This issue was reported in
+    // #27541. It is not ideal to statically access
+    // ui.PlatformDispatcher.instance because the PlatformDispatcher may be
+    // dependency injected elsewhere with a different instance. However, static
+    // access at this location seems to be the least bad option.
+    ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message, (ByteData? reply) {
+      try {
+        completer.complete(reply);
+      } catch (exception, stack) {
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          library: 'services library',
+          context: ErrorDescription('during a platform message response callback'),
+        ));
+      }
+    });
+    return completer.future;
+  }
+
+  @override
+  Future<void> handlePlatformMessage(
+    String channel,
+    ByteData? data,
+    ui.PlatformMessageResponseCallback? callback,
+  ) async {
+    ByteData? response;
+    try {
+      final MessageHandler? handler = _handlers[channel];
+      if (handler != null) {
+        response = await handler(data);
+      } else {
+        ui.channelBuffers.push(channel, data, callback!);
+        callback = null;
+      }
+    } catch (exception, stack) {
+      FlutterError.reportError(FlutterErrorDetails(
+        exception: exception,
+        stack: stack,
+        library: 'services library',
+        context: ErrorDescription('during a platform message callback'),
+      ));
+    } finally {
+      if (callback != null) {
+        callback(response);
+      }
+    }
+  }
+
+  @override
+  Future<ByteData?>? send(String channel, ByteData? message) {
+    final MessageHandler? handler = _mockHandlers[channel];
+    if (handler != null)
+      return handler(message);
+    return _sendPlatformMessage(channel, message);
+  }
+
+  @override
+  void setMessageHandler(String channel, MessageHandler? handler) {
+    if (handler == null) {
+      _handlers.remove(channel);
+    } else {
+      _handlers[channel] = handler;
+      ui.channelBuffers.drain(channel, (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
+        await handlePlatformMessage(channel, data, callback);
+      });
+    }
+  }
+
+  @override
+  bool checkMessageHandler(String channel, MessageHandler? handler) => _handlers[channel] == handler;
+
+  @override
+  void setMockMessageHandler(String channel, MessageHandler? handler) {
+    if (handler == null)
+      _mockHandlers.remove(channel);
+    else
+      _mockHandlers[channel] = handler;
+  }
+
+  @override
+  bool checkMockMessageHandler(String channel, MessageHandler? handler) => _mockHandlers[channel] == handler;
+}
diff --git a/lib/src/services/clipboard.dart b/lib/src/services/clipboard.dart
new file mode 100644
index 0000000..b43018b
--- /dev/null
+++ b/lib/src/services/clipboard.dart
@@ -0,0 +1,63 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+import 'system_channels.dart';
+
+/// Data stored on the system clipboard.
+///
+/// The system clipboard can contain data of various media types. This data
+/// structure currently supports only plain text data, in the [text] property.
+@immutable
+class ClipboardData {
+  /// Creates data for the system clipboard.
+  const ClipboardData({ this.text });
+
+  /// Plain text variant of this clipboard data.
+  final String? text;
+}
+
+/// Utility methods for interacting with the system's clipboard.
+class Clipboard {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  Clipboard._();
+
+  // Constants for common [getData] [format] types.
+
+  /// Plain text data format string.
+  ///
+  /// Used with [getData].
+  static const String kTextPlain = 'text/plain';
+
+  /// Stores the given clipboard data on the clipboard.
+  static Future<void> setData(ClipboardData data) async {
+    await SystemChannels.platform.invokeMethod<void>(
+      'Clipboard.setData',
+      <String, dynamic>{
+        'text': data.text,
+      },
+    );
+  }
+
+  /// Retrieves data from the clipboard that matches the given format.
+  ///
+  /// The `format` argument specifies the media type, such as `text/plain`, of
+  /// the data to obtain.
+  ///
+  /// Returns a future which completes to null if the data could not be
+  /// obtained, and to a [ClipboardData] object if it could.
+  static Future<ClipboardData?> getData(String format) async {
+    final Map<String, dynamic>? result = await SystemChannels.platform.invokeMethod(
+      'Clipboard.getData',
+      format,
+    );
+    if (result == null)
+      return null;
+    return ClipboardData(text: result['text'] as String?);
+  }
+}
diff --git a/lib/src/services/font_loader.dart b/lib/src/services/font_loader.dart
new file mode 100644
index 0000000..6e825ba
--- /dev/null
+++ b/lib/src/services/font_loader.dart
@@ -0,0 +1,80 @@
+// Copyright 2014 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.
+
+
+import 'dart:typed_data';
+import 'package:flute/ui.dart';
+
+import 'package:flute/foundation.dart';
+
+/// A class that enables the dynamic loading of fonts at runtime.
+///
+/// The [FontLoader] class provides a builder pattern, where the caller builds
+/// up the assets that make up a font family, then calls [load] to load the
+/// entire font family into a running Flutter application.
+class FontLoader {
+  /// Creates a new [FontLoader] that will load font assets for the specified
+  /// [family].
+  ///
+  /// The font family will not be available for use until [load] has been
+  /// called.
+  FontLoader(this.family)
+    : _loaded = false,
+      _fontFutures = <Future<Uint8List>>[];
+
+  /// The font family being loaded.
+  ///
+  /// The family groups a series of related font assets, each of which defines
+  /// how to render a specific [FontWeight] and [FontStyle] within the family.
+  final String family;
+
+  /// Registers a font asset to be loaded by this font loader.
+  ///
+  /// The [bytes] argument specifies the actual font asset bytes. Currently,
+  /// only OpenType (OTF) and TrueType (TTF) fonts are supported.
+  void addFont(Future<ByteData> bytes) {
+    if (_loaded)
+      throw StateError('FontLoader is already loaded');
+
+    _fontFutures.add(bytes.then(
+        (ByteData data) => Uint8List.view(data.buffer, data.offsetInBytes, data.lengthInBytes)
+    ));
+  }
+
+  /// Loads this font loader's font [family] and all of its associated assets
+  /// into the Flutter engine, making the font available to the current
+  /// application.
+  ///
+  /// This method should only be called once per font loader. Attempts to
+  /// load fonts from the same loader more than once will cause a [StateError]
+  /// to be thrown.
+  ///
+  /// The returned future will complete with an error if any of the font asset
+  /// futures yield an error.
+  Future<void> load() async {
+    if (_loaded)
+      throw StateError('FontLoader is already loaded');
+    _loaded = true;
+
+    final Iterable<Future<void>> loadFutures = _fontFutures.map(
+        (Future<Uint8List> f) => f.then<void>(
+            (Uint8List list) => loadFont(list, family)
+        )
+    );
+    await Future.wait(loadFutures.toList());
+  }
+
+  /// Hook called to load a font asset into the engine.
+  ///
+  /// Subclasses may override this to replace the default loading logic with
+  /// custom logic (for example, to mock the underlying engine API in tests).
+  @protected
+  @visibleForTesting
+  Future<void> loadFont(Uint8List list, String family) {
+    return loadFontFromList(list, fontFamily: family);
+  }
+
+  bool _loaded;
+  final List<Future<Uint8List>> _fontFutures;
+}
diff --git a/lib/src/services/haptic_feedback.dart b/lib/src/services/haptic_feedback.dart
new file mode 100644
index 0000000..523ea13
--- /dev/null
+++ b/lib/src/services/haptic_feedback.dart
@@ -0,0 +1,84 @@
+// Copyright 2014 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.
+
+
+import 'system_channels.dart';
+
+/// Allows access to the haptic feedback interface on the device.
+///
+/// This API is intentionally terse since it calls default platform behavior. It
+/// is not suitable for precise control of the system's haptic feedback module.
+class HapticFeedback {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  HapticFeedback._();
+
+  /// Provides vibration haptic feedback to the user for a short duration.
+  ///
+  /// On iOS devices that support haptic feedback, this uses the default system
+  /// vibration value (`kSystemSoundID_Vibrate`).
+  ///
+  /// On Android, this uses the platform haptic feedback API to simulate a
+  /// response to a long press (`HapticFeedbackConstants.LONG_PRESS`).
+  static Future<void> vibrate() async {
+    await SystemChannels.platform.invokeMethod<void>('HapticFeedback.vibrate');
+  }
+
+  /// Provides a haptic feedback corresponding a collision impact with a light mass.
+  ///
+  /// On iOS versions 10 and above, this uses a `UIImpactFeedbackGenerator` with
+  /// `UIImpactFeedbackStyleLight`. This call has no effects on iOS versions
+  /// below 10.
+  ///
+  /// On Android, this uses `HapticFeedbackConstants.VIRTUAL_KEY`.
+  static Future<void> lightImpact() async {
+    await SystemChannels.platform.invokeMethod<void>(
+      'HapticFeedback.vibrate',
+      'HapticFeedbackType.lightImpact',
+    );
+  }
+
+  /// Provides a haptic feedback corresponding a collision impact with a medium mass.
+  ///
+  /// On iOS versions 10 and above, this uses a `UIImpactFeedbackGenerator` with
+  /// `UIImpactFeedbackStyleMedium`. This call has no effects on iOS versions
+  /// below 10.
+  ///
+  /// On Android, this uses `HapticFeedbackConstants.KEYBOARD_TAP`.
+  static Future<void> mediumImpact() async {
+    await SystemChannels.platform.invokeMethod<void>(
+      'HapticFeedback.vibrate',
+      'HapticFeedbackType.mediumImpact',
+    );
+  }
+
+  /// Provides a haptic feedback corresponding a collision impact with a heavy mass.
+  ///
+  /// On iOS versions 10 and above, this uses a `UIImpactFeedbackGenerator` with
+  /// `UIImpactFeedbackStyleHeavy`. This call has no effects on iOS versions
+  /// below 10.
+  ///
+  /// On Android, this uses `HapticFeedbackConstants.CONTEXT_CLICK` on API levels
+  /// 23 and above. This call has no effects on Android API levels below 23.
+  static Future<void> heavyImpact() async {
+    await SystemChannels.platform.invokeMethod<void>(
+      'HapticFeedback.vibrate',
+      'HapticFeedbackType.heavyImpact',
+    );
+  }
+
+  /// Provides a haptic feedback indication selection changing through discrete values.
+  ///
+  /// On iOS versions 10 and above, this uses a `UISelectionFeedbackGenerator`.
+  /// This call has no effects on iOS versions below 10.
+  ///
+  /// On Android, this uses `HapticFeedbackConstants.CLOCK_TICK`.
+  static Future<void> selectionClick() async {
+    await SystemChannels.platform.invokeMethod<void>(
+      'HapticFeedback.vibrate',
+      'HapticFeedbackType.selectionClick',
+    );
+  }
+}
diff --git a/lib/src/services/keyboard_key.dart b/lib/src/services/keyboard_key.dart
new file mode 100644
index 0000000..24c1e1c
--- /dev/null
+++ b/lib/src/services/keyboard_key.dart
@@ -0,0 +1,3854 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+// DO NOT EDIT -- DO NOT EDIT -- DO NOT EDIT
+// This file is generated by dev/tools/gen_keycodes/bin/gen_keycodes.dart and
+// should not be edited directly.
+//
+// Edit the template dev/tools/gen_keycodes/data/keyboard_key.tmpl instead.
+// See dev/tools/gen_keycodes/README.md for more information.
+
+/// A base class for all keyboard key types.
+///
+/// See also:
+///
+///  * [PhysicalKeyboardKey], a class with static values that describe the keys
+///    that are returned from [RawKeyEvent.physicalKey].
+///  * [LogicalKeyboardKey], a class with static values that describe the keys
+///    that are returned from [RawKeyEvent.logicalKey].
+abstract class KeyboardKey with Diagnosticable {
+  /// A const constructor so that subclasses may be const.
+  const KeyboardKey();
+}
+
+/// A class with static values that describe the keys that are returned from
+/// [RawKeyEvent.logicalKey].
+///
+/// These represent *logical* keys, which are keys which are interpreted in the
+/// context of any modifiers, modes, or keyboard layouts which may be in effect.
+///
+/// This is contrast to [PhysicalKeyboardKey], which represents a physical key
+/// in a particular location on the keyboard, without regard for the modifier
+/// state, mode, or keyboard layout.
+///
+/// As an example, if you wanted to implement an app where the "Q" key "quit"
+/// something, you'd want to look at the logical key to detect this, since you
+/// would like to have it match the key with "Q" on it, instead of always
+/// looking for "the key next next to the TAB key", since on a French keyboard,
+/// the key next to the TAB key has an "A" on it.
+///
+/// Conversely, if you wanted a game where the key next to the CAPS LOCK (the
+/// "A" key on a QWERTY keyboard) moved the player to the left, you'd want to
+/// look at the physical key to make sure that regardless of the character the
+/// key produces, you got the key that is in that location on the keyboard.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+/// This example shows how to detect if the user has selected the logical "Q"
+/// key.
+///
+/// ```dart imports
+/// import 'package:flute/foundation.dart';
+/// import 'package:flute/services.dart';
+/// ```
+///
+/// ```dart
+/// // The node used to request the keyboard focus.
+/// final FocusNode _focusNode = FocusNode();
+/// // The message to display.
+/// String _message;
+///
+/// // Focus nodes need to be disposed.
+/// @override
+/// void dispose() {
+///   _focusNode.dispose();
+///   super.dispose();
+/// }
+///
+/// // Handles the key events from the RawKeyboardListener and update the
+/// // _message.
+/// void _handleKeyEvent(RawKeyEvent event) {
+///   setState(() {
+///     if (event.logicalKey == LogicalKeyboardKey.keyQ) {
+///       _message = 'Pressed the "Q" key!';
+///     } else {
+///       if (kReleaseMode) {
+///         _message = 'Not a Q: Key label is "${event.logicalKey.keyLabel ?? '<none>'}"';
+///       } else {
+///         // This will only print useful information in debug mode.
+///         _message = 'Not a Q: Pressed ${event.logicalKey.debugName}';
+///       }
+///     }
+///   });
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   final TextTheme textTheme = Theme.of(context).textTheme;
+///   return Container(
+///     color: Colors.white,
+///     alignment: Alignment.center,
+///     child: DefaultTextStyle(
+///       style: textTheme.headline4,
+///       child: RawKeyboardListener(
+///         focusNode: _focusNode,
+///         onKey: _handleKeyEvent,
+///         child: AnimatedBuilder(
+///           animation: _focusNode,
+///           builder: (BuildContext context, Widget child) {
+///             if (!_focusNode.hasFocus) {
+///               return GestureDetector(
+///                 onTap: () {
+///                   FocusScope.of(context).requestFocus(_focusNode);
+///                 },
+///                 child: const Text('Tap to focus'),
+///               );
+///             }
+///             return Text(_message ?? 'Press a key');
+///           },
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+/// See also:
+///
+///  * [RawKeyEvent], the keyboard event object received by widgets that listen
+///    to keyboard events.
+///  * [RawKeyboardListener], a widget used to listen to and supply handlers for
+///    keyboard events.
+@immutable
+class LogicalKeyboardKey extends KeyboardKey {
+  /// Creates a LogicalKeyboardKey object with an optional key label and debug
+  /// name.
+  ///
+  /// [keyId] must not be null.
+  ///
+  /// {@tool snippet}
+  /// To save executable size, it is recommended that the [debugName] be null in
+  /// release mode. You can do this by using the [kReleaseMode] constant.
+  ///
+  /// ```dart
+  /// const LogicalKeyboardKey(0x0010000000a, debugName: kReleaseMode ? null : 'Special Key')
+  /// ```
+  /// {@end-tool}
+  const LogicalKeyboardKey(this.keyId, {this.debugName, this.keyLabel = ''})
+      : assert(keyId != null);
+
+  /// A unique code representing this key.
+  ///
+  /// This is an opaque code. It should not be unpacked to derive information
+  /// from it, as the representation of the code could change at any time.
+  final int keyId;
+
+  /// The debug string to print for this keyboard key, which will be null in
+  /// release mode.
+  final String? debugName;
+
+  /// The Unicode string representing the character produced by a [RawKeyEvent].
+  ///
+  /// This value is useful for describing or matching mnemonic keyboard
+  /// shortcuts.
+  ///
+  /// This value is an empty string if there's no key label data for a key.
+  ///
+  /// On most platforms this is a single code point, but it could contain any
+  /// Unicode string. The `keyLabel` differs from [RawKeyEvent.character]
+  /// because `keyLabel` only takes into account the key being pressed, not any
+  /// combining keys pressed before it, so, for example, an “o” that follows a
+  /// combining dieresis (“¨”, COMBINING DIAERESIS (U+0308)) would just return
+  /// “o” for [keyLabel], but would return “ö” for [RawKeyEvent.character].
+  ///
+  /// {@macro flutter.services.RawKeyEventData.keyLabel}
+  final String keyLabel;
+
+  @override
+  int get hashCode => keyId.hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is LogicalKeyboardKey
+        && other.keyId == keyId;
+  }
+
+  /// Returns the [LogicalKeyboardKey] constant that matches the given ID, or
+  /// null, if not found.
+  static LogicalKeyboardKey? findKeyByKeyId(int keyId) => _knownLogicalKeys[keyId];
+
+  /// Returns true if the given label represents a Unicode control character.
+  ///
+  /// Examples of control characters are characters like "U+000A LINE FEED (LF)"
+  /// or "U+001B ESCAPE (ESC)".
+  ///
+  /// See <https://en.wikipedia.org/wiki/Unicode_control_characters> for more
+  /// information.
+  ///
+  /// Used by [RawKeyEvent] subclasses to help construct IDs.
+  static bool isControlCharacter(String label) {
+    if (label.length != 1) {
+      return false;
+    }
+    final int codeUnit = label.codeUnitAt(0);
+    return (codeUnit <= 0x1f && codeUnit >= 0x00) || (codeUnit >= 0x7f && codeUnit <= 0x9f);
+  }
+
+  /// Returns true if the [keyId] of this object is one that is auto-generated by
+  /// Flutter.
+  ///
+  /// Auto-generated key IDs are generated in response to platform key codes
+  /// which Flutter doesn't recognize, and their IDs shouldn't be used in a
+  /// persistent way.
+  ///
+  /// Auto-generated IDs should be a rare occurrence: Flutter supports most keys.
+  ///
+  /// Keys that generate Unicode characters (even if unknown to Flutter) will
+  /// not return true for `isAutogenerated`, since they will be assigned a
+  /// Unicode-based code that will remain stable.
+  ///
+  /// If Flutter adds support for a previously unsupported key code, the ID it
+  /// reports will change, but the ID will remain stable on the platform it is
+  /// produced on until Flutter adds support for recognizing it.
+  ///
+  /// So, hypothetically, if Android added a new key code of 0xffff,
+  /// representing a new "do what I mean" key, then the auto-generated code
+  /// would be 0x1020000ffff, but once Flutter added the "doWhatIMean" key to
+  /// the definitions below, the new code would be 0x0020000ffff for all
+  /// platforms that had a "do what I mean" key from then on.
+  bool get isAutogenerated => (keyId & autogeneratedMask) != 0;
+
+  /// Returns a set of pseudo-key synonyms for the given `key`.
+  ///
+  /// This allows finding the pseudo-keys that also represents a concrete
+  /// `key` so that a class with a key map can match pseudo-keys as well as the
+  /// actual generated keys.
+  ///
+  /// The pseudo-keys returned in the set are typically used to represent keys
+  /// which appear in multiple places on the keyboard, such as the [shift],
+  /// [alt], [control], and [meta] keys. The keys in the returned set won't ever
+  /// be generated directly, but if a more specific key event is received, then
+  /// this set can be used to find the more general pseudo-key. For example, if
+  /// this is a [shiftLeft] key, this accessor will return the set
+  /// `<LogicalKeyboardKey>{ shift }`.
+  Set<LogicalKeyboardKey> get synonyms {
+    final LogicalKeyboardKey? result = _synonyms[this];
+    return result == null ? <LogicalKeyboardKey>{} : <LogicalKeyboardKey>{result};
+  }
+
+  /// Takes a set of keys, and returns the same set, but with any keys that have
+  /// synonyms replaced.
+  ///
+  /// It is used, for example, to make sets of keys with members like
+  /// [controlRight] and [controlLeft] and convert that set to contain just
+  /// [control], so that the question "is any control key down?" can be asked.
+  static Set<LogicalKeyboardKey> collapseSynonyms(Set<LogicalKeyboardKey> input) {
+    final Set<LogicalKeyboardKey> result = <LogicalKeyboardKey>{};
+    for (final LogicalKeyboardKey key in input) {
+      final LogicalKeyboardKey? synonym = _synonyms[key];
+      result.add(synonym ?? key);
+    }
+    return result;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(StringProperty('keyId', '0x${keyId.toRadixString(16).padLeft(8, '0')}', showName: true));
+    properties.add(StringProperty('keyLabel', keyLabel, showName: true));
+    properties.add(StringProperty('debugName', debugName, showName: true, defaultValue: null));
+  }
+
+  /// Mask for the 32-bit value portion of the key code.
+  ///
+  /// This is used by platform-specific code to generate Flutter key codes.
+  static const int valueMask = 0x000FFFFFFFF;
+
+  /// Mask for the platform prefix portion of the key code.
+  ///
+  /// This is used by platform-specific code to generate Flutter key codes.
+  static const int platformMask = 0x0FF00000000;
+
+  /// Mask for the auto-generated bit portion of the key code.
+  ///
+  /// This is used by platform-specific code to generate new Flutter key codes
+  /// for keys which are not recognized.
+  static const int autogeneratedMask = 0x10000000000;
+
+  /// Mask for the synonym pseudo-keys generated for keys which appear in more
+  /// than one place on the keyboard.
+  ///
+  /// IDs in this range are used to represent keys which appear in multiple
+  /// places on the keyboard, such as the SHIFT, ALT, CTRL, and numeric keypad
+  /// keys. These key codes will never be generated by the key event system, but
+  /// may be used in key maps to represent the union of all the keys of each
+  /// type in order to match them.
+  ///
+  /// To look up the synonyms that are defined, look in the [synonyms] map.
+  static const int synonymMask = 0x20000000000;
+
+  /// The code prefix for keys which have a Unicode representation.
+  ///
+  /// This is used by platform-specific code to generate Flutter key codes.
+  static const int unicodePlane = 0x00000000000;
+
+  /// The code prefix for keys which do not have a Unicode representation.
+  ///
+  /// This is used by platform-specific code to generate Flutter key codes using
+  /// HID Usage codes.
+  static const int hidPlane = 0x00100000000;
+
+  /// Represents the logical "None" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey none = LogicalKeyboardKey(0x00100000000, debugName: kReleaseMode ? null : 'None');
+
+  /// Represents the logical "Hyper" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey hyper = LogicalKeyboardKey(0x00100000010, debugName: kReleaseMode ? null : 'Hyper');
+
+  /// Represents the logical "Super Key" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey superKey = LogicalKeyboardKey(0x00100000011, debugName: kReleaseMode ? null : 'Super Key');
+
+  /// Represents the logical "Fn Lock" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey fnLock = LogicalKeyboardKey(0x00100000013, debugName: kReleaseMode ? null : 'Fn Lock');
+
+  /// Represents the logical "Suspend" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey suspend = LogicalKeyboardKey(0x00100000014, debugName: kReleaseMode ? null : 'Suspend');
+
+  /// Represents the logical "Resume" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey resume = LogicalKeyboardKey(0x00100000015, debugName: kReleaseMode ? null : 'Resume');
+
+  /// Represents the logical "Turbo" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey turbo = LogicalKeyboardKey(0x00100000016, debugName: kReleaseMode ? null : 'Turbo');
+
+  /// Represents the logical "Privacy Screen Toggle" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey privacyScreenToggle = LogicalKeyboardKey(0x00100000017, debugName: kReleaseMode ? null : 'Privacy Screen Toggle');
+
+  /// Represents the logical "Sleep" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey sleep = LogicalKeyboardKey(0x00100010082, debugName: kReleaseMode ? null : 'Sleep');
+
+  /// Represents the logical "Wake Up" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey wakeUp = LogicalKeyboardKey(0x00100010083, debugName: kReleaseMode ? null : 'Wake Up');
+
+  /// Represents the logical "Display Toggle Int Ext" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey displayToggleIntExt = LogicalKeyboardKey(0x001000100b5, debugName: kReleaseMode ? null : 'Display Toggle Int Ext');
+
+  /// Represents the logical "Usb Reserved" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey usbReserved = LogicalKeyboardKey(0x00100070000, debugName: kReleaseMode ? null : 'Usb Reserved');
+
+  /// Represents the logical "Usb Error Roll Over" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey usbErrorRollOver = LogicalKeyboardKey(0x00100070001, debugName: kReleaseMode ? null : 'Usb Error Roll Over');
+
+  /// Represents the logical "Usb Post Fail" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey usbPostFail = LogicalKeyboardKey(0x00100070002, debugName: kReleaseMode ? null : 'Usb Post Fail');
+
+  /// Represents the logical "Usb Error Undefined" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey usbErrorUndefined = LogicalKeyboardKey(0x00100070003, debugName: kReleaseMode ? null : 'Usb Error Undefined');
+
+  /// Represents the logical "Key A" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyA = LogicalKeyboardKey(0x00000000061, keyLabel: r'a', debugName: kReleaseMode ? null : 'Key A');
+
+  /// Represents the logical "Key B" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyB = LogicalKeyboardKey(0x00000000062, keyLabel: r'b', debugName: kReleaseMode ? null : 'Key B');
+
+  /// Represents the logical "Key C" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyC = LogicalKeyboardKey(0x00000000063, keyLabel: r'c', debugName: kReleaseMode ? null : 'Key C');
+
+  /// Represents the logical "Key D" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyD = LogicalKeyboardKey(0x00000000064, keyLabel: r'd', debugName: kReleaseMode ? null : 'Key D');
+
+  /// Represents the logical "Key E" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyE = LogicalKeyboardKey(0x00000000065, keyLabel: r'e', debugName: kReleaseMode ? null : 'Key E');
+
+  /// Represents the logical "Key F" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyF = LogicalKeyboardKey(0x00000000066, keyLabel: r'f', debugName: kReleaseMode ? null : 'Key F');
+
+  /// Represents the logical "Key G" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyG = LogicalKeyboardKey(0x00000000067, keyLabel: r'g', debugName: kReleaseMode ? null : 'Key G');
+
+  /// Represents the logical "Key H" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyH = LogicalKeyboardKey(0x00000000068, keyLabel: r'h', debugName: kReleaseMode ? null : 'Key H');
+
+  /// Represents the logical "Key I" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyI = LogicalKeyboardKey(0x00000000069, keyLabel: r'i', debugName: kReleaseMode ? null : 'Key I');
+
+  /// Represents the logical "Key J" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyJ = LogicalKeyboardKey(0x0000000006a, keyLabel: r'j', debugName: kReleaseMode ? null : 'Key J');
+
+  /// Represents the logical "Key K" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyK = LogicalKeyboardKey(0x0000000006b, keyLabel: r'k', debugName: kReleaseMode ? null : 'Key K');
+
+  /// Represents the logical "Key L" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyL = LogicalKeyboardKey(0x0000000006c, keyLabel: r'l', debugName: kReleaseMode ? null : 'Key L');
+
+  /// Represents the logical "Key M" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyM = LogicalKeyboardKey(0x0000000006d, keyLabel: r'm', debugName: kReleaseMode ? null : 'Key M');
+
+  /// Represents the logical "Key N" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyN = LogicalKeyboardKey(0x0000000006e, keyLabel: r'n', debugName: kReleaseMode ? null : 'Key N');
+
+  /// Represents the logical "Key O" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyO = LogicalKeyboardKey(0x0000000006f, keyLabel: r'o', debugName: kReleaseMode ? null : 'Key O');
+
+  /// Represents the logical "Key P" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyP = LogicalKeyboardKey(0x00000000070, keyLabel: r'p', debugName: kReleaseMode ? null : 'Key P');
+
+  /// Represents the logical "Key Q" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyQ = LogicalKeyboardKey(0x00000000071, keyLabel: r'q', debugName: kReleaseMode ? null : 'Key Q');
+
+  /// Represents the logical "Key R" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyR = LogicalKeyboardKey(0x00000000072, keyLabel: r'r', debugName: kReleaseMode ? null : 'Key R');
+
+  /// Represents the logical "Key S" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyS = LogicalKeyboardKey(0x00000000073, keyLabel: r's', debugName: kReleaseMode ? null : 'Key S');
+
+  /// Represents the logical "Key T" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyT = LogicalKeyboardKey(0x00000000074, keyLabel: r't', debugName: kReleaseMode ? null : 'Key T');
+
+  /// Represents the logical "Key U" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyU = LogicalKeyboardKey(0x00000000075, keyLabel: r'u', debugName: kReleaseMode ? null : 'Key U');
+
+  /// Represents the logical "Key V" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyV = LogicalKeyboardKey(0x00000000076, keyLabel: r'v', debugName: kReleaseMode ? null : 'Key V');
+
+  /// Represents the logical "Key W" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyW = LogicalKeyboardKey(0x00000000077, keyLabel: r'w', debugName: kReleaseMode ? null : 'Key W');
+
+  /// Represents the logical "Key X" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyX = LogicalKeyboardKey(0x00000000078, keyLabel: r'x', debugName: kReleaseMode ? null : 'Key X');
+
+  /// Represents the logical "Key Y" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyY = LogicalKeyboardKey(0x00000000079, keyLabel: r'y', debugName: kReleaseMode ? null : 'Key Y');
+
+  /// Represents the logical "Key Z" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyZ = LogicalKeyboardKey(0x0000000007a, keyLabel: r'z', debugName: kReleaseMode ? null : 'Key Z');
+
+  /// Represents the logical "Digit 1" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey digit1 = LogicalKeyboardKey(0x00000000031, keyLabel: r'1', debugName: kReleaseMode ? null : 'Digit 1');
+
+  /// Represents the logical "Digit 2" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey digit2 = LogicalKeyboardKey(0x00000000032, keyLabel: r'2', debugName: kReleaseMode ? null : 'Digit 2');
+
+  /// Represents the logical "Digit 3" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey digit3 = LogicalKeyboardKey(0x00000000033, keyLabel: r'3', debugName: kReleaseMode ? null : 'Digit 3');
+
+  /// Represents the logical "Digit 4" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey digit4 = LogicalKeyboardKey(0x00000000034, keyLabel: r'4', debugName: kReleaseMode ? null : 'Digit 4');
+
+  /// Represents the logical "Digit 5" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey digit5 = LogicalKeyboardKey(0x00000000035, keyLabel: r'5', debugName: kReleaseMode ? null : 'Digit 5');
+
+  /// Represents the logical "Digit 6" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey digit6 = LogicalKeyboardKey(0x00000000036, keyLabel: r'6', debugName: kReleaseMode ? null : 'Digit 6');
+
+  /// Represents the logical "Digit 7" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey digit7 = LogicalKeyboardKey(0x00000000037, keyLabel: r'7', debugName: kReleaseMode ? null : 'Digit 7');
+
+  /// Represents the logical "Digit 8" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey digit8 = LogicalKeyboardKey(0x00000000038, keyLabel: r'8', debugName: kReleaseMode ? null : 'Digit 8');
+
+  /// Represents the logical "Digit 9" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey digit9 = LogicalKeyboardKey(0x00000000039, keyLabel: r'9', debugName: kReleaseMode ? null : 'Digit 9');
+
+  /// Represents the logical "Digit 0" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey digit0 = LogicalKeyboardKey(0x00000000030, keyLabel: r'0', debugName: kReleaseMode ? null : 'Digit 0');
+
+  /// Represents the logical "Enter" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey enter = LogicalKeyboardKey(0x00100070028, debugName: kReleaseMode ? null : 'Enter');
+
+  /// Represents the logical "Escape" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey escape = LogicalKeyboardKey(0x00100070029, debugName: kReleaseMode ? null : 'Escape');
+
+  /// Represents the logical "Backspace" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey backspace = LogicalKeyboardKey(0x0010007002a, debugName: kReleaseMode ? null : 'Backspace');
+
+  /// Represents the logical "Tab" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey tab = LogicalKeyboardKey(0x0010007002b, debugName: kReleaseMode ? null : 'Tab');
+
+  /// Represents the logical "Space" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey space = LogicalKeyboardKey(0x00000000020, keyLabel: r' ', debugName: kReleaseMode ? null : 'Space');
+
+  /// Represents the logical "Minus" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey minus = LogicalKeyboardKey(0x0000000002d, keyLabel: r'-', debugName: kReleaseMode ? null : 'Minus');
+
+  /// Represents the logical "Equal" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey equal = LogicalKeyboardKey(0x0000000003d, keyLabel: r'=', debugName: kReleaseMode ? null : 'Equal');
+
+  /// Represents the logical "Bracket Left" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey bracketLeft = LogicalKeyboardKey(0x0000000005b, keyLabel: r'[', debugName: kReleaseMode ? null : 'Bracket Left');
+
+  /// Represents the logical "Bracket Right" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey bracketRight = LogicalKeyboardKey(0x0000000005d, keyLabel: r']', debugName: kReleaseMode ? null : 'Bracket Right');
+
+  /// Represents the logical "Backslash" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey backslash = LogicalKeyboardKey(0x0000000005c, keyLabel: r'\', debugName: kReleaseMode ? null : 'Backslash');
+
+  /// Represents the logical "Semicolon" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey semicolon = LogicalKeyboardKey(0x0000000003b, keyLabel: r';', debugName: kReleaseMode ? null : 'Semicolon');
+
+  /// Represents the logical "Quote" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey quote = LogicalKeyboardKey(0x00000000027, keyLabel: r"'", debugName: kReleaseMode ? null : 'Quote');
+
+  /// Represents the logical "Backquote" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey backquote = LogicalKeyboardKey(0x00000000060, keyLabel: r'`', debugName: kReleaseMode ? null : 'Backquote');
+
+  /// Represents the logical "Comma" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey comma = LogicalKeyboardKey(0x0000000002c, keyLabel: r',', debugName: kReleaseMode ? null : 'Comma');
+
+  /// Represents the logical "Period" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey period = LogicalKeyboardKey(0x0000000002e, keyLabel: r'.', debugName: kReleaseMode ? null : 'Period');
+
+  /// Represents the logical "Slash" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey slash = LogicalKeyboardKey(0x0000000002f, keyLabel: r'/', debugName: kReleaseMode ? null : 'Slash');
+
+  /// Represents the logical "Caps Lock" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey capsLock = LogicalKeyboardKey(0x00100070039, debugName: kReleaseMode ? null : 'Caps Lock');
+
+  /// Represents the logical "F1" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f1 = LogicalKeyboardKey(0x0010007003a, debugName: kReleaseMode ? null : 'F1');
+
+  /// Represents the logical "F2" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f2 = LogicalKeyboardKey(0x0010007003b, debugName: kReleaseMode ? null : 'F2');
+
+  /// Represents the logical "F3" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f3 = LogicalKeyboardKey(0x0010007003c, debugName: kReleaseMode ? null : 'F3');
+
+  /// Represents the logical "F4" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f4 = LogicalKeyboardKey(0x0010007003d, debugName: kReleaseMode ? null : 'F4');
+
+  /// Represents the logical "F5" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f5 = LogicalKeyboardKey(0x0010007003e, debugName: kReleaseMode ? null : 'F5');
+
+  /// Represents the logical "F6" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f6 = LogicalKeyboardKey(0x0010007003f, debugName: kReleaseMode ? null : 'F6');
+
+  /// Represents the logical "F7" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f7 = LogicalKeyboardKey(0x00100070040, debugName: kReleaseMode ? null : 'F7');
+
+  /// Represents the logical "F8" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f8 = LogicalKeyboardKey(0x00100070041, debugName: kReleaseMode ? null : 'F8');
+
+  /// Represents the logical "F9" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f9 = LogicalKeyboardKey(0x00100070042, debugName: kReleaseMode ? null : 'F9');
+
+  /// Represents the logical "F10" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f10 = LogicalKeyboardKey(0x00100070043, debugName: kReleaseMode ? null : 'F10');
+
+  /// Represents the logical "F11" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f11 = LogicalKeyboardKey(0x00100070044, debugName: kReleaseMode ? null : 'F11');
+
+  /// Represents the logical "F12" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f12 = LogicalKeyboardKey(0x00100070045, debugName: kReleaseMode ? null : 'F12');
+
+  /// Represents the logical "Print Screen" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey printScreen = LogicalKeyboardKey(0x00100070046, debugName: kReleaseMode ? null : 'Print Screen');
+
+  /// Represents the logical "Scroll Lock" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey scrollLock = LogicalKeyboardKey(0x00100070047, debugName: kReleaseMode ? null : 'Scroll Lock');
+
+  /// Represents the logical "Pause" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey pause = LogicalKeyboardKey(0x00100070048, debugName: kReleaseMode ? null : 'Pause');
+
+  /// Represents the logical "Insert" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey insert = LogicalKeyboardKey(0x00100070049, debugName: kReleaseMode ? null : 'Insert');
+
+  /// Represents the logical "Home" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey home = LogicalKeyboardKey(0x0010007004a, debugName: kReleaseMode ? null : 'Home');
+
+  /// Represents the logical "Page Up" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey pageUp = LogicalKeyboardKey(0x0010007004b, debugName: kReleaseMode ? null : 'Page Up');
+
+  /// Represents the logical "Delete" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey delete = LogicalKeyboardKey(0x0010007004c, debugName: kReleaseMode ? null : 'Delete');
+
+  /// Represents the logical "End" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey end = LogicalKeyboardKey(0x0010007004d, debugName: kReleaseMode ? null : 'End');
+
+  /// Represents the logical "Page Down" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey pageDown = LogicalKeyboardKey(0x0010007004e, debugName: kReleaseMode ? null : 'Page Down');
+
+  /// Represents the logical "Arrow Right" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey arrowRight = LogicalKeyboardKey(0x0010007004f, debugName: kReleaseMode ? null : 'Arrow Right');
+
+  /// Represents the logical "Arrow Left" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey arrowLeft = LogicalKeyboardKey(0x00100070050, debugName: kReleaseMode ? null : 'Arrow Left');
+
+  /// Represents the logical "Arrow Down" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey arrowDown = LogicalKeyboardKey(0x00100070051, debugName: kReleaseMode ? null : 'Arrow Down');
+
+  /// Represents the logical "Arrow Up" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey arrowUp = LogicalKeyboardKey(0x00100070052, debugName: kReleaseMode ? null : 'Arrow Up');
+
+  /// Represents the logical "Num Lock" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numLock = LogicalKeyboardKey(0x00100070053, debugName: kReleaseMode ? null : 'Num Lock');
+
+  /// Represents the logical "Numpad Divide" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadDivide = LogicalKeyboardKey(0x00100070054, keyLabel: r'/', debugName: kReleaseMode ? null : 'Numpad Divide');
+
+  /// Represents the logical "Numpad Multiply" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadMultiply = LogicalKeyboardKey(0x00100070055, keyLabel: r'*', debugName: kReleaseMode ? null : 'Numpad Multiply');
+
+  /// Represents the logical "Numpad Subtract" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadSubtract = LogicalKeyboardKey(0x00100070056, keyLabel: r'-', debugName: kReleaseMode ? null : 'Numpad Subtract');
+
+  /// Represents the logical "Numpad Add" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadAdd = LogicalKeyboardKey(0x00100070057, keyLabel: r'+', debugName: kReleaseMode ? null : 'Numpad Add');
+
+  /// Represents the logical "Numpad Enter" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadEnter = LogicalKeyboardKey(0x00100070058, debugName: kReleaseMode ? null : 'Numpad Enter');
+
+  /// Represents the logical "Numpad 1" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpad1 = LogicalKeyboardKey(0x00100070059, keyLabel: r'1', debugName: kReleaseMode ? null : 'Numpad 1');
+
+  /// Represents the logical "Numpad 2" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpad2 = LogicalKeyboardKey(0x0010007005a, keyLabel: r'2', debugName: kReleaseMode ? null : 'Numpad 2');
+
+  /// Represents the logical "Numpad 3" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpad3 = LogicalKeyboardKey(0x0010007005b, keyLabel: r'3', debugName: kReleaseMode ? null : 'Numpad 3');
+
+  /// Represents the logical "Numpad 4" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpad4 = LogicalKeyboardKey(0x0010007005c, keyLabel: r'4', debugName: kReleaseMode ? null : 'Numpad 4');
+
+  /// Represents the logical "Numpad 5" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpad5 = LogicalKeyboardKey(0x0010007005d, keyLabel: r'5', debugName: kReleaseMode ? null : 'Numpad 5');
+
+  /// Represents the logical "Numpad 6" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpad6 = LogicalKeyboardKey(0x0010007005e, keyLabel: r'6', debugName: kReleaseMode ? null : 'Numpad 6');
+
+  /// Represents the logical "Numpad 7" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpad7 = LogicalKeyboardKey(0x0010007005f, keyLabel: r'7', debugName: kReleaseMode ? null : 'Numpad 7');
+
+  /// Represents the logical "Numpad 8" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpad8 = LogicalKeyboardKey(0x00100070060, keyLabel: r'8', debugName: kReleaseMode ? null : 'Numpad 8');
+
+  /// Represents the logical "Numpad 9" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpad9 = LogicalKeyboardKey(0x00100070061, keyLabel: r'9', debugName: kReleaseMode ? null : 'Numpad 9');
+
+  /// Represents the logical "Numpad 0" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpad0 = LogicalKeyboardKey(0x00100070062, keyLabel: r'0', debugName: kReleaseMode ? null : 'Numpad 0');
+
+  /// Represents the logical "Numpad Decimal" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadDecimal = LogicalKeyboardKey(0x00100070063, keyLabel: r'.', debugName: kReleaseMode ? null : 'Numpad Decimal');
+
+  /// Represents the logical "Intl Backslash" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey intlBackslash = LogicalKeyboardKey(0x00100070064, debugName: kReleaseMode ? null : 'Intl Backslash');
+
+  /// Represents the logical "Context Menu" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey contextMenu = LogicalKeyboardKey(0x00100070065, debugName: kReleaseMode ? null : 'Context Menu');
+
+  /// Represents the logical "Power" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey power = LogicalKeyboardKey(0x00100070066, debugName: kReleaseMode ? null : 'Power');
+
+  /// Represents the logical "Numpad Equal" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadEqual = LogicalKeyboardKey(0x00100070067, keyLabel: r'=', debugName: kReleaseMode ? null : 'Numpad Equal');
+
+  /// Represents the logical "F13" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f13 = LogicalKeyboardKey(0x00100070068, debugName: kReleaseMode ? null : 'F13');
+
+  /// Represents the logical "F14" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f14 = LogicalKeyboardKey(0x00100070069, debugName: kReleaseMode ? null : 'F14');
+
+  /// Represents the logical "F15" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f15 = LogicalKeyboardKey(0x0010007006a, debugName: kReleaseMode ? null : 'F15');
+
+  /// Represents the logical "F16" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f16 = LogicalKeyboardKey(0x0010007006b, debugName: kReleaseMode ? null : 'F16');
+
+  /// Represents the logical "F17" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f17 = LogicalKeyboardKey(0x0010007006c, debugName: kReleaseMode ? null : 'F17');
+
+  /// Represents the logical "F18" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f18 = LogicalKeyboardKey(0x0010007006d, debugName: kReleaseMode ? null : 'F18');
+
+  /// Represents the logical "F19" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f19 = LogicalKeyboardKey(0x0010007006e, debugName: kReleaseMode ? null : 'F19');
+
+  /// Represents the logical "F20" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f20 = LogicalKeyboardKey(0x0010007006f, debugName: kReleaseMode ? null : 'F20');
+
+  /// Represents the logical "F21" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f21 = LogicalKeyboardKey(0x00100070070, debugName: kReleaseMode ? null : 'F21');
+
+  /// Represents the logical "F22" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f22 = LogicalKeyboardKey(0x00100070071, debugName: kReleaseMode ? null : 'F22');
+
+  /// Represents the logical "F23" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f23 = LogicalKeyboardKey(0x00100070072, debugName: kReleaseMode ? null : 'F23');
+
+  /// Represents the logical "F24" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey f24 = LogicalKeyboardKey(0x00100070073, debugName: kReleaseMode ? null : 'F24');
+
+  /// Represents the logical "Open" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey open = LogicalKeyboardKey(0x00100070074, debugName: kReleaseMode ? null : 'Open');
+
+  /// Represents the logical "Help" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey help = LogicalKeyboardKey(0x00100070075, debugName: kReleaseMode ? null : 'Help');
+
+  /// Represents the logical "Select" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey select = LogicalKeyboardKey(0x00100070077, debugName: kReleaseMode ? null : 'Select');
+
+  /// Represents the logical "Again" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey again = LogicalKeyboardKey(0x00100070079, debugName: kReleaseMode ? null : 'Again');
+
+  /// Represents the logical "Undo" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey undo = LogicalKeyboardKey(0x0010007007a, debugName: kReleaseMode ? null : 'Undo');
+
+  /// Represents the logical "Cut" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey cut = LogicalKeyboardKey(0x0010007007b, debugName: kReleaseMode ? null : 'Cut');
+
+  /// Represents the logical "Copy" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey copy = LogicalKeyboardKey(0x0010007007c, debugName: kReleaseMode ? null : 'Copy');
+
+  /// Represents the logical "Paste" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey paste = LogicalKeyboardKey(0x0010007007d, debugName: kReleaseMode ? null : 'Paste');
+
+  /// Represents the logical "Find" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey find = LogicalKeyboardKey(0x0010007007e, debugName: kReleaseMode ? null : 'Find');
+
+  /// Represents the logical "Audio Volume Mute" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey audioVolumeMute = LogicalKeyboardKey(0x0010007007f, debugName: kReleaseMode ? null : 'Audio Volume Mute');
+
+  /// Represents the logical "Audio Volume Up" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey audioVolumeUp = LogicalKeyboardKey(0x00100070080, debugName: kReleaseMode ? null : 'Audio Volume Up');
+
+  /// Represents the logical "Audio Volume Down" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey audioVolumeDown = LogicalKeyboardKey(0x00100070081, debugName: kReleaseMode ? null : 'Audio Volume Down');
+
+  /// Represents the logical "Numpad Comma" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadComma = LogicalKeyboardKey(0x00100070085, keyLabel: r',', debugName: kReleaseMode ? null : 'Numpad Comma');
+
+  /// Represents the logical "Intl Ro" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey intlRo = LogicalKeyboardKey(0x00100070087, debugName: kReleaseMode ? null : 'Intl Ro');
+
+  /// Represents the logical "Kana Mode" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey kanaMode = LogicalKeyboardKey(0x00100070088, debugName: kReleaseMode ? null : 'Kana Mode');
+
+  /// Represents the logical "Intl Yen" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey intlYen = LogicalKeyboardKey(0x00100070089, debugName: kReleaseMode ? null : 'Intl Yen');
+
+  /// Represents the logical "Convert" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey convert = LogicalKeyboardKey(0x0010007008a, debugName: kReleaseMode ? null : 'Convert');
+
+  /// Represents the logical "Non Convert" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey nonConvert = LogicalKeyboardKey(0x0010007008b, debugName: kReleaseMode ? null : 'Non Convert');
+
+  /// Represents the logical "Lang 1" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey lang1 = LogicalKeyboardKey(0x00100070090, debugName: kReleaseMode ? null : 'Lang 1');
+
+  /// Represents the logical "Lang 2" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey lang2 = LogicalKeyboardKey(0x00100070091, debugName: kReleaseMode ? null : 'Lang 2');
+
+  /// Represents the logical "Lang 3" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey lang3 = LogicalKeyboardKey(0x00100070092, debugName: kReleaseMode ? null : 'Lang 3');
+
+  /// Represents the logical "Lang 4" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey lang4 = LogicalKeyboardKey(0x00100070093, debugName: kReleaseMode ? null : 'Lang 4');
+
+  /// Represents the logical "Lang 5" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey lang5 = LogicalKeyboardKey(0x00100070094, debugName: kReleaseMode ? null : 'Lang 5');
+
+  /// Represents the logical "Abort" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey abort = LogicalKeyboardKey(0x0010007009b, debugName: kReleaseMode ? null : 'Abort');
+
+  /// Represents the logical "Props" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey props = LogicalKeyboardKey(0x001000700a3, debugName: kReleaseMode ? null : 'Props');
+
+  /// Represents the logical "Numpad Paren Left" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadParenLeft = LogicalKeyboardKey(0x001000700b6, keyLabel: r'(', debugName: kReleaseMode ? null : 'Numpad Paren Left');
+
+  /// Represents the logical "Numpad Paren Right" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadParenRight = LogicalKeyboardKey(0x001000700b7, keyLabel: r')', debugName: kReleaseMode ? null : 'Numpad Paren Right');
+
+  /// Represents the logical "Numpad Backspace" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadBackspace = LogicalKeyboardKey(0x001000700bb, debugName: kReleaseMode ? null : 'Numpad Backspace');
+
+  /// Represents the logical "Numpad Memory Store" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadMemoryStore = LogicalKeyboardKey(0x001000700d0, debugName: kReleaseMode ? null : 'Numpad Memory Store');
+
+  /// Represents the logical "Numpad Memory Recall" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadMemoryRecall = LogicalKeyboardKey(0x001000700d1, debugName: kReleaseMode ? null : 'Numpad Memory Recall');
+
+  /// Represents the logical "Numpad Memory Clear" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadMemoryClear = LogicalKeyboardKey(0x001000700d2, debugName: kReleaseMode ? null : 'Numpad Memory Clear');
+
+  /// Represents the logical "Numpad Memory Add" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadMemoryAdd = LogicalKeyboardKey(0x001000700d3, debugName: kReleaseMode ? null : 'Numpad Memory Add');
+
+  /// Represents the logical "Numpad Memory Subtract" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadMemorySubtract = LogicalKeyboardKey(0x001000700d4, debugName: kReleaseMode ? null : 'Numpad Memory Subtract');
+
+  /// Represents the logical "Numpad Sign Change" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadSignChange = LogicalKeyboardKey(0x001000700d7, debugName: kReleaseMode ? null : 'Numpad Sign Change');
+
+  /// Represents the logical "Numpad Clear" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadClear = LogicalKeyboardKey(0x001000700d8, debugName: kReleaseMode ? null : 'Numpad Clear');
+
+  /// Represents the logical "Numpad Clear Entry" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey numpadClearEntry = LogicalKeyboardKey(0x001000700d9, debugName: kReleaseMode ? null : 'Numpad Clear Entry');
+
+  /// Represents the logical "Control Left" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey controlLeft = LogicalKeyboardKey(0x001000700e0, debugName: kReleaseMode ? null : 'Control Left');
+
+  /// Represents the logical "Shift Left" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey shiftLeft = LogicalKeyboardKey(0x001000700e1, debugName: kReleaseMode ? null : 'Shift Left');
+
+  /// Represents the logical "Alt Left" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey altLeft = LogicalKeyboardKey(0x001000700e2, debugName: kReleaseMode ? null : 'Alt Left');
+
+  /// Represents the logical "Meta Left" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey metaLeft = LogicalKeyboardKey(0x001000700e3, debugName: kReleaseMode ? null : 'Meta Left');
+
+  /// Represents the logical "Control Right" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey controlRight = LogicalKeyboardKey(0x001000700e4, debugName: kReleaseMode ? null : 'Control Right');
+
+  /// Represents the logical "Shift Right" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey shiftRight = LogicalKeyboardKey(0x001000700e5, debugName: kReleaseMode ? null : 'Shift Right');
+
+  /// Represents the logical "Alt Right" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey altRight = LogicalKeyboardKey(0x001000700e6, debugName: kReleaseMode ? null : 'Alt Right');
+
+  /// Represents the logical "Meta Right" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey metaRight = LogicalKeyboardKey(0x001000700e7, debugName: kReleaseMode ? null : 'Meta Right');
+
+  /// Represents the logical "Info" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey info = LogicalKeyboardKey(0x001000c0060, debugName: kReleaseMode ? null : 'Info');
+
+  /// Represents the logical "Closed Caption Toggle" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey closedCaptionToggle = LogicalKeyboardKey(0x001000c0061, debugName: kReleaseMode ? null : 'Closed Caption Toggle');
+
+  /// Represents the logical "Brightness Up" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey brightnessUp = LogicalKeyboardKey(0x001000c006f, debugName: kReleaseMode ? null : 'Brightness Up');
+
+  /// Represents the logical "Brightness Down" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey brightnessDown = LogicalKeyboardKey(0x001000c0070, debugName: kReleaseMode ? null : 'Brightness Down');
+
+  /// Represents the logical "Brightness Toggle" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey brightnessToggle = LogicalKeyboardKey(0x001000c0072, debugName: kReleaseMode ? null : 'Brightness Toggle');
+
+  /// Represents the logical "Brightness Minimum" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey brightnessMinimum = LogicalKeyboardKey(0x001000c0073, debugName: kReleaseMode ? null : 'Brightness Minimum');
+
+  /// Represents the logical "Brightness Maximum" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey brightnessMaximum = LogicalKeyboardKey(0x001000c0074, debugName: kReleaseMode ? null : 'Brightness Maximum');
+
+  /// Represents the logical "Brightness Auto" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey brightnessAuto = LogicalKeyboardKey(0x001000c0075, debugName: kReleaseMode ? null : 'Brightness Auto');
+
+  /// Represents the logical "Kbd Illum Up" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey kbdIllumUp = LogicalKeyboardKey(0x001000c0079, debugName: kReleaseMode ? null : 'Kbd Illum Up');
+
+  /// Represents the logical "Kbd Illum Down" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey kbdIllumDown = LogicalKeyboardKey(0x001000c007a, debugName: kReleaseMode ? null : 'Kbd Illum Down');
+
+  /// Represents the logical "Media Last" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey mediaLast = LogicalKeyboardKey(0x001000c0083, debugName: kReleaseMode ? null : 'Media Last');
+
+  /// Represents the logical "Launch Phone" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchPhone = LogicalKeyboardKey(0x001000c008c, debugName: kReleaseMode ? null : 'Launch Phone');
+
+  /// Represents the logical "Program Guide" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey programGuide = LogicalKeyboardKey(0x001000c008d, debugName: kReleaseMode ? null : 'Program Guide');
+
+  /// Represents the logical "Exit" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey exit = LogicalKeyboardKey(0x001000c0094, debugName: kReleaseMode ? null : 'Exit');
+
+  /// Represents the logical "Channel Up" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey channelUp = LogicalKeyboardKey(0x001000c009c, debugName: kReleaseMode ? null : 'Channel Up');
+
+  /// Represents the logical "Channel Down" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey channelDown = LogicalKeyboardKey(0x001000c009d, debugName: kReleaseMode ? null : 'Channel Down');
+
+  /// Represents the logical "Media Play" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey mediaPlay = LogicalKeyboardKey(0x001000c00b0, debugName: kReleaseMode ? null : 'Media Play');
+
+  /// Represents the logical "Media Pause" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey mediaPause = LogicalKeyboardKey(0x001000c00b1, debugName: kReleaseMode ? null : 'Media Pause');
+
+  /// Represents the logical "Media Record" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey mediaRecord = LogicalKeyboardKey(0x001000c00b2, debugName: kReleaseMode ? null : 'Media Record');
+
+  /// Represents the logical "Media Fast Forward" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey mediaFastForward = LogicalKeyboardKey(0x001000c00b3, debugName: kReleaseMode ? null : 'Media Fast Forward');
+
+  /// Represents the logical "Media Rewind" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey mediaRewind = LogicalKeyboardKey(0x001000c00b4, debugName: kReleaseMode ? null : 'Media Rewind');
+
+  /// Represents the logical "Media Track Next" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey mediaTrackNext = LogicalKeyboardKey(0x001000c00b5, debugName: kReleaseMode ? null : 'Media Track Next');
+
+  /// Represents the logical "Media Track Previous" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey mediaTrackPrevious = LogicalKeyboardKey(0x001000c00b6, debugName: kReleaseMode ? null : 'Media Track Previous');
+
+  /// Represents the logical "Media Stop" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey mediaStop = LogicalKeyboardKey(0x001000c00b7, debugName: kReleaseMode ? null : 'Media Stop');
+
+  /// Represents the logical "Eject" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey eject = LogicalKeyboardKey(0x001000c00b8, debugName: kReleaseMode ? null : 'Eject');
+
+  /// Represents the logical "Media Play Pause" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey mediaPlayPause = LogicalKeyboardKey(0x001000c00cd, debugName: kReleaseMode ? null : 'Media Play Pause');
+
+  /// Represents the logical "Speech Input Toggle" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey speechInputToggle = LogicalKeyboardKey(0x001000c00cf, debugName: kReleaseMode ? null : 'Speech Input Toggle');
+
+  /// Represents the logical "Bass Boost" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey bassBoost = LogicalKeyboardKey(0x001000c00e5, debugName: kReleaseMode ? null : 'Bass Boost');
+
+  /// Represents the logical "Media Select" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey mediaSelect = LogicalKeyboardKey(0x001000c0183, debugName: kReleaseMode ? null : 'Media Select');
+
+  /// Represents the logical "Launch Word Processor" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchWordProcessor = LogicalKeyboardKey(0x001000c0184, debugName: kReleaseMode ? null : 'Launch Word Processor');
+
+  /// Represents the logical "Launch Spreadsheet" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchSpreadsheet = LogicalKeyboardKey(0x001000c0186, debugName: kReleaseMode ? null : 'Launch Spreadsheet');
+
+  /// Represents the logical "Launch Mail" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchMail = LogicalKeyboardKey(0x001000c018a, debugName: kReleaseMode ? null : 'Launch Mail');
+
+  /// Represents the logical "Launch Contacts" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchContacts = LogicalKeyboardKey(0x001000c018d, debugName: kReleaseMode ? null : 'Launch Contacts');
+
+  /// Represents the logical "Launch Calendar" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchCalendar = LogicalKeyboardKey(0x001000c018e, debugName: kReleaseMode ? null : 'Launch Calendar');
+
+  /// Represents the logical "Launch App2" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchApp2 = LogicalKeyboardKey(0x001000c0192, debugName: kReleaseMode ? null : 'Launch App2');
+
+  /// Represents the logical "Launch App1" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchApp1 = LogicalKeyboardKey(0x001000c0194, debugName: kReleaseMode ? null : 'Launch App1');
+
+  /// Represents the logical "Launch Internet Browser" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchInternetBrowser = LogicalKeyboardKey(0x001000c0196, debugName: kReleaseMode ? null : 'Launch Internet Browser');
+
+  /// Represents the logical "Log Off" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey logOff = LogicalKeyboardKey(0x001000c019c, debugName: kReleaseMode ? null : 'Log Off');
+
+  /// Represents the logical "Lock Screen" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey lockScreen = LogicalKeyboardKey(0x001000c019e, debugName: kReleaseMode ? null : 'Lock Screen');
+
+  /// Represents the logical "Launch Control Panel" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchControlPanel = LogicalKeyboardKey(0x001000c019f, debugName: kReleaseMode ? null : 'Launch Control Panel');
+
+  /// Represents the logical "Select Task" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey selectTask = LogicalKeyboardKey(0x001000c01a2, debugName: kReleaseMode ? null : 'Select Task');
+
+  /// Represents the logical "Launch Documents" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchDocuments = LogicalKeyboardKey(0x001000c01a7, debugName: kReleaseMode ? null : 'Launch Documents');
+
+  /// Represents the logical "Spell Check" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey spellCheck = LogicalKeyboardKey(0x001000c01ab, debugName: kReleaseMode ? null : 'Spell Check');
+
+  /// Represents the logical "Launch Keyboard Layout" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchKeyboardLayout = LogicalKeyboardKey(0x001000c01ae, debugName: kReleaseMode ? null : 'Launch Keyboard Layout');
+
+  /// Represents the logical "Launch Screen Saver" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchScreenSaver = LogicalKeyboardKey(0x001000c01b1, debugName: kReleaseMode ? null : 'Launch Screen Saver');
+
+  /// Represents the logical "Launch Assistant" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchAssistant = LogicalKeyboardKey(0x001000c01cb, debugName: kReleaseMode ? null : 'Launch Assistant');
+
+  /// Represents the logical "Launch Audio Browser" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey launchAudioBrowser = LogicalKeyboardKey(0x001000c01b7, debugName: kReleaseMode ? null : 'Launch Audio Browser');
+
+  /// Represents the logical "New Key" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey newKey = LogicalKeyboardKey(0x001000c0201, debugName: kReleaseMode ? null : 'New Key');
+
+  /// Represents the logical "Close" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey close = LogicalKeyboardKey(0x001000c0203, debugName: kReleaseMode ? null : 'Close');
+
+  /// Represents the logical "Save" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey save = LogicalKeyboardKey(0x001000c0207, debugName: kReleaseMode ? null : 'Save');
+
+  /// Represents the logical "Print" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey print = LogicalKeyboardKey(0x001000c0208, debugName: kReleaseMode ? null : 'Print');
+
+  /// Represents the logical "Browser Search" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey browserSearch = LogicalKeyboardKey(0x001000c0221, debugName: kReleaseMode ? null : 'Browser Search');
+
+  /// Represents the logical "Browser Home" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey browserHome = LogicalKeyboardKey(0x001000c0223, debugName: kReleaseMode ? null : 'Browser Home');
+
+  /// Represents the logical "Browser Back" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey browserBack = LogicalKeyboardKey(0x001000c0224, debugName: kReleaseMode ? null : 'Browser Back');
+
+  /// Represents the logical "Browser Forward" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey browserForward = LogicalKeyboardKey(0x001000c0225, debugName: kReleaseMode ? null : 'Browser Forward');
+
+  /// Represents the logical "Browser Stop" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey browserStop = LogicalKeyboardKey(0x001000c0226, debugName: kReleaseMode ? null : 'Browser Stop');
+
+  /// Represents the logical "Browser Refresh" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey browserRefresh = LogicalKeyboardKey(0x001000c0227, debugName: kReleaseMode ? null : 'Browser Refresh');
+
+  /// Represents the logical "Browser Favorites" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey browserFavorites = LogicalKeyboardKey(0x001000c022a, debugName: kReleaseMode ? null : 'Browser Favorites');
+
+  /// Represents the logical "Zoom In" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey zoomIn = LogicalKeyboardKey(0x001000c022d, debugName: kReleaseMode ? null : 'Zoom In');
+
+  /// Represents the logical "Zoom Out" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey zoomOut = LogicalKeyboardKey(0x001000c022e, debugName: kReleaseMode ? null : 'Zoom Out');
+
+  /// Represents the logical "Zoom Toggle" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey zoomToggle = LogicalKeyboardKey(0x001000c0232, debugName: kReleaseMode ? null : 'Zoom Toggle');
+
+  /// Represents the logical "Redo" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey redo = LogicalKeyboardKey(0x001000c0279, debugName: kReleaseMode ? null : 'Redo');
+
+  /// Represents the logical "Mail Reply" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey mailReply = LogicalKeyboardKey(0x001000c0289, debugName: kReleaseMode ? null : 'Mail Reply');
+
+  /// Represents the logical "Mail Forward" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey mailForward = LogicalKeyboardKey(0x001000c028b, debugName: kReleaseMode ? null : 'Mail Forward');
+
+  /// Represents the logical "Mail Send" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey mailSend = LogicalKeyboardKey(0x001000c028c, debugName: kReleaseMode ? null : 'Mail Send');
+
+  /// Represents the logical "Keyboard Layout Select" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey keyboardLayoutSelect = LogicalKeyboardKey(0x001000c029d, debugName: kReleaseMode ? null : 'Keyboard Layout Select');
+
+  /// Represents the logical "Show All Windows" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey showAllWindows = LogicalKeyboardKey(0x001000c029f, debugName: kReleaseMode ? null : 'Show All Windows');
+
+  /// Represents the logical "Game Button 1" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton1 = LogicalKeyboardKey(0x0010005ff01, debugName: kReleaseMode ? null : 'Game Button 1');
+
+  /// Represents the logical "Game Button 2" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton2 = LogicalKeyboardKey(0x0010005ff02, debugName: kReleaseMode ? null : 'Game Button 2');
+
+  /// Represents the logical "Game Button 3" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton3 = LogicalKeyboardKey(0x0010005ff03, debugName: kReleaseMode ? null : 'Game Button 3');
+
+  /// Represents the logical "Game Button 4" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton4 = LogicalKeyboardKey(0x0010005ff04, debugName: kReleaseMode ? null : 'Game Button 4');
+
+  /// Represents the logical "Game Button 5" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton5 = LogicalKeyboardKey(0x0010005ff05, debugName: kReleaseMode ? null : 'Game Button 5');
+
+  /// Represents the logical "Game Button 6" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton6 = LogicalKeyboardKey(0x0010005ff06, debugName: kReleaseMode ? null : 'Game Button 6');
+
+  /// Represents the logical "Game Button 7" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton7 = LogicalKeyboardKey(0x0010005ff07, debugName: kReleaseMode ? null : 'Game Button 7');
+
+  /// Represents the logical "Game Button 8" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton8 = LogicalKeyboardKey(0x0010005ff08, debugName: kReleaseMode ? null : 'Game Button 8');
+
+  /// Represents the logical "Game Button 9" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton9 = LogicalKeyboardKey(0x0010005ff09, debugName: kReleaseMode ? null : 'Game Button 9');
+
+  /// Represents the logical "Game Button 10" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton10 = LogicalKeyboardKey(0x0010005ff0a, debugName: kReleaseMode ? null : 'Game Button 10');
+
+  /// Represents the logical "Game Button 11" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton11 = LogicalKeyboardKey(0x0010005ff0b, debugName: kReleaseMode ? null : 'Game Button 11');
+
+  /// Represents the logical "Game Button 12" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton12 = LogicalKeyboardKey(0x0010005ff0c, debugName: kReleaseMode ? null : 'Game Button 12');
+
+  /// Represents the logical "Game Button 13" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton13 = LogicalKeyboardKey(0x0010005ff0d, debugName: kReleaseMode ? null : 'Game Button 13');
+
+  /// Represents the logical "Game Button 14" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton14 = LogicalKeyboardKey(0x0010005ff0e, debugName: kReleaseMode ? null : 'Game Button 14');
+
+  /// Represents the logical "Game Button 15" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton15 = LogicalKeyboardKey(0x0010005ff0f, debugName: kReleaseMode ? null : 'Game Button 15');
+
+  /// Represents the logical "Game Button 16" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButton16 = LogicalKeyboardKey(0x0010005ff10, debugName: kReleaseMode ? null : 'Game Button 16');
+
+  /// Represents the logical "Game Button A" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonA = LogicalKeyboardKey(0x0010005ff11, debugName: kReleaseMode ? null : 'Game Button A');
+
+  /// Represents the logical "Game Button B" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonB = LogicalKeyboardKey(0x0010005ff12, debugName: kReleaseMode ? null : 'Game Button B');
+
+  /// Represents the logical "Game Button C" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonC = LogicalKeyboardKey(0x0010005ff13, debugName: kReleaseMode ? null : 'Game Button C');
+
+  /// Represents the logical "Game Button Left 1" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonLeft1 = LogicalKeyboardKey(0x0010005ff14, debugName: kReleaseMode ? null : 'Game Button Left 1');
+
+  /// Represents the logical "Game Button Left 2" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonLeft2 = LogicalKeyboardKey(0x0010005ff15, debugName: kReleaseMode ? null : 'Game Button Left 2');
+
+  /// Represents the logical "Game Button Mode" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonMode = LogicalKeyboardKey(0x0010005ff16, debugName: kReleaseMode ? null : 'Game Button Mode');
+
+  /// Represents the logical "Game Button Right 1" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonRight1 = LogicalKeyboardKey(0x0010005ff17, debugName: kReleaseMode ? null : 'Game Button Right 1');
+
+  /// Represents the logical "Game Button Right 2" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonRight2 = LogicalKeyboardKey(0x0010005ff18, debugName: kReleaseMode ? null : 'Game Button Right 2');
+
+  /// Represents the logical "Game Button Select" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonSelect = LogicalKeyboardKey(0x0010005ff19, debugName: kReleaseMode ? null : 'Game Button Select');
+
+  /// Represents the logical "Game Button Start" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonStart = LogicalKeyboardKey(0x0010005ff1a, debugName: kReleaseMode ? null : 'Game Button Start');
+
+  /// Represents the logical "Game Button Thumb Left" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonThumbLeft = LogicalKeyboardKey(0x0010005ff1b, debugName: kReleaseMode ? null : 'Game Button Thumb Left');
+
+  /// Represents the logical "Game Button Thumb Right" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonThumbRight = LogicalKeyboardKey(0x0010005ff1c, debugName: kReleaseMode ? null : 'Game Button Thumb Right');
+
+  /// Represents the logical "Game Button X" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonX = LogicalKeyboardKey(0x0010005ff1d, debugName: kReleaseMode ? null : 'Game Button X');
+
+  /// Represents the logical "Game Button Y" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonY = LogicalKeyboardKey(0x0010005ff1e, debugName: kReleaseMode ? null : 'Game Button Y');
+
+  /// Represents the logical "Game Button Z" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey gameButtonZ = LogicalKeyboardKey(0x0010005ff1f, debugName: kReleaseMode ? null : 'Game Button Z');
+
+  /// Represents the logical "Fn" key on the keyboard.
+  ///
+  /// See the function [RawKeyEvent.logicalKey] for more information.
+  static const LogicalKeyboardKey fn = LogicalKeyboardKey(0x00100000012, debugName: kReleaseMode ? null : 'Fn');
+
+  /// Represents the logical "Shift" key on the keyboard.
+  ///
+  /// This key represents the union of the keys {shiftLeft, shiftRight} when
+  /// comparing keys. This key will never be generated directly, its main use is
+  /// in defining key maps.
+  static const LogicalKeyboardKey shift = LogicalKeyboardKey(0x201000700e1, debugName: kReleaseMode ? null : 'Shift');
+
+  /// Represents the logical "Meta" key on the keyboard.
+  ///
+  /// This key represents the union of the keys {metaLeft, metaRight} when
+  /// comparing keys. This key will never be generated directly, its main use is
+  /// in defining key maps.
+  static const LogicalKeyboardKey meta = LogicalKeyboardKey(0x201000700e3, debugName: kReleaseMode ? null : 'Meta');
+
+  /// Represents the logical "Alt" key on the keyboard.
+  ///
+  /// This key represents the union of the keys {altLeft, altRight} when
+  /// comparing keys. This key will never be generated directly, its main use is
+  /// in defining key maps.
+  static const LogicalKeyboardKey alt = LogicalKeyboardKey(0x201000700e2, debugName: kReleaseMode ? null : 'Alt');
+
+  /// Represents the logical "Control" key on the keyboard.
+  ///
+  /// This key represents the union of the keys {controlLeft, controlRight} when
+  /// comparing keys. This key will never be generated directly, its main use is
+  /// in defining key maps.
+  static const LogicalKeyboardKey control = LogicalKeyboardKey(0x201000700e0, debugName: kReleaseMode ? null : 'Control');
+
+  // A list of all predefined constant LogicalKeyboardKeys so they can be
+  // searched.
+  static const Map<int, LogicalKeyboardKey> _knownLogicalKeys = <int, LogicalKeyboardKey>{
+    0x0100000000: none,
+    0x0100000010: hyper,
+    0x0100000011: superKey,
+    0x0100000013: fnLock,
+    0x0100000014: suspend,
+    0x0100000015: resume,
+    0x0100000016: turbo,
+    0x0100000017: privacyScreenToggle,
+    0x0100010082: sleep,
+    0x0100010083: wakeUp,
+    0x01000100b5: displayToggleIntExt,
+    0x0100070000: usbReserved,
+    0x0100070001: usbErrorRollOver,
+    0x0100070002: usbPostFail,
+    0x0100070003: usbErrorUndefined,
+    0x0000000061: keyA,
+    0x0000000062: keyB,
+    0x0000000063: keyC,
+    0x0000000064: keyD,
+    0x0000000065: keyE,
+    0x0000000066: keyF,
+    0x0000000067: keyG,
+    0x0000000068: keyH,
+    0x0000000069: keyI,
+    0x000000006a: keyJ,
+    0x000000006b: keyK,
+    0x000000006c: keyL,
+    0x000000006d: keyM,
+    0x000000006e: keyN,
+    0x000000006f: keyO,
+    0x0000000070: keyP,
+    0x0000000071: keyQ,
+    0x0000000072: keyR,
+    0x0000000073: keyS,
+    0x0000000074: keyT,
+    0x0000000075: keyU,
+    0x0000000076: keyV,
+    0x0000000077: keyW,
+    0x0000000078: keyX,
+    0x0000000079: keyY,
+    0x000000007a: keyZ,
+    0x0000000031: digit1,
+    0x0000000032: digit2,
+    0x0000000033: digit3,
+    0x0000000034: digit4,
+    0x0000000035: digit5,
+    0x0000000036: digit6,
+    0x0000000037: digit7,
+    0x0000000038: digit8,
+    0x0000000039: digit9,
+    0x0000000030: digit0,
+    0x0100070028: enter,
+    0x0100070029: escape,
+    0x010007002a: backspace,
+    0x010007002b: tab,
+    0x0000000020: space,
+    0x000000002d: minus,
+    0x000000003d: equal,
+    0x000000005b: bracketLeft,
+    0x000000005d: bracketRight,
+    0x000000005c: backslash,
+    0x000000003b: semicolon,
+    0x0000000027: quote,
+    0x0000000060: backquote,
+    0x000000002c: comma,
+    0x000000002e: period,
+    0x000000002f: slash,
+    0x0100070039: capsLock,
+    0x010007003a: f1,
+    0x010007003b: f2,
+    0x010007003c: f3,
+    0x010007003d: f4,
+    0x010007003e: f5,
+    0x010007003f: f6,
+    0x0100070040: f7,
+    0x0100070041: f8,
+    0x0100070042: f9,
+    0x0100070043: f10,
+    0x0100070044: f11,
+    0x0100070045: f12,
+    0x0100070046: printScreen,
+    0x0100070047: scrollLock,
+    0x0100070048: pause,
+    0x0100070049: insert,
+    0x010007004a: home,
+    0x010007004b: pageUp,
+    0x010007004c: delete,
+    0x010007004d: end,
+    0x010007004e: pageDown,
+    0x010007004f: arrowRight,
+    0x0100070050: arrowLeft,
+    0x0100070051: arrowDown,
+    0x0100070052: arrowUp,
+    0x0100070053: numLock,
+    0x0100070054: numpadDivide,
+    0x0100070055: numpadMultiply,
+    0x0100070056: numpadSubtract,
+    0x0100070057: numpadAdd,
+    0x0100070058: numpadEnter,
+    0x0100070059: numpad1,
+    0x010007005a: numpad2,
+    0x010007005b: numpad3,
+    0x010007005c: numpad4,
+    0x010007005d: numpad5,
+    0x010007005e: numpad6,
+    0x010007005f: numpad7,
+    0x0100070060: numpad8,
+    0x0100070061: numpad9,
+    0x0100070062: numpad0,
+    0x0100070063: numpadDecimal,
+    0x0100070064: intlBackslash,
+    0x0100070065: contextMenu,
+    0x0100070066: power,
+    0x0100070067: numpadEqual,
+    0x0100070068: f13,
+    0x0100070069: f14,
+    0x010007006a: f15,
+    0x010007006b: f16,
+    0x010007006c: f17,
+    0x010007006d: f18,
+    0x010007006e: f19,
+    0x010007006f: f20,
+    0x0100070070: f21,
+    0x0100070071: f22,
+    0x0100070072: f23,
+    0x0100070073: f24,
+    0x0100070074: open,
+    0x0100070075: help,
+    0x0100070077: select,
+    0x0100070079: again,
+    0x010007007a: undo,
+    0x010007007b: cut,
+    0x010007007c: copy,
+    0x010007007d: paste,
+    0x010007007e: find,
+    0x010007007f: audioVolumeMute,
+    0x0100070080: audioVolumeUp,
+    0x0100070081: audioVolumeDown,
+    0x0100070085: numpadComma,
+    0x0100070087: intlRo,
+    0x0100070088: kanaMode,
+    0x0100070089: intlYen,
+    0x010007008a: convert,
+    0x010007008b: nonConvert,
+    0x0100070090: lang1,
+    0x0100070091: lang2,
+    0x0100070092: lang3,
+    0x0100070093: lang4,
+    0x0100070094: lang5,
+    0x010007009b: abort,
+    0x01000700a3: props,
+    0x01000700b6: numpadParenLeft,
+    0x01000700b7: numpadParenRight,
+    0x01000700bb: numpadBackspace,
+    0x01000700d0: numpadMemoryStore,
+    0x01000700d1: numpadMemoryRecall,
+    0x01000700d2: numpadMemoryClear,
+    0x01000700d3: numpadMemoryAdd,
+    0x01000700d4: numpadMemorySubtract,
+    0x01000700d7: numpadSignChange,
+    0x01000700d8: numpadClear,
+    0x01000700d9: numpadClearEntry,
+    0x01000700e0: controlLeft,
+    0x01000700e1: shiftLeft,
+    0x01000700e2: altLeft,
+    0x01000700e3: metaLeft,
+    0x01000700e4: controlRight,
+    0x01000700e5: shiftRight,
+    0x01000700e6: altRight,
+    0x01000700e7: metaRight,
+    0x01000c0060: info,
+    0x01000c0061: closedCaptionToggle,
+    0x01000c006f: brightnessUp,
+    0x01000c0070: brightnessDown,
+    0x01000c0072: brightnessToggle,
+    0x01000c0073: brightnessMinimum,
+    0x01000c0074: brightnessMaximum,
+    0x01000c0075: brightnessAuto,
+    0x01000c0079: kbdIllumUp,
+    0x01000c007a: kbdIllumDown,
+    0x01000c0083: mediaLast,
+    0x01000c008c: launchPhone,
+    0x01000c008d: programGuide,
+    0x01000c0094: exit,
+    0x01000c009c: channelUp,
+    0x01000c009d: channelDown,
+    0x01000c00b0: mediaPlay,
+    0x01000c00b1: mediaPause,
+    0x01000c00b2: mediaRecord,
+    0x01000c00b3: mediaFastForward,
+    0x01000c00b4: mediaRewind,
+    0x01000c00b5: mediaTrackNext,
+    0x01000c00b6: mediaTrackPrevious,
+    0x01000c00b7: mediaStop,
+    0x01000c00b8: eject,
+    0x01000c00cd: mediaPlayPause,
+    0x01000c00cf: speechInputToggle,
+    0x01000c00e5: bassBoost,
+    0x01000c0183: mediaSelect,
+    0x01000c0184: launchWordProcessor,
+    0x01000c0186: launchSpreadsheet,
+    0x01000c018a: launchMail,
+    0x01000c018d: launchContacts,
+    0x01000c018e: launchCalendar,
+    0x01000c0192: launchApp2,
+    0x01000c0194: launchApp1,
+    0x01000c0196: launchInternetBrowser,
+    0x01000c019c: logOff,
+    0x01000c019e: lockScreen,
+    0x01000c019f: launchControlPanel,
+    0x01000c01a2: selectTask,
+    0x01000c01a7: launchDocuments,
+    0x01000c01ab: spellCheck,
+    0x01000c01ae: launchKeyboardLayout,
+    0x01000c01b1: launchScreenSaver,
+    0x01000c01cb: launchAssistant,
+    0x01000c01b7: launchAudioBrowser,
+    0x01000c0201: newKey,
+    0x01000c0203: close,
+    0x01000c0207: save,
+    0x01000c0208: print,
+    0x01000c0221: browserSearch,
+    0x01000c0223: browserHome,
+    0x01000c0224: browserBack,
+    0x01000c0225: browserForward,
+    0x01000c0226: browserStop,
+    0x01000c0227: browserRefresh,
+    0x01000c022a: browserFavorites,
+    0x01000c022d: zoomIn,
+    0x01000c022e: zoomOut,
+    0x01000c0232: zoomToggle,
+    0x01000c0279: redo,
+    0x01000c0289: mailReply,
+    0x01000c028b: mailForward,
+    0x01000c028c: mailSend,
+    0x01000c029d: keyboardLayoutSelect,
+    0x01000c029f: showAllWindows,
+    0x010005ff01: gameButton1,
+    0x010005ff02: gameButton2,
+    0x010005ff03: gameButton3,
+    0x010005ff04: gameButton4,
+    0x010005ff05: gameButton5,
+    0x010005ff06: gameButton6,
+    0x010005ff07: gameButton7,
+    0x010005ff08: gameButton8,
+    0x010005ff09: gameButton9,
+    0x010005ff0a: gameButton10,
+    0x010005ff0b: gameButton11,
+    0x010005ff0c: gameButton12,
+    0x010005ff0d: gameButton13,
+    0x010005ff0e: gameButton14,
+    0x010005ff0f: gameButton15,
+    0x010005ff10: gameButton16,
+    0x010005ff11: gameButtonA,
+    0x010005ff12: gameButtonB,
+    0x010005ff13: gameButtonC,
+    0x010005ff14: gameButtonLeft1,
+    0x010005ff15: gameButtonLeft2,
+    0x010005ff16: gameButtonMode,
+    0x010005ff17: gameButtonRight1,
+    0x010005ff18: gameButtonRight2,
+    0x010005ff19: gameButtonSelect,
+    0x010005ff1a: gameButtonStart,
+    0x010005ff1b: gameButtonThumbLeft,
+    0x010005ff1c: gameButtonThumbRight,
+    0x010005ff1d: gameButtonX,
+    0x010005ff1e: gameButtonY,
+    0x010005ff1f: gameButtonZ,
+    0x0100000012: fn,
+    0x201000700e1: shift,
+    0x201000700e3: meta,
+    0x201000700e2: alt,
+    0x201000700e0: control,
+  };
+
+  // A map of keys to the pseudo-key synonym for that key. Used by getSynonyms.
+  static final Map<LogicalKeyboardKey, LogicalKeyboardKey> _synonyms = <LogicalKeyboardKey, LogicalKeyboardKey>{
+    shiftLeft: shift,
+    shiftRight: shift,
+    metaLeft: meta,
+    metaRight: meta,
+    altLeft: alt,
+    altRight: alt,
+    controlLeft: control,
+    controlRight: control,
+  };
+}
+
+/// A class with static values that describe the keys that are returned from
+/// [RawKeyEvent.physicalKey].
+///
+/// These represent *physical* keys, which are keys which represent a particular
+/// key location on a QWERTY keyboard. It ignores any modifiers, modes, or
+/// keyboard layouts which may be in effect. This is contrast to
+/// [LogicalKeyboardKey], which represents a logical key interpreted in the
+/// context of modifiers, modes, and/or keyboard layouts.
+///
+/// As an example, if you wanted a game where the key next to the CAPS LOCK (the
+/// "A" key on a QWERTY keyboard) moved the player to the left, you'd want to
+/// look at the physical key to make sure that regardless of the character the
+/// key produces, you got the key that is in that location on the keyboard.
+///
+/// Conversely, if you wanted to implement an app where the "Q" key "quit"
+/// something, you'd want to look at the logical key to detect this, since you
+/// would like to have it match the key with "Q" on it, instead of always
+/// looking for "the key next next to the TAB key", since on a French keyboard,
+/// the key next to the TAB key has an "A" on it.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+/// This example shows how to detect if the user has selected the physical key
+/// to the right of the CAPS LOCK key.
+///
+/// ```dart imports
+/// import 'package:flute/services.dart';
+/// ```
+///
+/// ```dart
+/// // The node used to request the keyboard focus.
+/// final FocusNode _focusNode = FocusNode();
+/// // The message to display.
+/// String _message;
+///
+/// // Focus nodes need to be disposed.
+/// @override
+/// void dispose() {
+///   _focusNode.dispose();
+///   super.dispose();
+/// }
+///
+/// // Handles the key events from the RawKeyboardListener and update the
+/// // _message.
+/// void _handleKeyEvent(RawKeyEvent event) {
+///   setState(() {
+///     if (event.physicalKey == PhysicalKeyboardKey.keyA) {
+///       _message = 'Pressed the key next to CAPS LOCK!';
+///     } else {
+///       _message = 'Wrong key.';
+///     }
+///   });
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   final TextTheme textTheme = Theme.of(context).textTheme;
+///   return Container(
+///     color: Colors.white,
+///     alignment: Alignment.center,
+///     child: DefaultTextStyle(
+///       style: textTheme.headline4,
+///       child: RawKeyboardListener(
+///         focusNode: _focusNode,
+///         onKey: _handleKeyEvent,
+///         child: AnimatedBuilder(
+///           animation: _focusNode,
+///           builder: (BuildContext context, Widget child) {
+///             if (!_focusNode.hasFocus) {
+///               return GestureDetector(
+///                 onTap: () {
+///                   FocusScope.of(context).requestFocus(_focusNode);
+///                 },
+///                 child: Text('Tap to focus'),
+///               );
+///             }
+///             return Text(_message ?? 'Press a key');
+///           },
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [RawKeyEvent], the keyboard event object received by widgets that listen
+///    to keyboard events.
+///  * [RawKeyboardListener], a widget used to listen to and supply handlers for
+///    keyboard events.
+@immutable
+class PhysicalKeyboardKey extends KeyboardKey {
+  /// Creates a PhysicalKeyboardKey object with an optional debug name.
+  ///
+  /// The [usbHidUsage] must not be null.
+  ///
+  /// {@tool snippet}
+  /// To save executable size, it is recommended that the [debugName] be null in
+  /// release mode. You can do this using the [kReleaseMode] constant.
+  ///
+  /// ```dart
+  /// const PhysicalKeyboardKey(0x0000ffff, debugName: kReleaseMode ? null : 'Special Key')
+  /// ```
+  /// {@end-tool}
+  const PhysicalKeyboardKey(this.usbHidUsage, {this.debugName})
+      : assert(usbHidUsage != null);
+
+  /// The unique USB HID usage ID of this physical key on the keyboard.
+  ///
+  /// Due to the variations in platform APIs, this may not be the actual HID
+  /// usage code from the hardware, but a value derived from available
+  /// information on the platform.
+  ///
+  /// See <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
+  /// for the HID usage values and their meanings.
+  final int usbHidUsage;
+
+  /// The debug string to print for this keyboard key, which will be null in
+  /// release mode.
+  final String? debugName;
+
+  /// Finds a known [PhysicalKeyboardKey] that matches the given USB HID usage
+  /// code.
+  static PhysicalKeyboardKey? findKeyByCode(int usageCode) => _knownPhysicalKeys[usageCode];
+
+  @override
+  int get hashCode => usbHidUsage.hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is PhysicalKeyboardKey
+        && other.usbHidUsage == usbHidUsage;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(StringProperty('usbHidUsage', '0x${usbHidUsage.toRadixString(16).padLeft(8, '0')}', showName: true));
+    properties.add(StringProperty('debugName', debugName, showName: true, defaultValue: null));
+  }
+
+  // Key constants for all keyboard keys in the USB HID specification at the
+  // time Flutter was built.
+
+  /// Represents the location of the "None" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey none = PhysicalKeyboardKey(0x00000000, debugName: kReleaseMode ? null : 'None');
+
+  /// Represents the location of the "Hyper" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey hyper = PhysicalKeyboardKey(0x00000010, debugName: kReleaseMode ? null : 'Hyper');
+
+  /// Represents the location of the "Super Key" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey superKey = PhysicalKeyboardKey(0x00000011, debugName: kReleaseMode ? null : 'Super Key');
+
+  /// Represents the location of the "Fn Lock" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey fnLock = PhysicalKeyboardKey(0x00000013, debugName: kReleaseMode ? null : 'Fn Lock');
+
+  /// Represents the location of the "Suspend" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey suspend = PhysicalKeyboardKey(0x00000014, debugName: kReleaseMode ? null : 'Suspend');
+
+  /// Represents the location of the "Resume" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey resume = PhysicalKeyboardKey(0x00000015, debugName: kReleaseMode ? null : 'Resume');
+
+  /// Represents the location of the "Turbo" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey turbo = PhysicalKeyboardKey(0x00000016, debugName: kReleaseMode ? null : 'Turbo');
+
+  /// Represents the location of the "Privacy Screen Toggle" key on a
+  /// generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey privacyScreenToggle = PhysicalKeyboardKey(0x00000017, debugName: kReleaseMode ? null : 'Privacy Screen Toggle');
+
+  /// Represents the location of the "Sleep" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey sleep = PhysicalKeyboardKey(0x00010082, debugName: kReleaseMode ? null : 'Sleep');
+
+  /// Represents the location of the "Wake Up" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey wakeUp = PhysicalKeyboardKey(0x00010083, debugName: kReleaseMode ? null : 'Wake Up');
+
+  /// Represents the location of the "Display Toggle Int Ext" key on a
+  /// generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey displayToggleIntExt = PhysicalKeyboardKey(0x000100b5, debugName: kReleaseMode ? null : 'Display Toggle Int Ext');
+
+  /// Represents the location of the "Usb Reserved" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey usbReserved = PhysicalKeyboardKey(0x00070000, debugName: kReleaseMode ? null : 'Usb Reserved');
+
+  /// Represents the location of the "Usb Error Roll Over" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey usbErrorRollOver = PhysicalKeyboardKey(0x00070001, debugName: kReleaseMode ? null : 'Usb Error Roll Over');
+
+  /// Represents the location of the "Usb Post Fail" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey usbPostFail = PhysicalKeyboardKey(0x00070002, debugName: kReleaseMode ? null : 'Usb Post Fail');
+
+  /// Represents the location of the "Usb Error Undefined" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey usbErrorUndefined = PhysicalKeyboardKey(0x00070003, debugName: kReleaseMode ? null : 'Usb Error Undefined');
+
+  /// Represents the location of the "Key A" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyA = PhysicalKeyboardKey(0x00070004, debugName: kReleaseMode ? null : 'Key A');
+
+  /// Represents the location of the "Key B" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyB = PhysicalKeyboardKey(0x00070005, debugName: kReleaseMode ? null : 'Key B');
+
+  /// Represents the location of the "Key C" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyC = PhysicalKeyboardKey(0x00070006, debugName: kReleaseMode ? null : 'Key C');
+
+  /// Represents the location of the "Key D" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyD = PhysicalKeyboardKey(0x00070007, debugName: kReleaseMode ? null : 'Key D');
+
+  /// Represents the location of the "Key E" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyE = PhysicalKeyboardKey(0x00070008, debugName: kReleaseMode ? null : 'Key E');
+
+  /// Represents the location of the "Key F" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyF = PhysicalKeyboardKey(0x00070009, debugName: kReleaseMode ? null : 'Key F');
+
+  /// Represents the location of the "Key G" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyG = PhysicalKeyboardKey(0x0007000a, debugName: kReleaseMode ? null : 'Key G');
+
+  /// Represents the location of the "Key H" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyH = PhysicalKeyboardKey(0x0007000b, debugName: kReleaseMode ? null : 'Key H');
+
+  /// Represents the location of the "Key I" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyI = PhysicalKeyboardKey(0x0007000c, debugName: kReleaseMode ? null : 'Key I');
+
+  /// Represents the location of the "Key J" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyJ = PhysicalKeyboardKey(0x0007000d, debugName: kReleaseMode ? null : 'Key J');
+
+  /// Represents the location of the "Key K" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyK = PhysicalKeyboardKey(0x0007000e, debugName: kReleaseMode ? null : 'Key K');
+
+  /// Represents the location of the "Key L" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyL = PhysicalKeyboardKey(0x0007000f, debugName: kReleaseMode ? null : 'Key L');
+
+  /// Represents the location of the "Key M" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyM = PhysicalKeyboardKey(0x00070010, debugName: kReleaseMode ? null : 'Key M');
+
+  /// Represents the location of the "Key N" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyN = PhysicalKeyboardKey(0x00070011, debugName: kReleaseMode ? null : 'Key N');
+
+  /// Represents the location of the "Key O" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyO = PhysicalKeyboardKey(0x00070012, debugName: kReleaseMode ? null : 'Key O');
+
+  /// Represents the location of the "Key P" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyP = PhysicalKeyboardKey(0x00070013, debugName: kReleaseMode ? null : 'Key P');
+
+  /// Represents the location of the "Key Q" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyQ = PhysicalKeyboardKey(0x00070014, debugName: kReleaseMode ? null : 'Key Q');
+
+  /// Represents the location of the "Key R" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyR = PhysicalKeyboardKey(0x00070015, debugName: kReleaseMode ? null : 'Key R');
+
+  /// Represents the location of the "Key S" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyS = PhysicalKeyboardKey(0x00070016, debugName: kReleaseMode ? null : 'Key S');
+
+  /// Represents the location of the "Key T" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyT = PhysicalKeyboardKey(0x00070017, debugName: kReleaseMode ? null : 'Key T');
+
+  /// Represents the location of the "Key U" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyU = PhysicalKeyboardKey(0x00070018, debugName: kReleaseMode ? null : 'Key U');
+
+  /// Represents the location of the "Key V" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyV = PhysicalKeyboardKey(0x00070019, debugName: kReleaseMode ? null : 'Key V');
+
+  /// Represents the location of the "Key W" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyW = PhysicalKeyboardKey(0x0007001a, debugName: kReleaseMode ? null : 'Key W');
+
+  /// Represents the location of the "Key X" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyX = PhysicalKeyboardKey(0x0007001b, debugName: kReleaseMode ? null : 'Key X');
+
+  /// Represents the location of the "Key Y" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyY = PhysicalKeyboardKey(0x0007001c, debugName: kReleaseMode ? null : 'Key Y');
+
+  /// Represents the location of the "Key Z" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyZ = PhysicalKeyboardKey(0x0007001d, debugName: kReleaseMode ? null : 'Key Z');
+
+  /// Represents the location of the "Digit 1" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey digit1 = PhysicalKeyboardKey(0x0007001e, debugName: kReleaseMode ? null : 'Digit 1');
+
+  /// Represents the location of the "Digit 2" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey digit2 = PhysicalKeyboardKey(0x0007001f, debugName: kReleaseMode ? null : 'Digit 2');
+
+  /// Represents the location of the "Digit 3" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey digit3 = PhysicalKeyboardKey(0x00070020, debugName: kReleaseMode ? null : 'Digit 3');
+
+  /// Represents the location of the "Digit 4" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey digit4 = PhysicalKeyboardKey(0x00070021, debugName: kReleaseMode ? null : 'Digit 4');
+
+  /// Represents the location of the "Digit 5" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey digit5 = PhysicalKeyboardKey(0x00070022, debugName: kReleaseMode ? null : 'Digit 5');
+
+  /// Represents the location of the "Digit 6" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey digit6 = PhysicalKeyboardKey(0x00070023, debugName: kReleaseMode ? null : 'Digit 6');
+
+  /// Represents the location of the "Digit 7" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey digit7 = PhysicalKeyboardKey(0x00070024, debugName: kReleaseMode ? null : 'Digit 7');
+
+  /// Represents the location of the "Digit 8" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey digit8 = PhysicalKeyboardKey(0x00070025, debugName: kReleaseMode ? null : 'Digit 8');
+
+  /// Represents the location of the "Digit 9" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey digit9 = PhysicalKeyboardKey(0x00070026, debugName: kReleaseMode ? null : 'Digit 9');
+
+  /// Represents the location of the "Digit 0" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey digit0 = PhysicalKeyboardKey(0x00070027, debugName: kReleaseMode ? null : 'Digit 0');
+
+  /// Represents the location of the "Enter" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey enter = PhysicalKeyboardKey(0x00070028, debugName: kReleaseMode ? null : 'Enter');
+
+  /// Represents the location of the "Escape" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey escape = PhysicalKeyboardKey(0x00070029, debugName: kReleaseMode ? null : 'Escape');
+
+  /// Represents the location of the "Backspace" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey backspace = PhysicalKeyboardKey(0x0007002a, debugName: kReleaseMode ? null : 'Backspace');
+
+  /// Represents the location of the "Tab" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey tab = PhysicalKeyboardKey(0x0007002b, debugName: kReleaseMode ? null : 'Tab');
+
+  /// Represents the location of the "Space" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey space = PhysicalKeyboardKey(0x0007002c, debugName: kReleaseMode ? null : 'Space');
+
+  /// Represents the location of the "Minus" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey minus = PhysicalKeyboardKey(0x0007002d, debugName: kReleaseMode ? null : 'Minus');
+
+  /// Represents the location of the "Equal" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey equal = PhysicalKeyboardKey(0x0007002e, debugName: kReleaseMode ? null : 'Equal');
+
+  /// Represents the location of the "Bracket Left" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey bracketLeft = PhysicalKeyboardKey(0x0007002f, debugName: kReleaseMode ? null : 'Bracket Left');
+
+  /// Represents the location of the "Bracket Right" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey bracketRight = PhysicalKeyboardKey(0x00070030, debugName: kReleaseMode ? null : 'Bracket Right');
+
+  /// Represents the location of the "Backslash" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey backslash = PhysicalKeyboardKey(0x00070031, debugName: kReleaseMode ? null : 'Backslash');
+
+  /// Represents the location of the "Semicolon" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey semicolon = PhysicalKeyboardKey(0x00070033, debugName: kReleaseMode ? null : 'Semicolon');
+
+  /// Represents the location of the "Quote" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey quote = PhysicalKeyboardKey(0x00070034, debugName: kReleaseMode ? null : 'Quote');
+
+  /// Represents the location of the "Backquote" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey backquote = PhysicalKeyboardKey(0x00070035, debugName: kReleaseMode ? null : 'Backquote');
+
+  /// Represents the location of the "Comma" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey comma = PhysicalKeyboardKey(0x00070036, debugName: kReleaseMode ? null : 'Comma');
+
+  /// Represents the location of the "Period" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey period = PhysicalKeyboardKey(0x00070037, debugName: kReleaseMode ? null : 'Period');
+
+  /// Represents the location of the "Slash" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey slash = PhysicalKeyboardKey(0x00070038, debugName: kReleaseMode ? null : 'Slash');
+
+  /// Represents the location of the "Caps Lock" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey capsLock = PhysicalKeyboardKey(0x00070039, debugName: kReleaseMode ? null : 'Caps Lock');
+
+  /// Represents the location of the "F1" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f1 = PhysicalKeyboardKey(0x0007003a, debugName: kReleaseMode ? null : 'F1');
+
+  /// Represents the location of the "F2" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f2 = PhysicalKeyboardKey(0x0007003b, debugName: kReleaseMode ? null : 'F2');
+
+  /// Represents the location of the "F3" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f3 = PhysicalKeyboardKey(0x0007003c, debugName: kReleaseMode ? null : 'F3');
+
+  /// Represents the location of the "F4" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f4 = PhysicalKeyboardKey(0x0007003d, debugName: kReleaseMode ? null : 'F4');
+
+  /// Represents the location of the "F5" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f5 = PhysicalKeyboardKey(0x0007003e, debugName: kReleaseMode ? null : 'F5');
+
+  /// Represents the location of the "F6" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f6 = PhysicalKeyboardKey(0x0007003f, debugName: kReleaseMode ? null : 'F6');
+
+  /// Represents the location of the "F7" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f7 = PhysicalKeyboardKey(0x00070040, debugName: kReleaseMode ? null : 'F7');
+
+  /// Represents the location of the "F8" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f8 = PhysicalKeyboardKey(0x00070041, debugName: kReleaseMode ? null : 'F8');
+
+  /// Represents the location of the "F9" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f9 = PhysicalKeyboardKey(0x00070042, debugName: kReleaseMode ? null : 'F9');
+
+  /// Represents the location of the "F10" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f10 = PhysicalKeyboardKey(0x00070043, debugName: kReleaseMode ? null : 'F10');
+
+  /// Represents the location of the "F11" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f11 = PhysicalKeyboardKey(0x00070044, debugName: kReleaseMode ? null : 'F11');
+
+  /// Represents the location of the "F12" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f12 = PhysicalKeyboardKey(0x00070045, debugName: kReleaseMode ? null : 'F12');
+
+  /// Represents the location of the "Print Screen" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey printScreen = PhysicalKeyboardKey(0x00070046, debugName: kReleaseMode ? null : 'Print Screen');
+
+  /// Represents the location of the "Scroll Lock" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey scrollLock = PhysicalKeyboardKey(0x00070047, debugName: kReleaseMode ? null : 'Scroll Lock');
+
+  /// Represents the location of the "Pause" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey pause = PhysicalKeyboardKey(0x00070048, debugName: kReleaseMode ? null : 'Pause');
+
+  /// Represents the location of the "Insert" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey insert = PhysicalKeyboardKey(0x00070049, debugName: kReleaseMode ? null : 'Insert');
+
+  /// Represents the location of the "Home" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey home = PhysicalKeyboardKey(0x0007004a, debugName: kReleaseMode ? null : 'Home');
+
+  /// Represents the location of the "Page Up" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey pageUp = PhysicalKeyboardKey(0x0007004b, debugName: kReleaseMode ? null : 'Page Up');
+
+  /// Represents the location of the "Delete" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey delete = PhysicalKeyboardKey(0x0007004c, debugName: kReleaseMode ? null : 'Delete');
+
+  /// Represents the location of the "End" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey end = PhysicalKeyboardKey(0x0007004d, debugName: kReleaseMode ? null : 'End');
+
+  /// Represents the location of the "Page Down" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey pageDown = PhysicalKeyboardKey(0x0007004e, debugName: kReleaseMode ? null : 'Page Down');
+
+  /// Represents the location of the "Arrow Right" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey arrowRight = PhysicalKeyboardKey(0x0007004f, debugName: kReleaseMode ? null : 'Arrow Right');
+
+  /// Represents the location of the "Arrow Left" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey arrowLeft = PhysicalKeyboardKey(0x00070050, debugName: kReleaseMode ? null : 'Arrow Left');
+
+  /// Represents the location of the "Arrow Down" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey arrowDown = PhysicalKeyboardKey(0x00070051, debugName: kReleaseMode ? null : 'Arrow Down');
+
+  /// Represents the location of the "Arrow Up" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey arrowUp = PhysicalKeyboardKey(0x00070052, debugName: kReleaseMode ? null : 'Arrow Up');
+
+  /// Represents the location of the "Num Lock" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numLock = PhysicalKeyboardKey(0x00070053, debugName: kReleaseMode ? null : 'Num Lock');
+
+  /// Represents the location of the "Numpad Divide" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadDivide = PhysicalKeyboardKey(0x00070054, debugName: kReleaseMode ? null : 'Numpad Divide');
+
+  /// Represents the location of the "Numpad Multiply" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadMultiply = PhysicalKeyboardKey(0x00070055, debugName: kReleaseMode ? null : 'Numpad Multiply');
+
+  /// Represents the location of the "Numpad Subtract" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadSubtract = PhysicalKeyboardKey(0x00070056, debugName: kReleaseMode ? null : 'Numpad Subtract');
+
+  /// Represents the location of the "Numpad Add" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadAdd = PhysicalKeyboardKey(0x00070057, debugName: kReleaseMode ? null : 'Numpad Add');
+
+  /// Represents the location of the "Numpad Enter" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadEnter = PhysicalKeyboardKey(0x00070058, debugName: kReleaseMode ? null : 'Numpad Enter');
+
+  /// Represents the location of the "Numpad 1" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpad1 = PhysicalKeyboardKey(0x00070059, debugName: kReleaseMode ? null : 'Numpad 1');
+
+  /// Represents the location of the "Numpad 2" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpad2 = PhysicalKeyboardKey(0x0007005a, debugName: kReleaseMode ? null : 'Numpad 2');
+
+  /// Represents the location of the "Numpad 3" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpad3 = PhysicalKeyboardKey(0x0007005b, debugName: kReleaseMode ? null : 'Numpad 3');
+
+  /// Represents the location of the "Numpad 4" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpad4 = PhysicalKeyboardKey(0x0007005c, debugName: kReleaseMode ? null : 'Numpad 4');
+
+  /// Represents the location of the "Numpad 5" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpad5 = PhysicalKeyboardKey(0x0007005d, debugName: kReleaseMode ? null : 'Numpad 5');
+
+  /// Represents the location of the "Numpad 6" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpad6 = PhysicalKeyboardKey(0x0007005e, debugName: kReleaseMode ? null : 'Numpad 6');
+
+  /// Represents the location of the "Numpad 7" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpad7 = PhysicalKeyboardKey(0x0007005f, debugName: kReleaseMode ? null : 'Numpad 7');
+
+  /// Represents the location of the "Numpad 8" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpad8 = PhysicalKeyboardKey(0x00070060, debugName: kReleaseMode ? null : 'Numpad 8');
+
+  /// Represents the location of the "Numpad 9" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpad9 = PhysicalKeyboardKey(0x00070061, debugName: kReleaseMode ? null : 'Numpad 9');
+
+  /// Represents the location of the "Numpad 0" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpad0 = PhysicalKeyboardKey(0x00070062, debugName: kReleaseMode ? null : 'Numpad 0');
+
+  /// Represents the location of the "Numpad Decimal" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadDecimal = PhysicalKeyboardKey(0x00070063, debugName: kReleaseMode ? null : 'Numpad Decimal');
+
+  /// Represents the location of the "Intl Backslash" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey intlBackslash = PhysicalKeyboardKey(0x00070064, debugName: kReleaseMode ? null : 'Intl Backslash');
+
+  /// Represents the location of the "Context Menu" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey contextMenu = PhysicalKeyboardKey(0x00070065, debugName: kReleaseMode ? null : 'Context Menu');
+
+  /// Represents the location of the "Power" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey power = PhysicalKeyboardKey(0x00070066, debugName: kReleaseMode ? null : 'Power');
+
+  /// Represents the location of the "Numpad Equal" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadEqual = PhysicalKeyboardKey(0x00070067, debugName: kReleaseMode ? null : 'Numpad Equal');
+
+  /// Represents the location of the "F13" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f13 = PhysicalKeyboardKey(0x00070068, debugName: kReleaseMode ? null : 'F13');
+
+  /// Represents the location of the "F14" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f14 = PhysicalKeyboardKey(0x00070069, debugName: kReleaseMode ? null : 'F14');
+
+  /// Represents the location of the "F15" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f15 = PhysicalKeyboardKey(0x0007006a, debugName: kReleaseMode ? null : 'F15');
+
+  /// Represents the location of the "F16" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f16 = PhysicalKeyboardKey(0x0007006b, debugName: kReleaseMode ? null : 'F16');
+
+  /// Represents the location of the "F17" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f17 = PhysicalKeyboardKey(0x0007006c, debugName: kReleaseMode ? null : 'F17');
+
+  /// Represents the location of the "F18" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f18 = PhysicalKeyboardKey(0x0007006d, debugName: kReleaseMode ? null : 'F18');
+
+  /// Represents the location of the "F19" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f19 = PhysicalKeyboardKey(0x0007006e, debugName: kReleaseMode ? null : 'F19');
+
+  /// Represents the location of the "F20" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f20 = PhysicalKeyboardKey(0x0007006f, debugName: kReleaseMode ? null : 'F20');
+
+  /// Represents the location of the "F21" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f21 = PhysicalKeyboardKey(0x00070070, debugName: kReleaseMode ? null : 'F21');
+
+  /// Represents the location of the "F22" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f22 = PhysicalKeyboardKey(0x00070071, debugName: kReleaseMode ? null : 'F22');
+
+  /// Represents the location of the "F23" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f23 = PhysicalKeyboardKey(0x00070072, debugName: kReleaseMode ? null : 'F23');
+
+  /// Represents the location of the "F24" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey f24 = PhysicalKeyboardKey(0x00070073, debugName: kReleaseMode ? null : 'F24');
+
+  /// Represents the location of the "Open" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey open = PhysicalKeyboardKey(0x00070074, debugName: kReleaseMode ? null : 'Open');
+
+  /// Represents the location of the "Help" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey help = PhysicalKeyboardKey(0x00070075, debugName: kReleaseMode ? null : 'Help');
+
+  /// Represents the location of the "Select" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey select = PhysicalKeyboardKey(0x00070077, debugName: kReleaseMode ? null : 'Select');
+
+  /// Represents the location of the "Again" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey again = PhysicalKeyboardKey(0x00070079, debugName: kReleaseMode ? null : 'Again');
+
+  /// Represents the location of the "Undo" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey undo = PhysicalKeyboardKey(0x0007007a, debugName: kReleaseMode ? null : 'Undo');
+
+  /// Represents the location of the "Cut" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey cut = PhysicalKeyboardKey(0x0007007b, debugName: kReleaseMode ? null : 'Cut');
+
+  /// Represents the location of the "Copy" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey copy = PhysicalKeyboardKey(0x0007007c, debugName: kReleaseMode ? null : 'Copy');
+
+  /// Represents the location of the "Paste" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey paste = PhysicalKeyboardKey(0x0007007d, debugName: kReleaseMode ? null : 'Paste');
+
+  /// Represents the location of the "Find" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey find = PhysicalKeyboardKey(0x0007007e, debugName: kReleaseMode ? null : 'Find');
+
+  /// Represents the location of the "Audio Volume Mute" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey audioVolumeMute = PhysicalKeyboardKey(0x0007007f, debugName: kReleaseMode ? null : 'Audio Volume Mute');
+
+  /// Represents the location of the "Audio Volume Up" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey audioVolumeUp = PhysicalKeyboardKey(0x00070080, debugName: kReleaseMode ? null : 'Audio Volume Up');
+
+  /// Represents the location of the "Audio Volume Down" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey audioVolumeDown = PhysicalKeyboardKey(0x00070081, debugName: kReleaseMode ? null : 'Audio Volume Down');
+
+  /// Represents the location of the "Numpad Comma" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadComma = PhysicalKeyboardKey(0x00070085, debugName: kReleaseMode ? null : 'Numpad Comma');
+
+  /// Represents the location of the "Intl Ro" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey intlRo = PhysicalKeyboardKey(0x00070087, debugName: kReleaseMode ? null : 'Intl Ro');
+
+  /// Represents the location of the "Kana Mode" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey kanaMode = PhysicalKeyboardKey(0x00070088, debugName: kReleaseMode ? null : 'Kana Mode');
+
+  /// Represents the location of the "Intl Yen" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey intlYen = PhysicalKeyboardKey(0x00070089, debugName: kReleaseMode ? null : 'Intl Yen');
+
+  /// Represents the location of the "Convert" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey convert = PhysicalKeyboardKey(0x0007008a, debugName: kReleaseMode ? null : 'Convert');
+
+  /// Represents the location of the "Non Convert" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey nonConvert = PhysicalKeyboardKey(0x0007008b, debugName: kReleaseMode ? null : 'Non Convert');
+
+  /// Represents the location of the "Lang 1" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey lang1 = PhysicalKeyboardKey(0x00070090, debugName: kReleaseMode ? null : 'Lang 1');
+
+  /// Represents the location of the "Lang 2" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey lang2 = PhysicalKeyboardKey(0x00070091, debugName: kReleaseMode ? null : 'Lang 2');
+
+  /// Represents the location of the "Lang 3" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey lang3 = PhysicalKeyboardKey(0x00070092, debugName: kReleaseMode ? null : 'Lang 3');
+
+  /// Represents the location of the "Lang 4" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey lang4 = PhysicalKeyboardKey(0x00070093, debugName: kReleaseMode ? null : 'Lang 4');
+
+  /// Represents the location of the "Lang 5" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey lang5 = PhysicalKeyboardKey(0x00070094, debugName: kReleaseMode ? null : 'Lang 5');
+
+  /// Represents the location of the "Abort" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey abort = PhysicalKeyboardKey(0x0007009b, debugName: kReleaseMode ? null : 'Abort');
+
+  /// Represents the location of the "Props" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey props = PhysicalKeyboardKey(0x000700a3, debugName: kReleaseMode ? null : 'Props');
+
+  /// Represents the location of the "Numpad Paren Left" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadParenLeft = PhysicalKeyboardKey(0x000700b6, debugName: kReleaseMode ? null : 'Numpad Paren Left');
+
+  /// Represents the location of the "Numpad Paren Right" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadParenRight = PhysicalKeyboardKey(0x000700b7, debugName: kReleaseMode ? null : 'Numpad Paren Right');
+
+  /// Represents the location of the "Numpad Backspace" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadBackspace = PhysicalKeyboardKey(0x000700bb, debugName: kReleaseMode ? null : 'Numpad Backspace');
+
+  /// Represents the location of the "Numpad Memory Store" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadMemoryStore = PhysicalKeyboardKey(0x000700d0, debugName: kReleaseMode ? null : 'Numpad Memory Store');
+
+  /// Represents the location of the "Numpad Memory Recall" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadMemoryRecall = PhysicalKeyboardKey(0x000700d1, debugName: kReleaseMode ? null : 'Numpad Memory Recall');
+
+  /// Represents the location of the "Numpad Memory Clear" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadMemoryClear = PhysicalKeyboardKey(0x000700d2, debugName: kReleaseMode ? null : 'Numpad Memory Clear');
+
+  /// Represents the location of the "Numpad Memory Add" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadMemoryAdd = PhysicalKeyboardKey(0x000700d3, debugName: kReleaseMode ? null : 'Numpad Memory Add');
+
+  /// Represents the location of the "Numpad Memory Subtract" key on a
+  /// generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadMemorySubtract = PhysicalKeyboardKey(0x000700d4, debugName: kReleaseMode ? null : 'Numpad Memory Subtract');
+
+  /// Represents the location of the "Numpad Sign Change" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadSignChange = PhysicalKeyboardKey(0x000700d7, debugName: kReleaseMode ? null : 'Numpad Sign Change');
+
+  /// Represents the location of the "Numpad Clear" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadClear = PhysicalKeyboardKey(0x000700d8, debugName: kReleaseMode ? null : 'Numpad Clear');
+
+  /// Represents the location of the "Numpad Clear Entry" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey numpadClearEntry = PhysicalKeyboardKey(0x000700d9, debugName: kReleaseMode ? null : 'Numpad Clear Entry');
+
+  /// Represents the location of the "Control Left" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey controlLeft = PhysicalKeyboardKey(0x000700e0, debugName: kReleaseMode ? null : 'Control Left');
+
+  /// Represents the location of the "Shift Left" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey shiftLeft = PhysicalKeyboardKey(0x000700e1, debugName: kReleaseMode ? null : 'Shift Left');
+
+  /// Represents the location of the "Alt Left" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey altLeft = PhysicalKeyboardKey(0x000700e2, debugName: kReleaseMode ? null : 'Alt Left');
+
+  /// Represents the location of the "Meta Left" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey metaLeft = PhysicalKeyboardKey(0x000700e3, debugName: kReleaseMode ? null : 'Meta Left');
+
+  /// Represents the location of the "Control Right" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey controlRight = PhysicalKeyboardKey(0x000700e4, debugName: kReleaseMode ? null : 'Control Right');
+
+  /// Represents the location of the "Shift Right" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey shiftRight = PhysicalKeyboardKey(0x000700e5, debugName: kReleaseMode ? null : 'Shift Right');
+
+  /// Represents the location of the "Alt Right" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey altRight = PhysicalKeyboardKey(0x000700e6, debugName: kReleaseMode ? null : 'Alt Right');
+
+  /// Represents the location of the "Meta Right" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey metaRight = PhysicalKeyboardKey(0x000700e7, debugName: kReleaseMode ? null : 'Meta Right');
+
+  /// Represents the location of the "Info" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey info = PhysicalKeyboardKey(0x000c0060, debugName: kReleaseMode ? null : 'Info');
+
+  /// Represents the location of the "Closed Caption Toggle" key on a
+  /// generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey closedCaptionToggle = PhysicalKeyboardKey(0x000c0061, debugName: kReleaseMode ? null : 'Closed Caption Toggle');
+
+  /// Represents the location of the "Brightness Up" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey brightnessUp = PhysicalKeyboardKey(0x000c006f, debugName: kReleaseMode ? null : 'Brightness Up');
+
+  /// Represents the location of the "Brightness Down" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey brightnessDown = PhysicalKeyboardKey(0x000c0070, debugName: kReleaseMode ? null : 'Brightness Down');
+
+  /// Represents the location of the "Brightness Toggle" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey brightnessToggle = PhysicalKeyboardKey(0x000c0072, debugName: kReleaseMode ? null : 'Brightness Toggle');
+
+  /// Represents the location of the "Brightness Minimum" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey brightnessMinimum = PhysicalKeyboardKey(0x000c0073, debugName: kReleaseMode ? null : 'Brightness Minimum');
+
+  /// Represents the location of the "Brightness Maximum" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey brightnessMaximum = PhysicalKeyboardKey(0x000c0074, debugName: kReleaseMode ? null : 'Brightness Maximum');
+
+  /// Represents the location of the "Brightness Auto" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey brightnessAuto = PhysicalKeyboardKey(0x000c0075, debugName: kReleaseMode ? null : 'Brightness Auto');
+
+  /// Represents the location of the "Kbd Illum Up" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey kbdIllumUp = PhysicalKeyboardKey(0x000c0079, debugName: kReleaseMode ? null : 'Kbd Illum Up');
+
+  /// Represents the location of the "Kbd Illum Down" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey kbdIllumDown = PhysicalKeyboardKey(0x000c007a, debugName: kReleaseMode ? null : 'Kbd Illum Down');
+
+  /// Represents the location of the "Media Last" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey mediaLast = PhysicalKeyboardKey(0x000c0083, debugName: kReleaseMode ? null : 'Media Last');
+
+  /// Represents the location of the "Launch Phone" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchPhone = PhysicalKeyboardKey(0x000c008c, debugName: kReleaseMode ? null : 'Launch Phone');
+
+  /// Represents the location of the "Program Guide" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey programGuide = PhysicalKeyboardKey(0x000c008d, debugName: kReleaseMode ? null : 'Program Guide');
+
+  /// Represents the location of the "Exit" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey exit = PhysicalKeyboardKey(0x000c0094, debugName: kReleaseMode ? null : 'Exit');
+
+  /// Represents the location of the "Channel Up" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey channelUp = PhysicalKeyboardKey(0x000c009c, debugName: kReleaseMode ? null : 'Channel Up');
+
+  /// Represents the location of the "Channel Down" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey channelDown = PhysicalKeyboardKey(0x000c009d, debugName: kReleaseMode ? null : 'Channel Down');
+
+  /// Represents the location of the "Media Play" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey mediaPlay = PhysicalKeyboardKey(0x000c00b0, debugName: kReleaseMode ? null : 'Media Play');
+
+  /// Represents the location of the "Media Pause" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey mediaPause = PhysicalKeyboardKey(0x000c00b1, debugName: kReleaseMode ? null : 'Media Pause');
+
+  /// Represents the location of the "Media Record" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey mediaRecord = PhysicalKeyboardKey(0x000c00b2, debugName: kReleaseMode ? null : 'Media Record');
+
+  /// Represents the location of the "Media Fast Forward" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey mediaFastForward = PhysicalKeyboardKey(0x000c00b3, debugName: kReleaseMode ? null : 'Media Fast Forward');
+
+  /// Represents the location of the "Media Rewind" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey mediaRewind = PhysicalKeyboardKey(0x000c00b4, debugName: kReleaseMode ? null : 'Media Rewind');
+
+  /// Represents the location of the "Media Track Next" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey mediaTrackNext = PhysicalKeyboardKey(0x000c00b5, debugName: kReleaseMode ? null : 'Media Track Next');
+
+  /// Represents the location of the "Media Track Previous" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey mediaTrackPrevious = PhysicalKeyboardKey(0x000c00b6, debugName: kReleaseMode ? null : 'Media Track Previous');
+
+  /// Represents the location of the "Media Stop" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey mediaStop = PhysicalKeyboardKey(0x000c00b7, debugName: kReleaseMode ? null : 'Media Stop');
+
+  /// Represents the location of the "Eject" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey eject = PhysicalKeyboardKey(0x000c00b8, debugName: kReleaseMode ? null : 'Eject');
+
+  /// Represents the location of the "Media Play Pause" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey mediaPlayPause = PhysicalKeyboardKey(0x000c00cd, debugName: kReleaseMode ? null : 'Media Play Pause');
+
+  /// Represents the location of the "Speech Input Toggle" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey speechInputToggle = PhysicalKeyboardKey(0x000c00cf, debugName: kReleaseMode ? null : 'Speech Input Toggle');
+
+  /// Represents the location of the "Bass Boost" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey bassBoost = PhysicalKeyboardKey(0x000c00e5, debugName: kReleaseMode ? null : 'Bass Boost');
+
+  /// Represents the location of the "Media Select" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey mediaSelect = PhysicalKeyboardKey(0x000c0183, debugName: kReleaseMode ? null : 'Media Select');
+
+  /// Represents the location of the "Launch Word Processor" key on a
+  /// generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchWordProcessor = PhysicalKeyboardKey(0x000c0184, debugName: kReleaseMode ? null : 'Launch Word Processor');
+
+  /// Represents the location of the "Launch Spreadsheet" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchSpreadsheet = PhysicalKeyboardKey(0x000c0186, debugName: kReleaseMode ? null : 'Launch Spreadsheet');
+
+  /// Represents the location of the "Launch Mail" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchMail = PhysicalKeyboardKey(0x000c018a, debugName: kReleaseMode ? null : 'Launch Mail');
+
+  /// Represents the location of the "Launch Contacts" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchContacts = PhysicalKeyboardKey(0x000c018d, debugName: kReleaseMode ? null : 'Launch Contacts');
+
+  /// Represents the location of the "Launch Calendar" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchCalendar = PhysicalKeyboardKey(0x000c018e, debugName: kReleaseMode ? null : 'Launch Calendar');
+
+  /// Represents the location of the "Launch App2" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchApp2 = PhysicalKeyboardKey(0x000c0192, debugName: kReleaseMode ? null : 'Launch App2');
+
+  /// Represents the location of the "Launch App1" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchApp1 = PhysicalKeyboardKey(0x000c0194, debugName: kReleaseMode ? null : 'Launch App1');
+
+  /// Represents the location of the "Launch Internet Browser" key on a
+  /// generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchInternetBrowser = PhysicalKeyboardKey(0x000c0196, debugName: kReleaseMode ? null : 'Launch Internet Browser');
+
+  /// Represents the location of the "Log Off" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey logOff = PhysicalKeyboardKey(0x000c019c, debugName: kReleaseMode ? null : 'Log Off');
+
+  /// Represents the location of the "Lock Screen" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey lockScreen = PhysicalKeyboardKey(0x000c019e, debugName: kReleaseMode ? null : 'Lock Screen');
+
+  /// Represents the location of the "Launch Control Panel" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchControlPanel = PhysicalKeyboardKey(0x000c019f, debugName: kReleaseMode ? null : 'Launch Control Panel');
+
+  /// Represents the location of the "Select Task" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey selectTask = PhysicalKeyboardKey(0x000c01a2, debugName: kReleaseMode ? null : 'Select Task');
+
+  /// Represents the location of the "Launch Documents" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchDocuments = PhysicalKeyboardKey(0x000c01a7, debugName: kReleaseMode ? null : 'Launch Documents');
+
+  /// Represents the location of the "Spell Check" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey spellCheck = PhysicalKeyboardKey(0x000c01ab, debugName: kReleaseMode ? null : 'Spell Check');
+
+  /// Represents the location of the "Launch Keyboard Layout" key on a
+  /// generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchKeyboardLayout = PhysicalKeyboardKey(0x000c01ae, debugName: kReleaseMode ? null : 'Launch Keyboard Layout');
+
+  /// Represents the location of the "Launch Screen Saver" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchScreenSaver = PhysicalKeyboardKey(0x000c01b1, debugName: kReleaseMode ? null : 'Launch Screen Saver');
+
+  /// Represents the location of the "Launch Assistant" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchAssistant = PhysicalKeyboardKey(0x000c01cb, debugName: kReleaseMode ? null : 'Launch Assistant');
+
+  /// Represents the location of the "Launch Audio Browser" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey launchAudioBrowser = PhysicalKeyboardKey(0x000c01b7, debugName: kReleaseMode ? null : 'Launch Audio Browser');
+
+  /// Represents the location of the "New Key" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey newKey = PhysicalKeyboardKey(0x000c0201, debugName: kReleaseMode ? null : 'New Key');
+
+  /// Represents the location of the "Close" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey close = PhysicalKeyboardKey(0x000c0203, debugName: kReleaseMode ? null : 'Close');
+
+  /// Represents the location of the "Save" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey save = PhysicalKeyboardKey(0x000c0207, debugName: kReleaseMode ? null : 'Save');
+
+  /// Represents the location of the "Print" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey print = PhysicalKeyboardKey(0x000c0208, debugName: kReleaseMode ? null : 'Print');
+
+  /// Represents the location of the "Browser Search" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey browserSearch = PhysicalKeyboardKey(0x000c0221, debugName: kReleaseMode ? null : 'Browser Search');
+
+  /// Represents the location of the "Browser Home" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey browserHome = PhysicalKeyboardKey(0x000c0223, debugName: kReleaseMode ? null : 'Browser Home');
+
+  /// Represents the location of the "Browser Back" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey browserBack = PhysicalKeyboardKey(0x000c0224, debugName: kReleaseMode ? null : 'Browser Back');
+
+  /// Represents the location of the "Browser Forward" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey browserForward = PhysicalKeyboardKey(0x000c0225, debugName: kReleaseMode ? null : 'Browser Forward');
+
+  /// Represents the location of the "Browser Stop" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey browserStop = PhysicalKeyboardKey(0x000c0226, debugName: kReleaseMode ? null : 'Browser Stop');
+
+  /// Represents the location of the "Browser Refresh" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey browserRefresh = PhysicalKeyboardKey(0x000c0227, debugName: kReleaseMode ? null : 'Browser Refresh');
+
+  /// Represents the location of the "Browser Favorites" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey browserFavorites = PhysicalKeyboardKey(0x000c022a, debugName: kReleaseMode ? null : 'Browser Favorites');
+
+  /// Represents the location of the "Zoom In" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey zoomIn = PhysicalKeyboardKey(0x000c022d, debugName: kReleaseMode ? null : 'Zoom In');
+
+  /// Represents the location of the "Zoom Out" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey zoomOut = PhysicalKeyboardKey(0x000c022e, debugName: kReleaseMode ? null : 'Zoom Out');
+
+  /// Represents the location of the "Zoom Toggle" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey zoomToggle = PhysicalKeyboardKey(0x000c0232, debugName: kReleaseMode ? null : 'Zoom Toggle');
+
+  /// Represents the location of the "Redo" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey redo = PhysicalKeyboardKey(0x000c0279, debugName: kReleaseMode ? null : 'Redo');
+
+  /// Represents the location of the "Mail Reply" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey mailReply = PhysicalKeyboardKey(0x000c0289, debugName: kReleaseMode ? null : 'Mail Reply');
+
+  /// Represents the location of the "Mail Forward" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey mailForward = PhysicalKeyboardKey(0x000c028b, debugName: kReleaseMode ? null : 'Mail Forward');
+
+  /// Represents the location of the "Mail Send" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey mailSend = PhysicalKeyboardKey(0x000c028c, debugName: kReleaseMode ? null : 'Mail Send');
+
+  /// Represents the location of the "Keyboard Layout Select" key on a
+  /// generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey keyboardLayoutSelect = PhysicalKeyboardKey(0x000c029d, debugName: kReleaseMode ? null : 'Keyboard Layout Select');
+
+  /// Represents the location of the "Show All Windows" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey showAllWindows = PhysicalKeyboardKey(0x000c029f, debugName: kReleaseMode ? null : 'Show All Windows');
+
+  /// Represents the location of the "Game Button 1" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton1 = PhysicalKeyboardKey(0x0005ff01, debugName: kReleaseMode ? null : 'Game Button 1');
+
+  /// Represents the location of the "Game Button 2" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton2 = PhysicalKeyboardKey(0x0005ff02, debugName: kReleaseMode ? null : 'Game Button 2');
+
+  /// Represents the location of the "Game Button 3" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton3 = PhysicalKeyboardKey(0x0005ff03, debugName: kReleaseMode ? null : 'Game Button 3');
+
+  /// Represents the location of the "Game Button 4" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton4 = PhysicalKeyboardKey(0x0005ff04, debugName: kReleaseMode ? null : 'Game Button 4');
+
+  /// Represents the location of the "Game Button 5" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton5 = PhysicalKeyboardKey(0x0005ff05, debugName: kReleaseMode ? null : 'Game Button 5');
+
+  /// Represents the location of the "Game Button 6" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton6 = PhysicalKeyboardKey(0x0005ff06, debugName: kReleaseMode ? null : 'Game Button 6');
+
+  /// Represents the location of the "Game Button 7" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton7 = PhysicalKeyboardKey(0x0005ff07, debugName: kReleaseMode ? null : 'Game Button 7');
+
+  /// Represents the location of the "Game Button 8" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton8 = PhysicalKeyboardKey(0x0005ff08, debugName: kReleaseMode ? null : 'Game Button 8');
+
+  /// Represents the location of the "Game Button 9" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton9 = PhysicalKeyboardKey(0x0005ff09, debugName: kReleaseMode ? null : 'Game Button 9');
+
+  /// Represents the location of the "Game Button 10" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton10 = PhysicalKeyboardKey(0x0005ff0a, debugName: kReleaseMode ? null : 'Game Button 10');
+
+  /// Represents the location of the "Game Button 11" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton11 = PhysicalKeyboardKey(0x0005ff0b, debugName: kReleaseMode ? null : 'Game Button 11');
+
+  /// Represents the location of the "Game Button 12" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton12 = PhysicalKeyboardKey(0x0005ff0c, debugName: kReleaseMode ? null : 'Game Button 12');
+
+  /// Represents the location of the "Game Button 13" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton13 = PhysicalKeyboardKey(0x0005ff0d, debugName: kReleaseMode ? null : 'Game Button 13');
+
+  /// Represents the location of the "Game Button 14" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton14 = PhysicalKeyboardKey(0x0005ff0e, debugName: kReleaseMode ? null : 'Game Button 14');
+
+  /// Represents the location of the "Game Button 15" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton15 = PhysicalKeyboardKey(0x0005ff0f, debugName: kReleaseMode ? null : 'Game Button 15');
+
+  /// Represents the location of the "Game Button 16" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButton16 = PhysicalKeyboardKey(0x0005ff10, debugName: kReleaseMode ? null : 'Game Button 16');
+
+  /// Represents the location of the "Game Button A" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonA = PhysicalKeyboardKey(0x0005ff11, debugName: kReleaseMode ? null : 'Game Button A');
+
+  /// Represents the location of the "Game Button B" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonB = PhysicalKeyboardKey(0x0005ff12, debugName: kReleaseMode ? null : 'Game Button B');
+
+  /// Represents the location of the "Game Button C" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonC = PhysicalKeyboardKey(0x0005ff13, debugName: kReleaseMode ? null : 'Game Button C');
+
+  /// Represents the location of the "Game Button Left 1" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonLeft1 = PhysicalKeyboardKey(0x0005ff14, debugName: kReleaseMode ? null : 'Game Button Left 1');
+
+  /// Represents the location of the "Game Button Left 2" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonLeft2 = PhysicalKeyboardKey(0x0005ff15, debugName: kReleaseMode ? null : 'Game Button Left 2');
+
+  /// Represents the location of the "Game Button Mode" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonMode = PhysicalKeyboardKey(0x0005ff16, debugName: kReleaseMode ? null : 'Game Button Mode');
+
+  /// Represents the location of the "Game Button Right 1" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonRight1 = PhysicalKeyboardKey(0x0005ff17, debugName: kReleaseMode ? null : 'Game Button Right 1');
+
+  /// Represents the location of the "Game Button Right 2" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonRight2 = PhysicalKeyboardKey(0x0005ff18, debugName: kReleaseMode ? null : 'Game Button Right 2');
+
+  /// Represents the location of the "Game Button Select" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonSelect = PhysicalKeyboardKey(0x0005ff19, debugName: kReleaseMode ? null : 'Game Button Select');
+
+  /// Represents the location of the "Game Button Start" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonStart = PhysicalKeyboardKey(0x0005ff1a, debugName: kReleaseMode ? null : 'Game Button Start');
+
+  /// Represents the location of the "Game Button Thumb Left" key on a
+  /// generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonThumbLeft = PhysicalKeyboardKey(0x0005ff1b, debugName: kReleaseMode ? null : 'Game Button Thumb Left');
+
+  /// Represents the location of the "Game Button Thumb Right" key on a
+  /// generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonThumbRight = PhysicalKeyboardKey(0x0005ff1c, debugName: kReleaseMode ? null : 'Game Button Thumb Right');
+
+  /// Represents the location of the "Game Button X" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonX = PhysicalKeyboardKey(0x0005ff1d, debugName: kReleaseMode ? null : 'Game Button X');
+
+  /// Represents the location of the "Game Button Y" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonY = PhysicalKeyboardKey(0x0005ff1e, debugName: kReleaseMode ? null : 'Game Button Y');
+
+  /// Represents the location of the "Game Button Z" key on a generalized
+  /// keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey gameButtonZ = PhysicalKeyboardKey(0x0005ff1f, debugName: kReleaseMode ? null : 'Game Button Z');
+
+  /// Represents the location of the "Fn" key on a generalized keyboard.
+  ///
+  /// See the function [RawKeyEvent.physicalKey] for more information.
+  static const PhysicalKeyboardKey fn = PhysicalKeyboardKey(0x00000012, debugName: kReleaseMode ? null : 'Fn');
+
+  // A list of all the predefined constant PhysicalKeyboardKeys so that they
+  // can be searched.
+  static const Map<int, PhysicalKeyboardKey> _knownPhysicalKeys = <int, PhysicalKeyboardKey>{
+    0x00000000: none,
+    0x00000010: hyper,
+    0x00000011: superKey,
+    0x00000013: fnLock,
+    0x00000014: suspend,
+    0x00000015: resume,
+    0x00000016: turbo,
+    0x00000017: privacyScreenToggle,
+    0x00010082: sleep,
+    0x00010083: wakeUp,
+    0x000100b5: displayToggleIntExt,
+    0x00070000: usbReserved,
+    0x00070001: usbErrorRollOver,
+    0x00070002: usbPostFail,
+    0x00070003: usbErrorUndefined,
+    0x00070004: keyA,
+    0x00070005: keyB,
+    0x00070006: keyC,
+    0x00070007: keyD,
+    0x00070008: keyE,
+    0x00070009: keyF,
+    0x0007000a: keyG,
+    0x0007000b: keyH,
+    0x0007000c: keyI,
+    0x0007000d: keyJ,
+    0x0007000e: keyK,
+    0x0007000f: keyL,
+    0x00070010: keyM,
+    0x00070011: keyN,
+    0x00070012: keyO,
+    0x00070013: keyP,
+    0x00070014: keyQ,
+    0x00070015: keyR,
+    0x00070016: keyS,
+    0x00070017: keyT,
+    0x00070018: keyU,
+    0x00070019: keyV,
+    0x0007001a: keyW,
+    0x0007001b: keyX,
+    0x0007001c: keyY,
+    0x0007001d: keyZ,
+    0x0007001e: digit1,
+    0x0007001f: digit2,
+    0x00070020: digit3,
+    0x00070021: digit4,
+    0x00070022: digit5,
+    0x00070023: digit6,
+    0x00070024: digit7,
+    0x00070025: digit8,
+    0x00070026: digit9,
+    0x00070027: digit0,
+    0x00070028: enter,
+    0x00070029: escape,
+    0x0007002a: backspace,
+    0x0007002b: tab,
+    0x0007002c: space,
+    0x0007002d: minus,
+    0x0007002e: equal,
+    0x0007002f: bracketLeft,
+    0x00070030: bracketRight,
+    0x00070031: backslash,
+    0x00070033: semicolon,
+    0x00070034: quote,
+    0x00070035: backquote,
+    0x00070036: comma,
+    0x00070037: period,
+    0x00070038: slash,
+    0x00070039: capsLock,
+    0x0007003a: f1,
+    0x0007003b: f2,
+    0x0007003c: f3,
+    0x0007003d: f4,
+    0x0007003e: f5,
+    0x0007003f: f6,
+    0x00070040: f7,
+    0x00070041: f8,
+    0x00070042: f9,
+    0x00070043: f10,
+    0x00070044: f11,
+    0x00070045: f12,
+    0x00070046: printScreen,
+    0x00070047: scrollLock,
+    0x00070048: pause,
+    0x00070049: insert,
+    0x0007004a: home,
+    0x0007004b: pageUp,
+    0x0007004c: delete,
+    0x0007004d: end,
+    0x0007004e: pageDown,
+    0x0007004f: arrowRight,
+    0x00070050: arrowLeft,
+    0x00070051: arrowDown,
+    0x00070052: arrowUp,
+    0x00070053: numLock,
+    0x00070054: numpadDivide,
+    0x00070055: numpadMultiply,
+    0x00070056: numpadSubtract,
+    0x00070057: numpadAdd,
+    0x00070058: numpadEnter,
+    0x00070059: numpad1,
+    0x0007005a: numpad2,
+    0x0007005b: numpad3,
+    0x0007005c: numpad4,
+    0x0007005d: numpad5,
+    0x0007005e: numpad6,
+    0x0007005f: numpad7,
+    0x00070060: numpad8,
+    0x00070061: numpad9,
+    0x00070062: numpad0,
+    0x00070063: numpadDecimal,
+    0x00070064: intlBackslash,
+    0x00070065: contextMenu,
+    0x00070066: power,
+    0x00070067: numpadEqual,
+    0x00070068: f13,
+    0x00070069: f14,
+    0x0007006a: f15,
+    0x0007006b: f16,
+    0x0007006c: f17,
+    0x0007006d: f18,
+    0x0007006e: f19,
+    0x0007006f: f20,
+    0x00070070: f21,
+    0x00070071: f22,
+    0x00070072: f23,
+    0x00070073: f24,
+    0x00070074: open,
+    0x00070075: help,
+    0x00070077: select,
+    0x00070079: again,
+    0x0007007a: undo,
+    0x0007007b: cut,
+    0x0007007c: copy,
+    0x0007007d: paste,
+    0x0007007e: find,
+    0x0007007f: audioVolumeMute,
+    0x00070080: audioVolumeUp,
+    0x00070081: audioVolumeDown,
+    0x00070085: numpadComma,
+    0x00070087: intlRo,
+    0x00070088: kanaMode,
+    0x00070089: intlYen,
+    0x0007008a: convert,
+    0x0007008b: nonConvert,
+    0x00070090: lang1,
+    0x00070091: lang2,
+    0x00070092: lang3,
+    0x00070093: lang4,
+    0x00070094: lang5,
+    0x0007009b: abort,
+    0x000700a3: props,
+    0x000700b6: numpadParenLeft,
+    0x000700b7: numpadParenRight,
+    0x000700bb: numpadBackspace,
+    0x000700d0: numpadMemoryStore,
+    0x000700d1: numpadMemoryRecall,
+    0x000700d2: numpadMemoryClear,
+    0x000700d3: numpadMemoryAdd,
+    0x000700d4: numpadMemorySubtract,
+    0x000700d7: numpadSignChange,
+    0x000700d8: numpadClear,
+    0x000700d9: numpadClearEntry,
+    0x000700e0: controlLeft,
+    0x000700e1: shiftLeft,
+    0x000700e2: altLeft,
+    0x000700e3: metaLeft,
+    0x000700e4: controlRight,
+    0x000700e5: shiftRight,
+    0x000700e6: altRight,
+    0x000700e7: metaRight,
+    0x000c0060: info,
+    0x000c0061: closedCaptionToggle,
+    0x000c006f: brightnessUp,
+    0x000c0070: brightnessDown,
+    0x000c0072: brightnessToggle,
+    0x000c0073: brightnessMinimum,
+    0x000c0074: brightnessMaximum,
+    0x000c0075: brightnessAuto,
+    0x000c0079: kbdIllumUp,
+    0x000c007a: kbdIllumDown,
+    0x000c0083: mediaLast,
+    0x000c008c: launchPhone,
+    0x000c008d: programGuide,
+    0x000c0094: exit,
+    0x000c009c: channelUp,
+    0x000c009d: channelDown,
+    0x000c00b0: mediaPlay,
+    0x000c00b1: mediaPause,
+    0x000c00b2: mediaRecord,
+    0x000c00b3: mediaFastForward,
+    0x000c00b4: mediaRewind,
+    0x000c00b5: mediaTrackNext,
+    0x000c00b6: mediaTrackPrevious,
+    0x000c00b7: mediaStop,
+    0x000c00b8: eject,
+    0x000c00cd: mediaPlayPause,
+    0x000c00cf: speechInputToggle,
+    0x000c00e5: bassBoost,
+    0x000c0183: mediaSelect,
+    0x000c0184: launchWordProcessor,
+    0x000c0186: launchSpreadsheet,
+    0x000c018a: launchMail,
+    0x000c018d: launchContacts,
+    0x000c018e: launchCalendar,
+    0x000c0192: launchApp2,
+    0x000c0194: launchApp1,
+    0x000c0196: launchInternetBrowser,
+    0x000c019c: logOff,
+    0x000c019e: lockScreen,
+    0x000c019f: launchControlPanel,
+    0x000c01a2: selectTask,
+    0x000c01a7: launchDocuments,
+    0x000c01ab: spellCheck,
+    0x000c01ae: launchKeyboardLayout,
+    0x000c01b1: launchScreenSaver,
+    0x000c01cb: launchAssistant,
+    0x000c01b7: launchAudioBrowser,
+    0x000c0201: newKey,
+    0x000c0203: close,
+    0x000c0207: save,
+    0x000c0208: print,
+    0x000c0221: browserSearch,
+    0x000c0223: browserHome,
+    0x000c0224: browserBack,
+    0x000c0225: browserForward,
+    0x000c0226: browserStop,
+    0x000c0227: browserRefresh,
+    0x000c022a: browserFavorites,
+    0x000c022d: zoomIn,
+    0x000c022e: zoomOut,
+    0x000c0232: zoomToggle,
+    0x000c0279: redo,
+    0x000c0289: mailReply,
+    0x000c028b: mailForward,
+    0x000c028c: mailSend,
+    0x000c029d: keyboardLayoutSelect,
+    0x000c029f: showAllWindows,
+    0x0005ff01: gameButton1,
+    0x0005ff02: gameButton2,
+    0x0005ff03: gameButton3,
+    0x0005ff04: gameButton4,
+    0x0005ff05: gameButton5,
+    0x0005ff06: gameButton6,
+    0x0005ff07: gameButton7,
+    0x0005ff08: gameButton8,
+    0x0005ff09: gameButton9,
+    0x0005ff0a: gameButton10,
+    0x0005ff0b: gameButton11,
+    0x0005ff0c: gameButton12,
+    0x0005ff0d: gameButton13,
+    0x0005ff0e: gameButton14,
+    0x0005ff0f: gameButton15,
+    0x0005ff10: gameButton16,
+    0x0005ff11: gameButtonA,
+    0x0005ff12: gameButtonB,
+    0x0005ff13: gameButtonC,
+    0x0005ff14: gameButtonLeft1,
+    0x0005ff15: gameButtonLeft2,
+    0x0005ff16: gameButtonMode,
+    0x0005ff17: gameButtonRight1,
+    0x0005ff18: gameButtonRight2,
+    0x0005ff19: gameButtonSelect,
+    0x0005ff1a: gameButtonStart,
+    0x0005ff1b: gameButtonThumbLeft,
+    0x0005ff1c: gameButtonThumbRight,
+    0x0005ff1d: gameButtonX,
+    0x0005ff1e: gameButtonY,
+    0x0005ff1f: gameButtonZ,
+    0x00000012: fn,
+  };
+}
diff --git a/lib/src/services/keyboard_maps.dart b/lib/src/services/keyboard_maps.dart
new file mode 100644
index 0000000..af26748
--- /dev/null
+++ b/lib/src/services/keyboard_maps.dart
@@ -0,0 +1,2746 @@
+// Copyright 2014 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.
+
+// DO NOT EDIT -- DO NOT EDIT -- DO NOT EDIT
+// This file is generated by dev/tools/gen_keycodes/bin/gen_keycodes.dart and
+// should not be edited directly.
+//
+// Edit the template dev/tools/gen_keycodes/data/keyboard_maps.tmpl instead.
+// See dev/tools/gen_keycodes/README.md for more information.
+
+import 'keyboard_key.dart';
+
+/// Maps Android-specific key codes to the matching [LogicalKeyboardKey].
+const Map<int, LogicalKeyboardKey> kAndroidToLogicalKey = <int, LogicalKeyboardKey>{
+  0: LogicalKeyboardKey.none,
+  223: LogicalKeyboardKey.sleep,
+  224: LogicalKeyboardKey.wakeUp,
+  29: LogicalKeyboardKey.keyA,
+  30: LogicalKeyboardKey.keyB,
+  31: LogicalKeyboardKey.keyC,
+  32: LogicalKeyboardKey.keyD,
+  33: LogicalKeyboardKey.keyE,
+  34: LogicalKeyboardKey.keyF,
+  35: LogicalKeyboardKey.keyG,
+  36: LogicalKeyboardKey.keyH,
+  37: LogicalKeyboardKey.keyI,
+  38: LogicalKeyboardKey.keyJ,
+  39: LogicalKeyboardKey.keyK,
+  40: LogicalKeyboardKey.keyL,
+  41: LogicalKeyboardKey.keyM,
+  42: LogicalKeyboardKey.keyN,
+  43: LogicalKeyboardKey.keyO,
+  44: LogicalKeyboardKey.keyP,
+  45: LogicalKeyboardKey.keyQ,
+  46: LogicalKeyboardKey.keyR,
+  47: LogicalKeyboardKey.keyS,
+  48: LogicalKeyboardKey.keyT,
+  49: LogicalKeyboardKey.keyU,
+  50: LogicalKeyboardKey.keyV,
+  51: LogicalKeyboardKey.keyW,
+  52: LogicalKeyboardKey.keyX,
+  53: LogicalKeyboardKey.keyY,
+  54: LogicalKeyboardKey.keyZ,
+  8: LogicalKeyboardKey.digit1,
+  9: LogicalKeyboardKey.digit2,
+  10: LogicalKeyboardKey.digit3,
+  11: LogicalKeyboardKey.digit4,
+  12: LogicalKeyboardKey.digit5,
+  13: LogicalKeyboardKey.digit6,
+  14: LogicalKeyboardKey.digit7,
+  15: LogicalKeyboardKey.digit8,
+  16: LogicalKeyboardKey.digit9,
+  7: LogicalKeyboardKey.digit0,
+  66: LogicalKeyboardKey.enter,
+  111: LogicalKeyboardKey.escape,
+  67: LogicalKeyboardKey.backspace,
+  61: LogicalKeyboardKey.tab,
+  62: LogicalKeyboardKey.space,
+  69: LogicalKeyboardKey.minus,
+  70: LogicalKeyboardKey.equal,
+  71: LogicalKeyboardKey.bracketLeft,
+  72: LogicalKeyboardKey.bracketRight,
+  73: LogicalKeyboardKey.backslash,
+  74: LogicalKeyboardKey.semicolon,
+  75: LogicalKeyboardKey.quote,
+  68: LogicalKeyboardKey.backquote,
+  55: LogicalKeyboardKey.comma,
+  56: LogicalKeyboardKey.period,
+  76: LogicalKeyboardKey.slash,
+  115: LogicalKeyboardKey.capsLock,
+  131: LogicalKeyboardKey.f1,
+  132: LogicalKeyboardKey.f2,
+  133: LogicalKeyboardKey.f3,
+  134: LogicalKeyboardKey.f4,
+  135: LogicalKeyboardKey.f5,
+  136: LogicalKeyboardKey.f6,
+  137: LogicalKeyboardKey.f7,
+  138: LogicalKeyboardKey.f8,
+  139: LogicalKeyboardKey.f9,
+  140: LogicalKeyboardKey.f10,
+  141: LogicalKeyboardKey.f11,
+  142: LogicalKeyboardKey.f12,
+  120: LogicalKeyboardKey.printScreen,
+  116: LogicalKeyboardKey.scrollLock,
+  121: LogicalKeyboardKey.pause,
+  124: LogicalKeyboardKey.insert,
+  122: LogicalKeyboardKey.home,
+  92: LogicalKeyboardKey.pageUp,
+  112: LogicalKeyboardKey.delete,
+  123: LogicalKeyboardKey.end,
+  93: LogicalKeyboardKey.pageDown,
+  22: LogicalKeyboardKey.arrowRight,
+  21: LogicalKeyboardKey.arrowLeft,
+  20: LogicalKeyboardKey.arrowDown,
+  19: LogicalKeyboardKey.arrowUp,
+  143: LogicalKeyboardKey.numLock,
+  154: LogicalKeyboardKey.numpadDivide,
+  155: LogicalKeyboardKey.numpadMultiply,
+  156: LogicalKeyboardKey.numpadSubtract,
+  157: LogicalKeyboardKey.numpadAdd,
+  160: LogicalKeyboardKey.numpadEnter,
+  145: LogicalKeyboardKey.numpad1,
+  146: LogicalKeyboardKey.numpad2,
+  147: LogicalKeyboardKey.numpad3,
+  148: LogicalKeyboardKey.numpad4,
+  149: LogicalKeyboardKey.numpad5,
+  150: LogicalKeyboardKey.numpad6,
+  151: LogicalKeyboardKey.numpad7,
+  152: LogicalKeyboardKey.numpad8,
+  153: LogicalKeyboardKey.numpad9,
+  144: LogicalKeyboardKey.numpad0,
+  158: LogicalKeyboardKey.numpadDecimal,
+  82: LogicalKeyboardKey.contextMenu,
+  26: LogicalKeyboardKey.power,
+  161: LogicalKeyboardKey.numpadEqual,
+  259: LogicalKeyboardKey.help,
+  23: LogicalKeyboardKey.select,
+  277: LogicalKeyboardKey.cut,
+  278: LogicalKeyboardKey.copy,
+  279: LogicalKeyboardKey.paste,
+  164: LogicalKeyboardKey.audioVolumeMute,
+  24: LogicalKeyboardKey.audioVolumeUp,
+  25: LogicalKeyboardKey.audioVolumeDown,
+  159: LogicalKeyboardKey.numpadComma,
+  214: LogicalKeyboardKey.convert,
+  213: LogicalKeyboardKey.nonConvert,
+  162: LogicalKeyboardKey.numpadParenLeft,
+  163: LogicalKeyboardKey.numpadParenRight,
+  113: LogicalKeyboardKey.controlLeft,
+  59: LogicalKeyboardKey.shiftLeft,
+  57: LogicalKeyboardKey.altLeft,
+  117: LogicalKeyboardKey.metaLeft,
+  114: LogicalKeyboardKey.controlRight,
+  60: LogicalKeyboardKey.shiftRight,
+  58: LogicalKeyboardKey.altRight,
+  118: LogicalKeyboardKey.metaRight,
+  165: LogicalKeyboardKey.info,
+  175: LogicalKeyboardKey.closedCaptionToggle,
+  221: LogicalKeyboardKey.brightnessUp,
+  220: LogicalKeyboardKey.brightnessDown,
+  229: LogicalKeyboardKey.mediaLast,
+  166: LogicalKeyboardKey.channelUp,
+  167: LogicalKeyboardKey.channelDown,
+  126: LogicalKeyboardKey.mediaPlay,
+  127: LogicalKeyboardKey.mediaPause,
+  130: LogicalKeyboardKey.mediaRecord,
+  90: LogicalKeyboardKey.mediaFastForward,
+  89: LogicalKeyboardKey.mediaRewind,
+  87: LogicalKeyboardKey.mediaTrackNext,
+  88: LogicalKeyboardKey.mediaTrackPrevious,
+  86: LogicalKeyboardKey.mediaStop,
+  129: LogicalKeyboardKey.eject,
+  85: LogicalKeyboardKey.mediaPlayPause,
+  65: LogicalKeyboardKey.launchMail,
+  207: LogicalKeyboardKey.launchContacts,
+  208: LogicalKeyboardKey.launchCalendar,
+  219: LogicalKeyboardKey.launchAssistant,
+  128: LogicalKeyboardKey.close,
+  84: LogicalKeyboardKey.browserSearch,
+  125: LogicalKeyboardKey.browserForward,
+  174: LogicalKeyboardKey.browserFavorites,
+  168: LogicalKeyboardKey.zoomIn,
+  169: LogicalKeyboardKey.zoomOut,
+  255: LogicalKeyboardKey.zoomToggle,
+  188: LogicalKeyboardKey.gameButton1,
+  189: LogicalKeyboardKey.gameButton2,
+  190: LogicalKeyboardKey.gameButton3,
+  191: LogicalKeyboardKey.gameButton4,
+  192: LogicalKeyboardKey.gameButton5,
+  193: LogicalKeyboardKey.gameButton6,
+  194: LogicalKeyboardKey.gameButton7,
+  195: LogicalKeyboardKey.gameButton8,
+  196: LogicalKeyboardKey.gameButton9,
+  197: LogicalKeyboardKey.gameButton10,
+  198: LogicalKeyboardKey.gameButton11,
+  199: LogicalKeyboardKey.gameButton12,
+  200: LogicalKeyboardKey.gameButton13,
+  201: LogicalKeyboardKey.gameButton14,
+  202: LogicalKeyboardKey.gameButton15,
+  203: LogicalKeyboardKey.gameButton16,
+  96: LogicalKeyboardKey.gameButtonA,
+  97: LogicalKeyboardKey.gameButtonB,
+  98: LogicalKeyboardKey.gameButtonC,
+  102: LogicalKeyboardKey.gameButtonLeft1,
+  104: LogicalKeyboardKey.gameButtonLeft2,
+  110: LogicalKeyboardKey.gameButtonMode,
+  103: LogicalKeyboardKey.gameButtonRight1,
+  105: LogicalKeyboardKey.gameButtonRight2,
+  109: LogicalKeyboardKey.gameButtonSelect,
+  108: LogicalKeyboardKey.gameButtonStart,
+  106: LogicalKeyboardKey.gameButtonThumbLeft,
+  107: LogicalKeyboardKey.gameButtonThumbRight,
+  99: LogicalKeyboardKey.gameButtonX,
+  100: LogicalKeyboardKey.gameButtonY,
+  101: LogicalKeyboardKey.gameButtonZ,
+  119: LogicalKeyboardKey.fn,
+};
+
+/// Maps Android-specific scan codes to the matching [PhysicalKeyboardKey].
+const Map<int, PhysicalKeyboardKey> kAndroidToPhysicalKey = <int, PhysicalKeyboardKey>{
+  205: PhysicalKeyboardKey.suspend,
+  142: PhysicalKeyboardKey.sleep,
+  143: PhysicalKeyboardKey.wakeUp,
+  30: PhysicalKeyboardKey.keyA,
+  48: PhysicalKeyboardKey.keyB,
+  46: PhysicalKeyboardKey.keyC,
+  32: PhysicalKeyboardKey.keyD,
+  18: PhysicalKeyboardKey.keyE,
+  33: PhysicalKeyboardKey.keyF,
+  34: PhysicalKeyboardKey.keyG,
+  35: PhysicalKeyboardKey.keyH,
+  23: PhysicalKeyboardKey.keyI,
+  36: PhysicalKeyboardKey.keyJ,
+  37: PhysicalKeyboardKey.keyK,
+  38: PhysicalKeyboardKey.keyL,
+  50: PhysicalKeyboardKey.keyM,
+  49: PhysicalKeyboardKey.keyN,
+  24: PhysicalKeyboardKey.keyO,
+  25: PhysicalKeyboardKey.keyP,
+  16: PhysicalKeyboardKey.keyQ,
+  19: PhysicalKeyboardKey.keyR,
+  31: PhysicalKeyboardKey.keyS,
+  20: PhysicalKeyboardKey.keyT,
+  22: PhysicalKeyboardKey.keyU,
+  47: PhysicalKeyboardKey.keyV,
+  17: PhysicalKeyboardKey.keyW,
+  45: PhysicalKeyboardKey.keyX,
+  21: PhysicalKeyboardKey.keyY,
+  44: PhysicalKeyboardKey.keyZ,
+  2: PhysicalKeyboardKey.digit1,
+  3: PhysicalKeyboardKey.digit2,
+  4: PhysicalKeyboardKey.digit3,
+  5: PhysicalKeyboardKey.digit4,
+  6: PhysicalKeyboardKey.digit5,
+  7: PhysicalKeyboardKey.digit6,
+  8: PhysicalKeyboardKey.digit7,
+  9: PhysicalKeyboardKey.digit8,
+  10: PhysicalKeyboardKey.digit9,
+  11: PhysicalKeyboardKey.digit0,
+  28: PhysicalKeyboardKey.enter,
+  1: PhysicalKeyboardKey.escape,
+  14: PhysicalKeyboardKey.backspace,
+  15: PhysicalKeyboardKey.tab,
+  57: PhysicalKeyboardKey.space,
+  12: PhysicalKeyboardKey.minus,
+  13: PhysicalKeyboardKey.equal,
+  26: PhysicalKeyboardKey.bracketLeft,
+  27: PhysicalKeyboardKey.bracketRight,
+  43: PhysicalKeyboardKey.backslash,
+  86: PhysicalKeyboardKey.backslash,
+  39: PhysicalKeyboardKey.semicolon,
+  40: PhysicalKeyboardKey.quote,
+  41: PhysicalKeyboardKey.backquote,
+  51: PhysicalKeyboardKey.comma,
+  52: PhysicalKeyboardKey.period,
+  53: PhysicalKeyboardKey.slash,
+  58: PhysicalKeyboardKey.capsLock,
+  59: PhysicalKeyboardKey.f1,
+  60: PhysicalKeyboardKey.f2,
+  61: PhysicalKeyboardKey.f3,
+  62: PhysicalKeyboardKey.f4,
+  63: PhysicalKeyboardKey.f5,
+  64: PhysicalKeyboardKey.f6,
+  65: PhysicalKeyboardKey.f7,
+  66: PhysicalKeyboardKey.f8,
+  67: PhysicalKeyboardKey.f9,
+  68: PhysicalKeyboardKey.f10,
+  87: PhysicalKeyboardKey.f11,
+  88: PhysicalKeyboardKey.f12,
+  99: PhysicalKeyboardKey.printScreen,
+  70: PhysicalKeyboardKey.scrollLock,
+  119: PhysicalKeyboardKey.pause,
+  411: PhysicalKeyboardKey.pause,
+  110: PhysicalKeyboardKey.insert,
+  102: PhysicalKeyboardKey.home,
+  104: PhysicalKeyboardKey.pageUp,
+  177: PhysicalKeyboardKey.pageUp,
+  111: PhysicalKeyboardKey.delete,
+  107: PhysicalKeyboardKey.end,
+  109: PhysicalKeyboardKey.pageDown,
+  178: PhysicalKeyboardKey.pageDown,
+  106: PhysicalKeyboardKey.arrowRight,
+  105: PhysicalKeyboardKey.arrowLeft,
+  108: PhysicalKeyboardKey.arrowDown,
+  103: PhysicalKeyboardKey.arrowUp,
+  69: PhysicalKeyboardKey.numLock,
+  98: PhysicalKeyboardKey.numpadDivide,
+  55: PhysicalKeyboardKey.numpadMultiply,
+  74: PhysicalKeyboardKey.numpadSubtract,
+  78: PhysicalKeyboardKey.numpadAdd,
+  96: PhysicalKeyboardKey.numpadEnter,
+  79: PhysicalKeyboardKey.numpad1,
+  80: PhysicalKeyboardKey.numpad2,
+  81: PhysicalKeyboardKey.numpad3,
+  75: PhysicalKeyboardKey.numpad4,
+  76: PhysicalKeyboardKey.numpad5,
+  77: PhysicalKeyboardKey.numpad6,
+  71: PhysicalKeyboardKey.numpad7,
+  72: PhysicalKeyboardKey.numpad8,
+  73: PhysicalKeyboardKey.numpad9,
+  82: PhysicalKeyboardKey.numpad0,
+  83: PhysicalKeyboardKey.numpadDecimal,
+  127: PhysicalKeyboardKey.contextMenu,
+  139: PhysicalKeyboardKey.contextMenu,
+  116: PhysicalKeyboardKey.power,
+  152: PhysicalKeyboardKey.power,
+  117: PhysicalKeyboardKey.numpadEqual,
+  183: PhysicalKeyboardKey.f13,
+  184: PhysicalKeyboardKey.f14,
+  185: PhysicalKeyboardKey.f15,
+  186: PhysicalKeyboardKey.f16,
+  187: PhysicalKeyboardKey.f17,
+  188: PhysicalKeyboardKey.f18,
+  189: PhysicalKeyboardKey.f19,
+  190: PhysicalKeyboardKey.f20,
+  191: PhysicalKeyboardKey.f21,
+  192: PhysicalKeyboardKey.f22,
+  193: PhysicalKeyboardKey.f23,
+  194: PhysicalKeyboardKey.f24,
+  134: PhysicalKeyboardKey.open,
+  138: PhysicalKeyboardKey.help,
+  353: PhysicalKeyboardKey.select,
+  129: PhysicalKeyboardKey.again,
+  131: PhysicalKeyboardKey.undo,
+  137: PhysicalKeyboardKey.cut,
+  133: PhysicalKeyboardKey.copy,
+  135: PhysicalKeyboardKey.paste,
+  136: PhysicalKeyboardKey.find,
+  113: PhysicalKeyboardKey.audioVolumeMute,
+  115: PhysicalKeyboardKey.audioVolumeUp,
+  114: PhysicalKeyboardKey.audioVolumeDown,
+  95: PhysicalKeyboardKey.numpadComma,
+  121: PhysicalKeyboardKey.numpadComma,
+  92: PhysicalKeyboardKey.convert,
+  94: PhysicalKeyboardKey.nonConvert,
+  90: PhysicalKeyboardKey.lang3,
+  91: PhysicalKeyboardKey.lang4,
+  130: PhysicalKeyboardKey.props,
+  179: PhysicalKeyboardKey.numpadParenLeft,
+  180: PhysicalKeyboardKey.numpadParenRight,
+  29: PhysicalKeyboardKey.controlLeft,
+  42: PhysicalKeyboardKey.shiftLeft,
+  56: PhysicalKeyboardKey.altLeft,
+  125: PhysicalKeyboardKey.metaLeft,
+  97: PhysicalKeyboardKey.controlRight,
+  54: PhysicalKeyboardKey.shiftRight,
+  100: PhysicalKeyboardKey.altRight,
+  126: PhysicalKeyboardKey.metaRight,
+  358: PhysicalKeyboardKey.info,
+  370: PhysicalKeyboardKey.closedCaptionToggle,
+  225: PhysicalKeyboardKey.brightnessUp,
+  224: PhysicalKeyboardKey.brightnessDown,
+  405: PhysicalKeyboardKey.mediaLast,
+  174: PhysicalKeyboardKey.exit,
+  402: PhysicalKeyboardKey.channelUp,
+  403: PhysicalKeyboardKey.channelDown,
+  200: PhysicalKeyboardKey.mediaPlay,
+  207: PhysicalKeyboardKey.mediaPlay,
+  201: PhysicalKeyboardKey.mediaPause,
+  167: PhysicalKeyboardKey.mediaRecord,
+  208: PhysicalKeyboardKey.mediaFastForward,
+  168: PhysicalKeyboardKey.mediaRewind,
+  163: PhysicalKeyboardKey.mediaTrackNext,
+  165: PhysicalKeyboardKey.mediaTrackPrevious,
+  128: PhysicalKeyboardKey.mediaStop,
+  166: PhysicalKeyboardKey.mediaStop,
+  161: PhysicalKeyboardKey.eject,
+  162: PhysicalKeyboardKey.eject,
+  164: PhysicalKeyboardKey.mediaPlayPause,
+  209: PhysicalKeyboardKey.bassBoost,
+  155: PhysicalKeyboardKey.launchMail,
+  215: PhysicalKeyboardKey.launchMail,
+  429: PhysicalKeyboardKey.launchContacts,
+  397: PhysicalKeyboardKey.launchCalendar,
+  583: PhysicalKeyboardKey.launchAssistant,
+  181: PhysicalKeyboardKey.newKey,
+  160: PhysicalKeyboardKey.close,
+  206: PhysicalKeyboardKey.close,
+  210: PhysicalKeyboardKey.print,
+  217: PhysicalKeyboardKey.browserSearch,
+  159: PhysicalKeyboardKey.browserForward,
+  156: PhysicalKeyboardKey.browserFavorites,
+  182: PhysicalKeyboardKey.redo,
+  256: PhysicalKeyboardKey.gameButton1,
+  288: PhysicalKeyboardKey.gameButton1,
+  257: PhysicalKeyboardKey.gameButton2,
+  289: PhysicalKeyboardKey.gameButton2,
+  258: PhysicalKeyboardKey.gameButton3,
+  290: PhysicalKeyboardKey.gameButton3,
+  259: PhysicalKeyboardKey.gameButton4,
+  291: PhysicalKeyboardKey.gameButton4,
+  260: PhysicalKeyboardKey.gameButton5,
+  292: PhysicalKeyboardKey.gameButton5,
+  261: PhysicalKeyboardKey.gameButton6,
+  293: PhysicalKeyboardKey.gameButton6,
+  262: PhysicalKeyboardKey.gameButton7,
+  294: PhysicalKeyboardKey.gameButton7,
+  263: PhysicalKeyboardKey.gameButton8,
+  295: PhysicalKeyboardKey.gameButton8,
+  264: PhysicalKeyboardKey.gameButton9,
+  296: PhysicalKeyboardKey.gameButton9,
+  265: PhysicalKeyboardKey.gameButton10,
+  297: PhysicalKeyboardKey.gameButton10,
+  266: PhysicalKeyboardKey.gameButton11,
+  298: PhysicalKeyboardKey.gameButton11,
+  267: PhysicalKeyboardKey.gameButton12,
+  299: PhysicalKeyboardKey.gameButton12,
+  268: PhysicalKeyboardKey.gameButton13,
+  300: PhysicalKeyboardKey.gameButton13,
+  269: PhysicalKeyboardKey.gameButton14,
+  301: PhysicalKeyboardKey.gameButton14,
+  270: PhysicalKeyboardKey.gameButton15,
+  302: PhysicalKeyboardKey.gameButton15,
+  271: PhysicalKeyboardKey.gameButton16,
+  303: PhysicalKeyboardKey.gameButton16,
+  304: PhysicalKeyboardKey.gameButtonA,
+  305: PhysicalKeyboardKey.gameButtonB,
+  306: PhysicalKeyboardKey.gameButtonC,
+  310: PhysicalKeyboardKey.gameButtonLeft1,
+  312: PhysicalKeyboardKey.gameButtonLeft2,
+  316: PhysicalKeyboardKey.gameButtonMode,
+  311: PhysicalKeyboardKey.gameButtonRight1,
+  313: PhysicalKeyboardKey.gameButtonRight2,
+  314: PhysicalKeyboardKey.gameButtonSelect,
+  315: PhysicalKeyboardKey.gameButtonStart,
+  317: PhysicalKeyboardKey.gameButtonThumbLeft,
+  318: PhysicalKeyboardKey.gameButtonThumbRight,
+  307: PhysicalKeyboardKey.gameButtonX,
+  308: PhysicalKeyboardKey.gameButtonY,
+  309: PhysicalKeyboardKey.gameButtonZ,
+  464: PhysicalKeyboardKey.fn,
+};
+
+/// A map of Android key codes which have printable representations, but appear
+/// on the number pad. Used to provide different key objects for keys like
+/// KEY_EQUALS and NUMPAD_EQUALS.
+const Map<int, LogicalKeyboardKey> kAndroidNumPadMap = <int, LogicalKeyboardKey>{
+  154: LogicalKeyboardKey.numpadDivide,
+  155: LogicalKeyboardKey.numpadMultiply,
+  156: LogicalKeyboardKey.numpadSubtract,
+  157: LogicalKeyboardKey.numpadAdd,
+  145: LogicalKeyboardKey.numpad1,
+  146: LogicalKeyboardKey.numpad2,
+  147: LogicalKeyboardKey.numpad3,
+  148: LogicalKeyboardKey.numpad4,
+  149: LogicalKeyboardKey.numpad5,
+  150: LogicalKeyboardKey.numpad6,
+  151: LogicalKeyboardKey.numpad7,
+  152: LogicalKeyboardKey.numpad8,
+  153: LogicalKeyboardKey.numpad9,
+  144: LogicalKeyboardKey.numpad0,
+  158: LogicalKeyboardKey.numpadDecimal,
+  161: LogicalKeyboardKey.numpadEqual,
+  159: LogicalKeyboardKey.numpadComma,
+  162: LogicalKeyboardKey.numpadParenLeft,
+  163: LogicalKeyboardKey.numpadParenRight,
+};
+
+/// Maps Fuchsia-specific IDs to the matching [LogicalKeyboardKey].
+const Map<int, LogicalKeyboardKey> kFuchsiaToLogicalKey = <int, LogicalKeyboardKey>{
+  0x100000000: LogicalKeyboardKey.none,
+  0x100000010: LogicalKeyboardKey.hyper,
+  0x100000011: LogicalKeyboardKey.superKey,
+  0x100000013: LogicalKeyboardKey.fnLock,
+  0x100000014: LogicalKeyboardKey.suspend,
+  0x100000015: LogicalKeyboardKey.resume,
+  0x100000016: LogicalKeyboardKey.turbo,
+  0x100000017: LogicalKeyboardKey.privacyScreenToggle,
+  0x100010082: LogicalKeyboardKey.sleep,
+  0x100010083: LogicalKeyboardKey.wakeUp,
+  0x1000100b5: LogicalKeyboardKey.displayToggleIntExt,
+  0x100070000: LogicalKeyboardKey.usbReserved,
+  0x100070001: LogicalKeyboardKey.usbErrorRollOver,
+  0x100070002: LogicalKeyboardKey.usbPostFail,
+  0x100070003: LogicalKeyboardKey.usbErrorUndefined,
+  0x00000061: LogicalKeyboardKey.keyA,
+  0x00000062: LogicalKeyboardKey.keyB,
+  0x00000063: LogicalKeyboardKey.keyC,
+  0x00000064: LogicalKeyboardKey.keyD,
+  0x00000065: LogicalKeyboardKey.keyE,
+  0x00000066: LogicalKeyboardKey.keyF,
+  0x00000067: LogicalKeyboardKey.keyG,
+  0x00000068: LogicalKeyboardKey.keyH,
+  0x00000069: LogicalKeyboardKey.keyI,
+  0x0000006a: LogicalKeyboardKey.keyJ,
+  0x0000006b: LogicalKeyboardKey.keyK,
+  0x0000006c: LogicalKeyboardKey.keyL,
+  0x0000006d: LogicalKeyboardKey.keyM,
+  0x0000006e: LogicalKeyboardKey.keyN,
+  0x0000006f: LogicalKeyboardKey.keyO,
+  0x00000070: LogicalKeyboardKey.keyP,
+  0x00000071: LogicalKeyboardKey.keyQ,
+  0x00000072: LogicalKeyboardKey.keyR,
+  0x00000073: LogicalKeyboardKey.keyS,
+  0x00000074: LogicalKeyboardKey.keyT,
+  0x00000075: LogicalKeyboardKey.keyU,
+  0x00000076: LogicalKeyboardKey.keyV,
+  0x00000077: LogicalKeyboardKey.keyW,
+  0x00000078: LogicalKeyboardKey.keyX,
+  0x00000079: LogicalKeyboardKey.keyY,
+  0x0000007a: LogicalKeyboardKey.keyZ,
+  0x00000031: LogicalKeyboardKey.digit1,
+  0x00000032: LogicalKeyboardKey.digit2,
+  0x00000033: LogicalKeyboardKey.digit3,
+  0x00000034: LogicalKeyboardKey.digit4,
+  0x00000035: LogicalKeyboardKey.digit5,
+  0x00000036: LogicalKeyboardKey.digit6,
+  0x00000037: LogicalKeyboardKey.digit7,
+  0x00000038: LogicalKeyboardKey.digit8,
+  0x00000039: LogicalKeyboardKey.digit9,
+  0x00000030: LogicalKeyboardKey.digit0,
+  0x100070028: LogicalKeyboardKey.enter,
+  0x100070029: LogicalKeyboardKey.escape,
+  0x10007002a: LogicalKeyboardKey.backspace,
+  0x10007002b: LogicalKeyboardKey.tab,
+  0x00000020: LogicalKeyboardKey.space,
+  0x0000002d: LogicalKeyboardKey.minus,
+  0x0000003d: LogicalKeyboardKey.equal,
+  0x0000005b: LogicalKeyboardKey.bracketLeft,
+  0x0000005d: LogicalKeyboardKey.bracketRight,
+  0x0000005c: LogicalKeyboardKey.backslash,
+  0x0000003b: LogicalKeyboardKey.semicolon,
+  0x00000027: LogicalKeyboardKey.quote,
+  0x00000060: LogicalKeyboardKey.backquote,
+  0x0000002c: LogicalKeyboardKey.comma,
+  0x0000002e: LogicalKeyboardKey.period,
+  0x0000002f: LogicalKeyboardKey.slash,
+  0x100070039: LogicalKeyboardKey.capsLock,
+  0x10007003a: LogicalKeyboardKey.f1,
+  0x10007003b: LogicalKeyboardKey.f2,
+  0x10007003c: LogicalKeyboardKey.f3,
+  0x10007003d: LogicalKeyboardKey.f4,
+  0x10007003e: LogicalKeyboardKey.f5,
+  0x10007003f: LogicalKeyboardKey.f6,
+  0x100070040: LogicalKeyboardKey.f7,
+  0x100070041: LogicalKeyboardKey.f8,
+  0x100070042: LogicalKeyboardKey.f9,
+  0x100070043: LogicalKeyboardKey.f10,
+  0x100070044: LogicalKeyboardKey.f11,
+  0x100070045: LogicalKeyboardKey.f12,
+  0x100070046: LogicalKeyboardKey.printScreen,
+  0x100070047: LogicalKeyboardKey.scrollLock,
+  0x100070048: LogicalKeyboardKey.pause,
+  0x100070049: LogicalKeyboardKey.insert,
+  0x10007004a: LogicalKeyboardKey.home,
+  0x10007004b: LogicalKeyboardKey.pageUp,
+  0x10007004c: LogicalKeyboardKey.delete,
+  0x10007004d: LogicalKeyboardKey.end,
+  0x10007004e: LogicalKeyboardKey.pageDown,
+  0x10007004f: LogicalKeyboardKey.arrowRight,
+  0x100070050: LogicalKeyboardKey.arrowLeft,
+  0x100070051: LogicalKeyboardKey.arrowDown,
+  0x100070052: LogicalKeyboardKey.arrowUp,
+  0x100070053: LogicalKeyboardKey.numLock,
+  0x100070054: LogicalKeyboardKey.numpadDivide,
+  0x100070055: LogicalKeyboardKey.numpadMultiply,
+  0x100070056: LogicalKeyboardKey.numpadSubtract,
+  0x100070057: LogicalKeyboardKey.numpadAdd,
+  0x100070058: LogicalKeyboardKey.numpadEnter,
+  0x100070059: LogicalKeyboardKey.numpad1,
+  0x10007005a: LogicalKeyboardKey.numpad2,
+  0x10007005b: LogicalKeyboardKey.numpad3,
+  0x10007005c: LogicalKeyboardKey.numpad4,
+  0x10007005d: LogicalKeyboardKey.numpad5,
+  0x10007005e: LogicalKeyboardKey.numpad6,
+  0x10007005f: LogicalKeyboardKey.numpad7,
+  0x100070060: LogicalKeyboardKey.numpad8,
+  0x100070061: LogicalKeyboardKey.numpad9,
+  0x100070062: LogicalKeyboardKey.numpad0,
+  0x100070063: LogicalKeyboardKey.numpadDecimal,
+  0x100070064: LogicalKeyboardKey.intlBackslash,
+  0x100070065: LogicalKeyboardKey.contextMenu,
+  0x100070066: LogicalKeyboardKey.power,
+  0x100070067: LogicalKeyboardKey.numpadEqual,
+  0x100070068: LogicalKeyboardKey.f13,
+  0x100070069: LogicalKeyboardKey.f14,
+  0x10007006a: LogicalKeyboardKey.f15,
+  0x10007006b: LogicalKeyboardKey.f16,
+  0x10007006c: LogicalKeyboardKey.f17,
+  0x10007006d: LogicalKeyboardKey.f18,
+  0x10007006e: LogicalKeyboardKey.f19,
+  0x10007006f: LogicalKeyboardKey.f20,
+  0x100070070: LogicalKeyboardKey.f21,
+  0x100070071: LogicalKeyboardKey.f22,
+  0x100070072: LogicalKeyboardKey.f23,
+  0x100070073: LogicalKeyboardKey.f24,
+  0x100070074: LogicalKeyboardKey.open,
+  0x100070075: LogicalKeyboardKey.help,
+  0x100070077: LogicalKeyboardKey.select,
+  0x100070079: LogicalKeyboardKey.again,
+  0x10007007a: LogicalKeyboardKey.undo,
+  0x10007007b: LogicalKeyboardKey.cut,
+  0x10007007c: LogicalKeyboardKey.copy,
+  0x10007007d: LogicalKeyboardKey.paste,
+  0x10007007e: LogicalKeyboardKey.find,
+  0x10007007f: LogicalKeyboardKey.audioVolumeMute,
+  0x100070080: LogicalKeyboardKey.audioVolumeUp,
+  0x100070081: LogicalKeyboardKey.audioVolumeDown,
+  0x100070085: LogicalKeyboardKey.numpadComma,
+  0x100070087: LogicalKeyboardKey.intlRo,
+  0x100070088: LogicalKeyboardKey.kanaMode,
+  0x100070089: LogicalKeyboardKey.intlYen,
+  0x10007008a: LogicalKeyboardKey.convert,
+  0x10007008b: LogicalKeyboardKey.nonConvert,
+  0x100070090: LogicalKeyboardKey.lang1,
+  0x100070091: LogicalKeyboardKey.lang2,
+  0x100070092: LogicalKeyboardKey.lang3,
+  0x100070093: LogicalKeyboardKey.lang4,
+  0x100070094: LogicalKeyboardKey.lang5,
+  0x10007009b: LogicalKeyboardKey.abort,
+  0x1000700a3: LogicalKeyboardKey.props,
+  0x1000700b6: LogicalKeyboardKey.numpadParenLeft,
+  0x1000700b7: LogicalKeyboardKey.numpadParenRight,
+  0x1000700bb: LogicalKeyboardKey.numpadBackspace,
+  0x1000700d0: LogicalKeyboardKey.numpadMemoryStore,
+  0x1000700d1: LogicalKeyboardKey.numpadMemoryRecall,
+  0x1000700d2: LogicalKeyboardKey.numpadMemoryClear,
+  0x1000700d3: LogicalKeyboardKey.numpadMemoryAdd,
+  0x1000700d4: LogicalKeyboardKey.numpadMemorySubtract,
+  0x1000700d7: LogicalKeyboardKey.numpadSignChange,
+  0x1000700d8: LogicalKeyboardKey.numpadClear,
+  0x1000700d9: LogicalKeyboardKey.numpadClearEntry,
+  0x1000700e0: LogicalKeyboardKey.controlLeft,
+  0x1000700e1: LogicalKeyboardKey.shiftLeft,
+  0x1000700e2: LogicalKeyboardKey.altLeft,
+  0x1000700e3: LogicalKeyboardKey.metaLeft,
+  0x1000700e4: LogicalKeyboardKey.controlRight,
+  0x1000700e5: LogicalKeyboardKey.shiftRight,
+  0x1000700e6: LogicalKeyboardKey.altRight,
+  0x1000700e7: LogicalKeyboardKey.metaRight,
+  0x1000c0060: LogicalKeyboardKey.info,
+  0x1000c0061: LogicalKeyboardKey.closedCaptionToggle,
+  0x1000c006f: LogicalKeyboardKey.brightnessUp,
+  0x1000c0070: LogicalKeyboardKey.brightnessDown,
+  0x1000c0072: LogicalKeyboardKey.brightnessToggle,
+  0x1000c0073: LogicalKeyboardKey.brightnessMinimum,
+  0x1000c0074: LogicalKeyboardKey.brightnessMaximum,
+  0x1000c0075: LogicalKeyboardKey.brightnessAuto,
+  0x1000c0079: LogicalKeyboardKey.kbdIllumUp,
+  0x1000c007a: LogicalKeyboardKey.kbdIllumDown,
+  0x1000c0083: LogicalKeyboardKey.mediaLast,
+  0x1000c008c: LogicalKeyboardKey.launchPhone,
+  0x1000c008d: LogicalKeyboardKey.programGuide,
+  0x1000c0094: LogicalKeyboardKey.exit,
+  0x1000c009c: LogicalKeyboardKey.channelUp,
+  0x1000c009d: LogicalKeyboardKey.channelDown,
+  0x1000c00b0: LogicalKeyboardKey.mediaPlay,
+  0x1000c00b1: LogicalKeyboardKey.mediaPause,
+  0x1000c00b2: LogicalKeyboardKey.mediaRecord,
+  0x1000c00b3: LogicalKeyboardKey.mediaFastForward,
+  0x1000c00b4: LogicalKeyboardKey.mediaRewind,
+  0x1000c00b5: LogicalKeyboardKey.mediaTrackNext,
+  0x1000c00b6: LogicalKeyboardKey.mediaTrackPrevious,
+  0x1000c00b7: LogicalKeyboardKey.mediaStop,
+  0x1000c00b8: LogicalKeyboardKey.eject,
+  0x1000c00cd: LogicalKeyboardKey.mediaPlayPause,
+  0x1000c00cf: LogicalKeyboardKey.speechInputToggle,
+  0x1000c00e5: LogicalKeyboardKey.bassBoost,
+  0x1000c0183: LogicalKeyboardKey.mediaSelect,
+  0x1000c0184: LogicalKeyboardKey.launchWordProcessor,
+  0x1000c0186: LogicalKeyboardKey.launchSpreadsheet,
+  0x1000c018a: LogicalKeyboardKey.launchMail,
+  0x1000c018d: LogicalKeyboardKey.launchContacts,
+  0x1000c018e: LogicalKeyboardKey.launchCalendar,
+  0x1000c0192: LogicalKeyboardKey.launchApp2,
+  0x1000c0194: LogicalKeyboardKey.launchApp1,
+  0x1000c0196: LogicalKeyboardKey.launchInternetBrowser,
+  0x1000c019c: LogicalKeyboardKey.logOff,
+  0x1000c019e: LogicalKeyboardKey.lockScreen,
+  0x1000c019f: LogicalKeyboardKey.launchControlPanel,
+  0x1000c01a2: LogicalKeyboardKey.selectTask,
+  0x1000c01a7: LogicalKeyboardKey.launchDocuments,
+  0x1000c01ab: LogicalKeyboardKey.spellCheck,
+  0x1000c01ae: LogicalKeyboardKey.launchKeyboardLayout,
+  0x1000c01b1: LogicalKeyboardKey.launchScreenSaver,
+  0x1000c01cb: LogicalKeyboardKey.launchAssistant,
+  0x1000c01b7: LogicalKeyboardKey.launchAudioBrowser,
+  0x1000c0201: LogicalKeyboardKey.newKey,
+  0x1000c0203: LogicalKeyboardKey.close,
+  0x1000c0207: LogicalKeyboardKey.save,
+  0x1000c0208: LogicalKeyboardKey.print,
+  0x1000c0221: LogicalKeyboardKey.browserSearch,
+  0x1000c0223: LogicalKeyboardKey.browserHome,
+  0x1000c0224: LogicalKeyboardKey.browserBack,
+  0x1000c0225: LogicalKeyboardKey.browserForward,
+  0x1000c0226: LogicalKeyboardKey.browserStop,
+  0x1000c0227: LogicalKeyboardKey.browserRefresh,
+  0x1000c022a: LogicalKeyboardKey.browserFavorites,
+  0x1000c022d: LogicalKeyboardKey.zoomIn,
+  0x1000c022e: LogicalKeyboardKey.zoomOut,
+  0x1000c0232: LogicalKeyboardKey.zoomToggle,
+  0x1000c0279: LogicalKeyboardKey.redo,
+  0x1000c0289: LogicalKeyboardKey.mailReply,
+  0x1000c028b: LogicalKeyboardKey.mailForward,
+  0x1000c028c: LogicalKeyboardKey.mailSend,
+  0x1000c029d: LogicalKeyboardKey.keyboardLayoutSelect,
+  0x1000c029f: LogicalKeyboardKey.showAllWindows,
+  0x10005ff01: LogicalKeyboardKey.gameButton1,
+  0x10005ff02: LogicalKeyboardKey.gameButton2,
+  0x10005ff03: LogicalKeyboardKey.gameButton3,
+  0x10005ff04: LogicalKeyboardKey.gameButton4,
+  0x10005ff05: LogicalKeyboardKey.gameButton5,
+  0x10005ff06: LogicalKeyboardKey.gameButton6,
+  0x10005ff07: LogicalKeyboardKey.gameButton7,
+  0x10005ff08: LogicalKeyboardKey.gameButton8,
+  0x10005ff09: LogicalKeyboardKey.gameButton9,
+  0x10005ff0a: LogicalKeyboardKey.gameButton10,
+  0x10005ff0b: LogicalKeyboardKey.gameButton11,
+  0x10005ff0c: LogicalKeyboardKey.gameButton12,
+  0x10005ff0d: LogicalKeyboardKey.gameButton13,
+  0x10005ff0e: LogicalKeyboardKey.gameButton14,
+  0x10005ff0f: LogicalKeyboardKey.gameButton15,
+  0x10005ff10: LogicalKeyboardKey.gameButton16,
+  0x10005ff11: LogicalKeyboardKey.gameButtonA,
+  0x10005ff12: LogicalKeyboardKey.gameButtonB,
+  0x10005ff13: LogicalKeyboardKey.gameButtonC,
+  0x10005ff14: LogicalKeyboardKey.gameButtonLeft1,
+  0x10005ff15: LogicalKeyboardKey.gameButtonLeft2,
+  0x10005ff16: LogicalKeyboardKey.gameButtonMode,
+  0x10005ff17: LogicalKeyboardKey.gameButtonRight1,
+  0x10005ff18: LogicalKeyboardKey.gameButtonRight2,
+  0x10005ff19: LogicalKeyboardKey.gameButtonSelect,
+  0x10005ff1a: LogicalKeyboardKey.gameButtonStart,
+  0x10005ff1b: LogicalKeyboardKey.gameButtonThumbLeft,
+  0x10005ff1c: LogicalKeyboardKey.gameButtonThumbRight,
+  0x10005ff1d: LogicalKeyboardKey.gameButtonX,
+  0x10005ff1e: LogicalKeyboardKey.gameButtonY,
+  0x10005ff1f: LogicalKeyboardKey.gameButtonZ,
+  0x100000012: LogicalKeyboardKey.fn,
+};
+
+/// Maps Fuchsia-specific USB HID Usage IDs to the matching
+/// [PhysicalKeyboardKey].
+const Map<int, PhysicalKeyboardKey> kFuchsiaToPhysicalKey = <int, PhysicalKeyboardKey>{
+  0x00000000: PhysicalKeyboardKey.none,
+  0x00000010: PhysicalKeyboardKey.hyper,
+  0x00000011: PhysicalKeyboardKey.superKey,
+  0x00000013: PhysicalKeyboardKey.fnLock,
+  0x00000014: PhysicalKeyboardKey.suspend,
+  0x00000015: PhysicalKeyboardKey.resume,
+  0x00000016: PhysicalKeyboardKey.turbo,
+  0x00000017: PhysicalKeyboardKey.privacyScreenToggle,
+  0x00010082: PhysicalKeyboardKey.sleep,
+  0x00010083: PhysicalKeyboardKey.wakeUp,
+  0x000100b5: PhysicalKeyboardKey.displayToggleIntExt,
+  0x00070000: PhysicalKeyboardKey.usbReserved,
+  0x00070001: PhysicalKeyboardKey.usbErrorRollOver,
+  0x00070002: PhysicalKeyboardKey.usbPostFail,
+  0x00070003: PhysicalKeyboardKey.usbErrorUndefined,
+  0x00070004: PhysicalKeyboardKey.keyA,
+  0x00070005: PhysicalKeyboardKey.keyB,
+  0x00070006: PhysicalKeyboardKey.keyC,
+  0x00070007: PhysicalKeyboardKey.keyD,
+  0x00070008: PhysicalKeyboardKey.keyE,
+  0x00070009: PhysicalKeyboardKey.keyF,
+  0x0007000a: PhysicalKeyboardKey.keyG,
+  0x0007000b: PhysicalKeyboardKey.keyH,
+  0x0007000c: PhysicalKeyboardKey.keyI,
+  0x0007000d: PhysicalKeyboardKey.keyJ,
+  0x0007000e: PhysicalKeyboardKey.keyK,
+  0x0007000f: PhysicalKeyboardKey.keyL,
+  0x00070010: PhysicalKeyboardKey.keyM,
+  0x00070011: PhysicalKeyboardKey.keyN,
+  0x00070012: PhysicalKeyboardKey.keyO,
+  0x00070013: PhysicalKeyboardKey.keyP,
+  0x00070014: PhysicalKeyboardKey.keyQ,
+  0x00070015: PhysicalKeyboardKey.keyR,
+  0x00070016: PhysicalKeyboardKey.keyS,
+  0x00070017: PhysicalKeyboardKey.keyT,
+  0x00070018: PhysicalKeyboardKey.keyU,
+  0x00070019: PhysicalKeyboardKey.keyV,
+  0x0007001a: PhysicalKeyboardKey.keyW,
+  0x0007001b: PhysicalKeyboardKey.keyX,
+  0x0007001c: PhysicalKeyboardKey.keyY,
+  0x0007001d: PhysicalKeyboardKey.keyZ,
+  0x0007001e: PhysicalKeyboardKey.digit1,
+  0x0007001f: PhysicalKeyboardKey.digit2,
+  0x00070020: PhysicalKeyboardKey.digit3,
+  0x00070021: PhysicalKeyboardKey.digit4,
+  0x00070022: PhysicalKeyboardKey.digit5,
+  0x00070023: PhysicalKeyboardKey.digit6,
+  0x00070024: PhysicalKeyboardKey.digit7,
+  0x00070025: PhysicalKeyboardKey.digit8,
+  0x00070026: PhysicalKeyboardKey.digit9,
+  0x00070027: PhysicalKeyboardKey.digit0,
+  0x00070028: PhysicalKeyboardKey.enter,
+  0x00070029: PhysicalKeyboardKey.escape,
+  0x0007002a: PhysicalKeyboardKey.backspace,
+  0x0007002b: PhysicalKeyboardKey.tab,
+  0x0007002c: PhysicalKeyboardKey.space,
+  0x0007002d: PhysicalKeyboardKey.minus,
+  0x0007002e: PhysicalKeyboardKey.equal,
+  0x0007002f: PhysicalKeyboardKey.bracketLeft,
+  0x00070030: PhysicalKeyboardKey.bracketRight,
+  0x00070031: PhysicalKeyboardKey.backslash,
+  0x00070033: PhysicalKeyboardKey.semicolon,
+  0x00070034: PhysicalKeyboardKey.quote,
+  0x00070035: PhysicalKeyboardKey.backquote,
+  0x00070036: PhysicalKeyboardKey.comma,
+  0x00070037: PhysicalKeyboardKey.period,
+  0x00070038: PhysicalKeyboardKey.slash,
+  0x00070039: PhysicalKeyboardKey.capsLock,
+  0x0007003a: PhysicalKeyboardKey.f1,
+  0x0007003b: PhysicalKeyboardKey.f2,
+  0x0007003c: PhysicalKeyboardKey.f3,
+  0x0007003d: PhysicalKeyboardKey.f4,
+  0x0007003e: PhysicalKeyboardKey.f5,
+  0x0007003f: PhysicalKeyboardKey.f6,
+  0x00070040: PhysicalKeyboardKey.f7,
+  0x00070041: PhysicalKeyboardKey.f8,
+  0x00070042: PhysicalKeyboardKey.f9,
+  0x00070043: PhysicalKeyboardKey.f10,
+  0x00070044: PhysicalKeyboardKey.f11,
+  0x00070045: PhysicalKeyboardKey.f12,
+  0x00070046: PhysicalKeyboardKey.printScreen,
+  0x00070047: PhysicalKeyboardKey.scrollLock,
+  0x00070048: PhysicalKeyboardKey.pause,
+  0x00070049: PhysicalKeyboardKey.insert,
+  0x0007004a: PhysicalKeyboardKey.home,
+  0x0007004b: PhysicalKeyboardKey.pageUp,
+  0x0007004c: PhysicalKeyboardKey.delete,
+  0x0007004d: PhysicalKeyboardKey.end,
+  0x0007004e: PhysicalKeyboardKey.pageDown,
+  0x0007004f: PhysicalKeyboardKey.arrowRight,
+  0x00070050: PhysicalKeyboardKey.arrowLeft,
+  0x00070051: PhysicalKeyboardKey.arrowDown,
+  0x00070052: PhysicalKeyboardKey.arrowUp,
+  0x00070053: PhysicalKeyboardKey.numLock,
+  0x00070054: PhysicalKeyboardKey.numpadDivide,
+  0x00070055: PhysicalKeyboardKey.numpadMultiply,
+  0x00070056: PhysicalKeyboardKey.numpadSubtract,
+  0x00070057: PhysicalKeyboardKey.numpadAdd,
+  0x00070058: PhysicalKeyboardKey.numpadEnter,
+  0x00070059: PhysicalKeyboardKey.numpad1,
+  0x0007005a: PhysicalKeyboardKey.numpad2,
+  0x0007005b: PhysicalKeyboardKey.numpad3,
+  0x0007005c: PhysicalKeyboardKey.numpad4,
+  0x0007005d: PhysicalKeyboardKey.numpad5,
+  0x0007005e: PhysicalKeyboardKey.numpad6,
+  0x0007005f: PhysicalKeyboardKey.numpad7,
+  0x00070060: PhysicalKeyboardKey.numpad8,
+  0x00070061: PhysicalKeyboardKey.numpad9,
+  0x00070062: PhysicalKeyboardKey.numpad0,
+  0x00070063: PhysicalKeyboardKey.numpadDecimal,
+  0x00070064: PhysicalKeyboardKey.intlBackslash,
+  0x00070065: PhysicalKeyboardKey.contextMenu,
+  0x00070066: PhysicalKeyboardKey.power,
+  0x00070067: PhysicalKeyboardKey.numpadEqual,
+  0x00070068: PhysicalKeyboardKey.f13,
+  0x00070069: PhysicalKeyboardKey.f14,
+  0x0007006a: PhysicalKeyboardKey.f15,
+  0x0007006b: PhysicalKeyboardKey.f16,
+  0x0007006c: PhysicalKeyboardKey.f17,
+  0x0007006d: PhysicalKeyboardKey.f18,
+  0x0007006e: PhysicalKeyboardKey.f19,
+  0x0007006f: PhysicalKeyboardKey.f20,
+  0x00070070: PhysicalKeyboardKey.f21,
+  0x00070071: PhysicalKeyboardKey.f22,
+  0x00070072: PhysicalKeyboardKey.f23,
+  0x00070073: PhysicalKeyboardKey.f24,
+  0x00070074: PhysicalKeyboardKey.open,
+  0x00070075: PhysicalKeyboardKey.help,
+  0x00070077: PhysicalKeyboardKey.select,
+  0x00070079: PhysicalKeyboardKey.again,
+  0x0007007a: PhysicalKeyboardKey.undo,
+  0x0007007b: PhysicalKeyboardKey.cut,
+  0x0007007c: PhysicalKeyboardKey.copy,
+  0x0007007d: PhysicalKeyboardKey.paste,
+  0x0007007e: PhysicalKeyboardKey.find,
+  0x0007007f: PhysicalKeyboardKey.audioVolumeMute,
+  0x00070080: PhysicalKeyboardKey.audioVolumeUp,
+  0x00070081: PhysicalKeyboardKey.audioVolumeDown,
+  0x00070085: PhysicalKeyboardKey.numpadComma,
+  0x00070087: PhysicalKeyboardKey.intlRo,
+  0x00070088: PhysicalKeyboardKey.kanaMode,
+  0x00070089: PhysicalKeyboardKey.intlYen,
+  0x0007008a: PhysicalKeyboardKey.convert,
+  0x0007008b: PhysicalKeyboardKey.nonConvert,
+  0x00070090: PhysicalKeyboardKey.lang1,
+  0x00070091: PhysicalKeyboardKey.lang2,
+  0x00070092: PhysicalKeyboardKey.lang3,
+  0x00070093: PhysicalKeyboardKey.lang4,
+  0x00070094: PhysicalKeyboardKey.lang5,
+  0x0007009b: PhysicalKeyboardKey.abort,
+  0x000700a3: PhysicalKeyboardKey.props,
+  0x000700b6: PhysicalKeyboardKey.numpadParenLeft,
+  0x000700b7: PhysicalKeyboardKey.numpadParenRight,
+  0x000700bb: PhysicalKeyboardKey.numpadBackspace,
+  0x000700d0: PhysicalKeyboardKey.numpadMemoryStore,
+  0x000700d1: PhysicalKeyboardKey.numpadMemoryRecall,
+  0x000700d2: PhysicalKeyboardKey.numpadMemoryClear,
+  0x000700d3: PhysicalKeyboardKey.numpadMemoryAdd,
+  0x000700d4: PhysicalKeyboardKey.numpadMemorySubtract,
+  0x000700d7: PhysicalKeyboardKey.numpadSignChange,
+  0x000700d8: PhysicalKeyboardKey.numpadClear,
+  0x000700d9: PhysicalKeyboardKey.numpadClearEntry,
+  0x000700e0: PhysicalKeyboardKey.controlLeft,
+  0x000700e1: PhysicalKeyboardKey.shiftLeft,
+  0x000700e2: PhysicalKeyboardKey.altLeft,
+  0x000700e3: PhysicalKeyboardKey.metaLeft,
+  0x000700e4: PhysicalKeyboardKey.controlRight,
+  0x000700e5: PhysicalKeyboardKey.shiftRight,
+  0x000700e6: PhysicalKeyboardKey.altRight,
+  0x000700e7: PhysicalKeyboardKey.metaRight,
+  0x000c0060: PhysicalKeyboardKey.info,
+  0x000c0061: PhysicalKeyboardKey.closedCaptionToggle,
+  0x000c006f: PhysicalKeyboardKey.brightnessUp,
+  0x000c0070: PhysicalKeyboardKey.brightnessDown,
+  0x000c0072: PhysicalKeyboardKey.brightnessToggle,
+  0x000c0073: PhysicalKeyboardKey.brightnessMinimum,
+  0x000c0074: PhysicalKeyboardKey.brightnessMaximum,
+  0x000c0075: PhysicalKeyboardKey.brightnessAuto,
+  0x000c0079: PhysicalKeyboardKey.kbdIllumUp,
+  0x000c007a: PhysicalKeyboardKey.kbdIllumDown,
+  0x000c0083: PhysicalKeyboardKey.mediaLast,
+  0x000c008c: PhysicalKeyboardKey.launchPhone,
+  0x000c008d: PhysicalKeyboardKey.programGuide,
+  0x000c0094: PhysicalKeyboardKey.exit,
+  0x000c009c: PhysicalKeyboardKey.channelUp,
+  0x000c009d: PhysicalKeyboardKey.channelDown,
+  0x000c00b0: PhysicalKeyboardKey.mediaPlay,
+  0x000c00b1: PhysicalKeyboardKey.mediaPause,
+  0x000c00b2: PhysicalKeyboardKey.mediaRecord,
+  0x000c00b3: PhysicalKeyboardKey.mediaFastForward,
+  0x000c00b4: PhysicalKeyboardKey.mediaRewind,
+  0x000c00b5: PhysicalKeyboardKey.mediaTrackNext,
+  0x000c00b6: PhysicalKeyboardKey.mediaTrackPrevious,
+  0x000c00b7: PhysicalKeyboardKey.mediaStop,
+  0x000c00b8: PhysicalKeyboardKey.eject,
+  0x000c00cd: PhysicalKeyboardKey.mediaPlayPause,
+  0x000c00cf: PhysicalKeyboardKey.speechInputToggle,
+  0x000c00e5: PhysicalKeyboardKey.bassBoost,
+  0x000c0183: PhysicalKeyboardKey.mediaSelect,
+  0x000c0184: PhysicalKeyboardKey.launchWordProcessor,
+  0x000c0186: PhysicalKeyboardKey.launchSpreadsheet,
+  0x000c018a: PhysicalKeyboardKey.launchMail,
+  0x000c018d: PhysicalKeyboardKey.launchContacts,
+  0x000c018e: PhysicalKeyboardKey.launchCalendar,
+  0x000c0192: PhysicalKeyboardKey.launchApp2,
+  0x000c0194: PhysicalKeyboardKey.launchApp1,
+  0x000c0196: PhysicalKeyboardKey.launchInternetBrowser,
+  0x000c019c: PhysicalKeyboardKey.logOff,
+  0x000c019e: PhysicalKeyboardKey.lockScreen,
+  0x000c019f: PhysicalKeyboardKey.launchControlPanel,
+  0x000c01a2: PhysicalKeyboardKey.selectTask,
+  0x000c01a7: PhysicalKeyboardKey.launchDocuments,
+  0x000c01ab: PhysicalKeyboardKey.spellCheck,
+  0x000c01ae: PhysicalKeyboardKey.launchKeyboardLayout,
+  0x000c01b1: PhysicalKeyboardKey.launchScreenSaver,
+  0x000c01cb: PhysicalKeyboardKey.launchAssistant,
+  0x000c01b7: PhysicalKeyboardKey.launchAudioBrowser,
+  0x000c0201: PhysicalKeyboardKey.newKey,
+  0x000c0203: PhysicalKeyboardKey.close,
+  0x000c0207: PhysicalKeyboardKey.save,
+  0x000c0208: PhysicalKeyboardKey.print,
+  0x000c0221: PhysicalKeyboardKey.browserSearch,
+  0x000c0223: PhysicalKeyboardKey.browserHome,
+  0x000c0224: PhysicalKeyboardKey.browserBack,
+  0x000c0225: PhysicalKeyboardKey.browserForward,
+  0x000c0226: PhysicalKeyboardKey.browserStop,
+  0x000c0227: PhysicalKeyboardKey.browserRefresh,
+  0x000c022a: PhysicalKeyboardKey.browserFavorites,
+  0x000c022d: PhysicalKeyboardKey.zoomIn,
+  0x000c022e: PhysicalKeyboardKey.zoomOut,
+  0x000c0232: PhysicalKeyboardKey.zoomToggle,
+  0x000c0279: PhysicalKeyboardKey.redo,
+  0x000c0289: PhysicalKeyboardKey.mailReply,
+  0x000c028b: PhysicalKeyboardKey.mailForward,
+  0x000c028c: PhysicalKeyboardKey.mailSend,
+  0x000c029d: PhysicalKeyboardKey.keyboardLayoutSelect,
+  0x000c029f: PhysicalKeyboardKey.showAllWindows,
+  0x0005ff01: PhysicalKeyboardKey.gameButton1,
+  0x0005ff02: PhysicalKeyboardKey.gameButton2,
+  0x0005ff03: PhysicalKeyboardKey.gameButton3,
+  0x0005ff04: PhysicalKeyboardKey.gameButton4,
+  0x0005ff05: PhysicalKeyboardKey.gameButton5,
+  0x0005ff06: PhysicalKeyboardKey.gameButton6,
+  0x0005ff07: PhysicalKeyboardKey.gameButton7,
+  0x0005ff08: PhysicalKeyboardKey.gameButton8,
+  0x0005ff09: PhysicalKeyboardKey.gameButton9,
+  0x0005ff0a: PhysicalKeyboardKey.gameButton10,
+  0x0005ff0b: PhysicalKeyboardKey.gameButton11,
+  0x0005ff0c: PhysicalKeyboardKey.gameButton12,
+  0x0005ff0d: PhysicalKeyboardKey.gameButton13,
+  0x0005ff0e: PhysicalKeyboardKey.gameButton14,
+  0x0005ff0f: PhysicalKeyboardKey.gameButton15,
+  0x0005ff10: PhysicalKeyboardKey.gameButton16,
+  0x0005ff11: PhysicalKeyboardKey.gameButtonA,
+  0x0005ff12: PhysicalKeyboardKey.gameButtonB,
+  0x0005ff13: PhysicalKeyboardKey.gameButtonC,
+  0x0005ff14: PhysicalKeyboardKey.gameButtonLeft1,
+  0x0005ff15: PhysicalKeyboardKey.gameButtonLeft2,
+  0x0005ff16: PhysicalKeyboardKey.gameButtonMode,
+  0x0005ff17: PhysicalKeyboardKey.gameButtonRight1,
+  0x0005ff18: PhysicalKeyboardKey.gameButtonRight2,
+  0x0005ff19: PhysicalKeyboardKey.gameButtonSelect,
+  0x0005ff1a: PhysicalKeyboardKey.gameButtonStart,
+  0x0005ff1b: PhysicalKeyboardKey.gameButtonThumbLeft,
+  0x0005ff1c: PhysicalKeyboardKey.gameButtonThumbRight,
+  0x0005ff1d: PhysicalKeyboardKey.gameButtonX,
+  0x0005ff1e: PhysicalKeyboardKey.gameButtonY,
+  0x0005ff1f: PhysicalKeyboardKey.gameButtonZ,
+  0x00000012: PhysicalKeyboardKey.fn,
+};
+
+/// Maps macOS-specific key code values representing [PhysicalKeyboardKey].
+///
+/// MacOS doesn't provide a scan code, but a virtual keycode to represent a physical key.
+const Map<int, PhysicalKeyboardKey> kMacOsToPhysicalKey = <int, PhysicalKeyboardKey>{
+  0x00000000: PhysicalKeyboardKey.keyA,
+  0x0000000b: PhysicalKeyboardKey.keyB,
+  0x00000008: PhysicalKeyboardKey.keyC,
+  0x00000002: PhysicalKeyboardKey.keyD,
+  0x0000000e: PhysicalKeyboardKey.keyE,
+  0x00000003: PhysicalKeyboardKey.keyF,
+  0x00000005: PhysicalKeyboardKey.keyG,
+  0x00000004: PhysicalKeyboardKey.keyH,
+  0x00000022: PhysicalKeyboardKey.keyI,
+  0x00000026: PhysicalKeyboardKey.keyJ,
+  0x00000028: PhysicalKeyboardKey.keyK,
+  0x00000025: PhysicalKeyboardKey.keyL,
+  0x0000002e: PhysicalKeyboardKey.keyM,
+  0x0000002d: PhysicalKeyboardKey.keyN,
+  0x0000001f: PhysicalKeyboardKey.keyO,
+  0x00000023: PhysicalKeyboardKey.keyP,
+  0x0000000c: PhysicalKeyboardKey.keyQ,
+  0x0000000f: PhysicalKeyboardKey.keyR,
+  0x00000001: PhysicalKeyboardKey.keyS,
+  0x00000011: PhysicalKeyboardKey.keyT,
+  0x00000020: PhysicalKeyboardKey.keyU,
+  0x00000009: PhysicalKeyboardKey.keyV,
+  0x0000000d: PhysicalKeyboardKey.keyW,
+  0x00000007: PhysicalKeyboardKey.keyX,
+  0x00000010: PhysicalKeyboardKey.keyY,
+  0x00000006: PhysicalKeyboardKey.keyZ,
+  0x00000012: PhysicalKeyboardKey.digit1,
+  0x00000013: PhysicalKeyboardKey.digit2,
+  0x00000014: PhysicalKeyboardKey.digit3,
+  0x00000015: PhysicalKeyboardKey.digit4,
+  0x00000017: PhysicalKeyboardKey.digit5,
+  0x00000016: PhysicalKeyboardKey.digit6,
+  0x0000001a: PhysicalKeyboardKey.digit7,
+  0x0000001c: PhysicalKeyboardKey.digit8,
+  0x00000019: PhysicalKeyboardKey.digit9,
+  0x0000001d: PhysicalKeyboardKey.digit0,
+  0x00000024: PhysicalKeyboardKey.enter,
+  0x00000035: PhysicalKeyboardKey.escape,
+  0x00000033: PhysicalKeyboardKey.backspace,
+  0x00000030: PhysicalKeyboardKey.tab,
+  0x00000031: PhysicalKeyboardKey.space,
+  0x0000001b: PhysicalKeyboardKey.minus,
+  0x00000018: PhysicalKeyboardKey.equal,
+  0x00000021: PhysicalKeyboardKey.bracketLeft,
+  0x0000001e: PhysicalKeyboardKey.bracketRight,
+  0x0000002a: PhysicalKeyboardKey.backslash,
+  0x00000029: PhysicalKeyboardKey.semicolon,
+  0x00000027: PhysicalKeyboardKey.quote,
+  0x00000032: PhysicalKeyboardKey.backquote,
+  0x0000002b: PhysicalKeyboardKey.comma,
+  0x0000002f: PhysicalKeyboardKey.period,
+  0x0000002c: PhysicalKeyboardKey.slash,
+  0x00000039: PhysicalKeyboardKey.capsLock,
+  0x0000007a: PhysicalKeyboardKey.f1,
+  0x00000078: PhysicalKeyboardKey.f2,
+  0x00000063: PhysicalKeyboardKey.f3,
+  0x00000076: PhysicalKeyboardKey.f4,
+  0x00000060: PhysicalKeyboardKey.f5,
+  0x00000061: PhysicalKeyboardKey.f6,
+  0x00000062: PhysicalKeyboardKey.f7,
+  0x00000064: PhysicalKeyboardKey.f8,
+  0x00000065: PhysicalKeyboardKey.f9,
+  0x0000006d: PhysicalKeyboardKey.f10,
+  0x00000067: PhysicalKeyboardKey.f11,
+  0x0000006f: PhysicalKeyboardKey.f12,
+  0x00000072: PhysicalKeyboardKey.insert,
+  0x00000073: PhysicalKeyboardKey.home,
+  0x00000074: PhysicalKeyboardKey.pageUp,
+  0x00000075: PhysicalKeyboardKey.delete,
+  0x00000077: PhysicalKeyboardKey.end,
+  0x00000079: PhysicalKeyboardKey.pageDown,
+  0x0000007c: PhysicalKeyboardKey.arrowRight,
+  0x0000007b: PhysicalKeyboardKey.arrowLeft,
+  0x0000007d: PhysicalKeyboardKey.arrowDown,
+  0x0000007e: PhysicalKeyboardKey.arrowUp,
+  0x00000047: PhysicalKeyboardKey.numLock,
+  0x0000004b: PhysicalKeyboardKey.numpadDivide,
+  0x00000043: PhysicalKeyboardKey.numpadMultiply,
+  0x0000004e: PhysicalKeyboardKey.numpadSubtract,
+  0x00000045: PhysicalKeyboardKey.numpadAdd,
+  0x0000004c: PhysicalKeyboardKey.numpadEnter,
+  0x00000053: PhysicalKeyboardKey.numpad1,
+  0x00000054: PhysicalKeyboardKey.numpad2,
+  0x00000055: PhysicalKeyboardKey.numpad3,
+  0x00000056: PhysicalKeyboardKey.numpad4,
+  0x00000057: PhysicalKeyboardKey.numpad5,
+  0x00000058: PhysicalKeyboardKey.numpad6,
+  0x00000059: PhysicalKeyboardKey.numpad7,
+  0x0000005b: PhysicalKeyboardKey.numpad8,
+  0x0000005c: PhysicalKeyboardKey.numpad9,
+  0x00000052: PhysicalKeyboardKey.numpad0,
+  0x00000041: PhysicalKeyboardKey.numpadDecimal,
+  0x0000000a: PhysicalKeyboardKey.intlBackslash,
+  0x0000006e: PhysicalKeyboardKey.contextMenu,
+  0x00000051: PhysicalKeyboardKey.numpadEqual,
+  0x00000069: PhysicalKeyboardKey.f13,
+  0x0000006b: PhysicalKeyboardKey.f14,
+  0x00000071: PhysicalKeyboardKey.f15,
+  0x0000006a: PhysicalKeyboardKey.f16,
+  0x00000040: PhysicalKeyboardKey.f17,
+  0x0000004f: PhysicalKeyboardKey.f18,
+  0x00000050: PhysicalKeyboardKey.f19,
+  0x0000005a: PhysicalKeyboardKey.f20,
+  0x0000004a: PhysicalKeyboardKey.audioVolumeMute,
+  0x00000048: PhysicalKeyboardKey.audioVolumeUp,
+  0x00000049: PhysicalKeyboardKey.audioVolumeDown,
+  0x0000005f: PhysicalKeyboardKey.numpadComma,
+  0x0000005e: PhysicalKeyboardKey.intlRo,
+  0x0000005d: PhysicalKeyboardKey.intlYen,
+  0x00000068: PhysicalKeyboardKey.lang1,
+  0x00000066: PhysicalKeyboardKey.lang2,
+  0x0000003b: PhysicalKeyboardKey.controlLeft,
+  0x00000038: PhysicalKeyboardKey.shiftLeft,
+  0x0000003a: PhysicalKeyboardKey.altLeft,
+  0x00000037: PhysicalKeyboardKey.metaLeft,
+  0x0000003e: PhysicalKeyboardKey.controlRight,
+  0x0000003c: PhysicalKeyboardKey.shiftRight,
+  0x0000003d: PhysicalKeyboardKey.altRight,
+  0x00000036: PhysicalKeyboardKey.metaRight,
+  0x0000003f: PhysicalKeyboardKey.fn,
+};
+
+/// A map of macOS key codes which have printable representations, but appear
+/// on the number pad. Used to provide different key objects for keys like
+/// KEY_EQUALS and NUMPAD_EQUALS.
+const Map<int, LogicalKeyboardKey> kMacOsNumPadMap = <int, LogicalKeyboardKey>{
+  0x0000004b: LogicalKeyboardKey.numpadDivide,
+  0x00000043: LogicalKeyboardKey.numpadMultiply,
+  0x0000004e: LogicalKeyboardKey.numpadSubtract,
+  0x00000045: LogicalKeyboardKey.numpadAdd,
+  0x00000053: LogicalKeyboardKey.numpad1,
+  0x00000054: LogicalKeyboardKey.numpad2,
+  0x00000055: LogicalKeyboardKey.numpad3,
+  0x00000056: LogicalKeyboardKey.numpad4,
+  0x00000057: LogicalKeyboardKey.numpad5,
+  0x00000058: LogicalKeyboardKey.numpad6,
+  0x00000059: LogicalKeyboardKey.numpad7,
+  0x0000005b: LogicalKeyboardKey.numpad8,
+  0x0000005c: LogicalKeyboardKey.numpad9,
+  0x00000052: LogicalKeyboardKey.numpad0,
+  0x00000041: LogicalKeyboardKey.numpadDecimal,
+  0x00000051: LogicalKeyboardKey.numpadEqual,
+  0x0000005f: LogicalKeyboardKey.numpadComma,
+};
+
+/// A map of macOS key codes which are numbered function keys, so that they
+/// can be excluded when asking "is the Fn modifier down?".
+const Map<int, LogicalKeyboardKey> kMacOsFunctionKeyMap = <int, LogicalKeyboardKey>{
+  0x0000007a: LogicalKeyboardKey.f1,
+  0x00000078: LogicalKeyboardKey.f2,
+  0x00000063: LogicalKeyboardKey.f3,
+  0x00000076: LogicalKeyboardKey.f4,
+  0x00000060: LogicalKeyboardKey.f5,
+  0x00000061: LogicalKeyboardKey.f6,
+  0x00000062: LogicalKeyboardKey.f7,
+  0x00000064: LogicalKeyboardKey.f8,
+  0x00000065: LogicalKeyboardKey.f9,
+  0x0000006d: LogicalKeyboardKey.f10,
+  0x00000067: LogicalKeyboardKey.f11,
+  0x0000006f: LogicalKeyboardKey.f12,
+  0x00000069: LogicalKeyboardKey.f13,
+  0x0000006b: LogicalKeyboardKey.f14,
+  0x00000071: LogicalKeyboardKey.f15,
+  0x0000006a: LogicalKeyboardKey.f16,
+  0x00000040: LogicalKeyboardKey.f17,
+  0x0000004f: LogicalKeyboardKey.f18,
+  0x00000050: LogicalKeyboardKey.f19,
+  0x0000005a: LogicalKeyboardKey.f20,
+};
+
+/// Maps iOS-specific key code values representing [PhysicalKeyboardKey].
+///
+/// iOS doesn't provide a scan code, but a virtual keycode to represent a physical key.
+const Map<int, PhysicalKeyboardKey> kIosToPhysicalKey = <int, PhysicalKeyboardKey>{
+  0x00000000: PhysicalKeyboardKey.usbReserved,
+  0x00000001: PhysicalKeyboardKey.usbErrorRollOver,
+  0x00000002: PhysicalKeyboardKey.usbPostFail,
+  0x00000003: PhysicalKeyboardKey.usbErrorUndefined,
+  0x00000004: PhysicalKeyboardKey.keyA,
+  0x00000005: PhysicalKeyboardKey.keyB,
+  0x00000006: PhysicalKeyboardKey.keyC,
+  0x00000007: PhysicalKeyboardKey.keyD,
+  0x00000008: PhysicalKeyboardKey.keyE,
+  0x00000009: PhysicalKeyboardKey.keyF,
+  0x0000000a: PhysicalKeyboardKey.keyG,
+  0x0000000b: PhysicalKeyboardKey.keyH,
+  0x0000000c: PhysicalKeyboardKey.keyI,
+  0x0000000d: PhysicalKeyboardKey.keyJ,
+  0x0000000e: PhysicalKeyboardKey.keyK,
+  0x0000000f: PhysicalKeyboardKey.keyL,
+  0x00000010: PhysicalKeyboardKey.keyM,
+  0x00000011: PhysicalKeyboardKey.keyN,
+  0x00000012: PhysicalKeyboardKey.keyO,
+  0x00000013: PhysicalKeyboardKey.keyP,
+  0x00000014: PhysicalKeyboardKey.keyQ,
+  0x00000015: PhysicalKeyboardKey.keyR,
+  0x00000016: PhysicalKeyboardKey.keyS,
+  0x00000017: PhysicalKeyboardKey.keyT,
+  0x00000018: PhysicalKeyboardKey.keyU,
+  0x00000019: PhysicalKeyboardKey.keyV,
+  0x0000001a: PhysicalKeyboardKey.keyW,
+  0x0000001b: PhysicalKeyboardKey.keyX,
+  0x0000001c: PhysicalKeyboardKey.keyY,
+  0x0000001d: PhysicalKeyboardKey.keyZ,
+  0x0000001e: PhysicalKeyboardKey.digit1,
+  0x0000001f: PhysicalKeyboardKey.digit2,
+  0x00000020: PhysicalKeyboardKey.digit3,
+  0x00000021: PhysicalKeyboardKey.digit4,
+  0x00000022: PhysicalKeyboardKey.digit5,
+  0x00000023: PhysicalKeyboardKey.digit6,
+  0x00000024: PhysicalKeyboardKey.digit7,
+  0x00000025: PhysicalKeyboardKey.digit8,
+  0x00000026: PhysicalKeyboardKey.digit9,
+  0x00000027: PhysicalKeyboardKey.digit0,
+  0x00000028: PhysicalKeyboardKey.enter,
+  0x00000029: PhysicalKeyboardKey.escape,
+  0x0000002a: PhysicalKeyboardKey.backspace,
+  0x0000002b: PhysicalKeyboardKey.tab,
+  0x0000002c: PhysicalKeyboardKey.space,
+  0x0000002d: PhysicalKeyboardKey.minus,
+  0x0000002e: PhysicalKeyboardKey.equal,
+  0x0000002f: PhysicalKeyboardKey.bracketLeft,
+  0x00000030: PhysicalKeyboardKey.bracketRight,
+  0x00000031: PhysicalKeyboardKey.backslash,
+  0x00000033: PhysicalKeyboardKey.semicolon,
+  0x00000034: PhysicalKeyboardKey.quote,
+  0x00000035: PhysicalKeyboardKey.backquote,
+  0x00000036: PhysicalKeyboardKey.comma,
+  0x00000037: PhysicalKeyboardKey.period,
+  0x00000038: PhysicalKeyboardKey.slash,
+  0x00000039: PhysicalKeyboardKey.capsLock,
+  0x0000003a: PhysicalKeyboardKey.f1,
+  0x0000003b: PhysicalKeyboardKey.f2,
+  0x0000003c: PhysicalKeyboardKey.f3,
+  0x0000003d: PhysicalKeyboardKey.f4,
+  0x0000003e: PhysicalKeyboardKey.f5,
+  0x0000003f: PhysicalKeyboardKey.f6,
+  0x00000040: PhysicalKeyboardKey.f7,
+  0x00000041: PhysicalKeyboardKey.f8,
+  0x00000042: PhysicalKeyboardKey.f9,
+  0x00000043: PhysicalKeyboardKey.f10,
+  0x00000044: PhysicalKeyboardKey.f11,
+  0x00000045: PhysicalKeyboardKey.f12,
+  0x00000046: PhysicalKeyboardKey.printScreen,
+  0x00000047: PhysicalKeyboardKey.scrollLock,
+  0x00000048: PhysicalKeyboardKey.pause,
+  0x00000049: PhysicalKeyboardKey.insert,
+  0x0000004a: PhysicalKeyboardKey.home,
+  0x0000004b: PhysicalKeyboardKey.pageUp,
+  0x0000004c: PhysicalKeyboardKey.delete,
+  0x0000004d: PhysicalKeyboardKey.end,
+  0x0000004e: PhysicalKeyboardKey.pageDown,
+  0x0000004f: PhysicalKeyboardKey.arrowRight,
+  0x00000050: PhysicalKeyboardKey.arrowLeft,
+  0x00000051: PhysicalKeyboardKey.arrowDown,
+  0x00000052: PhysicalKeyboardKey.arrowUp,
+  0x00000053: PhysicalKeyboardKey.numLock,
+  0x00000054: PhysicalKeyboardKey.numpadDivide,
+  0x00000055: PhysicalKeyboardKey.numpadMultiply,
+  0x00000056: PhysicalKeyboardKey.numpadSubtract,
+  0x00000057: PhysicalKeyboardKey.numpadAdd,
+  0x00000058: PhysicalKeyboardKey.numpadEnter,
+  0x00000059: PhysicalKeyboardKey.numpad1,
+  0x0000005a: PhysicalKeyboardKey.numpad2,
+  0x0000005b: PhysicalKeyboardKey.numpad3,
+  0x0000005c: PhysicalKeyboardKey.numpad4,
+  0x0000005d: PhysicalKeyboardKey.numpad5,
+  0x0000005e: PhysicalKeyboardKey.numpad6,
+  0x0000005f: PhysicalKeyboardKey.numpad7,
+  0x00000060: PhysicalKeyboardKey.numpad8,
+  0x00000061: PhysicalKeyboardKey.numpad9,
+  0x00000062: PhysicalKeyboardKey.numpad0,
+  0x00000063: PhysicalKeyboardKey.numpadDecimal,
+  0x00000064: PhysicalKeyboardKey.intlBackslash,
+  0x00000065: PhysicalKeyboardKey.contextMenu,
+  0x00000066: PhysicalKeyboardKey.power,
+  0x00000067: PhysicalKeyboardKey.numpadEqual,
+  0x00000068: PhysicalKeyboardKey.f13,
+  0x00000069: PhysicalKeyboardKey.f14,
+  0x0000006a: PhysicalKeyboardKey.f15,
+  0x0000006b: PhysicalKeyboardKey.f16,
+  0x0000006c: PhysicalKeyboardKey.f17,
+  0x0000006d: PhysicalKeyboardKey.f18,
+  0x0000006e: PhysicalKeyboardKey.f19,
+  0x0000006f: PhysicalKeyboardKey.f20,
+  0x00000070: PhysicalKeyboardKey.f21,
+  0x00000071: PhysicalKeyboardKey.f22,
+  0x00000072: PhysicalKeyboardKey.f23,
+  0x00000073: PhysicalKeyboardKey.f24,
+  0x00000074: PhysicalKeyboardKey.open,
+  0x00000075: PhysicalKeyboardKey.help,
+  0x00000077: PhysicalKeyboardKey.select,
+  0x00000079: PhysicalKeyboardKey.again,
+  0x0000007a: PhysicalKeyboardKey.undo,
+  0x0000007b: PhysicalKeyboardKey.cut,
+  0x0000007c: PhysicalKeyboardKey.copy,
+  0x0000007d: PhysicalKeyboardKey.paste,
+  0x0000007e: PhysicalKeyboardKey.find,
+  0x0000007f: PhysicalKeyboardKey.audioVolumeMute,
+  0x00000080: PhysicalKeyboardKey.audioVolumeUp,
+  0x00000081: PhysicalKeyboardKey.audioVolumeDown,
+  0x00000085: PhysicalKeyboardKey.numpadComma,
+  0x00000087: PhysicalKeyboardKey.intlRo,
+  0x00000088: PhysicalKeyboardKey.kanaMode,
+  0x00000089: PhysicalKeyboardKey.intlYen,
+  0x0000008a: PhysicalKeyboardKey.convert,
+  0x0000008b: PhysicalKeyboardKey.nonConvert,
+  0x00000090: PhysicalKeyboardKey.lang1,
+  0x00000091: PhysicalKeyboardKey.lang2,
+  0x00000092: PhysicalKeyboardKey.lang3,
+  0x00000093: PhysicalKeyboardKey.lang4,
+  0x00000094: PhysicalKeyboardKey.lang5,
+  0x0000009b: PhysicalKeyboardKey.abort,
+  0x000000a3: PhysicalKeyboardKey.props,
+  0x000000b6: PhysicalKeyboardKey.numpadParenLeft,
+  0x000000b7: PhysicalKeyboardKey.numpadParenRight,
+  0x000000bb: PhysicalKeyboardKey.numpadBackspace,
+  0x000000d0: PhysicalKeyboardKey.numpadMemoryStore,
+  0x000000d1: PhysicalKeyboardKey.numpadMemoryRecall,
+  0x000000d2: PhysicalKeyboardKey.numpadMemoryClear,
+  0x000000d3: PhysicalKeyboardKey.numpadMemoryAdd,
+  0x000000d4: PhysicalKeyboardKey.numpadMemorySubtract,
+  0x000000d7: PhysicalKeyboardKey.numpadSignChange,
+  0x000000d8: PhysicalKeyboardKey.numpadClear,
+  0x000000d9: PhysicalKeyboardKey.numpadClearEntry,
+  0x000000e0: PhysicalKeyboardKey.controlLeft,
+  0x000000e1: PhysicalKeyboardKey.shiftLeft,
+  0x000000e2: PhysicalKeyboardKey.altLeft,
+  0x000000e3: PhysicalKeyboardKey.metaLeft,
+  0x000000e4: PhysicalKeyboardKey.controlRight,
+  0x000000e5: PhysicalKeyboardKey.shiftRight,
+  0x000000e6: PhysicalKeyboardKey.altRight,
+  0x000000e7: PhysicalKeyboardKey.metaRight,
+};
+
+/// A map of iOS key codes which have printable representations, but appear
+/// on the number pad. Used to provide different key objects for keys like
+/// KEY_EQUALS and NUMPAD_EQUALS.
+const Map<int, LogicalKeyboardKey> kIosNumPadMap = <int, LogicalKeyboardKey>{
+  0x00000054: LogicalKeyboardKey.numpadDivide,
+  0x00000055: LogicalKeyboardKey.numpadMultiply,
+  0x00000056: LogicalKeyboardKey.numpadSubtract,
+  0x00000057: LogicalKeyboardKey.numpadAdd,
+  0x00000059: LogicalKeyboardKey.numpad1,
+  0x0000005a: LogicalKeyboardKey.numpad2,
+  0x0000005b: LogicalKeyboardKey.numpad3,
+  0x0000005c: LogicalKeyboardKey.numpad4,
+  0x0000005d: LogicalKeyboardKey.numpad5,
+  0x0000005e: LogicalKeyboardKey.numpad6,
+  0x0000005f: LogicalKeyboardKey.numpad7,
+  0x00000060: LogicalKeyboardKey.numpad8,
+  0x00000061: LogicalKeyboardKey.numpad9,
+  0x00000062: LogicalKeyboardKey.numpad0,
+  0x00000063: LogicalKeyboardKey.numpadDecimal,
+  0x00000067: LogicalKeyboardKey.numpadEqual,
+  0x00000085: LogicalKeyboardKey.numpadComma,
+  0x000000b6: LogicalKeyboardKey.numpadParenLeft,
+  0x000000b7: LogicalKeyboardKey.numpadParenRight,
+};
+
+/// Maps GLFW-specific key codes to the matching [LogicalKeyboardKey].
+const Map<int, LogicalKeyboardKey> kGlfwToLogicalKey = <int, LogicalKeyboardKey>{
+  65: LogicalKeyboardKey.keyA,
+  66: LogicalKeyboardKey.keyB,
+  67: LogicalKeyboardKey.keyC,
+  68: LogicalKeyboardKey.keyD,
+  69: LogicalKeyboardKey.keyE,
+  70: LogicalKeyboardKey.keyF,
+  71: LogicalKeyboardKey.keyG,
+  72: LogicalKeyboardKey.keyH,
+  73: LogicalKeyboardKey.keyI,
+  74: LogicalKeyboardKey.keyJ,
+  75: LogicalKeyboardKey.keyK,
+  76: LogicalKeyboardKey.keyL,
+  77: LogicalKeyboardKey.keyM,
+  78: LogicalKeyboardKey.keyN,
+  79: LogicalKeyboardKey.keyO,
+  80: LogicalKeyboardKey.keyP,
+  81: LogicalKeyboardKey.keyQ,
+  82: LogicalKeyboardKey.keyR,
+  83: LogicalKeyboardKey.keyS,
+  84: LogicalKeyboardKey.keyT,
+  85: LogicalKeyboardKey.keyU,
+  86: LogicalKeyboardKey.keyV,
+  87: LogicalKeyboardKey.keyW,
+  88: LogicalKeyboardKey.keyX,
+  89: LogicalKeyboardKey.keyY,
+  90: LogicalKeyboardKey.keyZ,
+  49: LogicalKeyboardKey.digit1,
+  50: LogicalKeyboardKey.digit2,
+  51: LogicalKeyboardKey.digit3,
+  52: LogicalKeyboardKey.digit4,
+  53: LogicalKeyboardKey.digit5,
+  54: LogicalKeyboardKey.digit6,
+  55: LogicalKeyboardKey.digit7,
+  56: LogicalKeyboardKey.digit8,
+  57: LogicalKeyboardKey.digit9,
+  48: LogicalKeyboardKey.digit0,
+  257: LogicalKeyboardKey.enter,
+  256: LogicalKeyboardKey.escape,
+  259: LogicalKeyboardKey.backspace,
+  258: LogicalKeyboardKey.tab,
+  32: LogicalKeyboardKey.space,
+  45: LogicalKeyboardKey.minus,
+  61: LogicalKeyboardKey.equal,
+  91: LogicalKeyboardKey.bracketLeft,
+  93: LogicalKeyboardKey.bracketRight,
+  92: LogicalKeyboardKey.backslash,
+  59: LogicalKeyboardKey.semicolon,
+  39: LogicalKeyboardKey.quote,
+  96: LogicalKeyboardKey.backquote,
+  44: LogicalKeyboardKey.comma,
+  46: LogicalKeyboardKey.period,
+  47: LogicalKeyboardKey.slash,
+  280: LogicalKeyboardKey.capsLock,
+  290: LogicalKeyboardKey.f1,
+  291: LogicalKeyboardKey.f2,
+  292: LogicalKeyboardKey.f3,
+  293: LogicalKeyboardKey.f4,
+  294: LogicalKeyboardKey.f5,
+  295: LogicalKeyboardKey.f6,
+  296: LogicalKeyboardKey.f7,
+  297: LogicalKeyboardKey.f8,
+  298: LogicalKeyboardKey.f9,
+  299: LogicalKeyboardKey.f10,
+  300: LogicalKeyboardKey.f11,
+  301: LogicalKeyboardKey.f12,
+  283: LogicalKeyboardKey.printScreen,
+  284: LogicalKeyboardKey.pause,
+  260: LogicalKeyboardKey.insert,
+  268: LogicalKeyboardKey.home,
+  266: LogicalKeyboardKey.pageUp,
+  261: LogicalKeyboardKey.delete,
+  269: LogicalKeyboardKey.end,
+  267: LogicalKeyboardKey.pageDown,
+  262: LogicalKeyboardKey.arrowRight,
+  263: LogicalKeyboardKey.arrowLeft,
+  264: LogicalKeyboardKey.arrowDown,
+  265: LogicalKeyboardKey.arrowUp,
+  282: LogicalKeyboardKey.numLock,
+  331: LogicalKeyboardKey.numpadDivide,
+  332: LogicalKeyboardKey.numpadMultiply,
+  334: LogicalKeyboardKey.numpadAdd,
+  335: LogicalKeyboardKey.numpadEnter,
+  321: LogicalKeyboardKey.numpad1,
+  322: LogicalKeyboardKey.numpad2,
+  323: LogicalKeyboardKey.numpad3,
+  324: LogicalKeyboardKey.numpad4,
+  325: LogicalKeyboardKey.numpad5,
+  326: LogicalKeyboardKey.numpad6,
+  327: LogicalKeyboardKey.numpad7,
+  328: LogicalKeyboardKey.numpad8,
+  329: LogicalKeyboardKey.numpad9,
+  320: LogicalKeyboardKey.numpad0,
+  330: LogicalKeyboardKey.numpadDecimal,
+  348: LogicalKeyboardKey.contextMenu,
+  336: LogicalKeyboardKey.numpadEqual,
+  302: LogicalKeyboardKey.f13,
+  303: LogicalKeyboardKey.f14,
+  304: LogicalKeyboardKey.f15,
+  305: LogicalKeyboardKey.f16,
+  306: LogicalKeyboardKey.f17,
+  307: LogicalKeyboardKey.f18,
+  308: LogicalKeyboardKey.f19,
+  309: LogicalKeyboardKey.f20,
+  310: LogicalKeyboardKey.f21,
+  311: LogicalKeyboardKey.f22,
+  312: LogicalKeyboardKey.f23,
+  341: LogicalKeyboardKey.controlLeft,
+  340: LogicalKeyboardKey.shiftLeft,
+  342: LogicalKeyboardKey.altLeft,
+  343: LogicalKeyboardKey.metaLeft,
+  345: LogicalKeyboardKey.controlRight,
+  344: LogicalKeyboardKey.shiftRight,
+  346: LogicalKeyboardKey.altRight,
+  347: LogicalKeyboardKey.metaRight,
+};
+
+/// A map of GLFW key codes which have printable representations, but appear
+/// on the number pad. Used to provide different key objects for keys like
+/// KEY_EQUALS and NUMPAD_EQUALS.
+const Map<int, LogicalKeyboardKey> kGlfwNumpadMap = <int, LogicalKeyboardKey>{
+  331: LogicalKeyboardKey.numpadDivide,
+  332: LogicalKeyboardKey.numpadMultiply,
+  334: LogicalKeyboardKey.numpadAdd,
+  321: LogicalKeyboardKey.numpad1,
+  322: LogicalKeyboardKey.numpad2,
+  323: LogicalKeyboardKey.numpad3,
+  324: LogicalKeyboardKey.numpad4,
+  325: LogicalKeyboardKey.numpad5,
+  326: LogicalKeyboardKey.numpad6,
+  327: LogicalKeyboardKey.numpad7,
+  328: LogicalKeyboardKey.numpad8,
+  329: LogicalKeyboardKey.numpad9,
+  320: LogicalKeyboardKey.numpad0,
+  330: LogicalKeyboardKey.numpadDecimal,
+  336: LogicalKeyboardKey.numpadEqual,
+};
+
+/// Maps GTK-specific key codes to the matching [LogicalKeyboardKey].
+const Map<int, LogicalKeyboardKey> kGtkToLogicalKey = <int, LogicalKeyboardKey>{
+  65517: LogicalKeyboardKey.hyper,
+  65518: LogicalKeyboardKey.hyper,
+  65515: LogicalKeyboardKey.superKey,
+  65516: LogicalKeyboardKey.superKey,
+  269025191: LogicalKeyboardKey.suspend,
+  269025071: LogicalKeyboardKey.sleep,
+  269025067: LogicalKeyboardKey.wakeUp,
+  65: LogicalKeyboardKey.keyA,
+  66: LogicalKeyboardKey.keyB,
+  67: LogicalKeyboardKey.keyC,
+  68: LogicalKeyboardKey.keyD,
+  69: LogicalKeyboardKey.keyE,
+  70: LogicalKeyboardKey.keyF,
+  71: LogicalKeyboardKey.keyG,
+  72: LogicalKeyboardKey.keyH,
+  73: LogicalKeyboardKey.keyI,
+  74: LogicalKeyboardKey.keyJ,
+  75: LogicalKeyboardKey.keyK,
+  76: LogicalKeyboardKey.keyL,
+  77: LogicalKeyboardKey.keyM,
+  78: LogicalKeyboardKey.keyN,
+  79: LogicalKeyboardKey.keyO,
+  80: LogicalKeyboardKey.keyP,
+  81: LogicalKeyboardKey.keyQ,
+  82: LogicalKeyboardKey.keyR,
+  83: LogicalKeyboardKey.keyS,
+  84: LogicalKeyboardKey.keyT,
+  85: LogicalKeyboardKey.keyU,
+  86: LogicalKeyboardKey.keyV,
+  87: LogicalKeyboardKey.keyW,
+  88: LogicalKeyboardKey.keyX,
+  89: LogicalKeyboardKey.keyY,
+  90: LogicalKeyboardKey.keyZ,
+  49: LogicalKeyboardKey.digit1,
+  50: LogicalKeyboardKey.digit2,
+  51: LogicalKeyboardKey.digit3,
+  52: LogicalKeyboardKey.digit4,
+  53: LogicalKeyboardKey.digit5,
+  54: LogicalKeyboardKey.digit6,
+  55: LogicalKeyboardKey.digit7,
+  56: LogicalKeyboardKey.digit8,
+  57: LogicalKeyboardKey.digit9,
+  48: LogicalKeyboardKey.digit0,
+  65293: LogicalKeyboardKey.enter,
+  65076: LogicalKeyboardKey.enter,
+  65307: LogicalKeyboardKey.escape,
+  65288: LogicalKeyboardKey.backspace,
+  65289: LogicalKeyboardKey.tab,
+  65417: LogicalKeyboardKey.tab,
+  65056: LogicalKeyboardKey.tab,
+  32: LogicalKeyboardKey.space,
+  65408: LogicalKeyboardKey.space,
+  45: LogicalKeyboardKey.minus,
+  61: LogicalKeyboardKey.equal,
+  91: LogicalKeyboardKey.bracketLeft,
+  93: LogicalKeyboardKey.bracketRight,
+  92: LogicalKeyboardKey.backslash,
+  59: LogicalKeyboardKey.semicolon,
+  39: LogicalKeyboardKey.quote,
+  96: LogicalKeyboardKey.backquote,
+  44: LogicalKeyboardKey.comma,
+  46: LogicalKeyboardKey.period,
+  47: LogicalKeyboardKey.slash,
+  65509: LogicalKeyboardKey.capsLock,
+  65470: LogicalKeyboardKey.f1,
+  65425: LogicalKeyboardKey.f1,
+  65471: LogicalKeyboardKey.f2,
+  65426: LogicalKeyboardKey.f2,
+  65472: LogicalKeyboardKey.f3,
+  65427: LogicalKeyboardKey.f3,
+  65473: LogicalKeyboardKey.f4,
+  65428: LogicalKeyboardKey.f4,
+  65474: LogicalKeyboardKey.f5,
+  65475: LogicalKeyboardKey.f6,
+  65476: LogicalKeyboardKey.f7,
+  65477: LogicalKeyboardKey.f8,
+  65478: LogicalKeyboardKey.f9,
+  65479: LogicalKeyboardKey.f10,
+  65480: LogicalKeyboardKey.f11,
+  65481: LogicalKeyboardKey.f12,
+  64797: LogicalKeyboardKey.printScreen,
+  65300: LogicalKeyboardKey.scrollLock,
+  65299: LogicalKeyboardKey.pause,
+  65379: LogicalKeyboardKey.insert,
+  65438: LogicalKeyboardKey.insert,
+  65360: LogicalKeyboardKey.home,
+  65429: LogicalKeyboardKey.home,
+  65365: LogicalKeyboardKey.pageUp,
+  65434: LogicalKeyboardKey.pageUp,
+  65535: LogicalKeyboardKey.delete,
+  65439: LogicalKeyboardKey.delete,
+  65367: LogicalKeyboardKey.end,
+  65436: LogicalKeyboardKey.end,
+  65366: LogicalKeyboardKey.pageDown,
+  65435: LogicalKeyboardKey.pageDown,
+  65363: LogicalKeyboardKey.arrowRight,
+  65432: LogicalKeyboardKey.arrowRight,
+  65361: LogicalKeyboardKey.arrowLeft,
+  65430: LogicalKeyboardKey.arrowLeft,
+  65364: LogicalKeyboardKey.arrowDown,
+  65433: LogicalKeyboardKey.arrowDown,
+  65362: LogicalKeyboardKey.arrowUp,
+  65431: LogicalKeyboardKey.arrowUp,
+  65407: LogicalKeyboardKey.numLock,
+  65455: LogicalKeyboardKey.numpadDivide,
+  65450: LogicalKeyboardKey.numpadMultiply,
+  65453: LogicalKeyboardKey.numpadSubtract,
+  65451: LogicalKeyboardKey.numpadAdd,
+  65421: LogicalKeyboardKey.numpadEnter,
+  65457: LogicalKeyboardKey.numpad1,
+  65458: LogicalKeyboardKey.numpad2,
+  65459: LogicalKeyboardKey.numpad3,
+  65460: LogicalKeyboardKey.numpad4,
+  65461: LogicalKeyboardKey.numpad5,
+  65462: LogicalKeyboardKey.numpad6,
+  65463: LogicalKeyboardKey.numpad7,
+  65464: LogicalKeyboardKey.numpad8,
+  65465: LogicalKeyboardKey.numpad9,
+  65456: LogicalKeyboardKey.numpad0,
+  65454: LogicalKeyboardKey.numpadDecimal,
+  65383: LogicalKeyboardKey.contextMenu,
+  269025066: LogicalKeyboardKey.power,
+  65469: LogicalKeyboardKey.numpadEqual,
+  65482: LogicalKeyboardKey.f13,
+  65483: LogicalKeyboardKey.f14,
+  65484: LogicalKeyboardKey.f15,
+  65485: LogicalKeyboardKey.f16,
+  65486: LogicalKeyboardKey.f17,
+  65487: LogicalKeyboardKey.f18,
+  65488: LogicalKeyboardKey.f19,
+  65489: LogicalKeyboardKey.f20,
+  65490: LogicalKeyboardKey.f21,
+  65491: LogicalKeyboardKey.f22,
+  65492: LogicalKeyboardKey.f23,
+  65493: LogicalKeyboardKey.f24,
+  269025131: LogicalKeyboardKey.open,
+  65386: LogicalKeyboardKey.help,
+  65376: LogicalKeyboardKey.select,
+  65381: LogicalKeyboardKey.undo,
+  269025111: LogicalKeyboardKey.copy,
+  64789: LogicalKeyboardKey.copy,
+  269025133: LogicalKeyboardKey.paste,
+  65384: LogicalKeyboardKey.find,
+  269025042: LogicalKeyboardKey.audioVolumeMute,
+  269025043: LogicalKeyboardKey.audioVolumeUp,
+  269025041: LogicalKeyboardKey.audioVolumeDown,
+  65406: LogicalKeyboardKey.kanaMode,
+  165: LogicalKeyboardKey.intlYen,
+  65507: LogicalKeyboardKey.controlLeft,
+  65505: LogicalKeyboardKey.shiftLeft,
+  65513: LogicalKeyboardKey.altLeft,
+  65511: LogicalKeyboardKey.metaLeft,
+  65508: LogicalKeyboardKey.controlRight,
+  65506: LogicalKeyboardKey.shiftRight,
+  65514: LogicalKeyboardKey.altRight,
+  65512: LogicalKeyboardKey.metaRight,
+  269025026: LogicalKeyboardKey.brightnessUp,
+  269025027: LogicalKeyboardKey.brightnessDown,
+  269025029: LogicalKeyboardKey.kbdIllumUp,
+  269025030: LogicalKeyboardKey.kbdIllumDown,
+  269025134: LogicalKeyboardKey.launchPhone,
+  269025044: LogicalKeyboardKey.mediaPlay,
+  64790: LogicalKeyboardKey.mediaPlay,
+  269025073: LogicalKeyboardKey.mediaPause,
+  269025052: LogicalKeyboardKey.mediaRecord,
+  269025175: LogicalKeyboardKey.mediaFastForward,
+  269025086: LogicalKeyboardKey.mediaRewind,
+  269025047: LogicalKeyboardKey.mediaTrackNext,
+  269025046: LogicalKeyboardKey.mediaTrackPrevious,
+  269025045: LogicalKeyboardKey.mediaStop,
+  269025068: LogicalKeyboardKey.eject,
+  269025049: LogicalKeyboardKey.launchMail,
+  269025056: LogicalKeyboardKey.launchCalendar,
+  269025070: LogicalKeyboardKey.launchInternetBrowser,
+  269025121: LogicalKeyboardKey.logOff,
+  269025148: LogicalKeyboardKey.spellCheck,
+  269025069: LogicalKeyboardKey.launchScreenSaver,
+  269025170: LogicalKeyboardKey.launchAudioBrowser,
+  269025128: LogicalKeyboardKey.newKey,
+  269025110: LogicalKeyboardKey.close,
+  269025143: LogicalKeyboardKey.save,
+  65377: LogicalKeyboardKey.print,
+  269025051: LogicalKeyboardKey.browserSearch,
+  269025048: LogicalKeyboardKey.browserHome,
+  269025062: LogicalKeyboardKey.browserBack,
+  269025063: LogicalKeyboardKey.browserForward,
+  269025064: LogicalKeyboardKey.browserStop,
+  269025065: LogicalKeyboardKey.browserRefresh,
+  269025072: LogicalKeyboardKey.browserFavorites,
+  269025163: LogicalKeyboardKey.zoomIn,
+  269025164: LogicalKeyboardKey.zoomOut,
+  65382: LogicalKeyboardKey.redo,
+  269025138: LogicalKeyboardKey.mailReply,
+  269025168: LogicalKeyboardKey.mailForward,
+  269025147: LogicalKeyboardKey.mailSend,
+};
+
+/// A map of GTK key codes which have printable representations, but appear
+/// on the number pad. Used to provide different key objects for keys like
+/// KEY_EQUALS and NUMPAD_EQUALS.
+const Map<int, LogicalKeyboardKey> kGtkNumpadMap = <int, LogicalKeyboardKey>{
+  65455: LogicalKeyboardKey.numpadDivide,
+  65450: LogicalKeyboardKey.numpadMultiply,
+  65453: LogicalKeyboardKey.numpadSubtract,
+  65451: LogicalKeyboardKey.numpadAdd,
+  65457: LogicalKeyboardKey.numpad1,
+  65458: LogicalKeyboardKey.numpad2,
+  65459: LogicalKeyboardKey.numpad3,
+  65460: LogicalKeyboardKey.numpad4,
+  65461: LogicalKeyboardKey.numpad5,
+  65462: LogicalKeyboardKey.numpad6,
+  65463: LogicalKeyboardKey.numpad7,
+  65464: LogicalKeyboardKey.numpad8,
+  65465: LogicalKeyboardKey.numpad9,
+  65456: LogicalKeyboardKey.numpad0,
+  65454: LogicalKeyboardKey.numpadDecimal,
+  65469: LogicalKeyboardKey.numpadEqual,
+};
+
+/// Maps XKB specific key code values representing [PhysicalKeyboardKey].
+const Map<int, PhysicalKeyboardKey> kLinuxToPhysicalKey = <int, PhysicalKeyboardKey>{
+  0x00000281: PhysicalKeyboardKey.privacyScreenToggle,
+  0x00000096: PhysicalKeyboardKey.sleep,
+  0x00000097: PhysicalKeyboardKey.wakeUp,
+  0x000000eb: PhysicalKeyboardKey.displayToggleIntExt,
+  0x00000026: PhysicalKeyboardKey.keyA,
+  0x00000038: PhysicalKeyboardKey.keyB,
+  0x00000036: PhysicalKeyboardKey.keyC,
+  0x00000028: PhysicalKeyboardKey.keyD,
+  0x0000001a: PhysicalKeyboardKey.keyE,
+  0x00000029: PhysicalKeyboardKey.keyF,
+  0x0000002a: PhysicalKeyboardKey.keyG,
+  0x0000002b: PhysicalKeyboardKey.keyH,
+  0x0000001f: PhysicalKeyboardKey.keyI,
+  0x0000002c: PhysicalKeyboardKey.keyJ,
+  0x0000002d: PhysicalKeyboardKey.keyK,
+  0x0000002e: PhysicalKeyboardKey.keyL,
+  0x0000003a: PhysicalKeyboardKey.keyM,
+  0x00000039: PhysicalKeyboardKey.keyN,
+  0x00000020: PhysicalKeyboardKey.keyO,
+  0x00000021: PhysicalKeyboardKey.keyP,
+  0x00000018: PhysicalKeyboardKey.keyQ,
+  0x0000001b: PhysicalKeyboardKey.keyR,
+  0x00000027: PhysicalKeyboardKey.keyS,
+  0x0000001c: PhysicalKeyboardKey.keyT,
+  0x0000001e: PhysicalKeyboardKey.keyU,
+  0x00000037: PhysicalKeyboardKey.keyV,
+  0x00000019: PhysicalKeyboardKey.keyW,
+  0x00000035: PhysicalKeyboardKey.keyX,
+  0x0000001d: PhysicalKeyboardKey.keyY,
+  0x00000034: PhysicalKeyboardKey.keyZ,
+  0x0000000a: PhysicalKeyboardKey.digit1,
+  0x0000000b: PhysicalKeyboardKey.digit2,
+  0x0000000c: PhysicalKeyboardKey.digit3,
+  0x0000000d: PhysicalKeyboardKey.digit4,
+  0x0000000e: PhysicalKeyboardKey.digit5,
+  0x0000000f: PhysicalKeyboardKey.digit6,
+  0x00000010: PhysicalKeyboardKey.digit7,
+  0x00000011: PhysicalKeyboardKey.digit8,
+  0x00000012: PhysicalKeyboardKey.digit9,
+  0x00000013: PhysicalKeyboardKey.digit0,
+  0x00000024: PhysicalKeyboardKey.enter,
+  0x00000009: PhysicalKeyboardKey.escape,
+  0x00000016: PhysicalKeyboardKey.backspace,
+  0x00000017: PhysicalKeyboardKey.tab,
+  0x00000041: PhysicalKeyboardKey.space,
+  0x00000014: PhysicalKeyboardKey.minus,
+  0x00000015: PhysicalKeyboardKey.equal,
+  0x00000022: PhysicalKeyboardKey.bracketLeft,
+  0x00000023: PhysicalKeyboardKey.bracketRight,
+  0x00000033: PhysicalKeyboardKey.backslash,
+  0x0000002f: PhysicalKeyboardKey.semicolon,
+  0x00000030: PhysicalKeyboardKey.quote,
+  0x00000031: PhysicalKeyboardKey.backquote,
+  0x0000003b: PhysicalKeyboardKey.comma,
+  0x0000003c: PhysicalKeyboardKey.period,
+  0x0000003d: PhysicalKeyboardKey.slash,
+  0x00000042: PhysicalKeyboardKey.capsLock,
+  0x00000043: PhysicalKeyboardKey.f1,
+  0x00000044: PhysicalKeyboardKey.f2,
+  0x00000045: PhysicalKeyboardKey.f3,
+  0x00000046: PhysicalKeyboardKey.f4,
+  0x00000047: PhysicalKeyboardKey.f5,
+  0x00000048: PhysicalKeyboardKey.f6,
+  0x00000049: PhysicalKeyboardKey.f7,
+  0x0000004a: PhysicalKeyboardKey.f8,
+  0x0000004b: PhysicalKeyboardKey.f9,
+  0x0000004c: PhysicalKeyboardKey.f10,
+  0x0000005f: PhysicalKeyboardKey.f11,
+  0x00000060: PhysicalKeyboardKey.f12,
+  0x0000006b: PhysicalKeyboardKey.printScreen,
+  0x0000004e: PhysicalKeyboardKey.scrollLock,
+  0x0000007f: PhysicalKeyboardKey.pause,
+  0x00000076: PhysicalKeyboardKey.insert,
+  0x0000006e: PhysicalKeyboardKey.home,
+  0x00000070: PhysicalKeyboardKey.pageUp,
+  0x00000077: PhysicalKeyboardKey.delete,
+  0x00000073: PhysicalKeyboardKey.end,
+  0x00000075: PhysicalKeyboardKey.pageDown,
+  0x00000072: PhysicalKeyboardKey.arrowRight,
+  0x00000071: PhysicalKeyboardKey.arrowLeft,
+  0x00000074: PhysicalKeyboardKey.arrowDown,
+  0x0000006f: PhysicalKeyboardKey.arrowUp,
+  0x0000004d: PhysicalKeyboardKey.numLock,
+  0x0000006a: PhysicalKeyboardKey.numpadDivide,
+  0x0000003f: PhysicalKeyboardKey.numpadMultiply,
+  0x00000052: PhysicalKeyboardKey.numpadSubtract,
+  0x00000056: PhysicalKeyboardKey.numpadAdd,
+  0x00000068: PhysicalKeyboardKey.numpadEnter,
+  0x00000057: PhysicalKeyboardKey.numpad1,
+  0x00000058: PhysicalKeyboardKey.numpad2,
+  0x00000059: PhysicalKeyboardKey.numpad3,
+  0x00000053: PhysicalKeyboardKey.numpad4,
+  0x00000054: PhysicalKeyboardKey.numpad5,
+  0x00000055: PhysicalKeyboardKey.numpad6,
+  0x0000004f: PhysicalKeyboardKey.numpad7,
+  0x00000050: PhysicalKeyboardKey.numpad8,
+  0x00000051: PhysicalKeyboardKey.numpad9,
+  0x0000005a: PhysicalKeyboardKey.numpad0,
+  0x0000005b: PhysicalKeyboardKey.numpadDecimal,
+  0x0000005e: PhysicalKeyboardKey.intlBackslash,
+  0x00000087: PhysicalKeyboardKey.contextMenu,
+  0x0000007c: PhysicalKeyboardKey.power,
+  0x0000007d: PhysicalKeyboardKey.numpadEqual,
+  0x000000bf: PhysicalKeyboardKey.f13,
+  0x000000c0: PhysicalKeyboardKey.f14,
+  0x000000c1: PhysicalKeyboardKey.f15,
+  0x000000c2: PhysicalKeyboardKey.f16,
+  0x000000c3: PhysicalKeyboardKey.f17,
+  0x000000c4: PhysicalKeyboardKey.f18,
+  0x000000c5: PhysicalKeyboardKey.f19,
+  0x000000c6: PhysicalKeyboardKey.f20,
+  0x000000c7: PhysicalKeyboardKey.f21,
+  0x000000c8: PhysicalKeyboardKey.f22,
+  0x000000c9: PhysicalKeyboardKey.f23,
+  0x000000ca: PhysicalKeyboardKey.f24,
+  0x0000008e: PhysicalKeyboardKey.open,
+  0x00000092: PhysicalKeyboardKey.help,
+  0x0000008c: PhysicalKeyboardKey.select,
+  0x00000089: PhysicalKeyboardKey.again,
+  0x0000008b: PhysicalKeyboardKey.undo,
+  0x00000091: PhysicalKeyboardKey.cut,
+  0x0000008d: PhysicalKeyboardKey.copy,
+  0x0000008f: PhysicalKeyboardKey.paste,
+  0x00000090: PhysicalKeyboardKey.find,
+  0x00000079: PhysicalKeyboardKey.audioVolumeMute,
+  0x0000007b: PhysicalKeyboardKey.audioVolumeUp,
+  0x0000007a: PhysicalKeyboardKey.audioVolumeDown,
+  0x00000081: PhysicalKeyboardKey.numpadComma,
+  0x00000061: PhysicalKeyboardKey.intlRo,
+  0x00000065: PhysicalKeyboardKey.kanaMode,
+  0x00000084: PhysicalKeyboardKey.intlYen,
+  0x00000064: PhysicalKeyboardKey.convert,
+  0x00000066: PhysicalKeyboardKey.nonConvert,
+  0x00000082: PhysicalKeyboardKey.lang1,
+  0x00000083: PhysicalKeyboardKey.lang2,
+  0x00000062: PhysicalKeyboardKey.lang3,
+  0x00000063: PhysicalKeyboardKey.lang4,
+  0x0000005d: PhysicalKeyboardKey.lang5,
+  0x000000bb: PhysicalKeyboardKey.numpadParenLeft,
+  0x000000bc: PhysicalKeyboardKey.numpadParenRight,
+  0x0000007e: PhysicalKeyboardKey.numpadSignChange,
+  0x00000025: PhysicalKeyboardKey.controlLeft,
+  0x00000032: PhysicalKeyboardKey.shiftLeft,
+  0x00000040: PhysicalKeyboardKey.altLeft,
+  0x00000085: PhysicalKeyboardKey.metaLeft,
+  0x00000069: PhysicalKeyboardKey.controlRight,
+  0x0000003e: PhysicalKeyboardKey.shiftRight,
+  0x0000006c: PhysicalKeyboardKey.altRight,
+  0x00000086: PhysicalKeyboardKey.metaRight,
+  0x0000016e: PhysicalKeyboardKey.info,
+  0x0000017a: PhysicalKeyboardKey.closedCaptionToggle,
+  0x000000e9: PhysicalKeyboardKey.brightnessUp,
+  0x000000e8: PhysicalKeyboardKey.brightnessDown,
+  0x000001b7: PhysicalKeyboardKey.brightnessToggle,
+  0x00000258: PhysicalKeyboardKey.brightnessMinimum,
+  0x00000259: PhysicalKeyboardKey.brightnessMaximum,
+  0x000000fc: PhysicalKeyboardKey.brightnessAuto,
+  0x000000ee: PhysicalKeyboardKey.kbdIllumUp,
+  0x000000ed: PhysicalKeyboardKey.kbdIllumDown,
+  0x0000019d: PhysicalKeyboardKey.mediaLast,
+  0x000000b1: PhysicalKeyboardKey.launchPhone,
+  0x00000172: PhysicalKeyboardKey.programGuide,
+  0x000000b6: PhysicalKeyboardKey.exit,
+  0x000001a2: PhysicalKeyboardKey.channelUp,
+  0x000001a3: PhysicalKeyboardKey.channelDown,
+  0x000000d7: PhysicalKeyboardKey.mediaPlay,
+  0x000000d1: PhysicalKeyboardKey.mediaPause,
+  0x000000af: PhysicalKeyboardKey.mediaRecord,
+  0x000000d8: PhysicalKeyboardKey.mediaFastForward,
+  0x000000b0: PhysicalKeyboardKey.mediaRewind,
+  0x000000ab: PhysicalKeyboardKey.mediaTrackNext,
+  0x000000ad: PhysicalKeyboardKey.mediaTrackPrevious,
+  0x000000ae: PhysicalKeyboardKey.mediaStop,
+  0x000000a9: PhysicalKeyboardKey.eject,
+  0x000000ac: PhysicalKeyboardKey.mediaPlayPause,
+  0x0000024e: PhysicalKeyboardKey.speechInputToggle,
+  0x000000d9: PhysicalKeyboardKey.bassBoost,
+  0x000000b3: PhysicalKeyboardKey.mediaSelect,
+  0x000001ad: PhysicalKeyboardKey.launchWordProcessor,
+  0x000001af: PhysicalKeyboardKey.launchSpreadsheet,
+  0x000000a3: PhysicalKeyboardKey.launchMail,
+  0x000001b5: PhysicalKeyboardKey.launchContacts,
+  0x00000195: PhysicalKeyboardKey.launchCalendar,
+  0x00000094: PhysicalKeyboardKey.launchApp2,
+  0x00000098: PhysicalKeyboardKey.launchApp1,
+  0x0000009e: PhysicalKeyboardKey.launchInternetBrowser,
+  0x000001b9: PhysicalKeyboardKey.logOff,
+  0x000000a0: PhysicalKeyboardKey.lockScreen,
+  0x0000024b: PhysicalKeyboardKey.launchControlPanel,
+  0x0000024c: PhysicalKeyboardKey.selectTask,
+  0x000000f3: PhysicalKeyboardKey.launchDocuments,
+  0x000001b8: PhysicalKeyboardKey.spellCheck,
+  0x0000017e: PhysicalKeyboardKey.launchKeyboardLayout,
+  0x0000024d: PhysicalKeyboardKey.launchScreenSaver,
+  0x0000024f: PhysicalKeyboardKey.launchAssistant,
+  0x00000190: PhysicalKeyboardKey.launchAudioBrowser,
+  0x000000bd: PhysicalKeyboardKey.newKey,
+  0x000000d6: PhysicalKeyboardKey.close,
+  0x000000f2: PhysicalKeyboardKey.save,
+  0x000000da: PhysicalKeyboardKey.print,
+  0x000000e1: PhysicalKeyboardKey.browserSearch,
+  0x000000b4: PhysicalKeyboardKey.browserHome,
+  0x000000a6: PhysicalKeyboardKey.browserBack,
+  0x000000a7: PhysicalKeyboardKey.browserForward,
+  0x00000088: PhysicalKeyboardKey.browserStop,
+  0x000000b5: PhysicalKeyboardKey.browserRefresh,
+  0x000000a4: PhysicalKeyboardKey.browserFavorites,
+  0x000001aa: PhysicalKeyboardKey.zoomIn,
+  0x000001ab: PhysicalKeyboardKey.zoomOut,
+  0x0000017c: PhysicalKeyboardKey.zoomToggle,
+  0x000000be: PhysicalKeyboardKey.redo,
+  0x000000f0: PhysicalKeyboardKey.mailReply,
+  0x000000f1: PhysicalKeyboardKey.mailForward,
+  0x000000ef: PhysicalKeyboardKey.mailSend,
+  0x00000250: PhysicalKeyboardKey.keyboardLayoutSelect,
+  0x00000080: PhysicalKeyboardKey.showAllWindows,
+};
+
+/// Maps Web KeyboardEvent codes to the matching [LogicalKeyboardKey].
+const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboardKey>{
+  'None': LogicalKeyboardKey.none,
+  'Hyper': LogicalKeyboardKey.hyper,
+  'Super': LogicalKeyboardKey.superKey,
+  'FnLock': LogicalKeyboardKey.fnLock,
+  'Suspend': LogicalKeyboardKey.suspend,
+  'Resume': LogicalKeyboardKey.resume,
+  'Turbo': LogicalKeyboardKey.turbo,
+  'PrivacyScreenToggle': LogicalKeyboardKey.privacyScreenToggle,
+  'Sleep': LogicalKeyboardKey.sleep,
+  'WakeUp': LogicalKeyboardKey.wakeUp,
+  'DisplayToggleIntExt': LogicalKeyboardKey.displayToggleIntExt,
+  'KeyA': LogicalKeyboardKey.keyA,
+  'KeyB': LogicalKeyboardKey.keyB,
+  'KeyC': LogicalKeyboardKey.keyC,
+  'KeyD': LogicalKeyboardKey.keyD,
+  'KeyE': LogicalKeyboardKey.keyE,
+  'KeyF': LogicalKeyboardKey.keyF,
+  'KeyG': LogicalKeyboardKey.keyG,
+  'KeyH': LogicalKeyboardKey.keyH,
+  'KeyI': LogicalKeyboardKey.keyI,
+  'KeyJ': LogicalKeyboardKey.keyJ,
+  'KeyK': LogicalKeyboardKey.keyK,
+  'KeyL': LogicalKeyboardKey.keyL,
+  'KeyM': LogicalKeyboardKey.keyM,
+  'KeyN': LogicalKeyboardKey.keyN,
+  'KeyO': LogicalKeyboardKey.keyO,
+  'KeyP': LogicalKeyboardKey.keyP,
+  'KeyQ': LogicalKeyboardKey.keyQ,
+  'KeyR': LogicalKeyboardKey.keyR,
+  'KeyS': LogicalKeyboardKey.keyS,
+  'KeyT': LogicalKeyboardKey.keyT,
+  'KeyU': LogicalKeyboardKey.keyU,
+  'KeyV': LogicalKeyboardKey.keyV,
+  'KeyW': LogicalKeyboardKey.keyW,
+  'KeyX': LogicalKeyboardKey.keyX,
+  'KeyY': LogicalKeyboardKey.keyY,
+  'KeyZ': LogicalKeyboardKey.keyZ,
+  'Digit1': LogicalKeyboardKey.digit1,
+  'Digit2': LogicalKeyboardKey.digit2,
+  'Digit3': LogicalKeyboardKey.digit3,
+  'Digit4': LogicalKeyboardKey.digit4,
+  'Digit5': LogicalKeyboardKey.digit5,
+  'Digit6': LogicalKeyboardKey.digit6,
+  'Digit7': LogicalKeyboardKey.digit7,
+  'Digit8': LogicalKeyboardKey.digit8,
+  'Digit9': LogicalKeyboardKey.digit9,
+  'Digit0': LogicalKeyboardKey.digit0,
+  'Enter': LogicalKeyboardKey.enter,
+  'Escape': LogicalKeyboardKey.escape,
+  'Backspace': LogicalKeyboardKey.backspace,
+  'Tab': LogicalKeyboardKey.tab,
+  'Space': LogicalKeyboardKey.space,
+  'Minus': LogicalKeyboardKey.minus,
+  'Equal': LogicalKeyboardKey.equal,
+  'BracketLeft': LogicalKeyboardKey.bracketLeft,
+  'BracketRight': LogicalKeyboardKey.bracketRight,
+  'Backslash': LogicalKeyboardKey.backslash,
+  'Semicolon': LogicalKeyboardKey.semicolon,
+  'Quote': LogicalKeyboardKey.quote,
+  'Backquote': LogicalKeyboardKey.backquote,
+  'Comma': LogicalKeyboardKey.comma,
+  'Period': LogicalKeyboardKey.period,
+  'Slash': LogicalKeyboardKey.slash,
+  'CapsLock': LogicalKeyboardKey.capsLock,
+  'F1': LogicalKeyboardKey.f1,
+  'F2': LogicalKeyboardKey.f2,
+  'F3': LogicalKeyboardKey.f3,
+  'F4': LogicalKeyboardKey.f4,
+  'F5': LogicalKeyboardKey.f5,
+  'F6': LogicalKeyboardKey.f6,
+  'F7': LogicalKeyboardKey.f7,
+  'F8': LogicalKeyboardKey.f8,
+  'F9': LogicalKeyboardKey.f9,
+  'F10': LogicalKeyboardKey.f10,
+  'F11': LogicalKeyboardKey.f11,
+  'F12': LogicalKeyboardKey.f12,
+  'PrintScreen': LogicalKeyboardKey.printScreen,
+  'ScrollLock': LogicalKeyboardKey.scrollLock,
+  'Pause': LogicalKeyboardKey.pause,
+  'Insert': LogicalKeyboardKey.insert,
+  'Home': LogicalKeyboardKey.home,
+  'PageUp': LogicalKeyboardKey.pageUp,
+  'Delete': LogicalKeyboardKey.delete,
+  'End': LogicalKeyboardKey.end,
+  'PageDown': LogicalKeyboardKey.pageDown,
+  'ArrowRight': LogicalKeyboardKey.arrowRight,
+  'ArrowLeft': LogicalKeyboardKey.arrowLeft,
+  'ArrowDown': LogicalKeyboardKey.arrowDown,
+  'ArrowUp': LogicalKeyboardKey.arrowUp,
+  'NumLock': LogicalKeyboardKey.numLock,
+  'NumpadDivide': LogicalKeyboardKey.numpadDivide,
+  'NumpadMultiply': LogicalKeyboardKey.numpadMultiply,
+  'NumpadSubtract': LogicalKeyboardKey.numpadSubtract,
+  'NumpadAdd': LogicalKeyboardKey.numpadAdd,
+  'NumpadEnter': LogicalKeyboardKey.numpadEnter,
+  'Numpad1': LogicalKeyboardKey.numpad1,
+  'Numpad2': LogicalKeyboardKey.numpad2,
+  'Numpad3': LogicalKeyboardKey.numpad3,
+  'Numpad4': LogicalKeyboardKey.numpad4,
+  'Numpad5': LogicalKeyboardKey.numpad5,
+  'Numpad6': LogicalKeyboardKey.numpad6,
+  'Numpad7': LogicalKeyboardKey.numpad7,
+  'Numpad8': LogicalKeyboardKey.numpad8,
+  'Numpad9': LogicalKeyboardKey.numpad9,
+  'Numpad0': LogicalKeyboardKey.numpad0,
+  'NumpadDecimal': LogicalKeyboardKey.numpadDecimal,
+  'IntlBackslash': LogicalKeyboardKey.intlBackslash,
+  'ContextMenu': LogicalKeyboardKey.contextMenu,
+  'Power': LogicalKeyboardKey.power,
+  'NumpadEqual': LogicalKeyboardKey.numpadEqual,
+  'F13': LogicalKeyboardKey.f13,
+  'F14': LogicalKeyboardKey.f14,
+  'F15': LogicalKeyboardKey.f15,
+  'F16': LogicalKeyboardKey.f16,
+  'F17': LogicalKeyboardKey.f17,
+  'F18': LogicalKeyboardKey.f18,
+  'F19': LogicalKeyboardKey.f19,
+  'F20': LogicalKeyboardKey.f20,
+  'F21': LogicalKeyboardKey.f21,
+  'F22': LogicalKeyboardKey.f22,
+  'F23': LogicalKeyboardKey.f23,
+  'F24': LogicalKeyboardKey.f24,
+  'Open': LogicalKeyboardKey.open,
+  'Help': LogicalKeyboardKey.help,
+  'Select': LogicalKeyboardKey.select,
+  'Again': LogicalKeyboardKey.again,
+  'Undo': LogicalKeyboardKey.undo,
+  'Cut': LogicalKeyboardKey.cut,
+  'Copy': LogicalKeyboardKey.copy,
+  'Paste': LogicalKeyboardKey.paste,
+  'Find': LogicalKeyboardKey.find,
+  'AudioVolumeMute': LogicalKeyboardKey.audioVolumeMute,
+  'AudioVolumeUp': LogicalKeyboardKey.audioVolumeUp,
+  'AudioVolumeDown': LogicalKeyboardKey.audioVolumeDown,
+  'NumpadComma': LogicalKeyboardKey.numpadComma,
+  'IntlRo': LogicalKeyboardKey.intlRo,
+  'KanaMode': LogicalKeyboardKey.kanaMode,
+  'IntlYen': LogicalKeyboardKey.intlYen,
+  'Convert': LogicalKeyboardKey.convert,
+  'NonConvert': LogicalKeyboardKey.nonConvert,
+  'Lang1': LogicalKeyboardKey.lang1,
+  'Lang2': LogicalKeyboardKey.lang2,
+  'Lang3': LogicalKeyboardKey.lang3,
+  'Lang4': LogicalKeyboardKey.lang4,
+  'Lang5': LogicalKeyboardKey.lang5,
+  'Abort': LogicalKeyboardKey.abort,
+  'Props': LogicalKeyboardKey.props,
+  'NumpadParenLeft': LogicalKeyboardKey.numpadParenLeft,
+  'NumpadParenRight': LogicalKeyboardKey.numpadParenRight,
+  'NumpadBackspace': LogicalKeyboardKey.numpadBackspace,
+  'NumpadMemoryStore': LogicalKeyboardKey.numpadMemoryStore,
+  'NumpadMemoryRecall': LogicalKeyboardKey.numpadMemoryRecall,
+  'NumpadMemoryClear': LogicalKeyboardKey.numpadMemoryClear,
+  'NumpadMemoryAdd': LogicalKeyboardKey.numpadMemoryAdd,
+  'NumpadMemorySubtract': LogicalKeyboardKey.numpadMemorySubtract,
+  'NumpadClear': LogicalKeyboardKey.numpadClear,
+  'NumpadClearEntry': LogicalKeyboardKey.numpadClearEntry,
+  'ControlLeft': LogicalKeyboardKey.controlLeft,
+  'ShiftLeft': LogicalKeyboardKey.shiftLeft,
+  'AltLeft': LogicalKeyboardKey.altLeft,
+  'MetaLeft': LogicalKeyboardKey.metaLeft,
+  'ControlRight': LogicalKeyboardKey.controlRight,
+  'ShiftRight': LogicalKeyboardKey.shiftRight,
+  'AltRight': LogicalKeyboardKey.altRight,
+  'MetaRight': LogicalKeyboardKey.metaRight,
+  'BrightnessUp': LogicalKeyboardKey.brightnessUp,
+  'BrightnessDown': LogicalKeyboardKey.brightnessDown,
+  'MediaPlay': LogicalKeyboardKey.mediaPlay,
+  'MediaPause': LogicalKeyboardKey.mediaPause,
+  'MediaRecord': LogicalKeyboardKey.mediaRecord,
+  'MediaFastForward': LogicalKeyboardKey.mediaFastForward,
+  'MediaRewind': LogicalKeyboardKey.mediaRewind,
+  'MediaTrackNext': LogicalKeyboardKey.mediaTrackNext,
+  'MediaTrackPrevious': LogicalKeyboardKey.mediaTrackPrevious,
+  'MediaStop': LogicalKeyboardKey.mediaStop,
+  'Eject': LogicalKeyboardKey.eject,
+  'MediaPlayPause': LogicalKeyboardKey.mediaPlayPause,
+  'MediaSelect': LogicalKeyboardKey.mediaSelect,
+  'LaunchMail': LogicalKeyboardKey.launchMail,
+  'LaunchApp2': LogicalKeyboardKey.launchApp2,
+  'LaunchApp1': LogicalKeyboardKey.launchApp1,
+  'LaunchControlPanel': LogicalKeyboardKey.launchControlPanel,
+  'SelectTask': LogicalKeyboardKey.selectTask,
+  'LaunchScreenSaver': LogicalKeyboardKey.launchScreenSaver,
+  'LaunchAssistant': LogicalKeyboardKey.launchAssistant,
+  'BrowserSearch': LogicalKeyboardKey.browserSearch,
+  'BrowserHome': LogicalKeyboardKey.browserHome,
+  'BrowserBack': LogicalKeyboardKey.browserBack,
+  'BrowserForward': LogicalKeyboardKey.browserForward,
+  'BrowserStop': LogicalKeyboardKey.browserStop,
+  'BrowserRefresh': LogicalKeyboardKey.browserRefresh,
+  'BrowserFavorites': LogicalKeyboardKey.browserFavorites,
+  'ZoomToggle': LogicalKeyboardKey.zoomToggle,
+  'MailReply': LogicalKeyboardKey.mailReply,
+  'MailForward': LogicalKeyboardKey.mailForward,
+  'MailSend': LogicalKeyboardKey.mailSend,
+  'KeyboardLayoutSelect': LogicalKeyboardKey.keyboardLayoutSelect,
+  'ShowAllWindows': LogicalKeyboardKey.showAllWindows,
+  'GameButton1': LogicalKeyboardKey.gameButton1,
+  'GameButton2': LogicalKeyboardKey.gameButton2,
+  'GameButton3': LogicalKeyboardKey.gameButton3,
+  'GameButton4': LogicalKeyboardKey.gameButton4,
+  'GameButton5': LogicalKeyboardKey.gameButton5,
+  'GameButton6': LogicalKeyboardKey.gameButton6,
+  'GameButton7': LogicalKeyboardKey.gameButton7,
+  'GameButton8': LogicalKeyboardKey.gameButton8,
+  'GameButton9': LogicalKeyboardKey.gameButton9,
+  'GameButton10': LogicalKeyboardKey.gameButton10,
+  'GameButton11': LogicalKeyboardKey.gameButton11,
+  'GameButton12': LogicalKeyboardKey.gameButton12,
+  'GameButton13': LogicalKeyboardKey.gameButton13,
+  'GameButton14': LogicalKeyboardKey.gameButton14,
+  'GameButton15': LogicalKeyboardKey.gameButton15,
+  'GameButton16': LogicalKeyboardKey.gameButton16,
+  'GameButtonA': LogicalKeyboardKey.gameButtonA,
+  'GameButtonB': LogicalKeyboardKey.gameButtonB,
+  'GameButtonC': LogicalKeyboardKey.gameButtonC,
+  'GameButtonLeft1': LogicalKeyboardKey.gameButtonLeft1,
+  'GameButtonLeft2': LogicalKeyboardKey.gameButtonLeft2,
+  'GameButtonMode': LogicalKeyboardKey.gameButtonMode,
+  'GameButtonRight1': LogicalKeyboardKey.gameButtonRight1,
+  'GameButtonRight2': LogicalKeyboardKey.gameButtonRight2,
+  'GameButtonSelect': LogicalKeyboardKey.gameButtonSelect,
+  'GameButtonStart': LogicalKeyboardKey.gameButtonStart,
+  'GameButtonThumbLeft': LogicalKeyboardKey.gameButtonThumbLeft,
+  'GameButtonThumbRight': LogicalKeyboardKey.gameButtonThumbRight,
+  'GameButtonX': LogicalKeyboardKey.gameButtonX,
+  'GameButtonY': LogicalKeyboardKey.gameButtonY,
+  'GameButtonZ': LogicalKeyboardKey.gameButtonZ,
+  'Fn': LogicalKeyboardKey.fn,
+};
+
+/// Maps Web KeyboardEvent codes to the matching [PhysicalKeyboardKey].
+const Map<String, PhysicalKeyboardKey> kWebToPhysicalKey = <String, PhysicalKeyboardKey>{
+  'None': PhysicalKeyboardKey.none,
+  'Hyper': PhysicalKeyboardKey.hyper,
+  'Super': PhysicalKeyboardKey.superKey,
+  'FnLock': PhysicalKeyboardKey.fnLock,
+  'Suspend': PhysicalKeyboardKey.suspend,
+  'Resume': PhysicalKeyboardKey.resume,
+  'Turbo': PhysicalKeyboardKey.turbo,
+  'PrivacyScreenToggle': PhysicalKeyboardKey.privacyScreenToggle,
+  'Sleep': PhysicalKeyboardKey.sleep,
+  'WakeUp': PhysicalKeyboardKey.wakeUp,
+  'DisplayToggleIntExt': PhysicalKeyboardKey.displayToggleIntExt,
+  'KeyA': PhysicalKeyboardKey.keyA,
+  'KeyB': PhysicalKeyboardKey.keyB,
+  'KeyC': PhysicalKeyboardKey.keyC,
+  'KeyD': PhysicalKeyboardKey.keyD,
+  'KeyE': PhysicalKeyboardKey.keyE,
+  'KeyF': PhysicalKeyboardKey.keyF,
+  'KeyG': PhysicalKeyboardKey.keyG,
+  'KeyH': PhysicalKeyboardKey.keyH,
+  'KeyI': PhysicalKeyboardKey.keyI,
+  'KeyJ': PhysicalKeyboardKey.keyJ,
+  'KeyK': PhysicalKeyboardKey.keyK,
+  'KeyL': PhysicalKeyboardKey.keyL,
+  'KeyM': PhysicalKeyboardKey.keyM,
+  'KeyN': PhysicalKeyboardKey.keyN,
+  'KeyO': PhysicalKeyboardKey.keyO,
+  'KeyP': PhysicalKeyboardKey.keyP,
+  'KeyQ': PhysicalKeyboardKey.keyQ,
+  'KeyR': PhysicalKeyboardKey.keyR,
+  'KeyS': PhysicalKeyboardKey.keyS,
+  'KeyT': PhysicalKeyboardKey.keyT,
+  'KeyU': PhysicalKeyboardKey.keyU,
+  'KeyV': PhysicalKeyboardKey.keyV,
+  'KeyW': PhysicalKeyboardKey.keyW,
+  'KeyX': PhysicalKeyboardKey.keyX,
+  'KeyY': PhysicalKeyboardKey.keyY,
+  'KeyZ': PhysicalKeyboardKey.keyZ,
+  'Digit1': PhysicalKeyboardKey.digit1,
+  'Digit2': PhysicalKeyboardKey.digit2,
+  'Digit3': PhysicalKeyboardKey.digit3,
+  'Digit4': PhysicalKeyboardKey.digit4,
+  'Digit5': PhysicalKeyboardKey.digit5,
+  'Digit6': PhysicalKeyboardKey.digit6,
+  'Digit7': PhysicalKeyboardKey.digit7,
+  'Digit8': PhysicalKeyboardKey.digit8,
+  'Digit9': PhysicalKeyboardKey.digit9,
+  'Digit0': PhysicalKeyboardKey.digit0,
+  'Enter': PhysicalKeyboardKey.enter,
+  'Escape': PhysicalKeyboardKey.escape,
+  'Backspace': PhysicalKeyboardKey.backspace,
+  'Tab': PhysicalKeyboardKey.tab,
+  'Space': PhysicalKeyboardKey.space,
+  'Minus': PhysicalKeyboardKey.minus,
+  'Equal': PhysicalKeyboardKey.equal,
+  'BracketLeft': PhysicalKeyboardKey.bracketLeft,
+  'BracketRight': PhysicalKeyboardKey.bracketRight,
+  'Backslash': PhysicalKeyboardKey.backslash,
+  'Semicolon': PhysicalKeyboardKey.semicolon,
+  'Quote': PhysicalKeyboardKey.quote,
+  'Backquote': PhysicalKeyboardKey.backquote,
+  'Comma': PhysicalKeyboardKey.comma,
+  'Period': PhysicalKeyboardKey.period,
+  'Slash': PhysicalKeyboardKey.slash,
+  'CapsLock': PhysicalKeyboardKey.capsLock,
+  'F1': PhysicalKeyboardKey.f1,
+  'F2': PhysicalKeyboardKey.f2,
+  'F3': PhysicalKeyboardKey.f3,
+  'F4': PhysicalKeyboardKey.f4,
+  'F5': PhysicalKeyboardKey.f5,
+  'F6': PhysicalKeyboardKey.f6,
+  'F7': PhysicalKeyboardKey.f7,
+  'F8': PhysicalKeyboardKey.f8,
+  'F9': PhysicalKeyboardKey.f9,
+  'F10': PhysicalKeyboardKey.f10,
+  'F11': PhysicalKeyboardKey.f11,
+  'F12': PhysicalKeyboardKey.f12,
+  'PrintScreen': PhysicalKeyboardKey.printScreen,
+  'ScrollLock': PhysicalKeyboardKey.scrollLock,
+  'Pause': PhysicalKeyboardKey.pause,
+  'Insert': PhysicalKeyboardKey.insert,
+  'Home': PhysicalKeyboardKey.home,
+  'PageUp': PhysicalKeyboardKey.pageUp,
+  'Delete': PhysicalKeyboardKey.delete,
+  'End': PhysicalKeyboardKey.end,
+  'PageDown': PhysicalKeyboardKey.pageDown,
+  'ArrowRight': PhysicalKeyboardKey.arrowRight,
+  'ArrowLeft': PhysicalKeyboardKey.arrowLeft,
+  'ArrowDown': PhysicalKeyboardKey.arrowDown,
+  'ArrowUp': PhysicalKeyboardKey.arrowUp,
+  'NumLock': PhysicalKeyboardKey.numLock,
+  'NumpadDivide': PhysicalKeyboardKey.numpadDivide,
+  'NumpadMultiply': PhysicalKeyboardKey.numpadMultiply,
+  'NumpadSubtract': PhysicalKeyboardKey.numpadSubtract,
+  'NumpadAdd': PhysicalKeyboardKey.numpadAdd,
+  'NumpadEnter': PhysicalKeyboardKey.numpadEnter,
+  'Numpad1': PhysicalKeyboardKey.numpad1,
+  'Numpad2': PhysicalKeyboardKey.numpad2,
+  'Numpad3': PhysicalKeyboardKey.numpad3,
+  'Numpad4': PhysicalKeyboardKey.numpad4,
+  'Numpad5': PhysicalKeyboardKey.numpad5,
+  'Numpad6': PhysicalKeyboardKey.numpad6,
+  'Numpad7': PhysicalKeyboardKey.numpad7,
+  'Numpad8': PhysicalKeyboardKey.numpad8,
+  'Numpad9': PhysicalKeyboardKey.numpad9,
+  'Numpad0': PhysicalKeyboardKey.numpad0,
+  'NumpadDecimal': PhysicalKeyboardKey.numpadDecimal,
+  'IntlBackslash': PhysicalKeyboardKey.intlBackslash,
+  'ContextMenu': PhysicalKeyboardKey.contextMenu,
+  'Power': PhysicalKeyboardKey.power,
+  'NumpadEqual': PhysicalKeyboardKey.numpadEqual,
+  'F13': PhysicalKeyboardKey.f13,
+  'F14': PhysicalKeyboardKey.f14,
+  'F15': PhysicalKeyboardKey.f15,
+  'F16': PhysicalKeyboardKey.f16,
+  'F17': PhysicalKeyboardKey.f17,
+  'F18': PhysicalKeyboardKey.f18,
+  'F19': PhysicalKeyboardKey.f19,
+  'F20': PhysicalKeyboardKey.f20,
+  'F21': PhysicalKeyboardKey.f21,
+  'F22': PhysicalKeyboardKey.f22,
+  'F23': PhysicalKeyboardKey.f23,
+  'F24': PhysicalKeyboardKey.f24,
+  'Open': PhysicalKeyboardKey.open,
+  'Help': PhysicalKeyboardKey.help,
+  'Select': PhysicalKeyboardKey.select,
+  'Again': PhysicalKeyboardKey.again,
+  'Undo': PhysicalKeyboardKey.undo,
+  'Cut': PhysicalKeyboardKey.cut,
+  'Copy': PhysicalKeyboardKey.copy,
+  'Paste': PhysicalKeyboardKey.paste,
+  'Find': PhysicalKeyboardKey.find,
+  'AudioVolumeMute': PhysicalKeyboardKey.audioVolumeMute,
+  'AudioVolumeUp': PhysicalKeyboardKey.audioVolumeUp,
+  'AudioVolumeDown': PhysicalKeyboardKey.audioVolumeDown,
+  'NumpadComma': PhysicalKeyboardKey.numpadComma,
+  'IntlRo': PhysicalKeyboardKey.intlRo,
+  'KanaMode': PhysicalKeyboardKey.kanaMode,
+  'IntlYen': PhysicalKeyboardKey.intlYen,
+  'Convert': PhysicalKeyboardKey.convert,
+  'NonConvert': PhysicalKeyboardKey.nonConvert,
+  'Lang1': PhysicalKeyboardKey.lang1,
+  'Lang2': PhysicalKeyboardKey.lang2,
+  'Lang3': PhysicalKeyboardKey.lang3,
+  'Lang4': PhysicalKeyboardKey.lang4,
+  'Lang5': PhysicalKeyboardKey.lang5,
+  'Abort': PhysicalKeyboardKey.abort,
+  'Props': PhysicalKeyboardKey.props,
+  'NumpadParenLeft': PhysicalKeyboardKey.numpadParenLeft,
+  'NumpadParenRight': PhysicalKeyboardKey.numpadParenRight,
+  'NumpadBackspace': PhysicalKeyboardKey.numpadBackspace,
+  'NumpadMemoryStore': PhysicalKeyboardKey.numpadMemoryStore,
+  'NumpadMemoryRecall': PhysicalKeyboardKey.numpadMemoryRecall,
+  'NumpadMemoryClear': PhysicalKeyboardKey.numpadMemoryClear,
+  'NumpadMemoryAdd': PhysicalKeyboardKey.numpadMemoryAdd,
+  'NumpadMemorySubtract': PhysicalKeyboardKey.numpadMemorySubtract,
+  'NumpadClear': PhysicalKeyboardKey.numpadClear,
+  'NumpadClearEntry': PhysicalKeyboardKey.numpadClearEntry,
+  'ControlLeft': PhysicalKeyboardKey.controlLeft,
+  'ShiftLeft': PhysicalKeyboardKey.shiftLeft,
+  'AltLeft': PhysicalKeyboardKey.altLeft,
+  'MetaLeft': PhysicalKeyboardKey.metaLeft,
+  'ControlRight': PhysicalKeyboardKey.controlRight,
+  'ShiftRight': PhysicalKeyboardKey.shiftRight,
+  'AltRight': PhysicalKeyboardKey.altRight,
+  'MetaRight': PhysicalKeyboardKey.metaRight,
+  'BrightnessUp': PhysicalKeyboardKey.brightnessUp,
+  'BrightnessDown': PhysicalKeyboardKey.brightnessDown,
+  'MediaPlay': PhysicalKeyboardKey.mediaPlay,
+  'MediaPause': PhysicalKeyboardKey.mediaPause,
+  'MediaRecord': PhysicalKeyboardKey.mediaRecord,
+  'MediaFastForward': PhysicalKeyboardKey.mediaFastForward,
+  'MediaRewind': PhysicalKeyboardKey.mediaRewind,
+  'MediaTrackNext': PhysicalKeyboardKey.mediaTrackNext,
+  'MediaTrackPrevious': PhysicalKeyboardKey.mediaTrackPrevious,
+  'MediaStop': PhysicalKeyboardKey.mediaStop,
+  'Eject': PhysicalKeyboardKey.eject,
+  'MediaPlayPause': PhysicalKeyboardKey.mediaPlayPause,
+  'MediaSelect': PhysicalKeyboardKey.mediaSelect,
+  'LaunchMail': PhysicalKeyboardKey.launchMail,
+  'LaunchApp2': PhysicalKeyboardKey.launchApp2,
+  'LaunchApp1': PhysicalKeyboardKey.launchApp1,
+  'LaunchControlPanel': PhysicalKeyboardKey.launchControlPanel,
+  'SelectTask': PhysicalKeyboardKey.selectTask,
+  'LaunchScreenSaver': PhysicalKeyboardKey.launchScreenSaver,
+  'LaunchAssistant': PhysicalKeyboardKey.launchAssistant,
+  'BrowserSearch': PhysicalKeyboardKey.browserSearch,
+  'BrowserHome': PhysicalKeyboardKey.browserHome,
+  'BrowserBack': PhysicalKeyboardKey.browserBack,
+  'BrowserForward': PhysicalKeyboardKey.browserForward,
+  'BrowserStop': PhysicalKeyboardKey.browserStop,
+  'BrowserRefresh': PhysicalKeyboardKey.browserRefresh,
+  'BrowserFavorites': PhysicalKeyboardKey.browserFavorites,
+  'ZoomToggle': PhysicalKeyboardKey.zoomToggle,
+  'MailReply': PhysicalKeyboardKey.mailReply,
+  'MailForward': PhysicalKeyboardKey.mailForward,
+  'MailSend': PhysicalKeyboardKey.mailSend,
+  'KeyboardLayoutSelect': PhysicalKeyboardKey.keyboardLayoutSelect,
+  'ShowAllWindows': PhysicalKeyboardKey.showAllWindows,
+  'GameButton1': PhysicalKeyboardKey.gameButton1,
+  'GameButton2': PhysicalKeyboardKey.gameButton2,
+  'GameButton3': PhysicalKeyboardKey.gameButton3,
+  'GameButton4': PhysicalKeyboardKey.gameButton4,
+  'GameButton5': PhysicalKeyboardKey.gameButton5,
+  'GameButton6': PhysicalKeyboardKey.gameButton6,
+  'GameButton7': PhysicalKeyboardKey.gameButton7,
+  'GameButton8': PhysicalKeyboardKey.gameButton8,
+  'GameButton9': PhysicalKeyboardKey.gameButton9,
+  'GameButton10': PhysicalKeyboardKey.gameButton10,
+  'GameButton11': PhysicalKeyboardKey.gameButton11,
+  'GameButton12': PhysicalKeyboardKey.gameButton12,
+  'GameButton13': PhysicalKeyboardKey.gameButton13,
+  'GameButton14': PhysicalKeyboardKey.gameButton14,
+  'GameButton15': PhysicalKeyboardKey.gameButton15,
+  'GameButton16': PhysicalKeyboardKey.gameButton16,
+  'GameButtonA': PhysicalKeyboardKey.gameButtonA,
+  'GameButtonB': PhysicalKeyboardKey.gameButtonB,
+  'GameButtonC': PhysicalKeyboardKey.gameButtonC,
+  'GameButtonLeft1': PhysicalKeyboardKey.gameButtonLeft1,
+  'GameButtonLeft2': PhysicalKeyboardKey.gameButtonLeft2,
+  'GameButtonMode': PhysicalKeyboardKey.gameButtonMode,
+  'GameButtonRight1': PhysicalKeyboardKey.gameButtonRight1,
+  'GameButtonRight2': PhysicalKeyboardKey.gameButtonRight2,
+  'GameButtonSelect': PhysicalKeyboardKey.gameButtonSelect,
+  'GameButtonStart': PhysicalKeyboardKey.gameButtonStart,
+  'GameButtonThumbLeft': PhysicalKeyboardKey.gameButtonThumbLeft,
+  'GameButtonThumbRight': PhysicalKeyboardKey.gameButtonThumbRight,
+  'GameButtonX': PhysicalKeyboardKey.gameButtonX,
+  'GameButtonY': PhysicalKeyboardKey.gameButtonY,
+  'GameButtonZ': PhysicalKeyboardKey.gameButtonZ,
+  'Fn': PhysicalKeyboardKey.fn,
+};
+
+/// A map of Web KeyboardEvent codes which have printable representations, but appear
+/// on the number pad. Used to provide different key objects for keys like
+/// KEY_EQUALS and NUMPAD_EQUALS.
+const Map<String, LogicalKeyboardKey> kWebNumPadMap = <String, LogicalKeyboardKey>{
+  'NumpadDivide': LogicalKeyboardKey.numpadDivide,
+  'NumpadMultiply': LogicalKeyboardKey.numpadMultiply,
+  'NumpadSubtract': LogicalKeyboardKey.numpadSubtract,
+  'NumpadAdd': LogicalKeyboardKey.numpadAdd,
+  'Numpad1': LogicalKeyboardKey.numpad1,
+  'Numpad2': LogicalKeyboardKey.numpad2,
+  'Numpad3': LogicalKeyboardKey.numpad3,
+  'Numpad4': LogicalKeyboardKey.numpad4,
+  'Numpad5': LogicalKeyboardKey.numpad5,
+  'Numpad6': LogicalKeyboardKey.numpad6,
+  'Numpad7': LogicalKeyboardKey.numpad7,
+  'Numpad8': LogicalKeyboardKey.numpad8,
+  'Numpad9': LogicalKeyboardKey.numpad9,
+  'Numpad0': LogicalKeyboardKey.numpad0,
+  'NumpadDecimal': LogicalKeyboardKey.numpadDecimal,
+  'NumpadEqual': LogicalKeyboardKey.numpadEqual,
+  'NumpadComma': LogicalKeyboardKey.numpadComma,
+  'NumpadParenLeft': LogicalKeyboardKey.numpadParenLeft,
+  'NumpadParenRight': LogicalKeyboardKey.numpadParenRight,
+};
+
+/// Maps Windows KeyboardEvent codes to the matching [LogicalKeyboardKey].
+const Map<int, LogicalKeyboardKey> kWindowsToLogicalKey = <int, LogicalKeyboardKey>{
+  95: LogicalKeyboardKey.sleep,
+  65: LogicalKeyboardKey.keyA,
+  66: LogicalKeyboardKey.keyB,
+  67: LogicalKeyboardKey.keyC,
+  68: LogicalKeyboardKey.keyD,
+  69: LogicalKeyboardKey.keyE,
+  70: LogicalKeyboardKey.keyF,
+  71: LogicalKeyboardKey.keyG,
+  72: LogicalKeyboardKey.keyH,
+  73: LogicalKeyboardKey.keyI,
+  74: LogicalKeyboardKey.keyJ,
+  75: LogicalKeyboardKey.keyK,
+  76: LogicalKeyboardKey.keyL,
+  77: LogicalKeyboardKey.keyM,
+  78: LogicalKeyboardKey.keyN,
+  79: LogicalKeyboardKey.keyO,
+  80: LogicalKeyboardKey.keyP,
+  81: LogicalKeyboardKey.keyQ,
+  82: LogicalKeyboardKey.keyR,
+  83: LogicalKeyboardKey.keyS,
+  84: LogicalKeyboardKey.keyT,
+  85: LogicalKeyboardKey.keyU,
+  86: LogicalKeyboardKey.keyV,
+  87: LogicalKeyboardKey.keyW,
+  88: LogicalKeyboardKey.keyX,
+  89: LogicalKeyboardKey.keyY,
+  90: LogicalKeyboardKey.keyZ,
+  13: LogicalKeyboardKey.enter,
+  27: LogicalKeyboardKey.escape,
+  8: LogicalKeyboardKey.backspace,
+  9: LogicalKeyboardKey.tab,
+  32: LogicalKeyboardKey.space,
+  189: LogicalKeyboardKey.minus,
+  187: LogicalKeyboardKey.equal,
+  219: LogicalKeyboardKey.bracketLeft,
+  221: LogicalKeyboardKey.bracketRight,
+  220: LogicalKeyboardKey.backslash,
+  186: LogicalKeyboardKey.semicolon,
+  222: LogicalKeyboardKey.quote,
+  192: LogicalKeyboardKey.backquote,
+  188: LogicalKeyboardKey.comma,
+  190: LogicalKeyboardKey.period,
+  191: LogicalKeyboardKey.slash,
+  20: LogicalKeyboardKey.capsLock,
+  112: LogicalKeyboardKey.f1,
+  113: LogicalKeyboardKey.f2,
+  114: LogicalKeyboardKey.f3,
+  115: LogicalKeyboardKey.f4,
+  116: LogicalKeyboardKey.f5,
+  117: LogicalKeyboardKey.f6,
+  118: LogicalKeyboardKey.f7,
+  119: LogicalKeyboardKey.f8,
+  120: LogicalKeyboardKey.f9,
+  121: LogicalKeyboardKey.f10,
+  122: LogicalKeyboardKey.f11,
+  123: LogicalKeyboardKey.f12,
+  19: LogicalKeyboardKey.pause,
+  45: LogicalKeyboardKey.insert,
+  36: LogicalKeyboardKey.home,
+  46: LogicalKeyboardKey.delete,
+  35: LogicalKeyboardKey.end,
+  39: LogicalKeyboardKey.arrowRight,
+  37: LogicalKeyboardKey.arrowLeft,
+  40: LogicalKeyboardKey.arrowDown,
+  38: LogicalKeyboardKey.arrowUp,
+  111: LogicalKeyboardKey.numpadDivide,
+  106: LogicalKeyboardKey.numpadMultiply,
+  109: LogicalKeyboardKey.numpadSubtract,
+  107: LogicalKeyboardKey.numpadAdd,
+  97: LogicalKeyboardKey.numpad1,
+  98: LogicalKeyboardKey.numpad2,
+  99: LogicalKeyboardKey.numpad3,
+  100: LogicalKeyboardKey.numpad4,
+  101: LogicalKeyboardKey.numpad5,
+  102: LogicalKeyboardKey.numpad6,
+  103: LogicalKeyboardKey.numpad7,
+  104: LogicalKeyboardKey.numpad8,
+  105: LogicalKeyboardKey.numpad9,
+  96: LogicalKeyboardKey.numpad0,
+  110: LogicalKeyboardKey.numpadDecimal,
+  146: LogicalKeyboardKey.numpadEqual,
+  124: LogicalKeyboardKey.f13,
+  125: LogicalKeyboardKey.f14,
+  126: LogicalKeyboardKey.f15,
+  127: LogicalKeyboardKey.f16,
+  128: LogicalKeyboardKey.f17,
+  129: LogicalKeyboardKey.f18,
+  130: LogicalKeyboardKey.f19,
+  131: LogicalKeyboardKey.f20,
+  132: LogicalKeyboardKey.f21,
+  133: LogicalKeyboardKey.f22,
+  134: LogicalKeyboardKey.f23,
+  135: LogicalKeyboardKey.f24,
+  47: LogicalKeyboardKey.help,
+  41: LogicalKeyboardKey.select,
+  28: LogicalKeyboardKey.convert,
+  162: LogicalKeyboardKey.controlLeft,
+  160: LogicalKeyboardKey.shiftLeft,
+  164: LogicalKeyboardKey.altLeft,
+  91: LogicalKeyboardKey.metaLeft,
+  163: LogicalKeyboardKey.controlRight,
+  161: LogicalKeyboardKey.shiftRight,
+  165: LogicalKeyboardKey.altRight,
+  92: LogicalKeyboardKey.metaRight,
+  178: LogicalKeyboardKey.mediaStop,
+  179: LogicalKeyboardKey.mediaPlayPause,
+  180: LogicalKeyboardKey.launchMail,
+  183: LogicalKeyboardKey.launchApp2,
+  182: LogicalKeyboardKey.launchApp1,
+  42: LogicalKeyboardKey.print,
+  170: LogicalKeyboardKey.browserSearch,
+  172: LogicalKeyboardKey.browserHome,
+  166: LogicalKeyboardKey.browserBack,
+  167: LogicalKeyboardKey.browserForward,
+  169: LogicalKeyboardKey.browserStop,
+  168: LogicalKeyboardKey.browserRefresh,
+  171: LogicalKeyboardKey.browserFavorites,
+};
+
+/// Maps Windows KeyboardEvent codes to the matching [PhysicalKeyboardKey].
+const Map<int, PhysicalKeyboardKey> kWindowsToPhysicalKey = <int, PhysicalKeyboardKey>{
+  0x0000e05f: PhysicalKeyboardKey.sleep,
+  0x0000e063: PhysicalKeyboardKey.wakeUp,
+  0x000000ff: PhysicalKeyboardKey.usbErrorRollOver,
+  0x000000fc: PhysicalKeyboardKey.usbPostFail,
+  0x0000001e: PhysicalKeyboardKey.keyA,
+  0x00000030: PhysicalKeyboardKey.keyB,
+  0x0000002e: PhysicalKeyboardKey.keyC,
+  0x00000020: PhysicalKeyboardKey.keyD,
+  0x00000012: PhysicalKeyboardKey.keyE,
+  0x00000021: PhysicalKeyboardKey.keyF,
+  0x00000022: PhysicalKeyboardKey.keyG,
+  0x00000023: PhysicalKeyboardKey.keyH,
+  0x00000017: PhysicalKeyboardKey.keyI,
+  0x00000024: PhysicalKeyboardKey.keyJ,
+  0x00000025: PhysicalKeyboardKey.keyK,
+  0x00000026: PhysicalKeyboardKey.keyL,
+  0x00000032: PhysicalKeyboardKey.keyM,
+  0x00000031: PhysicalKeyboardKey.keyN,
+  0x00000018: PhysicalKeyboardKey.keyO,
+  0x00000019: PhysicalKeyboardKey.keyP,
+  0x00000010: PhysicalKeyboardKey.keyQ,
+  0x00000013: PhysicalKeyboardKey.keyR,
+  0x0000001f: PhysicalKeyboardKey.keyS,
+  0x00000014: PhysicalKeyboardKey.keyT,
+  0x00000016: PhysicalKeyboardKey.keyU,
+  0x0000002f: PhysicalKeyboardKey.keyV,
+  0x00000011: PhysicalKeyboardKey.keyW,
+  0x0000002d: PhysicalKeyboardKey.keyX,
+  0x00000015: PhysicalKeyboardKey.keyY,
+  0x0000002c: PhysicalKeyboardKey.keyZ,
+  0x00000002: PhysicalKeyboardKey.digit1,
+  0x00000003: PhysicalKeyboardKey.digit2,
+  0x00000004: PhysicalKeyboardKey.digit3,
+  0x00000005: PhysicalKeyboardKey.digit4,
+  0x00000006: PhysicalKeyboardKey.digit5,
+  0x00000007: PhysicalKeyboardKey.digit6,
+  0x00000008: PhysicalKeyboardKey.digit7,
+  0x00000009: PhysicalKeyboardKey.digit8,
+  0x0000000a: PhysicalKeyboardKey.digit9,
+  0x0000000b: PhysicalKeyboardKey.digit0,
+  0x0000001c: PhysicalKeyboardKey.enter,
+  0x00000001: PhysicalKeyboardKey.escape,
+  0x0000000e: PhysicalKeyboardKey.backspace,
+  0x0000000f: PhysicalKeyboardKey.tab,
+  0x00000039: PhysicalKeyboardKey.space,
+  0x0000000c: PhysicalKeyboardKey.minus,
+  0x0000000d: PhysicalKeyboardKey.equal,
+  0x0000001a: PhysicalKeyboardKey.bracketLeft,
+  0x0000001b: PhysicalKeyboardKey.bracketRight,
+  0x0000002b: PhysicalKeyboardKey.backslash,
+  0x00000027: PhysicalKeyboardKey.semicolon,
+  0x00000028: PhysicalKeyboardKey.quote,
+  0x00000029: PhysicalKeyboardKey.backquote,
+  0x00000033: PhysicalKeyboardKey.comma,
+  0x00000034: PhysicalKeyboardKey.period,
+  0x00000035: PhysicalKeyboardKey.slash,
+  0x0000003a: PhysicalKeyboardKey.capsLock,
+  0x0000003b: PhysicalKeyboardKey.f1,
+  0x0000003c: PhysicalKeyboardKey.f2,
+  0x0000003d: PhysicalKeyboardKey.f3,
+  0x0000003e: PhysicalKeyboardKey.f4,
+  0x0000003f: PhysicalKeyboardKey.f5,
+  0x00000040: PhysicalKeyboardKey.f6,
+  0x00000041: PhysicalKeyboardKey.f7,
+  0x00000042: PhysicalKeyboardKey.f8,
+  0x00000043: PhysicalKeyboardKey.f9,
+  0x00000044: PhysicalKeyboardKey.f10,
+  0x00000057: PhysicalKeyboardKey.f11,
+  0x00000058: PhysicalKeyboardKey.f12,
+  0x0000e037: PhysicalKeyboardKey.printScreen,
+  0x00000046: PhysicalKeyboardKey.scrollLock,
+  0x00000045: PhysicalKeyboardKey.pause,
+  0x0000e052: PhysicalKeyboardKey.insert,
+  0x0000e047: PhysicalKeyboardKey.home,
+  0x0000e049: PhysicalKeyboardKey.pageUp,
+  0x0000e053: PhysicalKeyboardKey.delete,
+  0x0000e04f: PhysicalKeyboardKey.end,
+  0x0000e051: PhysicalKeyboardKey.pageDown,
+  0x0000e04d: PhysicalKeyboardKey.arrowRight,
+  0x0000e04b: PhysicalKeyboardKey.arrowLeft,
+  0x0000e050: PhysicalKeyboardKey.arrowDown,
+  0x0000e048: PhysicalKeyboardKey.arrowUp,
+  0x0000e045: PhysicalKeyboardKey.numLock,
+  0x0000e035: PhysicalKeyboardKey.numpadDivide,
+  0x00000037: PhysicalKeyboardKey.numpadMultiply,
+  0x0000004a: PhysicalKeyboardKey.numpadSubtract,
+  0x0000004e: PhysicalKeyboardKey.numpadAdd,
+  0x0000e01c: PhysicalKeyboardKey.numpadEnter,
+  0x0000004f: PhysicalKeyboardKey.numpad1,
+  0x00000050: PhysicalKeyboardKey.numpad2,
+  0x00000051: PhysicalKeyboardKey.numpad3,
+  0x0000004b: PhysicalKeyboardKey.numpad4,
+  0x0000004c: PhysicalKeyboardKey.numpad5,
+  0x0000004d: PhysicalKeyboardKey.numpad6,
+  0x00000047: PhysicalKeyboardKey.numpad7,
+  0x00000048: PhysicalKeyboardKey.numpad8,
+  0x00000049: PhysicalKeyboardKey.numpad9,
+  0x00000052: PhysicalKeyboardKey.numpad0,
+  0x00000053: PhysicalKeyboardKey.numpadDecimal,
+  0x00000056: PhysicalKeyboardKey.intlBackslash,
+  0x0000e05d: PhysicalKeyboardKey.contextMenu,
+  0x0000e05e: PhysicalKeyboardKey.power,
+  0x00000059: PhysicalKeyboardKey.numpadEqual,
+  0x00000064: PhysicalKeyboardKey.f13,
+  0x00000065: PhysicalKeyboardKey.f14,
+  0x00000066: PhysicalKeyboardKey.f15,
+  0x00000067: PhysicalKeyboardKey.f16,
+  0x00000068: PhysicalKeyboardKey.f17,
+  0x00000069: PhysicalKeyboardKey.f18,
+  0x0000006a: PhysicalKeyboardKey.f19,
+  0x0000006b: PhysicalKeyboardKey.f20,
+  0x0000006c: PhysicalKeyboardKey.f21,
+  0x0000006d: PhysicalKeyboardKey.f22,
+  0x0000006e: PhysicalKeyboardKey.f23,
+  0x00000076: PhysicalKeyboardKey.f24,
+  0x0000e03b: PhysicalKeyboardKey.help,
+  0x0000e008: PhysicalKeyboardKey.undo,
+  0x0000e017: PhysicalKeyboardKey.cut,
+  0x0000e018: PhysicalKeyboardKey.copy,
+  0x0000e00a: PhysicalKeyboardKey.paste,
+  0x0000e020: PhysicalKeyboardKey.audioVolumeMute,
+  0x0000e030: PhysicalKeyboardKey.audioVolumeUp,
+  0x0000e02e: PhysicalKeyboardKey.audioVolumeDown,
+  0x0000007e: PhysicalKeyboardKey.numpadComma,
+  0x00000073: PhysicalKeyboardKey.intlRo,
+  0x00000070: PhysicalKeyboardKey.kanaMode,
+  0x0000007d: PhysicalKeyboardKey.intlYen,
+  0x00000079: PhysicalKeyboardKey.convert,
+  0x0000007b: PhysicalKeyboardKey.nonConvert,
+  0x00000072: PhysicalKeyboardKey.lang1,
+  0x00000071: PhysicalKeyboardKey.lang2,
+  0x00000078: PhysicalKeyboardKey.lang3,
+  0x00000077: PhysicalKeyboardKey.lang4,
+  0x0000001d: PhysicalKeyboardKey.controlLeft,
+  0x0000002a: PhysicalKeyboardKey.shiftLeft,
+  0x00000038: PhysicalKeyboardKey.altLeft,
+  0x0000e05b: PhysicalKeyboardKey.metaLeft,
+  0x0000e01d: PhysicalKeyboardKey.controlRight,
+  0x00000036: PhysicalKeyboardKey.shiftRight,
+  0x0000e038: PhysicalKeyboardKey.altRight,
+  0x0000e05c: PhysicalKeyboardKey.metaRight,
+  0x0000e019: PhysicalKeyboardKey.mediaTrackNext,
+  0x0000e010: PhysicalKeyboardKey.mediaTrackPrevious,
+  0x0000e024: PhysicalKeyboardKey.mediaStop,
+  0x0000e02c: PhysicalKeyboardKey.eject,
+  0x0000e022: PhysicalKeyboardKey.mediaPlayPause,
+  0x0000e06d: PhysicalKeyboardKey.mediaSelect,
+  0x0000e06c: PhysicalKeyboardKey.launchMail,
+  0x0000e021: PhysicalKeyboardKey.launchApp2,
+  0x0000e06b: PhysicalKeyboardKey.launchApp1,
+  0x0000e065: PhysicalKeyboardKey.browserSearch,
+  0x0000e032: PhysicalKeyboardKey.browserHome,
+  0x0000e06a: PhysicalKeyboardKey.browserBack,
+  0x0000e069: PhysicalKeyboardKey.browserForward,
+  0x0000e068: PhysicalKeyboardKey.browserStop,
+  0x0000e067: PhysicalKeyboardKey.browserRefresh,
+  0x0000e066: PhysicalKeyboardKey.browserFavorites,
+};
+
+/// A map of Windows KeyboardEvent codes which have printable representations, but appear
+/// on the number pad. Used to provide different key objects for keys like
+/// KEY_EQUALS and NUMPAD_EQUALS.
+const Map<int, LogicalKeyboardKey> kWindowsNumPadMap = <int, LogicalKeyboardKey>{
+  111: LogicalKeyboardKey.numpadDivide,
+  106: LogicalKeyboardKey.numpadMultiply,
+  109: LogicalKeyboardKey.numpadSubtract,
+  107: LogicalKeyboardKey.numpadAdd,
+  97: LogicalKeyboardKey.numpad1,
+  98: LogicalKeyboardKey.numpad2,
+  99: LogicalKeyboardKey.numpad3,
+  100: LogicalKeyboardKey.numpad4,
+  101: LogicalKeyboardKey.numpad5,
+  102: LogicalKeyboardKey.numpad6,
+  103: LogicalKeyboardKey.numpad7,
+  104: LogicalKeyboardKey.numpad8,
+  105: LogicalKeyboardKey.numpad9,
+  96: LogicalKeyboardKey.numpad0,
+  110: LogicalKeyboardKey.numpadDecimal,
+  146: LogicalKeyboardKey.numpadEqual,
+};
diff --git a/lib/src/services/message_codec.dart b/lib/src/services/message_codec.dart
new file mode 100644
index 0000000..1603d16
--- /dev/null
+++ b/lib/src/services/message_codec.dart
@@ -0,0 +1,156 @@
+// Copyright 2014 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.
+
+
+import 'dart:typed_data';
+
+import 'package:flute/foundation.dart';
+
+import 'platform_channel.dart';
+
+export 'dart:typed_data' show ByteData;
+
+/// A message encoding/decoding mechanism.
+///
+/// Both operations throw an exception, if conversion fails. Such situations
+/// should be treated as programming errors.
+///
+/// See also:
+///
+///  * [BasicMessageChannel], which use [MessageCodec]s for communication
+///    between Flutter and platform plugins.
+abstract class MessageCodec<T> {
+  /// Encodes the specified [message] in binary.
+  ///
+  /// Returns null if the message is null.
+  ByteData? encodeMessage(T message);
+
+  /// Decodes the specified [message] from binary.
+  ///
+  /// Returns null if the message is null.
+  T decodeMessage(ByteData? message);
+}
+
+/// An command object representing the invocation of a named method.
+@immutable
+class MethodCall {
+  /// Creates a [MethodCall] representing the invocation of [method] with the
+  /// specified [arguments].
+  const MethodCall(this.method, [this.arguments])
+    : assert(method != null);
+
+  /// The name of the method to be called.
+  final String method;
+
+  /// The arguments for the method.
+  ///
+  /// Must be a valid value for the [MethodCodec] used.
+  final dynamic arguments;
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'MethodCall')}($method, $arguments)';
+}
+
+/// A codec for method calls and enveloped results.
+///
+/// All operations throw an exception, if conversion fails.
+///
+/// See also:
+///
+///  * [MethodChannel], which use [MethodCodec]s for communication
+///    between Flutter and platform plugins.
+///  * [EventChannel], which use [MethodCodec]s for communication
+///    between Flutter and platform plugins.
+abstract class MethodCodec {
+  /// Encodes the specified [methodCall] into binary.
+  ByteData encodeMethodCall(MethodCall methodCall);
+
+  /// Decodes the specified [methodCall] from binary.
+  MethodCall decodeMethodCall(ByteData? methodCall);
+
+  /// Decodes the specified result [envelope] from binary.
+  ///
+  /// Throws [PlatformException], if [envelope] represents an error, otherwise
+  /// returns the enveloped result.
+  dynamic decodeEnvelope(ByteData envelope);
+
+  /// Encodes a successful [result] into a binary envelope.
+  ByteData encodeSuccessEnvelope(dynamic result);
+
+  /// Encodes an error result into a binary envelope.
+  ///
+  /// The specified error [code], human-readable error [message] and error
+  /// [details] correspond to the fields of [PlatformException].
+  ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details});
+}
+
+
+/// Thrown to indicate that a platform interaction failed in the platform
+/// plugin.
+///
+/// See also:
+///
+///  * [MethodCodec], which throws a [PlatformException], if a received result
+///    envelope represents an error.
+///  * [MethodChannel.invokeMethod], which completes the returned future
+///    with a [PlatformException], if invoking the platform plugin method
+///    results in an error envelope.
+///  * [EventChannel.receiveBroadcastStream], which emits
+///    [PlatformException]s as error events, whenever an event received from the
+///    platform plugin is wrapped in an error envelope.
+class PlatformException implements Exception {
+  /// Creates a [PlatformException] with the specified error [code] and optional
+  /// [message], and with the optional error [details] which must be a valid
+  /// value for the [MethodCodec] involved in the interaction.
+  PlatformException({
+    required this.code,
+    this.message,
+    this.details,
+    this.stacktrace,
+  }) : assert(code != null);
+
+  /// An error code.
+  final String code;
+
+  /// A human-readable error message, possibly null.
+  final String? message;
+
+  /// Error details, possibly null.
+  final dynamic details;
+
+  /// Native stacktrace for the error, possibly null.
+  /// This is strictly for native platform stacktrace.
+  /// The stacktrace info on dart platform can be found within the try-catch block for example:
+  /// try {
+  ///   ...
+  /// } catch (e, stacktrace) {
+  ///   print(stacktrace);
+  /// }
+  final String? stacktrace;
+
+  @override
+  String toString() => 'PlatformException($code, $message, $details, $stacktrace)';
+}
+
+/// Thrown to indicate that a platform interaction failed to find a handling
+/// plugin.
+///
+/// See also:
+///
+///  * [MethodChannel.invokeMethod], which completes the returned future
+///    with a [MissingPluginException], if no plugin handler for the method call
+///    was found.
+///  * [OptionalMethodChannel.invokeMethod], which completes the returned future
+///    with null, if no plugin handler for the method call was found.
+class MissingPluginException implements Exception {
+  /// Creates a [MissingPluginException] with an optional human-readable
+  /// error message.
+  MissingPluginException([this.message]);
+
+  /// A human-readable error message, possibly null.
+  final String? message;
+
+  @override
+  String toString() => 'MissingPluginException($message)';
+}
diff --git a/lib/src/services/message_codecs.dart b/lib/src/services/message_codecs.dart
new file mode 100644
index 0000000..3e18a8f
--- /dev/null
+++ b/lib/src/services/message_codecs.dart
@@ -0,0 +1,586 @@
+// Copyright 2014 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.
+
+
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:flute/foundation.dart' show ReadBuffer, WriteBuffer;
+
+import 'message_codec.dart';
+
+/// [MessageCodec] with unencoded binary messages represented using [ByteData].
+///
+/// On Android, messages will be represented using `java.nio.ByteBuffer`.
+/// On iOS, messages will be represented using `NSData`.
+///
+/// When sending outgoing messages from Android, be sure to use direct `ByteBuffer`
+/// as opposed to indirect. The `wrap()` API provides indirect buffers by default
+/// and you will get empty `ByteData` objects in Dart.
+class BinaryCodec implements MessageCodec<ByteData?> {
+  /// Creates a [MessageCodec] with unencoded binary messages represented using
+  /// [ByteData].
+  const BinaryCodec();
+
+  @override
+  ByteData? decodeMessage(ByteData? message) => message;
+
+  @override
+  ByteData? encodeMessage(ByteData? message) => message;
+}
+
+/// [MessageCodec] with UTF-8 encoded String messages.
+///
+/// On Android, messages will be represented using `java.util.String`.
+/// On iOS, messages will be represented using `NSString`.
+class StringCodec implements MessageCodec<String?> {
+  /// Creates a [MessageCodec] with UTF-8 encoded String messages.
+  const StringCodec();
+
+  @override
+  String? decodeMessage(ByteData? message) {
+    if (message == null)
+      return null;
+    return utf8.decoder.convert(message.buffer.asUint8List(message.offsetInBytes, message.lengthInBytes));
+  }
+
+  @override
+  ByteData? encodeMessage(String? message) {
+    if (message == null)
+      return null;
+    final Uint8List encoded = utf8.encoder.convert(message);
+    return encoded.buffer.asByteData();
+  }
+}
+
+/// [MessageCodec] with UTF-8 encoded JSON messages.
+///
+/// Supported messages are acyclic values of these forms:
+///
+///  * null
+///  * [bool]s
+///  * [num]s
+///  * [String]s
+///  * [List]s of supported values
+///  * [Map]s from strings to supported values
+///
+/// On Android, messages are decoded using the `org.json` library.
+/// On iOS, messages are decoded using the `NSJSONSerialization` library.
+/// In both cases, the use of top-level simple messages (null, [bool], [num],
+/// and [String]) is supported (by the Flutter SDK). The decoded value will be
+/// null/nil for null, and identical to what would result from decoding a
+/// singleton JSON array with a Boolean, number, or string value, and then
+/// extracting its single element.
+class JSONMessageCodec implements MessageCodec<dynamic> {
+  // The codec serializes messages as defined by the JSON codec of the
+  // dart:convert package. The format used must match the Android and
+  // iOS counterparts.
+
+  /// Creates a [MessageCodec] with UTF-8 encoded JSON messages.
+  const JSONMessageCodec();
+
+  @override
+  ByteData? encodeMessage(dynamic message) {
+    if (message == null)
+      return null;
+    return const StringCodec().encodeMessage(json.encode(message));
+  }
+
+  @override
+  dynamic decodeMessage(ByteData? message) {
+    if (message == null)
+      return message;
+    return json.decode(const StringCodec().decodeMessage(message)!);
+  }
+}
+
+/// [MethodCodec] with UTF-8 encoded JSON method calls and result envelopes.
+///
+/// Values supported as method arguments and result payloads are those supported
+/// by [JSONMessageCodec].
+class JSONMethodCodec implements MethodCodec {
+  // The codec serializes method calls, and result envelopes as outlined below.
+  // This format must match the Android and iOS counterparts.
+  //
+  // * Individual values are serialized as defined by the JSON codec of the
+  //   dart:convert package.
+  // * Method calls are serialized as two-element maps, with the method name
+  //   keyed by 'method' and the arguments keyed by 'args'.
+  // * Reply envelopes are serialized as either:
+  //   * one-element lists containing the successful result as its single
+  //     element, or
+  //   * three-element lists containing, in order, an error code String, an
+  //     error message String, and an error details value.
+
+  /// Creates a [MethodCodec] with UTF-8 encoded JSON method calls and result
+  /// envelopes.
+  const JSONMethodCodec();
+
+  @override
+  ByteData encodeMethodCall(MethodCall call) {
+    return const JSONMessageCodec().encodeMessage(<String, dynamic>{
+      'method': call.method,
+      'args': call.arguments,
+    })!;
+  }
+
+  @override
+  MethodCall decodeMethodCall(ByteData? methodCall) {
+    final dynamic decoded = const JSONMessageCodec().decodeMessage(methodCall);
+    if (decoded is! Map)
+      throw FormatException('Expected method call Map, got $decoded');
+    final dynamic method = decoded['method'];
+    final dynamic arguments = decoded['args'];
+    if (method is String)
+      return MethodCall(method, arguments);
+    throw FormatException('Invalid method call: $decoded');
+  }
+
+  @override
+  dynamic decodeEnvelope(ByteData envelope) {
+    final dynamic decoded = const JSONMessageCodec().decodeMessage(envelope);
+    if (decoded is! List)
+      throw FormatException('Expected envelope List, got $decoded');
+    if (decoded.length == 1)
+      return decoded[0];
+    if (decoded.length == 3
+        && decoded[0] is String
+        && (decoded[1] == null || decoded[1] is String))
+      throw PlatformException(
+        code: decoded[0] as String,
+        message: decoded[1] as String,
+        details: decoded[2],
+      );
+    if (decoded.length == 4
+        && decoded[0] is String
+        && (decoded[1] == null || decoded[1] is String)
+        && (decoded[3] == null || decoded[3] is String))
+      throw PlatformException(
+        code: decoded[0] as String,
+        message: decoded[1] as String,
+        details: decoded[2],
+        stacktrace: decoded[3] as String,
+      );
+    throw FormatException('Invalid envelope: $decoded');
+  }
+
+  @override
+  ByteData encodeSuccessEnvelope(dynamic result) {
+    return const JSONMessageCodec().encodeMessage(<dynamic>[result])!;
+  }
+
+  @override
+  ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details}) {
+    assert(code != null);
+    return const JSONMessageCodec().encodeMessage(<dynamic>[code, message, details])!;
+  }
+}
+
+/// [MessageCodec] using the Flutter standard binary encoding.
+///
+/// Supported messages are acyclic values of these forms:
+///
+///  * null
+///  * [bool]s
+///  * [num]s
+///  * [String]s
+///  * [Uint8List]s, [Int32List]s, [Int64List]s, [Float64List]s
+///  * [List]s of supported values
+///  * [Map]s from supported values to supported values
+///
+/// Decoded values will use `List<dynamic>` and `Map<dynamic, dynamic>`
+/// irrespective of content.
+///
+/// On Android, messages are represented as follows:
+///
+///  * null: null
+///  * [bool]\: `java.lang.Boolean`
+///  * [int]\: `java.lang.Integer` for values that are representable using 32-bit
+///    two's complement; `java.lang.Long` otherwise
+///  * [double]\: `java.lang.Double`
+///  * [String]\: `java.lang.String`
+///  * [Uint8List]\: `byte[]`
+///  * [Int32List]\: `int[]`
+///  * [Int64List]\: `long[]`
+///  * [Float64List]\: `double[]`
+///  * [List]\: `java.util.ArrayList`
+///  * [Map]\: `java.util.HashMap`
+///
+/// On iOS, messages are represented as follows:
+///
+///  * null: nil
+///  * [bool]\: `NSNumber numberWithBool:`
+///  * [int]\: `NSNumber numberWithInt:` for values that are representable using
+///    32-bit two's complement; `NSNumber numberWithLong:` otherwise
+///  * [double]\: `NSNumber numberWithDouble:`
+///  * [String]\: `NSString`
+///  * [Uint8List], [Int32List], [Int64List], [Float64List]\:
+///    `FlutterStandardTypedData`
+///  * [List]\: `NSArray`
+///  * [Map]\: `NSDictionary`
+///
+/// When sending a `java.math.BigInteger` from Java, it is converted into a
+/// [String] with the hexadecimal representation of the integer. (The value is
+/// tagged as being a big integer; subclasses of this class could be made to
+/// support it natively; see the discussion at [writeValue].) This codec does
+/// not support sending big integers from Dart.
+///
+/// The codec is extensible by subclasses overriding [writeValue] and
+/// [readValueOfType].
+class StandardMessageCodec implements MessageCodec<dynamic> {
+  /// Creates a [MessageCodec] using the Flutter standard binary encoding.
+  const StandardMessageCodec();
+
+  // The codec serializes messages as outlined below. This format must match the
+  // Android and iOS counterparts and cannot change (as it's possible for
+  // someone to end up using this for persistent storage).
+  //
+  // * A single byte with one of the constant values below determines the
+  //   type of the value.
+  // * The serialization of the value itself follows the type byte.
+  // * Numbers are represented using the host endianness throughout.
+  // * Lengths and sizes of serialized parts are encoded using an expanding
+  //   format optimized for the common case of small non-negative integers:
+  //   * values 0..253 inclusive using one byte with that value;
+  //   * values 254..2^16 inclusive using three bytes, the first of which is
+  //     254, the next two the usual unsigned representation of the value;
+  //   * values 2^16+1..2^32 inclusive using five bytes, the first of which is
+  //     255, the next four the usual unsigned representation of the value.
+  // * null, true, and false have empty serialization; they are encoded directly
+  //   in the type byte (using _valueNull, _valueTrue, _valueFalse)
+  // * Integers representable in 32 bits are encoded using 4 bytes two's
+  //   complement representation.
+  // * Larger integers are encoded using 8 bytes two's complement
+  //   representation.
+  // * doubles are encoded using the IEEE 754 64-bit double-precision binary
+  //   format. Zero bytes are added before the encoded double value to align it
+  //   to a 64 bit boundary in the full message.
+  // * Strings are encoded using their UTF-8 representation. First the length
+  //   of that in bytes is encoded using the expanding format, then follows the
+  //   UTF-8 encoding itself.
+  // * Uint8Lists, Int32Lists, Int64Lists, and Float64Lists are encoded by first
+  //   encoding the list's element count in the expanding format, then the
+  //   smallest number of zero bytes needed to align the position in the full
+  //   message with a multiple of the number of bytes per element, then the
+  //   encoding of the list elements themselves, end-to-end with no additional
+  //   type information, using two's complement or IEEE 754 as applicable.
+  // * Lists are encoded by first encoding their length in the expanding format,
+  //   then follows the recursive encoding of each element value, including the
+  //   type byte (Lists are assumed to be heterogeneous).
+  // * Maps are encoded by first encoding their length in the expanding format,
+  //   then follows the recursive encoding of each key/value pair, including the
+  //   type byte for both (Maps are assumed to be heterogeneous).
+  //
+  // The type labels below must not change, since it's possible for this interface
+  // to be used for persistent storage.
+  static const int _valueNull = 0;
+  static const int _valueTrue = 1;
+  static const int _valueFalse = 2;
+  static const int _valueInt32 = 3;
+  static const int _valueInt64 = 4;
+  static const int _valueLargeInt = 5;
+  static const int _valueFloat64 = 6;
+  static const int _valueString = 7;
+  static const int _valueUint8List = 8;
+  static const int _valueInt32List = 9;
+  static const int _valueInt64List = 10;
+  static const int _valueFloat64List = 11;
+  static const int _valueList = 12;
+  static const int _valueMap = 13;
+
+  @override
+  ByteData? encodeMessage(dynamic message) {
+    if (message == null)
+      return null;
+    final WriteBuffer buffer = WriteBuffer();
+    writeValue(buffer, message);
+    return buffer.done();
+  }
+
+  @override
+  dynamic decodeMessage(ByteData? message) {
+    if (message == null)
+      return null;
+    final ReadBuffer buffer = ReadBuffer(message);
+    final dynamic result = readValue(buffer);
+    if (buffer.hasRemaining)
+      throw const FormatException('Message corrupted');
+    return result;
+  }
+
+  /// Writes [value] to [buffer] by first writing a type discriminator
+  /// byte, then the value itself.
+  ///
+  /// This method may be called recursively to serialize container values.
+  ///
+  /// Type discriminators 0 through 127 inclusive are reserved for use by the
+  /// base class, as follows:
+  ///
+  ///  * null = 0
+  ///  * true = 1
+  ///  * false = 2
+  ///  * 32 bit integer = 3
+  ///  * 64 bit integer = 4
+  ///  * larger integers = 5 (see below)
+  ///  * 64 bit floating-point number = 6
+  ///  * String = 7
+  ///  * Uint8List = 8
+  ///  * Int32List = 9
+  ///  * Int64List = 10
+  ///  * Float64List = 11
+  ///  * List = 12
+  ///  * Map = 13
+  ///  * Reserved for future expansion: 14..127
+  ///
+  /// The codec can be extended by overriding this method, calling super
+  /// for values that the extension does not handle. Type discriminators
+  /// used by extensions must be greater than or equal to 128 in order to avoid
+  /// clashes with any later extensions to the base class.
+  ///
+  /// The "larger integers" type, 5, is never used by [writeValue]. A subclass
+  /// could represent big integers from another package using that type. The
+  /// format is first the type byte (0x05), then the actual number as an ASCII
+  /// string giving the hexadecimal representation of the integer, with the
+  /// string's length as encoded by [writeSize] followed by the string bytes. On
+  /// Android, that would get converted to a `java.math.BigInteger` object. On
+  /// iOS, the string representation is returned.
+  void writeValue(WriteBuffer buffer, dynamic value) {
+    if (value == null) {
+      buffer.putUint8(_valueNull);
+    } else if (value is bool) {
+      buffer.putUint8(value ? _valueTrue : _valueFalse);
+    } else if (value is double) {  // Double precedes int because in JS everything is a double.
+                                   // Therefore in JS, both `is int` and `is double` always
+                                   // return `true`. If we check int first, we'll end up treating
+                                   // all numbers as ints and attempt the int32/int64 conversion,
+                                   // which is wrong. This precedence rule is irrelevant when
+                                   // decoding because we use tags to detect the type of value.
+      buffer.putUint8(_valueFloat64);
+      buffer.putFloat64(value);
+    } else if (value is int) {
+      if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) {
+        buffer.putUint8(_valueInt32);
+        buffer.putInt32(value);
+      } else {
+        buffer.putUint8(_valueInt64);
+        buffer.putInt64(value);
+      }
+    } else if (value is String) {
+      buffer.putUint8(_valueString);
+      final Uint8List bytes = utf8.encoder.convert(value);
+      writeSize(buffer, bytes.length);
+      buffer.putUint8List(bytes);
+    } else if (value is Uint8List) {
+      buffer.putUint8(_valueUint8List);
+      writeSize(buffer, value.length);
+      buffer.putUint8List(value);
+    } else if (value is Int32List) {
+      buffer.putUint8(_valueInt32List);
+      writeSize(buffer, value.length);
+      buffer.putInt32List(value);
+    } else if (value is Int64List) {
+      buffer.putUint8(_valueInt64List);
+      writeSize(buffer, value.length);
+      buffer.putInt64List(value);
+    } else if (value is Float64List) {
+      buffer.putUint8(_valueFloat64List);
+      writeSize(buffer, value.length);
+      buffer.putFloat64List(value);
+    } else if (value is List) {
+      buffer.putUint8(_valueList);
+      writeSize(buffer, value.length);
+      for (final dynamic item in value) {
+        writeValue(buffer, item);
+      }
+    } else if (value is Map) {
+      buffer.putUint8(_valueMap);
+      writeSize(buffer, value.length);
+      value.forEach((dynamic key, dynamic value) {
+        writeValue(buffer, key);
+        writeValue(buffer, value);
+      });
+    } else {
+      throw ArgumentError.value(value);
+    }
+  }
+
+  /// Reads a value from [buffer] as written by [writeValue].
+  ///
+  /// This method is intended for use by subclasses overriding
+  /// [readValueOfType].
+  dynamic readValue(ReadBuffer buffer) {
+    if (!buffer.hasRemaining)
+      throw const FormatException('Message corrupted');
+    final int type = buffer.getUint8();
+    return readValueOfType(type, buffer);
+  }
+
+  /// Reads a value of the indicated [type] from [buffer].
+  ///
+  /// The codec can be extended by overriding this method, calling super for
+  /// types that the extension does not handle. See the discussion at
+  /// [writeValue].
+  dynamic readValueOfType(int type, ReadBuffer buffer) {
+    switch (type) {
+      case _valueNull:
+        return null;
+      case _valueTrue:
+        return true;
+      case _valueFalse:
+        return false;
+      case _valueInt32:
+        return buffer.getInt32();
+      case _valueInt64:
+        return buffer.getInt64();
+      case _valueFloat64:
+        return buffer.getFloat64();
+      case _valueLargeInt:
+      case _valueString:
+        final int length = readSize(buffer);
+        return utf8.decoder.convert(buffer.getUint8List(length));
+      case _valueUint8List:
+        final int length = readSize(buffer);
+        return buffer.getUint8List(length);
+      case _valueInt32List:
+        final int length = readSize(buffer);
+        return buffer.getInt32List(length);
+      case _valueInt64List:
+        final int length = readSize(buffer);
+        return buffer.getInt64List(length);
+      case _valueFloat64List:
+        final int length = readSize(buffer);
+        return buffer.getFloat64List(length);
+      case _valueList:
+        final int length = readSize(buffer);
+        final List<dynamic> result = List<dynamic>.filled(length, null, growable: false);
+        for (int i = 0; i < length; i++)
+          result[i] = readValue(buffer);
+        return result;
+      case _valueMap:
+        final int length = readSize(buffer);
+        final Map<dynamic, dynamic> result = <dynamic, dynamic>{};
+        for (int i = 0; i < length; i++)
+          result[readValue(buffer)] = readValue(buffer);
+        return result;
+      default: throw const FormatException('Message corrupted');
+    }
+  }
+
+  /// Writes a non-negative 32-bit integer [value] to [buffer]
+  /// using an expanding 1-5 byte encoding that optimizes for small values.
+  ///
+  /// This method is intended for use by subclasses overriding
+  /// [writeValue].
+  void writeSize(WriteBuffer buffer, int value) {
+    assert(0 <= value && value <= 0xffffffff);
+    if (value < 254) {
+      buffer.putUint8(value);
+    } else if (value <= 0xffff) {
+      buffer.putUint8(254);
+      buffer.putUint16(value);
+    } else {
+      buffer.putUint8(255);
+      buffer.putUint32(value);
+    }
+  }
+
+  /// Reads a non-negative int from [buffer] as written by [writeSize].
+  ///
+  /// This method is intended for use by subclasses overriding
+  /// [readValueOfType].
+  int readSize(ReadBuffer buffer) {
+    final int value = buffer.getUint8();
+    switch (value) {
+      case 254:
+        return buffer.getUint16();
+      case 255:
+        return buffer.getUint32();
+      default:
+        return value;
+    }
+  }
+}
+
+/// [MethodCodec] using the Flutter standard binary encoding.
+///
+/// The standard codec is guaranteed to be compatible with the corresponding
+/// standard codec for FlutterMethodChannels on the host platform. These parts
+/// of the Flutter SDK are evolved synchronously.
+///
+/// Values supported as method arguments and result payloads are those supported
+/// by [StandardMessageCodec].
+class StandardMethodCodec implements MethodCodec {
+  // The codec method calls, and result envelopes as outlined below. This format
+  // must match the Android and iOS counterparts.
+  //
+  // * Individual values are encoded using [StandardMessageCodec].
+  // * Method calls are encoded using the concatenation of the encoding
+  //   of the method name String and the arguments value.
+  // * Reply envelopes are encoded using first a single byte to distinguish the
+  //   success case (0) from the error case (1). Then follows:
+  //   * In the success case, the encoding of the result value.
+  //   * In the error case, the concatenation of the encoding of the error code
+  //     string, the error message string, and the error details value.
+
+  /// Creates a [MethodCodec] using the Flutter standard binary encoding.
+  const StandardMethodCodec([this.messageCodec = const StandardMessageCodec()]);
+
+  /// The message codec that this method codec uses for encoding values.
+  final StandardMessageCodec messageCodec;
+
+  @override
+  ByteData encodeMethodCall(MethodCall call) {
+    final WriteBuffer buffer = WriteBuffer();
+    messageCodec.writeValue(buffer, call.method);
+    messageCodec.writeValue(buffer, call.arguments);
+    return buffer.done();
+  }
+
+  @override
+  MethodCall decodeMethodCall(ByteData? methodCall) {
+    final ReadBuffer buffer = ReadBuffer(methodCall!);
+    final dynamic method = messageCodec.readValue(buffer);
+    final dynamic arguments = messageCodec.readValue(buffer);
+    if (method is String && !buffer.hasRemaining)
+      return MethodCall(method, arguments);
+    else
+      throw const FormatException('Invalid method call');
+  }
+
+  @override
+  ByteData encodeSuccessEnvelope(dynamic result) {
+    final WriteBuffer buffer = WriteBuffer();
+    buffer.putUint8(0);
+    messageCodec.writeValue(buffer, result);
+    return buffer.done();
+  }
+
+  @override
+  ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details}) {
+    final WriteBuffer buffer = WriteBuffer();
+    buffer.putUint8(1);
+    messageCodec.writeValue(buffer, code);
+    messageCodec.writeValue(buffer, message);
+    messageCodec.writeValue(buffer, details);
+    return buffer.done();
+  }
+
+  @override
+  dynamic decodeEnvelope(ByteData envelope) {
+    // First byte is zero in success case, and non-zero otherwise.
+    if (envelope.lengthInBytes == 0)
+      throw const FormatException('Expected envelope, got nothing');
+    final ReadBuffer buffer = ReadBuffer(envelope);
+    if (buffer.getUint8() == 0)
+      return messageCodec.readValue(buffer);
+    final dynamic errorCode = messageCodec.readValue(buffer);
+    final dynamic errorMessage = messageCodec.readValue(buffer);
+    final dynamic errorDetails = messageCodec.readValue(buffer);
+    final String? errorStacktrace = (buffer.hasRemaining) ? messageCodec.readValue(buffer) as String : null;
+    if (errorCode is String && (errorMessage == null || errorMessage is String) && !buffer.hasRemaining)
+      throw PlatformException(code: errorCode, message: errorMessage as String?, details: errorDetails, stacktrace: errorStacktrace);
+    else
+      throw const FormatException('Invalid envelope');
+  }
+}
diff --git a/lib/src/services/platform_channel.dart b/lib/src/services/platform_channel.dart
new file mode 100644
index 0000000..31a192f
--- /dev/null
+++ b/lib/src/services/platform_channel.dart
@@ -0,0 +1,573 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:flute/foundation.dart';
+
+import 'binary_messenger.dart';
+import 'binding.dart';
+import 'message_codec.dart';
+import 'message_codecs.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+
+/// A named channel for communicating with platform plugins using asynchronous
+/// message passing.
+///
+/// Messages are encoded into binary before being sent, and binary messages
+/// received are decoded into Dart values. The [MessageCodec] used must be
+/// compatible with the one used by the platform plugin. This can be achieved
+/// by creating a basic message channel counterpart of this channel on the
+/// platform side. The Dart type of messages sent and received is [T],
+/// but only the values supported by the specified [MessageCodec] can be used.
+/// The use of unsupported values should be considered programming errors, and
+/// will result in exceptions being thrown. The null message is supported
+/// for all codecs.
+///
+/// The logical identity of the channel is given by its name. Identically named
+/// channels will interfere with each other's communication.
+///
+/// See: <https://flutter.dev/platform-channels/>
+class BasicMessageChannel<T> {
+  /// Creates a [BasicMessageChannel] with the specified [name], [codec] and [binaryMessenger].
+  ///
+  /// The [name] and [codec] arguments cannot be null. The default [ServicesBinding.defaultBinaryMessenger]
+  /// instance is used if [binaryMessenger] is null.
+  const BasicMessageChannel(this.name, this.codec, { BinaryMessenger? binaryMessenger })
+      : assert(name != null),
+        assert(codec != null),
+        _binaryMessenger = binaryMessenger;
+
+  /// The logical channel on which communication happens, not null.
+  final String name;
+
+  /// The message codec used by this channel, not null.
+  final MessageCodec<T> codec;
+
+  /// The messenger which sends the bytes for this channel, not null.
+  BinaryMessenger get binaryMessenger => _binaryMessenger ?? defaultBinaryMessenger;
+  final BinaryMessenger? _binaryMessenger;
+
+  /// Sends the specified [message] to the platform plugins on this channel.
+  ///
+  /// Returns a [Future] which completes to the received response, which may
+  /// be null.
+  Future<T> send(T message) async {
+    return codec.decodeMessage(await binaryMessenger.send(name, codec.encodeMessage(message)));
+  }
+
+  /// Sets a callback for receiving messages from the platform plugins on this
+  /// channel. Messages may be null.
+  ///
+  /// The given callback will replace the currently registered callback for this
+  /// channel, if any. To remove the handler, pass null as the `handler`
+  /// argument.
+  ///
+  /// The handler's return value is sent back to the platform plugins as a
+  /// message reply. It may be null.
+  void setMessageHandler(Future<T> Function(T message)? handler) {
+    if (handler == null) {
+      binaryMessenger.setMessageHandler(name, null);
+    } else {
+      binaryMessenger.setMessageHandler(name, (ByteData? message) async {
+        return codec.encodeMessage(await handler(codec.decodeMessage(message)));
+      });
+    }
+  }
+
+  /// Sets a mock callback for intercepting messages sent on this channel.
+  /// Messages may be null.
+  ///
+  /// The given callback will replace the currently registered mock callback for
+  /// this channel, if any. To remove the mock handler, pass null as the
+  /// `handler` argument.
+  ///
+  /// The handler's return value is used as a message reply. It may be null.
+  ///
+  /// This is intended for testing. Messages intercepted in this manner are not
+  /// sent to platform plugins.
+  void setMockMessageHandler(Future<T> Function(T message)? handler) {
+    if (handler == null) {
+      binaryMessenger.setMockMessageHandler(name, null);
+    } else {
+      binaryMessenger.setMockMessageHandler(name, (ByteData? message) async {
+        return codec.encodeMessage(await handler(codec.decodeMessage(message)));
+      });
+    }
+  }
+}
+
+Expando<Object> _methodChannelHandlers = Expando<Object>();
+Expando<Object> _methodChannelMockHandlers = Expando<Object>();
+
+/// A named channel for communicating with platform plugins using asynchronous
+/// method calls.
+///
+/// Method calls are encoded into binary before being sent, and binary results
+/// received are decoded into Dart values. The [MethodCodec] used must be
+/// compatible with the one used by the platform plugin. This can be achieved
+/// by creating a method channel counterpart of this channel on the
+/// platform side. The Dart type of arguments and results is `dynamic`,
+/// but only values supported by the specified [MethodCodec] can be used.
+/// The use of unsupported values should be considered programming errors, and
+/// will result in exceptions being thrown. The null value is supported
+/// for all codecs.
+///
+/// The logical identity of the channel is given by its name. Identically named
+/// channels will interfere with each other's communication.
+///
+/// See: <https://flutter.dev/platform-channels/>
+class MethodChannel {
+  /// Creates a [MethodChannel] with the specified [name].
+  ///
+  /// The [codec] used will be [StandardMethodCodec], unless otherwise
+  /// specified.
+  ///
+  /// The [name] and [codec] arguments cannot be null. The default [ServicesBinding.defaultBinaryMessenger]
+  /// instance is used if [binaryMessenger] is null.
+  const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger ])
+      : assert(name != null),
+        assert(codec != null),
+        _binaryMessenger = binaryMessenger;
+
+  /// The logical channel on which communication happens, not null.
+  final String name;
+
+  /// The message codec used by this channel, not null.
+  final MethodCodec codec;
+
+  /// The messenger used by this channel to send platform messages.
+  ///
+  /// The messenger may not be null.
+  BinaryMessenger get binaryMessenger => _binaryMessenger ?? defaultBinaryMessenger;
+  final BinaryMessenger? _binaryMessenger;
+
+  @optionalTypeArgs
+  Future<T?> _invokeMethod<T>(String method, { required bool missingOk, dynamic arguments }) async {
+    assert(method != null);
+    final ByteData? result = await binaryMessenger.send(
+      name,
+      codec.encodeMethodCall(MethodCall(method, arguments)),
+    );
+    if (result == null) {
+      if (missingOk) {
+        return null;
+      }
+      throw MissingPluginException('No implementation found for method $method on channel $name');
+    }
+    return codec.decodeEnvelope(result) as T?;
+  }
+
+  /// Invokes a [method] on this channel with the specified [arguments].
+  ///
+  /// The static type of [arguments] is `dynamic`, but only values supported by
+  /// the [codec] of this channel can be used. The same applies to the returned
+  /// result. The values supported by the default codec and their platform-specific
+  /// counterparts are documented with [StandardMessageCodec].
+  ///
+  /// The generic argument `T` of the method can be inferred by the surrounding
+  /// context, or provided explicitly. If it does not match the returned type of
+  /// the channel, a [TypeError] will be thrown at runtime. `T` cannot be a class
+  /// with generics other than `dynamic`. For example, `Map<String, String>`
+  /// is not supported but `Map<dynamic, dynamic>` or `Map` is.
+  ///
+  /// Returns a [Future] which completes to one of the following:
+  ///
+  /// * a result (possibly null), on successful invocation;
+  /// * a [PlatformException], if the invocation failed in the platform plugin;
+  /// * a [MissingPluginException], if the method has not been implemented by a
+  ///   platform plugin.
+  ///
+  /// The following code snippets demonstrate how to invoke platform methods
+  /// in Dart using a MethodChannel and how to implement those methods in Java
+  /// (for Android) and Objective-C (for iOS).
+  ///
+  /// {@tool snippet}
+  ///
+  /// The code might be packaged up as a musical plugin, see
+  /// <https://flutter.dev/developing-packages/>:
+  ///
+  /// ```dart
+  /// class Music {
+  ///   static const MethodChannel _channel = MethodChannel('music');
+  ///
+  ///   static Future<bool> isLicensed() async {
+  ///     // invokeMethod returns a Future<T> which can be inferred as bool
+  ///     // in this context.
+  ///     return _channel.invokeMethod('isLicensed');
+  ///   }
+  ///
+  ///   static Future<List<Song>> songs() async {
+  ///     // invokeMethod here returns a Future<dynamic> that completes to a
+  ///     // List<dynamic> with Map<dynamic, dynamic> entries. Post-processing
+  ///     // code thus cannot assume e.g. List<Map<String, String>> even though
+  ///     // the actual values involved would support such a typed container.
+  ///     // The correct type cannot be inferred with any value of `T`.
+  ///     final List<dynamic> songs = await _channel.invokeMethod('getSongs');
+  ///     return songs.map(Song.fromJson).toList();
+  ///   }
+  ///
+  ///   static Future<void> play(Song song, double volume) async {
+  ///     // Errors occurring on the platform side cause invokeMethod to throw
+  ///     // PlatformExceptions.
+  ///     try {
+  ///       return _channel.invokeMethod('play', <String, dynamic>{
+  ///         'song': song.id,
+  ///         'volume': volume,
+  ///       });
+  ///     } on PlatformException catch (e) {
+  ///       throw 'Unable to play ${song.title}: ${e.message}';
+  ///     }
+  ///   }
+  /// }
+  ///
+  /// class Song {
+  ///   Song(this.id, this.title, this.artist);
+  ///
+  ///   final String id;
+  ///   final String title;
+  ///   final String artist;
+  ///
+  ///   static Song fromJson(dynamic json) {
+  ///     return Song(json['id'], json['title'], json['artist']);
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Java (for Android):
+  ///
+  /// ```java
+  /// // Assumes existence of an Android MusicApi.
+  /// public class MusicPlugin implements MethodCallHandler {
+  ///   @Override
+  ///   public void onMethodCall(MethodCall call, Result result) {
+  ///     switch (call.method) {
+  ///       case "isLicensed":
+  ///         result.success(MusicApi.checkLicense());
+  ///         break;
+  ///       case "getSongs":
+  ///         final List<MusicApi.Track> tracks = MusicApi.getTracks();
+  ///         final List<Object> json = ArrayList<>(tracks.size());
+  ///         for (MusicApi.Track track : tracks) {
+  ///           json.add(track.toJson()); // Map<String, Object> entries
+  ///         }
+  ///         result.success(json);
+  ///         break;
+  ///       case "play":
+  ///         final String song = call.argument("song");
+  ///         final double volume = call.argument("volume");
+  ///         try {
+  ///           MusicApi.playSongAtVolume(song, volume);
+  ///           result.success(null);
+  ///         } catch (MusicalException e) {
+  ///           result.error("playError", e.getMessage(), null);
+  ///         }
+  ///         break;
+  ///       default:
+  ///         result.notImplemented();
+  ///     }
+  ///   }
+  ///   // Other methods elided.
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Objective-C (for iOS):
+  ///
+  /// ```objectivec
+  /// @interface MusicPlugin : NSObject<FlutterPlugin>
+  /// @end
+  ///
+  /// // Assumes existence of an iOS Broadway Play Api.
+  /// @implementation MusicPlugin
+  /// - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
+  ///   if ([@"isLicensed" isEqualToString:call.method]) {
+  ///     result([NSNumber numberWithBool:[BWPlayApi isLicensed]]);
+  ///   } else if ([@"getSongs" isEqualToString:call.method]) {
+  ///     NSArray* items = [BWPlayApi items];
+  ///     NSMutableArray* json = [NSMutableArray arrayWithCapacity:items.count];
+  ///     for (final BWPlayItem* item in items) {
+  ///       [json addObject:@{@"id":item.itemId, @"title":item.name, @"artist":item.artist}];
+  ///     }
+  ///     result(json);
+  ///   } else if ([@"play" isEqualToString:call.method]) {
+  ///     NSString* itemId = call.arguments[@"song"];
+  ///     NSNumber* volume = call.arguments[@"volume"];
+  ///     NSError* error = nil;
+  ///     BOOL success = [BWPlayApi playItem:itemId volume:volume.doubleValue error:&error];
+  ///     if (success) {
+  ///       result(nil);
+  ///     } else {
+  ///       result([FlutterError errorWithCode:[NSString stringWithFormat:@"Error %ld", error.code]
+  ///                                  message:error.domain
+  ///                                  details:error.localizedDescription]);
+  ///     }
+  ///   } else {
+  ///     result(FlutterMethodNotImplemented);
+  ///   }
+  /// }
+  /// // Other methods elided.
+  /// @end
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [invokeListMethod], for automatically returning typed lists.
+  ///  * [invokeMapMethod], for automatically returning typed maps.
+  ///  * [StandardMessageCodec] which defines the payload values supported by
+  ///    [StandardMethodCodec].
+  ///  * [JSONMessageCodec] which defines the payload values supported by
+  ///    [JSONMethodCodec].
+  ///  * <https://api.flutter.dev/javadoc/io/flutter/plugin/common/MethodCall.html>
+  ///    for how to access method call arguments on Android.
+  @optionalTypeArgs
+  Future<T?> invokeMethod<T>(String method, [ dynamic arguments ]) {
+    return _invokeMethod<T>(method, missingOk: false, arguments: arguments);
+  }
+
+  /// An implementation of [invokeMethod] that can return typed lists.
+  ///
+  /// Dart generics are reified, meaning that an untyped List<dynamic>
+  /// cannot masquerade as a List<T>. Since invokeMethod can only return
+  /// dynamic maps, we instead create a new typed list using [List.cast].
+  ///
+  /// See also:
+  ///
+  ///  * [invokeMethod], which this call delegates to.
+  Future<List<T>?> invokeListMethod<T>(String method, [ dynamic arguments ]) async {
+    final List<dynamic>? result = await invokeMethod<List<dynamic>?>(method, arguments);
+    return result?.cast<T>();
+  }
+
+  /// An implementation of [invokeMethod] that can return typed maps.
+  ///
+  /// Dart generics are reified, meaning that an untyped Map<dynamic, dynamic>
+  /// cannot masquerade as a Map<K, V>. Since invokeMethod can only return
+  /// dynamic maps, we instead create a new typed map using [Map.cast].
+  ///
+  /// See also:
+  ///
+  ///  * [invokeMethod], which this call delegates to.
+  Future<Map<K, V>?> invokeMapMethod<K, V>(String method, [ dynamic arguments ]) async {
+    final Map<dynamic, dynamic>? result = await invokeMethod<Map<dynamic, dynamic>?>(method, arguments);
+    return result?.cast<K, V>();
+  }
+
+  /// Sets a callback for receiving method calls on this channel.
+  ///
+  /// The given callback will replace the currently registered callback for this
+  /// channel, if any. To remove the handler, pass null as the
+  /// `handler` argument.
+  ///
+  /// If the future returned by the handler completes with a result, that value
+  /// is sent back to the platform plugin caller wrapped in a success envelope
+  /// as defined by the [codec] of this channel. If the future completes with
+  /// a [PlatformException], the fields of that exception will be used to
+  /// populate an error envelope which is sent back instead. If the future
+  /// completes with a [MissingPluginException], an empty reply is sent
+  /// similarly to what happens if no method call handler has been set.
+  /// Any other exception results in an error envelope being sent.
+  void setMethodCallHandler(Future<dynamic> Function(MethodCall call)? handler) {
+    _methodChannelHandlers[this] = handler;
+    binaryMessenger.setMessageHandler(
+      name,
+      handler == null
+        ? null
+        : (ByteData? message) => _handleAsMethodCall(message, handler),
+    );
+  }
+
+  /// Returns true if the `handler` argument matches the `handler` previously
+  /// passed to [setMethodCallHandler].
+  ///
+  /// This method is useful for tests or test harnesses that want to assert the
+  /// handler for the specified channel has not been altered by a previous test.
+  ///
+  /// Passing null for the `handler` returns true if the handler for the channel
+  /// is not set.
+  bool checkMethodCallHandler(Future<dynamic> Function(MethodCall call)? handler) => _methodChannelHandlers[this] == handler;
+
+  /// Sets a mock callback for intercepting method invocations on this channel.
+  ///
+  /// The given callback will replace the currently registered mock callback for
+  /// this channel, if any. To remove the mock handler, pass null as the
+  /// `handler` argument.
+  ///
+  /// Later calls to [invokeMethod] will result in a successful result,
+  /// a [PlatformException] or a [MissingPluginException], determined by how
+  /// the future returned by the mock callback completes. The [codec] of this
+  /// channel is used to encode and decode values and errors.
+  ///
+  /// This is intended for testing. Method calls intercepted in this manner are
+  /// not sent to platform plugins.
+  ///
+  /// The provided `handler` must return a `Future` that completes with the
+  /// return value of the call. The value will be encoded using
+  /// [MethodCodec.encodeSuccessEnvelope], to act as if platform plugin had
+  /// returned that value.
+  void setMockMethodCallHandler(Future<dynamic>? Function(MethodCall call)? handler) {
+    _methodChannelMockHandlers[this] = handler;
+    binaryMessenger.setMockMessageHandler(
+      name,
+      handler == null ? null : (ByteData? message) => _handleAsMethodCall(message, handler),
+    );
+  }
+
+  /// Returns true if the `handler` argument matches the `handler` previously
+  /// passed to [setMockMethodCallHandler].
+  ///
+  /// This method is useful for tests or test harnesses that want to assert the
+  /// handler for the specified channel has not been altered by a previous test.
+  ///
+  /// Passing null for the `handler` returns true if the handler for the channel
+  /// is not set.
+  bool checkMockMethodCallHandler(Future<dynamic> Function(MethodCall call)? handler) => _methodChannelMockHandlers[this] == handler;
+
+  Future<ByteData?> _handleAsMethodCall(ByteData? message, Future<dynamic>? handler(MethodCall call)) async {
+    final MethodCall call = codec.decodeMethodCall(message);
+    try {
+      return codec.encodeSuccessEnvelope(await handler(call));
+    } on PlatformException catch (e) {
+      return codec.encodeErrorEnvelope(
+        code: e.code,
+        message: e.message,
+        details: e.details,
+      );
+    } on MissingPluginException {
+      return null;
+    } catch (e) {
+      return codec.encodeErrorEnvelope(code: 'error', message: e.toString(), details: null);
+    }
+  }
+}
+
+/// A [MethodChannel] that ignores missing platform plugins.
+///
+/// When [invokeMethod] fails to find the platform plugin, it returns null
+/// instead of throwing an exception.
+class OptionalMethodChannel extends MethodChannel {
+  /// Creates a [MethodChannel] that ignores missing platform plugins.
+  const OptionalMethodChannel(String name, [MethodCodec codec = const StandardMethodCodec()])
+    : super(name, codec);
+
+  @override
+  Future<T?> invokeMethod<T>(String method, [ dynamic arguments ]) async {
+    return super._invokeMethod<T>(method, missingOk: true, arguments: arguments);
+  }
+
+  @override
+  Future<List<T>?> invokeListMethod<T>(String method, [ dynamic arguments ]) async {
+    final List<dynamic>? result = await invokeMethod<List<dynamic>>(method, arguments);
+    return result?.cast<T>();
+  }
+
+  @override
+  Future<Map<K, V>?> invokeMapMethod<K, V>(String method, [ dynamic arguments ]) async {
+    final Map<dynamic, dynamic>? result = await invokeMethod<Map<dynamic, dynamic>>(method, arguments);
+    return result?.cast<K, V>();
+  }
+
+}
+
+/// A named channel for communicating with platform plugins using event streams.
+///
+/// Stream setup requests are encoded into binary before being sent,
+/// and binary events and errors received are decoded into Dart values.
+/// The [MethodCodec] used must be compatible with the one used by the platform
+/// plugin. This can be achieved by creating an `EventChannel` counterpart of
+/// this channel on the platform side. The Dart type of events sent and received
+/// is `dynamic`, but only values supported by the specified [MethodCodec] can
+/// be used.
+///
+/// The logical identity of the channel is given by its name. Identically named
+/// channels will interfere with each other's communication.
+///
+/// See: <https://flutter.dev/platform-channels/>
+class EventChannel {
+  /// Creates an [EventChannel] with the specified [name].
+  ///
+  /// The [codec] used will be [StandardMethodCodec], unless otherwise
+  /// specified.
+  ///
+  /// Neither [name] nor [codec] may be null. The default [ServicesBinding.defaultBinaryMessenger]
+  /// instance is used if [binaryMessenger] is null.
+  const EventChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger])
+      : assert(name != null),
+        assert(codec != null),
+        _binaryMessenger = binaryMessenger;
+
+  /// The logical channel on which communication happens, not null.
+  final String name;
+
+  /// The message codec used by this channel, not null.
+  final MethodCodec codec;
+
+  /// The messenger used by this channel to send platform messages, not null.
+  BinaryMessenger get binaryMessenger => _binaryMessenger ?? defaultBinaryMessenger;
+  final BinaryMessenger? _binaryMessenger;
+
+  /// Sets up a broadcast stream for receiving events on this channel.
+  ///
+  /// Returns a broadcast [Stream] which emits events to listeners as follows:
+  ///
+  /// * a decoded data event (possibly null) for each successful event
+  ///   received from the platform plugin;
+  /// * an error event containing a [PlatformException] for each error event
+  ///   received from the platform plugin.
+  ///
+  /// Errors occurring during stream activation or deactivation are reported
+  /// through the [FlutterError] facility. Stream activation happens only when
+  /// stream listener count changes from 0 to 1. Stream deactivation happens
+  /// only when stream listener count changes from 1 to 0.
+  Stream<dynamic> receiveBroadcastStream([ dynamic arguments ]) {
+    final MethodChannel methodChannel = MethodChannel(name, codec);
+    late StreamController<dynamic> controller;
+    controller = StreamController<dynamic>.broadcast(onListen: () async {
+      binaryMessenger.setMessageHandler(name, (ByteData? reply) async {
+        if (reply == null) {
+          controller.close();
+        } else {
+          try {
+            controller.add(codec.decodeEnvelope(reply));
+          } on PlatformException catch (e) {
+            controller.addError(e);
+          }
+        }
+        return null;
+      });
+      try {
+        await methodChannel.invokeMethod<void>('listen', arguments);
+      } catch (exception, stack) {
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          library: 'services library',
+          context: ErrorDescription('while activating platform stream on channel $name'),
+        ));
+      }
+    }, onCancel: () async {
+      binaryMessenger.setMessageHandler(name, null);
+      try {
+        await methodChannel.invokeMethod<void>('cancel', arguments);
+      } catch (exception, stack) {
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          library: 'services library',
+          context: ErrorDescription('while de-activating platform stream on channel $name'),
+        ));
+      }
+    });
+    return controller.stream;
+  }
+}
diff --git a/lib/src/services/platform_messages.dart b/lib/src/services/platform_messages.dart
new file mode 100644
index 0000000..50cb3bc
--- /dev/null
+++ b/lib/src/services/platform_messages.dart
@@ -0,0 +1,106 @@
+// Copyright 2014 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.
+
+
+import 'dart:typed_data';
+import 'package:flute/ui.dart' as ui;
+
+import 'binary_messenger.dart';
+import 'platform_channel.dart';
+
+/// Sends binary messages to and receives binary messages from platform plugins.
+///
+/// This class has been deprecated in favor of [defaultBinaryMessenger]. New
+/// code should not use [BinaryMessages].
+///
+/// See also:
+///
+///  * [BinaryMessenger], the interface which has replaced this class.
+///  * [BasicMessageChannel], which provides basic messaging services similar to
+///    `BinaryMessages`, but with pluggable message codecs in support of sending
+///    strings or semi-structured messages.
+///  * [MethodChannel], which provides platform communication using asynchronous
+///    method calls.
+///  * [EventChannel], which provides platform communication using event streams.
+///  * <https://flutter.dev/platform-channels/>
+@Deprecated(
+  'This class, which was just a collection of static methods, has been '
+  'deprecated in favor of BinaryMessenger, and its default implementation, '
+  'defaultBinaryMessenger. '
+  'This feature was deprecated after v1.6.5.'
+)
+class BinaryMessages {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  BinaryMessages._();
+
+  /// The messenger which sends the platform messages, not null.
+  static final BinaryMessenger _binaryMessenger = defaultBinaryMessenger;
+
+  /// Calls the handler registered for the given channel.
+  ///
+  /// Typically called by [ServicesBinding] to handle platform messages received
+  /// from [dart:ui.PlatformDispatcher.onPlatformMessage].
+  ///
+  /// To register a handler for a given message channel, see [setMessageHandler].
+  @Deprecated(
+    'Use defaultBinaryMessenger.handlePlatformMessage instead. '
+    'This feature was deprecated after v1.6.5.'
+  )
+  static Future<void> handlePlatformMessage(
+    String channel,
+    ByteData data,
+    ui.PlatformMessageResponseCallback callback,
+  ) {
+    return _binaryMessenger.handlePlatformMessage(channel, data, callback);
+  }
+
+  /// Send a binary message to the platform plugins on the given channel.
+  ///
+  /// Returns a [Future] which completes to the received response, undecoded, in
+  /// binary form.
+  @Deprecated(
+    'Use defaultBinaryMessenger.send instead. '
+    'This feature was deprecated after v1.6.5.'
+  )
+  static Future<ByteData?>? send(String channel, ByteData? message) {
+    return _binaryMessenger.send(channel, message);
+  }
+
+  /// Set a callback for receiving messages from the platform plugins on the
+  /// given channel, without decoding them.
+  ///
+  /// The given callback will replace the currently registered callback for that
+  /// channel, if any. To remove the handler, pass null as the `handler`
+  /// argument.
+  ///
+  /// The handler's return value, if non-null, is sent as a response, unencoded.
+  @Deprecated(
+    'Use defaultBinaryMessenger.setMessageHandler instead. '
+    'This feature was deprecated after v1.6.5.'
+  )
+  static void setMessageHandler(String channel, Future<ByteData?> Function(ByteData? message) handler) {
+    _binaryMessenger.setMessageHandler(channel, handler);
+  }
+
+  /// Set a mock callback for intercepting messages from the `send*` methods on
+  /// this class, on the given channel, without decoding them.
+  ///
+  /// The given callback will replace the currently registered mock callback for
+  /// that channel, if any. To remove the mock handler, pass null as the
+  /// `handler` argument.
+  ///
+  /// The handler's return value, if non-null, is used as a response, unencoded.
+  ///
+  /// This is intended for testing. Messages intercepted in this manner are not
+  /// sent to platform plugins.
+  @Deprecated(
+    'Use defaultBinaryMessenger.setMockMessageHandler instead. '
+    'This feature was deprecated after v1.6.5.'
+  )
+  static void setMockMessageHandler(String channel, Future<ByteData?> Function(ByteData? message) handler) {
+    _binaryMessenger.setMockMessageHandler(channel, handler);
+  }
+}
diff --git a/lib/src/services/platform_views.dart b/lib/src/services/platform_views.dart
new file mode 100644
index 0000000..0c68080
--- /dev/null
+++ b/lib/src/services/platform_views.dart
@@ -0,0 +1,1144 @@
+// Copyright 2014 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.
+
+
+import 'dart:typed_data';
+import 'package:flute/ui.dart';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+
+import 'message_codec.dart';
+import 'system_channels.dart';
+
+/// Converts a given point from the global coordinate system in logical pixels
+/// to the local coordinate system for a box.
+///
+/// Used by [AndroidViewController.pointTransformer].
+typedef PointTransformer = Offset Function(Offset position);
+
+/// The [PlatformViewsRegistry] responsible for generating unique identifiers for platform views.
+final PlatformViewsRegistry platformViewsRegistry = PlatformViewsRegistry._instance();
+
+/// A registry responsible for generating unique identifier for platform views.
+///
+/// A Flutter application has a single [PlatformViewsRegistry] which can be accesses
+/// through the [platformViewsRegistry] getter.
+class PlatformViewsRegistry {
+  PlatformViewsRegistry._instance();
+
+  // Always non-negative. The id value -1 is used in the accessibility bridge
+  // to indicate the absence of a platform view.
+  int _nextPlatformViewId = 0;
+
+  /// Allocates a unique identifier for a platform view.
+  ///
+  /// A platform view identifier can refer to a platform view that was never created,
+  /// a platform view that was disposed, or a platform view that is alive.
+  ///
+  /// Typically a platform view identifier is passed to a platform view widget
+  /// which creates the platform view and manages its lifecycle.
+  int getNextPlatformViewId() => _nextPlatformViewId++;
+}
+
+/// Callback signature for when a platform view was created.
+///
+/// `id` is the platform view's unique identifier.
+typedef PlatformViewCreatedCallback = void Function(int id);
+
+/// Provides access to the platform views service.
+///
+/// This service allows creating and controlling platform-specific views.
+class PlatformViewsService {
+  PlatformViewsService._() {
+    SystemChannels.platform_views.setMethodCallHandler(_onMethodCall);
+  }
+
+  static final PlatformViewsService _instance = PlatformViewsService._();
+
+  Future<void> _onMethodCall(MethodCall call) {
+    switch(call.method) {
+      case 'viewFocused':
+        final int id = call.arguments as int;
+        if (_focusCallbacks.containsKey(id)) {
+          _focusCallbacks[id]!();
+        }
+        break;
+      default:
+        throw UnimplementedError("${call.method} was invoked but isn't implemented by PlatformViewsService");
+    }
+    return Future<void>.value();
+  }
+
+  /// Maps platform view IDs to focus callbacks.
+  ///
+  /// The callbacks are invoked when the platform view asks to be focused.
+  final Map<int, VoidCallback> _focusCallbacks = <int, VoidCallback>{};
+
+
+  /// Creates a [TextureAndroidViewController] for a new Android view.
+  ///
+  /// The view is created after calling [TextureAndroidViewController.setSize].
+  ///
+  /// `id` is an unused unique identifier generated with [platformViewsRegistry].
+  ///
+  /// `viewType` is the identifier of the Android view type to be created, a
+  /// factory for this view type must have been registered on the platform side.
+  /// Platform view factories are typically registered by plugin code.
+  /// Plugins can register a platform view factory with
+  /// [PlatformViewRegistry#registerViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewRegistry.html#registerViewFactory-java.lang.String-io.flutter.plugin.platform.PlatformViewFactory-).
+  ///
+  /// `creationParams` will be passed as the args argument of [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-android.content.Context-int-java.lang.Object-)
+  ///
+  /// `creationParamsCodec` is the codec used to encode `creationParams` before sending it to the
+  /// platform side. It should match the codec passed to the constructor of [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#PlatformViewFactory-io.flutter.plugin.common.MessageCodec-).
+  /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec].
+  ///
+  /// `onFocus` is a callback that will be invoked when the Android View asks to get the
+  /// input focus.
+  ///
+  /// The Android view will only be created after [AndroidViewController.setSize] is called for the
+  /// first time.
+  ///
+  /// The `id, `viewType, and `layoutDirection` parameters must not be null.
+  /// If `creationParams` is non null then `creationParamsCodec` must not be null.
+  static TextureAndroidViewController initAndroidView({
+    required int id,
+    required String viewType,
+    required TextDirection layoutDirection,
+    dynamic creationParams,
+    MessageCodec<dynamic>? creationParamsCodec,
+    VoidCallback? onFocus,
+  }) {
+    assert(id != null);
+    assert(viewType != null);
+    assert(layoutDirection != null);
+    assert(creationParams == null || creationParamsCodec != null);
+
+    final TextureAndroidViewController controller = TextureAndroidViewController._(
+      viewId: id,
+      viewType: viewType,
+      layoutDirection: layoutDirection,
+      creationParams: creationParams,
+      creationParamsCodec: creationParamsCodec,
+    );
+
+    _instance._focusCallbacks[id] = onFocus ?? () {};
+    return controller;
+  }
+
+  /// Creates a [SurfaceAndroidViewController] for a new Android view.
+  ///
+  /// The view is created after calling [AndroidViewController.create].
+  ///
+  /// `id` is an unused unique identifier generated with [platformViewsRegistry].
+  ///
+  /// `viewType` is the identifier of the Android view type to be created, a
+  /// factory for this view type must have been registered on the platform side.
+  /// Platform view factories are typically registered by plugin code.
+  /// Plugins can register a platform view factory with
+  /// [PlatformViewRegistry#registerViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewRegistry.html#registerViewFactory-java.lang.String-io.flutter.plugin.platform.PlatformViewFactory-).
+  ///
+  /// `creationParams` will be passed as the args argument of [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-android.content.Context-int-java.lang.Object-)
+  ///
+  /// `creationParamsCodec` is the codec used to encode `creationParams` before sending it to the
+  /// platform side. It should match the codec passed to the constructor of [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#PlatformViewFactory-io.flutter.plugin.common.MessageCodec-).
+  /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec].
+  ///
+  /// `onFocus` is a callback that will be invoked when the Android View asks to get the
+  /// input focus.
+  ///
+  /// The Android view will only be created after [AndroidViewController.setSize] is called for the
+  /// first time.
+  ///
+  /// The `id, `viewType, and `layoutDirection` parameters must not be null.
+  /// If `creationParams` is non null then `creationParamsCodec` must not be null.
+  static SurfaceAndroidViewController initSurfaceAndroidView({
+    required int id,
+    required String viewType,
+    required TextDirection layoutDirection,
+    dynamic creationParams,
+    MessageCodec<dynamic>? creationParamsCodec,
+    VoidCallback? onFocus,
+  }) {
+    assert(id != null);
+    assert(viewType != null);
+    assert(layoutDirection != null);
+    assert(creationParams == null || creationParamsCodec != null);
+
+    final SurfaceAndroidViewController controller = SurfaceAndroidViewController._(
+      viewId: id,
+      viewType: viewType,
+      layoutDirection: layoutDirection,
+      creationParams: creationParams,
+      creationParamsCodec: creationParamsCodec,
+    );
+
+    _instance._focusCallbacks[id] = onFocus ?? () {};
+    return controller;
+  }
+
+  // TODO(amirh): reference the iOS plugin API for registering a UIView factory once it lands.
+  /// This is work in progress, not yet ready to be used, and requires a custom engine build. Creates a controller for a new iOS UIView.
+  ///
+  /// `id` is an unused unique identifier generated with [platformViewsRegistry].
+  ///
+  /// `viewType` is the identifier of the iOS view type to be created, a
+  /// factory for this view type must have been registered on the platform side.
+  /// Platform view factories are typically registered by plugin code.
+  ///
+  /// The `id, `viewType, and `layoutDirection` parameters must not be null.
+  /// If `creationParams` is non null then `creationParamsCodec` must not be null.
+  static Future<UiKitViewController> initUiKitView({
+    required int id,
+    required String viewType,
+    required TextDirection layoutDirection,
+    dynamic creationParams,
+    MessageCodec<dynamic>? creationParamsCodec,
+  }) async {
+    assert(id != null);
+    assert(viewType != null);
+    assert(layoutDirection != null);
+    assert(creationParams == null || creationParamsCodec != null);
+
+    // TODO(amirh): pass layoutDirection once the system channel supports it.
+    final Map<String, dynamic> args = <String, dynamic>{
+      'id': id,
+      'viewType': viewType,
+    };
+    if (creationParams != null) {
+      final ByteData paramsByteData = creationParamsCodec!.encodeMessage(creationParams)!;
+      args['params'] = Uint8List.view(
+        paramsByteData.buffer,
+        0,
+        paramsByteData.lengthInBytes,
+      );
+    }
+    await SystemChannels.platform_views.invokeMethod<void>('create', args);
+    return UiKitViewController._(id, layoutDirection);
+  }
+}
+
+/// Properties of an Android pointer.
+///
+/// A Dart version of Android's [MotionEvent.PointerProperties](https://developer.android.com/reference/android/view/MotionEvent.PointerProperties).
+class AndroidPointerProperties {
+  /// Creates an AndroidPointerProperties.
+  ///
+  /// All parameters must not be null.
+  const AndroidPointerProperties({
+    required this.id,
+    required this.toolType,
+  }) : assert(id != null),
+       assert(toolType != null);
+
+  /// See Android's [MotionEvent.PointerProperties#id](https://developer.android.com/reference/android/view/MotionEvent.PointerProperties.html#id).
+  final int id;
+
+  /// The type of tool used to make contact such as a finger or stylus, if known.
+  /// See Android's [MotionEvent.PointerProperties#toolType](https://developer.android.com/reference/android/view/MotionEvent.PointerProperties.html#toolType).
+  final int toolType;
+
+  /// Value for `toolType` when the tool type is unknown.
+  static const int kToolTypeUnknown = 0;
+
+  /// Value for `toolType` when the tool type is a finger.
+  static const int kToolTypeFinger = 1;
+
+  /// Value for `toolType` when the tool type is a stylus.
+  static const int kToolTypeStylus = 2;
+
+  /// Value for `toolType` when the tool type is a mouse.
+  static const int kToolTypeMouse = 3;
+
+  /// Value for `toolType` when the tool type is an eraser.
+  static const int kToolTypeEraser = 4;
+
+  List<int> _asList() => <int>[id, toolType];
+
+  @override
+  String toString() {
+    return 'AndroidPointerProperties(id: $id, toolType: $toolType)';
+  }
+}
+
+/// Position information for an Android pointer.
+///
+/// A Dart version of Android's [MotionEvent.PointerCoords](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords).
+class AndroidPointerCoords {
+  /// Creates an AndroidPointerCoords.
+  ///
+  /// All parameters must not be null.
+  const AndroidPointerCoords({
+    required this.orientation,
+    required this.pressure,
+    required this.size,
+    required this.toolMajor,
+    required this.toolMinor,
+    required this.touchMajor,
+    required this.touchMinor,
+    required this.x,
+    required this.y,
+  }) : assert(orientation != null),
+       assert(pressure != null),
+       assert(size != null),
+       assert(toolMajor != null),
+       assert(toolMinor != null),
+       assert(touchMajor != null),
+       assert(touchMinor != null),
+       assert(x != null),
+       assert(y != null);
+
+  /// The orientation of the touch area and tool area in radians clockwise from vertical.
+  ///
+  /// See Android's [MotionEvent.PointerCoords#orientation](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#orientation).
+  final double orientation;
+
+  /// A normalized value that describes the pressure applied to the device by a finger or other tool.
+  ///
+  /// See Android's [MotionEvent.PointerCoords#pressure](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#pressure).
+  final double pressure;
+
+  /// A normalized value that describes the approximate size of the pointer touch area in relation to the maximum detectable size of the device.
+  ///
+  /// See Android's [MotionEvent.PointerCoords#size](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#size).
+  final double size;
+
+  /// See Android's [MotionEvent.PointerCoords#toolMajor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#toolMajor).
+  final double toolMajor;
+
+  /// See Android's [MotionEvent.PointerCoords#toolMinor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#toolMinor).
+  final double toolMinor;
+
+  /// See Android's [MotionEvent.PointerCoords#touchMajor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#touchMajor).
+  final double touchMajor;
+
+  /// See Android's [MotionEvent.PointerCoords#touchMinor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#touchMinor).
+  final double touchMinor;
+
+  /// The X component of the pointer movement.
+  ///
+  /// See Android's [MotionEvent.PointerCoords#x](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#x).
+  final double x;
+
+  /// The Y component of the pointer movement.
+  ///
+  /// See Android's [MotionEvent.PointerCoords#y](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#y).
+  final double y;
+
+  List<double> _asList() {
+    return <double>[
+      orientation,
+      pressure,
+      size,
+      toolMajor,
+      toolMinor,
+      touchMajor,
+      touchMinor,
+      x,
+      y,
+    ];
+  }
+
+  @override
+  String toString() {
+    return 'AndroidPointerCoords(orientation: $orientation, pressure: $pressure, size: $size, toolMajor: $toolMajor, toolMinor: $toolMinor, touchMajor: $touchMajor, touchMinor: $touchMinor, x: $x, y: $y)';
+  }
+}
+
+/// A Dart version of Android's [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent).
+class AndroidMotionEvent {
+  /// Creates an AndroidMotionEvent.
+  ///
+  /// All parameters must not be null.
+  AndroidMotionEvent({
+    required this.downTime,
+    required this.eventTime,
+    required this.action,
+    required this.pointerCount,
+    required this.pointerProperties,
+    required this.pointerCoords,
+    required this.metaState,
+    required this.buttonState,
+    required this.xPrecision,
+    required this.yPrecision,
+    required this.deviceId,
+    required this.edgeFlags,
+    required this.source,
+    required this.flags,
+    required this.motionEventId,
+  }) : assert(downTime != null),
+       assert(eventTime != null),
+       assert(action != null),
+       assert(pointerCount != null),
+       assert(pointerProperties != null),
+       assert(pointerCoords != null),
+       assert(metaState != null),
+       assert(buttonState != null),
+       assert(xPrecision != null),
+       assert(yPrecision != null),
+       assert(deviceId != null),
+       assert(edgeFlags != null),
+       assert(source != null),
+       assert(flags != null),
+       assert(pointerProperties.length == pointerCount),
+       assert(pointerCoords.length == pointerCount);
+
+  /// The time (in ms) when the user originally pressed down to start a stream of position events,
+  /// relative to an arbitrary timeline.
+  ///
+  /// See Android's [MotionEvent#getDownTime](https://developer.android.com/reference/android/view/MotionEvent.html#getDownTime()).
+  final int downTime;
+
+  /// The time this event occurred, relative to an arbitrary timeline.
+  ///
+  /// See Android's [MotionEvent#getEventTime](https://developer.android.com/reference/android/view/MotionEvent.html#getEventTime()).
+  final int eventTime;
+
+  /// A value representing the kind of action being performed.
+  ///
+  /// See Android's [MotionEvent#getAction](https://developer.android.com/reference/android/view/MotionEvent.html#getAction()).
+  final int action;
+
+  /// The number of pointers that are part of this event.
+  /// This must be equivalent to the length of `pointerProperties` and `pointerCoords`.
+  ///
+  /// See Android's [MotionEvent#getPointerCount](https://developer.android.com/reference/android/view/MotionEvent.html#getPointerCount()).
+  final int pointerCount;
+
+  /// List of [AndroidPointerProperties] for each pointer that is part of this event.
+  final List<AndroidPointerProperties> pointerProperties;
+
+  /// List of [AndroidPointerCoords] for each pointer that is part of this event.
+  final List<AndroidPointerCoords> pointerCoords;
+
+  /// The state of any meta / modifier keys that were in effect when the event was generated.
+  ///
+  /// See Android's [MotionEvent#getMetaState](https://developer.android.com/reference/android/view/MotionEvent.html#getMetaState()).
+  final int metaState;
+
+  /// The state of all buttons that are pressed such as a mouse or stylus button.
+  ///
+  /// See Android's [MotionEvent#getButtonState](https://developer.android.com/reference/android/view/MotionEvent.html#getButtonState()).
+  final int buttonState;
+
+  /// The precision of the X coordinates being reported, in physical pixels.
+  ///
+  /// See Android's [MotionEvent#getXPrecision](https://developer.android.com/reference/android/view/MotionEvent.html#getXPrecision()).
+  final double xPrecision;
+
+  /// The precision of the Y coordinates being reported, in physical pixels.
+  ///
+  /// See Android's [MotionEvent#getYPrecision](https://developer.android.com/reference/android/view/MotionEvent.html#getYPrecision()).
+  final double yPrecision;
+
+  /// See Android's [MotionEvent#getDeviceId](https://developer.android.com/reference/android/view/MotionEvent.html#getDeviceId()).
+  final int deviceId;
+
+  /// A bit field indicating which edges, if any, were touched by this MotionEvent.
+  ///
+  /// See Android's [MotionEvent#getEdgeFlags](https://developer.android.com/reference/android/view/MotionEvent.html#getEdgeFlags()).
+  final int edgeFlags;
+
+  /// The source of this event (e.g a touchpad or stylus).
+  ///
+  /// See Android's [MotionEvent#getSource](https://developer.android.com/reference/android/view/MotionEvent.html#getSource()).
+  final int source;
+
+  /// See Android's [MotionEvent#getFlags](https://developer.android.com/reference/android/view/MotionEvent.html#getFlags()).
+  final int flags;
+
+  /// Used to identify this [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent.html) uniquely in the Flutter Engine.
+  final int motionEventId;
+
+  List<dynamic> _asList(int viewId) {
+    return <dynamic>[
+      viewId,
+      downTime,
+      eventTime,
+      action,
+      pointerCount,
+      pointerProperties.map<List<int>>((AndroidPointerProperties p) => p._asList()).toList(),
+      pointerCoords.map<List<double>>((AndroidPointerCoords p) => p._asList()).toList(),
+      metaState,
+      buttonState,
+      xPrecision,
+      yPrecision,
+      deviceId,
+      edgeFlags,
+      source,
+      flags,
+      motionEventId,
+    ];
+  }
+
+  @override
+  String toString() {
+    return 'AndroidPointerEvent(downTime: $downTime, eventTime: $eventTime, action: $action, pointerCount: $pointerCount, pointerProperties: $pointerProperties, pointerCoords: $pointerCoords, metaState: $metaState, buttonState: $buttonState, xPrecision: $xPrecision, yPrecision: $yPrecision, deviceId: $deviceId, edgeFlags: $edgeFlags, source: $source, flags: $flags)';
+  }
+}
+
+enum _AndroidViewState {
+  waitingForSize,
+  creating,
+  created,
+  disposed,
+}
+
+// Helper for converting PointerEvents into AndroidMotionEvents.
+class _AndroidMotionEventConverter {
+  _AndroidMotionEventConverter();
+
+  final Map<int, AndroidPointerCoords> pointerPositions =
+      <int, AndroidPointerCoords>{};
+  final Map<int, AndroidPointerProperties> pointerProperties =
+      <int, AndroidPointerProperties>{};
+  final Set<int> usedAndroidPointerIds = <int>{};
+
+  late PointTransformer _pointTransformer;
+
+  set pointTransformer(PointTransformer transformer) {
+    assert(transformer != null);
+    _pointTransformer = transformer;
+  }
+
+  int? downTimeMillis;
+
+  void handlePointerDownEvent(PointerDownEvent event) {
+    if (pointerProperties.isEmpty) {
+      downTimeMillis = event.timeStamp.inMilliseconds;
+    }
+    int androidPointerId = 0;
+    while (usedAndroidPointerIds.contains(androidPointerId)) {
+      androidPointerId++;
+    }
+    usedAndroidPointerIds.add(androidPointerId);
+    pointerProperties[event.pointer] = propertiesFor(event, androidPointerId);
+  }
+
+  void updatePointerPositions(PointerEvent event) {
+    final Offset position = _pointTransformer(event.position);
+    pointerPositions[event.pointer] = AndroidPointerCoords(
+      orientation: event.orientation,
+      pressure: event.pressure,
+      size: event.size,
+      toolMajor: event.radiusMajor,
+      toolMinor: event.radiusMinor,
+      touchMajor: event.radiusMajor,
+      touchMinor: event.radiusMinor,
+      x: position.dx,
+      y: position.dy,
+    );
+  }
+
+  void handlePointerUpEvent(PointerUpEvent event) {
+    pointerPositions.remove(event.pointer);
+    usedAndroidPointerIds.remove(pointerProperties[event.pointer]!.id);
+    pointerProperties.remove(event.pointer);
+    if (pointerProperties.isEmpty) {
+      downTimeMillis = null;
+    }
+  }
+
+  void handlePointerCancelEvent(PointerCancelEvent event) {
+    pointerPositions.clear();
+    pointerProperties.clear();
+    usedAndroidPointerIds.clear();
+    downTimeMillis = null;
+  }
+
+  AndroidMotionEvent? toAndroidMotionEvent(PointerEvent event) {
+    final List<int> pointers = pointerPositions.keys.toList();
+    final int pointerIdx = pointers.indexOf(event.pointer);
+    final int numPointers = pointers.length;
+
+    // This value must match the value in engine's FlutterView.java.
+    // This flag indicates whether the original Android pointer events were batched together.
+    const int kPointerDataFlagBatched = 1;
+
+    // Android MotionEvent objects can batch information on multiple pointers.
+    // Flutter breaks these such batched events into multiple PointerEvent objects.
+    // When there are multiple active pointers we accumulate the information for all pointers
+    // as we get PointerEvents, and only send it to the embedded Android view when
+    // we see the last pointer. This way we achieve the same batching as Android.
+    if (event.platformData == kPointerDataFlagBatched ||
+        (isSinglePointerAction(event) && pointerIdx < numPointers - 1)) {
+      return null;
+    }
+
+    final int action;
+    if (event is PointerDownEvent) {
+      action = numPointers == 1
+          ? AndroidViewController.kActionDown
+          : AndroidViewController.pointerAction(
+              pointerIdx, AndroidViewController.kActionPointerDown);
+    } else if (event is PointerUpEvent) {
+      action = numPointers == 1
+          ? AndroidViewController.kActionUp
+          : AndroidViewController.pointerAction(
+              pointerIdx, AndroidViewController.kActionPointerUp);
+    } else if (event is PointerMoveEvent) {
+      action = AndroidViewController.kActionMove;
+    } else if (event is PointerCancelEvent) {
+      action = AndroidViewController.kActionCancel;
+    } else {
+      return null;
+    }
+
+    return AndroidMotionEvent(
+      downTime: downTimeMillis!,
+      eventTime: event.timeStamp.inMilliseconds,
+      action: action,
+      pointerCount: pointerPositions.length,
+      pointerProperties: pointers
+          .map<AndroidPointerProperties>((int i) => pointerProperties[i]!)
+          .toList(),
+      pointerCoords: pointers
+          .map<AndroidPointerCoords>((int i) => pointerPositions[i]!)
+          .toList(),
+      metaState: 0,
+      buttonState: 0,
+      xPrecision: 1.0,
+      yPrecision: 1.0,
+      deviceId: 0,
+      edgeFlags: 0,
+      source: 0,
+      flags: 0,
+      motionEventId: event.embedderId,
+    );
+  }
+
+  AndroidPointerProperties propertiesFor(PointerEvent event, int pointerId) {
+    int toolType = AndroidPointerProperties.kToolTypeUnknown;
+    switch (event.kind) {
+      case PointerDeviceKind.touch:
+        toolType = AndroidPointerProperties.kToolTypeFinger;
+        break;
+      case PointerDeviceKind.mouse:
+        toolType = AndroidPointerProperties.kToolTypeMouse;
+        break;
+      case PointerDeviceKind.stylus:
+        toolType = AndroidPointerProperties.kToolTypeStylus;
+        break;
+      case PointerDeviceKind.invertedStylus:
+        toolType = AndroidPointerProperties.kToolTypeEraser;
+        break;
+      case PointerDeviceKind.unknown:
+        toolType = AndroidPointerProperties.kToolTypeUnknown;
+        break;
+    }
+    return AndroidPointerProperties(id: pointerId, toolType: toolType);
+  }
+
+  bool isSinglePointerAction(PointerEvent event) =>
+      event is! PointerDownEvent && event is! PointerUpEvent;
+}
+
+/// Controls an Android view.
+///
+/// Typically created with [PlatformViewsService.initAndroidView].
+// TODO(bparrishMines): Remove abstract methods that are not required by all subclasses.
+abstract class AndroidViewController extends PlatformViewController {
+  AndroidViewController._({
+    required this.viewId,
+    required String viewType,
+    required TextDirection layoutDirection,
+    dynamic creationParams,
+    MessageCodec<dynamic>? creationParamsCodec,
+    bool waitingForSize = false,
+  })  : assert(viewId != null),
+        assert(viewType != null),
+        assert(layoutDirection != null),
+        assert(creationParams == null || creationParamsCodec != null),
+        _viewType = viewType,
+        _layoutDirection = layoutDirection,
+        _creationParams = creationParams,
+        _creationParamsCodec = creationParamsCodec,
+        _state = waitingForSize
+            ? _AndroidViewState.waitingForSize
+            : _AndroidViewState.creating;
+
+  /// Action code for when a primary pointer touched the screen.
+  ///
+  /// Android's [MotionEvent.ACTION_DOWN](https://developer.android.com/reference/android/view/MotionEvent#ACTION_DOWN)
+  static const int kActionDown = 0;
+
+  /// Action code for when a primary pointer stopped touching the screen.
+  ///
+  /// Android's [MotionEvent.ACTION_UP](https://developer.android.com/reference/android/view/MotionEvent#ACTION_UP)
+  static const int kActionUp = 1;
+
+  /// Action code for when the event only includes information about pointer movement.
+  ///
+  /// Android's [MotionEvent.ACTION_MOVE](https://developer.android.com/reference/android/view/MotionEvent#ACTION_MOVE)
+  static const int kActionMove = 2;
+
+  /// Action code for when a motion event has been canceled.
+  ///
+  /// Android's [MotionEvent.ACTION_CANCEL](https://developer.android.com/reference/android/view/MotionEvent#ACTION_CANCEL)
+  static const int kActionCancel = 3;
+
+  /// Action code for when a secondary pointer touched the screen.
+  ///
+  /// Android's [MotionEvent.ACTION_POINTER_DOWN](https://developer.android.com/reference/android/view/MotionEvent#ACTION_POINTER_DOWN)
+  static const int kActionPointerDown = 5;
+
+  /// Action code for when a secondary pointer stopped touching the screen.
+  ///
+  /// Android's [MotionEvent.ACTION_POINTER_UP](https://developer.android.com/reference/android/view/MotionEvent#ACTION_POINTER_UP)
+  static const int kActionPointerUp = 6;
+
+  /// Android's [View.LAYOUT_DIRECTION_LTR](https://developer.android.com/reference/android/view/View.html#LAYOUT_DIRECTION_LTR) value.
+  static const int kAndroidLayoutDirectionLtr = 0;
+
+  /// Android's [View.LAYOUT_DIRECTION_RTL](https://developer.android.com/reference/android/view/View.html#LAYOUT_DIRECTION_RTL) value.
+  static const int kAndroidLayoutDirectionRtl = 1;
+
+  /// The unique identifier of the Android view controlled by this controller.
+  @override
+  final int viewId;
+
+  final String _viewType;
+
+  // Helps convert PointerEvents to AndroidMotionEvents.
+  final _AndroidMotionEventConverter _motionEventConverter =
+      _AndroidMotionEventConverter();
+
+  TextDirection _layoutDirection;
+
+  _AndroidViewState _state;
+
+  final dynamic _creationParams;
+
+  final MessageCodec<dynamic>? _creationParamsCodec;
+
+  final List<PlatformViewCreatedCallback> _platformViewCreatedCallbacks =
+      <PlatformViewCreatedCallback>[];
+
+  static int _getAndroidDirection(TextDirection direction) {
+    assert(direction != null);
+    switch (direction) {
+      case TextDirection.ltr:
+        return kAndroidLayoutDirectionLtr;
+      case TextDirection.rtl:
+        return kAndroidLayoutDirectionRtl;
+    }
+  }
+
+  /// Creates a masked Android MotionEvent action value for an indexed pointer.
+  static int pointerAction(int pointerId, int action) {
+    return ((pointerId << 8) & 0xff00) | (action & 0xff);
+  }
+
+  Future<void> _sendDisposeMessage();
+  Future<void> _sendCreateMessage();
+
+  /// Creates the Android View.
+  ///
+  /// Throws an [AssertionError] if view was already disposed.
+  Future<void> create() async {
+    assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view');
+
+    await _sendCreateMessage();
+
+    _state = _AndroidViewState.created;
+    for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
+      callback(viewId);
+    }
+  }
+
+  /// Sizes the Android View.
+  ///
+  /// `size` is the view's new size in logical pixel, it must not be null and must
+  /// be bigger than zero.
+  ///
+  /// The first time a size is set triggers the creation of the Android view.
+  Future<void> setSize(Size size);
+
+  /// Returns the texture entry id that the Android view is rendering into.
+  ///
+  /// Returns null if the Android view has not been successfully created, or if it has been
+  /// disposed.
+  int? get textureId;
+
+  /// The unique identifier of the Android view controlled by this controller.
+  @Deprecated(
+    'Call `controller.viewId` instead. '
+    'This feature was deprecated after v1.20.0-2.0.pre.'
+  )
+  int get id => viewId;
+
+  /// Sends an Android [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent)
+  /// to the view.
+  ///
+  /// The Android MotionEvent object is created with [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)).
+  /// See documentation of [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int))
+  /// for description of the parameters.
+  ///
+  /// See [AndroidViewController.dispatchPointerEvent] for sending a
+  /// [PointerEvent].
+  Future<void> sendMotionEvent(AndroidMotionEvent event) async {
+    await SystemChannels.platform_views.invokeMethod<dynamic>(
+      'touch',
+      event._asList(viewId),
+    );
+  }
+
+  /// Converts a given point from the global coordinate system in logical pixels to the local coordinate system for this box.
+  ///
+  /// This is required to convert a [PointerEvent] to an [AndroidMotionEvent].
+  /// It is typically provided by using [RenderBox.globalToLocal].
+  set pointTransformer(PointTransformer transformer) {
+    assert(transformer != null);
+    _motionEventConverter._pointTransformer = transformer;
+  }
+
+  /// Whether the platform view has already been created.
+  bool get isCreated => _state == _AndroidViewState.created;
+
+  /// Adds a callback that will get invoke after the platform view has been
+  /// created.
+  void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
+    assert(listener != null);
+    assert(_state != _AndroidViewState.disposed);
+    _platformViewCreatedCallbacks.add(listener);
+  }
+
+  /// Removes a callback added with [addOnPlatformViewCreatedListener].
+  void removeOnPlatformViewCreatedListener(
+      PlatformViewCreatedCallback listener) {
+    assert(_state != _AndroidViewState.disposed);
+    _platformViewCreatedCallbacks.remove(listener);
+  }
+
+  /// Sets the layout direction for the Android view.
+  Future<void> setLayoutDirection(TextDirection layoutDirection) async {
+    assert(_state != _AndroidViewState.disposed,
+        'trying to set a layout direction for a disposed UIView. View id: $viewId');
+
+    if (layoutDirection == _layoutDirection)
+      return;
+
+    assert(layoutDirection != null);
+    _layoutDirection = layoutDirection;
+
+    // If the view was not yet created we just update _layoutDirection and return, as the new
+    // direction will be used in _create.
+    if (_state == _AndroidViewState.waitingForSize)
+      return;
+
+    await SystemChannels.platform_views
+        .invokeMethod<void>('setDirection', <String, dynamic>{
+      'id': viewId,
+      'direction': _getAndroidDirection(layoutDirection),
+    });
+  }
+
+  /// Converts the [PointerEvent] and sends an Android [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent)
+  /// to the view.
+  ///
+  /// This method can only be used if a [PointTransformer] is provided to
+  /// [AndroidViewController.pointTransformer]. Otherwise, an [AssertionError]
+  /// is thrown. See [AndroidViewController.sendMotionEvent] for sending a
+  /// `MotionEvent` without a [PointTransformer].
+  ///
+  /// The Android MotionEvent object is created with [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)).
+  /// See documentation of [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int))
+  /// for description of the parameters.
+  @override
+  Future<void> dispatchPointerEvent(PointerEvent event) async {
+    if (event is PointerHoverEvent) {
+      return;
+    }
+
+    if (event is PointerDownEvent) {
+      _motionEventConverter.handlePointerDownEvent(event);
+    }
+
+    _motionEventConverter.updatePointerPositions(event);
+
+    final AndroidMotionEvent? androidEvent =
+        _motionEventConverter.toAndroidMotionEvent(event);
+
+    if (event is PointerUpEvent) {
+      _motionEventConverter.handlePointerUpEvent(event);
+    } else if (event is PointerCancelEvent) {
+      _motionEventConverter.handlePointerCancelEvent(event);
+    }
+
+    if (androidEvent != null) {
+      await sendMotionEvent(androidEvent);
+    }
+  }
+
+  /// Clears the focus from the Android View if it is focused.
+  @override
+  Future<void> clearFocus() {
+    if (_state != _AndroidViewState.created) {
+      return Future<void>.value();
+    }
+    return SystemChannels.platform_views.invokeMethod<void>('clearFocus', viewId);
+  }
+
+  /// Disposes the Android view.
+  ///
+  /// The [AndroidViewController] object is unusable after calling this.
+  /// The identifier of the platform view cannot be reused after the view is
+  /// disposed.
+  @override
+  Future<void> dispose() async {
+    if (_state == _AndroidViewState.creating || _state == _AndroidViewState.created)
+      await _sendDisposeMessage();
+    _platformViewCreatedCallbacks.clear();
+    _state = _AndroidViewState.disposed;
+    PlatformViewsService._instance._focusCallbacks.remove(viewId);
+  }
+}
+
+/// Controls an Android view by rendering to an [AndroidViewSurface].
+///
+/// Typically created with [PlatformViewsService.initAndroidView].
+class SurfaceAndroidViewController extends AndroidViewController {
+  SurfaceAndroidViewController._({
+    required int viewId,
+    required String viewType,
+    required TextDirection layoutDirection,
+    dynamic creationParams,
+    MessageCodec<dynamic>? creationParamsCodec,
+  }) : super._(
+            viewId: viewId,
+            viewType: viewType,
+            layoutDirection: layoutDirection,
+            creationParams: creationParams,
+            creationParamsCodec: creationParamsCodec);
+
+  @override
+  Future<void> _sendCreateMessage() {
+    final Map<String, dynamic> args = <String, dynamic>{
+      'id': viewId,
+      'viewType': _viewType,
+      'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
+      'hybrid': true,
+    };
+    if (_creationParams != null) {
+      final ByteData paramsByteData =
+          _creationParamsCodec!.encodeMessage(_creationParams)!;
+      args['params'] = Uint8List.view(
+        paramsByteData.buffer,
+        0,
+        paramsByteData.lengthInBytes,
+      );
+    }
+    return SystemChannels.platform_views.invokeMethod<void>('create', args);
+  }
+
+  @override
+  int get textureId {
+    throw UnimplementedError('Not supported for $SurfaceAndroidViewController.');
+  }
+
+  @override
+  Future<void> _sendDisposeMessage() {
+    return SystemChannels.platform_views
+        .invokeMethod<void>('dispose', <String, dynamic>{
+      'id': viewId,
+      'hybrid': true,
+    });
+  }
+
+  @override
+  Future<void> setSize(Size size) {
+    throw UnimplementedError('Not supported for $SurfaceAndroidViewController.');
+  }
+}
+
+/// Controls an Android view that is rendered to a texture.
+///
+/// This is typically used by [AndroidView] to display an Android View in a
+/// [VirtualDisplay](https://developer.android.com/reference/android/hardware/display/VirtualDisplay).
+///
+/// Typically created with [PlatformViewsService.initAndroidView].
+class TextureAndroidViewController extends AndroidViewController {
+  TextureAndroidViewController._({
+    required int viewId,
+    required String viewType,
+    required TextDirection layoutDirection,
+    dynamic creationParams,
+    MessageCodec<dynamic>? creationParamsCodec,
+  }) : super._(
+          viewId: viewId,
+          viewType: viewType,
+          layoutDirection: layoutDirection,
+          creationParams: creationParams,
+          creationParamsCodec: creationParamsCodec,
+          waitingForSize: true,
+        );
+
+  /// The texture entry id into which the Android view is rendered.
+  int? _textureId;
+
+  /// Returns the texture entry id that the Android view is rendering into.
+  ///
+  /// Returns null if the Android view has not been successfully created, or if it has been
+  /// disposed.
+  @override
+  int? get textureId => _textureId;
+
+  late Size _size;
+
+  @override
+  Future<void> setSize(Size size) async {
+    assert(_state != _AndroidViewState.disposed,
+        'trying to size a disposed Android View. View id: $viewId');
+
+    assert(size != null);
+    assert(!size.isEmpty);
+
+    if (_state == _AndroidViewState.waitingForSize) {
+      _size = size;
+      return create();
+    }
+
+    await SystemChannels.platform_views.invokeMethod<void>('resize', <String, dynamic>{
+      'id': viewId,
+      'width': size.width,
+      'height': size.height,
+    });
+  }
+
+  /// Creates the Android View.
+  ///
+  /// This should not be called before [AndroidViewController.setSize].
+  ///
+  /// Throws an [AssertionError] if view was already disposed.
+  @override
+  Future<void> create() => super.create();
+
+  @override
+  Future<void> _sendCreateMessage() async {
+    assert(!_size.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.');
+
+    final Map<String, dynamic> args = <String, dynamic>{
+      'id': viewId,
+      'viewType': _viewType,
+      'width': _size.width,
+      'height': _size.height,
+      'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
+    };
+    if (_creationParams != null) {
+      final ByteData paramsByteData = _creationParamsCodec!.encodeMessage(_creationParams)!;
+      args['params'] = Uint8List.view(
+        paramsByteData.buffer,
+        0,
+        paramsByteData.lengthInBytes,
+      );
+    }
+    _textureId = await SystemChannels.platform_views.invokeMethod<int>('create', args);
+  }
+
+  @override
+  Future<void> _sendDisposeMessage() {
+    return SystemChannels
+        .platform_views.invokeMethod<void>('dispose', <String, dynamic>{
+      'id': viewId,
+      'hybrid': false,
+    });
+  }
+}
+
+/// Controls an iOS UIView.
+///
+/// Typically created with [PlatformViewsService.initUiKitView].
+class UiKitViewController {
+  UiKitViewController._(
+    this.id,
+    TextDirection layoutDirection,
+  ) : assert(id != null),
+      assert(layoutDirection != null),
+      _layoutDirection = layoutDirection;
+
+
+  /// The unique identifier of the iOS view controlled by this controller.
+  ///
+  /// This identifier is typically generated by
+  /// [PlatformViewsRegistry.getNextPlatformViewId].
+  final int id;
+
+  bool _debugDisposed = false;
+
+  TextDirection _layoutDirection;
+
+  /// Sets the layout direction for the iOS UIView.
+  Future<void> setLayoutDirection(TextDirection layoutDirection) async {
+    assert(!_debugDisposed, 'trying to set a layout direction for a disposed iOS UIView. View id: $id');
+
+    if (layoutDirection == _layoutDirection)
+      return;
+
+    assert(layoutDirection != null);
+    _layoutDirection = layoutDirection;
+
+    // TODO(amirh): invoke the iOS platform views channel direction method once available.
+  }
+
+  /// Accept an active gesture.
+  ///
+  /// When a touch sequence is happening on the embedded UIView all touch events are delayed.
+  /// Calling this method releases the delayed events to the embedded UIView and makes it consume
+  /// any following touch events for the pointers involved in the active gesture.
+  Future<void> acceptGesture() {
+    final Map<String, dynamic> args = <String, dynamic>{
+      'id': id,
+    };
+    return SystemChannels.platform_views.invokeMethod('acceptGesture', args);
+  }
+
+  /// Rejects an active gesture.
+  ///
+  /// When a touch sequence is happening on the embedded UIView all touch events are delayed.
+  /// Calling this method drops the buffered touch events and prevents any future touch events for
+  /// the pointers that are part of the active touch sequence from arriving to the embedded view.
+  Future<void> rejectGesture() {
+    final Map<String, dynamic> args = <String, dynamic>{
+      'id': id,
+    };
+    return SystemChannels.platform_views.invokeMethod('rejectGesture', args);
+  }
+
+  /// Disposes the view.
+  ///
+  /// The [UiKitViewController] object is unusable after calling this.
+  /// The `id` of the platform view cannot be reused after the view is
+  /// disposed.
+  Future<void> dispose() async {
+    _debugDisposed = true;
+    await SystemChannels.platform_views.invokeMethod<void>('dispose', id);
+  }
+}
+
+/// An interface for a controlling a single platform view.
+///
+/// Used by [PlatformViewSurface] to interface with the platform view it embeds.
+abstract class PlatformViewController {
+
+  /// The viewId associated with this controller.
+  ///
+  /// The viewId should always be unique and non-negative. And it must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [PlatformViewsRegistry], which is a helper for managing platform view ids.
+  int get viewId;
+
+  /// Dispatches the `event` to the platform view.
+  Future<void> dispatchPointerEvent(PointerEvent event);
+
+  /// Disposes the platform view.
+  ///
+  /// The [PlatformViewController] is unusable after calling dispose.
+  Future<void> dispose();
+
+  /// Clears the view's focus on the platform side.
+  Future<void> clearFocus();
+}
diff --git a/lib/src/services/raw_keyboard.dart b/lib/src/services/raw_keyboard.dart
new file mode 100644
index 0000000..cbf50fc
--- /dev/null
+++ b/lib/src/services/raw_keyboard.dart
@@ -0,0 +1,765 @@
+// Copyright 2014 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.
+
+import 'dart:io';
+import 'package:flute/ui.dart';
+
+import 'package:flute/foundation.dart';
+
+import 'keyboard_key.dart';
+import 'raw_keyboard_android.dart';
+import 'raw_keyboard_fuchsia.dart';
+import 'raw_keyboard_ios.dart';
+import 'raw_keyboard_linux.dart';
+import 'raw_keyboard_macos.dart';
+import 'raw_keyboard_web.dart';
+import 'raw_keyboard_windows.dart';
+import 'system_channels.dart';
+
+/// An enum describing the side of the keyboard that a key is on, to allow
+/// discrimination between which key is pressed (e.g. the left or right SHIFT
+/// key).
+///
+/// See also:
+///
+///  * [RawKeyEventData.isModifierPressed], which accepts this enum as an
+///    argument.
+enum KeyboardSide {
+  /// Matches if either the left, right or both versions of the key are pressed.
+  any,
+
+  /// Matches the left version of the key.
+  left,
+
+  /// Matches the right version of the key.
+  right,
+
+  /// Matches the left and right version of the key pressed simultaneously.
+  all,
+}
+
+/// An enum describing the type of modifier key that is being pressed.
+///
+/// See also:
+///
+///  * [RawKeyEventData.isModifierPressed], which accepts this enum as an
+///    argument.
+enum ModifierKey {
+  /// The CTRL modifier key.
+  ///
+  /// Typically, there are two of these.
+  controlModifier,
+
+  /// The SHIFT modifier key.
+  ///
+  /// Typically, there are two of these.
+  shiftModifier,
+
+  /// The ALT modifier key.
+  ///
+  /// Typically, there are two of these.
+  altModifier,
+
+  /// The META modifier key.
+  ///
+  /// Typically, there are two of these. This is, for example, the Windows key
+  /// on Windows (⊞), the Command (⌘) key on macOS and iOS, and the Search (🔍)
+  /// key on Android.
+  metaModifier,
+
+  /// The CAPS LOCK modifier key.
+  ///
+  /// Typically, there is one of these. Only shown as "pressed" when the caps
+  /// lock is on, so on a key up when the mode is turned on, on each key press
+  /// when it's enabled, and on a key down when it is turned off.
+  capsLockModifier,
+
+  /// The NUM LOCK modifier key.
+  ///
+  /// Typically, there is one of these. Only shown as "pressed" when the num
+  /// lock is on, so on a key up when the mode is turned on, on each key press
+  /// when it's enabled, and on a key down when it is turned off.
+  numLockModifier,
+
+  /// The SCROLL LOCK modifier key.
+  ///
+  /// Typically, there is one of these.  Only shown as "pressed" when the scroll
+  /// lock is on, so on a key up when the mode is turned on, on each key press
+  /// when it's enabled, and on a key down when it is turned off.
+  scrollLockModifier,
+
+  /// The FUNCTION (Fn) modifier key.
+  ///
+  /// Typically, there is one of these.
+  functionModifier,
+
+  /// The SYMBOL modifier key.
+  ///
+  /// Typically, there is one of these.
+  symbolModifier,
+}
+
+/// Base class for platform-specific key event data.
+///
+/// This base class exists to have a common type to use for each of the
+/// target platform's key event data structures.
+///
+/// See also:
+///
+///  * [RawKeyEventDataAndroid], a specialization for Android.
+///  * [RawKeyEventDataFuchsia], a specialization for Fuchsia.
+///  * [RawKeyDownEvent] and [RawKeyUpEvent], the classes that hold the
+///    reference to [RawKeyEventData] subclasses.
+///  * [RawKeyboard], which uses these interfaces to expose key data.
+@immutable
+abstract class RawKeyEventData {
+  /// Abstract const constructor.
+  ///
+  /// This constructor enables subclasses to provide const constructors so that
+  /// they can be used in const expressions.
+  const RawKeyEventData();
+
+  /// Returns true if the given [ModifierKey] was pressed at the time of this
+  /// event.
+  ///
+  /// If [side] is specified, then this restricts its check to the specified
+  /// side of the keyboard. Defaults to checking for the key being down on
+  /// either side of the keyboard. If there is only one instance of the key on
+  /// the keyboard, then [side] is ignored.
+  bool isModifierPressed(ModifierKey key, { KeyboardSide side = KeyboardSide.any });
+
+  /// Returns a [KeyboardSide] enum value that describes which side or sides of
+  /// the given keyboard modifier key were pressed at the time of this event.
+  ///
+  /// If the modifier key wasn't pressed at the time of this event, returns
+  /// null. If the given key only appears in one place on the keyboard, returns
+  /// [KeyboardSide.all] if pressed. Never returns [KeyboardSide.any], because
+  /// that doesn't make sense in this context.
+  KeyboardSide? getModifierSide(ModifierKey key);
+
+  /// Returns true if a CTRL modifier key was pressed at the time of this event,
+  /// regardless of which side of the keyboard it is on.
+  ///
+  /// Use [isModifierPressed] if you need to know which control key was pressed.
+  bool get isControlPressed => isModifierPressed(ModifierKey.controlModifier, side: KeyboardSide.any);
+
+  /// Returns true if a SHIFT modifier key was pressed at the time of this
+  /// event, regardless of which side of the keyboard it is on.
+  ///
+  /// Use [isModifierPressed] if you need to know which shift key was pressed.
+  bool get isShiftPressed => isModifierPressed(ModifierKey.shiftModifier, side: KeyboardSide.any);
+
+  /// Returns true if a ALT modifier key was pressed at the time of this event,
+  /// regardless of which side of the keyboard it is on.
+  ///
+  /// Use [isModifierPressed] if you need to know which alt key was pressed.
+  bool get isAltPressed => isModifierPressed(ModifierKey.altModifier, side: KeyboardSide.any);
+
+  /// Returns true if a META modifier key was pressed at the time of this event,
+  /// regardless of which side of the keyboard it is on.
+  ///
+  /// Use [isModifierPressed] if you need to know which meta key was pressed.
+  bool get isMetaPressed => isModifierPressed(ModifierKey.metaModifier, side: KeyboardSide.any);
+
+  /// Returns a map of modifier keys that were pressed at the time of this
+  /// event, and the keyboard side or sides that the key was on.
+  Map<ModifierKey, KeyboardSide> get modifiersPressed {
+    final Map<ModifierKey, KeyboardSide> result = <ModifierKey, KeyboardSide>{};
+    for (final ModifierKey key in ModifierKey.values) {
+      if (isModifierPressed(key)) {
+        final KeyboardSide? side = getModifierSide(key);
+        if (side != null) {
+          result[key] = side;
+        }
+        assert((){
+          if (side == null) {
+            debugPrint('Raw key data is returning inconsistent information for '
+                'pressed modifiers. isModifierPressed returns true for $key '
+                'being pressed, but when getModifierSide is called, it says '
+                'that no modifiers are pressed.');
+            if (this is RawKeyEventDataAndroid) {
+              debugPrint('Android raw key metaState: ${(this as RawKeyEventDataAndroid).metaState}');
+            }
+          }
+          return true;
+        }());
+      }
+    }
+    return result;
+  }
+
+  /// Returns an object representing the physical location of this key on a
+  /// QWERTY keyboard.
+  ///
+  /// {@macro flutter.services.RawKeyEvent.physicalKey}
+  ///
+  /// See also:
+  ///
+  ///  * [logicalKey] for the non-location-specific key generated by this event.
+  ///  * [RawKeyEvent.physicalKey], where this value is available on the event.
+  PhysicalKeyboardKey get physicalKey;
+
+  /// Returns an object representing the logical key that was pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEvent.logicalKey}
+  ///
+  /// See also:
+  ///
+  ///  * [physicalKey] for the location-specific key generated by this event.
+  ///  * [RawKeyEvent.logicalKey], where this value is available on the event.
+  LogicalKeyboardKey get logicalKey;
+
+  /// Returns the Unicode string representing the label on this key.
+  ///
+  /// This value is an empty string if there's no key label data for a key.
+  ///
+  /// {@template flutter.services.RawKeyEventData.keyLabel}
+  /// Do not use the [keyLabel] to compose a text string: it will be missing
+  /// special processing for Unicode strings for combining characters and other
+  /// special characters, and the effects of modifiers.
+  ///
+  /// If you are looking for the character produced by a key event, use
+  /// [RawKeyEvent.character] instead.
+  ///
+  /// If you are composing text strings, use the [TextField] or
+  /// [CupertinoTextField] widgets, since those automatically handle many of the
+  /// complexities of managing keyboard input, like showing a soft keyboard or
+  /// interacting with an input method editor (IME).
+  /// {@endtemplate}
+  String get keyLabel;
+}
+
+/// Defines the interface for raw key events.
+///
+/// Raw key events pass through as much information as possible from the
+/// underlying platform's key events, which allows them to provide a high level
+/// of fidelity but a low level of portability.
+///
+/// The event also provides an abstraction for the [physicalKey] and the
+/// [logicalKey], describing the physical location of the key, and the logical
+/// meaning of the key, respectively. These are more portable representations of
+/// the key events, and should produce the same results regardless of platform.
+///
+/// See also:
+///
+///  * [LogicalKeyboardKey], an object that describes the logical meaning of a
+///    key.
+///  * [PhysicalKeyboardKey], an object that describes the physical location of
+///    a key.
+///  * [RawKeyDownEvent], a specialization for events representing the user
+///    pressing a key.
+///  * [RawKeyUpEvent], a specialization for events representing the user
+///    releasing a key.
+///  * [RawKeyboard], which uses this interface to expose key data.
+///  * [RawKeyboardListener], a widget that listens for raw key events.
+@immutable
+abstract class RawKeyEvent with Diagnosticable {
+  /// Initializes fields for subclasses, and provides a const constructor for
+  /// const subclasses.
+  const RawKeyEvent({
+    required this.data,
+    this.character,
+  });
+
+  /// Creates a concrete [RawKeyEvent] class from a message in the form received
+  /// on the [SystemChannels.keyEvent] channel.
+  factory RawKeyEvent.fromMessage(Map<String, dynamic> message) {
+    final RawKeyEventData data;
+    String? character;
+
+    final String keymap = message['keymap'] as String;
+    switch (keymap) {
+      case 'android':
+        data = RawKeyEventDataAndroid(
+          flags: message['flags'] as int? ?? 0,
+          codePoint: message['codePoint'] as int? ?? 0,
+          keyCode: message['keyCode'] as int? ?? 0,
+          plainCodePoint: message['plainCodePoint'] as int? ?? 0,
+          scanCode: message['scanCode'] as int? ?? 0,
+          metaState: message['metaState'] as int? ?? 0,
+          eventSource: message['source'] as int? ?? 0,
+          vendorId: message['vendorId'] as int? ?? 0,
+          productId: message['productId'] as int? ?? 0,
+          deviceId: message['deviceId'] as int? ?? 0,
+          repeatCount: message['repeatCount'] as int? ?? 0,
+        );
+        if (message.containsKey('character')) {
+          character = message['character'] as String?;
+        }
+        break;
+      case 'fuchsia':
+        final int codePoint = message['codePoint'] as int? ?? 0;
+        data = RawKeyEventDataFuchsia(
+          hidUsage: message['hidUsage'] as int? ?? 0,
+          codePoint: codePoint,
+          modifiers: message['modifiers'] as int? ?? 0,
+        );
+        if (codePoint != 0) {
+          character = String.fromCharCode(codePoint);
+        }
+        break;
+      case 'macos':
+        data = RawKeyEventDataMacOs(
+            characters: message['characters'] as String? ?? '',
+            charactersIgnoringModifiers: message['charactersIgnoringModifiers'] as String? ?? '',
+            keyCode: message['keyCode'] as int? ?? 0,
+            modifiers: message['modifiers'] as int? ?? 0);
+        character = message['characters'] as String?;
+        break;
+      case 'ios':
+        data = RawKeyEventDataIos(
+            characters: message['characters'] as String? ?? '',
+            charactersIgnoringModifiers: message['charactersIgnoringModifiers'] as String? ?? '',
+            keyCode: message['keyCode'] as int? ?? 0,
+            modifiers: message['modifiers'] as int? ?? 0);
+        break;
+      case 'linux':
+        final int unicodeScalarValues = message['unicodeScalarValues'] as int? ?? 0;
+        data = RawKeyEventDataLinux(
+            keyHelper: KeyHelper(message['toolkit'] as String? ?? ''),
+            unicodeScalarValues: unicodeScalarValues,
+            keyCode: message['keyCode'] as int? ?? 0,
+            scanCode: message['scanCode'] as int? ?? 0,
+            modifiers: message['modifiers'] as int? ?? 0,
+            isDown: message['type'] == 'keydown');
+        if (unicodeScalarValues != 0) {
+          character = String.fromCharCode(unicodeScalarValues);
+        }
+        break;
+      case 'web':
+        data = RawKeyEventDataWeb(
+          code: message['code'] as String? ?? '',
+          key: message['key'] as String? ?? '',
+          metaState: message['metaState'] as int? ?? 0,
+        );
+        character = message['key'] as String?;
+        break;
+      case 'windows':
+        final int characterCodePoint = message['characterCodePoint'] as int? ?? 0;
+        data = RawKeyEventDataWindows(
+          keyCode: message['keyCode'] as int? ?? 0,
+          scanCode: message['scanCode'] as int? ?? 0,
+          characterCodePoint: characterCodePoint,
+          modifiers: message['modifiers'] as int? ?? 0,
+        );
+        if (characterCodePoint != 0) {
+          character = String.fromCharCode(characterCodePoint);
+        }
+        break;
+      default:
+        /// This exception would only be hit on platforms that haven't yet
+        /// implemented raw key events, but will only be triggered if the
+        /// engine for those platforms sends raw key event messages in the
+        /// first place.
+        throw FlutterError('Unknown keymap for key events: $keymap');
+    }
+
+    final String type = message['type'] as String;
+    switch (type) {
+      case 'keydown':
+        return RawKeyDownEvent(data: data, character: character);
+      case 'keyup':
+        return RawKeyUpEvent(data: data);
+      default:
+        throw FlutterError('Unknown key event type: $type');
+    }
+  }
+
+  /// Returns true if the given [KeyboardKey] is pressed.
+  bool isKeyPressed(LogicalKeyboardKey key) => RawKeyboard.instance.keysPressed.contains(key);
+
+  /// Returns true if a CTRL modifier key is pressed, regardless of which side
+  /// of the keyboard it is on.
+  ///
+  /// Use [isKeyPressed] if you need to know which control key was pressed.
+  bool get isControlPressed {
+    return isKeyPressed(LogicalKeyboardKey.controlLeft) || isKeyPressed(LogicalKeyboardKey.controlRight);
+  }
+
+  /// Returns true if a SHIFT modifier key is pressed, regardless of which side
+  /// of the keyboard it is on.
+  ///
+  /// Use [isKeyPressed] if you need to know which shift key was pressed.
+  bool get isShiftPressed {
+    return isKeyPressed(LogicalKeyboardKey.shiftLeft) || isKeyPressed(LogicalKeyboardKey.shiftRight);
+  }
+
+  /// Returns true if a ALT modifier key is pressed, regardless of which side
+  /// of the keyboard it is on.
+  ///
+  /// Note that the ALTGR key that appears on some keyboards is considered to be
+  /// the same as [LogicalKeyboardKey.altRight] on some platforms (notably
+  /// Android). On platforms that can distinguish between `altRight` and
+  /// `altGr`, a press of `altGr` will not return true here, and will need to be
+  /// tested for separately.
+  ///
+  /// Use [isKeyPressed] if you need to know which alt key was pressed.
+  bool get isAltPressed {
+    return isKeyPressed(LogicalKeyboardKey.altLeft) || isKeyPressed(LogicalKeyboardKey.altRight);
+  }
+
+  /// Returns true if a META modifier key is pressed, regardless of which side
+  /// of the keyboard it is on.
+  ///
+  /// Use [isKeyPressed] if you need to know which meta key was pressed.
+  bool get isMetaPressed {
+    return isKeyPressed(LogicalKeyboardKey.metaLeft) || isKeyPressed(LogicalKeyboardKey.metaRight);
+  }
+
+  /// Returns an object representing the physical location of this key.
+  ///
+  /// {@template flutter.services.RawKeyEvent.physicalKey}
+  /// The [PhysicalKeyboardKey] ignores the key map, modifier keys (like SHIFT),
+  /// and the label on the key. It describes the location of the key as if it
+  /// were on a QWERTY keyboard regardless of the keyboard mapping in effect.
+  ///
+  /// [PhysicalKeyboardKey]s are used to describe and test for keys in a
+  /// particular location.
+  ///
+  /// For instance, if you wanted to make a game where the key to the right of
+  /// the CAPS LOCK key made the player move left, you would be comparing the
+  /// result of this `physicalKey` with [PhysicalKeyboardKey.keyA], since that
+  /// is the key next to the CAPS LOCK key on a QWERTY keyboard. This would
+  /// return the same thing even on a French keyboard where the key next to the
+  /// CAPS LOCK produces a "Q" when pressed.
+  ///
+  /// If you want to make your app respond to a key with a particular character
+  /// on it regardless of location of the key, use [RawKeyEvent.logicalKey] instead.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [logicalKey] for the non-location specific key generated by this event.
+  ///  * [character] for the character generated by this keypress (if any).
+  PhysicalKeyboardKey get physicalKey => data.physicalKey;
+
+  /// Returns an object representing the logical key that was pressed.
+  ///
+  /// {@template flutter.services.RawKeyEvent.logicalKey}
+  /// This method takes into account the key map and modifier keys (like SHIFT)
+  /// to determine which logical key to return.
+  ///
+  /// If you are looking for the character produced by a key event, use
+  /// [RawKeyEvent.character] instead.
+  ///
+  /// If you are collecting text strings, use the [TextField] or
+  /// [CupertinoTextField] widgets, since those automatically handle many of the
+  /// complexities of managing keyboard input, like showing a soft keyboard or
+  /// interacting with an input method editor (IME).
+  /// {@endtemplate}
+  LogicalKeyboardKey get logicalKey => data.logicalKey;
+
+  /// Returns the Unicode character (grapheme cluster) completed by this
+  /// keystroke, if any.
+  ///
+  /// This will only return a character if this keystroke, combined with any
+  /// preceding keystroke(s), generated a character, and only on a "key down"
+  /// event. It will return null if no character has been generated by the
+  /// keystroke (e.g. a "dead" or "combining" key), or if the corresponding key
+  /// is a key without a visual representation, such as a modifier key or a
+  /// control key.
+  ///
+  /// This can return multiple Unicode code points, since some characters (more
+  /// accurately referred to as grapheme clusters) are made up of more than one
+  /// code point.
+  ///
+  /// The `character` doesn't take into account edits by an input method editor
+  /// (IME), or manage the visibility of the soft keyboard on touch devices. For
+  /// composing text, use the [TextField] or [CupertinoTextField] widgets, since
+  /// those automatically handle many of the complexities of managing keyboard
+  /// input.
+  final String? character;
+
+  /// Platform-specific information about the key event.
+  final RawKeyEventData data;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<LogicalKeyboardKey>('logicalKey', logicalKey));
+    properties.add(DiagnosticsProperty<PhysicalKeyboardKey>('physicalKey', physicalKey));
+  }
+}
+
+/// The user has pressed a key on the keyboard.
+///
+/// See also:
+///
+///  * [RawKeyboard], which uses this interface to expose key data.
+class RawKeyDownEvent extends RawKeyEvent {
+  /// Creates a key event that represents the user pressing a key.
+  const RawKeyDownEvent({
+    required RawKeyEventData data,
+    String? character,
+  }) : super(data: data, character: character);
+}
+
+/// The user has released a key on the keyboard.
+///
+/// See also:
+///
+///  * [RawKeyboard], which uses this interface to expose key data.
+class RawKeyUpEvent extends RawKeyEvent {
+  /// Creates a key event that represents the user releasing a key.
+  const RawKeyUpEvent({
+    required RawKeyEventData data,
+    String? character,
+  }) : super(data: data, character: character);
+}
+
+/// A callback type used by [RawKeyboard.keyEventHandler] to send key events to
+/// a handler that can determine if the key has been handled or not.
+///
+/// The handler should return true if the key has been handled, and false if the
+/// key was not handled.  It must not return null.
+typedef RawKeyEventHandler = bool Function(RawKeyEvent event);
+
+/// An interface for listening to raw key events.
+///
+/// Raw key events pass through as much information as possible from the
+/// underlying platform's key events, which makes them provide a high level of
+/// fidelity but a low level of portability.
+///
+/// A [RawKeyboard] is useful for listening to raw key events and hardware
+/// buttons that are represented as keys. Typically used by games and other apps
+/// that use keyboards for purposes other than text entry.
+///
+/// These key events are typically only key events generated by a hardware
+/// keyboard, and not those from software keyboards or input method editors.
+///
+/// See also:
+///
+///  * [RawKeyDownEvent] and [RawKeyUpEvent], the classes used to describe
+///    specific raw key events.
+///  * [RawKeyboardListener], a widget that listens for raw key events.
+///  * [SystemChannels.keyEvent], the low-level channel used for receiving
+///    events from the system.
+class RawKeyboard {
+  RawKeyboard._() {
+    SystemChannels.keyEvent.setMessageHandler(_handleKeyEvent);
+  }
+
+  /// The shared instance of [RawKeyboard].
+  static final RawKeyboard instance = RawKeyboard._();
+
+  final List<ValueChanged<RawKeyEvent>> _listeners = <ValueChanged<RawKeyEvent>>[];
+
+  /// Register a listener that is called every time the user presses or releases
+  /// a hardware keyboard key.
+  ///
+  /// Since the listeners have no way to indicate what they did with the event,
+  /// listeners are assumed to not handle the key event. These events will also
+  /// be distributed to other listeners, and to the [keyEventHandler].
+  ///
+  /// Most applications prefer to use the focus system (see [Focus] and
+  /// [FocusManager]) to receive key events to the focused control instead of
+  /// this kind of passive listener.
+  ///
+  /// Listeners can be removed with [removeListener].
+  void addListener(ValueChanged<RawKeyEvent> listener) {
+    _listeners.add(listener);
+  }
+
+  /// Stop calling the given listener every time the user presses or releases a
+  /// hardware keyboard key.
+  ///
+  /// Listeners can be added with [addListener].
+  void removeListener(ValueChanged<RawKeyEvent> listener) {
+    _listeners.remove(listener);
+  }
+
+  /// A handler for hardware keyboard events that will stop propagation if the
+  /// handler returns true.
+  ///
+  /// Key events on the platform are given to Flutter to be handled by the
+  /// engine. If they are not handled, then the platform will continue to
+  /// distribute the keys (i.e. propagate them) to other (possibly non-Flutter)
+  /// components in the application. The return value from this handler tells
+  /// the platform to either stop propagation (by returning true: "event
+  /// handled"), or pass the event on to other controls (false: "event not
+  /// handled").
+  ///
+  /// This handler is normally set by the [FocusManager] so that it can control
+  /// the key event propagation to focused widgets.
+  ///
+  /// Most applications can use the focus system (see [Focus] and
+  /// [FocusManager]) to receive key events. If you are not using the
+  /// [FocusManager] to manage focus, then to be able to stop propagation of the
+  /// event by indicating that the event was handled, set this attribute to a
+  /// [RawKeyEventHandler]. Otherwise, key events will be assumed to not have
+  /// been handled by Flutter, and will also be sent to other (possibly
+  /// non-Flutter) controls in the application.
+  ///
+  /// See also:
+  ///
+  ///  * [Focus.onKey], a [Focus] callback attribute that will be given key
+  ///    events distributed by the [FocusManager] based on the current primary
+  ///    focus.
+  ///  * [addListener], to add passive key event listeners that do not stop event
+  ///    propagation.
+  RawKeyEventHandler? keyEventHandler;
+
+  Future<dynamic> _handleKeyEvent(dynamic message) async {
+    final RawKeyEvent event = RawKeyEvent.fromMessage(message as Map<String, dynamic>);
+    if (event.data is RawKeyEventDataMacOs && event.logicalKey == LogicalKeyboardKey.fn) {
+      // On macOS laptop keyboards, the fn key is used to generate home/end and
+      // f1-f12, but it ALSO generates a separate down/up event for the fn key
+      // itself. Other platforms hide the fn key, and just produce the key that
+      // it is combined with, so to keep it possible to write cross platform
+      // code that looks at which keys are pressed, the fn key is ignored on
+      // macOS.
+      return;
+    }
+    if (event is RawKeyDownEvent) {
+      _keysPressed[event.physicalKey] = event.logicalKey;
+    }
+    if (event is RawKeyUpEvent) {
+      // Use the physical key in the key up event to find the physical key from
+      // the corresponding key down event and remove it, even if the logical
+      // keys don't match.
+      _keysPressed.remove(event.physicalKey);
+    }
+    // Make sure that the modifiers reflect reality, in case a modifier key was
+    // pressed/released while the app didn't have focus.
+    _synchronizeModifiers(event);
+    assert(event is! RawKeyDownEvent || _keysPressed.isNotEmpty,
+        'Attempted to send a key down event when no keys are in keysPressed. '
+        "This state can occur if the key event being sent doesn't properly "
+        'set its modifier flags. This was the event: $event and its data: '
+        '${event.data}');
+    // Send the event to passive listeners.
+    for (final ValueChanged<RawKeyEvent> listener in List<ValueChanged<RawKeyEvent>>.from(_listeners)) {
+      if (_listeners.contains(listener)) {
+        listener(event);
+      }
+    }
+
+    // Send the key event to the keyEventHandler, then send the appropriate
+    // response to the platform so that it can resolve the event's handling.
+    // Defaults to false if keyEventHandler is null.
+    final bool handled = keyEventHandler != null && keyEventHandler!(event);
+    assert(handled != null, 'keyEventHandler returned null, which is not allowed');
+    return <String, dynamic>{ 'handled': handled };
+  }
+
+  static final Map<_ModifierSidePair, Set<PhysicalKeyboardKey>> _modifierKeyMap = <_ModifierSidePair, Set<PhysicalKeyboardKey>>{
+    const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.left): <PhysicalKeyboardKey>{PhysicalKeyboardKey.altLeft},
+    const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.right): <PhysicalKeyboardKey>{PhysicalKeyboardKey.altRight},
+    const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.altLeft, PhysicalKeyboardKey.altRight},
+    const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.any): <PhysicalKeyboardKey>{PhysicalKeyboardKey.altLeft},
+    const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.left): <PhysicalKeyboardKey>{PhysicalKeyboardKey.shiftLeft},
+    const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.right): <PhysicalKeyboardKey>{PhysicalKeyboardKey.shiftRight},
+    const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.shiftLeft, PhysicalKeyboardKey.shiftRight},
+    const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.any): <PhysicalKeyboardKey>{PhysicalKeyboardKey.shiftLeft},
+    const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.left): <PhysicalKeyboardKey>{PhysicalKeyboardKey.controlLeft},
+    const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.right): <PhysicalKeyboardKey>{PhysicalKeyboardKey.controlRight},
+    const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.controlLeft, PhysicalKeyboardKey.controlRight},
+    const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.any): <PhysicalKeyboardKey>{PhysicalKeyboardKey.controlLeft},
+    const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.left): <PhysicalKeyboardKey>{PhysicalKeyboardKey.metaLeft},
+    const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.right): <PhysicalKeyboardKey>{PhysicalKeyboardKey.metaRight},
+    const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.metaLeft, PhysicalKeyboardKey.metaRight},
+    const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.any): <PhysicalKeyboardKey>{PhysicalKeyboardKey.metaLeft},
+    const _ModifierSidePair(ModifierKey.capsLockModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.capsLock},
+    const _ModifierSidePair(ModifierKey.numLockModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock},
+    const _ModifierSidePair(ModifierKey.scrollLockModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.scrollLock},
+    const _ModifierSidePair(ModifierKey.functionModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.fn},
+    // The symbolModifier doesn't have a key representation on any of the
+    // platforms, so don't map it here.
+  };
+
+  // The map of all modifier keys except Fn, since that is treated differently
+  // on some platforms.
+  static final Map<PhysicalKeyboardKey, LogicalKeyboardKey> _allModifiersExceptFn = <PhysicalKeyboardKey, LogicalKeyboardKey>{
+    PhysicalKeyboardKey.altLeft: LogicalKeyboardKey.altLeft,
+    PhysicalKeyboardKey.altRight: LogicalKeyboardKey.altRight,
+    PhysicalKeyboardKey.shiftLeft: LogicalKeyboardKey.shiftLeft,
+    PhysicalKeyboardKey.shiftRight: LogicalKeyboardKey.shiftRight,
+    PhysicalKeyboardKey.controlLeft: LogicalKeyboardKey.controlLeft,
+    PhysicalKeyboardKey.controlRight: LogicalKeyboardKey.controlRight,
+    PhysicalKeyboardKey.metaLeft: LogicalKeyboardKey.metaLeft,
+    PhysicalKeyboardKey.metaRight: LogicalKeyboardKey.metaRight,
+    PhysicalKeyboardKey.capsLock: LogicalKeyboardKey.capsLock,
+    PhysicalKeyboardKey.numLock: LogicalKeyboardKey.numLock,
+    PhysicalKeyboardKey.scrollLock: LogicalKeyboardKey.scrollLock,
+  };
+
+  // The map of all modifier keys that are represented in modifier key bit
+  // masks on all platforms, so that they can be cleared out of pressedKeys when
+  // synchronizing.
+  static final Map<PhysicalKeyboardKey, LogicalKeyboardKey> _allModifiers = <PhysicalKeyboardKey, LogicalKeyboardKey>{
+    PhysicalKeyboardKey.fn: LogicalKeyboardKey.fn,
+    ..._allModifiersExceptFn,
+  };
+
+  void _synchronizeModifiers(RawKeyEvent event) {
+    // Don't send any key events for these changes, since there *should* be
+    // separate events for each modifier key down/up that occurs while the app
+    // has focus. This is just to synchronize the modifier keys when they are
+    // pressed/released while the app doesn't have focus, to make sure that
+    // _keysPressed reflects reality at all times.
+
+    final Map<ModifierKey, KeyboardSide?> modifiersPressed = event.data.modifiersPressed;
+    final Map<PhysicalKeyboardKey, LogicalKeyboardKey> modifierKeys = <PhysicalKeyboardKey, LogicalKeyboardKey>{};
+    for (final ModifierKey key in modifiersPressed.keys) {
+      final Set<PhysicalKeyboardKey>? mappedKeys = _modifierKeyMap[_ModifierSidePair(key, modifiersPressed[key])];
+      assert((){
+        if (mappedKeys == null) {
+          debugPrint('Platform key support for ${Platform.operatingSystem} is '
+              'producing unsupported modifier combinations for '
+              'modifier $key on side ${modifiersPressed[key]}.');
+          if (event.data is RawKeyEventDataAndroid) {
+            debugPrint('Android raw key metaState: ${(event.data as RawKeyEventDataAndroid).metaState}');
+          }
+        }
+        return true;
+      }());
+      if (mappedKeys == null) {
+        continue;
+      }
+      for (final PhysicalKeyboardKey physicalModifier in mappedKeys) {
+        modifierKeys[physicalModifier] = _allModifiers[physicalModifier]!;
+      }
+    }
+    _allModifiersExceptFn.keys.forEach(_keysPressed.remove);
+    if (event.data is! RawKeyEventDataFuchsia && event.data is! RawKeyEventDataMacOs) {
+      // On Fuchsia and macOS, the Fn key is not considered a modifier key.
+      _keysPressed.remove(PhysicalKeyboardKey.fn);
+    }
+    _keysPressed.addAll(modifierKeys);
+  }
+
+  final Map<PhysicalKeyboardKey, LogicalKeyboardKey> _keysPressed = <PhysicalKeyboardKey, LogicalKeyboardKey>{};
+
+  /// Returns the set of keys currently pressed.
+  Set<LogicalKeyboardKey> get keysPressed => _keysPressed.values.toSet();
+
+  /// Returns the set of physical keys currently pressed.
+  Set<PhysicalKeyboardKey> get physicalKeysPressed => _keysPressed.keys.toSet();
+
+  /// Clears the list of keys returned from [keysPressed].
+  ///
+  /// This is used by the testing framework to make sure tests are hermetic.
+  @visibleForTesting
+  void clearKeysPressed() => _keysPressed.clear();
+}
+
+@immutable
+class _ModifierSidePair {
+  const _ModifierSidePair(this.modifier, this.side);
+
+  final ModifierKey modifier;
+  final KeyboardSide? side;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _ModifierSidePair
+        && other.modifier == modifier
+        && other.side == side;
+  }
+
+  @override
+  int get hashCode => hashValues(modifier, side);
+}
diff --git a/lib/src/services/raw_keyboard_android.dart b/lib/src/services/raw_keyboard_android.dart
new file mode 100644
index 0000000..037bbf4
--- /dev/null
+++ b/lib/src/services/raw_keyboard_android.dart
@@ -0,0 +1,451 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+import 'keyboard_key.dart';
+import 'keyboard_maps.dart';
+import 'raw_keyboard.dart';
+
+// Android sets the 0x80000000 bit on a character to indicate that it is a
+// combining character, so we use this mask to remove that bit to make it a
+// valid Unicode character again.
+const int _kCombiningCharacterMask = 0x7fffffff;
+
+/// Platform-specific key event data for Android.
+///
+/// This object contains information about key events obtained from Android's
+/// `KeyEvent` interface.
+///
+/// See also:
+///
+///  * [RawKeyboard], which uses this interface to expose key data.
+class RawKeyEventDataAndroid extends RawKeyEventData {
+  /// Creates a key event data structure specific for Android.
+  ///
+  /// The [flags], [codePoint], [keyCode], [scanCode], and [metaState] arguments
+  /// must not be null.
+  const RawKeyEventDataAndroid({
+    this.flags = 0,
+    this.codePoint = 0,
+    this.plainCodePoint = 0,
+    this.keyCode = 0,
+    this.scanCode = 0,
+    this.metaState = 0,
+    this.eventSource = 0,
+    this.vendorId = 0,
+    this.productId = 0,
+    this.deviceId = 0,
+    this.repeatCount = 0,
+  }) : assert(flags != null),
+       assert(codePoint != null),
+       assert(keyCode != null),
+       assert(scanCode != null),
+       assert(metaState != null);
+
+  /// The current set of additional flags for this event.
+  ///
+  /// Flags indicate things like repeat state, etc.
+  ///
+  /// See <https://developer.android.com/reference/android/view/KeyEvent.html#getFlags()>
+  /// for more information.
+  final int flags;
+
+  /// The Unicode code point represented by the key event, if any.
+  ///
+  /// If there is no Unicode code point, this value is zero.
+  ///
+  /// Dead keys are represented as Unicode combining characters.
+  ///
+  /// See <https://developer.android.com/reference/android/view/KeyEvent.html#getUnicodeChar()>
+  /// for more information.
+  final int codePoint;
+
+  /// The Unicode code point represented by the key event, if any, without
+  /// regard to any modifier keys which are currently pressed.
+  ///
+  /// If there is no Unicode code point, this value is zero.
+  ///
+  /// Dead keys are represented as Unicode combining characters.
+  ///
+  /// This is the result of calling KeyEvent.getUnicodeChar(0) on Android.
+  ///
+  /// See <https://developer.android.com/reference/android/view/KeyEvent.html#getUnicodeChar(int)>
+  /// for more information.
+  final int plainCodePoint;
+
+  /// The hardware key code corresponding to this key event.
+  ///
+  /// This is the physical key that was pressed, not the Unicode character.
+  /// See [codePoint] for the Unicode character.
+  ///
+  /// See <https://developer.android.com/reference/android/view/KeyEvent.html#getKeyCode()>
+  /// for more information.
+  final int keyCode;
+
+  /// The hardware scan code id corresponding to this key event.
+  ///
+  /// These values are not reliable and vary from device to device, so this
+  /// information is mainly useful for debugging.
+  ///
+  /// See <https://developer.android.com/reference/android/view/KeyEvent.html#getScanCode()>
+  /// for more information.
+  final int scanCode;
+
+  /// The modifiers that were present when the key event occurred.
+  ///
+  /// See <https://developer.android.com/reference/android/view/KeyEvent.html#getMetaState()>
+  /// for the numerical values of the `metaState`. Many of these constants are
+  /// also replicated as static constants in this class.
+  ///
+  /// See also:
+  ///
+  ///  * [modifiersPressed], which returns a Map of currently pressed modifiers
+  ///    and their keyboard side.
+  ///  * [isModifierPressed], to see if a specific modifier is pressed.
+  ///  * [isControlPressed], to see if a CTRL key is pressed.
+  ///  * [isShiftPressed], to see if a SHIFT key is pressed.
+  ///  * [isAltPressed], to see if an ALT key is pressed.
+  ///  * [isMetaPressed], to see if a META key is pressed.
+  final int metaState;
+
+  /// The source of the event.
+  ///
+  /// See <https://developer.android.com/reference/android/view/KeyEvent.html#getSource()>
+  /// for the numerical values of the `source`. Many of these constants are also
+  /// replicated as static constants in this class.
+  final int eventSource;
+
+  /// The vendor ID of the device that produced the event.
+  ///
+  /// See <https://developer.android.com/reference/android/view/InputDevice.html#getVendorId()>
+  /// for the numerical values of the `vendorId`.
+  final int vendorId;
+
+  /// The product ID of the device that produced the event.
+  ///
+  /// See <https://developer.android.com/reference/android/view/InputDevice.html#getProductId()>
+  /// for the numerical values of the `productId`.
+  final int productId;
+
+  /// The ID of the device that produced the event.
+  ///
+  /// See https://developer.android.com/reference/android/view/InputDevice.html#getId()
+  final int deviceId;
+
+  /// The repeat count of the event.
+  ///
+  /// See <https://developer.android.com/reference/android/view/KeyEvent#getRepeatCount()>
+  /// for more information.
+  final int repeatCount;
+
+  // The source code that indicates that an event came from a joystick.
+  // from https://developer.android.com/reference/android/view/InputDevice.html#SOURCE_JOYSTICK
+  static const int _sourceJoystick = 0x01000010;
+
+  // Android only reports a single code point for the key label.
+  @override
+  String get keyLabel => plainCodePoint == 0 ? '' : String.fromCharCode(plainCodePoint & _kCombiningCharacterMask);
+
+  @override
+  PhysicalKeyboardKey get physicalKey {
+    if (kAndroidToPhysicalKey.containsKey(scanCode)) {
+      return kAndroidToPhysicalKey[scanCode] ?? PhysicalKeyboardKey.none;
+    }
+
+    // Android sends DPAD_UP, etc. as the keyCode for joystick DPAD events, but
+    // it doesn't set the scanCode for those, so we have to detect this, and set
+    // our own DPAD physical keys. The logical key will still match "arrowUp",
+    // etc.
+    if (eventSource & _sourceJoystick == _sourceJoystick) {
+      final LogicalKeyboardKey? foundKey = kAndroidToLogicalKey[keyCode];
+      if (foundKey == LogicalKeyboardKey.arrowUp) {
+        return PhysicalKeyboardKey.arrowUp;
+      }
+      if (foundKey == LogicalKeyboardKey.arrowDown) {
+        return PhysicalKeyboardKey.arrowDown;
+      }
+      if (foundKey == LogicalKeyboardKey.arrowLeft) {
+        return PhysicalKeyboardKey.arrowLeft;
+      }
+      if (foundKey == LogicalKeyboardKey.arrowRight) {
+        return PhysicalKeyboardKey.arrowRight;
+      }
+    }
+    return PhysicalKeyboardKey.none;
+  }
+
+  @override
+  LogicalKeyboardKey get logicalKey {
+    // Look to see if the keyCode is a printable number pad key, so that a
+    // difference between regular keys (e.g. "=") and the number pad version
+    // (e.g. the "=" on the number pad) can be determined.
+    final LogicalKeyboardKey? numPadKey = kAndroidNumPadMap[keyCode];
+    if (numPadKey != null) {
+      return numPadKey;
+    }
+
+    // If it has a non-control-character label, then either return the existing
+    // constant, or construct a new Unicode-based key from it. Don't mark it as
+    // autogenerated, since the label uniquely identifies an ID from the Unicode
+    // plane.
+    if (keyLabel.isNotEmpty && !LogicalKeyboardKey.isControlCharacter(keyLabel)) {
+      final int combinedCodePoint = plainCodePoint & _kCombiningCharacterMask;
+      final int keyId = LogicalKeyboardKey.unicodePlane | (combinedCodePoint & LogicalKeyboardKey.valueMask);
+      return LogicalKeyboardKey.findKeyByKeyId(keyId) ?? LogicalKeyboardKey(
+        keyId,
+        keyLabel: keyLabel,
+        debugName: kReleaseMode ? null : 'Key ${keyLabel.toUpperCase()}',
+      );
+    }
+
+    // Look to see if the keyCode is one we know about and have a mapping for.
+    LogicalKeyboardKey? newKey = kAndroidToLogicalKey[keyCode];
+    if (newKey != null) {
+      return newKey;
+    }
+
+    // This is a non-printable key that we don't know about, so we mint a new
+    // code with the autogenerated bit set.
+    const int androidKeyIdPlane = 0x00200000000;
+    newKey ??= LogicalKeyboardKey(
+      androidKeyIdPlane | keyCode | LogicalKeyboardKey.autogeneratedMask,
+      debugName: kReleaseMode ? null : 'Unknown Android key code $keyCode',
+    );
+    return newKey;
+  }
+
+  bool _isLeftRightModifierPressed(KeyboardSide side, int anyMask, int leftMask, int rightMask) {
+    if (metaState & anyMask == 0) {
+      return false;
+    }
+    switch (side) {
+      case KeyboardSide.any:
+        return true;
+      case KeyboardSide.all:
+        return metaState & leftMask != 0 && metaState & rightMask != 0;
+      case KeyboardSide.left:
+        return metaState & leftMask != 0;
+      case KeyboardSide.right:
+        return metaState & rightMask != 0;
+    }
+  }
+
+  @override
+  bool isModifierPressed(ModifierKey key, { KeyboardSide side = KeyboardSide.any }) {
+    assert(side != null);
+    switch (key) {
+      case ModifierKey.controlModifier:
+        return _isLeftRightModifierPressed(side, modifierControl, modifierLeftControl, modifierRightControl);
+      case ModifierKey.shiftModifier:
+        return _isLeftRightModifierPressed(side, modifierShift, modifierLeftShift, modifierRightShift);
+      case ModifierKey.altModifier:
+        return _isLeftRightModifierPressed(side, modifierAlt, modifierLeftAlt, modifierRightAlt);
+      case ModifierKey.metaModifier:
+        return _isLeftRightModifierPressed(side, modifierMeta, modifierLeftMeta, modifierRightMeta);
+      case ModifierKey.capsLockModifier:
+        return metaState & modifierCapsLock != 0;
+      case ModifierKey.numLockModifier:
+        return metaState & modifierNumLock != 0;
+      case ModifierKey.scrollLockModifier:
+        return metaState & modifierScrollLock != 0;
+      case ModifierKey.functionModifier:
+        return metaState & modifierFunction != 0;
+      case ModifierKey.symbolModifier:
+        return metaState & modifierSym != 0;
+    }
+  }
+
+  @override
+  KeyboardSide? getModifierSide(ModifierKey key) {
+    KeyboardSide? findSide(int anyMask, int leftMask, int rightMask) {
+      final int combinedMask = leftMask | rightMask;
+      final int combined = metaState & combinedMask;
+      if (combined == leftMask) {
+        return KeyboardSide.left;
+      } else if (combined == rightMask) {
+        return KeyboardSide.right;
+      } else if (combined == combinedMask) {
+        return KeyboardSide.all;
+      }
+      // If the platform code sets the "any" modifier, but not a specific side,
+      // then we return "all", assuming that there is only one of that modifier
+      // key on the keyboard.
+      if (metaState & anyMask != 0) {
+        return KeyboardSide.all;
+      }
+      return null;
+    }
+
+    switch (key) {
+      case ModifierKey.controlModifier:
+        return findSide(modifierControl, modifierLeftControl, modifierRightControl);
+      case ModifierKey.shiftModifier:
+        return findSide(modifierShift, modifierLeftShift, modifierRightShift);
+      case ModifierKey.altModifier:
+        return findSide(modifierAlt, modifierLeftAlt, modifierRightAlt);
+      case ModifierKey.metaModifier:
+        return findSide(modifierMeta, modifierLeftMeta, modifierRightMeta);
+      case ModifierKey.capsLockModifier:
+      case ModifierKey.numLockModifier:
+      case ModifierKey.scrollLockModifier:
+      case ModifierKey.functionModifier:
+      case ModifierKey.symbolModifier:
+        return KeyboardSide.all;
+    }
+  }
+
+  // Modifier key masks.
+
+  /// No modifier keys are pressed in the [metaState] field.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierNone = 0;
+
+  /// This mask is used to check the [metaState] field to test whether one of
+  /// the ALT modifier keys is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierAlt = 0x02;
+
+  /// This mask is used to check the [metaState] field to test whether the left
+  /// ALT modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierLeftAlt = 0x10;
+
+  /// This mask is used to check the [metaState] field to test whether the right
+  /// ALT modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierRightAlt = 0x20;
+
+  /// This mask is used to check the [metaState] field to test whether one of
+  /// the SHIFT modifier keys is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierShift = 0x01;
+
+  /// This mask is used to check the [metaState] field to test whether the left
+  /// SHIFT modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierLeftShift = 0x40;
+
+  /// This mask is used to check the [metaState] field to test whether the right
+  /// SHIFT modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierRightShift = 0x80;
+
+  /// This mask is used to check the [metaState] field to test whether the SYM
+  /// modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierSym = 0x04;
+
+  /// This mask is used to check the [metaState] field to test whether the
+  /// Function modifier key (Fn) is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierFunction = 0x08;
+
+  /// This mask is used to check the [metaState] field to test whether one of
+  /// the CTRL modifier keys is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierControl = 0x1000;
+
+  /// This mask is used to check the [metaState] field to test whether the left
+  /// CTRL modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierLeftControl = 0x2000;
+
+  /// This mask is used to check the [metaState] field to test whether the right
+  /// CTRL modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierRightControl = 0x4000;
+
+  /// This mask is used to check the [metaState] field to test whether one of
+  /// the META modifier keys is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierMeta = 0x10000;
+
+  /// This mask is used to check the [metaState] field to test whether the left
+  /// META modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierLeftMeta = 0x20000;
+
+  /// This mask is used to check the [metaState] field to test whether the right
+  /// META modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierRightMeta = 0x40000;
+
+  /// This mask is used to check the [metaState] field to test whether the CAPS
+  /// LOCK modifier key is on.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierCapsLock = 0x100000;
+
+  /// This mask is used to check the [metaState] field to test whether the NUM
+  /// LOCK modifier key is on.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierNumLock = 0x200000;
+
+  /// This mask is used to check the [metaState] field to test whether the
+  /// SCROLL LOCK modifier key is on.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierScrollLock = 0x400000;
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'RawKeyEventDataAndroid')}(keyLabel: $keyLabel flags: $flags, codePoint: $codePoint, '
+      'keyCode: $keyCode, scanCode: $scanCode, metaState: $metaState, '
+      'modifiers down: $modifiersPressed)';
+  }
+}
diff --git a/lib/src/services/raw_keyboard_fuchsia.dart b/lib/src/services/raw_keyboard_fuchsia.dart
new file mode 100644
index 0000000..0aade74
--- /dev/null
+++ b/lib/src/services/raw_keyboard_fuchsia.dart
@@ -0,0 +1,289 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+import 'keyboard_key.dart';
+import 'keyboard_maps.dart';
+import 'raw_keyboard.dart';
+
+/// Platform-specific key event data for Fuchsia.
+///
+/// This object contains information about key events obtained from Fuchsia's
+/// `KeyData` interface.
+///
+/// See also:
+///
+///  * [RawKeyboard], which uses this interface to expose key data.
+class RawKeyEventDataFuchsia extends RawKeyEventData {
+  /// Creates a key event data structure specific for Fuchsia.
+  ///
+  /// The [hidUsage], [codePoint], and [modifiers] arguments must not be null.
+  const RawKeyEventDataFuchsia({
+    this.hidUsage = 0,
+    this.codePoint = 0,
+    this.modifiers = 0,
+  }) : assert(hidUsage != null),
+       assert(codePoint != null),
+       assert(modifiers != null);
+
+  /// The USB HID usage.
+  ///
+  /// See <http://www.usb.org/developers/hidpage/Hut1_12v2.pdf> for more
+  /// information.
+  final int hidUsage;
+
+  /// The Unicode code point represented by the key event, if any.
+  ///
+  /// If there is no Unicode code point, this value is zero.
+  ///
+  /// Dead keys are represented as Unicode combining characters.
+  final int codePoint;
+
+  /// The modifiers that were present when the key event occurred.
+  ///
+  /// See <https://fuchsia.googlesource.com/garnet/+/master/public/fidl/fuchsia.ui.input/input_event_constants.fidl>
+  /// for the numerical values of the modifiers. Many of these are also
+  /// replicated as static constants in this class.
+  ///
+  /// See also:
+  ///
+  ///  * [modifiersPressed], which returns a Map of currently pressed modifiers
+  ///    and their keyboard side.
+  ///  * [isModifierPressed], to see if a specific modifier is pressed.
+  ///  * [isControlPressed], to see if a CTRL key is pressed.
+  ///  * [isShiftPressed], to see if a SHIFT key is pressed.
+  ///  * [isAltPressed], to see if an ALT key is pressed.
+  ///  * [isMetaPressed], to see if a META key is pressed.
+  final int modifiers;
+
+  // Fuchsia only reports a single code point for the key label.
+  @override
+  String get keyLabel => codePoint == 0 ? '' : String.fromCharCode(codePoint);
+
+  @override
+  LogicalKeyboardKey get logicalKey {
+    // If the key has a printable representation, then make a logical key based
+    // on that.
+    if (codePoint != 0) {
+      return LogicalKeyboardKey(
+        LogicalKeyboardKey.unicodePlane | codePoint & LogicalKeyboardKey.valueMask,
+        keyLabel: keyLabel,
+        debugName: kReleaseMode ? null : 'Key $keyLabel',
+      );
+    }
+
+    // Look to see if the hidUsage is one we know about and have a mapping for.
+    LogicalKeyboardKey? newKey = kFuchsiaToLogicalKey[hidUsage | LogicalKeyboardKey.hidPlane];
+    if (newKey != null) {
+      return newKey;
+    }
+
+    // This is a non-printable key that we don't know about, so we mint a new
+    // code with the autogenerated bit set.
+    const int fuchsiaKeyIdPlane = 0x00300000000;
+    newKey ??= LogicalKeyboardKey(
+      fuchsiaKeyIdPlane | hidUsage | LogicalKeyboardKey.autogeneratedMask,
+      debugName: kReleaseMode ? null : 'Ephemeral Fuchsia key code $hidUsage',
+    );
+    return newKey;
+  }
+
+  @override
+  PhysicalKeyboardKey get physicalKey => kFuchsiaToPhysicalKey[hidUsage] ?? PhysicalKeyboardKey.none;
+
+  bool _isLeftRightModifierPressed(KeyboardSide side, int anyMask, int leftMask, int rightMask) {
+    if (modifiers & anyMask == 0) {
+      return false;
+    }
+    switch (side) {
+      case KeyboardSide.any:
+        return true;
+      case KeyboardSide.all:
+        return modifiers & leftMask != 0 && modifiers & rightMask != 0;
+      case KeyboardSide.left:
+        return modifiers & leftMask != 0;
+      case KeyboardSide.right:
+        return modifiers & rightMask != 0;
+    }
+  }
+
+  @override
+  bool isModifierPressed(ModifierKey key, { KeyboardSide side = KeyboardSide.any }) {
+    assert(side != null);
+    switch (key) {
+      case ModifierKey.controlModifier:
+        return _isLeftRightModifierPressed(side, modifierControl, modifierLeftControl, modifierRightControl);
+      case ModifierKey.shiftModifier:
+        return _isLeftRightModifierPressed(side, modifierShift, modifierLeftShift, modifierRightShift);
+      case ModifierKey.altModifier:
+        return _isLeftRightModifierPressed(side, modifierAlt, modifierLeftAlt, modifierRightAlt);
+      case ModifierKey.metaModifier:
+        return _isLeftRightModifierPressed(side, modifierMeta, modifierLeftMeta, modifierRightMeta);
+      case ModifierKey.capsLockModifier:
+        return modifiers & modifierCapsLock != 0;
+      case ModifierKey.numLockModifier:
+      case ModifierKey.scrollLockModifier:
+      case ModifierKey.functionModifier:
+      case ModifierKey.symbolModifier:
+        // Fuchsia doesn't have masks for these keys (yet).
+        return false;
+    }
+  }
+
+  @override
+  KeyboardSide? getModifierSide(ModifierKey key) {
+    KeyboardSide? findSide(int anyMask, int leftMask, int rightMask) {
+      final int combined = modifiers & anyMask;
+      if (combined == leftMask) {
+        return KeyboardSide.left;
+      } else if (combined == rightMask) {
+        return KeyboardSide.right;
+      } else if (combined == anyMask) {
+        return KeyboardSide.all;
+      }
+      return null;
+    }
+
+    switch (key) {
+      case ModifierKey.controlModifier:
+        return findSide(modifierControl, modifierLeftControl, modifierRightControl, );
+      case ModifierKey.shiftModifier:
+        return findSide(modifierShift, modifierLeftShift, modifierRightShift);
+      case ModifierKey.altModifier:
+        return findSide(modifierAlt, modifierLeftAlt, modifierRightAlt);
+      case ModifierKey.metaModifier:
+        return findSide(modifierMeta, modifierLeftMeta, modifierRightMeta);
+      case ModifierKey.capsLockModifier:
+        return (modifiers & modifierCapsLock == 0) ? null : KeyboardSide.all;
+      case ModifierKey.numLockModifier:
+      case ModifierKey.scrollLockModifier:
+      case ModifierKey.functionModifier:
+      case ModifierKey.symbolModifier:
+        // Fuchsia doesn't support these modifiers, so they can't be pressed.
+        return null;
+    }
+  }
+
+  // Keyboard modifier masks for Fuchsia modifiers.
+
+  /// The [modifiers] field indicates that no modifier keys are pressed if it
+  /// equals this value.
+  ///
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierNone = 0x0;
+
+  /// This mask is used to check the [modifiers] field to test whether the CAPS
+  /// LOCK modifier key is on.
+  ///
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierCapsLock = 0x1;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// SHIFT modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierLeftShift = 0x2;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// SHIFT modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierRightShift = 0x4;
+
+  /// This mask is used to check the [modifiers] field to test whether one of
+  /// the SHIFT modifier keys is pressed.
+  ///
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierShift = modifierLeftShift | modifierRightShift;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// CTRL modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierLeftControl = 0x8;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// CTRL modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierRightControl = 0x10;
+
+  /// This mask is used to check the [modifiers] field to test whether one of
+  /// the CTRL modifier keys is pressed.
+  ///
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierControl = modifierLeftControl | modifierRightControl;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// ALT modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierLeftAlt = 0x20;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// ALT modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierRightAlt = 0x40;
+
+  /// This mask is used to check the [modifiers] field to test whether one of
+  /// the ALT modifier keys is pressed.
+  ///
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierAlt = modifierLeftAlt | modifierRightAlt;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// META modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierLeftMeta = 0x80;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// META modifier key is pressed.
+  ///
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierRightMeta = 0x100;
+
+  /// This mask is used to check the [modifiers] field to test whether one of
+  /// the META modifier keys is pressed.
+  ///
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierMeta = modifierLeftMeta | modifierRightMeta;
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'RawKeyEventDataFuchsia')}(hidUsage: $hidUsage, codePoint: $codePoint, modifiers: $modifiers, '
+        'modifiers down: $modifiersPressed)';
+  }
+}
diff --git a/lib/src/services/raw_keyboard_ios.dart b/lib/src/services/raw_keyboard_ios.dart
new file mode 100644
index 0000000..4f0917f
--- /dev/null
+++ b/lib/src/services/raw_keyboard_ios.dart
@@ -0,0 +1,370 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'keyboard_key.dart';
+import 'keyboard_maps.dart';
+import 'raw_keyboard.dart';
+
+/// Maps iOS specific string values of nonvisible keys to logical keys
+///
+/// See: https://developer.apple.com/documentation/uikit/uikeycommand/input_strings_for_special_keys?language=objc
+const Map<String, LogicalKeyboardKey> _kIosToLogicalMap = <String, LogicalKeyboardKey>{
+  'UIKeyInputEscape': LogicalKeyboardKey.escape,
+  'UIKeyInputF1': LogicalKeyboardKey.f1,
+  'UIKeyInputF2': LogicalKeyboardKey.f2,
+  'UIKeyInputF3': LogicalKeyboardKey.f3,
+  'UIKeyInputF4': LogicalKeyboardKey.f4,
+  'UIKeyInputF5': LogicalKeyboardKey.f5,
+  'UIKeyInputF6': LogicalKeyboardKey.f6,
+  'UIKeyInputF7': LogicalKeyboardKey.f7,
+  'UIKeyInputF8': LogicalKeyboardKey.f8,
+  'UIKeyInputF9': LogicalKeyboardKey.f9,
+  'UIKeyInputF10': LogicalKeyboardKey.f10,
+  'UIKeyInputF11': LogicalKeyboardKey.f11,
+  'UIKeyInputF12': LogicalKeyboardKey.f12,
+  'UIKeyInputUpArrow': LogicalKeyboardKey.arrowUp,
+  'UIKeyInputDownArrow': LogicalKeyboardKey.arrowDown,
+  'UIKeyInputLeftArrow': LogicalKeyboardKey.arrowLeft,
+  'UIKeyInputRightArrow': LogicalKeyboardKey.arrowRight,
+  'UIKeyInputHome': LogicalKeyboardKey.home,
+  'UIKeyInputEnd': LogicalKeyboardKey.enter,
+  'UIKeyInputPageUp': LogicalKeyboardKey.pageUp,
+  'UIKeyInputPageDown': LogicalKeyboardKey.pageDown
+};
+/// Platform-specific key event data for iOS.
+///
+/// This object contains information about key events obtained from iOS'
+/// `UIKey` interface.
+///
+/// See also:
+///
+///  * [RawKeyboard], which uses this interface to expose key data.
+class RawKeyEventDataIos extends RawKeyEventData {
+  /// Creates a key event data structure specific for iOS.
+  ///
+  /// The [characters], [charactersIgnoringModifiers], and [modifiers], arguments
+  /// must not be null.
+  const RawKeyEventDataIos({
+    this.characters = '',
+    this.charactersIgnoringModifiers = '',
+    this.keyCode = 0,
+    this.modifiers = 0,
+  }) : assert(characters != null),
+       assert(charactersIgnoringModifiers != null),
+       assert(keyCode != null),
+       assert(modifiers != null);
+
+  /// The Unicode characters associated with a key-up or key-down event.
+  ///
+  /// See also:
+  ///
+  ///  * [Apple's UIKey documentation](https://developer.apple.com/documentation/uikit/uikey/3526130-characters?language=objc)
+  final String characters;
+
+  /// The characters generated by a key event as if no modifier key (except for
+  /// Shift) applies.
+  ///
+  /// See also:
+  ///
+  ///  * [Apple's UIKey documentation](https://developer.apple.com/documentation/uikit/uikey/3526131-charactersignoringmodifiers?language=objc)
+  final String charactersIgnoringModifiers;
+
+  /// The virtual key code for the keyboard key associated with a key event.
+  ///
+  /// See also:
+  ///
+  ///  * [Apple's UIKey documentation](https://developer.apple.com/documentation/uikit/uikey/3526132-keycode?language=objc)
+  final int keyCode;
+
+  /// A mask of the current modifiers using the values in Modifier Flags.
+  ///
+  /// See also:
+  ///
+  ///  * [Apple's UIKey documentation](https://developer.apple.com/documentation/uikit/uikey/3526133-modifierflags?language=objc)
+  final int modifiers;
+
+  @override
+  String get keyLabel => charactersIgnoringModifiers;
+
+  @override
+  PhysicalKeyboardKey get physicalKey => kIosToPhysicalKey[keyCode] ?? PhysicalKeyboardKey.none;
+
+
+  @override
+  LogicalKeyboardKey get logicalKey {
+    // Look to see if the keyCode is a printable number pad key, so that a
+    // difference between regular keys (e.g. "=") and the number pad version
+    // (e.g. the "=" on the number pad) can be determined.
+    final LogicalKeyboardKey? numPadKey = kIosNumPadMap[keyCode];
+    if (numPadKey != null) {
+      return numPadKey;
+    }
+
+    // Look to see if the [keyLabel] is one we know about and have a mapping for.
+    final LogicalKeyboardKey? newKey = _kIosToLogicalMap[keyLabel];
+    if (newKey != null) {
+      return newKey;
+    }
+    // If this key is printable, generate the LogicalKeyboardKey from its
+    // Unicode value. Control keys such as ESC, CRTL, and SHIFT are not
+    // printable. HOME, DEL, arrow keys, and function keys are considered
+    // modifier function keys, which generate invalid Unicode scalar values.
+    if (keyLabel.isNotEmpty &&
+        !LogicalKeyboardKey.isControlCharacter(keyLabel)) {
+      // Given that charactersIgnoringModifiers can contain a String of
+      // arbitrary length, limit to a maximum of two Unicode scalar values. It
+      // is unlikely that a keyboard would produce a code point bigger than 32
+      // bits, but it is still worth defending against this case.
+      assert(charactersIgnoringModifiers.length <= 2);
+      int codeUnit = charactersIgnoringModifiers.codeUnitAt(0);
+      if (charactersIgnoringModifiers.length == 2) {
+        final int secondCode = charactersIgnoringModifiers.codeUnitAt(1);
+        codeUnit = (codeUnit << 16) | secondCode;
+      }
+
+      final int keyId = LogicalKeyboardKey.unicodePlane | (codeUnit & LogicalKeyboardKey.valueMask);
+      return LogicalKeyboardKey.findKeyByKeyId(keyId) ?? LogicalKeyboardKey(
+        keyId,
+        keyLabel: keyLabel,
+        debugName: kReleaseMode ? null : 'Key ${keyLabel.toUpperCase()}',
+      );
+    }
+
+    // Control keys like "backspace" and movement keys like arrow keys don't
+    // have a printable representation, but are present on the physical
+    // keyboard. Since there is no logical keycode map for iOS (iOS uses the
+    // keycode to reference physical keys), a LogicalKeyboardKey is created with
+    // the physical key's HID usage and debugName. This avoids duplicating the
+    // physical key map.
+    if (physicalKey != PhysicalKeyboardKey.none) {
+      final int keyId = physicalKey.usbHidUsage | LogicalKeyboardKey.hidPlane;
+      return LogicalKeyboardKey.findKeyByKeyId(keyId) ?? LogicalKeyboardKey(
+        keyId,
+        keyLabel: physicalKey.debugName ?? '',
+        debugName: physicalKey.debugName,
+      );
+    }
+
+    // This is a non-printable key that is unrecognized, so a new code is minted
+    // with the autogenerated bit set.
+    const int iosKeyIdPlane = 0x00400000000;
+
+    return LogicalKeyboardKey(
+      iosKeyIdPlane | keyCode | LogicalKeyboardKey.autogeneratedMask,
+      debugName: kReleaseMode ? null : 'Unknown iOS key code $keyCode',
+    );
+  }
+
+  bool _isLeftRightModifierPressed(KeyboardSide side, int anyMask, int leftMask, int rightMask) {
+    if (modifiers & anyMask == 0) {
+      return false;
+    }
+    // If only the "anyMask" bit is set, then we respond true for requests of
+    // whether either left or right is pressed. Handles the case where iOS
+    // supplies just the "either" modifier flag, but not the left/right flag.
+    // (e.g. modifierShift but not modifierLeftShift).
+    final bool anyOnly = modifiers & (leftMask | rightMask | anyMask) == anyMask;
+    switch (side) {
+      case KeyboardSide.any:
+        return true;
+      case KeyboardSide.all:
+        return modifiers & leftMask != 0 && modifiers & rightMask != 0 || anyOnly;
+      case KeyboardSide.left:
+        return modifiers & leftMask != 0 || anyOnly;
+      case KeyboardSide.right:
+        return modifiers & rightMask != 0 || anyOnly;
+    }
+  }
+
+  @override
+  bool isModifierPressed(ModifierKey key, {KeyboardSide side = KeyboardSide.any}) {
+    final int independentModifier = modifiers & deviceIndependentMask;
+    bool result;
+    switch (key) {
+      case ModifierKey.controlModifier:
+        result = _isLeftRightModifierPressed(side, independentModifier & modifierControl, modifierLeftControl, modifierRightControl);
+        break;
+      case ModifierKey.shiftModifier:
+        result = _isLeftRightModifierPressed(side, independentModifier & modifierShift, modifierLeftShift, modifierRightShift);
+        break;
+      case ModifierKey.altModifier:
+        result = _isLeftRightModifierPressed(side, independentModifier & modifierOption, modifierLeftOption, modifierRightOption);
+        break;
+      case ModifierKey.metaModifier:
+        result = _isLeftRightModifierPressed(side, independentModifier & modifierCommand, modifierLeftCommand, modifierRightCommand);
+        break;
+      case ModifierKey.capsLockModifier:
+        result = independentModifier & modifierCapsLock != 0;
+        break;
+    // On iOS, the function modifier bit is set for any function key, like F1,
+    // F2, etc., but the meaning of ModifierKey.modifierFunction in Flutter is
+    // that of the Fn modifier key, so there's no good way to emulate that on
+    // iOS.
+      case ModifierKey.functionModifier:
+      case ModifierKey.numLockModifier:
+      case ModifierKey.symbolModifier:
+      case ModifierKey.scrollLockModifier:
+        // These modifier masks are not used in iOS keyboards.
+        result = false;
+        break;
+    }
+    assert(!result || getModifierSide(key) != null, "$runtimeType thinks that a modifier is pressed, but can't figure out what side it's on.");
+    return result;
+  }
+
+  @override
+  KeyboardSide? getModifierSide(ModifierKey key) {
+    KeyboardSide? findSide(int anyMask, int leftMask, int rightMask) {
+      final int combinedMask = leftMask | rightMask;
+      final int combined = modifiers & combinedMask;
+      if (combined == leftMask) {
+        return KeyboardSide.left;
+      } else if (combined == rightMask) {
+        return KeyboardSide.right;
+      } else if (combined == combinedMask || modifiers & (combinedMask | anyMask) == anyMask) {
+        // Handles the case where iOS supplies just the "either" modifier
+        // flag, but not the left/right flag. (e.g. modifierShift but not
+        // modifierLeftShift), or if left and right flags are provided, but not
+        // the "either" modifier flag.
+        return KeyboardSide.all;
+      }
+      return null;
+    }
+
+    switch (key) {
+      case ModifierKey.controlModifier:
+        return findSide(modifierControl, modifierLeftControl, modifierRightControl);
+      case ModifierKey.shiftModifier:
+        return findSide(modifierShift, modifierLeftShift, modifierRightShift);
+      case ModifierKey.altModifier:
+        return findSide(modifierOption, modifierLeftOption, modifierRightOption);
+      case ModifierKey.metaModifier:
+        return findSide(modifierCommand, modifierLeftCommand, modifierRightCommand);
+      case ModifierKey.capsLockModifier:
+      case ModifierKey.numLockModifier:
+      case ModifierKey.scrollLockModifier:
+      case ModifierKey.functionModifier:
+      case ModifierKey.symbolModifier:
+        return KeyboardSide.all;
+    }
+  }
+
+  // Modifier key masks. See Apple's UIKey documentation
+  // https://developer.apple.com/documentation/uikit/uikeymodifierflags?language=objc
+  // https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-86/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h.auto.html
+
+  /// This mask is used to check the [modifiers] field to test whether the CAPS
+  /// LOCK modifier key is on.
+  ///
+  /// {@template flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  /// {@endtemplate}
+  static const int modifierCapsLock = 0x10000;
+
+  /// This mask is used to check the [modifiers] field to test whether one of the
+  /// SHIFT modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierShift = 0x20000;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// SHIFT modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierLeftShift = 0x02;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// SHIFT modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierRightShift = 0x04;
+
+  /// This mask is used to check the [modifiers] field to test whether one of the
+  /// CTRL modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierControl = 0x40000;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// CTRL modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierLeftControl = 0x01;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// CTRL modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierRightControl = 0x2000;
+
+  /// This mask is used to check the [modifiers] field to test whether one of the
+  /// ALT modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierOption = 0x80000;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// ALT modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierLeftOption = 0x20;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// ALT modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierRightOption = 0x40;
+
+  /// This mask is used to check the [modifiers] field to test whether one of the
+  /// CMD modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierCommand = 0x100000;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// CMD modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierLeftCommand = 0x08;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// CMD modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierRightCommand = 0x10;
+
+  /// This mask is used to check the [modifiers] field to test whether any key in
+  /// the numeric keypad is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierNumericPad = 0x200000;
+
+  /// This mask is used to check the [modifiers] field to test whether the
+  /// HELP modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierHelp = 0x400000;
+
+  /// This mask is used to check the [modifiers] field to test whether one of the
+  /// FUNCTION modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataIos.modifierCapsLock}
+  static const int modifierFunction = 0x800000;
+
+  /// Used to retrieve only the device-independent modifier flags, allowing
+  /// applications to mask off the device-dependent modifier flags, including
+  /// event coalescing information.
+  static const int deviceIndependentMask = 0xffff0000;
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'RawKeyEventDataIos')}(keyLabel: $keyLabel, keyCode: $keyCode, characters: $characters,'
+        ' unmodifiedCharacters: $charactersIgnoringModifiers, modifiers: $modifiers, '
+        'modifiers down: $modifiersPressed)';
+  }
+}
diff --git a/lib/src/services/raw_keyboard_linux.dart b/lib/src/services/raw_keyboard_linux.dart
new file mode 100644
index 0000000..722e8a7
--- /dev/null
+++ b/lib/src/services/raw_keyboard_linux.dart
@@ -0,0 +1,442 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+import 'keyboard_key.dart';
+import 'keyboard_maps.dart';
+import 'raw_keyboard.dart';
+
+/// Platform-specific key event data for Linux.
+///
+/// Different window toolkit implementations can map to different key codes. This class
+/// will use the correct mapping depending on the [keyHelper] provided.
+///
+/// See also:
+///
+///  * [RawKeyboard], which uses this interface to expose key data.
+class RawKeyEventDataLinux extends RawKeyEventData {
+  /// Creates a key event data structure specific for Linux.
+  ///
+  /// The [keyHelper], [scanCode], [unicodeScalarValues], [keyCode], and [modifiers],
+  /// arguments must not be null.
+  const RawKeyEventDataLinux({
+    required this.keyHelper,
+    this.unicodeScalarValues = 0,
+    this.scanCode = 0,
+    this.keyCode = 0,
+    this.modifiers = 0,
+    required this.isDown,
+  }) : assert(scanCode != null),
+       assert(unicodeScalarValues != null),
+       assert((unicodeScalarValues & ~LogicalKeyboardKey.valueMask) == 0),
+       assert(keyCode != null),
+       assert(modifiers != null),
+       assert(keyHelper != null);
+
+  /// A helper class that abstracts the fetching of the toolkit-specific mappings.
+  ///
+  /// There is no real concept of a "native" window toolkit on Linux, and each implementation
+  /// (GLFW, GTK, QT, etc) may have a different key code mapping.
+  final KeyHelper keyHelper;
+
+  /// An int with up to two Unicode scalar values generated by a single keystroke. An assertion
+  /// will fire if more than two values are encoded in a single keystroke.
+  ///
+  /// This is typically the character that [keyCode] would produce without any modifier keys.
+  /// For dead keys, it is typically the diacritic it would add to a character. Defaults to 0,
+  /// asserted to be not null.
+  final int unicodeScalarValues;
+
+  /// The hardware scan code id corresponding to this key event.
+  ///
+  /// These values are not reliable and vary from device to device, so this
+  /// information is mainly useful for debugging.
+  final int scanCode;
+
+  /// The hardware key code corresponding to this key event.
+  ///
+  /// This is the physical key that was pressed, not the Unicode character.
+  /// This value may be different depending on the window toolkit used. See [KeyHelper].
+  final int keyCode;
+
+  /// A mask of the current modifiers using the values in Modifier Flags.
+  /// This value may be different depending on the window toolkit used. See [KeyHelper].
+  final int modifiers;
+
+  /// Whether or not this key event is a key down (true) or key up (false).
+  final bool isDown;
+
+  @override
+  String get keyLabel => unicodeScalarValues == 0 ? '' : String.fromCharCode(unicodeScalarValues);
+
+  @override
+  PhysicalKeyboardKey get physicalKey => kLinuxToPhysicalKey[scanCode] ?? PhysicalKeyboardKey.none;
+
+  @override
+  LogicalKeyboardKey get logicalKey {
+    // Look to see if the keyCode is a printable number pad key, so that a
+    // difference between regular keys (e.g. "=") and the number pad version
+    // (e.g. the "=" on the number pad) can be determined.
+    final LogicalKeyboardKey? numPadKey = keyHelper.numpadKey(keyCode);
+    if (numPadKey != null) {
+      return numPadKey;
+    }
+
+    // If it has a non-control-character label, then either return the existing
+    // constant, or construct a new Unicode-based key from it. Don't mark it as
+    // autogenerated, since the label uniquely identifies an ID from the Unicode
+    // plane.
+    if (keyLabel.isNotEmpty &&
+        !LogicalKeyboardKey.isControlCharacter(keyLabel)) {
+      final int keyId = LogicalKeyboardKey.unicodePlane | (unicodeScalarValues & LogicalKeyboardKey.valueMask);
+      return LogicalKeyboardKey.findKeyByKeyId(keyId) ?? LogicalKeyboardKey(
+        keyId,
+        keyLabel: keyLabel,
+        debugName: kReleaseMode ? null : 'Key ${keyLabel.toUpperCase()}',
+      );
+    }
+
+    // Look to see if the keyCode is one we know about and have a mapping for.
+    LogicalKeyboardKey? newKey = keyHelper.logicalKey(keyCode);
+    if (newKey != null) {
+      return newKey;
+    }
+
+    const int linuxKeyIdPlane = 0x00600000000;
+
+    // This is a non-printable key that we don't know about, so we mint a new
+    // code with the autogenerated bit set.
+    newKey ??= LogicalKeyboardKey(
+      linuxKeyIdPlane | keyCode | LogicalKeyboardKey.autogeneratedMask,
+      debugName: kReleaseMode ? null : 'Unknown key code $keyCode',
+    );
+    return newKey;
+  }
+
+  @override
+  bool isModifierPressed(ModifierKey key, {KeyboardSide side = KeyboardSide.any}) {
+    return keyHelper.isModifierPressed(key, modifiers, side: side, keyCode: keyCode, isDown: isDown);
+  }
+
+  @override
+  KeyboardSide getModifierSide(ModifierKey key) {
+    return keyHelper.getModifierSide(key);
+  }
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'RawKeyEventDataLinux')}(keyLabel: $keyLabel, keyCode: $keyCode, scanCode: $scanCode,'
+        ' unicodeScalarValues: $unicodeScalarValues, modifiers: $modifiers, '
+        'modifiers down: $modifiersPressed)';
+  }
+}
+
+/// Abstract class for window-specific key mappings.
+///
+/// Given that there might be multiple window toolkit implementations (GLFW,
+/// GTK, QT, etc), this creates a common interface for each of the
+/// different toolkits.
+abstract class KeyHelper {
+  /// Create a KeyHelper implementation depending on the given toolkit.
+  factory KeyHelper(String toolkit) {
+    if (toolkit == 'glfw') {
+      return GLFWKeyHelper();
+    } else if (toolkit == 'gtk') {
+      return GtkKeyHelper();
+    } else {
+      throw FlutterError('Window toolkit not recognized: $toolkit');
+    }
+  }
+
+  /// Returns a [KeyboardSide] enum value that describes which side or sides of
+  /// the given keyboard modifier key were pressed at the time of this event.
+  KeyboardSide getModifierSide(ModifierKey key);
+
+  /// Returns true if the given [ModifierKey] was pressed at the time of this
+  /// event.
+  bool isModifierPressed(ModifierKey key, int modifiers, {KeyboardSide side = KeyboardSide.any, required int keyCode, required bool isDown});
+
+  /// The numpad key from the specific key code mapping.
+  LogicalKeyboardKey? numpadKey(int keyCode);
+
+  /// The logical key key from the specific key code mapping.
+  LogicalKeyboardKey? logicalKey(int keyCode);
+}
+
+/// Helper class that uses GLFW-specific key mappings.
+class GLFWKeyHelper with KeyHelper {
+  /// This mask is used to check the [RawKeyEventDataLinux.modifiers] field to
+  /// test whether the CAPS LOCK modifier key is on.
+  ///
+  /// {@template flutter.services.GLFWKeyHelper.modifierCapsLock}
+  /// Use this value if you need to decode the [RawKeyEventDataLinux.modifiers]
+  /// field yourself, but it's much easier to use [isModifierPressed] if you
+  /// just want to know if a modifier is pressed. This is especially true on
+  /// GLFW, since its modifiers don't include the effects of the current key
+  /// event.
+  /// {@endtemplate}
+  static const int modifierCapsLock = 0x0010;
+
+  /// This mask is used to check the [RawKeyEventDataLinux.modifiers] field to
+  /// test whether one of the SHIFT modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.GLFWKeyHelper.modifierCapsLock}
+  static const int modifierShift = 0x0001;
+
+  /// This mask is used to check the [RawKeyEventDataLinux.modifiers] field to
+  /// test whether one of the CTRL modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.GLFWKeyHelper.modifierCapsLock}
+  static const int modifierControl = 0x0002;
+
+  /// This mask is used to check the [RawKeyEventDataLinux.modifiers] field to
+  /// test whether one of the ALT modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.GLFWKeyHelper.modifierCapsLock}
+  static const int modifierAlt = 0x0004;
+
+  /// This mask is used to check the [RawKeyEventDataLinux.modifiers] field to
+  /// test whether one of the Meta(SUPER) modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.GLFWKeyHelper.modifierCapsLock}
+  static const int modifierMeta = 0x0008;
+
+
+  /// This mask is used to check the [RawKeyEventDataLinux.modifiers] field to
+  /// test whether any key in the numeric keypad is pressed.
+  ///
+  /// {@macro flutter.services.GLFWKeyHelper.modifierCapsLock}
+  static const int modifierNumericPad = 0x0020;
+
+  int _mergeModifiers({required int modifiers, required int keyCode, required bool isDown}) {
+    // GLFW Key codes for modifier keys.
+    const int shiftLeftKeyCode = 340;
+    const int shiftRightKeyCode = 344;
+    const int controlLeftKeyCode = 341;
+    const int controlRightKeyCode = 345;
+    const int altLeftKeyCode = 342;
+    const int altRightKeyCode = 346;
+    const int metaLeftKeyCode = 343;
+    const int metaRightKeyCode = 347;
+    const int capsLockKeyCode = 280;
+    const int numLockKeyCode = 282;
+
+    // On GLFW, the "modifiers" bitfield is the state as it is BEFORE this event
+    // happened, not AFTER, like every other platform. Consequently, if this is
+    // a key down, then we need to add the correct modifier bits, and if it's a
+    // key up, we need to remove them.
+
+    int modifierChange = 0;
+    switch (keyCode) {
+      case shiftLeftKeyCode:
+      case shiftRightKeyCode:
+        modifierChange = modifierShift;
+        break;
+      case controlLeftKeyCode:
+      case controlRightKeyCode:
+        modifierChange = modifierControl;
+        break;
+      case altLeftKeyCode:
+      case altRightKeyCode:
+        modifierChange = modifierAlt;
+        break;
+      case metaLeftKeyCode:
+      case metaRightKeyCode:
+        modifierChange = modifierMeta;
+        break;
+      case capsLockKeyCode:
+        modifierChange = modifierCapsLock;
+        break;
+      case numLockKeyCode:
+        modifierChange = modifierNumericPad;
+        break;
+      default:
+        break;
+    }
+
+    return isDown ? modifiers | modifierChange : modifiers & ~modifierChange;
+  }
+
+  @override
+  bool isModifierPressed(ModifierKey key, int modifiers, {KeyboardSide side = KeyboardSide.any, required int keyCode, required bool isDown}) {
+    modifiers = _mergeModifiers(modifiers: modifiers, keyCode: keyCode, isDown: isDown);
+    switch (key) {
+      case ModifierKey.controlModifier:
+        return modifiers & modifierControl != 0;
+      case ModifierKey.shiftModifier:
+        return modifiers & modifierShift != 0;
+      case ModifierKey.altModifier:
+        return modifiers & modifierAlt != 0;
+      case ModifierKey.metaModifier:
+        return modifiers & modifierMeta != 0;
+      case ModifierKey.capsLockModifier:
+        return modifiers & modifierCapsLock != 0;
+      case ModifierKey.numLockModifier:
+        return modifiers & modifierNumericPad != 0;
+      case ModifierKey.functionModifier:
+      case ModifierKey.symbolModifier:
+      case ModifierKey.scrollLockModifier:
+        // These are not used in GLFW keyboards.
+        return false;
+    }
+  }
+
+  @override
+  KeyboardSide getModifierSide(ModifierKey key) {
+    // Neither GLFW nor X11 provide a distinction between left and right
+    // modifiers, so defaults to KeyboardSide.all.
+    // https://code.woboq.org/qt5/include/X11/X.h.html#_M/ShiftMask
+    return KeyboardSide.all;
+  }
+
+  @override
+  LogicalKeyboardKey? numpadKey(int keyCode) {
+    return kGlfwNumpadMap[keyCode];
+  }
+
+  @override
+  LogicalKeyboardKey? logicalKey(int keyCode) {
+    return kGlfwToLogicalKey[keyCode];
+  }
+}
+
+/// Helper class that uses GTK-specific key mappings.
+class GtkKeyHelper with KeyHelper {
+  /// This mask is used to check the [RawKeyEventDataLinux.modifiers] field to
+  /// test whether one of the SHIFT modifier keys is pressed.
+  ///
+  /// {@template flutter.services.GtkKeyHelper.modifierShift}
+  /// Use this value if you need to decode the [RawKeyEventDataLinux.modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if a
+  /// modifier is pressed. This is especially true on GTK, since its modifiers
+  /// don't include the effects of the current key event.
+  /// {@endtemplate}
+  static const int modifierShift = 1 << 0;
+
+  /// This mask is used to check the [RawKeyEventDataLinux.modifiers] field to
+  /// test whether the CAPS LOCK modifier key is on.
+  ///
+  /// {@macro flutter.services.GtkKeyHelper.modifierShift}
+  static const int modifierCapsLock = 1 << 1;
+
+  /// This mask is used to check the [RawKeyEventDataLinux.modifiers] field to
+  /// test whether one of the CTRL modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.GtkKeyHelper.modifierShift}
+  static const int modifierControl = 1 << 2;
+
+  /// This mask is used to check the [RawKeyEventDataLinux.modifiers] field to
+  /// test whether the first modifier key is pressed (usually mapped to alt).
+  ///
+  /// {@macro flutter.services.GtkKeyHelper.modifierShift}
+  static const int modifierMod1 = 1 << 3;
+
+  /// This mask is used to check the [RawKeyEventDataLinux.modifiers] field to
+  /// test whether the second modifier key is pressed (assumed to be mapped to
+  /// num lock).
+  ///
+  /// {@macro flutter.services.GtkKeyHelper.modifierShift}
+  static const int modifierMod2 = 1 << 4;
+
+  /// This mask is used to check the [RawKeyEventDataLinux.modifiers] field to
+  /// test whether one of the Meta(SUPER) modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.GtkKeyHelper.modifierShift}
+  static const int modifierMeta = 1 << 26;
+
+  int _mergeModifiers({required int modifiers, required int keyCode, required bool isDown}) {
+    // GTK Key codes for modifier keys.
+    const int shiftLeftKeyCode = 0xffe1;
+    const int shiftRightKeyCode = 0xffe2;
+    const int controlLeftKeyCode = 0xffe3;
+    const int controlRightKeyCode = 0xffe4;
+    const int capsLockKeyCode = 0xffe5;
+    const int shiftLockKeyCode = 0xffe6;
+    const int altLeftKeyCode = 0xffe9;
+    const int altRightKeyCode = 0xffea;
+    const int metaLeftKeyCode = 0xffeb;
+    const int metaRightKeyCode = 0xffec;
+    const int numLockKeyCode = 0xff7f;
+
+    // On GTK, the "modifiers" bitfield is the state as it is BEFORE this event
+    // happened, not AFTER, like every other platform. Consequently, if this is
+    // a key down, then we need to add the correct modifier bits, and if it's a
+    // key up, we need to remove them.
+
+    int modifierChange = 0;
+    switch (keyCode) {
+      case shiftLeftKeyCode:
+      case shiftRightKeyCode:
+        modifierChange = modifierShift;
+        break;
+      case controlLeftKeyCode:
+      case controlRightKeyCode:
+        modifierChange = modifierControl;
+        break;
+      case altLeftKeyCode:
+      case altRightKeyCode:
+        modifierChange = modifierMod1;
+        break;
+      case metaLeftKeyCode:
+      case metaRightKeyCode:
+        modifierChange = modifierMeta;
+        break;
+      case capsLockKeyCode:
+      case shiftLockKeyCode:
+        modifierChange = modifierCapsLock;
+        break;
+      case numLockKeyCode:
+        modifierChange = modifierMod2;
+        break;
+      default:
+        break;
+    }
+
+    return isDown ? modifiers | modifierChange : modifiers & ~modifierChange;
+  }
+
+  @override
+  bool isModifierPressed(ModifierKey key, int modifiers, {KeyboardSide side = KeyboardSide.any, required int keyCode, required bool isDown}) {
+    modifiers = _mergeModifiers(modifiers: modifiers, keyCode: keyCode, isDown: isDown);
+    switch (key) {
+      case ModifierKey.controlModifier:
+        return modifiers & modifierControl != 0;
+      case ModifierKey.shiftModifier:
+        return modifiers & modifierShift != 0;
+      case ModifierKey.altModifier:
+        return modifiers & modifierMod1 != 0;
+      case ModifierKey.metaModifier:
+        return modifiers & modifierMeta != 0;
+      case ModifierKey.capsLockModifier:
+        return modifiers & modifierCapsLock != 0;
+      case ModifierKey.numLockModifier:
+        return modifiers & modifierMod2 != 0;
+      case ModifierKey.functionModifier:
+      case ModifierKey.symbolModifier:
+      case ModifierKey.scrollLockModifier:
+        // These are not used in GTK keyboards.
+        return false;
+    }
+  }
+
+  @override
+  KeyboardSide getModifierSide(ModifierKey key) {
+    // Neither GTK nor X11 provide a distinction between left and right
+    // modifiers, so defaults to KeyboardSide.all.
+    // https://code.woboq.org/qt5/include/X11/X.h.html#_M/ShiftMask
+    return KeyboardSide.all;
+  }
+
+  @override
+  LogicalKeyboardKey? numpadKey(int keyCode) {
+    return kGtkNumpadMap[keyCode];
+  }
+
+  @override
+  LogicalKeyboardKey? logicalKey(int keyCode) {
+    return kGtkToLogicalKey[keyCode];
+  }
+}
diff --git a/lib/src/services/raw_keyboard_macos.dart b/lib/src/services/raw_keyboard_macos.dart
new file mode 100644
index 0000000..d63f079
--- /dev/null
+++ b/lib/src/services/raw_keyboard_macos.dart
@@ -0,0 +1,356 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+import 'keyboard_key.dart';
+import 'keyboard_maps.dart';
+import 'raw_keyboard.dart';
+
+/// Platform-specific key event data for macOS.
+///
+/// This object contains information about key events obtained from macOS's
+/// `NSEvent` interface.
+///
+/// See also:
+///
+///  * [RawKeyboard], which uses this interface to expose key data.
+class RawKeyEventDataMacOs extends RawKeyEventData {
+  /// Creates a key event data structure specific for macOS.
+  ///
+  /// The [characters], [charactersIgnoringModifiers], and [modifiers], arguments
+  /// must not be null.
+  const RawKeyEventDataMacOs({
+    this.characters = '',
+    this.charactersIgnoringModifiers = '',
+    this.keyCode = 0,
+    this.modifiers = 0,
+  }) : assert(characters != null),
+       assert(charactersIgnoringModifiers != null),
+       assert(keyCode != null),
+       assert(modifiers != null);
+
+  /// The Unicode characters associated with a key-up or key-down event.
+  ///
+  /// See also:
+  ///
+  ///  * [Apple's NSEvent documentation](https://developer.apple.com/documentation/appkit/nsevent/1534183-characters?language=objc)
+  final String characters;
+
+  /// The characters generated by a key event as if no modifier key (except for
+  /// Shift) applies.
+  ///
+  /// See also:
+  ///
+  ///  * [Apple's NSEvent documentation](https://developer.apple.com/documentation/appkit/nsevent/1524605-charactersignoringmodifiers?language=objc)
+  final String charactersIgnoringModifiers;
+
+  /// The virtual key code for the keyboard key associated with a key event.
+  ///
+  /// See also:
+  ///
+  ///  * [Apple's NSEvent documentation](https://developer.apple.com/documentation/appkit/nsevent/1534513-keycode?language=objc)
+  final int keyCode;
+
+  /// A mask of the current modifiers using the values in Modifier Flags.
+  ///
+  /// See also:
+  ///
+  ///  * [Apple's NSEvent documentation](https://developer.apple.com/documentation/appkit/nsevent/1535211-modifierflags?language=objc)
+  final int modifiers;
+
+  @override
+  String get keyLabel => charactersIgnoringModifiers;
+
+  @override
+  PhysicalKeyboardKey get physicalKey => kMacOsToPhysicalKey[keyCode] ?? PhysicalKeyboardKey.none;
+
+  @override
+  LogicalKeyboardKey get logicalKey {
+    // Look to see if the keyCode is a printable number pad key, so that a
+    // difference between regular keys (e.g. "=") and the number pad version
+    // (e.g. the "=" on the number pad) can be determined.
+    final LogicalKeyboardKey? numPadKey = kMacOsNumPadMap[keyCode];
+    if (numPadKey != null) {
+      return numPadKey;
+    }
+    // If this key is printable, generate the LogicalKeyboardKey from its
+    // Unicode value. Control keys such as ESC, CRTL, and SHIFT are not
+    // printable. HOME, DEL, arrow keys, and function keys are considered
+    // modifier function keys, which generate invalid Unicode scalar values.
+    if (keyLabel.isNotEmpty &&
+        !LogicalKeyboardKey.isControlCharacter(keyLabel) &&
+        !_isUnprintableKey(keyLabel)) {
+      // Given that charactersIgnoringModifiers can contain a String of
+      // arbitrary length, limit to a maximum of two Unicode scalar values. It
+      // is unlikely that a keyboard would produce a code point bigger than 32
+      // bits, but it is still worth defending against this case.
+      assert(charactersIgnoringModifiers.length <= 2);
+      int codeUnit = charactersIgnoringModifiers.codeUnitAt(0);
+      if (charactersIgnoringModifiers.length == 2) {
+        final int secondCode = charactersIgnoringModifiers.codeUnitAt(1);
+        codeUnit = (codeUnit << 16) | secondCode;
+      }
+
+      final int keyId = LogicalKeyboardKey.unicodePlane | (codeUnit & LogicalKeyboardKey.valueMask);
+      return LogicalKeyboardKey.findKeyByKeyId(keyId) ?? LogicalKeyboardKey(
+        keyId,
+        keyLabel: keyLabel,
+        debugName: kReleaseMode ? null : 'Key ${keyLabel.toUpperCase()}',
+      );
+    }
+
+    // Control keys like "backspace" and movement keys like arrow keys don't
+    // have a printable representation, but are present on the physical
+    // keyboard. Since there is no logical keycode map for macOS (macOS uses the
+    // keycode to reference physical keys), a LogicalKeyboardKey is created with
+    // the physical key's HID usage and debugName. This avoids duplicating the
+    // physical key map.
+    if (physicalKey != PhysicalKeyboardKey.none) {
+      final int keyId = physicalKey.usbHidUsage | LogicalKeyboardKey.hidPlane;
+      return LogicalKeyboardKey.findKeyByKeyId(keyId) ?? LogicalKeyboardKey(
+        keyId,
+        keyLabel: physicalKey.debugName ?? '',
+        debugName: physicalKey.debugName,
+      );
+    }
+
+    // This is a non-printable key that is unrecognized, so a new code is minted
+    // with the autogenerated bit set.
+    const int macOsKeyIdPlane = 0x00500000000;
+
+    return LogicalKeyboardKey(
+      macOsKeyIdPlane | keyCode | LogicalKeyboardKey.autogeneratedMask,
+      debugName: kReleaseMode ? null : 'Unknown macOS key code $keyCode',
+    );
+  }
+
+  bool _isLeftRightModifierPressed(KeyboardSide side, int anyMask, int leftMask, int rightMask) {
+    if (modifiers & anyMask == 0) {
+      return false;
+    }
+    // If only the "anyMask" bit is set, then we respond true for requests of
+    // whether either left or right is pressed. Handles the case where macOS
+    // supplies just the "either" modifier flag, but not the left/right flag.
+    // (e.g. modifierShift but not modifierLeftShift).
+    final bool anyOnly = modifiers & (leftMask | rightMask | anyMask) == anyMask;
+    switch (side) {
+      case KeyboardSide.any:
+        return true;
+      case KeyboardSide.all:
+        return modifiers & leftMask != 0 && modifiers & rightMask != 0 || anyOnly;
+      case KeyboardSide.left:
+        return modifiers & leftMask != 0 || anyOnly;
+      case KeyboardSide.right:
+        return modifiers & rightMask != 0 || anyOnly;
+    }
+  }
+
+  @override
+  bool isModifierPressed(ModifierKey key, {KeyboardSide side = KeyboardSide.any}) {
+    final int independentModifier = modifiers & deviceIndependentMask;
+    final bool result;
+    switch (key) {
+      case ModifierKey.controlModifier:
+        result = _isLeftRightModifierPressed(side, independentModifier & modifierControl, modifierLeftControl, modifierRightControl);
+        break;
+      case ModifierKey.shiftModifier:
+        result = _isLeftRightModifierPressed(side, independentModifier & modifierShift, modifierLeftShift, modifierRightShift);
+        break;
+      case ModifierKey.altModifier:
+        result = _isLeftRightModifierPressed(side, independentModifier & modifierOption, modifierLeftOption, modifierRightOption);
+        break;
+      case ModifierKey.metaModifier:
+        result = _isLeftRightModifierPressed(side, independentModifier & modifierCommand, modifierLeftCommand, modifierRightCommand);
+        break;
+      case ModifierKey.capsLockModifier:
+        result = independentModifier & modifierCapsLock != 0;
+        break;
+    // On macOS, the function modifier bit is set for any function key, like F1,
+    // F2, etc., but the meaning of ModifierKey.modifierFunction in Flutter is
+    // that of the Fn modifier key, so there's no good way to emulate that on
+    // macOS.
+      case ModifierKey.functionModifier:
+      case ModifierKey.numLockModifier:
+      case ModifierKey.symbolModifier:
+      case ModifierKey.scrollLockModifier:
+        // These modifier masks are not used in macOS keyboards.
+        result = false;
+        break;
+    }
+    assert(!result || getModifierSide(key) != null, "$runtimeType thinks that a modifier is pressed, but can't figure out what side it's on.");
+    return result;
+  }
+
+  @override
+  KeyboardSide? getModifierSide(ModifierKey key) {
+    KeyboardSide? findSide(int anyMask, int leftMask, int rightMask) {
+      final int combinedMask = leftMask | rightMask;
+      final int combined = modifiers & combinedMask;
+      if (combined == leftMask) {
+        return KeyboardSide.left;
+      } else if (combined == rightMask) {
+        return KeyboardSide.right;
+      } else if (combined == combinedMask || modifiers & (combinedMask | anyMask) == anyMask) {
+        // Handles the case where macOS supplies just the "either" modifier
+        // flag, but not the left/right flag. (e.g. modifierShift but not
+        // modifierLeftShift), or if left and right flags are provided, but not
+        // the "either" modifier flag.
+        return KeyboardSide.all;
+      }
+      return null;
+    }
+
+    switch (key) {
+      case ModifierKey.controlModifier:
+        return findSide(modifierControl, modifierLeftControl, modifierRightControl);
+      case ModifierKey.shiftModifier:
+        return findSide(modifierShift, modifierLeftShift, modifierRightShift);
+      case ModifierKey.altModifier:
+        return findSide(modifierOption, modifierLeftOption, modifierRightOption);
+      case ModifierKey.metaModifier:
+        return findSide(modifierCommand, modifierLeftCommand, modifierRightCommand);
+      case ModifierKey.capsLockModifier:
+      case ModifierKey.numLockModifier:
+      case ModifierKey.scrollLockModifier:
+      case ModifierKey.functionModifier:
+      case ModifierKey.symbolModifier:
+        return KeyboardSide.all;
+    }
+  }
+
+  /// Returns true if the given label represents an unprintable key.
+  ///
+  /// Examples of unprintable keys are "NSUpArrowFunctionKey = 0xF700"
+  /// or "NSHomeFunctionKey = 0xF729".
+  ///
+  /// See <https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc> for more
+  /// information.
+  ///
+  /// Used by [RawKeyEvent] subclasses to help construct IDs.
+  static bool _isUnprintableKey(String label) {
+    if (label.length != 1) {
+      return false;
+    }
+    final int codeUnit = label.codeUnitAt(0);
+    return codeUnit >= 0xF700 && codeUnit <= 0xF8FF;
+  }
+
+  // Modifier key masks. See Apple's NSEvent documentation
+  // https://developer.apple.com/documentation/appkit/nseventmodifierflags?language=objc
+  // https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-86/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h.auto.html
+
+  /// This mask is used to check the [modifiers] field to test whether the CAPS
+  /// LOCK modifier key is on.
+  ///
+  /// {@template flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  /// {@endtemplate}
+  static const int modifierCapsLock = 0x10000;
+
+  /// This mask is used to check the [modifiers] field to test whether one of the
+  /// SHIFT modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierShift = 0x20000;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// SHIFT modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierLeftShift = 0x02;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// SHIFT modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierRightShift = 0x04;
+
+  /// This mask is used to check the [modifiers] field to test whether one of the
+  /// CTRL modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierControl = 0x40000;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// CTRL modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierLeftControl = 0x01;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// CTRL modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierRightControl = 0x2000;
+
+  /// This mask is used to check the [modifiers] field to test whether one of the
+  /// ALT modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierOption = 0x80000;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// ALT modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierLeftOption = 0x20;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// ALT modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierRightOption = 0x40;
+
+  /// This mask is used to check the [modifiers] field to test whether one of the
+  /// CMD modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierCommand = 0x100000;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// CMD modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierLeftCommand = 0x08;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// CMD modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierRightCommand = 0x10;
+
+  /// This mask is used to check the [modifiers] field to test whether any key in
+  /// the numeric keypad is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierNumericPad = 0x200000;
+
+  /// This mask is used to check the [modifiers] field to test whether the
+  /// HELP modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierHelp = 0x400000;
+
+  /// This mask is used to check the [modifiers] field to test whether one of the
+  /// FUNCTION modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataMacOs.modifierCapsLock}
+  static const int modifierFunction = 0x800000;
+
+  /// Used to retrieve only the device-independent modifier flags, allowing
+  /// applications to mask off the device-dependent modifier flags, including
+  /// event coalescing information.
+  static const int deviceIndependentMask = 0xffff0000;
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'RawKeyEventDataMacOs')}(keyLabel: $keyLabel, keyCode: $keyCode, characters: $characters,'
+        ' unmodifiedCharacters: $charactersIgnoringModifiers, modifiers: $modifiers, '
+        'modifiers down: $modifiersPressed)';
+  }
+}
diff --git a/lib/src/services/raw_keyboard_web.dart b/lib/src/services/raw_keyboard_web.dart
new file mode 100644
index 0000000..699cac0
--- /dev/null
+++ b/lib/src/services/raw_keyboard_web.dart
@@ -0,0 +1,198 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+import 'keyboard_key.dart';
+import 'keyboard_maps.dart';
+import 'raw_keyboard.dart';
+
+/// Platform-specific key event data for Web.
+///
+/// See also:
+///
+///  * [RawKeyboard], which uses this interface to expose key data.
+@immutable
+class RawKeyEventDataWeb extends RawKeyEventData {
+  /// Creates a key event data structure specific for Web.
+  ///
+  /// The [code] and [metaState] arguments must not be null.
+  const RawKeyEventDataWeb({
+    required this.code,
+    required this.key,
+    this.metaState = modifierNone,
+  })  : assert(code != null),
+        assert(metaState != null);
+
+  /// The `KeyboardEvent.code` corresponding to this event.
+  ///
+  /// See <https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code>
+  /// for more information.
+  final String code;
+
+  /// The `KeyboardEvent.key` corresponding to this event.
+  ///
+  /// See <https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key>
+  /// for more information.
+  final String key;
+
+  /// The modifiers that were present when the key event occurred.
+  ///
+  /// See `lib/src/engine/keyboard.dart` in the web engine for the numerical
+  /// values of the `metaState`. These constants are also replicated as static
+  /// constants in this class.
+  ///
+  /// See also:
+  ///
+  ///  * [modifiersPressed], which returns a Map of currently pressed modifiers
+  ///    and their keyboard side.
+  ///  * [isModifierPressed], to see if a specific modifier is pressed.
+  ///  * [isControlPressed], to see if a CTRL key is pressed.
+  ///  * [isShiftPressed], to see if a SHIFT key is pressed.
+  ///  * [isAltPressed], to see if an ALT key is pressed.
+  ///  * [isMetaPressed], to see if a META key is pressed.
+  final int metaState;
+
+  @override
+  String get keyLabel => key == 'Unidentified' ? '' : key;
+
+  @override
+  PhysicalKeyboardKey get physicalKey {
+    return kWebToPhysicalKey[code] ?? PhysicalKeyboardKey.none;
+  }
+
+  @override
+  LogicalKeyboardKey get logicalKey {
+    // Look to see if the keyCode is a printable number pad key, so that a
+    // difference between regular keys (e.g. ".") and the number pad version
+    // (e.g. the "." on the number pad) can be determined.
+    final LogicalKeyboardKey? numPadKey = kWebNumPadMap[code];
+    if (numPadKey != null) {
+      return numPadKey;
+    }
+
+    // Look to see if the [code] is one we know about and have a mapping for.
+    final LogicalKeyboardKey? newKey = kWebToLogicalKey[code];
+    if (newKey != null) {
+      return newKey;
+    }
+
+    // This is a non-printable key that we don't know about, so we mint a new
+    // code with the autogenerated bit set.
+    const int webKeyIdPlane = 0x00800000000;
+    return LogicalKeyboardKey(
+      webKeyIdPlane | code.hashCode | LogicalKeyboardKey.autogeneratedMask,
+      debugName: kReleaseMode ? null : 'Unknown Web code "$code"',
+    );
+  }
+
+  @override
+  bool isModifierPressed(
+    ModifierKey key, {
+    KeyboardSide side = KeyboardSide.any,
+  }) {
+    switch (key) {
+      case ModifierKey.controlModifier:
+        return metaState & modifierControl != 0;
+      case ModifierKey.shiftModifier:
+        return metaState & modifierShift != 0;
+      case ModifierKey.altModifier:
+        return metaState & modifierAlt != 0;
+      case ModifierKey.metaModifier:
+        return metaState & modifierMeta != 0;
+      case ModifierKey.numLockModifier:
+        return metaState & modifierNumLock != 0;
+      case ModifierKey.capsLockModifier:
+        return metaState & modifierCapsLock != 0;
+      case ModifierKey.scrollLockModifier:
+        return metaState & modifierScrollLock != 0;
+      case ModifierKey.functionModifier:
+      case ModifierKey.symbolModifier:
+        // On Web, the browser doesn't report the state of the FN and SYM modifiers.
+        return false;
+    }
+  }
+
+  @override
+  KeyboardSide getModifierSide(ModifierKey key) {
+    // On Web, we don't distinguish the sides of modifier keys. Both left shift
+    // and right shift, for example, are reported as the "Shift" modifier.
+    //
+    // See <https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState>
+    // for more information.
+    return KeyboardSide.all;
+  }
+
+  // Modifier key masks.
+
+  /// No modifier keys are pressed in the [metaState] field.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierNone = 0;
+
+  /// This mask is used to check the [metaState] field to test whether one of
+  /// the SHIFT modifier keys is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierShift = 0x01;
+
+  /// This mask is used to check the [metaState] field to test whether one of
+  /// the ALT modifier keys is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierAlt = 0x02;
+
+  /// This mask is used to check the [metaState] field to test whether one of
+  /// the CTRL modifier keys is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierControl = 0x04;
+
+  /// This mask is used to check the [metaState] field to test whether one of
+  /// the META modifier keys is pressed.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierMeta = 0x08;
+
+  /// This mask is used to check the [metaState] field to test whether the NUM
+  /// LOCK modifier key is on.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierNumLock = 0x10;
+
+  /// This mask is used to check the [metaState] field to test whether the CAPS
+  /// LOCK modifier key is on.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierCapsLock = 0x20;
+
+  /// This mask is used to check the [metaState] field to test whether the
+  /// SCROLL LOCK modifier key is on.
+  ///
+  /// Use this value if you need to decode the [metaState] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  static const int modifierScrollLock = 0x40;
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'RawKeyEventDataWeb')}(keyLabel: $keyLabel, code: $code, '
+        'metaState: $metaState, modifiers down: $modifiersPressed)';
+  }
+}
diff --git a/lib/src/services/raw_keyboard_windows.dart b/lib/src/services/raw_keyboard_windows.dart
new file mode 100644
index 0000000..9168c45
--- /dev/null
+++ b/lib/src/services/raw_keyboard_windows.dart
@@ -0,0 +1,289 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/foundation.dart';
+
+import 'keyboard_key.dart';
+import 'keyboard_maps.dart';
+import 'raw_keyboard.dart';
+
+/// Platform-specific key event data for Windows.
+///
+/// This object contains information about key events obtained from Windows's
+/// win32 API.
+///
+/// See also:
+///
+///  * [RawKeyboard], which uses this interface to expose key data.
+class RawKeyEventDataWindows extends RawKeyEventData {
+  /// Creates a key event data structure specific for Windows.
+  ///
+  /// The [keyCode], [scanCode], [characterCodePoint], and [modifiers], arguments
+  /// must not be null.
+  const RawKeyEventDataWindows({
+    this.keyCode = 0,
+    this.scanCode = 0,
+    this.characterCodePoint = 0,
+    this.modifiers = 0,
+  }) : assert(keyCode != null),
+       assert(scanCode != null),
+       assert(characterCodePoint != null),
+       assert(modifiers != null);
+
+  /// The hardware key code corresponding to this key event.
+  ///
+  /// This is the physical key that was pressed, not the Unicode character.
+  /// See [characterCodePoint] for the Unicode character.
+  final int keyCode;
+
+  /// The hardware scan code id corresponding to this key event.
+  ///
+  /// These values are not reliable and vary from device to device, so this
+  /// information is mainly useful for debugging.
+  final int scanCode;
+
+  /// The Unicode code point represented by the key event, if any.
+  ///
+  /// If there is no Unicode code point, this value is zero.
+  final int characterCodePoint;
+
+  /// A mask of the current modifiers. The modifier values must be in sync with
+  /// the ones defined in https://github.com/flutter/engine/blob/master/shell/platform/windows/key_event_handler.cc
+  final int modifiers;
+
+  @override
+  String get keyLabel => characterCodePoint == 0 ? '' : String.fromCharCode(characterCodePoint);
+
+  @override
+  PhysicalKeyboardKey get physicalKey => kWindowsToPhysicalKey[scanCode] ?? PhysicalKeyboardKey.none;
+
+ @override
+  LogicalKeyboardKey get logicalKey {
+    // Look to see if the keyCode is a printable number pad key, so that a
+    // difference between regular keys (e.g. "=") and the number pad version
+    // (e.g. the "=" on the number pad) can be determined.
+    final LogicalKeyboardKey? numPadKey = kWindowsNumPadMap[keyCode];
+    if (numPadKey != null) {
+      return numPadKey;
+    }
+
+    // If it has a non-control-character label, then either return the existing
+    // constant, or construct a new Unicode-based key from it. Don't mark it as
+    // autogenerated, since the label uniquely identifies an ID from the Unicode
+    // plane.
+    if (keyLabel.isNotEmpty && !LogicalKeyboardKey.isControlCharacter(keyLabel)) {
+      final int keyId = LogicalKeyboardKey.unicodePlane | (characterCodePoint & LogicalKeyboardKey.valueMask);
+      return LogicalKeyboardKey.findKeyByKeyId(keyId) ?? LogicalKeyboardKey(
+        keyId,
+        keyLabel: keyLabel,
+        debugName: kReleaseMode ? null : 'Key ${keyLabel.toUpperCase()}',
+      );
+    }
+    // Look to see if the keyCode is one we know about and have a mapping for.
+    LogicalKeyboardKey? newKey = kWindowsToLogicalKey[keyCode];
+    if (newKey != null) {
+      return newKey;
+    }
+
+    // This is a non-printable key that we don't know about, so we mint a new
+    // code with the autogenerated bit set.
+    const int windowsKeyIdPlane = 0x00700000000;
+    newKey ??= LogicalKeyboardKey(
+      windowsKeyIdPlane | keyCode | LogicalKeyboardKey.autogeneratedMask,
+      debugName: kReleaseMode ? null : 'Unknown Windows key code $keyCode',
+    );
+    return newKey;
+  }
+
+  bool _isLeftRightModifierPressed(KeyboardSide side, int anyMask, int leftMask, int rightMask) {
+    if (modifiers & anyMask == 0 &&
+        modifiers & leftMask == 0 &&
+        modifiers & rightMask == 0) {
+      return false;
+    }
+    // If only the "anyMask" bit is set, then we respond true for requests of
+    // whether either left or right is pressed. Handles the case where Windows
+    // supplies just the "either" modifier flag, but not the left/right flag.
+    // (e.g. modifierShift but not modifierLeftShift).
+    final bool anyOnly = modifiers & (leftMask | rightMask | anyMask) == anyMask;
+    switch (side) {
+      case KeyboardSide.any:
+        return true;
+      case KeyboardSide.all:
+        return modifiers & leftMask != 0 && modifiers & rightMask != 0 || anyOnly;
+      case KeyboardSide.left:
+        return modifiers & leftMask != 0 || anyOnly;
+      case KeyboardSide.right:
+        return modifiers & rightMask != 0 || anyOnly;
+    }
+  }
+
+  @override
+  bool isModifierPressed(ModifierKey key, {KeyboardSide side = KeyboardSide.any}) {
+    final bool result;
+    switch (key) {
+      case ModifierKey.controlModifier:
+        result = _isLeftRightModifierPressed(side, modifierControl, modifierLeftControl, modifierRightControl);
+        break;
+      case ModifierKey.shiftModifier:
+        result = _isLeftRightModifierPressed(side, modifierShift, modifierLeftShift, modifierRightShift);
+        break;
+      case ModifierKey.altModifier:
+        result = _isLeftRightModifierPressed(side, modifierAlt, modifierLeftAlt, modifierRightAlt);
+        break;
+      case ModifierKey.metaModifier:
+        // Windows does not provide an "any" key for win key press.
+        result = _isLeftRightModifierPressed(side, modifierLeftMeta | modifierRightMeta , modifierLeftMeta, modifierRightMeta);
+        break;
+      case ModifierKey.capsLockModifier:
+        result = modifiers & modifierCaps != 0;
+        break;
+      case ModifierKey.scrollLockModifier:
+        result = modifiers & modifierScrollLock != 0;
+        break;
+      case ModifierKey.numLockModifier:
+        result = modifiers & modifierNumLock != 0;
+        break;
+      // The OS does not expose the Fn key to the drivers, it doesn't generate a key message.
+      case ModifierKey.functionModifier:
+      case ModifierKey.symbolModifier:
+        // These modifier masks are not used in Windows keyboards.
+        result = false;
+        break;
+    }
+    assert(!result || getModifierSide(key) != null, "$runtimeType thinks that a modifier is pressed, but can't figure out what side it's on.");
+    return result;
+  }
+
+
+  @override
+  KeyboardSide? getModifierSide(ModifierKey key) {
+    KeyboardSide? findSide(int leftMask, int rightMask, int anyMask) {
+      final int combinedMask = leftMask | rightMask;
+      final int combined = modifiers & combinedMask;
+      if (combined == leftMask) {
+        return KeyboardSide.left;
+      } else if (combined == rightMask) {
+        return KeyboardSide.right;
+      } else if (combined == combinedMask || modifiers & (combinedMask | anyMask) == anyMask) {
+        // Handles the case where Windows supplies just the "either" modifier
+        // flag, but not the left/right flag. (e.g. modifierShift but not
+        // modifierLeftShift).
+        return KeyboardSide.all;
+      }
+      return null;
+    }
+
+    switch (key) {
+      case ModifierKey.controlModifier:
+        return findSide(modifierLeftControl, modifierRightControl, modifierControl);
+      case ModifierKey.shiftModifier:
+        return findSide(modifierLeftShift, modifierRightShift, modifierShift);
+      case ModifierKey.altModifier:
+        return findSide(modifierLeftAlt, modifierRightAlt, modifierAlt);
+      case ModifierKey.metaModifier:
+        return findSide(modifierLeftMeta, modifierRightMeta, 0);
+      case ModifierKey.capsLockModifier:
+      case ModifierKey.numLockModifier:
+      case ModifierKey.scrollLockModifier:
+      case ModifierKey.functionModifier:
+      case ModifierKey.symbolModifier:
+        return KeyboardSide.all;
+    }
+  }
+
+  // These are not the values defined by the Windows header for each modifier. Since they
+  // can't be packaged into a single int, we are re-defining them here to reduce the size
+  // of the message from the embedder. Embedders should map these values to the native key codes.
+  // Keep this in sync with https://github.com/flutter/engine/blob/master/shell/platform/windows/key_event_handler.cc
+
+  /// This mask is used to check the [modifiers] field to test whether one of the
+  /// SHIFT modifier keys is pressed.
+  ///
+  /// {@template flutter.services.RawKeyEventDataWindows.modifierShift}
+  /// Use this value if you need to decode the [modifiers] field yourself, but
+  /// it's much easier to use [isModifierPressed] if you just want to know if
+  /// a modifier is pressed.
+  /// {@endtemplate}
+  static const int modifierShift = 1 << 0;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// SHIFT modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataWindows.modifierShift}
+  static const int modifierLeftShift = 1 << 1;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// SHIFT modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataWindows.modifierShift}
+  static const int modifierRightShift = 1 << 2;
+
+  /// This mask is used to check the [modifiers] field to test whether one of the
+  /// CTRL modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataWindows.modifierShift}
+  static const int modifierControl = 1 << 3;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// CTRL modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataWindows.modifierShift}
+  static const int modifierLeftControl = 1 << 4;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// CTRL modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataWindows.modifierShift}
+  static const int modifierRightControl = 1 << 5;
+
+  /// This mask is used to check the [modifiers] field to test whether one of the
+  /// ALT modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataWindows.modifierShift}
+  static const int modifierAlt = 1 << 6;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// ALT modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataWindows.modifierShift}
+  static const int modifierLeftAlt = 1 << 7;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// ALT modifier key is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataWindows.modifierShift}
+  static const int modifierRightAlt = 1 << 8;
+
+  /// This mask is used to check the [modifiers] field to test whether the left
+  /// WIN modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataWindows.modifierShift}
+  static const int modifierLeftMeta = 1 << 9;
+
+  /// This mask is used to check the [modifiers] field to test whether the right
+  /// WIN modifier keys is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataWindows.modifierShift}
+  static const int modifierRightMeta = 1 << 10;
+
+  /// This mask is used to check the [modifiers] field to test whether the CAPS LOCK key
+  /// is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataWindows.modifierShift}
+  static const int modifierCaps = 1 << 11;
+
+  /// This mask is used to check the [modifiers] field to test whether the NUM LOCK key
+  /// is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataWindows.modifierShift}
+  static const int modifierNumLock = 1 << 12;
+
+    /// This mask is used to check the [modifiers] field to test whether the SCROLL LOCK key
+  /// is pressed.
+  ///
+  /// {@macro flutter.services.RawKeyEventDataWindows.modifierShift}
+  static const int modifierScrollLock = 1 << 13;
+}
diff --git a/lib/src/services/restoration.dart b/lib/src/services/restoration.dart
new file mode 100644
index 0000000..8e0c711
--- /dev/null
+++ b/lib/src/services/restoration.dart
@@ -0,0 +1,963 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+
+import 'message_codec.dart';
+import 'message_codecs.dart';
+import 'system_channels.dart';
+
+typedef _BucketVisitor = void Function(RestorationBucket bucket);
+
+/// Manages the restoration data in the framework and synchronizes it with the
+/// engine.
+///
+/// Restoration data can be serialized out and - at a later point in time - be
+/// used to restore the application to the previous state described by the
+/// serialized data. Mobile operating systems use the concept of state
+/// restoration to provide the illusion that apps continue to run in the
+/// background forever: after an app has been backgrounded, the user can always
+/// return to it and find it in the same state. In practice, the operating
+/// system may, however, terminate the app to free resources for other apps
+/// running in the foreground. Before that happens, the app gets a chance to
+/// serialize out its restoration data. When the user navigates back to the
+/// backgrounded app, it is restarted and the serialized restoration data is
+/// provided to it again. Ideally, the app will use that data to restore itself
+/// to the same state it was in when the user backgrounded the app.
+///
+/// In Flutter, restoration data is organized in a tree of [RestorationBucket]s
+/// which is rooted in the [rootBucket]. All information that the application
+/// needs to restore its current state must be stored in a bucket in this
+/// hierarchy. To store data in the hierarchy, entities (e.g. [Widget]s) must
+/// claim ownership of a child bucket from a parent bucket (which may be the
+/// [rootBucket] provided by this [RestorationManager]). The owner of a bucket
+/// may store arbitrary values in the bucket as long as they can be serialized
+/// with the [StandardMessageCodec]. The values are stored in the bucket under a
+/// given restoration ID as key. A restoration ID is a [String] that must be
+/// unique within a given bucket. To access the stored value again during state
+/// restoration, the same restoration ID must be provided again. The owner of
+/// the bucket may also make the bucket available to other entities so that they
+/// can claim child buckets from it for their own restoration needs. Within a
+/// bucket, child buckets are also identified by unique restoration IDs. The
+/// restoration ID must be provided when claiming a child bucket.
+///
+/// When restoration data is provided to the [RestorationManager] (e.g. after
+/// the application relaunched when foregrounded again), the bucket hierarchy
+/// with all the data stored in it is restored. Entities can retrieve the data
+/// again by using the same restoration IDs that they originally used to store
+/// the data.
+///
+/// In addition to providing restoration data when the app is launched,
+/// restoration data may also be provided to a running app to restore it to a
+/// previous state (e.g. when the user hits the back/forward button in the web
+/// browser). When this happens, the [RestorationManager] notifies its listeners
+/// (added via [addListener]) that a new [rootBucket] is available. In response
+/// to the notification, listeners must stop using the old bucket and restore
+/// their state from the information in the new [rootBucket].
+///
+/// Same platforms restrict the size of the restoration data. Therefore, the
+/// data stored in the buckets should be as small as possible while still
+/// allowing the app to restore its current state from it. Data that can be
+/// retrieved from other services (e.g. a database or a web server) should not
+/// be included in the restoration data. Instead, a small identifier (e.g. a
+/// UUID, database record number, or resource locator) should be stored that can
+/// be used to retrieve the data again from its original source during state
+/// restoration.
+///
+/// The [RestorationManager] sends a serialized version of the bucket hierarchy
+/// over to the engine at the end of a frame in which the data in the hierarchy
+/// or its shape has changed. The engine caches the data until the operating
+/// system needs it. The application is responsible for keeping the data in the
+/// bucket always up-to-date to reflect its current state.
+///
+/// ## Discussion
+///
+/// Due to Flutter's threading model and restrictions in the APIs of the
+/// platforms Flutter runs on, restoration data must be stored in the buckets
+/// proactively as described above. When the operating system asks for the
+/// restoration data, it will do so on the platform thread expecting a
+/// synchronous response. To avoid the risk of deadlocks, the platform thread
+/// cannot block and call into the UI thread (where the dart code is running) to
+/// retrieve the restoration data. For this reason, the [RestorationManager]
+/// always sends the latest copy of the restoration data from the UI thread over
+/// to the platform thread whenever it changes. That way, the restoration data
+/// is always ready to go on the platform thread when the operating system needs
+/// it.
+///
+/// See also:
+///
+///  * [ServicesBinding.restorationManager], which holds the singleton instance
+///    of the [RestorationManager] for the currently running application.
+///  * [RestorationBucket], which make up the restoration data hierarchy.
+///  * [RestorationMixin], which uses [RestorationBucket]s behind the scenes
+///    to make [State] objects of [StatefulWidget]s restorable.
+class RestorationManager extends ChangeNotifier {
+  /// Construct the restoration manager and set up the communications channels
+  /// with the engine to get restoration messages (by calling [initChannels]).
+  RestorationManager() {
+    initChannels();
+  }
+
+  /// Sets up the method call handler for [SystemChannels.restoration].
+  ///
+  /// This is called by the constructor to configure the communications channel
+  /// with the Flutter engine to get restoration messages.
+  ///
+  /// Subclasses (especially in tests) can override this to avoid setting up
+  /// that communications channel, or to set it up differently, as necessary.
+  @protected
+  void initChannels() {
+    assert(!SystemChannels.restoration.checkMethodCallHandler(_methodHandler));
+    SystemChannels.restoration.setMethodCallHandler(_methodHandler);
+  }
+
+  /// The root of the [RestorationBucket] hierarchy containing the restoration
+  /// data.
+  ///
+  /// Child buckets can be claimed from this bucket via
+  /// [RestorationBucket.claimChild]. If the [RestorationManager] has been asked
+  /// to restore the application to a previous state, these buckets will contain
+  /// the previously stored data. Otherwise the root bucket (and all children
+  /// claimed from it) will be empty.
+  ///
+  /// The [RestorationManager] informs its listeners (added via [addListener])
+  /// when the value returned by this getter changes. This happens when new
+  /// restoration data has been provided to the [RestorationManager] to restore
+  /// the application to a different state. In response to the notification,
+  /// listeners must stop using the old root bucket and obtain the new one via
+  /// this getter ([rootBucket] will have been updated to return the new bucket
+  /// just before the listeners are notified).
+  ///
+  /// The restoration data describing the current bucket hierarchy is retrieved
+  /// asynchronously from the engine the first time the root bucket is accessed
+  /// via this getter. After the data has been copied over from the engine, this
+  /// getter will return a [SynchronousFuture], that immediately resolves to the
+  /// root [RestorationBucket].
+  ///
+  /// The returned [Future] may resolve to null if state restoration is
+  /// currently turned off.
+  ///
+  /// See also:
+  ///
+  ///  * [RootRestorationScope], which makes the root bucket available in the
+  ///    [Widget] tree.
+  Future<RestorationBucket?> get rootBucket {
+    if (_rootBucketIsValid) {
+      return SynchronousFuture<RestorationBucket?>(_rootBucket);
+    }
+    if (_pendingRootBucket == null) {
+      _pendingRootBucket = Completer<RestorationBucket?>();
+      _getRootBucketFromEngine();
+    }
+    return _pendingRootBucket!.future;
+  }
+  RestorationBucket? _rootBucket; // May be null to indicate that restoration is turned off.
+  Completer<RestorationBucket?>? _pendingRootBucket;
+  bool _rootBucketIsValid = false;
+
+  /// Returns true for the frame after [rootBucket] has been replaced with a
+  /// new non-null bucket.
+  ///
+  /// When true, entities should forget their current state and restore
+  /// their state according to the information in the new [rootBucket].
+  ///
+  /// The [RestorationManager] informs its listeners (added via [addListener])
+  /// when this flag changes from false to true.
+  bool get isReplacing => _isReplacing;
+  bool _isReplacing = false;
+
+  Future<void> _getRootBucketFromEngine() async {
+    final Map<dynamic, dynamic>? config = await SystemChannels.restoration.invokeMethod<Map<dynamic, dynamic>>('get');
+    if (_pendingRootBucket == null) {
+      // The restoration data was obtained via other means (e.g. by calling
+      // [handleRestorationDataUpdate] while the request to the engine was
+      // outstanding. Ignore the engine's response.
+      return;
+    }
+    assert(_rootBucket == null);
+    _parseAndHandleRestorationUpdateFromEngine(config);
+  }
+
+  void _parseAndHandleRestorationUpdateFromEngine(Map<dynamic, dynamic>? update) {
+    handleRestorationUpdateFromEngine(
+      enabled: update != null && update['enabled'] as bool,
+      data: update == null ? null : update['data'] as Uint8List?,
+    );
+  }
+
+  /// Called by the [RestorationManager] on itself to parse the restoration
+  /// information obtained from the engine.
+  ///
+  /// The `enabled` parameter indicates whether the engine wants to receive
+  /// restoration data. When `enabled` is false, state restoration is turned
+  /// off and the [rootBucket] is set to null. When `enabled` is true, the
+  /// provided restoration `data` will be parsed into a new [rootBucket]. If
+  /// `data` is null, an empty [rootBucket] will be instantiated.
+  ///
+  /// Subclasses in test frameworks may call this method at any time to inject
+  /// restoration data (obtained e.g. by overriding [sendToEngine]) into the
+  /// [RestorationManager]. When the method is called before the [rootBucket] is
+  /// accessed, [rootBucket] will complete synchronously the next time it is
+  /// called.
+  @protected
+  void handleRestorationUpdateFromEngine({required bool enabled, required Uint8List? data}) {
+    assert(enabled != null);
+    assert(enabled || data == null);
+
+    _isReplacing = _rootBucketIsValid && enabled;
+    if (_isReplacing) {
+      SchedulerBinding.instance!.addPostFrameCallback((Duration _) {
+        _isReplacing = false;
+      });
+    }
+
+    final RestorationBucket? oldRoot = _rootBucket;
+    _rootBucket = enabled
+        ? RestorationBucket.root(manager: this, rawData: _decodeRestorationData(data))
+        : null;
+    _rootBucketIsValid = true;
+    assert(_pendingRootBucket == null || !_pendingRootBucket!.isCompleted);
+    _pendingRootBucket?.complete(_rootBucket);
+    _pendingRootBucket = null;
+
+    if (_rootBucket != oldRoot) {
+      notifyListeners();
+      oldRoot?.dispose();
+    }
+  }
+
+  /// Called by the [RestorationManager] on itself to send the provided
+  /// encoded restoration data to the engine.
+  ///
+  /// The `encodedData` describes the entire bucket hierarchy that makes up the
+  /// current restoration data.
+  ///
+  /// Subclasses in test frameworks may override this method to capture the
+  /// restoration data that would have been send to the engine. The captured
+  /// data can be re-injected into the [RestorationManager] via the
+  /// [handleRestorationUpdateFromEngine] method to restore the state described
+  /// by the data.
+  @protected
+  Future<void> sendToEngine(Uint8List encodedData) {
+    assert(encodedData != null);
+    return SystemChannels.restoration.invokeMethod<void>(
+      'put',
+      encodedData,
+    );
+  }
+
+  Future<dynamic> _methodHandler(MethodCall call) async {
+    switch (call.method) {
+      case 'push':
+        _parseAndHandleRestorationUpdateFromEngine(call.arguments as Map<dynamic, dynamic>);
+        break;
+      default:
+        throw UnimplementedError("${call.method} was invoked but isn't implemented by $runtimeType");
+    }
+  }
+
+  Map<dynamic, dynamic>? _decodeRestorationData(Uint8List? data) {
+    if (data == null) {
+      return null;
+    }
+    final ByteData encoded = data.buffer.asByteData(data.offsetInBytes, data.lengthInBytes);
+    return const StandardMessageCodec().decodeMessage(encoded) as Map<dynamic, dynamic>;
+  }
+
+  Uint8List _encodeRestorationData(Map<dynamic, dynamic> data) {
+    final ByteData encoded = const StandardMessageCodec().encodeMessage(data)!;
+    return encoded.buffer.asUint8List(encoded.offsetInBytes, encoded.lengthInBytes);
+  }
+
+  bool _debugDoingUpdate = false;
+  bool _serializationScheduled = false;
+
+  final Set<RestorationBucket> _bucketsNeedingSerialization = <RestorationBucket>{};
+
+  /// Called by a [RestorationBucket] to request serialization for that bucket.
+  ///
+  /// This method is called by a bucket in the hierarchy whenever the data
+  /// in it or the shape of the hierarchy has changed.
+  ///
+  /// Calling this is a no-op when the bucket is already scheduled for
+  /// serialization.
+  ///
+  /// It is exposed to allow testing of [RestorationBucket]s in isolation.
+  @protected
+  @visibleForTesting
+  void scheduleSerializationFor(RestorationBucket bucket) {
+    assert(bucket != null);
+    assert(bucket._manager == this);
+    assert(!_debugDoingUpdate);
+    _bucketsNeedingSerialization.add(bucket);
+    if (!_serializationScheduled) {
+      _serializationScheduled = true;
+      SchedulerBinding.instance!.addPostFrameCallback((Duration _) => _doSerialization());
+    }
+  }
+
+  /// Called by a [RestorationBucket] to unschedule a request for serialization.
+  ///
+  /// This method is called by a bucket in the hierarchy whenever it no longer
+  /// needs to be serialized (e.g. because the bucket got disposed).
+  ///
+  /// It is safe to call this even when the bucket wasn't scheduled for
+  /// serialization before.
+  ///
+  /// It is exposed to allow testing of [RestorationBucket]s in isolation.
+  @protected
+  @visibleForTesting
+  void unscheduleSerializationFor(RestorationBucket bucket) {
+    assert(bucket != null);
+    assert(bucket._manager == this);
+    assert(!_debugDoingUpdate);
+    _bucketsNeedingSerialization.remove(bucket);
+  }
+
+  void _doSerialization() {
+    if (!_serializationScheduled) {
+      return;
+    }
+    assert(() {
+      _debugDoingUpdate = true;
+      return true;
+    }());
+    _serializationScheduled = false;
+
+    for (final RestorationBucket bucket in _bucketsNeedingSerialization) {
+      bucket.finalize();
+    }
+    _bucketsNeedingSerialization.clear();
+    sendToEngine(_encodeRestorationData(_rootBucket!._rawData));
+
+    assert(() {
+      _debugDoingUpdate = false;
+      return true;
+    }());
+  }
+
+  /// Called to manually flush the restoration data to the engine.
+  ///
+  /// A change in restoration data is usually accompanied by scheduling a frame
+  /// (because the restoration data is modified inside a [State.setState] call,
+  /// because it is usually something that affects the interface). Restoration
+  /// data is automatically flushed to the engine at the end of a frame. As a
+  /// result, it is uncommon to need to call this method directly. However, if
+  /// restoration data is changed without triggering a frame, this method must
+  /// be called to ensure that the updated restoration data is sent to the
+  /// engine in a timely manner. An example of such a use case is the
+  /// [Scrollable], where the final scroll offset after a scroll activity
+  /// finishes is determined between frames without scheduling a new frame.
+  ///
+  /// Calling this method is a no-op if a frame is already scheduled. In that
+  /// case, the restoration data will be flushed to the engine at the end of
+  /// that frame. If this method is called and no frame is scheduled, the
+  /// current restoration data is directly sent to the engine.
+  void flushData() {
+    assert(!_debugDoingUpdate);
+    if (SchedulerBinding.instance!.hasScheduledFrame) {
+      return;
+    }
+    _doSerialization();
+    assert(!_serializationScheduled);
+  }
+}
+
+/// A [RestorationBucket] holds pieces of the restoration data that a part of
+/// the application needs to restore its state.
+///
+/// For a general overview of how state restoration works in Flutter, see the
+/// [RestorationManager].
+///
+/// [RestorationBucket]s are organized in a tree that is rooted in
+/// [RestorationManager.rootBucket] and managed by a [RestorationManager]. The
+/// tree is serializable and must contain all the data an application needs to
+/// restore its current state at a later point in time.
+///
+/// A [RestorationBucket] stores restoration data as key-value pairs. The key is
+/// a [String] representing a restoration ID that identifies a piece of data
+/// uniquely within a bucket. The value can be anything that is serializable via
+/// the [StandardMessageCodec]. Furthermore, a [RestorationBucket] may have
+/// child buckets, which are identified within their parent via a unique
+/// restoration ID as well.
+///
+/// During state restoration, the data previously stored in the
+/// [RestorationBucket] hierarchy will be made available again to the
+/// application to restore it to the state it had when the data was collected.
+/// State restoration to a previous state may happen when the app is launched
+/// (e.g. after it has been terminated gracefully while running in the
+/// background) or after the app has already been running for a while.
+///
+/// ## Lifecycle
+///
+/// A [RestorationBucket] is rarely instantiated directly via its constructors.
+/// Instead, when an entity wants to store data in or retrieve data from a
+/// restoration bucket, it typically obtains a child bucket from a parent by
+/// calling [claimChild]. If no parent is available,
+/// [RestorationManager.rootBucket] may be used as a parent. When claiming a
+/// child, the claimer must provide the restoration ID of the child it would
+/// like to own. A child bucket with a given restoration ID can at most have
+/// one owner. If another owner tries to claim a bucket with the same ID from
+/// the same parent, an exception is thrown (see discussion in [claimChild]).
+/// The restoration IDs that a given owner uses to claim a child (and to store
+/// data in that child, see below) must be stable across app launches to ensure
+/// that after the app restarts the owner can retrieve the same data again that
+/// it stored during a previous run.
+///
+/// Per convention, the owner of the bucket has exclusive access to the values
+/// stored in the bucket. It can read, add, modify, and remove values via the
+/// [read], [write], and [remove] methods. In general, the owner should store
+/// all the data in the bucket that it needs to restore its current state. If
+/// its current state changes, the data in the bucket must be updated. At the
+/// same time, the data in the bucket should be kept to a minimum. For example,
+/// for data that can be retrieved from other sources (like a database or
+/// web service) only enough information (e.g. an ID or resource locator) to
+/// re-obtain that data should be stored in the bucket. In addition to managing
+/// the data in a bucket, an owner may also make the bucket available to other
+/// entities so they can claim child buckets from it via [claimChild] for their
+/// own restoration needs.
+///
+/// The bucket returned by [claimChild] may either contain state information
+/// that the owner had previously (e.g. during a previous run of the
+/// application) stored in it or it may be empty. If the bucket contains data,
+/// the owner is expected to restore its state with the information previously
+/// stored in the bucket. If the bucket is empty, it may initialize itself to
+/// default values.
+///
+/// When the data stored in a bucket is no longer needed to restore the
+/// application to its current state (e.g. because the owner of the bucket is no
+/// longer shown on screen), the bucket must be [dispose]d. This will remove all
+/// information stored in the bucket from the app's restoration data and that
+/// information will not be available again when the application is restored to
+/// this state in the future.
+class RestorationBucket {
+  /// Creates an empty [RestorationBucket] to be provided to [adoptChild] to add
+  /// it to the bucket hierarchy.
+  ///
+  /// {@template flutter.services.RestorationBucket.empty.bucketCreation}
+  /// Instantiating a bucket directly is rare, most buckets are created by
+  /// claiming a child from a parent via [claimChild]. If no parent bucket is
+  /// available, [RestorationManager.rootBucket] may be used as a parent.
+  /// {@endtemplate}
+  ///
+  /// The `restorationId` must not be null.
+  RestorationBucket.empty({
+    required String restorationId,
+    required Object? debugOwner,
+  }) : assert(restorationId != null),
+       _restorationId = restorationId,
+       _rawData = <String, dynamic>{} {
+    assert(() {
+      _debugOwner = debugOwner;
+      return true;
+    }());
+  }
+
+  /// Creates the root [RestorationBucket] for the provided restoration
+  /// `manager`.
+  ///
+  /// The `rawData` must either be null (in which case an empty bucket will be
+  /// instantiated) or it must be a nested map describing the entire bucket
+  /// hierarchy in the following format:
+  ///
+  /// ```javascript
+  /// {
+  ///  'v': {  // key-value pairs
+  ///     // * key is a string representation a restoration ID
+  ///     // * value is any primitive that can be encoded with [StandardMessageCodec]
+  ///    '<restoration-id>: <Object>,
+  ///   },
+  ///  'c': {  // child buckets
+  ///    'restoration-id': <nested map representing a child bucket>
+  ///   }
+  /// }
+  /// ```
+  ///
+  /// {@macro flutter.services.RestorationBucket.empty.bucketCreation}
+  ///
+  /// The `manager` argument must not be null.
+  RestorationBucket.root({
+    required RestorationManager manager,
+    required Map<dynamic, dynamic>? rawData,
+  }) : assert(manager != null),
+       _manager = manager,
+       _rawData = rawData ?? <dynamic, dynamic>{},
+       _restorationId = 'root' {
+    assert(() {
+      _debugOwner = manager;
+      return true;
+    }());
+  }
+
+  /// Creates a child bucket initialized with the data that the provided
+  /// `parent` has stored under the provided [restorationId].
+  ///
+  /// This constructor cannot be used if the `parent` does not have any child
+  /// data stored under the given ID. In that case, create an empty bucket (via
+  /// [RestorationBucket.empty] and have the parent adopt it via [adoptChild].
+  ///
+  /// {@macro flutter.services.RestorationBucket.empty.bucketCreation}
+  ///
+  /// The `restorationId` and `parent` argument must not be null.
+  RestorationBucket.child({
+    required String restorationId,
+    required RestorationBucket parent,
+    required Object? debugOwner,
+  }) : assert(restorationId != null),
+       assert(parent != null),
+       assert(parent._rawChildren[restorationId] != null),
+       _manager = parent._manager,
+       _parent = parent,
+       _rawData = parent._rawChildren[restorationId] as Map<dynamic, dynamic>,
+       _restorationId = restorationId {
+    assert(() {
+      _debugOwner = debugOwner;
+      return true;
+    }());
+  }
+
+  static const String _childrenMapKey = 'c';
+  static const String _valuesMapKey = 'v';
+
+  final Map<dynamic, dynamic> _rawData;
+
+  /// The owner of the bucket that was provided when the bucket was claimed via
+  /// [claimChild].
+  ///
+  /// The value is used in error messages. Accessing the value is only valid
+  /// in debug mode, otherwise it will return null.
+  Object? get debugOwner {
+    assert(_debugAssertNotDisposed());
+    return _debugOwner;
+  }
+  Object? _debugOwner;
+
+  RestorationManager? _manager;
+  RestorationBucket? _parent;
+
+  /// Returns true when entities processing this bucket should restore their
+  /// state from the information in the bucket (e.g. via [read] and
+  /// [claimChild]) instead of copying their current state information into the
+  /// bucket (e.g. via [write] and [adoptChild].
+  ///
+  /// This flag is true for the frame after the [RestorationManager] has been
+  /// instructed to restore the application from newly provided restoration
+  /// data.
+  bool get isReplacing => _manager?.isReplacing ?? false;
+
+  /// The restoration ID under which the bucket is currently stored in the
+  /// parent of this bucket (or wants to be stored if it is currently
+  /// parent-less).
+  ///
+  /// This value is never null.
+  String get restorationId {
+    assert(_debugAssertNotDisposed());
+    return _restorationId;
+  }
+  String _restorationId;
+
+  // Maps a restoration ID to the raw map representation of a child bucket.
+  Map<dynamic, dynamic> get _rawChildren => _rawData.putIfAbsent(_childrenMapKey, () => <dynamic, dynamic>{}) as Map<dynamic, dynamic>;
+  // Maps a restoration ID to a value that is stored in this bucket.
+  Map<dynamic, dynamic> get _rawValues => _rawData.putIfAbsent(_valuesMapKey, () => <dynamic, dynamic>{}) as Map<dynamic, dynamic>;
+
+  // Get and store values.
+
+  /// Returns the value of type `P` that is currently stored in the bucket under
+  /// the provided `restorationId`.
+  ///
+  /// Returns null if nothing is stored under that id. Throws, if the value
+  /// stored under the ID is not of type `P`.
+  ///
+  /// See also:
+  ///
+  ///  * [write], which stores a value in the bucket.
+  ///  * [remove], which removes a value from the bucket.
+  ///  * [contains], which checks whether any value is stored under a given
+  ///    restoration ID.
+  P? read<P>(String restorationId) {
+    assert(_debugAssertNotDisposed());
+    assert(restorationId != null);
+    return _rawValues[restorationId] as P?;
+  }
+
+  /// Stores the provided `value` of type `P` under the provided `restorationId`
+  /// in the bucket.
+  ///
+  /// Any value that has previously been stored under that ID is overwritten
+  /// with the new value. The provided `value` must be serializable with the
+  /// [StandardMessageCodec].
+  ///
+  /// Null values will be stored in the bucket as-is. To remove a value, use
+  /// [remove].
+  ///
+  /// See also:
+  ///
+  ///  * [read], which retrieves a stored value from the bucket.
+  ///  * [remove], which removes a value from the bucket.
+  ///  * [contains], which checks whether any value is stored under a given
+  ///    restoration ID.
+  void write<P>(String restorationId, P value) {
+    assert(_debugAssertNotDisposed());
+    assert(restorationId != null);
+    assert(debugIsSerializableForRestoration(value));
+    if (_rawValues[restorationId] != value || !_rawValues.containsKey(restorationId)) {
+      _rawValues[restorationId] = value;
+      _markNeedsSerialization();
+    }
+  }
+
+  /// Deletes the value currently stored under the provided `restorationId` from
+  /// the bucket.
+  ///
+  /// The value removed from the bucket is casted to `P` and returned. If no
+  /// value was stored under that id, null is returned.
+  ///
+  /// See also:
+  ///
+  ///  * [read], which retrieves a stored value from the bucket.
+  ///  * [write], which stores a value in the bucket.
+  ///  * [contains], which checks whether any value is stored under a given
+  ///    restoration ID.
+  P? remove<P>(String restorationId) {
+    assert(_debugAssertNotDisposed());
+    assert(restorationId != null);
+    final bool needsUpdate = _rawValues.containsKey(restorationId);
+    final P? result = _rawValues.remove(restorationId) as P?;
+    if (_rawValues.isEmpty) {
+      _rawData.remove(_valuesMapKey);
+    }
+    if (needsUpdate) {
+      _markNeedsSerialization();
+    }
+    return result;
+  }
+
+  /// Checks whether a value stored in the bucket under the provided
+  /// `restorationId`.
+  ///
+  /// See also:
+  ///
+  ///  * [read], which retrieves a stored value from the bucket.
+  ///  * [write], which stores a value in the bucket.
+  ///  * [remove], which removes a value from the bucket.
+  bool contains(String restorationId) {
+    assert(_debugAssertNotDisposed());
+    assert(restorationId != null);
+    return _rawValues.containsKey(restorationId);
+  }
+
+  // Child management.
+
+  // The restoration IDs and associated buckets of children that have been
+  // claimed via [claimChild].
+  final Map<String, RestorationBucket> _claimedChildren = <String, RestorationBucket>{};
+  // Newly created child buckets whose restoration ID is still in use, see
+  // comment in [claimChild] for details.
+  final Map<String, List<RestorationBucket>> _childrenToAdd = <String, List<RestorationBucket>>{};
+
+  /// Claims ownership of the child with the provided `restorationId` from this
+  /// bucket.
+  ///
+  /// If the application is getting restored to a previous state, the bucket
+  /// will contain all the data that was previously stored in the bucket.
+  /// Otherwise, an empty bucket is returned.
+  ///
+  /// The claimer of the bucket is expected to use the data stored in the bucket
+  /// to restore itself to its previous state described by the data in the
+  /// bucket. If the bucket is empty, it should initialize itself to default
+  /// values. Whenever the information that the claimer needs to restore its
+  /// state changes, the data in the bucket should be updated to reflect that.
+  ///
+  /// A child bucket with a given `restorationId` can only have one owner. If
+  /// another owner claims a child bucket with the same `restorationId` an
+  /// exception will be thrown at the end of the current frame unless the
+  /// previous owner has either deleted its bucket by calling [dispose] or has
+  /// moved it to a new parent via [adoptChild].
+  ///
+  /// When the returned bucket is no longer needed, it must be [dispose]d to
+  /// delete the information stored in it from the app's restoration data.
+  RestorationBucket claimChild(String restorationId, {required Object? debugOwner}) {
+    assert(_debugAssertNotDisposed());
+    assert(restorationId != null);
+    // There are three cases to consider:
+    // 1. Claiming an ID that has already been claimed.
+    // 2. Claiming an ID that doesn't yet exist in [_rawChildren].
+    // 3. Claiming an ID that does exist in [_rawChildren] and hasn't been
+    //    claimed yet.
+    // If an ID has already been claimed (case 1) the current owner may give up
+    // that ID later this frame and it can be re-used. In anticipation of the
+    // previous owner's surrender of the id, we return an empty bucket for this
+    // new claim and check in [_debugAssertIntegrity] that at the end of the
+    // frame the old owner actually did surrendered the id.
+    // Case 2 also requires the creation of a new empty bucket.
+    // In Case 3 we create a new bucket wrapping the existing data in
+    // [_rawChildren].
+
+    // Case 1+2: Adopt and return an empty bucket.
+    if (_claimedChildren.containsKey(restorationId) || !_rawChildren.containsKey(restorationId)) {
+      final RestorationBucket child = RestorationBucket.empty(
+        debugOwner: debugOwner,
+        restorationId: restorationId,
+      );
+      adoptChild(child);
+      return child;
+    }
+
+    // Case 3: Return bucket wrapping the existing data.
+    assert(_rawChildren[restorationId] != null);
+    final RestorationBucket child = RestorationBucket.child(
+      restorationId: restorationId,
+      parent: this,
+      debugOwner: debugOwner,
+    );
+    _claimedChildren[restorationId] = child;
+    return child;
+  }
+
+  /// Adopts the provided `child` bucket.
+  ///
+  /// The `child` will be dropped from its old parent, if it had one.
+  ///
+  /// The `child` is stored under its [restorationId] in this bucket. If this
+  /// bucket already contains a child bucket under the same id, the owner of
+  /// that existing bucket must give it up (e.g. by moving the child bucket to a
+  /// different parent or by disposing it) before the end of the current frame.
+  /// Otherwise an exception indicating the illegal use of duplicated
+  /// restoration IDs will trigger in debug mode.
+  ///
+  /// No-op if the provided bucket is already a child of this bucket.
+  void adoptChild(RestorationBucket child) {
+    assert(_debugAssertNotDisposed());
+    assert(child != null);
+    if (child._parent != this) {
+      child._parent?._removeChildData(child);
+      child._parent = this;
+      _addChildData(child);
+      if (child._manager != _manager) {
+        _recursivelyUpdateManager(child);
+      }
+    }
+    assert(child._parent == this);
+    assert(child._manager == _manager);
+  }
+
+  void _dropChild(RestorationBucket child) {
+    assert(child != null);
+    assert(child._parent == this);
+    _removeChildData(child);
+    child._parent = null;
+    if (child._manager != null) {
+      child._updateManager(null);
+      child._visitChildren(_recursivelyUpdateManager);
+    }
+  }
+
+  bool _needsSerialization = false;
+  void _markNeedsSerialization() {
+    if (!_needsSerialization) {
+      _needsSerialization = true;
+      _manager?.scheduleSerializationFor(this);
+    }
+  }
+
+  /// Called by the [RestorationManager] just before the data of the bucket
+  /// is serialized and send to the engine.
+  ///
+  /// It is exposed to allow testing of [RestorationBucket]s in isolation.
+  @visibleForTesting
+  void finalize() {
+    assert(_debugAssertNotDisposed());
+    assert(_needsSerialization);
+    _needsSerialization = false;
+    assert(_debugAssertIntegrity());
+  }
+
+  void _recursivelyUpdateManager(RestorationBucket bucket) {
+    bucket._updateManager(_manager);
+    bucket._visitChildren(_recursivelyUpdateManager);
+  }
+
+  void _updateManager(RestorationManager? newManager) {
+    if (_manager == newManager) {
+      return;
+    }
+    if (_needsSerialization) {
+      _manager?.unscheduleSerializationFor(this);
+    }
+    _manager = newManager;
+    if (_needsSerialization && _manager != null) {
+      _needsSerialization = false;
+      _markNeedsSerialization();
+    }
+  }
+
+  bool _debugAssertIntegrity() {
+    assert(() {
+      if (_childrenToAdd.isEmpty) {
+        return true;
+      }
+      final List<DiagnosticsNode> error = <DiagnosticsNode>[
+        ErrorSummary('Multiple owners claimed child RestorationBuckets with the same IDs.'),
+        ErrorDescription('The following IDs were claimed multiple times from the parent $this:')
+      ];
+      for (final MapEntry<String, List<RestorationBucket>> child in _childrenToAdd.entries) {
+        final String id = child.key;
+        final List<RestorationBucket> buckets = child.value;
+        assert(buckets.isNotEmpty);
+        assert(_claimedChildren.containsKey(id));
+        error.addAll(<DiagnosticsNode>[
+          ErrorDescription(' * "$id" was claimed by:'),
+          ...buckets.map((RestorationBucket bucket) => ErrorDescription('   * ${bucket.debugOwner}')),
+          ErrorDescription('   * ${_claimedChildren[id]!.debugOwner} (current owner)'),
+        ]);
+      }
+      throw FlutterError.fromParts(error);
+    }());
+    return true;
+  }
+
+  void _removeChildData(RestorationBucket child) {
+    assert(child != null);
+    assert(child._parent == this);
+    if (_claimedChildren.remove(child.restorationId) == child) {
+      _rawChildren.remove(child.restorationId);
+      final List<RestorationBucket>? pendingChildren = _childrenToAdd[child.restorationId];
+      if (pendingChildren != null) {
+        final RestorationBucket toAdd = pendingChildren.removeLast();
+        _finalizeAddChildData(toAdd);
+        if (pendingChildren.isEmpty) {
+          _childrenToAdd.remove(child.restorationId);
+        }
+      }
+      if (_rawChildren.isEmpty) {
+        _rawData.remove(_childrenMapKey);
+      }
+      _markNeedsSerialization();
+      return;
+    }
+    _childrenToAdd[child.restorationId]?.remove(child);
+    if (_childrenToAdd[child.restorationId]?.isEmpty == true) {
+      _childrenToAdd.remove(child.restorationId);
+    }
+  }
+
+  void _addChildData(RestorationBucket child) {
+    assert(child != null);
+    assert(child._parent == this);
+    if (_claimedChildren.containsKey(child.restorationId)) {
+      // Delay addition until the end of the frame in the hopes that the current
+      // owner of the child with the same ID will have given up that child by
+      // then.
+      _childrenToAdd.putIfAbsent(child.restorationId, () => <RestorationBucket>[]).add(child);
+      _markNeedsSerialization();
+      return;
+    }
+    _finalizeAddChildData(child);
+    _markNeedsSerialization();
+  }
+
+  void _finalizeAddChildData(RestorationBucket child) {
+    assert(_claimedChildren[child.restorationId] == null);
+    assert(_rawChildren[child.restorationId] == null);
+    _claimedChildren[child.restorationId] = child;
+    _rawChildren[child.restorationId] = child._rawData;
+  }
+
+  void _visitChildren(_BucketVisitor visitor, {bool concurrentModification = false}) {
+    Iterable<RestorationBucket> children = _claimedChildren.values
+        .followedBy(_childrenToAdd.values.expand((List<RestorationBucket> buckets) => buckets));
+    if (concurrentModification) {
+      children = children.toList(growable: false);
+    }
+    children.forEach(visitor);
+  }
+
+  // Bucket management
+
+  /// Changes the restoration ID under which the bucket is (or will be) stored
+  /// in its parent to `newRestorationId`.
+  ///
+  /// No-op if the bucket is already stored under the provided id.
+  ///
+  /// If another owner has already claimed a bucket with the provided `newId` an
+  /// exception will be thrown at the end of the current frame unless the other
+  /// owner has deleted its bucket by calling [dispose], [rename]ed it using
+  /// another ID, or has moved it to a new parent via [adoptChild].
+  void rename(String newRestorationId) {
+    assert(_debugAssertNotDisposed());
+    assert(newRestorationId != null);
+    if (newRestorationId == restorationId) {
+      return;
+    }
+    _parent?._removeChildData(this);
+    _restorationId = newRestorationId;
+    _parent?._addChildData(this);
+  }
+
+  /// Deletes the bucket and all the data stored in it from the bucket
+  /// hierarchy.
+  ///
+  /// After [dispose] has been called, the data stored in this bucket and its
+  /// children are no longer part of the app's restoration data. The data
+  /// originally stored in the bucket will not be available again when the
+  /// application is restored to this state in the future. It is up to the
+  /// owners of the children to either move them (via [adoptChild]) to a new
+  /// parent that is still part of the bucket hierarchy or to [dispose] of them
+  /// as well.
+  ///
+  /// This method must only be called by the object's owner.
+  void dispose() {
+    assert(_debugAssertNotDisposed());
+    _visitChildren(_dropChild, concurrentModification: true);
+    _claimedChildren.clear();
+    _childrenToAdd.clear();
+    _parent?._removeChildData(this);
+    _parent = null;
+    _updateManager(null);
+    _debugDisposed = true;
+  }
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'RestorationBucket')}(restorationId: $restorationId, owner: $debugOwner)';
+
+  bool _debugDisposed = false;
+  bool _debugAssertNotDisposed() {
+    assert(() {
+      if (_debugDisposed) {
+        throw FlutterError(
+            'A $runtimeType was used after being disposed.\n'
+            'Once you have called dispose() on a $runtimeType, it can no longer be used.'
+        );
+      }
+      return true;
+    }());
+    return true;
+  }
+}
+
+/// Returns true when the provided `object` is serializable for state
+/// restoration.
+///
+/// Should only be called from within asserts. Always returns false outside
+/// of debug builds.
+bool debugIsSerializableForRestoration(Object? object) {
+  bool result = false;
+
+  assert(() {
+    try {
+      const StandardMessageCodec().encodeMessage(object);
+      result = true;
+    } catch (_) {
+      result = false;
+    }
+    return true;
+  }());
+
+  return result;
+}
diff --git a/lib/src/services/system_channels.dart b/lib/src/services/system_channels.dart
new file mode 100644
index 0000000..68b24bb
--- /dev/null
+++ b/lib/src/services/system_channels.dart
@@ -0,0 +1,347 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart';
+
+import 'message_codecs.dart';
+import 'platform_channel.dart';
+
+/// Platform channels used by the Flutter system.
+class SystemChannels {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  SystemChannels._();
+
+  /// A JSON [MethodChannel] for navigation.
+  ///
+  /// The following incoming methods are defined for this channel (registered
+  /// using [MethodChannel.setMethodCallHandler]):
+  ///
+  ///  * `popRoute`, which is called when the system wants the current route to
+  ///    be removed (e.g. if the user hits a system-level back button).
+  ///
+  ///  * `pushRoute`, which is called with a single string argument when the
+  ///    operating system instructs the application to open a particular page.
+  ///
+  ///  * `pushRouteInformation`, which is called with a map, which contains a
+  ///    location string and a state object, when the operating system instructs
+  ///    the application to open a particular page. These parameters are stored
+  ///    under the key `location` and `state` in the map.
+  ///
+  /// The following methods are used for the opposite direction data flow. The
+  /// framework notifies the engine about the route changes.
+  ///
+  ///  * `routeUpdated`, which is called when current route has changed.
+  ///
+  ///  * `routeInformationUpdated`, which is called by the [Router] when the
+  ///    application navigate to a new location.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver.didPopRoute] and
+  ///    [WidgetsBindingObserver.didPushRoute], which expose this channel's
+  ///    methods.
+  ///  * [Navigator] which manages transitions from one page to another.
+  ///    [Navigator.push], [Navigator.pushReplacement], [Navigator.pop] and
+  ///    [Navigator.replace], utilize this channel's methods to send route
+  ///    change information from framework to engine.
+  static const MethodChannel navigation = OptionalMethodChannel(
+      'flutter/navigation',
+      JSONMethodCodec(),
+  );
+
+  /// A JSON [MethodChannel] for invoking miscellaneous platform methods.
+  ///
+  /// The following outgoing methods are defined for this channel (invoked using
+  /// [OptionalMethodChannel.invokeMethod]):
+  ///
+  ///  * `Clipboard.setData`: Places the data from the `text` entry of the
+  ///    argument, which must be a [Map], onto the system clipboard. See
+  ///    [Clipboard.setData].
+  ///
+  ///  * `Clipboard.getData`: Returns the data that has the format specified in
+  ///    the argument, a [String], from the system clipboard. The only format
+  ///    currently supported is `text/plain` ([Clipboard.kTextPlain]). The
+  ///    result is a [Map] with a single key, `text`. See [Clipboard.getData].
+  ///
+  ///  * `HapticFeedback.vibrate`: Triggers a system-default haptic response.
+  ///    See [HapticFeedback.vibrate].
+  ///
+  ///  * `SystemSound.play`: Triggers a system audio effect. The argument must
+  ///    be a [String] describing the desired effect; currently only `click` is
+  ///    supported. See [SystemSound.play].
+  ///
+  ///  * `SystemChrome.setPreferredOrientations`: Informs the operating system
+  ///    of the desired orientation of the display. The argument is a [List] of
+  ///    values which are string representations of values of the
+  ///    [DeviceOrientation] enum. See [SystemChrome.setPreferredOrientations].
+  ///
+  ///  * `SystemChrome.setApplicationSwitcherDescription`: Informs the operating
+  ///    system of the desired label and color to be used to describe the
+  ///    application in any system-level application lists (e.g. application
+  ///    switchers). The argument is a [Map] with two keys, `label` giving a
+  ///    [String] description, and `primaryColor` giving a 32 bit integer value
+  ///    (the lower eight bits being the blue channel, the next eight bits being
+  ///    the green channel, the next eight bits being the red channel, and the
+  ///    high eight bits being set, as from [Color.value] for an opaque color).
+  ///    The `primaryColor` can also be zero to indicate that the system default
+  ///    should be used. See [SystemChrome.setApplicationSwitcherDescription].
+  ///
+  ///  * `SystemChrome.setEnabledSystemUIOverlays`: Specifies the set of system
+  ///    overlays to have visible when the application is running. The argument
+  ///    is a [List] of values which are string representations of values of the
+  ///    [SystemUiOverlay] enum. See [SystemChrome.setEnabledSystemUIOverlays].
+  ///
+  ///  * `SystemChrome.setSystemUIOverlayStyle`: Specifies whether system
+  ///    overlays (e.g. the status bar on Android or iOS) should be `light` or
+  ///    `dark`. The argument is one of those two strings. See
+  ///    [SystemChrome.setSystemUIOverlayStyle].
+  ///
+  ///  * `SystemNavigator.pop`: Tells the operating system to close the
+  ///    application, or the closest equivalent. See [SystemNavigator.pop].
+  ///
+  /// Calls to methods that are not implemented on the shell side are ignored
+  /// (so it is safe to call methods when the relevant plugin might be missing).
+  static const MethodChannel platform = OptionalMethodChannel(
+      'flutter/platform',
+      JSONMethodCodec(),
+  );
+
+  /// A JSON [MethodChannel] for handling text input.
+  ///
+  /// This channel exposes a system text input control for interacting with IMEs
+  /// (input method editors, for example on-screen keyboards). There is one
+  /// control, and at any time it can have one active transaction. Transactions
+  /// are represented by an integer. New Transactions are started by
+  /// `TextInput.setClient`. Messages that are sent are assumed to be for the
+  /// current transaction (the last "client" set by `TextInput.setClient`).
+  /// Messages received from the shell side specify the transaction to which
+  /// they apply, so that stale messages referencing past transactions can be
+  /// ignored.
+  ///
+  /// The methods described below are wrapped in a more convenient form by the
+  /// [TextInput] and [TextInputConnection] class.
+  ///
+  /// The following outgoing methods are defined for this channel (invoked using
+  /// [OptionalMethodChannel.invokeMethod]):
+  ///
+  ///  * `TextInput.setClient`: Establishes a new transaction. The arguments is
+  ///    a [List] whose first value is an integer representing a previously
+  ///    unused transaction identifier, and the second is a [String] with a
+  ///    JSON-encoded object with five keys, as obtained from
+  ///    [TextInputConfiguration.toJson]. This method must be invoked before any
+  ///    others (except `TextInput.hide`). See [TextInput.attach].
+  ///
+  ///  * `TextInput.show`: Show the keyboard. See [TextInputConnection.show].
+  ///
+  ///  * `TextInput.setEditingState`: Update the value in the text editing
+  ///    control. The argument is a [String] with a JSON-encoded object with
+  ///    seven keys, as obtained from [TextEditingValue.toJSON]. See
+  ///    [TextInputConnection.setEditingState].
+  ///
+  ///  * `TextInput.clearClient`: End the current transaction. The next method
+  ///    called must be `TextInput.setClient` (or `TextInput.hide`). See
+  ///    [TextInputConnection.close].
+  ///
+  ///  * `TextInput.hide`: Hide the keyboard. Unlike the other methods, this can
+  ///    be called at any time. See [TextInputConnection.close].
+  ///
+  /// The following incoming methods are defined for this channel (registered
+  /// using [MethodChannel.setMethodCallHandler]). In each case, the first argument
+  /// is a transaction identifier. Calls for stale transactions should be ignored.
+  ///
+  ///  * `TextInputClient.updateEditingState`: The user has changed the contents
+  ///    of the text control. The second argument is a [String] containing a
+  ///    JSON-encoded object with seven keys, in the form expected by
+  ///    [TextEditingValue.fromJSON].
+  ///
+  ///  * `TextInputClient.performAction`: The user has triggered an action. The
+  ///    second argument is a [String] consisting of the stringification of one
+  ///    of the values of the [TextInputAction] enum.
+  ///
+  ///  * `TextInputClient.requestExistingInputState`: The embedding may have
+  ///    lost its internal state about the current editing client, if there is
+  ///    one. The framework should call `TextInput.setClient` and
+  ///    `TextInput.setEditingState` again with its most recent information. If
+  ///    there is no existing state on the framework side, the call should
+  ///    fizzle.
+  ///
+  ///  * `TextInputClient.onConnectionClosed`: The text input connection closed
+  ///    on the platform side. For example the application is moved to
+  ///    background or used closed the virtual keyboard. This method informs
+  ///    [TextInputClient] to clear connection and finalize editing.
+  ///    `TextInput.clearClient` and `TextInput.hide` is not called after
+  ///    clearing the connection since on the platform side the connection is
+  ///    already finalized.
+  ///
+  /// Calls to methods that are not implemented on the shell side are ignored
+  /// (so it is safe to call methods when the relevant plugin might be missing).
+  static const MethodChannel textInput = OptionalMethodChannel(
+      'flutter/textinput',
+      JSONMethodCodec(),
+  );
+
+  /// A JSON [BasicMessageChannel] for keyboard events.
+  ///
+  /// Each incoming message received on this channel (registered using
+  /// [BasicMessageChannel.setMessageHandler]) consists of a [Map] with
+  /// platform-specific data, plus a `type` field which is either `keydown`, or
+  /// `keyup`.
+  ///
+  /// On Android, the available fields are those described by
+  /// [RawKeyEventDataAndroid]'s properties.
+  ///
+  /// On Fuchsia, the available fields are those described by
+  /// [RawKeyEventDataFuchsia]'s properties.
+  ///
+  /// No messages are sent on other platforms currently.
+  ///
+  /// See also:
+  ///
+  ///  * [RawKeyboard], which uses this channel to expose key data.
+  ///  * [new RawKeyEvent.fromMessage], which can decode this data into the [RawKeyEvent]
+  ///    subclasses mentioned above.
+  static const BasicMessageChannel<dynamic> keyEvent = BasicMessageChannel<dynamic>(
+      'flutter/keyevent',
+      JSONMessageCodec(),
+  );
+
+  /// A string [BasicMessageChannel] for lifecycle events.
+  ///
+  /// Valid messages are string representations of the values of the
+  /// [AppLifecycleState] enumeration. A handler can be registered using
+  /// [BasicMessageChannel.setMessageHandler].
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver.didChangeAppLifecycleState], which triggers
+  ///    whenever a message is received on this channel.
+  static const BasicMessageChannel<String?> lifecycle = BasicMessageChannel<String?>(
+      'flutter/lifecycle',
+      StringCodec(),
+  );
+
+  /// A JSON [BasicMessageChannel] for system events.
+  ///
+  /// Events are exposed as [Map]s with string keys. The `type` key specifies
+  /// the type of the event; the currently supported system event types are
+  /// those listed below. A handler can be registered using
+  /// [BasicMessageChannel.setMessageHandler].
+  ///
+  ///  * `memoryPressure`: Indicates that the operating system would like
+  ///    applications to release caches to free up more memory. See
+  ///    [WidgetsBindingObserver.didHaveMemoryPressure], which triggers whenever
+  ///    a message is received on this channel.
+  static const BasicMessageChannel<dynamic> system = BasicMessageChannel<dynamic>(
+      'flutter/system',
+      JSONMessageCodec(),
+  );
+
+  /// A [BasicMessageChannel] for accessibility events.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsEvent] and its subclasses for a list of valid accessibility
+  ///    events that can be sent over this channel.
+  ///  * [SemanticsNode.sendEvent], which uses this channel to dispatch events.
+  static const BasicMessageChannel<dynamic> accessibility = BasicMessageChannel<dynamic>(
+    'flutter/accessibility',
+    StandardMessageCodec(),
+  );
+
+  /// A [MethodChannel] for controlling platform views.
+  ///
+  /// See also:
+  ///
+  ///  * [PlatformViewsService] for the available operations on this channel.
+  static const MethodChannel platform_views = MethodChannel(
+    'flutter/platform_views',
+    StandardMethodCodec(),
+  );
+
+  /// A [MethodChannel] for configuring the Skia graphics library.
+  ///
+  /// The following outgoing methods are defined for this channel (invoked using
+  /// [OptionalMethodChannel.invokeMethod]):
+  ///
+  ///  * `Skia.setResourceCacheMaxBytes`: Set the maximum number of bytes that
+  ///    can be held in the GPU resource cache.
+  static const MethodChannel skia = MethodChannel(
+    'flutter/skia',
+    JSONMethodCodec(),
+  );
+
+  /// A [MethodChannel] for configuring mouse cursors.
+  ///
+  /// All outgoing methods defined for this channel uses a `Map<String, dynamic>`
+  /// to contain multiple parameters, including the following methods (invoked
+  /// using [OptionalMethodChannel.invokeMethod]):
+  ///
+  ///  * `activateSystemCursor`: Request to set the cursor of a pointer
+  ///    device to a system cursor. The parameters are
+  ///    integer `device`, and string `kind`.
+  static const MethodChannel mouseCursor = OptionalMethodChannel(
+    'flutter/mousecursor',
+    StandardMethodCodec(),
+  );
+
+  /// A [MethodChannel] for synchronizing restoration data with the engine.
+  ///
+  /// The following outgoing methods are defined for this channel (invoked using
+  /// [OptionalMethodChannel.invokeMethod]):
+  ///
+  ///  * `get`: Retrieves the current restoration information (e.g. provided by
+  ///    the operating system) from the engine. The method returns a map
+  ///    containing an `enabled` boolean to indicate whether collecting
+  ///    restoration data is supported by the embedder. If `enabled` is true,
+  ///    the map may also contain restoration data stored under the `data` key
+  ///    from which the state of the framework may be restored. The restoration
+  ///    data is encoded as [Uint8List].
+  ///  * `put`: Sends the current restoration data to the engine. Takes the
+  ///    restoration data encoded as [Uint8List] as argument.
+  ///
+  /// The following incoming methods are defined for this channel (registered
+  /// using [MethodChannel.setMethodCallHandler]).
+  ///
+  ///  * `push`: Called by the engine to send newly provided restoration
+  ///    information to the framework. The argument given to this method has
+  ///    the same format as the object that the `get` method returns.
+  ///
+  /// See also:
+  ///
+  ///  * [RestorationManager], which uses this channel and also describes how
+  ///    restoration data is used in Flutter.
+  static const MethodChannel restoration = OptionalMethodChannel(
+    'flutter/restoration',
+    StandardMethodCodec(),
+  );
+
+  /// A [MethodChannel] for installing and managing dynamic features.
+  ///
+  /// The following outgoing methods are defined for this channel (invoked using
+  /// [OptionalMethodChannel.invokeMethod]):
+  ///
+  ///  * `installDynamicFeature`: Requests that a dynamic feature identified by
+  ///    the provided loadingUnitId or moduleName be downloaded and installed.
+  ///    Providing a loadingUnitId with null moduleName will install a dynamic
+  ///    feature module that includes the desired loading unit. If a moduleName
+  ///    is provided, then the dynamic feature with the moduleName will be installed.
+  ///    This method returns a future that will not be completed until the
+  ///    feature is fully installed and ready to use. When an error occurs, the
+  ///    future will complete an error. Calling `loadLibrary()` on a deferred
+  ///    imported library is equivalent to calling this method with a
+  ///    loadingUnitId and null moduleName.
+  ///  * `getDynamicFeatureInstallState`: Gets the current installation state of
+  ///    the dynamic feature identified by the loadingUnitId or moduleName.
+  ///    This method returns a string that represents the state. Depending on
+  ///    the implementation, this string may vary, but the default Google Play
+  ///    Store implementation beings in the "Requested" state before transitioning
+  ///    into the "Downloading" and finally the "Installed" state.
+  static const MethodChannel dynamicfeature = OptionalMethodChannel(
+    'flutter/dynamicfeature',
+    StandardMethodCodec(),
+  );
+}
diff --git a/lib/src/services/system_chrome.dart b/lib/src/services/system_chrome.dart
new file mode 100644
index 0000000..9b0dc8d
--- /dev/null
+++ b/lib/src/services/system_chrome.dart
@@ -0,0 +1,424 @@
+// Copyright 2014 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.
+
+
+import 'dart:async';
+import 'package:flute/ui.dart';
+
+import 'package:flute/foundation.dart';
+
+import 'system_channels.dart';
+
+export 'package:flute/ui.dart' show Brightness;
+
+/// Specifies a particular device orientation.
+///
+/// To determine which values correspond to which orientations, first position
+/// the device in its default orientation (this is the orientation that the
+/// system first uses for its boot logo, or the orientation in which the
+/// hardware logos or markings are upright, or the orientation in which the
+/// cameras are at the top). If this is a portrait orientation, then this is
+/// [portraitUp]. Otherwise, it's [landscapeLeft]. As you rotate the device by
+/// 90 degrees in a counter-clockwise direction around the axis that pierces the
+/// screen, you step through each value in this enum in the order given.
+///
+/// For a device with a landscape default orientation, the orientation obtained
+/// by rotating the device 90 degrees clockwise from its default orientation is
+/// [portraitUp].
+///
+/// Used by [SystemChrome.setPreferredOrientations].
+enum DeviceOrientation {
+  /// If the device shows its boot logo in portrait, then the boot logo is shown
+  /// in [portraitUp]. Otherwise, the device shows its boot logo in landscape
+  /// and this orientation is obtained by rotating the device 90 degrees
+  /// clockwise from its boot orientation.
+  portraitUp,
+
+  /// The orientation that is 90 degrees clockwise from [portraitUp].
+  ///
+  /// If the device shows its boot logo in landscape, then the boot logo is
+  /// shown in [landscapeLeft].
+  landscapeLeft,
+
+  /// The orientation that is 180 degrees from [portraitUp].
+  portraitDown,
+
+  /// The orientation that is 90 degrees counterclockwise from [portraitUp].
+  landscapeRight,
+}
+
+/// Specifies a description of the application that is pertinent to the
+/// embedder's application switcher (also known as "recent tasks") user
+/// interface.
+///
+/// Used by [SystemChrome.setApplicationSwitcherDescription].
+@immutable
+class ApplicationSwitcherDescription {
+  /// Creates an ApplicationSwitcherDescription.
+  const ApplicationSwitcherDescription({ this.label, this.primaryColor });
+
+  /// A label and description of the current state of the application.
+  final String? label;
+
+  /// The application's primary color.
+  ///
+  /// This may influence the color that the operating system uses to represent
+  /// the application.
+  final int? primaryColor;
+}
+
+/// Specifies a system overlay at a particular location.
+///
+/// Used by [SystemChrome.setEnabledSystemUIOverlays].
+enum SystemUiOverlay {
+  /// The status bar provided by the embedder on the top of the application
+  /// surface, if any.
+  top,
+
+  /// The status bar provided by the embedder on the bottom of the application
+  /// surface, if any.
+  bottom,
+}
+
+/// Specifies a preference for the style of the system overlays.
+///
+/// Used by [SystemChrome.setSystemUIOverlayStyle].
+@immutable
+class SystemUiOverlayStyle {
+  /// Creates a new [SystemUiOverlayStyle].
+  const SystemUiOverlayStyle({
+    this.systemNavigationBarColor,
+    this.systemNavigationBarDividerColor,
+    this.systemNavigationBarIconBrightness,
+    this.statusBarColor,
+    this.statusBarBrightness,
+    this.statusBarIconBrightness,
+  });
+
+  /// The color of the system bottom navigation bar.
+  ///
+  /// Only honored in Android versions O and greater.
+  final Color? systemNavigationBarColor;
+
+  /// The color of the divider between the system's bottom navigation bar and the app's content.
+  ///
+  /// Only honored in Android versions P and greater.
+  final Color? systemNavigationBarDividerColor;
+
+  /// The brightness of the system navigation bar icons.
+  ///
+  /// Only honored in Android versions O and greater.
+  /// When set to [Brightness.light], the system navigation bar icons are light.
+  /// When set to [Brightness.dark], the system navigation bar icons are dark.
+  final Brightness? systemNavigationBarIconBrightness;
+
+  /// The color of top status bar.
+  ///
+  /// Only honored in Android version M and greater.
+  final Color? statusBarColor;
+
+  /// The brightness of top status bar.
+  ///
+  /// Only honored in iOS.
+  final Brightness? statusBarBrightness;
+
+  /// The brightness of the top status bar icons.
+  ///
+  /// Only honored in Android version M and greater.
+  final Brightness? statusBarIconBrightness;
+
+  /// System overlays should be drawn with a light color. Intended for
+  /// applications with a dark background.
+  static const SystemUiOverlayStyle light = SystemUiOverlayStyle(
+    systemNavigationBarColor: Color(0xFF000000),
+    systemNavigationBarDividerColor: null,
+    statusBarColor: null,
+    systemNavigationBarIconBrightness: Brightness.light,
+    statusBarIconBrightness: Brightness.light,
+    statusBarBrightness: Brightness.dark,
+  );
+
+  /// System overlays should be drawn with a dark color. Intended for
+  /// applications with a light background.
+  static const SystemUiOverlayStyle dark = SystemUiOverlayStyle(
+    systemNavigationBarColor: Color(0xFF000000),
+    systemNavigationBarDividerColor: null,
+    statusBarColor: null,
+    systemNavigationBarIconBrightness: Brightness.light,
+    statusBarIconBrightness: Brightness.dark,
+    statusBarBrightness: Brightness.light,
+  );
+
+  /// Convert this event to a map for serialization.
+  Map<String, dynamic> _toMap() {
+    return <String, dynamic>{
+      'systemNavigationBarColor': systemNavigationBarColor?.value,
+      'systemNavigationBarDividerColor': systemNavigationBarDividerColor?.value,
+      'statusBarColor': statusBarColor?.value,
+      'statusBarBrightness': statusBarBrightness?.toString(),
+      'statusBarIconBrightness': statusBarIconBrightness?.toString(),
+      'systemNavigationBarIconBrightness': systemNavigationBarIconBrightness?.toString(),
+    };
+  }
+
+  @override
+  String toString() => _toMap().toString();
+
+  /// Creates a copy of this theme with the given fields replaced with new values.
+  SystemUiOverlayStyle copyWith({
+    Color? systemNavigationBarColor,
+    Color? systemNavigationBarDividerColor,
+    Color? statusBarColor,
+    Brightness? statusBarBrightness,
+    Brightness? statusBarIconBrightness,
+    Brightness? systemNavigationBarIconBrightness,
+  }) {
+    return SystemUiOverlayStyle(
+      systemNavigationBarColor: systemNavigationBarColor ?? this.systemNavigationBarColor,
+      systemNavigationBarDividerColor: systemNavigationBarDividerColor ?? this.systemNavigationBarDividerColor,
+      statusBarColor: statusBarColor ?? this.statusBarColor,
+      statusBarIconBrightness: statusBarIconBrightness ?? this.statusBarIconBrightness,
+      statusBarBrightness: statusBarBrightness ?? this.statusBarBrightness,
+      systemNavigationBarIconBrightness: systemNavigationBarIconBrightness ?? this.systemNavigationBarIconBrightness,
+    );
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      systemNavigationBarColor,
+      systemNavigationBarDividerColor,
+      statusBarColor,
+      statusBarBrightness,
+      statusBarIconBrightness,
+      systemNavigationBarIconBrightness,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is SystemUiOverlayStyle
+        && other.systemNavigationBarColor == systemNavigationBarColor
+        && other.systemNavigationBarDividerColor == systemNavigationBarDividerColor
+        && other.statusBarColor == statusBarColor
+        && other.statusBarIconBrightness == statusBarIconBrightness
+        && other.statusBarBrightness == statusBarBrightness
+        && other.systemNavigationBarIconBrightness == systemNavigationBarIconBrightness;
+  }
+}
+
+List<String> _stringify(List<dynamic> list) => <String>[
+  for (final dynamic item in list) item.toString(),
+];
+
+/// Controls specific aspects of the operating system's graphical interface and
+/// how it interacts with the application.
+class SystemChrome {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  SystemChrome._();
+
+  /// Specifies the set of orientations the application interface can
+  /// be displayed in.
+  ///
+  /// The `orientation` argument is a list of [DeviceOrientation] enum values.
+  /// The empty list causes the application to defer to the operating system
+  /// default.
+  ///
+  /// ## Limitations
+  ///
+  /// This setting will only be respected on iPad if multitasking is disabled.
+  ///
+  /// You can decide to opt out of multitasking on iPad, then
+  /// setPreferredOrientations will work but your app will not
+  /// support Slide Over and Split View multitasking anymore.
+  ///
+  /// Should you decide to opt out of multitasking you can do this by
+  /// setting "Requires full screen" to true in the Xcode Deployment Info.
+  static Future<void> setPreferredOrientations(List<DeviceOrientation> orientations) async {
+    await SystemChannels.platform.invokeMethod<void>(
+      'SystemChrome.setPreferredOrientations',
+      _stringify(orientations),
+    );
+  }
+
+  /// Specifies the description of the current state of the application as it
+  /// pertains to the application switcher (also known as "recent tasks").
+  ///
+  /// Any part of the description that is unsupported on the current platform
+  /// will be ignored.
+  static Future<void> setApplicationSwitcherDescription(ApplicationSwitcherDescription description) async {
+    await SystemChannels.platform.invokeMethod<void>(
+      'SystemChrome.setApplicationSwitcherDescription',
+      <String, dynamic>{
+        'label': description.label,
+        'primaryColor': description.primaryColor,
+      },
+    );
+  }
+
+  /// Specifies the set of system overlays to have visible when the application
+  /// is running.
+  ///
+  /// The `overlays` argument is a list of [SystemUiOverlay] enum values
+  /// denoting the overlays to show.
+  ///
+  /// If a particular overlay is unsupported on the platform, enabling or
+  /// disabling that overlay will be ignored.
+  ///
+  /// The settings here can be overridden by the platform when System UI becomes
+  /// necessary for functionality.
+  ///
+  /// For example, on Android, when the keyboard becomes visible, it will enable the
+  /// navigation bar and status bar system UI overlays. When the keyboard is closed,
+  /// Android will not restore the previous UI visibility settings, and the UI
+  /// visibility cannot be changed until 1 second after the keyboard is closed to
+  /// prevent malware locking users from navigation buttons.
+  ///
+  /// To regain "fullscreen" after text entry, the UI overlays should be set again
+  /// after a delay of 1 second. This can be achieved through [restoreSystemUIOverlays]
+  /// or calling this again. Otherwise, the original UI overlay settings will be
+  /// automatically restored only when the application loses and regains focus.
+  static Future<void> setEnabledSystemUIOverlays(List<SystemUiOverlay> overlays) async {
+    await SystemChannels.platform.invokeMethod<void>(
+      'SystemChrome.setEnabledSystemUIOverlays',
+      _stringify(overlays),
+    );
+  }
+
+  /// Restores the system overlays to the last settings provided via
+  /// [setEnabledSystemUIOverlays]. May be used when the platform force enables/disables
+  /// UI elements.
+  ///
+  /// For example, when the Android keyboard disables hidden status and navigation bars,
+  /// this can be called to re-disable the bars when the keyboard is closed.
+  ///
+  /// On Android, the system UI cannot be changed until 1 second after the previous
+  /// change. This is to prevent malware from permanently hiding navigation buttons.
+  static Future<void> restoreSystemUIOverlays() async {
+    await SystemChannels.platform.invokeMethod<void>(
+      'SystemChrome.restoreSystemUIOverlays',
+      null,
+    );
+  }
+
+  /// Specifies the style to use for the system overlays that are visible (if
+  /// any).
+  ///
+  /// This method will schedule the embedder update to be run in a microtask.
+  /// Any subsequent calls to this method during the current event loop will
+  /// overwrite the pending value, such that only the last specified value takes
+  /// effect.
+  ///
+  /// Call this API in code whose lifecycle matches that of the desired
+  /// system UI styles. For instance, to change the system UI style on a new
+  /// page, consider calling when pushing/popping a new [PageRoute].
+  ///
+  /// However, the [AppBar] widget automatically sets the system overlay style
+  /// based on its [AppBar.brightness], so configure that instead of calling
+  /// this method directly. Likewise, do the same for [CupertinoNavigationBar]
+  /// via [CupertinoNavigationBar.backgroundColor].
+  ///
+  /// If a particular style is not supported on the platform, selecting it will
+  /// have no effect.
+  ///
+  /// {@tool snippet}
+  /// ```dart
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);
+  ///   return Placeholder();
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// For more complex control of the system overlay styles, consider using
+  /// an [AnnotatedRegion] widget instead of calling [setSystemUIOverlayStyle]
+  /// directly. This widget places a value directly into the layer tree where
+  /// it can be hit-tested by the framework. On every frame, the framework will
+  /// hit-test and select the annotated region it finds under the status and
+  /// navigation bar and synthesize them into a single style. This can be used
+  /// to configure the system styles when an app bar is not used.
+  ///
+  /// {@tool sample --template=stateful_widget_material_no_null_safety}
+  /// The following example creates a widget that changes the status bar color
+  /// to a random value on Android.
+  ///
+  /// ```dart imports
+  /// import 'package:flute/services.dart';
+  /// import 'dart:math' as math;
+  /// ```
+  ///
+  /// ```dart
+  /// final _random = math.Random();
+  /// SystemUiOverlayStyle _currentStyle = SystemUiOverlayStyle.light;
+  ///
+  /// void _changeColor() {
+  ///   final color = Color.fromRGBO(
+  ///     _random.nextInt(255),
+  ///     _random.nextInt(255),
+  ///     _random.nextInt(255),
+  ///     1.0,
+  ///   );
+  ///   setState(() {
+  ///     _currentStyle = SystemUiOverlayStyle.dark.copyWith(
+  ///       statusBarColor: color,
+  ///     );
+  ///   });
+  /// }
+  ///
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return AnnotatedRegion(
+  ///     value: _currentStyle,
+  ///     child: Center(
+  ///       child: ElevatedButton(
+  ///         child: const Text('Change Color'),
+  ///         onPressed: _changeColor,
+  ///        ),
+  ///      ),
+  ///    );
+  ///  }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [AnnotatedRegion], the widget used to place data into the layer tree.
+  static void setSystemUIOverlayStyle(SystemUiOverlayStyle style) {
+    assert(style != null);
+    if (_pendingStyle != null) {
+      // The microtask has already been queued; just update the pending value.
+      _pendingStyle = style;
+      return;
+    }
+    if (style == _latestStyle) {
+      // Trivial success: no microtask has been queued and the given style is
+      // already in effect, so no need to queue a microtask.
+      return;
+    }
+    _pendingStyle = style;
+    scheduleMicrotask(() {
+      assert(_pendingStyle != null);
+      if (_pendingStyle != _latestStyle) {
+        SystemChannels.platform.invokeMethod<void>(
+          'SystemChrome.setSystemUIOverlayStyle',
+          _pendingStyle!._toMap(),
+        );
+        _latestStyle = _pendingStyle;
+      }
+      _pendingStyle = null;
+    });
+  }
+
+  static SystemUiOverlayStyle? _pendingStyle;
+
+  /// The last style that was set using [SystemChrome.setSystemUIOverlayStyle].
+  @visibleForTesting
+  static SystemUiOverlayStyle? get latestStyle => _latestStyle;
+  static SystemUiOverlayStyle? _latestStyle;
+}
diff --git a/lib/src/services/system_navigator.dart b/lib/src/services/system_navigator.dart
new file mode 100644
index 0000000..9cd1b94
--- /dev/null
+++ b/lib/src/services/system_navigator.dart
@@ -0,0 +1,69 @@
+// Copyright 2014 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.
+
+
+import 'system_channels.dart';
+
+/// Controls specific aspects of the system navigation stack.
+class SystemNavigator {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  SystemNavigator._();
+
+  /// Removes the topmost Flutter instance, presenting what was before
+  /// it.
+  ///
+  /// On Android, removes this activity from the stack and returns to
+  /// the previous activity.
+  ///
+  /// On iOS, calls `popViewControllerAnimated:` if the root view
+  /// controller is a `UINavigationController`, or
+  /// `dismissViewControllerAnimated:completion:` if the top view
+  /// controller is a `FlutterViewController`.
+  ///
+  /// The optional `animated` parameter is ignored on all platforms
+  /// except iOS where it is an argument to the aforementioned
+  /// methods.
+  ///
+  /// This method should be preferred over calling `dart:io`'s [exit]
+  /// method, as the latter may cause the underlying platform to act
+  /// as if the application had crashed.
+  static Future<void> pop({bool? animated}) async {
+    await SystemChannels.platform.invokeMethod<void>('SystemNavigator.pop', animated);
+  }
+
+  /// Notifies the platform for a route information change.
+  ///
+  /// On Web, creates a new browser history entry and update URL with the route
+  /// information.
+  static void routeInformationUpdated({
+    required String location,
+    Object? state
+  }) {
+    SystemChannels.navigation.invokeMethod<void>(
+      'routeInformationUpdated',
+      <String, dynamic>{
+        'location': location,
+        'state': state,
+      },
+    );
+  }
+
+  /// Notifies the platform of a route change.
+  ///
+  /// On Web, updates the URL bar with the [routeName].
+  static void routeUpdated({
+    String? routeName,
+    String? previousRouteName
+  }) {
+    SystemChannels.navigation.invokeMethod<void>(
+      'routeUpdated',
+      <String, dynamic>{
+        'previousRouteName': previousRouteName,
+        'routeName': routeName,
+      },
+    );
+  }
+}
diff --git a/lib/src/services/system_sound.dart b/lib/src/services/system_sound.dart
new file mode 100644
index 0000000..4678927
--- /dev/null
+++ b/lib/src/services/system_sound.dart
@@ -0,0 +1,46 @@
+// Copyright 2014 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.
+
+
+import 'system_channels.dart';
+
+/// A sound provided by the system.
+///
+/// These sounds may be played with [SystemSound.play].
+enum SystemSoundType {
+  /// A short indication that a button was pressed.
+  click,
+
+  /// A short system alert sound indicating the need for user attention.
+  ///
+  /// Desktop platforms are the only platforms that support a system alert
+  /// sound, so on mobile platforms (Android, iOS), this will be ignored. The
+  /// web platform does not support playing any sounds, so this will be
+  /// ignored on the web as well.
+  alert,
+
+  // If you add new values here, you also need to update the `SoundType` Java
+  // enum in `PlatformChannel.java`.
+}
+
+/// Provides access to the library of short system specific sounds for common
+/// tasks.
+class SystemSound {
+  // This class is not meant to be instantiated or extended; this constructor
+  // prevents instantiation and extension.
+  // ignore: unused_element
+  SystemSound._();
+
+  /// Play the specified system sound. If that sound is not present on the
+  /// system, the call is ignored.
+  ///
+  /// The web platform currently does not support playing sounds, so this call
+  /// will yield no behavior on that platform.
+  static Future<void> play(SystemSoundType type) async {
+    await SystemChannels.platform.invokeMethod<void>(
+      'SystemSound.play',
+      type.toString(),
+    );
+  }
+}
diff --git a/lib/src/services/text_editing.dart b/lib/src/services/text_editing.dart
new file mode 100644
index 0000000..369fa09
--- /dev/null
+++ b/lib/src/services/text_editing.dart
@@ -0,0 +1,135 @@
+// Copyright 2014 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.
+
+
+import 'package:flute/ui.dart' show hashValues, TextAffinity, TextPosition, TextRange;
+
+import 'package:flute/foundation.dart';
+
+export 'package:flute/ui.dart' show TextAffinity, TextPosition, TextRange;
+
+/// A range of text that represents a selection.
+@immutable
+class TextSelection extends TextRange {
+  /// Creates a text selection.
+  ///
+  /// The [baseOffset] and [extentOffset] arguments must not be null.
+  const TextSelection({
+    required this.baseOffset,
+    required this.extentOffset,
+    this.affinity = TextAffinity.downstream,
+    this.isDirectional = false,
+  }) : super(
+         start: baseOffset < extentOffset ? baseOffset : extentOffset,
+         end: baseOffset < extentOffset ? extentOffset : baseOffset,
+       );
+
+  /// Creates a collapsed selection at the given offset.
+  ///
+  /// A collapsed selection starts and ends at the same offset, which means it
+  /// contains zero characters but instead serves as an insertion point in the
+  /// text.
+  ///
+  /// The [offset] argument must not be null.
+  const TextSelection.collapsed({
+    required int offset,
+    this.affinity = TextAffinity.downstream,
+  }) : baseOffset = offset,
+       extentOffset = offset,
+       isDirectional = false,
+       super.collapsed(offset);
+
+  /// Creates a collapsed selection at the given text position.
+  ///
+  /// A collapsed selection starts and ends at the same offset, which means it
+  /// contains zero characters but instead serves as an insertion point in the
+  /// text.
+  TextSelection.fromPosition(TextPosition position)
+    : baseOffset = position.offset,
+      extentOffset = position.offset,
+      affinity = position.affinity,
+      isDirectional = false,
+      super.collapsed(position.offset);
+
+  /// The offset at which the selection originates.
+  ///
+  /// Might be larger than, smaller than, or equal to extent.
+  final int baseOffset;
+
+  /// The offset at which the selection terminates.
+  ///
+  /// When the user uses the arrow keys to adjust the selection, this is the
+  /// value that changes. Similarly, if the current theme paints a caret on one
+  /// side of the selection, this is the location at which to paint the caret.
+  ///
+  /// Might be larger than, smaller than, or equal to base.
+  final int extentOffset;
+
+  /// If the text range is collapsed and has more than one visual location
+  /// (e.g., occurs at a line break), which of the two locations to use when
+  /// painting the caret.
+  final TextAffinity affinity;
+
+  /// Whether this selection has disambiguated its base and extent.
+  ///
+  /// On some platforms, the base and extent are not disambiguated until the
+  /// first time the user adjusts the selection. At that point, either the start
+  /// or the end of the selection becomes the base and the other one becomes the
+  /// extent and is adjusted.
+  final bool isDirectional;
+
+  /// The position at which the selection originates.
+  ///
+  /// Might be larger than, smaller than, or equal to extent.
+  TextPosition get base => TextPosition(offset: baseOffset, affinity: affinity);
+
+  /// The position at which the selection terminates.
+  ///
+  /// When the user uses the arrow keys to adjust the selection, this is the
+  /// value that changes. Similarly, if the current theme paints a caret on one
+  /// side of the selection, this is the location at which to paint the caret.
+  ///
+  /// Might be larger than, smaller than, or equal to base.
+  TextPosition get extent => TextPosition(offset: extentOffset, affinity: affinity);
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'TextSelection')}(baseOffset: $baseOffset, extentOffset: $extentOffset, affinity: $affinity, isDirectional: $isDirectional)';
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    return other is TextSelection
+        && other.baseOffset == baseOffset
+        && other.extentOffset == extentOffset
+        && other.affinity == affinity
+        && other.isDirectional == isDirectional;
+  }
+
+  @override
+  int get hashCode => hashValues(
+    baseOffset.hashCode,
+    extentOffset.hashCode,
+    affinity.hashCode,
+    isDirectional.hashCode,
+  );
+
+  /// Creates a new [TextSelection] based on the current selection, with the
+  /// provided parameters overridden.
+  TextSelection copyWith({
+    int? baseOffset,
+    int? extentOffset,
+    TextAffinity? affinity,
+    bool? isDirectional,
+  }) {
+    return TextSelection(
+      baseOffset: baseOffset ?? this.baseOffset,
+      extentOffset: extentOffset ?? this.extentOffset,
+      affinity: affinity ?? this.affinity,
+      isDirectional: isDirectional ?? this.isDirectional,
+    );
+  }
+}
diff --git a/lib/src/services/text_formatter.dart b/lib/src/services/text_formatter.dart
new file mode 100644
index 0000000..87238d0
--- /dev/null
+++ b/lib/src/services/text_formatter.dart
@@ -0,0 +1,571 @@
+// Copyright 2014 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.
+
+
+import 'dart:math' as math;
+
+import 'package:characters/characters.dart';
+import 'package:flute/foundation.dart';
+
+import 'text_editing.dart';
+import 'text_input.dart';
+
+/// {@template flutter.services.textFormatter.maxLengthEnforcement}
+/// ### [MaxLengthEnforcement.enforced] versus
+/// [MaxLengthEnforcement.truncateAfterCompositionEnds]
+///
+/// Both [MaxLengthEnforcement.enforced] and
+/// [MaxLengthEnforcement.truncateAfterCompositionEnds] make sure the final
+/// length of the text does not exceed the max length specified. The difference
+/// is that [MaxLengthEnforcement.enforced] truncates all text while
+/// [MaxLengthEnforcement.truncateAfterCompositionEnds] allows composing text to
+/// exceed the limit. Allowing this "placeholder" composing text to exceed the
+/// limit may provide a better user experience on some platforms for entering
+/// ideographic characters (e.g. CJK characters) via composing on phonetic
+/// keyboards.
+///
+/// Some input methods (Gboard on Android for example) initiate text composition
+/// even for Latin characters, in which case the best experience may be to
+/// truncate those composing characters with [MaxLengthEnforcement.enforced].
+///
+/// In fields that strictly support only a small subset of characters, such as
+/// verification code fields, [MaxLengthEnforcement.enforced] may provide the
+/// best experience.
+/// {@endtemplate}
+///
+/// See also:
+///
+///  * [TextField.maxLengthEnforcement] which is used in conjunction with
+///  [TextField.maxLength] to limit the length of user input. [TextField] also
+///  provides a character counter to provide visual feedback.
+enum MaxLengthEnforcement {
+  /// No enforcement applied to the editing value. It's possible to exceed the
+  /// max length.
+  none,
+
+  /// Keep the length of the text input from exceeding the max length even when
+  /// the text has an unfinished composing region.
+  enforced,
+
+  /// Users can still input text if the current value is composing even after
+  /// reaching the max length limit. After composing ends, the value will be
+  /// truncated.
+  truncateAfterCompositionEnds,
+}
+
+/// A [TextInputFormatter] can be optionally injected into an [EditableText]
+/// to provide as-you-type validation and formatting of the text being edited.
+///
+/// Text modification should only be applied when text is being committed by the
+/// IME and not on text under composition (i.e., only when
+/// [TextEditingValue.composing] is collapsed).
+///
+/// See also the [FilteringTextInputFormatter], a subclass that
+/// removes characters that the user tries to enter if they do, or do
+/// not, match a given pattern (as applicable).
+///
+/// To create custom formatters, extend the [TextInputFormatter] class and
+/// implement the [formatEditUpdate] method.
+///
+/// ## Handling emojis and other complex characters
+/// {@macro flutter.widgets.EditableText.onChanged}
+///
+/// See also:
+///
+///  * [EditableText] on which the formatting apply.
+///  * [FilteringTextInputFormatter], a provided formatter for filtering
+///    characters.
+abstract class TextInputFormatter {
+  /// Called when text is being typed or cut/copy/pasted in the [EditableText].
+  ///
+  /// You can override the resulting text based on the previous text value and
+  /// the incoming new text value.
+  ///
+  /// When formatters are chained, `oldValue` reflects the initial value of
+  /// [TextEditingValue] at the beginning of the chain.
+  TextEditingValue formatEditUpdate(
+    TextEditingValue oldValue,
+    TextEditingValue newValue,
+  );
+
+  /// A shorthand to creating a custom [TextInputFormatter] which formats
+  /// incoming text input changes with the given function.
+  static TextInputFormatter withFunction(
+    TextInputFormatFunction formatFunction,
+  ) {
+    return _SimpleTextInputFormatter(formatFunction);
+  }
+}
+
+/// Function signature expected for creating custom [TextInputFormatter]
+/// shorthands via [TextInputFormatter.withFunction].
+typedef TextInputFormatFunction = TextEditingValue Function(
+  TextEditingValue oldValue,
+  TextEditingValue newValue,
+);
+
+/// Wiring for [TextInputFormatter.withFunction].
+class _SimpleTextInputFormatter extends TextInputFormatter {
+  _SimpleTextInputFormatter(this.formatFunction)
+    : assert(formatFunction != null);
+
+  final TextInputFormatFunction formatFunction;
+
+  @override
+  TextEditingValue formatEditUpdate(
+    TextEditingValue oldValue,
+    TextEditingValue newValue,
+  ) {
+    return formatFunction(oldValue, newValue);
+  }
+}
+
+/// A [TextInputFormatter] that prevents the insertion of characters
+/// matching (or not matching) a particular pattern.
+///
+/// Instances of filtered characters found in the new [TextEditingValue]s
+/// will be replaced with the [replacementString] which defaults to the empty
+/// string.
+///
+/// Since this formatter only removes characters from the text, it attempts to
+/// preserve the existing [TextEditingValue.selection] to values it would now
+/// fall at with the removed characters.
+class FilteringTextInputFormatter extends TextInputFormatter {
+  /// Creates a formatter that prevents the insertion of characters
+  /// based on a filter pattern.
+  ///
+  /// If [allow] is true, then the filter pattern is an allow list,
+  /// and characters must match the pattern to be accepted. See also
+  /// the `FilteringTextInputFormatter.allow` constructor.
+  // TODO(goderbauer): Cannot link to the constructor because of https://github.com/dart-lang/dartdoc/issues/2276.
+  ///
+  /// If [allow] is false, then the filter pattern is a deny list,
+  /// and characters that match the pattern are rejected. See also
+  /// the [FilteringTextInputFormatter.deny] constructor.
+  ///
+  /// The [filterPattern], [allow], and [replacementString] arguments
+  /// must not be null.
+  FilteringTextInputFormatter(
+    this.filterPattern, {
+    required this.allow,
+    this.replacementString = '',
+  }) : assert(filterPattern != null),
+       assert(allow != null),
+       assert(replacementString != null);
+
+  /// Creates a formatter that only allows characters matching a pattern.
+  ///
+  /// The [filterPattern] and [replacementString] arguments
+  /// must not be null.
+  FilteringTextInputFormatter.allow(
+    this.filterPattern, {
+    this.replacementString = '',
+  }) : assert(filterPattern != null),
+       assert(replacementString != null),
+       allow = true;
+
+  /// Creates a formatter that blocks characters matching a pattern.
+  ///
+  /// The [filterPattern] and [replacementString] arguments
+  /// must not be null.
+  FilteringTextInputFormatter.deny(
+    this.filterPattern, {
+    this.replacementString = '',
+  }) : assert(filterPattern != null),
+       assert(replacementString != null),
+       allow = false;
+
+  /// A [Pattern] to match and replace in incoming [TextEditingValue]s.
+  ///
+  /// The behavior of the pattern depends on the [allow] property. If
+  /// it is true, then this is an allow list, specifying a pattern that
+  /// characters must match to be accepted. Otherwise, it is a deny list,
+  /// specifying a pattern that characters must not match to be accepted.
+  ///
+  /// In general, the pattern should only match one character at a
+  /// time. See the discussion at [replacementString].
+  ///
+  /// {@tool snippet}
+  /// Typically the pattern is a regular expression, as in:
+  ///
+  /// ```dart
+  /// var onlyDigits = FilteringTextInputFormatter.allow(RegExp(r'[0-9]'));
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@tool snippet}
+  /// If the pattern is a single character, a pattern consisting of a
+  /// [String] can be used:
+  ///
+  /// ```dart
+  /// var noTabs = FilteringTextInputFormatter.deny('\t');
+  /// ```
+  /// {@end-tool}
+  final Pattern filterPattern;
+
+  /// Whether the pattern is an allow list or not.
+  ///
+  /// When true, [filterPattern] denotes an allow list: characters
+  /// must match the filter to be allowed.
+  ///
+  /// When false, [filterPattern] denotes a deny list: characters
+  /// that match the filter are disallowed.
+  final bool allow;
+
+  /// String used to replace banned patterns.
+  ///
+  /// For deny lists ([allow] is false), each match of the
+  /// [filterPattern] is replaced with this string. If [filterPattern]
+  /// can match more than one character at a time, then this can
+  /// result in multiple characters being replaced by a single
+  /// instance of this [replacementString].
+  ///
+  /// For allow lists ([allow] is true), sequences between matches of
+  /// [filterPattern] are replaced as one, regardless of the number of
+  /// characters.
+  ///
+  /// For example, consider a [filterPattern] consisting of just the
+  /// letter "o", applied to text field whose initial value is the
+  /// string "Into The Woods", with the [replacementString] set to
+  /// `*`.
+  ///
+  /// If [allow] is true, then the result will be "*o*oo*". Each
+  /// sequence of characters not matching the pattern is replaced by
+  /// its own single copy of the replacement string, regardless of how
+  /// many characters are in that sequence.
+  ///
+  /// If [allow] is false, then the result will be "Int* the W**ds".
+  /// Every matching sequence is replaced, and each "o" matches the
+  /// pattern separately.
+  ///
+  /// If the pattern was the [RegExp] `o+`, the result would be the
+  /// same in the case where [allow] is true, but in the case where
+  /// [allow] is false, the result would be "Int* the W*ds" (with the
+  /// two "o"s replaced by a single occurrence of the replacement
+  /// string) because both of the "o"s would be matched simultaneously
+  /// by the pattern.
+  ///
+  /// Additionally, each segment of the string before, during, and
+  /// after the current selection in the [TextEditingValue] is handled
+  /// separately. This means that, in the case of the "Into the Woods"
+  /// example above, if the selection ended between the two "o"s in
+  /// "Woods", even if the pattern was `RegExp('o+')`, the result
+  /// would be "Int* the W**ds", since the two "o"s would be handled
+  /// in separate passes.
+  ///
+  /// See also [String.splitMapJoin], which is used to implement this
+  /// behavior in both cases.
+  final String replacementString;
+
+  @override
+  TextEditingValue formatEditUpdate(
+    TextEditingValue oldValue, // unused.
+    TextEditingValue newValue,
+  ) {
+    return _selectionAwareTextManipulation(
+      newValue,
+      (String substring) {
+        return substring.splitMapJoin(
+          filterPattern,
+          onMatch: !allow ? (Match match) => replacementString : null,
+          onNonMatch: allow ? (String nonMatch) => nonMatch.isNotEmpty ? replacementString : '' : null,
+        );
+      },
+    );
+  }
+
+  /// A [TextInputFormatter] that forces input to be a single line.
+  static final TextInputFormatter singleLineFormatter = FilteringTextInputFormatter.deny('\n');
+
+  /// A [TextInputFormatter] that takes in digits `[0-9]` only.
+  static final TextInputFormatter digitsOnly = FilteringTextInputFormatter.allow(RegExp(r'[0-9]'));
+}
+
+/// Old name for [FilteringTextInputFormatter.deny].
+@Deprecated(
+  'Use FilteringTextInputFormatter.deny instead. '
+  'This feature was deprecated after v1.20.0-1.0.pre.'
+)
+class BlacklistingTextInputFormatter extends FilteringTextInputFormatter {
+  /// Old name for [FilteringTextInputFormatter.deny].
+  @Deprecated(
+    'Use FilteringTextInputFormatter.deny instead. '
+    'This feature was deprecated after v1.20.0-1.0.pre.'
+  )
+  BlacklistingTextInputFormatter(
+    Pattern blacklistedPattern, {
+    String replacementString = '',
+  }) : super.deny(blacklistedPattern, replacementString: replacementString);
+
+  /// Old name for [filterPattern].
+  @Deprecated(
+    'Use filterPattern instead. '
+    'This feature was deprecated after v1.20.0-1.0.pre.'
+  )
+  Pattern get blacklistedPattern => filterPattern;
+
+  /// Old name for [FilteringTextInputFormatter.singleLineFormatter].
+  @Deprecated(
+    'Use FilteringTextInputFormatter.singleLineFormatter instead. '
+    'This feature was deprecated after v1.20.0-1.0.pre.'
+  )
+  static final BlacklistingTextInputFormatter singleLineFormatter
+      = BlacklistingTextInputFormatter(RegExp(r'\n'));
+}
+
+/// Old name for [FilteringTextInputFormatter.allow].
+@Deprecated(
+  'Use FilteringTextInputFormatter.allow instead. '
+  'This feature was deprecated after v1.20.0-1.0.pre.'
+)
+class WhitelistingTextInputFormatter extends FilteringTextInputFormatter {
+  /// Old name for [FilteringTextInputFormatter.allow].
+  @Deprecated(
+    'Use FilteringTextInputFormatter.allow instead. '
+    'This feature was deprecated after v1.20.0-1.0.pre.'
+  )
+  WhitelistingTextInputFormatter(Pattern whitelistedPattern)
+    : assert(whitelistedPattern != null),
+      super.allow(whitelistedPattern);
+
+  /// Old name for [filterPattern].
+  @Deprecated(
+    'Use filterPattern instead. '
+    'This feature was deprecated after v1.20.0-1.0.pre.'
+  )
+  Pattern get whitelistedPattern => filterPattern;
+
+  /// Old name for [FilteringTextInputFormatter.digitsOnly].
+  @Deprecated(
+    'Use FilteringTextInputFormatter.digitsOnly instead. '
+    'This feature was deprecated after v1.20.0-1.0.pre.'
+  )
+  static final WhitelistingTextInputFormatter digitsOnly
+      = WhitelistingTextInputFormatter(RegExp(r'\d+'));
+}
+
+/// A [TextInputFormatter] that prevents the insertion of more characters
+/// than allowed.
+///
+/// Since this formatter only prevents new characters from being added to the
+/// text, it preserves the existing [TextEditingValue.selection].
+///
+/// Characters are counted as user-perceived characters using the
+/// [characters](https://pub.dev/packages/characters) package, so even complex
+/// characters like extended grapheme clusters and surrogate pairs are counted
+/// as single characters.
+///
+/// See also:
+///  * [maxLength], which discusses the precise meaning of "number of
+///    characters".
+class LengthLimitingTextInputFormatter extends TextInputFormatter {
+  /// Creates a formatter that prevents the insertion of more characters than a
+  /// limit.
+  ///
+  /// The [maxLength] must be null, -1 or greater than zero. If it is null or -1
+  /// then no limit is enforced.
+  LengthLimitingTextInputFormatter(
+    this.maxLength, {
+    this.maxLengthEnforcement,
+  }) : assert(maxLength == null || maxLength == -1 || maxLength > 0);
+
+  /// The limit on the number of user-perceived characters that this formatter
+  /// will allow.
+  ///
+  /// The value must be null or greater than zero. If it is null or -1, then no
+  /// limit is enforced.
+  ///
+  /// {@template flutter.services.lengthLimitingTextInputFormatter.maxLength}
+  /// ## Characters
+  ///
+  /// For a specific definition of what is considered a character, see the
+  /// [characters](https://pub.dev/packages/characters) package on Pub, which is
+  /// what Flutter uses to delineate characters. In general, even complex
+  /// characters like surrogate pairs and extended grapheme clusters are
+  /// correctly interpreted by Flutter as each being a single user-perceived
+  /// character.
+  ///
+  /// For instance, the character "ö" can be represented as '\u{006F}\u{0308}',
+  /// which is the letter "o" followed by a composed diaeresis "¨", or it can
+  /// be represented as '\u{00F6}', which is the Unicode scalar value "LATIN
+  /// SMALL LETTER O WITH DIAERESIS". It will be counted as a single character
+  /// in both cases.
+  ///
+  /// Similarly, some emoji are represented by multiple scalar values. The
+  /// Unicode "THUMBS UP SIGN + MEDIUM SKIN TONE MODIFIER", "👍🏽"is counted as
+  /// a single character, even though it is a combination of two Unicode scalar
+  /// values, '\u{1F44D}\u{1F3FD}'.
+  /// {@endtemplate}
+  ///
+  /// ### Composing text behaviors
+  ///
+  /// There is no guarantee for the final value before the composing ends.
+  /// So while the value is composing, the constraint of [maxLength] will be
+  /// temporary lifted until the composing ends.
+  ///
+  /// In addition, if the current value already reached the [maxLength],
+  /// composing is not allowed.
+  final int? maxLength;
+
+  /// Determines how the [maxLength] limit should be enforced.
+  ///
+  /// Defaults to [MaxLengthEnforcement.enforced].
+  ///
+  /// {@macro flutter.services.textFormatter.maxLengthEnforcement}
+  final MaxLengthEnforcement? maxLengthEnforcement;
+
+  /// Returns a [MaxLengthEnforcement] that follows the specified [platform]'s
+  /// convention.
+  ///
+  /// {@template flutter.services.textFormatter.effectiveMaxLengthEnforcement}
+  /// ### Platform specific behaviors
+  ///
+  /// Different platforms follow different behaviors by default, according to
+  /// their native behavior.
+  ///  * Android, Windows: [MaxLengthEnforcement.enforced]. The native behavior
+  ///    of these platforms is enforced. The composing will be handled by the
+  ///    IME while users are entering CJK characters.
+  ///  * iOS: [MaxLengthEnforcement.truncateAfterCompositionEnds]. iOS has no
+  ///    default behavior and it requires users implement the behavior
+  ///    themselves. Allow the composition to exceed to avoid breaking CJK input.
+  ///  * Web, macOS, linux, fuchsia:
+  ///    [MaxLengthEnforcement.truncateAfterCompositionEnds]. These platforms
+  ///    allow the composition to exceed by default.
+  /// {@endtemplate}
+  static MaxLengthEnforcement getDefaultMaxLengthEnforcement([
+    TargetPlatform? platform,
+  ]) {
+    if (kIsWeb) {
+      return MaxLengthEnforcement.truncateAfterCompositionEnds;
+    } else {
+      switch (platform ?? defaultTargetPlatform) {
+        case TargetPlatform.android:
+        case TargetPlatform.windows:
+          return MaxLengthEnforcement.enforced;
+        case TargetPlatform.iOS:
+        case TargetPlatform.macOS:
+        case TargetPlatform.linux:
+        case TargetPlatform.fuchsia:
+          return MaxLengthEnforcement.truncateAfterCompositionEnds;
+      }
+    }
+  }
+
+  /// Truncate the given TextEditingValue to maxLength user-perceived
+  /// characters.
+  ///
+  /// See also:
+  ///  * [Dart's characters package](https://pub.dev/packages/characters).
+  ///  * [Dart's documentation on runes and grapheme clusters](https://dart.dev/guides/language/language-tour#runes-and-grapheme-clusters).
+  @visibleForTesting
+  static TextEditingValue truncate(TextEditingValue value, int maxLength) {
+    final CharacterRange iterator = CharacterRange(value.text);
+    if (value.text.characters.length > maxLength) {
+      iterator.expandNext(maxLength);
+    }
+    final String truncated = iterator.current;
+
+    return TextEditingValue(
+      text: truncated,
+      selection: value.selection.copyWith(
+        baseOffset: math.min(value.selection.start, truncated.length),
+        extentOffset: math.min(value.selection.end, truncated.length),
+      ),
+      composing: !value.composing.isCollapsed && truncated.length > value.composing.start
+        ? TextRange(
+            start: value.composing.start,
+            end: math.min(value.composing.end, truncated.length),
+          )
+        : TextRange.empty,
+    );
+  }
+
+  @override
+  TextEditingValue formatEditUpdate(
+    TextEditingValue oldValue,
+    TextEditingValue newValue,
+  ) {
+    final int? maxLength = this.maxLength;
+
+    if (maxLength == null ||
+      maxLength == -1 ||
+      newValue.text.characters.length <= maxLength) {
+      return newValue;
+    }
+
+    assert(maxLength > 0);
+
+    switch (maxLengthEnforcement ?? getDefaultMaxLengthEnforcement()) {
+      case MaxLengthEnforcement.none:
+        return newValue;
+      case MaxLengthEnforcement.enforced:
+        // If already at the maximum and tried to enter even more, and has no
+        // selection, keep the old value.
+        if (oldValue.text.characters.length == maxLength && !oldValue.selection.isValid) {
+          return oldValue;
+        }
+
+        // Enforced to return a truncated value.
+        return truncate(newValue, maxLength);
+      case MaxLengthEnforcement.truncateAfterCompositionEnds:
+        // If already at the maximum and tried to enter even more, and the old
+        // value is not composing, keep the old value.
+        if (oldValue.text.characters.length == maxLength &&
+          !oldValue.composing.isValid) {
+          return oldValue;
+        }
+
+        // Temporarily exempt `newValue` from the maxLength limit if it has a
+        // composing text going and no enforcement to the composing value, until
+        // the composing is finished.
+        if (newValue.composing.isValid) {
+          return newValue;
+        }
+
+        return truncate(newValue, maxLength);
+    }
+  }
+}
+
+TextEditingValue _selectionAwareTextManipulation(
+  TextEditingValue value,
+  String substringManipulation(String substring),
+) {
+  final int selectionStartIndex = value.selection.start;
+  final int selectionEndIndex = value.selection.end;
+  String manipulatedText;
+  TextSelection? manipulatedSelection;
+  if (selectionStartIndex < 0 || selectionEndIndex < 0) {
+    manipulatedText = substringManipulation(value.text);
+  } else {
+    final String beforeSelection = substringManipulation(
+      value.text.substring(0, selectionStartIndex)
+    );
+    final String inSelection = substringManipulation(
+      value.text.substring(selectionStartIndex, selectionEndIndex)
+    );
+    final String afterSelection = substringManipulation(
+      value.text.substring(selectionEndIndex)
+    );
+    manipulatedText = beforeSelection + inSelection + afterSelection;
+    if (value.selection.baseOffset > value.selection.extentOffset) {
+      manipulatedSelection = value.selection.copyWith(
+        baseOffset: beforeSelection.length + inSelection.length,
+        extentOffset: beforeSelection.length,
+      );
+    } else {
+      manipulatedSelection = value.selection.copyWith(
+        baseOffset: beforeSelection.length,
+        extentOffset: beforeSelection.length + inSelection.length,
+      );
+    }
+  }
+  return TextEditingValue(
+    text: manipulatedText,
+    selection: manipulatedSelection ?? const TextSelection.collapsed(offset: -1),
+    composing: manipulatedText == value.text
+        ? value.composing
+        : TextRange.empty,
+  );
+}
diff --git a/lib/src/services/text_input.dart b/lib/src/services/text_input.dart
new file mode 100644
index 0000000..ecb1284
--- /dev/null
+++ b/lib/src/services/text_input.dart
@@ -0,0 +1,1428 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:io' show Platform;
+import 'package:flute/ui.dart' show
+  FontWeight,
+  Offset,
+  Size,
+  Rect,
+  TextAffinity,
+  TextAlign,
+  TextDirection,
+  hashValues;
+
+import 'package:flute/foundation.dart';
+import 'package:vector_math/vector_math_64.dart' show Matrix4;
+
+import 'autofill.dart';
+import 'message_codec.dart';
+import 'platform_channel.dart';
+import 'system_channels.dart';
+import 'system_chrome.dart';
+import 'text_editing.dart';
+
+export 'package:flute/ui.dart' show TextAffinity;
+
+/// Indicates how to handle the intelligent replacement of dashes in text input.
+///
+/// See also:
+///
+///  * [TextField.smartDashesType]
+///  * [CupertinoTextField.smartDashesType]
+///  * [EditableText.smartDashesType]
+///  * [SmartQuotesType]
+///  * <https://developer.apple.com/documentation/uikit/uitextinputtraits>
+enum SmartDashesType {
+  /// Smart dashes is disabled.
+  ///
+  /// This corresponds to the
+  /// ["no" value of UITextSmartDashesType](https://developer.apple.com/documentation/uikit/uitextsmartdashestype/no).
+  disabled,
+
+  /// Smart dashes is enabled.
+  ///
+  /// This corresponds to the
+  /// ["yes" value of UITextSmartDashesType](https://developer.apple.com/documentation/uikit/uitextsmartdashestype/yes).
+  enabled,
+}
+
+/// Indicates how to handle the intelligent replacement of quotes in text input.
+///
+/// See also:
+///
+///  * [TextField.smartQuotesType]
+///  * [CupertinoTextField.smartQuotesType]
+///  * [EditableText.smartQuotesType]
+///  * <https://developer.apple.com/documentation/uikit/uitextinputtraits>
+enum SmartQuotesType {
+  /// Smart quotes is disabled.
+  ///
+  /// This corresponds to the
+  /// ["no" value of UITextSmartQuotesType](https://developer.apple.com/documentation/uikit/uitextsmartquotestype/no).
+  disabled,
+
+  /// Smart quotes is enabled.
+  ///
+  /// This corresponds to the
+  /// ["yes" value of UITextSmartQuotesType](https://developer.apple.com/documentation/uikit/uitextsmartquotestype/yes).
+  enabled,
+}
+
+/// The type of information for which to optimize the text input control.
+///
+/// On Android, behavior may vary across device and keyboard provider.
+///
+/// This class stays as close to `Enum` interface as possible, and allows
+/// for additional flags for some input types. For example, numeric input
+/// can specify whether it supports decimal numbers and/or signed numbers.
+@immutable
+class TextInputType {
+  const TextInputType._(this.index)
+    : signed = null,
+      decimal = null;
+
+  /// Optimize for numerical information.
+  ///
+  /// Requests a numeric keyboard with additional settings.
+  /// The [signed] and [decimal] parameters are optional.
+  const TextInputType.numberWithOptions({
+    this.signed = false,
+    this.decimal = false,
+  }) : index = 2;
+
+  /// Enum value index, corresponds to one of the [values].
+  final int index;
+
+  /// The number is signed, allowing a positive or negative sign at the start.
+  ///
+  /// This flag is only used for the [number] input type, otherwise `null`.
+  /// Use `const TextInputType.numberWithOptions(signed: true)` to set this.
+  final bool? signed;
+
+  /// The number is decimal, allowing a decimal point to provide fractional.
+  ///
+  /// This flag is only used for the [number] input type, otherwise `null`.
+  /// Use `const TextInputType.numberWithOptions(decimal: true)` to set this.
+  final bool? decimal;
+
+  /// Optimize for textual information.
+  ///
+  /// Requests the default platform keyboard.
+  static const TextInputType text = TextInputType._(0);
+
+  /// Optimize for multiline textual information.
+  ///
+  /// Requests the default platform keyboard, but accepts newlines when the
+  /// enter key is pressed. This is the input type used for all multiline text
+  /// fields.
+  static const TextInputType multiline = TextInputType._(1);
+
+  /// Optimize for unsigned numerical information without a decimal point.
+  ///
+  /// Requests a default keyboard with ready access to the number keys.
+  /// Additional options, such as decimal point and/or positive/negative
+  /// signs, can be requested using [new TextInputType.numberWithOptions].
+  static const TextInputType number = TextInputType.numberWithOptions();
+
+  /// Optimize for telephone numbers.
+  ///
+  /// Requests a keyboard with ready access to the number keys, "*", and "#".
+  static const TextInputType phone = TextInputType._(3);
+
+  /// Optimize for date and time information.
+  ///
+  /// On iOS, requests the default keyboard.
+  ///
+  /// On Android, requests a keyboard with ready access to the number keys,
+  /// ":", and "-".
+  static const TextInputType datetime = TextInputType._(4);
+
+  /// Optimize for email addresses.
+  ///
+  /// Requests a keyboard with ready access to the "@" and "." keys.
+  static const TextInputType emailAddress = TextInputType._(5);
+
+  /// Optimize for URLs.
+  ///
+  /// Requests a keyboard with ready access to the "/" and "." keys.
+  static const TextInputType url = TextInputType._(6);
+
+  /// Optimize for passwords that are visible to the user.
+  ///
+  /// Requests a keyboard with ready access to both letters and numbers.
+  static const TextInputType visiblePassword = TextInputType._(7);
+
+  /// Optimized for a person's name.
+  ///
+  /// On iOS, requests the
+  /// [UIKeyboardType.namePhonePad](https://developer.apple.com/documentation/uikit/uikeyboardtype/namephonepad)
+  /// keyboard, a keyboard optimized for entering a person’s name or phone number.
+  /// Does not support auto-capitalization.
+  ///
+  /// On Android, requests a keyboard optimized for
+  /// [TYPE_TEXT_VARIATION_PERSON_NAME](https://developer.android.com/reference/android/text/InputType#TYPE_TEXT_VARIATION_PERSON_NAME).
+  static const TextInputType name = TextInputType._(8);
+
+  /// Optimized for postal mailing addresses.
+  ///
+  /// On iOS, requests the default keyboard.
+  ///
+  /// On Android, requests a keyboard optimized for
+  /// [TYPE_TEXT_VARIATION_POSTAL_ADDRESS](https://developer.android.com/reference/android/text/InputType#TYPE_TEXT_VARIATION_POSTAL_ADDRESS).
+  static const TextInputType streetAddress = TextInputType._(9);
+
+  /// All possible enum values.
+  static const List<TextInputType> values = <TextInputType>[
+    text, multiline, number, phone, datetime, emailAddress, url, visiblePassword, name, streetAddress,
+  ];
+
+  // Corresponding string name for each of the [values].
+  static const List<String> _names = <String>[
+    'text', 'multiline', 'number', 'phone', 'datetime', 'emailAddress', 'url', 'visiblePassword', 'name', 'address',
+  ];
+
+  // Enum value name, this is what enum.toString() would normally return.
+  String get _name => 'TextInputType.${_names[index]}';
+
+  /// Returns a representation of this object as a JSON object.
+  Map<String, dynamic> toJson() {
+    return <String, dynamic>{
+      'name': _name,
+      'signed': signed,
+      'decimal': decimal,
+    };
+  }
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'TextInputType')}('
+        'name: $_name, '
+        'signed: $signed, '
+        'decimal: $decimal)';
+  }
+
+  @override
+  bool operator ==(Object other) {
+    return other is TextInputType
+        && other.index == index
+        && other.signed == signed
+        && other.decimal == decimal;
+  }
+
+  @override
+  int get hashCode => hashValues(index, signed, decimal);
+}
+
+/// An action the user has requested the text input control to perform.
+///
+/// Each action represents a logical meaning, and also configures the soft
+/// keyboard to display a certain kind of action button. The visual appearance
+/// of the action button might differ between versions of the same OS.
+///
+/// Despite the logical meaning of each action, choosing a particular
+/// [TextInputAction] does not necessarily cause any specific behavior to
+/// happen, other than changing the focus when approapriate. It is up to the
+/// developer to ensure that the behavior that occurs when an action button is
+/// pressed is appropriate for the action button chosen.
+///
+/// For example: If the user presses the keyboard action button on iOS when it
+/// reads "Emergency Call", the result should not be a focus change to the next
+/// TextField. This behavior is not logically appropriate for a button that says
+/// "Emergency Call".
+///
+/// See [EditableText] for more information about customizing action button
+/// behavior.
+///
+/// Most [TextInputAction]s are supported equally by both Android and iOS.
+/// However, there is not a complete, direct mapping between Android's IME input
+/// types and iOS's keyboard return types. Therefore, some [TextInputAction]s
+/// are inappropriate for one of the platforms. If a developer chooses an
+/// inappropriate [TextInputAction] when running in debug mode, an error will be
+/// thrown. If the same thing is done in release mode, then instead of sending
+/// the inappropriate value, Android will use "unspecified" on the platform
+/// side and iOS will use "default" on the platform side.
+///
+/// See also:
+///
+///  * [TextInput], which configures the platform's keyboard setup.
+///  * [EditableText], which invokes callbacks when the action button is pressed.
+enum TextInputAction {
+  /// Logical meaning: There is no relevant input action for the current input
+  /// source, e.g., [TextField].
+  ///
+  /// Android: Corresponds to Android's "IME_ACTION_NONE". The keyboard setup
+  /// is decided by the OS. The keyboard will likely show a return key.
+  ///
+  /// iOS: iOS does not have a keyboard return type of "none." It is
+  /// inappropriate to choose this [TextInputAction] when running on iOS.
+  none,
+
+  /// Logical meaning: Let the OS decide which action is most appropriate.
+  ///
+  /// Android: Corresponds to Android's "IME_ACTION_UNSPECIFIED". The OS chooses
+  /// which keyboard action to display. The decision will likely be a done
+  /// button or a return key.
+  ///
+  /// iOS: Corresponds to iOS's "UIReturnKeyDefault". The title displayed in
+  /// the action button is "return".
+  unspecified,
+
+  /// Logical meaning: The user is done providing input to a group of inputs
+  /// (like a form). Some kind of finalization behavior should now take place.
+  ///
+  /// Android: Corresponds to Android's "IME_ACTION_DONE". The OS displays a
+  /// button that represents completion, e.g., a checkmark button.
+  ///
+  /// iOS: Corresponds to iOS's "UIReturnKeyDone". The title displayed in the
+  /// action button is "Done".
+  done,
+
+  /// Logical meaning: The user has entered some text that represents a
+  /// destination, e.g., a restaurant name. The "go" button is intended to take
+  /// the user to a part of the app that corresponds to this destination.
+  ///
+  /// Android: Corresponds to Android's "IME_ACTION_GO". The OS displays a
+  /// button that represents taking "the user to the target of the text they
+  /// typed", e.g., a right-facing arrow button.
+  ///
+  /// iOS: Corresponds to iOS's "UIReturnKeyGo". The title displayed in the
+  /// action button is "Go".
+  go,
+
+  /// Logical meaning: Execute a search query.
+  ///
+  /// Android: Corresponds to Android's "IME_ACTION_SEARCH". The OS displays a
+  /// button that represents a search, e.g., a magnifying glass button.
+  ///
+  /// iOS: Corresponds to iOS's "UIReturnKeySearch". The title displayed in the
+  /// action button is "Search".
+  search,
+
+  /// Logical meaning: Sends something that the user has composed, e.g., an
+  /// email or a text message.
+  ///
+  /// Android: Corresponds to Android's "IME_ACTION_SEND". The OS displays a
+  /// button that represents sending something, e.g., a paper plane button.
+  ///
+  /// iOS: Corresponds to iOS's "UIReturnKeySend". The title displayed in the
+  /// action button is "Send".
+  send,
+
+  /// Logical meaning: The user is done with the current input source and wants
+  /// to move to the next one.
+  ///
+  /// Moves the focus to the next focusable item in the same [FocusScope].
+  ///
+  /// Android: Corresponds to Android's "IME_ACTION_NEXT". The OS displays a
+  /// button that represents moving forward, e.g., a right-facing arrow button.
+  ///
+  /// iOS: Corresponds to iOS's "UIReturnKeyNext". The title displayed in the
+  /// action button is "Next".
+  next,
+
+  /// Logical meaning: The user wishes to return to the previous input source
+  /// in the group, e.g., a form with multiple [TextField]s.
+  ///
+  /// Moves the focus to the previous focusable item in the same [FocusScope].
+  ///
+  /// Android: Corresponds to Android's "IME_ACTION_PREVIOUS". The OS displays a
+  /// button that represents moving backward, e.g., a left-facing arrow button.
+  ///
+  /// iOS: iOS does not have a keyboard return type of "previous." It is
+  /// inappropriate to choose this [TextInputAction] when running on iOS.
+  previous,
+
+  /// Logical meaning: In iOS apps, it is common for a "Back" button and
+  /// "Continue" button to appear at the top of the screen. However, when the
+  /// keyboard is open, these buttons are often hidden off-screen. Therefore,
+  /// the purpose of the "Continue" return key on iOS is to make the "Continue"
+  /// button available when the user is entering text.
+  ///
+  /// Historical context aside, [TextInputAction.continueAction] can be used any
+  /// time that the term "Continue" seems most appropriate for the given action.
+  ///
+  /// Android: Android does not have an IME input type of "continue." It is
+  /// inappropriate to choose this [TextInputAction] when running on Android.
+  ///
+  /// iOS: Corresponds to iOS's "UIReturnKeyContinue". The title displayed in the
+  /// action button is "Continue". This action is only available on iOS 9.0+.
+  ///
+  /// The reason that this value has "Action" post-fixed to it is because
+  /// "continue" is a reserved word in Dart, as well as many other languages.
+  continueAction,
+
+  /// Logical meaning: The user wants to join something, e.g., a wireless
+  /// network.
+  ///
+  /// Android: Android does not have an IME input type of "join." It is
+  /// inappropriate to choose this [TextInputAction] when running on Android.
+  ///
+  /// iOS: Corresponds to iOS's "UIReturnKeyJoin". The title displayed in the
+  /// action button is "Join".
+  join,
+
+  /// Logical meaning: The user wants routing options, e.g., driving directions.
+  ///
+  /// Android: Android does not have an IME input type of "route." It is
+  /// inappropriate to choose this [TextInputAction] when running on Android.
+  ///
+  /// iOS: Corresponds to iOS's "UIReturnKeyRoute". The title displayed in the
+  /// action button is "Route".
+  route,
+
+  /// Logical meaning: Initiate a call to emergency services.
+  ///
+  /// Android: Android does not have an IME input type of "emergencyCall." It is
+  /// inappropriate to choose this [TextInputAction] when running on Android.
+  ///
+  /// iOS: Corresponds to iOS's "UIReturnKeyEmergencyCall". The title displayed
+  /// in the action button is "Emergency Call".
+  emergencyCall,
+
+  /// Logical meaning: Insert a newline character in the focused text input,
+  /// e.g., [TextField].
+  ///
+  /// Android: Corresponds to Android's "IME_ACTION_NONE". The OS displays a
+  /// button that represents a new line, e.g., a carriage return button.
+  ///
+  /// iOS: Corresponds to iOS's "UIReturnKeyDefault". The title displayed in the
+  /// action button is "return".
+  ///
+  /// The term [TextInputAction.newline] exists in Flutter but not in Android
+  /// or iOS. The reason for introducing this term is so that developers can
+  /// achieve the common result of inserting new lines without needing to
+  /// understand the various IME actions on Android and return keys on iOS.
+  /// Thus, [TextInputAction.newline] is a convenience term that alleviates the
+  /// need to understand the underlying platforms to achieve this common behavior.
+  newline,
+}
+
+/// Configures how the platform keyboard will select an uppercase or
+/// lowercase keyboard.
+///
+/// Only supports text keyboards, other keyboard types will ignore this
+/// configuration. Capitalization is locale-aware.
+enum TextCapitalization {
+  /// Defaults to an uppercase keyboard for the first letter of each word.
+  ///
+  /// Corresponds to `InputType.TYPE_TEXT_FLAG_CAP_WORDS` on Android, and
+  /// `UITextAutocapitalizationTypeWords` on iOS.
+  words,
+
+  /// Defaults to an uppercase keyboard for the first letter of each sentence.
+  ///
+  /// Corresponds to `InputType.TYPE_TEXT_FLAG_CAP_SENTENCES` on Android, and
+  /// `UITextAutocapitalizationTypeSentences` on iOS.
+  sentences,
+
+  /// Defaults to an uppercase keyboard for each character.
+  ///
+  /// Corresponds to `InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS` on Android, and
+  /// `UITextAutocapitalizationTypeAllCharacters` on iOS.
+  characters,
+
+  /// Defaults to a lowercase keyboard.
+  none,
+}
+
+/// Controls the visual appearance of the text input control.
+///
+/// Many [TextInputAction]s are common between Android and iOS. However, if an
+/// [inputAction] is provided that is not supported by the current
+/// platform in debug mode, an error will be thrown when the corresponding
+/// text input is attached. For example, providing iOS's "emergencyCall"
+/// action when running on an Android device will result in an error when in
+/// debug mode. In release mode, incompatible [TextInputAction]s are replaced
+/// either with "unspecified" on Android, or "default" on iOS. Appropriate
+/// [inputAction]s can be chosen by checking the current platform and then
+/// selecting the appropriate action.
+///
+/// See also:
+///
+///  * [TextInput.attach]
+///  * [TextInputAction]
+@immutable
+class TextInputConfiguration {
+  /// Creates configuration information for a text input control.
+  ///
+  /// All arguments have default values, except [actionLabel]. Only
+  /// [actionLabel] may be null.
+  const TextInputConfiguration({
+    this.inputType = TextInputType.text,
+    this.readOnly = false,
+    this.obscureText = false,
+    this.autocorrect = true,
+    SmartDashesType? smartDashesType,
+    SmartQuotesType? smartQuotesType,
+    this.enableSuggestions = true,
+    this.actionLabel,
+    this.inputAction = TextInputAction.done,
+    this.keyboardAppearance = Brightness.light,
+    this.textCapitalization = TextCapitalization.none,
+    this.autofillConfiguration,
+  }) : assert(inputType != null),
+       assert(obscureText != null),
+       smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
+       smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
+       assert(autocorrect != null),
+       assert(enableSuggestions != null),
+       assert(keyboardAppearance != null),
+       assert(inputAction != null),
+       assert(textCapitalization != null);
+
+  /// The type of information for which to optimize the text input control.
+  final TextInputType inputType;
+
+  /// Whether the text field can be edited or not.
+  ///
+  /// Defaults to false.
+  final bool readOnly;
+
+  /// Whether to hide the text being edited (e.g., for passwords).
+  ///
+  /// Defaults to false.
+  final bool obscureText;
+
+  /// Whether to enable autocorrection.
+  ///
+  /// Defaults to true.
+  final bool autocorrect;
+
+  /// The configuration to use for autofill.
+  ///
+  /// Defaults to null, in which case no autofill information will be provided
+  /// to the platform. This will prevent the corresponding input field from
+  /// participating in autofills triggered by other fields. Additionally, on
+  /// Android and web, setting [autofillConfiguration] to null disables autofill.
+  final AutofillConfiguration? autofillConfiguration;
+
+  /// {@template flutter.services.TextInputConfiguration.smartDashesType}
+  /// Whether to allow the platform to automatically format dashes.
+  ///
+  /// This flag only affects iOS versions 11 and above. It sets
+  /// [`UITextSmartDashesType`](https://developer.apple.com/documentation/uikit/uitextsmartdashestype?language=objc)
+  /// in the engine. When true, it passes
+  /// [`UITextSmartDashesTypeYes`](https://developer.apple.com/documentation/uikit/uitextsmartdashestype/uitextsmartdashestypeyes?language=objc),
+  /// and when false, it passes
+  /// [`UITextSmartDashesTypeNo`](https://developer.apple.com/documentation/uikit/uitextsmartdashestype/uitextsmartdashestypeno?language=objc).
+  ///
+  /// As an example of what this does, two consecutive hyphen characters will be
+  /// automatically replaced with one en dash, and three consecutive hyphens
+  /// will become one em dash.
+  ///
+  /// Defaults to true, unless [obscureText] is true, when it defaults to false.
+  /// This is to avoid the problem where password fields receive autoformatted
+  /// characters.
+  ///
+  /// See also:
+  ///
+  ///  * [smartQuotesType]
+  ///  * <https://developer.apple.com/documentation/uikit/uitextinputtraits>
+  /// {@endtemplate}
+  final SmartDashesType smartDashesType;
+
+  /// {@template flutter.services.TextInputConfiguration.smartQuotesType}
+  /// Whether to allow the platform to automatically format quotes.
+  ///
+  /// This flag only affects iOS. It sets
+  /// [`UITextSmartQuotesType`](https://developer.apple.com/documentation/uikit/uitextsmartquotestype?language=objc)
+  /// in the engine. When true, it passes
+  /// [`UITextSmartQuotesTypeYes`](https://developer.apple.com/documentation/uikit/uitextsmartquotestype/uitextsmartquotestypeyes?language=objc),
+  /// and when false, it passes
+  /// [`UITextSmartQuotesTypeNo`](https://developer.apple.com/documentation/uikit/uitextsmartquotestype/uitextsmartquotestypeno?language=objc).
+  ///
+  /// As an example of what this does, a standard vertical double quote
+  /// character will be automatically replaced by a left or right double quote
+  /// depending on its position in a word.
+  ///
+  /// Defaults to true, unless [obscureText] is true, when it defaults to false.
+  /// This is to avoid the problem where password fields receive autoformatted
+  /// characters.
+  ///
+  /// See also:
+  ///
+  ///  * [smartDashesType]
+  ///  * <https://developer.apple.com/documentation/uikit/uitextinputtraits>
+  /// {@endtemplate}
+  final SmartQuotesType smartQuotesType;
+
+  /// {@template flutter.services.TextInputConfiguration.enableSuggestions}
+  /// Whether to show input suggestions as the user types.
+  ///
+  /// This flag only affects Android. On iOS, suggestions are tied directly to
+  /// [autocorrect], so that suggestions are only shown when [autocorrect] is
+  /// true. On Android autocorrection and suggestion are controlled separately.
+  ///
+  /// Defaults to true. Cannot be null.
+  ///
+  /// See also:
+  ///
+  ///  * <https://developer.android.com/reference/android/text/InputType.html#TYPE_TEXT_FLAG_NO_SUGGESTIONS>
+  /// {@endtemplate}
+  final bool enableSuggestions;
+
+  /// What text to display in the text input control's action button.
+  final String? actionLabel;
+
+  /// What kind of action to request for the action button on the IME.
+  final TextInputAction inputAction;
+
+  /// Specifies how platforms may automatically capitalize text entered by the
+  /// user.
+  ///
+  /// Defaults to [TextCapitalization.none].
+  ///
+  /// See also:
+  ///
+  ///  * [TextCapitalization], for a description of each capitalization behavior.
+  final TextCapitalization textCapitalization;
+
+  /// The appearance of the keyboard.
+  ///
+  /// This setting is only honored on iOS devices.
+  ///
+  /// Defaults to [Brightness.light].
+  final Brightness keyboardAppearance;
+
+  /// Returns a representation of this object as a JSON object.
+  Map<String, dynamic> toJson() {
+    return <String, dynamic>{
+      'inputType': inputType.toJson(),
+      'readOnly': readOnly,
+      'obscureText': obscureText,
+      'autocorrect': autocorrect,
+      'smartDashesType': smartDashesType.index.toString(),
+      'smartQuotesType': smartQuotesType.index.toString(),
+      'enableSuggestions': enableSuggestions,
+      'actionLabel': actionLabel,
+      'inputAction': inputAction.toString(),
+      'textCapitalization': textCapitalization.toString(),
+      'keyboardAppearance': keyboardAppearance.toString(),
+      if (autofillConfiguration != null) 'autofill': autofillConfiguration!.toJson(),
+    };
+  }
+}
+
+TextAffinity? _toTextAffinity(String? affinity) {
+  switch (affinity) {
+    case 'TextAffinity.downstream':
+      return TextAffinity.downstream;
+    case 'TextAffinity.upstream':
+      return TextAffinity.upstream;
+  }
+  return null;
+}
+
+/// A floating cursor state the user has induced by force pressing an iOS
+/// keyboard.
+enum FloatingCursorDragState {
+  /// A user has just activated a floating cursor.
+  Start,
+
+  /// A user is dragging a floating cursor.
+  Update,
+
+  /// A user has lifted their finger off the screen after using a floating
+  /// cursor.
+  End,
+}
+
+/// The current state and position of the floating cursor.
+class RawFloatingCursorPoint {
+  /// Creates information for setting the position and state of a floating
+  /// cursor.
+  ///
+  /// [state] must not be null and [offset] must not be null if the state is
+  /// [FloatingCursorDragState.Update].
+  RawFloatingCursorPoint({
+    this.offset,
+    required this.state,
+  }) : assert(state != null),
+       assert(state != FloatingCursorDragState.Update || offset != null);
+
+  /// The raw position of the floating cursor as determined by the iOS sdk.
+  final Offset? offset;
+
+  /// The state of the floating cursor.
+  final FloatingCursorDragState state;
+}
+
+/// The current text, selection, and composing state for editing a run of text.
+@immutable
+class TextEditingValue {
+  /// Creates information for editing a run of text.
+  ///
+  /// The selection and composing range must be within the text.
+  ///
+  /// The [text], [selection], and [composing] arguments must not be null but
+  /// each have default values.
+  const TextEditingValue({
+    this.text = '',
+    this.selection = const TextSelection.collapsed(offset: -1),
+    this.composing = TextRange.empty,
+  }) : assert(text != null),
+       assert(selection != null),
+       assert(composing != null);
+
+  /// Creates an instance of this class from a JSON object.
+  factory TextEditingValue.fromJSON(Map<String, dynamic> encoded) {
+    return TextEditingValue(
+      text: encoded['text'] as String,
+      selection: TextSelection(
+        baseOffset: encoded['selectionBase'] as int? ?? -1,
+        extentOffset: encoded['selectionExtent'] as int? ?? -1,
+        affinity: _toTextAffinity(encoded['selectionAffinity'] as String?) ?? TextAffinity.downstream,
+        isDirectional: encoded['selectionIsDirectional'] as bool? ?? false,
+      ),
+      composing: TextRange(
+        start: encoded['composingBase'] as int? ?? -1,
+        end: encoded['composingExtent'] as int? ?? -1,
+      ),
+    );
+  }
+
+  /// Returns a representation of this object as a JSON object.
+  Map<String, dynamic> toJSON() {
+    return <String, dynamic>{
+      'text': text,
+      'selectionBase': selection.baseOffset,
+      'selectionExtent': selection.extentOffset,
+      'selectionAffinity': selection.affinity.toString(),
+      'selectionIsDirectional': selection.isDirectional,
+      'composingBase': composing.start,
+      'composingExtent': composing.end,
+    };
+  }
+
+  /// The current text being edited.
+  final String text;
+
+  /// The range of text that is currently selected.
+  final TextSelection selection;
+
+  /// The range of text that is still being composed.
+  final TextRange composing;
+
+  /// A value that corresponds to the empty string with no selection and no composing range.
+  static const TextEditingValue empty = TextEditingValue();
+
+  /// Creates a copy of this value but with the given fields replaced with the new values.
+  TextEditingValue copyWith({
+    String? text,
+    TextSelection? selection,
+    TextRange? composing,
+  }) {
+    return TextEditingValue(
+      text: text ?? this.text,
+      selection: selection ?? this.selection,
+      composing: composing ?? this.composing,
+    );
+  }
+
+  /// Whether the [composing] range is a valid range within [text].
+  ///
+  /// Returns true if and only if the [composing] range is normalized, its start
+  /// is greater than or equal to 0, and its end is less than or equal to
+  /// [text]'s length.
+  ///
+  /// If this property is false while the [composing] range's `isValid` is true,
+  /// it usually indicates the current [composing] range is invalid because of a
+  /// programming error.
+  bool get isComposingRangeValid => composing.isValid && composing.isNormalized && composing.end <= text.length;
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'TextEditingValue')}(text: \u2524$text\u251C, selection: $selection, composing: $composing)';
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    return other is TextEditingValue
+        && other.text == text
+        && other.selection == selection
+        && other.composing == composing;
+  }
+
+  @override
+  int get hashCode => hashValues(
+    text.hashCode,
+    selection.hashCode,
+    composing.hashCode,
+  );
+}
+
+/// An interface for manipulating the selection, to be used by the implementor
+/// of the toolbar widget.
+abstract class TextSelectionDelegate {
+  /// Gets the current text input.
+  TextEditingValue get textEditingValue;
+
+  /// Indicates that the user has requested the delegate to replace its current
+  /// text editing state with [value].
+  ///
+  /// The new [value] is treated as user input and thus may subject to input
+  /// formatting.
+  ///
+  /// See also:
+  ///
+  /// * [EditableTextState.textEditingValue]: an implementation that applies
+  ///   additional pre-processing to the specified [value], before updating the
+  ///   text editing state.
+  set textEditingValue(TextEditingValue value);
+
+  /// Hides the text selection toolbar.
+  void hideToolbar();
+
+  /// Brings the provided [TextPosition] into the visible area of the text
+  /// input.
+  void bringIntoView(TextPosition position);
+
+  /// Whether cut is enabled, must not be null.
+  bool get cutEnabled => true;
+
+  /// Whether copy is enabled, must not be null.
+  bool get copyEnabled => true;
+
+  /// Whether paste is enabled, must not be null.
+  bool get pasteEnabled => true;
+
+  /// Whether select all is enabled, must not be null.
+  bool get selectAllEnabled => true;
+}
+
+/// An interface to receive information from [TextInput].
+///
+/// See also:
+///
+///  * [TextInput.attach]
+///  * [EditableText], a [TextInputClient] implementation.
+abstract class TextInputClient {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const TextInputClient();
+
+  /// The current state of the [TextEditingValue] held by this client.
+  TextEditingValue? get currentTextEditingValue;
+
+  /// The [AutofillScope] this [TextInputClient] belongs to, if any.
+  ///
+  /// It should return null if this [TextInputClient] does not need autofill
+  /// support. For a [TextInputClient] that supports autofill, returning null
+  /// causes it to participate in autofill alone.
+  ///
+  /// See also:
+  ///
+  /// * [AutofillGroup], a widget that creates an [AutofillScope] for its
+  ///   descendent autofillable [TextInputClient]s.
+  AutofillScope? get currentAutofillScope;
+
+  /// Requests that this client update its editing state to the given value.
+  ///
+  /// The new [value] is treated as user input and thus may subject to input
+  /// formatting.
+  void updateEditingValue(TextEditingValue value);
+
+  /// Requests that this client perform the given action.
+  void performAction(TextInputAction action);
+
+  /// Request from the input method that this client perform the given private
+  /// command.
+  ///
+  /// This can be used to provide domain-specific features that are only known
+  /// between certain input methods and their clients.
+  ///
+  /// See also:
+  ///   * [https://developer.android.com/reference/android/view/inputmethod/InputConnection#performPrivateCommand(java.lang.String,%20android.os.Bundle)],
+  ///     which is the Android documentation for performPrivateCommand, used to
+  ///     send a command from the input method.
+  ///   * [https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#sendAppPrivateCommand],
+  ///     which is the Android documentation for sendAppPrivateCommand, used to
+  ///     send a command to the input method.
+  void performPrivateCommand(String action, Map<String, dynamic> data);
+
+  /// Updates the floating cursor position and state.
+  void updateFloatingCursor(RawFloatingCursorPoint point);
+
+  /// Requests that this client display a prompt rectangle for the given text range,
+  /// to indicate the range of text that will be changed by a pending autocorrection.
+  ///
+  /// This method will only be called on iOS.
+  void showAutocorrectionPromptRect(int start, int end);
+
+  /// Platform notified framework of closed connection.
+  ///
+  /// [TextInputClient] should cleanup its connection and finalize editing.
+  void connectionClosed();
+}
+
+/// An interface for interacting with a text input control.
+///
+/// See also:
+///
+///  * [TextInput.attach], a method used to establish a [TextInputConnection]
+///    between the system's text input and a [TextInputClient].
+///  * [EditableText], a [TextInputClient] that connects to and interacts with
+///    the system's text input using a [TextInputConnection].
+class TextInputConnection {
+  TextInputConnection._(this._client)
+      : assert(_client != null),
+        _id = _nextId++;
+
+  Size? _cachedSize;
+  Matrix4? _cachedTransform;
+  Rect? _cachedRect;
+
+  static int _nextId = 1;
+  final int _id;
+
+  /// Resets the internal ID counter for testing purposes.
+  ///
+  /// This call has no effect when asserts are disabled. Calling it from
+  /// application code will likely break text input for the application.
+  @visibleForTesting
+  static void debugResetId({int to = 1}) {
+    assert(to != null);
+    assert(() {
+      _nextId = to;
+      return true;
+    }());
+  }
+
+  final TextInputClient _client;
+
+  /// Whether this connection is currently interacting with the text input control.
+  bool get attached => TextInput._instance._currentConnection == this;
+
+  /// Requests that the text input control become visible.
+  void show() {
+    assert(attached);
+    TextInput._instance._show();
+  }
+
+  /// Requests the system autofill UI to appear.
+  ///
+  /// Currently only works on Android. Other platforms do not respond to this
+  /// message.
+  ///
+  /// See also:
+  ///
+  ///  * [EditableText], a [TextInputClient] that calls this method when focused.
+  void requestAutofill() {
+    assert(attached);
+    TextInput._instance._requestAutofill();
+  }
+
+  /// Requests that the text input control update itself according to the new
+  /// [TextInputConfiguration].
+  void updateConfig(TextInputConfiguration configuration) {
+    assert(attached);
+    TextInput._instance._updateConfig(configuration);
+  }
+
+  /// Requests that the text input control change its internal state to match
+  /// the given state.
+  void setEditingState(TextEditingValue value) {
+    assert(attached);
+    TextInput._instance._setEditingState(value);
+  }
+
+  /// Send the size and transform of the editable text to engine.
+  ///
+  /// The values are sent as platform messages so they can be used on web for
+  /// example to correctly position and size the html input field.
+  ///
+  /// 1. [editableBoxSize]: size of the render editable box.
+  ///
+  /// 2. [transform]: a matrix that maps the local paint coordinate system
+  ///                 to the [PipelineOwner.rootNode].
+  void setEditableSizeAndTransform(Size editableBoxSize, Matrix4 transform) {
+    if (editableBoxSize != _cachedSize || transform != _cachedTransform) {
+      _cachedSize = editableBoxSize;
+      _cachedTransform = transform;
+      TextInput._instance._setEditableSizeAndTransform(
+        <String, dynamic>{
+          'width': editableBoxSize.width,
+          'height': editableBoxSize.height,
+          'transform': transform.storage,
+        },
+      );
+    }
+  }
+
+  /// Send the smallest rect that covers the text in the client that's currently
+  /// being composed.
+  ///
+  /// The given `rect` can not be null. If any of the 4 coordinates of the given
+  /// [Rect] is not finite, a [Rect] of size (-1, -1) will be sent instead.
+  ///
+  /// The information is currently only used on iOS, for positioning the IME bar.
+  void setComposingRect(Rect rect) {
+    assert(rect != null);
+    if (rect == _cachedRect)
+      return;
+    _cachedRect = rect;
+    final Rect validRect = rect.isFinite ? rect : Offset.zero & const Size(-1, -1);
+    TextInput._instance._setComposingTextRect(
+      <String, dynamic>{
+        'width': validRect.width,
+        'height': validRect.height,
+        'x': validRect.left,
+        'y': validRect.top,
+      },
+    );
+  }
+
+  /// Send text styling information.
+  ///
+  /// This information is used by the Flutter Web Engine to change the style
+  /// of the hidden native input's content. Hence, the content size will match
+  /// to the size of the editable widget's content.
+  void setStyle({
+    required String? fontFamily,
+    required double? fontSize,
+    required FontWeight? fontWeight,
+    required TextDirection textDirection,
+    required TextAlign textAlign,
+  }) {
+    assert(attached);
+
+    TextInput._instance._setStyle(
+      <String, dynamic>{
+        'fontFamily': fontFamily,
+        'fontSize': fontSize,
+        'fontWeightIndex': fontWeight?.index,
+        'textAlignIndex': textAlign.index,
+        'textDirectionIndex': textDirection.index,
+      },
+    );
+  }
+
+  /// Stop interacting with the text input control.
+  ///
+  /// After calling this method, the text input control might disappear if no
+  /// other client attaches to it within this animation frame.
+  void close() {
+    if (attached) {
+      TextInput._instance._clearClient();
+    }
+    assert(!attached);
+  }
+
+  /// Platform sent a notification informing the connection is closed.
+  ///
+  /// [TextInputConnection] should clean current client connection.
+  void connectionClosedReceived() {
+    TextInput._instance._currentConnection = null;
+    assert(!attached);
+  }
+}
+
+TextInputAction _toTextInputAction(String action) {
+  switch (action) {
+    case 'TextInputAction.none':
+      return TextInputAction.none;
+    case 'TextInputAction.unspecified':
+      return TextInputAction.unspecified;
+    case 'TextInputAction.go':
+      return TextInputAction.go;
+    case 'TextInputAction.search':
+      return TextInputAction.search;
+    case 'TextInputAction.send':
+      return TextInputAction.send;
+    case 'TextInputAction.next':
+      return TextInputAction.next;
+    case 'TextInputAction.previous':
+      return TextInputAction.previous;
+    case 'TextInputAction.continue_action':
+      return TextInputAction.continueAction;
+    case 'TextInputAction.join':
+      return TextInputAction.join;
+    case 'TextInputAction.route':
+      return TextInputAction.route;
+    case 'TextInputAction.emergencyCall':
+      return TextInputAction.emergencyCall;
+    case 'TextInputAction.done':
+      return TextInputAction.done;
+    case 'TextInputAction.newline':
+      return TextInputAction.newline;
+  }
+  throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Unknown text input action: $action')]);
+}
+
+FloatingCursorDragState _toTextCursorAction(String state) {
+  switch (state) {
+    case 'FloatingCursorDragState.start':
+      return FloatingCursorDragState.Start;
+    case 'FloatingCursorDragState.update':
+      return FloatingCursorDragState.Update;
+    case 'FloatingCursorDragState.end':
+      return FloatingCursorDragState.End;
+  }
+  throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Unknown text cursor action: $state')]);
+}
+
+RawFloatingCursorPoint _toTextPoint(FloatingCursorDragState state, Map<String, dynamic> encoded) {
+  assert(state != null, 'You must provide a state to set a new editing point.');
+  assert(encoded['X'] != null, 'You must provide a value for the horizontal location of the floating cursor.');
+  assert(encoded['Y'] != null, 'You must provide a value for the vertical location of the floating cursor.');
+  final Offset offset = state == FloatingCursorDragState.Update
+    ? Offset(encoded['X'] as double, encoded['Y'] as double)
+    : const Offset(0, 0);
+  return RawFloatingCursorPoint(offset: offset, state: state);
+}
+
+/// An low-level interface to the system's text input control.
+///
+/// To start interacting with the system's text input control, call [attach] to
+/// establish a [TextInputConnection] between the system's text input control
+/// and a [TextInputClient]. The majority of commands available for
+/// interacting with the text input control reside in the returned
+/// [TextInputConnection]. The communication between the system text input and
+/// the [TextInputClient] is asynchronous.
+///
+/// The platform text input plugin (which represents the system's text input)
+/// and the [TextInputClient] usually maintain their own text editing states
+/// ([TextEditingValue]) separately. They must be kept in sync as long as the
+/// [TextInputClient] is connected. The following methods can be used to send
+/// [TextEditingValue] to update the other party, when either party's text
+/// editing states change:
+///
+/// * The [TextInput.attach] method allows a [TextInputClient] to establish a
+///   connection to the text input. An optional field in its `configuration`
+///   parameter can be used to specify an initial value for the platform text
+///   input plugin's [TextEditingValue].
+///
+/// * The [TextInputClient] sends its [TextEditingValue] to the platform text
+///   input plugin using [TextInputConnection.setEditingState].
+///
+/// * The platform text input plugin sends its [TextEditingValue] to the
+///   connected [TextInputClient] via a "TextInput.setEditingState" message.
+///
+/// * When autofill happens on a disconnected [TextInputClient], the platform
+///   text input plugin sends the [TextEditingValue] to the connected
+///   [TextInputClient]'s [AutofillScope], and the [AutofillScope] will further
+///   relay the value to the correct [TextInputClient].
+///
+/// When synchronizing the [TextEditingValue]s, the communication may get stuck
+/// in an infinite when both parties are trying to send their own update. To
+/// mitigate the problem, only [TextInputClient]s are allowed to alter the
+/// received [TextEditingValue]s while platform text input plugins are to accept
+/// the received [TextEditingValue]s unmodified. More specifically:
+///
+/// * When a [TextInputClient] receives a new [TextEditingValue] from the
+///   platform text input plugin, it's allowed to modify the value (for example,
+///   apply [TextInputFormatter]s). If it decides to do so, it must send the
+///   updated [TextEditingValue] back to the platform text input plugin to keep
+///   the [TextEditingValue]s in sync.
+///
+/// * When the platform text input plugin receives a new value from the
+///   connected [TextInputClient], it must accept the new value as-is, to avoid
+///   sending back an updated value.
+///
+/// See also:
+///
+///  * [TextField], a widget in which the user may enter text.
+///  * [EditableText], a [TextInputClient] that connects to [TextInput] when it
+///    wants to take user input from the keyboard.
+class TextInput {
+  TextInput._() {
+    _channel = SystemChannels.textInput;
+    _channel.setMethodCallHandler(_handleTextInputInvocation);
+  }
+
+  /// Set the [MethodChannel] used to communicate with the system's text input
+  /// control.
+  ///
+  /// This is only meant for testing within the Flutter SDK. Changing this
+  /// will break the ability to input text. This has no effect if asserts are
+  /// disabled.
+  @visibleForTesting
+  static void setChannel(MethodChannel newChannel) {
+    assert(() {
+      _instance._channel = newChannel..setMethodCallHandler(_instance._handleTextInputInvocation);
+      return true;
+    }());
+  }
+
+  static final TextInput _instance = TextInput._();
+
+  static const List<TextInputAction> _androidSupportedInputActions = <TextInputAction>[
+    TextInputAction.none,
+    TextInputAction.unspecified,
+    TextInputAction.done,
+    TextInputAction.send,
+    TextInputAction.go,
+    TextInputAction.search,
+    TextInputAction.next,
+    TextInputAction.previous,
+    TextInputAction.newline,
+  ];
+
+  static const List<TextInputAction> _iOSSupportedInputActions = <TextInputAction>[
+    TextInputAction.unspecified,
+    TextInputAction.done,
+    TextInputAction.send,
+    TextInputAction.go,
+    TextInputAction.search,
+    TextInputAction.next,
+    TextInputAction.newline,
+    TextInputAction.continueAction,
+    TextInputAction.join,
+    TextInputAction.route,
+    TextInputAction.emergencyCall,
+  ];
+
+  /// Begin interacting with the text input control.
+  ///
+  /// Calling this function helps multiple clients coordinate about which one is
+  /// currently interacting with the text input control. The returned
+  /// [TextInputConnection] provides an interface for actually interacting with
+  /// the text input control.
+  ///
+  /// A client that no longer wishes to interact with the text input control
+  /// should call [TextInputConnection.close] on the returned
+  /// [TextInputConnection].
+  static TextInputConnection attach(TextInputClient client, TextInputConfiguration configuration) {
+    assert(client != null);
+    assert(configuration != null);
+    final TextInputConnection connection = TextInputConnection._(client);
+    _instance._attach(connection, configuration);
+    return connection;
+  }
+
+  /// This method actually notifies the embedding of the client. It is utilized
+  /// by [attach] and by [_handleTextInputInvocation] for the
+  /// `TextInputClient.requestExistingInputState` method.
+  void _attach(TextInputConnection connection, TextInputConfiguration configuration) {
+    assert(connection != null);
+    assert(connection._client != null);
+    assert(configuration != null);
+    assert(_debugEnsureInputActionWorksOnPlatform(configuration.inputAction));
+    _channel.invokeMethod<void>(
+      'TextInput.setClient',
+      <dynamic>[ connection._id, configuration.toJson() ],
+    );
+    _currentConnection = connection;
+    _currentConfiguration = configuration;
+  }
+
+  static bool _debugEnsureInputActionWorksOnPlatform(TextInputAction inputAction) {
+    assert(() {
+      if (kIsWeb) {
+        // TODO(flutterweb): what makes sense here?
+        return true;
+      }
+      if (Platform.isIOS) {
+        assert(
+          _iOSSupportedInputActions.contains(inputAction),
+          'The requested TextInputAction "$inputAction" is not supported on iOS.',
+        );
+      } else if (Platform.isAndroid) {
+        assert(
+          _androidSupportedInputActions.contains(inputAction),
+          'The requested TextInputAction "$inputAction" is not supported on Android.',
+        );
+      }
+      return true;
+    }());
+    return true;
+  }
+
+  late MethodChannel _channel;
+
+  TextInputConnection? _currentConnection;
+  late TextInputConfiguration _currentConfiguration;
+
+  Future<dynamic> _handleTextInputInvocation(MethodCall methodCall) async {
+    if (_currentConnection == null)
+      return;
+    final String method = methodCall.method;
+
+    // The requestExistingInputState request needs to be handled regardless of
+    // the client ID, as long as we have a _currentConnection.
+    if (method == 'TextInputClient.requestExistingInputState') {
+      assert(_currentConnection!._client != null);
+      _attach(_currentConnection!, _currentConfiguration);
+      final TextEditingValue? editingValue = _currentConnection!._client.currentTextEditingValue;
+      if (editingValue != null) {
+        _setEditingState(editingValue);
+      }
+      return;
+    }
+
+    final List<dynamic> args = methodCall.arguments as List<dynamic>;
+
+    if (method == 'TextInputClient.updateEditingStateWithTag') {
+      final TextInputClient client = _currentConnection!._client;
+      assert(client != null);
+      final AutofillScope? scope = client.currentAutofillScope;
+      final Map<String, dynamic> editingValue = args[1] as Map<String, dynamic>;
+      for (final String tag in editingValue.keys) {
+        final TextEditingValue textEditingValue = TextEditingValue.fromJSON(
+          editingValue[tag] as Map<String, dynamic>,
+        );
+        scope?.getAutofillClient(tag)?.updateEditingValue(textEditingValue);
+      }
+
+      return;
+    }
+
+    final int client = args[0] as int;
+    // The incoming message was for a different client.
+    if (client != _currentConnection!._id)
+      return;
+    switch (method) {
+      case 'TextInputClient.updateEditingState':
+        _currentConnection!._client.updateEditingValue(TextEditingValue.fromJSON(args[1] as Map<String, dynamic>));
+        break;
+      case 'TextInputClient.performAction':
+        _currentConnection!._client.performAction(_toTextInputAction(args[1] as String));
+        break;
+      case 'TextInputClient.performPrivateCommand':
+        _currentConnection!._client.performPrivateCommand(
+          args[1]['action'] as String, args[1]['data'] as Map<String, dynamic>);
+        break;
+      case 'TextInputClient.updateFloatingCursor':
+        _currentConnection!._client.updateFloatingCursor(_toTextPoint(
+          _toTextCursorAction(args[1] as String),
+          args[2] as Map<String, dynamic>,
+        ));
+        break;
+      case 'TextInputClient.onConnectionClosed':
+        _currentConnection!._client.connectionClosed();
+        break;
+      case 'TextInputClient.showAutocorrectionPromptRect':
+        _currentConnection!._client.showAutocorrectionPromptRect(args[1] as int, args[2] as int);
+        break;
+      default:
+        throw MissingPluginException();
+    }
+  }
+
+  bool _hidePending = false;
+
+  void _scheduleHide() {
+    if (_hidePending)
+      return;
+    _hidePending = true;
+
+    // Schedule a deferred task that hides the text input. If someone else
+    // shows the keyboard during this update cycle, then the task will do
+    // nothing.
+    scheduleMicrotask(() {
+      _hidePending = false;
+      if (_currentConnection == null)
+        _channel.invokeMethod<void>('TextInput.hide');
+    });
+  }
+
+  void _clearClient() {
+    _channel.invokeMethod<void>('TextInput.clearClient');
+    _currentConnection = null;
+    _scheduleHide();
+  }
+
+  void _updateConfig(TextInputConfiguration configuration) {
+    assert(configuration != null);
+    _channel.invokeMethod<void>(
+      'TextInput.updateConfig',
+      configuration.toJson(),
+    );
+  }
+
+  void _setEditingState(TextEditingValue value) {
+    assert(value != null);
+    _channel.invokeMethod<void>(
+      'TextInput.setEditingState',
+      value.toJSON(),
+    );
+  }
+
+  void _show() {
+    _channel.invokeMethod<void>('TextInput.show');
+  }
+
+  void _requestAutofill() {
+    _channel.invokeMethod<void>('TextInput.requestAutofill');
+  }
+
+  void _setEditableSizeAndTransform(Map<String, dynamic> args) {
+    _channel.invokeMethod<void>(
+      'TextInput.setEditableSizeAndTransform',
+      args,
+    );
+  }
+
+  void _setComposingTextRect(Map<String, dynamic> args) {
+    _channel.invokeMethod<void>(
+      'TextInput.setMarkedTextRect',
+      args,
+    );
+  }
+
+  void _setStyle(Map<String, dynamic> args) {
+    _channel.invokeMethod<void>(
+      'TextInput.setStyle',
+      args,
+    );
+  }
+
+  /// Finishes the current autofill context, and potentially saves the user
+  /// input for future use if `shouldSave` is true.
+  ///
+  /// Typically, this method should be called when the user has finalized their
+  /// input. For example, in a [Form], it's typically done immediately before or
+  /// after its content is submitted.
+  ///
+  /// The topmost [AutofillGroup]s also call [finishAutofillContext]
+  /// automatically when they are disposed. The default behavior can be
+  /// overridden in [AutofillGroup.onDisposeAction].
+  ///
+  /// {@template flutter.services.TextInput.finishAutofillContext}
+  /// An autofill context is a collection of input fields that live in the
+  /// platform's text input plugin. The platform is encouraged to save the user
+  /// input stored in the current autofill context before the context is
+  /// destroyed, when [TextInput.finishAutofillContext] is called with
+  /// `shouldSave` set to true.
+  ///
+  /// Currently, there can only be at most one autofill context at any given
+  /// time. When any input field in an [AutofillGroup] requests for autofill
+  /// (which is done automatically when an autofillable [EditableText] gains
+  /// focus), the current autofill context will merge the content of that
+  /// [AutofillGroup] into itself. When there isn't an existing autofill context,
+  /// one will be created to hold the newly added input fields from the group.
+  ///
+  /// Once added to an autofill context, an input field will stay in the context
+  /// until the context is destroyed. To prevent leaks, call
+  /// [TextInput.finishAutofillContext] to signal the text input plugin that the
+  /// user has finalized their input in the current autofill context. The
+  /// platform text input plugin either encourages or discourages the platform
+  /// from saving the user input based on the value of the `shouldSave`
+  /// parameter. The platform usually shows a "Save for autofill?" prompt for
+  /// user confirmation.
+  /// {@endtemplate}
+  ///
+  /// On many platforms, calling [finishAutofillContext] shows the save user
+  /// input dialog and disrupts the user's flow. Ideally the dialog should only
+  /// be shown no more than once for every screen. Consider removing premature
+  /// [finishAutofillContext] calls to prevent showing the save user input UI
+  /// too frequently. However, calling [finishAutofillContext] when there's no
+  /// existing autofill context usually does not bring up the save user input
+  /// UI.
+  ///
+  /// See also:
+  ///
+  /// * [EditableText.autofillHints] for autofill save troubleshooting tips.
+  /// * [AutofillGroup.onDisposeAction], a configurable action that runs when a
+  ///   topmost [AutofillGroup] is getting disposed.
+  static void finishAutofillContext({ bool shouldSave = true }) {
+    assert(shouldSave != null);
+    TextInput._instance._channel.invokeMethod<void>(
+      'TextInput.finishAutofillContext',
+      shouldSave ,
+    );
+  }
+}
diff --git a/lib/src/ui/annotations.dart b/lib/src/ui/annotations.dart
new file mode 100644
index 0000000..a77fabe
--- /dev/null
+++ b/lib/src/ui/annotations.dart
@@ -0,0 +1,46 @@
+// 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.
+
+// TODO(dnfield): Remove unused_import ignores when https://github.com/dart-lang/sdk/issues/35164 is resolved.
+
+
+part of dart.ui;
+
+// TODO(dnfield): Update this if/when we default this to on in the tool,
+// see: https://github.com/flutter/flutter/issues/52759
+/// Annotation used by Flutter's Dart compiler to indicate that an
+/// [Object.toString] override should not be replaced with a supercall.
+///
+/// Since `dart:ui` and `package:flutter` override `toString` purely for
+/// debugging purposes, the frontend compiler is instructed to replace all
+/// `toString` bodies with `return super.toString()` during compilation. This
+/// significantly reduces release code size, and would make it impossible to
+/// implement a meaningful override of `toString` for release mode without
+/// disabling the feature and losing the size savings. If a package uses this
+/// feature and has some unavoidable need to keep the `toString` implementation
+/// for a specific class, applying this annotation will direct the compiler
+/// to leave the method body as-is.
+///
+/// For example, in the following class the `toString` method will remain as
+/// `return _buffer.toString();`, even if the  `--delete-tostring-package-uri`
+/// option would otherwise apply and replace it with `return super.toString()`.
+///
+/// ```dart
+/// class MyStringBuffer {
+///   StringBuffer _buffer = StringBuffer();
+///
+///   // ...
+///
+///   @keepToString
+///   @override
+///   String toString() {
+///     return _buffer.toString();
+///   }
+/// }
+/// ```
+const _KeepToString keepToString = _KeepToString();
+
+class _KeepToString {
+  const _KeepToString();
+}
diff --git a/lib/src/ui/channel_buffers.dart b/lib/src/ui/channel_buffers.dart
new file mode 100644
index 0000000..ceb3f9f
--- /dev/null
+++ b/lib/src/ui/channel_buffers.dart
@@ -0,0 +1,556 @@
+// 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.
+
+
+// KEEP THIS SYNCHRONIZED WITH ../web_ui/lib/src/ui/channel_buffers.dart
+
+part of dart.ui;
+
+/// Signature for [ChannelBuffers.drain]'s `callback` argument.
+///
+/// The first argument is the data sent by the plugin.
+///
+/// The second argument is a closure that, when called, will send messages
+/// back to the plugin.
+// TODO(ianh): deprecate this once the framework is migrated to [ChannelCallback].
+typedef DrainChannelCallback = Future<void> Function(ByteData? data, PlatformMessageResponseCallback callback);
+
+/// Signature for [ChannelBuffers.setListener]'s `callback` argument.
+///
+/// The first argument is the data sent by the plugin.
+///
+/// The second argument is a closure that, when called, will send messages
+/// back to the plugin.
+///
+/// See also:
+///
+///  * [PlatformMessageResponseCallback], the type used for replies.
+typedef ChannelCallback = void Function(ByteData? data, PlatformMessageResponseCallback callback);
+
+/// The data and logic required to store and invoke a callback.
+///
+/// This tracks (and applies) the [Zone].
+class _ChannelCallbackRecord {
+  _ChannelCallbackRecord(this._callback) : _zone = Zone.current;
+  final ChannelCallback _callback;
+  final Zone _zone;
+
+  /// Call [callback] in [zone], using the given arguments.
+  void invoke(ByteData? dataArg, PlatformMessageResponseCallback callbackArg) {
+    _invoke2<ByteData?, PlatformMessageResponseCallback>(_callback, _zone, dataArg, callbackArg);
+  }
+}
+
+/// A saved platform message for a channel with its callback.
+class _StoredMessage {
+  /// Wraps the data and callback for a platform message into
+  /// a [_StoredMessage] instance.
+  ///
+  /// The first argument is a [ByteData] that represents the
+  /// payload of the message and a [PlatformMessageResponseCallback]
+  /// that represents the callback that will be called when the message
+  /// is handled.
+  _StoredMessage(this.data, this._callback) : _zone = Zone.current;
+
+  /// Representation of the message's payload.
+  final ByteData? data;
+
+  /// Callback to be used when replying to the message.
+  final PlatformMessageResponseCallback _callback;
+
+  final Zone _zone;
+
+  void invoke(ByteData? dataArg) {
+    _invoke1(_callback, _zone, dataArg);
+  }
+}
+
+/// The internal storage for a platform channel.
+///
+/// This consists of a fixed-size circular queue of [_StoredMessage]s,
+/// and the channel's callback, if any has been registered.
+class _Channel {
+  _Channel([ this._capacity = ChannelBuffers.kDefaultBufferSize ])
+    : _queue = collection.ListQueue<_StoredMessage>(_capacity);
+
+  /// The underlying data for the buffered messages.
+  final collection.ListQueue<_StoredMessage> _queue;
+
+  /// The number of messages currently in the [_Channel].
+  ///
+  /// This is equal to or less than the [capacity].
+  int get length => _queue.length;
+
+  /// Whether to dump messages to the console when a message is
+  /// discarded due to the channel overflowing.
+  ///
+  /// Has no effect in release builds.
+  bool debugEnableDiscardWarnings = true;
+
+  /// The number of messages that _can_ be stored in the [_Channel].
+  ///
+  /// When additional messages are stored, earlier ones are discarded,
+  /// in a first-in-first-out fashion.
+  int get capacity => _capacity;
+  int _capacity;
+  /// Set the [capacity] of the channel to the given size.
+  ///
+  /// If the new size is smaller than the [length], the oldest
+  /// messages are discarded until the capacity is reached. No
+  /// message is shown in case of overflow, regardless of the
+  /// value of [debugEnableDiscardWarnings].
+  set capacity(int newSize) {
+    _capacity = newSize;
+    _dropOverflowMessages(newSize);
+  }
+
+  /// Whether a microtask is queued to call [_drainStep].
+  ///
+  /// This is used to queue messages received while draining, rather
+  /// than sending them out of order. This generally cannot happen in
+  /// production but is possible in test scenarios.
+  ///
+  /// This is also necessary to avoid situations where multiple drains are
+  /// invoked simultaneously. For example, if a listener is set
+  /// (queuing a drain), then unset, then set again (which would queue
+  /// a drain again), all in one stack frame (not allowing the drain
+  /// itself an opportunity to check if a listener is set).
+  bool _draining = false;
+
+  /// Adds a message to the channel.
+  ///
+  /// If the channel overflows, earlier messages are discarded, in a
+  /// first-in-first-out fashion. See [capacity]. If
+  /// [debugEnableDiscardWarnings] is true, this method returns true
+  /// on overflow. It is the responsibility of the caller to show the
+  /// warning message.
+  bool push(_StoredMessage message) {
+    if (!_draining && _channelCallbackRecord != null) {
+      assert(_queue.isEmpty);
+      _channelCallbackRecord!.invoke(message.data, message.invoke);
+      return false;
+    }
+    if (_capacity <= 0) {
+      return debugEnableDiscardWarnings;
+    }
+    final bool result = _dropOverflowMessages(_capacity - 1);
+    _queue.addLast(message);
+    return result;
+  }
+
+  /// Returns the first message in the channel and removes it.
+  ///
+  /// Throws when empty.
+  _StoredMessage pop() => _queue.removeFirst();
+
+  /// Removes messages until [length] reaches `lengthLimit`.
+  ///
+  /// The callback of each removed message is invoked with null
+  /// as its argument.
+  ///
+  /// If any messages are removed, and [debugEnableDiscardWarnings] is
+  /// true, then returns true. The caller is responsible for showing
+  /// the warning message in that case.
+  bool _dropOverflowMessages(int lengthLimit) {
+    bool result = false;
+    while (_queue.length > lengthLimit) {
+      final _StoredMessage message = _queue.removeFirst();
+      message.invoke(null); // send empty reply to the plugin side
+      result = true;
+    }
+    return result;
+  }
+
+  _ChannelCallbackRecord? _channelCallbackRecord;
+
+  /// Sets the listener for this channel.
+  ///
+  /// When there is a listener, messages are sent immediately.
+  ///
+  /// If any messages were queued before the listener is added,
+  /// they are drained asynchronously after this method returns.
+  /// (See [_drain].)
+  ///
+  /// Only one listener may be set at a time. Setting a
+  /// new listener clears the previous one.
+  ///
+  /// Callbacks are invoked in their own stack frame and
+  /// use the zone that was current when the callback was
+  /// registered.
+  void setListener(ChannelCallback callback) {
+    final bool needDrain = _channelCallbackRecord == null;
+    _channelCallbackRecord = _ChannelCallbackRecord(callback);
+    if (needDrain && !_draining)
+      _drain();
+  }
+
+  /// Clears the listener for this channel.
+  ///
+  /// When there is no listener, messages are queued, up to [capacity],
+  /// and then discarded in a first-in-first-out fashion.
+  void clearListener() {
+    _channelCallbackRecord = null;
+  }
+
+  /// Drains all the messages in the channel (invoking the currently
+  /// registered listener for each one).
+  ///
+  /// Each message is handled in its own microtask. No messages can
+  /// be queued by plugins while the queue is being drained, but any
+  /// microtasks queued by the handler itself will be processed before
+  /// the next message is handled.
+  ///
+  /// The draining stops if the listener is removed.
+  ///
+  /// See also:
+  ///
+  ///  * [setListener], which is used to register the callback.
+  ///  * [clearListener], which removes it.
+  void _drain() {
+    assert(!_draining);
+    _draining = true;
+    scheduleMicrotask(_drainStep);
+  }
+
+  /// Drains a single message and then reinvokes itself asynchronously.
+  ///
+  /// See [_drain] for more details.
+  void _drainStep() {
+    assert(_draining);
+    if (_queue.isNotEmpty && _channelCallbackRecord != null) {
+      final _StoredMessage message = pop();
+      _channelCallbackRecord!.invoke(message.data, message.invoke);
+      scheduleMicrotask(_drainStep);
+    } else {
+      _draining = false;
+    }
+  }
+}
+
+/// The buffering and dispatch mechanism for messages sent by plugins
+/// on the engine side to their corresponding plugin code on the
+/// framework side.
+///
+/// Messages for a channel are stored until a listener is provided for that channel,
+/// using [setListener]. Only one listener may be configured per channel.
+///
+/// Typically these buffers are drained once a callback is setup on
+/// the [BinaryMessenger] in the Flutter framework. (See [setListener].)
+///
+/// ## Buffer capacity and overflow
+///
+/// Each channel has a finite buffer capacity and messages will
+/// be deleted in a first-in-first-out (FIFO) manner if the capacity is exceeded.
+///
+/// By default buffers store one message per channel, and when a
+/// message overflows, in debug mode, a message is printed to the
+/// console. The message looks like the following:
+///
+/// ```
+/// A message on the com.example channel was discarded before it could be
+/// handled.
+/// This happens when a plugin sends messages to the framework side before the
+/// framework has had an opportunity to register a listener. See the
+/// ChannelBuffers API documentation for details on how to configure the channel
+/// to expect more messages, or to expect messages to get discarded:
+///   https://api.flutter.dev/flutter/dart-ui/ChannelBuffers-class.html
+/// ```
+///
+/// There are tradeoffs associated with any size. The correct size
+/// should be chosen for the semantics of the channel. To change the
+/// size a plugin can send a message using the control channel,
+/// as described below.
+///
+/// Size 0 is appropriate for channels where channels sent before
+/// the engine and framework are ready should be ignored. For
+/// example, a plugin that notifies the framework any time a
+/// radiation sensor detects an ionization event might set its size
+/// to zero since past ionization events are typically not
+/// interesting, only instantaneous readings are worth tracking.
+///
+/// Size 1 is appropriate for level-triggered plugins. For example,
+/// a plugin that notifies the framework of the current value of a
+/// pressure sensor might leave its size at one (the default), while
+/// sending messages continually; once the framework side of the plugin
+/// registers with the channel, it will immediately receive the most
+/// up to date value and earlier messages will have been discarded.
+///
+/// Sizes greater than one are appropriate for plugins where every
+/// message is important. For example, a plugin that itself
+/// registers with another system that has been buffering events,
+/// and immediately forwards all the previously-buffered events,
+/// would likely wish to avoid having any messages dropped on the
+/// floor. In such situations, it is important to select a size that
+/// will avoid overflows. It is also important to consider the
+/// potential for the framework side to never fully initialize (e.g. if
+/// the user starts the application, but terminates it soon
+/// afterwards, leaving time for the platform side of a plugin to
+/// run but not the framework side).
+///
+/// ## The control channel
+///
+/// A plugin can configure its channel's buffers by sending messages to the
+/// control channel, `dev.flutter/channel-buffers` (see [kControlChannelName]).
+///
+/// There are two messages that can be sent to this control channel, to adjust
+/// the buffer size and to disable the overflow warnings. See [handleMessage]
+/// for details on these messages.
+class ChannelBuffers {
+  /// Create a buffer pool for platform messages.
+  ///
+  /// It is generally not necessary to create an instance of this class;
+  /// the global [channelBuffers] instance is the one used by the engine.
+  ChannelBuffers();
+
+  /// The number of messages that channel buffers will store by default.
+  static const int kDefaultBufferSize = 1;
+
+  /// The name of the channel that plugins can use to communicate with the
+  /// channel buffers system.
+  ///
+  /// These messages are handled by [handleMessage].
+  static const String kControlChannelName = 'dev.flutter/channel-buffers';
+
+  /// A mapping between a channel name and its associated [_Channel].
+  final Map<String, _Channel> _channels = <String, _Channel>{};
+
+  /// Adds a message (`data`) to the named channel buffer (`name`).
+  ///
+  /// The `callback` argument is a closure that, when called, will send messages
+  /// back to the plugin.
+  ///
+  /// If a message overflows the channel, and the channel has not been
+  /// configured to expect overflow, then, in debug mode, a message
+  /// will be printed to the console warning about the overflow.
+  void push(String name, ByteData? data, PlatformMessageResponseCallback callback) {
+    final _Channel channel = _channels.putIfAbsent(name, () => _Channel());
+    if (channel.push(_StoredMessage(data, callback))) {
+      _printDebug(
+        'A message on the $name channel was discarded before it could be handled.\n'
+        'This happens when a plugin sends messages to the framework side before the '
+        'framework has had an opportunity to register a listener. See the ChannelBuffers '
+        'API documentation for details on how to configure the channel to expect more '
+        'messages, or to expect messages to get discarded:\n'
+        '  https://api.flutter.dev/flutter/dart-ui/ChannelBuffers-class.html'
+      );
+    }
+  }
+
+  /// Sets the listener for the specified channel.
+  ///
+  /// When there is a listener, messages are sent immediately.
+  ///
+  /// Each channel may have up to one listener set at a time. Setting
+  /// a new listener on a channel with an existing listener clears the
+  /// previous one.
+  ///
+  /// Callbacks are invoked in their own stack frame and
+  /// use the zone that was current when the callback was
+  /// registered.
+  ///
+  /// ## Draining
+  ///
+  /// If any messages were queued before the listener is added,
+  /// they are drained asynchronously after this method returns.
+  ///
+  /// Each message is handled in its own microtask. No messages can
+  /// be queued by plugins while the queue is being drained, but any
+  /// microtasks queued by the handler itself will be processed before
+  /// the next message is handled.
+  ///
+  /// The draining stops if the listener is removed.
+  void setListener(String name, ChannelCallback callback) {
+    final _Channel channel = _channels.putIfAbsent(name, () => _Channel());
+    channel.setListener(callback);
+  }
+
+  /// Clears the listener for the specified channel.
+  ///
+  /// When there is no listener, messages on that channel are queued,
+  /// up to [kDefaultBufferSize] (or the size configured via the
+  /// control channel), and then discarded in a first-in-first-out
+  /// fashion.
+  void clearListener(String name) {
+    final _Channel? channel = _channels[name];
+    if (channel != null)
+      channel.clearListener();
+  }
+
+  /// Remove and process all stored messages for a given channel.
+  ///
+  /// This should be called once a channel is prepared to handle messages
+  /// (i.e. when a message handler is setup in the framework).
+  ///
+  /// The messages are processed by calling the given `callback`. Each message
+  /// is processed in its own microtask.
+  // TODO(ianh): deprecate once framework uses [setListener].
+  Future<void> drain(String name, DrainChannelCallback callback) async {
+    final _Channel? channel = _channels[name];
+    while (channel != null && !channel._queue.isEmpty) {
+      final _StoredMessage message = channel.pop();
+      await callback(message.data, message.invoke);
+    }
+  }
+
+  /// Handle a control message.
+  ///
+  /// This is intended to be called by the platform messages dispatcher, forwarding
+  /// messages from plugins to the [kControlChannelName] channel.
+  ///
+  /// Messages use the [StandardMethodCodec] format. There are two methods
+  /// supported: `resize` and `overflow`. The `resize` method changes the size
+  /// of the buffer, and the `overflow` method controls whether overflow is
+  /// expected or not.
+  ///
+  /// ## `resize`
+  ///
+  /// The `resize` method takes as its argument a list with two values, first
+  /// the channel name (a UTF-8 string less than 254 bytes long), and second the
+  /// allowed size of the channel buffer (an integer between 0 and 2147483647).
+  ///
+  /// Upon receiving the message, the channel's buffer is resized. If necessary,
+  /// messages are silently discarded to ensure the buffer is no bigger than
+  /// specified.
+  ///
+  /// For historical reasons, this message can also be sent using a bespoke
+  /// format consisting of a UTF-8-encoded string with three parts separated
+  /// from each other by U+000D CARRIAGE RETURN (CR) characters, the three parts
+  /// being the string `resize`, the string giving the channel name, and then
+  /// the string giving the decimal serialization of the new channel buffer
+  /// size. For example: `resize\rchannel\r1`
+  ///
+  /// ## `overflow`
+  ///
+  /// The `overflow` method takes as its argument a list with two values, first
+  /// the channel name (a UTF-8 string less than 254 bytes long), and second a
+  /// boolean which is true if overflow is expected and false if it is not.
+  ///
+  /// This sets a flag on the channel in debug mode. In release mode the message
+  /// is silently ignored. The flag indicates whether overflow is expected on this
+  /// channel. When the flag is set, messages are discarded silently. When the
+  /// flag is cleared (the default), any overflow on the channel causes a message
+  /// to be printed to the console, warning that a message was lost.
+  void handleMessage(ByteData data) {
+    // We hard-code the deserialization here because the StandardMethodCodec class
+    // is part of the framework, not dart:ui.
+    final Uint8List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
+    if (bytes[0] == 0x07) { // 7 = value code for string
+      final int methodNameLength = bytes[1];
+      if (methodNameLength >= 254) // lengths greater than 253 have more elaborate encoding
+        throw Exception('Unrecognized message sent to $kControlChannelName (method name too long)');
+      int index = 2; // where we are in reading the bytes
+      final String methodName = utf8.decode(bytes.sublist(index, index + methodNameLength));
+      index += methodNameLength;
+      switch (methodName) {
+        case 'resize':
+          if (bytes[index] != 0x0C) // 12 = value code for list
+            throw Exception('Invalid arguments for \'resize\' method sent to $kControlChannelName (arguments must be a two-element list, channel name and new capacity)');
+          index += 1;
+          if (bytes[index] < 0x02) // We ignore extra arguments, in case we need to support them in the future, hence <2 rather than !=2.
+            throw Exception('Invalid arguments for \'resize\' method sent to $kControlChannelName (arguments must be a two-element list, channel name and new capacity)');
+          index += 1;
+          if (bytes[index] != 0x07) // 7 = value code for string
+            throw Exception('Invalid arguments for \'resize\' method sent to $kControlChannelName (first argument must be a string)');
+          index += 1;
+          final int channelNameLength = bytes[index];
+          if (channelNameLength >= 254) // lengths greater than 253 have more elaborate encoding
+            throw Exception('Invalid arguments for \'resize\' method sent to $kControlChannelName (channel name must be less than 254 characters long)');
+          index += 1;
+          final String channelName = utf8.decode(bytes.sublist(index, index + channelNameLength));
+          index += channelNameLength;
+          if (bytes[index] != 0x03) // 3 = value code for uint32
+            throw Exception('Invalid arguments for \'resize\' method sent to $kControlChannelName (second argument must be an integer in the range 0 to 2147483647)');
+          index += 1;
+          resize(channelName, data.getUint32(index, Endian.host));
+          break;
+        case 'overflow':
+          if (bytes[index] != 0x0C) // 12 = value code for list
+            throw Exception('Invalid arguments for \'overflow\' method sent to $kControlChannelName (arguments must be a two-element list, channel name and flag state)');
+          index += 1;
+          if (bytes[index] < 0x02) // We ignore extra arguments, in case we need to support them in the future, hence <2 rather than !=2.
+            throw Exception('Invalid arguments for \'overflow\' method sent to $kControlChannelName (arguments must be a two-element list, channel name and flag state)');
+          index += 1;
+          if (bytes[index] != 0x07) // 7 = value code for string
+            throw Exception('Invalid arguments for \'overflow\' method sent to $kControlChannelName (first argument must be a string)');
+          index += 1;
+          final int channelNameLength = bytes[index];
+          if (channelNameLength >= 254) // lengths greater than 253 have more elaborate encoding
+            throw Exception('Invalid arguments for \'overflow\' method sent to $kControlChannelName (channel name must be less than 254 characters long)');
+          index += 1;
+          final String channelName = utf8.decode(bytes.sublist(index, index + channelNameLength));
+          index += channelNameLength;
+          if (bytes[index] != 0x01 && bytes[index] != 0x02) // 1 = value code for true, 2 = value code for false
+            throw Exception('Invalid arguments for \'overflow\' method sent to $kControlChannelName (second argument must be a boolean)');
+          allowOverflow(channelName, bytes[index] == 0x01);
+          break;
+        default:
+          throw Exception('Unrecognized method \'$methodName\' sent to $kControlChannelName');
+      }
+    } else {
+      final List<String> parts = utf8.decode(bytes).split('\r');
+      if (parts.length == 1 + /*arity=*/2 && parts[0] == 'resize') {
+        resize(parts[1], int.parse(parts[2]));
+      } else {
+        // If the message couldn't be decoded as UTF-8, a FormatException will
+        // have been thrown by utf8.decode() above.
+        throw Exception('Unrecognized message $parts sent to $kControlChannelName.');
+      }
+    }
+  }
+
+  /// Changes the capacity of the queue associated with the given channel.
+  ///
+  /// This could result in the dropping of messages if newSize is less
+  /// than the current length of the queue.
+  ///
+  /// This is expected to be called by platform-specific plugin code (indirectly
+  /// via the control channel), not by code on the framework side. See
+  /// [handleMessage].
+  ///
+  /// Calling this from framework code is redundant since by the time framework
+  /// code can be running, it can just subscribe to the relevant channel and
+  /// there is therefore no need for any buffering.
+  void resize(String name, int newSize) {
+    _Channel? channel = _channels[name];
+    if (channel == null) {
+      channel = _Channel(newSize);
+      _channels[name] = channel;
+    } else {
+      channel.capacity = newSize;
+    }
+  }
+
+  /// Toggles whether the channel should show warning messages when discarding
+  /// messages due to overflow.
+  ///
+  /// This is expected to be called by platform-specific plugin code (indirectly
+  /// via the control channel), not by code on the framework side. See
+  /// [handleMessage].
+  ///
+  /// Calling this from framework code is redundant since by the time framework
+  /// code can be running, it can just subscribe to the relevant channel and
+  /// there is therefore no need for any messages to overflow.
+  ///
+  /// This method has no effect in release builds.
+  void allowOverflow(String name, bool allowed) {
+    assert(() {
+      _Channel? channel = _channels[name];
+      if (channel == null && allowed) {
+        channel = _Channel();
+        _channels[name] = channel;
+      }
+      channel?.debugEnableDiscardWarnings = !allowed;
+      return true;
+    }());
+  }
+}
+
+/// [ChannelBuffers] that allow the storage of messages between the
+/// Engine and the Framework.  Typically messages that can't be delivered
+/// are stored here until the Framework is able to process them.
+///
+/// See also:
+///
+/// * [BinaryMessenger], where [ChannelBuffers] are typically read.
+final ChannelBuffers channelBuffers = ChannelBuffers();
diff --git a/lib/src/ui/compositing.dart b/lib/src/ui/compositing.dart
new file mode 100644
index 0000000..d3990b4
--- /dev/null
+++ b/lib/src/ui/compositing.dart
@@ -0,0 +1,853 @@
+// 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.
+
+
+part of dart.ui;
+
+/// An opaque object representing a composited scene.
+///
+/// To create a Scene object, use a [SceneBuilder].
+///
+/// Scene objects can be displayed on the screen using the [FlutterView.render]
+/// method.
+class Scene extends NativeFieldWrapperClass2 {
+  /// This class is created by the engine, and should not be instantiated
+  /// or extended directly.
+  ///
+  /// To create a Scene object, use a [SceneBuilder].
+  Scene._();
+
+  /// Creates a raster image representation of the current state of the scene.
+  /// This is a slow operation that is performed on a background thread.
+  ///
+  /// Callers must dispose the [Image] when they are done with it. If the result
+  /// will be shared with other methods or classes, [Image.clone] should be used
+  /// and each handle created must be disposed.
+  Future<Image> toImage(int width, int height) {
+    if (width <= 0 || height <= 0) {
+      throw Exception('Invalid image dimensions.');
+    }
+    throw UnimplementedError();
+  }
+
+  /// Releases the resources used by this scene.
+  ///
+  /// After calling this function, the scene is cannot be used further.
+  void dispose() {}
+}
+
+// Lightweight wrapper of a native layer object.
+//
+// This is used to provide a typed API for engine layers to prevent
+// incompatible layers from being passed to [SceneBuilder]'s push methods.
+// For example, this prevents a layer returned from `pushOpacity` from being
+// passed as `oldLayer` to `pushTransform`. This is achieved by having one
+// concrete subclass of this class per push method.
+abstract class _EngineLayerWrapper implements EngineLayer {
+  _EngineLayerWrapper._(this._nativeLayer);
+
+  EngineLayer _nativeLayer;
+
+  // Children of this layer.
+  //
+  // Null if this layer has no children. This field is populated only in debug
+  // mode.
+  List<_EngineLayerWrapper>? _debugChildren;
+
+  // Whether this layer was used as `oldLayer` in a past frame.
+  //
+  // It is illegal to use a layer object again after it is passed as an
+  // `oldLayer` argument.
+  bool _debugWasUsedAsOldLayer = false;
+
+  bool _debugCheckNotUsedAsOldLayer() {
+    assert(
+        !_debugWasUsedAsOldLayer,
+        'Layer $runtimeType was previously used as oldLayer.\n'
+        'Once a layer is used as oldLayer, it may not be used again. Instead, '
+        'after calling one of the SceneBuilder.push* methods and passing an oldLayer '
+        'to it, use the layer returned by the method as oldLayer in subsequent '
+        'frames.');
+    return true;
+  }
+}
+
+/// An opaque handle to a transform engine layer.
+///
+/// Instances of this class are created by [SceneBuilder.pushTransform].
+///
+/// {@template dart.ui.sceneBuilder.oldLayerCompatibility}
+/// `oldLayer` parameter in [SceneBuilder] methods only accepts objects created
+/// by the engine. [SceneBuilder] will throw an [AssertionError] if you pass it
+/// a custom implementation of this class.
+/// {@endtemplate}
+class TransformEngineLayer extends _EngineLayerWrapper {
+  TransformEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
+}
+
+/// An opaque handle to an offset engine layer.
+///
+/// Instances of this class are created by [SceneBuilder.pushOffset].
+///
+/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
+class OffsetEngineLayer extends _EngineLayerWrapper {
+  OffsetEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
+}
+
+/// An opaque handle to a clip rect engine layer.
+///
+/// Instances of this class are created by [SceneBuilder.pushClipRect].
+///
+/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
+class ClipRectEngineLayer extends _EngineLayerWrapper {
+  ClipRectEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
+}
+
+/// An opaque handle to a clip rounded rect engine layer.
+///
+/// Instances of this class are created by [SceneBuilder.pushClipRRect].
+///
+/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
+class ClipRRectEngineLayer extends _EngineLayerWrapper {
+  ClipRRectEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
+}
+
+/// An opaque handle to a clip path engine layer.
+///
+/// Instances of this class are created by [SceneBuilder.pushClipPath].
+///
+/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
+class ClipPathEngineLayer extends _EngineLayerWrapper {
+  ClipPathEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
+}
+
+/// An opaque handle to an opacity engine layer.
+///
+/// Instances of this class are created by [SceneBuilder.pushOpacity].
+///
+/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
+class OpacityEngineLayer extends _EngineLayerWrapper {
+  OpacityEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
+}
+
+/// An opaque handle to a color filter engine layer.
+///
+/// Instances of this class are created by [SceneBuilder.pushColorFilter].
+///
+/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
+class ColorFilterEngineLayer extends _EngineLayerWrapper {
+  ColorFilterEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
+}
+
+/// An opaque handle to an image filter engine layer.
+///
+/// Instances of this class are created by [SceneBuilder.pushImageFilter].
+///
+/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
+class ImageFilterEngineLayer extends _EngineLayerWrapper {
+  ImageFilterEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
+}
+
+/// An opaque handle to a backdrop filter engine layer.
+///
+/// Instances of this class are created by [SceneBuilder.pushBackdropFilter].
+///
+/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
+class BackdropFilterEngineLayer extends _EngineLayerWrapper {
+  BackdropFilterEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
+}
+
+/// An opaque handle to a shader mask engine layer.
+///
+/// Instances of this class are created by [SceneBuilder.pushShaderMask].
+///
+/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
+class ShaderMaskEngineLayer extends _EngineLayerWrapper {
+  ShaderMaskEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
+}
+
+/// An opaque handle to a physical shape engine layer.
+///
+/// Instances of this class are created by [SceneBuilder.pushPhysicalShape].
+///
+/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
+class PhysicalShapeEngineLayer extends _EngineLayerWrapper {
+  PhysicalShapeEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
+}
+
+/// Builds a [Scene] containing the given visuals.
+///
+/// A [Scene] can then be rendered using [FlutterView.render].
+///
+/// To draw graphical operations onto a [Scene], first create a
+/// [Picture] using a [PictureRecorder] and a [Canvas], and then add
+/// it to the scene using [addPicture].
+class SceneBuilder extends NativeFieldWrapperClass2 {
+  /// Creates an empty [SceneBuilder] object.
+  @pragma('vm:entry-point')
+  SceneBuilder();
+
+  // Layers used in this scene.
+  //
+  // The key is the layer used. The value is the description of what the layer
+  // is used for, e.g. "pushOpacity" or "addRetained".
+  Map<EngineLayer, String> _usedLayers = <EngineLayer, String>{};
+
+  // In debug mode checks that the `layer` is only used once in a given scene.
+  bool _debugCheckUsedOnce(EngineLayer layer, String usage) {
+    assert(() {
+      assert(
+          !_usedLayers.containsKey(layer),
+          'Layer ${layer.runtimeType} already used.\n'
+          'The layer is already being used as ${_usedLayers[layer]} in this scene.\n'
+          'A layer may only be used once in a given scene.');
+
+      _usedLayers[layer] = usage;
+      return true;
+    }());
+
+    return true;
+  }
+
+  bool _debugCheckCanBeUsedAsOldLayer(_EngineLayerWrapper? layer, String methodName) {
+    assert(() {
+      if (layer == null) {
+        return true;
+      }
+      layer._debugCheckNotUsedAsOldLayer();
+      assert(_debugCheckUsedOnce(layer, 'oldLayer in $methodName'));
+      layer._debugWasUsedAsOldLayer = true;
+      return true;
+    }());
+    return true;
+  }
+
+  final List<_EngineLayerWrapper> _layerStack = <_EngineLayerWrapper>[];
+
+  // Pushes the `newLayer` onto the `_layerStack` and adds it to the
+  // `_debugChildren` of the current layer in the stack, if any.
+  bool _debugPushLayer(_EngineLayerWrapper newLayer) {
+    assert(() {
+      if (_layerStack.isNotEmpty) {
+        final _EngineLayerWrapper currentLayer = _layerStack.last;
+        currentLayer._debugChildren ??= <_EngineLayerWrapper>[];
+        currentLayer._debugChildren!.add(newLayer);
+      }
+      _layerStack.add(newLayer);
+      return true;
+    }());
+    return true;
+  }
+
+  /// Pushes a transform operation onto the operation stack.
+  ///
+  /// The objects are transformed by the given matrix before rasterization.
+  ///
+  /// {@template dart.ui.sceneBuilder.oldLayer}
+  /// If `oldLayer` is not null the engine will attempt to reuse the resources
+  /// allocated for the old layer when rendering the new layer. This is purely
+  /// an optimization. It has no effect on the correctness of rendering.
+  /// {@endtemplate}
+  ///
+  /// {@template dart.ui.sceneBuilder.oldLayerVsRetained}
+  /// Passing a layer to [addRetained] or as `oldLayer` argument to a push
+  /// method counts as _usage_. A layer can be used no more than once in a scene.
+  /// For example, it may not be passed simultaneously to two push methods, or
+  /// to a push method and to `addRetained`.
+  ///
+  /// When a layer is passed to [addRetained] all descendant layers are also
+  /// considered as used in this scene. The same single-usage restriction
+  /// applies to descendants.
+  ///
+  /// When a layer is passed as an `oldLayer` argument to a push method, it may
+  /// no longer be used in subsequent frames. If you would like to continue
+  /// reusing the resources associated with the layer, store the layer object
+  /// returned by the push method and use that in the next frame instead of the
+  /// original object.
+  /// {@endtemplate}
+  ///
+  /// See [pop] for details about the operation stack.
+  TransformEngineLayer? pushTransform(
+    Float64List matrix4, {
+    TransformEngineLayer? oldLayer,
+  }) {
+    assert(_matrix4IsValid(matrix4));
+    assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushTransform'));
+    final EngineLayer engineLayer = EngineLayer._();
+    _pushTransform(engineLayer, matrix4);
+    final TransformEngineLayer layer = TransformEngineLayer._(engineLayer);
+    assert(_debugPushLayer(layer));
+    return layer;
+  }
+
+  void _pushTransform(EngineLayer layer, Float64List matrix4) { throw UnimplementedError(); }
+
+  /// Pushes an offset operation onto the operation stack.
+  ///
+  /// This is equivalent to [pushTransform] with a matrix with only translation.
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayer}
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
+  ///
+  /// See [pop] for details about the operation stack.
+  OffsetEngineLayer? pushOffset(
+    double dx,
+    double dy, {
+    OffsetEngineLayer? oldLayer,
+  }) {
+    assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushOffset'));
+    final EngineLayer engineLayer = EngineLayer._();
+    _pushOffset(engineLayer, dx, dy);
+    final OffsetEngineLayer layer = OffsetEngineLayer._(engineLayer);
+    assert(_debugPushLayer(layer));
+    return layer;
+  }
+
+  void _pushOffset(EngineLayer layer, double dx, double dy) { throw UnimplementedError(); }
+
+  /// Pushes a rectangular clip operation onto the operation stack.
+  ///
+  /// Rasterization outside the given rectangle is discarded.
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayer}
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
+  ///
+  /// See [pop] for details about the operation stack, and [Clip] for different clip modes.
+  /// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]).
+  ClipRectEngineLayer? pushClipRect(
+    Rect rect, {
+    Clip clipBehavior = Clip.antiAlias,
+    ClipRectEngineLayer? oldLayer,
+  }) {
+    assert(clipBehavior != null); // ignore: unnecessary_null_comparison
+    assert(clipBehavior != Clip.none);
+    assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipRect'));
+    final EngineLayer engineLayer = EngineLayer._();
+    _pushClipRect(engineLayer, rect.left, rect.right, rect.top, rect.bottom, clipBehavior.index);
+    final ClipRectEngineLayer layer = ClipRectEngineLayer._(engineLayer);
+    assert(_debugPushLayer(layer));
+    return layer;
+  }
+
+  void _pushClipRect(EngineLayer outEngineLayer, double left, double right, double top, double bottom, int clipBehavior)
+      { throw UnimplementedError(); }
+
+  /// Pushes a rounded-rectangular clip operation onto the operation stack.
+  ///
+  /// Rasterization outside the given rounded rectangle is discarded.
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayer}
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
+  ///
+  /// See [pop] for details about the operation stack, and [Clip] for different clip modes.
+  /// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]).
+  ClipRRectEngineLayer? pushClipRRect(
+    RRect rrect, {
+    Clip clipBehavior = Clip.antiAlias,
+    ClipRRectEngineLayer? oldLayer,
+  }) {
+    assert(clipBehavior != null); // ignore: unnecessary_null_comparison
+    assert(clipBehavior != Clip.none);
+    assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipRRect'));
+    final EngineLayer engineLayer = EngineLayer._();
+    _pushClipRRect(engineLayer, rrect._value32, clipBehavior.index);
+    final ClipRRectEngineLayer layer = ClipRRectEngineLayer._(engineLayer);
+    assert(_debugPushLayer(layer));
+    return layer;
+  }
+
+  void _pushClipRRect(EngineLayer layer, Float32List rrect, int clipBehavior)
+      { throw UnimplementedError(); }
+
+  /// Pushes a path clip operation onto the operation stack.
+  ///
+  /// Rasterization outside the given path is discarded.
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayer}
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
+  ///
+  /// See [pop] for details about the operation stack. See [Clip] for different clip modes.
+  /// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]).
+  ClipPathEngineLayer? pushClipPath(
+    Path path, {
+    Clip clipBehavior = Clip.antiAlias,
+    ClipPathEngineLayer? oldLayer,
+  }) {
+    assert(clipBehavior != null); // ignore: unnecessary_null_comparison
+    assert(clipBehavior != Clip.none);
+    assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipPath'));
+    final EngineLayer engineLayer = EngineLayer._();
+    _pushClipPath(engineLayer, path, clipBehavior.index);
+    final ClipPathEngineLayer layer = ClipPathEngineLayer._(engineLayer);
+    assert(_debugPushLayer(layer));
+    return layer;
+  }
+
+  void _pushClipPath(EngineLayer layer, Path path, int clipBehavior) { throw UnimplementedError(); }
+
+  /// Pushes an opacity operation onto the operation stack.
+  ///
+  /// The given alpha value is blended into the alpha value of the objects'
+  /// rasterization. An alpha value of 0 makes the objects entirely invisible.
+  /// An alpha value of 255 has no effect (i.e., the objects retain the current
+  /// opacity).
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayer}
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
+  ///
+  /// See [pop] for details about the operation stack.
+  OpacityEngineLayer? pushOpacity(
+    int alpha, {
+    Offset? offset = Offset.zero,
+    OpacityEngineLayer? oldLayer,
+  }) {
+    assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushOpacity'));
+    final EngineLayer engineLayer = EngineLayer._();
+    _pushOpacity(engineLayer, alpha, offset!.dx, offset.dy);
+    final OpacityEngineLayer layer = OpacityEngineLayer._(engineLayer);
+    assert(_debugPushLayer(layer));
+    return layer;
+  }
+
+  void _pushOpacity(EngineLayer layer, int alpha, double dx, double dy) { throw UnimplementedError(); }
+
+  /// Pushes a color filter operation onto the operation stack.
+  ///
+  /// The given color is applied to the objects' rasterization using the given
+  /// blend mode.
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayer}
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
+  ///
+  /// See [pop] for details about the operation stack.
+  ColorFilterEngineLayer? pushColorFilter(
+    ColorFilter filter, {
+    ColorFilterEngineLayer? oldLayer,
+  }) {
+    assert(filter != null); // ignore: unnecessary_null_comparison
+    assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushColorFilter'));
+    final _ColorFilter nativeFilter = filter._toNativeColorFilter()!;
+    assert(nativeFilter != null); // ignore: unnecessary_null_comparison
+    final EngineLayer engineLayer = EngineLayer._();
+    _pushColorFilter(engineLayer, nativeFilter);
+    final ColorFilterEngineLayer layer = ColorFilterEngineLayer._(engineLayer);
+    assert(_debugPushLayer(layer));
+    return layer;
+  }
+
+  void _pushColorFilter(EngineLayer layer, _ColorFilter filter) { throw UnimplementedError(); }
+
+  /// Pushes an image filter operation onto the operation stack.
+  ///
+  /// The given filter is applied to the children's rasterization before compositing them into
+  /// the scene.
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayer}
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
+  ///
+  /// See [pop] for details about the operation stack.
+  ImageFilterEngineLayer? pushImageFilter(
+    ImageFilter filter, {
+    ImageFilterEngineLayer? oldLayer,
+  }) {
+    assert(filter != null); // ignore: unnecessary_null_comparison
+    assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushImageFilter'));
+    final _ImageFilter nativeFilter = filter._toNativeImageFilter();
+    assert(nativeFilter != null); // ignore: unnecessary_null_comparison
+    final EngineLayer engineLayer = EngineLayer._();
+    _pushImageFilter(engineLayer, nativeFilter);
+    final ImageFilterEngineLayer layer = ImageFilterEngineLayer._(engineLayer);
+    assert(_debugPushLayer(layer));
+    return layer;
+  }
+
+  void _pushImageFilter(EngineLayer outEngineLayer, _ImageFilter filter) { throw UnimplementedError(); }
+
+  /// Pushes a backdrop filter operation onto the operation stack.
+  ///
+  /// The given filter is applied to the current contents of the scene prior to
+  /// rasterizing the given objects.
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayer}
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
+  ///
+  /// See [pop] for details about the operation stack.
+  BackdropFilterEngineLayer? pushBackdropFilter(
+    ImageFilter filter, {
+    BackdropFilterEngineLayer? oldLayer,
+  }) {
+    assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushBackdropFilter'));
+    final EngineLayer engineLayer = EngineLayer._();
+    _pushBackdropFilter(engineLayer, filter._toNativeImageFilter());
+    final BackdropFilterEngineLayer layer = BackdropFilterEngineLayer._(engineLayer);
+    assert(_debugPushLayer(layer));
+    return layer;
+  }
+
+  void _pushBackdropFilter(EngineLayer outEngineLayer, _ImageFilter filter) { throw UnimplementedError(); }
+
+  /// Pushes a shader mask operation onto the operation stack.
+  ///
+  /// The given shader is applied to the object's rasterization in the given
+  /// rectangle using the given blend mode.
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayer}
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
+  ///
+  /// See [pop] for details about the operation stack.
+  ShaderMaskEngineLayer? pushShaderMask(
+    Shader shader,
+    Rect maskRect,
+    BlendMode blendMode, {
+    ShaderMaskEngineLayer? oldLayer,
+  }) {
+    assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushShaderMask'));
+    final EngineLayer engineLayer = EngineLayer._();
+    _pushShaderMask(
+      engineLayer,
+      shader,
+      maskRect.left,
+      maskRect.right,
+      maskRect.top,
+      maskRect.bottom,
+      blendMode.index,
+    );
+    final ShaderMaskEngineLayer layer = ShaderMaskEngineLayer._(engineLayer);
+    assert(_debugPushLayer(layer));
+    return layer;
+  }
+
+  void _pushShaderMask(
+      EngineLayer engineLayer,
+      Shader shader,
+      double maskRectLeft,
+      double maskRectRight,
+      double maskRectTop,
+      double maskRectBottom,
+      int blendMode) { throw UnimplementedError(); }
+
+  /// Pushes a physical layer operation for an arbitrary shape onto the
+  /// operation stack.
+  ///
+  /// By default, the layer's content will not be clipped (clip = [Clip.none]).
+  /// If clip equals [Clip.hardEdge], [Clip.antiAlias], or [Clip.antiAliasWithSaveLayer],
+  /// then the content is clipped to the given shape defined by [path].
+  ///
+  /// If [elevation] is greater than 0.0, then a shadow is drawn around the layer.
+  /// [shadowColor] defines the color of the shadow if present and [color] defines the
+  /// color of the layer background.
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayer}
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
+  ///
+  /// See [pop] for details about the operation stack, and [Clip] for different clip modes.
+  // ignore: deprecated_member_use
+  PhysicalShapeEngineLayer? pushPhysicalShape({
+    required Path path,
+    required double elevation,
+    required Color color,
+    Color? shadowColor,
+    Clip clipBehavior = Clip.none,
+    PhysicalShapeEngineLayer? oldLayer,
+  }) {
+    assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushPhysicalShape'));
+    final EngineLayer engineLayer = EngineLayer._();
+    _pushPhysicalShape(
+      engineLayer,
+      path,
+      elevation,
+      color.value,
+      shadowColor?.value ?? 0xFF000000,
+      clipBehavior.index,
+    );
+    final PhysicalShapeEngineLayer layer = PhysicalShapeEngineLayer._(engineLayer);
+    assert(_debugPushLayer(layer));
+    return layer;
+  }
+
+  void _pushPhysicalShape(EngineLayer outEngineLayer, Path path, double elevation, int color, int shadowColor,
+      int clipBehavior) { throw UnimplementedError(); }
+
+  /// Ends the effect of the most recently pushed operation.
+  ///
+  /// Internally the scene builder maintains a stack of operations. Each of the
+  /// operations in the stack applies to each of the objects added to the scene.
+  /// Calling this function removes the most recently added operation from the
+  /// stack.
+  void pop() {
+    if (_layerStack.isNotEmpty) {
+      _layerStack.removeLast();
+    }
+    _pop();
+  }
+
+  void _pop() { throw UnimplementedError(); }
+
+  /// Add a retained engine layer subtree from previous frames.
+  ///
+  /// All the engine layers that are in the subtree of the retained layer will
+  /// be automatically appended to the current engine layer tree.
+  ///
+  /// Therefore, when implementing a subclass of the [Layer] concept defined in
+  /// the rendering layer of Flutter's framework, once this is called, there's
+  /// no need to call [Layer.addToScene] for its children layers.
+  ///
+  /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
+  void addRetained(EngineLayer retainedLayer) {
+    assert(retainedLayer is _EngineLayerWrapper);
+    assert(() {
+      final _EngineLayerWrapper layer = retainedLayer as _EngineLayerWrapper;
+
+      void recursivelyCheckChildrenUsedOnce(_EngineLayerWrapper parentLayer) {
+        _debugCheckUsedOnce(parentLayer, 'retained layer');
+        parentLayer._debugCheckNotUsedAsOldLayer();
+
+        final List<_EngineLayerWrapper>? children = parentLayer._debugChildren;
+        if (children == null || children.isEmpty) {
+          return;
+        }
+        children.forEach(recursivelyCheckChildrenUsedOnce);
+      }
+
+      recursivelyCheckChildrenUsedOnce(layer);
+
+      return true;
+    }());
+
+    final _EngineLayerWrapper wrapper = retainedLayer as _EngineLayerWrapper;
+    _addRetained(wrapper._nativeLayer);
+  }
+
+  void _addRetained(EngineLayer retainedLayer) { throw UnimplementedError(); }
+
+  /// Adds an object to the scene that displays performance statistics.
+  ///
+  /// Useful during development to assess the performance of the application.
+  /// The enabledOptions controls which statistics are displayed. The bounds
+  /// controls where the statistics are displayed.
+  ///
+  /// enabledOptions is a bit field with the following bits defined:
+  ///  - 0x01: displayRasterizerStatistics - show raster thread frame time
+  ///  - 0x02: visualizeRasterizerStatistics - graph raster thread frame times
+  ///  - 0x04: displayEngineStatistics - show UI thread frame time
+  ///  - 0x08: visualizeEngineStatistics - graph UI thread frame times
+  /// Set enabledOptions to 0x0F to enable all the currently defined features.
+  ///
+  /// The "UI thread" is the thread that includes all the execution of the main
+  /// Dart isolate (the isolate that can call [FlutterView.render]). The UI
+  /// thread frame time is the total time spent executing the
+  /// [PlatformDispatcher.onBeginFrame] callback. The "raster thread" is the
+  /// thread (running on the CPU) that subsequently processes the [Scene]
+  /// provided by the Dart code to turn it into GPU commands and send it to the
+  /// GPU.
+  ///
+  /// See also the [PerformanceOverlayOption] enum in the rendering library.
+  /// for more details.
+  // Values above must match constants in //engine/src/sky/compositor/performance_overlay_layer.h
+  void addPerformanceOverlay(int enabledOptions, Rect bounds) {
+    _addPerformanceOverlay(enabledOptions, bounds.left, bounds.right, bounds.top, bounds.bottom);
+  }
+
+  void _addPerformanceOverlay(
+    int enabledOptions,
+    double left,
+    double right,
+    double top,
+    double bottom,
+  ) { throw UnimplementedError(); }
+
+  /// Adds a [Picture] to the scene.
+  ///
+  /// The picture is rasterized at the given offset.
+  void addPicture(
+    Offset offset,
+    Picture picture, {
+    bool isComplexHint = false,
+    bool willChangeHint = false,
+  }) {
+    final int hints = (isComplexHint ? 1 : 0) | (willChangeHint ? 2 : 0);
+    _addPicture(offset.dx, offset.dy, picture, hints);
+  }
+
+  void _addPicture(double dx, double dy, Picture picture, int hints)
+      { throw UnimplementedError(); }
+
+  /// Adds a backend texture to the scene.
+  ///
+  /// The texture is scaled to the given size and rasterized at the given offset.
+  ///
+  /// If `freeze` is true the texture that is added to the scene will not
+  /// be updated with new frames. `freeze` is used when resizing an embedded
+  /// Android view: When resizing an Android view there is a short period during
+  /// which the framework cannot tell if the newest texture frame has the
+  /// previous or new size, to workaround this the framework "freezes" the
+  /// texture just before resizing the Android view and un-freezes it when it is
+  /// certain that a frame with the new size is ready.
+  void addTexture(
+    int textureId, {
+    Offset offset = Offset.zero,
+    double width = 0.0,
+    double height = 0.0,
+    bool freeze = false,
+    FilterQuality filterQuality = FilterQuality.low,
+  }) {
+    assert(offset != null, 'Offset argument was null'); // ignore: unnecessary_null_comparison
+    _addTexture(offset.dx, offset.dy, width, height, textureId, freeze, filterQuality.index);
+  }
+
+  void _addTexture(double dx, double dy, double width, double height, int textureId, bool freeze,
+      int filterQuality) { throw UnimplementedError(); }
+
+  /// Adds a platform view (e.g an iOS UIView) to the scene.
+  ///
+  /// On iOS this layer splits the current output surface into two surfaces, one for the scene nodes
+  /// preceding the platform view, and one for the scene nodes following the platform view.
+  ///
+  /// ## Performance impact
+  ///
+  /// Adding an additional surface doubles the amount of graphics memory directly used by Flutter
+  /// for output buffers. Quartz might allocated extra buffers for compositing the Flutter surfaces
+  /// and the platform view.
+  ///
+  /// With a platform view in the scene, Quartz has to composite the two Flutter surfaces and the
+  /// embedded UIView. In addition to that, on iOS versions greater than 9, the Flutter frames are
+  /// synchronized with the UIView frames adding additional performance overhead.
+  ///
+  /// The `offset` argument is not used for iOS and Android.
+  void addPlatformView(
+    int viewId, {
+    Offset offset = Offset.zero,
+    double width = 0.0,
+    double height = 0.0,
+  }) {
+    assert(offset != null, 'Offset argument was null'); // ignore: unnecessary_null_comparison
+    _addPlatformView(offset.dx, offset.dy, width, height, viewId);
+  }
+
+  void _addPlatformView(double dx, double dy, double width, double height, int viewId)
+      { throw UnimplementedError(); }
+
+  /// (Fuchsia-only) Adds a scene rendered by another application to the scene
+  /// for this application.
+  void addChildScene({
+    Offset offset = Offset.zero,
+    double width = 0.0,
+    double height = 0.0,
+    required SceneHost sceneHost,
+    bool hitTestable = true,
+  }) {
+    _addChildScene(offset.dx, offset.dy, width, height, sceneHost, hitTestable);
+  }
+
+  void _addChildScene(double dx, double dy, double width, double height, SceneHost sceneHost,
+      bool hitTestable) { throw UnimplementedError(); }
+
+  /// Sets a threshold after which additional debugging information should be recorded.
+  ///
+  /// Currently this interface is difficult to use by end-developers. If you're
+  /// interested in using this feature, please contact [flutter-dev](https://groups.google.com/forum/#!forum/flutter-dev).
+  /// We'll hopefully be able to figure out how to make this feature more useful
+  /// to you.
+  void setRasterizerTracingThreshold(int frameInterval)
+      { throw UnimplementedError(); }
+
+  /// Sets whether the raster cache should checkerboard cached entries. This is
+  /// only useful for debugging purposes.
+  ///
+  /// The compositor can sometimes decide to cache certain portions of the
+  /// widget hierarchy. Such portions typically don't change often from frame to
+  /// frame and are expensive to render. This can speed up overall rendering. However,
+  /// there is certain upfront cost to constructing these cache entries. And, if
+  /// the cache entries are not used very often, this cost may not be worth the
+  /// speedup in rendering of subsequent frames. If the developer wants to be certain
+  /// that populating the raster cache is not causing stutters, this option can be
+  /// set. Depending on the observations made, hints can be provided to the compositor
+  /// that aid it in making better decisions about caching.
+  ///
+  /// Currently this interface is difficult to use by end-developers. If you're
+  /// interested in using this feature, please contact [flutter-dev](https://groups.google.com/forum/#!forum/flutter-dev).
+  void setCheckerboardRasterCacheImages(bool checkerboard)
+      { throw UnimplementedError(); }
+
+  /// Sets whether the compositor should checkerboard layers that are rendered
+  /// to offscreen bitmaps.
+  ///
+  /// This is only useful for debugging purposes.
+  void setCheckerboardOffscreenLayers(bool checkerboard)
+      { throw UnimplementedError(); }
+
+  /// Finishes building the scene.
+  ///
+  /// Returns a [Scene] containing the objects that have been added to
+  /// this scene builder. The [Scene] can then be displayed on the
+  /// screen with [FlutterView.render].
+  ///
+  /// After calling this function, the scene builder object is invalid and
+  /// cannot be used further.
+  Scene build() {
+    final Scene scene = Scene._();
+    _build(scene);
+    return scene;
+  }
+
+  void _build(Scene outScene) { throw UnimplementedError(); }
+}
+
+/// (Fuchsia-only) Hosts content provided by another application.
+class SceneHost extends NativeFieldWrapperClass2 {
+  /// Creates a host for a child scene's content.
+  ///
+  /// The ViewHolder token is bound to a ViewHolder scene graph node which acts
+  /// as a container for the child's content.  The creator of the SceneHost is
+  /// responsible for sending the corresponding ViewToken to the child.
+  ///
+  /// The ViewHolder token is a dart:zircon Handle, but that type isn't
+  /// available here. This is called by ChildViewConnection in
+  /// //topaz/public/dart/fuchsia_scenic_flutter/.
+  ///
+  /// The SceneHost takes ownership of the provided ViewHolder token.
+  SceneHost(
+    dynamic viewHolderToken,
+    void Function()? viewConnectedCallback,
+    void Function()? viewDisconnectedCallback,
+    void Function(bool)? viewStateChangedCallback,
+  ) {
+    _constructor(
+        viewHolderToken, viewConnectedCallback, viewDisconnectedCallback, viewStateChangedCallback);
+  }
+
+  void _constructor(
+    dynamic viewHolderToken,
+    void Function()? viewConnectedCallback,
+    void Function()? viewDisconnectedCallback,
+    void Function(bool)? viewStateChangedCallbac,
+  ) { throw UnimplementedError(); }
+
+  /// Releases the resources associated with the SceneHost.
+  ///
+  /// After calling this function, the SceneHost cannot be used further.
+  void dispose() { throw UnimplementedError(); }
+
+  /// Set properties on the linked scene.  These properties include its bounds,
+  /// as well as whether it can be the target of focus events or not.
+  void setProperties(
+    double width,
+    double height,
+    double insetTop,
+    double insetRight,
+    double insetBottom,
+    double insetLeft,
+    bool focusable,
+  ) { throw UnimplementedError(); }
+}
diff --git a/lib/src/ui/geometry.dart b/lib/src/ui/geometry.dart
new file mode 100644
index 0000000..92545ec
--- /dev/null
+++ b/lib/src/ui/geometry.dart
@@ -0,0 +1,1755 @@
+// 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.
+
+
+
+part of dart.ui;
+
+/// Base class for [Size] and [Offset], which are both ways to describe
+/// a distance as a two-dimensional axis-aligned vector.
+abstract class OffsetBase {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  ///
+  /// The first argument sets the horizontal component, and the second the
+  /// vertical component.
+  const OffsetBase(this._dx, this._dy)
+      : assert(_dx != null), // ignore: unnecessary_null_comparison
+        assert(_dy != null); // ignore: unnecessary_null_comparison
+
+  final double _dx;
+  final double _dy;
+
+  /// Returns true if either component is [double.infinity], and false if both
+  /// are finite (or negative infinity, or NaN).
+  ///
+  /// This is different than comparing for equality with an instance that has
+  /// _both_ components set to [double.infinity].
+  ///
+  /// See also:
+  ///
+  ///  * [isFinite], which is true if both components are finite (and not NaN).
+  bool get isInfinite => _dx >= double.infinity || _dy >= double.infinity;
+
+  /// Whether both components are finite (neither infinite nor NaN).
+  ///
+  /// See also:
+  ///
+  ///  * [isInfinite], which returns true if either component is equal to
+  ///    positive infinity.
+  bool get isFinite => _dx.isFinite && _dy.isFinite;
+
+  /// Less-than operator. Compares an [Offset] or [Size] to another [Offset] or
+  /// [Size], and returns true if both the horizontal and vertical values of the
+  /// left-hand-side operand are smaller than the horizontal and vertical values
+  /// of the right-hand-side operand respectively. Returns false otherwise.
+  ///
+  /// This is a partial ordering. It is possible for two values to be neither
+  /// less, nor greater than, nor equal to, another.
+  bool operator <(OffsetBase other) => _dx < other._dx && _dy < other._dy;
+
+  /// Less-than-or-equal-to operator. Compares an [Offset] or [Size] to another
+  /// [Offset] or [Size], and returns true if both the horizontal and vertical
+  /// values of the left-hand-side operand are smaller than or equal to the
+  /// horizontal and vertical values of the right-hand-side operand
+  /// respectively. Returns false otherwise.
+  ///
+  /// This is a partial ordering. It is possible for two values to be neither
+  /// less, nor greater than, nor equal to, another.
+  bool operator <=(OffsetBase other) => _dx <= other._dx && _dy <= other._dy;
+
+  /// Greater-than operator. Compares an [Offset] or [Size] to another [Offset]
+  /// or [Size], and returns true if both the horizontal and vertical values of
+  /// the left-hand-side operand are bigger than the horizontal and vertical
+  /// values of the right-hand-side operand respectively. Returns false
+  /// otherwise.
+  ///
+  /// This is a partial ordering. It is possible for two values to be neither
+  /// less, nor greater than, nor equal to, another.
+  bool operator >(OffsetBase other) => _dx > other._dx && _dy > other._dy;
+
+  /// Greater-than-or-equal-to operator. Compares an [Offset] or [Size] to
+  /// another [Offset] or [Size], and returns true if both the horizontal and
+  /// vertical values of the left-hand-side operand are bigger than or equal to
+  /// the horizontal and vertical values of the right-hand-side operand
+  /// respectively. Returns false otherwise.
+  ///
+  /// This is a partial ordering. It is possible for two values to be neither
+  /// less, nor greater than, nor equal to, another.
+  bool operator >=(OffsetBase other) => _dx >= other._dx && _dy >= other._dy;
+
+  /// Equality operator. Compares an [Offset] or [Size] to another [Offset] or
+  /// [Size], and returns true if the horizontal and vertical values of the
+  /// left-hand-side operand are equal to the horizontal and vertical values of
+  /// the right-hand-side operand respectively. Returns false otherwise.
+  @override
+  bool operator ==(Object other) {
+    return other is OffsetBase
+        && other._dx == _dx
+        && other._dy == _dy;
+  }
+
+  @override
+  int get hashCode => hashValues(_dx, _dy);
+
+  @override
+  String toString() => 'OffsetBase(${_dx.toStringAsFixed(1)}, ${_dy.toStringAsFixed(1)})';
+}
+
+/// An immutable 2D floating-point offset.
+///
+/// Generally speaking, Offsets can be interpreted in two ways:
+///
+/// 1. As representing a point in Cartesian space a specified distance from a
+///    separately-maintained origin. For example, the top-left position of
+///    children in the [RenderBox] protocol is typically represented as an
+///    [Offset] from the top left of the parent box.
+///
+/// 2. As a vector that can be applied to coordinates. For example, when
+///    painting a [RenderObject], the parent is passed an [Offset] from the
+///    screen's origin which it can add to the offsets of its children to find
+///    the [Offset] from the screen's origin to each of the children.
+///
+/// Because a particular [Offset] can be interpreted as one sense at one time
+/// then as the other sense at a later time, the same class is used for both
+/// senses.
+///
+/// See also:
+///
+///  * [Size], which represents a vector describing the size of a rectangle.
+class Offset extends OffsetBase {
+  /// Creates an offset. The first argument sets [dx], the horizontal component,
+  /// and the second sets [dy], the vertical component.
+  const Offset(double dx, double dy) : super(dx, dy);
+
+  /// Creates an offset from its [direction] and [distance].
+  ///
+  /// The direction is in radians clockwise from the positive x-axis.
+  ///
+  /// The distance can be omitted, to create a unit vector (distance = 1.0).
+  factory Offset.fromDirection(double direction, [ double distance = 1.0 ]) {
+    return Offset(distance * math.cos(direction), distance * math.sin(direction));
+  }
+
+  /// The x component of the offset.
+  ///
+  /// The y component is given by [dy].
+  double get dx => _dx;
+
+  /// The y component of the offset.
+  ///
+  /// The x component is given by [dx].
+  double get dy => _dy;
+
+  /// The magnitude of the offset.
+  ///
+  /// If you need this value to compare it to another [Offset]'s distance,
+  /// consider using [distanceSquared] instead, since it is cheaper to compute.
+  double get distance => math.sqrt(dx * dx + dy * dy);
+
+  /// The square of the magnitude of the offset.
+  ///
+  /// This is cheaper than computing the [distance] itself.
+  double get distanceSquared => dx * dx + dy * dy;
+
+  /// The angle of this offset as radians clockwise from the positive x-axis, in
+  /// the range -[pi] to [pi], assuming positive values of the x-axis go to the
+  /// right and positive values of the y-axis go down.
+  ///
+  /// Zero means that [dy] is zero and [dx] is zero or positive.
+  ///
+  /// Values from zero to [pi]/2 indicate positive values of [dx] and [dy], the
+  /// bottom-right quadrant.
+  ///
+  /// Values from [pi]/2 to [pi] indicate negative values of [dx] and positive
+  /// values of [dy], the bottom-left quadrant.
+  ///
+  /// Values from zero to -[pi]/2 indicate positive values of [dx] and negative
+  /// values of [dy], the top-right quadrant.
+  ///
+  /// Values from -[pi]/2 to -[pi] indicate negative values of [dx] and [dy],
+  /// the top-left quadrant.
+  ///
+  /// When [dy] is zero and [dx] is negative, the [direction] is [pi].
+  ///
+  /// When [dx] is zero, [direction] is [pi]/2 if [dy] is positive and -[pi]/2
+  /// if [dy] is negative.
+  ///
+  /// See also:
+  ///
+  ///  * [distance], to compute the magnitude of the vector.
+  ///  * [Canvas.rotate], which uses the same convention for its angle.
+  double get direction => math.atan2(dy, dx);
+
+  /// An offset with zero magnitude.
+  ///
+  /// This can be used to represent the origin of a coordinate space.
+  static const Offset zero = Offset(0.0, 0.0);
+
+  /// An offset with infinite x and y components.
+  ///
+  /// See also:
+  ///
+  ///  * [isInfinite], which checks whether either component is infinite.
+  ///  * [isFinite], which checks whether both components are finite.
+  // This is included for completeness, because [Size.infinite] exists.
+  static const Offset infinite = Offset(double.infinity, double.infinity);
+
+  /// Returns a new offset with the x component scaled by `scaleX` and the y
+  /// component scaled by `scaleY`.
+  ///
+  /// If the two scale arguments are the same, consider using the `*` operator
+  /// instead:
+  ///
+  /// ```dart
+  /// Offset a = const Offset(10.0, 10.0);
+  /// Offset b = a * 2.0; // same as: a.scale(2.0, 2.0)
+  /// ```
+  ///
+  /// If the two arguments are -1, consider using the unary `-` operator
+  /// instead:
+  ///
+  /// ```dart
+  /// Offset a = const Offset(10.0, 10.0);
+  /// Offset b = -a; // same as: a.scale(-1.0, -1.0)
+  /// ```
+  Offset scale(double scaleX, double scaleY) => Offset(dx * scaleX, dy * scaleY);
+
+  /// Returns a new offset with translateX added to the x component and
+  /// translateY added to the y component.
+  ///
+  /// If the arguments come from another [Offset], consider using the `+` or `-`
+  /// operators instead:
+  ///
+  /// ```dart
+  /// Offset a = const Offset(10.0, 10.0);
+  /// Offset b = const Offset(10.0, 10.0);
+  /// Offset c = a + b; // same as: a.translate(b.dx, b.dy)
+  /// Offset d = a - b; // same as: a.translate(-b.dx, -b.dy)
+  /// ```
+  Offset translate(double translateX, double translateY) => Offset(dx + translateX, dy + translateY);
+
+  /// Unary negation operator.
+  ///
+  /// Returns an offset with the coordinates negated.
+  ///
+  /// If the [Offset] represents an arrow on a plane, this operator returns the
+  /// same arrow but pointing in the reverse direction.
+  Offset operator -() => Offset(-dx, -dy);
+
+  /// Binary subtraction operator.
+  ///
+  /// Returns an offset whose [dx] value is the left-hand-side operand's [dx]
+  /// minus the right-hand-side operand's [dx] and whose [dy] value is the
+  /// left-hand-side operand's [dy] minus the right-hand-side operand's [dy].
+  ///
+  /// See also [translate].
+  Offset operator -(Offset other) => Offset(dx - other.dx, dy - other.dy);
+
+  /// Binary addition operator.
+  ///
+  /// Returns an offset whose [dx] value is the sum of the [dx] values of the
+  /// two operands, and whose [dy] value is the sum of the [dy] values of the
+  /// two operands.
+  ///
+  /// See also [translate].
+  Offset operator +(Offset other) => Offset(dx + other.dx, dy + other.dy);
+
+  /// Multiplication operator.
+  ///
+  /// Returns an offset whose coordinates are the coordinates of the
+  /// left-hand-side operand (an Offset) multiplied by the scalar
+  /// right-hand-side operand (a double).
+  ///
+  /// See also [scale].
+  Offset operator *(double operand) => Offset(dx * operand, dy * operand);
+
+  /// Division operator.
+  ///
+  /// Returns an offset whose coordinates are the coordinates of the
+  /// left-hand-side operand (an Offset) divided by the scalar right-hand-side
+  /// operand (a double).
+  ///
+  /// See also [scale].
+  Offset operator /(double operand) => Offset(dx / operand, dy / operand);
+
+  /// Integer (truncating) division operator.
+  ///
+  /// Returns an offset whose coordinates are the coordinates of the
+  /// left-hand-side operand (an Offset) divided by the scalar right-hand-side
+  /// operand (a double), rounded towards zero.
+  Offset operator ~/(double operand) => Offset((dx ~/ operand).toDouble(), (dy ~/ operand).toDouble());
+
+  /// Modulo (remainder) operator.
+  ///
+  /// Returns an offset whose coordinates are the remainder of dividing the
+  /// coordinates of the left-hand-side operand (an Offset) by the scalar
+  /// right-hand-side operand (a double).
+  Offset operator %(double operand) => Offset(dx % operand, dy % operand);
+
+  /// Rectangle constructor operator.
+  ///
+  /// Combines an [Offset] and a [Size] to form a [Rect] whose top-left
+  /// coordinate is the point given by adding this offset, the left-hand-side
+  /// operand, to the origin, and whose size is the right-hand-side operand.
+  ///
+  /// ```dart
+  /// Rect myRect = Offset.zero & const Size(100.0, 100.0);
+  /// // same as: Rect.fromLTWH(0.0, 0.0, 100.0, 100.0)
+  /// ```
+  Rect operator &(Size other) => Rect.fromLTWH(dx, dy, other.width, other.height);
+
+  /// Linearly interpolate between two offsets.
+  ///
+  /// If either offset is null, this function interpolates from [Offset.zero].
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+  /// 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  static Offset? lerp(Offset? a, Offset? b, double t) {
+    assert(t != null); // ignore: unnecessary_null_comparison
+    if (b == null) {
+      if (a == null) {
+        return null;
+      } else {
+        return a * (1.0 - t);
+      }
+    } else {
+      if (a == null) {
+        return b * t;
+      } else {
+        return Offset(_lerpDouble(a.dx, b.dx, t), _lerpDouble(a.dy, b.dy, t));
+      }
+    }
+  }
+
+  /// Compares two Offsets for equality.
+  @override
+  bool operator ==(Object other) {
+    return other is Offset
+        && other.dx == dx
+        && other.dy == dy;
+  }
+
+  @override
+  int get hashCode => hashValues(dx, dy);
+
+  @override
+  String toString() => 'Offset(${dx.toStringAsFixed(1)}, ${dy.toStringAsFixed(1)})';
+}
+
+/// Holds a 2D floating-point size.
+///
+/// You can think of this as an [Offset] from the origin.
+class Size extends OffsetBase {
+  /// Creates a [Size] with the given [width] and [height].
+  const Size(double width, double height) : super(width, height);
+
+  /// Creates an instance of [Size] that has the same values as another.
+  // Used by the rendering library's _DebugSize hack.
+  Size.copy(Size source) : super(source.width, source.height);
+
+  /// Creates a square [Size] whose [width] and [height] are the given dimension.
+  ///
+  /// See also:
+  ///
+  ///  * [Size.fromRadius], which is more convenient when the available size
+  ///    is the radius of a circle.
+  const Size.square(double dimension) : super(dimension, dimension);
+
+  /// Creates a [Size] with the given [width] and an infinite [height].
+  const Size.fromWidth(double width) : super(width, double.infinity);
+
+  /// Creates a [Size] with the given [height] and an infinite [width].
+  const Size.fromHeight(double height) : super(double.infinity, height);
+
+  /// Creates a square [Size] whose [width] and [height] are twice the given
+  /// dimension.
+  ///
+  /// This is a square that contains a circle with the given radius.
+  ///
+  /// See also:
+  ///
+  ///  * [Size.square], which creates a square with the given dimension.
+  const Size.fromRadius(double radius) : super(radius * 2.0, radius * 2.0);
+
+  /// The horizontal extent of this size.
+  double get width => _dx;
+
+  /// The vertical extent of this size.
+  double get height => _dy;
+
+  /// The aspect ratio of this size.
+  ///
+  /// This returns the [width] divided by the [height].
+  ///
+  /// If the [width] is zero, the result will be zero. If the [height] is zero
+  /// (and the [width] is not), the result will be [double.infinity] or
+  /// [double.negativeInfinity] as determined by the sign of [width].
+  ///
+  /// See also:
+  ///
+  ///  * [AspectRatio], a widget for giving a child widget a specific aspect
+  ///    ratio.
+  ///  * [FittedBox], a widget that (in most modes) attempts to maintain a
+  ///    child widget's aspect ratio while changing its size.
+  double get aspectRatio {
+    if (height != 0.0)
+      return width / height;
+    if (width > 0.0)
+      return double.infinity;
+    if (width < 0.0)
+      return double.negativeInfinity;
+    return 0.0;
+  }
+
+  /// An empty size, one with a zero width and a zero height.
+  static const Size zero = Size(0.0, 0.0);
+
+  /// A size whose [width] and [height] are infinite.
+  ///
+  /// See also:
+  ///
+  ///  * [isInfinite], which checks whether either dimension is infinite.
+  ///  * [isFinite], which checks whether both dimensions are finite.
+  static const Size infinite = Size(double.infinity, double.infinity);
+
+  /// Whether this size encloses a non-zero area.
+  ///
+  /// Negative areas are considered empty.
+  bool get isEmpty => width <= 0.0 || height <= 0.0;
+
+  /// Binary subtraction operator for [Size].
+  ///
+  /// Subtracting a [Size] from a [Size] returns the [Offset] that describes how
+  /// much bigger the left-hand-side operand is than the right-hand-side
+  /// operand. Adding that resulting [Offset] to the [Size] that was the
+  /// right-hand-side operand would return a [Size] equal to the [Size] that was
+  /// the left-hand-side operand. (i.e. if `sizeA - sizeB -> offsetA`, then
+  /// `offsetA + sizeB -> sizeA`)
+  ///
+  /// Subtracting an [Offset] from a [Size] returns the [Size] that is smaller than
+  /// the [Size] operand by the difference given by the [Offset] operand. In other
+  /// words, the returned [Size] has a [width] consisting of the [width] of the
+  /// left-hand-side operand minus the [Offset.dx] dimension of the
+  /// right-hand-side operand, and a [height] consisting of the [height] of the
+  /// left-hand-side operand minus the [Offset.dy] dimension of the
+  /// right-hand-side operand.
+  OffsetBase operator -(OffsetBase other) {
+    if (other is Size)
+      return Offset(width - other.width, height - other.height);
+    if (other is Offset)
+      return Size(width - other.dx, height - other.dy);
+    throw ArgumentError(other);
+  }
+
+  /// Binary addition operator for adding an [Offset] to a [Size].
+  ///
+  /// Returns a [Size] whose [width] is the sum of the [width] of the
+  /// left-hand-side operand, a [Size], and the [Offset.dx] dimension of the
+  /// right-hand-side operand, an [Offset], and whose [height] is the sum of the
+  /// [height] of the left-hand-side operand and the [Offset.dy] dimension of
+  /// the right-hand-side operand.
+  Size operator +(Offset other) => Size(width + other.dx, height + other.dy);
+
+  /// Multiplication operator.
+  ///
+  /// Returns a [Size] whose dimensions are the dimensions of the left-hand-side
+  /// operand (a [Size]) multiplied by the scalar right-hand-side operand (a
+  /// [double]).
+  Size operator *(double operand) => Size(width * operand, height * operand);
+
+  /// Division operator.
+  ///
+  /// Returns a [Size] whose dimensions are the dimensions of the left-hand-side
+  /// operand (a [Size]) divided by the scalar right-hand-side operand (a
+  /// [double]).
+  Size operator /(double operand) => Size(width / operand, height / operand);
+
+  /// Integer (truncating) division operator.
+  ///
+  /// Returns a [Size] whose dimensions are the dimensions of the left-hand-side
+  /// operand (a [Size]) divided by the scalar right-hand-side operand (a
+  /// [double]), rounded towards zero.
+  Size operator ~/(double operand) => Size((width ~/ operand).toDouble(), (height ~/ operand).toDouble());
+
+  /// Modulo (remainder) operator.
+  ///
+  /// Returns a [Size] whose dimensions are the remainder of dividing the
+  /// left-hand-side operand (a [Size]) by the scalar right-hand-side operand (a
+  /// [double]).
+  Size operator %(double operand) => Size(width % operand, height % operand);
+
+  /// The lesser of the magnitudes of the [width] and the [height].
+  double get shortestSide => math.min(width.abs(), height.abs());
+
+  /// The greater of the magnitudes of the [width] and the [height].
+  double get longestSide => math.max(width.abs(), height.abs());
+
+  // Convenience methods that do the equivalent of calling the similarly named
+  // methods on a Rect constructed from the given origin and this size.
+
+  /// The offset to the intersection of the top and left edges of the rectangle
+  /// described by the given [Offset] (which is interpreted as the top-left corner)
+  /// and this [Size].
+  ///
+  /// See also [Rect.topLeft].
+  Offset topLeft(Offset origin) => origin;
+
+  /// The offset to the center of the top edge of the rectangle described by the
+  /// given offset (which is interpreted as the top-left corner) and this size.
+  ///
+  /// See also [Rect.topCenter].
+  Offset topCenter(Offset origin) => Offset(origin.dx + width / 2.0, origin.dy);
+
+  /// The offset to the intersection of the top and right edges of the rectangle
+  /// described by the given offset (which is interpreted as the top-left corner)
+  /// and this size.
+  ///
+  /// See also [Rect.topRight].
+  Offset topRight(Offset origin) => Offset(origin.dx + width, origin.dy);
+
+  /// The offset to the center of the left edge of the rectangle described by the
+  /// given offset (which is interpreted as the top-left corner) and this size.
+  ///
+  /// See also [Rect.centerLeft].
+  Offset centerLeft(Offset origin) => Offset(origin.dx, origin.dy + height / 2.0);
+
+  /// The offset to the point halfway between the left and right and the top and
+  /// bottom edges of the rectangle described by the given offset (which is
+  /// interpreted as the top-left corner) and this size.
+  ///
+  /// See also [Rect.center].
+  Offset center(Offset origin) => Offset(origin.dx + width / 2.0, origin.dy + height / 2.0);
+
+  /// The offset to the center of the right edge of the rectangle described by the
+  /// given offset (which is interpreted as the top-left corner) and this size.
+  ///
+  /// See also [Rect.centerLeft].
+  Offset centerRight(Offset origin) => Offset(origin.dx + width, origin.dy + height / 2.0);
+
+  /// The offset to the intersection of the bottom and left edges of the
+  /// rectangle described by the given offset (which is interpreted as the
+  /// top-left corner) and this size.
+  ///
+  /// See also [Rect.bottomLeft].
+  Offset bottomLeft(Offset origin) => Offset(origin.dx, origin.dy + height);
+
+  /// The offset to the center of the bottom edge of the rectangle described by
+  /// the given offset (which is interpreted as the top-left corner) and this
+  /// size.
+  ///
+  /// See also [Rect.bottomLeft].
+  Offset bottomCenter(Offset origin) => Offset(origin.dx + width / 2.0, origin.dy + height);
+
+  /// The offset to the intersection of the bottom and right edges of the
+  /// rectangle described by the given offset (which is interpreted as the
+  /// top-left corner) and this size.
+  ///
+  /// See also [Rect.bottomRight].
+  Offset bottomRight(Offset origin) => Offset(origin.dx + width, origin.dy + height);
+
+  /// Whether the point specified by the given offset (which is assumed to be
+  /// relative to the top left of the size) lies between the left and right and
+  /// the top and bottom edges of a rectangle of this size.
+  ///
+  /// Rectangles include their top and left edges but exclude their bottom and
+  /// right edges.
+  bool contains(Offset offset) {
+    return offset.dx >= 0.0 && offset.dx < width && offset.dy >= 0.0 && offset.dy < height;
+  }
+
+  /// A [Size] with the [width] and [height] swapped.
+  Size get flipped => Size(height, width);
+
+  /// Linearly interpolate between two sizes
+  ///
+  /// If either size is null, this function interpolates from [Size.zero].
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+  /// 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  static Size? lerp(Size? a, Size? b, double t) {
+    assert(t != null); // ignore: unnecessary_null_comparison
+    if (b == null) {
+      if (a == null) {
+        return null;
+      } else {
+        return a * (1.0 - t);
+      }
+    } else {
+      if (a == null) {
+        return b * t;
+      } else {
+        return Size(_lerpDouble(a.width, b.width, t), _lerpDouble(a.height, b.height, t));
+      }
+    }
+  }
+
+  /// Compares two Sizes for equality.
+  // We don't compare the runtimeType because of _DebugSize in the framework.
+  @override
+  bool operator ==(Object other) {
+    return other is Size
+        && other._dx == _dx
+        && other._dy == _dy;
+  }
+
+  @override
+  int get hashCode => hashValues(_dx, _dy);
+
+  @override
+  String toString() => 'Size(${width.toStringAsFixed(1)}, ${height.toStringAsFixed(1)})';
+}
+
+/// An immutable, 2D, axis-aligned, floating-point rectangle whose coordinates
+/// are relative to a given origin.
+///
+/// A Rect can be created with one its constructors or from an [Offset] and a
+/// [Size] using the `&` operator:
+///
+/// ```dart
+/// Rect myRect = const Offset(1.0, 2.0) & const Size(3.0, 4.0);
+/// ```
+class Rect {
+  /// Construct a rectangle from its left, top, right, and bottom edges.
+  @pragma('vm:entry-point')
+  const Rect.fromLTRB(this.left, this.top, this.right, this.bottom)
+      : assert(left != null), // ignore: unnecessary_null_comparison
+        assert(top != null), // ignore: unnecessary_null_comparison
+        assert(right != null), // ignore: unnecessary_null_comparison
+        assert(bottom != null); // ignore: unnecessary_null_comparison
+
+  /// Construct a rectangle from its left and top edges, its width, and its
+  /// height.
+  ///
+  /// To construct a [Rect] from an [Offset] and a [Size], you can use the
+  /// rectangle constructor operator `&`. See [Offset.&].
+  const Rect.fromLTWH(double left, double top, double width, double height) : this.fromLTRB(left, top, left + width, top + height);
+
+  /// Construct a rectangle that bounds the given circle.
+  ///
+  /// The `center` argument is assumed to be an offset from the origin.
+  Rect.fromCircle({ required Offset center, required double radius }) : this.fromCenter(
+    center: center,
+    width: radius * 2,
+    height: radius * 2,
+  );
+
+  /// Constructs a rectangle from its center point, width, and height.
+  ///
+  /// The `center` argument is assumed to be an offset from the origin.
+  Rect.fromCenter({ required Offset center, required double width, required double height }) : this.fromLTRB(
+    center.dx - width / 2,
+    center.dy - height / 2,
+    center.dx + width / 2,
+    center.dy + height / 2,
+  );
+
+  /// Construct the smallest rectangle that encloses the given offsets, treating
+  /// them as vectors from the origin.
+  Rect.fromPoints(Offset a, Offset b) : this.fromLTRB(
+    math.min(a.dx, b.dx),
+    math.min(a.dy, b.dy),
+    math.max(a.dx, b.dx),
+    math.max(a.dy, b.dy),
+  );
+
+  Float32List get _value32 => Float32List.fromList(<double>[left, top, right, bottom]);
+
+  /// The offset of the left edge of this rectangle from the x axis.
+  final double left;
+
+  /// The offset of the top edge of this rectangle from the y axis.
+  final double top;
+
+  /// The offset of the right edge of this rectangle from the x axis.
+  final double right;
+
+  /// The offset of the bottom edge of this rectangle from the y axis.
+  final double bottom;
+
+  /// The distance between the left and right edges of this rectangle.
+  double get width => right - left;
+
+  /// The distance between the top and bottom edges of this rectangle.
+  double get height => bottom - top;
+
+  /// The distance between the upper-left corner and the lower-right corner of
+  /// this rectangle.
+  Size get size => Size(width, height);
+
+  /// Whether any of the dimensions are `NaN`.
+  bool get hasNaN => left.isNaN || top.isNaN || right.isNaN || bottom.isNaN;
+
+  /// A rectangle with left, top, right, and bottom edges all at zero.
+  static const Rect zero = Rect.fromLTRB(0.0, 0.0, 0.0, 0.0);
+
+  static const double _giantScalar = 1.0E+9; // matches kGiantRect from layer.h
+
+  /// A rectangle that covers the entire coordinate space.
+  ///
+  /// This covers the space from -1e9,-1e9 to 1e9,1e9.
+  /// This is the space over which graphics operations are valid.
+  static const Rect largest = Rect.fromLTRB(-_giantScalar, -_giantScalar, _giantScalar, _giantScalar);
+
+  /// Whether any of the coordinates of this rectangle are equal to positive infinity.
+  // included for consistency with Offset and Size
+  bool get isInfinite {
+    return left >= double.infinity
+        || top >= double.infinity
+        || right >= double.infinity
+        || bottom >= double.infinity;
+  }
+
+  /// Whether all coordinates of this rectangle are finite.
+  bool get isFinite => left.isFinite && top.isFinite && right.isFinite && bottom.isFinite;
+
+  /// Whether this rectangle encloses a non-zero area. Negative areas are
+  /// considered empty.
+  bool get isEmpty => left >= right || top >= bottom;
+
+  /// Returns a new rectangle translated by the given offset.
+  ///
+  /// To translate a rectangle by separate x and y components rather than by an
+  /// [Offset], consider [translate].
+  Rect shift(Offset offset) {
+    return Rect.fromLTRB(left + offset.dx, top + offset.dy, right + offset.dx, bottom + offset.dy);
+  }
+
+  /// Returns a new rectangle with translateX added to the x components and
+  /// translateY added to the y components.
+  ///
+  /// To translate a rectangle by an [Offset] rather than by separate x and y
+  /// components, consider [shift].
+  Rect translate(double translateX, double translateY) {
+    return Rect.fromLTRB(left + translateX, top + translateY, right + translateX, bottom + translateY);
+  }
+
+  /// Returns a new rectangle with edges moved outwards by the given delta.
+  Rect inflate(double delta) {
+    return Rect.fromLTRB(left - delta, top - delta, right + delta, bottom + delta);
+  }
+
+  /// Returns a new rectangle with edges moved inwards by the given delta.
+  Rect deflate(double delta) => inflate(-delta);
+
+  /// Returns a new rectangle that is the intersection of the given
+  /// rectangle and this rectangle. The two rectangles must overlap
+  /// for this to be meaningful. If the two rectangles do not overlap,
+  /// then the resulting Rect will have a negative width or height.
+  Rect intersect(Rect other) {
+    return Rect.fromLTRB(
+      math.max(left, other.left),
+      math.max(top, other.top),
+      math.min(right, other.right),
+      math.min(bottom, other.bottom)
+    );
+  }
+
+  /// Returns a new rectangle which is the bounding box containing this
+  /// rectangle and the given rectangle.
+  Rect expandToInclude(Rect other) {
+    return Rect.fromLTRB(
+        math.min(left, other.left),
+        math.min(top, other.top),
+        math.max(right, other.right),
+        math.max(bottom, other.bottom),
+    );
+  }
+
+  /// Whether `other` has a nonzero area of overlap with this rectangle.
+  bool overlaps(Rect other) {
+    if (right <= other.left || other.right <= left)
+      return false;
+    if (bottom <= other.top || other.bottom <= top)
+      return false;
+    return true;
+  }
+
+  /// The lesser of the magnitudes of the [width] and the [height] of this
+  /// rectangle.
+  double get shortestSide => math.min(width.abs(), height.abs());
+
+  /// The greater of the magnitudes of the [width] and the [height] of this
+  /// rectangle.
+  double get longestSide => math.max(width.abs(), height.abs());
+
+  /// The offset to the intersection of the top and left edges of this rectangle.
+  ///
+  /// See also [Size.topLeft].
+  Offset get topLeft => Offset(left, top);
+
+  /// The offset to the center of the top edge of this rectangle.
+  ///
+  /// See also [Size.topCenter].
+  Offset get topCenter => Offset(left + width / 2.0, top);
+
+  /// The offset to the intersection of the top and right edges of this rectangle.
+  ///
+  /// See also [Size.topRight].
+  Offset get topRight => Offset(right, top);
+
+  /// The offset to the center of the left edge of this rectangle.
+  ///
+  /// See also [Size.centerLeft].
+  Offset get centerLeft => Offset(left, top + height / 2.0);
+
+  /// The offset to the point halfway between the left and right and the top and
+  /// bottom edges of this rectangle.
+  ///
+  /// See also [Size.center].
+  Offset get center => Offset(left + width / 2.0, top + height / 2.0);
+
+  /// The offset to the center of the right edge of this rectangle.
+  ///
+  /// See also [Size.centerLeft].
+  Offset get centerRight => Offset(right, top + height / 2.0);
+
+  /// The offset to the intersection of the bottom and left edges of this rectangle.
+  ///
+  /// See also [Size.bottomLeft].
+  Offset get bottomLeft => Offset(left, bottom);
+
+  /// The offset to the center of the bottom edge of this rectangle.
+  ///
+  /// See also [Size.bottomLeft].
+  Offset get bottomCenter => Offset(left + width / 2.0, bottom);
+
+  /// The offset to the intersection of the bottom and right edges of this rectangle.
+  ///
+  /// See also [Size.bottomRight].
+  Offset get bottomRight => Offset(right, bottom);
+
+  /// Whether the point specified by the given offset (which is assumed to be
+  /// relative to the origin) lies between the left and right and the top and
+  /// bottom edges of this rectangle.
+  ///
+  /// Rectangles include their top and left edges but exclude their bottom and
+  /// right edges.
+  bool contains(Offset offset) {
+    return offset.dx >= left && offset.dx < right && offset.dy >= top && offset.dy < bottom;
+  }
+
+  /// Linearly interpolate between two rectangles.
+  ///
+  /// If either rect is null, [Rect.zero] is used as a substitute.
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+  /// 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  static Rect? lerp(Rect? a, Rect? b, double t) {
+    assert(t != null); // ignore: unnecessary_null_comparison
+    if (b == null) {
+      if (a == null) {
+        return null;
+      } else {
+        final double k = 1.0 - t;
+        return Rect.fromLTRB(a.left * k, a.top * k, a.right * k, a.bottom * k);
+      }
+    } else {
+      if (a == null) {
+        return Rect.fromLTRB(b.left * t, b.top * t, b.right * t, b.bottom * t);
+      } else {
+        return Rect.fromLTRB(
+          _lerpDouble(a.left, b.left, t),
+          _lerpDouble(a.top, b.top, t),
+          _lerpDouble(a.right, b.right, t),
+          _lerpDouble(a.bottom, b.bottom, t),
+        );
+      }
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (runtimeType != other.runtimeType)
+      return false;
+    return other is Rect
+        && other.left   == left
+        && other.top    == top
+        && other.right  == right
+        && other.bottom == bottom;
+  }
+
+  @override
+  int get hashCode => hashValues(left, top, right, bottom);
+
+  @override
+  String toString() => 'Rect.fromLTRB(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)})';
+}
+
+/// A radius for either circular or elliptical shapes.
+class Radius {
+  /// Constructs a circular radius. [x] and [y] will have the same radius value.
+  const Radius.circular(double radius) : this.elliptical(radius, radius);
+
+  /// Constructs an elliptical radius with the given radii.
+  const Radius.elliptical(this.x, this.y);
+
+  /// The radius value on the horizontal axis.
+  final double x;
+
+  /// The radius value on the vertical axis.
+  final double y;
+
+  /// A radius with [x] and [y] values set to zero.
+  ///
+  /// You can use [Radius.zero] with [RRect] to have right-angle corners.
+  static const Radius zero = Radius.circular(0.0);
+
+  /// Unary negation operator.
+  ///
+  /// Returns a Radius with the distances negated.
+  ///
+  /// Radiuses with negative values aren't geometrically meaningful, but could
+  /// occur as part of expressions. For example, negating a radius of one pixel
+  /// and then adding the result to another radius is equivalent to subtracting
+  /// a radius of one pixel from the other.
+  Radius operator -() => Radius.elliptical(-x, -y);
+
+  /// Binary subtraction operator.
+  ///
+  /// Returns a radius whose [x] value is the left-hand-side operand's [x]
+  /// minus the right-hand-side operand's [x] and whose [y] value is the
+  /// left-hand-side operand's [y] minus the right-hand-side operand's [y].
+  Radius operator -(Radius other) => Radius.elliptical(x - other.x, y - other.y);
+
+  /// Binary addition operator.
+  ///
+  /// Returns a radius whose [x] value is the sum of the [x] values of the
+  /// two operands, and whose [y] value is the sum of the [y] values of the
+  /// two operands.
+  Radius operator +(Radius other) => Radius.elliptical(x + other.x, y + other.y);
+
+  /// Multiplication operator.
+  ///
+  /// Returns a radius whose coordinates are the coordinates of the
+  /// left-hand-side operand (a radius) multiplied by the scalar
+  /// right-hand-side operand (a double).
+  Radius operator *(double operand) => Radius.elliptical(x * operand, y * operand);
+
+  /// Division operator.
+  ///
+  /// Returns a radius whose coordinates are the coordinates of the
+  /// left-hand-side operand (a radius) divided by the scalar right-hand-side
+  /// operand (a double).
+  Radius operator /(double operand) => Radius.elliptical(x / operand, y / operand);
+
+  /// Integer (truncating) division operator.
+  ///
+  /// Returns a radius whose coordinates are the coordinates of the
+  /// left-hand-side operand (a radius) divided by the scalar right-hand-side
+  /// operand (a double), rounded towards zero.
+  Radius operator ~/(double operand) => Radius.elliptical((x ~/ operand).toDouble(), (y ~/ operand).toDouble());
+
+  /// Modulo (remainder) operator.
+  ///
+  /// Returns a radius whose coordinates are the remainder of dividing the
+  /// coordinates of the left-hand-side operand (a radius) by the scalar
+  /// right-hand-side operand (a double).
+  Radius operator %(double operand) => Radius.elliptical(x % operand, y % operand);
+
+  /// Linearly interpolate between two radii.
+  ///
+  /// If either is null, this function substitutes [Radius.zero] instead.
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+  /// 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  static Radius? lerp(Radius? a, Radius? b, double t) {
+    assert(t != null); // ignore: unnecessary_null_comparison
+    if (b == null) {
+      if (a == null) {
+        return null;
+      } else {
+        final double k = 1.0 - t;
+        return Radius.elliptical(a.x * k, a.y * k);
+      }
+    } else {
+      if (a == null) {
+        return Radius.elliptical(b.x * t, b.y * t);
+      } else {
+        return Radius.elliptical(
+          _lerpDouble(a.x, b.x, t),
+          _lerpDouble(a.y, b.y, t),
+        );
+      }
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (runtimeType != other.runtimeType)
+      return false;
+
+    return other is Radius
+        && other.x == x
+        && other.y == y;
+  }
+
+  @override
+  int get hashCode => hashValues(x, y);
+
+  @override
+  String toString() {
+    return x == y ? 'Radius.circular(${x.toStringAsFixed(1)})' :
+                    'Radius.elliptical(${x.toStringAsFixed(1)}, '
+                    '${y.toStringAsFixed(1)})';
+  }
+}
+
+/// An immutable rounded rectangle with the custom radii for all four corners.
+class RRect {
+  /// Construct a rounded rectangle from its left, top, right, and bottom edges,
+  /// and the same radii along its horizontal axis and its vertical axis.
+  const RRect.fromLTRBXY(double left, double top, double right, double bottom,
+                   double radiusX, double radiusY) : this._raw(
+    top: top,
+    left: left,
+    right: right,
+    bottom: bottom,
+    tlRadiusX: radiusX,
+    tlRadiusY: radiusY,
+    trRadiusX: radiusX,
+    trRadiusY: radiusY,
+    blRadiusX: radiusX,
+    blRadiusY: radiusY,
+    brRadiusX: radiusX,
+    brRadiusY: radiusY,
+  );
+
+  /// Construct a rounded rectangle from its left, top, right, and bottom edges,
+  /// and the same radius in each corner.
+  RRect.fromLTRBR(double left, double top, double right, double bottom,
+                  Radius radius)
+    : this._raw(
+        top: top,
+        left: left,
+        right: right,
+        bottom: bottom,
+        tlRadiusX: radius.x,
+        tlRadiusY: radius.y,
+        trRadiusX: radius.x,
+        trRadiusY: radius.y,
+        blRadiusX: radius.x,
+        blRadiusY: radius.y,
+        brRadiusX: radius.x,
+        brRadiusY: radius.y,
+      );
+
+  /// Construct a rounded rectangle from its bounding box and the same radii
+  /// along its horizontal axis and its vertical axis.
+  RRect.fromRectXY(Rect rect, double radiusX, double radiusY)
+    : this._raw(
+        top: rect.top,
+        left: rect.left,
+        right: rect.right,
+        bottom: rect.bottom,
+        tlRadiusX: radiusX,
+        tlRadiusY: radiusY,
+        trRadiusX: radiusX,
+        trRadiusY: radiusY,
+        blRadiusX: radiusX,
+        blRadiusY: radiusY,
+        brRadiusX: radiusX,
+        brRadiusY: radiusY,
+      );
+
+  /// Construct a rounded rectangle from its bounding box and a radius that is
+  /// the same in each corner.
+  RRect.fromRectAndRadius(Rect rect, Radius radius)
+    : this._raw(
+        top: rect.top,
+        left: rect.left,
+        right: rect.right,
+        bottom: rect.bottom,
+        tlRadiusX: radius.x,
+        tlRadiusY: radius.y,
+        trRadiusX: radius.x,
+        trRadiusY: radius.y,
+        blRadiusX: radius.x,
+        blRadiusY: radius.y,
+        brRadiusX: radius.x,
+        brRadiusY: radius.y,
+      );
+
+  /// Construct a rounded rectangle from its left, top, right, and bottom edges,
+  /// and topLeft, topRight, bottomRight, and bottomLeft radii.
+  ///
+  /// The corner radii default to [Radius.zero], i.e. right-angled corners.
+  RRect.fromLTRBAndCorners(
+    double left,
+    double top,
+    double right,
+    double bottom, {
+    Radius topLeft = Radius.zero,
+    Radius topRight = Radius.zero,
+    Radius bottomRight = Radius.zero,
+    Radius bottomLeft = Radius.zero,
+  }) : this._raw(
+         top: top,
+         left: left,
+         right: right,
+         bottom: bottom,
+         tlRadiusX: topLeft.x,
+         tlRadiusY: topLeft.y,
+         trRadiusX: topRight.x,
+         trRadiusY: topRight.y,
+         blRadiusX: bottomLeft.x,
+         blRadiusY: bottomLeft.y,
+         brRadiusX: bottomRight.x,
+         brRadiusY: bottomRight.y,
+       );
+
+  /// Construct a rounded rectangle from its bounding box and and topLeft,
+  /// topRight, bottomRight, and bottomLeft radii.
+  ///
+  /// The corner radii default to [Radius.zero], i.e. right-angled corners
+  RRect.fromRectAndCorners(
+    Rect rect,
+    {
+      Radius topLeft = Radius.zero,
+      Radius topRight = Radius.zero,
+      Radius bottomRight = Radius.zero,
+      Radius bottomLeft = Radius.zero
+    }
+  ) : this._raw(
+        top: rect.top,
+        left: rect.left,
+        right: rect.right,
+        bottom: rect.bottom,
+        tlRadiusX: topLeft.x,
+        tlRadiusY: topLeft.y,
+        trRadiusX: topRight.x,
+        trRadiusY: topRight.y,
+        blRadiusX: bottomLeft.x,
+        blRadiusY: bottomLeft.y,
+        brRadiusX: bottomRight.x,
+        brRadiusY: bottomRight.y,
+      );
+
+  const RRect._raw({
+    this.left = 0.0,
+    this.top = 0.0,
+    this.right = 0.0,
+    this.bottom = 0.0,
+    this.tlRadiusX = 0.0,
+    this.tlRadiusY = 0.0,
+    this.trRadiusX = 0.0,
+    this.trRadiusY = 0.0,
+    this.brRadiusX = 0.0,
+    this.brRadiusY = 0.0,
+    this.blRadiusX = 0.0,
+    this.blRadiusY = 0.0,
+  }) : assert(left != null), // ignore: unnecessary_null_comparison
+       assert(top != null), // ignore: unnecessary_null_comparison
+       assert(right != null), // ignore: unnecessary_null_comparison
+       assert(bottom != null), // ignore: unnecessary_null_comparison
+       assert(tlRadiusX != null), // ignore: unnecessary_null_comparison
+       assert(tlRadiusY != null), // ignore: unnecessary_null_comparison
+       assert(trRadiusX != null), // ignore: unnecessary_null_comparison
+       assert(trRadiusY != null), // ignore: unnecessary_null_comparison
+       assert(brRadiusX != null), // ignore: unnecessary_null_comparison
+       assert(brRadiusY != null), // ignore: unnecessary_null_comparison
+       assert(blRadiusX != null), // ignore: unnecessary_null_comparison
+       assert(blRadiusY != null); // ignore: unnecessary_null_comparison
+
+  Float32List get _value32 => Float32List.fromList(<double>[
+    left,
+    top,
+    right,
+    bottom,
+    tlRadiusX,
+    tlRadiusY,
+    trRadiusX,
+    trRadiusY,
+    brRadiusX,
+    brRadiusY,
+    blRadiusX,
+    blRadiusY,
+  ]);
+
+  /// The offset of the left edge of this rectangle from the x axis.
+  final double left;
+
+  /// The offset of the top edge of this rectangle from the y axis.
+  final double top;
+
+  /// The offset of the right edge of this rectangle from the x axis.
+  final double right;
+
+  /// The offset of the bottom edge of this rectangle from the y axis.
+  final double bottom;
+
+  /// The top-left horizontal radius.
+  final double tlRadiusX;
+
+  /// The top-left vertical radius.
+  final double tlRadiusY;
+
+  /// The top-left [Radius].
+  Radius get tlRadius => Radius.elliptical(tlRadiusX, tlRadiusY);
+
+  /// The top-right horizontal radius.
+  final double trRadiusX;
+
+  /// The top-right vertical radius.
+  final double trRadiusY;
+
+  /// The top-right [Radius].
+  Radius get trRadius => Radius.elliptical(trRadiusX, trRadiusY);
+
+  /// The bottom-right horizontal radius.
+  final double brRadiusX;
+
+  /// The bottom-right vertical radius.
+  final double brRadiusY;
+
+  /// The bottom-right [Radius].
+  Radius get brRadius => Radius.elliptical(brRadiusX, brRadiusY);
+
+  /// The bottom-left horizontal radius.
+  final double blRadiusX;
+
+  /// The bottom-left vertical radius.
+  final double blRadiusY;
+
+  /// The bottom-left [Radius].
+  Radius get blRadius => Radius.elliptical(blRadiusX, blRadiusY);
+
+  /// A rounded rectangle with all the values set to zero.
+  static const RRect zero = RRect._raw();
+
+  /// Returns a new [RRect] translated by the given offset.
+  RRect shift(Offset offset) {
+    return RRect._raw(
+      left: left + offset.dx,
+      top: top + offset.dy,
+      right: right + offset.dx,
+      bottom: bottom + offset.dy,
+      tlRadiusX: tlRadiusX,
+      tlRadiusY: tlRadiusY,
+      trRadiusX: trRadiusX,
+      trRadiusY: trRadiusY,
+      blRadiusX: blRadiusX,
+      blRadiusY: blRadiusY,
+      brRadiusX: brRadiusX,
+      brRadiusY: brRadiusY,
+    );
+  }
+
+  /// Returns a new [RRect] with edges and radii moved outwards by the given
+  /// delta.
+  RRect inflate(double delta) {
+    return RRect._raw(
+      left: left - delta,
+      top: top - delta,
+      right: right + delta,
+      bottom: bottom + delta,
+      tlRadiusX: tlRadiusX + delta,
+      tlRadiusY: tlRadiusY + delta,
+      trRadiusX: trRadiusX + delta,
+      trRadiusY: trRadiusY + delta,
+      blRadiusX: blRadiusX + delta,
+      blRadiusY: blRadiusY + delta,
+      brRadiusX: brRadiusX + delta,
+      brRadiusY: brRadiusY + delta,
+    );
+  }
+
+  /// Returns a new [RRect] with edges and radii moved inwards by the given delta.
+  RRect deflate(double delta) => inflate(-delta);
+
+  /// The distance between the left and right edges of this rectangle.
+  double get width => right - left;
+
+  /// The distance between the top and bottom edges of this rectangle.
+  double get height => bottom - top;
+
+  /// The bounding box of this rounded rectangle (the rectangle with no rounded corners).
+  Rect get outerRect => Rect.fromLTRB(left, top, right, bottom);
+
+  /// The non-rounded rectangle that is constrained by the smaller of the two
+  /// diagonals, with each diagonal traveling through the middle of the curve
+  /// corners. The middle of a corner is the intersection of the curve with its
+  /// respective quadrant bisector.
+  Rect get safeInnerRect {
+    const double kInsetFactor = 0.29289321881; // 1-cos(pi/4)
+
+    final double leftRadius = math.max(blRadiusX, tlRadiusX);
+    final double topRadius = math.max(tlRadiusY, trRadiusY);
+    final double rightRadius = math.max(trRadiusX, brRadiusX);
+    final double bottomRadius = math.max(brRadiusY, blRadiusY);
+
+    return Rect.fromLTRB(
+      left + leftRadius * kInsetFactor,
+      top + topRadius * kInsetFactor,
+      right - rightRadius * kInsetFactor,
+      bottom - bottomRadius * kInsetFactor
+    );
+  }
+
+  /// The rectangle that would be formed using the axis-aligned intersection of
+  /// the sides of the rectangle, i.e., the rectangle formed from the
+  /// inner-most centers of the ellipses that form the corners. This is the
+  /// intersection of the [wideMiddleRect] and the [tallMiddleRect]. If any of
+  /// the intersections are void, the resulting [Rect] will have negative width
+  /// or height.
+  Rect get middleRect {
+    final double leftRadius = math.max(blRadiusX, tlRadiusX);
+    final double topRadius = math.max(tlRadiusY, trRadiusY);
+    final double rightRadius = math.max(trRadiusX, brRadiusX);
+    final double bottomRadius = math.max(brRadiusY, blRadiusY);
+    return Rect.fromLTRB(
+      left + leftRadius,
+      top + topRadius,
+      right - rightRadius,
+      bottom - bottomRadius
+    );
+  }
+
+  /// The biggest rectangle that is entirely inside the rounded rectangle and
+  /// has the full width of the rounded rectangle. If the rounded rectangle does
+  /// not have an axis-aligned intersection of its left and right side, the
+  /// resulting [Rect] will have negative width or height.
+  Rect get wideMiddleRect {
+    final double topRadius = math.max(tlRadiusY, trRadiusY);
+    final double bottomRadius = math.max(brRadiusY, blRadiusY);
+    return Rect.fromLTRB(
+      left,
+      top + topRadius,
+      right,
+      bottom - bottomRadius
+    );
+  }
+
+  /// The biggest rectangle that is entirely inside the rounded rectangle and
+  /// has the full height of the rounded rectangle. If the rounded rectangle
+  /// does not have an axis-aligned intersection of its top and bottom side, the
+  /// resulting [Rect] will have negative width or height.
+  Rect get tallMiddleRect {
+    final double leftRadius = math.max(blRadiusX, tlRadiusX);
+    final double rightRadius = math.max(trRadiusX, brRadiusX);
+    return Rect.fromLTRB(
+      left + leftRadius,
+      top,
+      right - rightRadius,
+      bottom
+    );
+  }
+
+  /// Whether this rounded rectangle encloses a non-zero area.
+  /// Negative areas are considered empty.
+  bool get isEmpty => left >= right || top >= bottom;
+
+  /// Whether all coordinates of this rounded rectangle are finite.
+  bool get isFinite => left.isFinite && top.isFinite && right.isFinite && bottom.isFinite;
+
+  /// Whether this rounded rectangle is a simple rectangle with zero
+  /// corner radii.
+  bool get isRect {
+    return (tlRadiusX == 0.0 || tlRadiusY == 0.0) &&
+           (trRadiusX == 0.0 || trRadiusY == 0.0) &&
+           (blRadiusX == 0.0 || blRadiusY == 0.0) &&
+           (brRadiusX == 0.0 || brRadiusY == 0.0);
+  }
+
+  /// Whether this rounded rectangle has a side with no straight section.
+  bool get isStadium {
+    return tlRadius == trRadius
+        && trRadius == brRadius
+        && brRadius == blRadius
+        && (width <= 2.0 * tlRadiusX || height <= 2.0 * tlRadiusY);
+  }
+
+  /// Whether this rounded rectangle has no side with a straight section.
+  bool get isEllipse {
+    return tlRadius == trRadius
+        && trRadius == brRadius
+        && brRadius == blRadius
+        && width <= 2.0 * tlRadiusX
+        && height <= 2.0 * tlRadiusY;
+  }
+
+  /// Whether this rounded rectangle would draw as a circle.
+  bool get isCircle => width == height && isEllipse;
+
+  /// The lesser of the magnitudes of the [width] and the [height] of this
+  /// rounded rectangle.
+  double get shortestSide => math.min(width.abs(), height.abs());
+
+  /// The greater of the magnitudes of the [width] and the [height] of this
+  /// rounded rectangle.
+  double get longestSide => math.max(width.abs(), height.abs());
+
+  /// Whether any of the dimensions are `NaN`.
+  bool get hasNaN => left.isNaN || top.isNaN || right.isNaN || bottom.isNaN ||
+                     trRadiusX.isNaN || trRadiusY.isNaN || tlRadiusX.isNaN || tlRadiusY.isNaN ||
+                     brRadiusX.isNaN || brRadiusY.isNaN || blRadiusX.isNaN || blRadiusY.isNaN;
+
+  /// The offset to the point halfway between the left and right and the top and
+  /// bottom edges of this rectangle.
+  Offset get center => Offset(left + width / 2.0, top + height / 2.0);
+
+  // Returns the minimum between min and scale to which radius1 and radius2
+  // should be scaled with in order not to exceed the limit.
+  double _getMin(double min, double radius1, double radius2, double limit) {
+    final double sum = radius1 + radius2;
+    if (sum > limit && sum != 0.0)
+      return math.min(min, limit / sum);
+    return min;
+  }
+
+  /// Scales all radii so that on each side their sum will not exceed the size
+  /// of the width/height.
+  ///
+  /// Skia already handles RRects with radii that are too large in this way.
+  /// Therefore, this method is only needed for RRect use cases that require
+  /// the appropriately scaled radii values.
+  ///
+  /// See the [Skia scaling implementation](https://github.com/google/skia/blob/master/src/core/SkRRect.cpp)
+  /// for more details.
+  RRect scaleRadii() {
+    double scale = 1.0;
+    scale = _getMin(scale, blRadiusY, tlRadiusY, height);
+    scale = _getMin(scale, tlRadiusX, trRadiusX, width);
+    scale = _getMin(scale, trRadiusY, brRadiusY, height);
+    scale = _getMin(scale, brRadiusX, blRadiusX, width);
+
+    if (scale < 1.0) {
+      return RRect._raw(
+        top: top,
+        left: left,
+        right: right,
+        bottom: bottom,
+        tlRadiusX: tlRadiusX * scale,
+        tlRadiusY: tlRadiusY * scale,
+        trRadiusX: trRadiusX * scale,
+        trRadiusY: trRadiusY * scale,
+        blRadiusX: blRadiusX * scale,
+        blRadiusY: blRadiusY * scale,
+        brRadiusX: brRadiusX * scale,
+        brRadiusY: brRadiusY * scale,
+      );
+    }
+
+    return RRect._raw(
+      top: top,
+      left: left,
+      right: right,
+      bottom: bottom,
+      tlRadiusX: tlRadiusX,
+      tlRadiusY: tlRadiusY,
+      trRadiusX: trRadiusX,
+      trRadiusY: trRadiusY,
+      blRadiusX: blRadiusX,
+      blRadiusY: blRadiusY,
+      brRadiusX: brRadiusX,
+      brRadiusY: brRadiusY,
+    );
+  }
+
+  /// Whether the point specified by the given offset (which is assumed to be
+  /// relative to the origin) lies inside the rounded rectangle.
+  ///
+  /// This method may allocate (and cache) a copy of the object with normalized
+  /// radii the first time it is called on a particular [RRect] instance. When
+  /// using this method, prefer to reuse existing [RRect]s rather than
+  /// recreating the object each time.
+  bool contains(Offset point) {
+    if (point.dx < left || point.dx >= right || point.dy < top || point.dy >= bottom)
+      return false; // outside bounding box
+
+    final RRect scaled = scaleRadii();
+
+    double x;
+    double y;
+    double radiusX;
+    double radiusY;
+    // check whether point is in one of the rounded corner areas
+    // x, y -> translate to ellipse center
+    if (point.dx < left + scaled.tlRadiusX &&
+        point.dy < top + scaled.tlRadiusY) {
+      x = point.dx - left - scaled.tlRadiusX;
+      y = point.dy - top - scaled.tlRadiusY;
+      radiusX = scaled.tlRadiusX;
+      radiusY = scaled.tlRadiusY;
+    } else if (point.dx > right - scaled.trRadiusX &&
+               point.dy < top + scaled.trRadiusY) {
+      x = point.dx - right + scaled.trRadiusX;
+      y = point.dy - top - scaled.trRadiusY;
+      radiusX = scaled.trRadiusX;
+      radiusY = scaled.trRadiusY;
+    } else if (point.dx > right - scaled.brRadiusX &&
+               point.dy > bottom - scaled.brRadiusY) {
+      x = point.dx - right + scaled.brRadiusX;
+      y = point.dy - bottom + scaled.brRadiusY;
+      radiusX = scaled.brRadiusX;
+      radiusY = scaled.brRadiusY;
+    } else if (point.dx < left + scaled.blRadiusX &&
+               point.dy > bottom - scaled.blRadiusY) {
+      x = point.dx - left - scaled.blRadiusX;
+      y = point.dy - bottom + scaled.blRadiusY;
+      radiusX = scaled.blRadiusX;
+      radiusY = scaled.blRadiusY;
+    } else {
+      return true; // inside and not within the rounded corner area
+    }
+
+    x = x / radiusX;
+    y = y / radiusY;
+    // check if the point is outside the unit circle
+    if (x * x + y * y > 1.0)
+      return false;
+    return true;
+  }
+
+  /// Linearly interpolate between two rounded rectangles.
+  ///
+  /// If either is null, this function substitutes [RRect.zero] instead.
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+  /// 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  static RRect? lerp(RRect? a, RRect? b, double t) {
+    assert(t != null); // ignore: unnecessary_null_comparison
+    if (b == null) {
+      if (a == null) {
+        return null;
+      } else {
+        final double k = 1.0 - t;
+        return RRect._raw(
+          left: a.left * k,
+          top: a.top * k,
+          right: a.right * k,
+          bottom: a.bottom * k,
+          tlRadiusX: a.tlRadiusX * k,
+          tlRadiusY: a.tlRadiusY * k,
+          trRadiusX: a.trRadiusX * k,
+          trRadiusY: a.trRadiusY * k,
+          brRadiusX: a.brRadiusX * k,
+          brRadiusY: a.brRadiusY * k,
+          blRadiusX: a.blRadiusX * k,
+          blRadiusY: a.blRadiusY * k,
+        );
+      }
+    } else {
+      if (a == null) {
+        return RRect._raw(
+          left: b.left * t,
+          top: b.top * t,
+          right: b.right * t,
+          bottom: b.bottom * t,
+          tlRadiusX: b.tlRadiusX * t,
+          tlRadiusY: b.tlRadiusY * t,
+          trRadiusX: b.trRadiusX * t,
+          trRadiusY: b.trRadiusY * t,
+          brRadiusX: b.brRadiusX * t,
+          brRadiusY: b.brRadiusY * t,
+          blRadiusX: b.blRadiusX * t,
+          blRadiusY: b.blRadiusY * t,
+        );
+      } else {
+        return RRect._raw(
+          left: _lerpDouble(a.left, b.left, t),
+          top: _lerpDouble(a.top, b.top, t),
+          right: _lerpDouble(a.right, b.right, t),
+          bottom: _lerpDouble(a.bottom, b.bottom, t),
+          tlRadiusX: _lerpDouble(a.tlRadiusX, b.tlRadiusX, t),
+          tlRadiusY: _lerpDouble(a.tlRadiusY, b.tlRadiusY, t),
+          trRadiusX: _lerpDouble(a.trRadiusX, b.trRadiusX, t),
+          trRadiusY: _lerpDouble(a.trRadiusY, b.trRadiusY, t),
+          brRadiusX: _lerpDouble(a.brRadiusX, b.brRadiusX, t),
+          brRadiusY: _lerpDouble(a.brRadiusY, b.brRadiusY, t),
+          blRadiusX: _lerpDouble(a.blRadiusX, b.blRadiusX, t),
+          blRadiusY: _lerpDouble(a.blRadiusY, b.blRadiusY, t),
+        );
+      }
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (runtimeType != other.runtimeType)
+      return false;
+    return other is RRect
+        && other.left      == left
+        && other.top       == top
+        && other.right     == right
+        && other.bottom    == bottom
+        && other.tlRadiusX == tlRadiusX
+        && other.tlRadiusY == tlRadiusY
+        && other.trRadiusX == trRadiusX
+        && other.trRadiusY == trRadiusY
+        && other.blRadiusX == blRadiusX
+        && other.blRadiusY == blRadiusY
+        && other.brRadiusX == brRadiusX
+        && other.brRadiusY == brRadiusY;
+  }
+
+  @override
+  int get hashCode => hashValues(left, top, right, bottom,
+    tlRadiusX, tlRadiusY, trRadiusX, trRadiusY,
+    blRadiusX, blRadiusY, brRadiusX, brRadiusY);
+
+  @override
+  String toString() {
+    final String rect = '${left.toStringAsFixed(1)}, '
+                        '${top.toStringAsFixed(1)}, '
+                        '${right.toStringAsFixed(1)}, '
+                        '${bottom.toStringAsFixed(1)}';
+    if (tlRadius == trRadius &&
+        trRadius == brRadius &&
+        brRadius == blRadius) {
+      if (tlRadius.x == tlRadius.y)
+        return 'RRect.fromLTRBR($rect, ${tlRadius.x.toStringAsFixed(1)})';
+      return 'RRect.fromLTRBXY($rect, ${tlRadius.x.toStringAsFixed(1)}, ${tlRadius.y.toStringAsFixed(1)})';
+    }
+    return 'RRect.fromLTRBAndCorners('
+             '$rect, '
+             'topLeft: $tlRadius, '
+             'topRight: $trRadius, '
+             'bottomRight: $brRadius, '
+             'bottomLeft: $blRadius'
+           ')';
+  }
+}
+
+/// A transform consisting of a translation, a rotation, and a uniform scale.
+///
+/// Used by [Canvas.drawAtlas]. This is a more efficient way to represent these
+/// simple transformations than a full matrix.
+// Modeled after Skia's SkRSXform.
+class RSTransform {
+  /// Creates an RSTransform.
+  ///
+  /// An [RSTransform] expresses the combination of a translation, a rotation
+  /// around a particular point, and a scale factor.
+  ///
+  /// The first argument, `scos`, is the cosine of the rotation, multiplied by
+  /// the scale factor.
+  ///
+  /// The second argument, `ssin`, is the sine of the rotation, multiplied by
+  /// that same scale factor.
+  ///
+  /// The third argument is the x coordinate of the translation, minus the
+  /// `scos` argument multiplied by the x-coordinate of the rotation point, plus
+  /// the `ssin` argument multiplied by the y-coordinate of the rotation point.
+  ///
+  /// The fourth argument is the y coordinate of the translation, minus the `ssin`
+  /// argument multiplied by the x-coordinate of the rotation point, minus the
+  /// `scos` argument multiplied by the y-coordinate of the rotation point.
+  ///
+  /// The [RSTransform.fromComponents] method may be a simpler way to
+  /// construct these values. However, if there is a way to factor out the
+  /// computations of the sine and cosine of the rotation so that they can be
+  /// reused over multiple calls to this constructor, it may be more efficient
+  /// to directly use this constructor instead.
+  RSTransform(double scos, double ssin, double tx, double ty) {
+    _value
+      ..[0] = scos
+      ..[1] = ssin
+      ..[2] = tx
+      ..[3] = ty;
+  }
+
+  /// Creates an RSTransform from its individual components.
+  ///
+  /// The `rotation` parameter gives the rotation in radians.
+  ///
+  /// The `scale` parameter describes the uniform scale factor.
+  ///
+  /// The `anchorX` and `anchorY` parameters give the coordinate of the point
+  /// around which to rotate.
+  ///
+  /// The `translateX` and `translateY` parameters give the coordinate of the
+  /// offset by which to translate.
+  ///
+  /// This constructor computes the arguments of the [new RSTransform]
+  /// constructor and then defers to that constructor to actually create the
+  /// object. If many [RSTransform] objects are being created and there is a way
+  /// to factor out the computations of the sine and cosine of the rotation
+  /// (which are computed each time this constructor is called) and reuse them
+  /// over multiple [RSTransform] objects, it may be more efficient to directly
+  /// use the more direct [new RSTransform] constructor instead.
+  factory RSTransform.fromComponents({
+    required double rotation,
+    required double scale,
+    required double anchorX,
+    required double anchorY,
+    required double translateX,
+    required double translateY
+  }) {
+    final double scos = math.cos(rotation) * scale;
+    final double ssin = math.sin(rotation) * scale;
+    final double tx = translateX + -scos * anchorX + ssin * anchorY;
+    final double ty = translateY + -ssin * anchorX - scos * anchorY;
+    return RSTransform(scos, ssin, tx, ty);
+  }
+
+  final Float32List _value = Float32List(4);
+
+  /// The cosine of the rotation multiplied by the scale factor.
+  double get scos => _value[0];
+
+  /// The sine of the rotation multiplied by that same scale factor.
+  double get ssin => _value[1];
+
+  /// The x coordinate of the translation, minus [scos] multiplied by the
+  /// x-coordinate of the rotation point, plus [ssin] multiplied by the
+  /// y-coordinate of the rotation point.
+  double get tx => _value[2];
+
+  /// The y coordinate of the translation, minus [ssin] multiplied by the
+  /// x-coordinate of the rotation point, minus [scos] multiplied by the
+  /// y-coordinate of the rotation point.
+  double get ty => _value[3];
+}
diff --git a/lib/src/ui/hash_codes.dart b/lib/src/ui/hash_codes.dart
new file mode 100644
index 0000000..47891c4
--- /dev/null
+++ b/lib/src/ui/hash_codes.dart
@@ -0,0 +1,123 @@
+// 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.
+
+
+
+part of dart.ui;
+
+class _HashEnd { const _HashEnd(); }
+const _HashEnd _hashEnd = _HashEnd();
+
+/// Jenkins hash function, optimized for small integers.
+//
+// Borrowed from the dart sdk: sdk/lib/math/jenkins_smi_hash.dart.
+class _Jenkins {
+  static int combine(int hash, Object? o) {
+    assert(o is! Iterable);
+    hash = 0x1fffffff & (hash + o.hashCode);
+    hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
+    return hash ^ (hash >> 6);
+  }
+
+  static int finish(int hash) {
+    hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
+    hash = hash ^ (hash >> 11);
+    return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
+  }
+}
+
+/// Combine up to twenty objects' hash codes into one value.
+///
+/// If you only need to handle one object's hash code, then just refer to its
+/// [Object.hashCode] getter directly.
+///
+/// If you need to combine an arbitrary number of objects from a [List] or other
+/// [Iterable], use [hashList]. The output of [hashList] can be used as one of
+/// the arguments to this function.
+///
+/// For example:
+///
+/// ```dart
+/// int hashCode => hashValues(foo, bar, hashList(quux), baz);
+/// ```
+int hashValues(
+  Object? arg01,            Object? arg02,          [ Object? arg03 = _hashEnd,
+  Object? arg04 = _hashEnd, Object? arg05 = _hashEnd, Object? arg06 = _hashEnd,
+  Object? arg07 = _hashEnd, Object? arg08 = _hashEnd, Object? arg09 = _hashEnd,
+  Object? arg10 = _hashEnd, Object? arg11 = _hashEnd, Object? arg12 = _hashEnd,
+  Object? arg13 = _hashEnd, Object? arg14 = _hashEnd, Object? arg15 = _hashEnd,
+  Object? arg16 = _hashEnd, Object? arg17 = _hashEnd, Object? arg18 = _hashEnd,
+  Object? arg19 = _hashEnd, Object? arg20 = _hashEnd ]) {
+  int result = 0;
+  result = _Jenkins.combine(result, arg01);
+  result = _Jenkins.combine(result, arg02);
+  if (!identical(arg03, _hashEnd)) {
+    result = _Jenkins.combine(result, arg03);
+    if (!identical(arg04, _hashEnd)) {
+      result = _Jenkins.combine(result, arg04);
+      if (!identical(arg05, _hashEnd)) {
+        result = _Jenkins.combine(result, arg05);
+        if (!identical(arg06, _hashEnd)) {
+          result = _Jenkins.combine(result, arg06);
+          if (!identical(arg07, _hashEnd)) {
+            result = _Jenkins.combine(result, arg07);
+            if (!identical(arg08, _hashEnd)) {
+              result = _Jenkins.combine(result, arg08);
+              if (!identical(arg09, _hashEnd)) {
+                result = _Jenkins.combine(result, arg09);
+                if (!identical(arg10, _hashEnd)) {
+                  result = _Jenkins.combine(result, arg10);
+                  if (!identical(arg11, _hashEnd)) {
+                    result = _Jenkins.combine(result, arg11);
+                    if (!identical(arg12, _hashEnd)) {
+                      result = _Jenkins.combine(result, arg12);
+                      if (!identical(arg13, _hashEnd)) {
+                        result = _Jenkins.combine(result, arg13);
+                        if (!identical(arg14, _hashEnd)) {
+                          result = _Jenkins.combine(result, arg14);
+                          if (!identical(arg15, _hashEnd)) {
+                            result = _Jenkins.combine(result, arg15);
+                            if (!identical(arg16, _hashEnd)) {
+                              result = _Jenkins.combine(result, arg16);
+                              if (!identical(arg17, _hashEnd)) {
+                                result = _Jenkins.combine(result, arg17);
+                                if (!identical(arg18, _hashEnd)) {
+                                  result = _Jenkins.combine(result, arg18);
+                                  if (!identical(arg19, _hashEnd)) {
+                                    result = _Jenkins.combine(result, arg19);
+                                    if (!identical(arg20, _hashEnd)) {
+                                      result = _Jenkins.combine(result, arg20);
+                                      // I can see my house from here!
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  return _Jenkins.finish(result);
+}
+
+/// Combine the [Object.hashCode] values of an arbitrary number of objects from
+/// an [Iterable] into one value. This function will return the same value if
+/// given null as if given an empty list.
+int hashList(Iterable<Object?>? arguments) {
+  int result = 0;
+  if (arguments != null) {
+    for (Object? argument in arguments)
+      result = _Jenkins.combine(result, argument);
+  }
+  return _Jenkins.finish(result);
+}
diff --git a/lib/src/ui/hooks.dart b/lib/src/ui/hooks.dart
new file mode 100644
index 0000000..0056145
--- /dev/null
+++ b/lib/src/ui/hooks.dart
@@ -0,0 +1,220 @@
+// 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.
+
+// TODO(dnfield): Remove unused_import ignores when https://github.com/dart-lang/sdk/issues/35164 is resolved.
+
+
+
+part of dart.ui;
+
+@pragma('vm:entry-point')
+// ignore: unused_element
+void _updateWindowMetrics(
+  Object id,
+  double devicePixelRatio,
+  double width,
+  double height,
+  double viewPaddingTop,
+  double viewPaddingRight,
+  double viewPaddingBottom,
+  double viewPaddingLeft,
+  double viewInsetTop,
+  double viewInsetRight,
+  double viewInsetBottom,
+  double viewInsetLeft,
+  double systemGestureInsetTop,
+  double systemGestureInsetRight,
+  double systemGestureInsetBottom,
+  double systemGestureInsetLeft,
+) {
+  PlatformDispatcher.instance._updateWindowMetrics(
+    id,
+    devicePixelRatio,
+    width,
+    height,
+    viewPaddingTop,
+    viewPaddingRight,
+    viewPaddingBottom,
+    viewPaddingLeft,
+    viewInsetTop,
+    viewInsetRight,
+    viewInsetBottom,
+    viewInsetLeft,
+    systemGestureInsetTop,
+    systemGestureInsetRight,
+    systemGestureInsetBottom,
+    systemGestureInsetLeft,
+  );
+}
+
+typedef _LocaleClosure = String Function();
+
+@pragma('vm:entry-point')
+// ignore: unused_element
+_LocaleClosure? _getLocaleClosure() => PlatformDispatcher.instance._localeClosure;
+
+@pragma('vm:entry-point')
+// ignore: unused_element
+void _updateLocales(List<String> locales) {
+  PlatformDispatcher.instance._updateLocales(locales);
+}
+
+@pragma('vm:entry-point')
+// ignore: unused_element
+void _updateUserSettingsData(String jsonData) {
+  PlatformDispatcher.instance._updateUserSettingsData(jsonData);
+}
+
+@pragma('vm:entry-point')
+// ignore: unused_element
+void _updateLifecycleState(String state) {
+  PlatformDispatcher.instance._updateLifecycleState(state);
+}
+
+@pragma('vm:entry-point')
+// ignore: unused_element
+void _updateSemanticsEnabled(bool enabled) {
+  PlatformDispatcher.instance._updateSemanticsEnabled(enabled);
+}
+
+@pragma('vm:entry-point')
+// ignore: unused_element
+void _updateAccessibilityFeatures(int values) {
+  PlatformDispatcher.instance._updateAccessibilityFeatures(values);
+}
+
+@pragma('vm:entry-point')
+// ignore: unused_element
+void _dispatchPlatformMessage(String name, ByteData? data, int responseId) {
+  PlatformDispatcher.instance._dispatchPlatformMessage(name, data, responseId);
+}
+
+@pragma('vm:entry-point')
+// ignore: unused_element
+void _dispatchPointerDataPacket(ByteData packet) {
+  PlatformDispatcher.instance._dispatchPointerDataPacket(packet);
+}
+
+@pragma('vm:entry-point')
+// ignore: unused_element
+void _dispatchSemanticsAction(int id, int action, ByteData? args) {
+  PlatformDispatcher.instance._dispatchSemanticsAction(id, action, args);
+}
+
+@pragma('vm:entry-point')
+// ignore: unused_element
+void _beginFrame(int microseconds) {
+  PlatformDispatcher.instance._beginFrame(microseconds);
+}
+
+@pragma('vm:entry-point')
+// ignore: unused_element
+void _reportTimings(List<int> timings) {
+  PlatformDispatcher.instance._reportTimings(timings);
+}
+
+@pragma('vm:entry-point')
+// ignore: unused_element
+void _drawFrame() {
+  PlatformDispatcher.instance._drawFrame();
+}
+
+// ignore: always_declare_return_types, prefer_generic_function_type_aliases
+typedef _ListStringArgFunction(List<String> args);
+
+@pragma('vm:entry-point')
+// ignore: unused_element
+void _runMainZoned(Function startMainIsolateFunction,
+                   Function userMainFunction,
+                   List<String> args) {
+  startMainIsolateFunction(() {
+    runZonedGuarded<void>(() {
+      if (userMainFunction is _ListStringArgFunction) {
+        (userMainFunction as dynamic)(args);
+      } else {
+        userMainFunction();
+      }
+    }, (Object error, StackTrace stackTrace) {
+      _reportUnhandledException(error.toString(), stackTrace.toString());
+    });
+  }, null);
+}
+
+void _reportUnhandledException(String error, String stackTrace) { throw UnimplementedError(); }
+
+/// Invokes [callback] inside the given [zone].
+void _invoke(void Function()? callback, Zone zone) {
+  if (callback == null) {
+    return;
+  }
+
+  assert(zone != null); // ignore: unnecessary_null_comparison
+
+  if (identical(zone, Zone.current)) {
+    callback();
+  } else {
+    zone.runGuarded(callback);
+  }
+}
+
+/// Invokes [callback] inside the given [zone] passing it [arg].
+///
+/// The 1 in the name refers to the number of arguments expected by
+/// the callback (and thus passed to this function, in addition to the
+/// callback itself and the zone in which the callback is executed).
+void _invoke1<A>(void Function(A a)? callback, Zone zone, A arg) {
+  if (callback == null) {
+    return;
+  }
+
+  assert(zone != null); // ignore: unnecessary_null_comparison
+
+  if (identical(zone, Zone.current)) {
+    callback(arg);
+  } else {
+    zone.runUnaryGuarded<A>(callback, arg);
+  }
+}
+
+/// Invokes [callback] inside the given [zone] passing it [arg1] and [arg2].
+///
+/// The 2 in the name refers to the number of arguments expected by
+/// the callback (and thus passed to this function, in addition to the
+/// callback itself and the zone in which the callback is executed).
+void _invoke2<A1, A2>(void Function(A1 a1, A2 a2)? callback, Zone zone, A1 arg1, A2 arg2) {
+  if (callback == null) {
+    return;
+  }
+
+  assert(zone != null); // ignore: unnecessary_null_comparison
+
+  if (identical(zone, Zone.current)) {
+    callback(arg1, arg2);
+  } else {
+    zone.runGuarded(() {
+      callback(arg1, arg2);
+    });
+  }
+}
+
+/// Invokes [callback] inside the given [zone] passing it [arg1], [arg2], and [arg3].
+///
+/// The 3 in the name refers to the number of arguments expected by
+/// the callback (and thus passed to this function, in addition to the
+/// callback itself and the zone in which the callback is executed).
+void _invoke3<A1, A2, A3>(void Function(A1 a1, A2 a2, A3 a3)? callback, Zone zone, A1 arg1, A2 arg2, A3 arg3) {
+  if (callback == null) {
+    return;
+  }
+
+  assert(zone != null); // ignore: unnecessary_null_comparison
+
+  if (identical(zone, Zone.current)) {
+    callback(arg1, arg2, arg3);
+  } else {
+    zone.runGuarded(() {
+      callback(arg1, arg2, arg3);
+    });
+  }
+}
diff --git a/lib/src/ui/isolate_name_server.dart b/lib/src/ui/isolate_name_server.dart
new file mode 100644
index 0000000..e68f666
--- /dev/null
+++ b/lib/src/ui/isolate_name_server.dart
@@ -0,0 +1,81 @@
+// 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.
+
+
+
+part of dart.ui;
+
+/// Static methods to allow for simple sharing of [SendPort]s across [Isolate]s.
+///
+/// All isolates share a global mapping of names to ports. An isolate can
+/// register a [SendPort] with a given name using [registerPortWithName];
+/// another isolate can then look up that port using [lookupPortByName].
+///
+/// To create a [SendPort], first create a [ReceivePort], then use
+/// [ReceivePort.sendPort].
+///
+/// Since multiple isolates can each obtain the same [SendPort] associated with
+/// a particular [ReceivePort], the protocol built on top of this mechanism
+/// should typically consist of a single message. If more elaborate two-way
+/// communication or multiple-message communication is necessary, it is
+/// recommended to establish a separate communication channel in that first
+/// message (e.g. by passing a dedicated [SendPort]).
+class IsolateNameServer {
+  // This class is only a namespace, and should not be instantiated or
+  // extended directly.
+  factory IsolateNameServer._() => throw UnsupportedError('Namespace');
+
+  /// Looks up the [SendPort] associated with a given name.
+  ///
+  /// Returns null if the name does not exist. To register the name in the first
+  /// place, consider [registerPortWithName].
+  ///
+  /// The `name` argument must not be null.
+  static SendPort? lookupPortByName(String name) {
+    assert(name != null, "'name' cannot be null."); // ignore: unnecessary_null_comparison
+    return _lookupPortByName(name);
+  }
+
+  /// Registers a [SendPort] with a given name.
+  ///
+  /// Returns true if registration is successful, and false if the name entry
+  /// already existed (in which case the earlier registration is left
+  /// unchanged). To remove a registration, consider [removePortNameMapping].
+  ///
+  /// Once a port has been registered with a name, it can be obtained from any
+  /// [Isolate] using [lookupPortByName].
+  ///
+  /// Multiple isolates should avoid attempting to register ports with the same
+  /// name, as there is an inherent race condition in doing so.
+  ///
+  /// The `port` and `name` arguments must not be null.
+  static bool registerPortWithName(SendPort port, String name) {
+    assert(port != null, "'port' cannot be null."); // ignore: unnecessary_null_comparison
+    assert(name != null, "'name' cannot be null."); // ignore: unnecessary_null_comparison
+    return _registerPortWithName(port, name);
+  }
+
+  /// Removes a name-to-[SendPort] mapping given its name.
+  ///
+  /// Returns true if the mapping was successfully removed, false if the mapping
+  /// did not exist. To add a registration, consider [registerPortWithName].
+  ///
+  /// Generally, removing a port name mapping is an inherently racy operation
+  /// (another isolate could have obtained the name just prior to the name being
+  /// removed, and thus would still be able to communicate over the port even
+  /// after it has been removed).
+  ///
+  /// The `name` argument must not be null.
+  static bool removePortNameMapping(String name) {
+    assert(name != null, "'name' cannot be null."); // ignore: unnecessary_null_comparison
+    return _removePortNameMapping(name);
+  }
+
+  static SendPort? _lookupPortByName(String name)
+      { throw UnimplementedError(); }
+  static bool _registerPortWithName(SendPort port, String name)
+      { throw UnimplementedError(); }
+  static bool _removePortNameMapping(String name)
+      { throw UnimplementedError(); }
+}
diff --git a/lib/src/ui/lerp.dart b/lib/src/ui/lerp.dart
new file mode 100644
index 0000000..58e57d8
--- /dev/null
+++ b/lib/src/ui/lerp.dart
@@ -0,0 +1,50 @@
+// 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.
+
+
+
+part of dart.ui;
+
+/// Linearly interpolate between two numbers, `a` and `b`, by an extrapolation
+/// factor `t`.
+///
+/// When `a` and `b` are equal or both NaN, `a` is returned.  Otherwise, if
+/// `a`, `b`, and `t` are required to be finite or null, and the result of `a +
+/// (b - a) * t` is returned, where nulls are defaulted to 0.0.
+double? lerpDouble(num? a, num? b, double t) {
+  if (a == b || (a?.isNaN == true) && (b?.isNaN == true))
+    return a?.toDouble();
+  a ??= 0.0;
+  b ??= 0.0;
+  assert(a.isFinite, 'Cannot interpolate between finite and non-finite values');
+  assert(b.isFinite, 'Cannot interpolate between finite and non-finite values');
+  assert(t.isFinite, 't must be finite when interpolating between values');
+  return a * (1.0 - t) + b * t;
+}
+
+/// Linearly interpolate between two doubles.
+///
+/// Same as [lerpDouble] but specialized for non-null `double` type.
+double _lerpDouble(double a, double b, double t) {
+  return a * (1.0 - t) + b * t;
+}
+
+/// Linearly interpolate between two integers.
+///
+/// Same as [lerpDouble] but specialized for non-null `int` type.
+double _lerpInt(int a, int b, double t) {
+  return a + (b - a) * t;
+}
+
+/// Same as [num.clamp] but specialized for non-null [int].
+int _clampInt(int value, int min, int max) {
+  assert(min <= max);
+  if (value < min) {
+    return min;
+  } else if (value > max) {
+    return max;
+  } else {
+    return value;
+  }
+}
diff --git a/lib/src/ui/natives.dart b/lib/src/ui/natives.dart
new file mode 100644
index 0000000..65e02fe
--- /dev/null
+++ b/lib/src/ui/natives.dart
@@ -0,0 +1,92 @@
+// 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.
+
+// TODO(dnfield): remove unused_element ignores when https://github.com/dart-lang/sdk/issues/35164 is resolved.
+
+
+
+part of dart.ui;
+
+// Corelib 'print' implementation.
+void _print(dynamic arg) {
+  _Logger._printString(arg.toString());
+}
+
+void _printDebug(dynamic arg) {
+  _Logger._printDebugString(arg.toString());
+}
+
+class _Logger {
+  static void _printString(String? s) { throw UnimplementedError(); }
+  static void _printDebugString(String? s) { throw UnimplementedError(); }
+}
+
+// If we actually run on big endian machines, we'll need to do something smarter
+// here. We don't use [Endian.Host] because it's not a compile-time
+// constant and can't propagate into the set/get calls.
+const Endian _kFakeHostEndian = Endian.little;
+
+// A service protocol extension to schedule a frame to be rendered into the
+// window.
+Future<developer.ServiceExtensionResponse> _scheduleFrame(
+    String method,
+    Map<String, String> parameters
+    ) async {
+  // Schedule the frame.
+  PlatformDispatcher.instance.scheduleFrame();
+  // Always succeed.
+  return developer.ServiceExtensionResponse.result(json.encode(<String, String>{
+    'type': 'Success',
+  }));
+}
+
+@pragma('vm:entry-point')
+void _setupHooks() {  // ignore: unused_element
+  assert(() {
+    // In debug mode, register the schedule frame extension.
+    developer.registerExtension('ext.ui.window.scheduleFrame', _scheduleFrame);
+    return true;
+  }());
+}
+
+/// Returns runtime Dart compilation trace as a UTF-8 encoded memory buffer.
+///
+/// The buffer contains a list of symbols compiled by the Dart JIT at runtime up
+/// to the point when this function was called. This list can be saved to a text
+/// file and passed to tools such as `flutter build` or Dart `gen_snapshot` in
+/// order to pre-compile this code offline.
+///
+/// The list has one symbol per line of the following format:
+/// `<namespace>,<class>,<symbol>\n`.
+///
+/// Here are some examples:
+///
+/// ```
+/// dart:core,Duration,get:inMilliseconds
+/// package:flutter/src/widgets/binding.dart,::,runApp
+/// file:///.../my_app.dart,::,main
+/// ```
+///
+/// This function is only effective in debug and dynamic modes, and will throw in AOT mode.
+List<int> saveCompilationTrace() {
+  final dynamic result = _saveCompilationTrace();
+  if (result is Error)
+    throw result;
+  return result as List<int>;
+}
+
+dynamic _saveCompilationTrace() { throw UnimplementedError(); }
+
+void _scheduleMicrotask(void callback()) { throw UnimplementedError(); }
+
+int? _getCallbackHandle(Function closure) { throw UnimplementedError(); }
+Function? _getCallbackFromHandle(int handle) { throw UnimplementedError(); }
+
+// Required for gen_snapshot to work correctly.
+int? _isolateId; // ignore: unused_element
+
+@pragma('vm:entry-point')
+Function _getPrintClosure() => _print;  // ignore: unused_element
+@pragma('vm:entry-point')
+Function _getScheduleMicrotaskClosure() => _scheduleMicrotask; // ignore: unused_element
diff --git a/lib/src/ui/painting.dart b/lib/src/ui/painting.dart
new file mode 100644
index 0000000..3230a97
--- /dev/null
+++ b/lib/src/ui/painting.dart
@@ -0,0 +1,5276 @@
+// 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.
+
+
+
+part of dart.ui;
+
+// Some methods in this file assert that their arguments are not null. These
+// asserts are just to improve the error messages; they should only cover
+// arguments that are either dereferenced _in Dart_, before being passed to the
+// engine, or that the engine explicitly null-checks itself (after attempting to
+// convert the argument to a native type). It should not be possible for a null
+// or invalid value to be used by the engine even in release mode, since that
+// would cause a crash. It is, however, acceptable for error messages to be much
+// less useful or correct in release mode than in debug mode.
+//
+// Painting APIs will also warn about arguments representing NaN coordinates,
+// which can not be rendered by Skia.
+
+// Update this list when changing the list of supported codecs.
+/// {@template dart.ui.imageFormats}
+/// JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, and WBMP. Additional
+/// formats may be supported by the underlying platform. Flutter will
+/// attempt to call platform API to decode unrecognized formats, and if the
+/// platform API supports decoding the image Flutter will be able to render it.
+/// {@endtemplate}
+
+// TODO(gspencergoog): remove this template block once the framework templates
+// are renamed to not reference it.
+/// {@template flutter.dart:ui.imageFormats}
+/// JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, and WBMP. Additional
+/// formats may be supported by the underlying platform. Flutter will
+/// attempt to call platform API to decode unrecognized formats, and if the
+/// platform API supports decoding the image Flutter will be able to render it.
+/// {@endtemplate}
+
+bool _rectIsValid(Rect rect) {
+  assert(rect != null, 'Rect argument was null.'); // ignore: unnecessary_null_comparison
+  assert(!rect.hasNaN, 'Rect argument contained a NaN value.');
+  return true;
+}
+
+bool _rrectIsValid(RRect rrect) {
+  assert(rrect != null, 'RRect argument was null.'); // ignore: unnecessary_null_comparison
+  assert(!rrect.hasNaN, 'RRect argument contained a NaN value.');
+  return true;
+}
+
+bool _offsetIsValid(Offset offset) {
+  assert(offset != null, 'Offset argument was null.'); // ignore: unnecessary_null_comparison
+  assert(!offset.dx.isNaN && !offset.dy.isNaN, 'Offset argument contained a NaN value.');
+  return true;
+}
+
+bool _matrix4IsValid(Float64List matrix4) {
+  assert(matrix4 != null, 'Matrix4 argument was null.'); // ignore: unnecessary_null_comparison
+  assert(matrix4.length == 16, 'Matrix4 must have 16 entries.');
+  assert(matrix4.every((double value) => value.isFinite), 'Matrix4 entries must be finite.');
+  return true;
+}
+
+bool _radiusIsValid(Radius radius) {
+  assert(radius != null, 'Radius argument was null.'); // ignore: unnecessary_null_comparison
+  assert(!radius.x.isNaN && !radius.y.isNaN, 'Radius argument contained a NaN value.');
+  return true;
+}
+
+Color _scaleAlpha(Color a, double factor) {
+  return a.withAlpha((a.alpha * factor).round().clamp(0, 255));
+}
+
+/// An immutable 32 bit color value in ARGB format.
+///
+/// Consider the light teal of the Flutter logo. It is fully opaque, with a red
+/// channel value of 0x42 (66), a green channel value of 0xA5 (165), and a blue
+/// channel value of 0xF5 (245). In the common "hash syntax" for color values,
+/// it would be described as `#42A5F5`.
+///
+/// Here are some ways it could be constructed:
+///
+/// ```dart
+/// Color c = const Color(0xFF42A5F5);
+/// Color c = const Color.fromARGB(0xFF, 0x42, 0xA5, 0xF5);
+/// Color c = const Color.fromARGB(255, 66, 165, 245);
+/// Color c = const Color.fromRGBO(66, 165, 245, 1.0);
+/// ```
+///
+/// If you are having a problem with `Color` wherein it seems your color is just
+/// not painting, check to make sure you are specifying the full 8 hexadecimal
+/// digits. If you only specify six, then the leading two digits are assumed to
+/// be zero, which means fully-transparent:
+///
+/// ```dart
+/// Color c1 = const Color(0xFFFFFF); // fully transparent white (invisible)
+/// Color c2 = const Color(0xFFFFFFFF); // fully opaque white (visible)
+/// ```
+///
+/// See also:
+///
+///  * [Colors](https://docs.flutter.io/flutter/material/Colors-class.html), which
+///    defines the colors found in the Material Design specification.
+class Color {
+  /// Construct a color from the lower 32 bits of an [int].
+  ///
+  /// The bits are interpreted as follows:
+  ///
+  /// * Bits 24-31 are the alpha value.
+  /// * Bits 16-23 are the red value.
+  /// * Bits 8-15 are the green value.
+  /// * Bits 0-7 are the blue value.
+  ///
+  /// In other words, if AA is the alpha value in hex, RR the red value in hex,
+  /// GG the green value in hex, and BB the blue value in hex, a color can be
+  /// expressed as `const Color(0xAARRGGBB)`.
+  ///
+  /// For example, to get a fully opaque orange, you would use `const
+  /// Color(0xFFFF9000)` (`FF` for the alpha, `FF` for the red, `90` for the
+  /// green, and `00` for the blue).
+  @pragma('vm:entry-point')
+  const Color(int value) : value = value & 0xFFFFFFFF;
+
+  /// Construct a color from the lower 8 bits of four integers.
+  ///
+  /// * `a` is the alpha value, with 0 being transparent and 255 being fully
+  ///   opaque.
+  /// * `r` is [red], from 0 to 255.
+  /// * `g` is [green], from 0 to 255.
+  /// * `b` is [blue], from 0 to 255.
+  ///
+  /// Out of range values are brought into range using modulo 255.
+  ///
+  /// See also [fromRGBO], which takes the alpha value as a floating point
+  /// value.
+  const Color.fromARGB(int a, int r, int g, int b) :
+    value = (((a & 0xff) << 24) |
+             ((r & 0xff) << 16) |
+             ((g & 0xff) << 8)  |
+             ((b & 0xff) << 0)) & 0xFFFFFFFF;
+
+  /// Create a color from red, green, blue, and opacity, similar to `rgba()` in CSS.
+  ///
+  /// * `r` is [red], from 0 to 255.
+  /// * `g` is [green], from 0 to 255.
+  /// * `b` is [blue], from 0 to 255.
+  /// * `opacity` is alpha channel of this color as a double, with 0.0 being
+  ///   transparent and 1.0 being fully opaque.
+  ///
+  /// Out of range values are brought into range using modulo 255.
+  ///
+  /// See also [fromARGB], which takes the opacity as an integer value.
+  const Color.fromRGBO(int r, int g, int b, double opacity) :
+    value = ((((opacity * 0xff ~/ 1) & 0xff) << 24) |
+              ((r                    & 0xff) << 16) |
+              ((g                    & 0xff) << 8)  |
+              ((b                    & 0xff) << 0)) & 0xFFFFFFFF;
+
+  /// A 32 bit value representing this color.
+  ///
+  /// The bits are assigned as follows:
+  ///
+  /// * Bits 24-31 are the alpha value.
+  /// * Bits 16-23 are the red value.
+  /// * Bits 8-15 are the green value.
+  /// * Bits 0-7 are the blue value.
+  final int value;
+
+  /// The alpha channel of this color in an 8 bit value.
+  ///
+  /// A value of 0 means this color is fully transparent. A value of 255 means
+  /// this color is fully opaque.
+  int get alpha => (0xff000000 & value) >> 24;
+
+  /// The alpha channel of this color as a double.
+  ///
+  /// A value of 0.0 means this color is fully transparent. A value of 1.0 means
+  /// this color is fully opaque.
+  double get opacity => alpha / 0xFF;
+
+  /// The red channel of this color in an 8 bit value.
+  int get red => (0x00ff0000 & value) >> 16;
+
+  /// The green channel of this color in an 8 bit value.
+  int get green => (0x0000ff00 & value) >> 8;
+
+  /// The blue channel of this color in an 8 bit value.
+  int get blue => (0x000000ff & value) >> 0;
+
+  /// Returns a new color that matches this color with the alpha channel
+  /// replaced with `a` (which ranges from 0 to 255).
+  ///
+  /// Out of range values will have unexpected effects.
+  Color withAlpha(int a) {
+    return Color.fromARGB(a, red, green, blue);
+  }
+
+  /// Returns a new color that matches this color with the alpha channel
+  /// replaced with the given `opacity` (which ranges from 0.0 to 1.0).
+  ///
+  /// Out of range values will have unexpected effects.
+  Color withOpacity(double opacity) {
+    assert(opacity >= 0.0 && opacity <= 1.0);
+    return withAlpha((255.0 * opacity).round());
+  }
+
+  /// Returns a new color that matches this color with the red channel replaced
+  /// with `r` (which ranges from 0 to 255).
+  ///
+  /// Out of range values will have unexpected effects.
+  Color withRed(int r) {
+    return Color.fromARGB(alpha, r, green, blue);
+  }
+
+  /// Returns a new color that matches this color with the green channel
+  /// replaced with `g` (which ranges from 0 to 255).
+  ///
+  /// Out of range values will have unexpected effects.
+  Color withGreen(int g) {
+    return Color.fromARGB(alpha, red, g, blue);
+  }
+
+  /// Returns a new color that matches this color with the blue channel replaced
+  /// with `b` (which ranges from 0 to 255).
+  ///
+  /// Out of range values will have unexpected effects.
+  Color withBlue(int b) {
+    return Color.fromARGB(alpha, red, green, b);
+  }
+
+  // See <https://www.w3.org/TR/WCAG20/#relativeluminancedef>
+  static double _linearizeColorComponent(double component) {
+    if (component <= 0.03928)
+      return component / 12.92;
+    return math.pow((component + 0.055) / 1.055, 2.4) as double;
+  }
+
+  /// Returns a brightness value between 0 for darkest and 1 for lightest.
+  ///
+  /// Represents the relative luminance of the color. This value is computationally
+  /// expensive to calculate.
+  ///
+  /// See <https://en.wikipedia.org/wiki/Relative_luminance>.
+  double computeLuminance() {
+    // See <https://www.w3.org/TR/WCAG20/#relativeluminancedef>
+    final double R = _linearizeColorComponent(red / 0xFF);
+    final double G = _linearizeColorComponent(green / 0xFF);
+    final double B = _linearizeColorComponent(blue / 0xFF);
+    return 0.2126 * R + 0.7152 * G + 0.0722 * B;
+  }
+
+  /// Linearly interpolate between two colors.
+  ///
+  /// This is intended to be fast but as a result may be ugly. Consider
+  /// [HSVColor] or writing custom logic for interpolating colors.
+  ///
+  /// If either color is null, this function linearly interpolates from a
+  /// transparent instance of the other color. This is usually preferable to
+  /// interpolating from [material.Colors.transparent] (`const
+  /// Color(0x00000000)`), which is specifically transparent _black_.
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+  /// 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]). Each channel
+  /// will be clamped to the range 0 to 255.
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  static Color? lerp(Color? a, Color? b, double t) {
+    assert(t != null); // ignore: unnecessary_null_comparison
+    if (b == null) {
+      if (a == null) {
+        return null;
+      } else {
+        return _scaleAlpha(a, 1.0 - t);
+      }
+    } else {
+      if (a == null) {
+        return _scaleAlpha(b, t);
+      } else {
+        return Color.fromARGB(
+          _clampInt(_lerpInt(a.alpha, b.alpha, t).toInt(), 0, 255),
+          _clampInt(_lerpInt(a.red, b.red, t).toInt(), 0, 255),
+          _clampInt(_lerpInt(a.green, b.green, t).toInt(), 0, 255),
+          _clampInt(_lerpInt(a.blue, b.blue, t).toInt(), 0, 255),
+        );
+      }
+    }
+  }
+
+  /// Combine the foreground color as a transparent color over top
+  /// of a background color, and return the resulting combined color.
+  ///
+  /// This uses standard alpha blending ("SRC over DST") rules to produce a
+  /// blended color from two colors. This can be used as a performance
+  /// enhancement when trying to avoid needless alpha blending compositing
+  /// operations for two things that are solid colors with the same shape, but
+  /// overlay each other: instead, just paint one with the combined color.
+  static Color alphaBlend(Color foreground, Color background) {
+    final int alpha = foreground.alpha;
+    if (alpha == 0x00) { // Foreground completely transparent.
+      return background;
+    }
+    final int invAlpha = 0xff - alpha;
+    int backAlpha = background.alpha;
+    if (backAlpha == 0xff) { // Opaque background case
+      return Color.fromARGB(
+        0xff,
+        (alpha * foreground.red + invAlpha * background.red) ~/ 0xff,
+        (alpha * foreground.green + invAlpha * background.green) ~/ 0xff,
+        (alpha * foreground.blue + invAlpha * background.blue) ~/ 0xff,
+      );
+    } else { // General case
+      backAlpha = (backAlpha * invAlpha) ~/ 0xff;
+      final int outAlpha = alpha + backAlpha;
+      assert(outAlpha != 0x00);
+      return Color.fromARGB(
+        outAlpha,
+        (foreground.red * alpha + background.red * backAlpha) ~/ outAlpha,
+        (foreground.green * alpha + background.green * backAlpha) ~/ outAlpha,
+        (foreground.blue * alpha + background.blue * backAlpha) ~/ outAlpha,
+      );
+    }
+  }
+
+  /// Returns an alpha value representative of the provided [opacity] value.
+  ///
+  /// The [opacity] value may not be null.
+  static int getAlphaFromOpacity(double opacity) {
+    assert(opacity != null); // ignore: unnecessary_null_comparison
+    return (opacity.clamp(0.0, 1.0) * 255).round();
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is Color
+        && other.value == value;
+  }
+
+  @override
+  int get hashCode => value.hashCode;
+
+  @override
+  String toString() => 'Color(0x${value.toRadixString(16).padLeft(8, '0')})';
+}
+
+/// Algorithms to use when painting on the canvas.
+///
+/// When drawing a shape or image onto a canvas, different algorithms can be
+/// used to blend the pixels. The different values of [BlendMode] specify
+/// different such algorithms.
+///
+/// Each algorithm has two inputs, the _source_, which is the image being drawn,
+/// and the _destination_, which is the image into which the source image is
+/// being composited. The destination is often thought of as the _background_.
+/// The source and destination both have four color channels, the red, green,
+/// blue, and alpha channels. These are typically represented as numbers in the
+/// range 0.0 to 1.0. The output of the algorithm also has these same four
+/// channels, with values computed from the source and destination.
+///
+/// The documentation of each value below describes how the algorithm works. In
+/// each case, an image shows the output of blending a source image with a
+/// destination image. In the images below, the destination is represented by an
+/// image with horizontal lines and an opaque landscape photograph, and the
+/// source is represented by an image with vertical lines (the same lines but
+/// rotated) and a bird clip-art image. The [src] mode shows only the source
+/// image, and the [dst] mode shows only the destination image. In the
+/// documentation below, the transparency is illustrated by a checkerboard
+/// pattern. The [clear] mode drops both the source and destination, resulting
+/// in an output that is entirely transparent (illustrated by a solid
+/// checkerboard pattern).
+///
+/// The horizontal and vertical bars in these images show the red, green, and
+/// blue channels with varying opacity levels, then all three color channels
+/// together with those same varying opacity levels, then all three color
+/// channels set to zero with those varying opacity levels, then two bars showing
+/// a red/green/blue repeating gradient, the first with full opacity and the
+/// second with partial opacity, and finally a bar with the three color channels
+/// set to zero but the opacity varying in a repeating gradient.
+///
+/// ## Application to the [Canvas] API
+///
+/// When using [Canvas.saveLayer] and [Canvas.restore], the blend mode of the
+/// [Paint] given to the [Canvas.saveLayer] will be applied when
+/// [Canvas.restore] is called. Each call to [Canvas.saveLayer] introduces a new
+/// layer onto which shapes and images are painted; when [Canvas.restore] is
+/// called, that layer is then composited onto the parent layer, with the source
+/// being the most-recently-drawn shapes and images, and the destination being
+/// the parent layer. (For the first [Canvas.saveLayer] call, the parent layer
+/// is the canvas itself.)
+///
+/// See also:
+///
+///  * [Paint.blendMode], which uses [BlendMode] to define the compositing
+///    strategy.
+enum BlendMode {
+  // This list comes from Skia's SkXfermode.h and the values (order) should be
+  // kept in sync.
+  // See: https://skia.org/user/api/skpaint#SkXfermode
+
+  /// Drop both the source and destination images, leaving nothing.
+  ///
+  /// This corresponds to the "clear" Porter-Duff operator.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_clear.png)
+  clear,
+
+  /// Drop the destination image, only paint the source image.
+  ///
+  /// Conceptually, the destination is first cleared, then the source image is
+  /// painted.
+  ///
+  /// This corresponds to the "Copy" Porter-Duff operator.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_src.png)
+  src,
+
+  /// Drop the source image, only paint the destination image.
+  ///
+  /// Conceptually, the source image is discarded, leaving the destination
+  /// untouched.
+  ///
+  /// This corresponds to the "Destination" Porter-Duff operator.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_dst.png)
+  dst,
+
+  /// Composite the source image over the destination image.
+  ///
+  /// This is the default value. It represents the most intuitive case, where
+  /// shapes are painted on top of what is below, with transparent areas showing
+  /// the destination layer.
+  ///
+  /// This corresponds to the "Source over Destination" Porter-Duff operator,
+  /// also known as the Painter's Algorithm.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_srcOver.png)
+  srcOver,
+
+  /// Composite the source image under the destination image.
+  ///
+  /// This is the opposite of [srcOver].
+  ///
+  /// This corresponds to the "Destination over Source" Porter-Duff operator.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_dstOver.png)
+  ///
+  /// This is useful when the source image should have been painted before the
+  /// destination image, but could not be.
+  dstOver,
+
+  /// Show the source image, but only where the two images overlap. The
+  /// destination image is not rendered, it is treated merely as a mask. The
+  /// color channels of the destination are ignored, only the opacity has an
+  /// effect.
+  ///
+  /// To show the destination image instead, consider [dstIn].
+  ///
+  /// To reverse the semantic of the mask (only showing the source where the
+  /// destination is absent, rather than where it is present), consider
+  /// [srcOut].
+  ///
+  /// This corresponds to the "Source in Destination" Porter-Duff operator.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_srcIn.png)
+  srcIn,
+
+  /// Show the destination image, but only where the two images overlap. The
+  /// source image is not rendered, it is treated merely as a mask. The color
+  /// channels of the source are ignored, only the opacity has an effect.
+  ///
+  /// To show the source image instead, consider [srcIn].
+  ///
+  /// To reverse the semantic of the mask (only showing the source where the
+  /// destination is present, rather than where it is absent), consider [dstOut].
+  ///
+  /// This corresponds to the "Destination in Source" Porter-Duff operator.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_dstIn.png)
+  dstIn,
+
+  /// Show the source image, but only where the two images do not overlap. The
+  /// destination image is not rendered, it is treated merely as a mask. The color
+  /// channels of the destination are ignored, only the opacity has an effect.
+  ///
+  /// To show the destination image instead, consider [dstOut].
+  ///
+  /// To reverse the semantic of the mask (only showing the source where the
+  /// destination is present, rather than where it is absent), consider [srcIn].
+  ///
+  /// This corresponds to the "Source out Destination" Porter-Duff operator.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_srcOut.png)
+  srcOut,
+
+  /// Show the destination image, but only where the two images do not overlap. The
+  /// source image is not rendered, it is treated merely as a mask. The color
+  /// channels of the source are ignored, only the opacity has an effect.
+  ///
+  /// To show the source image instead, consider [srcOut].
+  ///
+  /// To reverse the semantic of the mask (only showing the destination where the
+  /// source is present, rather than where it is absent), consider [dstIn].
+  ///
+  /// This corresponds to the "Destination out Source" Porter-Duff operator.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_dstOut.png)
+  dstOut,
+
+  /// Composite the source image over the destination image, but only where it
+  /// overlaps the destination.
+  ///
+  /// This corresponds to the "Source atop Destination" Porter-Duff operator.
+  ///
+  /// This is essentially the [srcOver] operator, but with the output's opacity
+  /// channel being set to that of the destination image instead of being a
+  /// combination of both image's opacity channels.
+  ///
+  /// For a variant with the destination on top instead of the source, see
+  /// [dstATop].
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_srcATop.png)
+  srcATop,
+
+  /// Composite the destination image over the source image, but only where it
+  /// overlaps the source.
+  ///
+  /// This corresponds to the "Destination atop Source" Porter-Duff operator.
+  ///
+  /// This is essentially the [dstOver] operator, but with the output's opacity
+  /// channel being set to that of the source image instead of being a
+  /// combination of both image's opacity channels.
+  ///
+  /// For a variant with the source on top instead of the destination, see
+  /// [srcATop].
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_dstATop.png)
+  dstATop,
+
+  /// Apply a bitwise `xor` operator to the source and destination images. This
+  /// leaves transparency where they would overlap.
+  ///
+  /// This corresponds to the "Source xor Destination" Porter-Duff operator.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_xor.png)
+  xor,
+
+  /// Sum the components of the source and destination images.
+  ///
+  /// Transparency in a pixel of one of the images reduces the contribution of
+  /// that image to the corresponding output pixel, as if the color of that
+  /// pixel in that image was darker.
+  ///
+  /// This corresponds to the "Source plus Destination" Porter-Duff operator.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_plus.png)
+  plus,
+
+  /// Multiply the color components of the source and destination images.
+  ///
+  /// This can only result in the same or darker colors (multiplying by white,
+  /// 1.0, results in no change; multiplying by black, 0.0, results in black).
+  ///
+  /// When compositing two opaque images, this has similar effect to overlapping
+  /// two transparencies on a projector.
+  ///
+  /// For a variant that also multiplies the alpha channel, consider [multiply].
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_modulate.png)
+  ///
+  /// See also:
+  ///
+  ///  * [screen], which does a similar computation but inverted.
+  ///  * [overlay], which combines [modulate] and [screen] to favor the
+  ///    destination image.
+  ///  * [hardLight], which combines [modulate] and [screen] to favor the
+  ///    source image.
+  modulate,
+
+  // Following blend modes are defined in the CSS Compositing standard.
+
+  /// Multiply the inverse of the components of the source and destination
+  /// images, and inverse the result.
+  ///
+  /// Inverting the components means that a fully saturated channel (opaque
+  /// white) is treated as the value 0.0, and values normally treated as 0.0
+  /// (black, transparent) are treated as 1.0.
+  ///
+  /// This is essentially the same as [modulate] blend mode, but with the values
+  /// of the colors inverted before the multiplication and the result being
+  /// inverted back before rendering.
+  ///
+  /// This can only result in the same or lighter colors (multiplying by black,
+  /// 1.0, results in no change; multiplying by white, 0.0, results in white).
+  /// Similarly, in the alpha channel, it can only result in more opaque colors.
+  ///
+  /// This has similar effect to two projectors displaying their images on the
+  /// same screen simultaneously.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_screen.png)
+  ///
+  /// See also:
+  ///
+  ///  * [modulate], which does a similar computation but without inverting the
+  ///    values.
+  ///  * [overlay], which combines [modulate] and [screen] to favor the
+  ///    destination image.
+  ///  * [hardLight], which combines [modulate] and [screen] to favor the
+  ///    source image.
+  screen,  // The last coeff mode.
+
+  /// Multiply the components of the source and destination images after
+  /// adjusting them to favor the destination.
+  ///
+  /// Specifically, if the destination value is smaller, this multiplies it with
+  /// the source value, whereas is the source value is smaller, it multiplies
+  /// the inverse of the source value with the inverse of the destination value,
+  /// then inverts the result.
+  ///
+  /// Inverting the components means that a fully saturated channel (opaque
+  /// white) is treated as the value 0.0, and values normally treated as 0.0
+  /// (black, transparent) are treated as 1.0.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_overlay.png)
+  ///
+  /// See also:
+  ///
+  ///  * [modulate], which always multiplies the values.
+  ///  * [screen], which always multiplies the inverses of the values.
+  ///  * [hardLight], which is similar to [overlay] but favors the source image
+  ///    instead of the destination image.
+  overlay,
+
+  /// Composite the source and destination image by choosing the lowest value
+  /// from each color channel.
+  ///
+  /// The opacity of the output image is computed in the same way as for
+  /// [srcOver].
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_darken.png)
+  darken,
+
+  /// Composite the source and destination image by choosing the highest value
+  /// from each color channel.
+  ///
+  /// The opacity of the output image is computed in the same way as for
+  /// [srcOver].
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_lighten.png)
+  lighten,
+
+  /// Divide the destination by the inverse of the source.
+  ///
+  /// Inverting the components means that a fully saturated channel (opaque
+  /// white) is treated as the value 0.0, and values normally treated as 0.0
+  /// (black, transparent) are treated as 1.0.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_colorDodge.png)
+  colorDodge,
+
+  /// Divide the inverse of the destination by the source, and inverse the result.
+  ///
+  /// Inverting the components means that a fully saturated channel (opaque
+  /// white) is treated as the value 0.0, and values normally treated as 0.0
+  /// (black, transparent) are treated as 1.0.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_colorBurn.png)
+  colorBurn,
+
+  /// Multiply the components of the source and destination images after
+  /// adjusting them to favor the source.
+  ///
+  /// Specifically, if the source value is smaller, this multiplies it with the
+  /// destination value, whereas is the destination value is smaller, it
+  /// multiplies the inverse of the destination value with the inverse of the
+  /// source value, then inverts the result.
+  ///
+  /// Inverting the components means that a fully saturated channel (opaque
+  /// white) is treated as the value 0.0, and values normally treated as 0.0
+  /// (black, transparent) are treated as 1.0.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_hardLight.png)
+  ///
+  /// See also:
+  ///
+  ///  * [modulate], which always multiplies the values.
+  ///  * [screen], which always multiplies the inverses of the values.
+  ///  * [overlay], which is similar to [hardLight] but favors the destination
+  ///    image instead of the source image.
+  hardLight,
+
+  /// Use [colorDodge] for source values below 0.5 and [colorBurn] for source
+  /// values above 0.5.
+  ///
+  /// This results in a similar but softer effect than [overlay].
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_softLight.png)
+  ///
+  /// See also:
+  ///
+  ///  * [color], which is a more subtle tinting effect.
+  softLight,
+
+  /// Subtract the smaller value from the bigger value for each channel.
+  ///
+  /// Compositing black has no effect; compositing white inverts the colors of
+  /// the other image.
+  ///
+  /// The opacity of the output image is computed in the same way as for
+  /// [srcOver].
+  ///
+  /// The effect is similar to [exclusion] but harsher.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_difference.png)
+  difference,
+
+  /// Subtract double the product of the two images from the sum of the two
+  /// images.
+  ///
+  /// Compositing black has no effect; compositing white inverts the colors of
+  /// the other image.
+  ///
+  /// The opacity of the output image is computed in the same way as for
+  /// [srcOver].
+  ///
+  /// The effect is similar to [difference] but softer.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_exclusion.png)
+  exclusion,
+
+  /// Multiply the components of the source and destination images, including
+  /// the alpha channel.
+  ///
+  /// This can only result in the same or darker colors (multiplying by white,
+  /// 1.0, results in no change; multiplying by black, 0.0, results in black).
+  ///
+  /// Since the alpha channel is also multiplied, a fully-transparent pixel
+  /// (opacity 0.0) in one image results in a fully transparent pixel in the
+  /// output. This is similar to [dstIn], but with the colors combined.
+  ///
+  /// For a variant that multiplies the colors but does not multiply the alpha
+  /// channel, consider [modulate].
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_multiply.png)
+  multiply,  // The last separable mode.
+
+  /// Take the hue of the source image, and the saturation and luminosity of the
+  /// destination image.
+  ///
+  /// The effect is to tint the destination image with the source image.
+  ///
+  /// The opacity of the output image is computed in the same way as for
+  /// [srcOver]. Regions that are entirely transparent in the source image take
+  /// their hue from the destination.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_hue.png)
+  ///
+  /// See also:
+  ///
+  ///  * [color], which is a similar but stronger effect as it also applies the
+  ///    saturation of the source image.
+  ///  * [HSVColor], which allows colors to be expressed using Hue rather than
+  ///    the red/green/blue channels of [Color].
+  hue,
+
+  /// Take the saturation of the source image, and the hue and luminosity of the
+  /// destination image.
+  ///
+  /// The opacity of the output image is computed in the same way as for
+  /// [srcOver]. Regions that are entirely transparent in the source image take
+  /// their saturation from the destination.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_hue.png)
+  ///
+  /// See also:
+  ///
+  ///  * [color], which also applies the hue of the source image.
+  ///  * [luminosity], which applies the luminosity of the source image to the
+  ///    destination.
+  saturation,
+
+  /// Take the hue and saturation of the source image, and the luminosity of the
+  /// destination image.
+  ///
+  /// The effect is to tint the destination image with the source image.
+  ///
+  /// The opacity of the output image is computed in the same way as for
+  /// [srcOver]. Regions that are entirely transparent in the source image take
+  /// their hue and saturation from the destination.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_color.png)
+  ///
+  /// See also:
+  ///
+  ///  * [hue], which is a similar but weaker effect.
+  ///  * [softLight], which is a similar tinting effect but also tints white.
+  ///  * [saturation], which only applies the saturation of the source image.
+  color,
+
+  /// Take the luminosity of the source image, and the hue and saturation of the
+  /// destination image.
+  ///
+  /// The opacity of the output image is computed in the same way as for
+  /// [srcOver]. Regions that are entirely transparent in the source image take
+  /// their luminosity from the destination.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/blend_mode_luminosity.png)
+  ///
+  /// See also:
+  ///
+  ///  * [saturation], which applies the saturation of the source image to the
+  ///    destination.
+  ///  * [ImageFilter.blur], which can be used with [BackdropFilter] for a
+  ///    related effect.
+  luminosity,
+}
+
+/// Quality levels for image filters.
+///
+/// See [Paint.filterQuality].
+enum FilterQuality {
+  // This list comes from Skia's SkFilterQuality.h and the values (order) should
+  // be kept in sync.
+
+  /// Fastest possible filtering, albeit also the lowest quality.
+  ///
+  /// Typically this implies nearest-neighbor filtering.
+  none,
+
+  /// Better quality than [none], faster than [medium].
+  ///
+  /// Typically this implies bilinear interpolation.
+  low,
+
+  /// Better quality than [low], faster than [high].
+  ///
+  /// Typically this implies a combination of bilinear interpolation and
+  /// pyramidal parametric pre-filtering (mipmaps).
+  medium,
+
+  /// Best possible quality filtering, albeit also the slowest.
+  ///
+  /// Typically this implies bicubic interpolation or better.
+  high,
+}
+
+/// Styles to use for line endings.
+///
+/// See also:
+///
+///  * [Paint.strokeCap] for how this value is used.
+///  * [StrokeJoin] for the different kinds of line segment joins.
+// These enum values must be kept in sync with SkPaint::Cap.
+enum StrokeCap {
+  /// Begin and end contours with a flat edge and no extension.
+  ///
+  /// ![A butt cap ends line segments with a square end that stops at the end of
+  /// the line segment.](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/butt_cap.png)
+  ///
+  /// Compare to the [square] cap, which has the same shape, but extends past
+  /// the end of the line by half a stroke width.
+  butt,
+
+  /// Begin and end contours with a semi-circle extension.
+  ///
+  /// ![A round cap adds a rounded end to the line segment that protrudes
+  /// by one half of the thickness of the line (which is the radius of the cap)
+  /// past the end of the segment.](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/round_cap.png)
+  ///
+  /// The cap is colored in the diagram above to highlight it: in normal use it
+  /// is the same color as the line.
+  round,
+
+  /// Begin and end contours with a half square extension. This is
+  /// similar to extending each contour by half the stroke width (as
+  /// given by [Paint.strokeWidth]).
+  ///
+  /// ![A square cap has a square end that effectively extends the line length
+  /// by half of the stroke width.](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/square_cap.png)
+  ///
+  /// The cap is colored in the diagram above to highlight it: in normal use it
+  /// is the same color as the line.
+  ///
+  /// Compare to the [butt] cap, which has the same shape, but doesn't extend
+  /// past the end of the line.
+  square,
+}
+
+/// Styles to use for line segment joins.
+///
+/// This only affects line joins for polygons drawn by [Canvas.drawPath] and
+/// rectangles, not points drawn as lines with [Canvas.drawPoints].
+///
+/// See also:
+///
+/// * [Paint.strokeJoin] and [Paint.strokeMiterLimit] for how this value is
+///   used.
+/// * [StrokeCap] for the different kinds of line endings.
+// These enum values must be kept in sync with SkPaint::Join.
+enum StrokeJoin {
+  /// Joins between line segments form sharp corners.
+  ///
+  /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/miter_4_join.mp4}
+  ///
+  /// The center of the line segment is colored in the diagram above to
+  /// highlight the join, but in normal usage the join is the same color as the
+  /// line.
+  ///
+  /// See also:
+  ///
+  ///   * [Paint.strokeJoin], used to set the line segment join style to this
+  ///     value.
+  ///   * [Paint.strokeMiterLimit], used to define when a miter is drawn instead
+  ///     of a bevel when the join is set to this value.
+  miter,
+
+  /// Joins between line segments are semi-circular.
+  ///
+  /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/round_join.mp4}
+  ///
+  /// The center of the line segment is colored in the diagram above to
+  /// highlight the join, but in normal usage the join is the same color as the
+  /// line.
+  ///
+  /// See also:
+  ///
+  ///   * [Paint.strokeJoin], used to set the line segment join style to this
+  ///     value.
+  round,
+
+  /// Joins between line segments connect the corners of the butt ends of the
+  /// line segments to give a beveled appearance.
+  ///
+  /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/bevel_join.mp4}
+  ///
+  /// The center of the line segment is colored in the diagram above to
+  /// highlight the join, but in normal usage the join is the same color as the
+  /// line.
+  ///
+  /// See also:
+  ///
+  ///   * [Paint.strokeJoin], used to set the line segment join style to this
+  ///     value.
+  bevel,
+}
+
+/// Strategies for painting shapes and paths on a canvas.
+///
+/// See [Paint.style].
+// These enum values must be kept in sync with SkPaint::Style.
+enum PaintingStyle {
+  // This list comes from Skia's SkPaint.h and the values (order) should be kept
+  // in sync.
+
+  /// Apply the [Paint] to the inside of the shape. For example, when
+  /// applied to the [Canvas.drawCircle] call, this results in a disc
+  /// of the given size being painted.
+  fill,
+
+  /// Apply the [Paint] to the edge of the shape. For example, when
+  /// applied to the [Canvas.drawCircle] call, this results is a hoop
+  /// of the given size being painted. The line drawn on the edge will
+  /// be the width given by the [Paint.strokeWidth] property.
+  stroke,
+}
+
+
+/// Different ways to clip a widget's content.
+enum Clip {
+  /// No clip at all.
+  ///
+  /// This is the default option for most widgets: if the content does not
+  /// overflow the widget boundary, don't pay any performance cost for clipping.
+  ///
+  /// If the content does overflow, please explicitly specify the following
+  /// [Clip] options:
+  ///  * [hardEdge], which is the fastest clipping, but with lower fidelity.
+  ///  * [antiAlias], which is a little slower than [hardEdge], but with smoothed edges.
+  ///  * [antiAliasWithSaveLayer], which is much slower than [antiAlias], and should
+  ///    rarely be used.
+  none,
+
+  /// Clip, but do not apply anti-aliasing.
+  ///
+  /// This mode enables clipping, but curves and non-axis-aligned straight lines will be
+  /// jagged as no effort is made to anti-alias.
+  ///
+  /// Faster than other clipping modes, but slower than [none].
+  ///
+  /// This is a reasonable choice when clipping is needed, if the container is an axis-
+  /// aligned rectangle or an axis-aligned rounded rectangle with very small corner radii.
+  ///
+  /// See also:
+  ///
+  ///  * [antiAlias], which is more reasonable when clipping is needed and the shape is not
+  ///    an axis-aligned rectangle.
+  hardEdge,
+
+  /// Clip with anti-aliasing.
+  ///
+  /// This mode has anti-aliased clipping edges to achieve a smoother look.
+  ///
+  /// It' s much faster than [antiAliasWithSaveLayer], but slower than [hardEdge].
+  ///
+  /// This will be the common case when dealing with circles and arcs.
+  ///
+  /// Different from [hardEdge] and [antiAliasWithSaveLayer], this clipping may have
+  /// bleeding edge artifacts.
+  /// (See https://fiddle.skia.org/c/21cb4c2b2515996b537f36e7819288ae for an example.)
+  ///
+  /// See also:
+  ///
+  ///  * [hardEdge], which is a little faster, but with lower fidelity.
+  ///  * [antiAliasWithSaveLayer], which is much slower, but can avoid the
+  ///    bleeding edges if there's no other way.
+  ///  * [Paint.isAntiAlias], which is the anti-aliasing switch for general draw operations.
+  antiAlias,
+
+  /// Clip with anti-aliasing and saveLayer immediately following the clip.
+  ///
+  /// This mode not only clips with anti-aliasing, but also allocates an offscreen
+  /// buffer. All subsequent paints are carried out on that buffer before finally
+  /// being clipped and composited back.
+  ///
+  /// This is very slow. It has no bleeding edge artifacts (that [antiAlias] has)
+  /// but it changes the semantics as an offscreen buffer is now introduced.
+  /// (See https://github.com/flutter/flutter/issues/18057#issuecomment-394197336
+  /// for a difference between paint without saveLayer and paint with saveLayer.)
+  ///
+  /// This will be only rarely needed. One case where you might need this is if
+  /// you have an image overlaid on a very different background color. In these
+  /// cases, consider whether you can avoid overlaying multiple colors in one
+  /// spot (e.g. by having the background color only present where the image is
+  /// absent). If you can, [antiAlias] would be fine and much faster.
+  ///
+  /// See also:
+  ///
+  ///  * [antiAlias], which is much faster, and has similar clipping results.
+  antiAliasWithSaveLayer,
+}
+
+/// A description of the style to use when drawing on a [Canvas].
+///
+/// Most APIs on [Canvas] take a [Paint] object to describe the style
+/// to use for that operation.
+class Paint {
+  // Paint objects are encoded in two buffers:
+  //
+  // * _data is binary data in four-byte fields, each of which is either a
+  //   uint32_t or a float. The default value for each field is encoded as
+  //   zero to make initialization trivial. Most values already have a default
+  //   value of zero, but some, such as color, have a non-zero default value.
+  //   To encode or decode these values, XOR the value with the default value.
+  //
+  // * _objects is a list of unencodable objects, typically wrappers for native
+  //   objects. The objects are simply stored in the list without any additional
+  //   encoding.
+  //
+  // The binary format must match the deserialization code in paint.cc.
+
+  final ByteData _data = ByteData(_kDataByteCount);
+  static const int _kIsAntiAliasIndex = 0;
+  static const int _kColorIndex = 1;
+  static const int _kBlendModeIndex = 2;
+  static const int _kStyleIndex = 3;
+  static const int _kStrokeWidthIndex = 4;
+  static const int _kStrokeCapIndex = 5;
+  static const int _kStrokeJoinIndex = 6;
+  static const int _kStrokeMiterLimitIndex = 7;
+  static const int _kFilterQualityIndex = 8;
+  static const int _kMaskFilterIndex = 9;
+  static const int _kMaskFilterBlurStyleIndex = 10;
+  static const int _kMaskFilterSigmaIndex = 11;
+  static const int _kInvertColorIndex = 12;
+  static const int _kDitherIndex = 13;
+
+  static const int _kIsAntiAliasOffset = _kIsAntiAliasIndex << 2;
+  static const int _kColorOffset = _kColorIndex << 2;
+  static const int _kBlendModeOffset = _kBlendModeIndex << 2;
+  static const int _kStyleOffset = _kStyleIndex << 2;
+  static const int _kStrokeWidthOffset = _kStrokeWidthIndex << 2;
+  static const int _kStrokeCapOffset = _kStrokeCapIndex << 2;
+  static const int _kStrokeJoinOffset = _kStrokeJoinIndex << 2;
+  static const int _kStrokeMiterLimitOffset = _kStrokeMiterLimitIndex << 2;
+  static const int _kFilterQualityOffset = _kFilterQualityIndex << 2;
+  static const int _kMaskFilterOffset = _kMaskFilterIndex << 2;
+  static const int _kMaskFilterBlurStyleOffset = _kMaskFilterBlurStyleIndex << 2;
+  static const int _kMaskFilterSigmaOffset = _kMaskFilterSigmaIndex << 2;
+  static const int _kInvertColorOffset = _kInvertColorIndex << 2;
+  static const int _kDitherOffset = _kDitherIndex << 2;
+  // If you add more fields, remember to update _kDataByteCount.
+  static const int _kDataByteCount = 56;
+
+  // Binary format must match the deserialization code in paint.cc.
+  List<dynamic>? _objects;
+
+  List<dynamic> _ensureObjectsInitialized() {
+    return _objects ??= List<dynamic>.filled(_kObjectCount, null, growable: false);
+  }
+
+  static const int _kShaderIndex = 0;
+  static const int _kColorFilterIndex = 1;
+  static const int _kImageFilterIndex = 2;
+  static const int _kObjectCount = 3; // Must be one larger than the largest index.
+
+  /// Constructs an empty [Paint] object with all fields initialized to
+  /// their defaults.
+  Paint() {
+    if (enableDithering) {
+      _dither = true;
+    }
+  }
+
+  /// Whether to apply anti-aliasing to lines and images drawn on the
+  /// canvas.
+  ///
+  /// Defaults to true.
+  bool get isAntiAlias {
+    return _data.getInt32(_kIsAntiAliasOffset, _kFakeHostEndian) == 0;
+  }
+  set isAntiAlias(bool value) {
+    // We encode true as zero and false as one because the default value, which
+    // we always encode as zero, is true.
+    final int encoded = value ? 0 : 1;
+    _data.setInt32(_kIsAntiAliasOffset, encoded, _kFakeHostEndian);
+  }
+
+  // Must be kept in sync with the default in paint.cc.
+  static const int _kColorDefault = 0xFF000000;
+
+  /// The color to use when stroking or filling a shape.
+  ///
+  /// Defaults to opaque black.
+  ///
+  /// See also:
+  ///
+  ///  * [style], which controls whether to stroke or fill (or both).
+  ///  * [colorFilter], which overrides [color].
+  ///  * [shader], which overrides [color] with more elaborate effects.
+  ///
+  /// This color is not used when compositing. To colorize a layer, use
+  /// [colorFilter].
+  Color get color {
+    final int encoded = _data.getInt32(_kColorOffset, _kFakeHostEndian);
+    return Color(encoded ^ _kColorDefault);
+  }
+  set color(Color value) {
+    assert(value != null); // ignore: unnecessary_null_comparison
+    final int encoded = value.value ^ _kColorDefault;
+    _data.setInt32(_kColorOffset, encoded, _kFakeHostEndian);
+  }
+
+  // Must be kept in sync with the default in paint.cc.
+  static final int _kBlendModeDefault = BlendMode.srcOver.index;
+
+  /// A blend mode to apply when a shape is drawn or a layer is composited.
+  ///
+  /// The source colors are from the shape being drawn (e.g. from
+  /// [Canvas.drawPath]) or layer being composited (the graphics that were drawn
+  /// between the [Canvas.saveLayer] and [Canvas.restore] calls), after applying
+  /// the [colorFilter], if any.
+  ///
+  /// The destination colors are from the background onto which the shape or
+  /// layer is being composited.
+  ///
+  /// Defaults to [BlendMode.srcOver].
+  ///
+  /// See also:
+  ///
+  ///  * [Canvas.saveLayer], which uses its [Paint]'s [blendMode] to composite
+  ///    the layer when [Canvas.restore] is called.
+  ///  * [BlendMode], which discusses the user of [Canvas.saveLayer] with
+  ///    [blendMode].
+  BlendMode get blendMode {
+    final int encoded = _data.getInt32(_kBlendModeOffset, _kFakeHostEndian);
+    return BlendMode.values[encoded ^ _kBlendModeDefault];
+  }
+  set blendMode(BlendMode value) {
+    assert(value != null); // ignore: unnecessary_null_comparison
+    final int encoded = value.index ^ _kBlendModeDefault;
+    _data.setInt32(_kBlendModeOffset, encoded, _kFakeHostEndian);
+  }
+
+  /// Whether to paint inside shapes, the edges of shapes, or both.
+  ///
+  /// Defaults to [PaintingStyle.fill].
+  PaintingStyle get style {
+    return PaintingStyle.values[_data.getInt32(_kStyleOffset, _kFakeHostEndian)];
+  }
+  set style(PaintingStyle value) {
+    assert(value != null); // ignore: unnecessary_null_comparison
+    final int encoded = value.index;
+    _data.setInt32(_kStyleOffset, encoded, _kFakeHostEndian);
+  }
+
+  /// How wide to make edges drawn when [style] is set to
+  /// [PaintingStyle.stroke]. The width is given in logical pixels measured in
+  /// the direction orthogonal to the direction of the path.
+  ///
+  /// Defaults to 0.0, which correspond to a hairline width.
+  double get strokeWidth {
+    return _data.getFloat32(_kStrokeWidthOffset, _kFakeHostEndian);
+  }
+  set strokeWidth(double value) {
+    assert(value != null); // ignore: unnecessary_null_comparison
+    final double encoded = value;
+    _data.setFloat32(_kStrokeWidthOffset, encoded, _kFakeHostEndian);
+  }
+
+  /// The kind of finish to place on the end of lines drawn when
+  /// [style] is set to [PaintingStyle.stroke].
+  ///
+  /// Defaults to [StrokeCap.butt], i.e. no caps.
+  StrokeCap get strokeCap {
+    return StrokeCap.values[_data.getInt32(_kStrokeCapOffset, _kFakeHostEndian)];
+  }
+  set strokeCap(StrokeCap value) {
+    assert(value != null); // ignore: unnecessary_null_comparison
+    final int encoded = value.index;
+    _data.setInt32(_kStrokeCapOffset, encoded, _kFakeHostEndian);
+  }
+
+  /// The kind of finish to place on the joins between segments.
+  ///
+  /// This applies to paths drawn when [style] is set to [PaintingStyle.stroke],
+  /// It does not apply to points drawn as lines with [Canvas.drawPoints].
+  ///
+  /// Defaults to [StrokeJoin.miter], i.e. sharp corners.
+  ///
+  /// Some examples of joins:
+  ///
+  /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/miter_4_join.mp4}
+  ///
+  /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/round_join.mp4}
+  ///
+  /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/bevel_join.mp4}
+  ///
+  /// The centers of the line segments are colored in the diagrams above to
+  /// highlight the joins, but in normal usage the join is the same color as the
+  /// line.
+  ///
+  /// See also:
+  ///
+  ///  * [strokeMiterLimit] to control when miters are replaced by bevels when
+  ///    this is set to [StrokeJoin.miter].
+  ///  * [strokeCap] to control what is drawn at the ends of the stroke.
+  ///  * [StrokeJoin] for the definitive list of stroke joins.
+  StrokeJoin get strokeJoin {
+    return StrokeJoin.values[_data.getInt32(_kStrokeJoinOffset, _kFakeHostEndian)];
+  }
+  set strokeJoin(StrokeJoin value) {
+    assert(value != null); // ignore: unnecessary_null_comparison
+    final int encoded = value.index;
+    _data.setInt32(_kStrokeJoinOffset, encoded, _kFakeHostEndian);
+  }
+
+  // Must be kept in sync with the default in paint.cc.
+  static const double _kStrokeMiterLimitDefault = 4.0;
+
+  /// The limit for miters to be drawn on segments when the join is set to
+  /// [StrokeJoin.miter] and the [style] is set to [PaintingStyle.stroke]. If
+  /// this limit is exceeded, then a [StrokeJoin.bevel] join will be drawn
+  /// instead. This may cause some 'popping' of the corners of a path if the
+  /// angle between line segments is animated, as seen in the diagrams below.
+  ///
+  /// This limit is expressed as a limit on the length of the miter.
+  ///
+  /// Defaults to 4.0.  Using zero as a limit will cause a [StrokeJoin.bevel]
+  /// join to be used all the time.
+  ///
+  /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/miter_0_join.mp4}
+  ///
+  /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/miter_4_join.mp4}
+  ///
+  /// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/dart-ui/miter_6_join.mp4}
+  ///
+  /// The centers of the line segments are colored in the diagrams above to
+  /// highlight the joins, but in normal usage the join is the same color as the
+  /// line.
+  ///
+  /// See also:
+  ///
+  ///  * [strokeJoin] to control the kind of finish to place on the joins
+  ///    between segments.
+  ///  * [strokeCap] to control what is drawn at the ends of the stroke.
+  double get strokeMiterLimit {
+    return _data.getFloat32(_kStrokeMiterLimitOffset, _kFakeHostEndian);
+  }
+  set strokeMiterLimit(double value) {
+    assert(value != null); // ignore: unnecessary_null_comparison
+    final double encoded = value - _kStrokeMiterLimitDefault;
+    _data.setFloat32(_kStrokeMiterLimitOffset, encoded, _kFakeHostEndian);
+  }
+
+  /// A mask filter (for example, a blur) to apply to a shape after it has been
+  /// drawn but before it has been composited into the image.
+  ///
+  /// See [MaskFilter] for details.
+  MaskFilter? get maskFilter {
+    switch (_data.getInt32(_kMaskFilterOffset, _kFakeHostEndian)) {
+      case MaskFilter._TypeNone:
+        return null;
+      case MaskFilter._TypeBlur:
+        return MaskFilter.blur(
+          BlurStyle.values[_data.getInt32(_kMaskFilterBlurStyleOffset, _kFakeHostEndian)],
+          _data.getFloat32(_kMaskFilterSigmaOffset, _kFakeHostEndian),
+        );
+    }
+    return null;
+  }
+  set maskFilter(MaskFilter? value) {
+    if (value == null) {
+      _data.setInt32(_kMaskFilterOffset, MaskFilter._TypeNone, _kFakeHostEndian);
+      _data.setInt32(_kMaskFilterBlurStyleOffset, 0, _kFakeHostEndian);
+      _data.setFloat32(_kMaskFilterSigmaOffset, 0.0, _kFakeHostEndian);
+    } else {
+      // For now we only support one kind of MaskFilter, so we don't need to
+      // check what the type is if it's not null.
+      _data.setInt32(_kMaskFilterOffset, MaskFilter._TypeBlur, _kFakeHostEndian);
+      _data.setInt32(_kMaskFilterBlurStyleOffset, value._style.index, _kFakeHostEndian);
+      _data.setFloat32(_kMaskFilterSigmaOffset, value._sigma, _kFakeHostEndian);
+    }
+  }
+
+  /// Controls the performance vs quality trade-off to use when applying
+  /// filters, such as [maskFilter], or when drawing images, as with
+  /// [Canvas.drawImageRect] or [Canvas.drawImageNine].
+  ///
+  /// Defaults to [FilterQuality.none].
+  // TODO(ianh): verify that the image drawing methods actually respect this
+  FilterQuality get filterQuality {
+    return FilterQuality.values[_data.getInt32(_kFilterQualityOffset, _kFakeHostEndian)];
+  }
+  set filterQuality(FilterQuality value) {
+    assert(value != null); // ignore: unnecessary_null_comparison
+    final int encoded = value.index;
+    _data.setInt32(_kFilterQualityOffset, encoded, _kFakeHostEndian);
+  }
+
+  /// The shader to use when stroking or filling a shape.
+  ///
+  /// When this is null, the [color] is used instead.
+  ///
+  /// See also:
+  ///
+  ///  * [Gradient], a shader that paints a color gradient.
+  ///  * [ImageShader], a shader that tiles an [Image].
+  ///  * [colorFilter], which overrides [shader].
+  ///  * [color], which is used if [shader] and [colorFilter] are null.
+  Shader? get shader {
+    return _objects?[_kShaderIndex] as Shader?;
+  }
+  set shader(Shader? value) {
+    _ensureObjectsInitialized()[_kShaderIndex] = value;
+  }
+
+  /// A color filter to apply when a shape is drawn or when a layer is
+  /// composited.
+  ///
+  /// See [ColorFilter] for details.
+  ///
+  /// When a shape is being drawn, [colorFilter] overrides [color] and [shader].
+  ColorFilter? get colorFilter {
+    return _objects?[_kColorFilterIndex]?.creator as ColorFilter?;
+  }
+
+  set colorFilter(ColorFilter? value) {
+    final _ColorFilter? nativeFilter = value?._toNativeColorFilter();
+    if (nativeFilter == null) {
+      if (_objects != null) {
+        _objects![_kColorFilterIndex] = null;
+      }
+    } else {
+      _ensureObjectsInitialized()[_kColorFilterIndex] = nativeFilter;
+    }
+  }
+
+  /// The [ImageFilter] to use when drawing raster images.
+  ///
+  /// For example, to blur an image using [Canvas.drawImage], apply an
+  /// [ImageFilter.blur]:
+  ///
+  /// ```dart
+  /// import 'dart:ui' as ui;
+  ///
+  /// ui.Image image;
+  ///
+  /// void paint(Canvas canvas, Size size) {
+  ///   canvas.drawImage(
+  ///     image,
+  ///     Offset.zero,
+  ///     Paint()..imageFilter = ui.ImageFilter.blur(sigmaX: .5, sigmaY: .5),
+  ///   );
+  /// }
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * [MaskFilter], which is used for drawing geometry.
+  ImageFilter? get imageFilter {
+    return _objects?[_kImageFilterIndex]?.creator as ImageFilter?;
+  }
+
+  set imageFilter(ImageFilter? value) {
+    if (value == null) {
+      if (_objects != null) {
+        _objects![_kImageFilterIndex] = null;
+      }
+    } else {
+      final List<dynamic> objects = _ensureObjectsInitialized();
+      if (objects[_kImageFilterIndex]?.creator != value) {
+        objects[_kImageFilterIndex] = value._toNativeImageFilter();
+      }
+    }
+  }
+
+  /// Whether the colors of the image are inverted when drawn.
+  ///
+  /// Inverting the colors of an image applies a new color filter that will
+  /// be composed with any user provided color filters. This is primarily
+  /// used for implementing smart invert on iOS.
+  bool get invertColors {
+    return _data.getInt32(_kInvertColorOffset, _kFakeHostEndian) == 1;
+  }
+  set invertColors(bool value) {
+    _data.setInt32(_kInvertColorOffset, value ? 1 : 0, _kFakeHostEndian);
+  }
+
+  bool get _dither {
+    return _data.getInt32(_kDitherOffset, _kFakeHostEndian) == 1;
+  }
+  set _dither(bool value) {
+    _data.setInt32(_kDitherOffset, value ? 1 : 0, _kFakeHostEndian);
+  }
+
+  /// Whether to dither the output when drawing images.
+  ///
+  /// If false, the default value, dithering will be enabled when the input
+  /// color depth is higher than the output color depth. For example,
+  /// drawing an RGB8 image onto an RGB565 canvas.
+  ///
+  /// This value also controls dithering of [shader]s, which can make
+  /// gradients appear smoother.
+  ///
+  /// Whether or not dithering affects the output is implementation defined.
+  /// Some implementations may choose to ignore this completely, if they're
+  /// unable to control dithering.
+  ///
+  /// To ensure that dithering is consistently enabled for your entire
+  /// application, set this to true before invoking any drawing related code.
+  static bool enableDithering = false;
+
+  @override
+  String toString() {
+    if (const bool.fromEnvironment('dart.vm.product', defaultValue: false)) {
+      return super.toString();
+    }
+    final StringBuffer result = StringBuffer();
+    String semicolon = '';
+    result.write('Paint(');
+    if (style == PaintingStyle.stroke) {
+      result.write('$style');
+      if (strokeWidth != 0.0)
+        result.write(' ${strokeWidth.toStringAsFixed(1)}');
+      else
+        result.write(' hairline');
+      if (strokeCap != StrokeCap.butt)
+        result.write(' $strokeCap');
+      if (strokeJoin == StrokeJoin.miter) {
+        if (strokeMiterLimit != _kStrokeMiterLimitDefault)
+          result.write(' $strokeJoin up to ${strokeMiterLimit.toStringAsFixed(1)}');
+      } else {
+        result.write(' $strokeJoin');
+      }
+      semicolon = '; ';
+    }
+    if (isAntiAlias != true) {
+      result.write('${semicolon}antialias off');
+      semicolon = '; ';
+    }
+    if (color != const Color(_kColorDefault)) {
+      result.write('$semicolon$color');
+      semicolon = '; ';
+    }
+    if (blendMode.index != _kBlendModeDefault) {
+      result.write('$semicolon$blendMode');
+      semicolon = '; ';
+    }
+    if (colorFilter != null) {
+      result.write('${semicolon}colorFilter: $colorFilter');
+      semicolon = '; ';
+    }
+    if (maskFilter != null) {
+      result.write('${semicolon}maskFilter: $maskFilter');
+      semicolon = '; ';
+    }
+    if (filterQuality != FilterQuality.none) {
+      result.write('${semicolon}filterQuality: $filterQuality');
+      semicolon = '; ';
+    }
+    if (shader != null) {
+      result.write('${semicolon}shader: $shader');
+      semicolon = '; ';
+    }
+    if (imageFilter != null) {
+      result.write('${semicolon}imageFilter: $imageFilter');
+      semicolon = '; ';
+    }
+    if (invertColors)
+      result.write('${semicolon}invert: $invertColors');
+    if (_dither)
+      result.write('${semicolon}dither: $_dither');
+    result.write(')');
+    return result.toString();
+  }
+}
+
+/// The format in which image bytes should be returned when using
+/// [Image.toByteData].
+enum ImageByteFormat {
+  /// Raw RGBA format.
+  ///
+  /// Unencoded bytes, in RGBA row-primary form, 8 bits per channel.
+  rawRgba,
+
+  /// Raw unmodified format.
+  ///
+  /// Unencoded bytes, in the image's existing format. For example, a grayscale
+  /// image may use a single 8-bit channel for each pixel.
+  rawUnmodified,
+
+  /// PNG format.
+  ///
+  /// A loss-less compression format for images. This format is well suited for
+  /// images with hard edges, such as screenshots or sprites, and images with
+  /// text. Transparency is supported. The PNG format supports images up to
+  /// 2,147,483,647 pixels in either dimension, though in practice available
+  /// memory provides a more immediate limitation on maximum image size.
+  ///
+  /// PNG images normally use the `.png` file extension and the `image/png` MIME
+  /// type.
+  ///
+  /// See also:
+  ///
+  ///  * <https://en.wikipedia.org/wiki/Portable_Network_Graphics>, the Wikipedia page on PNG.
+  ///  * <https://tools.ietf.org/rfc/rfc2083.txt>, the PNG standard.
+  png,
+}
+
+/// The format of pixel data given to [decodeImageFromPixels].
+enum PixelFormat {
+  /// Each pixel is 32 bits, with the highest 8 bits encoding red, the next 8
+  /// bits encoding green, the next 8 bits encoding blue, and the lowest 8 bits
+  /// encoding alpha.
+  rgba8888,
+
+  /// Each pixel is 32 bits, with the highest 8 bits encoding blue, the next 8
+  /// bits encoding green, the next 8 bits encoding red, and the lowest 8 bits
+  /// encoding alpha.
+  bgra8888,
+}
+
+/// Opaque handle to raw decoded image data (pixels).
+///
+/// To obtain an [Image] object, use the [ImageDescriptor] API.
+///
+/// To draw an [Image], use one of the methods on the [Canvas] class, such as
+/// [Canvas.drawImage].
+///
+/// A class or method that receives an image object must call [dispose] on the
+/// handle when it is no longer needed. To create a shareable reference to the
+/// underlying image, call [clone]. The method or object that recieves
+/// the new instance will then be responsible for disposing it, and the
+/// underlying image itself will be disposed when all outstanding handles are
+/// disposed.
+///
+/// If `dart:ui` passes an `Image` object and the recipient wishes to share
+/// that handle with other callers, [clone] must be called _before_ [dispose].
+/// A handle that has been disposed cannot create new handles anymore.
+///
+/// See also:
+///
+///  * [Image](https://api.flutter.dev/flutter/widgets/Image-class.html), the class in the [widgets] library.
+///  * [ImageDescriptor], which allows reading information about the image and
+///    creating a codec to decode it.
+///  * [instantiateImageCodec], a utility method that wraps [ImageDescriptor].
+class Image {
+  Image._(this._image) {
+    assert(() {
+      _debugStack = StackTrace.current;
+      return true;
+    }());
+    _image._handles.add(this);
+  }
+
+  // C++ unit tests access this.
+  @pragma('vm:entry-point')
+  final _Image _image;
+
+  StackTrace? _debugStack;
+
+  /// The number of image pixels along the image's horizontal axis.
+  int get width {
+    assert(!_disposed && !_image._disposed);
+    return _image.width;
+  }
+
+  /// The number of image pixels along the image's vertical axis.
+  int get height {
+    assert(!_disposed && !_image._disposed);
+    return _image.height;
+  }
+
+  bool _disposed = false;
+  /// Release this handle's claim on the underlying Image. This handle is no
+  /// longer usable after this method is called.
+  ///
+  /// Once all outstanding handles have been disposed, the underlying image will
+  /// be disposed as well.
+  ///
+  /// In debug mode, [debugGetOpenHandleStackTraces] will return a list of
+  /// [StackTrace] objects from all open handles' creation points. This is
+  /// useful when trying to determine what parts of the program are keeping an
+  /// image resident in memory.
+  void dispose() {
+    assert(!_disposed && !_image._disposed);
+    assert(_image._handles.contains(this));
+    _disposed = true;
+    final bool removed = _image._handles.remove(this);
+    assert(removed);
+    if (_image._handles.isEmpty) {
+      _image.dispose();
+    }
+  }
+
+  /// Whether this reference to the underlying image is [dispose]d.
+  ///
+  /// This only returns a valid value if asserts are enabled, and must not be
+  /// used otherwise.
+  bool get debugDisposed {
+    bool? disposed;
+    assert(() {
+      disposed = _disposed;
+      return true;
+    }());
+    return disposed ?? (throw StateError('Image.debugDisposed is only available when asserts are enabled.'));
+  }
+
+  /// Converts the [Image] object into a byte array.
+  ///
+  /// The [format] argument specifies the format in which the bytes will be
+  /// returned.
+  ///
+  /// Returns a future that completes with the binary image data or an error
+  /// if encoding fails.
+  Future<ByteData?> toByteData({ImageByteFormat format = ImageByteFormat.rawRgba}) {
+    assert(!_disposed && !_image._disposed);
+    return _image.toByteData(format: format);
+  }
+
+  /// If asserts are enabled, returns the [StackTrace]s of each open handle from
+  /// [clone], in creation order.
+  ///
+  /// If asserts are disabled, this method always returns null.
+  List<StackTrace>? debugGetOpenHandleStackTraces() {
+    List<StackTrace>? stacks;
+    assert(() {
+      stacks = _image._handles.map((Image handle) => handle._debugStack!).toList();
+      return true;
+    }());
+    return stacks;
+  }
+
+  /// Creates a disposable handle to this image.
+  ///
+  /// Holders of an [Image] must dispose of the image when they no longer need
+  /// to access it or draw it. However, once the underlying image is disposed,
+  /// it is no longer possible to use it. If a holder of an image needs to share
+  /// access to that image with another object or method, [clone] creates a
+  /// duplicate handle. The underlying image will only be disposed once all
+  /// outstanding handles are disposed. This allows for safe sharing of image
+  /// references while still disposing of the underlying resources when all
+  /// consumers are finished.
+  ///
+  /// It is safe to pass an [Image] handle to another object or method if the
+  /// current holder no longer needs it.
+  ///
+  /// To check whether two [Image] references are refering to the same
+  /// underlying image memory, use [isCloneOf] rather than the equality operator
+  /// or [identical].
+  ///
+  /// The following example demonstrates valid usage.
+  ///
+  /// ```dart
+  /// import 'dart:async';
+  ///
+  /// Future<Image> _loadImage(int width, int height) {
+  ///   final Completer<Image> completer = Completer<Image>();
+  ///   decodeImageFromPixels(
+  ///     Uint8List.fromList(List<int>.filled(width * height * 4, 0xFF)),
+  ///     width,
+  ///     height,
+  ///     PixelFormat.rgba8888,
+  ///     // Don't worry about disposing or cloning this image - responsibility
+  ///     // is transferred to the caller, and that is safe since this method
+  ///     // will not touch it again.
+  ///     (Image image) => completer.complete(image),
+  ///   );
+  ///   return completer.future;
+  /// }
+  ///
+  /// Future<void> main() async {
+  ///   final Image image = await _loadImage(5, 5);
+  ///   // Make sure to clone the image, because MyHolder might dispose it
+  ///   // and we need to access it again.
+  ///   final MyImageHolder holder = MyImageHolder(image.clone());
+  ///   final MyImageHolder holder2 = MyImageHolder(image.clone());
+  ///   // Now we dispose it because we won't need it again.
+  ///   image.dispose();
+  ///
+  ///   final PictureRecorder recorder = PictureRecorder();
+  ///   final Canvas canvas = Canvas(recorder);
+  ///
+  ///   holder.draw(canvas);
+  ///   holder.dispose();
+  ///
+  ///   canvas.translate(50, 50);
+  ///   holder2.draw(canvas);
+  ///   holder2.dispose();
+  /// }
+  ///
+  /// class MyImageHolder {
+  ///   MyImageLoader(this.image);
+  ///
+  ///   final Image image;
+  ///
+  ///   void draw(Canvas canvas) {
+  ///     canvas.drawImage(image, Offset.zero, Paint());
+  ///   }
+  ///
+  ///   void dispose() => image.dispose();
+  /// }
+  /// ```
+  ///
+  /// The returned object behaves identically to this image. Calling
+  /// [dispose] on it will only dispose the underlying native resources if it
+  /// is the last remaining handle.
+  Image clone() {
+    if (_disposed) {
+      throw StateError(
+        'Cannot clone a disposed image.\n'
+        'The clone() method of a previously-disposed Image was called. Once an '
+        'Image object has been disposed, it can no longer be used to create '
+        'handles, as the underlying data may have been released.'
+      );
+    }
+    assert(!_image._disposed);
+    return Image._(_image);
+  }
+
+  /// Returns true if `other` is a [clone] of this and thus shares the same
+  /// underlying image memory, even if this or `other` is [dispose]d.
+  ///
+  /// This method may return false for two images that were decoded from the
+  /// same underlying asset, if they are not sharing the same memory. For
+  /// example, if the same file is decoded using [instantiateImageCodec] twice,
+  /// or the same bytes are decoded using [decodeImageFromPixels] twice, there
+  /// will be two distinct [Image]s that render the same but do not share
+  /// underlying memory, and so will not be treated as clones of each other.
+  bool isCloneOf(Image other) => other._image == _image;
+
+  @override
+  String toString() => _image.toString();
+}
+
+@pragma('vm:entry-point')
+class _Image extends NativeFieldWrapperClass2 {
+  // This class is created by the engine, and should not be instantiated
+  // or extended directly.
+  //
+  // _Images are always handed out wrapped in [Image]s. To create an [Image],
+  // use the ImageDescriptor API.
+  @pragma('vm:entry-point')
+  _Image._();
+
+  int get width { throw UnimplementedError(); }
+
+  int get height { throw UnimplementedError(); }
+
+  Future<ByteData?> toByteData({ImageByteFormat format = ImageByteFormat.rawRgba}) {
+    return _futurize((_Callback<ByteData> callback) {
+      return _toByteData(format.index, (Uint8List? encoded) {
+        callback(encoded!.buffer.asByteData());
+      });
+    });
+  }
+
+  /// Returns an error message on failure, null on success.
+  String? _toByteData(int format, _Callback<Uint8List?> callback) { throw UnimplementedError(); }
+
+  bool _disposed = false;
+  void dispose() {
+    assert(!_disposed);
+    assert(
+      _handles.isEmpty,
+      'Attempted to dispose of an Image object that has ${_handles.length} '
+      'open handles.\n'
+      'If you see this, it is a bug in dart:ui. Please file an issue at '
+      'https://github.com/flutter/flutter/issues/new.',
+    );
+    _disposed = true;
+    _dispose();
+  }
+
+  void _dispose() { throw UnimplementedError(); }
+
+  Set<Image> _handles = <Image>{};
+
+  @override
+  String toString() => '[$width\u00D7$height]';
+}
+
+/// Callback signature for [decodeImageFromList].
+typedef ImageDecoderCallback = void Function(Image result);
+
+/// Information for a single frame of an animation.
+///
+/// To obtain an instance of the [FrameInfo] interface, see
+/// [Codec.getNextFrame].
+///
+/// The recipient of an instance of this class is responsible for calling
+/// [Image.dispose] on [image]. To share the image with other interested
+/// parties, use [Image.clone]. If the [FrameInfo] object itself is passed to
+/// another method or object, that method or object must assume it is
+/// responsible for disposing the image when done, and the passer must not
+/// access the [image] after that point.
+///
+/// For example, the following code sample is incorrect:
+///
+/// ```dart
+/// /// BAD
+/// Future<void> nextFrameRoutine(Codec codec) async {
+///   final FrameInfo frameInfo = await codec.getNextFrame();
+///   _cacheImage(frameInfo);
+///   // ERROR - _cacheImage is now responsible for disposing the image, and
+///   // the image may not be available any more for this drawing routine.
+///   _drawImage(frameInfo);
+///   // ERROR again - the previous methods might or might not have created
+///   // handles to the image.
+///   frameInfo.image.dispose();
+/// }
+/// ```
+///
+/// Correct usage is:
+///
+/// ```dart
+/// /// GOOD
+/// Future<void> nextFrameRoutine(Codec codec) async {
+///   final FrameInfo frameInfo = await codec.getNextFrame();
+///   _cacheImage(frameInfo.image.clone(), frameInfo.duration);
+///   _drawImage(frameInfo.image.clone(), frameInfo.duration);
+///   // This method is done with its handle, and has passed handles to its
+///   // clients already.
+///   // The image will live until those clients dispose of their handles, and
+///   // this one must not be disposed since it will not be used again.
+///   frameInfo.image.dispose();
+/// }
+/// ```
+class FrameInfo {
+  /// This class is created by the engine, and should not be instantiated
+  /// or extended directly.
+  ///
+  /// To obtain an instance of the [FrameInfo] interface, see
+  /// [Codec.getNextFrame].
+  FrameInfo._({required this.duration, required this.image});
+
+  /// The duration this frame should be shown.
+  ///
+  /// A zero duration indicates that the frame should be shown indefinitely.
+  final Duration duration;
+
+
+  /// The [Image] object for this frame.
+  ///
+  /// This object must be disposed by the recipient of this frame info.
+  ///
+  /// To share this image with other interested parties, use [Image.clone].
+  final Image image;
+}
+
+/// A handle to an image codec.
+///
+/// This class is created by the engine, and should not be instantiated
+/// or extended directly.
+///
+/// To obtain an instance of the [Codec] interface, see
+/// [instantiateImageCodec].
+@pragma('vm:entry-point')
+class Codec extends NativeFieldWrapperClass2 {
+  //
+  // This class is created by the engine, and should not be instantiated
+  // or extended directly.
+  //
+  // To obtain an instance of the [Codec] interface, see
+  // [instantiateImageCodec].
+  @pragma('vm:entry-point')
+  Codec._();
+
+  int? _cachedFrameCount;
+  /// Number of frames in this image.
+  int get frameCount => _cachedFrameCount ??= _frameCount;
+  int get _frameCount { throw UnimplementedError(); }
+
+  int? _cachedRepetitionCount;
+  /// Number of times to repeat the animation.
+  ///
+  /// * 0 when the animation should be played once.
+  /// * -1 for infinity repetitions.
+  int get repetitionCount => _cachedRepetitionCount ??= _repetitionCount;
+  int get _repetitionCount { throw UnimplementedError(); }
+
+  /// Fetches the next animation frame.
+  ///
+  /// Wraps back to the first frame after returning the last frame.
+  ///
+  /// The returned future can complete with an error if the decoding has failed.
+  ///
+  /// The caller of this method is responsible for disposing the
+  /// [FrameInfo.image] on the returned object.
+  Future<FrameInfo> getNextFrame() async {
+    final Completer<FrameInfo> completer = Completer<FrameInfo>.sync();
+    final String? error = _getNextFrame((_Image? image, int durationMilliseconds) {
+      if (image == null) {
+        throw Exception('Codec failed to produce an image, possibly due to invalid image data.');
+      }
+      completer.complete(FrameInfo._(
+        image: Image._(image),
+        duration: Duration(milliseconds: durationMilliseconds),
+      ));
+    });
+    if (error != null) {
+      throw Exception(error);
+    }
+    return await completer.future;
+  }
+
+  /// Returns an error message on failure, null on success.
+  String? _getNextFrame(void Function(_Image?, int) callback) { throw UnimplementedError(); }
+
+  /// Release the resources used by this object. The object is no longer usable
+  /// after this method is called.
+  void dispose() { throw UnimplementedError(); }
+}
+
+/// Instantiates an image [Codec].
+///
+/// This method is a convenience wrapper around the [ImageDescriptor] API, and
+/// using [ImageDescriptor] directly is preferred since it allows the caller to
+/// make better determinations about how and whether to use the `targetWidth`
+/// and `targetHeight` parameters.
+///
+/// The `list` parameter is the binary image data (e.g a PNG or GIF binary data).
+/// The data can be for either static or animated images. The following image
+/// formats are supported: {@macro dart.ui.imageFormats}
+///
+/// The `targetWidth` and `targetHeight` arguments specify the size of the
+/// output image, in image pixels. If they are not equal to the intrinsic
+/// dimensions of the image, then the image will be scaled after being decoded.
+/// If the `allowUpscaling` parameter is not set to true, both dimensions will
+/// be capped at the intrinsic dimensions of the image, even if only one of
+/// them would have exceeded those intrinsic dimensions. If exactly one of these
+/// two arguments is specified, then the aspect ratio will be maintained while
+/// forcing the image to match the other given dimension. If neither is
+/// specified, then the image maintains its intrinsic size.
+///
+/// Scaling the image to larger than its intrinsic size should usually be
+/// avoided, since it causes the image to use more memory than necessary.
+/// Instead, prefer scaling the [Canvas] transform. If the image must be scaled
+/// up, the `allowUpscaling` parameter must be set to true.
+///
+/// The returned future can complete with an error if the image decoding has
+/// failed.
+Future<Codec> instantiateImageCodec(
+  Uint8List list, {
+  int? targetWidth,
+  int? targetHeight,
+  bool allowUpscaling = true,
+}) async {
+  final ImmutableBuffer buffer = await ImmutableBuffer.fromUint8List(list);
+  final ImageDescriptor descriptor = await ImageDescriptor.encoded(buffer);
+  if (!allowUpscaling) {
+    if (targetWidth != null && targetWidth > descriptor.width) {
+      targetWidth = descriptor.width;
+    }
+    if (targetHeight != null && targetHeight > descriptor.height) {
+      targetHeight = descriptor.height;
+    }
+  }
+  return descriptor.instantiateCodec(
+    targetWidth: targetWidth,
+    targetHeight: targetHeight,
+  );
+}
+
+/// Loads a single image frame from a byte array into an [Image] object.
+///
+/// This is a convenience wrapper around [instantiateImageCodec]. Prefer using
+/// [instantiateImageCodec] which also supports multi frame images and offers
+/// better error handling. This function swallows asynchronous errors.
+void decodeImageFromList(Uint8List list, ImageDecoderCallback callback) {
+  _decodeImageFromListAsync(list, callback);
+}
+
+Future<void> _decodeImageFromListAsync(Uint8List list,
+                                       ImageDecoderCallback callback) async {
+  final Codec codec = await instantiateImageCodec(list);
+  final FrameInfo frameInfo = await codec.getNextFrame();
+  callback(frameInfo.image);
+}
+
+/// Convert an array of pixel values into an [Image] object.
+///
+/// The `pixels` parameter is the pixel data in the encoding described by
+/// `format`.
+///
+/// The `rowBytes` parameter is the number of bytes consumed by each row of
+/// pixels in the data buffer. If unspecified, it defaults to `width` multiplied
+/// by the number of bytes per pixel in the provided `format`.
+///
+/// The `targetWidth` and `targetHeight` arguments specify the size of the
+/// output image, in image pixels. If they are not equal to the intrinsic
+/// dimensions of the image, then the image will be scaled after being decoded.
+/// If the `allowUpscaling` parameter is not set to true, both dimensions will
+/// be capped at the intrinsic dimensions of the image, even if only one of
+/// them would have exceeded those intrinsic dimensions. If exactly one of these
+/// two arguments is specified, then the aspect ratio will be maintained while
+/// forcing the image to match the other given dimension. If neither is
+/// specified, then the image maintains its intrinsic size.
+///
+/// Scaling the image to larger than its intrinsic size should usually be
+/// avoided, since it causes the image to use more memory than necessary.
+/// Instead, prefer scaling the [Canvas] transform. If the image must be scaled
+/// up, the `allowUpscaling` parameter must be set to true.
+void decodeImageFromPixels(
+  Uint8List pixels,
+  int width,
+  int height,
+  PixelFormat format,
+  ImageDecoderCallback callback, {
+  int? rowBytes,
+  int? targetWidth,
+  int? targetHeight,
+  bool allowUpscaling = true,
+}) {
+  if (targetWidth != null) {
+    assert(allowUpscaling || targetWidth <= width);
+  }
+  if (targetHeight != null) {
+    assert(allowUpscaling || targetHeight <= height);
+  }
+
+  ImmutableBuffer.fromUint8List(pixels)
+    .then((ImmutableBuffer buffer) {
+      final ImageDescriptor descriptor = ImageDescriptor.raw(
+        buffer,
+        width: width,
+        height: height,
+        rowBytes: rowBytes,
+        pixelFormat: format,
+      );
+
+      if (!allowUpscaling) {
+        if (targetWidth != null && targetWidth! > descriptor.width) {
+          targetWidth = descriptor.width;
+        }
+        if (targetHeight != null && targetHeight! > descriptor.height) {
+          targetHeight = descriptor.height;
+        }
+      }
+
+      descriptor
+        .instantiateCodec(
+          targetWidth: targetWidth,
+          targetHeight: targetHeight,
+        )
+        .then((Codec codec) => codec.getNextFrame())
+        .then((FrameInfo frameInfo) => callback(frameInfo.image));
+  });
+}
+
+/// Determines the winding rule that decides how the interior of a [Path] is
+/// calculated.
+///
+/// This enum is used by the [Path.fillType] property.
+enum PathFillType {
+  /// The interior is defined by a non-zero sum of signed edge crossings.
+  ///
+  /// For a given point, the point is considered to be on the inside of the path
+  /// if a line drawn from the point to infinity crosses lines going clockwise
+  /// around the point a different number of times than it crosses lines going
+  /// counter-clockwise around that point.
+  ///
+  /// See: <https://en.wikipedia.org/wiki/Nonzero-rule>
+  nonZero,
+
+  /// The interior is defined by an odd number of edge crossings.
+  ///
+  /// For a given point, the point is considered to be on the inside of the path
+  /// if a line drawn from the point to infinity crosses an odd number of lines.
+  ///
+  /// See: <https://en.wikipedia.org/wiki/Even-odd_rule>
+  evenOdd,
+}
+
+/// Strategies for combining paths.
+///
+/// See also:
+///
+/// * [Path.combine], which uses this enum to decide how to combine two paths.
+// Must be kept in sync with SkPathOp
+enum PathOperation {
+  /// Subtract the second path from the first path.
+  ///
+  /// For example, if the two paths are overlapping circles of equal diameter
+  /// but differing centers, the result would be a crescent portion of the
+  /// first circle that was not overlapped by the second circle.
+  ///
+  /// See also:
+  ///
+  ///  * [reverseDifference], which is the same but subtracting the first path
+  ///    from the second.
+  difference,
+  /// Create a new path that is the intersection of the two paths, leaving the
+  /// overlapping pieces of the path.
+  ///
+  /// For example, if the two paths are overlapping circles of equal diameter
+  /// but differing centers, the result would be only the overlapping portion
+  /// of the two circles.
+  ///
+  /// See also:
+  ///  * [xor], which is the inverse of this operation
+  intersect,
+  /// Create a new path that is the union (inclusive-or) of the two paths.
+  ///
+  /// For example, if the two paths are overlapping circles of equal diameter
+  /// but differing centers, the result would be a figure-eight like shape
+  /// matching the outer boundaries of both circles.
+  union,
+  /// Create a new path that is the exclusive-or of the two paths, leaving
+  /// everything but the overlapping pieces of the path.
+  ///
+  /// For example, if the two paths are overlapping circles of equal diameter
+  /// but differing centers, the figure-eight like shape less the overlapping parts
+  ///
+  /// See also:
+  ///  * [intersect], which is the inverse of this operation
+  xor,
+  /// Subtract the first path from the second path.
+  ///
+  /// For example, if the two paths are overlapping circles of equal diameter
+  /// but differing centers, the result would be a crescent portion of the
+  /// second circle that was not overlapped by the first circle.
+  ///
+  /// See also:
+  ///
+  ///  * [difference], which is the same but subtracting the second path
+  ///    from the first.
+  reverseDifference,
+}
+
+/// A handle for the framework to hold and retain an engine layer across frames.
+@pragma('vm:entry-point')
+class EngineLayer extends NativeFieldWrapperClass2 {
+  /// This class is created by the engine, and should not be instantiated
+  /// or extended directly.
+  @pragma('vm:entry-point')
+  EngineLayer._();
+}
+
+/// A complex, one-dimensional subset of a plane.
+///
+/// A path consists of a number of sub-paths, and a _current point_.
+///
+/// Sub-paths consist of segments of various types, such as lines,
+/// arcs, or beziers. Sub-paths can be open or closed, and can
+/// self-intersect.
+///
+/// Closed sub-paths enclose a (possibly discontiguous) region of the
+/// plane based on the current [fillType].
+///
+/// The _current point_ is initially at the origin. After each
+/// operation adding a segment to a sub-path, the current point is
+/// updated to the end of that segment.
+///
+/// Paths can be drawn on canvases using [Canvas.drawPath], and can
+/// used to create clip regions using [Canvas.clipPath].
+@pragma('vm:entry-point')
+class Path extends NativeFieldWrapperClass2 {
+  /// Create a new empty [Path] object.
+  @pragma('vm:entry-point')
+  Path() { _constructor(); }
+  void _constructor() { throw UnimplementedError(); }
+
+  /// Avoids creating a new native backing for the path for methods that will
+  /// create it later, such as [Path.from], [shift] and [transform].
+  Path._();
+
+  /// Creates a copy of another [Path].
+  ///
+  /// This copy is fast and does not require additional memory unless either
+  /// the `source` path or the path returned by this constructor are modified.
+  factory Path.from(Path source) {
+    final Path clonedPath = Path._();
+    source._clone(clonedPath);
+    return clonedPath;
+  }
+  void _clone(Path outPath) { throw UnimplementedError(); }
+
+  /// Determines how the interior of this path is calculated.
+  ///
+  /// Defaults to the non-zero winding rule, [PathFillType.nonZero].
+  PathFillType get fillType => PathFillType.values[_getFillType()];
+  set fillType(PathFillType value) => _setFillType(value.index);
+
+  int _getFillType() { throw UnimplementedError(); }
+  void _setFillType(int fillType) { throw UnimplementedError(); }
+
+  /// Starts a new sub-path at the given coordinate.
+  void moveTo(double x, double y) { throw UnimplementedError(); }
+
+  /// Starts a new sub-path at the given offset from the current point.
+  void relativeMoveTo(double dx, double dy) { throw UnimplementedError(); }
+
+  /// Adds a straight line segment from the current point to the given
+  /// point.
+  void lineTo(double x, double y) { throw UnimplementedError(); }
+
+  /// Adds a straight line segment from the current point to the point
+  /// at the given offset from the current point.
+  void relativeLineTo(double dx, double dy) { throw UnimplementedError(); }
+
+  /// Adds a quadratic bezier segment that curves from the current
+  /// point to the given point (x2,y2), using the control point
+  /// (x1,y1).
+  void quadraticBezierTo(double x1, double y1, double x2, double y2) { throw UnimplementedError(); }
+
+  /// Adds a quadratic bezier segment that curves from the current
+  /// point to the point at the offset (x2,y2) from the current point,
+  /// using the control point at the offset (x1,y1) from the current
+  /// point.
+  void relativeQuadraticBezierTo(double x1, double y1, double x2, double y2) { throw UnimplementedError(); }
+
+  /// Adds a cubic bezier segment that curves from the current point
+  /// to the given point (x3,y3), using the control points (x1,y1) and
+  /// (x2,y2).
+  void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3) { throw UnimplementedError(); }
+
+  /// Adds a cubic bezier segment that curves from the current point
+  /// to the point at the offset (x3,y3) from the current point, using
+  /// the control points at the offsets (x1,y1) and (x2,y2) from the
+  /// current point.
+  void relativeCubicTo(double x1, double y1, double x2, double y2, double x3, double y3) { throw UnimplementedError(); }
+
+  /// Adds a bezier segment that curves from the current point to the
+  /// given point (x2,y2), using the control points (x1,y1) and the
+  /// weight w. If the weight is greater than 1, then the curve is a
+  /// hyperbola; if the weight equals 1, it's a parabola; and if it is
+  /// less than 1, it is an ellipse.
+  void conicTo(double x1, double y1, double x2, double y2, double w) { throw UnimplementedError(); }
+
+  /// Adds a bezier segment that curves from the current point to the
+  /// point at the offset (x2,y2) from the current point, using the
+  /// control point at the offset (x1,y1) from the current point and
+  /// the weight w. If the weight is greater than 1, then the curve is
+  /// a hyperbola; if the weight equals 1, it's a parabola; and if it
+  /// is less than 1, it is an ellipse.
+  void relativeConicTo(double x1, double y1, double x2, double y2, double w) { throw UnimplementedError(); }
+
+  /// If the `forceMoveTo` argument is false, adds a straight line
+  /// segment and an arc segment.
+  ///
+  /// If the `forceMoveTo` argument is true, starts a new sub-path
+  /// consisting of an arc segment.
+  ///
+  /// In either case, the arc segment consists of the arc that follows
+  /// the edge of the oval bounded by the given rectangle, from
+  /// startAngle radians around the oval up to startAngle + sweepAngle
+  /// radians around the oval, with zero radians being the point on
+  /// the right hand side of the oval that crosses the horizontal line
+  /// that intersects the center of the rectangle and with positive
+  /// angles going clockwise around the oval.
+  ///
+  /// The line segment added if `forceMoveTo` is false starts at the
+  /// current point and ends at the start of the arc.
+  void arcTo(Rect rect, double startAngle, double sweepAngle, bool forceMoveTo) {
+    assert(_rectIsValid(rect));
+    _arcTo(rect.left, rect.top, rect.right, rect.bottom, startAngle, sweepAngle, forceMoveTo);
+  }
+  void _arcTo(double left, double top, double right, double bottom,
+              double startAngle, double sweepAngle, bool forceMoveTo) { throw UnimplementedError(); }
+
+  /// Appends up to four conic curves weighted to describe an oval of `radius`
+  /// and rotated by `rotation`.
+  ///
+  /// The first curve begins from the last point in the path and the last ends
+  /// at `arcEnd`. The curves follow a path in a direction determined by
+  /// `clockwise` and `largeArc` in such a way that the sweep angle
+  /// is always less than 360 degrees.
+  ///
+  /// A simple line is appended if either either radii are zero or the last
+  /// point in the path is `arcEnd`. The radii are scaled to fit the last path
+  /// point if both are greater than zero but too small to describe an arc.
+  ///
+  void arcToPoint(Offset arcEnd, {
+    Radius radius = Radius.zero,
+    double rotation = 0.0,
+    bool largeArc = false,
+    bool clockwise = true,
+  }) {
+    assert(_offsetIsValid(arcEnd));
+    assert(_radiusIsValid(radius));
+    _arcToPoint(arcEnd.dx, arcEnd.dy, radius.x, radius.y, rotation,
+                largeArc, clockwise);
+  }
+  void _arcToPoint(double arcEndX, double arcEndY, double radiusX,
+                   double radiusY, double rotation, bool largeArc,
+                   bool clockwise) { throw UnimplementedError(); }
+
+
+  /// Appends up to four conic curves weighted to describe an oval of `radius`
+  /// and rotated by `rotation`.
+  ///
+  /// The last path point is described by (px, py).
+  ///
+  /// The first curve begins from the last point in the path and the last ends
+  /// at `arcEndDelta.dx + px` and `arcEndDelta.dy + py`. The curves follow a
+  /// path in a direction determined by `clockwise` and `largeArc`
+  /// in such a way that the sweep angle is always less than 360 degrees.
+  ///
+  /// A simple line is appended if either either radii are zero, or, both
+  /// `arcEndDelta.dx` and `arcEndDelta.dy` are zero. The radii are scaled to
+  /// fit the last path point if both are greater than zero but too small to
+  /// describe an arc.
+  void relativeArcToPoint(Offset arcEndDelta, {
+    Radius radius = Radius.zero,
+    double rotation = 0.0,
+    bool largeArc = false,
+    bool clockwise = true,
+  }) {
+    assert(_offsetIsValid(arcEndDelta));
+    assert(_radiusIsValid(radius));
+    _relativeArcToPoint(arcEndDelta.dx, arcEndDelta.dy, radius.x, radius.y,
+                        rotation, largeArc, clockwise);
+  }
+  void _relativeArcToPoint(double arcEndX, double arcEndY, double radiusX,
+                           double radiusY, double rotation,
+                           bool largeArc, bool clockwise)
+                           { throw UnimplementedError(); }
+
+  /// Adds a new sub-path that consists of four lines that outline the
+  /// given rectangle.
+  void addRect(Rect rect) {
+    assert(_rectIsValid(rect));
+    _addRect(rect.left, rect.top, rect.right, rect.bottom);
+  }
+  void _addRect(double left, double top, double right, double bottom) { throw UnimplementedError(); }
+
+  /// Adds a new sub-path that consists of a curve that forms the
+  /// ellipse that fills the given rectangle.
+  ///
+  /// To add a circle, pass an appropriate rectangle as `oval`. [Rect.fromCircle]
+  /// can be used to easily describe the circle's center [Offset] and radius.
+  void addOval(Rect oval) {
+    assert(_rectIsValid(oval));
+    _addOval(oval.left, oval.top, oval.right, oval.bottom);
+  }
+  void _addOval(double left, double top, double right, double bottom) { throw UnimplementedError(); }
+
+  /// Adds a new sub-path with one arc segment that consists of the arc
+  /// that follows the edge of the oval bounded by the given
+  /// rectangle, from startAngle radians around the oval up to
+  /// startAngle + sweepAngle radians around the oval, with zero
+  /// radians being the point on the right hand side of the oval that
+  /// crosses the horizontal line that intersects the center of the
+  /// rectangle and with positive angles going clockwise around the
+  /// oval.
+  void addArc(Rect oval, double startAngle, double sweepAngle) {
+    assert(_rectIsValid(oval));
+    _addArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle);
+  }
+  void _addArc(double left, double top, double right, double bottom,
+               double startAngle, double sweepAngle) { throw UnimplementedError(); }
+
+  /// Adds a new sub-path with a sequence of line segments that connect the given
+  /// points.
+  ///
+  /// If `close` is true, a final line segment will be added that connects the
+  /// last point to the first point.
+  ///
+  /// The `points` argument is interpreted as offsets from the origin.
+  void addPolygon(List<Offset> points, bool close) {
+    assert(points != null); // ignore: unnecessary_null_comparison
+    _addPolygon(_encodePointList(points), close);
+  }
+  void _addPolygon(Float32List points, bool close) { throw UnimplementedError(); }
+
+  /// Adds a new sub-path that consists of the straight lines and
+  /// curves needed to form the rounded rectangle described by the
+  /// argument.
+  void addRRect(RRect rrect) {
+    assert(_rrectIsValid(rrect));
+    _addRRect(rrect._value32);
+  }
+  void _addRRect(Float32List rrect) { throw UnimplementedError(); }
+
+  /// Adds a new sub-path that consists of the given `path` offset by the given
+  /// `offset`.
+  ///
+  /// If `matrix4` is specified, the path will be transformed by this matrix
+  /// after the matrix is translated by the given offset. The matrix is a 4x4
+  /// matrix stored in column major order.
+  void addPath(Path path, Offset offset, {Float64List? matrix4}) {
+    // ignore: unnecessary_null_comparison
+    assert(path != null); // path is checked on the engine side
+    assert(_offsetIsValid(offset));
+    if (matrix4 != null) {
+      assert(_matrix4IsValid(matrix4));
+      _addPathWithMatrix(path, offset.dx, offset.dy, matrix4);
+    } else {
+      _addPath(path, offset.dx, offset.dy);
+    }
+  }
+  void _addPath(Path path, double dx, double dy) { throw UnimplementedError(); }
+  void _addPathWithMatrix(Path path, double dx, double dy, Float64List matrix) { throw UnimplementedError(); }
+
+  /// Adds the given path to this path by extending the current segment of this
+  /// path with the first segment of the given path.
+  ///
+  /// If `matrix4` is specified, the path will be transformed by this matrix
+  /// after the matrix is translated by the given `offset`.  The matrix is a 4x4
+  /// matrix stored in column major order.
+  void extendWithPath(Path path, Offset offset, {Float64List? matrix4}) {
+    // ignore: unnecessary_null_comparison
+    assert(path != null); // path is checked on the engine side
+    assert(_offsetIsValid(offset));
+    if (matrix4 != null) {
+      assert(_matrix4IsValid(matrix4));
+      _extendWithPathAndMatrix(path, offset.dx, offset.dy, matrix4);
+    } else {
+      _extendWithPath(path, offset.dx, offset.dy);
+    }
+  }
+  void _extendWithPath(Path path, double dx, double dy) { throw UnimplementedError(); }
+  void _extendWithPathAndMatrix(Path path, double dx, double dy, Float64List matrix) { throw UnimplementedError(); }
+
+  /// Closes the last sub-path, as if a straight line had been drawn
+  /// from the current point to the first point of the sub-path.
+  void close() { throw UnimplementedError(); }
+
+  /// Clears the [Path] object of all sub-paths, returning it to the
+  /// same state it had when it was created. The _current point_ is
+  /// reset to the origin.
+  void reset() { throw UnimplementedError(); }
+
+  /// Tests to see if the given point is within the path. (That is, whether the
+  /// point would be in the visible portion of the path if the path was used
+  /// with [Canvas.clipPath].)
+  ///
+  /// The `point` argument is interpreted as an offset from the origin.
+  ///
+  /// Returns true if the point is in the path, and false otherwise.
+  bool contains(Offset point) {
+    assert(_offsetIsValid(point));
+    return _contains(point.dx, point.dy);
+  }
+  bool _contains(double x, double y) { throw UnimplementedError(); }
+
+  /// Returns a copy of the path with all the segments of every
+  /// sub-path translated by the given offset.
+  Path shift(Offset offset) {
+    assert(_offsetIsValid(offset));
+    final Path path = Path._();
+    _shift(path, offset.dx, offset.dy);
+    return path;
+  }
+  void _shift(Path outPath, double dx, double dy) { throw UnimplementedError(); }
+
+  /// Returns a copy of the path with all the segments of every
+  /// sub-path transformed by the given matrix.
+  Path transform(Float64List matrix4) {
+    assert(_matrix4IsValid(matrix4));
+    final Path path = Path._();
+    _transform(path, matrix4);
+    return path;
+  }
+  void _transform(Path outPath, Float64List matrix4) { throw UnimplementedError(); }
+
+  /// Computes the bounding rectangle for this path.
+  ///
+  /// A path containing only axis-aligned points on the same straight line will
+  /// have no area, and therefore `Rect.isEmpty` will return true for such a
+  /// path. Consider checking `rect.width + rect.height > 0.0` instead, or
+  /// using the [computeMetrics] API to check the path length.
+  ///
+  /// For many more elaborate paths, the bounds may be inaccurate.  For example,
+  /// when a path contains a circle, the points used to compute the bounds are
+  /// the circle's implied control points, which form a square around the circle;
+  /// if the circle has a transformation applied using [transform] then that
+  /// square is rotated, and the (axis-aligned, non-rotated) bounding box
+  /// therefore ends up grossly overestimating the actual area covered by the
+  /// circle.
+  // see https://skia.org/user/api/SkPath_Reference#SkPath_getBounds
+  Rect getBounds() {
+    final Float32List rect = _getBounds();
+    return Rect.fromLTRB(rect[0], rect[1], rect[2], rect[3]);
+  }
+  Float32List _getBounds() { throw UnimplementedError(); }
+
+  /// Combines the two paths according to the manner specified by the given
+  /// `operation`.
+  ///
+  /// The resulting path will be constructed from non-overlapping contours. The
+  /// curve order is reduced where possible so that cubics may be turned into
+  /// quadratics, and quadratics maybe turned into lines.
+  static Path combine(PathOperation operation, Path path1, Path path2) {
+    assert(path1 != null); // ignore: unnecessary_null_comparison
+    assert(path2 != null); // ignore: unnecessary_null_comparison
+    final Path path = Path();
+    if (path._op(path1, path2, operation.index)) {
+      return path;
+    }
+    throw StateError('Path.combine() failed.  This may be due an invalid path; in particular, check for NaN values.');
+  }
+  bool _op(Path path1, Path path2, int operation) { throw UnimplementedError(); }
+
+  /// Creates a [PathMetrics] object for this path, which can describe various
+  /// properties about the contours of the path.
+  ///
+  /// A [Path] is made up of zero or more contours. A contour is made up of
+  /// connected curves and segments, created via methods like [lineTo],
+  /// [cubicTo], [arcTo], [quadraticBezierTo], their relative counterparts, as
+  /// well as the add* methods such as [addRect]. Creating a new [Path] starts
+  /// a new contour once it has any drawing instructions, and another new
+  /// contour is started for each [moveTo] instruction.
+  ///
+  /// A [PathMetric] object describes properties of an individual contour,
+  /// such as its length, whether it is closed, what the tangent vector of a
+  /// particular offset along the path is. It also provides a method for
+  /// creating sub-paths: [PathMetric.extractPath].
+  ///
+  /// Calculating [PathMetric] objects is not trivial. The [PathMetrics] object
+  /// returned by this method is a lazy [Iterable], meaning it only performs
+  /// calculations when the iterator is moved to the next [PathMetric]. Callers
+  /// that wish to memoize this iterable can easily do so by using
+  /// [Iterable.toList] on the result of this method. In particular, callers
+  /// looking for information about how many contours are in the path should
+  /// either store the result of `path.computeMetrics().length`, or should use
+  /// `path.computeMetrics().toList()` so they can repeatedly check the length,
+  /// since calling `Iterable.length` causes traversal of the entire iterable.
+  ///
+  /// In particular, callers should be aware that [PathMetrics.length] is the
+  /// number of contours, **not the length of the path**. To get the length of
+  /// a contour in a path, use [PathMetric.length].
+  ///
+  /// If `forceClosed` is set to true, the contours of the path will be measured
+  /// as if they had been closed, even if they were not explicitly closed.
+  PathMetrics computeMetrics({bool forceClosed = false}) {
+    return PathMetrics._(this, forceClosed);
+  }
+}
+
+/// The geometric description of a tangent: the angle at a point.
+///
+/// See also:
+///  * [PathMetric.getTangentForOffset], which returns the tangent of an offset along a path.
+class Tangent {
+  /// Creates a [Tangent] with the given values.
+  ///
+  /// The arguments must not be null.
+  const Tangent(this.position, this.vector)
+    : assert(position != null), // ignore: unnecessary_null_comparison
+      assert(vector != null); // ignore: unnecessary_null_comparison
+
+  /// Creates a [Tangent] based on the angle rather than the vector.
+  ///
+  /// The [vector] is computed to be the unit vector at the given angle, interpreted
+  /// as clockwise radians from the x axis.
+  factory Tangent.fromAngle(Offset position, double angle) {
+    return Tangent(position, Offset(math.cos(angle), math.sin(angle)));
+  }
+
+  /// Position of the tangent.
+  ///
+  /// When used with [PathMetric.getTangentForOffset], this represents the precise
+  /// position that the given offset along the path corresponds to.
+  final Offset position;
+
+  /// The vector of the curve at [position].
+  ///
+  /// When used with [PathMetric.getTangentForOffset], this is the vector of the
+  /// curve that is at the given offset along the path (i.e. the direction of the
+  /// curve at [position]).
+  final Offset vector;
+
+  /// The direction of the curve at [position].
+  ///
+  /// When used with [PathMetric.getTangentForOffset], this is the angle of the
+  /// curve that is the given offset along the path (i.e. the direction of the
+  /// curve at [position]).
+  ///
+  /// This value is in radians, with 0.0 meaning pointing along the x axis in
+  /// the positive x-axis direction, positive numbers pointing downward toward
+  /// the negative y-axis, i.e. in a clockwise direction, and negative numbers
+  /// pointing upward toward the positive y-axis, i.e. in a counter-clockwise
+  /// direction.
+  // flip the sign to be consistent with [Path.arcTo]'s `sweepAngle`
+  double get angle => -math.atan2(vector.dy, vector.dx);
+}
+
+/// An iterable collection of [PathMetric] objects describing a [Path].
+///
+/// A [PathMetrics] object is created by using the [Path.computeMetrics] method,
+/// and represents the path as it stood at the time of the call. Subsequent
+/// modifications of the path do not affect the [PathMetrics] object.
+///
+/// Each path metric corresponds to a segment, or contour, of a path.
+///
+/// For example, a path consisting of a [Path.lineTo], a [Path.moveTo], and
+/// another [Path.lineTo] will contain two contours and thus be represented by
+/// two [PathMetric] objects.
+///
+/// This iterable does not memoize. Callers who need to traverse the list
+/// multiple times, or who need to randomly access elements of the list, should
+/// use [toList] on this object.
+class PathMetrics extends collection.IterableBase<PathMetric> {
+  PathMetrics._(Path path, bool forceClosed) :
+    _iterator = PathMetricIterator._(_PathMeasure(path, forceClosed));
+
+  final Iterator<PathMetric> _iterator;
+
+  @override
+  Iterator<PathMetric> get iterator => _iterator;
+}
+
+/// Used by [PathMetrics] to track iteration from one segment of a path to the
+/// next for measurement.
+class PathMetricIterator implements Iterator<PathMetric> {
+  PathMetricIterator._(this._pathMeasure) : assert(_pathMeasure != null); // ignore: unnecessary_null_comparison
+
+  PathMetric? _pathMetric;
+  _PathMeasure _pathMeasure;
+
+  @override
+  PathMetric get current {
+    final PathMetric? currentMetric = _pathMetric;
+    if (currentMetric == null) {
+      throw RangeError(
+        'PathMetricIterator is not pointing to a PathMetric. This can happen in two situations:\n'
+        '- The iteration has not started yet. If so, call "moveNext" to start iteration.'
+        '- The iterator ran out of elements. If so, check that "moveNext" returns true prior to calling "current".'
+      );
+    }
+    return currentMetric;
+  }
+
+  @override
+  bool moveNext() {
+    if (_pathMeasure._nextContour()) {
+      _pathMetric = PathMetric._(_pathMeasure);
+      return true;
+    }
+    _pathMetric = null;
+    return false;
+  }
+}
+
+/// Utilities for measuring a [Path] and extracting sub-paths.
+///
+/// Iterate over the object returned by [Path.computeMetrics] to obtain
+/// [PathMetric] objects. Callers that want to randomly access elements or
+/// iterate multiple times should use `path.computeMetrics().toList()`, since
+/// [PathMetrics] does not memoize.
+///
+/// Once created, the metrics are only valid for the path as it was specified
+/// when [Path.computeMetrics] was called. If additional contours are added or
+/// any contours are updated, the metrics need to be recomputed. Previously
+/// created metrics will still refer to a snapshot of the path at the time they
+/// were computed, rather than to the actual metrics for the new mutations to
+/// the path.
+class PathMetric {
+  PathMetric._(this._measure)
+    : assert(_measure != null), // ignore: unnecessary_null_comparison
+      length = _measure.length(_measure.currentContourIndex),
+      isClosed = _measure.isClosed(_measure.currentContourIndex),
+      contourIndex = _measure.currentContourIndex;
+
+  /// Return the total length of the current contour.
+  final double length;
+
+  /// Whether the contour is closed.
+  ///
+  /// Returns true if the contour ends with a call to [Path.close] (which may
+  /// have been implied when using methods like [Path.addRect]) or if
+  /// `forceClosed` was specified as true in the call to [Path.computeMetrics].
+  /// Returns false otherwise.
+  final bool isClosed;
+
+  /// The zero-based index of the contour.
+  ///
+  /// [Path] objects are made up of zero or more contours. The first contour is
+  /// created once a drawing command (e.g. [Path.lineTo]) is issued. A
+  /// [Path.moveTo] command after a drawing command may create a new contour,
+  /// although it may not if optimizations are applied that determine the move
+  /// command did not actually result in moving the pen.
+  ///
+  /// This property is only valid with reference to its original iterator and
+  /// the contours of the path at the time the path's metrics were computed. If
+  /// additional contours were added or existing contours updated, this metric
+  /// will be invalid for the current state of the path.
+  final int contourIndex;
+
+  final _PathMeasure _measure;
+
+
+  /// Computes the position of the current contour at the given offset, and the
+  /// angle of the path at that point.
+  ///
+  /// For example, calling this method with a distance of 1.41 for a line from
+  /// 0.0,0.0 to 2.0,2.0 would give a point 1.0,1.0 and the angle 45 degrees
+  /// (but in radians).
+  ///
+  /// Returns null if the contour has zero [length].
+  ///
+  /// The distance is clamped to the [length] of the current contour.
+  Tangent? getTangentForOffset(double distance) {
+    return _measure.getTangentForOffset(contourIndex, distance);
+  }
+
+  /// Given a start and end distance, return the intervening segment(s).
+  ///
+  /// `start` and `end` are clamped to legal values (0..[length])
+  /// Begin the segment with a moveTo if `startWithMoveTo` is true.
+  Path extractPath(double start, double end, {bool startWithMoveTo = true}) {
+    return _measure.extractPath(contourIndex, start, end, startWithMoveTo: startWithMoveTo);
+  }
+
+  @override
+  String toString() => '$runtimeType{length: $length, isClosed: $isClosed, contourIndex:$contourIndex}';
+}
+
+class _PathMeasure extends NativeFieldWrapperClass2 {
+  _PathMeasure(Path path, bool forceClosed) {
+    _constructor(path, forceClosed);
+  }
+  void _constructor(Path path, bool forceClosed) { throw UnimplementedError(); }
+
+  double length(int contourIndex) {
+    assert(contourIndex <= currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.');
+    return _length(contourIndex);
+  }
+  double _length(int contourIndex) { throw UnimplementedError(); }
+
+  Tangent? getTangentForOffset(int contourIndex, double distance) {
+    assert(contourIndex <= currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.');
+    final Float32List posTan = _getPosTan(contourIndex, distance);
+    // first entry == 0 indicates that Skia returned false
+    if (posTan[0] == 0.0) {
+      return null;
+    } else {
+      return Tangent(
+        Offset(posTan[1], posTan[2]),
+        Offset(posTan[3], posTan[4])
+      );
+    }
+  }
+  Float32List _getPosTan(int contourIndex, double distance) { throw UnimplementedError(); }
+
+  Path extractPath(int contourIndex, double start, double end, {bool startWithMoveTo = true}) {
+    assert(contourIndex <= currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.');
+    final Path path = Path._();
+    _extractPath(path, contourIndex, start, end, startWithMoveTo: startWithMoveTo);
+    return path;
+  }
+  void _extractPath(Path outPath, int contourIndex, double start, double end, {bool startWithMoveTo = true}) { throw UnimplementedError(); }
+
+  bool isClosed(int contourIndex) {
+    assert(contourIndex <= currentContourIndex, 'Iterator must be advanced before index $contourIndex can be used.');
+    return _isClosed(contourIndex);
+  }
+  bool _isClosed(int contourIndex) { throw UnimplementedError(); }
+
+  // Move to the next contour in the path.
+  //
+  // A path can have a next contour if [Path.moveTo] was called after drawing began.
+  // Return true if one exists, or false.
+  bool _nextContour() {
+    final bool next = _nativeNextContour();
+    if (next) {
+      currentContourIndex++;
+    }
+    return next;
+  }
+  bool _nativeNextContour() { throw UnimplementedError(); }
+
+  /// The index of the current contour in the list of contours in the path.
+  ///
+  /// [nextContour] will increment this to the zero based index.
+  int currentContourIndex = -1;
+}
+
+/// Styles to use for blurs in [MaskFilter] objects.
+// These enum values must be kept in sync with SkBlurStyle.
+enum BlurStyle {
+  // These mirror SkBlurStyle and must be kept in sync.
+
+  /// Fuzzy inside and outside. This is useful for painting shadows that are
+  /// offset from the shape that ostensibly is casting the shadow.
+  normal,
+
+  /// Solid inside, fuzzy outside. This corresponds to drawing the shape, and
+  /// additionally drawing the blur. This can make objects appear brighter,
+  /// maybe even as if they were fluorescent.
+  solid,
+
+  /// Nothing inside, fuzzy outside. This is useful for painting shadows for
+  /// partially transparent shapes, when they are painted separately but without
+  /// an offset, so that the shadow doesn't paint below the shape.
+  outer,
+
+  /// Fuzzy inside, nothing outside. This can make shapes appear to be lit from
+  /// within.
+  inner,
+}
+
+/// A mask filter to apply to shapes as they are painted. A mask filter is a
+/// function that takes a bitmap of color pixels, and returns another bitmap of
+/// color pixels.
+///
+/// Instances of this class are used with [Paint.maskFilter] on [Paint] objects.
+class MaskFilter {
+  /// Creates a mask filter that takes the shape being drawn and blurs it.
+  ///
+  /// This is commonly used to approximate shadows.
+  ///
+  /// The `style` argument controls the kind of effect to draw; see [BlurStyle].
+  ///
+  /// The `sigma` argument controls the size of the effect. It is the standard
+  /// deviation of the Gaussian blur to apply. The value must be greater than
+  /// zero. The sigma corresponds to very roughly half the radius of the effect
+  /// in pixels.
+  ///
+  /// A blur is an expensive operation and should therefore be used sparingly.
+  ///
+  /// The arguments must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [Canvas.drawShadow], which is a more efficient way to draw shadows.
+  const MaskFilter.blur(
+    this._style,
+    this._sigma,
+  ) : assert(_style != null), // ignore: unnecessary_null_comparison
+      assert(_sigma != null); // ignore: unnecessary_null_comparison
+
+  final BlurStyle _style;
+  final double _sigma;
+
+  // The type of MaskFilter class to create for Skia.
+  // These constants must be kept in sync with MaskFilterType in paint.cc.
+  static const int _TypeNone = 0; // null
+  static const int _TypeBlur = 1; // SkBlurMaskFilter
+
+  @override
+  bool operator ==(Object other) {
+    return other is MaskFilter
+        && other._style == _style
+        && other._sigma == _sigma;
+  }
+
+  @override
+  int get hashCode => hashValues(_style, _sigma);
+
+  @override
+  String toString() => 'MaskFilter.blur($_style, ${_sigma.toStringAsFixed(1)})';
+}
+
+/// A description of a color filter to apply when drawing a shape or compositing
+/// a layer with a particular [Paint]. A color filter is a function that takes
+/// two colors, and outputs one color. When applied during compositing, it is
+/// independently applied to each pixel of the layer being drawn before the
+/// entire layer is merged with the destination.
+///
+/// Instances of this class are used with [Paint.colorFilter] on [Paint]
+/// objects.
+class ColorFilter implements ImageFilter {
+  /// Creates a color filter that applies the blend mode given as the second
+  /// argument. The source color is the one given as the first argument, and the
+  /// destination color is the one from the layer being composited.
+  ///
+  /// The output of this filter is then composited into the background according
+  /// to the [Paint.blendMode], using the output of this filter as the source
+  /// and the background as the destination.
+  const ColorFilter.mode(Color color, BlendMode blendMode)
+      : _color = color,
+        _blendMode = blendMode,
+        _matrix = null,
+        _type = _kTypeMode;
+
+  /// Construct a color filter that transforms a color by a 5x5 matrix, where
+  /// the fifth row is implicitly added in an identity configuration.
+  ///
+  /// Every pixel's color value, repsented as an `[R, G, B, A]`, is matrix
+  /// multiplied to create a new color:
+  ///
+  /// ```text
+  /// | R' |   | a00 a01 a02 a03 a04 |   | R |
+  /// | G' |   | a10 a11 a22 a33 a44 |   | G |
+  /// | B' | = | a20 a21 a22 a33 a44 | * | B |
+  /// | A' |   | a30 a31 a22 a33 a44 |   | A |
+  /// | 1  |   |  0   0   0   0   1  |   | 1 |
+  /// ```
+  ///
+  /// The matrix is in row-major order and the translation column is specified
+  /// in unnormalized, 0...255, space. For example, the identity matrix is:
+  ///
+  /// ```
+  /// const ColorFilter identity = ColorFilter.matrix(<double>[
+  ///   1, 0, 0, 0, 0,
+  ///   0, 1, 0, 0, 0,
+  ///   0, 0, 1, 0, 0,
+  ///   0, 0, 0, 1, 0,
+  /// ]);
+  /// ```
+  ///
+  /// ## Examples
+  ///
+  /// An inversion color matrix:
+  ///
+  /// ```
+  /// const ColorFilter invert = ColorFilter.matrix(<double>[
+  ///   -1,  0,  0, 0, 255,
+  ///    0, -1,  0, 0, 255,
+  ///    0,  0, -1, 0, 255,
+  ///    0,  0,  0, 1,   0,
+  /// ]);
+  /// ```
+  ///
+  /// A sepia-toned color matrix (values based on the [Filter Effects Spec](https://www.w3.org/TR/filter-effects-1/#sepiaEquivalent)):
+  ///
+  /// ```
+  /// const ColorFilter sepia = ColorFilter.matrix(<double>[
+  ///   0.393, 0.769, 0.189, 0, 0,
+  ///   0.349, 0.686, 0.168, 0, 0,
+  ///   0.272, 0.534, 0.131, 0, 0,
+  ///   0,     0,     0,     1, 0,
+  /// ]);
+  /// ```
+  ///
+  /// A greyscale color filter (values based on the [Filter Effects Spec](https://www.w3.org/TR/filter-effects-1/#grayscaleEquivalent)):
+  ///
+  /// ```
+  /// const ColorFilter greyscale = ColorFilter.matrix(<double>[
+  ///   0.2126, 0.7152, 0.0722, 0, 0,
+  ///   0.2126, 0.7152, 0.0722, 0, 0,
+  ///   0.2126, 0.7152, 0.0722, 0, 0,
+  ///   0,      0,      0,      1, 0,
+  /// ]);
+  /// ```
+  const ColorFilter.matrix(List<double> matrix)
+      : _color = null,
+        _blendMode = null,
+        _matrix = matrix,
+        _type = _kTypeMatrix;
+
+  /// Construct a color filter that applies the sRGB gamma curve to the RGB
+  /// channels.
+  const ColorFilter.linearToSrgbGamma()
+      : _color = null,
+        _blendMode = null,
+        _matrix = null,
+        _type = _kTypeLinearToSrgbGamma;
+
+  /// Creates a color filter that applies the inverse of the sRGB gamma curve
+  /// to the RGB channels.
+  const ColorFilter.srgbToLinearGamma()
+      : _color = null,
+        _blendMode = null,
+        _matrix = null,
+        _type = _kTypeSrgbToLinearGamma;
+
+  final Color? _color;
+  final BlendMode? _blendMode;
+  final List<double>? _matrix;
+  final int _type;
+
+  // The type of SkColorFilter class to create for Skia.
+  static const int _kTypeMode = 1; // MakeModeFilter
+  static const int _kTypeMatrix = 2; // MakeMatrixFilterRowMajor255
+  static const int _kTypeLinearToSrgbGamma = 3; // MakeLinearToSRGBGamma
+  static const int _kTypeSrgbToLinearGamma = 4; // MakeSRGBToLinearGamma
+
+  // SkImageFilters::ColorFilter
+  @override
+  _ImageFilter _toNativeImageFilter() => _ImageFilter.fromColorFilter(this);
+
+  _ColorFilter? _toNativeColorFilter() {
+    switch (_type) {
+      case _kTypeMode:
+        if (_color == null || _blendMode == null) {
+          return null;
+        }
+        return _ColorFilter.mode(this);
+      case _kTypeMatrix:
+        if (_matrix == null) {
+          return null;
+        }
+        assert(_matrix!.length == 20, 'Color Matrix must have 20 entries.');
+        return _ColorFilter.matrix(this);
+      case _kTypeLinearToSrgbGamma:
+        return _ColorFilter.linearToSrgbGamma(this);
+      case _kTypeSrgbToLinearGamma:
+        return _ColorFilter.srgbToLinearGamma(this);
+      default:
+        throw StateError('Unknown mode $_type for ColorFilter.');
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ColorFilter
+        && other._type == _type
+        && _listEquals<double>(other._matrix, _matrix)
+        && other._color == _color
+        && other._blendMode == _blendMode;
+  }
+
+  @override
+  int get hashCode => hashValues(_color, _blendMode, hashList(_matrix), _type);
+
+  @override
+  String get _shortDescription {
+    switch (_type) {
+      case _kTypeMode:
+        return 'ColorFilter.mode($_color, $_blendMode)';
+      case _kTypeMatrix:
+        return 'ColorFilter.matrix($_matrix)';
+      case _kTypeLinearToSrgbGamma:
+        return 'ColorFilter.linearToSrgbGamma()';
+      case _kTypeSrgbToLinearGamma:
+        return 'ColorFilter.srgbToLinearGamma()';
+      default:
+        return 'unknow ColorFilter';
+    }
+  }
+
+  @override
+  String toString() {
+    switch (_type) {
+      case _kTypeMode:
+        return 'ColorFilter.mode($_color, $_blendMode)';
+      case _kTypeMatrix:
+        return 'ColorFilter.matrix($_matrix)';
+      case _kTypeLinearToSrgbGamma:
+        return 'ColorFilter.linearToSrgbGamma()';
+      case _kTypeSrgbToLinearGamma:
+        return 'ColorFilter.srgbToLinearGamma()';
+      default:
+        return 'Unknown ColorFilter type. This is an error. If you\'re seeing this, please file an issue at https://github.com/flutter/flutter/issues/new.';
+    }
+  }
+}
+
+/// A [ColorFilter] that is backed by a native SkColorFilter.
+///
+/// This is a private class, rather than being the implementation of the public
+/// ColorFilter, because we want ColorFilter to be const constructible and
+/// efficiently comparable, so that widgets can check for ColorFilter equality to
+/// avoid repainting.
+class _ColorFilter extends NativeFieldWrapperClass2 {
+  _ColorFilter.mode(this.creator)
+    : assert(creator != null), // ignore: unnecessary_null_comparison
+      assert(creator._type == ColorFilter._kTypeMode) {
+    _constructor();
+    _initMode(creator._color!.value, creator._blendMode!.index);
+  }
+
+  _ColorFilter.matrix(this.creator)
+    : assert(creator != null), // ignore: unnecessary_null_comparison
+      assert(creator._type == ColorFilter._kTypeMatrix) {
+    _constructor();
+    _initMatrix(Float32List.fromList(creator._matrix!));
+  }
+  _ColorFilter.linearToSrgbGamma(this.creator)
+    : assert(creator != null), // ignore: unnecessary_null_comparison
+      assert(creator._type == ColorFilter._kTypeLinearToSrgbGamma) {
+    _constructor();
+    _initLinearToSrgbGamma();
+  }
+
+  _ColorFilter.srgbToLinearGamma(this.creator)
+    : assert(creator != null), // ignore: unnecessary_null_comparison
+      assert(creator._type == ColorFilter._kTypeSrgbToLinearGamma) {
+    _constructor();
+    _initSrgbToLinearGamma();
+  }
+
+  /// The original Dart object that created the native wrapper, which retains
+  /// the values used for the filter.
+  final ColorFilter creator;
+
+  void _constructor() { throw UnimplementedError(); }
+  void _initMode(int color, int blendMode) { throw UnimplementedError(); }
+  void _initMatrix(Float32List matrix) { throw UnimplementedError(); }
+  void _initLinearToSrgbGamma() { throw UnimplementedError(); }
+  void _initSrgbToLinearGamma() { throw UnimplementedError(); }
+}
+
+/// A filter operation to apply to a raster image.
+///
+/// See also:
+///
+///  * [BackdropFilter], a widget that applies [ImageFilter] to its rendering.
+///  * [ImageFiltered], a widget that applies [ImageFilter] to its children.
+///  * [SceneBuilder.pushBackdropFilter], which is the low-level API for using
+///    this class as a backdrop filter.
+///  * [SceneBuilder.pushImageFilter], which is the low-level API for using
+///    this class as a child layer filter.
+abstract class ImageFilter {
+  /// Creates an image filter that applies a Gaussian blur.
+  factory ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0, TileMode tileMode = TileMode.clamp }) {
+    assert(sigmaX != null); // ignore: unnecessary_null_comparison
+    assert(sigmaY != null); // ignore: unnecessary_null_comparison
+    assert(tileMode != null); // ignore: unnecessary_null_comparison
+    return _GaussianBlurImageFilter(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode);
+  }
+
+  /// Creates an image filter that applies a matrix transformation.
+  ///
+  /// For example, applying a positive scale matrix (see [Matrix4.diagonal3])
+  /// when used with [BackdropFilter] would magnify the background image.
+  factory ImageFilter.matrix(Float64List matrix4,
+                     { FilterQuality filterQuality = FilterQuality.low }) {
+    assert(matrix4 != null);       // ignore: unnecessary_null_comparison
+    assert(filterQuality != null); // ignore: unnecessary_null_comparison
+    if (matrix4.length != 16)
+      throw ArgumentError('"matrix4" must have 16 entries.');
+    return _MatrixImageFilter(data: Float64List.fromList(matrix4), filterQuality: filterQuality);
+  }
+
+  /// Composes the `inner` filter with `outer`, to combine their effects.
+  ///
+  /// Creates a single [ImageFilter] that when applied, has the same effect as
+  /// subsequently applying `inner` and `outer`, i.e.,
+  /// result = outer(inner(source)).
+  factory ImageFilter.compose({ required ImageFilter outer, required ImageFilter inner }) {
+    assert (inner != null && outer != null);  // ignore: unnecessary_null_comparison
+    return _ComposeImageFilter(innerFilter: inner, outerFilter: outer);
+  }
+
+  // Converts this to a native SkImageFilter. See the comments of this method in
+  // subclasses for the exact type of SkImageFilter this method converts to.
+  _ImageFilter _toNativeImageFilter();
+
+  // The description text to show when the filter is part of a composite
+  // [ImageFilter] created using [ImageFilter.compose].
+  String get _shortDescription;
+}
+
+class _MatrixImageFilter implements ImageFilter {
+  _MatrixImageFilter({ required this.data, required this.filterQuality });
+
+  final Float64List data;
+  final FilterQuality filterQuality;
+
+  // MakeMatrixFilterRowMajor255
+  late final _ImageFilter nativeFilter = _ImageFilter.matrix(this);
+  @override
+  _ImageFilter _toNativeImageFilter() => nativeFilter;
+
+  @override
+  String get _shortDescription => 'matrix($data, $filterQuality)';
+
+  @override
+  String toString() => 'ImageFilter.matrix($data, $filterQuality)';
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _MatrixImageFilter
+        && other.filterQuality == filterQuality
+        && _listEquals<double>(other.data, data);
+  }
+
+  @override
+  int get hashCode => hashValues(filterQuality, hashList(data));
+}
+
+class _GaussianBlurImageFilter implements ImageFilter {
+  _GaussianBlurImageFilter({ required this.sigmaX, required this.sigmaY, required this.tileMode });
+
+  final double sigmaX;
+  final double sigmaY;
+  final TileMode tileMode;
+
+  // MakeBlurFilter
+  late final _ImageFilter nativeFilter = _ImageFilter.blur(this);
+  @override
+  _ImageFilter _toNativeImageFilter() => nativeFilter;
+
+  String get _modeString {
+    switch(tileMode) {
+      case TileMode.clamp: return 'clamp';
+      case TileMode.mirror: return 'mirror';
+      case TileMode.repeated: return 'repeated';
+      case TileMode.decal: return 'decal';
+    }
+  }
+
+  @override
+  String get _shortDescription => 'blur($sigmaX, $sigmaY, $_modeString)';
+
+  @override
+  String toString() => 'ImageFilter.blur($sigmaX, $sigmaY, $_modeString)';
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _GaussianBlurImageFilter
+        && other.sigmaX == sigmaX
+        && other.sigmaY == sigmaY
+        && other.tileMode == tileMode;
+  }
+
+  @override
+  int get hashCode => hashValues(sigmaX, sigmaY);
+}
+
+class _ComposeImageFilter implements ImageFilter {
+  _ComposeImageFilter({ required this.innerFilter, required this.outerFilter });
+
+  final ImageFilter innerFilter;
+  final ImageFilter outerFilter;
+
+  // SkImageFilters::Compose
+  late final _ImageFilter nativeFilter = _ImageFilter.composed(this);
+  @override
+  _ImageFilter _toNativeImageFilter() => nativeFilter;
+
+  @override
+  String get _shortDescription => '${innerFilter._shortDescription} -> ${outerFilter._shortDescription}';
+
+  @override
+  String toString() => 'ImageFilter.compose(source -> $_shortDescription -> result)';
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _ComposeImageFilter
+        && other.innerFilter == innerFilter
+        && other.outerFilter == outerFilter;
+  }
+
+  @override
+  int get hashCode => hashValues(innerFilter, outerFilter);
+}
+
+/// An [ImageFilter] that is backed by a native SkImageFilter.
+///
+/// This is a private class, rather than being the implementation of the public
+/// ImageFilter, because we want ImageFilter to be efficiently comparable, so that
+/// widgets can check for ImageFilter equality to avoid repainting.
+class _ImageFilter extends NativeFieldWrapperClass2 {
+  void _constructor() { throw UnimplementedError(); }
+
+  /// Creates an image filter that applies a Gaussian blur.
+  _ImageFilter.blur(_GaussianBlurImageFilter filter)
+    : assert(filter != null), // ignore: unnecessary_null_comparison
+      creator = filter {    // ignore: prefer_initializing_formals
+    _constructor();
+    _initBlur(filter.sigmaX, filter.sigmaY, filter.tileMode.index);
+  }
+  void _initBlur(double sigmaX, double sigmaY, int tileMode) { throw UnimplementedError(); }
+
+  /// Creates an image filter that applies a matrix transformation.
+  ///
+  /// For example, applying a positive scale matrix (see [Matrix4.diagonal3])
+  /// when used with [BackdropFilter] would magnify the background image.
+  _ImageFilter.matrix(_MatrixImageFilter filter)
+    : assert(filter != null), // ignore: unnecessary_null_comparison
+      creator = filter {    // ignore: prefer_initializing_formals
+    if (filter.data.length != 16)
+      throw ArgumentError('"matrix4" must have 16 entries.');
+    _constructor();
+    _initMatrix(filter.data, filter.filterQuality.index);
+  }
+  void _initMatrix(Float64List matrix4, int filterQuality) { throw UnimplementedError(); }
+
+  /// Converts a color filter to an image filter.
+  _ImageFilter.fromColorFilter(ColorFilter filter)
+    : assert(filter != null), // ignore: unnecessary_null_comparison
+      creator = filter {    // ignore: prefer_initializing_formals
+    _constructor();
+    final _ColorFilter? nativeFilter = filter._toNativeColorFilter();
+    _initColorFilter(nativeFilter);
+  }
+  void _initColorFilter(_ColorFilter? colorFilter) { throw UnimplementedError(); }
+
+  /// Composes `_innerFilter` with `_outerFilter`.
+  _ImageFilter.composed(_ComposeImageFilter filter)
+    : assert(filter != null), // ignore: unnecessary_null_comparison
+      creator = filter {    // ignore: prefer_initializing_formals
+    _constructor();
+    final _ImageFilter nativeFilterInner = filter.innerFilter._toNativeImageFilter();
+    final _ImageFilter nativeFilterOuter = filter.outerFilter._toNativeImageFilter();
+    _initComposed(nativeFilterOuter,  nativeFilterInner);
+  }
+  void _initComposed(_ImageFilter outerFilter, _ImageFilter innerFilter) { throw UnimplementedError(); }
+  /// The original Dart object that created the native wrapper, which retains
+  /// the values used for the filter.
+  final ImageFilter creator;
+}
+
+/// Base class for objects such as [Gradient] and [ImageShader] which
+/// correspond to shaders as used by [Paint.shader].
+class Shader extends NativeFieldWrapperClass2 {
+  /// This class is created by the engine, and should not be instantiated
+  /// or extended directly.
+  @pragma('vm:entry-point')
+  Shader._();
+}
+
+/// Defines what happens at the edge of a gradient or the sampling of a source image
+/// in an [ImageFilter].
+///
+/// A gradient is defined along a finite inner area. In the case of a linear
+/// gradient, it's between the parallel lines that are orthogonal to the line
+/// drawn between two points. In the case of radial gradients, it's the disc
+/// that covers the circle centered on a particular point up to a given radius.
+///
+/// An image filter reads source samples from a source image and performs operations
+/// on those samples to produce a result image. An image defines color samples only
+/// for pixels within the bounds of the image but some filter operations, such as a blur
+/// filter, read samples over a wide area to compute the output for a given pixel. Such
+/// a filter would need to combine samples from inside the image with hypothetical
+/// color values from outside the image.
+///
+/// This enum is used to define how the gradient or image filter should treat the regions
+/// outside that defined inner area.
+///
+/// See also:
+///
+///  * [painting.Gradient], the superclass for [LinearGradient] and
+///    [RadialGradient], as used by [BoxDecoration] et al, which works in
+///    relative coordinates and can create a [Shader] representing the gradient
+///    for a particular [Rect] on demand.
+///  * [dart:ui.Gradient], the low-level class used when dealing with the
+///    [Paint.shader] property directly, with its [Gradient.linear] and
+///    [Gradient.radial] constructors.
+///  * [dart:ui.ImageFilter.blur], an ImageFilter that may sometimes need to
+///    read samples from outside an image to combine with the pixels near the
+///    edge of the image.
+// These enum values must be kept in sync with SkTileMode.
+enum TileMode {
+  /// Samples beyond the edge are clamped to the nearest color in the defined inner area.
+  ///
+  /// A gradient will paint all the regions outside the inner area with the
+  /// color at the end of the color stop list closest to that region.
+  ///
+  /// An image filter will substitute the nearest edge pixel for any samples taken from
+  /// outside its source image.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_linear.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_radial.png)
+  clamp,
+
+  /// Samples beyond the edge are repeated from the far end of the defined area.
+  ///
+  /// For a gradient, this technique is as if the stop points from 0.0 to 1.0 were then
+  /// repeated from 1.0 to 2.0, 2.0 to 3.0, and so forth (and for linear gradients, similarly
+  /// from -1.0 to 0.0, -2.0 to -1.0, etc).
+  ///
+  /// An image filter will treat its source image as if it were tiled across the enlarged
+  /// sample space from which it reads, each tile in the same orientation as the base image.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_linear.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_radial.png)
+  repeated,
+
+  /// Samples beyond the edge are mirrored back and forth across the defined area.
+  ///
+  /// For a gradient, this technique is as if the stop points from 0.0 to 1.0 were then
+  /// repeated backwards from 2.0 to 1.0, then forwards from 2.0 to 3.0, then backwards
+  /// again from 4.0 to 3.0, and so forth (and for linear gradients, similarly in the
+  /// negative direction).
+  ///
+  /// An image filter will treat its source image as tiled in an alternating forwards and
+  /// backwards or upwards and downwards direction across the sample space from which
+  /// it is reading.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_linear.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_radial.png)
+  mirror,
+
+  /// Samples beyond the edge are treated as transparent black.
+  ///
+  /// A gradient will render transparency over any region that is outside the circle of a
+  /// radial gradient or outside the parallel lines that define the inner area of a linear
+  /// gradient.
+  ///
+  /// An image filter will substitute transparent black for any sample it must read from
+  /// outside its source image.
+  decal,
+}
+
+Int32List _encodeColorList(List<Color> colors) {
+  final int colorCount = colors.length;
+  final Int32List result = Int32List(colorCount);
+  for (int i = 0; i < colorCount; ++i)
+    result[i] = colors[i].value;
+  return result;
+}
+
+Float32List _encodePointList(List<Offset> points) {
+  assert(points != null); // ignore: unnecessary_null_comparison
+  final int pointCount = points.length;
+  final Float32List result = Float32List(pointCount * 2);
+  for (int i = 0; i < pointCount; ++i) {
+    final int xIndex = i * 2;
+    final int yIndex = xIndex + 1;
+    final Offset point = points[i];
+    assert(_offsetIsValid(point));
+    result[xIndex] = point.dx;
+    result[yIndex] = point.dy;
+  }
+  return result;
+}
+
+Float32List _encodeTwoPoints(Offset pointA, Offset pointB) {
+  assert(_offsetIsValid(pointA));
+  assert(_offsetIsValid(pointB));
+  final Float32List result = Float32List(4);
+  result[0] = pointA.dx;
+  result[1] = pointA.dy;
+  result[2] = pointB.dx;
+  result[3] = pointB.dy;
+  return result;
+}
+
+/// A shader (as used by [Paint.shader]) that renders a color gradient.
+///
+/// There are several types of gradients, represented by the various constructors
+/// on this class.
+///
+/// See also:
+///
+///  * [Gradient](https://api.flutter.dev/flutter/painting/Gradient-class.html), the class in the [painting] library.
+///
+class Gradient extends Shader {
+
+  void _constructor() { throw UnimplementedError(); }
+
+  /// Creates a linear gradient from `from` to `to`.
+  ///
+  /// If `colorStops` is provided, `colorStops[i]` is a number from 0.0 to 1.0
+  /// that specifies where `color[i]` begins in the gradient. If `colorStops` is
+  /// not provided, then only two stops, at 0.0 and 1.0, are implied (and
+  /// `color` must therefore only have two entries).
+  ///
+  /// The behavior before `from` and after `to` is described by the `tileMode`
+  /// argument. For details, see the [TileMode] enum.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_linear.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_linear.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_linear.png)
+  ///
+  /// If `from`, `to`, `colors`, or `tileMode` are null, or if `colors` or
+  /// `colorStops` contain null values, this constructor will throw a
+  /// [NoSuchMethodError].
+  ///
+  /// If `matrix4` is provided, the gradient fill will be transformed by the
+  /// specified 4x4 matrix relative to the local coordinate system. `matrix4` must
+  /// be a column-major matrix packed into a list of 16 values.
+  Gradient.linear(
+    Offset from,
+    Offset to,
+    List<Color> colors, [
+    List<double>? colorStops,
+    TileMode tileMode = TileMode.clamp,
+    Float64List? matrix4,
+  ]) : assert(_offsetIsValid(from)),
+       assert(_offsetIsValid(to)),
+       assert(colors != null), // ignore: unnecessary_null_comparison
+       assert(tileMode != null), // ignore: unnecessary_null_comparison
+       assert(matrix4 == null || _matrix4IsValid(matrix4)), // ignore: unnecessary_null_comparison
+       super._() {
+    _validateColorStops(colors, colorStops);
+    final Float32List endPointsBuffer = _encodeTwoPoints(from, to);
+    final Int32List colorsBuffer = _encodeColorList(colors);
+    final Float32List? colorStopsBuffer = colorStops == null ? null : Float32List.fromList(colorStops);
+    _constructor();
+    _initLinear(endPointsBuffer, colorsBuffer, colorStopsBuffer, tileMode.index, matrix4);
+  }
+  void _initLinear(Float32List endPoints, Int32List colors, Float32List? colorStops, int tileMode, Float64List? matrix4) { throw UnimplementedError(); }
+
+  /// Creates a radial gradient centered at `center` that ends at `radius`
+  /// distance from the center.
+  ///
+  /// If `colorStops` is provided, `colorStops[i]` is a number from 0.0 to 1.0
+  /// that specifies where `color[i]` begins in the gradient. If `colorStops` is
+  /// not provided, then only two stops, at 0.0 and 1.0, are implied (and
+  /// `color` must therefore only have two entries).
+  ///
+  /// The behavior before and after the radius is described by the `tileMode`
+  /// argument. For details, see the [TileMode] enum.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_radial.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_radial.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_radial.png)
+  ///
+  /// If `center`, `radius`, `colors`, or `tileMode` are null, or if `colors` or
+  /// `colorStops` contain null values, this constructor will throw a
+  /// [NoSuchMethodError].
+  ///
+  /// If `matrix4` is provided, the gradient fill will be transformed by the
+  /// specified 4x4 matrix relative to the local coordinate system. `matrix4` must
+  /// be a column-major matrix packed into a list of 16 values.
+  ///
+  /// If `focal` is provided and not equal to `center` and `focalRadius` is
+  /// provided and not equal to 0.0, the generated shader will be a two point
+  /// conical radial gradient, with `focal` being the center of the focal
+  /// circle and `focalRadius` being the radius of that circle. If `focal` is
+  /// provided and not equal to `center`, at least one of the two offsets must
+  /// not be equal to [Offset.zero].
+  Gradient.radial(
+    Offset center,
+    double radius,
+    List<Color> colors, [
+    List<double>? colorStops,
+    TileMode tileMode = TileMode.clamp,
+    Float64List? matrix4,
+    Offset? focal,
+    double focalRadius = 0.0
+  ]) : assert(_offsetIsValid(center)),
+       assert(colors != null), // ignore: unnecessary_null_comparison
+       assert(tileMode != null), // ignore: unnecessary_null_comparison
+       assert(matrix4 == null || _matrix4IsValid(matrix4)),
+       super._() {
+    _validateColorStops(colors, colorStops);
+    final Int32List colorsBuffer = _encodeColorList(colors);
+    final Float32List? colorStopsBuffer = colorStops == null ? null : Float32List.fromList(colorStops);
+
+    // If focal is null or focal radius is null, this should be treated as a regular radial gradient
+    // If focal == center and the focal radius is 0.0, it's still a regular radial gradient
+    if (focal == null || (focal == center && focalRadius == 0.0)) {
+      _constructor();
+      _initRadial(center.dx, center.dy, radius, colorsBuffer, colorStopsBuffer, tileMode.index, matrix4);
+    } else {
+      assert(center != Offset.zero || focal != Offset.zero); // will result in exception(s) in Skia side
+      _constructor();
+      _initConical(focal.dx, focal.dy, focalRadius, center.dx, center.dy, radius, colorsBuffer, colorStopsBuffer, tileMode.index, matrix4);
+    }
+  }
+  void _initRadial(double centerX, double centerY, double radius, Int32List colors, Float32List? colorStops, int tileMode, Float64List? matrix4) { throw UnimplementedError(); }
+  void _initConical(double startX, double startY, double startRadius, double endX, double endY, double endRadius, Int32List colors, Float32List? colorStops, int tileMode, Float64List? matrix4) { throw UnimplementedError(); }
+
+  /// Creates a sweep gradient centered at `center` that starts at `startAngle`
+  /// and ends at `endAngle`.
+  ///
+  /// `startAngle` and `endAngle` should be provided in radians, with zero
+  /// radians being the horizontal line to the right of the `center` and with
+  /// positive angles going clockwise around the `center`.
+  ///
+  /// If `colorStops` is provided, `colorStops[i]` is a number from 0.0 to 1.0
+  /// that specifies where `color[i]` begins in the gradient. If `colorStops` is
+  /// not provided, then only two stops, at 0.0 and 1.0, are implied (and
+  /// `color` must therefore only have two entries).
+  ///
+  /// The behavior before `startAngle` and after `endAngle` is described by the
+  /// `tileMode` argument. For details, see the [TileMode] enum.
+  ///
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_sweep.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_sweep.png)
+  /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_sweep.png)
+  ///
+  /// If `center`, `colors`, `tileMode`, `startAngle`, or `endAngle` are null,
+  /// or if `colors` or `colorStops` contain null values, this constructor will
+  /// throw a [NoSuchMethodError].
+  ///
+  /// If `matrix4` is provided, the gradient fill will be transformed by the
+  /// specified 4x4 matrix relative to the local coordinate system. `matrix4` must
+  /// be a column-major matrix packed into a list of 16 values.
+  Gradient.sweep(
+    Offset center,
+    List<Color> colors, [
+    List<double>? colorStops,
+    TileMode tileMode = TileMode.clamp,
+    double startAngle = 0.0,
+    double endAngle = math.pi * 2,
+    Float64List? matrix4,
+  ]) : assert(_offsetIsValid(center)),
+       assert(colors != null), // ignore: unnecessary_null_comparison
+       assert(tileMode != null), // ignore: unnecessary_null_comparison
+       assert(startAngle != null), // ignore: unnecessary_null_comparison
+       assert(endAngle != null), // ignore: unnecessary_null_comparison
+       assert(startAngle < endAngle),
+       assert(matrix4 == null || _matrix4IsValid(matrix4)),
+       super._() {
+    _validateColorStops(colors, colorStops);
+    final Int32List colorsBuffer = _encodeColorList(colors);
+    final Float32List? colorStopsBuffer = colorStops == null ? null : Float32List.fromList(colorStops);
+    _constructor();
+    _initSweep(center.dx, center.dy, colorsBuffer, colorStopsBuffer, tileMode.index, startAngle, endAngle, matrix4);
+  }
+  void _initSweep(double centerX, double centerY, Int32List colors, Float32List? colorStops, int tileMode, double startAngle, double endAngle, Float64List? matrix) { throw UnimplementedError(); }
+
+  static void _validateColorStops(List<Color> colors, List<double>? colorStops) {
+    if (colorStops == null) {
+      if (colors.length != 2)
+        throw ArgumentError('"colors" must have length 2 if "colorStops" is omitted.');
+    } else {
+      if (colors.length != colorStops.length)
+        throw ArgumentError('"colors" and "colorStops" arguments must have equal length.');
+    }
+  }
+}
+
+/// A shader (as used by [Paint.shader]) that tiles an image.
+class ImageShader extends Shader {
+  /// Creates an image-tiling shader. The first argument specifies the image to
+  /// tile. The second and third arguments specify the [TileMode] for the x
+  /// direction and y direction respectively. The fourth argument gives the
+  /// matrix to apply to the effect. All the arguments are required and must not
+  /// be null.
+  @pragma('vm:entry-point')
+  ImageShader(Image image, TileMode tmx, TileMode tmy, Float64List matrix4) :
+    // ignore: unnecessary_null_comparison
+    assert(image != null), // image is checked on the engine side
+    assert(tmx != null), // ignore: unnecessary_null_comparison
+    assert(tmy != null), // ignore: unnecessary_null_comparison
+    assert(matrix4 != null), // ignore: unnecessary_null_comparison
+    super._() {
+    if (matrix4.length != 16)
+      throw ArgumentError('"matrix4" must have 16 entries.');
+    _constructor();
+    _initWithImage(image._image, tmx.index, tmy.index, matrix4);
+  }
+  void _constructor() { throw UnimplementedError(); }
+  void _initWithImage(_Image image, int tmx, int tmy, Float64List matrix4) { throw UnimplementedError(); }
+}
+
+/// Defines how a list of points is interpreted when drawing a set of triangles.
+///
+/// Used by [Canvas.drawVertices].
+// These enum values must be kept in sync with SkVertices::VertexMode.
+enum VertexMode {
+  /// Draw each sequence of three points as the vertices of a triangle.
+  triangles,
+
+  /// Draw each sliding window of three points as the vertices of a triangle.
+  triangleStrip,
+
+  /// Draw the first point and each sliding window of two points as the vertices of a triangle.
+  triangleFan,
+}
+
+/// A set of vertex data used by [Canvas.drawVertices].
+class Vertices extends NativeFieldWrapperClass2 {
+  /// Creates a set of vertex data for use with [Canvas.drawVertices].
+  ///
+  /// The [mode] and [positions] parameters must not be null.
+  ///
+  /// If the [textureCoordinates] or [colors] parameters are provided, they must
+  /// be the same length as [positions].
+  ///
+  /// If the [indices] parameter is provided, all values in the list must be
+  /// valid index values for [positions].
+  Vertices(
+    VertexMode mode,
+    List<Offset> positions, {
+    List<Offset>? textureCoordinates,
+    List<Color>? colors,
+    List<int>? indices,
+  }) : assert(mode != null), // ignore: unnecessary_null_comparison
+       assert(positions != null) { // ignore: unnecessary_null_comparison
+    if (textureCoordinates != null && textureCoordinates.length != positions.length)
+      throw ArgumentError('"positions" and "textureCoordinates" lengths must match.');
+    if (colors != null && colors.length != positions.length)
+      throw ArgumentError('"positions" and "colors" lengths must match.');
+    if (indices != null && indices.any((int i) => i < 0 || i >= positions.length))
+      throw ArgumentError('"indices" values must be valid indices in the positions list.');
+
+    final Float32List encodedPositions = _encodePointList(positions);
+    final Float32List? encodedTextureCoordinates = (textureCoordinates != null)
+      ? _encodePointList(textureCoordinates)
+      : null;
+    final Int32List? encodedColors = colors != null
+      ? _encodeColorList(colors)
+      : null;
+    final Uint16List? encodedIndices = indices != null
+      ? Uint16List.fromList(indices)
+      : null;
+
+    if (!_init(this, mode.index, encodedPositions, encodedTextureCoordinates, encodedColors, encodedIndices))
+      throw ArgumentError('Invalid configuration for vertices.');
+  }
+
+  /// Creates a set of vertex data for use with [Canvas.drawVertices], directly
+  /// using the encoding methods of [new Vertices].
+  ///
+  /// The [mode] parameter must not be null.
+  ///
+  /// The [positions] list is interpreted as a list of repeated pairs of x,y
+  /// coordinates. It must not be null.
+  ///
+  /// The [textureCoordinates] list is interpreted as a list of repeated pairs
+  /// of x,y coordinates, and must be the same length of [positions] if it
+  /// is not null.
+  ///
+  /// The [colors] list is interpreted as a list of RGBA encoded colors, similar
+  /// to [Color.value]. It must be half length of [positions] if it is not
+  /// null.
+  ///
+  /// If the [indices] list is provided, all values in the list must be
+  /// valid index values for [positions].
+  Vertices.raw(
+    VertexMode mode,
+    Float32List positions, {
+    Float32List? textureCoordinates,
+    Int32List? colors,
+    Uint16List? indices,
+  }) : assert(mode != null), // ignore: unnecessary_null_comparison
+       assert(positions != null) { // ignore: unnecessary_null_comparison
+    if (textureCoordinates != null && textureCoordinates.length != positions.length)
+      throw ArgumentError('"positions" and "textureCoordinates" lengths must match.');
+    if (colors != null && colors.length * 2 != positions.length)
+      throw ArgumentError('"positions" and "colors" lengths must match.');
+    if (indices != null && indices.any((int i) => i < 0 || i >= positions.length))
+      throw ArgumentError('"indices" values must be valid indices in the positions list.');
+
+    if (!_init(this, mode.index, positions, textureCoordinates, colors, indices))
+      throw ArgumentError('Invalid configuration for vertices.');
+  }
+
+  bool _init(Vertices outVertices,
+             int mode,
+             Float32List positions,
+             Float32List? textureCoordinates,
+             Int32List? colors,
+             Uint16List? indices) { throw UnimplementedError(); }
+}
+
+/// Defines how a list of points is interpreted when drawing a set of points.
+///
+// ignore: deprecated_member_use
+/// Used by [Canvas.drawPoints].
+// These enum values must be kept in sync with SkCanvas::PointMode.
+enum PointMode {
+  /// Draw each point separately.
+  ///
+  /// If the [Paint.strokeCap] is [StrokeCap.round], then each point is drawn
+  /// as a circle with the diameter of the [Paint.strokeWidth], filled as
+  /// described by the [Paint] (ignoring [Paint.style]).
+  ///
+  /// Otherwise, each point is drawn as an axis-aligned square with sides of
+  /// length [Paint.strokeWidth], filled as described by the [Paint] (ignoring
+  /// [Paint.style]).
+  points,
+
+  /// Draw each sequence of two points as a line segment.
+  ///
+  /// If the number of points is odd, then the last point is ignored.
+  ///
+  /// The lines are stroked as described by the [Paint] (ignoring
+  /// [Paint.style]).
+  lines,
+
+  /// Draw the entire sequence of point as one line.
+  ///
+  /// The lines are stroked as described by the [Paint] (ignoring
+  /// [Paint.style]).
+  polygon,
+}
+
+/// Defines how a new clip region should be merged with the existing clip
+/// region.
+///
+/// Used by [Canvas.clipRect].
+enum ClipOp {
+  /// Subtract the new region from the existing region.
+  difference,
+
+  /// Intersect the new region from the existing region.
+  intersect,
+}
+
+/// An interface for recording graphical operations.
+///
+/// [Canvas] objects are used in creating [Picture] objects, which can
+/// themselves be used with a [SceneBuilder] to build a [Scene]. In
+/// normal usage, however, this is all handled by the framework.
+///
+/// A canvas has a current transformation matrix which is applied to all
+/// operations. Initially, the transformation matrix is the identity transform.
+/// It can be modified using the [translate], [scale], [rotate], [skew],
+/// and [transform] methods.
+///
+/// A canvas also has a current clip region which is applied to all operations.
+/// Initially, the clip region is infinite. It can be modified using the
+/// [clipRect], [clipRRect], and [clipPath] methods.
+///
+/// The current transform and clip can be saved and restored using the stack
+/// managed by the [save], [saveLayer], and [restore] methods.
+class Canvas extends NativeFieldWrapperClass2 {
+  /// Creates a canvas for recording graphical operations into the
+  /// given picture recorder.
+  ///
+  /// Graphical operations that affect pixels entirely outside the given
+  /// `cullRect` might be discarded by the implementation. However, the
+  /// implementation might draw outside these bounds if, for example, a command
+  /// draws partially inside and outside the `cullRect`. To ensure that pixels
+  /// outside a given region are discarded, consider using a [clipRect]. The
+  /// `cullRect` is optional; by default, all operations are kept.
+  ///
+  /// To end the recording, call [PictureRecorder.endRecording] on the
+  /// given recorder.
+  @pragma('vm:entry-point')
+  Canvas(PictureRecorder recorder, [ Rect? cullRect ]) : assert(recorder != null) { // ignore: unnecessary_null_comparison
+    if (recorder.isRecording)
+      throw ArgumentError('"recorder" must not already be associated with another Canvas.');
+    _recorder = recorder;
+    _recorder!._canvas = this;
+    cullRect ??= Rect.largest;
+    _constructor(recorder, cullRect.left, cullRect.top, cullRect.right, cullRect.bottom);
+  }
+  void _constructor(PictureRecorder recorder,
+                    double left,
+                    double top,
+                    double right,
+                    double bottom) { throw UnimplementedError(); }
+
+  // The underlying Skia SkCanvas is owned by the PictureRecorder used to create this Canvas.
+  // The Canvas holds a reference to the PictureRecorder to prevent the recorder from being
+  // garbage collected until PictureRecorder.endRecording is called.
+  PictureRecorder? _recorder;
+
+  /// Saves a copy of the current transform and clip on the save stack.
+  ///
+  /// Call [restore] to pop the save stack.
+  ///
+  /// See also:
+  ///
+  ///  * [saveLayer], which does the same thing but additionally also groups the
+  ///    commands done until the matching [restore].
+  void save() { throw UnimplementedError(); }
+
+  /// Saves a copy of the current transform and clip on the save stack, and then
+  /// creates a new group which subsequent calls will become a part of. When the
+  /// save stack is later popped, the group will be flattened into a layer and
+  /// have the given `paint`'s [Paint.colorFilter] and [Paint.blendMode]
+  /// applied.
+  ///
+  /// This lets you create composite effects, for example making a group of
+  /// drawing commands semi-transparent. Without using [saveLayer], each part of
+  /// the group would be painted individually, so where they overlap would be
+  /// darker than where they do not. By using [saveLayer] to group them
+  /// together, they can be drawn with an opaque color at first, and then the
+  /// entire group can be made transparent using the [saveLayer]'s paint.
+  ///
+  /// Call [restore] to pop the save stack and apply the paint to the group.
+  ///
+  /// ## Using saveLayer with clips
+  ///
+  /// When a rectangular clip operation (from [clipRect]) is not axis-aligned
+  /// with the raster buffer, or when the clip operation is not rectilinear
+  /// (e.g. because it is a rounded rectangle clip created by [clipRRect] or an
+  /// arbitrarily complicated path clip created by [clipPath]), the edge of the
+  /// clip needs to be anti-aliased.
+  ///
+  /// If two draw calls overlap at the edge of such a clipped region, without
+  /// using [saveLayer], the first drawing will be anti-aliased with the
+  /// background first, and then the second will be anti-aliased with the result
+  /// of blending the first drawing and the background. On the other hand, if
+  /// [saveLayer] is used immediately after establishing the clip, the second
+  /// drawing will cover the first in the layer, and thus the second alone will
+  /// be anti-aliased with the background when the layer is clipped and
+  /// composited (when [restore] is called).
+  ///
+  /// For example, this [CustomPainter.paint] method paints a clean white
+  /// rounded rectangle:
+  ///
+  /// ```dart
+  /// void paint(Canvas canvas, Size size) {
+  ///   Rect rect = Offset.zero & size;
+  ///   canvas.save();
+  ///   canvas.clipRRect(new RRect.fromRectXY(rect, 100.0, 100.0));
+  ///   canvas.saveLayer(rect, Paint());
+  ///   canvas.drawPaint(new Paint()..color = Colors.red);
+  ///   canvas.drawPaint(new Paint()..color = Colors.white);
+  ///   canvas.restore();
+  ///   canvas.restore();
+  /// }
+  /// ```
+  ///
+  /// On the other hand, this one renders a red outline, the result of the red
+  /// paint being anti-aliased with the background at the clip edge, then the
+  /// white paint being similarly anti-aliased with the background _including
+  /// the clipped red paint_:
+  ///
+  /// ```dart
+  /// void paint(Canvas canvas, Size size) {
+  ///   // (this example renders poorly, prefer the example above)
+  ///   Rect rect = Offset.zero & size;
+  ///   canvas.save();
+  ///   canvas.clipRRect(new RRect.fromRectXY(rect, 100.0, 100.0));
+  ///   canvas.drawPaint(new Paint()..color = Colors.red);
+  ///   canvas.drawPaint(new Paint()..color = Colors.white);
+  ///   canvas.restore();
+  /// }
+  /// ```
+  ///
+  /// This point is moot if the clip only clips one draw operation. For example,
+  /// the following paint method paints a pair of clean white rounded
+  /// rectangles, even though the clips are not done on a separate layer:
+  ///
+  /// ```dart
+  /// void paint(Canvas canvas, Size size) {
+  ///   canvas.save();
+  ///   canvas.clipRRect(new RRect.fromRectXY(Offset.zero & (size / 2.0), 50.0, 50.0));
+  ///   canvas.drawPaint(new Paint()..color = Colors.white);
+  ///   canvas.restore();
+  ///   canvas.save();
+  ///   canvas.clipRRect(new RRect.fromRectXY(size.center(Offset.zero) & (size / 2.0), 50.0, 50.0));
+  ///   canvas.drawPaint(new Paint()..color = Colors.white);
+  ///   canvas.restore();
+  /// }
+  /// ```
+  ///
+  /// (Incidentally, rather than using [clipRRect] and [drawPaint] to draw
+  /// rounded rectangles like this, prefer the [drawRRect] method. These
+  /// examples are using [drawPaint] as a proxy for "complicated draw operations
+  /// that will get clipped", to illustrate the point.)
+  ///
+  /// ## Performance considerations
+  ///
+  /// Generally speaking, [saveLayer] is relatively expensive.
+  ///
+  /// There are a several different hardware architectures for GPUs (graphics
+  /// processing units, the hardware that handles graphics), but most of them
+  /// involve batching commands and reordering them for performance. When layers
+  /// are used, they cause the rendering pipeline to have to switch render
+  /// target (from one layer to another). Render target switches can flush the
+  /// GPU's command buffer, which typically means that optimizations that one
+  /// could get with larger batching are lost. Render target switches also
+  /// generate a lot of memory churn because the GPU needs to copy out the
+  /// current frame buffer contents from the part of memory that's optimized for
+  /// writing, and then needs to copy it back in once the previous render target
+  /// (layer) is restored.
+  ///
+  /// See also:
+  ///
+  ///  * [save], which saves the current state, but does not create a new layer
+  ///    for subsequent commands.
+  ///  * [BlendMode], which discusses the use of [Paint.blendMode] with
+  ///    [saveLayer].
+  void saveLayer(Rect? bounds, Paint paint) {
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    if (bounds == null) {
+      _saveLayerWithoutBounds(paint._objects, paint._data);
+    } else {
+      assert(_rectIsValid(bounds));
+      _saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom,
+                 paint._objects, paint._data);
+    }
+  }
+  void _saveLayerWithoutBounds(List<dynamic>? paintObjects, ByteData paintData)
+      { throw UnimplementedError(); }
+  void _saveLayer(double left,
+                  double top,
+                  double right,
+                  double bottom,
+                  List<dynamic>? paintObjects,
+                  ByteData paintData) { throw UnimplementedError(); }
+
+  /// Pops the current save stack, if there is anything to pop.
+  /// Otherwise, does nothing.
+  ///
+  /// Use [save] and [saveLayer] to push state onto the stack.
+  ///
+  /// If the state was pushed with with [saveLayer], then this call will also
+  /// cause the new layer to be composited into the previous layer.
+  void restore() { throw UnimplementedError(); }
+
+  /// Returns the number of items on the save stack, including the
+  /// initial state. This means it returns 1 for a clean canvas, and
+  /// that each call to [save] and [saveLayer] increments it, and that
+  /// each matching call to [restore] decrements it.
+  ///
+  /// This number cannot go below 1.
+  int getSaveCount() { throw UnimplementedError(); }
+
+  /// Add a translation to the current transform, shifting the coordinate space
+  /// horizontally by the first argument and vertically by the second argument.
+  void translate(double dx, double dy) { throw UnimplementedError(); }
+
+  /// Add an axis-aligned scale to the current transform, scaling by the first
+  /// argument in the horizontal direction and the second in the vertical
+  /// direction.
+  ///
+  /// If [sy] is unspecified, [sx] will be used for the scale in both
+  /// directions.
+  void scale(double sx, [double? sy]) => _scale(sx, sy ?? sx);
+
+  void _scale(double sx, double sy) { throw UnimplementedError(); }
+
+  /// Add a rotation to the current transform. The argument is in radians clockwise.
+  void rotate(double radians) { throw UnimplementedError(); }
+
+  /// Add an axis-aligned skew to the current transform, with the first argument
+  /// being the horizontal skew in rise over run units clockwise around the
+  /// origin, and the second argument being the vertical skew in rise over run
+  /// units clockwise around the origin.
+  void skew(double sx, double sy) { throw UnimplementedError(); }
+
+  /// Multiply the current transform by the specified 4⨉4 transformation matrix
+  /// specified as a list of values in column-major order.
+  void transform(Float64List matrix4) {
+    assert(matrix4 != null); // ignore: unnecessary_null_comparison
+    if (matrix4.length != 16)
+      throw ArgumentError('"matrix4" must have 16 entries.');
+    _transform(matrix4);
+  }
+  void _transform(Float64List matrix4) { throw UnimplementedError(); }
+
+  /// Reduces the clip region to the intersection of the current clip and the
+  /// given rectangle.
+  ///
+  /// If [doAntiAlias] is true, then the clip will be anti-aliased.
+  ///
+  /// If multiple draw commands intersect with the clip boundary, this can result
+  /// in incorrect blending at the clip boundary. See [saveLayer] for a
+  /// discussion of how to address that.
+  ///
+  /// Use [ClipOp.difference] to subtract the provided rectangle from the
+  /// current clip.
+  void clipRect(Rect rect, { ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = true }) {
+    assert(_rectIsValid(rect));
+    assert(clipOp != null); // ignore: unnecessary_null_comparison
+    assert(doAntiAlias != null); // ignore: unnecessary_null_comparison
+    _clipRect(rect.left, rect.top, rect.right, rect.bottom, clipOp.index, doAntiAlias);
+  }
+  void _clipRect(double left,
+                 double top,
+                 double right,
+                 double bottom,
+                 int clipOp,
+                 bool doAntiAlias) { throw UnimplementedError(); }
+
+  /// Reduces the clip region to the intersection of the current clip and the
+  /// given rounded rectangle.
+  ///
+  /// If [doAntiAlias] is true, then the clip will be anti-aliased.
+  ///
+  /// If multiple draw commands intersect with the clip boundary, this can result
+  /// in incorrect blending at the clip boundary. See [saveLayer] for a
+  /// discussion of how to address that and some examples of using [clipRRect].
+  void clipRRect(RRect rrect, {bool doAntiAlias = true}) {
+    assert(_rrectIsValid(rrect));
+    assert(doAntiAlias != null); // ignore: unnecessary_null_comparison
+    _clipRRect(rrect._value32, doAntiAlias);
+  }
+  void _clipRRect(Float32List rrect, bool doAntiAlias) { throw UnimplementedError(); }
+
+  /// Reduces the clip region to the intersection of the current clip and the
+  /// given [Path].
+  ///
+  /// If [doAntiAlias] is true, then the clip will be anti-aliased.
+  ///
+  /// If multiple draw commands intersect with the clip boundary, this can result
+  /// multiple draw commands intersect with the clip boundary, this can result
+  /// in incorrect blending at the clip boundary. See [saveLayer] for a
+  /// discussion of how to address that.
+  void clipPath(Path path, {bool doAntiAlias = true}) {
+    // ignore: unnecessary_null_comparison
+    assert(path != null); // path is checked on the engine side
+    assert(doAntiAlias != null); // ignore: unnecessary_null_comparison
+    _clipPath(path, doAntiAlias);
+  }
+  void _clipPath(Path path, bool doAntiAlias) { throw UnimplementedError(); }
+
+  /// Paints the given [Color] onto the canvas, applying the given
+  /// [BlendMode], with the given color being the source and the background
+  /// being the destination.
+  void drawColor(Color color, BlendMode blendMode) {
+    assert(color != null); // ignore: unnecessary_null_comparison
+    assert(blendMode != null); // ignore: unnecessary_null_comparison
+    _drawColor(color.value, blendMode.index);
+  }
+  void _drawColor(int color, int blendMode) { throw UnimplementedError(); }
+
+  /// Draws a line between the given points using the given paint. The line is
+  /// stroked, the value of the [Paint.style] is ignored for this call.
+  ///
+  /// The `p1` and `p2` arguments are interpreted as offsets from the origin.
+  void drawLine(Offset p1, Offset p2, Paint paint) {
+    assert(_offsetIsValid(p1));
+    assert(_offsetIsValid(p2));
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    _drawLine(p1.dx, p1.dy, p2.dx, p2.dy, paint._objects, paint._data);
+  }
+  void _drawLine(double x1,
+                 double y1,
+                 double x2,
+                 double y2,
+                 List<dynamic>? paintObjects,
+                 ByteData paintData) { throw UnimplementedError(); }
+
+  /// Fills the canvas with the given [Paint].
+  ///
+  /// To fill the canvas with a solid color and blend mode, consider
+  /// [drawColor] instead.
+  void drawPaint(Paint paint) {
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    _drawPaint(paint._objects, paint._data);
+  }
+  void _drawPaint(List<dynamic>? paintObjects, ByteData paintData) { throw UnimplementedError(); }
+
+  /// Draws a rectangle with the given [Paint]. Whether the rectangle is filled
+  /// or stroked (or both) is controlled by [Paint.style].
+  void drawRect(Rect rect, Paint paint) {
+    assert(_rectIsValid(rect));
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    _drawRect(rect.left, rect.top, rect.right, rect.bottom,
+              paint._objects, paint._data);
+  }
+  void _drawRect(double left,
+                 double top,
+                 double right,
+                 double bottom,
+                 List<dynamic>? paintObjects,
+                 ByteData paintData) { throw UnimplementedError(); }
+
+  /// Draws a rounded rectangle with the given [Paint]. Whether the rectangle is
+  /// filled or stroked (or both) is controlled by [Paint.style].
+  void drawRRect(RRect rrect, Paint paint) {
+    assert(_rrectIsValid(rrect));
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    _drawRRect(rrect._value32, paint._objects, paint._data);
+  }
+  void _drawRRect(Float32List rrect,
+                  List<dynamic>? paintObjects,
+                  ByteData paintData) { throw UnimplementedError(); }
+
+  /// Draws a shape consisting of the difference between two rounded rectangles
+  /// with the given [Paint]. Whether this shape is filled or stroked (or both)
+  /// is controlled by [Paint.style].
+  ///
+  /// This shape is almost but not quite entirely unlike an annulus.
+  void drawDRRect(RRect outer, RRect inner, Paint paint) {
+    assert(_rrectIsValid(outer));
+    assert(_rrectIsValid(inner));
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    _drawDRRect(outer._value32, inner._value32, paint._objects, paint._data);
+  }
+  void _drawDRRect(Float32List outer,
+                   Float32List inner,
+                   List<dynamic>? paintObjects,
+                   ByteData paintData) { throw UnimplementedError(); }
+
+  /// Draws an axis-aligned oval that fills the given axis-aligned rectangle
+  /// with the given [Paint]. Whether the oval is filled or stroked (or both) is
+  /// controlled by [Paint.style].
+  void drawOval(Rect rect, Paint paint) {
+    assert(_rectIsValid(rect));
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    _drawOval(rect.left, rect.top, rect.right, rect.bottom,
+              paint._objects, paint._data);
+  }
+  void _drawOval(double left,
+                 double top,
+                 double right,
+                 double bottom,
+                 List<dynamic>? paintObjects,
+                 ByteData paintData) { throw UnimplementedError(); }
+
+  /// Draws a circle centered at the point given by the first argument and
+  /// that has the radius given by the second argument, with the [Paint] given in
+  /// the third argument. Whether the circle is filled or stroked (or both) is
+  /// controlled by [Paint.style].
+  void drawCircle(Offset c, double radius, Paint paint) {
+    assert(_offsetIsValid(c));
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    _drawCircle(c.dx, c.dy, radius, paint._objects, paint._data);
+  }
+  void _drawCircle(double x,
+                   double y,
+                   double radius,
+                   List<dynamic>? paintObjects,
+                   ByteData paintData) { throw UnimplementedError(); }
+
+  /// Draw an arc scaled to fit inside the given rectangle.
+  ///
+  /// It starts from `startAngle` radians around the oval up to
+  /// `startAngle` + `sweepAngle` radians around the oval, with zero radians
+  /// being the point on the right hand side of the oval that crosses the
+  /// horizontal line that intersects the center of the rectangle and with positive
+  /// angles going clockwise around the oval. If `useCenter` is true, the arc is
+  /// closed back to the center, forming a circle sector. Otherwise, the arc is
+  /// not closed, forming a circle segment.
+  ///
+  /// This method is optimized for drawing arcs and should be faster than [Path.arcTo].
+  void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint) {
+    assert(_rectIsValid(rect));
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    _drawArc(rect.left, rect.top, rect.right, rect.bottom, startAngle,
+             sweepAngle, useCenter, paint._objects, paint._data);
+  }
+  void _drawArc(double left,
+                double top,
+                double right,
+                double bottom,
+                double startAngle,
+                double sweepAngle,
+                bool useCenter,
+                List<dynamic>? paintObjects,
+                ByteData paintData) { throw UnimplementedError(); }
+
+  /// Draws the given [Path] with the given [Paint].
+  ///
+  /// Whether this shape is filled or stroked (or both) is controlled by
+  /// [Paint.style]. If the path is filled, then sub-paths within it are
+  /// implicitly closed (see [Path.close]).
+  void drawPath(Path path, Paint paint) {
+    // ignore: unnecessary_null_comparison
+    assert(path != null); // path is checked on the engine side
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    _drawPath(path, paint._objects, paint._data);
+  }
+  void _drawPath(Path path,
+                 List<dynamic>? paintObjects,
+                 ByteData paintData) { throw UnimplementedError(); }
+
+  /// Draws the given [Image] into the canvas with its top-left corner at the
+  /// given [Offset]. The image is composited into the canvas using the given [Paint].
+  void drawImage(Image image, Offset offset, Paint paint) {
+    // ignore: unnecessary_null_comparison
+    assert(image != null); // image is checked on the engine side
+    assert(_offsetIsValid(offset));
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    _drawImage(image._image, offset.dx, offset.dy, paint._objects, paint._data);
+  }
+  void _drawImage(_Image image,
+                  double x,
+                  double y,
+                  List<dynamic>? paintObjects,
+                  ByteData paintData) { throw UnimplementedError(); }
+
+  /// Draws the subset of the given image described by the `src` argument into
+  /// the canvas in the axis-aligned rectangle given by the `dst` argument.
+  ///
+  /// This might sample from outside the `src` rect by up to half the width of
+  /// an applied filter.
+  ///
+  /// Multiple calls to this method with different arguments (from the same
+  /// image) can be batched into a single call to [drawAtlas] to improve
+  /// performance.
+  void drawImageRect(Image image, Rect src, Rect dst, Paint paint) {
+    // ignore: unnecessary_null_comparison
+    assert(image != null); // image is checked on the engine side
+    assert(_rectIsValid(src));
+    assert(_rectIsValid(dst));
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    _drawImageRect(image._image,
+                   src.left,
+                   src.top,
+                   src.right,
+                   src.bottom,
+                   dst.left,
+                   dst.top,
+                   dst.right,
+                   dst.bottom,
+                   paint._objects,
+                   paint._data);
+  }
+  void _drawImageRect(_Image image,
+                      double srcLeft,
+                      double srcTop,
+                      double srcRight,
+                      double srcBottom,
+                      double dstLeft,
+                      double dstTop,
+                      double dstRight,
+                      double dstBottom,
+                      List<dynamic>? paintObjects,
+                      ByteData paintData) { throw UnimplementedError(); }
+
+  /// Draws the given [Image] into the canvas using the given [Paint].
+  ///
+  /// The image is drawn in nine portions described by splitting the image by
+  /// drawing two horizontal lines and two vertical lines, where the `center`
+  /// argument describes the rectangle formed by the four points where these
+  /// four lines intersect each other. (This forms a 3-by-3 grid of regions,
+  /// the center region being described by the `center` argument.)
+  ///
+  /// The four regions in the corners are drawn, without scaling, in the four
+  /// corners of the destination rectangle described by `dst`. The remaining
+  /// five regions are drawn by stretching them to fit such that they exactly
+  /// cover the destination rectangle while maintaining their relative
+  /// positions.
+  void drawImageNine(Image image, Rect center, Rect dst, Paint paint) {
+    // ignore: unnecessary_null_comparison
+    assert(image != null); // image is checked on the engine side
+    assert(_rectIsValid(center));
+    assert(_rectIsValid(dst));
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    _drawImageNine(image._image,
+                   center.left,
+                   center.top,
+                   center.right,
+                   center.bottom,
+                   dst.left,
+                   dst.top,
+                   dst.right,
+                   dst.bottom,
+                   paint._objects,
+                   paint._data);
+  }
+  void _drawImageNine(_Image image,
+                      double centerLeft,
+                      double centerTop,
+                      double centerRight,
+                      double centerBottom,
+                      double dstLeft,
+                      double dstTop,
+                      double dstRight,
+                      double dstBottom,
+                      List<dynamic>? paintObjects,
+                      ByteData paintData) { throw UnimplementedError(); }
+
+  /// Draw the given picture onto the canvas. To create a picture, see
+  /// [PictureRecorder].
+  void drawPicture(Picture picture) {
+    // ignore: unnecessary_null_comparison
+    assert(picture != null); // picture is checked on the engine side
+    _drawPicture(picture);
+  }
+  void _drawPicture(Picture picture) { throw UnimplementedError(); }
+
+  /// Draws the text in the given [Paragraph] into this canvas at the given
+  /// [Offset].
+  ///
+  /// The [Paragraph] object must have had [Paragraph.layout] called on it
+  /// first.
+  ///
+  /// To align the text, set the `textAlign` on the [ParagraphStyle] object
+  /// passed to the [new ParagraphBuilder] constructor. For more details see
+  /// [TextAlign] and the discussion at [new ParagraphStyle].
+  ///
+  /// If the text is left aligned or justified, the left margin will be at the
+  /// position specified by the `offset` argument's [Offset.dx] coordinate.
+  ///
+  /// If the text is right aligned or justified, the right margin will be at the
+  /// position described by adding the [ParagraphConstraints.width] given to
+  /// [Paragraph.layout], to the `offset` argument's [Offset.dx] coordinate.
+  ///
+  /// If the text is centered, the centering axis will be at the position
+  /// described by adding half of the [ParagraphConstraints.width] given to
+  /// [Paragraph.layout], to the `offset` argument's [Offset.dx] coordinate.
+  void drawParagraph(Paragraph paragraph, Offset offset) {
+    assert(paragraph != null); // ignore: unnecessary_null_comparison
+    assert(_offsetIsValid(offset));
+    paragraph._paint(this, offset.dx, offset.dy);
+  }
+
+  /// Draws a sequence of points according to the given [PointMode].
+  ///
+  /// The `points` argument is interpreted as offsets from the origin.
+  ///
+  /// See also:
+  ///
+  ///  * [drawRawPoints], which takes `points` as a [Float32List] rather than a
+  ///    [List<Offset>].
+  void drawPoints(PointMode pointMode, List<Offset> points, Paint paint) {
+    assert(pointMode != null); // ignore: unnecessary_null_comparison
+    assert(points != null); // ignore: unnecessary_null_comparison
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    _drawPoints(paint._objects, paint._data, pointMode.index, _encodePointList(points));
+  }
+
+  /// Draws a sequence of points according to the given [PointMode].
+  ///
+  /// The `points` argument is interpreted as a list of pairs of floating point
+  /// numbers, where each pair represents an x and y offset from the origin.
+  ///
+  /// See also:
+  ///
+  ///  * [drawPoints], which takes `points` as a [List<Offset>] rather than a
+  ///    [List<Float32List>].
+  void drawRawPoints(PointMode pointMode, Float32List points, Paint paint) {
+    assert(pointMode != null); // ignore: unnecessary_null_comparison
+    assert(points != null); // ignore: unnecessary_null_comparison
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    if (points.length % 2 != 0)
+      throw ArgumentError('"points" must have an even number of values.');
+    _drawPoints(paint._objects, paint._data, pointMode.index, points);
+  }
+
+  void _drawPoints(List<dynamic>? paintObjects,
+                   ByteData paintData,
+                   int pointMode,
+                   Float32List points) { throw UnimplementedError(); }
+
+  /// Draws the set of [Vertices] onto the canvas.
+  ///
+  /// All parameters must not be null.
+  ///
+  /// See also:
+  ///   * [new Vertices], which creates a set of vertices to draw on the canvas.
+  ///   * [Vertices.raw], which creates the vertices using typed data lists
+  ///     rather than unencoded lists.
+  void drawVertices(Vertices vertices, BlendMode blendMode, Paint paint) {
+    // ignore: unnecessary_null_comparison
+    assert(vertices != null); // vertices is checked on the engine side
+    assert(paint != null); // ignore: unnecessary_null_comparison
+    assert(blendMode != null); // ignore: unnecessary_null_comparison
+    _drawVertices(vertices, blendMode.index, paint._objects, paint._data);
+  }
+  void _drawVertices(Vertices vertices,
+                     int blendMode,
+                     List<dynamic>? paintObjects,
+                     ByteData paintData) { throw UnimplementedError(); }
+
+  /// Draws many parts of an image - the [atlas] - onto the canvas.
+  ///
+  /// This method allows for optimization when you want to draw many parts of an
+  /// image onto the canvas, such as when using sprites or zooming. It is more efficient
+  /// than using multiple calls to [drawImageRect] and provides more functionality
+  /// to individually transform each image part by a separate rotation or scale and
+  /// blend or modulate those parts with a solid color.
+  ///
+  /// The method takes a list of [Rect] objects that each define a piece of the
+  /// [atlas] image to be drawn independently. Each [Rect] is associated with an
+  /// [RSTransform] entry in the [transforms] list which defines the location,
+  /// rotation, and (uniform) scale with which to draw that portion of the image.
+  /// Each [Rect] can also be associated with an optional [Color] which will be
+  /// composed with the associated image part using the [blendMode] before blending
+  /// the result onto the canvas. The full operation can be broken down as:
+  ///
+  /// - Blend each rectangular portion of the image specified by an entry in the
+  /// [rects] argument with its associated entry in the [colors] list using the
+  /// [blendMode] argument (if a color is specified). In this part of the operation,
+  /// the image part will be considered the source of the operation and the associated
+  /// color will be considered the destination.
+  /// - Blend the result from the first step onto the canvas using the translation,
+  /// rotation, and scale properties expressed in the associated entry in the
+  /// [transforms] list using the properties of the [Paint] object.
+  ///
+  /// If the first stage of the operation which blends each part of the image with
+  /// a color is needed, then both the [colors] and [blendMode] arguments must
+  /// not be null and there must be an entry in the [colors] list for each
+  /// image part. If that stage is not needed, then the [colors] argument can
+  /// be either null or an empty list and the [blendMode] argument may also be null.
+  ///
+  /// The optional [cullRect] argument can provide an estimate of the bounds of the
+  /// coordinates rendered by all components of the atlas to be compared against
+  /// the clip to quickly reject the operation if it does not intersect.
+  ///
+  /// An example usage to render many sprites from a single sprite atlas with no
+  /// rotations or scales:
+  ///
+  /// ```dart
+  /// class Sprite {
+  ///   int index;
+  ///   double centerX;
+  ///   double centerY;
+  /// }
+  ///
+  /// class MyPainter extends CustomPainter {
+  ///   // assume spriteAtlas contains N 10x10 sprites side by side in a (N*10)x10 image
+  ///   ui.Image spriteAtlas;
+  ///   List<Sprite> allSprites;
+  ///
+  ///   @override
+  ///   void paint(Canvas canvas, Size size) {
+  ///     Paint paint = Paint();
+  ///     canvas.drawAtlas(spriteAtlas, <RSTransform>[
+  ///       for (Sprite sprite in allSprites)
+  ///         RSTransform.fromComponents(
+  ///           rotation: 0.0,
+  ///           scale: 1.0,
+  ///           // Center of the sprite relative to its rect
+  ///           anchorX: 5.0,
+  ///           anchorY: 5.0,
+  ///           // Location at which to draw the center of the sprite
+  ///           translateX: sprite.centerX,
+  ///           translateY: sprite.centerY,
+  ///         ),
+  ///     ], <Rect>[
+  ///       for (Sprite sprite in allSprites)
+  ///         Rect.fromLTWH(sprite.index * 10.0, 0.0, 10.0, 10.0),
+  ///     ], null, null, null, paint);
+  ///   }
+  ///
+  ///   ...
+  /// }
+  /// ```
+  ///
+  /// Another example usage which renders sprites with an optional opacity and rotation:
+  ///
+  /// ```dart
+  /// class Sprite {
+  ///   int index;
+  ///   double centerX;
+  ///   double centerY;
+  ///   int alpha;
+  ///   double rotation;
+  /// }
+  ///
+  /// class MyPainter extends CustomPainter {
+  ///   // assume spriteAtlas contains N 10x10 sprites side by side in a (N*10)x10 image
+  ///   ui.Image spriteAtlas;
+  ///   List<Sprite> allSprites;
+  ///
+  ///   @override
+  ///   void paint(Canvas canvas, Size size) {
+  ///     Paint paint = Paint();
+  ///     canvas.drawAtlas(spriteAtlas, <RSTransform>[
+  ///       for (Sprite sprite in allSprites)
+  ///         RSTransform.fromComponents(
+  ///           rotation: sprite.rotation,
+  ///           scale: 1.0,
+  ///           // Center of the sprite relative to its rect
+  ///           anchorX: 5.0,
+  ///           anchorY: 5.0,
+  ///           // Location at which to draw the center of the sprite
+  ///           translateX: sprite.centerX,
+  ///           translateY: sprite.centerY,
+  ///         ),
+  ///     ], <Rect>[
+  ///       for (Sprite sprite in allSprites)
+  ///         Rect.fromLTWH(sprite.index * 10.0, 0.0, 10.0, 10.0),
+  ///     ], <Color>[
+  ///       for (Sprite sprite in allSprites)
+  ///         Colors.white.withAlpha(sprite.alpha),
+  ///     ], BlendMode.srcIn, null, paint);
+  ///   }
+  ///
+  ///   ...
+  /// }
+  /// ```
+  ///
+  /// The length of the [transforms] and [rects] lists must be equal and
+  /// if the [colors] argument is not null then it must either be empty or
+  /// have the same length as the other two lists.
+  ///
+  /// See also:
+  ///
+  ///  * [drawRawAtlas], which takes its arguments as typed data lists rather
+  ///    than objects.
+  void drawAtlas(Image atlas,
+                 List<RSTransform> transforms,
+                 List<Rect> rects,
+                 List<Color>? colors,
+                 BlendMode? blendMode,
+                 Rect? cullRect,
+                 Paint paint) {
+    // ignore: unnecessary_null_comparison
+    assert(atlas != null); // atlas is checked on the engine side
+    assert(transforms != null); // ignore: unnecessary_null_comparison
+    assert(rects != null); // ignore: unnecessary_null_comparison
+    assert(colors == null || colors.isEmpty || blendMode != null);
+    assert(paint != null); // ignore: unnecessary_null_comparison
+
+    final int rectCount = rects.length;
+    if (transforms.length != rectCount)
+      throw ArgumentError('"transforms" and "rects" lengths must match.');
+    if (colors != null && colors.isNotEmpty && colors.length != rectCount)
+      throw ArgumentError('If non-null, "colors" length must match that of "transforms" and "rects".');
+
+    final Float32List rstTransformBuffer = Float32List(rectCount * 4);
+    final Float32List rectBuffer = Float32List(rectCount * 4);
+
+    for (int i = 0; i < rectCount; ++i) {
+      final int index0 = i * 4;
+      final int index1 = index0 + 1;
+      final int index2 = index0 + 2;
+      final int index3 = index0 + 3;
+      final RSTransform rstTransform = transforms[i];
+      final Rect rect = rects[i];
+      assert(_rectIsValid(rect));
+      rstTransformBuffer[index0] = rstTransform.scos;
+      rstTransformBuffer[index1] = rstTransform.ssin;
+      rstTransformBuffer[index2] = rstTransform.tx;
+      rstTransformBuffer[index3] = rstTransform.ty;
+      rectBuffer[index0] = rect.left;
+      rectBuffer[index1] = rect.top;
+      rectBuffer[index2] = rect.right;
+      rectBuffer[index3] = rect.bottom;
+    }
+
+    final Int32List? colorBuffer = (colors == null || colors.isEmpty) ? null : _encodeColorList(colors);
+    final Float32List? cullRectBuffer = cullRect?._value32;
+
+    _drawAtlas(
+      paint._objects, paint._data, atlas._image, rstTransformBuffer, rectBuffer,
+      colorBuffer, (blendMode ?? BlendMode.src).index, cullRectBuffer
+    );
+  }
+
+  /// Draws many parts of an image - the [atlas] - onto the canvas.
+  ///
+  /// This method allows for optimization when you want to draw many parts of an
+  /// image onto the canvas, such as when using sprites or zooming. It is more efficient
+  /// than using multiple calls to [drawImageRect] and provides more functionality
+  /// to individually transform each image part by a separate rotation or scale and
+  /// blend or modulate those parts with a solid color. It is also more efficient
+  /// than [drawAtlas] as the data in the arguments is already packed in a format
+  /// that can be directly used by the rendering code.
+  ///
+  /// A full description of how this method uses its arguments to draw onto the
+  /// canvas can be found in the description of the [drawAtlas] method.
+  ///
+  /// The [rstTransforms] argument is interpreted as a list of four-tuples, with
+  /// each tuple being ([RSTransform.scos], [RSTransform.ssin],
+  /// [RSTransform.tx], [RSTransform.ty]).
+  ///
+  /// The [rects] argument is interpreted as a list of four-tuples, with each
+  /// tuple being ([Rect.left], [Rect.top], [Rect.right], [Rect.bottom]).
+  ///
+  /// The [colors] argument, which can be null, is interpreted as a list of
+  /// 32-bit colors, with the same packing as [Color.value]. If the [colors]
+  /// argument is not null then the [blendMode] argument must also not be null.
+  ///
+  /// An example usage to render many sprites from a single sprite atlas with no rotations
+  /// or scales:
+  ///
+  /// ```dart
+  /// class Sprite {
+  ///   int index;
+  ///   double centerX;
+  ///   double centerY;
+  /// }
+  ///
+  /// class MyPainter extends CustomPainter {
+  ///   // assume spriteAtlas contains N 10x10 sprites side by side in a (N*10)x10 image
+  ///   ui.Image spriteAtlas;
+  ///   List<Sprite> allSprites;
+  ///
+  ///   @override
+  ///   void paint(Canvas canvas, Size size) {
+  ///     // For best advantage, these lists should be cached and only specific
+  ///     // entries updated when the sprite information changes. This code is
+  ///     // illustrative of how to set up the data and not a recommendation for
+  ///     // optimal usage.
+  ///     Float32List rectList = Float32List(allSprites.length * 4);
+  ///     Float32List transformList = Float32List(allSprites.length * 4);
+  ///     for (int i = 0; i < allSprites.length; i++) {
+  ///       final double rectX = sprite.spriteIndex * 10.0;
+  ///       rectList[i * 4 + 0] = rectX;
+  ///       rectList[i * 4 + 1] = 0.0;
+  ///       rectList[i * 4 + 2] = rectX + 10.0;
+  ///       rectList[i * 4 + 3] = 10.0;
+  ///
+  ///       // This example sets the RSTransform values directly for a common case of no
+  ///       // rotations or scales and just a translation to position the atlas entry. For
+  ///       // more complicated transforms one could use the RSTransform class to compute
+  ///       // the necessary values or do the same math directly.
+  ///       transformList[i * 4 + 0] = 1.0;
+  ///       transformList[i * 4 + 1] = 0.0;
+  ///       transformList[i * 4 + 2] = sprite.centerX - 5.0;
+  ///       transformList[i * 4 + 3] = sprite.centerY - 5.0;
+  ///     }
+  ///     Paint paint = Paint();
+  ///     canvas.drawAtlas(spriteAtlas, transformList, rectList, null, null, null, paint);
+  ///   }
+  ///
+  ///   ...
+  /// }
+  /// ```
+  ///
+  /// Another example usage which renders sprites with an optional opacity and rotation:
+  ///
+  /// ```dart
+  /// class Sprite {
+  ///   int index;
+  ///   double centerX;
+  ///   double centerY;
+  ///   int alpha;
+  ///   double rotation;
+  /// }
+  ///
+  /// class MyPainter extends CustomPainter {
+  ///   // assume spriteAtlas contains N 10x10 sprites side by side in a (N*10)x10 image
+  ///   ui.Image spriteAtlas;
+  ///   List<Sprite> allSprites;
+  ///
+  ///   @override
+  ///   void paint(Canvas canvas, Size size) {
+  ///     // For best advantage, these lists should be cached and only specific
+  ///     // entries updated when the sprite information changes. This code is
+  ///     // illustrative of how to set up the data and not a recommendation for
+  ///     // optimal usage.
+  ///     Float32List rectList = Float32List(allSprites.length * 4);
+  ///     Float32List transformList = Float32List(allSprites.length * 4);
+  ///     Int32List colorList = Int32List(allSprites.length);
+  ///     for (int i = 0; i < allSprites.length; i++) {
+  ///       final double rectX = sprite.spriteIndex * 10.0;
+  ///       rectList[i * 4 + 0] = rectX;
+  ///       rectList[i * 4 + 1] = 0.0;
+  ///       rectList[i * 4 + 2] = rectX + 10.0;
+  ///       rectList[i * 4 + 3] = 10.0;
+  ///
+  ///       // This example uses an RSTransform object to compute the necessary values for
+  ///       // the transform using a factory helper method because the sprites contain
+  ///       // rotation values which are not trivial to work with. But if the math for the
+  ///       // values falls out from other calculations on the sprites then the values could
+  ///       // possibly be generated directly from the sprite update code.
+  ///       final RSTransform transform = RSTransform.fromComponents(
+  ///         rotation: sprite.rotation,
+  ///         scale: 1.0,
+  ///         // Center of the sprite relative to its rect
+  ///         anchorX: 5.0,
+  ///         anchorY: 5.0,
+  ///         // Location at which to draw the center of the sprite
+  ///         translateX: sprite.centerX,
+  ///         translateY: sprite.centerY,
+  ///       );
+  ///       transformList[i * 4 + 0] = transform.scos;
+  ///       transformList[i * 4 + 1] = transform.ssin;
+  ///       transformList[i * 4 + 2] = transform.tx;
+  ///       transformList[i * 4 + 3] = transform.ty;
+  ///
+  ///       // This example computes the color value directly, but one could also compute
+  ///       // an actual Color object and use its Color.value getter for the same result.
+  ///       // Since we are using BlendMode.srcIn, only the alpha component matters for
+  ///       // these colors which makes this a simple shift operation.
+  ///       colorList[i] = sprite.alpha << 24;
+  ///     }
+  ///     Paint paint = Paint();
+  ///     canvas.drawAtlas(spriteAtlas, transformList, rectList, colorList, BlendMode.srcIn, null, paint);
+  ///   }
+  ///
+  ///   ...
+  /// }
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * [drawAtlas], which takes its arguments as objects rather than typed
+  ///    data lists.
+  void drawRawAtlas(Image atlas,
+                    Float32List rstTransforms,
+                    Float32List rects,
+                    Int32List? colors,
+                    BlendMode? blendMode,
+                    Rect? cullRect,
+                    Paint paint) {
+    // ignore: unnecessary_null_comparison
+    assert(atlas != null); // atlas is checked on the engine side
+    assert(rstTransforms != null); // ignore: unnecessary_null_comparison
+    assert(rects != null); // ignore: unnecessary_null_comparison
+    assert(colors == null || blendMode != null);
+    assert(paint != null); // ignore: unnecessary_null_comparison
+
+    final int rectCount = rects.length;
+    if (rstTransforms.length != rectCount)
+      throw ArgumentError('"rstTransforms" and "rects" lengths must match.');
+    if (rectCount % 4 != 0)
+      throw ArgumentError('"rstTransforms" and "rects" lengths must be a multiple of four.');
+    if (colors != null && colors.length * 4 != rectCount)
+      throw ArgumentError('If non-null, "colors" length must be one fourth the length of "rstTransforms" and "rects".');
+
+    _drawAtlas(
+      paint._objects, paint._data, atlas._image, rstTransforms, rects,
+      colors, (blendMode ?? BlendMode.src).index, cullRect?._value32
+    );
+  }
+
+  void _drawAtlas(List<dynamic>? paintObjects,
+                  ByteData paintData,
+                  _Image atlas,
+                  Float32List rstTransforms,
+                  Float32List rects,
+                  Int32List? colors,
+                  int blendMode,
+                  Float32List? cullRect) { throw UnimplementedError(); }
+
+  /// Draws a shadow for a [Path] representing the given material elevation.
+  ///
+  /// The `transparentOccluder` argument should be true if the occluding object
+  /// is not opaque.
+  ///
+  /// The arguments must not be null.
+  void drawShadow(Path path, Color color, double elevation, bool transparentOccluder) {
+    // ignore: unnecessary_null_comparison
+    assert(path != null); // path is checked on the engine side
+    assert(color != null); // ignore: unnecessary_null_comparison
+    assert(transparentOccluder != null); // ignore: unnecessary_null_comparison
+    _drawShadow(path, color.value, elevation, transparentOccluder);
+  }
+  void _drawShadow(Path path,
+                   int color,
+                   double elevation,
+                   bool transparentOccluder) { throw UnimplementedError(); }
+}
+
+/// An object representing a sequence of recorded graphical operations.
+///
+/// To create a [Picture], use a [PictureRecorder].
+///
+/// A [Picture] can be placed in a [Scene] using a [SceneBuilder], via
+/// the [SceneBuilder.addPicture] method. A [Picture] can also be
+/// drawn into a [Canvas], using the [Canvas.drawPicture] method.
+@pragma('vm:entry-point')
+class Picture extends NativeFieldWrapperClass2 {
+  /// This class is created by the engine, and should not be instantiated
+  /// or extended directly.
+  ///
+  /// To create a [Picture], use a [PictureRecorder].
+  @pragma('vm:entry-point')
+  Picture._();
+
+  /// Creates an image from this picture.
+  ///
+  /// The returned image will be `width` pixels wide and `height` pixels high.
+  /// The picture is rasterized within the 0 (left), 0 (top), `width` (right),
+  /// `height` (bottom) bounds. Content outside these bounds is clipped.
+  ///
+  /// Although the image is returned synchronously, the picture is actually
+  /// rasterized the first time the image is drawn and then cached.
+  Future<Image> toImage(int width, int height) {
+    if (width <= 0 || height <= 0)
+      throw Exception('Invalid image dimensions.');
+    return _futurize(
+      (_Callback<Image> callback) => _toImage(width, height, (_Image image) {
+        callback(Image._(image));
+      }),
+    );
+  }
+
+  String? _toImage(int width, int height, _Callback<_Image> callback) { throw UnimplementedError(); }
+
+  /// Release the resources used by this object. The object is no longer usable
+  /// after this method is called.
+  void dispose() { throw UnimplementedError(); }
+
+  /// Returns the approximate number of bytes allocated for this object.
+  ///
+  /// The actual size of this picture may be larger, particularly if it contains
+  /// references to image or other large objects.
+  int get approximateBytesUsed { throw UnimplementedError(); }
+}
+
+/// Records a [Picture] containing a sequence of graphical operations.
+///
+/// To begin recording, construct a [Canvas] to record the commands.
+/// To end recording, use the [PictureRecorder.endRecording] method.
+class PictureRecorder extends NativeFieldWrapperClass2 {
+  /// Creates a new idle PictureRecorder. To associate it with a
+  /// [Canvas] and begin recording, pass this [PictureRecorder] to the
+  /// [Canvas] constructor.
+  @pragma('vm:entry-point')
+  PictureRecorder() { _constructor(); }
+  void _constructor() { throw UnimplementedError(); }
+
+  /// Whether this object is currently recording commands.
+  ///
+  /// Specifically, this returns true if a [Canvas] object has been
+  /// created to record commands and recording has not yet ended via a
+  /// call to [endRecording], and false if either this
+  /// [PictureRecorder] has not yet been associated with a [Canvas],
+  /// or the [endRecording] method has already been called.
+  bool get isRecording => _canvas != null;
+
+  /// Finishes recording graphical operations.
+  ///
+  /// Returns a picture containing the graphical operations that have been
+  /// recorded thus far. After calling this function, both the picture recorder
+  /// and the canvas objects are invalid and cannot be used further.
+  Picture endRecording() {
+    if (_canvas == null)
+      throw StateError('PictureRecorder did not start recording.');
+    final Picture picture = Picture._();
+    _endRecording(picture);
+    _canvas!._recorder = null;
+    _canvas = null;
+    return picture;
+  }
+
+  void _endRecording(Picture outPicture) { throw UnimplementedError(); }
+
+  Canvas? _canvas;
+}
+
+/// A single shadow.
+///
+/// Multiple shadows are stacked together in a [TextStyle].
+class Shadow {
+  /// Construct a shadow.
+  ///
+  /// The default shadow is a black shadow with zero offset and zero blur.
+  /// Default shadows should be completely covered by the casting element,
+  /// and not be visible.
+  ///
+  /// Transparency should be adjusted through the [color] alpha.
+  ///
+  /// Shadow order matters due to compositing multiple translucent objects not
+  /// being commutative.
+  const Shadow({
+    this.color = const Color(_kColorDefault),
+    this.offset = Offset.zero,
+    this.blurRadius = 0.0,
+  }) : assert(color != null, 'Text shadow color was null.'), // ignore: unnecessary_null_comparison
+       assert(offset != null, 'Text shadow offset was null.'), // ignore: unnecessary_null_comparison
+       assert(blurRadius >= 0.0, 'Text shadow blur radius should be non-negative.');
+
+  static const int _kColorDefault = 0xFF000000;
+  // Constants for shadow encoding.
+  static const int _kBytesPerShadow = 16;
+  static const int _kColorOffset = 0 << 2;
+  static const int _kXOffset = 1 << 2;
+  static const int _kYOffset = 2 << 2;
+  static const int _kBlurOffset = 3 << 2;
+
+  /// Color that the shadow will be drawn with.
+  ///
+  /// The shadows are shapes composited directly over the base canvas, and do not
+  /// represent optical occlusion.
+  final Color color;
+
+  /// The displacement of the shadow from the casting element.
+  ///
+  /// Positive x/y offsets will shift the shadow to the right and down, while
+  /// negative offsets shift the shadow to the left and up. The offsets are
+  /// relative to the position of the element that is casting it.
+  final Offset offset;
+
+  /// The standard deviation of the Gaussian to convolve with the shadow's shape.
+  final double blurRadius;
+
+  /// Converts a blur radius in pixels to sigmas.
+  ///
+  /// See the sigma argument to [MaskFilter.blur].
+  ///
+  // See SkBlurMask::ConvertRadiusToSigma().
+  // <https://github.com/google/skia/blob/bb5b77db51d2e149ee66db284903572a5aac09be/src/effects/SkBlurMask.cpp#L23>
+  static double convertRadiusToSigma(double radius) {
+    return radius * 0.57735 + 0.5;
+  }
+
+  /// The [blurRadius] in sigmas instead of logical pixels.
+  ///
+  /// See the sigma argument to [MaskFilter.blur].
+  double get blurSigma => convertRadiusToSigma(blurRadius);
+
+  /// Create the [Paint] object that corresponds to this shadow description.
+  ///
+  /// The [offset] is not represented in the [Paint] object.
+  /// To honor this as well, the shape should be translated by [offset] before
+  /// being filled using this [Paint].
+  ///
+  /// This class does not provide a way to disable shadows to avoid
+  /// inconsistencies in shadow blur rendering, primarily as a method of
+  /// reducing test flakiness. [toPaint] should be overridden in subclasses to
+  /// provide this functionality.
+  Paint toPaint() {
+    return Paint()
+      ..color = color
+      ..maskFilter = MaskFilter.blur(BlurStyle.normal, blurSigma);
+  }
+
+  /// Returns a new shadow with its [offset] and [blurRadius] scaled by the given
+  /// factor.
+  Shadow scale(double factor) {
+    return Shadow(
+      color: color,
+      offset: offset * factor,
+      blurRadius: blurRadius * factor,
+    );
+  }
+
+  /// Linearly interpolate between two shadows.
+  ///
+  /// If either shadow is null, this function linearly interpolates from a
+  /// a shadow that matches the other shadow in color but has a zero
+  /// offset and a zero blurRadius.
+  ///
+  /// {@template dart.ui.shadow.lerp}
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+  /// 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]).
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  /// {@endtemplate}
+  static Shadow? lerp(Shadow? a, Shadow? b, double t) {
+    assert(t != null); // ignore: unnecessary_null_comparison
+    if (b == null) {
+      if (a == null) {
+        return null;
+      } else {
+        return a.scale(1.0 - t);
+      }
+    } else {
+      if (a == null) {
+        return b.scale(t);
+      } else {
+        return Shadow(
+          color: Color.lerp(a.color, b.color, t)!,
+          offset: Offset.lerp(a.offset, b.offset, t)!,
+          blurRadius: _lerpDouble(a.blurRadius, b.blurRadius, t),
+        );
+      }
+    }
+  }
+
+  /// Linearly interpolate between two lists of shadows.
+  ///
+  /// If the lists differ in length, excess items are lerped with null.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static List<Shadow>? lerpList(List<Shadow>? a, List<Shadow>? b, double t) {
+    assert(t != null); // ignore: unnecessary_null_comparison
+    if (a == null && b == null)
+      return null;
+    a ??= <Shadow>[];
+    b ??= <Shadow>[];
+    final List<Shadow> result = <Shadow>[];
+    final int commonLength = math.min(a.length, b.length);
+    for (int i = 0; i < commonLength; i += 1)
+      result.add(Shadow.lerp(a[i], b[i], t)!);
+    for (int i = commonLength; i < a.length; i += 1)
+      result.add(a[i].scale(1.0 - t));
+    for (int i = commonLength; i < b.length; i += 1)
+      result.add(b[i].scale(t));
+    return result;
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    return other is Shadow
+        && other.color == color
+        && other.offset == offset
+        && other.blurRadius == blurRadius;
+  }
+
+  @override
+  int get hashCode => hashValues(color, offset, blurRadius);
+
+  // Serialize [shadows] into ByteData. The format is a single uint_32_t at
+  // the beginning indicating the number of shadows, followed by _kBytesPerShadow
+  // bytes for each shadow.
+  static ByteData _encodeShadows(List<Shadow>? shadows) {
+    if (shadows == null)
+      return ByteData(0);
+
+    final int byteCount = shadows.length * _kBytesPerShadow;
+    final ByteData shadowsData = ByteData(byteCount);
+
+    int shadowOffset = 0;
+    for (int shadowIndex = 0; shadowIndex < shadows.length; ++shadowIndex) {
+      final Shadow shadow = shadows[shadowIndex];
+      // TODO(yjbanov): remove the null check when the framework is migrated. While the list
+      //                of shadows contains non-nullable elements, unmigrated code can still
+      //                pass nulls.
+      // ignore: unnecessary_null_comparison
+      if (shadow != null) {
+        shadowOffset = shadowIndex * _kBytesPerShadow;
+
+        shadowsData.setInt32(_kColorOffset + shadowOffset,
+          shadow.color.value ^ Shadow._kColorDefault, _kFakeHostEndian);
+
+        shadowsData.setFloat32(_kXOffset + shadowOffset,
+          shadow.offset.dx, _kFakeHostEndian);
+
+        shadowsData.setFloat32(_kYOffset + shadowOffset,
+          shadow.offset.dy, _kFakeHostEndian);
+
+        shadowsData.setFloat32(_kBlurOffset + shadowOffset,
+          shadow.blurRadius, _kFakeHostEndian);
+      }
+    }
+
+    return shadowsData;
+  }
+
+  @override
+  String toString() => 'TextShadow($color, $offset, $blurRadius)';
+}
+
+/// A handle to a read-only byte buffer that is managed by the engine.
+class ImmutableBuffer extends NativeFieldWrapperClass2 {
+  ImmutableBuffer._(this.length);
+
+  /// Creates a copy of the data from a [Uint8List] suitable for internal use
+  /// in the engine.
+  static Future<ImmutableBuffer> fromUint8List(Uint8List list) {
+    final ImmutableBuffer instance = ImmutableBuffer._(list.length);
+    return _futurize((_Callback<void> callback) {
+      instance._init(list, callback);
+    }).then((_) => instance);
+  }
+  void _init(Uint8List list, _Callback<void> callback) { throw UnimplementedError(); }
+
+  /// The length, in bytes, of the underlying data.
+  final int length;
+
+  /// Release the resources used by this object. The object is no longer usable
+  /// after this method is called.
+  void dispose() { throw UnimplementedError(); }
+}
+
+/// A descriptor of data that can be turned into an [Image] via a [Codec].
+///
+/// Use this class to determine the height, width, and byte size of image data
+/// before decoding it.
+class ImageDescriptor extends NativeFieldWrapperClass2 {
+  ImageDescriptor._();
+
+  /// Creates an image descriptor from encoded data in a supported format.
+  static Future<ImageDescriptor> encoded(ImmutableBuffer buffer) {
+    final ImageDescriptor descriptor = ImageDescriptor._();
+    return _futurize((_Callback<void> callback) {
+      return descriptor._initEncoded(buffer, callback);
+    }).then((_) => descriptor);
+  }
+  String? _initEncoded(ImmutableBuffer buffer, _Callback<void> callback) { throw UnimplementedError(); }
+
+  /// Creates an image descriptor from raw image pixels.
+  ///
+  /// The `pixels` parameter is the pixel data in the encoding described by
+  /// `format`.
+  ///
+  /// The `rowBytes` parameter is the number of bytes consumed by each row of
+  /// pixels in the data buffer. If unspecified, it defaults to `width` multiplied
+  /// by the number of bytes per pixel in the provided `format`.
+  // Not async because there's no expensive work to do here.
+  ImageDescriptor.raw(
+    ImmutableBuffer buffer, {
+    required int width,
+    required int height,
+    int? rowBytes,
+    required PixelFormat pixelFormat,
+  }) {
+    _width = width;
+    _height = height;
+    // We only support 4 byte pixel formats in the PixelFormat enum.
+    _bytesPerPixel = 4;
+    _initRaw(this, buffer, width, height, rowBytes ?? -1, pixelFormat.index);
+  }
+  void _initRaw(ImageDescriptor outDescriptor, ImmutableBuffer buffer, int width, int height, int rowBytes, int pixelFormat) { throw UnimplementedError(); }
+
+  int? _width;
+  int _getWidth() { throw UnimplementedError(); }
+  /// The width, in pixels, of the image.
+  ///
+  /// On the Web, this is only supported for [raw] images.
+  int get width => _width ??= _getWidth();
+
+  int? _height;
+  int _getHeight() { throw UnimplementedError(); }
+  /// The height, in pixels, of the image.
+  ///
+  /// On the Web, this is only supported for [raw] images.
+  int get height => _height ??= _getHeight();
+
+  int? _bytesPerPixel;
+  int _getBytesPerPixel() { throw UnimplementedError(); }
+  /// The number of bytes per pixel in the image.
+  ///
+  /// On web, this is only supported for [raw] images.
+  int get bytesPerPixel => _bytesPerPixel ??= _getBytesPerPixel();
+
+  /// Release the resources used by this object. The object is no longer usable
+  /// after this method is called.
+  void dispose() { throw UnimplementedError(); }
+
+  /// Creates a [Codec] object which is suitable for decoding the data in the
+  /// buffer to an [Image].
+  ///
+  /// If only one of targetWidth or  targetHeight are specified, the other
+  /// dimension will be scaled according to the aspect ratio of the supplied
+  /// dimension.
+  ///
+  /// If either targetWidth or targetHeight is less than or equal to zero, it
+  /// will be treated as if it is null.
+  Future<Codec> instantiateCodec({int? targetWidth, int? targetHeight}) async {
+    if (targetWidth != null && targetWidth <= 0) {
+      targetWidth = null;
+    }
+    if (targetHeight != null && targetHeight <= 0) {
+      targetHeight = null;
+    }
+
+    if (targetWidth == null && targetHeight == null) {
+      targetWidth = width;
+      targetHeight = height;
+    } else if (targetWidth == null && targetHeight != null) {
+      targetWidth = (targetHeight * (width / height)).round();
+      targetHeight = targetHeight;
+    } else if (targetHeight == null && targetWidth != null) {
+      targetWidth = targetWidth;
+      targetHeight = targetWidth ~/ (width / height);
+    }
+    assert(targetWidth != null);
+    assert(targetHeight != null);
+
+    final Codec codec = Codec._();
+    _instantiateCodec(codec, targetWidth!, targetHeight!);
+    return codec;
+  }
+  void _instantiateCodec(Codec outCodec, int targetWidth, int targetHeight) { throw UnimplementedError(); }
+}
+
+/// Generic callback signature, used by [_futurize].
+typedef _Callback<T> = void Function(T result);
+
+/// Signature for a method that receives a [_Callback].
+///
+/// Return value should be null on success, and a string error message on
+/// failure.
+typedef _Callbacker<T> = String? Function(_Callback<T> callback);
+
+/// Converts a method that receives a value-returning callback to a method that
+/// returns a Future.
+///
+/// Return a [String] to cause an [Exception] to be synchronously thrown with
+/// that string as a message.
+///
+/// If the callback is called with null, the future completes with an error.
+///
+/// Example usage:
+///
+/// ```dart
+/// typedef IntCallback = void Function(int result);
+///
+/// String _doSomethingAndCallback(IntCallback callback) {
+///   Timer(new Duration(seconds: 1), () { callback(1); });
+/// }
+///
+/// Future<int> doSomething() {
+///   return _futurize(_doSomethingAndCallback);
+/// }
+/// ```
+Future<T> _futurize<T>(_Callbacker<T> callbacker) {
+  final Completer<T> completer = Completer<T>.sync();
+  final String? error = callbacker((T t) {
+    if (t == null) {
+      completer.completeError(Exception('operation failed'));
+    } else {
+      completer.complete(t);
+    }
+  });
+  if (error != null)
+    throw Exception(error);
+  return completer.future;
+}
diff --git a/lib/src/ui/platform_dispatcher.dart b/lib/src/ui/platform_dispatcher.dart
new file mode 100644
index 0000000..8c832ed
--- /dev/null
+++ b/lib/src/ui/platform_dispatcher.dart
@@ -0,0 +1,1550 @@
+// 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.
+
+
+part of dart.ui;
+
+/// Signature of callbacks that have no arguments and return no data.
+typedef VoidCallback = void Function();
+
+/// Signature for [PlatformDispatcher.onBeginFrame].
+typedef FrameCallback = void Function(Duration duration);
+
+/// Signature for [PlatformDispatcher.onReportTimings].
+///
+/// {@template dart.ui.TimingsCallback.list}
+/// The callback takes a list of [FrameTiming] because it may not be
+/// immediately triggered after each frame. Instead, Flutter tries to batch
+/// frames together and send all their timings at once to decrease the
+/// overhead (as this is available in the release mode). The list is sorted in
+/// ascending order of time (earliest frame first). The timing of any frame
+/// will be sent within about 1 second (100ms if in the profile/debug mode)
+/// even if there are no later frames to batch. The timing of the first frame
+/// will be sent immediately without batching.
+/// {@endtemplate}
+typedef TimingsCallback = void Function(List<FrameTiming> timings);
+
+/// Signature for [PlatformDispatcher.onPointerDataPacket].
+typedef PointerDataPacketCallback = void Function(PointerDataPacket packet);
+
+/// Signature for [PlatformDispatcher.onSemanticsAction].
+typedef SemanticsActionCallback = void Function(int id, SemanticsAction action, ByteData? args);
+
+/// Signature for responses to platform messages.
+///
+/// Used as a parameter to [PlatformDispatcher.sendPlatformMessage] and
+/// [PlatformDispatcher.onPlatformMessage].
+typedef PlatformMessageResponseCallback = void Function(ByteData? data);
+
+/// Signature for [PlatformDispatcher.onPlatformMessage].
+// TODO(ianh): deprecate once framework uses [ChannelBuffers.setListener].
+typedef PlatformMessageCallback = void Function(String name, ByteData? data, PlatformMessageResponseCallback? callback);
+
+// Signature for _setNeedsReportTimings.
+typedef _SetNeedsReportTimingsFunc = void Function(bool value);
+
+/// Signature for [PlatformDispatcher.onConfigurationChanged].
+typedef PlatformConfigurationChangedCallback = void Function(PlatformConfiguration configuration);
+
+/// Platform event dispatcher singleton.
+///
+/// The most basic interface to the host operating system's interface.
+///
+/// This is the central entry point for platform messages and configuration
+/// events from the platform.
+///
+/// It exposes the core scheduler API, the input event callback, the graphics
+/// drawing API, and other such core services.
+///
+/// It manages the list of the application's [views] and the [screens] attached
+/// to the device, as well as the [configuration] of various platform
+/// attributes.
+///
+/// Consider avoiding static references to this singleton through
+/// [PlatformDispatcher.instance] and instead prefer using a binding for
+/// dependency resolution such as `WidgetsBinding.instance.platformDispatcher`.
+/// See [PlatformDispatcher.instance] for more information about why this is
+/// preferred.
+class PlatformDispatcher {
+  /// Private constructor, since only dart:ui is supposed to create one of
+  /// these. Use [instance] to access the singleton.
+  PlatformDispatcher._() {
+    _setNeedsReportTimings = _nativeSetNeedsReportTimings;
+  }
+
+  /// The [PlatformDispatcher] singleton.
+  ///
+  /// Consider avoiding static references to this singleton though
+  /// [PlatformDispatcher.instance] and instead prefer using a binding for
+  /// dependency resolution such as `WidgetsBinding.instance.platformDispatcher`.
+  ///
+  /// Static access of this object means that Flutter has few, if any options to
+  /// fake or mock the given object in tests. Even in cases where Dart offers
+  /// special language constructs to forcefully shadow such properties, those
+  /// mechanisms would only be reasonable for tests and they would not be
+  /// reasonable for a future of Flutter where we legitimately want to select an
+  /// appropriate implementation at runtime.
+  ///
+  /// The only place that `WidgetsBinding.instance.platformDispatcher` is
+  /// inappropriate is if access to these APIs is required before the binding is
+  /// initialized by invoking `runApp()` or
+  /// `WidgetsFlutterBinding.instance.ensureInitialized()`. In that case, it is
+  /// necessary (though unfortunate) to use the [PlatformDispatcher.instance]
+  /// object statically.
+  static PlatformDispatcher get instance => _instance;
+  static final PlatformDispatcher _instance = PlatformDispatcher._();
+
+  /// The current platform configuration.
+  ///
+  /// If values in this configuration change, [onPlatformConfigurationChanged]
+  /// will be called.
+  PlatformConfiguration get configuration => _configuration;
+  PlatformConfiguration _configuration = const PlatformConfiguration();
+
+  /// Called when the platform configuration changes.
+  ///
+  /// The engine invokes this callback in the same zone in which the callback
+  /// was set.
+  VoidCallback? get onPlatformConfigurationChanged => _onPlatformConfigurationChanged;
+  VoidCallback? _onPlatformConfigurationChanged;
+  Zone _onPlatformConfigurationChangedZone = Zone.root;
+  set onPlatformConfigurationChanged(VoidCallback? callback) {
+    _onPlatformConfigurationChanged = callback;
+    _onPlatformConfigurationChangedZone = Zone.current;
+  }
+
+  /// The current list of views, including top level platform windows used by
+  /// the application.
+  ///
+  /// If any of their configurations change, [onMetricsChanged] will be called.
+  Iterable<FlutterView> get views => _views.values;
+  Map<Object, FlutterView> _views = <Object, FlutterView>{};
+
+  // A map of opaque platform view identifiers to view configurations.
+  Map<Object, ViewConfiguration> _viewConfigurations = <Object, ViewConfiguration>{};
+
+  /// A callback that is invoked whenever the [ViewConfiguration] of any of the
+  /// [views] changes.
+  ///
+  /// For example when the device is rotated or when the application is resized
+  /// (e.g. when showing applications side-by-side on Android),
+  /// `onMetricsChanged` is called.
+  ///
+  /// The engine invokes this callback in the same zone in which the callback
+  /// was set.
+  ///
+  /// The framework registers with this callback and updates the layout
+  /// appropriately.
+  ///
+  /// See also:
+  ///
+  /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///   register for notifications when this is called.
+  /// * [MediaQuery.of], a simpler mechanism for the same.
+  VoidCallback? get onMetricsChanged => _onMetricsChanged;
+  VoidCallback? _onMetricsChanged;
+  Zone _onMetricsChangedZone = Zone.root;
+  set onMetricsChanged(VoidCallback? callback) {
+    _onMetricsChanged = callback;
+    _onMetricsChangedZone = Zone.current;
+  }
+
+  // Called from the engine, via hooks.dart
+  //
+  // Updates the metrics of the window with the given id.
+  void _updateWindowMetrics(
+    Object id,
+    double devicePixelRatio,
+    double width,
+    double height,
+    double viewPaddingTop,
+    double viewPaddingRight,
+    double viewPaddingBottom,
+    double viewPaddingLeft,
+    double viewInsetTop,
+    double viewInsetRight,
+    double viewInsetBottom,
+    double viewInsetLeft,
+    double systemGestureInsetTop,
+    double systemGestureInsetRight,
+    double systemGestureInsetBottom,
+    double systemGestureInsetLeft,
+  ) {
+    final ViewConfiguration previousConfiguration =
+        _viewConfigurations[id] ?? const ViewConfiguration();
+    if (!_views.containsKey(id)) {
+      _views[id] = FlutterWindow._(id, this);
+    }
+    _viewConfigurations[id] = previousConfiguration.copyWith(
+      window: _views[id],
+      devicePixelRatio: devicePixelRatio,
+      geometry: Rect.fromLTWH(0.0, 0.0, width, height),
+      viewPadding: WindowPadding._(
+        top: viewPaddingTop,
+        right: viewPaddingRight,
+        bottom: viewPaddingBottom,
+        left: viewPaddingLeft,
+      ),
+      viewInsets: WindowPadding._(
+        top: viewInsetTop,
+        right: viewInsetRight,
+        bottom: viewInsetBottom,
+        left: viewInsetLeft,
+      ),
+      padding: WindowPadding._(
+        top: math.max(0.0, viewPaddingTop - viewInsetTop),
+        right: math.max(0.0, viewPaddingRight - viewInsetRight),
+        bottom: math.max(0.0, viewPaddingBottom - viewInsetBottom),
+        left: math.max(0.0, viewPaddingLeft - viewInsetLeft),
+      ),
+      systemGestureInsets: WindowPadding._(
+        top: math.max(0.0, systemGestureInsetTop),
+        right: math.max(0.0, systemGestureInsetRight),
+        bottom: math.max(0.0, systemGestureInsetBottom),
+        left: math.max(0.0, systemGestureInsetLeft),
+      ),
+    );
+    _invoke(onMetricsChanged, _onMetricsChangedZone);
+  }
+
+  /// A callback invoked when any view begins a frame.
+  ///
+  /// A callback that is invoked to notify the application that it is an
+  /// appropriate time to provide a scene using the [SceneBuilder] API and the
+  /// [FlutterView.render] method.
+  ///
+  /// When possible, this is driven by the hardware VSync signal of the attached
+  /// screen with the highest VSync rate. This is only called if
+  /// [PlatformDispatcher.scheduleFrame] has been called since the last time
+  /// this callback was invoked.
+  FrameCallback? get onBeginFrame => _onBeginFrame;
+  FrameCallback? _onBeginFrame;
+  Zone _onBeginFrameZone = Zone.root;
+  set onBeginFrame(FrameCallback? callback) {
+    _onBeginFrame = callback;
+    _onBeginFrameZone = Zone.current;
+  }
+
+  // Called from the engine, via hooks.dart
+  void _beginFrame(int microseconds) {
+    _invoke1<Duration>(
+      onBeginFrame,
+      _onBeginFrameZone,
+      Duration(microseconds: microseconds),
+    );
+  }
+
+  /// A callback that is invoked for each frame after [onBeginFrame] has
+  /// completed and after the microtask queue has been drained.
+  ///
+  /// This can be used to implement a second phase of frame rendering that
+  /// happens after any deferred work queued by the [onBeginFrame] phase.
+  VoidCallback? get onDrawFrame => _onDrawFrame;
+  VoidCallback? _onDrawFrame;
+  Zone _onDrawFrameZone = Zone.root;
+  set onDrawFrame(VoidCallback? callback) {
+    _onDrawFrame = callback;
+    _onDrawFrameZone = Zone.current;
+  }
+
+  // Called from the engine, via hooks.dart
+  void _drawFrame() {
+    _invoke(onDrawFrame, _onDrawFrameZone);
+  }
+
+  /// A callback that is invoked when pointer data is available.
+  ///
+  /// The framework invokes this callback in the same zone in which the callback
+  /// was set.
+  ///
+  /// See also:
+  ///
+  ///  * [GestureBinding], the Flutter framework class which manages pointer
+  ///    events.
+  PointerDataPacketCallback? get onPointerDataPacket => _onPointerDataPacket;
+  PointerDataPacketCallback? _onPointerDataPacket;
+  Zone _onPointerDataPacketZone = Zone.root;
+  set onPointerDataPacket(PointerDataPacketCallback? callback) {
+    _onPointerDataPacket = callback;
+    _onPointerDataPacketZone = Zone.current;
+  }
+
+  // Called from the engine, via hooks.dart
+  void _dispatchPointerDataPacket(ByteData packet) {
+    if (onPointerDataPacket != null) {
+      _invoke1<PointerDataPacket>(
+        onPointerDataPacket,
+        _onPointerDataPacketZone,
+        _unpackPointerDataPacket(packet),
+      );
+    }
+  }
+
+  // If this value changes, update the encoding code in the following files:
+  //
+  //  * pointer_data.cc
+  //  * pointer.dart
+  //  * AndroidTouchProcessor.java
+  static const int _kPointerDataFieldCount = 29;
+
+  static PointerDataPacket _unpackPointerDataPacket(ByteData packet) {
+    const int kStride = Int64List.bytesPerElement;
+    const int kBytesPerPointerData = _kPointerDataFieldCount * kStride;
+    final int length = packet.lengthInBytes ~/ kBytesPerPointerData;
+    assert(length * kBytesPerPointerData == packet.lengthInBytes);
+    final List<PointerData> data = <PointerData>[];
+    for (int i = 0; i < length; ++i) {
+      int offset = i * _kPointerDataFieldCount;
+      data.add(PointerData(
+        embedderId: packet.getInt64(kStride * offset++, _kFakeHostEndian),
+        timeStamp: Duration(microseconds: packet.getInt64(kStride * offset++, _kFakeHostEndian)),
+        change: PointerChange.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)],
+        kind: PointerDeviceKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)],
+        signalKind: PointerSignalKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)],
+        device: packet.getInt64(kStride * offset++, _kFakeHostEndian),
+        pointerIdentifier: packet.getInt64(kStride * offset++, _kFakeHostEndian),
+        physicalX: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        physicalY: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        physicalDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        physicalDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        buttons: packet.getInt64(kStride * offset++, _kFakeHostEndian),
+        obscured: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0,
+        synthesized: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0,
+        pressure: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        pressureMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        pressureMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        distance: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        distanceMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        size: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        radiusMajor: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        radiusMinor: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        radiusMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        radiusMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        orientation: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        tilt: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        platformData: packet.getInt64(kStride * offset++, _kFakeHostEndian),
+        scrollDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+        scrollDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
+      ));
+      assert(offset == (i + 1) * _kPointerDataFieldCount);
+    }
+    return PointerDataPacket(data: data);
+  }
+
+  /// A callback that is invoked to report the [FrameTiming] of recently
+  /// rasterized frames.
+  ///
+  /// It's preferred to use [SchedulerBinding.addTimingsCallback] than to use
+  /// [onReportTimings] directly because [SchedulerBinding.addTimingsCallback]
+  /// allows multiple callbacks.
+  ///
+  /// This can be used to see if the application has missed frames (through
+  /// [FrameTiming.buildDuration] and [FrameTiming.rasterDuration]), or high
+  /// latencies (through [FrameTiming.totalSpan]).
+  ///
+  /// Unlike [Timeline], the timing information here is available in the release
+  /// mode (additional to the profile and the debug mode). Hence this can be
+  /// used to monitor the application's performance in the wild.
+  ///
+  /// {@macro dart.ui.TimingsCallback.list}
+  ///
+  /// If this is null, no additional work will be done. If this is not null,
+  /// Flutter spends less than 0.1ms every 1 second to report the timings
+  /// (measured on iPhone6S). The 0.1ms is about 0.6% of 16ms (frame budget for
+  /// 60fps), or 0.01% CPU usage per second.
+  TimingsCallback? get onReportTimings => _onReportTimings;
+  TimingsCallback? _onReportTimings;
+  Zone _onReportTimingsZone = Zone.root;
+  set onReportTimings(TimingsCallback? callback) {
+    if ((callback == null) != (_onReportTimings == null)) {
+      _setNeedsReportTimings(callback != null);
+    }
+    _onReportTimings = callback;
+    _onReportTimingsZone = Zone.current;
+  }
+
+  late _SetNeedsReportTimingsFunc _setNeedsReportTimings;
+  void _nativeSetNeedsReportTimings(bool value)
+      { throw UnimplementedError(); }
+
+  // Called from the engine, via hooks.dart
+  void _reportTimings(List<int> timings) {
+    assert(timings.length % FramePhase.values.length == 0);
+    final List<FrameTiming> frameTimings = <FrameTiming>[];
+    for (int i = 0; i < timings.length; i += FramePhase.values.length) {
+      frameTimings.add(FrameTiming._(timings.sublist(i, i + FramePhase.values.length)));
+    }
+    _invoke1(onReportTimings, _onReportTimingsZone, frameTimings);
+  }
+
+  /// Sends a message to a platform-specific plugin.
+  ///
+  /// The `name` parameter determines which plugin receives the message. The
+  /// `data` parameter contains the message payload and is typically UTF-8
+  /// encoded JSON but can be arbitrary data. If the plugin replies to the
+  /// message, `callback` will be called with the response.
+  ///
+  /// The framework invokes [callback] in the same zone in which this method was
+  /// called.
+  void sendPlatformMessage(String name, ByteData? data, PlatformMessageResponseCallback? callback) {
+    final String? error =
+        _sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
+    if (error != null)
+      throw Exception(error);
+  }
+
+  String? _sendPlatformMessage(String name, PlatformMessageResponseCallback? callback, ByteData? data)
+      { throw UnimplementedError(); }
+
+  /// Called whenever this platform dispatcher receives a message from a
+  /// platform-specific plugin.
+  ///
+  /// The `name` parameter determines which plugin sent the message. The `data`
+  /// parameter is the payload and is typically UTF-8 encoded JSON but can be
+  /// arbitrary data.
+  ///
+  /// Message handlers must call the function given in the `callback` parameter.
+  /// If the handler does not need to respond, the handler should pass null to
+  /// the callback.
+  ///
+  /// The framework invokes this callback in the same zone in which the callback
+  /// was set.
+  // TODO(ianh): Deprecate onPlatformMessage once the framework is moved over
+  // to using channel buffers exclusively.
+  PlatformMessageCallback? get onPlatformMessage => _onPlatformMessage;
+  PlatformMessageCallback? _onPlatformMessage;
+  Zone _onPlatformMessageZone = Zone.root;
+  set onPlatformMessage(PlatformMessageCallback? callback) {
+    _onPlatformMessage = callback;
+    _onPlatformMessageZone = Zone.current;
+  }
+
+  /// Called by [_dispatchPlatformMessage].
+  void _respondToPlatformMessage(int responseId, ByteData? data)
+      { throw UnimplementedError(); }
+
+  /// Wraps the given [callback] in another callback that ensures that the
+  /// original callback is called in the zone it was registered in.
+  static PlatformMessageResponseCallback? _zonedPlatformMessageResponseCallback(
+    PlatformMessageResponseCallback? callback,
+  ) {
+    if (callback == null) {
+      return null;
+    }
+
+    // Store the zone in which the callback is being registered.
+    final Zone registrationZone = Zone.current;
+
+    return (ByteData? data) {
+      registrationZone.runUnaryGuarded(callback, data);
+    };
+  }
+
+  /// Send a message to the framework using the [ChannelBuffers].
+  ///
+  /// This method constructs the appropriate callback to respond
+  /// with the given `responseId`. It should only be called for messages
+  /// from the platform.
+  void _dispatchPlatformMessage(String name, ByteData? data, int responseId) {
+    if (name == ChannelBuffers.kControlChannelName) {
+      try {
+        channelBuffers.handleMessage(data!);
+      } finally {
+        _respondToPlatformMessage(responseId, null);
+      }
+    } else if (onPlatformMessage != null) {
+      _invoke3<String, ByteData?, PlatformMessageResponseCallback>(
+        onPlatformMessage,
+        _onPlatformMessageZone,
+        name,
+        data,
+        (ByteData? responseData) {
+          _respondToPlatformMessage(responseId, responseData);
+        },
+      );
+    } else {
+      channelBuffers.push(name, data, (ByteData? responseData) {
+        _respondToPlatformMessage(responseId, responseData);
+      });
+    }
+  }
+
+  /// Set the debug name associated with this platform dispatcher's root
+  /// isolate.
+  ///
+  /// Normally debug names are automatically generated from the Dart port, entry
+  /// point, and source file. For example: `main.dart$main-1234`.
+  ///
+  /// This can be combined with flutter tools `--isolate-filter` flag to debug
+  /// specific root isolates. For example: `flutter attach --isolate-filter=[name]`.
+  /// Note that this does not rename any child isolates of the root.
+  void setIsolateDebugName(String name) { throw UnimplementedError(); }
+
+  /// The embedder can specify data that the isolate can request synchronously
+  /// on launch. This accessor fetches that data.
+  ///
+  /// This data is persistent for the duration of the Flutter application and is
+  /// available even after isolate restarts. Because of this lifecycle, the size
+  /// of this data must be kept to a minimum.
+  ///
+  /// For asynchronous communication between the embedder and isolate, a
+  /// platform channel may be used.
+  ByteData? getPersistentIsolateData() { throw UnimplementedError(); }
+
+  /// Requests that, at the next appropriate opportunity, the [onBeginFrame] and
+  /// [onDrawFrame] callbacks be invoked.
+  ///
+  /// See also:
+  ///
+  ///  * [SchedulerBinding], the Flutter framework class which manages the
+  ///    scheduling of frames.
+  void scheduleFrame() { throw UnimplementedError(); }
+
+  /// Additional accessibility features that may be enabled by the platform.
+  AccessibilityFeatures get accessibilityFeatures => configuration.accessibilityFeatures;
+
+  /// A callback that is invoked when the value of [accessibilityFeatures]
+  /// changes.
+  ///
+  /// The framework invokes this callback in the same zone in which the callback
+  /// was set.
+  VoidCallback? get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged;
+  VoidCallback? _onAccessibilityFeaturesChanged;
+  Zone _onAccessibilityFeaturesChangedZone = Zone.root;
+  set onAccessibilityFeaturesChanged(VoidCallback? callback) {
+    _onAccessibilityFeaturesChanged = callback;
+    _onAccessibilityFeaturesChangedZone = Zone.current;
+  }
+
+  // Called from the engine, via hooks.dart
+  void _updateAccessibilityFeatures(int values) {
+    final AccessibilityFeatures newFeatures = AccessibilityFeatures._(values);
+    final PlatformConfiguration previousConfiguration = configuration;
+    if (newFeatures == previousConfiguration.accessibilityFeatures) {
+      return;
+    }
+    _configuration = previousConfiguration.copyWith(
+      accessibilityFeatures: newFeatures,
+    );
+    _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone,);
+    _invoke(onAccessibilityFeaturesChanged, _onAccessibilityFeaturesChangedZone,);
+  }
+
+  /// Change the retained semantics data about this platform dispatcher.
+  ///
+  /// If [semanticsEnabled] is true, the user has requested that this function
+  /// be called whenever the semantic content of this platform dispatcher
+  /// changes.
+  ///
+  /// In either case, this function disposes the given update, which means the
+  /// semantics update cannot be used further.
+  void updateSemantics(SemanticsUpdate update) { throw UnimplementedError(); }
+
+  /// The system-reported default locale of the device.
+  ///
+  /// This establishes the language and formatting conventions that application
+  /// should, if possible, use to render their user interface.
+  ///
+  /// This is the first locale selected by the user and is the user's primary
+  /// locale (the locale the device UI is displayed in)
+  ///
+  /// This is equivalent to `locales.first`, except that it will provide an
+  /// undefined (using the language tag "und") non-null locale if the [locales]
+  /// list has not been set or is empty.
+  Locale get locale => locales.isEmpty ? const Locale.fromSubtags() : locales.first;
+
+  /// The full system-reported supported locales of the device.
+  ///
+  /// This establishes the language and formatting conventions that application
+  /// should, if possible, use to render their user interface.
+  ///
+  /// The list is ordered in order of priority, with lower-indexed locales being
+  /// preferred over higher-indexed ones. The first element is the primary
+  /// [locale].
+  ///
+  /// The [onLocaleChanged] callback is called whenever this value changes.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this value changes.
+  List<Locale> get locales => configuration.locales;
+
+  /// Performs the platform-native locale resolution.
+  ///
+  /// Each platform may return different results.
+  ///
+  /// If the platform fails to resolve a locale, then this will return null.
+  ///
+  /// This method returns synchronously and is a direct call to
+  /// platform specific APIs without invoking method channels.
+  Locale? computePlatformResolvedLocale(List<Locale> supportedLocales) {
+    final List<String?> supportedLocalesData = <String?>[];
+    for (Locale locale in supportedLocales) {
+      supportedLocalesData.add(locale.languageCode);
+      supportedLocalesData.add(locale.countryCode);
+      supportedLocalesData.add(locale.scriptCode);
+    }
+
+    final List<String> result = _computePlatformResolvedLocale(supportedLocalesData);
+
+    if (result.isNotEmpty) {
+      return Locale.fromSubtags(
+        languageCode: result[0],
+        countryCode: result[1] == '' ? null : result[1],
+        scriptCode: result[2] == '' ? null : result[2]);
+    }
+    return null;
+  }
+  List<String> _computePlatformResolvedLocale(List<String?> supportedLocalesData) { throw UnimplementedError(); }
+
+  /// A callback that is invoked whenever [locale] changes value.
+  ///
+  /// The framework invokes this callback in the same zone in which the callback
+  /// was set.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this callback is invoked.
+  VoidCallback? get onLocaleChanged => _onLocaleChanged;
+  VoidCallback? _onLocaleChanged;
+  Zone _onLocaleChangedZone = Zone.root; // ignore: unused_field
+  set onLocaleChanged(VoidCallback? callback) {
+    _onLocaleChanged = callback;
+    _onLocaleChangedZone = Zone.current;
+  }
+
+  // Called from the engine, via hooks.dart
+  void _updateLocales(List<String> locales) {
+    const int stringsPerLocale = 4;
+    final int numLocales = locales.length ~/ stringsPerLocale;
+    final PlatformConfiguration previousConfiguration = configuration;
+    final List<Locale> newLocales = <Locale>[];
+    bool localesDiffer = numLocales != previousConfiguration.locales.length;
+    for (int localeIndex = 0; localeIndex < numLocales; localeIndex++) {
+      final String countryCode = locales[localeIndex * stringsPerLocale + 1];
+      final String scriptCode = locales[localeIndex * stringsPerLocale + 2];
+
+      newLocales.add(Locale.fromSubtags(
+        languageCode: locales[localeIndex * stringsPerLocale],
+        countryCode: countryCode.isEmpty ? null : countryCode,
+        scriptCode: scriptCode.isEmpty ? null : scriptCode,
+      ));
+      if (!localesDiffer && newLocales[localeIndex] != previousConfiguration.locales[localeIndex]) {
+        localesDiffer = true;
+      }
+    }
+    if (!localesDiffer) {
+      return;
+    }
+    _configuration = previousConfiguration.copyWith(locales: newLocales);
+    _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone);
+    _invoke(onLocaleChanged, _onLocaleChangedZone);
+  }
+
+  // Called from the engine, via hooks.dart
+  String _localeClosure() => locale.toString();
+
+  /// The lifecycle state immediately after dart isolate initialization.
+  ///
+  /// This property will not be updated as the lifecycle changes.
+  ///
+  /// It is used to initialize [SchedulerBinding.lifecycleState] at startup with
+  /// any buffered lifecycle state events.
+  String get initialLifecycleState {
+    _initialLifecycleStateAccessed = true;
+    return _initialLifecycleState;
+  }
+
+  late String _initialLifecycleState;
+
+  /// Tracks if the initial state has been accessed. Once accessed, we will stop
+  /// updating the [initialLifecycleState], as it is not the preferred way to
+  /// access the state.
+  bool _initialLifecycleStateAccessed = false;
+
+  // Called from the engine, via hooks.dart
+  void _updateLifecycleState(String state) {
+    // We do not update the state if the state has already been used to initialize
+    // the lifecycleState.
+    if (!_initialLifecycleStateAccessed)
+      _initialLifecycleState = state;
+  }
+
+  /// The setting indicating whether time should always be shown in the 24-hour
+  /// format.
+  ///
+  /// This option is used by [showTimePicker].
+  bool get alwaysUse24HourFormat => configuration.alwaysUse24HourFormat;
+
+  /// The system-reported text scale.
+  ///
+  /// This establishes the text scaling factor to use when rendering text,
+  /// according to the user's platform preferences.
+  ///
+  /// The [onTextScaleFactorChanged] callback is called whenever this value
+  /// changes.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this value changes.
+  double get textScaleFactor => configuration.textScaleFactor;
+
+  /// A callback that is invoked whenever [textScaleFactor] changes value.
+  ///
+  /// The framework invokes this callback in the same zone in which the callback
+  /// was set.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this callback is invoked.
+  VoidCallback? get onTextScaleFactorChanged => _onTextScaleFactorChanged;
+  VoidCallback? _onTextScaleFactorChanged;
+  Zone _onTextScaleFactorChangedZone = Zone.root;
+  set onTextScaleFactorChanged(VoidCallback? callback) {
+    _onTextScaleFactorChanged = callback;
+    _onTextScaleFactorChangedZone = Zone.current;
+  }
+
+  /// The setting indicating the current brightness mode of the host platform.
+  /// If the platform has no preference, [platformBrightness] defaults to
+  /// [Brightness.light].
+  Brightness get platformBrightness => configuration.platformBrightness;
+
+  /// A callback that is invoked whenever [platformBrightness] changes value.
+  ///
+  /// The framework invokes this callback in the same zone in which the callback
+  /// was set.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this callback is invoked.
+  VoidCallback? get onPlatformBrightnessChanged => _onPlatformBrightnessChanged;
+  VoidCallback? _onPlatformBrightnessChanged;
+  Zone _onPlatformBrightnessChangedZone = Zone.root;
+  set onPlatformBrightnessChanged(VoidCallback? callback) {
+    _onPlatformBrightnessChanged = callback;
+    _onPlatformBrightnessChangedZone = Zone.current;
+  }
+
+  // Called from the engine, via hooks.dart
+  void _updateUserSettingsData(String jsonData) {
+    final Map<String, dynamic> data = json.decode(jsonData) as Map<String, dynamic>;
+    if (data.isEmpty) {
+      return;
+    }
+
+    final double textScaleFactor = (data['textScaleFactor'] as num).toDouble();
+    final bool alwaysUse24HourFormat = data['alwaysUse24HourFormat'] as bool;
+    final Brightness platformBrightness =
+    data['platformBrightness'] as String == 'dark' ? Brightness.dark : Brightness.light;
+    final PlatformConfiguration previousConfiguration = configuration;
+    final bool platformBrightnessChanged =
+        previousConfiguration.platformBrightness != platformBrightness;
+    final bool textScaleFactorChanged = previousConfiguration.textScaleFactor != textScaleFactor;
+    final bool alwaysUse24HourFormatChanged =
+        previousConfiguration.alwaysUse24HourFormat != alwaysUse24HourFormat;
+    if (!platformBrightnessChanged && !textScaleFactorChanged && !alwaysUse24HourFormatChanged) {
+      return;
+    }
+    _configuration = previousConfiguration.copyWith(
+      textScaleFactor: textScaleFactor,
+      alwaysUse24HourFormat: alwaysUse24HourFormat,
+      platformBrightness: platformBrightness,
+    );
+    _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone);
+    if (textScaleFactorChanged) {
+      _invoke(onTextScaleFactorChanged, _onTextScaleFactorChangedZone);
+    }
+    if (platformBrightnessChanged) {
+      _invoke(onPlatformBrightnessChanged, _onPlatformBrightnessChangedZone);
+    }
+  }
+
+  /// Whether the user has requested that [updateSemantics] be called when the
+  /// semantic contents of a view changes.
+  ///
+  /// The [onSemanticsEnabledChanged] callback is called whenever this value
+  /// changes.
+  bool get semanticsEnabled => configuration.semanticsEnabled;
+
+  /// A callback that is invoked when the value of [semanticsEnabled] changes.
+  ///
+  /// The framework invokes this callback in the same zone in which the
+  /// callback was set.
+  VoidCallback? get onSemanticsEnabledChanged => _onSemanticsEnabledChanged;
+  VoidCallback? _onSemanticsEnabledChanged;
+  Zone _onSemanticsEnabledChangedZone = Zone.root;
+  set onSemanticsEnabledChanged(VoidCallback? callback) {
+    _onSemanticsEnabledChanged = callback;
+    _onSemanticsEnabledChangedZone = Zone.current;
+  }
+
+  // Called from the engine, via hooks.dart
+  void _updateSemanticsEnabled(bool enabled) {
+    final PlatformConfiguration previousConfiguration = configuration;
+    if (previousConfiguration.semanticsEnabled == enabled) {
+      return;
+    }
+    _configuration = previousConfiguration.copyWith(
+      semanticsEnabled: enabled,
+    );
+    _invoke(onPlatformConfigurationChanged, _onPlatformConfigurationChangedZone);
+    _invoke(onSemanticsEnabledChanged, _onSemanticsEnabledChangedZone);
+  }
+
+  /// A callback that is invoked whenever the user requests an action to be
+  /// performed.
+  ///
+  /// This callback is used when the user expresses the action they wish to
+  /// perform based on the semantics supplied by [updateSemantics].
+  ///
+  /// The framework invokes this callback in the same zone in which the
+  /// callback was set.
+  SemanticsActionCallback? get onSemanticsAction => _onSemanticsAction;
+  SemanticsActionCallback? _onSemanticsAction;
+  Zone _onSemanticsActionZone = Zone.root;
+  set onSemanticsAction(SemanticsActionCallback? callback) {
+    _onSemanticsAction = callback;
+    _onSemanticsActionZone = Zone.current;
+  }
+
+  // Called from the engine, via hooks.dart
+  void _dispatchSemanticsAction(int id, int action, ByteData? args) {
+    _invoke3<int, SemanticsAction, ByteData?>(
+      onSemanticsAction,
+      _onSemanticsActionZone,
+      id,
+      SemanticsAction.values[action]!,
+      args,
+    );
+  }
+
+  /// The route or path that the embedder requested when the application was
+  /// launched.
+  ///
+  /// This will be the string "`/`" if no particular route was requested.
+  ///
+  /// ## Android
+  ///
+  /// On Android, calling
+  /// [`FlutterView.setInitialRoute`](/javadoc/io/flutter/view/FlutterView.html#setInitialRoute-java.lang.String-)
+  /// will set this value. The value must be set sufficiently early, i.e. before
+  /// the [runApp] call is executed in Dart, for this to have any effect on the
+  /// framework. The `createFlutterView` method in your `FlutterActivity`
+  /// subclass is a suitable time to set the value. The application's
+  /// `AndroidManifest.xml` file must also be updated to have a suitable
+  /// [`<intent-filter>`](https://developer.android.com/guide/topics/manifest/intent-filter-element.html).
+  ///
+  /// ## iOS
+  ///
+  /// On iOS, calling
+  /// [`FlutterViewController.setInitialRoute`](/objcdoc/Classes/FlutterViewController.html#/c:objc%28cs%29FlutterViewController%28im%29setInitialRoute:)
+  /// will set this value. The value must be set sufficiently early, i.e. before
+  /// the [runApp] call is executed in Dart, for this to have any effect on the
+  /// framework. The `application:didFinishLaunchingWithOptions:` method is a
+  /// suitable time to set this value.
+  ///
+  /// See also:
+  ///
+  ///  * [Navigator], a widget that handles routing.
+  ///  * [SystemChannels.navigation], which handles subsequent navigation
+  ///    requests from the embedder.
+  String get defaultRouteName => _defaultRouteName();
+  String _defaultRouteName() { throw UnimplementedError(); }
+}
+
+/// Configuration of the platform.
+///
+/// Immutable class (but can't use @immutable in dart:ui)
+class PlatformConfiguration {
+  /// Const constructor for [PlatformConfiguration].
+  const PlatformConfiguration({
+    this.accessibilityFeatures = const AccessibilityFeatures._(0),
+    this.alwaysUse24HourFormat = false,
+    this.semanticsEnabled = false,
+    this.platformBrightness = Brightness.light,
+    this.textScaleFactor = 1.0,
+    this.locales = const <Locale>[],
+    this.defaultRouteName,
+  });
+
+  /// Copy a [PlatformConfiguration] with some fields replaced.
+  PlatformConfiguration copyWith({
+    AccessibilityFeatures? accessibilityFeatures,
+    bool? alwaysUse24HourFormat,
+    bool? semanticsEnabled,
+    Brightness? platformBrightness,
+    double? textScaleFactor,
+    List<Locale>? locales,
+    String? defaultRouteName,
+  }) {
+    return PlatformConfiguration(
+      accessibilityFeatures: accessibilityFeatures ?? this.accessibilityFeatures,
+      alwaysUse24HourFormat: alwaysUse24HourFormat ?? this.alwaysUse24HourFormat,
+      semanticsEnabled: semanticsEnabled ?? this.semanticsEnabled,
+      platformBrightness: platformBrightness ?? this.platformBrightness,
+      textScaleFactor: textScaleFactor ?? this.textScaleFactor,
+      locales: locales ?? this.locales,
+      defaultRouteName: defaultRouteName ?? this.defaultRouteName,
+    );
+  }
+
+  /// Additional accessibility features that may be enabled by the platform.
+  final AccessibilityFeatures accessibilityFeatures;
+
+  /// The setting indicating whether time should always be shown in the 24-hour
+  /// format.
+  final bool alwaysUse24HourFormat;
+
+  /// Whether the user has requested that [updateSemantics] be called when the
+  /// semantic contents of a view changes.
+  final bool semanticsEnabled;
+
+  /// The setting indicating the current brightness mode of the host platform.
+  /// If the platform has no preference, [platformBrightness] defaults to
+  /// [Brightness.light].
+  final Brightness platformBrightness;
+
+  /// The system-reported text scale.
+  final double textScaleFactor;
+
+  /// The full system-reported supported locales of the device.
+  final List<Locale> locales;
+
+  /// The route or path that the embedder requested when the application was
+  /// launched.
+  final String? defaultRouteName;
+}
+
+/// An immutable view configuration.
+class ViewConfiguration {
+  /// A const constructor for an immutable [ViewConfiguration].
+  const ViewConfiguration({
+    this.window,
+    this.devicePixelRatio = 1.0,
+    this.geometry = Rect.zero,
+    this.visible = false,
+    this.viewInsets = WindowPadding.zero,
+    this.viewPadding = WindowPadding.zero,
+    this.systemGestureInsets = WindowPadding.zero,
+    this.padding = WindowPadding.zero,
+  });
+
+  /// Copy this configuration with some fields replaced.
+  ViewConfiguration copyWith({
+    FlutterView? window,
+    double? devicePixelRatio,
+    Rect? geometry,
+    bool? visible,
+    WindowPadding? viewInsets,
+    WindowPadding? viewPadding,
+    WindowPadding? systemGestureInsets,
+    WindowPadding? padding,
+  }) {
+    return ViewConfiguration(
+      window: window ?? this.window,
+      devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
+      geometry: geometry ?? this.geometry,
+      visible: visible ?? this.visible,
+      viewInsets: viewInsets ?? this.viewInsets,
+      viewPadding: viewPadding ?? this.viewPadding,
+      systemGestureInsets: systemGestureInsets ?? this.systemGestureInsets,
+      padding: padding ?? this.padding,
+    );
+  }
+
+  /// The top level view into which the view is placed and its geometry is
+  /// relative to.
+  ///
+  /// If null, then this configuration represents a top level view itself.
+  final FlutterView? window;
+
+  /// The pixel density of the output surface.
+  final double devicePixelRatio;
+
+  /// The geometry requested for the view on the screen or within its parent
+  /// window, in logical pixels.
+  final Rect geometry;
+
+  /// Whether or not the view is currently visible on the screen.
+  final bool visible;
+
+  /// The view insets, as it intersects with [Screen.viewInsets] for the screen
+  /// it is on.
+  ///
+  /// For instance, if the view doesn't overlap the
+  /// [ScreenConfiguration.viewInsets] area, [viewInsets] will be
+  /// [WindowPadding.zero].
+  ///
+  /// The number of physical pixels on each side of this view rectangle into
+  /// which the application can draw, but over which the operating system will
+  /// likely place system UI, such as the keyboard or system menus, that fully
+  /// obscures any content.
+  final WindowPadding viewInsets;
+
+  /// The view insets, as it intersects with [ScreenConfiguration.viewPadding]
+  /// for the screen it is on.
+  ///
+  /// For instance, if the view doesn't overlap the
+  /// [ScreenConfiguration.viewPadding] area, [viewPadding] will be
+  /// [WindowPadding.zero].
+  ///
+  /// The number of physical pixels on each side of this screen rectangle into
+  /// which the application can place a view, but which may be partially
+  /// obscured by system UI (such as the system notification area), or physical
+  /// intrusions in the display (e.g. overscan regions on television screens or
+  /// phone sensor housings).
+  final WindowPadding viewPadding;
+
+  /// The view insets, as it intersects with
+  /// [ScreenConfiguration.systemGestureInsets] for the screen it is on.
+  ///
+  /// For instance, if the view doesn't overlap the
+  /// [ScreenConfiguration.systemGestureInsets] area, [systemGestureInsets] will
+  /// be [WindowPadding.zero].
+  ///
+  /// The number of physical pixels on each side of this screen rectangle into
+  /// which the application can place a view, but where the operating system
+  /// will consume input gestures for the sake of system navigation.
+  final WindowPadding systemGestureInsets;
+
+  /// The view insets, as it intersects with [ScreenConfiguration.padding] for
+  /// the screen it is on.
+  ///
+  /// For instance, if the view doesn't overlap the
+  /// [ScreenConfiguration.padding] area, [padding] will be
+  /// [WindowPadding.zero].
+  ///
+  /// The number of physical pixels on each side of this screen rectangle into
+  /// which the application can place a view, but which may be partially
+  /// obscured by system UI (such as the system notification area), or physical
+  /// intrusions in the display (e.g. overscan regions on television screens or
+  /// phone sensor housings).
+  final WindowPadding padding;
+
+  @override
+  String toString() {
+    return '$runtimeType[window: $window, geometry: $geometry]';
+  }
+}
+
+/// Various important time points in the lifetime of a frame.
+///
+/// [FrameTiming] records a timestamp of each phase for performance analysis.
+enum FramePhase {
+  /// The timestamp of the vsync signal given by the operating system.
+  ///
+  /// See also [FrameTiming.vsyncOverhead].
+  vsyncStart,
+
+  /// When the UI thread starts building a frame.
+  ///
+  /// See also [FrameTiming.buildDuration].
+  buildStart,
+
+  /// When the UI thread finishes building a frame.
+  ///
+  /// See also [FrameTiming.buildDuration].
+  buildFinish,
+
+  /// When the raster thread starts rasterizing a frame.
+  ///
+  /// See also [FrameTiming.rasterDuration].
+  rasterStart,
+
+  /// When the raster thread finishes rasterizing a frame.
+  ///
+  /// See also [FrameTiming.rasterDuration].
+  rasterFinish,
+}
+
+/// Time-related performance metrics of a frame.
+///
+/// If you're using the whole Flutter framework, please use
+/// [SchedulerBinding.addTimingsCallback] to get this. It's preferred over using
+/// [PlatformDispatcher.onReportTimings] directly because
+/// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. If
+/// [SchedulerBinding] is unavailable, then see [PlatformDispatcher.onReportTimings]
+/// for how to get this.
+///
+/// The metrics in debug mode (`flutter run` without any flags) may be very
+/// different from those in profile and release modes due to the debug overhead.
+/// Therefore it's recommended to only monitor and analyze performance metrics
+/// in profile and release modes.
+class FrameTiming {
+  /// Construct [FrameTiming] with raw timestamps in microseconds.
+  ///
+  /// This constructor is used for unit test only. Real [FrameTiming]s should
+  /// be retrieved from [PlatformDispatcher.onReportTimings].
+  factory FrameTiming({
+    required int vsyncStart,
+    required int buildStart,
+    required int buildFinish,
+    required int rasterStart,
+    required int rasterFinish,
+  }) {
+    return FrameTiming._(<int>[
+      vsyncStart,
+      buildStart,
+      buildFinish,
+      rasterStart,
+      rasterFinish
+    ]);
+  }
+
+  /// Construct [FrameTiming] with raw timestamps in microseconds.
+  ///
+  /// List [timestamps] must have the same number of elements as
+  /// [FramePhase.values].
+  ///
+  /// This constructor is usually only called by the Flutter engine, or a test.
+  /// To get the [FrameTiming] of your app, see [PlatformDispatcher.onReportTimings].
+  FrameTiming._(this._timestamps)
+      : assert(_timestamps.length == FramePhase.values.length);
+
+  /// This is a raw timestamp in microseconds from some epoch. The epoch in all
+  /// [FrameTiming] is the same, but it may not match [DateTime]'s epoch.
+  int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index];
+
+  Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]);
+
+  /// The duration to build the frame on the UI thread.
+  ///
+  /// The build starts approximately when [PlatformDispatcher.onBeginFrame] is
+  /// called. The [Duration] in the [PlatformDispatcher.onBeginFrame] callback
+  /// is exactly the `Duration(microseconds:
+  /// timestampInMicroseconds(FramePhase.buildStart))`.
+  ///
+  /// The build finishes when [FlutterView.render] is called.
+  ///
+  /// {@template dart.ui.FrameTiming.fps_smoothness_milliseconds}
+  /// To ensure smooth animations of X fps, this should not exceed 1000/X
+  /// milliseconds.
+  /// {@endtemplate}
+  /// {@template dart.ui.FrameTiming.fps_milliseconds}
+  /// That's about 16ms for 60fps, and 8ms for 120fps.
+  /// {@endtemplate}
+  Duration get buildDuration => _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart);
+
+  /// The duration to rasterize the frame on the raster thread.
+  ///
+  /// {@macro dart.ui.FrameTiming.fps_smoothness_milliseconds}
+  /// {@macro dart.ui.FrameTiming.fps_milliseconds}
+  Duration get rasterDuration => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart);
+
+  /// The duration between receiving the vsync signal and starting building the
+  /// frame.
+  Duration get vsyncOverhead => _rawDuration(FramePhase.buildStart) - _rawDuration(FramePhase.vsyncStart);
+
+  /// The timespan between vsync start and raster finish.
+  ///
+  /// To achieve the lowest latency on an X fps display, this should not exceed
+  /// 1000/X milliseconds.
+  /// {@macro dart.ui.FrameTiming.fps_milliseconds}
+  ///
+  /// See also [vsyncOverhead], [buildDuration] and [rasterDuration].
+  Duration get totalSpan => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.vsyncStart);
+
+  final List<int> _timestamps;  // in microseconds
+
+  String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms';
+
+  @override
+  String toString() {
+    return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, vsyncOverhead: ${_formatMS(vsyncOverhead)}, totalSpan: ${_formatMS(totalSpan)})';
+  }
+}
+
+/// States that an application can be in.
+///
+/// The values below describe notifications from the operating system.
+/// Applications should not expect to always receive all possible
+/// notifications. For example, if the users pulls out the battery from the
+/// device, no notification will be sent before the application is suddenly
+/// terminated, along with the rest of the operating system.
+///
+/// See also:
+///
+///  * [WidgetsBindingObserver], for a mechanism to observe the lifecycle state
+///    from the widgets layer.
+enum AppLifecycleState {
+  /// The application is visible and responding to user input.
+  resumed,
+
+  /// The application is in an inactive state and is not receiving user input.
+  ///
+  /// On iOS, this state corresponds to an app or the Flutter host view running
+  /// in the foreground inactive state. Apps transition to this state when in
+  /// a phone call, responding to a TouchID request, when entering the app
+  /// switcher or the control center, or when the UIViewController hosting the
+  /// Flutter app is transitioning.
+  ///
+  /// On Android, this corresponds to an app or the Flutter host view running
+  /// in the foreground inactive state.  Apps transition to this state when
+  /// another activity is focused, such as a split-screen app, a phone call,
+  /// a picture-in-picture app, a system dialog, or another window.
+  ///
+  /// Apps in this state should assume that they may be [paused] at any time.
+  inactive,
+
+  /// The application is not currently visible to the user, not responding to
+  /// user input, and running in the background.
+  ///
+  /// When the application is in this state, the engine will not call the
+  /// [PlatformDispatcher.onBeginFrame] and [PlatformDispatcher.onDrawFrame]
+  /// callbacks.
+  paused,
+
+  /// The application is still hosted on a flutter engine but is detached from
+  /// any host views.
+  ///
+  /// When the application is in this state, the engine is running without
+  /// a view. It can either be in the progress of attaching a view when engine
+  /// was first initializes, or after the view being destroyed due to a Navigator
+  /// pop.
+  detached,
+}
+
+/// A representation of distances for each of the four edges of a rectangle,
+/// used to encode the view insets and padding that applications should place
+/// around their user interface, as exposed by [FlutterView.viewInsets] and
+/// [FlutterView.padding]. View insets and padding are preferably read via
+/// [MediaQuery.of].
+///
+/// For a generic class that represents distances around a rectangle, see the
+/// [EdgeInsets] class.
+///
+/// See also:
+///
+///  * [WidgetsBindingObserver], for a widgets layer mechanism to receive
+///    notifications when the padding changes.
+///  * [MediaQuery.of], for the preferred mechanism for accessing these values.
+///  * [Scaffold], which automatically applies the padding in material design
+///    applications.
+class WindowPadding {
+  const WindowPadding._({ required this.left, required this.top, required this.right, required this.bottom });
+
+  /// The distance from the left edge to the first unpadded pixel, in physical pixels.
+  final double left;
+
+  /// The distance from the top edge to the first unpadded pixel, in physical pixels.
+  final double top;
+
+  /// The distance from the right edge to the first unpadded pixel, in physical pixels.
+  final double right;
+
+  /// The distance from the bottom edge to the first unpadded pixel, in physical pixels.
+  final double bottom;
+
+  /// A window padding that has zeros for each edge.
+  static const WindowPadding zero = WindowPadding._(left: 0.0, top: 0.0, right: 0.0, bottom: 0.0);
+
+  @override
+  String toString() {
+    return 'WindowPadding(left: $left, top: $top, right: $right, bottom: $bottom)';
+  }
+}
+
+/// An identifier used to select a user's language and formatting preferences.
+///
+/// This represents a [Unicode Language
+/// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier)
+/// (i.e. without Locale extensions), except variants are not supported.
+///
+/// Locales are canonicalized according to the "preferred value" entries in the
+/// [IANA Language Subtag
+/// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry).
+/// For example, `const Locale('he')` and `const Locale('iw')` are equal and
+/// both have the [languageCode] `he`, because `iw` is a deprecated language
+/// subtag that was replaced by the subtag `he`.
+///
+/// See also:
+///
+///  * [PlatformDispatcher.locale], which specifies the system's currently selected
+///    [Locale].
+class Locale {
+  /// Creates a new Locale object. The first argument is the
+  /// primary language subtag, the second is the region (also
+  /// referred to as 'country') subtag.
+  ///
+  /// For example:
+  ///
+  /// ```dart
+  /// const Locale swissFrench = Locale('fr', 'CH');
+  /// const Locale canadianFrench = Locale('fr', 'CA');
+  /// ```
+  ///
+  /// The primary language subtag must not be null. The region subtag is
+  /// optional. When there is no region/country subtag, the parameter should
+  /// be omitted or passed `null` instead of an empty-string.
+  ///
+  /// The subtag values are _case sensitive_ and must be one of the valid
+  /// subtags according to CLDR supplemental data:
+  /// [language](https://github.com/unicode-org/cldr/blob/master/common/validity/language.xml),
+  /// [region](https://github.com/unicode-org/cldr/blob/master/common/validity/region.xml). The
+  /// primary language subtag must be at least two and at most eight lowercase
+  /// letters, but not four letters. The region region subtag must be two
+  /// uppercase letters or three digits. See the [Unicode Language
+  /// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier)
+  /// specification.
+  ///
+  /// Validity is not checked by default, but some methods may throw away
+  /// invalid data.
+  ///
+  /// See also:
+  ///
+  ///  * [Locale.fromSubtags], which also allows a [scriptCode] to be
+  ///    specified.
+  const Locale(
+    this._languageCode, [
+    this._countryCode,
+  ]) : assert(_languageCode != null), // ignore: unnecessary_null_comparison
+       assert(_languageCode != ''),
+       scriptCode = null;
+
+  /// Creates a new Locale object.
+  ///
+  /// The keyword arguments specify the subtags of the Locale.
+  ///
+  /// The subtag values are _case sensitive_ and must be valid subtags according
+  /// to CLDR supplemental data:
+  /// [language](https://github.com/unicode-org/cldr/blob/master/common/validity/language.xml),
+  /// [script](https://github.com/unicode-org/cldr/blob/master/common/validity/script.xml) and
+  /// [region](https://github.com/unicode-org/cldr/blob/master/common/validity/region.xml) for
+  /// each of languageCode, scriptCode and countryCode respectively.
+  ///
+  /// The [languageCode] subtag is optional. When there is no language subtag,
+  /// the parameter should be omitted or set to "und". When not supplied, the
+  /// [languageCode] defaults to "und", an undefined language code.
+  ///
+  /// The [countryCode] subtag is optional. When there is no country subtag,
+  /// the parameter should be omitted or passed `null` instead of an empty-string.
+  ///
+  /// Validity is not checked by default, but some methods may throw away
+  /// invalid data.
+  const Locale.fromSubtags({
+    String languageCode = 'und',
+    this.scriptCode,
+    String? countryCode,
+  }) : assert(languageCode != null), // ignore: unnecessary_null_comparison
+       assert(languageCode != ''),
+       _languageCode = languageCode,
+       assert(scriptCode != ''),
+       assert(countryCode != ''),
+       _countryCode = countryCode;
+
+  /// The primary language subtag for the locale.
+  ///
+  /// This must not be null. It may be 'und', representing 'undefined'.
+  ///
+  /// This is expected to be string registered in the [IANA Language Subtag
+  /// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)
+  /// with the type "language". The string specified must match the case of the
+  /// string in the registry.
+  ///
+  /// Language subtags that are deprecated in the registry and have a preferred
+  /// code are changed to their preferred code. For example, `const
+  /// Locale('he')` and `const Locale('iw')` are equal, and both have the
+  /// [languageCode] `he`, because `iw` is a deprecated language subtag that was
+  /// replaced by the subtag `he`.
+  ///
+  /// This must be a valid Unicode Language subtag as listed in [Unicode CLDR
+  /// supplemental
+  /// data](https://github.com/unicode-org/cldr/blob/master/common/validity/language.xml).
+  ///
+  /// See also:
+  ///
+  ///  * [Locale.fromSubtags], which describes the conventions for creating
+  ///    [Locale] objects.
+  String get languageCode => _deprecatedLanguageSubtagMap[_languageCode] ?? _languageCode;
+  final String _languageCode;
+
+  // This map is generated by //flutter/tools/gen_locale.dart
+  // Mappings generated for language subtag registry as of 2019-02-27.
+  static const Map<String, String> _deprecatedLanguageSubtagMap = <String, String>{
+    'in': 'id', // Indonesian; deprecated 1989-01-01
+    'iw': 'he', // Hebrew; deprecated 1989-01-01
+    'ji': 'yi', // Yiddish; deprecated 1989-01-01
+    'jw': 'jv', // Javanese; deprecated 2001-08-13
+    'mo': 'ro', // Moldavian, Moldovan; deprecated 2008-11-22
+    'aam': 'aas', // Aramanik; deprecated 2015-02-12
+    'adp': 'dz', // Adap; deprecated 2015-02-12
+    'aue': 'ktz', // ǂKxʼauǁʼein; deprecated 2015-02-12
+    'ayx': 'nun', // Ayi (China); deprecated 2011-08-16
+    'bgm': 'bcg', // Baga Mboteni; deprecated 2016-05-30
+    'bjd': 'drl', // Bandjigali; deprecated 2012-08-12
+    'ccq': 'rki', // Chaungtha; deprecated 2012-08-12
+    'cjr': 'mom', // Chorotega; deprecated 2010-03-11
+    'cka': 'cmr', // Khumi Awa Chin; deprecated 2012-08-12
+    'cmk': 'xch', // Chimakum; deprecated 2010-03-11
+    'coy': 'pij', // Coyaima; deprecated 2016-05-30
+    'cqu': 'quh', // Chilean Quechua; deprecated 2016-05-30
+    'drh': 'khk', // Darkhat; deprecated 2010-03-11
+    'drw': 'prs', // Darwazi; deprecated 2010-03-11
+    'gav': 'dev', // Gabutamon; deprecated 2010-03-11
+    'gfx': 'vaj', // Mangetti Dune ǃXung; deprecated 2015-02-12
+    'ggn': 'gvr', // Eastern Gurung; deprecated 2016-05-30
+    'gti': 'nyc', // Gbati-ri; deprecated 2015-02-12
+    'guv': 'duz', // Gey; deprecated 2016-05-30
+    'hrr': 'jal', // Horuru; deprecated 2012-08-12
+    'ibi': 'opa', // Ibilo; deprecated 2012-08-12
+    'ilw': 'gal', // Talur; deprecated 2013-09-10
+    'jeg': 'oyb', // Jeng; deprecated 2017-02-23
+    'kgc': 'tdf', // Kasseng; deprecated 2016-05-30
+    'kgh': 'kml', // Upper Tanudan Kalinga; deprecated 2012-08-12
+    'koj': 'kwv', // Sara Dunjo; deprecated 2015-02-12
+    'krm': 'bmf', // Krim; deprecated 2017-02-23
+    'ktr': 'dtp', // Kota Marudu Tinagas; deprecated 2016-05-30
+    'kvs': 'gdj', // Kunggara; deprecated 2016-05-30
+    'kwq': 'yam', // Kwak; deprecated 2015-02-12
+    'kxe': 'tvd', // Kakihum; deprecated 2015-02-12
+    'kzj': 'dtp', // Coastal Kadazan; deprecated 2016-05-30
+    'kzt': 'dtp', // Tambunan Dusun; deprecated 2016-05-30
+    'lii': 'raq', // Lingkhim; deprecated 2015-02-12
+    'lmm': 'rmx', // Lamam; deprecated 2014-02-28
+    'meg': 'cir', // Mea; deprecated 2013-09-10
+    'mst': 'mry', // Cataelano Mandaya; deprecated 2010-03-11
+    'mwj': 'vaj', // Maligo; deprecated 2015-02-12
+    'myt': 'mry', // Sangab Mandaya; deprecated 2010-03-11
+    'nad': 'xny', // Nijadali; deprecated 2016-05-30
+    'ncp': 'kdz', // Ndaktup; deprecated 2018-03-08
+    'nnx': 'ngv', // Ngong; deprecated 2015-02-12
+    'nts': 'pij', // Natagaimas; deprecated 2016-05-30
+    'oun': 'vaj', // ǃOǃung; deprecated 2015-02-12
+    'pcr': 'adx', // Panang; deprecated 2013-09-10
+    'pmc': 'huw', // Palumata; deprecated 2016-05-30
+    'pmu': 'phr', // Mirpur Panjabi; deprecated 2015-02-12
+    'ppa': 'bfy', // Pao; deprecated 2016-05-30
+    'ppr': 'lcq', // Piru; deprecated 2013-09-10
+    'pry': 'prt', // Pray 3; deprecated 2016-05-30
+    'puz': 'pub', // Purum Naga; deprecated 2014-02-28
+    'sca': 'hle', // Sansu; deprecated 2012-08-12
+    'skk': 'oyb', // Sok; deprecated 2017-02-23
+    'tdu': 'dtp', // Tempasuk Dusun; deprecated 2016-05-30
+    'thc': 'tpo', // Tai Hang Tong; deprecated 2016-05-30
+    'thx': 'oyb', // The; deprecated 2015-02-12
+    'tie': 'ras', // Tingal; deprecated 2011-08-16
+    'tkk': 'twm', // Takpa; deprecated 2011-08-16
+    'tlw': 'weo', // South Wemale; deprecated 2012-08-12
+    'tmp': 'tyj', // Tai Mène; deprecated 2016-05-30
+    'tne': 'kak', // Tinoc Kallahan; deprecated 2016-05-30
+    'tnf': 'prs', // Tangshewi; deprecated 2010-03-11
+    'tsf': 'taj', // Southwestern Tamang; deprecated 2015-02-12
+    'uok': 'ema', // Uokha; deprecated 2015-02-12
+    'xba': 'cax', // Kamba (Brazil); deprecated 2016-05-30
+    'xia': 'acn', // Xiandao; deprecated 2013-09-10
+    'xkh': 'waw', // Karahawyana; deprecated 2016-05-30
+    'xsj': 'suj', // Subi; deprecated 2015-02-12
+    'ybd': 'rki', // Yangbye; deprecated 2012-08-12
+    'yma': 'lrr', // Yamphe; deprecated 2012-08-12
+    'ymt': 'mtm', // Mator-Taygi-Karagas; deprecated 2015-02-12
+    'yos': 'zom', // Yos; deprecated 2013-09-10
+    'yuu': 'yug', // Yugh; deprecated 2014-02-28
+  };
+
+  /// The script subtag for the locale.
+  ///
+  /// This may be null, indicating that there is no specified script subtag.
+  ///
+  /// This must be a valid Unicode Language Identifier script subtag as listed
+  /// in [Unicode CLDR supplemental
+  /// data](https://github.com/unicode-org/cldr/blob/master/common/validity/script.xml).
+  ///
+  /// See also:
+  ///
+  ///  * [Locale.fromSubtags], which describes the conventions for creating
+  ///    [Locale] objects.
+  final String? scriptCode;
+
+  /// The region subtag for the locale.
+  ///
+  /// This may be null, indicating that there is no specified region subtag.
+  ///
+  /// This is expected to be string registered in the [IANA Language Subtag
+  /// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)
+  /// with the type "region". The string specified must match the case of the
+  /// string in the registry.
+  ///
+  /// Region subtags that are deprecated in the registry and have a preferred
+  /// code are changed to their preferred code. For example, `const Locale('de',
+  /// 'DE')` and `const Locale('de', 'DD')` are equal, and both have the
+  /// [countryCode] `DE`, because `DD` is a deprecated language subtag that was
+  /// replaced by the subtag `DE`.
+  ///
+  /// See also:
+  ///
+  ///  * [Locale.fromSubtags], which describes the conventions for creating
+  ///    [Locale] objects.
+  String? get countryCode => _deprecatedRegionSubtagMap[_countryCode] ?? _countryCode;
+  final String? _countryCode;
+
+  // This map is generated by //flutter/tools/gen_locale.dart
+  // Mappings generated for language subtag registry as of 2019-02-27.
+  static const Map<String, String> _deprecatedRegionSubtagMap = <String, String>{
+    'BU': 'MM', // Burma; deprecated 1989-12-05
+    'DD': 'DE', // German Democratic Republic; deprecated 1990-10-30
+    'FX': 'FR', // Metropolitan France; deprecated 1997-07-14
+    'TP': 'TL', // East Timor; deprecated 2002-05-20
+    'YD': 'YE', // Democratic Yemen; deprecated 1990-08-14
+    'ZR': 'CD', // Zaire; deprecated 1997-07-14
+  };
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other is! Locale) {
+      return false;
+    }
+    final String? countryCode = _countryCode;
+    final String? otherCountryCode = other.countryCode;
+    return other.languageCode == languageCode
+        && other.scriptCode == scriptCode // scriptCode cannot be ''
+        && (other.countryCode == countryCode // Treat '' as equal to null.
+            || otherCountryCode != null && otherCountryCode.isEmpty && countryCode == null
+            || countryCode != null && countryCode.isEmpty && other.countryCode == null);
+  }
+
+  @override
+  int get hashCode => hashValues(languageCode, scriptCode, countryCode == '' ? null : countryCode);
+
+  static Locale? _cachedLocale;
+  static String? _cachedLocaleString;
+
+  /// Returns a string representing the locale.
+  ///
+  /// This identifier happens to be a valid Unicode Locale Identifier using
+  /// underscores as separator, however it is intended to be used for debugging
+  /// purposes only. For parsable results, use [toLanguageTag] instead.
+  @keepToString
+  @override
+  String toString() {
+    if (!identical(_cachedLocale, this)) {
+      _cachedLocale = this;
+      _cachedLocaleString = _rawToString('_');
+    }
+    return _cachedLocaleString!;
+  }
+
+  /// Returns a syntactically valid Unicode BCP47 Locale Identifier.
+  ///
+  /// Some examples of such identifiers: "en", "es-419", "hi-Deva-IN" and
+  /// "zh-Hans-CN". See http://www.unicode.org/reports/tr35/ for technical
+  /// details.
+  String toLanguageTag() => _rawToString('-');
+
+  String _rawToString(String separator) {
+    final StringBuffer out = StringBuffer(languageCode);
+    if (scriptCode != null && scriptCode!.isNotEmpty)
+      out.write('$separator$scriptCode');
+    if (_countryCode != null && _countryCode!.isNotEmpty)
+      out.write('$separator$countryCode');
+    return out.toString();
+  }
+}
\ No newline at end of file
diff --git a/lib/src/ui/plugins.dart b/lib/src/ui/plugins.dart
new file mode 100644
index 0000000..47fe9dd
--- /dev/null
+++ b/lib/src/ui/plugins.dart
@@ -0,0 +1,84 @@
+// 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.
+
+
+
+part of dart.ui;
+
+/// A wrapper for a raw callback handle.
+///
+/// This is the return type for [PluginUtilities.getCallbackHandle].
+class CallbackHandle {
+  /// Create an instance using a raw callback handle.
+  ///
+  /// Only values produced by a call to [CallbackHandle.toRawHandle] should be
+  /// used, otherwise this object will be an invalid handle.
+  CallbackHandle.fromRawHandle(this._handle)
+      : assert(_handle != null, "'_handle' must not be null."); // ignore: unnecessary_null_comparison
+
+  final int _handle;
+
+  /// Get the raw callback handle to pass over a [MethodChannel] or [SendPort]
+  /// (to pass to another [Isolate]).
+  int toRawHandle() => _handle;
+
+  @override
+  bool operator ==(Object other) {
+    if (runtimeType != other.runtimeType)
+      return false;
+    return other is CallbackHandle
+        && other._handle == _handle;
+  }
+
+  @override
+  int get hashCode => _handle.hashCode;
+}
+
+/// Functionality for Flutter plugin authors.
+///
+/// See also:
+///
+///  * [IsolateNameServer], which provides utilities for dealing with
+///    [Isolate]s.
+class PluginUtilities {
+  // This class is only a namespace, and should not be instantiated or
+  // extended directly.
+  factory PluginUtilities._() => throw UnsupportedError('Namespace');
+
+  static Map<Function, CallbackHandle?> _forwardCache =
+      <Function, CallbackHandle?>{};
+  static Map<CallbackHandle, Function?> _backwardCache =
+      <CallbackHandle, Function?>{};
+
+  /// Get a handle to a named top-level or static callback function which can
+  /// be easily passed between isolates.
+  ///
+  /// The `callback` argument must not be null.
+  ///
+  /// Returns a [CallbackHandle] that can be provided to
+  /// [PluginUtilities.getCallbackFromHandle] to retrieve a tear-off of the
+  /// original callback. If `callback` is not a top-level or static function,
+  /// null is returned.
+  static CallbackHandle? getCallbackHandle(Function callback) {
+    assert(callback != null, "'callback' must not be null."); // ignore: unnecessary_null_comparison
+    return _forwardCache.putIfAbsent(callback, () {
+      final int? handle = _getCallbackHandle(callback);
+      return handle != null ? CallbackHandle.fromRawHandle(handle) : null;
+    });
+  }
+
+  /// Get a tear-off of a named top-level or static callback represented by a
+  /// handle.
+  ///
+  /// The `handle` argument must not be null.
+  ///
+  /// If `handle` is not a valid handle returned by
+  /// [PluginUtilities.getCallbackHandle], null is returned. Otherwise, a
+  /// tear-off of the callback associated with `handle` is returned.
+  static Function? getCallbackFromHandle(CallbackHandle handle) {
+    assert(handle != null, "'handle' must not be null."); // ignore: unnecessary_null_comparison
+    return _backwardCache.putIfAbsent(
+        handle, () => _getCallbackFromHandle(handle.toRawHandle()));
+  }
+}
diff --git a/lib/src/ui/pointer.dart b/lib/src/ui/pointer.dart
new file mode 100644
index 0000000..af8b14e
--- /dev/null
+++ b/lib/src/ui/pointer.dart
@@ -0,0 +1,315 @@
+// 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.
+
+
+
+part of dart.ui;
+
+/// How the pointer has changed since the last report.
+enum PointerChange {
+  /// The input from the pointer is no longer directed towards this receiver.
+  cancel,
+
+  /// The device has started tracking the pointer.
+  ///
+  /// For example, the pointer might be hovering above the device, having not yet
+  /// made contact with the surface of the device.
+  add,
+
+  /// The device is no longer tracking the pointer.
+  ///
+  /// For example, the pointer might have drifted out of the device's hover
+  /// detection range or might have been disconnected from the system entirely.
+  remove,
+
+  /// The pointer has moved with respect to the device while not in contact with
+  /// the device.
+  hover,
+
+  /// The pointer has made contact with the device.
+  down,
+
+  /// The pointer has moved with respect to the device while in contact with the
+  /// device.
+  move,
+
+  /// The pointer has stopped making contact with the device.
+  up,
+}
+
+/// The kind of pointer device.
+enum PointerDeviceKind {
+  /// A touch-based pointer device.
+  touch,
+
+  /// A mouse-based pointer device.
+  mouse,
+
+  /// A pointer device with a stylus.
+  stylus,
+
+  /// A pointer device with a stylus that has been inverted.
+  invertedStylus,
+
+  /// An unknown pointer device.
+  unknown
+}
+
+/// The kind of pointer signal event.
+enum PointerSignalKind {
+  /// The event is not associated with a pointer signal.
+  none,
+
+  /// A pointer-generated scroll (e.g., mouse wheel or trackpad scroll).
+  scroll,
+
+  /// An unknown pointer signal kind.
+  unknown
+}
+
+/// Information about the state of a pointer.
+class PointerData {
+  /// Creates an object that represents the state of a pointer.
+  const PointerData({
+    this.embedderId = 0,
+    this.timeStamp = Duration.zero,
+    this.change = PointerChange.cancel,
+    this.kind = PointerDeviceKind.touch,
+    this.signalKind,
+    this.device = 0,
+    this.pointerIdentifier = 0,
+    this.physicalX = 0.0,
+    this.physicalY = 0.0,
+    this.physicalDeltaX = 0.0,
+    this.physicalDeltaY = 0.0,
+    this.buttons = 0,
+    this.obscured = false,
+    this.synthesized = false,
+    this.pressure = 0.0,
+    this.pressureMin = 0.0,
+    this.pressureMax = 0.0,
+    this.distance = 0.0,
+    this.distanceMax = 0.0,
+    this.size = 0.0,
+    this.radiusMajor = 0.0,
+    this.radiusMinor = 0.0,
+    this.radiusMin = 0.0,
+    this.radiusMax = 0.0,
+    this.orientation = 0.0,
+    this.tilt = 0.0,
+    this.platformData = 0,
+    this.scrollDeltaX = 0.0,
+    this.scrollDeltaY = 0.0,
+  });
+
+  /// Unique identifier that ties the [PointerEvent] to embedder event created it.
+  ///
+  /// No two pointer events can have the same [embedderId]. This is different from
+  /// [pointerIdentifier] - used for hit-testing, whereas [embedderId] is used to
+  /// identify the platform event.
+  final int embedderId;
+
+  /// Time of event dispatch, relative to an arbitrary timeline.
+  final Duration timeStamp;
+
+  /// How the pointer has changed since the last report.
+  final PointerChange change;
+
+  /// The kind of input device for which the event was generated.
+  final PointerDeviceKind kind;
+
+  /// The kind of signal for a pointer signal event.
+  final PointerSignalKind? signalKind;
+
+  /// Unique identifier for the pointing device, reused across interactions.
+  final int device;
+
+  /// Unique identifier for the pointer.
+  ///
+  /// This field changes for each new pointer down event. Framework uses this
+  /// identifier to determine hit test result.
+  final int pointerIdentifier;
+
+  /// X coordinate of the position of the pointer, in physical pixels in the
+  /// global coordinate space.
+  final double physicalX;
+
+  /// Y coordinate of the position of the pointer, in physical pixels in the
+  /// global coordinate space.
+  final double physicalY;
+
+  /// The distance of pointer movement on X coordinate in physical pixels.
+  final double physicalDeltaX;
+
+  /// The distance of pointer movement on Y coordinate in physical pixels.
+  final double physicalDeltaY;
+
+  /// Bit field using the *Button constants (primaryMouseButton,
+  /// secondaryStylusButton, etc). For example, if this has the value 6 and the
+  /// [kind] is [PointerDeviceKind.invertedStylus], then this indicates an
+  /// upside-down stylus with both its primary and secondary buttons pressed.
+  final int buttons;
+
+  /// Set if an application from a different security domain is in any way
+  /// obscuring this application's window. (Aspirational; not currently
+  /// implemented.)
+  final bool obscured;
+
+  /// Set if this pointer data was synthesized by pointer data packet converter.
+  /// pointer data packet converter will synthesize additional pointer datas if
+  /// the input sequence of pointer data is illegal.
+  ///
+  /// For example, a down pointer data will be synthesized if the converter receives
+  /// a move pointer data while the pointer is not previously down.
+  final bool synthesized;
+
+  /// The pressure of the touch as a number ranging from 0.0, indicating a touch
+  /// with no discernible pressure, to 1.0, indicating a touch with "normal"
+  /// pressure, and possibly beyond, indicating a stronger touch. For devices
+  /// that do not detect pressure (e.g. mice), returns 1.0.
+  final double pressure;
+
+  /// The minimum value that [pressure] can return for this pointer. For devices
+  /// that do not detect pressure (e.g. mice), returns 1.0. This will always be
+  /// a number less than or equal to 1.0.
+  final double pressureMin;
+
+  /// The maximum value that [pressure] can return for this pointer. For devices
+  /// that do not detect pressure (e.g. mice), returns 1.0. This will always be
+  /// a greater than or equal to 1.0.
+  final double pressureMax;
+
+  /// The distance of the detected object from the input surface (e.g. the
+  /// distance of a stylus or finger from a touch screen), in arbitrary units on
+  /// an arbitrary (not necessarily linear) scale. If the pointer is down, this
+  /// is 0.0 by definition.
+  final double distance;
+
+  /// The maximum value that a distance can return for this pointer. If this
+  /// input device cannot detect "hover touch" input events, then this will be
+  /// 0.0.
+  final double distanceMax;
+
+  /// The area of the screen being pressed, scaled to a value between 0 and 1.
+  /// The value of size can be used to determine fat touch events. This value
+  /// is only set on Android, and is a device specific approximation within
+  /// the range of detectable values. So, for example, the value of 0.1 could
+  /// mean a touch with the tip of the finger, 0.2 a touch with full finger,
+  /// and 0.3 the full palm.
+  final double size;
+
+  /// The radius of the contact ellipse along the major axis, in logical pixels.
+  final double radiusMajor;
+
+  /// The radius of the contact ellipse along the minor axis, in logical pixels.
+  final double radiusMinor;
+
+  /// The minimum value that could be reported for radiusMajor and radiusMinor
+  /// for this pointer, in logical pixels.
+  final double radiusMin;
+
+  /// The minimum value that could be reported for radiusMajor and radiusMinor
+  /// for this pointer, in logical pixels.
+  final double radiusMax;
+
+  /// For PointerDeviceKind.touch events:
+  ///
+  /// The angle of the contact ellipse, in radius in the range:
+  ///
+  ///    -pi/2 < orientation <= pi/2
+  ///
+  /// ...giving the angle of the major axis of the ellipse with the y-axis
+  /// (negative angles indicating an orientation along the top-left /
+  /// bottom-right diagonal, positive angles indicating an orientation along the
+  /// top-right / bottom-left diagonal, and zero indicating an orientation
+  /// parallel with the y-axis).
+  ///
+  /// For PointerDeviceKind.stylus and PointerDeviceKind.invertedStylus events:
+  ///
+  /// The angle of the stylus, in radians in the range:
+  ///
+  ///    -pi < orientation <= pi
+  ///
+  /// ...giving the angle of the axis of the stylus projected onto the input
+  /// surface, relative to the positive y-axis of that surface (thus 0.0
+  /// indicates the stylus, if projected onto that surface, would go from the
+  /// contact point vertically up in the positive y-axis direction, pi would
+  /// indicate that the stylus would go down in the negative y-axis direction;
+  /// pi/4 would indicate that the stylus goes up and to the right, -pi/2 would
+  /// indicate that the stylus goes to the left, etc).
+  final double orientation;
+
+  /// For PointerDeviceKind.stylus and PointerDeviceKind.invertedStylus events:
+  ///
+  /// The angle of the stylus, in radians in the range:
+  ///
+  ///    0 <= tilt <= pi/2
+  ///
+  /// ...giving the angle of the axis of the stylus, relative to the axis
+  /// perpendicular to the input surface (thus 0.0 indicates the stylus is
+  /// orthogonal to the plane of the input surface, while pi/2 indicates that
+  /// the stylus is flat on that surface).
+  final double tilt;
+
+  /// Opaque platform-specific data associated with the event.
+  final int platformData;
+
+  /// For events with signalKind of PointerSignalKind.scroll:
+  ///
+  /// The amount to scroll in the x direction, in physical pixels.
+  final double scrollDeltaX;
+
+  /// For events with signalKind of PointerSignalKind.scroll:
+  ///
+  /// The amount to scroll in the y direction, in physical pixels.
+  final double scrollDeltaY;
+
+  @override
+  String toString() => 'PointerData(x: $physicalX, y: $physicalY)';
+
+  /// Returns a complete textual description of the information in this object.
+  String toStringFull() {
+    return '$runtimeType('
+             'embedderId: $embedderId, '
+             'timeStamp: $timeStamp, '
+             'change: $change, '
+             'kind: $kind, '
+             'signalKind: $signalKind, '
+             'device: $device, '
+             'pointerIdentifier: $pointerIdentifier, '
+             'physicalX: $physicalX, '
+             'physicalY: $physicalY, '
+             'physicalDeltaX: $physicalDeltaX, '
+             'physicalDeltaY: $physicalDeltaY, '
+             'buttons: $buttons, '
+             'synthesized: $synthesized, '
+             'pressure: $pressure, '
+             'pressureMin: $pressureMin, '
+             'pressureMax: $pressureMax, '
+             'distance: $distance, '
+             'distanceMax: $distanceMax, '
+             'size: $size, '
+             'radiusMajor: $radiusMajor, '
+             'radiusMinor: $radiusMinor, '
+             'radiusMin: $radiusMin, '
+             'radiusMax: $radiusMax, '
+             'orientation: $orientation, '
+             'tilt: $tilt, '
+             'platformData: $platformData, '
+             'scrollDeltaX: $scrollDeltaX, '
+             'scrollDeltaY: $scrollDeltaY'
+           ')';
+  }
+}
+
+/// A sequence of reports about the state of pointers.
+class PointerDataPacket {
+  /// Creates a packet of pointer data reports.
+  const PointerDataPacket({ this.data = const <PointerData>[] }) : assert(data != null); // ignore: unnecessary_null_comparison
+
+  /// Data about the individual pointers in this packet.
+  ///
+  /// This list might contain multiple pieces of data about the same pointer.
+  final List<PointerData> data;
+}
diff --git a/lib/src/ui/semantics.dart b/lib/src/ui/semantics.dart
new file mode 100644
index 0000000..5a08f50
--- /dev/null
+++ b/lib/src/ui/semantics.dart
@@ -0,0 +1,866 @@
+// 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.
+
+
+
+part of dart.ui;
+
+/// The possible actions that can be conveyed from the operating system
+/// accessibility APIs to a semantics node.
+///
+/// \warning When changes are made to this class, the equivalent APIs in
+///         `lib/ui/semantics/semantics_node.h` and in each of the embedders
+///         *must* be updated.
+/// See also:
+///   - file://./../../lib/ui/semantics/semantics_node.h
+class SemanticsAction {
+  const SemanticsAction._(this.index) : assert(index != null); // ignore: unnecessary_null_comparison
+
+  static const int _kTapIndex = 1 << 0;
+  static const int _kLongPressIndex = 1 << 1;
+  static const int _kScrollLeftIndex = 1 << 2;
+  static const int _kScrollRightIndex = 1 << 3;
+  static const int _kScrollUpIndex = 1 << 4;
+  static const int _kScrollDownIndex = 1 << 5;
+  static const int _kIncreaseIndex = 1 << 6;
+  static const int _kDecreaseIndex = 1 << 7;
+  static const int _kShowOnScreenIndex = 1 << 8;
+  static const int _kMoveCursorForwardByCharacterIndex = 1 << 9;
+  static const int _kMoveCursorBackwardByCharacterIndex = 1 << 10;
+  static const int _kSetSelectionIndex = 1 << 11;
+  static const int _kCopyIndex = 1 << 12;
+  static const int _kCutIndex = 1 << 13;
+  static const int _kPasteIndex = 1 << 14;
+  static const int _kDidGainAccessibilityFocusIndex = 1 << 15;
+  static const int _kDidLoseAccessibilityFocusIndex = 1 << 16;
+  static const int _kCustomAction = 1 << 17;
+  static const int _kDismissIndex = 1 << 18;
+  static const int _kMoveCursorForwardByWordIndex = 1 << 19;
+  static const int _kMoveCursorBackwardByWordIndex = 1 << 20;
+  // READ THIS: if you add an action here, you MUST update the
+  // numSemanticsActions value in testing/dart/semantics_test.dart, or tests
+  // will fail.
+
+  /// The numerical value for this action.
+  ///
+  /// Each action has one bit set in this bit field.
+  final int index;
+
+  /// The equivalent of a user briefly tapping the screen with the finger
+  /// without moving it.
+  static const SemanticsAction tap = SemanticsAction._(_kTapIndex);
+
+  /// The equivalent of a user pressing and holding the screen with the finger
+  /// for a few seconds without moving it.
+  static const SemanticsAction longPress = SemanticsAction._(_kLongPressIndex);
+
+  /// The equivalent of a user moving their finger across the screen from right
+  /// to left.
+  ///
+  /// This action should be recognized by controls that are horizontally
+  /// scrollable.
+  static const SemanticsAction scrollLeft = SemanticsAction._(_kScrollLeftIndex);
+
+  /// The equivalent of a user moving their finger across the screen from left
+  /// to right.
+  ///
+  /// This action should be recognized by controls that are horizontally
+  /// scrollable.
+  static const SemanticsAction scrollRight = SemanticsAction._(_kScrollRightIndex);
+
+  /// The equivalent of a user moving their finger across the screen from
+  /// bottom to top.
+  ///
+  /// This action should be recognized by controls that are vertically
+  /// scrollable.
+  static const SemanticsAction scrollUp = SemanticsAction._(_kScrollUpIndex);
+
+  /// The equivalent of a user moving their finger across the screen from top
+  /// to bottom.
+  ///
+  /// This action should be recognized by controls that are vertically
+  /// scrollable.
+  static const SemanticsAction scrollDown = SemanticsAction._(_kScrollDownIndex);
+
+  /// A request to increase the value represented by the semantics node.
+  ///
+  /// For example, this action might be recognized by a slider control.
+  static const SemanticsAction increase = SemanticsAction._(_kIncreaseIndex);
+
+  /// A request to decrease the value represented by the semantics node.
+  ///
+  /// For example, this action might be recognized by a slider control.
+  static const SemanticsAction decrease = SemanticsAction._(_kDecreaseIndex);
+
+  /// A request to fully show the semantics node on screen.
+  ///
+  /// For example, this action might be send to a node in a scrollable list that
+  /// is partially off screen to bring it on screen.
+  static const SemanticsAction showOnScreen = SemanticsAction._(_kShowOnScreenIndex);
+
+  /// Move the cursor forward by one character.
+  ///
+  /// This is for example used by the cursor control in text fields.
+  ///
+  /// The action includes a boolean argument, which indicates whether the cursor
+  /// movement should extend (or start) a selection.
+  static const SemanticsAction moveCursorForwardByCharacter = SemanticsAction._(_kMoveCursorForwardByCharacterIndex);
+
+  /// Move the cursor backward by one character.
+  ///
+  /// This is for example used by the cursor control in text fields.
+  ///
+  /// The action includes a boolean argument, which indicates whether the cursor
+  /// movement should extend (or start) a selection.
+  static const SemanticsAction moveCursorBackwardByCharacter = SemanticsAction._(_kMoveCursorBackwardByCharacterIndex);
+
+  /// Set the text selection to the given range.
+  ///
+  /// The provided argument is a Map<String, int> which includes the keys `base`
+  /// and `extent` indicating where the selection within the `value` of the
+  /// semantics node should start and where it should end. Values for both
+  /// keys can range from 0 to length of `value` (inclusive).
+  ///
+  /// Setting `base` and `extent` to the same value will move the cursor to
+  /// that position (without selecting anything).
+  static const SemanticsAction setSelection = SemanticsAction._(_kSetSelectionIndex);
+
+  /// Copy the current selection to the clipboard.
+  static const SemanticsAction copy = SemanticsAction._(_kCopyIndex);
+
+  /// Cut the current selection and place it in the clipboard.
+  static const SemanticsAction cut = SemanticsAction._(_kCutIndex);
+
+  /// Paste the current content of the clipboard.
+  static const SemanticsAction paste = SemanticsAction._(_kPasteIndex);
+
+  /// Indicates that the node has gained accessibility focus.
+  ///
+  /// This handler is invoked when the node annotated with this handler gains
+  /// the accessibility focus. The accessibility focus is the
+  /// green (on Android with TalkBack) or black (on iOS with VoiceOver)
+  /// rectangle shown on screen to indicate what element an accessibility
+  /// user is currently interacting with.
+  ///
+  /// The accessibility focus is different from the input focus. The input focus
+  /// is usually held by the element that currently responds to keyboard inputs.
+  /// Accessibility focus and input focus can be held by two different nodes!
+  static const SemanticsAction didGainAccessibilityFocus = SemanticsAction._(_kDidGainAccessibilityFocusIndex);
+
+  /// Indicates that the node has lost accessibility focus.
+  ///
+  /// This handler is invoked when the node annotated with this handler
+  /// loses the accessibility focus. The accessibility focus is
+  /// the green (on Android with TalkBack) or black (on iOS with VoiceOver)
+  /// rectangle shown on screen to indicate what element an accessibility
+  /// user is currently interacting with.
+  ///
+  /// The accessibility focus is different from the input focus. The input focus
+  /// is usually held by the element that currently responds to keyboard inputs.
+  /// Accessibility focus and input focus can be held by two different nodes!
+  static const SemanticsAction didLoseAccessibilityFocus = SemanticsAction._(_kDidLoseAccessibilityFocusIndex);
+
+  /// Indicates that the user has invoked a custom accessibility action.
+  ///
+  /// This handler is added automatically whenever a custom accessibility
+  /// action is added to a semantics node.
+  static const SemanticsAction customAction = SemanticsAction._(_kCustomAction);
+
+  /// A request that the node should be dismissed.
+  ///
+  /// A [SnackBar], for example, may have a dismiss action to indicate to the
+  /// user that it can be removed after it is no longer relevant. On Android,
+  /// (with TalkBack) special hint text is spoken when focusing the node and
+  /// a custom action is available in the local context menu. On iOS,
+  /// (with VoiceOver) users can perform a standard gesture to dismiss it.
+  static const SemanticsAction dismiss = SemanticsAction._(_kDismissIndex);
+
+  /// Move the cursor forward by one word.
+  ///
+  /// This is for example used by the cursor control in text fields.
+  ///
+  /// The action includes a boolean argument, which indicates whether the cursor
+  /// movement should extend (or start) a selection.
+  static const SemanticsAction moveCursorForwardByWord = SemanticsAction._(_kMoveCursorForwardByWordIndex);
+
+  /// Move the cursor backward by one word.
+  ///
+  /// This is for example used by the cursor control in text fields.
+  ///
+  /// The action includes a boolean argument, which indicates whether the cursor
+  /// movement should extend (or start) a selection.
+  static const SemanticsAction moveCursorBackwardByWord = SemanticsAction._(_kMoveCursorBackwardByWordIndex);
+
+  /// The possible semantics actions.
+  ///
+  /// The map's key is the [index] of the action and the value is the action
+  /// itself.
+  static const Map<int, SemanticsAction> values = <int, SemanticsAction>{
+    _kTapIndex: tap,
+    _kLongPressIndex: longPress,
+    _kScrollLeftIndex: scrollLeft,
+    _kScrollRightIndex: scrollRight,
+    _kScrollUpIndex: scrollUp,
+    _kScrollDownIndex: scrollDown,
+    _kIncreaseIndex: increase,
+    _kDecreaseIndex: decrease,
+    _kShowOnScreenIndex: showOnScreen,
+    _kMoveCursorForwardByCharacterIndex: moveCursorForwardByCharacter,
+    _kMoveCursorBackwardByCharacterIndex: moveCursorBackwardByCharacter,
+    _kSetSelectionIndex: setSelection,
+    _kCopyIndex: copy,
+    _kCutIndex: cut,
+    _kPasteIndex: paste,
+    _kDidGainAccessibilityFocusIndex: didGainAccessibilityFocus,
+    _kDidLoseAccessibilityFocusIndex: didLoseAccessibilityFocus,
+    _kCustomAction: customAction,
+    _kDismissIndex: dismiss,
+    _kMoveCursorForwardByWordIndex: moveCursorForwardByWord,
+    _kMoveCursorBackwardByWordIndex: moveCursorBackwardByWord,
+  };
+
+  @override
+  String toString() {
+    switch (index) {
+      case _kTapIndex:
+        return 'SemanticsAction.tap';
+      case _kLongPressIndex:
+        return 'SemanticsAction.longPress';
+      case _kScrollLeftIndex:
+        return 'SemanticsAction.scrollLeft';
+      case _kScrollRightIndex:
+        return 'SemanticsAction.scrollRight';
+      case _kScrollUpIndex:
+        return 'SemanticsAction.scrollUp';
+      case _kScrollDownIndex:
+        return 'SemanticsAction.scrollDown';
+      case _kIncreaseIndex:
+        return 'SemanticsAction.increase';
+      case _kDecreaseIndex:
+        return 'SemanticsAction.decrease';
+      case _kShowOnScreenIndex:
+        return 'SemanticsAction.showOnScreen';
+      case _kMoveCursorForwardByCharacterIndex:
+        return 'SemanticsAction.moveCursorForwardByCharacter';
+      case _kMoveCursorBackwardByCharacterIndex:
+        return 'SemanticsAction.moveCursorBackwardByCharacter';
+      case _kSetSelectionIndex:
+        return 'SemanticsAction.setSelection';
+      case _kCopyIndex:
+        return 'SemanticsAction.copy';
+      case _kCutIndex:
+        return 'SemanticsAction.cut';
+      case _kPasteIndex:
+        return 'SemanticsAction.paste';
+      case _kDidGainAccessibilityFocusIndex:
+        return 'SemanticsAction.didGainAccessibilityFocus';
+      case _kDidLoseAccessibilityFocusIndex:
+        return 'SemanticsAction.didLoseAccessibilityFocus';
+      case _kCustomAction:
+        return 'SemanticsAction.customAction';
+      case _kDismissIndex:
+        return 'SemanticsAction.dismiss';
+      case _kMoveCursorForwardByWordIndex:
+        return 'SemanticsAction.moveCursorForwardByWord';
+      case _kMoveCursorBackwardByWordIndex:
+        return 'SemanticsAction.moveCursorBackwardByWord';
+    }
+    assert(false, 'Unhandled index: $index');
+    return '';
+  }
+}
+
+/// A Boolean value that can be associated with a semantics node.
+//
+// When changes are made to this class, the equivalent APIs in
+// `lib/ui/semantics/semantics_node.h` and in each of the embedders *must* be
+// updated.
+class SemanticsFlag {
+  static const int _kHasCheckedStateIndex = 1 << 0;
+  static const int _kIsCheckedIndex = 1 << 1;
+  static const int _kIsSelectedIndex = 1 << 2;
+  static const int _kIsButtonIndex = 1 << 3;
+  static const int _kIsTextFieldIndex = 1 << 4;
+  static const int _kIsFocusedIndex = 1 << 5;
+  static const int _kHasEnabledStateIndex = 1 << 6;
+  static const int _kIsEnabledIndex = 1 << 7;
+  static const int _kIsInMutuallyExclusiveGroupIndex = 1 << 8;
+  static const int _kIsHeaderIndex = 1 << 9;
+  static const int _kIsObscuredIndex = 1 << 10;
+  static const int _kScopesRouteIndex= 1 << 11;
+  static const int _kNamesRouteIndex = 1 << 12;
+  static const int _kIsHiddenIndex = 1 << 13;
+  static const int _kIsImageIndex = 1 << 14;
+  static const int _kIsLiveRegionIndex = 1 << 15;
+  static const int _kHasToggledStateIndex = 1 << 16;
+  static const int _kIsToggledIndex = 1 << 17;
+  static const int _kHasImplicitScrollingIndex = 1 << 18;
+  static const int _kIsMultilineIndex = 1 << 19;
+  static const int _kIsReadOnlyIndex = 1 << 20;
+  static const int _kIsFocusableIndex = 1 << 21;
+  static const int _kIsLinkIndex = 1 << 22;
+  static const int _kIsSliderIndex = 1 << 23;
+  // READ THIS: if you add a flag here, you MUST update the numSemanticsFlags
+  // value in testing/dart/semantics_test.dart, or tests will fail. Also,
+  // please update the Flag enum in
+  // flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java,
+  // and the SemanticsFlag class in lib/web_ui/lib/src/ui/semantics.dart.
+
+  const SemanticsFlag._(this.index) : assert(index != null); // ignore: unnecessary_null_comparison
+
+  /// The numerical value for this flag.
+  ///
+  /// Each flag has one bit set in this bit field.
+  final int index;
+
+  /// The semantics node has the quality of either being "checked" or "unchecked".
+  ///
+  /// This flag is mutually exclusive with [hasToggledState].
+  ///
+  /// For example, a checkbox or a radio button widget has checked state.
+  ///
+  /// See also:
+  ///
+  ///   * [SemanticsFlag.isChecked], which controls whether the node is "checked" or "unchecked".
+  static const SemanticsFlag hasCheckedState = SemanticsFlag._(_kHasCheckedStateIndex);
+
+  /// Whether a semantics node that [hasCheckedState] is checked.
+  ///
+  /// If true, the semantics node is "checked". If false, the semantics node is
+  /// "unchecked".
+  ///
+  /// For example, if a checkbox has a visible checkmark, [isChecked] is true.
+  ///
+  /// See also:
+  ///
+  ///   * [SemanticsFlag.hasCheckedState], which enables a checked state.
+  static const SemanticsFlag isChecked = SemanticsFlag._(_kIsCheckedIndex);
+
+
+  /// Whether a semantics node is selected.
+  ///
+  /// If true, the semantics node is "selected". If false, the semantics node is
+  /// "unselected".
+  ///
+  /// For example, the active tab in a tab bar has [isSelected] set to true.
+  static const SemanticsFlag isSelected = SemanticsFlag._(_kIsSelectedIndex);
+
+  /// Whether the semantic node represents a button.
+  ///
+  /// Platforms have special handling for buttons, for example Android's TalkBack
+  /// and iOS's VoiceOver provides an additional hint when the focused object is
+  /// a button.
+  static const SemanticsFlag isButton = SemanticsFlag._(_kIsButtonIndex);
+
+  /// Whether the semantic node represents a text field.
+  ///
+  /// Text fields are announced as such and allow text input via accessibility
+  /// affordances.
+  static const SemanticsFlag isTextField = SemanticsFlag._(_kIsTextFieldIndex);
+
+  /// Whether the semantic node represents a slider.
+  static const SemanticsFlag isSlider = SemanticsFlag._(_kIsSliderIndex);
+
+  /// Whether the semantic node is read only.
+  ///
+  /// Only applicable when [isTextField] is true.
+  static const SemanticsFlag isReadOnly = SemanticsFlag._(_kIsReadOnlyIndex);
+
+  /// Whether the semantic node is an interactive link.
+  ///
+  /// Platforms have special handling for links, for example iOS's VoiceOver
+  /// provides an additional hint when the focused object is a link, as well as
+  /// the ability to parse the links through another navigation menu.
+  static const SemanticsFlag isLink = SemanticsFlag._(_kIsLinkIndex);
+
+  /// Whether the semantic node is able to hold the user's focus.
+  ///
+  /// The focused element is usually the current receiver of keyboard inputs.
+  static const SemanticsFlag isFocusable = SemanticsFlag._(_kIsFocusableIndex);
+
+  /// Whether the semantic node currently holds the user's focus.
+  ///
+  /// The focused element is usually the current receiver of keyboard inputs.
+  static const SemanticsFlag isFocused = SemanticsFlag._(_kIsFocusedIndex);
+
+  /// The semantics node has the quality of either being "enabled" or
+  /// "disabled".
+  ///
+  /// For example, a button can be enabled or disabled and therefore has an
+  /// "enabled" state. Static text is usually neither enabled nor disabled and
+  /// therefore does not have an "enabled" state.
+  static const SemanticsFlag hasEnabledState = SemanticsFlag._(_kHasEnabledStateIndex);
+
+  /// Whether a semantic node that [hasEnabledState] is currently enabled.
+  ///
+  /// A disabled element does not respond to user interaction. For example, a
+  /// button that currently does not respond to user interaction should be
+  /// marked as disabled.
+  static const SemanticsFlag isEnabled = SemanticsFlag._(_kIsEnabledIndex);
+
+  /// Whether a semantic node is in a mutually exclusive group.
+  ///
+  /// For example, a radio button is in a mutually exclusive group because
+  /// only one radio button in that group can be marked as [isChecked].
+  static const SemanticsFlag isInMutuallyExclusiveGroup = SemanticsFlag._(_kIsInMutuallyExclusiveGroupIndex);
+
+  /// Whether a semantic node is a header that divides content into sections.
+  ///
+  /// For example, headers can be used to divide a list of alphabetically
+  /// sorted words into the sections A, B, C, etc. as can be found in many
+  /// address book applications.
+  static const SemanticsFlag isHeader = SemanticsFlag._(_kIsHeaderIndex);
+
+  /// Whether the value of the semantics node is obscured.
+  ///
+  /// This is usually used for text fields to indicate that its content
+  /// is a password or contains other sensitive information.
+  static const SemanticsFlag isObscured = SemanticsFlag._(_kIsObscuredIndex);
+
+  /// Whether the value of the semantics node is coming from a multi-line text
+  /// field.
+  ///
+  /// This is used for text fields to distinguish single-line text fields from
+  /// multi-line ones.
+  static const SemanticsFlag isMultiline = SemanticsFlag._(_kIsMultilineIndex);
+
+  /// Whether the semantics node is the root of a subtree for which a route name
+  /// should be announced.
+  ///
+  /// When a node with this flag is removed from the semantics tree, the
+  /// framework will select the last in depth-first, paint order node with this
+  /// flag.  When a node with this flag is added to the semantics tree, it is
+  /// selected automatically, unless there were multiple nodes with this flag
+  /// added.  In this case, the last added node in depth-first, paint order
+  /// will be selected.
+  ///
+  /// From this selected node, the framework will search in depth-first, paint
+  /// order for the first node with a [namesRoute] flag and a non-null,
+  /// non-empty label. The [namesRoute] and [scopesRoute] flags may be on the
+  /// same node. The label of the found node will be announced as an edge
+  /// transition. If no non-empty, non-null label is found then:
+  ///
+  ///   * VoiceOver will make a chime announcement.
+  ///   * TalkBack will make no announcement
+  ///
+  /// Semantic nodes annotated with this flag are generally not a11y focusable.
+  ///
+  /// This is used in widgets such as Routes, Drawers, and Dialogs to
+  /// communicate significant changes in the visible screen.
+  static const SemanticsFlag scopesRoute = SemanticsFlag._(_kScopesRouteIndex);
+
+  /// Whether the semantics node label is the name of a visually distinct
+  /// route.
+  ///
+  /// This is used by certain widgets like Drawers and Dialogs, to indicate
+  /// that the node's semantic label can be used to announce an edge triggered
+  /// semantics update.
+  ///
+  /// Semantic nodes annotated with this flag will still receive a11y focus.
+  ///
+  /// Updating this label within the same active route subtree will not cause
+  /// additional announcements.
+  static const SemanticsFlag namesRoute = SemanticsFlag._(_kNamesRouteIndex);
+
+  /// Whether the semantics node is considered hidden.
+  ///
+  /// Hidden elements are currently not visible on screen. They may be covered
+  /// by other elements or positioned outside of the visible area of a viewport.
+  ///
+  /// Hidden elements cannot gain accessibility focus though regular touch. The
+  /// only way they can be focused is by moving the focus to them via linear
+  /// navigation.
+  ///
+  /// Platforms are free to completely ignore hidden elements and new platforms
+  /// are encouraged to do so.
+  ///
+  /// Instead of marking an element as hidden it should usually be excluded from
+  /// the semantics tree altogether. Hidden elements are only included in the
+  /// semantics tree to work around platform limitations and they are mainly
+  /// used to implement accessibility scrolling on iOS.
+  ///
+  /// See also:
+  ///
+  /// * [RenderObject.describeSemanticsClip]
+  static const SemanticsFlag isHidden = SemanticsFlag._(_kIsHiddenIndex);
+
+  /// Whether the semantics node represents an image.
+  ///
+  /// Both TalkBack and VoiceOver will inform the user the semantics node
+  /// represents an image.
+  static const SemanticsFlag isImage = SemanticsFlag._(_kIsImageIndex);
+
+  /// Whether the semantics node is a live region.
+  ///
+  /// A live region indicates that updates to semantics node are important.
+  /// Platforms may use this information to make polite announcements to the
+  /// user to inform them of updates to this node.
+  ///
+  /// An example of a live region is a [SnackBar] widget. On Android and iOS,
+  /// live region causes a polite announcement to be generated automatically,
+  /// even if the widget does not have accessibility focus. This announcement
+  /// may not be spoken if the OS accessibility services are already
+  /// announcing something else, such as reading the label of a focused
+  /// widget or providing a system announcement.
+  static const SemanticsFlag isLiveRegion = SemanticsFlag._(_kIsLiveRegionIndex);
+
+  /// The semantics node has the quality of either being "on" or "off".
+  ///
+  /// This flag is mutually exclusive with [hasCheckedState].
+  ///
+  /// For example, a switch has toggled state.
+  ///
+  /// See also:
+  ///
+  ///    * [SemanticsFlag.isToggled], which controls whether the node is "on" or "off".
+  static const SemanticsFlag hasToggledState = SemanticsFlag._(_kHasToggledStateIndex);
+
+  /// If true, the semantics node is "on". If false, the semantics node is
+  /// "off".
+  ///
+  /// For example, if a switch is in the on position, [isToggled] is true.
+  ///
+  /// See also:
+  ///
+  ///   * [SemanticsFlag.hasToggledState], which enables a toggled state.
+  static const SemanticsFlag isToggled = SemanticsFlag._(_kIsToggledIndex);
+
+  /// Whether the platform can scroll the semantics node when the user attempts
+  /// to move focus to an offscreen child.
+  ///
+  /// For example, a [ListView] widget has implicit scrolling so that users can
+  /// easily move the accessibility focus to the next set of children. A
+  /// [PageView] widget does not have implicit scrolling, so that users don't
+  /// navigate to the next page when reaching the end of the current one.
+  static const SemanticsFlag hasImplicitScrolling = SemanticsFlag._(_kHasImplicitScrollingIndex);
+
+  /// The possible semantics flags.
+  ///
+  /// The map's key is the [index] of the flag and the value is the flag itself.
+  static const Map<int, SemanticsFlag> values = <int, SemanticsFlag>{
+    _kHasCheckedStateIndex: hasCheckedState,
+    _kIsCheckedIndex: isChecked,
+    _kIsSelectedIndex: isSelected,
+    _kIsButtonIndex: isButton,
+    _kIsTextFieldIndex: isTextField,
+    _kIsFocusedIndex: isFocused,
+    _kHasEnabledStateIndex: hasEnabledState,
+    _kIsEnabledIndex: isEnabled,
+    _kIsInMutuallyExclusiveGroupIndex: isInMutuallyExclusiveGroup,
+    _kIsHeaderIndex: isHeader,
+    _kIsObscuredIndex: isObscured,
+    _kScopesRouteIndex: scopesRoute,
+    _kNamesRouteIndex: namesRoute,
+    _kIsHiddenIndex: isHidden,
+    _kIsImageIndex: isImage,
+    _kIsLiveRegionIndex: isLiveRegion,
+    _kHasToggledStateIndex: hasToggledState,
+    _kIsToggledIndex: isToggled,
+    _kHasImplicitScrollingIndex: hasImplicitScrolling,
+    _kIsMultilineIndex: isMultiline,
+    _kIsReadOnlyIndex: isReadOnly,
+    _kIsFocusableIndex: isFocusable,
+    _kIsLinkIndex: isLink,
+    _kIsSliderIndex: isSlider,
+};
+
+  @override
+  String toString() {
+    switch (index) {
+      case _kHasCheckedStateIndex:
+        return 'SemanticsFlag.hasCheckedState';
+      case _kIsCheckedIndex:
+        return 'SemanticsFlag.isChecked';
+      case _kIsSelectedIndex:
+        return 'SemanticsFlag.isSelected';
+      case _kIsButtonIndex:
+        return 'SemanticsFlag.isButton';
+      case _kIsTextFieldIndex:
+        return 'SemanticsFlag.isTextField';
+      case _kIsFocusedIndex:
+        return 'SemanticsFlag.isFocused';
+      case _kHasEnabledStateIndex:
+        return 'SemanticsFlag.hasEnabledState';
+      case _kIsEnabledIndex:
+        return 'SemanticsFlag.isEnabled';
+      case _kIsInMutuallyExclusiveGroupIndex:
+        return 'SemanticsFlag.isInMutuallyExclusiveGroup';
+      case _kIsHeaderIndex:
+        return 'SemanticsFlag.isHeader';
+      case _kIsObscuredIndex:
+        return 'SemanticsFlag.isObscured';
+      case _kScopesRouteIndex:
+        return 'SemanticsFlag.scopesRoute';
+      case _kNamesRouteIndex:
+        return 'SemanticsFlag.namesRoute';
+      case _kIsHiddenIndex:
+        return 'SemanticsFlag.isHidden';
+      case _kIsImageIndex:
+        return 'SemanticsFlag.isImage';
+      case _kIsLiveRegionIndex:
+        return 'SemanticsFlag.isLiveRegion';
+      case _kHasToggledStateIndex:
+        return 'SemanticsFlag.hasToggledState';
+      case _kIsToggledIndex:
+        return 'SemanticsFlag.isToggled';
+      case _kHasImplicitScrollingIndex:
+        return 'SemanticsFlag.hasImplicitScrolling';
+      case _kIsMultilineIndex:
+        return 'SemanticsFlag.isMultiline';
+      case _kIsReadOnlyIndex:
+        return 'SemanticsFlag.isReadOnly';
+      case _kIsFocusableIndex:
+        return 'SemanticsFlag.isFocusable';
+      case _kIsLinkIndex:
+        return 'SemanticsFlag.isLink';
+      case _kIsSliderIndex:
+        return 'SemanticsFlag.isSlider';
+    }
+    assert(false, 'Unhandled index: $index');
+    return '';
+  }
+}
+
+/// An object that creates [SemanticsUpdate] objects.
+///
+/// Once created, the [SemanticsUpdate] objects can be passed to
+/// [PlatformDispatcher.updateSemantics] to update the semantics conveyed to the
+/// user.
+@pragma('vm:entry-point')
+class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 {
+  /// Creates an empty [SemanticsUpdateBuilder] object.
+  @pragma('vm:entry-point')
+  SemanticsUpdateBuilder() { _constructor(); }
+  void _constructor() { throw UnimplementedError(); }
+
+  /// Update the information associated with the node with the given `id`.
+  ///
+  /// The semantics nodes form a tree, with the root of the tree always having
+  /// an id of zero. The `childrenInTraversalOrder` and `childrenInHitTestOrder`
+  /// are the ids of the nodes that are immediate children of this node. The
+  /// former enumerates children in traversal order, and the latter enumerates
+  /// the same children in the hit test order. The two lists must have the same
+  /// length and contain the same ids. They may only differ in the order the
+  /// ids are listed in. For more information about different child orders, see
+  /// [DebugSemanticsDumpOrder].
+  ///
+  /// The system retains the nodes that are currently reachable from the root.
+  /// A given update need not contain information for nodes that do not change
+  /// in the update. If a node is not reachable from the root after an update,
+  /// the node will be discarded from the tree.
+  ///
+  /// The `flags` are a bit field of [SemanticsFlag]s that apply to this node.
+  ///
+  /// The `actions` are a bit field of [SemanticsAction]s that can be undertaken
+  /// by this node. If the user wishes to undertake one of these actions on this
+  /// node, the [PlatformDispatcher.onSemanticsAction] will be called with `id`
+  /// and one of the possible [SemanticsAction]s. Because the semantics tree is
+  /// maintained asynchronously, the [PlatformDispatcher.onSemanticsAction]
+  /// callback might be called with an action that is no longer possible.
+  ///
+  /// The `label` is a string that describes this node. The `value` property
+  /// describes the current value of the node as a string. The `increasedValue`
+  /// string will become the `value` string after a [SemanticsAction.increase]
+  /// action is performed. The `decreasedValue` string will become the `value`
+  /// string after a [SemanticsAction.decrease] action is performed. The `hint`
+  /// string describes what result an action performed on this node has. The
+  /// reading direction of all these strings is given by `textDirection`.
+  ///
+  /// The fields `textSelectionBase` and `textSelectionExtent` describe the
+  /// currently selected text within `value`. A value of -1 indicates no
+  /// current text selection base or extent.
+  ///
+  /// The field `maxValueLength` is used to indicate that an editable text
+  /// field has a limit on the number of characters entered. If it is -1 there
+  /// is no limit on the number of characters entered. The field
+  /// `currentValueLength` indicates how much of that limit has already been
+  /// used up. When `maxValueLength` is >= 0, `currentValueLength` must also be
+  /// >= 0, otherwise it should be specified to be -1.
+  ///
+  /// The field `platformViewId` references the platform view, whose semantics
+  /// nodes will be added as children to this node. If a platform view is
+  /// specified, `childrenInHitTestOrder` and `childrenInTraversalOrder` must
+  /// be empty. A value of -1 indicates that this node is not associated with a
+  /// platform view.
+  ///
+  /// For scrollable nodes `scrollPosition` describes the current scroll
+  /// position in logical pixel. `scrollExtentMax` and `scrollExtentMin`
+  /// describe the maximum and minimum in-rage values that `scrollPosition` can
+  /// be. Both or either may be infinity to indicate unbound scrolling. The
+  /// value for `scrollPosition` can (temporarily) be outside this range, for
+  /// example during an overscroll. `scrollChildren` is the count of the
+  /// total number of child nodes that contribute semantics and `scrollIndex`
+  /// is the index of the first visible child node that contributes semantics.
+  ///
+  /// The `rect` is the region occupied by this node in its own coordinate
+  /// system.
+  ///
+  /// The `transform` is a matrix that maps this node's coordinate system into
+  /// its parent's coordinate system.
+  ///
+  /// The `elevation` describes the distance in z-direction between this node
+  /// and the `elevation` of the parent.
+  ///
+  /// The `thickness` describes how much space this node occupies in the
+  /// z-direction starting at `elevation`. Basically, in the z-direction the
+  /// node starts at `elevation` above the parent and ends at `elevation` +
+  /// `thickness` above the parent.
+  void updateNode({
+    required int id,
+    required int flags,
+    required int actions,
+    required int maxValueLength,
+    required int currentValueLength,
+    required int textSelectionBase,
+    required int textSelectionExtent,
+    required int platformViewId,
+    required int scrollChildren,
+    required int scrollIndex,
+    required double scrollPosition,
+    required double scrollExtentMax,
+    required double scrollExtentMin,
+    required double elevation,
+    required double thickness,
+    required Rect rect,
+    required String label,
+    required String hint,
+    required String value,
+    required String increasedValue,
+    required String decreasedValue,
+    TextDirection? textDirection,
+    required Float64List transform,
+    required Int32List childrenInTraversalOrder,
+    required Int32List childrenInHitTestOrder,
+    required Int32List additionalActions,
+  }) {
+    assert(_matrix4IsValid(transform));
+    assert(
+      // ignore: unnecessary_null_comparison
+      scrollChildren == 0 || scrollChildren == null || (scrollChildren > 0 && childrenInHitTestOrder != null),
+      'If a node has scrollChildren, it must have childrenInHitTestOrder',
+    );
+    _updateNode(
+      id,
+      flags,
+      actions,
+      maxValueLength,
+      currentValueLength,
+      textSelectionBase,
+      textSelectionExtent,
+      platformViewId,
+      scrollChildren,
+      scrollIndex,
+      scrollPosition,
+      scrollExtentMax,
+      scrollExtentMin,
+      rect.left,
+      rect.top,
+      rect.right,
+      rect.bottom,
+      elevation,
+      thickness,
+      label,
+      hint,
+      value,
+      increasedValue,
+      decreasedValue,
+      textDirection != null ? textDirection.index + 1 : 0,
+      transform,
+      childrenInTraversalOrder,
+      childrenInHitTestOrder,
+      additionalActions,
+    );
+  }
+  void _updateNode(
+    int id,
+    int flags,
+    int actions,
+    int maxValueLength,
+    int currentValueLength,
+    int textSelectionBase,
+    int textSelectionExtent,
+    int platformViewId,
+    int scrollChildren,
+    int scrollIndex,
+    double scrollPosition,
+    double scrollExtentMax,
+    double scrollExtentMin,
+    double left,
+    double top,
+    double right,
+    double bottom,
+    double elevation,
+    double thickness,
+    String label,
+    String hint,
+    String value,
+    String increasedValue,
+    String decreasedValue,
+    int textDirection,
+    Float64List transform,
+    Int32List childrenInTraversalOrder,
+    Int32List childrenInHitTestOrder,
+    Int32List additionalActions,
+  ) { throw UnimplementedError(); }
+
+  /// Update the custom semantics action associated with the given `id`.
+  ///
+  /// The name of the action exposed to the user is the `label`. For overridden
+  /// standard actions this value is ignored.
+  ///
+  /// The `hint` should describe what happens when an action occurs, not the
+  /// manner in which a tap is accomplished. For example, use "delete" instead
+  /// of "double tap to delete".
+  ///
+  /// The text direction of the `hint` and `label` is the same as the global
+  /// window.
+  ///
+  /// For overridden standard actions, `overrideId` corresponds with a
+  /// [SemanticsAction.index] value. For custom actions this argument should not be
+  /// provided.
+  void updateCustomAction({required int id, String? label, String? hint, int overrideId = -1}) {
+    assert(id != null); // ignore: unnecessary_null_comparison
+    assert(overrideId != null); // ignore: unnecessary_null_comparison
+    _updateCustomAction(id, label, hint, overrideId);
+  }
+  void _updateCustomAction(
+      int id,
+      String? label,
+      String? hint,
+      int overrideId) { throw UnimplementedError(); }
+
+  /// Creates a [SemanticsUpdate] object that encapsulates the updates recorded
+  /// by this object.
+  ///
+  /// The returned object can be passed to [PlatformDispatcher.updateSemantics]
+  /// to actually update the semantics retained by the system.
+  SemanticsUpdate build() {
+    final SemanticsUpdate semanticsUpdate = SemanticsUpdate._();
+    _build(semanticsUpdate);
+    return semanticsUpdate;
+  }
+  void _build(SemanticsUpdate outSemanticsUpdate) { throw UnimplementedError(); }
+}
+
+/// An opaque object representing a batch of semantics updates.
+///
+/// To create a SemanticsUpdate object, use a [SemanticsUpdateBuilder].
+///
+/// Semantics updates can be applied to the system's retained semantics tree
+/// using the [PlatformDispatcher.updateSemantics] method.
+@pragma('vm:entry-point')
+class SemanticsUpdate extends NativeFieldWrapperClass2 {
+  /// This class is created by the engine, and should not be instantiated
+  /// or extended directly.
+  ///
+  /// To create a SemanticsUpdate object, use a [SemanticsUpdateBuilder].
+  @pragma('vm:entry-point')
+  SemanticsUpdate._();
+
+  /// Releases the resources used by this semantics update.
+  ///
+  /// After calling this function, the semantics update is cannot be used
+  /// further.
+  void dispose() { throw UnimplementedError(); }
+}
diff --git a/lib/src/ui/text.dart b/lib/src/ui/text.dart
new file mode 100644
index 0000000..cf76fe7
--- /dev/null
+++ b/lib/src/ui/text.dart
@@ -0,0 +1,2304 @@
+// 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.
+
+
+
+part of dart.ui;
+
+/// Whether to slant the glyphs in the font
+enum FontStyle {
+  /// Use the upright glyphs
+  normal,
+
+  /// Use glyphs designed for slanting
+  italic,
+}
+
+/// The thickness of the glyphs used to draw the text
+class FontWeight {
+  const FontWeight._(this.index);
+
+  /// The encoded integer value of this font weight.
+  final int index;
+
+  /// Thin, the least thick
+  static const FontWeight w100 = FontWeight._(0);
+
+  /// Extra-light
+  static const FontWeight w200 = FontWeight._(1);
+
+  /// Light
+  static const FontWeight w300 = FontWeight._(2);
+
+  /// Normal / regular / plain
+  static const FontWeight w400 = FontWeight._(3);
+
+  /// Medium
+  static const FontWeight w500 = FontWeight._(4);
+
+  /// Semi-bold
+  static const FontWeight w600 = FontWeight._(5);
+
+  /// Bold
+  static const FontWeight w700 = FontWeight._(6);
+
+  /// Extra-bold
+  static const FontWeight w800 = FontWeight._(7);
+
+  /// Black, the most thick
+  static const FontWeight w900 = FontWeight._(8);
+
+  /// The default font weight.
+  static const FontWeight normal = w400;
+
+  /// A commonly used font weight that is heavier than normal.
+  static const FontWeight bold = w700;
+
+  /// A list of all the font weights.
+  static const List<FontWeight> values = <FontWeight>[
+    w100, w200, w300, w400, w500, w600, w700, w800, w900
+  ];
+
+  /// Linearly interpolates between two font weights.
+  ///
+  /// Rather than using fractional weights, the interpolation rounds to the
+  /// nearest weight.
+  ///
+  /// If both `a` and `b` are null, then this method will return null. Otherwise,
+  /// any null values for `a` or `b` are interpreted as equivalent to [normal]
+  /// (also known as [w400]).
+  ///
+  /// The `t` argument represents position on the timeline, with 0.0 meaning
+  /// that the interpolation has not started, returning `a` (or something
+  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
+  /// returning `b` (or something equivalent to `b`), and values in between
+  /// meaning that the interpolation is at the relevant point on the timeline
+  /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
+  /// 1.0, so negative values and values greater than 1.0 are valid (and can
+  /// easily be generated by curves such as [Curves.elasticInOut]). The result
+  /// is clamped to the range [w100]–[w900].
+  ///
+  /// Values for `t` are usually obtained from an [Animation<double>], such as
+  /// an [AnimationController].
+  static FontWeight? lerp(FontWeight? a, FontWeight? b, double t) {
+    assert(t != null); // ignore: unnecessary_null_comparison
+    if (a == null && b == null)
+      return null;
+    return values[_lerpInt((a ?? normal).index, (b ?? normal).index, t).round().clamp(0, 8)];
+  }
+
+  @override
+  String toString() {
+    return const <int, String>{
+      0: 'FontWeight.w100',
+      1: 'FontWeight.w200',
+      2: 'FontWeight.w300',
+      3: 'FontWeight.w400',
+      4: 'FontWeight.w500',
+      5: 'FontWeight.w600',
+      6: 'FontWeight.w700',
+      7: 'FontWeight.w800',
+      8: 'FontWeight.w900',
+    }[index]!;
+  }
+}
+
+/// A feature tag and value that affect the selection of glyphs in a font.
+///
+/// {@tool sample --template=freeform}
+///
+/// This example shows usage of several OpenType font features, including
+/// Small Caps (smcp), old-style figures, fractional ligatures and stylistic
+/// sets.
+///
+/// ```dart
+///class TypePage extends StatelessWidget {
+///  // The Cardo, Milonga and Raleway Dots fonts can be downloaded from
+///  // Google Fonts (https://www.google.com/fonts).
+///
+///  final titleStyle = TextStyle(
+///    fontSize: 18,
+///    fontFeatures: [FontFeature.enable('smcp')],
+///    color: Colors.blueGrey[600],
+///  );
+///
+///  @override
+///  Widget build(BuildContext context) {
+///    return Scaffold(
+///      body: Center(
+///        child: Column(
+///          mainAxisAlignment: MainAxisAlignment.center,
+///          children: <Widget>[
+///            Spacer(flex: 5),
+///            Text('regular numbers have their place:', style: titleStyle),
+///            Text('The 1972 cup final was a 1-1 draw.',
+///                style: TextStyle(
+///                  fontFamily: 'Cardo',
+///                  fontSize: 24,
+///                )),
+///            Spacer(),
+///            Text('but old-style figures blend well with lower case:',
+///                style: titleStyle),
+///            Text('The 1972 cup final was a 1-1 draw.',
+///                style: TextStyle(
+///                    fontFamily: 'Cardo',
+///                    fontSize: 24,
+///                    fontFeatures: [FontFeature.oldstyleFigures()])),
+///            Spacer(),
+///            Divider(),
+///            Spacer(),
+///            Text('fractions look better with a custom ligature:',
+///                style: titleStyle),
+///            Text('Add 1/2 tsp of flour and stir.',
+///                style: TextStyle(
+///                    fontFamily: 'Milonga',
+///                    fontSize: 24,
+///                    fontFeatures: [FontFeature.enable('frac')])),
+///            Spacer(),
+///            Divider(),
+///            Spacer(),
+///            Text('multiple stylistic sets in one font:', style: titleStyle),
+///            Text('Raleway Dots',
+///                style: TextStyle(fontFamily: 'Raleway Dots', fontSize: 48)),
+///            Text('Raleway Dots',
+///                style: TextStyle(
+///                  fontFeatures: [FontFeature.stylisticSet(1)],
+///                  fontFamily: 'Raleway Dots',
+///                  fontSize: 48,
+///                )),
+///            Spacer(flex: 5),
+///          ],
+///        ),
+///      ),
+///    );
+///  }
+///}
+/// ```
+/// {@end-tool}
+class FontFeature {
+  /// Creates a [FontFeature] object, which can be added to a [TextStyle] to
+  /// change how the engine selects glyphs when rendering text.
+  ///
+  /// `feature` is the four-character tag that identifies the feature.
+  /// These tags are specified by font formats such as OpenType.
+  ///
+  /// `value` is the value that the feature will be set to.  The behavior
+  /// of the value depends on the specific feature.  Many features are
+  /// flags whose value can be 1 (when enabled) or 0 (when disabled).
+  ///
+  /// See <https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags>
+  // ignore: unnecessary_null_comparison
+  const FontFeature(this.feature, [ this.value = 1 ]) : assert(feature != null), assert(feature.length == 4), assert(value != null), assert(value >= 0);
+
+  /// Create a [FontFeature] object that enables the feature with the given tag.
+  const FontFeature.enable(String feature) : this(feature, 1);
+
+  /// Create a [FontFeature] object that disables the feature with the given tag.
+  const FontFeature.disable(String feature) : this(feature, 0);
+
+  /// Randomize the alternate forms used in text.
+  ///
+  /// For example, this can be used with suitably-prepared handwriting fonts to
+  /// vary the forms used for each character, so that, for instance, the word
+  /// "cross-section" would be rendered with two different "c"s, two different "o"s,
+  /// and three different "s"s.
+  ///
+  /// See also:
+  ///
+  ///  * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#rand>
+  const FontFeature.randomize() : feature = 'rand', value = 1;
+
+  /// Select a stylistic set.
+  ///
+  /// Fonts may have up to 20 stylistic sets, numbered 1 through 20.
+  ///
+  /// See also:
+  ///
+  ///  * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#ssxx>
+  factory FontFeature.stylisticSet(int value) {
+    assert(value >= 1);
+    assert(value <= 20);
+    return FontFeature('ss${value.toString().padLeft(2, "0")}');
+  }
+
+  /// Use the slashed zero.
+  ///
+  /// Some fonts contain both a circular zero and a zero with a slash. This
+  /// enables the use of the latter form.
+  ///
+  /// This is overridden by [FontFeature.oldstyleFigures].
+  ///
+  /// See also:
+  ///
+  ///  * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_uz#zero>
+  const FontFeature.slashedZero() : feature = 'zero', value = 1;
+
+  /// Use oldstyle figures.
+  ///
+  /// Some fonts have variants of the figures (e.g. the digit 9) that, when
+  /// this feature is enabled, render with descenders under the baseline instead
+  /// of being entirely above the baseline.
+  ///
+  /// This overrides [FontFeature.slashedZero].
+  ///
+  /// See also:
+  ///
+  ///  * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_ko#onum>
+  const FontFeature.oldstyleFigures() : feature = 'onum', value = 1;
+
+  /// Use proportional (varying width) figures.
+  ///
+  /// For fonts that have both proportional and tabular (monospace) figures,
+  /// this enables the proportional figures.
+  ///
+  /// This is mutually exclusive with [FontFeature.tabularFigures].
+  ///
+  /// The default behavior varies from font to font.
+  ///
+  /// See also:
+  ///
+  ///  * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#pnum>
+  const FontFeature.proportionalFigures() : feature = 'pnum', value = 1;
+
+  /// Use tabular (monospace) figures.
+  ///
+  /// For fonts that have both proportional (varying width) and tabular figures,
+  /// this enables the tabular figures.
+  ///
+  /// This is mutually exclusive with [FontFeature.proportionalFigures].
+  ///
+  /// The default behavior varies from font to font.
+  ///
+  /// See also:
+  ///
+  ///  * <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#tnum>
+  const FontFeature.tabularFigures() : feature = 'tnum', value = 1;
+
+  /// The tag that identifies the effect of this feature.  Must consist of 4
+  /// ASCII characters (typically lowercase letters).
+  ///
+  /// See <https://docs.microsoft.com/en-us/typography/opentype/spec/featuretags>
+  final String feature;
+
+  /// The value assigned to this feature.
+  ///
+  /// Must be a positive integer.  Many features are Boolean values that accept
+  /// values of either 0 (feature is disabled) or 1 (feature is enabled).
+  final int value;
+
+  static const int _kEncodedSize = 8;
+
+  void _encode(ByteData byteData) {
+    assert(feature.codeUnits.every((int c) => c >= 0x20 && c <= 0x7F));
+    for (int i = 0; i < 4; i++) {
+      byteData.setUint8(i, feature.codeUnitAt(i));
+    }
+    byteData.setInt32(4, value, _kFakeHostEndian);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is FontFeature
+        && other.feature == feature
+        && other.value == value;
+  }
+
+  @override
+  int get hashCode => hashValues(feature, value);
+
+  @override
+  String toString() => 'FontFeature($feature, $value)';
+}
+
+/// Whether and how to align text horizontally.
+// The order of this enum must match the order of the values in RenderStyleConstants.h's ETextAlign.
+enum TextAlign {
+  /// Align the text on the left edge of the container.
+  left,
+
+  /// Align the text on the right edge of the container.
+  right,
+
+  /// Align the text in the center of the container.
+  center,
+
+  /// Stretch lines of text that end with a soft line break to fill the width of
+  /// the container.
+  ///
+  /// Lines that end with hard line breaks are aligned towards the [start] edge.
+  justify,
+
+  /// Align the text on the leading edge of the container.
+  ///
+  /// For left-to-right text ([TextDirection.ltr]), this is the left edge.
+  ///
+  /// For right-to-left text ([TextDirection.rtl]), this is the right edge.
+  start,
+
+  /// Align the text on the trailing edge of the container.
+  ///
+  /// For left-to-right text ([TextDirection.ltr]), this is the right edge.
+  ///
+  /// For right-to-left text ([TextDirection.rtl]), this is the left edge.
+  end,
+}
+
+/// A horizontal line used for aligning text.
+enum TextBaseline {
+  /// The horizontal line used to align the bottom of glyphs for alphabetic characters.
+  alphabetic,
+
+  /// The horizontal line used to align ideographic characters.
+  ideographic,
+}
+
+/// A linear decoration to draw near the text.
+class TextDecoration {
+  const TextDecoration._(this._mask);
+
+  /// Creates a decoration that paints the union of all the given decorations.
+  factory TextDecoration.combine(List<TextDecoration> decorations) {
+    int mask = 0;
+    for (TextDecoration decoration in decorations)
+      mask |= decoration._mask;
+    return TextDecoration._(mask);
+  }
+
+  final int _mask;
+
+  /// Whether this decoration will paint at least as much decoration as the given decoration.
+  bool contains(TextDecoration other) {
+    return (_mask | other._mask) == _mask;
+  }
+
+  /// Do not draw a decoration
+  static const TextDecoration none = TextDecoration._(0x0);
+
+  /// Draw a line underneath each line of text
+  static const TextDecoration underline = TextDecoration._(0x1);
+
+  /// Draw a line above each line of text
+  static const TextDecoration overline = TextDecoration._(0x2);
+
+  /// Draw a line through each line of text
+  static const TextDecoration lineThrough = TextDecoration._(0x4);
+
+  @override
+  bool operator ==(Object other) {
+    return other is TextDecoration
+        && other._mask == _mask;
+  }
+
+  @override
+  int get hashCode => _mask.hashCode;
+
+  @override
+  String toString() {
+    if (_mask == 0)
+      return 'TextDecoration.none';
+    final List<String> values = <String>[];
+    if (_mask & underline._mask != 0)
+      values.add('underline');
+    if (_mask & overline._mask != 0)
+      values.add('overline');
+    if (_mask & lineThrough._mask != 0)
+      values.add('lineThrough');
+    if (values.length == 1)
+      return 'TextDecoration.${values[0]}';
+    return 'TextDecoration.combine([${values.join(", ")}])';
+  }
+}
+
+/// The style in which to draw a text decoration
+enum TextDecorationStyle {
+  /// Draw a solid line
+  solid,
+
+  /// Draw two lines
+  double,
+
+  /// Draw a dotted line
+  dotted,
+
+  /// Draw a dashed line
+  dashed,
+
+  /// Draw a sinusoidal line
+  wavy
+}
+
+/// {@template dart.ui.textHeightBehavior}
+/// Defines how the paragraph will apply [TextStyle.height] to the ascent of the
+/// first line and descent of the last line.
+///
+/// Each boolean value represents whether the [TextStyle.height] modifier will
+/// be applied to the corresponding metric. By default, all properties are true,
+/// and [TextStyle.height] is applied as normal. When set to false, the font's
+/// default ascent will be used.
+/// {@endtemplate}
+class TextHeightBehavior {
+
+  /// Creates a new TextHeightBehavior object.
+  ///
+  ///  * applyHeightToFirstAscent: When true, the [TextStyle.height] modifier
+  ///    will be applied to the ascent of the first line. When false, the font's
+  ///    default ascent will be used.
+  ///  * applyHeightToLastDescent: When true, the [TextStyle.height] modifier
+  ///    will be applied to the descent of the last line. When false, the font's
+  ///    default descent will be used.
+  ///
+  /// All properties default to true (height modifications applied as normal).
+  const TextHeightBehavior({
+    this.applyHeightToFirstAscent = true,
+    this.applyHeightToLastDescent = true,
+  });
+
+  /// Creates a new TextHeightBehavior object from an encoded form.
+  ///
+  /// See [encode] for the creation of the encoded form.
+  const TextHeightBehavior.fromEncoded(int encoded)
+    : applyHeightToFirstAscent = (encoded & 0x1) == 0,
+      applyHeightToLastDescent = (encoded & 0x2) == 0;
+
+
+  /// Whether to apply the [TextStyle.height] modifier to the ascent of the first
+  /// line in the paragraph.
+  ///
+  /// When true, the [TextStyle.height] modifier will be applied to to the ascent
+  /// of the first line. When false, the font's default ascent will be used and
+  /// the [TextStyle.height] will have no effect on the ascent of the first line.
+  ///
+  /// This property only has effect if a non-null [TextStyle.height] is specified.
+  ///
+  /// Defaults to true (height modifications applied as normal).
+  final bool applyHeightToFirstAscent;
+
+  /// Whether to apply the [TextStyle.height] modifier to the descent of the last
+  /// line in the paragraph.
+  ///
+  /// When true, the [TextStyle.height] modifier will be applied to to the descent
+  /// of the last line. When false, the font's default descent will be used and
+  /// the [TextStyle.height] will have no effect on the descent of the last line.
+  ///
+  /// This property only has effect if a non-null [TextStyle.height] is specified.
+  ///
+  /// Defaults to true (height modifications applied as normal).
+  final bool applyHeightToLastDescent;
+
+  /// Returns an encoded int representation of this object.
+  int encode() {
+    return (applyHeightToFirstAscent ? 0 : 1 << 0) | (applyHeightToLastDescent ? 0 : 1 << 1);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is TextHeightBehavior
+        && other.applyHeightToFirstAscent == applyHeightToFirstAscent
+        && other.applyHeightToLastDescent == applyHeightToLastDescent;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      applyHeightToFirstAscent,
+      applyHeightToLastDescent,
+    );
+  }
+
+  @override
+  String toString() {
+    return 'TextHeightBehavior('
+             'applyHeightToFirstAscent: $applyHeightToFirstAscent, '
+             'applyHeightToLastDescent: $applyHeightToLastDescent'
+           ')';
+  }
+}
+
+/// Determines if lists [a] and [b] are deep equivalent.
+///
+/// Returns true if the lists are both null, or if they are both non-null, have
+/// the same length, and contain the same elements in the same order. Returns
+/// false otherwise.
+bool _listEquals<T>(List<T>? a, List<T>? b) {
+  if (a == null)
+    return b == null;
+  if (b == null || a.length != b.length)
+    return false;
+  for (int index = 0; index < a.length; index += 1) {
+    if (a[index] != b[index])
+      return false;
+  }
+  return true;
+}
+
+// This encoding must match the C++ version of ParagraphBuilder::pushStyle.
+//
+// The encoded array buffer has 8 elements.
+//
+//  - Element 0: A bit field where the ith bit indicates whether the ith element
+//    has a non-null value. Bits 8 to 12 indicate whether |fontFamily|,
+//    |fontSize|, |letterSpacing|, |wordSpacing|, and |height| are non-null,
+//    respectively. Bit 0 is unused.
+//
+//  - Element 1: The |color| in ARGB with 8 bits per channel.
+//
+//  - Element 2: A bit field indicating which text decorations are present in
+//    the |textDecoration| list. The ith bit is set if there's a TextDecoration
+//    with enum index i in the list.
+//
+//  - Element 3: The |decorationColor| in ARGB with 8 bits per channel.
+//
+//  - Element 4: The bit field of the |decorationStyle|.
+//
+//  - Element 5: The index of the |fontWeight|.
+//
+//  - Element 6: The enum index of the |fontStyle|.
+//
+//  - Element 7: The enum index of the |textBaseline|.
+//
+Int32List _encodeTextStyle(
+  Color? color,
+  TextDecoration? decoration,
+  Color? decorationColor,
+  TextDecorationStyle? decorationStyle,
+  double? decorationThickness,
+  FontWeight? fontWeight,
+  FontStyle? fontStyle,
+  TextBaseline? textBaseline,
+  String? fontFamily,
+  List<String>? fontFamilyFallback,
+  double? fontSize,
+  double? letterSpacing,
+  double? wordSpacing,
+  double? height,
+  Locale? locale,
+  Paint? background,
+  Paint? foreground,
+  List<Shadow>? shadows,
+  List<FontFeature>? fontFeatures,
+) {
+  final Int32List result = Int32List(8);
+  if (color != null) {
+    result[0] |= 1 << 1;
+    result[1] = color.value;
+  }
+  if (decoration != null) {
+    result[0] |= 1 << 2;
+    result[2] = decoration._mask;
+  }
+  if (decorationColor != null) {
+    result[0] |= 1 << 3;
+    result[3] = decorationColor.value;
+  }
+  if (decorationStyle != null) {
+    result[0] |= 1 << 4;
+    result[4] = decorationStyle.index;
+  }
+  if (fontWeight != null) {
+    result[0] |= 1 << 5;
+    result[5] = fontWeight.index;
+  }
+  if (fontStyle != null) {
+    result[0] |= 1 << 6;
+    result[6] = fontStyle.index;
+  }
+  if (textBaseline != null) {
+    result[0] |= 1 << 7;
+    result[7] = textBaseline.index;
+  }
+  if (decorationThickness != null) {
+    result[0] |= 1 << 8;
+  }
+  if (fontFamily != null || (fontFamilyFallback != null && fontFamilyFallback.isNotEmpty)) {
+    result[0] |= 1 << 9;
+    // Passed separately to native.
+  }
+  if (fontSize != null) {
+    result[0] |= 1 << 10;
+    // Passed separately to native.
+  }
+  if (letterSpacing != null) {
+    result[0] |= 1 << 11;
+    // Passed separately to native.
+  }
+  if (wordSpacing != null) {
+    result[0] |= 1 << 12;
+    // Passed separately to native.
+  }
+  if (height != null) {
+    result[0] |= 1 << 13;
+    // Passed separately to native.
+  }
+  if (locale != null) {
+    result[0] |= 1 << 14;
+    // Passed separately to native.
+  }
+  if (background != null) {
+    result[0] |= 1 << 15;
+    // Passed separately to native.
+  }
+  if (foreground != null) {
+    result[0] |= 1 << 16;
+    // Passed separately to native.
+  }
+  if (shadows != null) {
+    result[0] |= 1 << 17;
+    // Passed separately to native.
+  }
+  if (fontFeatures != null) {
+    result[0] |= 1 << 18;
+    // Passed separately to native.
+  }
+  return result;
+}
+
+/// An opaque object that determines the size, position, and rendering of text.
+///
+/// See also:
+///
+///  * [TextStyle](https://api.flutter.dev/flutter/painting/TextStyle-class.html), the class in the [painting] library.
+///
+class TextStyle {
+  /// Creates a new TextStyle object.
+  ///
+  /// * `color`: The color to use when painting the text. If this is specified, `foreground` must be null.
+  /// * `decoration`: The decorations to paint near the text (e.g., an underline).
+  /// * `decorationColor`: The color in which to paint the text decorations.
+  /// * `decorationStyle`: The style in which to paint the text decorations (e.g., dashed).
+  /// * `decorationThickness`: The thickness of the decoration as a muliplier on the thickness specified by the font.
+  /// * `fontWeight`: The typeface thickness to use when painting the text (e.g., bold).
+  /// * `fontStyle`: The typeface variant to use when drawing the letters (e.g., italics).
+  /// * `fontFamily`: The name of the font to use when painting the text (e.g., Roboto). If a `fontFamilyFallback` is
+  ///   provided and `fontFamily` is not, then the first font family in `fontFamilyFallback` will take the position of
+  ///   the preferred font family. When a higher priority font cannot be found or does not contain a glyph, a lower
+  ///   priority font will be used.
+  /// * `fontFamilyFallback`: An ordered list of the names of the fonts to fallback on when a glyph cannot
+  ///   be found in a higher priority font. When the `fontFamily` is null, the first font family in this list
+  ///   is used as the preferred font. Internally, the 'fontFamily` is concatenated to the front of this list.
+  ///   When no font family is provided through 'fontFamilyFallback' (null or empty) or `fontFamily`, then the
+  ///   platform default font will be used.
+  /// * `fontSize`: The size of glyphs (in logical pixels) to use when painting the text.
+  /// * `letterSpacing`: The amount of space (in logical pixels) to add between each letter.
+  /// * `wordSpacing`: The amount of space (in logical pixels) to add at each sequence of white-space (i.e. between each word).
+  /// * `textBaseline`: The common baseline that should be aligned between this text span and its parent text span, or, for the root text spans, with the line box.
+  /// * `height`: The height of this text span, as a multiplier of the font size. Omitting `height` will allow the line height
+  ///   to take the height as defined by the font, which may not be exactly the height of the fontSize.
+  /// * `locale`: The locale used to select region-specific glyphs.
+  /// * `background`: The paint drawn as a background for the text.
+  /// * `foreground`: The paint used to draw the text. If this is specified, `color` must be null.
+  /// * `fontFeatures`: The font features that should be applied to the text.
+  TextStyle({
+    Color? color,
+    TextDecoration? decoration,
+    Color? decorationColor,
+    TextDecorationStyle? decorationStyle,
+    double? decorationThickness,
+    FontWeight? fontWeight,
+    FontStyle? fontStyle,
+    TextBaseline? textBaseline,
+    String? fontFamily,
+    List<String>? fontFamilyFallback,
+    double? fontSize,
+    double? letterSpacing,
+    double? wordSpacing,
+    double? height,
+    Locale? locale,
+    Paint? background,
+    Paint? foreground,
+    List<Shadow>? shadows,
+    List<FontFeature>? fontFeatures,
+  }) : assert(color == null || foreground == null,
+         'Cannot provide both a color and a foreground\n'
+         'The color argument is just a shorthand for "foreground: Paint()..color = color".'
+       ),
+       _encoded = _encodeTextStyle(
+         color,
+         decoration,
+         decorationColor,
+         decorationStyle,
+         decorationThickness,
+         fontWeight,
+         fontStyle,
+         textBaseline,
+         fontFamily,
+         fontFamilyFallback,
+         fontSize,
+         letterSpacing,
+         wordSpacing,
+         height,
+         locale,
+         background,
+         foreground,
+         shadows,
+         fontFeatures,
+       ),
+       _fontFamily = fontFamily ?? '',
+       _fontFamilyFallback = fontFamilyFallback,
+       _fontSize = fontSize,
+       _letterSpacing = letterSpacing,
+       _wordSpacing = wordSpacing,
+       _height = height,
+       _decorationThickness = decorationThickness,
+       _locale = locale,
+       _background = background,
+       _foreground = foreground,
+       _shadows = shadows,
+       _fontFeatures = fontFeatures;
+
+  final Int32List _encoded;
+  final String _fontFamily;
+  final List<String>? _fontFamilyFallback;
+  final double? _fontSize;
+  final double? _letterSpacing;
+  final double? _wordSpacing;
+  final double? _height;
+  final double? _decorationThickness;
+  final Locale? _locale;
+  final Paint? _background;
+  final Paint? _foreground;
+  final List<Shadow>? _shadows;
+  final List<FontFeature>? _fontFeatures;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    return other is TextStyle
+        && other._fontFamily == _fontFamily
+        && other._fontSize == _fontSize
+        && other._letterSpacing == _letterSpacing
+        && other._wordSpacing == _wordSpacing
+        && other._height == _height
+        && other._decorationThickness == _decorationThickness
+        && other._locale == _locale
+        && other._background == _background
+        && other._foreground == _foreground
+        && _listEquals<int>(other._encoded, _encoded)
+        && _listEquals<Shadow>(other._shadows, _shadows)
+        && _listEquals<String>(other._fontFamilyFallback, _fontFamilyFallback)
+        && _listEquals<FontFeature>(other._fontFeatures, _fontFeatures);
+  }
+
+  @override
+  int get hashCode => hashValues(hashList(_encoded), _fontFamily, _fontFamilyFallback, _fontSize, _letterSpacing, _wordSpacing, _height, _locale, _background, _foreground, hashList(_shadows), _decorationThickness, hashList(_fontFeatures));
+
+  @override
+  String toString() {
+    return 'TextStyle('
+             'color: ${              _encoded[0] & 0x00002 == 0x00002  ? Color(_encoded[1])                  : "unspecified"}, '
+             'decoration: ${         _encoded[0] & 0x00004 == 0x00004  ? TextDecoration._(_encoded[2])       : "unspecified"}, '
+             'decorationColor: ${    _encoded[0] & 0x00008 == 0x00008  ? Color(_encoded[3])                  : "unspecified"}, '
+             'decorationStyle: ${    _encoded[0] & 0x00010 == 0x00010  ? TextDecorationStyle.values[_encoded[4]] : "unspecified"}, '
+             // The decorationThickness is not in encoded order in order to keep it near the other decoration properties.
+             'decorationThickness: ${_encoded[0] & 0x00100 == 0x00100  ? _decorationThickness                    : "unspecified"}, '
+             'fontWeight: ${         _encoded[0] & 0x00020 == 0x00020  ? FontWeight.values[_encoded[5]]          : "unspecified"}, '
+             'fontStyle: ${          _encoded[0] & 0x00040 == 0x00040  ? FontStyle.values[_encoded[6]]           : "unspecified"}, '
+             'textBaseline: ${       _encoded[0] & 0x00080 == 0x00080  ? TextBaseline.values[_encoded[7]]        : "unspecified"}, '
+             'fontFamily: ${         _encoded[0] & 0x00200 == 0x00200
+                                     && _fontFamily != ''              ? _fontFamily                             : "unspecified"}, '
+             'fontFamilyFallback: ${ _encoded[0] & 0x00200 == 0x00200
+                                     && _fontFamilyFallback != null
+                                     && _fontFamilyFallback!.isNotEmpty ? _fontFamilyFallback                     : "unspecified"}, '
+             'fontSize: ${           _encoded[0] & 0x00400 == 0x00400  ? _fontSize                               : "unspecified"}, '
+             'letterSpacing: ${      _encoded[0] & 0x00800 == 0x00800  ? "${_letterSpacing}x"                    : "unspecified"}, '
+             'wordSpacing: ${        _encoded[0] & 0x01000 == 0x01000  ? "${_wordSpacing}x"                      : "unspecified"}, '
+             'height: ${             _encoded[0] & 0x02000 == 0x02000  ? "${_height}x"                           : "unspecified"}, '
+             'locale: ${             _encoded[0] & 0x04000 == 0x04000  ? _locale                                 : "unspecified"}, '
+             'background: ${         _encoded[0] & 0x08000 == 0x08000  ? _background                             : "unspecified"}, '
+             'foreground: ${         _encoded[0] & 0x10000 == 0x10000  ? _foreground                             : "unspecified"}, '
+             'shadows: ${            _encoded[0] & 0x20000 == 0x20000  ? _shadows                                : "unspecified"}, '
+             'fontFeatures: ${       _encoded[0] & 0x40000 == 0x40000  ? _fontFeatures                           : "unspecified"}'
+           ')';
+  }
+}
+
+// This encoding must match the C++ version ParagraphBuilder::build.
+//
+// The encoded array buffer has 6 elements.
+//
+//  - Element 0: A bit mask indicating which fields are non-null.
+//    Bit 0 is unused. Bits 1-n are set if the corresponding index in the
+//    encoded array is non-null.  The remaining bits represent fields that
+//    are passed separately from the array.
+//
+//  - Element 1: The enum index of the |textAlign|.
+//
+//  - Element 2: The enum index of the |textDirection|.
+//
+//  - Element 3: The index of the |fontWeight|.
+//
+//  - Element 4: The enum index of the |fontStyle|.
+//
+//  - Element 5: The value of |maxLines|.
+//
+//  - Element 6: The encoded value of |textHeightBehavior|.
+//
+Int32List _encodeParagraphStyle(
+  TextAlign? textAlign,
+  TextDirection? textDirection,
+  int? maxLines,
+  String? fontFamily,
+  double? fontSize,
+  double? height,
+  TextHeightBehavior? textHeightBehavior,
+  FontWeight? fontWeight,
+  FontStyle? fontStyle,
+  StrutStyle? strutStyle,
+  String? ellipsis,
+  Locale? locale,
+) {
+  final Int32List result = Int32List(7); // also update paragraph_builder.cc
+  if (textAlign != null) {
+    result[0] |= 1 << 1;
+    result[1] = textAlign.index;
+  }
+  if (textDirection != null) {
+    result[0] |= 1 << 2;
+    result[2] = textDirection.index;
+  }
+  if (fontWeight != null) {
+    result[0] |= 1 << 3;
+    result[3] = fontWeight.index;
+  }
+  if (fontStyle != null) {
+    result[0] |= 1 << 4;
+    result[4] = fontStyle.index;
+  }
+  if (maxLines != null) {
+    result[0] |= 1 << 5;
+    result[5] = maxLines;
+  }
+  if (textHeightBehavior != null) {
+    result[0] |= 1 << 6;
+    result[6] = textHeightBehavior.encode();
+  }
+  if (fontFamily != null) {
+    result[0] |= 1 << 7;
+    // Passed separately to native.
+  }
+  if (fontSize != null) {
+    result[0] |= 1 << 8;
+    // Passed separately to native.
+  }
+  if (height != null) {
+    result[0] |= 1 << 9;
+    // Passed separately to native.
+  }
+  if (strutStyle != null) {
+    result[0] |= 1 << 10;
+    // Passed separately to native.
+  }
+  if (ellipsis != null) {
+    result[0] |= 1 << 11;
+    // Passed separately to native.
+  }
+  if (locale != null) {
+    result[0] |= 1 << 12;
+    // Passed separately to native.
+  }
+  return result;
+}
+
+/// An opaque object that determines the configuration used by
+/// [ParagraphBuilder] to position lines within a [Paragraph] of text.
+class ParagraphStyle {
+  /// Creates a new ParagraphStyle object.
+  ///
+  /// * `textAlign`: The alignment of the text within the lines of the
+  ///   paragraph. If the last line is ellipsized (see `ellipsis` below), the
+  ///   alignment is applied to that line after it has been truncated but before
+  ///   the ellipsis has been added.
+   //   See: https://github.com/flutter/flutter/issues/9819
+  ///
+  /// * `textDirection`: The directionality of the text, left-to-right (e.g.
+  ///   Norwegian) or right-to-left (e.g. Hebrew). This controls the overall
+  ///   directionality of the paragraph, as well as the meaning of
+  ///   [TextAlign.start] and [TextAlign.end] in the `textAlign` field.
+  ///
+  /// * `maxLines`: The maximum number of lines painted. Lines beyond this
+  ///   number are silently dropped. For example, if `maxLines` is 1, then only
+  ///   one line is rendered. If `maxLines` is null, but `ellipsis` is not null,
+  ///   then lines after the first one that overflows the width constraints are
+  ///   dropped. The width constraints are those set in the
+  ///   [ParagraphConstraints] object passed to the [Paragraph.layout] method.
+  ///
+  /// * `fontFamily`: The name of the font family to apply when painting the text,
+  ///   in the absence of a `textStyle` being attached to the span.
+  ///
+  /// * `fontSize`: The fallback size of glyphs (in logical pixels) to
+  ///   use when painting the text. This is used when there is no [TextStyle].
+  ///
+  /// * `height`: The fallback height of the spans as a multiplier of the font
+  ///   size. The fallback height is used when no height is provided through
+  ///   [TextStyle.height]. Omitting `height` here and in [TextStyle] will allow
+  ///   the line height to take the height as defined by the font, which may not
+  ///   be exactly the height of the `fontSize`.
+  ///
+  /// * `textHeightBehavior`: Specifies how the `height` multiplier is
+  ///   applied to ascent of the first line and the descent of the last line.
+  ///
+  /// * `fontWeight`: The typeface thickness to use when painting the text
+  ///   (e.g., bold).
+  ///
+  /// * `fontStyle`: The typeface variant to use when drawing the letters (e.g.,
+  ///   italics).
+  ///
+  /// * `strutStyle`: The properties of the strut. Strut defines a set of minimum
+  ///   vertical line height related metrics and can be used to obtain more
+  ///   advanced line spacing behavior.
+  ///
+  /// * `ellipsis`: String used to ellipsize overflowing text. If `maxLines` is
+  ///   not null, then the `ellipsis`, if any, is applied to the last rendered
+  ///   line, if that line overflows the width constraints. If `maxLines` is
+  ///   null, then the `ellipsis` is applied to the first line that overflows
+  ///   the width constraints, and subsequent lines are dropped. The width
+  ///   constraints are those set in the [ParagraphConstraints] object passed to
+  ///   the [Paragraph.layout] method. The empty string and the null value are
+  ///   considered equivalent and turn off this behavior.
+  ///
+  /// * `locale`: The locale used to select region-specific glyphs.
+  ParagraphStyle({
+    TextAlign? textAlign,
+    TextDirection? textDirection,
+    int? maxLines,
+    String? fontFamily,
+    double? fontSize,
+    double? height,
+    TextHeightBehavior? textHeightBehavior,
+    FontWeight? fontWeight,
+    FontStyle? fontStyle,
+    StrutStyle? strutStyle,
+    String? ellipsis,
+    Locale? locale,
+  }) : _encoded = _encodeParagraphStyle(
+         textAlign,
+         textDirection,
+         maxLines,
+         fontFamily,
+         fontSize,
+         height,
+         textHeightBehavior,
+         fontWeight,
+         fontStyle,
+         strutStyle,
+         ellipsis,
+         locale,
+       ),
+       _fontFamily = fontFamily,
+       _fontSize = fontSize,
+       _height = height,
+       _strutStyle = strutStyle,
+       _ellipsis = ellipsis,
+       _locale = locale;
+
+  final Int32List _encoded;
+  final String? _fontFamily;
+  final double? _fontSize;
+  final double? _height;
+  final StrutStyle? _strutStyle;
+  final String? _ellipsis;
+  final Locale? _locale;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ParagraphStyle
+        && other._fontFamily == _fontFamily
+        && other._fontSize == _fontSize
+        && other._height == _height
+        && other._strutStyle == _strutStyle
+        && other._ellipsis == _ellipsis
+        && other._locale == _locale
+        && _listEquals<int>(other._encoded, _encoded);
+  }
+
+  @override
+  int get hashCode => hashValues(hashList(_encoded), _fontFamily, _fontSize, _height, _ellipsis, _locale);
+
+  @override
+  String toString() {
+    return 'ParagraphStyle('
+             'textAlign: ${     _encoded[0] & 0x002 == 0x002 ? TextAlign.values[_encoded[1]]     : "unspecified"}, '
+             'textDirection: ${ _encoded[0] & 0x004 == 0x004 ? TextDirection.values[_encoded[2]] : "unspecified"}, '
+             'fontWeight: ${    _encoded[0] & 0x008 == 0x008 ? FontWeight.values[_encoded[3]]    : "unspecified"}, '
+             'fontStyle: ${     _encoded[0] & 0x010 == 0x010 ? FontStyle.values[_encoded[4]]     : "unspecified"}, '
+             'maxLines: ${      _encoded[0] & 0x020 == 0x020 ? _encoded[5]                       : "unspecified"}, '
+             'textHeightBehavior: ${
+                                _encoded[0] & 0x040 == 0x040 ?
+                                          TextHeightBehavior.fromEncoded(_encoded[6]).toString() : "unspecified"}, '
+             'fontFamily: ${    _encoded[0] & 0x080 == 0x080 ? _fontFamily                       : "unspecified"}, '
+             'fontSize: ${      _encoded[0] & 0x100 == 0x100 ? _fontSize                         : "unspecified"}, '
+             'height: ${        _encoded[0] & 0x200 == 0x200 ? "${_height}x"                     : "unspecified"}, '
+             'ellipsis: ${      _encoded[0] & 0x400 == 0x400 ? "\"$_ellipsis\""                  : "unspecified"}, '
+             'locale: ${        _encoded[0] & 0x800 == 0x800 ? _locale                           : "unspecified"}'
+           ')';
+  }
+}
+
+// Serialize strut properties into ByteData. This encoding errs towards
+// compactness. The first 8 bits is a bitmask that records which properties
+// are null. The rest of the values are encoded in the same order encountered
+// in the bitmask. The final returned value truncates any unused bytes
+// at the end.
+//
+// We serialize this more thoroughly than ParagraphStyle because it is
+// much more likely that the strut is empty/null and we wish to add
+// minimal overhead for non-strut cases.
+ByteData _encodeStrut(
+  String? fontFamily,
+  List<String>? fontFamilyFallback,
+  double? fontSize,
+  double? height,
+  double? leading,
+  FontWeight? fontWeight,
+  FontStyle? fontStyle,
+  bool? forceStrutHeight) {
+  if (fontFamily == null &&
+    fontSize == null &&
+    height == null &&
+    leading == null &&
+    fontWeight == null &&
+    fontStyle == null &&
+    forceStrutHeight == null)
+    return ByteData(0);
+
+  final ByteData data = ByteData(15); // Max size is 15 bytes
+  int bitmask = 0; // 8 bit mask
+  int byteCount = 1;
+  if (fontWeight != null) {
+    bitmask |= 1 << 0;
+    data.setInt8(byteCount, fontWeight.index);
+    byteCount += 1;
+  }
+  if (fontStyle != null) {
+    bitmask |= 1 << 1;
+    data.setInt8(byteCount, fontStyle.index);
+    byteCount += 1;
+  }
+  if (fontFamily != null || (fontFamilyFallback != null && fontFamilyFallback.isNotEmpty)){
+    bitmask |= 1 << 2;
+    // passed separately to native
+  }
+  if (fontSize != null) {
+    bitmask |= 1 << 3;
+    data.setFloat32(byteCount, fontSize, _kFakeHostEndian);
+    byteCount += 4;
+  }
+  if (height != null) {
+    bitmask |= 1 << 4;
+    data.setFloat32(byteCount, height, _kFakeHostEndian);
+    byteCount += 4;
+  }
+  if (leading != null) {
+    bitmask |= 1 << 5;
+    data.setFloat32(byteCount, leading, _kFakeHostEndian);
+    byteCount += 4;
+  }
+  if (forceStrutHeight != null) {
+    bitmask |= 1 << 6;
+    // We store this boolean directly in the bitmask since there is
+    // extra space in the 16 bit int.
+    bitmask |= (forceStrutHeight ? 1 : 0) << 7;
+  }
+
+  data.setInt8(0, bitmask);
+
+  return ByteData.view(data.buffer, 0,  byteCount);
+}
+
+/// See also:
+///
+///  * [StrutStyle](https://api.flutter.dev/flutter/painting/StrutStyle-class.html), the class in the [painting] library.
+///
+class StrutStyle {
+  /// Creates a new StrutStyle object.
+  ///
+  /// * `fontFamily`: The name of the font to use when painting the text (e.g.,
+  ///   Roboto).
+  ///
+  /// * `fontFamilyFallback`: An ordered list of font family names that will be
+  ///    searched for when the font in `fontFamily` cannot be found.
+  ///
+  /// * `fontSize`: The size of glyphs (in logical pixels) to use when painting
+  ///   the text.
+  ///
+  /// * `height`: The minimum height of the line boxes, as a multiplier of the
+  ///   font size. The lines of the paragraph will be at least
+  ///   `(height + leading) * fontSize` tall when `fontSize` is not null. Omitting
+  ///   `height` will allow the minimum line height to take the height as defined
+  ///   by the font, which may not be exactly the height of the `fontSize`. When
+  ///   `fontSize` is null, there is no minimum line height. Tall glyphs due to
+  ///   baseline alignment or large [TextStyle.fontSize] may cause the actual line
+  ///   height after layout to be taller than specified here. The `fontSize` must
+  ///   be provided for this property to take effect.
+  ///
+  /// * `leading`: The minimum amount of leading between lines as a multiple of
+  ///   the font size. `fontSize` must be provided for this property to take effect.
+  ///
+  /// * `fontWeight`: The typeface thickness to use when painting the text
+  ///   (e.g., bold).
+  ///
+  /// * `fontStyle`: The typeface variant to use when drawing the letters (e.g.,
+  ///   italics).
+  ///
+  /// * `forceStrutHeight`: When true, the paragraph will force all lines to be exactly
+  ///   `(height + leading) * fontSize` tall from baseline to baseline.
+  ///   [TextStyle] is no longer able to influence the line height, and any tall
+  ///   glyphs may overlap with lines above. If a `fontFamily` is specified, the
+  ///   total ascent of the first line will be the min of the `Ascent + half-leading`
+  ///   of the `fontFamily` and `(height + leading) * fontSize`. Otherwise, it
+  ///   will be determined by the Ascent + half-leading of the first text.
+  StrutStyle({
+    String? fontFamily,
+    List<String>? fontFamilyFallback,
+    double? fontSize,
+    double? height,
+    double? leading,
+    FontWeight? fontWeight,
+    FontStyle? fontStyle,
+    bool? forceStrutHeight,
+  }) : _encoded = _encodeStrut(
+         fontFamily,
+         fontFamilyFallback,
+         fontSize,
+         height,
+         leading,
+         fontWeight,
+         fontStyle,
+         forceStrutHeight,
+       ),
+       _fontFamily = fontFamily,
+       _fontFamilyFallback = fontFamilyFallback;
+
+  final ByteData _encoded; // Most of the data for strut is encoded.
+  final String? _fontFamily;
+  final List<String>? _fontFamilyFallback;
+
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is StrutStyle
+        && other._fontFamily == _fontFamily
+        && _listEquals<String>(other._fontFamilyFallback, _fontFamilyFallback)
+        && _listEquals<int>(other._encoded.buffer.asInt8List(), _encoded.buffer.asInt8List());
+  }
+
+  @override
+  int get hashCode => hashValues(hashList(_encoded.buffer.asInt8List()), _fontFamily);
+
+}
+
+/// A direction in which text flows.
+///
+/// Some languages are written from the left to the right (for example, English,
+/// Tamil, or Chinese), while others are written from the right to the left (for
+/// example Aramaic, Hebrew, or Urdu). Some are also written in a mixture, for
+/// example Arabic is mostly written right-to-left, with numerals written
+/// left-to-right.
+///
+/// The text direction must be provided to APIs that render text or lay out
+/// boxes horizontally, so that they can determine which direction to start in:
+/// either right-to-left, [TextDirection.rtl]; or left-to-right,
+/// [TextDirection.ltr].
+///
+/// ## Design discussion
+///
+/// Flutter is designed to address the needs of applications written in any of
+/// the world's currently-used languages, whether they use a right-to-left or
+/// left-to-right writing direction. Flutter does not support other writing
+/// modes, such as vertical text or boustrophedon text, as these are rarely used
+/// in computer programs.
+///
+/// It is common when developing user interface frameworks to pick a default
+/// text direction — typically left-to-right, the direction most familiar to the
+/// engineers working on the framework — because this simplifies the development
+/// of applications on the platform. Unfortunately, this frequently results in
+/// the platform having unexpected left-to-right biases or assumptions, as
+/// engineers will typically miss places where they need to support
+/// right-to-left text. This then results in bugs that only manifest in
+/// right-to-left environments.
+///
+/// In an effort to minimize the extent to which Flutter experiences this
+/// category of issues, the lowest levels of the Flutter framework do not have a
+/// default text reading direction. Any time a reading direction is necessary,
+/// for example when text is to be displayed, or when a
+/// writing-direction-dependent value is to be interpreted, the reading
+/// direction must be explicitly specified. Where possible, such as in `switch`
+/// statements, the right-to-left case is listed first, to avoid the impression
+/// that it is an afterthought.
+///
+/// At the higher levels (specifically starting at the widgets library), an
+/// ambient [Directionality] is introduced, which provides a default. Thus, for
+/// instance, a [Text] widget in the scope of a [MaterialApp] widget does not
+/// need to be given an explicit writing direction. The [Directionality.of]
+/// static method can be used to obtain the ambient text direction for a
+/// particular [BuildContext].
+///
+/// ### Known left-to-right biases in Flutter
+///
+/// Despite the design intent described above, certain left-to-right biases have
+/// nonetheless crept into Flutter's design. These include:
+///
+///  * The [Canvas] origin is at the top left, and the x-axis increases in a
+///    left-to-right direction.
+///
+///  * The default localization in the widgets and material libraries is
+///    American English, which is left-to-right.
+///
+/// ### Visual properties vs directional properties
+///
+/// Many classes in the Flutter framework are offered in two versions, a
+/// visually-oriented variant, and a text-direction-dependent variant. For
+/// example, [EdgeInsets] is described in terms of top, left, right, and bottom,
+/// while [EdgeInsetsDirectional] is described in terms of top, start, end, and
+/// bottom, where start and end correspond to right and left in right-to-left
+/// text and left and right in left-to-right text.
+///
+/// There are distinct use cases for each of these variants.
+///
+/// Text-direction-dependent variants are useful when developing user interfaces
+/// that should "flip" with the text direction. For example, a paragraph of text
+/// in English will typically be left-aligned and a quote will be indented from
+/// the left, while in Arabic it will be right-aligned and indented from the
+/// right. Both of these cases are described by the direction-dependent
+/// [TextAlign.start] and [EdgeInsetsDirectional.start].
+///
+/// In contrast, the visual variants are useful when the text direction is known
+/// and not affected by the reading direction. For example, an application
+/// giving driving directions might show a "turn left" arrow on the left and a
+/// "turn right" arrow on the right — and would do so whether the application
+/// was localized to French (left-to-right) or Hebrew (right-to-left).
+///
+/// In practice, it is also expected that many developers will only be
+/// targeting one language, and in that case it may be simpler to think in
+/// visual terms.
+// The order of this enum must match the order of the values in TextDirection.h's TextDirection.
+enum TextDirection {
+  /// The text flows from right to left (e.g. Arabic, Hebrew).
+  rtl,
+
+  /// The text flows from left to right (e.g., English, French).
+  ltr,
+}
+
+/// A rectangle enclosing a run of text.
+///
+/// This is similar to [Rect] but includes an inherent [TextDirection].
+@pragma('vm:entry-point')
+class TextBox {
+  /// Creates an object that describes a box containing text.
+  const TextBox.fromLTRBD(
+    this.left,
+    this.top,
+    this.right,
+    this.bottom,
+    this.direction,
+  );
+
+  /// The left edge of the text box, irrespective of direction.
+  ///
+  /// To get the leading edge (which may depend on the [direction]), consider [start].
+  final double left;
+
+  /// The top edge of the text box.
+  final double top;
+
+  /// The right edge of the text box, irrespective of direction.
+  ///
+  /// To get the trailing edge (which may depend on the [direction]), consider [end].
+  final double right;
+
+  /// The bottom edge of the text box.
+  final double bottom;
+
+  /// The direction in which text inside this box flows.
+  final TextDirection direction;
+
+  /// Returns a rect of the same size as this box.
+  Rect toRect() => Rect.fromLTRB(left, top, right, bottom);
+
+  /// The [left] edge of the box for left-to-right text; the [right] edge of the box for right-to-left text.
+  ///
+  /// See also:
+  ///
+  ///  * [direction], which specifies the text direction.
+  double get start {
+    return (direction == TextDirection.ltr) ? left : right;
+  }
+
+  /// The [right] edge of the box for left-to-right text; the [left] edge of the box for right-to-left text.
+  ///
+  /// See also:
+  ///
+  ///  * [direction], which specifies the text direction.
+  double get end {
+    return (direction == TextDirection.ltr) ? right : left;
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is TextBox
+        && other.left == left
+        && other.top == top
+        && other.right == right
+        && other.bottom == bottom
+        && other.direction == direction;
+  }
+
+  @override
+  int get hashCode => hashValues(left, top, right, bottom, direction);
+
+  @override
+  String toString() => 'TextBox.fromLTRBD(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)}, $direction)';
+}
+
+/// A way to disambiguate a [TextPosition] when its offset could match two
+/// different locations in the rendered string.
+///
+/// For example, at an offset where the rendered text wraps, there are two
+/// visual positions that the offset could represent: one prior to the line
+/// break (at the end of the first line) and one after the line break (at the
+/// start of the second line). A text affinity disambiguates between these two
+/// cases.
+///
+/// This affects only line breaks caused by wrapping, not explicit newline
+/// characters. For newline characters, the position is fully specified by the
+/// offset alone, and there is no ambiguity.
+///
+/// [TextAffinity] also affects bidirectional text at the interface between LTR
+/// and RTL text. Consider the following string, where the lowercase letters
+/// will be displayed as LTR and the uppercase letters RTL: "helloHELLO".  When
+/// rendered, the string would appear visually as "helloOLLEH".  An offset of 5
+/// would be ambiguous without a corresponding [TextAffinity].  Looking at the
+/// string in code, the offset represents the position just after the "o" and
+/// just before the "H".  When rendered, this offset could be either in the
+/// middle of the string to the right of the "o" or at the end of the string to
+/// the right of the "H".
+enum TextAffinity {
+  /// The position has affinity for the upstream side of the text position, i.e.
+  /// in the direction of the beginning of the string.
+  ///
+  /// In the example of an offset at the place where text is wrapping, upstream
+  /// indicates the end of the first line.
+  ///
+  /// In the bidirectional text example "helloHELLO", an offset of 5 with
+  /// [TextAffinity] upstream would appear in the middle of the rendered text,
+  /// just to the right of the "o". See the definition of [TextAffinity] for the
+  /// full example.
+  upstream,
+
+  /// The position has affinity for the downstream side of the text position,
+  /// i.e. in the direction of the end of the string.
+  ///
+  /// In the example of an offset at the place where text is wrapping,
+  /// downstream indicates the beginning of the second line.
+  ///
+  /// In the bidirectional text example "helloHELLO", an offset of 5 with
+  /// [TextAffinity] downstream would appear at the end of the rendered text,
+  /// just to the right of the "H". See the definition of [TextAffinity] for the
+  /// full example.
+  downstream,
+}
+
+/// A position in a string of text.
+///
+/// A TextPosition can be used to locate a position in a string in code (using
+/// the [offset] property), and it can also be used to locate the same position
+/// visually in a rendered string of text (using [offset] and, when needed to
+/// resolve ambiguity, [affinity]).
+///
+/// The location of an offset in a rendered string is ambiguous in two cases.
+/// One happens when rendered text is forced to wrap. In this case, the offset
+/// where the wrap occurs could visually appear either at the end of the first
+/// line or the beginning of the second line. The second way is with
+/// bidirectional text.  An offset at the interface between two different text
+/// directions could have one of two locations in the rendered text.
+///
+/// See the documentation for [TextAffinity] for more information on how
+/// TextAffinity disambiguates situations like these.
+class TextPosition {
+  /// Creates an object representing a particular position in a string.
+  ///
+  /// The arguments must not be null (so the [offset] argument is required).
+  const TextPosition({
+    required this.offset,
+    this.affinity = TextAffinity.downstream,
+  }) : assert(offset != null), // ignore: unnecessary_null_comparison
+       assert(affinity != null); // ignore: unnecessary_null_comparison
+
+  /// The index of the character that immediately follows the position in the
+  /// string representation of the text.
+  ///
+  /// For example, given the string `'Hello'`, offset 0 represents the cursor
+  /// being before the `H`, while offset 5 represents the cursor being just
+  /// after the `o`.
+  final int offset;
+
+  /// Disambiguates cases where the position in the string given by [offset]
+  /// could represent two different visual positions in the rendered text. For
+  /// example, this can happen when text is forced to wrap, or when one string
+  /// of text is rendered with multiple text directions.
+  ///
+  /// See the documentation for [TextAffinity] for more information on how
+  /// TextAffinity disambiguates situations like these.
+  final TextAffinity affinity;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is TextPosition
+        && other.offset == offset
+        && other.affinity == affinity;
+  }
+
+  @override
+  int get hashCode => hashValues(offset, affinity);
+
+  @override
+  String toString() {
+    return 'TextPosition(offset: $offset, affinity: $affinity)';
+  }
+}
+
+/// A range of characters in a string of text.
+class TextRange {
+  /// Creates a text range.
+  ///
+  /// The [start] and [end] arguments must not be null. Both the [start] and
+  /// [end] must either be greater than or equal to zero or both exactly -1.
+  ///
+  /// The text included in the range includes the character at [start], but not
+  /// the one at [end].
+  ///
+  /// Instead of creating an empty text range, consider using the [empty]
+  /// constant.
+  const TextRange({
+    required this.start,
+    required this.end,
+  }) : assert(start != null && start >= -1), // ignore: unnecessary_null_comparison
+        assert(end != null && end >= -1); // ignore: unnecessary_null_comparison
+
+  /// A text range that starts and ends at offset.
+  ///
+  /// The [offset] argument must be non-null and greater than or equal to -1.
+  const TextRange.collapsed(int offset)
+      : assert(offset != null && offset >= -1), // ignore: unnecessary_null_comparison
+        start = offset,
+        end = offset;
+
+  /// A text range that contains nothing and is not in the text.
+  static const TextRange empty = TextRange(start: -1, end: -1);
+
+  /// The index of the first character in the range.
+  ///
+  /// If [start] and [end] are both -1, the text range is empty.
+  final int start;
+
+  /// The next index after the characters in this range.
+  ///
+  /// If [start] and [end] are both -1, the text range is empty.
+  final int end;
+
+  /// Whether this range represents a valid position in the text.
+  bool get isValid => start >= 0 && end >= 0;
+
+  /// Whether this range is empty (but still potentially placed inside the text).
+  bool get isCollapsed => start == end;
+
+  /// Whether the start of this range precedes the end.
+  bool get isNormalized => end >= start;
+
+  /// The text before this range.
+  String textBefore(String text) {
+    assert(isNormalized);
+    return text.substring(0, start);
+  }
+
+  /// The text after this range.
+  String textAfter(String text) {
+    assert(isNormalized);
+    return text.substring(end);
+  }
+
+  /// The text inside this range.
+  String textInside(String text) {
+    assert(isNormalized);
+    return text.substring(start, end);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    return other is TextRange
+        && other.start == start
+        && other.end == end;
+  }
+
+  @override
+  int get hashCode => hashValues(
+    start.hashCode,
+    end.hashCode,
+  );
+
+  @override
+  String toString() => 'TextRange(start: $start, end: $end)';
+}
+
+/// Layout constraints for [Paragraph] objects.
+///
+/// Instances of this class are typically used with [Paragraph.layout].
+///
+/// The only constraint that can be specified is the [width]. See the discussion
+/// at [width] for more details.
+class ParagraphConstraints {
+  /// Creates constraints for laying out a paragraph.
+  ///
+  /// The [width] argument must not be null.
+  const ParagraphConstraints({
+    required this.width,
+  }) : assert(width != null); // ignore: unnecessary_null_comparison
+
+  /// The width the paragraph should use whey computing the positions of glyphs.
+  ///
+  /// If possible, the paragraph will select a soft line break prior to reaching
+  /// this width. If no soft line break is available, the paragraph will select
+  /// a hard line break prior to reaching this width. If that would force a line
+  /// break without any characters having been placed (i.e. if the next
+  /// character to be laid out does not fit within the given width constraint)
+  /// then the next character is allowed to overflow the width constraint and a
+  /// forced line break is placed after it (even if an explicit line break
+  /// follows).
+  ///
+  /// The width influences how ellipses are applied. See the discussion at
+  /// [new ParagraphStyle] for more details.
+  ///
+  /// This width is also used to position glyphs according to the [TextAlign]
+  /// alignment described in the [ParagraphStyle] used when building the
+  /// [Paragraph] with a [ParagraphBuilder].
+  final double width;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ParagraphConstraints
+        && other.width == width;
+  }
+
+  @override
+  int get hashCode => width.hashCode;
+
+  @override
+  String toString() => 'ParagraphConstraints(width: $width)';
+}
+
+/// Defines various ways to vertically bound the boxes returned by
+/// [Paragraph.getBoxesForRange].
+///
+/// See [BoxWidthStyle] for a similar property to control width.
+enum BoxHeightStyle {
+  /// Provide tight bounding boxes that fit heights per run. This style may result
+  /// in uneven bounding boxes that do not nicely connect with adjacent boxes.
+  tight,
+
+  /// The height of the boxes will be the maximum height of all runs in the
+  /// line. All boxes in the same line will be the same height.
+  ///
+  /// This does not guarantee that the boxes will cover the entire vertical height of the line
+  /// when there is additional line spacing.
+  ///
+  /// See [BoxHeightStyle.includeLineSpacingTop], [BoxHeightStyle.includeLineSpacingMiddle],
+  /// and [BoxHeightStyle.includeLineSpacingBottom] for styles that will cover
+  /// the entire line.
+  max,
+
+  /// Extends the top and bottom edge of the bounds to fully cover any line
+  /// spacing.
+  ///
+  /// The top and bottom of each box will cover half of the
+  /// space above and half of the space below the line.
+  ///
+  /// {@template dart.ui.boxHeightStyle.includeLineSpacing}
+  /// The top edge of each line should be the same as the bottom edge
+  /// of the line above. There should be no gaps in vertical coverage given any
+  /// amount of line spacing. Line spacing is not included above the first line
+  /// and below the last line due to no additional space present there.
+  /// {@endtemplate}
+  includeLineSpacingMiddle,
+
+  /// Extends the top edge of the bounds to fully cover any line spacing.
+  ///
+  /// The line spacing will be added to the top of the box.
+  ///
+  /// {@macro dart.ui.boxHeightStyle.includeLineSpacing}
+  includeLineSpacingTop,
+
+  /// Extends the bottom edge of the bounds to fully cover any line spacing.
+  ///
+  /// The line spacing will be added to the bottom of the box.
+  ///
+  /// {@macro dart.ui.boxHeightStyle.includeLineSpacing}
+  includeLineSpacingBottom,
+
+  /// Calculate box heights based on the metrics of this paragraph's [StrutStyle].
+  ///
+  /// Boxes based on the strut will have consistent heights throughout the
+  /// entire paragraph.  The top edge of each line will align with the bottom
+  /// edge of the previous line.  It is possible for glyphs to extend outside
+  /// these boxes.
+  strut,
+}
+
+/// Defines various ways to horizontally bound the boxes returned by
+/// [Paragraph.getBoxesForRange].
+///
+/// See [BoxHeightStyle] for a similar property to control height.
+enum BoxWidthStyle {
+  /// Provide tight bounding boxes that fit widths to the runs of each line
+  /// independently.
+  tight,
+
+  /// Adds up to two additional boxes as needed at the beginning and/or end
+  /// of each line so that the widths of the boxes in line are the same width
+  /// as the widest line in the paragraph.
+  ///
+  /// The additional boxes on each line are only added when the relevant box
+  /// at the relevant edge of that line does not span the maximum width of
+  /// the paragraph.
+  max,
+}
+
+/// Where to vertically align the placeholder relative to the surrounding text.
+///
+/// Used by [ParagraphBuilder.addPlaceholder].
+enum PlaceholderAlignment {
+  /// Match the baseline of the placeholder with the baseline.
+  ///
+  /// The [TextBaseline] to use must be specified and non-null when using this
+  /// alignment mode.
+  baseline,
+
+  /// Align the bottom edge of the placeholder with the baseline such that the
+  /// placeholder sits on top of the baseline.
+  ///
+  /// The [TextBaseline] to use must be specified and non-null when using this
+  /// alignment mode.
+  aboveBaseline,
+
+  /// Align the top edge of the placeholder with the baseline specified
+  /// such that the placeholder hangs below the baseline.
+  ///
+  /// The [TextBaseline] to use must be specified and non-null when using this
+  /// alignment mode.
+  belowBaseline,
+
+  /// Align the top edge of the placeholder with the top edge of the text.
+  ///
+  /// When the placeholder is very tall, the extra space will hang from
+  /// the top and extend through the bottom of the line.
+  top,
+
+  /// Align the bottom edge of the placeholder with the bottom edge of the text.
+  ///
+  /// When the placeholder is very tall, the extra space will rise from the
+  /// bottom and extend through the top of the line.
+  bottom,
+
+  /// Align the middle of the placeholder with the middle of the text.
+  ///
+  /// When the placeholder is very tall, the extra space will grow equally
+  /// from the top and bottom of the line.
+  middle,
+}
+
+/// [LineMetrics] stores the measurements and statistics of a single line in the
+/// paragraph.
+///
+/// The measurements here are for the line as a whole, and represent the maximum
+/// extent of the line instead of per-run or per-glyph metrics. For more detailed
+/// metrics, see [TextBox] and [Paragraph.getBoxesForRange].
+///
+/// [LineMetrics] should be obtained directly from the [Paragraph.computeLineMetrics]
+/// method.
+class LineMetrics {
+  /// Creates a [LineMetrics] object with only the specified values.
+  ///
+  /// Omitted values will remain null. [Paragraph.computeLineMetrics] produces
+  /// fully defined [LineMetrics] with no null values.
+  LineMetrics({
+    required this.hardBreak,
+    required this.ascent,
+    required this.descent,
+    required this.unscaledAscent,
+    required this.height,
+    required this.width,
+    required this.left,
+    required this.baseline,
+    required this.lineNumber,
+  });
+
+  /// True if this line ends with an explicit line break (e.g. '\n') or is the end
+  /// of the paragraph. False otherwise.
+  final bool hardBreak;
+
+  /// The rise from the [baseline] as calculated from the font and style for this line.
+  ///
+  /// This is the final computed ascent and can be impacted by the strut, height, scaling,
+  /// as well as outlying runs that are very tall.
+  ///
+  /// The [ascent] is provided as a positive value, even though it is typically defined
+  /// in fonts as negative. This is to ensure the signage of operations with these
+  /// metrics directly reflects the intended signage of the value. For example,
+  /// the y coordinate of the top edge of the line is `baseline - ascent`.
+  final double ascent;
+
+  /// The drop from the [baseline] as calculated from the font and style for this line.
+  ///
+  /// This is the final computed ascent and can be impacted by the strut, height, scaling,
+  /// as well as outlying runs that are very tall.
+  ///
+  /// The y coordinate of the bottom edge of the line is `baseline + descent`.
+  final double descent;
+
+  /// The rise from the [baseline] as calculated from the font and style for this line
+  /// ignoring the [TextStyle.height].
+  ///
+  /// The [unscaledAscent] is provided as a positive value, even though it is typically
+  /// defined in fonts as negative. This is to ensure the signage of operations with
+  /// these metrics directly reflects the intended signage of the value.
+  final double unscaledAscent;
+
+  /// Total height of the line from the top edge to the bottom edge.
+  ///
+  /// This is equivalent to `round(ascent + descent)`. This value is provided
+  /// separately due to rounding causing sub-pixel differences from the unrounded
+  /// values.
+  final double height;
+
+  /// Width of the line from the left edge of the leftmost glyph to the right
+  /// edge of the rightmost glyph.
+  ///
+  /// This is not the same as the width of the pargraph.
+  ///
+  /// See also:
+  ///
+  ///  * [Paragraph.width], the max width passed in during layout.
+  ///  * [Paragraph.longestLine], the width of the longest line in the paragraph.
+  final double width;
+
+  /// The x coordinate of left edge of the line.
+  ///
+  /// The right edge can be obtained with `left + width`.
+  final double left;
+
+  /// The y coordinate of the baseline for this line from the top of the paragraph.
+  ///
+  /// The bottom edge of the paragraph up to and including this line may be obtained
+  /// through `baseline + descent`.
+  final double baseline;
+
+  /// The number of this line in the overall paragraph, with the first line being
+  /// index zero.
+  ///
+  /// For example, the first line is line 0, second line is line 1.
+  final int lineNumber;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is LineMetrics
+        && other.hardBreak == hardBreak
+        && other.ascent == ascent
+        && other.descent == descent
+        && other.unscaledAscent == unscaledAscent
+        && other.height == height
+        && other.width == width
+        && other.left == left
+        && other.baseline == baseline
+        && other.lineNumber == lineNumber;
+  }
+
+  @override
+  int get hashCode => hashValues(hardBreak, ascent, descent, unscaledAscent, height, width, left, baseline, lineNumber);
+
+  @override
+  String toString() {
+    return 'LineMetrics(hardBreak: $hardBreak, '
+                       'ascent: $ascent, '
+                       'descent: $descent, '
+                       'unscaledAscent: $unscaledAscent, '
+                       'height: $height, '
+                       'width: $width, '
+                       'left: $left, '
+                       'baseline: $baseline, '
+                       'lineNumber: $lineNumber)';
+  }
+}
+
+/// A paragraph of text.
+///
+/// A paragraph retains the size and position of each glyph in the text and can
+/// be efficiently resized and painted.
+///
+/// To create a [Paragraph] object, use a [ParagraphBuilder].
+///
+/// Paragraphs can be displayed on a [Canvas] using the [Canvas.drawParagraph]
+/// method.
+@pragma('vm:entry-point')
+class Paragraph extends NativeFieldWrapperClass2 {
+  /// This class is created by the engine, and should not be instantiated
+  /// or extended directly.
+  ///
+  /// To create a [Paragraph] object, use a [ParagraphBuilder].
+  @pragma('vm:entry-point')
+  Paragraph._();
+
+  /// The amount of horizontal space this paragraph occupies.
+  ///
+  /// Valid only after [layout] has been called.
+  double get width { throw UnimplementedError(); }
+
+  /// The amount of vertical space this paragraph occupies.
+  ///
+  /// Valid only after [layout] has been called.
+  double get height { throw UnimplementedError(); }
+
+  /// The distance from the left edge of the leftmost glyph to the right edge of
+  /// the rightmost glyph in the paragraph.
+  ///
+  /// Valid only after [layout] has been called.
+  double get longestLine { throw UnimplementedError(); }
+
+  /// The minimum width that this paragraph could be without failing to paint
+  /// its contents within itself.
+  ///
+  /// Valid only after [layout] has been called.
+  double get minIntrinsicWidth { throw UnimplementedError(); }
+
+  /// Returns the smallest width beyond which increasing the width never
+  /// decreases the height.
+  ///
+  /// Valid only after [layout] has been called.
+  double get maxIntrinsicWidth { throw UnimplementedError(); }
+
+  /// The distance from the top of the paragraph to the alphabetic
+  /// baseline of the first line, in logical pixels.
+  double get alphabeticBaseline { throw UnimplementedError(); }
+
+  /// The distance from the top of the paragraph to the ideographic
+  /// baseline of the first line, in logical pixels.
+  double get ideographicBaseline { throw UnimplementedError(); }
+
+  /// True if there is more vertical content, but the text was truncated, either
+  /// because we reached `maxLines` lines of text or because the `maxLines` was
+  /// null, `ellipsis` was not null, and one of the lines exceeded the width
+  /// constraint.
+  ///
+  /// See the discussion of the `maxLines` and `ellipsis` arguments at
+  /// [new ParagraphStyle].
+  bool get didExceedMaxLines { throw UnimplementedError(); }
+
+  /// Computes the size and position of each glyph in the paragraph.
+  ///
+  /// The [ParagraphConstraints] control how wide the text is allowed to be.
+  void layout(ParagraphConstraints constraints) => _layout(constraints.width);
+  void _layout(double width) { throw UnimplementedError(); }
+
+  List<TextBox> _decodeTextBoxes(Float32List encoded) {
+    final int count = encoded.length ~/ 5;
+    final List<TextBox> boxes = <TextBox>[];
+    int position = 0;
+    for (int index = 0; index < count; index += 1) {
+      boxes.add(TextBox.fromLTRBD(
+        encoded[position++],
+        encoded[position++],
+        encoded[position++],
+        encoded[position++],
+        TextDirection.values[encoded[position++].toInt()],
+      ));
+    }
+    return boxes;
+  }
+
+  /// Returns a list of text boxes that enclose the given text range.
+  ///
+  /// The [boxHeightStyle] and [boxWidthStyle] parameters allow customization
+  /// of how the boxes are bound vertically and horizontally. Both style
+  /// parameters default to the tight option, which will provide close-fitting
+  /// boxes and will not account for any line spacing.
+  ///
+  /// Coordinates of the TextBox are relative to the upper-left corner of the paragraph,
+  /// where positive y values indicate down.
+  ///
+  /// The [boxHeightStyle] and [boxWidthStyle] parameters must not be null.
+  ///
+  /// See [BoxHeightStyle] and [BoxWidthStyle] for full descriptions of each option.
+  List<TextBox> getBoxesForRange(int start, int end, {BoxHeightStyle boxHeightStyle = BoxHeightStyle.tight, BoxWidthStyle boxWidthStyle = BoxWidthStyle.tight}) {
+    assert(boxHeightStyle != null); // ignore: unnecessary_null_comparison
+    assert(boxWidthStyle != null); // ignore: unnecessary_null_comparison
+    return _decodeTextBoxes(_getBoxesForRange(start, end, boxHeightStyle.index, boxWidthStyle.index));
+  }
+  // See paragraph.cc for the layout of this return value.
+  Float32List _getBoxesForRange(int start, int end, int boxHeightStyle, int boxWidthStyle) { throw UnimplementedError(); }
+
+  /// Returns a list of text boxes that enclose all placeholders in the paragraph.
+  ///
+  /// The order of the boxes are in the same order as passed in through
+  /// [ParagraphBuilder.addPlaceholder].
+  ///
+  /// Coordinates of the [TextBox] are relative to the upper-left corner of the paragraph,
+  /// where positive y values indicate down.
+  List<TextBox> getBoxesForPlaceholders() {
+    return _decodeTextBoxes(_getBoxesForPlaceholders());
+  }
+  Float32List _getBoxesForPlaceholders() { throw UnimplementedError(); }
+
+  /// Returns the text position closest to the given offset.
+  TextPosition getPositionForOffset(Offset offset) {
+    final List<int> encoded = _getPositionForOffset(offset.dx, offset.dy);
+    return TextPosition(offset: encoded[0], affinity: TextAffinity.values[encoded[1]]);
+  }
+  List<int> _getPositionForOffset(double dx, double dy) { throw UnimplementedError(); }
+
+  /// Returns the [TextRange] of the word at the given [TextPosition].
+  ///
+  /// Characters not part of a word, such as spaces, symbols, and punctuation,
+  /// have word breaks on both sides. In such cases, this method will return
+  /// [offset, offset+1]. Word boundaries are defined more precisely in Unicode
+  /// Standard Annex #29 http://www.unicode.org/reports/tr29/#Word_Boundaries
+  TextRange getWordBoundary(TextPosition position) {
+    final List<int> boundary = _getWordBoundary(position.offset);
+    return TextRange(start: boundary[0], end: boundary[1]);
+  }
+  List<int> _getWordBoundary(int offset) { throw UnimplementedError(); }
+
+  /// Returns the [TextRange] of the line at the given [TextPosition].
+  ///
+  /// The newline (if any) is returned as part of the range.
+  ///
+  /// Not valid until after layout.
+  ///
+  /// This can potentially be expensive, since it needs to compute the line
+  /// metrics, so use it sparingly.
+  TextRange getLineBoundary(TextPosition position) {
+    final List<int> boundary = _getLineBoundary(position.offset);
+    return TextRange(start: boundary[0], end: boundary[1]);
+  }
+  List<int> _getLineBoundary(int offset) { throw UnimplementedError(); }
+
+  // Redirecting the paint function in this way solves some dependency problems
+  // in the C++ code. If we straighten out the C++ dependencies, we can remove
+  // this indirection.
+  void _paint(Canvas canvas, double x, double y) { throw UnimplementedError(); }
+
+  /// Returns the full list of [LineMetrics] that describe in detail the various
+  /// metrics of each laid out line.
+  ///
+  /// Not valid until after layout.
+  ///
+  /// This can potentially return a large amount of data, so it is not recommended
+  /// to repeatedly call this. Instead, cache the results.
+  List<LineMetrics> computeLineMetrics() {
+    final Float64List encoded = _computeLineMetrics();
+    final int count = encoded.length ~/ 9;
+    int position = 0;
+    final List<LineMetrics> metrics = <LineMetrics>[
+      for (int index = 0; index < count; index += 1)
+        LineMetrics(
+          hardBreak:      encoded[position++] != 0,
+          ascent:         encoded[position++],
+          descent:        encoded[position++],
+          unscaledAscent: encoded[position++],
+          height:         encoded[position++],
+          width:          encoded[position++],
+          left:           encoded[position++],
+          baseline:       encoded[position++],
+          lineNumber:     encoded[position++].toInt(),
+        )
+    ];
+    return metrics;
+  }
+  Float64List _computeLineMetrics() { throw UnimplementedError(); }
+}
+
+/// Builds a [Paragraph] containing text with the given styling information.
+///
+/// To set the paragraph's alignment, truncation, and ellipsizing behavior, pass
+/// an appropriately-configured [ParagraphStyle] object to the
+/// [new ParagraphBuilder] constructor.
+///
+/// Then, call combinations of [pushStyle], [addText], and [pop] to add styled
+/// text to the object.
+///
+/// Finally, call [build] to obtain the constructed [Paragraph] object. After
+/// this point, the builder is no longer usable.
+///
+/// After constructing a [Paragraph], call [Paragraph.layout] on it and then
+/// paint it with [Canvas.drawParagraph].
+class ParagraphBuilder extends NativeFieldWrapperClass2 {
+
+  /// Creates a new [ParagraphBuilder] object, which is used to create a
+  /// [Paragraph].
+  @pragma('vm:entry-point')
+  ParagraphBuilder(ParagraphStyle style) {
+    List<String>? strutFontFamilies;
+    final StrutStyle? strutStyle = style._strutStyle;
+    if (strutStyle != null) {
+      strutFontFamilies = <String>[];
+      final String? fontFamily = strutStyle._fontFamily;
+      if (fontFamily != null)
+        strutFontFamilies.add(fontFamily);
+      if (strutStyle._fontFamilyFallback != null)
+        strutFontFamilies.addAll(strutStyle._fontFamilyFallback!);
+    }
+    _constructor(
+      style._encoded,
+      strutStyle?._encoded,
+      style._fontFamily,
+      strutFontFamilies,
+      style._fontSize,
+      style._height,
+      style._ellipsis,
+      _encodeLocale(style._locale)
+    );
+  }
+
+  void _constructor(
+    Int32List encoded,
+    ByteData? strutData,
+    String? fontFamily,
+    List<dynamic>? strutFontFamily,
+    double? fontSize,
+    double? height,
+    String? ellipsis,
+    String locale
+  ) { throw UnimplementedError(); }
+
+  /// The number of placeholders currently in the paragraph.
+  int get placeholderCount => _placeholderCount;
+  int _placeholderCount = 0;
+
+  /// The scales of the placeholders in the paragraph.
+  List<double> get placeholderScales => _placeholderScales;
+  List<double> _placeholderScales = <double>[];
+
+  /// Applies the given style to the added text until [pop] is called.
+  ///
+  /// See [pop] for details.
+  void pushStyle(TextStyle style) {
+    final List<String> fullFontFamilies = <String>[];
+    fullFontFamilies.add(style._fontFamily);
+    if (style._fontFamilyFallback != null)
+    fullFontFamilies.addAll(style._fontFamilyFallback!);
+
+    ByteData? encodedFontFeatures;
+    final List<FontFeature>? fontFeatures = style._fontFeatures;
+    if (fontFeatures != null) {
+      encodedFontFeatures = ByteData(fontFeatures.length * FontFeature._kEncodedSize);
+      int byteOffset = 0;
+      for (FontFeature feature in fontFeatures) {
+        feature._encode(ByteData.view(encodedFontFeatures.buffer, byteOffset, FontFeature._kEncodedSize));
+        byteOffset += FontFeature._kEncodedSize;
+      }
+    }
+
+    _pushStyle(
+      style._encoded,
+      fullFontFamilies,
+      style._fontSize,
+      style._letterSpacing,
+      style._wordSpacing,
+      style._height,
+      style._decorationThickness,
+      _encodeLocale(style._locale),
+      style._background?._objects,
+      style._background?._data,
+      style._foreground?._objects,
+      style._foreground?._data,
+      Shadow._encodeShadows(style._shadows),
+      encodedFontFeatures,
+    );
+  }
+
+  void _pushStyle(
+    Int32List encoded,
+    List<dynamic> fontFamilies,
+    double? fontSize,
+    double? letterSpacing,
+    double? wordSpacing,
+    double? height,
+    double? decorationThickness,
+    String locale,
+    List<dynamic>? backgroundObjects,
+    ByteData? backgroundData,
+    List<dynamic>? foregroundObjects,
+    ByteData? foregroundData,
+    ByteData shadowsData,
+    ByteData? fontFeaturesData,
+  ) { throw UnimplementedError(); }
+
+  static String _encodeLocale(Locale? locale) => locale?.toString() ?? '';
+
+  /// Ends the effect of the most recent call to [pushStyle].
+  ///
+  /// Internally, the paragraph builder maintains a stack of text styles. Text
+  /// added to the paragraph is affected by all the styles in the stack. Calling
+  /// [pop] removes the topmost style in the stack, leaving the remaining styles
+  /// in effect.
+  void pop() { throw UnimplementedError(); }
+
+  /// Adds the given text to the paragraph.
+  ///
+  /// The text will be styled according to the current stack of text styles.
+  void addText(String text) {
+    final String? error = _addText(text);
+    if (error != null)
+      throw ArgumentError(error);
+  }
+  String? _addText(String text) { throw UnimplementedError(); }
+
+  /// Adds an inline placeholder space to the paragraph.
+  ///
+  /// The paragraph will contain a rectangular space with no text of the dimensions
+  /// specified.
+  ///
+  /// The `width` and `height` parameters specify the size of the placeholder rectangle.
+  ///
+  /// The `alignment` parameter specifies how the placeholder rectangle will be vertically
+  /// aligned with the surrounding text. When [PlaceholderAlignment.baseline],
+  /// [PlaceholderAlignment.aboveBaseline], and [PlaceholderAlignment.belowBaseline]
+  /// alignment modes are used, the baseline needs to be set with the `baseline`.
+  /// When using [PlaceholderAlignment.baseline], `baselineOffset` indicates the distance
+  /// of the baseline down from the top of of the rectangle. The default `baselineOffset`
+  /// is the `height`.
+  ///
+  /// Examples:
+  ///
+  /// * For a 30x50 placeholder with the bottom edge aligned with the bottom of the text, use:
+  /// `addPlaceholder(30, 50, PlaceholderAlignment.bottom);`
+  /// * For a 30x50 placeholder that is vertically centered around the text, use:
+  /// `addPlaceholder(30, 50, PlaceholderAlignment.middle);`.
+  /// * For a 30x50 placeholder that sits completely on top of the alphabetic baseline, use:
+  /// `addPlaceholder(30, 50, PlaceholderAlignment.aboveBaseline, baseline: TextBaseline.alphabetic)`.
+  /// * For a 30x50 placeholder with 40 pixels above and 10 pixels below the alphabetic baseline, use:
+  /// `addPlaceholder(30, 50, PlaceholderAlignment.baseline, baseline: TextBaseline.alphabetic, baselineOffset: 40)`.
+  ///
+  /// Lines are permitted to break around each placeholder.
+  ///
+  /// Decorations will be drawn based on the font defined in the most recently
+  /// pushed [TextStyle]. The decorations are drawn as if unicode text were present
+  /// in the placeholder space, and will draw the same regardless of the height and
+  /// alignment of the placeholder. To hide or manually adjust decorations to fit,
+  /// a text style with the desired decoration behavior should be pushed before
+  /// adding a placeholder.
+  ///
+  /// Any decorations drawn through a placeholder will exist on the same canvas/layer
+  /// as the text. This means any content drawn on top of the space reserved by
+  /// the placeholder will be drawn over the decoration, possibly obscuring the
+  /// decoration.
+  ///
+  /// Placeholders are represented by a unicode 0xFFFC "object replacement character"
+  /// in the text buffer. For each placeholder, one object replacement character is
+  /// added on to the text buffer.
+  ///
+  /// The `scale` parameter will scale the `width` and `height` by the specified amount,
+  /// and keep track of the scale. The scales of placeholders added can be accessed
+  /// through [placeholderScales]. This is primarily used for accessibility scaling.
+  void addPlaceholder(double width, double height, PlaceholderAlignment alignment, {
+    double scale = 1.0,
+    double? baselineOffset,
+    TextBaseline? baseline,
+  }) {
+    // Require a baseline to be specified if using a baseline-based alignment.
+    assert((alignment == PlaceholderAlignment.aboveBaseline ||
+            alignment == PlaceholderAlignment.belowBaseline ||
+            alignment == PlaceholderAlignment.baseline) ? baseline != null : true);
+    // Default the baselineOffset to height if null. This will place the placeholder
+    // fully above the baseline, similar to [PlaceholderAlignment.aboveBaseline].
+    baselineOffset = baselineOffset ?? height;
+    _addPlaceholder(width * scale, height * scale, alignment.index, baselineOffset * scale, baseline == null ? null : baseline.index);
+    _placeholderCount++;
+    _placeholderScales.add(scale);
+  }
+  String? _addPlaceholder(double width, double height, int alignment, double baselineOffset, int? baseline) { throw UnimplementedError(); }
+
+  /// Applies the given paragraph style and returns a [Paragraph] containing the
+  /// added text and associated styling.
+  ///
+  /// After calling this function, the paragraph builder object is invalid and
+  /// cannot be used further.
+  Paragraph build() {
+    final Paragraph paragraph = Paragraph._();
+    _build(paragraph);
+    return paragraph;
+  }
+  void _build(Paragraph outParagraph) { throw UnimplementedError(); }
+}
+
+/// Loads a font from a buffer and makes it available for rendering text.
+///
+/// * `list`: A list of bytes containing the font file.
+/// * `fontFamily`: The family name used to identify the font in text styles.
+///  If this is not provided, then the family name will be extracted from the font file.
+Future<void> loadFontFromList(Uint8List list, {String? fontFamily}) {
+  return _futurize(
+    (_Callback<void> callback) {
+      _loadFontFromList(list, callback, fontFamily);
+    }
+  ).then((_) => _sendFontChangeMessage());
+}
+
+final ByteData _fontChangeMessage = utf8.encoder.convert(
+  json.encode(<String, dynamic>{'type': 'fontsChange'})
+).buffer.asByteData();
+
+FutureOr<void> _sendFontChangeMessage() async {
+  const String kSystemChannelName = 'flutter/system';
+  if (PlatformDispatcher.instance.onPlatformMessage != null) {
+    _invoke3<String, ByteData?, PlatformMessageResponseCallback>(
+      PlatformDispatcher.instance.onPlatformMessage,
+      PlatformDispatcher.instance._onPlatformMessageZone,
+      kSystemChannelName,
+      _fontChangeMessage,
+      (ByteData? responseData) { },
+    );
+  } else {
+    channelBuffers.push(kSystemChannelName, _fontChangeMessage, (ByteData? responseData) { });
+  }
+}
+
+// TODO(gspencergoog): remove this template block once the framework templates
+// are renamed to not reference it.
+/// {@template flutter.dart:ui.textHeightBehavior}
+/// Defines how the paragraph will apply [TextStyle.height] to the ascent of the
+/// first line and descent of the last line.
+///
+/// Each boolean value represents whether the [TextStyle.height] modifier will
+/// be applied to the corresponding metric. By default, all properties are true,
+/// and [TextStyle.height] is applied as normal. When set to false, the font's
+/// default ascent will be used.
+/// {@endtemplate}
+
+void _loadFontFromList(Uint8List list, _Callback<void> callback, String? fontFamily) { throw UnimplementedError(); }
diff --git a/lib/src/ui/window.dart b/lib/src/ui/window.dart
new file mode 100644
index 0000000..3654a17
--- /dev/null
+++ b/lib/src/ui/window.dart
@@ -0,0 +1,842 @@
+// 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.
+
+
+part of dart.ui;
+
+/// A view into which a Flutter [Scene] is drawn.
+///
+/// Each [FlutterView] has its own layer tree that is rendered into an area
+/// inside of a [FlutterWindow] whenever [render] is called with a [Scene].
+///
+/// ## Insets and Padding
+///
+/// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/widgets/window_padding.mp4}
+///
+/// In this illustration, the black areas represent system UI that the app
+/// cannot draw over. The red area represents view padding that the view may not
+/// be able to detect gestures in and may not want to draw in. The grey area
+/// represents the system keyboard, which can cover over the bottom view padding
+/// when visible.
+///
+/// The [viewInsets] are the physical pixels which the operating
+/// system reserves for system UI, such as the keyboard, which would fully
+/// obscure any content drawn in that area.
+///
+/// The [viewPadding] are the physical pixels on each side of the
+/// display that may be partially obscured by system UI or by physical
+/// intrusions into the display, such as an overscan region on a television or a
+/// "notch" on a phone. Unlike the insets, these areas may have portions that
+/// show the user view-painted pixels without being obscured, such as a
+/// notch at the top of a phone that covers only a subset of the area. Insets,
+/// on the other hand, either partially or fully obscure the window, such as an
+/// opaque keyboard or a partially translucent status bar, which cover an area
+/// without gaps.
+///
+/// The [padding] property is computed from both
+/// [viewInsets] and [viewPadding]. It will allow a
+/// view inset to consume view padding where appropriate, such as when a phone's
+/// keyboard is covering the bottom view padding and so "absorbs" it.
+///
+/// Clients that want to position elements relative to the view padding
+/// regardless of the view insets should use the [viewPadding]
+/// property, e.g. if you wish to draw a widget at the center of the screen with
+/// respect to the iPhone "safe area" regardless of whether the keyboard is
+/// showing.
+///
+/// [padding] is useful for clients that want to know how much
+/// padding should be accounted for without concern for the current inset(s)
+/// state, e.g. determining whether a gesture should be considered for scrolling
+/// purposes. This value varies based on the current state of the insets. For
+/// example, a visible keyboard will consume all gestures in the bottom part of
+/// the [viewPadding] anyway, so there is no need to account for
+/// that in the [padding], which is always safe to use for such
+/// calculations.
+///
+/// See also:
+///
+///  * [FlutterWindow], a special case of a [FlutterView] that is represented on
+///    the platform as a separate window which can host other [FlutterView]s.
+abstract class FlutterView {
+  /// The platform dispatcher that this view is registered with, and gets its
+  /// information from.
+  PlatformDispatcher get platformDispatcher;
+
+  /// The configuration of this view.
+  ViewConfiguration get viewConfiguration;
+
+  /// The number of device pixels for each logical pixel for the screen this
+  /// view is displayed on.
+  ///
+  /// This number might not be a power of two. Indeed, it might not even be an
+  /// integer. For example, the Nexus 6 has a device pixel ratio of 3.5.
+  ///
+  /// Device pixels are also referred to as physical pixels. Logical pixels are
+  /// also referred to as device-independent or resolution-independent pixels.
+  ///
+  /// By definition, there are roughly 38 logical pixels per centimeter, or
+  /// about 96 logical pixels per inch, of the physical display. The value
+  /// returned by [devicePixelRatio] is ultimately obtained either from the
+  /// hardware itself, the device drivers, or a hard-coded value stored in the
+  /// operating system or firmware, and may be inaccurate, sometimes by a
+  /// significant margin.
+  ///
+  /// The Flutter framework operates in logical pixels, so it is rarely
+  /// necessary to directly deal with this property.
+  ///
+  /// When this changes, [onMetricsChanged] is called.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this value changes.
+  double get devicePixelRatio => viewConfiguration.devicePixelRatio;
+
+  /// The dimensions and location of the rectangle into which the scene rendered
+  /// in this view will be drawn on the screen, in physical pixels.
+  ///
+  /// When this changes, [onMetricsChanged] is called.
+  ///
+  /// At startup, the size and location of the view may not be known before Dart
+  /// code runs. If this value is observed early in the application lifecycle,
+  /// it may report [Rect.zero].
+  ///
+  /// This value does not take into account any on-screen keyboards or other
+  /// system UI. The [padding] and [viewInsets] properties provide a view into
+  /// how much of each side of the view may be obscured by system UI.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this value changes.
+  Rect get physicalGeometry => viewConfiguration.geometry;
+
+  /// The dimensions of the rectangle into which the scene rendered in this view
+  /// will be drawn on the screen, in physical pixels.
+  ///
+  /// When this changes, [onMetricsChanged] is called.
+  ///
+  /// At startup, the size of the view may not be known before Dart code runs.
+  /// If this value is observed early in the application lifecycle, it may
+  /// report [Size.zero].
+  ///
+  /// This value does not take into account any on-screen keyboards or other
+  /// system UI. The [padding] and [viewInsets] properties provide information
+  /// about how much of each side of the view may be obscured by system UI.
+  ///
+  /// This value is the same as the `size` member of [physicalGeometry].
+  ///
+  /// See also:
+  ///
+  ///  * [physicalGeometry], which reports the location of the view as well as
+  ///    its size.
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this value changes.
+  Size get physicalSize => viewConfiguration.geometry.size;
+
+  /// The number of physical pixels on each side of the display rectangle into
+  /// which the view can render, but over which the operating system will likely
+  /// place system UI, such as the keyboard, that fully obscures any content.
+  ///
+  /// When this property changes, [onMetricsChanged] is called.
+  ///
+  /// The relationship between this [viewInsets],
+  /// [viewPadding], and [padding] are described in
+  /// more detail in the documentation for [FlutterView].
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this value changes.
+  ///  * [MediaQuery.of], a simpler mechanism for the same.
+  ///  * [Scaffold], which automatically applies the view insets in material
+  ///    design applications.
+  WindowPadding get viewInsets => viewConfiguration.viewInsets;
+
+  /// The number of physical pixels on each side of the display rectangle into
+  /// which the view can render, but which may be partially obscured by system
+  /// UI (such as the system notification area), or or physical intrusions in
+  /// the display (e.g. overscan regions on television screens or phone sensor
+  /// housings).
+  ///
+  /// Unlike [padding], this value does not change relative to
+  /// [viewInsets]. For example, on an iPhone X, it will not
+  /// change in response to the soft keyboard being visible or hidden, whereas
+  /// [padding] will.
+  ///
+  /// When this property changes, [onMetricsChanged] is called.
+  ///
+  /// The relationship between this [viewInsets],
+  /// [viewPadding], and [padding] are described in
+  /// more detail in the documentation for [FlutterView].
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this value changes.
+  ///  * [MediaQuery.of], a simpler mechanism for the same.
+  ///  * [Scaffold], which automatically applies the padding in material design
+  ///    applications.
+  WindowPadding get viewPadding => viewConfiguration.viewPadding;
+
+  /// The number of physical pixels on each side of the display rectangle into
+  /// which the view can render, but where the operating system will consume
+  /// input gestures for the sake of system navigation.
+  ///
+  /// For example, an operating system might use the vertical edges of the
+  /// screen, where swiping inwards from the edges takes users backward
+  /// through the history of screens they previously visited.
+  ///
+  /// When this property changes, [onMetricsChanged] is called.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this value changes.
+  ///  * [MediaQuery.of], a simpler mechanism for the same.
+  WindowPadding get systemGestureInsets => viewConfiguration.systemGestureInsets;
+
+  /// The number of physical pixels on each side of the display rectangle into
+  /// which the view can render, but which may be partially obscured by system
+  /// UI (such as the system notification area), or or physical intrusions in
+  /// the display (e.g. overscan regions on television screens or phone sensor
+  /// housings).
+  ///
+  /// This value is calculated by taking `max(0.0, FlutterView.viewPadding -
+  /// FlutterView.viewInsets)`. This will treat a system IME that increases the
+  /// bottom inset as consuming that much of the bottom padding. For example, on
+  /// an iPhone X, [EdgeInsets.bottom] of [FlutterView.padding] is the same as
+  /// [EdgeInsets.bottom] of [FlutterView.viewPadding] when the soft keyboard is
+  /// not drawn (to account for the bottom soft button area), but will be `0.0`
+  /// when the soft keyboard is visible.
+  ///
+  /// When this changes, [onMetricsChanged] is called.
+  ///
+  /// The relationship between this [viewInsets], [viewPadding], and [padding]
+  /// are described in more detail in the documentation for [FlutterView].
+  ///
+  /// See also:
+  ///
+  /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///   observe when this value changes.
+  /// * [MediaQuery.of], a simpler mechanism for the same.
+  /// * [Scaffold], which automatically applies the padding in material design
+  ///   applications.
+  WindowPadding get padding => viewConfiguration.padding;
+
+  /// Updates the view's rendering on the GPU with the newly provided [Scene].
+  ///
+  /// This function must be called within the scope of the
+  /// [PlatformDispatcher.onBeginFrame] or [PlatformDispatcher.onDrawFrame]
+  /// callbacks being invoked.
+  ///
+  /// If this function is called a second time during a single
+  /// [PlatformDispatcher.onBeginFrame]/[PlatformDispatcher.onDrawFrame]
+  /// callback sequence or called outside the scope of those callbacks, the call
+  /// will be ignored.
+  ///
+  /// To record graphical operations, first create a [PictureRecorder], then
+  /// construct a [Canvas], passing that [PictureRecorder] to its constructor.
+  /// After issuing all the graphical operations, call the
+  /// [PictureRecorder.endRecording] function on the [PictureRecorder] to obtain
+  /// the final [Picture] that represents the issued graphical operations.
+  ///
+  /// Next, create a [SceneBuilder], and add the [Picture] to it using
+  /// [SceneBuilder.addPicture]. With the [SceneBuilder.build] method you can
+  /// then obtain a [Scene] object, which you can display to the user via this
+  /// [render] function.
+  ///
+  /// See also:
+  ///
+  /// * [SchedulerBinding], the Flutter framework class which manages the
+  ///   scheduling of frames.
+  /// * [RendererBinding], the Flutter framework class which manages layout and
+  ///   painting.
+  void render(Scene scene) => _render(scene, this);
+  void _render(Scene scene, FlutterView view) { throw UnimplementedError(); }
+}
+
+/// A top-level platform window displaying a Flutter layer tree drawn from a
+/// [Scene].
+///
+/// The current list of all Flutter views for the application is available from
+/// `WidgetsBinding.instance.platformDispatcher.views`. Only views that are of type
+/// [FlutterWindow] are top level platform windows.
+///
+/// There is also a [PlatformDispatcher.instance] singleton object in `dart:ui`
+/// if `WidgetsBinding` is unavailable, but we strongly advise avoiding a static
+/// reference to it. See the documentation for [PlatformDispatcher.instance] for
+/// more details about why it should be avoided.
+///
+/// See also:
+///
+/// * [PlatformDispatcher], which manages the current list of [FlutterView] (and
+///   thus [FlutterWindow]) instances.
+class FlutterWindow extends FlutterView {
+  FlutterWindow._(this._windowId, this.platformDispatcher);
+
+  /// The opaque ID for this view.
+  final Object _windowId;
+
+  @override
+  final PlatformDispatcher platformDispatcher;
+
+  @override
+  ViewConfiguration get viewConfiguration {
+    assert(platformDispatcher._viewConfigurations.containsKey(_windowId));
+    return platformDispatcher._viewConfigurations[_windowId]!;
+  }
+}
+
+/// A [FlutterWindow] that includes access to setting callbacks and retrieving
+/// properties that reside on the [PlatformDispatcher].
+///
+/// It is the type of the global [window] singleton used by applications that
+/// only have a single main window.
+///
+/// In addition to the properties of [FlutterView], this class provides access
+/// to platform-specific properties. To modify or retrieve these properties,
+/// applications designed for more than one main window should prefer using
+/// `WidgetsBinding.instance.platformDispatcher` instead.
+///
+/// Prefer access through `WidgetsBinding.instance.window` or
+/// `WidgetsBinding.instance.platformDispatcher` over a static reference to
+/// [window], or [PlatformDispatcher.instance]. See the documentation for
+/// [PlatformDispatcher.instance] for more details about this recommendation.
+class SingletonFlutterWindow extends FlutterWindow {
+  SingletonFlutterWindow._(Object windowId, PlatformDispatcher platformDispatcher)
+      : super._(windowId, platformDispatcher);
+
+  /// A callback that is invoked whenever the [devicePixelRatio],
+  /// [physicalSize], [padding], [viewInsets], [PlatformDispatcher.views], or
+  /// [systemGestureInsets] values change.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// See [PlatformDispatcher.onMetricsChanged] for more information.
+  VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged;
+  set onMetricsChanged(VoidCallback? callback) {
+    platformDispatcher.onMetricsChanged = callback;
+  }
+
+  /// The system-reported default locale of the device.
+  ///
+  /// {@template dart.ui.window.accessorForwardWarning}
+  /// Accessing this value returns the value contained in the
+  /// [PlatformDispatcher] singleton, so instead of getting it from here, you
+  /// should consider getting it from `WidgetsBinding.instance.platformDispatcher` instead
+  /// (or, when `WidgetsBinding` isn't available, from
+  /// [PlatformDispatcher.instance]). The reason this value forwards to the
+  /// [PlatformDispatcher] is to provide convenience for applications that only
+  /// use a single main window.
+  /// {@endtemplate}
+  ///
+  /// This establishes the language and formatting conventions that window
+  /// should, if possible, use to render their user interface.
+  ///
+  /// This is the first locale selected by the user and is the user's primary
+  /// locale (the locale the device UI is displayed in)
+  ///
+  /// This is equivalent to `locales.first` and will provide an empty non-null
+  /// locale if the [locales] list has not been set or is empty.
+  Locale get locale => platformDispatcher.locale;
+
+  /// The full system-reported supported locales of the device.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// This establishes the language and formatting conventions that window
+  /// should, if possible, use to render their user interface.
+  ///
+  /// The list is ordered in order of priority, with lower-indexed locales being
+  /// preferred over higher-indexed ones. The first element is the primary [locale].
+  ///
+  /// The [onLocaleChanged] callback is called whenever this value changes.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this value changes.
+  List<Locale> get locales => platformDispatcher.locales;
+
+  /// Performs the platform-native locale resolution.
+  ///
+  /// Each platform may return different results.
+  ///
+  /// If the platform fails to resolve a locale, then this will return null.
+  ///
+  /// This method returns synchronously and is a direct call to
+  /// platform specific APIs without invoking method channels.
+  Locale? computePlatformResolvedLocale(List<Locale> supportedLocales) {
+    return platformDispatcher.computePlatformResolvedLocale(supportedLocales);
+  }
+
+  /// A callback that is invoked whenever [locale] changes value.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// The framework invokes this callback in the same zone in which the
+  /// callback was set.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this callback is invoked.
+  VoidCallback? get onLocaleChanged => platformDispatcher.onLocaleChanged;
+  set onLocaleChanged(VoidCallback? callback) {
+    platformDispatcher.onLocaleChanged = callback;
+  }
+
+  /// The lifecycle state immediately after dart isolate initialization.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// This property will not be updated as the lifecycle changes.
+  ///
+  /// It is used to initialize [SchedulerBinding.lifecycleState] at startup
+  /// with any buffered lifecycle state events.
+  String get initialLifecycleState => platformDispatcher.initialLifecycleState;
+
+  /// The system-reported text scale.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// This establishes the text scaling factor to use when rendering text,
+  /// according to the user's platform preferences.
+  ///
+  /// The [onTextScaleFactorChanged] callback is called whenever this value
+  /// changes.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this value changes.
+  double get textScaleFactor => platformDispatcher.textScaleFactor;
+
+  /// The setting indicating whether time should always be shown in the 24-hour
+  /// format.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// This option is used by [showTimePicker].
+  bool get alwaysUse24HourFormat => platformDispatcher.alwaysUse24HourFormat;
+
+  /// A callback that is invoked whenever [textScaleFactor] changes value.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// The framework invokes this callback in the same zone in which the
+  /// callback was set.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this callback is invoked.
+  VoidCallback? get onTextScaleFactorChanged => platformDispatcher.onTextScaleFactorChanged;
+  set onTextScaleFactorChanged(VoidCallback? callback) {
+    platformDispatcher.onTextScaleFactorChanged = callback;
+  }
+
+  /// The setting indicating the current brightness mode of the host platform.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// If the platform has no preference, [platformBrightness] defaults to
+  /// [Brightness.light].
+  Brightness get platformBrightness => platformDispatcher.platformBrightness;
+
+  /// A callback that is invoked whenever [platformBrightness] changes value.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// The framework invokes this callback in the same zone in which the
+  /// callback was set.
+  ///
+  /// See also:
+  ///
+  ///  * [WidgetsBindingObserver], for a mechanism at the widgets layer to
+  ///    observe when this callback is invoked.
+  VoidCallback? get onPlatformBrightnessChanged => platformDispatcher.onPlatformBrightnessChanged;
+  set onPlatformBrightnessChanged(VoidCallback? callback) {
+    platformDispatcher.onPlatformBrightnessChanged = callback;
+  }
+
+  /// A callback that is invoked to notify the window that it is an appropriate
+  /// time to provide a scene using the [SceneBuilder] API and the [render]
+  /// method.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// When possible, this is driven by the hardware VSync signal. This is only
+  /// called if [scheduleFrame] has been called since the last time this
+  /// callback was invoked.
+  ///
+  /// The [onDrawFrame] callback is invoked immediately after [onBeginFrame],
+  /// after draining any microtasks (e.g. completions of any [Future]s) queued
+  /// by the [onBeginFrame] handler.
+  ///
+  /// The framework invokes this callback in the same zone in which the
+  /// callback was set.
+  ///
+  /// See also:
+  ///
+  ///  * [SchedulerBinding], the Flutter framework class which manages the
+  ///    scheduling of frames.
+  ///  * [RendererBinding], the Flutter framework class which manages layout and
+  ///    painting.
+  FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame;
+  set onBeginFrame(FrameCallback? callback) {
+    platformDispatcher.onBeginFrame = callback;
+  }
+
+  /// A callback that is invoked for each frame after [onBeginFrame] has
+  /// completed and after the microtask queue has been drained.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// This can be used to implement a second phase of frame rendering that
+  /// happens after any deferred work queued by the [onBeginFrame] phase.
+  ///
+  /// The framework invokes this callback in the same zone in which the
+  /// callback was set.
+  ///
+  /// See also:
+  ///
+  ///  * [SchedulerBinding], the Flutter framework class which manages the
+  ///    scheduling of frames.
+  ///  * [RendererBinding], the Flutter framework class which manages layout and
+  ///    painting.
+  VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame;
+  set onDrawFrame(VoidCallback? callback) {
+    platformDispatcher.onDrawFrame = callback;
+  }
+
+  /// A callback that is invoked to report the [FrameTiming] of recently
+  /// rasterized frames.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// It's prefered to use [SchedulerBinding.addTimingsCallback] than to use
+  /// [SingletonFlutterWindow.onReportTimings] directly because
+  /// [SchedulerBinding.addTimingsCallback] allows multiple callbacks.
+  ///
+  /// This can be used to see if the window has missed frames (through
+  /// [FrameTiming.buildDuration] and [FrameTiming.rasterDuration]), or high
+  /// latencies (through [FrameTiming.totalSpan]).
+  ///
+  /// Unlike [Timeline], the timing information here is available in the release
+  /// mode (additional to the profile and the debug mode). Hence this can be
+  /// used to monitor the application's performance in the wild.
+  ///
+  /// {@macro dart.ui.TimingsCallback.list}
+  ///
+  /// If this is null, no additional work will be done. If this is not null,
+  /// Flutter spends less than 0.1ms every 1 second to report the timings
+  /// (measured on iPhone6S). The 0.1ms is about 0.6% of 16ms (frame budget for
+  /// 60fps), or 0.01% CPU usage per second.
+  TimingsCallback? get onReportTimings => platformDispatcher.onReportTimings;
+  set onReportTimings(TimingsCallback? callback) {
+    platformDispatcher.onReportTimings = callback;
+  }
+
+  /// A callback that is invoked when pointer data is available.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// The framework invokes this callback in the same zone in which the
+  /// callback was set.
+  ///
+  /// See also:
+  ///
+  ///  * [GestureBinding], the Flutter framework class which manages pointer
+  ///    events.
+  PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket;
+  set onPointerDataPacket(PointerDataPacketCallback? callback) {
+    platformDispatcher.onPointerDataPacket = callback;
+  }
+
+  /// The route or path that the embedder requested when the application was
+  /// launched.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// This will be the string "`/`" if no particular route was requested.
+  ///
+  /// ## Android
+  ///
+  /// On Android, the initial route can be set on the [initialRoute](/javadoc/io/flutter/embedding/android/FlutterActivity.NewEngineIntentBuilder.html#initialRoute-java.lang.String-)
+  /// method of the [FlutterActivity](/javadoc/io/flutter/embedding/android/FlutterActivity.html)'s
+  /// intent builder.
+  ///
+  /// On a standalone engine, see https://flutter.dev/docs/development/add-to-app/android/add-flutter-screen#initial-route-with-a-cached-engine.
+  ///
+  /// ## iOS
+  ///
+  /// On iOS, the initial route can be set on the `initialRoute`
+  /// parameter of the [FlutterViewController](/objcdoc/Classes/FlutterViewController.html)'s
+  /// initializer.
+  ///
+  /// On a standalone engine, see https://flutter.dev/docs/development/add-to-app/ios/add-flutter-screen#route.
+  ///
+  /// See also:
+  ///
+  ///  * [Navigator], a widget that handles routing.
+  ///  * [SystemChannels.navigation], which handles subsequent navigation
+  ///    requests from the embedder.
+  String get defaultRouteName => platformDispatcher.defaultRouteName;
+
+  /// Requests that, at the next appropriate opportunity, the [onBeginFrame] and
+  /// [onDrawFrame] callbacks be invoked.
+  ///
+  /// {@template dart.ui.window.functionForwardWarning}
+  /// Calling this function forwards the call to the same function on the
+  /// [PlatformDispatcher] singleton, so instead of calling it here, you should
+  /// consider calling it on `WidgetsBinding.instance.platformDispatcher` instead (or, when
+  /// `WidgetsBinding` isn't available, on [PlatformDispatcher.instance]). The
+  /// reason this function forwards to the [PlatformDispatcher] is to provide
+  /// convenience for applications that only use a single main window.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  /// * [SchedulerBinding], the Flutter framework class which manages the
+  ///   scheduling of frames.
+  void scheduleFrame() => platformDispatcher.scheduleFrame();
+
+  /// Whether the user has requested that [updateSemantics] be called when
+  /// the semantic contents of window changes.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// The [onSemanticsEnabledChanged] callback is called whenever this value
+  /// changes.
+  bool get semanticsEnabled => platformDispatcher.semanticsEnabled;
+
+  /// A callback that is invoked when the value of [semanticsEnabled] changes.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// The framework invokes this callback in the same zone in which the
+  /// callback was set.
+  VoidCallback? get onSemanticsEnabledChanged => platformDispatcher.onSemanticsEnabledChanged;
+  set onSemanticsEnabledChanged(VoidCallback? callback) {
+    platformDispatcher.onSemanticsEnabledChanged = callback;
+  }
+
+  /// A callback that is invoked whenever the user requests an action to be
+  /// performed.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// This callback is used when the user expresses the action they wish to
+  /// perform based on the semantics supplied by [updateSemantics].
+  ///
+  /// The framework invokes this callback in the same zone in which the
+  /// callback was set.
+  SemanticsActionCallback? get onSemanticsAction => platformDispatcher.onSemanticsAction;
+  set onSemanticsAction(SemanticsActionCallback? callback) {
+    platformDispatcher.onSemanticsAction = callback;
+  }
+
+  /// Additional accessibility features that may be enabled by the platform.
+  AccessibilityFeatures get accessibilityFeatures => platformDispatcher.accessibilityFeatures;
+
+  /// A callback that is invoked when the value of [accessibilityFeatures] changes.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// The framework invokes this callback in the same zone in which the
+  /// callback was set.
+  VoidCallback? get onAccessibilityFeaturesChanged => platformDispatcher.onAccessibilityFeaturesChanged;
+  set onAccessibilityFeaturesChanged(VoidCallback? callback) {
+    platformDispatcher.onAccessibilityFeaturesChanged = callback;
+  }
+
+  /// Change the retained semantics data about this window.
+  ///
+  /// {@macro dart.ui.window.functionForwardWarning}
+  ///
+  /// If [semanticsEnabled] is true, the user has requested that this function
+  /// be called whenever the semantic content of this window changes.
+  ///
+  /// In either case, this function disposes the given update, which means the
+  /// semantics update cannot be used further.
+  void updateSemantics(SemanticsUpdate update) => platformDispatcher.updateSemantics(update);
+
+  /// Sends a message to a platform-specific plugin.
+  ///
+  /// {@macro dart.ui.window.functionForwardWarning}
+  ///
+  /// The `name` parameter determines which plugin receives the message. The
+  /// `data` parameter contains the message payload and is typically UTF-8
+  /// encoded JSON but can be arbitrary data. If the plugin replies to the
+  /// message, `callback` will be called with the response.
+  ///
+  /// The framework invokes [callback] in the same zone in which this method
+  /// was called.
+  void sendPlatformMessage(String name,
+      ByteData? data,
+      PlatformMessageResponseCallback? callback) {
+    platformDispatcher.sendPlatformMessage(name, data, callback);
+  }
+
+  /// Called whenever this window receives a message from a platform-specific
+  /// plugin.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// The `name` parameter determines which plugin sent the message. The `data`
+  /// parameter is the payload and is typically UTF-8 encoded JSON but can be
+  /// arbitrary data.
+  ///
+  /// Message handlers must call the function given in the `callback` parameter.
+  /// If the handler does not need to respond, the handler should pass null to
+  /// the callback.
+  ///
+  /// The framework invokes this callback in the same zone in which the
+  /// callback was set.
+  // TODO(ianh): deprecate once framework uses [ChannelBuffers.setListener].
+  PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage;
+  set onPlatformMessage(PlatformMessageCallback? callback) {
+    platformDispatcher.onPlatformMessage = callback;
+  }
+
+  /// Set the debug name associated with this platform dispatcher's root
+  /// isolate.
+  ///
+  /// {@macro dart.ui.window.accessorForwardWarning}
+  ///
+  /// Normally debug names are automatically generated from the Dart port, entry
+  /// point, and source file. For example: `main.dart$main-1234`.
+  ///
+  /// This can be combined with flutter tools `--isolate-filter` flag to debug
+  /// specific root isolates. For example: `flutter attach --isolate-filter=[name]`.
+  /// Note that this does not rename any child isolates of the root.
+  void setIsolateDebugName(String name) => PlatformDispatcher.instance.setIsolateDebugName(name);
+}
+
+/// Additional accessibility features that may be enabled by the platform.
+///
+/// It is not possible to enable these settings from Flutter, instead they are
+/// used by the platform to indicate that additional accessibility features are
+/// enabled.
+//
+// When changes are made to this class, the equivalent APIs in each of the
+// embedders *must* be updated.
+class AccessibilityFeatures {
+  const AccessibilityFeatures._(this._index);
+
+  static const int _kAccessibleNavigation = 1 << 0;
+  static const int _kInvertColorsIndex = 1 << 1;
+  static const int _kDisableAnimationsIndex = 1 << 2;
+  static const int _kBoldTextIndex = 1 << 3;
+  static const int _kReduceMotionIndex = 1 << 4;
+  static const int _kHighContrastIndex = 1 << 5;
+
+  // A bitfield which represents each enabled feature.
+  final int _index;
+
+  /// Whether there is a running accessibility service which is changing the
+  /// interaction model of the device.
+  ///
+  /// For example, TalkBack on Android and VoiceOver on iOS enable this flag.
+  bool get accessibleNavigation => _kAccessibleNavigation & _index != 0;
+
+  /// The platform is inverting the colors of the application.
+  bool get invertColors => _kInvertColorsIndex & _index != 0;
+
+  /// The platform is requesting that animations be disabled or simplified.
+  bool get disableAnimations => _kDisableAnimationsIndex & _index != 0;
+
+  /// The platform is requesting that text be rendered at a bold font weight.
+  ///
+  /// Only supported on iOS.
+  bool get boldText => _kBoldTextIndex & _index != 0;
+
+  /// The platform is requesting that certain animations be simplified and
+  /// parallax effects removed.
+  ///
+  /// Only supported on iOS.
+  bool get reduceMotion => _kReduceMotionIndex & _index != 0;
+
+  /// The platform is requesting that UI be rendered with darker colors.
+  ///
+  /// Only supported on iOS.
+  bool get highContrast => _kHighContrastIndex & _index != 0;
+
+  @override
+  String toString() {
+    final List<String> features = <String>[];
+    if (accessibleNavigation)
+      features.add('accessibleNavigation');
+    if (invertColors)
+      features.add('invertColors');
+    if (disableAnimations)
+      features.add('disableAnimations');
+    if (boldText)
+      features.add('boldText');
+    if (reduceMotion)
+      features.add('reduceMotion');
+    if (highContrast)
+      features.add('highContrast');
+    return 'AccessibilityFeatures$features';
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is AccessibilityFeatures
+        && other._index == _index;
+  }
+
+  @override
+  int get hashCode => _index.hashCode;
+}
+
+/// Describes the contrast of a theme or color palette.
+enum Brightness {
+  /// The color is dark and will require a light text color to achieve readable
+  /// contrast.
+  ///
+  /// For example, the color might be dark grey, requiring white text.
+  dark,
+
+  /// The color is light and will require a dark text color to achieve readable
+  /// contrast.
+  ///
+  /// For example, the color might be bright white, requiring black text.
+  light,
+}
+
+/// The [SingletonFlutterWindow] representing the main window for applications
+/// where there is only one window, such as applications designed for
+/// single-display mobile devices.
+///
+/// Applications that are designed to use more than one window should interact
+/// with the `WidgetsBinding.instance.platformDispatcher` instead.
+///
+/// Consider avoiding static references to this singleton through
+/// [PlatformDispatcher.instance] and instead prefer using a binding for
+/// dependency resolution such as `WidgetsBinding.instance.window`.
+///
+/// Static access of this `window` object means that Flutter has few, if any
+/// options to fake or mock the given object in tests. Even in cases where Dart
+/// offers special language constructs to forcefully shadow such properties,
+/// those mechanisms would only be reasonable for tests and they would not be
+/// reasonable for a future of Flutter where we legitimately want to select an
+/// appropriate implementation at runtime.
+///
+/// The only place that `WidgetsBinding.instance.window` is inappropriate is if
+/// access to these APIs is required before the binding is initialized by
+/// invoking `runApp()` or `WidgetsFlutterBinding.instance.ensureInitialized()`.
+/// In that case, it is necessary (though unfortunate) to use the
+/// [PlatformDispatcher.instance] object statically.
+///
+/// See also:
+///
+/// * [PlatformDispatcher.views], contains the current list of Flutter windows
+///   belonging to the application, including top level application windows like
+///   this one.
+final SingletonFlutterWindow window = SingletonFlutterWindow._(0, PlatformDispatcher.instance);
diff --git a/lib/src/widgets/actions.dart b/lib/src/widgets/actions.dart
new file mode 100644
index 0000000..8d95c68
--- /dev/null
+++ b/lib/src/widgets/actions.dart
@@ -0,0 +1,1319 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+
+import 'basic.dart';
+import 'focus_manager.dart';
+import 'focus_scope.dart';
+import 'framework.dart';
+import 'media_query.dart';
+import 'shortcuts.dart';
+
+// BuildContext/Element doesn't have a parent accessor, but it can be
+// simulated with visitAncestorElements. _getParent is needed because
+// context.getElementForInheritedWidgetOfExactType will return itself if it
+// happens to be of the correct type. getParent should be O(1), since we
+// always return false at the first ancestor.
+BuildContext _getParent(BuildContext context) {
+  late final BuildContext parent;
+  context.visitAncestorElements((Element ancestor) {
+    parent = ancestor;
+    return false;
+  });
+  return parent;
+}
+
+/// An abstract class representing a particular configuration of an [Action].
+///
+/// This class is what the [Shortcuts.shortcuts] map has as values, and is used
+/// by an [ActionDispatcher] to look up an action and invoke it, giving it this
+/// object to extract configuration information from.
+///
+/// See also:
+///
+///  * [Actions.invoke], which invokes the action associated with a specified
+///    [Intent] using the [Actions] widget that most tightly encloses the given
+///    [BuildContext].
+@immutable
+abstract class Intent with Diagnosticable {
+  /// A const constructor for an [Intent].
+  const Intent();
+
+  /// An intent that is mapped to a [DoNothingAction], which, as the name
+  /// implies, does nothing.
+  ///
+  /// This Intent is mapped to an action in the [WidgetsApp] that does nothing,
+  /// so that it can be bound to a key in a [Shortcuts] widget in order to
+  /// disable a key binding made above it in the hierarchy.
+  static const DoNothingIntent doNothing = DoNothingIntent._();
+}
+
+/// The kind of callback that an [Action] uses to notify of changes to the
+/// action's state.
+///
+/// To register an action listener, call [Action.addActionListener].
+typedef ActionListenerCallback = void Function(Action<Intent> action);
+
+/// Base class for actions.
+///
+/// As the name implies, an [Action] is an action or command to be performed.
+/// They are typically invoked as a result of a user action, such as a keyboard
+/// shortcut in a [Shortcuts] widget, which is used to look up an [Intent],
+/// which is given to an [ActionDispatcher] to map the [Intent] to an [Action]
+/// and invoke it.
+///
+/// The [ActionDispatcher] can invoke an [Action] on the primary focus, or
+/// without regard for focus.
+///
+/// See also:
+///
+///  * [Shortcuts], which is a widget that contains a key map, in which it looks
+///    up key combinations in order to invoke actions.
+///  * [Actions], which is a widget that defines a map of [Intent] to [Action]
+///    and allows redefining of actions for its descendants.
+///  * [ActionDispatcher], a class that takes an [Action] and invokes it, passing
+///    a given [Intent].
+abstract class Action<T extends Intent> with Diagnosticable {
+  final ObserverList<ActionListenerCallback> _listeners = ObserverList<ActionListenerCallback>();
+
+  /// Gets the type of intent this action responds to.
+  Type get intentType => T;
+
+  /// Returns true if the action is enabled and is ready to be invoked.
+  ///
+  /// This will be called by the [ActionDispatcher] before attempting to invoke
+  /// the action.
+  ///
+  /// If the enabled state changes, overriding subclasses must call
+  /// [notifyActionListeners] to notify any listeners of the change.
+  bool isEnabled(covariant T intent) => true;
+
+  /// Indicates whether this action should treat key events mapped to this
+  /// action as being "handled" when it is invoked via the key event.
+  ///
+  /// If the key is handled, then no other key event handlers in the focus chain
+  /// will receive the event.
+  ///
+  /// If the key event is not handled, it will be passed back to the engine, and
+  /// continue to be processed there, allowing text fields and non-Flutter
+  /// widgets to receive the key event.
+  ///
+  /// The default implementation returns true.
+  bool consumesKey(covariant T intent) => true;
+
+  /// Called when the action is to be performed.
+  ///
+  /// This is called by the [ActionDispatcher] when an action is invoked via
+  /// [Actions.invoke], or when an action is invoked using
+  /// [ActionDispatcher.invokeAction] directly.
+  ///
+  /// This method is only meant to be invoked by an [ActionDispatcher], or by
+  /// its subclasses, and only when [isEnabled] is true.
+  ///
+  /// When overriding this method, the returned value can be any Object, but
+  /// changing the return type of the override to match the type of the returned
+  /// value provides more type safety.
+  ///
+  /// For instance, if your override of `invoke` returns an `int`, then define
+  /// it like so:
+  ///
+  /// ```dart
+  /// class IncrementIntent extends Intent {
+  ///   const IncrementIntent({this.index});
+  ///
+  ///   final int index;
+  /// }
+  ///
+  /// class MyIncrementAction extends Action<IncrementIntent> {
+  ///   @override
+  ///   int invoke(IncrementIntent intent) {
+  ///     return intent.index + 1;
+  ///   }
+  /// }
+  /// ```
+  @protected
+  Object? invoke(covariant T intent);
+
+  /// Register a callback to listen for changes to the state of this action.
+  ///
+  /// If you call this, you must call [removeActionListener] a matching number
+  /// of times, or memory leaks will occur. To help manage this and avoid memory
+  /// leaks, use of the [ActionListener] widget to register and unregister your
+  /// listener appropriately is highly recommended.
+  ///
+  /// {@template flutter.widgets.Action.addActionListener}
+  /// If a listener had been added twice, and is removed once during an
+  /// iteration (i.e. in response to a notification), it will still be called
+  /// again. If, on the other hand, it is removed as many times as it was
+  /// registered, then it will no longer be called. This odd behavior is the
+  /// result of the [Action] not being able to determine which listener
+  /// is being removed, since they are identical, and therefore conservatively
+  /// still calling all the listeners when it knows that any are still
+  /// registered.
+  ///
+  /// This surprising behavior can be unexpectedly observed when registering a
+  /// listener on two separate objects which are both forwarding all
+  /// registrations to a common upstream object.
+  /// {@endtemplate}
+  @mustCallSuper
+  void addActionListener(ActionListenerCallback listener) => _listeners.add(listener);
+
+  /// Remove a previously registered closure from the list of closures that are
+  /// notified when the object changes.
+  ///
+  /// If the given listener is not registered, the call is ignored.
+  ///
+  /// If you call [addActionListener], you must call this method a matching
+  /// number of times, or memory leaks will occur. To help manage this and avoid
+  /// memory leaks, use of the [ActionListener] widget to register and
+  /// unregister your listener appropriately is highly recommended.
+  ///
+  /// {@macro flutter.widgets.Action.addActionListener}
+  @mustCallSuper
+  void removeActionListener(ActionListenerCallback listener) => _listeners.remove(listener);
+
+  /// Call all the registered listeners.
+  ///
+  /// Subclasses should call this method whenever the object changes, to notify
+  /// any clients the object may have changed. Listeners that are added during this
+  /// iteration will not be visited. Listeners that are removed during this
+  /// iteration will not be visited after they are removed.
+  ///
+  /// Exceptions thrown by listeners will be caught and reported using
+  /// [FlutterError.reportError].
+  ///
+  /// Surprising behavior can result when reentrantly removing a listener (i.e.
+  /// in response to a notification) that has been registered multiple times.
+  /// See the discussion at [removeActionListener].
+  @protected
+  @visibleForTesting
+  void notifyActionListeners() {
+    if (_listeners.isEmpty) {
+      return;
+    }
+
+    // Make a local copy so that a listener can unregister while the list is
+    // being iterated over.
+    final List<ActionListenerCallback> localListeners = List<ActionListenerCallback>.from(_listeners);
+    for (final ActionListenerCallback listener in localListeners) {
+      InformationCollector? collector;
+      assert(() {
+        collector = () sync* {
+          yield DiagnosticsProperty<Action<T>>(
+            'The $runtimeType sending notification was',
+            this,
+            style: DiagnosticsTreeStyle.errorProperty,
+          );
+        };
+        return true;
+      }());
+      try {
+        if (_listeners.contains(listener)) {
+          listener(this);
+        }
+      } catch (exception, stack) {
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          library: 'widgets library',
+          context: ErrorDescription('while dispatching notifications for $runtimeType'),
+          informationCollector: collector,
+        ));
+      }
+    }
+  }
+}
+
+/// A helper widget for making sure that listeners on an action are removed properly.
+///
+/// Listeners on the [Action] class must have their listener callbacks removed
+/// with [Action.removeActionListener] when the listener is disposed of. This widget
+/// helps with that, by providing a lifetime for the connection between the
+/// [listener] and the [Action], and by handling the adding and removing of
+/// the [listener] at the right points in the widget lifecycle.
+///
+/// If you listen to an [Action] widget in a widget hierarchy, you should use
+/// this widget. If you are using an [Action] outside of a widget context, then
+/// you must call removeListener yourself.
+@immutable
+class ActionListener extends StatefulWidget {
+  /// Create a const [ActionListener].
+  ///
+  /// The [listener], [action], and [child] arguments must not be null.
+  const ActionListener({
+    Key? key,
+    required this.listener,
+    required this.action,
+    required this.child,
+  })  : assert(listener != null),
+        assert(action != null),
+        assert(child != null),
+        super(key: key);
+
+  /// The [ActionListenerCallback] callback to register with the [action].
+  ///
+  /// Must not be null.
+  final ActionListenerCallback listener;
+
+  /// The [Action] that the callback will be registered with.
+  ///
+  /// Must not be null.
+  final Action<Intent> action;
+
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  _ActionListenerState createState() => _ActionListenerState();
+}
+
+class _ActionListenerState extends State<ActionListener> {
+  @override
+  void initState() {
+    super.initState();
+    widget.action.addActionListener(widget.listener);
+  }
+
+  @override
+  void didUpdateWidget(ActionListener oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.action == widget.action && oldWidget.listener == widget.listener) {
+      return;
+    }
+    oldWidget.action.removeActionListener(oldWidget.listener);
+    widget.action.addActionListener(widget.listener);
+  }
+
+  @override
+  void dispose() {
+    widget.action.removeActionListener(widget.listener);
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) => widget.child;
+}
+
+/// An abstract [Action] subclass that adds an optional [BuildContext] to the
+/// [invoke] method to be able to provide context to actions.
+///
+/// [ActionDispatcher.invokeAction] checks to see if the action it is invoking
+/// is a [ContextAction], and if it is, supplies it with a context.
+abstract class ContextAction<T extends Intent> extends Action<T> {
+  /// Called when the action is to be performed.
+  ///
+  /// This is called by the [ActionDispatcher] when an action is invoked via
+  /// [Actions.invoke], or when an action is invoked using
+  /// [ActionDispatcher.invokeAction] directly.
+  ///
+  /// This method is only meant to be invoked by an [ActionDispatcher], or by
+  /// its subclasses, and only when [isEnabled] is true.
+  ///
+  /// The optional `context` parameter is the context of the invocation of the
+  /// action, and in the case of an action invoked by a [ShortcutManager], via
+  /// a [Shortcuts] widget, will be the context of the [Shortcuts] widget.
+  ///
+  /// When overriding this method, the returned value can be any Object, but
+  /// changing the return type of the override to match the type of the returned
+  /// value provides more type safety.
+  ///
+  /// For instance, if your override of `invoke` returns an `int`, then define
+  /// it like so:
+  ///
+  /// ```dart
+  /// class IncrementIntent extends Intent {
+  ///   const IncrementIntent({this.index});
+  ///
+  ///   final int index;
+  /// }
+  ///
+  /// class MyIncrementAction extends ContextAction<IncrementIntent> {
+  ///   @override
+  ///   int invoke(IncrementIntent intent, [BuildContext context]) {
+  ///     return intent.index + 1;
+  ///   }
+  /// }
+  /// ```
+  @protected
+  @override
+  Object? invoke(covariant T intent, [BuildContext? context]);
+}
+
+/// The signature of a callback accepted by [CallbackAction].
+typedef OnInvokeCallback<T extends Intent> = Object? Function(T intent);
+
+/// An [Action] that takes a callback in order to configure it without having to
+/// create an explicit [Action] subclass just to call a callback.
+///
+/// See also:
+///
+///  * [Shortcuts], which is a widget that contains a key map, in which it looks
+///    up key combinations in order to invoke actions.
+///  * [Actions], which is a widget that defines a map of [Intent] to [Action]
+///    and allows redefining of actions for its descendants.
+///  * [ActionDispatcher], a class that takes an [Action] and invokes it using a
+///    [FocusNode] for context.
+class CallbackAction<T extends Intent> extends Action<T> {
+  /// A constructor for a [CallbackAction].
+  ///
+  /// The `intentKey` and [onInvoke] parameters must not be null.
+  /// The [onInvoke] parameter is required.
+  CallbackAction({required this.onInvoke}) : assert(onInvoke != null);
+
+  /// The callback to be called when invoked.
+  ///
+  /// Must not be null.
+  @protected
+  final OnInvokeCallback<T> onInvoke;
+
+  @override
+  Object? invoke(covariant T intent) => onInvoke(intent);
+}
+
+/// An action dispatcher that simply invokes the actions given to it.
+///
+/// See also:
+///
+///  - [ShortcutManager], that uses this class to invoke actions.
+///  - [Shortcuts] widget, which defines key mappings to [Intent]s.
+///  - [Actions] widget, which defines a mapping between a in [Intent] type and
+///    an [Action].
+class ActionDispatcher with Diagnosticable {
+  /// Const constructor so that subclasses can be immutable.
+  const ActionDispatcher();
+
+  /// Invokes the given `action`, passing it the given `intent`.
+  ///
+  /// The action will be invoked with the given `context`, if given, but only if
+  /// the action is a [ContextAction] subclass. If no `context` is given, and
+  /// the action is a [ContextAction], then the context from the [primaryFocus]
+  /// is used.
+  ///
+  /// Returns the object returned from [Action.invoke].
+  ///
+  /// The caller must receive a `true` result from [Action.isEnabled] before
+  /// calling this function. This function will assert if the action is not
+  /// enabled when called.
+  Object? invokeAction(
+    covariant Action<Intent> action,
+    covariant Intent intent, [
+    BuildContext? context,
+  ]) {
+    assert(action != null);
+    assert(intent != null);
+    assert(action.isEnabled(intent), 'Action must be enabled when calling invokeAction');
+    if (action is ContextAction) {
+      context ??= primaryFocus?.context;
+      return action.invoke(intent, context);
+    } else {
+      return action.invoke(intent);
+    }
+  }
+}
+
+/// A widget that establishes an [ActionDispatcher] and a map of [Intent] to
+/// [Action] to be used by its descendants when invoking an [Action].
+///
+/// Actions are typically invoked using [Actions.invoke] with the context
+/// containing the ambient [Actions] widget.
+///
+/// See also:
+///
+///  * [ActionDispatcher], the object that this widget uses to manage actions.
+///  * [Action], a class for containing and defining an invocation of a user
+///    action.
+///  * [Intent], a class that holds a unique [LocalKey] identifying an action,
+///    as well as configuration information for running the [Action].
+///  * [Shortcuts], a widget used to bind key combinations to [Intent]s.
+class Actions extends StatefulWidget {
+  /// Creates an [Actions] widget.
+  ///
+  /// The [child], [actions], and [dispatcher] arguments must not be null.
+  const Actions({
+    Key? key,
+    this.dispatcher,
+    required this.actions,
+    required this.child,
+  })  : assert(actions != null),
+        assert(child != null),
+        super(key: key);
+
+  /// The [ActionDispatcher] object that invokes actions.
+  ///
+  /// This is what is returned from [Actions.of], and used by [Actions.invoke].
+  ///
+  /// If this [dispatcher] is null, then [Actions.of] and [Actions.invoke] will
+  /// look up the tree until they find an Actions widget that has a dispatcher
+  /// set. If not such widget is found, then they will return/use a
+  /// default-constructed [ActionDispatcher].
+  final ActionDispatcher? dispatcher;
+
+  /// {@template flutter.widgets.actions.actions}
+  /// A map of [Intent] keys to [Action<Intent>] objects that defines which
+  /// actions this widget knows about.
+  ///
+  /// For performance reasons, it is recommended that a pre-built map is
+  /// passed in here (e.g. a final variable from your widget class) instead of
+  /// defining it inline in the build function.
+  /// {@endtemplate}
+  final Map<Type, Action<Intent>> actions;
+
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  // Visits the Actions widget ancestors of the given element using
+  // getElementForInheritedWidgetOfExactType. Returns true if the visitor found
+  // what it was looking for.
+  static bool _visitActionsAncestors(BuildContext context, bool visitor(InheritedElement element)) {
+    InheritedElement? actionsElement = context.getElementForInheritedWidgetOfExactType<_ActionsMarker>();
+    while (actionsElement != null) {
+      if (visitor(actionsElement) == true) {
+        break;
+      }
+      // _getParent is needed here because
+      // context.getElementForInheritedWidgetOfExactType will return itself if it
+      // happens to be of the correct type.
+      final BuildContext parent = _getParent(actionsElement);
+      actionsElement = parent.getElementForInheritedWidgetOfExactType<_ActionsMarker>();
+    }
+    return actionsElement != null;
+  }
+
+  // Finds the nearest valid ActionDispatcher, or creates a new one if it
+  // doesn't find one.
+  static ActionDispatcher _findDispatcher(BuildContext context) {
+    ActionDispatcher? dispatcher;
+    _visitActionsAncestors(context, (InheritedElement element) {
+      final ActionDispatcher? found = (element.widget as _ActionsMarker).dispatcher;
+      if (found != null) {
+        dispatcher = found;
+        return true;
+      }
+      return false;
+    });
+    return dispatcher ?? const ActionDispatcher();
+  }
+
+  /// Returns a [VoidCallback] handler that invokes the bound action for the
+  /// given `intent` if the action is enabled, and returns null if the action is
+  /// not enabled, or no matching action is found.
+  ///
+  /// This is intended to be used in widgets which have something similar to an
+  /// `onTap` handler, which takes a `VoidCallback`, and can be set to the
+  /// result of calling this function.
+  ///
+  /// Creates a dependency on the [Actions] widget that maps the bound action so
+  /// that if the actions change, the context will be rebuilt and find the
+  /// updated action.
+  static VoidCallback? handler<T extends Intent>(BuildContext context, T intent) {
+    final Action<T>? action = Actions.maybeFind<T>(context);
+    if (action != null && action.isEnabled(intent)) {
+      return () {
+        // Could be that the action was enabled when the closure was created,
+        // but is now no longer enabled, so check again.
+        if (action.isEnabled(intent)) {
+          Actions.of(context).invokeAction(action, intent, context);
+        }
+      };
+    }
+    return null;
+  }
+
+  /// Finds the [Action] bound to the given intent type `T` in the given `context`.
+  ///
+  /// Creates a dependency on the [Actions] widget that maps the bound action so
+  /// that if the actions change, the context will be rebuilt and find the
+  /// updated action.
+  ///
+  /// The optional `intent` argument supplies the type of the intent to look for
+  /// if the concrete type of the intent sought isn't available. If not
+  /// supplied, then `T` is used.
+  ///
+  /// If no [Actions] widget surrounds the given context, this function will
+  /// assert in debug mode, and throw an exception in release mode.
+  ///
+  /// See also:
+  ///
+  ///  * [maybeFind], which is similar to this function, but will return null if
+  ///    no [Actions] ancestor is found.
+  static Action<T> find<T extends Intent>(BuildContext context, { T? intent }) {
+    final Action<T>? action = maybeFind(context, intent: intent);
+
+    assert(() {
+      if (action == null) {
+        final Type type = intent?.runtimeType ?? T;
+        throw FlutterError('Unable to find an action for a $type in an $Actions widget '
+            'in the given context.\n'
+            "$Actions.find() was called on a context that doesn't contain an "
+            '$Actions widget with a mapping for the given intent type.\n'
+            'The context used was:\n'
+            '  $context\n'
+            'The intent type requested was:\n'
+            '  $type');
+      }
+      return true;
+    }());
+    return action!;
+  }
+
+  /// Finds the [Action] bound to the given intent type `T` in the given `context`.
+  ///
+  /// Creates a dependency on the [Actions] widget that maps the bound action so
+  /// that if the actions change, the context will be rebuilt and find the
+  /// updated action.
+  ///
+  /// The optional `intent` argument supplies the type of the intent to look for
+  /// if the concrete type of the intent sought isn't available. If not
+  /// supplied, then `T` is used.
+  ///
+  /// If no [Actions] widget surrounds the given context, this function will
+  /// return null.
+  ///
+  /// See also:
+  ///
+  ///  * [find], which is similar to this function, but will throw if
+  ///    no [Actions] ancestor is found.
+  static Action<T>? maybeFind<T extends Intent>(BuildContext context, { T? intent }) {
+    Action<T>? action;
+
+    // Specialize the type if a runtime example instance of the intent is given.
+    // This allows this function to be called by code that doesn't know the
+    // concrete type of the intent at compile time.
+    final Type type = intent?.runtimeType ?? T;
+    assert(type != Intent,
+      'The type passed to "find" resolved to "Intent": either a non-Intent'
+      'generic type argument or an example intent derived from Intent must be'
+      'specified. Intent may be used as the generic type as long as the optional'
+      '"intent" argument is passed.');
+
+    _visitActionsAncestors(context, (InheritedElement element) {
+      final _ActionsMarker actions = element.widget as _ActionsMarker;
+      final Action<T>? result = actions.actions[type] as Action<T>?;
+      if (result != null) {
+        context.dependOnInheritedElement(element);
+        action = result;
+        return true;
+      }
+      return false;
+    });
+
+    return action;
+  }
+
+  /// Returns the [ActionDispatcher] associated with the [Actions] widget that
+  /// most tightly encloses the given [BuildContext].
+  ///
+  /// Will return a newly created [ActionDispatcher] if no ambient [Actions]
+  /// widget is found.
+  static ActionDispatcher of(BuildContext context) {
+    assert(context != null);
+    final _ActionsMarker? marker = context.dependOnInheritedWidgetOfExactType<_ActionsMarker>();
+    return marker?.dispatcher ?? _findDispatcher(context);
+  }
+
+  /// Invokes the action associated with the given [Intent] using the
+  /// [Actions] widget that most tightly encloses the given [BuildContext].
+  ///
+  /// The `context`, `intent` and `nullOk` arguments must not be null.
+  ///
+  /// If the given `intent` doesn't map to an action, or doesn't map to one that
+  /// returns true for [Action.isEnabled] in an [Actions.actions] map it finds,
+  /// then it will look to the next ancestor [Actions] widget in the hierarchy
+  /// until it reaches the root.
+  ///
+  /// In debug mode, if `nullOk` is false, this method will throw an exception
+  /// if no ambient [Actions] widget is found, or if the given `intent` doesn't
+  /// map to an action in any of the [Actions.actions] maps that are found. In
+  /// release mode, this method will return null if no matching enabled action
+  /// is found, regardless of the setting of `nullOk`.
+  ///
+  /// Setting `nullOk` to true indicates that if no ambient [Actions] widget is
+  /// found, then in debug mode, this method should return null instead of
+  /// throwing an exception.
+  ///
+  /// This method returns the result of invoking the action's [Action.invoke]
+  /// method. If no action mapping was found for the specified intent (and
+  /// `nullOk` is true), or if the actions that were found were disabled, or the
+  /// action itself returns null from [Action.invoke], then this method returns
+  /// null.
+  static Object? invoke<T extends Intent>(
+    BuildContext context,
+    T intent, {
+    bool nullOk = false,
+  }) {
+    assert(intent != null);
+    assert(nullOk != null);
+    assert(context != null);
+    Action<T>? action;
+    InheritedElement? actionElement;
+
+    _visitActionsAncestors(context, (InheritedElement element) {
+      final _ActionsMarker actions = element.widget as _ActionsMarker;
+      final Action<T>? result = actions.actions[intent.runtimeType] as Action<T>?;
+      if (result != null) {
+        actionElement = element;
+        if (result.isEnabled(intent)) {
+          action = result;
+          return true;
+        }
+      }
+      return false;
+    });
+
+    assert(() {
+      if (!nullOk && actionElement == null) {
+        throw FlutterError('Unable to find an action for an Intent with type '
+            '${intent.runtimeType} in an $Actions widget in the given context.\n'
+            '$Actions.invoke() was unable to find an $Actions widget that '
+            "contained a mapping for the given intent, or the intent type isn't the "
+            'same as the type argument to invoke (which is $T - try supplying a '
+            'type argument to invoke if one was not given)\n'
+            'The context used was:\n'
+            '  $context\n'
+            'The intent type requested was:\n'
+            '  ${intent.runtimeType}');
+      }
+      return true;
+    }());
+    if (actionElement == null || action == null) {
+      return null;
+    }
+    // Invoke the action we found using the relevant dispatcher from the Actions
+    // Element we found.
+    return _findDispatcher(actionElement!).invokeAction(action!, intent, context);
+  }
+
+  @override
+  State<Actions> createState() => _ActionsState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ActionDispatcher>('dispatcher', dispatcher));
+    properties.add(DiagnosticsProperty<Map<Type, Action<Intent>>>('actions', actions));
+  }
+}
+
+class _ActionsState extends State<Actions> {
+  // The set of actions that this Actions widget is current listening to.
+  Set<Action<Intent>>? listenedActions = <Action<Intent>>{};
+  // Used to tell the marker to rebuild its dependencies when the state of an
+  // action in the map changes.
+  Object rebuildKey = Object();
+
+  @override
+  void initState() {
+    super.initState();
+    _updateActionListeners();
+  }
+
+  void _handleActionChanged(Action<Intent> action) {
+    // Generate a new key so that the marker notifies dependents.
+    setState(() {
+      rebuildKey = Object();
+    });
+  }
+
+  void _updateActionListeners() {
+    final Set<Action<Intent>> widgetActions = widget.actions.values.toSet();
+    final Set<Action<Intent>> removedActions = listenedActions!.difference(widgetActions);
+    final Set<Action<Intent>> addedActions = widgetActions.difference(listenedActions!);
+
+    for (final Action<Intent> action in removedActions) {
+      action.removeActionListener(_handleActionChanged);
+    }
+    for (final Action<Intent> action in addedActions) {
+      action.addActionListener(_handleActionChanged);
+    }
+    listenedActions = widgetActions;
+  }
+
+  @override
+  void didUpdateWidget(Actions oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    _updateActionListeners();
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    for (final Action<Intent> action in listenedActions!) {
+      action.removeActionListener(_handleActionChanged);
+    }
+    listenedActions = null;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _ActionsMarker(
+      actions: widget.actions,
+      dispatcher: widget.dispatcher,
+      rebuildKey: rebuildKey,
+      child: widget.child,
+    );
+  }
+}
+
+// An inherited widget used by Actions widget for fast lookup of the Actions
+// widget information.
+class _ActionsMarker extends InheritedWidget {
+  const _ActionsMarker({
+    required this.dispatcher,
+    required this.actions,
+    required this.rebuildKey,
+    Key? key,
+    required Widget child,
+  })  : assert(child != null),
+        assert(actions != null),
+        super(key: key, child: child);
+
+  final ActionDispatcher? dispatcher;
+  final Map<Type, Action<Intent>> actions;
+  final Object rebuildKey;
+
+  @override
+  bool updateShouldNotify(_ActionsMarker oldWidget) {
+    return rebuildKey != oldWidget.rebuildKey
+        || oldWidget.dispatcher != dispatcher
+        || !mapEquals<Type, Action<Intent>>(oldWidget.actions, actions);
+  }
+}
+
+/// A widget that combines the functionality of [Actions], [Shortcuts],
+/// [MouseRegion] and a [Focus] widget to create a detector that defines actions
+/// and key bindings, and provides callbacks for handling focus and hover
+/// highlights.
+///
+/// This widget can be used to give a control the required detection modes for
+/// focus and hover handling. It is most often used when authoring a new control
+/// widget, and the new control should be enabled for keyboard traversal and
+/// activation.
+///
+/// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+/// This example shows how keyboard interaction can be added to a custom control
+/// that changes color when hovered and focused, and can toggle a light when
+/// activated, either by touch or by hitting the `X` key on the keyboard when
+/// the "And Me" button has the keyboard focus (be sure to use TAB to move the
+/// focus to the "And Me" button before trying it out).
+///
+/// This example defines its own key binding for the `X` key, but in this case,
+/// there is also a default key binding for [ActivateAction] in the default key
+/// bindings created by [WidgetsApp] (the parent for [MaterialApp], and
+/// [CupertinoApp]), so the `ENTER` key will also activate the buttons.
+///
+/// ```dart imports
+/// import 'package:flute/services.dart';
+/// ```
+///
+/// ```dart preamble
+/// class FadButton extends StatefulWidget {
+///   const FadButton({Key key, this.onPressed, this.child}) : super(key: key);
+///
+///   final VoidCallback onPressed;
+///   final Widget child;
+///
+///   @override
+///   _FadButtonState createState() => _FadButtonState();
+/// }
+///
+/// class _FadButtonState extends State<FadButton> {
+///   bool _focused = false;
+///   bool _hovering = false;
+///   bool _on = false;
+///   Map<Type, Action<Intent>> _actionMap;
+///   Map<LogicalKeySet, Intent> _shortcutMap;
+///
+///   @override
+///   void initState() {
+///     super.initState();
+///     _actionMap = <Type, Action<Intent>>{
+///       ActivateIntent: CallbackAction(
+///         onInvoke: (Intent intent) => _toggleState(),
+///       ),
+///     };
+///     _shortcutMap = <LogicalKeySet, Intent>{
+///       LogicalKeySet(LogicalKeyboardKey.keyX): const ActivateIntent(),
+///     };
+///   }
+///
+///   Color get color {
+///     Color baseColor = Colors.lightBlue;
+///     if (_focused) {
+///       baseColor = Color.alphaBlend(Colors.black.withOpacity(0.25), baseColor);
+///     }
+///     if (_hovering) {
+///       baseColor = Color.alphaBlend(Colors.black.withOpacity(0.1), baseColor);
+///     }
+///     return baseColor;
+///   }
+///
+///   void _toggleState() {
+///     setState(() {
+///       _on = !_on;
+///     });
+///   }
+///
+///   void _handleFocusHighlight(bool value) {
+///     setState(() {
+///       _focused = value;
+///     });
+///   }
+///
+///   void _handleHoveHighlight(bool value) {
+///     setState(() {
+///       _hovering = value;
+///     });
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return GestureDetector(
+///       onTap: _toggleState,
+///       child: FocusableActionDetector(
+///         actions: _actionMap,
+///         shortcuts: _shortcutMap,
+///         onShowFocusHighlight: _handleFocusHighlight,
+///         onShowHoverHighlight: _handleHoveHighlight,
+///         child: Row(
+///           children: <Widget>[
+///             Container(
+///               padding: EdgeInsets.all(10.0),
+///               color: color,
+///               child: widget.child,
+///             ),
+///             Container(
+///               width: 30,
+///               height: 30,
+///               margin: EdgeInsets.all(10.0),
+///               color: _on ? Colors.red : Colors.transparent,
+///             ),
+///           ],
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: AppBar(
+///       title: Text('FocusableActionDetector Example'),
+///     ),
+///     body: Center(
+///       child: Row(
+///         mainAxisAlignment: MainAxisAlignment.center,
+///         children: <Widget>[
+///           Padding(
+///             padding: const EdgeInsets.all(8.0),
+///             child: TextButton(onPressed: () {}, child: Text('Press Me')),
+///           ),
+///           Padding(
+///             padding: const EdgeInsets.all(8.0),
+///             child: FadButton(onPressed: () {}, child: Text('And Me')),
+///           ),
+///         ],
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// This widget doesn't have any visual representation, it is just a detector that
+/// provides focus and hover capabilities.
+///
+/// It hosts its own [FocusNode] or uses [focusNode], if given.
+class FocusableActionDetector extends StatefulWidget {
+  /// Create a const [FocusableActionDetector].
+  ///
+  /// The [enabled], [autofocus], [mouseCursor], and [child] arguments must not be null.
+  const FocusableActionDetector({
+    Key? key,
+    this.enabled = true,
+    this.focusNode,
+    this.autofocus = false,
+    this.shortcuts,
+    this.actions,
+    this.onShowFocusHighlight,
+    this.onShowHoverHighlight,
+    this.onFocusChange,
+    this.mouseCursor = MouseCursor.defer,
+    required this.child,
+  })  : assert(enabled != null),
+        assert(autofocus != null),
+        assert(mouseCursor != null),
+        assert(child != null),
+        super(key: key);
+
+  /// Is this widget enabled or not.
+  ///
+  /// If disabled, will not send any notifications needed to update highlight or
+  /// focus state, and will not define or respond to any actions or shortcuts.
+  ///
+  /// When disabled, adds [Focus] to the widget tree, but sets
+  /// [Focus.canRequestFocus] to false.
+  final bool enabled;
+
+  /// {@macro flutter.widgets.Focus.focusNode}
+  final FocusNode? focusNode;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  /// {@macro flutter.widgets.actions.actions}
+  final Map<Type, Action<Intent>>? actions;
+
+  /// {@macro flutter.widgets.shortcuts.shortcuts}
+  final Map<LogicalKeySet, Intent>? shortcuts;
+
+  /// A function that will be called when the focus highlight should be shown or
+  /// hidden.
+  ///
+  /// This method is not triggered at the unmount of the widget.
+  final ValueChanged<bool>? onShowFocusHighlight;
+
+  /// A function that will be called when the hover highlight should be shown or hidden.
+  ///
+  /// This method is not triggered at the unmount of the widget.
+  final ValueChanged<bool>? onShowHoverHighlight;
+
+  /// A function that will be called when the focus changes.
+  ///
+  /// Called with true if the [focusNode] has primary focus.
+  final ValueChanged<bool>? onFocusChange;
+
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// The [mouseCursor] defaults to [MouseCursor.defer], deferring the choice of
+  /// cursor to the next region behind it in hit-test order.
+  final MouseCursor mouseCursor;
+
+  /// The child widget for this [FocusableActionDetector] widget.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  _FocusableActionDetectorState createState() => _FocusableActionDetectorState();
+}
+
+class _FocusableActionDetectorState extends State<FocusableActionDetector> {
+  @override
+  void initState() {
+    super.initState();
+    SchedulerBinding.instance!.addPostFrameCallback((Duration duration) {
+      _updateHighlightMode(FocusManager.instance.highlightMode);
+    });
+    FocusManager.instance.addHighlightModeListener(_handleFocusHighlightModeChange);
+  }
+
+  @override
+  void dispose() {
+    FocusManager.instance.removeHighlightModeListener(_handleFocusHighlightModeChange);
+    super.dispose();
+  }
+
+  bool _canShowHighlight = false;
+  void _updateHighlightMode(FocusHighlightMode mode) {
+    _mayTriggerCallback(task: () {
+      switch (FocusManager.instance.highlightMode) {
+        case FocusHighlightMode.touch:
+          _canShowHighlight = false;
+          break;
+        case FocusHighlightMode.traditional:
+          _canShowHighlight = true;
+          break;
+      }
+    });
+  }
+
+  // Have to have this separate from the _updateHighlightMode because it gets
+  // called in initState, where things aren't mounted yet.
+  // Since this method is a highlight mode listener, it is only called
+  // immediately following pointer events.
+  void _handleFocusHighlightModeChange(FocusHighlightMode mode) {
+    if (!mounted) {
+      return;
+    }
+    _updateHighlightMode(mode);
+  }
+
+  bool _hovering = false;
+  void _handleMouseEnter(PointerEnterEvent event) {
+    if (!_hovering) {
+      _mayTriggerCallback(task: () {
+        _hovering = true;
+      });
+    }
+  }
+
+  void _handleMouseExit(PointerExitEvent event) {
+    if (_hovering) {
+      _mayTriggerCallback(task: () {
+        _hovering = false;
+      });
+    }
+  }
+
+  bool _focused = false;
+  void _handleFocusChange(bool focused) {
+    if (_focused != focused) {
+      _mayTriggerCallback(task: () {
+        _focused = focused;
+      });
+      widget.onFocusChange?.call(_focused);
+    }
+  }
+
+  // Record old states, do `task` if not null, then compare old states with the
+  // new states, and trigger callbacks if necessary.
+  //
+  // The old states are collected from `oldWidget` if it is provided, or the
+  // current widget (before doing `task`) otherwise. The new states are always
+  // collected from the current widget.
+  void _mayTriggerCallback({VoidCallback? task, FocusableActionDetector? oldWidget}) {
+    bool shouldShowHoverHighlight(FocusableActionDetector target) {
+      return _hovering && target.enabled && _canShowHighlight;
+    }
+
+    bool canRequestFocus(FocusableActionDetector target) {
+      final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
+      switch (mode) {
+        case NavigationMode.traditional:
+          return target.enabled;
+        case NavigationMode.directional:
+          return true;
+      }
+    }
+
+    bool shouldShowFocusHighlight(FocusableActionDetector target) {
+      return _focused && _canShowHighlight && canRequestFocus(target);
+    }
+
+    assert(SchedulerBinding.instance!.schedulerPhase != SchedulerPhase.persistentCallbacks);
+    final FocusableActionDetector oldTarget = oldWidget ?? widget;
+    final bool didShowHoverHighlight = shouldShowHoverHighlight(oldTarget);
+    final bool didShowFocusHighlight = shouldShowFocusHighlight(oldTarget);
+    if (task != null) {
+      task();
+    }
+    final bool doShowHoverHighlight = shouldShowHoverHighlight(widget);
+    final bool doShowFocusHighlight = shouldShowFocusHighlight(widget);
+    if (didShowFocusHighlight != doShowFocusHighlight) {
+      widget.onShowFocusHighlight?.call(doShowFocusHighlight);
+    }
+    if (didShowHoverHighlight != doShowHoverHighlight) {
+      widget.onShowHoverHighlight?.call(doShowHoverHighlight);
+    }
+  }
+
+  @override
+  void didUpdateWidget(FocusableActionDetector oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.enabled != oldWidget.enabled) {
+      SchedulerBinding.instance!.addPostFrameCallback((Duration duration) {
+        _mayTriggerCallback(oldWidget: oldWidget);
+      });
+    }
+  }
+
+  bool get _canRequestFocus {
+    final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
+    switch (mode) {
+      case NavigationMode.traditional:
+        return widget.enabled;
+      case NavigationMode.directional:
+        return true;
+    }
+  }
+
+  // This global key is needed to keep only the necessary widgets in the tree
+  // while maintaining the subtree's state.
+  //
+  // See https://github.com/flutter/flutter/issues/64058 for an explanation of
+  // why using a global key over keeping the shape of the tree.
+  final GlobalKey _mouseRegionKey = GlobalKey();
+
+  @override
+  Widget build(BuildContext context) {
+    Widget child = MouseRegion(
+      key: _mouseRegionKey,
+      onEnter: _handleMouseEnter,
+      onExit: _handleMouseExit,
+      cursor: widget.mouseCursor,
+      child: Focus(
+        focusNode: widget.focusNode,
+        autofocus: widget.autofocus,
+        canRequestFocus: _canRequestFocus,
+        onFocusChange: _handleFocusChange,
+        child: widget.child,
+      ),
+    );
+    if (widget.enabled && widget.actions != null && widget.actions!.isNotEmpty) {
+      child = Actions(actions: widget.actions!, child: child);
+    }
+    if (widget.enabled && widget.shortcuts != null && widget.shortcuts!.isNotEmpty) {
+      child = Shortcuts(shortcuts: widget.shortcuts!, child: child);
+    }
+    return child;
+  }
+}
+
+/// An [Intent], that is bound to a [DoNothingAction].
+///
+/// Attaching a [DoNothingIntent] to a [Shortcuts] mapping is one way to disable
+/// a keyboard shortcut defined by a widget higher in the widget hierarchy and
+/// consume any key event that triggers it via a shortcut.
+///
+/// This intent cannot be subclassed.
+///
+/// See also:
+///
+///  * [DoNothingAndStopPropagationIntent], a similar intent that will not
+///    handle the key event, but will still keep it from being passed to other key
+///    handlers in the focus chain.
+class DoNothingIntent extends Intent {
+  /// Creates a const [DoNothingIntent].
+  factory DoNothingIntent() => const DoNothingIntent._();
+
+  // Make DoNothingIntent constructor private so it can't be subclassed.
+  const DoNothingIntent._();
+}
+
+/// An [Intent], that is bound to a [DoNothingAction], but, in addition to not
+/// performing an action, also stops the propagation of the key event bound to
+/// this intent to other key event handlers in the focus chain.
+///
+/// Attaching a [DoNothingAndStopPropagationIntent] to a [Shortcuts.shortcuts]
+/// mapping is one way to disable a keyboard shortcut defined by a widget higher
+/// in the widget hierarchy. In addition, the bound [DoNothingAction] will
+/// return false from [DoNothingAction.consumesKey], causing the key bound to
+/// this intent to be passed on to the platform embedding as "not handled" with
+/// out passing it to other key handlers in the focus chain (e.g. parent
+/// `Shortcuts` widgets higher up in the chain).
+///
+/// This intent cannot be subclassed.
+///
+/// See also:
+///
+///  * [DoNothingIntent], a similar intent that will handle the key event.
+class DoNothingAndStopPropagationIntent extends Intent {
+  /// Creates a const [DoNothingAndStopPropagationIntent].
+  factory DoNothingAndStopPropagationIntent() => const DoNothingAndStopPropagationIntent._();
+
+  // Make DoNothingAndStopPropagationIntent constructor private so it can't be subclassed.
+  const DoNothingAndStopPropagationIntent._();
+}
+
+/// An [Action], that doesn't perform any action when invoked.
+///
+/// Attaching a [DoNothingAction] to an [Actions.actions] mapping is a way to
+/// disable an action defined by a widget higher in the widget hierarchy.
+///
+/// If [consumesKey] returns false, then not only will this action do nothing,
+/// but it will stop the propagation of the key event used to trigger it to
+/// other widgets in the focus chain and tell the embedding that the key wasn't
+/// handled, allowing text input fields or other non-Flutter elements to receive
+/// that key event. The return value of [consumesKey] can be set via the
+/// `consumesKey` argument to the constructor.
+///
+/// This action can be bound to any [Intent].
+///
+/// See also:
+///  - [DoNothingIntent], which is an intent that can be bound to a [KeySet] in
+///    a [Shortcuts] widget to do nothing.
+///  - [DoNothingAndStopPropagationIntent], which is an intent that can be bound
+///    to a [KeySet] in a [Shortcuts] widget to do nothing and also stop key event
+///    propagation to other key handlers in the focus chain.
+class DoNothingAction extends Action<Intent> {
+  /// Creates a [DoNothingAction].
+  ///
+  /// The optional [consumesKey] argument defaults to true.
+  DoNothingAction({bool consumesKey = true}) : _consumesKey = consumesKey;
+
+  @override
+  bool consumesKey(Intent intent) => _consumesKey;
+  final bool _consumesKey;
+
+  @override
+  void invoke(Intent intent) {}
+}
+
+/// An [Intent] that activates the currently focused control.
+///
+/// This intent is bound by default to the [LogicalKeyboardKey.space] key on all
+/// platforms, and also to the [LogicalKeyboardKey.enter] key on all platforms
+/// except the web, where ENTER doesn't toggle selection. On the web, ENTER is
+/// bound to [ButtonActivateIntent] instead.
+///
+/// See also:
+///
+///  * [WidgetsApp.defaultShortcuts], which contains the default shortcuts used
+///    in apps.
+///  * [WidgetsApp.shortcuts], which defines the shortcuts to use in an
+///    application (and defaults to [WidgetsApp.defaultShortcuts]).
+class ActivateIntent extends Intent {
+  /// Creates a const [ActivateIntent] so subclasses can be const.
+  const ActivateIntent();
+}
+
+/// An [Intent] that activates the currently focused button.
+///
+/// This intent is bound by default to the [LogicalKeyboardKey.enter] key on the
+/// web, where ENTER can be used to activate buttons, but not toggle selection.
+/// All other platforms bind [LogicalKeyboardKey.enter] to [ActivateIntent].
+///
+/// See also:
+///
+///  * [WidgetsApp.defaultShortcuts], which contains the default shortcuts used
+///    in apps.
+///  * [WidgetsApp.shortcuts], which defines the shortcuts to use in an
+///    application (and defaults to [WidgetsApp.defaultShortcuts]).
+class ButtonActivateIntent extends Intent {
+  /// Creates a const [ButtonActivateIntent] so subclasses can be const.
+  const ButtonActivateIntent();
+}
+
+/// An action that activates the currently focused control.
+///
+/// This is an abstract class that serves as a base class for actions that
+/// activate a control. By default, is bound to [LogicalKeyboardKey.enter],
+/// [LogicalKeyboardKey.gameButtonA], and [LogicalKeyboardKey.space] in the
+/// default keyboard map in [WidgetsApp].
+abstract class ActivateAction extends Action<ActivateIntent> {}
+
+/// An intent that selects the currently focused control.
+class SelectIntent extends Intent {}
+
+/// An action that selects the currently focused control.
+///
+/// This is an abstract class that serves as a base class for actions that
+/// select something. It is not bound to any key by default.
+abstract class SelectAction extends Action<SelectIntent> {}
+
+/// An [Intent] that dismisses the currently focused widget.
+///
+/// The [WidgetsApp.defaultShortcuts] binds this intent to the
+/// [LogicalKeyboardKey.escape] and [LogicalKeyboardKey.gameButtonB] keys.
+///
+/// See also:
+///  - [ModalRoute] which listens for this intent to dismiss modal routes
+///    (dialogs, pop-up menus, drawers, etc).
+class DismissIntent extends Intent {
+  /// Creates a const [DismissIntent].
+  const DismissIntent();
+}
+
+/// An action that dismisses the focused widget.
+///
+/// This is an abstract class that serves as a base class for dismiss actions.
+abstract class DismissAction extends Action<DismissIntent> {}
diff --git a/lib/src/widgets/animated_cross_fade.dart b/lib/src/widgets/animated_cross_fade.dart
new file mode 100644
index 0000000..a488afb
--- /dev/null
+++ b/lib/src/widgets/animated_cross_fade.dart
@@ -0,0 +1,383 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+
+import 'animated_size.dart';
+import 'basic.dart';
+import 'framework.dart';
+import 'ticker_provider.dart';
+import 'transitions.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// bool _first;
+
+/// Specifies which of two children to show. See [AnimatedCrossFade].
+///
+/// The child that is shown will fade in, while the other will fade out.
+enum CrossFadeState {
+  /// Show the first child ([AnimatedCrossFade.firstChild]) and hide the second
+  /// ([AnimatedCrossFade.secondChild]]).
+  showFirst,
+
+  /// Show the second child ([AnimatedCrossFade.secondChild]) and hide the first
+  /// ([AnimatedCrossFade.firstChild]).
+  showSecond,
+}
+
+/// Signature for the [AnimatedCrossFade.layoutBuilder] callback.
+///
+/// The `topChild` is the child fading in, which is normally drawn on top. The
+/// `bottomChild` is the child fading out, normally drawn on the bottom.
+///
+/// For good performance, the returned widget tree should contain both the
+/// `topChild` and the `bottomChild`; the depth of the tree, and the types of
+/// the widgets in the tree, from the returned widget to each of the children
+/// should be the same; and where there is a widget with multiple children, the
+/// top child and the bottom child should be keyed using the provided
+/// `topChildKey` and `bottomChildKey` keys respectively.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// Widget defaultLayoutBuilder(Widget topChild, Key topChildKey, Widget bottomChild, Key bottomChildKey) {
+///   return Stack(
+///     fit: StackFit.loose,
+///     children: <Widget>[
+///       Positioned(
+///         key: bottomChildKey,
+///         left: 0.0,
+///         top: 0.0,
+///         right: 0.0,
+///         child: bottomChild,
+///       ),
+///       Positioned(
+///         key: topChildKey,
+///         child: topChild,
+///       )
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+typedef AnimatedCrossFadeBuilder = Widget Function(Widget topChild, Key topChildKey, Widget bottomChild, Key bottomChildKey);
+
+/// A widget that cross-fades between two given children and animates itself
+/// between their sizes.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=PGK2UUAyE54}
+///
+/// The animation is controlled through the [crossFadeState] parameter.
+/// [firstCurve] and [secondCurve] represent the opacity curves of the two
+/// children. The [firstCurve] is inverted, i.e. it fades out when providing a
+/// growing curve like [Curves.linear]. The [sizeCurve] is the curve used to
+/// animate between the size of the fading-out child and the size of the
+/// fading-in child.
+///
+/// This widget is intended to be used to fade a pair of widgets with the same
+/// width. In the case where the two children have different heights, the
+/// animation crops overflowing children during the animation by aligning their
+/// top edge, which means that the bottom will be clipped.
+///
+/// The animation is automatically triggered when an existing
+/// [AnimatedCrossFade] is rebuilt with a different value for the
+/// [crossFadeState] property.
+///
+/// {@tool snippet}
+///
+/// This code fades between two representations of the Flutter logo. It depends
+/// on a boolean field `_first`; when `_first` is true, the first logo is shown,
+/// otherwise the second logo is shown. When the field changes state, the
+/// [AnimatedCrossFade] widget cross-fades between the two forms of the logo
+/// over three seconds.
+///
+/// ```dart
+/// AnimatedCrossFade(
+///   duration: const Duration(seconds: 3),
+///   firstChild: const FlutterLogo(style: FlutterLogoStyle.horizontal, size: 100.0),
+///   secondChild: const FlutterLogo(style: FlutterLogoStyle.stacked, size: 100.0),
+///   crossFadeState: _first ? CrossFadeState.showFirst : CrossFadeState.showSecond,
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AnimatedOpacity], which fades between nothing and a single child.
+///  * [AnimatedSwitcher], which switches out a child for a new one with a
+///    customizable transition, supporting multiple cross-fades at once.
+///  * [AnimatedSize], the lower-level widget which [AnimatedCrossFade] uses to
+///    automatically change size.
+class AnimatedCrossFade extends StatefulWidget {
+  /// Creates a cross-fade animation widget.
+  ///
+  /// The [duration] of the animation is the same for all components (fade in,
+  /// fade out, and size), and you can pass [Interval]s instead of [Curve]s in
+  /// order to have finer control, e.g., creating an overlap between the fades.
+  ///
+  /// All the arguments other than [key] must be non-null.
+  const AnimatedCrossFade({
+    Key? key,
+    required this.firstChild,
+    required this.secondChild,
+    this.firstCurve = Curves.linear,
+    this.secondCurve = Curves.linear,
+    this.sizeCurve = Curves.linear,
+    this.alignment = Alignment.topCenter,
+    required this.crossFadeState,
+    required this.duration,
+    this.reverseDuration,
+    this.layoutBuilder = defaultLayoutBuilder,
+  }) : assert(firstChild != null),
+       assert(secondChild != null),
+       assert(firstCurve != null),
+       assert(secondCurve != null),
+       assert(sizeCurve != null),
+       assert(alignment != null),
+       assert(crossFadeState != null),
+       assert(duration != null),
+       assert(layoutBuilder != null),
+       super(key: key);
+
+  /// The child that is visible when [crossFadeState] is
+  /// [CrossFadeState.showFirst]. It fades out when transitioning
+  /// [crossFadeState] from [CrossFadeState.showFirst] to
+  /// [CrossFadeState.showSecond] and vice versa.
+  final Widget firstChild;
+
+  /// The child that is visible when [crossFadeState] is
+  /// [CrossFadeState.showSecond]. It fades in when transitioning
+  /// [crossFadeState] from [CrossFadeState.showFirst] to
+  /// [CrossFadeState.showSecond] and vice versa.
+  final Widget secondChild;
+
+  /// The child that will be shown when the animation has completed.
+  final CrossFadeState crossFadeState;
+
+  /// The duration of the whole orchestrated animation.
+  final Duration duration;
+
+  /// The duration of the whole orchestrated animation when running in reverse.
+  ///
+  /// If not supplied, this defaults to [duration].
+  final Duration? reverseDuration;
+
+  /// The fade curve of the first child.
+  ///
+  /// Defaults to [Curves.linear].
+  final Curve firstCurve;
+
+  /// The fade curve of the second child.
+  ///
+  /// Defaults to [Curves.linear].
+  final Curve secondCurve;
+
+  /// The curve of the animation between the two children's sizes.
+  ///
+  /// Defaults to [Curves.linear].
+  final Curve sizeCurve;
+
+  /// How the children should be aligned while the size is animating.
+  ///
+  /// Defaults to [Alignment.topCenter].
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], a class with convenient constants typically used to
+  ///    specify an [AlignmentGeometry].
+  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
+  ///    relative to text direction.
+  final AlignmentGeometry alignment;
+
+  /// A builder that positions the [firstChild] and [secondChild] widgets.
+  ///
+  /// The widget returned by this method is wrapped in an [AnimatedSize].
+  ///
+  /// By default, this uses [AnimatedCrossFade.defaultLayoutBuilder], which uses
+  /// a [Stack] and aligns the `bottomChild` to the top of the stack while
+  /// providing the `topChild` as the non-positioned child to fill the provided
+  /// constraints. This works well when the [AnimatedCrossFade] is in a position
+  /// to change size and when the children are not flexible. However, if the
+  /// children are less fussy about their sizes (for example a
+  /// [CircularProgressIndicator] inside a [Center]), or if the
+  /// [AnimatedCrossFade] is being forced to a particular size, then it can
+  /// result in the widgets jumping about when the cross-fade state is changed.
+  final AnimatedCrossFadeBuilder layoutBuilder;
+
+  /// The default layout algorithm used by [AnimatedCrossFade].
+  ///
+  /// The top child is placed in a stack that sizes itself to match the top
+  /// child. The bottom child is positioned at the top of the same stack, sized
+  /// to fit its width but without forcing the height. The stack is then
+  /// clipped.
+  ///
+  /// This is the default value for [layoutBuilder]. It implements
+  /// [AnimatedCrossFadeBuilder].
+  static Widget defaultLayoutBuilder(Widget topChild, Key topChildKey, Widget bottomChild, Key bottomChildKey) {
+    return Stack(
+      clipBehavior: Clip.none,
+      children: <Widget>[
+        Positioned(
+          key: bottomChildKey,
+          left: 0.0,
+          top: 0.0,
+          right: 0.0,
+          child: bottomChild,
+        ),
+        Positioned(
+          key: topChildKey,
+          child: topChild,
+        ),
+      ],
+    );
+  }
+
+  @override
+  _AnimatedCrossFadeState createState() => _AnimatedCrossFadeState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<CrossFadeState>('crossFadeState', crossFadeState));
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: Alignment.topCenter));
+    properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms'));
+    properties.add(IntProperty('reverseDuration', reverseDuration?.inMilliseconds, unit: 'ms', defaultValue: null));
+  }
+}
+
+class _AnimatedCrossFadeState extends State<AnimatedCrossFade> with TickerProviderStateMixin {
+  AnimationController? _controller;
+  late Animation<double> _firstAnimation;
+  late Animation<double> _secondAnimation;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(
+      duration: widget.duration,
+      reverseDuration: widget.reverseDuration,
+      vsync: this,
+    );
+    if (widget.crossFadeState == CrossFadeState.showSecond)
+      _controller!.value = 1.0;
+    _firstAnimation = _initAnimation(widget.firstCurve, true);
+    _secondAnimation = _initAnimation(widget.secondCurve, false);
+    _controller!.addStatusListener((AnimationStatus status) {
+      setState(() {
+        // Trigger a rebuild because it depends on _isTransitioning, which
+        // changes its value together with animation status.
+      });
+    });
+  }
+
+  Animation<double> _initAnimation(Curve curve, bool inverted) {
+    Animation<double> result = _controller!.drive(CurveTween(curve: curve));
+    if (inverted)
+      result = result.drive(Tween<double>(begin: 1.0, end: 0.0));
+    return result;
+  }
+
+  @override
+  void dispose() {
+    _controller!.dispose();
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(AnimatedCrossFade oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.duration != oldWidget.duration)
+      _controller!.duration = widget.duration;
+    if (widget.reverseDuration != oldWidget.reverseDuration)
+      _controller!.reverseDuration = widget.reverseDuration;
+    if (widget.firstCurve != oldWidget.firstCurve)
+      _firstAnimation = _initAnimation(widget.firstCurve, true);
+    if (widget.secondCurve != oldWidget.secondCurve)
+      _secondAnimation = _initAnimation(widget.secondCurve, false);
+    if (widget.crossFadeState != oldWidget.crossFadeState) {
+      switch (widget.crossFadeState) {
+        case CrossFadeState.showFirst:
+          _controller!.reverse();
+          break;
+        case CrossFadeState.showSecond:
+          _controller!.forward();
+          break;
+      }
+    }
+  }
+
+  /// Whether we're in the middle of cross-fading this frame.
+  bool get _isTransitioning => _controller!.status == AnimationStatus.forward || _controller!.status == AnimationStatus.reverse;
+
+  @override
+  Widget build(BuildContext context) {
+    const Key kFirstChildKey = ValueKey<CrossFadeState>(CrossFadeState.showFirst);
+    const Key kSecondChildKey = ValueKey<CrossFadeState>(CrossFadeState.showSecond);
+    final bool transitioningForwards = _controller!.status == AnimationStatus.completed ||
+                                       _controller!.status == AnimationStatus.forward;
+    final Key topKey;
+    Widget topChild;
+    final Animation<double> topAnimation;
+    final Key bottomKey;
+    Widget bottomChild;
+    final Animation<double> bottomAnimation;
+    if (transitioningForwards) {
+      topKey = kSecondChildKey;
+      topChild = widget.secondChild;
+      topAnimation = _secondAnimation;
+      bottomKey = kFirstChildKey;
+      bottomChild = widget.firstChild;
+      bottomAnimation = _firstAnimation;
+    } else {
+      topKey = kFirstChildKey;
+      topChild = widget.firstChild;
+      topAnimation = _firstAnimation;
+      bottomKey = kSecondChildKey;
+      bottomChild = widget.secondChild;
+      bottomAnimation = _secondAnimation;
+    }
+
+    bottomChild = TickerMode(
+      key: bottomKey,
+      enabled: _isTransitioning,
+      child: ExcludeSemantics(
+        excluding: true, // Always exclude the semantics of the widget that's fading out.
+        child: FadeTransition(
+          opacity: bottomAnimation,
+          child: bottomChild,
+        ),
+      ),
+    );
+    topChild = TickerMode(
+      key: topKey,
+      enabled: true, // Top widget always has its animations enabled.
+      child: ExcludeSemantics(
+        excluding: false, // Always publish semantics for the widget that's fading in.
+        child: FadeTransition(
+          opacity: topAnimation,
+          child: topChild,
+        ),
+      ),
+    );
+    return ClipRect(
+      child: AnimatedSize(
+        alignment: widget.alignment,
+        duration: widget.duration,
+        reverseDuration: widget.reverseDuration,
+        curve: widget.sizeCurve,
+        vsync: this,
+        child: widget.layoutBuilder(topChild, topKey, bottomChild, bottomKey),
+      ),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(EnumProperty<CrossFadeState>('crossFadeState', widget.crossFadeState));
+    description.add(DiagnosticsProperty<AnimationController>('controller', _controller, showName: false));
+    description.add(DiagnosticsProperty<AlignmentGeometry>('alignment', widget.alignment, defaultValue: Alignment.topCenter));
+  }
+}
diff --git a/lib/src/widgets/animated_list.dart b/lib/src/widgets/animated_list.dart
new file mode 100644
index 0000000..b7c344d
--- /dev/null
+++ b/lib/src/widgets/animated_list.dart
@@ -0,0 +1,1050 @@
+// Copyright 2014 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.
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'scroll_controller.dart';
+import 'scroll_physics.dart';
+import 'scroll_view.dart';
+import 'sliver.dart';
+import 'ticker_provider.dart';
+
+/// Signature for the builder callback used by [AnimatedList].
+typedef AnimatedListItemBuilder = Widget Function(BuildContext context, int index, Animation<double> animation);
+
+/// Signature for the builder callback used by [AnimatedListState.removeItem].
+typedef AnimatedListRemovedItemBuilder = Widget Function(BuildContext context, Animation<double> animation);
+
+// The default insert/remove animation duration.
+const Duration _kDuration = Duration(milliseconds: 300);
+
+// Incoming and outgoing AnimatedList items.
+class _ActiveItem implements Comparable<_ActiveItem> {
+  _ActiveItem.incoming(this.controller, this.itemIndex) : removedItemBuilder = null;
+  _ActiveItem.outgoing(this.controller, this.itemIndex, this.removedItemBuilder);
+  _ActiveItem.index(this.itemIndex)
+    : controller = null,
+      removedItemBuilder = null;
+
+  final AnimationController? controller;
+  final AnimatedListRemovedItemBuilder? removedItemBuilder;
+  int itemIndex;
+
+  @override
+  int compareTo(_ActiveItem other) => itemIndex - other.itemIndex;
+}
+
+/// A scrolling container that animates items when they are inserted or removed.
+///
+/// This widget's [AnimatedListState] can be used to dynamically insert or
+/// remove items. To refer to the [AnimatedListState] either provide a
+/// [GlobalKey] or use the static [of] method from an item's input callback.
+///
+/// This widget is similar to one created by [ListView.builder].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=ZtfItHwFlZ8}
+///
+/// {@tool dartpad --template=freeform_no_null_safety}
+/// This sample application uses an [AnimatedList] to create an effect when
+/// items are removed or added to the list.
+///
+/// ```dart imports
+/// import 'package:flute/foundation.dart';
+/// import 'package:flute/material.dart';
+/// ```
+///
+/// ```dart
+/// class AnimatedListSample extends StatefulWidget {
+///   @override
+///   _AnimatedListSampleState createState() => _AnimatedListSampleState();
+/// }
+///
+/// class _AnimatedListSampleState extends State<AnimatedListSample> {
+///   final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
+///   ListModel<int> _list;
+///   int _selectedItem;
+///   int _nextItem; // The next item inserted when the user presses the '+' button.
+///
+///   @override
+///   void initState() {
+///     super.initState();
+///     _list = ListModel<int>(
+///       listKey: _listKey,
+///       initialItems: <int>[0, 1, 2],
+///       removedItemBuilder: _buildRemovedItem,
+///     );
+///     _nextItem = 3;
+///   }
+///
+///   // Used to build list items that haven't been removed.
+///   Widget _buildItem(BuildContext context, int index, Animation<double> animation) {
+///     return CardItem(
+///       animation: animation,
+///       item: _list[index],
+///       selected: _selectedItem == _list[index],
+///       onTap: () {
+///         setState(() {
+///           _selectedItem = _selectedItem == _list[index] ? null : _list[index];
+///         });
+///       },
+///     );
+///   }
+///
+///   // Used to build an item after it has been removed from the list. This
+///   // method is needed because a removed item remains visible until its
+///   // animation has completed (even though it's gone as far this ListModel is
+///   // concerned). The widget will be used by the
+///   // [AnimatedListState.removeItem] method's
+///   // [AnimatedListRemovedItemBuilder] parameter.
+///   Widget _buildRemovedItem(int item, BuildContext context, Animation<double> animation) {
+///     return CardItem(
+///       animation: animation,
+///       item: item,
+///       selected: false,
+///       // No gesture detector here: we don't want removed items to be interactive.
+///     );
+///   }
+///
+///   // Insert the "next item" into the list model.
+///   void _insert() {
+///     final int index = _selectedItem == null ? _list.length : _list.indexOf(_selectedItem);
+///     _list.insert(index, _nextItem++);
+///   }
+///
+///   // Remove the selected item from the list model.
+///   void _remove() {
+///     if (_selectedItem != null) {
+///       _list.removeAt(_list.indexOf(_selectedItem));
+///       setState(() {
+///         _selectedItem = null;
+///       });
+///     }
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return MaterialApp(
+///       home: Scaffold(
+///         appBar: AppBar(
+///           title: const Text('AnimatedList'),
+///           actions: <Widget>[
+///             IconButton(
+///               icon: const Icon(Icons.add_circle),
+///               onPressed: _insert,
+///               tooltip: 'insert a new item',
+///             ),
+///             IconButton(
+///               icon: const Icon(Icons.remove_circle),
+///               onPressed: _remove,
+///               tooltip: 'remove the selected item',
+///             ),
+///           ],
+///         ),
+///         body: Padding(
+///           padding: const EdgeInsets.all(16.0),
+///           child: AnimatedList(
+///             key: _listKey,
+///             initialItemCount: _list.length,
+///             itemBuilder: _buildItem,
+///           ),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+///
+/// /// Keeps a Dart [List] in sync with an [AnimatedList].
+/// ///
+/// /// The [insert] and [removeAt] methods apply to both the internal list and
+/// /// the animated list that belongs to [listKey].
+/// ///
+/// /// This class only exposes as much of the Dart List API as is needed by the
+/// /// sample app. More list methods are easily added, however methods that
+/// /// mutate the list must make the same changes to the animated list in terms
+/// /// of [AnimatedListState.insertItem] and [AnimatedList.removeItem].
+/// class ListModel<E> {
+///   ListModel({
+///     @required this.listKey,
+///     @required this.removedItemBuilder,
+///     Iterable<E> initialItems,
+///   }) : assert(listKey != null),
+///       assert(removedItemBuilder != null),
+///       _items = List<E>.from(initialItems ?? <E>[]);
+///
+///   final GlobalKey<AnimatedListState> listKey;
+///   final dynamic removedItemBuilder;
+///   final List<E> _items;
+///
+///   AnimatedListState get _animatedList => listKey.currentState;
+///
+///   void insert(int index, E item) {
+///     _items.insert(index, item);
+///     _animatedList.insertItem(index);
+///   }
+///
+///   E removeAt(int index) {
+///     final E removedItem = _items.removeAt(index);
+///     if (removedItem != null) {
+///       _animatedList.removeItem(
+///         index,
+///           (BuildContext context, Animation<double> animation) => removedItemBuilder(removedItem, context, animation),
+///       );
+///     }
+///     return removedItem;
+///   }
+///
+///   int get length => _items.length;
+///
+///   E operator [](int index) => _items[index];
+///
+///   int indexOf(E item) => _items.indexOf(item);
+/// }
+///
+/// /// Displays its integer item as 'item N' on a Card whose color is based on
+/// /// the item's value.
+/// ///
+/// /// The text is displayed in bright green if [selected] is
+/// /// true. This widget's height is based on the [animation] parameter, it
+/// /// varies from 0 to 128 as the animation varies from 0.0 to 1.0.
+/// class CardItem extends StatelessWidget {
+///   const CardItem({
+///     Key key,
+///     @required this.animation,
+///     this.onTap,
+///     @required this.item,
+///     this.selected: false
+///   }) : assert(animation != null),
+///        assert(item != null && item >= 0),
+///        assert(selected != null),
+///        super(key: key);
+///
+///   final Animation<double> animation;
+///   final VoidCallback onTap;
+///   final int item;
+///   final bool selected;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     TextStyle textStyle = Theme.of(context).textTheme.headline4;
+///     if (selected)
+///       textStyle = textStyle.copyWith(color: Colors.lightGreenAccent[400]);
+///     return Padding(
+///       padding: const EdgeInsets.all(2.0),
+///       child: SizeTransition(
+///         axis: Axis.vertical,
+///         sizeFactor: animation,
+///         child: GestureDetector(
+///           behavior: HitTestBehavior.opaque,
+///           onTap: onTap,
+///           child: SizedBox(
+///             height: 80.0,
+///             child: Card(
+///               color: Colors.primaries[item % Colors.primaries.length],
+///               child: Center(
+///                 child: Text('Item $item', style: textStyle),
+///               ),
+///             ),
+///           ),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+///
+/// void main() {
+///   runApp(AnimatedListSample());
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [SliverAnimatedList], a sliver that animates items when they are inserted
+///    or removed from a list.
+class AnimatedList extends StatefulWidget {
+  /// Creates a scrolling container that animates items when they are inserted
+  /// or removed.
+  const AnimatedList({
+    Key? key,
+    required this.itemBuilder,
+    this.initialItemCount = 0,
+    this.scrollDirection = Axis.vertical,
+    this.reverse = false,
+    this.controller,
+    this.primary,
+    this.physics,
+    this.shrinkWrap = false,
+    this.padding,
+  }) : assert(itemBuilder != null),
+       assert(initialItemCount != null && initialItemCount >= 0),
+       super(key: key);
+
+  /// Called, as needed, to build list item widgets.
+  ///
+  /// List items are only built when they're scrolled into view.
+  ///
+  /// The [AnimatedListItemBuilder] index parameter indicates the item's
+  /// position in the list. The value of the index parameter will be between 0
+  /// and [initialItemCount] plus the total number of items that have been
+  /// inserted with [AnimatedListState.insertItem] and less the total number of
+  /// items that have been removed with [AnimatedListState.removeItem].
+  ///
+  /// Implementations of this callback should assume that
+  /// [AnimatedListState.removeItem] removes an item immediately.
+  final AnimatedListItemBuilder itemBuilder;
+
+  /// {@template flutter.widgets.animatedList.initialItemCount}
+  /// The number of items the list will start with.
+  ///
+  /// The appearance of the initial items is not animated. They
+  /// are created, as needed, by [itemBuilder] with an animation parameter
+  /// of [kAlwaysCompleteAnimation].
+  /// {@endtemplate}
+  final int initialItemCount;
+
+  /// The axis along which the scroll view scrolls.
+  ///
+  /// Defaults to [Axis.vertical].
+  final Axis scrollDirection;
+
+  /// Whether the scroll view scrolls in the reading direction.
+  ///
+  /// For example, if the reading direction is left-to-right and
+  /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from
+  /// left to right when [reverse] is false and from right to left when
+  /// [reverse] is true.
+  ///
+  /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
+  /// scrolls from top to bottom when [reverse] is false and from bottom to top
+  /// when [reverse] is true.
+  ///
+  /// Defaults to false.
+  final bool reverse;
+
+  /// An object that can be used to control the position to which this scroll
+  /// view is scrolled.
+  ///
+  /// Must be null if [primary] is true.
+  ///
+  /// A [ScrollController] serves several purposes. It can be used to control
+  /// the initial scroll position (see [ScrollController.initialScrollOffset]).
+  /// It can be used to control whether the scroll view should automatically
+  /// save and restore its scroll position in the [PageStorage] (see
+  /// [ScrollController.keepScrollOffset]). It can be used to read the current
+  /// scroll position (see [ScrollController.offset]), or change it (see
+  /// [ScrollController.animateTo]).
+  final ScrollController? controller;
+
+  /// Whether this is the primary scroll view associated with the parent
+  /// [PrimaryScrollController].
+  ///
+  /// On iOS, this identifies the scroll view that will scroll to top in
+  /// response to a tap in the status bar.
+  ///
+  /// Defaults to true when [scrollDirection] is [Axis.vertical] and
+  /// [controller] is null.
+  final bool? primary;
+
+  /// How the scroll view should respond to user input.
+  ///
+  /// For example, determines how the scroll view continues to animate after the
+  /// user stops dragging the scroll view.
+  ///
+  /// Defaults to matching platform conventions.
+  final ScrollPhysics? physics;
+
+  /// Whether the extent of the scroll view in the [scrollDirection] should be
+  /// determined by the contents being viewed.
+  ///
+  /// If the scroll view does not shrink wrap, then the scroll view will expand
+  /// to the maximum allowed size in the [scrollDirection]. If the scroll view
+  /// has unbounded constraints in the [scrollDirection], then [shrinkWrap] must
+  /// be true.
+  ///
+  /// Shrink wrapping the content of the scroll view is significantly more
+  /// expensive than expanding to the maximum allowed size because the content
+  /// can expand and contract during scrolling, which means the size of the
+  /// scroll view needs to be recomputed whenever the scroll position changes.
+  ///
+  /// Defaults to false.
+  final bool shrinkWrap;
+
+  /// The amount of space by which to inset the children.
+  final EdgeInsetsGeometry? padding;
+
+  /// The state from the closest instance of this class that encloses the given
+  /// context.
+  ///
+  /// This method is typically used by [AnimatedList] item widgets that insert
+  /// or remove items in response to user input.
+  ///
+  /// If no [AnimatedList] surrounds the context given, then this function will
+  /// assert in debug mode and throw an exception in release mode.
+  ///
+  /// See also:
+  ///
+  ///  * [maybeOf], a similar function that will return null if no
+  ///    [AnimatedList] ancestor is found.
+  static AnimatedListState of(BuildContext context) {
+    assert(context != null);
+    final AnimatedListState? result = context.findAncestorStateOfType<AnimatedListState>();
+    assert((){
+      if (result == null) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary(
+            'AnimatedList.of() called with a context that does not contain an AnimatedList.'),
+          ErrorDescription(
+            'No AnimatedList ancestor could be found starting from the context that was passed to AnimatedList.of().'),
+          ErrorHint(
+            'This can happen when the context provided is from the same StatefulWidget that '
+            'built the AnimatedList. Please see the AnimatedList documentation for examples '
+            'of how to refer to an AnimatedListState object:'
+            '  https://api.flutter.dev/flutter/widgets/AnimatedListState-class.html'
+          ),
+          context.describeElement('The context used was')
+        ]);
+      }
+      return true;
+    }());
+    return result!;
+  }
+
+  /// The state from the closest instance of this class that encloses the given
+  /// context.
+  ///
+  /// This method is typically used by [AnimatedList] item widgets that insert
+  /// or remove items in response to user input.
+  ///
+  /// If no [AnimatedList] surrounds the context given, then this function will
+  /// return null.
+  ///
+  /// See also:
+  ///
+  ///  * [of], a similar function that will throw if no [AnimatedList] ancestor
+  ///    is found.
+  static AnimatedListState? maybeOf(BuildContext context) {
+    assert(context != null);
+    return context.findAncestorStateOfType<AnimatedListState>();
+  }
+
+  @override
+  AnimatedListState createState() => AnimatedListState();
+}
+
+/// The state for a scrolling container that animates items when they are
+/// inserted or removed.
+///
+/// When an item is inserted with [insertItem] an animation begins running. The
+/// animation is passed to [AnimatedList.itemBuilder] whenever the item's widget
+/// is needed.
+///
+/// When an item is removed with [removeItem] its animation is reversed.
+/// The removed item's animation is passed to the [removeItem] builder
+/// parameter.
+///
+/// An app that needs to insert or remove items in response to an event
+/// can refer to the [AnimatedList]'s state with a global key:
+///
+/// ```dart
+/// GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
+/// ...
+/// AnimatedList(key: listKey, ...);
+/// ...
+/// listKey.currentState.insert(123);
+/// ```
+///
+/// [AnimatedList] item input handlers can also refer to their [AnimatedListState]
+/// with the static [AnimatedList.of] method.
+class AnimatedListState extends State<AnimatedList> with TickerProviderStateMixin<AnimatedList> {
+  final GlobalKey<SliverAnimatedListState> _sliverAnimatedListKey = GlobalKey();
+
+  /// Insert an item at [index] and start an animation that will be passed
+  /// to [AnimatedList.itemBuilder] when the item is visible.
+  ///
+  /// This method's semantics are the same as Dart's [List.insert] method:
+  /// it increases the length of the list by one and shifts all items at or
+  /// after [index] towards the end of the list.
+  void insertItem(int index, { Duration duration = _kDuration }) {
+    _sliverAnimatedListKey.currentState!.insertItem(index, duration: duration);
+  }
+
+  /// Remove the item at [index] and start an animation that will be passed
+  /// to [builder] when the item is visible.
+  ///
+  /// Items are removed immediately. After an item has been removed, its index
+  /// will no longer be passed to the [AnimatedList.itemBuilder]. However the
+  /// item will still appear in the list for [duration] and during that time
+  /// [builder] must construct its widget as needed.
+  ///
+  /// This method's semantics are the same as Dart's [List.remove] method:
+  /// it decreases the length of the list by one and shifts all items at or
+  /// before [index] towards the beginning of the list.
+  void removeItem(int index, AnimatedListRemovedItemBuilder builder, { Duration duration = _kDuration }) {
+    _sliverAnimatedListKey.currentState!.removeItem(index, builder, duration: duration);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return CustomScrollView(
+      scrollDirection: widget.scrollDirection,
+      reverse: widget.reverse,
+      controller: widget.controller,
+      primary: widget.primary,
+      physics: widget.physics,
+      shrinkWrap: widget.shrinkWrap,
+      slivers: <Widget>[
+        SliverPadding(
+          padding: widget.padding ?? const EdgeInsets.all(0),
+          sliver: SliverAnimatedList(
+            key: _sliverAnimatedListKey,
+            itemBuilder: widget.itemBuilder,
+            initialItemCount: widget.initialItemCount,
+          ),
+        ),
+      ],
+    );
+  }
+}
+
+/// A sliver that animates items when they are inserted or removed.
+///
+/// This widget's [SliverAnimatedListState] can be used to dynamically insert or
+/// remove items. To refer to the [SliverAnimatedListState] either provide a
+/// [GlobalKey] or use the static [SliverAnimatedList.of] method from an item's
+/// input callback.
+///
+/// {@tool dartpad --template=freeform_no_null_safety}
+/// This sample application uses a [SliverAnimatedList] to create an animated
+/// effect when items are removed or added to the list.
+///
+/// ```dart imports
+/// import 'package:flute/foundation.dart';
+/// import 'package:flute/material.dart';
+/// ```
+///
+/// ```dart
+/// void main() => runApp(SliverAnimatedListSample());
+///
+/// class SliverAnimatedListSample extends StatefulWidget {
+///   @override
+///   _SliverAnimatedListSampleState createState() => _SliverAnimatedListSampleState();
+/// }
+///
+/// class _SliverAnimatedListSampleState extends State<SliverAnimatedListSample> {
+///   final GlobalKey<SliverAnimatedListState> _listKey = GlobalKey<SliverAnimatedListState>();
+///   final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
+///   final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
+///   ListModel<int> _list;
+///   int _selectedItem;
+///   int _nextItem; // The next item inserted when the user presses the '+' button.
+///
+///   @override
+///   void initState() {
+///     super.initState();
+///     _list = ListModel<int>(
+///       listKey: _listKey,
+///       initialItems: <int>[0, 1, 2],
+///       removedItemBuilder: _buildRemovedItem,
+///     );
+///     _nextItem = 3;
+///   }
+///
+///   // Used to build list items that haven't been removed.
+///   Widget _buildItem(BuildContext context, int index, Animation<double> animation) {
+///     return CardItem(
+///       animation: animation,
+///       item: _list[index],
+///       selected: _selectedItem == _list[index],
+///       onTap: () {
+///         setState(() {
+///           _selectedItem = _selectedItem == _list[index] ? null : _list[index];
+///         });
+///       },
+///     );
+///   }
+///
+///   // Used to build an item after it has been removed from the list. This
+///   // method is needed because a removed item remains visible until its
+///   // animation has completed (even though it's gone as far this ListModel is
+///   // concerned). The widget will be used by the
+///   // [AnimatedListState.removeItem] method's
+///   // [AnimatedListRemovedItemBuilder] parameter.
+///   Widget _buildRemovedItem(int item, BuildContext context, Animation<double> animation) {
+///     return CardItem(
+///       animation: animation,
+///       item: item,
+///       selected: false,
+///     );
+///   }
+///
+///   // Insert the "next item" into the list model.
+///   void _insert() {
+///     final int index = _selectedItem == null ? _list.length : _list.indexOf(_selectedItem);
+///     _list.insert(index, _nextItem++);
+///   }
+///
+///   // Remove the selected item from the list model.
+///   void _remove() {
+///     if (_selectedItem != null) {
+///       _list.removeAt(_list.indexOf(_selectedItem));
+///       setState(() {
+///         _selectedItem = null;
+///       });
+///     } else {
+///       _scaffoldMessengerKey.currentState.showSnackBar(SnackBar(
+///         content: Text(
+///           'Select an item to remove from the list.',
+///           style: TextStyle(fontSize: 20),
+///         ),
+///       ));
+///     }
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return MaterialApp(
+///       scaffoldMessengerKey: _scaffoldMessengerKey,
+///       home: Scaffold(
+///         key: _scaffoldKey,
+///         body: CustomScrollView(
+///           slivers: <Widget>[
+///             SliverAppBar(
+///               title: Text(
+///                 'SliverAnimatedList',
+///                 style: TextStyle(fontSize: 30),
+///               ),
+///               expandedHeight: 60,
+///               centerTitle: true,
+///               backgroundColor: Colors.amber[900],
+///               leading: IconButton(
+///                 icon: const Icon(Icons.add_circle),
+///                 onPressed: _insert,
+///                 tooltip: 'Insert a new item.',
+///                 iconSize: 32,
+///               ),
+///               actions: [
+///                 IconButton(
+///                   icon: const Icon(Icons.remove_circle),
+///                   onPressed: _remove,
+///                   tooltip: 'Remove the selected item.',
+///                   iconSize: 32,
+///                 ),
+///               ],
+///             ),
+///             SliverAnimatedList(
+///               key: _listKey,
+///               initialItemCount: _list.length,
+///               itemBuilder: _buildItem,
+///             ),
+///           ],
+///         ),
+///       ),
+///     );
+///   }
+/// }
+///
+/// // Keeps a Dart [List] in sync with an [AnimatedList].
+/// //
+/// // The [insert] and [removeAt] methods apply to both the internal list and
+/// // the animated list that belongs to [listKey].
+/// //
+/// // This class only exposes as much of the Dart List API as is needed by the
+/// // sample app. More list methods are easily added, however methods that
+/// // mutate the list must make the same changes to the animated list in terms
+/// // of [AnimatedListState.insertItem] and [AnimatedList.removeItem].
+/// class ListModel<E> {
+///   ListModel({
+///     @required this.listKey,
+///     @required this.removedItemBuilder,
+///     Iterable<E> initialItems,
+///   }) : assert(listKey != null),
+///        assert(removedItemBuilder != null),
+///        _items = List<E>.from(initialItems ?? <E>[]);
+///
+///   final GlobalKey<SliverAnimatedListState> listKey;
+///   final dynamic removedItemBuilder;
+///   final List<E> _items;
+///
+///   SliverAnimatedListState get _animatedList => listKey.currentState;
+///
+///   void insert(int index, E item) {
+///     _items.insert(index, item);
+///     _animatedList.insertItem(index);
+///   }
+///
+///   E removeAt(int index) {
+///     final E removedItem = _items.removeAt(index);
+///     if (removedItem != null) {
+///       _animatedList.removeItem(
+///         index,
+///         (BuildContext context, Animation<double> animation) => removedItemBuilder(removedItem, context, animation),
+///       );
+///     }
+///     return removedItem;
+///   }
+///
+///   int get length => _items.length;
+///
+///   E operator [](int index) => _items[index];
+///
+///   int indexOf(E item) => _items.indexOf(item);
+/// }
+///
+/// // Displays its integer item as 'Item N' on a Card whose color is based on
+/// // the item's value.
+/// //
+/// // The card turns gray when [selected] is true. This widget's height
+/// // is based on the [animation] parameter. It varies as the animation value
+/// // transitions from 0.0 to 1.0.
+/// class CardItem extends StatelessWidget {
+///   const CardItem({
+///     Key key,
+///     @required this.animation,
+///     @required this.item,
+///     this.onTap,
+///     this.selected = false,
+///   }) : assert(animation != null),
+///        assert(item != null && item >= 0),
+///        assert(selected != null),
+///        super(key: key);
+///
+///   final Animation<double> animation;
+///   final VoidCallback onTap;
+///   final int item;
+///   final bool selected;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Padding(
+///       padding:
+///       const EdgeInsets.only(
+///         left: 2.0,
+///         right: 2.0,
+///         top: 2.0,
+///         bottom: 0.0,
+///       ),
+///       child: SizeTransition(
+///         axis: Axis.vertical,
+///         sizeFactor: animation,
+///         child: GestureDetector(
+///           onTap: onTap,
+///           child: SizedBox(
+///             height: 80.0,
+///             child: Card(
+///               color: selected
+///                 ? Colors.black12
+///                 : Colors.primaries[item % Colors.primaries.length],
+///               child: Center(
+///                 child: Text(
+///                   'Item $item',
+///                   style: Theme.of(context).textTheme.headline4,
+///                 ),
+///               ),
+///             ),
+///           ),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [SliverList], which does not animate items when they are inserted or
+///    removed.
+///  * [AnimatedList], a non-sliver scrolling container that animates items when
+///    they are inserted or removed.
+class SliverAnimatedList extends StatefulWidget {
+  /// Creates a sliver that animates items when they are inserted or removed.
+  const SliverAnimatedList({
+    Key? key,
+    required this.itemBuilder,
+    this.initialItemCount = 0,
+  }) : assert(itemBuilder != null),
+       assert(initialItemCount != null && initialItemCount >= 0),
+       super(key: key);
+
+  /// Called, as needed, to build list item widgets.
+  ///
+  /// List items are only built when they're scrolled into view.
+  ///
+  /// The [AnimatedListItemBuilder] index parameter indicates the item's
+  /// position in the list. The value of the index parameter will be between 0
+  /// and [initialItemCount] plus the total number of items that have been
+  /// inserted with [SliverAnimatedListState.insertItem] and less the total
+  /// number of items that have been removed with
+  /// [SliverAnimatedListState.removeItem].
+  ///
+  /// Implementations of this callback should assume that
+  /// [SliverAnimatedListState.removeItem] removes an item immediately.
+  final AnimatedListItemBuilder itemBuilder;
+
+  /// {@macro flutter.widgets.animatedList.initialItemCount}
+  final int initialItemCount;
+
+  @override
+  SliverAnimatedListState createState() => SliverAnimatedListState();
+
+  /// The state from the closest instance of this class that encloses the given
+  /// context.
+  ///
+  /// This method is typically used by [SliverAnimatedList] item widgets that
+  /// insert or remove items in response to user input.
+  ///
+  /// If no [SliverAnimatedList] surrounds the context given, then this function
+  /// will assert in debug mode and throw an exception in release mode.
+  ///
+  /// See also:
+  ///
+  ///  * [maybeOf], a similar function that will return null if no
+  ///    [SliverAnimatedList] ancestor is found.
+  static SliverAnimatedListState of(BuildContext context) {
+    assert(context != null);
+    final SliverAnimatedListState? result = context.findAncestorStateOfType<SliverAnimatedListState>();
+    assert((){
+      if (result == null) {
+        throw FlutterError(
+          'SliverAnimatedList.of() called with a context that does not contain a SliverAnimatedList.\n'
+          'No SliverAnimatedListState ancestor could be found starting from the '
+          'context that was passed to SliverAnimatedListState.of(). This can '
+          'happen when the context provided is from the same StatefulWidget that '
+          'built the AnimatedList. Please see the SliverAnimatedList documentation '
+          'for examples of how to refer to an AnimatedListState object: '
+          'https://docs.flutter.io/flutter/widgets/SliverAnimatedListState-class.html\n'
+          'The context used was:\n'
+          '  $context');
+      }
+      return true;
+    }());
+    return result!;
+  }
+
+  /// The state from the closest instance of this class that encloses the given
+  /// context.
+  ///
+  /// This method is typically used by [SliverAnimatedList] item widgets that
+  /// insert or remove items in response to user input.
+  ///
+  /// If no [SliverAnimatedList] surrounds the context given, then this function
+  /// will return null.
+  ///
+  /// See also:
+  ///
+  ///  * [of], a similar function that will throw if no [SliverAnimatedList]
+  ///    ancestor is found.
+  static SliverAnimatedListState? maybeOf(BuildContext context) {
+    assert(context != null);
+    return context.findAncestorStateOfType<SliverAnimatedListState>();
+  }
+}
+
+/// The state for a sliver that animates items when they are
+/// inserted or removed.
+///
+/// When an item is inserted with [insertItem] an animation begins running. The
+/// animation is passed to [SliverAnimatedList.itemBuilder] whenever the item's
+/// widget is needed.
+///
+/// When an item is removed with [removeItem] its animation is reversed.
+/// The removed item's animation is passed to the [removeItem] builder
+/// parameter.
+///
+/// An app that needs to insert or remove items in response to an event
+/// can refer to the [SliverAnimatedList]'s state with a global key:
+///
+/// ```dart
+/// GlobalKey<SliverAnimatedListState> listKey = GlobalKey<SliverAnimatedListState>();
+/// ...
+/// SliverAnimatedList(key: listKey, ...);
+/// ...
+/// listKey.currentState.insert(123);
+/// ```
+///
+/// [SliverAnimatedList] item input handlers can also refer to their
+/// [SliverAnimatedListState] with the static [SliverAnimatedList.of] method.
+class SliverAnimatedListState extends State<SliverAnimatedList> with TickerProviderStateMixin {
+
+  final List<_ActiveItem> _incomingItems = <_ActiveItem>[];
+  final List<_ActiveItem> _outgoingItems = <_ActiveItem>[];
+  int _itemsCount = 0;
+
+  @override
+  void initState() {
+    super.initState();
+    _itemsCount = widget.initialItemCount;
+  }
+
+  @override
+  void dispose() {
+    for (final _ActiveItem item in _incomingItems.followedBy(_outgoingItems)) {
+      item.controller!.dispose();
+    }
+    super.dispose();
+  }
+
+  _ActiveItem? _removeActiveItemAt(List<_ActiveItem> items, int itemIndex) {
+    final int i = binarySearch(items, _ActiveItem.index(itemIndex));
+    return i == -1 ? null : items.removeAt(i);
+  }
+
+  _ActiveItem? _activeItemAt(List<_ActiveItem> items, int itemIndex) {
+    final int i = binarySearch(items, _ActiveItem.index(itemIndex));
+    return i == -1 ? null : items[i];
+  }
+
+  // The insertItem() and removeItem() index parameters are defined as if the
+  // removeItem() operation removed the corresponding list entry immediately.
+  // The entry is only actually removed from the ListView when the remove animation
+  // finishes. The entry is added to _outgoingItems when removeItem is called
+  // and removed from _outgoingItems when the remove animation finishes.
+
+  int _indexToItemIndex(int index) {
+    int itemIndex = index;
+    for (final _ActiveItem item in _outgoingItems) {
+      if (item.itemIndex <= itemIndex)
+        itemIndex += 1;
+      else
+        break;
+    }
+    return itemIndex;
+  }
+
+  int _itemIndexToIndex(int itemIndex) {
+    int index = itemIndex;
+    for (final _ActiveItem item in _outgoingItems) {
+      assert(item.itemIndex != itemIndex);
+      if (item.itemIndex < itemIndex)
+        index -= 1;
+      else
+        break;
+    }
+    return index;
+  }
+
+  SliverChildDelegate _createDelegate() {
+    return SliverChildBuilderDelegate(_itemBuilder, childCount: _itemsCount);
+  }
+
+  /// Insert an item at [index] and start an animation that will be passed to
+  /// [SliverAnimatedList.itemBuilder] when the item is visible.
+  ///
+  /// This method's semantics are the same as Dart's [List.insert] method:
+  /// it increases the length of the list by one and shifts all items at or
+  /// after [index] towards the end of the list.
+  void insertItem(int index, { Duration duration = _kDuration }) {
+    assert(index != null && index >= 0);
+    assert(duration != null);
+
+    final int itemIndex = _indexToItemIndex(index);
+    assert(itemIndex >= 0 && itemIndex <= _itemsCount);
+
+    // Increment the incoming and outgoing item indices to account
+    // for the insertion.
+    for (final _ActiveItem item in _incomingItems) {
+      if (item.itemIndex >= itemIndex)
+        item.itemIndex += 1;
+    }
+    for (final _ActiveItem item in _outgoingItems) {
+      if (item.itemIndex >= itemIndex)
+        item.itemIndex += 1;
+    }
+
+    final AnimationController controller = AnimationController(
+      duration: duration,
+      vsync: this,
+    );
+    final _ActiveItem incomingItem = _ActiveItem.incoming(
+      controller,
+      itemIndex,
+    );
+    setState(() {
+      _incomingItems
+        ..add(incomingItem)
+        ..sort();
+      _itemsCount += 1;
+    });
+
+    controller.forward().then<void>((_) {
+      _removeActiveItemAt(_incomingItems, incomingItem.itemIndex)!.controller!.dispose();
+    });
+  }
+
+  /// Remove the item at [index] and start an animation that will be passed
+  /// to [builder] when the item is visible.
+  ///
+  /// Items are removed immediately. After an item has been removed, its index
+  /// will no longer be passed to the [SliverAnimatedList.itemBuilder]. However
+  /// the item will still appear in the list for [duration] and during that time
+  /// [builder] must construct its widget as needed.
+  ///
+  /// This method's semantics are the same as Dart's [List.remove] method:
+  /// it decreases the length of the list by one and shifts all items at or
+  /// before [index] towards the beginning of the list.
+  void removeItem(int index, AnimatedListRemovedItemBuilder builder, { Duration duration = _kDuration }) {
+    assert(index != null && index >= 0);
+    assert(builder != null);
+    assert(duration != null);
+
+    final int itemIndex = _indexToItemIndex(index);
+    assert(itemIndex >= 0 && itemIndex < _itemsCount);
+    assert(_activeItemAt(_outgoingItems, itemIndex) == null);
+
+    final _ActiveItem? incomingItem = _removeActiveItemAt(_incomingItems, itemIndex);
+    final AnimationController controller = incomingItem?.controller
+      ?? AnimationController(duration: duration, value: 1.0, vsync: this);
+    final _ActiveItem outgoingItem = _ActiveItem.outgoing(controller, itemIndex, builder);
+    setState(() {
+      _outgoingItems
+        ..add(outgoingItem)
+        ..sort();
+    });
+
+    controller.reverse().then<void>((void value) {
+      _removeActiveItemAt(_outgoingItems, outgoingItem.itemIndex)!.controller!.dispose();
+
+      // Decrement the incoming and outgoing item indices to account
+      // for the removal.
+      for (final _ActiveItem item in _incomingItems) {
+        if (item.itemIndex > outgoingItem.itemIndex)
+          item.itemIndex -= 1;
+      }
+      for (final _ActiveItem item in _outgoingItems) {
+        if (item.itemIndex > outgoingItem.itemIndex)
+          item.itemIndex -= 1;
+      }
+
+      setState(() => _itemsCount -= 1);
+    });
+  }
+
+  Widget _itemBuilder(BuildContext context, int itemIndex) {
+    final _ActiveItem? outgoingItem = _activeItemAt(_outgoingItems, itemIndex);
+    if (outgoingItem != null) {
+      return outgoingItem.removedItemBuilder!(
+        context,
+        outgoingItem.controller!.view,
+      );
+    }
+
+    final _ActiveItem? incomingItem = _activeItemAt(_incomingItems, itemIndex);
+    final Animation<double> animation = incomingItem?.controller?.view ?? kAlwaysCompleteAnimation;
+    return widget.itemBuilder(
+      context,
+      _itemIndexToIndex(itemIndex),
+      animation,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return SliverList(
+      delegate: _createDelegate(),
+    );
+  }
+}
diff --git a/lib/src/widgets/animated_size.dart b/lib/src/widgets/animated_size.dart
new file mode 100644
index 0000000..264e7dc
--- /dev/null
+++ b/lib/src/widgets/animated_size.dart
@@ -0,0 +1,143 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+
+/// Animated widget that automatically transitions its size over a given
+/// duration whenever the given child's size changes.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_freeform_state_no_null_safety}
+/// This example makes a [Container] react to being touched, causing the child
+/// of the [AnimatedSize] widget, here a [FlutterLogo], to animate.
+///
+/// ```dart
+/// class _MyStatefulWidgetState extends State<MyStatefulWidget> with SingleTickerProviderStateMixin {
+///   double _size = 50.0;
+///   bool _large = false;
+///
+///   void _updateSize() {
+///     setState(() {
+///       _size = _large ? 250.0 : 100.0;
+///       _large = !_large;
+///     });
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return GestureDetector(
+///       onTap: () => _updateSize(),
+///       child: Container(
+///         color: Colors.amberAccent,
+///         child: AnimatedSize(
+///           curve: Curves.easeIn,
+///           vsync: this,
+///           duration: Duration(seconds: 1),
+///           child: FlutterLogo(size: _size),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [SizeTransition], which changes its size based on an [Animation].
+class AnimatedSize extends SingleChildRenderObjectWidget {
+  /// Creates a widget that animates its size to match that of its child.
+  ///
+  /// The [curve] and [duration] arguments must not be null.
+  const AnimatedSize({
+    Key? key,
+    Widget? child,
+    this.alignment = Alignment.center,
+    this.curve = Curves.linear,
+    required this.duration,
+    this.reverseDuration,
+    required this.vsync,
+    this.clipBehavior = Clip.hardEdge,
+  }) : assert(clipBehavior != null),
+       super(key: key, child: child);
+
+  /// The alignment of the child within the parent when the parent is not yet
+  /// the same size as the child.
+  ///
+  /// The x and y values of the alignment control the horizontal and vertical
+  /// alignment, respectively. An x value of -1.0 means that the left edge of
+  /// the child is aligned with the left edge of the parent whereas an x value
+  /// of 1.0 means that the right edge of the child is aligned with the right
+  /// edge of the parent. Other values interpolate (and extrapolate) linearly.
+  /// For example, a value of 0.0 means that the center of the child is aligned
+  /// with the center of the parent.
+  ///
+  /// Defaults to [Alignment.center].
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], a class with convenient constants typically used to
+  ///    specify an [AlignmentGeometry].
+  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
+  ///    relative to text direction.
+  final AlignmentGeometry alignment;
+
+  /// The animation curve when transitioning this widget's size to match the
+  /// child's size.
+  final Curve curve;
+
+  /// The duration when transitioning this widget's size to match the child's
+  /// size.
+  final Duration duration;
+
+  /// The duration when transitioning this widget's size to match the child's
+  /// size when going in reverse.
+  ///
+  /// If not specified, defaults to [duration].
+  final Duration? reverseDuration;
+
+  /// The [TickerProvider] for this widget.
+  final TickerProvider vsync;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge], and must not be null.
+  final Clip clipBehavior;
+
+  @override
+  RenderAnimatedSize createRenderObject(BuildContext context) {
+    return RenderAnimatedSize(
+      alignment: alignment,
+      duration: duration,
+      reverseDuration: reverseDuration,
+      curve: curve,
+      vsync: vsync,
+      textDirection: Directionality.maybeOf(context),
+      clipBehavior: clipBehavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderAnimatedSize renderObject) {
+    renderObject
+      ..alignment = alignment
+      ..duration = duration
+      ..reverseDuration = reverseDuration
+      ..curve = curve
+      ..vsync = vsync
+      ..textDirection = Directionality.maybeOf(context)
+      ..clipBehavior = clipBehavior;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: Alignment.topCenter));
+    properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms'));
+    properties.add(IntProperty('reverseDuration', reverseDuration?.inMilliseconds, unit: 'ms', defaultValue: null));
+  }
+}
diff --git a/lib/src/widgets/animated_switcher.dart b/lib/src/widgets/animated_switcher.dart
new file mode 100644
index 0000000..97e3881
--- /dev/null
+++ b/lib/src/widgets/animated_switcher.dart
@@ -0,0 +1,433 @@
+// Copyright 2014 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.
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'ticker_provider.dart';
+import 'transitions.dart';
+
+// Internal representation of a child that, now or in the past, was set on the
+// AnimatedSwitcher.child field, but is now in the process of
+// transitioning. The internal representation includes fields that we don't want
+// to expose to the public API (like the controller).
+class _ChildEntry {
+  _ChildEntry({
+    required this.controller,
+    required this.animation,
+    required this.transition,
+    required this.widgetChild,
+  }) : assert(animation != null),
+       assert(transition != null),
+       assert(controller != null);
+
+  // The animation controller for the child's transition.
+  final AnimationController controller;
+
+  // The (curved) animation being used to drive the transition.
+  final Animation<double> animation;
+
+  // The currently built transition for this child.
+  Widget transition;
+
+  // The widget's child at the time this entry was created or updated.
+  // Used to rebuild the transition if necessary.
+  Widget widgetChild;
+
+  @override
+  String toString() => 'Entry#${shortHash(this)}($widgetChild)';
+}
+
+/// Signature for builders used to generate custom transitions for
+/// [AnimatedSwitcher].
+///
+/// The `child` should be transitioning in when the `animation` is running in
+/// the forward direction.
+///
+/// The function should return a widget which wraps the given `child`. It may
+/// also use the `animation` to inform its transition. It must not return null.
+typedef AnimatedSwitcherTransitionBuilder = Widget Function(Widget child, Animation<double> animation);
+
+/// Signature for builders used to generate custom layouts for
+/// [AnimatedSwitcher].
+///
+/// The builder should return a widget which contains the given children, laid
+/// out as desired. It must not return null. The builder should be able to
+/// handle an empty list of `previousChildren`, or a null `currentChild`.
+///
+/// The `previousChildren` list is an unmodifiable list, sorted with the oldest
+/// at the beginning and the newest at the end. It does not include the
+/// `currentChild`.
+typedef AnimatedSwitcherLayoutBuilder = Widget Function(Widget? currentChild, List<Widget> previousChildren);
+
+/// A widget that by default does a cross-fade between a new widget and the
+/// widget previously set on the [AnimatedSwitcher] as a child.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=2W7POjFb88g}
+///
+/// If they are swapped fast enough (i.e. before [duration] elapses), more than
+/// one previous child can exist and be transitioning out while the newest one
+/// is transitioning in.
+///
+/// If the "new" child is the same widget type and key as the "old" child, but
+/// with different parameters, then [AnimatedSwitcher] will *not* do a
+/// transition between them, since as far as the framework is concerned, they
+/// are the same widget and the existing widget can be updated with the new
+/// parameters. To force the transition to occur, set a [Key] on each child
+/// widget that you wish to be considered unique (typically a [ValueKey] on the
+/// widget data that distinguishes this child from the others).
+///
+/// The same key can be used for a new child as was used for an already-outgoing
+/// child; the two will not be considered related. (For example, if a progress
+/// indicator with key A is first shown, then an image with key B, then another
+/// progress indicator with key A again, all in rapid succession, then the old
+/// progress indicator and the image will be fading out while a new progress
+/// indicator is fading in.)
+///
+/// The type of transition can be changed from a cross-fade to a custom
+/// transition by setting the [transitionBuilder].
+///
+/// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+/// This sample shows a counter that animates the scale of a text widget
+/// whenever the value changes.
+///
+/// ```dart
+/// int _count = 0;
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Container(
+///     color: Colors.white,
+///     child: Column(
+///       mainAxisAlignment: MainAxisAlignment.center,
+///       children: <Widget>[
+///         AnimatedSwitcher(
+///           duration: const Duration(milliseconds: 500),
+///           transitionBuilder: (Widget child, Animation<double> animation) {
+///             return ScaleTransition(child: child, scale: animation);
+///           },
+///           child: Text(
+///             '$_count',
+///             // This key causes the AnimatedSwitcher to interpret this as a "new"
+///             // child each time the count changes, so that it will begin its animation
+///             // when the count changes.
+///             key: ValueKey<int>(_count),
+///             style: Theme.of(context).textTheme.headline4,
+///           ),
+///         ),
+///         ElevatedButton(
+///           child: const Text('Increment'),
+///           onPressed: () {
+///             setState(() {
+///               _count += 1;
+///             });
+///           },
+///         ),
+///       ],
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AnimatedCrossFade], which only fades between two children, but also
+///    interpolates their sizes, and is reversible.
+///  * [AnimatedOpacity], which can be used to switch between nothingness and
+///    a given child by fading the child in and out.
+///  * [FadeTransition], which [AnimatedSwitcher] uses to perform the transition.
+class AnimatedSwitcher extends StatefulWidget {
+  /// Creates an [AnimatedSwitcher].
+  ///
+  /// The [duration], [transitionBuilder], [layoutBuilder], [switchInCurve], and
+  /// [switchOutCurve] parameters must not be null.
+  const AnimatedSwitcher({
+    Key? key,
+    this.child,
+    required this.duration,
+    this.reverseDuration,
+    this.switchInCurve = Curves.linear,
+    this.switchOutCurve = Curves.linear,
+    this.transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder,
+    this.layoutBuilder = AnimatedSwitcher.defaultLayoutBuilder,
+  }) : assert(duration != null),
+       assert(switchInCurve != null),
+       assert(switchOutCurve != null),
+       assert(transitionBuilder != null),
+       assert(layoutBuilder != null),
+       super(key: key);
+
+  /// The current child widget to display. If there was a previous child, then
+  /// that child will be faded out using the [switchOutCurve], while the new
+  /// child is faded in with the [switchInCurve], over the [duration].
+  ///
+  /// If there was no previous child, then this child will fade in using the
+  /// [switchInCurve] over the [duration].
+  ///
+  /// The child is considered to be "new" if it has a different type or [Key]
+  /// (see [Widget.canUpdate]).
+  ///
+  /// To change the kind of transition used, see [transitionBuilder].
+  final Widget? child;
+
+  /// The duration of the transition from the old [child] value to the new one.
+  ///
+  /// This duration is applied to the given [child] when that property is set to
+  /// a new child. The same duration is used when fading out, unless
+  /// [reverseDuration] is set. Changing [duration] will not affect the
+  /// durations of transitions already in progress.
+  final Duration duration;
+
+  /// The duration of the transition from the new [child] value to the old one.
+  ///
+  /// This duration is applied to the given [child] when that property is set to
+  /// a new child. Changing [reverseDuration] will not affect the durations of
+  /// transitions already in progress.
+  ///
+  /// If not set, then the value of [duration] is used by default.
+  final Duration? reverseDuration;
+
+  /// The animation curve to use when transitioning in a new [child].
+  ///
+  /// This curve is applied to the given [child] when that property is set to a
+  /// new child. Changing [switchInCurve] will not affect the curve of a
+  /// transition already in progress.
+  ///
+  /// The [switchOutCurve] is used when fading out, except that if [child] is
+  /// changed while the current child is in the middle of fading in,
+  /// [switchInCurve] will be run in reverse from that point instead of jumping
+  /// to the corresponding point on [switchOutCurve].
+  final Curve switchInCurve;
+
+  /// The animation curve to use when transitioning a previous [child] out.
+  ///
+  /// This curve is applied to the [child] when the child is faded in (or when
+  /// the widget is created, for the first child). Changing [switchOutCurve]
+  /// will not affect the curves of already-visible widgets, it only affects the
+  /// curves of future children.
+  ///
+  /// If [child] is changed while the current child is in the middle of fading
+  /// in, [switchInCurve] will be run in reverse from that point instead of
+  /// jumping to the corresponding point on [switchOutCurve].
+  final Curve switchOutCurve;
+
+  /// A function that wraps a new [child] with an animation that transitions
+  /// the [child] in when the animation runs in the forward direction and out
+  /// when the animation runs in the reverse direction. This is only called
+  /// when a new [child] is set (not for each build), or when a new
+  /// [transitionBuilder] is set. If a new [transitionBuilder] is set, then
+  /// the transition is rebuilt for the current child and all previous children
+  /// using the new [transitionBuilder]. The function must not return null.
+  ///
+  /// The default is [AnimatedSwitcher.defaultTransitionBuilder].
+  ///
+  /// The animation provided to the builder has the [duration] and
+  /// [switchInCurve] or [switchOutCurve] applied as provided when the
+  /// corresponding [child] was first provided.
+  ///
+  /// See also:
+  ///
+  ///  * [AnimatedSwitcherTransitionBuilder] for more information about
+  ///    how a transition builder should function.
+  final AnimatedSwitcherTransitionBuilder transitionBuilder;
+
+  /// A function that wraps all of the children that are transitioning out, and
+  /// the [child] that's transitioning in, with a widget that lays all of them
+  /// out. This is called every time this widget is built. The function must not
+  /// return null.
+  ///
+  /// The default is [AnimatedSwitcher.defaultLayoutBuilder].
+  ///
+  /// See also:
+  ///
+  ///  * [AnimatedSwitcherLayoutBuilder] for more information about
+  ///    how a layout builder should function.
+  final AnimatedSwitcherLayoutBuilder layoutBuilder;
+
+  @override
+  _AnimatedSwitcherState createState() => _AnimatedSwitcherState();
+
+  /// The transition builder used as the default value of [transitionBuilder].
+  ///
+  /// The new child is given a [FadeTransition] which increases opacity as
+  /// the animation goes from 0.0 to 1.0, and decreases when the animation is
+  /// reversed.
+  ///
+  /// This is an [AnimatedSwitcherTransitionBuilder] function.
+  static Widget defaultTransitionBuilder(Widget child, Animation<double> animation) {
+    return FadeTransition(
+      opacity: animation,
+      child: child,
+    );
+  }
+
+  /// The layout builder used as the default value of [layoutBuilder].
+  ///
+  /// The new child is placed in a [Stack] that sizes itself to match the
+  /// largest of the child or a previous child. The children are centered on
+  /// each other.
+  ///
+  /// This is an [AnimatedSwitcherLayoutBuilder] function.
+  static Widget defaultLayoutBuilder(Widget? currentChild, List<Widget> previousChildren) {
+    return Stack(
+      children: <Widget>[
+        ...previousChildren,
+        if (currentChild != null) currentChild,
+      ],
+      alignment: Alignment.center,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms'));
+    properties.add(IntProperty('reverseDuration', reverseDuration?.inMilliseconds, unit: 'ms', defaultValue: null));
+  }
+}
+
+class _AnimatedSwitcherState extends State<AnimatedSwitcher> with TickerProviderStateMixin {
+  _ChildEntry? _currentEntry;
+  final Set<_ChildEntry> _outgoingEntries = <_ChildEntry>{};
+  List<Widget>? _outgoingWidgets = const <Widget>[];
+  int _childNumber = 0;
+
+  @override
+  void initState() {
+    super.initState();
+    _addEntryForNewChild(animate: false);
+  }
+
+  @override
+  void didUpdateWidget(AnimatedSwitcher oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    // If the transition builder changed, then update all of the previous
+    // transitions.
+    if (widget.transitionBuilder != oldWidget.transitionBuilder) {
+      _outgoingEntries.forEach(_updateTransitionForEntry);
+      if (_currentEntry != null)
+        _updateTransitionForEntry(_currentEntry!);
+      _markChildWidgetCacheAsDirty();
+    }
+
+    final bool hasNewChild = widget.child != null;
+    final bool hasOldChild = _currentEntry != null;
+    if (hasNewChild != hasOldChild ||
+        hasNewChild && !Widget.canUpdate(widget.child!, _currentEntry!.widgetChild)) {
+      // Child has changed, fade current entry out and add new entry.
+      _childNumber += 1;
+      _addEntryForNewChild(animate: true);
+    } else if (_currentEntry != null) {
+      assert(hasOldChild && hasNewChild);
+      assert(Widget.canUpdate(widget.child!, _currentEntry!.widgetChild));
+      // Child has been updated. Make sure we update the child widget and
+      // transition in _currentEntry even though we're not going to start a new
+      // animation, but keep the key from the previous transition so that we
+      // update the transition instead of replacing it.
+      _currentEntry!.widgetChild = widget.child!;
+      _updateTransitionForEntry(_currentEntry!); // uses entry.widgetChild
+      _markChildWidgetCacheAsDirty();
+    }
+  }
+
+  void _addEntryForNewChild({ required bool animate }) {
+    assert(animate || _currentEntry == null);
+    if (_currentEntry != null) {
+      assert(animate);
+      assert(!_outgoingEntries.contains(_currentEntry));
+      _outgoingEntries.add(_currentEntry!);
+      _currentEntry!.controller.reverse();
+      _markChildWidgetCacheAsDirty();
+      _currentEntry = null;
+    }
+    if (widget.child == null)
+      return;
+    final AnimationController controller = AnimationController(
+      duration: widget.duration,
+      reverseDuration: widget.reverseDuration,
+      vsync: this,
+    );
+    final Animation<double> animation = CurvedAnimation(
+      parent: controller,
+      curve: widget.switchInCurve,
+      reverseCurve: widget.switchOutCurve,
+    );
+    _currentEntry = _newEntry(
+      child: widget.child!,
+      controller: controller,
+      animation: animation,
+      builder: widget.transitionBuilder,
+    );
+    if (animate) {
+      controller.forward();
+    } else {
+      assert(_outgoingEntries.isEmpty);
+      controller.value = 1.0;
+    }
+  }
+
+  _ChildEntry _newEntry({
+    required Widget child,
+    required AnimatedSwitcherTransitionBuilder builder,
+    required AnimationController controller,
+    required Animation<double> animation,
+  }) {
+    final _ChildEntry entry = _ChildEntry(
+      widgetChild: child,
+      transition: KeyedSubtree.wrap(builder(child, animation), _childNumber),
+      animation: animation,
+      controller: controller,
+    );
+    animation.addStatusListener((AnimationStatus status) {
+      if (status == AnimationStatus.dismissed) {
+        setState(() {
+          assert(mounted);
+          assert(_outgoingEntries.contains(entry));
+          _outgoingEntries.remove(entry);
+          _markChildWidgetCacheAsDirty();
+        });
+        controller.dispose();
+      }
+    });
+    return entry;
+  }
+
+  void _markChildWidgetCacheAsDirty() {
+    _outgoingWidgets = null;
+  }
+
+  void _updateTransitionForEntry(_ChildEntry entry) {
+    entry.transition = KeyedSubtree(
+      key: entry.transition.key,
+      child: widget.transitionBuilder(entry.widgetChild, entry.animation),
+    );
+  }
+
+  void _rebuildOutgoingWidgetsIfNeeded() {
+    _outgoingWidgets ??= List<Widget>.unmodifiable(
+      _outgoingEntries.map<Widget>((_ChildEntry entry) => entry.transition),
+    );
+    assert(_outgoingEntries.length == _outgoingWidgets!.length);
+    assert(_outgoingEntries.isEmpty || _outgoingEntries.last.transition == _outgoingWidgets!.last);
+  }
+
+  @override
+  void dispose() {
+    if (_currentEntry != null)
+      _currentEntry!.controller.dispose();
+    for (final _ChildEntry entry in _outgoingEntries)
+      entry.controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    _rebuildOutgoingWidgetsIfNeeded();
+    return widget.layoutBuilder(_currentEntry?.transition, _outgoingWidgets!);
+  }
+}
diff --git a/lib/src/widgets/annotated_region.dart b/lib/src/widgets/annotated_region.dart
new file mode 100644
index 0000000..79ff0e0
--- /dev/null
+++ b/lib/src/widgets/annotated_region.dart
@@ -0,0 +1,56 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+import 'framework.dart';
+
+/// Annotates a region of the layer tree with a value.
+///
+/// See also:
+///
+///  * [Layer.find], for an example of how this value is retrieved.
+///  * [AnnotatedRegionLayer], the layer pushed into the layer tree.
+class AnnotatedRegion<T extends Object> extends SingleChildRenderObjectWidget {
+  /// Creates a new annotated region to insert [value] into the layer tree.
+  ///
+  /// Neither [child] nor [value] may be null.
+  ///
+  /// [sized] defaults to true and controls whether the annotated region will
+  /// clip its child.
+  const AnnotatedRegion({
+    Key? key,
+    required Widget child,
+    required this.value,
+    this.sized = true,
+  }) : assert(value != null),
+       assert(child != null),
+       super(key: key, child: child);
+
+  /// A value which can be retrieved using [Layer.find].
+  final T value;
+
+  /// If false, the layer pushed into the tree will not be provided with a size.
+  ///
+  /// An [AnnotatedRegionLayer] with a size checks that the offset provided in
+  /// [Layer.find] is within the bounds, returning null otherwise.
+  ///
+  /// See also:
+  ///
+  ///  * [AnnotatedRegionLayer], for a description of this behavior.
+  final bool sized;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return RenderAnnotatedRegion<T>(value: value, sized: sized);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderAnnotatedRegion<T> renderObject) {
+    renderObject
+      ..value = value
+      ..sized = sized;
+  }
+}
diff --git a/lib/src/widgets/app.dart b/lib/src/widgets/app.dart
new file mode 100644
index 0000000..64442da
--- /dev/null
+++ b/lib/src/widgets/app.dart
@@ -0,0 +1,1720 @@
+// Copyright 2014 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.
+
+import 'dart:collection' show HashMap;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+
+import 'actions.dart';
+import 'banner.dart';
+import 'basic.dart';
+import 'binding.dart';
+import 'focus_traversal.dart';
+import 'framework.dart';
+import 'localizations.dart';
+import 'media_query.dart';
+import 'navigator.dart';
+import 'pages.dart';
+import 'performance_overlay.dart';
+import 'restoration.dart';
+import 'router.dart';
+import 'scrollable.dart';
+import 'semantics_debugger.dart';
+import 'shortcuts.dart';
+import 'text.dart';
+import 'title.dart';
+import 'widget_inspector.dart';
+
+export 'package:flute/ui.dart' show Locale;
+
+// Examples can assume:
+// // @dart = 2.9
+
+/// The signature of [WidgetsApp.localeListResolutionCallback].
+///
+/// A [LocaleListResolutionCallback] is responsible for computing the locale of the app's
+/// [Localizations] object when the app starts and when user changes the list of
+/// locales for the device.
+///
+/// The [locales] list is the device's preferred locales when the app started, or the
+/// device's preferred locales the user selected after the app was started. This list
+/// is in order of preference. If this list is null or empty, then Flutter has not yet
+/// received the locale information from the platform. The [supportedLocales] parameter
+/// is just the value of [WidgetsApp.supportedLocales].
+///
+/// See also:
+///
+///  * [LocaleResolutionCallback], which takes only one default locale (instead of a list)
+///    and is attempted only after this callback fails or is null. [LocaleListResolutionCallback]
+///    is recommended over [LocaleResolutionCallback].
+typedef LocaleListResolutionCallback = Locale? Function(List<Locale>? locales, Iterable<Locale> supportedLocales);
+
+/// {@template flutter.widgets.LocaleResolutionCallback}
+/// The signature of [WidgetsApp.localeResolutionCallback].
+///
+/// It is recommended to provide a [LocaleListResolutionCallback] instead of a
+/// [LocaleResolutionCallback] when possible, as [LocaleResolutionCallback] only
+/// receives a subset of the information provided in [LocaleListResolutionCallback].
+///
+/// A [LocaleResolutionCallback] is responsible for computing the locale of the app's
+/// [Localizations] object when the app starts and when user changes the default
+/// locale for the device after [LocaleListResolutionCallback] fails or is not provided.
+///
+/// This callback is also used if the app is created with a specific locale using
+/// the [new WidgetsApp] `locale` parameter.
+///
+/// The [locale] is either the value of [WidgetsApp.locale], or the device's default
+/// locale when the app started, or the device locale the user selected after the app
+/// was started. The default locale is the first locale in the list of preferred
+/// locales. If [locale] is null, then Flutter has not yet received the locale
+/// information from the platform. The [supportedLocales] parameter is just the value of
+/// [WidgetsApp.supportedLocales].
+///
+/// See also:
+///
+///  * [LocaleListResolutionCallback], which takes a list of preferred locales (instead of one locale).
+///    Resolutions by [LocaleListResolutionCallback] take precedence over [LocaleResolutionCallback].
+/// {@endtemplate}
+typedef LocaleResolutionCallback = Locale? Function(Locale? locale, Iterable<Locale> supportedLocales);
+
+/// The signature of [WidgetsApp.onGenerateTitle].
+///
+/// Used to generate a value for the app's [Title.title], which the device uses
+/// to identify the app for the user. The `context` includes the [WidgetsApp]'s
+/// [Localizations] widget so that this method can be used to produce a
+/// localized title.
+///
+/// This function must not return null.
+typedef GenerateAppTitle = String Function(BuildContext context);
+
+/// The signature of [WidgetsApp.pageRouteBuilder].
+///
+/// Creates a [PageRoute] using the given [RouteSettings] and [WidgetBuilder].
+typedef PageRouteFactory = PageRoute<T> Function<T>(RouteSettings settings, WidgetBuilder builder);
+
+/// The signature of [WidgetsApp.onGenerateInitialRoutes].
+///
+/// Creates a series of one or more initial routes.
+typedef InitialRouteListFactory = List<Route<dynamic>> Function(String initialRoute);
+
+/// A convenience widget that wraps a number of widgets that are commonly
+/// required for an application.
+///
+/// One of the primary roles that [WidgetsApp] provides is binding the system
+/// back button to popping the [Navigator] or quitting the application.
+///
+/// It is used by both [MaterialApp] and [CupertinoApp] to implement base
+/// functionality for an app.
+///
+/// Find references to many of the widgets that [WidgetsApp] wraps in the "See
+/// also" section.
+///
+/// See also:
+///
+///  * [CheckedModeBanner], which displays a [Banner] saying "DEBUG" when
+///    running in checked mode.
+///  * [DefaultTextStyle], the text style to apply to descendant [Text] widgets
+///    without an explicit style.
+///  * [MediaQuery], which establishes a subtree in which media queries resolve
+///    to a [MediaQueryData].
+///  * [Localizations], which defines the [Locale] for its `child`.
+///  * [Title], a widget that describes this app in the operating system.
+///  * [Navigator], a widget that manages a set of child widgets with a stack
+///    discipline.
+///  * [Overlay], a widget that manages a [Stack] of entries that can be managed
+///    independently.
+///  * [SemanticsDebugger], a widget that visualizes the semantics for the child.
+class WidgetsApp extends StatefulWidget {
+  /// Creates a widget that wraps a number of widgets that are commonly
+  /// required for an application.
+  ///
+  /// The boolean arguments, [color], and [navigatorObservers] must not be null.
+  ///
+  /// Most callers will want to use the [home] or [routes] parameters, or both.
+  /// The [home] parameter is a convenience for the following [routes] map:
+  ///
+  /// ```dart
+  /// <String, WidgetBuilder>{ '/': (BuildContext context) => myWidget }
+  /// ```
+  ///
+  /// It is possible to specify both [home] and [routes], but only if [routes] does
+  ///  _not_ contain an entry for `'/'`.  Conversely, if [home] is omitted, [routes]
+  /// _must_ contain an entry for `'/'`.
+  ///
+  /// If [home] or [routes] are not null, the routing implementation needs to know how
+  /// appropriately build [PageRoute]s. This can be achieved by supplying the
+  /// [pageRouteBuilder] parameter.  The [pageRouteBuilder] is used by [MaterialApp]
+  /// and [CupertinoApp] to create [MaterialPageRoute]s and [CupertinoPageRoute],
+  /// respectively.
+  ///
+  /// The [builder] parameter is designed to provide the ability to wrap the visible
+  /// content of the app in some other widget. It is recommended that you use [home]
+  /// rather than [builder] if you intend to only display a single route in your app.
+  ///
+  /// [WidgetsApp] is also possible to provide a custom implementation of routing via the
+  /// [onGenerateRoute] and [onUnknownRoute] parameters. These parameters correspond
+  /// to [Navigator.onGenerateRoute] and [Navigator.onUnknownRoute]. If [home], [routes],
+  /// and [builder] are null, or if they fail to create a requested route,
+  /// [onGenerateRoute] will be invoked.  If that fails, [onUnknownRoute] will be invoked.
+  ///
+  /// The [pageRouteBuilder] will create a [PageRoute] that wraps newly built routes.
+  /// If the [builder] is non-null and the [onGenerateRoute] argument is null, then the
+  /// [builder] will not be provided only with the context and the child widget, whereas
+  /// the [pageRouteBuilder] will be provided with [RouteSettings]. If [onGenerateRoute]
+  /// is not provided, [navigatorKey], [onUnknownRoute], [navigatorObservers], and
+  /// [initialRoute] must have their default values, as they will have no effect.
+  ///
+  /// The `supportedLocales` argument must be a list of one or more elements.
+  /// By default supportedLocales is `[const Locale('en', 'US')]`.
+  WidgetsApp({ // can't be const because the asserts use methods on Iterable :-(
+    Key? key,
+    this.navigatorKey,
+    this.onGenerateRoute,
+    this.onGenerateInitialRoutes,
+    this.onUnknownRoute,
+    List<NavigatorObserver> this.navigatorObservers = const <NavigatorObserver>[],
+    this.initialRoute,
+    this.pageRouteBuilder,
+    this.home,
+    Map<String, WidgetBuilder> this.routes = const <String, WidgetBuilder>{},
+    this.builder,
+    this.title = '',
+    this.onGenerateTitle,
+    this.textStyle,
+    required this.color,
+    this.locale,
+    this.localizationsDelegates,
+    this.localeListResolutionCallback,
+    this.localeResolutionCallback,
+    this.supportedLocales = const <Locale>[Locale('en', 'US')],
+    this.showPerformanceOverlay = false,
+    this.checkerboardRasterCacheImages = false,
+    this.checkerboardOffscreenLayers = false,
+    this.showSemanticsDebugger = false,
+    this.debugShowWidgetInspector = false,
+    this.debugShowCheckedModeBanner = true,
+    this.inspectorSelectButtonBuilder,
+    this.shortcuts,
+    this.actions,
+    this.restorationScopeId,
+  }) : assert(navigatorObservers != null),
+       assert(routes != null),
+       assert(
+         home == null ||
+         onGenerateInitialRoutes == null,
+         'If onGenerateInitialRoutes is specified, the home argument will be '
+         'redundant.'
+       ),
+       assert(
+         home == null ||
+         !routes.containsKey(Navigator.defaultRouteName),
+         'If the home property is specified, the routes table '
+         'cannot include an entry for "/", since it would be redundant.'
+       ),
+       assert(
+         builder != null ||
+         home != null ||
+         routes.containsKey(Navigator.defaultRouteName) ||
+         onGenerateRoute != null ||
+         onUnknownRoute != null,
+         'Either the home property must be specified, '
+         'or the routes table must include an entry for "/", '
+         'or there must be on onGenerateRoute callback specified, '
+         'or there must be an onUnknownRoute callback specified, '
+         'or the builder property must be specified, '
+         'because otherwise there is nothing to fall back on if the '
+         'app is started with an intent that specifies an unknown route.'
+       ),
+       assert(
+         (home != null ||
+          routes.isNotEmpty ||
+          onGenerateRoute != null ||
+          onUnknownRoute != null)
+         ||
+         (builder != null &&
+          navigatorKey == null &&
+          initialRoute == null &&
+          navigatorObservers.isEmpty),
+         'If no route is provided using '
+         'home, routes, onGenerateRoute, or onUnknownRoute, '
+         'a non-null callback for the builder property must be provided, '
+         'and the other navigator-related properties, '
+         'navigatorKey, initialRoute, and navigatorObservers, '
+         'must have their initial values '
+         '(null, null, and the empty list, respectively).'
+       ),
+       assert(
+         builder != null ||
+         onGenerateRoute != null ||
+         pageRouteBuilder != null,
+         'If neither builder nor onGenerateRoute are provided, the '
+         'pageRouteBuilder must be specified so that the default handler '
+         'will know what kind of PageRoute transition to build.'
+       ),
+       assert(title != null),
+       assert(color != null),
+       assert(supportedLocales != null && supportedLocales.isNotEmpty),
+       assert(showPerformanceOverlay != null),
+       assert(checkerboardRasterCacheImages != null),
+       assert(checkerboardOffscreenLayers != null),
+       assert(showSemanticsDebugger != null),
+       assert(debugShowCheckedModeBanner != null),
+       assert(debugShowWidgetInspector != null),
+       routeInformationProvider = null,
+       routeInformationParser = null,
+       routerDelegate = null,
+       backButtonDispatcher = null,
+       super(key: key);
+
+  /// Creates a [WidgetsApp] that uses the [Router] instead of a [Navigator].
+  WidgetsApp.router({
+    Key? key,
+    this.routeInformationProvider,
+    required RouteInformationParser<Object> this.routeInformationParser,
+    required RouterDelegate<Object> this.routerDelegate,
+    BackButtonDispatcher? backButtonDispatcher,
+    this.builder,
+    this.title = '',
+    this.onGenerateTitle,
+    this.textStyle,
+    required this.color,
+    this.locale,
+    this.localizationsDelegates,
+    this.localeListResolutionCallback,
+    this.localeResolutionCallback,
+    this.supportedLocales = const <Locale>[Locale('en', 'US')],
+    this.showPerformanceOverlay = false,
+    this.checkerboardRasterCacheImages = false,
+    this.checkerboardOffscreenLayers = false,
+    this.showSemanticsDebugger = false,
+    this.debugShowWidgetInspector = false,
+    this.debugShowCheckedModeBanner = true,
+    this.inspectorSelectButtonBuilder,
+    this.shortcuts,
+    this.actions,
+    this.restorationScopeId,
+  }) : assert(
+         routeInformationParser != null &&
+         routerDelegate != null,
+         'The routeInformationParser and routerDelegate cannot be null.'
+       ),
+       assert(title != null),
+       assert(color != null),
+       assert(supportedLocales != null && supportedLocales.isNotEmpty),
+       assert(showPerformanceOverlay != null),
+       assert(checkerboardRasterCacheImages != null),
+       assert(checkerboardOffscreenLayers != null),
+       assert(showSemanticsDebugger != null),
+       assert(debugShowCheckedModeBanner != null),
+       assert(debugShowWidgetInspector != null),
+       navigatorObservers = null,
+       backButtonDispatcher = backButtonDispatcher ?? RootBackButtonDispatcher(),
+       navigatorKey = null,
+       onGenerateRoute = null,
+       pageRouteBuilder = null,
+       home = null,
+       onGenerateInitialRoutes = null,
+       onUnknownRoute = null,
+       routes = null,
+       initialRoute = null,
+       super(key: key);
+
+  /// {@template flutter.widgets.widgetsApp.navigatorKey}
+  /// A key to use when building the [Navigator].
+  ///
+  /// If a [navigatorKey] is specified, the [Navigator] can be directly
+  /// manipulated without first obtaining it from a [BuildContext] via
+  /// [Navigator.of]: from the [navigatorKey], use the [GlobalKey.currentState]
+  /// getter.
+  ///
+  /// If this is changed, a new [Navigator] will be created, losing all the
+  /// application state in the process; in that case, the [navigatorObservers]
+  /// must also be changed, since the previous observers will be attached to the
+  /// previous navigator.
+  ///
+  /// The [Navigator] is only built if [onGenerateRoute] is not null; if it is
+  /// null, [navigatorKey] must also be null.
+  /// {@endtemplate}
+  final GlobalKey<NavigatorState>? navigatorKey;
+
+  /// {@template flutter.widgets.widgetsApp.onGenerateRoute}
+  /// The route generator callback used when the app is navigated to a
+  /// named route.
+  ///
+  /// If this returns null when building the routes to handle the specified
+  /// [initialRoute], then all the routes are discarded and
+  /// [Navigator.defaultRouteName] is used instead (`/`). See [initialRoute].
+  ///
+  /// During normal app operation, the [onGenerateRoute] callback will only be
+  /// applied to route names pushed by the application, and so should never
+  /// return null.
+  ///
+  /// This is used if [routes] does not contain the requested route.
+  ///
+  /// The [Navigator] is only built if routes are provided (either via [home],
+  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
+  /// [builder] must not be null.
+  /// {@endtemplate}
+  ///
+  /// If this property is not set, either the [routes] or [home] properties must
+  /// be set, and the [pageRouteBuilder] must also be set so that the
+  /// default handler will know what routes and [PageRoute]s to build.
+  final RouteFactory? onGenerateRoute;
+
+  /// {@template flutter.widgets.widgetsApp.onGenerateInitialRoutes}
+  /// The routes generator callback used for generating initial routes if
+  /// [initialRoute] is provided.
+  ///
+  /// If this property is not set, the underlying
+  /// [Navigator.onGenerateInitialRoutes] will default to
+  /// [Navigator.defaultGenerateInitialRoutes].
+  /// {@endtemplate}
+  final InitialRouteListFactory? onGenerateInitialRoutes;
+
+  /// The [PageRoute] generator callback used when the app is navigated to a
+  /// named route.
+  ///
+  /// This callback can be used, for example, to specify that a [MaterialPageRoute]
+  /// or a [CupertinoPageRoute] should be used for building page transitions.
+  final PageRouteFactory? pageRouteBuilder;
+
+  /// {@template flutter.widgets.widgetsApp.routeInformationParser}
+  /// A delegate to parse the route information from the
+  /// [routeInformationProvider] into a generic data type to be processed by
+  /// the [routerDelegate] at a later stage.
+  ///
+  /// This object will be used by the underlying [Router].
+  ///
+  /// The generic type `T` must match the generic type of the [routerDelegate].
+  ///
+  /// See also:
+  ///
+  ///  * [Router.routeInformationParser]: which receives this object when this
+  ///    widget builds the [Router].
+  /// {@endtemplate}
+  final RouteInformationParser<Object>? routeInformationParser;
+
+  /// {@template flutter.widgets.widgetsApp.routerDelegate}
+  /// A delegate that configures a widget, typically a [Navigator], with
+  /// parsed result from the [routeInformationParser].
+  ///
+  /// This object will be used by the underlying [Router].
+  ///
+  /// The generic type `T` must match the generic type of the
+  /// [routeInformationParser].
+  ///
+  /// See also:
+  ///
+  ///  * [Router.routerDelegate]: which receives this object when this widget
+  ///    builds the [Router].
+  /// {@endtemplate}
+  final RouterDelegate<Object>? routerDelegate;
+
+  /// {@template flutter.widgets.widgetsApp.backButtonDispatcher}
+  /// A delegate that decide whether to handle the Android back button intent.
+  ///
+  /// This object will be used by the underlying [Router].
+  ///
+  /// If this is not provided, the widgets app will create a
+  /// [RootBackButtonDispatcher] by default.
+  ///
+  /// See also:
+  ///
+  ///  * [Router.backButtonDispatcher]: which receives this object when this
+  ///    widget builds the [Router].
+  /// {@endtemplate}
+  final BackButtonDispatcher? backButtonDispatcher;
+
+  /// {@template flutter.widgets.widgetsApp.routeInformationProvider}
+  /// A object that provides route information through the
+  /// [RouteInformationProvider.value] and notifies its listener when its value
+  /// changes.
+  ///
+  /// This object will be used by the underlying [Router].
+  ///
+  /// If this is not provided, the widgets app will create a
+  /// [PlatformRouteInformationProvider] with initial route name equal to the
+  /// [dart:ui.PlatformDispatcher.defaultRouteName] by default.
+  ///
+  /// See also:
+  ///
+  ///  * [Router.routeInformationProvider]: which receives this object when this
+  ///    widget builds the [Router].
+  /// {@endtemplate}
+  final RouteInformationProvider? routeInformationProvider;
+
+  /// {@template flutter.widgets.widgetsApp.home}
+  /// The widget for the default route of the app ([Navigator.defaultRouteName],
+  /// which is `/`).
+  ///
+  /// This is the route that is displayed first when the application is started
+  /// normally, unless [initialRoute] is specified. It's also the route that's
+  /// displayed if the [initialRoute] can't be displayed.
+  ///
+  /// To be able to directly call [Theme.of], [MediaQuery.of], etc, in the code
+  /// that sets the [home] argument in the constructor, you can use a [Builder]
+  /// widget to get a [BuildContext].
+  ///
+  /// If [home] is specified, then [routes] must not include an entry for `/`,
+  /// as [home] takes its place.
+  ///
+  /// The [Navigator] is only built if routes are provided (either via [home],
+  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
+  /// [builder] must not be null.
+  ///
+  /// The difference between using [home] and using [builder] is that the [home]
+  /// subtree is inserted into the application below a [Navigator] (and thus
+  /// below an [Overlay], which [Navigator] uses). With [home], therefore,
+  /// dialog boxes will work automatically, the [routes] table will be used, and
+  /// APIs such as [Navigator.push] and [Navigator.pop] will work as expected.
+  /// In contrast, the widget returned from [builder] is inserted _above_ the
+  /// app's [Navigator] (if any).
+  /// {@endtemplate}
+  ///
+  /// If this property is set, the [pageRouteBuilder] property must also be set
+  /// so that the default route handler will know what kind of [PageRoute]s to
+  /// build.
+  final Widget? home;
+
+  /// The application's top-level routing table.
+  ///
+  /// When a named route is pushed with [Navigator.pushNamed], the route name is
+  /// looked up in this map. If the name is present, the associated
+  /// [WidgetBuilder] is used to construct a [PageRoute] specified by
+  /// [pageRouteBuilder] to perform an appropriate transition, including [Hero]
+  /// animations, to the new route.
+  ///
+  /// {@template flutter.widgets.widgetsApp.routes}
+  /// If the app only has one page, then you can specify it using [home] instead.
+  ///
+  /// If [home] is specified, then it implies an entry in this table for the
+  /// [Navigator.defaultRouteName] route (`/`), and it is an error to
+  /// redundantly provide such a route in the [routes] table.
+  ///
+  /// If a route is requested that is not specified in this table (or by
+  /// [home]), then the [onGenerateRoute] callback is called to build the page
+  /// instead.
+  ///
+  /// The [Navigator] is only built if routes are provided (either via [home],
+  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
+  /// [builder] must not be null.
+  /// {@endtemplate}
+  ///
+  /// If the routes map is not empty, the [pageRouteBuilder] property must be set
+  /// so that the default route handler will know what kind of [PageRoute]s to
+  /// build.
+  final Map<String, WidgetBuilder>? routes;
+
+  /// {@template flutter.widgets.widgetsApp.onUnknownRoute}
+  /// Called when [onGenerateRoute] fails to generate a route, except for the
+  /// [initialRoute].
+  ///
+  /// This callback is typically used for error handling. For example, this
+  /// callback might always generate a "not found" page that describes the route
+  /// that wasn't found.
+  ///
+  /// Unknown routes can arise either from errors in the app or from external
+  /// requests to push routes, such as from Android intents.
+  ///
+  /// The [Navigator] is only built if routes are provided (either via [home],
+  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
+  /// [builder] must not be null.
+  /// {@endtemplate}
+  final RouteFactory? onUnknownRoute;
+
+  /// {@template flutter.widgets.widgetsApp.initialRoute}
+  /// The name of the first route to show, if a [Navigator] is built.
+  ///
+  /// Defaults to [dart:ui.PlatformDispatcher.defaultRouteName], which may be
+  /// overridden by the code that launched the application.
+  ///
+  /// If the route name starts with a slash, then it is treated as a "deep link",
+  /// and before this route is pushed, the routes leading to this one are pushed
+  /// also. For example, if the route was `/a/b/c`, then the app would start
+  /// with the four routes `/`, `/a`, `/a/b`, and `/a/b/c` loaded, in that order.
+  /// Even if the route was just `/a`, the app would start with `/` and `/a`
+  /// loaded. You can use the [onGenerateInitialRoutes] property to override
+  /// this behavior.
+  ///
+  /// Intermediate routes aren't required to exist. In the example above, `/a`
+  /// and `/a/b` could be skipped if they have no matching route. But `/a/b/c` is
+  /// required to have a route, else [initialRoute] is ignored and
+  /// [Navigator.defaultRouteName] is used instead (`/`). This can happen if the
+  /// app is started with an intent that specifies a non-existent route.
+  ///
+  /// The [Navigator] is only built if routes are provided (either via [home],
+  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
+  /// [initialRoute] must be null and [builder] must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [Navigator.initialRoute], which is used to implement this property.
+  ///  * [Navigator.push], for pushing additional routes.
+  ///  * [Navigator.pop], for removing a route from the stack.
+  ///
+  /// {@endtemplate}
+  final String? initialRoute;
+
+  /// {@template flutter.widgets.widgetsApp.navigatorObservers}
+  /// The list of observers for the [Navigator] created for this app.
+  ///
+  /// This list must be replaced by a list of newly-created observers if the
+  /// [navigatorKey] is changed.
+  ///
+  /// The [Navigator] is only built if routes are provided (either via [home],
+  /// [routes], [onGenerateRoute], or [onUnknownRoute]); if they are not,
+  /// [navigatorObservers] must be the empty list and [builder] must not be null.
+  /// {@endtemplate}
+  final List<NavigatorObserver>? navigatorObservers;
+
+  /// {@template flutter.widgets.widgetsApp.builder}
+  /// A builder for inserting widgets above the [Navigator] or - when the
+  /// [WidgetsApp.router] constructor is used - above the [Router] but below the
+  /// other widgets created by the [WidgetsApp] widget, or for replacing the
+  /// [Navigator]/[Router] entirely.
+  ///
+  /// For example, from the [BuildContext] passed to this method, the
+  /// [Directionality], [Localizations], [DefaultTextStyle], [MediaQuery], etc,
+  /// are all available. They can also be overridden in a way that impacts all
+  /// the routes in the [Navigator] or [Router].
+  ///
+  /// This is rarely useful, but can be used in applications that wish to
+  /// override those defaults, e.g. to force the application into right-to-left
+  /// mode despite being in English, or to override the [MediaQuery] metrics
+  /// (e.g. to leave a gap for advertisements shown by a plugin from OEM code).
+  ///
+  /// For specifically overriding the [title] with a value based on the
+  /// [Localizations], consider [onGenerateTitle] instead.
+  ///
+  /// The [builder] callback is passed two arguments, the [BuildContext] (as
+  /// `context`) and a [Navigator] or [Router] widget (as `child`).
+  ///
+  /// If no routes are provided to the regular [WidgetsApp] constructor using
+  /// [home], [routes], [onGenerateRoute], or [onUnknownRoute], the `child` will
+  /// be null, and it is the responsibility of the [builder] to provide the
+  /// application's routing machinery.
+  ///
+  /// If routes _are_ provided to the regular [WidgetsApp] constructor using one
+  /// or more of those properties or if the [WidgetsApp.router] constructor is
+  /// used, then `child` is not null, and the returned value should include the
+  /// `child` in the widget subtree; if it does not, then the application will
+  /// have no [Navigator] or [Router] and the routing related properties (i.e.
+  /// [navigatorKey], [home], [routes], [onGenerateRoute], [onUnknownRoute],
+  /// [initialRoute], [navigatorObservers], [routeInformationProvider],
+  /// [backButtonDispatcher], [routerDelegate], and [routeInformationParser])
+  /// are ignored.
+  ///
+  /// If [builder] is null, it is as if a builder was specified that returned
+  /// the `child` directly. If it is null, routes must be provided using one of
+  /// the other properties listed above.
+  ///
+  /// Unless a [Navigator] is provided, either implicitly from [builder] being
+  /// null, or by a [builder] including its `child` argument, or by a [builder]
+  /// explicitly providing a [Navigator] of its own, or by the [routerDelegate]
+  /// building one, widgets and APIs such as [Hero], [Navigator.push] and
+  /// [Navigator.pop], will not function.
+  /// {@endtemplate}
+  final TransitionBuilder? builder;
+
+  /// {@template flutter.widgets.widgetsApp.title}
+  /// A one-line description used by the device to identify the app for the user.
+  ///
+  /// On Android the titles appear above the task manager's app snapshots which are
+  /// displayed when the user presses the "recent apps" button. On iOS this
+  /// value cannot be used. `CFBundleDisplayName` from the app's `Info.plist` is
+  /// referred to instead whenever present, `CFBundleName` otherwise.
+  /// On the web it is used as the page title, which shows up in the browser's list of open tabs.
+  ///
+  /// To provide a localized title instead, use [onGenerateTitle].
+  /// {@endtemplate}
+  final String title;
+
+  /// {@template flutter.widgets.widgetsApp.onGenerateTitle}
+  /// If non-null this callback function is called to produce the app's
+  /// title string, otherwise [title] is used.
+  ///
+  /// The [onGenerateTitle] `context` parameter includes the [WidgetsApp]'s
+  /// [Localizations] widget so that this callback can be used to produce a
+  /// localized title.
+  ///
+  /// This callback function must not return null.
+  ///
+  /// The [onGenerateTitle] callback is called each time the [WidgetsApp]
+  /// rebuilds.
+  /// {@endtemplate}
+  final GenerateAppTitle? onGenerateTitle;
+
+  /// The default text style for [Text] in the application.
+  final TextStyle? textStyle;
+
+  /// {@template flutter.widgets.widgetsApp.color}
+  /// The primary color to use for the application in the operating system
+  /// interface.
+  ///
+  /// For example, on Android this is the color used for the application in the
+  /// application switcher.
+  /// {@endtemplate}
+  final Color color;
+
+  /// {@template flutter.widgets.widgetsApp.locale}
+  /// The initial locale for this app's [Localizations] widget is based
+  /// on this value.
+  ///
+  /// If the 'locale' is null then the system's locale value is used.
+  ///
+  /// The value of [Localizations.locale] will equal this locale if
+  /// it matches one of the [supportedLocales]. Otherwise it will be
+  /// the first element of [supportedLocales].
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [localeResolutionCallback], which can override the default
+  ///    [supportedLocales] matching algorithm.
+  ///  * [localizationsDelegates], which collectively define all of the localized
+  ///    resources used by this app.
+  final Locale? locale;
+
+  /// {@template flutter.widgets.widgetsApp.localizationsDelegates}
+  /// The delegates for this app's [Localizations] widget.
+  ///
+  /// The delegates collectively define all of the localized resources
+  /// for this application's [Localizations] widget.
+  /// {@endtemplate}
+  final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates;
+
+  /// {@template flutter.widgets.widgetsApp.localeListResolutionCallback}
+  /// This callback is responsible for choosing the app's locale
+  /// when the app is started, and when the user changes the
+  /// device's locale.
+  ///
+  /// When a [localeListResolutionCallback] is provided, Flutter will first
+  /// attempt to resolve the locale with the provided
+  /// [localeListResolutionCallback]. If the callback or result is null, it will
+  /// fallback to trying the [localeResolutionCallback]. If both
+  /// [localeResolutionCallback] and [localeListResolutionCallback] are left
+  /// null or fail to resolve (return null), the a basic fallback algorithm will
+  /// be used.
+  ///
+  /// The priority of each available fallback is:
+  ///
+  ///  1. [localeListResolutionCallback] is attempted first.
+  ///  1. [localeResolutionCallback] is attempted second.
+  ///  1. Flutter's basic resolution algorithm, as described in
+  ///     [supportedLocales], is attempted last.
+  ///
+  /// Properly localized projects should provide a more advanced algorithm than
+  /// the basic method from [supportedLocales], as it does not implement a
+  /// complete algorithm (such as the one defined in
+  /// [Unicode TR35](https://unicode.org/reports/tr35/#LanguageMatching))
+  /// and is optimized for speed at the detriment of some uncommon edge-cases.
+  /// {@endtemplate}
+  ///
+  /// This callback considers the entire list of preferred locales.
+  ///
+  /// This algorithm should be able to handle a null or empty list of preferred locales,
+  /// which indicates Flutter has not yet received locale information from the platform.
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialApp.localeListResolutionCallback], which sets the callback of the
+  ///    [WidgetsApp] it creates.
+  final LocaleListResolutionCallback? localeListResolutionCallback;
+
+  /// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
+  ///
+  /// This callback considers only the default locale, which is the first locale
+  /// in the preferred locales list. It is preferred to set [localeListResolutionCallback]
+  /// over [localeResolutionCallback] as it provides the full preferred locales list.
+  ///
+  /// This algorithm should be able to handle a null locale, which indicates
+  /// Flutter has not yet received locale information from the platform.
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialApp.localeResolutionCallback], which sets the callback of the
+  ///    [WidgetsApp] it creates.
+  final LocaleResolutionCallback? localeResolutionCallback;
+
+  /// {@template flutter.widgets.widgetsApp.supportedLocales}
+  /// The list of locales that this app has been localized for.
+  ///
+  /// By default only the American English locale is supported. Apps should
+  /// configure this list to match the locales they support.
+  ///
+  /// This list must not null. Its default value is just
+  /// `[const Locale('en', 'US')]`.
+  ///
+  /// The order of the list matters. The default locale resolution algorithm,
+  /// `basicLocaleListResolution`, attempts to match by the following priority:
+  ///
+  ///  1. [Locale.languageCode], [Locale.scriptCode], and [Locale.countryCode]
+  ///  2. [Locale.languageCode] and [Locale.scriptCode] only
+  ///  3. [Locale.languageCode] and [Locale.countryCode] only
+  ///  4. [Locale.languageCode] only
+  ///  5. [Locale.countryCode] only when all preferred locales fail to match
+  ///  6. Returns the first element of [supportedLocales] as a fallback
+  ///
+  /// When more than one supported locale matches one of these criteria, only
+  /// the first matching locale is returned.
+  ///
+  /// The default locale resolution algorithm can be overridden by providing a
+  /// value for [localeListResolutionCallback]. The provided
+  /// `basicLocaleListResolution` is optimized for speed and does not implement
+  /// a full algorithm (such as the one defined in
+  /// [Unicode TR35](https://unicode.org/reports/tr35/#LanguageMatching)) that
+  /// takes distances between languages into account.
+  ///
+  /// When supporting languages with more than one script, it is recommended
+  /// to specify the [Locale.scriptCode] explicitly. Locales may also be defined without
+  /// [Locale.countryCode] to specify a generic fallback for a particular script.
+  ///
+  /// A fully supported language with multiple scripts should define a generic language-only
+  /// locale (e.g. 'zh'), language+script only locales (e.g. 'zh_Hans' and 'zh_Hant'),
+  /// and any language+script+country locales (e.g. 'zh_Hans_CN'). Fully defining all of
+  /// these locales as supported is not strictly required but allows for proper locale resolution in
+  /// the most number of cases. These locales can be specified with the [Locale.fromSubtags]
+  /// constructor:
+  ///
+  /// ```dart
+  /// // Full Chinese support for CN, TW, and HK
+  /// supportedLocales: [
+  ///   const Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh'
+  ///   const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'), // generic simplified Chinese 'zh_Hans'
+  ///   const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'), // generic traditional Chinese 'zh_Hant'
+  ///   const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN'), // 'zh_Hans_CN'
+  ///   const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'), // 'zh_Hant_TW'
+  ///   const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'HK'), // 'zh_Hant_HK'
+  /// ],
+  /// ```
+  ///
+  /// Omitting some these fallbacks may result in improperly resolved
+  /// edge-cases, for example, a simplified Chinese user in Taiwan ('zh_Hans_TW')
+  /// may resolve to traditional Chinese if 'zh_Hans' and 'zh_Hans_CN' are
+  /// omitted.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialApp.supportedLocales], which sets the `supportedLocales`
+  ///    of the [WidgetsApp] it creates.
+  ///  * [localeResolutionCallback], an app callback that resolves the app's locale
+  ///    when the device's locale changes.
+  ///  * [localizationsDelegates], which collectively define all of the localized
+  ///    resources used by this app.
+  final Iterable<Locale> supportedLocales;
+
+  /// Turns on a performance overlay.
+  ///
+  /// See also:
+  ///
+  ///  * <https://flutter.dev/debugging/#performanceoverlay>
+  final bool showPerformanceOverlay;
+
+  /// Checkerboards raster cache images.
+  ///
+  /// See [PerformanceOverlay.checkerboardRasterCacheImages].
+  final bool checkerboardRasterCacheImages;
+
+  /// Checkerboards layers rendered to offscreen bitmaps.
+  ///
+  /// See [PerformanceOverlay.checkerboardOffscreenLayers].
+  final bool checkerboardOffscreenLayers;
+
+  /// Turns on an overlay that shows the accessibility information
+  /// reported by the framework.
+  final bool showSemanticsDebugger;
+
+  /// Turns on an overlay that enables inspecting the widget tree.
+  ///
+  /// The inspector is only available in checked mode as it depends on
+  /// [RenderObject.debugDescribeChildren] which should not be called outside of
+  /// checked mode.
+  final bool debugShowWidgetInspector;
+
+  /// Builds the widget the [WidgetInspector] uses to switch between view and
+  /// inspect modes.
+  ///
+  /// This lets [MaterialApp] to use a material button to toggle the inspector
+  /// select mode without requiring [WidgetInspector] to depend on the
+  /// material package.
+  final InspectorSelectButtonBuilder? inspectorSelectButtonBuilder;
+
+  /// {@template flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
+  /// Turns on a little "DEBUG" banner in checked mode to indicate
+  /// that the app is in checked mode. This is on by default (in
+  /// checked mode), to turn it off, set the constructor argument to
+  /// false. In release mode this has no effect.
+  ///
+  /// To get this banner in your application if you're not using
+  /// WidgetsApp, include a [CheckedModeBanner] widget in your app.
+  ///
+  /// This banner is intended to deter people from complaining that your
+  /// app is slow when it's in checked mode. In checked mode, Flutter
+  /// enables a large number of expensive diagnostics to aid in
+  /// development, and so performance in checked mode is not
+  /// representative of what will happen in release mode.
+  /// {@endtemplate}
+  final bool debugShowCheckedModeBanner;
+
+  /// {@template flutter.widgets.widgetsApp.shortcuts}
+  /// The default map of keyboard shortcuts to intents for the application.
+  ///
+  /// By default, this is set to [WidgetsApp.defaultShortcuts].
+  /// {@endtemplate}
+  ///
+  /// {@tool snippet}
+  /// This example shows how to add a single shortcut for
+  /// [LogicalKeyboardKey.select] to the default shortcuts without needing to
+  /// add your own [Shortcuts] widget.
+  ///
+  /// Alternatively, you could insert a [Shortcuts] widget with just the mapping
+  /// you want to add between the [WidgetsApp] and its child and get the same
+  /// effect.
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return WidgetsApp(
+  ///     shortcuts: <LogicalKeySet, Intent>{
+  ///       ... WidgetsApp.defaultShortcuts,
+  ///       LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
+  ///     },
+  ///     color: const Color(0xFFFF0000),
+  ///     builder: (BuildContext context, Widget child) {
+  ///       return const Placeholder();
+  ///     },
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@template flutter.widgets.widgetsApp.shortcuts.seeAlso}
+  /// See also:
+  ///
+  ///  * [LogicalKeySet], a set of [LogicalKeyboardKey]s that make up the keys
+  ///    for this map.
+  ///  * The [Shortcuts] widget, which defines a keyboard mapping.
+  ///  * The [Actions] widget, which defines the mapping from intent to action.
+  ///  * The [Intent] and [Action] classes, which allow definition of new
+  ///    actions.
+  /// {@endtemplate}
+  final Map<LogicalKeySet, Intent>? shortcuts;
+
+  /// {@template flutter.widgets.widgetsApp.actions}
+  /// The default map of intent keys to actions for the application.
+  ///
+  /// By default, this is the output of [WidgetsApp.defaultActions], called with
+  /// [defaultTargetPlatform]. Specifying [actions] for an app overrides the
+  /// default, so if you wish to modify the default [actions], you can call
+  /// [WidgetsApp.defaultActions] and modify the resulting map, passing it as
+  /// the [actions] for this app. You may also add to the bindings, or override
+  /// specific bindings for a widget subtree, by adding your own [Actions]
+  /// widget.
+  /// {@endtemplate}
+  ///
+  /// {@tool snippet}
+  /// This example shows how to add a single action handling an
+  /// [ActivateAction] to the default actions without needing to
+  /// add your own [Actions] widget.
+  ///
+  /// Alternatively, you could insert a [Actions] widget with just the mapping
+  /// you want to add between the [WidgetsApp] and its child and get the same
+  /// effect.
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return WidgetsApp(
+  ///     actions: <Type, Action<Intent>>{
+  ///       ... WidgetsApp.defaultActions,
+  ///       ActivateAction: CallbackAction(
+  ///         onInvoke: (Intent intent) {
+  ///           // Do something here...
+  ///           return null;
+  ///         },
+  ///       ),
+  ///     },
+  ///     color: const Color(0xFFFF0000),
+  ///     builder: (BuildContext context, Widget child) {
+  ///       return const Placeholder();
+  ///     },
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@template flutter.widgets.widgetsApp.actions.seeAlso}
+  /// See also:
+  ///
+  ///  * The [shortcuts] parameter, which defines the default set of shortcuts
+  ///    for the application.
+  ///  * The [Shortcuts] widget, which defines a keyboard mapping.
+  ///  * The [Actions] widget, which defines the mapping from intent to action.
+  ///  * The [Intent] and [Action] classes, which allow definition of new
+  ///    actions.
+  /// {@endtemplate}
+  final Map<Type, Action<Intent>>? actions;
+
+  /// {@template flutter.widgets.widgetsApp.restorationScopeId}
+  /// The identifier to use for state restoration of this app.
+  ///
+  /// Providing a restoration ID inserts a [RootRestorationScope] into the
+  /// widget hierarchy, which enables state restoration for descendant widgets.
+  ///
+  /// Providing a restoration ID also enables the [Navigator] built by the
+  /// [WidgetsApp] to restore its state (i.e. to restore the history stack of
+  /// active [Route]s). See the documentation on [Navigator] for more details
+  /// around state restoration of [Route]s.
+  ///
+  /// See also:
+  ///
+  ///  * [RestorationManager], which explains how state restoration works in
+  ///    Flutter.
+  /// {@endtemplate}
+  final String? restorationScopeId;
+
+  /// If true, forces the performance overlay to be visible in all instances.
+  ///
+  /// Used by the `showPerformanceOverlay` observatory extension.
+  static bool showPerformanceOverlayOverride = false;
+
+  /// If true, forces the widget inspector to be visible.
+  ///
+  /// Used by the `debugShowWidgetInspector` debugging extension.
+  ///
+  /// The inspector allows you to select a location on your device or emulator
+  /// and view what widgets and render objects associated with it. An outline of
+  /// the selected widget and some summary information is shown on device and
+  /// more detailed information is shown in the IDE or Observatory.
+  static bool debugShowWidgetInspectorOverride = false;
+
+  /// If false, prevents the debug banner from being visible.
+  ///
+  /// Used by the `debugAllowBanner` observatory extension.
+  ///
+  /// This is how `flutter run` turns off the banner when you take a screen shot
+  /// with "s".
+  static bool debugAllowBannerOverride = true;
+
+  static final Map<LogicalKeySet, Intent> _defaultShortcuts = <LogicalKeySet, Intent>{
+    // Activation
+    LogicalKeySet(LogicalKeyboardKey.enter): const ActivateIntent(),
+    LogicalKeySet(LogicalKeyboardKey.space): const ActivateIntent(),
+    LogicalKeySet(LogicalKeyboardKey.gameButtonA): const ActivateIntent(),
+
+    // Dismissal
+    LogicalKeySet(LogicalKeyboardKey.escape): const DismissIntent(),
+
+    // Keyboard traversal.
+    LogicalKeySet(LogicalKeyboardKey.tab): const NextFocusIntent(),
+    LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.tab): const PreviousFocusIntent(),
+    LogicalKeySet(LogicalKeyboardKey.arrowLeft): const DirectionalFocusIntent(TraversalDirection.left),
+    LogicalKeySet(LogicalKeyboardKey.arrowRight): const DirectionalFocusIntent(TraversalDirection.right),
+    LogicalKeySet(LogicalKeyboardKey.arrowDown): const DirectionalFocusIntent(TraversalDirection.down),
+    LogicalKeySet(LogicalKeyboardKey.arrowUp): const DirectionalFocusIntent(TraversalDirection.up),
+
+    // Scrolling
+    LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowUp): const ScrollIntent(direction: AxisDirection.up),
+    LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowDown): const ScrollIntent(direction: AxisDirection.down),
+    LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowLeft): const ScrollIntent(direction: AxisDirection.left),
+    LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowRight): const ScrollIntent(direction: AxisDirection.right),
+    LogicalKeySet(LogicalKeyboardKey.pageUp): const ScrollIntent(direction: AxisDirection.up, type: ScrollIncrementType.page),
+    LogicalKeySet(LogicalKeyboardKey.pageDown): const ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page),
+  };
+
+  // Default shortcuts for the web platform.
+  static final Map<LogicalKeySet, Intent> _defaultWebShortcuts = <LogicalKeySet, Intent>{
+    // Activation
+    LogicalKeySet(LogicalKeyboardKey.space): const ActivateIntent(),
+    // On the web, enter activates buttons, but not other controls.
+    LogicalKeySet(LogicalKeyboardKey.enter): const ButtonActivateIntent(),
+
+    // Dismissal
+    LogicalKeySet(LogicalKeyboardKey.escape): const DismissIntent(),
+
+    // Keyboard traversal.
+    LogicalKeySet(LogicalKeyboardKey.tab): const NextFocusIntent(),
+    LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.tab): const PreviousFocusIntent(),
+
+    // Scrolling
+    LogicalKeySet(LogicalKeyboardKey.arrowUp): const ScrollIntent(direction: AxisDirection.up),
+    LogicalKeySet(LogicalKeyboardKey.arrowDown): const ScrollIntent(direction: AxisDirection.down),
+    LogicalKeySet(LogicalKeyboardKey.arrowLeft): const ScrollIntent(direction: AxisDirection.left),
+    LogicalKeySet(LogicalKeyboardKey.arrowRight): const ScrollIntent(direction: AxisDirection.right),
+    LogicalKeySet(LogicalKeyboardKey.pageUp): const ScrollIntent(direction: AxisDirection.up, type: ScrollIncrementType.page),
+    LogicalKeySet(LogicalKeyboardKey.pageDown): const ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page),
+  };
+
+  // Default shortcuts for the macOS platform.
+  static final Map<LogicalKeySet, Intent> _defaultAppleOsShortcuts = <LogicalKeySet, Intent>{
+    // Activation
+    LogicalKeySet(LogicalKeyboardKey.enter): const ActivateIntent(),
+    LogicalKeySet(LogicalKeyboardKey.space): const ActivateIntent(),
+
+    // Dismissal
+    LogicalKeySet(LogicalKeyboardKey.escape): const DismissIntent(),
+
+    // Keyboard traversal
+    LogicalKeySet(LogicalKeyboardKey.tab): const NextFocusIntent(),
+    LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.tab): const PreviousFocusIntent(),
+    LogicalKeySet(LogicalKeyboardKey.arrowLeft): const DirectionalFocusIntent(TraversalDirection.left),
+    LogicalKeySet(LogicalKeyboardKey.arrowRight): const DirectionalFocusIntent(TraversalDirection.right),
+    LogicalKeySet(LogicalKeyboardKey.arrowDown): const DirectionalFocusIntent(TraversalDirection.down),
+    LogicalKeySet(LogicalKeyboardKey.arrowUp): const DirectionalFocusIntent(TraversalDirection.up),
+
+    // Scrolling
+    LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.arrowUp): const ScrollIntent(direction: AxisDirection.up),
+    LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.arrowDown): const ScrollIntent(direction: AxisDirection.down),
+    LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.arrowLeft): const ScrollIntent(direction: AxisDirection.left),
+    LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.arrowRight): const ScrollIntent(direction: AxisDirection.right),
+    LogicalKeySet(LogicalKeyboardKey.pageUp): const ScrollIntent(direction: AxisDirection.up, type: ScrollIncrementType.page),
+    LogicalKeySet(LogicalKeyboardKey.pageDown): const ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page),
+  };
+
+  /// Generates the default shortcut key bindings based on the
+  /// [defaultTargetPlatform].
+  ///
+  /// Used by [WidgetsApp] to assign a default value to [WidgetsApp.shortcuts].
+  static Map<LogicalKeySet, Intent> get defaultShortcuts {
+    if (kIsWeb) {
+      return _defaultWebShortcuts;
+    }
+
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        return _defaultShortcuts;
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        return _defaultAppleOsShortcuts;
+    }
+  }
+
+  /// The default value of [WidgetsApp.actions].
+  static Map<Type, Action<Intent>> defaultActions = <Type, Action<Intent>>{
+    DoNothingIntent: DoNothingAction(),
+    DoNothingAndStopPropagationIntent: DoNothingAction(consumesKey: false),
+    RequestFocusIntent: RequestFocusAction(),
+    NextFocusIntent: NextFocusAction(),
+    PreviousFocusIntent: PreviousFocusAction(),
+    DirectionalFocusIntent: DirectionalFocusAction(),
+    ScrollIntent: ScrollAction(),
+  };
+
+  @override
+  _WidgetsAppState createState() => _WidgetsAppState();
+}
+
+class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
+  // STATE LIFECYCLE
+
+  // If window.defaultRouteName isn't '/', we should assume it was set
+  // intentionally via `setInitialRoute`, and should override whatever is in
+  // [widget.initialRoute].
+  String get _initialRouteName => WidgetsBinding.instance!.window.defaultRouteName != Navigator.defaultRouteName
+    ? WidgetsBinding.instance!.window.defaultRouteName
+    : widget.initialRoute ?? WidgetsBinding.instance!.window.defaultRouteName;
+
+  @override
+  void initState() {
+    super.initState();
+    _updateRouting();
+    _locale = _resolveLocales(WidgetsBinding.instance!.window.locales, widget.supportedLocales);
+    WidgetsBinding.instance!.addObserver(this);
+  }
+
+  @override
+  void didUpdateWidget(WidgetsApp oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    _updateRouting(oldWidget: oldWidget);
+  }
+
+  @override
+  void dispose() {
+    WidgetsBinding.instance!.removeObserver(this);
+    _defaultRouteInformationProvider?.dispose();
+    super.dispose();
+  }
+
+  void _updateRouting({WidgetsApp? oldWidget}) {
+    if (_usesRouter) {
+      assert(!_usesNavigator);
+      _navigator = null;
+      if (oldWidget == null || oldWidget.routeInformationProvider != widget.routeInformationProvider) {
+        _defaultRouteInformationProvider?.dispose();
+        _defaultRouteInformationProvider = null;
+        if (widget.routeInformationProvider == null) {
+          _defaultRouteInformationProvider = PlatformRouteInformationProvider(
+            initialRouteInformation: RouteInformation(
+              location: _initialRouteName,
+            ),
+          );
+        }
+      }
+    } else if (_usesNavigator) {
+      assert(!_usesRouter);
+      _defaultRouteInformationProvider?.dispose();
+      _defaultRouteInformationProvider = null;
+      if (oldWidget == null || widget.navigatorKey != oldWidget.navigatorKey) {
+        _navigator = widget.navigatorKey ?? GlobalObjectKey<NavigatorState>(this);
+      }
+      assert(_navigator != null);
+    } else {
+      assert(widget.builder != null);
+      assert(!_usesRouter);
+      assert(!_usesNavigator);
+      _navigator = null;
+      _defaultRouteInformationProvider?.dispose();
+      _defaultRouteInformationProvider = null;
+    }
+    // If we use a navigator, we have a navigator key.
+    assert(_usesNavigator == (_navigator != null));
+  }
+
+  bool get _usesRouter => widget.routerDelegate != null;
+  bool get _usesNavigator => widget.home != null || widget.routes?.isNotEmpty == true || widget.onGenerateRoute != null || widget.onUnknownRoute != null;
+
+  // ROUTER
+
+  RouteInformationProvider? get _effectiveRouteInformationProvider => widget.routeInformationProvider ?? _defaultRouteInformationProvider;
+  PlatformRouteInformationProvider? _defaultRouteInformationProvider;
+
+  // NAVIGATOR
+
+  GlobalKey<NavigatorState>? _navigator;
+
+  Route<dynamic>? _onGenerateRoute(RouteSettings settings) {
+    final String? name = settings.name;
+    final WidgetBuilder? pageContentBuilder = name == Navigator.defaultRouteName && widget.home != null
+        ? (BuildContext context) => widget.home!
+        : widget.routes![name];
+
+    if (pageContentBuilder != null) {
+      assert(widget.pageRouteBuilder != null,
+        'The default onGenerateRoute handler for WidgetsApp must have a '
+        'pageRouteBuilder set if the home or routes properties are set.');
+      final Route<dynamic> route = widget.pageRouteBuilder!<dynamic>(
+        settings,
+        pageContentBuilder,
+      );
+      assert(route != null,
+        'The pageRouteBuilder for WidgetsApp must return a valid non-null Route.');
+      return route;
+    }
+    if (widget.onGenerateRoute != null)
+      return widget.onGenerateRoute!(settings);
+    return null;
+  }
+
+  Route<dynamic> _onUnknownRoute(RouteSettings settings) {
+    assert(() {
+      if (widget.onUnknownRoute == null) {
+        throw FlutterError(
+          'Could not find a generator for route $settings in the $runtimeType.\n'
+          'Make sure your root app widget has provided a way to generate \n'
+          'this route.\n'
+          'Generators for routes are searched for in the following order:\n'
+          ' 1. For the "/" route, the "home" property, if non-null, is used.\n'
+          ' 2. Otherwise, the "routes" table is used, if it has an entry for '
+          'the route.\n'
+          ' 3. Otherwise, onGenerateRoute is called. It should return a '
+          'non-null value for any valid route not handled by "home" and "routes".\n'
+          ' 4. Finally if all else fails onUnknownRoute is called.\n'
+          'Unfortunately, onUnknownRoute was not set.'
+        );
+      }
+      return true;
+    }());
+    final Route<dynamic>? result = widget.onUnknownRoute!(settings);
+    assert(() {
+      if (result == null) {
+        throw FlutterError(
+          'The onUnknownRoute callback returned null.\n'
+          'When the $runtimeType requested the route $settings from its '
+          'onUnknownRoute callback, the callback returned null. Such callbacks '
+          'must never return null.'
+        );
+      }
+      return true;
+    }());
+    return result!;
+  }
+
+  // On Android: the user has pressed the back button.
+  @override
+  Future<bool> didPopRoute() async {
+    assert(mounted);
+    // The back button dispatcher should handle the pop route if we use a
+    // router.
+    if (_usesRouter)
+      return false;
+
+    final NavigatorState? navigator = _navigator?.currentState;
+    if (navigator == null)
+      return false;
+    return await navigator.maybePop();
+  }
+
+  @override
+  Future<bool> didPushRoute(String route) async {
+    assert(mounted);
+    // The route name provider should handle the push route if we uses a
+    // router.
+    if (_usesRouter)
+      return false;
+
+    final NavigatorState? navigator = _navigator?.currentState;
+    if (navigator == null)
+      return false;
+    navigator.pushNamed(route);
+    return true;
+  }
+
+  // LOCALIZATION
+
+  /// This is the resolved locale, and is one of the supportedLocales.
+  Locale? _locale;
+
+  Locale _resolveLocales(List<Locale>? preferredLocales, Iterable<Locale> supportedLocales) {
+    // Attempt to use localeListResolutionCallback.
+    if (widget.localeListResolutionCallback != null) {
+      final Locale? locale = widget.localeListResolutionCallback!(preferredLocales, widget.supportedLocales);
+      if (locale != null)
+        return locale;
+    }
+    // localeListResolutionCallback failed, falling back to localeResolutionCallback.
+    if (widget.localeResolutionCallback != null) {
+      final Locale? locale = widget.localeResolutionCallback!(
+        preferredLocales != null && preferredLocales.isNotEmpty ? preferredLocales.first : null,
+        widget.supportedLocales,
+      );
+      if (locale != null)
+        return locale;
+    }
+    // Both callbacks failed, falling back to default algorithm.
+    return basicLocaleListResolution(preferredLocales, supportedLocales);
+  }
+
+  /// The default locale resolution algorithm.
+  ///
+  /// Custom resolution algorithms can be provided through
+  /// [WidgetsApp.localeListResolutionCallback] or
+  /// [WidgetsApp.localeResolutionCallback].
+  ///
+  /// When no custom locale resolution algorithms are provided or if both fail
+  /// to resolve, Flutter will default to calling this algorithm.
+  ///
+  /// This algorithm prioritizes speed at the cost of slightly less appropriate
+  /// resolutions for edge cases.
+  ///
+  /// This algorithm will resolve to the earliest preferred locale that
+  /// matches the most fields, prioritizing in the order of perfect match,
+  /// languageCode+countryCode, languageCode+scriptCode, languageCode-only.
+  ///
+  /// In the case where a locale is matched by languageCode-only and is not the
+  /// default (first) locale, the next preferred locale with a
+  /// perfect match can supersede the languageCode-only match if it exists.
+  ///
+  /// When a preferredLocale matches more than one supported locale, it will
+  /// resolve to the first matching locale listed in the supportedLocales.
+  ///
+  /// When all preferred locales have been exhausted without a match, the first
+  /// countryCode only match will be returned.
+  ///
+  /// When no match at all is found, the first (default) locale in
+  /// [supportedLocales] will be returned.
+  ///
+  /// To summarize, the main matching priority is:
+  ///
+  ///  1. [Locale.languageCode], [Locale.scriptCode], and [Locale.countryCode]
+  ///  1. [Locale.languageCode] and [Locale.scriptCode] only
+  ///  1. [Locale.languageCode] and [Locale.countryCode] only
+  ///  1. [Locale.languageCode] only (with caveats, see above)
+  ///  1. [Locale.countryCode] only when all [preferredLocales] fail to match
+  ///  1. Returns the first element of [supportedLocales] as a fallback
+  ///
+  /// This algorithm does not take language distance (how similar languages are to each other)
+  /// into account, and will not handle edge cases such as resolving `de` to `fr` rather than `zh`
+  /// when `de` is not supported and `zh` is listed before `fr` (German is closer to French
+  /// than Chinese).
+  static Locale basicLocaleListResolution(List<Locale>? preferredLocales, Iterable<Locale> supportedLocales) {
+    // preferredLocales can be null when called before the platform has had a chance to
+    // initialize the locales. Platforms without locale passing support will provide an empty list.
+    // We default to the first supported locale in these cases.
+    if (preferredLocales == null || preferredLocales.isEmpty) {
+      return supportedLocales.first;
+    }
+    // Hash the supported locales because apps can support many locales and would
+    // be expensive to search through them many times.
+    final Map<String, Locale> allSupportedLocales = HashMap<String, Locale>();
+    final Map<String, Locale> languageAndCountryLocales = HashMap<String, Locale>();
+    final Map<String, Locale> languageAndScriptLocales = HashMap<String, Locale>();
+    final Map<String, Locale> languageLocales = HashMap<String, Locale>();
+    final Map<String?, Locale> countryLocales = HashMap<String?, Locale>();
+    for (final Locale locale in supportedLocales) {
+      allSupportedLocales['${locale.languageCode}_${locale.scriptCode}_${locale.countryCode}'] ??= locale;
+      languageAndScriptLocales['${locale.languageCode}_${locale.scriptCode}'] ??= locale;
+      languageAndCountryLocales['${locale.languageCode}_${locale.countryCode}'] ??= locale;
+      languageLocales[locale.languageCode] ??= locale;
+      countryLocales[locale.countryCode] ??= locale;
+    }
+
+    // Since languageCode-only matches are possibly low quality, we don't return
+    // it instantly when we find such a match. We check to see if the next
+    // preferred locale in the list has a high accuracy match, and only return
+    // the languageCode-only match when a higher accuracy match in the next
+    // preferred locale cannot be found.
+    Locale? matchesLanguageCode;
+    Locale? matchesCountryCode;
+    // Loop over user's preferred locales
+    for (int localeIndex = 0; localeIndex < preferredLocales.length; localeIndex += 1) {
+      final Locale userLocale = preferredLocales[localeIndex];
+      // Look for perfect match.
+      if (allSupportedLocales.containsKey('${userLocale.languageCode}_${userLocale.scriptCode}_${userLocale.countryCode}')) {
+        return userLocale;
+      }
+      // Look for language+script match.
+      if (userLocale.scriptCode != null) {
+        final Locale? match = languageAndScriptLocales['${userLocale.languageCode}_${userLocale.scriptCode}'];
+        if (match != null) {
+          return match;
+        }
+      }
+      // Look for language+country match.
+      if (userLocale.countryCode != null) {
+        final Locale? match = languageAndCountryLocales['${userLocale.languageCode}_${userLocale.countryCode}'];
+        if (match != null) {
+          return match;
+        }
+      }
+      // If there was a languageCode-only match in the previous iteration's higher
+      // ranked preferred locale, we return it if the current userLocale does not
+      // have a better match.
+      if (matchesLanguageCode != null) {
+        return matchesLanguageCode;
+      }
+      // Look and store language-only match.
+      Locale? match = languageLocales[userLocale.languageCode];
+      if (match != null) {
+        matchesLanguageCode = match;
+        // Since first (default) locale is usually highly preferred, we will allow
+        // a languageCode-only match to be instantly matched. If the next preferred
+        // languageCode is the same, we defer hastily returning until the next iteration
+        // since at worst it is the same and at best an improved match.
+        if (localeIndex == 0 &&
+            !(localeIndex + 1 < preferredLocales.length && preferredLocales[localeIndex + 1].languageCode == userLocale.languageCode)) {
+          return matchesLanguageCode;
+        }
+      }
+      // countryCode-only match. When all else except default supported locale fails,
+      // attempt to match by country only, as a user is likely to be familiar with a
+      // language from their listed country.
+      if (matchesCountryCode == null && userLocale.countryCode != null) {
+        match = countryLocales[userLocale.countryCode];
+        if (match != null) {
+          matchesCountryCode = match;
+        }
+      }
+    }
+    // When there is no languageCode-only match. Fallback to matching countryCode only. Country
+    // fallback only applies on iOS. When there is no countryCode-only match, we return first
+    // supported locale.
+    final Locale resolvedLocale = matchesLanguageCode ?? matchesCountryCode ?? supportedLocales.first;
+    return resolvedLocale;
+  }
+
+  @override
+  void didChangeLocales(List<Locale>? locales) {
+    final Locale newLocale = _resolveLocales(locales, widget.supportedLocales);
+    if (newLocale != _locale) {
+      setState(() {
+        _locale = newLocale;
+      });
+    }
+  }
+
+  // Combine the Localizations for Widgets with the ones contributed
+  // by the localizationsDelegates parameter, if any. Only the first delegate
+  // of a particular LocalizationsDelegate.type is loaded so the
+  // localizationsDelegate parameter can be used to override
+  // WidgetsLocalizations.delegate.
+  Iterable<LocalizationsDelegate<dynamic>> get _localizationsDelegates sync* {
+    if (widget.localizationsDelegates != null)
+      yield* widget.localizationsDelegates!;
+    yield DefaultWidgetsLocalizations.delegate;
+  }
+
+  // BUILDER
+
+  bool _debugCheckLocalizations(Locale appLocale) {
+    assert(() {
+      final Set<Type> unsupportedTypes =
+        _localizationsDelegates.map<Type>((LocalizationsDelegate<dynamic> delegate) => delegate.type).toSet();
+      for (final LocalizationsDelegate<dynamic> delegate in _localizationsDelegates) {
+        if (!unsupportedTypes.contains(delegate.type))
+          continue;
+        if (delegate.isSupported(appLocale))
+          unsupportedTypes.remove(delegate.type);
+      }
+      if (unsupportedTypes.isEmpty)
+        return true;
+
+      // Currently the Cupertino library only provides english localizations.
+      // Remove this when https://github.com/flutter/flutter/issues/23847
+      // is fixed.
+      if (listEquals(unsupportedTypes.map((Type type) => type.toString()).toList(), <String>['CupertinoLocalizations']))
+        return true;
+
+      final StringBuffer message = StringBuffer();
+      message.writeln('\u2550' * 8);
+      message.writeln(
+        "Warning: This application's locale, $appLocale, is not supported by all of its\n"
+        'localization delegates.'
+      );
+      for (final Type unsupportedType in unsupportedTypes) {
+        // Currently the Cupertino library only provides english localizations.
+        // Remove this when https://github.com/flutter/flutter/issues/23847
+        // is fixed.
+        if (unsupportedType.toString() == 'CupertinoLocalizations')
+          continue;
+        message.writeln(
+          '> A $unsupportedType delegate that supports the $appLocale locale was not found.'
+        );
+      }
+      message.writeln(
+        'See https://flutter.dev/tutorials/internationalization/ for more\n'
+        "information about configuring an app's locale, supportedLocales,\n"
+        'and localizationsDelegates parameters.'
+      );
+      message.writeln('\u2550' * 8);
+      debugPrint(message.toString());
+      return true;
+    }());
+    return true;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    Widget? routing;
+    if (_usesRouter) {
+      assert(_effectiveRouteInformationProvider != null);
+      routing = Router<Object>(
+        routeInformationProvider: _effectiveRouteInformationProvider,
+        routeInformationParser: widget.routeInformationParser,
+        routerDelegate: widget.routerDelegate!,
+        backButtonDispatcher: widget.backButtonDispatcher,
+      );
+    } else if (_usesNavigator) {
+      assert(_navigator != null);
+      routing = Navigator(
+        restorationScopeId: 'nav',
+        key: _navigator,
+        initialRoute: _initialRouteName,
+        onGenerateRoute: _onGenerateRoute,
+        onGenerateInitialRoutes: widget.onGenerateInitialRoutes == null
+          ? Navigator.defaultGenerateInitialRoutes
+          : (NavigatorState navigator, String initialRouteName) {
+            return widget.onGenerateInitialRoutes!(initialRouteName);
+          },
+        onUnknownRoute: _onUnknownRoute,
+        observers: widget.navigatorObservers!,
+        reportsRouteUpdateToEngine: true,
+      );
+    }
+
+    Widget result;
+    if (widget.builder != null) {
+      result = Builder(
+        builder: (BuildContext context) {
+          return widget.builder!(context, routing);
+        },
+      );
+    } else {
+      assert(routing != null);
+      result = routing!;
+    }
+
+    if (widget.textStyle != null) {
+      result = DefaultTextStyle(
+        style: widget.textStyle!,
+        child: result,
+      );
+    }
+
+    PerformanceOverlay? performanceOverlay;
+    // We need to push a performance overlay if any of the display or checkerboarding
+    // options are set.
+    if (widget.showPerformanceOverlay || WidgetsApp.showPerformanceOverlayOverride) {
+      performanceOverlay = PerformanceOverlay.allEnabled(
+        checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
+        checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
+      );
+    } else if (widget.checkerboardRasterCacheImages || widget.checkerboardOffscreenLayers) {
+      performanceOverlay = PerformanceOverlay(
+        checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
+        checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
+      );
+    }
+    if (performanceOverlay != null) {
+      result = Stack(
+        children: <Widget>[
+          result,
+          Positioned(top: 0.0, left: 0.0, right: 0.0, child: performanceOverlay),
+        ],
+      );
+    }
+
+    if (widget.showSemanticsDebugger) {
+      result = SemanticsDebugger(
+        child: result,
+      );
+    }
+
+    assert(() {
+      if (widget.debugShowWidgetInspector || WidgetsApp.debugShowWidgetInspectorOverride) {
+        result = WidgetInspector(
+          child: result,
+          selectButtonBuilder: widget.inspectorSelectButtonBuilder,
+        );
+      }
+      if (widget.debugShowCheckedModeBanner && WidgetsApp.debugAllowBannerOverride) {
+        result = CheckedModeBanner(
+          child: result,
+        );
+      }
+      return true;
+    }());
+
+    final Widget title;
+    if (widget.onGenerateTitle != null) {
+      title = Builder(
+        // This Builder exists to provide a context below the Localizations widget.
+        // The onGenerateTitle callback can refer to Localizations via its context
+        // parameter.
+        builder: (BuildContext context) {
+          final String title = widget.onGenerateTitle!(context);
+          assert(title != null, 'onGenerateTitle must return a non-null String');
+          return Title(
+            title: title,
+            color: widget.color,
+            child: result,
+          );
+        },
+      );
+    } else {
+      title = Title(
+        title: widget.title,
+        color: widget.color,
+        child: result,
+      );
+    }
+
+    final Locale appLocale = widget.locale != null
+      ? _resolveLocales(<Locale>[widget.locale!], widget.supportedLocales)
+      : _locale!;
+
+    assert(_debugCheckLocalizations(appLocale));
+    return RootRestorationScope(
+      restorationId: widget.restorationScopeId,
+      child: Shortcuts(
+        shortcuts: widget.shortcuts ?? WidgetsApp.defaultShortcuts,
+        debugLabel: '<Default WidgetsApp Shortcuts>',
+        child: Actions(
+          actions: widget.actions ?? WidgetsApp.defaultActions,
+          child: FocusTraversalGroup(
+            policy: ReadingOrderTraversalPolicy(),
+            child: _MediaQueryFromWindow(
+              child: Localizations(
+                locale: appLocale,
+                delegates: _localizationsDelegates.toList(),
+                child: title,
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+/// Builds [MediaQuery] from `window` by listening to [WidgetsBinding].
+///
+/// It is performed in a standalone widget to rebuild **only** [MediaQuery] and
+/// its dependents when `window` changes, instead of rebuilding the entire widget tree.
+class _MediaQueryFromWindow extends StatefulWidget {
+  const _MediaQueryFromWindow({Key? key, required this.child}) : super(key: key);
+
+  final Widget child;
+
+  @override
+  _MediaQueryFromWindowsState createState() => _MediaQueryFromWindowsState();
+}
+
+class _MediaQueryFromWindowsState extends State<_MediaQueryFromWindow> with WidgetsBindingObserver {
+  @override
+  void initState() {
+    super.initState();
+    WidgetsBinding.instance!.addObserver(this);
+  }
+
+  // ACCESSIBILITY
+
+  @override
+  void didChangeAccessibilityFeatures() {
+    setState(() {
+      // The properties of window have changed. We use them in our build
+      // function, so we need setState(), but we don't cache anything locally.
+    });
+  }
+
+  // METRICS
+
+  @override
+  void didChangeMetrics() {
+    setState(() {
+      // The properties of window have changed. We use them in our build
+      // function, so we need setState(), but we don't cache anything locally.
+    });
+  }
+
+  @override
+  void didChangeTextScaleFactor() {
+    setState(() {
+      // The textScaleFactor property of window has changed. We reference
+      // window in our build function, so we need to call setState(), but
+      // we don't need to cache anything locally.
+    });
+  }
+
+  // RENDERING
+  @override
+  void didChangePlatformBrightness() {
+    setState(() {
+      // The platformBrightness property of window has changed. We reference
+      // window in our build function, so we need to call setState(), but
+      // we don't need to cache anything locally.
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
+    if (!kReleaseMode) {
+      data = data.copyWith(platformBrightness: debugBrightnessOverride);
+    }
+    return MediaQuery(
+      data: data,
+      child: widget.child,
+    );
+  }
+
+  @override
+  void dispose() {
+    WidgetsBinding.instance!.removeObserver(this);
+    super.dispose();
+  }
+}
diff --git a/lib/src/widgets/async.dart b/lib/src/widgets/async.dart
new file mode 100644
index 0000000..fdeee00
--- /dev/null
+++ b/lib/src/widgets/async.dart
@@ -0,0 +1,810 @@
+// Copyright 2014 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.
+
+/// Widgets that handle interaction with asynchronous computations.
+///
+/// Asynchronous computations are represented by [Future]s and [Stream]s.
+
+import 'dart:async' show Future, Stream, StreamSubscription;
+
+import 'package:flute/foundation.dart';
+
+import 'framework.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// dynamic _lot;
+// Future<String> _calculation;
+
+/// Base class for widgets that build themselves based on interaction with
+/// a specified [Stream].
+///
+/// A [StreamBuilderBase] is stateful and maintains a summary of the interaction
+/// so far. The type of the summary and how it is updated with each interaction
+/// is defined by sub-classes.
+///
+/// Examples of summaries include:
+///
+/// * the running average of a stream of integers;
+/// * the current direction and speed based on a stream of geolocation data;
+/// * a graph displaying data points from a stream.
+///
+/// In general, the summary is the result of a fold computation over the data
+/// items and errors received from the stream along with pseudo-events
+/// representing termination or change of stream. The initial summary is
+/// specified by sub-classes by overriding [initial]. The summary updates on
+/// receipt of stream data and errors are specified by overriding [afterData] and
+/// [afterError], respectively. If needed, the summary may be updated on stream
+/// termination by overriding [afterDone]. Finally, the summary may be updated
+/// on change of stream by overriding [afterDisconnected] and [afterConnected].
+///
+/// `T` is the type of stream events.
+///
+/// `S` is the type of interaction summary.
+///
+/// See also:
+///
+///  * [StreamBuilder], which is specialized for the case where only the most
+///    recent interaction is needed for widget building.
+abstract class StreamBuilderBase<T, S> extends StatefulWidget {
+  /// Creates a [StreamBuilderBase] connected to the specified [stream].
+  const StreamBuilderBase({ Key? key, this.stream }) : super(key: key);
+
+  /// The asynchronous computation to which this builder is currently connected,
+  /// possibly null. When changed, the current summary is updated using
+  /// [afterDisconnected], if the previous stream was not null, followed by
+  /// [afterConnected], if the new stream is not null.
+  final Stream<T>? stream;
+
+  /// Returns the initial summary of stream interaction, typically representing
+  /// the fact that no interaction has happened at all.
+  ///
+  /// Sub-classes must override this method to provide the initial value for
+  /// the fold computation.
+  S initial();
+
+  /// Returns an updated version of the [current] summary reflecting that we
+  /// are now connected to a stream.
+  ///
+  /// The default implementation returns [current] as is.
+  S afterConnected(S current) => current;
+
+  /// Returns an updated version of the [current] summary following a data event.
+  ///
+  /// Sub-classes must override this method to specify how the current summary
+  /// is combined with the new data item in the fold computation.
+  S afterData(S current, T data);
+
+  /// Returns an updated version of the [current] summary following an error
+  /// with a stack trace.
+  ///
+  /// The default implementation returns [current] as is.
+  S afterError(S current, Object error, StackTrace stackTrace) => current;
+
+  /// Returns an updated version of the [current] summary following stream
+  /// termination.
+  ///
+  /// The default implementation returns [current] as is.
+  S afterDone(S current) => current;
+
+  /// Returns an updated version of the [current] summary reflecting that we
+  /// are no longer connected to a stream.
+  ///
+  /// The default implementation returns [current] as is.
+  S afterDisconnected(S current) => current;
+
+  /// Returns a Widget based on the [currentSummary].
+  Widget build(BuildContext context, S currentSummary);
+
+  @override
+  State<StreamBuilderBase<T, S>> createState() => _StreamBuilderBaseState<T, S>();
+}
+
+/// State for [StreamBuilderBase].
+class _StreamBuilderBaseState<T, S> extends State<StreamBuilderBase<T, S>> {
+  StreamSubscription<T>? _subscription; // ignore: cancel_subscriptions
+  late S _summary;
+
+  @override
+  void initState() {
+    super.initState();
+    _summary = widget.initial();
+    _subscribe();
+  }
+
+  @override
+  void didUpdateWidget(StreamBuilderBase<T, S> oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.stream != widget.stream) {
+      if (_subscription != null) {
+        _unsubscribe();
+        _summary = widget.afterDisconnected(_summary);
+      }
+      _subscribe();
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) => widget.build(context, _summary);
+
+  @override
+  void dispose() {
+    _unsubscribe();
+    super.dispose();
+  }
+
+  void _subscribe() {
+    if (widget.stream != null) {
+      _subscription = widget.stream!.listen((T data) {
+        setState(() {
+          _summary = widget.afterData(_summary, data);
+        });
+      }, onError: (Object error, StackTrace stackTrace) {
+        setState(() {
+          _summary = widget.afterError(_summary, error, stackTrace);
+        });
+      }, onDone: () {
+        setState(() {
+          _summary = widget.afterDone(_summary);
+        });
+      });
+      _summary = widget.afterConnected(_summary);
+    }
+  }
+
+  void _unsubscribe() {
+    if (_subscription != null) {
+      _subscription!.cancel();
+      _subscription = null;
+    }
+  }
+}
+
+/// The state of connection to an asynchronous computation.
+///
+/// The usual flow of state is as follows:
+///
+/// 1. [none], maybe with some initial data.
+/// 2. [waiting], indicating that the asynchronous operation has begun,
+///    typically with the data being null.
+/// 3. [active], with data being non-null, and possible changing over time.
+/// 4. [done], with data being non-null.
+///
+/// See also:
+///
+///  * [AsyncSnapshot], which augments a connection state with information
+///    received from the asynchronous computation.
+enum ConnectionState {
+  /// Not currently connected to any asynchronous computation.
+  ///
+  /// For example, a [FutureBuilder] whose [FutureBuilder.future] is null.
+  none,
+
+  /// Connected to an asynchronous computation and awaiting interaction.
+  waiting,
+
+  /// Connected to an active asynchronous computation.
+  ///
+  /// For example, a [Stream] that has returned at least one value, but is not
+  /// yet done.
+  active,
+
+  /// Connected to a terminated asynchronous computation.
+  done,
+}
+
+/// Immutable representation of the most recent interaction with an asynchronous
+/// computation.
+///
+/// See also:
+///
+///  * [StreamBuilder], which builds itself based on a snapshot from interacting
+///    with a [Stream].
+///  * [FutureBuilder], which builds itself based on a snapshot from interacting
+///    with a [Future].
+@immutable
+class AsyncSnapshot<T> {
+  /// Creates an [AsyncSnapshot] with the specified [connectionState],
+  /// and optionally either [data] or [error] with an optional [stackTrace]
+  /// (but not both data and error).
+  const AsyncSnapshot._(this.connectionState, this.data, this.error, this.stackTrace)
+    : assert(connectionState != null),
+      assert(!(data != null && error != null)),
+      assert(stackTrace == null || error != null);
+
+  /// Creates an [AsyncSnapshot] in [ConnectionState.none] with null data and error.
+  const AsyncSnapshot.nothing() : this._(ConnectionState.none, null, null, null);
+
+  /// Creates an [AsyncSnapshot] in [ConnectionState.waiting] with null data and error.
+  const AsyncSnapshot.waiting() : this._(ConnectionState.waiting, null, null, null);
+
+  /// Creates an [AsyncSnapshot] in the specified [state] and with the specified [data].
+  const AsyncSnapshot.withData(ConnectionState state, T data): this._(state, data, null, null);
+
+  /// Creates an [AsyncSnapshot] in the specified [state] with the specified [error]
+  /// and a [stackTrace].
+  ///
+  /// If no [stackTrace] is explicitly specified, [StackTrace.empty] will be used instead.
+  const AsyncSnapshot.withError(
+    ConnectionState state,
+    Object error, [
+    StackTrace stackTrace = StackTrace.empty,
+  ]) : this._(state, null, error, stackTrace);
+
+  /// Current state of connection to the asynchronous computation.
+  final ConnectionState connectionState;
+
+  /// The latest data received by the asynchronous computation.
+  ///
+  /// If this is non-null, [hasData] will be true.
+  ///
+  /// If [error] is not null, this will be null. See [hasError].
+  ///
+  /// If the asynchronous computation has never returned a value, this may be
+  /// set to an initial data value specified by the relevant widget. See
+  /// [FutureBuilder.initialData] and [StreamBuilder.initialData].
+  final T? data;
+
+  /// Returns latest data received, failing if there is no data.
+  ///
+  /// Throws [error], if [hasError]. Throws [StateError], if neither [hasData]
+  /// nor [hasError].
+  T get requireData {
+    if (hasData)
+      return data!;
+    if (hasError)
+      throw error!;
+    throw StateError('Snapshot has neither data nor error');
+  }
+
+  /// The latest error object received by the asynchronous computation.
+  ///
+  /// If this is non-null, [hasError] will be true.
+  ///
+  /// If [data] is not null, this will be null.
+  final Object? error;
+
+  /// The latest stack trace object received by the asynchronous computation.
+  ///
+  /// This will not be null iff [error] is not null. Consequently, [stackTrace]
+  /// will be non-null when [hasError] is true.
+  ///
+  /// However, even when not null, [stackTrace] might be empty. The stack trace
+  /// is empty when there is an error but no stack trace has been provided.
+  final StackTrace? stackTrace;
+
+  /// Returns a snapshot like this one, but in the specified [state].
+  ///
+  /// The [data], [error], and [stackTrace] fields persist unmodified, even if
+  /// the new state is [ConnectionState.none].
+  AsyncSnapshot<T> inState(ConnectionState state) => AsyncSnapshot<T>._(state, data, error, stackTrace);
+
+  /// Returns whether this snapshot contains a non-null [data] value.
+  ///
+  /// This can be false even when the asynchronous computation has completed
+  /// successfully, if the computation did not return a non-null value. For
+  /// example, a [Future<void>] will complete with the null value even if it
+  /// completes successfully.
+  bool get hasData => data != null;
+
+  /// Returns whether this snapshot contains a non-null [error] value.
+  ///
+  /// This is always true if the asynchronous computation's last result was
+  /// failure.
+  bool get hasError => error != null;
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'AsyncSnapshot')}($connectionState, $data, $error, $stackTrace)';
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    return other is AsyncSnapshot<T>
+        && other.connectionState == connectionState
+        && other.data == data
+        && other.error == error
+        && other.stackTrace == stackTrace;
+  }
+
+  @override
+  int get hashCode => hashValues(connectionState, data, error);
+}
+
+/// Signature for strategies that build widgets based on asynchronous
+/// interaction.
+///
+/// See also:
+///
+///  * [StreamBuilder], which delegates to an [AsyncWidgetBuilder] to build
+///    itself based on a snapshot from interacting with a [Stream].
+///  * [FutureBuilder], which delegates to an [AsyncWidgetBuilder] to build
+///    itself based on a snapshot from interacting with a [Future].
+typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot);
+
+/// Widget that builds itself based on the latest snapshot of interaction with
+/// a [Stream].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=MkKEWHfy99Y}
+///
+/// Widget rebuilding is scheduled by each interaction, using [State.setState],
+/// but is otherwise decoupled from the timing of the stream. The [builder]
+/// is called at the discretion of the Flutter pipeline, and will thus receive a
+/// timing-dependent sub-sequence of the snapshots that represent the
+/// interaction with the stream.
+///
+/// As an example, when interacting with a stream producing the integers
+/// 0 through 9, the [builder] may be called with any ordered sub-sequence
+/// of the following snapshots that includes the last one (the one with
+/// ConnectionState.done):
+///
+/// * `AsyncSnapshot<int>.withData(ConnectionState.waiting, null)`
+/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 0)`
+/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 1)`
+/// * ...
+/// * `AsyncSnapshot<int>.withData(ConnectionState.active, 9)`
+/// * `AsyncSnapshot<int>.withData(ConnectionState.done, 9)`
+///
+/// The actual sequence of invocations of the [builder] depends on the relative
+/// timing of events produced by the stream and the build rate of the Flutter
+/// pipeline.
+///
+/// Changing the [StreamBuilder] configuration to another stream during event
+/// generation introduces snapshot pairs of the form:
+///
+/// * `AsyncSnapshot<int>.withData(ConnectionState.none, 5)`
+/// * `AsyncSnapshot<int>.withData(ConnectionState.waiting, 5)`
+///
+/// The latter will be produced only when the new stream is non-null, and the
+/// former only when the old stream is non-null.
+///
+/// The stream may produce errors, resulting in snapshots of the form:
+///
+/// * `AsyncSnapshot<int>.withError(ConnectionState.active, 'some error', someStackTrace)`
+///
+/// The data and error fields of snapshots produced are only changed when the
+/// state is `ConnectionState.active`.
+///
+/// The initial snapshot data can be controlled by specifying [initialData].
+/// This should be used to ensure that the first frame has the expected value,
+/// as the builder will always be called before the stream listener has a chance
+/// to be processed.
+///
+/// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+///
+/// This sample shows a [StreamBuilder] that listens to a Stream that emits bids
+/// for an auction. Every time the StreamBuilder receives a bid from the Stream,
+/// it will display the price of the bid below an icon. If the Stream emits an
+/// error, the error is displayed below an error icon. When the Stream finishes
+/// emitting bids, the final price is displayed.
+///
+/// ```dart
+/// Stream<int> _bids = (() async* {
+///   await Future<void>.delayed(Duration(seconds: 1));
+///   yield 1;
+///   await Future<void>.delayed(Duration(seconds: 1));
+/// })();
+///
+/// Widget build(BuildContext context) {
+///   return DefaultTextStyle(
+///     style: Theme.of(context).textTheme.headline2,
+///     textAlign: TextAlign.center,
+///     child: Container(
+///       alignment: FractionalOffset.center,
+///       color: Colors.white,
+///       child: StreamBuilder<int>(
+///         stream: _bids,
+///         builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
+///           List<Widget> children;
+///           if (snapshot.hasError) {
+///             children = <Widget>[
+///               Icon(
+///                 Icons.error_outline,
+///                 color: Colors.red,
+///                 size: 60,
+///               ),
+///               Padding(
+///                 padding: const EdgeInsets.only(top: 16),
+///                 child: Text('Error: ${snapshot.error}'),
+///               ),
+///               Padding(
+///                 padding: const EdgeInsets.only(top: 8),
+///                 child: Text('Stack trace: ${snapshot.stackTrace}'),
+///               ),
+///             ];
+///           } else {
+///             switch (snapshot.connectionState) {
+///               case ConnectionState.none:
+///                 children = <Widget>[
+///                   Icon(
+///                     Icons.info,
+///                     color: Colors.blue,
+///                     size: 60,
+///                   ),
+///                   const Padding(
+///                     padding: EdgeInsets.only(top: 16),
+///                     child: Text('Select a lot'),
+///                   )
+///                 ];
+///                 break;
+///               case ConnectionState.waiting:
+///                 children = <Widget>[
+///                   SizedBox(
+///                     child: const CircularProgressIndicator(),
+///                     width: 60,
+///                     height: 60,
+///                   ),
+///                   const Padding(
+///                     padding: EdgeInsets.only(top: 16),
+///                     child: Text('Awaiting bids...'),
+///                   )
+///                 ];
+///                 break;
+///               case ConnectionState.active:
+///                 children = <Widget>[
+///                   Icon(
+///                     Icons.check_circle_outline,
+///                     color: Colors.green,
+///                     size: 60,
+///                   ),
+///                   Padding(
+///                     padding: const EdgeInsets.only(top: 16),
+///                     child: Text('\$${snapshot.data}'),
+///                   )
+///                 ];
+///                 break;
+///               case ConnectionState.done:
+///                 children = <Widget>[
+///                   Icon(
+///                     Icons.info,
+///                     color: Colors.blue,
+///                     size: 60,
+///                   ),
+///                   Padding(
+///                     padding: const EdgeInsets.only(top: 16),
+///                     child: Text('\$${snapshot.data} (closed)'),
+///                   )
+///                 ];
+///                 break;
+///             }
+///           }
+///
+///           return Column(
+///             mainAxisAlignment: MainAxisAlignment.center,
+///             crossAxisAlignment: CrossAxisAlignment.center,
+///             children: children,
+///           );
+///         },
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ValueListenableBuilder], which wraps a [ValueListenable] instead of a
+///    [Stream].
+///  * [StreamBuilderBase], which supports widget building based on a computation
+///    that spans all interactions made with the stream.
+// TODO(ianh): remove unreachable code above once https://github.com/dart-lang/linter/issues/1139 is fixed
+class StreamBuilder<T> extends StreamBuilderBase<T, AsyncSnapshot<T>> {
+  /// Creates a new [StreamBuilder] that builds itself based on the latest
+  /// snapshot of interaction with the specified [stream] and whose build
+  /// strategy is given by [builder].
+  ///
+  /// The [initialData] is used to create the initial snapshot.
+  ///
+  /// The [builder] must not be null.
+  const StreamBuilder({
+    Key? key,
+    this.initialData,
+    Stream<T>? stream,
+    required this.builder,
+  }) : assert(builder != null),
+       super(key: key, stream: stream);
+
+  /// The build strategy currently used by this builder.
+  ///
+  /// This builder must only return a widget and should not have any side
+  /// effects as it may be called multiple times.
+  final AsyncWidgetBuilder<T> builder;
+
+  /// The data that will be used to create the initial snapshot.
+  ///
+  /// Providing this value (presumably obtained synchronously somehow when the
+  /// [Stream] was created) ensures that the first frame will show useful data.
+  /// Otherwise, the first frame will be built with the value null, regardless
+  /// of whether a value is available on the stream: since streams are
+  /// asynchronous, no events from the stream can be obtained before the initial
+  /// build.
+  final T? initialData;
+
+  @override
+  AsyncSnapshot<T> initial() => initialData == null
+      ? AsyncSnapshot<T>.nothing()
+      : AsyncSnapshot<T>.withData(ConnectionState.none, initialData as T);
+
+  @override
+  AsyncSnapshot<T> afterConnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.waiting);
+
+  @override
+  AsyncSnapshot<T> afterData(AsyncSnapshot<T> current, T data) {
+    return AsyncSnapshot<T>.withData(ConnectionState.active, data);
+  }
+
+  @override
+  AsyncSnapshot<T> afterError(AsyncSnapshot<T> current, Object error, StackTrace stackTrace) {
+    return AsyncSnapshot<T>.withError(ConnectionState.active, error, stackTrace);
+  }
+
+  @override
+  AsyncSnapshot<T> afterDone(AsyncSnapshot<T> current) => current.inState(ConnectionState.done);
+
+  @override
+  AsyncSnapshot<T> afterDisconnected(AsyncSnapshot<T> current) => current.inState(ConnectionState.none);
+
+  @override
+  Widget build(BuildContext context, AsyncSnapshot<T> currentSummary) => builder(context, currentSummary);
+}
+
+/// Widget that builds itself based on the latest snapshot of interaction with
+/// a [Future].
+///
+/// The [future] must have been obtained earlier, e.g. during [State.initState],
+/// [State.didUpdateWidget], or [State.didChangeDependencies]. It must not be
+/// created during the [State.build] or [StatelessWidget.build] method call when
+/// constructing the [FutureBuilder]. If the [future] is created at the same
+/// time as the [FutureBuilder], then every time the [FutureBuilder]'s parent is
+/// rebuilt, the asynchronous task will be restarted.
+///
+/// A general guideline is to assume that every `build` method could get called
+/// every frame, and to treat omitted calls as an optimization.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=ek8ZPdWj4Qo}
+///
+/// ## Timing
+///
+/// Widget rebuilding is scheduled by the completion of the future, using
+/// [State.setState], but is otherwise decoupled from the timing of the future.
+/// The [builder] callback is called at the discretion of the Flutter pipeline, and
+/// will thus receive a timing-dependent sub-sequence of the snapshots that
+/// represent the interaction with the future.
+///
+/// A side-effect of this is that providing a new but already-completed future
+/// to a [FutureBuilder] will result in a single frame in the
+/// [ConnectionState.waiting] state. This is because there is no way to
+/// synchronously determine that a [Future] has already completed.
+///
+/// ## Builder contract
+///
+/// For a future that completes successfully with data, assuming [initialData]
+/// is null, the [builder] will be called with either both or only the latter of
+/// the following snapshots:
+///
+/// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, null)`
+/// * `AsyncSnapshot<String>.withData(ConnectionState.done, 'some data')`
+///
+/// If that same future instead completed with an error, the [builder] would be
+/// called with either both or only the latter of:
+///
+/// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, null)`
+/// * `AsyncSnapshot<String>.withError(ConnectionState.done, 'some error', someStackTrace)`
+///
+/// The initial snapshot data can be controlled by specifying [initialData]. You
+/// would use this facility to ensure that if the [builder] is invoked before
+/// the future completes, the snapshot carries data of your choice rather than
+/// the default null value.
+///
+/// The data and error fields of the snapshot change only as the connection
+/// state field transitions from `waiting` to `done`, and they will be retained
+/// when changing the [FutureBuilder] configuration to another future. If the
+/// old future has already completed successfully with data as above, changing
+/// configuration to a new future results in snapshot pairs of the form:
+///
+/// * `AsyncSnapshot<String>.withData(ConnectionState.none, 'data of first future')`
+/// * `AsyncSnapshot<String>.withData(ConnectionState.waiting, 'data of second future')`
+///
+/// In general, the latter will be produced only when the new future is
+/// non-null, and the former only when the old future is non-null.
+///
+/// A [FutureBuilder] behaves identically to a [StreamBuilder] configured with
+/// `future?.asStream()`, except that snapshots with `ConnectionState.active`
+/// may appear for the latter, depending on how the stream is implemented.
+///
+/// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+///
+/// This sample shows a [FutureBuilder] that displays a loading spinner while it
+/// loads data. It displays a success icon and text if the [Future] completes
+/// with a result, or an error icon and text if the [Future] completes with an
+/// error. Assume the `_calculation` field is set by pressing a button elsewhere
+/// in the UI.
+///
+/// ```dart
+/// Future<String> _calculation = Future<String>.delayed(
+///   Duration(seconds: 2),
+///   () => 'Data Loaded',
+/// );
+///
+/// Widget build(BuildContext context) {
+///   return DefaultTextStyle(
+///     style: Theme.of(context).textTheme.headline2,
+///     textAlign: TextAlign.center,
+///     child: FutureBuilder<String>(
+///       future: _calculation, // a previously-obtained Future<String> or null
+///       builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
+///         List<Widget> children;
+///         if (snapshot.hasData) {
+///           children = <Widget>[
+///             Icon(
+///               Icons.check_circle_outline,
+///               color: Colors.green,
+///               size: 60,
+///             ),
+///             Padding(
+///               padding: const EdgeInsets.only(top: 16),
+///               child: Text('Result: ${snapshot.data}'),
+///             )
+///           ];
+///         } else if (snapshot.hasError) {
+///           children = <Widget>[
+///             Icon(
+///               Icons.error_outline,
+///               color: Colors.red,
+///               size: 60,
+///             ),
+///             Padding(
+///               padding: const EdgeInsets.only(top: 16),
+///               child: Text('Error: ${snapshot.error}'),
+///             )
+///           ];
+///         } else {
+///           children = <Widget>[
+///             SizedBox(
+///               child: CircularProgressIndicator(),
+///               width: 60,
+///               height: 60,
+///             ),
+///             const Padding(
+///               padding: EdgeInsets.only(top: 16),
+///               child: Text('Awaiting result...'),
+///             )
+///           ];
+///         }
+///         return Center(
+///           child: Column(
+///             mainAxisAlignment: MainAxisAlignment.center,
+///             crossAxisAlignment: CrossAxisAlignment.center,
+///             children: children,
+///           ),
+///         );
+///       },
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+// TODO(ianh): remove unreachable code above once https://github.com/dart-lang/sdk/issues/35520 is fixed
+class FutureBuilder<T> extends StatefulWidget {
+  /// Creates a widget that builds itself based on the latest snapshot of
+  /// interaction with a [Future].
+  ///
+  /// The [builder] must not be null.
+  const FutureBuilder({
+    Key? key,
+    this.future,
+    this.initialData,
+    required this.builder,
+  }) : assert(builder != null),
+       super(key: key);
+
+  /// The asynchronous computation to which this builder is currently connected,
+  /// possibly null.
+  ///
+  /// If no future has yet completed, including in the case where [future] is
+  /// null, the data provided to the [builder] will be set to [initialData].
+  final Future<T>? future;
+
+  /// The build strategy currently used by this builder.
+  ///
+  /// The builder is provided with an [AsyncSnapshot] object whose
+  /// [AsyncSnapshot.connectionState] property will be one of the following
+  /// values:
+  ///
+  ///  * [ConnectionState.none]: [future] is null. The [AsyncSnapshot.data] will
+  ///    be set to [initialData], unless a future has previously completed, in
+  ///    which case the previous result persists.
+  ///
+  ///  * [ConnectionState.waiting]: [future] is not null, but has not yet
+  ///    completed. The [AsyncSnapshot.data] will be set to [initialData],
+  ///    unless a future has previously completed, in which case the previous
+  ///    result persists.
+  ///
+  ///  * [ConnectionState.done]: [future] is not null, and has completed. If the
+  ///    future completed successfully, the [AsyncSnapshot.data] will be set to
+  ///    the value to which the future completed. If it completed with an error,
+  ///    [AsyncSnapshot.hasError] will be true and [AsyncSnapshot.error] will be
+  ///    set to the error object.
+  ///
+  /// This builder must only return a widget and should not have any side
+  /// effects as it may be called multiple times.
+  final AsyncWidgetBuilder<T> builder;
+
+  /// The data that will be used to create the snapshots provided until a
+  /// non-null [future] has completed.
+  ///
+  /// If the future completes with an error, the data in the [AsyncSnapshot]
+  /// provided to the [builder] will become null, regardless of [initialData].
+  /// (The error itself will be available in [AsyncSnapshot.error], and
+  /// [AsyncSnapshot.hasError] will be true.)
+  final T? initialData;
+
+  @override
+  State<FutureBuilder<T>> createState() => _FutureBuilderState<T>();
+}
+
+/// State for [FutureBuilder].
+class _FutureBuilderState<T> extends State<FutureBuilder<T>> {
+  /// An object that identifies the currently active callbacks. Used to avoid
+  /// calling setState from stale callbacks, e.g. after disposal of this state,
+  /// or after widget reconfiguration to a new Future.
+  Object? _activeCallbackIdentity;
+  late AsyncSnapshot<T> _snapshot;
+
+  @override
+  void initState() {
+    super.initState();
+    _snapshot = widget.initialData == null
+        ? AsyncSnapshot<T>.nothing()
+        : AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData as T);
+    _subscribe();
+  }
+
+  @override
+  void didUpdateWidget(FutureBuilder<T> oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.future != widget.future) {
+      if (_activeCallbackIdentity != null) {
+        _unsubscribe();
+        _snapshot = _snapshot.inState(ConnectionState.none);
+      }
+      _subscribe();
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) => widget.builder(context, _snapshot);
+
+  @override
+  void dispose() {
+    _unsubscribe();
+    super.dispose();
+  }
+
+  void _subscribe() {
+    if (widget.future != null) {
+      final Object callbackIdentity = Object();
+      _activeCallbackIdentity = callbackIdentity;
+      widget.future!.then<void>((T data) {
+        if (_activeCallbackIdentity == callbackIdentity) {
+          setState(() {
+            _snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
+          });
+        }
+      }, onError: (Object error, StackTrace stackTrace) {
+        if (_activeCallbackIdentity == callbackIdentity) {
+          setState(() {
+            _snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error, stackTrace);
+          });
+        }
+      });
+      _snapshot = _snapshot.inState(ConnectionState.waiting);
+    }
+  }
+
+  void _unsubscribe() {
+    _activeCallbackIdentity = null;
+  }
+}
diff --git a/lib/src/widgets/autocomplete.dart b/lib/src/widgets/autocomplete.dart
new file mode 100644
index 0000000..6599060
--- /dev/null
+++ b/lib/src/widgets/autocomplete.dart
@@ -0,0 +1,600 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+
+import 'basic.dart';
+import 'container.dart';
+import 'editable_text.dart';
+import 'focus_manager.dart';
+import 'framework.dart';
+import 'overlay.dart';
+
+/// The type of the [RawAutocomplete] callback which computes the list of
+/// optional completions for the widget's field based on the text the user has
+/// entered so far.
+///
+/// See also:
+///   * [RawAutocomplete.optionsBuilder], which is of this type.
+typedef AutocompleteOptionsBuilder<T extends Object> = Iterable<T> Function(TextEditingValue textEditingValue);
+
+/// The type of the callback used by the [RawAutocomplete] widget to indicate
+/// that the user has selected an option.
+///
+/// See also:
+///   * [RawAutocomplete.onSelected], which is of this type.
+typedef AutocompleteOnSelected<T extends Object> = void Function(T option);
+
+/// The type of the [RawAutocomplete] callback which returns a [Widget] that
+/// displays the specified [options] and calls [onSelected] if the user
+/// selects an option.
+///
+/// See also:
+///   * [RawAutocomplete.optionsViewBuilder], which is of this type.
+typedef AutocompleteOptionsViewBuilder<T extends Object> = Widget Function(
+  BuildContext context,
+  AutocompleteOnSelected<T> onSelected,
+  Iterable<T> options,
+);
+
+/// The type of the Autocomplete callback which returns the widget that
+/// contains the input [TextField] or [TextFormField].
+///
+/// See also:
+///   * [RawAutocomplete.fieldViewBuilder], which is of this type.
+typedef AutocompleteFieldViewBuilder = Widget Function(
+  BuildContext context,
+  TextEditingController textEditingController,
+  FocusNode focusNode,
+  VoidCallback onFieldSubmitted,
+);
+
+/// The type of the [RawAutocomplete] callback that converts an option value to
+/// a string which can be displayed in the widget's options menu.
+///
+/// See also:
+///   * [RawAutocomplete.displayStringForOption], which is of this type.
+typedef AutocompleteOptionToString<T extends Object> = String Function(T option);
+
+// TODO(justinmc): Mention Autocomplete and AutocompleteCupertino when they are
+// implemented.
+/// A widget for helping the user make a selection by entering some text and
+/// choosing from among a list of options.
+///
+/// This is a core framework widget with very basic UI.
+///
+/// The user's text input is received in a field built with the
+/// [fieldViewBuilder] parameter. The options to be displayed are determined
+/// using [optionsBuilder] and rendered with [optionsViewBuilder].
+///
+/// {@tool dartpad --template=freeform}
+/// This example shows how to create a very basic autocomplete widget using the
+/// [fieldViewBuilder] and [optionsViewBuilder] parameters.
+///
+/// ```dart imports
+/// import 'package:flute/widgets.dart';
+/// import 'package:flute/material.dart';
+/// ```
+///
+/// ```dart
+/// class AutocompleteBasicExample extends StatelessWidget {
+///   AutocompleteBasicExample({Key? key}) : super(key: key);
+///
+///   static final List<String> _options = <String>[
+///     'aardvark',
+///     'bobcat',
+///     'chameleon',
+///   ];
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return RawAutocomplete<String>(
+///       optionsBuilder: (TextEditingValue textEditingValue) {
+///         return _options.where((String option) {
+///           return option.contains(textEditingValue.text.toLowerCase());
+///         });
+///       },
+///       fieldViewBuilder: (BuildContext context, TextEditingController textEditingController, FocusNode focusNode, VoidCallback onFieldSubmitted) {
+///         return TextFormField(
+///           controller: textEditingController,
+///           focusNode: focusNode,
+///           onFieldSubmitted: (String value) {
+///             onFieldSubmitted();
+///           },
+///         );
+///       },
+///       optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<String> onSelected, Iterable<String> options) {
+///         return Align(
+///           alignment: Alignment.topLeft,
+///           child: Material(
+///             elevation: 4.0,
+///             child: Container(
+///               height: 200.0,
+///               child: ListView.builder(
+///                 padding: EdgeInsets.all(8.0),
+///                 itemCount: options.length,
+///                 itemBuilder: (BuildContext context, int index) {
+///                   final String option = options.elementAt(index);
+///                   return GestureDetector(
+///                     onTap: () {
+///                       onSelected(option);
+///                     },
+///                     child: ListTile(
+///                       title: Text(option),
+///                     ),
+///                   );
+///                 },
+///               ),
+///             ),
+///           ),
+///         );
+///       },
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// The type parameter T represents the type of the options. Most commonly this
+/// is a String, as in the example above. However, it's also possible to use
+/// another type with a `toString` method, or a custom [displayStringForOption].
+/// Options will be compared using `==`, so it may be beneficial to override
+/// [Object.==] and [Object.hashCode] for custom types.
+///
+/// {@tool dartpad --template=freeform}
+/// This example is similar to the previous example, but it uses a custom T data
+/// type instead of directly using String.
+///
+/// ```dart imports
+/// import 'package:flute/widgets.dart';
+/// import 'package:flute/material.dart';
+/// ```
+///
+/// ```dart
+/// // An example of a type that someone might want to autocomplete a list of.
+/// class User {
+///   const User({
+///     required this.email,
+///     required this.name,
+///   });
+///
+///   final String email;
+///   final String name;
+///
+///   @override
+///   String toString() {
+///     return '$name, $email';
+///   }
+///
+///   @override
+///   bool operator ==(Object other) {
+///     if (other.runtimeType != runtimeType)
+///       return false;
+///     return other is User
+///         && other.name == name
+///         && other.email == email;
+///   }
+///
+///   @override
+///   int get hashCode => hashValues(email, name);
+/// }
+///
+/// class AutocompleteCustomTypeExample extends StatelessWidget {
+///   AutocompleteCustomTypeExample({Key? key}) : super(key: key);
+///
+///   static final List<User> _userOptions = <User>[
+///     User(name: 'Alice', email: 'alice@example.com'),
+///     User(name: 'Bob', email: 'bob@example.com'),
+///     User(name: 'Charlie', email: 'charlie123@gmail.com'),
+///   ];
+///
+///   static String _displayStringForOption(User option) => option.name;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return RawAutocomplete<User>(
+///       optionsBuilder: (TextEditingValue textEditingValue) {
+///         return _userOptions.where((User option) {
+///           // Search based on User.toString, which includes both name and
+///           // email, even though the display string is just the name.
+///           return option.toString().contains(textEditingValue.text.toLowerCase());
+///         });
+///       },
+///       displayStringForOption: _displayStringForOption,
+///       fieldViewBuilder: (BuildContext context, TextEditingController textEditingController, FocusNode focusNode, VoidCallback onFieldSubmitted) {
+///         return TextFormField(
+///           controller: textEditingController,
+///           focusNode: focusNode,
+///           onFieldSubmitted: (String value) {
+///             onFieldSubmitted();
+///           },
+///         );
+///       },
+///       optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<User> onSelected, Iterable<User> options) {
+///         return Align(
+///           alignment: Alignment.topLeft,
+///           child: Material(
+///             elevation: 4.0,
+///             child: Container(
+///               height: 200.0,
+///               child: ListView.builder(
+///                 padding: EdgeInsets.all(8.0),
+///                 itemCount: options.length,
+///                 itemBuilder: (BuildContext context, int index) {
+///                   final User option = options.elementAt(index);
+///                   return GestureDetector(
+///                     onTap: () {
+///                       onSelected(option);
+///                     },
+///                     child: ListTile(
+///                       title: Text(_displayStringForOption(option)),
+///                     ),
+///                   );
+///                 },
+///               ),
+///             ),
+///           ),
+///         );
+///       },
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=freeform}
+/// This example shows the use of RawAutocomplete in a form.
+///
+/// ```dart imports
+/// import 'package:flute/widgets.dart';
+/// import 'package:flute/material.dart';
+/// ```
+///
+/// ```dart
+/// class AutocompleteFormExamplePage extends StatefulWidget {
+///   AutocompleteFormExamplePage({Key? key}) : super(key: key);
+///
+///   @override
+///   AutocompleteFormExample createState() => AutocompleteFormExample();
+/// }
+///
+/// class AutocompleteFormExample extends State<AutocompleteFormExamplePage> {
+///   final _formKey = GlobalKey<FormState>();
+///   final TextEditingController _textEditingController = TextEditingController();
+///   String? _dropdownValue;
+///   String? _autocompleteSelection;
+///
+///   final List<String> _options = <String>[
+///     'aardvark',
+///     'bobcat',
+///     'chameleon',
+///   ];
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Scaffold(
+///       appBar: AppBar(
+///         title: Text('Autocomplete Form Example'),
+///       ),
+///       body: Center(
+///         child: Form(
+///           key: _formKey,
+///           child: Column(
+///             children: <Widget>[
+///               DropdownButtonFormField<String>(
+///                 value: _dropdownValue,
+///                 icon: Icon(Icons.arrow_downward),
+///                 hint: const Text('This is a regular DropdownButtonFormField'),
+///                 iconSize: 24,
+///                 elevation: 16,
+///                 style: TextStyle(color: Colors.deepPurple),
+///                 onChanged: (String? newValue) {
+///                   setState(() {
+///                     _dropdownValue = newValue;
+///                   });
+///                 },
+///                 items: <String>['One', 'Two', 'Free', 'Four']
+///                     .map<DropdownMenuItem<String>>((String value) {
+///                   return DropdownMenuItem<String>(
+///                     value: value,
+///                     child: Text(value),
+///                   );
+///                 }).toList(),
+///                 validator: (String? value) {
+///                   if (value == null) {
+///                     return 'Must make a selection.';
+///                   }
+///                   return null;
+///                 },
+///               ),
+///               TextFormField(
+///                 controller: _textEditingController,
+///                 decoration: InputDecoration(
+///                   hintText: 'This is a regular TextFormField',
+///                 ),
+///                 validator: (String? value) {
+///                   if (value == null || value.isEmpty) {
+///                     return 'Can\'t be empty.';
+///                   }
+///                   return null;
+///                 },
+///               ),
+///               RawAutocomplete<String>(
+///                 optionsBuilder: (TextEditingValue textEditingValue) {
+///                   return _options.where((String option) {
+///                     return option.contains(textEditingValue.text.toLowerCase());
+///                   });
+///                 },
+///                 onSelected: (String selection) {
+///                   setState(() {
+///                     _autocompleteSelection = selection;
+///                   });
+///                 },
+///                 fieldViewBuilder: (BuildContext context, TextEditingController textEditingController, FocusNode focusNode, VoidCallback onFieldSubmitted) {
+///                   return TextFormField(
+///                     controller: textEditingController,
+///                     decoration: InputDecoration(
+///                       hintText: 'This is an RawAutocomplete!',
+///                     ),
+///                     focusNode: focusNode,
+///                     onFieldSubmitted: (String value) {
+///                       onFieldSubmitted();
+///                     },
+///                     validator: (String? value) {
+///                       if (!_options.contains(value)) {
+///                         return 'Nothing selected.';
+///                       }
+///                       return null;
+///                     },
+///                   );
+///                 },
+///                 optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<String> onSelected, Iterable<String> options) {
+///                   return Align(
+///                     alignment: Alignment.topLeft,
+///                     child: Material(
+///                       elevation: 4.0,
+///                       child: Container(
+///                         height: 200.0,
+///                         child: ListView.builder(
+///                           padding: EdgeInsets.all(8.0),
+///                           itemCount: options.length,
+///                           itemBuilder: (BuildContext context, int index) {
+///                             final String option = options.elementAt(index);
+///                             return GestureDetector(
+///                               onTap: () {
+///                                 onSelected(option);
+///                               },
+///                               child: ListTile(
+///                                 title: Text(option),
+///                               ),
+///                             );
+///                           },
+///                         ),
+///                       ),
+///                     ),
+///                   );
+///                 },
+///               ),
+///               ElevatedButton(
+///                 onPressed: () {
+///                   FocusScope.of(context).requestFocus(new FocusNode());
+///                   if (!_formKey.currentState!.validate()) {
+///                     return;
+///                   }
+///                   showDialog<void>(
+///                     context: context,
+///                     builder: (BuildContext context) {
+///                       return AlertDialog(
+///                         title: Text('Successfully submitted'),
+///                         content: SingleChildScrollView(
+///                           child: ListBody(
+///                             children: <Widget>[
+///                               Text('DropdownButtonFormField: "$_dropdownValue"'),
+///                               Text('TextFormField: "${_textEditingController.text}"'),
+///                               Text('RawAutocomplete: "$_autocompleteSelection"'),
+///                             ],
+///                           ),
+///                         ),
+///                         actions: <Widget>[
+///                           TextButton(
+///                             child: Text('Ok'),
+///                             onPressed: () {
+///                               Navigator.of(context).pop();
+///                             },
+///                           ),
+///                         ],
+///                       );
+///                     },
+///                   );
+///                 },
+///                 child: Text('Submit'),
+///               ),
+///             ],
+///           ),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+class RawAutocomplete<T extends Object> extends StatefulWidget {
+  /// Create an instance of RawAutocomplete.
+  ///
+  /// [fieldViewBuilder] and [optionsViewBuilder] must not be null.
+  const RawAutocomplete({
+    Key? key,
+    required this.fieldViewBuilder,
+    required this.optionsViewBuilder,
+    required this.optionsBuilder,
+    this.displayStringForOption = _defaultStringForOption,
+    this.onSelected,
+  }) : assert(displayStringForOption != null),
+       assert(fieldViewBuilder != null),
+       assert(optionsBuilder != null),
+       assert(optionsViewBuilder != null),
+       super(key: key);
+
+  /// Builds the field whose input is used to get the options.
+  ///
+  /// Pass the provided [TextEditingController] to the field built here so that
+  /// RawAutocomplete can listen for changes.
+  final AutocompleteFieldViewBuilder fieldViewBuilder;
+
+  /// Builds the selectable options widgets from a list of options objects.
+  ///
+  /// The options are displayed floating below the field using a
+  /// [CompositedTransformFollower] inside of an [Overlay], not at the same
+  /// place in the widget tree as RawAutocomplete.
+  final AutocompleteOptionsViewBuilder<T> optionsViewBuilder;
+
+  /// Returns the string to display in the field when the option is selected.
+  ///
+  /// This is useful when using a custom T type and the string to display is
+  /// different than the string to search by.
+  ///
+  /// If not provided, will use `option.toString()`.
+  final AutocompleteOptionToString<T> displayStringForOption;
+
+  /// Called when an option is selected by the user.
+  ///
+  /// Any [TextEditingController] listeners will not be called when the user
+  /// selects an option, even though the field will update with the selected
+  /// value, so use this to be informed of selection.
+  final AutocompleteOnSelected<T>? onSelected;
+
+  /// A function that returns the current selectable options objects given the
+  /// current TextEditingValue.
+  final AutocompleteOptionsBuilder<T> optionsBuilder;
+
+  // The default way to convert an option to a string.
+  static String _defaultStringForOption(dynamic option) {
+    return option.toString();
+  }
+
+  @override
+  _RawAutocompleteState<T> createState() => _RawAutocompleteState<T>();
+}
+
+class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>> {
+  final GlobalKey _fieldKey = GlobalKey();
+  final LayerLink _optionsLayerLink = LayerLink();
+  final TextEditingController _textEditingController = TextEditingController();
+  final FocusNode _focusNode = FocusNode();
+  Iterable<T> _options = Iterable<T>.empty();
+  T? _selection;
+
+  // The OverlayEntry containing the options.
+  OverlayEntry? _floatingOptions;
+
+  // True iff the state indicates that the options should be visible.
+  bool get _shouldShowOptions {
+    return _focusNode.hasFocus && _selection == null && _options.isNotEmpty;
+  }
+
+  // Called when _textEditingController changes.
+  void _onChangedField() {
+    final Iterable<T> options = widget.optionsBuilder(
+      _textEditingController.value,
+    );
+    _options = options;
+    if (_selection != null
+        && _textEditingController.text != widget.displayStringForOption(_selection!)) {
+      _selection = null;
+    }
+    _updateOverlay();
+  }
+
+  // Called when the field's FocusNode changes.
+  void _onChangedFocus() {
+    _updateOverlay();
+  }
+
+  // Called from fieldViewBuilder when the user submits the field.
+  void _onFieldSubmitted() {
+    if (_options.isEmpty) {
+      return;
+    }
+    _select(_options.first);
+  }
+
+  // Select the given option and update the widget.
+  void _select(T nextSelection) {
+    if (nextSelection == _selection) {
+      return;
+    }
+    _selection = nextSelection;
+    final String selectionString = widget.displayStringForOption(nextSelection);
+    _textEditingController.value = TextEditingValue(
+      selection: TextSelection.collapsed(offset: selectionString.length),
+      text: selectionString,
+    );
+    widget.onSelected?.call(_selection!);
+  }
+
+  // Hide or show the options overlay, if needed.
+  void _updateOverlay() {
+    if (_shouldShowOptions) {
+      _floatingOptions?.remove();
+      _floatingOptions = OverlayEntry(
+        builder: (BuildContext context) {
+          return CompositedTransformFollower(
+            link: _optionsLayerLink,
+            showWhenUnlinked: false,
+            targetAnchor: Alignment.bottomLeft,
+            child: widget.optionsViewBuilder(context, _select, _options),
+          );
+        },
+      );
+      Overlay.of(context, rootOverlay: true)!.insert(_floatingOptions!);
+    } else if (_floatingOptions != null) {
+      _floatingOptions!.remove();
+      _floatingOptions = null;
+    }
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _textEditingController.addListener(_onChangedField);
+    _focusNode.addListener(_onChangedFocus);
+    SchedulerBinding.instance!.addPostFrameCallback((Duration _) {
+      _updateOverlay();
+    });
+  }
+
+  @override
+  void didUpdateWidget(RawAutocomplete<T> oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    SchedulerBinding.instance!.addPostFrameCallback((Duration _) {
+      _updateOverlay();
+    });
+  }
+
+  @override
+  void dispose() {
+    _textEditingController.removeListener(_onChangedField);
+    _focusNode.removeListener(_onChangedFocus);
+    _floatingOptions?.remove();
+    _floatingOptions = null;
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      key: _fieldKey,
+      child: CompositedTransformTarget(
+        link: _optionsLayerLink,
+        child: widget.fieldViewBuilder(
+          context,
+          _textEditingController,
+          _focusNode,
+          _onFieldSubmitted,
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/widgets/autofill.dart b/lib/src/widgets/autofill.dart
new file mode 100644
index 0000000..03e717b
--- /dev/null
+++ b/lib/src/widgets/autofill.dart
@@ -0,0 +1,302 @@
+// Copyright 2014 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.
+
+import 'package:flute/services.dart';
+import 'framework.dart';
+
+export 'package:flute/services.dart' show AutofillHints;
+
+/// Predefined autofill context clean up actions.
+enum AutofillContextAction {
+  /// Destroys the current autofill context after informing the platform to save
+  /// the user input from it.
+  ///
+  /// Corresponds to calling [TextInput.finishAutofillContext] with
+  /// `shouldSave == true`.
+  commit,
+
+  /// Destroys the current autofill context without saving the user input.
+  ///
+  /// Corresponds to calling [TextInput.finishAutofillContext] with
+  /// `shouldSave == false`.
+  cancel,
+}
+
+/// An [AutofillScope] widget that groups [AutofillClient]s together.
+///
+/// [AutofillClient]s that share the same closest [AutofillGroup] ancestor must
+/// be built together, and they be will be autofilled together.
+///
+/// {@macro flutter.services.AutofillScope}
+///
+/// The [AutofillGroup] widget only knows about [AutofillClient]s registered to
+/// it using the [AutofillGroupState.register] API. Typically, [AutofillGroup]
+/// will not pick up [AutofillClient]s that are not mounted, for example, an
+/// [AutofillClient] within a [Scrollable] that has never been scrolled into the
+/// viewport. To workaround this problem, ensure clients in the same
+/// [AutofillGroup] are built together.
+///
+/// The topmost [AutofillGroup] widgets (the ones that are closest to the root
+/// widget) can be used to clean up the current autofill context when the
+/// current autofill context is no longer relevant.
+///
+/// {@macro flutter.services.TextInput.finishAutofillContext}
+///
+/// By default, [onDisposeAction] is set to [AutofillContextAction.commit], in
+/// which case when any of the topmost [AutofillGroup]s is being disposed, the
+/// platform will be informed to save the user input from the current autofill
+/// context, then the current autofill context will be destroyed, to free
+/// resources. You can, for example, wrap a route that contains a [Form] full of
+/// autofillable input fields in an [AutofillGroup], so the user input of the
+/// [Form] can be saved for future autofill by the platform.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+///
+/// An example form with autofillable fields grouped into different
+/// `AutofillGroup`s.
+///
+/// ```dart
+///  bool isSameAddress = true;
+///  final TextEditingController shippingAddress1 = TextEditingController();
+///  final TextEditingController shippingAddress2 = TextEditingController();
+///  final TextEditingController billingAddress1 = TextEditingController();
+///  final TextEditingController billingAddress2 = TextEditingController();
+///
+///  final TextEditingController creditCardNumber = TextEditingController();
+///  final TextEditingController creditCardSecurityCode = TextEditingController();
+///
+///  final TextEditingController phoneNumber = TextEditingController();
+///
+///  @override
+///  Widget build(BuildContext context) {
+///    return ListView(
+///      children: <Widget>[
+///        const Text('Shipping address'),
+///        // The address fields are grouped together as some platforms are
+///        // capable of autofilling all of these fields in one go.
+///        AutofillGroup(
+///          child: Column(
+///            children: <Widget>[
+///              TextField(
+///                controller: shippingAddress1,
+///                autofillHints: <String>[AutofillHints.streetAddressLine1],
+///              ),
+///              TextField(
+///                controller: shippingAddress2,
+///                autofillHints: <String>[AutofillHints.streetAddressLine2],
+///              ),
+///            ],
+///          ),
+///        ),
+///        const Text('Billing address'),
+///        Checkbox(
+///          value: isSameAddress,
+///          onChanged: (bool newValue) {
+///            setState(() { isSameAddress = newValue; });
+///          },
+///        ),
+///        // Again the address fields are grouped together for the same reason.
+///        if (!isSameAddress) AutofillGroup(
+///          child: Column(
+///            children: <Widget>[
+///              TextField(
+///                controller: billingAddress1,
+///                autofillHints: <String>[AutofillHints.streetAddressLine1],
+///              ),
+///              TextField(
+///                controller: billingAddress2,
+///                autofillHints: <String>[AutofillHints.streetAddressLine2],
+///              ),
+///            ],
+///          ),
+///        ),
+///        const Text('Credit Card Information'),
+///        // The credit card number and the security code are grouped together
+///        // as some platforms are capable of autofilling both fields.
+///        AutofillGroup(
+///          child: Column(
+///            children: <Widget>[
+///              TextField(
+///                controller: creditCardNumber,
+///                autofillHints: <String>[AutofillHints.creditCardNumber],
+///              ),
+///              TextField(
+///                controller: creditCardSecurityCode,
+///                autofillHints: <String>[AutofillHints.creditCardSecurityCode],
+///              ),
+///            ],
+///          ),
+///        ),
+///        const Text('Contact Phone Number'),
+///        // The phone number field can still be autofilled despite lacking an
+///        // `AutofillScope`.
+///        TextField(
+///          controller: phoneNumber,
+///          autofillHints: <String>[AutofillHints.telephoneNumber],
+///        ),
+///      ],
+///    );
+///  }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+/// * [AutofillContextAction], an enum that contains predefined autofill context
+///   clean up actions to be run when a topmost [AutofillGroup] is disposed.
+class AutofillGroup extends StatefulWidget {
+  /// Creates a scope for autofillable input fields.
+  ///
+  /// The [child] argument must not be null.
+  const AutofillGroup({
+    Key? key,
+    required this.child,
+    this.onDisposeAction = AutofillContextAction.commit,
+  }) : assert(child != null),
+       super(key: key);
+
+  /// Returns the closest [AutofillGroupState] which encloses the given context.
+  ///
+  /// {@macro flutter.widgets.AutofillGroupState}
+  ///
+  /// See also:
+  ///
+  /// * [EditableTextState], where this method is used to retrieve the closest
+  ///   [AutofillGroupState].
+  static AutofillGroupState? of(BuildContext context) {
+    final _AutofillScope? scope = context.dependOnInheritedWidgetOfExactType<_AutofillScope>();
+    return scope?._scope;
+  }
+
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The [AutofillContextAction] to be run when this [AutofillGroup] is the
+  /// topmost [AutofillGroup] and it's being disposed, in order to clean up the
+  /// current autofill context.
+  ///
+  /// {@macro flutter.services.TextInput.finishAutofillContext}
+  ///
+  /// Defaults to [AutofillContextAction.commit], which prompts the platform to
+  /// save the user input and destroy the current autofill context. No action
+  /// will be taken if [onDisposeAction] is set to null.
+  final AutofillContextAction onDisposeAction;
+
+  @override
+  AutofillGroupState createState() => AutofillGroupState();
+}
+
+/// State associated with an [AutofillGroup] widget.
+///
+/// {@template flutter.widgets.AutofillGroupState}
+/// An [AutofillGroupState] can be used to register an [AutofillClient] when it
+/// enters this [AutofillGroup] (for example, when an [EditableText] is mounted or
+/// reparented onto the [AutofillGroup]'s subtree), and unregister an
+/// [AutofillClient] when it exits (for example, when an [EditableText] gets
+/// unmounted or reparented out of the [AutofillGroup]'s subtree).
+///
+/// The [AutofillGroupState] class also provides an [AutofillGroupState.attach]
+/// method that can be called by [TextInputClient]s that support autofill,
+/// instead of [TextInput.attach], to create a [TextInputConnection] to interact
+/// with the platform's text input system.
+/// {@endtemplate}
+///
+/// Typically obtained using [AutofillGroup.of].
+class AutofillGroupState extends State<AutofillGroup> with AutofillScopeMixin {
+  final Map<String, AutofillClient> _clients = <String, AutofillClient>{};
+
+  // Whether this AutofillGroup widget is the topmost AutofillGroup (i.e., it
+  // has no AutofillGroup ancestor). Each topmost AutofillGroup runs its
+  // `AutofillGroup.onDisposeAction` when it gets disposed.
+  bool _isTopmostAutofillGroup = false;
+
+  @override
+  AutofillClient? getAutofillClient(String tag) => _clients[tag];
+
+  @override
+  Iterable<AutofillClient> get autofillClients {
+    return _clients.values
+      .where((AutofillClient client) => client.textInputConfiguration.autofillConfiguration != null);
+  }
+
+  /// Adds the [AutofillClient] to this [AutofillGroup].
+  ///
+  /// Typically, this is called by [TextInputClient]s that support autofill (for
+  /// example, [EditableTextState]) in [State.didChangeDependencies], when the
+  /// input field should be registered to a new [AutofillGroup].
+  ///
+  /// See also:
+  ///
+  /// * [EditableTextState.didChangeDependencies], where this method is called
+  ///   to update the current [AutofillScope] when needed.
+  void register(AutofillClient client) {
+    assert(client != null);
+    _clients.putIfAbsent(client.autofillId, () => client);
+  }
+
+  /// Removes an [AutofillClient] with the given `autofillId` from this
+  /// [AutofillGroup].
+  ///
+  /// Typically, this should be called by autofillable [TextInputClient]s in
+  /// [State.dispose] and [State.didChangeDependencies], when the input field
+  /// needs to be removed from the [AutofillGroup] it is currently registered to.
+  ///
+  /// See also:
+  ///
+  /// * [EditableTextState.didChangeDependencies], where this method is called
+  ///   to unregister from the previous [AutofillScope].
+  /// * [EditableTextState.dispose], where this method is called to unregister
+  ///   from the current [AutofillScope] when the widget is about to be removed
+  ///   from the tree.
+  void unregister(String autofillId) {
+    assert(autofillId != null && _clients.containsKey(autofillId));
+    _clients.remove(autofillId);
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _isTopmostAutofillGroup = AutofillGroup.of(context) == null;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _AutofillScope(
+      autofillScopeState: this,
+      child: widget.child,
+    );
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+
+    if (!_isTopmostAutofillGroup || widget.onDisposeAction == null)
+      return;
+    switch (widget.onDisposeAction) {
+      case AutofillContextAction.cancel:
+        TextInput.finishAutofillContext(shouldSave: false);
+        break;
+      case AutofillContextAction.commit:
+        TextInput.finishAutofillContext(shouldSave: true);
+        break;
+    }
+  }
+}
+
+class _AutofillScope extends InheritedWidget {
+  const _AutofillScope({
+    Key? key,
+    required Widget child,
+    AutofillGroupState? autofillScopeState,
+  }) : _scope = autofillScopeState,
+       super(key: key, child: child);
+
+  final AutofillGroupState? _scope;
+
+  AutofillGroup get client => _scope!.widget;
+
+  @override
+  bool updateShouldNotify(_AutofillScope old) => _scope != old._scope;
+}
diff --git a/lib/src/widgets/automatic_keep_alive.dart b/lib/src/widgets/automatic_keep_alive.dart
new file mode 100644
index 0000000..a3b4a5d
--- /dev/null
+++ b/lib/src/widgets/automatic_keep_alive.dart
@@ -0,0 +1,411 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+
+import 'framework.dart';
+import 'notification_listener.dart';
+import 'sliver.dart';
+
+/// Allows subtrees to request to be kept alive in lazy lists.
+///
+/// This widget is like [KeepAlive] but instead of being explicitly configured,
+/// it listens to [KeepAliveNotification] messages from the [child] and other
+/// descendants.
+///
+/// The subtree is kept alive whenever there is one or more descendant that has
+/// sent a [KeepAliveNotification] and not yet triggered its
+/// [KeepAliveNotification.handle].
+///
+/// To send these notifications, consider using [AutomaticKeepAliveClientMixin].
+class AutomaticKeepAlive extends StatefulWidget {
+  /// Creates a widget that listens to [KeepAliveNotification]s and maintains a
+  /// [KeepAlive] widget appropriately.
+  const AutomaticKeepAlive({
+    Key? key,
+    this.child,
+  }) : super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  @override
+  _AutomaticKeepAliveState createState() => _AutomaticKeepAliveState();
+}
+
+class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
+  Map<Listenable, VoidCallback>? _handles;
+  Widget? _child;
+  bool _keepingAlive = false;
+
+  @override
+  void initState() {
+    super.initState();
+    _updateChild();
+  }
+
+  @override
+  void didUpdateWidget(AutomaticKeepAlive oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    _updateChild();
+  }
+
+  void _updateChild() {
+    _child = NotificationListener<KeepAliveNotification>(
+      onNotification: _addClient,
+      child: widget.child!,
+    );
+  }
+
+  @override
+  void dispose() {
+    if (_handles != null) {
+      for (final Listenable handle in _handles!.keys)
+        handle.removeListener(_handles![handle]!);
+    }
+    super.dispose();
+  }
+
+  bool _addClient(KeepAliveNotification notification) {
+    final Listenable handle = notification.handle;
+    _handles ??= <Listenable, VoidCallback>{};
+    assert(!_handles!.containsKey(handle));
+    _handles![handle] = _createCallback(handle);
+    handle.addListener(_handles![handle]!);
+    if (!_keepingAlive) {
+      _keepingAlive = true;
+      final ParentDataElement<KeepAliveParentDataMixin>? childElement = _getChildElement();
+      if (childElement != null) {
+        // If the child already exists, update it synchronously.
+        _updateParentDataOfChild(childElement);
+      } else {
+        // If the child doesn't exist yet, we got called during the very first
+        // build of this subtree. Wait until the end of the frame to update
+        // the child when the child is guaranteed to be present.
+        SchedulerBinding.instance!.addPostFrameCallback((Duration timeStamp) {
+          if (!mounted) {
+            return;
+          }
+          final ParentDataElement<KeepAliveParentDataMixin>? childElement = _getChildElement();
+          assert(childElement != null);
+          _updateParentDataOfChild(childElement!);
+        });
+      }
+    }
+    return false;
+  }
+
+  /// Get the [Element] for the only [KeepAlive] child.
+  ///
+  /// While this widget is guaranteed to have a child, this may return null if
+  /// the first build of that child has not completed yet.
+  ParentDataElement<KeepAliveParentDataMixin>? _getChildElement() {
+    assert(mounted);
+    final Element element = context as Element;
+    Element? childElement;
+    // We use Element.visitChildren rather than context.visitChildElements
+    // because we might be called during build, and context.visitChildElements
+    // verifies that it is not called during build. Element.visitChildren does
+    // not, instead it assumes that the caller will be careful. (See the
+    // documentation for these methods for more details.)
+    //
+    // Here we know it's safe (with the exception outlined below) because we
+    // just received a notification, which we wouldn't be able to do if we
+    // hadn't built our child and its child -- our build method always builds
+    // the same subtree and it always includes the node we're looking for
+    // (KeepAlive) as the parent of the node that reports the notifications
+    // (NotificationListener).
+    //
+    // If we are called during the first build of this subtree the links to the
+    // children will not be hooked up yet. In that case this method returns
+    // null despite the fact that we will have a child after the build
+    // completes. It's the caller's responsibility to deal with this case.
+    //
+    // (We're only going down one level, to get our direct child.)
+    element.visitChildren((Element child) {
+      childElement = child;
+    });
+    assert(childElement == null || childElement is ParentDataElement<KeepAliveParentDataMixin>);
+    return childElement as ParentDataElement<KeepAliveParentDataMixin>?;
+  }
+
+  void _updateParentDataOfChild(ParentDataElement<KeepAliveParentDataMixin> childElement) {
+    childElement.applyWidgetOutOfTurn(build(context) as ParentDataWidget<KeepAliveParentDataMixin>);
+  }
+
+  VoidCallback _createCallback(Listenable handle) {
+    return () {
+      assert(() {
+        if (!mounted) {
+          throw FlutterError(
+            'AutomaticKeepAlive handle triggered after AutomaticKeepAlive was disposed.\n'
+            'Widgets should always trigger their KeepAliveNotification handle when they are '
+            'deactivated, so that they (or their handle) do not send spurious events later '
+            'when they are no longer in the tree.'
+          );
+        }
+        return true;
+      }());
+      _handles!.remove(handle);
+      if (_handles!.isEmpty) {
+        if (SchedulerBinding.instance!.schedulerPhase.index < SchedulerPhase.persistentCallbacks.index) {
+          // Build/layout haven't started yet so let's just schedule this for
+          // the next frame.
+          setState(() { _keepingAlive = false; });
+        } else {
+          // We were probably notified by a descendant when they were yanked out
+          // of our subtree somehow. We're probably in the middle of build or
+          // layout, so there's really nothing we can do to clean up this mess
+          // short of just scheduling another build to do the cleanup. This is
+          // very unfortunate, and means (for instance) that garbage collection
+          // of these resources won't happen for another 16ms.
+          //
+          // The problem is there's really no way for us to distinguish these
+          // cases:
+          //
+          //  * We haven't built yet (or missed out chance to build), but
+          //    someone above us notified our descendant and our descendant is
+          //    disconnecting from us. If we could mark ourselves dirty we would
+          //    be able to clean everything this frame. (This is a pretty
+          //    unlikely scenario in practice. Usually things change before
+          //    build/layout, not during build/layout.)
+          //
+          //  * Our child changed, and as our old child went away, it notified
+          //    us. We can't setState, since we _just_ built. We can't apply the
+          //    parent data information to our child because we don't _have_ a
+          //    child at this instant. We really want to be able to change our
+          //    mind about how we built, so we can give the KeepAlive widget a
+          //    new value, but it's too late.
+          //
+          //  * A deep descendant in another build scope just got yanked, and in
+          //    the process notified us. We could apply new parent data
+          //    information, but it may or may not get applied this frame,
+          //    depending on whether said child is in the same layout scope.
+          //
+          //  * A descendant is being moved from one position under us to
+          //    another position under us. They just notified us of the removal,
+          //    at some point in the future they will notify us of the addition.
+          //    We don't want to do anything. (This is why we check that
+          //    _handles is still empty below.)
+          //
+          //  * We're being notified in the paint phase, or even in a post-frame
+          //    callback. Either way it is far too late for us to make our
+          //    parent lay out again this frame, so the garbage won't get
+          //    collected this frame.
+          //
+          //  * We are being torn out of the tree ourselves, as is our
+          //    descendant, and it notified us while it was being deactivated.
+          //    We don't need to do anything, but we don't know yet because we
+          //    haven't been deactivated yet. (This is why we check mounted
+          //    below before calling setState.)
+          //
+          // Long story short, we have to schedule a new frame and request a
+          // frame there, but this is generally a bad practice, and you should
+          // avoid it if possible.
+          _keepingAlive = false;
+          scheduleMicrotask(() {
+            if (mounted && _handles!.isEmpty) {
+              // If mounted is false, we went away as well, so there's nothing to do.
+              // If _handles is no longer empty, then another client (or the same
+              // client in a new place) registered itself before we had a chance to
+              // turn off keepalive, so again there's nothing to do.
+              setState(() {
+                assert(!_keepingAlive);
+              });
+            }
+          });
+        }
+      }
+    };
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(_child != null);
+    return KeepAlive(
+      keepAlive: _keepingAlive,
+      child: _child!,
+    );
+  }
+
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(FlagProperty('_keepingAlive', value: _keepingAlive, ifTrue: 'keeping subtree alive'));
+    description.add(DiagnosticsProperty<Map<Listenable, VoidCallback>>(
+      'handles',
+      _handles,
+      description: _handles != null ?
+        '${_handles!.length} active client${ _handles!.length == 1 ? "" : "s" }' :
+        null,
+      ifNull: 'no notifications ever received',
+    ));
+  }
+}
+
+/// Indicates that the subtree through which this notification bubbles must be
+/// kept alive even if it would normally be discarded as an optimization.
+///
+/// For example, a focused text field might fire this notification to indicate
+/// that it should not be disposed even if the user scrolls the field off
+/// screen.
+///
+/// Each [KeepAliveNotification] is configured with a [handle] that consists of
+/// a [Listenable] that is triggered when the subtree no longer needs to be kept
+/// alive.
+///
+/// The [handle] should be triggered any time the sending widget is removed from
+/// the tree (in [State.deactivate]). If the widget is then rebuilt and still
+/// needs to be kept alive, it should immediately send a new notification
+/// (possible with the very same [Listenable]) during build.
+///
+/// This notification is listened to by the [AutomaticKeepAlive] widget, which
+/// is added to the tree automatically by [SliverList] (and [ListView]) and
+/// [SliverGrid] (and [GridView]) widgets.
+///
+/// Failure to trigger the [handle] in the manner described above will likely
+/// cause the [AutomaticKeepAlive] to lose track of whether the widget should be
+/// kept alive or not, leading to memory leaks or lost data. For example, if the
+/// widget that requested keepalive is removed from the subtree but doesn't
+/// trigger its [Listenable] on the way out, then the subtree will continue to
+/// be kept alive until the list itself is disposed. Similarly, if the
+/// [Listenable] is triggered while the widget needs to be kept alive, but a new
+/// [KeepAliveNotification] is not immediately sent, then the widget risks being
+/// garbage collected while it wants to be kept alive.
+///
+/// It is an error to use the same [handle] in two [KeepAliveNotification]s
+/// within the same [AutomaticKeepAlive] without triggering that [handle] before
+/// the second notification is sent.
+///
+/// For a more convenient way to interact with [AutomaticKeepAlive] widgets,
+/// consider using [AutomaticKeepAliveClientMixin], which uses
+/// [KeepAliveNotification] internally.
+class KeepAliveNotification extends Notification {
+  /// Creates a notification to indicate that a subtree must be kept alive.
+  ///
+  /// The [handle] must not be null.
+  const KeepAliveNotification(this.handle) : assert(handle != null);
+
+  /// A [Listenable] that will inform its clients when the widget that fired the
+  /// notification no longer needs to be kept alive.
+  ///
+  /// The [Listenable] should be triggered any time the sending widget is
+  /// removed from the tree (in [State.deactivate]). If the widget is then
+  /// rebuilt and still needs to be kept alive, it should immediately send a new
+  /// notification (possible with the very same [Listenable]) during build.
+  ///
+  /// See also:
+  ///
+  ///  * [KeepAliveHandle], a convenience class for use with this property.
+  final Listenable handle;
+}
+
+/// A [Listenable] which can be manually triggered.
+///
+/// Used with [KeepAliveNotification] objects as their
+/// [KeepAliveNotification.handle].
+///
+/// For a more convenient way to interact with [AutomaticKeepAlive] widgets,
+/// consider using [AutomaticKeepAliveClientMixin], which uses a
+/// [KeepAliveHandle] internally.
+class KeepAliveHandle extends ChangeNotifier {
+  /// Trigger the listeners to indicate that the widget
+  /// no longer needs to be kept alive.
+  void release() {
+    notifyListeners();
+  }
+}
+
+/// A mixin with convenience methods for clients of [AutomaticKeepAlive]. Used
+/// with [State] subclasses.
+///
+/// Subclasses must implement [wantKeepAlive], and their [build] methods must
+/// call `super.build` (though the return value should be ignored).
+///
+/// Then, whenever [wantKeepAlive]'s value changes (or might change), the
+/// subclass should call [updateKeepAlive].
+///
+/// The type argument `T` is the type of the [StatefulWidget] subclass of the
+/// [State] into which this class is being mixed.
+///
+/// See also:
+///
+///  * [AutomaticKeepAlive], which listens to messages from this mixin.
+///  * [KeepAliveNotification], the notifications sent by this mixin.
+@optionalTypeArgs
+mixin AutomaticKeepAliveClientMixin<T extends StatefulWidget> on State<T> {
+  KeepAliveHandle? _keepAliveHandle;
+
+  void _ensureKeepAlive() {
+    assert(_keepAliveHandle == null);
+    _keepAliveHandle = KeepAliveHandle();
+    KeepAliveNotification(_keepAliveHandle!).dispatch(context);
+  }
+
+  void _releaseKeepAlive() {
+    _keepAliveHandle!.release();
+    _keepAliveHandle = null;
+  }
+
+  /// Whether the current instance should be kept alive.
+  ///
+  /// Call [updateKeepAlive] whenever this getter's value changes.
+  @protected
+  bool get wantKeepAlive;
+
+  /// Ensures that any [AutomaticKeepAlive] ancestors are in a good state, by
+  /// firing a [KeepAliveNotification] or triggering the [KeepAliveHandle] as
+  /// appropriate.
+  @protected
+  void updateKeepAlive() {
+    if (wantKeepAlive) {
+      if (_keepAliveHandle == null)
+        _ensureKeepAlive();
+    } else {
+      if (_keepAliveHandle != null)
+        _releaseKeepAlive();
+    }
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    if (wantKeepAlive)
+      _ensureKeepAlive();
+  }
+
+  @override
+  void deactivate() {
+    if (_keepAliveHandle != null)
+      _releaseKeepAlive();
+    super.deactivate();
+  }
+
+  @mustCallSuper
+  @override
+  Widget build(BuildContext context) {
+    if (wantKeepAlive && _keepAliveHandle == null)
+      _ensureKeepAlive();
+    return const _NullWidget();
+  }
+}
+
+class _NullWidget extends StatelessWidget {
+  const _NullWidget();
+
+  @override
+  Widget build(BuildContext context) {
+    throw FlutterError(
+      'Widgets that mix AutomaticKeepAliveClientMixin into their State must '
+      'call super.build() but must ignore the return value of the superclass.'
+    );
+  }
+}
diff --git a/lib/src/widgets/banner.dart b/lib/src/widgets/banner.dart
new file mode 100644
index 0000000..ca5cc8d
--- /dev/null
+++ b/lib/src/widgets/banner.dart
@@ -0,0 +1,369 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart';
+
+import 'basic.dart';
+import 'debug.dart';
+import 'framework.dart';
+
+const double _kOffset = 40.0; // distance to bottom of banner, at a 45 degree angle inwards
+const double _kHeight = 12.0; // height of banner
+const double _kBottomOffset = _kOffset + 0.707 * _kHeight; // offset plus sqrt(2)/2 * banner height
+const Rect _kRect = Rect.fromLTWH(-_kOffset, _kOffset - _kHeight, _kOffset * 2.0, _kHeight);
+
+const Color _kColor = Color(0xA0B71C1C);
+const TextStyle _kTextStyle = TextStyle(
+  color: Color(0xFFFFFFFF),
+  fontSize: _kHeight * 0.85,
+  fontWeight: FontWeight.w900,
+  height: 1.0,
+);
+
+/// Where to show a [Banner].
+///
+/// The start and end locations are relative to the ambient [Directionality]
+/// (which can be overridden by [Banner.layoutDirection]).
+enum BannerLocation {
+  /// Show the banner in the top-right corner when the ambient [Directionality]
+  /// (or [Banner.layoutDirection]) is [TextDirection.rtl] and in the top-left
+  /// corner when the ambient [Directionality] is [TextDirection.ltr].
+  topStart,
+
+  /// Show the banner in the top-left corner when the ambient [Directionality]
+  /// (or [Banner.layoutDirection]) is [TextDirection.rtl] and in the top-right
+  /// corner when the ambient [Directionality] is [TextDirection.ltr].
+  topEnd,
+
+  /// Show the banner in the bottom-right corner when the ambient
+  /// [Directionality] (or [Banner.layoutDirection]) is [TextDirection.rtl] and
+  /// in the bottom-left corner when the ambient [Directionality] is
+  /// [TextDirection.ltr].
+  bottomStart,
+
+  /// Show the banner in the bottom-left corner when the ambient
+  /// [Directionality] (or [Banner.layoutDirection]) is [TextDirection.rtl] and
+  /// in the bottom-right corner when the ambient [Directionality] is
+  /// [TextDirection.ltr].
+  bottomEnd,
+}
+
+/// Paints a [Banner].
+class BannerPainter extends CustomPainter {
+  /// Creates a banner painter.
+  ///
+  /// The [message], [textDirection], [location], and [layoutDirection]
+  /// arguments must not be null.
+  BannerPainter({
+    required this.message,
+    required this.textDirection,
+    required this.location,
+    required this.layoutDirection,
+    this.color = _kColor,
+    this.textStyle = _kTextStyle,
+  }) : assert(message != null),
+       assert(textDirection != null),
+       assert(location != null),
+       assert(color != null),
+       assert(textStyle != null),
+       super(repaint: PaintingBinding.instance!.systemFonts);
+
+  /// The message to show in the banner.
+  final String message;
+
+  /// The directionality of the text.
+  ///
+  /// This value is used to disambiguate how to render bidirectional text. For
+  /// example, if the message is an English phrase followed by a Hebrew phrase,
+  /// in a [TextDirection.ltr] context the English phrase will be on the left
+  /// and the Hebrew phrase to its right, while in a [TextDirection.rtl]
+  /// context, the English phrase will be on the right and the Hebrew phrase on
+  /// its left.
+  ///
+  /// See also:
+  ///
+  ///  * [layoutDirection], which controls the interpretation of values in
+  ///    [location].
+  final TextDirection textDirection;
+
+  /// Where to show the banner (e.g., the upper right corner).
+  final BannerLocation location;
+
+  /// The directionality of the layout.
+  ///
+  /// This value is used to interpret the [location] of the banner.
+  ///
+  /// See also:
+  ///
+  ///  * [textDirection], which controls the reading direction of the [message].
+  final TextDirection layoutDirection;
+
+  /// The color to paint behind the [message].
+  ///
+  /// Defaults to a dark red.
+  final Color color;
+
+  /// The text style to use for the [message].
+  ///
+  /// Defaults to bold, white text.
+  final TextStyle textStyle;
+
+  static const BoxShadow _shadow = BoxShadow(
+    color: Color(0x7F000000),
+    blurRadius: 6.0,
+  );
+
+  bool _prepared = false;
+  late TextPainter _textPainter;
+  late Paint _paintShadow;
+  late Paint _paintBanner;
+
+  void _prepare() {
+    _paintShadow = _shadow.toPaint();
+    _paintBanner = Paint()
+      ..color = color;
+    _textPainter = TextPainter(
+      text: TextSpan(style: textStyle, text: message),
+      textAlign: TextAlign.center,
+      textDirection: textDirection,
+    );
+    _prepared = true;
+  }
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    if (!_prepared)
+      _prepare();
+    canvas
+      ..translate(_translationX(size.width), _translationY(size.height))
+      ..rotate(_rotation)
+      ..drawRect(_kRect, _paintShadow)
+      ..drawRect(_kRect, _paintBanner);
+    const double width = _kOffset * 2.0;
+    _textPainter.layout(minWidth: width, maxWidth: width);
+    _textPainter.paint(canvas, _kRect.topLeft + Offset(0.0, (_kRect.height - _textPainter.height) / 2.0));
+  }
+
+  @override
+  bool shouldRepaint(BannerPainter oldDelegate) {
+    return message != oldDelegate.message
+        || location != oldDelegate.location
+        || color != oldDelegate.color
+        || textStyle != oldDelegate.textStyle;
+  }
+
+  @override
+  bool hitTest(Offset position) => false;
+
+  double _translationX(double width) {
+    assert(location != null);
+    assert(layoutDirection != null);
+    switch (layoutDirection) {
+      case TextDirection.rtl:
+        switch (location) {
+          case BannerLocation.bottomEnd:
+            return _kBottomOffset;
+          case BannerLocation.topEnd:
+            return 0.0;
+          case BannerLocation.bottomStart:
+            return width - _kBottomOffset;
+          case BannerLocation.topStart:
+            return width;
+        }
+      case TextDirection.ltr:
+        switch (location) {
+          case BannerLocation.bottomEnd:
+            return width - _kBottomOffset;
+          case BannerLocation.topEnd:
+            return width;
+          case BannerLocation.bottomStart:
+            return _kBottomOffset;
+          case BannerLocation.topStart:
+            return 0.0;
+        }
+    }
+  }
+
+  double _translationY(double height) {
+    assert(location != null);
+    switch (location) {
+      case BannerLocation.bottomStart:
+      case BannerLocation.bottomEnd:
+        return height - _kBottomOffset;
+      case BannerLocation.topStart:
+      case BannerLocation.topEnd:
+        return 0.0;
+    }
+  }
+
+  double get _rotation {
+    assert(location != null);
+    assert(layoutDirection != null);
+    switch (layoutDirection) {
+      case TextDirection.rtl:
+        switch (location) {
+          case BannerLocation.bottomStart:
+          case BannerLocation.topEnd:
+            return -math.pi / 4.0;
+          case BannerLocation.bottomEnd:
+          case BannerLocation.topStart:
+            return math.pi / 4.0;
+        }
+      case TextDirection.ltr:
+        switch (location) {
+          case BannerLocation.bottomStart:
+          case BannerLocation.topEnd:
+            return math.pi / 4.0;
+          case BannerLocation.bottomEnd:
+          case BannerLocation.topStart:
+            return -math.pi / 4.0;
+        }
+    }
+  }
+}
+
+/// Displays a diagonal message above the corner of another widget.
+///
+/// Useful for showing the execution mode of an app (e.g., that asserts are
+/// enabled.)
+///
+/// See also:
+///
+///  * [CheckedModeBanner], which the [WidgetsApp] widget includes by default in
+///    debug mode, to show a banner that says "DEBUG".
+class Banner extends StatelessWidget {
+  /// Creates a banner.
+  ///
+  /// The [message] and [location] arguments must not be null.
+  const Banner({
+    Key? key,
+    this.child,
+    required this.message,
+    this.textDirection,
+    required this.location,
+    this.layoutDirection,
+    this.color = _kColor,
+    this.textStyle = _kTextStyle,
+  }) : assert(message != null),
+       assert(location != null),
+       assert(color != null),
+       assert(textStyle != null),
+       super(key: key);
+
+  /// The widget to show behind the banner.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// The message to show in the banner.
+  final String message;
+
+  /// The directionality of the text.
+  ///
+  /// This is used to disambiguate how to render bidirectional text. For
+  /// example, if the message is an English phrase followed by a Hebrew phrase,
+  /// in a [TextDirection.ltr] context the English phrase will be on the left
+  /// and the Hebrew phrase to its right, while in a [TextDirection.rtl]
+  /// context, the English phrase will be on the right and the Hebrew phrase on
+  /// its left.
+  ///
+  /// Defaults to the ambient [Directionality], if any.
+  ///
+  /// See also:
+  ///
+  ///  * [layoutDirection], which controls the interpretation of the [location].
+  final TextDirection? textDirection;
+
+  /// Where to show the banner (e.g., the upper right corner).
+  final BannerLocation location;
+
+  /// The directionality of the layout.
+  ///
+  /// This is used to resolve the [location] values.
+  ///
+  /// Defaults to the ambient [Directionality], if any.
+  ///
+  /// See also:
+  ///
+  ///  * [textDirection], which controls the reading direction of the [message].
+  final TextDirection? layoutDirection;
+
+  /// The color of the banner.
+  final Color color;
+
+  /// The style of the text shown on the banner.
+  final TextStyle textStyle;
+
+  @override
+  Widget build(BuildContext context) {
+    assert((textDirection != null && layoutDirection != null) || debugCheckHasDirectionality(context));
+    return CustomPaint(
+      foregroundPainter: BannerPainter(
+        message: message,
+        textDirection: textDirection ?? Directionality.of(context),
+        location: location,
+        layoutDirection: layoutDirection ?? Directionality.of(context),
+        color: color,
+        textStyle: textStyle,
+      ),
+      child: child,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(StringProperty('message', message, showName: false));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    properties.add(EnumProperty<BannerLocation>('location', location));
+    properties.add(EnumProperty<TextDirection>('layoutDirection', layoutDirection, defaultValue: null));
+    properties.add(ColorProperty('color', color, showName: false));
+    textStyle.debugFillProperties(properties, prefix: 'text ');
+  }
+}
+
+/// Displays a [Banner] saying "DEBUG" when running in checked mode.
+/// [MaterialApp] builds one of these by default.
+/// Does nothing in release mode.
+class CheckedModeBanner extends StatelessWidget {
+  /// Creates a const checked mode banner.
+  const CheckedModeBanner({
+    Key? key,
+    required this.child,
+  }) : super(key: key);
+
+  /// The widget to show behind the banner.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    Widget result = child;
+    assert(() {
+      result = Banner(
+        child: result,
+        message: 'DEBUG',
+        textDirection: TextDirection.ltr,
+        location: BannerLocation.topEnd,
+      );
+      return true;
+    }());
+    return result;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    String message = 'disabled';
+    assert(() {
+      message = '"DEBUG"';
+      return true;
+    }());
+    properties.add(DiagnosticsNode.message(message));
+  }
+}
diff --git a/lib/src/widgets/basic.dart b/lib/src/widgets/basic.dart
new file mode 100644
index 0000000..b11f109
--- /dev/null
+++ b/lib/src/widgets/basic.dart
@@ -0,0 +1,7694 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui show Image, ImageFilter, TextHeightBehavior;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+
+import 'binding.dart';
+import 'debug.dart';
+import 'framework.dart';
+import 'localizations.dart';
+import 'widget_span.dart';
+
+export 'package:flute/animation.dart';
+export 'package:flute/foundation.dart' show
+  ChangeNotifier,
+  FlutterErrorDetails,
+  Listenable,
+  TargetPlatform,
+  ValueNotifier;
+export 'package:flute/painting.dart';
+export 'package:flute/rendering.dart' show
+  AlignmentTween,
+  AlignmentGeometryTween,
+  Axis,
+  BoxConstraints,
+  CrossAxisAlignment,
+  CustomClipper,
+  CustomPainter,
+  CustomPainterSemantics,
+  DecorationPosition,
+  FlexFit,
+  FlowDelegate,
+  FlowPaintingContext,
+  FractionalOffsetTween,
+  HitTestBehavior,
+  LayerLink,
+  MainAxisAlignment,
+  MainAxisSize,
+  MultiChildLayoutDelegate,
+  Overflow,
+  PaintingContext,
+  PointerCancelEvent,
+  PointerCancelEventListener,
+  PointerDownEvent,
+  PointerDownEventListener,
+  PointerEvent,
+  PointerMoveEvent,
+  PointerMoveEventListener,
+  PointerUpEvent,
+  PointerUpEventListener,
+  RelativeRect,
+  SemanticsBuilderCallback,
+  ShaderCallback,
+  ShapeBorderClipper,
+  SingleChildLayoutDelegate,
+  StackFit,
+  TextOverflow,
+  ValueChanged,
+  ValueGetter,
+  WrapAlignment,
+  WrapCrossAlignment;
+
+// Examples can assume:
+// // @dart = 2.9
+// class TestWidget extends StatelessWidget { @override Widget build(BuildContext context) => const Placeholder(); }
+// WidgetTester tester;
+// bool _visible;
+// class Sky extends CustomPainter { @override void paint(Canvas c, Size s) => null; @override bool shouldRepaint(Sky s) => false; }
+// BuildContext context;
+// dynamic userAvatarUrl;
+
+// BIDIRECTIONAL TEXT SUPPORT
+
+/// A widget that determines the ambient directionality of text and
+/// text-direction-sensitive render objects.
+///
+/// For example, [Padding] depends on the [Directionality] to resolve
+/// [EdgeInsetsDirectional] objects into absolute [EdgeInsets] objects.
+class Directionality extends InheritedWidget {
+  /// Creates a widget that determines the directionality of text and
+  /// text-direction-sensitive render objects.
+  ///
+  /// The [textDirection] and [child] arguments must not be null.
+  const Directionality({
+    Key? key,
+    required this.textDirection,
+    required Widget child,
+  }) : assert(textDirection != null),
+       assert(child != null),
+       super(key: key, child: child);
+
+  /// The text direction for this subtree.
+  final TextDirection textDirection;
+
+  /// The text direction from the closest instance of this class that encloses
+  /// the given context.
+  ///
+  /// If there is no [Directionality] ancestor widget in the tree at the given
+  /// context, then this will throw a descriptive [FlutterError] in debug mode
+  /// and an exception in release mode.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// TextDirection textDirection = Directionality.of(context);
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * [maybeOf], which will return null if no [Directionality] ancestor
+  ///    widget is in the tree.
+  static TextDirection of(BuildContext context) {
+    assert(debugCheckHasDirectionality(context));
+    final Directionality widget = context.dependOnInheritedWidgetOfExactType<Directionality>()!;
+    return widget.textDirection;
+  }
+
+  /// The text direction from the closest instance of this class that encloses
+  /// the given context.
+  ///
+  /// If there is no [Directionality] ancestor widget in the tree at the given
+  /// context, then this will return null.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// TextDirection? textDirection = Directionality.maybeOf(context);
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * [of], which will throw if no [Directionality] ancestor widget is in the
+  ///    tree.
+  static TextDirection? maybeOf(BuildContext context) {
+    final Directionality? widget = context.dependOnInheritedWidgetOfExactType<Directionality>();
+    return widget?.textDirection;
+  }
+
+  @override
+  bool updateShouldNotify(Directionality oldWidget) => textDirection != oldWidget.textDirection;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection));
+  }
+}
+
+
+// PAINTING NODES
+
+/// A widget that makes its child partially transparent.
+///
+/// This class paints its child into an intermediate buffer and then blends the
+/// child back into the scene partially transparent.
+///
+/// For values of opacity other than 0.0 and 1.0, this class is relatively
+/// expensive because it requires painting the child into an intermediate
+/// buffer. For the value 0.0, the child is simply not painted at all. For the
+/// value 1.0, the child is painted immediately without an intermediate buffer.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=9hltevOHQBw}
+///
+/// {@tool snippet}
+///
+/// This example shows some [Text] when the `_visible` member field is true, and
+/// hides it when it is false:
+///
+/// ```dart
+/// Opacity(
+///   opacity: _visible ? 1.0 : 0.0,
+///   child: const Text("Now you see me, now you don't!"),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// This is more efficient than adding and removing the child widget from the
+/// tree on demand.
+///
+/// ## Performance considerations for opacity animation
+///
+/// Animating an [Opacity] widget directly causes the widget (and possibly its
+/// subtree) to rebuild each frame, which is not very efficient. Consider using
+/// an [AnimatedOpacity] instead.
+///
+/// ## Transparent image
+///
+/// If only a single [Image] or [Color] needs to be composited with an opacity
+/// between 0.0 and 1.0, it's much faster to directly use them without [Opacity]
+/// widgets.
+///
+/// For example, `Container(color: Color.fromRGBO(255, 0, 0, 0.5))` is much
+/// faster than `Opacity(opacity: 0.5, child: Container(color: Colors.red))`.
+///
+/// {@tool snippet}
+///
+/// The following example draws an [Image] with 0.5 opacity without using
+/// [Opacity]:
+///
+/// ```dart
+/// Image.network(
+///   'https://raw.githubusercontent.com/flutter/assets-for-api-docs/master/packages/diagrams/assets/blend_mode_destination.jpeg',
+///   color: Color.fromRGBO(255, 255, 255, 0.5),
+///   colorBlendMode: BlendMode.modulate
+/// )
+/// ```
+///
+/// {@end-tool}
+///
+/// Directly drawing an [Image] or [Color] with opacity is faster than using
+/// [Opacity] on top of them because [Opacity] could apply the opacity to a
+/// group of widgets and therefore a costly offscreen buffer will be used.
+/// Drawing content into the offscreen buffer may also trigger render target
+/// switches and such switching is particularly slow in older GPUs.
+///
+/// See also:
+///
+///  * [Visibility], which can hide a child more efficiently (albeit less
+///    subtly, because it is either visible or hidden, rather than allowing
+///    fractional opacity values).
+///  * [ShaderMask], which can apply more elaborate effects to its child.
+///  * [Transform], which applies an arbitrary transform to its child widget at
+///    paint time.
+///  * [AnimatedOpacity], which uses an animation internally to efficiently
+///    animate opacity.
+///  * [FadeTransition], which uses a provided animation to efficiently animate
+///    opacity.
+///  * [Image], which can directly provide a partially transparent image with
+///    much less performance hit.
+class Opacity extends SingleChildRenderObjectWidget {
+  /// Creates a widget that makes its child partially transparent.
+  ///
+  /// The [opacity] argument must not be null and must be between 0.0 and 1.0
+  /// (inclusive).
+  const Opacity({
+    Key? key,
+    required this.opacity,
+    this.alwaysIncludeSemantics = false,
+    Widget? child,
+  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
+       assert(alwaysIncludeSemantics != null),
+       super(key: key, child: child);
+
+  /// The fraction to scale the child's alpha value.
+  ///
+  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
+  /// (i.e., invisible).
+  ///
+  /// The opacity must not be null.
+  ///
+  /// Values 1.0 and 0.0 are painted with a fast path. Other values
+  /// require painting the child into an intermediate buffer, which is
+  /// expensive.
+  final double opacity;
+
+  /// Whether the semantic information of the children is always included.
+  ///
+  /// Defaults to false.
+  ///
+  /// When true, regardless of the opacity settings the child semantic
+  /// information is exposed as if the widget were fully visible. This is
+  /// useful in cases where labels may be hidden during animations that
+  /// would otherwise contribute relevant semantics.
+  final bool alwaysIncludeSemantics;
+
+  @override
+  RenderOpacity createRenderObject(BuildContext context) {
+    return RenderOpacity(
+      opacity: opacity,
+      alwaysIncludeSemantics: alwaysIncludeSemantics,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
+    renderObject
+      ..opacity = opacity
+      ..alwaysIncludeSemantics = alwaysIncludeSemantics;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('opacity', opacity));
+    properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
+  }
+}
+
+/// A widget that applies a mask generated by a [Shader] to its child.
+///
+/// For example, [ShaderMask] can be used to gradually fade out the edge
+/// of a child by using a [new ui.Gradient.linear] mask.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=7sUL66pTQ7Q}
+///
+/// {@tool snippet}
+///
+/// This example makes the text look like it is on fire:
+///
+/// ```dart
+/// ShaderMask(
+///   shaderCallback: (Rect bounds) {
+///     return RadialGradient(
+///       center: Alignment.topLeft,
+///       radius: 1.0,
+///       colors: <Color>[Colors.yellow, Colors.deepOrange.shade900],
+///       tileMode: TileMode.mirror,
+///     ).createShader(bounds);
+///   },
+///   child: const Text('I’m burning the memories'),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Opacity], which can apply a uniform alpha effect to its child.
+///  * [CustomPaint], which lets you draw directly on the canvas.
+///  * [DecoratedBox], for another approach at decorating child widgets.
+///  * [BackdropFilter], which applies an image filter to the background.
+class ShaderMask extends SingleChildRenderObjectWidget {
+  /// Creates a widget that applies a mask generated by a [Shader] to its child.
+  ///
+  /// The [shaderCallback] and [blendMode] arguments must not be null.
+  const ShaderMask({
+    Key? key,
+    required this.shaderCallback,
+    this.blendMode = BlendMode.modulate,
+    Widget? child,
+  }) : assert(shaderCallback != null),
+       assert(blendMode != null),
+       super(key: key, child: child);
+
+  /// Called to create the [dart:ui.Shader] that generates the mask.
+  ///
+  /// The shader callback is called with the current size of the child so that
+  /// it can customize the shader to the size and location of the child.
+  ///
+  /// Typically this will use a [LinearGradient], [RadialGradient], or
+  /// [SweepGradient] to create the [dart:ui.Shader], though the
+  /// [dart:ui.ImageShader] class could also be used.
+  final ShaderCallback shaderCallback;
+
+  /// The [BlendMode] to use when applying the shader to the child.
+  ///
+  /// The default, [BlendMode.modulate], is useful for applying an alpha blend
+  /// to the child. Other blend modes can be used to create other effects.
+  final BlendMode blendMode;
+
+  @override
+  RenderShaderMask createRenderObject(BuildContext context) {
+    return RenderShaderMask(
+      shaderCallback: shaderCallback,
+      blendMode: blendMode,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderShaderMask renderObject) {
+    renderObject
+      ..shaderCallback = shaderCallback
+      ..blendMode = blendMode;
+  }
+}
+
+/// A widget that applies a filter to the existing painted content and then
+/// paints [child].
+///
+/// The filter will be applied to all the area within its parent or ancestor
+/// widget's clip. If there's no clip, the filter will be applied to the full
+/// screen.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=dYRs7Q1vfYI}
+///
+/// {@tool snippet}
+/// If the [BackdropFilter] needs to be applied to an area that exactly matches
+/// its child, wraps the [BackdropFilter] with a clip widget that clips exactly
+/// to that child.
+///
+/// ```dart
+/// Stack(
+///   fit: StackFit.expand,
+///   children: <Widget>[
+///     Text('0' * 10000),
+///     Center(
+///       child: ClipRect(  // <-- clips to the 200x200 [Container] below
+///         child: BackdropFilter(
+///           filter: ui.ImageFilter.blur(
+///             sigmaX: 5.0,
+///             sigmaY: 5.0,
+///           ),
+///           child: Container(
+///             alignment: Alignment.center,
+///             width: 200.0,
+///             height: 200.0,
+///             child: Text('Hello World'),
+///           ),
+///         ),
+///       ),
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// This effect is relatively expensive, especially if the filter is non-local,
+/// such as a blur.
+///
+/// If all you want to do is apply an [ImageFilter] to a single widget
+/// (as opposed to applying the filter to everything _beneath_ a widget), use
+/// [ImageFiltered] instead. For that scenario, [ImageFiltered] is both
+/// easier to use and less expensive than [BackdropFilter].
+///
+/// See also:
+///
+///  * [ImageFiltered], which applies an [ImageFilter] to its child.
+///  * [DecoratedBox], which draws a background under (or over) a widget.
+///  * [Opacity], which changes the opacity of the widget itself.
+class BackdropFilter extends SingleChildRenderObjectWidget {
+  /// Creates a backdrop filter.
+  ///
+  /// The [filter] argument must not be null.
+  const BackdropFilter({
+    Key? key,
+    required this.filter,
+    Widget? child,
+  }) : assert(filter != null),
+       super(key: key, child: child);
+
+  /// The image filter to apply to the existing painted content before painting the child.
+  ///
+  /// For example, consider using [ImageFilter.blur] to create a backdrop
+  /// blur effect.
+  final ui.ImageFilter filter;
+
+  @override
+  RenderBackdropFilter createRenderObject(BuildContext context) {
+    return RenderBackdropFilter(filter: filter);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderBackdropFilter renderObject) {
+    renderObject.filter = filter;
+  }
+}
+
+/// A widget that provides a canvas on which to draw during the paint phase.
+///
+/// When asked to paint, [CustomPaint] first asks its [painter] to paint on the
+/// current canvas, then it paints its child, and then, after painting its
+/// child, it asks its [foregroundPainter] to paint. The coordinate system of the
+/// canvas matches the coordinate system of the [CustomPaint] object. The
+/// painters are expected to paint within a rectangle starting at the origin and
+/// encompassing a region of the given size. (If the painters paint outside
+/// those bounds, there might be insufficient memory allocated to rasterize the
+/// painting commands and the resulting behavior is undefined.) To enforce
+/// painting within those bounds, consider wrapping this [CustomPaint] with a
+/// [ClipRect] widget.
+///
+/// Painters are implemented by subclassing [CustomPainter].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=kp14Y4uHpHs}
+///
+/// Because custom paint calls its painters during paint, you cannot call
+/// `setState` or `markNeedsLayout` during the callback (the layout for this
+/// frame has already happened).
+///
+/// Custom painters normally size themselves to their child. If they do not have
+/// a child, they attempt to size themselves to the [size], which defaults to
+/// [Size.zero]. [size] must not be null.
+///
+/// [isComplex] and [willChange] are hints to the compositor's raster cache
+/// and must not be null.
+///
+/// {@tool snippet}
+///
+/// This example shows how the sample custom painter shown at [CustomPainter]
+/// could be used in a [CustomPaint] widget to display a background to some
+/// text.
+///
+/// ```dart
+/// CustomPaint(
+///   painter: Sky(),
+///   child: Center(
+///     child: Text(
+///       'Once upon a time...',
+///       style: const TextStyle(
+///         fontSize: 40.0,
+///         fontWeight: FontWeight.w900,
+///         color: Color(0xFFFFFFFF),
+///       ),
+///     ),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [CustomPainter], the class to extend when creating custom painters.
+///  * [Canvas], the class that a custom painter uses to paint.
+class CustomPaint extends SingleChildRenderObjectWidget {
+  /// Creates a widget that delegates its painting.
+  const CustomPaint({
+    Key? key,
+    this.painter,
+    this.foregroundPainter,
+    this.size = Size.zero,
+    this.isComplex = false,
+    this.willChange = false,
+    Widget? child,
+  }) : assert(size != null),
+       assert(isComplex != null),
+       assert(willChange != null),
+       assert(painter != null || foregroundPainter != null || (!isComplex && !willChange)),
+       super(key: key, child: child);
+
+  /// The painter that paints before the children.
+  final CustomPainter? painter;
+
+  /// The painter that paints after the children.
+  final CustomPainter? foregroundPainter;
+
+  /// The size that this [CustomPaint] should aim for, given the layout
+  /// constraints, if there is no child.
+  ///
+  /// Defaults to [Size.zero].
+  ///
+  /// If there's a child, this is ignored, and the size of the child is used
+  /// instead.
+  final Size size;
+
+  /// Whether the painting is complex enough to benefit from caching.
+  ///
+  /// The compositor contains a raster cache that holds bitmaps of layers in
+  /// order to avoid the cost of repeatedly rendering those layers on each
+  /// frame. If this flag is not set, then the compositor will apply its own
+  /// heuristics to decide whether the this layer is complex enough to benefit
+  /// from caching.
+  ///
+  /// This flag can't be set to true if both [painter] and [foregroundPainter]
+  /// are null because this flag will be ignored in such case.
+  final bool isComplex;
+
+  /// Whether the raster cache should be told that this painting is likely
+  /// to change in the next frame.
+  ///
+  /// This flag can't be set to true if both [painter] and [foregroundPainter]
+  /// are null because this flag will be ignored in such case.
+  final bool willChange;
+
+  @override
+  RenderCustomPaint createRenderObject(BuildContext context) {
+    return RenderCustomPaint(
+      painter: painter,
+      foregroundPainter: foregroundPainter,
+      preferredSize: size,
+      isComplex: isComplex,
+      willChange: willChange,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderCustomPaint renderObject) {
+    renderObject
+      ..painter = painter
+      ..foregroundPainter = foregroundPainter
+      ..preferredSize = size
+      ..isComplex = isComplex
+      ..willChange = willChange;
+  }
+
+  @override
+  void didUnmountRenderObject(RenderCustomPaint renderObject) {
+    renderObject
+      ..painter = null
+      ..foregroundPainter = null;
+  }
+}
+
+/// A widget that clips its child using a rectangle.
+///
+/// By default, [ClipRect] prevents its child from painting outside its
+/// bounds, but the size and location of the clip rect can be customized using a
+/// custom [clipper].
+///
+/// [ClipRect] is commonly used with these widgets, which commonly paint outside
+/// their bounds:
+///
+///  * [CustomPaint]
+///  * [CustomSingleChildLayout]
+///  * [CustomMultiChildLayout]
+///  * [Align] and [Center] (e.g., if [Align.widthFactor] or
+///    [Align.heightFactor] is less than 1.0).
+///  * [OverflowBox]
+///  * [SizedOverflowBox]
+///
+/// {@tool snippet}
+///
+/// For example, by combining a [ClipRect] with an [Align], one can show just
+/// the top half of an [Image]:
+///
+/// ```dart
+/// ClipRect(
+///   child: Align(
+///     alignment: Alignment.topCenter,
+///     heightFactor: 0.5,
+///     child: Image.network(userAvatarUrl),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [CustomClipper], for information about creating custom clips.
+///  * [ClipRRect], for a clip with rounded corners.
+///  * [ClipOval], for an elliptical clip.
+///  * [ClipPath], for an arbitrarily shaped clip.
+class ClipRect extends SingleChildRenderObjectWidget {
+  /// Creates a rectangular clip.
+  ///
+  /// If [clipper] is null, the clip will match the layout size and position of
+  /// the child.
+  ///
+  /// The [clipBehavior] argument must not be null or [Clip.none].
+  const ClipRect({ Key? key, this.clipper, this.clipBehavior = Clip.hardEdge, Widget? child })
+      : assert(clipBehavior != null),
+        super(key: key, child: child);
+
+  /// If non-null, determines which clip to use.
+  final CustomClipper<Rect>? clipper;
+
+  /// {@macro flutter.rendering.ClipRectLayer.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge].
+  final Clip clipBehavior;
+
+  @override
+  RenderClipRect createRenderObject(BuildContext context) {
+    assert(clipBehavior != Clip.none);
+    return RenderClipRect(clipper: clipper, clipBehavior: clipBehavior);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderClipRect renderObject) {
+    assert(clipBehavior != Clip.none);
+    renderObject
+      ..clipper = clipper
+      ..clipBehavior = clipBehavior;
+  }
+
+  @override
+  void didUnmountRenderObject(RenderClipRect renderObject) {
+    renderObject.clipper = null;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<CustomClipper<Rect>>('clipper', clipper, defaultValue: null));
+  }
+}
+
+/// A widget that clips its child using a rounded rectangle.
+///
+/// By default, [ClipRRect] uses its own bounds as the base rectangle for the
+/// clip, but the size and location of the clip can be customized using a custom
+/// [clipper].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=eI43jkQkrvs}
+///
+/// See also:
+///
+///  * [CustomClipper], for information about creating custom clips.
+///  * [ClipRect], for more efficient clips without rounded corners.
+///  * [ClipOval], for an elliptical clip.
+///  * [ClipPath], for an arbitrarily shaped clip.
+class ClipRRect extends SingleChildRenderObjectWidget {
+  /// Creates a rounded-rectangular clip.
+  ///
+  /// The [borderRadius] defaults to [BorderRadius.zero], i.e. a rectangle with
+  /// right-angled corners.
+  ///
+  /// If [clipper] is non-null, then [borderRadius] is ignored.
+  ///
+  /// The [clipBehavior] argument must not be null or [Clip.none].
+  const ClipRRect({
+    Key? key,
+    this.borderRadius = BorderRadius.zero,
+    this.clipper,
+    this.clipBehavior = Clip.antiAlias,
+    Widget? child,
+  }) : assert(borderRadius != null || clipper != null),
+       assert(clipBehavior != null),
+       super(key: key, child: child);
+
+  /// The border radius of the rounded corners.
+  ///
+  /// Values are clamped so that horizontal and vertical radii sums do not
+  /// exceed width/height.
+  ///
+  /// This value is ignored if [clipper] is non-null.
+  final BorderRadius? borderRadius;
+
+  /// If non-null, determines which clip to use.
+  final CustomClipper<RRect>? clipper;
+
+  /// {@macro flutter.rendering.ClipRectLayer.clipBehavior}
+  ///
+  /// Defaults to [Clip.antiAlias].
+  final Clip clipBehavior;
+
+  @override
+  RenderClipRRect createRenderObject(BuildContext context) {
+    assert(clipBehavior != Clip.none);
+    return RenderClipRRect(borderRadius: borderRadius!, clipper: clipper, clipBehavior: clipBehavior);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderClipRRect renderObject) {
+    assert(clipBehavior != Clip.none);
+    renderObject
+      ..borderRadius = borderRadius!
+      ..clipBehavior = clipBehavior
+      ..clipper = clipper;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius, showName: false, defaultValue: null));
+    properties.add(DiagnosticsProperty<CustomClipper<RRect>>('clipper', clipper, defaultValue: null));
+  }
+}
+
+/// A widget that clips its child using an oval.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=vzWWDO6whIM}
+///
+/// By default, inscribes an axis-aligned oval into its layout dimensions and
+/// prevents its child from painting outside that oval, but the size and
+/// location of the clip oval can be customized using a custom [clipper].
+///
+/// See also:
+///
+///  * [CustomClipper], for information about creating custom clips.
+///  * [ClipRect], for more efficient clips without rounded corners.
+///  * [ClipRRect], for a clip with rounded corners.
+///  * [ClipPath], for an arbitrarily shaped clip.
+class ClipOval extends SingleChildRenderObjectWidget {
+  /// Creates an oval-shaped clip.
+  ///
+  /// If [clipper] is null, the oval will be inscribed into the layout size and
+  /// position of the child.
+  ///
+  /// The [clipBehavior] argument must not be null or [Clip.none].
+  const ClipOval({Key? key, this.clipper, this.clipBehavior = Clip.antiAlias, Widget? child})
+      : assert(clipBehavior != null),
+        super(key: key, child: child);
+
+  /// If non-null, determines which clip to use.
+  ///
+  /// The delegate returns a rectangle that describes the axis-aligned
+  /// bounding box of the oval. The oval's axes will themselves also
+  /// be axis-aligned.
+  ///
+  /// If the [clipper] delegate is null, then the oval uses the
+  /// widget's bounding box (the layout dimensions of the render
+  /// object) instead.
+  final CustomClipper<Rect>? clipper;
+
+  /// {@macro flutter.rendering.ClipRectLayer.clipBehavior}
+  ///
+  /// Defaults to [Clip.antiAlias].
+  final Clip clipBehavior;
+
+  @override
+  RenderClipOval createRenderObject(BuildContext context) {
+    assert(clipBehavior != Clip.none);
+    return RenderClipOval(clipper: clipper, clipBehavior: clipBehavior);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderClipOval renderObject) {
+    assert(clipBehavior != Clip.none);
+    renderObject
+      ..clipper = clipper
+      ..clipBehavior = clipBehavior;
+  }
+
+  @override
+  void didUnmountRenderObject(RenderClipOval renderObject) {
+    renderObject.clipper = null;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<CustomClipper<Rect>>('clipper', clipper, defaultValue: null));
+  }
+}
+
+/// A widget that clips its child using a path.
+///
+/// Calls a callback on a delegate whenever the widget is to be
+/// painted. The callback returns a path and the widget prevents the
+/// child from painting outside the path.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=oAUebVIb-7s}
+///
+/// Clipping to a path is expensive. Certain shapes have more
+/// optimized widgets:
+///
+///  * To clip to a rectangle, consider [ClipRect].
+///  * To clip to an oval or circle, consider [ClipOval].
+///  * To clip to a rounded rectangle, consider [ClipRRect].
+///
+/// To clip to a particular [ShapeBorder], consider using either the
+/// [ClipPath.shape] static method or the [ShapeBorderClipper] custom clipper
+/// class.
+class ClipPath extends SingleChildRenderObjectWidget {
+  /// Creates a path clip.
+  ///
+  /// If [clipper] is null, the clip will be a rectangle that matches the layout
+  /// size and location of the child. However, rather than use this default,
+  /// consider using a [ClipRect], which can achieve the same effect more
+  /// efficiently.
+  ///
+  /// The [clipBehavior] argument must not be null or [Clip.none].
+  const ClipPath({
+    Key? key,
+    this.clipper,
+    this.clipBehavior = Clip.antiAlias,
+    Widget? child,
+  }) : assert(clipBehavior != null),
+       super(key: key, child: child);
+
+  /// Creates a shape clip.
+  ///
+  /// Uses a [ShapeBorderClipper] to configure the [ClipPath] to clip to the
+  /// given [ShapeBorder].
+  static Widget shape({
+    Key? key,
+    required ShapeBorder shape,
+    Clip clipBehavior = Clip.antiAlias,
+    Widget? child,
+  }) {
+    assert(clipBehavior != null);
+    assert(clipBehavior != Clip.none);
+    assert(shape != null);
+    return Builder(
+      key: key,
+      builder: (BuildContext context) {
+        return ClipPath(
+          clipper: ShapeBorderClipper(
+            shape: shape,
+            textDirection: Directionality.maybeOf(context),
+          ),
+          clipBehavior: clipBehavior,
+          child: child,
+        );
+      },
+    );
+  }
+
+  /// If non-null, determines which clip to use.
+  ///
+  /// The default clip, which is used if this property is null, is the
+  /// bounding box rectangle of the widget. [ClipRect] is a more
+  /// efficient way of obtaining that effect.
+  final CustomClipper<Path>? clipper;
+
+  /// {@macro flutter.rendering.ClipRectLayer.clipBehavior}
+  ///
+  /// Defaults to [Clip.antiAlias].
+  final Clip clipBehavior;
+
+  @override
+  RenderClipPath createRenderObject(BuildContext context) {
+    assert(clipBehavior != Clip.none);
+    return RenderClipPath(clipper: clipper, clipBehavior: clipBehavior);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderClipPath renderObject) {
+    assert(clipBehavior != Clip.none);
+    renderObject
+      ..clipper = clipper
+      ..clipBehavior = clipBehavior;
+  }
+
+  @override
+  void didUnmountRenderObject(RenderClipPath renderObject) {
+    renderObject.clipper = null;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<CustomClipper<Path>>('clipper', clipper, defaultValue: null));
+  }
+}
+
+/// A widget representing a physical layer that clips its children to a shape.
+///
+/// Physical layers cast shadows based on an [elevation] which is nominally in
+/// logical pixels, coming vertically out of the rendering surface.
+///
+/// For shapes that cannot be expressed as a rectangle with rounded corners use
+/// [PhysicalShape].
+///
+/// See also:
+///
+///  * [AnimatedPhysicalModel], which animates property changes smoothly over
+///    a given duration.
+///  * [DecoratedBox], which can apply more arbitrary shadow effects.
+///  * [ClipRect], which applies a clip to its child.
+class PhysicalModel extends SingleChildRenderObjectWidget {
+  /// Creates a physical model with a rounded-rectangular clip.
+  ///
+  /// The [color] is required; physical things have a color.
+  ///
+  /// The [shape], [elevation], [color], [clipBehavior], and [shadowColor] must
+  /// not be null. Additionally, the [elevation] must be non-negative.
+  const PhysicalModel({
+    Key? key,
+    this.shape = BoxShape.rectangle,
+    this.clipBehavior = Clip.none,
+    this.borderRadius,
+    this.elevation = 0.0,
+    required this.color,
+    this.shadowColor = const Color(0xFF000000),
+    Widget? child,
+  }) : assert(shape != null),
+       assert(elevation != null && elevation >= 0.0),
+       assert(color != null),
+       assert(shadowColor != null),
+       assert(clipBehavior != null),
+       super(key: key, child: child);
+
+  /// The type of shape.
+  final BoxShape shape;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none].
+  final Clip clipBehavior;
+
+  /// The border radius of the rounded corners.
+  ///
+  /// Values are clamped so that horizontal and vertical radii sums do not
+  /// exceed width/height.
+  ///
+  /// This is ignored if the [shape] is not [BoxShape.rectangle].
+  final BorderRadius? borderRadius;
+
+  /// The z-coordinate relative to the parent at which to place this physical
+  /// object.
+  ///
+  /// The value is non-negative.
+  final double elevation;
+
+  /// The background color.
+  final Color color;
+
+  /// The shadow color.
+  final Color shadowColor;
+
+  @override
+  RenderPhysicalModel createRenderObject(BuildContext context) {
+    return RenderPhysicalModel(
+      shape: shape,
+      clipBehavior: clipBehavior,
+      borderRadius: borderRadius,
+      elevation: elevation, color: color,
+      shadowColor: shadowColor,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderPhysicalModel renderObject) {
+    renderObject
+      ..shape = shape
+      ..clipBehavior = clipBehavior
+      ..borderRadius = borderRadius
+      ..elevation = elevation
+      ..color = color
+      ..shadowColor = shadowColor;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<BoxShape>('shape', shape));
+    properties.add(DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius));
+    properties.add(DoubleProperty('elevation', elevation));
+    properties.add(ColorProperty('color', color));
+    properties.add(ColorProperty('shadowColor', shadowColor));
+  }
+}
+
+/// A widget representing a physical layer that clips its children to a path.
+///
+/// Physical layers cast shadows based on an [elevation] which is nominally in
+/// logical pixels, coming vertically out of the rendering surface.
+///
+/// [PhysicalModel] does the same but only supports shapes that can be expressed
+/// as rectangles with rounded corners.
+///
+/// See also:
+///
+///  * [ShapeBorderClipper], which converts a [ShapeBorder] to a [CustomClipper], as
+///    needed by this widget.
+class PhysicalShape extends SingleChildRenderObjectWidget {
+  /// Creates a physical model with an arbitrary shape clip.
+  ///
+  /// The [color] is required; physical things have a color.
+  ///
+  /// The [clipper], [elevation], [color], [clipBehavior], and [shadowColor]
+  /// must not be null. Additionally, the [elevation] must be non-negative.
+  const PhysicalShape({
+    Key? key,
+    required this.clipper,
+    this.clipBehavior = Clip.none,
+    this.elevation = 0.0,
+    required this.color,
+    this.shadowColor = const Color(0xFF000000),
+    Widget? child,
+  }) : assert(clipper != null),
+       assert(clipBehavior != null),
+       assert(elevation != null && elevation >= 0.0),
+       assert(color != null),
+       assert(shadowColor != null),
+       super(key: key, child: child);
+
+  /// Determines which clip to use.
+  ///
+  /// If the path in question is expressed as a [ShapeBorder] subclass,
+  /// consider using the [ShapeBorderClipper] delegate class to adapt the
+  /// shape for use with this widget.
+  final CustomClipper<Path> clipper;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none].
+  final Clip clipBehavior;
+
+  /// The z-coordinate relative to the parent at which to place this physical
+  /// object.
+  ///
+  /// The value is non-negative.
+  final double elevation;
+
+  /// The background color.
+  final Color color;
+
+  /// When elevation is non zero the color to use for the shadow color.
+  final Color shadowColor;
+
+  @override
+  RenderPhysicalShape createRenderObject(BuildContext context) {
+    return RenderPhysicalShape(
+      clipper: clipper,
+      clipBehavior: clipBehavior,
+      elevation: elevation,
+      color: color,
+      shadowColor: shadowColor,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderPhysicalShape renderObject) {
+    renderObject
+      ..clipper = clipper
+      ..clipBehavior = clipBehavior
+      ..elevation = elevation
+      ..color = color
+      ..shadowColor = shadowColor;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<CustomClipper<Path>>('clipper', clipper));
+    properties.add(DoubleProperty('elevation', elevation));
+    properties.add(ColorProperty('color', color));
+    properties.add(ColorProperty('shadowColor', shadowColor));
+  }
+}
+
+// POSITIONING AND SIZING NODES
+
+/// A widget that applies a transformation before painting its child.
+///
+/// Unlike [RotatedBox], which applies a rotation prior to layout, this object
+/// applies its transformation just prior to painting, which means the
+/// transformation is not taken into account when calculating how much space
+/// this widget's child (and thus this widget) consumes.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=9z_YNlRlWfA}
+///
+/// {@tool snippet}
+///
+/// This example rotates and skews an orange box containing text, keeping the
+/// top right corner pinned to its original position.
+///
+/// ```dart
+/// Container(
+///   color: Colors.black,
+///   child: Transform(
+///     alignment: Alignment.topRight,
+///     transform: Matrix4.skewY(0.3)..rotateZ(-math.pi / 12.0),
+///     child: Container(
+///       padding: const EdgeInsets.all(8.0),
+///       color: const Color(0xFFE8581C),
+///       child: const Text('Apartment for rent!'),
+///     ),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [RotatedBox], which rotates the child widget during layout, not just
+///    during painting.
+///  * [FractionalTranslation], which applies a translation to the child
+///    that is relative to the child's size.
+///  * [FittedBox], which sizes and positions its child widget to fit the parent
+///    according to a given [BoxFit] discipline.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Transform extends SingleChildRenderObjectWidget {
+  /// Creates a widget that transforms its child.
+  ///
+  /// The [transform] argument must not be null.
+  const Transform({
+    Key? key,
+    required this.transform,
+    this.origin,
+    this.alignment,
+    this.transformHitTests = true,
+    Widget? child,
+  }) : assert(transform != null),
+       super(key: key, child: child);
+
+  /// Creates a widget that transforms its child using a rotation around the
+  /// center.
+  ///
+  /// The `angle` argument must not be null. It gives the rotation in clockwise
+  /// radians.
+  ///
+  /// {@tool snippet}
+  ///
+  /// This example rotates an orange box containing text around its center by
+  /// fifteen degrees.
+  ///
+  /// ```dart
+  /// Transform.rotate(
+  ///   angle: -math.pi / 12.0,
+  ///   child: Container(
+  ///     padding: const EdgeInsets.all(8.0),
+  ///     color: const Color(0xFFE8581C),
+  ///     child: const Text('Apartment for rent!'),
+  ///   ),
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [RotationTransition], which animates changes in rotation smoothly
+  ///    over a given duration.
+  Transform.rotate({
+    Key? key,
+    required double angle,
+    this.origin,
+    this.alignment = Alignment.center,
+    this.transformHitTests = true,
+    Widget? child,
+  }) : transform = Matrix4.rotationZ(angle),
+       super(key: key, child: child);
+
+  /// Creates a widget that transforms its child using a translation.
+  ///
+  /// The `offset` argument must not be null. It specifies the translation.
+  ///
+  /// {@tool snippet}
+  ///
+  /// This example shifts the silver-colored child down by fifteen pixels.
+  ///
+  /// ```dart
+  /// Transform.translate(
+  ///   offset: const Offset(0.0, 15.0),
+  ///   child: Container(
+  ///     padding: const EdgeInsets.all(8.0),
+  ///     color: const Color(0xFF7F7F7F),
+  ///     child: const Text('Quarter'),
+  ///   ),
+  /// )
+  /// ```
+  /// {@end-tool}
+  Transform.translate({
+    Key? key,
+    required Offset offset,
+    this.transformHitTests = true,
+    Widget? child,
+  }) : transform = Matrix4.translationValues(offset.dx, offset.dy, 0.0),
+       origin = null,
+       alignment = null,
+       super(key: key, child: child);
+
+  /// Creates a widget that scales its child uniformly.
+  ///
+  /// The `scale` argument must not be null. It gives the scalar by which
+  /// to multiply the `x` and `y` axes.
+  ///
+  /// The [alignment] controls the origin of the scale; by default, this is
+  /// the center of the box.
+  ///
+  /// {@tool snippet}
+  ///
+  /// This example shrinks an orange box containing text such that each dimension
+  /// is half the size it would otherwise be.
+  ///
+  /// ```dart
+  /// Transform.scale(
+  ///   scale: 0.5,
+  ///   child: Container(
+  ///     padding: const EdgeInsets.all(8.0),
+  ///     color: const Color(0xFFE8581C),
+  ///     child: const Text('Bad Idea Bears'),
+  ///   ),
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [ScaleTransition], which animates changes in scale smoothly
+  ///    over a given duration.
+  Transform.scale({
+    Key? key,
+    required double scale,
+    this.origin,
+    this.alignment = Alignment.center,
+    this.transformHitTests = true,
+    Widget? child,
+  }) : transform = Matrix4.diagonal3Values(scale, scale, 1.0),
+       super(key: key, child: child);
+
+  /// The matrix to transform the child by during painting.
+  final Matrix4 transform;
+
+  /// The origin of the coordinate system (relative to the upper left corner of
+  /// this render object) in which to apply the matrix.
+  ///
+  /// Setting an origin is equivalent to conjugating the transform matrix by a
+  /// translation. This property is provided just for convenience.
+  final Offset? origin;
+
+  /// The alignment of the origin, relative to the size of the box.
+  ///
+  /// This is equivalent to setting an origin based on the size of the box.
+  /// If it is specified at the same time as the [origin], both are applied.
+  ///
+  /// An [AlignmentDirectional.centerStart] value is the same as an [Alignment]
+  /// whose [Alignment.x] value is `-1.0` if [Directionality.of] returns
+  /// [TextDirection.ltr], and `1.0` if [Directionality.of] returns
+  /// [TextDirection.rtl].	 Similarly [AlignmentDirectional.centerEnd] is the
+  /// same as an [Alignment] whose [Alignment.x] value is `1.0` if
+  /// [Directionality.of] returns	 [TextDirection.ltr], and `-1.0` if
+  /// [Directionality.of] returns [TextDirection.rtl].
+  final AlignmentGeometry? alignment;
+
+  /// Whether to apply the transformation when performing hit tests.
+  final bool transformHitTests;
+
+  @override
+  RenderTransform createRenderObject(BuildContext context) {
+    return RenderTransform(
+      transform: transform,
+      origin: origin,
+      alignment: alignment,
+      textDirection: Directionality.maybeOf(context),
+      transformHitTests: transformHitTests,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderTransform renderObject) {
+    renderObject
+      ..transform = transform
+      ..origin = origin
+      ..alignment = alignment
+      ..textDirection = Directionality.maybeOf(context)
+      ..transformHitTests = transformHitTests;
+  }
+}
+
+/// A widget that can be targeted by a [CompositedTransformFollower].
+///
+/// When this widget is composited during the compositing phase (which comes
+/// after the paint phase, as described in [WidgetsBinding.drawFrame]), it
+/// updates the [link] object so that any [CompositedTransformFollower] widgets
+/// that are subsequently composited in the same frame and were given the same
+/// [LayerLink] can position themselves at the same screen location.
+///
+/// A single [CompositedTransformTarget] can be followed by multiple
+/// [CompositedTransformFollower] widgets.
+///
+/// The [CompositedTransformTarget] must come earlier in the paint order than
+/// any linked [CompositedTransformFollower]s.
+///
+/// See also:
+///
+///  * [CompositedTransformFollower], the widget that can target this one.
+///  * [LeaderLayer], the layer that implements this widget's logic.
+class CompositedTransformTarget extends SingleChildRenderObjectWidget {
+  /// Creates a composited transform target widget.
+  ///
+  /// The [link] property must not be null, and must not be currently being used
+  /// by any other [CompositedTransformTarget] object that is in the tree.
+  const CompositedTransformTarget({
+    Key? key,
+    required this.link,
+    Widget? child,
+  }) : assert(link != null),
+       super(key: key, child: child);
+
+  /// The link object that connects this [CompositedTransformTarget] with one or
+  /// more [CompositedTransformFollower]s.
+  ///
+  /// This property must not be null. The object must not be associated with
+  /// another [CompositedTransformTarget] that is also being painted.
+  final LayerLink link;
+
+  @override
+  RenderLeaderLayer createRenderObject(BuildContext context) {
+    return RenderLeaderLayer(
+      link: link,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderLeaderLayer renderObject) {
+    renderObject.link = link;
+  }
+}
+
+/// A widget that follows a [CompositedTransformTarget].
+///
+/// When this widget is composited during the compositing phase (which comes
+/// after the paint phase, as described in [WidgetsBinding.drawFrame]), it
+/// applies a transformation that brings [targetAnchor] of the linked
+/// [CompositedTransformTarget] and [followerAnchor] of this widget together.
+/// The two anchor points will have the same global coordinates, unless [offset]
+/// is not [Offset.zero], in which case [followerAnchor] will be offset by
+/// [offset] in the linked [CompositedTransformTarget]'s coordinate space.
+///
+/// The [LayerLink] object used as the [link] must be the same object as that
+/// provided to the matching [CompositedTransformTarget].
+///
+/// The [CompositedTransformTarget] must come earlier in the paint order than
+/// this [CompositedTransformFollower].
+///
+/// Hit testing on descendants of this widget will only work if the target
+/// position is within the box that this widget's parent considers to be
+/// hittable. If the parent covers the screen, this is trivially achievable, so
+/// this widget is usually used as the root of an [OverlayEntry] in an app-wide
+/// [Overlay] (e.g. as created by the [MaterialApp] widget's [Navigator]).
+///
+/// See also:
+///
+///  * [CompositedTransformTarget], the widget that this widget can target.
+///  * [FollowerLayer], the layer that implements this widget's logic.
+///  * [Transform], which applies an arbitrary transform to a child.
+class CompositedTransformFollower extends SingleChildRenderObjectWidget {
+  /// Creates a composited transform target widget.
+  ///
+  /// The [link] property must not be null. If it was also provided to a
+  /// [CompositedTransformTarget], that widget must come earlier in the paint
+  /// order.
+  ///
+  /// The [showWhenUnlinked] and [offset] properties must also not be null.
+  const CompositedTransformFollower({
+    Key? key,
+    required this.link,
+    this.showWhenUnlinked = true,
+    this.offset = Offset.zero,
+    this.targetAnchor = Alignment.topLeft,
+    this.followerAnchor = Alignment.topLeft,
+    Widget? child,
+  }) : assert(link != null),
+       assert(showWhenUnlinked != null),
+       assert(offset != null),
+       assert(targetAnchor != null),
+       assert(followerAnchor != null),
+       super(key: key, child: child);
+
+  /// The link object that connects this [CompositedTransformFollower] with a
+  /// [CompositedTransformTarget].
+  ///
+  /// This property must not be null.
+  final LayerLink link;
+
+  /// Whether to show the widget's contents when there is no corresponding
+  /// [CompositedTransformTarget] with the same [link].
+  ///
+  /// When the widget is linked, the child is positioned such that it has the
+  /// same global position as the linked [CompositedTransformTarget].
+  ///
+  /// When the widget is not linked, then: if [showWhenUnlinked] is true, the
+  /// child is visible and not repositioned; if it is false, then child is
+  /// hidden.
+  final bool showWhenUnlinked;
+
+  /// The anchor point on the linked [CompositedTransformTarget] that
+  /// [followerAnchor] will line up with.
+  ///
+  /// {@template flutter.widgets.CompositedTransformFollower.targetAnchor}
+  /// For example, when [targetAnchor] and [followerAnchor] are both
+  /// [Alignment.topLeft], this widget will be top left aligned with the linked
+  /// [CompositedTransformTarget]. When [targetAnchor] is
+  /// [Alignment.bottomLeft] and [followerAnchor] is [Alignment.topLeft], this
+  /// widget will be left aligned with the linked [CompositedTransformTarget],
+  /// and its top edge will line up with the [CompositedTransformTarget]'s
+  /// bottom edge.
+  /// {@endtemplate}
+  ///
+  /// Defaults to [Alignment.topLeft].
+  final Alignment targetAnchor;
+
+  /// The anchor point on this widget that will line up with [followerAnchor] on
+  /// the linked [CompositedTransformTarget].
+  ///
+  /// {@macro flutter.widgets.CompositedTransformFollower.targetAnchor}
+  ///
+  /// Defaults to [Alignment.topLeft].
+  final Alignment followerAnchor;
+
+  /// The additional offset to apply to the [targetAnchor] of the linked
+  /// [CompositedTransformTarget] to obtain this widget's [followerAnchor]
+  /// position.
+  final Offset offset;
+
+  @override
+  RenderFollowerLayer createRenderObject(BuildContext context) {
+    return RenderFollowerLayer(
+      link: link,
+      showWhenUnlinked: showWhenUnlinked,
+      offset: offset,
+      leaderAnchor: targetAnchor,
+      followerAnchor: followerAnchor,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderFollowerLayer renderObject) {
+    renderObject
+      ..link = link
+      ..showWhenUnlinked = showWhenUnlinked
+      ..offset = offset
+      ..leaderAnchor = targetAnchor
+      ..followerAnchor = followerAnchor;
+  }
+}
+
+/// Scales and positions its child within itself according to [fit].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=T4Uehk3_wlY}
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_center_no_null_safety}
+///
+/// In this example, the image is stretched to fill the entire [Container], which would
+/// not happen normally without using FittedBox.
+///
+/// ```dart
+/// Widget build(BuildContext) {
+///   return Container(
+///     height: 400,
+///     width: 300,
+///     color: Colors.red,
+///     child: FittedBox(
+///       child: Image.network('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'),
+///       fit: BoxFit.fill,
+///     ),
+///   );
+/// }
+/// ```
+///
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Transform], which applies an arbitrary transform to its child widget at
+///    paint time.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class FittedBox extends SingleChildRenderObjectWidget {
+  /// Creates a widget that scales and positions its child within itself according to [fit].
+  ///
+  /// The [fit] and [alignment] arguments must not be null.
+  const FittedBox({
+    Key? key,
+    this.fit = BoxFit.contain,
+    this.alignment = Alignment.center,
+    this.clipBehavior = Clip.none,
+    Widget? child,
+  }) : assert(fit != null),
+       assert(alignment != null),
+       assert(clipBehavior != null),
+       super(key: key, child: child);
+
+  /// How to inscribe the child into the space allocated during layout.
+  final BoxFit fit;
+
+  /// How to align the child within its parent's bounds.
+  ///
+  /// An alignment of (-1.0, -1.0) aligns the child to the top-left corner of its
+  /// parent's bounds. An alignment of (1.0, 0.0) aligns the child to the middle
+  /// of the right edge of its parent's bounds.
+  ///
+  /// Defaults to [Alignment.center].
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], a class with convenient constants typically used to
+  ///    specify an [AlignmentGeometry].
+  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
+  ///    relative to text direction.
+  final AlignmentGeometry alignment;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none].
+  final Clip clipBehavior;
+
+  @override
+  RenderFittedBox createRenderObject(BuildContext context) {
+    return RenderFittedBox(
+      fit: fit,
+      alignment: alignment,
+      textDirection: Directionality.maybeOf(context),
+      clipBehavior: clipBehavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderFittedBox renderObject) {
+    renderObject
+      ..fit = fit
+      ..alignment = alignment
+      ..textDirection = Directionality.maybeOf(context)
+      ..clipBehavior = clipBehavior;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<BoxFit>('fit', fit));
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
+  }
+}
+
+/// Applies a translation transformation before painting its child.
+///
+/// The translation is expressed as a [Offset] scaled to the child's size. For
+/// example, an [Offset] with a `dx` of 0.25 will result in a horizontal
+/// translation of one quarter the width of the child.
+///
+/// Hit tests will only be detected inside the bounds of the
+/// [FractionalTranslation], even if the contents are offset such that
+/// they overflow.
+///
+/// See also:
+///
+///  * [Transform], which applies an arbitrary transform to its child widget at
+///    paint time.
+///  * [new Transform.translate], which applies an absolute offset translation
+///    transformation instead of an offset scaled to the child.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class FractionalTranslation extends SingleChildRenderObjectWidget {
+  /// Creates a widget that translates its child's painting.
+  ///
+  /// The [translation] argument must not be null.
+  const FractionalTranslation({
+    Key? key,
+    required this.translation,
+    this.transformHitTests = true,
+    Widget? child,
+  }) : assert(translation != null),
+       super(key: key, child: child);
+
+  /// The translation to apply to the child, scaled to the child's size.
+  ///
+  /// For example, an [Offset] with a `dx` of 0.25 will result in a horizontal
+  /// translation of one quarter the width of the child.
+  final Offset translation;
+
+  /// Whether to apply the translation when performing hit tests.
+  final bool transformHitTests;
+
+  @override
+  RenderFractionalTranslation createRenderObject(BuildContext context) {
+    return RenderFractionalTranslation(
+      translation: translation,
+      transformHitTests: transformHitTests,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderFractionalTranslation renderObject) {
+    renderObject
+      ..translation = translation
+      ..transformHitTests = transformHitTests;
+  }
+}
+
+/// A widget that rotates its child by a integral number of quarter turns.
+///
+/// Unlike [Transform], which applies a transform just prior to painting,
+/// this object applies its rotation prior to layout, which means the entire
+/// rotated box consumes only as much space as required by the rotated child.
+///
+/// {@tool snippet}
+///
+/// This snippet rotates the child (some [Text]) so that it renders from bottom
+/// to top, like an axis label on a graph:
+///
+/// ```dart
+/// RotatedBox(
+///   quarterTurns: 3,
+///   child: const Text('Hello World!'),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Transform], which is a paint effect that allows you to apply an
+///    arbitrary transform to a child.
+///  * [new Transform.rotate], which applies a rotation paint effect.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class RotatedBox extends SingleChildRenderObjectWidget {
+  /// A widget that rotates its child.
+  ///
+  /// The [quarterTurns] argument must not be null.
+  const RotatedBox({
+    Key? key,
+    required this.quarterTurns,
+    Widget? child,
+  }) : assert(quarterTurns != null),
+       super(key: key, child: child);
+
+  /// The number of clockwise quarter turns the child should be rotated.
+  final int quarterTurns;
+
+  @override
+  RenderRotatedBox createRenderObject(BuildContext context) => RenderRotatedBox(quarterTurns: quarterTurns);
+
+  @override
+  void updateRenderObject(BuildContext context, RenderRotatedBox renderObject) {
+    renderObject.quarterTurns = quarterTurns;
+  }
+}
+
+/// A widget that insets its child by the given padding.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=oD5RtLhhubg}
+///
+/// When passing layout constraints to its child, padding shrinks the
+/// constraints by the given padding, causing the child to layout at a smaller
+/// size. Padding then sizes itself to its child's size, inflated by the
+/// padding, effectively creating empty space around the child.
+///
+/// {@tool snippet}
+///
+/// This snippet creates "Hello World!" [Text] inside a [Card] that is indented
+/// by sixteen pixels in each direction.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/padding.png)
+///
+/// ```dart
+/// const Card(
+///   child: Padding(
+///     padding: EdgeInsets.all(16.0),
+///     child: Text('Hello World!'),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ## Design discussion
+///
+/// ### Why use a [Padding] widget rather than a [Container] with a [Container.padding] property?
+///
+/// There isn't really any difference between the two. If you supply a
+/// [Container.padding] argument, [Container] simply builds a [Padding] widget
+/// for you.
+///
+/// [Container] doesn't implement its properties directly. Instead, [Container]
+/// combines a number of simpler widgets together into a convenient package. For
+/// example, the [Container.padding] property causes the container to build a
+/// [Padding] widget and the [Container.decoration] property causes the
+/// container to build a [DecoratedBox] widget. If you find [Container]
+/// convenient, feel free to use it. If not, feel free to build these simpler
+/// widgets in whatever combination meets your needs.
+///
+/// In fact, the majority of widgets in Flutter are simply combinations of other
+/// simpler widgets. Composition, rather than inheritance, is the primary
+/// mechanism for building up widgets.
+///
+/// See also:
+///
+///  * [AnimatedPadding], which animates changes in [padding] over a given
+///    duration.
+///  * [EdgeInsets], the class that is used to describe the padding dimensions.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Padding extends SingleChildRenderObjectWidget {
+  /// Creates a widget that insets its child.
+  ///
+  /// The [padding] argument must not be null.
+  const Padding({
+    Key? key,
+    required this.padding,
+    Widget? child,
+  }) : assert(padding != null),
+       super(key: key, child: child);
+
+  /// The amount of space by which to inset the child.
+  final EdgeInsetsGeometry padding;
+
+  @override
+  RenderPadding createRenderObject(BuildContext context) {
+    return RenderPadding(
+      padding: padding,
+      textDirection: Directionality.maybeOf(context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderPadding renderObject) {
+    renderObject
+      ..padding = padding
+      ..textDirection = Directionality.maybeOf(context);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
+  }
+}
+
+/// A widget that aligns its child within itself and optionally sizes itself
+/// based on the child's size.
+///
+/// For example, to align a box at the bottom right, you would pass this box a
+/// tight constraint that is bigger than the child's natural size,
+/// with an alignment of [Alignment.bottomRight].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=g2E7yl3MwMk}
+///
+/// This widget will be as big as possible if its dimensions are constrained and
+/// [widthFactor] and [heightFactor] are null. If a dimension is unconstrained
+/// and the corresponding size factor is null then the widget will match its
+/// child's size in that dimension. If a size factor is non-null then the
+/// corresponding dimension of this widget will be the product of the child's
+/// dimension and the size factor. For example if widthFactor is 2.0 then
+/// the width of this widget will always be twice its child's width.
+///
+/// ## How it works
+///
+/// The [alignment] property describes a point in the `child`'s coordinate system
+/// and a different point in the coordinate system of this widget. The [Align]
+/// widget positions the `child` such that both points are lined up on top of
+/// each other.
+///
+/// {@tool snippet}
+/// The [Align] widget in this example uses one of the defined constants from
+/// [Alignment], [Alignment.topRight]. This places the [FlutterLogo] in the top
+/// right corner of the parent blue [Container].
+///
+/// ![A blue square container with the Flutter logo in the top right corner.](https://flutter.github.io/assets-for-api-docs/assets/widgets/align_constant.png)
+///
+/// ```dart
+/// Center(
+///   child: Container(
+///     height: 120.0,
+///     width: 120.0,
+///     color: Colors.blue[50],
+///     child: Align(
+///       alignment: Alignment.topRight,
+///       child: FlutterLogo(
+///         size: 60,
+///       ),
+///     ),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+/// The [Alignment] used in the following example defines a single point:
+///
+///   * (0.2 * width of [FlutterLogo]/2 + width of [FlutterLogo]/2, 0.6 * height
+///   of [FlutterLogo]/2 + height of [FlutterLogo]/2) = (36.0, 48.0).
+///
+/// The [Alignment] class uses a coordinate system with an origin in the center
+/// of the [Container], as shown with the [Icon] above. [Align] will place the
+/// [FlutterLogo] at (36.0, 48.0) according to this coordinate system.
+///
+/// ![A blue square container with the Flutter logo positioned according to the
+/// Alignment specified above. A point is marked at the center of the container
+/// for the origin of the Alignment coordinate system.](https://flutter.github.io/assets-for-api-docs/assets/widgets/align_alignment.png)
+///
+/// ```dart
+/// Center(
+///   child: Container(
+///     height: 120.0,
+///     width: 120.0,
+///     color: Colors.blue[50],
+///     child: Align(
+///       alignment: Alignment(0.2, 0.6),
+///       child: FlutterLogo(
+///         size: 60,
+///       ),
+///     ),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+/// The [FractionalOffset] used in the following example defines two points:
+///
+///   * (0.2 * width of [FlutterLogo], 0.6 * height of [FlutterLogo]) = (12.0, 36.0)
+///     in the coordinate system of the blue container.
+///   * (0.2 * width of [Align], 0.6 * height of [Align]) = (24.0, 72.0) in the
+///     coordinate system of the [Align] widget.
+///
+/// The [Align] widget positions the [FlutterLogo] such that the two points are on
+/// top of each other. In this example, the top left of the [FlutterLogo] will
+/// be placed at (24.0, 72.0) - (12.0, 36.0) = (12.0, 36.0) from the top left of
+/// the [Align] widget.
+///
+/// The [FractionalOffset] class uses a coordinate system with an origin in the top-left
+/// corner of the [Container] in difference to the center-oriented system used in
+/// the example above with [Alignment].
+///
+/// ![A blue square container with the Flutter logo positioned according to the
+/// FractionalOffset specified above. A point is marked at the top left corner
+/// of the container for the origin of the FractionalOffset coordinate system.](https://flutter.github.io/assets-for-api-docs/assets/widgets/align_fractional_offset.png)
+///
+/// ```dart
+/// Center(
+///   child: Container(
+///     height: 120.0,
+///     width: 120.0,
+///     color: Colors.blue[50],
+///     child: Align(
+///       alignment: FractionalOffset(0.2, 0.6),
+///       child: FlutterLogo(
+///         size: 60,
+///       ),
+///     ),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AnimatedAlign], which animates changes in [alignment] smoothly over a
+///    given duration.
+///  * [CustomSingleChildLayout], which uses a delegate to control the layout of
+///    a single child.
+///  * [Center], which is the same as [Align] but with the [alignment] always
+///    set to [Alignment.center].
+///  * [FractionallySizedBox], which sizes its child based on a fraction of its
+///    own size and positions the child according to an [Alignment] value.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Align extends SingleChildRenderObjectWidget {
+  /// Creates an alignment widget.
+  ///
+  /// The alignment defaults to [Alignment.center].
+  const Align({
+    Key? key,
+    this.alignment = Alignment.center,
+    this.widthFactor,
+    this.heightFactor,
+    Widget? child,
+  }) : assert(alignment != null),
+       assert(widthFactor == null || widthFactor >= 0.0),
+       assert(heightFactor == null || heightFactor >= 0.0),
+       super(key: key, child: child);
+
+  /// How to align the child.
+  ///
+  /// The x and y values of the [Alignment] control the horizontal and vertical
+  /// alignment, respectively. An x value of -1.0 means that the left edge of
+  /// the child is aligned with the left edge of the parent whereas an x value
+  /// of 1.0 means that the right edge of the child is aligned with the right
+  /// edge of the parent. Other values interpolate (and extrapolate) linearly.
+  /// For example, a value of 0.0 means that the center of the child is aligned
+  /// with the center of the parent.
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], which has more details and some convenience constants for
+  ///    common positions.
+  ///  * [AlignmentDirectional], which has a horizontal coordinate orientation
+  ///    that depends on the [TextDirection].
+  final AlignmentGeometry alignment;
+
+  /// If non-null, sets its width to the child's width multiplied by this factor.
+  ///
+  /// Can be both greater and less than 1.0 but must be non-negative.
+  final double? widthFactor;
+
+  /// If non-null, sets its height to the child's height multiplied by this factor.
+  ///
+  /// Can be both greater and less than 1.0 but must be non-negative.
+  final double? heightFactor;
+
+  @override
+  RenderPositionedBox createRenderObject(BuildContext context) {
+    return RenderPositionedBox(
+      alignment: alignment,
+      widthFactor: widthFactor,
+      heightFactor: heightFactor,
+      textDirection: Directionality.maybeOf(context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderPositionedBox renderObject) {
+    renderObject
+      ..alignment = alignment
+      ..widthFactor = widthFactor
+      ..heightFactor = heightFactor
+      ..textDirection = Directionality.maybeOf(context);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
+    properties.add(DoubleProperty('widthFactor', widthFactor, defaultValue: null));
+    properties.add(DoubleProperty('heightFactor', heightFactor, defaultValue: null));
+  }
+}
+
+/// A widget that centers its child within itself.
+///
+/// This widget will be as big as possible if its dimensions are constrained and
+/// [widthFactor] and [heightFactor] are null. If a dimension is unconstrained
+/// and the corresponding size factor is null then the widget will match its
+/// child's size in that dimension. If a size factor is non-null then the
+/// corresponding dimension of this widget will be the product of the child's
+/// dimension and the size factor. For example if widthFactor is 2.0 then
+/// the width of this widget will always be twice its child's width.
+///
+/// See also:
+///
+///  * [Align], which lets you arbitrarily position a child within itself,
+///    rather than just centering it.
+///  * [Row], a widget that displays its children in a horizontal array.
+///  * [Column], a widget that displays its children in a vertical array.
+///  * [Container], a convenience widget that combines common painting,
+///    positioning, and sizing widgets.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Center extends Align {
+  /// Creates a widget that centers its child.
+  const Center({ Key? key, double? widthFactor, double? heightFactor, Widget? child })
+    : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
+}
+
+/// A widget that defers the layout of its single child to a delegate.
+///
+/// The delegate can determine the layout constraints for the child and can
+/// decide where to position the child. The delegate can also determine the size
+/// of the parent, but the size of the parent cannot depend on the size of the
+/// child.
+///
+/// See also:
+///
+///  * [SingleChildLayoutDelegate], which controls the layout of the child.
+///  * [Align], which sizes itself based on its child's size and positions
+///    the child according to an [Alignment] value.
+///  * [FractionallySizedBox], which sizes its child based on a fraction of its own
+///    size and positions the child according to an [Alignment] value.
+///  * [CustomMultiChildLayout], which uses a delegate to position multiple
+///    children.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class CustomSingleChildLayout extends SingleChildRenderObjectWidget {
+  /// Creates a custom single child layout.
+  ///
+  /// The [delegate] argument must not be null.
+  const CustomSingleChildLayout({
+    Key? key,
+    required this.delegate,
+    Widget? child,
+  }) : assert(delegate != null),
+       super(key: key, child: child);
+
+  /// The delegate that controls the layout of the child.
+  final SingleChildLayoutDelegate delegate;
+
+  @override
+  RenderCustomSingleChildLayoutBox createRenderObject(BuildContext context) {
+    return RenderCustomSingleChildLayoutBox(delegate: delegate);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderCustomSingleChildLayoutBox renderObject) {
+    renderObject.delegate = delegate;
+  }
+}
+
+/// Metadata for identifying children in a [CustomMultiChildLayout].
+///
+/// The [MultiChildLayoutDelegate.hasChild],
+/// [MultiChildLayoutDelegate.layoutChild], and
+/// [MultiChildLayoutDelegate.positionChild] methods use these identifiers.
+class LayoutId extends ParentDataWidget<MultiChildLayoutParentData> {
+  /// Marks a child with a layout identifier.
+  ///
+  /// Both the child and the id arguments must not be null.
+  LayoutId({
+    Key? key,
+    required this.id,
+    required Widget child,
+  }) : assert(child != null),
+       assert(id != null),
+       super(key: key ?? ValueKey<Object>(id), child: child);
+
+  /// An object representing the identity of this child.
+  ///
+  /// The [id] needs to be unique among the children that the
+  /// [CustomMultiChildLayout] manages.
+  final Object id;
+
+  @override
+  void applyParentData(RenderObject renderObject) {
+    assert(renderObject.parentData is MultiChildLayoutParentData);
+    final MultiChildLayoutParentData parentData = renderObject.parentData! as MultiChildLayoutParentData;
+    if (parentData.id != id) {
+      parentData.id = id;
+      final AbstractNode? targetParent = renderObject.parent;
+      if (targetParent is RenderObject)
+        targetParent.markNeedsLayout();
+    }
+  }
+
+  @override
+  Type get debugTypicalAncestorWidgetClass => CustomMultiChildLayout;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Object>('id', id));
+  }
+}
+
+/// A widget that uses a delegate to size and position multiple children.
+///
+/// The delegate can determine the layout constraints for each child and can
+/// decide where to position each child. The delegate can also determine the
+/// size of the parent, but the size of the parent cannot depend on the sizes of
+/// the children.
+///
+/// [CustomMultiChildLayout] is appropriate when there are complex relationships
+/// between the size and positioning of multiple widgets. To control the
+/// layout of a single child, [CustomSingleChildLayout] is more appropriate. For
+/// simple cases, such as aligning a widget to one or another edge, the [Stack]
+/// widget is more appropriate.
+///
+/// Each child must be wrapped in a [LayoutId] widget to identify the widget for
+/// the delegate.
+///
+/// See also:
+///
+///  * [MultiChildLayoutDelegate], for details about how to control the layout of
+///    the children.
+///  * [CustomSingleChildLayout], which uses a delegate to control the layout of
+///    a single child.
+///  * [Stack], which arranges children relative to the edges of the container.
+///  * [Flow], which provides paint-time control of its children using transform
+///    matrices.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class CustomMultiChildLayout extends MultiChildRenderObjectWidget {
+  /// Creates a custom multi-child layout.
+  ///
+  /// The [delegate] argument must not be null.
+  CustomMultiChildLayout({
+    Key? key,
+    required this.delegate,
+    List<Widget> children = const <Widget>[],
+  }) : assert(delegate != null),
+       super(key: key, children: children);
+
+  /// The delegate that controls the layout of the children.
+  final MultiChildLayoutDelegate delegate;
+
+  @override
+  RenderCustomMultiChildLayoutBox createRenderObject(BuildContext context) {
+    return RenderCustomMultiChildLayoutBox(delegate: delegate);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderCustomMultiChildLayoutBox renderObject) {
+    renderObject.delegate = delegate;
+  }
+}
+
+/// A box with a specified size.
+///
+/// If given a child, this widget forces it to have a specific width and/or height.
+/// These values will be ignored if this widget's parent does not permit them.
+/// For example, this happens if the parent is the screen (forces the child to
+/// be the same size as the parent), or another [SizedBox] (forces its child to
+/// have a specific width and/or height). This can be remedied by wrapping the
+/// child [SizedBox] in a widget that does permit it to be any size up to the
+/// size of the parent, such as [Center] or [Align].
+///
+/// If either the width or height is null, this widget will try to size itself to
+/// match the child's size in that dimension. If the child's size depends on the
+/// size of its parent, the height and width must be provided.
+///
+/// If not given a child, [SizedBox] will try to size itself as close to the
+/// specified height and width as possible given the parent's constraints. If
+/// [height] or [width] is null or unspecified, it will be treated as zero.
+///
+/// The [new SizedBox.expand] constructor can be used to make a [SizedBox] that
+/// sizes itself to fit the parent. It is equivalent to setting [width] and
+/// [height] to [double.infinity].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=EHPu_DzRfqA}
+///
+/// {@tool snippet}
+///
+/// This snippet makes the child widget (a [Card] with some [Text]) have the
+/// exact size 200x300, parental constraints permitting:
+///
+/// ```dart
+/// SizedBox(
+///   width: 200.0,
+///   height: 300.0,
+///   child: const Card(child: Text('Hello World!')),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ConstrainedBox], a more generic version of this class that takes
+///    arbitrary [BoxConstraints] instead of an explicit width and height.
+///  * [UnconstrainedBox], a container that tries to let its child draw without
+///    constraints.
+///  * [FractionallySizedBox], a widget that sizes its child to a fraction of
+///    the total available space.
+///  * [AspectRatio], a widget that attempts to fit within the parent's
+///    constraints while also sizing its child to match a given aspect ratio.
+///  * [FittedBox], which sizes and positions its child widget to fit the parent
+///    according to a given [BoxFit] discipline.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+///  * [Understanding constraints](https://flutter.dev/docs/development/ui/layout/constraints),
+///    an in-depth article about layout in Flutter.
+class SizedBox extends SingleChildRenderObjectWidget {
+  /// Creates a fixed size box. The [width] and [height] parameters can be null
+  /// to indicate that the size of the box should not be constrained in
+  /// the corresponding dimension.
+  const SizedBox({ Key? key, this.width, this.height, Widget? child })
+    : super(key: key, child: child);
+
+  /// Creates a box that will become as large as its parent allows.
+  const SizedBox.expand({ Key? key, Widget? child })
+    : width = double.infinity,
+      height = double.infinity,
+      super(key: key, child: child);
+
+  /// Creates a box that will become as small as its parent allows.
+  const SizedBox.shrink({ Key? key, Widget? child })
+    : width = 0.0,
+      height = 0.0,
+      super(key: key, child: child);
+
+  /// Creates a box with the specified size.
+  SizedBox.fromSize({ Key? key, Widget? child, Size? size })
+    : width = size?.width,
+      height = size?.height,
+      super(key: key, child: child);
+
+  /// If non-null, requires the child to have exactly this width.
+  final double? width;
+
+  /// If non-null, requires the child to have exactly this height.
+  final double? height;
+
+  @override
+  RenderConstrainedBox createRenderObject(BuildContext context) {
+    return RenderConstrainedBox(
+      additionalConstraints: _additionalConstraints,
+    );
+  }
+
+  BoxConstraints get _additionalConstraints {
+    return BoxConstraints.tightFor(width: width, height: height);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderConstrainedBox renderObject) {
+    renderObject.additionalConstraints = _additionalConstraints;
+  }
+
+  @override
+  String toStringShort() {
+    final String type;
+    if (width == double.infinity && height == double.infinity) {
+      type = '${objectRuntimeType(this, 'SizedBox')}.expand';
+    } else if (width == 0.0 && height == 0.0) {
+      type = '${objectRuntimeType(this, 'SizedBox')}.shrink';
+    } else {
+      type = objectRuntimeType(this, 'SizedBox');
+    }
+    return key == null ? type : '$type-$key';
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    final DiagnosticLevel level;
+    if ((width == double.infinity && height == double.infinity) ||
+        (width == 0.0 && height == 0.0)) {
+      level = DiagnosticLevel.hidden;
+    } else {
+      level = DiagnosticLevel.info;
+    }
+    properties.add(DoubleProperty('width', width, defaultValue: null, level: level));
+    properties.add(DoubleProperty('height', height, defaultValue: null, level: level));
+  }
+}
+
+/// A widget that imposes additional constraints on its child.
+///
+/// For example, if you wanted [child] to have a minimum height of 50.0 logical
+/// pixels, you could use `const BoxConstraints(minHeight: 50.0)` as the
+/// [constraints].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=o2KveVr7adg}
+///
+/// {@tool snippet}
+///
+/// This snippet makes the child widget (a [Card] with some [Text]) fill the
+/// parent, by applying [BoxConstraints.expand] constraints:
+///
+/// ```dart
+/// ConstrainedBox(
+///   constraints: const BoxConstraints.expand(),
+///   child: const Card(child: Text('Hello World!')),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// The same behavior can be obtained using the [new SizedBox.expand] widget.
+///
+/// See also:
+///
+///  * [BoxConstraints], the class that describes constraints.
+///  * [UnconstrainedBox], a container that tries to let its child draw without
+///    constraints.
+///  * [SizedBox], which lets you specify tight constraints by explicitly
+///    specifying the height or width.
+///  * [FractionallySizedBox], which sizes its child based on a fraction of its
+///    own size and positions the child according to an [Alignment] value.
+///  * [AspectRatio], a widget that attempts to fit within the parent's
+///    constraints while also sizing its child to match a given aspect ratio.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class ConstrainedBox extends SingleChildRenderObjectWidget {
+  /// Creates a widget that imposes additional constraints on its child.
+  ///
+  /// The [constraints] argument must not be null.
+  ConstrainedBox({
+    Key? key,
+    required this.constraints,
+    Widget? child,
+  }) : assert(constraints != null),
+       assert(constraints.debugAssertIsValid()),
+       super(key: key, child: child);
+
+  /// The additional constraints to impose on the child.
+  final BoxConstraints constraints;
+
+  @override
+  RenderConstrainedBox createRenderObject(BuildContext context) {
+    return RenderConstrainedBox(additionalConstraints: constraints);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderConstrainedBox renderObject) {
+    renderObject.additionalConstraints = constraints;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, showName: false));
+  }
+}
+
+/// A widget that imposes no constraints on its child, allowing it to render
+/// at its "natural" size.
+///
+/// This allows a child to render at the size it would render if it were alone
+/// on an infinite canvas with no constraints. This container will then attempt
+/// to adopt the same size, within the limits of its own constraints. If it ends
+/// up with a different size, it will align the child based on [alignment].
+/// If the box cannot expand enough to accommodate the entire child, the
+/// child will be clipped.
+///
+/// In debug mode, if the child overflows the container, a warning will be
+/// printed on the console, and black and yellow striped areas will appear where
+/// the overflow occurs.
+///
+/// See also:
+///
+///  * [ConstrainedBox], for a box which imposes constraints on its child.
+///  * [Align], which loosens the constraints given to the child rather than
+///    removing them entirely.
+///  * [Container], a convenience widget that combines common painting,
+///    positioning, and sizing widgets.
+///  * [OverflowBox], a widget that imposes different constraints on its child
+///    than it gets from its parent, possibly allowing the child to overflow
+///    the parent.
+class UnconstrainedBox extends SingleChildRenderObjectWidget {
+  /// Creates a widget that imposes no constraints on its child, allowing it to
+  /// render at its "natural" size. If the child overflows the parents
+  /// constraints, a warning will be given in debug mode.
+  const UnconstrainedBox({
+    Key? key,
+    Widget? child,
+    this.textDirection,
+    this.alignment = Alignment.center,
+    this.constrainedAxis,
+    this.clipBehavior = Clip.none,
+  }) : assert(alignment != null),
+       assert(clipBehavior != null),
+       super(key: key, child: child);
+
+  /// The text direction to use when interpreting the [alignment] if it is an
+  /// [AlignmentDirectional].
+  final TextDirection? textDirection;
+
+  /// The alignment to use when laying out the child.
+  ///
+  /// If this is an [AlignmentDirectional], then [textDirection] must not be
+  /// null.
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment] for non-[Directionality]-aware alignments.
+  ///  * [AlignmentDirectional] for [Directionality]-aware alignments.
+  final AlignmentGeometry alignment;
+
+  /// The axis to retain constraints on, if any.
+  ///
+  /// If not set, or set to null (the default), neither axis will retain its
+  /// constraints. If set to [Axis.vertical], then vertical constraints will
+  /// be retained, and if set to [Axis.horizontal], then horizontal constraints
+  /// will be retained.
+  final Axis? constrainedAxis;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none].
+  final Clip clipBehavior;
+
+  @override
+  void updateRenderObject(BuildContext context, covariant RenderUnconstrainedBox renderObject) {
+    renderObject
+      ..textDirection = textDirection ?? Directionality.maybeOf(context)
+      ..alignment = alignment
+      ..constrainedAxis = constrainedAxis
+      ..clipBehavior = clipBehavior;
+  }
+
+  @override
+  RenderUnconstrainedBox createRenderObject(BuildContext context) => RenderUnconstrainedBox(
+    textDirection: textDirection ?? Directionality.maybeOf(context),
+    alignment: alignment,
+    constrainedAxis: constrainedAxis,
+    clipBehavior: clipBehavior,
+  );
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
+    properties.add(EnumProperty<Axis>('constrainedAxis', constrainedAxis, defaultValue: null));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+  }
+}
+
+/// A widget that sizes its child to a fraction of the total available space.
+/// For more details about the layout algorithm, see
+/// [RenderFractionallySizedOverflowBox].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=PEsY654EGZ0}
+///
+/// See also:
+///
+///  * [Align], which sizes itself based on its child's size and positions
+///    the child according to an [Alignment] value.
+///  * [OverflowBox], a widget that imposes different constraints on its child
+///    than it gets from its parent, possibly allowing the child to overflow the
+///    parent.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class FractionallySizedBox extends SingleChildRenderObjectWidget {
+  /// Creates a widget that sizes its child to a fraction of the total available space.
+  ///
+  /// If non-null, the [widthFactor] and [heightFactor] arguments must be
+  /// non-negative.
+  const FractionallySizedBox({
+    Key? key,
+    this.alignment = Alignment.center,
+    this.widthFactor,
+    this.heightFactor,
+    Widget? child,
+  }) : assert(alignment != null),
+       assert(widthFactor == null || widthFactor >= 0.0),
+       assert(heightFactor == null || heightFactor >= 0.0),
+       super(key: key, child: child);
+
+  /// If non-null, the fraction of the incoming width given to the child.
+  ///
+  /// If non-null, the child is given a tight width constraint that is the max
+  /// incoming width constraint multiplied by this factor.
+  ///
+  /// If null, the incoming width constraints are passed to the child
+  /// unmodified.
+  final double? widthFactor;
+
+  /// If non-null, the fraction of the incoming height given to the child.
+  ///
+  /// If non-null, the child is given a tight height constraint that is the max
+  /// incoming height constraint multiplied by this factor.
+  ///
+  /// If null, the incoming height constraints are passed to the child
+  /// unmodified.
+  final double? heightFactor;
+
+  /// How to align the child.
+  ///
+  /// The x and y values of the alignment control the horizontal and vertical
+  /// alignment, respectively. An x value of -1.0 means that the left edge of
+  /// the child is aligned with the left edge of the parent whereas an x value
+  /// of 1.0 means that the right edge of the child is aligned with the right
+  /// edge of the parent. Other values interpolate (and extrapolate) linearly.
+  /// For example, a value of 0.0 means that the center of the child is aligned
+  /// with the center of the parent.
+  ///
+  /// Defaults to [Alignment.center].
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], a class with convenient constants typically used to
+  ///    specify an [AlignmentGeometry].
+  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
+  ///    relative to text direction.
+  final AlignmentGeometry alignment;
+
+  @override
+  RenderFractionallySizedOverflowBox createRenderObject(BuildContext context) {
+    return RenderFractionallySizedOverflowBox(
+      alignment: alignment,
+      widthFactor: widthFactor,
+      heightFactor: heightFactor,
+      textDirection: Directionality.maybeOf(context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderFractionallySizedOverflowBox renderObject) {
+    renderObject
+      ..alignment = alignment
+      ..widthFactor = widthFactor
+      ..heightFactor = heightFactor
+      ..textDirection = Directionality.maybeOf(context);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
+    properties.add(DoubleProperty('widthFactor', widthFactor, defaultValue: null));
+    properties.add(DoubleProperty('heightFactor', heightFactor, defaultValue: null));
+  }
+}
+
+/// A box that limits its size only when it's unconstrained.
+///
+/// If this widget's maximum width is unconstrained then its child's width is
+/// limited to [maxWidth]. Similarly, if this widget's maximum height is
+/// unconstrained then its child's height is limited to [maxHeight].
+///
+/// This has the effect of giving the child a natural dimension in unbounded
+/// environments. For example, by providing a [maxHeight] to a widget that
+/// normally tries to be as big as possible, the widget will normally size
+/// itself to fit its parent, but when placed in a vertical list, it will take
+/// on the given height.
+///
+/// This is useful when composing widgets that normally try to match their
+/// parents' size, so that they behave reasonably in lists (which are
+/// unbounded).
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=uVki2CIzBTs}
+///
+/// See also:
+///
+///  * [ConstrainedBox], which applies its constraints in all cases, not just
+///    when the incoming constraints are unbounded.
+///  * [SizedBox], which lets you specify tight constraints by explicitly
+///    specifying the height or width.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class LimitedBox extends SingleChildRenderObjectWidget {
+  /// Creates a box that limits its size only when it's unconstrained.
+  ///
+  /// The [maxWidth] and [maxHeight] arguments must not be null and must not be
+  /// negative.
+  const LimitedBox({
+    Key? key,
+    this.maxWidth = double.infinity,
+    this.maxHeight = double.infinity,
+    Widget? child,
+  }) : assert(maxWidth != null && maxWidth >= 0.0),
+       assert(maxHeight != null && maxHeight >= 0.0),
+       super(key: key, child: child);
+
+  /// The maximum width limit to apply in the absence of a
+  /// [BoxConstraints.maxWidth] constraint.
+  final double maxWidth;
+
+  /// The maximum height limit to apply in the absence of a
+  /// [BoxConstraints.maxHeight] constraint.
+  final double maxHeight;
+
+  @override
+  RenderLimitedBox createRenderObject(BuildContext context) {
+    return RenderLimitedBox(
+      maxWidth: maxWidth,
+      maxHeight: maxHeight,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderLimitedBox renderObject) {
+    renderObject
+      ..maxWidth = maxWidth
+      ..maxHeight = maxHeight;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('maxWidth', maxWidth, defaultValue: double.infinity));
+    properties.add(DoubleProperty('maxHeight', maxHeight, defaultValue: double.infinity));
+  }
+}
+
+/// A widget that imposes different constraints on its child than it gets
+/// from its parent, possibly allowing the child to overflow the parent.
+///
+/// See also:
+///
+///  * [RenderConstrainedOverflowBox] for details about how [OverflowBox] is
+///    rendered.
+///  * [SizedOverflowBox], a widget that is a specific size but passes its
+///    original constraints through to its child, which may then overflow.
+///  * [ConstrainedBox], a widget that imposes additional constraints on its
+///    child.
+///  * [UnconstrainedBox], a container that tries to let its child draw without
+///    constraints.
+///  * [SizedBox], a box with a specified size.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class OverflowBox extends SingleChildRenderObjectWidget {
+  /// Creates a widget that lets its child overflow itself.
+  const OverflowBox({
+    Key? key,
+    this.alignment = Alignment.center,
+    this.minWidth,
+    this.maxWidth,
+    this.minHeight,
+    this.maxHeight,
+    Widget? child,
+  }) : super(key: key, child: child);
+
+  /// How to align the child.
+  ///
+  /// The x and y values of the alignment control the horizontal and vertical
+  /// alignment, respectively. An x value of -1.0 means that the left edge of
+  /// the child is aligned with the left edge of the parent whereas an x value
+  /// of 1.0 means that the right edge of the child is aligned with the right
+  /// edge of the parent. Other values interpolate (and extrapolate) linearly.
+  /// For example, a value of 0.0 means that the center of the child is aligned
+  /// with the center of the parent.
+  ///
+  /// Defaults to [Alignment.center].
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], a class with convenient constants typically used to
+  ///    specify an [AlignmentGeometry].
+  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
+  ///    relative to text direction.
+  final AlignmentGeometry alignment;
+
+  /// The minimum width constraint to give the child. Set this to null (the
+  /// default) to use the constraint from the parent instead.
+  final double? minWidth;
+
+  /// The maximum width constraint to give the child. Set this to null (the
+  /// default) to use the constraint from the parent instead.
+  final double? maxWidth;
+
+  /// The minimum height constraint to give the child. Set this to null (the
+  /// default) to use the constraint from the parent instead.
+  final double? minHeight;
+
+  /// The maximum height constraint to give the child. Set this to null (the
+  /// default) to use the constraint from the parent instead.
+  final double? maxHeight;
+
+  @override
+  RenderConstrainedOverflowBox createRenderObject(BuildContext context) {
+    return RenderConstrainedOverflowBox(
+      alignment: alignment,
+      minWidth: minWidth,
+      maxWidth: maxWidth,
+      minHeight: minHeight,
+      maxHeight: maxHeight,
+      textDirection: Directionality.maybeOf(context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderConstrainedOverflowBox renderObject) {
+    renderObject
+      ..alignment = alignment
+      ..minWidth = minWidth
+      ..maxWidth = maxWidth
+      ..minHeight = minHeight
+      ..maxHeight = maxHeight
+      ..textDirection = Directionality.maybeOf(context);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
+    properties.add(DoubleProperty('minWidth', minWidth, defaultValue: null));
+    properties.add(DoubleProperty('maxWidth', maxWidth, defaultValue: null));
+    properties.add(DoubleProperty('minHeight', minHeight, defaultValue: null));
+    properties.add(DoubleProperty('maxHeight', maxHeight, defaultValue: null));
+  }
+}
+
+/// A widget that is a specific size but passes its original constraints
+/// through to its child, which may then overflow.
+///
+/// See also:
+///
+///  * [OverflowBox], A widget that imposes different constraints on its child
+///    than it gets from its parent, possibly allowing the child to overflow the
+///    parent.
+///  * [ConstrainedBox], a widget that imposes additional constraints on its
+///    child.
+///  * [UnconstrainedBox], a container that tries to let its child draw without
+///    constraints.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class SizedOverflowBox extends SingleChildRenderObjectWidget {
+  /// Creates a widget of a given size that lets its child overflow.
+  ///
+  /// The [size] argument must not be null.
+  const SizedOverflowBox({
+    Key? key,
+    required this.size,
+    this.alignment = Alignment.center,
+    Widget? child,
+  }) : assert(size != null),
+       assert(alignment != null),
+       super(key: key, child: child);
+
+  /// How to align the child.
+  ///
+  /// The x and y values of the alignment control the horizontal and vertical
+  /// alignment, respectively. An x value of -1.0 means that the left edge of
+  /// the child is aligned with the left edge of the parent whereas an x value
+  /// of 1.0 means that the right edge of the child is aligned with the right
+  /// edge of the parent. Other values interpolate (and extrapolate) linearly.
+  /// For example, a value of 0.0 means that the center of the child is aligned
+  /// with the center of the parent.
+  ///
+  /// Defaults to [Alignment.center].
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], a class with convenient constants typically used to
+  ///    specify an [AlignmentGeometry].
+  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
+  ///    relative to text direction.
+  final AlignmentGeometry alignment;
+
+  /// The size this widget should attempt to be.
+  final Size size;
+
+  @override
+  RenderSizedOverflowBox createRenderObject(BuildContext context) {
+    return RenderSizedOverflowBox(
+      alignment: alignment,
+      requestedSize: size,
+      textDirection: Directionality.of(context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderSizedOverflowBox renderObject) {
+    renderObject
+      ..alignment = alignment
+      ..requestedSize = size
+      ..textDirection = Directionality.of(context);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
+    properties.add(DiagnosticsProperty<Size>('size', size, defaultValue: null));
+  }
+}
+
+/// A widget that lays the child out as if it was in the tree, but without
+/// painting anything, without making the child available for hit testing, and
+/// without taking any room in the parent.
+///
+/// Offstage children are still active: they can receive focus and have keyboard
+/// input directed to them.
+///
+/// Animations continue to run in offstage children, and therefore use battery
+/// and CPU time, regardless of whether the animations end up being visible.
+///
+/// [Offstage] can be used to measure the dimensions of a widget without
+/// bringing it on screen (yet). To hide a widget from view while it is not
+/// needed, prefer removing the widget from the tree entirely rather than
+/// keeping it alive in an [Offstage] subtree.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+///
+/// This example shows a [FlutterLogo] widget when the `_offstage` member field
+/// is false, and hides it without any room in the parent when it is true. When
+/// offstage, this app displays a button to get the logo size, which will be
+/// displayed in a [SnackBar].
+///
+/// ```dart
+/// GlobalKey _key = GlobalKey();
+/// bool _offstage = true;
+///
+/// Size _getFlutterLogoSize() {
+///   final RenderBox renderLogo = _key.currentContext.findRenderObject();
+///   return renderLogo.size;
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Column(
+///     mainAxisAlignment: MainAxisAlignment.center,
+///     children: <Widget>[
+///       Offstage(
+///         offstage: _offstage,
+///         child: FlutterLogo(
+///           key: _key,
+///           size: 150.0,
+///         ),
+///       ),
+///       Text('Flutter logo is offstage: $_offstage'),
+///       RaisedButton(
+///         child: Text('Toggle Offstage Value'),
+///         onPressed: () {
+///           setState(() {
+///             _offstage = !_offstage;
+///           });
+///         },
+///       ),
+///       if (_offstage)
+///         RaisedButton(
+///           child: Text('Get Flutter Logo size'),
+///           onPressed: () {
+///             ScaffoldMessenger.of(context).showSnackBar(
+///               SnackBar(
+///                 content: Text('Flutter Logo size is ${_getFlutterLogoSize()}'),
+///               ),
+///             );
+///           }
+///         ),
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Visibility], which can hide a child more efficiently (albeit less
+///    subtly).
+///  * [TickerMode], which can be used to disable animations in a subtree.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Offstage extends SingleChildRenderObjectWidget {
+  /// Creates a widget that visually hides its child.
+  const Offstage({ Key? key, this.offstage = true, Widget? child })
+    : assert(offstage != null),
+      super(key: key, child: child);
+
+  /// Whether the child is hidden from the rest of the tree.
+  ///
+  /// If true, the child is laid out as if it was in the tree, but without
+  /// painting anything, without making the child available for hit testing, and
+  /// without taking any room in the parent.
+  ///
+  /// Offstage children are still active: they can receive focus and have keyboard
+  /// input directed to them.
+  ///
+  /// Animations continue to run in offstage children, and therefore use battery
+  /// and CPU time, regardless of whether the animations end up being visible.
+  ///
+  /// If false, the child is included in the tree as normal.
+  final bool offstage;
+
+  @override
+  RenderOffstage createRenderObject(BuildContext context) => RenderOffstage(offstage: offstage);
+
+  @override
+  void updateRenderObject(BuildContext context, RenderOffstage renderObject) {
+    renderObject.offstage = offstage;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('offstage', offstage));
+  }
+
+  @override
+  _OffstageElement createElement() => _OffstageElement(this);
+}
+
+class _OffstageElement extends SingleChildRenderObjectElement {
+  _OffstageElement(Offstage widget) : super(widget);
+
+  @override
+  Offstage get widget => super.widget as Offstage;
+
+  @override
+  void debugVisitOnstageChildren(ElementVisitor visitor) {
+    if (!widget.offstage)
+      super.debugVisitOnstageChildren(visitor);
+  }
+}
+
+/// A widget that attempts to size the child to a specific aspect ratio.
+///
+/// The widget first tries the largest width permitted by the layout
+/// constraints. The height of the widget is determined by applying the
+/// given aspect ratio to the width, expressed as a ratio of width to height.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=XcnP3_mO_Ms}
+///
+/// For example, a 16:9 width:height aspect ratio would have a value of
+/// 16.0/9.0. If the maximum width is infinite, the initial width is determined
+/// by applying the aspect ratio to the maximum height.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// This examples shows how AspectRatio sets width when its parent's width
+/// constraint is infinite. Since its parent's allowed height is a fixed value,
+/// the actual width is determined via the given AspectRatio.
+///
+/// Since the height is fixed at 100.0 in this example and the aspect ratio is
+/// set to 16 / 9, the width should then be 100.0 / 9 * 16.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Container(
+///     color: Colors.blue,
+///     alignment: Alignment.center,
+///     width: double.infinity,
+///     height: 100.0,
+///     child: AspectRatio(
+///       aspectRatio: 16 / 9,
+///       child: Container(
+///         color: Colors.green,
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// Now consider a second example, this time with an aspect ratio of 2.0 and
+/// layout constraints that require the width to be between 0.0 and 100.0 and
+/// the height to be between 0.0 and 100.0. We'll select a width of 100.0 (the
+/// biggest allowed) and a height of 50.0 (to match the aspect ratio).
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Container(
+///     color: Colors.blue,
+///     alignment: Alignment.center,
+///     width: 100.0,
+///     height: 100.0,
+///     child: AspectRatio(
+///       aspectRatio: 2.0,
+///       child: Container(
+///         width: 100.0,
+///         height: 50.0,
+///         color: Colors.green,
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// In that same situation, if the aspect ratio is 0.5, we'll also select a
+/// width of 100.0 (still the biggest allowed) and we'll attempt to use a height
+/// of 200.0. Unfortunately, that violates the constraints because the child can
+/// be at most 100.0 pixels tall. The widget will then take that value
+/// and apply the aspect ratio again to obtain a width of 50.0. That width is
+/// permitted by the constraints and the child receives a width of 50.0 and a
+/// height of 100.0. If the width were not permitted, the widget would
+/// continue iterating through the constraints. If the widget does not
+/// find a feasible size after consulting each constraint, the widget
+/// will eventually select a size for the child that meets the layout
+/// constraints but fails to meet the aspect ratio constraints.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Container(
+///     color: Colors.blue,
+///     alignment: Alignment.center,
+///     width: 100.0,
+///     height: 100.0,
+///     child: AspectRatio(
+///       aspectRatio: 0.5,
+///       child: Container(
+///         width: 100.0,
+///         height: 50.0,
+///         color: Colors.green,
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Align], a widget that aligns its child within itself and optionally
+///    sizes itself based on the child's size.
+///  * [ConstrainedBox], a widget that imposes additional constraints on its
+///    child.
+///  * [UnconstrainedBox], a container that tries to let its child draw without
+///    constraints.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class AspectRatio extends SingleChildRenderObjectWidget {
+  /// Creates a widget with a specific aspect ratio.
+  ///
+  /// The [aspectRatio] argument must be a finite number greater than zero.
+  const AspectRatio({
+    Key? key,
+    required this.aspectRatio,
+    Widget? child,
+  }) : assert(aspectRatio != null),
+       assert(aspectRatio > 0.0),
+       // can't test isFinite because that's not a constant expression
+       super(key: key, child: child);
+
+  /// The aspect ratio to attempt to use.
+  ///
+  /// The aspect ratio is expressed as a ratio of width to height. For example,
+  /// a 16:9 width:height aspect ratio would have a value of 16.0/9.0.
+  final double aspectRatio;
+
+  @override
+  RenderAspectRatio createRenderObject(BuildContext context) => RenderAspectRatio(aspectRatio: aspectRatio);
+
+  @override
+  void updateRenderObject(BuildContext context, RenderAspectRatio renderObject) {
+    renderObject.aspectRatio = aspectRatio;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('aspectRatio', aspectRatio));
+  }
+}
+
+/// A widget that sizes its child to the child's maximum intrinsic width.
+///
+/// This class is useful, for example, when unlimited width is available and
+/// you would like a child that would otherwise attempt to expand infinitely to
+/// instead size itself to a more reasonable width.
+///
+/// The constraints that this widget passes to its child will adhere to the
+/// parent's constraints, so if the constraints are not large enough to satisfy
+/// the child's maximum intrinsic width, then the child will get less width
+/// than it otherwise would. Likewise, if the minimum width constraint is
+/// larger than the child's maximum intrinsic width, the child will be given
+/// more width than it otherwise would.
+///
+/// If [stepWidth] is non-null, the child's width will be snapped to a multiple
+/// of the [stepWidth]. Similarly, if [stepHeight] is non-null, the child's
+/// height will be snapped to a multiple of the [stepHeight].
+///
+/// This class is relatively expensive, because it adds a speculative layout
+/// pass before the final layout phase. Avoid using it where possible. In the
+/// worst case, this widget can result in a layout that is O(N²) in the depth of
+/// the tree.
+///
+/// See also:
+///
+///  * [Align], a widget that aligns its child within itself. This can be used
+///    to loosen the constraints passed to the [RenderIntrinsicWidth],
+///    allowing the [RenderIntrinsicWidth]'s child to be smaller than that of
+///    its parent.
+///  * [Row], which when used with [CrossAxisAlignment.stretch] can be used
+///    to loosen just the width constraints that are passed to the
+///    [RenderIntrinsicWidth], allowing the [RenderIntrinsicWidth]'s child's
+///    width to be smaller than that of its parent.
+///  * [The catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class IntrinsicWidth extends SingleChildRenderObjectWidget {
+  /// Creates a widget that sizes its child to the child's intrinsic width.
+  ///
+  /// This class is relatively expensive. Avoid using it where possible.
+  const IntrinsicWidth({ Key? key, this.stepWidth, this.stepHeight, Widget? child })
+    : assert(stepWidth == null || stepWidth >= 0.0),
+      assert(stepHeight == null || stepHeight >= 0.0),
+      super(key: key, child: child);
+
+  /// If non-null, force the child's width to be a multiple of this value.
+  ///
+  /// If null or 0.0 the child's width will be the same as its maximum
+  /// intrinsic width.
+  ///
+  /// This value must not be negative.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderBox.getMaxIntrinsicWidth], which defines a widget's max
+  ///    intrinsic width  in general.
+  final double? stepWidth;
+
+  /// If non-null, force the child's height to be a multiple of this value.
+  ///
+  /// If null or 0.0 the child's height will not be constrained.
+  ///
+  /// This value must not be negative.
+  final double? stepHeight;
+
+  double? get _stepWidth => stepWidth == 0.0 ? null : stepWidth;
+  double? get _stepHeight => stepHeight == 0.0 ? null : stepHeight;
+
+  @override
+  RenderIntrinsicWidth createRenderObject(BuildContext context) {
+    return RenderIntrinsicWidth(stepWidth: _stepWidth, stepHeight: _stepHeight);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderIntrinsicWidth renderObject) {
+    renderObject
+      ..stepWidth = _stepWidth
+      ..stepHeight = _stepHeight;
+  }
+}
+
+/// A widget that sizes its child to the child's intrinsic height.
+///
+/// This class is useful, for example, when unlimited height is available and
+/// you would like a child that would otherwise attempt to expand infinitely to
+/// instead size itself to a more reasonable height.
+///
+/// The constraints that this widget passes to its child will adhere to the
+/// parent's constraints, so if the constraints are not large enough to satisfy
+/// the child's maximum intrinsic height, then the child will get less height
+/// than it otherwise would. Likewise, if the minimum height constraint is
+/// larger than the child's maximum intrinsic height, the child will be given
+/// more height than it otherwise would.
+///
+/// This class is relatively expensive, because it adds a speculative layout
+/// pass before the final layout phase. Avoid using it where possible. In the
+/// worst case, this widget can result in a layout that is O(N²) in the depth of
+/// the tree.
+///
+/// See also:
+///
+///  * [Align], a widget that aligns its child within itself. This can be used
+///    to loosen the constraints passed to the [RenderIntrinsicHeight],
+///    allowing the [RenderIntrinsicHeight]'s child to be smaller than that of
+///    its parent.
+///  * [Column], which when used with [CrossAxisAlignment.stretch] can be used
+///    to loosen just the height constraints that are passed to the
+///    [RenderIntrinsicHeight], allowing the [RenderIntrinsicHeight]'s child's
+///    height to be smaller than that of its parent.
+///  * [The catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class IntrinsicHeight extends SingleChildRenderObjectWidget {
+  /// Creates a widget that sizes its child to the child's intrinsic height.
+  ///
+  /// This class is relatively expensive. Avoid using it where possible.
+  const IntrinsicHeight({ Key? key, Widget? child }) : super(key: key, child: child);
+
+  @override
+  RenderIntrinsicHeight createRenderObject(BuildContext context) => RenderIntrinsicHeight();
+}
+
+/// A widget that positions its child according to the child's baseline.
+///
+/// This widget shifts the child down such that the child's baseline (or the
+/// bottom of the child, if the child has no baseline) is [baseline]
+/// logical pixels below the top of this box, then sizes this box to
+/// contain the child. If [baseline] is less than the distance from
+/// the top of the child to the baseline of the child, then the child
+/// is top-aligned instead.
+///
+/// See also:
+///
+///  * [Align], a widget that aligns its child within itself and optionally
+///    sizes itself based on the child's size.
+///  * [Center], a widget that centers its child within itself.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Baseline extends SingleChildRenderObjectWidget {
+  /// Creates a widget that positions its child according to the child's baseline.
+  ///
+  /// The [baseline] and [baselineType] arguments must not be null.
+  const Baseline({
+    Key? key,
+    required this.baseline,
+    required this.baselineType,
+    Widget? child,
+  }) : assert(baseline != null),
+       assert(baselineType != null),
+       super(key: key, child: child);
+
+  /// The number of logical pixels from the top of this box at which to position
+  /// the child's baseline.
+  final double baseline;
+
+  /// The type of baseline to use for positioning the child.
+  final TextBaseline baselineType;
+
+  @override
+  RenderBaseline createRenderObject(BuildContext context) {
+    return RenderBaseline(baseline: baseline, baselineType: baselineType);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderBaseline renderObject) {
+    renderObject
+      ..baseline = baseline
+      ..baselineType = baselineType;
+  }
+}
+
+
+// SLIVERS
+
+/// A sliver that contains a single box widget.
+///
+/// Slivers are special-purpose widgets that can be combined using a
+/// [CustomScrollView] to create custom scroll effects. A [SliverToBoxAdapter]
+/// is a basic sliver that creates a bridge back to one of the usual box-based
+/// widgets.
+///
+/// Rather than using multiple [SliverToBoxAdapter] widgets to display multiple
+/// box widgets in a [CustomScrollView], consider using [SliverList],
+/// [SliverFixedExtentList], [SliverPrototypeExtentList], or [SliverGrid],
+/// which are more efficient because they instantiate only those children that
+/// are actually visible through the scroll view's viewport.
+///
+/// See also:
+///
+///  * [CustomScrollView], which displays a scrollable list of slivers.
+///  * [SliverList], which displays multiple box widgets in a linear array.
+///  * [SliverFixedExtentList], which displays multiple box widgets with the
+///    same main-axis extent in a linear array.
+///  * [SliverPrototypeExtentList], which displays multiple box widgets with the
+///    same main-axis extent as a prototype item, in a linear array.
+///  * [SliverGrid], which displays multiple box widgets in arbitrary positions.
+class SliverToBoxAdapter extends SingleChildRenderObjectWidget {
+  /// Creates a sliver that contains a single box widget.
+  const SliverToBoxAdapter({
+    Key? key,
+    Widget? child,
+  }) : super(key: key, child: child);
+
+  @override
+  RenderSliverToBoxAdapter createRenderObject(BuildContext context) => RenderSliverToBoxAdapter();
+}
+
+/// A sliver that applies padding on each side of another sliver.
+///
+/// Slivers are special-purpose widgets that can be combined using a
+/// [CustomScrollView] to create custom scroll effects. A [SliverPadding]
+/// is a basic sliver that insets another sliver by applying padding on each
+/// side.
+///
+/// {@macro flutter.rendering.RenderSliverEdgeInsetsPadding}
+///
+/// See also:
+///
+///  * [CustomScrollView], which displays a scrollable list of slivers.
+class SliverPadding extends SingleChildRenderObjectWidget {
+  /// Creates a sliver that applies padding on each side of another sliver.
+  ///
+  /// The [padding] argument must not be null.
+  const SliverPadding({
+    Key? key,
+    required this.padding,
+    Widget? sliver,
+  }) : assert(padding != null),
+       super(key: key, child: sliver);
+
+  /// The amount of space by which to inset the child sliver.
+  final EdgeInsetsGeometry padding;
+
+  @override
+  RenderSliverPadding createRenderObject(BuildContext context) {
+    return RenderSliverPadding(
+      padding: padding,
+      textDirection: Directionality.of(context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderSliverPadding renderObject) {
+    renderObject
+      ..padding = padding
+      ..textDirection = Directionality.of(context);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
+  }
+}
+
+
+// LAYOUT NODES
+
+/// Returns the [AxisDirection] in the given [Axis] in the current
+/// [Directionality] (or the reverse if `reverse` is true).
+///
+/// If `axis` is [Axis.vertical], this function returns [AxisDirection.down]
+/// unless `reverse` is true, in which case this function returns
+/// [AxisDirection.up].
+///
+/// If `axis` is [Axis.horizontal], this function checks the current
+/// [Directionality]. If the current [Directionality] is right-to-left, then
+/// this function returns [AxisDirection.left] (unless `reverse` is true, in
+/// which case it returns [AxisDirection.right]). Similarly, if the current
+/// [Directionality] is left-to-right, then this function returns
+/// [AxisDirection.right] (unless `reverse` is true, in which case it returns
+/// [AxisDirection.left]).
+///
+/// This function is used by a number of scrolling widgets (e.g., [ListView],
+/// [GridView], [PageView], and [SingleChildScrollView]) as well as [ListBody]
+/// to translate their [Axis] and `reverse` properties into a concrete
+/// [AxisDirection].
+AxisDirection getAxisDirectionFromAxisReverseAndDirectionality(
+  BuildContext context,
+  Axis axis,
+  bool reverse,
+) {
+  switch (axis) {
+    case Axis.horizontal:
+      assert(debugCheckHasDirectionality(context));
+      final TextDirection textDirection = Directionality.of(context);
+      final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
+      return reverse ? flipAxisDirection(axisDirection) : axisDirection;
+    case Axis.vertical:
+      return reverse ? AxisDirection.up : AxisDirection.down;
+  }
+}
+
+/// A widget that arranges its children sequentially along a given axis, forcing
+/// them to the dimension of the parent in the other axis.
+///
+/// This widget is rarely used directly. Instead, consider using [ListView],
+/// which combines a similar layout algorithm with scrolling behavior, or
+/// [Column], which gives you more flexible control over the layout of a
+/// vertical set of boxes.
+///
+/// See also:
+///
+///  * [RenderListBody], which implements this layout algorithm and the
+///    documentation for which describes some of its subtleties.
+///  * [SingleChildScrollView], which is sometimes used with [ListBody] to
+///    make the contents scrollable.
+///  * [Column] and [Row], which implement a more elaborate version of
+///    this layout algorithm (at the cost of being slightly less efficient).
+///  * [ListView], which implements an efficient scrolling version of this
+///    layout algorithm.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class ListBody extends MultiChildRenderObjectWidget {
+  /// Creates a layout widget that arranges its children sequentially along a
+  /// given axis.
+  ///
+  /// By default, the [mainAxis] is [Axis.vertical].
+  ListBody({
+    Key? key,
+    this.mainAxis = Axis.vertical,
+    this.reverse = false,
+    List<Widget> children = const <Widget>[],
+  }) : assert(mainAxis != null),
+       super(key: key, children: children);
+
+  /// The direction to use as the main axis.
+  final Axis mainAxis;
+
+  /// Whether the list body positions children in the reading direction.
+  ///
+  /// For example, if the reading direction is left-to-right and
+  /// [mainAxis] is [Axis.horizontal], then the list body positions children
+  /// from left to right when [reverse] is false and from right to left when
+  /// [reverse] is true.
+  ///
+  /// Similarly, if [mainAxis] is [Axis.vertical], then the list body positions
+  /// from top to bottom when [reverse] is false and from bottom to top when
+  /// [reverse] is true.
+  ///
+  /// Defaults to false.
+  final bool reverse;
+
+  AxisDirection _getDirection(BuildContext context) {
+    return getAxisDirectionFromAxisReverseAndDirectionality(context, mainAxis, reverse);
+  }
+
+  @override
+  RenderListBody createRenderObject(BuildContext context) {
+    return RenderListBody(axisDirection: _getDirection(context));
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderListBody renderObject) {
+    renderObject.axisDirection = _getDirection(context);
+  }
+}
+
+/// A widget that positions its children relative to the edges of its box.
+///
+/// This class is useful if you want to overlap several children in a simple
+/// way, for example having some text and an image, overlaid with a gradient and
+/// a button attached to the bottom.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=liEGSeD3Zt8}
+///
+/// Each child of a [Stack] widget is either _positioned_ or _non-positioned_.
+/// Positioned children are those wrapped in a [Positioned] widget that has at
+/// least one non-null property. The stack sizes itself to contain all the
+/// non-positioned children, which are positioned according to [alignment]
+/// (which defaults to the top-left corner in left-to-right environments and the
+/// top-right corner in right-to-left environments). The positioned children are
+/// then placed relative to the stack according to their top, right, bottom, and
+/// left properties.
+///
+/// The stack paints its children in order with the first child being at the
+/// bottom. If you want to change the order in which the children paint, you
+/// can rebuild the stack with the children in the new order. If you reorder
+/// the children in this way, consider giving the children non-null keys.
+/// These keys will cause the framework to move the underlying objects for
+/// the children to their new locations rather than recreate them at their
+/// new location.
+///
+/// For more details about the stack layout algorithm, see [RenderStack].
+///
+/// If you want to lay a number of children out in a particular pattern, or if
+/// you want to make a custom layout manager, you probably want to use
+/// [CustomMultiChildLayout] instead. In particular, when using a [Stack] you
+/// can't position children relative to their size or the stack's own size.
+///
+/// {@tool snippet}
+///
+/// Using a [Stack] you can position widgets over one another.
+///
+/// ![The sample creates a blue box that overlaps a larger green box, which itself overlaps an even larger red box.](https://flutter.github.io/assets-for-api-docs/assets/widgets/stack.png)
+///
+/// ```dart
+/// Stack(
+///   children: <Widget>[
+///     Container(
+///       width: 100,
+///       height: 100,
+///       color: Colors.red,
+///     ),
+///     Container(
+///       width: 90,
+///       height: 90,
+///       color: Colors.green,
+///     ),
+///     Container(
+///       width: 80,
+///       height: 80,
+///       color: Colors.blue,
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+///
+/// This example shows how [Stack] can be used to enhance text visibility
+/// by adding gradient backdrops.
+///
+/// ![The gradient fades from transparent to dark grey at the bottom, with white text on top of the darker portion.](https://flutter.github.io/assets-for-api-docs/assets/widgets/stack_with_gradient.png)
+///
+/// ```dart
+/// SizedBox(
+///   width: 250,
+///   height: 250,
+///   child: Stack(
+///     children: <Widget>[
+///       Container(
+///         width: 250,
+///         height: 250,
+///         color: Colors.white,
+///       ),
+///       Container(
+///         padding: EdgeInsets.all(5.0),
+///         alignment: Alignment.bottomCenter,
+///         decoration: BoxDecoration(
+///           gradient: LinearGradient(
+///             begin: Alignment.topCenter,
+///             end: Alignment.bottomCenter,
+///             colors: <Color>[
+///               Colors.black.withAlpha(0),
+///               Colors.black12,
+///               Colors.black45
+///             ],
+///           ),
+///         ),
+///         child: Text(
+///           "Foreground Text",
+///           style: TextStyle(color: Colors.white, fontSize: 20.0),
+///         ),
+///       ),
+///     ],
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Align], which sizes itself based on its child's size and positions
+///    the child according to an [Alignment] value.
+///  * [CustomSingleChildLayout], which uses a delegate to control the layout of
+///    a single child.
+///  * [CustomMultiChildLayout], which uses a delegate to position multiple
+///    children.
+///  * [Flow], which provides paint-time control of its children using transform
+///    matrices.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Stack extends MultiChildRenderObjectWidget {
+  /// Creates a stack layout widget.
+  ///
+  /// By default, the non-positioned children of the stack are aligned by their
+  /// top left corners.
+  Stack({
+    Key? key,
+    this.alignment = AlignmentDirectional.topStart,
+    this.textDirection,
+    this.fit = StackFit.loose,
+    @Deprecated(
+      'Use clipBehavior instead. See the migration guide in flutter.dev/go/clip-behavior. '
+      'This feature was deprecated after v1.22.0-12.0.pre.'
+    )
+    this.overflow = Overflow.clip,
+    this.clipBehavior = Clip.hardEdge,
+    List<Widget> children = const <Widget>[],
+  }) : assert(clipBehavior != null),
+       super(key: key, children: children);
+
+  /// How to align the non-positioned and partially-positioned children in the
+  /// stack.
+  ///
+  /// The non-positioned children are placed relative to each other such that
+  /// the points determined by [alignment] are co-located. For example, if the
+  /// [alignment] is [Alignment.topLeft], then the top left corner of
+  /// each non-positioned child will be located at the same global coordinate.
+  ///
+  /// Partially-positioned children, those that do not specify an alignment in a
+  /// particular axis (e.g. that have neither `top` nor `bottom` set), use the
+  /// alignment to determine how they should be positioned in that
+  /// under-specified axis.
+  ///
+  /// Defaults to [AlignmentDirectional.topStart].
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], a class with convenient constants typically used to
+  ///    specify an [AlignmentGeometry].
+  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
+  ///    relative to text direction.
+  final AlignmentGeometry alignment;
+
+  /// The text direction with which to resolve [alignment].
+  ///
+  /// Defaults to the ambient [Directionality].
+  final TextDirection? textDirection;
+
+  /// How to size the non-positioned children in the stack.
+  ///
+  /// The constraints passed into the [Stack] from its parent are either
+  /// loosened ([StackFit.loose]) or tightened to their biggest size
+  /// ([StackFit.expand]).
+  final StackFit fit;
+
+  /// Whether overflowing children should be clipped. See [Overflow].
+  ///
+  /// Some children in a stack might overflow its box. When this flag is set to
+  /// [Overflow.clip], children cannot paint outside of the stack's box.
+  ///
+  /// When set to [Overflow.visible], the visible overflow area will not accept
+  /// hit testing.
+  ///
+  /// This overrides [clipBehavior] for now due to a staged roll out.
+  /// We will remove it and only use [clipBehavior] soon.
+  ///
+  /// Deprecated. Use [clipBehavior] instead.
+  @Deprecated(
+    'Use clipBehavior instead. See the migration guide in flutter.dev/go/clip-behavior. '
+    'This feature was deprecated after v1.22.0-12.0.pre.'
+  )
+  final Overflow overflow;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge].
+  final Clip clipBehavior;
+
+  bool _debugCheckHasDirectionality(BuildContext context) {
+    if (alignment is AlignmentDirectional && textDirection == null) {
+      assert(debugCheckHasDirectionality(
+        context,
+        why: 'to resolve the \'alignment\' argument',
+        hint: alignment == AlignmentDirectional.topStart ? 'The default value for \'alignment\' is AlignmentDirectional.topStart, which requires a text direction.' : null,
+        alternative: 'Instead of providing a Directionality widget, another solution would be passing a non-directional \'alignment\', or an explicit \'textDirection\', to the $runtimeType.'),
+      );
+    }
+    return true;
+  }
+
+  @override
+  RenderStack createRenderObject(BuildContext context) {
+    assert(_debugCheckHasDirectionality(context));
+    return RenderStack(
+      alignment: alignment,
+      textDirection: textDirection ?? Directionality.maybeOf(context),
+      fit: fit,
+      clipBehavior: overflow == Overflow.visible ? Clip.none : clipBehavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderStack renderObject) {
+    assert(_debugCheckHasDirectionality(context));
+    renderObject
+      ..alignment = alignment
+      ..textDirection = textDirection ?? Directionality.maybeOf(context)
+      ..fit = fit
+      ..clipBehavior = overflow == Overflow.visible ? Clip.none : clipBehavior;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    properties.add(EnumProperty<StackFit>('fit', fit));
+    properties.add(EnumProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge));
+  }
+}
+
+/// A [Stack] that shows a single child from a list of children.
+///
+/// The displayed child is the one with the given [index]. The stack is
+/// always as big as the largest child.
+///
+/// If value is null, then nothing is displayed.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=_O0PPD1Xfbk}
+///
+/// See also:
+///
+///  * [Stack], for more details about stacks.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class IndexedStack extends Stack {
+  /// Creates a [Stack] widget that paints a single child.
+  ///
+  /// The [index] argument must not be null.
+  IndexedStack({
+    Key? key,
+    AlignmentGeometry alignment = AlignmentDirectional.topStart,
+    TextDirection? textDirection,
+    StackFit sizing = StackFit.loose,
+    this.index = 0,
+    List<Widget> children = const <Widget>[],
+  }) : super(key: key, alignment: alignment, textDirection: textDirection, fit: sizing, children: children);
+
+  /// The index of the child to show.
+  final int? index;
+
+  @override
+  RenderIndexedStack createRenderObject(BuildContext context) {
+    assert(_debugCheckHasDirectionality(context));
+    return RenderIndexedStack(
+      index: index,
+      alignment: alignment,
+      textDirection: textDirection ?? Directionality.maybeOf(context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderIndexedStack renderObject) {
+    assert(_debugCheckHasDirectionality(context));
+    renderObject
+      ..index = index
+      ..alignment = alignment
+      ..textDirection = textDirection ?? Directionality.maybeOf(context);
+  }
+}
+
+/// A widget that controls where a child of a [Stack] is positioned.
+///
+/// A [Positioned] widget must be a descendant of a [Stack], and the path from
+/// the [Positioned] widget to its enclosing [Stack] must contain only
+/// [StatelessWidget]s or [StatefulWidget]s (not other kinds of widgets, like
+/// [RenderObjectWidget]s).
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=EgtPleVwxBQ}
+///
+/// If a widget is wrapped in a [Positioned], then it is a _positioned_ widget
+/// in its [Stack]. If the [top] property is non-null, the top edge of this child
+/// will be positioned [top] layout units from the top of the stack widget. The
+/// [right], [bottom], and [left] properties work analogously.
+///
+/// If both the [top] and [bottom] properties are non-null, then the child will
+/// be forced to have exactly the height required to satisfy both constraints.
+/// Similarly, setting the [right] and [left] properties to non-null values will
+/// force the child to have a particular width. Alternatively the [width] and
+/// [height] properties can be used to give the dimensions, with one
+/// corresponding position property (e.g. [top] and [height]).
+///
+/// If all three values on a particular axis are null, then the
+/// [Stack.alignment] property is used to position the child.
+///
+/// If all six values are null, the child is a non-positioned child. The [Stack]
+/// uses only the non-positioned children to size itself.
+///
+/// See also:
+///
+///  * [AnimatedPositioned], which automatically transitions the child's
+///    position over a given duration whenever the given position changes.
+///  * [PositionedTransition], which takes a provided [Animation] to transition
+///    changes in the child's position over a given duration.
+///  * [PositionedDirectional], which adapts to the ambient [Directionality].
+class Positioned extends ParentDataWidget<StackParentData> {
+  /// Creates a widget that controls where a child of a [Stack] is positioned.
+  ///
+  /// Only two out of the three horizontal values ([left], [right],
+  /// [width]), and only two out of the three vertical values ([top],
+  /// [bottom], [height]), can be set. In each case, at least one of
+  /// the three must be null.
+  ///
+  /// See also:
+  ///
+  ///  * [Positioned.directional], which specifies the widget's horizontal
+  ///    position using `start` and `end` rather than `left` and `right`.
+  ///  * [PositionedDirectional], which is similar to [Positioned.directional]
+  ///    but adapts to the ambient [Directionality].
+  const Positioned({
+    Key? key,
+    this.left,
+    this.top,
+    this.right,
+    this.bottom,
+    this.width,
+    this.height,
+    required Widget child,
+  }) : assert(left == null || right == null || width == null),
+       assert(top == null || bottom == null || height == null),
+       super(key: key, child: child);
+
+  /// Creates a Positioned object with the values from the given [Rect].
+  ///
+  /// This sets the [left], [top], [width], and [height] properties
+  /// from the given [Rect]. The [right] and [bottom] properties are
+  /// set to null.
+  Positioned.fromRect({
+    Key? key,
+    required Rect rect,
+    required Widget child,
+  }) : left = rect.left,
+       top = rect.top,
+       width = rect.width,
+       height = rect.height,
+       right = null,
+       bottom = null,
+       super(key: key, child: child);
+
+  /// Creates a Positioned object with the values from the given [RelativeRect].
+  ///
+  /// This sets the [left], [top], [right], and [bottom] properties from the
+  /// given [RelativeRect]. The [height] and [width] properties are set to null.
+  Positioned.fromRelativeRect({
+    Key? key,
+    required RelativeRect rect,
+    required Widget child,
+  }) : left = rect.left,
+       top = rect.top,
+       right = rect.right,
+       bottom = rect.bottom,
+       width = null,
+       height = null,
+       super(key: key, child: child);
+
+  /// Creates a Positioned object with [left], [top], [right], and [bottom] set
+  /// to 0.0 unless a value for them is passed.
+  const Positioned.fill({
+    Key? key,
+    this.left = 0.0,
+    this.top = 0.0,
+    this.right = 0.0,
+    this.bottom = 0.0,
+    required Widget child,
+  }) : width = null,
+       height = null,
+       super(key: key, child: child);
+
+  /// Creates a widget that controls where a child of a [Stack] is positioned.
+  ///
+  /// Only two out of the three horizontal values (`start`, `end`,
+  /// [width]), and only two out of the three vertical values ([top],
+  /// [bottom], [height]), can be set. In each case, at least one of
+  /// the three must be null.
+  ///
+  /// If `textDirection` is [TextDirection.rtl], then the `start` argument is
+  /// used for the [right] property and the `end` argument is used for the
+  /// [left] property. Otherwise, if `textDirection` is [TextDirection.ltr],
+  /// then the `start` argument is used for the [left] property and the `end`
+  /// argument is used for the [right] property.
+  ///
+  /// The `textDirection` argument must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [PositionedDirectional], which adapts to the ambient [Directionality].
+  factory Positioned.directional({
+    Key? key,
+    required TextDirection textDirection,
+    double? start,
+    double? top,
+    double? end,
+    double? bottom,
+    double? width,
+    double? height,
+    required Widget child,
+  }) {
+    assert(textDirection != null);
+    double? left;
+    double? right;
+    switch (textDirection) {
+      case TextDirection.rtl:
+        left = end;
+        right = start;
+        break;
+      case TextDirection.ltr:
+        left = start;
+        right = end;
+        break;
+    }
+    return Positioned(
+      key: key,
+      left: left,
+      top: top,
+      right: right,
+      bottom: bottom,
+      width: width,
+      height: height,
+      child: child,
+    );
+  }
+
+  /// The distance that the child's left edge is inset from the left of the stack.
+  ///
+  /// Only two out of the three horizontal values ([left], [right], [width]) can be
+  /// set. The third must be null.
+  ///
+  /// If all three are null, the [Stack.alignment] is used to position the child
+  /// horizontally.
+  final double? left;
+
+  /// The distance that the child's top edge is inset from the top of the stack.
+  ///
+  /// Only two out of the three vertical values ([top], [bottom], [height]) can be
+  /// set. The third must be null.
+  ///
+  /// If all three are null, the [Stack.alignment] is used to position the child
+  /// vertically.
+  final double? top;
+
+  /// The distance that the child's right edge is inset from the right of the stack.
+  ///
+  /// Only two out of the three horizontal values ([left], [right], [width]) can be
+  /// set. The third must be null.
+  ///
+  /// If all three are null, the [Stack.alignment] is used to position the child
+  /// horizontally.
+  final double? right;
+
+  /// The distance that the child's bottom edge is inset from the bottom of the stack.
+  ///
+  /// Only two out of the three vertical values ([top], [bottom], [height]) can be
+  /// set. The third must be null.
+  ///
+  /// If all three are null, the [Stack.alignment] is used to position the child
+  /// vertically.
+  final double? bottom;
+
+  /// The child's width.
+  ///
+  /// Only two out of the three horizontal values ([left], [right], [width]) can be
+  /// set. The third must be null.
+  ///
+  /// If all three are null, the [Stack.alignment] is used to position the child
+  /// horizontally.
+  final double? width;
+
+  /// The child's height.
+  ///
+  /// Only two out of the three vertical values ([top], [bottom], [height]) can be
+  /// set. The third must be null.
+  ///
+  /// If all three are null, the [Stack.alignment] is used to position the child
+  /// vertically.
+  final double? height;
+
+  @override
+  void applyParentData(RenderObject renderObject) {
+    assert(renderObject.parentData is StackParentData);
+    final StackParentData parentData = renderObject.parentData! as StackParentData;
+    bool needsLayout = false;
+
+    if (parentData.left != left) {
+      parentData.left = left;
+      needsLayout = true;
+    }
+
+    if (parentData.top != top) {
+      parentData.top = top;
+      needsLayout = true;
+    }
+
+    if (parentData.right != right) {
+      parentData.right = right;
+      needsLayout = true;
+    }
+
+    if (parentData.bottom != bottom) {
+      parentData.bottom = bottom;
+      needsLayout = true;
+    }
+
+    if (parentData.width != width) {
+      parentData.width = width;
+      needsLayout = true;
+    }
+
+    if (parentData.height != height) {
+      parentData.height = height;
+      needsLayout = true;
+    }
+
+    if (needsLayout) {
+      final AbstractNode? targetParent = renderObject.parent;
+      if (targetParent is RenderObject)
+        targetParent.markNeedsLayout();
+    }
+  }
+
+  @override
+  Type get debugTypicalAncestorWidgetClass => Stack;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('left', left, defaultValue: null));
+    properties.add(DoubleProperty('top', top, defaultValue: null));
+    properties.add(DoubleProperty('right', right, defaultValue: null));
+    properties.add(DoubleProperty('bottom', bottom, defaultValue: null));
+    properties.add(DoubleProperty('width', width, defaultValue: null));
+    properties.add(DoubleProperty('height', height, defaultValue: null));
+  }
+}
+
+/// A widget that controls where a child of a [Stack] is positioned without
+/// committing to a specific [TextDirection].
+///
+/// The ambient [Directionality] is used to determine whether [start] is to the
+/// left or to the right.
+///
+/// A [PositionedDirectional] widget must be a descendant of a [Stack], and the
+/// path from the [PositionedDirectional] widget to its enclosing [Stack] must
+/// contain only [StatelessWidget]s or [StatefulWidget]s (not other kinds of
+/// widgets, like [RenderObjectWidget]s).
+///
+/// If a widget is wrapped in a [PositionedDirectional], then it is a
+/// _positioned_ widget in its [Stack]. If the [top] property is non-null, the
+/// top edge of this child/ will be positioned [top] layout units from the top
+/// of the stack widget. The [start], [bottom], and [end] properties work
+/// analogously.
+///
+/// If both the [top] and [bottom] properties are non-null, then the child will
+/// be forced to have exactly the height required to satisfy both constraints.
+/// Similarly, setting the [start] and [end] properties to non-null values will
+/// force the child to have a particular width. Alternatively the [width] and
+/// [height] properties can be used to give the dimensions, with one
+/// corresponding position property (e.g. [top] and [height]).
+///
+/// See also:
+///
+///  * [Positioned], which specifies the widget's position visually.
+///  * [Positioned.directional], which also specifies the widget's horizontal
+///    position using [start] and [end] but has an explicit [TextDirection].
+///  * [AnimatedPositionedDirectional], which automatically transitions
+///    the child's position over a given duration whenever the given position
+///    changes.
+class PositionedDirectional extends StatelessWidget {
+  /// Creates a widget that controls where a child of a [Stack] is positioned.
+  ///
+  /// Only two out of the three horizontal values (`start`, `end`,
+  /// [width]), and only two out of the three vertical values ([top],
+  /// [bottom], [height]), can be set. In each case, at least one of
+  /// the three must be null.
+  ///
+  /// See also:
+  ///
+  ///  * [Positioned.directional], which also specifies the widget's horizontal
+  ///    position using [start] and [end] but has an explicit [TextDirection].
+  const PositionedDirectional({
+    Key? key,
+    this.start,
+    this.top,
+    this.end,
+    this.bottom,
+    this.width,
+    this.height,
+    required this.child,
+  }) : super(key: key);
+
+  /// The distance that the child's leading edge is inset from the leading edge
+  /// of the stack.
+  ///
+  /// Only two out of the three horizontal values ([start], [end], [width]) can be
+  /// set. The third must be null.
+  final double? start;
+
+  /// The distance that the child's top edge is inset from the top of the stack.
+  ///
+  /// Only two out of the three vertical values ([top], [bottom], [height]) can be
+  /// set. The third must be null.
+  final double? top;
+
+  /// The distance that the child's trailing edge is inset from the trailing
+  /// edge of the stack.
+  ///
+  /// Only two out of the three horizontal values ([start], [end], [width]) can be
+  /// set. The third must be null.
+  final double? end;
+
+  /// The distance that the child's bottom edge is inset from the bottom of the stack.
+  ///
+  /// Only two out of the three vertical values ([top], [bottom], [height]) can be
+  /// set. The third must be null.
+  final double? bottom;
+
+  /// The child's width.
+  ///
+  /// Only two out of the three horizontal values ([start], [end], [width]) can be
+  /// set. The third must be null.
+  final double? width;
+
+  /// The child's height.
+  ///
+  /// Only two out of the three vertical values ([top], [bottom], [height]) can be
+  /// set. The third must be null.
+  final double? height;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    return Positioned.directional(
+      textDirection: Directionality.of(context),
+      start: start,
+      top: top,
+      end: end,
+      bottom: bottom,
+      width: width,
+      height: height,
+      child: child,
+    );
+  }
+}
+
+/// A widget that displays its children in a one-dimensional array.
+///
+/// The [Flex] widget allows you to control the axis along which the children are
+/// placed (horizontal or vertical). This is referred to as the _main axis_. If
+/// you know the main axis in advance, then consider using a [Row] (if it's
+/// horizontal) or [Column] (if it's vertical) instead, because that will be less
+/// verbose.
+///
+/// To cause a child to expand to fill the available space in the [direction]
+/// of this widget's main axis, wrap the child in an [Expanded] widget.
+///
+/// The [Flex] widget does not scroll (and in general it is considered an error
+/// to have more children in a [Flex] than will fit in the available room). If
+/// you have some widgets and want them to be able to scroll if there is
+/// insufficient room, consider using a [ListView].
+///
+/// If you only have one child, then rather than using [Flex], [Row], or
+/// [Column], consider using [Align] or [Center] to position the child.
+///
+/// ## Layout algorithm
+///
+/// _This section describes how a [Flex] is rendered by the framework._
+/// _See [BoxConstraints] for an introduction to box layout models._
+///
+/// Layout for a [Flex] proceeds in six steps:
+///
+/// 1. Layout each child a null or zero flex factor (e.g., those that are not
+///    [Expanded]) with unbounded main axis constraints and the incoming
+///    cross axis constraints. If the [crossAxisAlignment] is
+///    [CrossAxisAlignment.stretch], instead use tight cross axis constraints
+///    that match the incoming max extent in the cross axis.
+/// 2. Divide the remaining main axis space among the children with non-zero
+///    flex factors (e.g., those that are [Expanded]) according to their flex
+///    factor. For example, a child with a flex factor of 2.0 will receive twice
+///    the amount of main axis space as a child with a flex factor of 1.0.
+/// 3. Layout each of the remaining children with the same cross axis
+///    constraints as in step 1, but instead of using unbounded main axis
+///    constraints, use max axis constraints based on the amount of space
+///    allocated in step 2. Children with [Flexible.fit] properties that are
+///    [FlexFit.tight] are given tight constraints (i.e., forced to fill the
+///    allocated space), and children with [Flexible.fit] properties that are
+///    [FlexFit.loose] are given loose constraints (i.e., not forced to fill the
+///    allocated space).
+/// 4. The cross axis extent of the [Flex] is the maximum cross axis extent of
+///    the children (which will always satisfy the incoming constraints).
+/// 5. The main axis extent of the [Flex] is determined by the [mainAxisSize]
+///    property. If the [mainAxisSize] property is [MainAxisSize.max], then the
+///    main axis extent of the [Flex] is the max extent of the incoming main
+///    axis constraints. If the [mainAxisSize] property is [MainAxisSize.min],
+///    then the main axis extent of the [Flex] is the sum of the main axis
+///    extents of the children (subject to the incoming constraints).
+/// 6. Determine the position for each child according to the
+///    [mainAxisAlignment] and the [crossAxisAlignment]. For example, if the
+///    [mainAxisAlignment] is [MainAxisAlignment.spaceBetween], any main axis
+///    space that has not been allocated to children is divided evenly and
+///    placed between the children.
+///
+/// See also:
+///
+///  * [Row], for a version of this widget that is always horizontal.
+///  * [Column], for a version of this widget that is always vertical.
+///  * [Expanded], to indicate children that should take all the remaining room.
+///  * [Flexible], to indicate children that should share the remaining room.
+///  * [Spacer], a widget that takes up space proportional to its flex value.
+///    that may be sized smaller (leaving some remaining room unused).
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Flex extends MultiChildRenderObjectWidget {
+  /// Creates a flex layout.
+  ///
+  /// The [direction] is required.
+  ///
+  /// The [direction], [mainAxisAlignment], [crossAxisAlignment], and
+  /// [verticalDirection] arguments must not be null. If [crossAxisAlignment] is
+  /// [CrossAxisAlignment.baseline], then [textBaseline] must not be null.
+  ///
+  /// The [textDirection] argument defaults to the ambient [Directionality], if
+  /// any. If there is no ambient directionality, and a text direction is going
+  /// to be necessary to decide which direction to lay the children in or to
+  /// disambiguate `start` or `end` values for the main or cross axis
+  /// directions, the [textDirection] must not be null.
+  Flex({
+    Key? key,
+    required this.direction,
+    this.mainAxisAlignment = MainAxisAlignment.start,
+    this.mainAxisSize = MainAxisSize.max,
+    this.crossAxisAlignment = CrossAxisAlignment.center,
+    this.textDirection,
+    this.verticalDirection = VerticalDirection.down,
+    this.textBaseline, // NO DEFAULT: we don't know what the text's baseline should be
+    this.clipBehavior = Clip.none,
+    List<Widget> children = const <Widget>[],
+  }) : assert(direction != null),
+       assert(mainAxisAlignment != null),
+       assert(mainAxisSize != null),
+       assert(crossAxisAlignment != null),
+       assert(verticalDirection != null),
+       assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null, 'textBaseline is required if you specify the crossAxisAlignment with CrossAxisAlignment.baseline'),
+       assert(clipBehavior != null),
+       super(key: key, children: children);
+
+  /// The direction to use as the main axis.
+  ///
+  /// If you know the axis in advance, then consider using a [Row] (if it's
+  /// horizontal) or [Column] (if it's vertical) instead of a [Flex], since that
+  /// will be less verbose. (For [Row] and [Column] this property is fixed to
+  /// the appropriate axis.)
+  final Axis direction;
+
+  /// How the children should be placed along the main axis.
+  ///
+  /// For example, [MainAxisAlignment.start], the default, places the children
+  /// at the start (i.e., the left for a [Row] or the top for a [Column]) of the
+  /// main axis.
+  final MainAxisAlignment mainAxisAlignment;
+
+  /// How much space should be occupied in the main axis.
+  ///
+  /// After allocating space to children, there might be some remaining free
+  /// space. This value controls whether to maximize or minimize the amount of
+  /// free space, subject to the incoming layout constraints.
+  ///
+  /// If some children have a non-zero flex factors (and none have a fit of
+  /// [FlexFit.loose]), they will expand to consume all the available space and
+  /// there will be no remaining free space to maximize or minimize, making this
+  /// value irrelevant to the final layout.
+  final MainAxisSize mainAxisSize;
+
+  /// How the children should be placed along the cross axis.
+  ///
+  /// For example, [CrossAxisAlignment.center], the default, centers the
+  /// children in the cross axis (e.g., horizontally for a [Column]).
+  final CrossAxisAlignment crossAxisAlignment;
+
+  /// Determines the order to lay children out horizontally and how to interpret
+  /// `start` and `end` in the horizontal direction.
+  ///
+  /// Defaults to the ambient [Directionality].
+  ///
+  /// If [textDirection] is [TextDirection.rtl], then the direction in which
+  /// text flows starts from right to left. Otherwise, if [textDirection] is
+  /// [TextDirection.ltr], then the direction in which text flows starts from
+  /// left to right.
+  ///
+  /// If the [direction] is [Axis.horizontal], this controls the order in which
+  /// the children are positioned (left-to-right or right-to-left), and the
+  /// meaning of the [mainAxisAlignment] property's [MainAxisAlignment.start] and
+  /// [MainAxisAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.horizontal], and either the
+  /// [mainAxisAlignment] is either [MainAxisAlignment.start] or
+  /// [MainAxisAlignment.end], or there's more than one child, then the
+  /// [textDirection] (or the ambient [Directionality]) must not be null.
+  ///
+  /// If the [direction] is [Axis.vertical], this controls the meaning of the
+  /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and
+  /// [CrossAxisAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is
+  /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
+  /// [textDirection] (or the ambient [Directionality]) must not be null.
+  final TextDirection? textDirection;
+
+  /// Determines the order to lay children out vertically and how to interpret
+  /// `start` and `end` in the vertical direction.
+  ///
+  /// Defaults to [VerticalDirection.down].
+  ///
+  /// If the [direction] is [Axis.vertical], this controls which order children
+  /// are painted in (down or up), the meaning of the [mainAxisAlignment]
+  /// property's [MainAxisAlignment.start] and [MainAxisAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.vertical], and either the [mainAxisAlignment]
+  /// is either [MainAxisAlignment.start] or [MainAxisAlignment.end], or there's
+  /// more than one child, then the [verticalDirection] must not be null.
+  ///
+  /// If the [direction] is [Axis.horizontal], this controls the meaning of the
+  /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and
+  /// [CrossAxisAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is
+  /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
+  /// [verticalDirection] must not be null.
+  final VerticalDirection verticalDirection;
+
+  /// If aligning items according to their baseline, which baseline to use.
+  ///
+  /// This must be set if using baseline alignment. There is no default because there is no
+  /// way for the framework to know the correct baseline _a priori_.
+  final TextBaseline? textBaseline;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none].
+  final Clip clipBehavior;
+
+  bool get _needTextDirection {
+    assert(direction != null);
+    switch (direction) {
+      case Axis.horizontal:
+        return true; // because it affects the layout order.
+      case Axis.vertical:
+        assert(crossAxisAlignment != null);
+        return crossAxisAlignment == CrossAxisAlignment.start
+            || crossAxisAlignment == CrossAxisAlignment.end;
+    }
+  }
+
+  /// The value to pass to [RenderFlex.textDirection].
+  ///
+  /// This value is derived from the [textDirection] property and the ambient
+  /// [Directionality]. The value is null if there is no need to specify the
+  /// text direction. In practice there's always a need to specify the direction
+  /// except for vertical flexes (e.g. [Column]s) whose [crossAxisAlignment] is
+  /// not dependent on the text direction (not `start` or `end`). In particular,
+  /// a [Row] always needs a text direction because the text direction controls
+  /// its layout order. (For [Column]s, the layout order is controlled by
+  /// [verticalDirection], which is always specified as it does not depend on an
+  /// inherited widget and defaults to [VerticalDirection.down].)
+  ///
+  /// This method exists so that subclasses of [Flex] that create their own
+  /// render objects that are derived from [RenderFlex] can do so and still use
+  /// the logic for providing a text direction only when it is necessary.
+  @protected
+  TextDirection? getEffectiveTextDirection(BuildContext context) {
+    return textDirection ?? (_needTextDirection ? Directionality.maybeOf(context) : null);
+  }
+
+  @override
+  RenderFlex createRenderObject(BuildContext context) {
+    return RenderFlex(
+      direction: direction,
+      mainAxisAlignment: mainAxisAlignment,
+      mainAxisSize: mainAxisSize,
+      crossAxisAlignment: crossAxisAlignment,
+      textDirection: getEffectiveTextDirection(context),
+      verticalDirection: verticalDirection,
+      textBaseline: textBaseline,
+      clipBehavior: clipBehavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, covariant RenderFlex renderObject) {
+    renderObject
+      ..direction = direction
+      ..mainAxisAlignment = mainAxisAlignment
+      ..mainAxisSize = mainAxisSize
+      ..crossAxisAlignment = crossAxisAlignment
+      ..textDirection = getEffectiveTextDirection(context)
+      ..verticalDirection = verticalDirection
+      ..textBaseline = textBaseline
+      ..clipBehavior = clipBehavior;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<Axis>('direction', direction));
+    properties.add(EnumProperty<MainAxisAlignment>('mainAxisAlignment', mainAxisAlignment));
+    properties.add(EnumProperty<MainAxisSize>('mainAxisSize', mainAxisSize, defaultValue: MainAxisSize.max));
+    properties.add(EnumProperty<CrossAxisAlignment>('crossAxisAlignment', crossAxisAlignment));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    properties.add(EnumProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: VerticalDirection.down));
+    properties.add(EnumProperty<TextBaseline>('textBaseline', textBaseline, defaultValue: null));
+  }
+}
+
+/// A widget that displays its children in a horizontal array.
+///
+/// To cause a child to expand to fill the available horizontal space, wrap the
+/// child in an [Expanded] widget.
+///
+/// The [Row] widget does not scroll (and in general it is considered an error
+/// to have more children in a [Row] than will fit in the available room). If
+/// you have a line of widgets and want them to be able to scroll if there is
+/// insufficient room, consider using a [ListView].
+///
+/// For a vertical variant, see [Column].
+///
+/// If you only have one child, then consider using [Align] or [Center] to
+/// position the child.
+///
+/// {@tool snippet}
+///
+/// This example divides the available space into three (horizontally), and
+/// places text centered in the first two cells and the Flutter logo centered in
+/// the third:
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/row.png)
+///
+/// ```dart
+/// Row(
+///   children: <Widget>[
+///     Expanded(
+///       child: Text('Deliver features faster', textAlign: TextAlign.center),
+///     ),
+///     Expanded(
+///       child: Text('Craft beautiful UIs', textAlign: TextAlign.center),
+///     ),
+///     Expanded(
+///       child: FittedBox(
+///         fit: BoxFit.contain, // otherwise the logo will be tiny
+///         child: const FlutterLogo(),
+///       ),
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ## Troubleshooting
+///
+/// ### Why does my row have a yellow and black warning stripe?
+///
+/// If the non-flexible contents of the row (those that are not wrapped in
+/// [Expanded] or [Flexible] widgets) are together wider than the row itself,
+/// then the row is said to have overflowed. When a row overflows, the row does
+/// not have any remaining space to share between its [Expanded] and [Flexible]
+/// children. The row reports this by drawing a yellow and black striped
+/// warning box on the edge that is overflowing. If there is room on the outside
+/// of the row, the amount of overflow is printed in red lettering.
+///
+/// #### Story time
+///
+/// Suppose, for instance, that you had this code:
+///
+/// ```dart
+/// Row(
+///   children: <Widget>[
+///     const FlutterLogo(),
+///     const Text("Flutter's hot reload helps you quickly and easily experiment, build UIs, add features, and fix bug faster. Experience sub-second reload times, without losing state, on emulators, simulators, and hardware for iOS and Android."),
+///     const Icon(Icons.sentiment_very_satisfied),
+///   ],
+/// )
+/// ```
+///
+/// The row first asks its first child, the [FlutterLogo], to lay out, at
+/// whatever size the logo would like. The logo is friendly and happily decides
+/// to be 24 pixels to a side. This leaves lots of room for the next child. The
+/// row then asks that next child, the text, to lay out, at whatever size it
+/// thinks is best.
+///
+/// At this point, the text, not knowing how wide is too wide, says "Ok, I will
+/// be thiiiiiiiiiiiiiiiiiiiis wide.", and goes well beyond the space that the
+/// row has available, not wrapping. The row responds, "That's not fair, now I
+/// have no more room available for my other children!", and gets angry and
+/// sprouts a yellow and black strip.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/row_error.png)
+///
+/// The fix is to wrap the second child in an [Expanded] widget, which tells the
+/// row that the child should be given the remaining room:
+///
+/// ```dart
+/// Row(
+///   children: <Widget>[
+///     const FlutterLogo(),
+///     const Expanded(
+///       child: Text("Flutter's hot reload helps you quickly and easily experiment, build UIs, add features, and fix bug faster. Experience sub-second reload times, without losing state, on emulators, simulators, and hardware for iOS and Android."),
+///     ),
+///     const Icon(Icons.sentiment_very_satisfied),
+///   ],
+/// )
+/// ```
+///
+/// Now, the row first asks the logo to lay out, and then asks the _icon_ to lay
+/// out. The [Icon], like the logo, is happy to take on a reasonable size (also
+/// 24 pixels, not coincidentally, since both [FlutterLogo] and [Icon] honor the
+/// ambient [IconTheme]). This leaves some room left over, and now the row tells
+/// the text exactly how wide to be: the exact width of the remaining space. The
+/// text, now happy to comply to a reasonable request, wraps the text within
+/// that width, and you end up with a paragraph split over several lines.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/row_fixed.png)
+///
+/// The [textDirection] property controls the direction that children are rendered in.
+/// [TextDirection.ltr] is the default [textDirection] of [Row] children, so the first
+/// child is rendered at the `start` of the [Row], to the left, with subsequent children
+/// following to the right. If you want to order children in the opposite
+/// direction (right to left), then [textDirection] can be set to
+/// [TextDirection.rtl]. This is shown in the example below
+///
+/// ```dart
+/// Row(
+///   textDirection: TextDirection.rtl,
+///   children: <Widget>[
+///     const FlutterLogo(),
+///     const Expanded(
+///       child: Text("Flutter's hot reload helps you quickly and easily experiment, build UIs, add features, and fix bug faster. Experience sub-second reload times, without losing state, on emulators, simulators, and hardware for iOS and Android."),
+///     ),
+///     const Icon(Icons.sentiment_very_satisfied),
+///   ],
+/// )
+/// ```
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/row_textDirection.png)
+///
+/// ## Layout algorithm
+///
+/// _This section describes how a [Row] is rendered by the framework._
+/// _See [BoxConstraints] for an introduction to box layout models._
+///
+/// Layout for a [Row] proceeds in six steps:
+///
+/// 1. Layout each child a null or zero flex factor (e.g., those that are not
+///    [Expanded]) with unbounded horizontal constraints and the incoming
+///    vertical constraints. If the [crossAxisAlignment] is
+///    [CrossAxisAlignment.stretch], instead use tight vertical constraints that
+///    match the incoming max height.
+/// 2. Divide the remaining horizontal space among the children with non-zero
+///    flex factors (e.g., those that are [Expanded]) according to their flex
+///    factor. For example, a child with a flex factor of 2.0 will receive twice
+///    the amount of horizontal space as a child with a flex factor of 1.0.
+/// 3. Layout each of the remaining children with the same vertical constraints
+///    as in step 1, but instead of using unbounded horizontal constraints, use
+///    horizontal constraints based on the amount of space allocated in step 2.
+///    Children with [Flexible.fit] properties that are [FlexFit.tight] are
+///    given tight constraints (i.e., forced to fill the allocated space), and
+///    children with [Flexible.fit] properties that are [FlexFit.loose] are
+///    given loose constraints (i.e., not forced to fill the allocated space).
+/// 4. The height of the [Row] is the maximum height of the children (which will
+///    always satisfy the incoming vertical constraints).
+/// 5. The width of the [Row] is determined by the [mainAxisSize] property. If
+///    the [mainAxisSize] property is [MainAxisSize.max], then the width of the
+///    [Row] is the max width of the incoming constraints. If the [mainAxisSize]
+///    property is [MainAxisSize.min], then the width of the [Row] is the sum
+///    of widths of the children (subject to the incoming constraints).
+/// 6. Determine the position for each child according to the
+///    [mainAxisAlignment] and the [crossAxisAlignment]. For example, if the
+///    [mainAxisAlignment] is [MainAxisAlignment.spaceBetween], any horizontal
+///    space that has not been allocated to children is divided evenly and
+///    placed between the children.
+///
+/// See also:
+///
+///  * [Column], for a vertical equivalent.
+///  * [Flex], if you don't know in advance if you want a horizontal or vertical
+///    arrangement.
+///  * [Expanded], to indicate children that should take all the remaining room.
+///  * [Flexible], to indicate children that should share the remaining room but
+///    that may by sized smaller (leaving some remaining room unused).
+///  * [Spacer], a widget that takes up space proportional to its flex value.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Row extends Flex {
+  /// Creates a horizontal array of children.
+  ///
+  /// The [mainAxisAlignment], [mainAxisSize], [crossAxisAlignment], and
+  /// [verticalDirection] arguments must not be null.
+  /// If [crossAxisAlignment] is [CrossAxisAlignment.baseline], then
+  /// [textBaseline] must not be null.
+  ///
+  /// The [textDirection] argument defaults to the ambient [Directionality], if
+  /// any. If there is no ambient directionality, and a text direction is going
+  /// to be necessary to determine the layout order (which is always the case
+  /// unless the row has no children or only one child) or to disambiguate
+  /// `start` or `end` values for the [mainAxisAlignment], the [textDirection]
+  /// must not be null.
+  Row({
+    Key? key,
+    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
+    MainAxisSize mainAxisSize = MainAxisSize.max,
+    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
+    TextDirection? textDirection,
+    VerticalDirection verticalDirection = VerticalDirection.down,
+    TextBaseline? textBaseline, // NO DEFAULT: we don't know what the text's baseline should be
+    List<Widget> children = const <Widget>[],
+  }) : super(
+    children: children,
+    key: key,
+    direction: Axis.horizontal,
+    mainAxisAlignment: mainAxisAlignment,
+    mainAxisSize: mainAxisSize,
+    crossAxisAlignment: crossAxisAlignment,
+    textDirection: textDirection,
+    verticalDirection: verticalDirection,
+    textBaseline: textBaseline,
+  );
+}
+
+/// A widget that displays its children in a vertical array.
+///
+/// To cause a child to expand to fill the available vertical space, wrap the
+/// child in an [Expanded] widget.
+///
+/// The [Column] widget does not scroll (and in general it is considered an error
+/// to have more children in a [Column] than will fit in the available room). If
+/// you have a line of widgets and want them to be able to scroll if there is
+/// insufficient room, consider using a [ListView].
+///
+/// For a horizontal variant, see [Row].
+///
+/// If you only have one child, then consider using [Align] or [Center] to
+/// position the child.
+///
+/// {@tool snippet}
+///
+/// This example uses a [Column] to arrange three widgets vertically, the last
+/// being made to fill all the remaining space.
+///
+/// ![Using the Column in this way creates two short lines of text with a large Flutter underneath.](https://flutter.github.io/assets-for-api-docs/assets/widgets/column.png)
+///
+/// ```dart
+/// Column(
+///   children: <Widget>[
+///     Text('Deliver features faster'),
+///     Text('Craft beautiful UIs'),
+///     Expanded(
+///       child: FittedBox(
+///         fit: BoxFit.contain, // otherwise the logo will be tiny
+///         child: const FlutterLogo(),
+///       ),
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// In the sample above, the text and the logo are centered on each line. In the
+/// following example, the [crossAxisAlignment] is set to
+/// [CrossAxisAlignment.start], so that the children are left-aligned. The
+/// [mainAxisSize] is set to [MainAxisSize.min], so that the column shrinks to
+/// fit the children.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/column_properties.png)
+///
+/// ```dart
+/// Column(
+///   crossAxisAlignment: CrossAxisAlignment.start,
+///   mainAxisSize: MainAxisSize.min,
+///   children: <Widget>[
+///     Text('We move under cover and we move as one'),
+///     Text('Through the night, we have one shot to live another day'),
+///     Text('We cannot let a stray gunshot give us away'),
+///     Text('We will fight up close, seize the moment and stay in it'),
+///     Text('It’s either that or meet the business end of a bayonet'),
+///     Text('The code word is ‘Rochambeau,’ dig me?'),
+///     Text('Rochambeau!', style: DefaultTextStyle.of(context).style.apply(fontSizeFactor: 2.0)),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ## Troubleshooting
+///
+/// ### When the incoming vertical constraints are unbounded
+///
+/// When a [Column] has one or more [Expanded] or [Flexible] children, and is
+/// placed in another [Column], or in a [ListView], or in some other context
+/// that does not provide a maximum height constraint for the [Column], you will
+/// get an exception at runtime saying that there are children with non-zero
+/// flex but the vertical constraints are unbounded.
+///
+/// The problem, as described in the details that accompany that exception, is
+/// that using [Flexible] or [Expanded] means that the remaining space after
+/// laying out all the other children must be shared equally, but if the
+/// incoming vertical constraints are unbounded, there is infinite remaining
+/// space.
+///
+/// The key to solving this problem is usually to determine why the [Column] is
+/// receiving unbounded vertical constraints.
+///
+/// One common reason for this to happen is that the [Column] has been placed in
+/// another [Column] (without using [Expanded] or [Flexible] around the inner
+/// nested [Column]). When a [Column] lays out its non-flex children (those that
+/// have neither [Expanded] or [Flexible] around them), it gives them unbounded
+/// constraints so that they can determine their own dimensions (passing
+/// unbounded constraints usually signals to the child that it should
+/// shrink-wrap its contents). The solution in this case is typically to just
+/// wrap the inner column in an [Expanded] to indicate that it should take the
+/// remaining space of the outer column, rather than being allowed to take any
+/// amount of room it desires.
+///
+/// Another reason for this message to be displayed is nesting a [Column] inside
+/// a [ListView] or other vertical scrollable. In that scenario, there really is
+/// infinite vertical space (the whole point of a vertical scrolling list is to
+/// allow infinite space vertically). In such scenarios, it is usually worth
+/// examining why the inner [Column] should have an [Expanded] or [Flexible]
+/// child: what size should the inner children really be? The solution in this
+/// case is typically to remove the [Expanded] or [Flexible] widgets from around
+/// the inner children.
+///
+/// For more discussion about constraints, see [BoxConstraints].
+///
+/// ### The yellow and black striped banner
+///
+/// When the contents of a [Column] exceed the amount of space available, the
+/// [Column] overflows, and the contents are clipped. In debug mode, a yellow
+/// and black striped bar is rendered at the overflowing edge to indicate the
+/// problem, and a message is printed below the [Column] saying how much
+/// overflow was detected.
+///
+/// The usual solution is to use a [ListView] rather than a [Column], to enable
+/// the contents to scroll when vertical space is limited.
+///
+/// ## Layout algorithm
+///
+/// _This section describes how a [Column] is rendered by the framework._
+/// _See [BoxConstraints] for an introduction to box layout models._
+///
+/// Layout for a [Column] proceeds in six steps:
+///
+/// 1. Layout each child a null or zero flex factor (e.g., those that are not
+///    [Expanded]) with unbounded vertical constraints and the incoming
+///    horizontal constraints. If the [crossAxisAlignment] is
+///    [CrossAxisAlignment.stretch], instead use tight horizontal constraints
+///    that match the incoming max width.
+/// 2. Divide the remaining vertical space among the children with non-zero
+///    flex factors (e.g., those that are [Expanded]) according to their flex
+///    factor. For example, a child with a flex factor of 2.0 will receive twice
+///    the amount of vertical space as a child with a flex factor of 1.0.
+/// 3. Layout each of the remaining children with the same horizontal
+///    constraints as in step 1, but instead of using unbounded vertical
+///    constraints, use vertical constraints based on the amount of space
+///    allocated in step 2. Children with [Flexible.fit] properties that are
+///    [FlexFit.tight] are given tight constraints (i.e., forced to fill the
+///    allocated space), and children with [Flexible.fit] properties that are
+///    [FlexFit.loose] are given loose constraints (i.e., not forced to fill the
+///    allocated space).
+/// 4. The width of the [Column] is the maximum width of the children (which
+///    will always satisfy the incoming horizontal constraints).
+/// 5. The height of the [Column] is determined by the [mainAxisSize] property.
+///    If the [mainAxisSize] property is [MainAxisSize.max], then the height of
+///    the [Column] is the max height of the incoming constraints. If the
+///    [mainAxisSize] property is [MainAxisSize.min], then the height of the
+///    [Column] is the sum of heights of the children (subject to the incoming
+///    constraints).
+/// 6. Determine the position for each child according to the
+///    [mainAxisAlignment] and the [crossAxisAlignment]. For example, if the
+///    [mainAxisAlignment] is [MainAxisAlignment.spaceBetween], any vertical
+///    space that has not been allocated to children is divided evenly and
+///    placed between the children.
+///
+/// See also:
+///
+///  * [Row], for a horizontal equivalent.
+///  * [Flex], if you don't know in advance if you want a horizontal or vertical
+///    arrangement.
+///  * [Expanded], to indicate children that should take all the remaining room.
+///  * [Flexible], to indicate children that should share the remaining room but
+///    that may size smaller (leaving some remaining room unused).
+///  * [SingleChildScrollView], whose documentation discusses some ways to
+///    use a [Column] inside a scrolling container.
+///  * [Spacer], a widget that takes up space proportional to its flex value.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Column extends Flex {
+  /// Creates a vertical array of children.
+  ///
+  /// The [mainAxisAlignment], [mainAxisSize], [crossAxisAlignment], and
+  /// [verticalDirection] arguments must not be null.
+  /// If [crossAxisAlignment] is [CrossAxisAlignment.baseline], then
+  /// [textBaseline] must not be null.
+  ///
+  /// The [textDirection] argument defaults to the ambient [Directionality], if
+  /// any. If there is no ambient directionality, and a text direction is going
+  /// to be necessary to disambiguate `start` or `end` values for the
+  /// [crossAxisAlignment], the [textDirection] must not be null.
+  Column({
+    Key? key,
+    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
+    MainAxisSize mainAxisSize = MainAxisSize.max,
+    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
+    TextDirection? textDirection,
+    VerticalDirection verticalDirection = VerticalDirection.down,
+    TextBaseline? textBaseline,
+    List<Widget> children = const <Widget>[],
+  }) : super(
+    children: children,
+    key: key,
+    direction: Axis.vertical,
+    mainAxisAlignment: mainAxisAlignment,
+    mainAxisSize: mainAxisSize,
+    crossAxisAlignment: crossAxisAlignment,
+    textDirection: textDirection,
+    verticalDirection: verticalDirection,
+    textBaseline: textBaseline,
+  );
+}
+
+/// A widget that controls how a child of a [Row], [Column], or [Flex] flexes.
+///
+/// Using a [Flexible] widget gives a child of a [Row], [Column], or [Flex]
+/// the flexibility to expand to fill the available space in the main axis
+/// (e.g., horizontally for a [Row] or vertically for a [Column]), but, unlike
+/// [Expanded], [Flexible] does not require the child to fill the available
+/// space.
+///
+/// A [Flexible] widget must be a descendant of a [Row], [Column], or [Flex],
+/// and the path from the [Flexible] widget to its enclosing [Row], [Column], or
+/// [Flex] must contain only [StatelessWidget]s or [StatefulWidget]s (not other
+/// kinds of widgets, like [RenderObjectWidget]s).
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=CI7x0mAZiY0}
+///
+/// See also:
+///
+///  * [Expanded], which forces the child to expand to fill the available space.
+///  * [Spacer], a widget that takes up space proportional to its flex value.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Flexible extends ParentDataWidget<FlexParentData> {
+  /// Creates a widget that controls how a child of a [Row], [Column], or [Flex]
+  /// flexes.
+  const Flexible({
+    Key? key,
+    this.flex = 1,
+    this.fit = FlexFit.loose,
+    required Widget child,
+  }) : super(key: key, child: child);
+
+  /// The flex factor to use for this child.
+  ///
+  /// If null or zero, the child is inflexible and determines its own size. If
+  /// non-zero, the amount of space the child's can occupy in the main axis is
+  /// determined by dividing the free space (after placing the inflexible
+  /// children) according to the flex factors of the flexible children.
+  final int flex;
+
+  /// How a flexible child is inscribed into the available space.
+  ///
+  /// If [flex] is non-zero, the [fit] determines whether the child fills the
+  /// space the parent makes available during layout. If the fit is
+  /// [FlexFit.tight], the child is required to fill the available space. If the
+  /// fit is [FlexFit.loose], the child can be at most as large as the available
+  /// space (but is allowed to be smaller).
+  final FlexFit fit;
+
+  @override
+  void applyParentData(RenderObject renderObject) {
+    assert(renderObject.parentData is FlexParentData);
+    final FlexParentData parentData = renderObject.parentData! as FlexParentData;
+    bool needsLayout = false;
+
+    if (parentData.flex != flex) {
+      parentData.flex = flex;
+      needsLayout = true;
+    }
+
+    if (parentData.fit != fit) {
+      parentData.fit = fit;
+      needsLayout = true;
+    }
+
+    if (needsLayout) {
+      final AbstractNode? targetParent = renderObject.parent;
+      if (targetParent is RenderObject)
+        targetParent.markNeedsLayout();
+    }
+  }
+
+  @override
+  Type get debugTypicalAncestorWidgetClass => Flex;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(IntProperty('flex', flex));
+  }
+}
+
+/// A widget that expands a child of a [Row], [Column], or [Flex]
+/// so that the child fills the available space.
+///
+/// Using an [Expanded] widget makes a child of a [Row], [Column], or [Flex]
+/// expand to fill the available space along the main axis (e.g., horizontally for
+/// a [Row] or vertically for a [Column]). If multiple children are expanded,
+/// the available space is divided among them according to the [flex] factor.
+///
+/// An [Expanded] widget must be a descendant of a [Row], [Column], or [Flex],
+/// and the path from the [Expanded] widget to its enclosing [Row], [Column], or
+/// [Flex] must contain only [StatelessWidget]s or [StatefulWidget]s (not other
+/// kinds of widgets, like [RenderObjectWidget]s).
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=_rnZaagadyo}
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+/// This example shows how to use an [Expanded] widget in a [Column] so that
+/// its middle child, a [Container] here, expands to fill the space.
+///
+/// ![This results in two thin blue boxes with a larger amber box in between.](https://flutter.github.io/assets-for-api-docs/assets/widgets/expanded_column.png)
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: AppBar(
+///       title: Text('Expanded Column Sample'),
+///     ),
+///     body: Center(
+///        child: Column(
+///         children: <Widget>[
+///           Container(
+///             color: Colors.blue,
+///             height: 100,
+///             width: 100,
+///           ),
+///           Expanded(
+///             child: Container(
+///               color: Colors.amber,
+///               width: 100,
+///             ),
+///           ),
+///           Container(
+///             color: Colors.blue,
+///             height: 100,
+///             width: 100,
+///           ),
+///         ],
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+/// This example shows how to use an [Expanded] widget in a [Row] with multiple
+/// children expanded, utilizing the [flex] factor to prioritize available space.
+///
+/// ![This results in a wide amber box, followed by a thin blue box, with a medium width amber box at the end.](https://flutter.github.io/assets-for-api-docs/assets/widgets/expanded_row.png)
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: AppBar(
+///       title: Text('Expanded Row Sample'),
+///     ),
+///     body: Center(
+///       child: Row(
+///         children: <Widget>[
+///           Expanded(
+///             flex: 2,
+///             child: Container(
+///               color: Colors.amber,
+///               height: 100,
+///             ),
+///           ),
+///           Container(
+///             color: Colors.blue,
+///             height: 100,
+///             width: 50,
+///           ),
+///           Expanded(
+///             flex: 1,
+///             child: Container(
+///               color: Colors.amber,
+///               height: 100,
+///             ),
+///           ),
+///         ],
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Flexible], which does not force the child to fill the available space.
+///  * [Spacer], a widget that takes up space proportional to its flex value.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Expanded extends Flexible {
+  /// Creates a widget that expands a child of a [Row], [Column], or [Flex]
+  /// so that the child fills the available space along the flex widget's
+  /// main axis.
+  const Expanded({
+    Key? key,
+    int flex = 1,
+    required Widget child,
+  }) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
+}
+
+/// A widget that displays its children in multiple horizontal or vertical runs.
+///
+/// A [Wrap] lays out each child and attempts to place the child adjacent to the
+/// previous child in the main axis, given by [direction], leaving [spacing]
+/// space in between. If there is not enough space to fit the child, [Wrap]
+/// creates a new _run_ adjacent to the existing children in the cross axis.
+///
+/// After all the children have been allocated to runs, the children within the
+/// runs are positioned according to the [alignment] in the main axis and
+/// according to the [crossAxisAlignment] in the cross axis.
+///
+/// The runs themselves are then positioned in the cross axis according to the
+/// [runSpacing] and [runAlignment].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=z5iw2SeFx2M}
+///
+/// {@tool snippet}
+///
+/// This example renders some [Chip]s representing four contacts in a [Wrap] so
+/// that they flow across lines as necessary.
+///
+/// ```dart
+/// Wrap(
+///   spacing: 8.0, // gap between adjacent chips
+///   runSpacing: 4.0, // gap between lines
+///   children: <Widget>[
+///     Chip(
+///       avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('AH')),
+///       label: Text('Hamilton'),
+///     ),
+///     Chip(
+///       avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('ML')),
+///       label: Text('Lafayette'),
+///     ),
+///     Chip(
+///       avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('HM')),
+///       label: Text('Mulligan'),
+///     ),
+///     Chip(
+///       avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('JL')),
+///       label: Text('Laurens'),
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Row], which places children in one line, and gives control over their
+///    alignment and spacing.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Wrap extends MultiChildRenderObjectWidget {
+  /// Creates a wrap layout.
+  ///
+  /// By default, the wrap layout is horizontal and both the children and the
+  /// runs are aligned to the start.
+  ///
+  /// The [textDirection] argument defaults to the ambient [Directionality], if
+  /// any. If there is no ambient directionality, and a text direction is going
+  /// to be necessary to decide which direction to lay the children in or to
+  /// disambiguate `start` or `end` values for the main or cross axis
+  /// directions, the [textDirection] must not be null.
+  Wrap({
+    Key? key,
+    this.direction = Axis.horizontal,
+    this.alignment = WrapAlignment.start,
+    this.spacing = 0.0,
+    this.runAlignment = WrapAlignment.start,
+    this.runSpacing = 0.0,
+    this.crossAxisAlignment = WrapCrossAlignment.start,
+    this.textDirection,
+    this.verticalDirection = VerticalDirection.down,
+    this.clipBehavior = Clip.none,
+    List<Widget> children = const <Widget>[],
+  }) : assert(clipBehavior != null), super(key: key, children: children);
+
+  /// The direction to use as the main axis.
+  ///
+  /// For example, if [direction] is [Axis.horizontal], the default, the
+  /// children are placed adjacent to one another in a horizontal run until the
+  /// available horizontal space is consumed, at which point a subsequent
+  /// children are placed in a new run vertically adjacent to the previous run.
+  final Axis direction;
+
+  /// How the children within a run should be placed in the main axis.
+  ///
+  /// For example, if [alignment] is [WrapAlignment.center], the children in
+  /// each run are grouped together in the center of their run in the main axis.
+  ///
+  /// Defaults to [WrapAlignment.start].
+  ///
+  /// See also:
+  ///
+  ///  * [runAlignment], which controls how the runs are placed relative to each
+  ///    other in the cross axis.
+  ///  * [crossAxisAlignment], which controls how the children within each run
+  ///    are placed relative to each other in the cross axis.
+  final WrapAlignment alignment;
+
+  /// How much space to place between children in a run in the main axis.
+  ///
+  /// For example, if [spacing] is 10.0, the children will be spaced at least
+  /// 10.0 logical pixels apart in the main axis.
+  ///
+  /// If there is additional free space in a run (e.g., because the wrap has a
+  /// minimum size that is not filled or because some runs are longer than
+  /// others), the additional free space will be allocated according to the
+  /// [alignment].
+  ///
+  /// Defaults to 0.0.
+  final double spacing;
+
+  /// How the runs themselves should be placed in the cross axis.
+  ///
+  /// For example, if [runAlignment] is [WrapAlignment.center], the runs are
+  /// grouped together in the center of the overall [Wrap] in the cross axis.
+  ///
+  /// Defaults to [WrapAlignment.start].
+  ///
+  /// See also:
+  ///
+  ///  * [alignment], which controls how the children within each run are placed
+  ///    relative to each other in the main axis.
+  ///  * [crossAxisAlignment], which controls how the children within each run
+  ///    are placed relative to each other in the cross axis.
+  final WrapAlignment runAlignment;
+
+  /// How much space to place between the runs themselves in the cross axis.
+  ///
+  /// For example, if [runSpacing] is 10.0, the runs will be spaced at least
+  /// 10.0 logical pixels apart in the cross axis.
+  ///
+  /// If there is additional free space in the overall [Wrap] (e.g., because
+  /// the wrap has a minimum size that is not filled), the additional free space
+  /// will be allocated according to the [runAlignment].
+  ///
+  /// Defaults to 0.0.
+  final double runSpacing;
+
+  /// How the children within a run should be aligned relative to each other in
+  /// the cross axis.
+  ///
+  /// For example, if this is set to [WrapCrossAlignment.end], and the
+  /// [direction] is [Axis.horizontal], then the children within each
+  /// run will have their bottom edges aligned to the bottom edge of the run.
+  ///
+  /// Defaults to [WrapCrossAlignment.start].
+  ///
+  /// See also:
+  ///
+  ///  * [alignment], which controls how the children within each run are placed
+  ///    relative to each other in the main axis.
+  ///  * [runAlignment], which controls how the runs are placed relative to each
+  ///    other in the cross axis.
+  final WrapCrossAlignment crossAxisAlignment;
+
+  /// Determines the order to lay children out horizontally and how to interpret
+  /// `start` and `end` in the horizontal direction.
+  ///
+  /// Defaults to the ambient [Directionality].
+  ///
+  /// If the [direction] is [Axis.horizontal], this controls order in which the
+  /// children are positioned (left-to-right or right-to-left), and the meaning
+  /// of the [alignment] property's [WrapAlignment.start] and
+  /// [WrapAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.horizontal], and either the
+  /// [alignment] is either [WrapAlignment.start] or [WrapAlignment.end], or
+  /// there's more than one child, then the [textDirection] (or the ambient
+  /// [Directionality]) must not be null.
+  ///
+  /// If the [direction] is [Axis.vertical], this controls the order in which
+  /// runs are positioned, the meaning of the [runAlignment] property's
+  /// [WrapAlignment.start] and [WrapAlignment.end] values, as well as the
+  /// [crossAxisAlignment] property's [WrapCrossAlignment.start] and
+  /// [WrapCrossAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.vertical], and either the
+  /// [runAlignment] is either [WrapAlignment.start] or [WrapAlignment.end], the
+  /// [crossAxisAlignment] is either [WrapCrossAlignment.start] or
+  /// [WrapCrossAlignment.end], or there's more than one child, then the
+  /// [textDirection] (or the ambient [Directionality]) must not be null.
+  final TextDirection? textDirection;
+
+  /// Determines the order to lay children out vertically and how to interpret
+  /// `start` and `end` in the vertical direction.
+  ///
+  /// If the [direction] is [Axis.vertical], this controls which order children
+  /// are painted in (down or up), the meaning of the [alignment] property's
+  /// [WrapAlignment.start] and [WrapAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.vertical], and either the [alignment]
+  /// is either [WrapAlignment.start] or [WrapAlignment.end], or there's
+  /// more than one child, then the [verticalDirection] must not be null.
+  ///
+  /// If the [direction] is [Axis.horizontal], this controls the order in which
+  /// runs are positioned, the meaning of the [runAlignment] property's
+  /// [WrapAlignment.start] and [WrapAlignment.end] values, as well as the
+  /// [crossAxisAlignment] property's [WrapCrossAlignment.start] and
+  /// [WrapCrossAlignment.end] values.
+  ///
+  /// If the [direction] is [Axis.horizontal], and either the
+  /// [runAlignment] is either [WrapAlignment.start] or [WrapAlignment.end], the
+  /// [crossAxisAlignment] is either [WrapCrossAlignment.start] or
+  /// [WrapCrossAlignment.end], or there's more than one child, then the
+  /// [verticalDirection] must not be null.
+  final VerticalDirection verticalDirection;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none].
+  final Clip clipBehavior;
+
+  @override
+  RenderWrap createRenderObject(BuildContext context) {
+    return RenderWrap(
+      direction: direction,
+      alignment: alignment,
+      spacing: spacing,
+      runAlignment: runAlignment,
+      runSpacing: runSpacing,
+      crossAxisAlignment: crossAxisAlignment,
+      textDirection: textDirection ?? Directionality.maybeOf(context),
+      verticalDirection: verticalDirection,
+      clipBehavior: clipBehavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderWrap renderObject) {
+    renderObject
+      ..direction = direction
+      ..alignment = alignment
+      ..spacing = spacing
+      ..runAlignment = runAlignment
+      ..runSpacing = runSpacing
+      ..crossAxisAlignment = crossAxisAlignment
+      ..textDirection = textDirection ?? Directionality.maybeOf(context)
+      ..verticalDirection = verticalDirection
+      ..clipBehavior = clipBehavior;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<Axis>('direction', direction));
+    properties.add(EnumProperty<WrapAlignment>('alignment', alignment));
+    properties.add(DoubleProperty('spacing', spacing));
+    properties.add(EnumProperty<WrapAlignment>('runAlignment', runAlignment));
+    properties.add(DoubleProperty('runSpacing', runSpacing));
+    properties.add(DoubleProperty('crossAxisAlignment', runSpacing));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    properties.add(EnumProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: VerticalDirection.down));
+  }
+}
+
+/// A widget that sizes and positions children efficiently, according to the
+/// logic in a [FlowDelegate].
+///
+/// Flow layouts are optimized for repositioning children using transformation
+/// matrices.
+///
+/// The flow container is sized independently from the children by the
+/// [FlowDelegate.getSize] function of the delegate. The children are then sized
+/// independently given the constraints from the
+/// [FlowDelegate.getConstraintsForChild] function.
+///
+/// Rather than positioning the children during layout, the children are
+/// positioned using transformation matrices during the paint phase using the
+/// matrices from the [FlowDelegate.paintChildren] function. The children can be
+/// repositioned efficiently by simply repainting the flow, which happens
+/// without the children being laid out again (contrast this with a [Stack],
+/// which does the sizing and positioning together during layout).
+///
+/// The most efficient way to trigger a repaint of the flow is to supply an
+/// animation to the constructor of the [FlowDelegate]. The flow will listen to
+/// this animation and repaint whenever the animation ticks, avoiding both the
+/// build and layout phases of the pipeline.
+///
+/// See also:
+///
+///  * [Wrap], which provides the layout model that some other frameworks call
+///    "flow", and is otherwise unrelated to [Flow].
+///  * [FlowDelegate], which controls the visual presentation of the children.
+///  * [Stack], which arranges children relative to the edges of the container.
+///  * [CustomSingleChildLayout], which uses a delegate to control the layout of
+///    a single child.
+///  * [CustomMultiChildLayout], which uses a delegate to position multiple
+///    children.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+///
+///
+/// {@tool dartpad --template=freeform_no_null_safety}
+///
+/// This example uses the [Flow] widget to create a menu that opens and closes
+/// as it is interacted with, shown above. The color of the button in the menu
+/// changes to indicate which one has been selected.
+///
+/// ```dart main
+/// import 'package:flute/material.dart';
+///
+/// void main() => runApp(FlowApp());
+///
+/// class FlowApp extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return MaterialApp(
+///       home: Scaffold(
+///         appBar: AppBar(
+///           title: const Text('Flow Example'),
+///         ),
+///         body: FlowMenu(),
+///       ),
+///     );
+///   }
+/// }
+///
+/// class FlowMenu extends StatefulWidget {
+///   @override
+///   _FlowMenuState createState() => _FlowMenuState();
+/// }
+///
+/// class _FlowMenuState extends State<FlowMenu> with SingleTickerProviderStateMixin {
+///   AnimationController menuAnimation;
+///   IconData lastTapped = Icons.notifications;
+///   final List<IconData> menuItems = <IconData>[
+///     Icons.home,
+///     Icons.new_releases,
+///     Icons.notifications,
+///     Icons.settings,
+///     Icons.menu,
+///   ];
+///
+///   void _updateMenu(IconData icon) {
+///     if (icon != Icons.menu)
+///       setState(() => lastTapped = icon);
+///   }
+///
+///   @override
+///   void initState() {
+///     super.initState();
+///     menuAnimation = AnimationController(
+///       duration: const Duration(milliseconds: 250),
+///       vsync: this,
+///     );
+///   }
+///
+///   Widget flowMenuItem(IconData icon) {
+///     final double buttonDiameter = MediaQuery.of(context).size.width / menuItems.length;
+///     return Padding(
+///       padding: const EdgeInsets.symmetric(vertical: 8.0),
+///       child: RawMaterialButton(
+///         fillColor: lastTapped == icon ? Colors.amber[700] : Colors.blue,
+///         splashColor: Colors.amber[100],
+///         shape: CircleBorder(),
+///         constraints: BoxConstraints.tight(Size(buttonDiameter, buttonDiameter)),
+///         onPressed: () {
+///           _updateMenu(icon);
+///           menuAnimation.status == AnimationStatus.completed
+///             ? menuAnimation.reverse()
+///             : menuAnimation.forward();
+///         },
+///         child: Icon(
+///           icon,
+///           color: Colors.white,
+///           size: 45.0,
+///         ),
+///       ),
+///     );
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Container(
+///       child: Flow(
+///         delegate: FlowMenuDelegate(menuAnimation: menuAnimation),
+///         children: menuItems.map<Widget>((IconData icon) => flowMenuItem(icon)).toList(),
+///       ),
+///     );
+///   }
+/// }
+///
+/// class FlowMenuDelegate extends FlowDelegate {
+///   FlowMenuDelegate({this.menuAnimation}) : super(repaint: menuAnimation);
+///
+///   final Animation<double> menuAnimation;
+///
+///   @override
+///   bool shouldRepaint(FlowMenuDelegate oldDelegate) {
+///     return menuAnimation != oldDelegate.menuAnimation;
+///   }
+///
+///   @override
+///   void paintChildren(FlowPaintingContext context) {
+///     double dx = 0.0;
+///     for (int i = 0; i < context.childCount; ++i) {
+///       dx = context.getChildSize(i).width * i;
+///       context.paintChild(
+///         i,
+///         transform: Matrix4.translationValues(
+///           dx * menuAnimation.value,
+///           0,
+///           0,
+///         ),
+///       );
+///     }
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+class Flow extends MultiChildRenderObjectWidget {
+  /// Creates a flow layout.
+  ///
+  /// Wraps each of the given children in a [RepaintBoundary] to avoid
+  /// repainting the children when the flow repaints.
+  ///
+  /// The [delegate] argument must not be null.
+  Flow({
+    Key? key,
+    required this.delegate,
+    List<Widget> children = const <Widget>[],
+    this.clipBehavior = Clip.hardEdge,
+  }) : assert(delegate != null),
+       assert(clipBehavior != null),
+       super(key: key, children: RepaintBoundary.wrapAll(children));
+       // https://github.com/dart-lang/sdk/issues/29277
+
+  /// Creates a flow layout.
+  ///
+  /// Does not wrap the given children in repaint boundaries, unlike the default
+  /// constructor. Useful when the child is trivial to paint or already contains
+  /// a repaint boundary.
+  ///
+  /// The [delegate] argument must not be null.
+  Flow.unwrapped({
+    Key? key,
+    required this.delegate,
+    List<Widget> children = const <Widget>[],
+    this.clipBehavior = Clip.hardEdge,
+  }) : assert(delegate != null),
+       assert(clipBehavior != null),
+       super(key: key, children: children);
+
+  /// The delegate that controls the transformation matrices of the children.
+  final FlowDelegate delegate;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  final Clip clipBehavior;
+
+  @override
+  RenderFlow createRenderObject(BuildContext context) => RenderFlow(delegate: delegate, clipBehavior: clipBehavior);
+
+  @override
+  void updateRenderObject(BuildContext context, RenderFlow renderObject) {
+    renderObject.delegate = delegate;
+    renderObject.clipBehavior = clipBehavior;
+  }
+}
+
+/// A paragraph of rich text.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=rykDVh-QFfw}
+///
+/// The [RichText] widget displays text that uses multiple different styles. The
+/// text to display is described using a tree of [TextSpan] objects, each of
+/// which has an associated style that is used for that subtree. The text might
+/// break across multiple lines or might all be displayed on the same line
+/// depending on the layout constraints.
+///
+/// Text displayed in a [RichText] widget must be explicitly styled. When
+/// picking which style to use, consider using [DefaultTextStyle.of] the current
+/// [BuildContext] to provide defaults. For more details on how to style text in
+/// a [RichText] widget, see the documentation for [TextStyle].
+///
+/// Consider using the [Text] widget to integrate with the [DefaultTextStyle]
+/// automatically. When all the text uses the same style, the default constructor
+/// is less verbose. The [Text.rich] constructor allows you to style multiple
+/// spans with the default text style while still allowing specified styles per
+/// span.
+///
+/// {@tool snippet}
+///
+/// This sample demonstrates how to mix and match text with different text
+/// styles using the [RichText] Widget. It displays the text "Hello bold world,"
+/// emphasizing the word "bold" using a bold font weight.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/rich_text.png)
+///
+/// ```dart
+/// RichText(
+///   text: TextSpan(
+///     text: 'Hello ',
+///     style: DefaultTextStyle.of(context).style,
+///     children: <TextSpan>[
+///       TextSpan(text: 'bold', style: TextStyle(fontWeight: FontWeight.bold)),
+///       TextSpan(text: ' world!'),
+///     ],
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [TextStyle], which discusses how to style text.
+///  * [TextSpan], which is used to describe the text in a paragraph.
+///  * [Text], which automatically applies the ambient styles described by a
+///    [DefaultTextStyle] to a single string.
+///  * [Text.rich], a const text widget that provides similar functionality
+///    as [RichText]. [Text.rich] will inherit [TextStyle] from [DefaultTextStyle].
+class RichText extends MultiChildRenderObjectWidget {
+  /// Creates a paragraph of rich text.
+  ///
+  /// The [text], [textAlign], [softWrap], [overflow], and [textScaleFactor]
+  /// arguments must not be null.
+  ///
+  /// The [maxLines] property may be null (and indeed defaults to null), but if
+  /// it is not null, it must be greater than zero.
+  ///
+  /// The [textDirection], if null, defaults to the ambient [Directionality],
+  /// which in that case must not be null.
+  RichText({
+    Key? key,
+    required this.text,
+    this.textAlign = TextAlign.start,
+    this.textDirection,
+    this.softWrap = true,
+    this.overflow = TextOverflow.clip,
+    this.textScaleFactor = 1.0,
+    this.maxLines,
+    this.locale,
+    this.strutStyle,
+    this.textWidthBasis = TextWidthBasis.parent,
+    this.textHeightBehavior,
+  }) : assert(text != null),
+       assert(textAlign != null),
+       assert(softWrap != null),
+       assert(overflow != null),
+       assert(textScaleFactor != null),
+       assert(maxLines == null || maxLines > 0),
+       assert(textWidthBasis != null),
+       super(key: key, children: _extractChildren(text));
+
+  // Traverses the InlineSpan tree and depth-first collects the list of
+  // child widgets that are created in WidgetSpans.
+  static List<Widget> _extractChildren(InlineSpan span) {
+    int index = 0;
+    final List<Widget> result = <Widget>[];
+    span.visitChildren((InlineSpan span) {
+      if (span is WidgetSpan) {
+        result.add(Semantics(
+          tagForChildren: PlaceholderSpanIndexSemanticsTag(index++),
+          child: span.child,
+        ));
+      }
+      return true;
+    });
+    return result;
+  }
+
+  /// The text to display in this widget.
+  final InlineSpan text;
+
+  /// How the text should be aligned horizontally.
+  final TextAlign textAlign;
+
+  /// The directionality of the text.
+  ///
+  /// This decides how [textAlign] values like [TextAlign.start] and
+  /// [TextAlign.end] are interpreted.
+  ///
+  /// This is also used to disambiguate how to render bidirectional text. For
+  /// example, if the [text] is an English phrase followed by a Hebrew phrase,
+  /// in a [TextDirection.ltr] context the English phrase will be on the left
+  /// and the Hebrew phrase to its right, while in a [TextDirection.rtl]
+  /// context, the English phrase will be on the right and the Hebrew phrase on
+  /// its left.
+  ///
+  /// Defaults to the ambient [Directionality], if any. If there is no ambient
+  /// [Directionality], then this must not be null.
+  final TextDirection? textDirection;
+
+  /// Whether the text should break at soft line breaks.
+  ///
+  /// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space.
+  final bool softWrap;
+
+  /// How visual overflow should be handled.
+  final TextOverflow overflow;
+
+  /// The number of font pixels for each logical pixel.
+  ///
+  /// For example, if the text scale factor is 1.5, text will be 50% larger than
+  /// the specified font size.
+  final double textScaleFactor;
+
+  /// An optional maximum number of lines for the text to span, wrapping if necessary.
+  /// If the text exceeds the given number of lines, it will be truncated according
+  /// to [overflow].
+  ///
+  /// If this is 1, text will not wrap. Otherwise, text will be wrapped at the
+  /// edge of the box.
+  final int? maxLines;
+
+  /// Used to select a font when the same Unicode character can
+  /// be rendered differently, depending on the locale.
+  ///
+  /// It's rarely necessary to set this property. By default its value
+  /// is inherited from the enclosing app with `Localizations.localeOf(context)`.
+  ///
+  /// See [RenderParagraph.locale] for more information.
+  final Locale? locale;
+
+  /// {@macro flutter.painting.textPainter.strutStyle}
+  final StrutStyle? strutStyle;
+
+  /// {@macro flutter.painting.textPainter.textWidthBasis}
+  final TextWidthBasis textWidthBasis;
+
+  /// {@macro flutter.dart:ui.textHeightBehavior}
+  final ui.TextHeightBehavior? textHeightBehavior;
+
+  @override
+  RenderParagraph createRenderObject(BuildContext context) {
+    assert(textDirection != null || debugCheckHasDirectionality(context));
+    return RenderParagraph(text,
+      textAlign: textAlign,
+      textDirection: textDirection ?? Directionality.of(context),
+      softWrap: softWrap,
+      overflow: overflow,
+      textScaleFactor: textScaleFactor,
+      maxLines: maxLines,
+      strutStyle: strutStyle,
+      textWidthBasis: textWidthBasis,
+      textHeightBehavior: textHeightBehavior,
+      locale: locale ?? Localizations.maybeLocaleOf(context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
+    assert(textDirection != null || debugCheckHasDirectionality(context));
+    renderObject
+      ..text = text
+      ..textAlign = textAlign
+      ..textDirection = textDirection ?? Directionality.of(context)
+      ..softWrap = softWrap
+      ..overflow = overflow
+      ..textScaleFactor = textScaleFactor
+      ..maxLines = maxLines
+      ..strutStyle = strutStyle
+      ..textWidthBasis = textWidthBasis
+      ..textHeightBehavior = textHeightBehavior
+      ..locale = locale ?? Localizations.maybeLocaleOf(context);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: TextAlign.start));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    properties.add(FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true));
+    properties.add(EnumProperty<TextOverflow>('overflow', overflow, defaultValue: TextOverflow.clip));
+    properties.add(DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: 1.0));
+    properties.add(IntProperty('maxLines', maxLines, ifNull: 'unlimited'));
+    properties.add(EnumProperty<TextWidthBasis>('textWidthBasis', textWidthBasis, defaultValue: TextWidthBasis.parent));
+    properties.add(StringProperty('text', text.toPlainText()));
+    properties.add(DiagnosticsProperty<Locale>('locale', locale, defaultValue: null));
+    properties.add(DiagnosticsProperty<StrutStyle>('strutStyle', strutStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextHeightBehavior>('textHeightBehavior', textHeightBehavior, defaultValue: null));
+  }
+}
+
+/// A widget that displays a [dart:ui.Image] directly.
+///
+/// The image is painted using [paintImage], which describes the meanings of the
+/// various fields on this class in more detail.
+///
+/// The [image] is not disposed of by this widget. Creators of the widget are
+/// expected to call [Image.dispose] on the [image] once the [RawImage] is no
+/// longer buildable.
+///
+/// This widget is rarely used directly. Instead, consider using [Image].
+class RawImage extends LeafRenderObjectWidget {
+  /// Creates a widget that displays an image.
+  ///
+  /// The [scale], [alignment], [repeat], [matchTextDirection] and [filterQuality] arguments must
+  /// not be null.
+  const RawImage({
+    Key? key,
+    this.image,
+    this.debugImageLabel,
+    this.width,
+    this.height,
+    this.scale = 1.0,
+    this.color,
+    this.colorBlendMode,
+    this.fit,
+    this.alignment = Alignment.center,
+    this.repeat = ImageRepeat.noRepeat,
+    this.centerSlice,
+    this.matchTextDirection = false,
+    this.invertColors = false,
+    this.filterQuality = FilterQuality.low,
+    this.isAntiAlias = false,
+  }) : assert(scale != null),
+       assert(alignment != null),
+       assert(repeat != null),
+       assert(matchTextDirection != null),
+       assert(isAntiAlias != null),
+       super(key: key);
+
+  /// The image to display.
+  ///
+  /// Since a [RawImage] is stateless, it does not ever dispose this image.
+  /// Creators of a [RawImage] are expected to call [Image.dispose] on this
+  /// image handle when the [RawImage] will no longer be needed.
+  final ui.Image? image;
+
+  /// A string identifying the source of the image.
+  final String? debugImageLabel;
+
+  /// If non-null, require the image to have this width.
+  ///
+  /// If null, the image will pick a size that best preserves its intrinsic
+  /// aspect ratio.
+  final double? width;
+
+  /// If non-null, require the image to have this height.
+  ///
+  /// If null, the image will pick a size that best preserves its intrinsic
+  /// aspect ratio.
+  final double? height;
+
+  /// Specifies the image's scale.
+  ///
+  /// Used when determining the best display size for the image.
+  final double scale;
+
+  /// If non-null, this color is blended with each image pixel using [colorBlendMode].
+  final Color? color;
+
+  /// Used to set the filterQuality of the image
+  /// Use the "low" quality setting to scale the image, which corresponds to
+  /// bilinear interpolation, rather than the default "none" which corresponds
+  /// to nearest-neighbor.
+  final FilterQuality filterQuality;
+
+  /// Used to combine [color] with this image.
+  ///
+  /// The default is [BlendMode.srcIn]. In terms of the blend mode, [color] is
+  /// the source and this image is the destination.
+  ///
+  /// See also:
+  ///
+  ///  * [BlendMode], which includes an illustration of the effect of each blend mode.
+  final BlendMode? colorBlendMode;
+
+  /// How to inscribe the image into the space allocated during layout.
+  ///
+  /// The default varies based on the other fields. See the discussion at
+  /// [paintImage].
+  final BoxFit? fit;
+
+  /// How to align the image within its bounds.
+  ///
+  /// The alignment aligns the given position in the image to the given position
+  /// in the layout bounds. For example, an [Alignment] alignment of (-1.0,
+  /// -1.0) aligns the image to the top-left corner of its layout bounds, while a
+  /// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
+  /// image with the bottom right corner of its layout bounds. Similarly, an
+  /// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
+  /// middle of the bottom edge of its layout bounds.
+  ///
+  /// To display a subpart of an image, consider using a [CustomPainter] and
+  /// [Canvas.drawImageRect].
+  ///
+  /// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
+  /// [AlignmentDirectional]), then an ambient [Directionality] widget
+  /// must be in scope.
+  ///
+  /// Defaults to [Alignment.center].
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], a class with convenient constants typically used to
+  ///    specify an [AlignmentGeometry].
+  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
+  ///    relative to text direction.
+  final AlignmentGeometry alignment;
+
+  /// How to paint any portions of the layout bounds not covered by the image.
+  final ImageRepeat repeat;
+
+  /// The center slice for a nine-patch image.
+  ///
+  /// The region of the image inside the center slice will be stretched both
+  /// horizontally and vertically to fit the image into its destination. The
+  /// region of the image above and below the center slice will be stretched
+  /// only horizontally and the region of the image to the left and right of
+  /// the center slice will be stretched only vertically.
+  final Rect? centerSlice;
+
+  /// Whether to paint the image in the direction of the [TextDirection].
+  ///
+  /// If this is true, then in [TextDirection.ltr] contexts, the image will be
+  /// drawn with its origin in the top left (the "normal" painting direction for
+  /// images); and in [TextDirection.rtl] contexts, the image will be drawn with
+  /// a scaling factor of -1 in the horizontal direction so that the origin is
+  /// in the top right.
+  ///
+  /// This is occasionally used with images in right-to-left environments, for
+  /// images that were designed for left-to-right locales. Be careful, when
+  /// using this, to not flip images with integral shadows, text, or other
+  /// effects that will look incorrect when flipped.
+  ///
+  /// If this is true, there must be an ambient [Directionality] widget in
+  /// scope.
+  final bool matchTextDirection;
+
+  /// Whether the colors of the image are inverted when drawn.
+  ///
+  /// inverting the colors of an image applies a new color filter to the paint.
+  /// If there is another specified color filter, the invert will be applied
+  /// after it. This is primarily used for implementing smart invert on iOS.
+  ///
+  /// See also:
+  ///
+  ///  * [Paint.invertColors], for the dart:ui implementation.
+  final bool invertColors;
+
+  /// Whether to paint the image with anti-aliasing.
+  ///
+  /// Anti-aliasing alleviates the sawtooth artifact when the image is rotated.
+  final bool isAntiAlias;
+
+  @override
+  RenderImage createRenderObject(BuildContext context) {
+    assert((!matchTextDirection && alignment is Alignment) || debugCheckHasDirectionality(context));
+    assert(
+      image?.debugGetOpenHandleStackTraces()?.isNotEmpty ?? true,
+      'Creator of a RawImage disposed of the image when the RawImage still '
+      'needed it.'
+    );
+    return RenderImage(
+      image: image?.clone(),
+      debugImageLabel: debugImageLabel,
+      width: width,
+      height: height,
+      scale: scale,
+      color: color,
+      colorBlendMode: colorBlendMode,
+      fit: fit,
+      alignment: alignment,
+      repeat: repeat,
+      centerSlice: centerSlice,
+      matchTextDirection: matchTextDirection,
+      textDirection: matchTextDirection || alignment is! Alignment ? Directionality.of(context) : null,
+      invertColors: invertColors,
+      filterQuality: filterQuality,
+      isAntiAlias: isAntiAlias,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderImage renderObject) {
+    assert(
+      image?.debugGetOpenHandleStackTraces()?.isNotEmpty ?? true,
+      'Creator of a RawImage disposed of the image when the RawImage still '
+      'needed it.'
+    );
+    renderObject
+      ..image = image?.clone()
+      ..debugImageLabel = debugImageLabel
+      ..width = width
+      ..height = height
+      ..scale = scale
+      ..color = color
+      ..colorBlendMode = colorBlendMode
+      ..alignment = alignment
+      ..fit = fit
+      ..repeat = repeat
+      ..centerSlice = centerSlice
+      ..matchTextDirection = matchTextDirection
+      ..textDirection = matchTextDirection || alignment is! Alignment ? Directionality.of(context) : null
+      ..invertColors = invertColors
+      ..filterQuality = filterQuality;
+  }
+
+  @override
+  void didUnmountRenderObject(RenderImage renderObject) {
+    // Have the render object dispose its image handle.
+    renderObject.image = null;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ui.Image>('image', image));
+    properties.add(DoubleProperty('width', width, defaultValue: null));
+    properties.add(DoubleProperty('height', height, defaultValue: null));
+    properties.add(DoubleProperty('scale', scale, defaultValue: 1.0));
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null));
+    properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null));
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
+    properties.add(EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
+    properties.add(DiagnosticsProperty<Rect>('centerSlice', centerSlice, defaultValue: null));
+    properties.add(FlagProperty('matchTextDirection', value: matchTextDirection, ifTrue: 'match text direction'));
+    properties.add(DiagnosticsProperty<bool>('invertColors', invertColors));
+    properties.add(EnumProperty<FilterQuality>('filterQuality', filterQuality));
+  }
+}
+
+/// A widget that determines the default asset bundle for its descendants.
+///
+/// For example, used by [Image] to determine which bundle to use for
+/// [AssetImage]s if no bundle is specified explicitly.
+///
+/// {@tool snippet}
+///
+/// This can be used in tests to override what the current asset bundle is, thus
+/// allowing specific resources to be injected into the widget under test.
+///
+/// For example, a test could create a test asset bundle like this:
+///
+/// ```dart
+/// class TestAssetBundle extends CachingAssetBundle {
+///   @override
+///   Future<ByteData> load(String key) async {
+///     if (key == 'resources/test')
+///       return ByteData.view(Uint8List.fromList(utf8.encode('Hello World!')).buffer);
+///     return null;
+///   }
+/// }
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// ...then wrap the widget under test with a [DefaultAssetBundle] using this
+/// bundle implementation:
+///
+/// ```dart
+/// await tester.pumpWidget(
+///   MaterialApp(
+///     home: DefaultAssetBundle(
+///       bundle: TestAssetBundle(),
+///       child: TestWidget(),
+///     ),
+///   ),
+/// );
+/// ```
+/// {@end-tool}
+///
+/// Assuming that `TestWidget` uses [DefaultAssetBundle.of] to obtain its
+/// [AssetBundle], it will now see the `TestAssetBundle`'s "Hello World!" data
+/// when requesting the "resources/test" asset.
+///
+/// See also:
+///
+///  * [AssetBundle], the interface for asset bundles.
+///  * [rootBundle], the default default asset bundle.
+class DefaultAssetBundle extends InheritedWidget {
+  /// Creates a widget that determines the default asset bundle for its descendants.
+  ///
+  /// The [bundle] and [child] arguments must not be null.
+  const DefaultAssetBundle({
+    Key? key,
+    required this.bundle,
+    required Widget child,
+  }) : assert(bundle != null),
+       assert(child != null),
+       super(key: key, child: child);
+
+  /// The bundle to use as a default.
+  final AssetBundle bundle;
+
+  /// The bundle from the closest instance of this class that encloses
+  /// the given context.
+  ///
+  /// If there is no [DefaultAssetBundle] ancestor widget in the tree
+  /// at the given context, then this will return the [rootBundle].
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// AssetBundle bundle = DefaultAssetBundle.of(context);
+  /// ```
+  static AssetBundle of(BuildContext context) {
+    final DefaultAssetBundle? result = context.dependOnInheritedWidgetOfExactType<DefaultAssetBundle>();
+    return result?.bundle ?? rootBundle;
+  }
+
+  @override
+  bool updateShouldNotify(DefaultAssetBundle oldWidget) => bundle != oldWidget.bundle;
+}
+
+/// An adapter for placing a specific [RenderBox] in the widget tree.
+///
+/// A given render object can be placed at most once in the widget tree. This
+/// widget enforces that restriction by keying itself using a [GlobalObjectKey]
+/// for the given render object.
+class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget {
+  /// Creates an adapter for placing a specific [RenderBox] in the widget tree.
+  ///
+  /// The [renderBox] argument must not be null.
+  WidgetToRenderBoxAdapter({
+    required this.renderBox,
+    this.onBuild,
+  }) : assert(renderBox != null),
+       // WidgetToRenderBoxAdapter objects are keyed to their render box. This
+       // prevents the widget being used in the widget hierarchy in two different
+       // places, which would cause the RenderBox to get inserted in multiple
+       // places in the RenderObject tree.
+       super(key: GlobalObjectKey(renderBox));
+
+  /// The render box to place in the widget tree.
+  final RenderBox renderBox;
+
+  /// Called when it is safe to update the render box and its descendants. If
+  /// you update the RenderObject subtree under this widget outside of
+  /// invocations of this callback, features like hit-testing will fail as the
+  /// tree will be dirty.
+  final VoidCallback? onBuild;
+
+  @override
+  RenderBox createRenderObject(BuildContext context) => renderBox;
+
+  @override
+  void updateRenderObject(BuildContext context, RenderBox renderObject) {
+    if (onBuild != null)
+      onBuild!();
+  }
+}
+
+
+// EVENT HANDLING
+
+/// A widget that calls callbacks in response to common pointer events.
+///
+/// It listens to events that can construct gestures, such as when the
+/// pointer is pressed, moved, then released or canceled.
+///
+/// It does not listen to events that are exclusive to mouse, such as when the
+/// mouse enters, exits or hovers a region without pressing any buttons. For
+/// these events, use [MouseRegion].
+///
+/// Rather than listening for raw pointer events, consider listening for
+/// higher-level gestures using [GestureDetector].
+///
+/// ## Layout behavior
+///
+/// _See [BoxConstraints] for an introduction to box layout models._
+///
+/// If it has a child, this widget defers to the child for sizing behavior. If
+/// it does not have a child, it grows to fit the parent instead.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+/// This example makes a [Container] react to being touched, showing a count of
+/// the number of pointer downs and ups.
+///
+/// ```dart imports
+/// import 'package:flute/widgets.dart';
+/// ```
+///
+/// ```dart
+/// int _downCounter = 0;
+/// int _upCounter = 0;
+/// double x = 0.0;
+/// double y = 0.0;
+///
+/// void _incrementDown(PointerEvent details) {
+///   _updateLocation(details);
+///   setState(() {
+///     _downCounter++;
+///   });
+/// }
+/// void _incrementUp(PointerEvent details) {
+///   _updateLocation(details);
+///   setState(() {
+///     _upCounter++;
+///   });
+/// }
+/// void _updateLocation(PointerEvent details) {
+///   setState(() {
+///     x = details.position.dx;
+///     y = details.position.dy;
+///   });
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return ConstrainedBox(
+///     constraints: new BoxConstraints.tight(Size(300.0, 200.0)),
+///     child: Listener(
+///       onPointerDown: _incrementDown,
+///       onPointerMove: _updateLocation,
+///       onPointerUp: _incrementUp,
+///       child: Container(
+///         color: Colors.lightBlueAccent,
+///         child: Column(
+///           mainAxisAlignment: MainAxisAlignment.center,
+///           children: <Widget>[
+///             Text('You have pressed or released in this area this many times:'),
+///             Text(
+///               '$_downCounter presses\n$_upCounter releases',
+///               style: Theme.of(context).textTheme.headline4,
+///             ),
+///             Text(
+///               'The cursor is here: (${x.toStringAsFixed(2)}, ${y.toStringAsFixed(2)})',
+///             ),
+///           ],
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+class Listener extends SingleChildRenderObjectWidget {
+  /// Creates a widget that forwards point events to callbacks.
+  ///
+  /// The [behavior] argument defaults to [HitTestBehavior.deferToChild].
+  const Listener({
+    Key? key,
+    this.onPointerDown,
+    this.onPointerMove,
+    this.onPointerUp,
+    this.onPointerHover,
+    this.onPointerCancel,
+    this.onPointerSignal,
+    this.behavior = HitTestBehavior.deferToChild,
+    Widget? child,
+  }) : assert(behavior != null),
+       super(key: key, child: child);
+
+  /// Called when a pointer comes into contact with the screen (for touch
+  /// pointers), or has its button pressed (for mouse pointers) at this widget's
+  /// location.
+  final PointerDownEventListener? onPointerDown;
+
+  /// Called when a pointer that triggered an [onPointerDown] changes position.
+  final PointerMoveEventListener? onPointerMove;
+
+  /// Called when a pointer that triggered an [onPointerDown] is no longer in
+  /// contact with the screen.
+  final PointerUpEventListener? onPointerUp;
+
+  /// Called when a pointer that has not triggered an [onPointerDown] changes
+  /// position.
+  ///
+  /// This is only fired for pointers which report their location when not down
+  /// (e.g. mouse pointers, but not most touch pointers).
+  final PointerHoverEventListener? onPointerHover;
+
+  /// Called when the input from a pointer that triggered an [onPointerDown] is
+  /// no longer directed towards this receiver.
+  final PointerCancelEventListener? onPointerCancel;
+
+  /// Called when a pointer signal occurs over this object.
+  ///
+  /// See also:
+  ///
+  ///  * [PointerSignalEvent], which goes into more detail on pointer signal
+  ///    events.
+  final PointerSignalEventListener? onPointerSignal;
+
+  /// How to behave during hit testing.
+  final HitTestBehavior behavior;
+
+  @override
+  RenderPointerListener createRenderObject(BuildContext context) {
+    return RenderPointerListener(
+      onPointerDown: onPointerDown,
+      onPointerMove: onPointerMove,
+      onPointerUp: onPointerUp,
+      onPointerHover: onPointerHover,
+      onPointerCancel: onPointerCancel,
+      onPointerSignal: onPointerSignal,
+      behavior: behavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderPointerListener renderObject) {
+    renderObject
+      ..onPointerDown = onPointerDown
+      ..onPointerMove = onPointerMove
+      ..onPointerUp = onPointerUp
+      ..onPointerHover = onPointerHover
+      ..onPointerCancel = onPointerCancel
+      ..onPointerSignal = onPointerSignal
+      ..behavior = behavior;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    final List<String> listeners = <String>[
+      if (onPointerDown != null) 'down',
+      if (onPointerMove != null) 'move',
+      if (onPointerUp != null) 'up',
+      if (onPointerCancel != null) 'cancel',
+      if (onPointerSignal != null) 'signal',
+    ];
+    properties.add(IterableProperty<String>('listeners', listeners, ifEmpty: '<none>'));
+    properties.add(EnumProperty<HitTestBehavior>('behavior', behavior));
+  }
+}
+
+/// A widget that tracks the movement of mice.
+///
+/// [MouseRegion] is used
+/// when it is needed to compare the list of objects that a mouse pointer is
+/// hovering over between this frame and the last frame. This means entering
+/// events, exiting events, and mouse cursors.
+///
+/// To listen to general pointer events, use [Listener], or more preferably,
+/// [GestureDetector].
+///
+/// ## Layout behavior
+///
+/// _See [BoxConstraints] for an introduction to box layout models._
+///
+/// If it has a child, this widget defers to the child for sizing behavior. If
+/// it does not have a child, it grows to fit the parent instead.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+/// This example makes a [Container] react to being entered by a mouse
+/// pointer, showing a count of the number of entries and exits.
+///
+/// ```dart imports
+/// import 'package:flute/widgets.dart';
+/// ```
+///
+/// ```dart
+/// int _enterCounter = 0;
+/// int _exitCounter = 0;
+/// double x = 0.0;
+/// double y = 0.0;
+///
+/// void _incrementEnter(PointerEvent details) {
+///   setState(() {
+///     _enterCounter++;
+///   });
+/// }
+/// void _incrementExit(PointerEvent details) {
+///   setState(() {
+///     _exitCounter++;
+///   });
+/// }
+/// void _updateLocation(PointerEvent details) {
+///   setState(() {
+///     x = details.position.dx;
+///     y = details.position.dy;
+///   });
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return ConstrainedBox(
+///     constraints: new BoxConstraints.tight(Size(300.0, 200.0)),
+///     child: MouseRegion(
+///       onEnter: _incrementEnter,
+///       onHover: _updateLocation,
+///       onExit: _incrementExit,
+///       child: Container(
+///         color: Colors.lightBlueAccent,
+///         child: Column(
+///           mainAxisAlignment: MainAxisAlignment.center,
+///           children: <Widget>[
+///             Text('You have entered or exited this box this many times:'),
+///             Text(
+///               '$_enterCounter Entries\n$_exitCounter Exits',
+///               style: Theme.of(context).textTheme.headline4,
+///             ),
+///             Text(
+///               'The cursor is here: (${x.toStringAsFixed(2)}, ${y.toStringAsFixed(2)})',
+///             ),
+///           ],
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Listener], a similar widget that tracks pointer events when the pointer
+///    has buttons pressed.
+class MouseRegion extends StatefulWidget {
+  /// Creates a widget that forwards mouse events to callbacks.
+  ///
+  /// By default, all callbacks are empty, [cursor] is [MouseCursor.defer], and
+  /// [opaque] is true. The [cursor] must not be null.
+  const MouseRegion({
+    Key? key,
+    this.onEnter,
+    this.onExit,
+    this.onHover,
+    this.cursor = MouseCursor.defer,
+    this.opaque = true,
+    this.child,
+  }) : assert(cursor != null),
+       assert(opaque != null),
+       super(key: key);
+
+  /// Triggered when a mouse pointer has entered this widget.
+  ///
+  /// This callback is triggered when the pointer, with or without buttons
+  /// pressed, has started to be contained by the region of this widget. More
+  /// specifically, the callback is triggered by the following cases:
+  ///
+  ///  * This widget has appeared under a pointer.
+  ///  * This widget has moved to under a pointer.
+  ///  * A new pointer has been added to somewhere within this widget.
+  ///  * An existing pointer has moved into this widget.
+  ///
+  /// This callback is not always matched by an [onExit]. If the [MouseRegion]
+  /// is unmounted while being hovered by a pointer, the [onExit] of the widget
+  /// callback will never called. For more details, see [onExit].
+  ///
+  /// {@template flutter.widgets.MouseRegion.onEnter.triggerTime}
+  /// The time that this callback is triggered is always between frames: either
+  /// during the post-frame callbacks, or during the callback of a pointer
+  /// event.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [onExit], which is triggered when a mouse pointer exits the region.
+  ///  * [MouseTrackerAnnotation.onEnter], which is how this callback is
+  ///    internally implemented.
+  final PointerEnterEventListener? onEnter;
+
+  /// Triggered when a pointer moves into a position within this widget without
+  /// buttons pressed.
+  ///
+  /// Usually this is only fired for pointers which report their location when
+  /// not down (e.g. mouse pointers). Certain devices also fire this event on
+  /// single taps in accessibility mode.
+  ///
+  /// This callback is not triggered by the movement of the widget.
+  ///
+  /// The time that this callback is triggered is during the callback of a
+  /// pointer event, which is always between frames.
+  ///
+  /// See also:
+  ///
+  ///  * [Listener.onPointerHover], which does the same job. Prefer using
+  ///    [Listener.onPointerHover], since hover events are similar to other regular
+  ///    events.
+  final PointerHoverEventListener? onHover;
+
+  /// Triggered when a mouse pointer has exited this widget when the widget is
+  /// still mounted.
+  ///
+  /// This callback is triggered when the pointer, with or without buttons
+  /// pressed, has stopped being contained by the region of this widget, except
+  /// when the exit is caused by the disappearance of this widget. More
+  /// specifically, this callback is triggered by the following cases:
+  ///
+  ///  * A pointer that is hovering this widget has moved away.
+  ///  * A pointer that is hovering this widget has been removed.
+  ///  * This widget, which is being hovered by a pointer, has moved away.
+  ///
+  /// And is __not__ triggered by the following case:
+  ///
+  ///  * This widget, which is being hovered by a pointer, has disappeared.
+  ///
+  /// This means that a [MouseRegion.onExit] might not be matched by a
+  /// [MouseRegion.onEnter].
+  ///
+  /// This restriction aims to prevent a common misuse: if [State.setState] is
+  /// called during [MouseRegion.onExit] without checking whether the widget is
+  /// still mounted, an exception will occur. This is because the callback is
+  /// triggered during the post-frame phase, at which point the widget has been
+  /// unmounted. Since [State.setState] is exclusive to widgets, the restriction
+  /// is specific to [MouseRegion], and does not apply to its lower-level
+  /// counterparts, [RenderMouseRegion] and [MouseTrackerAnnotation].
+  ///
+  /// There are a few ways to mitigate this restriction:
+  ///
+  ///  * If the hover state is completely contained within a widget that
+  ///    unconditionally creates this [MouseRegion], then this will not be a
+  ///    concern, since after the [MouseRegion] is unmounted the state is no
+  ///    longer used.
+  ///  * Otherwise, the outer widget very likely has access to the variable that
+  ///    controls whether this [MouseRegion] is present. If so, call [onExit] at
+  ///    the event that turns the condition from true to false.
+  ///  * In cases where the solutions above won't work, you can always
+  ///    override [State.dispose] and call [onExit], or create your own widget
+  ///    using [RenderMouseRegion].
+  ///
+  /// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+  /// The following example shows a blue rectangular that turns yellow when
+  /// hovered. Since the hover state is completely contained within a widget
+  /// that unconditionally creates the `MouseRegion`, you can ignore the
+  /// aforementioned restriction.
+  ///
+  /// ```dart
+  ///   bool hovered = false;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Container(
+  ///       height: 100,
+  ///       width: 100,
+  ///       decoration: BoxDecoration(color: hovered ? Colors.yellow : Colors.blue),
+  ///       child: MouseRegion(
+  ///         onEnter: (_) {
+  ///           setState(() { hovered = true; });
+  ///         },
+  ///         onExit: (_) {
+  ///           setState(() { hovered = false; });
+  ///         },
+  ///       ),
+  ///     );
+  ///   }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+  /// The following example shows a widget that hides its content one second
+  /// after being hovered, and also exposes the enter and exit callbacks.
+  /// Because the widget conditionally creates the `MouseRegion`, and leaks the
+  /// hover state, it needs to take the restriction into consideration. In this
+  /// case, since it has access to the event that triggers the disappearance of
+  /// the `MouseRegion`, it simply trigger the exit callback during that event
+  /// as well.
+  ///
+  /// ```dart preamble
+  /// // A region that hides its content one second after being hovered.
+  /// class MyTimedButton extends StatefulWidget {
+  ///   MyTimedButton({ Key key, this.onEnterButton, this.onExitButton })
+  ///     : super(key: key);
+  ///
+  ///   final VoidCallback onEnterButton;
+  ///   final VoidCallback onExitButton;
+  ///
+  ///   @override
+  ///   _MyTimedButton createState() => _MyTimedButton();
+  /// }
+  ///
+  /// class _MyTimedButton extends State<MyTimedButton> {
+  ///   bool regionIsHidden = false;
+  ///   bool hovered = false;
+  ///
+  ///   void startCountdown() async {
+  ///     await Future.delayed(const Duration(seconds: 1));
+  ///     hideButton();
+  ///   }
+  ///
+  ///   void hideButton() {
+  ///     setState(() { regionIsHidden = true; });
+  ///     // This statement is necessary.
+  ///     if (hovered)
+  ///       widget.onExitButton();
+  ///   }
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Container(
+  ///       width: 100,
+  ///       height: 100,
+  ///       child: MouseRegion(
+  ///         child: regionIsHidden ? null : MouseRegion(
+  ///           onEnter: (_) {
+  ///             widget.onEnterButton();
+  ///             setState(() { hovered = true; });
+  ///             startCountdown();
+  ///           },
+  ///           onExit: (_) {
+  ///             setState(() { hovered = false; });
+  ///             widget.onExitButton();
+  ///           },
+  ///           child: Container(color: Colors.red),
+  ///         ),
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  ///
+  /// ```dart
+  ///   Key key = UniqueKey();
+  ///   bool hovering = false;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Column(
+  ///       children: <Widget>[
+  ///         ElevatedButton(
+  ///           onPressed: () {
+  ///             setState(() { key = UniqueKey(); });
+  ///           },
+  ///           child: Text('Refresh'),
+  ///         ),
+  ///         hovering ? Text('Hovering') : Text('Not hovering'),
+  ///         MyTimedButton(
+  ///           key: key,
+  ///           onEnterButton: () {
+  ///             setState(() { hovering = true; });
+  ///           },
+  ///           onExitButton: () {
+  ///             setState(() { hovering = false; });
+  ///           },
+  ///         ),
+  ///       ],
+  ///     );
+  ///   }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@macro flutter.widgets.MouseRegion.onEnter.triggerTime}
+  ///
+  /// See also:
+  ///
+  ///  * [onEnter], which is triggered when a mouse pointer enters the region.
+  ///  * [RenderMouseRegion] and [MouseTrackerAnnotation.onExit], which are how
+  ///    this callback is internally implemented, but without the restriction.
+  final PointerExitEventListener? onExit;
+
+  /// The mouse cursor for mouse pointers that are hovering over the region.
+  ///
+  /// When a mouse enters the region, its cursor will be changed to the [cursor].
+  /// When the mouse leaves the region, the cursor will be decided by the region
+  /// found at the new location.
+  ///
+  /// The [cursor] defaults to [MouseCursor.defer], deferring the choice of
+  /// cursor to the next region behind it in hit-test order.
+  final MouseCursor cursor;
+
+  /// Whether this widget should prevent other [MouseRegion]s visually behind it
+  /// from detecting the pointer.
+  ///
+  /// This changes the list of regions that a pointer hovers, thus affecting how
+  /// their [onHover], [onEnter], [onExit], and [cursor] behave.
+  ///
+  /// If [opaque] is true, this widget will absorb the mouse pointer and
+  /// prevent this widget's siblings (or any other widgets that are not
+  /// ancestors or descendants of this widget) from detecting the mouse
+  /// pointer even when the pointer is within their areas.
+  ///
+  /// If [opaque] is false, this object will not affect how [MouseRegion]s
+  /// behind it behave, which will detect the mouse pointer as long as the
+  /// pointer is within their areas.
+  ///
+  /// This defaults to true.
+  final bool opaque;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  @override
+  _MouseRegionState createState() => _MouseRegionState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    final List<String> listeners = <String>[];
+    if (onEnter != null)
+      listeners.add('enter');
+    if (onExit != null)
+      listeners.add('exit');
+    if (onHover != null)
+      listeners.add('hover');
+    properties.add(IterableProperty<String>('listeners', listeners, ifEmpty: '<none>'));
+    properties.add(DiagnosticsProperty<MouseCursor>('cursor', cursor, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('opaque', opaque, defaultValue: true));
+  }
+}
+
+class _MouseRegionState extends State<MouseRegion> {
+  void handleExit(PointerExitEvent event) {
+    if (widget.onExit != null && mounted)
+      widget.onExit!(event);
+  }
+
+  PointerExitEventListener? getHandleExit() {
+    return widget.onExit == null ? null : handleExit;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _RawMouseRegion(this);
+  }
+}
+
+class _RawMouseRegion extends SingleChildRenderObjectWidget {
+  _RawMouseRegion(this.owner) : super(child: owner.widget.child);
+
+  final _MouseRegionState owner;
+
+  @override
+  RenderMouseRegion createRenderObject(BuildContext context) {
+    final MouseRegion widget = owner.widget;
+    return RenderMouseRegion(
+      onEnter: widget.onEnter,
+      onHover: widget.onHover,
+      onExit: owner.getHandleExit(),
+      cursor: widget.cursor,
+      opaque: widget.opaque,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderMouseRegion renderObject) {
+    final MouseRegion widget = owner.widget;
+    renderObject
+      ..onEnter = widget.onEnter
+      ..onHover = widget.onHover
+      ..onExit = owner.getHandleExit()
+      ..cursor = widget.cursor
+      ..opaque = widget.opaque;
+  }
+}
+
+/// A widget that creates a separate display list for its child.
+///
+/// This widget creates a separate display list for its child, which
+/// can improve performance if the subtree repaints at different times than
+/// the surrounding parts of the tree.
+///
+/// This is useful since [RenderObject.paint] may be triggered even if its
+/// associated [Widget] instances did not change or rebuild. A [RenderObject]
+/// will repaint whenever any [RenderObject] that shares the same [Layer] is
+/// marked as being dirty and needing paint (see [RenderObject.markNeedsPaint]),
+/// such as when an ancestor scrolls or when an ancestor or descendant animates.
+///
+/// Containing [RenderObject.paint] to parts of the render subtree that are
+/// actually visually changing using [RepaintBoundary] explicitly or implicitly
+/// is therefore critical to minimizing redundant work and improving the app's
+/// performance.
+///
+/// When a [RenderObject] is flagged as needing to paint via
+/// [RenderObject.markNeedsPaint], the nearest ancestor [RenderObject] with
+/// [RenderObject.isRepaintBoundary], up to possibly the root of the application,
+/// is requested to repaint. That nearest ancestor's [RenderObject.paint] method
+/// will cause _all_ of its descendant [RenderObject]s to repaint in the same
+/// layer.
+///
+/// [RepaintBoundary] is therefore used, both while propagating the
+/// `markNeedsPaint` flag up the render tree and while traversing down the
+/// render tree via [PaintingContext.paintChild], to strategically contain
+/// repaints to the render subtree that visually changed for performance. This
+/// is done because the [RepaintBoundary] widget creates a [RenderObject] that
+/// always has a [Layer], decoupling ancestor render objects from the descendant
+/// render objects.
+///
+/// [RepaintBoundary] has the further side-effect of possibly hinting to the
+/// engine that it should further optimize animation performance if the render
+/// subtree behind the [RepaintBoundary] is sufficiently complex and is static
+/// while the surrounding tree changes frequently. In those cases, the engine
+/// may choose to pay a one time cost of rasterizing and caching the pixel
+/// values of the subtree for faster future GPU re-rendering speed.
+///
+/// Several framework widgets insert [RepaintBoundary] widgets to mark natural
+/// separation points in applications. For instance, contents in Material Design
+/// drawers typically don't change while the drawer opens and closes, so
+/// repaints are automatically contained to regions inside or outside the drawer
+/// when using the [Drawer] widget during transitions.
+///
+/// See also:
+///
+///  * [debugRepaintRainbowEnabled], a debugging flag to help visually monitor
+///    render tree repaints in a running app.
+///  * [debugProfilePaintsEnabled], a debugging flag to show render tree
+///    repaints in the observatory's timeline view.
+class RepaintBoundary extends SingleChildRenderObjectWidget {
+  /// Creates a widget that isolates repaints.
+  const RepaintBoundary({ Key? key, Widget? child }) : super(key: key, child: child);
+
+  /// Wraps the given child in a [RepaintBoundary].
+  ///
+  /// The key for the [RepaintBoundary] is derived either from the child's key
+  /// (if the child has a non-null key) or from the given `childIndex`.
+  factory RepaintBoundary.wrap(Widget child, int childIndex) {
+    assert(child != null);
+    final Key key = child.key != null ? ValueKey<Key>(child.key!) : ValueKey<int>(childIndex);
+    return RepaintBoundary(key: key, child: child);
+  }
+
+  /// Wraps each of the given children in [RepaintBoundary]s.
+  ///
+  /// The key for each [RepaintBoundary] is derived either from the wrapped
+  /// child's key (if the wrapped child has a non-null key) or from the wrapped
+  /// child's index in the list.
+  static List<RepaintBoundary> wrapAll(List<Widget> widgets) => <RepaintBoundary>[
+    for (int i = 0; i < widgets.length; ++i) RepaintBoundary.wrap(widgets[i], i),
+  ];
+
+  @override
+  RenderRepaintBoundary createRenderObject(BuildContext context) => RenderRepaintBoundary();
+}
+
+/// A widget that is invisible during hit testing.
+///
+/// When [ignoring] is true, this widget (and its subtree) is invisible
+/// to hit testing. It still consumes space during layout and paints its child
+/// as usual. It just cannot be the target of located events, because it returns
+/// false from [RenderBox.hitTest].
+///
+/// When [ignoringSemantics] is true, the subtree will be invisible to
+/// the semantics layer (and thus e.g. accessibility tools). If
+/// [ignoringSemantics] is null, it uses the value of [ignoring].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=qV9pqHWxYgI}
+///
+/// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+/// The following sample has an [IgnorePointer] widget wrapping the `Column`
+/// which contains a button.
+/// When [ignoring] is set to `true` anything inside the `Column` can
+/// not be tapped. When [ignoring] is set to `false` anything
+/// inside the `Column` can be tapped.
+///
+/// ```dart
+/// bool ignoring = false;
+/// void setIgnoring(bool newValue) {
+///   setState(() {
+///     ignoring = newValue;
+///   });
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: AppBar(
+///       centerTitle: true,
+///       title: ElevatedButton(
+///         onPressed: () {
+///           setIgnoring(!ignoring);
+///         },
+///         child: Text(
+///           ignoring ? 'Set ignoring to false' : 'Set ignoring to true',
+///         ),
+///       ),
+///     ),
+///     body: Center(
+///       child: IgnorePointer(
+///         ignoring: ignoring,
+///         child: Column(
+///           mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+///           children: <Widget>[
+///             Text(
+///               'Ignoring: $ignoring',
+///             ),
+///             ElevatedButton(
+///               onPressed: () {},
+///               child: Text(
+///                 'Click me!',
+///               ),
+///             ),
+///           ],
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AbsorbPointer], which also prevents its children from receiving pointer
+///    events but is itself visible to hit testing.
+class IgnorePointer extends SingleChildRenderObjectWidget {
+  /// Creates a widget that is invisible to hit testing.
+  ///
+  /// The [ignoring] argument must not be null. If [ignoringSemantics] is null,
+  /// this render object will be ignored for semantics if [ignoring] is true.
+  const IgnorePointer({
+    Key? key,
+    this.ignoring = true,
+    this.ignoringSemantics,
+    Widget? child,
+  }) : assert(ignoring != null),
+       super(key: key, child: child);
+
+  /// Whether this widget is ignored during hit testing.
+  ///
+  /// Regardless of whether this widget is ignored during hit testing, it will
+  /// still consume space during layout and be visible during painting.
+  final bool ignoring;
+
+  /// Whether the semantics of this widget is ignored when compiling the semantics tree.
+  ///
+  /// If null, defaults to value of [ignoring].
+  ///
+  /// See [SemanticsNode] for additional information about the semantics tree.
+  final bool? ignoringSemantics;
+
+  @override
+  RenderIgnorePointer createRenderObject(BuildContext context) {
+    return RenderIgnorePointer(
+      ignoring: ignoring,
+      ignoringSemantics: ignoringSemantics,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderIgnorePointer renderObject) {
+    renderObject
+      ..ignoring = ignoring
+      ..ignoringSemantics = ignoringSemantics;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('ignoring', ignoring));
+    properties.add(DiagnosticsProperty<bool>('ignoringSemantics', ignoringSemantics, defaultValue: null));
+  }
+}
+
+/// A widget that absorbs pointers during hit testing.
+///
+/// When [absorbing] is true, this widget prevents its subtree from receiving
+/// pointer events by terminating hit testing at itself. It still consumes space
+/// during layout and paints its child as usual. It just prevents its children
+/// from being the target of located events, because it returns true from
+/// [RenderBox.hitTest].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=65HoWqBboI8}
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_center_no_null_safety}
+/// The following sample has an [AbsorbPointer] widget wrapping the button on
+/// top of the stack, which absorbs pointer events, preventing its child button
+/// __and__ the button below it in the stack from receiving the pointer events.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Stack(
+///     alignment: AlignmentDirectional.center,
+///     children: [
+///       SizedBox(
+///         width: 200.0,
+///         height: 100.0,
+///         child: ElevatedButton(
+///           onPressed: () {},
+///           child: null,
+///         ),
+///       ),
+///       SizedBox(
+///         width: 100.0,
+///         height: 200.0,
+///         child: AbsorbPointer(
+///           absorbing: true,
+///           child: ElevatedButton(
+///             style: ElevatedButton.styleFrom(
+///               primary: Colors.blue.shade200,
+///             ),
+///             onPressed: () {},
+///             child: null,
+///           ),
+///         ),
+///       ),
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [IgnorePointer], which also prevents its children from receiving pointer
+///    events but is itself invisible to hit testing.
+class AbsorbPointer extends SingleChildRenderObjectWidget {
+  /// Creates a widget that absorbs pointers during hit testing.
+  ///
+  /// The [absorbing] argument must not be null.
+  const AbsorbPointer({
+    Key? key,
+    this.absorbing = true,
+    Widget? child,
+    this.ignoringSemantics,
+  }) : assert(absorbing != null),
+       super(key: key, child: child);
+
+  /// Whether this widget absorbs pointers during hit testing.
+  ///
+  /// Regardless of whether this render object absorbs pointers during hit
+  /// testing, it will still consume space during layout and be visible during
+  /// painting.
+  final bool absorbing;
+
+  /// Whether the semantics of this render object is ignored when compiling the
+  /// semantics tree.
+  ///
+  /// If null, defaults to the value of [absorbing].
+  ///
+  /// See [SemanticsNode] for additional information about the semantics tree.
+  final bool? ignoringSemantics;
+
+  @override
+  RenderAbsorbPointer createRenderObject(BuildContext context) {
+    return RenderAbsorbPointer(
+      absorbing: absorbing,
+      ignoringSemantics: ignoringSemantics,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderAbsorbPointer renderObject) {
+    renderObject
+      ..absorbing = absorbing
+      ..ignoringSemantics = ignoringSemantics;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('absorbing', absorbing));
+    properties.add(DiagnosticsProperty<bool>('ignoringSemantics', ignoringSemantics, defaultValue: null));
+  }
+}
+
+/// Holds opaque meta data in the render tree.
+///
+/// Useful for decorating the render tree with information that will be consumed
+/// later. For example, you could store information in the render tree that will
+/// be used when the user interacts with the render tree but has no visual
+/// impact prior to the interaction.
+class MetaData extends SingleChildRenderObjectWidget {
+  /// Creates a widget that hold opaque meta data.
+  ///
+  /// The [behavior] argument defaults to [HitTestBehavior.deferToChild].
+  const MetaData({
+    Key? key,
+    this.metaData,
+    this.behavior = HitTestBehavior.deferToChild,
+    Widget? child,
+  }) : super(key: key, child: child);
+
+  /// Opaque meta data ignored by the render tree.
+  final dynamic metaData;
+
+  /// How to behave during hit testing.
+  final HitTestBehavior behavior;
+
+  @override
+  RenderMetaData createRenderObject(BuildContext context) {
+    return RenderMetaData(
+      metaData: metaData,
+      behavior: behavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderMetaData renderObject) {
+    renderObject
+      ..metaData = metaData
+      ..behavior = behavior;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<HitTestBehavior>('behavior', behavior));
+    properties.add(DiagnosticsProperty<dynamic>('metaData', metaData));
+  }
+}
+
+
+// UTILITY NODES
+
+/// A widget that annotates the widget tree with a description of the meaning of
+/// the widgets.
+///
+/// Used by accessibility tools, search engines, and other semantic analysis
+/// software to determine the meaning of the application.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=NvtMt_DtFrQ}
+///
+/// See also:
+///
+///  * [MergeSemantics], which marks a subtree as being a single node for
+///    accessibility purposes.
+///  * [ExcludeSemantics], which excludes a subtree from the semantics tree
+///    (which might be useful if it is, e.g., totally decorative and not
+///    important to the user).
+///  * [RenderObject.describeSemanticsConfiguration], the rendering library API
+///    through which the [Semantics] widget is actually implemented.
+///  * [SemanticsNode], the object used by the rendering library to represent
+///    semantics in the semantics tree.
+///  * [SemanticsDebugger], an overlay to help visualize the semantics tree. Can
+///    be enabled using [WidgetsApp.showSemanticsDebugger] or
+///    [MaterialApp.showSemanticsDebugger].
+@immutable
+class Semantics extends SingleChildRenderObjectWidget {
+  /// Creates a semantic annotation.
+  ///
+  /// The [container] argument must not be null. To create a `const` instance
+  /// of [Semantics], use the [Semantics.fromProperties] constructor.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsSortKey] for a class that determines accessibility traversal
+  ///    order.
+  Semantics({
+    Key? key,
+    Widget? child,
+    bool container = false,
+    bool explicitChildNodes = false,
+    bool excludeSemantics = false,
+    bool? enabled,
+    bool? checked,
+    bool? selected,
+    bool? toggled,
+    bool? button,
+    bool? slider,
+    bool? link,
+    bool? header,
+    bool? textField,
+    bool? readOnly,
+    bool? focusable,
+    bool? focused,
+    bool? inMutuallyExclusiveGroup,
+    bool? obscured,
+    bool? multiline,
+    bool? scopesRoute,
+    bool? namesRoute,
+    bool? hidden,
+    bool? image,
+    bool? liveRegion,
+    int? maxValueLength,
+    int? currentValueLength,
+    String? label,
+    String? value,
+    String? increasedValue,
+    String? decreasedValue,
+    String? hint,
+    String? onTapHint,
+    String? onLongPressHint,
+    TextDirection? textDirection,
+    SemanticsSortKey? sortKey,
+    SemanticsTag? tagForChildren,
+    VoidCallback? onTap,
+    VoidCallback? onLongPress,
+    VoidCallback? onScrollLeft,
+    VoidCallback? onScrollRight,
+    VoidCallback? onScrollUp,
+    VoidCallback? onScrollDown,
+    VoidCallback? onIncrease,
+    VoidCallback? onDecrease,
+    VoidCallback? onCopy,
+    VoidCallback? onCut,
+    VoidCallback? onPaste,
+    VoidCallback? onDismiss,
+    MoveCursorHandler? onMoveCursorForwardByCharacter,
+    MoveCursorHandler? onMoveCursorBackwardByCharacter,
+    SetSelectionHandler? onSetSelection,
+    VoidCallback? onDidGainAccessibilityFocus,
+    VoidCallback? onDidLoseAccessibilityFocus,
+    Map<CustomSemanticsAction, VoidCallback>? customSemanticsActions,
+  }) : this.fromProperties(
+    key: key,
+    child: child,
+    container: container,
+    explicitChildNodes: explicitChildNodes,
+    excludeSemantics: excludeSemantics,
+    properties: SemanticsProperties(
+      enabled: enabled,
+      checked: checked,
+      toggled: toggled,
+      selected: selected,
+      button: button,
+      slider: slider,
+      link: link,
+      header: header,
+      textField: textField,
+      readOnly: readOnly,
+      focusable: focusable,
+      focused: focused,
+      inMutuallyExclusiveGroup: inMutuallyExclusiveGroup,
+      obscured: obscured,
+      multiline: multiline,
+      scopesRoute: scopesRoute,
+      namesRoute: namesRoute,
+      hidden: hidden,
+      image: image,
+      liveRegion: liveRegion,
+      maxValueLength: maxValueLength,
+      currentValueLength: currentValueLength,
+      label: label,
+      value: value,
+      increasedValue: increasedValue,
+      decreasedValue: decreasedValue,
+      hint: hint,
+      textDirection: textDirection,
+      sortKey: sortKey,
+      tagForChildren: tagForChildren,
+      onTap: onTap,
+      onLongPress: onLongPress,
+      onScrollLeft: onScrollLeft,
+      onScrollRight: onScrollRight,
+      onScrollUp: onScrollUp,
+      onScrollDown: onScrollDown,
+      onIncrease: onIncrease,
+      onDecrease: onDecrease,
+      onCopy: onCopy,
+      onCut: onCut,
+      onPaste: onPaste,
+      onMoveCursorForwardByCharacter: onMoveCursorForwardByCharacter,
+      onMoveCursorBackwardByCharacter: onMoveCursorBackwardByCharacter,
+      onDidGainAccessibilityFocus: onDidGainAccessibilityFocus,
+      onDidLoseAccessibilityFocus: onDidLoseAccessibilityFocus,
+      onDismiss: onDismiss,
+      onSetSelection: onSetSelection,
+      customSemanticsActions: customSemanticsActions,
+      hintOverrides: onTapHint != null || onLongPressHint != null ?
+        SemanticsHintOverrides(
+          onTapHint: onTapHint,
+          onLongPressHint: onLongPressHint,
+        ) : null,
+    ),
+  );
+
+  /// Creates a semantic annotation using [SemanticsProperties].
+  ///
+  /// The [container] and [properties] arguments must not be null.
+  const Semantics.fromProperties({
+    Key? key,
+    Widget? child,
+    this.container = false,
+    this.explicitChildNodes = false,
+    this.excludeSemantics = false,
+    required this.properties,
+  }) : assert(container != null),
+       assert(properties != null),
+       super(key: key, child: child);
+
+  /// Contains properties used by assistive technologies to make the application
+  /// more accessible.
+  final SemanticsProperties properties;
+
+  /// If [container] is true, this widget will introduce a new
+  /// node in the semantics tree. Otherwise, the semantics will be
+  /// merged with the semantics of any ancestors (if the ancestor allows that).
+  ///
+  /// Whether descendants of this widget can add their semantic information to the
+  /// [SemanticsNode] introduced by this configuration is controlled by
+  /// [explicitChildNodes].
+  final bool container;
+
+  /// Whether descendants of this widget are allowed to add semantic information
+  /// to the [SemanticsNode] annotated by this widget.
+  ///
+  /// When set to false descendants are allowed to annotate [SemanticsNode]s of
+  /// their parent with the semantic information they want to contribute to the
+  /// semantic tree.
+  /// When set to true the only way for descendants to contribute semantic
+  /// information to the semantic tree is to introduce new explicit
+  /// [SemanticsNode]s to the tree.
+  ///
+  /// If the semantics properties of this node include
+  /// [SemanticsProperties.scopesRoute] set to true, then [explicitChildNodes]
+  /// must be true also.
+  ///
+  /// This setting is often used in combination with [SemanticsConfiguration.isSemanticBoundary]
+  /// to create semantic boundaries that are either writable or not for children.
+  final bool explicitChildNodes;
+
+  /// Whether to replace all child semantics with this node.
+  ///
+  /// Defaults to false.
+  ///
+  /// When this flag is set to true, all child semantics nodes are ignored.
+  /// This can be used as a convenience for cases where a child is wrapped in
+  /// an [ExcludeSemantics] widget and then another [Semantics] widget.
+  final bool excludeSemantics;
+
+  @override
+  RenderSemanticsAnnotations createRenderObject(BuildContext context) {
+    return RenderSemanticsAnnotations(
+      container: container,
+      explicitChildNodes: explicitChildNodes,
+      excludeSemantics: excludeSemantics,
+      enabled: properties.enabled,
+      checked: properties.checked,
+      toggled: properties.toggled,
+      selected: properties.selected,
+      button: properties.button,
+      slider: properties.slider,
+      link: properties.link,
+      header: properties.header,
+      textField: properties.textField,
+      readOnly: properties.readOnly,
+      focusable: properties.focusable,
+      focused: properties.focused,
+      liveRegion: properties.liveRegion,
+      maxValueLength: properties.maxValueLength,
+      currentValueLength: properties.currentValueLength,
+      inMutuallyExclusiveGroup: properties.inMutuallyExclusiveGroup,
+      obscured: properties.obscured,
+      multiline: properties.multiline,
+      scopesRoute: properties.scopesRoute,
+      namesRoute: properties.namesRoute,
+      hidden: properties.hidden,
+      image: properties.image,
+      label: properties.label,
+      value: properties.value,
+      increasedValue: properties.increasedValue,
+      decreasedValue: properties.decreasedValue,
+      hint: properties.hint,
+      hintOverrides: properties.hintOverrides,
+      textDirection: _getTextDirection(context),
+      sortKey: properties.sortKey,
+      tagForChildren: properties.tagForChildren,
+      onTap: properties.onTap,
+      onLongPress: properties.onLongPress,
+      onScrollLeft: properties.onScrollLeft,
+      onScrollRight: properties.onScrollRight,
+      onScrollUp: properties.onScrollUp,
+      onScrollDown: properties.onScrollDown,
+      onIncrease: properties.onIncrease,
+      onDecrease: properties.onDecrease,
+      onCopy: properties.onCopy,
+      onDismiss: properties.onDismiss,
+      onCut: properties.onCut,
+      onPaste: properties.onPaste,
+      onMoveCursorForwardByCharacter: properties.onMoveCursorForwardByCharacter,
+      onMoveCursorBackwardByCharacter: properties.onMoveCursorBackwardByCharacter,
+      onMoveCursorForwardByWord: properties.onMoveCursorForwardByWord,
+      onMoveCursorBackwardByWord: properties.onMoveCursorBackwardByWord,
+      onSetSelection: properties.onSetSelection,
+      onDidGainAccessibilityFocus: properties.onDidGainAccessibilityFocus,
+      onDidLoseAccessibilityFocus: properties.onDidLoseAccessibilityFocus,
+      customSemanticsActions: properties.customSemanticsActions,
+    );
+  }
+
+  TextDirection? _getTextDirection(BuildContext context) {
+    if (properties.textDirection != null)
+      return properties.textDirection;
+
+    final bool containsText = properties.label != null || properties.value != null || properties.hint != null;
+
+    if (!containsText)
+      return null;
+
+    return Directionality.maybeOf(context);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderSemanticsAnnotations renderObject) {
+    renderObject
+      ..container = container
+      ..explicitChildNodes = explicitChildNodes
+      ..excludeSemantics = excludeSemantics
+      ..scopesRoute = properties.scopesRoute
+      ..enabled = properties.enabled
+      ..checked = properties.checked
+      ..toggled = properties.toggled
+      ..selected = properties.selected
+      ..button = properties.button
+      ..slider = properties.slider
+      ..link = properties.link
+      ..header = properties.header
+      ..textField = properties.textField
+      ..readOnly = properties.readOnly
+      ..focusable = properties.focusable
+      ..focused = properties.focused
+      ..inMutuallyExclusiveGroup = properties.inMutuallyExclusiveGroup
+      ..obscured = properties.obscured
+      ..multiline = properties.multiline
+      ..hidden = properties.hidden
+      ..image = properties.image
+      ..liveRegion = properties.liveRegion
+      ..maxValueLength = properties.maxValueLength
+      ..currentValueLength = properties.currentValueLength
+      ..label = properties.label
+      ..value = properties.value
+      ..increasedValue = properties.increasedValue
+      ..decreasedValue = properties.decreasedValue
+      ..hint = properties.hint
+      ..hintOverrides = properties.hintOverrides
+      ..namesRoute = properties.namesRoute
+      ..textDirection = _getTextDirection(context)
+      ..sortKey = properties.sortKey
+      ..tagForChildren = properties.tagForChildren
+      ..onTap = properties.onTap
+      ..onLongPress = properties.onLongPress
+      ..onScrollLeft = properties.onScrollLeft
+      ..onScrollRight = properties.onScrollRight
+      ..onScrollUp = properties.onScrollUp
+      ..onScrollDown = properties.onScrollDown
+      ..onIncrease = properties.onIncrease
+      ..onDismiss = properties.onDismiss
+      ..onDecrease = properties.onDecrease
+      ..onCopy = properties.onCopy
+      ..onCut = properties.onCut
+      ..onPaste = properties.onPaste
+      ..onMoveCursorForwardByCharacter = properties.onMoveCursorForwardByCharacter
+      ..onMoveCursorBackwardByCharacter = properties.onMoveCursorForwardByCharacter
+      ..onMoveCursorForwardByWord = properties.onMoveCursorForwardByWord
+      ..onMoveCursorBackwardByWord = properties.onMoveCursorBackwardByWord
+      ..onSetSelection = properties.onSetSelection
+      ..onDidGainAccessibilityFocus = properties.onDidGainAccessibilityFocus
+      ..onDidLoseAccessibilityFocus = properties.onDidLoseAccessibilityFocus
+      ..customSemanticsActions = properties.customSemanticsActions;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('container', container));
+    properties.add(DiagnosticsProperty<SemanticsProperties>('properties', this.properties));
+    this.properties.debugFillProperties(properties);
+  }
+}
+
+/// A widget that merges the semantics of its descendants.
+///
+/// Causes all the semantics of the subtree rooted at this node to be
+/// merged into one node in the semantics tree. For example, if you
+/// have a widget with a Text node next to a checkbox widget, this
+/// could be used to merge the label from the Text node with the
+/// "checked" semantic state of the checkbox into a single node that
+/// had both the label and the checked state. Otherwise, the label
+/// would be presented as a separate feature than the checkbox, and
+/// the user would not be able to be sure that they were related.
+///
+/// {@tool snippet}
+/// This snippet shows how to use [MergeSemantics] to merge the semantics of
+/// a [Checkbox] and [Text] widget.
+///
+/// ```dart
+/// MergeSemantics(
+///   child: Row(
+///     children: <Widget>[
+///       Checkbox(
+///         value: true,
+///         onChanged: (bool value) => null,
+///       ),
+///       const Text("Settings"),
+///     ],
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// Be aware that if two nodes in the subtree have conflicting
+/// semantics, the result may be nonsensical. For example, a subtree
+/// with a checked checkbox and an unchecked checkbox will be
+/// presented as checked. All the labels will be merged into a single
+/// string (with newlines separating each label from the other). If
+/// multiple nodes in the merged subtree can handle semantic gestures,
+/// the first one in tree order will be the one to receive the
+/// callbacks.
+class MergeSemantics extends SingleChildRenderObjectWidget {
+  /// Creates a widget that merges the semantics of its descendants.
+  const MergeSemantics({ Key? key, Widget? child }) : super(key: key, child: child);
+
+  @override
+  RenderMergeSemantics createRenderObject(BuildContext context) => RenderMergeSemantics();
+}
+
+/// A widget that drops the semantics of all widget that were painted before it
+/// in the same semantic container.
+///
+/// This is useful to hide widgets from accessibility tools that are painted
+/// behind a certain widget, e.g. an alert should usually disallow interaction
+/// with any widget located "behind" the alert (even when they are still
+/// partially visible). Similarly, an open [Drawer] blocks interactions with
+/// any widget outside the drawer.
+///
+/// See also:
+///
+///  * [ExcludeSemantics] which drops all semantics of its descendants.
+class BlockSemantics extends SingleChildRenderObjectWidget {
+  /// Creates a widget that excludes the semantics of all widgets painted before
+  /// it in the same semantic container.
+  const BlockSemantics({ Key? key, this.blocking = true, Widget? child }) : super(key: key, child: child);
+
+  /// Whether this widget is blocking semantics of all widget that were painted
+  /// before it in the same semantic container.
+  final bool blocking;
+
+  @override
+  RenderBlockSemantics createRenderObject(BuildContext context) => RenderBlockSemantics(blocking: blocking);
+
+  @override
+  void updateRenderObject(BuildContext context, RenderBlockSemantics renderObject) {
+    renderObject.blocking = blocking;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('blocking', blocking));
+  }
+}
+
+/// A widget that drops all the semantics of its descendants.
+///
+/// When [excluding] is true, this widget (and its subtree) is excluded from
+/// the semantics tree.
+///
+/// This can be used to hide descendant widgets that would otherwise be
+/// reported but that would only be confusing. For example, the
+/// material library's [Chip] widget hides the avatar since it is
+/// redundant with the chip label.
+///
+/// See also:
+///
+///  * [BlockSemantics] which drops semantics of widgets earlier in the tree.
+class ExcludeSemantics extends SingleChildRenderObjectWidget {
+  /// Creates a widget that drops all the semantics of its descendants.
+  const ExcludeSemantics({
+    Key? key,
+    this.excluding = true,
+    Widget? child,
+  }) : assert(excluding != null),
+       super(key: key, child: child);
+
+  /// Whether this widget is excluded in the semantics tree.
+  final bool excluding;
+
+  @override
+  RenderExcludeSemantics createRenderObject(BuildContext context) => RenderExcludeSemantics(excluding: excluding);
+
+  @override
+  void updateRenderObject(BuildContext context, RenderExcludeSemantics renderObject) {
+    renderObject.excluding = excluding;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('excluding', excluding));
+  }
+}
+
+/// A widget that annotates the child semantics with an index.
+///
+/// Semantic indexes are used by TalkBack/Voiceover to make announcements about
+/// the current scroll state. Certain widgets like the [ListView] will
+/// automatically provide a child index for building semantics. A user may wish
+/// to manually provide semantic indexes if not all child of the scrollable
+/// contribute semantics.
+///
+/// {@tool snippet}
+///
+/// The example below handles spacers in a scrollable that don't contribute
+/// semantics. The automatic indexes would give the spaces a semantic index,
+/// causing scroll announcements to erroneously state that there are four items
+/// visible.
+///
+/// ```dart
+/// ListView(
+///   addSemanticIndexes: false,
+///   semanticChildCount: 2,
+///   children: const <Widget>[
+///     IndexedSemantics(index: 0, child: Text('First')),
+///     Spacer(),
+///     IndexedSemantics(index: 1, child: Text('Second')),
+///     Spacer(),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [CustomScrollView], for an explanation of index semantics.
+class IndexedSemantics extends SingleChildRenderObjectWidget {
+  /// Creates a widget that annotated the first child semantics node with an index.
+  ///
+  /// [index] must not be null.
+  const IndexedSemantics({
+    Key? key,
+    required this.index,
+    Widget? child,
+  }) : assert(index != null),
+       super(key: key, child: child);
+
+  /// The index used to annotate the first child semantics node.
+  final int index;
+
+  @override
+  RenderIndexedSemantics createRenderObject(BuildContext context) => RenderIndexedSemantics(index: index);
+
+  @override
+  void updateRenderObject(BuildContext context, RenderIndexedSemantics renderObject) {
+    renderObject.index = index;
+  }
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<int>('index', index));
+  }
+}
+
+/// A widget that builds its child.
+///
+/// Useful for attaching a key to an existing widget.
+class KeyedSubtree extends StatelessWidget {
+  /// Creates a widget that builds its child.
+  const KeyedSubtree({
+    Key? key,
+    required this.child,
+  }) : assert(child != null),
+       super(key: key);
+
+  /// Creates a KeyedSubtree for child with a key that's based on the child's existing key or childIndex.
+  factory KeyedSubtree.wrap(Widget child, int childIndex) {
+    final Key key = child.key != null ? ValueKey<Key>(child.key!) : ValueKey<int>(childIndex);
+    return KeyedSubtree(key: key, child: child);
+  }
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// Wrap each item in a KeyedSubtree whose key is based on the item's existing key or
+  /// the sum of its list index and `baseIndex`.
+  static List<Widget> ensureUniqueKeysForList(List<Widget> items, { int baseIndex = 0 }) {
+    if (items == null || items.isEmpty)
+      return items;
+
+    final List<Widget> itemsWithUniqueKeys = <Widget>[];
+    int itemIndex = baseIndex;
+    for (final Widget item in items) {
+      itemsWithUniqueKeys.add(KeyedSubtree.wrap(item, itemIndex));
+      itemIndex += 1;
+    }
+
+    assert(!debugItemsHaveDuplicateKeys(itemsWithUniqueKeys));
+    return itemsWithUniqueKeys;
+  }
+
+  @override
+  Widget build(BuildContext context) => child;
+}
+
+/// A stateless utility widget whose [build] method uses its
+/// [builder] callback to create the widget's child.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=xXNOkIuSYuA}
+///
+/// This widget is a simple inline alternative to defining a [StatelessWidget]
+/// subclass. For example a widget defined and used like this:
+///
+/// ```dart
+/// class Foo extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) => Text('foo');
+/// }
+///
+/// Center(child: Foo())
+/// ```
+///
+/// Could equally well be defined and used like this, without
+/// defining a new widget class:
+///
+/// ```dart
+/// Center(
+///   child: Builder(
+///     builder: (BuildContext context) => Text('foo');
+///   ),
+/// )
+/// ```
+///
+/// The difference between either of the previous examples and simply
+/// creating a child directly, without an intervening widget, is the
+/// extra [BuildContext] element that the additional widget adds. This
+/// is particularly noticeable when the tree contains an inherited
+/// widget that is referred to by a method like [Scaffold.of],
+/// which visits the child widget's BuildContext ancestors.
+///
+/// In the following example the button's `onPressed` callback is unable
+/// to find the enclosing [ScaffoldState] with [Scaffold.of]:
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: Center(
+///       child: TextButton(
+///         onPressed: () {
+///           // Fails because Scaffold.of() doesn't find anything
+///           // above this widget's context.
+///           print(Scaffold.of(context).hasAppBar);
+///         },
+///         child: Text('hasAppBar'),
+///       )
+///     ),
+///   );
+/// }
+/// ```
+///
+/// A [Builder] widget introduces an additional [BuildContext] element
+/// and so the [Scaffold.of] method succeeds.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: Builder(
+///       builder: (BuildContext context) {
+///         return Center(
+///           child: TextButton(
+///             onPressed: () {
+///               print(Scaffold.of(context).hasAppBar);
+///             },
+///             child: Text('hasAppBar'),
+///           ),
+///         );
+///       },
+///     ),
+///   );
+/// }
+/// ```
+///
+/// See also:
+///
+///  * [StatefulBuilder], A stateful utility widget whose [build] method uses its
+///    [builder] callback to create the widget's child.
+class Builder extends StatelessWidget {
+  /// Creates a widget that delegates its build to a callback.
+  ///
+  /// The [builder] argument must not be null.
+  const Builder({
+    Key? key,
+    required this.builder,
+  }) : assert(builder != null),
+       super(key: key);
+
+  /// Called to obtain the child widget.
+  ///
+  /// This function is called whenever this widget is included in its parent's
+  /// build and the old widget (if any) that it synchronizes with has a distinct
+  /// object identity. Typically the parent's build method will construct
+  /// a new tree of widgets and so a new Builder child will not be [identical]
+  /// to the corresponding old one.
+  final WidgetBuilder builder;
+
+  @override
+  Widget build(BuildContext context) => builder(context);
+}
+
+/// Signature for the builder callback used by [StatefulBuilder].
+///
+/// Call `setState` to schedule the [StatefulBuilder] to rebuild.
+typedef StatefulWidgetBuilder = Widget Function(BuildContext context, StateSetter setState);
+
+/// A platonic widget that both has state and calls a closure to obtain its child widget.
+///
+/// The [StateSetter] function passed to the [builder] is used to invoke a
+/// rebuild instead of a typical [State]'s [State.setState].
+///
+/// Since the [builder] is re-invoked when the [StateSetter] is called, any
+/// variables that represents state should be kept outside the [builder] function.
+///
+/// {@tool snippet}
+///
+/// This example shows using an inline StatefulBuilder that rebuilds and that
+/// also has state.
+///
+/// ```dart
+/// await showDialog<void>(
+///   context: context,
+///   builder: (BuildContext context) {
+///     int selectedRadio = 0;
+///     return AlertDialog(
+///       content: StatefulBuilder(
+///         builder: (BuildContext context, StateSetter setState) {
+///           return Column(
+///             mainAxisSize: MainAxisSize.min,
+///             children: List<Widget>.generate(4, (int index) {
+///               return Radio<int>(
+///                 value: index,
+///                 groupValue: selectedRadio,
+///                 onChanged: (int value) {
+///                   setState(() => selectedRadio = value);
+///                 },
+///               );
+///             }),
+///           );
+///         },
+///       ),
+///     );
+///   },
+/// );
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Builder], the platonic stateless widget.
+class StatefulBuilder extends StatefulWidget {
+  /// Creates a widget that both has state and delegates its build to a callback.
+  ///
+  /// The [builder] argument must not be null.
+  const StatefulBuilder({
+    Key? key,
+    required this.builder,
+  }) : assert(builder != null),
+       super(key: key);
+
+  /// Called to obtain the child widget.
+  ///
+  /// This function is called whenever this widget is included in its parent's
+  /// build and the old widget (if any) that it synchronizes with has a distinct
+  /// object identity. Typically the parent's build method will construct
+  /// a new tree of widgets and so a new Builder child will not be [identical]
+  /// to the corresponding old one.
+  final StatefulWidgetBuilder builder;
+
+  @override
+  _StatefulBuilderState createState() => _StatefulBuilderState();
+}
+
+class _StatefulBuilderState extends State<StatefulBuilder> {
+  @override
+  Widget build(BuildContext context) => widget.builder(context, setState);
+}
+
+/// A widget that paints its area with a specified [Color] and then draws its
+/// child on top of that color.
+class ColoredBox extends SingleChildRenderObjectWidget {
+  /// Creates a widget that paints its area with the specified [Color].
+  ///
+  /// The [color] parameter must not be null.
+  const ColoredBox({ required this.color, Widget? child, Key? key })
+      : assert(color != null),
+        super(key: key, child: child);
+
+  /// The color to paint the background area with.
+  final Color color;
+
+  @override
+  _RenderColoredBox createRenderObject(BuildContext context) {
+    return _RenderColoredBox(color: color);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderColoredBox renderObject) {
+    renderObject.color = color;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Color>('color', color));
+  }
+}
+
+class _RenderColoredBox extends RenderProxyBoxWithHitTestBehavior {
+  _RenderColoredBox({ required Color color })
+    : _color = color,
+      super(behavior: HitTestBehavior.opaque);
+
+  /// The fill color for this render object.
+  ///
+  /// This parameter must not be null.
+  Color get color => _color;
+  Color _color;
+  set color(Color value) {
+    assert(value != null);
+    if (value == _color) {
+      return;
+    }
+    _color = value;
+    markNeedsPaint();
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (size > Size.zero) {
+      context.canvas.drawRect(offset & size, Paint()..color = color);
+    }
+    if (child != null) {
+      context.paintChild(child!, offset);
+    }
+  }
+}
diff --git a/lib/src/widgets/binding.dart b/lib/src/widgets/binding.dart
new file mode 100644
index 0000000..d28c603
--- /dev/null
+++ b/lib/src/widgets/binding.dart
@@ -0,0 +1,1269 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:developer' as developer;
+import 'package:flute/ui.dart' show AppLifecycleState, Locale, AccessibilityFeatures, FrameTiming, TimingsCallback, PlatformDispatcher;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/services.dart';
+
+import 'app.dart';
+import 'debug.dart';
+import 'focus_manager.dart';
+import 'framework.dart';
+import 'router.dart';
+import 'widget_inspector.dart';
+
+export 'package:flute/ui.dart' show AppLifecycleState, Locale;
+
+// Examples can assume:
+// // @dart = 2.9
+
+/// Interface for classes that register with the Widgets layer binding.
+///
+/// When used as a mixin, provides no-op method implementations.
+///
+/// See [WidgetsBinding.addObserver] and [WidgetsBinding.removeObserver].
+///
+/// This class can be extended directly, to get default behaviors for all of the
+/// handlers, or can used with the `implements` keyword, in which case all the
+/// handlers must be implemented (and the analyzer will list those that have
+/// been omitted).
+///
+/// {@tool snippet}
+///
+/// This [StatefulWidget] implements the parts of the [State] and
+/// [WidgetsBindingObserver] protocols necessary to react to application
+/// lifecycle messages. See [didChangeAppLifecycleState].
+///
+/// ```dart
+/// class AppLifecycleReactor extends StatefulWidget {
+///   const AppLifecycleReactor({ Key key }) : super(key: key);
+///
+///   @override
+///   _AppLifecycleReactorState createState() => _AppLifecycleReactorState();
+/// }
+///
+/// class _AppLifecycleReactorState extends State<AppLifecycleReactor> with WidgetsBindingObserver {
+///   @override
+///   void initState() {
+///     super.initState();
+///     WidgetsBinding.instance.addObserver(this);
+///   }
+///
+///   @override
+///   void dispose() {
+///     WidgetsBinding.instance.removeObserver(this);
+///     super.dispose();
+///   }
+///
+///   AppLifecycleState _notification;
+///
+///   @override
+///   void didChangeAppLifecycleState(AppLifecycleState state) {
+///     setState(() { _notification = state; });
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Text('Last notification: $_notification');
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// To respond to other notifications, replace the [didChangeAppLifecycleState]
+/// method above with other methods from this class.
+abstract class WidgetsBindingObserver {
+  /// Called when the system tells the app to pop the current route.
+  /// For example, on Android, this is called when the user presses
+  /// the back button.
+  ///
+  /// Observers are notified in registration order until one returns
+  /// true. If none return true, the application quits.
+  ///
+  /// Observers are expected to return true if they were able to
+  /// handle the notification, for example by closing an active dialog
+  /// box, and false otherwise. The [WidgetsApp] widget uses this
+  /// mechanism to notify the [Navigator] widget that it should pop
+  /// its current route if possible.
+  ///
+  /// This method exposes the `popRoute` notification from
+  /// [SystemChannels.navigation].
+  Future<bool> didPopRoute() => Future<bool>.value(false);
+
+  /// Called when the host tells the application to push a new route onto the
+  /// navigator.
+  ///
+  /// Observers are expected to return true if they were able to
+  /// handle the notification. Observers are notified in registration
+  /// order until one returns true.
+  ///
+  /// This method exposes the `pushRoute` notification from
+  /// [SystemChannels.navigation].
+  Future<bool> didPushRoute(String route) => Future<bool>.value(false);
+
+  /// Called when the host tells the application to push a new
+  /// [RouteInformation] and a restoration state onto the router.
+  ///
+  /// Observers are expected to return true if they were able to
+  /// handle the notification. Observers are notified in registration
+  /// order until one returns true.
+  ///
+  /// This method exposes the `pushRouteInformation` notification from
+  /// [SystemChannels.navigation].
+  ///
+  /// The default implementation is to call the [didPushRoute] directly with the
+  /// [RouteInformation.location].
+  Future<bool> didPushRouteInformation(RouteInformation routeInformation) {
+    return didPushRoute(routeInformation.location!);
+  }
+
+  /// Called when the application's dimensions change. For example,
+  /// when a phone is rotated.
+  ///
+  /// This method exposes notifications from
+  /// [dart:ui.PlatformDispatcher.onMetricsChanged].
+  ///
+  /// {@tool snippet}
+  ///
+  /// This [StatefulWidget] implements the parts of the [State] and
+  /// [WidgetsBindingObserver] protocols necessary to react when the device is
+  /// rotated (or otherwise changes dimensions).
+  ///
+  /// ```dart
+  /// class MetricsReactor extends StatefulWidget {
+  ///   const MetricsReactor({ Key key }) : super(key: key);
+  ///
+  ///   @override
+  ///   _MetricsReactorState createState() => _MetricsReactorState();
+  /// }
+  ///
+  /// class _MetricsReactorState extends State<MetricsReactor> with WidgetsBindingObserver {
+  ///   Size _lastSize;
+  ///
+  ///   @override
+  ///   void initState() {
+  ///     super.initState();
+  ///     _lastSize = WidgetsBinding.instance.window.physicalSize;
+  ///     WidgetsBinding.instance.addObserver(this);
+  ///   }
+  ///
+  ///   @override
+  ///   void dispose() {
+  ///     WidgetsBinding.instance.removeObserver(this);
+  ///     super.dispose();
+  ///   }
+  ///
+  ///   @override
+  ///   void didChangeMetrics() {
+  ///     setState(() { _lastSize = WidgetsBinding.instance.window.physicalSize; });
+  ///   }
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Text('Current size: $_lastSize');
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// In general, this is unnecessary as the layout system takes care of
+  /// automatically recomputing the application geometry when the application
+  /// size changes.
+  ///
+  /// See also:
+  ///
+  ///  * [MediaQuery.of], which provides a similar service with less
+  ///    boilerplate.
+  void didChangeMetrics() { }
+
+  /// Called when the platform's text scale factor changes.
+  ///
+  /// This typically happens as the result of the user changing system
+  /// preferences, and it should affect all of the text sizes in the
+  /// application.
+  ///
+  /// This method exposes notifications from
+  /// [dart:ui.PlatformDispatcher.onTextScaleFactorChanged].
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// class TextScaleFactorReactor extends StatefulWidget {
+  ///   const TextScaleFactorReactor({ Key key }) : super(key: key);
+  ///
+  ///   @override
+  ///   _TextScaleFactorReactorState createState() => _TextScaleFactorReactorState();
+  /// }
+  ///
+  /// class _TextScaleFactorReactorState extends State<TextScaleFactorReactor> with WidgetsBindingObserver {
+  ///   @override
+  ///   void initState() {
+  ///     super.initState();
+  ///     WidgetsBinding.instance.addObserver(this);
+  ///   }
+  ///
+  ///   @override
+  ///   void dispose() {
+  ///     WidgetsBinding.instance.removeObserver(this);
+  ///     super.dispose();
+  ///   }
+  ///
+  ///   double _lastTextScaleFactor;
+  ///
+  ///   @override
+  ///   void didChangeTextScaleFactor() {
+  ///     setState(() { _lastTextScaleFactor = WidgetsBinding.instance.window.textScaleFactor; });
+  ///   }
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Text('Current scale factor: $_lastTextScaleFactor');
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [MediaQuery.of], which provides a similar service with less
+  ///    boilerplate.
+  void didChangeTextScaleFactor() { }
+
+  /// Called when the platform brightness changes.
+  ///
+  /// This method exposes notifications from
+  /// [dart:ui.PlatformDispatcher.onPlatformBrightnessChanged].
+  void didChangePlatformBrightness() { }
+
+  /// Called when the system tells the app that the user's locale has
+  /// changed. For example, if the user changes the system language
+  /// settings.
+  ///
+  /// This method exposes notifications from
+  /// [dart:ui.PlatformDispatcher.onLocaleChanged].
+  void didChangeLocales(List<Locale>? locales) { }
+
+  /// Called when the system puts the app in the background or returns
+  /// the app to the foreground.
+  ///
+  /// An example of implementing this method is provided in the class-level
+  /// documentation for the [WidgetsBindingObserver] class.
+  ///
+  /// This method exposes notifications from [SystemChannels.lifecycle].
+  void didChangeAppLifecycleState(AppLifecycleState state) { }
+
+  /// Called when the system is running low on memory.
+  ///
+  /// This method exposes the `memoryPressure` notification from
+  /// [SystemChannels.system].
+  void didHaveMemoryPressure() { }
+
+  /// Called when the system changes the set of currently active accessibility
+  /// features.
+  ///
+  /// This method exposes notifications from
+  /// [dart:ui.PlatformDispatcher.onAccessibilityFeaturesChanged].
+  void didChangeAccessibilityFeatures() { }
+}
+
+/// The glue between the widgets layer and the Flutter engine.
+mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
+  @override
+  void initInstances() {
+    super.initInstances();
+    _instance = this;
+
+    assert(() {
+      _debugAddStackFilters();
+      return true;
+    }());
+
+    // Initialization of [_buildOwner] has to be done after
+    // [super.initInstances] is called, as it requires [ServicesBinding] to
+    // properly setup the [defaultBinaryMessenger] instance.
+    _buildOwner = BuildOwner();
+    buildOwner!.onBuildScheduled = _handleBuildScheduled;
+    window.onLocaleChanged = handleLocaleChanged;
+    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
+    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
+    FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
+  }
+
+  void _debugAddStackFilters() {
+    const PartialStackFrame elementInflateWidget = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'Element', method: 'inflateWidget');
+    const PartialStackFrame elementUpdateChild = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'Element', method: 'updateChild');
+    const PartialStackFrame elementRebuild = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'Element', method: 'rebuild');
+    const PartialStackFrame componentElementPerformRebuild = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'ComponentElement', method: 'performRebuild');
+    const PartialStackFrame componentElementFirstBuild = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'ComponentElement', method: '_firstBuild');
+    const PartialStackFrame componentElementMount = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'ComponentElement', method: 'mount');
+    const PartialStackFrame statefulElementFirstBuild = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'StatefulElement', method: '_firstBuild');
+    const PartialStackFrame singleChildMount = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'SingleChildRenderObjectElement', method: 'mount');
+    const PartialStackFrame statefulElementRebuild = PartialStackFrame(package: 'package:flutter/src/widgets/framework.dart', className: 'StatefulElement', method: 'performRebuild');
+
+    const String replacementString = '...     Normal element mounting';
+
+    // ComponentElement variations
+    FlutterError.addDefaultStackFilter(const RepetitiveStackFrameFilter(
+      frames: <PartialStackFrame>[
+        elementInflateWidget,
+        elementUpdateChild,
+        componentElementPerformRebuild,
+        elementRebuild,
+        componentElementFirstBuild,
+        componentElementMount,
+      ],
+      replacement: replacementString,
+    ));
+    FlutterError.addDefaultStackFilter(const RepetitiveStackFrameFilter(
+      frames: <PartialStackFrame>[
+        elementUpdateChild,
+        componentElementPerformRebuild,
+        elementRebuild,
+        componentElementFirstBuild,
+        componentElementMount,
+      ],
+      replacement: replacementString,
+    ));
+
+    // StatefulElement variations
+    FlutterError.addDefaultStackFilter(const RepetitiveStackFrameFilter(
+      frames: <PartialStackFrame>[
+        elementInflateWidget,
+        elementUpdateChild,
+        componentElementPerformRebuild,
+        statefulElementRebuild,
+        elementRebuild,
+        componentElementFirstBuild,
+        statefulElementFirstBuild,
+        componentElementMount,
+      ],
+      replacement: replacementString,
+    ));
+    FlutterError.addDefaultStackFilter(const RepetitiveStackFrameFilter(
+      frames: <PartialStackFrame>[
+        elementUpdateChild,
+        componentElementPerformRebuild,
+        statefulElementRebuild,
+        elementRebuild,
+        componentElementFirstBuild,
+        statefulElementFirstBuild,
+        componentElementMount,
+      ],
+      replacement: replacementString,
+    ));
+
+    // SingleChildRenderObjectElement variations
+    FlutterError.addDefaultStackFilter(const RepetitiveStackFrameFilter(
+      frames: <PartialStackFrame>[
+        elementInflateWidget,
+        elementUpdateChild,
+        singleChildMount,
+      ],
+      replacement: replacementString,
+    ));
+    FlutterError.addDefaultStackFilter(const RepetitiveStackFrameFilter(
+      frames: <PartialStackFrame>[
+        elementUpdateChild,
+        singleChildMount,
+      ],
+      replacement: replacementString,
+    ));
+  }
+
+  /// The current [WidgetsBinding], if one has been created.
+  ///
+  /// If you need the binding to be constructed before calling [runApp],
+  /// you can ensure a Widget binding has been constructed by calling the
+  /// `WidgetsFlutterBinding.ensureInitialized()` function.
+  static WidgetsBinding? get instance => _instance;
+  static WidgetsBinding? _instance;
+
+  @override
+  void initServiceExtensions() {
+    super.initServiceExtensions();
+
+    if (!kReleaseMode) {
+      registerSignalServiceExtension(
+        name: 'debugDumpApp',
+        callback: () {
+          debugDumpApp();
+          return debugPrintDone;
+        },
+      );
+
+      if (!kIsWeb) {
+        registerBoolServiceExtension(
+          name: 'showPerformanceOverlay',
+          getter: () =>
+          Future<bool>.value(WidgetsApp.showPerformanceOverlayOverride),
+          setter: (bool value) {
+            if (WidgetsApp.showPerformanceOverlayOverride == value)
+              return Future<void>.value();
+            WidgetsApp.showPerformanceOverlayOverride = value;
+            return _forceRebuild();
+          },
+        );
+      }
+
+      registerServiceExtension(
+        name: 'didSendFirstFrameEvent',
+        callback: (_) async {
+          return <String, dynamic>{
+            // This is defined to return a STRING, not a boolean.
+            // Devtools, the Intellij plugin, and the flutter tool all depend
+            // on it returning a string and not a boolean.
+            'enabled': _needToReportFirstFrame ? 'false' : 'true',
+          };
+        },
+      );
+
+      // This returns 'true' when the first frame is rasterized, and the trace
+      // event 'Rasterized first useful frame' is sent out.
+      registerServiceExtension(
+        name: 'didSendFirstFrameRasterizedEvent',
+        callback: (_) async {
+          return <String, dynamic>{
+            // This is defined to return a STRING, not a boolean.
+            // Devtools, the Intellij plugin, and the flutter tool all depend
+            // on it returning a string and not a boolean.
+            'enabled': firstFrameRasterized ? 'true' : 'false',
+          };
+        },
+      );
+
+      registerServiceExtension(
+        name: 'fastReassemble',
+        callback: (Map<String, Object> params) async {
+          final String? className = params['className'] as String?;
+          void markElementsDirty(Element element) {
+            if (element.widget.runtimeType.toString() == className) {
+              element.markNeedsBuild();
+            }
+            element.visitChildElements(markElementsDirty);
+          }
+          if (renderViewElement != null) {
+            markElementsDirty(renderViewElement!);
+          }
+          await endOfFrame;
+          return <String, String>{'type': 'Success'};
+        },
+      );
+
+      // Expose the ability to send Widget rebuilds as [Timeline] events.
+      registerBoolServiceExtension(
+        name: 'profileWidgetBuilds',
+        getter: () async => debugProfileBuildsEnabled,
+        setter: (bool value) async {
+          if (debugProfileBuildsEnabled != value)
+            debugProfileBuildsEnabled = value;
+        },
+      );
+    }
+
+    assert(() {
+      registerBoolServiceExtension(
+        name: 'debugAllowBanner',
+        getter: () => Future<bool>.value(WidgetsApp.debugAllowBannerOverride),
+        setter: (bool value) {
+          if (WidgetsApp.debugAllowBannerOverride == value)
+            return Future<void>.value();
+          WidgetsApp.debugAllowBannerOverride = value;
+          return _forceRebuild();
+        },
+      );
+
+      // This service extension is deprecated and will be removed by 12/1/2018.
+      // Use ext.flutter.inspector.show instead.
+      registerBoolServiceExtension(
+          name: 'debugWidgetInspector',
+          getter: () async => WidgetsApp.debugShowWidgetInspectorOverride,
+          setter: (bool value) {
+            if (WidgetsApp.debugShowWidgetInspectorOverride == value)
+              return Future<void>.value();
+            WidgetsApp.debugShowWidgetInspectorOverride = value;
+            return _forceRebuild();
+          },
+      );
+
+      WidgetInspectorService.instance.initServiceExtensions(registerServiceExtension);
+
+      return true;
+    }());
+  }
+
+  Future<void> _forceRebuild() {
+    if (renderViewElement != null) {
+      buildOwner!.reassemble(renderViewElement!);
+      return endOfFrame;
+    }
+    return Future<void>.value();
+  }
+
+  /// The [BuildOwner] in charge of executing the build pipeline for the
+  /// widget tree rooted at this binding.
+  BuildOwner? get buildOwner => _buildOwner;
+  // Initialization of [_buildOwner] has to be done within the [initInstances]
+  // method, as it requires [ServicesBinding] to properly setup the
+  // [defaultBinaryMessenger] instance.
+  BuildOwner? _buildOwner;
+
+  /// The object in charge of the focus tree.
+  ///
+  /// Rarely used directly. Instead, consider using [FocusScope.of] to obtain
+  /// the [FocusScopeNode] for a given [BuildContext].
+  ///
+  /// See [FocusManager] for more details.
+  FocusManager get focusManager => _buildOwner!.focusManager;
+
+  final List<WidgetsBindingObserver> _observers = <WidgetsBindingObserver>[];
+
+  /// Registers the given object as a binding observer. Binding
+  /// observers are notified when various application events occur,
+  /// for example when the system locale changes. Generally, one
+  /// widget in the widget tree registers itself as a binding
+  /// observer, and converts the system state into inherited widgets.
+  ///
+  /// For example, the [WidgetsApp] widget registers as a binding
+  /// observer and passes the screen size to a [MediaQuery] widget
+  /// each time it is built, which enables other widgets to use the
+  /// [MediaQuery.of] static method and (implicitly) the
+  /// [InheritedWidget] mechanism to be notified whenever the screen
+  /// size changes (e.g. whenever the screen rotates).
+  ///
+  /// See also:
+  ///
+  ///  * [removeObserver], to release the resources reserved by this method.
+  ///  * [WidgetsBindingObserver], which has an example of using this method.
+  void addObserver(WidgetsBindingObserver observer) => _observers.add(observer);
+
+  /// Unregisters the given observer. This should be used sparingly as
+  /// it is relatively expensive (O(N) in the number of registered
+  /// observers).
+  ///
+  /// See also:
+  ///
+  ///  * [addObserver], for the method that adds observers in the first place.
+  ///  * [WidgetsBindingObserver], which has an example of using this method.
+  bool removeObserver(WidgetsBindingObserver observer) => _observers.remove(observer);
+
+  @override
+  void handleMetricsChanged() {
+    super.handleMetricsChanged();
+    for (final WidgetsBindingObserver observer in _observers)
+      observer.didChangeMetrics();
+  }
+
+  @override
+  void handleTextScaleFactorChanged() {
+    super.handleTextScaleFactorChanged();
+    for (final WidgetsBindingObserver observer in _observers)
+      observer.didChangeTextScaleFactor();
+  }
+
+  @override
+  void handlePlatformBrightnessChanged() {
+    super.handlePlatformBrightnessChanged();
+    for (final WidgetsBindingObserver observer in _observers)
+      observer.didChangePlatformBrightness();
+  }
+
+  @override
+  void handleAccessibilityFeaturesChanged() {
+    super.handleAccessibilityFeaturesChanged();
+    for (final WidgetsBindingObserver observer in _observers)
+      observer.didChangeAccessibilityFeatures();
+  }
+
+  /// Called when the system locale changes.
+  ///
+  /// Calls [dispatchLocalesChanged] to notify the binding observers.
+  ///
+  /// See [dart:ui.PlatformDispatcher.onLocaleChanged].
+  @protected
+  @mustCallSuper
+  void handleLocaleChanged() {
+    dispatchLocalesChanged(window.locales);
+  }
+
+  /// Notify all the observers that the locale has changed (using
+  /// [WidgetsBindingObserver.didChangeLocales]), giving them the
+  /// `locales` argument.
+  ///
+  /// This is called by [handleLocaleChanged] when the
+  /// [PlatformDispatcher.onLocaleChanged] notification is received.
+  @protected
+  @mustCallSuper
+  void dispatchLocalesChanged(List<Locale>? locales) {
+    for (final WidgetsBindingObserver observer in _observers)
+      observer.didChangeLocales(locales);
+  }
+
+  /// Notify all the observers that the active set of [AccessibilityFeatures]
+  /// has changed (using [WidgetsBindingObserver.didChangeAccessibilityFeatures]),
+  /// giving them the `features` argument.
+  ///
+  /// This is called by [handleAccessibilityFeaturesChanged] when the
+  /// [PlatformDispatcher.onAccessibilityFeaturesChanged] notification is received.
+  @protected
+  @mustCallSuper
+  void dispatchAccessibilityFeaturesChanged() {
+    for (final WidgetsBindingObserver observer in _observers)
+      observer.didChangeAccessibilityFeatures();
+  }
+
+  /// Called when the system pops the current route.
+  ///
+  /// This first notifies the binding observers (using
+  /// [WidgetsBindingObserver.didPopRoute]), in registration order, until one
+  /// returns true, meaning that it was able to handle the request (e.g. by
+  /// closing a dialog box). If none return true, then the application is shut
+  /// down by calling [SystemNavigator.pop].
+  ///
+  /// [WidgetsApp] uses this in conjunction with a [Navigator] to
+  /// cause the back button to close dialog boxes, return from modal
+  /// pages, and so forth.
+  ///
+  /// This method exposes the `popRoute` notification from
+  /// [SystemChannels.navigation].
+  @protected
+  Future<void> handlePopRoute() async {
+    for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
+      if (await observer.didPopRoute())
+        return;
+    }
+    SystemNavigator.pop();
+  }
+
+  /// Called when the host tells the app to push a new route onto the
+  /// navigator.
+  ///
+  /// This notifies the binding observers (using
+  /// [WidgetsBindingObserver.didPushRoute]), in registration order, until one
+  /// returns true, meaning that it was able to handle the request (e.g. by
+  /// opening a dialog box). If none return true, then nothing happens.
+  ///
+  /// This method exposes the `pushRoute` notification from
+  /// [SystemChannels.navigation].
+  @protected
+  @mustCallSuper
+  Future<void> handlePushRoute(String route) async {
+    for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
+      if (await observer.didPushRoute(route))
+        return;
+    }
+  }
+
+  Future<void> _handlePushRouteInformation(Map<dynamic, dynamic> routeArguments) async {
+    for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
+      if (
+        await observer.didPushRouteInformation(
+          RouteInformation(
+            location: routeArguments['location'] as String,
+            state: routeArguments['state'] as Object,
+          )
+        )
+      )
+      return;
+    }
+  }
+
+  Future<dynamic> _handleNavigationInvocation(MethodCall methodCall) {
+    switch (methodCall.method) {
+      case 'popRoute':
+        return handlePopRoute();
+      case 'pushRoute':
+        return handlePushRoute(methodCall.arguments as String);
+      case 'pushRouteInformation':
+        return _handlePushRouteInformation(methodCall.arguments as Map<dynamic, dynamic>);
+    }
+    return Future<dynamic>.value();
+  }
+
+  @override
+  void handleAppLifecycleStateChanged(AppLifecycleState state) {
+    super.handleAppLifecycleStateChanged(state);
+    for (final WidgetsBindingObserver observer in _observers)
+      observer.didChangeAppLifecycleState(state);
+  }
+
+  @override
+  void handleMemoryPressure() {
+    super.handleMemoryPressure();
+    for (final WidgetsBindingObserver observer in _observers)
+      observer.didHaveMemoryPressure();
+  }
+
+  bool _needToReportFirstFrame = true;
+
+  final Completer<void> _firstFrameCompleter = Completer<void>();
+
+  /// Whether the Flutter engine has rasterized the first frame.
+  ///
+  /// {@macro flutter.flutter_driver.WaitUntilFirstFrameRasterized}
+  ///
+  /// See also:
+  ///
+  ///  * [waitUntilFirstFrameRasterized], the future when [firstFrameRasterized]
+  ///    becomes true.
+  bool get firstFrameRasterized => _firstFrameCompleter.isCompleted;
+
+  /// A future that completes when the Flutter engine has rasterized the first
+  /// frame.
+  ///
+  /// {@macro flutter.flutter_driver.WaitUntilFirstFrameRasterized}
+  ///
+  /// See also:
+  ///
+  ///  * [firstFrameRasterized], whether this future has completed or not.
+  Future<void> get waitUntilFirstFrameRasterized => _firstFrameCompleter.future;
+
+  /// Whether the first frame has finished building.
+  ///
+  /// This value can also be obtained over the VM service protocol as
+  /// `ext.flutter.didSendFirstFrameEvent`.
+  ///
+  /// See also:
+  ///
+  ///  * [firstFrameRasterized], whether the first frame has finished rendering.
+  bool get debugDidSendFirstFrameEvent => !_needToReportFirstFrame;
+
+  /// Tell the framework not to report the frame it is building as a "useful"
+  /// first frame until there is a corresponding call to [allowFirstFrameReport].
+  ///
+  /// Deprecated. Use [deferFirstFrame]/[allowFirstFrame] to delay rendering the
+  /// first frame.
+  @Deprecated(
+    'Use deferFirstFrame/allowFirstFrame to delay rendering the first frame. '
+    'This feature was deprecated after v1.12.4.'
+  )
+  void deferFirstFrameReport() {
+    if (!kReleaseMode) {
+      deferFirstFrame();
+    }
+  }
+
+  /// When called after [deferFirstFrameReport]: tell the framework to report
+  /// the frame it is building as a "useful" first frame.
+  ///
+  /// Deprecated. Use [deferFirstFrame]/[allowFirstFrame] to delay rendering the
+  /// first frame.
+  @Deprecated(
+    'Use deferFirstFrame/allowFirstFrame to delay rendering the first frame. '
+    'This feature was deprecated after v1.12.4.'
+  )
+  void allowFirstFrameReport() {
+    if (!kReleaseMode) {
+      allowFirstFrame();
+    }
+  }
+
+  void _handleBuildScheduled() {
+    // If we're in the process of building dirty elements, then changes
+    // should not trigger a new frame.
+    assert(() {
+      if (debugBuildingDirtyElements) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('Build scheduled during frame.'),
+          ErrorDescription(
+            'While the widget tree was being built, laid out, and painted, '
+            'a new frame was scheduled to rebuild the widget tree.'
+          ),
+          ErrorHint(
+            'This might be because setState() was called from a layout or '
+            'paint callback. '
+            'If a change is needed to the widget tree, it should be applied '
+            'as the tree is being built. Scheduling a change for the subsequent '
+            'frame instead results in an interface that lags behind by one frame. '
+            'If this was done to make your build dependent on a size measured at '
+            'layout time, consider using a LayoutBuilder, CustomSingleChildLayout, '
+            'or CustomMultiChildLayout. If, on the other hand, the one frame delay '
+            'is the desired effect, for example because this is an '
+            'animation, consider scheduling the frame in a post-frame callback '
+            'using SchedulerBinding.addPostFrameCallback or '
+            'using an AnimationController to trigger the animation.',
+          )
+        ]);
+      }
+      return true;
+    }());
+    ensureVisualUpdate();
+  }
+
+  /// Whether we are currently in a frame. This is used to verify
+  /// that frames are not scheduled redundantly.
+  ///
+  /// This is public so that test frameworks can change it.
+  ///
+  /// This flag is not used in release builds.
+  @protected
+  bool debugBuildingDirtyElements = false;
+
+  /// Pump the build and rendering pipeline to generate a frame.
+  ///
+  /// This method is called by [handleDrawFrame], which itself is called
+  /// automatically by the engine when it is time to lay out and paint a
+  /// frame.
+  ///
+  /// Each frame consists of the following phases:
+  ///
+  /// 1. The animation phase: The [handleBeginFrame] method, which is registered
+  /// with [PlatformDispatcher.onBeginFrame], invokes all the transient frame
+  /// callbacks registered with [scheduleFrameCallback], in registration order.
+  /// This includes all the [Ticker] instances that are driving
+  /// [AnimationController] objects, which means all of the active [Animation]
+  /// objects tick at this point.
+  ///
+  /// 2. Microtasks: After [handleBeginFrame] returns, any microtasks that got
+  /// scheduled by transient frame callbacks get to run. This typically includes
+  /// callbacks for futures from [Ticker]s and [AnimationController]s that
+  /// completed this frame.
+  ///
+  /// After [handleBeginFrame], [handleDrawFrame], which is registered with
+  /// [PlatformDispatcher.onDrawFrame], is called, which invokes all the
+  /// persistent frame callbacks, of which the most notable is this method,
+  /// [drawFrame], which proceeds as follows:
+  ///
+  /// 3. The build phase: All the dirty [Element]s in the widget tree are
+  /// rebuilt (see [State.build]). See [State.setState] for further details on
+  /// marking a widget dirty for building. See [BuildOwner] for more information
+  /// on this step.
+  ///
+  /// 4. The layout phase: All the dirty [RenderObject]s in the system are laid
+  /// out (see [RenderObject.performLayout]). See [RenderObject.markNeedsLayout]
+  /// for further details on marking an object dirty for layout.
+  ///
+  /// 5. The compositing bits phase: The compositing bits on any dirty
+  /// [RenderObject] objects are updated. See
+  /// [RenderObject.markNeedsCompositingBitsUpdate].
+  ///
+  /// 6. The paint phase: All the dirty [RenderObject]s in the system are
+  /// repainted (see [RenderObject.paint]). This generates the [Layer] tree. See
+  /// [RenderObject.markNeedsPaint] for further details on marking an object
+  /// dirty for paint.
+  ///
+  /// 7. The compositing phase: The layer tree is turned into a [Scene] and
+  /// sent to the GPU.
+  ///
+  /// 8. The semantics phase: All the dirty [RenderObject]s in the system have
+  /// their semantics updated (see [RenderObject.assembleSemanticsNode]). This
+  /// generates the [SemanticsNode] tree. See
+  /// [RenderObject.markNeedsSemanticsUpdate] for further details on marking an
+  /// object dirty for semantics.
+  ///
+  /// For more details on steps 4-8, see [PipelineOwner].
+  ///
+  /// 9. The finalization phase in the widgets layer: The widgets tree is
+  /// finalized. This causes [State.dispose] to be invoked on any objects that
+  /// were removed from the widgets tree this frame. See
+  /// [BuildOwner.finalizeTree] for more details.
+  ///
+  /// 10. The finalization phase in the scheduler layer: After [drawFrame]
+  /// returns, [handleDrawFrame] then invokes post-frame callbacks (registered
+  /// with [addPostFrameCallback]).
+  //
+  // When editing the above, also update rendering/binding.dart's copy.
+  @override
+  void drawFrame() {
+    assert(!debugBuildingDirtyElements);
+    assert(() {
+      debugBuildingDirtyElements = true;
+      return true;
+    }());
+
+    TimingsCallback? firstFrameCallback;
+    if (_needToReportFirstFrame) {
+      assert(!_firstFrameCompleter.isCompleted);
+
+      firstFrameCallback = (List<FrameTiming> timings) {
+        assert(sendFramesToEngine);
+        if (!kReleaseMode) {
+          developer.Timeline.instantSync('Rasterized first useful frame');
+          developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
+        }
+        SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
+        firstFrameCallback = null;
+        _firstFrameCompleter.complete();
+      };
+      // Callback is only invoked when FlutterView.render is called. When
+      // sendFramesToEngine is set to false during the frame, it will not be
+      // called and we need to remove the callback (see below).
+      SchedulerBinding.instance!.addTimingsCallback(firstFrameCallback!);
+    }
+
+    try {
+      if (renderViewElement != null)
+        buildOwner!.buildScope(renderViewElement!);
+      super.drawFrame();
+      buildOwner!.finalizeTree();
+    } finally {
+      assert(() {
+        debugBuildingDirtyElements = false;
+        return true;
+      }());
+    }
+    if (!kReleaseMode) {
+      if (_needToReportFirstFrame && sendFramesToEngine) {
+        developer.Timeline.instantSync('Widgets built first useful frame');
+      }
+    }
+    _needToReportFirstFrame = false;
+    if (firstFrameCallback != null && !sendFramesToEngine) {
+      // This frame is deferred and not the first frame sent to the engine that
+      // should be reported.
+      _needToReportFirstFrame = true;
+      SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
+    }
+  }
+
+  /// The [Element] that is at the root of the hierarchy (and which wraps the
+  /// [RenderView] object at the root of the rendering hierarchy).
+  ///
+  /// This is initialized the first time [runApp] is called.
+  Element? get renderViewElement => _renderViewElement;
+  Element? _renderViewElement;
+
+  bool _readyToProduceFrames = false;
+
+  @override
+  bool get framesEnabled => super.framesEnabled && _readyToProduceFrames;
+
+  /// Schedules a [Timer] for attaching the root widget.
+  ///
+  /// This is called by [runApp] to configure the widget tree. Consider using
+  /// [attachRootWidget] if you want to build the widget tree synchronously.
+  @protected
+  void scheduleAttachRootWidget(Widget rootWidget) {
+    Timer.run(() {
+      attachRootWidget(rootWidget);
+    });
+  }
+
+  /// Takes a widget and attaches it to the [renderViewElement], creating it if
+  /// necessary.
+  ///
+  /// This is called by [runApp] to configure the widget tree.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderObjectToWidgetAdapter.attachToRenderTree], which inflates a
+  ///    widget and attaches it to the render tree.
+  void attachRootWidget(Widget rootWidget) {
+    _readyToProduceFrames = true;
+    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
+      container: renderView,
+      debugShortDescription: '[root]',
+      child: rootWidget,
+    ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
+  }
+
+  /// Whether the [renderViewElement] has been initialized.
+  ///
+  /// This will be false until [runApp] is called (or [WidgetTester.pumpWidget]
+  /// is called in the context of a [TestWidgetsFlutterBinding]).
+  bool get isRootWidgetAttached => _renderViewElement != null;
+
+  @override
+  Future<void> performReassemble() {
+    assert(() {
+      WidgetInspectorService.instance.performReassemble();
+      return true;
+    }());
+
+    if (renderViewElement != null)
+      buildOwner!.reassemble(renderViewElement!);
+    return super.performReassemble();
+  }
+
+  /// Computes the locale the current platform would resolve to.
+  ///
+  /// This method is meant to be used as part of a
+  /// [WidgetsApp.localeListResolutionCallback]. Since this method may return
+  /// null, a Flutter/dart algorithm should still be provided as a fallback in
+  /// case a native resolved locale cannot be determined or if the native
+  /// resolved locale is undesirable.
+  ///
+  /// This method may return a null [Locale] if the platform does not support
+  /// native locale resolution, or if the resolution failed.
+  ///
+  /// The first `supportedLocale` is treated as the default locale and will be returned
+  /// if no better match is found.
+  ///
+  /// Android and iOS are currently supported.
+  ///
+  /// On Android, the algorithm described in
+  /// https://developer.android.com/guide/topics/resources/multilingual-support
+  /// is used to determine the resolved locale. Depending on the android version
+  /// of the device, either the modern (>= API 24) or legacy (< API 24) algorithm
+  /// will be used.
+  ///
+  /// On iOS, the result of `preferredLocalizationsFromArray` method of `NSBundle`
+  /// is returned. See:
+  /// https://developer.apple.com/documentation/foundation/nsbundle/1417249-preferredlocalizationsfromarray?language=objc
+  /// for details on the used method.
+  ///
+  /// iOS treats script code as necessary for a match, so a user preferred locale of
+  /// `zh_Hans_CN` will not resolve to a supported locale of `zh_CN`.
+  ///
+  /// Since implementation may vary by platform and has potential to be heavy,
+  /// it is recommended to cache the results of this method if the value is
+  /// used multiple times.
+  ///
+  /// Second-best (and n-best) matching locales should be obtained by calling this
+  /// method again with the matched locale of the first call omitted from
+  /// `supportedLocales`.
+  Locale? computePlatformResolvedLocale(List<Locale> supportedLocales) {
+    return window.computePlatformResolvedLocale(supportedLocales);
+  }
+}
+
+/// Inflate the given widget and attach it to the screen.
+///
+/// The widget is given constraints during layout that force it to fill the
+/// entire screen. If you wish to align your widget to one side of the screen
+/// (e.g., the top), consider using the [Align] widget. If you wish to center
+/// your widget, you can also use the [Center] widget.
+///
+/// Calling [runApp] again will detach the previous root widget from the screen
+/// and attach the given widget in its place. The new widget tree is compared
+/// against the previous widget tree and any differences are applied to the
+/// underlying render tree, similar to what happens when a [StatefulWidget]
+/// rebuilds after calling [State.setState].
+///
+/// Initializes the binding using [WidgetsFlutterBinding] if necessary.
+///
+/// See also:
+///
+///  * [WidgetsBinding.attachRootWidget], which creates the root widget for the
+///    widget hierarchy.
+///  * [RenderObjectToWidgetAdapter.attachToRenderTree], which creates the root
+///    element for the element hierarchy.
+///  * [WidgetsBinding.handleBeginFrame], which pumps the widget pipeline to
+///    ensure the widget, element, and render trees are all built.
+void runApp(Widget app) {
+  WidgetsFlutterBinding.ensureInitialized()
+    ..scheduleAttachRootWidget(app)
+    ..scheduleWarmUpFrame();
+}
+
+/// Print a string representation of the currently running app.
+void debugDumpApp() {
+  assert(WidgetsBinding.instance != null);
+  String mode = 'RELEASE MODE';
+  assert(() {
+    mode = 'CHECKED MODE';
+    return true;
+  }());
+  debugPrint('${WidgetsBinding.instance.runtimeType} - $mode');
+  if (WidgetsBinding.instance!.renderViewElement != null) {
+    debugPrint(WidgetsBinding.instance!.renderViewElement!.toStringDeep());
+  } else {
+    debugPrint('<no tree currently mounted>');
+  }
+}
+
+/// A bridge from a [RenderObject] to an [Element] tree.
+///
+/// The given container is the [RenderObject] that the [Element] tree should be
+/// inserted into. It must be a [RenderObject] that implements the
+/// [RenderObjectWithChildMixin] protocol. The type argument `T` is the kind of
+/// [RenderObject] that the container expects as its child.
+///
+/// Used by [runApp] to bootstrap applications.
+class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
+  /// Creates a bridge from a [RenderObject] to an [Element] tree.
+  ///
+  /// Used by [WidgetsBinding] to attach the root widget to the [RenderView].
+  RenderObjectToWidgetAdapter({
+    this.child,
+    required this.container,
+    this.debugShortDescription,
+  }) : super(key: GlobalObjectKey(container));
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// The [RenderObject] that is the parent of the [Element] created by this widget.
+  final RenderObjectWithChildMixin<T> container;
+
+  /// A short description of this widget used by debugging aids.
+  final String? debugShortDescription;
+
+  @override
+  RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
+
+  @override
+  RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;
+
+  @override
+  void updateRenderObject(BuildContext context, RenderObject renderObject) { }
+
+  /// Inflate this widget and actually set the resulting [RenderObject] as the
+  /// child of [container].
+  ///
+  /// If `element` is null, this function will create a new element. Otherwise,
+  /// the given element will have an update scheduled to switch to this widget.
+  ///
+  /// Used by [runApp] to bootstrap applications.
+  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
+    if (element == null) {
+      owner.lockState(() {
+        element = createElement();
+        assert(element != null);
+        element!.assignOwner(owner);
+      });
+      owner.buildScope(element!, () {
+        element!.mount(null, null);
+      });
+      // This is most likely the first time the framework is ready to produce
+      // a frame. Ensure that we are asked for one.
+      SchedulerBinding.instance!.ensureVisualUpdate();
+    } else {
+      element._newWidget = this;
+      element.markNeedsBuild();
+    }
+    return element!;
+  }
+
+  @override
+  String toStringShort() => debugShortDescription ?? super.toStringShort();
+}
+
+/// A [RootRenderObjectElement] that is hosted by a [RenderObject].
+///
+/// This element class is the instantiation of a [RenderObjectToWidgetAdapter]
+/// widget. It can be used only as the root of an [Element] tree (it cannot be
+/// mounted into another [Element]; it's parent must be null).
+///
+/// In typical usage, it will be instantiated for a [RenderObjectToWidgetAdapter]
+/// whose container is the [RenderView] that connects to the Flutter engine. In
+/// this usage, it is normally instantiated by the bootstrapping logic in the
+/// [WidgetsFlutterBinding] singleton created by [runApp].
+class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObjectElement {
+  /// Creates an element that is hosted by a [RenderObject].
+  ///
+  /// The [RenderObject] created by this element is not automatically set as a
+  /// child of the hosting [RenderObject]. To actually attach this element to
+  /// the render tree, call [RenderObjectToWidgetAdapter.attachToRenderTree].
+  RenderObjectToWidgetElement(RenderObjectToWidgetAdapter<T> widget) : super(widget);
+
+  @override
+  RenderObjectToWidgetAdapter<T> get widget => super.widget as RenderObjectToWidgetAdapter<T>;
+
+  Element? _child;
+
+  static const Object _rootChildSlot = Object();
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    if (_child != null)
+      visitor(_child!);
+  }
+
+  @override
+  void forgetChild(Element child) {
+    assert(child == _child);
+    _child = null;
+    super.forgetChild(child);
+  }
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    assert(parent == null);
+    super.mount(parent, newSlot);
+    _rebuild();
+  }
+
+  @override
+  void update(RenderObjectToWidgetAdapter<T> newWidget) {
+    super.update(newWidget);
+    assert(widget == newWidget);
+    _rebuild();
+  }
+
+  // When we are assigned a new widget, we store it here
+  // until we are ready to update to it.
+  Widget? _newWidget;
+
+  @override
+  void performRebuild() {
+    if (_newWidget != null) {
+      // _newWidget can be null if, for instance, we were rebuilt
+      // due to a reassemble.
+      final Widget newWidget = _newWidget!;
+      _newWidget = null;
+      update(newWidget as RenderObjectToWidgetAdapter<T>);
+    }
+    super.performRebuild();
+    assert(_newWidget == null);
+  }
+
+  void _rebuild() {
+    try {
+      _child = updateChild(_child, widget.child, _rootChildSlot);
+      assert(_child != null);
+    } catch (exception, stack) {
+      final FlutterErrorDetails details = FlutterErrorDetails(
+        exception: exception,
+        stack: stack,
+        library: 'widgets library',
+        context: ErrorDescription('attaching to the render tree'),
+      );
+      FlutterError.reportError(details);
+      final Widget error = ErrorWidget.builder(details);
+      _child = updateChild(null, error, _rootChildSlot);
+    }
+  }
+
+  @override
+  RenderObjectWithChildMixin<T> get renderObject => super.renderObject as RenderObjectWithChildMixin<T>;
+
+  @override
+  void insertRenderObjectChild(RenderObject child, dynamic slot) {
+    assert(slot == _rootChildSlot);
+    assert(renderObject.debugValidateChild(child));
+    renderObject.child = child as T;
+  }
+
+  @override
+  void moveRenderObjectChild(RenderObject child, dynamic oldSlot, dynamic newSlot) {
+    assert(false);
+  }
+
+  @override
+  void removeRenderObjectChild(RenderObject child, dynamic slot) {
+    assert(renderObject.child == child);
+    renderObject.child = null;
+  }
+}
+
+/// A concrete binding for applications based on the Widgets framework.
+///
+/// This is the glue that binds the framework to the Flutter engine.
+class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
+
+  /// Returns an instance of the [WidgetsBinding], creating and
+  /// initializing it if necessary. If one is created, it will be a
+  /// [WidgetsFlutterBinding]. If one was previously initialized, then
+  /// it will at least implement [WidgetsBinding].
+  ///
+  /// You only need to call this method if you need the binding to be
+  /// initialized before calling [runApp].
+  ///
+  /// In the `flutter_test` framework, [testWidgets] initializes the
+  /// binding instance to a [TestWidgetsFlutterBinding], not a
+  /// [WidgetsFlutterBinding].
+  static WidgetsBinding ensureInitialized() {
+    if (WidgetsBinding.instance == null)
+      WidgetsFlutterBinding();
+    return WidgetsBinding.instance!;
+  }
+}
diff --git a/lib/src/widgets/bottom_navigation_bar_item.dart b/lib/src/widgets/bottom_navigation_bar_item.dart
new file mode 100644
index 0000000..c09297a
--- /dev/null
+++ b/lib/src/widgets/bottom_navigation_bar_item.dart
@@ -0,0 +1,107 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show Color;
+
+import 'framework.dart';
+
+/// An interactive button within either material's [BottomNavigationBar]
+/// or the iOS themed [CupertinoTabBar] with an icon and title.
+///
+/// This class is rarely used in isolation. It is typically embedded in one of
+/// the bottom navigation widgets above.
+///
+/// See also:
+///
+///  * [BottomNavigationBar]
+///  * <https://material.io/design/components/bottom-navigation.html>
+///  * [CupertinoTabBar]
+///  * <https://developer.apple.com/ios/human-interface-guidelines/bars/tab-bars>
+class BottomNavigationBarItem {
+  /// Creates an item that is used with [BottomNavigationBar.items].
+  ///
+  /// The argument [icon] should not be null and the argument [label] should not be null when used in a Material Design's [BottomNavigationBar].
+  const BottomNavigationBarItem({
+    required this.icon,
+    @Deprecated(
+      'Use "label" instead, as it allows for an improved text-scaling experience. '
+      'This feature was deprecated after v1.19.0.'
+    )
+    this.title,
+    this.label,
+    Widget? activeIcon,
+    this.backgroundColor,
+    this.tooltip,
+  }) : activeIcon = activeIcon ?? icon,
+       assert(label == null || title == null),
+       assert(icon != null);
+
+  /// The icon of the item.
+  ///
+  /// Typically the icon is an [Icon] or an [ImageIcon] widget. If another type
+  /// of widget is provided then it should configure itself to match the current
+  /// [IconTheme] size and color.
+  ///
+  /// If [activeIcon] is provided, this will only be displayed when the item is
+  /// not selected.
+  ///
+  /// To make the bottom navigation bar more accessible, consider choosing an
+  /// icon with a stroked and filled version, such as [Icons.cloud] and
+  /// [Icons.cloud_queue]. [icon] should be set to the stroked version and
+  /// [activeIcon] to the filled version.
+  ///
+  /// If a particular icon doesn't have a stroked or filled version, then don't
+  /// pair unrelated icons. Instead, make sure to use a
+  /// [BottomNavigationBarType.shifting].
+  final Widget icon;
+
+  /// An alternative icon displayed when this bottom navigation item is
+  /// selected.
+  ///
+  /// If this icon is not provided, the bottom navigation bar will display
+  /// [icon] in either state.
+  ///
+  /// See also:
+  ///
+  ///  * [BottomNavigationBarItem.icon], for a description of how to pair icons.
+  final Widget activeIcon;
+
+  /// The title of the item. If the title is not provided only the icon will be shown when not used in a Material Design [BottomNavigationBar].
+  ///
+  /// This field is deprecated, use [label] instead.
+  @Deprecated(
+    'Use "label" instead, as it allows for an improved text-scaling experience. '
+    'This feature was deprecated after v1.19.0.'
+  )
+  final Widget? title;
+
+  /// The text label for this [BottomNavigationBarItem].
+  ///
+  /// This will be used to create a [Text] widget to put in the bottom navigation bar.
+  final String? label;
+
+  /// The color of the background radial animation for material [BottomNavigationBar].
+  ///
+  /// If the navigation bar's type is [BottomNavigationBarType.shifting], then
+  /// the entire bar is flooded with the [backgroundColor] when this item is
+  /// tapped. This will override [BottomNavigationBar.backgroundColor].
+  ///
+  /// Not used for [CupertinoTabBar]. Control the invariant bar color directly
+  /// via [CupertinoTabBar.backgroundColor].
+  ///
+  /// See also:
+  ///
+  ///  * [Icon.color] and [ImageIcon.color] to control the foreground color of
+  ///    the icons themselves.
+  final Color? backgroundColor;
+
+  /// The text to display in the tooltip for this [BottomNavigationBarItem], when
+  /// the user long presses the item.
+  ///
+  /// The [Tooltip] will only appear on an item in a Material design [BottomNavigationBar], and
+  /// when the string is not empty.
+  ///
+  /// Defaults to null, in which case the [label] text will be used.
+  final String? tooltip;
+}
diff --git a/lib/src/widgets/color_filter.dart b/lib/src/widgets/color_filter.dart
new file mode 100644
index 0000000..fc22ded
--- /dev/null
+++ b/lib/src/widgets/color_filter.dart
@@ -0,0 +1,62 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+import 'framework.dart';
+
+/// Applies a [ColorFilter] to its child.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=F7Cll22Dno8}
+@immutable
+class ColorFiltered extends SingleChildRenderObjectWidget {
+  /// Creates a widget that applies a [ColorFilter] to its child.
+  ///
+  /// The [colorFilter] must not be null.
+  const ColorFiltered({required this.colorFilter, Widget? child, Key? key})
+      : assert(colorFilter != null),
+        super(key: key, child: child);
+
+  /// The color filter to apply to the child of this widget.
+  final ColorFilter colorFilter;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) => _ColorFilterRenderObject(colorFilter);
+
+  @override
+  void updateRenderObject(BuildContext context, _ColorFilterRenderObject renderObject) {
+    renderObject.colorFilter = colorFilter;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ColorFilter>('colorFilter', colorFilter));
+  }
+}
+
+class _ColorFilterRenderObject extends RenderProxyBox {
+  _ColorFilterRenderObject(this._colorFilter);
+
+  ColorFilter get colorFilter => _colorFilter;
+  ColorFilter _colorFilter;
+  set colorFilter(ColorFilter value) {
+    assert(value != null);
+    if (value != _colorFilter) {
+      _colorFilter = value;
+      markNeedsPaint();
+    }
+  }
+
+  @override
+  bool get alwaysNeedsCompositing => child != null;
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    layer = context.pushColorFilter(offset, colorFilter, super.paint, oldLayer: layer as ColorFilterLayer?);
+  }
+}
diff --git a/lib/src/widgets/constants.dart b/lib/src/widgets/constants.dart
new file mode 100644
index 0000000..49ad496
--- /dev/null
+++ b/lib/src/widgets/constants.dart
@@ -0,0 +1,16 @@
+// Copyright 2014 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.
+
+/// The minimum dimension of any interactive region according to the Material
+/// guidelines.
+///
+/// This is used to avoid small regions that are hard for the user to interact
+/// with. It applies to both dimensions of a region, so a square of size
+/// kMinInteractiveDimension x kMinInteractiveDimension is the smallest
+/// acceptable region that should respond to gestures.
+///
+/// See also:
+///
+///  * [kMinInteractiveDimensionCupertino]
+const double kMinInteractiveDimension = 48.0;
diff --git a/lib/src/widgets/container.dart b/lib/src/widgets/container.dart
new file mode 100644
index 0000000..e81186d
--- /dev/null
+++ b/lib/src/widgets/container.dart
@@ -0,0 +1,479 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart';
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'image.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// BuildContext context;
+
+/// A widget that paints a [Decoration] either before or after its child paints.
+///
+/// [Container] insets its child by the widths of the borders; this widget does
+/// not.
+///
+/// Commonly used with [BoxDecoration].
+///
+/// The [child] is not clipped. To clip a child to the shape of a particular
+/// [ShapeDecoration], consider using a [ClipPath] widget.
+///
+/// {@tool snippet}
+///
+/// This sample shows a radial gradient that draws a moon on a night sky:
+///
+/// ```dart
+/// DecoratedBox(
+///   decoration: BoxDecoration(
+///     gradient: RadialGradient(
+///       center: const Alignment(-0.5, -0.6),
+///       radius: 0.15,
+///       colors: <Color>[
+///         const Color(0xFFEEEEEE),
+///         const Color(0xFF111133),
+///       ],
+///       stops: <double>[0.9, 1.0],
+///     ),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Ink], which paints a [Decoration] on a [Material], allowing
+///    [InkResponse] and [InkWell] splashes to paint over them.
+///  * [DecoratedBoxTransition], the version of this class that animates on the
+///    [decoration] property.
+///  * [Decoration], which you can extend to provide other effects with
+///    [DecoratedBox].
+///  * [CustomPaint], another way to draw custom effects from the widget layer.
+class DecoratedBox extends SingleChildRenderObjectWidget {
+  /// Creates a widget that paints a [Decoration].
+  ///
+  /// The [decoration] and [position] arguments must not be null. By default the
+  /// decoration paints behind the child.
+  const DecoratedBox({
+    Key? key,
+    required this.decoration,
+    this.position = DecorationPosition.background,
+    Widget? child,
+  }) : assert(decoration != null),
+       assert(position != null),
+       super(key: key, child: child);
+
+  /// What decoration to paint.
+  ///
+  /// Commonly a [BoxDecoration].
+  final Decoration decoration;
+
+  /// Whether to paint the box decoration behind or in front of the child.
+  final DecorationPosition position;
+
+  @override
+  RenderDecoratedBox createRenderObject(BuildContext context) {
+    return RenderDecoratedBox(
+      decoration: decoration,
+      position: position,
+      configuration: createLocalImageConfiguration(context),
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderDecoratedBox renderObject) {
+    renderObject
+      ..decoration = decoration
+      ..configuration = createLocalImageConfiguration(context)
+      ..position = position;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    final String label;
+    switch (position) {
+      case DecorationPosition.background:
+        label = 'bg';
+        break;
+      case DecorationPosition.foreground:
+        label = 'fg';
+        break;
+    }
+    properties.add(EnumProperty<DecorationPosition>('position', position, level: DiagnosticLevel.hidden));
+    properties.add(DiagnosticsProperty<Decoration>(label, decoration));
+  }
+}
+
+/// A convenience widget that combines common painting, positioning, and sizing
+/// widgets.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=c1xLMaTUWCY}
+///
+/// A container first surrounds the child with [padding] (inflated by any
+/// borders present in the [decoration]) and then applies additional
+/// [constraints] to the padded extent (incorporating the `width` and `height`
+/// as constraints, if either is non-null). The container is then surrounded by
+/// additional empty space described from the [margin].
+///
+/// During painting, the container first applies the given [transform], then
+/// paints the [decoration] to fill the padded extent, then it paints the child,
+/// and finally paints the [foregroundDecoration], also filling the padded
+/// extent.
+///
+/// Containers with no children try to be as big as possible unless the incoming
+/// constraints are unbounded, in which case they try to be as small as
+/// possible. Containers with children size themselves to their children. The
+/// `width`, `height`, and [constraints] arguments to the constructor override
+/// this.
+///
+/// By default, containers return false for all hit tests. If the [color]
+/// property is specified, the hit testing is handled by [ColoredBox], which
+/// always returns true. If the [decoration] or [foregroundDecoration] properties
+/// are specified, hit testing is handled by [Decoration.hitTest].
+///
+/// ## Layout behavior
+///
+/// _See [BoxConstraints] for an introduction to box layout models._
+///
+/// Since [Container] combines a number of other widgets each with their own
+/// layout behavior, [Container]'s layout behavior is somewhat complicated.
+///
+/// Summary: [Container] tries, in order: to honor [alignment], to size itself
+/// to the [child], to honor the `width`, `height`, and [constraints], to expand
+/// to fit the parent, to be as small as possible.
+///
+/// More specifically:
+///
+/// If the widget has no child, no `height`, no `width`, no [constraints],
+/// and the parent provides unbounded constraints, then [Container] tries to
+/// size as small as possible.
+///
+/// If the widget has no child and no [alignment], but a `height`, `width`, or
+/// [constraints] are provided, then the [Container] tries to be as small as
+/// possible given the combination of those constraints and the parent's
+/// constraints.
+///
+/// If the widget has no child, no `height`, no `width`, no [constraints], and
+/// no [alignment], but the parent provides bounded constraints, then
+/// [Container] expands to fit the constraints provided by the parent.
+///
+/// If the widget has an [alignment], and the parent provides unbounded
+/// constraints, then the [Container] tries to size itself around the child.
+///
+/// If the widget has an [alignment], and the parent provides bounded
+/// constraints, then the [Container] tries to expand to fit the parent, and
+/// then positions the child within itself as per the [alignment].
+///
+/// Otherwise, the widget has a [child] but no `height`, no `width`, no
+/// [constraints], and no [alignment], and the [Container] passes the
+/// constraints from the parent to the child and sizes itself to match the
+/// child.
+///
+/// The [margin] and [padding] properties also affect the layout, as described
+/// in the documentation for those properties. (Their effects merely augment the
+/// rules described above.) The [decoration] can implicitly increase the
+/// [padding] (e.g. borders in a [BoxDecoration] contribute to the [padding]);
+/// see [Decoration.padding].
+///
+/// ## Example
+///
+/// {@tool snippet}
+/// This example shows a 48x48 amber square (placed inside a [Center] widget in
+/// case the parent widget has its own opinions regarding the size that the
+/// [Container] should take), with a margin so that it stays away from
+/// neighboring widgets:
+///
+/// ![An amber colored container with the dimensions of 48 square pixels.](https://flutter.github.io/assets-for-api-docs/assets/widgets/container_a.png)
+///
+/// ```dart
+/// Center(
+///   child: Container(
+///     margin: const EdgeInsets.all(10.0),
+///     color: Colors.amber[600],
+///     width: 48.0,
+///     height: 48.0,
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+///
+/// This example shows how to use many of the features of [Container] at once.
+/// The [constraints] are set to fit the font size plus ample headroom
+/// vertically, while expanding horizontally to fit the parent. The [padding] is
+/// used to make sure there is space between the contents and the text. The
+/// [color] makes the box blue. The [alignment] causes the [child] to be
+/// centered in the box. Finally, the [transform] applies a slight rotation to the
+/// entire contraption to complete the effect.
+///
+/// ![A blue rectangular container with 'Hello World' in the center, rotated
+/// slightly in the z axis.](https://flutter.github.io/assets-for-api-docs/assets/widgets/container_b.png)
+///
+/// ```dart
+/// Container(
+///   constraints: BoxConstraints.expand(
+///     height: Theme.of(context).textTheme.headline4.fontSize * 1.1 + 200.0,
+///   ),
+///   padding: const EdgeInsets.all(8.0),
+///   color: Colors.blue[600],
+///   alignment: Alignment.center,
+///   child: Text('Hello World',
+///     style: Theme.of(context)
+///         .textTheme
+///         .headline4
+///         .copyWith(color: Colors.white)),
+///   transform: Matrix4.rotationZ(0.1),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AnimatedContainer], a variant that smoothly animates the properties when
+///    they change.
+///  * [Border], which has a sample which uses [Container] heavily.
+///  * [Ink], which paints a [Decoration] on a [Material], allowing
+///    [InkResponse] and [InkWell] splashes to paint over them.
+///  * Cookbook: [Animate the properties of a container](https://flutter.dev/docs/cookbook/animation/animated-container)
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Container extends StatelessWidget {
+  /// Creates a widget that combines common painting, positioning, and sizing widgets.
+  ///
+  /// The `height` and `width` values include the padding.
+  ///
+  /// The `color` and `decoration` arguments cannot both be supplied, since
+  /// it would potentially result in the decoration drawing over the background
+  /// color. To supply a decoration with a color, use `decoration:
+  /// BoxDecoration(color: color)`.
+  Container({
+    Key? key,
+    this.alignment,
+    this.padding,
+    this.color,
+    this.decoration,
+    this.foregroundDecoration,
+    double? width,
+    double? height,
+    BoxConstraints? constraints,
+    this.margin,
+    this.transform,
+    this.transformAlignment,
+    this.child,
+    this.clipBehavior = Clip.none,
+  }) : assert(margin == null || margin.isNonNegative),
+       assert(padding == null || padding.isNonNegative),
+       assert(decoration == null || decoration.debugAssertIsValid()),
+       assert(constraints == null || constraints.debugAssertIsValid()),
+       assert(clipBehavior != null),
+       assert(decoration != null || clipBehavior == Clip.none),
+       assert(color == null || decoration == null,
+         'Cannot provide both a color and a decoration\n'
+         'To provide both, use "decoration: BoxDecoration(color: color)".'
+       ),
+       constraints =
+        (width != null || height != null)
+          ? constraints?.tighten(width: width, height: height)
+            ?? BoxConstraints.tightFor(width: width, height: height)
+          : constraints,
+       super(key: key);
+
+  /// The [child] contained by the container.
+  ///
+  /// If null, and if the [constraints] are unbounded or also null, the
+  /// container will expand to fill all available space in its parent, unless
+  /// the parent provides unbounded constraints, in which case the container
+  /// will attempt to be as small as possible.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// Align the [child] within the container.
+  ///
+  /// If non-null, the container will expand to fill its parent and position its
+  /// child within itself according to the given value. If the incoming
+  /// constraints are unbounded, then the child will be shrink-wrapped instead.
+  ///
+  /// Ignored if [child] is null.
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], a class with convenient constants typically used to
+  ///    specify an [AlignmentGeometry].
+  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
+  ///    relative to text direction.
+  final AlignmentGeometry? alignment;
+
+  /// Empty space to inscribe inside the [decoration]. The [child], if any, is
+  /// placed inside this padding.
+  ///
+  /// This padding is in addition to any padding inherent in the [decoration];
+  /// see [Decoration.padding].
+  final EdgeInsetsGeometry? padding;
+
+  /// The color to paint behind the [child].
+  ///
+  /// This property should be preferred when the background is a simple color.
+  /// For other cases, such as gradients or images, use the [decoration]
+  /// property.
+  ///
+  /// If the [decoration] is used, this property must be null. A background
+  /// color may still be painted by the [decoration] even if this property is
+  /// null.
+  final Color? color;
+
+  /// The decoration to paint behind the [child].
+  ///
+  /// Use the [color] property to specify a simple solid color.
+  ///
+  /// The [child] is not clipped to the decoration. To clip a child to the shape
+  /// of a particular [ShapeDecoration], consider using a [ClipPath] widget.
+  final Decoration? decoration;
+
+  /// The decoration to paint in front of the [child].
+  final Decoration? foregroundDecoration;
+
+  /// Additional constraints to apply to the child.
+  ///
+  /// The constructor `width` and `height` arguments are combined with the
+  /// `constraints` argument to set this property.
+  ///
+  /// The [padding] goes inside the constraints.
+  final BoxConstraints? constraints;
+
+  /// Empty space to surround the [decoration] and [child].
+  final EdgeInsetsGeometry? margin;
+
+  /// The transformation matrix to apply before painting the container.
+  final Matrix4? transform;
+
+  /// The alignment of the origin, relative to the size of the container, if [transform] is specified.
+  ///
+  /// When [transform] is null, the value of this property is ignored.
+  ///
+  /// See also:
+  ///
+  ///  * [Transform.alignment], which is set by this property.
+  final AlignmentGeometry? transformAlignment;
+
+  /// The clip behavior when [Container.decoration] is not null.
+  ///
+  /// Defaults to [Clip.none]. Must be [Clip.none] if [decoration] is null.
+  ///
+  /// If a clip is to be applied, the [Decoration.getClipPath] method
+  /// for the provided decoration must return a clip path. (This is not
+  /// supported by all decorations; the default implementation of that
+  /// method throws an [UnsupportedError].)
+  final Clip clipBehavior;
+
+  EdgeInsetsGeometry? get _paddingIncludingDecoration {
+    if (decoration == null || decoration!.padding == null)
+      return padding;
+    final EdgeInsetsGeometry? decorationPadding = decoration!.padding;
+    if (padding == null)
+      return decorationPadding;
+    return padding!.add(decorationPadding!);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    Widget? current = child;
+
+    if (child == null && (constraints == null || !constraints!.isTight)) {
+      current = LimitedBox(
+        maxWidth: 0.0,
+        maxHeight: 0.0,
+        child: ConstrainedBox(constraints: const BoxConstraints.expand()),
+      );
+    }
+
+    if (alignment != null)
+      current = Align(alignment: alignment!, child: current);
+
+    final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
+    if (effectivePadding != null)
+      current = Padding(padding: effectivePadding, child: current);
+
+    if (color != null)
+      current = ColoredBox(color: color!, child: current);
+
+    if (clipBehavior != Clip.none) {
+      assert(decoration != null);
+      current = ClipPath(
+        clipper: _DecorationClipper(
+          textDirection: Directionality.maybeOf(context),
+          decoration: decoration!,
+        ),
+        clipBehavior: clipBehavior,
+        child: current,
+      );
+    }
+
+    if (decoration != null)
+      current = DecoratedBox(decoration: decoration!, child: current);
+
+    if (foregroundDecoration != null) {
+      current = DecoratedBox(
+        decoration: foregroundDecoration!,
+        position: DecorationPosition.foreground,
+        child: current,
+      );
+    }
+
+    if (constraints != null)
+      current = ConstrainedBox(constraints: constraints!, child: current);
+
+    if (margin != null)
+      current = Padding(padding: margin!, child: current);
+
+    if (transform != null)
+      current = Transform(transform: transform!, child: current, alignment: transformAlignment);
+
+    return current!;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
+    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.none));
+    if (color != null)
+      properties.add(DiagnosticsProperty<Color>('bg', color));
+    else
+      properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
+    properties.add(DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
+    properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
+    properties.add(ObjectFlagProperty<Matrix4>.has('transform', transform));
+  }
+}
+
+/// A clipper that uses [Decoration.getClipPath] to clip.
+class _DecorationClipper extends CustomClipper<Path> {
+  _DecorationClipper({
+    TextDirection? textDirection,
+    required this.decoration
+  }) : assert(decoration != null),
+       textDirection = textDirection ?? TextDirection.ltr;
+
+  final TextDirection textDirection;
+  final Decoration decoration;
+
+  @override
+  Path getClip(Size size) {
+    return decoration.getClipPath(Offset.zero & size, textDirection);
+  }
+
+  @override
+  bool shouldReclip(_DecorationClipper oldClipper) {
+    return oldClipper.decoration != decoration
+        || oldClipper.textDirection != textDirection;
+  }
+}
diff --git a/lib/src/widgets/debug.dart b/lib/src/widgets/debug.dart
new file mode 100644
index 0000000..3fd1b10
--- /dev/null
+++ b/lib/src/widgets/debug.dart
@@ -0,0 +1,385 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+import 'dart:developer' show Timeline; // to disambiguate reference in dartdocs below
+
+import 'package:flute/foundation.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'localizations.dart';
+import 'media_query.dart';
+import 'table.dart';
+
+// Any changes to this file should be reflected in the debugAssertAllWidgetVarsUnset()
+// function below.
+
+/// Log the dirty widgets that are built each frame.
+///
+/// Combined with [debugPrintBuildScope] or [debugPrintBeginFrameBanner], this
+/// allows you to distinguish builds triggered by the initial mounting of a
+/// widget tree (e.g. in a call to [runApp]) from the regular builds triggered
+/// by the pipeline.
+///
+/// Combined with [debugPrintScheduleBuildForStacks], this lets you watch a
+/// widget's dirty/clean lifecycle.
+///
+/// To get similar information but showing it on the timeline available from the
+/// Observatory rather than getting it in the console (where it can be
+/// overwhelming), consider [debugProfileBuildsEnabled].
+///
+/// See also:
+///
+///  * [WidgetsBinding.drawFrame], which pumps the build and rendering pipeline
+///    to generate a frame.
+bool debugPrintRebuildDirtyWidgets = false;
+
+/// Signature for [debugOnRebuildDirtyWidget] implementations.
+typedef RebuildDirtyWidgetCallback = void Function(Element e, bool builtOnce);
+
+/// Callback invoked for every dirty widget built each frame.
+///
+/// This callback is only invoked in debug builds.
+///
+/// See also:
+///
+///  * [debugPrintRebuildDirtyWidgets], which does something similar but logs
+///    to the console instead of invoking a callback.
+///  * [debugOnProfilePaint], which does something similar for [RenderObject]
+///    painting.
+///  * [WidgetInspectorService], which uses the [debugOnRebuildDirtyWidget]
+///    callback to generate aggregate profile statistics describing which widget
+///    rebuilds occurred when the
+///    `ext.flutter.inspector.trackRebuildDirtyWidgets` service extension is
+///    enabled.
+RebuildDirtyWidgetCallback? debugOnRebuildDirtyWidget;
+
+/// Log all calls to [BuildOwner.buildScope].
+///
+/// Combined with [debugPrintScheduleBuildForStacks], this allows you to track
+/// when a [State.setState] call gets serviced.
+///
+/// Combined with [debugPrintRebuildDirtyWidgets] or
+/// [debugPrintBeginFrameBanner], this allows you to distinguish builds
+/// triggered by the initial mounting of a widget tree (e.g. in a call to
+/// [runApp]) from the regular builds triggered by the pipeline.
+///
+/// See also:
+///
+///  * [WidgetsBinding.drawFrame], which pumps the build and rendering pipeline
+///    to generate a frame.
+bool debugPrintBuildScope = false;
+
+/// Log the call stacks that mark widgets as needing to be rebuilt.
+///
+/// This is called whenever [BuildOwner.scheduleBuildFor] adds an element to the
+/// dirty list. Typically this is as a result of [Element.markNeedsBuild] being
+/// called, which itself is usually a result of [State.setState] being called.
+///
+/// To see when a widget is rebuilt, see [debugPrintRebuildDirtyWidgets].
+///
+/// To see when the dirty list is flushed, see [debugPrintBuildScope].
+///
+/// To see when a frame is scheduled, see [debugPrintScheduleFrameStacks].
+bool debugPrintScheduleBuildForStacks = false;
+
+/// Log when widgets with global keys are deactivated and log when they are
+/// reactivated (retaken).
+///
+/// This can help track down framework bugs relating to the [GlobalKey] logic.
+bool debugPrintGlobalKeyedWidgetLifecycle = false;
+
+/// Adds [Timeline] events for every Widget built.
+///
+/// For details on how to use [Timeline] events in the Dart Observatory to
+/// optimize your app, see https://flutter.dev/docs/testing/debugging#tracing-any-dart-code-performance
+/// and https://fuchsia.googlesource.com/topaz/+/master/shell/docs/performance.md
+///
+/// See also:
+///
+///  * [debugPrintRebuildDirtyWidgets], which does something similar but
+///    reporting the builds to the console.
+///  * [debugProfileLayoutsEnabled], which does something similar for layout,
+///    and [debugPrintLayouts], its console equivalent.
+///  * [debugProfilePaintsEnabled], which does something similar for painting.
+bool debugProfileBuildsEnabled = false;
+
+/// Show banners for deprecated widgets.
+bool debugHighlightDeprecatedWidgets = false;
+
+Key? _firstNonUniqueKey(Iterable<Widget> widgets) {
+  final Set<Key> keySet = HashSet<Key>();
+  for (final Widget widget in widgets) {
+    assert(widget != null);
+    if (widget.key == null)
+      continue;
+    if (!keySet.add(widget.key!))
+      return widget.key;
+  }
+  return null;
+}
+
+/// Asserts if the given child list contains any duplicate non-null keys.
+///
+/// To invoke this function, use the following pattern, typically in the
+/// relevant Widget's constructor:
+///
+/// ```dart
+/// assert(!debugChildrenHaveDuplicateKeys(this, children));
+/// ```
+///
+/// For a version of this function that can be used in contexts where
+/// the list of items does not have a particular parent, see
+/// [debugItemsHaveDuplicateKeys].
+///
+/// Does nothing if asserts are disabled. Always returns true.
+bool debugChildrenHaveDuplicateKeys(Widget parent, Iterable<Widget> children) {
+  assert(() {
+    final Key? nonUniqueKey = _firstNonUniqueKey(children);
+    if (nonUniqueKey != null) {
+      throw FlutterError(
+        'Duplicate keys found.\n'
+        'If multiple keyed nodes exist as children of another node, they must have unique keys.\n'
+        '$parent has multiple children with key $nonUniqueKey.'
+      );
+    }
+    return true;
+  }());
+  return false;
+}
+
+/// Asserts if the given list of items contains any duplicate non-null keys.
+///
+/// To invoke this function, use the following pattern:
+///
+/// ```dart
+/// assert(!debugItemsHaveDuplicateKeys(items));
+/// ```
+///
+/// For a version of this function specifically intended for parents
+/// checking their children lists, see [debugChildrenHaveDuplicateKeys].
+///
+/// Does nothing if asserts are disabled. Always returns true.
+bool debugItemsHaveDuplicateKeys(Iterable<Widget> items) {
+  assert(() {
+    final Key? nonUniqueKey = _firstNonUniqueKey(items);
+    if (nonUniqueKey != null)
+      throw FlutterError('Duplicate key found: $nonUniqueKey.');
+    return true;
+  }());
+  return false;
+}
+
+/// Asserts that the given context has a [Table] ancestor.
+///
+/// Used by [TableRowInkWell] to make sure that it is only used in an appropriate context.
+///
+/// To invoke this function, use the following pattern, typically in the
+/// relevant Widget's build method:
+///
+/// ```dart
+/// assert(debugCheckHasTable(context));
+/// ```
+///
+/// Does nothing if asserts are disabled. Always returns true.
+bool debugCheckHasTable(BuildContext context) {
+  assert(() {
+    if (context.widget is! Table && context.findAncestorWidgetOfExactType<Table>() == null) {
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('No Table widget found.'),
+        ErrorDescription('${context.widget.runtimeType} widgets require a Table widget ancestor.'),
+        context.describeWidget('The specific widget that could not find a Table ancestor was'),
+        context.describeOwnershipChain('The ownership chain for the affected widget is'),
+      ]);
+    }
+    return true;
+  }());
+  return true;
+}
+
+/// Asserts that the given context has a [MediaQuery] ancestor.
+///
+/// Used by various widgets to make sure that they are only used in an
+/// appropriate context.
+///
+/// To invoke this function, use the following pattern, typically in the
+/// relevant Widget's build method:
+///
+/// ```dart
+/// assert(debugCheckHasMediaQuery(context));
+/// ```
+///
+/// Does nothing if asserts are disabled. Always returns true.
+bool debugCheckHasMediaQuery(BuildContext context) {
+  assert(() {
+    if (context.widget is! MediaQuery && context.findAncestorWidgetOfExactType<MediaQuery>() == null) {
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('No MediaQuery widget ancestor found.'),
+        ErrorDescription('${context.widget.runtimeType} widgets require a MediaQuery widget ancestor.'),
+        context.describeWidget('The specific widget that could not find a MediaQuery ancestor was'),
+        context.describeOwnershipChain('The ownership chain for the affected widget is'),
+        ErrorHint(
+          'No MediaQuery ancestor could be found starting from the context '
+          'that was passed to MediaQuery.of(). This can happen because you '
+          'have not added a WidgetsApp, CupertinoApp, or MaterialApp widget '
+          '(those widgets introduce a MediaQuery), or it can happen if the '
+          'context you use comes from a widget above those widgets.'
+        ),
+      ]);
+    }
+    return true;
+  }());
+  return true;
+}
+
+/// Asserts that the given context has a [Directionality] ancestor.
+///
+/// Used by various widgets to make sure that they are only used in an
+/// appropriate context.
+///
+/// To invoke this function, use the following pattern, typically in the
+/// relevant Widget's build method:
+///
+/// ```dart
+/// assert(debugCheckHasDirectionality(context));
+/// ```
+///
+/// To improve the error messages you can add some extra color using the
+/// named arguments.
+///
+///  * why: explain why the direction is needed, for example "to resolve
+///    the 'alignment' argument". Should be an adverb phrase describing why.
+///  * hint: explain why this might be happening, for example "The default
+///    value of the 'alignment' argument of the $runtimeType widget is an
+///    AlignmentDirectional value.". Should be a fully punctuated sentence.
+///  * alternative: provide additional advice specific to the situation,
+///    especially an alternative to providing a Directionality ancestor.
+///    For example, "Alternatively, consider specifying the 'textDirection'
+///    argument.". Should be a fully punctuated sentence.
+///
+/// Each one can be null, in which case it is skipped (this is the default).
+/// If they are non-null, they are included in the order above, interspersed
+/// with the more generic advice regarding [Directionality].
+///
+/// Does nothing if asserts are disabled. Always returns true.
+bool debugCheckHasDirectionality(BuildContext context, { String? why, String? hint, String? alternative }) {
+  assert(() {
+    if (context.widget is! Directionality && context.findAncestorWidgetOfExactType<Directionality>() == null) {
+      why = why == null ? '' : ' $why';
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('No Directionality widget found.'),
+        ErrorDescription('${context.widget.runtimeType} widgets require a Directionality widget ancestor$why.\n'),
+        if (hint != null)
+          ErrorHint(hint),
+        context.describeWidget('The specific widget that could not find a Directionality ancestor was'),
+        context.describeOwnershipChain('The ownership chain for the affected widget is'),
+        ErrorHint(
+          'Typically, the Directionality widget is introduced by the MaterialApp '
+          'or WidgetsApp widget at the top of your application widget tree. It '
+          'determines the ambient reading direction and is used, for example, to '
+          'determine how to lay out text, how to interpret "start" and "end" '
+          'values, and to resolve EdgeInsetsDirectional, '
+          'AlignmentDirectional, and other *Directional objects.'
+        ),
+        if (alternative != null)
+          ErrorHint(alternative),
+      ]);
+    }
+    return true;
+  }());
+  return true;
+}
+
+/// Asserts that the `built` widget is not null.
+///
+/// Used when the given `widget` calls a builder function to check that the
+/// function returned a non-null value, as typically required.
+///
+/// Does nothing when asserts are disabled.
+void debugWidgetBuilderValue(Widget widget, Widget? built) {
+  assert(() {
+    if (built == null) {
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('A build function returned null.'),
+        DiagnosticsProperty<Widget>('The offending widget is', widget, style: DiagnosticsTreeStyle.errorProperty),
+        ErrorDescription('Build functions must never return null.'),
+        ErrorHint(
+          'To return an empty space that causes the building widget to fill available room, return "Container()". '
+          'To return an empty space that takes as little room as possible, return "Container(width: 0.0, height: 0.0)".'
+        ),
+      ]);
+    }
+    if (widget == built) {
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('A build function returned context.widget.'),
+        DiagnosticsProperty<Widget>('The offending widget is', widget, style: DiagnosticsTreeStyle.errorProperty),
+        ErrorDescription(
+          'Build functions must never return their BuildContext parameter\'s widget or a child that contains "context.widget". '
+          'Doing so introduces a loop in the widget tree that can cause the app to crash.'
+        ),
+      ]);
+    }
+    return true;
+  }());
+}
+
+/// Asserts that the given context has a [Localizations] ancestor that contains
+/// a [WidgetsLocalizations] delegate.
+///
+/// To call this function, use the following pattern, typically in the
+/// relevant Widget's build method:
+///
+/// ```dart
+/// assert(debugCheckHasWidgetsLocalizations(context));
+/// ```
+///
+/// Does nothing if asserts are disabled. Always returns true.
+bool debugCheckHasWidgetsLocalizations(BuildContext context) {
+  assert(() {
+    if (Localizations.of<WidgetsLocalizations>(context, WidgetsLocalizations) == null) {
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('No WidgetsLocalizations found.'),
+        ErrorDescription(
+          '${context.widget.runtimeType} widgets require WidgetsLocalizations '
+          'to be provided by a Localizations widget ancestor.'
+        ),
+        ErrorDescription(
+          'The widgets library uses Localizations to generate messages, '
+          'labels, and abbreviations.'
+        ),
+        ErrorHint(
+          'To introduce a WidgetsLocalizations, either use a '
+          'WidgetsApp at the root of your application to include them '
+          'automatically, or add a Localization widget with a '
+          'WidgetsLocalizations delegate.'
+        ),
+        ...context.describeMissingAncestor(expectedAncestorType: WidgetsLocalizations)
+      ]);
+    }
+    return true;
+  }());
+  return true;
+}
+
+/// Returns true if none of the widget library debug variables have been changed.
+///
+/// This function is used by the test framework to ensure that debug variables
+/// haven't been inadvertently changed.
+///
+/// See [the widgets library](widgets/widgets-library.html) for a complete list.
+bool debugAssertAllWidgetVarsUnset(String reason) {
+  assert(() {
+    if (debugPrintRebuildDirtyWidgets ||
+        debugPrintBuildScope ||
+        debugPrintScheduleBuildForStacks ||
+        debugPrintGlobalKeyedWidgetLifecycle ||
+        debugProfileBuildsEnabled ||
+        debugHighlightDeprecatedWidgets) {
+      throw FlutterError(reason);
+    }
+    return true;
+  }());
+  return true;
+}
diff --git a/lib/src/widgets/dismissible.dart b/lib/src/widgets/dismissible.dart
new file mode 100644
index 0000000..151574a
--- /dev/null
+++ b/lib/src/widgets/dismissible.dart
@@ -0,0 +1,628 @@
+// Copyright 2014 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.
+
+import 'package:flute/gestures.dart';
+
+import 'automatic_keep_alive.dart';
+import 'basic.dart';
+import 'debug.dart';
+import 'framework.dart';
+import 'gesture_detector.dart';
+import 'ticker_provider.dart';
+import 'transitions.dart';
+
+const Curve _kResizeTimeCurve = Interval(0.4, 1.0, curve: Curves.ease);
+const double _kMinFlingVelocity = 700.0;
+const double _kMinFlingVelocityDelta = 400.0;
+const double _kFlingVelocityScale = 1.0 / 300.0;
+const double _kDismissThreshold = 0.4;
+
+/// Signature used by [Dismissible] to indicate that it has been dismissed in
+/// the given `direction`.
+///
+/// Used by [Dismissible.onDismissed].
+typedef DismissDirectionCallback = void Function(DismissDirection direction);
+
+/// Signature used by [Dismissible] to give the application an opportunity to
+/// confirm or veto a dismiss gesture.
+///
+/// Used by [Dismissible.confirmDismiss].
+typedef ConfirmDismissCallback = Future<bool?> Function(DismissDirection direction);
+
+/// The direction in which a [Dismissible] can be dismissed.
+enum DismissDirection {
+  /// The [Dismissible] can be dismissed by dragging either up or down.
+  vertical,
+
+  /// The [Dismissible] can be dismissed by dragging either left or right.
+  horizontal,
+
+  /// The [Dismissible] can be dismissed by dragging in the reverse of the
+  /// reading direction (e.g., from right to left in left-to-right languages).
+  endToStart,
+
+  /// The [Dismissible] can be dismissed by dragging in the reading direction
+  /// (e.g., from left to right in left-to-right languages).
+  startToEnd,
+
+  /// The [Dismissible] can be dismissed by dragging up only.
+  up,
+
+  /// The [Dismissible] can be dismissed by dragging down only.
+  down,
+
+  /// The [Dismissible] cannot be dismissed by dragging.
+  none
+}
+
+/// A widget that can be dismissed by dragging in the indicated [direction].
+///
+/// Dragging or flinging this widget in the [DismissDirection] causes the child
+/// to slide out of view. Following the slide animation, if [resizeDuration] is
+/// non-null, the Dismissible widget animates its height (or width, whichever is
+/// perpendicular to the dismiss direction) to zero over the [resizeDuration].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=iEMgjrfuc58}
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+///
+/// This sample shows how you can use the [Dismissible] widget to
+/// remove list items using swipe gestures. Swipe any of the list
+/// tiles to the left or right to dismiss them from the [ListView].
+///
+/// ```dart
+/// List<int> items = List<int>.generate(100, (index) => index);
+///
+/// Widget build(BuildContext context) {
+///   return ListView.builder(
+///     itemCount: items.length,
+///     padding: const EdgeInsets.symmetric(vertical: 16),
+///     itemBuilder: (BuildContext context, int index) {
+///       return Dismissible(
+///         child: ListTile(
+///           title: Text(
+///             'Item ${items[index]}',
+///           ),
+///         ),
+///         background: Container(
+///           color: Colors.green,
+///         ),
+///         key: ValueKey(items[index]),
+///         onDismissed: (DismissDirection direction) {
+///           setState(() {
+///             items.remove(index);
+///           });
+///         },
+///       );
+///     },
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// Backgrounds can be used to implement the "leave-behind" idiom. If a background
+/// is specified it is stacked behind the Dismissible's child and is exposed when
+/// the child moves.
+///
+/// The widget calls the [onDismissed] callback either after its size has
+/// collapsed to zero (if [resizeDuration] is non-null) or immediately after
+/// the slide animation (if [resizeDuration] is null). If the Dismissible is a
+/// list item, it must have a key that distinguishes it from the other items and
+/// its [onDismissed] callback must remove the item from the list.
+class Dismissible extends StatefulWidget {
+  /// Creates a widget that can be dismissed.
+  ///
+  /// The [key] argument must not be null because [Dismissible]s are commonly
+  /// used in lists and removed from the list when dismissed. Without keys, the
+  /// default behavior is to sync widgets based on their index in the list,
+  /// which means the item after the dismissed item would be synced with the
+  /// state of the dismissed item. Using keys causes the widgets to sync
+  /// according to their keys and avoids this pitfall.
+  const Dismissible({
+    required Key key,
+    required this.child,
+    this.background,
+    this.secondaryBackground,
+    this.confirmDismiss,
+    this.onResize,
+    this.onDismissed,
+    this.direction = DismissDirection.horizontal,
+    this.resizeDuration = const Duration(milliseconds: 300),
+    this.dismissThresholds = const <DismissDirection, double>{},
+    this.movementDuration = const Duration(milliseconds: 200),
+    this.crossAxisEndOffset = 0.0,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.behavior = HitTestBehavior.opaque,
+  }) : assert(key != null),
+       assert(secondaryBackground == null || background != null),
+       assert(dragStartBehavior != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// A widget that is stacked behind the child. If secondaryBackground is also
+  /// specified then this widget only appears when the child has been dragged
+  /// down or to the right.
+  final Widget? background;
+
+  /// A widget that is stacked behind the child and is exposed when the child
+  /// has been dragged up or to the left. It may only be specified when background
+  /// has also been specified.
+  final Widget? secondaryBackground;
+
+  /// Gives the app an opportunity to confirm or veto a pending dismissal.
+  ///
+  /// If the returned Future<bool> completes true, then this widget will be
+  /// dismissed, otherwise it will be moved back to its original location.
+  ///
+  /// If the returned Future<bool?> completes to false or null the [onResize]
+  /// and [onDismissed] callbacks will not run.
+  final ConfirmDismissCallback? confirmDismiss;
+
+  /// Called when the widget changes size (i.e., when contracting before being dismissed).
+  final VoidCallback? onResize;
+
+  /// Called when the widget has been dismissed, after finishing resizing.
+  final DismissDirectionCallback? onDismissed;
+
+  /// The direction in which the widget can be dismissed.
+  final DismissDirection direction;
+
+  /// The amount of time the widget will spend contracting before [onDismissed] is called.
+  ///
+  /// If null, the widget will not contract and [onDismissed] will be called
+  /// immediately after the widget is dismissed.
+  final Duration? resizeDuration;
+
+  /// The offset threshold the item has to be dragged in order to be considered
+  /// dismissed.
+  ///
+  /// Represented as a fraction, e.g. if it is 0.4 (the default), then the item
+  /// has to be dragged at least 40% towards one direction to be considered
+  /// dismissed. Clients can define different thresholds for each dismiss
+  /// direction.
+  ///
+  /// Flinging is treated as being equivalent to dragging almost to 1.0, so
+  /// flinging can dismiss an item past any threshold less than 1.0.
+  ///
+  /// Setting a threshold of 1.0 (or greater) prevents a drag in the given
+  /// [DismissDirection] even if it would be allowed by the [direction]
+  /// property.
+  ///
+  /// See also:
+  ///
+  ///  * [direction], which controls the directions in which the items can
+  ///    be dismissed.
+  final Map<DismissDirection, double> dismissThresholds;
+
+  /// Defines the duration for card to dismiss or to come back to original position if not dismissed.
+  final Duration movementDuration;
+
+  /// Defines the end offset across the main axis after the card is dismissed.
+  ///
+  /// If non-zero value is given then widget moves in cross direction depending on whether
+  /// it is positive or negative.
+  final double crossAxisEndOffset;
+
+  /// Determines the way that drag start behavior is handled.
+  ///
+  /// If set to [DragStartBehavior.start], the drag gesture used to dismiss a
+  /// dismissible will begin upon the detection of a drag gesture. If set to
+  /// [DragStartBehavior.down] it will begin when a down event is first detected.
+  ///
+  /// In general, setting this to [DragStartBehavior.start] will make drag
+  /// animation smoother and setting it to [DragStartBehavior.down] will make
+  /// drag behavior feel slightly more reactive.
+  ///
+  /// By default, the drag start behavior is [DragStartBehavior.start].
+  ///
+  /// See also:
+  ///
+  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
+  final DragStartBehavior dragStartBehavior;
+
+  /// How to behave during hit tests.
+  ///
+  /// This defaults to [HitTestBehavior.opaque].
+  final HitTestBehavior behavior;
+
+  @override
+  _DismissibleState createState() => _DismissibleState();
+}
+
+class _DismissibleClipper extends CustomClipper<Rect> {
+  _DismissibleClipper({
+    required this.axis,
+    required this.moveAnimation,
+  }) : assert(axis != null),
+       assert(moveAnimation != null),
+       super(reclip: moveAnimation);
+
+  final Axis axis;
+  final Animation<Offset> moveAnimation;
+
+  @override
+  Rect getClip(Size size) {
+    assert(axis != null);
+    switch (axis) {
+      case Axis.horizontal:
+        final double offset = moveAnimation.value.dx * size.width;
+        if (offset < 0)
+          return Rect.fromLTRB(size.width + offset, 0.0, size.width, size.height);
+        return Rect.fromLTRB(0.0, 0.0, offset, size.height);
+      case Axis.vertical:
+        final double offset = moveAnimation.value.dy * size.height;
+        if (offset < 0)
+          return Rect.fromLTRB(0.0, size.height + offset, size.width, size.height);
+        return Rect.fromLTRB(0.0, 0.0, size.width, offset);
+    }
+  }
+
+  @override
+  Rect getApproximateClipRect(Size size) => getClip(size);
+
+  @override
+  bool shouldReclip(_DismissibleClipper oldClipper) {
+    return oldClipper.axis != axis
+        || oldClipper.moveAnimation.value != moveAnimation.value;
+  }
+}
+
+enum _FlingGestureKind { none, forward, reverse }
+
+class _DismissibleState extends State<Dismissible> with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
+  @override
+  void initState() {
+    super.initState();
+    _moveController = AnimationController(duration: widget.movementDuration, vsync: this)
+      ..addStatusListener(_handleDismissStatusChanged);
+    _updateMoveAnimation();
+  }
+
+  AnimationController? _moveController;
+  late Animation<Offset> _moveAnimation;
+
+  AnimationController? _resizeController;
+  Animation<double>? _resizeAnimation;
+
+  double _dragExtent = 0.0;
+  bool _dragUnderway = false;
+  Size? _sizePriorToCollapse;
+
+  @override
+  bool get wantKeepAlive => _moveController?.isAnimating == true || _resizeController?.isAnimating == true;
+
+  @override
+  void dispose() {
+    _moveController!.dispose();
+    _resizeController?.dispose();
+    super.dispose();
+  }
+
+  bool get _directionIsXAxis {
+    return widget.direction == DismissDirection.horizontal
+        || widget.direction == DismissDirection.endToStart
+        || widget.direction == DismissDirection.startToEnd;
+  }
+
+  DismissDirection _extentToDirection(double extent) {
+    if (extent == 0.0)
+      return DismissDirection.none;
+    if (_directionIsXAxis) {
+      switch (Directionality.of(context)) {
+        case TextDirection.rtl:
+          return extent < 0 ? DismissDirection.startToEnd : DismissDirection.endToStart;
+        case TextDirection.ltr:
+          return extent > 0 ? DismissDirection.startToEnd : DismissDirection.endToStart;
+      }
+    }
+    return extent > 0 ? DismissDirection.down : DismissDirection.up;
+  }
+
+  DismissDirection get _dismissDirection => _extentToDirection(_dragExtent);
+
+  bool get _isActive {
+    return _dragUnderway || _moveController!.isAnimating;
+  }
+
+  double get _overallDragAxisExtent {
+    final Size size = context.size!;
+    return _directionIsXAxis ? size.width : size.height;
+  }
+
+  void _handleDragStart(DragStartDetails details) {
+    _dragUnderway = true;
+    if (_moveController!.isAnimating) {
+      _dragExtent = _moveController!.value * _overallDragAxisExtent * _dragExtent.sign;
+      _moveController!.stop();
+    } else {
+      _dragExtent = 0.0;
+      _moveController!.value = 0.0;
+    }
+    setState(() {
+      _updateMoveAnimation();
+    });
+  }
+
+  void _handleDragUpdate(DragUpdateDetails details) {
+    if (!_isActive || _moveController!.isAnimating)
+      return;
+
+    final double delta = details.primaryDelta!;
+    final double oldDragExtent = _dragExtent;
+    switch (widget.direction) {
+      case DismissDirection.horizontal:
+      case DismissDirection.vertical:
+        _dragExtent += delta;
+        break;
+
+      case DismissDirection.up:
+        if (_dragExtent + delta < 0)
+          _dragExtent += delta;
+        break;
+
+      case DismissDirection.down:
+        if (_dragExtent + delta > 0)
+          _dragExtent += delta;
+        break;
+
+      case DismissDirection.endToStart:
+        switch (Directionality.of(context)) {
+          case TextDirection.rtl:
+            if (_dragExtent + delta > 0)
+              _dragExtent += delta;
+            break;
+          case TextDirection.ltr:
+            if (_dragExtent + delta < 0)
+              _dragExtent += delta;
+            break;
+        }
+        break;
+
+      case DismissDirection.startToEnd:
+        switch (Directionality.of(context)) {
+          case TextDirection.rtl:
+            if (_dragExtent + delta < 0)
+              _dragExtent += delta;
+            break;
+          case TextDirection.ltr:
+            if (_dragExtent + delta > 0)
+              _dragExtent += delta;
+            break;
+        }
+        break;
+
+      case DismissDirection.none:
+        _dragExtent = 0;
+        break;
+    }
+    if (oldDragExtent.sign != _dragExtent.sign) {
+      setState(() {
+        _updateMoveAnimation();
+      });
+    }
+    if (!_moveController!.isAnimating) {
+      _moveController!.value = _dragExtent.abs() / _overallDragAxisExtent;
+    }
+  }
+
+  void _updateMoveAnimation() {
+    final double end = _dragExtent.sign;
+    _moveAnimation = _moveController!.drive(
+      Tween<Offset>(
+        begin: Offset.zero,
+        end: _directionIsXAxis
+            ? Offset(end, widget.crossAxisEndOffset)
+            : Offset(widget.crossAxisEndOffset, end),
+      ),
+    );
+  }
+
+  _FlingGestureKind _describeFlingGesture(Velocity velocity) {
+    assert(widget.direction != null);
+    if (_dragExtent == 0.0) {
+      // If it was a fling, then it was a fling that was let loose at the exact
+      // middle of the range (i.e. when there's no displacement). In that case,
+      // we assume that the user meant to fling it back to the center, as
+      // opposed to having wanted to drag it out one way, then fling it past the
+      // center and into and out the other side.
+      return _FlingGestureKind.none;
+    }
+    final double vx = velocity.pixelsPerSecond.dx;
+    final double vy = velocity.pixelsPerSecond.dy;
+    DismissDirection flingDirection;
+    // Verify that the fling is in the generally right direction and fast enough.
+    if (_directionIsXAxis) {
+      if (vx.abs() - vy.abs() < _kMinFlingVelocityDelta || vx.abs() < _kMinFlingVelocity)
+        return _FlingGestureKind.none;
+      assert(vx != 0.0);
+      flingDirection = _extentToDirection(vx);
+    } else {
+      if (vy.abs() - vx.abs() < _kMinFlingVelocityDelta || vy.abs() < _kMinFlingVelocity)
+        return _FlingGestureKind.none;
+      assert(vy != 0.0);
+      flingDirection = _extentToDirection(vy);
+    }
+    assert(_dismissDirection != null);
+    if (flingDirection == _dismissDirection)
+      return _FlingGestureKind.forward;
+    return _FlingGestureKind.reverse;
+  }
+
+  Future<void> _handleDragEnd(DragEndDetails details) async {
+    if (!_isActive || _moveController!.isAnimating)
+      return;
+    _dragUnderway = false;
+    if (_moveController!.isCompleted && await _confirmStartResizeAnimation() == true) {
+      _startResizeAnimation();
+      return;
+    }
+    final double flingVelocity = _directionIsXAxis ? details.velocity.pixelsPerSecond.dx : details.velocity.pixelsPerSecond.dy;
+    switch (_describeFlingGesture(details.velocity)) {
+      case _FlingGestureKind.forward:
+        assert(_dragExtent != 0.0);
+        assert(!_moveController!.isDismissed);
+        if ((widget.dismissThresholds[_dismissDirection] ?? _kDismissThreshold) >= 1.0) {
+          _moveController!.reverse();
+          break;
+        }
+        _dragExtent = flingVelocity.sign;
+        _moveController!.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale);
+        break;
+      case _FlingGestureKind.reverse:
+        assert(_dragExtent != 0.0);
+        assert(!_moveController!.isDismissed);
+        _dragExtent = flingVelocity.sign;
+        _moveController!.fling(velocity: -flingVelocity.abs() * _kFlingVelocityScale);
+        break;
+      case _FlingGestureKind.none:
+        if (!_moveController!.isDismissed) { // we already know it's not completed, we check that above
+          if (_moveController!.value > (widget.dismissThresholds[_dismissDirection] ?? _kDismissThreshold)) {
+            _moveController!.forward();
+          } else {
+            _moveController!.reverse();
+          }
+        }
+        break;
+    }
+  }
+
+  Future<void> _handleDismissStatusChanged(AnimationStatus status) async {
+    if (status == AnimationStatus.completed && !_dragUnderway) {
+      if (await _confirmStartResizeAnimation() == true)
+        _startResizeAnimation();
+      else
+        _moveController!.reverse();
+    }
+    updateKeepAlive();
+  }
+
+  Future<bool?> _confirmStartResizeAnimation() async {
+    if (widget.confirmDismiss != null) {
+      final DismissDirection direction = _dismissDirection;
+      return widget.confirmDismiss!(direction);
+    }
+    return true;
+  }
+
+  void _startResizeAnimation() {
+    assert(_moveController != null);
+    assert(_moveController!.isCompleted);
+    assert(_resizeController == null);
+    assert(_sizePriorToCollapse == null);
+    if (widget.resizeDuration == null) {
+      if (widget.onDismissed != null) {
+        final DismissDirection direction = _dismissDirection;
+        widget.onDismissed!(direction);
+      }
+    } else {
+      _resizeController = AnimationController(duration: widget.resizeDuration, vsync: this)
+        ..addListener(_handleResizeProgressChanged)
+        ..addStatusListener((AnimationStatus status) => updateKeepAlive());
+      _resizeController!.forward();
+      setState(() {
+        _sizePriorToCollapse = context.size;
+        _resizeAnimation = _resizeController!.drive(
+          CurveTween(
+            curve: _kResizeTimeCurve
+          ),
+        ).drive(
+          Tween<double>(
+            begin: 1.0,
+            end: 0.0,
+          ),
+        );
+      });
+    }
+  }
+
+  void _handleResizeProgressChanged() {
+    if (_resizeController!.isCompleted) {
+      if (widget.onDismissed != null) {
+        final DismissDirection direction = _dismissDirection;
+        widget.onDismissed!(direction);
+      }
+    } else {
+      if (widget.onResize != null)
+        widget.onResize!();
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    super.build(context); // See AutomaticKeepAliveClientMixin.
+
+    assert(!_directionIsXAxis || debugCheckHasDirectionality(context));
+
+    Widget? background = widget.background;
+    if (widget.secondaryBackground != null) {
+      final DismissDirection direction = _dismissDirection;
+      if (direction == DismissDirection.endToStart || direction == DismissDirection.up)
+        background = widget.secondaryBackground;
+    }
+
+    if (_resizeAnimation != null) {
+      // we've been dragged aside, and are now resizing.
+      assert(() {
+        if (_resizeAnimation!.status != AnimationStatus.forward) {
+          assert(_resizeAnimation!.status == AnimationStatus.completed);
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('A dismissed Dismissible widget is still part of the tree.'),
+            ErrorHint(
+              'Make sure to implement the onDismissed handler and to immediately remove the Dismissible '
+              'widget from the application once that handler has fired.'
+            )
+          ]);
+        }
+        return true;
+      }());
+
+      return SizeTransition(
+        sizeFactor: _resizeAnimation!,
+        axis: _directionIsXAxis ? Axis.vertical : Axis.horizontal,
+        child: SizedBox(
+          width: _sizePriorToCollapse!.width,
+          height: _sizePriorToCollapse!.height,
+          child: background,
+        ),
+      );
+    }
+
+    Widget content = SlideTransition(
+      position: _moveAnimation,
+      child: widget.child,
+    );
+
+    if (background != null) {
+      content = Stack(children: <Widget>[
+        if (!_moveAnimation.isDismissed)
+          Positioned.fill(
+            child: ClipRect(
+              clipper: _DismissibleClipper(
+                axis: _directionIsXAxis ? Axis.horizontal : Axis.vertical,
+                moveAnimation: _moveAnimation,
+              ),
+              child: background,
+            ),
+          ),
+        content,
+      ]);
+    }
+    // We are not resizing but we may be being dragging in widget.direction.
+    return GestureDetector(
+      onHorizontalDragStart: _directionIsXAxis ? _handleDragStart : null,
+      onHorizontalDragUpdate: _directionIsXAxis ? _handleDragUpdate : null,
+      onHorizontalDragEnd: _directionIsXAxis ? _handleDragEnd : null,
+      onVerticalDragStart: _directionIsXAxis ? null : _handleDragStart,
+      onVerticalDragUpdate: _directionIsXAxis ? null : _handleDragUpdate,
+      onVerticalDragEnd: _directionIsXAxis ? null : _handleDragEnd,
+      behavior: widget.behavior,
+      child: content,
+      dragStartBehavior: widget.dragStartBehavior,
+    );
+  }
+}
diff --git a/lib/src/widgets/disposable_build_context.dart b/lib/src/widgets/disposable_build_context.dart
new file mode 100644
index 0000000..3a049ed
--- /dev/null
+++ b/lib/src/widgets/disposable_build_context.dart
@@ -0,0 +1,72 @@
+// Copyright 2014 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.
+
+import 'framework.dart';
+
+/// Provides non-leaking access to a [BuildContext].
+///
+/// A [BuildContext] is only valid if it is pointing to an active [Element].
+/// Once the [Element] is unmounted, the [BuildContext] should not be accessed
+/// further. This class makes it possible for a [StatefulWidget] to share its
+/// build context safely with other objects.
+///
+/// Creators of this object must guarantee the following:
+///
+///   1. They create this object at or after [State.initState] but before
+///      [State.dispose]. In particular, do not attempt to create this from the
+///      constructor of a state.
+///   2. They call [dispose] from [State.dispose].
+///
+/// This object will not hold on to the [State] after disposal.
+@optionalTypeArgs
+class DisposableBuildContext<T extends State> {
+  /// Creates an object that provides access to a [BuildContext] without leaking
+  /// a [State].
+  ///
+  /// Creators must call [dispose] when the [State] is disposed.
+  ///
+  /// The [State] must not be null, and [State.mounted] must be true.
+  DisposableBuildContext(T this._state)
+      : assert(_state != null),
+        assert(_state.mounted, 'A DisposableBuildContext was given a BuildContext for an Element that is not mounted.');
+
+  T? _state;
+
+  /// Provides safe access to the build context.
+  ///
+  /// If [dispose] has been called, will return null.
+  ///
+  /// Otherwise, asserts the [_state] is still mounted and returns its context.
+  BuildContext? get context {
+    assert(_debugValidate());
+    if (_state == null) {
+      return null;
+    }
+    return _state!.context;
+  }
+
+  /// Called from asserts or tests to determine whether this object is in a
+  /// valid state.
+  ///
+  /// Always returns true, but will assert if [dispose] has not been called
+  /// but the state this is tracking is unmounted.
+  bool _debugValidate() {
+    assert(
+      _state == null || _state!.mounted,
+      'A DisposableBuildContext tried to access the BuildContext of a disposed '
+      'State object. This can happen when the creator of this '
+      'DisposableBuildContext fails to call dispose when it is disposed.',
+    );
+    return true;
+  }
+
+
+  /// Marks the [BuildContext] as disposed.
+  ///
+  /// Creators of this object must call [dispose] when their [Element] is
+  /// unmounted, i.e. when [State.dispose] is called.
+  void dispose() {
+    _state = null;
+  }
+}
diff --git a/lib/src/widgets/drag_target.dart b/lib/src/widgets/drag_target.dart
new file mode 100644
index 0000000..69c03d4
--- /dev/null
+++ b/lib/src/widgets/drag_target.dart
@@ -0,0 +1,897 @@
+// Copyright 2014 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.
+
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+
+import 'basic.dart';
+import 'binding.dart';
+import 'framework.dart';
+import 'overlay.dart';
+
+/// Signature for determining whether the given data will be accepted by a [DragTarget].
+///
+/// Used by [DragTarget.onWillAccept].
+typedef DragTargetWillAccept<T> = bool Function(T? data);
+
+/// Signature for causing a [DragTarget] to accept the given data.
+///
+/// Used by [DragTarget.onAccept].
+typedef DragTargetAccept<T> = void Function(T data);
+
+/// Signature for determining information about the acceptance by a [DragTarget].
+///
+/// Used by [DragTarget.onAcceptWithDetails].
+typedef DragTargetAcceptWithDetails<T> = void Function(DragTargetDetails<T> details);
+
+/// Signature for building children of a [DragTarget].
+///
+/// The `candidateData` argument contains the list of drag data that is hovering
+/// over this [DragTarget] and that has passed [DragTarget.onWillAccept]. The
+/// `rejectedData` argument contains the list of drag data that is hovering over
+/// this [DragTarget] and that will not be accepted by the [DragTarget].
+///
+/// Used by [DragTarget.builder].
+typedef DragTargetBuilder<T> = Widget Function(BuildContext context, List<T?> candidateData, List<dynamic> rejectedData);
+
+/// Signature for when a [Draggable] is dragged across the screen.
+///
+/// Used by [Draggable.onDragUpdate].
+typedef DragUpdateCallback = void Function(DragUpdateDetails details);
+
+/// Signature for when a [Draggable] is dropped without being accepted by a [DragTarget].
+///
+/// Used by [Draggable.onDraggableCanceled].
+typedef DraggableCanceledCallback = void Function(Velocity velocity, Offset offset);
+
+/// Signature for when the draggable is dropped.
+///
+/// The velocity and offset at which the pointer was moving when the draggable
+/// was dropped is available in the [DraggableDetails]. Also included in the
+/// `details` is whether the draggable's [DragTarget] accepted it.
+///
+/// Used by [Draggable.onDragEnd]
+typedef DragEndCallback = void Function(DraggableDetails details);
+
+/// Signature for when a [Draggable] leaves a [DragTarget].
+///
+/// Used by [DragTarget.onLeave].
+typedef DragTargetLeave = void Function(Object? data);
+
+/// Signature for when a [Draggable] moves within a [DragTarget].
+///
+/// Used by [DragTarget.onMove].
+typedef DragTargetMove = void Function(DragTargetDetails<dynamic> details);
+
+/// Where the [Draggable] should be anchored during a drag.
+enum DragAnchor {
+  /// Display the feedback anchored at the position of the original child. If
+  /// feedback is identical to the child, then this means the feedback will
+  /// exactly overlap the original child when the drag starts.
+  child,
+
+  /// Display the feedback anchored at the position of the touch that started
+  /// the drag. If feedback is identical to the child, then this means the top
+  /// left of the feedback will be under the finger when the drag starts. This
+  /// will likely not exactly overlap the original child, e.g. if the child is
+  /// big and the touch was not centered. This mode is useful when the feedback
+  /// is transformed so as to move the feedback to the left by half its width,
+  /// and up by half its width plus the height of the finger, since then it
+  /// appears as if putting the finger down makes the touch feedback appear
+  /// above the finger. (It feels weird for it to appear offset from the
+  /// original child if it's anchored to the child and not the finger.)
+  pointer,
+}
+
+/// A widget that can be dragged from to a [DragTarget].
+///
+/// When a draggable widget recognizes the start of a drag gesture, it displays
+/// a [feedback] widget that tracks the user's finger across the screen. If the
+/// user lifts their finger while on top of a [DragTarget], that target is given
+/// the opportunity to accept the [data] carried by the draggable.
+///
+/// On multitouch devices, multiple drags can occur simultaneously because there
+/// can be multiple pointers in contact with the device at once. To limit the
+/// number of simultaneous drags, use the [maxSimultaneousDrags] property. The
+/// default is to allow an unlimited number of simultaneous drags.
+///
+/// This widget displays [child] when zero drags are under way. If
+/// [childWhenDragging] is non-null, this widget instead displays
+/// [childWhenDragging] when one or more drags are underway. Otherwise, this
+/// widget always displays [child].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=QzA4c4QHZCY}
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+///
+/// The following example has a [Draggable] widget along with a [DragTarget]
+/// in a row demonstrating an incremented `acceptedData` integer value when
+/// you drag the element to the target.
+///
+/// ```dart
+/// int acceptedData = 0;
+/// Widget build(BuildContext context) {
+///   return Row(
+///     mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+///     children: [
+///       Draggable<int>(
+///         // Data is the value this Draggable stores.
+///         data: 10,
+///         child: Container(
+///           height: 100.0,
+///           width: 100.0,
+///           color: Colors.lightGreenAccent,
+///           child: Center(
+///             child: Text("Draggable"),
+///           ),
+///         ),
+///         feedback: Container(
+///           color: Colors.deepOrange,
+///           height: 100,
+///           width: 100,
+///           child: Icon(Icons.directions_run),
+///         ),
+///         childWhenDragging: Container(
+///           height: 100.0,
+///           width: 100.0,
+///           color: Colors.pinkAccent,
+///           child: Center(
+///             child: Text("Child When Dragging"),
+///           ),
+///         ),
+///       ),
+///       DragTarget(
+///         builder: (
+///           BuildContext context,
+///           List<dynamic> accepted,
+///           List<dynamic> rejected,
+///         ) {
+///           return Container(
+///             height: 100.0,
+///             width: 100.0,
+///             color: Colors.cyan,
+///             child: Center(
+///               child: Text("Value is updated to: $acceptedData"),
+///             ),
+///           );
+///         },
+///         onAccept: (int data) {
+///           setState(() {
+///             acceptedData += data;
+///           });
+///         },
+///       ),
+///     ],
+///   );
+/// }
+///
+/// ```
+///
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [DragTarget]
+///  * [LongPressDraggable]
+class Draggable<T extends Object> extends StatefulWidget {
+  /// Creates a widget that can be dragged to a [DragTarget].
+  ///
+  /// The [child] and [feedback] arguments must not be null. If
+  /// [maxSimultaneousDrags] is non-null, it must be non-negative.
+  const Draggable({
+    Key? key,
+    required this.child,
+    required this.feedback,
+    this.data,
+    this.axis,
+    this.childWhenDragging,
+    this.feedbackOffset = Offset.zero,
+    this.dragAnchor = DragAnchor.child,
+    this.affinity,
+    this.maxSimultaneousDrags,
+    this.onDragStarted,
+    this.onDragUpdate,
+    this.onDraggableCanceled,
+    this.onDragEnd,
+    this.onDragCompleted,
+    this.ignoringFeedbackSemantics = true,
+    this.rootOverlay = false,
+  }) : assert(child != null),
+       assert(feedback != null),
+       assert(ignoringFeedbackSemantics != null),
+       assert(maxSimultaneousDrags == null || maxSimultaneousDrags >= 0),
+       super(key: key);
+
+  /// The data that will be dropped by this draggable.
+  final T? data;
+
+  /// The [Axis] to restrict this draggable's movement, if specified.
+  ///
+  /// When axis is set to [Axis.horizontal], this widget can only be dragged
+  /// horizontally. Behavior is similar for [Axis.vertical].
+  ///
+  /// Defaults to allowing drag on both [Axis.horizontal] and [Axis.vertical].
+  ///
+  /// When null, allows drag on both [Axis.horizontal] and [Axis.vertical].
+  ///
+  /// For the direction of gestures this widget competes with to start a drag
+  /// event, see [Draggable.affinity].
+  final Axis? axis;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// This widget displays [child] when zero drags are under way. If
+  /// [childWhenDragging] is non-null, this widget instead displays
+  /// [childWhenDragging] when one or more drags are underway. Otherwise, this
+  /// widget always displays [child].
+  ///
+  /// The [feedback] widget is shown under the pointer when a drag is under way.
+  ///
+  /// To limit the number of simultaneous drags on multitouch devices, see
+  /// [maxSimultaneousDrags].
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The widget to display instead of [child] when one or more drags are under way.
+  ///
+  /// If this is null, then this widget will always display [child] (and so the
+  /// drag source representation will not change while a drag is under
+  /// way).
+  ///
+  /// The [feedback] widget is shown under the pointer when a drag is under way.
+  ///
+  /// To limit the number of simultaneous drags on multitouch devices, see
+  /// [maxSimultaneousDrags].
+  final Widget? childWhenDragging;
+
+  /// The widget to show under the pointer when a drag is under way.
+  ///
+  /// See [child] and [childWhenDragging] for information about what is shown
+  /// at the location of the [Draggable] itself when a drag is under way.
+  final Widget feedback;
+
+  /// The feedbackOffset can be used to set the hit test target point for the
+  /// purposes of finding a drag target. It is especially useful if the feedback
+  /// is transformed compared to the child.
+  final Offset feedbackOffset;
+
+  /// Where this widget should be anchored during a drag.
+  final DragAnchor dragAnchor;
+
+  /// Whether the semantics of the [feedback] widget is ignored when building
+  /// the semantics tree.
+  ///
+  /// This value should be set to false when the [feedback] widget is intended
+  /// to be the same object as the [child].  Placing a [GlobalKey] on this
+  /// widget will ensure semantic focus is kept on the element as it moves in
+  /// and out of the feedback position.
+  ///
+  /// Defaults to true.
+  final bool ignoringFeedbackSemantics;
+
+  /// Controls how this widget competes with other gestures to initiate a drag.
+  ///
+  /// If affinity is null, this widget initiates a drag as soon as it recognizes
+  /// a tap down gesture, regardless of any directionality. If affinity is
+  /// horizontal (or vertical), then this widget will compete with other
+  /// horizontal (or vertical, respectively) gestures.
+  ///
+  /// For example, if this widget is placed in a vertically scrolling region and
+  /// has horizontal affinity, pointer motion in the vertical direction will
+  /// result in a scroll and pointer motion in the horizontal direction will
+  /// result in a drag. Conversely, if the widget has a null or vertical
+  /// affinity, pointer motion in any direction will result in a drag rather
+  /// than in a scroll because the draggable widget, being the more specific
+  /// widget, will out-compete the [Scrollable] for vertical gestures.
+  ///
+  /// For the directions this widget can be dragged in after the drag event
+  /// starts, see [Draggable.axis].
+  final Axis? affinity;
+
+  /// How many simultaneous drags to support.
+  ///
+  /// When null, no limit is applied. Set this to 1 if you want to only allow
+  /// the drag source to have one item dragged at a time. Set this to 0 if you
+  /// want to prevent the draggable from actually being dragged.
+  ///
+  /// If you set this property to 1, consider supplying an "empty" widget for
+  /// [childWhenDragging] to create the illusion of actually moving [child].
+  final int? maxSimultaneousDrags;
+
+  /// Called when the draggable starts being dragged.
+  final VoidCallback? onDragStarted;
+
+  /// Called when the draggable is being dragged.
+  ///
+  /// This function will only be called while this widget is still mounted to
+  /// the tree (i.e. [State.mounted] is true), and if this widget has actually moved.
+  final DragUpdateCallback? onDragUpdate;
+
+  /// Called when the draggable is dropped without being accepted by a [DragTarget].
+  ///
+  /// This function might be called after this widget has been removed from the
+  /// tree. For example, if a drag was in progress when this widget was removed
+  /// from the tree and the drag ended up being canceled, this callback will
+  /// still be called. For this reason, implementations of this callback might
+  /// need to check [State.mounted] to check whether the state receiving the
+  /// callback is still in the tree.
+  final DraggableCanceledCallback? onDraggableCanceled;
+
+  /// Called when the draggable is dropped and accepted by a [DragTarget].
+  ///
+  /// This function might be called after this widget has been removed from the
+  /// tree. For example, if a drag was in progress when this widget was removed
+  /// from the tree and the drag ended up completing, this callback will
+  /// still be called. For this reason, implementations of this callback might
+  /// need to check [State.mounted] to check whether the state receiving the
+  /// callback is still in the tree.
+  final VoidCallback? onDragCompleted;
+
+  /// Called when the draggable is dropped.
+  ///
+  /// The velocity and offset at which the pointer was moving when it was
+  /// dropped is available in the [DraggableDetails]. Also included in the
+  /// `details` is whether the draggable's [DragTarget] accepted it.
+  ///
+  /// This function will only be called while this widget is still mounted to
+  /// the tree (i.e. [State.mounted] is true).
+  final DragEndCallback? onDragEnd;
+
+  /// Whether the feedback widget will be put on the root [Overlay].
+  ///
+  /// When false, the feedback widget will be put on the closest [Overlay]. When
+  /// true, the [feedback] widget will be put on the farthest (aka root)
+  /// [Overlay].
+  ///
+  /// Defaults to false.
+  final bool rootOverlay;
+
+  /// Creates a gesture recognizer that recognizes the start of the drag.
+  ///
+  /// Subclasses can override this function to customize when they start
+  /// recognizing a drag.
+  @protected
+  MultiDragGestureRecognizer<MultiDragPointerState> createRecognizer(GestureMultiDragStartCallback onStart) {
+    switch (affinity) {
+      case Axis.horizontal:
+        return HorizontalMultiDragGestureRecognizer()..onStart = onStart;
+      case Axis.vertical:
+        return VerticalMultiDragGestureRecognizer()..onStart = onStart;
+      case null:
+        return ImmediateMultiDragGestureRecognizer()..onStart = onStart;
+    }
+  }
+
+  @override
+  _DraggableState<T> createState() => _DraggableState<T>();
+}
+
+/// Makes its child draggable starting from long press.
+class LongPressDraggable<T extends Object> extends Draggable<T> {
+  /// Creates a widget that can be dragged starting from long press.
+  ///
+  /// The [child] and [feedback] arguments must not be null. If
+  /// [maxSimultaneousDrags] is non-null, it must be non-negative.
+  const LongPressDraggable({
+    Key? key,
+    required Widget child,
+    required Widget feedback,
+    T? data,
+    Axis? axis,
+    Widget? childWhenDragging,
+    Offset feedbackOffset = Offset.zero,
+    DragAnchor dragAnchor = DragAnchor.child,
+    int? maxSimultaneousDrags,
+    VoidCallback? onDragStarted,
+    DragUpdateCallback? onDragUpdate,
+    DraggableCanceledCallback? onDraggableCanceled,
+    DragEndCallback? onDragEnd,
+    VoidCallback? onDragCompleted,
+    this.hapticFeedbackOnStart = true,
+    bool ignoringFeedbackSemantics = true,
+  }) : super(
+    key: key,
+    child: child,
+    feedback: feedback,
+    data: data,
+    axis: axis,
+    childWhenDragging: childWhenDragging,
+    feedbackOffset: feedbackOffset,
+    dragAnchor: dragAnchor,
+    maxSimultaneousDrags: maxSimultaneousDrags,
+    onDragStarted: onDragStarted,
+    onDragUpdate: onDragUpdate,
+    onDraggableCanceled: onDraggableCanceled,
+    onDragEnd: onDragEnd,
+    onDragCompleted: onDragCompleted,
+    ignoringFeedbackSemantics: ignoringFeedbackSemantics,
+  );
+
+  /// Whether haptic feedback should be triggered on drag start.
+  final bool hapticFeedbackOnStart;
+
+  @override
+  DelayedMultiDragGestureRecognizer createRecognizer(GestureMultiDragStartCallback onStart) {
+    return DelayedMultiDragGestureRecognizer()
+      ..onStart = (Offset position) {
+        final Drag? result = onStart(position);
+        if (result != null && hapticFeedbackOnStart)
+          HapticFeedback.selectionClick();
+        return result;
+      };
+  }
+}
+
+class _DraggableState<T extends Object> extends State<Draggable<T>> {
+  @override
+  void initState() {
+    super.initState();
+    _recognizer = widget.createRecognizer(_startDrag);
+  }
+
+  @override
+  void dispose() {
+    _disposeRecognizerIfInactive();
+    super.dispose();
+  }
+
+  // This gesture recognizer has an unusual lifetime. We want to support the use
+  // case of removing the Draggable from the tree in the middle of a drag. That
+  // means we need to keep this recognizer alive after this state object has
+  // been disposed because it's the one listening to the pointer events that are
+  // driving the drag.
+  //
+  // We achieve that by keeping count of the number of active drags and only
+  // disposing the gesture recognizer after (a) this state object has been
+  // disposed and (b) there are no more active drags.
+  GestureRecognizer? _recognizer;
+  int _activeCount = 0;
+
+  void _disposeRecognizerIfInactive() {
+    if (_activeCount > 0)
+      return;
+    _recognizer!.dispose();
+    _recognizer = null;
+  }
+
+  void _routePointer(PointerDownEvent event) {
+    if (widget.maxSimultaneousDrags != null && _activeCount >= widget.maxSimultaneousDrags!)
+      return;
+    _recognizer!.addPointer(event);
+  }
+
+  _DragAvatar<T>? _startDrag(Offset position) {
+    if (widget.maxSimultaneousDrags != null && _activeCount >= widget.maxSimultaneousDrags!)
+      return null;
+    final Offset dragStartPoint;
+    switch (widget.dragAnchor) {
+      case DragAnchor.child:
+        final RenderBox renderObject = context.findRenderObject()! as RenderBox;
+        dragStartPoint = renderObject.globalToLocal(position);
+        break;
+      case DragAnchor.pointer:
+        dragStartPoint = Offset.zero;
+        break;
+    }
+    setState(() {
+      _activeCount += 1;
+    });
+    final _DragAvatar<T> avatar = _DragAvatar<T>(
+      overlayState: Overlay.of(context, debugRequiredFor: widget, rootOverlay: widget.rootOverlay)!,
+      data: widget.data,
+      axis: widget.axis,
+      initialPosition: position,
+      dragStartPoint: dragStartPoint,
+      feedback: widget.feedback,
+      feedbackOffset: widget.feedbackOffset,
+      ignoringFeedbackSemantics: widget.ignoringFeedbackSemantics,
+      onDragUpdate: (DragUpdateDetails details) {
+        if (mounted && widget.onDragUpdate != null) {
+          widget.onDragUpdate!(details);
+        }
+      },
+      onDragEnd: (Velocity velocity, Offset offset, bool wasAccepted) {
+        if (mounted) {
+          setState(() {
+            _activeCount -= 1;
+          });
+        } else {
+          _activeCount -= 1;
+          _disposeRecognizerIfInactive();
+        }
+        if (mounted && widget.onDragEnd != null) {
+          widget.onDragEnd!(DraggableDetails(
+              wasAccepted: wasAccepted,
+              velocity: velocity,
+              offset: offset,
+          ));
+        }
+        if (wasAccepted && widget.onDragCompleted != null)
+          widget.onDragCompleted!();
+        if (!wasAccepted && widget.onDraggableCanceled != null)
+          widget.onDraggableCanceled!(velocity, offset);
+      },
+    );
+    if (widget.onDragStarted != null)
+      widget.onDragStarted!();
+    return avatar;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(Overlay.of(context, debugRequiredFor: widget, rootOverlay: widget.rootOverlay) != null);
+    final bool canDrag = widget.maxSimultaneousDrags == null ||
+                         _activeCount < widget.maxSimultaneousDrags!;
+    final bool showChild = _activeCount == 0 || widget.childWhenDragging == null;
+    return Listener(
+      onPointerDown: canDrag ? _routePointer : null,
+      child: showChild ? widget.child : widget.childWhenDragging,
+    );
+  }
+}
+
+/// Represents the details when a specific pointer event occurred on
+/// the [Draggable].
+///
+/// This includes the [Velocity] at which the pointer was moving and [Offset]
+/// when the draggable event occurred, and whether its [DragTarget] accepted it.
+///
+/// Also, this is the details object for callbacks that use [DragEndCallback].
+class DraggableDetails {
+  /// Creates details for a [DraggableDetails].
+  ///
+  /// If [wasAccepted] is not specified, it will default to `false`.
+  ///
+  /// The [velocity] or [offset] arguments must not be `null`.
+  DraggableDetails({
+    this.wasAccepted = false,
+    required this.velocity,
+    required this.offset,
+  }) : assert(velocity != null),
+       assert(offset != null);
+
+  /// Determines whether the [DragTarget] accepted this draggable.
+  final bool wasAccepted;
+
+  /// The velocity at which the pointer was moving when the specific pointer
+  /// event occurred on the draggable.
+  final Velocity velocity;
+
+  /// The global position when the specific pointer event occurred on
+  /// the draggable.
+  final Offset offset;
+}
+
+/// Represents the details when a pointer event occurred on the [DragTarget].
+class DragTargetDetails<T> {
+  /// Creates details for a [DragTarget] callback.
+  ///
+  /// The [offset] must not be null.
+  DragTargetDetails({required this.data, required this.offset}) : assert(offset != null);
+
+  /// The data that was dropped onto this [DragTarget].
+  final T data;
+
+  /// The global position when the specific pointer event occurred on
+  /// the draggable.
+  final Offset offset;
+}
+
+/// A widget that receives data when a [Draggable] widget is dropped.
+///
+/// When a draggable is dragged on top of a drag target, the drag target is
+/// asked whether it will accept the data the draggable is carrying. If the user
+/// does drop the draggable on top of the drag target (and the drag target has
+/// indicated that it will accept the draggable's data), then the drag target is
+/// asked to accept the draggable's data.
+///
+/// See also:
+///
+///  * [Draggable]
+///  * [LongPressDraggable]
+class DragTarget<T extends Object> extends StatefulWidget {
+  /// Creates a widget that receives drags.
+  ///
+  /// The [builder] argument must not be null.
+  const DragTarget({
+    Key? key,
+    required this.builder,
+    this.onWillAccept,
+    this.onAccept,
+    this.onAcceptWithDetails,
+    this.onLeave,
+    this.onMove,
+  }) : super(key: key);
+
+  /// Called to build the contents of this widget.
+  ///
+  /// The builder can build different widgets depending on what is being dragged
+  /// into this drag target.
+  final DragTargetBuilder<T> builder;
+
+  /// Called to determine whether this widget is interested in receiving a given
+  /// piece of data being dragged over this drag target.
+  ///
+  /// Called when a piece of data enters the target. This will be followed by
+  /// either [onAccept] and [onAcceptWithDetails], if the data is dropped, or
+  /// [onLeave], if the drag leaves the target.
+  final DragTargetWillAccept<T>? onWillAccept;
+
+  /// Called when an acceptable piece of data was dropped over this drag target.
+  ///
+  /// Equivalent to [onAcceptWithDetails], but only includes the data.
+  final DragTargetAccept<T>? onAccept;
+
+  /// Called when an acceptable piece of data was dropped over this drag target.
+  ///
+  /// Equivalent to [onAccept], but with information, including the data, in a
+  /// [DragTargetDetails].
+  final DragTargetAcceptWithDetails<T>? onAcceptWithDetails;
+
+  /// Called when a given piece of data being dragged over this target leaves
+  /// the target.
+  final DragTargetLeave? onLeave;
+
+  /// Called when a [Draggable] moves within this [DragTarget].
+  ///
+  /// Note that this includes entering and leaving the target.
+  final DragTargetMove? onMove;
+
+  @override
+  _DragTargetState<T> createState() => _DragTargetState<T>();
+}
+
+List<T?> _mapAvatarsToData<T extends Object>(List<_DragAvatar<T>> avatars) {
+  return avatars.map<T?>((_DragAvatar<T> avatar) => avatar.data).toList();
+}
+
+class _DragTargetState<T extends Object> extends State<DragTarget<T>> {
+  final List<_DragAvatar<T>> _candidateAvatars = <_DragAvatar<T>>[];
+  final List<_DragAvatar<Object>> _rejectedAvatars = <_DragAvatar<Object>>[];
+
+  bool didEnter(_DragAvatar<Object> avatar) {
+    assert(!_candidateAvatars.contains(avatar));
+    assert(!_rejectedAvatars.contains(avatar));
+    if (avatar is _DragAvatar<T> && (widget.onWillAccept == null || widget.onWillAccept!(avatar.data))) {
+      setState(() {
+        _candidateAvatars.add(avatar);
+      });
+      return true;
+    } else {
+      setState(() {
+        _rejectedAvatars.add(avatar);
+      });
+      return false;
+    }
+  }
+
+  void didLeave(_DragAvatar<Object> avatar) {
+    assert(_candidateAvatars.contains(avatar) || _rejectedAvatars.contains(avatar));
+    if (!mounted)
+      return;
+    setState(() {
+      _candidateAvatars.remove(avatar);
+      _rejectedAvatars.remove(avatar);
+    });
+    if (widget.onLeave != null)
+      widget.onLeave!(avatar.data);
+  }
+
+  void didDrop(_DragAvatar<Object> avatar) {
+    assert(_candidateAvatars.contains(avatar));
+    if (!mounted)
+      return;
+    setState(() {
+      _candidateAvatars.remove(avatar);
+    });
+    if (widget.onAccept != null)
+      widget.onAccept!(avatar.data! as T);
+    if (widget.onAcceptWithDetails != null)
+      widget.onAcceptWithDetails!(DragTargetDetails<T>(data: avatar.data! as T, offset: avatar._lastOffset!));
+  }
+
+  void didMove(_DragAvatar<Object> avatar) {
+    if (!mounted)
+      return;
+    if (widget.onMove != null)
+      widget.onMove!(DragTargetDetails<dynamic>(data: avatar.data, offset: avatar._lastOffset!));
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(widget.builder != null);
+    return MetaData(
+      metaData: this,
+      behavior: HitTestBehavior.translucent,
+      child: widget.builder(context, _mapAvatarsToData<T>(_candidateAvatars), _mapAvatarsToData<Object>(_rejectedAvatars)),
+    );
+  }
+}
+
+enum _DragEndKind { dropped, canceled }
+typedef _OnDragEnd = void Function(Velocity velocity, Offset offset, bool wasAccepted);
+
+// The lifetime of this object is a little dubious right now. Specifically, it
+// lives as long as the pointer is down. Arguably it should self-immolate if the
+// overlay goes away. _DraggableState has some delicate logic to continue
+// needing this object pointer events even after it has been disposed.
+class _DragAvatar<T extends Object> extends Drag {
+  _DragAvatar({
+    required this.overlayState,
+    this.data,
+    this.axis,
+    required Offset initialPosition,
+    this.dragStartPoint = Offset.zero,
+    this.feedback,
+    this.feedbackOffset = Offset.zero,
+    this.onDragUpdate,
+    this.onDragEnd,
+    required this.ignoringFeedbackSemantics,
+  }) : assert(overlayState != null),
+       assert(ignoringFeedbackSemantics != null),
+       assert(dragStartPoint != null),
+       assert(feedbackOffset != null),
+       _position = initialPosition {
+    _entry = OverlayEntry(builder: _build);
+    overlayState.insert(_entry!);
+    updateDrag(initialPosition);
+  }
+
+  final T? data;
+  final Axis? axis;
+  final Offset dragStartPoint;
+  final Widget? feedback;
+  final Offset feedbackOffset;
+  final DragUpdateCallback? onDragUpdate;
+  final _OnDragEnd? onDragEnd;
+  final OverlayState overlayState;
+  final bool ignoringFeedbackSemantics;
+
+  _DragTargetState<T>? _activeTarget;
+  final List<_DragTargetState<T>> _enteredTargets = <_DragTargetState<T>>[];
+  Offset _position;
+  Offset? _lastOffset;
+  OverlayEntry? _entry;
+
+  @override
+  void update(DragUpdateDetails details) {
+    final Offset oldPosition = _position;
+    _position += _restrictAxis(details.delta);
+    updateDrag(_position);
+    if (onDragUpdate != null && _position != oldPosition) {
+      onDragUpdate!(details);
+    }
+  }
+
+  @override
+  void end(DragEndDetails details) {
+    finishDrag(_DragEndKind.dropped, _restrictVelocityAxis(details.velocity));
+  }
+
+
+  @override
+  void cancel() {
+    finishDrag(_DragEndKind.canceled);
+  }
+
+  void updateDrag(Offset globalPosition) {
+    _lastOffset = globalPosition - dragStartPoint;
+    _entry!.markNeedsBuild();
+    final HitTestResult result = HitTestResult();
+    WidgetsBinding.instance!.hitTest(result, globalPosition + feedbackOffset);
+
+    final List<_DragTargetState<T>> targets = _getDragTargets(result.path).toList();
+
+    bool listsMatch = false;
+    if (targets.length >= _enteredTargets.length && _enteredTargets.isNotEmpty) {
+      listsMatch = true;
+      final Iterator<_DragTargetState<T>> iterator = targets.iterator;
+      for (int i = 0; i < _enteredTargets.length; i += 1) {
+        iterator.moveNext();
+        if (iterator.current != _enteredTargets[i]) {
+          listsMatch = false;
+          break;
+        }
+      }
+    }
+
+    // If everything's the same, report moves, and bail early.
+    if (listsMatch) {
+      for (final _DragTargetState<T> target in _enteredTargets) {
+        target.didMove(this);
+      }
+      return;
+    }
+
+    // Leave old targets.
+    _leaveAllEntered();
+
+    // Enter new targets.
+    final _DragTargetState<T>? newTarget = targets.cast<_DragTargetState<T>?>().firstWhere(
+      (_DragTargetState<T>? target) {
+        if (target == null)
+          return false;
+        _enteredTargets.add(target);
+        return target.didEnter(this);
+      },
+      orElse: () => null,
+    );
+
+    // Report moves to the targets.
+    for (final _DragTargetState<T> target in _enteredTargets) {
+      target.didMove(this);
+    }
+
+    _activeTarget = newTarget;
+  }
+
+  Iterable<_DragTargetState<T>> _getDragTargets(Iterable<HitTestEntry> path) sync* {
+    // Look for the RenderBoxes that corresponds to the hit target (the hit target
+    // widgets build RenderMetaData boxes for us for this purpose).
+    for (final HitTestEntry entry in path) {
+      final HitTestTarget target = entry.target;
+      if (target is RenderMetaData) {
+        final dynamic metaData = target.metaData;
+        if (metaData is _DragTargetState<T>)
+          yield metaData;
+      }
+    }
+  }
+
+  void _leaveAllEntered() {
+    for (int i = 0; i < _enteredTargets.length; i += 1)
+      _enteredTargets[i].didLeave(this);
+    _enteredTargets.clear();
+  }
+
+  void finishDrag(_DragEndKind endKind, [ Velocity? velocity ]) {
+    bool wasAccepted = false;
+    if (endKind == _DragEndKind.dropped && _activeTarget != null) {
+      _activeTarget!.didDrop(this);
+      wasAccepted = true;
+      _enteredTargets.remove(_activeTarget);
+    }
+    _leaveAllEntered();
+    _activeTarget = null;
+    _entry!.remove();
+    _entry = null;
+    // TODO(ianh): consider passing _entry as well so the client can perform an animation.
+    if (onDragEnd != null)
+      onDragEnd!(velocity ?? Velocity.zero, _lastOffset!, wasAccepted);
+  }
+
+  Widget _build(BuildContext context) {
+    final RenderBox box = overlayState.context.findRenderObject()! as RenderBox;
+    final Offset overlayTopLeft = box.localToGlobal(Offset.zero);
+    return Positioned(
+      left: _lastOffset!.dx - overlayTopLeft.dx,
+      top: _lastOffset!.dy - overlayTopLeft.dy,
+      child: IgnorePointer(
+        child: feedback,
+        ignoringSemantics: ignoringFeedbackSemantics,
+      ),
+    );
+  }
+
+  Velocity _restrictVelocityAxis(Velocity velocity) {
+    if (axis == null) {
+      return velocity;
+    }
+    return Velocity(
+      pixelsPerSecond: _restrictAxis(velocity.pixelsPerSecond),
+    );
+  }
+
+  Offset _restrictAxis(Offset offset) {
+    if (axis == null) {
+      return offset;
+    }
+    if (axis == Axis.horizontal) {
+      return Offset(offset.dx, 0.0);
+    }
+    return Offset(0.0, offset.dy);
+  }
+}
diff --git a/lib/src/widgets/draggable_scrollable_sheet.dart b/lib/src/widgets/draggable_scrollable_sheet.dart
new file mode 100644
index 0000000..87118df
--- /dev/null
+++ b/lib/src/widgets/draggable_scrollable_sheet.dart
@@ -0,0 +1,608 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'inherited_notifier.dart';
+import 'layout_builder.dart';
+import 'notification_listener.dart';
+import 'scroll_context.dart';
+import 'scroll_controller.dart';
+import 'scroll_notification.dart';
+import 'scroll_physics.dart';
+import 'scroll_position.dart';
+import 'scroll_position_with_single_context.dart';
+import 'scroll_simulation.dart';
+
+/// The signature of a method that provides a [BuildContext] and
+/// [ScrollController] for building a widget that may overflow the draggable
+/// [Axis] of the containing [DraggableScrollableSheet].
+///
+/// Users should apply the [scrollController] to a [ScrollView] subclass, such
+/// as a [SingleChildScrollView], [ListView] or [GridView], to have the whole
+/// sheet be draggable.
+typedef ScrollableWidgetBuilder = Widget Function(
+  BuildContext context,
+  ScrollController scrollController,
+);
+
+/// A container for a [Scrollable] that responds to drag gestures by resizing
+/// the scrollable until a limit is reached, and then scrolling.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=Hgw819mL_78}
+///
+/// This widget can be dragged along the vertical axis between its
+/// [minChildSize], which defaults to `0.25` and [maxChildSize], which defaults
+/// to `1.0`. These sizes are percentages of the height of the parent container.
+///
+/// The widget coordinates resizing and scrolling of the widget returned by
+/// builder as the user drags along the horizontal axis.
+///
+/// The widget will initially be displayed at its initialChildSize which
+/// defaults to `0.5`, meaning half the height of its parent. Dragging will work
+/// between the range of minChildSize and maxChildSize (as percentages of the
+/// parent container's height) as long as the builder creates a widget which
+/// uses the provided [ScrollController]. If the widget created by the
+/// [ScrollableWidgetBuilder] does not use the provided [ScrollController], the
+/// sheet will remain at the initialChildSize.
+///
+/// By default, the widget will expand its non-occupied area to fill available
+/// space in the parent. If this is not desired, e.g. because the parent wants
+/// to position sheet based on the space it is taking, the [expand] property
+/// may be set to false.
+///
+/// {@tool snippet}
+///
+/// This is a sample widget which shows a [ListView] that has 25 [ListTile]s.
+/// It starts out as taking up half the body of the [Scaffold], and can be
+/// dragged up to the full height of the scaffold or down to 25% of the height
+/// of the scaffold. Upon reaching full height, the list contents will be
+/// scrolled up or down, until they reach the top of the list again and the user
+/// drags the sheet back down.
+///
+/// ```dart
+/// class HomePage extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return Scaffold(
+///       appBar: AppBar(
+///         title: const Text('DraggableScrollableSheet'),
+///       ),
+///       body: SizedBox.expand(
+///         child: DraggableScrollableSheet(
+///           builder: (BuildContext context, ScrollController scrollController) {
+///             return Container(
+///               color: Colors.blue[100],
+///               child: ListView.builder(
+///                 controller: scrollController,
+///                 itemCount: 25,
+///                 itemBuilder: (BuildContext context, int index) {
+///                   return ListTile(title: Text('Item $index'));
+///                 },
+///               ),
+///             );
+///           },
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+class DraggableScrollableSheet extends StatefulWidget {
+  /// Creates a widget that can be dragged and scrolled in a single gesture.
+  ///
+  /// The [builder], [initialChildSize], [minChildSize], [maxChildSize] and
+  /// [expand] parameters must not be null.
+  const DraggableScrollableSheet({
+    Key? key,
+    this.initialChildSize = 0.5,
+    this.minChildSize = 0.25,
+    this.maxChildSize = 1.0,
+    this.expand = true,
+    required this.builder,
+  })  : assert(initialChildSize != null),
+        assert(minChildSize != null),
+        assert(maxChildSize != null),
+        assert(minChildSize >= 0.0),
+        assert(maxChildSize <= 1.0),
+        assert(minChildSize <= initialChildSize),
+        assert(initialChildSize <= maxChildSize),
+        assert(expand != null),
+        assert(builder != null),
+        super(key: key);
+
+  /// The initial fractional value of the parent container's height to use when
+  /// displaying the widget.
+  ///
+  /// The default value is `0.5`.
+  final double initialChildSize;
+
+  /// The minimum fractional value of the parent container's height to use when
+  /// displaying the widget.
+  ///
+  /// The default value is `0.25`.
+  final double minChildSize;
+
+  /// The maximum fractional value of the parent container's height to use when
+  /// displaying the widget.
+  ///
+  /// The default value is `1.0`.
+  final double maxChildSize;
+
+  /// Whether the widget should expand to fill the available space in its parent
+  /// or not.
+  ///
+  /// In most cases, this should be true. However, in the case of a parent
+  /// widget that will position this one based on its desired size (such as a
+  /// [Center]), this should be set to false.
+  ///
+  /// The default value is true.
+  final bool expand;
+
+  /// The builder that creates a child to display in this widget, which will
+  /// use the provided [ScrollController] to enable dragging and scrolling
+  /// of the contents.
+  final ScrollableWidgetBuilder builder;
+
+  @override
+  _DraggableScrollableSheetState createState() => _DraggableScrollableSheetState();
+}
+
+/// A [Notification] related to the extent, which is the size, and scroll
+/// offset, which is the position of the child list, of the
+/// [DraggableScrollableSheet].
+///
+/// [DraggableScrollableSheet] widgets notify their ancestors when the size of
+/// the sheet changes. When the extent of the sheet changes via a drag,
+/// this notification bubbles up through the tree, which means a given
+/// [NotificationListener] will receive notifications for all descendant
+/// [DraggableScrollableSheet] widgets. To focus on notifications from the
+/// nearest [DraggableScrollableSheet] descendant, check that the [depth]
+/// property of the notification is zero.
+///
+/// When an extent notification is received by a [NotificationListener], the
+/// listener will already have completed build and layout, and it is therefore
+/// too late for that widget to call [State.setState]. Any attempt to adjust the
+/// build or layout based on an extent notification would result in a layout
+/// that lagged one frame behind, which is a poor user experience. Extent
+/// notifications are used primarily to drive animations. The [Scaffold] widget
+/// listens for extent notifications and responds by driving animations for the
+/// [FloatingActionButton] as the bottom sheet scrolls up.
+class DraggableScrollableNotification extends Notification with ViewportNotificationMixin {
+  /// Creates a notification that the extent of a [DraggableScrollableSheet] has
+  /// changed.
+  ///
+  /// All parameters are required. The [minExtent] must be >= 0.  The [maxExtent]
+  /// must be <= 1.0.  The [extent] must be between [minExtent] and [maxExtent].
+  DraggableScrollableNotification({
+    required this.extent,
+    required this.minExtent,
+    required this.maxExtent,
+    required this.initialExtent,
+    required this.context,
+  }) : assert(extent != null),
+       assert(initialExtent != null),
+       assert(minExtent != null),
+       assert(maxExtent != null),
+       assert(0.0 <= minExtent),
+       assert(maxExtent <= 1.0),
+       assert(minExtent <= extent),
+       assert(minExtent <= initialExtent),
+       assert(extent <= maxExtent),
+       assert(initialExtent <= maxExtent),
+       assert(context != null);
+
+  /// The current value of the extent, between [minExtent] and [maxExtent].
+  final double extent;
+
+  /// The minimum value of [extent], which is >= 0.
+  final double minExtent;
+
+  /// The maximum value of [extent].
+  final double maxExtent;
+
+  /// The initially requested value for [extent].
+  final double initialExtent;
+
+  /// The build context of the widget that fired this notification.
+  ///
+  /// This can be used to find the sheet's render objects to determine the size
+  /// of the viewport, for instance. A listener can only assume this context
+  /// is live when it first gets the notification.
+  final BuildContext context;
+
+  @override
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('minExtent: $minExtent, extent: $extent, maxExtent: $maxExtent, initialExtent: $initialExtent');
+  }
+}
+
+/// Manages state between [_DraggableScrollableSheetState],
+/// [_DraggableScrollableSheetScrollController], and
+/// [_DraggableScrollableSheetScrollPosition].
+///
+/// The State knows the pixels available along the axis the widget wants to
+/// scroll, but expects to get a fraction of those pixels to render the sheet.
+///
+/// The ScrollPosition knows the number of pixels a user wants to move the sheet.
+///
+/// The [currentExtent] will never be null.
+/// The [availablePixels] will never be null, but may be `double.infinity`.
+class _DraggableSheetExtent {
+  _DraggableSheetExtent({
+    required this.minExtent,
+    required this.maxExtent,
+    required this.initialExtent,
+    required VoidCallback listener,
+  }) : assert(minExtent != null),
+       assert(maxExtent != null),
+       assert(initialExtent != null),
+       assert(minExtent >= 0),
+       assert(maxExtent <= 1),
+       assert(minExtent <= initialExtent),
+       assert(initialExtent <= maxExtent),
+       _currentExtent = ValueNotifier<double>(initialExtent)..addListener(listener),
+       availablePixels = double.infinity;
+
+  final double minExtent;
+  final double maxExtent;
+  final double initialExtent;
+  final ValueNotifier<double> _currentExtent;
+  double availablePixels;
+
+  bool get isAtMin => minExtent >= _currentExtent.value;
+  bool get isAtMax => maxExtent <= _currentExtent.value;
+
+  set currentExtent(double value) {
+    assert(value != null);
+    _currentExtent.value = value.clamp(minExtent, maxExtent);
+  }
+  double get currentExtent => _currentExtent.value;
+
+  double get additionalMinExtent => isAtMin ? 0.0 : 1.0;
+  double get additionalMaxExtent => isAtMax ? 0.0 : 1.0;
+
+  /// The scroll position gets inputs in terms of pixels, but the extent is
+  /// expected to be expressed as a number between 0..1.
+  void addPixelDelta(double delta, BuildContext context) {
+    if (availablePixels == 0) {
+      return;
+    }
+    currentExtent += delta / availablePixels * maxExtent;
+    DraggableScrollableNotification(
+      minExtent: minExtent,
+      maxExtent: maxExtent,
+      extent: currentExtent,
+      initialExtent: initialExtent,
+      context: context,
+    ).dispatch(context);
+  }
+}
+
+class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
+  late _DraggableScrollableSheetScrollController _scrollController;
+  late _DraggableSheetExtent _extent;
+
+  @override
+  void initState() {
+    super.initState();
+    _extent = _DraggableSheetExtent(
+      minExtent: widget.minChildSize,
+      maxExtent: widget.maxChildSize,
+      initialExtent: widget.initialChildSize,
+      listener: _setExtent,
+    );
+    _scrollController = _DraggableScrollableSheetScrollController(extent: _extent);
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    if (_InheritedResetNotifier.shouldReset(context)) {
+      // jumpTo can result in trying to replace semantics during build.
+      // Just animate really fast.
+      // Avoid doing it at all if the offset is already 0.0.
+      if (_scrollController.offset != 0.0) {
+        _scrollController.animateTo(
+          0.0,
+          duration: const Duration(milliseconds: 1),
+          curve: Curves.linear,
+        );
+      }
+      _extent._currentExtent.value = _extent.initialExtent;
+    }
+  }
+
+  void _setExtent() {
+    setState(() {
+      // _extent has been updated when this is called.
+    });
+
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return LayoutBuilder(
+      builder: (BuildContext context, BoxConstraints constraints) {
+        _extent.availablePixels = widget.maxChildSize * constraints.biggest.height;
+        final Widget sheet = FractionallySizedBox(
+          heightFactor: _extent.currentExtent,
+          child: widget.builder(context, _scrollController),
+          alignment: Alignment.bottomCenter,
+        );
+        return widget.expand ? SizedBox.expand(child: sheet) : sheet;
+      },
+    );
+  }
+
+  @override
+  void dispose() {
+    _scrollController.dispose();
+    super.dispose();
+  }
+}
+
+/// A [ScrollController] suitable for use in a [ScrollableWidgetBuilder] created
+/// by a [DraggableScrollableSheet].
+///
+/// If a [DraggableScrollableSheet] contains content that is exceeds the height
+/// of its container, this controller will allow the sheet to both be dragged to
+/// fill the container and then scroll the child content.
+///
+/// See also:
+///
+///  * [_DraggableScrollableSheetScrollPosition], which manages the positioning logic for
+///    this controller.
+///  * [PrimaryScrollController], which can be used to establish a
+///    [_DraggableScrollableSheetScrollController] as the primary controller for
+///    descendants.
+class _DraggableScrollableSheetScrollController extends ScrollController {
+  _DraggableScrollableSheetScrollController({
+    double initialScrollOffset = 0.0,
+    String? debugLabel,
+    required this.extent,
+  }) : assert(extent != null),
+       super(
+         debugLabel: debugLabel,
+         initialScrollOffset: initialScrollOffset,
+       );
+
+  final _DraggableSheetExtent extent;
+
+  @override
+  _DraggableScrollableSheetScrollPosition createScrollPosition(
+    ScrollPhysics physics,
+    ScrollContext context,
+    ScrollPosition? oldPosition,
+  ) {
+    return _DraggableScrollableSheetScrollPosition(
+      physics: physics,
+      context: context,
+      oldPosition: oldPosition,
+      extent: extent,
+    );
+  }
+
+  @override
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('extent: $extent');
+  }
+}
+
+/// A scroll position that manages scroll activities for
+/// [_DraggableScrollableSheetScrollController].
+///
+/// This class is a concrete subclass of [ScrollPosition] logic that handles a
+/// single [ScrollContext], such as a [Scrollable]. An instance of this class
+/// manages [ScrollActivity] instances, which changes the
+/// [_DraggableSheetExtent.currentExtent] or visible content offset in the
+/// [Scrollable]'s [Viewport]
+///
+/// See also:
+///
+///  * [_DraggableScrollableSheetScrollController], which uses this as its [ScrollPosition].
+class _DraggableScrollableSheetScrollPosition
+    extends ScrollPositionWithSingleContext {
+  _DraggableScrollableSheetScrollPosition({
+    required ScrollPhysics physics,
+    required ScrollContext context,
+    double initialPixels = 0.0,
+    bool keepScrollOffset = true,
+    ScrollPosition? oldPosition,
+    String? debugLabel,
+    required this.extent,
+  })  : assert(extent != null),
+        super(
+          physics: physics,
+          context: context,
+          initialPixels: initialPixels,
+          keepScrollOffset: keepScrollOffset,
+          oldPosition: oldPosition,
+          debugLabel: debugLabel,
+        );
+
+  VoidCallback? _dragCancelCallback;
+  final _DraggableSheetExtent extent;
+  bool get listShouldScroll => pixels > 0.0;
+
+  @override
+  bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
+    // We need to provide some extra extent if we haven't yet reached the max or
+    // min extents. Otherwise, a list with fewer children than the extent of
+    // the available space will get stuck.
+    return super.applyContentDimensions(
+      minScrollExtent - extent.additionalMinExtent,
+      maxScrollExtent + extent.additionalMaxExtent,
+    );
+  }
+
+  @override
+  void applyUserOffset(double delta) {
+    if (!listShouldScroll &&
+        (!(extent.isAtMin || extent.isAtMax) ||
+          (extent.isAtMin && delta < 0) ||
+          (extent.isAtMax && delta > 0))) {
+      extent.addPixelDelta(-delta, context.notificationContext!);
+    } else {
+      super.applyUserOffset(delta);
+    }
+  }
+
+  @override
+  void goBallistic(double velocity) {
+    if (velocity == 0.0 ||
+       (velocity < 0.0 && listShouldScroll) ||
+       (velocity > 0.0 && extent.isAtMax)) {
+      super.goBallistic(velocity);
+      return;
+    }
+    // Scrollable expects that we will dispose of its current _dragCancelCallback
+    _dragCancelCallback?.call();
+    _dragCancelCallback = null;
+
+    // The iOS bouncing simulation just isn't right here - once we delegate
+    // the ballistic back to the ScrollView, it will use the right simulation.
+    final Simulation simulation = ClampingScrollSimulation(
+      position: extent.currentExtent,
+      velocity: velocity,
+      tolerance: physics.tolerance,
+    );
+
+    final AnimationController ballisticController = AnimationController.unbounded(
+      debugLabel: objectRuntimeType(this, '_DraggableScrollableSheetPosition'),
+      vsync: context.vsync,
+    );
+    double lastDelta = 0;
+    void _tick() {
+      final double delta = ballisticController.value - lastDelta;
+      lastDelta = ballisticController.value;
+      extent.addPixelDelta(delta, context.notificationContext!);
+      if ((velocity > 0 && extent.isAtMax) || (velocity < 0 && extent.isAtMin)) {
+        // Make sure we pass along enough velocity to keep scrolling - otherwise
+        // we just "bounce" off the top making it look like the list doesn't
+        // have more to scroll.
+        velocity = ballisticController.velocity + (physics.tolerance.velocity * ballisticController.velocity.sign);
+        super.goBallistic(velocity);
+        ballisticController.stop();
+      } else if (ballisticController.isCompleted) {
+        super.goBallistic(0);
+      }
+    }
+
+    ballisticController
+      ..addListener(_tick)
+      ..animateWith(simulation).whenCompleteOrCancel(
+        ballisticController.dispose,
+      );
+  }
+
+  @override
+  Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) {
+    // Save this so we can call it later if we have to [goBallistic] on our own.
+    _dragCancelCallback = dragCancelCallback;
+    return super.drag(details, dragCancelCallback);
+  }
+}
+
+/// A widget that can notify a descendent [DraggableScrollableSheet] that it
+/// should reset its position to the initial state.
+///
+/// The [Scaffold] uses this widget to notify a persistent bottom sheet that
+/// the user has tapped back if the sheet has started to cover more of the body
+/// than when at its initial position. This is important for users of assistive
+/// technology, where dragging may be difficult to communicate.
+class DraggableScrollableActuator extends StatelessWidget {
+  /// Creates a widget that can notify descendent [DraggableScrollableSheet]s
+  /// to reset to their initial position.
+  ///
+  /// The [child] parameter is required.
+  DraggableScrollableActuator({
+    Key? key,
+    required this.child,
+  }) : super(key: key);
+
+  /// This child's [DraggableScrollableSheet] descendant will be reset when the
+  /// [reset] method is applied to a context that includes it.
+  ///
+  /// Must not be null.
+  final Widget child;
+
+  final _ResetNotifier _notifier = _ResetNotifier();
+
+  /// Notifies any descendant [DraggableScrollableSheet] that it should reset
+  /// to its initial position.
+  ///
+  /// Returns `true` if a [DraggableScrollableActuator] is available and
+  /// some [DraggableScrollableSheet] is listening for updates, `false`
+  /// otherwise.
+  static bool reset(BuildContext context) {
+    final _InheritedResetNotifier? notifier = context.dependOnInheritedWidgetOfExactType<_InheritedResetNotifier>();
+    if (notifier == null) {
+      return false;
+    }
+    return notifier._sendReset();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _InheritedResetNotifier(child: child, notifier: _notifier);
+  }
+}
+
+/// A [ChangeNotifier] to use with [InheritedResetNotifier] to notify
+/// descendants that they should reset to initial state.
+class _ResetNotifier extends ChangeNotifier {
+  /// Whether someone called [sendReset] or not.
+  ///
+  /// This flag should be reset after checking it.
+  bool _wasCalled = false;
+
+  /// Fires a reset notification to descendants.
+  ///
+  /// Returns false if there are no listeners.
+  bool sendReset() {
+    if (!hasListeners) {
+      return false;
+    }
+    _wasCalled = true;
+    notifyListeners();
+    return true;
+  }
+}
+
+class _InheritedResetNotifier extends InheritedNotifier<_ResetNotifier> {
+  /// Creates an [InheritedNotifier] that the [DraggableScrollableSheet] will
+  /// listen to for an indication that it should change its extent.
+  ///
+  /// The [child] and [notifier] properties must not be null.
+  const _InheritedResetNotifier({
+    Key? key,
+    required Widget child,
+    required _ResetNotifier notifier,
+  }) : super(key: key, child: child, notifier: notifier);
+
+  bool _sendReset() => notifier!.sendReset();
+
+  /// Specifies whether the [DraggableScrollableSheet] should reset to its
+  /// initial position.
+  ///
+  /// Returns true if the notifier requested a reset, false otherwise.
+  static bool shouldReset(BuildContext context) {
+    final InheritedWidget? widget = context.dependOnInheritedWidgetOfExactType<_InheritedResetNotifier>();
+    if (widget == null) {
+      return false;
+    }
+    assert(widget is _InheritedResetNotifier);
+    final _InheritedResetNotifier inheritedNotifier = widget as _InheritedResetNotifier;
+    final bool wasCalled = inheritedNotifier.notifier!._wasCalled;
+    inheritedNotifier.notifier!._wasCalled = false;
+    return wasCalled;
+  }
+}
diff --git a/lib/src/widgets/dual_transition_builder.dart b/lib/src/widgets/dual_transition_builder.dart
new file mode 100644
index 0000000..7da4d28
--- /dev/null
+++ b/lib/src/widgets/dual_transition_builder.dart
@@ -0,0 +1,198 @@
+// Copyright 2014 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.
+
+import 'basic.dart';
+import 'framework.dart';
+
+/// Builder callback used by [DualTransitionBuilder].
+///
+/// The builder is expected to return a transition powered by the provided
+/// `animation` and wrapping the provided `child`.
+///
+/// The `animation` provided to the builder always runs forward from 0.0 to 1.0.
+typedef AnimatedTransitionBuilder = Widget Function(
+  BuildContext context,
+  Animation<double> animation,
+  Widget? child,
+);
+
+/// A transition builder that animates its [child] based on the
+/// [AnimationStatus] of the provided [animation].
+///
+/// This widget can be used to specify different enter and exit transitions for
+/// a [child].
+///
+/// While the [animation] runs forward, the [child] is animated according to
+/// [forwardBuilder] and while the [animation] is running in reverse, it is
+/// animated according to [reverseBuilder].
+///
+/// Using this builder allows the widget tree to maintain its shape by nesting
+/// the enter and exit transitions. This ensures that no state information of
+/// any descendant widget is lost when the transition starts or completes.
+class DualTransitionBuilder extends StatefulWidget {
+  /// Creates a [DualTransitionBuilder].
+  ///
+  /// The [animation], [forwardBuilder], and [reverseBuilder] arguments are
+  /// required and must not be null.
+  const DualTransitionBuilder({
+    Key? key,
+    required this.animation,
+    required this.forwardBuilder,
+    required this.reverseBuilder,
+    this.child,
+  }) : assert(animation != null),
+       assert(forwardBuilder != null),
+       assert(reverseBuilder != null),
+       super(key: key);
+
+  /// The animation that drives the [child]'s transition.
+  ///
+  /// When this animation runs forward, the [child] transitions as specified by
+  /// [forwardBuilder]. When it runs in reverse, the child transitions according
+  /// to [reverseBuilder].
+  final Animation<double> animation;
+
+  /// A builder for the transition that makes [child] appear on screen.
+  ///
+  /// The [child] should be fully visible when the provided `animation` reaches
+  /// 1.0.
+  ///
+  /// The `animation` provided to this builder is running forward from 0.0 to
+  /// 1.0 when [animation] runs _forward_. When [animation] runs in reverse,
+  /// the given `animation` is set to [kAlwaysCompleteAnimation].
+  ///
+  /// See also:
+  ///
+  ///  * [reverseBuilder], which builds the transition for making the [child]
+  ///   disappear from the screen.
+  final AnimatedTransitionBuilder forwardBuilder;
+
+  /// A builder for a transition that makes [child] disappear from the screen.
+  ///
+  /// The [child] should be fully invisible when the provided `animation`
+  /// reaches 1.0.
+  ///
+  /// The `animation` provided to this builder is running forward from 0.0 to
+  /// 1.0 when [animation] runs in _reverse_. When [animation] runs forward,
+  /// the given `animation` is set to [kAlwaysDismissedAnimation].
+  ///
+  /// See also:
+  ///
+  ///  * [forwardBuilder], which builds the transition for making the [child]
+  ///    appear on screen.
+  final AnimatedTransitionBuilder reverseBuilder;
+
+  /// The widget below this [DualTransitionBuilder] in the tree.
+  ///
+  /// This child widget will be wrapped by the transitions built by
+  /// [forwardBuilder] and [reverseBuilder].
+  final Widget? child;
+
+  @override
+  State<DualTransitionBuilder> createState() => _DualTransitionBuilderState();
+}
+
+class _DualTransitionBuilderState extends State<DualTransitionBuilder> {
+  late AnimationStatus _effectiveAnimationStatus;
+  final ProxyAnimation _forwardAnimation = ProxyAnimation();
+  final ProxyAnimation _reverseAnimation = ProxyAnimation();
+
+  @override
+  void initState() {
+    super.initState();
+    _effectiveAnimationStatus = widget.animation.status;
+    widget.animation.addStatusListener(_animationListener);
+    _updateAnimations();
+  }
+
+  void _animationListener(AnimationStatus animationStatus) {
+    final AnimationStatus oldEffective = _effectiveAnimationStatus;
+    _effectiveAnimationStatus = _calculateEffectiveAnimationStatus(
+      lastEffective: _effectiveAnimationStatus,
+      current: animationStatus,
+    );
+    if (oldEffective != _effectiveAnimationStatus) {
+      _updateAnimations();
+    }
+  }
+
+  @override
+  void didUpdateWidget(DualTransitionBuilder oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.animation != widget.animation) {
+      oldWidget.animation.removeStatusListener(_animationListener);
+      widget.animation.addStatusListener(_animationListener);
+      _animationListener(widget.animation.status);
+    }
+  }
+
+  // When a transition is interrupted midway we just want to play the ongoing
+  // animation in reverse. Switching to the actual reverse transition would
+  // yield a disjoint experience since the forward and reverse transitions are
+  // very different.
+  AnimationStatus _calculateEffectiveAnimationStatus({
+    required AnimationStatus lastEffective,
+    required AnimationStatus current,
+  }) {
+    assert(current != null);
+    assert(lastEffective != null);
+    switch (current) {
+      case AnimationStatus.dismissed:
+      case AnimationStatus.completed:
+        return current;
+      case AnimationStatus.forward:
+        switch (lastEffective) {
+          case AnimationStatus.dismissed:
+          case AnimationStatus.completed:
+          case AnimationStatus.forward:
+            return current;
+          case AnimationStatus.reverse:
+            return lastEffective;
+        }
+      case AnimationStatus.reverse:
+        switch (lastEffective) {
+          case AnimationStatus.dismissed:
+          case AnimationStatus.completed:
+          case AnimationStatus.reverse:
+            return current;
+          case AnimationStatus.forward:
+            return lastEffective;
+        }
+    }
+  }
+
+  void _updateAnimations() {
+    switch (_effectiveAnimationStatus) {
+      case AnimationStatus.dismissed:
+      case AnimationStatus.forward:
+        _forwardAnimation.parent = widget.animation;
+        _reverseAnimation.parent = kAlwaysDismissedAnimation;
+        break;
+      case AnimationStatus.reverse:
+      case AnimationStatus.completed:
+        _forwardAnimation.parent = kAlwaysCompleteAnimation;
+        _reverseAnimation.parent = ReverseAnimation(widget.animation);
+        break;
+    }
+  }
+
+  @override
+  void dispose() {
+    widget.animation.removeStatusListener(_animationListener);
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return widget.forwardBuilder(
+      context,
+      _forwardAnimation,
+      widget.reverseBuilder(
+        context,
+        _reverseAnimation,
+        widget.child,
+      ),
+    );
+  }
+}
diff --git a/lib/src/widgets/editable_text.dart b/lib/src/widgets/editable_text.dart
new file mode 100644
index 0000000..fb9d01c
--- /dev/null
+++ b/lib/src/widgets/editable_text.dart
@@ -0,0 +1,3047 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:math' as math;
+import 'package:flute/ui.dart' as ui hide TextStyle;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart' show DragStartBehavior;
+import 'package:flute/painting.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/services.dart';
+
+import 'autofill.dart';
+import 'automatic_keep_alive.dart';
+import 'basic.dart';
+import 'binding.dart';
+import 'constants.dart';
+import 'debug.dart';
+import 'focus_manager.dart';
+import 'focus_scope.dart';
+import 'framework.dart';
+import 'localizations.dart';
+import 'media_query.dart';
+import 'scroll_controller.dart';
+import 'scroll_physics.dart';
+import 'scrollable.dart';
+import 'text.dart';
+import 'text_selection.dart';
+import 'ticker_provider.dart';
+
+export 'package:flute/rendering.dart' show SelectionChangedCause;
+export 'package:flute/services.dart' show TextEditingValue, TextSelection, TextInputType, SmartQuotesType, SmartDashesType;
+
+/// Signature for the callback that reports when the user changes the selection
+/// (including the cursor location).
+typedef SelectionChangedCallback = void Function(TextSelection selection, SelectionChangedCause? cause);
+
+/// Signature for the callback that reports the app private command results.
+typedef AppPrivateCommandCallback = void Function(String, Map<String, dynamic>);
+
+// The time it takes for the cursor to fade from fully opaque to fully
+// transparent and vice versa. A full cursor blink, from transparent to opaque
+// to transparent, is twice this duration.
+const Duration _kCursorBlinkHalfPeriod = Duration(milliseconds: 500);
+
+// The time the cursor is static in opacity before animating to become
+// transparent.
+const Duration _kCursorBlinkWaitForStart = Duration(milliseconds: 150);
+
+// Number of cursor ticks during which the most recently entered character
+// is shown in an obscured text field.
+const int _kObscureShowLatestCharCursorTicks = 3;
+
+/// A controller for an editable text field.
+///
+/// Whenever the user modifies a text field with an associated
+/// [TextEditingController], the text field updates [value] and the controller
+/// notifies its listeners. Listeners can then read the [text] and [selection]
+/// properties to learn what the user has typed or how the selection has been
+/// updated.
+///
+/// Similarly, if you modify the [text] or [selection] properties, the text
+/// field will be notified and will update itself appropriately.
+///
+/// A [TextEditingController] can also be used to provide an initial value for a
+/// text field. If you build a text field with a controller that already has
+/// [text], the text field will use that text as its initial value.
+///
+/// The [value] (as well as [text] and [selection]) of this controller can be
+/// updated from within a listener added to this controller. Be aware of
+/// infinite loops since the listener will also be notified of the changes made
+/// from within itself. Modifying the composing region from within a listener
+/// can also have a bad interaction with some input methods. Gboard, for
+/// example, will try to restore the composing region of the text if it was
+/// modified programmatically, creating an infinite loop of communications
+/// between the framework and the input method. Consider using
+/// [TextInputFormatter]s instead for as-you-type text modification.
+///
+/// If both the [text] or [selection] properties need to be changed, set the
+/// controller's [value] instead.
+///
+/// Remember to [dispose] of the [TextEditingController] when it is no longer
+/// needed. This will ensure we discard any resources used by the object.
+/// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+/// This example creates a [TextField] with a [TextEditingController] whose
+/// change listener forces the entered text to be lower case and keeps the
+/// cursor at the end of the input.
+///
+/// ```dart
+/// final _controller = TextEditingController();
+///
+/// void initState() {
+///   super.initState();
+///   _controller.addListener(() {
+///     final text = _controller.text.toLowerCase();
+///     _controller.value = _controller.value.copyWith(
+///       text: text,
+///       selection: TextSelection(baseOffset: text.length, extentOffset: text.length),
+///       composing: TextRange.empty,
+///     );
+///   });
+/// }
+///
+/// void dispose() {
+///   _controller.dispose();
+///   super.dispose();
+/// }
+///
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: Container(
+///      alignment: Alignment.center,
+///       padding: const EdgeInsets.all(6),
+///       child: TextFormField(
+///         controller: _controller,
+///         decoration: InputDecoration(border: OutlineInputBorder()),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [TextField], which is a Material Design text field that can be controlled
+///    with a [TextEditingController].
+///  * [EditableText], which is a raw region of editable text that can be
+///    controlled with a [TextEditingController].
+///  * Learn how to use a [TextEditingController] in one of our [cookbook recipes](https://flutter.dev/docs/cookbook/forms/text-field-changes#2-use-a-texteditingcontroller).
+class TextEditingController extends ValueNotifier<TextEditingValue> {
+  /// Creates a controller for an editable text field.
+  ///
+  /// This constructor treats a null [text] argument as if it were the empty
+  /// string.
+  TextEditingController({ String? text })
+    : super(text == null ? TextEditingValue.empty : TextEditingValue(text: text));
+
+  /// Creates a controller for an editable text field from an initial [TextEditingValue].
+  ///
+  /// This constructor treats a null [value] argument as if it were
+  /// [TextEditingValue.empty].
+  TextEditingController.fromValue(TextEditingValue? value)
+    : assert(
+        value == null || !value.composing.isValid || value.isComposingRangeValid,
+        'New TextEditingValue $value has an invalid non-empty composing range '
+        '${value.composing}. It is recommended to use a valid composing range, '
+        'even for readonly text fields',
+      ),
+      super(value ?? TextEditingValue.empty);
+
+  /// The current string the user is editing.
+  String get text => value.text;
+  /// Setting this will notify all the listeners of this [TextEditingController]
+  /// that they need to update (it calls [notifyListeners]). For this reason,
+  /// this value should only be set between frames, e.g. in response to user
+  /// actions, not during the build, layout, or paint phases.
+  ///
+  /// This property can be set from a listener added to this
+  /// [TextEditingController]; however, one should not also set [selection]
+  /// in a separate statement. To change both the [text] and the [selection]
+  /// change the controller's [value].
+  set text(String newText) {
+    value = value.copyWith(
+      text: newText,
+      selection: const TextSelection.collapsed(offset: -1),
+      composing: TextRange.empty,
+    );
+  }
+
+  @override
+  set value(TextEditingValue newValue) {
+    assert(
+      !newValue.composing.isValid || newValue.isComposingRangeValid,
+      'New TextEditingValue $newValue has an invalid non-empty composing range '
+      '${newValue.composing}. It is recommended to use a valid composing range, '
+      'even for readonly text fields',
+    );
+    super.value = newValue;
+  }
+
+  /// Builds [TextSpan] from current editing value.
+  ///
+  /// By default makes text in composing range appear as underlined. Descendants
+  /// can override this method to customize appearance of text.
+  TextSpan buildTextSpan({TextStyle? style , required bool withComposing}) {
+    assert(!value.composing.isValid || !withComposing || value.isComposingRangeValid);
+    // If the composing range is out of range for the current text, ignore it to
+    // preserve the tree integrity, otherwise in release mode a RangeError will
+    // be thrown and this EditableText will be built with a broken subtree.
+    if (!value.isComposingRangeValid || !withComposing) {
+      return TextSpan(style: style, text: text);
+    }
+    final TextStyle composingStyle = style!.merge(
+      const TextStyle(decoration: TextDecoration.underline),
+    );
+    return TextSpan(
+      style: style,
+      children: <TextSpan>[
+        TextSpan(text: value.composing.textBefore(value.text)),
+        TextSpan(
+          style: composingStyle,
+          text: value.composing.textInside(value.text),
+        ),
+        TextSpan(text: value.composing.textAfter(value.text)),
+    ]);
+  }
+
+  /// The currently selected [text].
+  ///
+  /// If the selection is collapsed, then this property gives the offset of the
+  /// cursor within the text.
+  TextSelection get selection => value.selection;
+  /// Setting this will notify all the listeners of this [TextEditingController]
+  /// that they need to update (it calls [notifyListeners]). For this reason,
+  /// this value should only be set between frames, e.g. in response to user
+  /// actions, not during the build, layout, or paint phases.
+  ///
+  /// This property can be set from a listener added to this
+  /// [TextEditingController]; however, one should not also set [text]
+  /// in a separate statement. To change both the [text] and the [selection]
+  /// change the controller's [value].
+  ///
+  /// If the new selection if of non-zero length, or is outside the composing
+  /// range, the composing composing range is cleared.
+  set selection(TextSelection newSelection) {
+    if (!isSelectionWithinTextBounds(newSelection))
+      throw FlutterError('invalid text selection: $newSelection');
+    final TextRange newComposing =
+        newSelection.isCollapsed && _isSelectionWithinComposingRange(newSelection)
+            ? value.composing
+            : TextRange.empty;
+    value = value.copyWith(selection: newSelection, composing: newComposing);
+  }
+
+  /// Set the [value] to empty.
+  ///
+  /// After calling this function, [text] will be the empty string and the
+  /// selection will be collapsed at zero offset.
+  ///
+  /// Calling this will notify all the listeners of this [TextEditingController]
+  /// that they need to update (it calls [notifyListeners]). For this reason,
+  /// this method should only be called between frames, e.g. in response to user
+  /// actions, not during the build, layout, or paint phases.
+  void clear() {
+    value = const TextEditingValue(selection: TextSelection.collapsed(offset: 0));
+  }
+
+  /// Set the composing region to an empty range.
+  ///
+  /// The composing region is the range of text that is still being composed.
+  /// Calling this function indicates that the user is done composing that
+  /// region.
+  ///
+  /// Calling this will notify all the listeners of this [TextEditingController]
+  /// that they need to update (it calls [notifyListeners]). For this reason,
+  /// this method should only be called between frames, e.g. in response to user
+  /// actions, not during the build, layout, or paint phases.
+  void clearComposing() {
+    value = value.copyWith(composing: TextRange.empty);
+  }
+
+  /// Check that the [selection] is inside of the bounds of [text].
+  bool isSelectionWithinTextBounds(TextSelection selection) {
+    return selection.start <= text.length && selection.end <= text.length;
+  }
+
+  /// Check that the [selection] is inside of the composing range.
+  bool _isSelectionWithinComposingRange(TextSelection selection) {
+    return selection.start >= value.composing.start && selection.end <= value.composing.end;
+  }
+}
+
+/// Toolbar configuration for [EditableText].
+///
+/// Toolbar is a context menu that will show up when user right click or long
+/// press the [EditableText]. It includes several options: cut, copy, paste,
+/// and select all.
+///
+/// [EditableText] and its derived widgets have their own default [ToolbarOptions].
+/// Create a custom [ToolbarOptions] if you want explicit control over the toolbar
+/// option.
+class ToolbarOptions {
+  /// Create a toolbar configuration with given options.
+  ///
+  /// All options default to false if they are not explicitly set.
+  const ToolbarOptions({
+    this.copy = false,
+    this.cut = false,
+    this.paste = false,
+    this.selectAll = false,
+  }) : assert(copy != null),
+       assert(cut != null),
+       assert(paste != null),
+       assert(selectAll != null);
+
+  /// Whether to show copy option in toolbar.
+  ///
+  /// Defaults to false. Must not be null.
+  final bool copy;
+
+  /// Whether to show cut option in toolbar.
+  ///
+  /// If [EditableText.readOnly] is set to true, cut will be disabled regardless.
+  ///
+  /// Defaults to false. Must not be null.
+  final bool cut;
+
+  /// Whether to show paste option in toolbar.
+  ///
+  /// If [EditableText.readOnly] is set to true, paste will be disabled regardless.
+  ///
+  /// Defaults to false. Must not be null.
+  final bool paste;
+
+  /// Whether to show select all option in toolbar.
+  ///
+  /// Defaults to false. Must not be null.
+  final bool selectAll;
+}
+
+/// A basic text input field.
+///
+/// This widget interacts with the [TextInput] service to let the user edit the
+/// text it contains. It also provides scrolling, selection, and cursor
+/// movement. This widget does not provide any focus management (e.g.,
+/// tap-to-focus).
+///
+/// ## Handling User Input
+///
+/// Currently the user may change the text this widget contains via keyboard or
+/// the text selection menu. When the user inserted or deleted text, you will be
+/// notified of the change and get a chance to modify the new text value:
+///
+/// * The [inputFormatters] will be first applied to the user input.
+///
+/// * The [controller]'s [TextEditingController.value] will be updated with the
+///   formatted result, and the [controller]'s listeners will be notified.
+///
+/// * The [onChanged] callback, if specified, will be called last.
+///
+/// ## Input Actions
+///
+/// A [TextInputAction] can be provided to customize the appearance of the
+/// action button on the soft keyboard for Android and iOS. The default action
+/// is [TextInputAction.done].
+///
+/// Many [TextInputAction]s are common between Android and iOS. However, if a
+/// [textInputAction] is provided that is not supported by the current
+/// platform in debug mode, an error will be thrown when the corresponding
+/// EditableText receives focus. For example, providing iOS's "emergencyCall"
+/// action when running on an Android device will result in an error when in
+/// debug mode. In release mode, incompatible [TextInputAction]s are replaced
+/// either with "unspecified" on Android, or "default" on iOS. Appropriate
+/// [textInputAction]s can be chosen by checking the current platform and then
+/// selecting the appropriate action.
+///
+/// ## Lifecycle
+///
+/// Upon completion of editing, like pressing the "done" button on the keyboard,
+/// two actions take place:
+///
+///   1st: Editing is finalized. The default behavior of this step includes
+///   an invocation of [onChanged]. That default behavior can be overridden.
+///   See [onEditingComplete] for details.
+///
+///   2nd: [onSubmitted] is invoked with the user's input value.
+///
+/// [onSubmitted] can be used to manually move focus to another input widget
+/// when a user finishes with the currently focused input widget.
+///
+/// Rather than using this widget directly, consider using [TextField], which
+/// is a full-featured, material-design text input field with placeholder text,
+/// labels, and [Form] integration.
+///
+/// ## Gesture Events Handling
+///
+/// This widget provides rudimentary, platform-agnostic gesture handling for
+/// user actions such as tapping, long-pressing and scrolling when
+/// [rendererIgnoresPointer] is false (false by default). To tightly conform
+/// to the platform behavior with respect to input gestures in text fields, use
+/// [TextField] or [CupertinoTextField]. For custom selection behavior, call
+/// methods such as [RenderEditable.selectPosition],
+/// [RenderEditable.selectWord], etc. programmatically.
+///
+/// See also:
+///
+///  * [TextField], which is a full-featured, material-design text input field
+///    with placeholder text, labels, and [Form] integration.
+class EditableText extends StatefulWidget {
+  /// Creates a basic text input control.
+  ///
+  /// The [maxLines] property can be set to null to remove the restriction on
+  /// the number of lines. By default, it is one, meaning this is a single-line
+  /// text field. [maxLines] must be null or greater than zero.
+  ///
+  /// If [keyboardType] is not set or is null, its value will be inferred from
+  /// [autofillHints], if [autofillHints] is not empty. Otherwise it defaults to
+  /// [TextInputType.text] if [maxLines] is exactly one, and
+  /// [TextInputType.multiline] if [maxLines] is null or greater than one.
+  ///
+  /// The text cursor is not shown if [showCursor] is false or if [showCursor]
+  /// is null (the default) and [readOnly] is true.
+  ///
+  /// The [controller], [focusNode], [obscureText], [autocorrect], [autofocus],
+  /// [showSelectionHandles], [enableInteractiveSelection], [forceLine],
+  /// [style], [cursorColor], [cursorOpacityAnimates],[backgroundCursorColor],
+  /// [enableSuggestions], [paintCursorAboveText], [selectionHeightStyle],
+  /// [selectionWidthStyle], [textAlign], [dragStartBehavior], [scrollPadding],
+  /// [dragStartBehavior], [toolbarOptions], [rendererIgnoresPointer], and
+  /// [readOnly] arguments must not be null.
+  EditableText({
+    Key? key,
+    required this.controller,
+    required this.focusNode,
+    this.readOnly = false,
+    this.obscuringCharacter = '•',
+    this.obscureText = false,
+    this.autocorrect = true,
+    SmartDashesType? smartDashesType,
+    SmartQuotesType? smartQuotesType,
+    this.enableSuggestions = true,
+    required this.style,
+    StrutStyle? strutStyle,
+    required this.cursorColor,
+    required this.backgroundCursorColor,
+    this.textAlign = TextAlign.start,
+    this.textDirection,
+    this.locale,
+    this.textScaleFactor,
+    this.maxLines = 1,
+    this.minLines,
+    this.expands = false,
+    this.forceLine = true,
+    this.textHeightBehavior,
+    this.textWidthBasis = TextWidthBasis.parent,
+    this.autofocus = false,
+    bool? showCursor,
+    this.showSelectionHandles = false,
+    this.selectionColor,
+    this.selectionControls,
+    TextInputType? keyboardType,
+    this.textInputAction,
+    this.textCapitalization = TextCapitalization.none,
+    this.onChanged,
+    this.onEditingComplete,
+    this.onSubmitted,
+    this.onAppPrivateCommand,
+    this.onSelectionChanged,
+    this.onSelectionHandleTapped,
+    List<TextInputFormatter>? inputFormatters,
+    this.mouseCursor,
+    this.rendererIgnoresPointer = false,
+    this.cursorWidth = 2.0,
+    this.cursorHeight,
+    this.cursorRadius,
+    this.cursorOpacityAnimates = false,
+    this.cursorOffset,
+    this.paintCursorAboveText = false,
+    this.selectionHeightStyle = ui.BoxHeightStyle.tight,
+    this.selectionWidthStyle = ui.BoxWidthStyle.tight,
+    this.scrollPadding = const EdgeInsets.all(20.0),
+    this.keyboardAppearance = Brightness.light,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.enableInteractiveSelection = true,
+    this.scrollController,
+    this.scrollPhysics,
+    this.autocorrectionTextRectColor,
+    this.toolbarOptions = const ToolbarOptions(
+      copy: true,
+      cut: true,
+      paste: true,
+      selectAll: true,
+    ),
+    this.autofillHints,
+    this.clipBehavior = Clip.hardEdge,
+    this.restorationId,
+  }) : assert(controller != null),
+       assert(focusNode != null),
+       assert(obscuringCharacter != null && obscuringCharacter.length == 1),
+       assert(obscureText != null),
+       assert(autocorrect != null),
+       smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
+       smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
+       assert(enableSuggestions != null),
+       assert(showSelectionHandles != null),
+       assert(enableInteractiveSelection != null),
+       assert(readOnly != null),
+       assert(forceLine != null),
+       assert(style != null),
+       assert(cursorColor != null),
+       assert(cursorOpacityAnimates != null),
+       assert(paintCursorAboveText != null),
+       assert(backgroundCursorColor != null),
+       assert(selectionHeightStyle != null),
+       assert(selectionWidthStyle != null),
+       assert(textAlign != null),
+       assert(maxLines == null || maxLines > 0),
+       assert(minLines == null || minLines > 0),
+       assert(
+         (maxLines == null) || (minLines == null) || (maxLines >= minLines),
+         "minLines can't be greater than maxLines",
+       ),
+       assert(expands != null),
+       assert(
+         !expands || (maxLines == null && minLines == null),
+         'minLines and maxLines must be null when expands is true.',
+       ),
+       assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
+       assert(autofocus != null),
+       assert(rendererIgnoresPointer != null),
+       assert(scrollPadding != null),
+       assert(dragStartBehavior != null),
+       assert(toolbarOptions != null),
+       assert(clipBehavior != null),
+       assert(
+         !readOnly || autofillHints == null,
+         "Read-only fields can't have autofill hints.",
+       ),
+       _strutStyle = strutStyle,
+       keyboardType = keyboardType ?? _inferKeyboardType(autofillHints: autofillHints, maxLines: maxLines),
+       inputFormatters = maxLines == 1
+           ? <TextInputFormatter>[
+               FilteringTextInputFormatter.singleLineFormatter,
+               ...inputFormatters ?? const Iterable<TextInputFormatter>.empty(),
+             ]
+           : inputFormatters,
+       showCursor = showCursor ?? !readOnly,
+       super(key: key);
+
+  /// Controls the text being edited.
+  final TextEditingController controller;
+
+  /// Controls whether this widget has keyboard focus.
+  final FocusNode focusNode;
+
+  /// {@template flutter.widgets.editableText.obscuringCharacter}
+  /// Character used for obscuring text if [obscureText] is true.
+  ///
+  /// Must be only a single character.
+  ///
+  /// Defaults to the character U+2022 BULLET (•).
+  /// {@endtemplate}
+  final String obscuringCharacter;
+
+  /// {@template flutter.widgets.editableText.obscureText}
+  /// Whether to hide the text being edited (e.g., for passwords).
+  ///
+  /// When this is set to true, all the characters in the text field are
+  /// replaced by [obscuringCharacter].
+  ///
+  /// Defaults to false. Cannot be null.
+  /// {@endtemplate}
+  final bool obscureText;
+
+  /// {@macro flutter.dart:ui.textHeightBehavior}
+  final TextHeightBehavior? textHeightBehavior;
+
+  /// {@macro flutter.painting.textPainter.textWidthBasis}
+  final TextWidthBasis textWidthBasis;
+
+  /// {@template flutter.widgets.editableText.readOnly}
+  /// Whether the text can be changed.
+  ///
+  /// When this is set to true, the text cannot be modified
+  /// by any shortcut or keyboard operation. The text is still selectable.
+  ///
+  /// Defaults to false. Must not be null.
+  /// {@endtemplate}
+  final bool readOnly;
+
+  /// Whether the text will take the full width regardless of the text width.
+  ///
+  /// When this is set to false, the width will be based on text width, which
+  /// will also be affected by [textWidthBasis].
+  ///
+  /// Defaults to true. Must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [textWidthBasis], which controls the calculation of text width.
+  final bool forceLine;
+
+  /// Configuration of toolbar options.
+  ///
+  /// By default, all options are enabled. If [readOnly] is true,
+  /// paste and cut will be disabled regardless.
+  final ToolbarOptions toolbarOptions;
+
+  /// Whether to show selection handles.
+  ///
+  /// When a selection is active, there will be two handles at each side of
+  /// boundary, or one handle if the selection is collapsed. The handles can be
+  /// dragged to adjust the selection.
+  ///
+  /// See also:
+  ///
+  ///  * [showCursor], which controls the visibility of the cursor.
+  final bool showSelectionHandles;
+
+  /// {@template flutter.widgets.editableText.showCursor}
+  /// Whether to show cursor.
+  ///
+  /// The cursor refers to the blinking caret when the [EditableText] is focused.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [showSelectionHandles], which controls the visibility of the selection handles.
+  final bool showCursor;
+
+  /// {@template flutter.widgets.editableText.autocorrect}
+  /// Whether to enable autocorrection.
+  ///
+  /// Defaults to true. Cannot be null.
+  /// {@endtemplate}
+  final bool autocorrect;
+
+  /// {@macro flutter.services.TextInputConfiguration.smartDashesType}
+  final SmartDashesType smartDashesType;
+
+  /// {@macro flutter.services.TextInputConfiguration.smartQuotesType}
+  final SmartQuotesType smartQuotesType;
+
+  /// {@macro flutter.services.TextInputConfiguration.enableSuggestions}
+  final bool enableSuggestions;
+
+  /// The text style to use for the editable text.
+  final TextStyle style;
+
+  /// {@template flutter.widgets.editableText.strutStyle}
+  /// The strut style used for the vertical layout.
+  ///
+  /// [StrutStyle] is used to establish a predictable vertical layout.
+  /// Since fonts may vary depending on user input and due to font
+  /// fallback, [StrutStyle.forceStrutHeight] is enabled by default
+  /// to lock all lines to the height of the base [TextStyle], provided by
+  /// [style]. This ensures the typed text fits within the allotted space.
+  ///
+  /// If null, the strut used will is inherit values from the [style] and will
+  /// have [StrutStyle.forceStrutHeight] set to true. When no [style] is
+  /// passed, the theme's [TextStyle] will be used to generate [strutStyle]
+  /// instead.
+  ///
+  /// To disable strut-based vertical alignment and allow dynamic vertical
+  /// layout based on the glyphs typed, use [StrutStyle.disabled].
+  ///
+  /// Flutter's strut is based on [typesetting strut](https://en.wikipedia.org/wiki/Strut_(typesetting))
+  /// and CSS's [line-height](https://www.w3.org/TR/CSS2/visudet.html#line-height).
+  /// {@endtemplate}
+  ///
+  /// Within editable text and text fields, [StrutStyle] will not use its standalone
+  /// default values, and will instead inherit omitted/null properties from the
+  /// [TextStyle] instead. See [StrutStyle.inheritFromTextStyle].
+  StrutStyle get strutStyle {
+    if (_strutStyle == null) {
+      return StrutStyle.fromTextStyle(style, forceStrutHeight: true);
+    }
+    return _strutStyle!.inheritFromTextStyle(style);
+  }
+  final StrutStyle? _strutStyle;
+
+  /// {@template flutter.widgets.editableText.textAlign}
+  /// How the text should be aligned horizontally.
+  ///
+  /// Defaults to [TextAlign.start] and cannot be null.
+  /// {@endtemplate}
+  final TextAlign textAlign;
+
+  /// {@template flutter.widgets.editableText.textDirection}
+  /// The directionality of the text.
+  ///
+  /// This decides how [textAlign] values like [TextAlign.start] and
+  /// [TextAlign.end] are interpreted.
+  ///
+  /// This is also used to disambiguate how to render bidirectional text. For
+  /// example, if the text is an English phrase followed by a Hebrew phrase,
+  /// in a [TextDirection.ltr] context the English phrase will be on the left
+  /// and the Hebrew phrase to its right, while in a [TextDirection.rtl]
+  /// context, the English phrase will be on the right and the Hebrew phrase on
+  /// its left.
+  ///
+  /// When LTR text is entered into an RTL field, or RTL text is entered into an
+  /// LTR field, [LRM](https://en.wikipedia.org/wiki/Left-to-right_mark) or
+  /// [RLM](https://en.wikipedia.org/wiki/Right-to-left_mark) characters will be
+  /// inserted alongside whitespace characters, respectively. This is to
+  /// eliminate ambiguous directionality in whitespace and ensure proper caret
+  /// placement. These characters will affect the length of the string and may
+  /// need to be parsed out when doing things like string comparison with other
+  /// text.
+  ///
+  /// Defaults to the ambient [Directionality], if any.
+  /// {@endtemplate}
+  final TextDirection? textDirection;
+
+  /// {@template flutter.widgets.editableText.textCapitalization}
+  /// Configures how the platform keyboard will select an uppercase or
+  /// lowercase keyboard.
+  ///
+  /// Only supports text keyboards, other keyboard types will ignore this
+  /// configuration. Capitalization is locale-aware.
+  ///
+  /// Defaults to [TextCapitalization.none]. Must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [TextCapitalization], for a description of each capitalization behavior.
+  ///
+  /// {@endtemplate}
+  final TextCapitalization textCapitalization;
+
+  /// Used to select a font when the same Unicode character can
+  /// be rendered differently, depending on the locale.
+  ///
+  /// It's rarely necessary to set this property. By default its value
+  /// is inherited from the enclosing app with `Localizations.localeOf(context)`.
+  ///
+  /// See [RenderEditable.locale] for more information.
+  final Locale? locale;
+
+  /// {@template flutter.widgets.editableText.textScaleFactor}
+  /// The number of font pixels for each logical pixel.
+  ///
+  /// For example, if the text scale factor is 1.5, text will be 50% larger than
+  /// the specified font size.
+  ///
+  /// Defaults to the [MediaQueryData.textScaleFactor] obtained from the ambient
+  /// [MediaQuery], or 1.0 if there is no [MediaQuery] in scope.
+  /// {@endtemplate}
+  final double? textScaleFactor;
+
+  /// The color to use when painting the cursor.
+  ///
+  /// Cannot be null.
+  final Color cursorColor;
+
+  /// The color to use when painting the autocorrection Rect.
+  ///
+  /// For [CupertinoTextField]s, the value is set to the ambient
+  /// [CupertinoThemeData.primaryColor] with 20% opacity. For [TextField]s, the
+  /// value is null on non-iOS platforms and the same color used in [CupertinoTextField]
+  /// on iOS.
+  ///
+  /// Currently the autocorrection Rect only appears on iOS.
+  ///
+  /// Defaults to null, which disables autocorrection Rect painting.
+  final Color? autocorrectionTextRectColor;
+
+  /// The color to use when painting the background cursor aligned with the text
+  /// while rendering the floating cursor.
+  ///
+  /// Cannot be null. By default it is the disabled grey color from
+  /// CupertinoColors.
+  final Color backgroundCursorColor;
+
+  /// {@template flutter.widgets.editableText.maxLines}
+  /// The maximum number of lines for the text to span, wrapping if necessary.
+  ///
+  /// If this is 1 (the default), the text will not wrap, but will scroll
+  /// horizontally instead.
+  ///
+  /// If this is null, there is no limit to the number of lines, and the text
+  /// container will start with enough vertical space for one line and
+  /// automatically grow to accommodate additional lines as they are entered.
+  ///
+  /// If this is not null, the value must be greater than zero, and it will lock
+  /// the input to the given number of lines and take up enough horizontal space
+  /// to accommodate that number of lines. Setting [minLines] as well allows the
+  /// input to grow between the indicated range.
+  ///
+  /// The full set of behaviors possible with [minLines] and [maxLines] are as
+  /// follows. These examples apply equally to `TextField`, `TextFormField`, and
+  /// `EditableText`.
+  ///
+  /// Input that occupies a single line and scrolls horizontally as needed.
+  /// ```dart
+  /// TextField()
+  /// ```
+  ///
+  /// Input whose height grows from one line up to as many lines as needed for
+  /// the text that was entered. If a height limit is imposed by its parent, it
+  /// will scroll vertically when its height reaches that limit.
+  /// ```dart
+  /// TextField(maxLines: null)
+  /// ```
+  ///
+  /// The input's height is large enough for the given number of lines. If
+  /// additional lines are entered the input scrolls vertically.
+  /// ```dart
+  /// TextField(maxLines: 2)
+  /// ```
+  ///
+  /// Input whose height grows with content between a min and max. An infinite
+  /// max is possible with `maxLines: null`.
+  /// ```dart
+  /// TextField(minLines: 2, maxLines: 4)
+  /// ```
+  /// {@endtemplate}
+  final int? maxLines;
+
+  /// {@template flutter.widgets.editableText.minLines}
+  /// The minimum number of lines to occupy when the content spans fewer lines.
+  ///
+  /// If this is null (default), text container starts with enough vertical space
+  /// for one line and grows to accommodate additional lines as they are entered.
+  ///
+  /// This can be used in combination with [maxLines] for a varying set of behaviors.
+  ///
+  /// If the value is set, it must be greater than zero. If the value is greater
+  /// than 1, [maxLines] should also be set to either null or greater than
+  /// this value.
+  ///
+  /// When [maxLines] is set as well, the height will grow between the indicated
+  /// range of lines. When [maxLines] is null, it will grow as high as needed,
+  /// starting from [minLines].
+  ///
+  /// A few examples of behaviors possible with [minLines] and [maxLines] are as follows.
+  /// These apply equally to `TextField`, `TextFormField`, `CupertinoTextField`,
+  /// and `EditableText`.
+  ///
+  /// Input that always occupies at least 2 lines and has an infinite max.
+  /// Expands vertically as needed.
+  /// ```dart
+  /// TextField(minLines: 2)
+  /// ```
+  ///
+  /// Input whose height starts from 2 lines and grows up to 4 lines at which
+  /// point the height limit is reached. If additional lines are entered it will
+  /// scroll vertically.
+  /// ```dart
+  /// TextField(minLines:2, maxLines: 4)
+  /// ```
+  ///
+  /// See the examples in [maxLines] for the complete picture of how [maxLines]
+  /// and [minLines] interact to produce various behaviors.
+  ///
+  /// Defaults to null.
+  /// {@endtemplate}
+  final int? minLines;
+
+  /// {@template flutter.widgets.editableText.expands}
+  /// Whether this widget's height will be sized to fill its parent.
+  ///
+  /// If set to true and wrapped in a parent widget like [Expanded] or
+  /// [SizedBox], the input will expand to fill the parent.
+  ///
+  /// [maxLines] and [minLines] must both be null when this is set to true,
+  /// otherwise an error is thrown.
+  ///
+  /// Defaults to false.
+  ///
+  /// See the examples in [maxLines] for the complete picture of how [maxLines],
+  /// [minLines], and [expands] interact to produce various behaviors.
+  ///
+  /// Input that matches the height of its parent:
+  /// ```dart
+  /// Expanded(
+  ///   child: TextField(maxLines: null, expands: true),
+  /// )
+  /// ```
+  /// {@endtemplate}
+  final bool expands;
+
+  /// {@template flutter.widgets.editableText.autofocus}
+  /// Whether this text field should focus itself if nothing else is already
+  /// focused.
+  ///
+  /// If true, the keyboard will open as soon as this text field obtains focus.
+  /// Otherwise, the keyboard is only shown after the user taps the text field.
+  ///
+  /// Defaults to false. Cannot be null.
+  /// {@endtemplate}
+  // See https://github.com/flutter/flutter/issues/7035 for the rationale for this
+  // keyboard behavior.
+  final bool autofocus;
+
+  /// The color to use when painting the selection.
+  ///
+  /// For [CupertinoTextField]s, the value is set to the ambient
+  /// [CupertinoThemeData.primaryColor] with 20% opacity. For [TextField]s, the
+  /// value is set to the ambient [ThemeData.textSelectionColor].
+  final Color? selectionColor;
+
+  /// {@template flutter.widgets.editableText.selectionControls}
+  /// Optional delegate for building the text selection handles and toolbar.
+  ///
+  /// The [EditableText] widget used on its own will not trigger the display
+  /// of the selection toolbar by itself. The toolbar is shown by calling
+  /// [EditableTextState.showToolbar] in response to an appropriate user event.
+  ///
+  /// See also:
+  ///
+  ///  * [CupertinoTextField], which wraps an [EditableText] and which shows the
+  ///    selection toolbar upon user events that are appropriate on the iOS
+  ///    platform.
+  ///  * [TextField], a Material Design themed wrapper of [EditableText], which
+  ///    shows the selection toolbar upon appropriate user events based on the
+  ///    user's platform set in [ThemeData.platform].
+  /// {@endtemplate}
+  final TextSelectionControls? selectionControls;
+
+  /// {@template flutter.widgets.editableText.keyboardType}
+  /// The type of keyboard to use for editing the text.
+  ///
+  /// Defaults to [TextInputType.text] if [maxLines] is one and
+  /// [TextInputType.multiline] otherwise.
+  /// {@endtemplate}
+  final TextInputType keyboardType;
+
+  /// The type of action button to use with the soft keyboard.
+  final TextInputAction? textInputAction;
+
+  /// {@template flutter.widgets.editableText.onChanged}
+  /// Called when the user initiates a change to the TextField's
+  /// value: when they have inserted or deleted text.
+  ///
+  /// This callback doesn't run when the TextField's text is changed
+  /// programmatically, via the TextField's [controller]. Typically it
+  /// isn't necessary to be notified of such changes, since they're
+  /// initiated by the app itself.
+  ///
+  /// To be notified of all changes to the TextField's text, cursor,
+  /// and selection, one can add a listener to its [controller] with
+  /// [TextEditingController.addListener].
+  ///
+  /// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+  ///
+  /// This example shows how onChanged could be used to check the TextField's
+  /// current value each time the user inserts or deletes a character.
+  ///
+  /// ```dart
+  /// TextEditingController _controller;
+  ///
+  /// void initState() {
+  ///   super.initState();
+  ///   _controller = TextEditingController();
+  /// }
+  ///
+  /// void dispose() {
+  ///   _controller.dispose();
+  ///   super.dispose();
+  /// }
+  ///
+  /// Widget build(BuildContext context) {
+  ///   return Scaffold(
+  ///     body: Column(
+  ///       mainAxisAlignment: MainAxisAlignment.center,
+  ///       children: <Widget>[
+  ///         const Text('What number comes next in the sequence?'),
+  ///         const Text('1, 1, 2, 3, 5, 8...?'),
+  ///         TextField(
+  ///           controller: _controller,
+  ///           onChanged: (String value) async {
+  ///             if (value != '13') {
+  ///               return;
+  ///             }
+  ///             await showDialog<void>(
+  ///               context: context,
+  ///               builder: (BuildContext context) {
+  ///                 return AlertDialog(
+  ///                   title: const Text('Thats correct!'),
+  ///                   content: Text ('13 is the right answer.'),
+  ///                   actions: <Widget>[
+  ///                     TextButton(
+  ///                       onPressed: () { Navigator.pop(context); },
+  ///                       child: const Text('OK'),
+  ///                     ),
+  ///                   ],
+  ///                 );
+  ///               },
+  ///             );
+  ///           },
+  ///         ),
+  ///       ],
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  /// {@endtemplate}
+  ///
+  /// ## Handling emojis and other complex characters
+  /// {@template flutter.widgets.EditableText.onChanged}
+  /// It's important to always use
+  /// [characters](https://pub.dev/packages/characters) when dealing with user
+  /// input text that may contain complex characters. This will ensure that
+  /// extended grapheme clusters and surrogate pairs are treated as single
+  /// characters, as they appear to the user.
+  ///
+  /// For example, when finding the length of some user input, use
+  /// `string.characters.length`. Do NOT use `string.length` or even
+  /// `string.runes.length`. For the complex character "👨‍👩‍👦", this
+  /// appears to the user as a single character, and `string.characters.length`
+  /// intuitively returns 1. On the other hand, `string.length` returns 8, and
+  /// `string.runes.length` returns 5!
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [inputFormatters], which are called before [onChanged]
+  ///    runs and can validate and change ("format") the input value.
+  ///  * [onEditingComplete], [onSubmitted], [onSelectionChanged]:
+  ///    which are more specialized input change notifications.
+  final ValueChanged<String>? onChanged;
+
+  /// {@template flutter.widgets.editableText.onEditingComplete}
+  /// Called when the user submits editable content (e.g., user presses the "done"
+  /// button on the keyboard).
+  ///
+  /// The default implementation of [onEditingComplete] executes 2 different
+  /// behaviors based on the situation:
+  ///
+  ///  - When a completion action is pressed, such as "done", "go", "send", or
+  ///    "search", the user's content is submitted to the [controller] and then
+  ///    focus is given up.
+  ///
+  ///  - When a non-completion action is pressed, such as "next" or "previous",
+  ///    the user's content is submitted to the [controller], but focus is not
+  ///    given up because developers may want to immediately move focus to
+  ///    another input widget within [onSubmitted].
+  ///
+  /// Providing [onEditingComplete] prevents the aforementioned default behavior.
+  /// {@endtemplate}
+  final VoidCallback? onEditingComplete;
+
+  /// {@template flutter.widgets.editableText.onSubmitted}
+  /// Called when the user indicates that they are done editing the text in the
+  /// field.
+  /// {@endtemplate}
+  final ValueChanged<String>? onSubmitted;
+
+  /// {@template flutter.widgets.editableText.onAppPrivateCommand}
+  /// This is used to receive a private command from the input method.
+  ///
+  /// Called when the result of [TextInputClient.performPrivateCommand] is
+  /// received.
+  ///
+  /// This can be used to provide domain-specific features that are only known
+  /// between certain input methods and their clients.
+  ///
+  /// See also:
+  ///   * [https://developer.android.com/reference/android/view/inputmethod/InputConnection#performPrivateCommand(java.lang.String,%20android.os.Bundle)],
+  ///     which is the Android documentation for performPrivateCommand, used to
+  ///     send a command from the input method.
+  ///   * [https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#sendAppPrivateCommand],
+  ///     which is the Android documentation for sendAppPrivateCommand, used to
+  ///     send a command to the input method.
+  /// {@endtemplate}
+  final AppPrivateCommandCallback? onAppPrivateCommand;
+
+  /// {@template flutter.widgets.editableText.onSelectionChanged}
+  /// Called when the user changes the selection of text (including the cursor
+  /// location).
+  /// {@endtemplate}
+  final SelectionChangedCallback? onSelectionChanged;
+
+  /// {@macro flutter.widgets.TextSelectionOverlay.onSelectionHandleTapped}
+  final VoidCallback? onSelectionHandleTapped;
+
+  /// {@template flutter.widgets.editableText.inputFormatters}
+  /// Optional input validation and formatting overrides.
+  ///
+  /// Formatters are run in the provided order when the text input changes. When
+  /// this parameter changes, the new formatters will not be applied until the
+  /// next time the user inserts or deletes text.
+  /// {@endtemplate}
+  final List<TextInputFormatter>? inputFormatters;
+
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If this property is null, [SystemMouseCursors.text] will be used.
+  ///
+  /// The [mouseCursor] is the only property of [EditableText] that controls the
+  /// appearance of the mouse pointer. All other properties related to "cursor"
+  /// stands for the text cursor, which is usually a blinking vertical line at
+  /// the editing position.
+  final MouseCursor? mouseCursor;
+
+  /// If true, the [RenderEditable] created by this widget will not handle
+  /// pointer events, see [RenderEditable] and [RenderEditable.ignorePointer].
+  ///
+  /// This property is false by default.
+  final bool rendererIgnoresPointer;
+
+  /// {@template flutter.widgets.editableText.cursorWidth}
+  /// How thick the cursor will be.
+  ///
+  /// Defaults to 2.0.
+  ///
+  /// The cursor will draw under the text. The cursor width will extend
+  /// to the right of the boundary between characters for left-to-right text
+  /// and to the left for right-to-left text. This corresponds to extending
+  /// downstream relative to the selected position. Negative values may be used
+  /// to reverse this behavior.
+  /// {@endtemplate}
+  final double cursorWidth;
+
+  /// {@template flutter.widgets.editableText.cursorHeight}
+  /// How tall the cursor will be.
+  ///
+  /// If this property is null, [RenderEditable.preferredLineHeight] will be used.
+  /// {@endtemplate}
+  final double? cursorHeight;
+
+  /// {@template flutter.widgets.editableText.cursorRadius}
+  /// How rounded the corners of the cursor should be.
+  ///
+  /// By default, the cursor has no radius.
+  /// {@endtemplate}
+  final Radius? cursorRadius;
+
+  /// Whether the cursor will animate from fully transparent to fully opaque
+  /// during each cursor blink.
+  ///
+  /// By default, the cursor opacity will animate on iOS platforms and will not
+  /// animate on Android platforms.
+  final bool cursorOpacityAnimates;
+
+  ///{@macro flutter.rendering.RenderEditable.cursorOffset}
+  final Offset? cursorOffset;
+
+  ///{@macro flutter.rendering.RenderEditable.paintCursorAboveText}
+  final bool paintCursorAboveText;
+
+  /// Controls how tall the selection highlight boxes are computed to be.
+  ///
+  /// See [ui.BoxHeightStyle] for details on available styles.
+  final ui.BoxHeightStyle selectionHeightStyle;
+
+  /// Controls how wide the selection highlight boxes are computed to be.
+  ///
+  /// See [ui.BoxWidthStyle] for details on available styles.
+  final ui.BoxWidthStyle selectionWidthStyle;
+
+  /// The appearance of the keyboard.
+  ///
+  /// This setting is only honored on iOS devices.
+  ///
+  /// Defaults to [Brightness.light].
+  final Brightness keyboardAppearance;
+
+  /// {@template flutter.widgets.editableText.scrollPadding}
+  /// Configures padding to edges surrounding a [Scrollable] when the Textfield scrolls into view.
+  ///
+  /// When this widget receives focus and is not completely visible (for example scrolled partially
+  /// off the screen or overlapped by the keyboard)
+  /// then it will attempt to make itself visible by scrolling a surrounding [Scrollable], if one is present.
+  /// This value controls how far from the edges of a [Scrollable] the TextField will be positioned after the scroll.
+  ///
+  /// Defaults to EdgeInsets.all(20.0).
+  /// {@endtemplate}
+  final EdgeInsets scrollPadding;
+
+  /// {@template flutter.widgets.editableText.enableInteractiveSelection}
+  /// Whether to enable user interface affordances for changing the
+  /// text selection.
+  ///
+  /// For example, setting this to true will enable features such as
+  /// long-pressing the TextField to select text and show the
+  /// cut/copy/paste menu, and tapping to move the text caret.
+  ///
+  /// When this is false, the text selection cannot be adjusted by
+  /// the user, text cannot be copied, and the user cannot paste into
+  /// the text field from the clipboard.
+  /// {@endtemplate}
+  final bool enableInteractiveSelection;
+
+  /// Setting this property to true makes the cursor stop blinking or fading
+  /// on and off once the cursor appears on focus. This property is useful for
+  /// testing purposes.
+  ///
+  /// It does not affect the necessity to focus the EditableText for the cursor
+  /// to appear in the first place.
+  ///
+  /// Defaults to false, resulting in a typical blinking cursor.
+  static bool debugDeterministicCursor = false;
+
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  /// {@template flutter.widgets.editableText.scrollController}
+  /// The [ScrollController] to use when vertically scrolling the input.
+  ///
+  /// If null, it will instantiate a new ScrollController.
+  ///
+  /// See [Scrollable.controller].
+  /// {@endtemplate}
+  final ScrollController? scrollController;
+
+  /// {@template flutter.widgets.editableText.scrollPhysics}
+  /// The [ScrollPhysics] to use when vertically scrolling the input.
+  ///
+  /// If not specified, it will behave according to the current platform.
+  ///
+  /// See [Scrollable.physics].
+  /// {@endtemplate}
+  final ScrollPhysics? scrollPhysics;
+
+  /// {@template flutter.widgets.editableText.selectionEnabled}
+  /// Same as [enableInteractiveSelection].
+  ///
+  /// This getter exists primarily for consistency with
+  /// [RenderEditable.selectionEnabled].
+  /// {@endtemplate}
+  bool get selectionEnabled => enableInteractiveSelection;
+
+  /// {@template flutter.widgets.editableText.autofillHints}
+  /// A list of strings that helps the autofill service identify the type of this
+  /// text input.
+  ///
+  /// When set to null or empty, this text input will not send its autofill
+  /// information to the platform, preventing it from participating in
+  /// autofills triggered by a different [AutofillClient], even if they're in the
+  /// same [AutofillScope]. Additionally, on Android and web, setting this to
+  /// null or empty will disable autofill for this text field.
+  ///
+  /// The minimum platform SDK version that supports Autofill is API level 26
+  /// for Android, and iOS 10.0 for iOS.
+  ///
+  /// ### Setting up iOS autofill:
+  ///
+  /// To provide the best user experience and ensure your app fully supports
+  /// password autofill on iOS, follow these steps:
+  ///
+  /// * Set up your iOS app's
+  ///   [associated domains](https://developer.apple.com/documentation/safariservices/supporting_associated_domains_in_your_app).
+  /// * Some autofill hints only work with specific [keyboardType]s. For example,
+  ///   [AutofillHints.name] requires [TextInputType.name] and [AutofillHints.email]
+  ///   works only with [TextInputType.emailAddress]. Make sure the input field has a
+  ///   compatible [keyboardType]. Empirically, [TextInputType.name] works well
+  ///   with many autofill hints that are predefined on iOS.
+  ///
+  /// ### Troubleshooting Autofill
+  ///
+  /// Autofill service providers rely heavily on [autofillHints]. Make sure the
+  /// entries in [autofillHints] are supported by the autofill service currently
+  /// in use (the name of the service can typically be found in your mobile
+  /// device's system settings).
+  ///
+  /// #### Autofill UI refuses to show up when I tap on the text field
+  ///
+  /// Check the device's system settings and make sure autofill is turned on,
+  /// and there're available credentials stored in the autofill service.
+  ///
+  /// * iOS password autofill: Go to Settings -> Password, turn on "Autofill
+  ///   Passwords", and add new passwords for testing by pressing the top right
+  ///   "+" button. Use an arbitrary "website" if you don't have associated
+  ///   domains set up for your app. As long as there's at least one password
+  ///   stored, you should be able to see a key-shaped icon in the quick type
+  ///   bar on the software keyboard, when a password related field is focused.
+  ///
+  /// * iOS contact information autofill: iOS seems to pull contact info from
+  ///   the Apple ID currently associated with the device. Go to Settings ->
+  ///   Apple ID (usually the first entry, or "Sign in to your iPhone" if you
+  ///   haven't set up one on the device), and fill out the relevant fields. If
+  ///   you wish to test more contact info types, try adding them in Contacts ->
+  ///   My Card.
+  ///
+  /// * Android autofill: Go to Settings -> System -> Languages & input ->
+  ///   Autofill service. Enable the autofill service of your choice, and make
+  ///   sure there're available credentials associated with your app.
+  ///
+  /// #### I called `TextInput.finishAutofillContext` but the autofill save
+  /// prompt isn't showing
+  ///
+  /// * iOS: iOS may not show a prompt or any other visual indication when it
+  ///   saves user password. Go to Settings -> Password and check if your new
+  ///   password is saved. Neither saving password nor auto-generating strong
+  ///   password works without properly setting up associated domains in your
+  ///   app. To set up associated domains, follow the instructions in
+  ///   <https://developer.apple.com/documentation/safariservices/supporting_associated_domains_in_your_app>.
+  ///
+  /// {@endtemplate}
+  /// {@macro flutter.services.AutofillConfiguration.autofillHints}
+  final Iterable<String>? autofillHints;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge].
+  final Clip clipBehavior;
+
+  /// Restoration ID to save and restore the scroll offset of the
+  /// [EditableText].
+  ///
+  /// If a restoration id is provided, the [EditableText] will persist its
+  /// current scroll offset and restore it during state restoration.
+  ///
+  /// The scroll offset is persisted in a [RestorationBucket] claimed from
+  /// the surrounding [RestorationScope] using the provided restoration ID.
+  ///
+  /// Persisting and restoring the content of the [EditableText] is the
+  /// responsibility of the owner of the [controller], who may use a
+  /// [RestorableTextEditingController] for that purpose.
+  ///
+  /// See also:
+  ///
+  ///  * [RestorationManager], which explains how state restoration works in
+  ///    Flutter.
+  final String? restorationId;
+
+  // Infer the keyboard type of an `EditableText` if it's not specified.
+  static TextInputType _inferKeyboardType({
+    required Iterable<String>? autofillHints,
+    required int? maxLines,
+  }) {
+    if (autofillHints?.isEmpty ?? true) {
+      return maxLines == 1 ? TextInputType.text : TextInputType.multiline;
+    }
+
+    TextInputType? returnValue;
+    final String effectiveHint = autofillHints!.first;
+
+    // On iOS oftentimes specifying a text content type is not enough to qualify
+    // the input field for autofill. The keyboard type also needs to be compatible
+    // with the content type. To get autofill to work by default on EditableText,
+    // the keyboard type inference on iOS is done differently from other platforms.
+    //
+    // The entries with "autofill not working" comments are the iOS text content
+    // types that should work with the specified keyboard type but won't trigger
+    // (even within a native app). Tested on iOS 13.5.
+    if (!kIsWeb) {
+      switch (defaultTargetPlatform) {
+        case TargetPlatform.iOS:
+        case TargetPlatform.macOS:
+          const Map<String, TextInputType> iOSKeyboardType = <String, TextInputType> {
+            AutofillHints.addressCity : TextInputType.name,
+            AutofillHints.addressCityAndState : TextInputType.name, // Autofill not working.
+            AutofillHints.addressState : TextInputType.name,
+            AutofillHints.countryName : TextInputType.name,
+            AutofillHints.creditCardNumber : TextInputType.number,  // Couldn't test.
+            AutofillHints.email : TextInputType.emailAddress,
+            AutofillHints.familyName : TextInputType.name,
+            AutofillHints.fullStreetAddress : TextInputType.name,
+            AutofillHints.givenName : TextInputType.name,
+            AutofillHints.jobTitle : TextInputType.name,            // Autofill not working.
+            AutofillHints.location : TextInputType.name,            // Autofill not working.
+            AutofillHints.middleName : TextInputType.name,          // Autofill not working.
+            AutofillHints.name : TextInputType.name,
+            AutofillHints.namePrefix : TextInputType.name,          // Autofill not working.
+            AutofillHints.nameSuffix : TextInputType.name,          // Autofill not working.
+            AutofillHints.newPassword : TextInputType.text,
+            AutofillHints.newUsername : TextInputType.text,
+            AutofillHints.nickname : TextInputType.name,            // Autofill not working.
+            AutofillHints.oneTimeCode : TextInputType.number,
+            AutofillHints.organizationName : TextInputType.text,    // Autofill not working.
+            AutofillHints.password : TextInputType.text,
+            AutofillHints.postalCode : TextInputType.name,
+            AutofillHints.streetAddressLine1 : TextInputType.name,
+            AutofillHints.streetAddressLine2 : TextInputType.name,  // Autofill not working.
+            AutofillHints.sublocality : TextInputType.name,         // Autofill not working.
+            AutofillHints.telephoneNumber : TextInputType.name,
+            AutofillHints.url : TextInputType.url,                  // Autofill not working.
+            AutofillHints.username : TextInputType.text,
+          };
+
+          returnValue = iOSKeyboardType[effectiveHint];
+          break;
+        case TargetPlatform.android:
+        case TargetPlatform.fuchsia:
+        case TargetPlatform.linux:
+        case TargetPlatform.windows:
+          break;
+      }
+    }
+
+    if (returnValue != null || maxLines != 1)
+      return returnValue ?? TextInputType.multiline;
+
+    const Map<String, TextInputType> inferKeyboardType = <String, TextInputType> {
+      AutofillHints.addressCity : TextInputType.streetAddress,
+      AutofillHints.addressCityAndState : TextInputType.streetAddress,
+      AutofillHints.addressState : TextInputType.streetAddress,
+      AutofillHints.birthday : TextInputType.datetime,
+      AutofillHints.birthdayDay : TextInputType.datetime,
+      AutofillHints.birthdayMonth : TextInputType.datetime,
+      AutofillHints.birthdayYear : TextInputType.datetime,
+      AutofillHints.countryCode : TextInputType.number,
+      AutofillHints.countryName : TextInputType.text,
+      AutofillHints.creditCardExpirationDate : TextInputType.datetime,
+      AutofillHints.creditCardExpirationDay : TextInputType.datetime,
+      AutofillHints.creditCardExpirationMonth : TextInputType.datetime,
+      AutofillHints.creditCardExpirationYear : TextInputType.datetime,
+      AutofillHints.creditCardFamilyName : TextInputType.name,
+      AutofillHints.creditCardGivenName : TextInputType.name,
+      AutofillHints.creditCardMiddleName : TextInputType.name,
+      AutofillHints.creditCardName : TextInputType.name,
+      AutofillHints.creditCardNumber : TextInputType.number,
+      AutofillHints.creditCardSecurityCode : TextInputType.number,
+      AutofillHints.creditCardType : TextInputType.text,
+      AutofillHints.email : TextInputType.emailAddress,
+      AutofillHints.familyName : TextInputType.name,
+      AutofillHints.fullStreetAddress : TextInputType.streetAddress,
+      AutofillHints.gender : TextInputType.text,
+      AutofillHints.givenName : TextInputType.name,
+      AutofillHints.impp : TextInputType.url,
+      AutofillHints.jobTitle : TextInputType.text,
+      AutofillHints.language : TextInputType.text,
+      AutofillHints.location : TextInputType.streetAddress,
+      AutofillHints.middleInitial : TextInputType.name,
+      AutofillHints.middleName : TextInputType.name,
+      AutofillHints.name : TextInputType.name,
+      AutofillHints.namePrefix : TextInputType.name,
+      AutofillHints.nameSuffix : TextInputType.name,
+      AutofillHints.newPassword : TextInputType.text,
+      AutofillHints.newUsername : TextInputType.text,
+      AutofillHints.nickname : TextInputType.text,
+      AutofillHints.oneTimeCode : TextInputType.text,
+      AutofillHints.organizationName : TextInputType.text,
+      AutofillHints.password : TextInputType.text,
+      AutofillHints.photo : TextInputType.text,
+      AutofillHints.postalAddress : TextInputType.streetAddress,
+      AutofillHints.postalAddressExtended : TextInputType.streetAddress,
+      AutofillHints.postalAddressExtendedPostalCode : TextInputType.number,
+      AutofillHints.postalCode : TextInputType.number,
+      AutofillHints.streetAddressLevel1 : TextInputType.streetAddress,
+      AutofillHints.streetAddressLevel2 : TextInputType.streetAddress,
+      AutofillHints.streetAddressLevel3 : TextInputType.streetAddress,
+      AutofillHints.streetAddressLevel4 : TextInputType.streetAddress,
+      AutofillHints.streetAddressLine1 : TextInputType.streetAddress,
+      AutofillHints.streetAddressLine2 : TextInputType.streetAddress,
+      AutofillHints.streetAddressLine3 : TextInputType.streetAddress,
+      AutofillHints.sublocality : TextInputType.streetAddress,
+      AutofillHints.telephoneNumber : TextInputType.phone,
+      AutofillHints.telephoneNumberAreaCode : TextInputType.phone,
+      AutofillHints.telephoneNumberCountryCode : TextInputType.phone,
+      AutofillHints.telephoneNumberDevice : TextInputType.phone,
+      AutofillHints.telephoneNumberExtension : TextInputType.phone,
+      AutofillHints.telephoneNumberLocal : TextInputType.phone,
+      AutofillHints.telephoneNumberLocalPrefix : TextInputType.phone,
+      AutofillHints.telephoneNumberLocalSuffix : TextInputType.phone,
+      AutofillHints.telephoneNumberNational : TextInputType.phone,
+      AutofillHints.transactionAmount : TextInputType.numberWithOptions(decimal: true),
+      AutofillHints.transactionCurrency : TextInputType.text,
+      AutofillHints.url : TextInputType.url,
+      AutofillHints.username : TextInputType.text,
+    };
+
+    return inferKeyboardType[effectiveHint] ?? TextInputType.text;
+  }
+
+  @override
+  EditableTextState createState() => EditableTextState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<TextEditingController>('controller', controller));
+    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode));
+    properties.add(DiagnosticsProperty<bool>('obscureText', obscureText, defaultValue: false));
+    properties.add(DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: true));
+    properties.add(EnumProperty<SmartDashesType>('smartDashesType', smartDashesType, defaultValue: obscureText ? SmartDashesType.disabled : SmartDashesType.enabled));
+    properties.add(EnumProperty<SmartQuotesType>('smartQuotesType', smartQuotesType, defaultValue: obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled));
+    properties.add(DiagnosticsProperty<bool>('enableSuggestions', enableSuggestions, defaultValue: true));
+    style.debugFillProperties(properties);
+    properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    properties.add(DiagnosticsProperty<Locale>('locale', locale, defaultValue: null));
+    properties.add(DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null));
+    properties.add(IntProperty('maxLines', maxLines, defaultValue: 1));
+    properties.add(IntProperty('minLines', minLines, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('expands', expands, defaultValue: false));
+    properties.add(DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false));
+    properties.add(DiagnosticsProperty<TextInputType>('keyboardType', keyboardType, defaultValue: null));
+    properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, defaultValue: null));
+    properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
+    properties.add(DiagnosticsProperty<Iterable<String>>('autofillHints', autofillHints, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextHeightBehavior>('textHeightBehavior', textHeightBehavior, defaultValue: null));
+  }
+}
+
+/// State for a [EditableText].
+class EditableTextState extends State<EditableText> with AutomaticKeepAliveClientMixin<EditableText>, WidgetsBindingObserver, TickerProviderStateMixin<EditableText> implements TextSelectionDelegate, TextInputClient, AutofillClient {
+  Timer? _cursorTimer;
+  bool _targetCursorVisibility = false;
+  final ValueNotifier<bool> _cursorVisibilityNotifier = ValueNotifier<bool>(true);
+  final GlobalKey _editableKey = GlobalKey();
+  final ClipboardStatusNotifier? _clipboardStatus = kIsWeb ? null : ClipboardStatusNotifier();
+
+  TextInputConnection? _textInputConnection;
+  TextSelectionOverlay? _selectionOverlay;
+
+  ScrollController? _scrollController;
+
+  late AnimationController _cursorBlinkOpacityController;
+
+  final LayerLink _toolbarLayerLink = LayerLink();
+  final LayerLink _startHandleLayerLink = LayerLink();
+  final LayerLink _endHandleLayerLink = LayerLink();
+
+  bool _didAutoFocus = false;
+  FocusAttachment? _focusAttachment;
+
+  AutofillGroupState? _currentAutofillScope;
+  @override
+  AutofillScope? get currentAutofillScope => _currentAutofillScope;
+
+  // Is this field in the current autofill context.
+  bool _isInAutofillContext = false;
+
+  /// Whether to create an input connection with the platform for text editing
+  /// or not.
+  ///
+  /// Read-only input fields do not need a connection with the platform since
+  /// there's no need for text editing capabilities (e.g. virtual keyboard).
+  ///
+  /// On the web, we always need a connection because we want some browser
+  /// functionalities to continue to work on read-only input fields like:
+  ///
+  /// - Relevant context menu.
+  /// - cmd/ctrl+c shortcut to copy.
+  /// - cmd/ctrl+a to select all.
+  /// - Changing the selection using a physical keyboard.
+  bool get _shouldCreateInputConnection => kIsWeb || !widget.readOnly;
+
+  // This value is an eyeball estimation of the time it takes for the iOS cursor
+  // to ease in and out.
+  static const Duration _fadeDuration = Duration(milliseconds: 250);
+
+  // The time it takes for the floating cursor to snap to the text aligned
+  // cursor position after the user has finished placing it.
+  static const Duration _floatingCursorResetTime = Duration(milliseconds: 125);
+
+  late AnimationController _floatingCursorResetController;
+
+  @override
+  bool get wantKeepAlive => widget.focusNode.hasFocus;
+
+  Color get _cursorColor => widget.cursorColor.withOpacity(_cursorBlinkOpacityController.value);
+
+  @override
+  bool get cutEnabled => widget.toolbarOptions.cut && !widget.readOnly;
+
+  @override
+  bool get copyEnabled => widget.toolbarOptions.copy;
+
+  @override
+  bool get pasteEnabled => widget.toolbarOptions.paste && !widget.readOnly;
+
+  @override
+  bool get selectAllEnabled => widget.toolbarOptions.selectAll;
+
+  void _onChangedClipboardStatus() {
+    setState(() {
+      // Inform the widget that the value of clipboardStatus has changed.
+    });
+  }
+
+  // State lifecycle:
+
+  @override
+  void initState() {
+    super.initState();
+    _clipboardStatus?.addListener(_onChangedClipboardStatus);
+    widget.controller.addListener(_didChangeTextEditingValue);
+    _focusAttachment = widget.focusNode.attach(context);
+    widget.focusNode.addListener(_handleFocusChanged);
+    _scrollController = widget.scrollController ?? ScrollController();
+    _scrollController!.addListener(() { _selectionOverlay?.updateForScroll(); });
+    _cursorBlinkOpacityController = AnimationController(vsync: this, duration: _fadeDuration);
+    _cursorBlinkOpacityController.addListener(_onCursorColorTick);
+    _floatingCursorResetController = AnimationController(vsync: this);
+    _floatingCursorResetController.addListener(_onFloatingCursorResetTick);
+    _cursorVisibilityNotifier.value = widget.showCursor;
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+
+    final AutofillGroupState? newAutofillGroup = AutofillGroup.of(context);
+    if (currentAutofillScope != newAutofillGroup) {
+      _currentAutofillScope?.unregister(autofillId);
+      _currentAutofillScope = newAutofillGroup;
+      newAutofillGroup?.register(this);
+      _isInAutofillContext = _isInAutofillContext || _shouldBeInAutofillContext;
+    }
+
+    if (!_didAutoFocus && widget.autofocus) {
+      _didAutoFocus = true;
+      SchedulerBinding.instance!.addPostFrameCallback((_) {
+        if (mounted) {
+          FocusScope.of(context).autofocus(widget.focusNode);
+        }
+      });
+    }
+  }
+
+  @override
+  void didUpdateWidget(EditableText oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.controller != oldWidget.controller) {
+      oldWidget.controller.removeListener(_didChangeTextEditingValue);
+      widget.controller.addListener(_didChangeTextEditingValue);
+      _updateRemoteEditingValueIfNeeded();
+    }
+    if (widget.controller.selection != oldWidget.controller.selection) {
+      _selectionOverlay?.update(_value);
+    }
+    _selectionOverlay?.handlesVisible = widget.showSelectionHandles;
+    _isInAutofillContext = _isInAutofillContext || _shouldBeInAutofillContext;
+
+    if (widget.focusNode != oldWidget.focusNode) {
+      oldWidget.focusNode.removeListener(_handleFocusChanged);
+      _focusAttachment?.detach();
+      _focusAttachment = widget.focusNode.attach(context);
+      widget.focusNode.addListener(_handleFocusChanged);
+      updateKeepAlive();
+    }
+    if (!_shouldCreateInputConnection) {
+      _closeInputConnectionIfNeeded();
+    } else {
+      if (oldWidget.readOnly && _hasFocus) {
+        _openInputConnection();
+      }
+    }
+
+    if (kIsWeb && _hasInputConnection) {
+      if (oldWidget.readOnly != widget.readOnly) {
+        _textInputConnection!.updateConfig(textInputConfiguration);
+      }
+    }
+
+    if (widget.style != oldWidget.style) {
+      final TextStyle style = widget.style;
+      // The _textInputConnection will pick up the new style when it attaches in
+      // _openInputConnection.
+      if (_hasInputConnection) {
+        _textInputConnection!.setStyle(
+          fontFamily: style.fontFamily,
+          fontSize: style.fontSize,
+          fontWeight: style.fontWeight,
+          textDirection: _textDirection,
+          textAlign: widget.textAlign,
+        );
+      }
+    }
+    if (widget.selectionEnabled && pasteEnabled && widget.selectionControls?.canPaste(this) == true) {
+      _clipboardStatus?.update();
+    }
+  }
+
+  @override
+  void dispose() {
+    _currentAutofillScope?.unregister(autofillId);
+    widget.controller.removeListener(_didChangeTextEditingValue);
+    _cursorBlinkOpacityController.removeListener(_onCursorColorTick);
+    _floatingCursorResetController.removeListener(_onFloatingCursorResetTick);
+    _closeInputConnectionIfNeeded();
+    assert(!_hasInputConnection);
+    _stopCursorTimer();
+    assert(_cursorTimer == null);
+    _selectionOverlay?.dispose();
+    _selectionOverlay = null;
+    _focusAttachment!.detach();
+    widget.focusNode.removeListener(_handleFocusChanged);
+    WidgetsBinding.instance!.removeObserver(this);
+    _clipboardStatus?.removeListener(_onChangedClipboardStatus);
+    _clipboardStatus?.dispose();
+    super.dispose();
+    assert(_batchEditDepth <= 0, 'unfinished batch edits: $_batchEditDepth');
+  }
+
+  // TextInputClient implementation:
+
+  /// The last known [TextEditingValue] of the platform text input plugin.
+  ///
+  /// This value is updated when the platform text input plugin sends a new
+  /// update via [updateEditingValue], or when [EditableText] calls
+  /// [TextInputConnection.setEditingState] to overwrite the platform text input
+  /// plugin's [TextEditingValue].
+  ///
+  /// Used in [_updateRemoteEditingValueIfNeeded] to determine whether the
+  /// remote value is outdated and needs updating.
+  TextEditingValue? _lastKnownRemoteTextEditingValue;
+
+  @override
+  TextEditingValue get currentTextEditingValue => _value;
+
+  @override
+  void updateEditingValue(TextEditingValue value) {
+    // This method handles text editing state updates from the platform text
+    // input plugin. The [EditableText] may not have the focus or an open input
+    // connection, as autofill can update a disconnected [EditableText].
+
+    // Since we still have to support keyboard select, this is the best place
+    // to disable text updating.
+    if (!_shouldCreateInputConnection) {
+      return;
+    }
+
+    if (widget.readOnly) {
+      // In the read-only case, we only care about selection changes, and reject
+      // everything else.
+      value = _value.copyWith(selection: value.selection);
+    }
+    _lastKnownRemoteTextEditingValue = value;
+
+    if (value == _value) {
+      // This is possible, for example, when the numeric keyboard is input,
+      // the engine will notify twice for the same value.
+      // Track at https://github.com/flutter/flutter/issues/65811
+      return;
+    }
+
+    if (value.text == _value.text && value.composing == _value.composing) {
+      // `selection` is the only change.
+      _handleSelectionChanged(value.selection, renderEditable, SelectionChangedCause.keyboard);
+    } else {
+      hideToolbar();
+      _currentPromptRectRange = null;
+
+      if (_hasInputConnection) {
+        _showCaretOnScreen();
+        if (widget.obscureText && value.text.length == _value.text.length + 1) {
+          _obscureShowCharTicksPending = _kObscureShowLatestCharCursorTicks;
+          _obscureLatestCharIndex = _value.selection.baseOffset;
+        }
+      }
+
+      _formatAndSetValue(value);
+    }
+
+    if (_hasInputConnection) {
+      // To keep the cursor from blinking while typing, we want to restart the
+      // cursor timer every time a new character is typed.
+      _stopCursorTimer(resetCharTicks: false);
+      _startCursorTimer();
+    }
+  }
+
+  @override
+  void performAction(TextInputAction action) {
+    switch (action) {
+      case TextInputAction.newline:
+        // If this is a multiline EditableText, do nothing for a "newline"
+        // action; The newline is already inserted. Otherwise, finalize
+        // editing.
+        if (!_isMultiline)
+          _finalizeEditing(action, shouldUnfocus: true);
+        break;
+      case TextInputAction.done:
+      case TextInputAction.go:
+      case TextInputAction.next:
+      case TextInputAction.previous:
+      case TextInputAction.search:
+      case TextInputAction.send:
+        _finalizeEditing(action, shouldUnfocus: true);
+        break;
+      case TextInputAction.continueAction:
+      case TextInputAction.emergencyCall:
+      case TextInputAction.join:
+      case TextInputAction.none:
+      case TextInputAction.route:
+      case TextInputAction.unspecified:
+        // Finalize editing, but don't give up focus because this keyboard
+        // action does not imply the user is done inputting information.
+        _finalizeEditing(action, shouldUnfocus: false);
+        break;
+    }
+  }
+
+  @override
+  void performPrivateCommand(String action, Map<String, dynamic> data) {
+    widget.onAppPrivateCommand!(action, data);
+  }
+
+  // The original position of the caret on FloatingCursorDragState.start.
+  Rect? _startCaretRect;
+
+  // The most recent text position as determined by the location of the floating
+  // cursor.
+  TextPosition? _lastTextPosition;
+
+  // The offset of the floating cursor as determined from the start call.
+  Offset? _pointOffsetOrigin;
+
+  // The most recent position of the floating cursor.
+  Offset? _lastBoundedOffset;
+
+  // Because the center of the cursor is preferredLineHeight / 2 below the touch
+  // origin, but the touch origin is used to determine which line the cursor is
+  // on, we need this offset to correctly render and move the cursor.
+  Offset get _floatingCursorOffset => Offset(0, renderEditable.preferredLineHeight / 2);
+
+  @override
+  void updateFloatingCursor(RawFloatingCursorPoint point) {
+    switch(point.state){
+      case FloatingCursorDragState.Start:
+        if (_floatingCursorResetController.isAnimating) {
+          _floatingCursorResetController.stop();
+          _onFloatingCursorResetTick();
+        }
+        // We want to send in points that are centered around a (0,0) origin, so
+        // we cache the position.
+        _pointOffsetOrigin = point.offset;
+
+        final TextPosition currentTextPosition = TextPosition(offset: renderEditable.selection!.baseOffset);
+        _startCaretRect = renderEditable.getLocalRectForCaret(currentTextPosition);
+
+        _lastBoundedOffset = _startCaretRect!.center - _floatingCursorOffset;
+        _lastTextPosition = currentTextPosition;
+        renderEditable.setFloatingCursor(point.state, _lastBoundedOffset!, _lastTextPosition!);
+        break;
+      case FloatingCursorDragState.Update:
+        final Offset centeredPoint = point.offset! - _pointOffsetOrigin!;
+        final Offset rawCursorOffset = _startCaretRect!.center + centeredPoint - _floatingCursorOffset;
+
+        _lastBoundedOffset = renderEditable.calculateBoundedFloatingCursorOffset(rawCursorOffset);
+        _lastTextPosition = renderEditable.getPositionForPoint(renderEditable.localToGlobal(_lastBoundedOffset! + _floatingCursorOffset));
+        renderEditable.setFloatingCursor(point.state, _lastBoundedOffset!, _lastTextPosition!);
+        break;
+      case FloatingCursorDragState.End:
+        // We skip animation if no update has happened.
+        if (_lastTextPosition != null && _lastBoundedOffset != null) {
+          _floatingCursorResetController.value = 0.0;
+          _floatingCursorResetController.animateTo(1.0, duration: _floatingCursorResetTime, curve: Curves.decelerate);
+        }
+        break;
+    }
+  }
+
+  void _onFloatingCursorResetTick() {
+    final Offset finalPosition = renderEditable.getLocalRectForCaret(_lastTextPosition!).centerLeft - _floatingCursorOffset;
+    if (_floatingCursorResetController.isCompleted) {
+      renderEditable.setFloatingCursor(FloatingCursorDragState.End, finalPosition, _lastTextPosition!);
+      if (_lastTextPosition!.offset != renderEditable.selection!.baseOffset)
+        // The cause is technically the force cursor, but the cause is listed as tap as the desired functionality is the same.
+        _handleSelectionChanged(TextSelection.collapsed(offset: _lastTextPosition!.offset), renderEditable, SelectionChangedCause.forcePress);
+      _startCaretRect = null;
+      _lastTextPosition = null;
+      _pointOffsetOrigin = null;
+      _lastBoundedOffset = null;
+    } else {
+      final double lerpValue = _floatingCursorResetController.value;
+      final double lerpX = ui.lerpDouble(_lastBoundedOffset!.dx, finalPosition.dx, lerpValue)!;
+      final double lerpY = ui.lerpDouble(_lastBoundedOffset!.dy, finalPosition.dy, lerpValue)!;
+
+      renderEditable.setFloatingCursor(FloatingCursorDragState.Update, Offset(lerpX, lerpY), _lastTextPosition!, resetLerpValue: lerpValue);
+    }
+  }
+
+  void _finalizeEditing(TextInputAction action, {required bool shouldUnfocus}) {
+    // Take any actions necessary now that the user has completed editing.
+    if (widget.onEditingComplete != null) {
+      try {
+        widget.onEditingComplete!();
+      } catch (exception, stack) {
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          library: 'widgets',
+          context: ErrorDescription('while calling onEditingComplete for $action'),
+        ));
+      }
+    } else {
+      // Default behavior if the developer did not provide an
+      // onEditingComplete callback: Finalize editing and remove focus, or move
+      // it to the next/previous field, depending on the action.
+      widget.controller.clearComposing();
+      if (shouldUnfocus) {
+        switch (action) {
+          case TextInputAction.none:
+          case TextInputAction.unspecified:
+          case TextInputAction.done:
+          case TextInputAction.go:
+          case TextInputAction.search:
+          case TextInputAction.send:
+          case TextInputAction.continueAction:
+          case TextInputAction.join:
+          case TextInputAction.route:
+          case TextInputAction.emergencyCall:
+          case TextInputAction.newline:
+            widget.focusNode.unfocus();
+            break;
+          case TextInputAction.next:
+            widget.focusNode.nextFocus();
+            break;
+          case TextInputAction.previous:
+            widget.focusNode.previousFocus();
+            break;
+        }
+      }
+    }
+
+    // Invoke optional callback with the user's submitted content.
+    try {
+      widget.onSubmitted?.call(_value.text);
+    } catch (exception, stack) {
+      FlutterError.reportError(FlutterErrorDetails(
+        exception: exception,
+        stack: stack,
+        library: 'widgets',
+        context: ErrorDescription('while calling onSubmitted for $action'),
+      ));
+    }
+  }
+
+  int _batchEditDepth = 0;
+
+  /// Begins a new batch edit, within which new updates made to the text editing
+  /// value will not be sent to the platform text input plugin.
+  ///
+  /// Batch edits nest. When the outermost batch edit finishes, [endBatchEdit]
+  /// will attempt to send [currentTextEditingValue] to the text input plugin if
+  /// it detected a change.
+  void beginBatchEdit() {
+    _batchEditDepth += 1;
+  }
+
+  /// Ends the current batch edit started by the last call to [beginBatchEdit],
+  /// and send [currentTextEditingValue] to the text input plugin if needed.
+  ///
+  /// Throws an error in debug mode if this [EditableText] is not in a batch
+  /// edit.
+  void endBatchEdit() {
+    _batchEditDepth -= 1;
+    assert(
+      _batchEditDepth >= 0,
+      'Unbalanced call to endBatchEdit: beginBatchEdit must be called first.',
+    );
+    _updateRemoteEditingValueIfNeeded();
+  }
+
+  void _updateRemoteEditingValueIfNeeded() {
+    if (_batchEditDepth > 0 || !_hasInputConnection)
+      return;
+    final TextEditingValue localValue = _value;
+    if (localValue == _lastKnownRemoteTextEditingValue)
+      return;
+    _textInputConnection!.setEditingState(localValue);
+    _lastKnownRemoteTextEditingValue = localValue;
+  }
+
+  TextEditingValue get _value => widget.controller.value;
+  set _value(TextEditingValue value) {
+    widget.controller.value = value;
+  }
+
+  bool get _hasFocus => widget.focusNode.hasFocus;
+  bool get _isMultiline => widget.maxLines != 1;
+
+  // Finds the closest scroll offset to the current scroll offset that fully
+  // reveals the given caret rect. If the given rect's main axis extent is too
+  // large to be fully revealed in `renderEditable`, it will be centered along
+  // the main axis.
+  //
+  // If this is a multiline EditableText (which means the Editable can only
+  // scroll vertically), the given rect's height will first be extended to match
+  // `renderEditable.preferredLineHeight`, before the target scroll offset is
+  // calculated.
+  RevealedOffset _getOffsetToRevealCaret(Rect rect) {
+    if (!_scrollController!.position.allowImplicitScrolling)
+      return RevealedOffset(offset: _scrollController!.offset, rect: rect);
+
+    final Size editableSize = renderEditable.size;
+    final double additionalOffset;
+    final Offset unitOffset;
+
+    if (!_isMultiline) {
+      additionalOffset = rect.width >= editableSize.width
+        // Center `rect` if it's oversized.
+        ? editableSize.width / 2 - rect.center.dx
+        // Valid additional offsets range from (rect.right - size.width)
+        // to (rect.left). Pick the closest one if out of range.
+        : 0.0.clamp(rect.right - editableSize.width, rect.left);
+      unitOffset = const Offset(1, 0);
+    } else {
+      // The caret is vertically centered within the line. Expand the caret's
+      // height so that it spans the line because we're going to ensure that the
+      // entire expanded caret is scrolled into view.
+      final Rect expandedRect = Rect.fromCenter(
+        center: rect.center,
+        width: rect.width,
+        height: math.max(rect.height, renderEditable.preferredLineHeight),
+      );
+
+      additionalOffset = expandedRect.height >= editableSize.height
+        ? editableSize.height / 2 - expandedRect.center.dy
+        : 0.0.clamp(expandedRect.bottom - editableSize.height, expandedRect.top);
+      unitOffset = const Offset(0, 1);
+    }
+
+    // No overscrolling when encountering tall fonts/scripts that extend past
+    // the ascent.
+    final double targetOffset = (additionalOffset + _scrollController!.offset)
+      .clamp(
+        _scrollController!.position.minScrollExtent,
+        _scrollController!.position.maxScrollExtent,
+      );
+
+    final double offsetDelta = _scrollController!.offset - targetOffset;
+    return RevealedOffset(rect: rect.shift(unitOffset * offsetDelta), offset: targetOffset);
+  }
+
+  bool get _hasInputConnection => _textInputConnection?.attached ?? false;
+  bool get _needsAutofill => widget.autofillHints?.isNotEmpty ?? false;
+  bool get _shouldBeInAutofillContext => _needsAutofill && currentAutofillScope != null;
+
+  void _openInputConnection() {
+    if (!_shouldCreateInputConnection) {
+      return;
+    }
+    if (!_hasInputConnection) {
+      final TextEditingValue localValue = _value;
+
+      // When _needsAutofill == true && currentAutofillScope == null, autofill
+      // is allowed but saving the user input from the text field is
+      // discouraged.
+      //
+      // In case the autofillScope changes from a non-null value to null, or
+      // _needsAutofill changes to false from true, the platform needs to be
+      // notified to exclude this field from the autofill context. So we need to
+      // provide the autofillId.
+      _textInputConnection = _needsAutofill && currentAutofillScope != null
+        ? currentAutofillScope!.attach(this, textInputConfiguration)
+        : TextInput.attach(this, _createTextInputConfiguration(_isInAutofillContext || _needsAutofill));
+      _textInputConnection!.show();
+      _updateSizeAndTransform();
+      _updateComposingRectIfNeeded();
+      if (_needsAutofill) {
+        // Request autofill AFTER the size and the transform have been sent to
+        // the platform text input plugin.
+        _textInputConnection!.requestAutofill();
+      }
+
+      final TextStyle style = widget.style;
+      _textInputConnection!
+        ..setStyle(
+          fontFamily: style.fontFamily,
+          fontSize: style.fontSize,
+          fontWeight: style.fontWeight,
+          textDirection: _textDirection,
+          textAlign: widget.textAlign,
+        )
+        ..setEditingState(localValue);
+    } else {
+      _textInputConnection!.show();
+    }
+  }
+
+  void _closeInputConnectionIfNeeded() {
+    if (_hasInputConnection) {
+      _textInputConnection!.close();
+      _textInputConnection = null;
+      _lastKnownRemoteTextEditingValue = null;
+    }
+  }
+
+  void _openOrCloseInputConnectionIfNeeded() {
+    if (_hasFocus && widget.focusNode.consumeKeyboardToken()) {
+      _openInputConnection();
+    } else if (!_hasFocus) {
+      _closeInputConnectionIfNeeded();
+      widget.controller.clearComposing();
+    }
+  }
+
+  @override
+  void connectionClosed() {
+    if (_hasInputConnection) {
+      _textInputConnection!.connectionClosedReceived();
+      _textInputConnection = null;
+      _lastKnownRemoteTextEditingValue = null;
+      _finalizeEditing(TextInputAction.done, shouldUnfocus: true);
+    }
+  }
+
+  /// Express interest in interacting with the keyboard.
+  ///
+  /// If this control is already attached to the keyboard, this function will
+  /// request that the keyboard become visible. Otherwise, this function will
+  /// ask the focus system that it become focused. If successful in acquiring
+  /// focus, the control will then attach to the keyboard and request that the
+  /// keyboard become visible.
+  void requestKeyboard() {
+    if (_hasFocus) {
+      _openInputConnection();
+    } else {
+      widget.focusNode.requestFocus();
+    }
+  }
+
+  void _updateOrDisposeSelectionOverlayIfNeeded() {
+    if (_selectionOverlay != null) {
+      if (_hasFocus) {
+        _selectionOverlay!.update(_value);
+      } else {
+        _selectionOverlay!.dispose();
+        _selectionOverlay = null;
+      }
+    }
+  }
+
+  void _handleSelectionChanged(TextSelection selection, RenderEditable renderObject, SelectionChangedCause? cause) {
+    // We return early if the selection is not valid. This can happen when the
+    // text of [EditableText] is updated at the same time as the selection is
+    // changed by a gesture event.
+    if (!widget.controller.isSelectionWithinTextBounds(selection))
+      return;
+
+    widget.controller.selection = selection;
+
+    // This will show the keyboard for all selection changes on the
+    // EditableWidget, not just changes triggered by user gestures.
+    requestKeyboard();
+
+    _selectionOverlay?.hide();
+    _selectionOverlay = null;
+
+    if (widget.selectionControls != null) {
+      _selectionOverlay = TextSelectionOverlay(
+        clipboardStatus: _clipboardStatus,
+        context: context,
+        value: _value,
+        debugRequiredFor: widget,
+        toolbarLayerLink: _toolbarLayerLink,
+        startHandleLayerLink: _startHandleLayerLink,
+        endHandleLayerLink: _endHandleLayerLink,
+        renderObject: renderObject,
+        selectionControls: widget.selectionControls,
+        selectionDelegate: this,
+        dragStartBehavior: widget.dragStartBehavior,
+        onSelectionHandleTapped: widget.onSelectionHandleTapped,
+      );
+      _selectionOverlay!.handlesVisible = widget.showSelectionHandles;
+      _selectionOverlay!.showHandles();
+      try {
+        widget.onSelectionChanged?.call(selection, cause);
+      } catch (exception, stack) {
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          library: 'widgets',
+          context: ErrorDescription('while calling onSelectionChanged for $cause'),
+        ));
+      }
+    }
+
+    // To keep the cursor from blinking while it moves, restart the timer here.
+    if (_cursorTimer != null) {
+      _stopCursorTimer(resetCharTicks: false);
+      _startCursorTimer();
+    }
+  }
+
+  bool _textChangedSinceLastCaretUpdate = false;
+  Rect? _currentCaretRect;
+
+  void _handleCaretChanged(Rect caretRect) {
+    _currentCaretRect = caretRect;
+    // If the caret location has changed due to an update to the text or
+    // selection, then scroll the caret into view.
+    if (_textChangedSinceLastCaretUpdate) {
+      _textChangedSinceLastCaretUpdate = false;
+      _showCaretOnScreen();
+    }
+  }
+
+  // Animation configuration for scrolling the caret back on screen.
+  static const Duration _caretAnimationDuration = Duration(milliseconds: 100);
+  static const Curve _caretAnimationCurve = Curves.fastOutSlowIn;
+
+  bool _showCaretOnScreenScheduled = false;
+
+  void _showCaretOnScreen() {
+    if (_showCaretOnScreenScheduled) {
+      return;
+    }
+    _showCaretOnScreenScheduled = true;
+    SchedulerBinding.instance!.addPostFrameCallback((Duration _) {
+      _showCaretOnScreenScheduled = false;
+      if (_currentCaretRect == null || !_scrollController!.hasClients) {
+        return;
+      }
+
+      final double lineHeight = renderEditable.preferredLineHeight;
+
+      // Enlarge the target rect by scrollPadding to ensure that caret is not
+      // positioned directly at the edge after scrolling.
+      double bottomSpacing = widget.scrollPadding.bottom;
+      if (_selectionOverlay?.selectionControls != null) {
+        final double handleHeight = _selectionOverlay!.selectionControls!
+          .getHandleSize(lineHeight).height;
+        final double interactiveHandleHeight = math.max(
+          handleHeight,
+          kMinInteractiveDimension,
+        );
+        final Offset anchor = _selectionOverlay!.selectionControls!
+          .getHandleAnchor(
+            TextSelectionHandleType.collapsed,
+            lineHeight,
+          );
+        final double handleCenter = handleHeight / 2 - anchor.dy;
+        bottomSpacing = math.max(
+          handleCenter + interactiveHandleHeight / 2,
+          bottomSpacing,
+        );
+      }
+
+      final EdgeInsets caretPadding = widget.scrollPadding
+        .copyWith(bottom: bottomSpacing);
+
+      final RevealedOffset targetOffset = _getOffsetToRevealCaret(_currentCaretRect!);
+
+      _scrollController!.animateTo(
+        targetOffset.offset,
+        duration: _caretAnimationDuration,
+        curve: _caretAnimationCurve,
+      );
+
+      renderEditable.showOnScreen(
+        rect: caretPadding.inflateRect(targetOffset.rect),
+        duration: _caretAnimationDuration,
+        curve: _caretAnimationCurve,
+      );
+    });
+  }
+
+  late double _lastBottomViewInset;
+
+  @override
+  void didChangeMetrics() {
+    if (_lastBottomViewInset < WidgetsBinding.instance!.window.viewInsets.bottom) {
+      _showCaretOnScreen();
+    }
+    _lastBottomViewInset = WidgetsBinding.instance!.window.viewInsets.bottom;
+  }
+
+  late final _WhitespaceDirectionalityFormatter _whitespaceFormatter = _WhitespaceDirectionalityFormatter(textDirection: _textDirection);
+
+  void _formatAndSetValue(TextEditingValue value) {
+    // Only apply input formatters if the text has changed (including uncommited
+    // text in the composing region), or when the user committed the composing
+    // text.
+    // Gboard is very persistent in restoring the composing region. Applying
+    // input formatters on composing-region-only changes (except clearing the
+    // current composing region) is very infinite-loop-prone: the formatters
+    // will keep trying to modify the composing region while Gboard will keep
+    // trying to restore the original composing region.
+    final bool textChanged = _value.text != value.text
+                          || (!_value.composing.isCollapsed && value.composing.isCollapsed);
+    final bool selectionChanged = _value.selection != value.selection;
+
+    if (textChanged) {
+      value = widget.inputFormatters?.fold<TextEditingValue>(
+        value,
+        (TextEditingValue newValue, TextInputFormatter formatter) => formatter.formatEditUpdate(_value, newValue),
+      ) ?? value;
+
+      // Always pass the text through the whitespace directionality formatter to
+      // maintain expected behavior with carets on trailing whitespace.
+      // TODO(LongCatIsLooong): The if statement here is for retaining the
+      // previous behavior. The input formatter logic will be updated in an
+      // upcoming PR.
+      if (widget.inputFormatters?.isNotEmpty ?? false)
+        value = _whitespaceFormatter.formatEditUpdate(_value, value);
+    }
+
+    // Put all optional user callback invocations in a batch edit to prevent
+    // sending multiple `TextInput.updateEditingValue` messages.
+    beginBatchEdit();
+    _value = value;
+    if (textChanged) {
+      try {
+        widget.onChanged?.call(value.text);
+      } catch (exception, stack) {
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          library: 'widgets',
+          context: ErrorDescription('while calling onChanged'),
+        ));
+      }
+    }
+
+    if (selectionChanged) {
+      try {
+        widget.onSelectionChanged?.call(value.selection, null);
+      } catch (exception, stack) {
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          library: 'widgets',
+          context: ErrorDescription('while calling onSelectionChanged'),
+        ));
+      }
+    }
+
+    endBatchEdit();
+  }
+
+  void _onCursorColorTick() {
+    renderEditable.cursorColor = widget.cursorColor.withOpacity(_cursorBlinkOpacityController.value);
+    _cursorVisibilityNotifier.value = widget.showCursor && _cursorBlinkOpacityController.value > 0;
+  }
+
+  /// Whether the blinking cursor is actually visible at this precise moment
+  /// (it's hidden half the time, since it blinks).
+  @visibleForTesting
+  bool get cursorCurrentlyVisible => _cursorBlinkOpacityController.value > 0;
+
+  /// The cursor blink interval (the amount of time the cursor is in the "on"
+  /// state or the "off" state). A complete cursor blink period is twice this
+  /// value (half on, half off).
+  @visibleForTesting
+  Duration get cursorBlinkInterval => _kCursorBlinkHalfPeriod;
+
+  /// The current status of the text selection handles.
+  @visibleForTesting
+  TextSelectionOverlay? get selectionOverlay => _selectionOverlay;
+
+  int _obscureShowCharTicksPending = 0;
+  int? _obscureLatestCharIndex;
+
+  void _cursorTick(Timer timer) {
+    _targetCursorVisibility = !_targetCursorVisibility;
+    final double targetOpacity = _targetCursorVisibility ? 1.0 : 0.0;
+    if (widget.cursorOpacityAnimates) {
+      // If we want to show the cursor, we will animate the opacity to the value
+      // of 1.0, and likewise if we want to make it disappear, to 0.0. An easing
+      // curve is used for the animation to mimic the aesthetics of the native
+      // iOS cursor.
+      //
+      // These values and curves have been obtained through eyeballing, so are
+      // likely not exactly the same as the values for native iOS.
+      _cursorBlinkOpacityController.animateTo(targetOpacity, curve: Curves.easeOut);
+    } else {
+      _cursorBlinkOpacityController.value = targetOpacity;
+    }
+
+    if (_obscureShowCharTicksPending > 0) {
+      setState(() {
+        _obscureShowCharTicksPending--;
+      });
+    }
+  }
+
+  void _cursorWaitForStart(Timer timer) {
+    assert(_kCursorBlinkHalfPeriod > _fadeDuration);
+    _cursorTimer?.cancel();
+    _cursorTimer = Timer.periodic(_kCursorBlinkHalfPeriod, _cursorTick);
+  }
+
+  void _startCursorTimer() {
+    _targetCursorVisibility = true;
+    _cursorBlinkOpacityController.value = 1.0;
+    if (EditableText.debugDeterministicCursor)
+      return;
+    if (widget.cursorOpacityAnimates) {
+      _cursorTimer = Timer.periodic(_kCursorBlinkWaitForStart, _cursorWaitForStart);
+    } else {
+      _cursorTimer = Timer.periodic(_kCursorBlinkHalfPeriod, _cursorTick);
+    }
+  }
+
+  void _stopCursorTimer({ bool resetCharTicks = true }) {
+    _cursorTimer?.cancel();
+    _cursorTimer = null;
+    _targetCursorVisibility = false;
+    _cursorBlinkOpacityController.value = 0.0;
+    if (EditableText.debugDeterministicCursor)
+      return;
+    if (resetCharTicks)
+      _obscureShowCharTicksPending = 0;
+    if (widget.cursorOpacityAnimates) {
+      _cursorBlinkOpacityController.stop();
+      _cursorBlinkOpacityController.value = 0.0;
+    }
+  }
+
+  void _startOrStopCursorTimerIfNeeded() {
+    if (_cursorTimer == null && _hasFocus && _value.selection.isCollapsed)
+      _startCursorTimer();
+    else if (_cursorTimer != null && (!_hasFocus || !_value.selection.isCollapsed))
+      _stopCursorTimer();
+  }
+
+  void _didChangeTextEditingValue() {
+    _updateRemoteEditingValueIfNeeded();
+    _startOrStopCursorTimerIfNeeded();
+    _updateOrDisposeSelectionOverlayIfNeeded();
+    _textChangedSinceLastCaretUpdate = true;
+    // TODO(abarth): Teach RenderEditable about ValueNotifier<TextEditingValue>
+    // to avoid this setState().
+    setState(() { /* We use widget.controller.value in build(). */ });
+  }
+
+  void _handleFocusChanged() {
+    _openOrCloseInputConnectionIfNeeded();
+    _startOrStopCursorTimerIfNeeded();
+    _updateOrDisposeSelectionOverlayIfNeeded();
+    if (_hasFocus) {
+      // Listen for changing viewInsets, which indicates keyboard showing up.
+      WidgetsBinding.instance!.addObserver(this);
+      _lastBottomViewInset = WidgetsBinding.instance!.window.viewInsets.bottom;
+      _showCaretOnScreen();
+      if (!_value.selection.isValid) {
+        // Place cursor at the end if the selection is invalid when we receive focus.
+        _handleSelectionChanged(TextSelection.collapsed(offset: _value.text.length), renderEditable, null);
+      }
+    } else {
+      WidgetsBinding.instance!.removeObserver(this);
+      // Clear the selection and composition state if this widget lost focus.
+      _value = TextEditingValue(text: _value.text);
+      _currentPromptRectRange = null;
+    }
+    updateKeepAlive();
+  }
+
+  void _updateSizeAndTransform() {
+    if (_hasInputConnection) {
+      final Size size = renderEditable.size;
+      final Matrix4 transform = renderEditable.getTransformTo(null);
+      _textInputConnection!.setEditableSizeAndTransform(size, transform);
+      SchedulerBinding.instance!
+          .addPostFrameCallback((Duration _) => _updateSizeAndTransform());
+    }
+  }
+
+  // Sends the current composing rect to the iOS text input plugin via the text
+  // input channel. We need to keep sending the information even if no text is
+  // currently marked, as the information usually lags behind. The text input
+  // plugin needs to estimate the composing rect based on the latest caret rect,
+  // when the composing rect info didn't arrive in time.
+  void _updateComposingRectIfNeeded() {
+    final TextRange composingRange = _value.composing;
+    if (_hasInputConnection) {
+      assert(mounted);
+      Rect? composingRect = renderEditable.getRectForComposingRange(composingRange);
+      // Send the caret location instead if there's no marked text yet.
+      if (composingRect == null) {
+        assert(!composingRange.isValid || composingRange.isCollapsed);
+        final int offset = composingRange.isValid ? composingRange.start : 0;
+        composingRect = renderEditable.getLocalRectForCaret(TextPosition(offset: offset));
+      }
+      assert(composingRect != null);
+      _textInputConnection!.setComposingRect(composingRect);
+      SchedulerBinding.instance!
+          .addPostFrameCallback((Duration _) => _updateComposingRectIfNeeded());
+    }
+  }
+
+  TextDirection get _textDirection {
+    final TextDirection result = widget.textDirection ?? Directionality.of(context);
+    assert(result != null, '$runtimeType created without a textDirection and with no ambient Directionality.');
+    return result;
+  }
+
+  /// The renderer for this widget's descendant.
+  ///
+  /// This property is typically used to notify the renderer of input gestures
+  /// when [RenderEditable.ignorePointer] is true.
+  RenderEditable get renderEditable => _editableKey.currentContext!.findRenderObject()! as RenderEditable;
+
+  @override
+  TextEditingValue get textEditingValue => _value;
+
+  double get _devicePixelRatio => MediaQuery.of(context).devicePixelRatio;
+
+  @override
+  set textEditingValue(TextEditingValue value) {
+    _selectionOverlay?.update(value);
+    _formatAndSetValue(value);
+  }
+
+  @override
+  void bringIntoView(TextPosition position) {
+    final Rect localRect = renderEditable.getLocalRectForCaret(position);
+    final RevealedOffset targetOffset = _getOffsetToRevealCaret(localRect);
+
+    _scrollController!.jumpTo(targetOffset.offset);
+    renderEditable.showOnScreen(rect: targetOffset.rect);
+  }
+
+  /// Shows the selection toolbar at the location of the current cursor.
+  ///
+  /// Returns `false` if a toolbar couldn't be shown, such as when the toolbar
+  /// is already shown, or when no text selection currently exists.
+  bool showToolbar() {
+    // Web is using native dom elements to enable clipboard functionality of the
+    // toolbar: copy, paste, select, cut. It might also provide additional
+    // functionality depending on the browser (such as translate). Due to this
+    // we should not show a Flutter toolbar for the editable text elements.
+    if (kIsWeb) {
+      return false;
+    }
+
+    if (_selectionOverlay == null || _selectionOverlay!.toolbarIsVisible) {
+      return false;
+    }
+
+    _selectionOverlay!.showToolbar();
+    return true;
+  }
+
+  @override
+  void hideToolbar() {
+    _selectionOverlay?.hide();
+  }
+
+  /// Toggles the visibility of the toolbar.
+  void toggleToolbar() {
+    assert(_selectionOverlay != null);
+    if (_selectionOverlay!.toolbarIsVisible) {
+      hideToolbar();
+    } else {
+      showToolbar();
+    }
+  }
+
+  @override
+  String get autofillId => 'EditableText-$hashCode';
+
+  TextInputConfiguration _createTextInputConfiguration(bool needsAutofillConfiguration) {
+    assert(needsAutofillConfiguration != null);
+    return TextInputConfiguration(
+      inputType: widget.keyboardType,
+      readOnly: widget.readOnly,
+      obscureText: widget.obscureText,
+      autocorrect: widget.autocorrect,
+      smartDashesType: widget.smartDashesType,
+      smartQuotesType: widget.smartQuotesType,
+      enableSuggestions: widget.enableSuggestions,
+      inputAction: widget.textInputAction ?? (widget.keyboardType == TextInputType.multiline
+        ? TextInputAction.newline
+        : TextInputAction.done
+      ),
+      textCapitalization: widget.textCapitalization,
+      keyboardAppearance: widget.keyboardAppearance,
+      autofillConfiguration: !needsAutofillConfiguration ? null : AutofillConfiguration(
+        uniqueIdentifier: autofillId,
+        autofillHints: widget.autofillHints?.toList(growable: false) ?? <String>[],
+        currentEditingValue: currentTextEditingValue,
+      ),
+    );
+  }
+
+  @override
+  TextInputConfiguration get textInputConfiguration {
+    return _createTextInputConfiguration(_needsAutofill);
+  }
+
+  // null if no promptRect should be shown.
+  TextRange? _currentPromptRectRange;
+
+  @override
+  void showAutocorrectionPromptRect(int start, int end) {
+    setState(() {
+      _currentPromptRectRange = TextRange(start: start, end: end);
+    });
+  }
+
+  VoidCallback? _semanticsOnCopy(TextSelectionControls? controls) {
+    return widget.selectionEnabled && copyEnabled && _hasFocus && controls?.canCopy(this) == true
+      ? () => controls!.handleCopy(this, _clipboardStatus)
+      : null;
+  }
+
+  VoidCallback? _semanticsOnCut(TextSelectionControls? controls) {
+    return widget.selectionEnabled && cutEnabled && _hasFocus && controls?.canCut(this) == true
+      ? () => controls!.handleCut(this)
+      : null;
+  }
+
+  VoidCallback? _semanticsOnPaste(TextSelectionControls? controls) {
+    return widget.selectionEnabled && pasteEnabled && _hasFocus && controls?.canPaste(this) == true && (_clipboardStatus == null || _clipboardStatus!.value == ClipboardStatus.pasteable)
+      ? () => controls!.handlePaste(this)
+      : null;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    _focusAttachment!.reparent();
+    super.build(context); // See AutomaticKeepAliveClientMixin.
+
+    final TextSelectionControls? controls = widget.selectionControls;
+    return MouseRegion(
+      cursor: widget.mouseCursor ?? SystemMouseCursors.text,
+      child: Scrollable(
+        excludeFromSemantics: true,
+        axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
+        controller: _scrollController,
+        physics: widget.scrollPhysics,
+        dragStartBehavior: widget.dragStartBehavior,
+        restorationId: widget.restorationId,
+        viewportBuilder: (BuildContext context, ViewportOffset offset) {
+          return CompositedTransformTarget(
+            link: _toolbarLayerLink,
+            child: Semantics(
+              onCopy: _semanticsOnCopy(controls),
+              onCut: _semanticsOnCut(controls),
+              onPaste: _semanticsOnPaste(controls),
+              child: _Editable(
+                key: _editableKey,
+                startHandleLayerLink: _startHandleLayerLink,
+                endHandleLayerLink: _endHandleLayerLink,
+                textSpan: buildTextSpan(),
+                value: _value,
+                cursorColor: _cursorColor,
+                backgroundCursorColor: widget.backgroundCursorColor,
+                showCursor: EditableText.debugDeterministicCursor
+                    ? ValueNotifier<bool>(widget.showCursor)
+                    : _cursorVisibilityNotifier,
+                forceLine: widget.forceLine,
+                readOnly: widget.readOnly,
+                hasFocus: _hasFocus,
+                maxLines: widget.maxLines,
+                minLines: widget.minLines,
+                expands: widget.expands,
+                strutStyle: widget.strutStyle,
+                selectionColor: widget.selectionColor,
+                textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
+                textAlign: widget.textAlign,
+                textDirection: _textDirection,
+                locale: widget.locale,
+                textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.of(context),
+                textWidthBasis: widget.textWidthBasis,
+                obscuringCharacter: widget.obscuringCharacter,
+                obscureText: widget.obscureText,
+                autocorrect: widget.autocorrect,
+                smartDashesType: widget.smartDashesType,
+                smartQuotesType: widget.smartQuotesType,
+                enableSuggestions: widget.enableSuggestions,
+                offset: offset,
+                onSelectionChanged: _handleSelectionChanged,
+                onCaretChanged: _handleCaretChanged,
+                rendererIgnoresPointer: widget.rendererIgnoresPointer,
+                cursorWidth: widget.cursorWidth,
+                cursorHeight: widget.cursorHeight,
+                cursorRadius: widget.cursorRadius,
+                cursorOffset: widget.cursorOffset,
+                selectionHeightStyle: widget.selectionHeightStyle,
+                selectionWidthStyle: widget.selectionWidthStyle,
+                paintCursorAboveText: widget.paintCursorAboveText,
+                enableInteractiveSelection: widget.enableInteractiveSelection,
+                textSelectionDelegate: this,
+                devicePixelRatio: _devicePixelRatio,
+                promptRectRange: _currentPromptRectRange,
+                promptRectColor: widget.autocorrectionTextRectColor,
+                clipBehavior: widget.clipBehavior,
+              ),
+            ),
+          );
+        },
+      ),
+    );
+  }
+
+  /// Builds [TextSpan] from current editing value.
+  ///
+  /// By default makes text in composing range appear as underlined.
+  /// Descendants can override this method to customize appearance of text.
+  TextSpan buildTextSpan() {
+    if (widget.obscureText) {
+      String text = _value.text;
+      text = widget.obscuringCharacter * text.length;
+      // Reveal the latest character in an obscured field only on mobile.
+      if ((defaultTargetPlatform == TargetPlatform.android ||
+              defaultTargetPlatform == TargetPlatform.iOS ||
+              defaultTargetPlatform == TargetPlatform.fuchsia) &&
+          !kIsWeb) {
+        final int? o =
+            _obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null;
+        if (o != null && o >= 0 && o < text.length)
+          text = text.replaceRange(o, o + 1, _value.text.substring(o, o + 1));
+      }
+      return TextSpan(style: widget.style, text: text);
+    }
+    // Read only mode should not paint text composing.
+    return widget.controller.buildTextSpan(
+      style: widget.style,
+      withComposing: !widget.readOnly,
+    );
+  }
+}
+
+class _Editable extends LeafRenderObjectWidget {
+  const _Editable({
+    Key? key,
+    required this.textSpan,
+    required this.value,
+    required this.startHandleLayerLink,
+    required this.endHandleLayerLink,
+    this.cursorColor,
+    this.backgroundCursorColor,
+    required this.showCursor,
+    required this.forceLine,
+    required this.readOnly,
+    this.textHeightBehavior,
+    required this.textWidthBasis,
+    required this.hasFocus,
+    required this.maxLines,
+    this.minLines,
+    required this.expands,
+    this.strutStyle,
+    this.selectionColor,
+    required this.textScaleFactor,
+    required this.textAlign,
+    required this.textDirection,
+    this.locale,
+    required this.obscuringCharacter,
+    required this.obscureText,
+    required this.autocorrect,
+    required this.smartDashesType,
+    required this.smartQuotesType,
+    required this.enableSuggestions,
+    required this.offset,
+    this.onSelectionChanged,
+    this.onCaretChanged,
+    this.rendererIgnoresPointer = false,
+    required this.cursorWidth,
+    this.cursorHeight,
+    this.cursorRadius,
+    this.cursorOffset,
+    required this.paintCursorAboveText,
+    this.selectionHeightStyle = ui.BoxHeightStyle.tight,
+    this.selectionWidthStyle = ui.BoxWidthStyle.tight,
+    this.enableInteractiveSelection = true,
+    required this.textSelectionDelegate,
+    required this.devicePixelRatio,
+    this.promptRectRange,
+    this.promptRectColor,
+    required this.clipBehavior,
+  }) : assert(textDirection != null),
+       assert(rendererIgnoresPointer != null),
+       super(key: key);
+
+  final TextSpan textSpan;
+  final TextEditingValue value;
+  final Color? cursorColor;
+  final LayerLink startHandleLayerLink;
+  final LayerLink endHandleLayerLink;
+  final Color? backgroundCursorColor;
+  final ValueNotifier<bool> showCursor;
+  final bool forceLine;
+  final bool readOnly;
+  final bool hasFocus;
+  final int? maxLines;
+  final int? minLines;
+  final bool expands;
+  final StrutStyle? strutStyle;
+  final Color? selectionColor;
+  final double textScaleFactor;
+  final TextAlign textAlign;
+  final TextDirection textDirection;
+  final Locale? locale;
+  final String obscuringCharacter;
+  final bool obscureText;
+  final TextHeightBehavior? textHeightBehavior;
+  final TextWidthBasis textWidthBasis;
+  final bool autocorrect;
+  final SmartDashesType smartDashesType;
+  final SmartQuotesType smartQuotesType;
+  final bool enableSuggestions;
+  final ViewportOffset offset;
+  final SelectionChangedHandler? onSelectionChanged;
+  final CaretChangedHandler? onCaretChanged;
+  final bool rendererIgnoresPointer;
+  final double cursorWidth;
+  final double? cursorHeight;
+  final Radius? cursorRadius;
+  final Offset? cursorOffset;
+  final bool paintCursorAboveText;
+  final ui.BoxHeightStyle selectionHeightStyle;
+  final ui.BoxWidthStyle selectionWidthStyle;
+  final bool enableInteractiveSelection;
+  final TextSelectionDelegate textSelectionDelegate;
+  final double devicePixelRatio;
+  final TextRange? promptRectRange;
+  final Color? promptRectColor;
+  final Clip clipBehavior;
+
+  @override
+  RenderEditable createRenderObject(BuildContext context) {
+    return RenderEditable(
+      text: textSpan,
+      cursorColor: cursorColor,
+      startHandleLayerLink: startHandleLayerLink,
+      endHandleLayerLink: endHandleLayerLink,
+      backgroundCursorColor: backgroundCursorColor,
+      showCursor: showCursor,
+      forceLine: forceLine,
+      readOnly: readOnly,
+      hasFocus: hasFocus,
+      maxLines: maxLines,
+      minLines: minLines,
+      expands: expands,
+      strutStyle: strutStyle,
+      selectionColor: selectionColor,
+      textScaleFactor: textScaleFactor,
+      textAlign: textAlign,
+      textDirection: textDirection,
+      locale: locale ?? Localizations.maybeLocaleOf(context),
+      selection: value.selection,
+      offset: offset,
+      onSelectionChanged: onSelectionChanged,
+      onCaretChanged: onCaretChanged,
+      ignorePointer: rendererIgnoresPointer,
+      obscuringCharacter: obscuringCharacter,
+      obscureText: obscureText,
+      textHeightBehavior: textHeightBehavior,
+      textWidthBasis: textWidthBasis,
+      cursorWidth: cursorWidth,
+      cursorHeight: cursorHeight,
+      cursorRadius: cursorRadius,
+      cursorOffset: cursorOffset,
+      paintCursorAboveText: paintCursorAboveText,
+      selectionHeightStyle: selectionHeightStyle,
+      selectionWidthStyle: selectionWidthStyle,
+      enableInteractiveSelection: enableInteractiveSelection,
+      textSelectionDelegate: textSelectionDelegate,
+      devicePixelRatio: devicePixelRatio,
+      promptRectRange: promptRectRange,
+      promptRectColor: promptRectColor,
+      clipBehavior: clipBehavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderEditable renderObject) {
+    renderObject
+      ..text = textSpan
+      ..cursorColor = cursorColor
+      ..startHandleLayerLink = startHandleLayerLink
+      ..endHandleLayerLink = endHandleLayerLink
+      ..showCursor = showCursor
+      ..forceLine = forceLine
+      ..readOnly = readOnly
+      ..hasFocus = hasFocus
+      ..maxLines = maxLines
+      ..minLines = minLines
+      ..expands = expands
+      ..strutStyle = strutStyle
+      ..selectionColor = selectionColor
+      ..textScaleFactor = textScaleFactor
+      ..textAlign = textAlign
+      ..textDirection = textDirection
+      ..locale = locale ?? Localizations.maybeLocaleOf(context)
+      ..selection = value.selection
+      ..offset = offset
+      ..onSelectionChanged = onSelectionChanged
+      ..onCaretChanged = onCaretChanged
+      ..ignorePointer = rendererIgnoresPointer
+      ..textHeightBehavior = textHeightBehavior
+      ..textWidthBasis = textWidthBasis
+      ..obscuringCharacter = obscuringCharacter
+      ..obscureText = obscureText
+      ..cursorWidth = cursorWidth
+      ..cursorHeight = cursorHeight
+      ..cursorRadius = cursorRadius
+      ..cursorOffset = cursorOffset
+      ..selectionHeightStyle = selectionHeightStyle
+      ..selectionWidthStyle = selectionWidthStyle
+      ..textSelectionDelegate = textSelectionDelegate
+      ..devicePixelRatio = devicePixelRatio
+      ..paintCursorAboveText = paintCursorAboveText
+      ..promptRectColor = promptRectColor
+      ..clipBehavior = clipBehavior
+      ..setPromptRectRange(promptRectRange);
+  }
+}
+
+// This formatter inserts [Unicode.RLM] and [Unicode.LRM] into the
+// string in order to preserve expected caret behavior when trailing
+// whitespace is inserted.
+//
+// When typing in a direction that opposes the base direction
+// of the paragraph, un-enclosed whitespace gets the directionality
+// of the paragraph. This is often at odds with what is immediately
+// being typed causing the caret to jump to the wrong side of the text.
+// This formatter makes use of the RLM and LRM to cause the text
+// shaper to inherently treat the whitespace as being surrounded
+// by the directionality of the previous non-whitespace codepoint.
+class _WhitespaceDirectionalityFormatter extends TextInputFormatter {
+  // The [textDirection] should be the base directionality of the
+  // paragraph/editable.
+  _WhitespaceDirectionalityFormatter({TextDirection? textDirection})
+    : _baseDirection = textDirection,
+      _previousNonWhitespaceDirection = textDirection;
+
+  // Using regex here instead of ICU is suboptimal, but is enough
+  // to produce the correct results for any reasonable input where this
+  // is even relevant. Using full ICU would be a much heavier change,
+  // requiring exposure of the C++ ICU API.
+  //
+  // LTR covers most scripts and symbols, including but not limited to Latin,
+  // ideographic scripts (Chinese, Japanese, etc), Cyrilic, Indic, and
+  // SE Asian scripts.
+  final RegExp _ltrRegExp = RegExp(r'[A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF]');
+  // RTL covers Arabic, Hebrew, and other RTL languages such as Urdu,
+  // Aramic, Farsi, Dhivehi.
+  final RegExp _rtlRegExp = RegExp(r'[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]');
+  // Although whitespaces are not the only codepoints that have weak directionality,
+  // these are the primary cause of the caret being misplaced.
+  final RegExp _whitespaceRegExp = RegExp(r'\s');
+
+  final TextDirection? _baseDirection;
+  // Tracks the directionality of the most recently encountered
+  // codepoint that was not whitespace. This becomes the direction of
+  // marker inserted to fully surround ambiguous whitespace.
+  TextDirection? _previousNonWhitespaceDirection;
+
+  // Prevents the formatter from attempting more expensive formatting
+  // operations mixed directionality is found.
+  bool _hasOpposingDirection = false;
+
+  // See [Unicode.RLM] and [Unicode.LRM].
+  //
+  // We do not directly use the [Unicode] constants since they are strings.
+  static const int _rlm = 0x200F;
+  static const int _lrm = 0x200E;
+
+  @override
+  TextEditingValue formatEditUpdate(
+    TextEditingValue oldValue,
+    TextEditingValue newValue,
+  ) {
+    // Skip formatting (which can be more expensive) if there are no cases of
+    // mixing directionality. Once a case of mixed directionality is found,
+    // always perform the formatting.
+    if (!_hasOpposingDirection) {
+      _hasOpposingDirection = _baseDirection == TextDirection.ltr ?
+        _rtlRegExp.hasMatch(newValue.text) : _ltrRegExp.hasMatch(newValue.text);
+    }
+
+    if (_hasOpposingDirection) {
+      _previousNonWhitespaceDirection = _baseDirection;
+
+      final List<int> outputCodepoints = <int>[];
+
+      // We add/subtract from these as we insert/remove markers.
+      int selectionBase = newValue.selection.baseOffset;
+      int selectionExtent = newValue.selection.extentOffset;
+      int composingStart = newValue.composing.start;
+      int composingEnd = newValue.composing.end;
+
+      void addToLength() {
+        selectionBase += outputCodepoints.length <= selectionBase ? 1 : 0;
+        selectionExtent += outputCodepoints.length <= selectionExtent ? 1 : 0;
+
+        composingStart += outputCodepoints.length <= composingStart ? 1 : 0;
+        composingEnd += outputCodepoints.length <= composingEnd ? 1 : 0;
+      }
+      void subtractFromLength() {
+        selectionBase -= outputCodepoints.length < selectionBase ? 1 : 0;
+        selectionExtent -= outputCodepoints.length < selectionExtent ? 1 : 0;
+
+        composingStart -= outputCodepoints.length < composingStart ? 1 : 0;
+        composingEnd -= outputCodepoints.length < composingEnd ? 1 : 0;
+      }
+
+     final bool isBackspace = oldValue.text.runes.length - newValue.text.runes.length == 1 &&
+                              isDirectionalityMarker(oldValue.text.runes.last) &&
+                              oldValue.text.substring(0, oldValue.text.length - 1) == newValue.text;
+
+      bool previousWasWhitespace = false;
+      bool previousWasDirectionalityMarker = false;
+      int? previousNonWhitespaceCodepoint;
+      int index = 0;
+      for (final int codepoint in newValue.text.runes) {
+        if (isWhitespace(codepoint)) {
+          // Only compute the directionality of the non-whitespace
+          // when the value is needed.
+          if (!previousWasWhitespace && previousNonWhitespaceCodepoint != null) {
+            _previousNonWhitespaceDirection = getDirection(previousNonWhitespaceCodepoint);
+          }
+          // If we already added directionality for this run of whitespace,
+          // "shift" the marker added to the end of the whitespace run.
+          if (previousWasWhitespace) {
+            subtractFromLength();
+            outputCodepoints.removeLast();
+          }
+          // Handle trailing whitespace deleting the directionality char instead of the whitespace.
+          if (isBackspace && index == newValue.text.runes.length - 1) {
+            // Do not append the whitespace to the outputCodepoints.
+            subtractFromLength();
+          } else {
+            outputCodepoints.add(codepoint);
+            addToLength();
+            outputCodepoints.add(_previousNonWhitespaceDirection == TextDirection.rtl ? _rlm : _lrm);
+          }
+
+          previousWasWhitespace = true;
+          previousWasDirectionalityMarker = false;
+        } else if (isDirectionalityMarker(codepoint)) {
+          // Handle pre-existing directionality markers. Use pre-existing marker
+          // instead of the one we add.
+          if (previousWasWhitespace) {
+            subtractFromLength();
+            outputCodepoints.removeLast();
+          }
+          outputCodepoints.add(codepoint);
+
+          previousWasWhitespace = false;
+          previousWasDirectionalityMarker = true;
+        } else {
+          // If the whitespace was already enclosed by the same directionality,
+          // we can remove the artificially added marker.
+          if (!previousWasDirectionalityMarker &&
+              previousWasWhitespace &&
+              getDirection(codepoint) == _previousNonWhitespaceDirection) {
+            subtractFromLength();
+            outputCodepoints.removeLast();
+          }
+          // Normal character, track its codepoint add it to the string.
+          previousNonWhitespaceCodepoint = codepoint;
+          outputCodepoints.add(codepoint);
+
+          previousWasWhitespace = false;
+          previousWasDirectionalityMarker = false;
+        }
+        index++;
+      }
+      final String formatted = String.fromCharCodes(outputCodepoints);
+      return TextEditingValue(
+        text: formatted,
+        selection: TextSelection(
+          baseOffset: selectionBase,
+          extentOffset: selectionExtent,
+          affinity: newValue.selection.affinity,
+          isDirectional: newValue.selection.isDirectional
+        ),
+        composing: TextRange(start: composingStart, end: composingEnd),
+      );
+    }
+    return newValue;
+  }
+
+  bool isWhitespace(int value) {
+    return _whitespaceRegExp.hasMatch(String.fromCharCode(value));
+  }
+
+  bool isDirectionalityMarker(int value) {
+    return value == _rlm || value == _lrm;
+  }
+
+  TextDirection getDirection(int value) {
+    // Use the LTR version as short-circuiting will be more efficient since
+    // there are more LTR codepoints.
+    return _ltrRegExp.hasMatch(String.fromCharCode(value)) ? TextDirection.ltr : TextDirection.rtl;
+  }
+}
diff --git a/lib/src/widgets/fade_in_image.dart b/lib/src/widgets/fade_in_image.dart
new file mode 100644
index 0000000..dce69ae
--- /dev/null
+++ b/lib/src/widgets/fade_in_image.dart
@@ -0,0 +1,537 @@
+// Copyright 2014 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.
+
+import 'dart:typed_data';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/services.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'image.dart';
+import 'implicit_animations.dart';
+import 'transitions.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// Uint8List bytes;
+
+/// An image that shows a [placeholder] image while the target [image] is
+/// loading, then fades in the new image when it loads.
+///
+/// Use this class to display long-loading images, such as [new NetworkImage],
+/// so that the image appears on screen with a graceful animation rather than
+/// abruptly popping onto the screen.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=pK738Pg9cxc}
+///
+/// If the [image] emits an [ImageInfo] synchronously, such as when the image
+/// has been loaded and cached, the [image] is displayed immediately, and the
+/// [placeholder] is never displayed.
+///
+/// The [fadeOutDuration] and [fadeOutCurve] properties control the fade-out
+/// animation of the [placeholder].
+///
+/// The [fadeInDuration] and [fadeInCurve] properties control the fade-in
+/// animation of the target [image].
+///
+/// Prefer a [placeholder] that's already cached so that it is displayed
+/// immediately. This prevents it from popping onto the screen.
+///
+/// When [image] changes, it is resolved to a new [ImageStream]. If the new
+/// [ImageStream.key] is different, this widget subscribes to the new stream and
+/// replaces the displayed image with images emitted by the new stream.
+///
+/// When [placeholder] changes and the [image] has not yet emitted an
+/// [ImageInfo], then [placeholder] is resolved to a new [ImageStream]. If the
+/// new [ImageStream.key] is different, this widget subscribes to the new stream
+/// and replaces the displayed image to images emitted by the new stream.
+///
+/// When either [placeholder] or [image] changes, this widget continues showing
+/// the previously loaded image (if any) until the new image provider provides a
+/// different image. This is known as "gapless playback" (see also
+/// [Image.gaplessPlayback]).
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// FadeInImage(
+///   // here `bytes` is a Uint8List containing the bytes for the in-memory image
+///   placeholder: MemoryImage(bytes),
+///   image: NetworkImage('https://backend.example.com/image.png'),
+/// )
+/// ```
+/// {@end-tool}
+class FadeInImage extends StatelessWidget {
+  /// Creates a widget that displays a [placeholder] while an [image] is loading,
+  /// then fades-out the placeholder and fades-in the image.
+  ///
+  /// The [placeholder] and [image] may be composed in a [ResizeImage] to provide
+  /// a custom decode/cache size.
+  ///
+  /// The [placeholder], [image], [fadeOutDuration], [fadeOutCurve],
+  /// [fadeInDuration], [fadeInCurve], [alignment], [repeat], and
+  /// [matchTextDirection] arguments must not be null.
+  ///
+  /// If [excludeFromSemantics] is true, then [imageSemanticLabel] will be ignored.
+  const FadeInImage({
+    Key? key,
+    required this.placeholder,
+    this.placeholderErrorBuilder,
+    required this.image,
+    this.imageErrorBuilder,
+    this.excludeFromSemantics = false,
+    this.imageSemanticLabel,
+    this.fadeOutDuration = const Duration(milliseconds: 300),
+    this.fadeOutCurve = Curves.easeOut,
+    this.fadeInDuration = const Duration(milliseconds: 700),
+    this.fadeInCurve = Curves.easeIn,
+    this.width,
+    this.height,
+    this.fit,
+    this.alignment = Alignment.center,
+    this.repeat = ImageRepeat.noRepeat,
+    this.matchTextDirection = false,
+  }) : assert(placeholder != null),
+       assert(image != null),
+       assert(fadeOutDuration != null),
+       assert(fadeOutCurve != null),
+       assert(fadeInDuration != null),
+       assert(fadeInCurve != null),
+       assert(alignment != null),
+       assert(repeat != null),
+       assert(matchTextDirection != null),
+       super(key: key);
+
+  /// Creates a widget that uses a placeholder image stored in memory while
+  /// loading the final image from the network.
+  ///
+  /// The `placeholder` argument contains the bytes of the in-memory image.
+  ///
+  /// The `image` argument is the URL of the final image.
+  ///
+  /// The `placeholderScale` and `imageScale` arguments are passed to their
+  /// respective [ImageProvider]s (see also [ImageInfo.scale]).
+  ///
+  /// If [placeholderCacheWidth], [placeholderCacheHeight], [imageCacheWidth],
+  /// or [imageCacheHeight] are provided, it indicates to the
+  /// engine that the respective image should be decoded at the specified size.
+  /// The image will be rendered to the constraints of the layout or [width]
+  /// and [height] regardless of these parameters. These parameters are primarily
+  /// intended to reduce the memory usage of [ImageCache].
+  ///
+  /// The [placeholder], [image], [placeholderScale], [imageScale],
+  /// [fadeOutDuration], [fadeOutCurve], [fadeInDuration], [fadeInCurve],
+  /// [alignment], [repeat], and [matchTextDirection] arguments must not be
+  /// null.
+  ///
+  /// See also:
+  ///
+  ///  * [new Image.memory], which has more details about loading images from
+  ///    memory.
+  ///  * [new Image.network], which has more details about loading images from
+  ///    the network.
+  FadeInImage.memoryNetwork({
+    Key? key,
+    required Uint8List placeholder,
+    this.placeholderErrorBuilder,
+    required String image,
+    this.imageErrorBuilder,
+    double placeholderScale = 1.0,
+    double imageScale = 1.0,
+    this.excludeFromSemantics = false,
+    this.imageSemanticLabel,
+    this.fadeOutDuration = const Duration(milliseconds: 300),
+    this.fadeOutCurve = Curves.easeOut,
+    this.fadeInDuration = const Duration(milliseconds: 700),
+    this.fadeInCurve = Curves.easeIn,
+    this.width,
+    this.height,
+    this.fit,
+    this.alignment = Alignment.center,
+    this.repeat = ImageRepeat.noRepeat,
+    this.matchTextDirection = false,
+    int? placeholderCacheWidth,
+    int? placeholderCacheHeight,
+    int? imageCacheWidth,
+    int? imageCacheHeight,
+  }) : assert(placeholder != null),
+       assert(image != null),
+       assert(placeholderScale != null),
+       assert(imageScale != null),
+       assert(fadeOutDuration != null),
+       assert(fadeOutCurve != null),
+       assert(fadeInDuration != null),
+       assert(fadeInCurve != null),
+       assert(alignment != null),
+       assert(repeat != null),
+       assert(matchTextDirection != null),
+       placeholder = ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, MemoryImage(placeholder, scale: placeholderScale)),
+       image = ResizeImage.resizeIfNeeded(imageCacheWidth, imageCacheHeight, NetworkImage(image, scale: imageScale)),
+       super(key: key);
+
+  /// Creates a widget that uses a placeholder image stored in an asset bundle
+  /// while loading the final image from the network.
+  ///
+  /// The `placeholder` argument is the key of the image in the asset bundle.
+  ///
+  /// The `image` argument is the URL of the final image.
+  ///
+  /// The `placeholderScale` and `imageScale` arguments are passed to their
+  /// respective [ImageProvider]s (see also [ImageInfo.scale]).
+  ///
+  /// If `placeholderScale` is omitted or is null, pixel-density-aware asset
+  /// resolution will be attempted for the [placeholder] image. Otherwise, the
+  /// exact asset specified will be used.
+  ///
+  /// If [placeholderCacheWidth], [placeholderCacheHeight], [imageCacheWidth],
+  /// or [imageCacheHeight] are provided, it indicates to the
+  /// engine that the respective image should be decoded at the specified size.
+  /// The image will be rendered to the constraints of the layout or [width]
+  /// and [height] regardless of these parameters. These parameters are primarily
+  /// intended to reduce the memory usage of [ImageCache].
+  ///
+  /// The [placeholder], [image], [imageScale], [fadeOutDuration],
+  /// [fadeOutCurve], [fadeInDuration], [fadeInCurve], [alignment], [repeat],
+  /// and [matchTextDirection] arguments must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [new Image.asset], which has more details about loading images from
+  ///    asset bundles.
+  ///  * [new Image.network], which has more details about loading images from
+  ///    the network.
+  FadeInImage.assetNetwork({
+    Key? key,
+    required String placeholder,
+    this.placeholderErrorBuilder,
+    required String image,
+    this.imageErrorBuilder,
+    AssetBundle? bundle,
+    double? placeholderScale,
+    double imageScale = 1.0,
+    this.excludeFromSemantics = false,
+    this.imageSemanticLabel,
+    this.fadeOutDuration = const Duration(milliseconds: 300),
+    this.fadeOutCurve = Curves.easeOut,
+    this.fadeInDuration = const Duration(milliseconds: 700),
+    this.fadeInCurve = Curves.easeIn,
+    this.width,
+    this.height,
+    this.fit,
+    this.alignment = Alignment.center,
+    this.repeat = ImageRepeat.noRepeat,
+    this.matchTextDirection = false,
+    int? placeholderCacheWidth,
+    int? placeholderCacheHeight,
+    int? imageCacheWidth,
+    int? imageCacheHeight,
+  }) : assert(placeholder != null),
+       assert(image != null),
+       placeholder = placeholderScale != null
+         ? ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, ExactAssetImage(placeholder, bundle: bundle, scale: placeholderScale))
+         : ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, AssetImage(placeholder, bundle: bundle)),
+       assert(imageScale != null),
+       assert(fadeOutDuration != null),
+       assert(fadeOutCurve != null),
+       assert(fadeInDuration != null),
+       assert(fadeInCurve != null),
+       assert(alignment != null),
+       assert(repeat != null),
+       assert(matchTextDirection != null),
+       image = ResizeImage.resizeIfNeeded(imageCacheWidth, imageCacheHeight, NetworkImage(image, scale: imageScale)),
+       super(key: key);
+
+  /// Image displayed while the target [image] is loading.
+  final ImageProvider placeholder;
+
+  /// A builder function that is called if an error occurs during placeholder
+  /// image loading.
+  ///
+  /// If this builder is not provided, any exceptions will be reported to
+  /// [FlutterError.onError]. If it is provided, the caller should either handle
+  /// the exception by providing a replacement widget, or rethrow the exception.
+  final ImageErrorWidgetBuilder? placeholderErrorBuilder;
+
+  /// The target image that is displayed once it has loaded.
+  final ImageProvider image;
+
+  /// A builder function that is called if an error occurs during image loading.
+  ///
+  /// If this builder is not provided, any exceptions will be reported to
+  /// [FlutterError.onError]. If it is provided, the caller should either handle
+  /// the exception by providing a replacement widget, or rethrow the exception.
+  final ImageErrorWidgetBuilder? imageErrorBuilder;
+
+  /// The duration of the fade-out animation for the [placeholder].
+  final Duration fadeOutDuration;
+
+  /// The curve of the fade-out animation for the [placeholder].
+  final Curve fadeOutCurve;
+
+  /// The duration of the fade-in animation for the [image].
+  final Duration fadeInDuration;
+
+  /// The curve of the fade-in animation for the [image].
+  final Curve fadeInCurve;
+
+  /// If non-null, require the image to have this width.
+  ///
+  /// If null, the image will pick a size that best preserves its intrinsic
+  /// aspect ratio. This may result in a sudden change if the size of the
+  /// placeholder image does not match that of the target image. The size is
+  /// also affected by the scale factor.
+  final double? width;
+
+  /// If non-null, require the image to have this height.
+  ///
+  /// If null, the image will pick a size that best preserves its intrinsic
+  /// aspect ratio. This may result in a sudden change if the size of the
+  /// placeholder image does not match that of the target image. The size is
+  /// also affected by the scale factor.
+  final double? height;
+
+  /// How to inscribe the image into the space allocated during layout.
+  ///
+  /// The default varies based on the other fields. See the discussion at
+  /// [paintImage].
+  final BoxFit? fit;
+
+  /// How to align the image within its bounds.
+  ///
+  /// The alignment aligns the given position in the image to the given position
+  /// in the layout bounds. For example, an [Alignment] alignment of (-1.0,
+  /// -1.0) aligns the image to the top-left corner of its layout bounds, while an
+  /// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
+  /// image with the bottom right corner of its layout bounds. Similarly, an
+  /// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
+  /// middle of the bottom edge of its layout bounds.
+  ///
+  /// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
+  /// [AlignmentDirectional]), then an ambient [Directionality] widget
+  /// must be in scope.
+  ///
+  /// Defaults to [Alignment.center].
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], a class with convenient constants typically used to
+  ///    specify an [AlignmentGeometry].
+  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
+  ///    relative to text direction.
+  final AlignmentGeometry alignment;
+
+  /// How to paint any portions of the layout bounds not covered by the image.
+  final ImageRepeat repeat;
+
+  /// Whether to paint the image in the direction of the [TextDirection].
+  ///
+  /// If this is true, then in [TextDirection.ltr] contexts, the image will be
+  /// drawn with its origin in the top left (the "normal" painting direction for
+  /// images); and in [TextDirection.rtl] contexts, the image will be drawn with
+  /// a scaling factor of -1 in the horizontal direction so that the origin is
+  /// in the top right.
+  ///
+  /// This is occasionally used with images in right-to-left environments, for
+  /// images that were designed for left-to-right locales. Be careful, when
+  /// using this, to not flip images with integral shadows, text, or other
+  /// effects that will look incorrect when flipped.
+  ///
+  /// If this is true, there must be an ambient [Directionality] widget in
+  /// scope.
+  final bool matchTextDirection;
+
+  /// Whether to exclude this image from semantics.
+  ///
+  /// This is useful for images which do not contribute meaningful information
+  /// to an application.
+  final bool excludeFromSemantics;
+
+  /// A semantic description of the [image].
+  ///
+  /// Used to provide a description of the [image] to TalkBack on Android, and
+  /// VoiceOver on iOS.
+  ///
+  /// This description will be used both while the [placeholder] is shown and
+  /// once the image has loaded.
+  final String? imageSemanticLabel;
+
+  Image _image({
+    required ImageProvider image,
+    ImageErrorWidgetBuilder? errorBuilder,
+    ImageFrameBuilder? frameBuilder,
+  }) {
+    assert(image != null);
+    return Image(
+      image: image,
+      errorBuilder: errorBuilder,
+      frameBuilder: frameBuilder,
+      width: width,
+      height: height,
+      fit: fit,
+      alignment: alignment,
+      repeat: repeat,
+      matchTextDirection: matchTextDirection,
+      gaplessPlayback: true,
+      excludeFromSemantics: true,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    Widget result = _image(
+      image: image,
+      errorBuilder: imageErrorBuilder,
+      frameBuilder: (BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) {
+        if (wasSynchronouslyLoaded)
+          return child;
+        return _AnimatedFadeOutFadeIn(
+          target: child,
+          placeholder: _image(image: placeholder, errorBuilder: placeholderErrorBuilder),
+          isTargetLoaded: frame != null,
+          fadeInDuration: fadeInDuration,
+          fadeOutDuration: fadeOutDuration,
+          fadeInCurve: fadeInCurve,
+          fadeOutCurve: fadeOutCurve,
+        );
+      },
+    );
+
+    if (!excludeFromSemantics) {
+      result = Semantics(
+        container: imageSemanticLabel != null,
+        image: true,
+        label: imageSemanticLabel ?? '',
+        child: result,
+      );
+    }
+
+    return result;
+  }
+}
+
+class _AnimatedFadeOutFadeIn extends ImplicitlyAnimatedWidget {
+  const _AnimatedFadeOutFadeIn({
+    Key? key,
+    required this.target,
+    required this.placeholder,
+    required this.isTargetLoaded,
+    required this.fadeOutDuration,
+    required this.fadeOutCurve,
+    required this.fadeInDuration,
+    required this.fadeInCurve,
+  }) : assert(target != null),
+       assert(placeholder != null),
+       assert(isTargetLoaded != null),
+       assert(fadeOutDuration != null),
+       assert(fadeOutCurve != null),
+       assert(fadeInDuration != null),
+       assert(fadeInCurve != null),
+       super(key: key, duration: fadeInDuration + fadeOutDuration);
+
+  final Widget target;
+  final Widget placeholder;
+  final bool isTargetLoaded;
+  final Duration fadeInDuration;
+  final Duration fadeOutDuration;
+  final Curve fadeInCurve;
+  final Curve fadeOutCurve;
+
+  @override
+  _AnimatedFadeOutFadeInState createState() => _AnimatedFadeOutFadeInState();
+}
+
+class _AnimatedFadeOutFadeInState extends ImplicitlyAnimatedWidgetState<_AnimatedFadeOutFadeIn> {
+  Tween<double>? _targetOpacity;
+  Tween<double>? _placeholderOpacity;
+  Animation<double>? _targetOpacityAnimation;
+  Animation<double>? _placeholderOpacityAnimation;
+
+  @override
+  void forEachTween(TweenVisitor<dynamic> visitor) {
+    _targetOpacity = visitor(
+      _targetOpacity,
+      widget.isTargetLoaded ? 1.0 : 0.0,
+      (dynamic value) => Tween<double>(begin: value as double),
+    ) as Tween<double>?;
+    _placeholderOpacity = visitor(
+      _placeholderOpacity,
+      widget.isTargetLoaded ? 0.0 : 1.0,
+      (dynamic value) => Tween<double>(begin: value as double),
+    ) as Tween<double>?;
+  }
+
+  @override
+  void didUpdateTweens() {
+    _placeholderOpacityAnimation = animation.drive(TweenSequence<double>(<TweenSequenceItem<double>>[
+      TweenSequenceItem<double>(
+        tween: _placeholderOpacity!.chain(CurveTween(curve: widget.fadeOutCurve)),
+        weight: widget.fadeOutDuration.inMilliseconds.toDouble(),
+      ),
+      TweenSequenceItem<double>(
+        tween: ConstantTween<double>(0),
+        weight: widget.fadeInDuration.inMilliseconds.toDouble(),
+      ),
+    ]))..addStatusListener((AnimationStatus status) {
+      if (_placeholderOpacityAnimation!.isCompleted) {
+        // Need to rebuild to remove placeholder now that it is invisible.
+        setState(() {});
+      }
+    });
+
+    _targetOpacityAnimation = animation.drive(TweenSequence<double>(<TweenSequenceItem<double>>[
+      TweenSequenceItem<double>(
+        tween: ConstantTween<double>(0),
+        weight: widget.fadeOutDuration.inMilliseconds.toDouble(),
+      ),
+      TweenSequenceItem<double>(
+        tween: _targetOpacity!.chain(CurveTween(curve: widget.fadeInCurve)),
+        weight: widget.fadeInDuration.inMilliseconds.toDouble(),
+      ),
+    ]));
+    if (!widget.isTargetLoaded && _isValid(_placeholderOpacity!) && _isValid(_targetOpacity!)) {
+      // Jump (don't fade) back to the placeholder image, so as to be ready
+      // for the full animation when the new target image becomes ready.
+      controller.value = controller.upperBound;
+    }
+  }
+
+  bool _isValid(Tween<double> tween) {
+    return tween.begin != null && tween.end != null;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final Widget target = FadeTransition(
+      opacity: _targetOpacityAnimation!,
+      child: widget.target,
+    );
+
+    if (_placeholderOpacityAnimation!.isCompleted) {
+      return target;
+    }
+
+    return Stack(
+      fit: StackFit.passthrough,
+      alignment: AlignmentDirectional.center,
+      // Text direction is irrelevant here since we're using center alignment,
+      // but it allows the Stack to avoid a call to Directionality.of()
+      textDirection: TextDirection.ltr,
+      children: <Widget>[
+        target,
+        FadeTransition(
+          opacity: _placeholderOpacityAnimation!,
+          child: widget.placeholder,
+        ),
+      ],
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Animation<double>>('targetOpacity', _targetOpacityAnimation));
+    properties.add(DiagnosticsProperty<Animation<double>>('placeholderOpacity', _placeholderOpacityAnimation));
+  }
+}
diff --git a/lib/src/widgets/focus_manager.dart b/lib/src/widgets/focus_manager.dart
new file mode 100644
index 0000000..0e42d38
--- /dev/null
+++ b/lib/src/widgets/focus_manager.dart
@@ -0,0 +1,1829 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'package:flute/ui.dart';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/painting.dart';
+import 'package:flute/services.dart';
+
+import 'binding.dart';
+import 'focus_scope.dart';
+import 'focus_traversal.dart';
+import 'framework.dart';
+
+// Used for debugging focus code. Set to true to see highly verbose debug output
+// when focus changes occur.
+const bool _kDebugFocus = false;
+
+bool _focusDebug(String message, [Iterable<String>? details]) {
+  if (_kDebugFocus) {
+    debugPrint('FOCUS: $message');
+    if (details != null && details.isNotEmpty) {
+      for (final String detail in details) {
+        debugPrint('    $detail');
+      }
+    }
+  }
+  return true;
+}
+
+/// An enum that describes how to handle a key event handled by a
+/// [FocusOnKeyCallback].
+enum KeyEventResult {
+  /// The key event has been handled, and the event should not be propagated to
+  /// other key event handlers.
+  handled,
+  /// The key event has not been handled, and the event should continue to be
+  /// propagated to other key event handlers, even non-Flutter ones.
+  ignored,
+  /// The key event has not been handled, but the key event should not be
+  /// propagated to other key event handlers.
+  ///
+  /// It will be returned to the platform embedding to be propagated to text
+  /// fields and non-Flutter key event handlers on the platform.
+  skipRemainingHandlers,
+}
+
+/// Signature of a callback used by [Focus.onKey] and [FocusScope.onKey]
+/// to receive key events.
+///
+/// The [node] is the node that received the event.
+///
+/// Returns a [KeyEventResult] that describes how, and whether, the key event
+/// was handled.
+// TODO(gspencergoog): Convert this from dynamic to KeyEventResult once migration is complete.
+typedef FocusOnKeyCallback = dynamic Function(FocusNode node, RawKeyEvent event);
+
+/// An attachment point for a [FocusNode].
+///
+/// Using a [FocusAttachment] is rarely needed, unless you are building
+/// something akin to the [Focus] or [FocusScope] widgets from scratch.
+///
+/// Once created, a [FocusNode] must be attached to the widget tree by its
+/// _host_ [StatefulWidget] via a [FocusAttachment] object. [FocusAttachment]s
+/// are owned by the [StatefulWidget] that hosts a [FocusNode] or
+/// [FocusScopeNode]. There can be multiple [FocusAttachment]s for each
+/// [FocusNode], but the node will only ever be attached to one of them at a
+/// time.
+///
+/// This attachment is created by calling [FocusNode.attach], usually from the
+/// host widget's [State.initState] method. If the widget is updated to have a
+/// different focus node, then the new node needs to be attached in
+/// [State.didUpdateWidget], after calling [detach] on the previous
+/// [FocusAttachment]. Once detached, the attachment is defunct and will no
+/// longer make changes to the [FocusNode] through [reparent].
+///
+/// Without these attachment points, it would be possible for a focus node to
+/// simultaneously be attached to more than one part of the widget tree during
+/// the build stage.
+class FocusAttachment {
+  /// A private constructor, because [FocusAttachment]s are only to be created
+  /// by [FocusNode.attach].
+  FocusAttachment._(this._node) : assert(_node != null);
+
+  // The focus node that this attachment manages an attachment for. The node may
+  // not yet have a parent, or may have been detached from this attachment, so
+  // don't count on this node being in a usable state.
+  final FocusNode _node;
+
+  /// Returns true if the associated node is attached to this attachment.
+  ///
+  /// It is possible to be attached to the widget tree, but not be placed in
+  /// the focus tree (i.e. to not have a parent yet in the focus tree).
+  bool get isAttached => _node._attachment == this;
+
+  /// Detaches the [FocusNode] this attachment point is associated with from the
+  /// focus tree, and disconnects it from this attachment point.
+  ///
+  /// Calling [FocusNode.dispose] will also automatically detach the node.
+  void detach() {
+    assert(_node != null);
+    assert(_focusDebug('Detaching node:', <String>[_node.toString(), 'With enclosing scope ${_node.enclosingScope}']));
+    if (isAttached) {
+      if (_node.hasPrimaryFocus || (_node._manager != null && _node._manager!._markedForFocus == _node)) {
+        _node.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
+      }
+      // This node is no longer in the tree, so shouldn't send notifications anymore.
+      _node._manager?._markDetached(_node);
+      _node._parent?._removeChild(_node);
+      _node._attachment = null;
+      assert(!_node.hasPrimaryFocus);
+      assert(_node._manager?._markedForFocus != _node);
+    }
+    assert(!isAttached);
+  }
+
+  /// Ensures that the [FocusNode] attached at this attachment point has the
+  /// proper parent node, changing it if necessary.
+  ///
+  /// If given, ensures that the given [parent] node is the parent of the node
+  /// that is attached at this attachment point, changing it if necessary.
+  /// However, it is usually not necessary to supply an explicit parent, since
+  /// [reparent] will use [Focus.of] to determine the correct parent node for
+  /// the context given in [FocusNode.attach].
+  ///
+  /// If [isAttached] is false, then calling this method does nothing.
+  ///
+  /// Should be called whenever the associated widget is rebuilt in order to
+  /// maintain the focus hierarchy.
+  ///
+  /// A [StatefulWidget] that hosts a [FocusNode] should call this method on the
+  /// node it hosts during its [State.build] or [State.didChangeDependencies]
+  /// methods in case the widget is moved from one location in the tree to
+  /// another location that has a different [FocusScope] or context.
+  ///
+  /// The optional [parent] argument must be supplied when not using [Focus] and
+  /// [FocusScope] widgets to build the focus tree, or if there is a need to
+  /// supply the parent explicitly (which are both uncommon).
+  void reparent({FocusNode? parent}) {
+    assert(_node != null);
+    if (isAttached) {
+      assert(_node.context != null);
+      parent ??= Focus.maybeOf(_node.context!, scopeOk: true);
+      parent ??= _node.context!.owner!.focusManager.rootScope;
+      assert(parent != null);
+      parent._reparent(_node);
+    }
+  }
+}
+
+/// Describe what should happen after [FocusNode.unfocus] is called.
+///
+/// See also:
+///
+///  * [FocusNode.unfocus], which takes this as its `disposition` parameter.
+enum UnfocusDisposition {
+  /// Focus the nearest focusable enclosing scope of this node, but do not
+  /// descend to locate the leaf [FocusScopeNode.focusedChild] the way
+  /// [previouslyFocusedChild] does.
+  ///
+  /// Focusing the scope in this way clears the [FocusScopeNode.focusedChild]
+  /// history for the enclosing scope when it receives focus. Because of this,
+  /// calling a traversal method like [FocusNode.nextFocus] after unfocusing
+  /// will cause the [FocusTraversalPolicy] to pick the node it thinks should be
+  /// first in the scope.
+  ///
+  /// This is the default disposition for [FocusNode.unfocus].
+  scope,
+
+  /// Focus the previously focused child of the nearest focusable enclosing
+  /// scope of this node.
+  ///
+  /// If there is no previously focused child, then this is equivalent to
+  /// using the [scope] disposition.
+  ///
+  /// Unfocusing with this disposition will cause [FocusNode.unfocus] to walk up
+  /// the tree to the nearest focusable enclosing scope, then start to walk down
+  /// the tree, looking for a focused child at its
+  /// [FocusScopeNode.focusedChild].
+  ///
+  /// If the [FocusScopeNode.focusedChild] is a scope, then look for its
+  /// [FocusScopeNode.focusedChild], and so on, finding the leaf
+  /// [FocusScopeNode.focusedChild] that is not a scope, or, failing that, a
+  /// leaf scope that has no focused child.
+  previouslyFocusedChild,
+}
+
+/// An object that can be used by a stateful widget to obtain the keyboard focus
+/// and to handle keyboard events.
+///
+/// _Please see the [Focus] and [FocusScope] widgets, which are utility widgets
+/// that manage their own [FocusNode]s and [FocusScopeNode]s, respectively. If
+/// they aren't appropriate, [FocusNode]s can be managed directly, but doing
+/// this yourself is rare._
+///
+/// [FocusNode]s are persistent objects that form a _focus tree_ that is a
+/// representation of the widgets in the hierarchy that are interested in focus.
+/// A focus node might need to be created if it is passed in from an ancestor of
+/// a [Focus] widget to control the focus of the children from the ancestor, or
+/// a widget might need to host one if the widget subsystem is not being used,
+/// or if the [Focus] and [FocusScope] widgets provide insufficient control.
+///
+/// [FocusNode]s are organized into _scopes_ (see [FocusScopeNode]), which form
+/// sub-trees of nodes that restrict traversal to a group of nodes. Within a
+/// scope, the most recent nodes to have focus are remembered, and if a node is
+/// focused and then unfocused, the previous node receives focus again.
+///
+/// The focus node hierarchy can be traversed using the [parent], [children],
+/// [ancestors] and [descendants] accessors.
+///
+/// [FocusNode]s are [ChangeNotifier]s, so a listener can be registered to
+/// receive a notification when the focus changes. If the [Focus] and
+/// [FocusScope] widgets are being used to manage the nodes, consider
+/// establishing an [InheritedWidget] dependency on them by calling [Focus.of]
+/// or [FocusScope.of] instead. [FocusNode.hasFocus] can also be used to
+/// establish a similar dependency, especially if all that is needed is to
+/// determine whether or not the widget is focused at build time.
+///
+/// To see the focus tree in the debug console, call [debugDumpFocusTree]. To
+/// get the focus tree as a string, call [debugDescribeFocusTree].
+///
+/// {@template flutter.widgets.FocusNode.lifecycle}
+/// ## Lifecycle
+///
+/// There are several actors involved in the lifecycle of a
+/// [FocusNode]/[FocusScopeNode]. They are created and disposed by their
+/// _owner_, attached, detached, and re-parented using a [FocusAttachment] by
+/// their _host_ (which must be owned by the [State] of a [StatefulWidget]), and
+/// they are managed by the [FocusManager]. Different parts of the [FocusNode]
+/// API are intended for these different actors.
+///
+/// [FocusNode]s (and hence [FocusScopeNode]s) are persistent objects that form
+/// part of a _focus tree_ that is a sparse representation of the widgets in the
+/// hierarchy that are interested in receiving keyboard events. They must be
+/// managed like other persistent state, which is typically done by a
+/// [StatefulWidget] that owns the node. A stateful widget that owns a focus
+/// scope node must call [dispose] from its [State.dispose] method.
+///
+/// Once created, a [FocusNode] must be attached to the widget tree via a
+/// [FocusAttachment] object. This attachment is created by calling [attach],
+/// usually from the [State.initState] method. If the hosting widget is updated
+/// to have a different focus node, then the updated node needs to be attached
+/// in [State.didUpdateWidget], after calling [FocusAttachment.detach] on the
+/// previous [FocusAttachment].
+///
+/// Because [FocusNode]s form a sparse representation of the widget tree, they
+/// must be updated whenever the widget tree is rebuilt. This is done by calling
+/// [FocusAttachment.reparent], usually from the [State.build] or
+/// [State.didChangeDependencies] methods of the widget that represents the
+/// focused region, so that the [BuildContext] assigned to the [FocusScopeNode]
+/// can be tracked (the context is used to obtain the [RenderObject], from which
+/// the geometry of focused regions can be determined).
+///
+/// Creating a [FocusNode] each time [State.build] is invoked will cause the
+/// focus to be lost each time the widget is built, which is usually not desired
+/// behavior (call [unfocus] if losing focus is desired).
+///
+/// If, as is common, the hosting [StatefulWidget] is also the owner of the
+/// focus node, then it will also call [dispose] from its [State.dispose] (in
+/// which case the [FocusAttachment.detach] may be skipped, since dispose will
+/// automatically detach). If another object owns the focus node, then it must
+/// call [dispose] when the node is done being used.
+/// {@endtemplate}
+///
+/// {@template flutter.widgets.FocusNode.keyEvents}
+/// ## Key Event Propagation
+///
+/// The [FocusManager] receives key events from [RawKeyboard] and will pass them
+/// to the focused nodes. It starts with the node with the primary focus, and
+/// will call the [onKey] callback for that node. If the callback returns false,
+/// indicating that it did not handle the event, the [FocusManager] will move to
+/// the parent of that node and call its [onKey]. If that [onKey] returns true,
+/// then it will stop propagating the event. If it reaches the root
+/// [FocusScopeNode], [FocusManager.rootScope], the event is discarded.
+/// {@endtemplate}
+///
+/// ## Focus Traversal
+///
+/// The term _traversal_, sometimes called _tab traversal_, refers to moving the
+/// focus from one widget to the next in a particular order (also sometimes
+/// referred to as the _tab order_, since the TAB key is often bound to the
+/// action to move to the next widget).
+///
+/// To give focus to the logical _next_ or _previous_ widget in the UI, call the
+/// [nextFocus] or [previousFocus] methods. To give the focus to a widget in a
+/// particular direction, call the [focusInDirection] method.
+///
+/// The policy for what the _next_ or _previous_ widget is, or the widget in a
+/// particular direction, is determined by the [FocusTraversalPolicy] in force.
+///
+/// The ambient policy is determined by looking up the widget hierarchy for a
+/// [FocusTraversalGroup] widget, and obtaining the focus traversal policy from
+/// it. Different focus nodes can inherit difference policies, so part of the
+/// app can go in a predefined order (using [OrderedTraversalPolicy]), and part
+/// can go in reading order (using [ReadingOrderTraversalPolicy]), depending
+/// upon the use case.
+///
+/// Predefined policies include [WidgetOrderTraversalPolicy],
+/// [ReadingOrderTraversalPolicy], [OrderedTraversalPolicy], and
+/// [DirectionalFocusTraversalPolicyMixin], but custom policies can be built
+/// based upon these policies. See [FocusTraversalPolicy] for more information.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety} This example shows how
+/// a FocusNode should be managed if not using the [Focus] or [FocusScope]
+/// widgets. See the [Focus] widget for a similar example using [Focus] and
+/// [FocusScope] widgets.
+///
+/// ```dart imports
+/// import 'package:flute/services.dart';
+/// ```
+///
+/// ```dart preamble
+/// class ColorfulButton extends StatefulWidget {
+///   ColorfulButton({Key key}) : super(key: key);
+///
+///   @override
+///   _ColorfulButtonState createState() => _ColorfulButtonState();
+/// }
+///
+/// class _ColorfulButtonState extends State<ColorfulButton> {
+///   FocusNode _node;
+///   bool _focused = false;
+///   FocusAttachment _nodeAttachment;
+///   Color _color = Colors.white;
+///
+///   @override
+///   void initState() {
+///     super.initState();
+///     _node = FocusNode(debugLabel: 'Button');
+///     _node.addListener(_handleFocusChange);
+///     _nodeAttachment = _node.attach(context, onKey: _handleKeyPress);
+///   }
+///
+///   void _handleFocusChange() {
+///     if (_node.hasFocus != _focused) {
+///       setState(() {
+///         _focused = _node.hasFocus;
+///       });
+///     }
+///   }
+///
+///   KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
+///     if (event is RawKeyDownEvent) {
+///       print('Focus node ${node.debugLabel} got key event: ${event.logicalKey}');
+///       if (event.logicalKey == LogicalKeyboardKey.keyR) {
+///         print('Changing color to red.');
+///         setState(() {
+///           _color = Colors.red;
+///         });
+///         return KeyEventResult.handled;
+///       } else if (event.logicalKey == LogicalKeyboardKey.keyG) {
+///         print('Changing color to green.');
+///         setState(() {
+///           _color = Colors.green;
+///         });
+///         return KeyEventResult.handled;
+///       } else if (event.logicalKey == LogicalKeyboardKey.keyB) {
+///         print('Changing color to blue.');
+///         setState(() {
+///           _color = Colors.blue;
+///         });
+///         return KeyEventResult.handled;
+///       }
+///     }
+///     return KeyEventResult.ignored;
+///   }
+///
+///   @override
+///   void dispose() {
+///     _node.removeListener(_handleFocusChange);
+///     // The attachment will automatically be detached in dispose().
+///     _node.dispose();
+///     super.dispose();
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     _nodeAttachment.reparent();
+///     return GestureDetector(
+///       onTap: () {
+///         if (_focused) {
+///             _node.unfocus();
+///         } else {
+///            _node.requestFocus();
+///         }
+///       },
+///       child: Center(
+///         child: Container(
+///           width: 400,
+///           height: 100,
+///           color: _focused ? _color : Colors.white,
+///           alignment: Alignment.center,
+///           child: Text(
+///               _focused ? "I'm in color! Press R,G,B!" : 'Press to focus'),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   final TextTheme textTheme = Theme.of(context).textTheme;
+///   return DefaultTextStyle(
+///     style: textTheme.headline4,
+///     child: ColorfulButton(),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+/// * [Focus], a widget that manages a [FocusNode] and provides access to focus
+///   information and actions to its descendant widgets.
+/// * [FocusTraversalGroup], a widget used to group together and configure the
+///   focus traversal policy for a widget subtree.
+/// * [FocusManager], a singleton that manages the primary focus and distributes
+///   key events to focused nodes.
+/// * [FocusTraversalPolicy], a class used to determine how to move the focus to
+///   other nodes.
+class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
+  /// Creates a focus node.
+  ///
+  /// The [debugLabel] is ignored on release builds.
+  ///
+  /// The [skipTraversal], [descendantsAreFocusable], and [canRequestFocus]
+  /// arguments must not be null.
+  FocusNode({
+    String? debugLabel,
+    FocusOnKeyCallback? onKey,
+    bool skipTraversal = false,
+    bool canRequestFocus = true,
+    bool descendantsAreFocusable = true,
+  })  : assert(skipTraversal != null),
+        assert(canRequestFocus != null),
+        assert(descendantsAreFocusable != null),
+        _skipTraversal = skipTraversal,
+        _canRequestFocus = canRequestFocus,
+        _descendantsAreFocusable = descendantsAreFocusable,
+        _onKey = onKey {
+    // Set it via the setter so that it does nothing on release builds.
+    this.debugLabel = debugLabel;
+  }
+
+  /// If true, tells the focus traversal policy to skip over this node for
+  /// purposes of the traversal algorithm.
+  ///
+  /// This may be used to place nodes in the focus tree that may be focused, but
+  /// not traversed, allowing them to receive key events as part of the focus
+  /// chain, but not be traversed to via focus traversal.
+  ///
+  /// This is different from [canRequestFocus] because it only implies that the
+  /// node can't be reached via traversal, not that it can't be focused. It may
+  /// still be focused explicitly.
+  bool get skipTraversal => _skipTraversal;
+  bool _skipTraversal;
+  set skipTraversal(bool value) {
+    if (value != _skipTraversal) {
+      _skipTraversal = value;
+      _manager?._markPropertiesChanged(this);
+    }
+  }
+
+  /// If true, this focus node may request the primary focus.
+  ///
+  /// Defaults to true.  Set to false if you want this node to do nothing when
+  /// [requestFocus] is called on it.
+  ///
+  /// If set to false on a [FocusScopeNode], will cause all of the children of
+  /// the scope node to not be focusable.
+  ///
+  /// If set to false on a [FocusNode], it will not affect the children of the
+  /// node.
+  ///
+  /// The [hasFocus] member can still return true if this node is the ancestor
+  /// of a node with primary focus.
+  ///
+  /// This is different than [skipTraversal] because [skipTraversal] still
+  /// allows the node to be focused, just not traversed to via the
+  /// [FocusTraversalPolicy]
+  ///
+  /// Setting [canRequestFocus] to false implies that the node will also be
+  /// skipped for traversal purposes.
+  ///
+  /// See also:
+  ///
+  ///  * [FocusTraversalGroup], a widget used to group together and configure the
+  ///    focus traversal policy for a widget subtree.
+  ///  * [FocusTraversalPolicy], a class that can be extended to describe a
+  ///    traversal policy.
+  bool get canRequestFocus {
+    if (!_canRequestFocus) {
+      return false;
+    }
+    final FocusScopeNode? scope = enclosingScope;
+    if (scope != null && !scope.canRequestFocus) {
+      return false;
+    }
+    for (final FocusNode ancestor in ancestors) {
+      if (!ancestor.descendantsAreFocusable) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  bool _canRequestFocus;
+  @mustCallSuper
+  set canRequestFocus(bool value) {
+    if (value != _canRequestFocus) {
+      // Have to set this first before unfocusing, since it checks this to cull
+      // unfocusable, previously-focused children.
+      _canRequestFocus = value;
+      if (hasFocus && !value) {
+        unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
+      }
+      _manager?._markPropertiesChanged(this);
+    }
+  }
+
+  /// If false, will disable focus for all of this node's descendants.
+  ///
+  /// Defaults to true. Does not affect focusability of this node: for that,
+  /// use [canRequestFocus].
+  ///
+  /// If any descendants are focused when this is set to false, they will be
+  /// unfocused. When `descendantsAreFocusable` is set to true again, they will
+  /// not be refocused, although they will be able to accept focus again.
+  ///
+  /// Does not affect the value of [canRequestFocus] on the descendants.
+  ///
+  /// If a descendant node loses focus when this value is changed, the focus
+  /// will move to the scope enclosing this node.
+  ///
+  /// See also:
+  ///
+  ///  * [ExcludeFocus], a widget that uses this property to conditionally
+  ///    exclude focus for a subtree.
+  ///  * [Focus], a widget that exposes this setting as a parameter.
+  ///  * [FocusTraversalGroup], a widget used to group together and configure
+  ///    the focus traversal policy for a widget subtree that also has an
+  ///    `descendantsAreFocusable` parameter that prevents its children from
+  ///    being focused.
+  bool get descendantsAreFocusable => _descendantsAreFocusable;
+  bool _descendantsAreFocusable;
+  @mustCallSuper
+  set descendantsAreFocusable(bool value) {
+    if (value == _descendantsAreFocusable) {
+      return;
+    }
+    // Set _descendantsAreFocusable before unfocusing, so the scope won't try
+    // and focus any of the children here again if it is false.
+    _descendantsAreFocusable = value;
+    if (!value && hasFocus) {
+      unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
+    }
+    _manager?._markPropertiesChanged(this);
+  }
+
+  /// The context that was supplied to [attach].
+  ///
+  /// This is typically the context for the widget that is being focused, as it
+  /// is used to determine the bounds of the widget.
+  BuildContext? get context => _context;
+  BuildContext? _context;
+
+  /// Called if this focus node receives a key event while focused (i.e. when
+  /// [hasFocus] returns true).
+  ///
+  /// {@macro flutter.widgets.FocusNode.keyEvents}
+  FocusOnKeyCallback? get onKey => _onKey;
+  FocusOnKeyCallback? _onKey;
+
+  FocusManager? _manager;
+  List<FocusNode>? _ancestors;
+  List<FocusNode>? _descendants;
+  bool _hasKeyboardToken = false;
+
+  /// Returns the parent node for this object.
+  ///
+  /// All nodes except for the root [FocusScopeNode] ([FocusManager.rootScope])
+  /// will be given a parent when they are added to the focus tree, which is
+  /// done using [FocusAttachment.reparent].
+  FocusNode? get parent => _parent;
+  FocusNode? _parent;
+
+  /// An iterator over the children of this node.
+  Iterable<FocusNode> get children => _children;
+  final List<FocusNode> _children = <FocusNode>[];
+
+  /// An iterator over the children that are allowed to be traversed by the
+  /// [FocusTraversalPolicy].
+  Iterable<FocusNode> get traversalChildren {
+    if (!canRequestFocus) {
+      return const <FocusNode>[];
+    }
+    return children.where(
+      (FocusNode node) => !node.skipTraversal && node.canRequestFocus,
+    );
+  }
+
+  /// A debug label that is used for diagnostic output.
+  ///
+  /// Will always return null in release builds.
+  String? get debugLabel => _debugLabel;
+  String? _debugLabel;
+  set debugLabel(String? value) {
+    assert(() {
+      // Only set the value in debug builds.
+      _debugLabel = value;
+      return true;
+    }());
+  }
+
+  FocusAttachment? _attachment;
+
+  /// An [Iterable] over the hierarchy of children below this one, in
+  /// depth-first order.
+  Iterable<FocusNode> get descendants {
+    if (_descendants == null) {
+      final List<FocusNode> result = <FocusNode>[];
+      for (final FocusNode child in _children) {
+        result.addAll(child.descendants);
+        result.add(child);
+      }
+      _descendants = result;
+    }
+    return _descendants!;
+  }
+
+  /// Returns all descendants which do not have the [skipTraversal] and do have
+  /// the [canRequestFocus] flag set.
+  Iterable<FocusNode> get traversalDescendants => descendants.where((FocusNode node) => !node.skipTraversal && node.canRequestFocus);
+
+  /// An [Iterable] over the ancestors of this node.
+  ///
+  /// Iterates the ancestors of this node starting at the parent and iterating
+  /// over successively more remote ancestors of this node, ending at the root
+  /// [FocusScopeNode] ([FocusManager.rootScope]).
+  Iterable<FocusNode> get ancestors {
+    if (_ancestors == null) {
+      final List<FocusNode> result = <FocusNode>[];
+      FocusNode? parent = _parent;
+      while (parent != null) {
+        result.add(parent);
+        parent = parent._parent;
+      }
+      _ancestors = result;
+    }
+    return _ancestors!;
+  }
+
+  /// Whether this node has input focus.
+  ///
+  /// A [FocusNode] has focus when it is an ancestor of a node that returns true
+  /// from [hasPrimaryFocus], or it has the primary focus itself.
+  ///
+  /// The [hasFocus] accessor is different from [hasPrimaryFocus] in that
+  /// [hasFocus] is true if the node is anywhere in the focus chain, but for
+  /// [hasPrimaryFocus] the node must to be at the end of the chain to return
+  /// true.
+  ///
+  /// A node that returns true for [hasFocus] will receive key events if none of
+  /// its focused descendants returned true from their [onKey] handler.
+  ///
+  /// This object is a [ChangeNotifier], and notifies its [Listenable] listeners
+  /// (registered via [addListener]) whenever this value changes.
+  ///
+  /// See also:
+  ///
+  ///  * [Focus.isAt], which is a static method that will return the focus
+  ///    state of the nearest ancestor [Focus] widget's focus node.
+  bool get hasFocus => hasPrimaryFocus || (_manager?.primaryFocus?.ancestors.contains(this) ?? false);
+
+  /// Returns true if this node currently has the application-wide input focus.
+  ///
+  /// A [FocusNode] has the primary focus when the node is focused in its
+  /// nearest ancestor [FocusScopeNode] and [hasFocus] is true for all its
+  /// ancestor nodes, but none of its descendants.
+  ///
+  /// This is different from [hasFocus] in that [hasFocus] is true if the node
+  /// is anywhere in the focus chain, but here the node has to be at the end of
+  /// the chain to return true.
+  ///
+  /// A node that returns true for [hasPrimaryFocus] will be the first node to
+  /// receive key events through its [onKey] handler.
+  ///
+  /// This object notifies its listeners whenever this value changes.
+  bool get hasPrimaryFocus => _manager?.primaryFocus == this;
+
+  /// Returns the [FocusHighlightMode] that is currently in effect for this node.
+  FocusHighlightMode get highlightMode => FocusManager.instance.highlightMode;
+
+  /// Returns the nearest enclosing scope node above this node, including
+  /// this node, if it's a scope.
+  ///
+  /// Returns null if no scope is found.
+  ///
+  /// Use [enclosingScope] to look for scopes above this node.
+  FocusScopeNode? get nearestScope => enclosingScope;
+
+  /// Returns the nearest enclosing scope node above this node, or null if the
+  /// node has not yet be added to the focus tree.
+  ///
+  /// If this node is itself a scope, this will only return ancestors of this
+  /// scope.
+  ///
+  /// Use [nearestScope] to start at this node instead of above it.
+  FocusScopeNode? get enclosingScope {
+    for (final FocusNode node in ancestors) {
+      if (node is FocusScopeNode)
+        return node;
+    }
+    return null;
+  }
+
+  /// Returns the size of the attached widget's [RenderObject], in logical
+  /// units.
+  ///
+  /// Size is the size of the transformed widget in global coordinates.
+  Size get size => rect.size;
+
+  /// Returns the global offset to the upper left corner of the attached
+  /// widget's [RenderObject], in logical units.
+  ///
+  /// Offset is the offset of the transformed widget in global coordinates.
+  Offset get offset {
+    assert(
+        context != null,
+        "Tried to get the offset of a focus node that didn't have its context set yet.\n"
+        'The context needs to be set before trying to evaluate traversal policies. '
+        'Setting the context is typically done with the attach method.');
+    final RenderObject object = context!.findRenderObject()!;
+    return MatrixUtils.transformPoint(object.getTransformTo(null), object.semanticBounds.topLeft);
+  }
+
+  /// Returns the global rectangle of the attached widget's [RenderObject], in
+  /// logical units.
+  ///
+  /// Rect is the rectangle of the transformed widget in global coordinates.
+  Rect get rect {
+    assert(
+        context != null,
+        "Tried to get the bounds of a focus node that didn't have its context set yet.\n"
+        'The context needs to be set before trying to evaluate traversal policies. '
+        'Setting the context is typically done with the attach method.');
+    final RenderObject object = context!.findRenderObject()!;
+    final Offset topLeft = MatrixUtils.transformPoint(object.getTransformTo(null), object.semanticBounds.topLeft);
+    final Offset bottomRight = MatrixUtils.transformPoint(object.getTransformTo(null), object.semanticBounds.bottomRight);
+    return Rect.fromLTRB(topLeft.dx, topLeft.dy, bottomRight.dx, bottomRight.dy);
+  }
+
+  /// Removes the focus on this node by moving the primary focus to another node.
+  ///
+  /// This method removes focus from a node that has the primary focus, cancels
+  /// any outstanding requests to focus it, while setting the primary focus to
+  /// another node according to the `disposition`.
+  ///
+  /// It is safe to call regardless of whether this node has ever requested
+  /// focus or not. If this node doesn't have focus or primary focus, nothing
+  /// happens.
+  ///
+  /// The `disposition` argument determines which node will receive primary
+  /// focus after this one loses it.
+  ///
+  /// If `disposition` is set to [UnfocusDisposition.scope] (the default), then
+  /// the previously focused node history of the enclosing scope will be
+  /// cleared, and the primary focus will be moved to the nearest enclosing
+  /// scope ancestor that is enabled for focus, ignoring the
+  /// [FocusScopeNode.focusedChild] for that scope.
+  ///
+  /// If `disposition` is set to [UnfocusDisposition.previouslyFocusedChild],
+  /// then this node will be removed from the previously focused list in the
+  /// [enclosingScope], and the focus will be moved to the previously focused
+  /// node of the [enclosingScope], which (if it is a scope itself), will find
+  /// its focused child, etc., until a leaf focus node is found. If there is no
+  /// previously focused child, then the scope itself will receive focus, as if
+  /// [UnfocusDisposition.scope] were specified.
+  ///
+  /// If you want this node to lose focus and the focus to move to the next or
+  /// previous node in the enclosing [FocusTraversalGroup], call [nextFocus] or
+  /// [previousFocus] instead of calling `unfocus`.
+  ///
+  /// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+  /// This example shows the difference between the different [UnfocusDisposition]
+  /// values for [unfocus].
+  ///
+  /// Try setting focus on the four text fields by selecting them, and then
+  /// select "UNFOCUS" to see what happens when the current
+  /// [FocusManager.primaryFocus] is unfocused.
+  ///
+  /// Try pressing the TAB key after unfocusing to see what the next widget
+  /// chosen is.
+  ///
+  /// ```dart imports
+  /// import 'package:flute/foundation.dart';
+  /// ```
+  ///
+  /// ```dart
+  /// UnfocusDisposition disposition = UnfocusDisposition.scope;
+  ///
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return Material(
+  ///     child: Container(
+  ///       color: Colors.white,
+  ///       child: Column(
+  ///         mainAxisAlignment: MainAxisAlignment.center,
+  ///         children: <Widget>[
+  ///           Wrap(
+  ///             children: List<Widget>.generate(4, (int index) {
+  ///               return SizedBox(
+  ///                 width: 200,
+  ///                 child: Padding(
+  ///                   padding: const EdgeInsets.all(8.0),
+  ///                   child: TextField(
+  ///                     decoration: InputDecoration(border: OutlineInputBorder()),
+  ///                   ),
+  ///                 ),
+  ///               );
+  ///             }),
+  ///           ),
+  ///           Row(
+  ///             mainAxisAlignment: MainAxisAlignment.spaceAround,
+  ///             children: <Widget>[
+  ///               ...List<Widget>.generate(UnfocusDisposition.values.length,
+  ///                   (int index) {
+  ///                 return Row(
+  ///                   mainAxisSize: MainAxisSize.min,
+  ///                   children: <Widget>[
+  ///                     Radio<UnfocusDisposition>(
+  ///                       groupValue: disposition,
+  ///                       onChanged: (UnfocusDisposition value) {
+  ///                         setState(() {
+  ///                           disposition = value;
+  ///                         });
+  ///                       },
+  ///                       value: UnfocusDisposition.values[index],
+  ///                     ),
+  ///                     Text(describeEnum(UnfocusDisposition.values[index])),
+  ///                   ],
+  ///                 );
+  ///               }),
+  ///               OutlinedButton(
+  ///                 child: const Text('UNFOCUS'),
+  ///                 onPressed: () {
+  ///                   setState(() {
+  ///                     primaryFocus.unfocus(disposition: disposition);
+  ///                   });
+  ///                 },
+  ///               ),
+  ///             ],
+  ///           ),
+  ///         ],
+  ///       ),
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  void unfocus({
+    UnfocusDisposition disposition = UnfocusDisposition.scope,
+  }) {
+    assert(disposition != null);
+    if (!hasFocus && (_manager == null || _manager!._markedForFocus != this)) {
+      return;
+    }
+    FocusScopeNode? scope = enclosingScope;
+    if (scope == null) {
+      // If the scope is null, then this is either the root node, or a node that
+      // is not yet in the tree, neither of which do anything when unfocused.
+      return;
+    }
+    switch (disposition) {
+      case UnfocusDisposition.scope:
+        // If it can't request focus, then don't modify its focused children.
+        if (scope.canRequestFocus) {
+          // Clearing the focused children here prevents re-focusing the node
+          // that we just unfocused if we immediately hit "next" after
+          // unfocusing, and also prevents choosing to refocus the next-to-last
+          // focused child if unfocus is called more than once.
+          scope._focusedChildren.clear();
+        }
+
+        while (!scope!.canRequestFocus) {
+          scope = scope.enclosingScope ?? _manager?.rootScope;
+        }
+        scope._doRequestFocus(findFirstFocus: false);
+        break;
+      case UnfocusDisposition.previouslyFocusedChild:
+        // Select the most recent focused child from the nearest focusable scope
+        // and focus that. If there isn't one, focus the scope itself.
+        if (scope.canRequestFocus) {
+          scope._focusedChildren.remove(this);
+        }
+        while (!scope!.canRequestFocus) {
+          scope.enclosingScope?._focusedChildren.remove(scope);
+          scope = scope.enclosingScope ?? _manager?.rootScope;
+        }
+        scope._doRequestFocus(findFirstFocus: true);
+        break;
+    }
+    assert(_focusDebug('Unfocused node:', <String>['primary focus was $this', 'next focus will be ${_manager?._markedForFocus}']));
+  }
+
+  /// Removes the keyboard token from this focus node if it has one.
+  ///
+  /// This mechanism helps distinguish between an input control gaining focus by
+  /// default and gaining focus as a result of an explicit user action.
+  ///
+  /// When a focus node requests the focus (either via
+  /// [FocusScopeNode.requestFocus] or [FocusScopeNode.autofocus]), the focus
+  /// node receives a keyboard token if it does not already have one. Later,
+  /// when the focus node becomes focused, the widget that manages the
+  /// [TextInputConnection] should show the keyboard (i.e. call
+  /// [TextInputConnection.show]) only if it successfully consumes the keyboard
+  /// token from the focus node.
+  ///
+  /// Returns true if this method successfully consumes the keyboard token.
+  bool consumeKeyboardToken() {
+    if (!_hasKeyboardToken) {
+      return false;
+    }
+    _hasKeyboardToken = false;
+    return true;
+  }
+
+  // Marks the node as being the next to be focused, meaning that it will become
+  // the primary focus and notify listeners of a focus change the next time
+  // focus is resolved by the manager. If something else calls _markNextFocus
+  // before then, then that node will become the next focus instead of the
+  // previous one.
+  void _markNextFocus(FocusNode newFocus) {
+    if (_manager != null) {
+      // If we have a manager, then let it handle the focus change.
+      _manager!._markNextFocus(this);
+      return;
+    }
+    // If we don't have a manager, then change the focus locally.
+    newFocus._setAsFocusedChildForScope();
+    newFocus._notify();
+    if (newFocus != this) {
+      _notify();
+    }
+  }
+
+  // Removes the given FocusNode and its children as a child of this node.
+  @mustCallSuper
+  void _removeChild(FocusNode node, {bool removeScopeFocus = true}) {
+    assert(node != null);
+    assert(_children.contains(node), "Tried to remove a node that wasn't a child.");
+    assert(node._parent == this);
+    assert(node._manager == _manager);
+
+    if (removeScopeFocus) {
+      node.enclosingScope?._focusedChildren.remove(node);
+    }
+
+    node._parent = null;
+    _children.remove(node);
+    for (final FocusNode ancestor in ancestors) {
+      ancestor._descendants = null;
+    }
+    _descendants = null;
+    assert(_manager == null || !_manager!.rootScope.descendants.contains(node));
+  }
+
+  void _updateManager(FocusManager? manager) {
+    _manager = manager;
+    for (final FocusNode descendant in descendants) {
+      descendant._manager = manager;
+      descendant._ancestors = null;
+    }
+  }
+
+  // Used by FocusAttachment.reparent to perform the actual parenting operation.
+  @mustCallSuper
+  void _reparent(FocusNode child) {
+    assert(child != null);
+    assert(child != this, 'Tried to make a child into a parent of itself.');
+    if (child._parent == this) {
+      assert(_children.contains(child), "Found a node that says it's a child, but doesn't appear in the child list.");
+      // The child is already a child of this parent.
+      return;
+    }
+    assert(_manager == null || child != _manager!.rootScope, "Reparenting the root node isn't allowed.");
+    assert(!ancestors.contains(child), 'The supplied child is already an ancestor of this node. Loops are not allowed.');
+    final FocusScopeNode? oldScope = child.enclosingScope;
+    final bool hadFocus = child.hasFocus;
+    child._parent?._removeChild(child, removeScopeFocus: oldScope != nearestScope);
+    _children.add(child);
+    child._parent = this;
+    child._ancestors = null;
+    child._updateManager(_manager);
+    for (final FocusNode ancestor in child.ancestors) {
+      ancestor._descendants = null;
+    }
+    if (hadFocus) {
+      // Update the focus chain for the current focus without changing it.
+      _manager?.primaryFocus?._setAsFocusedChildForScope();
+    }
+    if (oldScope != null && child.context != null && child.enclosingScope != oldScope) {
+      FocusTraversalGroup.maybeOf(child.context!)?.changedScope(node: child, oldScope: oldScope);
+    }
+    if (child._requestFocusWhenReparented) {
+      child._doRequestFocus(findFirstFocus: true);
+      child._requestFocusWhenReparented = false;
+    }
+  }
+
+  /// Called by the _host_ [StatefulWidget] to attach a [FocusNode] to the
+  /// widget tree.
+  ///
+  /// In order to attach a [FocusNode] to the widget tree, call [attach],
+  /// typically from the [StatefulWidget]'s [State.initState] method.
+  ///
+  /// If the focus node in the host widget is swapped out, the new node will
+  /// need to be attached. [FocusAttachment.detach] should be called on the old
+  /// node, and then [attach] called on the new node. This typically happens in
+  /// the [State.didUpdateWidget] method.
+  @mustCallSuper
+  FocusAttachment attach(BuildContext? context, {FocusOnKeyCallback? onKey}) {
+    _context = context;
+    _onKey = onKey ?? _onKey;
+    _attachment = FocusAttachment._(this);
+    return _attachment!;
+  }
+
+  @override
+  void dispose() {
+    // Detaching will also unfocus and clean up the manager's data structures.
+    _attachment?.detach();
+    super.dispose();
+  }
+
+  @mustCallSuper
+  void _notify() {
+    if (_parent == null) {
+      // no longer part of the tree, so don't notify.
+      return;
+    }
+    if (hasPrimaryFocus) {
+      _setAsFocusedChildForScope();
+    }
+    notifyListeners();
+  }
+
+  /// Requests the primary focus for this node, or for a supplied [node], which
+  /// will also give focus to its [ancestors].
+  ///
+  /// If called without a node, request focus for this node. If the node hasn't
+  /// been added to the focus tree yet, then defer the focus request until it
+  /// is, allowing newly created widgets to request focus as soon as they are
+  /// added.
+  ///
+  /// If the given [node] is not yet a part of the focus tree, then this method
+  /// will add the [node] as a child of this node before requesting focus.
+  ///
+  /// If the given [node] is a [FocusScopeNode] and that focus scope node has a
+  /// non-null [FocusScopeNode.focusedChild], then request the focus for the
+  /// focused child. This process is recursive and continues until it encounters
+  /// either a focus scope node with a null focused child or an ordinary
+  /// (non-scope) [FocusNode] is found.
+  ///
+  /// The node is notified that it has received the primary focus in a
+  /// microtask, so notification may lag the request by up to one frame.
+  void requestFocus([FocusNode? node]) {
+    if (node != null) {
+      if (node._parent == null) {
+        _reparent(node);
+      }
+      assert(node.ancestors.contains(this), 'Focus was requested for a node that is not a descendant of the scope from which it was requested.');
+      node._doRequestFocus(findFirstFocus: true);
+      return;
+    }
+    _doRequestFocus(findFirstFocus: true);
+  }
+
+  // Note that this is overridden in FocusScopeNode.
+  void _doRequestFocus({required bool findFirstFocus}) {
+    assert(findFirstFocus != null);
+    if (!canRequestFocus) {
+      assert(_focusDebug('Node NOT requesting focus because canRequestFocus is false: $this'));
+      return;
+    }
+    // If the node isn't part of the tree, then we just defer the focus request
+    // until the next time it is reparented, so that it's possible to focus
+    // newly added widgets.
+    if (_parent == null) {
+      _requestFocusWhenReparented = true;
+      return;
+    }
+    _setAsFocusedChildForScope();
+    if (hasPrimaryFocus && (_manager!._markedForFocus == null || _manager!._markedForFocus == this)) {
+      return;
+    }
+    _hasKeyboardToken = true;
+    assert(_focusDebug('Node requesting focus: $this'));
+    _markNextFocus(this);
+  }
+
+  // If set to true, the node will request focus on this node the next time
+  // this node is reparented in the focus tree.
+  //
+  // Once requestFocus has been called at the next reparenting, this value
+  // will be reset to false.
+  //
+  // This will only force a call to requestFocus for the node once the next time
+  // the node is reparented. After that, _requestFocusWhenReparented would need
+  // to be set to true again to have it be focused again on the next
+  // reparenting.
+  //
+  // This is used when requestFocus is called and there is no parent yet.
+  bool _requestFocusWhenReparented = false;
+
+  /// Sets this node as the [FocusScopeNode.focusedChild] of the enclosing
+  /// scope.
+  ///
+  /// Sets this node as the focused child for the enclosing scope, and that
+  /// scope as the focused child for the scope above it, etc., until it reaches
+  /// the root node. It doesn't change the primary focus, it just changes what
+  /// node would be focused if the enclosing scope receives focus, and keeps
+  /// track of previously focused children in that scope, so that if the focused
+  /// child in that scope is removed, the previous focus returns.
+  void _setAsFocusedChildForScope() {
+    FocusNode scopeFocus = this;
+    for (final FocusScopeNode ancestor in ancestors.whereType<FocusScopeNode>()) {
+      assert(scopeFocus != ancestor, 'Somehow made a loop by setting focusedChild to its scope.');
+      assert(_focusDebug('Setting $scopeFocus as focused child for scope:', <String>[ancestor.toString()]));
+      // Remove it anywhere in the focused child history.
+      ancestor._focusedChildren.remove(scopeFocus);
+      // Add it to the end of the list, which is also the top of the queue: The
+      // end of the list represents the currently focused child.
+      ancestor._focusedChildren.add(scopeFocus);
+      scopeFocus = ancestor;
+    }
+  }
+
+  /// Request to move the focus to the next focus node, by calling the
+  /// [FocusTraversalPolicy.next] method.
+  ///
+  /// Returns true if it successfully found a node and requested focus.
+  bool nextFocus() => FocusTraversalGroup.of(context!).next(this);
+
+  /// Request to move the focus to the previous focus node, by calling the
+  /// [FocusTraversalPolicy.previous] method.
+  ///
+  /// Returns true if it successfully found a node and requested focus.
+  bool previousFocus() => FocusTraversalGroup.of(context!).previous(this);
+
+  /// Request to move the focus to the nearest focus node in the given
+  /// direction, by calling the [FocusTraversalPolicy.inDirection] method.
+  ///
+  /// Returns true if it successfully found a node and requested focus.
+  bool focusInDirection(TraversalDirection direction) => FocusTraversalGroup.of(context!).inDirection(this, direction);
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<BuildContext>('context', context, defaultValue: null));
+    properties.add(FlagProperty('descendantsAreFocusable', value: descendantsAreFocusable, ifFalse: 'DESCENDANTS UNFOCUSABLE', defaultValue: true));
+    properties.add(FlagProperty('canRequestFocus', value: canRequestFocus, ifFalse: 'NOT FOCUSABLE', defaultValue: true));
+    properties.add(FlagProperty('hasFocus', value: hasFocus && !hasPrimaryFocus, ifTrue: 'IN FOCUS PATH', defaultValue: false));
+    properties.add(FlagProperty('hasPrimaryFocus', value: hasPrimaryFocus, ifTrue: 'PRIMARY FOCUS', defaultValue: false));
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    int count = 1;
+    return _children.map<DiagnosticsNode>((FocusNode child) {
+      return child.toDiagnosticsNode(name: 'Child ${count++}');
+    }).toList();
+  }
+
+  @override
+  String toStringShort() {
+    final bool hasDebugLabel = debugLabel != null && debugLabel!.isNotEmpty;
+    final String extraData = '${hasDebugLabel ? debugLabel : ''}'
+        '${hasFocus && hasDebugLabel ? ' ' : ''}'
+        '${hasFocus && !hasPrimaryFocus ? '[IN FOCUS PATH]' : ''}'
+        '${hasPrimaryFocus ? '[PRIMARY FOCUS]' : ''}';
+    return '${describeIdentity(this)}${extraData.isNotEmpty ? '($extraData)' : ''}';
+  }
+}
+
+/// A subclass of [FocusNode] that acts as a scope for its descendants,
+/// maintaining information about which descendant is currently or was last
+/// focused.
+///
+/// _Please see the [FocusScope] and [Focus] widgets, which are utility widgets
+/// that manage their own [FocusScopeNode]s and [FocusNode]s, respectively. If
+/// they aren't appropriate, [FocusScopeNode]s can be managed directly._
+///
+/// [FocusScopeNode] organizes [FocusNode]s into _scopes_. Scopes form sub-trees
+/// of nodes that can be traversed as a group. Within a scope, the most recent
+/// nodes to have focus are remembered, and if a node is focused and then
+/// removed, the original node receives focus again.
+///
+/// From a [FocusScopeNode], calling [setFirstFocus], sets the given focus scope
+/// as the [focusedChild] of this node, adopting if it isn't already part of the
+/// focus tree.
+///
+/// {@macro flutter.widgets.FocusNode.lifecycle}
+/// {@macro flutter.widgets.FocusNode.keyEvents}
+///
+/// See also:
+///
+///  * [Focus], a widget that manages a [FocusNode] and provides access to focus
+///    information and actions to its descendant widgets.
+///  * [FocusManager], a singleton that manages the primary focus and
+///    distributes key events to focused nodes.
+class FocusScopeNode extends FocusNode {
+  /// Creates a [FocusScopeNode].
+  ///
+  /// All parameters are optional.
+  FocusScopeNode({
+    String? debugLabel,
+    FocusOnKeyCallback? onKey,
+    bool skipTraversal = false,
+    bool canRequestFocus = true,
+  })  : assert(skipTraversal != null),
+        assert(canRequestFocus != null),
+        super(
+          debugLabel: debugLabel,
+          onKey: onKey,
+          canRequestFocus: canRequestFocus,
+          descendantsAreFocusable: true,
+          skipTraversal: skipTraversal,
+        );
+
+  @override
+  FocusScopeNode get nearestScope => this;
+
+  /// Returns true if this scope is the focused child of its parent scope.
+  bool get isFirstFocus => enclosingScope!.focusedChild == this;
+
+  /// Returns the child of this node that should receive focus if this scope
+  /// node receives focus.
+  ///
+  /// If [hasFocus] is true, then this points to the child of this node that is
+  /// currently focused.
+  ///
+  /// Returns null if there is no currently focused child.
+  FocusNode? get focusedChild {
+    assert(_focusedChildren.isEmpty || _focusedChildren.last.enclosingScope == this, 'Focused child does not have the same idea of its enclosing scope as the scope does.');
+    return _focusedChildren.isNotEmpty ? _focusedChildren.last : null;
+  }
+
+  // A stack of the children that have been set as the focusedChild, most recent
+  // last (which is the top of the stack).
+  final List<FocusNode> _focusedChildren = <FocusNode>[];
+
+  /// Make the given [scope] the active child scope for this scope.
+  ///
+  /// If the given [scope] is not yet a part of the focus tree, then add it to
+  /// the tree as a child of this scope. If it is already part of the focus
+  /// tree, the given scope must be a descendant of this scope.
+  void setFirstFocus(FocusScopeNode scope) {
+    assert(scope != null);
+    assert(scope != this, 'Unexpected self-reference in setFirstFocus.');
+    assert(_focusDebug('Setting scope as first focus in $this to node:', <String>[scope.toString()]));
+    if (scope._parent == null) {
+      _reparent(scope);
+    }
+    assert(scope.ancestors.contains(this), '$FocusScopeNode $scope must be a child of $this to set it as first focus.');
+    if (hasFocus) {
+      scope._doRequestFocus(findFirstFocus: true);
+    } else {
+      scope._setAsFocusedChildForScope();
+    }
+  }
+
+  /// If this scope lacks a focus, request that the given node become the focus.
+  ///
+  /// If the given node is not yet part of the focus tree, then add it as a
+  /// child of this node.
+  ///
+  /// Useful for widgets that wish to grab the focus if no other widget already
+  /// has the focus.
+  ///
+  /// The node is notified that it has received the primary focus in a
+  /// microtask, so notification may lag the request by up to one frame.
+  void autofocus(FocusNode node) {
+    assert(_focusDebug('Node autofocusing: $node'));
+    if (focusedChild == null) {
+      if (node._parent == null) {
+        _reparent(node);
+      }
+      assert(node.ancestors.contains(this), 'Autofocus was requested for a node that is not a descendant of the scope from which it was requested.');
+      node._doRequestFocus(findFirstFocus: true);
+    }
+  }
+
+  @override
+  void _doRequestFocus({required bool findFirstFocus}) {
+    assert(findFirstFocus != null);
+
+    // It is possible that a previously focused child is no longer focusable.
+    while (focusedChild != null && !focusedChild!.canRequestFocus)
+      _focusedChildren.removeLast();
+
+    // If findFirstFocus is false, then the request is to make this scope the
+    // focus instead of looking for the ultimate first focus for this scope and
+    // its descendants.
+    if (!findFirstFocus) {
+      if (canRequestFocus) {
+        _setAsFocusedChildForScope();
+        _markNextFocus(this);
+      }
+      return;
+    }
+
+    // Start with the primary focus as the focused child of this scope, if there
+    // is one. Otherwise start with this node itself.
+    FocusNode primaryFocus = focusedChild ?? this;
+    // Keep going down through scopes until the ultimately focusable item is
+    // found, a scope doesn't have a focusedChild, or a non-scope is
+    // encountered.
+    while (primaryFocus is FocusScopeNode && primaryFocus.focusedChild != null) {
+      primaryFocus = primaryFocus.focusedChild!;
+    }
+    if (identical(primaryFocus, this)) {
+      // We didn't find a FocusNode at the leaf, so we're focusing the scope, if
+      // allowed.
+      if (primaryFocus.canRequestFocus) {
+        _setAsFocusedChildForScope();
+        _markNextFocus(this);
+      }
+    } else {
+      // We found a FocusScopeNode at the leaf, so ask it to focus itself
+      // instead of this scope. That will cause this scope to return true from
+      // hasFocus, but false from hasPrimaryFocus.
+      primaryFocus._doRequestFocus(findFirstFocus: findFirstFocus);
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    if (_focusedChildren.isEmpty) {
+      return;
+    }
+    final List<String> childList = _focusedChildren.reversed.map<String>((FocusNode child) {
+      return child.toStringShort();
+    }).toList();
+    properties.add(IterableProperty<String>('focusedChildren', childList, defaultValue: <String>[]));
+  }
+}
+
+/// An enum to describe which kind of focus highlight behavior to use when
+/// displaying focus information.
+enum FocusHighlightMode {
+  /// Touch interfaces will not show the focus highlight except for controls
+  /// which bring up the soft keyboard.
+  ///
+  /// If a device that uses a traditional mouse and keyboard has a touch screen
+  /// attached, it can also enter `touch` mode if the user is using the touch
+  /// screen.
+  touch,
+
+  /// Traditional interfaces (keyboard and mouse) will show the currently
+  /// focused control via a focus highlight of some sort.
+  ///
+  /// If a touch device (like a mobile phone) has a keyboard and/or mouse
+  /// attached, it also can enter `traditional` mode if the user is using these
+  /// input devices.
+  traditional,
+}
+
+/// An enum to describe how the current value of [FocusManager.highlightMode] is
+/// determined. The strategy is set on [FocusManager.highlightStrategy].
+enum FocusHighlightStrategy {
+  /// Automatic switches between the various highlight modes based on the last
+  /// kind of input that was received. This is the default.
+  automatic,
+
+  /// [FocusManager.highlightMode] always returns [FocusHighlightMode.touch].
+  alwaysTouch,
+
+  /// [FocusManager.highlightMode] always returns [FocusHighlightMode.traditional].
+  alwaysTraditional,
+}
+
+/// Manages the focus tree.
+///
+/// The focus tree is a separate, sparser, tree from the widget tree that
+/// maintains the hierarchical relationship between focusable widgets in the
+/// widget tree.
+///
+/// The focus manager is responsible for tracking which [FocusNode] has the
+/// primary input focus (the [primaryFocus]), holding the [FocusScopeNode] that
+/// is the root of the focus tree (the [rootScope]), and what the current
+/// [highlightMode] is. It also distributes key events from [RawKeyboard] to the
+/// nodes in the focus tree.
+///
+/// The singleton [FocusManager] instance is held by the [WidgetsBinding] as
+/// [WidgetsBinding.focusManager], and can be conveniently accessed using the
+/// [FocusManager.instance] static accessor.
+///
+/// To find the [FocusNode] for a given [BuildContext], use [Focus.of]. To find
+/// the [FocusScopeNode] for a given [BuildContext], use [FocusScope.of].
+///
+/// If you would like notification whenever the [primaryFocus] changes, register
+/// a listener with [addListener]. When you no longer want to receive these
+/// events, as when your object is about to be disposed, you must unregister
+/// with [removeListener] to avoid memory leaks. Removing listeners is typically
+/// done in [State.dispose] on stateful widgets.
+///
+/// The [highlightMode] describes how focus highlights should be displayed on
+/// components in the UI. The [highlightMode] changes are notified separately
+/// via [addHighlightModeListener] and removed with
+/// [removeHighlightModeListener]. The highlight mode changes when the user
+/// switches from a mouse to a touch interface, or vice versa.
+///
+/// The widgets that are used to manage focus in the widget tree are:
+///
+///  * [Focus], a widget that manages a [FocusNode] in the focus tree so that
+///    the focus tree reflects changes in the widget hierarchy.
+///  * [FocusScope], a widget that manages a [FocusScopeNode] in the focus tree,
+///    creating a new scope for restricting focus to a set of focus nodes.
+///  * [FocusTraversalGroup], a widget that groups together nodes that should be
+///    traversed using an order described by a given [FocusTraversalPolicy].
+///
+/// See also:
+///
+///  * [FocusNode], which is a node in the focus tree that can receive focus.
+///  * [FocusScopeNode], which is a node in the focus tree used to collect
+///    subtrees into groups and restrict focus to them.
+///  * The [primaryFocus] global accessor, for convenient access from anywhere
+///    to the current focus manager state.
+class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
+  /// Creates an object that manages the focus tree.
+  ///
+  /// This constructor is rarely called directly. To access the [FocusManager],
+  /// consider using the [FocusManager.instance] accessor instead (which gets it
+  /// from the [WidgetsBinding] singleton).
+  FocusManager() {
+    rootScope._manager = this;
+    RawKeyboard.instance.keyEventHandler = _handleRawKeyEvent;
+    GestureBinding.instance!.pointerRouter.addGlobalRoute(_handlePointerEvent);
+  }
+
+  /// Provides convenient access to the current [FocusManager] singleton from
+  /// the [WidgetsBinding] instance.
+  static FocusManager get instance => WidgetsBinding.instance!.focusManager;
+
+  /// Sets the strategy by which [highlightMode] is determined.
+  ///
+  /// If set to [FocusHighlightStrategy.automatic], then the highlight mode will
+  /// change depending upon the interaction mode used last. For instance, if the
+  /// last interaction was a touch interaction, then [highlightMode] will return
+  /// [FocusHighlightMode.touch], and focus highlights will only appear on
+  /// widgets that bring up a soft keyboard. If the last interaction was a
+  /// non-touch interaction (hardware keyboard press, mouse click, etc.), then
+  /// [highlightMode] will return [FocusHighlightMode.traditional], and focus
+  /// highlights will appear on all widgets.
+  ///
+  /// If set to [FocusHighlightStrategy.alwaysTouch] or
+  /// [FocusHighlightStrategy.alwaysTraditional], then [highlightMode] will
+  /// always return [FocusHighlightMode.touch] or
+  /// [FocusHighlightMode.traditional], respectively, regardless of the last UI
+  /// interaction type.
+  ///
+  /// The initial value of [highlightMode] depends upon the value of
+  /// [defaultTargetPlatform] and [BaseMouseTracker.mouseIsConnected] of
+  /// [RendererBinding.mouseTracker], making a guess about which interaction is
+  /// most appropriate for the initial interaction mode.
+  ///
+  /// Defaults to [FocusHighlightStrategy.automatic].
+  FocusHighlightStrategy get highlightStrategy => _highlightStrategy;
+  FocusHighlightStrategy _highlightStrategy = FocusHighlightStrategy.automatic;
+  set highlightStrategy(FocusHighlightStrategy highlightStrategy) {
+    _highlightStrategy = highlightStrategy;
+    _updateHighlightMode();
+  }
+
+  static FocusHighlightMode get _defaultModeForPlatform {
+    // Assume that if we're on one of the mobile platforms, and there's no mouse
+    // connected, that the initial interaction will be touch-based, and that
+    // it's traditional mouse and keyboard on all other platforms.
+    //
+    // This only affects the initial value: the ongoing value is updated to a
+    // known correct value as soon as any pointer/keyboard events are received.
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.iOS:
+        if (WidgetsBinding.instance!.mouseTracker.mouseIsConnected) {
+          return FocusHighlightMode.traditional;
+        }
+        return FocusHighlightMode.touch;
+      case TargetPlatform.linux:
+      case TargetPlatform.macOS:
+      case TargetPlatform.windows:
+        return FocusHighlightMode.traditional;
+    }
+  }
+
+  /// Indicates the current interaction mode for focus highlights.
+  ///
+  /// The value returned depends upon the [highlightStrategy] used, and possibly
+  /// (depending on the value of [highlightStrategy]) the most recent
+  /// interaction mode that they user used.
+  ///
+  /// If [highlightMode] returns [FocusHighlightMode.touch], then widgets should
+  /// not draw their focus highlight unless they perform text entry.
+  ///
+  /// If [highlightMode] returns [FocusHighlightMode.traditional], then widgets should
+  /// draw their focus highlight whenever they are focused.
+  // Don't want to set _highlightMode here, since it's possible for the target
+  // platform to change (especially in tests).
+  FocusHighlightMode get highlightMode => _highlightMode ?? _defaultModeForPlatform;
+  FocusHighlightMode? _highlightMode;
+
+  // If set, indicates if the last interaction detected was touch or not.
+  // If null, no interactions have occurred yet.
+  bool? _lastInteractionWasTouch;
+
+  // Update function to be called whenever the state relating to highlightMode
+  // changes.
+  void _updateHighlightMode() {
+    final FocusHighlightMode newMode;
+    switch (highlightStrategy) {
+      case FocusHighlightStrategy.automatic:
+        if (_lastInteractionWasTouch == null) {
+          // If we don't have any information about the last interaction yet,
+          // then just rely on the default value for the platform, which will be
+          // determined based on the target platform if _highlightMode is not
+          // set.
+          return;
+        }
+        if (_lastInteractionWasTouch!) {
+          newMode = FocusHighlightMode.touch;
+        } else {
+          newMode = FocusHighlightMode.traditional;
+        }
+        break;
+      case FocusHighlightStrategy.alwaysTouch:
+        newMode = FocusHighlightMode.touch;
+        break;
+      case FocusHighlightStrategy.alwaysTraditional:
+        newMode = FocusHighlightMode.traditional;
+        break;
+    }
+    // We can't just compare newMode with _highlightMode here, since
+    // _highlightMode could be null, so we want to compare with the return value
+    // for the getter, since that's what clients will be looking at.
+    final FocusHighlightMode oldMode = highlightMode;
+    _highlightMode = newMode;
+    if (highlightMode != oldMode) {
+      _notifyHighlightModeListeners();
+    }
+  }
+
+  // The list of listeners for [highlightMode] state changes.
+  final HashedObserverList<ValueChanged<FocusHighlightMode>> _listeners = HashedObserverList<ValueChanged<FocusHighlightMode>>();
+
+  /// Register a closure to be called when the [FocusManager] notifies its listeners
+  /// that the value of [highlightMode] has changed.
+  void addHighlightModeListener(ValueChanged<FocusHighlightMode> listener) => _listeners.add(listener);
+
+  /// Remove a previously registered closure from the list of closures that the
+  /// [FocusManager] notifies.
+  void removeHighlightModeListener(ValueChanged<FocusHighlightMode> listener) => _listeners.remove(listener);
+
+  void _notifyHighlightModeListeners() {
+    if (_listeners.isEmpty) {
+      return;
+    }
+    final List<ValueChanged<FocusHighlightMode>> localListeners = List<ValueChanged<FocusHighlightMode>>.from(_listeners);
+    for (final ValueChanged<FocusHighlightMode> listener in localListeners) {
+      try {
+        if (_listeners.contains(listener)) {
+          listener(highlightMode);
+        }
+      } catch (exception, stack) {
+        InformationCollector? collector;
+        assert(() {
+          collector = () sync* {
+            yield DiagnosticsProperty<FocusManager>(
+              'The $runtimeType sending notification was',
+              this,
+              style: DiagnosticsTreeStyle.errorProperty,
+            );
+          };
+          return true;
+        }());
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          library: 'widgets library',
+          context: ErrorDescription('while dispatching notifications for $runtimeType'),
+          informationCollector: collector,
+        ));
+      }
+    }
+  }
+
+  /// The root [FocusScopeNode] in the focus tree.
+  ///
+  /// This field is rarely used directly. To find the nearest [FocusScopeNode]
+  /// for a given [FocusNode], call [FocusNode.nearestScope].
+  final FocusScopeNode rootScope = FocusScopeNode(debugLabel: 'Root Focus Scope');
+
+  void _handlePointerEvent(PointerEvent event) {
+    final FocusHighlightMode expectedMode;
+    switch (event.kind) {
+      case PointerDeviceKind.touch:
+      case PointerDeviceKind.stylus:
+      case PointerDeviceKind.invertedStylus:
+        _lastInteractionWasTouch = true;
+        expectedMode = FocusHighlightMode.touch;
+        break;
+      case PointerDeviceKind.mouse:
+      case PointerDeviceKind.unknown:
+        _lastInteractionWasTouch = false;
+        expectedMode = FocusHighlightMode.traditional;
+        break;
+    }
+    if (expectedMode != highlightMode) {
+      _updateHighlightMode();
+    }
+  }
+
+  bool _handleRawKeyEvent(RawKeyEvent event) {
+    // Update highlightMode first, since things responding to the keys might
+    // look at the highlight mode, and it should be accurate.
+    _lastInteractionWasTouch = false;
+    _updateHighlightMode();
+
+    assert(_focusDebug('Received key event ${event.logicalKey}'));
+    if (_primaryFocus == null) {
+      assert(_focusDebug('No primary focus for key event, ignored: $event'));
+      return false;
+    }
+
+    // Walk the current focus from the leaf to the root, calling each one's
+    // onKey on the way up, and if one responds that they handled it or want to
+    // stop propagation, stop.
+    bool handled = false;
+    for (final FocusNode node in <FocusNode>[_primaryFocus!, ..._primaryFocus!.ancestors]) {
+      if (node.onKey != null) {
+        // TODO(gspencergoog): Convert this from dynamic to KeyEventResult once migration is complete.
+        final dynamic result = node.onKey!(node, event);
+        assert(result is bool || result is KeyEventResult,
+            'Value returned from onKey handler must be a non-null bool or KeyEventResult, not ${result.runtimeType}');
+        if (result is KeyEventResult) {
+          switch (result) {
+            case KeyEventResult.handled:
+              assert(_focusDebug('Node $node handled key event $event.'));
+              handled = true;
+              break;
+            case KeyEventResult.skipRemainingHandlers:
+              assert(_focusDebug('Node $node stopped key event propagation: $event.'));
+              handled = false;
+              break;
+            case KeyEventResult.ignored:
+              continue;
+          }
+        } else if (result is bool){
+          if (result) {
+            assert(_focusDebug('Node $node handled key event $event.'));
+            handled = true;
+            break;
+          } else {
+            continue;
+          }
+        }
+        break;
+      }
+    }
+    if (!handled) {
+      assert(_focusDebug('Key event not handled by anyone: $event.'));
+    }
+    return handled;
+  }
+
+  /// The node that currently has the primary focus.
+  FocusNode? get primaryFocus => _primaryFocus;
+  FocusNode? _primaryFocus;
+
+  // The set of nodes that need to notify their listeners of changes at the next
+  // update.
+  final Set<FocusNode> _dirtyNodes = <FocusNode>{};
+
+  // The node that has requested to have the primary focus, but hasn't been
+  // given it yet.
+  FocusNode? _markedForFocus;
+
+  void _markDetached(FocusNode node) {
+    // The node has been removed from the tree, so it no longer needs to be
+    // notified of changes.
+    assert(_focusDebug('Node was detached: $node'));
+    if (_primaryFocus == node) {
+      _primaryFocus = null;
+    }
+    _dirtyNodes.remove(node);
+  }
+
+  void _markPropertiesChanged(FocusNode node) {
+    _markNeedsUpdate();
+    assert(_focusDebug('Properties changed for node $node.'));
+    _dirtyNodes.add(node);
+  }
+
+  void _markNextFocus(FocusNode node) {
+    if (_primaryFocus == node) {
+      // The caller asked for the current focus to be the next focus, so just
+      // pretend that didn't happen.
+      _markedForFocus = null;
+    } else {
+      _markedForFocus = node;
+      _markNeedsUpdate();
+    }
+  }
+
+  // True indicates that there is an update pending.
+  bool _haveScheduledUpdate = false;
+
+  // Request that an update be scheduled, optionally requesting focus for the
+  // given newFocus node.
+  void _markNeedsUpdate() {
+    assert(_focusDebug('Scheduling update, current focus is $_primaryFocus, next focus will be $_markedForFocus'));
+    if (_haveScheduledUpdate) {
+      return;
+    }
+    _haveScheduledUpdate = true;
+    scheduleMicrotask(_applyFocusChange);
+  }
+
+  void _applyFocusChange() {
+    _haveScheduledUpdate = false;
+    final FocusNode? previousFocus = _primaryFocus;
+    if (_primaryFocus == null && _markedForFocus == null) {
+      // If we don't have any current focus, and nobody has asked to focus yet,
+      // then revert to the root scope.
+      _markedForFocus = rootScope;
+    }
+    assert(_focusDebug('Refreshing focus state. Next focus will be $_markedForFocus'));
+    // A node has requested to be the next focus, and isn't already the primary
+    // focus.
+    if (_markedForFocus != null && _markedForFocus != _primaryFocus) {
+      final Set<FocusNode> previousPath = previousFocus?.ancestors.toSet() ?? <FocusNode>{};
+      final Set<FocusNode> nextPath = _markedForFocus!.ancestors.toSet();
+      // Notify nodes that are newly focused.
+      _dirtyNodes.addAll(nextPath.difference(previousPath));
+      // Notify nodes that are no longer focused
+      _dirtyNodes.addAll(previousPath.difference(nextPath));
+
+      _primaryFocus = _markedForFocus;
+      _markedForFocus = null;
+    }
+    if (previousFocus != _primaryFocus) {
+      assert(_focusDebug('Updating focus from $previousFocus to $_primaryFocus'));
+      if (previousFocus != null) {
+        _dirtyNodes.add(previousFocus);
+      }
+      if (_primaryFocus != null) {
+        _dirtyNodes.add(_primaryFocus!);
+      }
+    }
+    assert(_focusDebug('Notifying ${_dirtyNodes.length} dirty nodes:', _dirtyNodes.toList().map<String>((FocusNode node) => node.toString())));
+    for (final FocusNode node in _dirtyNodes) {
+      node._notify();
+    }
+    _dirtyNodes.clear();
+    if (previousFocus != _primaryFocus) {
+      notifyListeners();
+    }
+    assert(() {
+      if (_kDebugFocus) {
+        debugDumpFocusTree();
+      }
+      return true;
+    }());
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    return <DiagnosticsNode>[
+      rootScope.toDiagnosticsNode(name: 'rootScope'),
+    ];
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    properties.add(FlagProperty('haveScheduledUpdate', value: _haveScheduledUpdate, ifTrue: 'UPDATE SCHEDULED'));
+    properties.add(DiagnosticsProperty<FocusNode>('primaryFocus', primaryFocus, defaultValue: null));
+    properties.add(DiagnosticsProperty<FocusNode>('nextFocus', _markedForFocus, defaultValue: null));
+    final Element? element = primaryFocus?.context as Element?;
+    if (element != null) {
+      properties.add(DiagnosticsProperty<String>('primaryFocusCreator', element.debugGetCreatorChain(20)));
+    }
+  }
+}
+
+/// Provides convenient access to the current [FocusManager.primaryFocus] from the
+/// [WidgetsBinding] instance.
+FocusNode? get primaryFocus => WidgetsBinding.instance!.focusManager.primaryFocus;
+
+/// Returns a text representation of the current focus tree, along with the
+/// current attributes on each node.
+///
+/// Will return an empty string in release builds.
+String debugDescribeFocusTree() {
+  assert(WidgetsBinding.instance != null);
+  String? result;
+  assert(() {
+    result = FocusManager.instance.toStringDeep();
+    return true;
+  }());
+  return result ?? '';
+}
+
+/// Prints a text representation of the current focus tree, along with the
+/// current attributes on each node.
+///
+/// Will do nothing in release builds.
+void debugDumpFocusTree() {
+  assert(() {
+    debugPrint(debugDescribeFocusTree());
+    return true;
+  }());
+}
diff --git a/lib/src/widgets/focus_scope.dart b/lib/src/widgets/focus_scope.dart
new file mode 100644
index 0000000..afd9cea
--- /dev/null
+++ b/lib/src/widgets/focus_scope.dart
@@ -0,0 +1,1039 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'basic.dart';
+import 'focus_manager.dart';
+import 'framework.dart';
+import 'inherited_notifier.dart';
+
+/// A widget that manages a [FocusNode] to allow keyboard focus to be given
+/// to this widget and its descendants.
+///
+/// When the focus is gained or lost, [onFocusChange] is called.
+///
+/// For keyboard events, [onKey] is called if [FocusNode.hasFocus] is true for
+/// this widget's [focusNode], unless a focused descendant's [onKey] callback
+/// returns true when called.
+///
+/// This widget does not provide any visual indication that the focus has
+/// changed. Any desired visual changes should be made when [onFocusChange] is
+/// called.
+///
+/// To access the [FocusNode] of the nearest ancestor [Focus] widget and
+/// establish a relationship that will rebuild the widget when the focus
+/// changes, use the [Focus.of] and [FocusScope.of] static methods.
+///
+/// To access the focused state of the nearest [Focus] widget, use
+/// [FocusNode.hasFocus] from a build method, which also establishes a relationship
+/// between the calling widget and the [Focus] widget that will rebuild the
+/// calling widget when the focus changes.
+///
+/// Managing a [FocusNode] means managing its lifecycle, listening for changes
+/// in focus, and re-parenting it when needed to keep the focus hierarchy in
+/// sync with the widget hierarchy. This widget does all of those things for
+/// you. See [FocusNode] for more information about the details of what node
+/// management entails if you are not using a [Focus] widget and you need to do
+/// it yourself.
+///
+/// To collect a sub-tree of nodes into an exclusive group that restricts focus
+/// traversal to the group, use a [FocusScope]. To collect a sub-tree of nodes
+/// into a group that has a specific order to its traversal but allows the
+/// traversal to escape the group, use a [FocusTraversalGroup].
+///
+/// To move the focus, use methods on [FocusNode] by getting the [FocusNode]
+/// through the [of] method. For instance, to move the focus to the next node in
+/// the focus traversal order, call `Focus.of(context).nextFocus()`. To unfocus
+/// a widget, call `Focus.of(context).unfocus()`.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+/// This example shows how to manage focus using the [Focus] and [FocusScope]
+/// widgets. See [FocusNode] for a similar example that doesn't use [Focus] or
+/// [FocusScope].
+///
+/// ```dart imports
+/// import 'package:flute/services.dart';
+/// ```
+///
+/// ```dart
+/// Color _color = Colors.white;
+///
+/// KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
+///   if (event is RawKeyDownEvent) {
+///     print('Focus node ${node.debugLabel} got key event: ${event.logicalKey}');
+///     if (event.logicalKey == LogicalKeyboardKey.keyR) {
+///       print('Changing color to red.');
+///       setState(() {
+///         _color = Colors.red;
+///       });
+///       return KeyEventResult.handled;
+///     } else if (event.logicalKey == LogicalKeyboardKey.keyG) {
+///       print('Changing color to green.');
+///       setState(() {
+///         _color = Colors.green;
+///       });
+///       return KeyEventResult.handled;
+///     } else if (event.logicalKey == LogicalKeyboardKey.keyB) {
+///       print('Changing color to blue.');
+///       setState(() {
+///         _color = Colors.blue;
+///       });
+///       return KeyEventResult.handled;
+///     }
+///   }
+///   return KeyEventResult.ignored;
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   final TextTheme textTheme = Theme.of(context).textTheme;
+///   return FocusScope(
+///     debugLabel: 'Scope',
+///     autofocus: true,
+///     child: DefaultTextStyle(
+///       style: textTheme.headline4,
+///       child: Focus(
+///         onKey: _handleKeyPress,
+///         debugLabel: 'Button',
+///         child: Builder(
+///           builder: (BuildContext context) {
+///             final FocusNode focusNode = Focus.of(context);
+///             final bool hasFocus = focusNode.hasFocus;
+///             return GestureDetector(
+///               onTap: () {
+///                 if (hasFocus) {
+///                   focusNode.unfocus();
+///                 } else {
+///                   focusNode.requestFocus();
+///                 }
+///               },
+///               child: Center(
+///                 child: Container(
+///                   width: 400,
+///                   height: 100,
+///                   alignment: Alignment.center,
+///                   color: hasFocus ? _color : Colors.white,
+///                   child: Text(hasFocus ? "I'm in color! Press R,G,B!" : 'Press to focus'),
+///                 ),
+///               ),
+///             );
+///           },
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+/// This example shows how to wrap another widget in a [Focus] widget to make it
+/// focusable. It wraps a [Container], and changes the container's color when it
+/// is set as the [FocusManager.primaryFocus].
+///
+/// If you also want to handle mouse hover and/or keyboard actions on a widget,
+/// consider using a [FocusableActionDetector], which combines several different
+/// widgets to provide those capabilities.
+///
+/// ```dart preamble
+/// class FocusableText extends StatelessWidget {
+///   const FocusableText(this.data, {Key key, this.autofocus}) : super(key: key);
+///
+///   /// The string to display as the text for this widget.
+///   final String data;
+///
+///   /// Whether or not to focus this widget initially if nothing else is focused.
+///   final bool autofocus;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Focus(
+///       autofocus: autofocus,
+///       child: Builder(builder: (BuildContext context) {
+///         // The contents of this Builder are being made focusable. It is inside
+///         // of a Builder because the builder provides the correct context
+///         // variable for Focus.of() to be able to find the Focus widget that is
+///         // the Builder's parent. Without the builder, the context variable used
+///         // would be the one given the FocusableText build function, and that
+///         // would start looking for a Focus widget ancestor of the FocusableText
+///         // instead of finding the one inside of its build function.
+///         return Container(
+///           padding: EdgeInsets.all(8.0),
+///           // Change the color based on whether or not this Container has focus.
+///           color: Focus.of(context).hasPrimaryFocus ? Colors.black12 : null,
+///           child: Text(data),
+///         );
+///       }),
+///     );
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: ListView.builder(
+///       itemBuilder: (context, index) => FocusableText(
+///         'Item $index',
+///         autofocus: index == 0,
+///       ),
+///       itemCount: 50,
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+/// This example shows how to focus a newly-created widget immediately after it
+/// is created.
+///
+/// The focus node will not actually be given the focus until after the frame in
+/// which it has requested focus is drawn, so it is OK to call
+/// [FocusNode.requestFocus] on a node which is not yet in the focus tree.
+///
+/// ```dart
+/// int focusedChild = 0;
+/// List<Widget> children = <Widget>[];
+/// List<FocusNode> childFocusNodes = <FocusNode>[];
+///
+/// @override
+/// void initState() {
+///   super.initState();
+///   // Add the first child.
+///   _addChild();
+/// }
+///
+/// @override
+/// void dispose() {
+///   super.dispose();
+///   childFocusNodes.forEach((FocusNode node) => node.dispose());
+/// }
+///
+/// void _addChild() {
+///   // Calling requestFocus here creates a deferred request for focus, since the
+///   // node is not yet part of the focus tree.
+///   childFocusNodes
+///       .add(FocusNode(debugLabel: 'Child ${children.length}')..requestFocus());
+///
+///   children.add(Padding(
+///     padding: const EdgeInsets.all(2.0),
+///     child: ActionChip(
+///       focusNode: childFocusNodes.last,
+///       label: Text('CHILD ${children.length}'),
+///       onPressed: () {},
+///     ),
+///   ));
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: Center(
+///       child: Wrap(
+///         children: children,
+///       ),
+///     ),
+///     floatingActionButton: FloatingActionButton(
+///       onPressed: () {
+///         setState(() {
+///           focusedChild = children.length;
+///           _addChild();
+///         });
+///       },
+///       child: Icon(Icons.add),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [FocusNode], which represents a node in the focus hierarchy and
+///    [FocusNode]'s API documentation includes a detailed explanation of its role
+///    in the overall focus system.
+///  * [FocusScope], a widget that manages a group of focusable widgets using a
+///    [FocusScopeNode].
+///  * [FocusScopeNode], a node that collects focus nodes into a group for
+///    traversal.
+///  * [FocusManager], a singleton that manages the primary focus and
+///    distributes key events to focused nodes.
+///  * [FocusTraversalPolicy], an object used to determine how to move the focus
+///    to other nodes.
+///  * [FocusTraversalGroup], a widget that groups together and imposes a
+///    traversal policy on the [Focus] nodes below it in the widget hierarchy.
+class Focus extends StatefulWidget {
+  /// Creates a widget that manages a [FocusNode].
+  ///
+  /// The [child] argument is required and must not be null.
+  ///
+  /// The [autofocus] argument must not be null.
+  const Focus({
+    Key? key,
+    required this.child,
+    this.focusNode,
+    this.autofocus = false,
+    this.onFocusChange,
+    this.onKey,
+    this.debugLabel,
+    this.canRequestFocus,
+    this.descendantsAreFocusable = true,
+    this.skipTraversal,
+    this.includeSemantics = true,
+  })  : assert(child != null),
+        assert(autofocus != null),
+        assert(descendantsAreFocusable != null),
+        assert(includeSemantics != null),
+        super(key: key);
+
+  /// A debug label for this widget.
+  ///
+  /// Not used for anything except to be printed in the diagnostic output from
+  /// [toString] or [toStringDeep]. Also unused if a [focusNode] is provided,
+  /// since that node can have its own [FocusNode.debugLabel].
+  ///
+  /// To get a string with the entire tree, call [debugDescribeFocusTree]. To
+  /// print it to the console call [debugDumpFocusTree].
+  ///
+  /// Defaults to null.
+  final String? debugLabel;
+
+  /// The child widget of this [Focus].
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// Handler for keys pressed when this object or one of its children has
+  /// focus.
+  ///
+  /// Key events are first given to the [FocusNode] that has primary focus, and
+  /// if its [onKey] method return false, then they are given to each ancestor
+  /// node up the focus hierarchy in turn. If an event reaches the root of the
+  /// hierarchy, it is discarded.
+  ///
+  /// This is not the way to get text input in the manner of a text field: it
+  /// leaves out support for input method editors, and doesn't support soft
+  /// keyboards in general. For text input, consider [TextField],
+  /// [EditableText], or [CupertinoTextField] instead, which do support these
+  /// things.
+  final FocusOnKeyCallback? onKey;
+
+  /// Handler called when the focus changes.
+  ///
+  /// Called with true if this widget's node gains focus, and false if it loses
+  /// focus.
+  final ValueChanged<bool>? onFocusChange;
+
+  /// {@template flutter.widgets.Focus.autofocus}
+  /// True if this widget will be selected as the initial focus when no other
+  /// node in its scope is currently focused.
+  ///
+  /// Ideally, there is only one widget with autofocus set in each [FocusScope].
+  /// If there is more than one widget with autofocus set, then the first one
+  /// added to the tree will get focus.
+  ///
+  /// Must not be null. Defaults to false.
+  /// {@endtemplate}
+  final bool autofocus;
+
+  /// {@template flutter.widgets.Focus.focusNode}
+  /// An optional focus node to use as the focus node for this widget.
+  ///
+  /// If one is not supplied, then one will be automatically allocated, owned,
+  /// and managed by this widget. The widget will be focusable even if a
+  /// [focusNode] is not supplied. If supplied, the given `focusNode` will be
+  /// _hosted_ by this widget, but not owned. See [FocusNode] for more
+  /// information on what being hosted and/or owned implies.
+  ///
+  /// Supplying a focus node is sometimes useful if an ancestor to this widget
+  /// wants to control when this widget has the focus. The owner will be
+  /// responsible for calling [FocusNode.dispose] on the focus node when it is
+  /// done with it, but this widget will attach/detach and reparent the node
+  /// when needed.
+  /// {@endtemplate}
+  final FocusNode? focusNode;
+
+  /// Sets the [FocusNode.skipTraversal] flag on the focus node so that it won't
+  /// be visited by the [FocusTraversalPolicy].
+  ///
+  /// This is sometimes useful if a [Focus] widget should receive key events as
+  /// part of the focus chain, but shouldn't be accessible via focus traversal.
+  ///
+  /// This is different from [FocusNode.canRequestFocus] because it only implies
+  /// that the widget can't be reached via traversal, not that it can't be
+  /// focused. It may still be focused explicitly.
+  final bool? skipTraversal;
+
+  /// {@template flutter.widgets.Focus.includeSemantics}
+  /// Include semantics information in this widget.
+  ///
+  /// If true, this widget will include a [Semantics] node that indicates the
+  /// [SemanticsProperties.focusable] and [SemanticsProperties.focused]
+  /// properties.
+  ///
+  /// It is not typical to set this to false, as that can affect the semantics
+  /// information available to accessibility systems.
+  ///
+  /// Must not be null, defaults to true.
+  /// {@endtemplate}
+  final bool includeSemantics;
+
+  /// {@template flutter.widgets.Focus.canRequestFocus}
+  /// If true, this widget may request the primary focus.
+  ///
+  /// Defaults to true.  Set to false if you want the [FocusNode] this widget
+  /// manages to do nothing when [FocusNode.requestFocus] is called on it. Does
+  /// not affect the children of this node, and [FocusNode.hasFocus] can still
+  /// return true if this node is the ancestor of the primary focus.
+  ///
+  /// This is different than [Focus.skipTraversal] because [Focus.skipTraversal]
+  /// still allows the widget to be focused, just not traversed to.
+  ///
+  /// Setting [FocusNode.canRequestFocus] to false implies that the widget will
+  /// also be skipped for traversal purposes.
+  ///
+  /// See also:
+  ///
+  /// * [FocusTraversalGroup], a widget that sets the traversal policy for its
+  ///   descendants.
+  /// * [FocusTraversalPolicy], a class that can be extended to describe a
+  ///   traversal policy.
+  /// {@endtemplate}
+  final bool? canRequestFocus;
+
+  /// {@template flutter.widgets.Focus.descendantsAreFocusable}
+  /// If false, will make this widget's descendants unfocusable.
+  ///
+  /// Defaults to true. Does not affect focusability of this node (just its
+  /// descendants): for that, use [FocusNode.canRequestFocus].
+  ///
+  /// If any descendants are focused when this is set to false, they will be
+  /// unfocused. When `descendantsAreFocusable` is set to true again, they will
+  /// not be refocused, although they will be able to accept focus again.
+  ///
+  /// Does not affect the value of [FocusNode.canRequestFocus] on the
+  /// descendants.
+  ///
+  /// See also:
+  ///
+  /// * [ExcludeFocus], a widget that uses this property to conditionally
+  ///   exclude focus for a subtree.
+  /// * [FocusTraversalGroup], a widget used to group together and configure the
+  ///   focus traversal policy for a widget subtree that has a
+  ///   `descendantsAreFocusable` parameter to conditionally block focus for a
+  ///   subtree.
+  /// {@endtemplate}
+  final bool descendantsAreFocusable;
+
+  /// Returns the [focusNode] of the [Focus] that most tightly encloses the
+  /// given [BuildContext].
+  ///
+  /// If no [Focus] node is found before reaching the nearest [FocusScope]
+  /// widget, or there is no [Focus] widget in scope, then this method will
+  /// throw an exception.
+  ///
+  /// The `context` and `scopeOk` arguments must not be null.
+  ///
+  /// Calling this function creates a dependency that will rebuild the given
+  /// context when the focus changes.
+  ///
+  /// See also:
+  ///
+  ///  * [maybeOf], which is similar to this function, but will return null
+  ///    instead of throwing if it doesn't find a [Focus] node.
+  static FocusNode of(BuildContext context, { bool scopeOk = false }) {
+    assert(context != null);
+    assert(scopeOk != null);
+    final _FocusMarker? marker = context.dependOnInheritedWidgetOfExactType<_FocusMarker>();
+    final FocusNode? node = marker?.notifier;
+    assert(() {
+      if (node == null) {
+        throw FlutterError(
+          'Focus.of() was called with a context that does not contain a Focus widget.\n'
+          'No Focus widget ancestor could be found starting from the context that was passed to '
+          'Focus.of(). This can happen because you are using a widget that looks for a Focus '
+          'ancestor, and do not have a Focus widget descendant in the nearest FocusScope.\n'
+          'The context used was:\n'
+          '  $context'
+        );
+      }
+      return true;
+    }());
+    assert(() {
+      if (!scopeOk && node is FocusScopeNode) {
+        throw FlutterError(
+          'Focus.of() was called with a context that does not contain a Focus between the given '
+          'context and the nearest FocusScope widget.\n'
+          'No Focus ancestor could be found starting from the context that was passed to '
+          'Focus.of() to the point where it found the nearest FocusScope widget. This can happen '
+          'because you are using a widget that looks for a Focus ancestor, and do not have a '
+          'Focus widget ancestor in the current FocusScope.\n'
+          'The context used was:\n'
+          '  $context'
+        );
+      }
+      return true;
+    }());
+    return node!;
+  }
+
+  /// Returns the [focusNode] of the [Focus] that most tightly encloses the
+  /// given [BuildContext].
+  ///
+  /// If no [Focus] node is found before reaching the nearest [FocusScope]
+  /// widget, or there is no [Focus] widget in scope, then this method will
+  /// return null.
+  ///
+  /// The `context` and `scopeOk` arguments must not be null.
+  ///
+  /// Calling this function creates a dependency that will rebuild the given
+  /// context when the focus changes.
+  ///
+  /// See also:
+  ///
+  ///  * [of], which is similar to this function, but will throw an exception if
+  ///    it doesn't find a [Focus] node instead of returning null.
+  static FocusNode? maybeOf(BuildContext context, { bool scopeOk = false }) {
+    assert(context != null);
+    assert(scopeOk != null);
+    final _FocusMarker? marker = context.dependOnInheritedWidgetOfExactType<_FocusMarker>();
+    final FocusNode? node = marker?.notifier;
+    if (node == null) {
+      return null;
+    }
+    if (!scopeOk && node is FocusScopeNode) {
+      return null;
+    }
+    return node;
+  }
+
+  /// Returns true if the nearest enclosing [Focus] widget's node is focused.
+  ///
+  /// A convenience method to allow build methods to write:
+  /// `Focus.isAt(context)` to get whether or not the nearest [Focus] above them
+  /// in the widget hierarchy currently has the input focus.
+  ///
+  /// Returns false if no [Focus] widget is found before reaching the nearest
+  /// [FocusScope], or if the root of the focus tree is reached without finding
+  /// a [Focus] widget.
+  ///
+  /// Calling this function creates a dependency that will rebuild the given
+  /// context when the focus changes.
+  static bool isAt(BuildContext context) => Focus.maybeOf(context)?.hasFocus ?? false;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(StringProperty('debugLabel', debugLabel, defaultValue: null));
+    properties.add(FlagProperty('autofocus', value: autofocus, ifTrue: 'AUTOFOCUS', defaultValue: false));
+    properties.add(FlagProperty('canRequestFocus', value: canRequestFocus, ifFalse: 'NOT FOCUSABLE', defaultValue: false));
+    properties.add(FlagProperty('descendantsAreFocusable', value: descendantsAreFocusable, ifFalse: 'DESCENDANTS UNFOCUSABLE', defaultValue: true));
+    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
+  }
+
+  @override
+  _FocusState createState() => _FocusState();
+}
+
+class _FocusState extends State<Focus> {
+  FocusNode? _internalNode;
+  FocusNode get focusNode => widget.focusNode ?? _internalNode!;
+  bool? _hasPrimaryFocus;
+  bool? _canRequestFocus;
+  bool? _descendantsAreFocusable;
+  bool _didAutofocus = false;
+  FocusAttachment? _focusAttachment;
+
+  @override
+  void initState() {
+    super.initState();
+    _initNode();
+  }
+
+  void _initNode() {
+    if (widget.focusNode == null) {
+      // Only create a new node if the widget doesn't have one.
+      // This calls a function instead of just allocating in place because
+      // _createNode is overridden in _FocusScopeState.
+      _internalNode ??= _createNode();
+    }
+    focusNode.descendantsAreFocusable = widget.descendantsAreFocusable;
+    if (widget.skipTraversal != null) {
+      focusNode.skipTraversal = widget.skipTraversal!;
+    }
+    if (widget.canRequestFocus != null) {
+      focusNode.canRequestFocus = widget.canRequestFocus!;
+    }
+    _canRequestFocus = focusNode.canRequestFocus;
+    _descendantsAreFocusable = focusNode.descendantsAreFocusable;
+    _hasPrimaryFocus = focusNode.hasPrimaryFocus;
+    _focusAttachment = focusNode.attach(context, onKey: widget.onKey);
+
+    // Add listener even if the _internalNode existed before, since it should
+    // not be listening now if we're re-using a previous one because it should
+    // have already removed its listener.
+    focusNode.addListener(_handleFocusChanged);
+  }
+
+  FocusNode _createNode() {
+    return FocusNode(
+      debugLabel: widget.debugLabel,
+      canRequestFocus: widget.canRequestFocus ?? true,
+      descendantsAreFocusable: widget.descendantsAreFocusable,
+      skipTraversal: widget.skipTraversal ?? false,
+    );
+  }
+
+  @override
+  void dispose() {
+    // Regardless of the node owner, we need to remove it from the tree and stop
+    // listening to it.
+    focusNode.removeListener(_handleFocusChanged);
+    _focusAttachment!.detach();
+
+    // Don't manage the lifetime of external nodes given to the widget, just the
+    // internal node.
+    _internalNode?.dispose();
+    super.dispose();
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _focusAttachment?.reparent();
+    _handleAutofocus();
+  }
+
+  void _handleAutofocus() {
+    if (!_didAutofocus && widget.autofocus) {
+      FocusScope.of(context).autofocus(focusNode);
+      _didAutofocus = true;
+    }
+  }
+
+  @override
+  void deactivate() {
+    super.deactivate();
+    // The focus node's location in the tree is no longer valid here. But
+    // we can't unfocus or remove the node from the tree because if the widget
+    // is moved to a different part of the tree (via global key) it should
+    // retain its focus state. That's why we temporarily park it on the root
+    // focus node (via reparent) until it either gets moved to a different part
+    // of the tree (via didChangeDependencies) or until it is disposed.
+    _focusAttachment?.reparent();
+    _didAutofocus = false;
+  }
+
+  @override
+  void didUpdateWidget(Focus oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    assert(() {
+      // Only update the debug label in debug builds, and only if we own the
+      // node.
+      if (oldWidget.debugLabel != widget.debugLabel && _internalNode != null) {
+        _internalNode!.debugLabel = widget.debugLabel;
+      }
+      return true;
+    }());
+
+    if (oldWidget.focusNode == widget.focusNode) {
+      if (widget.skipTraversal != null) {
+        focusNode.skipTraversal = widget.skipTraversal!;
+      }
+      if (widget.canRequestFocus != null) {
+        focusNode.canRequestFocus = widget.canRequestFocus!;
+      }
+      focusNode.descendantsAreFocusable = widget.descendantsAreFocusable;
+    } else {
+      _focusAttachment!.detach();
+      focusNode.removeListener(_handleFocusChanged);
+      _initNode();
+    }
+
+    if (oldWidget.autofocus != widget.autofocus) {
+      _handleAutofocus();
+    }
+  }
+
+  void _handleFocusChanged() {
+    final bool hasPrimaryFocus = focusNode.hasPrimaryFocus;
+    final bool canRequestFocus = focusNode.canRequestFocus;
+    final bool descendantsAreFocusable = focusNode.descendantsAreFocusable;
+    if (widget.onFocusChange != null) {
+      widget.onFocusChange!(focusNode.hasFocus);
+    }
+    if (_hasPrimaryFocus != hasPrimaryFocus) {
+      setState(() {
+        _hasPrimaryFocus = hasPrimaryFocus;
+      });
+    }
+    if (_canRequestFocus != canRequestFocus) {
+      setState(() {
+        _canRequestFocus = canRequestFocus;
+      });
+    }
+    if (_descendantsAreFocusable != descendantsAreFocusable) {
+      setState(() {
+        _descendantsAreFocusable = descendantsAreFocusable;
+      });
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    _focusAttachment!.reparent();
+    Widget child = widget.child;
+    if (widget.includeSemantics) {
+      child = Semantics(
+        focusable: _canRequestFocus,
+        focused: _hasPrimaryFocus,
+        child: widget.child,
+      );
+    }
+    return _FocusMarker(
+      node: focusNode,
+      child: child,
+    );
+  }
+}
+
+/// A [FocusScope] is similar to a [Focus], but also serves as a scope for its
+/// descendants, restricting focus traversal to the scoped controls.
+///
+/// For example a new [FocusScope] is created automatically when a route is
+/// pushed, keeping the focus traversal from moving to a control in a previous
+/// route.
+///
+/// If you just want to group widgets together in a group so that they are
+/// traversed in a particular order, but the focus can still leave the group,
+/// use a [FocusTraversalGroup].
+///
+/// Like [Focus], [FocusScope] provides an [onFocusChange] as a way to be
+/// notified when the focus is given to or removed from this widget.
+///
+/// The [onKey] argument allows specification of a key event handler that is
+/// invoked when this node or one of its children has focus. Keys are handed to
+/// the primary focused widget first, and then they propagate through the
+/// ancestors of that node, stopping if one of them returns true from [onKey],
+/// indicating that it has handled the event.
+///
+/// Managing a [FocusScopeNode] means managing its lifecycle, listening for
+/// changes in focus, and re-parenting it when needed to keep the focus
+/// hierarchy in sync with the widget hierarchy. This widget does all of those
+/// things for you. See [FocusScopeNode] for more information about the details
+/// of what node management entails if you are not using a [FocusScope] widget
+/// and you need to do it yourself.
+///
+/// [FocusScopeNode]s remember the last [FocusNode] that was focused within
+/// their descendants, and can move that focus to the next/previous node, or a
+/// node in a particular direction when the [FocusNode.nextFocus],
+/// [FocusNode.previousFocus], or [FocusNode.focusInDirection] are called on a
+/// [FocusNode] or [FocusScopeNode].
+///
+/// To move the focus, use methods on [FocusNode] by getting the [FocusNode]
+/// through the [of] method. For instance, to move the focus to the next node in
+/// the focus traversal order, call `Focus.of(context).nextFocus()`. To unfocus
+/// a widget, call `Focus.of(context).unfocus()`.
+///
+/// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+/// This example demonstrates using a [FocusScope] to restrict focus to a particular
+/// portion of the app. In this case, restricting focus to the visible part of a
+/// Stack.
+///
+/// ```dart preamble
+/// /// A demonstration pane.
+/// ///
+/// /// This is just a separate widget to simplify the example.
+/// class Pane extends StatelessWidget {
+///   const Pane({
+///     Key key,
+///     this.focusNode,
+///     this.onPressed,
+///     this.child,
+///     this.backgroundColor,
+///     this.icon,
+///   }) : super(key: key);
+///
+///   final FocusNode focusNode;
+///   final VoidCallback onPressed;
+///   final Widget child;
+///   final Color backgroundColor;
+///   final Widget icon;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Material(
+///       color: backgroundColor,
+///       child: Stack(
+///         fit: StackFit.expand,
+///         children: <Widget>[
+///           Center(
+///             child: child,
+///           ),
+///           Align(
+///             alignment: Alignment.topLeft,
+///             child: IconButton(
+///               autofocus: true,
+///               focusNode: focusNode,
+///               onPressed: onPressed,
+///               icon: icon,
+///             ),
+///           ),
+///         ],
+///       ),
+///     );
+///   }
+/// }
+/// ```
+///
+/// ```dart
+///   bool backdropIsVisible = false;
+///   FocusNode backdropNode = FocusNode(debugLabel: 'Close Backdrop Button');
+///   FocusNode foregroundNode = FocusNode(debugLabel: 'Option Button');
+///
+///   @override
+///   void dispose() {
+///     super.dispose();
+///     backdropNode.dispose();
+///     foregroundNode.dispose();
+///   }
+///
+///   Widget _buildStack(BuildContext context, BoxConstraints constraints) {
+///     Size stackSize = constraints.biggest;
+///     return Stack(
+///       fit: StackFit.expand,
+///       // The backdrop is behind the front widget in the Stack, but the widgets
+///       // would still be active and traversable without the FocusScope.
+///       children: <Widget>[
+///         // TRY THIS: Try removing this FocusScope entirely to see how it affects
+///         // the behavior. Without this FocusScope, the "ANOTHER BUTTON TO FOCUS"
+///         // button, and the IconButton in the backdrop Pane would be focusable
+///         // even when the backdrop wasn't visible.
+///         FocusScope(
+///           // TRY THIS: Try commenting out this line. Notice that the focus
+///           // starts on the backdrop and is stuck there? It seems like the app is
+///           // non-responsive, but it actually isn't. This line makes sure that
+///           // this focus scope and its children can't be focused when they're not
+///           // visible. It might help to make the background color of the
+///           // foreground pane semi-transparent to see it clearly.
+///           canRequestFocus: backdropIsVisible,
+///           child: Pane(
+///             icon: Icon(Icons.close),
+///             focusNode: backdropNode,
+///             backgroundColor: Colors.lightBlue,
+///             onPressed: () => setState(() => backdropIsVisible = false),
+///             child: Column(
+///               mainAxisAlignment: MainAxisAlignment.center,
+///               children: <Widget>[
+///                 // This button would be not visible, but still focusable from
+///                 // the foreground pane without the FocusScope.
+///                 ElevatedButton(
+///                   onPressed: () => print('You pressed the other button!'),
+///                   child: Text('ANOTHER BUTTON TO FOCUS'),
+///                 ),
+///                 DefaultTextStyle(
+///                     style: Theme.of(context).textTheme.headline2,
+///                     child: Text('BACKDROP')),
+///               ],
+///             ),
+///           ),
+///         ),
+///         AnimatedPositioned(
+///           curve: Curves.easeInOut,
+///           duration: const Duration(milliseconds: 300),
+///           top: backdropIsVisible ? stackSize.height * 0.9 : 0.0,
+///           width: stackSize.width,
+///           height: stackSize.height,
+///           onEnd: () {
+///             if (backdropIsVisible) {
+///               backdropNode.requestFocus();
+///             } else {
+///               foregroundNode.requestFocus();
+///             }
+///           },
+///           child: Pane(
+///             icon: Icon(Icons.menu),
+///             focusNode: foregroundNode,
+///             // TRY THIS: Try changing this to Colors.green.withOpacity(0.8) to see for
+///             // yourself that the hidden components do/don't get focus.
+///             backgroundColor: Colors.green,
+///             onPressed: backdropIsVisible
+///                 ? null
+///                 : () => setState(() => backdropIsVisible = true),
+///             child: DefaultTextStyle(
+///                 style: Theme.of(context).textTheme.headline2,
+///                 child: Text('FOREGROUND')),
+///           ),
+///         ),
+///       ],
+///     );
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     // Use a LayoutBuilder so that we can base the size of the stack on the size
+///     // of its parent.
+///     return LayoutBuilder(builder: _buildStack);
+///   }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [FocusScopeNode], which represents a scope node in the focus hierarchy.
+///  * [FocusNode], which represents a node in the focus hierarchy and has an
+///    explanation of the focus system.
+///  * [Focus], a widget that manages a [FocusNode] and allows easy access to
+///    managing focus without having to manage the node.
+///  * [FocusManager], a singleton that manages the focus and distributes key
+///    events to focused nodes.
+///  * [FocusTraversalPolicy], an object used to determine how to move the focus
+///    to other nodes.
+///  * [FocusTraversalGroup], a widget used to configure the focus traversal
+///    policy for a widget subtree.
+class FocusScope extends Focus {
+  /// Creates a widget that manages a [FocusScopeNode].
+  ///
+  /// The [child] argument is required and must not be null.
+  ///
+  /// The [autofocus] argument must not be null.
+  const FocusScope({
+    Key? key,
+    FocusScopeNode? node,
+    required Widget child,
+    bool autofocus = false,
+    ValueChanged<bool>? onFocusChange,
+    bool? canRequestFocus,
+    bool? skipTraversal,
+    FocusOnKeyCallback? onKey,
+    String? debugLabel,
+  })  : assert(child != null),
+        assert(autofocus != null),
+        super(
+          key: key,
+          child: child,
+          focusNode: node,
+          autofocus: autofocus,
+          onFocusChange: onFocusChange,
+          canRequestFocus: canRequestFocus,
+          skipTraversal: skipTraversal,
+          onKey: onKey,
+          debugLabel: debugLabel,
+        );
+
+  /// Returns the [FocusScopeNode] of the [FocusScope] that most tightly
+  /// encloses the given [context].
+  ///
+  /// If this node doesn't have a [Focus] widget ancestor, then the
+  /// [FocusManager.rootScope] is returned.
+  ///
+  /// The [context] argument must not be null.
+  static FocusScopeNode of(BuildContext context) {
+    assert(context != null);
+    final _FocusMarker? marker = context.dependOnInheritedWidgetOfExactType<_FocusMarker>();
+    return marker?.notifier?.nearestScope ?? context.owner!.focusManager.rootScope;
+  }
+
+  @override
+  _FocusScopeState createState() => _FocusScopeState();
+}
+
+class _FocusScopeState extends _FocusState {
+  @override
+  FocusScopeNode _createNode() {
+    return FocusScopeNode(
+      debugLabel: widget.debugLabel,
+      canRequestFocus: widget.canRequestFocus ?? true,
+      skipTraversal: widget.skipTraversal ?? false,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    _focusAttachment!.reparent();
+    return Semantics(
+      explicitChildNodes: true,
+      child: _FocusMarker(
+        node: focusNode,
+        child: widget.child,
+      ),
+    );
+  }
+}
+
+// The InheritedWidget marker for Focus and FocusScope.
+class _FocusMarker extends InheritedNotifier<FocusNode> {
+  const _FocusMarker({
+    Key? key,
+    required FocusNode node,
+    required Widget child,
+  })  : assert(node != null),
+        assert(child != null),
+        super(key: key, notifier: node, child: child);
+}
+
+/// A widget that controls whether or not the descendants of this widget are
+/// focusable.
+///
+/// Does not affect the value of [Focus.canRequestFocus] on the descendants.
+///
+/// See also:
+///
+///  * [Focus], a widget for adding and managing a [FocusNode] in the widget tree.
+///  * [FocusTraversalGroup], a widget that groups widgets for focus traversal,
+///    and can also be used in the same way as this widget by setting its
+///    `descendantsAreFocusable` attribute.
+class ExcludeFocus extends StatelessWidget {
+  /// Const constructor for [ExcludeFocus] widget.
+  ///
+  /// The [excluding] argument must not be null.
+  ///
+  /// The [child] argument is required, and must not be null.
+  const ExcludeFocus({
+    Key? key,
+    this.excluding = true,
+    required this.child,
+  })  : assert(excluding != null),
+        assert(child != null),
+        super(key: key);
+
+  /// If true, will make this widget's descendants unfocusable.
+  ///
+  /// Defaults to true.
+  ///
+  /// If any descendants are focused when this is set to true, they will be
+  /// unfocused. When `excluding` is set to false again, they will not be
+  /// refocused, although they will be able to accept focus again.
+  ///
+  /// Does not affect the value of [FocusNode.canRequestFocus] on the
+  /// descendants.
+  ///
+  /// See also:
+  ///
+  /// * [Focus.descendantsAreFocusable], the attribute of a [Focus] widget that
+  ///   controls this same property for focus widgets.
+  /// * [FocusTraversalGroup], a widget used to group together and configure the
+  ///   focus traversal policy for a widget subtree that has a
+  ///   `descendantsAreFocusable` parameter to conditionally block focus for a
+  ///   subtree.
+  final bool excluding;
+
+  /// The child widget of this [ExcludeFocus].
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    return Focus(
+      canRequestFocus: false,
+      skipTraversal: true,
+      includeSemantics: false,
+      descendantsAreFocusable: !excluding,
+      child: child,
+    );
+  }
+}
diff --git a/lib/src/widgets/focus_traversal.dart b/lib/src/widgets/focus_traversal.dart
new file mode 100644
index 0000000..a7692ad
--- /dev/null
+++ b/lib/src/widgets/focus_traversal.dart
@@ -0,0 +1,1936 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart';
+
+import 'actions.dart';
+import 'basic.dart';
+import 'editable_text.dart';
+import 'focus_manager.dart';
+import 'focus_scope.dart';
+import 'framework.dart';
+import 'scroll_position.dart';
+import 'scrollable.dart';
+
+// BuildContext/Element doesn't have a parent accessor, but it can be simulated
+// with visitAncestorElements. _getAncestor is needed because
+// context.getElementForInheritedWidgetOfExactType will return itself if it
+// happens to be of the correct type. _getAncestor should be O(count), since we
+// always return false at a specific ancestor. By default it returns the parent,
+// which is O(1).
+BuildContext? _getAncestor(BuildContext context, {int count = 1}) {
+  BuildContext? target;
+  context.visitAncestorElements((Element ancestor) {
+    count--;
+    if (count == 0) {
+      target = ancestor;
+      return false;
+    }
+    return true;
+  });
+  return target;
+}
+
+void _focusAndEnsureVisible(
+  FocusNode node, {
+  ScrollPositionAlignmentPolicy alignmentPolicy = ScrollPositionAlignmentPolicy.explicit,
+}) {
+  node.requestFocus();
+  Scrollable.ensureVisible(node.context!, alignment: 1.0, alignmentPolicy: alignmentPolicy);
+}
+
+// A class to temporarily hold information about FocusTraversalGroups when
+// sorting their contents.
+class _FocusTraversalGroupInfo {
+  _FocusTraversalGroupInfo(
+    _FocusTraversalGroupMarker? marker, {
+    FocusTraversalPolicy? defaultPolicy,
+    List<FocusNode>? members,
+  })  : groupNode = marker?.focusNode,
+        policy = marker?.policy ?? defaultPolicy ?? ReadingOrderTraversalPolicy(),
+        members = members ?? <FocusNode>[];
+
+  final FocusNode? groupNode;
+  final FocusTraversalPolicy policy;
+  final List<FocusNode> members;
+}
+
+/// A direction along either the horizontal or vertical axes.
+///
+/// This is used by the [DirectionalFocusTraversalPolicyMixin], and
+/// [FocusNode.focusInDirection] to indicate which direction to look in for the
+/// next focus.
+enum TraversalDirection {
+  /// Indicates a direction above the currently focused widget.
+  up,
+
+  /// Indicates a direction to the right of the currently focused widget.
+  ///
+  /// This direction is unaffected by the [Directionality] of the current
+  /// context.
+  right,
+
+  /// Indicates a direction below the currently focused widget.
+  down,
+
+  /// Indicates a direction to the left of the currently focused widget.
+  ///
+  /// This direction is unaffected by the [Directionality] of the current
+  /// context.
+  left,
+
+  // TODO(gspencer): Add diagonal traversal directions used by TV remotes and
+  // game controllers when we support them.
+}
+
+/// An object used to specify a focus traversal policy used for configuring a
+/// [FocusTraversalGroup] widget.
+///
+/// The focus traversal policy is what determines which widget is "next",
+/// "previous", or in a direction from the widget associated with the currently
+/// focused [FocusNode] (usually a [Focus] widget).
+///
+/// One of the pre-defined subclasses may be used, or define a custom policy to
+/// create a unique focus order.
+///
+/// When defining your own, your subclass should implement [sortDescendants] to
+/// provide the order in which you would like the descendants to be traversed.
+///
+/// See also:
+///
+///  * [FocusNode], for a description of the focus system.
+///  * [FocusTraversalGroup], a widget that groups together and imposes a
+///    traversal policy on the [Focus] nodes below it in the widget hierarchy.
+///  * [FocusNode], which is affected by the traversal policy.
+///  * [WidgetOrderTraversalPolicy], a policy that relies on the widget
+///    creation order to describe the order of traversal.
+///  * [ReadingOrderTraversalPolicy], a policy that describes the order as the
+///    natural "reading order" for the current [Directionality].
+///  * [OrderedTraversalPolicy], a policy that describes the order
+///    explicitly using [FocusTraversalOrder] widgets.
+///  * [DirectionalFocusTraversalPolicyMixin] a mixin class that implements
+///    focus traversal in a direction.
+@immutable
+abstract class FocusTraversalPolicy with Diagnosticable {
+  /// A const constructor so subclasses can be const.
+  const FocusTraversalPolicy();
+
+  /// Returns the node that should receive focus if focus is traversing
+  /// forwards, and there is no current focus.
+  ///
+  /// The node returned is the node that should receive focus if focus is
+  /// traversing forwards (i.e. with [next]), and there is no current focus in
+  /// the nearest [FocusScopeNode] that `currentNode` belongs to.
+  ///
+  /// The `currentNode` argument must not be null.
+  ///
+  /// The default implementation returns the [FocusScopeNode.focusedChild], if
+  /// set, on the nearest scope of the `currentNode`, otherwise, returns the
+  /// first node from [sortDescendants], or the given `currentNode` if there are
+  /// no descendants.
+  ///
+  /// See also:
+  ///
+  ///  * [next], the function that is called to move the focus to the next node.
+  ///  * [DirectionalFocusTraversalPolicyMixin.findFirstFocusInDirection], a
+  ///    function that finds the first focusable widget in a particular direction.
+  FocusNode? findFirstFocus(FocusNode currentNode) => _findInitialFocus(currentNode);
+
+  /// Returns the node that should receive focus if focus is traversing
+  /// backwards, and there is no current focus.
+  ///
+  /// The node returned is the one that should receive focus if focus is
+  /// traversing backwards (i.e. with [previous]), and there is no current focus
+  /// in the nearest [FocusScopeNode] that `currentNode` belongs to.
+  ///
+  /// The `currentNode` argument must not be null.
+  ///
+  /// The default implementation returns the [FocusScopeNode.focusedChild], if
+  /// set, on the nearest scope of the `currentNode`, otherwise, returns the
+  /// last node from [sortDescendants], or the given `currentNode` if there are
+  /// no descendants.
+  ///
+  /// See also:
+  ///
+  ///  * [previous], the function that is called to move the focus to the next node.
+  ///  * [DirectionalFocusTraversalPolicyMixin.findFirstFocusInDirection], a
+  ///    function that finds the first focusable widget in a particular direction.
+  FocusNode findLastFocus(FocusNode currentNode) => _findInitialFocus(currentNode, fromEnd: true);
+
+  FocusNode _findInitialFocus(FocusNode currentNode, {bool fromEnd = false}) {
+    assert(currentNode != null);
+    final FocusScopeNode scope = currentNode.nearestScope!;
+    FocusNode? candidate = scope.focusedChild;
+    if (candidate == null && scope.descendants.isNotEmpty) {
+      final Iterable<FocusNode> sorted = _sortAllDescendants(scope, currentNode);
+      if (sorted.isEmpty) {
+        candidate = null;
+      } else {
+        candidate = fromEnd ? sorted.last : sorted.first;
+      }
+    }
+
+    // If we still didn't find any candidate, use the current node as a
+    // fallback.
+    candidate ??= currentNode;
+    return candidate;
+  }
+
+  /// Returns the first node in the given `direction` that should receive focus
+  /// if there is no current focus in the scope to which the `currentNode`
+  /// belongs.
+  ///
+  /// This is typically used by [inDirection] to determine which node to focus
+  /// if it is called when no node is currently focused.
+  ///
+  /// All arguments must not be null.
+  FocusNode? findFirstFocusInDirection(FocusNode currentNode, TraversalDirection direction);
+
+  /// Clears the data associated with the given [FocusScopeNode] for this object.
+  ///
+  /// This is used to indicate that the focus policy has changed its mode, and
+  /// so any cached policy data should be invalidated. For example, changing the
+  /// direction in which focus is moving, or changing from directional to
+  /// next/previous navigation modes.
+  ///
+  /// The default implementation does nothing.
+  @mustCallSuper
+  @protected
+  void invalidateScopeData(FocusScopeNode node) {}
+
+  /// This is called whenever the given [node] is re-parented into a new scope,
+  /// so that the policy has a chance to update or invalidate any cached data
+  /// that it maintains per scope about the node.
+  ///
+  /// The [oldScope] is the previous scope that this node belonged to, if any.
+  ///
+  /// The default implementation does nothing.
+  @mustCallSuper
+  void changedScope({FocusNode? node, FocusScopeNode? oldScope}) {}
+
+  /// Focuses the next widget in the focus scope that contains the given
+  /// [currentNode].
+  ///
+  /// This should determine what the next node to receive focus should be by
+  /// inspecting the node tree, and then calling [FocusNode.requestFocus] on
+  /// the node that has been selected.
+  ///
+  /// Returns true if it successfully found a node and requested focus.
+  ///
+  /// The [currentNode] argument must not be null.
+  bool next(FocusNode currentNode) => _moveFocus(currentNode, forward: true);
+
+  /// Focuses the previous widget in the focus scope that contains the given
+  /// [currentNode].
+  ///
+  /// This should determine what the previous node to receive focus should be by
+  /// inspecting the node tree, and then calling [FocusNode.requestFocus] on
+  /// the node that has been selected.
+  ///
+  /// Returns true if it successfully found a node and requested focus.
+  ///
+  /// The [currentNode] argument must not be null.
+  bool previous(FocusNode currentNode) => _moveFocus(currentNode, forward: false);
+
+  /// Focuses the next widget in the given [direction] in the focus scope that
+  /// contains the given [currentNode].
+  ///
+  /// This should determine what the next node to receive focus in the given
+  /// [direction] should be by inspecting the node tree, and then calling
+  /// [FocusNode.requestFocus] on the node that has been selected.
+  ///
+  /// Returns true if it successfully found a node and requested focus.
+  ///
+  /// All arguments must not be null.
+  bool inDirection(FocusNode currentNode, TraversalDirection direction);
+
+  /// Sorts the given `descendants` into focus order.
+  ///
+  /// Subclasses should override this to implement a different sort for [next]
+  /// and [previous] to use in their ordering. If the returned iterable omits a
+  /// node that is a descendant of the given scope, then the user will be unable
+  /// to use next/previous keyboard traversal to reach that node.
+  ///
+  /// The node used to initiate the traversal (the one passed to [next] or
+  /// [previous]) is passed as `currentNode`.
+  ///
+  /// Having the current node in the list is what allows the algorithm to
+  /// determine which nodes are adjacent to the current node. If the
+  /// `currentNode` is removed from the list, then the focus will be unchanged
+  /// when [next] or [previous] are called, and they will return false.
+  ///
+  /// This is not used for directional focus ([inDirection]), only for
+  /// determining the focus order for [next] and [previous].
+  ///
+  /// When implementing an override for this function, be sure to use
+  /// [mergeSort] instead of Dart's default list sorting algorithm when sorting
+  /// items, since the default algorithm is not stable (items deemed to be equal
+  /// can appear in arbitrary order, and change positions between sorts), whereas
+  /// [mergeSort] is stable.
+  @protected
+  Iterable<FocusNode> sortDescendants(Iterable<FocusNode> descendants, FocusNode currentNode);
+
+  _FocusTraversalGroupMarker? _getMarker(BuildContext? context) {
+    return context?.getElementForInheritedWidgetOfExactType<_FocusTraversalGroupMarker>()?.widget as _FocusTraversalGroupMarker?;
+  }
+
+  // Sort all descendants, taking into account the FocusTraversalGroup
+  // that they are each in, and filtering out non-traversable/focusable nodes.
+  List<FocusNode> _sortAllDescendants(FocusScopeNode scope, FocusNode currentNode) {
+    assert(scope != null);
+    final _FocusTraversalGroupMarker? scopeGroupMarker = _getMarker(scope.context);
+    final FocusTraversalPolicy defaultPolicy = scopeGroupMarker?.policy ?? ReadingOrderTraversalPolicy();
+    // Build the sorting data structure, separating descendants into groups.
+    final Map<FocusNode?, _FocusTraversalGroupInfo> groups = <FocusNode?, _FocusTraversalGroupInfo>{};
+    for (final FocusNode node in scope.descendants) {
+      final _FocusTraversalGroupMarker? groupMarker = _getMarker(node.context);
+      final FocusNode? groupNode = groupMarker?.focusNode;
+      // Group nodes need to be added to their parent's node, or to the "null"
+      // node if no parent is found. This creates the hierarchy of group nodes
+      // and makes it so the entire group is sorted along with the other members
+      // of the parent group.
+      if (node == groupNode) {
+        // To find the parent of the group node, we need to skip over the parent
+        // of the Focus node in _FocusTraversalGroupState.build, and start
+        // looking with that node's parent, since _getMarker will return the
+        // context it was called on if it matches the type.
+        final BuildContext? parentContext = _getAncestor(groupNode!.context!, count: 2);
+        final _FocusTraversalGroupMarker? parentMarker = _getMarker(parentContext);
+        final FocusNode? parentNode = parentMarker?.focusNode;
+        groups[parentNode] ??= _FocusTraversalGroupInfo(parentMarker, members: <FocusNode>[], defaultPolicy: defaultPolicy);
+        assert(!groups[parentNode]!.members.contains(node));
+        groups[parentNode]!.members.add(groupNode);
+        continue;
+      }
+      // Skip non-focusable and non-traversable nodes in the same way that
+      // FocusScopeNode.traversalDescendants would.
+      if (node.canRequestFocus && !node.skipTraversal) {
+        groups[groupNode] ??= _FocusTraversalGroupInfo(groupMarker, members: <FocusNode>[], defaultPolicy: defaultPolicy);
+        assert(!groups[groupNode]!.members.contains(node));
+        groups[groupNode]!.members.add(node);
+      }
+    }
+
+    // Sort the member lists using the individual policy sorts.
+    final Set<FocusNode?> groupKeys = groups.keys.toSet();
+    for (final FocusNode? key in groups.keys) {
+      final List<FocusNode> sortedMembers = groups[key]!.policy.sortDescendants(groups[key]!.members, currentNode).toList();
+      groups[key]!.members.clear();
+      groups[key]!.members.addAll(sortedMembers);
+    }
+
+    // Traverse the group tree, adding the children of members in the order they
+    // appear in the member lists.
+    final List<FocusNode> sortedDescendants = <FocusNode>[];
+    void visitGroups(_FocusTraversalGroupInfo info) {
+      for (final FocusNode node in info.members) {
+        if (groupKeys.contains(node)) {
+          // This is a policy group focus node. Replace it with the members of
+          // the corresponding policy group.
+          visitGroups(groups[node]!);
+        } else {
+          sortedDescendants.add(node);
+        }
+      }
+    }
+
+    visitGroups(groups[scopeGroupMarker?.focusNode]!);
+    assert(
+      sortedDescendants.length <= scope.traversalDescendants.length && sortedDescendants.toSet().difference(scope.traversalDescendants.toSet()).isEmpty,
+      'sorted descendants contains more nodes than it should: (${sortedDescendants.toSet().difference(scope.traversalDescendants.toSet())})'
+    );
+    return sortedDescendants;
+  }
+
+  /// Moves the focus to the next node in the FocusScopeNode nearest to the
+  /// currentNode argument, either in a forward or reverse direction, depending
+  /// on the value of the forward argument.
+  ///
+  /// This function is called by the next and previous members to move to the
+  /// next or previous node, respectively.
+  ///
+  /// Uses [findFirstFocus]/[findLastFocus] to find the first/last node if there is
+  /// no [FocusScopeNode.focusedChild] set. If there is a focused child for the
+  /// scope, then it calls sortDescendants to get a sorted list of descendants,
+  /// and then finds the node after the current first focus of the scope if
+  /// forward is true, and the node before it if forward is false.
+  ///
+  /// Returns true if a node requested focus.
+  @protected
+  bool _moveFocus(FocusNode currentNode, {required bool forward}) {
+    assert(forward != null);
+    final FocusScopeNode nearestScope = currentNode.nearestScope!;
+    invalidateScopeData(nearestScope);
+    final FocusNode? focusedChild = nearestScope.focusedChild;
+    if (focusedChild == null) {
+      final FocusNode? firstFocus = forward ? findFirstFocus(currentNode) : findLastFocus(currentNode);
+      if (firstFocus != null) {
+        _focusAndEnsureVisible(
+          firstFocus,
+          alignmentPolicy: forward ? ScrollPositionAlignmentPolicy.keepVisibleAtEnd : ScrollPositionAlignmentPolicy.keepVisibleAtStart,
+        );
+        return true;
+      }
+    }
+    final List<FocusNode> sortedNodes = _sortAllDescendants(nearestScope, currentNode);
+    if (forward && focusedChild == sortedNodes.last) {
+      _focusAndEnsureVisible(sortedNodes.first, alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd);
+      return true;
+    }
+    if (!forward && focusedChild == sortedNodes.first) {
+      _focusAndEnsureVisible(sortedNodes.last, alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart);
+      return true;
+    }
+
+    final Iterable<FocusNode> maybeFlipped = forward ? sortedNodes : sortedNodes.reversed;
+    FocusNode? previousNode;
+    for (final FocusNode node in maybeFlipped) {
+      if (previousNode == focusedChild) {
+        _focusAndEnsureVisible(
+          node,
+          alignmentPolicy: forward ? ScrollPositionAlignmentPolicy.keepVisibleAtEnd : ScrollPositionAlignmentPolicy.keepVisibleAtStart,
+        );
+        return true;
+      }
+      previousNode = node;
+    }
+    return false;
+  }
+}
+
+// A policy data object for use by the DirectionalFocusTraversalPolicyMixin so
+// it can keep track of the traversal history.
+class _DirectionalPolicyDataEntry {
+  const _DirectionalPolicyDataEntry({required this.direction, required this.node})
+      : assert(direction != null),
+        assert(node != null);
+
+  final TraversalDirection direction;
+  final FocusNode node;
+}
+
+class _DirectionalPolicyData {
+  const _DirectionalPolicyData({required this.history}) : assert(history != null);
+
+  /// A queue of entries that describe the path taken to the current node.
+  final List<_DirectionalPolicyDataEntry> history;
+}
+
+/// A mixin class that provides an implementation for finding a node in a
+/// particular direction.
+///
+/// This can be mixed in to other [FocusTraversalPolicy] implementations that
+/// only want to implement new next/previous policies.
+///
+/// Since hysteresis in the navigation order is undesirable, this implementation
+/// maintains a stack of previous locations that have been visited on the
+/// policy data for the affected [FocusScopeNode]. If the previous direction
+/// was the opposite of the current direction, then the this policy will request
+/// focus on the previously focused node. Change to another direction other than
+/// the current one or its opposite will clear the stack.
+///
+/// For instance, if the focus moves down, down, down, and then up, up, up, it
+/// will follow the same path through the widgets in both directions. However,
+/// if it moves down, down, down, left, right, and then up, up, up, it may not
+/// follow the same path on the way up as it did on the way down, since changing
+/// the axis of motion resets the history.
+///
+/// See also:
+///
+///  * [FocusNode], for a description of the focus system.
+///  * [FocusTraversalGroup], a widget that groups together and imposes a
+///    traversal policy on the [Focus] nodes below it in the widget hierarchy.
+///  * [WidgetOrderTraversalPolicy], a policy that relies on the widget
+///    creation order to describe the order of traversal.
+///  * [ReadingOrderTraversalPolicy], a policy that describes the order as the
+///    natural "reading order" for the current [Directionality].
+///  * [OrderedTraversalPolicy], a policy that describes the order
+///    explicitly using [FocusTraversalOrder] widgets.
+mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy {
+  final Map<FocusScopeNode, _DirectionalPolicyData> _policyData = <FocusScopeNode, _DirectionalPolicyData>{};
+
+  @override
+  void invalidateScopeData(FocusScopeNode node) {
+    super.invalidateScopeData(node);
+    _policyData.remove(node);
+  }
+
+  @override
+  void changedScope({FocusNode? node, FocusScopeNode? oldScope}) {
+    super.changedScope(node: node, oldScope: oldScope);
+    if (oldScope != null) {
+      _policyData[oldScope]?.history.removeWhere((_DirectionalPolicyDataEntry entry) {
+        return entry.node == node;
+      });
+    }
+  }
+
+  @override
+  FocusNode? findFirstFocusInDirection(FocusNode currentNode, TraversalDirection direction) {
+    assert(direction != null);
+    assert(currentNode != null);
+    switch (direction) {
+      case TraversalDirection.up:
+        // Find the bottom-most node so we can go up from there.
+        return _sortAndFindInitial(currentNode, vertical: true, first: false);
+      case TraversalDirection.down:
+        // Find the top-most node so we can go down from there.
+        return _sortAndFindInitial(currentNode, vertical: true, first: true);
+      case TraversalDirection.left:
+        // Find the right-most node so we can go left from there.
+        return _sortAndFindInitial(currentNode, vertical: false, first: false);
+      case TraversalDirection.right:
+        // Find the left-most node so we can go right from there.
+        return _sortAndFindInitial(currentNode, vertical: false, first: true);
+    }
+  }
+
+  FocusNode? _sortAndFindInitial(FocusNode currentNode, {required bool vertical, required bool first}) {
+    final Iterable<FocusNode> nodes = currentNode.nearestScope!.traversalDescendants;
+    final List<FocusNode> sorted = nodes.toList();
+    mergeSort<FocusNode>(sorted, compare: (FocusNode a, FocusNode b) {
+      if (vertical) {
+        if (first) {
+          return a.rect.top.compareTo(b.rect.top);
+        } else {
+          return b.rect.bottom.compareTo(a.rect.bottom);
+        }
+      } else {
+        if (first) {
+          return a.rect.left.compareTo(b.rect.left);
+        } else {
+          return b.rect.right.compareTo(a.rect.right);
+        }
+      }
+    });
+
+    if (sorted.isNotEmpty) {
+      return sorted.first;
+    }
+
+    return null;
+  }
+
+  // Sorts nodes from left to right horizontally, and removes nodes that are
+  // either to the right of the left side of the target node if we're going
+  // left, or to the left of the right side of the target node if we're going
+  // right.
+  //
+  // This doesn't need to take into account directionality because it is
+  // typically intending to actually go left or right, not in a reading
+  // direction.
+  Iterable<FocusNode>? _sortAndFilterHorizontally(
+    TraversalDirection direction,
+    Rect target,
+    FocusNode nearestScope,
+  ) {
+    assert(direction == TraversalDirection.left || direction == TraversalDirection.right);
+    final Iterable<FocusNode> nodes = nearestScope.traversalDescendants;
+    assert(!nodes.contains(nearestScope));
+    final List<FocusNode> sorted = nodes.toList();
+    mergeSort<FocusNode>(sorted, compare: (FocusNode a, FocusNode b) => a.rect.center.dx.compareTo(b.rect.center.dx));
+    Iterable<FocusNode>? result;
+    switch (direction) {
+      case TraversalDirection.left:
+        result = sorted.where((FocusNode node) => node.rect != target && node.rect.center.dx <= target.left);
+        break;
+      case TraversalDirection.right:
+        result = sorted.where((FocusNode node) => node.rect != target && node.rect.center.dx >= target.right);
+        break;
+      case TraversalDirection.up:
+      case TraversalDirection.down:
+        break;
+    }
+    return result;
+  }
+
+  // Sorts nodes from top to bottom vertically, and removes nodes that are
+  // either below the top of the target node if we're going up, or above the
+  // bottom of the target node if we're going down.
+  Iterable<FocusNode>? _sortAndFilterVertically(
+    TraversalDirection direction,
+    Rect target,
+    Iterable<FocusNode> nodes,
+  ) {
+    final List<FocusNode> sorted = nodes.toList();
+    mergeSort<FocusNode>(sorted, compare: (FocusNode a, FocusNode b) => a.rect.center.dy.compareTo(b.rect.center.dy));
+    switch (direction) {
+      case TraversalDirection.up:
+        return sorted.where((FocusNode node) => node.rect != target && node.rect.center.dy <= target.top);
+      case TraversalDirection.down:
+        return sorted.where((FocusNode node) => node.rect != target && node.rect.center.dy >= target.bottom);
+      case TraversalDirection.left:
+      case TraversalDirection.right:
+        break;
+    }
+    assert(direction == TraversalDirection.up || direction == TraversalDirection.down);
+    return null;
+  }
+
+  // Updates the policy data to keep the previously visited node so that we can
+  // avoid hysteresis when we change directions in navigation.
+  //
+  // Returns true if focus was requested on a previous node.
+  bool _popPolicyDataIfNeeded(TraversalDirection direction, FocusScopeNode nearestScope, FocusNode focusedChild) {
+    final _DirectionalPolicyData? policyData = _policyData[nearestScope];
+    if (policyData != null && policyData.history.isNotEmpty && policyData.history.first.direction != direction) {
+      if (policyData.history.last.node.parent == null) {
+        // If a node has been removed from the tree, then we should stop
+        // referencing it and reset the scope data so that we don't try and
+        // request focus on it. This can happen in slivers where the rendered
+        // node has been unmounted. This has the side effect that hysteresis
+        // might not be avoided when items that go off screen get unmounted.
+        invalidateScopeData(nearestScope);
+        return false;
+      }
+
+      // Returns true if successfully popped the history.
+      bool popOrInvalidate(TraversalDirection direction) {
+        final FocusNode lastNode = policyData.history.removeLast().node;
+        if (Scrollable.of(lastNode.context!) != Scrollable.of(primaryFocus!.context!)) {
+          invalidateScopeData(nearestScope);
+          return false;
+        }
+        final ScrollPositionAlignmentPolicy alignmentPolicy;
+        switch (direction) {
+          case TraversalDirection.up:
+          case TraversalDirection.left:
+            alignmentPolicy = ScrollPositionAlignmentPolicy.keepVisibleAtStart;
+            break;
+          case TraversalDirection.right:
+          case TraversalDirection.down:
+            alignmentPolicy = ScrollPositionAlignmentPolicy.keepVisibleAtEnd;
+            break;
+        }
+        _focusAndEnsureVisible(
+          lastNode,
+          alignmentPolicy: alignmentPolicy,
+        );
+        return true;
+      }
+
+      switch (direction) {
+        case TraversalDirection.down:
+        case TraversalDirection.up:
+          switch (policyData.history.first.direction) {
+            case TraversalDirection.left:
+            case TraversalDirection.right:
+              // Reset the policy data if we change directions.
+              invalidateScopeData(nearestScope);
+              break;
+            case TraversalDirection.up:
+            case TraversalDirection.down:
+              if (popOrInvalidate(direction)) {
+                return true;
+              }
+              break;
+          }
+          break;
+        case TraversalDirection.left:
+        case TraversalDirection.right:
+          switch (policyData.history.first.direction) {
+            case TraversalDirection.left:
+            case TraversalDirection.right:
+              if (popOrInvalidate(direction)) {
+                return true;
+              }
+              break;
+            case TraversalDirection.up:
+            case TraversalDirection.down:
+              // Reset the policy data if we change directions.
+              invalidateScopeData(nearestScope);
+              break;
+          }
+      }
+    }
+    if (policyData != null && policyData.history.isEmpty) {
+      invalidateScopeData(nearestScope);
+    }
+    return false;
+  }
+
+  void _pushPolicyData(TraversalDirection direction, FocusScopeNode nearestScope, FocusNode focusedChild) {
+    final _DirectionalPolicyData? policyData = _policyData[nearestScope];
+    final _DirectionalPolicyDataEntry newEntry = _DirectionalPolicyDataEntry(node: focusedChild, direction: direction);
+    if (policyData != null) {
+      policyData.history.add(newEntry);
+    } else {
+      _policyData[nearestScope] = _DirectionalPolicyData(history: <_DirectionalPolicyDataEntry>[newEntry]);
+    }
+  }
+
+  /// Focuses the next widget in the given [direction] in the [FocusScope] that
+  /// contains the [currentNode].
+  ///
+  /// This determines what the next node to receive focus in the given
+  /// [direction] will be by inspecting the node tree, and then calling
+  /// [FocusNode.requestFocus] on it.
+  ///
+  /// Returns true if it successfully found a node and requested focus.
+  ///
+  /// Maintains a stack of previous locations that have been visited on the
+  /// policy data for the affected [FocusScopeNode]. If the previous direction
+  /// was the opposite of the current direction, then the this policy will
+  /// request focus on the previously focused node. Change to another direction
+  /// other than the current one or its opposite will clear the stack.
+  ///
+  /// If this function returns true when called by a subclass, then the subclass
+  /// should return true and not request focus from any node.
+  @mustCallSuper
+  @override
+  bool inDirection(FocusNode currentNode, TraversalDirection direction) {
+    final FocusScopeNode nearestScope = currentNode.nearestScope!;
+    final FocusNode? focusedChild = nearestScope.focusedChild;
+    if (focusedChild == null) {
+      final FocusNode firstFocus = findFirstFocusInDirection(currentNode, direction) ?? currentNode;
+      switch (direction) {
+        case TraversalDirection.up:
+        case TraversalDirection.left:
+          _focusAndEnsureVisible(
+            firstFocus,
+            alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart,
+          );
+          break;
+        case TraversalDirection.right:
+        case TraversalDirection.down:
+          _focusAndEnsureVisible(
+            firstFocus,
+            alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd,
+          );
+          break;
+      }
+      return true;
+    }
+    if (_popPolicyDataIfNeeded(direction, nearestScope, focusedChild)) {
+      return true;
+    }
+    FocusNode? found;
+    final ScrollableState? focusedScrollable = Scrollable.of(focusedChild.context!);
+    switch (direction) {
+      case TraversalDirection.down:
+      case TraversalDirection.up:
+        Iterable<FocusNode>? eligibleNodes = _sortAndFilterVertically(
+          direction,
+          focusedChild.rect,
+          nearestScope.traversalDescendants,
+        );
+        if (focusedScrollable != null && !focusedScrollable.position.atEdge) {
+          final Iterable<FocusNode> filteredEligibleNodes = eligibleNodes!.where((FocusNode node) => Scrollable.of(node.context!) == focusedScrollable);
+          if (filteredEligibleNodes.isNotEmpty) {
+            eligibleNodes = filteredEligibleNodes;
+          }
+        }
+        if (eligibleNodes!.isEmpty) {
+          break;
+        }
+        List<FocusNode> sorted = eligibleNodes.toList();
+        if (direction == TraversalDirection.up) {
+          sorted = sorted.reversed.toList();
+        }
+        // Find any nodes that intersect the band of the focused child.
+        final Rect band = Rect.fromLTRB(focusedChild.rect.left, -double.infinity, focusedChild.rect.right, double.infinity);
+        final Iterable<FocusNode> inBand = sorted.where((FocusNode node) => !node.rect.intersect(band).isEmpty);
+        if (inBand.isNotEmpty) {
+          // The inBand list is already sorted by horizontal distance, so pick
+          // the closest one.
+          found = inBand.first;
+          break;
+        }
+        // Only out-of-band targets remain, so pick the one that is closest the
+        // to the center line horizontally.
+        mergeSort<FocusNode>(sorted, compare: (FocusNode a, FocusNode b) {
+          return (a.rect.center.dx - focusedChild.rect.center.dx).abs().compareTo((b.rect.center.dx - focusedChild.rect.center.dx).abs());
+        });
+        found = sorted.first;
+        break;
+      case TraversalDirection.right:
+      case TraversalDirection.left:
+        Iterable<FocusNode>? eligibleNodes = _sortAndFilterHorizontally(direction, focusedChild.rect, nearestScope);
+        if (focusedScrollable != null && !focusedScrollable.position.atEdge) {
+          final Iterable<FocusNode> filteredEligibleNodes = eligibleNodes!.where((FocusNode node) => Scrollable.of(node.context!) == focusedScrollable);
+          if (filteredEligibleNodes.isNotEmpty) {
+            eligibleNodes = filteredEligibleNodes;
+          }
+        }
+        if (eligibleNodes!.isEmpty) {
+          break;
+        }
+        List<FocusNode> sorted = eligibleNodes.toList();
+        if (direction == TraversalDirection.left) {
+          sorted = sorted.reversed.toList();
+        }
+        // Find any nodes that intersect the band of the focused child.
+        final Rect band = Rect.fromLTRB(-double.infinity, focusedChild.rect.top, double.infinity, focusedChild.rect.bottom);
+        final Iterable<FocusNode> inBand = sorted.where((FocusNode node) => !node.rect.intersect(band).isEmpty);
+        if (inBand.isNotEmpty) {
+          // The inBand list is already sorted by vertical distance, so pick the
+          // closest one.
+          found = inBand.first;
+          break;
+        }
+        // Only out-of-band targets remain, so pick the one that is closest the
+        // to the center line vertically.
+        mergeSort<FocusNode>(sorted, compare: (FocusNode a, FocusNode b) {
+          return (a.rect.center.dy - focusedChild.rect.center.dy).abs().compareTo((b.rect.center.dy - focusedChild.rect.center.dy).abs());
+        });
+        found = sorted.first;
+        break;
+    }
+    if (found != null) {
+      _pushPolicyData(direction, nearestScope, focusedChild);
+      switch (direction) {
+        case TraversalDirection.up:
+        case TraversalDirection.left:
+          _focusAndEnsureVisible(
+            found,
+            alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart,
+          );
+          break;
+        case TraversalDirection.down:
+        case TraversalDirection.right:
+          _focusAndEnsureVisible(
+            found,
+            alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd,
+          );
+          break;
+      }
+      return true;
+    }
+    return false;
+  }
+}
+
+/// A [FocusTraversalPolicy] that traverses the focus order in widget hierarchy
+/// order.
+///
+/// This policy is used when the order desired is the order in which widgets are
+/// created in the widget hierarchy.
+///
+/// See also:
+///
+///  * [FocusNode], for a description of the focus system.
+///  * [FocusTraversalGroup], a widget that groups together and imposes a
+///    traversal policy on the [Focus] nodes below it in the widget hierarchy.
+///  * [ReadingOrderTraversalPolicy], a policy that describes the order as the
+///    natural "reading order" for the current [Directionality].
+///  * [DirectionalFocusTraversalPolicyMixin] a mixin class that implements
+///    focus traversal in a direction.
+///  * [OrderedTraversalPolicy], a policy that describes the order
+///    explicitly using [FocusTraversalOrder] widgets.
+class WidgetOrderTraversalPolicy extends FocusTraversalPolicy with DirectionalFocusTraversalPolicyMixin {
+  @override
+  Iterable<FocusNode> sortDescendants(Iterable<FocusNode> descendants, FocusNode currentNode) => descendants;
+}
+
+// This class exists mainly for efficiency reasons: the rect is copied out of
+// the node, because it will be accessed many times in the reading order
+// algorithm, and the FocusNode.rect accessor does coordinate transformation. If
+// not for this optimization, it could just be removed, and the node used
+// directly.
+//
+// It's also a convenient place to put some utility functions having to do with
+// the sort data.
+class _ReadingOrderSortData with Diagnosticable {
+  _ReadingOrderSortData(this.node)
+      : assert(node != null),
+        rect = node.rect,
+        directionality = _findDirectionality(node.context!);
+
+  final TextDirection? directionality;
+  final Rect rect;
+  final FocusNode node;
+
+  // Find the directionality in force for a build context without creating a
+  // dependency.
+  static TextDirection? _findDirectionality(BuildContext context) {
+    return (context.getElementForInheritedWidgetOfExactType<Directionality>()?.widget as Directionality?)?.textDirection;
+  }
+
+  /// Finds the common Directional ancestor of an entire list of groups.
+  static TextDirection? commonDirectionalityOf(List<_ReadingOrderSortData> list) {
+    final Iterable<Set<Directionality>> allAncestors = list.map<Set<Directionality>>((_ReadingOrderSortData member) => member.directionalAncestors.toSet());
+    Set<Directionality>? common;
+    for (final Set<Directionality> ancestorSet in allAncestors) {
+      common ??= ancestorSet;
+      common = common.intersection(ancestorSet);
+    }
+    if (common!.isEmpty) {
+      // If there is no common ancestor, then arbitrarily pick the
+      // directionality of the first group, which is the equivalent of the "first
+      // strongly typed" item in a bidi algorithm.
+      return list.first.directionality;
+    }
+    // Find the closest common ancestor. The memberAncestors list contains the
+    // ancestors for all members, but the first member's ancestry was
+    // added in order from nearest to furthest, so we can still use that
+    // to determine the closest one.
+    return list.first.directionalAncestors.firstWhere(common.contains).textDirection;
+  }
+
+  static void sortWithDirectionality(List<_ReadingOrderSortData> list, TextDirection directionality) {
+    mergeSort<_ReadingOrderSortData>(list, compare: (_ReadingOrderSortData a, _ReadingOrderSortData b) {
+      switch (directionality) {
+        case TextDirection.ltr:
+          return a.rect.left.compareTo(b.rect.left);
+        case TextDirection.rtl:
+          return b.rect.right.compareTo(a.rect.right);
+      }
+    });
+  }
+
+  /// Returns the list of Directionality ancestors, in order from nearest to
+  /// furthest.
+  Iterable<Directionality> get directionalAncestors {
+    List<Directionality> getDirectionalityAncestors(BuildContext context) {
+      final List<Directionality> result = <Directionality>[];
+      InheritedElement? directionalityElement = context.getElementForInheritedWidgetOfExactType<Directionality>();
+      while (directionalityElement != null) {
+        result.add(directionalityElement.widget as Directionality);
+        directionalityElement = _getAncestor(directionalityElement)?.getElementForInheritedWidgetOfExactType<Directionality>();
+      }
+      return result;
+    }
+
+    _directionalAncestors ??= getDirectionalityAncestors(node.context!);
+    return _directionalAncestors!;
+  }
+
+  List<Directionality>? _directionalAncestors;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<TextDirection>('directionality', directionality));
+    properties.add(StringProperty('name', node.debugLabel, defaultValue: null));
+    properties.add(DiagnosticsProperty<Rect>('rect', rect));
+  }
+}
+
+// A class for containing group data while sorting in reading order while taking
+// into account the ambient directionality.
+class _ReadingOrderDirectionalGroupData with Diagnosticable {
+  _ReadingOrderDirectionalGroupData(this.members);
+
+  final List<_ReadingOrderSortData> members;
+
+  TextDirection? get directionality => members.first.directionality;
+
+  Rect? _rect;
+  Rect get rect {
+    if (_rect == null) {
+      for (final Rect rect in members.map<Rect>((_ReadingOrderSortData data) => data.rect)) {
+        _rect ??= rect;
+        _rect = _rect!.expandToInclude(rect);
+      }
+    }
+    return _rect!;
+  }
+
+  List<Directionality> get memberAncestors {
+    if (_memberAncestors == null) {
+      _memberAncestors = <Directionality>[];
+      for (final _ReadingOrderSortData member in members) {
+        _memberAncestors!.addAll(member.directionalAncestors);
+      }
+    }
+    return _memberAncestors!;
+  }
+
+  List<Directionality>? _memberAncestors;
+
+  static void sortWithDirectionality(List<_ReadingOrderDirectionalGroupData> list, TextDirection directionality) {
+    mergeSort<_ReadingOrderDirectionalGroupData>(list, compare: (_ReadingOrderDirectionalGroupData a, _ReadingOrderDirectionalGroupData b) {
+      switch (directionality) {
+        case TextDirection.ltr:
+          return a.rect.left.compareTo(b.rect.left);
+        case TextDirection.rtl:
+          return b.rect.right.compareTo(a.rect.right);
+      }
+    });
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<TextDirection>('directionality', directionality));
+    properties.add(DiagnosticsProperty<Rect>('rect', rect));
+    properties.add(IterableProperty<String>('members', members.map<String>((_ReadingOrderSortData member) {
+      return '"${member.node.debugLabel}"(${member.rect})';
+    })));
+  }
+}
+
+/// Traverses the focus order in "reading order".
+///
+/// By default, reading order traversal goes in the reading direction, and then
+/// down, using this algorithm:
+///
+/// 1. Find the node rectangle that has the highest `top` on the screen.
+/// 2. Find any other nodes that intersect the infinite horizontal band defined
+///    by the highest rectangle's top and bottom edges.
+/// 3. Pick the closest to the beginning of the reading order from among the
+///    nodes discovered above.
+///
+/// It uses the ambient [Directionality] in the context for the enclosing
+/// [FocusTraversalGroup] to determine which direction is "reading order".
+///
+/// See also:
+///
+///  * [FocusNode], for a description of the focus system.
+///  * [FocusTraversalGroup], a widget that groups together and imposes a
+///    traversal policy on the [Focus] nodes below it in the widget hierarchy.
+///  * [WidgetOrderTraversalPolicy], a policy that relies on the widget
+///    creation order to describe the order of traversal.
+///  * [DirectionalFocusTraversalPolicyMixin] a mixin class that implements
+///    focus traversal in a direction.
+///  * [OrderedTraversalPolicy], a policy that describes the order
+///    explicitly using [FocusTraversalOrder] widgets.
+class ReadingOrderTraversalPolicy extends FocusTraversalPolicy with DirectionalFocusTraversalPolicyMixin {
+  // Collects the given candidates into groups by directionality. The candidates
+  // have already been sorted as if they all had the directionality of the
+  // nearest Directionality ancestor.
+  List<_ReadingOrderDirectionalGroupData> _collectDirectionalityGroups(Iterable<_ReadingOrderSortData> candidates) {
+    TextDirection? currentDirection = candidates.first.directionality;
+    List<_ReadingOrderSortData> currentGroup = <_ReadingOrderSortData>[];
+    final List<_ReadingOrderDirectionalGroupData> result = <_ReadingOrderDirectionalGroupData>[];
+    // Split candidates into runs of the same directionality.
+    for (final _ReadingOrderSortData candidate in candidates) {
+      if (candidate.directionality == currentDirection) {
+        currentGroup.add(candidate);
+        continue;
+      }
+      currentDirection = candidate.directionality;
+      result.add(_ReadingOrderDirectionalGroupData(currentGroup));
+      currentGroup = <_ReadingOrderSortData>[candidate];
+    }
+    if (currentGroup.isNotEmpty) {
+      result.add(_ReadingOrderDirectionalGroupData(currentGroup));
+    }
+    // Sort each group separately. Each group has the same directionality.
+    for (final _ReadingOrderDirectionalGroupData bandGroup in result) {
+      if (bandGroup.members.length == 1) {
+        continue; // No need to sort one node.
+      }
+      _ReadingOrderSortData.sortWithDirectionality(bandGroup.members, bandGroup.directionality!);
+    }
+    return result;
+  }
+
+  _ReadingOrderSortData _pickNext(List<_ReadingOrderSortData> candidates) {
+    // Find the topmost node by sorting on the top of the rectangles.
+    mergeSort<_ReadingOrderSortData>(candidates, compare: (_ReadingOrderSortData a, _ReadingOrderSortData b) => a.rect.top.compareTo(b.rect.top));
+    final _ReadingOrderSortData topmost = candidates.first;
+
+    // Find the candidates that are in the same horizontal band as the current one.
+    List<_ReadingOrderSortData> inBand(_ReadingOrderSortData current, Iterable<_ReadingOrderSortData> candidates) {
+      final Rect band = Rect.fromLTRB(double.negativeInfinity, current.rect.top, double.infinity, current.rect.bottom);
+      return candidates.where((_ReadingOrderSortData item) {
+        return !item.rect.intersect(band).isEmpty;
+      }).toList();
+    }
+
+    final List<_ReadingOrderSortData> inBandOfTop = inBand(topmost, candidates);
+    // It has to have at least topmost in it if the topmost is not degenerate.
+    assert(topmost.rect.isEmpty || inBandOfTop.isNotEmpty);
+
+    // The topmost rect in is in a band by itself, so just return that one.
+    if (inBandOfTop.length <= 1) {
+      return topmost;
+    }
+
+    // Now that we know there are others in the same band as the topmost, then pick
+    // the one at the beginning, depending on the text direction in force.
+
+    // Find out the directionality of the nearest common Directionality
+    // ancestor for all nodes. This provides a base directionality to use for
+    // the ordering of the groups.
+    final TextDirection? nearestCommonDirectionality = _ReadingOrderSortData.commonDirectionalityOf(inBandOfTop);
+
+    // Do an initial common-directionality-based sort to get consistent geometric
+    // ordering for grouping into directionality groups. It has to use the
+    // common directionality to be able to group into sane groups for the
+    // given directionality, since rectangles can overlap and give different
+    // results for different directionalities.
+    _ReadingOrderSortData.sortWithDirectionality(inBandOfTop, nearestCommonDirectionality!);
+
+    // Collect the top band into internally sorted groups with shared directionality.
+    final List<_ReadingOrderDirectionalGroupData> bandGroups = _collectDirectionalityGroups(inBandOfTop);
+    if (bandGroups.length == 1) {
+      // There's only one directionality group, so just send back the first
+      // one in that group, since it's already sorted.
+      return bandGroups.first.members.first;
+    }
+
+    // Sort the groups based on the common directionality and bounding boxes.
+    _ReadingOrderDirectionalGroupData.sortWithDirectionality(bandGroups, nearestCommonDirectionality);
+    return bandGroups.first.members.first;
+  }
+
+  // Sorts the list of nodes based on their geometry into the desired reading
+  // order based on the directionality of the context for each node.
+  @override
+  Iterable<FocusNode> sortDescendants(Iterable<FocusNode> descendants, FocusNode currentNode) {
+    assert(descendants != null);
+    if (descendants.length <= 1) {
+      return descendants;
+    }
+
+    final List<_ReadingOrderSortData> data = <_ReadingOrderSortData>[
+      for (final FocusNode node in descendants) _ReadingOrderSortData(node),
+    ];
+
+    final List<FocusNode> sortedList = <FocusNode>[];
+    final List<_ReadingOrderSortData> unplaced = data;
+
+    // Pick the initial widget as the one that is at the beginning of the band
+    // of the topmost, or the topmost, if there are no others in its band.
+    _ReadingOrderSortData current = _pickNext(unplaced);
+    sortedList.add(current.node);
+    unplaced.remove(current);
+
+    // Go through each node, picking the next one after eliminating the previous
+    // one, since removing the previously picked node will expose a new band in
+    // which to choose candidates.
+    while (unplaced.isNotEmpty) {
+      final _ReadingOrderSortData next = _pickNext(unplaced);
+      current = next;
+      sortedList.add(current.node);
+      unplaced.remove(current);
+    }
+    return sortedList;
+  }
+}
+
+/// Base class for all sort orders for [OrderedTraversalPolicy] traversal.
+///
+/// {@template flutter.widgets.FocusOrder.comparable}
+/// Only orders of the same type are comparable. If a set of widgets in the same
+/// [FocusTraversalGroup] contains orders that are not comparable with each
+/// other, it will assert, since the ordering between such keys is undefined. To
+/// avoid collisions, use a [FocusTraversalGroup] to group similarly ordered
+/// widgets together.
+///
+/// When overriding, [FocusOrder.doCompare] must be overridden instead of
+/// [FocusOrder.compareTo], which calls [FocusOrder.doCompare] to do the actual
+/// comparison.
+/// {@endtemplate}
+///
+/// See also:
+///
+/// * [FocusTraversalGroup], a widget that groups together and imposes a
+///   traversal policy on the [Focus] nodes below it in the widget hierarchy.
+/// * [FocusTraversalOrder], a widget that assigns an order to a widget subtree
+///   for the [OrderedTraversalPolicy] to use.
+/// * [NumericFocusOrder], for a focus order that describes its order with a
+///   `double`.
+/// * [LexicalFocusOrder], a focus order that assigns a string-based lexical
+///   traversal order to a [FocusTraversalOrder] widget.
+@immutable
+abstract class FocusOrder with Diagnosticable implements Comparable<FocusOrder> {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const FocusOrder();
+
+  /// Compares this object to another [Comparable].
+  ///
+  /// When overriding [FocusOrder], implement [doCompare] instead of this
+  /// function to do the actual comparison.
+  ///
+  /// Returns a value like a [Comparator] when comparing `this` to [other].
+  /// That is, it returns a negative integer if `this` is ordered before [other],
+  /// a positive integer if `this` is ordered after [other],
+  /// and zero if `this` and [other] are ordered together.
+  ///
+  /// The [other] argument must be a value that is comparable to this object.
+  @override
+  @nonVirtual
+  int compareTo(FocusOrder other) {
+    assert(
+        runtimeType == other.runtimeType,
+        "The sorting algorithm must not compare incomparable keys, since they don't "
+        'know how to order themselves relative to each other. Comparing $this with $other');
+    return doCompare(other);
+  }
+
+  /// The subclass implementation called by [compareTo] to compare orders.
+  ///
+  /// The argument is guaranteed to be of the same [runtimeType] as this object.
+  ///
+  /// The method should return a negative number if this object comes earlier in
+  /// the sort order than the `other` argument; and a positive number if it
+  /// comes later in the sort order than `other`. Returning zero causes the
+  /// system to fall back to the secondary sort order defined by
+  /// [OrderedTraversalPolicy.secondary]
+  @protected
+  int doCompare(covariant FocusOrder other);
+}
+
+/// Can be given to a [FocusTraversalOrder] widget to assign a numerical order
+/// to a widget subtree that is using a [OrderedTraversalPolicy] to define the
+/// order in which widgets should be traversed with the keyboard.
+///
+/// {@macro flutter.widgets.FocusOrder.comparable}
+///
+/// See also:
+///
+///  * [FocusTraversalOrder], a widget that assigns an order to a widget subtree
+///    for the [OrderedTraversalPolicy] to use.
+class NumericFocusOrder extends FocusOrder {
+  /// Const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const NumericFocusOrder(this.order) : assert(order != null);
+
+  /// The numerical order to assign to the widget subtree using
+  /// [FocusTraversalOrder].
+  ///
+  /// Determines the placement of this widget in a sequence of widgets that defines
+  /// the order in which this node is traversed by the focus policy.
+  ///
+  /// Lower values will be traversed first.
+  final double order;
+
+  @override
+  int doCompare(NumericFocusOrder other) => order.compareTo(other.order);
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('order', order));
+  }
+}
+
+/// Can be given to a [FocusTraversalOrder] widget to use a String to assign a
+/// lexical order to a widget subtree that is using a
+/// [OrderedTraversalPolicy] to define the order in which widgets should be
+/// traversed with the keyboard.
+///
+/// This sorts strings using Dart's default string comparison, which is not
+/// locale specific.
+///
+/// {@macro flutter.widgets.FocusOrder.comparable}
+///
+/// See also:
+///
+///  * [FocusTraversalOrder], a widget that assigns an order to a widget subtree
+///    for the [OrderedTraversalPolicy] to use.
+class LexicalFocusOrder extends FocusOrder {
+  /// Const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const LexicalFocusOrder(this.order) : assert(order != null);
+
+  /// The String that defines the lexical order to assign to the widget subtree
+  /// using [FocusTraversalOrder].
+  ///
+  /// Determines the placement of this widget in a sequence of widgets that defines
+  /// the order in which this node is traversed by the focus policy.
+  ///
+  /// Lower lexical values will be traversed first (e.g. 'a' comes before 'z').
+  final String order;
+
+  @override
+  int doCompare(LexicalFocusOrder other) => order.compareTo(other.order);
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(StringProperty('order', order));
+  }
+}
+
+// Used to help sort the focus nodes in an OrderedFocusTraversalPolicy.
+class _OrderedFocusInfo {
+  const _OrderedFocusInfo({required this.node, required this.order})
+      : assert(node != null),
+        assert(order != null);
+
+  final FocusNode node;
+  final FocusOrder order;
+}
+
+/// A [FocusTraversalPolicy] that orders nodes by an explicit order that resides
+/// in the nearest [FocusTraversalOrder] widget ancestor.
+///
+/// {@macro flutter.widgets.FocusOrder.comparable}
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_center_no_null_safety}
+/// This sample shows how to assign a traversal order to a widget. In the
+/// example, the focus order goes from bottom right (the "One" button) to top
+/// left (the "Six" button).
+///
+/// ```dart preamble
+/// class DemoButton extends StatelessWidget {
+///   const DemoButton({this.name, this.autofocus = false, this.order});
+///
+///   final String name;
+///   final bool autofocus;
+///   final double order;
+///
+///   void _handleOnPressed() {
+///     print('Button $name pressed.');
+///     debugDumpFocusTree();
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return FocusTraversalOrder(
+///       order: NumericFocusOrder(order),
+///       child: TextButton(
+///         autofocus: autofocus,
+///         onPressed: () => _handleOnPressed(),
+///         child: Text(name),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return FocusTraversalGroup(
+///     policy: OrderedTraversalPolicy(),
+///     child: Column(
+///       mainAxisAlignment: MainAxisAlignment.center,
+///       children: <Widget>[
+///         Row(
+///           mainAxisAlignment: MainAxisAlignment.center,
+///           children: const <Widget>[
+///             DemoButton(name: 'Six', order: 6),
+///           ],
+///         ),
+///         Row(
+///           mainAxisAlignment: MainAxisAlignment.center,
+///           children: const <Widget>[
+///             DemoButton(name: 'Five', order: 5),
+///             DemoButton(name: 'Four', order: 4),
+///           ],
+///         ),
+///         Row(
+///           mainAxisAlignment: MainAxisAlignment.center,
+///           children: const <Widget>[
+///             DemoButton(name: 'Three', order: 3),
+///             DemoButton(name: 'Two', order: 2),
+///             DemoButton(name: 'One', order: 1, autofocus: true),
+///           ],
+///         ),
+///       ],
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [FocusTraversalGroup], a widget that groups together and imposes a
+///    traversal policy on the [Focus] nodes below it in the widget hierarchy.
+///  * [WidgetOrderTraversalPolicy], a policy that relies on the widget
+///    creation order to describe the order of traversal.
+///  * [ReadingOrderTraversalPolicy], a policy that describes the order as the
+///    natural "reading order" for the current [Directionality].
+///  * [NumericFocusOrder], a focus order that assigns a numeric traversal order
+///    to a [FocusTraversalOrder] widget.
+///  * [LexicalFocusOrder], a focus order that assigns a string-based lexical
+///    traversal order to a [FocusTraversalOrder] widget.
+///  * [FocusOrder], an abstract base class for all types of focus traversal
+///    orderings.
+class OrderedTraversalPolicy extends FocusTraversalPolicy with DirectionalFocusTraversalPolicyMixin {
+  /// Constructs a traversal policy that orders widgets for keyboard traversal
+  /// based on an explicit order.
+  ///
+  /// If [secondary] is null, it will default to [ReadingOrderTraversalPolicy].
+  OrderedTraversalPolicy({this.secondary});
+
+  /// This is the policy that is used when a node doesn't have an order
+  /// assigned, or when multiple nodes have orders which are identical.
+  ///
+  /// If not set, this defaults to [ReadingOrderTraversalPolicy].
+  ///
+  /// This policy determines the secondary sorting order of nodes which evaluate
+  /// as having an identical order (including those with no order specified).
+  ///
+  /// Nodes with no order specified will be sorted after nodes with an explicit
+  /// order.
+  final FocusTraversalPolicy? secondary;
+
+  @override
+  Iterable<FocusNode> sortDescendants(Iterable<FocusNode> descendants, FocusNode currentNode) {
+    final FocusTraversalPolicy secondaryPolicy = secondary ?? ReadingOrderTraversalPolicy();
+    final Iterable<FocusNode> sortedDescendants = secondaryPolicy.sortDescendants(descendants, currentNode);
+    final List<FocusNode> unordered = <FocusNode>[];
+    final List<_OrderedFocusInfo> ordered = <_OrderedFocusInfo>[];
+    for (final FocusNode node in sortedDescendants) {
+      final FocusOrder? order = FocusTraversalOrder.maybeOf(node.context!);
+      if (order != null) {
+        ordered.add(_OrderedFocusInfo(node: node, order: order));
+      } else {
+        unordered.add(node);
+      }
+    }
+    mergeSort<_OrderedFocusInfo>(ordered, compare: (_OrderedFocusInfo a, _OrderedFocusInfo b) {
+      assert(
+        a.order.runtimeType == b.order.runtimeType,
+        'When sorting nodes for determining focus order, the order (${a.order}) of '
+        "node ${a.node}, isn't the same type as the order (${b.order}) of ${b.node}. "
+        "Incompatible order types can't be compared.  Use a FocusTraversalGroup to group "
+        'similar orders together.',
+      );
+      return a.order.compareTo(b.order);
+    });
+    return ordered.map<FocusNode>((_OrderedFocusInfo info) => info.node).followedBy(unordered);
+  }
+}
+
+/// An inherited widget that describes the order in which its child subtree
+/// should be traversed.
+///
+/// {@macro flutter.widgets.FocusOrder.comparable}
+///
+/// The order for a widget is determined by the [FocusOrder] returned by
+/// [FocusTraversalOrder.of] for a particular context.
+class FocusTraversalOrder extends InheritedWidget {
+  /// A const constructor so that subclasses can be const.
+  const FocusTraversalOrder({Key? key, required this.order, required Widget child}) : super(key: key, child: child);
+
+  /// The order for the widget descendants of this [FocusTraversalOrder].
+  final FocusOrder order;
+
+  /// Finds the [FocusOrder] in the nearest ancestor [FocusTraversalOrder] widget.
+  ///
+  /// It does not create a rebuild dependency because changing the traversal
+  /// order doesn't change the widget tree, so nothing needs to be rebuilt as a
+  /// result of an order change.
+  ///
+  /// If no [FocusTraversalOrder] ancestor exists, or the order is null, this
+  /// will assert in debug mode, and throw an exception in release mode.
+  static FocusOrder of(BuildContext context) {
+    assert(context != null);
+    final FocusTraversalOrder? marker = context.getElementForInheritedWidgetOfExactType<FocusTraversalOrder>()?.widget as FocusTraversalOrder?;
+    assert((){
+      if (marker == null) {
+        throw FlutterError(
+          'FocusTraversalOrder.of() was called with a context that '
+          'does not contain a FocusTraversalOrder widget. No TraversalOrder widget '
+          'ancestor could be found starting from the context that was passed to '
+          'FocusTraversalOrder.of().\n'
+          'The context used was:\n'
+          '  $context',
+        );
+      }
+      return true;
+    }());
+    return marker!.order;
+  }
+
+  /// Finds the [FocusOrder] in the nearest ancestor [FocusTraversalOrder] widget.
+  ///
+  /// It does not create a rebuild dependency because changing the traversal
+  /// order doesn't change the widget tree, so nothing needs to be rebuilt as a
+  /// result of an order change.
+  ///
+  /// If no [FocusTraversalOrder] ancestor exists, or the order is null, returns null.
+  static FocusOrder? maybeOf(BuildContext context) {
+    assert(context != null);
+    final FocusTraversalOrder? marker = context.getElementForInheritedWidgetOfExactType<FocusTraversalOrder>()?.widget as FocusTraversalOrder?;
+    return marker?.order;
+  }
+
+  // Since the order of traversal doesn't affect display of anything, we don't
+  // need to force a rebuild of anything that depends upon it.
+  @override
+  bool updateShouldNotify(InheritedWidget oldWidget) => false;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<FocusOrder>('order', order));
+  }
+}
+
+/// A widget that describes the inherited focus policy for focus traversal for
+/// its descendants, grouping them into a separate traversal group.
+///
+/// A traversal group is treated as one entity when sorted by the traversal
+/// algorithm, so it can be used to segregate different parts of the widget tree
+/// that need to be sorted using different algorithms and/or sort orders when
+/// using an [OrderedTraversalPolicy].
+///
+/// Within the group, it will use the given [policy] to order the elements. The
+/// group itself will be ordered using the parent group's policy.
+///
+/// By default, traverses in reading order using [ReadingOrderTraversalPolicy].
+///
+/// To prevent the members of the group from being focused, set the
+/// [descendantsAreFocusable] attribute to false.
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+/// This sample shows three rows of buttons, each grouped by a
+/// [FocusTraversalGroup], each with different traversal order policies. Use tab
+/// traversal to see the order they are traversed in.  The first row follows a
+/// numerical order, the second follows a lexical order (ordered to traverse
+/// right to left), and the third ignores the numerical order assigned to it and
+/// traverses in widget order.
+///
+/// ```dart preamble
+/// /// A button wrapper that adds either a numerical or lexical order, depending on
+/// /// the type of T.
+/// class OrderedButton<T> extends StatefulWidget {
+///   const OrderedButton({
+///     this.name,
+///     this.canRequestFocus = true,
+///     this.autofocus = false,
+///     this.order,
+///   });
+///
+///   final String name;
+///   final bool canRequestFocus;
+///   final bool autofocus;
+///   final T order;
+///
+///   @override
+///   _OrderedButtonState createState() => _OrderedButtonState();
+/// }
+///
+/// class _OrderedButtonState<T> extends State<OrderedButton<T>> {
+///   FocusNode focusNode;
+///
+///   @override
+///   void initState() {
+///     super.initState();
+///     focusNode = FocusNode(
+///       debugLabel: widget.name,
+///       canRequestFocus: widget.canRequestFocus,
+///     );
+///   }
+///
+///   @override
+///   void dispose() {
+///     focusNode?.dispose();
+///     super.dispose();
+///   }
+///
+///   @override
+///   void didUpdateWidget(OrderedButton oldWidget) {
+///     super.didUpdateWidget(oldWidget);
+///     focusNode.canRequestFocus = widget.canRequestFocus;
+///   }
+///
+///   void _handleOnPressed() {
+///     focusNode.requestFocus();
+///     print('Button ${widget.name} pressed.');
+///     debugDumpFocusTree();
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     FocusOrder order;
+///     if (widget.order is num) {
+///       order = NumericFocusOrder((widget.order as num).toDouble());
+///     } else {
+///       order = LexicalFocusOrder(widget.order.toString());
+///     }
+///
+///     Color overlayColor(Set<MaterialState> states) {
+///       if (states.contains(MaterialState.focused)) {
+///         return Colors.red;
+///       }
+///       if (states.contains(MaterialState.hovered)) {
+///         return Colors.blue;
+///       }
+///       return null;  // defer to the default overlayColor
+///     }
+///
+///     Color foregroundColor(Set<MaterialState> states) {
+///       if (states.contains(MaterialState.focused) || states.contains(MaterialState.hovered)) {
+///         return Colors.white;
+///       }
+///       return null;  // defer to the default foregroundColor
+///     }
+///
+///     return FocusTraversalOrder(
+///       order: order,
+///       child: Padding(
+///         padding: const EdgeInsets.all(8.0),
+///         child: OutlinedButton(
+///           focusNode: focusNode,
+///           autofocus: widget.autofocus,
+///           style: ButtonStyle(
+///             overlayColor: MaterialStateProperty.resolveWith<Color>(overlayColor),
+///             foregroundColor: MaterialStateProperty.resolveWith<Color>(foregroundColor),
+///           ),
+///           onPressed: () => _handleOnPressed(),
+///           child: Text(widget.name),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Container(
+///     color: Colors.white,
+///     child: FocusTraversalGroup(
+///       policy: OrderedTraversalPolicy(),
+///       child: Column(
+///         mainAxisAlignment: MainAxisAlignment.center,
+///         children: <Widget>[
+///           // A group that is ordered with a numerical order, from left to right.
+///           FocusTraversalGroup(
+///             policy: OrderedTraversalPolicy(),
+///             child: Row(
+///               mainAxisAlignment: MainAxisAlignment.center,
+///               children: List<Widget>.generate(3, (int index) {
+///                 return OrderedButton<num>(
+///                   name: 'num: $index',
+///                   // TRY THIS: change this to "3 - index" and see how the order changes.
+///                   order: index,
+///                 );
+///               }),
+///             ),
+///           ),
+///           // A group that is ordered with a lexical order, from right to left.
+///           FocusTraversalGroup(
+///             policy: OrderedTraversalPolicy(),
+///             child: Row(
+///               mainAxisAlignment: MainAxisAlignment.center,
+///               children: List<Widget>.generate(3, (int index) {
+///                 // Order as "C" "B", "A".
+///                 String order =
+///                     String.fromCharCode('A'.codeUnitAt(0) + (2 - index));
+///                 return OrderedButton<String>(
+///                   name: 'String: $order',
+///                   order: order,
+///                 );
+///               }),
+///             ),
+///           ),
+///           // A group that orders in widget order, regardless of what the order is set to.
+///           FocusTraversalGroup(
+///             // Note that because this is NOT an OrderedTraversalPolicy, the
+///             // assigned order of these OrderedButtons is ignored, and they
+///             // are traversed in widget order. TRY THIS: change this to
+///             // "OrderedTraversalPolicy()" and see that it now follows the
+///             // numeric order set on them instead of the widget order.
+///             policy: WidgetOrderTraversalPolicy(),
+///             child: Row(
+///               mainAxisAlignment: MainAxisAlignment.center,
+///               children: List<Widget>.generate(3, (int index) {
+///                 return OrderedButton<num>(
+///                   name: 'ignored num: ${3 - index}',
+///                   order: 3 - index,
+///                 );
+///               }),
+///             ),
+///           ),
+///         ],
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [FocusNode], for a description of the focus system.
+///  * [WidgetOrderTraversalPolicy], a policy that relies on the widget
+///    creation order to describe the order of traversal.
+///  * [ReadingOrderTraversalPolicy], a policy that describes the order as the
+///    natural "reading order" for the current [Directionality].
+///  * [DirectionalFocusTraversalPolicyMixin] a mixin class that implements
+///    focus traversal in a direction.
+class FocusTraversalGroup extends StatefulWidget {
+  /// Creates a [FocusTraversalGroup] object.
+  ///
+  /// The [child] and [descendantsAreFocusable] arguments must not be null.
+  FocusTraversalGroup({
+    Key? key,
+    FocusTraversalPolicy? policy,
+    this.descendantsAreFocusable = true,
+    required this.child,
+  })  : assert(descendantsAreFocusable != null),
+        policy = policy ?? ReadingOrderTraversalPolicy(),
+        super(key: key);
+
+  /// The policy used to move the focus from one focus node to another when
+  /// traversing them using a keyboard.
+  ///
+  /// If not specified, traverses in reading order using
+  /// [ReadingOrderTraversalPolicy].
+  ///
+  /// See also:
+  ///
+  ///  * [FocusTraversalPolicy] for the API used to impose traversal order
+  ///    policy.
+  ///  * [WidgetOrderTraversalPolicy] for a traversal policy that traverses
+  ///    nodes in the order they are added to the widget tree.
+  ///  * [ReadingOrderTraversalPolicy] for a traversal policy that traverses
+  ///    nodes in the reading order defined in the widget tree, and then top to
+  ///    bottom.
+  final FocusTraversalPolicy policy;
+
+  /// {@macro flutter.widgets.Focus.descendantsAreFocusable}
+  final bool descendantsAreFocusable;
+
+  /// The child widget of this [FocusTraversalGroup].
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// Returns the focus policy set by the [FocusTraversalGroup] that most
+  /// tightly encloses the given [BuildContext].
+  ///
+  /// It does not create a rebuild dependency because changing the traversal
+  /// order doesn't change the widget tree, so nothing needs to be rebuilt as a
+  /// result of an order change.
+  ///
+  /// Will assert if no [FocusTraversalGroup] ancestor is found.
+  ///
+  /// See also:
+  ///
+  ///  * [maybeOf] for a similar function that will return null if no
+  ///    [FocusTraversalGroup] ancestor is found.
+  static FocusTraversalPolicy of(BuildContext context) {
+    assert(context != null);
+    final _FocusTraversalGroupMarker? inherited = context.dependOnInheritedWidgetOfExactType<_FocusTraversalGroupMarker>();
+    assert(() {
+      if (inherited == null) {
+        throw FlutterError(
+          'Unable to find a FocusTraversalGroup widget in the context.\n'
+          'FocusTraversalGroup.of() was called with a context that does not contain a '
+          'FocusTraversalGroup.\n'
+          'No FocusTraversalGroup ancestor could be found starting from the context that was '
+          'passed to FocusTraversalGroup.of(). This can happen because there is not a '
+          'WidgetsApp or MaterialApp widget (those widgets introduce a FocusTraversalGroup), '
+          'or it can happen if the context comes from a widget above those widgets.\n'
+          'The context used was:\n'
+          '  $context',
+        );
+      }
+      return true;
+    }());
+    return inherited!.policy;
+  }
+
+  /// Returns the focus policy set by the [FocusTraversalGroup] that most
+  /// tightly encloses the given [BuildContext].
+  ///
+  /// It does not create a rebuild dependency because changing the traversal
+  /// order doesn't change the widget tree, so nothing needs to be rebuilt as a
+  /// result of an order change.
+  ///
+  /// Will return null if it doesn't find a [FocusTraversalGroup] ancestor.
+  ///
+  /// See also:
+  ///
+  ///  * [of] for a similar function that will throw if no [FocusTraversalGroup]
+  ///    ancestor is found.
+  static FocusTraversalPolicy? maybeOf(BuildContext context) {
+    assert(context != null);
+    final _FocusTraversalGroupMarker? inherited = context.dependOnInheritedWidgetOfExactType<_FocusTraversalGroupMarker>();
+    return inherited?.policy;
+  }
+
+  @override
+  _FocusTraversalGroupState createState() => _FocusTraversalGroupState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<FocusTraversalPolicy>('policy', policy));
+  }
+}
+
+class _FocusTraversalGroupState extends State<FocusTraversalGroup> {
+  // The internal focus node used to collect the children of this node into a
+  // group, and to provide a context for the traversal algorithm to sort the
+  // group with.
+  FocusNode? focusNode;
+
+  @override
+  void initState() {
+    super.initState();
+    focusNode = FocusNode(
+      canRequestFocus: false,
+      skipTraversal: true,
+      debugLabel: 'FocusTraversalGroup',
+    );
+  }
+
+  @override
+  void dispose() {
+    focusNode?.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _FocusTraversalGroupMarker(
+      policy: widget.policy,
+      focusNode: focusNode!,
+      child: Focus(
+        focusNode: focusNode,
+        canRequestFocus: false,
+        skipTraversal: true,
+        includeSemantics: false,
+        descendantsAreFocusable: widget.descendantsAreFocusable,
+        child: widget.child,
+      ),
+    );
+  }
+}
+
+// A "marker" inherited widget to make the group faster to find.
+class _FocusTraversalGroupMarker extends InheritedWidget {
+  const _FocusTraversalGroupMarker({
+    required this.policy,
+    required this.focusNode,
+    required Widget child,
+  })  : assert(policy != null),
+        assert(focusNode != null),
+        super(child: child);
+
+  final FocusTraversalPolicy policy;
+  final FocusNode focusNode;
+
+  @override
+  bool updateShouldNotify(InheritedWidget oldWidget) => false;
+}
+
+/// An intent for use with the [RequestFocusAction], which supplies the
+/// [FocusNode] that should be focused.
+class RequestFocusIntent extends Intent {
+  /// A const constructor for a [RequestFocusIntent], so that subclasses may be
+  /// const.
+  const RequestFocusIntent(this.focusNode)
+      : assert(focusNode != null);
+
+  /// The [FocusNode] that is to be focused.
+  final FocusNode focusNode;
+}
+
+/// An [Action] that requests the focus on the node it is given in its
+/// [RequestFocusIntent].
+///
+/// This action can be used to request focus for a particular node, by calling
+/// [Action.invoke] like so:
+///
+/// ```dart
+/// Actions.invoke(context, const RequestFocusIntent(focusNode));
+/// ```
+///
+/// Where the `focusNode` is the node for which the focus will be requested.
+///
+/// The difference between requesting focus in this way versus calling
+/// [FocusNode.requestFocus] directly is that it will use the [Action]
+/// registered in the nearest [Actions] widget associated with
+/// [RequestFocusIntent] to make the request, rather than just requesting focus
+/// directly. This allows the action to have additional side effects, like
+/// logging, or undo and redo functionality.
+///
+/// This [RequestFocusAction] class is the default action associated with the
+/// [RequestFocusIntent] in the [WidgetsApp], and it simply requests focus. You
+/// can redefine the associated action with your own [Actions] widget.
+///
+/// See [FocusTraversalPolicy] for more information about focus traversal.
+class RequestFocusAction extends Action<RequestFocusIntent> {
+  @override
+  void invoke(RequestFocusIntent intent) {
+    _focusAndEnsureVisible(intent.focusNode);
+  }
+}
+
+/// An [Intent] bound to [NextFocusAction], which moves the focus to the next
+/// focusable node in the focus traversal order.
+///
+/// See [FocusTraversalPolicy] for more information about focus traversal.
+class NextFocusIntent extends Intent {
+  /// Creates a const [NextFocusIntent] so subclasses can be const.
+  const NextFocusIntent();
+}
+
+/// An [Action] that moves the focus to the next focusable node in the focus
+/// order.
+///
+/// This action is the default action registered for the [NextFocusIntent], and
+/// by default is bound to the [LogicalKeyboardKey.tab] key in the [WidgetsApp].
+///
+/// See [FocusTraversalPolicy] for more information about focus traversal.
+class NextFocusAction extends Action<NextFocusIntent> {
+  @override
+  void invoke(NextFocusIntent intent) {
+    primaryFocus!.nextFocus();
+  }
+}
+
+/// An [Intent] bound to [PreviousFocusAction], which moves the focus to the
+/// previous focusable node in the focus traversal order.
+///
+/// See [FocusTraversalPolicy] for more information about focus traversal.
+class PreviousFocusIntent extends Intent {
+  /// Creates a const [PreviousFocusIntent] so subclasses can be const.
+  const PreviousFocusIntent();
+}
+
+/// An [Action] that moves the focus to the previous focusable node in the focus
+/// order.
+///
+/// This action is the default action registered for the [PreviousFocusIntent],
+/// and by default is bound to a combination of the [LogicalKeyboardKey.tab] key
+/// and the [LogicalKeyboardKey.shift] key in the [WidgetsApp].
+///
+/// See [FocusTraversalPolicy] for more information about focus traversal.
+class PreviousFocusAction extends Action<PreviousFocusIntent> {
+  @override
+  void invoke(PreviousFocusIntent intent) {
+    primaryFocus!.previousFocus();
+  }
+}
+
+/// An [Intent] that represents moving to the next focusable node in the given
+/// [direction].
+///
+/// This is the [Intent] bound by default to the [LogicalKeyboardKey.arrowUp],
+/// [LogicalKeyboardKey.arrowDown], [LogicalKeyboardKey.arrowLeft], and
+/// [LogicalKeyboardKey.arrowRight] keys in the [WidgetsApp], with the
+/// appropriate associated directions.
+///
+/// See [FocusTraversalPolicy] for more information about focus traversal.
+class DirectionalFocusIntent extends Intent {
+  /// Creates a [DirectionalFocusIntent] intending to move the focus in the
+  /// given [direction].
+  const DirectionalFocusIntent(this.direction, {this.ignoreTextFields = true})
+      : assert(ignoreTextFields != null);
+
+  /// The direction in which to look for the next focusable node when the
+  /// associated [DirectionalFocusAction] is invoked.
+  final TraversalDirection direction;
+
+  /// If true, then directional focus actions that occur within a text field
+  /// will not happen when the focus node which received the key is a text
+  /// field.
+  ///
+  /// Defaults to true.
+  final bool ignoreTextFields;
+}
+
+/// An [Action] that moves the focus to the focusable node in the direction
+/// configured by the associated [DirectionalFocusIntent.direction].
+///
+/// This is the [Action] associated with [DirectionalFocusIntent] and bound by
+/// default to the [LogicalKeyboardKey.arrowUp], [LogicalKeyboardKey.arrowDown],
+/// [LogicalKeyboardKey.arrowLeft], and [LogicalKeyboardKey.arrowRight] keys in
+/// the [WidgetsApp], with the appropriate associated directions.
+class DirectionalFocusAction extends Action<DirectionalFocusIntent> {
+  @override
+  void invoke(DirectionalFocusIntent intent) {
+    if (!intent.ignoreTextFields || primaryFocus!.context!.widget is! EditableText) {
+      primaryFocus!.focusInDirection(intent.direction);
+    }
+  }
+}
diff --git a/lib/src/widgets/form.dart b/lib/src/widgets/form.dart
new file mode 100644
index 0000000..0d64127
--- /dev/null
+++ b/lib/src/widgets/form.dart
@@ -0,0 +1,541 @@
+// Copyright 2014 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.
+
+import 'framework.dart';
+import 'navigator.dart';
+import 'will_pop_scope.dart';
+
+/// An optional container for grouping together multiple form field widgets
+/// (e.g. [TextField] widgets).
+///
+/// Each individual form field should be wrapped in a [FormField] widget, with
+/// the [Form] widget as a common ancestor of all of those. Call methods on
+/// [FormState] to save, reset, or validate each [FormField] that is a
+/// descendant of this [Form]. To obtain the [FormState], you may use [Form.of]
+/// with a context whose ancestor is the [Form], or pass a [GlobalKey] to the
+/// [Form] constructor and call [GlobalKey.currentState].
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+/// This example shows a [Form] with one [TextFormField] to enter an email
+/// address and an [ElevatedButton] to submit the form. A [GlobalKey] is used here
+/// to identify the [Form] and validate input.
+///
+/// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/form.png)
+///
+/// ```dart
+/// final _formKey = GlobalKey<FormState>();
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Form(
+///     key: _formKey,
+///     child: Column(
+///       crossAxisAlignment: CrossAxisAlignment.start,
+///       children: <Widget>[
+///         TextFormField(
+///           decoration: const InputDecoration(
+///             hintText: 'Enter your email',
+///           ),
+///           validator: (value) {
+///             if (value.isEmpty) {
+///               return 'Please enter some text';
+///             }
+///             return null;
+///           },
+///         ),
+///         Padding(
+///           padding: const EdgeInsets.symmetric(vertical: 16.0),
+///           child: ElevatedButton(
+///             onPressed: () {
+///               // Validate will return true if the form is valid, or false if
+///               // the form is invalid.
+///               if (_formKey.currentState.validate()) {
+///                 // Process data.
+///               }
+///             },
+///             child: Text('Submit'),
+///           ),
+///         ),
+///       ],
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [GlobalKey], a key that is unique across the entire app.
+///  * [FormField], a single form field widget that maintains the current state.
+///  * [TextFormField], a convenience widget that wraps a [TextField] widget in a [FormField].
+class Form extends StatefulWidget {
+  /// Creates a container for form fields.
+  ///
+  /// The [child] argument must not be null.
+  const Form({
+    Key? key,
+    required this.child,
+    @Deprecated(
+      'Use autoValidateMode parameter which provides more specific '
+      'behavior related to auto validation. '
+      'This feature was deprecated after v1.19.0.'
+    )
+    this.autovalidate = false,
+    this.onWillPop,
+    this.onChanged,
+    AutovalidateMode? autovalidateMode,
+  }) : assert(child != null),
+       assert(autovalidate != null),
+       assert(
+         autovalidate == false ||
+         autovalidate == true && autovalidateMode == null,
+         'autovalidate and autovalidateMode should not be used together.'
+       ),
+       autovalidateMode = autovalidateMode ??
+         (autovalidate ? AutovalidateMode.always : AutovalidateMode.disabled),
+       super(key: key);
+
+  /// Returns the closest [FormState] which encloses the given context.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// FormState form = Form.of(context);
+  /// form.save();
+  /// ```
+  static FormState? of(BuildContext context) {
+    final _FormScope? scope = context.dependOnInheritedWidgetOfExactType<_FormScope>();
+    return scope?._formState;
+  }
+
+  /// The widget below this widget in the tree.
+  ///
+  /// This is the root of the widget hierarchy that contains this form.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// Enables the form to veto attempts by the user to dismiss the [ModalRoute]
+  /// that contains the form.
+  ///
+  /// If the callback returns a Future that resolves to false, the form's route
+  /// will not be popped.
+  ///
+  /// See also:
+  ///
+  ///  * [WillPopScope], another widget that provides a way to intercept the
+  ///    back button.
+  final WillPopCallback? onWillPop;
+
+  /// Called when one of the form fields changes.
+  ///
+  /// In addition to this callback being invoked, all the form fields themselves
+  /// will rebuild.
+  final VoidCallback? onChanged;
+
+  /// Used to enable/disable form fields auto validation and update their error
+  /// text.
+  ///
+  /// {@macro flutter.widgets.FormField.autovalidateMode}
+  final AutovalidateMode autovalidateMode;
+
+  /// Used to enable/disable form fields auto validation and update their error
+  /// text.
+  @Deprecated(
+    'Use autoValidateMode parameter which provides more specific '
+    'behavior related to auto validation. '
+    'This feature was deprecated after v1.19.0.'
+  )
+  final bool autovalidate;
+
+  @override
+  FormState createState() => FormState();
+}
+
+/// State associated with a [Form] widget.
+///
+/// A [FormState] object can be used to [save], [reset], and [validate] every
+/// [FormField] that is a descendant of the associated [Form].
+///
+/// Typically obtained via [Form.of].
+class FormState extends State<Form> {
+  int _generation = 0;
+  bool _hasInteractedByUser = false;
+  final Set<FormFieldState<dynamic>> _fields = <FormFieldState<dynamic>>{};
+
+  // Called when a form field has changed. This will cause all form fields
+  // to rebuild, useful if form fields have interdependencies.
+  void _fieldDidChange() {
+    if (widget.onChanged != null)
+      widget.onChanged!();
+
+
+    _hasInteractedByUser = _fields
+        .any((FormFieldState<dynamic> field) => field._hasInteractedByUser);
+    _forceRebuild();
+  }
+
+  void _forceRebuild() {
+    setState(() {
+      ++_generation;
+    });
+  }
+
+  void _register(FormFieldState<dynamic> field) {
+    _fields.add(field);
+  }
+
+  void _unregister(FormFieldState<dynamic> field) {
+    _fields.remove(field);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    switch (widget.autovalidateMode) {
+      case AutovalidateMode.always:
+        _validate();
+        break;
+      case AutovalidateMode.onUserInteraction:
+        if (_hasInteractedByUser) {
+          _validate();
+        }
+        break;
+      case AutovalidateMode.disabled:
+        break;
+    }
+
+    return WillPopScope(
+      onWillPop: widget.onWillPop,
+      child: _FormScope(
+        formState: this,
+        generation: _generation,
+        child: widget.child,
+      ),
+    );
+  }
+
+  /// Saves every [FormField] that is a descendant of this [Form].
+  void save() {
+    for (final FormFieldState<dynamic> field in _fields)
+      field.save();
+  }
+
+  /// Resets every [FormField] that is a descendant of this [Form] back to its
+  /// [FormField.initialValue].
+  ///
+  /// The [Form.onChanged] callback will be called.
+  ///
+  /// If the form's [Form.autovalidateMode] property is [AutovalidateMode.always],
+  /// the fields will all be revalidated after being reset.
+  void reset() {
+    for (final FormFieldState<dynamic> field in _fields)
+      field.reset();
+    _hasInteractedByUser = false;
+    _fieldDidChange();
+  }
+
+  /// Validates every [FormField] that is a descendant of this [Form], and
+  /// returns true if there are no errors.
+  ///
+  /// The form will rebuild to report the results.
+  bool validate() {
+    _hasInteractedByUser = true;
+    _forceRebuild();
+    return _validate();
+  }
+
+  bool _validate() {
+    bool hasError = false;
+    for (final FormFieldState<dynamic> field in _fields)
+      hasError = !field.validate() || hasError;
+    return !hasError;
+  }
+}
+
+class _FormScope extends InheritedWidget {
+  const _FormScope({
+    Key? key,
+    required Widget child,
+    required FormState formState,
+    required int generation,
+  }) : _formState = formState,
+       _generation = generation,
+       super(key: key, child: child);
+
+  final FormState _formState;
+
+  /// Incremented every time a form field has changed. This lets us know when
+  /// to rebuild the form.
+  final int _generation;
+
+  /// The [Form] associated with this widget.
+  Form get form => _formState.widget;
+
+  @override
+  bool updateShouldNotify(_FormScope old) => _generation != old._generation;
+}
+
+/// Signature for validating a form field.
+///
+/// Returns an error string to display if the input is invalid, or null
+/// otherwise.
+///
+/// Used by [FormField.validator].
+typedef FormFieldValidator<T> = String? Function(T? value);
+
+/// Signature for being notified when a form field changes value.
+///
+/// Used by [FormField.onSaved].
+typedef FormFieldSetter<T> = void Function(T? newValue);
+
+/// Signature for building the widget representing the form field.
+///
+/// Used by [FormField.builder].
+typedef FormFieldBuilder<T> = Widget Function(FormFieldState<T> field);
+
+/// A single form field.
+///
+/// This widget maintains the current state of the form field, so that updates
+/// and validation errors are visually reflected in the UI.
+///
+/// When used inside a [Form], you can use methods on [FormState] to query or
+/// manipulate the form data as a whole. For example, calling [FormState.save]
+/// will invoke each [FormField]'s [onSaved] callback in turn.
+///
+/// Use a [GlobalKey] with [FormField] if you want to retrieve its current
+/// state, for example if you want one form field to depend on another.
+///
+/// A [Form] ancestor is not required. The [Form] simply makes it easier to
+/// save, reset, or validate multiple fields at once. To use without a [Form],
+/// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to
+/// save or reset the form field.
+///
+/// See also:
+///
+///  * [Form], which is the widget that aggregates the form fields.
+///  * [TextField], which is a commonly used form field for entering text.
+class FormField<T> extends StatefulWidget {
+  /// Creates a single form field.
+  ///
+  /// The [builder] argument must not be null.
+  const FormField({
+    Key? key,
+    required this.builder,
+    this.onSaved,
+    this.validator,
+    this.initialValue,
+    @Deprecated(
+      'Use autoValidateMode parameter which provides more specific '
+      'behavior related to auto validation. '
+      'This feature was deprecated after v1.19.0.'
+    )
+    this.autovalidate = false,
+    this.enabled = true,
+    AutovalidateMode? autovalidateMode,
+  }) : assert(builder != null),
+       assert(
+         autovalidate == false ||
+         autovalidate == true && autovalidateMode == null,
+         'autovalidate and autovalidateMode should not be used together.'
+       ),
+        autovalidateMode = autovalidateMode ??
+          (autovalidate ? AutovalidateMode.always : AutovalidateMode.disabled),
+       super(key: key);
+
+  /// An optional method to call with the final value when the form is saved via
+  /// [FormState.save].
+  final FormFieldSetter<T>? onSaved;
+
+  /// An optional method that validates an input. Returns an error string to
+  /// display if the input is invalid, or null otherwise.
+  ///
+  /// The returned value is exposed by the [FormFieldState.errorText] property.
+  /// The [TextFormField] uses this to override the [InputDecoration.errorText]
+  /// value.
+  ///
+  /// Alternating between error and normal state can cause the height of the
+  /// [TextFormField] to change if no other subtext decoration is set on the
+  /// field. To create a field whose height is fixed regardless of whether or
+  /// not an error is displayed, either wrap the  [TextFormField] in a fixed
+  /// height parent like [SizedBox], or set the [InputDecoration.helperText]
+  /// parameter to a space.
+  final FormFieldValidator<T>? validator;
+
+  /// Function that returns the widget representing this form field. It is
+  /// passed the form field state as input, containing the current value and
+  /// validation state of this field.
+  final FormFieldBuilder<T> builder;
+
+  /// An optional value to initialize the form field to, or null otherwise.
+  final T? initialValue;
+
+  /// Whether the form is able to receive user input.
+  ///
+  /// Defaults to true. If [autovalidateMode] is not [AutovalidateMode.disabled],
+  /// the field will be auto validated. Likewise, if this field is false, the widget
+  /// will not be validated regardless of [autovalidateMode].
+  final bool enabled;
+
+  /// Used to enable/disable this form field auto validation and update its
+  /// error text.
+  ///
+  /// {@template flutter.widgets.FormField.autovalidateMode}
+  /// If [AutovalidateMode.onUserInteraction] this form field will only
+  /// auto-validate after its content changes, if [AutovalidateMode.always] it
+  /// will auto validate even without user interaction and
+  /// if [AutovalidateMode.disabled] the auto validation will be disabled.
+  ///
+  /// Defaults to [AutovalidateMode.disabled] if `autovalidate` is false which
+  /// means no auto validation will occur. If `autovalidate` is true then this
+  /// is set to [AutovalidateMode.always] for backward compatibility.
+  /// {@endtemplate}
+  final AutovalidateMode autovalidateMode;
+
+  /// Used to enable/disable auto validation and update their error
+  /// text.
+  @Deprecated(
+    'Use autoValidateMode parameter which provides more specific '
+    'behavior related to auto validation. '
+    'This feature was deprecated after v1.19.0.'
+  )
+  final bool autovalidate;
+
+  @override
+  FormFieldState<T> createState() => FormFieldState<T>();
+}
+
+/// The current state of a [FormField]. Passed to the [FormFieldBuilder] method
+/// for use in constructing the form field's widget.
+class FormFieldState<T> extends State<FormField<T>> {
+  T? _value;
+  String? _errorText;
+  bool _hasInteractedByUser = false;
+
+  /// The current value of the form field.
+  T? get value => _value;
+
+  /// The current validation error returned by the [FormField.validator]
+  /// callback, or null if no errors have been triggered. This only updates when
+  /// [validate] is called.
+  String? get errorText => _errorText;
+
+  /// True if this field has any validation errors.
+  bool get hasError => _errorText != null;
+
+  /// True if the current value is valid.
+  ///
+  /// This will not set [errorText] or [hasError] and it will not update
+  /// error display.
+  ///
+  /// See also:
+  ///
+  ///  * [validate], which may update [errorText] and [hasError].
+  bool get isValid => widget.validator?.call(_value) == null;
+
+  /// Calls the [FormField]'s onSaved method with the current value.
+  void save() {
+    if (widget.onSaved != null)
+      widget.onSaved!(value);
+  }
+
+  /// Resets the field to its initial value.
+  void reset() {
+    setState(() {
+      _value = widget.initialValue;
+      _hasInteractedByUser = false;
+      _errorText = null;
+    });
+    Form.of(context)?._fieldDidChange();
+  }
+
+  /// Calls [FormField.validator] to set the [errorText]. Returns true if there
+  /// were no errors.
+  ///
+  /// See also:
+  ///
+  ///  * [isValid], which passively gets the validity without setting
+  ///    [errorText] or [hasError].
+  bool validate() {
+    setState(() {
+      _validate();
+    });
+    return !hasError;
+  }
+
+  void _validate() {
+    if (widget.validator != null)
+      _errorText = widget.validator!(_value);
+  }
+
+  /// Updates this field's state to the new value. Useful for responding to
+  /// child widget changes, e.g. [Slider]'s [Slider.onChanged] argument.
+  ///
+  /// Triggers the [Form.onChanged] callback and, if [Form.autovalidateMode] is
+  /// [AutovalidateMode.always] or [AutovalidateMode.onUserInteraction],
+  /// revalidates all the fields of the form.
+  void didChange(T? value) {
+    setState(() {
+      _value = value;
+      _hasInteractedByUser = true;
+    });
+    Form.of(context)?._fieldDidChange();
+  }
+
+  /// Sets the value associated with this form field.
+  ///
+  /// This method should only be called by subclasses that need to update
+  /// the form field value due to state changes identified during the widget
+  /// build phase, when calling `setState` is prohibited. In all other cases,
+  /// the value should be set by a call to [didChange], which ensures that
+  /// `setState` is called.
+  @protected
+  void setValue(T? value) {
+    _value = value;
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _value = widget.initialValue;
+  }
+
+  @override
+  void deactivate() {
+    Form.of(context)?._unregister(this);
+    super.deactivate();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (widget.enabled) {
+      switch (widget.autovalidateMode) {
+        case AutovalidateMode.always:
+          _validate();
+          break;
+        case AutovalidateMode.onUserInteraction:
+          if (_hasInteractedByUser) {
+            _validate();
+          }
+          break;
+        case AutovalidateMode.disabled:
+          break;
+      }
+    }
+    Form.of(context)?._register(this);
+    return widget.builder(this);
+  }
+}
+
+/// Used to configure the auto validation of [FormField] and [Form] widgets.
+enum AutovalidateMode {
+  /// No auto validation will occur.
+  disabled,
+
+  /// Used to auto-validate [Form] and [FormField] even without user interaction.
+  always,
+
+  /// Used to auto-validate [Form] and [FormField] only after each user
+  /// interaction.
+  onUserInteraction,
+}
diff --git a/lib/src/widgets/framework.dart b/lib/src/widgets/framework.dart
new file mode 100644
index 0000000..ef79a91
--- /dev/null
+++ b/lib/src/widgets/framework.dart
@@ -0,0 +1,6392 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:developer';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+import 'debug.dart';
+import 'focus_manager.dart';
+import 'inherited_model.dart';
+
+export 'package:flute/ui.dart' show hashValues, hashList;
+
+export 'package:flute/foundation.dart' show
+  factory,
+  immutable,
+  mustCallSuper,
+  optionalTypeArgs,
+  protected,
+  required,
+  visibleForTesting;
+export 'package:flute/foundation.dart' show FlutterError, ErrorSummary, ErrorDescription, ErrorHint, debugPrint, debugPrintStack;
+export 'package:flute/foundation.dart' show VoidCallback, ValueChanged, ValueGetter, ValueSetter;
+export 'package:flute/foundation.dart' show DiagnosticsNode, DiagnosticLevel;
+export 'package:flute/foundation.dart' show Key, LocalKey, ValueKey;
+export 'package:flute/rendering.dart' show RenderObject, RenderBox, debugDumpRenderTree, debugDumpLayerTree;
+
+// Examples can assume:
+// // @dart = 2.9
+// BuildContext context;
+// void setState(VoidCallback fn) { }
+// abstract class RenderFrogJar extends RenderObject { }
+// abstract class FrogJar extends RenderObjectWidget { }
+// abstract class FrogJarParentData extends ParentData { Size size; }
+
+// KEYS
+
+/// A key that is only equal to itself.
+///
+/// This cannot be created with a const constructor because that implies that
+/// all instantiated keys would be the same instance and therefore not be unique.
+class UniqueKey extends LocalKey {
+  /// Creates a key that is equal only to itself.
+  ///
+  /// The key cannot be created with a const constructor because that implies
+  /// that all instantiated keys would be the same instance and therefore not
+  /// be unique.
+  // ignore: prefer_const_constructors_in_immutables , never use const for this class
+  UniqueKey();
+
+  @override
+  String toString() => '[#${shortHash(this)}]';
+}
+
+/// A key that takes its identity from the object used as its value.
+///
+/// Used to tie the identity of a widget to the identity of an object used to
+/// generate that widget.
+///
+/// See also:
+///
+///  * [Key], the base class for all keys.
+///  * The discussion at [Widget.key] for more information about how widgets use
+///    keys.
+class ObjectKey extends LocalKey {
+  /// Creates a key that uses [identical] on [value] for its [operator==].
+  const ObjectKey(this.value);
+
+  /// The object whose identity is used by this key's [operator==].
+  final Object? value;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is ObjectKey
+        && identical(other.value, value);
+  }
+
+  @override
+  int get hashCode => hashValues(runtimeType, identityHashCode(value));
+
+  @override
+  String toString() {
+    if (runtimeType == ObjectKey)
+      return '[${describeIdentity(value)}]';
+    return '[${objectRuntimeType(this, 'ObjectKey')} ${describeIdentity(value)}]';
+  }
+}
+
+/// A key that is unique across the entire app.
+///
+/// Global keys uniquely identify elements. Global keys provide access to other
+/// objects that are associated with those elements, such as [BuildContext].
+/// For [StatefulWidget]s, global keys also provide access to [State].
+///
+/// Widgets that have global keys reparent their subtrees when they are moved
+/// from one location in the tree to another location in the tree. In order to
+/// reparent its subtree, a widget must arrive at its new location in the tree
+/// in the same animation frame in which it was removed from its old location in
+/// the tree.
+///
+/// Reparenting an [Element] using a global key is relatively expensive, as
+/// this operation will trigger a call to [State.deactivate] on the associated
+/// [State] and all of its descendants; then force all widgets that depends
+/// on an [InheritedWidget] to rebuild.
+///
+/// If you don't need any of the features listed above, consider using a [Key],
+/// [ValueKey], [ObjectKey], or [UniqueKey] instead.
+///
+/// You cannot simultaneously include two widgets in the tree with the same
+/// global key. Attempting to do so will assert at runtime.
+///
+/// ## Pitfalls
+/// GlobalKeys should not be re-created on every build. They should usually be
+/// long-lived objects owned by a [State] object, for example.
+///
+/// Creating a new GlobalKey on every build will throw away the state of the
+/// subtree associated with the old key and create a new fresh subtree for the
+/// new key. Besides harming performance, this can also cause unexpected
+/// behavior in widgets in the subtree. For example, a [GestureDetector] in the
+/// subtree will be unable to track ongoing gestures since it will be recreated
+/// on each build.
+///
+/// Instead, a good practice is to let a State object own the GlobalKey, and
+/// instantiate it outside the build method, such as in [State.initState].
+///
+/// See also:
+///
+///  * The discussion at [Widget.key] for more information about how widgets use
+///    keys.
+@optionalTypeArgs
+abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
+  /// Creates a [LabeledGlobalKey], which is a [GlobalKey] with a label used for
+  /// debugging.
+  ///
+  /// The label is purely for debugging and not used for comparing the identity
+  /// of the key.
+  factory GlobalKey({ String? debugLabel }) => LabeledGlobalKey<T>(debugLabel);
+
+  /// Creates a global key without a label.
+  ///
+  /// Used by subclasses because the factory constructor shadows the implicit
+  /// constructor.
+  const GlobalKey.constructor() : super.empty();
+
+  static final Map<GlobalKey, Element> _registry = <GlobalKey, Element>{};
+  static final Set<Element> _debugIllFatedElements = HashSet<Element>();
+  // This map keeps track which child reserves the global key with the parent.
+  // Parent, child -> global key.
+  // This provides us a way to remove old reservation while parent rebuilds the
+  // child in the same slot.
+  static final Map<Element, Map<Element, GlobalKey>> _debugReservations = <Element, Map<Element, GlobalKey>>{};
+
+  static void _debugRemoveReservationFor(Element parent, Element child) {
+    assert(() {
+      assert(parent != null);
+      assert(child != null);
+      _debugReservations[parent]?.remove(child);
+      return true;
+    }());
+  }
+
+  void _register(Element element) {
+    assert(() {
+      if (_registry.containsKey(this)) {
+        assert(element.widget != null);
+        final Element oldElement = _registry[this]!;
+        assert(oldElement.widget != null);
+        assert(element.widget.runtimeType != oldElement.widget.runtimeType);
+        _debugIllFatedElements.add(oldElement);
+      }
+      return true;
+    }());
+    _registry[this] = element;
+  }
+
+  void _unregister(Element element) {
+    assert(() {
+      if (_registry.containsKey(this) && _registry[this] != element) {
+        assert(element.widget != null);
+        final Element oldElement = _registry[this]!;
+        assert(oldElement.widget != null);
+        assert(element.widget.runtimeType != oldElement.widget.runtimeType);
+      }
+      return true;
+    }());
+    if (_registry[this] == element)
+      _registry.remove(this);
+  }
+
+  void _debugReserveFor(Element parent, Element child) {
+    assert(() {
+      assert(parent != null);
+      assert(child != null);
+      _debugReservations[parent] ??= <Element, GlobalKey>{};
+      _debugReservations[parent]![child] = this;
+      return true;
+    }());
+  }
+
+  static void _debugVerifyGlobalKeyReservation() {
+    assert(() {
+      final Map<GlobalKey, Element> keyToParent = <GlobalKey, Element>{};
+      _debugReservations.forEach((Element parent, Map<Element, GlobalKey> childToKey) {
+        // We ignore parent that are unmounted or detached.
+        if (parent._lifecycleState == _ElementLifecycle.defunct || parent.renderObject?.attached == false)
+          return;
+        childToKey.forEach((Element child, GlobalKey key) {
+          // If parent = null, the node is deactivated by its parent and is
+          // not re-attached to other part of the tree. We should ignore this
+          // node.
+          if (child._parent == null)
+            return;
+          // It is possible the same key registers to the same parent twice
+          // with different children. That is illegal, but it is not in the
+          // scope of this check. Such error will be detected in
+          // _debugVerifyIllFatedPopulation or
+          // _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans.
+          if (keyToParent.containsKey(key) && keyToParent[key] != parent) {
+            // We have duplication reservations for the same global key.
+            final Element older = keyToParent[key]!;
+            final Element newer = parent;
+            final FlutterError error;
+            if (older.toString() != newer.toString()) {
+              error = FlutterError.fromParts(<DiagnosticsNode>[
+                ErrorSummary('Multiple widgets used the same GlobalKey.'),
+                ErrorDescription(
+                  'The key $key was used by multiple widgets. The parents of those widgets were:\n'
+                    '- ${older.toString()}\n'
+                    '- ${newer.toString()}\n'
+                    'A GlobalKey can only be specified on one widget at a time in the widget tree.'
+                ),
+              ]);
+            } else {
+              error = FlutterError.fromParts(<DiagnosticsNode>[
+                ErrorSummary('Multiple widgets used the same GlobalKey.'),
+                ErrorDescription(
+                  'The key $key was used by multiple widgets. The parents of those widgets were '
+                    'different widgets that both had the following description:\n'
+                    '  ${parent.toString()}\n'
+                    'A GlobalKey can only be specified on one widget at a time in the widget tree.'
+                ),
+              ]);
+            }
+            // Fix the tree by removing the duplicated child from one of its
+            // parents to resolve the duplicated key issue. This allows us to
+            // tear down the tree during testing without producing additional
+            // misleading exceptions.
+            if (child._parent != older) {
+              older.visitChildren((Element currentChild) {
+                if (currentChild == child)
+                  older.forgetChild(child);
+              });
+            }
+            if (child._parent != newer) {
+              newer.visitChildren((Element currentChild) {
+                if (currentChild == child)
+                  newer.forgetChild(child);
+              });
+            }
+            throw error;
+          } else {
+            keyToParent[key] = parent;
+          }
+        });
+      });
+      _debugReservations.clear();
+      return true;
+    }());
+  }
+
+  static void _debugVerifyIllFatedPopulation() {
+    assert(() {
+      Map<GlobalKey, Set<Element>>? duplicates;
+      for (final Element element in _debugIllFatedElements) {
+        if (element._lifecycleState != _ElementLifecycle.defunct) {
+          assert(element != null);
+          assert(element.widget != null);
+          assert(element.widget.key != null);
+          final GlobalKey key = element.widget.key! as GlobalKey;
+          assert(_registry.containsKey(key));
+          duplicates ??= <GlobalKey, Set<Element>>{};
+          // Uses ordered set to produce consistent error message.
+          final Set<Element> elements = duplicates.putIfAbsent(key, () => LinkedHashSet<Element>());
+          elements.add(element);
+          elements.add(_registry[key]!);
+        }
+      }
+      _debugIllFatedElements.clear();
+      if (duplicates != null) {
+        final List<DiagnosticsNode> information = <DiagnosticsNode>[];
+        information.add(ErrorSummary('Multiple widgets used the same GlobalKey.'));
+        for (final GlobalKey key in duplicates.keys) {
+          final Set<Element> elements = duplicates[key]!;
+          // TODO(jacobr): this will omit the '- ' before each widget name and
+          // use the more standard whitespace style instead. Please let me know
+          // if the '- ' style is a feature we want to maintain and we can add
+          // another tree style that supports it. I also see '* ' in some places
+          // so it would be nice to unify and normalize.
+          information.add(Element.describeElements('The key $key was used by ${elements.length} widgets', elements));
+        }
+        information.add(ErrorDescription('A GlobalKey can only be specified on one widget at a time in the widget tree.'));
+        throw FlutterError.fromParts(information);
+      }
+      return true;
+    }());
+  }
+
+  Element? get _currentElement => _registry[this];
+
+  /// The build context in which the widget with this key builds.
+  ///
+  /// The current context is null if there is no widget in the tree that matches
+  /// this global key.
+  BuildContext? get currentContext => _currentElement;
+
+  /// The widget in the tree that currently has this global key.
+  ///
+  /// The current widget is null if there is no widget in the tree that matches
+  /// this global key.
+  Widget? get currentWidget => _currentElement?.widget;
+
+  /// The [State] for the widget in the tree that currently has this global key.
+  ///
+  /// The current state is null if (1) there is no widget in the tree that
+  /// matches this global key, (2) that widget is not a [StatefulWidget], or the
+  /// associated [State] object is not a subtype of `T`.
+  T? get currentState {
+    final Element? element = _currentElement;
+    if (element is StatefulElement) {
+      final StatefulElement statefulElement = element;
+      final State state = statefulElement.state;
+      if (state is T)
+        return state;
+    }
+    return null;
+  }
+}
+
+/// A global key with a debugging label.
+///
+/// The debug label is useful for documentation and for debugging. The label
+/// does not affect the key's identity.
+@optionalTypeArgs
+class LabeledGlobalKey<T extends State<StatefulWidget>> extends GlobalKey<T> {
+  /// Creates a global key with a debugging label.
+  ///
+  /// The label does not affect the key's identity.
+  // ignore: prefer_const_constructors_in_immutables , never use const for this class
+  LabeledGlobalKey(this._debugLabel) : super.constructor();
+
+  final String? _debugLabel;
+
+  @override
+  String toString() {
+    final String label = _debugLabel != null ? ' $_debugLabel' : '';
+    if (runtimeType == LabeledGlobalKey)
+      return '[GlobalKey#${shortHash(this)}$label]';
+    return '[${describeIdentity(this)}$label]';
+  }
+}
+
+/// A global key that takes its identity from the object used as its value.
+///
+/// Used to tie the identity of a widget to the identity of an object used to
+/// generate that widget.
+///
+/// If the object is not private, then it is possible that collisions will occur
+/// where independent widgets will reuse the same object as their
+/// [GlobalObjectKey] value in a different part of the tree, leading to a global
+/// key conflict. To avoid this problem, create a private [GlobalObjectKey]
+/// subclass, as in:
+///
+/// ```dart
+/// class _MyKey extends GlobalObjectKey {
+///   const _MyKey(Object value) : super(value);
+/// }
+/// ```
+///
+/// Since the [runtimeType] of the key is part of its identity, this will
+/// prevent clashes with other [GlobalObjectKey]s even if they have the same
+/// value.
+///
+/// Any [GlobalObjectKey] created for the same value will match.
+@optionalTypeArgs
+class GlobalObjectKey<T extends State<StatefulWidget>> extends GlobalKey<T> {
+  /// Creates a global key that uses [identical] on [value] for its [operator==].
+  const GlobalObjectKey(this.value) : super.constructor();
+
+  /// The object whose identity is used by this key's [operator==].
+  final Object value;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is GlobalObjectKey<T>
+        && identical(other.value, value);
+  }
+
+  @override
+  int get hashCode => identityHashCode(value);
+
+  @override
+  String toString() {
+    String selfType = objectRuntimeType(this, 'GlobalObjectKey');
+    // The runtimeType string of a GlobalObjectKey() returns 'GlobalObjectKey<State<StatefulWidget>>'
+    // because GlobalObjectKey is instantiated to its bounds. To avoid cluttering the output
+    // we remove the suffix.
+    const String suffix = '<State<StatefulWidget>>';
+    if (selfType.endsWith(suffix)) {
+      selfType = selfType.substring(0, selfType.length - suffix.length);
+    }
+    return '[$selfType ${describeIdentity(value)}]';
+  }
+}
+
+/// This class is a work-around for the "is" operator not accepting a variable value as its right operand.
+///
+/// This class is deprecated. It will be deleted soon.
+// TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+@Deprecated(
+  'TypeMatcher has been deprecated because it is no longer used in framework(only in deprecated methods). '
+  'This feature was deprecated after v1.12.1.'
+)
+@optionalTypeArgs
+class TypeMatcher<T> {
+  /// Creates a type matcher for the given type parameter.
+  const TypeMatcher();
+
+  /// Returns true if the given object is of type `T`.
+  bool check(dynamic object) => object is T;
+}
+
+/// Describes the configuration for an [Element].
+///
+/// Widgets are the central class hierarchy in the Flutter framework. A widget
+/// is an immutable description of part of a user interface. Widgets can be
+/// inflated into elements, which manage the underlying render tree.
+///
+/// Widgets themselves have no mutable state (all their fields must be final).
+/// If you wish to associate mutable state with a widget, consider using a
+/// [StatefulWidget], which creates a [State] object (via
+/// [StatefulWidget.createState]) whenever it is inflated into an element and
+/// incorporated into the tree.
+///
+/// A given widget can be included in the tree zero or more times. In particular
+/// a given widget can be placed in the tree multiple times. Each time a widget
+/// is placed in the tree, it is inflated into an [Element], which means a
+/// widget that is incorporated into the tree multiple times will be inflated
+/// multiple times.
+///
+/// The [key] property controls how one widget replaces another widget in the
+/// tree. If the [runtimeType] and [key] properties of the two widgets are
+/// [operator==], respectively, then the new widget replaces the old widget by
+/// updating the underlying element (i.e., by calling [Element.update] with the
+/// new widget). Otherwise, the old element is removed from the tree, the new
+/// widget is inflated into an element, and the new element is inserted into the
+/// tree.
+///
+/// See also:
+///
+///  * [StatefulWidget] and [State], for widgets that can build differently
+///    several times over their lifetime.
+///  * [InheritedWidget], for widgets that introduce ambient state that can
+///    be read by descendant widgets.
+///  * [StatelessWidget], for widgets that always build the same way given a
+///    particular configuration and ambient state.
+@immutable
+abstract class Widget extends DiagnosticableTree {
+  /// Initializes [key] for subclasses.
+  const Widget({ this.key });
+
+  /// Controls how one widget replaces another widget in the tree.
+  ///
+  /// If the [runtimeType] and [key] properties of the two widgets are
+  /// [operator==], respectively, then the new widget replaces the old widget by
+  /// updating the underlying element (i.e., by calling [Element.update] with the
+  /// new widget). Otherwise, the old element is removed from the tree, the new
+  /// widget is inflated into an element, and the new element is inserted into the
+  /// tree.
+  ///
+  /// In addition, using a [GlobalKey] as the widget's [key] allows the element
+  /// to be moved around the tree (changing parent) without losing state. When a
+  /// new widget is found (its key and type do not match a previous widget in
+  /// the same location), but there was a widget with that same global key
+  /// elsewhere in the tree in the previous frame, then that widget's element is
+  /// moved to the new location.
+  ///
+  /// Generally, a widget that is the only child of another widget does not need
+  /// an explicit key.
+  ///
+  /// See also:
+  ///
+  ///  * The discussions at [Key] and [GlobalKey].
+  final Key? key;
+
+  /// Inflates this configuration to a concrete instance.
+  ///
+  /// A given widget can be included in the tree zero or more times. In particular
+  /// a given widget can be placed in the tree multiple times. Each time a widget
+  /// is placed in the tree, it is inflated into an [Element], which means a
+  /// widget that is incorporated into the tree multiple times will be inflated
+  /// multiple times.
+  @protected
+  @factory
+  Element createElement();
+
+  /// A short, textual description of this widget.
+  @override
+  String toStringShort() {
+    final String type = objectRuntimeType(this, 'Widget');
+    return key == null ? type : '$type-$key';
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
+  }
+
+  @override
+  @nonVirtual
+  bool operator ==(Object other) => super == other;
+
+  @override
+  @nonVirtual
+  int get hashCode => super.hashCode;
+
+  /// Whether the `newWidget` can be used to update an [Element] that currently
+  /// has the `oldWidget` as its configuration.
+  ///
+  /// An element that uses a given widget as its configuration can be updated to
+  /// use another widget as its configuration if, and only if, the two widgets
+  /// have [runtimeType] and [key] properties that are [operator==].
+  ///
+  /// If the widgets have no key (their key is null), then they are considered a
+  /// match if they have the same type, even if their children are completely
+  /// different.
+  static bool canUpdate(Widget oldWidget, Widget newWidget) {
+    return oldWidget.runtimeType == newWidget.runtimeType
+        && oldWidget.key == newWidget.key;
+  }
+
+  // Return a numeric encoding of the specific `Widget` concrete subtype.
+  // This is used in `Element.updateChild` to determine if a hot reload modified the
+  // superclass of a mounted element's configuration. The encoding of each `Widget`
+  // must match the corresponding `Element` encoding in `Element._debugConcreteSubtype`.
+  static int _debugConcreteSubtype(Widget widget) {
+    return widget is StatefulWidget ? 1 :
+           widget is StatelessWidget ? 2 :
+           0;
+    }
+}
+
+/// A widget that does not require mutable state.
+///
+/// A stateless widget is a widget that describes part of the user interface by
+/// building a constellation of other widgets that describe the user interface
+/// more concretely. The building process continues recursively until the
+/// description of the user interface is fully concrete (e.g., consists
+/// entirely of [RenderObjectWidget]s, which describe concrete [RenderObject]s).
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=wE7khGHVkYY}
+///
+/// Stateless widget are useful when the part of the user interface you are
+/// describing does not depend on anything other than the configuration
+/// information in the object itself and the [BuildContext] in which the widget
+/// is inflated. For compositions that can change dynamically, e.g. due to
+/// having an internal clock-driven state, or depending on some system state,
+/// consider using [StatefulWidget].
+///
+/// ## Performance considerations
+///
+/// The [build] method of a stateless widget is typically only called in three
+/// situations: the first time the widget is inserted in the tree, when the
+/// widget's parent changes its configuration, and when an [InheritedWidget] it
+/// depends on changes.
+///
+/// If a widget's parent will regularly change the widget's configuration, or if
+/// it depends on inherited widgets that frequently change, then it is important
+/// to optimize the performance of the [build] method to maintain a fluid
+/// rendering performance.
+///
+/// There are several techniques one can use to minimize the impact of
+/// rebuilding a stateless widget:
+///
+///  * Minimize the number of nodes transitively created by the build method and
+///    any widgets it creates. For example, instead of an elaborate arrangement
+///    of [Row]s, [Column]s, [Padding]s, and [SizedBox]es to position a single
+///    child in a particularly fancy manner, consider using just an [Align] or a
+///    [CustomSingleChildLayout]. Instead of an intricate layering of multiple
+///    [Container]s and with [Decoration]s to draw just the right graphical
+///    effect, consider a single [CustomPaint] widget.
+///
+///  * Use `const` widgets where possible, and provide a `const` constructor for
+///    the widget so that users of the widget can also do so.
+///
+///  * Consider refactoring the stateless widget into a stateful widget so that
+///    it can use some of the techniques described at [StatefulWidget], such as
+///    caching common parts of subtrees and using [GlobalKey]s when changing the
+///    tree structure.
+///
+///  * If the widget is likely to get rebuilt frequently due to the use of
+///    [InheritedWidget]s, consider refactoring the stateless widget into
+///    multiple widgets, with the parts of the tree that change being pushed to
+///    the leaves. For example instead of building a tree with four widgets, the
+///    inner-most widget depending on the [Theme], consider factoring out the
+///    part of the build function that builds the inner-most widget into its own
+///    widget, so that only the inner-most widget needs to be rebuilt when the
+///    theme changes.
+///
+/// {@tool snippet}
+///
+/// The following is a skeleton of a stateless widget subclass called `GreenFrog`.
+///
+/// Normally, widgets have more constructor arguments, each of which corresponds
+/// to a `final` property.
+///
+/// ```dart
+/// class GreenFrog extends StatelessWidget {
+///   const GreenFrog({ Key key }) : super(key: key);
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Container(color: const Color(0xFF2DBD3A));
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+///
+/// This next example shows the more generic widget `Frog` which can be given
+/// a color and a child:
+///
+/// ```dart
+/// class Frog extends StatelessWidget {
+///   const Frog({
+///     Key key,
+///     this.color = const Color(0xFF2DBD3A),
+///     this.child,
+///   }) : super(key: key);
+///
+///   final Color color;
+///   final Widget child;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Container(color: color, child: child);
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// By convention, widget constructors only use named arguments. Named arguments
+/// can be marked as required using [@required]. Also by convention, the first
+/// argument is [key], and the last argument is `child`, `children`, or the
+/// equivalent.
+///
+/// See also:
+///
+///  * [StatefulWidget] and [State], for widgets that can build differently
+///    several times over their lifetime.
+///  * [InheritedWidget], for widgets that introduce ambient state that can
+///    be read by descendant widgets.
+abstract class StatelessWidget extends Widget {
+  /// Initializes [key] for subclasses.
+  const StatelessWidget({ Key? key }) : super(key: key);
+
+  /// Creates a [StatelessElement] to manage this widget's location in the tree.
+  ///
+  /// It is uncommon for subclasses to override this method.
+  @override
+  StatelessElement createElement() => StatelessElement(this);
+
+  /// Describes the part of the user interface represented by this widget.
+  ///
+  /// The framework calls this method when this widget is inserted into the tree
+  /// in a given [BuildContext] and when the dependencies of this widget change
+  /// (e.g., an [InheritedWidget] referenced by this widget changes). This
+  /// method can potentially be called in every frame and should not have any side
+  /// effects beyond building a widget.
+  ///
+  /// The framework replaces the subtree below this widget with the widget
+  /// returned by this method, either by updating the existing subtree or by
+  /// removing the subtree and inflating a new subtree, depending on whether the
+  /// widget returned by this method can update the root of the existing
+  /// subtree, as determined by calling [Widget.canUpdate].
+  ///
+  /// Typically implementations return a newly created constellation of widgets
+  /// that are configured with information from this widget's constructor and
+  /// from the given [BuildContext].
+  ///
+  /// The given [BuildContext] contains information about the location in the
+  /// tree at which this widget is being built. For example, the context
+  /// provides the set of inherited widgets for this location in the tree. A
+  /// given widget might be built with multiple different [BuildContext]
+  /// arguments over time if the widget is moved around the tree or if the
+  /// widget is inserted into the tree in multiple places at once.
+  ///
+  /// The implementation of this method must only depend on:
+  ///
+  /// * the fields of the widget, which themselves must not change over time,
+  ///   and
+  /// * any ambient state obtained from the `context` using
+  ///   [BuildContext.dependOnInheritedWidgetOfExactType].
+  ///
+  /// If a widget's [build] method is to depend on anything else, use a
+  /// [StatefulWidget] instead.
+  ///
+  /// See also:
+  ///
+  ///  * [StatelessWidget], which contains the discussion on performance considerations.
+  @protected
+  Widget build(BuildContext context);
+}
+
+/// A widget that has mutable state.
+///
+/// State is information that (1) can be read synchronously when the widget is
+/// built and (2) might change during the lifetime of the widget. It is the
+/// responsibility of the widget implementer to ensure that the [State] is
+/// promptly notified when such state changes, using [State.setState].
+///
+/// A stateful widget is a widget that describes part of the user interface by
+/// building a constellation of other widgets that describe the user interface
+/// more concretely. The building process continues recursively until the
+/// description of the user interface is fully concrete (e.g., consists
+/// entirely of [RenderObjectWidget]s, which describe concrete [RenderObject]s).
+///
+/// Stateful widgets are useful when the part of the user interface you are
+/// describing can change dynamically, e.g. due to having an internal
+/// clock-driven state, or depending on some system state. For compositions that
+/// depend only on the configuration information in the object itself and the
+/// [BuildContext] in which the widget is inflated, consider using
+/// [StatelessWidget].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=AqCMFXEmf3w}
+///
+/// [StatefulWidget] instances themselves are immutable and store their mutable
+/// state either in separate [State] objects that are created by the
+/// [createState] method, or in objects to which that [State] subscribes, for
+/// example [Stream] or [ChangeNotifier] objects, to which references are stored
+/// in final fields on the [StatefulWidget] itself.
+///
+/// The framework calls [createState] whenever it inflates a
+/// [StatefulWidget], which means that multiple [State] objects might be
+/// associated with the same [StatefulWidget] if that widget has been inserted
+/// into the tree in multiple places. Similarly, if a [StatefulWidget] is
+/// removed from the tree and later inserted in to the tree again, the framework
+/// will call [createState] again to create a fresh [State] object, simplifying
+/// the lifecycle of [State] objects.
+///
+/// A [StatefulWidget] keeps the same [State] object when moving from one
+/// location in the tree to another if its creator used a [GlobalKey] for its
+/// [key]. Because a widget with a [GlobalKey] can be used in at most one
+/// location in the tree, a widget that uses a [GlobalKey] has at most one
+/// associated element. The framework takes advantage of this property when
+/// moving a widget with a global key from one location in the tree to another
+/// by grafting the (unique) subtree associated with that widget from the old
+/// location to the new location (instead of recreating the subtree at the new
+/// location). The [State] objects associated with [StatefulWidget] are grafted
+/// along with the rest of the subtree, which means the [State] object is reused
+/// (instead of being recreated) in the new location. However, in order to be
+/// eligible for grafting, the widget must be inserted into the new location in
+/// the same animation frame in which it was removed from the old location.
+///
+/// ## Performance considerations
+///
+/// There are two primary categories of [StatefulWidget]s.
+///
+/// The first is one which allocates resources in [State.initState] and disposes
+/// of them in [State.dispose], but which does not depend on [InheritedWidget]s
+/// or call [State.setState]. Such widgets are commonly used at the root of an
+/// application or page, and communicate with subwidgets via [ChangeNotifier]s,
+/// [Stream]s, or other such objects. Stateful widgets following such a pattern
+/// are relatively cheap (in terms of CPU and GPU cycles), because they are
+/// built once then never update. They can, therefore, have somewhat complicated
+/// and deep build methods.
+///
+/// The second category is widgets that use [State.setState] or depend on
+/// [InheritedWidget]s. These will typically rebuild many times during the
+/// application's lifetime, and it is therefore important to minimize the impact
+/// of rebuilding such a widget. (They may also use [State.initState] or
+/// [State.didChangeDependencies] and allocate resources, but the important part
+/// is that they rebuild.)
+///
+/// There are several techniques one can use to minimize the impact of
+/// rebuilding a stateful widget:
+///
+///  * Push the state to the leaves. For example, if your page has a ticking
+///    clock, rather than putting the state at the top of the page and
+///    rebuilding the entire page each time the clock ticks, create a dedicated
+///    clock widget that only updates itself.
+///
+///  * Minimize the number of nodes transitively created by the build method and
+///    any widgets it creates. Ideally, a stateful widget would only create a
+///    single widget, and that widget would be a [RenderObjectWidget].
+///    (Obviously this isn't always practical, but the closer a widget gets to
+///    this ideal, the more efficient it will be.)
+///
+///  * If a subtree does not change, cache the widget that represents that
+///    subtree and re-use it each time it can be used. It is massively more
+///    efficient for a widget to be re-used than for a new (but
+///    identically-configured) widget to be created. Factoring out the stateful
+///    part into a widget that takes a child argument is a common way of doing
+///    this.
+///
+///  * Use `const` widgets where possible. (This is equivalent to caching a
+///    widget and re-using it.)
+///
+///  * Avoid changing the depth of any created subtrees or changing the type of
+///    any widgets in the subtree. For example, rather than returning either the
+///    child or the child wrapped in an [IgnorePointer], always wrap the child
+///    widget in an [IgnorePointer] and control the [IgnorePointer.ignoring]
+///    property. This is because changing the depth of the subtree requires
+///    rebuilding, laying out, and painting the entire subtree, whereas just
+///    changing the property will require the least possible change to the
+///    render tree (in the case of [IgnorePointer], for example, no layout or
+///    repaint is necessary at all).
+///
+///  * If the depth must be changed for some reason, consider wrapping the
+///    common parts of the subtrees in widgets that have a [GlobalKey] that
+///    remains consistent for the life of the stateful widget. (The
+///    [KeyedSubtree] widget may be useful for this purpose if no other widget
+///    can conveniently be assigned the key.)
+///
+/// {@tool snippet}
+///
+/// This is a skeleton of a stateful widget subclass called `YellowBird`.
+///
+/// In this example. the [State] has no actual state. State is normally
+/// represented as private member fields. Also, normally widgets have more
+/// constructor arguments, each of which corresponds to a `final` property.
+///
+/// ```dart
+/// class YellowBird extends StatefulWidget {
+///   const YellowBird({ Key key }) : super(key: key);
+///
+///   @override
+///   _YellowBirdState createState() => _YellowBirdState();
+/// }
+///
+/// class _YellowBirdState extends State<YellowBird> {
+///   @override
+///   Widget build(BuildContext context) {
+///     return Container(color: const Color(0xFFFFE306));
+///   }
+/// }
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// This example shows the more generic widget `Bird` which can be given a
+/// color and a child, and which has some internal state with a method that
+/// can be called to mutate it:
+///
+/// ```dart
+/// class Bird extends StatefulWidget {
+///   const Bird({
+///     Key key,
+///     this.color = const Color(0xFFFFE306),
+///     this.child,
+///   }) : super(key: key);
+///
+///   final Color color;
+///   final Widget child;
+///
+///   _BirdState createState() => _BirdState();
+/// }
+///
+/// class _BirdState extends State<Bird> {
+///   double _size = 1.0;
+///
+///   void grow() {
+///     setState(() { _size += 0.1; });
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Container(
+///       color: widget.color,
+///       transform: Matrix4.diagonal3Values(_size, _size, 1.0),
+///       child: widget.child,
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// By convention, widget constructors only use named arguments. Named arguments
+/// can be marked as required using [@required]. Also by convention, the first
+/// argument is [key], and the last argument is `child`, `children`, or the
+/// equivalent.
+///
+/// See also:
+///
+///  * [State], where the logic behind a [StatefulWidget] is hosted.
+///  * [StatelessWidget], for widgets that always build the same way given a
+///    particular configuration and ambient state.
+///  * [InheritedWidget], for widgets that introduce ambient state that can
+///    be read by descendant widgets.
+abstract class StatefulWidget extends Widget {
+  /// Initializes [key] for subclasses.
+  const StatefulWidget({ Key? key }) : super(key: key);
+
+  /// Creates a [StatefulElement] to manage this widget's location in the tree.
+  ///
+  /// It is uncommon for subclasses to override this method.
+  @override
+  StatefulElement createElement() => StatefulElement(this);
+
+  /// Creates the mutable state for this widget at a given location in the tree.
+  ///
+  /// Subclasses should override this method to return a newly created
+  /// instance of their associated [State] subclass:
+  ///
+  /// ```dart
+  /// @override
+  /// _MyState createState() => _MyState();
+  /// ```
+  ///
+  /// The framework can call this method multiple times over the lifetime of
+  /// a [StatefulWidget]. For example, if the widget is inserted into the tree
+  /// in multiple locations, the framework will create a separate [State] object
+  /// for each location. Similarly, if the widget is removed from the tree and
+  /// later inserted into the tree again, the framework will call [createState]
+  /// again to create a fresh [State] object, simplifying the lifecycle of
+  /// [State] objects.
+  @protected
+  @factory
+  State createState(); // ignore: no_logic_in_create_state, this is the original sin
+}
+
+/// Tracks the lifecycle of [State] objects when asserts are enabled.
+enum _StateLifecycle {
+  /// The [State] object has been created. [State.initState] is called at this
+  /// time.
+  created,
+
+  /// The [State.initState] method has been called but the [State] object is
+  /// not yet ready to build. [State.didChangeDependencies] is called at this time.
+  initialized,
+
+  /// The [State] object is ready to build and [State.dispose] has not yet been
+  /// called.
+  ready,
+
+  /// The [State.dispose] method has been called and the [State] object is
+  /// no longer able to build.
+  defunct,
+}
+
+/// The signature of [State.setState] functions.
+typedef StateSetter = void Function(VoidCallback fn);
+
+/// The logic and internal state for a [StatefulWidget].
+///
+/// State is information that (1) can be read synchronously when the widget is
+/// built and (2) might change during the lifetime of the widget. It is the
+/// responsibility of the widget implementer to ensure that the [State] is
+/// promptly notified when such state changes, using [State.setState].
+///
+/// [State] objects are created by the framework by calling the
+/// [StatefulWidget.createState] method when inflating a [StatefulWidget] to
+/// insert it into the tree. Because a given [StatefulWidget] instance can be
+/// inflated multiple times (e.g., the widget is incorporated into the tree in
+/// multiple places at once), there might be more than one [State] object
+/// associated with a given [StatefulWidget] instance. Similarly, if a
+/// [StatefulWidget] is removed from the tree and later inserted in to the tree
+/// again, the framework will call [StatefulWidget.createState] again to create
+/// a fresh [State] object, simplifying the lifecycle of [State] objects.
+///
+/// [State] objects have the following lifecycle:
+///
+///  * The framework creates a [State] object by calling
+///    [StatefulWidget.createState].
+///  * The newly created [State] object is associated with a [BuildContext].
+///    This association is permanent: the [State] object will never change its
+///    [BuildContext]. However, the [BuildContext] itself can be moved around
+///    the tree along with its subtree. At this point, the [State] object is
+///    considered [mounted].
+///  * The framework calls [initState]. Subclasses of [State] should override
+///    [initState] to perform one-time initialization that depends on the
+///    [BuildContext] or the widget, which are available as the [context] and
+///    [widget] properties, respectively, when the [initState] method is
+///    called.
+///  * The framework calls [didChangeDependencies]. Subclasses of [State] should
+///    override [didChangeDependencies] to perform initialization involving
+///    [InheritedWidget]s. If [BuildContext.dependOnInheritedWidgetOfExactType] is
+///    called, the [didChangeDependencies] method will be called again if the
+///    inherited widgets subsequently change or if the widget moves in the tree.
+///  * At this point, the [State] object is fully initialized and the framework
+///    might call its [build] method any number of times to obtain a
+///    description of the user interface for this subtree. [State] objects can
+///    spontaneously request to rebuild their subtree by callings their
+///    [setState] method, which indicates that some of their internal state
+///    has changed in a way that might impact the user interface in this
+///    subtree.
+///  * During this time, a parent widget might rebuild and request that this
+///    location in the tree update to display a new widget with the same
+///    [runtimeType] and [Widget.key]. When this happens, the framework will
+///    update the [widget] property to refer to the new widget and then call the
+///    [didUpdateWidget] method with the previous widget as an argument. [State]
+///    objects should override [didUpdateWidget] to respond to changes in their
+///    associated widget (e.g., to start implicit animations). The framework
+///    always calls [build] after calling [didUpdateWidget], which means any
+///    calls to [setState] in [didUpdateWidget] are redundant.
+///  * During development, if a hot reload occurs (whether initiated from the
+///    command line `flutter` tool by pressing `r`, or from an IDE), the
+///    [reassemble] method is called. This provides an opportunity to
+///    reinitialize any data that was prepared in the [initState] method.
+///  * If the subtree containing the [State] object is removed from the tree
+///    (e.g., because the parent built a widget with a different [runtimeType]
+///    or [Widget.key]), the framework calls the [deactivate] method. Subclasses
+///    should override this method to clean up any links between this object
+///    and other elements in the tree (e.g. if you have provided an ancestor
+///    with a pointer to a descendant's [RenderObject]).
+///  * At this point, the framework might reinsert this subtree into another
+///    part of the tree. If that happens, the framework will ensure that it
+///    calls [build] to give the [State] object a chance to adapt to its new
+///    location in the tree. If the framework does reinsert this subtree, it
+///    will do so before the end of the animation frame in which the subtree was
+///    removed from the tree. For this reason, [State] objects can defer
+///    releasing most resources until the framework calls their [dispose]
+///    method.
+///  * If the framework does not reinsert this subtree by the end of the current
+///    animation frame, the framework will call [dispose], which indicates that
+///    this [State] object will never build again. Subclasses should override
+///    this method to release any resources retained by this object (e.g.,
+///    stop any active animations).
+///  * After the framework calls [dispose], the [State] object is considered
+///    unmounted and the [mounted] property is false. It is an error to call
+///    [setState] at this point. This stage of the lifecycle is terminal: there
+///    is no way to remount a [State] object that has been disposed.
+///
+/// See also:
+///
+///  * [StatefulWidget], where the current configuration of a [State] is hosted,
+///    and whose documentation has sample code for [State].
+///  * [StatelessWidget], for widgets that always build the same way given a
+///    particular configuration and ambient state.
+///  * [InheritedWidget], for widgets that introduce ambient state that can
+///    be read by descendant widgets.
+///  * [Widget], for an overview of widgets in general.
+@optionalTypeArgs
+abstract class State<T extends StatefulWidget> with Diagnosticable {
+  /// The current configuration.
+  ///
+  /// A [State] object's configuration is the corresponding [StatefulWidget]
+  /// instance. This property is initialized by the framework before calling
+  /// [initState]. If the parent updates this location in the tree to a new
+  /// widget with the same [runtimeType] and [Widget.key] as the current
+  /// configuration, the framework will update this property to refer to the new
+  /// widget and then call [didUpdateWidget], passing the old configuration as
+  /// an argument.
+  T get widget => _widget!;
+  T? _widget;
+
+  /// The current stage in the lifecycle for this state object.
+  ///
+  /// This field is used by the framework when asserts are enabled to verify
+  /// that [State] objects move through their lifecycle in an orderly fashion.
+  _StateLifecycle _debugLifecycleState = _StateLifecycle.created;
+
+  /// Verifies that the [State] that was created is one that expects to be
+  /// created for that particular [Widget].
+  bool _debugTypesAreRight(Widget widget) => widget is T;
+
+  /// The location in the tree where this widget builds.
+  ///
+  /// The framework associates [State] objects with a [BuildContext] after
+  /// creating them with [StatefulWidget.createState] and before calling
+  /// [initState]. The association is permanent: the [State] object will never
+  /// change its [BuildContext]. However, the [BuildContext] itself can be moved
+  /// around the tree.
+  ///
+  /// After calling [dispose], the framework severs the [State] object's
+  /// connection with the [BuildContext].
+  BuildContext get context {
+    assert(() {
+      if (_element == null) {
+        throw FlutterError(
+          'This widget has been unmounted, so the State no longer has a context (and should be considered defunct). \n'
+          'Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.'
+        );
+      }
+      return true;
+    }());
+    return _element!;
+  }
+  StatefulElement? _element;
+
+  /// Whether this [State] object is currently in a tree.
+  ///
+  /// After creating a [State] object and before calling [initState], the
+  /// framework "mounts" the [State] object by associating it with a
+  /// [BuildContext]. The [State] object remains mounted until the framework
+  /// calls [dispose], after which time the framework will never ask the [State]
+  /// object to [build] again.
+  ///
+  /// It is an error to call [setState] unless [mounted] is true.
+  bool get mounted => _element != null;
+
+  /// Called when this object is inserted into the tree.
+  ///
+  /// The framework will call this method exactly once for each [State] object
+  /// it creates.
+  ///
+  /// Override this method to perform initialization that depends on the
+  /// location at which this object was inserted into the tree (i.e., [context])
+  /// or on the widget used to configure this object (i.e., [widget]).
+  ///
+  /// {@template flutter.widgets.State.initState}
+  /// If a [State]'s [build] method depends on an object that can itself
+  /// change state, for example a [ChangeNotifier] or [Stream], or some
+  /// other object to which one can subscribe to receive notifications, then
+  /// be sure to subscribe and unsubscribe properly in [initState],
+  /// [didUpdateWidget], and [dispose]:
+  ///
+  ///  * In [initState], subscribe to the object.
+  ///  * In [didUpdateWidget] unsubscribe from the old object and subscribe
+  ///    to the new one if the updated widget configuration requires
+  ///    replacing the object.
+  ///  * In [dispose], unsubscribe from the object.
+  ///
+  /// {@endtemplate}
+  ///
+  /// You cannot use [BuildContext.dependOnInheritedWidgetOfExactType] from this
+  /// method. However, [didChangeDependencies] will be called immediately
+  /// following this method, and [BuildContext.dependOnInheritedWidgetOfExactType] can
+  /// be used there.
+  ///
+  /// If you override this, make sure your method starts with a call to
+  /// super.initState().
+  @protected
+  @mustCallSuper
+  void initState() {
+    assert(_debugLifecycleState == _StateLifecycle.created);
+  }
+
+  /// Called whenever the widget configuration changes.
+  ///
+  /// If the parent widget rebuilds and request that this location in the tree
+  /// update to display a new widget with the same [runtimeType] and
+  /// [Widget.key], the framework will update the [widget] property of this
+  /// [State] object to refer to the new widget and then call this method
+  /// with the previous widget as an argument.
+  ///
+  /// Override this method to respond when the [widget] changes (e.g., to start
+  /// implicit animations).
+  ///
+  /// The framework always calls [build] after calling [didUpdateWidget], which
+  /// means any calls to [setState] in [didUpdateWidget] are redundant.
+  ///
+  /// {@macro flutter.widgets.State.initState}
+  ///
+  /// If you override this, make sure your method starts with a call to
+  /// super.didUpdateWidget(oldWidget).
+  @mustCallSuper
+  @protected
+  void didUpdateWidget(covariant T oldWidget) { }
+
+  /// {@macro flutter.widgets.Element.reassemble}
+  ///
+  /// In addition to this method being invoked, it is guaranteed that the
+  /// [build] method will be invoked when a reassemble is signaled. Most
+  /// widgets therefore do not need to do anything in the [reassemble] method.
+  ///
+  /// See also:
+  ///
+  ///  * [Element.reassemble]
+  ///  * [BindingBase.reassembleApplication]
+  ///  * [Image], which uses this to reload images.
+  @protected
+  @mustCallSuper
+  void reassemble() { }
+
+  /// Notify the framework that the internal state of this object has changed.
+  ///
+  /// Whenever you change the internal state of a [State] object, make the
+  /// change in a function that you pass to [setState]:
+  ///
+  /// ```dart
+  /// setState(() { _myState = newValue; });
+  /// ```
+  ///
+  /// The provided callback is immediately called synchronously. It must not
+  /// return a future (the callback cannot be `async`), since then it would be
+  /// unclear when the state was actually being set.
+  ///
+  /// Calling [setState] notifies the framework that the internal state of this
+  /// object has changed in a way that might impact the user interface in this
+  /// subtree, which causes the framework to schedule a [build] for this [State]
+  /// object.
+  ///
+  /// If you just change the state directly without calling [setState], the
+  /// framework might not schedule a [build] and the user interface for this
+  /// subtree might not be updated to reflect the new state.
+  ///
+  /// Generally it is recommended that the `setState` method only be used to
+  /// wrap the actual changes to the state, not any computation that might be
+  /// associated with the change. For example, here a value used by the [build]
+  /// function is incremented, and then the change is written to disk, but only
+  /// the increment is wrapped in the `setState`:
+  ///
+  /// ```dart
+  /// Future<void> _incrementCounter() async {
+  ///   setState(() {
+  ///     _counter++;
+  ///   });
+  ///   Directory directory = await getApplicationDocumentsDirectory();
+  ///   final String dirName = directory.path;
+  ///   await File('$dir/counter.txt').writeAsString('$_counter');
+  /// }
+  /// ```
+  ///
+  /// It is an error to call this method after the framework calls [dispose].
+  /// You can determine whether it is legal to call this method by checking
+  /// whether the [mounted] property is true.
+  @protected
+  void setState(VoidCallback fn) {
+    assert(fn != null);
+    assert(() {
+      if (_debugLifecycleState == _StateLifecycle.defunct) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('setState() called after dispose(): $this'),
+          ErrorDescription(
+            'This error happens if you call setState() on a State object for a widget that '
+            'no longer appears in the widget tree (e.g., whose parent widget no longer '
+            'includes the widget in its build). This error can occur when code calls '
+            'setState() from a timer or an animation callback.'
+          ),
+          ErrorHint(
+            'The preferred solution is '
+            'to cancel the timer or stop listening to the animation in the dispose() '
+            'callback. Another solution is to check the "mounted" property of this '
+            'object before calling setState() to ensure the object is still in the '
+            'tree.'
+          ),
+          ErrorHint(
+            'This error might indicate a memory leak if setState() is being called '
+            'because another object is retaining a reference to this State object '
+            'after it has been removed from the tree. To avoid memory leaks, '
+            'consider breaking the reference to this object during dispose().'
+          ),
+        ]);
+      }
+      if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('setState() called in constructor: $this'),
+          ErrorHint(
+            'This happens when you call setState() on a State object for a widget that '
+            "hasn't been inserted into the widget tree yet. It is not necessary to call "
+            'setState() in the constructor, since the state is already assumed to be dirty '
+            'when it is initially created.'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    final dynamic result = fn() as dynamic;
+    assert(() {
+      if (result is Future) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('setState() callback argument returned a Future.'),
+          ErrorDescription(
+            'The setState() method on $this was called with a closure or method that '
+            'returned a Future. Maybe it is marked as "async".'
+          ),
+          ErrorHint(
+            'Instead of performing asynchronous work inside a call to setState(), first '
+            'execute the work (without updating the widget state), and then synchronously '
+           'update the state inside a call to setState().'
+          ),
+        ]);
+      }
+      // We ignore other types of return values so that you can do things like:
+      //   setState(() => x = 3);
+      return true;
+    }());
+    _element!.markNeedsBuild();
+  }
+
+  /// Called when this object is removed from the tree.
+  ///
+  /// The framework calls this method whenever it removes this [State] object
+  /// from the tree. In some cases, the framework will reinsert the [State]
+  /// object into another part of the tree (e.g., if the subtree containing this
+  /// [State] object is grafted from one location in the tree to another). If
+  /// that happens, the framework will ensure that it calls [build] to give the
+  /// [State] object a chance to adapt to its new location in the tree. If
+  /// the framework does reinsert this subtree, it will do so before the end of
+  /// the animation frame in which the subtree was removed from the tree. For
+  /// this reason, [State] objects can defer releasing most resources until the
+  /// framework calls their [dispose] method.
+  ///
+  /// Subclasses should override this method to clean up any links between
+  /// this object and other elements in the tree (e.g. if you have provided an
+  /// ancestor with a pointer to a descendant's [RenderObject]).
+  ///
+  /// If you override this, make sure to end your method with a call to
+  /// super.deactivate().
+  ///
+  /// See also:
+  ///
+  ///  * [dispose], which is called after [deactivate] if the widget is removed
+  ///    from the tree permanently.
+  @protected
+  @mustCallSuper
+  void deactivate() { }
+
+  /// Called when this object is removed from the tree permanently.
+  ///
+  /// The framework calls this method when this [State] object will never
+  /// build again. After the framework calls [dispose], the [State] object is
+  /// considered unmounted and the [mounted] property is false. It is an error
+  /// to call [setState] at this point. This stage of the lifecycle is terminal:
+  /// there is no way to remount a [State] object that has been disposed.
+  ///
+  /// Subclasses should override this method to release any resources retained
+  /// by this object (e.g., stop any active animations).
+  ///
+  /// {@macro flutter.widgets.State.initState}
+  ///
+  /// If you override this, make sure to end your method with a call to
+  /// super.dispose().
+  ///
+  /// See also:
+  ///
+  ///  * [deactivate], which is called prior to [dispose].
+  @protected
+  @mustCallSuper
+  void dispose() {
+    assert(_debugLifecycleState == _StateLifecycle.ready);
+    assert(() {
+      _debugLifecycleState = _StateLifecycle.defunct;
+      return true;
+    }());
+  }
+
+  /// Describes the part of the user interface represented by this widget.
+  ///
+  /// The framework calls this method in a number of different situations. For
+  /// example:
+  ///
+  ///  * After calling [initState].
+  ///  * After calling [didUpdateWidget].
+  ///  * After receiving a call to [setState].
+  ///  * After a dependency of this [State] object changes (e.g., an
+  ///    [InheritedWidget] referenced by the previous [build] changes).
+  ///  * After calling [deactivate] and then reinserting the [State] object into
+  ///    the tree at another location.
+  ///
+  /// This method can potentially be called in every frame and should not have
+  /// any side effects beyond building a widget.
+  ///
+  /// The framework replaces the subtree below this widget with the widget
+  /// returned by this method, either by updating the existing subtree or by
+  /// removing the subtree and inflating a new subtree, depending on whether the
+  /// widget returned by this method can update the root of the existing
+  /// subtree, as determined by calling [Widget.canUpdate].
+  ///
+  /// Typically implementations return a newly created constellation of widgets
+  /// that are configured with information from this widget's constructor, the
+  /// given [BuildContext], and the internal state of this [State] object.
+  ///
+  /// The given [BuildContext] contains information about the location in the
+  /// tree at which this widget is being built. For example, the context
+  /// provides the set of inherited widgets for this location in the tree. The
+  /// [BuildContext] argument is always the same as the [context] property of
+  /// this [State] object and will remain the same for the lifetime of this
+  /// object. The [BuildContext] argument is provided redundantly here so that
+  /// this method matches the signature for a [WidgetBuilder].
+  ///
+  /// ## Design discussion
+  ///
+  /// ### Why is the [build] method on [State], and not [StatefulWidget]?
+  ///
+  /// Putting a `Widget build(BuildContext context)` method on [State] rather
+  /// than putting a `Widget build(BuildContext context, State state)` method
+  /// on [StatefulWidget] gives developers more flexibility when subclassing
+  /// [StatefulWidget].
+  ///
+  /// For example, [AnimatedWidget] is a subclass of [StatefulWidget] that
+  /// introduces an abstract `Widget build(BuildContext context)` method for its
+  /// subclasses to implement. If [StatefulWidget] already had a [build] method
+  /// that took a [State] argument, [AnimatedWidget] would be forced to provide
+  /// its [State] object to subclasses even though its [State] object is an
+  /// internal implementation detail of [AnimatedWidget].
+  ///
+  /// Conceptually, [StatelessWidget] could also be implemented as a subclass of
+  /// [StatefulWidget] in a similar manner. If the [build] method were on
+  /// [StatefulWidget] rather than [State], that would not be possible anymore.
+  ///
+  /// Putting the [build] function on [State] rather than [StatefulWidget] also
+  /// helps avoid a category of bugs related to closures implicitly capturing
+  /// `this`. If you defined a closure in a [build] function on a
+  /// [StatefulWidget], that closure would implicitly capture `this`, which is
+  /// the current widget instance, and would have the (immutable) fields of that
+  /// instance in scope:
+  ///
+  /// ```dart
+  /// class MyButton extends StatefulWidget {
+  ///   ...
+  ///   final Color color;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context, MyButtonState state) {
+  ///     ... () { print("color: $color"); } ...
+  ///   }
+  /// }
+  /// ```
+  ///
+  /// For example, suppose the parent builds `MyButton` with `color` being blue,
+  /// the `$color` in the print function refers to blue, as expected. Now,
+  /// suppose the parent rebuilds `MyButton` with green. The closure created by
+  /// the first build still implicitly refers to the original widget and the
+  /// `$color` still prints blue even through the widget has been updated to
+  /// green.
+  ///
+  /// In contrast, with the [build] function on the [State] object, closures
+  /// created during [build] implicitly capture the [State] instance instead of
+  /// the widget instance:
+  ///
+  /// ```dart
+  /// class MyButtonState extends State<MyButton> {
+  ///   ...
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     ... () { print("color: ${widget.color}"); } ...
+  ///   }
+  /// }
+  /// ```
+  ///
+  /// Now when the parent rebuilds `MyButton` with green, the closure created by
+  /// the first build still refers to [State] object, which is preserved across
+  /// rebuilds, but the framework has updated that [State] object's [widget]
+  /// property to refer to the new `MyButton` instance and `${widget.color}`
+  /// prints green, as expected.
+  ///
+  /// See also:
+  ///
+  ///  * [StatefulWidget], which contains the discussion on performance considerations.
+  @protected
+  Widget build(BuildContext context);
+
+  /// Called when a dependency of this [State] object changes.
+  ///
+  /// For example, if the previous call to [build] referenced an
+  /// [InheritedWidget] that later changed, the framework would call this
+  /// method to notify this object about the change.
+  ///
+  /// This method is also called immediately after [initState]. It is safe to
+  /// call [BuildContext.dependOnInheritedWidgetOfExactType] from this method.
+  ///
+  /// Subclasses rarely override this method because the framework always
+  /// calls [build] after a dependency changes. Some subclasses do override
+  /// this method because they need to do some expensive work (e.g., network
+  /// fetches) when their dependencies change, and that work would be too
+  /// expensive to do for every build.
+  @protected
+  @mustCallSuper
+  void didChangeDependencies() { }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    assert(() {
+      properties.add(EnumProperty<_StateLifecycle>('lifecycle state', _debugLifecycleState, defaultValue: _StateLifecycle.ready));
+      return true;
+    }());
+    properties.add(ObjectFlagProperty<T>('_widget', _widget, ifNull: 'no widget'));
+    properties.add(ObjectFlagProperty<StatefulElement>('_element', _element, ifNull: 'not mounted'));
+  }
+}
+
+/// A widget that has a child widget provided to it, instead of building a new
+/// widget.
+///
+/// Useful as a base class for other widgets, such as [InheritedWidget] and
+/// [ParentDataWidget].
+///
+/// See also:
+///
+///  * [InheritedWidget], for widgets that introduce ambient state that can
+///    be read by descendant widgets.
+///  * [ParentDataWidget], for widgets that populate the
+///    [RenderObject.parentData] slot of their child's [RenderObject] to
+///    configure the parent widget's layout.
+///  * [StatefulWidget] and [State], for widgets that can build differently
+///    several times over their lifetime.
+///  * [StatelessWidget], for widgets that always build the same way given a
+///    particular configuration and ambient state.
+///  * [Widget], for an overview of widgets in general.
+abstract class ProxyWidget extends Widget {
+  /// Creates a widget that has exactly one child widget.
+  const ProxyWidget({ Key? key, required this.child }) : super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@template flutter.widgets.ProxyWidget.child}
+  /// This widget can only have one child. To lay out multiple children, let this
+  /// widget's child be a widget such as [Row], [Column], or [Stack], which have a
+  /// `children` property, and then provide the children to that widget.
+  /// {@endtemplate}
+  final Widget child;
+}
+
+/// Base class for widgets that hook [ParentData] information to children of
+/// [RenderObjectWidget]s.
+///
+/// This can be used to provide per-child configuration for
+/// [RenderObjectWidget]s with more than one child. For example, [Stack] uses
+/// the [Positioned] parent data widget to position each child.
+///
+/// A [ParentDataWidget] is specific to a particular kind of [ParentData]. That
+/// class is `T`, the [ParentData] type argument.
+///
+/// {@tool snippet}
+///
+/// This example shows how you would build a [ParentDataWidget] to configure a
+/// `FrogJar` widget's children by specifying a [Size] for each one.
+///
+/// ```dart
+/// class FrogSize extends ParentDataWidget<FrogJarParentData> {
+///   FrogSize({
+///     Key key,
+///     @required this.size,
+///     @required Widget child,
+///   }) : assert(child != null),
+///        assert(size != null),
+///        super(key: key, child: child);
+///
+///   final Size size;
+///
+///   @override
+///   void applyParentData(RenderObject renderObject) {
+///     final FrogJarParentData parentData = renderObject.parentData;
+///     if (parentData.size != size) {
+///       parentData.size = size;
+///       final RenderFrogJar targetParent = renderObject.parent;
+///       targetParent.markNeedsLayout();
+///     }
+///   }
+///
+///   @override
+///   Type get debugTypicalAncestorWidgetClass => FrogJar;
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [RenderObject], the superclass for layout algorithms.
+///  * [RenderObject.parentData], the slot that this class configures.
+///  * [ParentData], the superclass of the data that will be placed in
+///    [RenderObject.parentData] slots. The `T` type parameter for
+///    [ParentDataWidget] is a [ParentData].
+///  * [RenderObjectWidget], the class for widgets that wrap [RenderObject]s.
+///  * [StatefulWidget] and [State], for widgets that can build differently
+///    several times over their lifetime.
+abstract class ParentDataWidget<T extends ParentData> extends ProxyWidget {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const ParentDataWidget({ Key? key, required Widget child })
+    : super(key: key, child: child);
+
+  @override
+  ParentDataElement<T> createElement() => ParentDataElement<T>(this);
+
+  /// Checks if this widget can apply its parent data to the provided
+  /// `renderObject`.
+  ///
+  /// The [RenderObject.parentData] of the provided `renderObject` is
+  /// typically set up by an ancestor [RenderObjectWidget] of the type returned
+  /// by [debugTypicalAncestorWidgetClass].
+  ///
+  /// This is called just before [applyParentData] is invoked with the same
+  /// [RenderObject] provided to that method.
+  bool debugIsValidRenderObject(RenderObject renderObject) {
+    assert(T != dynamic);
+    assert(T != ParentData);
+    return renderObject.parentData is T;
+  }
+
+  /// The [RenderObjectWidget] that is typically used to set up the [ParentData]
+  /// that [applyParentData] will write to.
+  ///
+  /// This is only used in error messages to tell users what widget typically
+  /// wraps this ParentDataWidget.
+  Type get debugTypicalAncestorWidgetClass;
+
+  Iterable<DiagnosticsNode> _debugDescribeIncorrectParentDataType({
+    required ParentData? parentData,
+    RenderObjectWidget? parentDataCreator,
+    DiagnosticsNode? ownershipChain,
+  }) sync* {
+    assert(T != dynamic);
+    assert(T != ParentData);
+    assert(debugTypicalAncestorWidgetClass != null);
+
+    final String description = 'The ParentDataWidget $this wants to apply ParentData of type $T to a RenderObject';
+    if (parentData == null) {
+      yield ErrorDescription(
+        '$description, which has not been set up to receive any ParentData.'
+      );
+    } else {
+      yield ErrorDescription(
+        '$description, which has been set up to accept ParentData of incompatible type ${parentData.runtimeType}.'
+      );
+    }
+    yield ErrorHint(
+      'Usually, this means that the $runtimeType widget has the wrong ancestor RenderObjectWidget. '
+      'Typically, $runtimeType widgets are placed directly inside $debugTypicalAncestorWidgetClass widgets.'
+    );
+    if (parentDataCreator != null) {
+      yield ErrorHint(
+        'The offending $runtimeType is currently placed inside a ${parentDataCreator.runtimeType} widget.'
+      );
+    }
+    if (ownershipChain != null) {
+      yield ErrorDescription(
+        'The ownership chain for the RenderObject that received the incompatible parent data was:\n  $ownershipChain'
+      );
+    }
+  }
+
+  /// Write the data from this widget into the given render object's parent data.
+  ///
+  /// The framework calls this function whenever it detects that the
+  /// [RenderObject] associated with the [child] has outdated
+  /// [RenderObject.parentData]. For example, if the render object was recently
+  /// inserted into the render tree, the render object's parent data might not
+  /// match the data in this widget.
+  ///
+  /// Subclasses are expected to override this function to copy data from their
+  /// fields into the [RenderObject.parentData] field of the given render
+  /// object. The render object's parent is guaranteed to have been created by a
+  /// widget of type `T`, which usually means that this function can assume that
+  /// the render object's parent data object inherits from a particular class.
+  ///
+  /// If this function modifies data that can change the parent's layout or
+  /// painting, this function is responsible for calling
+  /// [RenderObject.markNeedsLayout] or [RenderObject.markNeedsPaint] on the
+  /// parent, as appropriate.
+  @protected
+  void applyParentData(RenderObject renderObject);
+
+  /// Whether the [ParentDataElement.applyWidgetOutOfTurn] method is allowed
+  /// with this widget.
+  ///
+  /// This should only return true if this widget represents a [ParentData]
+  /// configuration that will have no impact on the layout or paint phase.
+  ///
+  /// See also:
+  ///
+  ///  * [ParentDataElement.applyWidgetOutOfTurn], which verifies this in debug
+  ///    mode.
+  @protected
+  bool debugCanApplyOutOfTurn() => false;
+}
+
+/// Base class for widgets that efficiently propagate information down the tree.
+///
+/// To obtain the nearest instance of a particular type of inherited widget from
+/// a build context, use [BuildContext.dependOnInheritedWidgetOfExactType].
+///
+/// Inherited widgets, when referenced in this way, will cause the consumer to
+/// rebuild when the inherited widget itself changes state.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=Zbm3hjPjQMk}
+///
+/// {@tool snippet}
+///
+/// The following is a skeleton of an inherited widget called `FrogColor`:
+///
+/// ```dart
+/// class FrogColor extends InheritedWidget {
+///   const FrogColor({
+///     Key key,
+///     @required this.color,
+///     @required Widget child,
+///   }) : assert(color != null),
+///        assert(child != null),
+///        super(key: key, child: child);
+///
+///   final Color color;
+///
+///   static FrogColor of(BuildContext context) {
+///     return context.dependOnInheritedWidgetOfExactType<FrogColor>();
+///   }
+///
+///   @override
+///   bool updateShouldNotify(FrogColor old) => color != old.color;
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ## Implementing the `of` method
+///
+/// The convention is to provide a static method `of` on the [InheritedWidget]
+/// which does the call to [BuildContext.dependOnInheritedWidgetOfExactType]. This
+/// allows the class to define its own fallback logic in case there isn't
+/// a widget in scope. In the example above, the value returned will be
+/// null in that case, but it could also have defaulted to a value.
+///
+/// Sometimes, the `of` method returns the data rather than the inherited
+/// widget; for example, in this case it could have returned a [Color] instead
+/// of the `FrogColor` widget.
+///
+/// Occasionally, the inherited widget is an implementation detail of another
+/// class, and is therefore private. The `of` method in that case is typically
+/// put on the public class instead. For example, [Theme] is implemented as a
+/// [StatelessWidget] that builds a private inherited widget; [Theme.of] looks
+/// for that inherited widget using [BuildContext.dependOnInheritedWidgetOfExactType]
+/// and then returns the [ThemeData].
+///
+/// ## Calling the `of` method
+///
+/// When using the `of` method, the `context` must be a descendant of the
+/// [InheritedWidget], meaning it must be "below" the [InheritedWidget] in the
+/// tree.
+///
+/// {@tool snippet}
+///
+/// In this example, the `context` used is the one from the [Builder], which is
+/// a child of the FrogColor widget, so this works.
+///
+/// ```dart
+/// class MyPage extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return Scaffold(
+///       body: FrogColor(
+///         color: Colors.green,
+///         child: Builder(
+///           builder: (BuildContext innerContext) {
+///             return Text(
+///               'Hello Frog',
+///               style: TextStyle(color: FrogColor.of(innerContext).color),
+///             );
+///           },
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+///
+/// In this example, the `context` used is the one from the MyOtherPage widget,
+/// which is a parent of the FrogColor widget, so this does not work.
+///
+/// ```dart
+/// class MyOtherPage extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return Scaffold(
+///       body: FrogColor(
+///         color: Colors.green,
+///         child: Text(
+///           'Hello Frog',
+///           style: TextStyle(color: FrogColor.of(context).color),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+/// {@youtube 560 315 https://www.youtube.com/watch?v=1t-8rBCGBYw}
+///
+/// See also:
+///
+///  * [StatefulWidget] and [State], for widgets that can build differently
+///    several times over their lifetime.
+///  * [StatelessWidget], for widgets that always build the same way given a
+///    particular configuration and ambient state.
+///  * [Widget], for an overview of widgets in general.
+///  * [InheritedNotifier], an inherited widget whose value can be a
+///    [Listenable], and which will notify dependents whenever the value
+///    sends notifications.
+///  * [InheritedModel], an inherited widget that allows clients to subscribe
+///    to changes for subparts of the value.
+abstract class InheritedWidget extends ProxyWidget {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const InheritedWidget({ Key? key, required Widget child })
+    : super(key: key, child: child);
+
+  @override
+  InheritedElement createElement() => InheritedElement(this);
+
+  /// Whether the framework should notify widgets that inherit from this widget.
+  ///
+  /// When this widget is rebuilt, sometimes we need to rebuild the widgets that
+  /// inherit from this widget but sometimes we do not. For example, if the data
+  /// held by this widget is the same as the data held by `oldWidget`, then we
+  /// do not need to rebuild the widgets that inherited the data held by
+  /// `oldWidget`.
+  ///
+  /// The framework distinguishes these cases by calling this function with the
+  /// widget that previously occupied this location in the tree as an argument.
+  /// The given widget is guaranteed to have the same [runtimeType] as this
+  /// object.
+  @protected
+  bool updateShouldNotify(covariant InheritedWidget oldWidget);
+}
+
+/// RenderObjectWidgets provide the configuration for [RenderObjectElement]s,
+/// which wrap [RenderObject]s, which provide the actual rendering of the
+/// application.
+abstract class RenderObjectWidget extends Widget {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const RenderObjectWidget({ Key? key }) : super(key: key);
+
+  /// RenderObjectWidgets always inflate to a [RenderObjectElement] subclass.
+  @override
+  @factory
+  RenderObjectElement createElement();
+
+  /// Creates an instance of the [RenderObject] class that this
+  /// [RenderObjectWidget] represents, using the configuration described by this
+  /// [RenderObjectWidget].
+  ///
+  /// This method should not do anything with the children of the render object.
+  /// That should instead be handled by the method that overrides
+  /// [RenderObjectElement.mount] in the object rendered by this object's
+  /// [createElement] method. See, for example,
+  /// [SingleChildRenderObjectElement.mount].
+  @protected
+  @factory
+  RenderObject createRenderObject(BuildContext context);
+
+  /// Copies the configuration described by this [RenderObjectWidget] to the
+  /// given [RenderObject], which will be of the same type as returned by this
+  /// object's [createRenderObject].
+  ///
+  /// This method should not do anything to update the children of the render
+  /// object. That should instead be handled by the method that overrides
+  /// [RenderObjectElement.update] in the object rendered by this object's
+  /// [createElement] method. See, for example,
+  /// [SingleChildRenderObjectElement.update].
+  @protected
+  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
+
+  /// A render object previously associated with this widget has been removed
+  /// from the tree. The given [RenderObject] will be of the same type as
+  /// returned by this object's [createRenderObject].
+  @protected
+  void didUnmountRenderObject(covariant RenderObject renderObject) { }
+}
+
+/// A superclass for RenderObjectWidgets that configure RenderObject subclasses
+/// that have no children.
+abstract class LeafRenderObjectWidget extends RenderObjectWidget {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const LeafRenderObjectWidget({ Key? key }) : super(key: key);
+
+  @override
+  LeafRenderObjectElement createElement() => LeafRenderObjectElement(this);
+}
+
+/// A superclass for [RenderObjectWidget]s that configure [RenderObject] subclasses
+/// that have a single child slot. (This superclass only provides the storage
+/// for that child, it doesn't actually provide the updating logic.)
+///
+/// Typically, the render object assigned to this widget will make use of
+/// [RenderObjectWithChildMixin] to implement a single-child model. The mixin
+/// exposes a [RenderObjectWithChildMixin.child] property that allows
+/// retrieving the render object belonging to the [child] widget.
+abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const SingleChildRenderObjectWidget({ Key? key, this.child }) : super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  @override
+  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
+}
+
+/// A superclass for [RenderObjectWidget]s that configure [RenderObject] subclasses
+/// that have a single list of children. (This superclass only provides the
+/// storage for that child list, it doesn't actually provide the updating
+/// logic.)
+///
+/// Subclasses must return a [RenderObject] that mixes in
+/// [ContainerRenderObjectMixin], which provides the necessary functionality to
+/// visit the children of the container render object (the render object
+/// belonging to the [children] widgets). Typically, subclasses will return a
+/// [RenderBox] that mixes in both [ContainerRenderObjectMixin] and
+/// [RenderBoxContainerDefaultsMixin].
+///
+/// See also:
+///
+///  * [Stack], which uses [MultiChildRenderObjectWidget].
+///  * [RenderStack], for an example implementation of the associated render object.
+abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
+  /// Initializes fields for subclasses.
+  ///
+  /// The [children] argument must not be null and must not contain any null
+  /// objects.
+  MultiChildRenderObjectWidget({ Key? key, this.children = const <Widget>[] })
+    : assert(children != null),
+      super(key: key) {
+    assert(() {
+      for (int index = 0; index < children.length; index++) {
+        // TODO(a14n): remove this check to have a lot more const widget
+        if (children[index] == null) { // ignore: dead_code
+          throw FlutterError(
+              "$runtimeType's children must not contain any null values, "
+                  'but a null value was found at index $index'
+          );
+        }
+      }
+      return true;
+    }()); // https://github.com/dart-lang/sdk/issues/29276
+  }
+
+  /// The widgets below this widget in the tree.
+  ///
+  /// If this list is going to be mutated, it is usually wise to put a [Key] on
+  /// each of the child widgets, so that the framework can match old
+  /// configurations to new configurations and maintain the underlying render
+  /// objects.
+  ///
+  /// Also, a [Widget] in Flutter is immutable, so directly modifying the
+  /// [children] such as `someMultiChildRenderObjectWidget.children.add(...)` or
+  /// as the example code below will result in incorrect behaviors. Whenever the
+  /// children list is modified, a new list object should be provided.
+  ///
+  /// ```dart
+  /// class SomeWidgetState extends State<SomeWidget> {
+  ///   List<Widget> _children;
+  ///
+  ///   void initState() {
+  ///     _children = [];
+  ///   }
+  ///
+  ///   void someHandler() {
+  ///     setState(() {
+  ///         _children.add(...);
+  ///     });
+  ///   }
+  ///
+  ///   Widget build(...) {
+  ///     // Reusing `List<Widget> _children` here is problematic.
+  ///     return Row(children: _children);
+  ///   }
+  /// }
+  /// ```
+  ///
+  /// The following code corrects the problem mentioned above.
+  ///
+  /// ```dart
+  /// class SomeWidgetState extends State<SomeWidget> {
+  ///   List<Widget> _children;
+  ///
+  ///   void initState() {
+  ///     _children = [];
+  ///   }
+  ///
+  ///   void someHandler() {
+  ///     setState(() {
+  ///       // The key here allows Flutter to reuse the underlying render
+  ///       // objects even if the children list is recreated.
+  ///       _children.add(ChildWidget(key: ...));
+  ///     });
+  ///   }
+  ///
+  ///   Widget build(...) {
+  ///     // Always create a new list of children as a Widget is immutable.
+  ///     return Row(children: List.from(_children));
+  ///   }
+  /// }
+  /// ```
+  final List<Widget> children;
+
+  @override
+  MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
+}
+
+
+// ELEMENTS
+
+enum _ElementLifecycle {
+  initial,
+  active,
+  inactive,
+  defunct,
+}
+
+class _InactiveElements {
+  bool _locked = false;
+  final Set<Element> _elements = HashSet<Element>();
+
+  void _unmount(Element element) {
+    assert(element._lifecycleState == _ElementLifecycle.inactive);
+    assert(() {
+      if (debugPrintGlobalKeyedWidgetLifecycle) {
+        if (element.widget.key is GlobalKey)
+          debugPrint('Discarding $element from inactive elements list.');
+      }
+      return true;
+    }());
+    element.visitChildren((Element child) {
+      assert(child._parent == element);
+      _unmount(child);
+    });
+    element.unmount();
+    assert(element._lifecycleState == _ElementLifecycle.defunct);
+  }
+
+  void _unmountAll() {
+    _locked = true;
+    final List<Element> elements = _elements.toList()..sort(Element._sort);
+    _elements.clear();
+    try {
+      elements.reversed.forEach(_unmount);
+    } finally {
+      assert(_elements.isEmpty);
+      _locked = false;
+    }
+  }
+
+  static void _deactivateRecursively(Element element) {
+    assert(element._lifecycleState == _ElementLifecycle.active);
+    element.deactivate();
+    assert(element._lifecycleState == _ElementLifecycle.inactive);
+    element.visitChildren(_deactivateRecursively);
+    assert(() {
+      element.debugDeactivated();
+      return true;
+    }());
+  }
+
+  void add(Element element) {
+    assert(!_locked);
+    assert(!_elements.contains(element));
+    assert(element._parent == null);
+    if (element._lifecycleState == _ElementLifecycle.active)
+      _deactivateRecursively(element);
+    _elements.add(element);
+  }
+
+  void remove(Element element) {
+    assert(!_locked);
+    assert(_elements.contains(element));
+    assert(element._parent == null);
+    _elements.remove(element);
+    assert(element._lifecycleState != _ElementLifecycle.active);
+  }
+
+  bool debugContains(Element element) {
+    late bool result;
+    assert(() {
+      result = _elements.contains(element);
+      return true;
+    }());
+    return result;
+  }
+}
+
+/// Signature for the callback to [BuildContext.visitChildElements].
+///
+/// The argument is the child being visited.
+///
+/// It is safe to call `element.visitChildElements` reentrantly within
+/// this callback.
+typedef ElementVisitor = void Function(Element element);
+
+/// A handle to the location of a widget in the widget tree.
+///
+/// This class presents a set of methods that can be used from
+/// [StatelessWidget.build] methods and from methods on [State] objects.
+///
+/// [BuildContext] objects are passed to [WidgetBuilder] functions (such as
+/// [StatelessWidget.build]), and are available from the [State.context] member.
+/// Some static functions (e.g. [showDialog], [Theme.of], and so forth) also
+/// take build contexts so that they can act on behalf of the calling widget, or
+/// obtain data specifically for the given context.
+///
+/// Each widget has its own [BuildContext], which becomes the parent of the
+/// widget returned by the [StatelessWidget.build] or [State.build] function.
+/// (And similarly, the parent of any children for [RenderObjectWidget]s.)
+///
+/// In particular, this means that within a build method, the build context of
+/// the widget of the build method is not the same as the build context of the
+/// widgets returned by that build method. This can lead to some tricky cases.
+/// For example, [Theme.of(context)] looks for the nearest enclosing [Theme] of
+/// the given build context. If a build method for a widget Q includes a [Theme]
+/// within its returned widget tree, and attempts to use [Theme.of] passing its
+/// own context, the build method for Q will not find that [Theme] object. It
+/// will instead find whatever [Theme] was an ancestor to the widget Q. If the
+/// build context for a subpart of the returned tree is needed, a [Builder]
+/// widget can be used: the build context passed to the [Builder.builder]
+/// callback will be that of the [Builder] itself.
+///
+/// For example, in the following snippet, the [ScaffoldState.showBottomSheet]
+/// method is called on the [Scaffold] widget that the build method itself
+/// creates. If a [Builder] had not been used, and instead the `context`
+/// argument of the build method itself had been used, no [Scaffold] would have
+/// been found, and the [Scaffold.of] function would have returned null.
+///
+/// ```dart
+/// @override
+/// Widget build(BuildContext context) {
+///   // here, Scaffold.of(context) returns null
+///   return Scaffold(
+///     appBar: const AppBar(title: Text('Demo')),
+///     body: Builder(
+///       builder: (BuildContext context) {
+///         return TextButton(
+///           child: const Text('BUTTON'),
+///           onPressed: () {
+///             Scaffold.of(context).showBottomSheet<void>(
+///               (BuildContext context) {
+///                 return Container(
+///                   alignment: Alignment.center,
+///                   height: 200,
+///                   color: Colors.amber,
+///                   child: Center(
+///                     child: Column(
+///                       mainAxisSize: MainAxisSize.min,
+///                       children: <Widget>[
+///                         const Text('BottomSheet'),
+///                         ElevatedButton(
+///                           child: const Text('Close BottomSheet'),
+///                           onPressed: () {
+///                             Navigator.pop(context),
+///                           },
+///                         )
+///                       ],
+///                     ),
+///                   ),
+///                 );
+///               },
+///             );
+///           },
+///         );
+///       },
+///     )
+///   );
+/// }
+/// ```
+///
+/// The [BuildContext] for a particular widget can change location over time as
+/// the widget is moved around the tree. Because of this, values returned from
+/// the methods on this class should not be cached beyond the execution of a
+/// single synchronous function.
+///
+/// [BuildContext] objects are actually [Element] objects. The [BuildContext]
+/// interface is used to discourage direct manipulation of [Element] objects.
+abstract class BuildContext {
+  /// The current configuration of the [Element] that is this [BuildContext].
+  Widget get widget;
+
+  /// The [BuildOwner] for this context. The [BuildOwner] is in charge of
+  /// managing the rendering pipeline for this context.
+  BuildOwner? get owner;
+
+  /// Whether the [widget] is currently updating the widget or render tree.
+  ///
+  /// For [StatefulWidget]s and [StatelessWidget]s this flag is true while
+  /// their respective build methods are executing.
+  /// [RenderObjectWidget]s set this to true while creating or configuring their
+  /// associated [RenderObject]s.
+  /// Other [Widget] types may set this to true for conceptually similar phases
+  /// of their lifecycle.
+  ///
+  /// When this is true, it is safe for [widget] to establish a dependency to an
+  /// [InheritedWidget] by calling [dependOnInheritedElement] or
+  /// [dependOnInheritedWidgetOfExactType].
+  ///
+  /// Accessing this flag in release mode is not valid.
+  bool get debugDoingBuild;
+
+  /// The current [RenderObject] for the widget. If the widget is a
+  /// [RenderObjectWidget], this is the render object that the widget created
+  /// for itself. Otherwise, it is the render object of the first descendant
+  /// [RenderObjectWidget].
+  ///
+  /// This method will only return a valid result after the build phase is
+  /// complete. It is therefore not valid to call this from a build method.
+  /// It should only be called from interaction event handlers (e.g.
+  /// gesture callbacks) or layout or paint callbacks.
+  ///
+  /// If the render object is a [RenderBox], which is the common case, then the
+  /// size of the render object can be obtained from the [size] getter. This is
+  /// only valid after the layout phase, and should therefore only be examined
+  /// from paint callbacks or interaction event handlers (e.g. gesture
+  /// callbacks).
+  ///
+  /// For details on the different phases of a frame, see the discussion at
+  /// [WidgetsBinding.drawFrame].
+  ///
+  /// Calling this method is theoretically relatively expensive (O(N) in the
+  /// depth of the tree), but in practice is usually cheap because the tree
+  /// usually has many render objects and therefore the distance to the nearest
+  /// render object is usually short.
+  RenderObject? findRenderObject();
+
+  /// The size of the [RenderBox] returned by [findRenderObject].
+  ///
+  /// This getter will only return a valid result after the layout phase is
+  /// complete. It is therefore not valid to call this from a build method.
+  /// It should only be called from paint callbacks or interaction event
+  /// handlers (e.g. gesture callbacks).
+  ///
+  /// For details on the different phases of a frame, see the discussion at
+  /// [WidgetsBinding.drawFrame].
+  ///
+  /// This getter will only return a valid result if [findRenderObject] actually
+  /// returns a [RenderBox]. If [findRenderObject] returns a render object that
+  /// is not a subtype of [RenderBox] (e.g., [RenderView]), this getter will
+  /// throw an exception in checked mode and will return null in release mode.
+  ///
+  /// Calling this getter is theoretically relatively expensive (O(N) in the
+  /// depth of the tree), but in practice is usually cheap because the tree
+  /// usually has many render objects and therefore the distance to the nearest
+  /// render object is usually short.
+  Size? get size;
+
+  /// Registers this build context with [ancestor] such that when
+  /// [ancestor]'s widget changes this build context is rebuilt.
+  ///
+  /// This method is deprecated. Please use [dependOnInheritedElement] instead.
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use dependOnInheritedElement instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect });
+
+  /// Registers this build context with [ancestor] such that when
+  /// [ancestor]'s widget changes this build context is rebuilt.
+  ///
+  /// Returns `ancestor.widget`.
+  ///
+  /// This method is rarely called directly. Most applications should use
+  /// [dependOnInheritedWidgetOfExactType], which calls this method after finding
+  /// the appropriate [InheritedElement] ancestor.
+  ///
+  /// All of the qualifications about when [dependOnInheritedWidgetOfExactType] can
+  /// be called apply to this method as well.
+  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect });
+
+  /// Obtains the nearest widget of the given type, which must be the type of a
+  /// concrete [InheritedWidget] subclass, and registers this build context with
+  /// that widget such that when that widget changes (or a new widget of that
+  /// type is introduced, or the widget goes away), this build context is
+  /// rebuilt so that it can obtain new values from that widget.
+  ///
+  /// This method is deprecated. Please use [dependOnInheritedWidgetOfExactType] instead.
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use dependOnInheritedWidgetOfExactType instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  InheritedWidget? inheritFromWidgetOfExactType(Type targetType, { Object? aspect });
+
+  /// Obtains the nearest widget of the given type `T`, which must be the type of a
+  /// concrete [InheritedWidget] subclass, and registers this build context with
+  /// that widget such that when that widget changes (or a new widget of that
+  /// type is introduced, or the widget goes away), this build context is
+  /// rebuilt so that it can obtain new values from that widget.
+  ///
+  /// This is typically called implicitly from `of()` static methods, e.g.
+  /// [Theme.of].
+  ///
+  /// This method should not be called from widget constructors or from
+  /// [State.initState] methods, because those methods would not get called
+  /// again if the inherited value were to change. To ensure that the widget
+  /// correctly updates itself when the inherited value changes, only call this
+  /// (directly or indirectly) from build methods, layout and paint callbacks, or
+  /// from [State.didChangeDependencies].
+  ///
+  /// This method should not be called from [State.dispose] because the element
+  /// tree is no longer stable at that time. To refer to an ancestor from that
+  /// method, save a reference to the ancestor in [State.didChangeDependencies].
+  /// It is safe to use this method from [State.deactivate], which is called
+  /// whenever the widget is removed from the tree.
+  ///
+  /// It is also possible to call this method from interaction event handlers
+  /// (e.g. gesture callbacks) or timers, to obtain a value once, if that value
+  /// is not going to be cached and reused later.
+  ///
+  /// Calling this method is O(1) with a small constant factor, but will lead to
+  /// the widget being rebuilt more often.
+  ///
+  /// Once a widget registers a dependency on a particular type by calling this
+  /// method, it will be rebuilt, and [State.didChangeDependencies] will be
+  /// called, whenever changes occur relating to that widget until the next time
+  /// the widget or one of its ancestors is moved (for example, because an
+  /// ancestor is added or removed).
+  ///
+  /// The [aspect] parameter is only used when `T` is an
+  /// [InheritedWidget] subclasses that supports partial updates, like
+  /// [InheritedModel]. It specifies what "aspect" of the inherited
+  /// widget this context depends on.
+  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
+
+  /// Obtains the element corresponding to the nearest widget of the given type,
+  /// which must be the type of a concrete [InheritedWidget] subclass.
+  ///
+  /// This method is deprecated. Please use [getElementForInheritedWidgetOfExactType] instead.
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use getElementForInheritedWidgetOfExactType instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  InheritedElement? ancestorInheritedElementForWidgetOfExactType(Type targetType);
+
+  /// Obtains the element corresponding to the nearest widget of the given type `T`,
+  /// which must be the type of a concrete [InheritedWidget] subclass.
+  ///
+  /// Returns null if no such element is found.
+  ///
+  /// Calling this method is O(1) with a small constant factor.
+  ///
+  /// This method does not establish a relationship with the target in the way
+  /// that [dependOnInheritedWidgetOfExactType] does.
+  ///
+  /// This method should not be called from [State.dispose] because the element
+  /// tree is no longer stable at that time. To refer to an ancestor from that
+  /// method, save a reference to the ancestor by calling
+  /// [dependOnInheritedWidgetOfExactType] in [State.didChangeDependencies]. It is
+  /// safe to use this method from [State.deactivate], which is called whenever
+  /// the widget is removed from the tree.
+  InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>();
+
+  /// Returns the nearest ancestor widget of the given type, which must be the
+  /// type of a concrete [Widget] subclass.
+  ///
+  /// This method is deprecated. Please use [findAncestorWidgetOfExactType] instead.
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use findAncestorWidgetOfExactType instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  Widget? ancestorWidgetOfExactType(Type targetType);
+
+  /// Returns the nearest ancestor widget of the given type `T`, which must be the
+  /// type of a concrete [Widget] subclass.
+  ///
+  /// In general, [dependOnInheritedWidgetOfExactType] is more useful, since
+  /// inherited widgets will trigger consumers to rebuild when they change. This
+  /// method is appropriate when used in interaction event handlers (e.g.
+  /// gesture callbacks) or for performing one-off tasks such as asserting that
+  /// you have or don't have a widget of a specific type as an ancestor. The
+  /// return value of a Widget's build method should not depend on the value
+  /// returned by this method, because the build context will not rebuild if the
+  /// return value of this method changes. This could lead to a situation where
+  /// data used in the build method changes, but the widget is not rebuilt.
+  ///
+  /// Calling this method is relatively expensive (O(N) in the depth of the
+  /// tree). Only call this method if the distance from this widget to the
+  /// desired ancestor is known to be small and bounded.
+  ///
+  /// This method should not be called from [State.deactivate] or [State.dispose]
+  /// because the widget tree is no longer stable at that time. To refer to
+  /// an ancestor from one of those methods, save a reference to the ancestor
+  /// by calling [findAncestorWidgetOfExactType] in [State.didChangeDependencies].
+  ///
+  /// Returns null if a widget of the requested type does not appear in the
+  /// ancestors of this context.
+  T? findAncestorWidgetOfExactType<T extends Widget>();
+
+  /// Returns the [State] object of the nearest ancestor [StatefulWidget] widget
+  /// that matches the given [TypeMatcher].
+  ///
+  /// This method is deprecated. Please use [findAncestorStateOfType] instead.
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use findAncestorStateOfType instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  State? ancestorStateOfType(TypeMatcher matcher);
+
+  /// Returns the [State] object of the nearest ancestor [StatefulWidget] widget
+  /// that is an instance of the given type `T`.
+  ///
+  /// This should not be used from build methods, because the build context will
+  /// not be rebuilt if the value that would be returned by this method changes.
+  /// In general, [dependOnInheritedWidgetOfExactType] is more appropriate for such
+  /// cases. This method is useful for changing the state of an ancestor widget in
+  /// a one-off manner, for example, to cause an ancestor scrolling list to
+  /// scroll this build context's widget into view, or to move the focus in
+  /// response to user interaction.
+  ///
+  /// In general, though, consider using a callback that triggers a stateful
+  /// change in the ancestor rather than using the imperative style implied by
+  /// this method. This will usually lead to more maintainable and reusable code
+  /// since it decouples widgets from each other.
+  ///
+  /// Calling this method is relatively expensive (O(N) in the depth of the
+  /// tree). Only call this method if the distance from this widget to the
+  /// desired ancestor is known to be small and bounded.
+  ///
+  /// This method should not be called from [State.deactivate] or [State.dispose]
+  /// because the widget tree is no longer stable at that time. To refer to
+  /// an ancestor from one of those methods, save a reference to the ancestor
+  /// by calling [findAncestorStateOfType] in [State.didChangeDependencies].
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// ScrollableState scrollable = context.findAncestorStateOfType<ScrollableState>();
+  /// ```
+  /// {@end-tool}
+  T? findAncestorStateOfType<T extends State>();
+
+  /// Returns the [State] object of the furthest ancestor [StatefulWidget] widget
+  /// that matches the given [TypeMatcher].
+  ///
+  /// This method is deprecated. Please use [findRootAncestorStateOfType] instead.
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use findRootAncestorStateOfType instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  State? rootAncestorStateOfType(TypeMatcher matcher);
+
+  /// Returns the [State] object of the furthest ancestor [StatefulWidget] widget
+  /// that is an instance of the given type `T`.
+  ///
+  /// Functions the same way as [findAncestorStateOfType] but keeps visiting subsequent
+  /// ancestors until there are none of the type instance of `T` remaining.
+  /// Then returns the last one found.
+  ///
+  /// This operation is O(N) as well though N is the entire widget tree rather than
+  /// a subtree.
+  T? findRootAncestorStateOfType<T extends State>();
+
+  /// Returns the [RenderObject] object of the nearest ancestor [RenderObjectWidget] widget
+  /// that matches the given [TypeMatcher].
+  ///
+  /// This method is deprecated. Please use [findAncestorRenderObjectOfType] instead.
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use findAncestorRenderObjectOfType instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  RenderObject? ancestorRenderObjectOfType(TypeMatcher matcher);
+
+  /// Returns the [RenderObject] object of the nearest ancestor [RenderObjectWidget] widget
+  /// that is an instance of the given type `T`.
+  ///
+  /// This should not be used from build methods, because the build context will
+  /// not be rebuilt if the value that would be returned by this method changes.
+  /// In general, [dependOnInheritedWidgetOfExactType] is more appropriate for such
+  /// cases. This method is useful only in esoteric cases where a widget needs
+  /// to cause an ancestor to change its layout or paint behavior. For example,
+  /// it is used by [Material] so that [InkWell] widgets can trigger the ink
+  /// splash on the [Material]'s actual render object.
+  ///
+  /// Calling this method is relatively expensive (O(N) in the depth of the
+  /// tree). Only call this method if the distance from this widget to the
+  /// desired ancestor is known to be small and bounded.
+  ///
+  /// This method should not be called from [State.deactivate] or [State.dispose]
+  /// because the widget tree is no longer stable at that time. To refer to
+  /// an ancestor from one of those methods, save a reference to the ancestor
+  /// by calling [findAncestorRenderObjectOfType] in [State.didChangeDependencies].
+  T? findAncestorRenderObjectOfType<T extends RenderObject>();
+
+  /// Walks the ancestor chain, starting with the parent of this build context's
+  /// widget, invoking the argument for each ancestor. The callback is given a
+  /// reference to the ancestor widget's corresponding [Element] object. The
+  /// walk stops when it reaches the root widget or when the callback returns
+  /// false. The callback must not return null.
+  ///
+  /// This is useful for inspecting the widget tree.
+  ///
+  /// Calling this method is relatively expensive (O(N) in the depth of the tree).
+  ///
+  /// This method should not be called from [State.deactivate] or [State.dispose]
+  /// because the element tree is no longer stable at that time. To refer to
+  /// an ancestor from one of those methods, save a reference to the ancestor
+  /// by calling [visitAncestorElements] in [State.didChangeDependencies].
+  void visitAncestorElements(bool visitor(Element element));
+
+  /// Walks the children of this widget.
+  ///
+  /// This is useful for applying changes to children after they are built
+  /// without waiting for the next frame, especially if the children are known,
+  /// and especially if there is exactly one child (as is always the case for
+  /// [StatefulWidget]s or [StatelessWidget]s).
+  ///
+  /// Calling this method is very cheap for build contexts that correspond to
+  /// [StatefulWidget]s or [StatelessWidget]s (O(1), since there's only one
+  /// child).
+  ///
+  /// Calling this method is potentially expensive for build contexts that
+  /// correspond to [RenderObjectWidget]s (O(N) in the number of children).
+  ///
+  /// Calling this method recursively is extremely expensive (O(N) in the number
+  /// of descendants), and should be avoided if possible. Generally it is
+  /// significantly cheaper to use an [InheritedWidget] and have the descendants
+  /// pull data down, than it is to use [visitChildElements] recursively to push
+  /// data down to them.
+  void visitChildElements(ElementVisitor visitor);
+
+  /// Returns a description of an [Element] from the current build context.
+  DiagnosticsNode describeElement(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty});
+
+  /// Returns a description of the [Widget] associated with the current build context.
+  DiagnosticsNode describeWidget(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty});
+
+  /// Adds a description of a specific type of widget missing from the current
+  /// build context's ancestry tree.
+  ///
+  /// You can find an example of using this method in [debugCheckHasMaterial].
+  List<DiagnosticsNode> describeMissingAncestor({ required Type expectedAncestorType });
+
+  /// Adds a description of the ownership chain from a specific [Element]
+  /// to the error report.
+  ///
+  /// The ownership chain is useful for debugging the source of an element.
+  DiagnosticsNode describeOwnershipChain(String name);
+}
+
+/// Manager class for the widgets framework.
+///
+/// This class tracks which widgets need rebuilding, and handles other tasks
+/// that apply to widget trees as a whole, such as managing the inactive element
+/// list for the tree and triggering the "reassemble" command when necessary
+/// during hot reload when debugging.
+///
+/// The main build owner is typically owned by the [WidgetsBinding], and is
+/// driven from the operating system along with the rest of the
+/// build/layout/paint pipeline.
+///
+/// Additional build owners can be built to manage off-screen widget trees.
+///
+/// To assign a build owner to a tree, use the
+/// [RootRenderObjectElement.assignOwner] method on the root element of the
+/// widget tree.
+class BuildOwner {
+  /// Creates an object that manages widgets.
+  BuildOwner({ this.onBuildScheduled, FocusManager? focusManager }) :
+      focusManager = focusManager ?? FocusManager();
+
+  /// Called on each build pass when the first buildable element is marked
+  /// dirty.
+  VoidCallback? onBuildScheduled;
+
+  final _InactiveElements _inactiveElements = _InactiveElements();
+
+  final List<Element> _dirtyElements = <Element>[];
+  bool _scheduledFlushDirtyElements = false;
+
+  /// Whether [_dirtyElements] need to be sorted again as a result of more
+  /// elements becoming dirty during the build.
+  ///
+  /// This is necessary to preserve the sort order defined by [Element._sort].
+  ///
+  /// This field is set to null when [buildScope] is not actively rebuilding
+  /// the widget tree.
+  bool? _dirtyElementsNeedsResorting;
+
+  /// Whether [buildScope] is actively rebuilding the widget tree.
+  ///
+  /// [scheduleBuildFor] should only be called when this value is true.
+  bool get _debugIsInBuildScope => _dirtyElementsNeedsResorting != null;
+
+  /// The object in charge of the focus tree.
+  ///
+  /// Rarely used directly. Instead, consider using [FocusScope.of] to obtain
+  /// the [FocusScopeNode] for a given [BuildContext].
+  ///
+  /// See [FocusManager] for more details.
+  FocusManager focusManager;
+
+  /// Adds an element to the dirty elements list so that it will be rebuilt
+  /// when [WidgetsBinding.drawFrame] calls [buildScope].
+  void scheduleBuildFor(Element element) {
+    assert(element != null);
+    assert(element.owner == this);
+    assert(() {
+      if (debugPrintScheduleBuildForStacks)
+        debugPrintStack(label: 'scheduleBuildFor() called for $element${_dirtyElements.contains(element) ? " (ALREADY IN LIST)" : ""}');
+      if (!element.dirty) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('scheduleBuildFor() called for a widget that is not marked as dirty.'),
+          element.describeElement('The method was called for the following element'),
+          ErrorDescription(
+            'This element is not current marked as dirty. Make sure to set the dirty flag before '
+            'calling scheduleBuildFor().'),
+          ErrorHint(
+            'If you did not attempt to call scheduleBuildFor() yourself, then this probably '
+            'indicates a bug in the widgets framework. Please report it:\n'
+            '  https://github.com/flutter/flutter/issues/new?template=2_bug.md'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    if (element._inDirtyList) {
+      assert(() {
+        if (debugPrintScheduleBuildForStacks)
+          debugPrintStack(label: 'BuildOwner.scheduleBuildFor() called; _dirtyElementsNeedsResorting was $_dirtyElementsNeedsResorting (now true); dirty list is: $_dirtyElements');
+        if (!_debugIsInBuildScope) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('BuildOwner.scheduleBuildFor() called inappropriately.'),
+            ErrorHint(
+              'The BuildOwner.scheduleBuildFor() method should only be called while the '
+              'buildScope() method is actively rebuilding the widget tree.'
+            ),
+          ]);
+        }
+        return true;
+      }());
+      _dirtyElementsNeedsResorting = true;
+      return;
+    }
+    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
+      _scheduledFlushDirtyElements = true;
+      onBuildScheduled!();
+    }
+    _dirtyElements.add(element);
+    element._inDirtyList = true;
+    assert(() {
+      if (debugPrintScheduleBuildForStacks)
+        debugPrint('...dirty list is now: $_dirtyElements');
+      return true;
+    }());
+  }
+
+  int _debugStateLockLevel = 0;
+  bool get _debugStateLocked => _debugStateLockLevel > 0;
+
+  /// Whether this widget tree is in the build phase.
+  ///
+  /// Only valid when asserts are enabled.
+  bool get debugBuilding => _debugBuilding;
+  bool _debugBuilding = false;
+  Element? _debugCurrentBuildTarget;
+
+  /// Establishes a scope in which calls to [State.setState] are forbidden, and
+  /// calls the given `callback`.
+  ///
+  /// This mechanism is used to ensure that, for instance, [State.dispose] does
+  /// not call [State.setState].
+  void lockState(void callback()) {
+    assert(callback != null);
+    assert(_debugStateLockLevel >= 0);
+    assert(() {
+      _debugStateLockLevel += 1;
+      return true;
+    }());
+    try {
+      callback();
+    } finally {
+      assert(() {
+        _debugStateLockLevel -= 1;
+        return true;
+      }());
+    }
+    assert(_debugStateLockLevel >= 0);
+  }
+
+  /// Establishes a scope for updating the widget tree, and calls the given
+  /// `callback`, if any. Then, builds all the elements that were marked as
+  /// dirty using [scheduleBuildFor], in depth order.
+  ///
+  /// This mechanism prevents build methods from transitively requiring other
+  /// build methods to run, potentially causing infinite loops.
+  ///
+  /// The dirty list is processed after `callback` returns, building all the
+  /// elements that were marked as dirty using [scheduleBuildFor], in depth
+  /// order. If elements are marked as dirty while this method is running, they
+  /// must be deeper than the `context` node, and deeper than any
+  /// previously-built node in this pass.
+  ///
+  /// To flush the current dirty list without performing any other work, this
+  /// function can be called with no callback. This is what the framework does
+  /// each frame, in [WidgetsBinding.drawFrame].
+  ///
+  /// Only one [buildScope] can be active at a time.
+  ///
+  /// A [buildScope] implies a [lockState] scope as well.
+  ///
+  /// To print a console message every time this method is called, set
+  /// [debugPrintBuildScope] to true. This is useful when debugging problems
+  /// involving widgets not getting marked dirty, or getting marked dirty too
+  /// often.
+  void buildScope(Element context, [ VoidCallback? callback ]) {
+    if (callback == null && _dirtyElements.isEmpty)
+      return;
+    assert(context != null);
+    assert(_debugStateLockLevel >= 0);
+    assert(!_debugBuilding);
+    assert(() {
+      if (debugPrintBuildScope)
+        debugPrint('buildScope called with context $context; dirty list is: $_dirtyElements');
+      _debugStateLockLevel += 1;
+      _debugBuilding = true;
+      return true;
+    }());
+    Timeline.startSync('Build', arguments: timelineArgumentsIndicatingLandmarkEvent);
+    try {
+      _scheduledFlushDirtyElements = true;
+      if (callback != null) {
+        assert(_debugStateLocked);
+        Element? debugPreviousBuildTarget;
+        assert(() {
+          context._debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
+          debugPreviousBuildTarget = _debugCurrentBuildTarget;
+          _debugCurrentBuildTarget = context;
+          return true;
+        }());
+        _dirtyElementsNeedsResorting = false;
+        try {
+          callback();
+        } finally {
+          assert(() {
+            context._debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
+            assert(_debugCurrentBuildTarget == context);
+            _debugCurrentBuildTarget = debugPreviousBuildTarget;
+            _debugElementWasRebuilt(context);
+            return true;
+          }());
+        }
+      }
+      _dirtyElements.sort(Element._sort);
+      _dirtyElementsNeedsResorting = false;
+      int dirtyCount = _dirtyElements.length;
+      int index = 0;
+      while (index < dirtyCount) {
+        assert(_dirtyElements[index] != null);
+        assert(_dirtyElements[index]._inDirtyList);
+        assert(() {
+          if (_dirtyElements[index]._lifecycleState == _ElementLifecycle.active && !_dirtyElements[index]._debugIsInScope(context)) {
+            throw FlutterError.fromParts(<DiagnosticsNode>[
+              ErrorSummary('Tried to build dirty widget in the wrong build scope.'),
+              ErrorDescription(
+                'A widget which was marked as dirty and is still active was scheduled to be built, '
+                'but the current build scope unexpectedly does not contain that widget.',
+              ),
+              ErrorHint(
+                'Sometimes this is detected when an element is removed from the widget tree, but the '
+                'element somehow did not get marked as inactive. In that case, it might be caused by '
+                'an ancestor element failing to implement visitChildren correctly, thus preventing '
+                'some or all of its descendants from being correctly deactivated.',
+              ),
+              DiagnosticsProperty<Element>(
+                'The root of the build scope was',
+                context,
+                style: DiagnosticsTreeStyle.errorProperty,
+              ),
+              DiagnosticsProperty<Element>(
+                'The offending element (which does not appear to be a descendant of the root of the build scope) was',
+                _dirtyElements[index],
+                style: DiagnosticsTreeStyle.errorProperty,
+              ),
+            ]);
+          }
+          return true;
+        }());
+        try {
+          _dirtyElements[index].rebuild();
+        } catch (e, stack) {
+          _debugReportException(
+            ErrorDescription('while rebuilding dirty elements'),
+            e,
+            stack,
+            informationCollector: () sync* {
+              if (index < _dirtyElements.length) {
+                yield DiagnosticsDebugCreator(DebugCreator(_dirtyElements[index]));
+                yield _dirtyElements[index].describeElement('The element being rebuilt at the time was index $index of $dirtyCount');
+              } else {
+                yield ErrorHint('The element being rebuilt at the time was index $index of $dirtyCount, but _dirtyElements only had ${_dirtyElements.length} entries. This suggests some confusion in the framework internals.');
+              }
+            },
+          );
+        }
+        index += 1;
+        if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
+          _dirtyElements.sort(Element._sort);
+          _dirtyElementsNeedsResorting = false;
+          dirtyCount = _dirtyElements.length;
+          while (index > 0 && _dirtyElements[index - 1].dirty) {
+            // It is possible for previously dirty but inactive widgets to move right in the list.
+            // We therefore have to move the index left in the list to account for this.
+            // We don't know how many could have moved. However, we do know that the only possible
+            // change to the list is that nodes that were previously to the left of the index have
+            // now moved to be to the right of the right-most cleaned node, and we do know that
+            // all the clean nodes were to the left of the index. So we move the index left
+            // until just after the right-most clean node.
+            index -= 1;
+          }
+        }
+      }
+      assert(() {
+        if (_dirtyElements.any((Element element) => element._lifecycleState == _ElementLifecycle.active && element.dirty)) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('buildScope missed some dirty elements.'),
+            ErrorHint('This probably indicates that the dirty list should have been resorted but was not.'),
+            Element.describeElements('The list of dirty elements at the end of the buildScope call was', _dirtyElements),
+          ]);
+        }
+        return true;
+      }());
+    } finally {
+      for (final Element element in _dirtyElements) {
+        assert(element._inDirtyList);
+        element._inDirtyList = false;
+      }
+      _dirtyElements.clear();
+      _scheduledFlushDirtyElements = false;
+      _dirtyElementsNeedsResorting = null;
+      Timeline.finishSync();
+      assert(_debugBuilding);
+      assert(() {
+        _debugBuilding = false;
+        _debugStateLockLevel -= 1;
+        if (debugPrintBuildScope)
+          debugPrint('buildScope finished');
+        return true;
+      }());
+    }
+    assert(_debugStateLockLevel >= 0);
+  }
+
+  Map<Element, Set<GlobalKey>>? _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans;
+
+  void _debugTrackElementThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans(Element node, GlobalKey key) {
+    _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans ??= HashMap<Element, Set<GlobalKey>>();
+    final Set<GlobalKey> keys = _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans!
+      .putIfAbsent(node, () => HashSet<GlobalKey>());
+    keys.add(key);
+  }
+
+  void _debugElementWasRebuilt(Element node) {
+    _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans?.remove(node);
+  }
+
+  /// Complete the element build pass by unmounting any elements that are no
+  /// longer active.
+  ///
+  /// This is called by [WidgetsBinding.drawFrame].
+  ///
+  /// In debug mode, this also runs some sanity checks, for example checking for
+  /// duplicate global keys.
+  ///
+  /// After the current call stack unwinds, a microtask that notifies listeners
+  /// about changes to global keys will run.
+  void finalizeTree() {
+    Timeline.startSync('Finalize tree', arguments: timelineArgumentsIndicatingLandmarkEvent);
+    try {
+      lockState(() {
+        _inactiveElements._unmountAll(); // this unregisters the GlobalKeys
+      });
+      assert(() {
+        try {
+          GlobalKey._debugVerifyGlobalKeyReservation();
+          GlobalKey._debugVerifyIllFatedPopulation();
+          if (_debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans != null &&
+              _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans!.isNotEmpty) {
+            final Set<GlobalKey> keys = HashSet<GlobalKey>();
+            for (final Element element in _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans!.keys) {
+              if (element._lifecycleState != _ElementLifecycle.defunct)
+                keys.addAll(_debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans![element]!);
+            }
+            if (keys.isNotEmpty) {
+              final Map<String, int> keyStringCount = HashMap<String, int>();
+              for (final String key in keys.map<String>((GlobalKey key) => key.toString())) {
+                if (keyStringCount.containsKey(key)) {
+                  keyStringCount.update(key, (int value) => value + 1);
+                } else {
+                  keyStringCount[key] = 1;
+                }
+              }
+              final List<String> keyLabels = <String>[];
+              keyStringCount.forEach((String key, int count) {
+                if (count == 1) {
+                  keyLabels.add(key);
+                } else {
+                  keyLabels.add('$key ($count different affected keys had this toString representation)');
+                }
+              });
+              final Iterable<Element> elements = _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans!.keys;
+              final Map<String, int> elementStringCount = HashMap<String, int>();
+              for (final String element in elements.map<String>((Element element) => element.toString())) {
+                if (elementStringCount.containsKey(element)) {
+                  elementStringCount.update(element, (int value) => value + 1);
+                } else {
+                  elementStringCount[element] = 1;
+                }
+              }
+              final List<String> elementLabels = <String>[];
+              elementStringCount.forEach((String element, int count) {
+                if (count == 1) {
+                  elementLabels.add(element);
+                } else {
+                  elementLabels.add('$element ($count different affected elements had this toString representation)');
+                }
+              });
+              assert(keyLabels.isNotEmpty);
+              final String the = keys.length == 1 ? ' the' : '';
+              final String s = keys.length == 1 ? '' : 's';
+              final String were = keys.length == 1 ? 'was' : 'were';
+              final String their = keys.length == 1 ? 'its' : 'their';
+              final String respective = elementLabels.length == 1 ? '' : ' respective';
+              final String those = keys.length == 1 ? 'that' : 'those';
+              final String s2 = elementLabels.length == 1 ? '' : 's';
+              final String those2 = elementLabels.length == 1 ? 'that' : 'those';
+              final String they = elementLabels.length == 1 ? 'it' : 'they';
+              final String think = elementLabels.length == 1 ? 'thinks' : 'think';
+              final String are = elementLabels.length == 1 ? 'is' : 'are';
+              // TODO(jacobr): make this error more structured to better expose which widgets had problems.
+              throw FlutterError.fromParts(<DiagnosticsNode>[
+                ErrorSummary('Duplicate GlobalKey$s detected in widget tree.'),
+                // TODO(jacobr): refactor this code so the elements are clickable
+                // in GUI debug tools.
+                ErrorDescription(
+                  'The following GlobalKey$s $were specified multiple times in the widget tree. This will lead to '
+                  'parts of the widget tree being truncated unexpectedly, because the second time a key is seen, '
+                  'the previous instance is moved to the new location. The key$s $were:\n'
+                  '- ${keyLabels.join("\n  ")}\n'
+                  'This was determined by noticing that after$the widget$s with the above global key$s $were moved '
+                  'out of $their$respective previous parent$s2, $those2 previous parent$s2 never updated during this frame, meaning '
+                  'that $they either did not update at all or updated before the widget$s $were moved, in either case '
+                  'implying that $they still $think that $they should have a child with $those global key$s.\n'
+                  'The specific parent$s2 that did not update after having one or more children forcibly removed '
+                  'due to GlobalKey reparenting $are:\n'
+                  '- ${elementLabels.join("\n  ")}'
+                  '\nA GlobalKey can only be specified on one widget at a time in the widget tree.'
+                ),
+              ]);
+            }
+          }
+        } finally {
+          _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans?.clear();
+        }
+        return true;
+      }());
+    } catch (e, stack) {
+      // Catching the exception directly to avoid activating the ErrorWidget.
+      // Since the tree is in a broken state, adding the ErrorWidget would
+      // cause more exceptions.
+      _debugReportException(ErrorSummary('while finalizing the widget tree'), e, stack);
+    } finally {
+      Timeline.finishSync();
+    }
+  }
+
+  /// Cause the entire subtree rooted at the given [Element] to be entirely
+  /// rebuilt. This is used by development tools when the application code has
+  /// changed and is being hot-reloaded, to cause the widget tree to pick up any
+  /// changed implementations.
+  ///
+  /// This is expensive and should not be called except during development.
+  void reassemble(Element root) {
+    Timeline.startSync('Dirty Element Tree');
+    try {
+      assert(root._parent == null);
+      assert(root.owner == this);
+      root.reassemble();
+    } finally {
+      Timeline.finishSync();
+    }
+  }
+}
+
+/// An instantiation of a [Widget] at a particular location in the tree.
+///
+/// Widgets describe how to configure a subtree but the same widget can be used
+/// to configure multiple subtrees simultaneously because widgets are immutable.
+/// An [Element] represents the use of a widget to configure a specific location
+/// in the tree. Over time, the widget associated with a given element can
+/// change, for example, if the parent widget rebuilds and creates a new widget
+/// for this location.
+///
+/// Elements form a tree. Most elements have a unique child, but some widgets
+/// (e.g., subclasses of [RenderObjectElement]) can have multiple children.
+///
+/// Elements have the following lifecycle:
+///
+///  * The framework creates an element by calling [Widget.createElement] on the
+///    widget that will be used as the element's initial configuration.
+///  * The framework calls [mount] to add the newly created element to the tree
+///    at a given slot in a given parent. The [mount] method is responsible for
+///    inflating any child widgets and calling [attachRenderObject] as
+///    necessary to attach any associated render objects to the render tree.
+///  * At this point, the element is considered "active" and might appear on
+///    screen.
+///  * At some point, the parent might decide to change the widget used to
+///    configure this element, for example because the parent rebuilt with new
+///    state. When this happens, the framework will call [update] with the new
+///    widget. The new widget will always have the same [runtimeType] and key as
+///    old widget. If the parent wishes to change the [runtimeType] or key of
+///    the widget at this location in the tree, it can do so by unmounting this
+///    element and inflating the new widget at this location.
+///  * At some point, an ancestor might decide to remove this element (or an
+///    intermediate ancestor) from the tree, which the ancestor does by calling
+///    [deactivateChild] on itself. Deactivating the intermediate ancestor will
+///    remove that element's render object from the render tree and add this
+///    element to the [owner]'s list of inactive elements, causing the framework
+///    to call [deactivate] on this element.
+///  * At this point, the element is considered "inactive" and will not appear
+///    on screen. An element can remain in the inactive state only until
+///    the end of the current animation frame. At the end of the animation
+///    frame, any elements that are still inactive will be unmounted.
+///  * If the element gets reincorporated into the tree (e.g., because it or one
+///    of its ancestors has a global key that is reused), the framework will
+///    remove the element from the [owner]'s list of inactive elements, call
+///    [activate] on the element, and reattach the element's render object to
+///    the render tree. (At this point, the element is again considered "active"
+///    and might appear on screen.)
+///  * If the element does not get reincorporated into the tree by the end of
+///    the current animation frame, the framework will call [unmount] on the
+///    element.
+///  * At this point, the element is considered "defunct" and will not be
+///    incorporated into the tree in the future.
+abstract class Element extends DiagnosticableTree implements BuildContext {
+  /// Creates an element that uses the given widget as its configuration.
+  ///
+  /// Typically called by an override of [Widget.createElement].
+  Element(Widget widget)
+    : assert(widget != null),
+      _widget = widget;
+
+  Element? _parent;
+
+  // Custom implementation of `operator ==` optimized for the ".of" pattern
+  // used with `InheritedWidgets`.
+  @nonVirtual
+  @override
+  // ignore: avoid_equals_and_hash_code_on_mutable_classes
+  bool operator ==(Object other) => identical(this, other);
+
+  // Custom implementation of hash code optimized for the ".of" pattern used
+  // with `InheritedWidgets`.
+  //
+  // `Element.dependOnInheritedWidgetOfExactType` relies heavily on hash-based
+  // `Set` look-ups, putting this getter on the performance critical path.
+  //
+  // The value is designed to fit within the SMI representation. This makes
+  // the cached value use less memory (one field and no extra heap objects) and
+  // cheap to compare (no indirection).
+  //
+  // See also:
+  //
+  //  * https://dart.dev/articles/dart-vm/numeric-computation, which
+  //    explains how numbers are represented in Dart.
+  @nonVirtual
+  @override
+  // ignore: avoid_equals_and_hash_code_on_mutable_classes
+  int get hashCode => _cachedHash;
+  final int _cachedHash = _nextHashCode = (_nextHashCode + 1) % 0xffffff;
+  static int _nextHashCode = 1;
+
+  /// Information set by parent to define where this child fits in its parent's
+  /// child list.
+  ///
+  /// Subclasses of Element that only have one child should use null for
+  /// the slot for that child.
+  dynamic get slot => _slot;
+  dynamic _slot;
+
+  /// An integer that is guaranteed to be greater than the parent's, if any.
+  /// The element at the root of the tree must have a depth greater than 0.
+  int get depth {
+    assert(() {
+      if (_lifecycleState == _ElementLifecycle.initial) {
+        throw FlutterError('Depth is only available when element has been mounted.');
+      }
+      return true;
+    }());
+    return _depth;
+  }
+  late int _depth;
+
+  static int _sort(Element a, Element b) {
+    if (a.depth < b.depth)
+      return -1;
+    if (b.depth < a.depth)
+      return 1;
+    if (b.dirty && !a.dirty)
+      return -1;
+    if (a.dirty && !b.dirty)
+      return 1;
+    return 0;
+  }
+
+  // Return a numeric encoding of the specific `Element` concrete subtype.
+  // This is used in `Element.updateChild` to determine if a hot reload modified the
+  // superclass of a mounted element's configuration. The encoding of each `Element`
+  // must match the corresponding `Widget` encoding in `Widget._debugConcreteSubtype`.
+  static int _debugConcreteSubtype(Element element) {
+    return element is StatefulElement ? 1 :
+           element is StatelessElement ? 2 :
+           0;
+  }
+
+  /// The configuration for this element.
+  @override
+  Widget get widget => _widget;
+  Widget _widget;
+
+  /// The object that manages the lifecycle of this element.
+  @override
+  BuildOwner? get owner => _owner;
+  BuildOwner? _owner;
+
+  /// {@template flutter.widgets.Element.reassemble}
+  /// Called whenever the application is reassembled during debugging, for
+  /// example during hot reload.
+  ///
+  /// This method should rerun any initialization logic that depends on global
+  /// state, for example, image loading from asset bundles (since the asset
+  /// bundle may have changed).
+  ///
+  /// This function will only be called during development. In release builds,
+  /// the `ext.flutter.reassemble` hook is not available, and so this code will
+  /// never execute.
+  ///
+  /// Implementers should not rely on any ordering for hot reload source update,
+  /// reassemble, and build methods after a hot reload has been initiated. It is
+  /// possible that a [Timer] (e.g. an [Animation]) or a debugging session
+  /// attached to the isolate could trigger a build with reloaded code _before_
+  /// reassemble is called. Code that expects preconditions to be set by
+  /// reassemble after a hot reload must be resilient to being called out of
+  /// order, e.g. by fizzling instead of throwing. That said, once reassemble is
+  /// called, build will be called after it at least once.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [State.reassemble]
+  ///  * [BindingBase.reassembleApplication]
+  ///  * [Image], which uses this to reload images.
+  @mustCallSuper
+  @protected
+  void reassemble() {
+    markNeedsBuild();
+    visitChildren((Element child) {
+      child.reassemble();
+    });
+  }
+
+  bool _debugIsInScope(Element target) {
+    Element? current = this;
+    while (current != null) {
+      if (target == current)
+        return true;
+      current = current._parent;
+    }
+    return false;
+  }
+
+  /// The render object at (or below) this location in the tree.
+  ///
+  /// If this object is a [RenderObjectElement], the render object is the one at
+  /// this location in the tree. Otherwise, this getter will walk down the tree
+  /// until it finds a [RenderObjectElement].
+  RenderObject? get renderObject {
+    RenderObject? result;
+    void visit(Element element) {
+      assert(result == null); // this verifies that there's only one child
+      if (element is RenderObjectElement)
+        result = element.renderObject;
+      else
+        element.visitChildren(visit);
+    }
+    visit(this);
+    return result;
+  }
+
+  @override
+  List<DiagnosticsNode> describeMissingAncestor({ required Type expectedAncestorType }) {
+    final List<DiagnosticsNode> information = <DiagnosticsNode>[];
+    final List<Element> ancestors = <Element>[];
+    visitAncestorElements((Element element) {
+      ancestors.add(element);
+      return true;
+    });
+
+    information.add(DiagnosticsProperty<Element>(
+      'The specific widget that could not find a $expectedAncestorType ancestor was',
+      this,
+      style: DiagnosticsTreeStyle.errorProperty,
+    ));
+
+    if (ancestors.isNotEmpty) {
+      information.add(describeElements('The ancestors of this widget were', ancestors));
+    } else {
+      information.add(ErrorDescription(
+        'This widget is the root of the tree, so it has no '
+        'ancestors, let alone a "$expectedAncestorType" ancestor.'
+      ));
+    }
+    return information;
+  }
+
+  /// Returns a list of [Element]s from the current build context to the error report.
+  static DiagnosticsNode describeElements(String name, Iterable<Element> elements) {
+    return DiagnosticsBlock(
+      name: name,
+      children: elements.map<DiagnosticsNode>((Element element) => DiagnosticsProperty<Element>('', element)).toList(),
+      allowTruncate: true,
+    );
+  }
+
+  @override
+  DiagnosticsNode describeElement(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty}) {
+    return DiagnosticsProperty<Element>(name, this, style: style);
+  }
+
+  @override
+  DiagnosticsNode describeWidget(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty}) {
+    return DiagnosticsProperty<Element>(name, this, style: style);
+  }
+
+  @override
+  DiagnosticsNode describeOwnershipChain(String name) {
+    // TODO(jacobr): make this structured so clients can support clicks on
+    // individual entries. For example, is this an iterable with arrows as
+    // separators?
+    return StringProperty(name, debugGetCreatorChain(10));
+  }
+
+  // This is used to verify that Element objects move through life in an
+  // orderly fashion.
+  _ElementLifecycle _lifecycleState = _ElementLifecycle.initial;
+
+  /// Calls the argument for each child. Must be overridden by subclasses that
+  /// support having children.
+  ///
+  /// There is no guaranteed order in which the children will be visited, though
+  /// it should be consistent over time.
+  ///
+  /// Calling this during build is dangerous: the child list might still be
+  /// being updated at that point, so the children might not be constructed yet,
+  /// or might be old children that are going to be replaced. This method should
+  /// only be called if it is provable that the children are available.
+  void visitChildren(ElementVisitor visitor) { }
+
+  /// Calls the argument for each child considered onstage.
+  ///
+  /// Classes like [Offstage] and [Overlay] override this method to hide their
+  /// children.
+  ///
+  /// Being onstage affects the element's discoverability during testing when
+  /// you use Flutter's [Finder] objects. For example, when you instruct the
+  /// test framework to tap on a widget, by default the finder will look for
+  /// onstage elements and ignore the offstage ones.
+  ///
+  /// The default implementation defers to [visitChildren] and therefore treats
+  /// the element as onstage.
+  ///
+  /// See also:
+  ///
+  ///  * [Offstage] widget that hides its children.
+  ///  * [Finder] that skips offstage widgets by default.
+  ///  * [RenderObject.visitChildrenForSemantics], in contrast to this method,
+  ///    designed specifically for excluding parts of the UI from the semantics
+  ///    tree.
+  void debugVisitOnstageChildren(ElementVisitor visitor) => visitChildren(visitor);
+
+  /// Wrapper around [visitChildren] for [BuildContext].
+  @override
+  void visitChildElements(ElementVisitor visitor) {
+    assert(() {
+      if (owner == null || !owner!._debugStateLocked)
+        return true;
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('visitChildElements() called during build.'),
+        ErrorDescription(
+          "The BuildContext.visitChildElements() method can't be called during "
+          'build because the child list is still being updated at that point, '
+          'so the children might not be constructed yet, or might be old children '
+          'that are going to be replaced.'
+        ),
+      ]);
+    }());
+    visitChildren(visitor);
+  }
+
+  /// Update the given child with the given new configuration.
+  ///
+  /// This method is the core of the widgets system. It is called each time we
+  /// are to add, update, or remove a child based on an updated configuration.
+  ///
+  /// The `newSlot` argument specifies the new value for this element's [slot].
+  ///
+  /// If the `child` is null, and the `newWidget` is not null, then we have a new
+  /// child for which we need to create an [Element], configured with `newWidget`.
+  ///
+  /// If the `newWidget` is null, and the `child` is not null, then we need to
+  /// remove it because it no longer has a configuration.
+  ///
+  /// If neither are null, then we need to update the `child`'s configuration to
+  /// be the new configuration given by `newWidget`. If `newWidget` can be given
+  /// to the existing child (as determined by [Widget.canUpdate]), then it is so
+  /// given. Otherwise, the old child needs to be disposed and a new child
+  /// created for the new configuration.
+  ///
+  /// If both are null, then we don't have a child and won't have a child, so we
+  /// do nothing.
+  ///
+  /// The [updateChild] method returns the new child, if it had to create one,
+  /// or the child that was passed in, if it just had to update the child, or
+  /// null, if it removed the child and did not replace it.
+  ///
+  /// The following table summarizes the above:
+  ///
+  /// |                     | **newWidget == null**  | **newWidget != null**   |
+  /// | :-----------------: | :--------------------- | :---------------------- |
+  /// |  **child == null**  |  Returns null.         |  Returns new [Element]. |
+  /// |  **child != null**  |  Old child is removed, returns null. | Old child updated if possible, returns child or new [Element]. |
+  ///
+  /// The `newSlot` argument is used only if `newWidget` is not null. If `child`
+  /// is null (or if the old child cannot be updated), then the `newSlot` is
+  /// given to the new [Element] that is created for the child, via
+  /// [inflateWidget]. If `child` is not null (and the old child _can_ be
+  /// updated), then the `newSlot` is given to [updateSlotForChild] to update
+  /// its slot, in case it has moved around since it was last built.
+  ///
+  /// See the [RenderObjectElement] documentation for more information on slots.
+  @protected
+  Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
+    if (newWidget == null) {
+      if (child != null)
+        deactivateChild(child);
+      return null;
+    }
+    final Element newChild;
+    if (child != null) {
+      bool hasSameSuperclass = true;
+      // When the type of a widget is changed between Stateful and Stateless via
+      // hot reload, the element tree will end up in a partially invalid state.
+      // That is, if the widget was a StatefulWidget and is now a StatelessWidget,
+      // then the element tree currently contains a StatefulElement that is incorrectly
+      // referencing a StatelessWidget (and likewise with StatelessElement).
+      //
+      // To avoid crashing due to type errors, we need to gently guide the invalid
+      // element out of the tree. To do so, we ensure that the `hasSameSuperclass` condition
+      // returns false which prevents us from trying to update the existing element
+      // incorrectly.
+      //
+      // For the case where the widget becomes Stateful, we also need to avoid
+      // accessing `StatelessElement.widget` as the cast on the getter will
+      // cause a type error to be thrown. Here we avoid that by short-circuiting
+      // the `Widget.canUpdate` check once `hasSameSuperclass` is false.
+      assert(() {
+        final int oldElementClass = Element._debugConcreteSubtype(child);
+        final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
+        hasSameSuperclass = oldElementClass == newWidgetClass;
+        return true;
+      }());
+      if (hasSameSuperclass && child.widget == newWidget) {
+        if (child.slot != newSlot)
+          updateSlotForChild(child, newSlot);
+        newChild = child;
+      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
+        if (child.slot != newSlot)
+          updateSlotForChild(child, newSlot);
+        child.update(newWidget);
+        assert(child.widget == newWidget);
+        assert(() {
+          child.owner!._debugElementWasRebuilt(child);
+          return true;
+        }());
+        newChild = child;
+      } else {
+        deactivateChild(child);
+        assert(child._parent == null);
+        newChild = inflateWidget(newWidget, newSlot);
+      }
+    } else {
+      newChild = inflateWidget(newWidget, newSlot);
+    }
+
+    assert(() {
+      if (child != null)
+        _debugRemoveGlobalKeyReservation(child);
+      final Key? key = newWidget.key;
+      if (key is GlobalKey) {
+        key._debugReserveFor(this, newChild);
+      }
+      return true;
+    }());
+
+    return newChild;
+  }
+
+  /// Add this element to the tree in the given slot of the given parent.
+  ///
+  /// The framework calls this function when a newly created element is added to
+  /// the tree for the first time. Use this method to initialize state that
+  /// depends on having a parent. State that is independent of the parent can
+  /// more easily be initialized in the constructor.
+  ///
+  /// This method transitions the element from the "initial" lifecycle state to
+  /// the "active" lifecycle state.
+  ///
+  /// Subclasses that override this method are likely to want to also override
+  /// [update], [visitChildren], [RenderObjectElement.insertRenderObjectChild],
+  /// [RenderObjectElement.moveRenderObjectChild], and
+  /// [RenderObjectElement.removeRenderObjectChild].
+  @mustCallSuper
+  void mount(Element? parent, dynamic newSlot) {
+    assert(_lifecycleState == _ElementLifecycle.initial);
+    assert(widget != null);
+    assert(_parent == null);
+    assert(parent == null || parent._lifecycleState == _ElementLifecycle.active);
+    assert(slot == null);
+    _parent = parent;
+    _slot = newSlot;
+    _lifecycleState = _ElementLifecycle.active;
+    _depth = _parent != null ? _parent!.depth + 1 : 1;
+    if (parent != null) // Only assign ownership if the parent is non-null
+      _owner = parent.owner;
+    final Key? key = widget.key;
+    if (key is GlobalKey) {
+      key._register(this);
+    }
+    _updateInheritance();
+  }
+
+  void _debugRemoveGlobalKeyReservation(Element child) {
+    GlobalKey._debugRemoveReservationFor(this, child);
+  }
+
+  /// Change the widget used to configure this element.
+  ///
+  /// The framework calls this function when the parent wishes to use a
+  /// different widget to configure this element. The new widget is guaranteed
+  /// to have the same [runtimeType] as the old widget.
+  ///
+  /// This function is called only during the "active" lifecycle state.
+  @mustCallSuper
+  void update(covariant Widget newWidget) {
+    // This code is hot when hot reloading, so we try to
+    // only call _AssertionError._evaluateAssertion once.
+    assert(_lifecycleState == _ElementLifecycle.active
+        && widget != null
+        && newWidget != null
+        && newWidget != widget
+        && depth != null
+        && Widget.canUpdate(widget, newWidget));
+    // This Element was told to update and we can now release all the global key
+    // reservations of forgotten children. We cannot do this earlier because the
+    // forgotten children still represent global key duplications if the element
+    // never updates (the forgotten children are not removed from the tree
+    // until the call to update happens)
+    assert(() {
+      _debugForgottenChildrenWithGlobalKey.forEach(_debugRemoveGlobalKeyReservation);
+      _debugForgottenChildrenWithGlobalKey.clear();
+      return true;
+    }());
+    _widget = newWidget;
+  }
+
+  /// Change the slot that the given child occupies in its parent.
+  ///
+  /// Called by [MultiChildRenderObjectElement], and other [RenderObjectElement]
+  /// subclasses that have multiple children, when child moves from one position
+  /// to another in this element's child list.
+  @protected
+  void updateSlotForChild(Element child, dynamic newSlot) {
+    assert(_lifecycleState == _ElementLifecycle.active);
+    assert(child != null);
+    assert(child._parent == this);
+    void visit(Element element) {
+      element._updateSlot(newSlot);
+      if (element is! RenderObjectElement)
+        element.visitChildren(visit);
+    }
+    visit(child);
+  }
+
+  void _updateSlot(dynamic newSlot) {
+    assert(_lifecycleState == _ElementLifecycle.active);
+    assert(widget != null);
+    assert(_parent != null);
+    assert(_parent!._lifecycleState == _ElementLifecycle.active);
+    assert(depth != null);
+    _slot = newSlot;
+  }
+
+  void _updateDepth(int parentDepth) {
+    final int expectedDepth = parentDepth + 1;
+    if (_depth < expectedDepth) {
+      _depth = expectedDepth;
+      visitChildren((Element child) {
+        child._updateDepth(expectedDepth);
+      });
+    }
+  }
+
+  /// Remove [renderObject] from the render tree.
+  ///
+  /// The default implementation of this function simply calls
+  /// [detachRenderObject] recursively on each child. The
+  /// [RenderObjectElement.detachRenderObject] override does the actual work of
+  /// removing [renderObject] from the render tree.
+  ///
+  /// This is called by [deactivateChild].
+  void detachRenderObject() {
+    visitChildren((Element child) {
+      child.detachRenderObject();
+    });
+    _slot = null;
+  }
+
+  /// Add [renderObject] to the render tree at the location specified by `newSlot`.
+  ///
+  /// The default implementation of this function simply calls
+  /// [attachRenderObject] recursively on each child. The
+  /// [RenderObjectElement.attachRenderObject] override does the actual work of
+  /// adding [renderObject] to the render tree.
+  ///
+  /// The `newSlot` argument specifies the new value for this element's [slot].
+  void attachRenderObject(dynamic newSlot) {
+    assert(_slot == null);
+    visitChildren((Element child) {
+      child.attachRenderObject(newSlot);
+    });
+    _slot = newSlot;
+  }
+
+  Element? _retakeInactiveElement(GlobalKey key, Widget newWidget) {
+    // The "inactivity" of the element being retaken here may be forward-looking: if
+    // we are taking an element with a GlobalKey from an element that currently has
+    // it as a child, then we know that element will soon no longer have that
+    // element as a child. The only way that assumption could be false is if the
+    // global key is being duplicated, and we'll try to track that using the
+    // _debugTrackElementThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans call below.
+    final Element? element = key._currentElement;
+    if (element == null)
+      return null;
+    if (!Widget.canUpdate(element.widget, newWidget))
+      return null;
+    assert(() {
+      if (debugPrintGlobalKeyedWidgetLifecycle)
+        debugPrint('Attempting to take $element from ${element._parent ?? "inactive elements list"} to put in $this.');
+      return true;
+    }());
+    final Element? parent = element._parent;
+    if (parent != null) {
+      assert(() {
+        if (parent == this) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary("A GlobalKey was used multiple times inside one widget's child list."),
+            DiagnosticsProperty<GlobalKey>('The offending GlobalKey was', key),
+            parent.describeElement('The parent of the widgets with that key was'),
+            element.describeElement('The first child to get instantiated with that key became'),
+            DiagnosticsProperty<Widget>('The second child that was to be instantiated with that key was', widget, style: DiagnosticsTreeStyle.errorProperty),
+            ErrorDescription('A GlobalKey can only be specified on one widget at a time in the widget tree.'),
+          ]);
+        }
+        parent.owner!._debugTrackElementThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans(
+          parent,
+          key,
+        );
+        return true;
+      }());
+      parent.forgetChild(element);
+      parent.deactivateChild(element);
+    }
+    assert(element._parent == null);
+    owner!._inactiveElements.remove(element);
+    return element;
+  }
+
+  /// Create an element for the given widget and add it as a child of this
+  /// element in the given slot.
+  ///
+  /// This method is typically called by [updateChild] but can be called
+  /// directly by subclasses that need finer-grained control over creating
+  /// elements.
+  ///
+  /// If the given widget has a global key and an element already exists that
+  /// has a widget with that global key, this function will reuse that element
+  /// (potentially grafting it from another location in the tree or reactivating
+  /// it from the list of inactive elements) rather than creating a new element.
+  ///
+  /// The `newSlot` argument specifies the new value for this element's [slot].
+  ///
+  /// The element returned by this function will already have been mounted and
+  /// will be in the "active" lifecycle state.
+  @protected
+  Element inflateWidget(Widget newWidget, dynamic newSlot) {
+    assert(newWidget != null);
+    final Key? key = newWidget.key;
+    if (key is GlobalKey) {
+      final Element? newChild = _retakeInactiveElement(key, newWidget);
+      if (newChild != null) {
+        assert(newChild._parent == null);
+        assert(() {
+          _debugCheckForCycles(newChild);
+          return true;
+        }());
+        newChild._activateWithParent(this, newSlot);
+        final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
+        assert(newChild == updatedChild);
+        return updatedChild!;
+      }
+    }
+    final Element newChild = newWidget.createElement();
+    assert(() {
+      _debugCheckForCycles(newChild);
+      return true;
+    }());
+    newChild.mount(this, newSlot);
+    assert(newChild._lifecycleState == _ElementLifecycle.active);
+    return newChild;
+  }
+
+  void _debugCheckForCycles(Element newChild) {
+    assert(newChild._parent == null);
+    assert(() {
+      Element node = this;
+      while (node._parent != null)
+        node = node._parent!;
+      assert(node != newChild); // indicates we are about to create a cycle
+      return true;
+    }());
+  }
+
+  /// Move the given element to the list of inactive elements and detach its
+  /// render object from the render tree.
+  ///
+  /// This method stops the given element from being a child of this element by
+  /// detaching its render object from the render tree and moving the element to
+  /// the list of inactive elements.
+  ///
+  /// This method (indirectly) calls [deactivate] on the child.
+  ///
+  /// The caller is responsible for removing the child from its child model.
+  /// Typically [deactivateChild] is called by the element itself while it is
+  /// updating its child model; however, during [GlobalKey] reparenting, the new
+  /// parent proactively calls the old parent's [deactivateChild], first using
+  /// [forgetChild] to cause the old parent to update its child model.
+  @protected
+  void deactivateChild(Element child) {
+    assert(child != null);
+    assert(child._parent == this);
+    child._parent = null;
+    child.detachRenderObject();
+    owner!._inactiveElements.add(child); // this eventually calls child.deactivate()
+    assert(() {
+      if (debugPrintGlobalKeyedWidgetLifecycle) {
+        if (child.widget.key is GlobalKey)
+          debugPrint('Deactivated $child (keyed child of $this)');
+      }
+      return true;
+    }());
+  }
+
+  // The children that have been forgotten by forgetChild. This will be used in
+  // [update] to remove the global key reservations of forgotten children.
+  final Set<Element> _debugForgottenChildrenWithGlobalKey = HashSet<Element>();
+
+  /// Remove the given child from the element's child list, in preparation for
+  /// the child being reused elsewhere in the element tree.
+  ///
+  /// This updates the child model such that, e.g., [visitChildren] does not
+  /// walk that child anymore.
+  ///
+  /// The element will still have a valid parent when this is called, and the
+  /// child's [Element.slot] value will be valid in the context of that parent.
+  /// After this is called, [deactivateChild] is called to sever the link to
+  /// this object.
+  ///
+  /// The [update] is responsible for updating or creating the new child that
+  /// will replace this [child].
+  @protected
+  @mustCallSuper
+  void forgetChild(Element child) {
+    // This method is called on the old parent when the given child (with a
+    // global key) is given a new parent. We cannot remove the global key
+    // reservation directly in this method because the forgotten child is not
+    // removed from the tree until this Element is updated in [update]. If
+    // [update] is never called, the forgotten child still represents a global
+    // key duplication that we need to catch.
+    assert(() {
+      if (child.widget.key is GlobalKey)
+        _debugForgottenChildrenWithGlobalKey.add(child);
+      return true;
+    }());
+  }
+
+  void _activateWithParent(Element parent, dynamic newSlot) {
+    assert(_lifecycleState == _ElementLifecycle.inactive);
+    _parent = parent;
+    assert(() {
+      if (debugPrintGlobalKeyedWidgetLifecycle)
+        debugPrint('Reactivating $this (now child of $_parent).');
+      return true;
+    }());
+    _updateDepth(_parent!.depth);
+    _activateRecursively(this);
+    attachRenderObject(newSlot);
+    assert(_lifecycleState == _ElementLifecycle.active);
+  }
+
+  static void _activateRecursively(Element element) {
+    assert(element._lifecycleState == _ElementLifecycle.inactive);
+    element.activate();
+    assert(element._lifecycleState == _ElementLifecycle.active);
+    element.visitChildren(_activateRecursively);
+  }
+
+  /// Transition from the "inactive" to the "active" lifecycle state.
+  ///
+  /// The framework calls this method when a previously deactivated element has
+  /// been reincorporated into the tree. The framework does not call this method
+  /// the first time an element becomes active (i.e., from the "initial"
+  /// lifecycle state). Instead, the framework calls [mount] in that situation.
+  ///
+  /// See the lifecycle documentation for [Element] for additional information.
+  @mustCallSuper
+  void activate() {
+    assert(_lifecycleState == _ElementLifecycle.inactive);
+    assert(widget != null);
+    assert(owner != null);
+    assert(depth != null);
+    final bool hadDependencies = (_dependencies != null && _dependencies!.isNotEmpty) || _hadUnsatisfiedDependencies;
+    _lifecycleState = _ElementLifecycle.active;
+    // We unregistered our dependencies in deactivate, but never cleared the list.
+    // Since we're going to be reused, let's clear our list now.
+    _dependencies?.clear();
+    _hadUnsatisfiedDependencies = false;
+    _updateInheritance();
+    if (_dirty)
+      owner!.scheduleBuildFor(this);
+    if (hadDependencies)
+      didChangeDependencies();
+  }
+
+  /// Transition from the "active" to the "inactive" lifecycle state.
+  ///
+  /// The framework calls this method when a previously active element is moved
+  /// to the list of inactive elements. While in the inactive state, the element
+  /// will not appear on screen. The element can remain in the inactive state
+  /// only until the end of the current animation frame. At the end of the
+  /// animation frame, if the element has not be reactivated, the framework will
+  /// unmount the element.
+  ///
+  /// This is (indirectly) called by [deactivateChild].
+  ///
+  /// See the lifecycle documentation for [Element] for additional information.
+  @mustCallSuper
+  void deactivate() {
+    assert(_lifecycleState == _ElementLifecycle.active);
+    assert(_widget != null); // Use the private property to avoid a CastError during hot reload.
+    assert(depth != null);
+    if (_dependencies != null && _dependencies!.isNotEmpty) {
+      for (final InheritedElement dependency in _dependencies!)
+        dependency._dependents.remove(this);
+      // For expediency, we don't actually clear the list here, even though it's
+      // no longer representative of what we are registered with. If we never
+      // get re-used, it doesn't matter. If we do, then we'll clear the list in
+      // activate(). The benefit of this is that it allows Element's activate()
+      // implementation to decide whether to rebuild based on whether we had
+      // dependencies here.
+    }
+    _inheritedWidgets = null;
+    _lifecycleState = _ElementLifecycle.inactive;
+  }
+
+  /// Called, in debug mode, after children have been deactivated (see [deactivate]).
+  ///
+  /// This method is not called in release builds.
+  @mustCallSuper
+  void debugDeactivated() {
+    assert(_lifecycleState == _ElementLifecycle.inactive);
+  }
+
+  /// Transition from the "inactive" to the "defunct" lifecycle state.
+  ///
+  /// Called when the framework determines that an inactive element will never
+  /// be reactivated. At the end of each animation frame, the framework calls
+  /// [unmount] on any remaining inactive elements, preventing inactive elements
+  /// from remaining inactive for longer than a single animation frame.
+  ///
+  /// After this function is called, the element will not be incorporated into
+  /// the tree again.
+  ///
+  /// See the lifecycle documentation for [Element] for additional information.
+  @mustCallSuper
+  void unmount() {
+    assert(_lifecycleState == _ElementLifecycle.inactive);
+    assert(_widget != null); // Use the private property to avoid a CastError during hot reload.
+    assert(depth != null);
+    // Use the private property to avoid a CastError during hot reload.
+    final Key? key = _widget.key;
+    if (key is GlobalKey) {
+      key._unregister(this);
+    }
+    _lifecycleState = _ElementLifecycle.defunct;
+  }
+
+  @override
+  RenderObject? findRenderObject() => renderObject;
+
+  @override
+  Size? get size {
+    assert(() {
+      if (_lifecycleState != _ElementLifecycle.active) {
+        // TODO(jacobr): is this a good separation into contract and violation?
+        // I have added a line of white space.
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('Cannot get size of inactive element.'),
+          ErrorDescription(
+            'In order for an element to have a valid size, the element must be '
+            'active, which means it is part of the tree.\n'
+            'Instead, this element is in the $_lifecycleState state.'
+          ),
+          describeElement('The size getter was called for the following element'),
+        ]);
+      }
+      if (owner!._debugBuilding) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('Cannot get size during build.'),
+          ErrorDescription(
+            'The size of this render object has not yet been determined because '
+            'the framework is still in the process of building widgets, which '
+            'means the render tree for this frame has not yet been determined. '
+            'The size getter should only be called from paint callbacks or '
+            'interaction event handlers (e.g. gesture callbacks).'
+          ),
+          ErrorSpacer(),
+          ErrorHint(
+            'If you need some sizing information during build to decide which '
+            'widgets to build, consider using a LayoutBuilder widget, which can '
+            'tell you the layout constraints at a given location in the tree. See '
+            '<https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html> '
+            'for more details.'
+          ),
+          ErrorSpacer(),
+          describeElement('The size getter was called for the following element'),
+        ]);
+      }
+      return true;
+    }());
+    final RenderObject? renderObject = findRenderObject();
+    assert(() {
+      if (renderObject == null) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('Cannot get size without a render object.'),
+          ErrorHint(
+            'In order for an element to have a valid size, the element must have '
+            'an associated render object. This element does not have an associated '
+            'render object, which typically means that the size getter was called '
+            'too early in the pipeline (e.g., during the build phase) before the '
+            'framework has created the render tree.'
+          ),
+          describeElement('The size getter was called for the following element'),
+        ]);
+      }
+      if (renderObject is RenderSliver) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('Cannot get size from a RenderSliver.'),
+          ErrorHint(
+            'The render object associated with this element is a '
+            '${renderObject.runtimeType}, which is a subtype of RenderSliver. '
+            'Slivers do not have a size per se. They have a more elaborate '
+            'geometry description, which can be accessed by calling '
+            'findRenderObject and then using the "geometry" getter on the '
+            'resulting object.'
+          ),
+          describeElement('The size getter was called for the following element'),
+          renderObject.describeForError('The associated render sliver was'),
+        ]);
+      }
+      if (renderObject is! RenderBox) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('Cannot get size from a render object that is not a RenderBox.'),
+          ErrorHint(
+            'Instead of being a subtype of RenderBox, the render object associated '
+            'with this element is a ${renderObject.runtimeType}. If this type of '
+            'render object does have a size, consider calling findRenderObject '
+            'and extracting its size manually.'
+          ),
+          describeElement('The size getter was called for the following element'),
+          renderObject.describeForError('The associated render object was'),
+        ]);
+      }
+      final RenderBox box = renderObject;
+      if (!box.hasSize) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('Cannot get size from a render object that has not been through layout.'),
+          ErrorHint(
+            'The size of this render object has not yet been determined because '
+            'this render object has not yet been through layout, which typically '
+            'means that the size getter was called too early in the pipeline '
+            '(e.g., during the build phase) before the framework has determined '
+           'the size and position of the render objects during layout.'
+          ),
+          describeElement('The size getter was called for the following element'),
+          box.describeForError('The render object from which the size was to be obtained was'),
+        ]);
+      }
+      if (box.debugNeedsLayout) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('Cannot get size from a render object that has been marked dirty for layout.'),
+          ErrorHint(
+            'The size of this render object is ambiguous because this render object has '
+            'been modified since it was last laid out, which typically means that the size '
+            'getter was called too early in the pipeline (e.g., during the build phase) '
+            'before the framework has determined the size and position of the render '
+            'objects during layout.'
+          ),
+          describeElement('The size getter was called for the following element'),
+          box.describeForError('The render object from which the size was to be obtained was'),
+          ErrorHint(
+            'Consider using debugPrintMarkNeedsLayoutStacks to determine why the render '
+            'object in question is dirty, if you did not expect this.'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    if (renderObject is RenderBox)
+      return renderObject.size;
+    return null;
+  }
+
+  Map<Type, InheritedElement>? _inheritedWidgets;
+  Set<InheritedElement>? _dependencies;
+  bool _hadUnsatisfiedDependencies = false;
+
+  bool _debugCheckStateIsActiveForAncestorLookup() {
+    assert(() {
+      if (_lifecycleState != _ElementLifecycle.active) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary("Looking up a deactivated widget's ancestor is unsafe."),
+          ErrorDescription(
+            "At this point the state of the widget's element tree is no longer "
+            'stable.'
+          ),
+          ErrorHint(
+            "To safely refer to a widget's ancestor in its dispose() method, "
+            'save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() '
+            "in the widget's didChangeDependencies() method."
+          ),
+        ]);
+      }
+      return true;
+    }());
+    return true;
+  }
+
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use dependOnInheritedElement instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  @override
+  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object? aspect }) {
+    return dependOnInheritedElement(ancestor, aspect: aspect);
+  }
+
+  @override
+  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
+    assert(ancestor != null);
+    _dependencies ??= HashSet<InheritedElement>();
+    _dependencies!.add(ancestor);
+    ancestor.updateDependencies(this, aspect);
+    return ancestor.widget;
+  }
+
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use dependOnInheritedWidgetOfExactType instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  @override
+  InheritedWidget? inheritFromWidgetOfExactType(Type targetType, { Object? aspect }) {
+    assert(_debugCheckStateIsActiveForAncestorLookup());
+    final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![targetType];
+    if (ancestor != null) {
+      assert(ancestor is InheritedElement);
+      return inheritFromElement(ancestor, aspect: aspect);
+    }
+    _hadUnsatisfiedDependencies = true;
+    return null;
+  }
+
+  @override
+  T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) {
+    assert(_debugCheckStateIsActiveForAncestorLookup());
+    final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
+    if (ancestor != null) {
+      assert(ancestor is InheritedElement);
+      return dependOnInheritedElement(ancestor, aspect: aspect) as T;
+    }
+    _hadUnsatisfiedDependencies = true;
+    return null;
+  }
+
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use getElementForInheritedWidgetOfExactType instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  @override
+  InheritedElement? ancestorInheritedElementForWidgetOfExactType(Type targetType) {
+    assert(_debugCheckStateIsActiveForAncestorLookup());
+    final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![targetType];
+    return ancestor;
+  }
+
+  @override
+  InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>() {
+    assert(_debugCheckStateIsActiveForAncestorLookup());
+    final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T];
+    return ancestor;
+  }
+
+  void _updateInheritance() {
+    assert(_lifecycleState == _ElementLifecycle.active);
+    _inheritedWidgets = _parent?._inheritedWidgets;
+  }
+
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use findAncestorWidgetOfExactType instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  @override
+  Widget? ancestorWidgetOfExactType(Type targetType) {
+    assert(_debugCheckStateIsActiveForAncestorLookup());
+    Element? ancestor = _parent;
+    while (ancestor != null && ancestor.widget.runtimeType != targetType)
+      ancestor = ancestor._parent;
+    return ancestor?.widget;
+  }
+
+  @override
+  T? findAncestorWidgetOfExactType<T extends Widget>() {
+    assert(_debugCheckStateIsActiveForAncestorLookup());
+    Element? ancestor = _parent;
+    while (ancestor != null && ancestor.widget.runtimeType != T)
+      ancestor = ancestor._parent;
+    return ancestor?.widget as T?;
+  }
+
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use findAncestorStateOfType instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  @override
+  State? ancestorStateOfType(TypeMatcher matcher) {
+    assert(_debugCheckStateIsActiveForAncestorLookup());
+    Element? ancestor = _parent;
+    while (ancestor != null) {
+      if (ancestor is StatefulElement && matcher.check(ancestor.state))
+        break;
+      ancestor = ancestor._parent;
+    }
+    final StatefulElement? statefulAncestor = ancestor as StatefulElement?;
+    return statefulAncestor?.state;
+  }
+
+  @override
+  T? findAncestorStateOfType<T extends State<StatefulWidget>>() {
+    assert(_debugCheckStateIsActiveForAncestorLookup());
+    Element? ancestor = _parent;
+    while (ancestor != null) {
+      if (ancestor is StatefulElement && ancestor.state is T)
+        break;
+      ancestor = ancestor._parent;
+    }
+    final StatefulElement? statefulAncestor = ancestor as StatefulElement?;
+    return statefulAncestor?.state as T?;
+  }
+
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use findRootAncestorStateOfType instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  @override
+  State? rootAncestorStateOfType(TypeMatcher matcher) {
+    assert(_debugCheckStateIsActiveForAncestorLookup());
+    Element? ancestor = _parent;
+    StatefulElement? statefulAncestor;
+    while (ancestor != null) {
+      if (ancestor is StatefulElement && matcher.check(ancestor.state))
+        statefulAncestor = ancestor;
+      ancestor = ancestor._parent;
+    }
+    return statefulAncestor?.state;
+  }
+
+  @override
+  T? findRootAncestorStateOfType<T extends State<StatefulWidget>>() {
+    assert(_debugCheckStateIsActiveForAncestorLookup());
+    Element? ancestor = _parent;
+    StatefulElement? statefulAncestor;
+    while (ancestor != null) {
+      if (ancestor is StatefulElement && ancestor.state is T)
+        statefulAncestor = ancestor;
+      ancestor = ancestor._parent;
+    }
+    return statefulAncestor?.state as T?;
+  }
+
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use findAncestorRenderObjectOfType instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  @override
+  RenderObject? ancestorRenderObjectOfType(TypeMatcher matcher) {
+    assert(_debugCheckStateIsActiveForAncestorLookup());
+    Element? ancestor = _parent;
+    while (ancestor != null) {
+      if (ancestor is RenderObjectElement && matcher.check(ancestor.renderObject))
+        return ancestor.renderObject;
+      ancestor = ancestor._parent;
+    }
+    return null;
+  }
+
+  @override
+  T? findAncestorRenderObjectOfType<T extends RenderObject>() {
+    assert(_debugCheckStateIsActiveForAncestorLookup());
+    Element? ancestor = _parent;
+    while (ancestor != null) {
+      if (ancestor is RenderObjectElement && ancestor.renderObject is T)
+        return ancestor.renderObject as T;
+      ancestor = ancestor._parent;
+    }
+    return null;
+  }
+
+  @override
+  void visitAncestorElements(bool visitor(Element element)) {
+    assert(_debugCheckStateIsActiveForAncestorLookup());
+    Element? ancestor = _parent;
+    while (ancestor != null && visitor(ancestor))
+      ancestor = ancestor._parent;
+  }
+
+  /// Called when a dependency of this element changes.
+  ///
+  /// The [dependOnInheritedWidgetOfExactType] registers this element as depending on
+  /// inherited information of the given type. When the information of that type
+  /// changes at this location in the tree (e.g., because the [InheritedElement]
+  /// updated to a new [InheritedWidget] and
+  /// [InheritedWidget.updateShouldNotify] returned true), the framework calls
+  /// this function to notify this element of the change.
+  @mustCallSuper
+  void didChangeDependencies() {
+    assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
+    assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
+    markNeedsBuild();
+  }
+
+  bool _debugCheckOwnerBuildTargetExists(String methodName) {
+    assert(() {
+      if (owner!._debugCurrentBuildTarget == null) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary(
+            '$methodName for ${widget.runtimeType} was called at an '
+            'inappropriate time.'
+          ),
+          ErrorDescription('It may only be called while the widgets are being built.'),
+          ErrorHint(
+            'A possible cause of this error is when $methodName is called during '
+            'one of:\n'
+            ' * network I/O event\n'
+            ' * file I/O event\n'
+            ' * timer\n'
+            ' * microtask (caused by Future.then, async/await, scheduleMicrotask)'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    return true;
+  }
+
+  /// Returns a description of what caused this element to be created.
+  ///
+  /// Useful for debugging the source of an element.
+  String debugGetCreatorChain(int limit) {
+    final List<String> chain = <String>[];
+    Element? node = this;
+    while (chain.length < limit && node != null) {
+      chain.add(node.toStringShort());
+      node = node._parent;
+    }
+    if (node != null)
+      chain.add('\u22EF');
+    return chain.join(' \u2190 ');
+  }
+
+  /// Returns the parent chain from this element back to the root of the tree.
+  ///
+  /// Useful for debug display of a tree of Elements with only nodes in the path
+  /// from the root to this Element expanded.
+  List<Element> debugGetDiagnosticChain() {
+    final List<Element> chain = <Element>[this];
+    Element? node = _parent;
+    while (node != null) {
+      chain.add(node);
+      node = node._parent;
+    }
+    return chain;
+  }
+
+  /// A short, textual description of this element.
+  @override
+  String toStringShort() => widget.toStringShort();
+
+  @override
+  DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
+    return _ElementDiagnosticableTreeNode(
+      name: name,
+      value: this,
+      style: style,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.defaultDiagnosticsTreeStyle= DiagnosticsTreeStyle.dense;
+    if (_lifecycleState != _ElementLifecycle.initial) {
+      properties.add(ObjectFlagProperty<int>('depth', depth, ifNull: 'no depth'));
+    }
+    properties.add(ObjectFlagProperty<Widget>('widget', widget, ifNull: 'no widget'));
+    properties.add(DiagnosticsProperty<Key>('key', widget.key, showName: false, defaultValue: null, level: DiagnosticLevel.hidden));
+    widget.debugFillProperties(properties);
+    properties.add(FlagProperty('dirty', value: dirty, ifTrue: 'dirty'));
+    if (_dependencies != null && _dependencies!.isNotEmpty) {
+      final List<DiagnosticsNode> diagnosticsDependencies = _dependencies!
+        .map((InheritedElement element) => element.widget.toDiagnosticsNode(style: DiagnosticsTreeStyle.sparse))
+        .toList();
+      properties.add(DiagnosticsProperty<List<DiagnosticsNode>>('dependencies', diagnosticsDependencies));
+    }
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    final List<DiagnosticsNode> children = <DiagnosticsNode>[];
+    visitChildren((Element child) {
+      children.add(child.toDiagnosticsNode());
+    });
+    return children;
+  }
+
+  /// Returns true if the element has been marked as needing rebuilding.
+  bool get dirty => _dirty;
+  bool _dirty = true;
+
+  // Whether this is in owner._dirtyElements. This is used to know whether we
+  // should be adding the element back into the list when it's reactivated.
+  bool _inDirtyList = false;
+
+  // Whether we've already built or not. Set in [rebuild].
+  bool _debugBuiltOnce = false;
+
+  // We let widget authors call setState from initState, didUpdateWidget, and
+  // build even when state is locked because its convenient and a no-op anyway.
+  // This flag ensures that this convenience is only allowed on the element
+  // currently undergoing initState, didUpdateWidget, or build.
+  bool _debugAllowIgnoredCallsToMarkNeedsBuild = false;
+  bool _debugSetAllowIgnoredCallsToMarkNeedsBuild(bool value) {
+    assert(_debugAllowIgnoredCallsToMarkNeedsBuild == !value);
+    _debugAllowIgnoredCallsToMarkNeedsBuild = value;
+    return true;
+  }
+
+  /// Marks the element as dirty and adds it to the global list of widgets to
+  /// rebuild in the next frame.
+  ///
+  /// Since it is inefficient to build an element twice in one frame,
+  /// applications and widgets should be structured so as to only mark
+  /// widgets dirty during event handlers before the frame begins, not during
+  /// the build itself.
+  void markNeedsBuild() {
+    assert(_lifecycleState != _ElementLifecycle.defunct);
+    if (_lifecycleState != _ElementLifecycle.active)
+      return;
+    assert(owner != null);
+    assert(_lifecycleState == _ElementLifecycle.active);
+    assert(() {
+      if (owner!._debugBuilding) {
+        assert(owner!._debugCurrentBuildTarget != null);
+        assert(owner!._debugStateLocked);
+        if (_debugIsInScope(owner!._debugCurrentBuildTarget!))
+          return true;
+        if (!_debugAllowIgnoredCallsToMarkNeedsBuild) {
+          final List<DiagnosticsNode> information = <DiagnosticsNode>[
+            ErrorSummary('setState() or markNeedsBuild() called during build.'),
+            ErrorDescription(
+              'This ${widget.runtimeType} widget cannot be marked as needing to build because the framework '
+              'is already in the process of building widgets.  A widget can be marked as '
+              'needing to be built during the build phase only if one of its ancestors '
+              'is currently building. This exception is allowed because the framework '
+              'builds parent widgets before children, which means a dirty descendant '
+              'will always be built. Otherwise, the framework might not visit this '
+              'widget during this build phase.'
+            ),
+            describeElement(
+              'The widget on which setState() or markNeedsBuild() was called was',
+            ),
+          ];
+          if (owner!._debugCurrentBuildTarget != null)
+            information.add(owner!._debugCurrentBuildTarget!.describeWidget('The widget which was currently being built when the offending call was made was'));
+          throw FlutterError.fromParts(information);
+        }
+        assert(dirty); // can only get here if we're not in scope, but ignored calls are allowed, and our call would somehow be ignored (since we're already dirty)
+      } else if (owner!._debugStateLocked) {
+        assert(!_debugAllowIgnoredCallsToMarkNeedsBuild);
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('setState() or markNeedsBuild() called when widget tree was locked.'),
+          ErrorDescription(
+            'This ${widget.runtimeType} widget cannot be marked as needing to build '
+            'because the framework is locked.'
+          ),
+          describeElement('The widget on which setState() or markNeedsBuild() was called was'),
+        ]);
+      }
+      return true;
+    }());
+    if (dirty)
+      return;
+    _dirty = true;
+    owner!.scheduleBuildFor(this);
+  }
+
+  /// Called by the [BuildOwner] when [BuildOwner.scheduleBuildFor] has been
+  /// called to mark this element dirty, by [mount] when the element is first
+  /// built, and by [update] when the widget has changed.
+  void rebuild() {
+    assert(_lifecycleState != _ElementLifecycle.initial);
+    if (_lifecycleState != _ElementLifecycle.active || !_dirty)
+      return;
+    assert(() {
+      if (debugOnRebuildDirtyWidget != null) {
+        debugOnRebuildDirtyWidget!(this, _debugBuiltOnce);
+      }
+      if (debugPrintRebuildDirtyWidgets) {
+        if (!_debugBuiltOnce) {
+          debugPrint('Building $this');
+          _debugBuiltOnce = true;
+        } else {
+          debugPrint('Rebuilding $this');
+        }
+      }
+      return true;
+    }());
+    assert(_lifecycleState == _ElementLifecycle.active);
+    assert(owner!._debugStateLocked);
+    Element? debugPreviousBuildTarget;
+    assert(() {
+      debugPreviousBuildTarget = owner!._debugCurrentBuildTarget;
+      owner!._debugCurrentBuildTarget = this;
+      return true;
+    }());
+    performRebuild();
+    assert(() {
+      assert(owner!._debugCurrentBuildTarget == this);
+      owner!._debugCurrentBuildTarget = debugPreviousBuildTarget;
+      return true;
+    }());
+    assert(!_dirty);
+  }
+
+  /// Called by rebuild() after the appropriate checks have been made.
+  @protected
+  void performRebuild();
+}
+
+class _ElementDiagnosticableTreeNode extends DiagnosticableTreeNode {
+  _ElementDiagnosticableTreeNode({
+    String? name,
+    required Element value,
+    required DiagnosticsTreeStyle? style,
+    this.stateful = false,
+  }) : super(
+    name: name,
+    value: value,
+    style: style,
+  );
+
+  final bool stateful;
+
+  @override
+  Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
+    final Map<String, Object?> json = super.toJsonMap(delegate);
+    final Element element = value as Element;
+    json['widgetRuntimeType'] = element.widget.runtimeType.toString();
+    json['stateful'] = stateful;
+    return json;
+  }
+}
+
+/// Signature for the constructor that is called when an error occurs while
+/// building a widget.
+///
+/// The argument provides information regarding the cause of the error.
+///
+/// See also:
+///
+///  * [ErrorWidget.builder], which can be set to override the default
+///    [ErrorWidget] builder.
+///  * [FlutterError.reportError], which is typically called with the same
+///    [FlutterErrorDetails] object immediately prior to [ErrorWidget.builder]
+///    being called.
+typedef ErrorWidgetBuilder = Widget Function(FlutterErrorDetails details);
+
+/// A widget that renders an exception's message.
+///
+/// This widget is used when a build method fails, to help with determining
+/// where the problem lies. Exceptions are also logged to the console, which you
+/// can read using `flutter logs`. The console will also include additional
+/// information such as the stack trace for the exception.
+///
+/// It is possible to override this widget.
+///
+/// {@tool sample --template=freeform_no_null_safety}
+/// ```dart
+/// import 'package:flute/material.dart';
+///
+/// void main() {
+///   ErrorWidget.builder = (FlutterErrorDetails details) {
+///     bool inDebug = false;
+///     assert(() { inDebug = true; return true; }());
+///     // In debug mode, use the normal error widget which shows
+///     // the error message:
+///     if (inDebug)
+///       return ErrorWidget(details.exception);
+///     // In release builds, show a yellow-on-blue message instead:
+///     return Container(
+///       alignment: Alignment.center,
+///       child: Text(
+///         'Error!',
+///         style: TextStyle(color: Colors.yellow),
+///         textDirection: TextDirection.ltr,
+///       ),
+///     );
+///   };
+///   // Here we would normally runApp() the root widget, but to demonstrate
+///   // the error handling we artificially fail:
+///   return runApp(Builder(
+///     builder: (BuildContext context) {
+///       throw 'oh no, an error';
+///     },
+///   ));
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [FlutterError.onError], which can be set to a method that exits the
+///    application if that is preferable to showing an error message.
+///  * <https://flutter.dev/docs/testing/errors>, more information about error
+///    handling in Flutter.
+class ErrorWidget extends LeafRenderObjectWidget {
+  /// Creates a widget that displays the given exception.
+  ///
+  /// The message will be the stringification of the given exception, unless
+  /// computing that value itself throws an exception, in which case it will
+  /// be the string "Error".
+  ///
+  /// If this object is inspected from an IDE or the devtools, and the original
+  /// exception is a [FlutterError] object, the original exception itself will
+  /// be shown in the inspection output.
+  ErrorWidget(Object exception)
+    : message = _stringify(exception),
+      _flutterError = exception is FlutterError ? exception : null,
+      super(key: UniqueKey());
+
+  /// Creates a widget that displays the given error message.
+  ///
+  /// An explicit [FlutterError] can be provided to be reported to inspection
+  /// tools. It need not match the message.
+  ErrorWidget.withDetails({ this.message = '', FlutterError? error })
+    : _flutterError = error,
+      super(key: UniqueKey());
+
+  /// The configurable factory for [ErrorWidget].
+  ///
+  /// When an error occurs while building a widget, the broken widget is
+  /// replaced by the widget returned by this function. By default, an
+  /// [ErrorWidget] is returned.
+  ///
+  /// The system is typically in an unstable state when this function is called.
+  /// An exception has just been thrown in the middle of build (and possibly
+  /// layout), so surrounding widgets and render objects may be in a rather
+  /// fragile state. The framework itself (especially the [BuildOwner]) may also
+  /// be confused, and additional exceptions are quite likely to be thrown.
+  ///
+  /// Because of this, it is highly recommended that the widget returned from
+  /// this function perform the least amount of work possible. A
+  /// [LeafRenderObjectWidget] is the best choice, especially one that
+  /// corresponds to a [RenderBox] that can handle the most absurd of incoming
+  /// constraints. The default constructor maps to a [RenderErrorBox].
+  ///
+  /// The default behavior is to show the exception's message in debug mode,
+  /// and to show nothing but a gray background in release builds.
+  ///
+  /// See also:
+  ///
+  ///  * [FlutterError.onError], which is typically called with the same
+  ///    [FlutterErrorDetails] object immediately prior to this callback being
+  ///    invoked, and which can also be configured to control how errors are
+  ///    reported.
+  ///  * <https://flutter.dev/docs/testing/errors>, more information about error
+  ///    handling in Flutter.
+  static ErrorWidgetBuilder builder = _defaultErrorWidgetBuilder;
+
+  static Widget _defaultErrorWidgetBuilder(FlutterErrorDetails details) {
+    String message = '';
+    assert(() {
+      message = _stringify(details.exception) + '\nSee also: https://flutter.dev/docs/testing/errors';
+      return true;
+    }());
+    final Object exception = details.exception;
+    return ErrorWidget.withDetails(message: message, error: exception is FlutterError ? exception : null);
+  }
+
+  static String _stringify(dynamic exception) {
+    try {
+      return exception.toString();
+    } catch (e) {
+      // intentionally left empty.
+    }
+    return 'Error';
+  }
+
+  /// The message to display.
+  final String message;
+  final FlutterError? _flutterError;
+
+  @override
+  RenderBox createRenderObject(BuildContext context) => RenderErrorBox(message);
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    if (_flutterError == null)
+      properties.add(StringProperty('message', message, quoted: false));
+    else
+      properties.add(_flutterError!.toDiagnosticsNode(style: DiagnosticsTreeStyle.whitespace));
+  }
+}
+
+/// Signature for a function that creates a widget, e.g. [StatelessWidget.build]
+/// or [State.build].
+///
+/// Used by [Builder.builder], [OverlayEntry.builder], etc.
+///
+/// See also:
+///
+///  * [IndexedWidgetBuilder], which is similar but also takes an index.
+///  * [TransitionBuilder], which is similar but also takes a child.
+///  * [ValueWidgetBuilder], which is similar but takes a value and a child.
+typedef WidgetBuilder = Widget Function(BuildContext context);
+
+/// Signature for a function that creates a widget for a given index, e.g., in a
+/// list.
+///
+/// Used by [ListView.builder] and other APIs that use lazily-generated widgets.
+///
+/// See also:
+///
+///  * [WidgetBuilder], which is similar but only takes a [BuildContext].
+///  * [TransitionBuilder], which is similar but also takes a child.
+///  * [NullableIndexedWidgetBuilder], which is similar but may return null.
+typedef IndexedWidgetBuilder = Widget Function(BuildContext context, int index);
+
+/// Signature for a function that creates a widget for a given index, e.g., in a
+/// list, but may return null.
+///
+/// Used by [SliverChildBuilderDelegate.builder] and other APIs that
+/// use lazily-generated widgets where the child count is not known
+/// ahead of time.
+///
+/// Unlike most builders, this callback can return null, indicating the index
+/// is out of range. Whether and when this is valid depends on the semantics
+/// of the builder. For example, [SliverChildBuilderDelegate.builder] returns
+/// null when the index is out of range, where the range is defined by the
+/// [SliverChildBuilderDelegate.childCount]; so in that case the `index`
+/// parameter's value may determine whether returning null is valid or not.
+///
+/// See also:
+///
+///  * [WidgetBuilder], which is similar but only takes a [BuildContext].
+///  * [TransitionBuilder], which is similar but also takes a child.
+///  * [IndexedWidgetBuilder], which is similar but not nullable.
+typedef NullableIndexedWidgetBuilder = Widget? Function(BuildContext context, int index);
+
+/// A builder that builds a widget given a child.
+///
+/// The child should typically be part of the returned widget tree.
+///
+/// Used by [AnimatedBuilder.builder], as well as [WidgetsApp.builder] and
+/// [MaterialApp.builder].
+///
+/// See also:
+///
+///  * [WidgetBuilder], which is similar but only takes a [BuildContext].
+///  * [IndexedWidgetBuilder], which is similar but also takes an index.
+///  * [ValueWidgetBuilder], which is similar but takes a value and a child.
+typedef TransitionBuilder = Widget Function(BuildContext context, Widget? child);
+
+/// A builder that creates a widget given the two callbacks `onStepContinue` and
+/// `onStepCancel`.
+///
+/// Used by [Stepper.controlsBuilder].
+///
+/// See also:
+///
+///  * [WidgetBuilder], which is similar but only takes a [BuildContext].
+typedef ControlsWidgetBuilder = Widget Function(BuildContext context, { VoidCallback? onStepContinue, VoidCallback? onStepCancel });
+
+/// An [Element] that composes other [Element]s.
+///
+/// Rather than creating a [RenderObject] directly, a [ComponentElement] creates
+/// [RenderObject]s indirectly by creating other [Element]s.
+///
+/// Contrast with [RenderObjectElement].
+abstract class ComponentElement extends Element {
+  /// Creates an element that uses the given widget as its configuration.
+  ComponentElement(Widget widget) : super(widget);
+
+  Element? _child;
+
+  bool _debugDoingBuild = false;
+  @override
+  bool get debugDoingBuild => _debugDoingBuild;
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    assert(_child == null);
+    assert(_lifecycleState == _ElementLifecycle.active);
+    _firstBuild();
+    assert(_child != null);
+  }
+
+  void _firstBuild() {
+    rebuild();
+  }
+
+  /// Calls the [StatelessWidget.build] method of the [StatelessWidget] object
+  /// (for stateless widgets) or the [State.build] method of the [State] object
+  /// (for stateful widgets) and then updates the widget tree.
+  ///
+  /// Called automatically during [mount] to generate the first build, and by
+  /// [rebuild] when the element needs updating.
+  @override
+  void performRebuild() {
+    if (!kReleaseMode && debugProfileBuildsEnabled)
+      Timeline.startSync('${widget.runtimeType}',  arguments: timelineArgumentsIndicatingLandmarkEvent);
+
+    assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
+    Widget? built;
+    try {
+      assert(() {
+        _debugDoingBuild = true;
+        return true;
+      }());
+      built = build();
+      assert(() {
+        _debugDoingBuild = false;
+        return true;
+      }());
+      debugWidgetBuilderValue(widget, built);
+    } catch (e, stack) {
+      _debugDoingBuild = false;
+      built = ErrorWidget.builder(
+        _debugReportException(
+          ErrorDescription('building $this'),
+          e,
+          stack,
+          informationCollector: () sync* {
+            yield DiagnosticsDebugCreator(DebugCreator(this));
+          },
+        ),
+      );
+    } finally {
+      // We delay marking the element as clean until after calling build() so
+      // that attempts to markNeedsBuild() during build() will be ignored.
+      _dirty = false;
+      assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
+    }
+    try {
+      _child = updateChild(_child, built, slot);
+      assert(_child != null);
+    } catch (e, stack) {
+      built = ErrorWidget.builder(
+        _debugReportException(
+          ErrorDescription('building $this'),
+          e,
+          stack,
+          informationCollector: () sync* {
+            yield DiagnosticsDebugCreator(DebugCreator(this));
+          },
+        ),
+      );
+      _child = updateChild(null, built, slot);
+    }
+
+    if (!kReleaseMode && debugProfileBuildsEnabled)
+      Timeline.finishSync();
+  }
+
+  /// Subclasses should override this function to actually call the appropriate
+  /// `build` function (e.g., [StatelessWidget.build] or [State.build]) for
+  /// their widget.
+  @protected
+  Widget build();
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    if (_child != null)
+      visitor(_child!);
+  }
+
+  @override
+  void forgetChild(Element child) {
+    assert(child == _child);
+    _child = null;
+    super.forgetChild(child);
+  }
+}
+
+/// An [Element] that uses a [StatelessWidget] as its configuration.
+class StatelessElement extends ComponentElement {
+  /// Creates an element that uses the given widget as its configuration.
+  StatelessElement(StatelessWidget widget) : super(widget);
+
+  @override
+  StatelessWidget get widget => super.widget as StatelessWidget;
+
+  @override
+  Widget build() => widget.build(this);
+
+  @override
+  void update(StatelessWidget newWidget) {
+    super.update(newWidget);
+    assert(widget == newWidget);
+    _dirty = true;
+    rebuild();
+  }
+}
+
+/// An [Element] that uses a [StatefulWidget] as its configuration.
+class StatefulElement extends ComponentElement {
+  /// Creates an element that uses the given widget as its configuration.
+  StatefulElement(StatefulWidget widget)
+      : state = widget.createState(),
+        super(widget) {
+    assert(() {
+      if (!state._debugTypesAreRight(widget)) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('StatefulWidget.createState must return a subtype of State<${widget.runtimeType}>'),
+          ErrorDescription(
+            'The createState function for ${widget.runtimeType} returned a state '
+            'of type ${state.runtimeType}, which is not a subtype of '
+            'State<${widget.runtimeType}>, violating the contract for createState.'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    assert(state._element == null);
+    state._element = this;
+    assert(
+      state._widget == null,
+      'The createState function for $widget returned an old or invalid state '
+      'instance: ${state._widget}, which is not null, violating the contract '
+      'for createState.',
+    );
+    state._widget = widget;
+    assert(state._debugLifecycleState == _StateLifecycle.created);
+  }
+
+  @override
+  Widget build() => state.build(this);
+
+  /// The [State] instance associated with this location in the tree.
+  ///
+  /// There is a one-to-one relationship between [State] objects and the
+  /// [StatefulElement] objects that hold them. The [State] objects are created
+  /// by [StatefulElement] in [mount].
+  final State<StatefulWidget> state;
+
+  @override
+  void reassemble() {
+    state.reassemble();
+    super.reassemble();
+  }
+
+  @override
+  void _firstBuild() {
+    assert(state._debugLifecycleState == _StateLifecycle.created);
+    try {
+      _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
+      final dynamic debugCheckForReturnedFuture = state.initState() as dynamic;
+      assert(() {
+        if (debugCheckForReturnedFuture is Future) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('${state.runtimeType}.initState() returned a Future.'),
+            ErrorDescription('State.initState() must be a void method without an `async` keyword.'),
+            ErrorHint(
+              'Rather than awaiting on asynchronous work directly inside of initState, '
+              'call a separate method to do this work without awaiting it.'
+            ),
+          ]);
+        }
+        return true;
+      }());
+    } finally {
+      _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
+    }
+    assert(() {
+      state._debugLifecycleState = _StateLifecycle.initialized;
+      return true;
+    }());
+    state.didChangeDependencies();
+    assert(() {
+      state._debugLifecycleState = _StateLifecycle.ready;
+      return true;
+    }());
+    super._firstBuild();
+  }
+
+  @override
+  void performRebuild() {
+    if (_didChangeDependencies) {
+      state.didChangeDependencies();
+      _didChangeDependencies = false;
+    }
+    super.performRebuild();
+  }
+
+  @override
+  void update(StatefulWidget newWidget) {
+    super.update(newWidget);
+    assert(widget == newWidget);
+    final StatefulWidget oldWidget = state._widget!;
+    // We mark ourselves as dirty before calling didUpdateWidget to
+    // let authors call setState from within didUpdateWidget without triggering
+    // asserts.
+    _dirty = true;
+    state._widget = widget as StatefulWidget;
+    try {
+      _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
+      final dynamic debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
+      assert(() {
+        if (debugCheckForReturnedFuture is Future) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('${state.runtimeType}.didUpdateWidget() returned a Future.'),
+            ErrorDescription( 'State.didUpdateWidget() must be a void method without an `async` keyword.'),
+            ErrorHint(
+              'Rather than awaiting on asynchronous work directly inside of didUpdateWidget, '
+              'call a separate method to do this work without awaiting it.'
+            ),
+          ]);
+        }
+        return true;
+      }());
+    } finally {
+      _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
+    }
+    rebuild();
+  }
+
+  @override
+  void activate() {
+    super.activate();
+    // Since the State could have observed the deactivate() and thus disposed of
+    // resources allocated in the build method, we have to rebuild the widget
+    // so that its State can reallocate its resources.
+    assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
+    markNeedsBuild();
+  }
+
+  @override
+  void deactivate() {
+    state.deactivate();
+    super.deactivate();
+  }
+
+  @override
+  void unmount() {
+    super.unmount();
+    state.dispose();
+    assert(() {
+      if (state._debugLifecycleState == _StateLifecycle.defunct)
+        return true;
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('${state.runtimeType}.dispose failed to call super.dispose.'),
+        ErrorDescription(
+          'dispose() implementations must always call their superclass dispose() method, to ensure '
+         'that all the resources used by the widget are fully released.'
+        ),
+      ]);
+    }());
+    state._element = null;
+  }
+
+  // TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189
+  @Deprecated(
+    'Use dependOnInheritedElement instead. '
+    'This feature was deprecated after v1.12.1.'
+  )
+  @override
+  InheritedWidget inheritFromElement(Element ancestor, { Object? aspect }) {
+    return dependOnInheritedElement(ancestor, aspect: aspect);
+  }
+
+  @override
+  InheritedWidget dependOnInheritedElement(Element ancestor, { Object? aspect }) {
+    assert(ancestor != null);
+    assert(() {
+      final Type targetType = ancestor.widget.runtimeType;
+      if (state._debugLifecycleState == _StateLifecycle.created) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('dependOnInheritedWidgetOfExactType<$targetType>() or dependOnInheritedElement() was called before ${state.runtimeType}.initState() completed.'),
+          ErrorDescription(
+            'When an inherited widget changes, for example if the value of Theme.of() changes, '
+            "its dependent widgets are rebuilt. If the dependent widget's reference to "
+            'the inherited widget is in a constructor or an initState() method, '
+            'then the rebuilt dependent widget will not reflect the changes in the '
+            'inherited widget.',
+          ),
+          ErrorHint(
+            'Typically references to inherited widgets should occur in widget build() methods. Alternatively, '
+            'initialization based on inherited widgets can be placed in the didChangeDependencies method, which '
+            'is called after initState and whenever the dependencies change thereafter.'
+          ),
+        ]);
+      }
+      if (state._debugLifecycleState == _StateLifecycle.defunct) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('dependOnInheritedWidgetOfExactType<$targetType>() or dependOnInheritedElement() was called after dispose(): $this'),
+          ErrorDescription(
+            'This error happens if you call dependOnInheritedWidgetOfExactType() on the '
+            'BuildContext for a widget that no longer appears in the widget tree '
+            '(e.g., whose parent widget no longer includes the widget in its '
+            'build). This error can occur when code calls '
+            'dependOnInheritedWidgetOfExactType() from a timer or an animation callback.'
+          ),
+          ErrorHint(
+            'The preferred solution is to cancel the timer or stop listening to the '
+            'animation in the dispose() callback. Another solution is to check the '
+            '"mounted" property of this object before calling '
+            'dependOnInheritedWidgetOfExactType() to ensure the object is still in the '
+            'tree.'
+          ),
+          ErrorHint(
+            'This error might indicate a memory leak if '
+            'dependOnInheritedWidgetOfExactType() is being called because another object '
+            'is retaining a reference to this State object after it has been '
+            'removed from the tree. To avoid memory leaks, consider breaking the '
+            'reference to this object during dispose().'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    return super.dependOnInheritedElement(ancestor as InheritedElement, aspect: aspect);
+  }
+
+  /// This controls whether we should call [State.didChangeDependencies] from
+  /// the start of [build], to avoid calls when the [State] will not get built.
+  /// This can happen when the widget has dropped out of the tree, but depends
+  /// on an [InheritedWidget] that is still in the tree.
+  ///
+  /// It is set initially to false, since [_firstBuild] makes the initial call
+  /// on the [state]. When it is true, [build] will call
+  /// `state.didChangeDependencies` and then sets it to false. Subsequent calls
+  /// to [didChangeDependencies] set it to true.
+  bool _didChangeDependencies = false;
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _didChangeDependencies = true;
+  }
+
+  @override
+  DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
+    return _ElementDiagnosticableTreeNode(
+      name: name,
+      value: this,
+      style: style,
+      stateful: true,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<State<StatefulWidget>>('state', state, defaultValue: null));
+  }
+}
+
+/// An [Element] that uses a [ProxyWidget] as its configuration.
+abstract class ProxyElement extends ComponentElement {
+  /// Initializes fields for subclasses.
+  ProxyElement(ProxyWidget widget) : super(widget);
+
+  @override
+  ProxyWidget get widget => super.widget as ProxyWidget;
+
+  @override
+  Widget build() => widget.child;
+
+  @override
+  void update(ProxyWidget newWidget) {
+    final ProxyWidget oldWidget = widget;
+    assert(widget != null);
+    assert(widget != newWidget);
+    super.update(newWidget);
+    assert(widget == newWidget);
+    updated(oldWidget);
+    _dirty = true;
+    rebuild();
+  }
+
+  /// Called during build when the [widget] has changed.
+  ///
+  /// By default, calls [notifyClients]. Subclasses may override this method to
+  /// avoid calling [notifyClients] unnecessarily (e.g. if the old and new
+  /// widgets are equivalent).
+  @protected
+  void updated(covariant ProxyWidget oldWidget) {
+    notifyClients(oldWidget);
+  }
+
+  /// Notify other objects that the widget associated with this element has
+  /// changed.
+  ///
+  /// Called during [update] (via [updated]) after changing the widget
+  /// associated with this element but before rebuilding this element.
+  @protected
+  void notifyClients(covariant ProxyWidget oldWidget);
+}
+
+/// An [Element] that uses a [ParentDataWidget] as its configuration.
+class ParentDataElement<T extends ParentData> extends ProxyElement {
+  /// Creates an element that uses the given widget as its configuration.
+  ParentDataElement(ParentDataWidget<T> widget) : super(widget);
+
+  @override
+  ParentDataWidget<T> get widget => super.widget as ParentDataWidget<T>;
+
+  void _applyParentData(ParentDataWidget<T> widget) {
+    void applyParentDataToChild(Element child) {
+      if (child is RenderObjectElement) {
+        child._updateParentData(widget);
+      } else {
+        assert(child is! ParentDataElement<ParentData>);
+        child.visitChildren(applyParentDataToChild);
+      }
+    }
+    visitChildren(applyParentDataToChild);
+  }
+
+  /// Calls [ParentDataWidget.applyParentData] on the given widget, passing it
+  /// the [RenderObject] whose parent data this element is ultimately
+  /// responsible for.
+  ///
+  /// This allows a render object's [RenderObject.parentData] to be modified
+  /// without triggering a build. This is generally ill-advised, but makes sense
+  /// in situations such as the following:
+  ///
+  ///  * Build and layout are currently under way, but the [ParentData] in question
+  ///    does not affect layout, and the value to be applied could not be
+  ///    determined before build and layout (e.g. it depends on the layout of a
+  ///    descendant).
+  ///
+  ///  * Paint is currently under way, but the [ParentData] in question does not
+  ///    affect layout or paint, and the value to be applied could not be
+  ///    determined before paint (e.g. it depends on the compositing phase).
+  ///
+  /// In either case, the next build is expected to cause this element to be
+  /// configured with the given new widget (or a widget with equivalent data).
+  ///
+  /// Only [ParentDataWidget]s that return true for
+  /// [ParentDataWidget.debugCanApplyOutOfTurn] can be applied this way.
+  ///
+  /// The new widget must have the same child as the current widget.
+  ///
+  /// An example of when this is used is the [AutomaticKeepAlive] widget. If it
+  /// receives a notification during the build of one of its descendants saying
+  /// that its child must be kept alive, it will apply a [KeepAlive] widget out
+  /// of turn. This is safe, because by definition the child is already alive,
+  /// and therefore this will not change the behavior of the parent this frame.
+  /// It is more efficient than requesting an additional frame just for the
+  /// purpose of updating the [KeepAlive] widget.
+  void applyWidgetOutOfTurn(ParentDataWidget<T> newWidget) {
+    assert(newWidget != null);
+    assert(newWidget.debugCanApplyOutOfTurn());
+    assert(newWidget.child == widget.child);
+    _applyParentData(newWidget);
+  }
+
+  @override
+  void notifyClients(ParentDataWidget<T> oldWidget) {
+    _applyParentData(widget);
+  }
+}
+
+/// An [Element] that uses an [InheritedWidget] as its configuration.
+class InheritedElement extends ProxyElement {
+  /// Creates an element that uses the given widget as its configuration.
+  InheritedElement(InheritedWidget widget) : super(widget);
+
+  @override
+  InheritedWidget get widget => super.widget as InheritedWidget;
+
+  final Map<Element, Object?> _dependents = HashMap<Element, Object?>();
+
+  @override
+  void _updateInheritance() {
+    assert(_lifecycleState == _ElementLifecycle.active);
+    final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
+    if (incomingWidgets != null)
+      _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
+    else
+      _inheritedWidgets = HashMap<Type, InheritedElement>();
+    _inheritedWidgets![widget.runtimeType] = this;
+  }
+
+  @override
+  void debugDeactivated() {
+    assert(() {
+      assert(_dependents.isEmpty);
+      return true;
+    }());
+    super.debugDeactivated();
+  }
+
+  /// Returns the dependencies value recorded for [dependent]
+  /// with [setDependencies].
+  ///
+  /// Each dependent element is mapped to a single object value
+  /// which represents how the element depends on this
+  /// [InheritedElement]. This value is null by default and by default
+  /// dependent elements are rebuilt unconditionally.
+  ///
+  /// Subclasses can manage these values with [updateDependencies]
+  /// so that they can selectively rebuild dependents in
+  /// [notifyDependent].
+  ///
+  /// This method is typically only called in overrides of [updateDependencies].
+  ///
+  /// See also:
+  ///
+  ///  * [updateDependencies], which is called each time a dependency is
+  ///    created with [dependOnInheritedWidgetOfExactType].
+  ///  * [setDependencies], which sets dependencies value for a dependent
+  ///    element.
+  ///  * [notifyDependent], which can be overridden to use a dependent's
+  ///    dependencies value to decide if the dependent needs to be rebuilt.
+  ///  * [InheritedModel], which is an example of a class that uses this method
+  ///    to manage dependency values.
+  @protected
+  Object? getDependencies(Element dependent) {
+    return _dependents[dependent];
+  }
+
+  /// Sets the value returned by [getDependencies] value for [dependent].
+  ///
+  /// Each dependent element is mapped to a single object value
+  /// which represents how the element depends on this
+  /// [InheritedElement]. The [updateDependencies] method sets this value to
+  /// null by default so that dependent elements are rebuilt unconditionally.
+  ///
+  /// Subclasses can manage these values with [updateDependencies]
+  /// so that they can selectively rebuild dependents in [notifyDependent].
+  ///
+  /// This method is typically only called in overrides of [updateDependencies].
+  ///
+  /// See also:
+  ///
+  ///  * [updateDependencies], which is called each time a dependency is
+  ///    created with [dependOnInheritedWidgetOfExactType].
+  ///  * [getDependencies], which returns the current value for a dependent
+  ///    element.
+  ///  * [notifyDependent], which can be overridden to use a dependent's
+  ///    [getDependencies] value to decide if the dependent needs to be rebuilt.
+  ///  * [InheritedModel], which is an example of a class that uses this method
+  ///    to manage dependency values.
+  @protected
+  void setDependencies(Element dependent, Object? value) {
+    _dependents[dependent] = value;
+  }
+
+  /// Called by [dependOnInheritedWidgetOfExactType] when a new [dependent] is added.
+  ///
+  /// Each dependent element can be mapped to a single object value with
+  /// [setDependencies]. This method can lookup the existing dependencies with
+  /// [getDependencies].
+  ///
+  /// By default this method sets the inherited dependencies for [dependent]
+  /// to null. This only serves to record an unconditional dependency on
+  /// [dependent].
+  ///
+  /// Subclasses can manage their own dependencies values so that they
+  /// can selectively rebuild dependents in [notifyDependent].
+  ///
+  /// See also:
+  ///
+  ///  * [getDependencies], which returns the current value for a dependent
+  ///    element.
+  ///  * [setDependencies], which sets the value for a dependent element.
+  ///  * [notifyDependent], which can be overridden to use a dependent's
+  ///    dependencies value to decide if the dependent needs to be rebuilt.
+  ///  * [InheritedModel], which is an example of a class that uses this method
+  ///    to manage dependency values.
+  @protected
+  void updateDependencies(Element dependent, Object? aspect) {
+    setDependencies(dependent, null);
+  }
+
+  /// Called by [notifyClients] for each dependent.
+  ///
+  /// Calls `dependent.didChangeDependencies()` by default.
+  ///
+  /// Subclasses can override this method to selectively call
+  /// [didChangeDependencies] based on the value of [getDependencies].
+  ///
+  /// See also:
+  ///
+  ///  * [updateDependencies], which is called each time a dependency is
+  ///    created with [dependOnInheritedWidgetOfExactType].
+  ///  * [getDependencies], which returns the current value for a dependent
+  ///    element.
+  ///  * [setDependencies], which sets the value for a dependent element.
+  ///  * [InheritedModel], which is an example of a class that uses this method
+  ///    to manage dependency values.
+  @protected
+  void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
+    dependent.didChangeDependencies();
+  }
+
+  /// Calls [Element.didChangeDependencies] of all dependent elements, if
+  /// [InheritedWidget.updateShouldNotify] returns true.
+  ///
+  /// Called by [update], immediately prior to [build].
+  ///
+  /// Calls [notifyClients] to actually trigger the notifications.
+  @override
+  void updated(InheritedWidget oldWidget) {
+    if (widget.updateShouldNotify(oldWidget))
+      super.updated(oldWidget);
+  }
+
+  /// Notifies all dependent elements that this inherited widget has changed, by
+  /// calling [Element.didChangeDependencies].
+  ///
+  /// This method must only be called during the build phase. Usually this
+  /// method is called automatically when an inherited widget is rebuilt, e.g.
+  /// as a result of calling [State.setState] above the inherited widget.
+  ///
+  /// See also:
+  ///
+  ///  * [InheritedNotifier], a subclass of [InheritedWidget] that also calls
+  ///    this method when its [Listenable] sends a notification.
+  @override
+  void notifyClients(InheritedWidget oldWidget) {
+    assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
+    for (final Element dependent in _dependents.keys) {
+      assert(() {
+        // check that it really is our descendant
+        Element? ancestor = dependent._parent;
+        while (ancestor != this && ancestor != null)
+          ancestor = ancestor._parent;
+        return ancestor == this;
+      }());
+      // check that it really depends on us
+      assert(dependent._dependencies!.contains(this));
+      notifyDependent(oldWidget, dependent);
+    }
+  }
+}
+
+/// An [Element] that uses a [RenderObjectWidget] as its configuration.
+///
+/// [RenderObjectElement] objects have an associated [RenderObject] widget in
+/// the render tree, which handles concrete operations like laying out,
+/// painting, and hit testing.
+///
+/// Contrast with [ComponentElement].
+///
+/// For details on the lifecycle of an element, see the discussion at [Element].
+///
+/// ## Writing a RenderObjectElement subclass
+///
+/// There are three common child models used by most [RenderObject]s:
+///
+/// * Leaf render objects, with no children: The [LeafRenderObjectElement] class
+///   handles this case.
+///
+/// * A single child: The [SingleChildRenderObjectElement] class handles this
+///   case.
+///
+/// * A linked list of children: The [MultiChildRenderObjectElement] class
+///   handles this case.
+///
+/// Sometimes, however, a render object's child model is more complicated. Maybe
+/// it has a two-dimensional array of children. Maybe it constructs children on
+/// demand. Maybe it features multiple lists. In such situations, the
+/// corresponding [Element] for the [Widget] that configures that [RenderObject]
+/// will be a new subclass of [RenderObjectElement].
+///
+/// Such a subclass is responsible for managing children, specifically the
+/// [Element] children of this object, and the [RenderObject] children of its
+/// corresponding [RenderObject].
+///
+/// ### Specializing the getters
+///
+/// [RenderObjectElement] objects spend much of their time acting as
+/// intermediaries between their [widget] and their [renderObject]. To make this
+/// more tractable, most [RenderObjectElement] subclasses override these getters
+/// so that they return the specific type that the element expects, e.g.:
+///
+/// ```dart
+/// class FooElement extends RenderObjectElement {
+///
+///   @override
+///   Foo get widget => super.widget as Foo;
+///
+///   @override
+///   RenderFoo get renderObject => super.renderObject as RenderFoo;
+///
+///   // ...
+/// }
+/// ```
+///
+/// ### Slots
+///
+/// Each child [Element] corresponds to a [RenderObject] which should be
+/// attached to this element's render object as a child.
+///
+/// However, the immediate children of the element may not be the ones that
+/// eventually produce the actual [RenderObject] that they correspond to. For
+/// example a [StatelessElement] (the element of a [StatelessWidget]) simply
+/// corresponds to whatever [RenderObject] its child (the element returned by
+/// its [StatelessWidget.build] method) corresponds to.
+///
+/// Each child is therefore assigned a _slot_ token. This is an identifier whose
+/// meaning is private to this [RenderObjectElement] node. When the descendant
+/// that finally produces the [RenderObject] is ready to attach it to this
+/// node's render object, it passes that slot token back to this node, and that
+/// allows this node to cheaply identify where to put the child render object
+/// relative to the others in the parent render object.
+///
+/// ### Updating children
+///
+/// Early in the lifecycle of an element, the framework calls the [mount]
+/// method. This method should call [updateChild] for each child, passing in
+/// the widget for that child, and the slot for that child, thus obtaining a
+/// list of child [Element]s.
+///
+/// Subsequently, the framework will call the [update] method. In this method,
+/// the [RenderObjectElement] should call [updateChild] for each child, passing
+/// in the [Element] that was obtained during [mount] or the last time [update]
+/// was run (whichever happened most recently), the new [Widget], and the slot.
+/// This provides the object with a new list of [Element] objects.
+///
+/// Where possible, the [update] method should attempt to map the elements from
+/// the last pass to the widgets in the new pass. For example, if one of the
+/// elements from the last pass was configured with a particular [Key], and one
+/// of the widgets in this new pass has that same key, they should be paired up,
+/// and the old element should be updated with the widget (and the slot
+/// corresponding to the new widget's new position, also). The [updateChildren]
+/// method may be useful in this regard.
+///
+/// [updateChild] should be called for children in their logical order. The
+/// order can matter; for example, if two of the children use [PageStorage]'s
+/// `writeState` feature in their build method (and neither has a [Widget.key]),
+/// then the state written by the first will be overwritten by the second.
+///
+/// #### Dynamically determining the children during the build phase
+///
+/// The child widgets need not necessarily come from this element's widget
+/// verbatim. They could be generated dynamically from a callback, or generated
+/// in other more creative ways.
+///
+/// #### Dynamically determining the children during layout
+///
+/// If the widgets are to be generated at layout time, then generating them in
+/// the [mount] and [update] methods won't work: layout of this element's render
+/// object hasn't started yet at that point. Instead, the [update] method can
+/// mark the render object as needing layout (see
+/// [RenderObject.markNeedsLayout]), and then the render object's
+/// [RenderObject.performLayout] method can call back to the element to have it
+/// generate the widgets and call [updateChild] accordingly.
+///
+/// For a render object to call an element during layout, it must use
+/// [RenderObject.invokeLayoutCallback]. For an element to call [updateChild]
+/// outside of its [update] method, it must use [BuildOwner.buildScope].
+///
+/// The framework provides many more checks in normal operation than it does
+/// when doing a build during layout. For this reason, creating widgets with
+/// layout-time build semantics should be done with great care.
+///
+/// #### Handling errors when building
+///
+/// If an element calls a builder function to obtain widgets for its children,
+/// it may find that the build throws an exception. Such exceptions should be
+/// caught and reported using [FlutterError.reportError]. If a child is needed
+/// but a builder has failed in this way, an instance of [ErrorWidget] can be
+/// used instead.
+///
+/// ### Detaching children
+///
+/// It is possible, when using [GlobalKey]s, for a child to be proactively
+/// removed by another element before this element has been updated.
+/// (Specifically, this happens when the subtree rooted at a widget with a
+/// particular [GlobalKey] is being moved from this element to an element
+/// processed earlier in the build phase.) When this happens, this element's
+/// [forgetChild] method will be called with a reference to the affected child
+/// element.
+///
+/// The [forgetChild] method of a [RenderObjectElement] subclass must remove the
+/// child element from its child list, so that when it next [update]s its
+/// children, the removed child is not considered.
+///
+/// For performance reasons, if there are many elements, it may be quicker to
+/// track which elements were forgotten by storing them in a [Set], rather than
+/// proactively mutating the local record of the child list and the identities
+/// of all the slots. For example, see the implementation of
+/// [MultiChildRenderObjectElement].
+///
+/// ### Maintaining the render object tree
+///
+/// Once a descendant produces a render object, it will call
+/// [insertRenderObjectChild]. If the descendant's slot changes identity, it
+/// will call [moveRenderObjectChild]. If a descendant goes away, it will call
+/// [removeRenderObjectChild].
+///
+/// These three methods should update the render tree accordingly, attaching,
+/// moving, and detaching the given child render object from this element's own
+/// render object respectively.
+///
+/// ### Walking the children
+///
+/// If a [RenderObjectElement] object has any children [Element]s, it must
+/// expose them in its implementation of the [visitChildren] method. This method
+/// is used by many of the framework's internal mechanisms, and so should be
+/// fast. It is also used by the test framework and [debugDumpApp].
+abstract class RenderObjectElement extends Element {
+  /// Creates an element that uses the given widget as its configuration.
+  RenderObjectElement(RenderObjectWidget widget) : super(widget);
+
+  @override
+  RenderObjectWidget get widget => super.widget as RenderObjectWidget;
+
+  /// The underlying [RenderObject] for this element.
+  @override
+  RenderObject get renderObject => _renderObject;
+  late RenderObject _renderObject;
+
+  bool _debugDoingBuild = false;
+  @override
+  bool get debugDoingBuild => _debugDoingBuild;
+
+  RenderObjectElement? _ancestorRenderObjectElement;
+
+  RenderObjectElement? _findAncestorRenderObjectElement() {
+    Element? ancestor = _parent;
+    while (ancestor != null && ancestor is! RenderObjectElement)
+      ancestor = ancestor._parent;
+    return ancestor as RenderObjectElement?;
+  }
+
+  ParentDataElement<ParentData>? _findAncestorParentDataElement() {
+    Element? ancestor = _parent;
+    ParentDataElement<ParentData>? result;
+    while (ancestor != null && ancestor is! RenderObjectElement) {
+      if (ancestor is ParentDataElement<ParentData>) {
+        result = ancestor;
+        break;
+      }
+      ancestor = ancestor._parent;
+    }
+    assert(() {
+      if (result == null || ancestor == null) {
+        return true;
+      }
+      // Check that no other ParentDataWidgets want to provide parent data.
+      final List<ParentDataElement<ParentData>> badAncestors = <ParentDataElement<ParentData>>[];
+      ancestor = ancestor!._parent;
+      while (ancestor != null && ancestor is! RenderObjectElement) {
+        if (ancestor is ParentDataElement<ParentData>) {
+          badAncestors.add(ancestor! as ParentDataElement<ParentData>);
+        }
+        ancestor = ancestor!._parent;
+      }
+      if (badAncestors.isNotEmpty) {
+        badAncestors.insert(0, result);
+        try {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('Incorrect use of ParentDataWidget.'),
+            ErrorDescription('The following ParentDataWidgets are providing parent data to the same RenderObject:'),
+            for (final ParentDataElement<ParentData> ancestor in badAncestors)
+              ErrorDescription('- ${ancestor.widget} (typically placed directly inside a ${ancestor.widget.debugTypicalAncestorWidgetClass} widget)'),
+            ErrorDescription('However, a RenderObject can only receive parent data from at most one ParentDataWidget.'),
+            ErrorHint('Usually, this indicates that at least one of the offending ParentDataWidgets listed above is not placed directly inside a compatible ancestor widget.'),
+            ErrorDescription('The ownership chain for the RenderObject that received the parent data was:\n  ${debugGetCreatorChain(10)}'),
+          ]);
+        } on FlutterError catch (e) {
+          _debugReportException(ErrorSummary('while looking for parent data.'), e, e.stackTrace);
+        }
+      }
+      return true;
+    }());
+    return result;
+  }
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    assert(() {
+      _debugDoingBuild = true;
+      return true;
+    }());
+    _renderObject = widget.createRenderObject(this);
+    assert(() {
+      _debugDoingBuild = false;
+      return true;
+    }());
+    assert(() {
+      _debugUpdateRenderObjectOwner();
+      return true;
+    }());
+    assert(_slot == newSlot);
+    attachRenderObject(newSlot);
+    _dirty = false;
+  }
+
+  @override
+  void update(covariant RenderObjectWidget newWidget) {
+    super.update(newWidget);
+    assert(widget == newWidget);
+    assert(() {
+      _debugUpdateRenderObjectOwner();
+      return true;
+    }());
+    assert(() {
+      _debugDoingBuild = true;
+      return true;
+    }());
+    widget.updateRenderObject(this, renderObject);
+    assert(() {
+      _debugDoingBuild = false;
+      return true;
+    }());
+    _dirty = false;
+  }
+
+  void _debugUpdateRenderObjectOwner() {
+    assert(() {
+      _renderObject.debugCreator = DebugCreator(this);
+      return true;
+    }());
+  }
+
+  @override
+  void performRebuild() {
+    assert(() {
+      _debugDoingBuild = true;
+      return true;
+    }());
+    widget.updateRenderObject(this, renderObject);
+    assert(() {
+      _debugDoingBuild = false;
+      return true;
+    }());
+    _dirty = false;
+  }
+
+  /// Updates the children of this element to use new widgets.
+  ///
+  /// Attempts to update the given old children list using the given new
+  /// widgets, removing obsolete elements and introducing new ones as necessary,
+  /// and then returns the new child list.
+  ///
+  /// During this function the `oldChildren` list must not be modified. If the
+  /// caller wishes to remove elements from `oldChildren` re-entrantly while
+  /// this function is on the stack, the caller can supply a `forgottenChildren`
+  /// argument, which can be modified while this function is on the stack.
+  /// Whenever this function reads from `oldChildren`, this function first
+  /// checks whether the child is in `forgottenChildren`. If it is, the function
+  /// acts as if the child was not in `oldChildren`.
+  ///
+  /// This function is a convenience wrapper around [updateChild], which updates
+  /// each individual child. When calling [updateChild], this function uses an
+  /// [IndexedSlot<Element>] as the value for the `newSlot` argument.
+  /// [IndexedSlot.index] is set to the index that the currently processed
+  /// `child` corresponds to in the `newWidgets` list and [IndexedSlot.value] is
+  /// set to the [Element] of the previous widget in that list (or null if it is
+  /// the first child).
+  ///
+  /// When the [slot] value of an [Element] changes, its
+  /// associated [renderObject] needs to move to a new position in the child
+  /// list of its parents. If that [RenderObject] organizes its children in a
+  /// linked list (as is done by the [ContainerRenderObjectMixin]) this can
+  /// be implemented by re-inserting the child [RenderObject] into the
+  /// list after the [RenderObject] associated with the [Element] provided as
+  /// [IndexedSlot.value] in the [slot] object.
+  ///
+  /// Simply using the previous sibling as a [slot] is not enough, though, because
+  /// child [RenderObject]s are only moved around when the [slot] of their
+  /// associated [RenderObjectElement]s is updated. When the order of child
+  /// [Element]s is changed, some elements in the list may move to a new index
+  /// but still have the same previous sibling. For example, when
+  /// `[e1, e2, e3, e4]` is changed to `[e1, e3, e4, e2]` the element e4
+  /// continues to have e3 as a previous sibling even though its index in the list
+  /// has changed and its [RenderObject] needs to move to come before e2's
+  /// [RenderObject]. In order to trigger this move, a new [slot] value needs to
+  /// be assigned to its [Element] whenever its index in its
+  /// parent's child list changes. Using an [IndexedSlot<Element>] achieves
+  /// exactly that and also ensures that the underlying parent [RenderObject]
+  /// knows where a child needs to move to in a linked list by providing its new
+  /// previous sibling.
+  @protected
+  List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element>? forgottenChildren }) {
+    assert(oldChildren != null);
+    assert(newWidgets != null);
+
+    Element? replaceWithNullIfForgotten(Element child) {
+      return forgottenChildren != null && forgottenChildren.contains(child) ? null : child;
+    }
+
+    // This attempts to diff the new child list (newWidgets) with
+    // the old child list (oldChildren), and produce a new list of elements to
+    // be the new list of child elements of this element. The called of this
+    // method is expected to update this render object accordingly.
+
+    // The cases it tries to optimize for are:
+    //  - the old list is empty
+    //  - the lists are identical
+    //  - there is an insertion or removal of one or more widgets in
+    //    only one place in the list
+    // If a widget with a key is in both lists, it will be synced.
+    // Widgets without keys might be synced but there is no guarantee.
+
+    // The general approach is to sync the entire new list backwards, as follows:
+    // 1. Walk the lists from the top, syncing nodes, until you no longer have
+    //    matching nodes.
+    // 2. Walk the lists from the bottom, without syncing nodes, until you no
+    //    longer have matching nodes. We'll sync these nodes at the end. We
+    //    don't sync them now because we want to sync all the nodes in order
+    //    from beginning to end.
+    // At this point we narrowed the old and new lists to the point
+    // where the nodes no longer match.
+    // 3. Walk the narrowed part of the old list to get the list of
+    //    keys and sync null with non-keyed items.
+    // 4. Walk the narrowed part of the new list forwards:
+    //     * Sync non-keyed items with null
+    //     * Sync keyed items with the source if it exists, else with null.
+    // 5. Walk the bottom of the list again, syncing the nodes.
+    // 6. Sync null with any items in the list of keys that are still
+    //    mounted.
+
+    int newChildrenTop = 0;
+    int oldChildrenTop = 0;
+    int newChildrenBottom = newWidgets.length - 1;
+    int oldChildrenBottom = oldChildren.length - 1;
+
+    final List<Element> newChildren = oldChildren.length == newWidgets.length ?
+        oldChildren : List<Element>.filled(newWidgets.length, _NullElement.instance, growable: false);
+
+    Element? previousChild;
+
+    // Update the top of the list.
+    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
+      final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
+      final Widget newWidget = newWidgets[newChildrenTop];
+      assert(oldChild == null || oldChild._lifecycleState == _ElementLifecycle.active);
+      if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
+        break;
+      final Element newChild = updateChild(oldChild, newWidget, IndexedSlot<Element?>(newChildrenTop, previousChild))!;
+      assert(newChild._lifecycleState == _ElementLifecycle.active);
+      newChildren[newChildrenTop] = newChild;
+      previousChild = newChild;
+      newChildrenTop += 1;
+      oldChildrenTop += 1;
+    }
+
+    // Scan the bottom of the list.
+    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
+      final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);
+      final Widget newWidget = newWidgets[newChildrenBottom];
+      assert(oldChild == null || oldChild._lifecycleState == _ElementLifecycle.active);
+      if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
+        break;
+      oldChildrenBottom -= 1;
+      newChildrenBottom -= 1;
+    }
+
+    // Scan the old children in the middle of the list.
+    final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
+    Map<Key, Element>? oldKeyedChildren;
+    if (haveOldChildren) {
+      oldKeyedChildren = <Key, Element>{};
+      while (oldChildrenTop <= oldChildrenBottom) {
+        final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
+        assert(oldChild == null || oldChild._lifecycleState == _ElementLifecycle.active);
+        if (oldChild != null) {
+          if (oldChild.widget.key != null)
+            oldKeyedChildren[oldChild.widget.key!] = oldChild;
+          else
+            deactivateChild(oldChild);
+        }
+        oldChildrenTop += 1;
+      }
+    }
+
+    // Update the middle of the list.
+    while (newChildrenTop <= newChildrenBottom) {
+      Element? oldChild;
+      final Widget newWidget = newWidgets[newChildrenTop];
+      if (haveOldChildren) {
+        final Key? key = newWidget.key;
+        if (key != null) {
+          oldChild = oldKeyedChildren![key];
+          if (oldChild != null) {
+            if (Widget.canUpdate(oldChild.widget, newWidget)) {
+              // we found a match!
+              // remove it from oldKeyedChildren so we don't unsync it later
+              oldKeyedChildren.remove(key);
+            } else {
+              // Not a match, let's pretend we didn't see it for now.
+              oldChild = null;
+            }
+          }
+        }
+      }
+      assert(oldChild == null || Widget.canUpdate(oldChild.widget, newWidget));
+      final Element newChild = updateChild(oldChild, newWidget, IndexedSlot<Element?>(newChildrenTop, previousChild))!;
+      assert(newChild._lifecycleState == _ElementLifecycle.active);
+      assert(oldChild == newChild || oldChild == null || oldChild._lifecycleState != _ElementLifecycle.active);
+      newChildren[newChildrenTop] = newChild;
+      previousChild = newChild;
+      newChildrenTop += 1;
+    }
+
+    // We've scanned the whole list.
+    assert(oldChildrenTop == oldChildrenBottom + 1);
+    assert(newChildrenTop == newChildrenBottom + 1);
+    assert(newWidgets.length - newChildrenTop == oldChildren.length - oldChildrenTop);
+    newChildrenBottom = newWidgets.length - 1;
+    oldChildrenBottom = oldChildren.length - 1;
+
+    // Update the bottom of the list.
+    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
+      final Element oldChild = oldChildren[oldChildrenTop];
+      assert(replaceWithNullIfForgotten(oldChild) != null);
+      assert(oldChild._lifecycleState == _ElementLifecycle.active);
+      final Widget newWidget = newWidgets[newChildrenTop];
+      assert(Widget.canUpdate(oldChild.widget, newWidget));
+      final Element newChild = updateChild(oldChild, newWidget, IndexedSlot<Element?>(newChildrenTop, previousChild))!;
+      assert(newChild._lifecycleState == _ElementLifecycle.active);
+      assert(oldChild == newChild || oldChild == null || oldChild._lifecycleState != _ElementLifecycle.active);
+      newChildren[newChildrenTop] = newChild;
+      previousChild = newChild;
+      newChildrenTop += 1;
+      oldChildrenTop += 1;
+    }
+
+    // Clean up any of the remaining middle nodes from the old list.
+    if (haveOldChildren && oldKeyedChildren!.isNotEmpty) {
+      for (final Element oldChild in oldKeyedChildren.values) {
+        if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
+          deactivateChild(oldChild);
+      }
+    }
+    assert(newChildren.every((Element element) => element is! _NullElement));
+    return newChildren;
+  }
+
+  @override
+  void deactivate() {
+    super.deactivate();
+    assert(!renderObject.attached,
+      'A RenderObject was still attached when attempting to deactivate its '
+      'RenderObjectElement: $renderObject');
+  }
+
+  @override
+  void unmount() {
+    super.unmount();
+    assert(!renderObject.attached,
+      'A RenderObject was still attached when attempting to unmount its '
+      'RenderObjectElement: $renderObject');
+    widget.didUnmountRenderObject(renderObject);
+  }
+
+  void _updateParentData(ParentDataWidget<ParentData> parentDataWidget) {
+    bool applyParentData = true;
+    assert(() {
+      try {
+        if (!parentDataWidget.debugIsValidRenderObject(renderObject)) {
+          applyParentData = false;
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('Incorrect use of ParentDataWidget.'),
+            ...parentDataWidget._debugDescribeIncorrectParentDataType(
+              parentData: renderObject.parentData,
+              parentDataCreator: _ancestorRenderObjectElement!.widget,
+              ownershipChain: ErrorDescription(debugGetCreatorChain(10)),
+            ),
+          ]);
+        }
+      } on FlutterError catch (e) {
+        // Catching the exception directly to avoid activating the ErrorWidget.
+        // Since the tree is in a broken state, adding the ErrorWidget would
+        // cause more exceptions.
+        _debugReportException(ErrorSummary('while applying parent data.'), e, e.stackTrace);
+      }
+      return true;
+    }());
+    if (applyParentData)
+      parentDataWidget.applyParentData(renderObject);
+  }
+
+  @override
+  void _updateSlot(dynamic newSlot) {
+    final dynamic oldSlot = slot;
+    assert(oldSlot != newSlot);
+    super._updateSlot(newSlot);
+    assert(slot == newSlot);
+    _ancestorRenderObjectElement!.moveRenderObjectChild(renderObject, oldSlot, slot);
+  }
+
+  @override
+  void attachRenderObject(dynamic newSlot) {
+    assert(_ancestorRenderObjectElement == null);
+    _slot = newSlot;
+    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
+    _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
+    final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
+    if (parentDataElement != null)
+      _updateParentData(parentDataElement.widget);
+  }
+
+  @override
+  void detachRenderObject() {
+    if (_ancestorRenderObjectElement != null) {
+      _ancestorRenderObjectElement!.removeRenderObjectChild(renderObject, slot);
+      _ancestorRenderObjectElement = null;
+    }
+    _slot = null;
+  }
+
+  /// Insert the given child into [renderObject] at the given slot.
+  ///
+  /// {@macro flutter.widgets.RenderObjectElement.insertRenderObjectChild}
+  ///
+  /// ## Deprecation
+  ///
+  /// This method has been deprecated in favor of [insertRenderObjectChild].
+  ///
+  /// The reason for the deprecation is to provide the `oldSlot` argument to
+  /// the [moveRenderObjectChild] method (such an argument was missing from
+  /// the now-deprecated [moveChildRenderObject] method) and the `slot`
+  /// argument to the [removeRenderObjectChild] method (such an argument was
+  /// missing from the now-deprecated [removeChildRenderObject] method). While
+  /// no argument was added to [insertRenderObjectChild], the name change (and
+  /// corresponding deprecation) was made to maintain naming parity with the
+  /// other two methods.
+  ///
+  /// To migrate, simply override [insertRenderObjectChild] instead of
+  /// [insertChildRenderObject]. The arguments stay the same. Subclasses should
+  /// _not_ call `super.insertRenderObjectChild(...)`.
+  @protected
+  @mustCallSuper
+  @Deprecated(
+    'Override insertRenderObjectChild instead. '
+    'This feature was deprecated after v1.21.0-9.0.pre.'
+  )
+  void insertChildRenderObject(covariant RenderObject child, covariant dynamic slot) {
+    assert(() {
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('RenderObjectElement.insertChildRenderObject() is deprecated.'),
+        toDiagnosticsNode(
+          name: 'insertChildRenderObject() was called on this Element',
+          style: DiagnosticsTreeStyle.shallow,
+        ),
+        ErrorDescription('insertChildRenderObject() has been deprecated in favor of '
+          'insertRenderObjectChild(). See https://github.com/flutter/flutter/issues/63269 '
+          'for details.'),
+        ErrorHint('Rather than overriding insertChildRenderObject() in your '
+          'RenderObjectElement subclass, override insertRenderObjectChild() instead, '
+          "and DON'T call super.insertRenderObjectChild(). If you're implementing a "
+          'new RenderObjectElement, you should override/implement '
+          'insertRenderObjectChild(), moveRenderObjectChild(), and '
+          'removeRenderObjectChild().'),
+      ]);
+    }());
+  }
+
+  /// Insert the given child into [renderObject] at the given slot.
+  ///
+  /// {@template flutter.widgets.RenderObjectElement.insertRenderObjectChild}
+  /// The semantics of `slot` are determined by this element. For example, if
+  /// this element has a single child, the slot should always be null. If this
+  /// element has a list of children, the previous sibling element wrapped in an
+  /// [IndexedSlot] is a convenient value for the slot.
+  /// {@endtemplate}
+  @protected
+  void insertRenderObjectChild(covariant RenderObject child, covariant dynamic slot) {
+    insertChildRenderObject(child, slot);
+  }
+
+  /// Move the given child to the given slot.
+  ///
+  /// The given child is guaranteed to have [renderObject] as its parent.
+  ///
+  /// {@macro flutter.widgets.RenderObjectElement.insertRenderObjectChild}
+  ///
+  /// This method is only ever called if [updateChild] can end up being called
+  /// with an existing [Element] child and a `slot` that differs from the slot
+  /// that element was previously given. [MultiChildRenderObjectElement] does this,
+  /// for example. [SingleChildRenderObjectElement] does not (since the `slot` is
+  /// always null). An [Element] that has a specific set of slots with each child
+  /// always having the same slot (and where children in different slots are never
+  /// compared against each other for the purposes of updating one slot with the
+  /// element from another slot) would never call this.
+  ///
+  /// ## Deprecation
+  ///
+  /// This method has been deprecated in favor of [moveRenderObjectChild].
+  ///
+  /// The reason for the deprecation is to provide the `oldSlot` argument to
+  /// the [moveRenderObjectChild] method (such an argument was missing from
+  /// the now-deprecated [moveChildRenderObject] method) and the `slot`
+  /// argument to the [removeRenderObjectChild] method (such an argument was
+  /// missing from the now-deprecated [removeChildRenderObject] method). While
+  /// no argument was added to [insertRenderObjectChild], the name change (and
+  /// corresponding deprecation) was made to maintain naming parity with the
+  /// other two methods.
+  ///
+  /// To migrate, simply override [moveRenderObjectChild] instead of
+  /// [moveChildRenderObject]. The `slot` argument becomes the `newSlot`
+  /// argument, and the method will now take a new `oldSlot` argument that
+  /// subclasses may find useful. Subclasses should _not_ call
+  /// `super.moveRenderObjectChild(...)`.
+  @protected
+  @mustCallSuper
+  @Deprecated(
+    'Override moveRenderObjectChild instead. '
+    'This feature was deprecated after v1.21.0-9.0.pre.'
+  )
+  void moveChildRenderObject(covariant RenderObject child, covariant dynamic slot) {
+    assert(() {
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('RenderObjectElement.moveChildRenderObject() is deprecated.'),
+        toDiagnosticsNode(
+          name: 'super.moveChildRenderObject() was called on this Element',
+          style: DiagnosticsTreeStyle.shallow,
+        ),
+        ErrorDescription('moveChildRenderObject() has been deprecated in favor of '
+            'moveRenderObjectChild(). See https://github.com/flutter/flutter/issues/63269 '
+            'for details.'),
+        ErrorHint('Rather than overriding moveChildRenderObject() in your '
+            'RenderObjectElement subclass, override moveRenderObjectChild() instead, '
+            "and DON'T call super.moveRenderObjectChild(). If you're implementing a "
+            'new RenderObjectElement, you should override/implement '
+            'insertRenderObjectChild(), moveRenderObjectChild(), and '
+            'removeRenderObjectChild().'),
+      ]);
+    }());
+  }
+
+  /// Move the given child from the given old slot to the given new slot.
+  ///
+  /// The given child is guaranteed to have [renderObject] as its parent.
+  ///
+  /// {@macro flutter.widgets.RenderObjectElement.insertRenderObjectChild}
+  ///
+  /// This method is only ever called if [updateChild] can end up being called
+  /// with an existing [Element] child and a `slot` that differs from the slot
+  /// that element was previously given. [MultiChildRenderObjectElement] does this,
+  /// for example. [SingleChildRenderObjectElement] does not (since the `slot` is
+  /// always null). An [Element] that has a specific set of slots with each child
+  /// always having the same slot (and where children in different slots are never
+  /// compared against each other for the purposes of updating one slot with the
+  /// element from another slot) would never call this.
+  @protected
+  void moveRenderObjectChild(covariant RenderObject child, covariant dynamic oldSlot, covariant dynamic newSlot) {
+    moveChildRenderObject(child, newSlot);
+  }
+
+  /// Remove the given child from [renderObject].
+  ///
+  /// The given child is guaranteed to have [renderObject] as its parent.
+  ///
+  /// ## Deprecation
+  ///
+  /// This method has been deprecated in favor of [removeRenderObjectChild].
+  ///
+  /// The reason for the deprecation is to provide the `oldSlot` argument to
+  /// the [moveRenderObjectChild] method (such an argument was missing from
+  /// the now-deprecated [moveChildRenderObject] method) and the `slot`
+  /// argument to the [removeRenderObjectChild] method (such an argument was
+  /// missing from the now-deprecated [removeChildRenderObject] method). While
+  /// no argument was added to [insertRenderObjectChild], the name change (and
+  /// corresponding deprecation) was made to maintain naming parity with the
+  /// other two methods.
+  ///
+  /// To migrate, simply override [removeRenderObjectChild] instead of
+  /// [removeChildRenderObject]. The method will now take a new `slot` argument
+  /// that subclasses may find useful. Subclasses should _not_ call
+  /// `super.removeRenderObjectChild(...)`.
+  @protected
+  @mustCallSuper
+  @Deprecated(
+    'Override removeRenderObjectChild instead. '
+    'This feature was deprecated after v1.21.0-9.0.pre.'
+  )
+  void removeChildRenderObject(covariant RenderObject child) {
+    assert(() {
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('RenderObjectElement.removeChildRenderObject() is deprecated.'),
+        toDiagnosticsNode(
+          name: 'super.removeChildRenderObject() was called on this Element',
+          style: DiagnosticsTreeStyle.shallow,
+        ),
+        ErrorDescription('removeChildRenderObject() has been deprecated in favor of '
+            'removeRenderObjectChild(). See https://github.com/flutter/flutter/issues/63269 '
+            'for details.'),
+        ErrorHint('Rather than overriding removeChildRenderObject() in your '
+            'RenderObjectElement subclass, override removeRenderObjectChild() instead, '
+            "and DON'T call super.removeRenderObjectChild(). If you're implementing a "
+            'new RenderObjectElement, you should override/implement '
+            'insertRenderObjectChild(), moveRenderObjectChild(), and '
+            'removeRenderObjectChild().'),
+      ]);
+    }());
+  }
+
+  /// Remove the given child from [renderObject].
+  ///
+  /// The given child is guaranteed to have been inserted at the given `slot`
+  /// and have [renderObject] as its parent.
+  @protected
+  void removeRenderObjectChild(covariant RenderObject child, covariant dynamic slot) {
+    removeChildRenderObject(child);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<RenderObject>('renderObject', renderObject, defaultValue: null));
+  }
+}
+
+/// The element at the root of the tree.
+///
+/// Only root elements may have their owner set explicitly. All other
+/// elements inherit their owner from their parent.
+abstract class RootRenderObjectElement extends RenderObjectElement {
+  /// Initializes fields for subclasses.
+  RootRenderObjectElement(RenderObjectWidget widget) : super(widget);
+
+  /// Set the owner of the element. The owner will be propagated to all the
+  /// descendants of this element.
+  ///
+  /// The owner manages the dirty elements list.
+  ///
+  /// The [WidgetsBinding] introduces the primary owner,
+  /// [WidgetsBinding.buildOwner], and assigns it to the widget tree in the call
+  /// to [runApp]. The binding is responsible for driving the build pipeline by
+  /// calling the build owner's [BuildOwner.buildScope] method. See
+  /// [WidgetsBinding.drawFrame].
+  void assignOwner(BuildOwner owner) {
+    _owner = owner;
+  }
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    // Root elements should never have parents.
+    assert(parent == null);
+    assert(newSlot == null);
+    super.mount(parent, newSlot);
+  }
+}
+
+/// An [Element] that uses a [LeafRenderObjectWidget] as its configuration.
+class LeafRenderObjectElement extends RenderObjectElement {
+  /// Creates an element that uses the given widget as its configuration.
+  LeafRenderObjectElement(LeafRenderObjectWidget widget) : super(widget);
+
+  @override
+  void forgetChild(Element child) {
+    assert(false);
+    super.forgetChild(child);
+  }
+
+  @override
+  void insertRenderObjectChild(RenderObject child, dynamic slot) {
+    assert(false);
+  }
+
+  @override
+  void moveRenderObjectChild(RenderObject child, dynamic oldSlot, dynamic newSlot) {
+    assert(false);
+  }
+
+  @override
+  void removeRenderObjectChild(RenderObject child, dynamic slot) {
+    assert(false);
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    return widget.debugDescribeChildren();
+  }
+}
+
+/// An [Element] that uses a [SingleChildRenderObjectWidget] as its configuration.
+///
+/// The child is optional.
+///
+/// This element subclass can be used for RenderObjectWidgets whose
+/// RenderObjects use the [RenderObjectWithChildMixin] mixin. Such widgets are
+/// expected to inherit from [SingleChildRenderObjectWidget].
+class SingleChildRenderObjectElement extends RenderObjectElement {
+  /// Creates an element that uses the given widget as its configuration.
+  SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);
+
+  @override
+  SingleChildRenderObjectWidget get widget => super.widget as SingleChildRenderObjectWidget;
+
+  Element? _child;
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    if (_child != null)
+      visitor(_child!);
+  }
+
+  @override
+  void forgetChild(Element child) {
+    assert(child == _child);
+    _child = null;
+    super.forgetChild(child);
+  }
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    _child = updateChild(_child, widget.child, null);
+  }
+
+  @override
+  void update(SingleChildRenderObjectWidget newWidget) {
+    super.update(newWidget);
+    assert(widget == newWidget);
+    _child = updateChild(_child, widget.child, null);
+  }
+
+  @override
+  void insertRenderObjectChild(RenderObject child, dynamic slot) {
+    final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject as RenderObjectWithChildMixin<RenderObject>;
+    assert(slot == null);
+    assert(renderObject.debugValidateChild(child));
+    renderObject.child = child;
+    assert(renderObject == this.renderObject);
+  }
+
+  @override
+  void moveRenderObjectChild(RenderObject child, dynamic oldSlot, dynamic newSlot) {
+    assert(false);
+  }
+
+  @override
+  void removeRenderObjectChild(RenderObject child, dynamic slot) {
+    final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject as RenderObjectWithChildMixin<RenderObject>;
+    assert(slot == null);
+    assert(renderObject.child == child);
+    renderObject.child = null;
+    assert(renderObject == this.renderObject);
+  }
+}
+
+/// An [Element] that uses a [MultiChildRenderObjectWidget] as its configuration.
+///
+/// This element subclass can be used for RenderObjectWidgets whose
+/// RenderObjects use the [ContainerRenderObjectMixin] mixin with a parent data
+/// type that implements [ContainerParentDataMixin<RenderObject>]. Such widgets
+/// are expected to inherit from [MultiChildRenderObjectWidget].
+///
+/// See also:
+///
+/// * [IndexedSlot], which is used as [Element.slot]s for the children of a
+///   [MultiChildRenderObjectElement].
+/// * [RenderObjectElement.updateChildren], which discusses why [IndexedSlot]
+///   is used for the slots of the children.
+class MultiChildRenderObjectElement extends RenderObjectElement {
+  /// Creates an element that uses the given widget as its configuration.
+  MultiChildRenderObjectElement(MultiChildRenderObjectWidget widget)
+    : assert(!debugChildrenHaveDuplicateKeys(widget, widget.children)),
+      super(widget);
+
+  @override
+  MultiChildRenderObjectWidget get widget => super.widget as MultiChildRenderObjectWidget;
+
+  /// The current list of children of this element.
+  ///
+  /// This list is filtered to hide elements that have been forgotten (using
+  /// [forgetChild]).
+  @protected
+  @visibleForTesting
+  Iterable<Element> get children => _children.where((Element child) => !_forgottenChildren.contains(child));
+
+  late List<Element> _children;
+  // We keep a set of forgotten children to avoid O(n^2) work walking _children
+  // repeatedly to remove children.
+  final Set<Element> _forgottenChildren = HashSet<Element>();
+
+  @override
+  void insertRenderObjectChild(RenderObject child, IndexedSlot<Element?> slot) {
+    final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject =
+      this.renderObject as ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>>;
+    assert(renderObject.debugValidateChild(child));
+    renderObject.insert(child, after: slot.value?.renderObject);
+    assert(renderObject == this.renderObject);
+  }
+
+  @override
+  void moveRenderObjectChild(RenderObject child, IndexedSlot<Element?> oldSlot, IndexedSlot<Element?> newSlot) {
+    final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject =
+      this.renderObject as ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>>;
+    assert(child.parent == renderObject);
+    renderObject.move(child, after: newSlot.value?.renderObject);
+    assert(renderObject == this.renderObject);
+  }
+
+  @override
+  void removeRenderObjectChild(RenderObject child, dynamic slot) {
+    final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject =
+      this.renderObject as ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>>;
+    assert(child.parent == renderObject);
+    renderObject.remove(child);
+    assert(renderObject == this.renderObject);
+  }
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    for (final Element child in _children) {
+      if (!_forgottenChildren.contains(child))
+        visitor(child);
+    }
+  }
+
+  @override
+  void forgetChild(Element child) {
+    assert(_children.contains(child));
+    assert(!_forgottenChildren.contains(child));
+    _forgottenChildren.add(child);
+    super.forgetChild(child);
+  }
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    final List<Element> children = List<Element>.filled(widget.children.length, _NullElement.instance, growable: false);
+    Element? previousChild;
+    for (int i = 0; i < children.length; i += 1) {
+      final Element newChild = inflateWidget(widget.children[i], IndexedSlot<Element?>(i, previousChild));
+      children[i] = newChild;
+      previousChild = newChild;
+    }
+    _children = children;
+  }
+
+  @override
+  void update(MultiChildRenderObjectWidget newWidget) {
+    super.update(newWidget);
+    assert(widget == newWidget);
+    _children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
+    _forgottenChildren.clear();
+  }
+}
+
+/// A wrapper class for the [Element] that is the creator of a [RenderObject].
+///
+/// Attaching a [DebugCreator] attach the [RenderObject] will lead to better error
+/// message.
+class DebugCreator {
+  /// Create a [DebugCreator] instance with input [Element].
+  DebugCreator(this.element);
+
+  /// The creator of the [RenderObject].
+  final Element element;
+
+  @override
+  String toString() => element.debugGetCreatorChain(12);
+}
+
+FlutterErrorDetails _debugReportException(
+  DiagnosticsNode context,
+  Object exception,
+  StackTrace? stack, {
+  InformationCollector? informationCollector,
+}) {
+  final FlutterErrorDetails details = FlutterErrorDetails(
+    exception: exception,
+    stack: stack,
+    library: 'widgets library',
+    context: context,
+    informationCollector: informationCollector,
+  );
+  FlutterError.reportError(details);
+  return details;
+}
+
+/// A value for [Element.slot] used for children of
+/// [MultiChildRenderObjectElement]s.
+///
+/// A slot for a [MultiChildRenderObjectElement] consists of an [index]
+/// identifying where the child occupying this slot is located in the
+/// [MultiChildRenderObjectElement]'s child list and an arbitrary [value] that
+/// can further define where the child occupying this slot fits in its
+/// parent's child list.
+///
+/// See also:
+///
+///  * [RenderObjectElement.updateChildren], which discusses why this class is
+///    used as slot values for the children of a [MultiChildRenderObjectElement].
+@immutable
+class IndexedSlot<T> {
+  /// Creates an [IndexedSlot] with the provided [index] and slot [value].
+  const IndexedSlot(this.index, this.value);
+
+  /// Information to define where the child occupying this slot fits in its
+  /// parent's child list.
+  final T value;
+
+  /// The index of this slot in the parent's child list.
+  final int index;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is IndexedSlot
+        && index == other.index
+        && value == other.value;
+  }
+
+  @override
+  int get hashCode => hashValues(index, value);
+}
+
+class _NullElement extends Element {
+  _NullElement() : super(_NullWidget());
+
+  static _NullElement instance = _NullElement();
+
+  @override
+  bool get debugDoingBuild => throw UnimplementedError();
+
+  @override
+  void performRebuild() { }
+}
+
+class _NullWidget extends Widget {
+  @override
+  Element createElement() => throw UnimplementedError();
+}
diff --git a/lib/src/widgets/gesture_detector.dart b/lib/src/widgets/gesture_detector.dart
new file mode 100644
index 0000000..4da60e6
--- /dev/null
+++ b/lib/src/widgets/gesture_detector.dart
@@ -0,0 +1,1439 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+
+export 'package:flute/gestures.dart' show
+  DragDownDetails,
+  DragStartDetails,
+  DragUpdateDetails,
+  DragEndDetails,
+  GestureTapDownCallback,
+  GestureTapUpCallback,
+  GestureTapCallback,
+  GestureTapCancelCallback,
+  GestureLongPressCallback,
+  GestureLongPressStartCallback,
+  GestureLongPressMoveUpdateCallback,
+  GestureLongPressUpCallback,
+  GestureLongPressEndCallback,
+  GestureDragDownCallback,
+  GestureDragStartCallback,
+  GestureDragUpdateCallback,
+  GestureDragEndCallback,
+  GestureDragCancelCallback,
+  GestureScaleStartCallback,
+  GestureScaleUpdateCallback,
+  GestureScaleEndCallback,
+  GestureForcePressStartCallback,
+  GestureForcePressPeakCallback,
+  GestureForcePressEndCallback,
+  GestureForcePressUpdateCallback,
+  LongPressStartDetails,
+  LongPressMoveUpdateDetails,
+  LongPressEndDetails,
+  ScaleStartDetails,
+  ScaleUpdateDetails,
+  ScaleEndDetails,
+  TapDownDetails,
+  TapUpDetails,
+  ForcePressDetails,
+  Velocity;
+export 'package:flute/rendering.dart' show RenderSemanticsGestureHandler;
+
+// Examples can assume:
+// // @dart = 2.9
+// bool _lights;
+// void setState(VoidCallback fn) { }
+// String _last;
+// Color _color;
+
+/// Factory for creating gesture recognizers.
+///
+/// `T` is the type of gesture recognizer this class manages.
+///
+/// Used by [RawGestureDetector.gestures].
+@optionalTypeArgs
+abstract class GestureRecognizerFactory<T extends GestureRecognizer> {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const GestureRecognizerFactory();
+
+  /// Must return an instance of T.
+  T constructor();
+
+  /// Must configure the given instance (which will have been created by
+  /// `constructor`).
+  ///
+  /// This normally means setting the callbacks.
+  void initializer(T instance);
+
+  bool _debugAssertTypeMatches(Type type) {
+    assert(type == T, 'GestureRecognizerFactory of type $T was used where type $type was specified.');
+    return true;
+  }
+}
+
+/// Signature for closures that implement [GestureRecognizerFactory.constructor].
+typedef GestureRecognizerFactoryConstructor<T extends GestureRecognizer> = T Function();
+
+/// Signature for closures that implement [GestureRecognizerFactory.initializer].
+typedef GestureRecognizerFactoryInitializer<T extends GestureRecognizer> = void Function(T instance);
+
+/// Factory for creating gesture recognizers that delegates to callbacks.
+///
+/// Used by [RawGestureDetector.gestures].
+class GestureRecognizerFactoryWithHandlers<T extends GestureRecognizer> extends GestureRecognizerFactory<T> {
+  /// Creates a gesture recognizer factory with the given callbacks.
+  ///
+  /// The arguments must not be null.
+  const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer)
+    : assert(_constructor != null),
+      assert(_initializer != null);
+
+  final GestureRecognizerFactoryConstructor<T> _constructor;
+
+  final GestureRecognizerFactoryInitializer<T> _initializer;
+
+  @override
+  T constructor() => _constructor();
+
+  @override
+  void initializer(T instance) => _initializer(instance);
+}
+
+/// A widget that detects gestures.
+///
+/// Attempts to recognize gestures that correspond to its non-null callbacks.
+///
+/// If this widget has a child, it defers to that child for its sizing behavior.
+/// If it does not have a child, it grows to fit the parent instead.
+///
+/// By default a GestureDetector with an invisible child ignores touches;
+/// this behavior can be controlled with [behavior].
+///
+/// GestureDetector also listens for accessibility events and maps
+/// them to the callbacks. To ignore accessibility events, set
+/// [excludeFromSemantics] to true.
+///
+/// See <http://flutter.dev/gestures/> for additional information.
+///
+/// Material design applications typically react to touches with ink splash
+/// effects. The [InkWell] class implements this effect and can be used in place
+/// of a [GestureDetector] for handling taps.
+///
+/// {@animation 200 150 https://flutter.github.io/assets-for-api-docs/assets/widgets/gesture_detector.mp4}
+///
+/// {@tool snippet}
+///
+/// This example of a [Container] contains a black light bulb wrapped in a [GestureDetector].
+/// It turns the light bulb yellow when the "turn lights on" button is tapped
+/// by setting the `_lights` field. Above animation shows the code in use:
+///
+/// ```dart
+/// Container(
+///   alignment: FractionalOffset.center,
+///   color: Colors.white,
+///   child: Column(
+///     mainAxisAlignment: MainAxisAlignment.center,
+///     children: <Widget>[
+///       Padding(
+///         padding: const EdgeInsets.all(8.0),
+///         child: Icon(
+///           Icons.lightbulb_outline,
+///           color: _lights ? Colors.yellow.shade600 : Colors.black,
+///           size: 60,
+///         ),
+///       ),
+///       GestureDetector(
+///         onTap: () {
+///           setState(() {
+///             _lights = true;
+///           });
+///         },
+///         child: Container(
+///           color: Colors.yellow.shade600,
+///           padding: const EdgeInsets.all(8),
+///           child: const Text('TURN LIGHTS ON'),
+///         ),
+///       ),
+///     ],
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+///
+/// This example of a [Container] wraps a [GestureDetector] widget.
+/// Since the [GestureDetector] does not have a child it takes on the size of
+/// its parent making the entire area of the surrounding [Container] clickable.
+/// When tapped the [Container] turns yellow by setting the `_color` field:
+///
+/// ```dart
+/// Container(
+///   color: _color,
+///   height: 200.0,
+///   width: 200.0,
+///   child: GestureDetector(
+///     onTap: () {
+///       setState(() {
+///         _color = Colors.yellow;
+///       });
+///     },
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ## Debugging
+///
+/// To see how large the hit test box of a [GestureDetector] is for debugging
+/// purposes, set [debugPaintPointersEnabled] to true.
+///
+/// See also:
+///
+///  * [Listener], a widget for listening to lower-level raw pointer events.
+///  * [MouseRegion], a widget that tracks the movement of mice, even when no
+///    button is pressed.
+class GestureDetector extends StatelessWidget {
+  /// Creates a widget that detects gestures.
+  ///
+  /// Pan and scale callbacks cannot be used simultaneously because scale is a
+  /// superset of pan. Simply use the scale callbacks instead.
+  ///
+  /// Horizontal and vertical drag callbacks cannot be used simultaneously
+  /// because a combination of a horizontal and vertical drag is a pan. Simply
+  /// use the pan callbacks instead.
+  ///
+  /// By default, gesture detectors contribute semantic information to the tree
+  /// that is used by assistive technology.
+  GestureDetector({
+    Key? key,
+    this.child,
+    this.onTapDown,
+    this.onTapUp,
+    this.onTap,
+    this.onTapCancel,
+    this.onSecondaryTap,
+    this.onSecondaryTapDown,
+    this.onSecondaryTapUp,
+    this.onSecondaryTapCancel,
+    this.onTertiaryTapDown,
+    this.onTertiaryTapUp,
+    this.onTertiaryTapCancel,
+    this.onDoubleTapDown,
+    this.onDoubleTap,
+    this.onDoubleTapCancel,
+    this.onLongPress,
+    this.onLongPressStart,
+    this.onLongPressMoveUpdate,
+    this.onLongPressUp,
+    this.onLongPressEnd,
+    this.onSecondaryLongPress,
+    this.onSecondaryLongPressStart,
+    this.onSecondaryLongPressMoveUpdate,
+    this.onSecondaryLongPressUp,
+    this.onSecondaryLongPressEnd,
+    this.onVerticalDragDown,
+    this.onVerticalDragStart,
+    this.onVerticalDragUpdate,
+    this.onVerticalDragEnd,
+    this.onVerticalDragCancel,
+    this.onHorizontalDragDown,
+    this.onHorizontalDragStart,
+    this.onHorizontalDragUpdate,
+    this.onHorizontalDragEnd,
+    this.onHorizontalDragCancel,
+    this.onForcePressStart,
+    this.onForcePressPeak,
+    this.onForcePressUpdate,
+    this.onForcePressEnd,
+    this.onPanDown,
+    this.onPanStart,
+    this.onPanUpdate,
+    this.onPanEnd,
+    this.onPanCancel,
+    this.onScaleStart,
+    this.onScaleUpdate,
+    this.onScaleEnd,
+    this.behavior,
+    this.excludeFromSemantics = false,
+    this.dragStartBehavior = DragStartBehavior.start,
+  }) : assert(excludeFromSemantics != null),
+       assert(dragStartBehavior != null),
+       assert(() {
+         final bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null;
+         final bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null;
+         final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null;
+         final bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;
+         if (havePan || haveScale) {
+           if (havePan && haveScale) {
+             throw FlutterError.fromParts(<DiagnosticsNode>[
+               ErrorSummary('Incorrect GestureDetector arguments.'),
+               ErrorDescription(
+                 'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.'
+               ),
+               ErrorHint('Just use the scale gesture recognizer.')
+             ]);
+           }
+           final String recognizer = havePan ? 'pan' : 'scale';
+           if (haveVerticalDrag && haveHorizontalDrag) {
+             throw FlutterError(
+               'Incorrect GestureDetector arguments.\n'
+               'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer '
+               'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.'
+             );
+           }
+         }
+         return true;
+       }()),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// A pointer that might cause a tap with a primary button has contacted the
+  /// screen at a particular location.
+  ///
+  /// This is called after a short timeout, even if the winning gesture has not
+  /// yet been selected. If the tap gesture wins, [onTapUp] will be called,
+  /// otherwise [onTapCancel] will be called.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureTapDownCallback? onTapDown;
+
+  /// A pointer that will trigger a tap with a primary button has stopped
+  /// contacting the screen at a particular location.
+  ///
+  /// This triggers immediately before [onTap] in the case of the tap gesture
+  /// winning. If the tap gesture did not win, [onTapCancel] is called instead.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureTapUpCallback? onTapUp;
+
+  /// A tap with a primary button has occurred.
+  ///
+  /// This triggers when the tap gesture wins. If the tap gesture did not win,
+  /// [onTapCancel] is called instead.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [onTapUp], which is called at the same time but includes details
+  ///    regarding the pointer position.
+  final GestureTapCallback? onTap;
+
+  /// The pointer that previously triggered [onTapDown] will not end up causing
+  /// a tap.
+  ///
+  /// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if
+  /// the tap gesture did not win.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureTapCancelCallback? onTapCancel;
+
+  /// A tap with a secondary button has occurred.
+  ///
+  /// This triggers when the tap gesture wins. If the tap gesture did not win,
+  /// [onSecondaryTapCancel] is called instead.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [onSecondaryTapUp], which is called at the same time but includes details
+  ///    regarding the pointer position.
+  final GestureTapCallback? onSecondaryTap;
+
+  /// A pointer that might cause a tap with a secondary button has contacted the
+  /// screen at a particular location.
+  ///
+  /// This is called after a short timeout, even if the winning gesture has not
+  /// yet been selected. If the tap gesture wins, [onSecondaryTapUp] will be
+  /// called, otherwise [onSecondaryTapCancel] will be called.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  final GestureTapDownCallback? onSecondaryTapDown;
+
+  /// A pointer that will trigger a tap with a secondary button has stopped
+  /// contacting the screen at a particular location.
+  ///
+  /// This triggers in the case of the tap gesture winning. If the tap gesture
+  /// did not win, [onSecondaryTapCancel] is called instead.
+  ///
+  /// See also:
+  ///
+  ///  * [onSecondaryTap], a handler triggered right after this one that doesn't
+  ///    pass any details about the tap.
+  ///  * [kSecondaryButton], the button this callback responds to.
+  final GestureTapUpCallback? onSecondaryTapUp;
+
+  /// The pointer that previously triggered [onSecondaryTapDown] will not end up
+  /// causing a tap.
+  ///
+  /// This is called after [onSecondaryTapDown], and instead of
+  /// [onSecondaryTapUp], if the tap gesture did not win.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  final GestureTapCancelCallback? onSecondaryTapCancel;
+
+  /// A pointer that might cause a tap with a tertiary button has contacted the
+  /// screen at a particular location.
+  ///
+  /// This is called after a short timeout, even if the winning gesture has not
+  /// yet been selected. If the tap gesture wins, [onTertiaryTapUp] will be
+  /// called, otherwise [onTertiaryTapCancel] will be called.
+  ///
+  /// See also:
+  ///
+  ///  * [kTertiaryButton], the button this callback responds to.
+  final GestureTapDownCallback? onTertiaryTapDown;
+
+  /// A pointer that will trigger a tap with a tertiary button has stopped
+  /// contacting the screen at a particular location.
+  ///
+  /// This triggers in the case of the tap gesture winning. If the tap gesture
+  /// did not win, [onTertiaryTapCancel] is called instead.
+  ///
+  /// See also:
+  ///
+  ///  * [kTertiaryButton], the button this callback responds to.
+  final GestureTapUpCallback? onTertiaryTapUp;
+
+  /// The pointer that previously triggered [onTertiaryTapDown] will not end up
+  /// causing a tap.
+  ///
+  /// This is called after [onTertiaryTapDown], and instead of
+  /// [onTertiaryTapUp], if the tap gesture did not win.
+  ///
+  /// See also:
+  ///
+  ///  * [kTertiaryButton], the button this callback responds to.
+  final GestureTapCancelCallback? onTertiaryTapCancel;
+
+  /// A pointer that might cause a double tap has contacted the screen at a
+  /// particular location.
+  ///
+  /// Triggered immediately after the down event of the second tap.
+  ///
+  /// If the user completes the double tap and the gesture wins, [onDoubleTap]
+  /// will be called after this callback. Otherwise, [onDoubleTapCancel] will
+  /// be called after this callback.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureTapDownCallback? onDoubleTapDown;
+
+  /// The user has tapped the screen with a primary button at the same location
+  /// twice in quick succession.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureTapCallback? onDoubleTap;
+
+  /// The pointer that previously triggered [onDoubleTapDown] will not end up
+  /// causing a double tap.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureTapCancelCallback? onDoubleTapCancel;
+
+  /// Called when a long press gesture with a primary button has been recognized.
+  ///
+  /// Triggered when a pointer has remained in contact with the screen at the
+  /// same location for a long period of time.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [onLongPressStart], which has the same timing but has gesture details.
+  final GestureLongPressCallback? onLongPress;
+
+  /// Called when a long press gesture with a primary button has been recognized.
+  ///
+  /// Triggered when a pointer has remained in contact with the screen at the
+  /// same location for a long period of time.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [onLongPress], which has the same timing but without the gesture details.
+  final GestureLongPressStartCallback? onLongPressStart;
+
+  /// A pointer has been drag-moved after a long press with a primary button.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate;
+
+  /// A pointer that has triggered a long-press with a primary button has
+  /// stopped contacting the screen.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [onLongPressEnd], which has the same timing but has gesture details.
+  final GestureLongPressUpCallback? onLongPressUp;
+
+  /// A pointer that has triggered a long-press with a primary button has
+  /// stopped contacting the screen.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [onLongPressUp], which has the same timing but without the gesture
+  ///    details.
+  final GestureLongPressEndCallback? onLongPressEnd;
+
+  /// Called when a long press gesture with a secondary button has been
+  /// recognized.
+  ///
+  /// Triggered when a pointer has remained in contact with the screen at the
+  /// same location for a long period of time.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [onSecondaryLongPressStart], which has the same timing but has gesture
+  ///    details.
+  final GestureLongPressCallback? onSecondaryLongPress;
+
+  /// Called when a long press gesture with a secondary button has been
+  /// recognized.
+  ///
+  /// Triggered when a pointer has remained in contact with the screen at the
+  /// same location for a long period of time.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [onSecondaryLongPress], which has the same timing but without the
+  ///    gesture details.
+  final GestureLongPressStartCallback? onSecondaryLongPressStart;
+
+  /// A pointer has been drag-moved after a long press with a secondary button.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  final GestureLongPressMoveUpdateCallback? onSecondaryLongPressMoveUpdate;
+
+  /// A pointer that has triggered a long-press with a secondary button has
+  /// stopped contacting the screen.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [onSecondaryLongPressEnd], which has the same timing but has gesture
+  ///    details.
+  final GestureLongPressUpCallback? onSecondaryLongPressUp;
+
+  /// A pointer that has triggered a long-press with a secondary button has
+  /// stopped contacting the screen.
+  ///
+  /// See also:
+  ///
+  ///  * [kSecondaryButton], the button this callback responds to.
+  ///  * [onSecondaryLongPressUp], which has the same timing but without the
+  ///    gesture details.
+  final GestureLongPressEndCallback? onSecondaryLongPressEnd;
+
+  /// A pointer has contacted the screen with a primary button and might begin
+  /// to move vertically.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragDownCallback? onVerticalDragDown;
+
+  /// A pointer has contacted the screen with a primary button and has begun to
+  /// move vertically.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragStartCallback? onVerticalDragStart;
+
+  /// A pointer that is in contact with the screen with a primary button and
+  /// moving vertically has moved in the vertical direction.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragUpdateCallback? onVerticalDragUpdate;
+
+  /// A pointer that was previously in contact with the screen with a primary
+  /// button and moving vertically is no longer in contact with the screen and
+  /// was moving at a specific velocity when it stopped contacting the screen.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragEndCallback? onVerticalDragEnd;
+
+  /// The pointer that previously triggered [onVerticalDragDown] did not
+  /// complete.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragCancelCallback? onVerticalDragCancel;
+
+  /// A pointer has contacted the screen with a primary button and might begin
+  /// to move horizontally.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragDownCallback? onHorizontalDragDown;
+
+  /// A pointer has contacted the screen with a primary button and has begun to
+  /// move horizontally.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragStartCallback? onHorizontalDragStart;
+
+  /// A pointer that is in contact with the screen with a primary button and
+  /// moving horizontally has moved in the horizontal direction.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragUpdateCallback? onHorizontalDragUpdate;
+
+  /// A pointer that was previously in contact with the screen with a primary
+  /// button and moving horizontally is no longer in contact with the screen and
+  /// was moving at a specific velocity when it stopped contacting the screen.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragEndCallback? onHorizontalDragEnd;
+
+  /// The pointer that previously triggered [onHorizontalDragDown] did not
+  /// complete.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragCancelCallback? onHorizontalDragCancel;
+
+  /// A pointer has contacted the screen with a primary button and might begin
+  /// to move.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragDownCallback? onPanDown;
+
+  /// A pointer has contacted the screen with a primary button and has begun to
+  /// move.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragStartCallback? onPanStart;
+
+  /// A pointer that is in contact with the screen with a primary button and
+  /// moving has moved again.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragUpdateCallback? onPanUpdate;
+
+  /// A pointer that was previously in contact with the screen with a primary
+  /// button and moving is no longer in contact with the screen and was moving
+  /// at a specific velocity when it stopped contacting the screen.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragEndCallback? onPanEnd;
+
+  /// The pointer that previously triggered [onPanDown] did not complete.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  final GestureDragCancelCallback? onPanCancel;
+
+  /// The pointers in contact with the screen have established a focal point and
+  /// initial scale of 1.0.
+  final GestureScaleStartCallback? onScaleStart;
+
+  /// The pointers in contact with the screen have indicated a new focal point
+  /// and/or scale.
+  final GestureScaleUpdateCallback? onScaleUpdate;
+
+  /// The pointers are no longer in contact with the screen.
+  final GestureScaleEndCallback? onScaleEnd;
+
+  /// The pointer is in contact with the screen and has pressed with sufficient
+  /// force to initiate a force press. The amount of force is at least
+  /// [ForcePressGestureRecognizer.startPressure].
+  ///
+  /// Note that this callback will only be fired on devices with pressure
+  /// detecting screens.
+  final GestureForcePressStartCallback? onForcePressStart;
+
+  /// The pointer is in contact with the screen and has pressed with the maximum
+  /// force. The amount of force is at least
+  /// [ForcePressGestureRecognizer.peakPressure].
+  ///
+  /// Note that this callback will only be fired on devices with pressure
+  /// detecting screens.
+  final GestureForcePressPeakCallback? onForcePressPeak;
+
+  /// A pointer is in contact with the screen, has previously passed the
+  /// [ForcePressGestureRecognizer.startPressure] and is either moving on the
+  /// plane of the screen, pressing the screen with varying forces or both
+  /// simultaneously.
+  ///
+  /// Note that this callback will only be fired on devices with pressure
+  /// detecting screens.
+  final GestureForcePressUpdateCallback? onForcePressUpdate;
+
+  /// The pointer is no longer in contact with the screen.
+  ///
+  /// Note that this callback will only be fired on devices with pressure
+  /// detecting screens.
+  final GestureForcePressEndCallback? onForcePressEnd;
+
+  /// How this gesture detector should behave during hit testing.
+  ///
+  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
+  /// [HitTestBehavior.translucent] if child is null.
+  final HitTestBehavior? behavior;
+
+  /// Whether to exclude these gestures from the semantics tree. For
+  /// example, the long-press gesture for showing a tooltip is
+  /// excluded because the tooltip itself is included in the semantics
+  /// tree directly and so having a gesture to show it would result in
+  /// duplication of information.
+  final bool excludeFromSemantics;
+
+  /// Determines the way that drag start behavior is handled.
+  ///
+  /// If set to [DragStartBehavior.start], gesture drag behavior will
+  /// begin upon the detection of a drag gesture. If set to
+  /// [DragStartBehavior.down] it will begin when a down event is first detected.
+  ///
+  /// In general, setting this to [DragStartBehavior.start] will make drag
+  /// animation smoother and setting it to [DragStartBehavior.down] will make
+  /// drag behavior feel slightly more reactive.
+  ///
+  /// By default, the drag start behavior is [DragStartBehavior.start].
+  ///
+  /// Only the [DragGestureRecognizer.onStart] callbacks for the
+  /// [VerticalDragGestureRecognizer], [HorizontalDragGestureRecognizer] and
+  /// [PanGestureRecognizer] are affected by this setting.
+  ///
+  /// See also:
+  ///
+  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
+  final DragStartBehavior dragStartBehavior;
+
+  @override
+  Widget build(BuildContext context) {
+    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
+
+    if (onTapDown != null ||
+        onTapUp != null ||
+        onTap != null ||
+        onTapCancel != null ||
+        onSecondaryTap != null ||
+        onSecondaryTapDown != null ||
+        onSecondaryTapUp != null ||
+        onSecondaryTapCancel != null||
+        onTertiaryTapDown != null ||
+        onTertiaryTapUp != null ||
+        onTertiaryTapCancel != null
+    ) {
+      gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
+        () => TapGestureRecognizer(debugOwner: this),
+        (TapGestureRecognizer instance) {
+          instance
+            ..onTapDown = onTapDown
+            ..onTapUp = onTapUp
+            ..onTap = onTap
+            ..onTapCancel = onTapCancel
+            ..onSecondaryTap = onSecondaryTap
+            ..onSecondaryTapDown = onSecondaryTapDown
+            ..onSecondaryTapUp = onSecondaryTapUp
+            ..onSecondaryTapCancel = onSecondaryTapCancel
+            ..onTertiaryTapDown = onTertiaryTapDown
+            ..onTertiaryTapUp = onTertiaryTapUp
+            ..onTertiaryTapCancel = onTertiaryTapCancel;
+        },
+      );
+    }
+
+    if (onDoubleTap != null) {
+      gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
+        () => DoubleTapGestureRecognizer(debugOwner: this),
+        (DoubleTapGestureRecognizer instance) {
+          instance
+            ..onDoubleTapDown = onDoubleTapDown
+            ..onDoubleTap = onDoubleTap
+            ..onDoubleTapCancel = onDoubleTapCancel;
+        },
+      );
+    }
+
+    if (onLongPress != null ||
+        onLongPressUp != null ||
+        onLongPressStart != null ||
+        onLongPressMoveUpdate != null ||
+        onLongPressEnd != null ||
+        onSecondaryLongPress != null ||
+        onSecondaryLongPressUp != null ||
+        onSecondaryLongPressStart != null ||
+        onSecondaryLongPressMoveUpdate != null ||
+        onSecondaryLongPressEnd != null) {
+      gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
+        () => LongPressGestureRecognizer(debugOwner: this),
+        (LongPressGestureRecognizer instance) {
+          instance
+            ..onLongPress = onLongPress
+            ..onLongPressStart = onLongPressStart
+            ..onLongPressMoveUpdate = onLongPressMoveUpdate
+            ..onLongPressEnd = onLongPressEnd
+            ..onLongPressUp = onLongPressUp
+            ..onSecondaryLongPress = onSecondaryLongPress
+            ..onSecondaryLongPressStart = onSecondaryLongPressStart
+            ..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate
+            ..onSecondaryLongPressEnd = onSecondaryLongPressEnd
+            ..onSecondaryLongPressUp = onSecondaryLongPressUp;
+        },
+      );
+    }
+
+    if (onVerticalDragDown != null ||
+        onVerticalDragStart != null ||
+        onVerticalDragUpdate != null ||
+        onVerticalDragEnd != null ||
+        onVerticalDragCancel != null) {
+      gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
+        () => VerticalDragGestureRecognizer(debugOwner: this),
+        (VerticalDragGestureRecognizer instance) {
+          instance
+            ..onDown = onVerticalDragDown
+            ..onStart = onVerticalDragStart
+            ..onUpdate = onVerticalDragUpdate
+            ..onEnd = onVerticalDragEnd
+            ..onCancel = onVerticalDragCancel
+            ..dragStartBehavior = dragStartBehavior;
+        },
+      );
+    }
+
+    if (onHorizontalDragDown != null ||
+        onHorizontalDragStart != null ||
+        onHorizontalDragUpdate != null ||
+        onHorizontalDragEnd != null ||
+        onHorizontalDragCancel != null) {
+      gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
+        () => HorizontalDragGestureRecognizer(debugOwner: this),
+        (HorizontalDragGestureRecognizer instance) {
+          instance
+            ..onDown = onHorizontalDragDown
+            ..onStart = onHorizontalDragStart
+            ..onUpdate = onHorizontalDragUpdate
+            ..onEnd = onHorizontalDragEnd
+            ..onCancel = onHorizontalDragCancel
+            ..dragStartBehavior = dragStartBehavior;
+        },
+      );
+    }
+
+    if (onPanDown != null ||
+        onPanStart != null ||
+        onPanUpdate != null ||
+        onPanEnd != null ||
+        onPanCancel != null) {
+      gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
+        () => PanGestureRecognizer(debugOwner: this),
+        (PanGestureRecognizer instance) {
+          instance
+            ..onDown = onPanDown
+            ..onStart = onPanStart
+            ..onUpdate = onPanUpdate
+            ..onEnd = onPanEnd
+            ..onCancel = onPanCancel
+            ..dragStartBehavior = dragStartBehavior;
+        },
+      );
+    }
+
+    if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
+      gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
+        () => ScaleGestureRecognizer(debugOwner: this),
+        (ScaleGestureRecognizer instance) {
+          instance
+            ..onStart = onScaleStart
+            ..onUpdate = onScaleUpdate
+            ..onEnd = onScaleEnd;
+        },
+      );
+    }
+
+    if (onForcePressStart != null ||
+        onForcePressPeak != null ||
+        onForcePressUpdate != null ||
+        onForcePressEnd != null) {
+      gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
+        () => ForcePressGestureRecognizer(debugOwner: this),
+        (ForcePressGestureRecognizer instance) {
+          instance
+            ..onStart = onForcePressStart
+            ..onPeak = onForcePressPeak
+            ..onUpdate = onForcePressUpdate
+            ..onEnd = onForcePressEnd;
+        },
+      );
+    }
+
+    return RawGestureDetector(
+      gestures: gestures,
+      behavior: behavior,
+      excludeFromSemantics: excludeFromSemantics,
+      child: child,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<DragStartBehavior>('startBehavior', dragStartBehavior));
+  }
+}
+
+/// A widget that detects gestures described by the given gesture
+/// factories.
+///
+/// For common gestures, use a [GestureRecognizer].
+/// [RawGestureDetector] is useful primarily when developing your
+/// own gesture recognizers.
+///
+/// Configuring the gesture recognizers requires a carefully constructed map, as
+/// described in [gestures] and as shown in the example below.
+///
+/// {@tool snippet}
+///
+/// This example shows how to hook up a [TapGestureRecognizer]. It assumes that
+/// the code is being used inside a [State] object with a `_last` field that is
+/// then displayed as the child of the gesture detector.
+///
+/// ```dart
+/// RawGestureDetector(
+///   gestures: <Type, GestureRecognizerFactory>{
+///     TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
+///       () => TapGestureRecognizer(),
+///       (TapGestureRecognizer instance) {
+///         instance
+///           ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }
+///           ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }
+///           ..onTap = () { setState(() { _last = 'tap'; }); }
+///           ..onTapCancel = () { setState(() { _last = 'cancel'; }); };
+///       },
+///     ),
+///   },
+///   child: Container(width: 300.0, height: 300.0, color: Colors.yellow, child: Text(_last)),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [GestureDetector], a less flexible but much simpler widget that does the same thing.
+///  * [Listener], a widget that reports raw pointer events.
+///  * [GestureRecognizer], the class that you extend to create a custom gesture recognizer.
+class RawGestureDetector extends StatefulWidget {
+  /// Creates a widget that detects gestures.
+  ///
+  /// Gesture detectors can contribute semantic information to the tree that is
+  /// used by assistive technology. The behavior can be configured by
+  /// [semantics], or disabled with [excludeFromSemantics].
+  const RawGestureDetector({
+    Key? key,
+    this.child,
+    this.gestures = const <Type, GestureRecognizerFactory>{},
+    this.behavior,
+    this.excludeFromSemantics = false,
+    this.semantics,
+  }) : assert(gestures != null),
+       assert(excludeFromSemantics != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// The gestures that this widget will attempt to recognize.
+  ///
+  /// This should be a map from [GestureRecognizer] subclasses to
+  /// [GestureRecognizerFactory] subclasses specialized with the same type.
+  ///
+  /// This value can be late-bound at layout time using
+  /// [RawGestureDetectorState.replaceGestureRecognizers].
+  final Map<Type, GestureRecognizerFactory> gestures;
+
+  /// How this gesture detector should behave during hit testing.
+  ///
+  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
+  /// [HitTestBehavior.translucent] if child is null.
+  final HitTestBehavior? behavior;
+
+  /// Whether to exclude these gestures from the semantics tree. For
+  /// example, the long-press gesture for showing a tooltip is
+  /// excluded because the tooltip itself is included in the semantics
+  /// tree directly and so having a gesture to show it would result in
+  /// duplication of information.
+  final bool excludeFromSemantics;
+
+  /// Describes the semantics notations that should be added to the underlying
+  /// render object [RenderSemanticsGestureHandler].
+  ///
+  /// It has no effect if [excludeFromSemantics] is true.
+  ///
+  /// When [semantics] is null, [RawGestureDetector] will fall back to a
+  /// default delegate which checks if the detector owns certain gesture
+  /// recognizers and calls their callbacks if they exist:
+  ///
+  ///  * During a semantic tap, it calls [TapGestureRecognizer]'s
+  ///    `onTapDown`, `onTapUp`, and `onTap`.
+  ///  * During a semantic long press, it calls [LongPressGestureRecognizer]'s
+  ///    `onLongPressStart`, `onLongPress`, `onLongPressEnd` and `onLongPressUp`.
+  ///  * During a semantic horizontal drag, it calls [HorizontalDragGestureRecognizer]'s
+  ///    `onDown`, `onStart`, `onUpdate` and `onEnd`, then
+  ///    [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`.
+  ///  * During a semantic vertical drag, it calls [VerticalDragGestureRecognizer]'s
+  ///    `onDown`, `onStart`, `onUpdate` and `onEnd`, then
+  ///    [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`.
+  ///
+  /// {@tool snippet}
+  /// This custom gesture detector listens to force presses, while also allows
+  /// the same callback to be triggered by semantic long presses.
+  ///
+  /// ```dart
+  /// class ForcePressGestureDetectorWithSemantics extends StatelessWidget {
+  ///   const ForcePressGestureDetectorWithSemantics({
+  ///     this.child,
+  ///     this.onForcePress,
+  ///   });
+  ///
+  ///   final Widget child;
+  ///   final VoidCallback onForcePress;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return RawGestureDetector(
+  ///       gestures: <Type, GestureRecognizerFactory>{
+  ///         ForcePressGestureRecognizer: GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
+  ///           () => ForcePressGestureRecognizer(debugOwner: this),
+  ///           (ForcePressGestureRecognizer instance) {
+  ///             instance.onStart = (_) => onForcePress();
+  ///           }
+  ///         ),
+  ///       },
+  ///       behavior: HitTestBehavior.opaque,
+  ///       semantics: _LongPressSemanticsDelegate(onForcePress),
+  ///       child: child,
+  ///     );
+  ///   }
+  /// }
+  ///
+  /// class _LongPressSemanticsDelegate extends SemanticsGestureDelegate {
+  ///   _LongPressSemanticsDelegate(this.onLongPress);
+  ///
+  ///   VoidCallback onLongPress;
+  ///
+  ///   @override
+  ///   void assignSemantics(RenderSemanticsGestureHandler renderObject) {
+  ///     renderObject.onLongPress = onLongPress;
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  final SemanticsGestureDelegate? semantics;
+
+  @override
+  RawGestureDetectorState createState() => RawGestureDetectorState();
+}
+
+/// State for a [RawGestureDetector].
+class RawGestureDetectorState extends State<RawGestureDetector> {
+  Map<Type, GestureRecognizer>? _recognizers = const <Type, GestureRecognizer>{};
+  SemanticsGestureDelegate? _semantics;
+
+  @override
+  void initState() {
+    super.initState();
+    _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
+    _syncAll(widget.gestures);
+  }
+
+  @override
+  void didUpdateWidget(RawGestureDetector oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (!(oldWidget.semantics == null && widget.semantics == null)) {
+      _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
+    }
+    _syncAll(widget.gestures);
+  }
+
+  /// This method can be called after the build phase, during the
+  /// layout of the nearest descendant [RenderObjectWidget] of the
+  /// gesture detector, to update the list of active gesture
+  /// recognizers.
+  ///
+  /// The typical use case is [Scrollable]s, which put their viewport
+  /// in their gesture detector, and then need to know the dimensions
+  /// of the viewport and the viewport's child to determine whether
+  /// the gesture detector should be enabled.
+  ///
+  /// The argument should follow the same conventions as
+  /// [RawGestureDetector.gestures]. It acts like a temporary replacement for
+  /// that value until the next build.
+  void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
+    assert(() {
+      if (!context.findRenderObject()!.owner!.debugDoingLayout) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.'),
+          ErrorDescription('The replaceGestureRecognizers() method can only be called during the layout phase.'),
+          ErrorHint(
+            'To set the gesture recognizers at other times, trigger a new build using setState() '
+            'and provide the new gesture recognizers as constructor arguments to the corresponding '
+            'RawGestureDetector or GestureDetector object.'
+          ),
+        ]);
+      }
+      return true;
+    }());
+    _syncAll(gestures);
+    if (!widget.excludeFromSemantics) {
+      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject()! as RenderSemanticsGestureHandler;
+      _updateSemanticsForRenderObject(semanticsGestureHandler);
+    }
+  }
+
+  /// This method can be called to filter the list of available semantic actions,
+  /// after the render object was created.
+  ///
+  /// The actual filtering is happening in the next frame and a frame will be
+  /// scheduled if non is pending.
+  ///
+  /// This is used by [Scrollable] to configure system accessibility tools so
+  /// that they know in which direction a particular list can be scrolled.
+  ///
+  /// If this is never called, then the actions are not filtered. If the list of
+  /// actions to filter changes, it must be called again.
+  void replaceSemanticsActions(Set<SemanticsAction> actions) {
+    if (widget.excludeFromSemantics)
+      return;
+
+    final RenderSemanticsGestureHandler? semanticsGestureHandler = context.findRenderObject() as RenderSemanticsGestureHandler?;
+    assert(() {
+      if (semanticsGestureHandler == null) {
+        throw FlutterError(
+          'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n'
+          'The replaceSemanticsActions() method can only be called after the RenderSemanticsGestureHandler has been created.'
+        );
+      }
+      return true;
+    }());
+
+    semanticsGestureHandler!.validActions = actions; // will call _markNeedsSemanticsUpdate(), if required.
+  }
+
+  @override
+  void dispose() {
+    for (final GestureRecognizer recognizer in _recognizers!.values)
+      recognizer.dispose();
+    _recognizers = null;
+    super.dispose();
+  }
+
+  void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
+    assert(_recognizers != null);
+    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
+    _recognizers = <Type, GestureRecognizer>{};
+    for (final Type type in gestures.keys) {
+      assert(gestures[type] != null);
+      assert(gestures[type]!._debugAssertTypeMatches(type));
+      assert(!_recognizers!.containsKey(type));
+      _recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor();
+      assert(_recognizers![type].runtimeType == type, 'GestureRecognizerFactory of type $type created a GestureRecognizer of type ${_recognizers![type].runtimeType}. The GestureRecognizerFactory must be specialized with the type of the class that it returns from its constructor method.');
+      gestures[type]!.initializer(_recognizers![type]!);
+    }
+    for (final Type type in oldRecognizers.keys) {
+      if (!_recognizers!.containsKey(type))
+        oldRecognizers[type]!.dispose();
+    }
+  }
+
+  void _handlePointerDown(PointerDownEvent event) {
+    assert(_recognizers != null);
+    for (final GestureRecognizer recognizer in _recognizers!.values)
+      recognizer.addPointer(event);
+  }
+
+  HitTestBehavior get _defaultBehavior {
+    return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
+  }
+
+  void _updateSemanticsForRenderObject(RenderSemanticsGestureHandler renderObject) {
+    assert(!widget.excludeFromSemantics);
+    assert(_semantics != null);
+    _semantics!.assignSemantics(renderObject);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    Widget result = Listener(
+      onPointerDown: _handlePointerDown,
+      behavior: widget.behavior ?? _defaultBehavior,
+      child: widget.child,
+    );
+    if (!widget.excludeFromSemantics)
+      result = _GestureSemantics(
+        child: result,
+        assignSemantics: _updateSemanticsForRenderObject,
+      );
+    return result;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    if (_recognizers == null) {
+      properties.add(DiagnosticsNode.message('DISPOSED'));
+    } else {
+      final List<String> gestures = _recognizers!.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList();
+      properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
+      properties.add(IterableProperty<GestureRecognizer>('recognizers', _recognizers!.values, level: DiagnosticLevel.fine));
+      properties.add(DiagnosticsProperty<bool>('excludeFromSemantics', widget.excludeFromSemantics, defaultValue: false));
+      if (!widget.excludeFromSemantics) {
+        properties.add(DiagnosticsProperty<SemanticsGestureDelegate>('semantics', widget.semantics, defaultValue: null));
+      }
+    }
+    properties.add(EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
+  }
+}
+
+typedef _AssignSemantics = void Function(RenderSemanticsGestureHandler);
+
+class _GestureSemantics extends SingleChildRenderObjectWidget {
+  const _GestureSemantics({
+    Key? key,
+    Widget? child,
+    required this.assignSemantics,
+  }) : assert(assignSemantics != null),
+       super(key: key, child: child);
+
+  final _AssignSemantics assignSemantics;
+
+  @override
+  RenderSemanticsGestureHandler createRenderObject(BuildContext context) {
+    final RenderSemanticsGestureHandler renderObject = RenderSemanticsGestureHandler();
+    assignSemantics(renderObject);
+    return renderObject;
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
+    assignSemantics(renderObject);
+  }
+}
+
+/// A base class that describes what semantics notations a [RawGestureDetector]
+/// should add to the render object [RenderSemanticsGestureHandler].
+///
+/// It is used to allow custom [GestureDetector]s to add semantics notations.
+abstract class SemanticsGestureDelegate {
+  /// Create a delegate of gesture semantics.
+  const SemanticsGestureDelegate();
+
+  /// Assigns semantics notations to the [RenderSemanticsGestureHandler] render
+  /// object of the gesture detector.
+  ///
+  /// This method is called when the widget is created, updated, or during
+  /// [RawGestureDetectorState.replaceGestureRecognizers].
+  void assignSemantics(RenderSemanticsGestureHandler renderObject);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'SemanticsGestureDelegate')}()';
+}
+
+// The default semantics delegate of [RawGestureDetector]. Its behavior is
+// described in [RawGestureDetector.semantics].
+//
+// For readers who come here to learn how to write custom semantics delegates:
+// this is not a proper sample code. It has access to the detector state as well
+// as its private properties, which are inaccessible normally. It is designed
+// this way in order to work independently in a [RawGestureRecognizer] to
+// preserve existing behavior.
+//
+// Instead, a normal delegate will store callbacks as properties, and use them
+// in `assignSemantics`.
+class _DefaultSemanticsGestureDelegate extends SemanticsGestureDelegate {
+  _DefaultSemanticsGestureDelegate(this.detectorState);
+
+  final RawGestureDetectorState detectorState;
+
+  @override
+  void assignSemantics(RenderSemanticsGestureHandler renderObject) {
+    assert(!detectorState.widget.excludeFromSemantics);
+    final Map<Type, GestureRecognizer> recognizers = detectorState._recognizers!;
+    renderObject
+      ..onTap = _getTapHandler(recognizers)
+      ..onLongPress = _getLongPressHandler(recognizers)
+      ..onHorizontalDragUpdate = _getHorizontalDragUpdateHandler(recognizers)
+      ..onVerticalDragUpdate = _getVerticalDragUpdateHandler(recognizers);
+  }
+
+  GestureTapCallback? _getTapHandler(Map<Type, GestureRecognizer> recognizers) {
+    final TapGestureRecognizer? tap = recognizers[TapGestureRecognizer] as TapGestureRecognizer?;
+    if (tap == null)
+      return null;
+    assert(tap is TapGestureRecognizer);
+
+    return () {
+      assert(tap != null);
+      if (tap.onTapDown != null)
+        tap.onTapDown!(TapDownDetails());
+      if (tap.onTapUp != null)
+        tap.onTapUp!(TapUpDetails(kind: PointerDeviceKind.unknown));
+      if (tap.onTap != null)
+        tap.onTap!();
+    };
+  }
+
+  GestureLongPressCallback? _getLongPressHandler(Map<Type, GestureRecognizer> recognizers) {
+    final LongPressGestureRecognizer? longPress = recognizers[LongPressGestureRecognizer] as LongPressGestureRecognizer?;
+    if (longPress == null)
+      return null;
+
+    return () {
+      assert(longPress is LongPressGestureRecognizer);
+      if (longPress.onLongPressStart != null)
+        longPress.onLongPressStart!(const LongPressStartDetails());
+      if (longPress.onLongPress != null)
+        longPress.onLongPress!();
+      if (longPress.onLongPressEnd != null)
+        longPress.onLongPressEnd!(const LongPressEndDetails());
+      if (longPress.onLongPressUp != null)
+        longPress.onLongPressUp!();
+    };
+  }
+
+  GestureDragUpdateCallback? _getHorizontalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) {
+    final HorizontalDragGestureRecognizer? horizontal = recognizers[HorizontalDragGestureRecognizer] as HorizontalDragGestureRecognizer?;
+    final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?;
+
+    final GestureDragUpdateCallback? horizontalHandler = horizontal == null ?
+      null :
+      (DragUpdateDetails details) {
+        assert(horizontal is HorizontalDragGestureRecognizer);
+        if (horizontal.onDown != null)
+          horizontal.onDown!(DragDownDetails());
+        if (horizontal.onStart != null)
+          horizontal.onStart!(DragStartDetails());
+        if (horizontal.onUpdate != null)
+          horizontal.onUpdate!(details);
+        if (horizontal.onEnd != null)
+          horizontal.onEnd!(DragEndDetails(primaryVelocity: 0.0));
+      };
+
+    final GestureDragUpdateCallback? panHandler = pan == null ?
+      null :
+      (DragUpdateDetails details) {
+        assert(pan is PanGestureRecognizer);
+        if (pan.onDown != null)
+          pan.onDown!(DragDownDetails());
+        if (pan.onStart != null)
+          pan.onStart!(DragStartDetails());
+        if (pan.onUpdate != null)
+          pan.onUpdate!(details);
+        if (pan.onEnd != null)
+          pan.onEnd!(DragEndDetails());
+      };
+
+    if (horizontalHandler == null && panHandler == null)
+      return null;
+    return (DragUpdateDetails details) {
+      if (horizontalHandler != null)
+        horizontalHandler(details);
+      if (panHandler != null)
+        panHandler(details);
+    };
+  }
+
+  GestureDragUpdateCallback? _getVerticalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) {
+    final VerticalDragGestureRecognizer? vertical = recognizers[VerticalDragGestureRecognizer] as VerticalDragGestureRecognizer?;
+    final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?;
+
+    final GestureDragUpdateCallback? verticalHandler = vertical == null ?
+      null :
+      (DragUpdateDetails details) {
+        assert(vertical is VerticalDragGestureRecognizer);
+        if (vertical.onDown != null)
+          vertical.onDown!(DragDownDetails());
+        if (vertical.onStart != null)
+          vertical.onStart!(DragStartDetails());
+        if (vertical.onUpdate != null)
+          vertical.onUpdate!(details);
+        if (vertical.onEnd != null)
+          vertical.onEnd!(DragEndDetails(primaryVelocity: 0.0));
+      };
+
+    final GestureDragUpdateCallback? panHandler = pan == null ?
+      null :
+      (DragUpdateDetails details) {
+        assert(pan is PanGestureRecognizer);
+        if (pan.onDown != null)
+          pan.onDown!(DragDownDetails());
+        if (pan.onStart != null)
+          pan.onStart!(DragStartDetails());
+        if (pan.onUpdate != null)
+          pan.onUpdate!(details);
+        if (pan.onEnd != null)
+          pan.onEnd!(DragEndDetails());
+      };
+
+    if (verticalHandler == null && panHandler == null)
+      return null;
+    return (DragUpdateDetails details) {
+      if (verticalHandler != null)
+        verticalHandler(details);
+      if (panHandler != null)
+        panHandler(details);
+    };
+  }
+}
diff --git a/lib/src/widgets/grid_paper.dart b/lib/src/widgets/grid_paper.dart
new file mode 100644
index 0000000..ef9063d
--- /dev/null
+++ b/lib/src/widgets/grid_paper.dart
@@ -0,0 +1,120 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+
+class _GridPaperPainter extends CustomPainter {
+  const _GridPaperPainter({
+    required this.color,
+    required this.interval,
+    required this.divisions,
+    required this.subdivisions,
+  });
+
+  final Color color;
+  final double interval;
+  final int divisions;
+  final int subdivisions;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final Paint linePaint = Paint()
+      ..color = color;
+    final double allDivisions = (divisions * subdivisions).toDouble();
+    for (double x = 0.0; x <= size.width; x += interval / allDivisions) {
+      linePaint.strokeWidth = (x % interval == 0.0) ? 1.0 : (x % (interval / subdivisions) == 0.0) ? 0.5 : 0.25;
+      canvas.drawLine(Offset(x, 0.0), Offset(x, size.height), linePaint);
+    }
+    for (double y = 0.0; y <= size.height; y += interval / allDivisions) {
+      linePaint.strokeWidth = (y % interval == 0.0) ? 1.0 : (y % (interval / subdivisions) == 0.0) ? 0.5 : 0.25;
+      canvas.drawLine(Offset(0.0, y), Offset(size.width, y), linePaint);
+    }
+  }
+
+  @override
+  bool shouldRepaint(_GridPaperPainter oldPainter) {
+    return oldPainter.color != color
+        || oldPainter.interval != interval
+        || oldPainter.divisions != divisions
+        || oldPainter.subdivisions != subdivisions;
+  }
+
+  @override
+  bool hitTest(Offset position) => false;
+}
+
+/// A widget that draws a rectilinear grid of lines one pixel wide.
+///
+/// Useful with a [Stack] for visualizing your layout along a grid.
+///
+/// The grid's origin (where the first primary horizontal line and the first
+/// primary vertical line intersect) is at the top left of the widget.
+///
+/// The grid is drawn over the [child] widget.
+class GridPaper extends StatelessWidget {
+  /// Creates a widget that draws a rectilinear grid of 1-pixel-wide lines.
+  const GridPaper({
+    Key? key,
+    this.color = const Color(0x7FC3E8F3),
+    this.interval = 100.0,
+    this.divisions = 2,
+    this.subdivisions = 5,
+    this.child,
+  }) : assert(divisions > 0, 'The "divisions" property must be greater than zero. If there were no divisions, the grid paper would not paint anything.'),
+       assert(subdivisions > 0, 'The "subdivisions" property must be greater than zero. If there were no subdivisions, the grid paper would not paint anything.'),
+       super(key: key);
+
+  /// The color to draw the lines in the grid.
+  ///
+  /// Defaults to a light blue commonly seen on traditional grid paper.
+  final Color color;
+
+  /// The distance between the primary lines in the grid, in logical pixels.
+  ///
+  /// Each primary line is one logical pixel wide.
+  final double interval;
+
+  /// The number of major divisions within each primary grid cell.
+  ///
+  /// This is the number of major divisions per [interval], including the
+  /// primary grid's line.
+  ///
+  /// The lines after the first are half a logical pixel wide.
+  ///
+  /// If this is set to 2 (the default), then for each [interval] there will be
+  /// a 1-pixel line on the left, a half-pixel line in the middle, and a 1-pixel
+  /// line on the right (the latter being the 1-pixel line on the left of the
+  /// next [interval]).
+  final int divisions;
+
+  /// The number of minor divisions within each major division, including the
+  /// major division itself.
+  ///
+  /// If [subdivisions] is 5 (the default), it means that there will be four
+  /// lines between each major ([divisions]) line.
+  ///
+  /// The subdivision lines after the first are a quarter of a logical pixel wide.
+  final int subdivisions;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  @override
+  Widget build(BuildContext context) {
+    return CustomPaint(
+      foregroundPainter: _GridPaperPainter(
+        color: color,
+        interval: interval,
+        divisions: divisions,
+        subdivisions: subdivisions,
+      ),
+      child: child,
+    );
+  }
+}
diff --git a/lib/src/widgets/heroes.dart b/lib/src/widgets/heroes.dart
new file mode 100644
index 0000000..3ecf42a
--- /dev/null
+++ b/lib/src/widgets/heroes.dart
@@ -0,0 +1,961 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'basic.dart';
+import 'binding.dart';
+import 'framework.dart';
+import 'navigator.dart';
+import 'overlay.dart';
+import 'pages.dart';
+import 'routes.dart';
+import 'ticker_provider.dart' show TickerMode;
+import 'transitions.dart';
+
+/// Signature for a function that takes two [Rect] instances and returns a
+/// [RectTween] that transitions between them.
+///
+/// This is typically used with a [HeroController] to provide an animation for
+/// [Hero] positions that looks nicer than a linear movement. For example, see
+/// [MaterialRectArcTween].
+typedef CreateRectTween = Tween<Rect?> Function(Rect? begin, Rect? end);
+
+/// Signature for a function that builds a [Hero] placeholder widget given a
+/// child and a [Size].
+///
+/// The child can optionally be part of the returned widget tree. The returned
+/// widget should typically be constrained to [heroSize], if it doesn't do so
+/// implicitly.
+///
+/// See also:
+///
+///  * [TransitionBuilder], which is similar but only takes a [BuildContext]
+///    and a child widget.
+typedef HeroPlaceholderBuilder = Widget Function(
+  BuildContext context,
+  Size heroSize,
+  Widget child,
+);
+
+/// A function that lets [Hero]es self supply a [Widget] that is shown during the
+/// hero's flight from one route to another instead of default (which is to
+/// show the destination route's instance of the Hero).
+typedef HeroFlightShuttleBuilder = Widget Function(
+  BuildContext flightContext,
+  Animation<double> animation,
+  HeroFlightDirection flightDirection,
+  BuildContext fromHeroContext,
+  BuildContext toHeroContext,
+);
+
+typedef _OnFlightEnded = void Function(_HeroFlight flight);
+
+/// Direction of the hero's flight based on the navigation operation.
+enum HeroFlightDirection {
+  /// A flight triggered by a route push.
+  ///
+  /// The animation goes from 0 to 1.
+  ///
+  /// If no custom [HeroFlightShuttleBuilder] is supplied, the top route's
+  /// [Hero] child is shown in flight.
+  push,
+
+  /// A flight triggered by a route pop.
+  ///
+  /// The animation goes from 1 to 0.
+  ///
+  /// If no custom [HeroFlightShuttleBuilder] is supplied, the bottom route's
+  /// [Hero] child is shown in flight.
+  pop,
+}
+
+// The bounding box for context in ancestorContext coordinate system, or in the global
+// coordinate system when null.
+Rect _boundingBoxFor(BuildContext context, [BuildContext? ancestorContext]) {
+  final RenderBox box = context.findRenderObject()! as RenderBox;
+  assert(box != null && box.hasSize);
+  return MatrixUtils.transformRect(
+      box.getTransformTo(ancestorContext?.findRenderObject()),
+      Offset.zero & box.size,
+  );
+}
+
+/// A widget that marks its child as being a candidate for
+/// [hero animations](https://flutter.dev/docs/development/ui/animations/hero-animations).
+///
+/// When a [PageRoute] is pushed or popped with the [Navigator], the entire
+/// screen's content is replaced. An old route disappears and a new route
+/// appears. If there's a common visual feature on both routes then it can
+/// be helpful for orienting the user for the feature to physically move from
+/// one page to the other during the routes' transition. Such an animation
+/// is called a *hero animation*. The hero widgets "fly" in the Navigator's
+/// overlay during the transition and while they're in-flight they're, by
+/// default, not shown in their original locations in the old and new routes.
+///
+/// To label a widget as such a feature, wrap it in a [Hero] widget. When
+/// navigation happens, the [Hero] widgets on each route are identified
+/// by the [HeroController]. For each pair of [Hero] widgets that have the
+/// same tag, a hero animation is triggered.
+///
+/// If a [Hero] is already in flight when navigation occurs, its
+/// flight animation will be redirected to its new destination. The
+/// widget shown in-flight during the transition is, by default, the
+/// destination route's [Hero]'s child.
+///
+/// For a Hero animation to trigger, the Hero has to exist on the very first
+/// frame of the new page's animation.
+///
+/// Routes must not contain more than one [Hero] for each [tag].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=Be9UH1kXFDw}
+///
+/// ## Discussion
+///
+/// Heroes and the [Navigator]'s [Overlay] [Stack] must be axis-aligned for
+/// all this to work. The top left and bottom right coordinates of each animated
+/// Hero will be converted to global coordinates and then from there converted
+/// to that [Stack]'s coordinate space, and the entire Hero subtree will, for
+/// the duration of the animation, be lifted out of its original place, and
+/// positioned on that stack. If the [Hero] isn't axis aligned, this is going to
+/// fail in a rather ugly fashion. Don't rotate your heroes!
+///
+/// To make the animations look good, it's critical that the widget tree for the
+/// hero in both locations be essentially identical. The widget of the *target*
+/// is, by default, used to do the transition: when going from route A to route
+/// B, route B's hero's widget is placed over route A's hero's widget. If a
+/// [flightShuttleBuilder] is supplied, its output widget is shown during the
+/// flight transition instead.
+///
+/// By default, both route A and route B's heroes are hidden while the
+/// transitioning widget is animating in-flight above the 2 routes.
+/// [placeholderBuilder] can be used to show a custom widget in their place
+/// instead once the transition has taken flight.
+///
+/// During the transition, the transition widget is animated to route B's hero's
+/// position, and then the widget is inserted into route B. When going back from
+/// B to A, route A's hero's widget is, by default, placed over where route B's
+/// hero's widget was, and then the animation goes the other way.
+///
+/// ### Nested Navigators
+///
+/// If either or both routes contain nested [Navigator]s, only [Hero]es
+/// contained in the top-most routes (as defined by [Route.isCurrent]) *of those
+/// nested [Navigator]s* are considered for animation. Just like in the
+/// non-nested case the top-most routes containing these [Hero]es in the nested
+/// [Navigator]s have to be [PageRoute]s.
+///
+/// ## Parts of a Hero Transition
+///
+/// ![Diagrams with parts of the Hero transition.](https://flutter.github.io/assets-for-api-docs/assets/interaction/heroes.png)
+class Hero extends StatefulWidget {
+  /// Create a hero.
+  ///
+  /// The [tag] and [child] parameters must not be null.
+  /// The [child] parameter and all of the its descendants must not be [Hero]es.
+  const Hero({
+    Key? key,
+    required this.tag,
+    this.createRectTween,
+    this.flightShuttleBuilder,
+    this.placeholderBuilder,
+    this.transitionOnUserGestures = false,
+    required this.child,
+  }) : assert(tag != null),
+       assert(transitionOnUserGestures != null),
+       assert(child != null),
+       super(key: key);
+
+  /// The identifier for this particular hero. If the tag of this hero matches
+  /// the tag of a hero on a [PageRoute] that we're navigating to or from, then
+  /// a hero animation will be triggered.
+  final Object tag;
+
+  /// Defines how the destination hero's bounds change as it flies from the starting
+  /// route to the destination route.
+  ///
+  /// A hero flight begins with the destination hero's [child] aligned with the
+  /// starting hero's child. The [Tween<Rect>] returned by this callback is used
+  /// to compute the hero's bounds as the flight animation's value goes from 0.0
+  /// to 1.0.
+  ///
+  /// If this property is null, the default, then the value of
+  /// [HeroController.createRectTween] is used. The [HeroController] created by
+  /// [MaterialApp] creates a [MaterialRectArcTween].
+  final CreateRectTween? createRectTween;
+
+  /// The widget subtree that will "fly" from one route to another during a
+  /// [Navigator] push or pop transition.
+  ///
+  /// The appearance of this subtree should be similar to the appearance of
+  /// the subtrees of any other heroes in the application with the same [tag].
+  /// Changes in scale and aspect ratio work well in hero animations, changes
+  /// in layout or composition do not.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// Optional override to supply a widget that's shown during the hero's flight.
+  ///
+  /// This in-flight widget can depend on the route transition's animation as
+  /// well as the incoming and outgoing routes' [Hero] descendants' widgets and
+  /// layout.
+  ///
+  /// When both the source and destination [Hero]es provide a [flightShuttleBuilder],
+  /// the destination's [flightShuttleBuilder] takes precedence.
+  ///
+  /// If none is provided, the destination route's Hero child is shown in-flight
+  /// by default.
+  ///
+  /// ## Limitations
+  ///
+  /// If a widget built by [flightShuttleBuilder] takes part in a [Navigator]
+  /// push transition, that widget or its descendants must not have any
+  /// [GlobalKey] that is used in the source Hero's descendant widgets. That is
+  /// because both subtrees will be included in the widget tree during the Hero
+  /// flight animation, and [GlobalKey]s must be unique across the entire widget
+  /// tree.
+  ///
+  /// If the said [GlobalKey] is essential to your application, consider providing
+  /// a custom [placeholderBuilder] for the source Hero, to avoid the [GlobalKey]
+  /// collision, such as a builder that builds an empty [SizedBox], keeping the
+  /// Hero [child]'s original size.
+  final HeroFlightShuttleBuilder? flightShuttleBuilder;
+
+  /// Placeholder widget left in place as the Hero's [child] once the flight takes
+  /// off.
+  ///
+  /// By default the placeholder widget is an empty [SizedBox] keeping the Hero
+  /// child's original size, unless this Hero is a source Hero of a [Navigator]
+  /// push transition, in which case [child] will be a descendant of the placeholder
+  /// and will be kept [Offstage] during the Hero's flight.
+  final HeroPlaceholderBuilder? placeholderBuilder;
+
+  /// Whether to perform the hero transition if the [PageRoute] transition was
+  /// triggered by a user gesture, such as a back swipe on iOS.
+  ///
+  /// If [Hero]es with the same [tag] on both the from and the to routes have
+  /// [transitionOnUserGestures] set to true, a back swipe gesture will
+  /// trigger the same hero animation as a programmatically triggered push or
+  /// pop.
+  ///
+  /// The route being popped to or the bottom route must also have
+  /// [PageRoute.maintainState] set to true for a gesture triggered hero
+  /// transition to work.
+  ///
+  /// Defaults to false and cannot be null.
+  final bool transitionOnUserGestures;
+
+  // Returns a map of all of the heroes in `context` indexed by hero tag that
+  // should be considered for animation when `navigator` transitions from one
+  // PageRoute to another.
+  static Map<Object, _HeroState> _allHeroesFor(
+    BuildContext context,
+    bool isUserGestureTransition,
+    NavigatorState navigator,
+  ) {
+    assert(context != null);
+    assert(isUserGestureTransition != null);
+    assert(navigator != null);
+    final Map<Object, _HeroState> result = <Object, _HeroState>{};
+
+    void inviteHero(StatefulElement hero, Object tag) {
+      assert(() {
+        if (result.containsKey(tag)) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('There are multiple heroes that share the same tag within a subtree.'),
+            ErrorDescription(
+              'Within each subtree for which heroes are to be animated (i.e. a PageRoute subtree), '
+              'each Hero must have a unique non-null tag.\n'
+              'In this case, multiple heroes had the following tag: $tag\n'
+            ),
+            DiagnosticsProperty<StatefulElement>('Here is the subtree for one of the offending heroes', hero, linePrefix: '# ', style: DiagnosticsTreeStyle.dense),
+          ]);
+        }
+        return true;
+      }());
+      final Hero heroWidget = hero.widget as Hero;
+      final _HeroState heroState = hero.state as _HeroState;
+      if (!isUserGestureTransition || heroWidget.transitionOnUserGestures) {
+        result[tag] = heroState;
+      } else {
+        // If transition is not allowed, we need to make sure hero is not hidden.
+        // A hero can be hidden previously due to hero transition.
+        heroState.ensurePlaceholderIsHidden();
+      }
+    }
+
+    void visitor(Element element) {
+      final Widget widget = element.widget;
+      if (widget is Hero) {
+        final StatefulElement hero = element as StatefulElement;
+        final Object tag = widget.tag;
+        assert(tag != null);
+        if (Navigator.of(hero) == navigator) {
+          inviteHero(hero, tag);
+        } else {
+          // The nearest navigator to the Hero is not the Navigator that is
+          // currently transitioning from one route to another. This means
+          // the Hero is inside a nested Navigator and should only be
+          // considered for animation if it is part of the top-most route in
+          // that nested Navigator and if that route is also a PageRoute.
+          final ModalRoute<Object?>? heroRoute = ModalRoute.of(hero);
+          if (heroRoute != null && heroRoute is PageRoute && heroRoute.isCurrent) {
+            inviteHero(hero, tag);
+          }
+        }
+      } else if (widget is HeroMode && !widget.enabled) {
+        return;
+      }
+      element.visitChildren(visitor);
+    }
+
+    context.visitChildElements(visitor);
+    return result;
+  }
+
+  @override
+  _HeroState createState() => _HeroState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Object>('tag', tag));
+  }
+}
+
+class _HeroState extends State<Hero> {
+  final GlobalKey _key = GlobalKey();
+  Size? _placeholderSize;
+  // Whether the placeholder widget should wrap the hero's child widget as its
+  // own child, when `_placeholderSize` is non-null (i.e. the hero is currently
+  // in its flight animation). See `startFlight`.
+  bool _shouldIncludeChild = true;
+
+  // The `shouldIncludeChildInPlaceholder` flag dictates if the child widget of
+  // this hero should be included in the placeholder widget as a descendant.
+  //
+  // When a new hero flight animation takes place, a placeholder widget
+  // needs to be built to replace the original hero widget. When
+  // `shouldIncludeChildInPlaceholder` is set to true and `widget.placeholderBuilder`
+  // is null, the placeholder widget will include the original hero's child
+  // widget as a descendant, allowing the original element tree to be preserved.
+  //
+  // It is typically set to true for the *from* hero in a push transition,
+  // and false otherwise.
+  void startFlight({ bool shouldIncludedChildInPlaceholder = false }) {
+    _shouldIncludeChild = shouldIncludedChildInPlaceholder;
+    assert(mounted);
+    final RenderBox box = context.findRenderObject()! as RenderBox;
+    assert(box != null && box.hasSize);
+    setState(() {
+      _placeholderSize = box.size;
+    });
+  }
+
+  void ensurePlaceholderIsHidden() {
+    if (mounted) {
+      setState(() {
+        _placeholderSize = null;
+      });
+    }
+  }
+
+  // When `keepPlaceholder` is true, the placeholder will continue to be shown
+  // after the flight ends. Otherwise the child of the Hero will become visible
+  // and its TickerMode will be re-enabled.
+  void endFlight({ bool keepPlaceholder = false }) {
+    if (!keepPlaceholder) {
+      ensurePlaceholderIsHidden();
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(
+      context.findAncestorWidgetOfExactType<Hero>() == null,
+      'A Hero widget cannot be the descendant of another Hero widget.'
+    );
+
+    final bool showPlaceholder = _placeholderSize != null;
+
+    if (showPlaceholder && widget.placeholderBuilder != null) {
+      return widget.placeholderBuilder!(context, _placeholderSize!, widget.child);
+    }
+
+    if (showPlaceholder && !_shouldIncludeChild) {
+      return SizedBox(
+        width: _placeholderSize!.width,
+        height: _placeholderSize!.height,
+      );
+    }
+
+    return SizedBox(
+      width: _placeholderSize?.width,
+      height: _placeholderSize?.height,
+      child: Offstage(
+        offstage: showPlaceholder,
+        child: TickerMode(
+          enabled: !showPlaceholder,
+          child: KeyedSubtree(key: _key, child: widget.child),
+        ),
+      ),
+    );
+  }
+}
+
+// Everything known about a hero flight that's to be started or diverted.
+class _HeroFlightManifest {
+  _HeroFlightManifest({
+    required this.type,
+    required this.overlay,
+    required this.navigatorRect,
+    required this.fromRoute,
+    required this.toRoute,
+    required this.fromHero,
+    required this.toHero,
+    required this.createRectTween,
+    required this.shuttleBuilder,
+    required this.isUserGestureTransition,
+    required this.isDiverted,
+  }) : assert(fromHero.widget.tag == toHero.widget.tag);
+
+  final HeroFlightDirection type;
+  final OverlayState? overlay;
+  final Rect navigatorRect;
+  final PageRoute<dynamic> fromRoute;
+  final PageRoute<dynamic> toRoute;
+  final _HeroState fromHero;
+  final _HeroState toHero;
+  final CreateRectTween? createRectTween;
+  final HeroFlightShuttleBuilder shuttleBuilder;
+  final bool isUserGestureTransition;
+  final bool isDiverted;
+
+  Object get tag => fromHero.widget.tag;
+
+  Animation<double> get animation {
+    return CurvedAnimation(
+      parent: (type == HeroFlightDirection.push) ? toRoute.animation! : fromRoute.animation!,
+      curve: Curves.fastOutSlowIn,
+      reverseCurve: isDiverted ? null : Curves.fastOutSlowIn.flipped,
+    );
+  }
+
+  @override
+  String toString() {
+    return '_HeroFlightManifest($type tag: $tag from route: ${fromRoute.settings} '
+        'to route: ${toRoute.settings} with hero: $fromHero to $toHero)';
+  }
+}
+
+// Builds the in-flight hero widget.
+class _HeroFlight {
+  _HeroFlight(this.onFlightEnded) {
+    _proxyAnimation = ProxyAnimation()..addStatusListener(_handleAnimationUpdate);
+  }
+
+  final _OnFlightEnded onFlightEnded;
+
+  late Tween<Rect?> heroRectTween;
+  Widget? shuttle;
+
+  Animation<double> _heroOpacity = kAlwaysCompleteAnimation;
+  late ProxyAnimation _proxyAnimation;
+  _HeroFlightManifest? manifest;
+  OverlayEntry? overlayEntry;
+  bool _aborted = false;
+
+  Tween<Rect?> _doCreateRectTween(Rect? begin, Rect? end) {
+    final CreateRectTween? createRectTween = manifest!.toHero.widget.createRectTween ?? manifest!.createRectTween;
+    if (createRectTween != null)
+      return createRectTween(begin, end);
+    return RectTween(begin: begin, end: end);
+  }
+
+  static final Animatable<double> _reverseTween = Tween<double>(begin: 1.0, end: 0.0);
+
+  // The OverlayEntry WidgetBuilder callback for the hero's overlay.
+  Widget _buildOverlay(BuildContext context) {
+    assert(manifest != null);
+    shuttle ??= manifest!.shuttleBuilder(
+      context,
+      manifest!.animation,
+      manifest!.type,
+      manifest!.fromHero.context,
+      manifest!.toHero.context,
+    );
+    assert(shuttle != null);
+
+    return AnimatedBuilder(
+      animation: _proxyAnimation,
+      child: shuttle,
+      builder: (BuildContext context, Widget? child) {
+        final RenderBox? toHeroBox = manifest!.toHero.mounted
+          ? manifest!.toHero.context.findRenderObject() as RenderBox?
+          : null;
+        if (_aborted || toHeroBox == null || !toHeroBox.attached) {
+          // The toHero no longer exists or it's no longer the flight's destination.
+          // Continue flying while fading out.
+          if (_heroOpacity.isCompleted) {
+            _heroOpacity = _proxyAnimation.drive(
+              _reverseTween.chain(CurveTween(curve: Interval(_proxyAnimation.value, 1.0))),
+            );
+          }
+        } else if (toHeroBox.hasSize) {
+          // The toHero has been laid out. If it's no longer where the hero animation is
+          // supposed to end up then recreate the heroRect tween.
+          final RenderBox? finalRouteBox = manifest!.toRoute.subtreeContext?.findRenderObject() as RenderBox?;
+          final Offset toHeroOrigin = toHeroBox.localToGlobal(Offset.zero, ancestor: finalRouteBox);
+          if (toHeroOrigin != heroRectTween.end!.topLeft) {
+            final Rect heroRectEnd = toHeroOrigin & heroRectTween.end!.size;
+            heroRectTween = _doCreateRectTween(heroRectTween.begin, heroRectEnd);
+          }
+        }
+
+        final Rect rect = heroRectTween.evaluate(_proxyAnimation)!;
+        final Size size = manifest!.navigatorRect.size;
+        final RelativeRect offsets = RelativeRect.fromSize(rect, size);
+
+        return Positioned(
+          top: offsets.top,
+          right: offsets.right,
+          bottom: offsets.bottom,
+          left: offsets.left,
+          child: IgnorePointer(
+            child: RepaintBoundary(
+              child: Opacity(
+                opacity: _heroOpacity.value,
+                child: child,
+              ),
+            ),
+          ),
+        );
+      },
+    );
+  }
+
+  void _performAnimationUpdate(AnimationStatus status) {
+    if (status == AnimationStatus.completed || status == AnimationStatus.dismissed) {
+      _proxyAnimation.parent = null;
+
+      assert(overlayEntry != null);
+      overlayEntry!.remove();
+      overlayEntry = null;
+      // We want to keep the hero underneath the current page hidden. If
+      // [AnimationStatus.completed], toHero will be the one on top and we keep
+      // fromHero hidden. If [AnimationStatus.dismissed], the animation is
+      // triggered but canceled before it finishes. In this case, we keep toHero
+      // hidden instead.
+      manifest!.fromHero.endFlight(keepPlaceholder: status == AnimationStatus.completed);
+      manifest!.toHero.endFlight(keepPlaceholder: status == AnimationStatus.dismissed);
+      onFlightEnded(this);
+    }
+  }
+
+  bool _scheduledPerformAnimtationUpdate = false;
+  void _handleAnimationUpdate(AnimationStatus status) {
+    // The animation will not finish until the user lifts their finger, so we
+    // should suppress the status update if the gesture is in progress, and
+    // delay it until the finger is lifted.
+    if (manifest!.fromRoute.navigator?.userGestureInProgress != true) {
+      _performAnimationUpdate(status);
+      return;
+    }
+
+    if (_scheduledPerformAnimtationUpdate)
+      return;
+
+    // The `navigator` must be non-null here, or the first if clause above would
+    // have returned from this method.
+    final NavigatorState navigator = manifest!.fromRoute.navigator!;
+
+    void delayedPerformAnimtationUpdate() {
+      assert(!navigator.userGestureInProgress);
+      assert(_scheduledPerformAnimtationUpdate);
+      _scheduledPerformAnimtationUpdate = false;
+      navigator.userGestureInProgressNotifier.removeListener(delayedPerformAnimtationUpdate);
+      _performAnimationUpdate(_proxyAnimation.status);
+    }
+    assert(navigator.userGestureInProgress);
+    _scheduledPerformAnimtationUpdate = true;
+    navigator.userGestureInProgressNotifier.addListener(delayedPerformAnimtationUpdate);
+  }
+
+  // The simple case: we're either starting a push or a pop animation.
+  void start(_HeroFlightManifest initialManifest) {
+    assert(!_aborted);
+    assert(() {
+      final Animation<double> initial = initialManifest.animation;
+      assert(initial != null);
+      final HeroFlightDirection type = initialManifest.type;
+      assert(type != null);
+      switch (type) {
+        case HeroFlightDirection.pop:
+          return initial.value == 1.0 && initialManifest.isUserGestureTransition
+              // During user gesture transitions, the animation controller isn't
+              // driving the reverse transition, but should still be in a previously
+              // completed stage with the initial value at 1.0.
+              ? initial.status == AnimationStatus.completed
+              : initial.status == AnimationStatus.reverse;
+        case HeroFlightDirection.push:
+          return initial.value == 0.0 && initial.status == AnimationStatus.forward;
+      }
+    }());
+
+    manifest = initialManifest;
+
+    if (manifest!.type == HeroFlightDirection.pop)
+      _proxyAnimation.parent = ReverseAnimation(manifest!.animation);
+    else
+      _proxyAnimation.parent = manifest!.animation;
+
+    manifest!.fromHero.startFlight(shouldIncludedChildInPlaceholder: manifest!.type == HeroFlightDirection.push);
+    manifest!.toHero.startFlight();
+
+    heroRectTween = _doCreateRectTween(
+      _boundingBoxFor(manifest!.fromHero.context, manifest!.fromRoute.subtreeContext),
+      _boundingBoxFor(manifest!.toHero.context, manifest!.toRoute.subtreeContext),
+    );
+
+    overlayEntry = OverlayEntry(builder: _buildOverlay);
+    manifest!.overlay!.insert(overlayEntry!);
+  }
+
+  // While this flight's hero was in transition a push or a pop occurred for
+  // routes with the same hero. Redirect the in-flight hero to the new toRoute.
+  void divert(_HeroFlightManifest newManifest) {
+    assert(manifest!.tag == newManifest.tag);
+    if (manifest!.type == HeroFlightDirection.push && newManifest.type == HeroFlightDirection.pop) {
+      // A push flight was interrupted by a pop.
+      assert(newManifest.animation.status == AnimationStatus.reverse);
+      assert(manifest!.fromHero == newManifest.toHero);
+      assert(manifest!.toHero == newManifest.fromHero);
+      assert(manifest!.fromRoute == newManifest.toRoute);
+      assert(manifest!.toRoute == newManifest.fromRoute);
+
+      // The same heroRect tween is used in reverse, rather than creating
+      // a new heroRect with _doCreateRectTween(heroRect.end, heroRect.begin).
+      // That's because tweens like MaterialRectArcTween may create a different
+      // path for swapped begin and end parameters. We want the pop flight
+      // path to be the same (in reverse) as the push flight path.
+      _proxyAnimation.parent = ReverseAnimation(newManifest.animation);
+      heroRectTween = ReverseTween<Rect?>(heroRectTween);
+    } else if (manifest!.type == HeroFlightDirection.pop && newManifest.type == HeroFlightDirection.push) {
+      // A pop flight was interrupted by a push.
+      assert(newManifest.animation.status == AnimationStatus.forward);
+      assert(manifest!.toHero == newManifest.fromHero);
+      assert(manifest!.toRoute == newManifest.fromRoute);
+
+      _proxyAnimation.parent = newManifest.animation.drive(
+        Tween<double>(
+          begin: manifest!.animation.value,
+          end: 1.0,
+        ),
+      );
+      if (manifest!.fromHero != newManifest.toHero) {
+        manifest!.fromHero.endFlight(keepPlaceholder: true);
+        newManifest.toHero.startFlight();
+        heroRectTween = _doCreateRectTween(
+            heroRectTween.end,
+            _boundingBoxFor(newManifest.toHero.context, newManifest.toRoute.subtreeContext),
+        );
+      } else {
+        // TODO(hansmuller): Use ReverseTween here per github.com/flutter/flutter/pull/12203.
+        heroRectTween = _doCreateRectTween(heroRectTween.end, heroRectTween.begin);
+      }
+    } else {
+      // A push or a pop flight is heading to a new route, i.e.
+      // manifest.type == _HeroFlightType.push && newManifest.type == _HeroFlightType.push ||
+      // manifest.type == _HeroFlightType.pop && newManifest.type == _HeroFlightType.pop
+      assert(manifest!.fromHero != newManifest.fromHero);
+      assert(manifest!.toHero != newManifest.toHero);
+
+      heroRectTween = _doCreateRectTween(
+          heroRectTween.evaluate(_proxyAnimation),
+          _boundingBoxFor(newManifest.toHero.context, newManifest.toRoute.subtreeContext),
+      );
+      shuttle = null;
+
+      if (newManifest.type == HeroFlightDirection.pop)
+        _proxyAnimation.parent = ReverseAnimation(newManifest.animation);
+      else
+        _proxyAnimation.parent = newManifest.animation;
+
+      manifest!.fromHero.endFlight(keepPlaceholder: true);
+      manifest!.toHero.endFlight(keepPlaceholder: true);
+
+      // Let the heroes in each of the routes rebuild with their placeholders.
+      newManifest.fromHero.startFlight(shouldIncludedChildInPlaceholder: newManifest.type == HeroFlightDirection.push);
+      newManifest.toHero.startFlight();
+
+      // Let the transition overlay on top of the routes also rebuild since
+      // we cleared the old shuttle.
+      overlayEntry!.markNeedsBuild();
+    }
+
+    _aborted = false;
+    manifest = newManifest;
+  }
+
+  void abort() {
+    _aborted = true;
+  }
+
+  @override
+  String toString() {
+    final RouteSettings from = manifest!.fromRoute.settings;
+    final RouteSettings to = manifest!.toRoute.settings;
+    final Object tag = manifest!.tag;
+    return 'HeroFlight(for: $tag, from: $from, to: $to ${_proxyAnimation.parent})';
+  }
+}
+
+/// A [Navigator] observer that manages [Hero] transitions.
+///
+/// An instance of [HeroController] should be used in [Navigator.observers].
+/// This is done automatically by [MaterialApp].
+class HeroController extends NavigatorObserver {
+  /// Creates a hero controller with the given [RectTween] constructor if any.
+  ///
+  /// The [createRectTween] argument is optional. If null, the controller uses a
+  /// linear [Tween<Rect>].
+  HeroController({ this.createRectTween });
+
+  /// Used to create [RectTween]s that interpolate the position of heroes in flight.
+  ///
+  /// If null, the controller uses a linear [RectTween].
+  final CreateRectTween? createRectTween;
+
+  // All of the heroes that are currently in the overlay and in motion.
+  // Indexed by the hero tag.
+  final Map<Object, _HeroFlight> _flights = <Object, _HeroFlight>{};
+
+  @override
+  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
+    assert(navigator != null);
+    assert(route != null);
+    _maybeStartHeroTransition(previousRoute, route, HeroFlightDirection.push, false);
+  }
+
+  @override
+  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
+    assert(navigator != null);
+    assert(route != null);
+    // Don't trigger another flight when a pop is committed as a user gesture
+    // back swipe is snapped.
+    if (!navigator!.userGestureInProgress)
+      _maybeStartHeroTransition(route, previousRoute, HeroFlightDirection.pop, false);
+  }
+
+  @override
+  void didReplace({ Route<dynamic>? newRoute, Route<dynamic>? oldRoute }) {
+    assert(navigator != null);
+    if (newRoute?.isCurrent == true) {
+      // Only run hero animations if the top-most route got replaced.
+      _maybeStartHeroTransition(oldRoute, newRoute, HeroFlightDirection.push, false);
+    }
+  }
+
+  @override
+  void didStartUserGesture(Route<dynamic> route, Route<dynamic>? previousRoute) {
+    assert(navigator != null);
+    assert(route != null);
+    _maybeStartHeroTransition(route, previousRoute, HeroFlightDirection.pop, true);
+  }
+
+  @override
+  void didStopUserGesture() {
+    if (navigator!.userGestureInProgress)
+      return;
+
+    // If the user horizontal drag gesture initiated the flight (i.e. the back swipe)
+    // didn't move towards the pop direction at all, the animation will not play
+    // and thus the status update callback _handleAnimationUpdate will never be
+    // called when the gesture finishes. In this case the initiated flight needs
+    // to be manually invalidated.
+    bool isInvalidFlight(_HeroFlight flight) {
+      return flight.manifest!.isUserGestureTransition
+          && flight.manifest!.type == HeroFlightDirection.pop
+          && flight._proxyAnimation.isDismissed;
+    }
+
+    final List<_HeroFlight> invalidFlights = _flights.values
+      .where(isInvalidFlight)
+      .toList(growable: false);
+
+    // Treat these invalidated flights as dismissed. Calling _handleAnimationUpdate
+    // will also remove the flight from _flights.
+    for (final _HeroFlight flight in invalidFlights) {
+      flight._handleAnimationUpdate(AnimationStatus.dismissed);
+    }
+  }
+
+  // If we're transitioning between different page routes, start a hero transition
+  // after the toRoute has been laid out with its animation's value at 1.0.
+  void _maybeStartHeroTransition(
+    Route<dynamic>? fromRoute,
+    Route<dynamic>? toRoute,
+    HeroFlightDirection flightType,
+    bool isUserGestureTransition,
+  ) {
+    if (toRoute != fromRoute && toRoute is PageRoute<dynamic> && fromRoute is PageRoute<dynamic>) {
+      final PageRoute<dynamic> from = fromRoute;
+      final PageRoute<dynamic> to = toRoute;
+      final Animation<double> animation = (flightType == HeroFlightDirection.push) ? to.animation! : from.animation!;
+
+      // A user gesture may have already completed the pop, or we might be the initial route
+      switch (flightType) {
+        case HeroFlightDirection.pop:
+          if (animation.value == 0.0) {
+            return;
+          }
+          break;
+        case HeroFlightDirection.push:
+          if (animation.value == 1.0) {
+            return;
+          }
+          break;
+      }
+
+      // For pop transitions driven by a user gesture: if the "to" page has
+      // maintainState = true, then the hero's final dimensions can be measured
+      // immediately because their page's layout is still valid.
+      if (isUserGestureTransition && flightType == HeroFlightDirection.pop && to.maintainState) {
+        _startHeroTransition(from, to, animation, flightType, isUserGestureTransition);
+      } else {
+        // Otherwise, delay measuring until the end of the next frame to allow
+        // the 'to' route to build and layout.
+
+        // Putting a route offstage changes its animation value to 1.0. Once this
+        // frame completes, we'll know where the heroes in the `to` route are
+        // going to end up, and the `to` route will go back onstage.
+        to.offstage = to.animation!.value == 0.0;
+
+        WidgetsBinding.instance!.addPostFrameCallback((Duration value) {
+          _startHeroTransition(from, to, animation, flightType, isUserGestureTransition);
+        });
+      }
+    }
+  }
+
+  // Find the matching pairs of heroes in from and to and either start or a new
+  // hero flight, or divert an existing one.
+  void _startHeroTransition(
+    PageRoute<dynamic> from,
+    PageRoute<dynamic> to,
+    Animation<double> animation,
+    HeroFlightDirection flightType,
+    bool isUserGestureTransition,
+  ) {
+    // If the navigator or one of the routes subtrees was removed before this
+    // end-of-frame callback was called, then don't actually start a transition.
+    if (navigator == null || from.subtreeContext == null || to.subtreeContext == null) {
+      to.offstage = false; // in case we set this in _maybeStartHeroTransition
+      return;
+    }
+
+    final Rect navigatorRect = _boundingBoxFor(navigator!.context);
+
+    // At this point the toHeroes may have been built and laid out for the first time.
+    final Map<Object, _HeroState> fromHeroes = Hero._allHeroesFor(from.subtreeContext!, isUserGestureTransition, navigator!);
+    final Map<Object, _HeroState> toHeroes = Hero._allHeroesFor(to.subtreeContext!, isUserGestureTransition, navigator!);
+
+    // If the `to` route was offstage, then we're implicitly restoring its
+    // animation value back to what it was before it was "moved" offstage.
+    to.offstage = false;
+
+    for (final Object tag in fromHeroes.keys) {
+      if (toHeroes[tag] != null) {
+        final HeroFlightShuttleBuilder? fromShuttleBuilder = fromHeroes[tag]!.widget.flightShuttleBuilder;
+        final HeroFlightShuttleBuilder? toShuttleBuilder = toHeroes[tag]!.widget.flightShuttleBuilder;
+        final bool isDiverted = _flights[tag] != null;
+
+        final _HeroFlightManifest manifest = _HeroFlightManifest(
+          type: flightType,
+          overlay: navigator!.overlay,
+          navigatorRect: navigatorRect,
+          fromRoute: from,
+          toRoute: to,
+          fromHero: fromHeroes[tag]!,
+          toHero: toHeroes[tag]!,
+          createRectTween: createRectTween,
+          shuttleBuilder:
+              toShuttleBuilder ?? fromShuttleBuilder ?? _defaultHeroFlightShuttleBuilder,
+          isUserGestureTransition: isUserGestureTransition,
+          isDiverted: isDiverted,
+        );
+
+        if (isDiverted)
+          _flights[tag]!.divert(manifest);
+        else
+          _flights[tag] = _HeroFlight(_handleFlightEnded)..start(manifest);
+      } else if (_flights[tag] != null) {
+        _flights[tag]!.abort();
+      }
+    }
+
+    // If the from hero is gone, the flight won't start and the to hero needs to
+    // be put on stage again.
+    for (final Object tag in toHeroes.keys) {
+      if (fromHeroes[tag] == null)
+        toHeroes[tag]!.ensurePlaceholderIsHidden();
+    }
+  }
+
+  void _handleFlightEnded(_HeroFlight flight) {
+    _flights.remove(flight.manifest!.tag);
+  }
+
+  static final HeroFlightShuttleBuilder _defaultHeroFlightShuttleBuilder = (
+    BuildContext flightContext,
+    Animation<double> animation,
+    HeroFlightDirection flightDirection,
+    BuildContext fromHeroContext,
+    BuildContext toHeroContext,
+  ) {
+    final Hero toHero = toHeroContext.widget as Hero;
+    return toHero.child;
+  };
+}
+
+/// Enables or disables [Hero]es in the widget subtree.
+///
+/// When [enabled] is false, all [Hero] widgets in this subtree will not be
+/// involved in hero animations.
+///
+/// When [enabled] is true (the default), [Hero] widgets may be involved in
+/// hero animations, as usual.
+class HeroMode extends StatelessWidget {
+  /// Creates a widget that enables or disables [Hero]es.
+  ///
+  /// The [child] and [enabled] arguments must not be null.
+  const HeroMode({
+    Key? key,
+    required this.child,
+    this.enabled = true,
+  }) : assert(child != null),
+       assert(enabled != null),
+       super(key: key);
+
+  /// The subtree to place inside the [HeroMode].
+  final Widget child;
+
+  /// Whether or not [Hero]es are enabled in this subtree.
+  ///
+  /// If this property is false, the [Hero]es in this subtree will not animate
+  /// on route changes. Otherwise, they will animate as usual.
+  ///
+  /// Defaults to true and must not be null.
+  final bool enabled;
+
+  @override
+  Widget build(BuildContext context) => child;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('mode', value: enabled, ifTrue: 'enabled', ifFalse: 'disabled', showName: true));
+  }
+}
diff --git a/lib/src/widgets/icon.dart b/lib/src/widgets/icon.dart
new file mode 100644
index 0000000..7e8ff4a
--- /dev/null
+++ b/lib/src/widgets/icon.dart
@@ -0,0 +1,224 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'debug.dart';
+import 'framework.dart';
+import 'icon_data.dart';
+import 'icon_theme.dart';
+import 'icon_theme_data.dart';
+
+/// A graphical icon widget drawn with a glyph from a font described in
+/// an [IconData] such as material's predefined [IconData]s in [Icons].
+///
+/// Icons are not interactive. For an interactive icon, consider material's
+/// [IconButton].
+///
+/// There must be an ambient [Directionality] widget when using [Icon].
+/// Typically this is introduced automatically by the [WidgetsApp] or
+/// [MaterialApp].
+///
+/// This widget assumes that the rendered icon is squared. Non-squared icons may
+/// render incorrectly.
+///
+/// {@tool snippet}
+///
+/// This example shows how to create a [Row] of [Icon]s in different colors and
+/// sizes. The first [Icon] uses a [semanticLabel] to announce in accessibility
+/// modes like TalkBack and VoiceOver.
+///
+/// ![A row of icons representing a pink heart, a green musical note, and a blue umbrella](https://flutter.github.io/assets-for-api-docs/assets/widgets/icon.png)
+///
+/// ```dart
+/// Row(
+///   mainAxisAlignment: MainAxisAlignment.spaceAround,
+///   children: const <Widget>[
+///     Icon(
+///       Icons.favorite,
+///       color: Colors.pink,
+///       size: 24.0,
+///       semanticLabel: 'Text to announce in accessibility modes',
+///     ),
+///     Icon(
+///       Icons.audiotrack,
+///       color: Colors.green,
+///       size: 30.0,
+///     ),
+///     Icon(
+///       Icons.beach_access,
+///       color: Colors.blue,
+///       size: 36.0,
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [IconButton], for interactive icons.
+///  * [Icons], for the list of available icons for use with this class.
+///  * [IconTheme], which provides ambient configuration for icons.
+///  * [ImageIcon], for showing icons from [AssetImage]s or other [ImageProvider]s.
+class Icon extends StatelessWidget {
+  /// Creates an icon.
+  ///
+  /// The [size] and [color] default to the value given by the current [IconTheme].
+  const Icon(
+    this.icon, {
+    Key? key,
+    this.size,
+    this.color,
+    this.semanticLabel,
+    this.textDirection,
+  }) : super(key: key);
+
+  /// The icon to display. The available icons are described in [Icons].
+  ///
+  /// The icon can be null, in which case the widget will render as an empty
+  /// space of the specified [size].
+  final IconData? icon;
+
+  /// The size of the icon in logical pixels.
+  ///
+  /// Icons occupy a square with width and height equal to size.
+  ///
+  /// Defaults to the current [IconTheme] size, if any. If there is no
+  /// [IconTheme], or it does not specify an explicit size, then it defaults to
+  /// 24.0.
+  ///
+  /// If this [Icon] is being placed inside an [IconButton], then use
+  /// [IconButton.iconSize] instead, so that the [IconButton] can make the splash
+  /// area the appropriate size as well. The [IconButton] uses an [IconTheme] to
+  /// pass down the size to the [Icon].
+  final double? size;
+
+  /// The color to use when drawing the icon.
+  ///
+  /// Defaults to the current [IconTheme] color, if any.
+  ///
+  /// The given color will be adjusted by the opacity of the current
+  /// [IconTheme], if any.
+  ///
+  ///
+  /// In material apps, if there is a [Theme] without any [IconTheme]s
+  /// specified, icon colors default to white if the theme is dark
+  /// and black if the theme is light.
+  ///
+  /// If no [IconTheme] and no [Theme] is specified, icons will default to black.
+  ///
+  /// See [Theme] to set the current theme and [ThemeData.brightness]
+  /// for setting the current theme's brightness.
+  ///
+  /// {@tool snippet}
+  /// Typically, a material design color will be used, as follows:
+  ///
+  /// ```dart
+  /// Icon(
+  ///   Icons.widgets,
+  ///   color: Colors.blue.shade400,
+  /// )
+  /// ```
+  /// {@end-tool}
+  final Color? color;
+
+  /// Semantic label for the icon.
+  ///
+  /// Announced in accessibility modes (e.g TalkBack/VoiceOver).
+  /// This label does not show in the UI.
+  ///
+  ///  * [SemanticsProperties.label], which is set to [semanticLabel] in the
+  ///    underlying	 [Semantics] widget.
+  final String? semanticLabel;
+
+  /// The text direction to use for rendering the icon.
+  ///
+  /// If this is null, the ambient [Directionality] is used instead.
+  ///
+  /// Some icons follow the reading direction. For example, "back" buttons point
+  /// left in left-to-right environments and right in right-to-left
+  /// environments. Such icons have their [IconData.matchTextDirection] field
+  /// set to true, and the [Icon] widget uses the [textDirection] to determine
+  /// the orientation in which to draw the icon.
+  ///
+  /// This property has no effect if the [icon]'s [IconData.matchTextDirection]
+  /// field is false, but for consistency a text direction value must always be
+  /// specified, either directly using this property or using [Directionality].
+  final TextDirection? textDirection;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(this.textDirection != null || debugCheckHasDirectionality(context));
+    final TextDirection textDirection = this.textDirection ?? Directionality.of(context);
+
+    final IconThemeData iconTheme = IconTheme.of(context);
+
+    final double? iconSize = size ?? iconTheme.size;
+
+    if (icon == null) {
+      return Semantics(
+        label: semanticLabel,
+        child: SizedBox(width: iconSize, height: iconSize),
+      );
+    }
+
+    final double iconOpacity = iconTheme.opacity ?? 1.0;
+    Color iconColor = color ?? iconTheme.color!;
+    if (iconOpacity != 1.0)
+      iconColor = iconColor.withOpacity(iconColor.opacity * iconOpacity);
+
+    Widget iconWidget = RichText(
+      overflow: TextOverflow.visible, // Never clip.
+      textDirection: textDirection, // Since we already fetched it for the assert...
+      text: TextSpan(
+        text: String.fromCharCode(icon!.codePoint),
+        style: TextStyle(
+          inherit: false,
+          color: iconColor,
+          fontSize: iconSize,
+          fontFamily: icon!.fontFamily,
+          package: icon!.fontPackage,
+        ),
+      ),
+    );
+
+    if (icon!.matchTextDirection) {
+      switch (textDirection) {
+        case TextDirection.rtl:
+          iconWidget = Transform(
+            transform: Matrix4.identity()..scale(-1.0, 1.0, 1.0),
+            alignment: Alignment.center,
+            transformHitTests: false,
+            child: iconWidget,
+          );
+          break;
+        case TextDirection.ltr:
+          break;
+      }
+    }
+
+    return Semantics(
+      label: semanticLabel,
+      child: ExcludeSemantics(
+        child: SizedBox(
+          width: iconSize,
+          height: iconSize,
+          child: Center(
+            child: iconWidget,
+          ),
+        ),
+      ),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(IconDataProperty('icon', icon, ifNull: '<empty>', showName: false));
+    properties.add(DoubleProperty('size', size, defaultValue: null));
+    properties.add(ColorProperty('color', color, defaultValue: null));
+  }
+}
diff --git a/lib/src/widgets/icon_data.dart b/lib/src/widgets/icon_data.dart
new file mode 100644
index 0000000..01a79a0
--- /dev/null
+++ b/lib/src/widgets/icon_data.dart
@@ -0,0 +1,102 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show hashValues;
+
+import 'package:flute/foundation.dart';
+
+/// A description of an icon fulfilled by a font glyph.
+///
+/// See [Icons] for a number of predefined icons available for material
+/// design applications.
+@immutable
+class IconData {
+  /// Creates icon data.
+  ///
+  /// Rarely used directly. Instead, consider using one of the predefined icons
+  /// like the [Icons] collection.
+  ///
+  /// The [fontPackage] argument must be non-null when using a font family that
+  /// is included in a package. This is used when selecting the font.
+  const IconData(
+    this.codePoint, {
+    this.fontFamily,
+    this.fontPackage,
+    this.matchTextDirection = false,
+  });
+
+  /// The Unicode code point at which this icon is stored in the icon font.
+  final int codePoint;
+
+  /// The font family from which the glyph for the [codePoint] will be selected.
+  final String? fontFamily;
+
+  /// The name of the package from which the font family is included.
+  ///
+  /// The name is used by the [Icon] widget when configuring the [TextStyle] so
+  /// that the given [fontFamily] is obtained from the appropriate asset.
+  ///
+  /// See also:
+  ///
+  ///  * [TextStyle], which describes how to use fonts from other packages.
+  final String? fontPackage;
+
+  /// Whether this icon should be automatically mirrored in right-to-left
+  /// environments.
+  ///
+  /// The [Icon] widget respects this value by mirroring the icon when the
+  /// [Directionality] is [TextDirection.rtl].
+  final bool matchTextDirection;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is IconData
+        && other.codePoint == codePoint
+        && other.fontFamily == fontFamily
+        && other.fontPackage == fontPackage
+        && other.matchTextDirection == matchTextDirection;
+  }
+
+  @override
+  int get hashCode => hashValues(codePoint, fontFamily, fontPackage, matchTextDirection);
+
+  @override
+  String toString() => 'IconData(U+${codePoint.toRadixString(16).toUpperCase().padLeft(5, '0')})';
+}
+
+/// [DiagnosticsProperty] that has an [IconData] as value.
+class IconDataProperty extends DiagnosticsProperty<IconData> {
+  /// Create a diagnostics property for [IconData].
+  ///
+  /// The [showName], [style], and [level] arguments must not be null.
+  IconDataProperty(
+    String name,
+    IconData? value, {
+    String? ifNull,
+    bool showName = true,
+    DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
+    DiagnosticLevel level = DiagnosticLevel.info,
+  }) : assert(showName != null),
+       assert(style != null),
+       assert(level != null),
+       super(name, value,
+         showName: showName,
+         ifNull: ifNull,
+         style: style,
+         level: level,
+       );
+
+  @override
+  Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
+    final Map<String, Object?> json = super.toJsonMap(delegate);
+    if (value != null) {
+      json['valueProperties'] = <String, Object>{
+        'codePoint': value!.codePoint,
+      };
+    }
+    return json;
+  }
+}
diff --git a/lib/src/widgets/icon_theme.dart b/lib/src/widgets/icon_theme.dart
new file mode 100644
index 0000000..d5bf6bf
--- /dev/null
+++ b/lib/src/widgets/icon_theme.dart
@@ -0,0 +1,90 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'icon_theme_data.dart';
+import 'inherited_theme.dart';
+
+/// Controls the default color, opacity, and size of icons in a widget subtree.
+///
+/// The icon theme is honored by [Icon] and [ImageIcon] widgets.
+class IconTheme extends InheritedTheme {
+  /// Creates an icon theme that controls the color, opacity, and size of
+  /// descendant widgets.
+  ///
+  /// Both [data] and [child] arguments must not be null.
+  const IconTheme({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(data != null),
+       assert(child != null),
+       super(key: key, child: child);
+
+  /// Creates an icon theme that controls the color, opacity, and size of
+  /// descendant widgets, and merges in the current icon theme, if any.
+  ///
+  /// The [data] and [child] arguments must not be null.
+  static Widget merge({
+    Key? key,
+    required IconThemeData data,
+    required Widget child,
+  }) {
+    return Builder(
+      builder: (BuildContext context) {
+        return IconTheme(
+          key: key,
+          data: _getInheritedIconThemeData(context).merge(data),
+          child: child,
+        );
+      },
+    );
+  }
+
+  /// The color, opacity, and size to use for icons in this subtree.
+  final IconThemeData data;
+
+  /// The data from the closest instance of this class that encloses the given
+  /// context.
+  ///
+  /// Defaults to the current [ThemeData.iconTheme].
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// IconThemeData theme = IconTheme.of(context);
+  /// ```
+  static IconThemeData of(BuildContext context) {
+    final IconThemeData iconThemeData = _getInheritedIconThemeData(context).resolve(context);
+    return iconThemeData.isConcrete
+      ? iconThemeData
+      : iconThemeData.copyWith(
+        size: iconThemeData.size ?? const IconThemeData.fallback().size,
+        color: iconThemeData.color ?? const IconThemeData.fallback().color,
+        opacity: iconThemeData.opacity ?? const IconThemeData.fallback().opacity,
+      );
+  }
+
+  static IconThemeData _getInheritedIconThemeData(BuildContext context) {
+    final IconTheme? iconTheme = context.dependOnInheritedWidgetOfExactType<IconTheme>();
+    return iconTheme?.data ?? const IconThemeData.fallback();
+  }
+
+  @override
+  bool updateShouldNotify(IconTheme oldWidget) => data != oldWidget.data;
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return IconTheme(data: data, child: child);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    data.debugFillProperties(properties);
+  }
+}
diff --git a/lib/src/widgets/icon_theme_data.dart b/lib/src/widgets/icon_theme_data.dart
new file mode 100644
index 0000000..2349b87
--- /dev/null
+++ b/lib/src/widgets/icon_theme_data.dart
@@ -0,0 +1,123 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show Color, hashValues;
+import 'package:flute/ui.dart' as ui show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart';
+
+import 'framework.dart' show BuildContext;
+
+/// Defines the color, opacity, and size of icons.
+///
+/// Used by [IconTheme] to control the color, opacity, and size of icons in a
+/// widget subtree.
+///
+/// To obtain the current icon theme, use [IconTheme.of]. To convert an icon
+/// theme to a version with all the fields filled in, use [new
+/// IconThemeData.fallback].
+@immutable
+class IconThemeData with Diagnosticable {
+  /// Creates an icon theme data.
+  ///
+  /// The opacity applies to both explicit and default icon colors. The value
+  /// is clamped between 0.0 and 1.0.
+  const IconThemeData({this.color, double? opacity, this.size}) : _opacity = opacity;
+
+  /// Creates an icon them with some reasonable default values.
+  ///
+  /// The [color] is black, the [opacity] is 1.0, and the [size] is 24.0.
+  const IconThemeData.fallback()
+    : color = const Color(0xFF000000),
+      _opacity = 1.0,
+      size = 24.0;
+
+  /// Creates a copy of this icon theme but with the given fields replaced with
+  /// the new values.
+  IconThemeData copyWith({ Color? color, double? opacity, double? size }) {
+    return IconThemeData(
+      color: color ?? this.color,
+      opacity: opacity ?? this.opacity,
+      size: size ?? this.size,
+    );
+  }
+
+  /// Returns a new icon theme that matches this icon theme but with some values
+  /// replaced by the non-null parameters of the given icon theme. If the given
+  /// icon theme is null, simply returns this icon theme.
+  IconThemeData merge(IconThemeData? other) {
+    if (other == null)
+      return this;
+    return copyWith(
+      color: other.color,
+      opacity: other.opacity,
+      size: other.size,
+    );
+  }
+
+  /// Called by [IconTheme.of] to convert this instance to an [IconThemeData]
+  /// that fits the given [BuildContext].
+  ///
+  /// This method gives the ambient [IconThemeData] a chance to update itself,
+  /// after it's been retrieved by [IconTheme.of], and before being returned as
+  /// the final result. For instance, [CupertinoIconThemeData] overrides this method
+  /// to resolve [color], in case [color] is a [CupertinoDynamicColor] and needs
+  /// to be resolved against the given [BuildContext] before it can be used as a
+  /// regular [Color].
+  ///
+  /// The default implementation returns this [IconThemeData] as-is.
+  ///
+  /// See also:
+  ///
+  ///  * [CupertinoIconThemeData.resolve] an implementation that resolves
+  ///    the color of [CupertinoIconThemeData] before returning.
+  IconThemeData resolve(BuildContext context) => this;
+
+  /// Whether all the properties of this object are non-null.
+  bool get isConcrete => color != null && opacity != null && size != null;
+
+  /// The default color for icons.
+  final Color? color;
+
+  /// An opacity to apply to both explicit and default icon colors.
+  double? get opacity => _opacity?.clamp(0.0, 1.0);
+  final double? _opacity;
+
+  /// The default size for icons.
+  final double? size;
+
+  /// Linearly interpolate between two icon theme data objects.
+  ///
+  /// {@macro dart.ui.shadow.lerp}
+  static IconThemeData lerp(IconThemeData? a, IconThemeData? b, double t) {
+    assert(t != null);
+    return IconThemeData(
+      color: Color.lerp(a?.color, b?.color, t),
+      opacity: ui.lerpDouble(a?.opacity, b?.opacity, t),
+      size: ui.lerpDouble(a?.size, b?.size, t),
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is IconThemeData
+        && other.color == color
+        && other.opacity == opacity
+        && other.size == size;
+  }
+
+  @override
+  int get hashCode => hashValues(color, opacity, size);
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(DoubleProperty('opacity', opacity, defaultValue: null));
+    properties.add(DoubleProperty('size', size, defaultValue: null));
+  }
+}
diff --git a/lib/src/widgets/image.dart b/lib/src/widgets/image.dart
new file mode 100644
index 0000000..e66511b
--- /dev/null
+++ b/lib/src/widgets/image.dart
@@ -0,0 +1,1322 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:io' show File;
+import 'dart:typed_data';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/services.dart';
+import 'package:flute/semantics.dart';
+
+import 'basic.dart';
+import 'binding.dart';
+import 'disposable_build_context.dart';
+import 'framework.dart';
+import 'localizations.dart';
+import 'media_query.dart';
+import 'scroll_aware_image_provider.dart';
+import 'ticker_provider.dart';
+
+export 'package:flute/painting.dart' show
+  AssetImage,
+  ExactAssetImage,
+  FileImage,
+  FilterQuality,
+  ImageConfiguration,
+  ImageInfo,
+  ImageStream,
+  ImageProvider,
+  MemoryImage,
+  NetworkImage;
+
+/// Creates an [ImageConfiguration] based on the given [BuildContext] (and
+/// optionally size).
+///
+/// This is the object that must be passed to [BoxPainter.paint] and to
+/// [ImageProvider.resolve].
+///
+/// If this is not called from a build method, then it should be reinvoked
+/// whenever the dependencies change, e.g. by calling it from
+/// [State.didChangeDependencies], so that any changes in the environment are
+/// picked up (e.g. if the device pixel ratio changes).
+///
+/// See also:
+///
+///  * [ImageProvider], which has an example showing how this might be used.
+ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size? size }) {
+  return ImageConfiguration(
+    bundle: DefaultAssetBundle.of(context),
+    devicePixelRatio: MediaQuery.maybeOf(context)?.devicePixelRatio ?? 1.0,
+    locale: Localizations.maybeLocaleOf(context),
+    textDirection: Directionality.maybeOf(context),
+    size: size,
+    platform: defaultTargetPlatform,
+  );
+}
+
+/// Prefetches an image into the image cache.
+///
+/// Returns a [Future] that will complete when the first image yielded by the
+/// [ImageProvider] is available or failed to load.
+///
+/// If the image is later used by an [Image] or [BoxDecoration] or [FadeInImage],
+/// it will probably be loaded faster. The consumer of the image does not need
+/// to use the same [ImageProvider] instance. The [ImageCache] will find the image
+/// as long as both images share the same key, and the image is held by the
+/// cache.
+///
+/// The cache may refuse to hold the image if it is disabled, the image is too
+/// large, or some other criteria implemented by a custom [ImageCache]
+/// implementation.
+///
+/// The [ImageCache] holds a reference to all images passed to
+/// [ImageCache.putIfAbsent] as long as their [ImageStreamCompleter] has at
+/// least one listener. This method will wait until the end of the frame after
+/// its future completes before releasing its own listener. This gives callers a
+/// chance to listen to the stream if necessary. A caller can determine if the
+/// image ended up in the cache by calling [ImageProvider.obtainCacheStatus]. If
+/// it is only held as [ImageCacheStatus.live], and the caller wishes to keep
+/// the resolved image in memory, the caller should immediately call
+/// `provider.resolve` and add a listener to the returned [ImageStream]. The
+/// image will remain pinned in memory at least until the caller removes its
+/// listener from the stream, even if it would not otherwise fit into the cache.
+///
+/// Callers should be cautious about pinning large images or a large number of
+/// images in memory, as this can result in running out of memory and being
+/// killed by the operating system. The lower the available physical memory, the
+/// more susceptible callers will be to running into OOM issues. These issues
+/// manifest as immediate process death, sometimes with no other error messages.
+///
+/// The [BuildContext] and [Size] are used to select an image configuration
+/// (see [createLocalImageConfiguration]).
+///
+/// The returned future will not complete with error, even if precaching
+/// failed. The `onError` argument can be used to manually handle errors while
+/// pre-caching.
+///
+/// See also:
+///
+///  * [ImageCache], which holds images that may be reused.
+Future<void> precacheImage(
+  ImageProvider provider,
+  BuildContext context, {
+  Size? size,
+  ImageErrorListener? onError,
+}) {
+  final ImageConfiguration config = createLocalImageConfiguration(context, size: size);
+  final Completer<void> completer = Completer<void>();
+  final ImageStream stream = provider.resolve(config);
+  ImageStreamListener? listener;
+  listener = ImageStreamListener(
+    (ImageInfo? image, bool sync) {
+      if (!completer.isCompleted) {
+        completer.complete();
+      }
+      // Give callers until at least the end of the frame to subscribe to the
+      // image stream.
+      // See ImageCache._liveImages
+      SchedulerBinding.instance!.addPostFrameCallback((Duration timeStamp) {
+        stream.removeListener(listener!);
+      });
+    },
+    onError: (Object exception, StackTrace? stackTrace) {
+      if (!completer.isCompleted) {
+        completer.complete();
+      }
+      stream.removeListener(listener!);
+      if (onError != null) {
+        onError(exception, stackTrace);
+      } else {
+        FlutterError.reportError(FlutterErrorDetails(
+          context: ErrorDescription('image failed to precache'),
+          library: 'image resource service',
+          exception: exception,
+          stack: stackTrace,
+          silent: true,
+        ));
+      }
+    },
+  );
+  stream.addListener(listener);
+  return completer.future;
+}
+
+/// Signature used by [Image.frameBuilder] to control the widget that will be
+/// used when an [Image] is built.
+///
+/// The `child` argument contains the default image widget and is guaranteed to
+/// be non-null. Typically, this builder will wrap the `child` widget in some
+/// way and return the wrapped widget. If this builder returns `child` directly,
+/// it will yield the same result as if [Image.frameBuilder] was null.
+///
+/// The `frame` argument specifies the index of the current image frame being
+/// rendered. It will be null before the first image frame is ready, and zero
+/// for the first image frame. For single-frame images, it will never be greater
+/// than zero. For multi-frame images (such as animated GIFs), it will increase
+/// by one every time a new image frame is shown (including when the image
+/// animates in a loop).
+///
+/// The `wasSynchronouslyLoaded` argument specifies whether the image was
+/// available synchronously (on the same
+/// [rendering pipeline frame](rendering/RendererBinding/drawFrame.html) as the
+/// `Image` widget itself was created) and thus able to be painted immediately.
+/// If this is false, then there was one or more rendering pipeline frames where
+/// the image wasn't yet available to be painted. For multi-frame images (such
+/// as animated GIFs), the value of this argument will be the same for all image
+/// frames. In other words, if the first image frame was available immediately,
+/// then this argument will be true for all image frames.
+///
+/// This builder must not return null.
+///
+/// See also:
+///
+///  * [Image.frameBuilder], which makes use of this signature in the [Image]
+///    widget.
+typedef ImageFrameBuilder = Widget Function(
+  BuildContext context,
+  Widget child,
+  int? frame,
+  bool wasSynchronouslyLoaded,
+);
+
+/// Signature used by [Image.loadingBuilder] to build a representation of the
+/// image's loading progress.
+///
+/// This is useful for images that are incrementally loaded (e.g. over a local
+/// file system or a network), and the application wishes to give the user an
+/// indication of when the image will be displayed.
+///
+/// The `child` argument contains the default image widget and is guaranteed to
+/// be non-null. Typically, this builder will wrap the `child` widget in some
+/// way and return the wrapped widget. If this builder returns `child` directly,
+/// it will yield the same result as if [Image.loadingBuilder] was null.
+///
+/// The `loadingProgress` argument contains the current progress towards loading
+/// the image. This argument will be non-null while the image is loading, but it
+/// will be null in the following cases:
+///
+///   * When the widget is first rendered before any bytes have been loaded.
+///   * When an image has been fully loaded and is available to be painted.
+///
+/// If callers are implementing custom [ImageProvider] and [ImageStream]
+/// instances (which is very rare), it's possible to produce image streams that
+/// continue to fire image chunk events after an image frame has been loaded.
+/// In such cases, the `child` parameter will represent the current
+/// fully-loaded image frame.
+///
+/// This builder must not return null.
+///
+/// See also:
+///
+///  * [Image.loadingBuilder], which makes use of this signature in the [Image]
+///    widget.
+///  * [ImageChunkListener], a lower-level signature for listening to raw
+///    [ImageChunkEvent]s.
+typedef ImageLoadingBuilder = Widget Function(
+  BuildContext context,
+  Widget child,
+  ImageChunkEvent? loadingProgress,
+);
+
+/// Signature used by [Image.errorBuilder] to create a replacement widget to
+/// render instead of the image.
+typedef ImageErrorWidgetBuilder = Widget Function(
+  BuildContext context,
+  Object error,
+  StackTrace? stackTrace,
+);
+
+/// A widget that displays an image.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=7oIAs-0G4mw}
+///
+/// Several constructors are provided for the various ways that an image can be
+/// specified:
+///
+///  * [new Image], for obtaining an image from an [ImageProvider].
+///  * [new Image.asset], for obtaining an image from an [AssetBundle]
+///    using a key.
+///  * [new Image.network], for obtaining an image from a URL.
+///  * [new Image.file], for obtaining an image from a [File].
+///  * [new Image.memory], for obtaining an image from a [Uint8List].
+///
+/// The following image formats are supported: {@macro flutter.dart:ui.imageFormats}
+///
+/// To automatically perform pixel-density-aware asset resolution, specify the
+/// image using an [AssetImage] and make sure that a [MaterialApp], [WidgetsApp],
+/// or [MediaQuery] widget exists above the [Image] widget in the widget tree.
+///
+/// The image is painted using [paintImage], which describes the meanings of the
+/// various fields on this class in more detail.
+///
+/// {@tool snippet}
+/// The default constructor can be used with any [ImageProvider], such as a
+/// [NetworkImage], to display an image from the internet.
+///
+/// ![An image of an owl displayed by the image widget](https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg)
+///
+/// ```dart
+/// const Image(
+///   image: NetworkImage('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg'),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+/// The [Image] Widget also provides several constructors to display different
+/// types of images for convenience. In this example, use the [Image.network]
+/// constructor to display an image from the internet.
+///
+/// ![An image of an owl displayed by the image widget using the shortcut constructor](https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg)
+///
+/// ```dart
+/// Image.network('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg')
+/// ```
+/// {@end-tool}
+///
+/// The [Image.asset], [Image.network], [Image.file], and [Image.memory]
+/// constructors allow a custom decode size to be specified through
+/// `cacheWidth` and `cacheHeight` parameters. The engine will decode the
+/// image to the specified size, which is primarily intended to reduce the
+/// memory usage of [ImageCache].
+///
+/// In the case where a network image is used on the Web platform, the
+/// `cacheWidth` and `cacheHeight` parameters are ignored as the Web engine
+/// delegates image decoding of network images to the Web, which does not support
+/// custom decode sizes.
+///
+/// See also:
+///
+///  * [Icon], which shows an image from a font.
+///  * [new Ink.image], which is the preferred way to show an image in a
+///    material application (especially if the image is in a [Material] and will
+///    have an [InkWell] on top of it).
+///  * [Image](dart-ui/Image-class.html), the class in the [dart:ui] library.
+///  * Cookbook: [Display images from the internet](https://flutter.dev/docs/cookbook/images/network-image)
+///  * Cookbook: [Work with cached images](https://flutter.dev/docs/cookbook/images/cached-images)
+///  * Cookbook: [Fade in images with a placeholder](https://flutter.dev/docs/cookbook/images/fading-in-images)
+class Image extends StatefulWidget {
+  /// Creates a widget that displays an image.
+  ///
+  /// To show an image from the network or from an asset bundle, consider using
+  /// [new Image.network] and [new Image.asset] respectively.
+  ///
+  /// The [image], [alignment], [repeat], and [matchTextDirection] arguments
+  /// must not be null.
+  ///
+  /// Either the [width] and [height] arguments should be specified, or the
+  /// widget should be placed in a context that sets tight layout constraints.
+  /// Otherwise, the image dimensions will change as the image is loaded, which
+  /// will result in ugly layout changes.
+  ///
+  /// Use [filterQuality] to change the quality when scaling an image.
+  /// Use the [FilterQuality.low] quality setting to scale the image,
+  /// which corresponds to bilinear interpolation, rather than the default
+  /// [FilterQuality.none] which corresponds to nearest-neighbor.
+  ///
+  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
+  const Image({
+    Key? key,
+    required this.image,
+    this.frameBuilder,
+    this.loadingBuilder,
+    this.errorBuilder,
+    this.semanticLabel,
+    this.excludeFromSemantics = false,
+    this.width,
+    this.height,
+    this.color,
+    this.colorBlendMode,
+    this.fit,
+    this.alignment = Alignment.center,
+    this.repeat = ImageRepeat.noRepeat,
+    this.centerSlice,
+    this.matchTextDirection = false,
+    this.gaplessPlayback = false,
+    this.isAntiAlias = false,
+    this.filterQuality = FilterQuality.low,
+  }) : assert(image != null),
+       assert(alignment != null),
+       assert(repeat != null),
+       assert(filterQuality != null),
+       assert(matchTextDirection != null),
+       assert(isAntiAlias != null),
+       super(key: key);
+
+  /// Creates a widget that displays an [ImageStream] obtained from the network.
+  ///
+  /// The [src], [scale], and [repeat] arguments must not be null.
+  ///
+  /// Either the [width] and [height] arguments should be specified, or the
+  /// widget should be placed in a context that sets tight layout constraints.
+  /// Otherwise, the image dimensions will change as the image is loaded, which
+  /// will result in ugly layout changes.
+  ///
+  /// All network images are cached regardless of HTTP headers.
+  ///
+  /// An optional [headers] argument can be used to send custom HTTP headers
+  /// with the image request.
+  ///
+  /// Use [filterQuality] to change the quality when scaling an image.
+  /// Use the [FilterQuality.low] quality setting to scale the image,
+  /// which corresponds to bilinear interpolation, rather than the default
+  /// [FilterQuality.none] which corresponds to nearest-neighbor.
+  ///
+  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
+  ///
+  /// If [cacheWidth] or [cacheHeight] are provided, it indicates to the
+  /// engine that the image should be decoded at the specified size. The image
+  /// will be rendered to the constraints of the layout or [width] and [height]
+  /// regardless of these parameters. These parameters are primarily intended
+  /// to reduce the memory usage of [ImageCache].
+  ///
+  /// In the case where the network image is on the Web platform, the [cacheWidth]
+  /// and [cacheHeight] parameters are ignored as the web engine delegates
+  /// image decoding to the web which does not support custom decode sizes.
+  //
+  // TODO(garyq): We should eventually support custom decoding of network images
+  // on Web as well, see https://github.com/flutter/flutter/issues/42789.
+  Image.network(
+    String src, {
+    Key? key,
+    double scale = 1.0,
+    this.frameBuilder,
+    this.loadingBuilder,
+    this.errorBuilder,
+    this.semanticLabel,
+    this.excludeFromSemantics = false,
+    this.width,
+    this.height,
+    this.color,
+    this.colorBlendMode,
+    this.fit,
+    this.alignment = Alignment.center,
+    this.repeat = ImageRepeat.noRepeat,
+    this.centerSlice,
+    this.matchTextDirection = false,
+    this.gaplessPlayback = false,
+    this.filterQuality = FilterQuality.low,
+    this.isAntiAlias = false,
+    Map<String, String>? headers,
+    int? cacheWidth,
+    int? cacheHeight,
+  }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, NetworkImage(src, scale: scale, headers: headers)),
+       assert(alignment != null),
+       assert(repeat != null),
+       assert(matchTextDirection != null),
+       assert(cacheWidth == null || cacheWidth > 0),
+       assert(cacheHeight == null || cacheHeight > 0),
+       assert(isAntiAlias != null),
+       super(key: key);
+
+  /// Creates a widget that displays an [ImageStream] obtained from a [File].
+  ///
+  /// The [file], [scale], and [repeat] arguments must not be null.
+  ///
+  /// Either the [width] and [height] arguments should be specified, or the
+  /// widget should be placed in a context that sets tight layout constraints.
+  /// Otherwise, the image dimensions will change as the image is loaded, which
+  /// will result in ugly layout changes.
+  ///
+  /// On Android, this may require the
+  /// `android.permission.READ_EXTERNAL_STORAGE` permission.
+  ///
+  /// Use [filterQuality] to change the quality when scaling an image.
+  /// Use the [FilterQuality.low] quality setting to scale the image,
+  /// which corresponds to bilinear interpolation, rather than the default
+  /// [FilterQuality.none] which corresponds to nearest-neighbor.
+  ///
+  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
+  ///
+  /// If [cacheWidth] or [cacheHeight] are provided, it indicates to the
+  /// engine that the image must be decoded at the specified size. The image
+  /// will be rendered to the constraints of the layout or [width] and [height]
+  /// regardless of these parameters. These parameters are primarily intended
+  /// to reduce the memory usage of [ImageCache].
+  ///
+  /// Loading an image from a file creates an in memory copy of the file,
+  /// which is retained in the [ImageCache]. The underlying file is not
+  /// monitored for changes. If it does change, the application should evict
+  /// the entry from the [ImageCache].
+  ///
+  /// See also:
+  ///
+  ///  * [FileImage] provider for evicting the underlying file easily.
+  Image.file(
+    File file, {
+    Key? key,
+    double scale = 1.0,
+    this.frameBuilder,
+    this.errorBuilder,
+    this.semanticLabel,
+    this.excludeFromSemantics = false,
+    this.width,
+    this.height,
+    this.color,
+    this.colorBlendMode,
+    this.fit,
+    this.alignment = Alignment.center,
+    this.repeat = ImageRepeat.noRepeat,
+    this.centerSlice,
+    this.matchTextDirection = false,
+    this.gaplessPlayback = false,
+    this.isAntiAlias = false,
+    this.filterQuality = FilterQuality.low,
+    int? cacheWidth,
+    int? cacheHeight,
+  }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, FileImage(file, scale: scale)),
+       loadingBuilder = null,
+       assert(alignment != null),
+       assert(repeat != null),
+       assert(filterQuality != null),
+       assert(matchTextDirection != null),
+       assert(cacheWidth == null || cacheWidth > 0),
+       assert(cacheHeight == null || cacheHeight > 0),
+       assert(isAntiAlias != null),
+       super(key: key);
+
+
+  // TODO(ianh): Implement the following (see ../services/image_resolution.dart):
+  //
+  // * If [width] and [height] are both specified, and [scale] is not, then
+  //   size-aware asset resolution will be attempted also, with the given
+  //   dimensions interpreted as logical pixels.
+  //
+  // * If the images have platform, locale, or directionality variants, the
+  //   current platform, locale, and directionality are taken into account
+  //   during asset resolution as well.
+  /// Creates a widget that displays an [ImageStream] obtained from an asset
+  /// bundle. The key for the image is given by the `name` argument.
+  ///
+  /// The `package` argument must be non-null when displaying an image from a
+  /// package and null otherwise. See the `Assets in packages` section for
+  /// details.
+  ///
+  /// If the `bundle` argument is omitted or null, then the
+  /// [DefaultAssetBundle] will be used.
+  ///
+  /// By default, the pixel-density-aware asset resolution will be attempted. In
+  /// addition:
+  ///
+  /// * If the `scale` argument is provided and is not null, then the exact
+  /// asset specified will be used. To display an image variant with a specific
+  /// density, the exact path must be provided (e.g. `images/2x/cat.png`).
+  ///
+  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
+  ///
+  /// If [cacheWidth] or [cacheHeight] are provided, it indicates to the
+  /// engine that the image must be decoded at the specified size. The image
+  /// will be rendered to the constraints of the layout or [width] and [height]
+  /// regardless of these parameters. These parameters are primarily intended
+  /// to reduce the memory usage of [ImageCache].
+  ///
+  /// The [name] and [repeat] arguments must not be null.
+  ///
+  /// Either the [width] and [height] arguments should be specified, or the
+  /// widget should be placed in a context that sets tight layout constraints.
+  /// Otherwise, the image dimensions will change as the image is loaded, which
+  /// will result in ugly layout changes.
+  ///
+  /// Use [filterQuality] to change the quality when scaling an image.
+  /// Use the [FilterQuality.low] quality setting to scale the image,
+  /// which corresponds to bilinear interpolation, rather than the default
+  /// [FilterQuality.none] which corresponds to nearest-neighbor.
+  ///
+  /// {@tool snippet}
+  ///
+  /// Suppose that the project's `pubspec.yaml` file contains the following:
+  ///
+  /// ```yaml
+  /// flutter:
+  ///   assets:
+  ///     - images/cat.png
+  ///     - images/2x/cat.png
+  ///     - images/3.5x/cat.png
+  /// ```
+  /// {@end-tool}
+  ///
+  /// On a screen with a device pixel ratio of 2.0, the following widget would
+  /// render the `images/2x/cat.png` file:
+  ///
+  /// ```dart
+  /// Image.asset('images/cat.png')
+  /// ```
+  ///
+  /// This corresponds to the file that is in the project's `images/2x/`
+  /// directory with the name `cat.png` (the paths are relative to the
+  /// `pubspec.yaml` file).
+  ///
+  /// On a device with a 4.0 device pixel ratio, the `images/3.5x/cat.png` asset
+  /// would be used. On a device with a 1.0 device pixel ratio, the
+  /// `images/cat.png` resource would be used.
+  ///
+  /// The `images/cat.png` image can be omitted from disk (though it must still
+  /// be present in the manifest). If it is omitted, then on a device with a 1.0
+  /// device pixel ratio, the `images/2x/cat.png` image would be used instead.
+  ///
+  ///
+  /// ## Assets in packages
+  ///
+  /// To create the widget with an asset from a package, the [package] argument
+  /// must be provided. For instance, suppose a package called `my_icons` has
+  /// `icons/heart.png` .
+  ///
+  /// {@tool snippet}
+  /// Then to display the image, use:
+  ///
+  /// ```dart
+  /// Image.asset('icons/heart.png', package: 'my_icons')
+  /// ```
+  /// {@end-tool}
+  ///
+  /// Assets used by the package itself should also be displayed using the
+  /// [package] argument as above.
+  ///
+  /// If the desired asset is specified in the `pubspec.yaml` of the package, it
+  /// is bundled automatically with the app. In particular, assets used by the
+  /// package itself must be specified in its `pubspec.yaml`.
+  ///
+  /// A package can also choose to have assets in its 'lib/' folder that are not
+  /// specified in its `pubspec.yaml`. In this case for those images to be
+  /// bundled, the app has to specify which ones to include. For instance a
+  /// package named `fancy_backgrounds` could have:
+  ///
+  /// ```
+  /// lib/backgrounds/background1.png
+  /// lib/backgrounds/background2.png
+  /// lib/backgrounds/background3.png
+  /// ```
+  ///
+  /// To include, say the first image, the `pubspec.yaml` of the app should
+  /// specify it in the assets section:
+  ///
+  /// ```yaml
+  ///   assets:
+  ///     - packages/fancy_backgrounds/backgrounds/background1.png
+  /// ```
+  ///
+  /// The `lib/` is implied, so it should not be included in the asset path.
+  ///
+  ///
+  /// See also:
+  ///
+  ///  * [AssetImage], which is used to implement the behavior when the scale is
+  ///    omitted.
+  ///  * [ExactAssetImage], which is used to implement the behavior when the
+  ///    scale is present.
+  ///  * <https://flutter.dev/assets-and-images/>, an introduction to assets in
+  ///    Flutter.
+  Image.asset(
+    String name, {
+    Key? key,
+    AssetBundle? bundle,
+    this.frameBuilder,
+    this.errorBuilder,
+    this.semanticLabel,
+    this.excludeFromSemantics = false,
+    double? scale,
+    this.width,
+    this.height,
+    this.color,
+    this.colorBlendMode,
+    this.fit,
+    this.alignment = Alignment.center,
+    this.repeat = ImageRepeat.noRepeat,
+    this.centerSlice,
+    this.matchTextDirection = false,
+    this.gaplessPlayback = false,
+    this.isAntiAlias = false,
+    String? package,
+    this.filterQuality = FilterQuality.low,
+    int? cacheWidth,
+    int? cacheHeight,
+  }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, scale != null
+         ? ExactAssetImage(name, bundle: bundle, scale: scale, package: package)
+         : AssetImage(name, bundle: bundle, package: package)
+       ),
+       loadingBuilder = null,
+       assert(alignment != null),
+       assert(repeat != null),
+       assert(matchTextDirection != null),
+       assert(cacheWidth == null || cacheWidth > 0),
+       assert(cacheHeight == null || cacheHeight > 0),
+       assert(isAntiAlias != null),
+       super(key: key);
+
+  /// Creates a widget that displays an [ImageStream] obtained from a [Uint8List].
+  ///
+  /// The [bytes], [scale], and [repeat] arguments must not be null.
+  ///
+  /// This only accepts compressed image formats (e.g. PNG). Uncompressed
+  /// formats like rawRgba (the default format of [dart:ui.Image.toByteData])
+  /// will lead to exceptions.
+  ///
+  /// Either the [width] and [height] arguments should be specified, or the
+  /// widget should be placed in a context that sets tight layout constraints.
+  /// Otherwise, the image dimensions will change as the image is loaded, which
+  /// will result in ugly layout changes.
+  ///
+  /// Use [filterQuality] to change the quality when scaling an image.
+  /// Use the [FilterQuality.low] quality setting to scale the image,
+  /// which corresponds to bilinear interpolation, rather than the default
+  /// [FilterQuality.none] which corresponds to nearest-neighbor.
+  ///
+  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
+  ///
+  /// If [cacheWidth] or [cacheHeight] are provided, it indicates to the
+  /// engine that the image must be decoded at the specified size. The image
+  /// will be rendered to the constraints of the layout or [width] and [height]
+  /// regardless of these parameters. These parameters are primarily intended
+  /// to reduce the memory usage of [ImageCache].
+  Image.memory(
+    Uint8List bytes, {
+    Key? key,
+    double scale = 1.0,
+    this.frameBuilder,
+    this.errorBuilder,
+    this.semanticLabel,
+    this.excludeFromSemantics = false,
+    this.width,
+    this.height,
+    this.color,
+    this.colorBlendMode,
+    this.fit,
+    this.alignment = Alignment.center,
+    this.repeat = ImageRepeat.noRepeat,
+    this.centerSlice,
+    this.matchTextDirection = false,
+    this.gaplessPlayback = false,
+    this.isAntiAlias = false,
+    this.filterQuality = FilterQuality.low,
+    int? cacheWidth,
+    int? cacheHeight,
+  }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, MemoryImage(bytes, scale: scale)),
+       loadingBuilder = null,
+       assert(alignment != null),
+       assert(repeat != null),
+       assert(matchTextDirection != null),
+       assert(cacheWidth == null || cacheWidth > 0),
+       assert(cacheHeight == null || cacheHeight > 0),
+       assert(isAntiAlias != null),
+       super(key: key);
+
+  /// The image to display.
+  final ImageProvider image;
+
+  /// A builder function responsible for creating the widget that represents
+  /// this image.
+  ///
+  /// If this is null, this widget will display an image that is painted as
+  /// soon as the first image frame is available (and will appear to "pop" in
+  /// if it becomes available asynchronously). Callers might use this builder to
+  /// add effects to the image (such as fading the image in when it becomes
+  /// available) or to display a placeholder widget while the image is loading.
+  ///
+  /// To have finer-grained control over the way that an image's loading
+  /// progress is communicated to the user, see [loadingBuilder].
+  ///
+  /// ## Chaining with [loadingBuilder]
+  ///
+  /// If a [loadingBuilder] has _also_ been specified for an image, the two
+  /// builders will be chained together: the _result_ of this builder will
+  /// be passed as the `child` argument to the [loadingBuilder]. For example,
+  /// consider the following builders used in conjunction:
+  ///
+  /// {@template flutter.widgets.Image.frameBuilder.chainedBuildersExample}
+  /// ```dart
+  /// Image(
+  ///   ...
+  ///   frameBuilder: (BuildContext context, Widget child, int frame, bool wasSynchronouslyLoaded) {
+  ///     return Padding(
+  ///       padding: EdgeInsets.all(8.0),
+  ///       child: child,
+  ///     );
+  ///   },
+  ///   loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent loadingProgress) {
+  ///     return Center(child: child);
+  ///   },
+  /// )
+  /// ```
+  ///
+  /// In this example, the widget hierarchy will contain the following:
+  ///
+  /// ```dart
+  /// Center(
+  ///   Padding(
+  ///     padding: EdgeInsets.all(8.0),
+  ///     child: <image>,
+  ///   ),
+  /// )
+  /// ```
+  /// {@endtemplate}
+  ///
+  /// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+  ///
+  /// The following sample demonstrates how to use this builder to implement an
+  /// image that fades in once it's been loaded.
+  ///
+  /// This sample contains a limited subset of the functionality that the
+  /// [FadeInImage] widget provides out of the box.
+  ///
+  /// ```dart
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return DecoratedBox(
+  ///     decoration: BoxDecoration(
+  ///       color: Colors.white,
+  ///       border: Border.all(),
+  ///       borderRadius: BorderRadius.circular(20),
+  ///     ),
+  ///     child: Image.network(
+  ///       'https://flutter.github.io/assets-for-api-docs/assets/widgets/puffin.jpg',
+  ///       frameBuilder: (BuildContext context, Widget child, int frame, bool wasSynchronouslyLoaded) {
+  ///         if (wasSynchronouslyLoaded) {
+  ///           return child;
+  ///         }
+  ///         return AnimatedOpacity(
+  ///           child: child,
+  ///           opacity: frame == null ? 0 : 1,
+  ///           duration: const Duration(seconds: 1),
+  ///           curve: Curves.easeOut,
+  ///         );
+  ///       },
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  final ImageFrameBuilder? frameBuilder;
+
+  /// A builder that specifies the widget to display to the user while an image
+  /// is still loading.
+  ///
+  /// If this is null, and the image is loaded incrementally (e.g. over a
+  /// network), the user will receive no indication of the progress as the
+  /// bytes of the image are loaded.
+  ///
+  /// For more information on how to interpret the arguments that are passed to
+  /// this builder, see the documentation on [ImageLoadingBuilder].
+  ///
+  /// ## Performance implications
+  ///
+  /// If a [loadingBuilder] is specified for an image, the [Image] widget is
+  /// likely to be rebuilt on every
+  /// [rendering pipeline frame](rendering/RendererBinding/drawFrame.html) until
+  /// the image has loaded. This is useful for cases such as displaying a loading
+  /// progress indicator, but for simpler cases such as displaying a placeholder
+  /// widget that doesn't depend on the loading progress (e.g. static "loading"
+  /// text), [frameBuilder] will likely work and not incur as much cost.
+  ///
+  /// ## Chaining with [frameBuilder]
+  ///
+  /// If a [frameBuilder] has _also_ been specified for an image, the two
+  /// builders will be chained together: the `child` argument to this
+  /// builder will contain the _result_ of the [frameBuilder]. For example,
+  /// consider the following builders used in conjunction:
+  ///
+  /// {@macro flutter.widgets.Image.frameBuilder.chainedBuildersExample}
+  ///
+  /// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+  ///
+  /// The following sample uses [loadingBuilder] to show a
+  /// [CircularProgressIndicator] while an image loads over the network.
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return DecoratedBox(
+  ///     decoration: BoxDecoration(
+  ///       color: Colors.white,
+  ///       border: Border.all(),
+  ///       borderRadius: BorderRadius.circular(20),
+  ///     ),
+  ///     child: Image.network(
+  ///       'https://example.com/image.jpg',
+  ///       loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent loadingProgress) {
+  ///         if (loadingProgress == null)
+  ///           return child;
+  ///         return Center(
+  ///           child: CircularProgressIndicator(
+  ///             value: loadingProgress.expectedTotalBytes != null
+  ///                 ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes
+  ///                 : null,
+  ///           ),
+  ///         );
+  ///       },
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// Run against a real-world image on a slow network, the previous example
+  /// renders the following loading progress indicator while the image loads
+  /// before rendering the completed image.
+  ///
+  /// {@animation 400 400 https://flutter.github.io/assets-for-api-docs/assets/widgets/loading_progress_image.mp4}
+  final ImageLoadingBuilder? loadingBuilder;
+
+  /// A builder function that is called if an error occurs during image loading.
+  ///
+  /// If this builder is not provided, any exceptions will be reported to
+  /// [FlutterError.onError]. If it is provided, the caller should either handle
+  /// the exception by providing a replacement widget, or rethrow the exception.
+  ///
+  /// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+  ///
+  /// The following sample uses [errorBuilder] to show a '😢' in place of the
+  /// image that fails to load, and prints the error to the console.
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return DecoratedBox(
+  ///     decoration: BoxDecoration(
+  ///       color: Colors.white,
+  ///       border: Border.all(),
+  ///       borderRadius: BorderRadius.circular(20),
+  ///     ),
+  ///     child: Image.network(
+  ///       'https://example.does.not.exist/image.jpg',
+  ///       errorBuilder: (BuildContext context, Object exception, StackTrace stackTrace) {
+  ///         // Appropriate logging or analytics, e.g.
+  ///         // myAnalytics.recordError(
+  ///         //   'An error occurred loading "https://example.does.not.exist/image.jpg"',
+  ///         //   exception,
+  ///         //   stackTrace,
+  ///         // );
+  ///         return Text('😢');
+  ///       },
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  final ImageErrorWidgetBuilder? errorBuilder;
+
+  /// If non-null, require the image to have this width.
+  ///
+  /// If null, the image will pick a size that best preserves its intrinsic
+  /// aspect ratio.
+  ///
+  /// It is strongly recommended that either both the [width] and the [height]
+  /// be specified, or that the widget be placed in a context that sets tight
+  /// layout constraints, so that the image does not change size as it loads.
+  /// Consider using [fit] to adapt the image's rendering to fit the given width
+  /// and height if the exact image dimensions are not known in advance.
+  final double? width;
+
+  /// If non-null, require the image to have this height.
+  ///
+  /// If null, the image will pick a size that best preserves its intrinsic
+  /// aspect ratio.
+  ///
+  /// It is strongly recommended that either both the [width] and the [height]
+  /// be specified, or that the widget be placed in a context that sets tight
+  /// layout constraints, so that the image does not change size as it loads.
+  /// Consider using [fit] to adapt the image's rendering to fit the given width
+  /// and height if the exact image dimensions are not known in advance.
+  final double? height;
+
+  /// If non-null, this color is blended with each image pixel using [colorBlendMode].
+  final Color? color;
+
+  /// Used to set the [FilterQuality] of the image.
+  ///
+  /// Use the [FilterQuality.low] quality setting to scale the image with
+  /// bilinear interpolation, or the [FilterQuality.none] which corresponds
+  /// to nearest-neighbor.
+  final FilterQuality filterQuality;
+
+  /// Used to combine [color] with this image.
+  ///
+  /// The default is [BlendMode.srcIn]. In terms of the blend mode, [color] is
+  /// the source and this image is the destination.
+  ///
+  /// See also:
+  ///
+  ///  * [BlendMode], which includes an illustration of the effect of each blend mode.
+  final BlendMode? colorBlendMode;
+
+  /// How to inscribe the image into the space allocated during layout.
+  ///
+  /// The default varies based on the other fields. See the discussion at
+  /// [paintImage].
+  final BoxFit? fit;
+
+  /// How to align the image within its bounds.
+  ///
+  /// The alignment aligns the given position in the image to the given position
+  /// in the layout bounds. For example, an [Alignment] alignment of (-1.0,
+  /// -1.0) aligns the image to the top-left corner of its layout bounds, while an
+  /// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
+  /// image with the bottom right corner of its layout bounds. Similarly, an
+  /// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
+  /// middle of the bottom edge of its layout bounds.
+  ///
+  /// To display a subpart of an image, consider using a [CustomPainter] and
+  /// [Canvas.drawImageRect].
+  ///
+  /// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
+  /// [AlignmentDirectional]), then an ambient [Directionality] widget
+  /// must be in scope.
+  ///
+  /// Defaults to [Alignment.center].
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], a class with convenient constants typically used to
+  ///    specify an [AlignmentGeometry].
+  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
+  ///    relative to text direction.
+  final AlignmentGeometry alignment;
+
+  /// How to paint any portions of the layout bounds not covered by the image.
+  final ImageRepeat repeat;
+
+  /// The center slice for a nine-patch image.
+  ///
+  /// The region of the image inside the center slice will be stretched both
+  /// horizontally and vertically to fit the image into its destination. The
+  /// region of the image above and below the center slice will be stretched
+  /// only horizontally and the region of the image to the left and right of
+  /// the center slice will be stretched only vertically.
+  final Rect? centerSlice;
+
+  /// Whether to paint the image in the direction of the [TextDirection].
+  ///
+  /// If this is true, then in [TextDirection.ltr] contexts, the image will be
+  /// drawn with its origin in the top left (the "normal" painting direction for
+  /// images); and in [TextDirection.rtl] contexts, the image will be drawn with
+  /// a scaling factor of -1 in the horizontal direction so that the origin is
+  /// in the top right.
+  ///
+  /// This is occasionally used with images in right-to-left environments, for
+  /// images that were designed for left-to-right locales. Be careful, when
+  /// using this, to not flip images with integral shadows, text, or other
+  /// effects that will look incorrect when flipped.
+  ///
+  /// If this is true, there must be an ambient [Directionality] widget in
+  /// scope.
+  final bool matchTextDirection;
+
+  /// Whether to continue showing the old image (true), or briefly show nothing
+  /// (false), when the image provider changes. The default value is false.
+  ///
+  /// ## Design discussion
+  ///
+  /// ### Why is the default value of [gaplessPlayback] false?
+  ///
+  /// Having the default value of [gaplessPlayback] be false helps prevent
+  /// situations where stale or misleading information might be presented.
+  /// Consider the following case:
+  ///
+  /// We have constructed a 'Person' widget that displays an avatar [Image] of
+  /// the currently loaded person along with their name. We could request for a
+  /// new person to be loaded into the widget at any time. Suppose we have a
+  /// person currently loaded and the widget loads a new person. What happens
+  /// if the [Image] fails to load?
+  ///
+  /// * Option A ([gaplessPlayback] = false): The new person's name is coupled
+  /// with a blank image.
+  ///
+  /// * Option B ([gaplessPlayback] = true): The widget displays the avatar of
+  /// the previous person and the name of the newly loaded person.
+  ///
+  /// This is why the default value is false. Most of the time, when you change
+  /// the image provider you're not just changing the image, you're removing the
+  /// old widget and adding a new one and not expecting them to have any
+  /// relationship. With [gaplessPlayback] on you might accidentally break this
+  /// expectation and re-use the old widget.
+  final bool gaplessPlayback;
+
+  /// A Semantic description of the image.
+  ///
+  /// Used to provide a description of the image to TalkBack on Android, and
+  /// VoiceOver on iOS.
+  final String? semanticLabel;
+
+  /// Whether to exclude this image from semantics.
+  ///
+  /// Useful for images which do not contribute meaningful information to an
+  /// application.
+  final bool excludeFromSemantics;
+
+  /// Whether to paint the image with anti-aliasing.
+  ///
+  /// Anti-aliasing alleviates the sawtooth artifact when the image is rotated.
+  final bool isAntiAlias;
+
+  @override
+  _ImageState createState() => _ImageState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ImageProvider>('image', image));
+    properties.add(DiagnosticsProperty<Function>('frameBuilder', frameBuilder));
+    properties.add(DiagnosticsProperty<Function>('loadingBuilder', loadingBuilder));
+    properties.add(DoubleProperty('width', width, defaultValue: null));
+    properties.add(DoubleProperty('height', height, defaultValue: null));
+    properties.add(ColorProperty('color', color, defaultValue: null));
+    properties.add(EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null));
+    properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null));
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
+    properties.add(EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
+    properties.add(DiagnosticsProperty<Rect>('centerSlice', centerSlice, defaultValue: null));
+    properties.add(FlagProperty('matchTextDirection', value: matchTextDirection, ifTrue: 'match text direction'));
+    properties.add(StringProperty('semanticLabel', semanticLabel, defaultValue: null));
+    properties.add(DiagnosticsProperty<bool>('this.excludeFromSemantics', excludeFromSemantics));
+    properties.add(EnumProperty<FilterQuality>('filterQuality', filterQuality));
+  }
+}
+
+class _ImageState extends State<Image> with WidgetsBindingObserver {
+  ImageStream? _imageStream;
+  ImageInfo? _imageInfo;
+  ImageChunkEvent? _loadingProgress;
+  bool _isListeningToStream = false;
+  late bool _invertColors;
+  int? _frameNumber;
+  bool _wasSynchronouslyLoaded = false;
+  late DisposableBuildContext<State<Image>> _scrollAwareContext;
+  Object? _lastException;
+  StackTrace? _lastStack;
+  ImageStreamCompleterHandle? _completerHandle;
+
+  @override
+  void initState() {
+    super.initState();
+    WidgetsBinding.instance!.addObserver(this);
+    _scrollAwareContext = DisposableBuildContext<State<Image>>(this);
+  }
+
+  @override
+  void dispose() {
+    assert(_imageStream != null);
+    WidgetsBinding.instance!.removeObserver(this);
+    _stopListeningToStream();
+    _completerHandle?.dispose();
+    _scrollAwareContext.dispose();
+    _replaceImage(info: null);
+    super.dispose();
+  }
+
+  @override
+  void didChangeDependencies() {
+    _updateInvertColors();
+    _resolveImage();
+
+    if (TickerMode.of(context))
+      _listenToStream();
+    else
+      _stopListeningToStream(keepStreamAlive: true);
+
+    super.didChangeDependencies();
+  }
+
+  @override
+  void didUpdateWidget(Image oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (_isListeningToStream &&
+        (widget.loadingBuilder == null) != (oldWidget.loadingBuilder == null)) {
+      final ImageStreamListener oldListener = _getListener();
+      _imageStream!.addListener(_getListener(recreateListener: true));
+      _imageStream!.removeListener(oldListener);
+    }
+    if (widget.image != oldWidget.image)
+      _resolveImage();
+  }
+
+  @override
+  void didChangeAccessibilityFeatures() {
+    super.didChangeAccessibilityFeatures();
+    setState(() {
+      _updateInvertColors();
+    });
+  }
+
+  @override
+  void reassemble() {
+    _resolveImage(); // in case the image cache was flushed
+    super.reassemble();
+  }
+
+  void _updateInvertColors() {
+    _invertColors = MediaQuery.maybeOf(context)?.invertColors
+        ?? SemanticsBinding.instance!.accessibilityFeatures.invertColors;
+  }
+
+  void _resolveImage() {
+    final ScrollAwareImageProvider provider = ScrollAwareImageProvider<Object>(
+      context: _scrollAwareContext,
+      imageProvider: widget.image,
+    );
+    final ImageStream newStream =
+      provider.resolve(createLocalImageConfiguration(
+        context,
+        size: widget.width != null && widget.height != null ? Size(widget.width!, widget.height!) : null,
+      ));
+    assert(newStream != null);
+    _updateSourceStream(newStream);
+  }
+
+  ImageStreamListener? _imageStreamListener;
+  ImageStreamListener _getListener({bool recreateListener = false}) {
+    if(_imageStreamListener == null || recreateListener) {
+      _lastException = null;
+      _lastStack = null;
+      _imageStreamListener = ImageStreamListener(
+        _handleImageFrame,
+        onChunk: widget.loadingBuilder == null ? null : _handleImageChunk,
+        onError: widget.errorBuilder != null
+            ? (dynamic error, StackTrace? stackTrace) {
+                setState(() {
+                  _lastException = error;
+                  _lastStack = stackTrace;
+                });
+              }
+            : null,
+      );
+    }
+    return _imageStreamListener!;
+  }
+
+  void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) {
+    setState(() {
+      _replaceImage(info: imageInfo);
+      _loadingProgress = null;
+      _lastException = null;
+      _lastStack = null;
+      _frameNumber = _frameNumber == null ? 0 : _frameNumber! + 1;
+      _wasSynchronouslyLoaded = _wasSynchronouslyLoaded | synchronousCall;
+    });
+  }
+
+  void _handleImageChunk(ImageChunkEvent event) {
+    assert(widget.loadingBuilder != null);
+    setState(() {
+      _loadingProgress = event;
+      _lastException = null;
+      _lastStack = null;
+    });
+  }
+
+  void _replaceImage({required ImageInfo? info}) {
+    _imageInfo?.dispose();
+    _imageInfo = info;
+  }
+
+  // Updates _imageStream to newStream, and moves the stream listener
+  // registration from the old stream to the new stream (if a listener was
+  // registered).
+  void _updateSourceStream(ImageStream newStream) {
+    if (_imageStream?.key == newStream.key)
+      return;
+
+    if (_isListeningToStream)
+      _imageStream!.removeListener(_getListener());
+
+    if (!widget.gaplessPlayback)
+      setState(() { _replaceImage(info: null); });
+
+    setState(() {
+      _loadingProgress = null;
+      _frameNumber = null;
+      _wasSynchronouslyLoaded = false;
+    });
+
+    _imageStream = newStream;
+    if (_isListeningToStream)
+      _imageStream!.addListener(_getListener());
+  }
+
+  void _listenToStream() {
+    if (_isListeningToStream)
+      return;
+
+    _imageStream!.addListener(_getListener());
+    _completerHandle?.dispose();
+    _completerHandle = null;
+
+    _isListeningToStream = true;
+  }
+
+  /// Stops listening to the image stream, if this state object has attached a
+  /// listener.
+  ///
+  /// If the listener from this state is the last listener on the stream, the
+  /// stream will be disposed. To keep the stream alive, set `keepStreamAlive`
+  /// to true, which create [ImageStreamCompleterHandle] to keep the completer
+  /// alive and is compatible with the [TickerMode] being off.
+  void _stopListeningToStream({bool keepStreamAlive = false}) {
+    if (!_isListeningToStream)
+      return;
+
+    if (keepStreamAlive && _completerHandle == null && _imageStream?.completer != null) {
+      _completerHandle = _imageStream!.completer!.keepAlive();
+    }
+
+    _imageStream!.removeListener(_getListener());
+    _isListeningToStream = false;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (_lastException  != null) {
+      assert(widget.errorBuilder != null);
+      return widget.errorBuilder!(context, _lastException!, _lastStack);
+    }
+
+    Widget result = RawImage(
+      // Do not clone the image, because RawImage is a stateless wrapper.
+      // The image will be disposed by this state object when it is not needed
+      // anymore, such as when it is unmounted or when the image stream pushes
+      // a new image.
+      image: _imageInfo?.image,
+      debugImageLabel: _imageInfo?.debugLabel,
+      width: widget.width,
+      height: widget.height,
+      scale: _imageInfo?.scale ?? 1.0,
+      color: widget.color,
+      colorBlendMode: widget.colorBlendMode,
+      fit: widget.fit,
+      alignment: widget.alignment,
+      repeat: widget.repeat,
+      centerSlice: widget.centerSlice,
+      matchTextDirection: widget.matchTextDirection,
+      invertColors: _invertColors,
+      isAntiAlias: widget.isAntiAlias,
+      filterQuality: widget.filterQuality,
+    );
+
+    if (!widget.excludeFromSemantics) {
+      result = Semantics(
+        container: widget.semanticLabel != null,
+        image: true,
+        label: widget.semanticLabel ?? '',
+        child: result,
+      );
+    }
+
+    if (widget.frameBuilder != null)
+      result = widget.frameBuilder!(context, result, _frameNumber, _wasSynchronouslyLoaded);
+
+    if (widget.loadingBuilder != null)
+      result = widget.loadingBuilder!(context, result, _loadingProgress);
+
+    return result;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(DiagnosticsProperty<ImageStream>('stream', _imageStream));
+    description.add(DiagnosticsProperty<ImageInfo>('pixels', _imageInfo));
+    description.add(DiagnosticsProperty<ImageChunkEvent>('loadingProgress', _loadingProgress));
+    description.add(DiagnosticsProperty<int>('frameNumber', _frameNumber));
+    description.add(DiagnosticsProperty<bool>('wasSynchronouslyLoaded', _wasSynchronouslyLoaded));
+  }
+}
diff --git a/lib/src/widgets/image_filter.dart b/lib/src/widgets/image_filter.dart
new file mode 100644
index 0000000..a308eb0
--- /dev/null
+++ b/lib/src/widgets/image_filter.dart
@@ -0,0 +1,77 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+import 'framework.dart';
+
+/// Applies an [ImageFilter] to its child.
+///
+/// See also:
+///
+/// * [BackdropFilter], which applies an [ImageFilter] to everything
+///   beneath its child.
+/// * [ColorFiltered], which applies a [ColorFilter] to its child.
+@immutable
+class ImageFiltered extends SingleChildRenderObjectWidget {
+  /// Creates a widget that applies an [ImageFilter] to its child.
+  ///
+  /// The [imageFilter] must not be null.
+  const ImageFiltered({
+    Key? key,
+    required this.imageFilter,
+    Widget? child,
+  }) : assert(imageFilter != null),
+       super(key: key, child: child);
+
+  /// The image filter to apply to the child of this widget.
+  final ImageFilter imageFilter;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) => _ImageFilterRenderObject(imageFilter);
+
+  @override
+  void updateRenderObject(BuildContext context, _ImageFilterRenderObject renderObject) {
+    renderObject.imageFilter = imageFilter;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ImageFilter>('imageFilter', imageFilter));
+  }
+}
+
+class _ImageFilterRenderObject extends RenderProxyBox {
+  _ImageFilterRenderObject(this._imageFilter);
+
+  ImageFilter get imageFilter => _imageFilter;
+  ImageFilter _imageFilter;
+  set imageFilter(ImageFilter value) {
+    assert(value != null);
+    if (value != _imageFilter) {
+      _imageFilter = value;
+      markNeedsPaint();
+    }
+  }
+
+  @override
+  bool get alwaysNeedsCompositing => child != null;
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    assert(imageFilter != null);
+    if (layer == null) {
+      layer = ImageFilterLayer(imageFilter: imageFilter);
+    } else {
+      final ImageFilterLayer filterLayer = layer! as ImageFilterLayer;
+      filterLayer.imageFilter = imageFilter;
+    }
+    context.pushLayer(layer!, super.paint, offset);
+    assert(layer != null);
+  }
+}
diff --git a/lib/src/widgets/image_icon.dart b/lib/src/widgets/image_icon.dart
new file mode 100644
index 0000000..cd3e8e0
--- /dev/null
+++ b/lib/src/widgets/image_icon.dart
@@ -0,0 +1,105 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'icon.dart';
+import 'icon_theme.dart';
+import 'icon_theme_data.dart';
+import 'image.dart';
+
+/// An icon that comes from an [ImageProvider], e.g. an [AssetImage].
+///
+/// See also:
+///
+///  * [IconButton], for interactive icons.
+///  * [IconTheme], which provides ambient configuration for icons.
+///  * [Icon], for icons based on glyphs from fonts instead of images.
+///  * [Icons], a predefined font based set of icons from the material design library.
+class ImageIcon extends StatelessWidget {
+  /// Creates an image icon.
+  ///
+  /// The [size] and [color] default to the value given by the current [IconTheme].
+  const ImageIcon(
+    this.image, {
+    Key? key,
+    this.size,
+    this.color,
+    this.semanticLabel,
+  }) : super(key: key);
+
+  /// The image to display as the icon.
+  ///
+  /// The icon can be null, in which case the widget will render as an empty
+  /// space of the specified [size].
+  final ImageProvider? image;
+
+  /// The size of the icon in logical pixels.
+  ///
+  /// Icons occupy a square with width and height equal to size.
+  ///
+  /// Defaults to the current [IconTheme] size, if any. If there is no
+  /// [IconTheme], or it does not specify an explicit size, then it defaults to
+  /// 24.0.
+  final double? size;
+
+  /// The color to use when drawing the icon.
+  ///
+  /// Defaults to the current [IconTheme] color, if any. If there is
+  /// no [IconTheme], then it defaults to not recolorizing the image.
+  ///
+  /// The image will additionally be adjusted by the opacity of the current
+  /// [IconTheme], if any.
+  final Color? color;
+
+  /// Semantic label for the icon.
+  ///
+  /// Announced in accessibility modes (e.g TalkBack/VoiceOver).
+  /// This label does not show in the UI.
+  ///
+  ///  * [SemanticsProperties.label], which is set to [semanticLabel] in the
+  ///    underlying	 [Semantics] widget.
+  final String? semanticLabel;
+
+  @override
+  Widget build(BuildContext context) {
+    final IconThemeData iconTheme = IconTheme.of(context);
+    final double? iconSize = size ?? iconTheme.size;
+
+    if (image == null)
+      return Semantics(
+        label: semanticLabel,
+        child: SizedBox(width: iconSize, height: iconSize),
+      );
+
+    final double? iconOpacity = iconTheme.opacity;
+    Color iconColor = color ?? iconTheme.color!;
+
+    if (iconOpacity != null && iconOpacity != 1.0)
+      iconColor = iconColor.withOpacity(iconColor.opacity * iconOpacity);
+
+    return Semantics(
+      label: semanticLabel,
+      child: Image(
+        image: image!,
+        width: iconSize,
+        height: iconSize,
+        color: iconColor,
+        fit: BoxFit.scaleDown,
+        alignment: Alignment.center,
+        excludeFromSemantics: true,
+      ),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ImageProvider>('image', image, ifNull: '<empty>', showName: false));
+    properties.add(DoubleProperty('size', size, defaultValue: null));
+    properties.add(ColorProperty('color', color, defaultValue: null));
+  }
+}
diff --git a/lib/src/widgets/implicit_animations.dart b/lib/src/widgets/implicit_animations.dart
new file mode 100644
index 0000000..578d6d2
--- /dev/null
+++ b/lib/src/widgets/implicit_animations.dart
@@ -0,0 +1,1934 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui show TextHeightBehavior;
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart';
+import 'package:flute/rendering.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'basic.dart';
+import 'container.dart';
+import 'debug.dart';
+import 'framework.dart';
+import 'text.dart';
+import 'ticker_provider.dart';
+import 'transitions.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// class MyWidget extends ImplicitlyAnimatedWidget {
+//   MyWidget() : super(duration: const Duration(seconds: 1));
+//   final Color targetColor = Colors.black;
+//   @override
+//   MyWidgetState createState() => MyWidgetState();
+// }
+// void setState(VoidCallback fn) { }
+
+/// An interpolation between two [BoxConstraints].
+///
+/// This class specializes the interpolation of [Tween<BoxConstraints>] to use
+/// [BoxConstraints.lerp].
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+class BoxConstraintsTween extends Tween<BoxConstraints> {
+  /// Creates a [BoxConstraints] tween.
+  ///
+  /// The [begin] and [end] properties may be null; the null value
+  /// is treated as a tight constraint of zero size.
+  BoxConstraintsTween({ BoxConstraints? begin, BoxConstraints? end }) : super(begin: begin, end: end);
+
+  /// Returns the value this variable has at the given animation clock value.
+  @override
+  BoxConstraints lerp(double t) => BoxConstraints.lerp(begin, end, t)!;
+}
+
+/// An interpolation between two [Decoration]s.
+///
+/// This class specializes the interpolation of [Tween<BoxConstraints>] to use
+/// [Decoration.lerp].
+///
+/// For [ShapeDecoration]s which know how to [ShapeDecoration.lerpTo] or
+/// [ShapeDecoration.lerpFrom] each other, this will produce a smooth
+/// interpolation between decorations.
+///
+/// See also:
+///
+///  * [Tween] for a discussion on how to use interpolation objects.
+///  * [ShapeDecoration], [RoundedRectangleBorder], [CircleBorder], and
+///    [StadiumBorder] for examples of shape borders that can be smoothly
+///    interpolated.
+///  * [BoxBorder] for a border that can only be smoothly interpolated between other
+///    [BoxBorder]s.
+class DecorationTween extends Tween<Decoration> {
+  /// Creates a decoration tween.
+  ///
+  /// The [begin] and [end] properties may be null. If both are null, then the
+  /// result is always null. If [end] is not null, then its lerping logic is
+  /// used (via [Decoration.lerpTo]). Otherwise, [begin]'s lerping logic is used
+  /// (via [Decoration.lerpFrom]).
+  DecorationTween({ Decoration? begin, Decoration? end }) : super(begin: begin, end: end);
+
+  /// Returns the value this variable has at the given animation clock value.
+  @override
+  Decoration lerp(double t) => Decoration.lerp(begin, end, t)!;
+}
+
+/// An interpolation between two [EdgeInsets]s.
+///
+/// This class specializes the interpolation of [Tween<EdgeInsets>] to use
+/// [EdgeInsets.lerp].
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+///
+/// See also:
+///
+///  * [EdgeInsetsGeometryTween], which interpolates between two
+///    [EdgeInsetsGeometry] objects.
+class EdgeInsetsTween extends Tween<EdgeInsets> {
+  /// Creates an [EdgeInsets] tween.
+  ///
+  /// The [begin] and [end] properties may be null; the null value
+  /// is treated as an [EdgeInsets] with no inset.
+  EdgeInsetsTween({ EdgeInsets? begin, EdgeInsets? end }) : super(begin: begin, end: end);
+
+  /// Returns the value this variable has at the given animation clock value.
+  @override
+  EdgeInsets lerp(double t) => EdgeInsets.lerp(begin, end, t)!;
+}
+
+/// An interpolation between two [EdgeInsetsGeometry]s.
+///
+/// This class specializes the interpolation of [Tween<EdgeInsetsGeometry>] to
+/// use [EdgeInsetsGeometry.lerp].
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+///
+/// See also:
+///
+///  * [EdgeInsetsTween], which interpolates between two [EdgeInsets] objects.
+class EdgeInsetsGeometryTween extends Tween<EdgeInsetsGeometry> {
+  /// Creates an [EdgeInsetsGeometry] tween.
+  ///
+  /// The [begin] and [end] properties may be null; the null value
+  /// is treated as an [EdgeInsetsGeometry] with no inset.
+  EdgeInsetsGeometryTween({ EdgeInsetsGeometry? begin, EdgeInsetsGeometry? end }) : super(begin: begin, end: end);
+
+  /// Returns the value this variable has at the given animation clock value.
+  @override
+  EdgeInsetsGeometry lerp(double t) => EdgeInsetsGeometry.lerp(begin, end, t)!;
+}
+
+/// An interpolation between two [BorderRadius]s.
+///
+/// This class specializes the interpolation of [Tween<BorderRadius>] to use
+/// [BorderRadius.lerp].
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+class BorderRadiusTween extends Tween<BorderRadius> {
+  /// Creates a [BorderRadius] tween.
+  ///
+  /// The [begin] and [end] properties may be null; the null value
+  /// is treated as a right angle (no radius).
+  BorderRadiusTween({ BorderRadius? begin, BorderRadius? end }) : super(begin: begin, end: end);
+
+  /// Returns the value this variable has at the given animation clock value.
+  @override
+  BorderRadius lerp(double t) => BorderRadius.lerp(begin, end, t)!;
+}
+
+/// An interpolation between two [Border]s.
+///
+/// This class specializes the interpolation of [Tween<Border>] to use
+/// [Border.lerp].
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+class BorderTween extends Tween<Border> {
+  /// Creates a [Border] tween.
+  ///
+  /// The [begin] and [end] properties may be null; the null value
+  /// is treated as having no border.
+  BorderTween({ Border? begin, Border? end }) : super(begin: begin, end: end);
+
+  /// Returns the value this variable has at the given animation clock value.
+  @override
+  Border lerp(double t) => Border.lerp(begin, end, t)!;
+}
+
+/// An interpolation between two [Matrix4]s.
+///
+/// This class specializes the interpolation of [Tween<Matrix4>] to be
+/// appropriate for transformation matrices.
+///
+/// Currently this class works only for translations.
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+class Matrix4Tween extends Tween<Matrix4> {
+  /// Creates a [Matrix4] tween.
+  ///
+  /// The [begin] and [end] properties must be non-null before the tween is
+  /// first used, but the arguments can be null if the values are going to be
+  /// filled in later.
+  Matrix4Tween({ Matrix4? begin, Matrix4? end }) : super(begin: begin, end: end);
+
+  @override
+  Matrix4 lerp(double t) {
+    assert(begin != null);
+    assert(end != null);
+    final Vector3 beginTranslation = Vector3.zero();
+    final Vector3 endTranslation = Vector3.zero();
+    final Quaternion beginRotation = Quaternion.identity();
+    final Quaternion endRotation = Quaternion.identity();
+    final Vector3 beginScale = Vector3.zero();
+    final Vector3 endScale = Vector3.zero();
+    begin!.decompose(beginTranslation, beginRotation, beginScale);
+    end!.decompose(endTranslation, endRotation, endScale);
+    final Vector3 lerpTranslation =
+        beginTranslation * (1.0 - t) + endTranslation * t;
+    // TODO(alangardner): Implement slerp for constant rotation
+    final Quaternion lerpRotation =
+        (beginRotation.scaled(1.0 - t) + endRotation.scaled(t)).normalized();
+    final Vector3 lerpScale = beginScale * (1.0 - t) + endScale * t;
+    return Matrix4.compose(lerpTranslation, lerpRotation, lerpScale);
+  }
+}
+
+/// An interpolation between two [TextStyle]s.
+///
+/// This class specializes the interpolation of [Tween<TextStyle>] to use
+/// [TextStyle.lerp].
+///
+/// This will not work well if the styles don't set the same fields.
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+class TextStyleTween extends Tween<TextStyle> {
+  /// Creates a text style tween.
+  ///
+  /// The [begin] and [end] properties must be non-null before the tween is
+  /// first used, but the arguments can be null if the values are going to be
+  /// filled in later.
+  TextStyleTween({ TextStyle? begin, TextStyle? end }) : super(begin: begin, end: end);
+
+  /// Returns the value this variable has at the given animation clock value.
+  @override
+  TextStyle lerp(double t) => TextStyle.lerp(begin, end, t)!;
+}
+
+/// An abstract class for building widgets that animate changes to their
+/// properties.
+///
+/// Widgets of this type will not animate when they are first added to the
+/// widget tree. Rather, when they are rebuilt with different values, they will
+/// respond to those _changes_ by animating the changes over a specified
+/// [duration].
+///
+/// Which properties are animated is left up to the subclass. Subclasses' [State]s
+/// must extend [ImplicitlyAnimatedWidgetState] and provide a way to visit the
+/// relevant fields to animate.
+///
+/// ## Relationship to [AnimatedWidget]s
+///
+/// [ImplicitlyAnimatedWidget]s (and their subclasses) automatically animate
+/// changes in their properties whenever they change. For this,
+/// they create and manage their own internal [AnimationController]s to power
+/// the animation. While these widgets are simple to use and don't require you
+/// to manually manage the lifecycle of an [AnimationController], they
+/// are also somewhat limited: Besides the target value for the animated
+/// property, developers can only choose a [duration] and [curve] for the
+/// animation. If you require more control over the animation (e.g. you want
+/// to stop it somewhere in the middle), consider using an
+/// [AnimatedWidget] or one of its subclasses. These widgets take an [Animation]
+/// as an argument to power the animation. This gives the developer full control
+/// over the animation at the cost of requiring you to manually manage the
+/// underlying [AnimationController].
+///
+/// ## Common implicitly animated widgets
+///
+/// A number of implicitly animated widgets ship with the framework. They are
+/// usually named `AnimatedFoo`, where `Foo` is the name of the non-animated
+/// version of that widget. Commonly used implicitly animated widgets include:
+///
+///  * [TweenAnimationBuilder], which animates any property expressed by
+///    a [Tween] to a specified target value.
+///  * [AnimatedAlign], which is an implicitly animated version of [Align].
+///  * [AnimatedContainer], which is an implicitly animated version of
+///    [Container].
+///  * [AnimatedDefaultTextStyle], which is an implicitly animated version of
+///    [DefaultTextStyle].
+///  * [AnimatedOpacity], which is an implicitly animated version of [Opacity].
+///  * [AnimatedPadding], which is an implicitly animated version of [Padding].
+///  * [AnimatedPhysicalModel], which is an implicitly animated version of
+///    [PhysicalModel].
+///  * [AnimatedPositioned], which is an implicitly animated version of
+///    [Positioned].
+///  * [AnimatedPositionedDirectional], which is an implicitly animated version
+///    of [PositionedDirectional].
+///  * [AnimatedTheme], which is an implicitly animated version of [Theme].
+///  * [AnimatedCrossFade], which cross-fades between two given children and
+///    animates itself between their sizes.
+///  * [AnimatedSize], which automatically transitions its size over a given
+///    duration.
+///  * [AnimatedSwitcher], which fades from one widget to another.
+abstract class ImplicitlyAnimatedWidget extends StatefulWidget {
+  /// Initializes fields for subclasses.
+  ///
+  /// The [curve] and [duration] arguments must not be null.
+  const ImplicitlyAnimatedWidget({
+    Key? key,
+    this.curve = Curves.linear,
+    required this.duration,
+    this.onEnd,
+  }) : assert(curve != null),
+       assert(duration != null),
+       super(key: key);
+
+  /// The curve to apply when animating the parameters of this container.
+  final Curve curve;
+
+  /// The duration over which to animate the parameters of this container.
+  final Duration duration;
+
+  /// Called every time an animation completes.
+  ///
+  /// This can be useful to trigger additional actions (e.g. another animation)
+  /// at the end of the current animation.
+  final VoidCallback? onEnd;
+
+  @override
+  ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState(); // ignore: no_logic_in_create_state, https://github.com/dart-lang/linter/issues/2345
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms'));
+  }
+}
+
+/// Signature for a [Tween] factory.
+///
+/// This is the type of one of the arguments of [TweenVisitor], the signature
+/// used by [AnimatedWidgetBaseState.forEachTween].
+///
+/// Instances of this function are expected to take a value and return a tween
+/// beginning at that value.
+typedef TweenConstructor<T extends Object> = Tween<T> Function(T targetValue);
+
+/// Signature for callbacks passed to [ImplicitlyAnimatedWidgetState.forEachTween].
+///
+/// {@template flutter.widgets.TweenVisitor.arguments}
+/// The `tween` argument should contain the current tween value. This will
+/// initially be null when the state is first initialized.
+///
+/// The `targetValue` argument should contain the value toward which the state
+/// is animating. For instance, if the state is animating its widget's
+/// opacity value, then this argument should contain the widget's current
+/// opacity value.
+///
+/// The `constructor` argument should contain a function that takes a value
+/// (the widget's value being animated) and returns a tween beginning at that
+/// value.
+///
+/// {@endtemplate}
+///
+/// `forEachTween()` is expected to update its tween value to the return value
+/// of this visitor.
+///
+/// The `<T>` parameter specifies the type of value that's being animated.
+typedef TweenVisitor<T extends Object> = Tween<T>? Function(Tween<T>? tween, T targetValue, TweenConstructor<T> constructor);
+
+/// A base class for the `State` of widgets with implicit animations.
+///
+/// [ImplicitlyAnimatedWidgetState] requires that subclasses respond to the
+/// animation themselves. If you would like `setState()` to be called
+/// automatically as the animation changes, use [AnimatedWidgetBaseState].
+///
+/// Properties that subclasses choose to animate are represented by [Tween]
+/// instances. Subclasses must implement the [forEachTween] method to allow
+/// [ImplicitlyAnimatedWidgetState] to iterate through the widget's fields and
+/// animate them.
+abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget> extends State<T> with SingleTickerProviderStateMixin<T> {
+  /// The animation controller driving this widget's implicit animations.
+  @protected
+  AnimationController get controller => _controller;
+  late final AnimationController _controller = AnimationController(
+    duration: widget.duration,
+    debugLabel: kDebugMode ? widget.toStringShort() : null,
+    vsync: this,
+  );
+
+  /// The animation driving this widget's implicit animations.
+  Animation<double> get animation => _animation;
+  late Animation<double> _animation = _createCurve();
+
+  @override
+  void initState() {
+    super.initState();
+    _controller.addStatusListener((AnimationStatus status) {
+      switch (status) {
+        case AnimationStatus.completed:
+          if (widget.onEnd != null)
+            widget.onEnd!();
+          break;
+        case AnimationStatus.dismissed:
+        case AnimationStatus.forward:
+        case AnimationStatus.reverse:
+      }
+    });
+    _constructTweens();
+    didUpdateTweens();
+  }
+
+  @override
+  void didUpdateWidget(T oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.curve != oldWidget.curve)
+      _animation = _createCurve();
+    _controller.duration = widget.duration;
+    if (_constructTweens()) {
+      forEachTween((Tween<dynamic>? tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {
+        _updateTween(tween, targetValue);
+        return tween;
+      });
+      _controller
+        ..value = 0.0
+        ..forward();
+      didUpdateTweens();
+    }
+  }
+
+  CurvedAnimation _createCurve() {
+    return CurvedAnimation(parent: _controller, curve: widget.curve);
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  bool _shouldAnimateTween(Tween<dynamic> tween, dynamic targetValue) {
+    return targetValue != (tween.end ?? tween.begin);
+  }
+
+  void _updateTween(Tween<dynamic>? tween, dynamic targetValue) {
+    if (tween == null)
+      return;
+    tween
+      ..begin = tween.evaluate(_animation)
+      ..end = targetValue;
+  }
+
+  bool _constructTweens() {
+    bool shouldStartAnimation = false;
+    forEachTween((Tween<dynamic>? tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {
+      if (targetValue != null) {
+        tween ??= constructor(targetValue);
+        if (_shouldAnimateTween(tween, targetValue))
+          shouldStartAnimation = true;
+      } else {
+        tween = null;
+      }
+      return tween;
+    });
+    return shouldStartAnimation;
+  }
+
+  /// Visits each tween controlled by this state with the specified `visitor`
+  /// function.
+  ///
+  /// ### Subclass responsibility
+  ///
+  /// Properties to be animated are represented by [Tween] member variables in
+  /// the state. For each such tween, [forEachTween] implementations are
+  /// expected to call `visitor` with the appropriate arguments and store the
+  /// result back into the member variable. The arguments to `visitor` are as
+  /// follows:
+  ///
+  /// {@macro flutter.widgets.TweenVisitor.arguments}
+  ///
+  /// ### When this method will be called
+  ///
+  /// [forEachTween] is initially called during [initState]. It is expected that
+  /// the visitor's `tween` argument will be set to null, causing the visitor to
+  /// call its `constructor` argument to construct the tween for the first time.
+  /// The resulting tween will have its `begin` value set to the target value
+  /// and will have its `end` value set to null. The animation will not be
+  /// started.
+  ///
+  /// When this state's [widget] is updated (thus triggering the
+  /// [didUpdateWidget] method to be called), [forEachTween] will be called
+  /// again to check if the target value has changed. If the target value has
+  /// changed, signaling that the [animation] should start, then the visitor
+  /// will update the tween's `start` and `end` values accordingly, and the
+  /// animation will be started.
+  ///
+  /// ### Other member variables
+  ///
+  /// Subclasses that contain properties based on tweens created by
+  /// [forEachTween] should override [didUpdateTweens] to update those
+  /// properties. Dependent properties should not be updated within
+  /// [forEachTween].
+  ///
+  /// {@tool snippet}
+  ///
+  /// This sample implements an implicitly animated widget's `State`.
+  /// The widget animates between colors whenever `widget.targetColor`
+  /// changes.
+  ///
+  /// ```dart
+  /// class MyWidgetState extends AnimatedWidgetBaseState<MyWidget> {
+  ///   ColorTween _colorTween;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Text(
+  ///       'Hello World',
+  ///       // Computes the value of the text color at any given time.
+  ///       style: TextStyle(color: _colorTween.evaluate(animation)),
+  ///     );
+  ///   }
+  ///
+  ///   @override
+  ///   void forEachTween(TweenVisitor<dynamic> visitor) {
+  ///     // Update the tween using the provided visitor function.
+  ///     _colorTween = visitor(
+  ///       // The latest tween value. Can be `null`.
+  ///       _colorTween,
+  ///       // The color value toward which we are animating.
+  ///       widget.targetColor,
+  ///       // A function that takes a color value and returns a tween
+  ///       // beginning at that value.
+  ///       (value) => ColorTween(begin: value),
+  ///     );
+  ///
+  ///     // We could have more tweens than one by using the visitor
+  ///     // multiple times.
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  @protected
+  void forEachTween(TweenVisitor<dynamic> visitor);
+
+  /// Optional hook for subclasses that runs after all tweens have been updated
+  /// via [forEachTween].
+  ///
+  /// Any properties that depend upon tweens created by [forEachTween] should be
+  /// updated within [didUpdateTweens], not within [forEachTween].
+  ///
+  /// This method will be called both:
+  ///
+  ///  1. After the tweens are _initially_ constructed (by
+  ///     the `constructor` argument to the [TweenVisitor] that's passed to
+  ///     [forEachTween]). In this case, the tweens are likely to contain only
+  ///     a [Tween.begin] value and not a [Tween.end].
+  ///
+  ///  2. When the state's [widget] is updated, and one or more of the tweens
+  ///     visited by [forEachTween] specifies a target value that's different
+  ///     than the widget's current value, thus signaling that the [animation]
+  ///     should run. In this case, the [Tween.begin] value for each tween will
+  ///     an evaluation of the tween against the current [animation], and the
+  ///     [Tween.end] value for each tween will be the target value.
+  @protected
+  void didUpdateTweens() { }
+}
+
+/// A base class for widgets with implicit animations that need to rebuild their
+/// widget tree as the animation runs.
+///
+/// This class calls [build] each frame that the animation tickets. For a
+/// variant that does not rebuild each frame, consider subclassing
+/// [ImplicitlyAnimatedWidgetState] directly.
+///
+/// Subclasses must implement the [forEachTween] method to allow
+/// [AnimatedWidgetBaseState] to iterate through the subclasses' widget's fields
+/// and animate them.
+abstract class AnimatedWidgetBaseState<T extends ImplicitlyAnimatedWidget> extends ImplicitlyAnimatedWidgetState<T> {
+  @override
+  void initState() {
+    super.initState();
+    controller.addListener(_handleAnimationChanged);
+  }
+
+  void _handleAnimationChanged() {
+    setState(() { /* The animation ticked. Rebuild with new animation value */ });
+  }
+}
+
+/// Animated version of [Container] that gradually changes its values over a period of time.
+///
+/// The [AnimatedContainer] will automatically animate between the old and
+/// new values of properties when they change using the provided curve and
+/// duration. Properties that are null are not animated. Its child and
+/// descendants are not animated.
+///
+/// This class is useful for generating simple implicit transitions between
+/// different parameters to [Container] with its internal [AnimationController].
+/// For more complex animations, you'll likely want to use a subclass of
+/// [AnimatedWidget] such as the [DecoratedBoxTransition] or use your own
+/// [AnimationController].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=yI-8QHpGIP4}
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+///
+/// The following example (depicted above) transitions an AnimatedContainer
+/// between two states. It adjusts the `height`, `width`, `color`, and
+/// [alignment] properties when tapped.
+///
+/// ```dart
+/// bool selected = false;
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return GestureDetector(
+///     onTap: () {
+///       setState(() {
+///         selected = !selected;
+///       });
+///     },
+///     child: Center(
+///       child: AnimatedContainer(
+///         width: selected ? 200.0 : 100.0,
+///         height: selected ? 100.0 : 200.0,
+///         color: selected ? Colors.red : Colors.blue,
+///         alignment: selected ? Alignment.center : AlignmentDirectional.topCenter,
+///         duration: Duration(seconds: 2),
+///         curve: Curves.fastOutSlowIn,
+///         child: FlutterLogo(size: 75),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AnimatedPadding], which is a subset of this widget that only
+///    supports animating the [padding].
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+///  * [AnimatedPositioned], which, as a child of a [Stack], automatically
+///    transitions its child's position over a given duration whenever the given
+///    position changes.
+///  * [AnimatedAlign], which automatically transitions its child's
+///    position over a given duration whenever the given [alignment] changes.
+///  * [AnimatedSwitcher], which switches out a child for a new one with a customizable transition.
+///  * [AnimatedCrossFade], which fades between two children and interpolates their sizes.
+class AnimatedContainer extends ImplicitlyAnimatedWidget {
+  /// Creates a container that animates its parameters implicitly.
+  ///
+  /// The [curve] and [duration] arguments must not be null.
+  AnimatedContainer({
+    Key? key,
+    this.alignment,
+    this.padding,
+    Color? color,
+    Decoration? decoration,
+    this.foregroundDecoration,
+    double? width,
+    double? height,
+    BoxConstraints? constraints,
+    this.margin,
+    this.transform,
+    this.transformAlignment,
+    this.child,
+    this.clipBehavior = Clip.none,
+    Curve curve = Curves.linear,
+    required Duration duration,
+    VoidCallback? onEnd,
+  }) : assert(margin == null || margin.isNonNegative),
+       assert(padding == null || padding.isNonNegative),
+       assert(decoration == null || decoration.debugAssertIsValid()),
+       assert(constraints == null || constraints.debugAssertIsValid()),
+       assert(color == null || decoration == null,
+         'Cannot provide both a color and a decoration\n'
+         'The color argument is just a shorthand for "decoration: BoxDecoration(color: color)".'
+       ),
+       decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null),
+       constraints =
+        (width != null || height != null)
+          ? constraints?.tighten(width: width, height: height)
+            ?? BoxConstraints.tightFor(width: width, height: height)
+          : constraints,
+       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
+
+  /// The [child] contained by the container.
+  ///
+  /// If null, and if the [constraints] are unbounded or also null, the
+  /// container will expand to fill all available space in its parent, unless
+  /// the parent provides unbounded constraints, in which case the container
+  /// will attempt to be as small as possible.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// Align the [child] within the container.
+  ///
+  /// If non-null, the container will expand to fill its parent and position its
+  /// child within itself according to the given value. If the incoming
+  /// constraints are unbounded, then the child will be shrink-wrapped instead.
+  ///
+  /// Ignored if [child] is null.
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], a class with convenient constants typically used to
+  ///    specify an [AlignmentGeometry].
+  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
+  ///    relative to text direction.
+  final AlignmentGeometry? alignment;
+
+  /// Empty space to inscribe inside the [decoration]. The [child], if any, is
+  /// placed inside this padding.
+  final EdgeInsetsGeometry? padding;
+
+  /// The decoration to paint behind the [child].
+  ///
+  /// A shorthand for specifying just a solid color is available in the
+  /// constructor: set the `color` argument instead of the `decoration`
+  /// argument.
+  final Decoration? decoration;
+
+  /// The decoration to paint in front of the child.
+  final Decoration? foregroundDecoration;
+
+  /// Additional constraints to apply to the child.
+  ///
+  /// The constructor `width` and `height` arguments are combined with the
+  /// `constraints` argument to set this property.
+  ///
+  /// The [padding] goes inside the constraints.
+  final BoxConstraints? constraints;
+
+  /// Empty space to surround the [decoration] and [child].
+  final EdgeInsetsGeometry? margin;
+
+  /// The transformation matrix to apply before painting the container.
+  final Matrix4? transform;
+
+  /// The alignment of the origin, relative to the size of the container, if [transform] is specified.
+  ///
+  /// When [transform] is null, the value of this property is ignored.
+  ///
+  /// See also:
+  ///
+  ///  * [Transform.alignment], which is set by this property.
+  final AlignmentGeometry? transformAlignment;
+
+  /// The clip behavior when [AnimatedContainer.decoration] is not null.
+  ///
+  /// Defaults to [Clip.none]. Must be [Clip.none] if [decoration] is null.
+  ///
+  /// Unlike other properties of [AnimatedContainer], changes to this property
+  /// apply immediately and have no animation.
+  ///
+  /// If a clip is to be applied, the [Decoration.getClipPath] method
+  /// for the provided decoration must return a clip path. (This is not
+  /// supported by all decorations; the default implementation of that
+  /// method throws an [UnsupportedError].)
+  final Clip clipBehavior;
+
+  @override
+  _AnimatedContainerState createState() => _AnimatedContainerState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
+    properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
+    properties.add(DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
+    properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null, showName: false));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
+    properties.add(ObjectFlagProperty<Matrix4>.has('transform', transform));
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('transformAlignment', transformAlignment, defaultValue: null));
+    properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior));
+  }
+}
+
+class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> {
+  AlignmentGeometryTween? _alignment;
+  EdgeInsetsGeometryTween? _padding;
+  DecorationTween? _decoration;
+  DecorationTween? _foregroundDecoration;
+  BoxConstraintsTween? _constraints;
+  EdgeInsetsGeometryTween? _margin;
+  Matrix4Tween? _transform;
+  AlignmentGeometryTween? _transformAlignment;
+
+  @override
+  void forEachTween(TweenVisitor<dynamic> visitor) {
+    _alignment = visitor(_alignment, widget.alignment, (dynamic value) => AlignmentGeometryTween(begin: value as AlignmentGeometry)) as AlignmentGeometryTween?;
+    _padding = visitor(_padding, widget.padding, (dynamic value) => EdgeInsetsGeometryTween(begin: value as EdgeInsetsGeometry)) as EdgeInsetsGeometryTween?;
+    _decoration = visitor(_decoration, widget.decoration, (dynamic value) => DecorationTween(begin: value as Decoration)) as DecorationTween?;
+    _foregroundDecoration = visitor(_foregroundDecoration, widget.foregroundDecoration, (dynamic value) => DecorationTween(begin: value as Decoration)) as DecorationTween?;
+    _constraints = visitor(_constraints, widget.constraints, (dynamic value) => BoxConstraintsTween(begin: value as BoxConstraints)) as BoxConstraintsTween?;
+    _margin = visitor(_margin, widget.margin, (dynamic value) => EdgeInsetsGeometryTween(begin: value as EdgeInsetsGeometry)) as EdgeInsetsGeometryTween?;
+    _transform = visitor(_transform, widget.transform, (dynamic value) => Matrix4Tween(begin: value as Matrix4)) as Matrix4Tween?;
+    _transformAlignment = visitor(_transformAlignment, widget.transformAlignment, (dynamic value) => AlignmentGeometryTween(begin: value as AlignmentGeometry)) as AlignmentGeometryTween?;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final Animation<double> animation = this.animation;
+    return Container(
+      child: widget.child,
+      alignment: _alignment?.evaluate(animation),
+      padding: _padding?.evaluate(animation),
+      decoration: _decoration?.evaluate(animation),
+      foregroundDecoration: _foregroundDecoration?.evaluate(animation),
+      constraints: _constraints?.evaluate(animation),
+      margin: _margin?.evaluate(animation),
+      transform: _transform?.evaluate(animation),
+      transformAlignment: _transformAlignment?.evaluate(animation),
+      clipBehavior: widget.clipBehavior,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(DiagnosticsProperty<AlignmentGeometryTween>('alignment', _alignment, showName: false, defaultValue: null));
+    description.add(DiagnosticsProperty<EdgeInsetsGeometryTween>('padding', _padding, defaultValue: null));
+    description.add(DiagnosticsProperty<DecorationTween>('bg', _decoration, defaultValue: null));
+    description.add(DiagnosticsProperty<DecorationTween>('fg', _foregroundDecoration, defaultValue: null));
+    description.add(DiagnosticsProperty<BoxConstraintsTween>('constraints', _constraints, showName: false, defaultValue: null));
+    description.add(DiagnosticsProperty<EdgeInsetsGeometryTween>('margin', _margin, defaultValue: null));
+    description.add(ObjectFlagProperty<Matrix4Tween>.has('transform', _transform));
+    description.add(DiagnosticsProperty<AlignmentGeometryTween>('transformAlignment', _transformAlignment, defaultValue: null));
+  }
+}
+
+/// Animated version of [Padding] which automatically transitions the
+/// indentation over a given duration whenever the given inset changes.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=PY2m0fhGNz4}
+///
+/// Here's an illustration of what using this widget looks like, using a [curve]
+/// of [Curves.fastOutSlowIn].
+/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_padding.mp4}
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+///
+/// The following code implements the [AnimatedPadding] widget, using a [curve] of
+/// [Curves.easeInOut].
+///
+/// ```dart
+/// double padValue = 0.0;
+/// _updatePadding(double value) {
+///   setState(() {
+///     padValue = value;
+///   });
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Column(
+///     mainAxisAlignment: MainAxisAlignment.center,
+///     children: [
+///       AnimatedPadding(
+///         padding: EdgeInsets.all(padValue),
+///         duration: const Duration(seconds: 2),
+///         curve: Curves.easeInOut,
+///         child: Container(
+///           width: MediaQuery.of(context).size.width,
+///           height: MediaQuery.of(context).size.height / 5,
+///           color: Colors.blue,
+///         ),
+///       ),
+///       Text('Padding: $padValue'),
+///       ElevatedButton(
+///         child: Text('Change padding'),
+///         onPressed: () {
+///           _updatePadding(padValue == 0.0 ? 100.0 : 0.0);
+///         }
+///       ),
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AnimatedContainer], which can transition more values at once.
+///  * [AnimatedAlign], which automatically transitions its child's
+///    position over a given duration whenever the given
+///    [AnimatedAlign.alignment] changes.
+class AnimatedPadding extends ImplicitlyAnimatedWidget {
+  /// Creates a widget that insets its child by a value that animates
+  /// implicitly.
+  ///
+  /// The [padding], [curve], and [duration] arguments must not be null.
+  AnimatedPadding({
+    Key? key,
+    required this.padding,
+    this.child,
+    Curve curve = Curves.linear,
+    required Duration duration,
+    VoidCallback? onEnd,
+  }) : assert(padding != null),
+       assert(padding.isNonNegative),
+       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
+
+  /// The amount of space by which to inset the child.
+  final EdgeInsetsGeometry padding;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  @override
+  _AnimatedPaddingState createState() => _AnimatedPaddingState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
+  }
+}
+
+class _AnimatedPaddingState extends AnimatedWidgetBaseState<AnimatedPadding> {
+  EdgeInsetsGeometryTween? _padding;
+
+  @override
+  void forEachTween(TweenVisitor<dynamic> visitor) {
+    _padding = visitor(_padding, widget.padding, (dynamic value) => EdgeInsetsGeometryTween(begin: value as EdgeInsetsGeometry)) as EdgeInsetsGeometryTween?;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: _padding!
+        .evaluate(animation)
+        .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity),
+      child: widget.child,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(DiagnosticsProperty<EdgeInsetsGeometryTween>('padding', _padding, defaultValue: null));
+  }
+}
+
+/// Animated version of [Align] which automatically transitions the child's
+/// position over a given duration whenever the given [alignment] changes.
+///
+/// Here's an illustration of what this can look like, using a [curve] of
+/// [Curves.fastOutSlowIn].
+/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_align.mp4}
+///
+/// For the animation, you can choose a [curve] as well as a [duration] and the
+/// widget will automatically animate to the new target [alignment]. If you require
+/// more control over the animation (e.g. if you want to stop it mid-animation),
+/// consider using an [AlignTransition] instead, which takes a provided
+/// [Animation] as argument. While that allows you to fine-tune the animation,
+/// it also requires more development overhead as you have to manually manage
+/// the lifecycle of the underlying [AnimationController].
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_no_null_safety}
+///
+/// The following code implements the [AnimatedAlign] widget, using a [curve] of
+/// [Curves.fastOutSlowIn].
+///
+/// ```dart
+/// bool selected = false;
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return GestureDetector(
+///     onTap: () {
+///       setState(() {
+///         selected = !selected;
+///       });
+///     },
+///     child: Center(
+///       child: Container(
+///         width: 250.0,
+///         height: 250.0,
+///         color: Colors.red,
+///         child: AnimatedAlign(
+///           alignment: selected ? Alignment.topRight : Alignment.bottomLeft,
+///           duration: const Duration(seconds: 1),
+///           curve: Curves.fastOutSlowIn,
+///           child: const FlutterLogo(size: 50.0),
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AnimatedContainer], which can transition more values at once.
+///  * [AnimatedPadding], which can animate the padding instead of the
+///    alignment.
+///  * [AnimatedPositioned], which, as a child of a [Stack], automatically
+///    transitions its child's position over a given duration whenever the given
+///    position changes.
+class AnimatedAlign extends ImplicitlyAnimatedWidget {
+  /// Creates a widget that positions its child by an alignment that animates
+  /// implicitly.
+  ///
+  /// The [alignment], [curve], and [duration] arguments must not be null.
+  const AnimatedAlign({
+    Key? key,
+    required this.alignment,
+    this.child,
+    this.heightFactor,
+    this.widthFactor,
+    Curve curve = Curves.linear,
+    required Duration duration,
+    VoidCallback? onEnd,
+  }) : assert(alignment != null),
+       assert(widthFactor == null || widthFactor >= 0.0),
+       assert(heightFactor == null || heightFactor >= 0.0),
+       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
+
+  /// How to align the child.
+  ///
+  /// The x and y values of the [Alignment] control the horizontal and vertical
+  /// alignment, respectively. An x value of -1.0 means that the left edge of
+  /// the child is aligned with the left edge of the parent whereas an x value
+  /// of 1.0 means that the right edge of the child is aligned with the right
+  /// edge of the parent. Other values interpolate (and extrapolate) linearly.
+  /// For example, a value of 0.0 means that the center of the child is aligned
+  /// with the center of the parent.
+  ///
+  /// See also:
+  ///
+  ///  * [Alignment], which has more details and some convenience constants for
+  ///    common positions.
+  ///  * [AlignmentDirectional], which has a horizontal coordinate orientation
+  ///    that depends on the [TextDirection].
+  final AlignmentGeometry alignment;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// If non-null, sets its height to the child's height multiplied by this factor.
+  ///
+  /// Must be greater than or equal to 0.0, defaults to null.
+  final double? heightFactor;
+
+  /// If non-null, sets its width to the child's width multiplied by this factor.
+  ///
+  /// Must be greater than or equal to 0.0, defaults to null.
+  final double? widthFactor;
+
+  @override
+  _AnimatedAlignState createState() => _AnimatedAlignState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
+  }
+}
+
+class _AnimatedAlignState extends AnimatedWidgetBaseState<AnimatedAlign> {
+  AlignmentGeometryTween? _alignment;
+  Tween<double>? _heightFactorTween;
+  Tween<double>? _widthFactorTween;
+
+  @override
+  void forEachTween(TweenVisitor<dynamic> visitor) {
+    _alignment = visitor(_alignment, widget.alignment, (dynamic value) => AlignmentGeometryTween(begin: value as AlignmentGeometry)) as AlignmentGeometryTween?;
+    if(widget.heightFactor != null) {
+      _heightFactorTween = visitor(_heightFactorTween, widget.heightFactor, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+    }
+    if(widget.widthFactor != null) {
+      _widthFactorTween = visitor(_widthFactorTween, widget.widthFactor, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Align(
+      alignment: _alignment!.evaluate(animation)!,
+      heightFactor: _heightFactorTween?.evaluate(animation),
+      widthFactor: _widthFactorTween?.evaluate(animation),
+      child: widget.child,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(DiagnosticsProperty<AlignmentGeometryTween>('alignment', _alignment, defaultValue: null));
+    description.add(DiagnosticsProperty<Tween<double>>('widthFactor', _widthFactorTween, defaultValue: null));
+    description.add(DiagnosticsProperty<Tween<double>>('heightFactor', _heightFactorTween, defaultValue: null));
+  }
+}
+
+/// Animated version of [Positioned] which automatically transitions the child's
+/// position over a given duration whenever the given position changes.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=hC3s2YdtWt8}
+///
+/// Only works if it's the child of a [Stack].
+///
+/// This widget is a good choice if the _size_ of the child would end up
+/// changing as a result of this animation. If the size is intended to remain
+/// the same, with only the _position_ changing over time, then consider
+/// [SlideTransition] instead. [SlideTransition] only triggers a repaint each
+/// frame of the animation, whereas [AnimatedPositioned] will trigger a relayout
+/// as well.
+///
+/// Here's an illustration of what using this widget looks like, using a [curve]
+/// of [Curves.fastOutSlowIn].
+/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_positioned.mp4}
+///
+/// For the animation, you can choose a [curve] as well as a [duration] and the
+/// widget will automatically animate to the new target position. If you require
+/// more control over the animation (e.g. if you want to stop it mid-animation),
+/// consider using a [PositionedTransition] instead, which takes a provided
+/// [Animation] as an argument. While that allows you to fine-tune the animation,
+/// it also requires more development overhead as you have to manually manage
+/// the lifecycle of the underlying [AnimationController].
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+///
+/// The following example transitions an AnimatedPositioned
+/// between two states. It adjusts the `height`, `width`, and
+/// [Positioned] properties when tapped.
+///
+/// ```dart
+/// bool selected = false;
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Container(
+///     width: 200,
+///     height: 350,
+///     child: Stack(
+///       children: [
+///         AnimatedPositioned(
+///           width: selected ? 200.0 : 50.0,
+///           height: selected ? 50.0 : 200.0,
+///           top: selected ? 50.0 : 150.0,
+///           duration: Duration(seconds: 2),
+///           curve: Curves.fastOutSlowIn,
+///           child: GestureDetector(
+///             onTap: () {
+///               setState(() {
+///                 selected = !selected;
+///               });
+///             },
+///             child: Container(
+///               color: Colors.blue,
+///               child: Center(child: Text('Tap me')),
+///             ),
+///           ),
+///         ),
+///       ],
+///     ),
+///   );
+/// }
+///```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AnimatedPositionedDirectional], which adapts to the ambient
+///    [Directionality] (the same as this widget, but for animating
+///    [PositionedDirectional]).
+class AnimatedPositioned extends ImplicitlyAnimatedWidget {
+  /// Creates a widget that animates its position implicitly.
+  ///
+  /// Only two out of the three horizontal values ([left], [right],
+  /// [width]), and only two out of the three vertical values ([top],
+  /// [bottom], [height]), can be set. In each case, at least one of
+  /// the three must be null.
+  ///
+  /// The [curve] and [duration] arguments must not be null.
+  const AnimatedPositioned({
+    Key? key,
+    required this.child,
+    this.left,
+    this.top,
+    this.right,
+    this.bottom,
+    this.width,
+    this.height,
+    Curve curve = Curves.linear,
+    required Duration duration,
+    VoidCallback? onEnd,
+  }) : assert(left == null || right == null || width == null),
+       assert(top == null || bottom == null || height == null),
+       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
+
+  /// Creates a widget that animates the rectangle it occupies implicitly.
+  ///
+  /// The [curve] and [duration] arguments must not be null.
+  AnimatedPositioned.fromRect({
+    Key? key,
+    required this.child,
+    required Rect rect,
+    Curve curve = Curves.linear,
+    required Duration duration,
+    VoidCallback? onEnd,
+  }) : left = rect.left,
+       top = rect.top,
+       width = rect.width,
+       height = rect.height,
+       right = null,
+       bottom = null,
+       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The offset of the child's left edge from the left of the stack.
+  final double? left;
+
+  /// The offset of the child's top edge from the top of the stack.
+  final double? top;
+
+  /// The offset of the child's right edge from the right of the stack.
+  final double? right;
+
+  /// The offset of the child's bottom edge from the bottom of the stack.
+  final double? bottom;
+
+  /// The child's width.
+  ///
+  /// Only two out of the three horizontal values ([left], [right], [width]) can
+  /// be set. The third must be null.
+  final double? width;
+
+  /// The child's height.
+  ///
+  /// Only two out of the three vertical values ([top], [bottom], [height]) can
+  /// be set. The third must be null.
+  final double? height;
+
+  @override
+  _AnimatedPositionedState createState() => _AnimatedPositionedState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('left', left, defaultValue: null));
+    properties.add(DoubleProperty('top', top, defaultValue: null));
+    properties.add(DoubleProperty('right', right, defaultValue: null));
+    properties.add(DoubleProperty('bottom', bottom, defaultValue: null));
+    properties.add(DoubleProperty('width', width, defaultValue: null));
+    properties.add(DoubleProperty('height', height, defaultValue: null));
+  }
+}
+
+class _AnimatedPositionedState extends AnimatedWidgetBaseState<AnimatedPositioned> {
+  Tween<double>? _left;
+  Tween<double>? _top;
+  Tween<double>? _right;
+  Tween<double>? _bottom;
+  Tween<double>? _width;
+  Tween<double>? _height;
+
+  @override
+  void forEachTween(TweenVisitor<dynamic> visitor) {
+    _left = visitor(_left, widget.left, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+    _top = visitor(_top, widget.top, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+    _right = visitor(_right, widget.right, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+    _bottom = visitor(_bottom, widget.bottom, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+    _width = visitor(_width, widget.width, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+    _height = visitor(_height, widget.height, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Positioned(
+      child: widget.child,
+      left: _left?.evaluate(animation),
+      top: _top?.evaluate(animation),
+      right: _right?.evaluate(animation),
+      bottom: _bottom?.evaluate(animation),
+      width: _width?.evaluate(animation),
+      height: _height?.evaluate(animation),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(ObjectFlagProperty<Tween<double>>.has('left', _left));
+    description.add(ObjectFlagProperty<Tween<double>>.has('top', _top));
+    description.add(ObjectFlagProperty<Tween<double>>.has('right', _right));
+    description.add(ObjectFlagProperty<Tween<double>>.has('bottom', _bottom));
+    description.add(ObjectFlagProperty<Tween<double>>.has('width', _width));
+    description.add(ObjectFlagProperty<Tween<double>>.has('height', _height));
+  }
+}
+
+/// Animated version of [PositionedDirectional] which automatically transitions
+/// the child's position over a given duration whenever the given position
+/// changes.
+///
+/// The ambient [Directionality] is used to determine whether [start] is to the
+/// left or to the right.
+///
+/// Only works if it's the child of a [Stack].
+///
+/// This widget is a good choice if the _size_ of the child would end up
+/// changing as a result of this animation. If the size is intended to remain
+/// the same, with only the _position_ changing over time, then consider
+/// [SlideTransition] instead. [SlideTransition] only triggers a repaint each
+/// frame of the animation, whereas [AnimatedPositionedDirectional] will trigger
+/// a relayout as well. ([SlideTransition] is also text-direction-aware.)
+///
+/// Here's an illustration of what using this widget looks like, using a [curve]
+/// of [Curves.fastOutSlowIn].
+/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_positioned_directional.mp4}
+///
+/// See also:
+///
+///  * [AnimatedPositioned], which specifies the widget's position visually (the
+///    same as this widget, but for animating [Positioned]).
+class AnimatedPositionedDirectional extends ImplicitlyAnimatedWidget {
+  /// Creates a widget that animates its position implicitly.
+  ///
+  /// Only two out of the three horizontal values ([start], [end], [width]), and
+  /// only two out of the three vertical values ([top], [bottom], [height]), can
+  /// be set. In each case, at least one of the three must be null.
+  ///
+  /// The [curve] and [duration] arguments must not be null.
+  const AnimatedPositionedDirectional({
+    Key? key,
+    required this.child,
+    this.start,
+    this.top,
+    this.end,
+    this.bottom,
+    this.width,
+    this.height,
+    Curve curve = Curves.linear,
+    required Duration duration,
+    VoidCallback? onEnd,
+  }) : assert(start == null || end == null || width == null),
+       assert(top == null || bottom == null || height == null),
+       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The offset of the child's start edge from the start of the stack.
+  final double? start;
+
+  /// The offset of the child's top edge from the top of the stack.
+  final double? top;
+
+  /// The offset of the child's end edge from the end of the stack.
+  final double? end;
+
+  /// The offset of the child's bottom edge from the bottom of the stack.
+  final double? bottom;
+
+  /// The child's width.
+  ///
+  /// Only two out of the three horizontal values ([start], [end], [width]) can
+  /// be set. The third must be null.
+  final double? width;
+
+  /// The child's height.
+  ///
+  /// Only two out of the three vertical values ([top], [bottom], [height]) can
+  /// be set. The third must be null.
+  final double? height;
+
+  @override
+  _AnimatedPositionedDirectionalState createState() => _AnimatedPositionedDirectionalState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('start', start, defaultValue: null));
+    properties.add(DoubleProperty('top', top, defaultValue: null));
+    properties.add(DoubleProperty('end', end, defaultValue: null));
+    properties.add(DoubleProperty('bottom', bottom, defaultValue: null));
+    properties.add(DoubleProperty('width', width, defaultValue: null));
+    properties.add(DoubleProperty('height', height, defaultValue: null));
+  }
+}
+
+class _AnimatedPositionedDirectionalState extends AnimatedWidgetBaseState<AnimatedPositionedDirectional> {
+  Tween<double>? _start;
+  Tween<double>? _top;
+  Tween<double>? _end;
+  Tween<double>? _bottom;
+  Tween<double>? _width;
+  Tween<double>? _height;
+
+  @override
+  void forEachTween(TweenVisitor<dynamic> visitor) {
+    _start = visitor(_start, widget.start, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+    _top = visitor(_top, widget.top, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+    _end = visitor(_end, widget.end, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+    _bottom = visitor(_bottom, widget.bottom, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+    _width = visitor(_width, widget.width, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+    _height = visitor(_height, widget.height, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasDirectionality(context));
+    return Positioned.directional(
+      textDirection: Directionality.of(context),
+      child: widget.child,
+      start: _start?.evaluate(animation),
+      top: _top?.evaluate(animation),
+      end: _end?.evaluate(animation),
+      bottom: _bottom?.evaluate(animation),
+      width: _width?.evaluate(animation),
+      height: _height?.evaluate(animation),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(ObjectFlagProperty<Tween<double>>.has('start', _start));
+    description.add(ObjectFlagProperty<Tween<double>>.has('top', _top));
+    description.add(ObjectFlagProperty<Tween<double>>.has('end', _end));
+    description.add(ObjectFlagProperty<Tween<double>>.has('bottom', _bottom));
+    description.add(ObjectFlagProperty<Tween<double>>.has('width', _width));
+    description.add(ObjectFlagProperty<Tween<double>>.has('height', _height));
+  }
+}
+
+/// Animated version of [Opacity] which automatically transitions the child's
+/// opacity over a given duration whenever the given opacity changes.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=QZAvjqOqiLY}
+///
+/// Animating an opacity is relatively expensive because it requires painting
+/// the child into an intermediate buffer.
+///
+/// Here's an illustration of what using this widget looks like, using a [curve]
+/// of [Curves.fastOutSlowIn].
+/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_opacity.mp4}
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// class LogoFade extends StatefulWidget {
+///   @override
+///   createState() => LogoFadeState();
+/// }
+///
+/// class LogoFadeState extends State<LogoFade> {
+///   double opacityLevel = 1.0;
+///
+///   void _changeOpacity() {
+///     setState(() => opacityLevel = opacityLevel == 0 ? 1.0 : 0.0);
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Column(
+///       mainAxisAlignment: MainAxisAlignment.center,
+///       children: [
+///         AnimatedOpacity(
+///           opacity: opacityLevel,
+///           duration: Duration(seconds: 3),
+///           child: FlutterLogo(),
+///         ),
+///         ElevatedButton(
+///           child: Text('Fade Logo'),
+///           onPressed: _changeOpacity,
+///         ),
+///       ],
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AnimatedCrossFade], for fading between two children.
+///  * [AnimatedSwitcher], for fading between many children in sequence.
+///  * [FadeTransition], an explicitly animated version of this widget, where
+///    an [Animation] is provided by the caller instead of being built in.
+///  * [SliverAnimatedOpacity], for automatically transitioning a sliver's
+///    opacity over a given duration whenever the given opacity changes.
+class AnimatedOpacity extends ImplicitlyAnimatedWidget {
+  /// Creates a widget that animates its opacity implicitly.
+  ///
+  /// The [opacity] argument must not be null and must be between 0.0 and 1.0,
+  /// inclusive. The [curve] and [duration] arguments must not be null.
+  const AnimatedOpacity({
+    Key? key,
+    this.child,
+    required this.opacity,
+    Curve curve = Curves.linear,
+    required Duration duration,
+    VoidCallback? onEnd,
+    this.alwaysIncludeSemantics = false,
+  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
+       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// The target opacity.
+  ///
+  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
+  /// (i.e., invisible).
+  ///
+  /// The opacity must not be null.
+  final double opacity;
+
+  /// Whether the semantic information of the children is always included.
+  ///
+  /// Defaults to false.
+  ///
+  /// When true, regardless of the opacity settings the child semantic
+  /// information is exposed as if the widget were fully visible. This is
+  /// useful in cases where labels may be hidden during animations that
+  /// would otherwise contribute relevant semantics.
+  final bool alwaysIncludeSemantics;
+
+  @override
+  _AnimatedOpacityState createState() => _AnimatedOpacityState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('opacity', opacity));
+  }
+}
+
+class _AnimatedOpacityState extends ImplicitlyAnimatedWidgetState<AnimatedOpacity> {
+  Tween<double>? _opacity;
+  late Animation<double> _opacityAnimation;
+
+  @override
+  void forEachTween(TweenVisitor<dynamic> visitor) {
+    _opacity = visitor(_opacity, widget.opacity, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+  }
+
+  @override
+  void didUpdateTweens() {
+    _opacityAnimation = animation.drive(_opacity!);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return FadeTransition(
+      opacity: _opacityAnimation,
+      child: widget.child,
+      alwaysIncludeSemantics: widget.alwaysIncludeSemantics,
+    );
+  }
+}
+
+/// Animated version of [SliverOpacity] which automatically transitions the
+/// sliver child's opacity over a given duration whenever the given opacity
+/// changes.
+///
+/// Animating an opacity is relatively expensive because it requires painting
+/// the sliver child into an intermediate buffer.
+///
+/// Here's an illustration of what using this widget looks like, using a [curve]
+/// of [Curves.fastOutSlowIn].
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_freeform_state_no_null_safety}
+/// Creates a [CustomScrollView] with a [SliverFixedExtentList] and a
+/// [FloatingActionButton]. Pressing the button animates the lists' opacity.
+///
+/// ```dart
+/// class _MyStatefulWidgetState extends State<MyStatefulWidget> with SingleTickerProviderStateMixin {
+///   bool _visible = true;
+///
+///   Widget build(BuildContext context) {
+///     return CustomScrollView(
+///       slivers: <Widget>[
+///         SliverAnimatedOpacity(
+///           opacity: _visible ? 1.0 : 0.0,
+///           duration: Duration(milliseconds: 500),
+///           sliver: SliverFixedExtentList(
+///             itemExtent: 100.0,
+///             delegate: SliverChildBuilderDelegate(
+///               (BuildContext context, int index) {
+///                 return Container(
+///                   color: index % 2 == 0
+///                     ? Colors.indigo[200]
+///                     : Colors.orange[200],
+///                 );
+///               },
+///               childCount: 5,
+///             ),
+///           ),
+///         ),
+///         SliverToBoxAdapter(
+///           child: FloatingActionButton(
+///             onPressed: () {
+///               setState(() {
+///                 _visible = !_visible;
+///               });
+///             },
+///             tooltip: 'Toggle opacity',
+///             child: Icon(Icons.flip),
+///           )
+///         ),
+///       ]
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [SliverFadeTransition], an explicitly animated version of this widget, where
+///    an [Animation] is provided by the caller instead of being built in.
+///  * [AnimatedOpacity], for automatically transitioning a box child's
+///    opacity over a given duration whenever the given opacity changes.
+class SliverAnimatedOpacity extends ImplicitlyAnimatedWidget {
+  /// Creates a widget that animates its opacity implicitly.
+  ///
+  /// The [opacity] argument must not be null and must be between 0.0 and 1.0,
+  /// inclusive. The [curve] and [duration] arguments must not be null.
+  const SliverAnimatedOpacity({
+    Key? key,
+    this.sliver,
+    required this.opacity,
+    Curve curve = Curves.linear,
+    required Duration duration,
+    VoidCallback? onEnd,
+    this.alwaysIncludeSemantics = false,
+  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
+      super(key: key, curve: curve, duration: duration, onEnd: onEnd);
+
+  /// The sliver below this widget in the tree.
+  final Widget? sliver;
+
+  /// The target opacity.
+  ///
+  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
+  /// (i.e., invisible).
+  ///
+  /// The opacity must not be null.
+  final double opacity;
+
+  /// Whether the semantic information of the children is always included.
+  ///
+  /// Defaults to false.
+  ///
+  /// When true, regardless of the opacity settings the sliver child's semantic
+  /// information is exposed as if the widget were fully visible. This is
+  /// useful in cases where labels may be hidden during animations that
+  /// would otherwise contribute relevant semantics.
+  final bool alwaysIncludeSemantics;
+
+  @override
+  _SliverAnimatedOpacityState createState() => _SliverAnimatedOpacityState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('opacity', opacity));
+  }
+}
+
+class _SliverAnimatedOpacityState extends ImplicitlyAnimatedWidgetState<SliverAnimatedOpacity> {
+  Tween<double>? _opacity;
+  late Animation<double> _opacityAnimation;
+
+  @override
+  void forEachTween(TweenVisitor<dynamic> visitor) {
+    _opacity = visitor(_opacity, widget.opacity, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+  }
+
+  @override
+  void didUpdateTweens() {
+    _opacityAnimation = animation.drive(_opacity!);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return SliverFadeTransition(
+      opacity: _opacityAnimation,
+      sliver: widget.sliver,
+      alwaysIncludeSemantics: widget.alwaysIncludeSemantics,
+    );
+  }
+}
+
+/// Animated version of [DefaultTextStyle] which automatically transitions the
+/// default text style (the text style to apply to descendant [Text] widgets
+/// without explicit style) over a given duration whenever the given style
+/// changes.
+///
+/// The [textAlign], [softWrap], [overflow], [maxLines], [textWidthBasis]
+/// and [textHeightBehavior] properties are not animated and take effect
+/// immediately when changed.
+///
+/// Here's an illustration of what using this widget looks like, using a [curve]
+/// of [Curves.elasticInOut].
+/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_default_text_style.mp4}
+///
+/// For the animation, you can choose a [curve] as well as a [duration] and the
+/// widget will automatically animate to the new default text style. If you require
+/// more control over the animation (e.g. if you want to stop it mid-animation),
+/// consider using a [DefaultTextStyleTransition] instead, which takes a provided
+/// [Animation] as argument. While that allows you to fine-tune the animation,
+/// it also requires more development overhead as you have to manually manage
+/// the lifecycle of the underlying [AnimationController].
+class AnimatedDefaultTextStyle extends ImplicitlyAnimatedWidget {
+  /// Creates a widget that animates the default text style implicitly.
+  ///
+  /// The [child], [style], [softWrap], [overflow], [curve], and [duration]
+  /// arguments must not be null.
+  const AnimatedDefaultTextStyle({
+    Key? key,
+    required this.child,
+    required this.style,
+    this.textAlign,
+    this.softWrap = true,
+    this.overflow = TextOverflow.clip,
+    this.maxLines,
+    this.textWidthBasis = TextWidthBasis.parent,
+    this.textHeightBehavior,
+    Curve curve = Curves.linear,
+    required Duration duration,
+    VoidCallback? onEnd,
+  }) : assert(style != null),
+       assert(child != null),
+       assert(softWrap != null),
+       assert(overflow != null),
+       assert(maxLines == null || maxLines > 0),
+       assert(textWidthBasis != null),
+       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The target text style.
+  ///
+  /// The text style must not be null.
+  ///
+  /// When this property is changed, the style will be animated over [duration] time.
+  final TextStyle style;
+
+  /// How the text should be aligned horizontally.
+  ///
+  /// This property takes effect immediately when changed, it is not animated.
+  final TextAlign? textAlign;
+
+  /// Whether the text should break at soft line breaks.
+  ///
+  /// This property takes effect immediately when changed, it is not animated.
+  ///
+  /// See [DefaultTextStyle.softWrap] for more details.
+  final bool softWrap;
+
+  /// How visual overflow should be handled.
+  ///
+  /// This property takes effect immediately when changed, it is not animated.
+  final TextOverflow overflow;
+
+  /// An optional maximum number of lines for the text to span, wrapping if necessary.
+  ///
+  /// This property takes effect immediately when changed, it is not animated.
+  ///
+  /// See [DefaultTextStyle.maxLines] for more details.
+  final int? maxLines;
+
+  /// The strategy to use when calculating the width of the Text.
+  ///
+  /// See [TextWidthBasis] for possible values and their implications.
+  final TextWidthBasis textWidthBasis;
+
+  /// {@macro flutter.dart:ui.textHeightBehavior}
+  final ui.TextHeightBehavior? textHeightBehavior;
+
+  @override
+  _AnimatedDefaultTextStyleState createState() => _AnimatedDefaultTextStyleState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    style.debugFillProperties(properties);
+    properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
+    properties.add(FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true));
+    properties.add(EnumProperty<TextOverflow>('overflow', overflow, defaultValue: null));
+    properties.add(IntProperty('maxLines', maxLines, defaultValue: null));
+    properties.add(EnumProperty<TextWidthBasis>('textWidthBasis', textWidthBasis, defaultValue: TextWidthBasis.parent));
+    properties.add(DiagnosticsProperty<ui.TextHeightBehavior>('textHeightBehavior', textHeightBehavior, defaultValue: null));
+  }
+}
+
+class _AnimatedDefaultTextStyleState extends AnimatedWidgetBaseState<AnimatedDefaultTextStyle> {
+  TextStyleTween? _style;
+
+  @override
+  void forEachTween(TweenVisitor<dynamic> visitor) {
+    _style = visitor(_style, widget.style, (dynamic value) => TextStyleTween(begin: value as TextStyle)) as TextStyleTween?;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return DefaultTextStyle(
+      style: _style!.evaluate(animation),
+      textAlign: widget.textAlign,
+      softWrap: widget.softWrap,
+      overflow: widget.overflow,
+      maxLines: widget.maxLines,
+      textWidthBasis: widget.textWidthBasis,
+      textHeightBehavior: widget.textHeightBehavior,
+      child: widget.child,
+    );
+  }
+}
+
+/// Animated version of [PhysicalModel].
+///
+/// The [borderRadius] and [elevation] are animated.
+///
+/// The [color] is animated if the [animateColor] property is set; otherwise,
+/// the color changes immediately at the start of the animation for the other
+/// two properties. This allows the color to be animated independently (e.g.
+/// because it is being driven by an [AnimatedTheme]).
+///
+/// The [shape] is not animated.
+///
+/// Here's an illustration of what using this widget looks like, using a [curve]
+/// of [Curves.fastOutSlowIn].
+/// {@animation 250 266 https://flutter.github.io/assets-for-api-docs/assets/widgets/animated_physical_model.mp4}
+class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget {
+  /// Creates a widget that animates the properties of a [PhysicalModel].
+  ///
+  /// The [child], [shape], [borderRadius], [elevation], [color], [shadowColor],
+  /// [curve], [clipBehavior], and [duration] arguments must not be null.
+  /// Additionally, [elevation] must be non-negative.
+  ///
+  /// Animating [color] is optional and is controlled by the [animateColor] flag.
+  ///
+  /// Animating [shadowColor] is optional and is controlled by the [animateShadowColor] flag.
+  const AnimatedPhysicalModel({
+    Key? key,
+    required this.child,
+    required this.shape,
+    this.clipBehavior = Clip.none,
+    this.borderRadius = BorderRadius.zero,
+    required this.elevation,
+    required this.color,
+    this.animateColor = true,
+    required this.shadowColor,
+    this.animateShadowColor = true,
+    Curve curve = Curves.linear,
+    required Duration duration,
+    VoidCallback? onEnd,
+  }) : assert(child != null),
+       assert(shape != null),
+       assert(clipBehavior != null),
+       assert(borderRadius != null),
+       assert(elevation != null && elevation >= 0.0),
+       assert(color != null),
+       assert(shadowColor != null),
+       assert(animateColor != null),
+       assert(animateShadowColor != null),
+       super(key: key, curve: curve, duration: duration, onEnd: onEnd);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The type of shape.
+  ///
+  /// This property is not animated.
+  final BoxShape shape;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none].
+  final Clip clipBehavior;
+
+  /// The target border radius of the rounded corners for a rectangle shape.
+  final BorderRadius borderRadius;
+
+  /// The target z-coordinate relative to the parent at which to place this
+  /// physical object.
+  ///
+  /// The value will always be non-negative.
+  final double elevation;
+
+  /// The target background color.
+  final Color color;
+
+  /// Whether the color should be animated.
+  final bool animateColor;
+
+  /// The target shadow color.
+  final Color shadowColor;
+
+  /// Whether the shadow color should be animated.
+  final bool animateShadowColor;
+
+  @override
+  _AnimatedPhysicalModelState createState() => _AnimatedPhysicalModelState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<BoxShape>('shape', shape));
+    properties.add(DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius));
+    properties.add(DoubleProperty('elevation', elevation));
+    properties.add(ColorProperty('color', color));
+    properties.add(DiagnosticsProperty<bool>('animateColor', animateColor));
+    properties.add(ColorProperty('shadowColor', shadowColor));
+    properties.add(DiagnosticsProperty<bool>('animateShadowColor', animateShadowColor));
+  }
+}
+
+class _AnimatedPhysicalModelState extends AnimatedWidgetBaseState<AnimatedPhysicalModel> {
+  BorderRadiusTween? _borderRadius;
+  Tween<double>? _elevation;
+  ColorTween? _color;
+  ColorTween? _shadowColor;
+
+  @override
+  void forEachTween(TweenVisitor<dynamic> visitor) {
+    _borderRadius = visitor(_borderRadius, widget.borderRadius, (dynamic value) => BorderRadiusTween(begin: value as BorderRadius)) as BorderRadiusTween?;
+    _elevation = visitor(_elevation, widget.elevation, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;
+    _color = visitor(_color, widget.color, (dynamic value) => ColorTween(begin: value as Color)) as ColorTween?;
+    _shadowColor = visitor(_shadowColor, widget.shadowColor, (dynamic value) => ColorTween(begin: value as Color)) as ColorTween?;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return PhysicalModel(
+      child: widget.child,
+      shape: widget.shape,
+      clipBehavior: widget.clipBehavior,
+      borderRadius: _borderRadius!.evaluate(animation),
+      elevation: _elevation!.evaluate(animation),
+      color: widget.animateColor ? _color!.evaluate(animation)! : widget.color,
+      shadowColor: widget.animateShadowColor
+          ? _shadowColor!.evaluate(animation)!
+          : widget.shadowColor,
+    );
+  }
+}
diff --git a/lib/src/widgets/inherited_model.dart b/lib/src/widgets/inherited_model.dart
new file mode 100644
index 0000000..98fa8fe
--- /dev/null
+++ b/lib/src/widgets/inherited_model.dart
@@ -0,0 +1,214 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+
+import 'package:flute/foundation.dart';
+
+import 'framework.dart';
+
+/// An [InheritedWidget] that's intended to be used as the base class for
+/// models whose dependents may only depend on one part or "aspect" of the
+/// overall model.
+///
+/// An inherited widget's dependents are unconditionally rebuilt when the
+/// inherited widget changes per [InheritedWidget.updateShouldNotify].
+/// This widget is similar except that dependents aren't rebuilt
+/// unconditionally.
+///
+/// Widgets that depend on an [InheritedModel] qualify their dependence
+/// with a value that indicates what "aspect" of the model they depend
+/// on. When the model is rebuilt, dependents will also be rebuilt, but
+/// only if there was a change in the model that corresponds to the aspect
+/// they provided.
+///
+/// The type parameter `T` is the type of the model aspect objects.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=ml5uefGgkaA}
+///
+/// Widgets create a dependency on an [InheritedModel] with a static method:
+/// [InheritedModel.inheritFrom]. This method's `context` parameter
+/// defines the subtree that will be rebuilt when the model changes.
+/// Typically the `inheritFrom` method is called from a model-specific
+/// static `of` method. For example:
+///
+/// ```dart
+/// class MyModel extends InheritedModel<String> {
+///   // ...
+///   static MyModel of(BuildContext context, String aspect) {
+///     return InheritedModel.inheritFrom<MyModel>(context, aspect: aspect);
+///   }
+/// }
+/// ```
+///
+/// Calling `MyModel.of(context, 'foo')` means that `context` should only
+/// be rebuilt when the `foo` aspect of `MyModel` changes. If the aspect
+/// is null, then the model supports all aspects.
+///
+/// When the inherited model is rebuilt the [updateShouldNotify] and
+/// [updateShouldNotifyDependent] methods are used to decide what
+/// should be rebuilt. If [updateShouldNotify] returns true, then the
+/// inherited model's [updateShouldNotifyDependent] method is tested for
+/// each dependent and the set of aspect objects it depends on.
+/// The [updateShouldNotifyDependent] method must compare the set of aspect
+/// dependencies with the changes in the model itself.
+///
+/// For example:
+///
+/// ```dart
+/// class ABModel extends InheritedModel<String> {
+///   ABModel({ this.a, this.b, Widget child }) : super(child: child);
+///
+///   final int a;
+///   final int b;
+///
+///   @override
+///   bool updateShouldNotify(ABModel old) {
+///     return a != old.a || b != old.b;
+///   }
+///
+///   @override
+///   bool updateShouldNotifyDependent(ABModel old, Set<String> aspects) {
+///     return (a != old.a && aspects.contains('a'))
+///         || (b != old.b && aspects.contains('b'))
+///   }
+///
+///   // ...
+/// }
+/// ```
+///
+/// In the previous example the dependencies checked by
+/// [updateShouldNotifyDependent] are just the aspect strings passed to
+/// `dependOnInheritedWidgetOfExactType`. They're represented as a [Set] because
+/// one Widget can depend on more than one aspect of the model.
+/// If a widget depends on the model but doesn't specify an aspect,
+/// then changes in the model will cause the widget to be rebuilt
+/// unconditionally.
+///
+/// See also:
+///
+///  * [InheritedWidget], an inherited widget that only notifies dependents
+///    when its value is different.
+///  * [InheritedNotifier], an inherited widget whose value can be a
+///    [Listenable], and which will notify dependents whenever the value
+///    sends notifications.
+abstract class InheritedModel<T> extends InheritedWidget {
+  /// Creates an inherited widget that supports dependencies qualified by
+  /// "aspects", i.e. a descendant widget can indicate that it should
+  /// only be rebuilt if a specific aspect of the model changes.
+  const InheritedModel({ Key? key, required Widget child }) : super(key: key, child: child);
+
+  @override
+  InheritedModelElement<T> createElement() => InheritedModelElement<T>(this);
+
+  /// Return true if the changes between this model and [oldWidget] match any
+  /// of the [dependencies].
+  @protected
+  bool updateShouldNotifyDependent(covariant InheritedModel<T> oldWidget, Set<T> dependencies);
+
+  /// Returns true if this model supports the given [aspect].
+  ///
+  /// Returns true by default: this model supports all aspects.
+  ///
+  /// Subclasses may override this method to indicate that they do not support
+  /// all model aspects. This is typically done when a model can be used
+  /// to "shadow" some aspects of an ancestor.
+  @protected
+  bool isSupportedAspect(Object aspect) => true;
+
+  // The [result] will be a list of all of context's type T ancestors concluding
+  // with the one that supports the specified model [aspect].
+  static void _findModels<T extends InheritedModel<Object>>(BuildContext context, Object aspect, List<InheritedElement> results) {
+    final InheritedElement? model = context.getElementForInheritedWidgetOfExactType<T>();
+    if (model == null)
+      return;
+
+    results.add(model);
+
+    assert(model.widget is T);
+    final T modelWidget = model.widget as T;
+    if (modelWidget.isSupportedAspect(aspect))
+      return;
+
+    Element? modelParent;
+    model.visitAncestorElements((Element ancestor) {
+      modelParent = ancestor;
+      return false;
+    });
+    if (modelParent == null)
+      return;
+
+    _findModels<T>(modelParent!, aspect, results);
+  }
+
+  /// Makes [context] dependent on the specified [aspect] of an [InheritedModel]
+  /// of type T.
+  ///
+  /// When the given [aspect] of the model changes, the [context] will be
+  /// rebuilt. The [updateShouldNotifyDependent] method must determine if a
+  /// change in the model widget corresponds to an [aspect] value.
+  ///
+  /// The dependencies created by this method target all [InheritedModel] ancestors
+  /// of type T up to and including the first one for which [isSupportedAspect]
+  /// returns true.
+  ///
+  /// If [aspect] is null this method is the same as
+  /// `context.dependOnInheritedWidgetOfExactType<T>()`.
+  ///
+  /// If no ancestor of type T exists, null is returned.
+  static T? inheritFrom<T extends InheritedModel<Object>>(BuildContext context, { Object? aspect }) {
+    if (aspect == null)
+      return context.dependOnInheritedWidgetOfExactType<T>();
+
+    // Create a dependency on all of the type T ancestor models up until
+    // a model is found for which isSupportedAspect(aspect) is true.
+    final List<InheritedElement> models = <InheritedElement>[];
+    _findModels<T>(context, aspect, models);
+    if (models.isEmpty) {
+      return null;
+    }
+
+    final InheritedElement lastModel = models.last;
+    for (final InheritedElement model in models) {
+      final T value = context.dependOnInheritedElement(model, aspect: aspect) as T;
+      if (model == lastModel)
+        return value;
+    }
+
+    assert(false);
+    return null;
+  }
+}
+
+/// An [Element] that uses a [InheritedModel] as its configuration.
+class InheritedModelElement<T> extends InheritedElement {
+  /// Creates an element that uses the given widget as its configuration.
+  InheritedModelElement(InheritedModel<T> widget) : super(widget);
+
+  @override
+  InheritedModel<T> get widget => super.widget as InheritedModel<T>;
+
+  @override
+  void updateDependencies(Element dependent, Object? aspect) {
+    final Set<T>? dependencies = getDependencies(dependent) as Set<T>?;
+    if (dependencies != null && dependencies.isEmpty)
+      return;
+
+    if (aspect == null) {
+      setDependencies(dependent, HashSet<T>());
+    } else {
+      assert(aspect is T);
+      setDependencies(dependent, (dependencies ?? HashSet<T>())..add(aspect as T));
+    }
+  }
+
+  @override
+  void notifyDependent(InheritedModel<T> oldWidget, Element dependent) {
+    final Set<T>? dependencies = getDependencies(dependent) as Set<T>?;
+    if (dependencies == null)
+      return;
+    if (dependencies.isEmpty || widget.updateShouldNotifyDependent(oldWidget, dependencies))
+      dependent.didChangeDependencies();
+  }
+}
diff --git a/lib/src/widgets/inherited_notifier.dart b/lib/src/widgets/inherited_notifier.dart
new file mode 100644
index 0000000..dd71455
--- /dev/null
+++ b/lib/src/widgets/inherited_notifier.dart
@@ -0,0 +1,207 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'framework.dart';
+
+/// An inherited widget for a [Listenable] [notifier], which updates its
+/// dependencies when the [notifier] is triggered.
+///
+/// This is a variant of [InheritedWidget], specialized for subclasses of
+/// [Listenable], such as [ChangeNotifier] or [ValueNotifier].
+///
+/// Dependents are notified whenever the [notifier] sends notifications, or
+/// whenever the identity of the [notifier] changes.
+///
+/// Multiple notifications are coalesced, so that dependents only rebuild once
+/// even if the [notifier] fires multiple times between two frames.
+///
+/// Typically this class is subclassed with a class that provides an `of` static
+/// method that calls [BuildContext.dependOnInheritedWidgetOfExactType] with that
+/// class.
+///
+/// The [updateShouldNotify] method may also be overridden, to change the logic
+/// in the cases where [notifier] itself is changed. The [updateShouldNotify]
+/// method is called with the old [notifier] in the case of the [notifier] being
+/// changed. When it returns true, the dependents are marked as needing to be
+/// rebuilt this frame.
+///
+/// {@tool dartpad --template=stateful_widget_material_ticker_no_null_safety}
+///
+/// This example shows three spinning squares that use the value of the notifier
+/// on an ancestor [InheritedNotifier] (`SpinModel`) to give them their
+/// rotation. The [InheritedNotifier] doesn't need to know about the children,
+/// and the `notifier` argument doesn't need to be an animation controller, it
+/// can be anything that implements [Listenable] (like a [ChangeNotifier]).
+///
+/// The `SpinModel` class could just as easily listen to another object (say, a
+/// separate object that keeps the value of an input or data model value) that
+/// is a [Listenable], and get the value from that. The descendants also don't
+/// need to have an instance of the [InheritedNotifier] in order to use it, they
+/// just need to know that there is one in their ancestry. This can help with
+/// decoupling widgets from their models.
+///
+/// ```dart imports
+/// import 'dart:math' as math;
+/// ```
+///
+/// ```dart preamble
+/// class SpinModel extends InheritedNotifier<AnimationController> {
+///   SpinModel({
+///     Key key,
+///     AnimationController notifier,
+///     Widget child,
+///   }) : super(key: key, notifier: notifier, child: child);
+///
+///   static double of(BuildContext context) {
+///     return context.dependOnInheritedWidgetOfExactType<SpinModel>().notifier.value;
+///   }
+/// }
+///
+/// class Spinner extends StatelessWidget {
+///   const Spinner();
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Transform.rotate(
+///       angle: SpinModel.of(context) * 2.0 * math.pi,
+///       child: Container(
+///         width: 100,
+///         height: 100,
+///         color: Colors.green,
+///         child: const Center(
+///           child: Text('Whee!'),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// AnimationController _controller;
+///
+/// @override
+/// void initState() {
+///   super.initState();
+///   _controller = AnimationController(
+///     duration: const Duration(seconds: 10),
+///     vsync: this,
+///   )..repeat();
+/// }
+///
+/// @override
+/// void dispose() {
+///   _controller.dispose();
+///   super.dispose();
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return SpinModel(
+///     notifier: _controller,
+///     child: Row(
+///       mainAxisAlignment: MainAxisAlignment.spaceAround,
+///       children: const <Widget>[
+///         Spinner(),
+///         Spinner(),
+///         Spinner(),
+///       ],
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Animation], an implementation of [Listenable] that ticks each frame to
+///    update a value.
+///  * [ViewportOffset] or its subclass [ScrollPosition], implementations of
+///    [Listenable] that trigger when a view is scrolled.
+///  * [InheritedWidget], an inherited widget that only notifies dependents
+///    when its value is different.
+///  * [InheritedModel], an inherited widget that allows clients to subscribe
+///    to changes for subparts of the value.
+abstract class InheritedNotifier<T extends Listenable> extends InheritedWidget {
+  /// Create an inherited widget that updates its dependents when [notifier]
+  /// sends notifications.
+  ///
+  /// The [child] argument must not be null.
+  const InheritedNotifier({
+    Key? key,
+    this.notifier,
+    required Widget child,
+  }) : assert(child != null),
+       super(key: key, child: child);
+
+  /// The [Listenable] object to which to listen.
+  ///
+  /// Whenever this object sends change notifications, the dependents of this
+  /// widget are triggered.
+  ///
+  /// By default, whenever the [notifier] is changed (including when changing to
+  /// or from null), if the old notifier is not equal to the new notifier (as
+  /// determined by the `==` operator), notifications are sent. This behavior
+  /// can be overridden by overriding [updateShouldNotify].
+  ///
+  /// While the [notifier] is null, no notifications are sent, since the null
+  /// object cannot itself send notifications.
+  final T? notifier;
+
+  @override
+  bool updateShouldNotify(InheritedNotifier<T> oldWidget) {
+    return oldWidget.notifier != notifier;
+  }
+
+  @override
+  _InheritedNotifierElement<T> createElement() => _InheritedNotifierElement<T>(this);
+}
+
+class _InheritedNotifierElement<T extends Listenable> extends InheritedElement {
+  _InheritedNotifierElement(InheritedNotifier<T> widget) : super(widget) {
+    widget.notifier?.addListener(_handleUpdate);
+  }
+
+  @override
+  InheritedNotifier<T> get widget => super.widget as InheritedNotifier<T>;
+
+  bool _dirty = false;
+
+  @override
+  void update(InheritedNotifier<T> newWidget) {
+    final T? oldNotifier = widget.notifier;
+    final T? newNotifier = newWidget.notifier;
+    if (oldNotifier != newNotifier) {
+      oldNotifier?.removeListener(_handleUpdate);
+      newNotifier?.addListener(_handleUpdate);
+    }
+    super.update(newWidget);
+  }
+
+  @override
+  Widget build() {
+    if (_dirty)
+      notifyClients(widget);
+    return super.build();
+  }
+
+  void _handleUpdate() {
+    _dirty = true;
+    markNeedsBuild();
+  }
+
+  @override
+  void notifyClients(InheritedNotifier<T> oldWidget) {
+    super.notifyClients(oldWidget);
+    _dirty = false;
+  }
+
+  @override
+  void unmount() {
+    widget.notifier?.removeListener(_handleUpdate);
+    super.unmount();
+  }
+}
diff --git a/lib/src/widgets/inherited_theme.dart b/lib/src/widgets/inherited_theme.dart
new file mode 100644
index 0000000..27508bf
--- /dev/null
+++ b/lib/src/widgets/inherited_theme.dart
@@ -0,0 +1,219 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'framework.dart';
+
+/// An [InheritedWidget] that defines visual properties like colors
+/// and text styles, which the [child]'s subtree depends on.
+///
+/// The [wrap] method is used by [captureAll] and [CapturedThemes.wrap] to
+/// construct a widget that will wrap a child in all of the inherited themes
+/// which are present in a specified part of the widget tree.
+///
+/// A widget that's shown in a different context from the one it's built in,
+/// like the contents of a new route or an overlay, will be able to see the
+/// ancestor inherited themes of the context it was built in.
+///
+/// {@tool dartpad --template=freeform_no_null_safety}
+/// This example demonstrates how `InheritedTheme.capture()` can be used
+/// to wrap the contents of a new route with the inherited themes that
+/// are present when the route was built - but are not present when route
+/// is actually shown.
+///
+/// If the same code is run without `InheritedTheme.capture(), the
+/// new route's Text widget will inherit the "something must be wrong"
+/// fallback text style, rather than the default text style defined in MyApp.
+///
+/// ```dart imports
+/// import 'package:flute/material.dart';
+/// ```
+///
+/// ```dart main
+/// void main() {
+///   runApp(MyApp());
+/// }
+/// ```
+///
+/// ```dart
+/// class MyAppBody extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     final NavigatorState navigator = Navigator.of(context);
+///     // This InheritedTheme.capture() saves references to themes that are
+///     // found above the context provided to this widget's build method
+///     // excluding themes are are found above the navigator. Those themes do
+///     // not have to be captured, because they will already be visible from
+///     // the new route pushed onto said navigator.
+///     // Themes are captured outside of the route's builder because when the
+///     // builder executes, the context may not be valid anymore.
+///     final CapturedThemes themes = InheritedTheme.capture(from: context, to: navigator.context);
+///     return GestureDetector(
+///       onTap: () {
+///         Navigator.of(context).push(
+///           MaterialPageRoute(
+///             builder: (BuildContext _) {
+///               // Wrap the actual child of the route in the previously
+///               // captured themes.
+///               return themes.wrap(Container(
+///                 alignment: Alignment.center,
+///                 color: Colors.white,
+///                 child: Text('Hello World'),
+///               ));
+///             },
+///           ),
+///         );
+///       },
+///       child: Center(child: Text('Tap Here')),
+///     );
+///   }
+/// }
+///
+/// class MyApp extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return MaterialApp(
+///       home: Scaffold(
+///         // Override the DefaultTextStyle defined by the Scaffold.
+///         // Descendant widgets will inherit this big blue text style.
+///         body: DefaultTextStyle(
+///           style: TextStyle(fontSize: 48, color: Colors.blue),
+///           child: MyAppBody(),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+abstract class InheritedTheme extends InheritedWidget {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+
+  const InheritedTheme({
+    Key? key,
+    required Widget child,
+  }) : super(key: key, child: child);
+
+  /// Return a copy of this inherited theme with the specified [child].
+  ///
+  /// This implementation for [TooltipTheme] is typical:
+  ///
+  /// ```dart
+  /// Widget wrap(BuildContext context, Widget child) {
+  ///   return TooltipTheme(data: data, child: child);
+  /// }
+  /// ```
+  Widget wrap(BuildContext context, Widget child);
+
+  /// Returns a widget that will [wrap] `child` in all of the inherited themes
+  /// which are present between `context` and the specified `to`
+  /// [BuildContext].
+  ///
+  /// The `to` context must be an ancestor of `context`. If `to` is not
+  /// specified, all inherited themes up to the root of the widget tree are
+  /// captured.
+  ///
+  /// After calling this method, the themes present between `context` and `to`
+  /// are frozen for the provided `child`. If the themes (or their theme data)
+  /// change in the original subtree, those changes will not be visible to
+  /// the wrapped `child` - unless this method is called again to re-wrap the
+  /// child.
+  static Widget captureAll(BuildContext context, Widget child, {BuildContext? to}) {
+    assert(child != null);
+    assert(context != null);
+
+    return capture(from: context, to: to).wrap(child);
+  }
+
+  /// Returns a [CapturedThemes] object that includes all the [InheritedTheme]s
+  /// between the given `from` and `to` [BuildContext]s.
+  ///
+  /// The `to` context must be an ancestor of the `from` context. If `to` is
+  /// null, all ancestor inherited themes of `from` up to the root of the
+  /// widget tree are captured.
+  ///
+  /// After calling this method, the themes present between `from` and `to` are
+  /// frozen in the returned [CapturedThemes] object. If the themes (or their
+  /// theme data) change in the original subtree, those changes will not be
+  /// applied to the themes captured in the [CapturedThemes] object - unless
+  /// this method is called again to re-capture the updated themes.
+  ///
+  /// To wrap a [Widget] in the captured themes, call [CapturedThemes.wrap].
+  static CapturedThemes capture({ required BuildContext from, required BuildContext? to }) {
+    assert(from != null);
+
+    if (from == to) {
+      // Nothing to capture.
+      return CapturedThemes._(const <InheritedTheme>[]);
+    }
+
+    final List<InheritedTheme> themes = <InheritedTheme>[];
+    final Set<Type> themeTypes = <Type>{};
+    late bool debugDidFindAncestor;
+    assert(() {
+      debugDidFindAncestor = to == null;
+      return true;
+    }());
+    from.visitAncestorElements((Element ancestor) {
+      if (ancestor == to) {
+        assert(() {
+          debugDidFindAncestor = true;
+          return true;
+        }());
+        return false;
+      }
+      if (ancestor is InheritedElement && ancestor.widget is InheritedTheme) {
+        final InheritedTheme theme = ancestor.widget as InheritedTheme;
+        final Type themeType = theme.runtimeType;
+        // Only remember the first theme of any type. This assumes
+        // that inherited themes completely shadow ancestors of the
+        // same type.
+        if (!themeTypes.contains(themeType)) {
+          themeTypes.add(themeType);
+          themes.add(theme);
+        }
+      }
+      return true;
+    });
+
+    assert(debugDidFindAncestor, 'The provided `to` context must be an ancestor of the `from` context.');
+    return CapturedThemes._(themes);
+  }
+}
+
+/// Stores a list of captured [InheritedTheme]s that can be wrapped around a
+/// child [Widget].
+///
+/// Used as return type by [InheritedTheme.capture].
+class CapturedThemes {
+  CapturedThemes._(this._themes);
+
+  final List<InheritedTheme> _themes;
+
+  /// Wraps a `child` [Widget] in the [InheritedTheme]s captured in this object.
+  Widget wrap(Widget child) {
+    return _CaptureAll(themes: _themes, child: child);
+  }
+}
+
+class _CaptureAll extends StatelessWidget {
+  const _CaptureAll({
+    Key? key,
+    required this.themes,
+    required this.child,
+  }) : assert(themes != null), assert(child != null), super(key: key);
+
+  final List<InheritedTheme> themes;
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    Widget wrappedChild = child;
+    for (final InheritedTheme theme in themes)
+      wrappedChild = theme.wrap(context, wrappedChild);
+    return wrappedChild;
+  }
+}
diff --git a/lib/src/widgets/interactive_viewer.dart b/lib/src/widgets/interactive_viewer.dart
new file mode 100644
index 0000000..ba9cd11
--- /dev/null
+++ b/lib/src/widgets/interactive_viewer.dart
@@ -0,0 +1,1268 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/gestures.dart';
+import 'package:flute/physics.dart';
+import 'package:vector_math/vector_math_64.dart' show Quad, Vector3, Matrix4;
+
+import 'basic.dart';
+import 'framework.dart';
+import 'gesture_detector.dart';
+import 'ticker_provider.dart';
+
+/// A widget that enables pan and zoom interactions with its child.
+///
+/// The user can transform the child by dragging to pan or pinching to zoom.
+///
+/// By default, InteractiveViewer may draw outside of its original area of the
+/// screen, such as when a child is zoomed in and increases in size. However, it
+/// will not receive gestures outside of its original area. To prevent
+/// InteractiveViewer from drawing outside of its original size, wrap it in a
+/// [ClipRect]. Or, to prevent dead areas where InteractiveViewer does not
+/// receive gestures, be sure that the InteractiveViewer widget is the size of
+/// the area that should be interactive. See
+/// [flutter-go](https://github.com/justinmc/flutter-go) for an example of
+/// robust positioning of an InteractiveViewer child that works for all screen
+/// sizes and child sizes.
+///
+/// The [child] must not be null.
+///
+/// See also:
+///   * The [Flutter Gallery's transformations demo](https://github.com/flutter/gallery/blob/master/lib/demos/reference/transformations_demo.dart),
+///     which includes the use of InteractiveViewer.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold}
+/// This example shows a simple Container that can be panned and zoomed.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Center(
+///     child: InteractiveViewer(
+///       boundaryMargin: EdgeInsets.all(20.0),
+///       minScale: 0.1,
+///       maxScale: 1.6,
+///       child: Container(
+///         decoration: BoxDecoration(
+///           gradient: LinearGradient(
+///             begin: Alignment.topCenter,
+///             end: Alignment.bottomCenter,
+///             colors: <Color>[Colors.orange, Colors.red],
+///             stops: <double>[0.0, 1.0],
+///           ),
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+@immutable
+class InteractiveViewer extends StatefulWidget {
+  /// Create an InteractiveViewer.
+  ///
+  /// The [child] parameter must not be null.
+  InteractiveViewer({
+    Key? key,
+    this.alignPanAxis = false,
+    this.boundaryMargin = EdgeInsets.zero,
+    this.constrained = true,
+    // These default scale values were eyeballed as reasonable limits for common
+    // use cases.
+    this.maxScale = 2.5,
+    this.minScale = 0.8,
+    this.onInteractionEnd,
+    this.onInteractionStart,
+    this.onInteractionUpdate,
+    this.panEnabled = true,
+    this.scaleEnabled = true,
+    this.transformationController,
+    required this.child,
+  }) : assert(alignPanAxis != null),
+       assert(child != null),
+       assert(constrained != null),
+       assert(minScale != null),
+       assert(minScale > 0),
+       assert(minScale.isFinite),
+       assert(maxScale != null),
+       assert(maxScale > 0),
+       assert(!maxScale.isNaN),
+       assert(maxScale >= minScale),
+       assert(panEnabled != null),
+       assert(scaleEnabled != null),
+       // boundaryMargin must be either fully infinite or fully finite, but not
+       // a mix of both.
+       assert((boundaryMargin.horizontal.isInfinite
+           && boundaryMargin.vertical.isInfinite) || (boundaryMargin.top.isFinite
+           && boundaryMargin.right.isFinite && boundaryMargin.bottom.isFinite
+           && boundaryMargin.left.isFinite)),
+       super(key: key);
+
+  /// If true, panning is only allowed in the direction of the horizontal axis
+  /// or the vertical axis.
+  ///
+  /// In other words, when this is true, diagonal panning is not allowed. A
+  /// single gesture begun along one axis cannot also cause panning along the
+  /// other axis without stopping and beginning a new gesture. This is a common
+  /// pattern in tables where data is displayed in columns and rows.
+  ///
+  /// See also:
+  ///  * [constrained], which has an example of creating a table that uses
+  ///    alignPanAxis.
+  final bool alignPanAxis;
+
+  /// A margin for the visible boundaries of the child.
+  ///
+  /// Any transformation that results in the viewport being able to view outside
+  /// of the boundaries will be stopped at the boundary. The boundaries do not
+  /// rotate with the rest of the scene, so they are always aligned with the
+  /// viewport.
+  ///
+  /// To produce no boundaries at all, pass infinite [EdgeInsets], such as
+  /// `EdgeInsets.all(double.infinity)`.
+  ///
+  /// No edge can be NaN.
+  ///
+  /// Defaults to [EdgeInsets.zero], which results in boundaries that are the
+  /// exact same size and position as the [child].
+  final EdgeInsets boundaryMargin;
+
+  /// The Widget to perform the transformations on.
+  ///
+  /// Cannot be null.
+  final Widget child;
+
+  /// Whether the normal size constraints at this point in the widget tree are
+  /// applied to the child.
+  ///
+  /// If set to false, then the child will be given infinite constraints. This
+  /// is often useful when a child should be bigger than the InteractiveViewer.
+  ///
+  /// For example, for a child which is bigger than the viewport but can be
+  /// panned to reveal parts that were initially offscreen, [constrained] must
+  /// be set to false to allow it to size itself properly. If [constrained] is
+  /// true and the child can only size itself to the viewport, then areas
+  /// initially outside of the viewport will not be able to receive user
+  /// interaction events. If experiencing regions of the child that are not
+  /// receptive to user gestures, make sure [constrained] is false and the child
+  /// is sized properly.
+  ///
+  /// Defaults to true.
+  ///
+  /// {@tool dartpad --template=stateless_widget_scaffold}
+  /// This example shows how to create a pannable table. Because the table is
+  /// larger than the entire screen, setting `constrained` to false is necessary
+  /// to allow it to be drawn to its full size. The parts of the table that
+  /// exceed the screen size can then be panned into view.
+  ///
+  /// ```dart
+  ///   Widget build(BuildContext context) {
+  ///     const int _rowCount = 48;
+  ///     const int _columnCount = 6;
+  ///
+  ///     return InteractiveViewer(
+  ///       alignPanAxis: true,
+  ///       constrained: false,
+  ///       scaleEnabled: false,
+  ///       child: Table(
+  ///         columnWidths: <int, TableColumnWidth>{
+  ///           for (int column = 0; column < _columnCount; column += 1)
+  ///             column: const FixedColumnWidth(200.0),
+  ///         },
+  ///         children: <TableRow>[
+  ///           for (int row = 0; row < _rowCount; row += 1)
+  ///             TableRow(
+  ///               children: <Widget>[
+  ///                 for (int column = 0; column < _columnCount; column += 1)
+  ///                   Container(
+  ///                     height: 26,
+  ///                     color: row % 2 + column % 2 == 1
+  ///                         ? Colors.white
+  ///                         : Colors.grey.withOpacity(0.1),
+  ///                     child: Align(
+  ///                       alignment: Alignment.centerLeft,
+  ///                       child: Text('$row x $column'),
+  ///                     ),
+  ///                   ),
+  ///               ],
+  ///             ),
+  ///         ],
+  ///       ),
+  ///     );
+  ///   }
+  /// ```
+  /// {@end-tool}
+  final bool constrained;
+
+  /// If false, the user will be prevented from panning.
+  ///
+  /// Defaults to true.
+  ///
+  /// See also:
+  ///
+  ///   * [scaleEnabled], which is similar but for scale.
+  final bool panEnabled;
+
+  /// If false, the user will be prevented from scaling.
+  ///
+  /// Defaults to true.
+  ///
+  /// See also:
+  ///
+  ///   * [panEnabled], which is similar but for panning.
+  final bool scaleEnabled;
+
+  /// The maximum allowed scale.
+  ///
+  /// The scale will be clamped between this and [minScale] inclusively.
+  ///
+  /// Defaults to 2.5.
+  ///
+  /// Cannot be null, and must be greater than zero and greater than minScale.
+  final double maxScale;
+
+  /// The minimum allowed scale.
+  ///
+  /// The scale will be clamped between this and [maxScale] inclusively.
+  ///
+  /// Scale is also affected by [boundaryMargin]. If the scale would result in
+  /// viewing beyond the boundary, then it will not be allowed. By default,
+  /// boundaryMargin is EdgeInsets.zero, so scaling below 1.0 will not be
+  /// allowed in most cases without first increasing the boundaryMargin.
+  ///
+  /// Defaults to 0.8.
+  ///
+  /// Cannot be null, and must be a finite number greater than zero and less
+  /// than maxScale.
+  final double minScale;
+
+  /// Called when the user ends a pan or scale gesture on the widget.
+  ///
+  /// At the time this is called, the [TransformationController] will have
+  /// already been updated to reflect the change caused by the interaction.
+  ///
+  /// {@template flutter.widgets.InteractiveViewer.onInteractionEnd}
+  /// Will be called even if the interaction is disabled with
+  /// [panEnabled] or [scaleEnabled].
+  ///
+  /// A [GestureDetector] wrapping the InteractiveViewer will not respond to
+  /// [GestureDetector.onScaleStart], [GestureDetector.onScaleUpdate], and
+  /// [GestureDetector.onScaleEnd]. Use [onInteractionStart],
+  /// [onInteractionUpdate], and [onInteractionEnd] to respond to those
+  /// gestures.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [onInteractionStart], which handles the start of the same interaction.
+  ///  * [onInteractionUpdate], which handles an update to the same interaction.
+  final GestureScaleEndCallback? onInteractionEnd;
+
+  /// Called when the user begins a pan or scale gesture on the widget.
+  ///
+  /// At the time this is called, the [TransformationController] will not have
+  /// changed due to this interaction.
+  ///
+  /// {@macro flutter.widgets.InteractiveViewer.onInteractionEnd}
+  ///
+  /// The coordinates provided in the details' `focalPoint` and
+  /// `localFocalPoint` are normal Flutter event coordinates, not
+  /// InteractiveViewer scene coordinates. See
+  /// [TransformationController.toScene] for how to convert these coordinates to
+  /// scene coordinates relative to the child.
+  ///
+  /// See also:
+  ///
+  ///  * [onInteractionUpdate], which handles an update to the same interaction.
+  ///  * [onInteractionEnd], which handles the end of the same interaction.
+  final GestureScaleStartCallback? onInteractionStart;
+
+  /// Called when the user updates a pan or scale gesture on the widget.
+  ///
+  /// At the time this is called, the [TransformationController] will have
+  /// already been updated to reflect the change caused by the interaction.
+  ///
+  /// {@macro flutter.widgets.InteractiveViewer.onInteractionEnd}
+  ///
+  /// The coordinates provided in the details' `focalPoint` and
+  /// `localFocalPoint` are normal Flutter event coordinates, not
+  /// InteractiveViewer scene coordinates. See
+  /// [TransformationController.toScene] for how to convert these coordinates to
+  /// scene coordinates relative to the child.
+  ///
+  /// See also:
+  ///
+  ///  * [onInteractionStart], which handles the start of the same interaction.
+  ///  * [onInteractionEnd], which handles the end of the same interaction.
+  final GestureScaleUpdateCallback? onInteractionUpdate;
+
+  /// A [TransformationController] for the transformation performed on the
+  /// child.
+  ///
+  /// Whenever the child is transformed, the [Matrix4] value is updated and all
+  /// listeners are notified. If the value is set, InteractiveViewer will update
+  /// to respect the new value.
+  ///
+  /// {@tool dartpad --template=stateful_widget_material_ticker}
+  /// This example shows how transformationController can be used to animate the
+  /// transformation back to its starting position.
+  ///
+  /// ```dart
+  /// final TransformationController _transformationController = TransformationController();
+  /// Animation<Matrix4>? _animationReset;
+  /// late final AnimationController _controllerReset;
+  ///
+  /// void _onAnimateReset() {
+  ///   _transformationController.value = _animationReset!.value;
+  ///   if (!_controllerReset.isAnimating) {
+  ///     _animationReset!.removeListener(_onAnimateReset);
+  ///     _animationReset = null;
+  ///     _controllerReset.reset();
+  ///   }
+  /// }
+  ///
+  /// void _animateResetInitialize() {
+  ///   _controllerReset.reset();
+  ///   _animationReset = Matrix4Tween(
+  ///     begin: _transformationController.value,
+  ///     end: Matrix4.identity(),
+  ///   ).animate(_controllerReset);
+  ///   _animationReset!.addListener(_onAnimateReset);
+  ///   _controllerReset.forward();
+  /// }
+  ///
+  /// // Stop a running reset to home transform animation.
+  /// void _animateResetStop() {
+  ///   _controllerReset.stop();
+  ///   _animationReset?.removeListener(_onAnimateReset);
+  ///   _animationReset = null;
+  ///   _controllerReset.reset();
+  /// }
+  ///
+  /// void _onInteractionStart(ScaleStartDetails details) {
+  ///   // If the user tries to cause a transformation while the reset animation is
+  ///   // running, cancel the reset animation.
+  ///   if (_controllerReset.status == AnimationStatus.forward) {
+  ///     _animateResetStop();
+  ///   }
+  /// }
+  ///
+  /// @override
+  /// void initState() {
+  ///   super.initState();
+  ///   _controllerReset = AnimationController(
+  ///     vsync: this,
+  ///     duration: const Duration(milliseconds: 400),
+  ///   );
+  /// }
+  ///
+  /// @override
+  /// void dispose() {
+  ///   _controllerReset.dispose();
+  ///   super.dispose();
+  /// }
+  ///
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   return Scaffold(
+  ///     backgroundColor: Theme.of(context).colorScheme.primary,
+  ///     appBar: AppBar(
+  ///       automaticallyImplyLeading: false,
+  ///       title: const Text('Controller demo'),
+  ///     ),
+  ///     body: Center(
+  ///       child: InteractiveViewer(
+  ///         boundaryMargin: EdgeInsets.all(double.infinity),
+  ///         transformationController: _transformationController,
+  ///         minScale: 0.1,
+  ///         maxScale: 1.0,
+  ///         onInteractionStart: _onInteractionStart,
+  ///         child: Container(
+  ///           decoration: BoxDecoration(
+  ///             gradient: LinearGradient(
+  ///               begin: Alignment.topCenter,
+  ///               end: Alignment.bottomCenter,
+  ///               colors: <Color>[Colors.orange, Colors.red],
+  ///               stops: <double>[0.0, 1.0],
+  ///             ),
+  ///           ),
+  ///         ),
+  ///       ),
+  ///     ),
+  ///     persistentFooterButtons: [
+  ///       IconButton(
+  ///         onPressed: _animateResetInitialize,
+  ///         tooltip: 'Reset',
+  ///         color: Theme.of(context).colorScheme.surface,
+  ///         icon: const Icon(Icons.replay),
+  ///       ),
+  ///     ],
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [ValueNotifier], the parent class of TransformationController.
+  ///  * [TextEditingController] for an example of another similar pattern.
+  final TransformationController? transformationController;
+
+  /// Returns the closest point to the given point on the given line segment.
+  @visibleForTesting
+  static Vector3 getNearestPointOnLine(Vector3 point, Vector3 l1, Vector3 l2) {
+    final double lengthSquared = math.pow(l2.x - l1.x, 2.0).toDouble()
+        + math.pow(l2.y - l1.y, 2.0).toDouble();
+
+    // In this case, l1 == l2.
+    if (lengthSquared == 0) {
+      return l1;
+    }
+
+    // Calculate how far down the line segment the closest point is and return
+    // the point.
+    final Vector3 l1P = point - l1;
+    final Vector3 l1L2 = l2 - l1;
+    final double fraction = (l1P.dot(l1L2) / lengthSquared).clamp(0.0, 1.0).toDouble();
+    return l1 + l1L2 * fraction;
+  }
+
+  /// Given a quad, return its axis aligned bounding box.
+  @visibleForTesting
+  static Quad getAxisAlignedBoundingBox(Quad quad) {
+    final double minX = math.min(
+      quad.point0.x,
+      math.min(
+        quad.point1.x,
+        math.min(
+          quad.point2.x,
+          quad.point3.x,
+        ),
+      ),
+    );
+    final double minY = math.min(
+      quad.point0.y,
+      math.min(
+        quad.point1.y,
+        math.min(
+          quad.point2.y,
+          quad.point3.y,
+        ),
+      ),
+    );
+    final double maxX = math.max(
+      quad.point0.x,
+      math.max(
+        quad.point1.x,
+        math.max(
+          quad.point2.x,
+          quad.point3.x,
+        ),
+      ),
+    );
+    final double maxY = math.max(
+      quad.point0.y,
+      math.max(
+        quad.point1.y,
+        math.max(
+          quad.point2.y,
+          quad.point3.y,
+        ),
+      ),
+    );
+    return Quad.points(
+      Vector3(minX, minY, 0),
+      Vector3(maxX, minY, 0),
+      Vector3(maxX, maxY, 0),
+      Vector3(minX, maxY, 0),
+    );
+  }
+
+  /// Returns true iff the point is inside the rectangle given by the Quad,
+  /// inclusively.
+  /// Algorithm from https://math.stackexchange.com/a/190373.
+  @visibleForTesting
+  static bool pointIsInside(Vector3 point, Quad quad) {
+    final Vector3 aM = point - quad.point0;
+    final Vector3 aB = quad.point1 - quad.point0;
+    final Vector3 aD = quad.point3 - quad.point0;
+
+    final double aMAB = aM.dot(aB);
+    final double aBAB = aB.dot(aB);
+    final double aMAD = aM.dot(aD);
+    final double aDAD = aD.dot(aD);
+
+    return 0 <= aMAB && aMAB <= aBAB && 0 <= aMAD && aMAD <= aDAD;
+  }
+
+  /// Get the point inside (inclusively) the given Quad that is nearest to the
+  /// given Vector3.
+  @visibleForTesting
+  static Vector3 getNearestPointInside(Vector3 point, Quad quad) {
+    // If the point is inside the axis aligned bounding box, then it's ok where
+    // it is.
+    if (pointIsInside(point, quad)) {
+      return point;
+    }
+
+    // Otherwise, return the nearest point on the quad.
+    final List<Vector3> closestPoints = <Vector3>[
+      InteractiveViewer.getNearestPointOnLine(point, quad.point0, quad.point1),
+      InteractiveViewer.getNearestPointOnLine(point, quad.point1, quad.point2),
+      InteractiveViewer.getNearestPointOnLine(point, quad.point2, quad.point3),
+      InteractiveViewer.getNearestPointOnLine(point, quad.point3, quad.point0),
+    ];
+    double minDistance = double.infinity;
+    late Vector3 closestOverall;
+    for (final Vector3 closePoint in closestPoints) {
+      final double distance = math.sqrt(
+        math.pow(point.x - closePoint.x, 2) + math.pow(point.y - closePoint.y, 2),
+      );
+      if (distance < minDistance) {
+        minDistance = distance;
+        closestOverall = closePoint;
+      }
+    }
+    return closestOverall;
+  }
+
+  @override _InteractiveViewerState createState() => _InteractiveViewerState();
+}
+
+class _InteractiveViewerState extends State<InteractiveViewer> with TickerProviderStateMixin {
+  TransformationController? _transformationController;
+
+  final GlobalKey _childKey = GlobalKey();
+  final GlobalKey _parentKey = GlobalKey();
+  Animation<Offset>? _animation;
+  late AnimationController _controller;
+  Axis? _panAxis; // Used with alignPanAxis.
+  Offset? _referenceFocalPoint; // Point where the current gesture began.
+  double? _scaleStart; // Scale value at start of scaling gesture.
+  double? _rotationStart = 0.0; // Rotation at start of rotation gesture.
+  double _currentRotation = 0.0; // Rotation of _transformationController.value.
+  _GestureType? _gestureType;
+
+  // TODO(justinmc): Add rotateEnabled parameter to the widget and remove this
+  // hardcoded value when the rotation feature is implemented.
+  // https://github.com/flutter/flutter/issues/57698
+  final bool _rotateEnabled = false;
+
+  // Used as the coefficient of friction in the inertial translation animation.
+  // This value was eyeballed to give a feel similar to Google Photos.
+  static const double _kDrag = 0.0000135;
+
+  // The _boundaryRect is calculated by adding the boundaryMargin to the size of
+  // the child.
+  Rect get _boundaryRect {
+    assert(_childKey.currentContext != null);
+    assert(!widget.boundaryMargin.left.isNaN);
+    assert(!widget.boundaryMargin.right.isNaN);
+    assert(!widget.boundaryMargin.top.isNaN);
+    assert(!widget.boundaryMargin.bottom.isNaN);
+
+    final RenderBox childRenderBox = _childKey.currentContext!.findRenderObject()! as RenderBox;
+    final Size childSize = childRenderBox.size;
+    final Rect boundaryRect = widget.boundaryMargin.inflateRect(Offset.zero & childSize);
+    // Boundaries that are partially infinite are not allowed because Matrix4's
+    // rotation and translation methods don't handle infinites well.
+    assert(boundaryRect.isFinite ||
+        (boundaryRect.left.isInfinite
+        && boundaryRect.top.isInfinite
+        && boundaryRect.right.isInfinite
+        && boundaryRect.bottom.isInfinite), 'boundaryRect must either be infinite in all directions or finite in all directions.');
+    return boundaryRect;
+  }
+
+  // The Rect representing the child's parent.
+  Rect get _viewport {
+    assert(_parentKey.currentContext != null);
+    final RenderBox parentRenderBox = _parentKey.currentContext!.findRenderObject()! as RenderBox;
+    return Offset.zero & parentRenderBox.size;
+  }
+
+  // Return a new matrix representing the given matrix after applying the given
+  // translation.
+  Matrix4 _matrixTranslate(Matrix4 matrix, Offset translation) {
+    if (translation == Offset.zero) {
+      return matrix.clone();
+    }
+
+    final Offset alignedTranslation = widget.alignPanAxis && _panAxis != null
+      ? _alignAxis(translation, _panAxis!)
+      : translation;
+
+    final Matrix4 nextMatrix = matrix.clone()..translate(
+      alignedTranslation.dx,
+      alignedTranslation.dy,
+    );
+
+    // Transform the viewport to determine where its four corners will be after
+    // the child has been transformed.
+    final Quad nextViewport = _transformViewport(nextMatrix, _viewport);
+
+    // If the boundaries are infinite, then no need to check if the translation
+    // fits within them.
+    if (_boundaryRect.isInfinite) {
+      return nextMatrix;
+    }
+
+    // Expand the boundaries with rotation. This prevents the problem where a
+    // mismatch in orientation between the viewport and boundaries effectively
+    // limits translation. With this approach, all points that are visible with
+    // no rotation are visible after rotation.
+    final Quad boundariesAabbQuad = _getAxisAlignedBoundingBoxWithRotation(
+      _boundaryRect,
+      _currentRotation,
+    );
+
+    // If the given translation fits completely within the boundaries, allow it.
+    final Offset offendingDistance = _exceedsBy(boundariesAabbQuad, nextViewport);
+    if (offendingDistance == Offset.zero) {
+      return nextMatrix;
+    }
+
+    // Desired translation goes out of bounds, so translate to the nearest
+    // in-bounds point instead.
+    final Offset nextTotalTranslation = _getMatrixTranslation(nextMatrix);
+    final double currentScale = matrix.getMaxScaleOnAxis();
+    final Offset correctedTotalTranslation = Offset(
+      nextTotalTranslation.dx - offendingDistance.dx * currentScale,
+      nextTotalTranslation.dy - offendingDistance.dy * currentScale,
+    );
+    // TODO(justinmc): This needs some work to handle rotation properly. The
+    // idea is that the boundaries are axis aligned (boundariesAabbQuad), but
+    // calculating the translation to put the viewport inside that Quad is more
+    // complicated than this when rotated.
+     // https://github.com/flutter/flutter/issues/57698
+    final Matrix4 correctedMatrix = matrix.clone()..setTranslation(Vector3(
+      correctedTotalTranslation.dx,
+      correctedTotalTranslation.dy,
+      0.0,
+    ));
+
+    // Double check that the corrected translation fits.
+    final Quad correctedViewport = _transformViewport(correctedMatrix, _viewport);
+    final Offset offendingCorrectedDistance = _exceedsBy(boundariesAabbQuad, correctedViewport);
+    if (offendingCorrectedDistance == Offset.zero) {
+      return correctedMatrix;
+    }
+
+    // If the corrected translation doesn't fit in either direction, don't allow
+    // any translation at all. This happens when the viewport is larger than the
+    // entire boundary.
+    if (offendingCorrectedDistance.dx != 0.0 && offendingCorrectedDistance.dy != 0.0) {
+      return matrix.clone();
+    }
+
+    // Otherwise, allow translation in only the direction that fits. This
+    // happens when the viewport is larger than the boundary in one direction.
+    final Offset unidirectionalCorrectedTotalTranslation = Offset(
+      offendingCorrectedDistance.dx == 0.0 ? correctedTotalTranslation.dx : 0.0,
+      offendingCorrectedDistance.dy == 0.0 ? correctedTotalTranslation.dy : 0.0,
+    );
+    return matrix.clone()..setTranslation(Vector3(
+      unidirectionalCorrectedTotalTranslation.dx,
+      unidirectionalCorrectedTotalTranslation.dy,
+      0.0,
+    ));
+  }
+
+  // Return a new matrix representing the given matrix after applying the given
+  // scale.
+  Matrix4 _matrixScale(Matrix4 matrix, double scale) {
+    if (scale == 1.0) {
+      return matrix.clone();
+    }
+    assert(scale != 0.0);
+
+    // Don't allow a scale that results in an overall scale beyond min/max
+    // scale.
+    final double currentScale = _transformationController!.value.getMaxScaleOnAxis();
+    final double totalScale = math.max(
+      currentScale * scale,
+      // Ensure that the scale cannot make the child so big that it can't fit
+      // inside the boundaries (in either direction).
+      math.max(
+        _viewport.width / _boundaryRect.width,
+        _viewport.height / _boundaryRect.height,
+      ),
+    );
+    final double clampedTotalScale = totalScale.clamp(
+      widget.minScale,
+      widget.maxScale,
+    );
+    final double clampedScale = clampedTotalScale / currentScale;
+    return matrix.clone()..scale(clampedScale);
+  }
+
+  // Return a new matrix representing the given matrix after applying the given
+  // rotation.
+  Matrix4 _matrixRotate(Matrix4 matrix, double rotation, Offset focalPoint) {
+    if (rotation == 0) {
+      return matrix.clone();
+    }
+    final Offset focalPointScene = _transformationController!.toScene(
+      focalPoint,
+    );
+    return matrix
+      .clone()
+      ..translate(focalPointScene.dx, focalPointScene.dy)
+      ..rotateZ(-rotation)
+      ..translate(-focalPointScene.dx, -focalPointScene.dy);
+  }
+
+  // Returns true iff the given _GestureType is enabled.
+  bool _gestureIsSupported(_GestureType? gestureType) {
+    switch (gestureType) {
+      case _GestureType.rotate:
+        return _rotateEnabled;
+
+      case _GestureType.scale:
+        return widget.scaleEnabled;
+
+      case _GestureType.pan:
+      case null:
+        return widget.panEnabled;
+    }
+  }
+
+  // Decide which type of gesture this is by comparing the amount of scale
+  // and rotation in the gesture, if any. Scale starts at 1 and rotation
+  // starts at 0. Pan will have no scale and no rotation because it uses only one
+  // finger.
+  _GestureType _getGestureType(ScaleUpdateDetails details) {
+    final double scale = !widget.scaleEnabled ? 1.0 : details.scale;
+    final double rotation = !_rotateEnabled ? 0.0 : details.rotation;
+    if ((scale - 1).abs() > rotation.abs()) {
+      return _GestureType.scale;
+    } else if (rotation != 0.0) {
+      return _GestureType.rotate;
+    } else {
+      return _GestureType.pan;
+    }
+  }
+
+  // Handle the start of a gesture. All of pan, scale, and rotate are handled
+  // with GestureDetector's scale gesture.
+  void _onScaleStart(ScaleStartDetails details) {
+    widget.onInteractionStart?.call(details);
+
+    if (_controller.isAnimating) {
+      _controller.stop();
+      _controller.reset();
+      _animation?.removeListener(_onAnimate);
+      _animation = null;
+    }
+
+    _gestureType = null;
+    _panAxis = null;
+    _scaleStart = _transformationController!.value.getMaxScaleOnAxis();
+    _referenceFocalPoint = _transformationController!.toScene(
+      details.localFocalPoint,
+    );
+    _rotationStart = _currentRotation;
+  }
+
+  // Handle an update to an ongoing gesture. All of pan, scale, and rotate are
+  // handled with GestureDetector's scale gesture.
+  void _onScaleUpdate(ScaleUpdateDetails details) {
+    final double scale = _transformationController!.value.getMaxScaleOnAxis();
+    final Offset focalPointScene = _transformationController!.toScene(
+      details.localFocalPoint,
+    );
+
+    if (_gestureType == _GestureType.pan) {
+      // When a gesture first starts, it sometimes has no change in scale and
+      // rotation despite being a two-finger gesture. Here the gesture is
+      // allowed to be reinterpreted as its correct type after originally
+      // being marked as a pan.
+      _gestureType = _getGestureType(details);
+    } else {
+      _gestureType ??= _getGestureType(details);
+    }
+    if (!_gestureIsSupported(_gestureType)) {
+      return;
+    }
+
+    switch (_gestureType!) {
+      case _GestureType.scale:
+        assert(_scaleStart != null);
+        // details.scale gives us the amount to change the scale as of the
+        // start of this gesture, so calculate the amount to scale as of the
+        // previous call to _onScaleUpdate.
+        final double desiredScale = _scaleStart! * details.scale;
+        final double scaleChange = desiredScale / scale;
+        _transformationController!.value = _matrixScale(
+          _transformationController!.value,
+          scaleChange,
+        );
+
+        // While scaling, translate such that the user's two fingers stay on
+        // the same places in the scene. That means that the focal point of
+        // the scale should be on the same place in the scene before and after
+        // the scale.
+        final Offset focalPointSceneScaled = _transformationController!.toScene(
+          details.localFocalPoint,
+        );
+        _transformationController!.value = _matrixTranslate(
+          _transformationController!.value,
+          focalPointSceneScaled - _referenceFocalPoint!,
+        );
+
+        // details.localFocalPoint should now be at the same location as the
+        // original _referenceFocalPoint point. If it's not, that's because
+        // the translate came in contact with a boundary. In that case, update
+        // _referenceFocalPoint so subsequent updates happen in relation to
+        // the new effective focal point.
+        final Offset focalPointSceneCheck = _transformationController!.toScene(
+          details.localFocalPoint,
+        );
+        if (_round(_referenceFocalPoint!) != _round(focalPointSceneCheck)) {
+          _referenceFocalPoint = focalPointSceneCheck;
+        }
+        break;
+
+      case _GestureType.rotate:
+        if (details.rotation == 0.0) {
+          return;
+        }
+        final double desiredRotation = _rotationStart! + details.rotation;
+        _transformationController!.value = _matrixRotate(
+          _transformationController!.value,
+          _currentRotation - desiredRotation,
+          details.localFocalPoint,
+        );
+        _currentRotation = desiredRotation;
+        break;
+
+      case _GestureType.pan:
+        assert(_referenceFocalPoint != null);
+        // details may have a change in scale here when scaleEnabled is false.
+        // In an effort to keep the behavior similar whether or not scaleEnabled
+        // is true, these gestures are thrown away.
+        if (details.scale != 1.0) {
+          return;
+        }
+        _panAxis ??= _getPanAxis(_referenceFocalPoint!, focalPointScene);
+        // Translate so that the same point in the scene is underneath the
+        // focal point before and after the movement.
+        final Offset translationChange = focalPointScene - _referenceFocalPoint!;
+        _transformationController!.value = _matrixTranslate(
+          _transformationController!.value,
+          translationChange,
+        );
+        _referenceFocalPoint = _transformationController!.toScene(
+          details.localFocalPoint,
+        );
+        break;
+    }
+    widget.onInteractionUpdate?.call(ScaleUpdateDetails(
+      focalPoint: details.focalPoint,
+      localFocalPoint: details.localFocalPoint,
+      scale: details.scale,
+      rotation: details.rotation,
+    ));
+  }
+
+  // Handle the end of a gesture of _GestureType. All of pan, scale, and rotate
+  // are handled with GestureDetector's scale gesture.
+  void _onScaleEnd(ScaleEndDetails details) {
+    widget.onInteractionEnd?.call(details);
+    _scaleStart = null;
+    _rotationStart = null;
+    _referenceFocalPoint = null;
+
+    _animation?.removeListener(_onAnimate);
+    _controller.reset();
+
+    if (!_gestureIsSupported(_gestureType)) {
+      _panAxis = null;
+      return;
+    }
+
+    // If the scale ended with enough velocity, animate inertial movement.
+    if (_gestureType != _GestureType.pan || details.velocity.pixelsPerSecond.distance < kMinFlingVelocity) {
+      _panAxis = null;
+      return;
+    }
+
+    final Vector3 translationVector = _transformationController!.value.getTranslation();
+    final Offset translation = Offset(translationVector.x, translationVector.y);
+    final FrictionSimulation frictionSimulationX = FrictionSimulation(
+      _kDrag,
+      translation.dx,
+      details.velocity.pixelsPerSecond.dx,
+    );
+    final FrictionSimulation frictionSimulationY = FrictionSimulation(
+      _kDrag,
+      translation.dy,
+      details.velocity.pixelsPerSecond.dy,
+    );
+    final double tFinal = _getFinalTime(
+      details.velocity.pixelsPerSecond.distance,
+      _kDrag,
+    );
+    _animation = Tween<Offset>(
+      begin: translation,
+      end: Offset(frictionSimulationX.finalX, frictionSimulationY.finalX),
+    ).animate(CurvedAnimation(
+      parent: _controller,
+      curve: Curves.decelerate,
+    ));
+    _controller.duration = Duration(milliseconds: (tFinal * 1000).round());
+    _animation!.addListener(_onAnimate);
+    _controller.forward();
+  }
+
+  // Handle mousewheel scroll events.
+  void _receivedPointerSignal(PointerSignalEvent event) {
+    if (event is PointerScrollEvent) {
+      widget.onInteractionStart?.call(
+        ScaleStartDetails(
+          focalPoint: event.position,
+          localFocalPoint: event.localPosition,
+        ),
+      );
+      if (!_gestureIsSupported(_GestureType.scale)) {
+        widget.onInteractionEnd?.call(ScaleEndDetails());
+        return;
+      }
+      final RenderBox childRenderBox = _childKey.currentContext!.findRenderObject()! as RenderBox;
+      final Size childSize = childRenderBox.size;
+      final double scaleChange = 1.0 - event.scrollDelta.dy / childSize.height;
+      if (scaleChange == 0.0) {
+        return;
+      }
+      final Offset focalPointScene = _transformationController!.toScene(
+        event.localPosition,
+      );
+
+      _transformationController!.value = _matrixScale(
+        _transformationController!.value,
+        scaleChange,
+      );
+
+      // After scaling, translate such that the event's position is at the
+      // same scene point before and after the scale.
+      final Offset focalPointSceneScaled = _transformationController!.toScene(
+        event.localPosition,
+      );
+      _transformationController!.value = _matrixTranslate(
+        _transformationController!.value,
+        focalPointSceneScaled - focalPointScene,
+      );
+
+      widget.onInteractionUpdate?.call(ScaleUpdateDetails(
+        focalPoint: event.position,
+        localFocalPoint: event.localPosition,
+        rotation: 0.0,
+        scale: scaleChange,
+        horizontalScale: 1.0,
+        verticalScale: 1.0,
+      ));
+      widget.onInteractionEnd?.call(ScaleEndDetails());
+    }
+  }
+
+  // Handle inertia drag animation.
+  void _onAnimate() {
+    if (!_controller.isAnimating) {
+      _panAxis = null;
+      _animation?.removeListener(_onAnimate);
+      _animation = null;
+      _controller.reset();
+      return;
+    }
+    // Translate such that the resulting translation is _animation.value.
+    final Vector3 translationVector = _transformationController!.value.getTranslation();
+    final Offset translation = Offset(translationVector.x, translationVector.y);
+    final Offset translationScene = _transformationController!.toScene(
+      translation,
+    );
+    final Offset animationScene = _transformationController!.toScene(
+      _animation!.value,
+    );
+    final Offset translationChangeScene = animationScene - translationScene;
+    _transformationController!.value = _matrixTranslate(
+      _transformationController!.value,
+      translationChangeScene,
+    );
+  }
+
+  void _onTransformationControllerChange() {
+    // A change to the TransformationController's value is a change to the
+    // state.
+    setState(() {});
+  }
+
+  @override
+  void initState() {
+    super.initState();
+
+    _transformationController = widget.transformationController
+        ?? TransformationController();
+    _transformationController!.addListener(_onTransformationControllerChange);
+    _controller = AnimationController(
+      vsync: this,
+    );
+  }
+
+  @override
+  void didUpdateWidget(InteractiveViewer oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    // Handle all cases of needing to dispose and initialize
+    // transformationControllers.
+    if (oldWidget.transformationController == null) {
+      if (widget.transformationController != null) {
+        _transformationController!.removeListener(_onTransformationControllerChange);
+        _transformationController!.dispose();
+        _transformationController = widget.transformationController;
+        _transformationController!.addListener(_onTransformationControllerChange);
+      }
+    } else {
+      if (widget.transformationController == null) {
+        _transformationController!.removeListener(_onTransformationControllerChange);
+        _transformationController = TransformationController();
+        _transformationController!.addListener(_onTransformationControllerChange);
+      } else if (widget.transformationController != oldWidget.transformationController) {
+        _transformationController!.removeListener(_onTransformationControllerChange);
+        _transformationController = widget.transformationController;
+        _transformationController!.addListener(_onTransformationControllerChange);
+      }
+    }
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    _transformationController!.removeListener(_onTransformationControllerChange);
+    if (widget.transformationController == null) {
+      _transformationController!.dispose();
+    }
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    Widget child = Transform(
+      transform: _transformationController!.value,
+      child: KeyedSubtree(
+        key: _childKey,
+        child: widget.child,
+      ),
+    );
+
+    if (!widget.constrained) {
+      child = ClipRect(
+        child: OverflowBox(
+          alignment: Alignment.topLeft,
+          minWidth: 0.0,
+          minHeight: 0.0,
+          maxWidth: double.infinity,
+          maxHeight: double.infinity,
+          child: child,
+        ),
+      );
+    }
+
+    // A GestureDetector allows the detection of panning and zooming gestures on
+    // the child.
+    return Listener(
+      key: _parentKey,
+      onPointerSignal: _receivedPointerSignal,
+      child: GestureDetector(
+        behavior: HitTestBehavior.opaque, // Necessary when panning off screen.
+        onScaleEnd: _onScaleEnd,
+        onScaleStart: _onScaleStart,
+        onScaleUpdate: _onScaleUpdate,
+        child: child,
+      ),
+    );
+  }
+}
+
+/// A thin wrapper on [ValueNotifier] whose value is a [Matrix4] representing a
+/// transformation.
+///
+/// The [value] defaults to the identity matrix, which corresponds to no
+/// transformation.
+///
+/// See also:
+///
+///  * [InteractiveViewer.transformationController] for detailed documentation
+///    on how to use TransformationController with [InteractiveViewer].
+class TransformationController extends ValueNotifier<Matrix4> {
+  /// Create an instance of [TransformationController].
+  ///
+  /// The [value] defaults to the identity matrix, which corresponds to no
+  /// transformation.
+  TransformationController([Matrix4? value]) : super(value ?? Matrix4.identity());
+
+  /// Return the scene point at the given viewport point.
+  ///
+  /// A viewport point is relative to the parent while a scene point is relative
+  /// to the child, regardless of transformation. Calling toScene with a
+  /// viewport point essentially returns the scene coordinate that lies
+  /// underneath the viewport point given the transform.
+  ///
+  /// The viewport transforms as the inverse of the child (i.e. moving the child
+  /// left is equivalent to moving the viewport right).
+  ///
+  /// This method is often useful when determining where an event on the parent
+  /// occurs on the child. This example shows how to determine where a tap on
+  /// the parent occurred on the child.
+  ///
+  /// ```dart
+  /// @override
+  /// void build(BuildContext context) {
+  ///   return GestureDetector(
+  ///     onTapUp: (TapUpDetails details) {
+  ///       _childWasTappedAt = _transformationController.toScene(
+  ///         details.localPosition,
+  ///       );
+  ///     },
+  ///     child: InteractiveViewer(
+  ///       transformationController: _transformationController,
+  ///       child: child,
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  Offset toScene(Offset viewportPoint) {
+    // On viewportPoint, perform the inverse transformation of the scene to get
+    // where the point would be in the scene before the transformation.
+    final Matrix4 inverseMatrix = Matrix4.inverted(value);
+    final Vector3 untransformed = inverseMatrix.transform3(Vector3(
+      viewportPoint.dx,
+      viewportPoint.dy,
+      0,
+    ));
+    return Offset(untransformed.x, untransformed.y);
+  }
+}
+
+// A classification of relevant user gestures. Each contiguous user gesture is
+// represented by exactly one _GestureType.
+enum _GestureType {
+  pan,
+  scale,
+  rotate,
+}
+
+// Given a velocity and drag, calculate the time at which motion will come to
+// a stop, within the margin of effectivelyMotionless.
+double _getFinalTime(double velocity, double drag) {
+  const double effectivelyMotionless = 10.0;
+  return math.log(effectivelyMotionless / velocity) / math.log(drag / 100);
+}
+
+// Return the translation from the given Matrix4 as an Offset.
+Offset _getMatrixTranslation(Matrix4 matrix) {
+  final Vector3 nextTranslation = matrix.getTranslation();
+  return Offset(nextTranslation.x, nextTranslation.y);
+}
+
+// Transform the four corners of the viewport by the inverse of the given
+// matrix. This gives the viewport after the child has been transformed by the
+// given matrix. The viewport transforms as the inverse of the child (i.e.
+// moving the child left is equivalent to moving the viewport right).
+Quad _transformViewport(Matrix4 matrix, Rect viewport) {
+  final Matrix4 inverseMatrix = matrix.clone()..invert();
+  return Quad.points(
+    inverseMatrix.transform3(Vector3(
+      viewport.topLeft.dx,
+      viewport.topLeft.dy,
+      0.0,
+    )),
+    inverseMatrix.transform3(Vector3(
+      viewport.topRight.dx,
+      viewport.topRight.dy,
+      0.0,
+    )),
+    inverseMatrix.transform3(Vector3(
+      viewport.bottomRight.dx,
+      viewport.bottomRight.dy,
+      0.0,
+    )),
+    inverseMatrix.transform3(Vector3(
+      viewport.bottomLeft.dx,
+      viewport.bottomLeft.dy,
+      0.0,
+    )),
+  );
+}
+
+// Find the axis aligned bounding box for the rect rotated about its center by
+// the given amount.
+Quad _getAxisAlignedBoundingBoxWithRotation(Rect rect, double rotation) {
+  final Matrix4 rotationMatrix = Matrix4.identity()
+      ..translate(rect.size.width / 2, rect.size.height / 2)
+      ..rotateZ(rotation)
+      ..translate(-rect.size.width / 2, -rect.size.height / 2);
+  final Quad boundariesRotated = Quad.points(
+    rotationMatrix.transform3(Vector3(rect.left, rect.top, 0.0)),
+    rotationMatrix.transform3(Vector3(rect.right, rect.top, 0.0)),
+    rotationMatrix.transform3(Vector3(rect.right, rect.bottom, 0.0)),
+    rotationMatrix.transform3(Vector3(rect.left, rect.bottom, 0.0)),
+  );
+  return InteractiveViewer.getAxisAlignedBoundingBox(boundariesRotated);
+}
+
+// Return the amount that viewport lies outside of boundary. If the viewport
+// is completely contained within the boundary (inclusively), then returns
+// Offset.zero.
+Offset _exceedsBy(Quad boundary, Quad viewport) {
+  final List<Vector3> viewportPoints = <Vector3>[
+    viewport.point0, viewport.point1, viewport.point2, viewport.point3,
+  ];
+  Offset largestExcess = Offset.zero;
+  for (final Vector3 point in viewportPoints) {
+    final Vector3 pointInside = InteractiveViewer.getNearestPointInside(point, boundary);
+    final Offset excess = Offset(
+      pointInside.x - point.x,
+      pointInside.y - point.y,
+    );
+    if (excess.dx.abs() > largestExcess.dx.abs()) {
+      largestExcess = Offset(excess.dx, largestExcess.dy);
+    }
+    if (excess.dy.abs() > largestExcess.dy.abs()) {
+      largestExcess = Offset(largestExcess.dx, excess.dy);
+    }
+  }
+
+  return _round(largestExcess);
+}
+
+// Round the output values. This works around a precision problem where
+// values that should have been zero were given as within 10^-10 of zero.
+Offset _round(Offset offset) {
+  return Offset(
+    double.parse(offset.dx.toStringAsFixed(9)),
+    double.parse(offset.dy.toStringAsFixed(9)),
+  );
+}
+
+// Align the given offset to the given axis by allowing movement only in the
+// axis direction.
+Offset _alignAxis(Offset offset, Axis axis) {
+  switch (axis) {
+    case Axis.horizontal:
+      return Offset(offset.dx, 0.0);
+    case Axis.vertical:
+      return Offset(0.0, offset.dy);
+  }
+}
+
+// Given two points, return the axis where the distance between the points is
+// greatest. If they are equal, return null.
+Axis? _getPanAxis(Offset point1, Offset point2) {
+  if (point1 == point2) {
+    return null;
+  }
+  final double x = point2.dx - point1.dx;
+  final double y = point2.dy - point1.dy;
+  return x.abs() > y.abs() ? Axis.horizontal : Axis.vertical;
+}
diff --git a/lib/src/widgets/layout_builder.dart b/lib/src/widgets/layout_builder.dart
new file mode 100644
index 0000000..c8a64b8
--- /dev/null
+++ b/lib/src/widgets/layout_builder.dart
@@ -0,0 +1,418 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+import 'debug.dart';
+import 'framework.dart';
+
+/// The signature of the [LayoutBuilder] builder function.
+typedef LayoutWidgetBuilder = Widget Function(BuildContext context, BoxConstraints constraints);
+
+/// An abstract superclass for widgets that defer their building until layout.
+///
+/// Similar to the [Builder] widget except that the framework calls the [builder]
+/// function at layout time and provides the constraints that this widget should
+/// adhere to. This is useful when the parent constrains the child's size and layout,
+/// and doesn't depend on the child's intrinsic size.
+///
+/// {@template flutter.widgets.ConstrainedLayoutBuilder}
+/// The [builder] function is called in the following situations:
+///
+/// * The first time the widget is laid out.
+/// * When the parent widget passes different layout constraints.
+/// * When the parent widget updates this widget.
+/// * When the dependencies that the [builder] function subscribes to change.
+///
+/// The [builder] function is _not_ called during layout if the parent passes
+/// the same constraints repeatedly.
+/// {@endtemplate}
+///
+/// Subclasses must return a [RenderObject] that mixes in
+/// [RenderConstrainedLayoutBuilder].
+abstract class ConstrainedLayoutBuilder<ConstraintType extends Constraints> extends RenderObjectWidget {
+  /// Creates a widget that defers its building until layout.
+  ///
+  /// The [builder] argument must not be null, and the returned widget should not
+  /// be null.
+  const ConstrainedLayoutBuilder({
+    Key? key,
+    required this.builder,
+  }) : assert(builder != null),
+       super(key: key);
+
+  @override
+  _LayoutBuilderElement<ConstraintType> createElement() => _LayoutBuilderElement<ConstraintType>(this);
+
+  /// Called at layout time to construct the widget tree.
+  ///
+  /// The builder must not return null.
+  final Widget Function(BuildContext, ConstraintType) builder;
+
+  // updateRenderObject is redundant with the logic in the LayoutBuilderElement below.
+}
+
+class _LayoutBuilderElement<ConstraintType extends Constraints> extends RenderObjectElement {
+  _LayoutBuilderElement(ConstrainedLayoutBuilder<ConstraintType> widget) : super(widget);
+
+  @override
+  ConstrainedLayoutBuilder<ConstraintType> get widget => super.widget as ConstrainedLayoutBuilder<ConstraintType>;
+
+  @override
+  RenderConstrainedLayoutBuilder<ConstraintType, RenderObject> get renderObject => super.renderObject as RenderConstrainedLayoutBuilder<ConstraintType, RenderObject>;
+
+  Element? _child;
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    if (_child != null)
+      visitor(_child!);
+  }
+
+  @override
+  void forgetChild(Element child) {
+    assert(child == _child);
+    _child = null;
+    super.forgetChild(child);
+  }
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot); // Creates the renderObject.
+    renderObject.updateCallback(_layout);
+  }
+
+  @override
+  void update(ConstrainedLayoutBuilder<ConstraintType> newWidget) {
+    assert(widget != newWidget);
+    super.update(newWidget);
+    assert(widget == newWidget);
+
+    renderObject.updateCallback(_layout);
+    // Force the callback to be called, even if the layout constraints are the
+    // same, because the logic in the callback might have changed.
+    renderObject.markNeedsBuild();
+  }
+
+  @override
+  void performRebuild() {
+    // This gets called if markNeedsBuild() is called on us.
+    // That might happen if, e.g., our builder uses Inherited widgets.
+
+    // Force the callback to be called, even if the layout constraints are the
+    // same. This is because that callback may depend on the updated widget
+    // configuration, or an inherited widget.
+    renderObject.markNeedsBuild();
+    super.performRebuild(); // Calls widget.updateRenderObject (a no-op in this case).
+  }
+
+  @override
+  void unmount() {
+    renderObject.updateCallback(null);
+    super.unmount();
+  }
+
+  void _layout(ConstraintType constraints) {
+    owner!.buildScope(this, () {
+      Widget built;
+      try {
+        built = widget.builder(this, constraints);
+        debugWidgetBuilderValue(widget, built);
+      } catch (e, stack) {
+        built = ErrorWidget.builder(
+          _debugReportException(
+            ErrorDescription('building $widget'),
+            e,
+            stack,
+            informationCollector: () sync* {
+              yield DiagnosticsDebugCreator(DebugCreator(this));
+            },
+          ),
+        );
+      }
+      try {
+        _child = updateChild(_child, built, null);
+        assert(_child != null);
+      } catch (e, stack) {
+        built = ErrorWidget.builder(
+          _debugReportException(
+            ErrorDescription('building $widget'),
+            e,
+            stack,
+            informationCollector: () sync* {
+              yield DiagnosticsDebugCreator(DebugCreator(this));
+            },
+          ),
+        );
+        _child = updateChild(null, built, slot);
+      }
+    });
+  }
+
+  @override
+  void insertRenderObjectChild(RenderObject child, dynamic slot) {
+    final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
+    assert(slot == null);
+    assert(renderObject.debugValidateChild(child));
+    renderObject.child = child;
+    assert(renderObject == this.renderObject);
+  }
+
+  @override
+  void moveRenderObjectChild(RenderObject child, dynamic oldSlot, dynamic newSlot) {
+    assert(false);
+  }
+
+  @override
+  void removeRenderObjectChild(RenderObject child, dynamic slot) {
+    final RenderConstrainedLayoutBuilder<ConstraintType, RenderObject> renderObject = this.renderObject;
+    assert(renderObject.child == child);
+    renderObject.child = null;
+    assert(renderObject == this.renderObject);
+  }
+}
+
+/// Generic mixin for [RenderObject]s created by [ConstrainedLayoutBuilder].
+///
+/// Provides a callback that should be called at layout time, typically in
+/// [RenderObject.performLayout].
+mixin RenderConstrainedLayoutBuilder<ConstraintType extends Constraints, ChildType extends RenderObject> on RenderObjectWithChildMixin<ChildType> {
+  LayoutCallback<ConstraintType>? _callback;
+  /// Change the layout callback.
+  void updateCallback(LayoutCallback<ConstraintType>? value) {
+    if (value == _callback)
+      return;
+    _callback = value;
+    markNeedsLayout();
+  }
+
+  bool _needsBuild = true;
+
+  /// Marks this layout builder as needing to rebuild.
+  ///
+  /// The layout build rebuilds automatically when layout constraints change.
+  /// However, we must also rebuild when the widget updates, e.g. after
+  /// [State.setState], or [State.didChangeDependencies], even when the layout
+  /// constraints remain unchanged.
+  ///
+  /// See also:
+  ///
+  ///  * [ConstrainedLayoutBuilder.builder], which is called during the rebuild.
+  void markNeedsBuild() {
+    // Do not call the callback directly. It must be called during the layout
+    // phase, when parent constraints are available. Calling `markNeedsLayout`
+    // will cause it to be called at the right time.
+    _needsBuild = true;
+    markNeedsLayout();
+  }
+
+  // The constraints that were passed to this class last time it was laid out.
+  // These constraints are compared to the new constraints to determine whether
+  // [ConstrainedLayoutBuilder.builder] needs to be called.
+  Constraints? _previousConstraints;
+
+  /// Invoke the callback supplied via [updateCallback].
+  ///
+  /// Typically this results in [ConstrainedLayoutBuilder.builder] being called
+  /// during layout.
+  void rebuildIfNecessary() {
+    assert(_callback != null);
+    if (_needsBuild || constraints != _previousConstraints) {
+      _previousConstraints = constraints;
+      _needsBuild = false;
+      invokeLayoutCallback(_callback!);
+    }
+  }
+}
+
+/// Builds a widget tree that can depend on the parent widget's size.
+///
+/// Similar to the [Builder] widget except that the framework calls the [builder]
+/// function at layout time and provides the parent widget's constraints. This
+/// is useful when the parent constrains the child's size and doesn't depend on
+/// the child's intrinsic size. The [LayoutBuilder]'s final size will match its
+/// child's size.
+///
+/// {@macro flutter.widgets.ConstrainedLayoutBuilder}
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=IYDVcriKjsw}
+///
+/// If the child should be smaller than the parent, consider wrapping the child
+/// in an [Align] widget. If the child might want to be bigger, consider
+/// wrapping it in a [SingleChildScrollView] or [OverflowBox].
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+///
+/// This example uses a [LayoutBuilder] to build a different widget depending on the available width. Resize the
+/// DartPad window to see [LayoutBuilder] in action!
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: AppBar(title: Text("LayoutBuilder Example")),
+///     body: LayoutBuilder(
+///       builder: (context, constraints) {
+///         if (constraints.maxWidth > 600) {
+///           return _buildWideContainers();
+///         } else {
+///           return _buildNormalContainer();
+///         }
+///       },
+///     ),
+///   );
+/// }
+///
+/// Widget _buildNormalContainer() {
+///   return Center(
+///     child: Container(
+///       height: 100.0,
+///       width: 100.0,
+///       color: Colors.red,
+///     ),
+///   );
+/// }
+///
+/// Widget _buildWideContainers() {
+///   return Center(
+///     child: Row(
+///       mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+///       children: <Widget>[
+///         Container(
+///           height: 100.0,
+///           width: 100.0,
+///           color: Colors.red,
+///         ),
+///         Container(
+///           height: 100.0,
+///           width: 100.0,
+///           color: Colors.yellow,
+///         ),
+///       ],
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [SliverLayoutBuilder], the sliver counterpart of this widget.
+///  * [Builder], which calls a `builder` function at build time.
+///  * [StatefulBuilder], which passes its `builder` function a `setState` callback.
+///  * [CustomSingleChildLayout], which positions its child during layout.
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class LayoutBuilder extends ConstrainedLayoutBuilder<BoxConstraints> {
+  /// Creates a widget that defers its building until layout.
+  ///
+  /// The [builder] argument must not be null.
+  const LayoutBuilder({
+    Key? key,
+    required LayoutWidgetBuilder builder,
+  }) : assert(builder != null),
+       super(key: key, builder: builder);
+
+  @override
+  LayoutWidgetBuilder get builder => super.builder;
+
+  @override
+  _RenderLayoutBuilder createRenderObject(BuildContext context) => _RenderLayoutBuilder();
+}
+
+class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<RenderBox>, RenderConstrainedLayoutBuilder<BoxConstraints, RenderBox> {
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    assert(_debugThrowIfNotCheckingIntrinsics());
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    assert(_debugThrowIfNotCheckingIntrinsics());
+    return 0.0;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    assert(_debugThrowIfNotCheckingIntrinsics());
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    assert(_debugThrowIfNotCheckingIntrinsics());
+    return 0.0;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    assert(debugCannotComputeDryLayout(reason:
+      'Calculating the dry layout would require running the layout callback '
+      'speculatively, which might mutate the live render object tree.',
+    ));
+    return const Size(0, 0);
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    rebuildIfNecessary();
+    if (child != null) {
+      child!.layout(constraints, parentUsesSize: true);
+      size = constraints.constrain(child!.size);
+    } else {
+      size = constraints.biggest;
+    }
+  }
+
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    if (child != null)
+      return child!.getDistanceToActualBaseline(baseline);
+    return super.computeDistanceToActualBaseline(baseline);
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    return child?.hitTest(result, position: position) ?? false;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null)
+      context.paintChild(child!, offset);
+  }
+
+  bool _debugThrowIfNotCheckingIntrinsics() {
+    assert(() {
+      if (!RenderObject.debugCheckingIntrinsics) {
+        throw FlutterError(
+          'LayoutBuilder does not support returning intrinsic dimensions.\n'
+          'Calculating the intrinsic dimensions would require running the layout '
+          'callback speculatively, which might mutate the live render object tree.'
+        );
+      }
+      return true;
+    }());
+
+    return true;
+  }
+}
+
+FlutterErrorDetails _debugReportException(
+  DiagnosticsNode context,
+  Object exception,
+  StackTrace stack, {
+  InformationCollector? informationCollector,
+}) {
+  final FlutterErrorDetails details = FlutterErrorDetails(
+    exception: exception,
+    stack: stack,
+    library: 'widgets library',
+    context: context,
+    informationCollector: informationCollector,
+  );
+  FlutterError.reportError(details);
+  return details;
+}
diff --git a/lib/src/widgets/list_wheel_scroll_view.dart b/lib/src/widgets/list_wheel_scroll_view.dart
new file mode 100644
index 0000000..1ce4c0a
--- /dev/null
+++ b/lib/src/widgets/list_wheel_scroll_view.dart
@@ -0,0 +1,1083 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+import 'dart:math' as math;
+
+import 'package:flute/animation.dart';
+import 'package:flute/physics.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'notification_listener.dart';
+import 'scroll_context.dart';
+import 'scroll_controller.dart';
+import 'scroll_metrics.dart';
+import 'scroll_notification.dart';
+import 'scroll_physics.dart';
+import 'scroll_position.dart';
+import 'scroll_position_with_single_context.dart';
+import 'scrollable.dart';
+
+/// A delegate that supplies children for [ListWheelScrollView].
+///
+/// [ListWheelScrollView] lazily constructs its children during layout to avoid
+/// creating more children than are visible through the [Viewport]. This
+/// delegate is responsible for providing children to [ListWheelScrollView]
+/// during that stage.
+///
+/// See also:
+///
+///  * [ListWheelChildListDelegate], a delegate that supplies children using an
+///    explicit list.
+///  * [ListWheelChildLoopingListDelegate], a delegate that supplies infinite
+///    children by looping an explicit list.
+///  * [ListWheelChildBuilderDelegate], a delegate that supplies children using
+///    a builder callback.
+abstract class ListWheelChildDelegate {
+  /// Return the child at the given index. If the child at the given
+  /// index does not exist, return null.
+  Widget? build(BuildContext context, int index);
+
+  /// Returns an estimate of the number of children this delegate will build.
+  int? get estimatedChildCount;
+
+  /// Returns the true index for a child built at a given index. Defaults to
+  /// the given index, however if the delegate is [ListWheelChildLoopingListDelegate],
+  /// this value is the index of the true element that the delegate is looping to.
+  ///
+  ///
+  /// Example: [ListWheelChildLoopingListDelegate] is built by looping a list of
+  /// length 8. Then, trueIndexOf(10) = 2 and trueIndexOf(-5) = 3.
+  int trueIndexOf(int index) => index;
+
+  /// Called to check whether this and the old delegate are actually 'different',
+  /// so that the caller can decide to rebuild or not.
+  bool shouldRebuild(covariant ListWheelChildDelegate oldDelegate);
+}
+
+/// A delegate that supplies children for [ListWheelScrollView] using an
+/// explicit list.
+///
+/// [ListWheelScrollView] lazily constructs its children to avoid creating more
+/// children than are visible through the [Viewport]. This delegate provides
+/// children using an explicit list, which is convenient but reduces the benefit
+/// of building children lazily.
+///
+/// In general building all the widgets in advance is not efficient. It is
+/// better to create a delegate that builds them on demand using
+/// [ListWheelChildBuilderDelegate] or by subclassing [ListWheelChildDelegate]
+/// directly.
+///
+/// This class is provided for the cases where either the list of children is
+/// known well in advance (ideally the children are themselves compile-time
+/// constants, for example), and therefore will not be built each time the
+/// delegate itself is created, or the list is small, such that it's likely
+/// always visible (and thus there is nothing to be gained by building it on
+/// demand). For example, the body of a dialog box might fit both of these
+/// conditions.
+class ListWheelChildListDelegate extends ListWheelChildDelegate {
+  /// Constructs the delegate from a concrete list of children.
+  ListWheelChildListDelegate({required this.children}) : assert(children != null);
+
+  /// The list containing all children that can be supplied.
+  final List<Widget> children;
+
+  @override
+  int get estimatedChildCount => children.length;
+
+  @override
+  Widget? build(BuildContext context, int index) {
+    if (index < 0 || index >= children.length)
+      return null;
+    return IndexedSemantics(child: children[index], index: index);
+  }
+
+  @override
+  bool shouldRebuild(covariant ListWheelChildListDelegate oldDelegate) {
+    return children != oldDelegate.children;
+  }
+}
+
+/// A delegate that supplies infinite children for [ListWheelScrollView] by
+/// looping an explicit list.
+///
+/// [ListWheelScrollView] lazily constructs its children to avoid creating more
+/// children than are visible through the [Viewport]. This delegate provides
+/// children using an explicit list, which is convenient but reduces the benefit
+/// of building children lazily.
+///
+/// In general building all the widgets in advance is not efficient. It is
+/// better to create a delegate that builds them on demand using
+/// [ListWheelChildBuilderDelegate] or by subclassing [ListWheelChildDelegate]
+/// directly.
+///
+/// This class is provided for the cases where either the list of children is
+/// known well in advance (ideally the children are themselves compile-time
+/// constants, for example), and therefore will not be built each time the
+/// delegate itself is created, or the list is small, such that it's likely
+/// always visible (and thus there is nothing to be gained by building it on
+/// demand). For example, the body of a dialog box might fit both of these
+/// conditions.
+class ListWheelChildLoopingListDelegate extends ListWheelChildDelegate {
+  /// Constructs the delegate from a concrete list of children.
+  ListWheelChildLoopingListDelegate({required this.children}) : assert(children != null);
+
+  /// The list containing all children that can be supplied.
+  final List<Widget> children;
+
+  @override
+  int? get estimatedChildCount => null;
+
+  @override
+  int trueIndexOf(int index) => index % children.length;
+
+  @override
+  Widget? build(BuildContext context, int index) {
+    if (children.isEmpty)
+      return null;
+    return IndexedSemantics(child: children[index % children.length], index: index);
+  }
+
+  @override
+  bool shouldRebuild(covariant ListWheelChildLoopingListDelegate oldDelegate) {
+    return children != oldDelegate.children;
+  }
+}
+
+/// A delegate that supplies children for [ListWheelScrollView] using a builder
+/// callback.
+///
+/// [ListWheelScrollView] lazily constructs its children to avoid creating more
+/// children than are visible through the [Viewport]. This delegate provides
+/// children using an [IndexedWidgetBuilder] callback, so that the children do
+/// not have to be built until they are displayed.
+class ListWheelChildBuilderDelegate extends ListWheelChildDelegate {
+  /// Constructs the delegate from a builder callback.
+  ListWheelChildBuilderDelegate({
+    required this.builder,
+    this.childCount,
+  }) : assert(builder != null);
+
+  /// Called lazily to build children.
+  final NullableIndexedWidgetBuilder builder;
+
+  /// {@template flutter.widgets.ListWheelChildBuilderDelegate.childCount}
+  /// If non-null, [childCount] is the maximum number of children that can be
+  /// provided, and children are available from 0 to [childCount] - 1.
+  ///
+  /// If null, then the lower and upper limit are not known. However the [builder]
+  /// must provide children for a contiguous segment. If the builder returns null
+  /// at some index, the segment terminates there.
+  /// {@endtemplate}
+  final int? childCount;
+
+  @override
+  int? get estimatedChildCount => childCount;
+
+  @override
+  Widget? build(BuildContext context, int index) {
+    if (childCount == null) {
+      final Widget? child = builder(context, index);
+      return child == null ? null : IndexedSemantics(child: child, index: index);
+    }
+    if (index < 0 || index >= childCount!)
+      return null;
+    return IndexedSemantics(child: builder(context, index), index: index);
+  }
+
+  @override
+  bool shouldRebuild(covariant ListWheelChildBuilderDelegate oldDelegate) {
+    return builder != oldDelegate.builder || childCount != oldDelegate.childCount;
+  }
+}
+
+/// A controller for scroll views whose items have the same size.
+///
+/// Similar to a standard [ScrollController] but with the added convenience
+/// mechanisms to read and go to item indices rather than a raw pixel scroll
+/// offset.
+///
+/// See also:
+///
+///  * [ListWheelScrollView], a scrollable view widget with fixed size items
+///    that this widget controls.
+///  * [FixedExtentMetrics], the `metrics` property exposed by
+///    [ScrollNotification] from [ListWheelScrollView] which can be used
+///    to listen to the current item index on a push basis rather than polling
+///    the [FixedExtentScrollController].
+class FixedExtentScrollController extends ScrollController {
+  /// Creates a scroll controller for scrollables whose items have the same size.
+  ///
+  /// [initialItem] defaults to 0 and must not be null.
+  FixedExtentScrollController({
+    this.initialItem = 0,
+  }) : assert(initialItem != null);
+
+  /// The page to show when first creating the scroll view.
+  ///
+  /// Defaults to 0 and must not be null.
+  final int initialItem;
+
+  /// The currently selected item index that's closest to the center of the viewport.
+  ///
+  /// There are circumstances that this [FixedExtentScrollController] can't know
+  /// the current item. Reading [selectedItem] will throw an [AssertionError] in
+  /// the following cases:
+  ///
+  /// 1. No scroll view is currently using this [FixedExtentScrollController].
+  /// 2. More than one scroll views using the same [FixedExtentScrollController].
+  ///
+  /// The [hasClients] property can be used to check if a scroll view is
+  /// attached prior to accessing [selectedItem].
+  int get selectedItem {
+    assert(
+      positions.isNotEmpty,
+      'FixedExtentScrollController.selectedItem cannot be accessed before a '
+      'scroll view is built with it.',
+    );
+    assert(
+      positions.length == 1,
+      'The selectedItem property cannot be read when multiple scroll views are '
+      'attached to the same FixedExtentScrollController.',
+    );
+    final _FixedExtentScrollPosition position = this.position as _FixedExtentScrollPosition;
+    return position.itemIndex;
+  }
+
+  /// Animates the controlled scroll view to the given item index.
+  ///
+  /// The animation lasts for the given duration and follows the given curve.
+  /// The returned [Future] resolves when the animation completes.
+  ///
+  /// The `duration` and `curve` arguments must not be null.
+  Future<void> animateToItem(
+    int itemIndex, {
+    required Duration duration,
+    required Curve curve,
+  }) async {
+    if (!hasClients) {
+      return;
+    }
+
+    await Future.wait<void>(<Future<void>>[
+      for (final _FixedExtentScrollPosition position in positions.cast<_FixedExtentScrollPosition>())
+        position.animateTo(
+          itemIndex * position.itemExtent,
+          duration: duration,
+          curve: curve,
+        ),
+    ]);
+  }
+
+  /// Changes which item index is centered in the controlled scroll view.
+  ///
+  /// Jumps the item index position from its current value to the given value,
+  /// without animation, and without checking if the new value is in range.
+  void jumpToItem(int itemIndex) {
+    for (final _FixedExtentScrollPosition position in positions.cast<_FixedExtentScrollPosition>()) {
+      position.jumpTo(itemIndex * position.itemExtent);
+    }
+  }
+
+  @override
+  ScrollPosition createScrollPosition(ScrollPhysics physics, ScrollContext context, ScrollPosition? oldPosition) {
+    return _FixedExtentScrollPosition(
+      physics: physics,
+      context: context,
+      initialItem: initialItem,
+      oldPosition: oldPosition,
+    );
+  }
+}
+
+/// Metrics for a [ScrollPosition] to a scroll view with fixed item sizes.
+///
+/// The metrics are available on [ScrollNotification]s generated from a scroll
+/// views such as [ListWheelScrollView]s with a [FixedExtentScrollController]
+/// and exposes the current [itemIndex] and the scroll view's extents.
+///
+/// `FixedExtent` refers to the fact that the scrollable items have the same
+/// size. This is distinct from `Fixed` in the parent class name's
+/// [FixedScrollMetrics] which refers to its immutability.
+class FixedExtentMetrics extends FixedScrollMetrics {
+  /// Creates an immutable snapshot of values associated with a
+  /// [ListWheelScrollView].
+  FixedExtentMetrics({
+    required double? minScrollExtent,
+    required double? maxScrollExtent,
+    required double? pixels,
+    required double? viewportDimension,
+    required AxisDirection axisDirection,
+    required this.itemIndex,
+  }) : super(
+         minScrollExtent: minScrollExtent,
+         maxScrollExtent: maxScrollExtent,
+         pixels: pixels,
+         viewportDimension: viewportDimension,
+         axisDirection: axisDirection,
+       );
+
+  @override
+  FixedExtentMetrics copyWith({
+    double? minScrollExtent,
+    double? maxScrollExtent,
+    double? pixels,
+    double? viewportDimension,
+    AxisDirection? axisDirection,
+    int? itemIndex,
+  }) {
+    return FixedExtentMetrics(
+      minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
+      maxScrollExtent: maxScrollExtent ?? (hasContentDimensions ? this.maxScrollExtent : null),
+      pixels: pixels ?? (hasPixels ? this.pixels : null),
+      viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
+      axisDirection: axisDirection ?? this.axisDirection,
+      itemIndex: itemIndex ?? this.itemIndex,
+    );
+  }
+
+  /// The scroll view's currently selected item index.
+  final int itemIndex;
+}
+
+int _getItemFromOffset({
+  required double offset,
+  required double itemExtent,
+  required double minScrollExtent,
+  required double maxScrollExtent,
+}) {
+  return (_clipOffsetToScrollableRange(offset, minScrollExtent, maxScrollExtent) / itemExtent).round();
+}
+
+double _clipOffsetToScrollableRange(
+  double offset,
+  double minScrollExtent,
+  double maxScrollExtent,
+) {
+  return math.min(math.max(offset, minScrollExtent), maxScrollExtent);
+}
+
+/// A [ScrollPositionWithSingleContext] that can only be created based on
+/// [_FixedExtentScrollable] and can access its `itemExtent` to derive [itemIndex].
+class _FixedExtentScrollPosition extends ScrollPositionWithSingleContext implements FixedExtentMetrics {
+  _FixedExtentScrollPosition({
+    required ScrollPhysics physics,
+    required ScrollContext context,
+    required int initialItem,
+    bool keepScrollOffset = true,
+    ScrollPosition? oldPosition,
+    String? debugLabel,
+  }) : assert(
+         context is _FixedExtentScrollableState,
+         'FixedExtentScrollController can only be used with ListWheelScrollViews'
+       ),
+       super(
+         physics: physics,
+         context: context,
+         initialPixels: _getItemExtentFromScrollContext(context) * initialItem,
+         keepScrollOffset: keepScrollOffset,
+         oldPosition: oldPosition,
+         debugLabel: debugLabel,
+       );
+
+  static double _getItemExtentFromScrollContext(ScrollContext context) {
+    final _FixedExtentScrollableState scrollable = context as _FixedExtentScrollableState;
+    return scrollable.itemExtent;
+  }
+
+  double get itemExtent => _getItemExtentFromScrollContext(context);
+
+  @override
+  int get itemIndex {
+    return _getItemFromOffset(
+      offset: pixels,
+      itemExtent: itemExtent,
+      minScrollExtent: minScrollExtent,
+      maxScrollExtent: maxScrollExtent,
+    );
+  }
+
+  @override
+  FixedExtentMetrics copyWith({
+    double? minScrollExtent,
+    double? maxScrollExtent,
+    double? pixels,
+    double? viewportDimension,
+    AxisDirection? axisDirection,
+    int? itemIndex,
+  }) {
+    return FixedExtentMetrics(
+      minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
+      maxScrollExtent: maxScrollExtent ?? (hasContentDimensions ? this.maxScrollExtent : null),
+      pixels: pixels ?? (hasPixels ? this.pixels : null),
+      viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
+      axisDirection: axisDirection ?? this.axisDirection,
+      itemIndex: itemIndex ?? this.itemIndex,
+    );
+  }
+}
+
+/// A [Scrollable] which must be given its viewport children's item extent
+/// size so it can pass it on ultimately to the [FixedExtentScrollController].
+class _FixedExtentScrollable extends Scrollable {
+  const _FixedExtentScrollable({
+    Key? key,
+    AxisDirection axisDirection = AxisDirection.down,
+    ScrollController? controller,
+    ScrollPhysics? physics,
+    required this.itemExtent,
+    required ViewportBuilder viewportBuilder,
+    String? restorationId,
+  }) : super (
+    key: key,
+    axisDirection: axisDirection,
+    controller: controller,
+    physics: physics,
+    viewportBuilder: viewportBuilder,
+    restorationId: restorationId,
+  );
+
+  final double itemExtent;
+
+  @override
+  _FixedExtentScrollableState createState() => _FixedExtentScrollableState();
+}
+
+/// This [ScrollContext] is used by [_FixedExtentScrollPosition] to read the
+/// prescribed [itemExtent].
+class _FixedExtentScrollableState extends ScrollableState {
+  double get itemExtent {
+    // Downcast because only _FixedExtentScrollable can make _FixedExtentScrollableState.
+    final _FixedExtentScrollable actualWidget = widget as _FixedExtentScrollable;
+    return actualWidget.itemExtent;
+  }
+}
+
+/// A snapping physics that always lands directly on items instead of anywhere
+/// within the scroll extent.
+///
+/// Behaves similarly to a slot machine wheel except the ballistics simulation
+/// never overshoots and rolls back within a single item if it's to settle on
+/// that item.
+///
+/// Must be used with a scrollable that uses a [FixedExtentScrollController].
+///
+/// Defers back to the parent beyond the scroll extents.
+class FixedExtentScrollPhysics extends ScrollPhysics {
+  /// Creates a scroll physics that always lands on items.
+  const FixedExtentScrollPhysics({ ScrollPhysics? parent }) : super(parent: parent);
+
+  @override
+  FixedExtentScrollPhysics applyTo(ScrollPhysics? ancestor) {
+    return FixedExtentScrollPhysics(parent: buildParent(ancestor));
+  }
+
+  @override
+  Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {
+    assert(
+      position is _FixedExtentScrollPosition,
+      'FixedExtentScrollPhysics can only be used with Scrollables that uses '
+      'the FixedExtentScrollController'
+    );
+
+    final _FixedExtentScrollPosition metrics = position as _FixedExtentScrollPosition;
+
+    // Scenario 1:
+    // If we're out of range and not headed back in range, defer to the parent
+    // ballistics, which should put us back in range at the scrollable's boundary.
+    if ((velocity <= 0.0 && metrics.pixels <= metrics.minScrollExtent) ||
+        (velocity >= 0.0 && metrics.pixels >= metrics.maxScrollExtent)) {
+      return super.createBallisticSimulation(metrics, velocity);
+    }
+
+    // Create a test simulation to see where it would have ballistically fallen
+    // naturally without settling onto items.
+    final Simulation? testFrictionSimulation =
+        super.createBallisticSimulation(metrics, velocity);
+
+    // Scenario 2:
+    // If it was going to end up past the scroll extent, defer back to the
+    // parent physics' ballistics again which should put us on the scrollable's
+    // boundary.
+    if (testFrictionSimulation != null
+        && (testFrictionSimulation.x(double.infinity) == metrics.minScrollExtent
+            || testFrictionSimulation.x(double.infinity) == metrics.maxScrollExtent)) {
+      return super.createBallisticSimulation(metrics, velocity);
+    }
+
+    // From the natural final position, find the nearest item it should have
+    // settled to.
+    final int settlingItemIndex = _getItemFromOffset(
+      offset: testFrictionSimulation?.x(double.infinity) ?? metrics.pixels,
+      itemExtent: metrics.itemExtent,
+      minScrollExtent: metrics.minScrollExtent,
+      maxScrollExtent: metrics.maxScrollExtent,
+    );
+
+    final double settlingPixels = settlingItemIndex * metrics.itemExtent;
+
+    // Scenario 3:
+    // If there's no velocity and we're already at where we intend to land,
+    // do nothing.
+    if (velocity.abs() < tolerance.velocity
+        && (settlingPixels - metrics.pixels).abs() < tolerance.distance) {
+      return null;
+    }
+
+    // Scenario 4:
+    // If we're going to end back at the same item because initial velocity
+    // is too low to break past it, use a spring simulation to get back.
+    if (settlingItemIndex == metrics.itemIndex) {
+      return SpringSimulation(
+        spring,
+        metrics.pixels,
+        settlingPixels,
+        velocity,
+        tolerance: tolerance,
+      );
+    }
+
+    // Scenario 5:
+    // Create a new friction simulation except the drag will be tweaked to land
+    // exactly on the item closest to the natural stopping point.
+    return FrictionSimulation.through(
+      metrics.pixels,
+      settlingPixels,
+      velocity,
+      tolerance.velocity * velocity.sign,
+    );
+  }
+}
+
+/// A box in which children on a wheel can be scrolled.
+///
+/// This widget is similar to a [ListView] but with the restriction that all
+/// children must be the same size along the scrolling axis.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=dUhmWAz4C7Y}
+///
+/// When the list is at the zero scroll offset, the first child is aligned with
+/// the middle of the viewport. When the list is at the final scroll offset,
+/// the last child is aligned with the middle of the viewport.
+///
+/// The children are rendered as if rotating on a wheel instead of scrolling on
+/// a plane.
+class ListWheelScrollView extends StatefulWidget {
+  /// Constructs a list in which children are scrolled a wheel. Its children
+  /// are passed to a delegate and lazily built during layout.
+  ListWheelScrollView({
+    Key? key,
+    this.controller,
+    this.physics,
+    this.diameterRatio = RenderListWheelViewport.defaultDiameterRatio,
+    this.perspective = RenderListWheelViewport.defaultPerspective,
+    this.offAxisFraction = 0.0,
+    this.useMagnifier = false,
+    this.magnification = 1.0,
+    this.overAndUnderCenterOpacity = 1.0,
+    required this.itemExtent,
+    this.squeeze = 1.0,
+    this.onSelectedItemChanged,
+    this.renderChildrenOutsideViewport = false,
+    this.clipBehavior = Clip.hardEdge,
+    this.restorationId,
+    required List<Widget> children,
+  }) : assert(children != null),
+       assert(diameterRatio != null),
+       assert(diameterRatio > 0.0, RenderListWheelViewport.diameterRatioZeroMessage),
+       assert(perspective != null),
+       assert(perspective > 0),
+       assert(perspective <= 0.01, RenderListWheelViewport.perspectiveTooHighMessage),
+       assert(magnification > 0),
+       assert(overAndUnderCenterOpacity != null),
+       assert(overAndUnderCenterOpacity >= 0 && overAndUnderCenterOpacity <= 1),
+       assert(itemExtent != null),
+       assert(itemExtent > 0),
+       assert(squeeze != null),
+       assert(squeeze > 0),
+       assert(renderChildrenOutsideViewport != null),
+       assert(clipBehavior != null),
+       assert(
+         !renderChildrenOutsideViewport || clipBehavior == Clip.none,
+         RenderListWheelViewport.clipBehaviorAndRenderChildrenOutsideViewportConflict,
+       ),
+       childDelegate = ListWheelChildListDelegate(children: children),
+       super(key: key);
+
+  /// Constructs a list in which children are scrolled a wheel. Its children
+  /// are managed by a delegate and are lazily built during layout.
+  const ListWheelScrollView.useDelegate({
+    Key? key,
+    this.controller,
+    this.physics,
+    this.diameterRatio = RenderListWheelViewport.defaultDiameterRatio,
+    this.perspective = RenderListWheelViewport.defaultPerspective,
+    this.offAxisFraction = 0.0,
+    this.useMagnifier = false,
+    this.magnification = 1.0,
+    this.overAndUnderCenterOpacity = 1.0,
+    required this.itemExtent,
+    this.squeeze = 1.0,
+    this.onSelectedItemChanged,
+    this.renderChildrenOutsideViewport = false,
+    this.clipBehavior = Clip.hardEdge,
+    this.restorationId,
+    required this.childDelegate,
+  }) : assert(childDelegate != null),
+       assert(diameterRatio != null),
+       assert(diameterRatio > 0.0, RenderListWheelViewport.diameterRatioZeroMessage),
+       assert(perspective != null),
+       assert(perspective > 0),
+       assert(perspective <= 0.01, RenderListWheelViewport.perspectiveTooHighMessage),
+       assert(magnification > 0),
+       assert(overAndUnderCenterOpacity != null),
+       assert(overAndUnderCenterOpacity >= 0 && overAndUnderCenterOpacity <= 1),
+       assert(itemExtent != null),
+       assert(itemExtent > 0),
+       assert(squeeze != null),
+       assert(squeeze > 0),
+       assert(renderChildrenOutsideViewport != null),
+       assert(clipBehavior != null),
+       assert(
+         !renderChildrenOutsideViewport || clipBehavior == Clip.none,
+         RenderListWheelViewport.clipBehaviorAndRenderChildrenOutsideViewportConflict,
+       ),
+       super(key: key);
+
+  /// Typically a [FixedExtentScrollController] used to control the current item.
+  ///
+  /// A [FixedExtentScrollController] can be used to read the currently
+  /// selected/centered child item and can be used to change the current item.
+  ///
+  /// If none is provided, a new [FixedExtentScrollController] is implicitly
+  /// created.
+  ///
+  /// If a [ScrollController] is used instead of [FixedExtentScrollController],
+  /// [ScrollNotification.metrics] will no longer provide [FixedExtentMetrics]
+  /// to indicate the current item index and [onSelectedItemChanged] will not
+  /// work.
+  ///
+  /// To read the current selected item only when the value changes, use
+  /// [onSelectedItemChanged].
+  final ScrollController? controller;
+
+  /// How the scroll view should respond to user input.
+  ///
+  /// For example, determines how the scroll view continues to animate after the
+  /// user stops dragging the scroll view.
+  ///
+  /// Defaults to matching platform conventions.
+  final ScrollPhysics? physics;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.diameterRatio}
+  final double diameterRatio;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.perspective}
+  final double perspective;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.offAxisFraction}
+  final double offAxisFraction;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.useMagnifier}
+  final bool useMagnifier;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.magnification}
+  final double magnification;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.overAndUnderCenterOpacity}
+  final double overAndUnderCenterOpacity;
+
+  /// Size of each child in the main axis. Must not be null and must be
+  /// positive.
+  final double itemExtent;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.squeeze}
+  ///
+  /// Defaults to 1.
+  final double squeeze;
+
+  /// On optional listener that's called when the centered item changes.
+  final ValueChanged<int>? onSelectedItemChanged;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.renderChildrenOutsideViewport}
+  final bool renderChildrenOutsideViewport;
+
+  /// A delegate that helps lazily instantiating child.
+  final ListWheelChildDelegate childDelegate;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge].
+  final Clip clipBehavior;
+
+  /// {@macro flutter.widgets.scrollable.restorationId}
+  final String? restorationId;
+
+  @override
+  _ListWheelScrollViewState createState() => _ListWheelScrollViewState();
+}
+
+class _ListWheelScrollViewState extends State<ListWheelScrollView> {
+  int _lastReportedItemIndex = 0;
+  ScrollController? scrollController;
+
+  @override
+  void initState() {
+    super.initState();
+    scrollController = widget.controller ?? FixedExtentScrollController();
+    if (widget.controller is FixedExtentScrollController) {
+      final FixedExtentScrollController controller = widget.controller! as FixedExtentScrollController;
+      _lastReportedItemIndex = controller.initialItem;
+    }
+  }
+
+  @override
+  void didUpdateWidget(ListWheelScrollView oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.controller != null && widget.controller != scrollController) {
+      final ScrollController? oldScrollController = scrollController;
+      SchedulerBinding.instance!.addPostFrameCallback((_) {
+        oldScrollController!.dispose();
+      });
+      scrollController = widget.controller;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return NotificationListener<ScrollNotification>(
+      onNotification: (ScrollNotification notification) {
+        if (notification.depth == 0
+            && widget.onSelectedItemChanged != null
+            && notification is ScrollUpdateNotification
+            && notification.metrics is FixedExtentMetrics) {
+          final FixedExtentMetrics metrics = notification.metrics as FixedExtentMetrics;
+          final int currentItemIndex = metrics.itemIndex;
+          if (currentItemIndex != _lastReportedItemIndex) {
+            _lastReportedItemIndex = currentItemIndex;
+            final int trueIndex = widget.childDelegate.trueIndexOf(currentItemIndex);
+            widget.onSelectedItemChanged!(trueIndex);
+          }
+        }
+        return false;
+      },
+      child: _FixedExtentScrollable(
+        controller: scrollController,
+        physics: widget.physics,
+        itemExtent: widget.itemExtent,
+        restorationId: widget.restorationId,
+        viewportBuilder: (BuildContext context, ViewportOffset offset) {
+          return ListWheelViewport(
+            diameterRatio: widget.diameterRatio,
+            perspective: widget.perspective,
+            offAxisFraction: widget.offAxisFraction,
+            useMagnifier: widget.useMagnifier,
+            magnification: widget.magnification,
+            overAndUnderCenterOpacity: widget.overAndUnderCenterOpacity,
+            itemExtent: widget.itemExtent,
+            squeeze: widget.squeeze,
+            renderChildrenOutsideViewport: widget.renderChildrenOutsideViewport,
+            offset: offset,
+            childDelegate: widget.childDelegate,
+            clipBehavior: widget.clipBehavior,
+          );
+        },
+      ),
+    );
+  }
+}
+
+/// Element that supports building children lazily for [ListWheelViewport].
+class ListWheelElement extends RenderObjectElement implements ListWheelChildManager {
+  /// Creates an element that lazily builds children for the given widget.
+  ListWheelElement(ListWheelViewport widget) : super(widget);
+
+  @override
+  ListWheelViewport get widget => super.widget as ListWheelViewport;
+
+  @override
+  RenderListWheelViewport get renderObject => super.renderObject as RenderListWheelViewport;
+
+  // We inflate widgets at two different times:
+  //  1. When we ourselves are told to rebuild (see performRebuild).
+  //  2. When our render object needs a new child (see createChild).
+  // In both cases, we cache the results of calling into our delegate to get the
+  // widget, so that if we do case 2 later, we don't call the builder again.
+  // Any time we do case 1, though, we reset the cache.
+
+  /// A cache of widgets so that we don't have to rebuild every time.
+  final Map<int, Widget?> _childWidgets = HashMap<int, Widget?>();
+
+  /// The map containing all active child elements. SplayTreeMap is used so that
+  /// we have all elements ordered and iterable by their keys.
+  final SplayTreeMap<int, Element> _childElements = SplayTreeMap<int, Element>();
+
+  @override
+  void update(ListWheelViewport newWidget) {
+    final ListWheelViewport oldWidget = widget;
+    super.update(newWidget);
+    final ListWheelChildDelegate newDelegate = newWidget.childDelegate;
+    final ListWheelChildDelegate oldDelegate = oldWidget.childDelegate;
+    if (newDelegate != oldDelegate &&
+        (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRebuild(oldDelegate)))
+      performRebuild();
+  }
+
+  @override
+  int? get childCount => widget.childDelegate.estimatedChildCount;
+
+  @override
+  void performRebuild() {
+    _childWidgets.clear();
+    super.performRebuild();
+    if (_childElements.isEmpty)
+      return;
+
+    final int firstIndex = _childElements.firstKey()!;
+    final int lastIndex = _childElements.lastKey()!;
+
+    for (int index = firstIndex; index <= lastIndex; ++index) {
+      final Element? newChild = updateChild(_childElements[index], retrieveWidget(index), index);
+      if (newChild != null) {
+        _childElements[index] = newChild;
+      } else {
+        _childElements.remove(index);
+      }
+    }
+  }
+
+  /// Asks the underlying delegate for a widget at the given index.
+  ///
+  /// Normally the builder is only called once for each index and the result
+  /// will be cached. However when the element is rebuilt, the cache will be
+  /// cleared.
+  Widget? retrieveWidget(int index) {
+    return _childWidgets.putIfAbsent(index, () => widget.childDelegate.build(this, index));
+  }
+
+  @override
+  bool childExistsAt(int index) => retrieveWidget(index) != null;
+
+  @override
+  void createChild(int index, { required RenderBox? after }) {
+    owner!.buildScope(this, () {
+      final bool insertFirst = after == null;
+      assert(insertFirst || _childElements[index - 1] != null);
+      final Element? newChild =
+        updateChild(_childElements[index], retrieveWidget(index), index);
+      if (newChild != null) {
+        _childElements[index] = newChild;
+      } else {
+        _childElements.remove(index);
+      }
+    });
+  }
+
+  @override
+  void removeChild(RenderBox child) {
+    final int index = renderObject.indexOf(child);
+    owner!.buildScope(this, () {
+      assert(_childElements.containsKey(index));
+      final Element? result = updateChild(_childElements[index], null, index);
+      assert(result == null);
+      _childElements.remove(index);
+      assert(!_childElements.containsKey(index));
+    });
+  }
+
+  @override
+  Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
+    final ListWheelParentData? oldParentData = child?.renderObject?.parentData as ListWheelParentData?;
+    final Element? newChild = super.updateChild(child, newWidget, newSlot);
+    final ListWheelParentData? newParentData = newChild?.renderObject?.parentData as ListWheelParentData?;
+    if (newParentData != null) {
+      newParentData.index = newSlot as int;
+      if (oldParentData != null)
+        newParentData.offset = oldParentData.offset;
+    }
+
+    return newChild;
+  }
+
+  @override
+  void insertRenderObjectChild(RenderObject child, int slot) {
+    final RenderListWheelViewport renderObject = this.renderObject;
+    assert(renderObject.debugValidateChild(child));
+    renderObject.insert(child as RenderBox, after: _childElements[slot - 1]?.renderObject as RenderBox?);
+    assert(renderObject == this.renderObject);
+  }
+
+  @override
+  void moveRenderObjectChild(RenderObject child, int oldSlot, int newSlot) {
+    const String moveChildRenderObjectErrorMessage =
+        'Currently we maintain the list in contiguous increasing order, so '
+        'moving children around is not allowed.';
+    assert(false, moveChildRenderObjectErrorMessage);
+  }
+
+  @override
+  void removeRenderObjectChild(RenderObject child, int slot) {
+    assert(child.parent == renderObject);
+    renderObject.remove(child as RenderBox);
+  }
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    _childElements.forEach((int key, Element child) {
+      visitor(child);
+    });
+  }
+
+  @override
+  void forgetChild(Element child) {
+    _childElements.remove(child.slot);
+    super.forgetChild(child);
+  }
+
+}
+
+/// A viewport showing a subset of children on a wheel.
+///
+/// Typically used with [ListWheelScrollView], this viewport is similar to
+/// [Viewport] in that it shows a subset of children in a scrollable based
+/// on the scrolling offset and the children's dimensions. But uses
+/// [RenderListWheelViewport] to display the children on a wheel.
+///
+/// See also:
+///
+///  * [ListWheelScrollView], widget that combines this viewport with a scrollable.
+///  * [RenderListWheelViewport], the render object that renders the children
+///    on a wheel.
+class ListWheelViewport extends RenderObjectWidget {
+  /// Creates a viewport where children are rendered onto a wheel.
+  ///
+  /// The [diameterRatio] argument defaults to 2.0 and must not be null.
+  ///
+  /// The [perspective] argument defaults to 0.003 and must not be null.
+  ///
+  /// The [itemExtent] argument in pixels must be provided and must be positive.
+  ///
+  /// The [clipBehavior] argument defaults to [Clip.hardEdge] and must not be null.
+  ///
+  /// The [renderChildrenOutsideViewport] argument defaults to false and must
+  /// not be null.
+  ///
+  /// The [offset] argument must be provided and must not be null.
+  const ListWheelViewport({
+    Key? key,
+    this.diameterRatio = RenderListWheelViewport.defaultDiameterRatio,
+    this.perspective = RenderListWheelViewport.defaultPerspective,
+    this.offAxisFraction = 0.0,
+    this.useMagnifier = false,
+    this.magnification = 1.0,
+    this.overAndUnderCenterOpacity = 1.0,
+    required this.itemExtent,
+    this.squeeze = 1.0,
+    this.renderChildrenOutsideViewport = false,
+    required this.offset,
+    required this.childDelegate,
+    this.clipBehavior = Clip.hardEdge,
+  }) : assert(childDelegate != null),
+       assert(offset != null),
+       assert(diameterRatio != null),
+       assert(diameterRatio > 0, RenderListWheelViewport.diameterRatioZeroMessage),
+       assert(perspective != null),
+       assert(perspective > 0),
+       assert(perspective <= 0.01, RenderListWheelViewport.perspectiveTooHighMessage),
+       assert(overAndUnderCenterOpacity != null),
+       assert(overAndUnderCenterOpacity >= 0 && overAndUnderCenterOpacity <= 1),
+       assert(itemExtent != null),
+       assert(itemExtent > 0),
+       assert(squeeze != null),
+       assert(squeeze > 0),
+       assert(renderChildrenOutsideViewport != null),
+       assert(clipBehavior != null),
+       assert(
+         !renderChildrenOutsideViewport || clipBehavior == Clip.none,
+         RenderListWheelViewport.clipBehaviorAndRenderChildrenOutsideViewportConflict,
+       ),
+       super(key: key);
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.diameterRatio}
+  final double diameterRatio;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.perspective}
+  final double perspective;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.offAxisFraction}
+  final double offAxisFraction;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.useMagnifier}
+  final bool useMagnifier;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.magnification}
+  final double magnification;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.overAndUnderCenterOpacity}
+  final double overAndUnderCenterOpacity;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.itemExtent}
+  final double itemExtent;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.squeeze}
+  ///
+  /// Defaults to 1.
+  final double squeeze;
+
+  /// {@macro flutter.rendering.RenderListWheelViewport.renderChildrenOutsideViewport}
+  final bool renderChildrenOutsideViewport;
+
+  /// [ViewportOffset] object describing the content that should be visible
+  /// in the viewport.
+  final ViewportOffset offset;
+
+  /// A delegate that lazily instantiates children.
+  final ListWheelChildDelegate childDelegate;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge].
+  final Clip clipBehavior;
+
+  @override
+  ListWheelElement createElement() => ListWheelElement(this);
+
+  @override
+  RenderListWheelViewport createRenderObject(BuildContext context) {
+    final ListWheelElement childManager = context as ListWheelElement;
+    return RenderListWheelViewport(
+      childManager: childManager,
+      offset: offset,
+      diameterRatio: diameterRatio,
+      perspective: perspective,
+      offAxisFraction: offAxisFraction,
+      useMagnifier: useMagnifier,
+      magnification: magnification,
+      overAndUnderCenterOpacity: overAndUnderCenterOpacity,
+      itemExtent: itemExtent,
+      squeeze: squeeze,
+      renderChildrenOutsideViewport: renderChildrenOutsideViewport,
+      clipBehavior: clipBehavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderListWheelViewport renderObject) {
+    renderObject
+      ..offset = offset
+      ..diameterRatio = diameterRatio
+      ..perspective = perspective
+      ..offAxisFraction = offAxisFraction
+      ..useMagnifier = useMagnifier
+      ..magnification = magnification
+      ..overAndUnderCenterOpacity = overAndUnderCenterOpacity
+      ..itemExtent = itemExtent
+      ..squeeze = squeeze
+      ..renderChildrenOutsideViewport = renderChildrenOutsideViewport
+      ..clipBehavior = clipBehavior;
+  }
+}
diff --git a/lib/src/widgets/localizations.dart b/lib/src/widgets/localizations.dart
new file mode 100644
index 0000000..6a99d2b
--- /dev/null
+++ b/lib/src/widgets/localizations.dart
@@ -0,0 +1,595 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' show Locale;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'binding.dart';
+import 'container.dart';
+import 'debug.dart';
+import 'framework.dart';
+
+// Examples can assume:
+// class Intl { static String message(String s, { String? name, String? locale }) => ''; }
+// Future<void> initializeMessages(String locale) => Future.value();
+
+// Used by loadAll() to record LocalizationsDelegate.load() futures we're
+// waiting for.
+class _Pending {
+  _Pending(this.delegate, this.futureValue);
+  final LocalizationsDelegate<dynamic> delegate;
+  final Future<dynamic> futureValue;
+}
+
+// A utility function used by Localizations to generate one future
+// that completes when all of the LocalizationsDelegate.load() futures
+// complete. The returned map is indexed by each delegate's type.
+//
+// The input future values must have distinct types.
+//
+// The returned Future<Map> will resolve when all of the input map's
+// future values have resolved. If all of the input map's values are
+// SynchronousFutures then a SynchronousFuture will be returned
+// immediately.
+//
+// This is more complicated than just applying Future.wait to input
+// because some of the input.values may be SynchronousFutures. We don't want
+// to Future.wait for the synchronous futures.
+Future<Map<Type, dynamic>> _loadAll(Locale locale, Iterable<LocalizationsDelegate<dynamic>> allDelegates) {
+  final Map<Type, dynamic> output = <Type, dynamic>{};
+  List<_Pending>? pendingList;
+
+  // Only load the first delegate for each delegate type that supports
+  // locale.languageCode.
+  final Set<Type> types = <Type>{};
+  final List<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[];
+  for (final LocalizationsDelegate<dynamic> delegate in allDelegates) {
+    if (!types.contains(delegate.type) && delegate.isSupported(locale)) {
+      types.add(delegate.type);
+      delegates.add(delegate);
+    }
+  }
+
+  for (final LocalizationsDelegate<dynamic> delegate in delegates) {
+    final Future<dynamic> inputValue = delegate.load(locale);
+    dynamic completedValue;
+    final Future<dynamic> futureValue = inputValue.then<dynamic>((dynamic value) {
+      return completedValue = value;
+    });
+    if (completedValue != null) { // inputValue was a SynchronousFuture
+      final Type type = delegate.type;
+      assert(!output.containsKey(type));
+      output[type] = completedValue;
+    } else {
+      pendingList ??= <_Pending>[];
+      pendingList.add(_Pending(delegate, futureValue));
+    }
+  }
+
+  // All of the delegate.load() values were synchronous futures, we're done.
+  if (pendingList == null)
+    return SynchronousFuture<Map<Type, dynamic>>(output);
+
+  // Some of delegate.load() values were asynchronous futures. Wait for them.
+  return Future.wait<dynamic>(pendingList.map<Future<dynamic>>((_Pending p) => p.futureValue))
+    .then<Map<Type, dynamic>>((List<dynamic> values) {
+      assert(values.length == pendingList!.length);
+      for (int i = 0; i < values.length; i += 1) {
+        final Type type = pendingList![i].delegate.type;
+        assert(!output.containsKey(type));
+        output[type] = values[i];
+      }
+      return output;
+    });
+}
+
+/// A factory for a set of localized resources of type `T`, to be loaded by a
+/// [Localizations] widget.
+///
+/// Typical applications have one [Localizations] widget which is created by the
+/// [WidgetsApp] and configured with the app's `localizationsDelegates`
+/// parameter (a list of delegates). The delegate's [type] is used to identify
+/// the object created by an individual delegate's [load] method.
+abstract class LocalizationsDelegate<T> {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const LocalizationsDelegate();
+
+  /// Whether resources for the given locale can be loaded by this delegate.
+  ///
+  /// Return true if the instance of `T` loaded by this delegate's [load]
+  /// method supports the given `locale`'s language.
+  bool isSupported(Locale locale);
+
+  /// Start loading the resources for `locale`. The returned future completes
+  /// when the resources have finished loading.
+  ///
+  /// It's assumed that the this method will return an object that contains
+  /// a collection of related resources (typically defined with one method per
+  /// resource). The object will be retrieved with [Localizations.of].
+  Future<T> load(Locale locale);
+
+  /// Returns true if the resources for this delegate should be loaded
+  /// again by calling the [load] method.
+  ///
+  /// This method is called whenever its [Localizations] widget is
+  /// rebuilt. If it returns true then dependent widgets will be rebuilt
+  /// after [load] has completed.
+  bool shouldReload(covariant LocalizationsDelegate<T> old);
+
+  /// The type of the object returned by the [load] method, T by default.
+  ///
+  /// This type is used to retrieve the object "loaded" by this
+  /// [LocalizationsDelegate] from the [Localizations] inherited widget.
+  /// For example the object loaded by `LocalizationsDelegate<Foo>` would
+  /// be retrieved with:
+  /// ```dart
+  /// Foo foo = Localizations.of<Foo>(context, Foo);
+  /// ```
+  ///
+  /// It's rarely necessary to override this getter.
+  Type get type => T;
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'LocalizationsDelegate')}[$type]';
+}
+
+/// Interface for localized resource values for the lowest levels of the Flutter
+/// framework.
+///
+/// In particular, this maps locales to a specific [Directionality] using the
+/// [textDirection] property.
+///
+/// See also:
+///
+///  * [DefaultWidgetsLocalizations], which implements this interface and
+///    supports a variety of locales.
+abstract class WidgetsLocalizations {
+  /// The reading direction for text in this locale.
+  TextDirection get textDirection;
+
+  /// The `WidgetsLocalizations` from the closest [Localizations] instance
+  /// that encloses the given context.
+  ///
+  /// This method is just a convenient shorthand for:
+  /// `Localizations.of<WidgetsLocalizations>(context, WidgetsLocalizations)!`.
+  ///
+  /// References to the localized resources defined by this class are typically
+  /// written in terms of this method. For example:
+  ///
+  /// ```dart
+  /// textDirection: WidgetsLocalizations.of(context).textDirection,
+  /// ```
+  static WidgetsLocalizations of(BuildContext context) {
+    assert(debugCheckHasWidgetsLocalizations(context));
+    return Localizations.of<WidgetsLocalizations>(context, WidgetsLocalizations)!;
+  }
+}
+
+class _WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
+  const _WidgetsLocalizationsDelegate();
+
+  // This is convenient simplification. It would be more correct test if the locale's
+  // text-direction is LTR.
+  @override
+  bool isSupported(Locale locale) => true;
+
+  @override
+  Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale);
+
+  @override
+  bool shouldReload(_WidgetsLocalizationsDelegate old) => false;
+
+  @override
+  String toString() => 'DefaultWidgetsLocalizations.delegate(en_US)';
+}
+
+/// US English localizations for the widgets library.
+///
+/// See also:
+///
+///  * [GlobalWidgetsLocalizations], which provides widgets localizations for
+///    many languages.
+///  * [WidgetsApp.localizationsDelegates], which automatically includes
+///    [DefaultWidgetsLocalizations.delegate] by default.
+class DefaultWidgetsLocalizations implements WidgetsLocalizations {
+  /// Construct an object that defines the localized values for the widgets
+  /// library for US English (only).
+  ///
+  /// [LocalizationsDelegate] implementations typically call the static [load]
+  const DefaultWidgetsLocalizations();
+
+  @override
+  TextDirection get textDirection => TextDirection.ltr;
+
+  /// Creates an object that provides US English resource values for the
+  /// lowest levels of the widgets library.
+  ///
+  /// The [locale] parameter is ignored.
+  ///
+  /// This method is typically used to create a [LocalizationsDelegate].
+  /// The [WidgetsApp] does so by default.
+  static Future<WidgetsLocalizations> load(Locale locale) {
+    return SynchronousFuture<WidgetsLocalizations>(const DefaultWidgetsLocalizations());
+  }
+
+  /// A [LocalizationsDelegate] that uses [DefaultWidgetsLocalizations.load]
+  /// to create an instance of this class.
+  ///
+  /// [WidgetsApp] automatically adds this value to [WidgetsApp.localizationsDelegates].
+  static const LocalizationsDelegate<WidgetsLocalizations> delegate = _WidgetsLocalizationsDelegate();
+}
+
+class _LocalizationsScope extends InheritedWidget {
+  const _LocalizationsScope({
+    Key? key,
+    required this.locale,
+    required this.localizationsState,
+    required this.typeToResources,
+    required Widget child,
+  }) : assert(localizationsState != null),
+       assert(typeToResources != null),
+       super(key: key, child: child);
+
+  final Locale locale;
+  final _LocalizationsState localizationsState;
+  final Map<Type, dynamic> typeToResources;
+
+  @override
+  bool updateShouldNotify(_LocalizationsScope old) {
+    return typeToResources != old.typeToResources;
+  }
+}
+
+/// Defines the [Locale] for its `child` and the localized resources that the
+/// child depends on.
+///
+/// Localized resources are loaded by the list of [LocalizationsDelegate]
+/// `delegates`. Each delegate is essentially a factory for a collection
+/// of localized resources. There are multiple delegates because there are
+/// multiple sources for localizations within an app.
+///
+/// Delegates are typically simple subclasses of [LocalizationsDelegate] that
+/// override [LocalizationsDelegate.load]. For example a delegate for the
+/// `MyLocalizations` class defined below would be:
+///
+/// ```dart
+/// class _MyDelegate extends LocalizationsDelegate<MyLocalizations> {
+///   @override
+///   Future<MyLocalizations> load(Locale locale) => MyLocalizations.load(locale);
+///
+///   @override
+///   bool shouldReload(MyLocalizationsDelegate old) => false;
+/// }
+/// ```
+///
+/// Each delegate can be viewed as a factory for objects that encapsulate a
+/// a set of localized resources. These objects are retrieved with
+/// by runtime type with [Localizations.of].
+///
+/// The [WidgetsApp] class creates a `Localizations` widget so most apps
+/// will not need to create one. The widget app's `Localizations` delegates can
+/// be initialized with [WidgetsApp.localizationsDelegates]. The [MaterialApp]
+/// class also provides a `localizationsDelegates` parameter that's just
+/// passed along to the [WidgetsApp].
+///
+/// Apps should retrieve collections of localized resources with
+/// `Localizations.of<MyLocalizations>(context, MyLocalizations)`,
+/// where MyLocalizations is an app specific class defines one function per
+/// resource. This is conventionally done by a static `.of` method on the
+/// MyLocalizations class.
+///
+/// For example, using the `MyLocalizations` class defined below, one would
+/// lookup a localized title string like this:
+/// ```dart
+/// MyLocalizations.of(context).title()
+/// ```
+/// If `Localizations` were to be rebuilt with a new `locale` then
+/// the widget subtree that corresponds to [BuildContext] `context` would
+/// be rebuilt after the corresponding resources had been loaded.
+///
+/// This class is effectively an [InheritedWidget]. If it's rebuilt with
+/// a new `locale` or a different list of delegates or any of its
+/// delegates' [LocalizationsDelegate.shouldReload()] methods returns true,
+/// then widgets that have created a dependency by calling
+/// `Localizations.of(context)` will be rebuilt after the resources
+/// for the new locale have been loaded.
+///
+/// The `Localizations` widget also instantiates [Directionality] in order to
+/// support the appropriate [Directionality.textDirection] of the localized
+/// resources.
+///
+/// {@tool snippet}
+///
+/// This following class is defined in terms of the
+/// [Dart `intl` package](https://github.com/dart-lang/intl). Using the `intl`
+/// package isn't required.
+///
+/// ```dart
+/// class MyLocalizations {
+///   MyLocalizations(this.locale);
+///
+///   final Locale locale;
+///
+///   static Future<MyLocalizations> load(Locale locale) {
+///     return initializeMessages(locale.toString())
+///       .then((void _) {
+///         return MyLocalizations(locale);
+///       });
+///   }
+///
+///   static MyLocalizations of(BuildContext context) {
+///     return Localizations.of<MyLocalizations>(context, MyLocalizations)!;
+///   }
+///
+///   String title() => Intl.message('<title>', name: 'title', locale: locale.toString());
+///   // ... more Intl.message() methods like title()
+/// }
+/// ```
+/// {@end-tool}
+/// A class based on the `intl` package imports a generated message catalog that provides
+/// the `initializeMessages()` function and the per-locale backing store for `Intl.message()`.
+/// The message catalog is produced by an `intl` tool that analyzes the source code for
+/// classes that contain `Intl.message()` calls. In this case that would just be the
+/// `MyLocalizations` class.
+///
+/// One could choose another approach for loading localized resources and looking them up while
+/// still conforming to the structure of this example.
+class Localizations extends StatefulWidget {
+  /// Create a widget from which localizations (like translated strings) can be obtained.
+  Localizations({
+    Key? key,
+    required this.locale,
+    required this.delegates,
+    this.child,
+  }) : assert(locale != null),
+       assert(delegates != null),
+       assert(delegates.any((LocalizationsDelegate<dynamic> delegate) => delegate is LocalizationsDelegate<WidgetsLocalizations>)),
+       super(key: key);
+
+  /// Overrides the inherited [Locale] or [LocalizationsDelegate]s for `child`.
+  ///
+  /// This factory constructor is used for the (usually rare) situation where part
+  /// of an app should be localized for a different locale than the one defined
+  /// for the device, or if its localizations should come from a different list
+  /// of [LocalizationsDelegate]s than the list defined by
+  /// [WidgetsApp.localizationsDelegates].
+  ///
+  /// For example you could specify that `myWidget` was only to be localized for
+  /// the US English locale:
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return Localizations.override(
+  ///     context: context,
+  ///     locale: const Locale('en', 'US'),
+  ///     child: myWidget,
+  ///   );
+  /// }
+  /// ```
+  ///
+  /// The `locale` and `delegates` parameters default to the [Localizations.locale]
+  /// and [Localizations.delegates] values from the nearest [Localizations] ancestor.
+  ///
+  /// To override the [Localizations.locale] or [Localizations.delegates] for an
+  /// entire app, specify [WidgetsApp.locale] or [WidgetsApp.localizationsDelegates]
+  /// (or specify the same parameters for [MaterialApp]).
+  factory Localizations.override({
+    Key? key,
+    required BuildContext context,
+    Locale? locale,
+    List<LocalizationsDelegate<dynamic>>? delegates,
+    Widget? child,
+  }) {
+    final List<LocalizationsDelegate<dynamic>> mergedDelegates = Localizations._delegatesOf(context);
+    if (delegates != null)
+      mergedDelegates.insertAll(0, delegates);
+    return Localizations(
+      key: key,
+      locale: locale ?? Localizations.localeOf(context)!,
+      delegates: mergedDelegates,
+      child: child,
+    );
+  }
+
+  /// The resources returned by [Localizations.of] will be specific to this locale.
+  final Locale locale;
+
+  /// This list collectively defines the localized resources objects that can
+  /// be retrieved with [Localizations.of].
+  final List<LocalizationsDelegate<dynamic>> delegates;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// The locale of the Localizations widget for the widget tree that
+  /// corresponds to [BuildContext] `context`.
+  ///
+  /// If no [Localizations] widget is in scope then the [Localizations.localeOf]
+  /// method will throw an exception, unless the `nullOk` argument is set to
+  /// true, in which case it returns null.
+  static Locale? localeOf(BuildContext context, { bool nullOk = false }) {
+    assert(context != null);
+    final _LocalizationsScope? scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
+    if (nullOk && scope == null)
+      return null;
+    assert(() {
+      if (scope == null) {
+        throw FlutterError(
+          'Requested the Locale of a context that does not include a Localizations ancestor.\n'
+          'To request the Locale, the context used to retrieve the Localizations widget must '
+          'be that of a widget that is a descendant of a Localizations widget.'
+        );
+      }
+      if (!nullOk && scope.localizationsState.locale == null) {
+        throw FlutterError(
+          'Localizations.localeOf found a Localizations widget that had a unexpected null locale.\n'
+        );
+      }
+      return true;
+    }());
+    return scope!.localizationsState.locale;
+  }
+
+  /// The locale of the Localizations widget for the widget tree that
+  /// corresponds to [BuildContext] `context`.
+  ///
+  /// If no [Localizations] widget is in scope then this function will return
+  /// null. Equivalent to calling [localeOf] with `nullOk` set to true.
+  static Locale? maybeLocaleOf(BuildContext context) {
+    assert(context != null);
+    final _LocalizationsScope? scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
+    return scope?.localizationsState.locale;
+  }
+
+  // There doesn't appear to be a need to make this public. See the
+  // Localizations.override factory constructor.
+  static List<LocalizationsDelegate<dynamic>> _delegatesOf(BuildContext context) {
+    assert(context != null);
+    final _LocalizationsScope? scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
+    assert(scope != null, 'a Localizations ancestor was not found');
+    return List<LocalizationsDelegate<dynamic>>.from(scope!.localizationsState.widget.delegates);
+  }
+
+  /// Returns the localized resources object of the given `type` for the widget
+  /// tree that corresponds to the given `context`.
+  ///
+  /// Returns null if no resources object of the given `type` exists within
+  /// the given `context`.
+  ///
+  /// This method is typically used by a static factory method on the `type`
+  /// class. For example Flutter's MaterialLocalizations class looks up Material
+  /// resources with a method defined like this:
+  ///
+  /// ```dart
+  /// static MaterialLocalizations of(BuildContext context) {
+  ///    return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
+  /// }
+  /// ```
+  static T? of<T>(BuildContext context, Type type) {
+    assert(context != null);
+    assert(type != null);
+    final _LocalizationsScope? scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
+    return scope?.localizationsState.resourcesFor<T?>(type);
+  }
+
+  @override
+  _LocalizationsState createState() => _LocalizationsState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Locale>('locale', locale));
+    properties.add(IterableProperty<LocalizationsDelegate<dynamic>>('delegates', delegates));
+  }
+}
+
+class _LocalizationsState extends State<Localizations> {
+  final GlobalKey _localizedResourcesScopeKey = GlobalKey();
+  Map<Type, dynamic> _typeToResources = <Type, dynamic>{};
+
+  Locale? get locale => _locale;
+  Locale? _locale;
+
+  @override
+  void initState() {
+    super.initState();
+    load(widget.locale);
+  }
+
+  bool _anyDelegatesShouldReload(Localizations old) {
+    if (widget.delegates.length != old.delegates.length)
+      return true;
+    final List<LocalizationsDelegate<dynamic>> delegates = widget.delegates.toList();
+    final List<LocalizationsDelegate<dynamic>> oldDelegates = old.delegates.toList();
+    for (int i = 0; i < delegates.length; i += 1) {
+      final LocalizationsDelegate<dynamic> delegate = delegates[i];
+      final LocalizationsDelegate<dynamic> oldDelegate = oldDelegates[i];
+      if (delegate.runtimeType != oldDelegate.runtimeType || delegate.shouldReload(oldDelegate))
+        return true;
+    }
+    return false;
+  }
+
+  @override
+  void didUpdateWidget(Localizations old) {
+    super.didUpdateWidget(old);
+    if (widget.locale != old.locale
+        || (widget.delegates == null)
+        || (widget.delegates != null && old.delegates == null)
+        || (widget.delegates != null && _anyDelegatesShouldReload(old)))
+      load(widget.locale);
+  }
+
+  void load(Locale locale) {
+    final Iterable<LocalizationsDelegate<dynamic>> delegates = widget.delegates;
+    if (delegates == null || delegates.isEmpty) {
+      _locale = locale;
+      return;
+    }
+
+    Map<Type, dynamic>? typeToResources;
+    final Future<Map<Type, dynamic>> typeToResourcesFuture = _loadAll(locale, delegates)
+      .then<Map<Type, dynamic>>((Map<Type, dynamic> value) {
+        return typeToResources = value;
+      });
+
+    if (typeToResources != null) {
+      // All of the delegates' resources loaded synchronously.
+      _typeToResources = typeToResources!;
+      _locale = locale;
+    } else {
+      // - Don't rebuild the dependent widgets until the resources for the new locale
+      // have finished loading. Until then the old locale will continue to be used.
+      // - If we're running at app startup time then defer reporting the first
+      // "useful" frame until after the async load has completed.
+      RendererBinding.instance!.deferFirstFrame();
+      typeToResourcesFuture.then<void>((Map<Type, dynamic> value) {
+        if (mounted) {
+          setState(() {
+            _typeToResources = value;
+            _locale = locale;
+          });
+        }
+        RendererBinding.instance!.allowFirstFrame();
+      });
+    }
+  }
+
+  T resourcesFor<T>(Type type) {
+    assert(type != null);
+    final T resources = _typeToResources[type] as T;
+    return resources;
+  }
+
+  TextDirection get _textDirection {
+    final WidgetsLocalizations resources = _typeToResources[WidgetsLocalizations] as WidgetsLocalizations;
+    assert(resources != null);
+    return resources.textDirection;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (_locale == null)
+      return Container();
+    return Semantics(
+      textDirection: _textDirection,
+      child: _LocalizationsScope(
+        key: _localizedResourcesScopeKey,
+        locale: _locale!,
+        localizationsState: this,
+        typeToResources: _typeToResources,
+        child: Directionality(
+          textDirection: _textDirection,
+          child: widget.child!,
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/widgets/media_query.dart b/lib/src/widgets/media_query.dart
new file mode 100644
index 0000000..39a2270
--- /dev/null
+++ b/lib/src/widgets/media_query.dart
@@ -0,0 +1,924 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' as ui;
+import 'package:flute/ui.dart' show Brightness;
+
+import 'package:flute/foundation.dart';
+
+import 'basic.dart';
+import 'debug.dart';
+import 'framework.dart';
+
+/// Whether in portrait or landscape.
+enum Orientation {
+  /// Taller than wide.
+  portrait,
+
+  /// Wider than tall.
+  landscape
+}
+
+/// Information about a piece of media (e.g., a window).
+///
+/// For example, the [MediaQueryData.size] property contains the width and
+/// height of the current window.
+///
+/// To obtain the current [MediaQueryData] for a given [BuildContext], use the
+/// [MediaQuery.of] function. For example, to obtain the size of the current
+/// window, use `MediaQuery.of(context).size`.
+///
+/// If no [MediaQuery] is in scope then the [MediaQuery.of] method will throw an
+/// exception, unless the `nullOk` argument is set to true, in which case it
+/// returns null.
+///
+/// ## Insets and Padding
+///
+/// ![A diagram of padding, viewInsets, and viewPadding in correlation with each
+/// other](https://flutter.github.io/assets-for-api-docs/assets/widgets/media_query.png)
+///
+/// This diagram illustrates how [padding] relates to [viewPadding] and
+/// [viewInsets], shown here in its simplest configuration, as the difference
+/// between the two. In cases when the viewInsets exceed the viewPadding, like
+/// when a software keyboard is shown below, padding goes to zero rather than a
+/// negative value. Therefore, padding is calculated by taking
+/// `max(0.0, viewPadding - viewInsets)`.
+///
+/// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/widgets/window_padding.mp4}
+///
+/// In this diagram, the black areas represent system UI that the app cannot
+/// draw over. The red area represents view padding that the application may not
+/// be able to detect gestures in and may not want to draw in. The grey area
+/// represents the system keyboard, which can cover over the bottom view padding
+/// when visible.
+///
+/// MediaQueryData includes three [EdgeInsets] values:
+/// [padding], [viewPadding], and [viewInsets]. These values reflect the
+/// configuration of the device and are used and optionally consumed by widgets
+/// that position content within these insets. The padding value defines areas
+/// that might not be completely visible, like the display "notch" on the iPhone
+/// X. The viewInsets value defines areas that aren't visible at all, typically
+/// because they're obscured by the device's keyboard. Similar to viewInsets,
+/// viewPadding does not differentiate padding in areas that may be obscured.
+/// For example, by using the viewPadding property, padding would defer to the
+/// iPhone "safe area" regardless of whether a keyboard is showing.
+///
+/// The viewInsets and viewPadding are independent values, they're
+/// measured from the edges of the MediaQuery widget's bounds. Together they
+/// inform the [padding] property. The bounds of the top level MediaQuery
+/// created by [WidgetsApp] are the same as the window that contains the app.
+///
+/// Widgets whose layouts consume space defined by [viewInsets], [viewPadding],
+/// or [padding] should enclose their children in secondary MediaQuery
+/// widgets that reduce those properties by the same amount.
+/// The [removePadding], [removeViewPadding], and [removeViewInsets] methods are
+/// useful for this.
+///
+/// See also:
+///
+///  * [Scaffold], [SafeArea], [CupertinoTabScaffold], and
+///    [CupertinoPageScaffold], all of which are informed by [padding],
+///    [viewPadding], and [viewInsets].
+@immutable
+class MediaQueryData {
+  /// Creates data for a media query with explicit values.
+  ///
+  /// Consider using [MediaQueryData.fromWindow] to create data based on a
+  /// [dart:ui.PlatformDispatcher].
+  const MediaQueryData({
+    this.size = Size.zero,
+    this.devicePixelRatio = 1.0,
+    this.textScaleFactor = 1.0,
+    this.platformBrightness = Brightness.light,
+    this.padding = EdgeInsets.zero,
+    this.viewInsets = EdgeInsets.zero,
+    this.systemGestureInsets = EdgeInsets.zero,
+    this.viewPadding = EdgeInsets.zero,
+    this.alwaysUse24HourFormat = false,
+    this.accessibleNavigation = false,
+    this.invertColors = false,
+    this.highContrast = false,
+    this.disableAnimations = false,
+    this.boldText = false,
+    this.navigationMode = NavigationMode.traditional,
+  }) : assert(size != null),
+       assert(devicePixelRatio != null),
+       assert(textScaleFactor != null),
+       assert(platformBrightness != null),
+       assert(padding != null),
+       assert(viewInsets != null),
+       assert(systemGestureInsets != null),
+       assert(viewPadding != null),
+       assert(alwaysUse24HourFormat != null),
+       assert(accessibleNavigation != null),
+       assert(invertColors != null),
+       assert(highContrast != null),
+       assert(disableAnimations != null),
+       assert(boldText != null),
+       assert(navigationMode != null);
+
+  /// Creates data for a media query based on the given window.
+  ///
+  /// If you use this, you should ensure that you also register for
+  /// notifications so that you can update your [MediaQueryData] when the
+  /// window's metrics change. For example, see
+  /// [WidgetsBindingObserver.didChangeMetrics] or
+  /// [dart:ui.PlatformDispatcher.onMetricsChanged].
+  MediaQueryData.fromWindow(ui.SingletonFlutterWindow window)
+    : size = window.physicalSize / window.devicePixelRatio,
+      devicePixelRatio = window.devicePixelRatio,
+      textScaleFactor = window.textScaleFactor,
+      platformBrightness = window.platformBrightness,
+      padding = EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio),
+      viewPadding = EdgeInsets.fromWindowPadding(window.viewPadding, window.devicePixelRatio),
+      viewInsets = EdgeInsets.fromWindowPadding(window.viewInsets, window.devicePixelRatio),
+      systemGestureInsets = EdgeInsets.fromWindowPadding(window.systemGestureInsets, window.devicePixelRatio),
+      accessibleNavigation = window.accessibilityFeatures.accessibleNavigation,
+      invertColors = window.accessibilityFeatures.invertColors,
+      disableAnimations = window.accessibilityFeatures.disableAnimations,
+      boldText = window.accessibilityFeatures.boldText,
+      highContrast = window.accessibilityFeatures.highContrast,
+      alwaysUse24HourFormat = window.alwaysUse24HourFormat,
+      navigationMode = NavigationMode.traditional;
+
+  /// The size of the media in logical pixels (e.g, the size of the screen).
+  ///
+  /// Logical pixels are roughly the same visual size across devices. Physical
+  /// pixels are the size of the actual hardware pixels on the device. The
+  /// number of physical pixels per logical pixel is described by the
+  /// [devicePixelRatio].
+  final Size size;
+
+  /// The number of device pixels for each logical pixel. This number might not
+  /// be a power of two. Indeed, it might not even be an integer. For example,
+  /// the Nexus 6 has a device pixel ratio of 3.5.
+  final double devicePixelRatio;
+
+  /// The number of font pixels for each logical pixel.
+  ///
+  /// For example, if the text scale factor is 1.5, text will be 50% larger than
+  /// the specified font size.
+  ///
+  /// See also:
+  ///
+  ///  * [MediaQuery.textScaleFactorOf], a convenience method which returns the
+  ///    textScaleFactor defined for a [BuildContext].
+  final double textScaleFactor;
+
+  /// The current brightness mode of the host platform.
+  ///
+  /// For example, starting in Android Pie, battery saver mode asks all apps to
+  /// render in a "dark mode".
+  ///
+  /// Not all platforms necessarily support a concept of brightness mode. Those
+  /// platforms will report [Brightness.light] in this property.
+  final Brightness platformBrightness;
+
+  /// The parts of the display that are completely obscured by system UI,
+  /// typically by the device's keyboard.
+  ///
+  /// When a mobile device's keyboard is visible `viewInsets.bottom`
+  /// corresponds to the top of the keyboard.
+  ///
+  /// This value is independent of the [padding] and [viewPadding]. viewPadding
+  /// is measured from the edges of the [MediaQuery] widget's bounds. Padding is
+  /// calculated based on the viewPadding and viewInsets. The bounds of the top
+  /// level MediaQuery created by [WidgetsApp] are the same as the window
+  /// (often the mobile device screen) that contains the app.
+  ///
+  /// See also:
+  ///
+  ///  * [ui.window], which provides some additional detail about this property
+  ///    and how it relates to [padding] and [viewPadding].
+  final EdgeInsets viewInsets;
+
+  /// The parts of the display that are partially obscured by system UI,
+  /// typically by the hardware display "notches" or the system status bar.
+  ///
+  /// If you consumed this padding (e.g. by building a widget that envelops or
+  /// accounts for this padding in its layout in such a way that children are
+  /// no longer exposed to this padding), you should remove this padding
+  /// for subsequent descendants in the widget tree by inserting a new
+  /// [MediaQuery] widget using the [MediaQuery.removePadding] factory.
+  ///
+  /// Padding is derived from the values of [viewInsets] and [viewPadding].
+  ///
+  /// See also:
+  ///
+  ///  * [ui.window], which provides some additional detail about this
+  ///    property and how it relates to [viewInsets] and [viewPadding].
+  ///  * [SafeArea], a widget that consumes this padding with a [Padding] widget
+  ///    and automatically removes it from the [MediaQuery] for its child.
+  final EdgeInsets padding;
+
+  /// The parts of the display that are partially obscured by system UI,
+  /// typically by the hardware display "notches" or the system status bar.
+  ///
+  /// This value remains the same regardless of whether the system is reporting
+  /// other obstructions in the same physical area of the screen. For example, a
+  /// software keyboard on the bottom of the screen that may cover and consume
+  /// the same area that requires bottom padding will not affect this value.
+  ///
+  /// This value is independent of the [padding] and [viewInsets]: their values
+  /// are measured from the edges of the [MediaQuery] widget's bounds. The
+  /// bounds of the top level MediaQuery created by [WidgetsApp] are the
+  /// same as the window that contains the app. On mobile devices, this will
+  /// typically be the full screen.
+  ///
+  /// See also:
+  ///
+  ///  * [ui.window], which provides some additional detail about this
+  ///    property and how it relates to [padding] and [viewInsets].
+  final EdgeInsets viewPadding;
+
+  /// The areas along the edges of the display where the system consumes
+  /// certain input events and blocks delivery of those events to the app.
+  ///
+  /// Starting with Android Q, simple swipe gestures that start within the
+  /// [systemGestureInsets] areas are used by the system for page navigation
+  /// and may not be delivered to the app. Taps and swipe gestures that begin
+  /// with a long-press are delivered to the app, but simple press-drag-release
+  /// swipe gestures which begin within the area defined by [systemGestureInsets]
+  /// may not be.
+  ///
+  /// Apps should avoid locating gesture detectors within the system gesture
+  /// insets area. Apps should feel free to put visual elements within
+  /// this area.
+  ///
+  /// This property is currently only expected to be set to a non-default value
+  /// on Android starting with version Q.
+  ///
+  /// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+  ///
+  /// For apps that might be deployed on Android Q devices with full gesture
+  /// navigation enabled, use [systemGestureInsets] with [Padding]
+  /// to avoid having the left and right edges of the [Slider] from appearing
+  /// within the area reserved for system gesture navigation.
+  ///
+  /// By default, [Slider]s expand to fill the available width. So, we pad the
+  /// left and right sides.
+  ///
+  /// ```dart
+  /// double _currentValue = 0.2;
+  ///
+  /// @override
+  /// Widget build(BuildContext context) {
+  ///   EdgeInsets systemGestureInsets = MediaQuery.of(context).systemGestureInsets;
+  ///   return Scaffold(
+  ///     appBar: AppBar(title: Text('Pad Slider to avoid systemGestureInsets')),
+  ///     body: Padding(
+  ///       padding: EdgeInsets.only( // only left and right padding are needed here
+  ///         left: systemGestureInsets.left,
+  ///         right: systemGestureInsets.right,
+  ///       ),
+  ///       child: Slider(
+  ///         value: _currentValue.toDouble(),
+  ///         onChanged: (double newValue) {
+  ///           setState(() {
+  ///             _currentValue = newValue;
+  ///           });
+  ///         },
+  ///       ),
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  final EdgeInsets systemGestureInsets;
+
+  /// Whether to use 24-hour format when formatting time.
+  ///
+  /// The behavior of this flag is different across platforms:
+  ///
+  /// - On Android this flag is reported directly from the user settings called
+  ///   "Use 24-hour format". It applies to any locale used by the application,
+  ///   whether it is the system-wide locale, or the custom locale set by the
+  ///   application.
+  /// - On iOS this flag is set to true when the user setting called "24-Hour
+  ///   Time" is set or the system-wide locale's default uses 24-hour
+  ///   formatting.
+  final bool alwaysUse24HourFormat;
+
+  /// Whether the user is using an accessibility service like TalkBack or
+  /// VoiceOver to interact with the application.
+  ///
+  /// When this setting is true, features such as timeouts should be disabled or
+  /// have minimum durations increased.
+  ///
+  /// See also:
+  ///
+  ///  * [dart:ui.PlatformDispatcher.accessibilityFeatures], where the setting originates.
+  final bool accessibleNavigation;
+
+  /// Whether the device is inverting the colors of the platform.
+  ///
+  /// This flag is currently only updated on iOS devices.
+  ///
+  /// See also:
+  ///
+  ///  * [dart:ui.PlatformDispatcher.accessibilityFeatures], where the setting
+  ///    originates.
+  final bool invertColors;
+
+  /// Whether the user requested a high contrast between foreground and background
+  /// content on iOS, via Settings -> Accessibility -> Increase Contrast.
+  ///
+  /// This flag is currently only updated on iOS devices that are running iOS 13
+  /// or above.
+  final bool highContrast;
+
+  /// Whether the platform is requesting that animations be disabled or reduced
+  /// as much as possible.
+  ///
+  /// See also:
+  ///
+  ///  * [dart:ui.PlatformDispatcher.accessibilityFeatures], where the setting
+  ///    originates.
+  final bool disableAnimations;
+
+  /// Whether the platform is requesting that text be drawn with a bold font
+  /// weight.
+  ///
+  /// See also:
+  ///
+  ///  * [dart:ui.PlatformDispatcher.accessibilityFeatures], where the setting
+  ///    originates.
+  final bool boldText;
+
+  /// Describes the navigation mode requested by the platform.
+  ///
+  /// Some user interfaces are better navigated using a directional pad (DPAD)
+  /// or arrow keys, and for those interfaces, some widgets need to handle these
+  /// directional events differently. In order to know when to do that, these
+  /// widgets will look for the navigation mode in effect for their context.
+  ///
+  /// For instance, in a television interface, [NavigationMode.directional]
+  /// should be set, so that directional navigation is used to navigate away
+  /// from a text field using the DPAD. In contrast, on a regular desktop
+  /// application with the `navigationMode` set to [NavigationMode.traditional],
+  /// the arrow keys are used to move the cursor instead of navigating away.
+  ///
+  /// The [NavigationMode] values indicate the type of navigation to be used in
+  /// a widget subtree for those widgets sensitive to it.
+  final NavigationMode navigationMode;
+
+  /// The orientation of the media (e.g., whether the device is in landscape or
+  /// portrait mode).
+  Orientation get orientation {
+    return size.width > size.height ? Orientation.landscape : Orientation.portrait;
+  }
+
+  /// Creates a copy of this media query data but with the given fields replaced
+  /// with the new values.
+  MediaQueryData copyWith({
+    Size? size,
+    double? devicePixelRatio,
+    double? textScaleFactor,
+    Brightness? platformBrightness,
+    EdgeInsets? padding,
+    EdgeInsets? viewPadding,
+    EdgeInsets? viewInsets,
+    EdgeInsets? systemGestureInsets,
+    bool? alwaysUse24HourFormat,
+    bool? highContrast,
+    bool? disableAnimations,
+    bool? invertColors,
+    bool? accessibleNavigation,
+    bool? boldText,
+    NavigationMode? navigationMode,
+  }) {
+    return MediaQueryData(
+      size: size ?? this.size,
+      devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
+      textScaleFactor: textScaleFactor ?? this.textScaleFactor,
+      platformBrightness: platformBrightness ?? this.platformBrightness,
+      padding: padding ?? this.padding,
+      viewPadding: viewPadding ?? this.viewPadding,
+      viewInsets: viewInsets ?? this.viewInsets,
+      systemGestureInsets: systemGestureInsets ?? this.systemGestureInsets,
+      alwaysUse24HourFormat: alwaysUse24HourFormat ?? this.alwaysUse24HourFormat,
+      invertColors: invertColors ?? this.invertColors,
+      highContrast: highContrast ?? this.highContrast,
+      disableAnimations: disableAnimations ?? this.disableAnimations,
+      accessibleNavigation: accessibleNavigation ?? this.accessibleNavigation,
+      boldText: boldText ?? this.boldText,
+      navigationMode: navigationMode ?? this.navigationMode,
+    );
+  }
+
+  /// Creates a copy of this media query data but with the given [padding]s
+  /// replaced with zero.
+  ///
+  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
+  /// must not be null. If all four are false (the default) then this
+  /// [MediaQueryData] is returned unmodified.
+  ///
+  /// See also:
+  ///
+  ///  * [MediaQuery.removePadding], which uses this method to remove [padding]
+  ///    from the ambient [MediaQuery].
+  ///  * [SafeArea], which both removes the padding from the [MediaQuery] and
+  ///    adds a [Padding] widget.
+  ///  * [removeViewInsets], the same thing but for [viewInsets].
+  ///  * [removeViewPadding], the same thing but for [viewPadding].
+  MediaQueryData removePadding({
+    bool removeLeft = false,
+    bool removeTop = false,
+    bool removeRight = false,
+    bool removeBottom = false,
+  }) {
+    if (!(removeLeft || removeTop || removeRight || removeBottom))
+      return this;
+    return MediaQueryData(
+      size: size,
+      devicePixelRatio: devicePixelRatio,
+      textScaleFactor: textScaleFactor,
+      platformBrightness: platformBrightness,
+      padding: padding.copyWith(
+        left: removeLeft ? 0.0 : null,
+        top: removeTop ? 0.0 : null,
+        right: removeRight ? 0.0 : null,
+        bottom: removeBottom ? 0.0 : null,
+      ),
+      viewPadding: viewPadding.copyWith(
+        left: removeLeft ? math.max(0.0, viewPadding.left - padding.left) : null,
+        top: removeTop ? math.max(0.0, viewPadding.top - padding.top) : null,
+        right: removeRight ? math.max(0.0, viewPadding.right - padding.right) : null,
+        bottom: removeBottom ? math.max(0.0, viewPadding.bottom - padding.bottom) : null,
+      ),
+      viewInsets: viewInsets,
+      alwaysUse24HourFormat: alwaysUse24HourFormat,
+      highContrast: highContrast,
+      disableAnimations: disableAnimations,
+      invertColors: invertColors,
+      accessibleNavigation: accessibleNavigation,
+      boldText: boldText,
+    );
+  }
+
+  /// Creates a copy of this media query data but with the given [viewInsets]
+  /// replaced with zero.
+  ///
+  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
+  /// must not be null. If all four are false (the default) then this
+  /// [MediaQueryData] is returned unmodified.
+  ///
+  /// See also:
+  ///
+  ///  * [MediaQuery.removeViewInsets], which uses this method to remove
+  ///    [viewInsets] from the ambient [MediaQuery].
+  ///  * [removePadding], the same thing but for [padding].
+  ///  * [removeViewPadding], the same thing but for [viewPadding].
+  MediaQueryData removeViewInsets({
+    bool removeLeft = false,
+    bool removeTop = false,
+    bool removeRight = false,
+    bool removeBottom = false,
+  }) {
+    if (!(removeLeft || removeTop || removeRight || removeBottom))
+      return this;
+    return MediaQueryData(
+      size: size,
+      devicePixelRatio: devicePixelRatio,
+      textScaleFactor: textScaleFactor,
+      platformBrightness: platformBrightness,
+      padding: padding,
+      viewPadding: viewPadding.copyWith(
+        left: removeLeft ? math.max(0.0, viewPadding.left - viewInsets.left) : null,
+        top: removeTop ? math.max(0.0, viewPadding.top - viewInsets.top) : null,
+        right: removeRight ? math.max(0.0, viewPadding.right - viewInsets.right) : null,
+        bottom: removeBottom ? math.max(0.0, viewPadding.bottom - viewInsets.bottom) : null,
+      ),
+      viewInsets: viewInsets.copyWith(
+        left: removeLeft ? 0.0 : null,
+        top: removeTop ? 0.0 : null,
+        right: removeRight ? 0.0 : null,
+        bottom: removeBottom ? 0.0 : null,
+      ),
+      alwaysUse24HourFormat: alwaysUse24HourFormat,
+      highContrast: highContrast,
+      disableAnimations: disableAnimations,
+      invertColors: invertColors,
+      accessibleNavigation: accessibleNavigation,
+      boldText: boldText,
+    );
+  }
+
+  /// Creates a copy of this media query data but with the given [viewPadding]
+  /// replaced with zero.
+  ///
+  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
+  /// must not be null. If all four are false (the default) then this
+  /// [MediaQueryData] is returned unmodified.
+  ///
+  /// See also:
+  ///
+  ///  * [MediaQuery.removeViewPadding], which uses this method to remove
+  ///    [viewPadding] from the ambient [MediaQuery].
+  ///  * [removePadding], the same thing but for [padding].
+  ///  * [removeViewInsets], the same thing but for [viewInsets].
+  MediaQueryData removeViewPadding({
+    bool removeLeft = false,
+    bool removeTop = false,
+    bool removeRight = false,
+    bool removeBottom = false,
+  }) {
+    if (!(removeLeft || removeTop || removeRight || removeBottom))
+      return this;
+    return MediaQueryData(
+      size: size,
+      devicePixelRatio: devicePixelRatio,
+      textScaleFactor: textScaleFactor,
+      platformBrightness: platformBrightness,
+      padding: padding.copyWith(
+        left: removeLeft ? 0.0 : null,
+        top: removeTop ? 0.0 : null,
+        right: removeRight ? 0.0 : null,
+        bottom: removeBottom ? 0.0 : null,
+      ),
+      viewInsets: viewInsets,
+      viewPadding: viewPadding.copyWith(
+        left: removeLeft ? 0.0 : null,
+        top: removeTop ? 0.0 : null,
+        right: removeRight ? 0.0 : null,
+        bottom: removeBottom ? 0.0 : null,
+      ),
+      alwaysUse24HourFormat: alwaysUse24HourFormat,
+      highContrast: highContrast,
+      disableAnimations: disableAnimations,
+      invertColors: invertColors,
+      accessibleNavigation: accessibleNavigation,
+      boldText: boldText,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is MediaQueryData
+        && other.size == size
+        && other.devicePixelRatio == devicePixelRatio
+        && other.textScaleFactor == textScaleFactor
+        && other.platformBrightness == platformBrightness
+        && other.padding == padding
+        && other.viewPadding == viewPadding
+        && other.viewInsets == viewInsets
+        && other.alwaysUse24HourFormat == alwaysUse24HourFormat
+        && other.highContrast == highContrast
+        && other.disableAnimations == disableAnimations
+        && other.invertColors == invertColors
+        && other.accessibleNavigation == accessibleNavigation
+        && other.boldText == boldText
+        && other.navigationMode == navigationMode;
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(
+      size,
+      devicePixelRatio,
+      textScaleFactor,
+      platformBrightness,
+      padding,
+      viewPadding,
+      viewInsets,
+      alwaysUse24HourFormat,
+      highContrast,
+      disableAnimations,
+      invertColors,
+      accessibleNavigation,
+      boldText,
+      navigationMode,
+    );
+  }
+
+  @override
+  String toString() {
+    final List<String> properties = <String>[
+      'size: $size',
+      'devicePixelRatio: ${devicePixelRatio.toStringAsFixed(1)}',
+      'textScaleFactor: ${textScaleFactor.toStringAsFixed(1)}',
+      'platformBrightness: $platformBrightness',
+      'padding: $padding',
+      'viewPadding: $viewPadding',
+      'viewInsets: $viewInsets',
+      'alwaysUse24HourFormat: $alwaysUse24HourFormat',
+      'accessibleNavigation: $accessibleNavigation',
+      'highContrast: $highContrast',
+      'disableAnimations: $disableAnimations',
+      'invertColors: $invertColors',
+      'boldText: $boldText',
+      'navigationMode: ${describeEnum(navigationMode)}',
+    ];
+    return '${objectRuntimeType(this, 'MediaQueryData')}(${properties.join(', ')})';
+  }
+}
+
+/// Establishes a subtree in which media queries resolve to the given data.
+///
+/// For example, to learn the size of the current media (e.g., the window
+/// containing your app), you can read the [MediaQueryData.size] property from
+/// the [MediaQueryData] returned by [MediaQuery.of]:
+/// `MediaQuery.of(context).size`.
+///
+/// Querying the current media using [MediaQuery.of] will cause your widget to
+/// rebuild automatically whenever the [MediaQueryData] changes (e.g., if the
+/// user rotates their device).
+///
+/// If no [MediaQuery] is in scope then the [MediaQuery.of] method will throw an
+/// exception, unless the `nullOk` argument is set to true, in which case it
+/// returns null.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=A3WrA4zAaPw}
+///
+/// See also:
+///
+///  * [WidgetsApp] and [MaterialApp], which introduce a [MediaQuery] and keep
+///    it up to date with the current screen metrics as they change.
+///  * [MediaQueryData], the data structure that represents the metrics.
+class MediaQuery extends InheritedWidget {
+  /// Creates a widget that provides [MediaQueryData] to its descendants.
+  ///
+  /// The [data] and [child] arguments must not be null.
+  const MediaQuery({
+    Key? key,
+    required this.data,
+    required Widget child,
+  }) : assert(child != null),
+       assert(data != null),
+       super(key: key, child: child);
+
+  /// Creates a new [MediaQuery] that inherits from the ambient [MediaQuery]
+  /// from the given context, but removes the specified padding.
+  ///
+  /// This should be inserted into the widget tree when the [MediaQuery] padding
+  /// is consumed by a widget in such a way that the padding is no longer
+  /// exposed to the widget's descendants or siblings.
+  ///
+  /// The [context] argument is required, must not be null, and must have a
+  /// [MediaQuery] in scope.
+  ///
+  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
+  /// must not be null. If all four are false (the default) then the returned
+  /// [MediaQuery] reuses the ambient [MediaQueryData] unmodified, which is not
+  /// particularly useful.
+  ///
+  /// The [child] argument is required and must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [SafeArea], which both removes the padding from the [MediaQuery] and
+  ///    adds a [Padding] widget.
+  ///  * [MediaQueryData.padding], the affected property of the
+  ///    [MediaQueryData].
+  ///  * [removeViewInsets], the same thing but for [MediaQueryData.viewInsets].
+  ///  * [removeViewPadding], the same thing but for
+  ///    [MediaQueryData.viewPadding].
+  factory MediaQuery.removePadding({
+    Key? key,
+    required BuildContext context,
+    bool removeLeft = false,
+    bool removeTop = false,
+    bool removeRight = false,
+    bool removeBottom = false,
+    required Widget child,
+  }) {
+    return MediaQuery(
+      key: key,
+      data: MediaQuery.of(context).removePadding(
+        removeLeft: removeLeft,
+        removeTop: removeTop,
+        removeRight: removeRight,
+        removeBottom: removeBottom,
+      ),
+      child: child,
+    );
+  }
+
+  /// Creates a new [MediaQuery] that inherits from the ambient [MediaQuery]
+  /// from the given context, but removes the specified view insets.
+  ///
+  /// This should be inserted into the widget tree when the [MediaQuery] view
+  /// insets are consumed by a widget in such a way that the view insets are no
+  /// longer exposed to the widget's descendants or siblings.
+  ///
+  /// The [context] argument is required, must not be null, and must have a
+  /// [MediaQuery] in scope.
+  ///
+  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
+  /// must not be null. If all four are false (the default) then the returned
+  /// [MediaQuery] reuses the ambient [MediaQueryData] unmodified, which is not
+  /// particularly useful.
+  ///
+  /// The [child] argument is required and must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [MediaQueryData.viewInsets], the affected property of the
+  ///    [MediaQueryData].
+  ///  * [removePadding], the same thing but for [MediaQueryData.padding].
+  ///  * [removeViewPadding], the same thing but for
+  ///    [MediaQueryData.viewPadding].
+  factory MediaQuery.removeViewInsets({
+    Key? key,
+    required BuildContext context,
+    bool removeLeft = false,
+    bool removeTop = false,
+    bool removeRight = false,
+    bool removeBottom = false,
+    required Widget child,
+  }) {
+    return MediaQuery(
+      key: key,
+      data: MediaQuery.of(context).removeViewInsets(
+        removeLeft: removeLeft,
+        removeTop: removeTop,
+        removeRight: removeRight,
+        removeBottom: removeBottom,
+      ),
+      child: child,
+    );
+  }
+
+  /// Creates a new [MediaQuery] that inherits from the ambient [MediaQuery]
+  /// from the given context, but removes the specified view padding.
+  ///
+  /// This should be inserted into the widget tree when the [MediaQuery] view
+  /// padding is consumed by a widget in such a way that the view padding is no
+  /// longer exposed to the widget's descendants or siblings.
+  ///
+  /// The [context] argument is required, must not be null, and must have a
+  /// [MediaQuery] in scope.
+  ///
+  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
+  /// must not be null. If all four are false (the default) then the returned
+  /// [MediaQuery] reuses the ambient [MediaQueryData] unmodified, which is not
+  /// particularly useful.
+  ///
+  /// The [child] argument is required and must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [MediaQueryData.viewPadding], the affected property of the
+  ///    [MediaQueryData].
+  ///  * [removePadding], the same thing but for [MediaQueryData.padding].
+  ///  * [removeViewInsets], the same thing but for [MediaQueryData.viewInsets].
+  factory MediaQuery.removeViewPadding({
+    Key? key,
+    required BuildContext context,
+    bool removeLeft = false,
+    bool removeTop = false,
+    bool removeRight = false,
+    bool removeBottom = false,
+    required Widget child,
+  }) {
+    return MediaQuery(
+      key: key,
+      data: MediaQuery.of(context).removeViewPadding(
+        removeLeft: removeLeft,
+        removeTop: removeTop,
+        removeRight: removeRight,
+        removeBottom: removeBottom,
+      ),
+      child: child,
+    );
+  }
+
+  /// Contains information about the current media.
+  ///
+  /// For example, the [MediaQueryData.size] property contains the width and
+  /// height of the current window.
+  final MediaQueryData data;
+
+  /// The data from the closest instance of this class that encloses the given
+  /// context.
+  ///
+  /// You can use this function to query the size and orientation of the screen,
+  /// as well as other media parameters (see [MediaQueryData] for more
+  /// examples). When that information changes, your widget will be scheduled to
+  /// be rebuilt, keeping your widget up-to-date.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// MediaQueryData media = MediaQuery.of(context);
+  /// ```
+  ///
+  /// If there is no [MediaQuery] in scope, this will throw a [TypeError]
+  /// exception in release builds, and throw a descriptive [FlutterError] in
+  /// debug builds.
+  ///
+  /// See also:
+  ///
+  ///  * [maybeOf], which doesn't throw or assert if it doesn't find a
+  ///    [MediaQuery] ancestor, it returns null instead.
+  static MediaQueryData of(BuildContext context) {
+    assert(context != null);
+    assert(debugCheckHasMediaQuery(context));
+    return context.dependOnInheritedWidgetOfExactType<MediaQuery>()!.data;
+  }
+
+  /// The data from the closest instance of this class that encloses the given
+  /// context, if any.
+  ///
+  /// Use this function if you want to allow situations where no [MediaQuery] is
+  /// in scope. Prefer using [MediaQuery.of] in situations where a media query
+  /// is always expected to exist.
+  ///
+  /// If there is no [MediaQuery] in scope, then this function will return null.
+  ///
+  /// You can use this function to query the size and orientation of the screen,
+  /// as well as other media parameters (see [MediaQueryData] for more
+  /// examples). When that information changes, your widget will be scheduled to
+  /// be rebuilt, keeping your widget up-to-date.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// MediaQueryData? mediaQuery = MediaQuery.maybeOf(context);
+  /// if (mediaQuery == null) {
+  ///   // Do something else instead.
+  /// }
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * [of], which will throw if it doesn't find a [MediaQuery] ancestor,
+  ///    instead of returning null.
+  static MediaQueryData? maybeOf(BuildContext context) {
+    assert(context != null);
+    return context.dependOnInheritedWidgetOfExactType<MediaQuery>()?.data;
+  }
+
+  /// Returns textScaleFactor for the nearest MediaQuery ancestor or 1.0, if
+  /// no such ancestor exists.
+  static double textScaleFactorOf(BuildContext context) {
+    return MediaQuery.maybeOf(context)?.textScaleFactor ?? 1.0;
+  }
+
+  /// Returns platformBrightness for the nearest MediaQuery ancestor or
+  /// [Brightness.light], if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// any property of the ancestor [MediaQuery] changes.
+  static Brightness platformBrightnessOf(BuildContext context) {
+    return MediaQuery.maybeOf(context)?.platformBrightness ?? Brightness.light;
+  }
+
+  /// Returns highContrast for the nearest MediaQuery ancestor or false, if no
+  /// such ancestor exists.
+  ///
+  /// See also:
+  ///
+  ///  * [MediaQueryData.highContrast], which indicates the platform's
+  ///    desire to increase contrast.
+  static bool highContrastOf(BuildContext context) {
+    return MediaQuery.maybeOf(context)?.highContrast ?? false;
+  }
+
+  /// Returns the boldText accessibility setting for the nearest MediaQuery
+  /// ancestor, or false if no such ancestor exists.
+  static bool boldTextOverride(BuildContext context) {
+    return MediaQuery.maybeOf(context)?.boldText ?? false;
+  }
+
+  @override
+  bool updateShouldNotify(MediaQuery oldWidget) => data != oldWidget.data;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<MediaQueryData>('data', data, showName: false));
+  }
+}
+
+/// Describes the navigation mode to be set by a [MediaQuery] widget.
+///
+/// The different modes indicate the type of navigation to be used in a widget
+/// subtree for those widgets sensitive to it.
+///
+/// Use `MediaQuery.of(context).navigationMode` to determine the navigation mode
+/// in effect for the given context. Use a [MediaQuery] widget to set the
+/// navigation mode for its descendant widgets.
+enum NavigationMode {
+  /// This indicates a traditional keyboard-and-mouse navigation modality.
+  ///
+  /// This navigation mode is where the arrow keys can be used for secondary
+  /// modification operations, like moving sliders or cursors, and disabled
+  /// controls will lose focus and not be traversable.
+  traditional,
+
+  /// This indicates a directional-based navigation mode.
+  ///
+  /// This navigation mode indicates that arrow keys should be reserved for
+  /// navigation operations, and secondary modifications operations, like moving
+  /// sliders or cursors, will use alternative bindings or be disabled.
+  ///
+  /// Some behaviors are also affected by this mode. For instance, disabled
+  /// controls will retain focus when disabled, and will be able to receive
+  /// focus (although they remain disabled) when traversed.
+  directional,
+}
diff --git a/lib/src/widgets/modal_barrier.dart b/lib/src/widgets/modal_barrier.dart
new file mode 100644
index 0000000..b9dcb62
--- /dev/null
+++ b/lib/src/widgets/modal_barrier.dart
@@ -0,0 +1,300 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+
+import 'basic.dart';
+import 'container.dart';
+import 'debug.dart';
+import 'framework.dart';
+import 'gesture_detector.dart';
+import 'navigator.dart';
+import 'transitions.dart';
+
+/// A widget that prevents the user from interacting with widgets behind itself.
+///
+/// The modal barrier is the scrim that is rendered behind each route, which
+/// generally prevents the user from interacting with the route below the
+/// current route, and normally partially obscures such routes.
+///
+/// For example, when a dialog is on the screen, the page below the dialog is
+/// usually darkened by the modal barrier.
+///
+/// See also:
+///
+///  * [ModalRoute], which indirectly uses this widget.
+///  * [AnimatedModalBarrier], which is similar but takes an animated [color]
+///    instead of a single color value.
+class ModalBarrier extends StatelessWidget {
+  /// Creates a widget that blocks user interaction.
+  const ModalBarrier({
+    Key? key,
+    this.color,
+    this.dismissible = true,
+    this.semanticsLabel,
+    this.barrierSemanticsDismissible = true,
+  }) : super(key: key);
+
+  /// If non-null, fill the barrier with this color.
+  ///
+  /// See also:
+  ///
+  ///  * [ModalRoute.barrierColor], which controls this property for the
+  ///    [ModalBarrier] built by [ModalRoute] pages.
+  final Color? color;
+
+  /// Whether touching the barrier will pop the current route off the [Navigator].
+  ///
+  /// See also:
+  ///
+  ///  * [ModalRoute.barrierDismissible], which controls this property for the
+  ///    [ModalBarrier] built by [ModalRoute] pages.
+  final bool dismissible;
+
+  /// Whether the modal barrier semantics are included in the semantics tree.
+  ///
+  /// See also:
+  ///
+  ///  * [ModalRoute.semanticsDismissible], which controls this property for
+  ///    the [ModalBarrier] built by [ModalRoute] pages.
+  final bool? barrierSemanticsDismissible;
+
+  /// Semantics label used for the barrier if it is [dismissible].
+  ///
+  /// The semantics label is read out by accessibility tools (e.g. TalkBack
+  /// on Android and VoiceOver on iOS) when the barrier is focused.
+  ///
+  /// See also:
+  ///
+  ///  * [ModalRoute.barrierLabel], which controls this property for the
+  ///    [ModalBarrier] built by [ModalRoute] pages.
+  final String? semanticsLabel;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(!dismissible || semanticsLabel == null || debugCheckHasDirectionality(context));
+    final bool platformSupportsDismissingBarrier;
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        platformSupportsDismissingBarrier = false;
+        break;
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        platformSupportsDismissingBarrier = true;
+        break;
+    }
+    assert(platformSupportsDismissingBarrier != null);
+    final bool semanticsDismissible = dismissible && platformSupportsDismissingBarrier;
+    final bool modalBarrierSemanticsDismissible = barrierSemanticsDismissible ?? semanticsDismissible;
+    return BlockSemantics(
+      child: ExcludeSemantics(
+        // On Android, the back button is used to dismiss a modal. On iOS, some
+        // modal barriers are not dismissible in accessibility mode.
+        excluding: !semanticsDismissible || !modalBarrierSemanticsDismissible,
+        child: _ModalBarrierGestureDetector(
+          onDismiss: () {
+            if (dismissible)
+              Navigator.maybePop(context);
+            else
+              SystemSound.play(SystemSoundType.alert);
+          },
+          child: Semantics(
+            label: semanticsDismissible ? semanticsLabel : null,
+            textDirection: semanticsDismissible && semanticsLabel != null ? Directionality.of(context) : null,
+            child: MouseRegion(
+              cursor: SystemMouseCursors.basic,
+              opaque: true,
+              child: ConstrainedBox(
+                constraints: const BoxConstraints.expand(),
+                child: color == null ? null : DecoratedBox(
+                  decoration: BoxDecoration(
+                    color: color,
+                  ),
+                ),
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+/// A widget that prevents the user from interacting with widgets behind itself,
+/// and can be configured with an animated color value.
+///
+/// The modal barrier is the scrim that is rendered behind each route, which
+/// generally prevents the user from interacting with the route below the
+/// current route, and normally partially obscures such routes.
+///
+/// For example, when a dialog is on the screen, the page below the dialog is
+/// usually darkened by the modal barrier.
+///
+/// This widget is similar to [ModalBarrier] except that it takes an animated
+/// [color] instead of a single color.
+///
+/// See also:
+///
+///  * [ModalRoute], which uses this widget.
+class AnimatedModalBarrier extends AnimatedWidget {
+  /// Creates a widget that blocks user interaction.
+  const AnimatedModalBarrier({
+    Key? key,
+    required Animation<Color?> color,
+    this.dismissible = true,
+    this.semanticsLabel,
+    this.barrierSemanticsDismissible,
+  }) : super(key: key, listenable: color);
+
+  /// If non-null, fill the barrier with this color.
+  ///
+  /// See also:
+  ///
+  ///  * [ModalRoute.barrierColor], which controls this property for the
+  ///    [AnimatedModalBarrier] built by [ModalRoute] pages.
+  Animation<Color?> get color => listenable as Animation<Color?>;
+
+  /// Whether touching the barrier will pop the current route off the [Navigator].
+  ///
+  /// See also:
+  ///
+  ///  * [ModalRoute.barrierDismissible], which controls this property for the
+  ///    [AnimatedModalBarrier] built by [ModalRoute] pages.
+  final bool dismissible;
+
+  /// Semantics label used for the barrier if it is [dismissible].
+  ///
+  /// The semantics label is read out by accessibility tools (e.g. TalkBack
+  /// on Android and VoiceOver on iOS) when the barrier is focused.
+  /// See also:
+  ///
+  ///  * [ModalRoute.barrierLabel], which controls this property for the
+  ///    [ModalBarrier] built by [ModalRoute] pages.
+  final String? semanticsLabel;
+
+  /// Whether the modal barrier semantics are included in the semantics tree.
+  ///
+  /// See also:
+  ///
+  ///  * [ModalRoute.semanticsDismissible], which controls this property for
+  ///    the [ModalBarrier] built by [ModalRoute] pages.
+  final bool? barrierSemanticsDismissible;
+
+  @override
+  Widget build(BuildContext context) {
+    return ModalBarrier(
+      color: color.value,
+      dismissible: dismissible,
+      semanticsLabel: semanticsLabel,
+      barrierSemanticsDismissible: barrierSemanticsDismissible,
+    );
+  }
+}
+
+// Recognizes tap down by any pointer button.
+//
+// It is similar to [TapGestureRecognizer.onTapDown], but accepts any single
+// button, which means the gesture also takes parts in gesture arenas.
+class _AnyTapGestureRecognizer extends BaseTapGestureRecognizer {
+  _AnyTapGestureRecognizer({ Object? debugOwner })
+    : super(debugOwner: debugOwner);
+
+  VoidCallback? onAnyTapUp;
+
+  @protected
+  @override
+  bool isPointerAllowed(PointerDownEvent event) {
+    if (onAnyTapUp == null)
+      return false;
+    return super.isPointerAllowed(event);
+  }
+
+  @protected
+  @override
+  void handleTapDown({PointerDownEvent? down}) {
+    // Do nothing.
+  }
+
+  @protected
+  @override
+  void handleTapUp({PointerDownEvent? down, PointerUpEvent? up}) {
+    if (onAnyTapUp != null)
+      onAnyTapUp!();
+  }
+
+  @protected
+  @override
+  void handleTapCancel({PointerDownEvent? down, PointerCancelEvent? cancel, String? reason}) {
+    // Do nothing.
+  }
+
+  @override
+  String get debugDescription => 'any tap';
+}
+
+class _ModalBarrierSemanticsDelegate extends SemanticsGestureDelegate {
+  const _ModalBarrierSemanticsDelegate({this.onDismiss});
+
+  final VoidCallback? onDismiss;
+
+  @override
+  void assignSemantics(RenderSemanticsGestureHandler renderObject) {
+    renderObject.onTap = onDismiss;
+  }
+}
+
+
+class _AnyTapGestureRecognizerFactory extends GestureRecognizerFactory<_AnyTapGestureRecognizer> {
+  const _AnyTapGestureRecognizerFactory({this.onAnyTapUp});
+
+  final VoidCallback? onAnyTapUp;
+
+  @override
+  _AnyTapGestureRecognizer constructor() => _AnyTapGestureRecognizer();
+
+  @override
+  void initializer(_AnyTapGestureRecognizer instance) {
+    instance.onAnyTapUp = onAnyTapUp;
+  }
+}
+
+// A GestureDetector used by ModalBarrier. It only has one callback,
+// [onAnyTapDown], which recognizes tap down unconditionally.
+class _ModalBarrierGestureDetector extends StatelessWidget {
+  const _ModalBarrierGestureDetector({
+    Key? key,
+    required this.child,
+    required this.onDismiss,
+  }) : assert(child != null),
+       assert(onDismiss != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  /// See [RawGestureDetector.child].
+  final Widget child;
+
+  /// Immediately called when an event that should dismiss the modal barrier
+  /// has happened.
+  final VoidCallback onDismiss;
+
+  @override
+  Widget build(BuildContext context) {
+    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{
+      _AnyTapGestureRecognizer: _AnyTapGestureRecognizerFactory(onAnyTapUp: onDismiss),
+    };
+
+    return RawGestureDetector(
+      gestures: gestures,
+      behavior: HitTestBehavior.opaque,
+      semantics: _ModalBarrierSemanticsDelegate(onDismiss: onDismiss),
+      child: child,
+    );
+  }
+}
diff --git a/lib/src/widgets/navigation_toolbar.dart b/lib/src/widgets/navigation_toolbar.dart
new file mode 100644
index 0000000..7cb4880
--- /dev/null
+++ b/lib/src/widgets/navigation_toolbar.dart
@@ -0,0 +1,185 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'debug.dart';
+import 'framework.dart';
+
+/// [NavigationToolbar] is a layout helper to position 3 widgets or groups of
+/// widgets along a horizontal axis that's sensible for an application's
+/// navigation bar such as in Material Design and in iOS.
+///
+/// The [leading] and [trailing] widgets occupy the edges of the widget with
+/// reasonable size constraints while the [middle] widget occupies the remaining
+/// space in either a center aligned or start aligned fashion.
+///
+/// Either directly use the themed app bars such as the Material [AppBar] or
+/// the iOS [CupertinoNavigationBar] or wrap this widget with more theming
+/// specifications for your own custom app bar.
+class NavigationToolbar extends StatelessWidget {
+
+  /// Creates a widget that lays out its children in a manner suitable for a
+  /// toolbar.
+  const NavigationToolbar({
+    Key? key,
+    this.leading,
+    this.middle,
+    this.trailing,
+    this.centerMiddle = true,
+    this.middleSpacing = kMiddleSpacing,
+  }) : assert(centerMiddle != null),
+       assert(middleSpacing != null),
+       super(key: key);
+
+  /// The default spacing around the [middle] widget in dp.
+  static const double kMiddleSpacing = 16.0;
+
+  /// Widget to place at the start of the horizontal toolbar.
+  final Widget? leading;
+
+  /// Widget to place in the middle of the horizontal toolbar, occupying
+  /// as much remaining space as possible.
+  final Widget? middle;
+
+  /// Widget to place at the end of the horizontal toolbar.
+  final Widget? trailing;
+
+  /// Whether to align the [middle] widget to the center of this widget or
+  /// next to the [leading] widget when false.
+  final bool centerMiddle;
+
+  /// The spacing around the [middle] widget on horizontal axis.
+  ///
+  /// Defaults to [kMiddleSpacing].
+  final double middleSpacing;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasDirectionality(context));
+    final TextDirection textDirection = Directionality.of(context);
+    return CustomMultiChildLayout(
+      delegate: _ToolbarLayout(
+        centerMiddle: centerMiddle,
+        middleSpacing: middleSpacing,
+        textDirection: textDirection,
+      ),
+      children: <Widget>[
+        if (leading != null) LayoutId(id: _ToolbarSlot.leading, child: leading!),
+        if (middle != null) LayoutId(id: _ToolbarSlot.middle, child: middle!),
+        if (trailing != null) LayoutId(id: _ToolbarSlot.trailing, child: trailing!),
+      ],
+    );
+  }
+}
+
+enum _ToolbarSlot {
+  leading,
+  middle,
+  trailing,
+}
+
+class _ToolbarLayout extends MultiChildLayoutDelegate {
+  _ToolbarLayout({
+    required this.centerMiddle,
+    required this.middleSpacing,
+    required this.textDirection,
+  }) : assert(middleSpacing != null),
+       assert(textDirection != null);
+
+  // If false the middle widget should be start-justified within the space
+  // between the leading and trailing widgets.
+  // If true the middle widget is centered within the toolbar (not within the horizontal
+  // space between the leading and trailing widgets).
+  final bool centerMiddle;
+
+  /// The spacing around middle widget on horizontal axis.
+  final double middleSpacing;
+
+  final TextDirection textDirection;
+
+  @override
+  void performLayout(Size size) {
+    double leadingWidth = 0.0;
+    double trailingWidth = 0.0;
+
+    if (hasChild(_ToolbarSlot.leading)) {
+      final BoxConstraints constraints = BoxConstraints(
+        minWidth: 0.0,
+        maxWidth: size.width / 3.0, // The leading widget shouldn't take up more than 1/3 of the space.
+        minHeight: size.height, // The height should be exactly the height of the bar.
+        maxHeight: size.height,
+      );
+      leadingWidth = layoutChild(_ToolbarSlot.leading, constraints).width;
+      final double leadingX;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          leadingX = size.width - leadingWidth;
+          break;
+        case TextDirection.ltr:
+          leadingX = 0.0;
+          break;
+      }
+      positionChild(_ToolbarSlot.leading, Offset(leadingX, 0.0));
+    }
+
+    if (hasChild(_ToolbarSlot.trailing)) {
+      final BoxConstraints constraints = BoxConstraints.loose(size);
+      final Size trailingSize = layoutChild(_ToolbarSlot.trailing, constraints);
+      final double trailingX;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          trailingX = 0.0;
+          break;
+        case TextDirection.ltr:
+          trailingX = size.width - trailingSize.width;
+          break;
+      }
+      final double trailingY = (size.height - trailingSize.height) / 2.0;
+      trailingWidth = trailingSize.width;
+      positionChild(_ToolbarSlot.trailing, Offset(trailingX, trailingY));
+    }
+
+    if (hasChild(_ToolbarSlot.middle)) {
+      final double maxWidth = math.max(size.width - leadingWidth - trailingWidth - middleSpacing * 2.0, 0.0);
+      final BoxConstraints constraints = BoxConstraints.loose(size).copyWith(maxWidth: maxWidth);
+      final Size middleSize = layoutChild(_ToolbarSlot.middle, constraints);
+
+      final double middleStartMargin = leadingWidth + middleSpacing;
+      double middleStart = middleStartMargin;
+      final double middleY = (size.height - middleSize.height) / 2.0;
+      // If the centered middle will not fit between the leading and trailing
+      // widgets, then align its left or right edge with the adjacent boundary.
+      if (centerMiddle) {
+        middleStart = (size.width - middleSize.width) / 2.0;
+        if (middleStart + middleSize.width > size.width - trailingWidth)
+          middleStart = size.width - trailingWidth - middleSize.width;
+        else if (middleStart < middleStartMargin)
+          middleStart = middleStartMargin;
+      }
+
+      final double middleX;
+      switch (textDirection) {
+        case TextDirection.rtl:
+          middleX = size.width - middleSize.width - middleStart;
+          break;
+        case TextDirection.ltr:
+          middleX = middleStart;
+          break;
+      }
+
+      positionChild(_ToolbarSlot.middle, Offset(middleX, middleY));
+    }
+  }
+
+  @override
+  bool shouldRelayout(_ToolbarLayout oldDelegate) {
+    return oldDelegate.centerMiddle != centerMiddle
+        || oldDelegate.middleSpacing != middleSpacing
+        || oldDelegate.textDirection != textDirection;
+  }
+}
diff --git a/lib/src/widgets/navigator.dart b/lib/src/widgets/navigator.dart
new file mode 100644
index 0000000..c92bed3
--- /dev/null
+++ b/lib/src/widgets/navigator.dart
@@ -0,0 +1,5812 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:convert';
+import 'dart:developer' as developer;
+import 'package:flute/ui.dart' as ui;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/services.dart';
+
+import 'basic.dart';
+import 'binding.dart';
+import 'focus_manager.dart';
+import 'focus_scope.dart';
+import 'framework.dart';
+import 'heroes.dart';
+import 'overlay.dart';
+import 'restoration.dart';
+import 'restoration_properties.dart';
+import 'routes.dart';
+import 'ticker_provider.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// class MyPage extends Placeholder { MyPage({String title}); }
+// class MyHomePage extends Placeholder { }
+// NavigatorState navigator;
+// BuildContext context;
+
+/// Creates a route for the given route settings.
+///
+/// Used by [Navigator.onGenerateRoute].
+///
+/// See also:
+///
+///  * [Navigator], which is where all the [Route]s end up.
+typedef RouteFactory = Route<dynamic>? Function(RouteSettings settings);
+
+/// Creates a series of one or more routes.
+///
+/// Used by [Navigator.onGenerateInitialRoutes].
+typedef RouteListFactory = List<Route<dynamic>> Function(NavigatorState navigator, String initialRoute);
+
+/// Creates a [Route] that is to be added to a [Navigator].
+///
+/// The route can be configured with the provided `arguments`. The provided
+/// `context` is the `BuildContext` of the [Navigator] to which the route is
+/// added.
+///
+/// Used by the restorable methods of the [Navigator] that add anonymous routes
+/// (e.g. [NavigatorState.restorablePush]). For this use case, the
+/// [RestorableRouteBuilder] must be static function as the [Navigator] will
+/// call it again during state restoration to re-create the route.
+typedef RestorableRouteBuilder<T> = Route<T> Function(BuildContext context, Object? arguments);
+
+/// Signature for the [Navigator.popUntil] predicate argument.
+typedef RoutePredicate = bool Function(Route<dynamic> route);
+
+/// Signature for a callback that verifies that it's OK to call [Navigator.pop].
+///
+/// Used by [Form.onWillPop], [ModalRoute.addScopedWillPopCallback],
+/// [ModalRoute.removeScopedWillPopCallback], and [WillPopScope].
+typedef WillPopCallback = Future<bool> Function();
+
+/// Signature for the [Navigator.onPopPage] callback.
+///
+/// This callback must call [Route.didPop] on the specified route and must
+/// properly update the pages list the next time it is passed into
+/// [Navigator.pages] so that it no longer includes the corresponding [Page].
+/// (Otherwise, the page will be interpreted as a new page to show when the
+/// [Navigator.pages] list is next updated.)
+typedef PopPageCallback = bool Function(Route<dynamic> route, dynamic result);
+
+/// Indicates whether the current route should be popped.
+///
+/// Used as the return value for [Route.willPop].
+///
+/// See also:
+///
+///  * [WillPopScope], a widget that hooks into the route's [Route.willPop]
+///    mechanism.
+enum RoutePopDisposition {
+  /// Pop the route.
+  ///
+  /// If [Route.willPop] returns [pop] then the back button will actually pop
+  /// the current route.
+  pop,
+
+  /// Do not pop the route.
+  ///
+  /// If [Route.willPop] returns [doNotPop] then the back button will be ignored.
+  doNotPop,
+
+  /// Delegate this to the next level of navigation.
+  ///
+  /// If [Route.willPop] returns [bubble] then the back button will be handled
+  /// by the [SystemNavigator], which will usually close the application.
+  bubble,
+}
+
+/// An abstraction for an entry managed by a [Navigator].
+///
+/// This class defines an abstract interface between the navigator and the
+/// "routes" that are pushed on and popped off the navigator. Most routes have
+/// visual affordances, which they place in the navigators [Overlay] using one
+/// or more [OverlayEntry] objects.
+///
+/// See [Navigator] for more explanation of how to use a [Route] with
+/// navigation, including code examples.
+///
+/// See [MaterialPageRoute] for a route that replaces the entire screen with a
+/// platform-adaptive transition.
+///
+/// A route can belong to a page if the [settings] are a subclass of [Page]. A
+/// page-based route, as opposed to a pageless route, is created from
+/// [Page.createRoute] during [Navigator.pages] updates. The page associated
+/// with this route may change during the lifetime of the route. If the
+/// [Navigator] updates the page of this route, it calls [changedInternalState]
+/// to notify the route that the page has been updated.
+///
+/// The type argument `T` is the route's return type, as used by
+/// [currentResult], [popped], and [didPop]. The type `void` may be used if the
+/// route does not return a value.
+abstract class Route<T> {
+  /// Initialize the [Route].
+  ///
+  /// If the [settings] are not provided, an empty [RouteSettings] object is
+  /// used instead.
+  Route({ RouteSettings? settings }) : _settings = settings ?? const RouteSettings();
+
+  /// The navigator that the route is in, if any.
+  NavigatorState? get navigator => _navigator;
+  NavigatorState? _navigator;
+
+  /// The settings for this route.
+  ///
+  /// See [RouteSettings] for details.
+  ///
+  /// The settings can change during the route's lifetime. If the settings
+  /// change, the route's overlays will be marked dirty (see
+  /// [changedInternalState]).
+  ///
+  /// If the route is created from a [Page] in the [Navigator.pages] list, then
+  /// this will be a [Page] subclass, and it will be updated each time its
+  /// corresponding [Page] in the [Navigator.pages] has changed. Once the
+  /// [Route] is removed from the history, this value stops updating (and
+  /// remains with its last value).
+  RouteSettings get settings => _settings;
+  RouteSettings _settings;
+
+  /// The restoration scope ID to be used for the [RestorationScope] surrounding
+  /// this route.
+  ///
+  /// The restoration scope ID is null if restoration is currently disabled
+  /// for this route.
+  ///
+  /// If the restoration scope ID changes (e.g. because restoration is enabled
+  /// or disabled) during the life of the route, the [ValueListenable] notifies
+  /// its listeners. As an example, the ID changes to null while the route is
+  /// transitioning off screen, which triggers a notification on this field. At
+  /// that point, the route is considered as no longer present for restoration
+  /// purposes and its state will not be restored.
+  ValueListenable<String?> get restorationScopeId => _restorationScopeId;
+  final ValueNotifier<String?> _restorationScopeId = ValueNotifier<String?>(null);
+
+  void _updateSettings(RouteSettings newSettings) {
+    assert(newSettings != null);
+    if (_settings != newSettings) {
+      _settings = newSettings;
+      changedInternalState();
+    }
+  }
+
+  void _updateRestorationId(String? restorationId) {
+    _restorationScopeId.value = restorationId;
+  }
+
+  /// The overlay entries of this route.
+  ///
+  /// These are typically populated by [install]. The [Navigator] is in charge
+  /// of adding them to and removing them from the [Overlay].
+  ///
+  /// There must be at least one entry in this list after [install] has been
+  /// invoked.
+  ///
+  /// The [Navigator] will take care of keeping the entries together if the
+  /// route is moved in the history.
+  List<OverlayEntry> get overlayEntries => const <OverlayEntry>[];
+
+  /// Called when the route is inserted into the navigator.
+  ///
+  /// Uses this to populate [overlayEntries]. There must be at least one entry in
+  /// this list after [install] has been invoked. The [Navigator] will be in charge
+  /// to add them to the [Overlay] or remove them from it by calling
+  /// [OverlayEntry.remove].
+  @protected
+  @mustCallSuper
+  void install() { }
+
+  /// Called after [install] when the route is pushed onto the navigator.
+  ///
+  /// The returned value resolves when the push transition is complete.
+  ///
+  /// The [didAdd] method will be called instead of [didPush] when the route
+  /// immediately appears on screen without any push transition.
+  ///
+  /// The [didChangeNext] and [didChangePrevious] methods are typically called
+  /// immediately after this method is called.
+  @protected
+  @mustCallSuper
+  TickerFuture didPush() {
+    return TickerFuture.complete()..then<void>((void _) {
+      navigator?.focusScopeNode.requestFocus();
+    });
+  }
+
+  /// Called after [install] when the route is added to the navigator.
+  ///
+  /// This method is called instead of [didPush] when the route immediately
+  /// appears on screen without any push transition.
+  ///
+  /// The [didChangeNext] and [didChangePrevious] methods are typically called
+  /// immediately after this method is called.
+  @protected
+  @mustCallSuper
+  void didAdd() {
+    // This TickerFuture serves two purposes. First, we want to make sure
+    // animations triggered by other operations finish before focusing the
+    // navigator. Second, navigator.focusScopeNode might acquire more focused
+    // children in Route.install asynchronously. This TickerFuture will wait for
+    // it to finish first.
+    //
+    // The later case can be found when subclasses manage their own focus scopes.
+    // For example, ModalRoute create a focus scope in its overlay entries. The
+    // focused child can only be attached to navigator after initState which
+    // will be guarded by the asynchronous gap.
+    TickerFuture.complete().then<void>((void _) {
+      // The route can be disposed before the ticker future completes. This can
+      // happen when the navigator is under a TabView that warps from one tab to
+      // another, non-adjacent tab, with an animation. The TabView reorders its
+      // children before and after the warping completes, and that causes its
+      // children to be built and disposed within the same frame. If one of its
+      // children contains a navigator, the routes in that navigator are also
+      // added and disposed within that frame.
+      //
+      // Since the reference to the navigator will be set to null after it is
+      // disposed, we have to do a null-safe operation in case that happens
+      // within the same frame when it is added.
+      navigator?.focusScopeNode.requestFocus();
+    });
+  }
+
+  /// Called after [install] when the route replaced another in the navigator.
+  ///
+  /// The [didChangeNext] and [didChangePrevious] methods are typically called
+  /// immediately after this method is called.
+  @protected
+  @mustCallSuper
+  void didReplace(Route<dynamic>? oldRoute) { }
+
+  /// Returns whether calling [Navigator.maybePop] when this [Route] is current
+  /// ([isCurrent]) should do anything.
+  ///
+  /// [Navigator.maybePop] is usually used instead of [Navigator.pop] to handle
+  /// the system back button.
+  ///
+  /// By default, if a [Route] is the first route in the history (i.e., if
+  /// [isFirst]), it reports that pops should be bubbled
+  /// ([RoutePopDisposition.bubble]). This behavior prevents the user from
+  /// popping the first route off the history and being stranded at a blank
+  /// screen; instead, the larger scope is popped (e.g. the application quits,
+  /// so that the user returns to the previous application).
+  ///
+  /// In other cases, the default behavior is to accept the pop
+  /// ([RoutePopDisposition.pop]).
+  ///
+  /// The third possible value is [RoutePopDisposition.doNotPop], which causes
+  /// the pop request to be ignored entirely.
+  ///
+  /// See also:
+  ///
+  ///  * [Form], which provides a [Form.onWillPop] callback that uses this
+  ///    mechanism.
+  ///  * [WillPopScope], another widget that provides a way to intercept the
+  ///    back button.
+  Future<RoutePopDisposition> willPop() async {
+    return isFirst ? RoutePopDisposition.bubble : RoutePopDisposition.pop;
+  }
+
+  /// Whether calling [didPop] would return false.
+  bool get willHandlePopInternally => false;
+
+  /// When this route is popped (see [Navigator.pop]) if the result isn't
+  /// specified or if it's null, this value will be used instead.
+  ///
+  /// This fallback is implemented by [didComplete]. This value is used if the
+  /// argument to that method is null.
+  T? get currentResult => null;
+
+  /// A future that completes when this route is popped off the navigator.
+  ///
+  /// The future completes with the value given to [Navigator.pop], if any, or
+  /// else the value of [currentResult]. See [didComplete] for more discussion
+  /// on this topic.
+  Future<T?> get popped => _popCompleter.future;
+  final Completer<T?> _popCompleter = Completer<T?>();
+
+  /// A request was made to pop this route. If the route can handle it
+  /// internally (e.g. because it has its own stack of internal state) then
+  /// return false, otherwise return true (by returning the value of calling
+  /// `super.didPop`). Returning false will prevent the default behavior of
+  /// [NavigatorState.pop].
+  ///
+  /// When this function returns true, the navigator removes this route from
+  /// the history but does not yet call [dispose]. Instead, it is the route's
+  /// responsibility to call [NavigatorState.finalizeRoute], which will in turn
+  /// call [dispose] on the route. This sequence lets the route perform an
+  /// exit animation (or some other visual effect) after being popped but prior
+  /// to being disposed.
+  ///
+  /// This method should call [didComplete] to resolve the [popped] future (and
+  /// this is all that the default implementation does); routes should not wait
+  /// for their exit animation to complete before doing so.
+  ///
+  /// See [popped], [didComplete], and [currentResult] for a discussion of the
+  /// `result` argument.
+  @mustCallSuper
+  bool didPop(T? result) {
+    didComplete(result);
+    return true;
+  }
+
+  /// The route was popped or is otherwise being removed somewhat gracefully.
+  ///
+  /// This is called by [didPop] and in response to
+  /// [NavigatorState.pushReplacement]. If [didPop] was not called, then the
+  /// [NavigatorState.finalizeRoute] method must be called immediately, and no exit
+  /// animation will run.
+  ///
+  /// The [popped] future is completed by this method. The `result` argument
+  /// specifies the value that this future is completed with, unless it is null,
+  /// in which case [currentResult] is used instead.
+  ///
+  /// This should be called before the pop animation, if any, takes place,
+  /// though in some cases the animation may be driven by the user before the
+  /// route is committed to being popped; this can in particular happen with the
+  /// iOS-style back gesture. See [NavigatorState.didStartUserGesture].
+  @protected
+  @mustCallSuper
+  void didComplete(T? result) {
+    _popCompleter.complete(result ?? currentResult);
+  }
+
+  /// The given route, which was above this one, has been popped off the
+  /// navigator.
+  ///
+  /// This route is now the current route ([isCurrent] is now true), and there
+  /// is no next route.
+  @protected
+  @mustCallSuper
+  void didPopNext(Route<dynamic> nextRoute) { }
+
+  /// This route's next route has changed to the given new route.
+  ///
+  /// This is called on a route whenever the next route changes for any reason,
+  /// so long as it is in the history, including when a route is first added to
+  /// a [Navigator] (e.g. by [Navigator.push]), except for cases when
+  /// [didPopNext] would be called.
+  ///
+  /// The `nextRoute` argument will be null if there's no new next route (i.e.
+  /// if [isCurrent] is true).
+  @protected
+  @mustCallSuper
+  void didChangeNext(Route<dynamic>? nextRoute) { }
+
+  /// This route's previous route has changed to the given new route.
+  ///
+  /// This is called on a route whenever the previous route changes for any
+  /// reason, so long as it is in the history, except for immediately after the
+  /// route itself has been pushed (in which case [didPush] or [didReplace] will
+  /// be called instead).
+  ///
+  /// The `previousRoute` argument will be null if there's no previous route
+  /// (i.e. if [isFirst] is true).
+  @protected
+  @mustCallSuper
+  void didChangePrevious(Route<dynamic>? previousRoute) { }
+
+  /// Called whenever the internal state of the route has changed.
+  ///
+  /// This should be called whenever [willHandlePopInternally], [didPop],
+  /// [ModalRoute.offstage], or other internal state of the route changes value.
+  /// It is used by [ModalRoute], for example, to report the new information via
+  /// its inherited widget to any children of the route.
+  ///
+  /// See also:
+  ///
+  ///  * [changedExternalState], which is called when the [Navigator] rebuilds.
+  @protected
+  @mustCallSuper
+  void changedInternalState() { }
+
+  /// Called whenever the [Navigator] has its widget rebuilt, to indicate that
+  /// the route may wish to rebuild as well.
+  ///
+  /// This is called by the [Navigator] whenever the [NavigatorState]'s
+  /// [State.widget] changes, for example because the [MaterialApp] has been rebuilt.
+  /// This ensures that routes that directly refer to the state of the widget
+  /// that built the [MaterialApp] will be notified when that widget rebuilds,
+  /// since it would otherwise be difficult to notify the routes that state they
+  /// depend on may have changed.
+  ///
+  /// See also:
+  ///
+  ///  * [changedInternalState], the equivalent but for changes to the internal
+  ///    state of the route.
+  @protected
+  @mustCallSuper
+  void changedExternalState() { }
+
+  /// Discards any resources used by the object.
+  ///
+  /// This method should not remove its [overlayEntries] from the [Overlay]. The
+  /// object's owner is in charge of doing that.
+  ///
+  /// After this is called, the object is not in a usable state and should be
+  /// discarded.
+  ///
+  /// This method should only be called by the object's owner; typically the
+  /// [Navigator] owns a route and so will call this method when the route is
+  /// removed, after which the route is no longer referenced by the navigator.
+  @mustCallSuper
+  @protected
+  void dispose() {
+    _navigator = null;
+  }
+
+  /// Whether this route is the top-most route on the navigator.
+  ///
+  /// If this is true, then [isActive] is also true.
+  bool get isCurrent {
+    if (_navigator == null)
+      return false;
+    final _RouteEntry? currentRouteEntry = _navigator!._history.cast<_RouteEntry?>().lastWhere(
+      (_RouteEntry? e) => e != null && _RouteEntry.isPresentPredicate(e),
+      orElse: () => null,
+    );
+    if (currentRouteEntry == null)
+      return false;
+    return currentRouteEntry.route == this;
+  }
+
+  /// Whether this route is the bottom-most route on the navigator.
+  ///
+  /// If this is true, then [Navigator.canPop] will return false if this route's
+  /// [willHandlePopInternally] returns false.
+  ///
+  /// If [isFirst] and [isCurrent] are both true then this is the only route on
+  /// the navigator (and [isActive] will also be true).
+  bool get isFirst {
+    if (_navigator == null)
+      return false;
+    final _RouteEntry? currentRouteEntry = _navigator!._history.cast<_RouteEntry?>().firstWhere(
+      (_RouteEntry? e) => e != null && _RouteEntry.isPresentPredicate(e),
+      orElse: () => null,
+    );
+    if (currentRouteEntry == null)
+      return false;
+    return currentRouteEntry.route == this;
+  }
+
+  /// Whether this route is on the navigator.
+  ///
+  /// If the route is not only active, but also the current route (the top-most
+  /// route), then [isCurrent] will also be true. If it is the first route (the
+  /// bottom-most route), then [isFirst] will also be true.
+  ///
+  /// If a higher route is entirely opaque, then the route will be active but not
+  /// rendered. It is even possible for the route to be active but for the stateful
+  /// widgets within the route to not be instantiated. See [ModalRoute.maintainState].
+  bool get isActive {
+    if (_navigator == null)
+      return false;
+    return _navigator!._history.cast<_RouteEntry?>().firstWhere(
+      (_RouteEntry? e) => e != null && _RouteEntry.isRoutePredicate(this)(e),
+      orElse: () => null,
+    )?.isPresent == true;
+  }
+}
+
+/// Data that might be useful in constructing a [Route].
+@immutable
+class RouteSettings {
+  /// Creates data used to construct routes.
+  const RouteSettings({
+    this.name,
+    this.arguments,
+  });
+
+  /// Creates a copy of this route settings object with the given fields
+  /// replaced with the new values.
+  RouteSettings copyWith({
+    String? name,
+    Object? arguments,
+  }) {
+    return RouteSettings(
+      name: name ?? this.name,
+      arguments: arguments ?? this.arguments,
+    );
+  }
+
+  /// The name of the route (e.g., "/settings").
+  ///
+  /// If null, the route is anonymous.
+  final String? name;
+
+  /// The arguments passed to this route.
+  ///
+  /// May be used when building the route, e.g. in [Navigator.onGenerateRoute].
+  final Object? arguments;
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'RouteSettings')}("$name", $arguments)';
+}
+
+/// Describes the configuration of a [Route].
+///
+/// The type argument `T` is the corresponding [Route]'s return type, as
+/// used by [Route.currentResult], [Route.popped], and [Route.didPop].
+///
+/// See also:
+///
+///  * [Navigator.pages], which accepts a list of [Page]s and updates its routes
+///    history.
+abstract class Page<T> extends RouteSettings {
+  /// Creates a page and initializes [key] for subclasses.
+  ///
+  /// The [arguments] argument must not be null.
+  const Page({
+    this.key,
+    String? name,
+    Object? arguments,
+    this.restorationId,
+  }) : super(name: name, arguments: arguments);
+
+  /// The key associated with this page.
+  ///
+  /// This key will be used for comparing pages in [canUpdate].
+  final LocalKey? key;
+
+  /// Restoration ID to save and restore the state of the [Route] configured by
+  /// this page.
+  ///
+  /// If no restoration ID is provided, the [Route] will not restore its state.
+  ///
+  /// See also:
+  ///
+  ///  * [RestorationManager], which explains how state restoration works in
+  ///    Flutter.
+  final String? restorationId;
+
+  /// Whether this page can be updated with the [other] page.
+  ///
+  /// Two pages are consider updatable if they have same the [runtimeType] and
+  /// [key].
+  bool canUpdate(Page<dynamic> other) {
+    return other.runtimeType == runtimeType &&
+           other.key == key;
+  }
+
+  /// Creates the [Route] that corresponds to this page.
+  ///
+  /// The created [Route] must have its [Route.settings] property set to this [Page].
+  @factory
+  Route<T> createRoute(BuildContext context);
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'Page')}("$name", $key, $arguments)';
+}
+
+/// An interface for observing the behavior of a [Navigator].
+class NavigatorObserver {
+  /// The navigator that the observer is observing, if any.
+  NavigatorState? get navigator => _navigator;
+  NavigatorState? _navigator;
+
+  /// The [Navigator] pushed `route`.
+  ///
+  /// The route immediately below that one, and thus the previously active
+  /// route, is `previousRoute`.
+  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) { }
+
+  /// The [Navigator] popped `route`.
+  ///
+  /// The route immediately below that one, and thus the newly active
+  /// route, is `previousRoute`.
+  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { }
+
+  /// The [Navigator] removed `route`.
+  ///
+  /// If only one route is being removed, then the route immediately below
+  /// that one, if any, is `previousRoute`.
+  ///
+  /// If multiple routes are being removed, then the route below the
+  /// bottommost route being removed, if any, is `previousRoute`, and this
+  /// method will be called once for each removed route, from the topmost route
+  /// to the bottommost route.
+  void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) { }
+
+  /// The [Navigator] replaced `oldRoute` with `newRoute`.
+  void didReplace({ Route<dynamic>? newRoute, Route<dynamic>? oldRoute }) { }
+
+  /// The [Navigator]'s routes are being moved by a user gesture.
+  ///
+  /// For example, this is called when an iOS back gesture starts, and is used
+  /// to disabled hero animations during such interactions.
+  void didStartUserGesture(Route<dynamic> route, Route<dynamic>? previousRoute) { }
+
+  /// User gesture is no longer controlling the [Navigator].
+  ///
+  /// Paired with an earlier call to [didStartUserGesture].
+  void didStopUserGesture() { }
+}
+
+/// An inherited widget to host a hero controller.
+///
+/// The hosted hero controller will be picked up by the navigator in the
+/// [child] subtree. Once a navigator picks up this controller, the navigator
+/// will bar any navigator below its subtree from receiving this controller.
+///
+/// The hero controller inside the [HeroControllerScope] can only subscribe to
+/// one navigator at a time. An assertion will be thrown if the hero controller
+/// subscribes to more than one navigators. This can happen when there are
+/// multiple navigators under the same [HeroControllerScope] in parallel.
+class HeroControllerScope extends InheritedWidget {
+  /// Creates a widget to host the input [controller].
+  const HeroControllerScope({
+    Key? key,
+    required HeroController this.controller,
+    required Widget child,
+  }) : assert(controller != null),
+       super(key: key, child: child);
+
+  /// Creates a widget to prevent the subtree from receiving the hero controller
+  /// above.
+  const HeroControllerScope.none({
+    Key? key,
+    required Widget child,
+  }) : controller = null,
+       super(key: key, child: child);
+
+  /// The hero controller that is hosted inside this widget.
+  final HeroController? controller;
+
+  /// Retrieves the [HeroController] from the closest [HeroControllerScope]
+  /// ancestor.
+  static HeroController? of(BuildContext context) {
+    final HeroControllerScope? host = context.dependOnInheritedWidgetOfExactType<HeroControllerScope>();
+    return host?.controller;
+  }
+
+  @override
+  bool updateShouldNotify(HeroControllerScope oldWidget) {
+    return oldWidget.controller != controller;
+  }
+}
+
+/// A [Route] wrapper interface that can be staged for [TransitionDelegate] to
+/// decide how its underlying [Route] should transition on or off screen.
+abstract class RouteTransitionRecord {
+  /// Retrieves the wrapped [Route].
+  Route<dynamic> get route;
+
+  /// Whether this route is waiting for the decision on how to enter the screen.
+  ///
+  /// If this property is true, this route requires an explicit decision on how
+  /// to transition into the screen. Such a decision should be made in the
+  /// [TransitionDelegate.resolve].
+  bool get isWaitingForEnteringDecision;
+
+  /// Whether this route is waiting for the decision on how to exit the screen.
+  ///
+  /// If this property is true, this route requires an explicit decision on how
+  /// to transition off the screen. Such a decision should be made in the
+  /// [TransitionDelegate.resolve].
+  bool get isWaitingForExitingDecision;
+
+  /// Marks the [route] to be pushed with transition.
+  ///
+  /// During [TransitionDelegate.resolve], this can be called on an entering
+  /// route (where [RouteTransitionRecord.isWaitingForEnteringDecision] is true) in indicate that the
+  /// route should be pushed onto the [Navigator] with an animated transition.
+  void markForPush();
+
+  /// Marks the [route] to be added without transition.
+  ///
+  /// During [TransitionDelegate.resolve], this can be called on an entering
+  /// route (where [RouteTransitionRecord.isWaitingForEnteringDecision] is true) in indicate that the
+  /// route should be added onto the [Navigator] without an animated transition.
+  void markForAdd();
+
+  /// Marks the [route] to be popped with transition.
+  ///
+  /// During [TransitionDelegate.resolve], this can be called on an exiting
+  /// route to indicate that the route should be popped off the [Navigator] with
+  /// an animated transition.
+  void markForPop([dynamic result]);
+
+  /// Marks the [route] to be completed without transition.
+  ///
+  /// During [TransitionDelegate.resolve], this can be called on an exiting
+  /// route to indicate that the route should be completed with the provided
+  /// result and removed from the [Navigator] without an animated transition.
+  void markForComplete([dynamic result]);
+
+  /// Marks the [route] to be removed without transition.
+  ///
+  /// During [TransitionDelegate.resolve], this can be called on an exiting
+  /// route to indicate that the route should be removed from the [Navigator]
+  /// without completing and without an animated transition.
+  void markForRemove();
+}
+
+/// The delegate that decides how pages added and removed from [Navigator.pages]
+/// transition in or out of the screen.
+///
+/// This abstract class implements the API to be called by [Navigator] when it
+/// requires explicit decisions on how the routes transition on or off the screen.
+///
+/// To make route transition decisions, subclass must implement [resolve].
+///
+/// {@tool sample --template=freeform_no_null_safety}
+/// The following example demonstrates how to implement a subclass that always
+/// removes or adds routes without animated transitions and puts the removed
+/// routes at the top of the list.
+///
+/// ```dart imports
+/// import 'package:flute/widgets.dart';
+/// ```
+///
+/// ```dart
+/// class NoAnimationTransitionDelegate extends TransitionDelegate<void> {
+///   @override
+///   Iterable<RouteTransitionRecord> resolve({
+///     List<RouteTransitionRecord> newPageRouteHistory,
+///     Map<RouteTransitionRecord, RouteTransitionRecord> locationToExitingPageRoute,
+///     Map<RouteTransitionRecord, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
+///   }) {
+///     final List<RouteTransitionRecord> results = <RouteTransitionRecord>[];
+///
+///     for (final RouteTransitionRecord pageRoute in newPageRouteHistory) {
+///       if (pageRoute.isWaitingForEnteringDecision) {
+///         pageRoute.markForAdd();
+///       }
+///       results.add(pageRoute);
+///
+///     }
+///     for (final RouteTransitionRecord exitingPageRoute in locationToExitingPageRoute.values) {
+///       if (exitingPageRoute.isWaitingForExitingDecision) {
+///        exitingPageRoute.markForRemove();
+///        final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute];
+///        if (pagelessRoutes != null) {
+///          for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) {
+///             pagelessRoute.markForRemove();
+///           }
+///        }
+///       }
+///       results.add(exitingPageRoute);
+///
+///     }
+///     return results;
+///   }
+/// }
+///
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Navigator.transitionDelegate], which uses this class to make route
+///    transition decisions.
+///  * [DefaultTransitionDelegate], which implements the default way to decide
+///    how routes transition in or out of the screen.
+abstract class TransitionDelegate<T> {
+  /// Creates a delegate and enables subclass to create a constant class.
+  const TransitionDelegate();
+
+  Iterable<RouteTransitionRecord> _transition({
+    required List<RouteTransitionRecord> newPageRouteHistory,
+    required Map<RouteTransitionRecord?, RouteTransitionRecord> locationToExitingPageRoute,
+    required Map<RouteTransitionRecord?, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
+  }) {
+    final Iterable<RouteTransitionRecord> results = resolve(
+      newPageRouteHistory: newPageRouteHistory,
+      locationToExitingPageRoute: locationToExitingPageRoute,
+      pageRouteToPagelessRoutes: pageRouteToPagelessRoutes,
+    );
+    // Verifies the integrity after the decisions have been made.
+    //
+    // Here are the rules:
+    // - All the entering routes in newPageRouteHistory must either be pushed or
+    //   added.
+    // - All the exiting routes in locationToExitingPageRoute must either be
+    //   popped, completed or removed.
+    // - All the pageless routes that belong to exiting routes must either be
+    //   popped, completed or removed.
+    // - All the entering routes in the result must preserve the same order as
+    //   the entering routes in newPageRouteHistory, and the result must contain
+    //   all exiting routes.
+    //     ex:
+    //
+    //     newPageRouteHistory = [A, B, C]
+    //
+    //     locationToExitingPageRoute = {A -> D, C -> E}
+    //
+    //     results = [A, B ,C ,D ,E] is valid
+    //     results = [D, A, B ,C ,E] is also valid because exiting route can be
+    //     inserted in any place
+    //
+    //     results = [B, A, C ,D ,E] is invalid because B must be after A.
+    //     results = [A, B, C ,E] is invalid because results must include D.
+    assert(() {
+      final List<RouteTransitionRecord> resultsToVerify = results.toList(growable: false);
+      final Set<RouteTransitionRecord> exitingPageRoutes = locationToExitingPageRoute.values.toSet();
+      // Firstly, verifies all exiting routes have been marked.
+      for (final RouteTransitionRecord exitingPageRoute in exitingPageRoutes) {
+        assert(!exitingPageRoute.isWaitingForExitingDecision);
+        if (pageRouteToPagelessRoutes.containsKey(exitingPageRoute)) {
+          for (final RouteTransitionRecord pagelessRoute in pageRouteToPagelessRoutes[exitingPageRoute]!) {
+            assert(!pagelessRoute.isWaitingForExitingDecision);
+          }
+        }
+      }
+      // Secondly, verifies the order of results matches the newPageRouteHistory
+      // and contains all the exiting routes.
+      int indexOfNextRouteInNewHistory = 0;
+
+      for (final _RouteEntry routeEntry in resultsToVerify.cast<_RouteEntry>()) {
+        assert(routeEntry != null);
+        assert(!routeEntry.isWaitingForEnteringDecision && !routeEntry.isWaitingForExitingDecision);
+        if (
+          indexOfNextRouteInNewHistory >= newPageRouteHistory.length ||
+          routeEntry != newPageRouteHistory[indexOfNextRouteInNewHistory]
+        ) {
+          assert(exitingPageRoutes.contains(routeEntry));
+          exitingPageRoutes.remove(routeEntry);
+        } else {
+          indexOfNextRouteInNewHistory += 1;
+        }
+      }
+
+      assert(
+        indexOfNextRouteInNewHistory == newPageRouteHistory.length &&
+        exitingPageRoutes.isEmpty,
+        'The merged result from the $runtimeType.resolve does not include all '
+        'required routes. Do you remember to merge all exiting routes?'
+      );
+      return true;
+    }());
+
+    return results;
+  }
+
+  /// A method that will be called by the [Navigator] to decide how routes
+  /// transition in or out of the screen when [Navigator.pages] is updated.
+  ///
+  /// The `newPageRouteHistory` list contains all page-based routes in the order
+  /// that will be on the [Navigator]'s history stack after this update
+  /// completes. If a route in `newPageRouteHistory` has its
+  /// [RouteTransitionRecord.isWaitingForEnteringDecision] set to true, this
+  /// route requires explicit decision on how it should transition onto the
+  /// Navigator. To make a decision, call [RouteTransitionRecord.markForPush] or
+  /// [RouteTransitionRecord.markForAdd].
+  ///
+  /// The `locationToExitingPageRoute` contains the pages-based routes that
+  /// are removed from the routes history after page update. This map records
+  /// page-based routes to be removed with the location of the route in the
+  /// original route history before the update. The keys are the locations
+  /// represented by the page-based routes that are directly below the removed
+  /// routes, and the value are the page-based routes to be removed. The
+  /// location is null if the route to be removed is the bottom most route. If
+  /// a route in `locationToExitingPageRoute` has its
+  /// [RouteTransitionRecord.isWaitingForExitingDecision] set to true, this
+  /// route requires explicit decision on how it should transition off the
+  /// Navigator. To make a decision for a removed route, call
+  /// [RouteTransitionRecord.markForPop],
+  /// [RouteTransitionRecord.markForComplete] or
+  /// [RouteTransitionRecord.markForRemove]. It is possible that decisions are
+  /// not required for routes in the `locationToExitingPageRoute`. This can
+  /// happen if the routes have already been popped in earlier page updates and
+  /// are still waiting for popping animations to finish. In such case, those
+  /// routes are still included in the `locationToExitingPageRoute` with their
+  /// [RouteTransitionRecord.isWaitingForExitingDecision] set to false and no
+  /// decisions are required.
+  ///
+  /// The `pageRouteToPagelessRoutes` records the page-based routes and their
+  /// associated pageless routes. If a page-based route is waiting for exiting
+  /// decision, its associated pageless routes also require explicit decisions
+  /// on how to transition off the screen.
+  ///
+  /// Once all the decisions have been made, this method must merge the removed
+  /// routes (whether or not they require decisions) and the
+  /// `newPageRouteHistory` and return the merged result. The order in the
+  /// result will be the order the [Navigator] uses for updating the route
+  /// history. The return list must preserve the same order of routes in
+  /// `newPageRouteHistory`. The removed routes, however, can be inserted into
+  /// the return list freely as long as all of them are included.
+  ///
+  /// For example, consider the following case.
+  ///
+  /// newPageRouteHistory = [A, B, C]
+  ///
+  /// locationToExitingPageRoute = {A -> D, C -> E}
+  ///
+  /// The following outputs are valid.
+  ///
+  /// result = [A, B ,C ,D ,E] is valid.
+  /// result = [D, A, B ,C ,E] is also valid because exiting route can be
+  /// inserted in any place.
+  ///
+  /// The following outputs are invalid.
+  ///
+  /// result = [B, A, C ,D ,E] is invalid because B must be after A.
+  /// result = [A, B, C ,E] is invalid because results must include D.
+  ///
+  /// See also:
+  ///
+  ///  * [RouteTransitionRecord.markForPush], which makes route enter the screen
+  ///    with an animated transition.
+  ///  * [RouteTransitionRecord.markForAdd], which makes route enter the screen
+  ///    without an animated transition.
+  ///  * [RouteTransitionRecord.markForPop], which makes route exit the screen
+  ///    with an animated transition.
+  ///  * [RouteTransitionRecord.markForRemove], which does not complete the
+  ///    route and makes it exit the screen without an animated transition.
+  ///  * [RouteTransitionRecord.markForComplete], which completes the route and
+  ///    makes it exit the screen without an animated transition.
+  ///  * [DefaultTransitionDelegate.resolve], which implements the default way
+  ///    to decide how routes transition in or out of the screen.
+  Iterable<RouteTransitionRecord> resolve({
+    required List<RouteTransitionRecord> newPageRouteHistory,
+    required Map<RouteTransitionRecord?, RouteTransitionRecord> locationToExitingPageRoute,
+    required Map<RouteTransitionRecord?, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
+  });
+}
+
+/// The default implementation of [TransitionDelegate] that the [Navigator] will
+/// use if its [Navigator.transitionDelegate] is not specified.
+///
+/// This transition delegate follows two rules. Firstly, all the entering routes
+/// are placed on top of the exiting routes if they are at the same location.
+/// Secondly, the top most route will always transition with an animated transition.
+/// All the other routes below will either be completed with
+/// [Route.currentResult] or added without an animated transition.
+class DefaultTransitionDelegate<T> extends TransitionDelegate<T> {
+  /// Creates a default transition delegate.
+  const DefaultTransitionDelegate() : super();
+
+  @override
+  Iterable<RouteTransitionRecord> resolve({
+    required List<RouteTransitionRecord> newPageRouteHistory,
+    required Map<RouteTransitionRecord?, RouteTransitionRecord> locationToExitingPageRoute,
+    required Map<RouteTransitionRecord?, List<RouteTransitionRecord>> pageRouteToPagelessRoutes,
+  }) {
+    final List<RouteTransitionRecord> results = <RouteTransitionRecord>[];
+    // This method will handle the exiting route and its corresponding pageless
+    // route at this location. It will also recursively check if there is any
+    // other exiting routes above it and handle them accordingly.
+    void handleExitingRoute(RouteTransitionRecord? location, bool isLast) {
+      final RouteTransitionRecord? exitingPageRoute = locationToExitingPageRoute[location];
+      if (exitingPageRoute == null)
+        return;
+      if (exitingPageRoute.isWaitingForExitingDecision) {
+        final bool hasPagelessRoute = pageRouteToPagelessRoutes.containsKey(exitingPageRoute);
+        final bool isLastExitingPageRoute = isLast && !locationToExitingPageRoute.containsKey(exitingPageRoute);
+        if (isLastExitingPageRoute && !hasPagelessRoute) {
+          exitingPageRoute.markForPop(exitingPageRoute.route.currentResult);
+        } else {
+          exitingPageRoute.markForComplete(exitingPageRoute.route.currentResult);
+        }
+        if (hasPagelessRoute) {
+          final List<RouteTransitionRecord> pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute]!;
+          for (final RouteTransitionRecord pagelessRoute in pagelessRoutes) {
+            // It is possible that a pageless route that belongs to an exiting
+            // page-based route does not require exiting decision. This can
+            // happen if the page list is updated right after a Navigator.pop.
+            if (pagelessRoute.isWaitingForExitingDecision) {
+              if (isLastExitingPageRoute && pagelessRoute == pagelessRoutes.last) {
+                pagelessRoute.markForPop(pagelessRoute.route.currentResult);
+              } else {
+                pagelessRoute.markForComplete(pagelessRoute.route.currentResult);
+              }
+            }
+          }
+        }
+      }
+      results.add(exitingPageRoute);
+
+      // It is possible there is another exiting route above this exitingPageRoute.
+      handleExitingRoute(exitingPageRoute, isLast);
+    }
+
+    // Handles exiting route in the beginning of list.
+    handleExitingRoute(null, newPageRouteHistory.isEmpty);
+
+    for (final RouteTransitionRecord pageRoute in newPageRouteHistory) {
+      final bool isLastIteration = newPageRouteHistory.last == pageRoute;
+      if (pageRoute.isWaitingForEnteringDecision) {
+        if (!locationToExitingPageRoute.containsKey(pageRoute) && isLastIteration) {
+          pageRoute.markForPush();
+        } else {
+          pageRoute.markForAdd();
+        }
+      }
+      results.add(pageRoute);
+      handleExitingRoute(pageRoute, isLastIteration);
+    }
+    return results;
+  }
+}
+
+/// A widget that manages a set of child widgets with a stack discipline.
+///
+/// Many apps have a navigator near the top of their widget hierarchy in order
+/// to display their logical history using an [Overlay] with the most recently
+/// visited pages visually on top of the older pages. Using this pattern lets
+/// the navigator visually transition from one page to another by moving the widgets
+/// around in the overlay. Similarly, the navigator can be used to show a dialog
+/// by positioning the dialog widget above the current page.
+///
+/// ## Using the Navigator API
+///
+/// Mobile apps typically reveal their contents via full-screen elements
+/// called "screens" or "pages". In Flutter these elements are called
+/// routes and they're managed by a [Navigator] widget. The navigator
+/// manages a stack of [Route] objects and provides two ways for managing
+/// the stack, the declarative API [Navigator.pages] or imperative API
+/// [Navigator.push] and [Navigator.pop].
+///
+/// When your user interface fits this paradigm of a stack, where the user
+/// should be able to _navigate_ back to an earlier element in the stack,
+/// the use of routes and the Navigator is appropriate. On certain platforms,
+/// such as Android, the system UI will provide a back button (outside the
+/// bounds of your application) that will allow the user to navigate back
+/// to earlier routes in your application's stack. On platforms that don't
+/// have this build-in navigation mechanism, the use of an [AppBar] (typically
+/// used in the [Scaffold.appBar] property) can automatically add a back
+/// button for user navigation.
+///
+/// ## Using the Pages API
+///
+/// The [Navigator] will convert its [Navigator.pages] into a stack of [Route]s
+/// if it is provided. A change in [Navigator.pages] will trigger an update to
+/// the stack of [Route]s. The [Navigator] will update its routes to match the
+/// new configuration of its [Navigator.pages]. To use this API, one can create
+/// a [Page] subclass and defines a list of [Page]s for [Navigator.pages]. A
+/// [Navigator.onPopPage] callback is also required to properly clean up the
+/// input pages in case of a pop.
+///
+/// By Default, the [Navigator] will use [DefaultTransitionDelegate] to decide
+/// how routes transition in or out of the screen. To customize it, define a
+/// [TransitionDelegate] subclass and provide it to the
+/// [Navigator.transitionDelegate].
+///
+/// ### Displaying a full-screen route
+///
+/// Although you can create a navigator directly, it's most common to use the
+/// navigator created by the `Router` which itself is created and configured by
+/// a [WidgetsApp] or a [MaterialApp] widget. You can refer to that navigator
+/// with [Navigator.of].
+///
+/// A [MaterialApp] is the simplest way to set things up. The [MaterialApp]'s
+/// home becomes the route at the bottom of the [Navigator]'s stack. It is what
+/// you see when the app is launched.
+///
+/// ```dart
+/// void main() {
+///   runApp(MaterialApp(home: MyAppHome()));
+/// }
+/// ```
+///
+/// To push a new route on the stack you can create an instance of
+/// [MaterialPageRoute] with a builder function that creates whatever you
+/// want to appear on the screen. For example:
+///
+/// ```dart
+/// Navigator.push(context, MaterialPageRoute<void>(
+///   builder: (BuildContext context) {
+///     return Scaffold(
+///       appBar: AppBar(title: Text('My Page')),
+///       body: Center(
+///         child: TextButton(
+///           child: Text('POP'),
+///           onPressed: () {
+///             Navigator.pop(context);
+///           },
+///         ),
+///       ),
+///     );
+///   },
+/// ));
+/// ```
+///
+/// The route defines its widget with a builder function instead of a
+/// child widget because it will be built and rebuilt in different
+/// contexts depending on when it's pushed and popped.
+///
+/// As you can see, the new route can be popped, revealing the app's home
+/// page, with the Navigator's pop method:
+///
+/// ```dart
+/// Navigator.pop(context);
+/// ```
+///
+/// It usually isn't necessary to provide a widget that pops the Navigator
+/// in a route with a [Scaffold] because the Scaffold automatically adds a
+/// 'back' button to its AppBar. Pressing the back button causes
+/// [Navigator.pop] to be called. On Android, pressing the system back
+/// button does the same thing.
+///
+/// ### Using named navigator routes
+///
+/// Mobile apps often manage a large number of routes and it's often
+/// easiest to refer to them by name. Route names, by convention,
+/// use a path-like structure (for example, '/a/b/c').
+/// The app's home page route is named '/' by default.
+///
+/// The [MaterialApp] can be created
+/// with a [Map<String, WidgetBuilder>] which maps from a route's name to
+/// a builder function that will create it. The [MaterialApp] uses this
+/// map to create a value for its navigator's [onGenerateRoute] callback.
+///
+/// ```dart
+/// void main() {
+///   runApp(MaterialApp(
+///     home: MyAppHome(), // becomes the route named '/'
+///     routes: <String, WidgetBuilder> {
+///       '/a': (BuildContext context) => MyPage(title: 'page A'),
+///       '/b': (BuildContext context) => MyPage(title: 'page B'),
+///       '/c': (BuildContext context) => MyPage(title: 'page C'),
+///     },
+///   ));
+/// }
+/// ```
+///
+/// To show a route by name:
+///
+/// ```dart
+/// Navigator.pushNamed(context, '/b');
+/// ```
+///
+/// ### Routes can return a value
+///
+/// When a route is pushed to ask the user for a value, the value can be
+/// returned via the [pop] method's result parameter.
+///
+/// Methods that push a route return a [Future]. The Future resolves when the
+/// route is popped and the [Future]'s value is the [pop] method's `result`
+/// parameter.
+///
+/// For example if we wanted to ask the user to press 'OK' to confirm an
+/// operation we could `await` the result of [Navigator.push]:
+///
+/// ```dart
+/// bool value = await Navigator.push(context, MaterialPageRoute<bool>(
+///   builder: (BuildContext context) {
+///     return Center(
+///       child: GestureDetector(
+///         child: Text('OK'),
+///         onTap: () { Navigator.pop(context, true); }
+///       ),
+///     );
+///   }
+/// ));
+/// ```
+///
+/// If the user presses 'OK' then value will be true. If the user backs
+/// out of the route, for example by pressing the Scaffold's back button,
+/// the value will be null.
+///
+/// When a route is used to return a value, the route's type parameter must
+/// match the type of [pop]'s result. That's why we've used
+/// `MaterialPageRoute<bool>` instead of `MaterialPageRoute<void>` or just
+/// `MaterialPageRoute`. (If you prefer to not specify the types, though, that's
+/// fine too.)
+///
+/// ### Popup routes
+///
+/// Routes don't have to obscure the entire screen. [PopupRoute]s cover the
+/// screen with a [ModalRoute.barrierColor] that can be only partially opaque to
+/// allow the current screen to show through. Popup routes are "modal" because
+/// they block input to the widgets below.
+///
+/// There are functions which create and show popup routes. For
+/// example: [showDialog], [showMenu], and [showModalBottomSheet]. These
+/// functions return their pushed route's Future as described above.
+/// Callers can await the returned value to take an action when the
+/// route is popped, or to discover the route's value.
+///
+/// There are also widgets which create popup routes, like [PopupMenuButton] and
+/// [DropdownButton]. These widgets create internal subclasses of PopupRoute
+/// and use the Navigator's push and pop methods to show and dismiss them.
+///
+/// ### Custom routes
+///
+/// You can create your own subclass of one of the widget library route classes
+/// like [PopupRoute], [ModalRoute], or [PageRoute], to control the animated
+/// transition employed to show the route, the color and behavior of the route's
+/// modal barrier, and other aspects of the route.
+///
+/// The [PageRouteBuilder] class makes it possible to define a custom route
+/// in terms of callbacks. Here's an example that rotates and fades its child
+/// when the route appears or disappears. This route does not obscure the entire
+/// screen because it specifies `opaque: false`, just as a popup route does.
+///
+/// ```dart
+/// Navigator.push(context, PageRouteBuilder(
+///   opaque: false,
+///   pageBuilder: (BuildContext context, _, __) {
+///     return Center(child: Text('My PageRoute'));
+///   },
+///   transitionsBuilder: (___, Animation<double> animation, ____, Widget child) {
+///     return FadeTransition(
+///       opacity: animation,
+///       child: RotationTransition(
+///         turns: Tween<double>(begin: 0.5, end: 1.0).animate(animation),
+///         child: child,
+///       ),
+///     );
+///   }
+/// ));
+/// ```
+///
+/// The page route is built in two parts, the "page" and the
+/// "transitions". The page becomes a descendant of the child passed to
+/// the `transitionsBuilder` function. Typically the page is only built once,
+/// because it doesn't depend on its animation parameters (elided with `_`
+/// and `__` in this example). The transition is built on every frame
+/// for its duration.
+///
+/// ### Nesting Navigators
+///
+/// An app can use more than one [Navigator]. Nesting one [Navigator] below
+/// another [Navigator] can be used to create an "inner journey" such as tabbed
+/// navigation, user registration, store checkout, or other independent journeys
+/// that represent a subsection of your overall application.
+///
+/// #### Example
+///
+/// It is standard practice for iOS apps to use tabbed navigation where each
+/// tab maintains its own navigation history. Therefore, each tab has its own
+/// [Navigator], creating a kind of "parallel navigation."
+///
+/// In addition to the parallel navigation of the tabs, it is still possible to
+/// launch full-screen pages that completely cover the tabs. For example: an
+/// on-boarding flow, or an alert dialog. Therefore, there must exist a "root"
+/// [Navigator] that sits above the tab navigation. As a result, each of the
+/// tab's [Navigator]s are actually nested [Navigator]s sitting below a single
+/// root [Navigator].
+///
+/// In practice, the nested [Navigator]s for tabbed navigation sit in the
+/// [WidgetsApp] and [CupertinoTabView] widgets and do not need to be explicitly
+/// created or managed.
+///
+/// {@tool sample --template=freeform_no_null_safety}
+/// The following example demonstrates how a nested [Navigator] can be used to
+/// present a standalone user registration journey.
+///
+/// Even though this example uses two [Navigator]s to demonstrate nested
+/// [Navigator]s, a similar result is possible using only a single [Navigator].
+///
+/// Run this example with `flutter run --route=/signup` to start it with
+/// the signup flow instead of on the home page.
+///
+/// ```dart imports
+/// import 'package:flute/material.dart';
+/// ```
+///
+/// ```dart main
+/// void main() => runApp(MyApp());
+/// ```
+///
+/// ```dart
+/// class MyApp extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return MaterialApp(
+///       title: 'Flutter Code Sample for Navigator',
+///       // MaterialApp contains our top-level Navigator
+///       initialRoute: '/',
+///       routes: {
+///         '/': (BuildContext context) => HomePage(),
+///         '/signup': (BuildContext context) => SignUpPage(),
+///       },
+///     );
+///   }
+/// }
+///
+/// class HomePage extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return DefaultTextStyle(
+///       style: Theme.of(context).textTheme.headline4,
+///       child: Container(
+///         color: Colors.white,
+///         alignment: Alignment.center,
+///         child: Text('Home Page'),
+///       ),
+///     );
+///   }
+/// }
+///
+/// class CollectPersonalInfoPage extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return DefaultTextStyle(
+///       style: Theme.of(context).textTheme.headline4,
+///       child: GestureDetector(
+///         onTap: () {
+///           // This moves from the personal info page to the credentials page,
+///           // replacing this page with that one.
+///           Navigator.of(context)
+///             .pushReplacementNamed('signup/choose_credentials');
+///         },
+///         child: Container(
+///           color: Colors.lightBlue,
+///           alignment: Alignment.center,
+///           child: Text('Collect Personal Info Page'),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+///
+/// class ChooseCredentialsPage extends StatelessWidget {
+///   const ChooseCredentialsPage({
+///     this.onSignupComplete,
+///   });
+///
+///   final VoidCallback onSignupComplete;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return GestureDetector(
+///       onTap: onSignupComplete,
+///       child: DefaultTextStyle(
+///         style: Theme.of(context).textTheme.headline4,
+///         child: Container(
+///           color: Colors.pinkAccent,
+///           alignment: Alignment.center,
+///           child: Text('Choose Credentials Page'),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+///
+/// class SignUpPage extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     // SignUpPage builds its own Navigator which ends up being a nested
+///     // Navigator in our app.
+///     return Navigator(
+///       initialRoute: 'signup/personal_info',
+///       onGenerateRoute: (RouteSettings settings) {
+///         WidgetBuilder builder;
+///         switch (settings.name) {
+///           case 'signup/personal_info':
+///           // Assume CollectPersonalInfoPage collects personal info and then
+///           // navigates to 'signup/choose_credentials'.
+///             builder = (BuildContext _) => CollectPersonalInfoPage();
+///             break;
+///           case 'signup/choose_credentials':
+///           // Assume ChooseCredentialsPage collects new credentials and then
+///           // invokes 'onSignupComplete()'.
+///             builder = (BuildContext _) => ChooseCredentialsPage(
+///               onSignupComplete: () {
+///                 // Referencing Navigator.of(context) from here refers to the
+///                 // top level Navigator because SignUpPage is above the
+///                 // nested Navigator that it created. Therefore, this pop()
+///                 // will pop the entire "sign up" journey and return to the
+///                 // "/" route, AKA HomePage.
+///                 Navigator.of(context).pop();
+///               },
+///             );
+///             break;
+///           default:
+///             throw Exception('Invalid route: ${settings.name}');
+///         }
+///         return MaterialPageRoute(builder: builder, settings: settings);
+///       },
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// [Navigator.of] operates on the nearest ancestor [Navigator] from the given
+/// [BuildContext]. Be sure to provide a [BuildContext] below the intended
+/// [Navigator], especially in large `build` methods where nested [Navigator]s
+/// are created. The [Builder] widget can be used to access a [BuildContext] at
+/// a desired location in the widget subtree.
+///
+/// ## State Restoration
+///
+/// If provided with a [restorationScopeId] and when surrounded by a valid
+/// [RestorationScope] the [Navigator] will restore its state by recreating
+/// the current history stack of [Route]s during state restoration and by
+/// restoring the internal state of those [Route]s. However, not all [Route]s
+/// on the stack can be restored:
+///
+///  * [Page]-based routes restore their state if [Page.restorationId] is
+///    provided.
+///  * [Route]s added with the classic imperative API ([push], [pushNamed], and
+///    friends) can never restore their state.
+///  * A [Route] added with the restorable imperative API ([restorablePush],
+///    [restorablePushNamed], and all other imperative methods with "restorable"
+///    in their name) restores its state if all routes below it up to and
+///    including the first [Page]-based route below it are restored. If there
+///    is no [Page]-based route below it, it only restores its state if all
+///    routes below it restore theirs.
+///
+/// If a [Route] is deemed restorable, the [Navigator] will set its
+/// [Route.restorationScopeId] to a non-null value. Routes can use that ID to
+/// store and restore their own state. As an example, the [ModalRoute] will
+/// use this ID to create a [RestorationScope] for its content widgets.
+class Navigator extends StatefulWidget {
+  /// Creates a widget that maintains a stack-based history of child widgets.
+  ///
+  /// The [onGenerateRoute], [pages], [onGenerateInitialRoutes],
+  /// [transitionDelegate], [observers]  arguments must not be null.
+  ///
+  /// If the [pages] is not empty, the [onPopPage] must not be null.
+  const Navigator({
+    Key? key,
+    this.pages = const <Page<dynamic>>[],
+    this.onPopPage,
+    this.initialRoute,
+    this.onGenerateInitialRoutes = Navigator.defaultGenerateInitialRoutes,
+    this.onGenerateRoute,
+    this.onUnknownRoute,
+    this.transitionDelegate = const DefaultTransitionDelegate<dynamic>(),
+    this.reportsRouteUpdateToEngine = false,
+    this.observers = const <NavigatorObserver>[],
+    this.restorationScopeId,
+  }) : assert(pages != null),
+       assert(onGenerateInitialRoutes != null),
+       assert(transitionDelegate != null),
+       assert(observers != null),
+       assert(reportsRouteUpdateToEngine != null),
+       super(key: key);
+
+  /// The list of pages with which to populate the history.
+  ///
+  /// Pages are turned into routes using [Page.createRoute] in a manner
+  /// analogous to how [Widget]s are turned into [Element]s (and [State]s or
+  /// [RenderObject]s) using [Widget.createElement] (and
+  /// [StatefulWidget.createState] or [RenderObjectWidget.createRenderObject]).
+  ///
+  /// When this list is updated, the new list is compared to the previous
+  /// list and the set of routes is updated accordingly.
+  ///
+  /// Some [Route]s do not correspond to [Page] objects, namely, those that are
+  /// added to the history using the [Navigator] API ([push] and friends). A
+  /// [Route] that does not correspond to a [Page] object is called a pageless
+  /// route and is tied to the [Route] that _does_ correspond to a [Page] object
+  /// that is below it in the history.
+  ///
+  /// Pages that are added or removed may be animated as controlled by the
+  /// [transitionDelegate]. If a page is removed that had other pageless routes
+  /// pushed on top of it using [push] and friends, those pageless routes are
+  /// also removed with or without animation as determined by the
+  /// [transitionDelegate].
+  ///
+  /// To use this API, an [onPopPage] callback must also be provided to properly
+  /// clean up this list if a page has been popped.
+  ///
+  /// If [initialRoute] is non-null when the widget is first created, then
+  /// [onGenerateInitialRoutes] is used to generate routes that are above those
+  /// corresponding to [pages] in the initial history.
+  final List<Page<dynamic>> pages;
+
+  /// Called when [pop] is invoked but the current [Route] corresponds to a
+  /// [Page] found in the [pages] list.
+  ///
+  /// The `result` argument is the value with which the route is to complete
+  /// (e.g. the value returned from a dialog).
+  ///
+  /// This callback is responsible for calling [Route.didPop] and returning
+  /// whether this pop is successful.
+  ///
+  /// The [Navigator] widget should be rebuilt with a [pages] list that does not
+  /// contain the [Page] for the given [Route]. The next time the [pages] list
+  /// is updated, if the [Page] corresponding to this [Route] is still present,
+  /// it will be interpreted as a new route to display.
+  final PopPageCallback? onPopPage;
+
+  /// The delegate used for deciding how routes transition in or off the screen
+  /// during the [pages] updates.
+  ///
+  /// Defaults to [DefaultTransitionDelegate] if not specified, cannot be null.
+  final TransitionDelegate<dynamic> transitionDelegate;
+
+  /// The name of the first route to show.
+  ///
+  /// Defaults to [Navigator.defaultRouteName].
+  ///
+  /// The value is interpreted according to [onGenerateInitialRoutes], which
+  /// defaults to [defaultGenerateInitialRoutes].
+  final String? initialRoute;
+
+  /// Called to generate a route for a given [RouteSettings].
+  final RouteFactory? onGenerateRoute;
+
+  /// Called when [onGenerateRoute] fails to generate a route.
+  ///
+  /// This callback is typically used for error handling. For example, this
+  /// callback might always generate a "not found" page that describes the route
+  /// that wasn't found.
+  ///
+  /// Unknown routes can arise either from errors in the app or from external
+  /// requests to push routes, such as from Android intents.
+  final RouteFactory? onUnknownRoute;
+
+  /// A list of observers for this navigator.
+  final List<NavigatorObserver> observers;
+
+  /// Restoration ID to save and restore the state of the navigator, including
+  /// its history.
+  ///
+  /// {@template flutter.widgets.navigator.restorationScopeId}
+  /// If a restoration ID is provided, the navigator will persist its internal
+  /// state (including the route history as well as the restorable state of the
+  /// routes) and restore it during state restoration.
+  ///
+  /// If no restoration ID is provided, the route history stack will not be
+  /// restored and state restoration is disabled for the individual routes as
+  /// well.
+  ///
+  /// The state is persisted in a [RestorationBucket] claimed from
+  /// the surrounding [RestorationScope] using the provided restoration ID.
+  /// Within that bucket, the [Navigator] also creates a new [RestorationScope]
+  /// for its children (the [Route]s).
+  ///
+  /// See also:
+  ///
+  ///  * [RestorationManager], which explains how state restoration works in
+  ///    Flutter.
+  ///  * [RestorationMixin], which contains a runnable code sample showcasing
+  ///    state restoration in Flutter.
+  ///  * [Navigator], which explains under the heading "state restoration"
+  ///    how and under what conditions the navigator restores its state.
+  ///  * [Navigator.restorablePush], which includes an example showcasing how
+  ///    to push a restorable route unto the navigator.
+  /// {@endtemplate}
+  final String? restorationScopeId;
+
+  /// The name for the default route of the application.
+  ///
+  /// See also:
+  ///
+  ///  * [dart:ui.PlatformDispatcher.defaultRouteName], which reflects the route that the
+  ///    application was started with.
+  static const String defaultRouteName = '/';
+
+  /// Called when the widget is created to generate the initial list of [Route]
+  /// objects if [initialRoute] is not null.
+  ///
+  /// Defaults to [defaultGenerateInitialRoutes].
+  ///
+  /// The [NavigatorState] and [initialRoute] will be passed to the callback.
+  /// The callback must return a list of [Route] objects with which the history
+  /// will be primed.
+  ///
+  /// When parsing the initialRoute, if there's any chance that the it may
+  /// contain complex characters, it's best to use the
+  /// [characters](https://pub.dev/packages/characters) API. This will ensure
+  /// that extended grapheme clusters and surrogate pairs are treated as single
+  /// characters by the code, the same way that they appear to the user. For
+  /// example, the string "👨‍👩‍👦" appears to the user as a single
+  /// character and `string.characters.length` intuitively returns 1. On the
+  /// other hand, `string.length` returns 8, and `string.runes.length` returns
+  /// 5!
+  final RouteListFactory onGenerateInitialRoutes;
+
+  /// Whether this navigator should report route update message back to the
+  /// engine when the top-most route changes.
+  ///
+  /// If the property is set to true, this navigator automatically sends the
+  /// route update message to the engine when it detects top-most route changes.
+  /// The messages are used by the web engine to update the browser URL bar.
+  ///
+  /// If there are multiple navigators in the widget tree, at most one of them
+  /// can set this property to true (typically, the top-most one created from
+  /// the [WidgetsApp]). Otherwise, the web engine may receive multiple route
+  /// update messages from different navigators and fail to update the URL
+  /// bar.
+  ///
+  /// Defaults to false.
+  final bool reportsRouteUpdateToEngine;
+
+  /// Push a named route onto the navigator that most tightly encloses the given
+  /// context.
+  ///
+  /// {@template flutter.widgets.navigator.pushNamed}
+  /// The route name will be passed to the [Navigator.onGenerateRoute]
+  /// callback. The returned route will be pushed into the navigator.
+  ///
+  /// The new route and the previous route (if any) are notified (see
+  /// [Route.didPush] and [Route.didChangeNext]). If the [Navigator] has any
+  /// [Navigator.observers], they will be notified as well (see
+  /// [NavigatorObserver.didPush]).
+  ///
+  /// Ongoing gestures within the current route are canceled when a new route is
+  /// pushed.
+  ///
+  /// Returns a [Future] that completes to the `result` value passed to [pop]
+  /// when the pushed route is popped off the navigator.
+  ///
+  /// The `T` type argument is the type of the return value of the route.
+  ///
+  /// To use [pushNamed], an [Navigator.onGenerateRoute] callback must be
+  /// provided,
+  /// {@endtemplate}
+  ///
+  /// {@template flutter.widgets.Navigator.pushNamed}
+  /// The provided `arguments` are passed to the pushed route via
+  /// [RouteSettings.arguments]. Any object can be passed as `arguments` (e.g. a
+  /// [String], [int], or an instance of a custom `MyRouteArguments` class).
+  /// Often, a [Map] is used to pass key-value pairs.
+  ///
+  /// The `arguments` may be used in [Navigator.onGenerateRoute] or
+  /// [Navigator.onUnknownRoute] to construct the route.
+  /// {@endtemplate}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _didPushButton() {
+  ///   Navigator.pushNamed(context, '/settings');
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@tool snippet}
+  ///
+  /// The following example shows how to pass additional `arguments` to the
+  /// route:
+  ///
+  /// ```dart
+  /// void _showBerlinWeather() {
+  ///   Navigator.pushNamed(
+  ///     context,
+  ///     '/weather',
+  ///     arguments: <String, String>{
+  ///       'city': 'Berlin',
+  ///       'country': 'Germany',
+  ///     },
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@tool snippet}
+  ///
+  /// The following example shows how to pass a custom Object to the route:
+  ///
+  /// ```dart
+  /// class WeatherRouteArguments {
+  ///   WeatherRouteArguments({ this.city, this.country });
+  ///   final String city;
+  ///   final String country;
+  ///
+  ///   bool get isGermanCapital {
+  ///     return country == 'Germany' && city == 'Berlin';
+  ///   }
+  /// }
+  ///
+  /// void _showWeather() {
+  ///   Navigator.pushNamed(
+  ///     context,
+  ///     '/weather',
+  ///     arguments: WeatherRouteArguments(city: 'Berlin', country: 'Germany'),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [restorablePushNamed], which pushes a route that can be restored
+  ///    during state restoration.
+  @optionalTypeArgs
+  static Future<T?> pushNamed<T extends Object?>(
+    BuildContext context,
+    String routeName, {
+    Object? arguments,
+   }) {
+    return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments);
+  }
+
+  /// Push a named route onto the navigator that most tightly encloses the given
+  /// context.
+  ///
+  /// {@template flutter.widgets.navigator.restorablePushNamed}
+  /// Unlike [Route]s pushed via [pushNamed], [Route]s pushed with this method
+  /// are restored during state restoration according to the rules outlined
+  /// in the "State Restoration" section of [Navigator].
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.widgets.navigator.pushNamed}
+  ///
+  /// {@template flutter.widgets.Navigator.restorablePushNamed.arguments}
+  /// The provided `arguments` are passed to the pushed route via
+  /// [RouteSettings.arguments]. Any object that is serializable via the
+  /// [StandardMessageCodec] can be passed as `arguments`. Often, a Map is used
+  /// to pass key-value pairs.
+  ///
+  /// The arguments may be used in [Navigator.onGenerateRoute] or
+  /// [Navigator.onUnknownRoute] to construct the route.
+  /// {@endtemplate}
+  ///
+  /// {@template flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  /// The method returns an opaque ID for the pushed route that can be used by
+  /// the [RestorableRouteFuture] to gain access to the actual [Route] object
+  /// added to the navigator and its return value. You can ignore the return
+  /// value of this method, if you do not care about the route object or the
+  /// route's return value.
+  /// {@endtemplate}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _showParisWeather() {
+  ///   Navigator.restorablePushNamed(
+  ///     context,
+  ///     '/weather',
+  ///     arguments: <String, String>{
+  ///       'city': 'Paris',
+  ///       'country': 'France',
+  ///     },
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  static String restorablePushNamed<T extends Object?>(
+    BuildContext context,
+    String routeName, {
+    Object? arguments,
+  }) {
+    return Navigator.of(context).restorablePushNamed<T>(routeName, arguments: arguments);
+  }
+
+  /// Replace the current route of the navigator that most tightly encloses the
+  /// given context by pushing the route named [routeName] and then disposing
+  /// the previous route once the new route has finished animating in.
+  ///
+  /// {@template flutter.widgets.navigator.pushReplacementNamed}
+  /// If non-null, `result` will be used as the result of the route that is
+  /// removed; the future that had been returned from pushing that old route
+  /// will complete with `result`. Routes such as dialogs or popup menus
+  /// typically use this mechanism to return the value selected by the user to
+  /// the widget that created their route. The type of `result`, if provided,
+  /// must match the type argument of the class of the old route (`TO`).
+  ///
+  /// The route name will be passed to the [Navigator.onGenerateRoute]
+  /// callback. The returned route will be pushed into the navigator.
+  ///
+  /// The new route and the route below the removed route are notified (see
+  /// [Route.didPush] and [Route.didChangeNext]). If the [Navigator] has any
+  /// [Navigator.observers], they will be notified as well (see
+  /// [NavigatorObserver.didReplace]). The removed route is notified once the
+  /// new route has finished animating (see [Route.didComplete]). The removed
+  /// route's exit animation is not run (see [popAndPushNamed] for a variant
+  /// that does animated the removed route).
+  ///
+  /// Ongoing gestures within the current route are canceled when a new route is
+  /// pushed.
+  ///
+  /// Returns a [Future] that completes to the `result` value passed to [pop]
+  /// when the pushed route is popped off the navigator.
+  ///
+  /// The `T` type argument is the type of the return value of the new route,
+  /// and `TO` is the type of the return value of the old route.
+  ///
+  /// To use [pushReplacementNamed], a [Navigator.onGenerateRoute] callback must
+  /// be provided.
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.widgets.Navigator.pushNamed}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _switchToBrightness() {
+  ///   Navigator.pushReplacementNamed(context, '/settings/brightness');
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [restorablePushReplacementNamed], which pushes a replacement route that
+  ///    can be restored during state restoration.
+  @optionalTypeArgs
+  static Future<T?> pushReplacementNamed<T extends Object?, TO extends Object?>(
+    BuildContext context,
+    String routeName, {
+    TO? result,
+    Object? arguments,
+  }) {
+    return Navigator.of(context).pushReplacementNamed<T, TO>(routeName, arguments: arguments, result: result);
+  }
+
+  /// Replace the current route of the navigator that most tightly encloses the
+  /// given context by pushing the route named [routeName] and then disposing
+  /// the previous route once the new route has finished animating in.
+  ///
+  /// {@template flutter.widgets.navigator.restorablePushReplacementNamed}
+  /// Unlike [Route]s pushed via [pushReplacementNamed], [Route]s pushed with
+  /// this method are restored during state restoration according to the rules
+  /// outlined in the "State Restoration" section of [Navigator].
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.widgets.navigator.pushReplacementNamed}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.arguments}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _switchToAudioVolume() {
+  ///   Navigator.restorablePushReplacementNamed(context, '/settings/volume');
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  static String restorablePushReplacementNamed<T extends Object?, TO extends Object?>(
+    BuildContext context,
+    String routeName, {
+    TO? result,
+    Object? arguments,
+  }) {
+    return Navigator.of(context).restorablePushReplacementNamed<T, TO>(routeName, arguments: arguments, result: result);
+  }
+
+  /// Pop the current route off the navigator that most tightly encloses the
+  /// given context and push a named route in its place.
+  ///
+  /// {@template flutter.widgets.navigator.popAndPushNamed}
+  /// The popping of the previous route is handled as per [pop].
+  ///
+  /// The new route's name will be passed to the [Navigator.onGenerateRoute]
+  /// callback. The returned route will be pushed into the navigator.
+  ///
+  /// The new route, the old route, and the route below the old route (if any)
+  /// are all notified (see [Route.didPop], [Route.didComplete],
+  /// [Route.didPopNext], [Route.didPush], and [Route.didChangeNext]). If the
+  /// [Navigator] has any [Navigator.observers], they will be notified as well
+  /// (see [NavigatorObserver.didPop] and [NavigatorObserver.didPush]). The
+  /// animations for the pop and the push are performed simultaneously, so the
+  /// route below may be briefly visible even if both the old route and the new
+  /// route are opaque (see [TransitionRoute.opaque]).
+  ///
+  /// Ongoing gestures within the current route are canceled when a new route is
+  /// pushed.
+  ///
+  /// Returns a [Future] that completes to the `result` value passed to [pop]
+  /// when the pushed route is popped off the navigator.
+  ///
+  /// The `T` type argument is the type of the return value of the new route,
+  /// and `TO` is the return value type of the old route.
+  ///
+  /// To use [popAndPushNamed], a [Navigator.onGenerateRoute] callback must be provided.
+  ///
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.widgets.Navigator.pushNamed}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _selectAccessibility() {
+  ///   Navigator.popAndPushNamed(context, '/settings/accessibility');
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [restorablePopAndPushNamed], which pushes a new route that can be
+  ///    restored during state restoration.
+  @optionalTypeArgs
+  static Future<T?> popAndPushNamed<T extends Object?, TO extends Object?>(
+    BuildContext context,
+    String routeName, {
+    TO? result,
+    Object? arguments,
+   }) {
+    return Navigator.of(context).popAndPushNamed<T, TO>(routeName, arguments: arguments, result: result);
+  }
+
+  /// Pop the current route off the navigator that most tightly encloses the
+  /// given context and push a named route in its place.
+  ///
+  /// {@template flutter.widgets.navigator.restorablePopAndPushNamed}
+  /// Unlike [Route]s pushed via [popAndPushNamed], [Route]s pushed with
+  /// this method are restored during state restoration according to the rules
+  /// outlined in the "State Restoration" section of [Navigator].
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.widgets.navigator.popAndPushNamed}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.arguments}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _selectNetwork() {
+  ///   Navigator.restorablePopAndPushNamed(context, '/settings/network');
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  static String restorablePopAndPushNamed<T extends Object?, TO extends Object?>(
+      BuildContext context,
+      String routeName, {
+      TO? result,
+      Object? arguments,
+    }) {
+    return Navigator.of(context).restorablePopAndPushNamed<T, TO>(routeName, arguments: arguments, result: result);
+  }
+
+  /// Push the route with the given name onto the navigator that most tightly
+  /// encloses the given context, and then remove all the previous routes until
+  /// the `predicate` returns true.
+  ///
+  /// {@template flutter.widgets.navigator.pushNamedAndRemoveUntil}
+  /// The predicate may be applied to the same route more than once if
+  /// [Route.willHandlePopInternally] is true.
+  ///
+  /// To remove routes until a route with a certain name, use the
+  /// [RoutePredicate] returned from [ModalRoute.withName].
+  ///
+  /// To remove all the routes below the pushed route, use a [RoutePredicate]
+  /// that always returns false (e.g. `(Route<dynamic> route) => false`).
+  ///
+  /// The removed routes are removed without being completed, so this method
+  /// does not take a return value argument.
+  ///
+  /// The new route's name (`routeName`) will be passed to the
+  /// [Navigator.onGenerateRoute] callback. The returned route will be pushed
+  /// into the navigator.
+  ///
+  /// The new route and the route below the bottommost removed route (which
+  /// becomes the route below the new route) are notified (see [Route.didPush]
+  /// and [Route.didChangeNext]). If the [Navigator] has any
+  /// [Navigator.observers], they will be notified as well (see
+  /// [NavigatorObserver.didPush] and [NavigatorObserver.didRemove]). The
+  /// removed routes are disposed, without being notified, once the new route
+  /// has finished animating. The futures that had been returned from pushing
+  /// those routes will not complete.
+  ///
+  /// Ongoing gestures within the current route are canceled when a new route is
+  /// pushed.
+  ///
+  /// Returns a [Future] that completes to the `result` value passed to [pop]
+  /// when the pushed route is popped off the navigator.
+  ///
+  /// The `T` type argument is the type of the return value of the new route.
+  ///
+  /// To use [pushNamedAndRemoveUntil], an [Navigator.onGenerateRoute] callback
+  /// must be provided.
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.widgets.Navigator.pushNamed}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _resetToCalendar() {
+  ///   Navigator.pushNamedAndRemoveUntil(context, '/calendar', ModalRoute.withName('/'));
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [restorablePushNamedAndRemoveUntil], which pushes a new route that can
+  ///    be restored during state restoration.
+  @optionalTypeArgs
+  static Future<T?> pushNamedAndRemoveUntil<T extends Object?>(
+    BuildContext context,
+    String newRouteName,
+    RoutePredicate predicate, {
+    Object? arguments,
+  }) {
+    return Navigator.of(context).pushNamedAndRemoveUntil<T>(newRouteName, predicate, arguments: arguments);
+  }
+
+  /// Push the route with the given name onto the navigator that most tightly
+  /// encloses the given context, and then remove all the previous routes until
+  /// the `predicate` returns true.
+  ///
+  /// {@template flutter.widgets.navigator.restorablePushNamedAndRemoveUntil}
+  /// Unlike [Route]s pushed via [pushNamedAndRemoveUntil], [Route]s pushed with
+  /// this method are restored during state restoration according to the rules
+  /// outlined in the "State Restoration" section of [Navigator].
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.widgets.navigator.pushNamedAndRemoveUntil}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.arguments}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _resetToOverview() {
+  ///   Navigator.restorablePushNamedAndRemoveUntil(context, '/overview', ModalRoute.withName('/'));
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  static String restorablePushNamedAndRemoveUntil<T extends Object?>(
+      BuildContext context,
+      String newRouteName,
+      RoutePredicate predicate, {
+      Object? arguments,
+    }) {
+    return Navigator.of(context).restorablePushNamedAndRemoveUntil<T>(newRouteName, predicate, arguments: arguments);
+  }
+
+  /// Push the given route onto the navigator that most tightly encloses the
+  /// given context.
+  ///
+  /// {@template flutter.widgets.navigator.push}
+  /// The new route and the previous route (if any) are notified (see
+  /// [Route.didPush] and [Route.didChangeNext]). If the [Navigator] has any
+  /// [Navigator.observers], they will be notified as well (see
+  /// [NavigatorObserver.didPush]).
+  ///
+  /// Ongoing gestures within the current route are canceled when a new route is
+  /// pushed.
+  ///
+  /// Returns a [Future] that completes to the `result` value passed to [pop]
+  /// when the pushed route is popped off the navigator.
+  ///
+  /// The `T` type argument is the type of the return value of the route.
+  /// {@endtemplate}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _openMyPage() {
+  ///   Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => MyPage()));
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [restorablePush], which pushes a route that can be restored during
+  ///    state restoration.
+  @optionalTypeArgs
+  static Future<T?> push<T extends Object?>(BuildContext context, Route<T> route) {
+    return Navigator.of(context).push(route);
+  }
+
+  /// Push a new route onto the navigator that most tightly encloses the
+  /// given context.
+  ///
+  /// {@template flutter.widgets.navigator.restorablePush}
+  /// Unlike [Route]s pushed via [push], [Route]s pushed with this method are
+  /// restored during state restoration according to the rules outlined in the
+  /// "State Restoration" section of [Navigator].
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.widgets.navigator.push}
+  ///
+  /// {@template flutter.widgets.Navigator.restorablePush}
+  /// The method takes a _static_ [RestorableRouteBuilder] as argument, which
+  /// must instantiate and return a new [Route] object that will be added to
+  /// the navigator. The provided `arguments` object is passed to the
+  /// `routeBuilder`. The navigator calls the static `routeBuilder` function
+  /// again during state restoration to re-create the route object.
+  ///
+  /// Any object that is serializable via the [StandardMessageCodec] can be
+  /// passed as `arguments`. Often, a Map is used to pass key-value pairs.
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  ///
+  /// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// static Route _myRouteBuilder(BuildContext context, Object arguments) {
+  ///   return MaterialPageRoute(
+  ///     builder: (BuildContext context) => MyStatefulWidget(),
+  ///   );
+  /// }
+  ///
+  /// Widget build(BuildContext context) {
+  ///   return Scaffold(
+  ///     appBar: AppBar(
+  ///       title: const Text('Sample Code'),
+  ///     ),
+  ///     floatingActionButton: FloatingActionButton(
+  ///       onPressed: () => Navigator.restorablePush(context, _myRouteBuilder),
+  ///       tooltip: 'Increment Counter',
+  ///       child: const Icon(Icons.add),
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  static String restorablePush<T extends Object?>(BuildContext context, RestorableRouteBuilder<T> routeBuilder, {Object? arguments}) {
+    return Navigator.of(context).restorablePush(routeBuilder, arguments: arguments);
+  }
+
+  /// Replace the current route of the navigator that most tightly encloses the
+  /// given context by pushing the given route and then disposing the previous
+  /// route once the new route has finished animating in.
+  ///
+  /// {@template flutter.widgets.navigator.pushReplacement}
+  /// If non-null, `result` will be used as the result of the route that is
+  /// removed; the future that had been returned from pushing that old route will
+  /// complete with `result`. Routes such as dialogs or popup menus typically
+  /// use this mechanism to return the value selected by the user to the widget
+  /// that created their route. The type of `result`, if provided, must match
+  /// the type argument of the class of the old route (`TO`).
+  ///
+  /// The new route and the route below the removed route are notified (see
+  /// [Route.didPush] and [Route.didChangeNext]). If the [Navigator] has any
+  /// [Navigator.observers], they will be notified as well (see
+  /// [NavigatorObserver.didReplace]). The removed route is notified once the
+  /// new route has finished animating (see [Route.didComplete]).
+  ///
+  /// Ongoing gestures within the current route are canceled when a new route is
+  /// pushed.
+  ///
+  /// Returns a [Future] that completes to the `result` value passed to [pop]
+  /// when the pushed route is popped off the navigator.
+  ///
+  /// The `T` type argument is the type of the return value of the new route,
+  /// and `TO` is the type of the return value of the old route.
+  /// {@endtemplate}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _completeLogin() {
+  ///   Navigator.pushReplacement(
+  ///       context, MaterialPageRoute(builder: (BuildContext context) => MyHomePage()));
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [restorablePushReplacement], which pushes a replacement route that can
+  ///    be restored during state restoration.
+  @optionalTypeArgs
+  static Future<T?> pushReplacement<T extends Object?, TO extends Object?>(BuildContext context, Route<T> newRoute, { TO? result }) {
+    return Navigator.of(context).pushReplacement<T, TO>(newRoute, result: result);
+  }
+
+  /// Replace the current route of the navigator that most tightly encloses the
+  /// given context by pushing a new route and then disposing the previous
+  /// route once the new route has finished animating in.
+  ///
+  /// {@template flutter.widgets.navigator.restorablePushReplacement}
+  /// Unlike [Route]s pushed via [pushReplacement], [Route]s pushed with this
+  /// method are restored during state restoration according to the rules
+  /// outlined in the "State Restoration" section of [Navigator].
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.widgets.navigator.pushReplacement}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePush}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  ///
+  /// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// static Route _myRouteBuilder(BuildContext context, Object arguments) {
+  ///   return MaterialPageRoute(
+  ///     builder: (BuildContext context) => MyStatefulWidget(),
+  ///   );
+  /// }
+  ///
+  /// Widget build(BuildContext context) {
+  ///   return Scaffold(
+  ///     appBar: AppBar(
+  ///       title: const Text('Sample Code'),
+  ///     ),
+  ///     floatingActionButton: FloatingActionButton(
+  ///       onPressed: () => Navigator.restorablePushReplacement(context, _myRouteBuilder),
+  ///       tooltip: 'Increment Counter',
+  ///       child: const Icon(Icons.add),
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  static String restorablePushReplacement<T extends Object?, TO extends Object?>(BuildContext context, RestorableRouteBuilder<T> routeBuilder, { TO? result, Object? arguments }) {
+    return Navigator.of(context).restorablePushReplacement<T, TO>(routeBuilder, result: result, arguments: arguments);
+  }
+
+  /// Push the given route onto the navigator that most tightly encloses the
+  /// given context, and then remove all the previous routes until the
+  /// `predicate` returns true.
+  ///
+  /// {@template flutter.widgets.navigator.pushAndRemoveUntil}
+  /// The predicate may be applied to the same route more than once if
+  /// [Route.willHandlePopInternally] is true.
+  ///
+  /// To remove routes until a route with a certain name, use the
+  /// [RoutePredicate] returned from [ModalRoute.withName].
+  ///
+  /// To remove all the routes below the pushed route, use a [RoutePredicate]
+  /// that always returns false (e.g. `(Route<dynamic> route) => false`).
+  ///
+  /// The removed routes are removed without being completed, so this method
+  /// does not take a return value argument.
+  ///
+  /// The newly pushed route and its preceding route are notified for
+  /// [Route.didPush]. After removal, the new route and its new preceding route,
+  /// (the route below the bottommost removed route) are notified through
+  /// [Route.didChangeNext]). If the [Navigator] has any [Navigator.observers],
+  /// they will be notified as well (see [NavigatorObserver.didPush] and
+  /// [NavigatorObserver.didRemove]). The removed routes are disposed of and
+  /// notified, once the new route has finished animating. The futures that had
+  /// been returned from pushing those routes will not complete.
+  ///
+  /// Ongoing gestures within the current route are canceled when a new route is
+  /// pushed.
+  ///
+  /// Returns a [Future] that completes to the `result` value passed to [pop]
+  /// when the pushed route is popped off the navigator.
+  ///
+  /// The `T` type argument is the type of the return value of the new route.
+  /// {@endtemplate}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _finishAccountCreation() {
+  ///   Navigator.pushAndRemoveUntil(
+  ///     context,
+  ///     MaterialPageRoute(builder: (BuildContext context) => MyHomePage()),
+  ///     ModalRoute.withName('/'),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [restorablePushAndRemoveUntil], which pushes a route that can be
+  ///    restored during state restoration.
+  @optionalTypeArgs
+  static Future<T?> pushAndRemoveUntil<T extends Object?>(BuildContext context, Route<T> newRoute, RoutePredicate predicate) {
+    return Navigator.of(context).pushAndRemoveUntil<T>(newRoute, predicate);
+  }
+
+  /// Push a new route onto the navigator that most tightly encloses the
+  /// given context, and then remove all the previous routes until the
+  /// `predicate` returns true.
+  ///
+  /// {@template flutter.widgets.navigator.restorablePushAndRemoveUntil}
+  /// Unlike [Route]s pushed via [pushAndRemoveUntil], [Route]s pushed with this
+  /// method are restored during state restoration according to the rules
+  /// outlined in the "State Restoration" section of [Navigator].
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.widgets.navigator.pushAndRemoveUntil}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePush}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  ///
+  /// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// static Route _myRouteBuilder(BuildContext context, Object arguments) {
+  ///   return MaterialPageRoute(
+  ///     builder: (BuildContext context) => MyStatefulWidget(),
+  ///   );
+  /// }
+  ///
+  /// Widget build(BuildContext context) {
+  ///   return Scaffold(
+  ///     appBar: AppBar(
+  ///       title: const Text('Sample Code'),
+  ///     ),
+  ///     floatingActionButton: FloatingActionButton(
+  ///       onPressed: () => Navigator.restorablePushAndRemoveUntil(
+  ///         context,
+  ///         _myRouteBuilder,
+  ///         ModalRoute.withName('/'),
+  ///       ),
+  ///       tooltip: 'Increment Counter',
+  ///       child: const Icon(Icons.add),
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  static String restorablePushAndRemoveUntil<T extends Object?>(BuildContext context, RestorableRouteBuilder<T> newRouteBuilder, RoutePredicate predicate, {Object? arguments}) {
+    return Navigator.of(context).restorablePushAndRemoveUntil<T>(newRouteBuilder, predicate, arguments: arguments);
+  }
+
+  /// Replaces a route on the navigator that most tightly encloses the given
+  /// context with a new route.
+  ///
+  /// {@template flutter.widgets.navigator.replace}
+  /// The old route must not be currently visible, as this method skips the
+  /// animations and therefore the removal would be jarring if it was visible.
+  /// To replace the top-most route, consider [pushReplacement] instead, which
+  /// _does_ animate the new route, and delays removing the old route until the
+  /// new route has finished animating.
+  ///
+  /// The removed route is removed without being completed, so this method does
+  /// not take a return value argument.
+  ///
+  /// The new route, the route below the new route (if any), and the route above
+  /// the new route, are all notified (see [Route.didReplace],
+  /// [Route.didChangeNext], and [Route.didChangePrevious]). If the [Navigator]
+  /// has any [Navigator.observers], they will be notified as well (see
+  /// [NavigatorObserver.didReplace]). The removed route is disposed without
+  /// being notified. The future that had been returned from pushing that routes
+  /// will not complete.
+  ///
+  /// This can be useful in combination with [removeRouteBelow] when building a
+  /// non-linear user experience.
+  ///
+  /// The `T` type argument is the type of the return value of the new route.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [replaceRouteBelow], which is the same but identifies the route to be
+  ///    removed by reference to the route above it, rather than directly.
+  ///  * [restorableReplace], which adds a replacement route that can be
+  ///    restored during state restoration.
+  @optionalTypeArgs
+  static void replace<T extends Object?>(BuildContext context, { required Route<dynamic> oldRoute, required Route<T> newRoute }) {
+    return Navigator.of(context).replace<T>(oldRoute: oldRoute, newRoute: newRoute);
+  }
+
+  /// Replaces a route on the navigator that most tightly encloses the given
+  /// context with a new route.
+  ///
+  /// {@template flutter.widgets.navigator.restorableReplace}
+  /// Unlike [Route]s added via [replace], [Route]s added with this method are
+  /// restored during state restoration according to the rules outlined in the
+  /// "State Restoration" section of [Navigator].
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.widgets.navigator.replace}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePush}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  @optionalTypeArgs
+  static String restorableReplace<T extends Object?>(BuildContext context, { required Route<dynamic> oldRoute, required RestorableRouteBuilder<T> newRouteBuilder, Object? arguments }) {
+    return Navigator.of(context).restorableReplace<T>(oldRoute: oldRoute, newRouteBuilder: newRouteBuilder, arguments: arguments);
+  }
+
+  /// Replaces a route on the navigator that most tightly encloses the given
+  /// context with a new route. The route to be replaced is the one below the
+  /// given `anchorRoute`.
+  ///
+  /// {@template flutter.widgets.navigator.replaceRouteBelow}
+  /// The old route must not be current visible, as this method skips the
+  /// animations and therefore the removal would be jarring if it was visible.
+  /// To replace the top-most route, consider [pushReplacement] instead, which
+  /// _does_ animate the new route, and delays removing the old route until the
+  /// new route has finished animating.
+  ///
+  /// The removed route is removed without being completed, so this method does
+  /// not take a return value argument.
+  ///
+  /// The new route, the route below the new route (if any), and the route above
+  /// the new route, are all notified (see [Route.didReplace],
+  /// [Route.didChangeNext], and [Route.didChangePrevious]). If the [Navigator]
+  /// has any [Navigator.observers], they will be notified as well (see
+  /// [NavigatorObserver.didReplace]). The removed route is disposed without
+  /// being notified. The future that had been returned from pushing that routes
+  /// will not complete.
+  ///
+  /// The `T` type argument is the type of the return value of the new route.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [replace], which is the same but identifies the route to be removed
+  ///    directly.
+  ///  * [restorableReplaceRouteBelow], which adds a replacement route that can
+  ///    be restored during state restoration.
+  @optionalTypeArgs
+  static void replaceRouteBelow<T extends Object?>(BuildContext context, { required Route<dynamic> anchorRoute, required Route<T> newRoute }) {
+    return Navigator.of(context).replaceRouteBelow<T>(anchorRoute: anchorRoute, newRoute: newRoute);
+  }
+
+  /// Replaces a route on the navigator that most tightly encloses the given
+  /// context with a new route. The route to be replaced is the one below the
+  /// given `anchorRoute`.
+  ///
+  /// {@template flutter.widgets.navigator.restorableReplaceRouteBelow}
+  /// Unlike [Route]s added via [restorableReplaceRouteBelow], [Route]s added
+  /// with this method are restored during state restoration according to the
+  /// rules outlined in the "State Restoration" section of [Navigator].
+  /// {@endtemplate}
+  ///
+  /// {@macro flutter.widgets.navigator.replaceRouteBelow}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePush}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  @optionalTypeArgs
+  static String restorableReplaceRouteBelow<T extends Object?>(BuildContext context, { required Route<dynamic> anchorRoute, required RestorableRouteBuilder<T> newRouteBuilder, Object? arguments }) {
+    return Navigator.of(context).restorableReplaceRouteBelow<T>(anchorRoute: anchorRoute, newRouteBuilder: newRouteBuilder, arguments: arguments);
+  }
+
+  /// Whether the navigator that most tightly encloses the given context can be
+  /// popped.
+  ///
+  /// {@template flutter.widgets.navigator.canPop}
+  /// The initial route cannot be popped off the navigator, which implies that
+  /// this function returns true only if popping the navigator would not remove
+  /// the initial route.
+  ///
+  /// If there is no [Navigator] in scope, returns false.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [Route.isFirst], which returns true for routes for which [canPop]
+  ///    returns false.
+  static bool canPop(BuildContext context) {
+    final NavigatorState? navigator = Navigator.maybeOf(context);
+    return navigator != null && navigator.canPop();
+  }
+
+  /// Consults the current route's [Route.willPop] method, and acts accordingly,
+  /// potentially popping the route as a result; returns whether the pop request
+  /// should be considered handled.
+  ///
+  /// {@template flutter.widgets.navigator.maybePop}
+  /// If [Route.willPop] returns [RoutePopDisposition.pop], then the [pop]
+  /// method is called, and this method returns true, indicating that it handled
+  /// the pop request.
+  ///
+  /// If [Route.willPop] returns [RoutePopDisposition.doNotPop], then this
+  /// method returns true, but does not do anything beyond that.
+  ///
+  /// If [Route.willPop] returns [RoutePopDisposition.bubble], then this method
+  /// returns false, and the caller is responsible for sending the request to
+  /// the containing scope (e.g. by closing the application).
+  ///
+  /// This method is typically called for a user-initiated [pop]. For example on
+  /// Android it's called by the binding for the system's back button.
+  ///
+  /// The `T` type argument is the type of the return value of the current
+  /// route. (Typically this isn't known; consider specifying `dynamic` or
+  /// `Null`.)
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [Form], which provides an `onWillPop` callback that enables the form
+  ///    to veto a [pop] initiated by the app's back button.
+  ///  * [ModalRoute], which provides a `scopedWillPopCallback` that can be used
+  ///    to define the route's `willPop` method.
+  @optionalTypeArgs
+  static Future<bool> maybePop<T extends Object?>(BuildContext context, [ T? result ]) {
+    return Navigator.of(context).maybePop<T>(result);
+  }
+
+  /// Pop the top-most route off the navigator that most tightly encloses the
+  /// given context.
+  ///
+  /// {@template flutter.widgets.navigator.pop}
+  /// The current route's [Route.didPop] method is called first. If that method
+  /// returns false, then the route remains in the [Navigator]'s history (the
+  /// route is expected to have popped some internal state; see e.g.
+  /// [LocalHistoryRoute]). Otherwise, the rest of this description applies.
+  ///
+  /// If non-null, `result` will be used as the result of the route that is
+  /// popped; the future that had been returned from pushing the popped route
+  /// will complete with `result`. Routes such as dialogs or popup menus
+  /// typically use this mechanism to return the value selected by the user to
+  /// the widget that created their route. The type of `result`, if provided,
+  /// must match the type argument of the class of the popped route (`T`).
+  ///
+  /// The popped route and the route below it are notified (see [Route.didPop],
+  /// [Route.didComplete], and [Route.didPopNext]). If the [Navigator] has any
+  /// [Navigator.observers], they will be notified as well (see
+  /// [NavigatorObserver.didPop]).
+  ///
+  /// The `T` type argument is the type of the return value of the popped route.
+  ///
+  /// The type of `result`, if provided, must match the type argument of the
+  /// class of the popped route (`T`).
+  /// {@endtemplate}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage for closing a route is as follows:
+  ///
+  /// ```dart
+  /// void _close() {
+  ///   Navigator.pop(context);
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// A dialog box might be closed with a result:
+  ///
+  /// ```dart
+  /// void _accept() {
+  ///   Navigator.pop(context, true); // dialog returns true
+  /// }
+  /// ```
+  @optionalTypeArgs
+  static void pop<T extends Object?>(BuildContext context, [ T? result ]) {
+    Navigator.of(context).pop<T>(result);
+  }
+
+  /// Calls [pop] repeatedly on the navigator that most tightly encloses the
+  /// given context until the predicate returns true.
+  ///
+  /// {@template flutter.widgets.navigator.popUntil}
+  /// The predicate may be applied to the same route more than once if
+  /// [Route.willHandlePopInternally] is true.
+  ///
+  /// To pop until a route with a certain name, use the [RoutePredicate]
+  /// returned from [ModalRoute.withName].
+  ///
+  /// The routes are closed with null as their `return` value.
+  ///
+  /// See [pop] for more details of the semantics of popping a route.
+  /// {@endtemplate}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _logout() {
+  ///   Navigator.popUntil(context, ModalRoute.withName('/login'));
+  /// }
+  /// ```
+  /// {@end-tool}
+  static void popUntil(BuildContext context, RoutePredicate predicate) {
+    Navigator.of(context).popUntil(predicate);
+  }
+
+  /// Immediately remove `route` from the navigator that most tightly encloses
+  /// the given context, and [Route.dispose] it.
+  ///
+  /// {@template flutter.widgets.navigator.removeRoute}
+  /// The removed route is removed without being completed, so this method does
+  /// not take a return value argument. No animations are run as a result of
+  /// this method call.
+  ///
+  /// The routes below and above the removed route are notified (see
+  /// [Route.didChangeNext] and [Route.didChangePrevious]). If the [Navigator]
+  /// has any [Navigator.observers], they will be notified as well (see
+  /// [NavigatorObserver.didRemove]). The removed route is disposed without
+  /// being notified. The future that had been returned from pushing that routes
+  /// will not complete.
+  ///
+  /// The given `route` must be in the history; this method will throw an
+  /// exception if it is not.
+  ///
+  /// Ongoing gestures within the current route are canceled.
+  /// {@endtemplate}
+  ///
+  /// This method is used, for example, to instantly dismiss dropdown menus that
+  /// are up when the screen's orientation changes.
+  static void removeRoute(BuildContext context, Route<dynamic> route) {
+    return Navigator.of(context).removeRoute(route);
+  }
+
+  /// Immediately remove a route from the navigator that most tightly encloses
+  /// the given context, and [Route.dispose] it. The route to be removed is the
+  /// one below the given `anchorRoute`.
+  ///
+  /// {@template flutter.widgets.navigator.removeRouteBelow}
+  /// The removed route is removed without being completed, so this method does
+  /// not take a return value argument. No animations are run as a result of
+  /// this method call.
+  ///
+  /// The routes below and above the removed route are notified (see
+  /// [Route.didChangeNext] and [Route.didChangePrevious]). If the [Navigator]
+  /// has any [Navigator.observers], they will be notified as well (see
+  /// [NavigatorObserver.didRemove]). The removed route is disposed without
+  /// being notified. The future that had been returned from pushing that routes
+  /// will not complete.
+  ///
+  /// The given `anchorRoute` must be in the history and must have a route below
+  /// it; this method will throw an exception if it is not or does not.
+  ///
+  /// Ongoing gestures within the current route are canceled.
+  /// {@endtemplate}
+  static void removeRouteBelow(BuildContext context, Route<dynamic> anchorRoute) {
+    return Navigator.of(context).removeRouteBelow(anchorRoute);
+  }
+
+  /// The state from the closest instance of this class that encloses the given
+  /// context.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// Navigator.of(context)
+  ///   ..pop()
+  ///   ..pop()
+  ///   ..pushNamed('/settings');
+  /// ```
+  ///
+  /// If `rootNavigator` is set to true, the state from the furthest instance of
+  /// this class is given instead. Useful for pushing contents above all
+  /// subsequent instances of [Navigator].
+  ///
+  /// If there is no [Navigator] in the give `context`, this function will throw
+  /// a [FlutterError] in debug mode, and an exception in release mode.
+  static NavigatorState of(
+    BuildContext context, {
+    bool rootNavigator = false,
+  }) {
+    // Handles the case where the input context is a navigator element.
+    NavigatorState? navigator;
+    if (context is StatefulElement && context.state is NavigatorState) {
+        navigator = context.state as NavigatorState;
+    }
+    if (rootNavigator) {
+      navigator = context.findRootAncestorStateOfType<NavigatorState>() ?? navigator;
+    } else {
+      navigator = navigator ?? context.findAncestorStateOfType<NavigatorState>();
+    }
+
+    assert(() {
+      if (navigator == null) {
+        throw FlutterError(
+          'Navigator operation requested with a context that does not include a Navigator.\n'
+          'The context used to push or pop routes from the Navigator must be that of a '
+          'widget that is a descendant of a Navigator widget.'
+        );
+      }
+      return true;
+    }());
+    return navigator!;
+  }
+
+  /// The state from the closest instance of this class that encloses the given
+  /// context, if any.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// NavigatorState? navigatorState = Navigator.maybeOf(context);
+  /// if (navigatorState != null) {
+  ///   navigatorState
+  ///     ..pop()
+  ///     ..pop()
+  ///     ..pushNamed('/settings');
+  /// }
+  /// ```
+  ///
+  /// If `rootNavigator` is set to true, the state from the furthest instance of
+  /// this class is given instead. Useful for pushing contents above all
+  /// subsequent instances of [Navigator].
+  ///
+  /// Will return null if there is no ancestor [Navigator] in the `context`.
+  static NavigatorState? maybeOf(
+      BuildContext context, {
+        bool rootNavigator = false,
+      }) {
+    // Handles the case where the input context is a navigator element.
+    NavigatorState? navigator;
+    if (context is StatefulElement && context.state is NavigatorState) {
+      navigator = context.state as NavigatorState;
+    }
+    if (rootNavigator) {
+      navigator = context.findRootAncestorStateOfType<NavigatorState>() ?? navigator;
+    } else {
+      navigator = navigator ?? context.findAncestorStateOfType<NavigatorState>();
+    }
+    return navigator;
+  }
+
+  /// Turn a route name into a set of [Route] objects.
+  ///
+  /// This is the default value of [onGenerateInitialRoutes], which is used if
+  /// [initialRoute] is not null.
+  ///
+  /// If this string starts with a `/` character and has multiple `/` characters
+  /// in it, then the string is split on those characters and substrings from
+  /// the start of the string up to each such character are, in turn, used as
+  /// routes to push.
+  ///
+  /// For example, if the route `/stocks/HOOLI` was used as the [initialRoute],
+  /// then the [Navigator] would push the following routes on startup: `/`,
+  /// `/stocks`, `/stocks/HOOLI`. This enables deep linking while allowing the
+  /// application to maintain a predictable route history.
+  static List<Route<dynamic>> defaultGenerateInitialRoutes(NavigatorState navigator, String initialRouteName) {
+    final List<Route<dynamic>?> result = <Route<dynamic>?>[];
+    if (initialRouteName.startsWith('/') && initialRouteName.length > 1) {
+      initialRouteName = initialRouteName.substring(1); // strip leading '/'
+      assert(Navigator.defaultRouteName == '/');
+      List<String>? debugRouteNames;
+      assert(() {
+        debugRouteNames = <String>[ Navigator.defaultRouteName ];
+        return true;
+      }());
+      result.add(navigator._routeNamed<dynamic>(Navigator.defaultRouteName, arguments: null, allowNull: true));
+      final List<String> routeParts = initialRouteName.split('/');
+      if (initialRouteName.isNotEmpty) {
+        String routeName = '';
+        for (final String part in routeParts) {
+          routeName += '/$part';
+          assert(() {
+            debugRouteNames!.add(routeName);
+            return true;
+          }());
+          result.add(navigator._routeNamed<dynamic>(routeName, arguments: null, allowNull: true));
+        }
+      }
+      if (result.last == null) {
+        assert(() {
+          FlutterError.reportError(
+            FlutterErrorDetails(
+              exception:
+                'Could not navigate to initial route.\n'
+                'The requested route name was: "/$initialRouteName"\n'
+                'There was no corresponding route in the app, and therefore the initial route specified will be '
+                'ignored and "${Navigator.defaultRouteName}" will be used instead.'
+            ),
+          );
+          return true;
+        }());
+        result.clear();
+      }
+    } else if (initialRouteName != Navigator.defaultRouteName) {
+      // If initialRouteName wasn't '/', then we try to get it with allowNull:true, so that if that fails,
+      // we fall back to '/' (without allowNull:true, see below).
+      result.add(navigator._routeNamed<dynamic>(initialRouteName, arguments: null, allowNull: true));
+    }
+    // Null route might be a result of gap in initialRouteName
+    //
+    // For example, routes = ['A', 'A/B/C'], and initialRouteName = 'A/B/C'
+    // This should result in result = ['A', null,'A/B/C'] where 'A/B' produces
+    // the null. In this case, we want to filter out the null and return
+    // result = ['A', 'A/B/C'].
+    result.removeWhere((Route<dynamic>? route) => route == null);
+    if (result.isEmpty)
+      result.add(navigator._routeNamed<dynamic>(Navigator.defaultRouteName, arguments: null));
+    return result.cast<Route<dynamic>>();
+  }
+
+  @override
+  NavigatorState createState() => NavigatorState();
+}
+
+// The _RouteLifecycle state machine (only goes down):
+//
+//                    [creation of a _RouteEntry]
+//                                 |
+//                                 +
+//                                 |\
+//                                 | \
+//                                 | staging
+//                                 | /
+//                                 |/
+//                    +-+----------+--+-------+
+//                   /  |             |       |
+//                  /   |             |       |
+//                 /    |             |       |
+//                /     |             |       |
+//               /      |             |       |
+//      pushReplace   push*         add*   replace*
+//               \       |            |       |
+//                \      |            |      /
+//                 +--pushing#      adding  /
+//                          \        /     /
+//                           \      /     /
+//                           idle--+-----+
+//                           /  \
+//                          /    \
+//                        pop*  remove*
+//                        /        \
+//                       /       removing#
+//                     popping#       |
+//                      |             |
+//                   [finalizeRoute]  |
+//                              \     |
+//                              dispose*
+//                                 |
+//                                 |
+//                              disposed
+//                                 |
+//                                 |
+//                  [_RouteEntry garbage collected]
+//                          (terminal state)
+//
+// * These states are transient; as soon as _flushHistoryUpdates is run the
+//   route entry will exit that state.
+// # These states await futures or other events, then transition automatically.
+enum _RouteLifecycle {
+  staging, // we will wait for transition delegate to decide what to do with this route.
+  //
+  // routes that are present:
+  //
+  add, // we'll want to run install, didAdd, etc; a route created by onGenerateInitialRoutes or by the initial widget.pages
+  adding, // we'll waiting for the future from didPush of top-most route to complete
+  // routes that are ready for transition.
+  push, // we'll want to run install, didPush, etc; a route added via push() and friends
+  pushReplace, // we'll want to run install, didPush, etc; a route added via pushReplace() and friends
+  pushing, // we're waiting for the future from didPush to complete
+  replace, // we'll want to run install, didReplace, etc; a route added via replace() and friends
+  idle, // route is being harmless
+  //
+  // routes that are not present:
+  //
+  // routes that should be included in route announcement and should still listen to transition changes.
+  pop, // we'll want to call didPop
+  remove, // we'll want to run didReplace/didRemove etc
+  // routes should not be included in route announcement but should still listen to transition changes.
+  popping, // we're waiting for the route to call finalizeRoute to switch to dispose
+  removing, // we are waiting for subsequent routes to be done animating, then will switch to dispose
+  // routes that are completely removed from the navigator and overlay.
+  dispose, // we will dispose the route momentarily
+  disposed, // we have disposed the route
+}
+
+typedef _RouteEntryPredicate = bool Function(_RouteEntry entry);
+
+class _NotAnnounced extends Route<void> {
+  // A placeholder for the lastAnnouncedPreviousRoute, the
+  // lastAnnouncedPoppedNextRoute, and the lastAnnouncedNextRoute before any
+  // change has been announced.
+}
+
+class _RouteEntry extends RouteTransitionRecord {
+  _RouteEntry(
+    this.route, {
+      required _RouteLifecycle initialState,
+      this.restorationInformation,
+    }) : assert(route != null),
+         assert(initialState != null),
+         assert(
+           initialState == _RouteLifecycle.staging ||
+           initialState == _RouteLifecycle.add ||
+           initialState == _RouteLifecycle.push ||
+           initialState == _RouteLifecycle.pushReplace ||
+           initialState == _RouteLifecycle.replace
+         ),
+         currentState = initialState;
+
+  @override
+  final Route<dynamic> route;
+  final _RestorationInformation? restorationInformation;
+
+  static Route<dynamic> notAnnounced = _NotAnnounced();
+
+  _RouteLifecycle currentState;
+  Route<dynamic>? lastAnnouncedPreviousRoute = notAnnounced; // last argument to Route.didChangePrevious
+  Route<dynamic> lastAnnouncedPoppedNextRoute = notAnnounced; // last argument to Route.didPopNext
+  Route<dynamic>? lastAnnouncedNextRoute = notAnnounced; // last argument to Route.didChangeNext
+
+  /// Restoration ID to be used for the encapsulating route when restoration is
+  /// enabled for it or null if restoration cannot be enabled for it.
+  String? get restorationId {
+    // User-provided restoration ids of Pages are prefixed with 'p+'. Generated
+    // ids for pageless routes are prefixed with 'r+' to avoid clashes.
+    if (hasPage) {
+      final Page<Object?> page = route.settings as Page<Object?>;
+      return page.restorationId != null ? 'p+${page.restorationId}' : null;
+    }
+    if (restorationInformation != null) {
+      return 'r+${restorationInformation!.restorationScopeId}';
+    }
+    return null;
+  }
+
+  bool get hasPage => route.settings is Page;
+
+  bool canUpdateFrom(Page<dynamic> page) {
+    if (currentState.index > _RouteLifecycle.idle.index)
+      return false;
+    if (!hasPage)
+      return false;
+    final Page<dynamic> routePage = route.settings as Page<dynamic>;
+    return page.canUpdate(routePage);
+  }
+
+  void handleAdd({ required NavigatorState navigator, required Route<dynamic>? previousPresent }) {
+    assert(currentState == _RouteLifecycle.add);
+    assert(navigator != null);
+    assert(navigator._debugLocked);
+    assert(route._navigator == null);
+    route._navigator = navigator;
+    route.install();
+    assert(route.overlayEntries.isNotEmpty);
+    currentState = _RouteLifecycle.adding;
+    navigator._observedRouteAdditions.add(
+      _NavigatorPushObservation(route, previousPresent)
+    );
+  }
+
+  void handlePush({ required NavigatorState navigator, required bool isNewFirst, required Route<dynamic>? previous, required Route<dynamic>? previousPresent }) {
+    assert(currentState == _RouteLifecycle.push || currentState == _RouteLifecycle.pushReplace || currentState == _RouteLifecycle.replace);
+    assert(navigator != null);
+    assert(navigator._debugLocked);
+    assert(
+      route._navigator == null,
+      'The pushed route has already been used. When pushing a route, a new '
+      'Route object must be provided.',
+    );
+    final _RouteLifecycle previousState = currentState;
+    route._navigator = navigator;
+    route.install();
+    assert(route.overlayEntries.isNotEmpty);
+    if (currentState == _RouteLifecycle.push || currentState == _RouteLifecycle.pushReplace) {
+      final TickerFuture routeFuture = route.didPush();
+      currentState = _RouteLifecycle.pushing;
+      routeFuture.whenCompleteOrCancel(() {
+        if (currentState == _RouteLifecycle.pushing) {
+          currentState = _RouteLifecycle.idle;
+          assert(!navigator._debugLocked);
+          assert(() { navigator._debugLocked = true; return true; }());
+          navigator._flushHistoryUpdates();
+          assert(() { navigator._debugLocked = false; return true; }());
+        }
+      });
+    } else {
+      assert(currentState == _RouteLifecycle.replace);
+      route.didReplace(previous);
+      currentState = _RouteLifecycle.idle;
+    }
+    if (isNewFirst) {
+      route.didChangeNext(null);
+    }
+
+    if (previousState == _RouteLifecycle.replace || previousState == _RouteLifecycle.pushReplace) {
+      navigator._observedRouteAdditions.add(
+        _NavigatorReplaceObservation(route, previousPresent)
+      );
+    } else {
+      assert(previousState == _RouteLifecycle.push);
+      navigator._observedRouteAdditions.add(
+        _NavigatorPushObservation(route, previousPresent)
+      );
+    }
+  }
+
+  void handleDidPopNext(Route<dynamic> poppedRoute) {
+    route.didPopNext(poppedRoute);
+    lastAnnouncedPoppedNextRoute = poppedRoute;
+  }
+
+  void handlePop({ required NavigatorState navigator, required Route<dynamic>? previousPresent }) {
+    assert(navigator != null);
+    assert(navigator._debugLocked);
+    assert(route._navigator == navigator);
+    currentState = _RouteLifecycle.popping;
+    navigator._observedRouteDeletions.add(
+      _NavigatorPopObservation(route, previousPresent)
+    );
+  }
+
+  void handleRemoval({ required NavigatorState navigator, required Route<dynamic>? previousPresent }) {
+    assert(navigator != null);
+    assert(navigator._debugLocked);
+    assert(route._navigator == navigator);
+    currentState = _RouteLifecycle.removing;
+    if (_reportRemovalToObserver) {
+      navigator._observedRouteDeletions.add(
+        _NavigatorRemoveObservation(route, previousPresent)
+      );
+    }
+  }
+
+  bool doingPop = false;
+
+  void didAdd({ required NavigatorState navigator, required bool isNewFirst}) {
+    route.didAdd();
+    currentState = _RouteLifecycle.idle;
+    if (isNewFirst) {
+      route.didChangeNext(null);
+    }
+  }
+
+  void pop<T>(T? result) {
+    assert(isPresent);
+    doingPop = true;
+    if (route.didPop(result) && doingPop) {
+      currentState = _RouteLifecycle.pop;
+    }
+    doingPop = false;
+  }
+
+  bool _reportRemovalToObserver = true;
+
+  // Route is removed without being completed.
+  void remove({ bool isReplaced = false }) {
+    assert(
+      !hasPage || isWaitingForExitingDecision,
+      'A page-based route cannot be completed using imperative api, provide a '
+      'new list without the corresponding Page to Navigator.pages instead. '
+    );
+    if (currentState.index >= _RouteLifecycle.remove.index)
+      return;
+    assert(isPresent);
+    _reportRemovalToObserver = !isReplaced;
+    currentState = _RouteLifecycle.remove;
+  }
+
+  // Route completes with `result` and is removed.
+  void complete<T>(T result, { bool isReplaced = false }) {
+    assert(
+      !hasPage || isWaitingForExitingDecision,
+      'A page-based route cannot be completed using imperative api, provide a '
+      'new list without the corresponding Page to Navigator.pages instead. '
+    );
+    if (currentState.index >= _RouteLifecycle.remove.index)
+      return;
+    assert(isPresent);
+    _reportRemovalToObserver = !isReplaced;
+    route.didComplete(result);
+    assert(route._popCompleter.isCompleted); // implies didComplete was called
+    currentState = _RouteLifecycle.remove;
+  }
+
+  void finalize() {
+    assert(currentState.index < _RouteLifecycle.dispose.index);
+    currentState = _RouteLifecycle.dispose;
+  }
+
+  void dispose() {
+    assert(currentState.index < _RouteLifecycle.disposed.index);
+    currentState = _RouteLifecycle.disposed;
+
+    // If the overlay entries are still mounted, widgets in the route's subtree
+    // may still reference resources from the route and we delay disposal of
+    // the route until the overlay entries are no longer mounted.
+    // Since the overlay entry is the root of the route's subtree it will only
+    // get unmounted after every other widget in the subtree has been unmounted.
+
+    final Iterable<OverlayEntry> mountedEntries = route.overlayEntries.where((OverlayEntry e) => e.mounted);
+
+    if (mountedEntries.isEmpty) {
+      route.dispose();
+    } else {
+      int mounted = mountedEntries.length;
+      assert(mounted > 0);
+      for (final OverlayEntry entry in mountedEntries) {
+        late VoidCallback listener;
+        listener = () {
+          assert(mounted > 0);
+          assert(!entry.mounted);
+          mounted--;
+          entry.removeListener(listener);
+          if (mounted == 0) {
+            assert(route.overlayEntries.every((OverlayEntry e) => !e.mounted));
+            route.dispose();
+          }
+        };
+        entry.addListener(listener);
+      }
+    }
+  }
+
+  bool get willBePresent {
+    return currentState.index <= _RouteLifecycle.idle.index &&
+           currentState.index >= _RouteLifecycle.add.index;
+  }
+
+  bool get isPresent {
+    return currentState.index <= _RouteLifecycle.remove.index &&
+           currentState.index >= _RouteLifecycle.add.index;
+  }
+
+  bool get isPresentForRestoration => currentState.index <= _RouteLifecycle.idle.index;
+
+  bool get suitableForAnnouncement {
+    return currentState.index <= _RouteLifecycle.removing.index &&
+           currentState.index >= _RouteLifecycle.push.index;
+  }
+
+  bool get suitableForTransitionAnimation {
+    return currentState.index <= _RouteLifecycle.remove.index &&
+           currentState.index >= _RouteLifecycle.push.index;
+  }
+
+  bool shouldAnnounceChangeToNext(Route<dynamic>? nextRoute) {
+    assert(nextRoute != lastAnnouncedNextRoute);
+    // Do not announce if `next` changes from a just popped route to null. We
+    // already announced this change by calling didPopNext.
+    return !(
+      nextRoute == null &&
+        lastAnnouncedPoppedNextRoute != null &&
+        lastAnnouncedPoppedNextRoute == lastAnnouncedNextRoute
+    );
+  }
+
+  static final _RouteEntryPredicate isPresentPredicate = (_RouteEntry entry) => entry.isPresent;
+  static final _RouteEntryPredicate suitableForTransitionAnimationPredicate = (_RouteEntry entry) => entry.suitableForTransitionAnimation;
+  static final _RouteEntryPredicate willBePresentPredicate = (_RouteEntry entry) => entry.willBePresent;
+
+  static _RouteEntryPredicate isRoutePredicate(Route<dynamic> route) {
+    return (_RouteEntry entry) => entry.route == route;
+  }
+
+  @override
+  bool get isWaitingForEnteringDecision => currentState == _RouteLifecycle.staging;
+
+  @override
+  bool get isWaitingForExitingDecision => _isWaitingForExitingDecision;
+  bool _isWaitingForExitingDecision = false;
+
+  void markNeedsExitingDecision() => _isWaitingForExitingDecision = true;
+
+  @override
+  void markForPush() {
+    assert(
+      isWaitingForEnteringDecision && !isWaitingForExitingDecision,
+      'This route cannot be marked for push. Either a decision has already been '
+      'made or it does not require an explicit decision on how to transition in.'
+    );
+    currentState = _RouteLifecycle.push;
+  }
+
+  @override
+  void markForAdd() {
+    assert(
+      isWaitingForEnteringDecision && !isWaitingForExitingDecision,
+      'This route cannot be marked for add. Either a decision has already been '
+      'made or it does not require an explicit decision on how to transition in.'
+    );
+    currentState = _RouteLifecycle.add;
+  }
+
+  @override
+  void markForPop([dynamic result]) {
+    assert(
+      !isWaitingForEnteringDecision && isWaitingForExitingDecision && isPresent,
+      'This route cannot be marked for pop. Either a decision has already been '
+      'made or it does not require an explicit decision on how to transition out.'
+    );
+    pop<dynamic>(result);
+    _isWaitingForExitingDecision = false;
+  }
+
+  @override
+  void markForComplete([dynamic result]) {
+    assert(
+      !isWaitingForEnteringDecision && isWaitingForExitingDecision && isPresent,
+      'This route cannot be marked for complete. Either a decision has already '
+      'been made or it does not require an explicit decision on how to transition '
+      'out.'
+    );
+    complete<dynamic>(result);
+    _isWaitingForExitingDecision = false;
+  }
+
+  @override
+  void markForRemove() {
+    assert(
+      !isWaitingForEnteringDecision && isWaitingForExitingDecision && isPresent,
+      'This route cannot be marked for remove. Either a decision has already '
+      'been made or it does not require an explicit decision on how to transition '
+      'out.'
+    );
+    remove();
+    _isWaitingForExitingDecision = false;
+  }
+
+  bool get restorationEnabled => route.restorationScopeId.value != null;
+  set restorationEnabled(bool value) {
+    assert(!value || restorationId != null);
+    route._updateRestorationId(value ? restorationId : null);
+  }
+}
+
+abstract class _NavigatorObservation {
+  _NavigatorObservation(
+    this.primaryRoute,
+    this.secondaryRoute,
+  );
+  final Route<dynamic> primaryRoute;
+  final Route<dynamic>? secondaryRoute;
+
+  void notify(NavigatorObserver observer);
+}
+
+class _NavigatorPushObservation extends _NavigatorObservation {
+  _NavigatorPushObservation(
+    Route<dynamic> primaryRoute,
+    Route<dynamic>? secondaryRoute
+  ) : super(primaryRoute, secondaryRoute);
+
+  @override
+  void notify(NavigatorObserver observer) {
+    observer.didPush(primaryRoute, secondaryRoute);
+  }
+}
+
+class _NavigatorPopObservation extends _NavigatorObservation {
+  _NavigatorPopObservation(
+    Route<dynamic> primaryRoute,
+    Route<dynamic>? secondaryRoute
+  ) : super(primaryRoute, secondaryRoute);
+
+  @override
+  void notify(NavigatorObserver observer) {
+    observer.didPop(primaryRoute, secondaryRoute);
+  }
+}
+
+class _NavigatorRemoveObservation extends _NavigatorObservation {
+  _NavigatorRemoveObservation(
+    Route<dynamic> primaryRoute,
+    Route<dynamic>? secondaryRoute
+  ) : super(primaryRoute, secondaryRoute);
+
+  @override
+  void notify(NavigatorObserver observer) {
+    observer.didRemove(primaryRoute, secondaryRoute);
+  }
+}
+
+class _NavigatorReplaceObservation extends _NavigatorObservation {
+  _NavigatorReplaceObservation(
+    Route<dynamic> primaryRoute,
+    Route<dynamic>? secondaryRoute
+  ) : super(primaryRoute, secondaryRoute);
+
+  @override
+  void notify(NavigatorObserver observer) {
+    observer.didReplace(newRoute: primaryRoute, oldRoute: secondaryRoute);
+  }
+}
+
+/// The state for a [Navigator] widget.
+///
+/// A reference to this class can be obtained by calling [Navigator.of].
+class NavigatorState extends State<Navigator> with TickerProviderStateMixin, RestorationMixin {
+  late GlobalKey<OverlayState> _overlayKey;
+  List<_RouteEntry> _history = <_RouteEntry>[];
+  final _HistoryProperty _serializableHistory = _HistoryProperty();
+  final Queue<_NavigatorObservation> _observedRouteAdditions = Queue<_NavigatorObservation>();
+  final Queue<_NavigatorObservation> _observedRouteDeletions = Queue<_NavigatorObservation>();
+
+  /// The [FocusScopeNode] for the [FocusScope] that encloses the routes.
+  final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'Navigator Scope');
+
+  bool _debugLocked = false; // used to prevent re-entrant calls to push, pop, and friends
+
+  HeroController? _heroControllerFromScope;
+
+  late List<NavigatorObserver> _effectiveObservers;
+
+  @override
+  void initState() {
+    super.initState();
+    assert(
+      widget.pages.isEmpty || widget.onPopPage != null,
+      'The Navigator.onPopPage must be provided to use the Navigator.pages API',
+    );
+    for (final NavigatorObserver observer in widget.observers) {
+      assert(observer.navigator == null);
+      observer._navigator = this;
+    }
+    _effectiveObservers = widget.observers;
+
+    // We have to manually extract the inherited widget in initState because
+    // the current context is not fully initialized.
+    final HeroControllerScope? heroControllerScope = context
+      .getElementForInheritedWidgetOfExactType<HeroControllerScope>()
+      ?.widget as HeroControllerScope?;
+    _updateHeroController(heroControllerScope?.controller);
+  }
+
+  // Use [_nextPagelessRestorationScopeId] to get the next id.
+  final RestorableNum<int> _rawNextPagelessRestorationScopeId = RestorableNum<int>(0);
+
+  int get _nextPagelessRestorationScopeId => _rawNextPagelessRestorationScopeId.value++;
+
+  @override
+  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
+    registerForRestoration(_rawNextPagelessRestorationScopeId, 'id');
+    registerForRestoration(_serializableHistory, 'history');
+
+    // Delete everything in the old history and clear the overlay.
+    while (_history.isNotEmpty) {
+      _history.removeLast().dispose();
+    }
+    assert(_history.isEmpty);
+    _overlayKey = GlobalKey<OverlayState>();
+
+    // Populate the new history from restoration data.
+    _history.addAll(_serializableHistory.restoreEntriesForPage(null, this));
+    for (final Page<dynamic> page in widget.pages) {
+      final _RouteEntry entry = _RouteEntry(
+        page.createRoute(context),
+        initialState: _RouteLifecycle.add,
+      );
+      assert(
+        entry.route.settings == page,
+        'The settings getter of a page-based Route must return a Page object. '
+        'Please set the settings to the Page in the Page.createRoute method.'
+      );
+      _history.add(entry);
+      _history.addAll(_serializableHistory.restoreEntriesForPage(entry, this));
+    }
+
+    // If there was nothing to restore, we need to process the initial route.
+    if (!_serializableHistory.hasData) {
+      String? initialRoute = widget.initialRoute;
+      if (widget.pages.isEmpty) {
+        initialRoute = initialRoute ?? Navigator.defaultRouteName;
+      }
+      if (initialRoute != null) {
+        _history.addAll(
+          widget.onGenerateInitialRoutes(
+            this,
+            widget.initialRoute ?? Navigator.defaultRouteName,
+          ).map((Route<dynamic> route) => _RouteEntry(
+              route,
+              initialState: _RouteLifecycle.add,
+              restorationInformation: route.settings.name != null
+                ? _RestorationInformation.named(
+                  name: route.settings.name!,
+                  arguments: null,
+                  restorationScopeId: _nextPagelessRestorationScopeId,
+                )
+                : null,
+            ),
+          ),
+        );
+      }
+    }
+
+    assert(!_debugLocked);
+    assert(() { _debugLocked = true; return true; }());
+    _flushHistoryUpdates();
+    assert(() { _debugLocked = false; return true; }());
+  }
+
+  @override
+  void didToggleBucket(RestorationBucket? oldBucket) {
+    super.didToggleBucket(oldBucket);
+    if (bucket != null) {
+      _serializableHistory.update(_history);
+    } else {
+      _serializableHistory.clear();
+    }
+  }
+  @override
+  String? get restorationId => widget.restorationScopeId;
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _updateHeroController(HeroControllerScope.of(context));
+  }
+
+  void _updateHeroController(HeroController? newHeroController) {
+    if (_heroControllerFromScope != newHeroController) {
+      if (newHeroController != null) {
+        // Makes sure the same hero controller is not shared between two navigators.
+        assert(() {
+          // It is possible that the hero controller subscribes to an existing
+          // navigator. We are fine as long as that navigator gives up the hero
+          // controller at the end of the build.
+          if (newHeroController.navigator != null) {
+            final NavigatorState previousOwner = newHeroController.navigator!;
+            ServicesBinding.instance!.addPostFrameCallback((Duration timestamp) {
+              // We only check if this navigator still owns the hero controller.
+              if (_heroControllerFromScope == newHeroController) {
+                assert(_heroControllerFromScope!._navigator == this);
+                assert(previousOwner._heroControllerFromScope != newHeroController);
+              }
+            });
+          }
+          return true;
+        }());
+        newHeroController._navigator = this;
+      }
+      // Only unsubscribe the hero controller when it is currently subscribe to
+      // this navigator.
+      if (_heroControllerFromScope?._navigator == this)
+        _heroControllerFromScope?._navigator = null;
+      _heroControllerFromScope = newHeroController;
+      _updateEffectiveObservers();
+    }
+  }
+
+  void _updateEffectiveObservers() {
+    if (_heroControllerFromScope != null)
+      _effectiveObservers = widget.observers + <NavigatorObserver>[_heroControllerFromScope!];
+    else
+      _effectiveObservers = widget.observers;
+  }
+
+  @override
+  void didUpdateWidget(Navigator oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    assert(
+      widget.pages.isEmpty || widget.onPopPage != null,
+      'The Navigator.onPopPage must be provided to use the Navigator.pages API',
+    );
+    if (oldWidget.observers != widget.observers) {
+      for (final NavigatorObserver observer in oldWidget.observers)
+        observer._navigator = null;
+      for (final NavigatorObserver observer in widget.observers) {
+        assert(observer.navigator == null);
+        observer._navigator = this;
+      }
+      _updateEffectiveObservers();
+    }
+    if (oldWidget.pages != widget.pages && !restorePending) {
+      assert(
+        widget.pages.isNotEmpty,
+        'To use the Navigator.pages, there must be at least one page in the list.'
+      );
+      _updatePages();
+    }
+
+    for (final _RouteEntry entry in _history)
+      entry.route.changedExternalState();
+  }
+
+  void _debugCheckDuplicatedPageKeys() {
+    assert((){
+      final Set<Key> keyReservation = <Key>{};
+      for (final Page<dynamic> page in widget.pages) {
+        final LocalKey? key = page.key;
+        if (key != null) {
+          assert(!keyReservation.contains(key));
+          keyReservation.add(key);
+        }
+      }
+      return true;
+    }());
+  }
+
+  @override
+  void dispose() {
+    assert(!_debugLocked);
+    assert(() {
+      _debugLocked = true;
+      return true;
+    }());
+    _updateHeroController(null);
+    for (final NavigatorObserver observer in _effectiveObservers)
+      observer._navigator = null;
+    focusScopeNode.dispose();
+    for (final _RouteEntry entry in _history)
+      entry.dispose();
+    super.dispose();
+    // don't unlock, so that the object becomes unusable
+    assert(_debugLocked);
+  }
+
+  /// The overlay this navigator uses for its visual presentation.
+  OverlayState? get overlay => _overlayKey.currentState;
+
+  Iterable<OverlayEntry> get _allRouteOverlayEntries sync* {
+    for (final _RouteEntry entry in _history)
+      yield* entry.route.overlayEntries;
+  }
+
+  String? _lastAnnouncedRouteName;
+
+  bool _debugUpdatingPage = false;
+  void _updatePages() {
+    assert(() {
+      assert(!_debugUpdatingPage);
+      _debugCheckDuplicatedPageKeys();
+      _debugUpdatingPage = true;
+      return true;
+    }());
+
+    // This attempts to diff the new pages list (widget.pages) with
+    // the old _RouteEntry[s] list (_history), and produces a new list of
+    // _RouteEntry[s] to be the new list of _history. This method roughly
+    // follows the same outline of RenderObjectElement.updateChildren.
+    //
+    // The cases it tries to optimize for are:
+    //  - the old list is empty
+    //  - All the pages in the new list can match the page-based routes in the old
+    //    list, and their orders are the same.
+    //  - there is an insertion or removal of one or more page-based route in
+    //    only one place in the list
+    // If a page-based route with a key is in both lists, it will be synced.
+    // Page-based routes without keys might be synced but there is no guarantee.
+
+    // The general approach is to sync the entire new list backwards, as follows:
+    // 1. Walk the lists from the bottom, syncing nodes, and record pageless routes,
+    //    until you no longer have matching nodes.
+    // 2. Walk the lists from the top, without syncing nodes, until you no
+    //    longer have matching nodes. We'll sync these nodes at the end. We
+    //    don't sync them now because we want to sync all the nodes in order
+    //    from beginning to end.
+    // At this point we narrowed the old and new lists to the point
+    // where the nodes no longer match.
+    // 3. Walk the narrowed part of the old list to get the list of
+    //    keys.
+    // 4. Walk the narrowed part of the new list forwards:
+    //     * Create a new _RouteEntry for non-keyed items and record them for
+    //       transitionDelegate.
+    //     * Sync keyed items with the source if it exists.
+    // 5. Walk the narrowed part of the old list again to records the
+    //    _RouteEntry[s], as well as pageless routes, needed to be removed for
+    //    transitionDelegate.
+    // 5. Walk the top of the list again, syncing the nodes and recording
+    //    pageless routes.
+    // 6. Use transitionDelegate for explicit decisions on how _RouteEntry[s]
+    //    transition in or off the screens.
+    // 7. Fill pageless routes back into the new history.
+
+    bool needsExplicitDecision = false;
+    int newPagesBottom = 0;
+    int oldEntriesBottom = 0;
+    int newPagesTop = widget.pages.length - 1;
+    int oldEntriesTop = _history.length - 1;
+
+    final List<_RouteEntry> newHistory = <_RouteEntry>[];
+    final Map<_RouteEntry?, List<_RouteEntry>> pageRouteToPagelessRoutes = <_RouteEntry?, List<_RouteEntry>>{};
+
+    // Updates the bottom of the list.
+    _RouteEntry? previousOldPageRouteEntry;
+    while (oldEntriesBottom <= oldEntriesTop) {
+      final _RouteEntry oldEntry = _history[oldEntriesBottom];
+      assert(oldEntry != null && oldEntry.currentState != _RouteLifecycle.disposed);
+      // Records pageless route. The bottom most pageless routes will be
+      // stored in key = null.
+      if (!oldEntry.hasPage) {
+        final List<_RouteEntry> pagelessRoutes = pageRouteToPagelessRoutes.putIfAbsent(
+          previousOldPageRouteEntry,
+          () => <_RouteEntry>[],
+        );
+        pagelessRoutes.add(oldEntry);
+        oldEntriesBottom += 1;
+        continue;
+      }
+      if (newPagesBottom > newPagesTop)
+        break;
+      final Page<dynamic> newPage = widget.pages[newPagesBottom];
+      if (!oldEntry.canUpdateFrom(newPage))
+        break;
+      previousOldPageRouteEntry = oldEntry;
+      oldEntry.route._updateSettings(newPage);
+      newHistory.add(oldEntry);
+      newPagesBottom += 1;
+      oldEntriesBottom += 1;
+    }
+
+    int pagelessRoutesToSkip = 0;
+    // Scans the top of the list until we found a page-based route that cannot be
+    // updated.
+    while ((oldEntriesBottom <= oldEntriesTop) && (newPagesBottom <= newPagesTop)) {
+      final _RouteEntry oldEntry = _history[oldEntriesTop];
+      assert(oldEntry != null && oldEntry.currentState != _RouteLifecycle.disposed);
+      if (!oldEntry.hasPage) {
+        // This route might need to be skipped if we can not find a page above.
+        pagelessRoutesToSkip += 1;
+        oldEntriesTop -= 1;
+        continue;
+      }
+      final Page<dynamic> newPage = widget.pages[newPagesTop];
+      if (!oldEntry.canUpdateFrom(newPage))
+        break;
+      // We found the page for all the consecutive pageless routes below. Those
+      // pageless routes do not need to be skipped.
+      pagelessRoutesToSkip = 0;
+      oldEntriesTop -= 1;
+      newPagesTop -= 1;
+    }
+    // Reverts the pageless routes that cannot be updated.
+    oldEntriesTop += pagelessRoutesToSkip;
+
+    // Scans middle of the old entries and records the page key to old entry map.
+    int oldEntriesBottomToScan = oldEntriesBottom;
+    final Map<LocalKey, _RouteEntry> pageKeyToOldEntry = <LocalKey, _RouteEntry>{};
+    while (oldEntriesBottomToScan <= oldEntriesTop) {
+      final _RouteEntry oldEntry = _history[oldEntriesBottomToScan];
+      oldEntriesBottomToScan += 1;
+      assert(
+        oldEntry != null &&
+        oldEntry.currentState != _RouteLifecycle.disposed
+      );
+      // Pageless routes will be recorded when we update the middle of the old
+      // list.
+      if (!oldEntry.hasPage)
+        continue;
+
+      assert(oldEntry.hasPage);
+
+      final Page<dynamic> page = oldEntry.route.settings as Page<dynamic>;
+      if (page.key == null)
+        continue;
+
+      assert(!pageKeyToOldEntry.containsKey(page.key));
+      pageKeyToOldEntry[page.key!] = oldEntry;
+    }
+
+    // Updates the middle of the list.
+    while (newPagesBottom <= newPagesTop) {
+      final Page<dynamic> nextPage = widget.pages[newPagesBottom];
+      newPagesBottom += 1;
+      if (
+        nextPage.key == null ||
+        !pageKeyToOldEntry.containsKey(nextPage.key) ||
+        !pageKeyToOldEntry[nextPage.key]!.canUpdateFrom(nextPage)
+      ) {
+        // There is no matching key in the old history, we need to create a new
+        // route and wait for the transition delegate to decide how to add
+        // it into the history.
+        final _RouteEntry newEntry = _RouteEntry(
+          nextPage.createRoute(context),
+          initialState: _RouteLifecycle.staging,
+        );
+        needsExplicitDecision = true;
+        assert(
+          newEntry.route.settings == nextPage,
+          'The settings getter of a page-based Route must return a Page object. '
+          'Please set the settings to the Page in the Page.createRoute method.'
+        );
+        newHistory.add(newEntry);
+      } else {
+        // Removes the key from pageKeyToOldEntry to indicate it is taken.
+        final _RouteEntry matchingEntry = pageKeyToOldEntry.remove(nextPage.key)!;
+        assert(matchingEntry.canUpdateFrom(nextPage));
+        matchingEntry.route._updateSettings(nextPage);
+        newHistory.add(matchingEntry);
+      }
+    }
+
+    // Any remaining old routes that do not have a match will need to be removed.
+    final Map<RouteTransitionRecord?, RouteTransitionRecord> locationToExitingPageRoute = <RouteTransitionRecord?, RouteTransitionRecord>{};
+    while (oldEntriesBottom <= oldEntriesTop) {
+      final _RouteEntry potentialEntryToRemove = _history[oldEntriesBottom];
+      oldEntriesBottom += 1;
+
+      if (!potentialEntryToRemove.hasPage) {
+        assert(previousOldPageRouteEntry != null);
+        final List<_RouteEntry> pagelessRoutes = pageRouteToPagelessRoutes
+          .putIfAbsent(
+            previousOldPageRouteEntry,
+            () => <_RouteEntry>[],
+          );
+        pagelessRoutes.add(potentialEntryToRemove);
+        if (previousOldPageRouteEntry!.isWaitingForExitingDecision && potentialEntryToRemove.isPresent)
+          potentialEntryToRemove.markNeedsExitingDecision();
+        continue;
+      }
+
+      final Page<dynamic> potentialPageToRemove = potentialEntryToRemove.route.settings as Page<dynamic>;
+      // Marks for transition delegate to remove if this old page does not have
+      // a key or was not taken during updating the middle of new page.
+      if (
+        potentialPageToRemove.key == null ||
+        pageKeyToOldEntry.containsKey(potentialPageToRemove.key)
+      ) {
+        locationToExitingPageRoute[previousOldPageRouteEntry] = potentialEntryToRemove;
+        // We only need a decision if it has not already been popped.
+        if (potentialEntryToRemove.isPresent)
+          potentialEntryToRemove.markNeedsExitingDecision();
+      }
+      previousOldPageRouteEntry = potentialEntryToRemove;
+    }
+
+    // We've scanned the whole list.
+    assert(oldEntriesBottom == oldEntriesTop + 1);
+    assert(newPagesBottom == newPagesTop + 1);
+    newPagesTop = widget.pages.length - 1;
+    oldEntriesTop = _history.length - 1;
+    // Verifies we either reach the bottom or the oldEntriesBottom must be updatable
+    // by newPagesBottom.
+    assert(() {
+      if (oldEntriesBottom <= oldEntriesTop)
+        return newPagesBottom <= newPagesTop &&
+          _history[oldEntriesBottom].hasPage &&
+          _history[oldEntriesBottom].canUpdateFrom(widget.pages[newPagesBottom]);
+      else
+        return newPagesBottom > newPagesTop;
+    }());
+
+    // Updates the top of the list.
+    while ((oldEntriesBottom <= oldEntriesTop) && (newPagesBottom <= newPagesTop)) {
+      final _RouteEntry oldEntry = _history[oldEntriesBottom];
+      assert(oldEntry != null && oldEntry.currentState != _RouteLifecycle.disposed);
+      if (!oldEntry.hasPage) {
+        assert(previousOldPageRouteEntry != null);
+        final List<_RouteEntry> pagelessRoutes = pageRouteToPagelessRoutes
+          .putIfAbsent(
+          previousOldPageRouteEntry,
+            () => <_RouteEntry>[]
+        );
+        pagelessRoutes.add(oldEntry);
+        continue;
+      }
+      previousOldPageRouteEntry = oldEntry;
+      final Page<dynamic> newPage = widget.pages[newPagesBottom];
+      assert(oldEntry.canUpdateFrom(newPage));
+      oldEntry.route._updateSettings(newPage);
+      newHistory.add(oldEntry);
+      oldEntriesBottom += 1;
+      newPagesBottom += 1;
+    }
+
+    // Finally, uses transition delegate to make explicit decision if needed.
+    needsExplicitDecision = needsExplicitDecision || locationToExitingPageRoute.isNotEmpty;
+    Iterable<_RouteEntry> results = newHistory;
+    if (needsExplicitDecision) {
+      results = widget.transitionDelegate._transition(
+        newPageRouteHistory: newHistory,
+        locationToExitingPageRoute: locationToExitingPageRoute,
+        pageRouteToPagelessRoutes: pageRouteToPagelessRoutes,
+      ).cast<_RouteEntry>();
+    }
+    _history = <_RouteEntry>[];
+    // Adds the leading pageless routes if there is any.
+    if (pageRouteToPagelessRoutes.containsKey(null)) {
+      _history.addAll(pageRouteToPagelessRoutes[null]!);
+    }
+    for (final _RouteEntry result in results) {
+      _history.add(result);
+      if (pageRouteToPagelessRoutes.containsKey(result)) {
+        _history.addAll(pageRouteToPagelessRoutes[result]!);
+      }
+    }
+    assert(() {_debugUpdatingPage = false; return true;}());
+    assert(() { _debugLocked = true; return true; }());
+    _flushHistoryUpdates();
+    assert(() { _debugLocked = false; return true; }());
+  }
+
+  void _flushHistoryUpdates({bool rearrangeOverlay = true}) {
+    assert(_debugLocked && !_debugUpdatingPage);
+    // Clean up the list, sending updates to the routes that changed. Notably,
+    // we don't send the didChangePrevious/didChangeNext updates to those that
+    // did not change at this point, because we're not yet sure exactly what the
+    // routes will be at the end of the day (some might get disposed).
+    int index = _history.length - 1;
+    _RouteEntry? next;
+    _RouteEntry? entry = _history[index];
+    _RouteEntry? previous = index > 0 ? _history[index - 1] : null;
+    bool canRemoveOrAdd = false; // Whether there is a fully opaque route on top to silently remove or add route underneath.
+    Route<dynamic>? poppedRoute; // The route that should trigger didPopNext on the top active route.
+    bool seenTopActiveRoute = false; // Whether we've seen the route that would get didPopNext.
+    final List<_RouteEntry> toBeDisposed = <_RouteEntry>[];
+    while (index >= 0) {
+      switch (entry!.currentState) {
+        case _RouteLifecycle.add:
+          assert(rearrangeOverlay);
+          entry.handleAdd(
+            navigator: this,
+            previousPresent: _getRouteBefore(index - 1, _RouteEntry.isPresentPredicate)?.route,
+          );
+          assert(entry.currentState == _RouteLifecycle.adding);
+          continue;
+        case _RouteLifecycle.adding:
+          if (canRemoveOrAdd || next == null) {
+            entry.didAdd(
+              navigator: this,
+              isNewFirst: next == null
+            );
+            assert(entry.currentState == _RouteLifecycle.idle);
+            continue;
+          }
+          break;
+        case _RouteLifecycle.push:
+        case _RouteLifecycle.pushReplace:
+        case _RouteLifecycle.replace:
+          assert(rearrangeOverlay);
+          entry.handlePush(
+            navigator: this,
+            previous: previous?.route,
+            previousPresent: _getRouteBefore(index - 1, _RouteEntry.isPresentPredicate)?.route,
+            isNewFirst: next == null,
+          );
+          assert(entry.currentState != _RouteLifecycle.push);
+          assert(entry.currentState != _RouteLifecycle.pushReplace);
+          assert(entry.currentState != _RouteLifecycle.replace);
+          if (entry.currentState == _RouteLifecycle.idle) {
+            continue;
+          }
+          break;
+        case _RouteLifecycle.pushing: // Will exit this state when animation completes.
+          if (!seenTopActiveRoute && poppedRoute != null)
+            entry.handleDidPopNext(poppedRoute);
+          seenTopActiveRoute = true;
+          break;
+        case _RouteLifecycle.idle:
+          if (!seenTopActiveRoute && poppedRoute != null)
+            entry.handleDidPopNext(poppedRoute);
+          seenTopActiveRoute = true;
+          // This route is idle, so we are allowed to remove subsequent (earlier)
+          // routes that are waiting to be removed silently:
+          canRemoveOrAdd = true;
+          break;
+        case _RouteLifecycle.pop:
+          if (!seenTopActiveRoute) {
+            if (poppedRoute != null)
+              entry.handleDidPopNext(poppedRoute);
+            poppedRoute = entry.route;
+          }
+          entry.handlePop(
+            navigator: this,
+            previousPresent: _getRouteBefore(index, _RouteEntry.willBePresentPredicate)?.route,
+          );
+          assert(entry.currentState == _RouteLifecycle.popping);
+          canRemoveOrAdd = true;
+          break;
+        case _RouteLifecycle.popping:
+          // Will exit this state when animation completes.
+          break;
+        case _RouteLifecycle.remove:
+          if (!seenTopActiveRoute) {
+            if (poppedRoute != null)
+              entry.route.didPopNext(poppedRoute);
+            poppedRoute = null;
+          }
+          entry.handleRemoval(
+            navigator: this,
+            previousPresent: _getRouteBefore(index, _RouteEntry.willBePresentPredicate)?.route,
+          );
+          assert(entry.currentState == _RouteLifecycle.removing);
+          continue;
+        case _RouteLifecycle.removing:
+          if (!canRemoveOrAdd && next != null) {
+            // We aren't allowed to remove this route yet.
+            break;
+          }
+          entry.currentState = _RouteLifecycle.dispose;
+          continue;
+        case _RouteLifecycle.dispose:
+          // Delay disposal until didChangeNext/didChangePrevious have been sent.
+          toBeDisposed.add(_history.removeAt(index));
+          entry = next;
+          break;
+        case _RouteLifecycle.disposed:
+        case _RouteLifecycle.staging:
+          assert(false);
+          break;
+      }
+      index -= 1;
+      next = entry;
+      entry = previous;
+      previous = index > 0 ? _history[index - 1] : null;
+    }
+
+    // Informs navigator observers about route changes.
+    _flushObserverNotifications();
+
+    // Now that the list is clean, send the didChangeNext/didChangePrevious
+    // notifications.
+    _flushRouteAnnouncement();
+
+    // Announces route name changes.
+    if (widget.reportsRouteUpdateToEngine) {
+      final _RouteEntry? lastEntry = _history.cast<_RouteEntry?>().lastWhere(
+        (_RouteEntry? e) => e != null && _RouteEntry.isPresentPredicate(e), orElse: () => null);
+      final String? routeName = lastEntry?.route.settings.name;
+      if (routeName != _lastAnnouncedRouteName) {
+        SystemNavigator.routeUpdated(
+          routeName: routeName,
+          previousRouteName: _lastAnnouncedRouteName
+        );
+        _lastAnnouncedRouteName = routeName;
+      }
+    }
+
+    // Lastly, removes the overlay entries of all marked entries and disposes
+    // them.
+    for (final _RouteEntry entry in toBeDisposed) {
+      for (final OverlayEntry overlayEntry in entry.route.overlayEntries)
+        overlayEntry.remove();
+      entry.dispose();
+    }
+    if (rearrangeOverlay) {
+      overlay?.rearrange(_allRouteOverlayEntries);
+    }
+    if (bucket != null) {
+      _serializableHistory.update(_history);
+    }
+  }
+
+  void _flushObserverNotifications() {
+    if (_effectiveObservers.isEmpty) {
+      _observedRouteDeletions.clear();
+      _observedRouteAdditions.clear();
+      return;
+    }
+    while (_observedRouteAdditions.isNotEmpty) {
+      final _NavigatorObservation observation = _observedRouteAdditions.removeLast();
+      _effectiveObservers.forEach(observation.notify);
+    }
+
+    while (_observedRouteDeletions.isNotEmpty) {
+      final _NavigatorObservation observation = _observedRouteDeletions.removeFirst();
+      _effectiveObservers.forEach(observation.notify);
+    }
+  }
+
+  void _flushRouteAnnouncement() {
+    int index = _history.length - 1;
+    while (index >= 0) {
+      final _RouteEntry entry = _history[index];
+      if (!entry.suitableForAnnouncement) {
+        index -= 1;
+        continue;
+      }
+      final _RouteEntry? next = _getRouteAfter(index + 1, _RouteEntry.suitableForTransitionAnimationPredicate);
+
+      if (next?.route != entry.lastAnnouncedNextRoute) {
+        if (entry.shouldAnnounceChangeToNext(next?.route)) {
+          entry.route.didChangeNext(next?.route);
+        }
+        entry.lastAnnouncedNextRoute = next?.route;
+      }
+      final _RouteEntry? previous = _getRouteBefore(index - 1, _RouteEntry.suitableForTransitionAnimationPredicate);
+      if (previous?.route != entry.lastAnnouncedPreviousRoute) {
+        entry.route.didChangePrevious(previous?.route);
+        entry.lastAnnouncedPreviousRoute = previous?.route;
+      }
+      index -= 1;
+    }
+  }
+
+  _RouteEntry? _getRouteBefore(int index, _RouteEntryPredicate predicate) {
+    index = _getIndexBefore(index, predicate);
+    return index >= 0 ? _history[index] : null;
+  }
+
+  int _getIndexBefore(int index, _RouteEntryPredicate predicate) {
+    while(index >= 0 && !predicate(_history[index])) {
+      index -= 1;
+    }
+    return index;
+  }
+
+  _RouteEntry? _getRouteAfter(int index, _RouteEntryPredicate predicate) {
+    while (index < _history.length && !predicate(_history[index])) {
+      index += 1;
+    }
+    return index < _history.length ? _history[index] : null;
+  }
+
+  Route<T>? _routeNamed<T>(String name, { required Object? arguments, bool allowNull = false }) {
+    assert(!_debugLocked);
+    assert(name != null);
+    if (allowNull && widget.onGenerateRoute == null)
+      return null;
+    assert(() {
+      if (widget.onGenerateRoute == null) {
+        throw FlutterError(
+          'Navigator.onGenerateRoute was null, but the route named "$name" was referenced.\n'
+          'To use the Navigator API with named routes (pushNamed, pushReplacementNamed, or '
+          'pushNamedAndRemoveUntil), the Navigator must be provided with an '
+          'onGenerateRoute handler.\n'
+          'The Navigator was:\n'
+          '  $this'
+        );
+      }
+      return true;
+    }());
+    final RouteSettings settings = RouteSettings(
+      name: name,
+      arguments: arguments,
+    );
+    Route<T>? route = widget.onGenerateRoute!(settings) as Route<T>?;
+    if (route == null && !allowNull) {
+      assert(() {
+        if (widget.onUnknownRoute == null) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('Navigator.onGenerateRoute returned null when requested to build route "$name".'),
+            ErrorDescription(
+              'The onGenerateRoute callback must never return null, unless an onUnknownRoute '
+              'callback is provided as well.'
+            ),
+            DiagnosticsProperty<NavigatorState>('The Navigator was', this, style: DiagnosticsTreeStyle.errorProperty),
+          ]);
+        }
+        return true;
+      }());
+      route = widget.onUnknownRoute!(settings) as Route<T>?;
+      assert(() {
+        if (route == null) {
+          throw FlutterError.fromParts(<DiagnosticsNode>[
+            ErrorSummary('Navigator.onUnknownRoute returned null when requested to build route "$name".'),
+            ErrorDescription('The onUnknownRoute callback must never return null.'),
+            DiagnosticsProperty<NavigatorState>('The Navigator was', this, style: DiagnosticsTreeStyle.errorProperty),
+          ]);
+        }
+        return true;
+      }());
+    }
+    assert(route != null || allowNull);
+    return route;
+  }
+
+  /// Push a named route onto the navigator.
+  ///
+  /// {@macro flutter.widgets.navigator.pushNamed}
+  ///
+  /// {@macro flutter.widgets.Navigator.pushNamed}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _aaronBurrSir() {
+  ///   navigator.pushNamed('/nyc/1776');
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [restorablePushNamed], which pushes a route that can be restored
+  ///    during state restoration.
+  @optionalTypeArgs
+  Future<T?> pushNamed<T extends Object?>(
+    String routeName, {
+    Object? arguments,
+  }) {
+    return push<T>(_routeNamed<T>(routeName, arguments: arguments)!);
+  }
+
+  /// Push a named route onto the navigator.
+  ///
+  /// {@macro flutter.widgets.navigator.restorablePushNamed}
+  ///
+  /// {@macro flutter.widgets.navigator.pushNamed}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.arguments}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _openDetails() {
+  ///   navigator.restorablePushNamed('/nyc/1776');
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  String restorablePushNamed<T extends Object?>(
+    String routeName, {
+    Object? arguments,
+  }) {
+    assert(routeName != null);
+    assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
+    final _RouteEntry entry = _RestorationInformation.named(
+      name: routeName,
+      arguments: arguments,
+      restorationScopeId: _nextPagelessRestorationScopeId,
+    ).toRouteEntry(this, initialState: _RouteLifecycle.push);
+    _pushEntry(entry);
+    return entry.restorationId!;
+  }
+
+  /// Replace the current route of the navigator by pushing the route named
+  /// [routeName] and then disposing the previous route once the new route has
+  /// finished animating in.
+  ///
+  /// {@macro flutter.widgets.navigator.pushReplacementNamed}
+  ///
+  /// {@macro flutter.widgets.Navigator.pushNamed}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _startBike() {
+  ///   navigator.pushReplacementNamed('/jouett/1781');
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [restorablePushReplacementNamed], which pushes a replacement route that
+  ///  can be restored during state restoration.
+  @optionalTypeArgs
+  Future<T?> pushReplacementNamed<T extends Object?, TO extends Object?>(
+    String routeName, {
+    TO? result,
+    Object? arguments,
+  }) {
+    return pushReplacement<T, TO>(_routeNamed<T>(routeName, arguments: arguments)!, result: result);
+  }
+
+  /// Replace the current route of the navigator by pushing the route named
+  /// [routeName] and then disposing the previous route once the new route has
+  /// finished animating in.
+  ///
+  /// {@macro flutter.widgets.navigator.restorablePushReplacementNamed}
+  ///
+  /// {@macro flutter.widgets.navigator.pushReplacementNamed}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.arguments}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _startCar() {
+  ///   navigator.restorablePushReplacementNamed('/jouett/1781');
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  String restorablePushReplacementNamed<T extends Object?, TO extends Object?>(
+    String routeName, {
+    TO? result,
+    Object? arguments,
+  }) {
+    assert(routeName != null);
+    assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
+    final _RouteEntry entry = _RestorationInformation.named(
+      name: routeName,
+      arguments: arguments,
+      restorationScopeId: _nextPagelessRestorationScopeId,
+    ).toRouteEntry(this, initialState: _RouteLifecycle.pushReplace);
+    _pushReplacementEntry(entry, result);
+    return entry.restorationId!;
+  }
+
+  /// Pop the current route off the navigator and push a named route in its
+  /// place.
+  ///
+  /// {@macro flutter.widgets.navigator.popAndPushNamed}
+  ///
+  /// {@macro flutter.widgets.Navigator.pushNamed}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _begin() {
+  ///   navigator.popAndPushNamed('/nyc/1776');
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [restorablePopAndPushNamed], which pushes a new route that can be
+  ///    restored during state restoration.
+  @optionalTypeArgs
+  Future<T?> popAndPushNamed<T extends Object?, TO extends Object?>(
+    String routeName, {
+    TO? result,
+    Object? arguments,
+  }) {
+    pop<TO>(result);
+    return pushNamed<T>(routeName, arguments: arguments);
+  }
+
+  /// Pop the current route off the navigator and push a named route in its
+  /// place.
+  ///
+  /// {@macro flutter.widgets.navigator.restorablePopAndPushNamed}
+  ///
+  /// {@macro flutter.widgets.navigator.popAndPushNamed}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.arguments}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _end() {
+  ///   navigator.restorablePopAndPushNamed('/nyc/1776');
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  String restorablePopAndPushNamed<T extends Object?, TO extends Object?>(
+    String routeName, {
+    TO? result,
+    Object? arguments,
+  }) {
+    pop<TO>(result);
+    return restorablePushNamed(routeName, arguments: arguments);
+  }
+
+  /// Push the route with the given name onto the navigator, and then remove all
+  /// the previous routes until the `predicate` returns true.
+  ///
+  /// {@macro flutter.widgets.navigator.pushNamedAndRemoveUntil}
+  ///
+  /// {@macro flutter.widgets.Navigator.pushNamed}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _handleOpenCalendar() {
+  ///   navigator.pushNamedAndRemoveUntil('/calendar', ModalRoute.withName('/'));
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [restorablePushNamedAndRemoveUntil], which pushes a new route that can
+  ///    be restored during state restoration.
+  @optionalTypeArgs
+  Future<T?> pushNamedAndRemoveUntil<T extends Object?>(
+    String newRouteName,
+    RoutePredicate predicate, {
+    Object? arguments,
+  }) {
+    return pushAndRemoveUntil<T>(_routeNamed<T>(newRouteName, arguments: arguments)!, predicate);
+  }
+
+  /// Push the route with the given name onto the navigator, and then remove all
+  /// the previous routes until the `predicate` returns true.
+  ///
+  /// {@macro flutter.widgets.navigator.restorablePushNamedAndRemoveUntil}
+  ///
+  /// {@macro flutter.widgets.navigator.pushNamedAndRemoveUntil}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.arguments}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _openCalendar() {
+  ///   navigator.restorablePushNamedAndRemoveUntil('/calendar', ModalRoute.withName('/'));
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  String restorablePushNamedAndRemoveUntil<T extends Object?>(
+    String newRouteName,
+    RoutePredicate predicate, {
+    Object? arguments,
+  }) {
+    assert(newRouteName != null);
+    assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
+    final _RouteEntry entry = _RestorationInformation.named(
+      name: newRouteName,
+      arguments: arguments,
+      restorationScopeId: _nextPagelessRestorationScopeId,
+    ).toRouteEntry(this, initialState: _RouteLifecycle.push);
+    _pushEntryAndRemoveUntil(entry, predicate);
+    return entry.restorationId!;
+  }
+
+  /// Push the given route onto the navigator.
+  ///
+  /// {@macro flutter.widgets.navigator.push}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _openPage() {
+  ///   navigator.push(MaterialPageRoute(builder: (BuildContext context) => MyPage()));
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [restorablePush], which pushes a route that can be restored during
+  ///    state restoration.
+  @optionalTypeArgs
+  Future<T?> push<T extends Object?>(Route<T> route) {
+    _pushEntry(_RouteEntry(route, initialState: _RouteLifecycle.push));
+    return route.popped;
+  }
+
+  bool _debugIsStaticCallback(Function callback) {
+    bool result = false;
+    assert(() {
+      // TODO(goderbauer): remove the kIsWeb check when https://github.com/flutter/flutter/issues/33615 is resolved.
+      result = kIsWeb || ui.PluginUtilities.getCallbackHandle(callback) != null;
+      return true;
+    }());
+    return result;
+  }
+
+  /// Push a new route onto the navigator.
+  ///
+  /// {@macro flutter.widgets.navigator.restorablePush}
+  ///
+  /// {@macro flutter.widgets.navigator.push}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePush}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  ///
+  /// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// static Route _myRouteBuilder(BuildContext context, Object arguments) {
+  ///   return MaterialPageRoute(
+  ///     builder: (BuildContext context) => MyStatefulWidget(),
+  ///   );
+  /// }
+  ///
+  /// Widget build(BuildContext context) {
+  ///   return Scaffold(
+  ///     appBar: AppBar(
+  ///       title: const Text('Sample Code'),
+  ///     ),
+  ///     floatingActionButton: FloatingActionButton(
+  ///       onPressed: () => Navigator.of(context).restorablePush(_myRouteBuilder),
+  ///       tooltip: 'Increment Counter',
+  ///       child: const Icon(Icons.add),
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  String restorablePush<T extends Object?>(RestorableRouteBuilder<T> routeBuilder, {Object? arguments}) {
+    assert(routeBuilder != null);
+    assert(_debugIsStaticCallback(routeBuilder), 'The provided routeBuilder must be a static function.');
+    assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
+    final _RouteEntry entry = _RestorationInformation.anonymous(
+      routeBuilder: routeBuilder,
+      arguments: arguments,
+      restorationScopeId: _nextPagelessRestorationScopeId,
+    ).toRouteEntry(this, initialState: _RouteLifecycle.push);
+    _pushEntry(entry);
+    return entry.restorationId!;
+  }
+
+  void _pushEntry(_RouteEntry entry) {
+    assert(!_debugLocked);
+    assert(() {
+      _debugLocked = true;
+      return true;
+    }());
+    assert(entry.route != null);
+    assert(entry.route._navigator == null);
+    assert(entry.currentState == _RouteLifecycle.push);
+    _history.add(entry);
+    _flushHistoryUpdates();
+    assert(() {
+      _debugLocked = false;
+      return true;
+    }());
+    _afterNavigation(entry.route);
+  }
+
+  void _afterNavigation(Route<dynamic>? route) {
+    if (!kReleaseMode) {
+      // Among other uses, performance tools use this event to ensure that perf
+      // stats reflect the time interval since the last navigation event
+      // occurred, ensuring that stats only reflect the current page.
+
+      Map<String, dynamic>? routeJsonable;
+      if (route != null) {
+        routeJsonable = <String, dynamic>{};
+
+        final String description;
+        if (route is TransitionRoute<dynamic>) {
+          final TransitionRoute<dynamic> transitionRoute = route;
+          description = transitionRoute.debugLabel;
+        } else {
+          description = '$route';
+        }
+        routeJsonable['description'] = description;
+
+        final RouteSettings settings = route.settings;
+        final Map<String, dynamic> settingsJsonable = <String, dynamic> {
+          'name': settings.name,
+        };
+        if (settings.arguments != null) {
+          settingsJsonable['arguments'] = jsonEncode(
+            settings.arguments,
+            toEncodable: (Object? object) => '$object',
+          );
+        }
+        routeJsonable['settings'] = settingsJsonable;
+      }
+
+      developer.postEvent('Flutter.Navigation', <String, dynamic>{
+        'route': routeJsonable,
+      });
+    }
+    _cancelActivePointers();
+  }
+
+  /// Replace the current route of the navigator by pushing the given route and
+  /// then disposing the previous route once the new route has finished
+  /// animating in.
+  ///
+  /// {@macro flutter.widgets.navigator.pushReplacement}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _doOpenPage() {
+  ///   navigator.pushReplacement(
+  ///       MaterialPageRoute(builder: (BuildContext context) => MyHomePage()));
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [restorablePushReplacement], which pushes a replacement route that can
+  ///    be restored during state restoration.
+  @optionalTypeArgs
+  Future<T?> pushReplacement<T extends Object?, TO extends Object?>(Route<T> newRoute, { TO? result }) {
+    assert(newRoute != null);
+    assert(newRoute._navigator == null);
+    _pushReplacementEntry(_RouteEntry(newRoute, initialState: _RouteLifecycle.pushReplace), result);
+    return newRoute.popped;
+  }
+
+  /// Replace the current route of the navigator by pushing a new route and
+  /// then disposing the previous route once the new route has finished
+  /// animating in.
+  ///
+  /// {@macro flutter.widgets.navigator.restorablePushReplacement}
+  ///
+  /// {@macro flutter.widgets.navigator.pushReplacement}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePush}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  ///
+  /// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// static Route _myRouteBuilder(BuildContext context, Object arguments) {
+  ///   return MaterialPageRoute(
+  ///     builder: (BuildContext context) => MyStatefulWidget(),
+  ///   );
+  /// }
+  ///
+  /// Widget build(BuildContext context) {
+  ///   return Scaffold(
+  ///     appBar: AppBar(
+  ///       title: const Text('Sample Code'),
+  ///     ),
+  ///     floatingActionButton: FloatingActionButton(
+  ///       onPressed: () => Navigator.of(context).restorablePushReplacement(
+  ///         _myRouteBuilder,
+  ///       ),
+  ///       tooltip: 'Increment Counter',
+  ///       child: const Icon(Icons.add),
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  String restorablePushReplacement<T extends Object?, TO extends Object?>(RestorableRouteBuilder<T> routeBuilder, { TO? result, Object? arguments }) {
+    assert(routeBuilder != null);
+    assert(_debugIsStaticCallback(routeBuilder), 'The provided routeBuilder must be a static function.');
+    assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
+    final _RouteEntry entry = _RestorationInformation.anonymous(
+      routeBuilder: routeBuilder,
+      arguments: arguments,
+      restorationScopeId: _nextPagelessRestorationScopeId,
+    ).toRouteEntry(this, initialState: _RouteLifecycle.pushReplace);
+    _pushReplacementEntry(entry, result);
+    return entry.restorationId!;
+  }
+
+  void _pushReplacementEntry<TO extends Object?>(_RouteEntry entry, TO? result) {
+    assert(!_debugLocked);
+    assert(() {
+      _debugLocked = true;
+      return true;
+    }());
+    assert(entry.route != null);
+    assert(entry.route._navigator == null);
+    assert(_history.isNotEmpty);
+    assert(_history.any(_RouteEntry.isPresentPredicate), 'Navigator has no active routes to replace.');
+    assert(entry.currentState == _RouteLifecycle.pushReplace);
+    _history.lastWhere(_RouteEntry.isPresentPredicate).complete(result, isReplaced: true);
+    _history.add(entry);
+    _flushHistoryUpdates();
+    assert(() {
+      _debugLocked = false;
+      return true;
+    }());
+    _afterNavigation(entry.route);
+  }
+
+  /// Push the given route onto the navigator, and then remove all the previous
+  /// routes until the `predicate` returns true.
+  ///
+  /// {@macro flutter.widgets.navigator.pushAndRemoveUntil}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _resetAndOpenPage() {
+  ///   navigator.pushAndRemoveUntil(
+  ///     MaterialPageRoute(builder: (BuildContext context) => MyHomePage()),
+  ///     ModalRoute.withName('/'),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  ///
+  /// See also:
+  ///
+  ///  * [restorablePushAndRemoveUntil], which pushes a route that can be
+  ///    restored during state restoration.
+  @optionalTypeArgs
+  Future<T?> pushAndRemoveUntil<T extends Object?>(Route<T> newRoute, RoutePredicate predicate) {
+    assert(newRoute != null);
+    assert(newRoute._navigator == null);
+    assert(newRoute.overlayEntries.isEmpty);
+    _pushEntryAndRemoveUntil(_RouteEntry(newRoute, initialState: _RouteLifecycle.push), predicate);
+    return newRoute.popped;
+  }
+
+  /// Push a new route onto the navigator, and then remove all the previous
+  /// routes until the `predicate` returns true.
+  ///
+  /// {@macro flutter.widgets.navigator.restorablePushAndRemoveUntil}
+  ///
+  /// {@macro flutter.widgets.navigator.pushAndRemoveUntil}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePush}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  ///
+  /// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// static Route _myRouteBuilder(BuildContext context, Object arguments) {
+  ///   return MaterialPageRoute(
+  ///     builder: (BuildContext context) => MyStatefulWidget(),
+  ///   );
+  /// }
+  ///
+  /// Widget build(BuildContext context) {
+  ///   return Scaffold(
+  ///     appBar: AppBar(
+  ///       title: const Text('Sample Code'),
+  ///     ),
+  ///     floatingActionButton: FloatingActionButton(
+  ///       onPressed: () => Navigator.of(context).restorablePushAndRemoveUntil(
+  ///         _myRouteBuilder,
+  ///         ModalRoute.withName('/'),
+  ///       ),
+  ///       tooltip: 'Increment Counter',
+  ///       child: const Icon(Icons.add),
+  ///     ),
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  String restorablePushAndRemoveUntil<T extends Object?>(RestorableRouteBuilder<T> newRouteBuilder, RoutePredicate predicate, {Object? arguments}) {
+    assert(newRouteBuilder != null);
+    assert(_debugIsStaticCallback(newRouteBuilder), 'The provided routeBuilder must be a static function.');
+    assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
+    final _RouteEntry entry = _RestorationInformation.anonymous(
+      routeBuilder: newRouteBuilder,
+      arguments: arguments,
+      restorationScopeId: _nextPagelessRestorationScopeId,
+    ).toRouteEntry(this, initialState: _RouteLifecycle.push);
+    _pushEntryAndRemoveUntil(entry, predicate);
+    return entry.restorationId!;
+  }
+
+  void _pushEntryAndRemoveUntil(_RouteEntry entry, RoutePredicate predicate) {
+    assert(!_debugLocked);
+    assert(() {
+      _debugLocked = true;
+      return true;
+    }());
+    assert(entry.route != null);
+    assert(entry.route._navigator == null);
+    assert(entry.route.overlayEntries.isEmpty);
+    assert(predicate != null);
+    assert(entry.currentState == _RouteLifecycle.push);
+    int index = _history.length - 1;
+    _history.add(entry);
+    while (index >= 0 && !predicate(_history[index].route)) {
+      if (_history[index].isPresent)
+        _history[index].remove();
+      index -= 1;
+    }
+    _flushHistoryUpdates();
+
+    assert(() {
+      _debugLocked = false;
+      return true;
+    }());
+    _afterNavigation(entry.route);
+  }
+
+  /// Replaces a route on the navigator with a new route.
+  ///
+  /// {@macro flutter.widgets.navigator.replace}
+  ///
+  /// See also:
+  ///
+  ///  * [replaceRouteBelow], which is the same but identifies the route to be
+  ///    removed by reference to the route above it, rather than directly.
+  ///  * [restorableReplace], which adds a replacement route that can be
+  ///    restored during state restoration.
+  @optionalTypeArgs
+  void replace<T extends Object?>({ required Route<dynamic> oldRoute, required Route<T> newRoute }) {
+    assert(!_debugLocked);
+    assert(oldRoute != null);
+    assert(oldRoute._navigator == this);
+    assert(newRoute != null);
+    _replaceEntry(_RouteEntry(newRoute, initialState: _RouteLifecycle.replace), oldRoute);
+  }
+
+  /// Replaces a route on the navigator with a new route.
+  ///
+  /// {@macro flutter.widgets.navigator.restorableReplace}
+  ///
+  /// {@macro flutter.widgets.navigator.replace}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePush}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  @optionalTypeArgs
+  String restorableReplace<T extends Object?>({ required Route<dynamic> oldRoute, required RestorableRouteBuilder<T> newRouteBuilder, Object? arguments }) {
+    assert(oldRoute != null);
+    assert(oldRoute._navigator == this);
+    assert(newRouteBuilder != null);
+    assert(_debugIsStaticCallback(newRouteBuilder), 'The provided routeBuilder must be a static function.');
+    assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
+    assert(oldRoute != null);
+    final _RouteEntry entry = _RestorationInformation.anonymous(
+      routeBuilder: newRouteBuilder,
+      arguments: arguments,
+      restorationScopeId: _nextPagelessRestorationScopeId,
+    ).toRouteEntry(this, initialState: _RouteLifecycle.replace);
+    _replaceEntry(entry, oldRoute);
+    return entry.restorationId!;
+  }
+
+  void _replaceEntry(_RouteEntry entry, Route<dynamic> oldRoute) {
+    assert(!_debugLocked);
+    if (oldRoute == entry.route)
+      return;
+    assert(() {
+      _debugLocked = true;
+      return true;
+    }());
+    assert(entry.currentState == _RouteLifecycle.replace);
+    assert(entry.route._navigator == null);
+    final int index = _history.indexWhere(_RouteEntry.isRoutePredicate(oldRoute));
+    assert(index >= 0, 'This Navigator does not contain the specified oldRoute.');
+    assert(_history[index].isPresent, 'The specified oldRoute has already been removed from the Navigator.');
+    final bool wasCurrent = oldRoute.isCurrent;
+    _history.insert(index + 1, entry);
+    _history[index].remove(isReplaced: true);
+    _flushHistoryUpdates();
+    assert(() {
+      _debugLocked = false;
+      return true;
+    }());
+    if (wasCurrent)
+      _afterNavigation(entry.route);
+  }
+
+  /// Replaces a route on the navigator with a new route. The route to be
+  /// replaced is the one below the given `anchorRoute`.
+  ///
+  /// {@macro flutter.widgets.navigator.replaceRouteBelow}
+  ///
+  /// See also:
+  ///
+  ///  * [replace], which is the same but identifies the route to be removed
+  ///    directly.
+  ///  * [restorableReplaceRouteBelow], which adds a replacement route that can
+  ///    be restored during state restoration.
+  @optionalTypeArgs
+  void replaceRouteBelow<T extends Object?>({ required Route<dynamic> anchorRoute, required Route<T> newRoute }) {
+    assert(newRoute != null);
+    assert(newRoute._navigator == null);
+    assert(anchorRoute != null);
+    assert(anchorRoute._navigator == this);
+    _replaceEntryBelow(_RouteEntry(newRoute, initialState: _RouteLifecycle.replace), anchorRoute);
+  }
+
+  /// Replaces a route on the navigator with a new route. The route to be
+  /// replaced is the one below the given `anchorRoute`.
+  ///
+  /// {@macro flutter.widgets.navigator.restorableReplaceRouteBelow}
+  ///
+  /// {@macro flutter.widgets.navigator.replaceRouteBelow}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePush}
+  ///
+  /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
+  @optionalTypeArgs
+  String restorableReplaceRouteBelow<T extends Object?>({ required Route<dynamic> anchorRoute, required RestorableRouteBuilder<T> newRouteBuilder, Object? arguments }) {
+    assert(anchorRoute != null);
+    assert(anchorRoute._navigator == this);
+    assert(newRouteBuilder != null);
+    assert(_debugIsStaticCallback(newRouteBuilder), 'The provided routeBuilder must be a static function.');
+    assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
+    assert(anchorRoute != null);
+    final _RouteEntry entry = _RestorationInformation.anonymous(
+      routeBuilder: newRouteBuilder,
+      arguments: arguments,
+      restorationScopeId: _nextPagelessRestorationScopeId,
+    ).toRouteEntry(this, initialState: _RouteLifecycle.replace);
+    _replaceEntryBelow(entry, anchorRoute);
+    return entry.restorationId!;
+  }
+
+  void _replaceEntryBelow(_RouteEntry entry, Route<dynamic> anchorRoute) {
+    assert(!_debugLocked);
+    assert(() { _debugLocked = true; return true; }());
+    final int anchorIndex = _history.indexWhere(_RouteEntry.isRoutePredicate(anchorRoute));
+    assert(anchorIndex >= 0, 'This Navigator does not contain the specified anchorRoute.');
+    assert(_history[anchorIndex].isPresent, 'The specified anchorRoute has already been removed from the Navigator.');
+    int index = anchorIndex - 1;
+    while (index >= 0) {
+      if (_history[index].isPresent)
+        break;
+      index -= 1;
+    }
+    assert(index >= 0, 'There are no routes below the specified anchorRoute.');
+    _history.insert(index + 1, entry);
+    _history[index].remove(isReplaced: true);
+    _flushHistoryUpdates();
+    assert(() { _debugLocked = false; return true; }());
+  }
+
+  /// Whether the navigator can be popped.
+  ///
+  /// {@macro flutter.widgets.navigator.canPop}
+  ///
+  /// See also:
+  ///
+  ///  * [Route.isFirst], which returns true for routes for which [canPop]
+  ///    returns false.
+  bool canPop() {
+    final Iterator<_RouteEntry> iterator = _history.where(_RouteEntry.isPresentPredicate).iterator;
+    if (!iterator.moveNext())
+      return false; // we have no active routes, so we can't pop
+    if (iterator.current.route.willHandlePopInternally)
+      return true; // the first route can handle pops itself, so we can pop
+    if (!iterator.moveNext())
+      return false; // there's only one route, so we can't pop
+    return true; // there's at least two routes, so we can pop
+  }
+
+  /// Consults the current route's [Route.willPop] method, and acts accordingly,
+  /// potentially popping the route as a result; returns whether the pop request
+  /// should be considered handled.
+  ///
+  /// {@macro flutter.widgets.navigator.maybePop}
+  ///
+  /// See also:
+  ///
+  ///  * [Form], which provides an `onWillPop` callback that enables the form
+  ///    to veto a [pop] initiated by the app's back button.
+  ///  * [ModalRoute], which provides a `scopedWillPopCallback` that can be used
+  ///    to define the route's `willPop` method.
+  @optionalTypeArgs
+  Future<bool> maybePop<T extends Object?>([ T? result ]) async {
+    final _RouteEntry? lastEntry = _history.cast<_RouteEntry?>().lastWhere(
+      (_RouteEntry? e) => e != null && _RouteEntry.isPresentPredicate(e),
+      orElse: () => null,
+    );
+    if (lastEntry == null)
+      return false;
+    assert(lastEntry.route._navigator == this);
+    final RoutePopDisposition disposition = await lastEntry.route.willPop(); // this is asynchronous
+    assert(disposition != null);
+    if (!mounted)
+      return true; // forget about this pop, we were disposed in the meantime
+    final _RouteEntry? newLastEntry = _history.cast<_RouteEntry?>().lastWhere(
+      (_RouteEntry? e) => e != null && _RouteEntry.isPresentPredicate(e),
+      orElse: () => null,
+    );
+    if (lastEntry != newLastEntry)
+      return true; // forget about this pop, something happened to our history in the meantime
+    switch (disposition) {
+      case RoutePopDisposition.bubble:
+        return false;
+      case RoutePopDisposition.pop:
+        pop(result);
+        return true;
+      case RoutePopDisposition.doNotPop:
+        return true;
+    }
+  }
+
+  /// Pop the top-most route off the navigator.
+  ///
+  /// {@macro flutter.widgets.navigator.pop}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage for closing a route is as follows:
+  ///
+  /// ```dart
+  /// void _handleClose() {
+  ///   navigator.pop();
+  /// }
+  /// ```
+  /// {@end-tool}
+  /// {@tool snippet}
+  ///
+  /// A dialog box might be closed with a result:
+  ///
+  /// ```dart
+  /// void _handleAccept() {
+  ///   navigator.pop(true); // dialog returns true
+  /// }
+  /// ```
+  /// {@end-tool}
+  @optionalTypeArgs
+  void pop<T extends Object?>([ T? result ]) {
+    assert(!_debugLocked);
+    assert(() {
+      _debugLocked = true;
+      return true;
+    }());
+    final _RouteEntry entry = _history.lastWhere(_RouteEntry.isPresentPredicate);
+    if (entry.hasPage) {
+      if (widget.onPopPage!(entry.route, result))
+        entry.currentState = _RouteLifecycle.pop;
+    } else {
+      entry.pop<T>(result);
+    }
+    if (entry.currentState == _RouteLifecycle.pop) {
+      // Flush the history if the route actually wants to be popped (the pop
+      // wasn't handled internally).
+      _flushHistoryUpdates(rearrangeOverlay: false);
+      assert(entry.route._popCompleter.isCompleted);
+    }
+    assert(() {
+      _debugLocked = false;
+      return true;
+    }());
+    _afterNavigation(entry.route);
+  }
+
+  /// Calls [pop] repeatedly until the predicate returns true.
+  ///
+  /// {@macro flutter.widgets.navigator.popUntil}
+  ///
+  /// {@tool snippet}
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// void _doLogout() {
+  ///   navigator.popUntil(ModalRoute.withName('/login'));
+  /// }
+  /// ```
+  /// {@end-tool}
+  void popUntil(RoutePredicate predicate) {
+    _RouteEntry? candidate = _history.cast<_RouteEntry?>().lastWhere(
+      (_RouteEntry? e) => e != null && _RouteEntry.isPresentPredicate(e),
+      orElse: () => null,
+    );
+    while(candidate != null) {
+      if (predicate(candidate.route))
+        return;
+      pop();
+      candidate = _history.cast<_RouteEntry?>().lastWhere(
+        (_RouteEntry? e) => e != null && _RouteEntry.isPresentPredicate(e),
+        orElse: () => null,
+      );
+    }
+  }
+
+  /// Immediately remove `route` from the navigator, and [Route.dispose] it.
+  ///
+  /// {@macro flutter.widgets.navigator.removeRoute}
+  void removeRoute(Route<dynamic> route) {
+    assert(route != null);
+    assert(!_debugLocked);
+    assert(() {
+      _debugLocked = true;
+      return true;
+    }());
+    assert(route._navigator == this);
+    final bool wasCurrent = route.isCurrent;
+    final _RouteEntry entry = _history.firstWhere(_RouteEntry.isRoutePredicate(route));
+    assert(entry != null);
+    entry.remove();
+    _flushHistoryUpdates(rearrangeOverlay: false);
+    assert(() {
+      _debugLocked = false;
+      return true;
+    }());
+    if (wasCurrent)
+      _afterNavigation(
+        _history.cast<_RouteEntry?>().lastWhere(
+          (_RouteEntry? e) => e != null && _RouteEntry.isPresentPredicate(e),
+          orElse: () => null
+        )?.route
+      );
+  }
+
+  /// Immediately remove a route from the navigator, and [Route.dispose] it. The
+  /// route to be removed is the one below the given `anchorRoute`.
+  ///
+  /// {@macro flutter.widgets.navigator.removeRouteBelow}
+  void removeRouteBelow(Route<dynamic> anchorRoute) {
+    assert(!_debugLocked);
+    assert(() {
+      _debugLocked = true;
+      return true;
+    }());
+    assert(anchorRoute != null);
+    assert(anchorRoute._navigator == this);
+    final int anchorIndex = _history.indexWhere(_RouteEntry.isRoutePredicate(anchorRoute));
+    assert(anchorIndex >= 0, 'This Navigator does not contain the specified anchorRoute.');
+    assert(_history[anchorIndex].isPresent, 'The specified anchorRoute has already been removed from the Navigator.');
+    int index = anchorIndex - 1;
+    while (index >= 0) {
+      if (_history[index].isPresent)
+        break;
+      index -= 1;
+    }
+    assert(index >= 0, 'There are no routes below the specified anchorRoute.');
+    _history[index].remove();
+    _flushHistoryUpdates(rearrangeOverlay: false);
+    assert(() {
+      _debugLocked = false;
+      return true;
+    }());
+  }
+
+  /// Complete the lifecycle for a route that has been popped off the navigator.
+  ///
+  /// When the navigator pops a route, the navigator retains a reference to the
+  /// route in order to call [Route.dispose] if the navigator itself is removed
+  /// from the tree. When the route is finished with any exit animation, the
+  /// route should call this function to complete its lifecycle (e.g., to
+  /// receive a call to [Route.dispose]).
+  ///
+  /// The given `route` must have already received a call to [Route.didPop].
+  /// This function may be called directly from [Route.didPop] if [Route.didPop]
+  /// will return true.
+  void finalizeRoute(Route<dynamic> route) {
+    // FinalizeRoute may have been called while we were already locked as a
+    // responds to route.didPop(). Make sure to leave in the state we were in
+    // before the call.
+    bool? wasDebugLocked;
+    assert(() { wasDebugLocked = _debugLocked; _debugLocked = true; return true; }());
+    assert(_history.where(_RouteEntry.isRoutePredicate(route)).length == 1);
+    final _RouteEntry entry =  _history.firstWhere(_RouteEntry.isRoutePredicate(route));
+    if (entry.doingPop) {
+      // We were called synchronously from Route.didPop(), but didn't process
+      // the pop yet. Let's do that now before finalizing.
+      entry.currentState = _RouteLifecycle.pop;
+      _flushHistoryUpdates(rearrangeOverlay: false);
+    }
+    assert(entry.currentState != _RouteLifecycle.pop);
+    entry.finalize();
+    _flushHistoryUpdates(rearrangeOverlay: false);
+    assert(() { _debugLocked = wasDebugLocked!; return true; }());
+  }
+
+  @optionalTypeArgs
+  Route<T>? _getRouteById<T>(String id) {
+    assert(id != null);
+    return _history.cast<_RouteEntry?>().firstWhere(
+      (_RouteEntry? entry) => entry!.restorationId == id,
+      orElse: () => null,
+    )?.route as Route<T>?;
+  }
+
+  int get _userGesturesInProgress => _userGesturesInProgressCount;
+  int _userGesturesInProgressCount = 0;
+  set _userGesturesInProgress(int value) {
+    _userGesturesInProgressCount = value;
+    userGestureInProgressNotifier.value = _userGesturesInProgress > 0;
+  }
+
+  /// Whether a route is currently being manipulated by the user, e.g.
+  /// as during an iOS back gesture.
+  ///
+  /// See also:
+  ///
+  ///  * [userGestureInProgressNotifier], which notifies its listeners if
+  ///    the value of [userGestureInProgress] changes.
+  bool get userGestureInProgress => userGestureInProgressNotifier.value;
+
+  /// Notifies its listeners if the value of [userGestureInProgress] changes.
+  final ValueNotifier<bool> userGestureInProgressNotifier = ValueNotifier<bool>(false);
+
+  /// The navigator is being controlled by a user gesture.
+  ///
+  /// For example, called when the user beings an iOS back gesture.
+  ///
+  /// When the gesture finishes, call [didStopUserGesture].
+  void didStartUserGesture() {
+    _userGesturesInProgress += 1;
+    if (_userGesturesInProgress == 1) {
+      final int routeIndex = _getIndexBefore(
+        _history.length - 1,
+        _RouteEntry.willBePresentPredicate,
+      );
+      assert(routeIndex != null);
+      final Route<dynamic> route = _history[routeIndex].route;
+      Route<dynamic>? previousRoute;
+      if (!route.willHandlePopInternally && routeIndex > 0) {
+        previousRoute = _getRouteBefore(
+          routeIndex - 1,
+          _RouteEntry.willBePresentPredicate,
+        )!.route;
+      }
+      for (final NavigatorObserver observer in _effectiveObservers)
+        observer.didStartUserGesture(route, previousRoute);
+    }
+  }
+
+  /// A user gesture completed.
+  ///
+  /// Notifies the navigator that a gesture regarding which the navigator was
+  /// previously notified with [didStartUserGesture] has completed.
+  void didStopUserGesture() {
+    assert(_userGesturesInProgress > 0);
+    _userGesturesInProgress -= 1;
+    if (_userGesturesInProgress == 0) {
+      for (final NavigatorObserver observer in _effectiveObservers)
+        observer.didStopUserGesture();
+    }
+  }
+
+  final Set<int> _activePointers = <int>{};
+
+  void _handlePointerDown(PointerDownEvent event) {
+    _activePointers.add(event.pointer);
+  }
+
+  void _handlePointerUpOrCancel(PointerEvent event) {
+    _activePointers.remove(event.pointer);
+  }
+
+  void _cancelActivePointers() {
+    // TODO(abarth): This mechanism is far from perfect. See https://github.com/flutter/flutter/issues/4770
+    if (SchedulerBinding.instance!.schedulerPhase == SchedulerPhase.idle) {
+      // If we're between frames (SchedulerPhase.idle) then absorb any
+      // subsequent pointers from this frame. The absorbing flag will be
+      // reset in the next frame, see build().
+      final RenderAbsorbPointer? absorber = _overlayKey.currentContext?.findAncestorRenderObjectOfType<RenderAbsorbPointer>();
+      setState(() {
+        absorber?.absorbing = true;
+        // We do this in setState so that we'll reset the absorbing value back
+        // to false on the next frame.
+      });
+    }
+    _activePointers.toList().forEach(WidgetsBinding.instance!.cancelPointer);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(!_debugLocked);
+    assert(_history.isNotEmpty);
+    // Hides the HeroControllerScope for the widget subtree so that the other
+    // nested navigator underneath will not pick up the hero controller above
+    // this level.
+    return HeroControllerScope.none(
+      child: Listener(
+        onPointerDown: _handlePointerDown,
+        onPointerUp: _handlePointerUpOrCancel,
+        onPointerCancel: _handlePointerUpOrCancel,
+        child: AbsorbPointer(
+          absorbing: false, // it's mutated directly by _cancelActivePointers above
+          child: FocusScope(
+            node: focusScopeNode,
+            autofocus: true,
+            child: UnmanagedRestorationScope(
+              bucket: bucket,
+              child: Overlay(
+                key: _overlayKey,
+                initialEntries: overlay == null ?  _allRouteOverlayEntries.toList(growable: false) : const <OverlayEntry>[],
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+enum _RouteRestorationType {
+  named,
+  anonymous,
+}
+
+abstract class _RestorationInformation {
+  _RestorationInformation(this.type) : assert(type != null);
+  factory _RestorationInformation.named({
+    required String name,
+    required Object? arguments,
+    required int restorationScopeId,
+  }) = _NamedRestorationInformation;
+  factory _RestorationInformation.anonymous({
+    required RestorableRouteBuilder routeBuilder,
+    required Object? arguments,
+    required int restorationScopeId,
+  }) = _AnonymousRestorationInformation;
+
+  factory _RestorationInformation.fromSerializableData(Object data) {
+    assert(data != null);
+    final List<Object?> casted = data as List<Object?>;
+    assert(casted.isNotEmpty);
+    final _RouteRestorationType type = _RouteRestorationType.values[casted[0]! as int];
+    switch (type) {
+      case _RouteRestorationType.named:
+        return _NamedRestorationInformation.fromSerializableData(casted.sublist(1));
+      case _RouteRestorationType.anonymous:
+        return _AnonymousRestorationInformation.fromSerializableData(casted.sublist(1));
+    }
+    throw StateError('Invalid type: $type'); // ignore: dead_code
+  }
+
+  final _RouteRestorationType type;
+  int get restorationScopeId;
+  Object? _serializableData;
+
+  bool get isRestorable => true;
+
+  Object getSerializableData() {
+    _serializableData ??= computeSerializableData();
+    return _serializableData!;
+  }
+
+  @mustCallSuper
+  List<Object> computeSerializableData() {
+    return <Object>[type.index];
+  }
+
+  @protected
+  Route<dynamic> createRoute(NavigatorState navigator);
+
+  _RouteEntry toRouteEntry(NavigatorState navigator, {_RouteLifecycle initialState = _RouteLifecycle.add}) {
+    assert(navigator != null);
+    assert(initialState != null);
+    final Route<Object?> route = createRoute(navigator);
+    assert(route != null);
+    return _RouteEntry(
+      route,
+      initialState: initialState,
+      restorationInformation: this,
+    );
+  }
+}
+
+class _NamedRestorationInformation extends _RestorationInformation {
+  _NamedRestorationInformation({
+    required this.name,
+    required this.arguments,
+    required this.restorationScopeId,
+  }) : assert(name != null), super(_RouteRestorationType.named);
+
+  factory _NamedRestorationInformation.fromSerializableData(List<Object?> data) {
+    assert(data.length >= 2);
+    return _NamedRestorationInformation(
+      restorationScopeId: data[0]! as int,
+      name: data[1]! as String,
+      arguments: data.length > 2 ? data[2] : null,
+    );
+  }
+
+  @override
+  List<Object> computeSerializableData() {
+    return super.computeSerializableData()..addAll(<Object>[
+      restorationScopeId,
+      name,
+      if (arguments != null)
+        arguments!,
+    ]);
+  }
+
+  @override
+  final int restorationScopeId;
+  final String name;
+  final Object? arguments;
+
+  @override
+  Route<dynamic> createRoute(NavigatorState navigator) {
+    final Route<dynamic> route = navigator._routeNamed<dynamic>(name, arguments: arguments, allowNull: false)!;
+    assert(route != null);
+    return route;
+  }
+}
+
+class _AnonymousRestorationInformation extends _RestorationInformation {
+  _AnonymousRestorationInformation({
+    required this.routeBuilder,
+    required this.arguments,
+    required this.restorationScopeId,
+  }) : assert(routeBuilder != null), super(_RouteRestorationType.anonymous);
+
+  factory _AnonymousRestorationInformation.fromSerializableData(List<Object?> data) {
+    assert(data.length > 1);
+    final RestorableRouteBuilder routeBuilder = ui.PluginUtilities.getCallbackFromHandle(ui.CallbackHandle.fromRawHandle(data[1]! as int))! as RestorableRouteBuilder;
+    return _AnonymousRestorationInformation(
+      restorationScopeId: data[0]! as int,
+      routeBuilder: routeBuilder,
+      arguments: data.length > 2 ? data[2] : null,
+    );
+  }
+
+  @override
+  // TODO(goderbauer): remove the kIsWeb check when https://github.com/flutter/flutter/issues/33615 is resolved.
+  bool get isRestorable => !kIsWeb;
+
+  @override
+  List<Object> computeSerializableData() {
+    assert(isRestorable);
+    final ui.CallbackHandle? handle = ui.PluginUtilities.getCallbackHandle(routeBuilder);
+    assert(handle != null);
+    return super.computeSerializableData()..addAll(<Object>[
+      restorationScopeId,
+      handle!.toRawHandle(),
+      if (arguments != null)
+        arguments!,
+    ]);
+  }
+
+  @override
+  final int restorationScopeId;
+  final RestorableRouteBuilder routeBuilder;
+  final Object? arguments;
+
+  @override
+  Route<dynamic> createRoute(NavigatorState navigator) {
+    final Route<dynamic> result = routeBuilder(navigator.context, arguments);
+    assert(result != null);
+    return result;
+  }
+}
+
+class _HistoryProperty extends RestorableProperty<Map<String?, List<Object>>?> {
+  // Routes not associated with a page are stored under key 'null'.
+  Map<String?, List<Object>>? _pageToPagelessRoutes;
+
+  // Updating.
+
+  void update(List<_RouteEntry> history) {
+    assert(isRegistered);
+    final bool wasUninitialized = _pageToPagelessRoutes == null;
+    bool needsSerialization = wasUninitialized;
+    _pageToPagelessRoutes ??= <String, List<Object>>{};
+    _RouteEntry? currentPage;
+    List<Object> newRoutesForCurrentPage = <Object>[];
+    List<Object> oldRoutesForCurrentPage = _pageToPagelessRoutes![null] ?? const <Object>[];
+    bool restorationEnabled = true;
+
+    final Map<String?, List<Object>> newMap = <String?, List<Object>>{};
+    final Set<String?> removedPages = _pageToPagelessRoutes!.keys.toSet();
+
+    for (final _RouteEntry entry in history) {
+      if (!entry.isPresentForRestoration) {
+        entry.restorationEnabled = false;
+        continue;
+      }
+
+      assert(entry.isPresentForRestoration);
+      if (entry.hasPage) {
+        needsSerialization = needsSerialization || newRoutesForCurrentPage.length != oldRoutesForCurrentPage.length;
+        _finalizePage(newRoutesForCurrentPage, currentPage, newMap, removedPages);
+        currentPage = entry;
+        restorationEnabled = entry.restorationId != null;
+        entry.restorationEnabled = restorationEnabled;
+        if (restorationEnabled) {
+          assert(entry.restorationId != null);
+          newRoutesForCurrentPage = <Object>[];
+          oldRoutesForCurrentPage = _pageToPagelessRoutes![entry.restorationId] ?? const <Object>[];
+        } else {
+          newRoutesForCurrentPage = const <Object>[];
+          oldRoutesForCurrentPage = const <Object>[];
+        }
+        continue;
+      }
+
+      assert(!entry.hasPage);
+      restorationEnabled = restorationEnabled && entry.restorationInformation?.isRestorable == true;
+      entry.restorationEnabled = restorationEnabled;
+      if (restorationEnabled) {
+        assert(entry.restorationId != null);
+        assert(currentPage == null || currentPage.restorationId != null);
+        assert(entry.restorationInformation != null);
+        final Object serializedData = entry.restorationInformation!.getSerializableData();
+        needsSerialization = needsSerialization
+            || oldRoutesForCurrentPage.length <= newRoutesForCurrentPage.length
+            || oldRoutesForCurrentPage[newRoutesForCurrentPage.length] != serializedData;
+        newRoutesForCurrentPage.add(serializedData);
+      }
+    }
+    needsSerialization = needsSerialization || newRoutesForCurrentPage.length != oldRoutesForCurrentPage.length;
+    _finalizePage(newRoutesForCurrentPage, currentPage, newMap, removedPages);
+
+    needsSerialization = needsSerialization || removedPages.isNotEmpty;
+
+    assert(wasUninitialized || _debugMapsEqual(_pageToPagelessRoutes!, newMap) != needsSerialization);
+
+    if (needsSerialization) {
+      _pageToPagelessRoutes = newMap;
+      notifyListeners();
+    }
+  }
+
+  void _finalizePage(
+    List<Object> routes,
+    _RouteEntry? page,
+    Map<String?, List<Object>> pageToRoutes,
+    Set<String?> pagesToRemove,
+  ) {
+    assert(page == null || page.hasPage);
+    assert(pageToRoutes != null);
+    assert(!pageToRoutes.containsKey(page?.restorationId));
+    if (routes != null && routes.isNotEmpty) {
+      assert(page == null || page.restorationId != null);
+      final String? restorationId = page?.restorationId;
+      pageToRoutes[restorationId] = routes;
+      pagesToRemove.remove(restorationId);
+    }
+  }
+
+  bool _debugMapsEqual(Map<String?, List<Object>> a, Map<String?, List<Object>> b) {
+    if (!setEquals(a.keys.toSet(), b.keys.toSet())) {
+      return false;
+    }
+    for (final String? key in a.keys) {
+      if (!listEquals(a[key], b[key])) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  void clear() {
+    assert(isRegistered);
+    if (_pageToPagelessRoutes == null) {
+      return;
+    }
+    _pageToPagelessRoutes = null;
+    notifyListeners();
+  }
+
+  // Restoration.
+
+  bool get hasData => _pageToPagelessRoutes != null;
+
+  List<_RouteEntry> restoreEntriesForPage(_RouteEntry? page, NavigatorState navigator) {
+    assert(isRegistered);
+    assert(page == null || page.hasPage);
+    final List<_RouteEntry> result = <_RouteEntry>[];
+    if (_pageToPagelessRoutes == null || (page != null && page.restorationId == null)) {
+      return result;
+    }
+    final List<Object>? serializedData = _pageToPagelessRoutes![page?.restorationId];
+    if (serializedData == null) {
+      return result;
+    }
+    for (final Object data in serializedData) {
+      result.add(_RestorationInformation.fromSerializableData(data).toRouteEntry(navigator));
+    }
+    return result;
+  }
+
+  // RestorableProperty overrides.
+
+  @override
+  Map<String?, List<Object>>? createDefaultValue() {
+    return null;
+  }
+
+  @override
+  Map<String?, List<Object>>? fromPrimitives(Object? data) {
+    final Map<dynamic, dynamic> casted = data! as Map<dynamic, dynamic>;
+    return casted.map<String?, List<Object>>((dynamic key, dynamic value) => MapEntry<String?, List<Object>>(
+      key as String?,
+      List<Object>.from(value as List<dynamic>, growable: true),
+    ));
+  }
+
+  @override
+  void initWithValue(Map<String?, List<Object>>? value) {
+    _pageToPagelessRoutes = value;
+  }
+
+  @override
+  Object? toPrimitives() {
+    return _pageToPagelessRoutes;
+  }
+
+  @override
+  bool get enabled => hasData;
+}
+
+/// A callback that given a [BuildContext] finds a [NavigatorState].
+///
+/// Used by [RestorableRouteFuture.navigatorFinder] to determine the navigator
+/// to which a new route should be added.
+typedef NavigatorFinderCallback = NavigatorState Function(BuildContext context);
+
+/// A callback that given some `arguments` and a `navigator` adds a new
+/// restorable route to that `navigator` and returns the opaque ID of that
+/// new route.
+///
+/// Usually, this callback calls one of the imperative methods on the Navigator
+/// that have "restorable" in the name and returns their return value.
+///
+/// Used by [RestorableRouteFuture.onPresent].
+typedef RoutePresentationCallback = String Function(NavigatorState navigator, Object? arguments);
+
+/// A callback to handle the result of a completed [Route].
+///
+/// The return value of the route (which can be null for e.g. void routes) is
+/// passed to the callback.
+///
+/// Used by [RestorableRouteFuture.onComplete].
+typedef RouteCompletionCallback<T> = void Function(T result);
+
+/// Gives access to a [Route] object and its return value that was added to a
+/// navigator via one of its "restorable" API methods.
+///
+/// When a [State] object wants access to the return value of a [Route] object
+/// it has pushed onto the [Navigator], a [RestorableRouteFuture] ensures that
+/// it will also have access to that value after state restoration.
+///
+/// To show a new route on the navigator defined by the [navigatorFinder], call
+/// [present], which will invoke the [onPresent] callback. The [onPresent]
+/// callback must add a new route to the navigator provided to it using one
+/// of the "restorable" API methods. When the newly added route completes, the
+/// [onComplete] callback executes. It is given the return value of the route,
+/// which may be null.
+///
+/// While the route added via [present] is shown on the navigator, it can be
+/// accessed via the [route] getter.
+///
+/// If the property is restored to a state in which [present] had been called on
+/// it, but the route has not completed yet, the [RestorableRouteFuture] will
+/// obtain the restored route object from the navigator again and call
+/// [onComplete] once it completes.
+///
+/// The [RestorableRouteFuture] can only keep track of one active [route].
+/// When [present] has been called to add a route, it may only be called again
+/// after the previously added route has completed.
+///
+/// {@tool dartpad --template=freeform_no_null_safety}
+/// This example uses a [RestorableRouteFuture] in the `_MyHomeState` to push a
+/// new `MyCounter` route and to retrieve its return value.
+///
+/// ```dart imports
+/// import 'package:flute/material.dart';
+/// ```
+///
+/// ```dart main
+/// void main() => runApp(MyApp());
+/// ```
+///
+/// ```dart preamble
+/// class MyApp extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return MaterialApp(
+///       restorationScopeId: 'app',
+///       home: Scaffold(
+///         appBar: AppBar(title: Text('RestorableRouteFuture Example')),
+///         body: MyHome(),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// class MyHome extends StatefulWidget {
+///   const MyHome({Key key}) : super(key: key);
+///
+///   @override
+///   State<MyHome> createState() => _MyHomeState();
+/// }
+///
+/// class _MyHomeState extends State<MyHome> with RestorationMixin {
+///   final RestorableInt _lastCount = RestorableInt(0);
+///   RestorableRouteFuture<int> _counterRoute;
+///
+///   @override
+///   String get restorationId => 'home';
+///
+///   void initState() {
+///     super.initState();
+///     _counterRoute = RestorableRouteFuture<int>(
+///       onPresent: (NavigatorState navigator, Object arguments) {
+///         // Defines what route should be shown (and how it should be added
+///         // to the navigator) when `RestorableRouteFuture.present` is called.
+///         return navigator.restorablePush(
+///           _counterRouteBuilder,
+///           arguments: arguments,
+///         );
+///       },
+///       onComplete: (int count) {
+///         // Defines what should happen with the return value when the route
+///         // completes.
+///         setState(() {
+///           _lastCount.value = count;
+///         });
+///       }
+///     );
+///   }
+///
+///   @override
+///   void restoreState(RestorationBucket oldBucket, bool initialRestore) {
+///     // Register the `RestorableRouteFuture` with the state restoration framework.
+///     registerForRestoration(_counterRoute, 'route');
+///     registerForRestoration(_lastCount, 'count');
+///   }
+///
+///   @override
+///   void dispose() {
+///     super.dispose();
+///     _lastCount.dispose();
+///     _counterRoute?.dispose();
+///   }
+///
+///   // A static `RestorableRouteBuilder` that can re-create the route during
+///   // state restoration.
+///   static Route<int> _counterRouteBuilder(BuildContext context, Object arguments) {
+///     return MaterialPageRoute(
+///       builder: (BuildContext context) => MyCounter(
+///         title: arguments as String,
+///       ),
+///     );
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Center(
+///       child: Column(
+///         mainAxisSize: MainAxisSize.min,
+///         children: <Widget>[
+///           Text('Last count: ${_lastCount.value}'),
+///           ElevatedButton(
+///             onPressed: () {
+///               // Show the route defined by the `RestorableRouteFuture`.
+///               _counterRoute.present('Awesome Counter');
+///             },
+///             child: Text('Open Counter'),
+///           ),
+///         ],
+///       ),
+///     );
+///   }
+/// }
+///
+/// // Widget for the route pushed by the `RestorableRouteFuture`.
+/// class MyCounter extends StatefulWidget {
+///   const MyCounter({Key key, this.title}) : super(key: key);
+///
+///   final String title;
+///
+///   @override
+///   State<MyCounter> createState() => _MyCounterState();
+/// }
+///
+/// class _MyCounterState extends State<MyCounter> with RestorationMixin {
+///   final RestorableInt _count = RestorableInt(0);
+///
+///   @override
+///   String get restorationId => 'counter';
+///
+///   @override
+///   void restoreState(RestorationBucket oldBucket, bool initialRestore) {
+///     registerForRestoration(_count, 'count');
+///   }
+///
+///   @override
+///   void dispose() {
+///     super.dispose();
+///     _count.dispose();
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Scaffold(
+///       appBar: AppBar(
+///         title: Text(widget.title),
+///         leading: BackButton(
+///           onPressed: () {
+///             // Return the current count of the counter from this route.
+///             Navigator.of(context).pop(_count.value);
+///           },
+///         ),
+///       ),
+///       body: Center(
+///         child: Text('Count: ${_count.value}'),
+///       ),
+///       floatingActionButton: FloatingActionButton(
+///         child: Icon(Icons.add),
+///         onPressed: () {
+///           setState(() {
+///             _count.value++;
+///           });
+///         },
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+class RestorableRouteFuture<T> extends RestorableProperty<String?> {
+  /// Creates a [RestorableRouteFuture].
+  ///
+  /// The [onPresent] and [navigatorFinder] arguments must not be null.
+  RestorableRouteFuture({
+    this.navigatorFinder = _defaultNavigatorFinder,
+    required this.onPresent,
+    this.onComplete,
+  }) : assert(onPresent != null), assert(navigatorFinder != null);
+
+  /// A callback that given the [BuildContext] of the [State] object to which
+  /// this property is registered returns the [NavigatorState] of the navigator
+  /// to which the route instantiated in [onPresent] is added.
+  final NavigatorFinderCallback navigatorFinder;
+
+  /// A callback that add a new [Route] to the provided navigator.
+  ///
+  /// The callback must use one of the API methods on the [NavigatorState] that
+  /// have "restorable" in their name (e.g. [NavigatorState.restorablePush],
+  /// [NavigatorState.restorablePushNamed], etc.) and return the opaque ID
+  /// returned by those methods.
+  ///
+  /// This callback is invoked when [present] is called with the `arguments`
+  /// Object that was passed to that method and the [NavigatorState] obtained
+  /// from [navigatorFinder].
+  final RoutePresentationCallback onPresent;
+
+  /// A callback that is invoked when the [Route] added via [onPresent]
+  /// completes.
+  ///
+  /// The return value of that route is passed to this method.
+  final RouteCompletionCallback<T>? onComplete;
+
+  /// Shows the route created by [onPresent] and invoke [onComplete] when it
+  /// completes.
+  ///
+  /// The `arguments` object is passed to [onPresent] and can be used to
+  /// customize the route. It must be serializable via the
+  /// [StandardMessageCodec]. Often, a [Map] is used to pass key-value pairs.
+  void present([Object? arguments]) {
+    assert(!isPresent);
+    assert(isRegistered);
+    final String routeId = onPresent(_navigator, arguments);
+    assert(routeId != null);
+    _hookOntoRouteFuture(routeId);
+    notifyListeners();
+  }
+
+  /// Whether the [Route] created by [present] is currently shown.
+  ///
+  /// Returns true after [present] has been called until the [Route] completes.
+  bool get isPresent => route != null;
+
+  /// The route that [present] added to the Navigator.
+  ///
+  /// Returns null when currently no route is shown
+  Route<T>? get route => _route;
+  Route<T>? _route;
+
+  @override
+  String? createDefaultValue() => null;
+
+  @override
+  void initWithValue(String? value) {
+    if (value != null) {
+      _hookOntoRouteFuture(value);
+    }
+  }
+
+  @override
+  Object? toPrimitives() {
+    assert(route != null);
+    assert(enabled);
+    return route?.restorationScopeId.value;
+  }
+
+  @override
+  String fromPrimitives(Object? data) {
+    assert(data != null);
+    return data! as String;
+  }
+
+  bool _disposed = false;
+
+  @override
+  void dispose() {
+    super.dispose();
+    _route?.restorationScopeId.removeListener(notifyListeners);
+    _disposed = true;
+  }
+
+  @override
+  bool get enabled => route?.restorationScopeId.value != null;
+
+  NavigatorState get _navigator {
+    final NavigatorState navigator = navigatorFinder(state.context);
+    assert(navigator != null);
+    return navigator;
+  }
+
+  void _hookOntoRouteFuture(String id) {
+    assert(id != null);
+    _route = _navigator._getRouteById<T>(id);
+    assert(_route != null);
+    route!.restorationScopeId.addListener(notifyListeners);
+    route!.popped.then((dynamic result) {
+      if (_disposed) {
+        return;
+      }
+      _route?.restorationScopeId.removeListener(notifyListeners);
+      _route = null;
+      notifyListeners();
+      if (onComplete != null) {
+        onComplete!(result as T);
+      }
+    });
+  }
+
+  static NavigatorState _defaultNavigatorFinder(BuildContext context) => Navigator.of(context);
+}
diff --git a/lib/src/widgets/nested_scroll_view.dart b/lib/src/widgets/nested_scroll_view.dart
new file mode 100644
index 0000000..ac29163
--- /dev/null
+++ b/lib/src/widgets/nested_scroll_view.dart
@@ -0,0 +1,2245 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/painting.dart';
+import 'package:flute/physics.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/gestures.dart' show DragStartBehavior;
+
+import 'basic.dart';
+import 'framework.dart';
+import 'primary_scroll_controller.dart';
+import 'scroll_activity.dart';
+import 'scroll_context.dart';
+import 'scroll_controller.dart';
+import 'scroll_metrics.dart';
+import 'scroll_physics.dart';
+import 'scroll_position.dart';
+import 'scroll_view.dart';
+import 'sliver_fill.dart';
+import 'ticker_provider.dart';
+import 'viewport.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// List<String> _tabs;
+
+/// Signature used by [NestedScrollView] for building its header.
+///
+/// The `innerBoxIsScrolled` argument is typically used to control the
+/// [SliverAppBar.forceElevated] property to ensure that the app bar shows a
+/// shadow, since it would otherwise not necessarily be aware that it had
+/// content ostensibly below it.
+typedef NestedScrollViewHeaderSliversBuilder = List<Widget> Function(BuildContext context, bool innerBoxIsScrolled);
+
+/// A scrolling view inside of which can be nested other scrolling views, with
+/// their scroll positions being intrinsically linked.
+///
+/// The most common use case for this widget is a scrollable view with a
+/// flexible [SliverAppBar] containing a [TabBar] in the header (built by
+/// [headerSliverBuilder], and with a [TabBarView] in the [body], such that the
+/// scrollable view's contents vary based on which tab is visible.
+///
+/// ## Motivation
+///
+/// In a normal [ScrollView], there is one set of slivers (the components of the
+/// scrolling view). If one of those slivers hosted a [TabBarView] which scrolls
+/// in the opposite direction (e.g. allowing the user to swipe horizontally
+/// between the pages represented by the tabs, while the list scrolls
+/// vertically), then any list inside that [TabBarView] would not interact with
+/// the outer [ScrollView]. For example, flinging the inner list to scroll to
+/// the top would not cause a collapsed [SliverAppBar] in the outer [ScrollView]
+/// to expand.
+///
+/// [NestedScrollView] solves this problem by providing custom
+/// [ScrollController]s for the outer [ScrollView] and the inner [ScrollView]s
+/// (those inside the [TabBarView], hooking them together so that they appear,
+/// to the user, as one coherent scroll view.
+///
+/// {@tool sample --template=stateless_widget_material_no_null_safety}
+///
+/// This example shows a [NestedScrollView] whose header is the combination of a
+/// [TabBar] in a [SliverAppBar] and whose body is a [TabBarView]. It uses a
+/// [SliverOverlapAbsorber]/[SliverOverlapInjector] pair to make the inner lists
+/// align correctly, and it uses [SafeArea] to avoid any horizontal disturbances
+/// (e.g. the "notch" on iOS when the phone is horizontal). In addition,
+/// [PageStorageKey]s are used to remember the scroll position of each tab's
+/// list.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   final List<String> _tabs = ['Tab 1', 'Tab 2'];
+///   return DefaultTabController(
+///     length: _tabs.length, // This is the number of tabs.
+///     child: Scaffold(
+///       body: NestedScrollView(
+///         headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
+///           // These are the slivers that show up in the "outer" scroll view.
+///           return <Widget>[
+///             SliverOverlapAbsorber(
+///               // This widget takes the overlapping behavior of the SliverAppBar,
+///               // and redirects it to the SliverOverlapInjector below. If it is
+///               // missing, then it is possible for the nested "inner" scroll view
+///               // below to end up under the SliverAppBar even when the inner
+///               // scroll view thinks it has not been scrolled.
+///               // This is not necessary if the "headerSliverBuilder" only builds
+///               // widgets that do not overlap the next sliver.
+///               handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
+///               sliver: SliverAppBar(
+///                 title: const Text('Books'), // This is the title in the app bar.
+///                 pinned: true,
+///                 expandedHeight: 150.0,
+///                 // The "forceElevated" property causes the SliverAppBar to show
+///                 // a shadow. The "innerBoxIsScrolled" parameter is true when the
+///                 // inner scroll view is scrolled beyond its "zero" point, i.e.
+///                 // when it appears to be scrolled below the SliverAppBar.
+///                 // Without this, there are cases where the shadow would appear
+///                 // or not appear inappropriately, because the SliverAppBar is
+///                 // not actually aware of the precise position of the inner
+///                 // scroll views.
+///                 forceElevated: innerBoxIsScrolled,
+///                 bottom: TabBar(
+///                   // These are the widgets to put in each tab in the tab bar.
+///                   tabs: _tabs.map((String name) => Tab(text: name)).toList(),
+///                 ),
+///               ),
+///             ),
+///           ];
+///         },
+///         body: TabBarView(
+///           // These are the contents of the tab views, below the tabs.
+///           children: _tabs.map((String name) {
+///             return SafeArea(
+///               top: false,
+///               bottom: false,
+///               child: Builder(
+///                 // This Builder is needed to provide a BuildContext that is
+///                 // "inside" the NestedScrollView, so that
+///                 // sliverOverlapAbsorberHandleFor() can find the
+///                 // NestedScrollView.
+///                 builder: (BuildContext context) {
+///                   return CustomScrollView(
+///                     // The "controller" and "primary" members should be left
+///                     // unset, so that the NestedScrollView can control this
+///                     // inner scroll view.
+///                     // If the "controller" property is set, then this scroll
+///                     // view will not be associated with the NestedScrollView.
+///                     // The PageStorageKey should be unique to this ScrollView;
+///                     // it allows the list to remember its scroll position when
+///                     // the tab view is not on the screen.
+///                     key: PageStorageKey<String>(name),
+///                     slivers: <Widget>[
+///                       SliverOverlapInjector(
+///                         // This is the flip side of the SliverOverlapAbsorber
+///                         // above.
+///                         handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
+///                       ),
+///                       SliverPadding(
+///                         padding: const EdgeInsets.all(8.0),
+///                         // In this example, the inner scroll view has
+///                         // fixed-height list items, hence the use of
+///                         // SliverFixedExtentList. However, one could use any
+///                         // sliver widget here, e.g. SliverList or SliverGrid.
+///                         sliver: SliverFixedExtentList(
+///                           // The items in this example are fixed to 48 pixels
+///                           // high. This matches the Material Design spec for
+///                           // ListTile widgets.
+///                           itemExtent: 48.0,
+///                           delegate: SliverChildBuilderDelegate(
+///                             (BuildContext context, int index) {
+///                               // This builder is called for each child.
+///                               // In this example, we just number each list item.
+///                               return ListTile(
+///                                 title: Text('Item $index'),
+///                               );
+///                             },
+///                             // The childCount of the SliverChildBuilderDelegate
+///                             // specifies how many children this inner list
+///                             // has. In this example, each tab has a list of
+///                             // exactly 30 items, but this is arbitrary.
+///                             childCount: 30,
+///                           ),
+///                         ),
+///                       ),
+///                     ],
+///                   );
+///                 },
+///               ),
+///             );
+///           }).toList(),
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ## [SliverAppBar]s with [NestedScrollView]s
+///
+/// Using a [SliverAppBar] in the outer scroll view, or [headerSliverBuilder],
+/// of a [NestedScrollView] may require special configurations in order to work
+/// as it would if the outer and inner were one single scroll view, like a
+/// [CustomScrollView].
+///
+/// ### Pinned [SliverAppBar]s
+///
+/// A pinned [SliverAppBar] works in a [NestedScrollView] exactly as it would in
+/// another scroll view, like [CustomScrollView]. When using
+/// [SliverAppBar.pinned], the app bar remains visible at the top of the scroll
+/// view. The app bar can still expand and contract as the user scrolls, but it
+/// will remain visible rather than being scrolled out of view.
+///
+/// This works naturally in a [NestedScrollView], as the pinned [SliverAppBar]
+/// is not expected to move in or out of the visible portion of the viewport.
+/// As the inner or outer [Scrollable]s are moved, the app bar persists as
+/// expected.
+///
+/// If the app bar is floating, pinned, and using an expanded height, follow the
+/// floating convention laid out below.
+///
+/// ### Floating [SliverAppBar]s
+///
+/// When placed in the outer scrollable, or the [headerSliverBuilder],
+/// a [SliverAppBar] that floats, using [SliverAppBar.floating] will not be
+/// triggered to float over the inner scroll view, or [body], automatically.
+///
+/// This is because a floating app bar uses the scroll offset of its own
+/// [Scrollable] to dictate the floating action. Being two separate inner and
+/// outer [Scrollable]s, a [SliverAppBar] in the outer header is not aware of
+/// changes in the scroll offset of the inner body.
+///
+/// In order to float the outer, use [NestedScrollView.floatHeaderSlivers]. When
+/// set to true, the nested scrolling coordinator will prioritize floating in
+/// the header slivers before applying the remaining drag to the body.
+///
+/// Furthermore, the `floatHeaderSlivers` flag should also be used when using an
+/// app bar that is floating, pinned, and has an expanded height. In this
+/// configuration, the flexible space of the app bar will open and collapse,
+/// while the primary portion of the app bar remains pinned.
+///
+/// {@tool sample --template=stateless_widget_material_no_null_safety}
+///
+/// This simple example shows a [NestedScrollView] whose header contains a
+/// floating [SliverAppBar]. By using the [floatHeaderSlivers] property, the
+/// floating behavior is coordinated between the outer and inner [Scrollable]s,
+/// so it behaves as it would in a single scrollable.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: NestedScrollView(
+///       // Setting floatHeaderSlivers to true is required in order to float
+///       // the outer slivers over the inner scrollable.
+///       floatHeaderSlivers: true,
+///       headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
+///         return <Widget>[
+///           SliverAppBar(
+///             title: const Text('Floating Nested SliverAppBar'),
+///             floating: true,
+///             expandedHeight: 200.0,
+///             forceElevated: innerBoxIsScrolled,
+///           ),
+///         ];
+///       },
+///       body: ListView.builder(
+///         padding: const EdgeInsets.all(8),
+///         itemCount: 30,
+///         itemBuilder: (BuildContext context, int index) {
+///           return Container(
+///             height: 50,
+///             child: Center(child: Text('Item $index')),
+///           );
+///         }
+///       )
+///     )
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ### Snapping [SliverAppBar]s
+///
+/// Floating [SliverAppBar]s also have the option to perform a snapping animation.
+/// If [SliverAppBar.snap] is true, then a scroll that exposes the floating app
+/// bar will trigger an animation that slides the entire app bar into view.
+/// Similarly if a scroll dismisses the app bar, the animation will slide the
+/// app bar completely out of view.
+///
+/// It is possible with a [NestedScrollView] to perform just the snapping
+/// animation without floating the app bar in and out. By not using the
+/// [NestedScrollView.floatHeaderSlivers], the app bar will snap in and out
+/// without floating.
+///
+/// The [SliverAppBar.snap] animation should be used in conjunction with the
+/// [SliverOverlapAbsorber] and  [SliverOverlapInjector] widgets when
+/// implemented in a [NestedScrollView]. These widgets take any overlapping
+/// behavior of the [SliverAppBar] in the header and redirect it to the
+/// [SliverOverlapInjector] in the body. If it is missing, then it is possible
+/// for the nested "inner" scroll view below to end up under the [SliverAppBar]
+/// even when the inner scroll view thinks it has not been scrolled.
+///
+/// {@tool sample --template=stateless_widget_material_no_null_safety}
+///
+/// This simple example shows a [NestedScrollView] whose header contains a
+/// snapping, floating [SliverAppBar]. _Without_ setting any additional flags,
+/// e.g [NestedScrollView.floatHeaderSlivers], the [SliverAppBar] will animate
+/// in and out without floating. The [SliverOverlapAbsorber] and
+/// [SliverOverlapInjector] maintain the proper alignment between the two
+/// separate scroll views.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: NestedScrollView(
+///       headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
+///         return <Widget>[
+///           SliverOverlapAbsorber(
+///             handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
+///             sliver: SliverAppBar(
+///               title: const Text('Snapping Nested SliverAppBar'),
+///               floating: true,
+///               snap: true,
+///               expandedHeight: 200.0,
+///               forceElevated: innerBoxIsScrolled,
+///             ),
+///           )
+///         ];
+///       },
+///       body: Builder(
+///         builder: (BuildContext context) {
+///           return CustomScrollView(
+///             // The "controller" and "primary" members should be left
+///             // unset, so that the NestedScrollView can control this
+///             // inner scroll view.
+///             // If the "controller" property is set, then this scroll
+///             // view will not be associated with the NestedScrollView.
+///             slivers: <Widget>[
+///               SliverOverlapInjector(handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context)),
+///               SliverFixedExtentList(
+///                 itemExtent: 48.0,
+///                 delegate: SliverChildBuilderDelegate(
+///                     (BuildContext context, int index) => ListTile(title: Text('Item $index')),
+///                   childCount: 30,
+///                 ),
+///               ),
+///             ],
+///           );
+///         }
+///       )
+///     )
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ### Snapping and Floating [SliverAppBar]s
+///
+// See https://github.com/flutter/flutter/issues/59189
+/// Currently, [NestedScrollView] does not support simultaneously floating and
+/// snapping the outer scrollable, e.g. when using [SliverAppBar.floating] &
+/// [SliverAppBar.snap] at the same time.
+///
+/// ### Stretching [SliverAppBar]s
+///
+// TODO(Piinks): Support stretching, https://github.com/flutter/flutter/issues/54059
+/// Currently, [NestedScrollView] does not support stretching the outer
+/// scrollable, e.g. when using [SliverAppBar.stretch].
+///
+/// See also:
+///
+///  * [SliverAppBar], for examples on different configurations like floating,
+///    pinned and snap behaviors.
+///  * [SliverOverlapAbsorber], a sliver that wraps another, forcing its layout
+///    extent to be treated as overlap.
+///  * [SliverOverlapInjector], a sliver that has a sliver geometry based on
+///    the values stored in a [SliverOverlapAbsorberHandle].
+class NestedScrollView extends StatefulWidget {
+  /// Creates a nested scroll view.
+  ///
+  /// The [reverse], [headerSliverBuilder], and [body] arguments must not be
+  /// null.
+  const NestedScrollView({
+    Key? key,
+    this.controller,
+    this.scrollDirection = Axis.vertical,
+    this.reverse = false,
+    this.physics,
+    required this.headerSliverBuilder,
+    required this.body,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.floatHeaderSlivers = false,
+    this.clipBehavior = Clip.hardEdge,
+    this.restorationId,
+  }) : assert(scrollDirection != null),
+       assert(reverse != null),
+       assert(headerSliverBuilder != null),
+       assert(body != null),
+       assert(floatHeaderSlivers != null),
+       assert(clipBehavior != null),
+       super(key: key);
+
+  /// An object that can be used to control the position to which the outer
+  /// scroll view is scrolled.
+  final ScrollController? controller;
+
+  /// The axis along which the scroll view scrolls.
+  ///
+  /// Defaults to [Axis.vertical].
+  final Axis scrollDirection;
+
+  /// Whether the scroll view scrolls in the reading direction.
+  ///
+  /// For example, if the reading direction is left-to-right and
+  /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from
+  /// left to right when [reverse] is false and from right to left when
+  /// [reverse] is true.
+  ///
+  /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
+  /// scrolls from top to bottom when [reverse] is false and from bottom to top
+  /// when [reverse] is true.
+  ///
+  /// Defaults to false.
+  final bool reverse;
+
+  /// How the scroll view should respond to user input.
+  ///
+  /// For example, determines how the scroll view continues to animate after the
+  /// user stops dragging the scroll view (providing a custom implementation of
+  /// [ScrollPhysics.createBallisticSimulation] allows this particular aspect of
+  /// the physics to be overridden).
+  ///
+  /// Defaults to matching platform conventions.
+  ///
+  /// The [ScrollPhysics.applyBoundaryConditions] implementation of the provided
+  /// object should not allow scrolling outside the scroll extent range
+  /// described by the [ScrollMetrics.minScrollExtent] and
+  /// [ScrollMetrics.maxScrollExtent] properties passed to that method. If that
+  /// invariant is not maintained, the nested scroll view may respond to user
+  /// scrolling erratically.
+  final ScrollPhysics? physics;
+
+  /// A builder for any widgets that are to precede the inner scroll views (as
+  /// given by [body]).
+  ///
+  /// Typically this is used to create a [SliverAppBar] with a [TabBar].
+  final NestedScrollViewHeaderSliversBuilder headerSliverBuilder;
+
+  /// The widget to show inside the [NestedScrollView].
+  ///
+  /// Typically this will be [TabBarView].
+  ///
+  /// The [body] is built in a context that provides a [PrimaryScrollController]
+  /// that interacts with the [NestedScrollView]'s scroll controller. Any
+  /// [ListView] or other [Scrollable]-based widget inside the [body] that is
+  /// intended to scroll with the [NestedScrollView] should therefore not be
+  /// given an explicit [ScrollController], instead allowing it to default to
+  /// the [PrimaryScrollController] provided by the [NestedScrollView].
+  final Widget body;
+
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  /// Whether or not the [NestedScrollView]'s coordinator should prioritize the
+  /// outer scrollable over the inner when scrolling back.
+  ///
+  /// This is useful for an outer scrollable containing a [SliverAppBar] that
+  /// is expected to float. This cannot be null.
+  final bool floatHeaderSlivers;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge].
+  final Clip clipBehavior;
+
+  /// {@macro flutter.widgets.scrollable.restorationId}
+  final String? restorationId;
+
+  /// Returns the [SliverOverlapAbsorberHandle] of the nearest ancestor
+  /// [NestedScrollView].
+  ///
+  /// This is necessary to configure the [SliverOverlapAbsorber] and
+  /// [SliverOverlapInjector] widgets.
+  ///
+  /// For sample code showing how to use this method, see the [NestedScrollView]
+  /// documentation.
+  static SliverOverlapAbsorberHandle sliverOverlapAbsorberHandleFor(BuildContext context) {
+    final _InheritedNestedScrollView? target = context.dependOnInheritedWidgetOfExactType<_InheritedNestedScrollView>();
+    assert(
+      target != null,
+      'NestedScrollView.sliverOverlapAbsorberHandleFor must be called with a context that contains a NestedScrollView.',
+    );
+    return target!.state._absorberHandle;
+  }
+
+  List<Widget> _buildSlivers(BuildContext context, ScrollController innerController, bool bodyIsScrolled) {
+    return <Widget>[
+      ...headerSliverBuilder(context, bodyIsScrolled),
+      SliverFillRemaining(
+        child: PrimaryScrollController(
+          controller: innerController,
+          child: body,
+        ),
+      ),
+    ];
+  }
+
+  @override
+  NestedScrollViewState createState() => NestedScrollViewState();
+}
+
+/// The [State] for a [NestedScrollView].
+///
+/// The [ScrollController]s, [innerController] and [outerController], of the
+/// [NestedScrollView]'s children may be accessed through its state. This is
+/// useful for obtaining respective scroll positions in the [NestedScrollView].
+///
+/// If you want to access the inner or outer scroll controller of a
+/// [NestedScrollView], you can get its [NestedScrollViewState] by supplying a
+/// `GlobalKey<NestedScrollViewState>` to the [NestedScrollView.key] parameter).
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+/// [NestedScrollViewState] can be obtained using a [GlobalKey].
+/// Using the following setup, you can access the inner scroll controller
+/// using `globalKey.currentState.innerController`.
+///
+/// ```dart preamble
+/// final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
+/// ```
+/// ```dart
+/// @override
+/// Widget build(BuildContext context) {
+///   return NestedScrollView(
+///     key: globalKey,
+///     headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
+///       return <Widget>[
+///         SliverAppBar(
+///           title: Text('NestedScrollViewState Demo!'),
+///         ),
+///       ];
+///     },
+///     body: CustomScrollView(
+///       // Body slivers go here!
+///     ),
+///   );
+/// }
+///
+/// ScrollController get innerController {
+///   return globalKey.currentState.innerController;
+/// }
+/// ```
+/// {@end-tool}
+class NestedScrollViewState extends State<NestedScrollView> {
+  final SliverOverlapAbsorberHandle _absorberHandle = SliverOverlapAbsorberHandle();
+
+  /// The [ScrollController] provided to the [ScrollView] in
+  /// [NestedScrollView.body].
+  ///
+  /// Manipulating the [ScrollPosition] of this controller pushes the outer
+  /// header sliver(s) up and out of view. The position of the [outerController]
+  /// will be set to [ScrollPosition.maxScrollExtent], unless you use
+  /// [ScrollPosition.setPixels].
+  ///
+  /// See also:
+  ///
+  ///  * [outerController], which exposes the [ScrollController] used by the
+  ///    sliver(s) contained in [NestedScrollView.headerSliverBuilder].
+  ScrollController get innerController => _coordinator!._innerController;
+
+  /// The [ScrollController] provided to the [ScrollView] in
+  /// [NestedScrollView.headerSliverBuilder].
+  ///
+  /// This is equivalent to [NestedScrollView.controller], if provided.
+  ///
+  /// Manipulating the [ScrollPosition] of this controller pushes the inner body
+  /// sliver(s) down. The position of the [innerController] will be set to
+  /// [ScrollPosition.minScrollExtent], unless you use
+  /// [ScrollPosition.setPixels]. Visually, the inner body will be scrolled to
+  /// its beginning.
+  ///
+  /// See also:
+  ///
+  ///  * [innerController], which exposes the [ScrollController] used by the
+  ///    [ScrollView] contained in [NestedScrollView.body].
+  ScrollController get outerController => _coordinator!._outerController;
+
+  _NestedScrollCoordinator? _coordinator;
+
+  @override
+  void initState() {
+    super.initState();
+    _coordinator = _NestedScrollCoordinator(
+      this,
+      widget.controller,
+      _handleHasScrolledBodyChanged,
+      widget.floatHeaderSlivers,
+    );
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _coordinator!.setParent(widget.controller);
+  }
+
+  @override
+  void didUpdateWidget(NestedScrollView oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.controller != widget.controller)
+      _coordinator!.setParent(widget.controller);
+  }
+
+  @override
+  void dispose() {
+    _coordinator!.dispose();
+    _coordinator = null;
+    super.dispose();
+  }
+
+  bool? _lastHasScrolledBody;
+
+  void _handleHasScrolledBodyChanged() {
+    if (!mounted)
+      return;
+    final bool newHasScrolledBody = _coordinator!.hasScrolledBody;
+    if (_lastHasScrolledBody != newHasScrolledBody) {
+      setState(() {
+        // _coordinator.hasScrolledBody changed (we use it in the build method)
+        // (We record _lastHasScrolledBody in the build() method, rather than in
+        // this setState call, because the build() method may be called more
+        // often than just from here, and we want to only call setState when the
+        // new value is different than the last built value.)
+      });
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _InheritedNestedScrollView(
+      state: this,
+      child: Builder(
+        builder: (BuildContext context) {
+          _lastHasScrolledBody = _coordinator!.hasScrolledBody;
+          return _NestedScrollViewCustomScrollView(
+            dragStartBehavior: widget.dragStartBehavior,
+            scrollDirection: widget.scrollDirection,
+            reverse: widget.reverse,
+            physics: widget.physics != null
+              ? widget.physics!.applyTo(const ClampingScrollPhysics())
+              : const ClampingScrollPhysics(),
+            controller: _coordinator!._outerController,
+            slivers: widget._buildSlivers(
+              context,
+              _coordinator!._innerController,
+              _lastHasScrolledBody!,
+            ),
+            handle: _absorberHandle,
+            clipBehavior: widget.clipBehavior,
+            restorationId: widget.restorationId,
+          );
+        },
+      ),
+    );
+  }
+}
+
+class _NestedScrollViewCustomScrollView extends CustomScrollView {
+  const _NestedScrollViewCustomScrollView({
+    required Axis scrollDirection,
+    required bool reverse,
+    required ScrollPhysics physics,
+    required ScrollController controller,
+    required List<Widget> slivers,
+    required this.handle,
+    required Clip clipBehavior,
+    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
+    String? restorationId,
+  }) : super(
+         scrollDirection: scrollDirection,
+         reverse: reverse,
+         physics: physics,
+         controller: controller,
+         slivers: slivers,
+         dragStartBehavior: dragStartBehavior,
+         restorationId: restorationId,
+         clipBehavior: clipBehavior,
+       );
+
+  final SliverOverlapAbsorberHandle handle;
+
+  @override
+  Widget buildViewport(
+    BuildContext context,
+    ViewportOffset offset,
+    AxisDirection axisDirection,
+    List<Widget> slivers,
+  ) {
+    assert(!shrinkWrap);
+    return NestedScrollViewViewport(
+      axisDirection: axisDirection,
+      offset: offset,
+      slivers: slivers,
+      handle: handle,
+      clipBehavior: clipBehavior,
+    );
+  }
+}
+
+class _InheritedNestedScrollView extends InheritedWidget {
+  const _InheritedNestedScrollView({
+    Key? key,
+    required this.state,
+    required Widget child,
+  }) : assert(state != null),
+       assert(child != null),
+       super(key: key, child: child);
+
+  final NestedScrollViewState state;
+
+  @override
+  bool updateShouldNotify(_InheritedNestedScrollView old) => state != old.state;
+}
+
+class _NestedScrollMetrics extends FixedScrollMetrics {
+  _NestedScrollMetrics({
+    required double? minScrollExtent,
+    required double? maxScrollExtent,
+    required double? pixels,
+    required double? viewportDimension,
+    required AxisDirection axisDirection,
+    required this.minRange,
+    required this.maxRange,
+    required this.correctionOffset,
+  }) : super(
+    minScrollExtent: minScrollExtent,
+    maxScrollExtent: maxScrollExtent,
+    pixels: pixels,
+    viewportDimension: viewportDimension,
+    axisDirection: axisDirection,
+  );
+
+  @override
+  _NestedScrollMetrics copyWith({
+    double? minScrollExtent,
+    double? maxScrollExtent,
+    double? pixels,
+    double? viewportDimension,
+    AxisDirection? axisDirection,
+    double? minRange,
+    double? maxRange,
+    double? correctionOffset,
+  }) {
+    return _NestedScrollMetrics(
+      minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
+      maxScrollExtent: maxScrollExtent ?? (hasContentDimensions ? this.maxScrollExtent : null),
+      pixels: pixels ?? (hasPixels ? this.pixels : null),
+      viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
+      axisDirection: axisDirection ?? this.axisDirection,
+      minRange: minRange ?? this.minRange,
+      maxRange: maxRange ?? this.maxRange,
+      correctionOffset: correctionOffset ?? this.correctionOffset,
+    );
+  }
+
+  final double minRange;
+
+  final double maxRange;
+
+  final double correctionOffset;
+}
+
+typedef _NestedScrollActivityGetter = ScrollActivity Function(_NestedScrollPosition position);
+
+class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldController {
+  _NestedScrollCoordinator(
+    this._state,
+    this._parent,
+    this._onHasScrolledBodyChanged,
+    this._floatHeaderSlivers,
+  ) {
+    final double initialScrollOffset = _parent?.initialScrollOffset ?? 0.0;
+    _outerController = _NestedScrollController(
+      this,
+      initialScrollOffset: initialScrollOffset,
+      debugLabel: 'outer',
+    );
+    _innerController = _NestedScrollController(
+      this,
+      initialScrollOffset: 0.0,
+      debugLabel: 'inner',
+    );
+  }
+
+  final NestedScrollViewState _state;
+  ScrollController? _parent;
+  final VoidCallback _onHasScrolledBodyChanged;
+  final bool _floatHeaderSlivers;
+
+  late _NestedScrollController _outerController;
+  late _NestedScrollController _innerController;
+
+  _NestedScrollPosition? get _outerPosition {
+    if (!_outerController.hasClients)
+      return null;
+    return _outerController.nestedPositions.single;
+  }
+
+  Iterable<_NestedScrollPosition> get _innerPositions {
+    return _innerController.nestedPositions;
+  }
+
+  bool get canScrollBody {
+    final _NestedScrollPosition? outer = _outerPosition;
+    if (outer == null)
+      return true;
+    return outer.haveDimensions && outer.extentAfter == 0.0;
+  }
+
+  bool get hasScrolledBody {
+    for (final _NestedScrollPosition position in _innerPositions) {
+      assert(position.hasContentDimensions && position.hasPixels);
+      if (position.pixels > position.minScrollExtent) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  void updateShadow() { _onHasScrolledBodyChanged(); }
+
+  ScrollDirection get userScrollDirection => _userScrollDirection;
+  ScrollDirection _userScrollDirection = ScrollDirection.idle;
+
+  void updateUserScrollDirection(ScrollDirection value) {
+    assert(value != null);
+    if (userScrollDirection == value)
+      return;
+    _userScrollDirection = value;
+    _outerPosition!.didUpdateScrollDirection(value);
+    for (final _NestedScrollPosition position in _innerPositions)
+      position.didUpdateScrollDirection(value);
+  }
+
+  ScrollDragController? _currentDrag;
+
+  void beginActivity(ScrollActivity newOuterActivity, _NestedScrollActivityGetter innerActivityGetter) {
+    _outerPosition!.beginActivity(newOuterActivity);
+    bool scrolling = newOuterActivity.isScrolling;
+    for (final _NestedScrollPosition position in _innerPositions) {
+      final ScrollActivity newInnerActivity = innerActivityGetter(position);
+      position.beginActivity(newInnerActivity);
+      scrolling = scrolling && newInnerActivity.isScrolling;
+    }
+    _currentDrag?.dispose();
+    _currentDrag = null;
+    if (!scrolling)
+      updateUserScrollDirection(ScrollDirection.idle);
+  }
+
+  @override
+  AxisDirection get axisDirection => _outerPosition!.axisDirection;
+
+  static IdleScrollActivity _createIdleScrollActivity(_NestedScrollPosition position) {
+    return IdleScrollActivity(position);
+  }
+
+  @override
+  void goIdle() {
+    beginActivity(
+      _createIdleScrollActivity(_outerPosition!),
+      _createIdleScrollActivity,
+    );
+  }
+
+  @override
+  void goBallistic(double velocity) {
+    beginActivity(
+      createOuterBallisticScrollActivity(velocity),
+      (_NestedScrollPosition position) {
+        return createInnerBallisticScrollActivity(
+          position,
+          velocity,
+        );
+      },
+    );
+  }
+
+  ScrollActivity createOuterBallisticScrollActivity(double velocity) {
+    // This function creates a ballistic scroll for the outer scrollable.
+    //
+    // It assumes that the outer scrollable can't be overscrolled, and sets up a
+    // ballistic scroll over the combined space of the innerPositions and the
+    // outerPosition.
+
+    // First we must pick a representative inner position that we will care
+    // about. This is somewhat arbitrary. Ideally we'd pick the one that is "in
+    // the center" but there isn't currently a good way to do that so we
+    // arbitrarily pick the one that is the furthest away from the infinity we
+    // are heading towards.
+    _NestedScrollPosition? innerPosition;
+    if (velocity != 0.0) {
+      for (final _NestedScrollPosition position in _innerPositions) {
+        if (innerPosition != null) {
+          if (velocity > 0.0) {
+            if (innerPosition.pixels < position.pixels)
+              continue;
+          } else {
+            assert(velocity < 0.0);
+            if (innerPosition.pixels > position.pixels)
+              continue;
+          }
+        }
+        innerPosition = position;
+      }
+    }
+
+    if (innerPosition == null) {
+      // It's either just us or a velocity=0 situation.
+      return _outerPosition!.createBallisticScrollActivity(
+        _outerPosition!.physics.createBallisticSimulation(
+          _outerPosition!,
+          velocity,
+        ),
+        mode: _NestedBallisticScrollActivityMode.independent,
+      );
+    }
+
+    final _NestedScrollMetrics metrics = _getMetrics(innerPosition, velocity);
+
+    return _outerPosition!.createBallisticScrollActivity(
+      _outerPosition!.physics.createBallisticSimulation(metrics, velocity),
+      mode: _NestedBallisticScrollActivityMode.outer,
+      metrics: metrics,
+    );
+  }
+
+  @protected
+  ScrollActivity createInnerBallisticScrollActivity(_NestedScrollPosition position, double velocity) {
+    return position.createBallisticScrollActivity(
+      position.physics.createBallisticSimulation(
+        _getMetrics(position, velocity),
+        velocity,
+      ),
+      mode: _NestedBallisticScrollActivityMode.inner,
+    );
+  }
+
+  _NestedScrollMetrics _getMetrics(_NestedScrollPosition innerPosition, double velocity) {
+    assert(innerPosition != null);
+    double pixels, minRange, maxRange, correctionOffset;
+    double extra = 0.0;
+    if (innerPosition.pixels == innerPosition.minScrollExtent) {
+      pixels = _outerPosition!.pixels.clamp(
+        _outerPosition!.minScrollExtent,
+        _outerPosition!.maxScrollExtent,
+      ); // TODO(ianh): gracefully handle out-of-range outer positions
+      minRange = _outerPosition!.minScrollExtent;
+      maxRange = _outerPosition!.maxScrollExtent;
+      assert(minRange <= maxRange);
+      correctionOffset = 0.0;
+    } else {
+      assert(innerPosition.pixels != innerPosition.minScrollExtent);
+      if (innerPosition.pixels < innerPosition.minScrollExtent) {
+        pixels = innerPosition.pixels - innerPosition.minScrollExtent + _outerPosition!.minScrollExtent;
+      } else {
+        assert(innerPosition.pixels > innerPosition.minScrollExtent);
+        pixels = innerPosition.pixels - innerPosition.minScrollExtent + _outerPosition!.maxScrollExtent;
+      }
+      if ((velocity > 0.0) && (innerPosition.pixels > innerPosition.minScrollExtent)) {
+        // This handles going forward (fling up) and inner list is scrolled past
+        // zero. We want to grab the extra pixels immediately to shrink.
+        extra = _outerPosition!.maxScrollExtent - _outerPosition!.pixels;
+        assert(extra >= 0.0);
+        minRange = pixels;
+        maxRange = pixels + extra;
+        assert(minRange <= maxRange);
+        correctionOffset = _outerPosition!.pixels - pixels;
+      } else if ((velocity < 0.0) && (innerPosition.pixels < innerPosition.minScrollExtent)) {
+        // This handles going backward (fling down) and inner list is
+        // underscrolled. We want to grab the extra pixels immediately to grow.
+        extra = _outerPosition!.pixels - _outerPosition!.minScrollExtent;
+        assert(extra >= 0.0);
+        minRange = pixels - extra;
+        maxRange = pixels;
+        assert(minRange <= maxRange);
+        correctionOffset = _outerPosition!.pixels - pixels;
+      } else {
+        // This handles going forward (fling up) and inner list is
+        // underscrolled, OR, going backward (fling down) and inner list is
+        // scrolled past zero. We want to skip the pixels we don't need to grow
+        // or shrink over.
+        if (velocity > 0.0) {
+          // shrinking
+          extra = _outerPosition!.minScrollExtent - _outerPosition!.pixels;
+        } else if (velocity < 0.0) {
+          // growing
+          extra = _outerPosition!.pixels - (_outerPosition!.maxScrollExtent - _outerPosition!.minScrollExtent);
+        }
+        assert(extra <= 0.0);
+        minRange = _outerPosition!.minScrollExtent;
+        maxRange = _outerPosition!.maxScrollExtent + extra;
+        assert(minRange <= maxRange);
+        correctionOffset = 0.0;
+      }
+    }
+    return _NestedScrollMetrics(
+      minScrollExtent: _outerPosition!.minScrollExtent,
+      maxScrollExtent: _outerPosition!.maxScrollExtent + innerPosition.maxScrollExtent - innerPosition.minScrollExtent + extra,
+      pixels: pixels,
+      viewportDimension: _outerPosition!.viewportDimension,
+      axisDirection: _outerPosition!.axisDirection,
+      minRange: minRange,
+      maxRange: maxRange,
+      correctionOffset: correctionOffset,
+    );
+  }
+
+  double unnestOffset(double value, _NestedScrollPosition source) {
+    if (source == _outerPosition)
+      return value.clamp(
+        _outerPosition!.minScrollExtent,
+        _outerPosition!.maxScrollExtent,
+      );
+    if (value < source.minScrollExtent)
+      return value - source.minScrollExtent + _outerPosition!.minScrollExtent;
+    return value - source.minScrollExtent + _outerPosition!.maxScrollExtent;
+  }
+
+  double nestOffset(double value, _NestedScrollPosition target) {
+    if (target == _outerPosition)
+      return value.clamp(
+        _outerPosition!.minScrollExtent,
+        _outerPosition!.maxScrollExtent,
+      );
+    if (value < _outerPosition!.minScrollExtent)
+      return value - _outerPosition!.minScrollExtent + target.minScrollExtent;
+    if (value > _outerPosition!.maxScrollExtent)
+      return value - _outerPosition!.maxScrollExtent + target.minScrollExtent;
+    return target.minScrollExtent;
+  }
+
+  void updateCanDrag() {
+    if (!_outerPosition!.haveDimensions)
+      return;
+    double maxInnerExtent = 0.0;
+    for (final _NestedScrollPosition position in _innerPositions) {
+      if (!position.haveDimensions)
+        return;
+      maxInnerExtent = math.max(
+        maxInnerExtent,
+        position.maxScrollExtent - position.minScrollExtent,
+      );
+    }
+    _outerPosition!.updateCanDrag(maxInnerExtent);
+  }
+
+  Future<void> animateTo(
+    double to, {
+    required Duration duration,
+    required Curve curve,
+  }) async {
+    final DrivenScrollActivity outerActivity = _outerPosition!.createDrivenScrollActivity(
+      nestOffset(to, _outerPosition!),
+      duration,
+      curve,
+    );
+    final List<Future<void>> resultFutures = <Future<void>>[outerActivity.done];
+    beginActivity(
+      outerActivity,
+      (_NestedScrollPosition position) {
+        final DrivenScrollActivity innerActivity = position.createDrivenScrollActivity(
+          nestOffset(to, position),
+          duration,
+          curve,
+        );
+        resultFutures.add(innerActivity.done);
+        return innerActivity;
+      },
+    );
+    await Future.wait<void>(resultFutures);
+  }
+
+  void jumpTo(double to) {
+    goIdle();
+    _outerPosition!.localJumpTo(nestOffset(to, _outerPosition!));
+    for (final _NestedScrollPosition position in _innerPositions)
+      position.localJumpTo(nestOffset(to, position));
+    goBallistic(0.0);
+  }
+
+  void pointerScroll(double delta) {
+    assert(delta != 0.0);
+
+    goIdle();
+    updateUserScrollDirection(
+        delta < 0.0 ? ScrollDirection.forward : ScrollDirection.reverse
+    );
+
+    if (_innerPositions.isEmpty) {
+      // Does not enter overscroll.
+      _outerPosition!.applyClampedPointerSignalUpdate(delta);
+    } else if (delta > 0.0) {
+      // Dragging "up" - delta is positive
+      // Prioritize getting rid of any inner overscroll, and then the outer
+      // view, so that the app bar will scroll out of the way asap.
+      double outerDelta = delta;
+      for (final _NestedScrollPosition position in _innerPositions) {
+        if (position.pixels < 0.0) { // This inner position is in overscroll.
+          final double potentialOuterDelta = position.applyClampedPointerSignalUpdate(delta);
+          // In case there are multiple positions in varying states of
+          // overscroll, the first to 'reach' the outer view above takes
+          // precedence.
+          outerDelta = math.max(outerDelta, potentialOuterDelta);
+        }
+      }
+      if (outerDelta != 0.0) {
+        final double innerDelta = _outerPosition!.applyClampedPointerSignalUpdate(
+            outerDelta
+        );
+        if (innerDelta != 0.0) {
+          for (final _NestedScrollPosition position in _innerPositions)
+            position.applyClampedPointerSignalUpdate(innerDelta);
+        }
+      }
+    } else {
+      // Dragging "down" - delta is negative
+      double innerDelta = delta;
+      // Apply delta to the outer header first if it is configured to float.
+      if (_floatHeaderSlivers)
+        innerDelta = _outerPosition!.applyClampedPointerSignalUpdate(delta);
+
+      if (innerDelta != 0.0) {
+        // Apply the innerDelta, if we have not floated in the outer scrollable,
+        // any leftover delta after this will be passed on to the outer
+        // scrollable by the outerDelta.
+        double outerDelta = 0.0; // it will go negative if it changes
+        for (final _NestedScrollPosition position in _innerPositions) {
+          final double overscroll = position.applyClampedPointerSignalUpdate(innerDelta);
+          outerDelta = math.min(outerDelta, overscroll);
+        }
+        if (outerDelta != 0.0)
+          _outerPosition!.applyClampedPointerSignalUpdate(outerDelta);
+      }
+    }
+    goBallistic(0.0);
+  }
+
+  @override
+  double setPixels(double newPixels) {
+    assert(false);
+    return 0.0;
+  }
+
+  ScrollHoldController hold(VoidCallback holdCancelCallback) {
+    beginActivity(
+      HoldScrollActivity(
+        delegate: _outerPosition!,
+        onHoldCanceled: holdCancelCallback,
+      ),
+      (_NestedScrollPosition position) => HoldScrollActivity(delegate: position),
+    );
+    return this;
+  }
+
+  @override
+  void cancel() {
+    goBallistic(0.0);
+  }
+
+  Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) {
+    final ScrollDragController drag = ScrollDragController(
+      delegate: this,
+      details: details,
+      onDragCanceled: dragCancelCallback,
+    );
+    beginActivity(
+      DragScrollActivity(_outerPosition!, drag),
+      (_NestedScrollPosition position) => DragScrollActivity(position, drag),
+    );
+    assert(_currentDrag == null);
+    _currentDrag = drag;
+    return drag;
+  }
+
+  @override
+  void applyUserOffset(double delta) {
+    updateUserScrollDirection(
+      delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse
+    );
+    assert(delta != 0.0);
+    if (_innerPositions.isEmpty) {
+      _outerPosition!.applyFullDragUpdate(delta);
+    } else if (delta < 0.0) {
+      // Dragging "up"
+      // Prioritize getting rid of any inner overscroll, and then the outer
+      // view, so that the app bar will scroll out of the way asap.
+      double outerDelta = delta;
+      for (final _NestedScrollPosition position in _innerPositions) {
+        if (position.pixels < 0.0) { // This inner position is in overscroll.
+          final double potentialOuterDelta = position.applyClampedDragUpdate(delta);
+          // In case there are multiple positions in varying states of
+          // overscroll, the first to 'reach' the outer view above takes
+          // precedence.
+          outerDelta = math.max(outerDelta, potentialOuterDelta);
+        }
+      }
+      if (outerDelta != 0.0) {
+        final double innerDelta = _outerPosition!.applyClampedDragUpdate(
+          outerDelta
+        );
+        if (innerDelta != 0.0) {
+          for (final _NestedScrollPosition position in _innerPositions)
+            position.applyFullDragUpdate(innerDelta);
+        }
+      }
+    } else {
+      // Dragging "down" - delta is positive
+      double innerDelta = delta;
+      // Apply delta to the outer header first if it is configured to float.
+      if (_floatHeaderSlivers)
+        innerDelta = _outerPosition!.applyClampedDragUpdate(delta);
+
+      if (innerDelta != 0.0) {
+        // Apply the innerDelta, if we have not floated in the outer scrollable,
+        // any leftover delta after this will be passed on to the outer
+        // scrollable by the outerDelta.
+        double outerDelta = 0.0; // it will go positive if it changes
+        final List<double> overscrolls = <double>[];
+        final List<_NestedScrollPosition> innerPositions = _innerPositions.toList();
+        for (final _NestedScrollPosition position in innerPositions) {
+          final double overscroll = position.applyClampedDragUpdate(innerDelta);
+          outerDelta = math.max(outerDelta, overscroll);
+          overscrolls.add(overscroll);
+        }
+        if (outerDelta != 0.0)
+          outerDelta -= _outerPosition!.applyClampedDragUpdate(outerDelta);
+
+        // Now deal with any overscroll
+        // TODO(Piinks): Configure which scrollable receives overscroll to
+        // support stretching app bars. createOuterBallisticScrollActivity will
+        // need to be updated as it currently assumes the outer position will
+        // never overscroll, https://github.com/flutter/flutter/issues/54059
+        for (int i = 0; i < innerPositions.length; ++i) {
+          final double remainingDelta = overscrolls[i] - outerDelta;
+          if (remainingDelta > 0.0)
+            innerPositions[i].applyFullDragUpdate(remainingDelta);
+        }
+      }
+    }
+  }
+
+  void setParent(ScrollController? value) {
+    _parent = value;
+    updateParent();
+  }
+
+  void updateParent() {
+    _outerPosition?.setParent(
+      _parent ?? PrimaryScrollController.of(_state.context)
+    );
+  }
+
+  @mustCallSuper
+  void dispose() {
+    _currentDrag?.dispose();
+    _currentDrag = null;
+    _outerController.dispose();
+    _innerController.dispose();
+  }
+
+  @override
+  String toString() => '${objectRuntimeType(this, '_NestedScrollCoordinator')}(outer=$_outerController; inner=$_innerController)';
+}
+
+class _NestedScrollController extends ScrollController {
+  _NestedScrollController(
+    this.coordinator, {
+    double initialScrollOffset = 0.0,
+    String? debugLabel,
+  }) : super(initialScrollOffset: initialScrollOffset, debugLabel: debugLabel);
+
+  final _NestedScrollCoordinator coordinator;
+
+  @override
+  ScrollPosition createScrollPosition(
+    ScrollPhysics physics,
+    ScrollContext context,
+    ScrollPosition? oldPosition,
+  ) {
+    return _NestedScrollPosition(
+      coordinator: coordinator,
+      physics: physics,
+      context: context,
+      initialPixels: initialScrollOffset,
+      oldPosition: oldPosition,
+      debugLabel: debugLabel,
+    );
+  }
+
+  @override
+  void attach(ScrollPosition position) {
+    assert(position is _NestedScrollPosition);
+    super.attach(position);
+    coordinator.updateParent();
+    coordinator.updateCanDrag();
+    position.addListener(_scheduleUpdateShadow);
+    _scheduleUpdateShadow();
+  }
+
+  @override
+  void detach(ScrollPosition position) {
+    assert(position is _NestedScrollPosition);
+    position.removeListener(_scheduleUpdateShadow);
+    super.detach(position);
+    _scheduleUpdateShadow();
+  }
+
+  void _scheduleUpdateShadow() {
+    // We do this asynchronously for attach() so that the new position has had
+    // time to be initialized, and we do it asynchronously for detach() and from
+    // the position change notifications because those happen synchronously
+    // during a frame, at a time where it's too late to call setState. Since the
+    // result is usually animated, the lag incurred is no big deal.
+    SchedulerBinding.instance!.addPostFrameCallback(
+      (Duration timeStamp) {
+        coordinator.updateShadow();
+      }
+    );
+  }
+
+  Iterable<_NestedScrollPosition> get nestedPositions sync* {
+    // TODO(vegorov): use instance method version of castFrom when it is available.
+    yield* Iterable.castFrom<ScrollPosition, _NestedScrollPosition>(positions);
+  }
+}
+
+// The _NestedScrollPosition is used by both the inner and outer viewports of a
+// NestedScrollView. It tracks the offset to use for those viewports, and knows
+// about the _NestedScrollCoordinator, so that when activities are triggered on
+// this class, they can defer, or be influenced by, the coordinator.
+class _NestedScrollPosition extends ScrollPosition implements ScrollActivityDelegate {
+  _NestedScrollPosition({
+    required ScrollPhysics physics,
+    required ScrollContext context,
+    double initialPixels = 0.0,
+    ScrollPosition? oldPosition,
+    String? debugLabel,
+    required this.coordinator,
+  }) : super(
+    physics: physics,
+    context: context,
+    oldPosition: oldPosition,
+    debugLabel: debugLabel,
+  ) {
+    if (!hasPixels && initialPixels != null)
+      correctPixels(initialPixels);
+    if (activity == null)
+      goIdle();
+    assert(activity != null);
+    saveScrollOffset(); // in case we didn't restore but could, so that we don't restore it later
+  }
+
+  final _NestedScrollCoordinator coordinator;
+
+  TickerProvider get vsync => context.vsync;
+
+  ScrollController? _parent;
+
+  void setParent(ScrollController? value) {
+    _parent?.detach(this);
+    _parent = value;
+    _parent?.attach(this);
+  }
+
+  @override
+  AxisDirection get axisDirection => context.axisDirection;
+
+  @override
+  void absorb(ScrollPosition other) {
+    super.absorb(other);
+    activity!.updateDelegate(this);
+  }
+
+  @override
+  void restoreScrollOffset() {
+    if (coordinator.canScrollBody)
+      super.restoreScrollOffset();
+  }
+
+  // Returns the amount of delta that was not used.
+  //
+  // Positive delta means going down (exposing stuff above), negative delta
+  // going up (exposing stuff below).
+  double applyClampedDragUpdate(double delta) {
+    assert(delta != 0.0);
+    // If we are going towards the maxScrollExtent (negative scroll offset),
+    // then the furthest we can be in the minScrollExtent direction is negative
+    // infinity. For example, if we are already overscrolled, then scrolling to
+    // reduce the overscroll should not disallow the overscroll.
+    //
+    // If we are going towards the minScrollExtent (positive scroll offset),
+    // then the furthest we can be in the minScrollExtent direction is wherever
+    // we are now, if we are already overscrolled (in which case pixels is less
+    // than the minScrollExtent), or the minScrollExtent if we are not.
+    //
+    // In other words, we cannot, via applyClampedDragUpdate, _enter_ an
+    // overscroll situation.
+    //
+    // An overscroll situation might be nonetheless entered via several means.
+    // One is if the physics allow it, via applyFullDragUpdate (see below). An
+    // overscroll situation can also be forced, e.g. if the scroll position is
+    // artificially set using the scroll controller.
+    final double min = delta < 0.0
+      ? -double.infinity
+      : math.min(minScrollExtent, pixels);
+    // The logic for max is equivalent but on the other side.
+    final double max = delta > 0.0
+      ? double.infinity
+      // If pixels < 0.0, then we are currently in overscroll. The max should be
+      // 0.0, representing the end of the overscrolled portion.
+      : pixels < 0.0 ? 0.0 : math.max(maxScrollExtent, pixels);
+    final double oldPixels = pixels;
+    final double newPixels = (pixels - delta).clamp(min, max);
+    final double clampedDelta = newPixels - pixels;
+    if (clampedDelta == 0.0)
+      return delta;
+    final double overscroll = physics.applyBoundaryConditions(this, newPixels);
+    final double actualNewPixels = newPixels - overscroll;
+    final double offset = actualNewPixels - oldPixels;
+    if (offset != 0.0) {
+      forcePixels(actualNewPixels);
+      didUpdateScrollPositionBy(offset);
+    }
+    return delta + offset;
+  }
+
+  // Returns the overscroll.
+  double applyFullDragUpdate(double delta) {
+    assert(delta != 0.0);
+    final double oldPixels = pixels;
+    // Apply friction:
+    final double newPixels = pixels - physics.applyPhysicsToUserOffset(
+      this,
+      delta,
+    );
+    if (oldPixels == newPixels)
+      return 0.0; // delta must have been so small we dropped it during floating point addition
+    // Check for overscroll:
+    final double overscroll = physics.applyBoundaryConditions(this, newPixels);
+    final double actualNewPixels = newPixels - overscroll;
+    if (actualNewPixels != oldPixels) {
+      forcePixels(actualNewPixels);
+      didUpdateScrollPositionBy(actualNewPixels - oldPixels);
+    }
+    if (overscroll != 0.0) {
+      didOverscrollBy(overscroll);
+      return overscroll;
+    }
+    return 0.0;
+  }
+
+
+  // Returns the amount of delta that was not used.
+  //
+  // Negative delta represents a forward ScrollDirection, while the positive
+  // would be a reverse ScrollDirection.
+  //
+  // The method doesn't take into account the effects of [ScrollPhysics].
+  double applyClampedPointerSignalUpdate(double delta) {
+    assert(delta != 0.0);
+
+    final double min = delta > 0.0
+        ? -double.infinity
+        : math.min(minScrollExtent, pixels);
+    // The logic for max is equivalent but on the other side.
+    final double max = delta < 0.0
+        ? double.infinity
+        : math.max(maxScrollExtent, pixels);
+    final double newPixels = (pixels + delta).clamp(min, max);
+    final double clampedDelta = newPixels - pixels;
+    if (clampedDelta == 0.0)
+      return delta;
+    forcePixels(newPixels);
+    didUpdateScrollPositionBy(clampedDelta);
+    return delta - clampedDelta;
+  }
+
+  @override
+  ScrollDirection get userScrollDirection => coordinator.userScrollDirection;
+
+  DrivenScrollActivity createDrivenScrollActivity(double to, Duration duration, Curve curve) {
+    return DrivenScrollActivity(
+      this,
+      from: pixels,
+      to: to,
+      duration: duration,
+      curve: curve,
+      vsync: vsync,
+    );
+  }
+
+  @override
+  double applyUserOffset(double delta) {
+    assert(false);
+    return 0.0;
+  }
+
+  // This is called by activities when they finish their work.
+  @override
+  void goIdle() {
+    beginActivity(IdleScrollActivity(this));
+  }
+
+  // This is called by activities when they finish their work and want to go
+  // ballistic.
+  @override
+  void goBallistic(double velocity) {
+    Simulation? simulation;
+    if (velocity != 0.0 || outOfRange)
+      simulation = physics.createBallisticSimulation(this, velocity);
+    beginActivity(createBallisticScrollActivity(
+      simulation,
+      mode: _NestedBallisticScrollActivityMode.independent,
+    ));
+  }
+
+  ScrollActivity createBallisticScrollActivity(
+    Simulation? simulation, {
+    required _NestedBallisticScrollActivityMode mode,
+    _NestedScrollMetrics? metrics,
+  }) {
+    if (simulation == null)
+      return IdleScrollActivity(this);
+    assert(mode != null);
+    switch (mode) {
+      case _NestedBallisticScrollActivityMode.outer:
+        assert(metrics != null);
+        if (metrics!.minRange == metrics.maxRange)
+          return IdleScrollActivity(this);
+        return _NestedOuterBallisticScrollActivity(
+          coordinator,
+          this,
+          metrics,
+          simulation,
+          context.vsync,
+        );
+      case _NestedBallisticScrollActivityMode.inner:
+        return _NestedInnerBallisticScrollActivity(
+          coordinator,
+          this,
+          simulation,
+          context.vsync,
+        );
+      case _NestedBallisticScrollActivityMode.independent:
+        return BallisticScrollActivity(this, simulation, context.vsync);
+    }
+  }
+
+  @override
+  Future<void> animateTo(
+    double to, {
+    required Duration duration,
+    required Curve curve,
+  }) {
+    return coordinator.animateTo(
+      coordinator.unnestOffset(to, this),
+      duration: duration,
+      curve: curve,
+    );
+  }
+
+  @override
+  void jumpTo(double value) {
+    return coordinator.jumpTo(coordinator.unnestOffset(value, this));
+  }
+
+  @override
+  void pointerScroll(double delta) {
+    return coordinator.pointerScroll(delta);
+  }
+
+
+  @override
+  void jumpToWithoutSettling(double value) {
+    assert(false);
+  }
+
+  void localJumpTo(double value) {
+    if (pixels != value) {
+      final double oldPixels = pixels;
+      forcePixels(value);
+      didStartScroll();
+      didUpdateScrollPositionBy(pixels - oldPixels);
+      didEndScroll();
+    }
+  }
+
+  @override
+  void applyNewDimensions() {
+    super.applyNewDimensions();
+    coordinator.updateCanDrag();
+  }
+
+  void updateCanDrag(double totalExtent) {
+    context.setCanDrag(totalExtent > (viewportDimension - maxScrollExtent) || minScrollExtent != maxScrollExtent);
+  }
+
+  @override
+  ScrollHoldController hold(VoidCallback holdCancelCallback) {
+    return coordinator.hold(holdCancelCallback);
+  }
+
+  @override
+  Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) {
+    return coordinator.drag(details, dragCancelCallback);
+  }
+
+  @override
+  void dispose() {
+    _parent?.detach(this);
+    super.dispose();
+  }
+}
+
+enum _NestedBallisticScrollActivityMode { outer, inner, independent }
+
+class _NestedInnerBallisticScrollActivity extends BallisticScrollActivity {
+  _NestedInnerBallisticScrollActivity(
+    this.coordinator,
+    _NestedScrollPosition position,
+    Simulation simulation,
+    TickerProvider vsync,
+  ) : super(position, simulation, vsync);
+
+  final _NestedScrollCoordinator coordinator;
+
+  @override
+  _NestedScrollPosition get delegate => super.delegate as _NestedScrollPosition;
+
+  @override
+  void resetActivity() {
+    delegate.beginActivity(coordinator.createInnerBallisticScrollActivity(
+      delegate,
+      velocity,
+    ));
+  }
+
+  @override
+  void applyNewDimensions() {
+    delegate.beginActivity(coordinator.createInnerBallisticScrollActivity(
+      delegate,
+      velocity,
+    ));
+  }
+
+  @override
+  bool applyMoveTo(double value) {
+    return super.applyMoveTo(coordinator.nestOffset(value, delegate));
+  }
+}
+
+class _NestedOuterBallisticScrollActivity extends BallisticScrollActivity {
+  _NestedOuterBallisticScrollActivity(
+    this.coordinator,
+    _NestedScrollPosition position,
+    this.metrics,
+    Simulation simulation,
+    TickerProvider vsync,
+  ) : assert(metrics.minRange != metrics.maxRange),
+      assert(metrics.maxRange > metrics.minRange),
+      super(position, simulation, vsync);
+
+  final _NestedScrollCoordinator coordinator;
+  final _NestedScrollMetrics metrics;
+
+  @override
+  _NestedScrollPosition get delegate => super.delegate as _NestedScrollPosition;
+
+  @override
+  void resetActivity() {
+    delegate.beginActivity(
+      coordinator.createOuterBallisticScrollActivity(velocity)
+    );
+  }
+
+  @override
+  void applyNewDimensions() {
+    delegate.beginActivity(
+      coordinator.createOuterBallisticScrollActivity(velocity)
+    );
+  }
+
+  @override
+  bool applyMoveTo(double value) {
+    bool done = false;
+    if (velocity > 0.0) {
+      if (value < metrics.minRange)
+        return true;
+      if (value > metrics.maxRange) {
+        value = metrics.maxRange;
+        done = true;
+      }
+    } else if (velocity < 0.0) {
+      if (value > metrics.maxRange)
+        return true;
+      if (value < metrics.minRange) {
+        value = metrics.minRange;
+        done = true;
+      }
+    } else {
+      value = value.clamp(metrics.minRange, metrics.maxRange);
+      done = true;
+    }
+    final bool result = super.applyMoveTo(value + metrics.correctionOffset);
+    assert(result); // since we tried to pass an in-range value, it shouldn't ever overflow
+    return !done;
+  }
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, '_NestedOuterBallisticScrollActivity')}(${metrics.minRange} .. ${metrics.maxRange}; correcting by ${metrics.correctionOffset})';
+  }
+}
+
+/// Handle to provide to a [SliverOverlapAbsorber], a [SliverOverlapInjector],
+/// and an [NestedScrollViewViewport], to shift overlap in a [NestedScrollView].
+///
+/// A particular [SliverOverlapAbsorberHandle] can only be assigned to a single
+/// [SliverOverlapAbsorber] at a time. It can also be (and normally is) assigned
+/// to one or more [SliverOverlapInjector]s, which must be later descendants of
+/// the same [NestedScrollViewViewport] as the [SliverOverlapAbsorber]. The
+/// [SliverOverlapAbsorber] must be a direct descendant of the
+/// [NestedScrollViewViewport], taking part in the same sliver layout. (The
+/// [SliverOverlapInjector] can be a descendant that takes part in a nested
+/// scroll view's sliver layout.)
+///
+/// Whenever the [NestedScrollViewViewport] is marked dirty for layout, it will
+/// cause its assigned [SliverOverlapAbsorberHandle] to fire notifications. It
+/// is the responsibility of the [SliverOverlapInjector]s (and any other
+/// clients) to mark themselves dirty when this happens, in case the geometry
+/// subsequently changes during layout.
+///
+/// See also:
+///
+///  * [NestedScrollView], which uses a [NestedScrollViewViewport] and a
+///    [SliverOverlapAbsorber] to align its children, and which shows sample
+///    usage for this class.
+class SliverOverlapAbsorberHandle extends ChangeNotifier {
+  // Incremented when a RenderSliverOverlapAbsorber takes ownership of this
+  // object, decremented when it releases it. This allows us to find cases where
+  // the same handle is being passed to two render objects.
+  int _writers = 0;
+
+  /// The current amount of overlap being absorbed by the
+  /// [SliverOverlapAbsorber].
+  ///
+  /// This corresponds to the [SliverGeometry.layoutExtent] of the child of the
+  /// [SliverOverlapAbsorber].
+  ///
+  /// This is updated during the layout of the [SliverOverlapAbsorber]. It
+  /// should not change at any other time. No notifications are sent when it
+  /// changes; clients (e.g. [SliverOverlapInjector]s) are responsible for
+  /// marking themselves dirty whenever this object sends notifications, which
+  /// happens any time the [SliverOverlapAbsorber] might subsequently change the
+  /// value during that layout.
+  double? get layoutExtent => _layoutExtent;
+  double? _layoutExtent;
+
+  /// The total scroll extent of the gap being absorbed by the
+  /// [SliverOverlapAbsorber].
+  ///
+  /// This corresponds to the [SliverGeometry.scrollExtent] of the child of the
+  /// [SliverOverlapAbsorber].
+  ///
+  /// This is updated during the layout of the [SliverOverlapAbsorber]. It
+  /// should not change at any other time. No notifications are sent when it
+  /// changes; clients (e.g. [SliverOverlapInjector]s) are responsible for
+  /// marking themselves dirty whenever this object sends notifications, which
+  /// happens any time the [SliverOverlapAbsorber] might subsequently change the
+  /// value during that layout.
+  double? get scrollExtent => _scrollExtent;
+  double? _scrollExtent;
+
+  void _setExtents(double? layoutValue, double? scrollValue) {
+    assert(
+      _writers == 1,
+      'Multiple RenderSliverOverlapAbsorbers have been provided the same SliverOverlapAbsorberHandle.',
+    );
+    _layoutExtent = layoutValue;
+    _scrollExtent = scrollValue;
+  }
+
+  void _markNeedsLayout() => notifyListeners();
+
+  @override
+  String toString() {
+    String? extra;
+    switch (_writers) {
+      case 0:
+        extra = ', orphan';
+        break;
+      case 1:
+        // normal case
+        break;
+      default:
+        extra = ', $_writers WRITERS ASSIGNED';
+        break;
+    }
+    return '${objectRuntimeType(this, 'SliverOverlapAbsorberHandle')}($layoutExtent$extra)';
+  }
+}
+
+/// A sliver that wraps another, forcing its layout extent to be treated as
+/// overlap.
+///
+/// The difference between the overlap requested by the child `sliver` and the
+/// overlap reported by this widget, called the _absorbed overlap_, is reported
+/// to the [SliverOverlapAbsorberHandle], which is typically passed to a
+/// [SliverOverlapInjector].
+///
+/// See also:
+///
+///  * [NestedScrollView], whose documentation has sample code showing how to
+///    use this widget.
+class SliverOverlapAbsorber extends SingleChildRenderObjectWidget {
+  /// Creates a sliver that absorbs overlap and reports it to a
+  /// [SliverOverlapAbsorberHandle].
+  ///
+  /// The [handle] must not be null.
+  const SliverOverlapAbsorber({
+    Key? key,
+    required this.handle,
+    Widget? sliver,
+  }) : assert(handle != null),
+      super(key: key, child: sliver);
+
+  /// The object in which the absorbed overlap is recorded.
+  ///
+  /// A particular [SliverOverlapAbsorberHandle] can only be assigned to a
+  /// single [SliverOverlapAbsorber] at a time.
+  final SliverOverlapAbsorberHandle handle;
+
+  @override
+  RenderSliverOverlapAbsorber createRenderObject(BuildContext context) {
+    return RenderSliverOverlapAbsorber(
+      handle: handle,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderSliverOverlapAbsorber renderObject) {
+    renderObject.handle = handle;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<SliverOverlapAbsorberHandle>('handle', handle));
+  }
+}
+
+/// A sliver that wraps another, forcing its layout extent to be treated as
+/// overlap.
+///
+/// The difference between the overlap requested by the child `sliver` and the
+/// overlap reported by this widget, called the _absorbed overlap_, is reported
+/// to the [SliverOverlapAbsorberHandle], which is typically passed to a
+/// [RenderSliverOverlapInjector].
+class RenderSliverOverlapAbsorber extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
+  /// Create a sliver that absorbs overlap and reports it to a
+  /// [SliverOverlapAbsorberHandle].
+  ///
+  /// The [handle] must not be null.
+  ///
+  /// The [sliver] must be a [RenderSliver].
+  RenderSliverOverlapAbsorber({
+    required SliverOverlapAbsorberHandle handle,
+    RenderSliver? sliver,
+  }) : assert(handle != null),
+       _handle = handle {
+    child = sliver;
+  }
+
+  /// The object in which the absorbed overlap is recorded.
+  ///
+  /// A particular [SliverOverlapAbsorberHandle] can only be assigned to a
+  /// single [RenderSliverOverlapAbsorber] at a time.
+  SliverOverlapAbsorberHandle get handle => _handle;
+  SliverOverlapAbsorberHandle _handle;
+  set handle(SliverOverlapAbsorberHandle value) {
+    assert(value != null);
+    if (handle == value)
+      return;
+    if (attached) {
+      handle._writers -= 1;
+      value._writers += 1;
+      value._setExtents(handle.layoutExtent, handle.scrollExtent);
+    }
+    _handle = value;
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    handle._writers += 1;
+  }
+
+  @override
+  void detach() {
+    handle._writers -= 1;
+    super.detach();
+  }
+
+  @override
+  void performLayout() {
+    assert(
+      handle._writers == 1,
+      'A SliverOverlapAbsorberHandle cannot be passed to multiple RenderSliverOverlapAbsorber objects at the same time.',
+    );
+    if (child == null) {
+      geometry = const SliverGeometry();
+      return;
+    }
+    child!.layout(constraints, parentUsesSize: true);
+    final SliverGeometry childLayoutGeometry = child!.geometry!;
+    geometry = SliverGeometry(
+      scrollExtent: childLayoutGeometry.scrollExtent - childLayoutGeometry.maxScrollObstructionExtent,
+      paintExtent: childLayoutGeometry.paintExtent,
+      paintOrigin: childLayoutGeometry.paintOrigin,
+      layoutExtent: math.max(0, childLayoutGeometry.paintExtent - childLayoutGeometry.maxScrollObstructionExtent),
+      maxPaintExtent: childLayoutGeometry.maxPaintExtent,
+      maxScrollObstructionExtent: childLayoutGeometry.maxScrollObstructionExtent,
+      hitTestExtent: childLayoutGeometry.hitTestExtent,
+      visible: childLayoutGeometry.visible,
+      hasVisualOverflow: childLayoutGeometry.hasVisualOverflow,
+      scrollOffsetCorrection: childLayoutGeometry.scrollOffsetCorrection,
+    );
+    handle._setExtents(
+      childLayoutGeometry.maxScrollObstructionExtent,
+      childLayoutGeometry.maxScrollObstructionExtent,
+    );
+  }
+
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) {
+    // child is always at our origin
+  }
+
+  @override
+  bool hitTestChildren(SliverHitTestResult result, { required double mainAxisPosition, required double crossAxisPosition }) {
+    if (child != null)
+      return child!.hitTest(
+        result,
+        mainAxisPosition: mainAxisPosition,
+        crossAxisPosition: crossAxisPosition,
+      );
+    return false;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null)
+      context.paintChild(child!, offset);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<SliverOverlapAbsorberHandle>('handle', handle));
+  }
+}
+
+/// A sliver that has a sliver geometry based on the values stored in a
+/// [SliverOverlapAbsorberHandle].
+///
+/// The [SliverOverlapAbsorber] must be an earlier descendant of a common
+/// ancestor [Viewport], so that it will always be laid out before the
+/// [SliverOverlapInjector] during a particular frame.
+///
+/// See also:
+///
+///  * [NestedScrollView], which uses a [SliverOverlapAbsorber] to align its
+///    children, and which shows sample usage for this class.
+class SliverOverlapInjector extends SingleChildRenderObjectWidget {
+  /// Creates a sliver that is as tall as the value of the given [handle]'s
+  /// layout extent.
+  ///
+  /// The [handle] must not be null.
+  const SliverOverlapInjector({
+    Key? key,
+    required this.handle,
+    Widget? sliver,
+  }) : assert(handle != null),
+       super(key: key, child: sliver);
+
+  /// The handle to the [SliverOverlapAbsorber] that is feeding this injector.
+  ///
+  /// This should be a handle owned by a [SliverOverlapAbsorber] and a
+  /// [NestedScrollViewViewport].
+  final SliverOverlapAbsorberHandle handle;
+
+  @override
+  RenderSliverOverlapInjector createRenderObject(BuildContext context) {
+    return RenderSliverOverlapInjector(
+      handle: handle,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderSliverOverlapInjector renderObject) {
+    renderObject.handle = handle;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<SliverOverlapAbsorberHandle>('handle', handle));
+  }
+}
+
+/// A sliver that has a sliver geometry based on the values stored in a
+/// [SliverOverlapAbsorberHandle].
+///
+/// The [RenderSliverOverlapAbsorber] must be an earlier descendant of a common
+/// ancestor [RenderViewport] (probably a [RenderNestedScrollViewViewport]), so
+/// that it will always be laid out before the [RenderSliverOverlapInjector]
+/// during a particular frame.
+class RenderSliverOverlapInjector extends RenderSliver {
+  /// Creates a sliver that is as tall as the value of the given [handle]'s extent.
+  ///
+  /// The [handle] must not be null.
+  RenderSliverOverlapInjector({
+    required SliverOverlapAbsorberHandle handle,
+  }) : assert(handle != null),
+       _handle = handle;
+
+  double? _currentLayoutExtent;
+  double? _currentMaxExtent;
+
+  /// The object that specifies how wide to make the gap injected by this render
+  /// object.
+  ///
+  /// This should be a handle owned by a [RenderSliverOverlapAbsorber] and a
+  /// [RenderNestedScrollViewViewport].
+  SliverOverlapAbsorberHandle get handle => _handle;
+  SliverOverlapAbsorberHandle _handle;
+  set handle(SliverOverlapAbsorberHandle value) {
+    assert(value != null);
+    if (handle == value)
+      return;
+    if (attached) {
+      handle.removeListener(markNeedsLayout);
+    }
+    _handle = value;
+    if (attached) {
+      handle.addListener(markNeedsLayout);
+      if (handle.layoutExtent != _currentLayoutExtent ||
+          handle.scrollExtent != _currentMaxExtent)
+        markNeedsLayout();
+    }
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    handle.addListener(markNeedsLayout);
+    if (handle.layoutExtent != _currentLayoutExtent ||
+        handle.scrollExtent != _currentMaxExtent)
+      markNeedsLayout();
+  }
+
+  @override
+  void detach() {
+    handle.removeListener(markNeedsLayout);
+    super.detach();
+  }
+
+  @override
+  void performLayout() {
+    _currentLayoutExtent = handle.layoutExtent;
+    _currentMaxExtent = handle.layoutExtent;
+    final double clampedLayoutExtent = math.min(
+      _currentLayoutExtent! - constraints.scrollOffset,
+      constraints.remainingPaintExtent,
+    );
+    geometry = SliverGeometry(
+      scrollExtent: _currentLayoutExtent!,
+      paintExtent: math.max(0.0, clampedLayoutExtent),
+      maxPaintExtent: _currentMaxExtent!,
+    );
+  }
+
+  @override
+  void debugPaint(PaintingContext context, Offset offset) {
+    assert(() {
+      if (debugPaintSizeEnabled) {
+        final Paint paint = Paint()
+          ..color = const Color(0xFFCC9933)
+          ..strokeWidth = 3.0
+          ..style = PaintingStyle.stroke;
+        Offset start, end, delta;
+        switch (constraints.axis) {
+          case Axis.vertical:
+            final double x = offset.dx + constraints.crossAxisExtent / 2.0;
+            start = Offset(x, offset.dy);
+            end = Offset(x, offset.dy + geometry!.paintExtent);
+            delta = Offset(constraints.crossAxisExtent / 5.0, 0.0);
+            break;
+          case Axis.horizontal:
+            final double y = offset.dy + constraints.crossAxisExtent / 2.0;
+            start = Offset(offset.dx, y);
+            end = Offset(offset.dy + geometry!.paintExtent, y);
+            delta = Offset(0.0, constraints.crossAxisExtent / 5.0);
+            break;
+        }
+        for (int index = -2; index <= 2; index += 1) {
+          paintZigZag(
+            context.canvas,
+            paint,
+            start - delta * index.toDouble(),
+            end - delta * index.toDouble(),
+            10,
+            10.0,
+          );
+        }
+      }
+      return true;
+    }());
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<SliverOverlapAbsorberHandle>('handle', handle));
+  }
+}
+
+/// The [Viewport] variant used by [NestedScrollView].
+///
+/// This viewport takes a [SliverOverlapAbsorberHandle] and notifies it any time
+/// the viewport needs to recompute its layout (e.g. when it is scrolled).
+class NestedScrollViewViewport extends Viewport {
+  /// Creates a variant of [Viewport] that has a [SliverOverlapAbsorberHandle].
+  ///
+  /// The [handle] must not be null.
+  NestedScrollViewViewport({
+    Key? key,
+    AxisDirection axisDirection = AxisDirection.down,
+    AxisDirection? crossAxisDirection,
+    double anchor = 0.0,
+    required ViewportOffset offset,
+    Key? center,
+    List<Widget> slivers = const <Widget>[],
+    required this.handle,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : assert(handle != null),
+       super(
+         key: key,
+         axisDirection: axisDirection,
+         crossAxisDirection: crossAxisDirection,
+         anchor: anchor,
+         offset: offset,
+         center: center,
+         slivers: slivers,
+         clipBehavior: clipBehavior,
+       );
+
+  /// The handle to the [SliverOverlapAbsorber] that is feeding this injector.
+  final SliverOverlapAbsorberHandle handle;
+
+  @override
+  RenderNestedScrollViewViewport createRenderObject(BuildContext context) {
+    return RenderNestedScrollViewViewport(
+      axisDirection: axisDirection,
+      crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(
+        context,
+        axisDirection,
+      ),
+      anchor: anchor,
+      offset: offset,
+      handle: handle,
+      clipBehavior: clipBehavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderNestedScrollViewViewport renderObject) {
+    renderObject
+      ..axisDirection = axisDirection
+      ..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(
+        context,
+        axisDirection,
+      )
+      ..anchor = anchor
+      ..offset = offset
+      ..handle = handle
+      ..clipBehavior = clipBehavior;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<SliverOverlapAbsorberHandle>('handle', handle));
+  }
+}
+
+/// The [RenderViewport] variant used by [NestedScrollView].
+///
+/// This viewport takes a [SliverOverlapAbsorberHandle] and notifies it any time
+/// the viewport needs to recompute its layout (e.g. when it is scrolled).
+class RenderNestedScrollViewViewport extends RenderViewport {
+  /// Create a variant of [RenderViewport] that has a
+  /// [SliverOverlapAbsorberHandle].
+  ///
+  /// The [handle] must not be null.
+  RenderNestedScrollViewViewport({
+    AxisDirection axisDirection = AxisDirection.down,
+    required AxisDirection crossAxisDirection,
+    required ViewportOffset offset,
+    double anchor = 0.0,
+    List<RenderSliver>? children,
+    RenderSliver? center,
+    required SliverOverlapAbsorberHandle handle,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : assert(handle != null),
+       _handle = handle,
+       super(
+         axisDirection: axisDirection,
+         crossAxisDirection: crossAxisDirection,
+         offset: offset,
+         anchor: anchor,
+         children: children,
+         center: center,
+         clipBehavior: clipBehavior,
+       );
+
+  /// The object to notify when [markNeedsLayout] is called.
+  SliverOverlapAbsorberHandle get handle => _handle;
+  SliverOverlapAbsorberHandle _handle;
+  /// Setting this will trigger notifications on the new object.
+  set handle(SliverOverlapAbsorberHandle value) {
+    assert(value != null);
+    if (handle == value)
+      return;
+    _handle = value;
+    handle._markNeedsLayout();
+  }
+
+  @override
+  void markNeedsLayout() {
+    handle._markNeedsLayout();
+    super.markNeedsLayout();
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<SliverOverlapAbsorberHandle>('handle', handle));
+  }
+}
diff --git a/lib/src/widgets/notification_listener.dart b/lib/src/widgets/notification_listener.dart
new file mode 100644
index 0000000..0a52cda
--- /dev/null
+++ b/lib/src/widgets/notification_listener.dart
@@ -0,0 +1,244 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'framework.dart';
+
+/// Signature for [Notification] listeners.
+///
+/// Return true to cancel the notification bubbling. Return false to allow the
+/// notification to continue to be dispatched to further ancestors.
+///
+/// [NotificationListener] is useful when listening scroll events
+/// in [ListView],[NestedScrollView],[GridView] or any Scrolling widgets.
+/// Used by [NotificationListener.onNotification].
+
+typedef NotificationListenerCallback<T extends Notification> = bool Function(T notification);
+
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+///
+/// This example shows a [NotificationListener] widget
+/// that listens for [ScrollNotification] notifications. When a scroll
+/// event occurs in the [NestedScrollView],
+/// this widget is notified. The events could be either a
+/// [ScrollStartNotification]or[ScrollEndNotification].
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+/// const List<String> _tabs = ["Months", "Days"];
+/// const List<String> _months = [ "January","February","March", ];
+/// const List<String> _days = [ "Sunday", "Monday","Tuesday", ];
+///   return DefaultTabController(
+///     length: _tabs.length,
+///     child: Scaffold(
+///       // Listens to the scroll events and returns the current position.
+///       body: NotificationListener<ScrollNotification>(
+///         onNotification: (scrollNotification) {
+///           if (scrollNotification is ScrollStartNotification) {
+///             print('Scrolling has started');
+///           } else if (scrollNotification is ScrollEndNotification) {
+///             print("Scrolling has ended");
+///           }
+///           // Return true to cancel the notification bubbling.
+///           return true;
+///         },
+///         child: NestedScrollView(
+///           headerSliverBuilder:
+///               (BuildContext context, bool innerBoxIsScrolled) {
+///             return <Widget>[
+///               SliverAppBar(
+///                 title: const Text("Flutter Code Sample"),
+///                 pinned: true,
+///                 floating: true,
+///                 bottom: TabBar(
+///                   tabs: _tabs.map((name) => Tab(text: name)).toList(),
+///                 ),
+///               ),
+///             ];
+///           },
+///           body: TabBarView(
+///             children: [
+///               ListView.builder(
+///                 itemCount: _months.length,
+///                 itemBuilder: (context, index) {
+///                   return ListTile(title: Text(_months[index]));
+///                 },
+///               ),
+///               ListView.builder(
+///                 itemCount: _days.length,
+///                 itemBuilder: (context, index) {
+///                   return ListTile(title: Text(_days[index]));
+///                 },
+///               ),
+///            ],
+///           ),
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ScrollNotification] which describes the notification lifecycle.
+///  * [ScrollStartNotification] which returns the start position of scrolling.
+///  * [ScrollEndNotification] which returns the end position of scrolling.
+///  * [NestedScrollView] which creates a nested scroll view.
+///
+/// A notification that can bubble up the widget tree.
+///
+/// You can determine the type of a notification using the `is` operator to
+/// check the [runtimeType] of the notification.
+///
+/// To listen for notifications in a subtree, use a [NotificationListener].
+///
+/// To send a notification, call [dispatch] on the notification you wish to
+/// send. The notification will be delivered to any [NotificationListener]
+/// widgets with the appropriate type parameters that are ancestors of the given
+/// [BuildContext].
+abstract class Notification {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const Notification();
+
+  /// Applied to each ancestor of the [dispatch] target.
+  ///
+  /// The [Notification] class implementation of this method dispatches the
+  /// given [Notification] to each ancestor [NotificationListener] widget.
+  ///
+  /// Subclasses can override this to apply additional filtering or to update
+  /// the notification as it is bubbled (for example, increasing a `depth` field
+  /// for each ancestor of a particular type).
+  @protected
+  @mustCallSuper
+  bool visitAncestor(Element element) {
+    if (element is StatelessElement) {
+      final StatelessWidget widget = element.widget;
+      if (widget is NotificationListener<Notification>) {
+        if (widget._dispatch(this, element)) // that function checks the type dynamically
+          return false;
+      }
+    }
+    return true;
+  }
+
+  /// Start bubbling this notification at the given build context.
+  ///
+  /// The notification will be delivered to any [NotificationListener] widgets
+  /// with the appropriate type parameters that are ancestors of the given
+  /// [BuildContext]. If the [BuildContext] is null, the notification is not
+  /// dispatched.
+  void dispatch(BuildContext? target) {
+    // The `target` may be null if the subtree the notification is supposed to be
+    // dispatched in is in the process of being disposed.
+    target?.visitAncestorElements(visitAncestor);
+  }
+
+  @override
+  String toString() {
+    final List<String> description = <String>[];
+    debugFillDescription(description);
+    return '${objectRuntimeType(this, 'Notification')}(${description.join(", ")})';
+  }
+
+  /// Add additional information to the given description for use by [toString].
+  ///
+  /// This method makes it easier for subclasses to coordinate to provide a
+  /// high-quality [toString] implementation. The [toString] implementation on
+  /// the [Notification] base class calls [debugFillDescription] to collect
+  /// useful information from subclasses to incorporate into its return value.
+  ///
+  /// If you override this, make sure to start your method with a call to
+  /// `super.debugFillDescription(description)`.
+  @protected
+  @mustCallSuper
+  void debugFillDescription(List<String> description) { }
+}
+
+/// A widget that listens for [Notification]s bubbling up the tree.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=cAnFbFoGM50}
+///
+/// Notifications will trigger the [onNotification] callback only if their
+/// [runtimeType] is a subtype of `T`.
+///
+/// To dispatch notifications, use the [Notification.dispatch] method.
+class NotificationListener<T extends Notification> extends StatelessWidget {
+  /// Creates a widget that listens for notifications.
+  const NotificationListener({
+    Key? key,
+    required this.child,
+    this.onNotification,
+  }) : super(key: key);
+
+  /// The widget directly below this widget in the tree.
+  ///
+  /// This is not necessarily the widget that dispatched the notification.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// Called when a notification of the appropriate type arrives at this
+  /// location in the tree.
+  ///
+  /// Return true to cancel the notification bubbling. Return false (or null) to
+  /// allow the notification to continue to be dispatched to further ancestors.
+  ///
+  /// The notification's [Notification.visitAncestor] method is called for each
+  /// ancestor, and invokes this callback as appropriate.
+  ///
+  /// Notifications vary in terms of when they are dispatched. There are two
+  /// main possibilities: dispatch between frames, and dispatch during layout.
+  ///
+  /// For notifications that dispatch during layout, such as those that inherit
+  /// from [LayoutChangedNotification], it is too late to call [State.setState]
+  /// in response to the notification (as layout is currently happening in a
+  /// descendant, by definition, since notifications bubble up the tree). For
+  /// widgets that depend on layout, consider a [LayoutBuilder] instead.
+  final NotificationListenerCallback<T>? onNotification;
+
+  bool _dispatch(Notification notification, Element element) {
+    if (onNotification != null && notification is T) {
+      final bool result = onNotification!(notification);
+      return result == true; // so that null and false have the same effect
+    }
+    return false;
+  }
+
+  @override
+  Widget build(BuildContext context) => child;
+}
+
+/// Indicates that the layout of one of the descendants of the object receiving
+/// this notification has changed in some way, and that therefore any
+/// assumptions about that layout are no longer valid.
+///
+/// Useful if, for instance, you're trying to align multiple descendants.
+///
+/// To listen for notifications in a subtree, use a
+/// [NotificationListener<LayoutChangedNotification>].
+///
+/// To send a notification, call [dispatch] on the notification you wish to
+/// send. The notification will be delivered to any [NotificationListener]
+/// widgets with the appropriate type parameters that are ancestors of the given
+/// [BuildContext].
+///
+/// In the widgets library, only the [SizeChangedLayoutNotifier] class and
+/// [Scrollable] classes dispatch this notification (specifically, they dispatch
+/// [SizeChangedLayoutNotification]s and [ScrollNotification]s respectively).
+/// Transitions, in particular, do not. Changing one's layout in one's build
+/// function does not cause this notification to be dispatched automatically. If
+/// an ancestor expects to be notified for any layout change, make sure you
+/// either only use widgets that never change layout, or that notify their
+/// ancestors when appropriate, or alternatively, dispatch the notifications
+/// yourself when appropriate.
+///
+/// Also, since this notification is sent when the layout is changed, it is only
+/// useful for paint effects that depend on the layout. If you were to use this
+/// notification to change the build, for instance, you would always be one
+/// frame behind, which would look really ugly and laggy.
+class LayoutChangedNotification extends Notification { }
diff --git a/lib/src/widgets/orientation_builder.dart b/lib/src/widgets/orientation_builder.dart
new file mode 100644
index 0000000..c3bd3d8
--- /dev/null
+++ b/lib/src/widgets/orientation_builder.dart
@@ -0,0 +1,57 @@
+// Copyright 2014 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.
+
+import 'basic.dart';
+import 'framework.dart';
+import 'layout_builder.dart';
+import 'media_query.dart';
+
+/// Signature for a function that builds a widget given an [Orientation].
+///
+/// Used by [OrientationBuilder.builder].
+typedef OrientationWidgetBuilder = Widget Function(BuildContext context, Orientation orientation);
+
+/// Builds a widget tree that can depend on the parent widget's orientation
+/// (distinct from the device orientation).
+///
+/// See also:
+///
+///  * [LayoutBuilder], which exposes the complete constraints, not just the
+///    orientation.
+///  * [CustomSingleChildLayout], which positions its child during layout.
+///  * [CustomMultiChildLayout], with which you can define the precise layout
+///    of a list of children during the layout phase.
+///  * [MediaQueryData.orientation], which exposes whether the device is in
+///    landscape or portrait mode.
+class OrientationBuilder extends StatelessWidget {
+  /// Creates an orientation builder.
+  ///
+  /// The [builder] argument must not be null.
+  const OrientationBuilder({
+    Key? key,
+    required this.builder,
+  }) : assert(builder != null),
+       super(key: key);
+
+  /// Builds the widgets below this widget given this widget's orientation.
+  ///
+  /// A widget's orientation is simply a factor of its width relative to its
+  /// height. For example, a [Column] widget will have a landscape orientation
+  /// if its width exceeds its height, even though it displays its children in
+  /// a vertical array.
+  final OrientationWidgetBuilder builder;
+
+  Widget _buildWithConstraints(BuildContext context, BoxConstraints constraints) {
+    // If the constraints are fully unbounded (i.e., maxWidth and maxHeight are
+    // both infinite), we prefer Orientation.portrait because its more common to
+    // scroll vertically then horizontally.
+    final Orientation orientation = constraints.maxWidth > constraints.maxHeight ? Orientation.landscape : Orientation.portrait;
+    return builder(context, orientation);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return LayoutBuilder(builder: _buildWithConstraints);
+  }
+}
diff --git a/lib/src/widgets/overflow_bar.dart b/lib/src/widgets/overflow_bar.dart
new file mode 100644
index 0000000..19268a3
--- /dev/null
+++ b/lib/src/widgets/overflow_bar.dart
@@ -0,0 +1,547 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+
+/// Defines the horizontal alignment of [OverflowBar] children
+/// when they're laid out in an overflow column.
+///
+/// This value must be interpreted relative to the ambient
+/// [TextDirection].
+enum OverflowBarAlignment {
+  /// Each child is left-aligned for [TextDirection.ltr],
+  /// right-aligned for [TextDirection.rtl].
+  start,
+
+  /// Each child is right-aligned for [TextDirection.ltr],
+  /// left-aligned for [TextDirection.rtl].
+  end,
+
+  /// Each child is horizontally centered.
+  center,
+}
+
+/// A widget that lays out its [children] in a row unless they
+/// "overflow" the available horizontal space, in which case it lays
+/// them out in a column instead.
+///
+/// This widget's width will expand to contain its children and the
+/// specified [spacing] until it overflows. The overflow column will
+/// consume all of the available width.  The [overflowAlignment]
+/// defines how each child will be aligned within the overflow column
+/// and the [overflowSpacing] defines the gap between each child.
+///
+/// The order that the children appear in the horizontal layout
+/// is defined by the [textDirection], just like the [Row] widget.
+/// If the layout overflows, then children's order within their
+/// column is specified by [overflowDirection] instead.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_center_no_null_safety}
+///
+/// This example defines a simple approximation of a dialog
+/// layout, where the layout of the dialog's action buttons are
+/// defined by an [OverflowBar]. The content is wrapped in a
+/// [SingleChildScrollView], so that if overflow occurs, the
+/// action buttons will still be accessible by scrolling,
+/// no matter how much vertical space is available.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Container(
+///     alignment: Alignment.center,
+///     padding: EdgeInsets.all(16),
+///     color: Colors.black.withOpacity(0.15),
+///     child: Material(
+///       color: Colors.white,
+///       elevation: 24,
+///       shape: RoundedRectangleBorder(
+///         borderRadius: BorderRadius.all(Radius.circular(4))
+///       ),
+///       child: Padding(
+///         padding: EdgeInsets.all(8),
+///         child: SingleChildScrollView(
+///           child: Column(
+///             mainAxisSize: MainAxisSize.min,
+///             crossAxisAlignment: CrossAxisAlignment.stretch,
+///             children: <Widget>[
+///               Container(height: 128, child: Placeholder()),
+///               Align(
+///                 alignment: AlignmentDirectional.centerEnd,
+///                 child: OverflowBar(
+///                   spacing: 8,
+///                   overflowAlignment: OverflowBarAlignment.end,
+///                   children: <Widget>[
+///                     TextButton(child: Text('Cancel'), onPressed: () { }),
+///                     TextButton(child: Text('Really Really Cancel'), onPressed: () { }),
+///                     OutlinedButton(child: Text('OK'), onPressed: () { }),
+///                   ],
+///                 ),
+///               ),
+///             ],
+///           ),
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+class OverflowBar extends MultiChildRenderObjectWidget {
+  /// Constructs an OverflowBar.
+  ///
+  /// The [spacing], [overflowSpacing], [overflowAlignment],
+  /// [overflowDirection], and [clipBehavior] parameters must not be
+  /// null. The [children] argument must not be null and must not contain
+  /// any null objects.
+  OverflowBar({
+    Key? key,
+    this.spacing = 0.0,
+    this.overflowSpacing = 0.0,
+    this.overflowAlignment = OverflowBarAlignment.start,
+    this.overflowDirection = VerticalDirection.down,
+    this.textDirection,
+    this.clipBehavior = Clip.none,
+    List<Widget> children = const <Widget>[],
+  }) : assert(spacing != null),
+       assert(overflowSpacing != null),
+       assert(overflowAlignment != null),
+       assert(overflowDirection != null),
+       assert(clipBehavior != null),
+       super(key: key, children: children);
+
+  /// The width of the gap between [children] for the default
+  /// horizontal layout.
+  ///
+  /// If the horizontal layout overflows, then [overflowSpacing] is
+  /// used instead.
+  ///
+  /// Defaults to 0.0.
+  final double spacing;
+
+  /// The height of the gap between [children] in the vertical
+  /// "overflow" layout.
+  ///
+  /// This parameter is only used if the horizontal layout overflows, i.e.
+  /// if there isn't enough horizontal room for the [children] and [spacing].
+  ///
+  /// Defaults to 0.0.
+  ///
+  /// See also:
+  ///
+  ///  * [spacing], The width of the gap between each pair of children
+  ///    for the default horizontal layout.
+  final double overflowSpacing;
+
+  /// The horizontal alignment of the [children] within the vertical
+  /// "overflow" layout.
+  ///
+  /// This parameter is only used if the horizontal layout overflows, i.e.
+  /// if there isn't enough horizontal room for the [children] and [spacing].
+  /// In that case the overflow bar will expand to fill the available
+  /// width and it will layout its [children] in a column. The
+  /// horizontal alignment of each child within that column is
+  /// defined by this parameter and the [textDirection]. If the
+  /// [textDirection] is [TextDirection.ltr] then each child will be
+  /// aligned with the left edge of the available space for
+  /// [OverflowBarAlignment.start], with the right edge of the
+  /// available space for [OverflowBarAlignment.end]. Similarly, if the
+  /// [textDirection] is [TextDirection.rtl] then each child will
+  /// be aligned with the right edge of the available space for
+  /// [OverflowBarAlignment.start], and with the left edge of the
+  /// available space for [OverflowBarAlignment.end]. For
+  /// [OverflowBarAlignment.center] each child is horizontally
+  /// centered within the available space.
+  ///
+  /// Defaults to [OverflowBarAlignment.start].
+  ///
+  /// See also:
+  ///
+  ///  * [overflowDirection], which defines the order that the
+  ///    [OverflowBar]'s children appear in, if the horizontal layout
+  ///    overflows.
+  final OverflowBarAlignment overflowAlignment;
+
+  /// Defines the order that the [children] appear in, if
+  /// the horizontal layout overflows.
+  ///
+  /// This parameter is only used if the horizontal layout overflows, i.e.
+  /// if there isn't enough horizontal room for the [children] and [spacing].
+  ///
+  /// If the children do not fit into a single row, then they
+  /// are arranged in a column. The first child is at the top of the
+  /// column if this property is set to [VerticalDirection.down], since it
+  /// "starts" at the top and "ends" at the bottom. On the other hand,
+  /// the first child will be at the bottom of the column if this
+  /// property is set to [VerticalDirection.up], since it "starts" at the
+  /// bottom and "ends" at the top.
+  ///
+  /// Defaults to [VerticalDirection.down].
+  ///
+  /// See also:
+  ///
+  ///  * [overflowAlignment], which defines the horizontal alignment
+  ///    of the children within the vertical "overflow" layout.
+  final VerticalDirection overflowDirection;
+
+  /// Determines the order that the [children] appear in for the default
+  /// horizontal layout, and the interpretation of
+  /// [OverflowBarAlignment.start] and [OverflowBarAlignment.end] for
+  /// the vertical overflow layout.
+  ///
+  /// For the default horizontal layout, if [textDirection] is
+  /// [TextDirection.rtl] then the last child is laid out first. If
+  /// [textDirection] is [TextDirection.ltr] then the first child is
+  /// laid out first.
+  ///
+  /// If this parameter is null, then the value of
+  /// `Directionality.of(context)` is used.
+  ///
+  /// See also:
+  ///
+  ///  * [overflowDirection], which defines the order that the
+  ///    [OverflowBar]'s children appear in, if the horizontal layout
+  ///    overflows.
+  ///  * [Directionality], which defines the ambient directionality of
+  ///    text and text-direction-sensitive render objects.
+  final TextDirection? textDirection;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  final Clip clipBehavior;
+
+  @override
+  _RenderOverflowBar createRenderObject(BuildContext context) {
+    return _RenderOverflowBar(
+      spacing: spacing,
+      overflowSpacing: overflowSpacing,
+      overflowAlignment: overflowAlignment,
+      overflowDirection: overflowDirection,
+      textDirection: textDirection ?? Directionality.of(context),
+      clipBehavior: clipBehavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderOverflowBar renderObject) {
+    renderObject
+      ..spacing = spacing
+      ..overflowSpacing = overflowSpacing
+      ..overflowAlignment = overflowAlignment
+      ..overflowDirection = overflowDirection
+      ..textDirection = textDirection ?? Directionality.of(context)
+      ..clipBehavior = clipBehavior;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('spacing', spacing, defaultValue: 0));
+    properties.add(DoubleProperty('overflowSpacing', overflowSpacing, defaultValue: 0));
+    properties.add(EnumProperty<OverflowBarAlignment>('overflowAlignment', overflowAlignment, defaultValue: OverflowBarAlignment.start));
+    properties.add(EnumProperty<VerticalDirection>('overflowDirection', overflowDirection, defaultValue: VerticalDirection.down));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+  }
+}
+
+class _OverflowBarParentData extends ContainerBoxParentData<RenderBox> { }
+
+class _RenderOverflowBar extends RenderBox
+    with ContainerRenderObjectMixin<RenderBox, _OverflowBarParentData>,
+         RenderBoxContainerDefaultsMixin<RenderBox, _OverflowBarParentData> {
+  _RenderOverflowBar({
+    List<RenderBox>? children,
+    double spacing = 0.0,
+    double overflowSpacing = 0.0,
+    OverflowBarAlignment overflowAlignment = OverflowBarAlignment.start,
+    VerticalDirection overflowDirection = VerticalDirection.down,
+    required TextDirection textDirection,
+    Clip clipBehavior = Clip.none,
+  }) : assert(spacing != null),
+       assert(overflowSpacing != null),
+       assert(overflowAlignment != null),
+       assert(textDirection != null),
+       assert(clipBehavior != null),
+       _spacing = spacing,
+       _overflowSpacing = overflowSpacing,
+       _overflowAlignment = overflowAlignment,
+       _overflowDirection = overflowDirection,
+       _textDirection = textDirection,
+       _clipBehavior = clipBehavior {
+    addAll(children);
+  }
+
+  double get spacing => _spacing;
+  double _spacing;
+  set spacing (double value) {
+    assert(value != null);
+    if (_spacing == value)
+      return;
+    _spacing = value;
+    markNeedsLayout();
+  }
+
+  double get overflowSpacing => _overflowSpacing;
+  double _overflowSpacing;
+  set overflowSpacing (double value) {
+    assert(value != null);
+    if (_overflowSpacing == value)
+      return;
+    _overflowSpacing = value;
+    markNeedsLayout();
+  }
+
+  OverflowBarAlignment get overflowAlignment => _overflowAlignment;
+  OverflowBarAlignment _overflowAlignment;
+  set overflowAlignment (OverflowBarAlignment value) {
+    assert(value != null);
+    if (_overflowAlignment == value)
+      return;
+    _overflowAlignment = value;
+    markNeedsLayout();
+  }
+
+  VerticalDirection get overflowDirection => _overflowDirection;
+  VerticalDirection _overflowDirection;
+  set overflowDirection (VerticalDirection value) {
+    assert(value != null);
+    if (_overflowDirection == value)
+      return;
+    _overflowDirection = value;
+    markNeedsLayout();
+  }
+
+  TextDirection get textDirection => _textDirection;
+  TextDirection _textDirection;
+  set textDirection(TextDirection value) {
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    markNeedsLayout();
+  }
+
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior = Clip.none;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value == _clipBehavior)
+      return;
+    _clipBehavior = value;
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! _OverflowBarParentData)
+      child.parentData = _OverflowBarParentData();
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    RenderBox? child = firstChild;
+    if (child == null)
+      return 0;
+    double barWidth = 0.0;
+    while (child != null) {
+      barWidth += child.getMinIntrinsicWidth(double.infinity);
+      child = childAfter(child);
+    }
+    barWidth += spacing * (childCount - 1);
+
+    double height = 0.0;
+    if (barWidth > width) {
+      child = firstChild;
+      while (child != null) {
+        height += child.getMinIntrinsicHeight(width);
+        child = childAfter(child);
+      }
+      return height + overflowSpacing * (childCount - 1);
+    } else {
+      child = firstChild;
+      while (child != null) {
+        height = math.max(height, child.getMinIntrinsicHeight(width));
+        child = childAfter(child);
+      }
+      return height;
+    }
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    RenderBox? child = firstChild;
+    if (child == null)
+      return 0;
+    double barWidth = 0.0;
+    while (child != null) {
+      barWidth += child.getMinIntrinsicWidth(double.infinity);
+      child = childAfter(child);
+    }
+    barWidth += spacing * (childCount - 1);
+
+    double height = 0.0;
+    if (barWidth > width) {
+      child = firstChild;
+      while (child != null) {
+        height += child.getMaxIntrinsicHeight(width);
+        child = childAfter(child);
+      }
+      return height + overflowSpacing * (childCount - 1);
+    } else {
+      child = firstChild;
+      while (child != null) {
+        height = math.max(height, child.getMaxIntrinsicHeight(width));
+        child = childAfter(child);
+      }
+      return height;
+    }
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    RenderBox? child = firstChild;
+    if (child == null)
+      return 0;
+    double width = 0.0;
+    while (child != null) {
+      width += child.getMinIntrinsicWidth(double.infinity);
+      child = childAfter(child);
+    }
+    return width + spacing * (childCount - 1);
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    RenderBox? child = firstChild;
+    if (child == null)
+      return 0;
+    double width = 0.0;
+    while (child != null) {
+      width += child.getMaxIntrinsicWidth(double.infinity);
+      child = childAfter(child);
+    }
+    return width + spacing * (childCount - 1);
+  }
+
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    return defaultComputeDistanceToHighestActualBaseline(baseline);
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    RenderBox? child = firstChild;
+    if (child == null) {
+      return constraints.smallest;
+    }
+    final BoxConstraints childConstraints = constraints.loosen();
+    double childrenWidth = 0.0;
+    double maxChildHeight = 0.0;
+    double y = 0.0;
+    while (child != null) {
+      final Size childSize = child.getDryLayout(childConstraints);
+      childrenWidth += childSize.width;
+      maxChildHeight = math.max(maxChildHeight, childSize.height);
+      y += childSize.height + overflowSpacing;
+      child = childAfter(child);
+    }
+    final double actualWidth = childrenWidth + spacing * (childCount - 1);
+    if (actualWidth > constraints.maxWidth) {
+      return constraints.constrain(Size(constraints.maxWidth, y - overflowSpacing));
+    } else {
+      return constraints.constrain(Size(actualWidth, maxChildHeight));
+    }
+  }
+
+  @override
+  void performLayout() {
+    RenderBox? child = firstChild;
+    if (child == null) {
+      size = constraints.smallest;
+      return;
+    }
+
+    final BoxConstraints childConstraints = constraints.loosen();
+    double childrenWidth = 0;
+    double maxChildHeight = 0;
+    double maxChildWidth = 0;
+
+    while (child != null) {
+      child.layout(childConstraints, parentUsesSize: true);
+      childrenWidth += child.size.width;
+      maxChildHeight = math.max(maxChildHeight, child.size.height);
+      maxChildWidth = math.max(maxChildWidth, child.size.width);
+      child = childAfter(child);
+    }
+
+    final bool rtl = textDirection == TextDirection.rtl;
+    final double actualWidth = childrenWidth + spacing * (childCount - 1);
+
+    if (actualWidth > constraints.maxWidth) {
+      // Overflow vertical layout
+      child = overflowDirection == VerticalDirection.down ? firstChild : lastChild;
+      RenderBox? nextChild() => overflowDirection == VerticalDirection.down ? childAfter(child!) : childBefore(child!);
+      double y = 0;
+      while (child != null) {
+        final _OverflowBarParentData childParentData = child.parentData! as _OverflowBarParentData;
+        double x = 0;
+        switch (overflowAlignment) {
+          case OverflowBarAlignment.start:
+            x = rtl ? constraints.maxWidth - child.size.width : 0;
+            break;
+          case OverflowBarAlignment.center:
+            x = (constraints.maxWidth - child.size.width) / 2;
+            break;
+          case OverflowBarAlignment.end:
+            x = rtl ? 0 : constraints.maxWidth - child.size.width;
+            break;
+        }
+        assert(x != null);
+        childParentData.offset = Offset(x, y);
+        y += child.size.height + overflowSpacing;
+        child = nextChild();
+      }
+      size = constraints.constrain(Size(constraints.maxWidth, y - overflowSpacing));
+    } else {
+      // Default horizontal layout
+      child = rtl ? lastChild : firstChild;
+      RenderBox? nextChild() => rtl ? childBefore(child!) : childAfter(child!);
+      double x  = 0;
+      while (child != null) {
+        final _OverflowBarParentData childParentData = child.parentData! as _OverflowBarParentData;
+        childParentData.offset = Offset(x, (maxChildHeight - child.size.height) / 2);
+        x += child.size.width + spacing;
+        child = nextChild();
+      }
+      size = constraints.constrain(Size(actualWidth, maxChildHeight));
+    }
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    return defaultHitTestChildren(result, position: position);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    defaultPaint(context, offset);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('spacing', spacing, defaultValue: 0));
+    properties.add(DoubleProperty('overflowSpacing', overflowSpacing, defaultValue: 0));
+    properties.add(EnumProperty<OverflowBarAlignment>('overflowAlignment', overflowAlignment, defaultValue: OverflowBarAlignment.start));
+    properties.add(EnumProperty<VerticalDirection>('overflowDirection', overflowDirection, defaultValue: VerticalDirection.down));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+  }
+}
diff --git a/lib/src/widgets/overlay.dart b/lib/src/widgets/overlay.dart
new file mode 100644
index 0000000..346fee5
--- /dev/null
+++ b/lib/src/widgets/overlay.dart
@@ -0,0 +1,864 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'ticker_provider.dart';
+
+/// A place in an [Overlay] that can contain a widget.
+///
+/// Overlay entries are inserted into an [Overlay] using the
+/// [OverlayState.insert] or [OverlayState.insertAll] functions. To find the
+/// closest enclosing overlay for a given [BuildContext], use the [Overlay.of]
+/// function.
+///
+/// An overlay entry can be in at most one overlay at a time. To remove an entry
+/// from its overlay, call the [remove] function on the overlay entry.
+///
+/// Because an [Overlay] uses a [Stack] layout, overlay entries can use
+/// [Positioned] and [AnimatedPositioned] to position themselves within the
+/// overlay.
+///
+/// For example, [Draggable] uses an [OverlayEntry] to show the drag avatar that
+/// follows the user's finger across the screen after the drag begins. Using the
+/// overlay to display the drag avatar lets the avatar float over the other
+/// widgets in the app. As the user's finger moves, draggable calls
+/// [markNeedsBuild] on the overlay entry to cause it to rebuild. In its build,
+/// the entry includes a [Positioned] with its top and left property set to
+/// position the drag avatar near the user's finger. When the drag is over,
+/// [Draggable] removes the entry from the overlay to remove the drag avatar
+/// from view.
+///
+/// By default, if there is an entirely [opaque] entry over this one, then this
+/// one will not be included in the widget tree (in particular, stateful widgets
+/// within the overlay entry will not be instantiated). To ensure that your
+/// overlay entry is still built even if it is not visible, set [maintainState]
+/// to true. This is more expensive, so should be done with care. In particular,
+/// if widgets in an overlay entry with [maintainState] set to true repeatedly
+/// call [State.setState], the user's battery will be drained unnecessarily.
+///
+/// [OverlayEntry] is a [ChangeNotifier] that notifies when the widget built by
+/// [builder] is mounted or unmounted, whose exact state can be queried by
+/// [mounted].
+///
+/// See also:
+///
+///  * [Overlay]
+///  * [OverlayState]
+///  * [WidgetsApp]
+///  * [MaterialApp]
+class OverlayEntry extends ChangeNotifier {
+  /// Creates an overlay entry.
+  ///
+  /// To insert the entry into an [Overlay], first find the overlay using
+  /// [Overlay.of] and then call [OverlayState.insert]. To remove the entry,
+  /// call [remove] on the overlay entry itself.
+  OverlayEntry({
+    required this.builder,
+    bool opaque = false,
+    bool maintainState = false,
+  }) : assert(builder != null),
+       assert(opaque != null),
+       assert(maintainState != null),
+       _opaque = opaque,
+       _maintainState = maintainState;
+
+  /// This entry will include the widget built by this builder in the overlay at
+  /// the entry's position.
+  ///
+  /// To cause this builder to be called again, call [markNeedsBuild] on this
+  /// overlay entry.
+  final WidgetBuilder builder;
+
+  /// Whether this entry occludes the entire overlay.
+  ///
+  /// If an entry claims to be opaque, then, for efficiency, the overlay will
+  /// skip building entries below that entry unless they have [maintainState]
+  /// set.
+  bool get opaque => _opaque;
+  bool _opaque;
+  set opaque(bool value) {
+    if (_opaque == value)
+      return;
+    _opaque = value;
+    _overlay?._didChangeEntryOpacity();
+  }
+
+  /// Whether this entry must be included in the tree even if there is a fully
+  /// [opaque] entry above it.
+  ///
+  /// By default, if there is an entirely [opaque] entry over this one, then this
+  /// one will not be included in the widget tree (in particular, stateful widgets
+  /// within the overlay entry will not be instantiated). To ensure that your
+  /// overlay entry is still built even if it is not visible, set [maintainState]
+  /// to true. This is more expensive, so should be done with care. In particular,
+  /// if widgets in an overlay entry with [maintainState] set to true repeatedly
+  /// call [State.setState], the user's battery will be drained unnecessarily.
+  ///
+  /// This is used by the [Navigator] and [Route] objects to ensure that routes
+  /// are kept around even when in the background, so that [Future]s promised
+  /// from subsequent routes will be handled properly when they complete.
+  bool get maintainState => _maintainState;
+  bool _maintainState;
+  set maintainState(bool value) {
+    assert(_maintainState != null);
+    if (_maintainState == value)
+      return;
+    _maintainState = value;
+    assert(_overlay != null);
+    _overlay!._didChangeEntryOpacity();
+  }
+
+  /// Whether the [OverlayEntry] is currently mounted in the widget tree.
+  ///
+  /// The [OverlayEntry] notifies its listeners when this value changes.
+  bool get mounted => _mounted;
+  bool _mounted = false;
+  void _updateMounted(bool value) {
+    if (value == _mounted) {
+      return;
+    }
+    _mounted = value;
+    notifyListeners();
+  }
+
+  OverlayState? _overlay;
+  final GlobalKey<_OverlayEntryWidgetState> _key = GlobalKey<_OverlayEntryWidgetState>();
+
+  /// Remove this entry from the overlay.
+  ///
+  /// This should only be called once.
+  ///
+  /// This method removes this overlay entry from the overlay immediately. The
+  /// UI will be updated in the same frame if this method is called before the
+  /// overlay rebuild in this frame; otherwise, the UI will be updated in the
+  /// next frame. This means that it is safe to call during builds, but	also
+  /// that if you do call this after the overlay rebuild, the UI will not update
+  /// until	the next frame (i.e. many milliseconds later).
+  void remove() {
+    assert(_overlay != null);
+    final OverlayState overlay = _overlay!;
+    _overlay = null;
+    if (!overlay.mounted)
+      return;
+
+    overlay._entries.remove(this);
+    if (SchedulerBinding.instance!.schedulerPhase == SchedulerPhase.persistentCallbacks) {
+      SchedulerBinding.instance!.addPostFrameCallback((Duration duration) {
+        overlay._markDirty();
+      });
+    } else {
+      overlay._markDirty();
+    }
+  }
+
+  /// Cause this entry to rebuild during the next pipeline flush.
+  ///
+  /// You need to call this function if the output of [builder] has changed.
+  void markNeedsBuild() {
+    _key.currentState?._markNeedsBuild();
+  }
+
+  @override
+  String toString() => '${describeIdentity(this)}(opaque: $opaque; maintainState: $maintainState)';
+}
+
+class _OverlayEntryWidget extends StatefulWidget {
+  const _OverlayEntryWidget({
+    required Key key,
+    required this.entry,
+    this.tickerEnabled = true,
+  }) : assert(key != null),
+       assert(entry != null),
+       assert(tickerEnabled != null),
+       super(key: key);
+
+  final OverlayEntry entry;
+  final bool tickerEnabled;
+
+  @override
+  _OverlayEntryWidgetState createState() => _OverlayEntryWidgetState();
+}
+
+class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> {
+  @override
+  void initState() {
+    super.initState();
+    widget.entry._updateMounted(true);
+  }
+
+  @override
+  void dispose() {
+    widget.entry._updateMounted(false);
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return TickerMode(
+      enabled: widget.tickerEnabled,
+      child: widget.entry.builder(context),
+    );
+  }
+
+  void _markNeedsBuild() {
+    setState(() { /* the state that changed is in the builder */ });
+  }
+}
+
+/// A [Stack] of entries that can be managed independently.
+///
+/// Overlays let independent child widgets "float" visual elements on top of
+/// other widgets by inserting them into the overlay's [Stack]. The overlay lets
+/// each of these widgets manage their participation in the overlay using
+/// [OverlayEntry] objects.
+///
+/// Although you can create an [Overlay] directly, it's most common to use the
+/// overlay created by the [Navigator] in a [WidgetsApp] or a [MaterialApp]. The
+/// navigator uses its overlay to manage the visual appearance of its routes.
+///
+/// See also:
+///
+///  * [OverlayEntry].
+///  * [OverlayState].
+///  * [WidgetsApp].
+///  * [MaterialApp].
+class Overlay extends StatefulWidget {
+  /// Creates an overlay.
+  ///
+  /// The initial entries will be inserted into the overlay when its associated
+  /// [OverlayState] is initialized.
+  ///
+  /// Rather than creating an overlay, consider using the overlay that is
+  /// created by the [Navigator] in a [WidgetsApp] or a [MaterialApp] for the application.
+  const Overlay({
+    Key? key,
+    this.initialEntries = const <OverlayEntry>[],
+    this.clipBehavior = Clip.hardEdge,
+  }) : assert(initialEntries != null),
+       assert(clipBehavior != null),
+       super(key: key);
+
+  /// The entries to include in the overlay initially.
+  ///
+  /// These entries are only used when the [OverlayState] is initialized. If you
+  /// are providing a new [Overlay] description for an overlay that's already in
+  /// the tree, then the new entries are ignored.
+  ///
+  /// To add entries to an [Overlay] that is already in the tree, use
+  /// [Overlay.of] to obtain the [OverlayState] (or assign a [GlobalKey] to the
+  /// [Overlay] widget and obtain the [OverlayState] via
+  /// [GlobalKey.currentState]), and then use [OverlayState.insert] or
+  /// [OverlayState.insertAll].
+  ///
+  /// To remove an entry from an [Overlay], use [OverlayEntry.remove].
+  final List<OverlayEntry> initialEntries;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge], and must not be null.
+  final Clip clipBehavior;
+
+  /// The state from the closest instance of this class that encloses the given context.
+  ///
+  /// In debug mode, if the `debugRequiredFor` argument is provided then this
+  /// function will assert that an overlay was found and will throw an exception
+  /// if not. The exception attempts to explain that the calling [Widget] (the
+  /// one given by the `debugRequiredFor` argument) needs an [Overlay] to be
+  /// present to function.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// OverlayState overlay = Overlay.of(context);
+  /// ```
+  ///
+  /// If `rootOverlay` is set to true, the state from the furthest instance of
+  /// this class is given instead. Useful for installing overlay entries
+  /// above all subsequent instances of [Overlay].
+  static OverlayState? of(
+    BuildContext context, {
+    bool rootOverlay = false,
+    Widget? debugRequiredFor,
+  }) {
+    final OverlayState? result = rootOverlay
+        ? context.findRootAncestorStateOfType<OverlayState>()
+        : context.findAncestorStateOfType<OverlayState>();
+    assert(() {
+      if (debugRequiredFor != null && result == null) {
+        final List<DiagnosticsNode> information = <DiagnosticsNode>[
+          ErrorSummary('No Overlay widget found.'),
+          ErrorDescription('${debugRequiredFor.runtimeType} widgets require an Overlay widget ancestor for correct operation.'),
+          ErrorHint('The most common way to add an Overlay to an application is to include a MaterialApp or Navigator widget in the runApp() call.'),
+          DiagnosticsProperty<Widget>('The specific widget that failed to find an overlay was', debugRequiredFor, style: DiagnosticsTreeStyle.errorProperty),
+          if (context.widget != debugRequiredFor)
+            context.describeElement('The context from which that widget was searching for an overlay was')
+        ];
+
+        throw FlutterError.fromParts(information);
+      }
+      return true;
+    }());
+    return result;
+  }
+
+  @override
+  OverlayState createState() => OverlayState();
+}
+
+/// The current state of an [Overlay].
+///
+/// Used to insert [OverlayEntry]s into the overlay using the [insert] and
+/// [insertAll] functions.
+class OverlayState extends State<Overlay> with TickerProviderStateMixin {
+  final List<OverlayEntry> _entries = <OverlayEntry>[];
+
+  @override
+  void initState() {
+    super.initState();
+    insertAll(widget.initialEntries);
+  }
+
+  int _insertionIndex(OverlayEntry? below, OverlayEntry? above) {
+    assert(above == null || below == null);
+    if (below != null)
+      return _entries.indexOf(below);
+    if (above != null)
+      return _entries.indexOf(above) + 1;
+    return _entries.length;
+  }
+
+  /// Insert the given entry into the overlay.
+  ///
+  /// If `below` is non-null, the entry is inserted just below `below`.
+  /// If `above` is non-null, the entry is inserted just above `above`.
+  /// Otherwise, the entry is inserted on top.
+  ///
+  /// It is an error to specify both `above` and `below`.
+  void insert(OverlayEntry entry, { OverlayEntry? below, OverlayEntry? above }) {
+    assert(_debugVerifyInsertPosition(above, below));
+    assert(!_entries.contains(entry), 'The specified entry is already present in the Overlay.');
+    assert(entry._overlay == null, 'The specified entry is already present in another Overlay.');
+    entry._overlay = this;
+    setState(() {
+      _entries.insert(_insertionIndex(below, above), entry);
+    });
+  }
+
+  /// Insert all the entries in the given iterable.
+  ///
+  /// If `below` is non-null, the entries are inserted just below `below`.
+  /// If `above` is non-null, the entries are inserted just above `above`.
+  /// Otherwise, the entries are inserted on top.
+  ///
+  /// It is an error to specify both `above` and `below`.
+  void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry? below, OverlayEntry? above }) {
+    assert(_debugVerifyInsertPosition(above, below));
+    assert(
+      entries.every((OverlayEntry entry) => !_entries.contains(entry)),
+      'One or more of the specified entries are already present in the Overlay.'
+    );
+    assert(
+      entries.every((OverlayEntry entry) => entry._overlay == null),
+      'One or more of the specified entries are already present in another Overlay.'
+    );
+    if (entries.isEmpty)
+      return;
+    for (final OverlayEntry entry in entries) {
+      assert(entry._overlay == null);
+      entry._overlay = this;
+    }
+    setState(() {
+      _entries.insertAll(_insertionIndex(below, above), entries);
+    });
+  }
+
+  bool _debugVerifyInsertPosition(OverlayEntry? above, OverlayEntry? below, { Iterable<OverlayEntry>? newEntries }) {
+    assert(
+      above == null || below == null,
+      'Only one of `above` and `below` may be specified.',
+    );
+    assert(
+      above == null || (above._overlay == this && _entries.contains(above) && (newEntries?.contains(above) ?? true)),
+      'The provided entry used for `above` must be present in the Overlay${newEntries != null ? ' and in the `newEntriesList`' : ''}.',
+    );
+    assert(
+      below == null || (below._overlay == this && _entries.contains(below) && (newEntries?.contains(below) ?? true)),
+      'The provided entry used for `below` must be present in the Overlay${newEntries != null ? ' and in the `newEntriesList`' : ''}.',
+    );
+    return true;
+  }
+
+  /// Remove all the entries listed in the given iterable, then reinsert them
+  /// into the overlay in the given order.
+  ///
+  /// Entries mention in `newEntries` but absent from the overlay are inserted
+  /// as if with [insertAll].
+  ///
+  /// Entries not mentioned in `newEntries` but present in the overlay are
+  /// positioned as a group in the resulting list relative to the entries that
+  /// were moved, as specified by one of `below` or `above`, which, if
+  /// specified, must be one of the entries in `newEntries`:
+  ///
+  /// If `below` is non-null, the group is positioned just below `below`.
+  /// If `above` is non-null, the group is positioned just above `above`.
+  /// Otherwise, the group is left on top, with all the rearranged entries
+  /// below.
+  ///
+  /// It is an error to specify both `above` and `below`.
+  void rearrange(Iterable<OverlayEntry> newEntries, { OverlayEntry? below, OverlayEntry? above }) {
+    final List<OverlayEntry> newEntriesList = newEntries is List<OverlayEntry> ? newEntries : newEntries.toList(growable: false);
+    assert(_debugVerifyInsertPosition(above, below, newEntries: newEntriesList));
+    assert(
+      newEntriesList.every((OverlayEntry entry) => entry._overlay == null || entry._overlay == this),
+      'One or more of the specified entries are already present in another Overlay.'
+    );
+    assert(
+      newEntriesList.every((OverlayEntry entry) => _entries.indexOf(entry) == _entries.lastIndexOf(entry)),
+      'One or more of the specified entries are specified multiple times.'
+    );
+    if (newEntriesList.isEmpty)
+      return;
+    if (listEquals(_entries, newEntriesList))
+      return;
+    final LinkedHashSet<OverlayEntry> old = LinkedHashSet<OverlayEntry>.from(_entries);
+    for (final OverlayEntry entry in newEntriesList) {
+      entry._overlay ??= this;
+    }
+    setState(() {
+      _entries.clear();
+      _entries.addAll(newEntriesList);
+      old.removeAll(newEntriesList);
+      _entries.insertAll(_insertionIndex(below, above), old);
+    });
+  }
+
+  void _markDirty() {
+    if (mounted) {
+      setState(() {});
+    }
+  }
+
+  /// (DEBUG ONLY) Check whether a given entry is visible (i.e., not behind an
+  /// opaque entry).
+  ///
+  /// This is an O(N) algorithm, and should not be necessary except for debug
+  /// asserts. To avoid people depending on it, this function is implemented
+  /// only in debug mode, and always returns false in release mode.
+  bool debugIsVisible(OverlayEntry entry) {
+    bool result = false;
+    assert(_entries.contains(entry));
+    assert(() {
+      for (int i = _entries.length - 1; i > 0; i -= 1) {
+        final OverlayEntry candidate = _entries[i];
+        if (candidate == entry) {
+          result = true;
+          break;
+        }
+        if (candidate.opaque)
+          break;
+      }
+      return true;
+    }());
+    return result;
+  }
+
+  void _didChangeEntryOpacity() {
+    setState(() {
+      // We use the opacity of the entry in our build function, which means we
+      // our state has changed.
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // This list is filled backwards and then reversed below before
+    // it is added to the tree.
+    final List<Widget> children = <Widget>[];
+    bool onstage = true;
+    int onstageCount = 0;
+    for (int i = _entries.length - 1; i >= 0; i -= 1) {
+      final OverlayEntry entry = _entries[i];
+      if (onstage) {
+        onstageCount += 1;
+        children.add(_OverlayEntryWidget(
+          key: entry._key,
+          entry: entry,
+        ));
+        if (entry.opaque)
+          onstage = false;
+      } else if (entry.maintainState) {
+        children.add(_OverlayEntryWidget(
+          key: entry._key,
+          entry: entry,
+          tickerEnabled: false,
+        ));
+      }
+    }
+    return _Theatre(
+      skipCount: children.length - onstageCount,
+      children: children.reversed.toList(growable: false),
+      clipBehavior: widget.clipBehavior,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    // TODO(jacobr): use IterableProperty instead as that would
+    // provide a slightly more consistent string summary of the List.
+    properties.add(DiagnosticsProperty<List<OverlayEntry>>('entries', _entries));
+  }
+}
+
+/// Special version of a [Stack], that doesn't layout and render the first
+/// [skipCount] children.
+///
+/// The first [skipCount] children are considered "offstage".
+class _Theatre extends MultiChildRenderObjectWidget {
+  _Theatre({
+    Key? key,
+    this.skipCount = 0,
+    this.clipBehavior = Clip.hardEdge,
+    List<Widget> children = const <Widget>[],
+  }) : assert(skipCount != null),
+       assert(skipCount >= 0),
+       assert(children != null),
+       assert(children.length >= skipCount),
+       assert(clipBehavior != null),
+       super(key: key, children: children);
+
+  final int skipCount;
+
+  final Clip clipBehavior;
+
+  @override
+  _TheatreElement createElement() => _TheatreElement(this);
+
+  @override
+  _RenderTheatre createRenderObject(BuildContext context) {
+    return _RenderTheatre(
+      skipCount: skipCount,
+      textDirection: Directionality.of(context),
+      clipBehavior: clipBehavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderTheatre renderObject) {
+    renderObject
+      ..skipCount = skipCount
+      ..textDirection = Directionality.of(context)
+      ..clipBehavior = clipBehavior;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(IntProperty('skipCount', skipCount));
+  }
+}
+
+class _TheatreElement extends MultiChildRenderObjectElement {
+  _TheatreElement(_Theatre widget) : super(widget);
+
+  @override
+  _Theatre get widget => super.widget as _Theatre;
+
+  @override
+  _RenderTheatre get renderObject => super.renderObject as _RenderTheatre;
+
+  @override
+  void debugVisitOnstageChildren(ElementVisitor visitor) {
+    assert(children.length >= widget.skipCount);
+    children.skip(widget.skipCount).forEach(visitor);
+  }
+}
+
+class _RenderTheatre extends RenderBox with ContainerRenderObjectMixin<RenderBox, StackParentData> {
+  _RenderTheatre({
+    List<RenderBox>? children,
+    required TextDirection textDirection,
+    int skipCount = 0,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : assert(skipCount != null),
+       assert(skipCount >= 0),
+       assert(textDirection != null),
+       assert(clipBehavior != null),
+       _textDirection = textDirection,
+       _skipCount = skipCount,
+       _clipBehavior = clipBehavior {
+    addAll(children);
+  }
+
+  bool _hasVisualOverflow = false;
+
+  @override
+  void setupParentData(RenderBox child) {
+    if (child.parentData is! StackParentData)
+      child.parentData = StackParentData();
+  }
+
+  Alignment? _resolvedAlignment;
+
+  void _resolve() {
+    if (_resolvedAlignment != null)
+      return;
+    _resolvedAlignment = AlignmentDirectional.topStart.resolve(textDirection);
+  }
+
+  void _markNeedResolution() {
+    _resolvedAlignment = null;
+    markNeedsLayout();
+  }
+
+  TextDirection get textDirection => _textDirection;
+  TextDirection _textDirection;
+  set textDirection(TextDirection value) {
+    if (_textDirection == value)
+      return;
+    _textDirection = value;
+    _markNeedResolution();
+  }
+
+  int get skipCount => _skipCount;
+  int _skipCount;
+  set skipCount(int value) {
+    assert(value != null);
+    if (_skipCount != value) {
+      _skipCount = value;
+      markNeedsLayout();
+    }
+  }
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge], and must not be null.
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior = Clip.hardEdge;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  RenderBox? get _firstOnstageChild {
+    if (skipCount == super.childCount) {
+      return null;
+    }
+    RenderBox? child = super.firstChild;
+    for (int toSkip = skipCount; toSkip > 0; toSkip--) {
+      final StackParentData childParentData = child!.parentData! as StackParentData;
+      child = childParentData.nextSibling;
+      assert(child != null);
+    }
+    return child;
+  }
+
+  RenderBox? get _lastOnstageChild => skipCount == super.childCount ? null : lastChild;
+
+  int get _onstageChildCount => childCount - skipCount;
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMinIntrinsicWidth(height));
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMaxIntrinsicWidth(height));
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMinIntrinsicHeight(width));
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    return RenderStack.getIntrinsicDimension(_firstOnstageChild, (RenderBox child) => child.getMaxIntrinsicHeight(width));
+  }
+
+  @override
+  double? computeDistanceToActualBaseline(TextBaseline baseline) {
+    assert(!debugNeedsLayout);
+    double? result;
+    RenderBox? child = _firstOnstageChild;
+    while (child != null) {
+      assert(!child.debugNeedsLayout);
+      final StackParentData childParentData = child.parentData! as StackParentData;
+      double? candidate = child.getDistanceToActualBaseline(baseline);
+      if (candidate != null) {
+        candidate += childParentData.offset.dy;
+        if (result != null) {
+          result = math.min(result, candidate);
+        } else {
+          result = candidate;
+        }
+      }
+      child = childParentData.nextSibling;
+    }
+    return result;
+  }
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    assert(constraints.biggest.isFinite);
+    return constraints.biggest;
+  }
+
+  @override
+  void performLayout() {
+    _hasVisualOverflow = false;
+
+    if (_onstageChildCount == 0) {
+      return;
+    }
+
+    _resolve();
+    assert(_resolvedAlignment != null);
+
+    // Same BoxConstraints as used by RenderStack for StackFit.expand.
+    final BoxConstraints nonPositionedConstraints = BoxConstraints.tight(constraints.biggest);
+
+    RenderBox? child = _firstOnstageChild;
+    while (child != null) {
+      final StackParentData childParentData = child.parentData! as StackParentData;
+
+      if (!childParentData.isPositioned) {
+        child.layout(nonPositionedConstraints, parentUsesSize: true);
+        childParentData.offset = _resolvedAlignment!.alongOffset(size - child.size as Offset);
+      } else {
+        _hasVisualOverflow = RenderStack.layoutPositionedChild(child, childParentData, size, _resolvedAlignment!) || _hasVisualOverflow;
+      }
+
+      assert(child.parentData == childParentData);
+      child = childParentData.nextSibling;
+    }
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    RenderBox? child = _lastOnstageChild;
+    for (int i = 0; i < _onstageChildCount; i++) {
+      assert(child != null);
+      final StackParentData childParentData = child!.parentData! as StackParentData;
+      final bool isHit = result.addWithPaintOffset(
+        offset: childParentData.offset,
+        position: position,
+        hitTest: (BoxHitTestResult result, Offset? transformed) {
+          assert(transformed == position - childParentData.offset);
+          return child!.hitTest(result, position: transformed!);
+        },
+      );
+      if (isHit)
+        return true;
+      child = childParentData.previousSibling;
+    }
+    return false;
+  }
+
+  @protected
+  void paintStack(PaintingContext context, Offset offset) {
+    RenderBox? child = _firstOnstageChild;
+    while (child != null) {
+      final StackParentData childParentData = child.parentData! as StackParentData;
+      context.paintChild(child, childParentData.offset + offset);
+      child = childParentData.nextSibling;
+    }
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (_hasVisualOverflow && clipBehavior != Clip.none) {
+      _clipRectLayer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintStack,
+          clipBehavior: clipBehavior, oldLayer: _clipRectLayer);
+    } else {
+      _clipRectLayer = null;
+      paintStack(context, offset);
+    }
+  }
+
+  ClipRectLayer? _clipRectLayer;
+
+  @override
+  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
+    RenderBox? child = _firstOnstageChild;
+    while (child != null) {
+      visitor(child);
+      final StackParentData childParentData = child.parentData! as StackParentData;
+      child = childParentData.nextSibling;
+    }
+  }
+
+  @override
+  Rect? describeApproximatePaintClip(RenderObject child) => _hasVisualOverflow ? Offset.zero & size : null;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(IntProperty('skipCount', skipCount));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection));
+  }
+
+  @override
+  List<DiagnosticsNode> debugDescribeChildren() {
+    final List<DiagnosticsNode> offstageChildren = <DiagnosticsNode>[];
+    final List<DiagnosticsNode> onstageChildren = <DiagnosticsNode>[];
+
+    int count = 1;
+    bool onstage = false;
+    RenderBox? child = firstChild;
+    final RenderBox? firstOnstageChild = _firstOnstageChild;
+    while (child != null) {
+      if (child == firstOnstageChild) {
+        onstage = true;
+        count = 1;
+      }
+
+      if (onstage) {
+        onstageChildren.add(
+          child.toDiagnosticsNode(
+            name: 'onstage $count',
+          ),
+        );
+      } else {
+        offstageChildren.add(
+          child.toDiagnosticsNode(
+            name: 'offstage $count',
+            style: DiagnosticsTreeStyle.offstage,
+          ),
+        );
+      }
+
+      final StackParentData childParentData = child.parentData! as StackParentData;
+      child = childParentData.nextSibling;
+      count += 1;
+    }
+
+    return <DiagnosticsNode>[
+      ...onstageChildren,
+      if (offstageChildren.isNotEmpty)
+        ...offstageChildren
+      else
+        DiagnosticsNode.message(
+          'no offstage children',
+          style: DiagnosticsTreeStyle.offstage,
+        ),
+    ];
+  }
+}
diff --git a/lib/src/widgets/overscroll_indicator.dart b/lib/src/widgets/overscroll_indicator.dart
new file mode 100644
index 0000000..3fb07f9
--- /dev/null
+++ b/lib/src/widgets/overscroll_indicator.dart
@@ -0,0 +1,683 @@
+// Copyright 2014 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.
+
+import 'dart:async' show Timer;
+import 'dart:math' as math;
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/physics.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'notification_listener.dart';
+import 'scroll_notification.dart';
+import 'ticker_provider.dart';
+
+/// A visual indication that a scroll view has overscrolled.
+///
+/// A [GlowingOverscrollIndicator] listens for [ScrollNotification]s in order
+/// to control the overscroll indication. These notifications are typically
+/// generated by a [ScrollView], such as a [ListView] or a [GridView].
+///
+/// [GlowingOverscrollIndicator] generates [OverscrollIndicatorNotification]
+/// before showing an overscroll indication. To prevent the indicator from
+/// showing the indication, call [OverscrollIndicatorNotification.disallowGlow]
+/// on the notification.
+///
+/// Created automatically by [ScrollBehavior.buildViewportChrome] on platforms
+/// (e.g., Android) that commonly use this type of overscroll indication.
+///
+/// In a [MaterialApp], the edge glow color is the [ThemeData.accentColor].
+///
+/// ## Customizing the Glow Position for Advanced Scroll Views
+///
+/// When building a [CustomScrollView] with a [GlowingOverscrollIndicator], the
+/// indicator will apply to the entire scrollable area, regardless of what
+/// slivers the CustomScrollView contains.
+///
+/// For example, if your CustomScrollView contains a SliverAppBar in the first
+/// position, the GlowingOverscrollIndicator will overlay the SliverAppBar. To
+/// manipulate the position of the GlowingOverscrollIndicator in this case,
+/// you can either make use of a [NotificationListener] and provide a
+/// [OverscrollIndicatorNotification.paintOffset] to the
+/// notification, or use a [NestedScrollView].
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// This example demonstrates how to use a [NotificationListener] to manipulate
+/// the placement of a [GlowingOverscrollIndicator] when building a
+/// [CustomScrollView]. Drag the scrollable to see the bounds of the overscroll
+/// indicator.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   double leadingPaintOffset = MediaQuery.of(context).padding.top + AppBar().preferredSize.height;
+///   return NotificationListener<OverscrollIndicatorNotification>(
+///     onNotification: (notification) {
+///       if (notification.leading) {
+///         notification.paintOffset = leadingPaintOffset;
+///       }
+///       return false;
+///     },
+///     child: CustomScrollView(
+///       slivers: [
+///         SliverAppBar(title: Text('Custom PaintOffset')),
+///         SliverToBoxAdapter(
+///           child: Container(
+///             color: Colors.amberAccent,
+///             height: 100,
+///             child: Center(child: Text('Glow all day!')),
+///           ),
+///         ),
+///         SliverFillRemaining(child: FlutterLogo()),
+///       ],
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// This example demonstrates how to use a [NestedScrollView] to manipulate the
+/// placement of a [GlowingOverscrollIndicator] when building a
+/// [CustomScrollView]. Drag the scrollable to see the bounds of the overscroll
+/// indicator.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return NestedScrollView(
+///     headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
+///       return <Widget>[
+///         SliverAppBar(title: Text('Custom NestedScrollViews')),
+///       ];
+///     },
+///     body: CustomScrollView(
+///       slivers: <Widget>[
+///         SliverToBoxAdapter(
+///           child: Container(
+///             color: Colors.amberAccent,
+///             height: 100,
+///             child: Center(child: Text('Glow all day!')),
+///           ),
+///         ),
+///         SliverFillRemaining(child: FlutterLogo()),
+///       ],
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [OverscrollIndicatorNotification], which can be used to manipulate the
+///    glow position or prevent the glow from being painted at all
+///  * [NotificationListener], to listen for the
+///    [OverscrollIndicatorNotification]
+class GlowingOverscrollIndicator extends StatefulWidget {
+  /// Creates a visual indication that a scroll view has overscrolled.
+  ///
+  /// In order for this widget to display an overscroll indication, the [child]
+  /// widget must contain a widget that generates a [ScrollNotification], such
+  /// as a [ListView] or a [GridView].
+  ///
+  /// The [showLeading], [showTrailing], [axisDirection], [color], and
+  /// [notificationPredicate] arguments must not be null.
+  const GlowingOverscrollIndicator({
+    Key? key,
+    this.showLeading = true,
+    this.showTrailing = true,
+    required this.axisDirection,
+    required this.color,
+    this.notificationPredicate = defaultScrollNotificationPredicate,
+    this.child,
+  }) : assert(showLeading != null),
+       assert(showTrailing != null),
+       assert(axisDirection != null),
+       assert(color != null),
+       assert(notificationPredicate != null),
+       super(key: key);
+
+  /// Whether to show the overscroll glow on the side with negative scroll
+  /// offsets.
+  ///
+  /// For a vertical downwards viewport, this is the top side.
+  ///
+  /// Defaults to true.
+  ///
+  /// See [showTrailing] for the corresponding control on the other side of the
+  /// viewport.
+  final bool showLeading;
+
+  /// Whether to show the overscroll glow on the side with positive scroll
+  /// offsets.
+  ///
+  /// For a vertical downwards viewport, this is the bottom side.
+  ///
+  /// Defaults to true.
+  ///
+  /// See [showLeading] for the corresponding control on the other side of the
+  /// viewport.
+  final bool showTrailing;
+
+  /// The direction of positive scroll offsets in the [Scrollable] whose
+  /// overscrolls are to be visualized.
+  final AxisDirection axisDirection;
+
+  /// The axis along which scrolling occurs in the [Scrollable] whose
+  /// overscrolls are to be visualized.
+  Axis get axis => axisDirectionToAxis(axisDirection);
+
+  /// The color of the glow. The alpha channel is ignored.
+  final Color color;
+
+  /// A check that specifies whether a [ScrollNotification] should be
+  /// handled by this widget.
+  ///
+  /// By default, checks whether `notification.depth == 0`. Set it to something
+  /// else for more complicated layouts.
+  final ScrollNotificationPredicate notificationPredicate;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// The overscroll indicator will paint on top of this child. This child (and its
+  /// subtree) should include a source of [ScrollNotification] notifications.
+  ///
+  /// Typically a [GlowingOverscrollIndicator] is created by a
+  /// [ScrollBehavior.buildViewportChrome] method, in which case
+  /// the child is usually the one provided as an argument to that method.
+  final Widget? child;
+
+  @override
+  _GlowingOverscrollIndicatorState createState() => _GlowingOverscrollIndicatorState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<AxisDirection>('axisDirection', axisDirection));
+    final String showDescription;
+    if (showLeading && showTrailing) {
+      showDescription = 'both sides';
+    } else if (showLeading) {
+      showDescription = 'leading side only';
+    } else if (showTrailing) {
+      showDescription = 'trailing side only';
+    } else {
+      showDescription = 'neither side (!)';
+    }
+    properties.add(MessageProperty('show', showDescription));
+    properties.add(ColorProperty('color', color, showName: false));
+  }
+}
+
+class _GlowingOverscrollIndicatorState extends State<GlowingOverscrollIndicator> with TickerProviderStateMixin {
+  _GlowController? _leadingController;
+  _GlowController? _trailingController;
+  Listenable? _leadingAndTrailingListener;
+
+  @override
+  void initState() {
+    super.initState();
+    _leadingController = _GlowController(vsync: this, color: widget.color, axis: widget.axis);
+    _trailingController = _GlowController(vsync: this, color: widget.color, axis: widget.axis);
+    _leadingAndTrailingListener = Listenable.merge(<Listenable>[_leadingController!, _trailingController!]);
+  }
+
+  @override
+  void didUpdateWidget(GlowingOverscrollIndicator oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.color != widget.color || oldWidget.axis != widget.axis) {
+      _leadingController!.color = widget.color;
+      _leadingController!.axis = widget.axis;
+      _trailingController!.color = widget.color;
+      _trailingController!.axis = widget.axis;
+    }
+  }
+
+  Type? _lastNotificationType;
+  final Map<bool, bool> _accepted = <bool, bool>{false: true, true: true};
+
+  bool _handleScrollNotification(ScrollNotification notification) {
+    if (!widget.notificationPredicate(notification))
+      return false;
+
+    // Update the paint offset with the current scroll position. This makes
+    // sure that the glow effect correctly scrolls in line with the current
+    // scroll, e.g. when scrolling in the opposite direction again to hide
+    // the glow. Otherwise, the glow would always stay in a fixed position,
+    // even if the top of the content already scrolled away.
+    // For example (CustomScrollView with sliver before center), the scroll
+    // extent is [-200.0, 300.0], scroll in the opposite direction with 10.0 pixels
+    // before glow disappears, so the current pixels is -190.0,
+    // in this case, we should move the glow up 10.0 pixels and should not
+    // overflow the scrollable widget's edge. https://github.com/flutter/flutter/issues/64149.
+    _leadingController!._paintOffsetScrollPixels =
+      -math.min(notification.metrics.pixels - notification.metrics.minScrollExtent, _leadingController!._paintOffset);
+    _trailingController!._paintOffsetScrollPixels =
+      -math.min(notification.metrics.maxScrollExtent - notification.metrics.pixels, _trailingController!._paintOffset);
+
+    if (notification is OverscrollNotification) {
+      _GlowController? controller;
+      if (notification.overscroll < 0.0) {
+        controller = _leadingController;
+      } else if (notification.overscroll > 0.0) {
+        controller = _trailingController;
+      } else {
+        assert(false);
+      }
+      final bool isLeading = controller == _leadingController;
+      if (_lastNotificationType != OverscrollNotification) {
+        final OverscrollIndicatorNotification confirmationNotification = OverscrollIndicatorNotification(leading: isLeading);
+        confirmationNotification.dispatch(context);
+        _accepted[isLeading] = confirmationNotification._accepted;
+        if (_accepted[isLeading]!) {
+          controller!._paintOffset = confirmationNotification.paintOffset;
+        }
+      }
+      assert(controller != null);
+      assert(notification.metrics.axis == widget.axis);
+      if (_accepted[isLeading]!) {
+        if (notification.velocity != 0.0) {
+          assert(notification.dragDetails == null);
+          controller!.absorbImpact(notification.velocity.abs());
+        } else {
+          assert(notification.overscroll != 0.0);
+          if (notification.dragDetails != null) {
+            assert(notification.dragDetails!.globalPosition != null);
+            final RenderBox renderer = notification.context!.findRenderObject()! as RenderBox;
+            assert(renderer != null);
+            assert(renderer.hasSize);
+            final Size size = renderer.size;
+            final Offset position = renderer.globalToLocal(notification.dragDetails!.globalPosition);
+            switch (notification.metrics.axis) {
+              case Axis.horizontal:
+                controller!.pull(notification.overscroll.abs(), size.width, position.dy.clamp(0.0, size.height), size.height);
+                break;
+              case Axis.vertical:
+                controller!.pull(notification.overscroll.abs(), size.height, position.dx.clamp(0.0, size.width), size.width);
+                break;
+            }
+          }
+        }
+      }
+    } else if (notification is ScrollEndNotification || notification is ScrollUpdateNotification) {
+      if ((notification as dynamic).dragDetails != null) {
+        _leadingController!.scrollEnd();
+        _trailingController!.scrollEnd();
+      }
+    }
+    _lastNotificationType = notification.runtimeType;
+    return false;
+  }
+
+  @override
+  void dispose() {
+    _leadingController!.dispose();
+    _trailingController!.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return NotificationListener<ScrollNotification>(
+      onNotification: _handleScrollNotification,
+      child: RepaintBoundary(
+        child: CustomPaint(
+          foregroundPainter: _GlowingOverscrollIndicatorPainter(
+            leadingController: widget.showLeading ? _leadingController : null,
+            trailingController: widget.showTrailing ? _trailingController : null,
+            axisDirection: widget.axisDirection,
+            repaint: _leadingAndTrailingListener,
+          ),
+          child: RepaintBoundary(
+            child: widget.child,
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+// The Glow logic is a port of the logic in the following file:
+// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/EdgeEffect.java
+// as of December 2016.
+
+enum _GlowState { idle, absorb, pull, recede }
+
+class _GlowController extends ChangeNotifier {
+  _GlowController({
+    required TickerProvider vsync,
+    required Color color,
+    required Axis axis,
+  }) : assert(vsync != null),
+       assert(color != null),
+       assert(axis != null),
+       _color = color,
+       _axis = axis {
+    _glowController = AnimationController(vsync: vsync)
+      ..addStatusListener(_changePhase);
+    final Animation<double> decelerator = CurvedAnimation(
+      parent: _glowController,
+      curve: Curves.decelerate,
+    )..addListener(notifyListeners);
+    _glowOpacity = decelerator.drive(_glowOpacityTween);
+    _glowSize = decelerator.drive(_glowSizeTween);
+    _displacementTicker = vsync.createTicker(_tickDisplacement);
+  }
+
+  // animation of the main axis direction
+  _GlowState _state = _GlowState.idle;
+  late final AnimationController _glowController;
+  Timer? _pullRecedeTimer;
+  double _paintOffset = 0.0;
+  double _paintOffsetScrollPixels = 0.0;
+
+  // animation values
+  final Tween<double> _glowOpacityTween = Tween<double>(begin: 0.0, end: 0.0);
+  late final Animation<double> _glowOpacity;
+  final Tween<double> _glowSizeTween = Tween<double>(begin: 0.0, end: 0.0);
+  late final Animation<double> _glowSize;
+
+  // animation of the cross axis position
+  late final Ticker _displacementTicker;
+  Duration? _displacementTickerLastElapsed;
+  double _displacementTarget = 0.5;
+  double _displacement = 0.5;
+
+  // tracking the pull distance
+  double _pullDistance = 0.0;
+
+  Color get color => _color;
+  Color _color;
+  set color(Color value) {
+    assert(color != null);
+    if (color == value)
+      return;
+    _color = value;
+    notifyListeners();
+  }
+
+  Axis get axis => _axis;
+  Axis _axis;
+  set axis(Axis value) {
+    assert(axis != null);
+    if (axis == value)
+      return;
+    _axis = value;
+    notifyListeners();
+  }
+
+  static const Duration _recedeTime = Duration(milliseconds: 600);
+  static const Duration _pullTime = Duration(milliseconds: 167);
+  static const Duration _pullHoldTime = Duration(milliseconds: 167);
+  static const Duration _pullDecayTime = Duration(milliseconds: 2000);
+  static final Duration _crossAxisHalfTime = Duration(microseconds: (Duration.microsecondsPerSecond / 60.0).round());
+
+  static const double _maxOpacity = 0.5;
+  static const double _pullOpacityGlowFactor = 0.8;
+  static const double _velocityGlowFactor = 0.00006;
+  static const double _sqrt3 = 1.73205080757; // const math.sqrt(3)
+  static const double _widthToHeightFactor = (3.0 / 4.0) * (2.0 - _sqrt3);
+
+  // absorbed velocities are clamped to the range _minVelocity.._maxVelocity
+  static const double _minVelocity = 100.0; // logical pixels per second
+  static const double _maxVelocity = 10000.0; // logical pixels per second
+
+  @override
+  void dispose() {
+    _glowController.dispose();
+    _displacementTicker.dispose();
+    _pullRecedeTimer?.cancel();
+    super.dispose();
+  }
+
+  /// Handle a scroll slamming into the edge at a particular velocity.
+  ///
+  /// The velocity must be positive.
+  void absorbImpact(double velocity) {
+    assert(velocity >= 0.0);
+    _pullRecedeTimer?.cancel();
+    _pullRecedeTimer = null;
+    velocity = velocity.clamp(_minVelocity, _maxVelocity);
+    _glowOpacityTween.begin = _state == _GlowState.idle ? 0.3 : _glowOpacity.value;
+    _glowOpacityTween.end = (velocity * _velocityGlowFactor).clamp(_glowOpacityTween.begin!, _maxOpacity);
+    _glowSizeTween.begin = _glowSize.value;
+    _glowSizeTween.end = math.min(0.025 + 7.5e-7 * velocity * velocity, 1.0);
+    _glowController.duration = Duration(milliseconds: (0.15 + velocity * 0.02).round());
+    _glowController.forward(from: 0.0);
+    _displacement = 0.5;
+    _state = _GlowState.absorb;
+  }
+
+  /// Handle a user-driven overscroll.
+  ///
+  /// The `overscroll` argument should be the scroll distance in logical pixels,
+  /// the `extent` argument should be the total dimension of the viewport in the
+  /// main axis in logical pixels, the `crossAxisOffset` argument should be the
+  /// distance from the leading (left or top) edge of the cross axis of the
+  /// viewport, and the `crossExtent` should be the size of the cross axis. For
+  /// example, a pull of 50 pixels up the middle of a 200 pixel high and 100
+  /// pixel wide vertical viewport should result in a call of `pull(50.0, 200.0,
+  /// 50.0, 100.0)`. The `overscroll` value should be positive regardless of the
+  /// direction.
+  void pull(double overscroll, double extent, double crossAxisOffset, double crossExtent) {
+    _pullRecedeTimer?.cancel();
+    _pullDistance += overscroll / 200.0; // This factor is magic. Not clear why we need it to match Android.
+    _glowOpacityTween.begin = _glowOpacity.value;
+    _glowOpacityTween.end = math.min(_glowOpacity.value + overscroll / extent * _pullOpacityGlowFactor, _maxOpacity);
+    final double height = math.min(extent, crossExtent * _widthToHeightFactor);
+    _glowSizeTween.begin = _glowSize.value;
+    _glowSizeTween.end = math.max(1.0 - 1.0 / (0.7 * math.sqrt(_pullDistance * height)), _glowSize.value);
+    _displacementTarget = crossAxisOffset / crossExtent;
+    if (_displacementTarget != _displacement) {
+      if (!_displacementTicker.isTicking) {
+        assert(_displacementTickerLastElapsed == null);
+        _displacementTicker.start();
+      }
+    } else {
+      _displacementTicker.stop();
+      _displacementTickerLastElapsed = null;
+    }
+    _glowController.duration = _pullTime;
+    if (_state != _GlowState.pull) {
+      _glowController.forward(from: 0.0);
+      _state = _GlowState.pull;
+    } else {
+      if (!_glowController.isAnimating) {
+        assert(_glowController.value == 1.0);
+        notifyListeners();
+      }
+    }
+    _pullRecedeTimer = Timer(_pullHoldTime, () => _recede(_pullDecayTime));
+  }
+
+  void scrollEnd() {
+    if (_state == _GlowState.pull)
+      _recede(_recedeTime);
+  }
+
+  void _changePhase(AnimationStatus status) {
+    if (status != AnimationStatus.completed)
+      return;
+    switch (_state) {
+      case _GlowState.absorb:
+        _recede(_recedeTime);
+        break;
+      case _GlowState.recede:
+        _state = _GlowState.idle;
+        _pullDistance = 0.0;
+        break;
+      case _GlowState.pull:
+      case _GlowState.idle:
+        break;
+    }
+  }
+
+  void _recede(Duration duration) {
+    if (_state == _GlowState.recede || _state == _GlowState.idle)
+      return;
+    _pullRecedeTimer?.cancel();
+    _pullRecedeTimer = null;
+    _glowOpacityTween.begin = _glowOpacity.value;
+    _glowOpacityTween.end = 0.0;
+    _glowSizeTween.begin = _glowSize.value;
+    _glowSizeTween.end = 0.0;
+    _glowController.duration = duration;
+    _glowController.forward(from: 0.0);
+    _state = _GlowState.recede;
+  }
+
+  void _tickDisplacement(Duration elapsed) {
+    if (_displacementTickerLastElapsed != null) {
+      final double t = (elapsed.inMicroseconds - _displacementTickerLastElapsed!.inMicroseconds).toDouble();
+      _displacement = _displacementTarget - (_displacementTarget - _displacement) * math.pow(2.0, -t / _crossAxisHalfTime.inMicroseconds);
+      notifyListeners();
+    }
+    if (nearEqual(_displacementTarget, _displacement, Tolerance.defaultTolerance.distance)) {
+      _displacementTicker.stop();
+      _displacementTickerLastElapsed = null;
+    } else {
+      _displacementTickerLastElapsed = elapsed;
+    }
+  }
+
+  void paint(Canvas canvas, Size size) {
+    if (_glowOpacity.value == 0.0)
+      return;
+    final double baseGlowScale = size.width > size.height ? size.height / size.width : 1.0;
+    final double radius = size.width * 3.0 / 2.0;
+    final double height = math.min(size.height, size.width * _widthToHeightFactor);
+    final double scaleY = _glowSize.value * baseGlowScale;
+    final Rect rect = Rect.fromLTWH(0.0, 0.0, size.width, height);
+    final Offset center = Offset((size.width / 2.0) * (0.5 + _displacement), height - radius);
+    final Paint paint = Paint()..color = color.withOpacity(_glowOpacity.value);
+    canvas.save();
+    canvas.translate(0.0, _paintOffset + _paintOffsetScrollPixels);
+    canvas.scale(1.0, scaleY);
+    canvas.clipRect(rect);
+    canvas.drawCircle(center, radius, paint);
+    canvas.restore();
+  }
+}
+
+class _GlowingOverscrollIndicatorPainter extends CustomPainter {
+  _GlowingOverscrollIndicatorPainter({
+    this.leadingController,
+    this.trailingController,
+    required this.axisDirection,
+    Listenable? repaint,
+  }) : super(
+    repaint: repaint,
+  );
+
+  /// The controller for the overscroll glow on the side with negative scroll offsets.
+  ///
+  /// For a vertical downwards viewport, this is the top side.
+  final _GlowController? leadingController;
+
+  /// The controller for the overscroll glow on the side with positive scroll offsets.
+  ///
+  /// For a vertical downwards viewport, this is the bottom side.
+  final _GlowController? trailingController;
+
+  /// The direction of the viewport.
+  final AxisDirection axisDirection;
+
+  static const double piOver2 = math.pi / 2.0;
+
+  void _paintSide(Canvas canvas, Size size, _GlowController? controller, AxisDirection axisDirection, GrowthDirection growthDirection) {
+    if (controller == null)
+      return;
+    switch (applyGrowthDirectionToAxisDirection(axisDirection, growthDirection)) {
+      case AxisDirection.up:
+        controller.paint(canvas, size);
+        break;
+      case AxisDirection.down:
+        canvas.save();
+        canvas.translate(0.0, size.height);
+        canvas.scale(1.0, -1.0);
+        controller.paint(canvas, size);
+        canvas.restore();
+        break;
+      case AxisDirection.left:
+        canvas.save();
+        canvas.rotate(piOver2);
+        canvas.scale(1.0, -1.0);
+        controller.paint(canvas, Size(size.height, size.width));
+        canvas.restore();
+        break;
+      case AxisDirection.right:
+        canvas.save();
+        canvas.translate(size.width, 0.0);
+        canvas.rotate(piOver2);
+        controller.paint(canvas, Size(size.height, size.width));
+        canvas.restore();
+        break;
+    }
+  }
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    _paintSide(canvas, size, leadingController, axisDirection, GrowthDirection.reverse);
+    _paintSide(canvas, size, trailingController, axisDirection, GrowthDirection.forward);
+  }
+
+  @override
+  bool shouldRepaint(_GlowingOverscrollIndicatorPainter oldDelegate) {
+    return oldDelegate.leadingController != leadingController
+        || oldDelegate.trailingController != trailingController;
+  }
+}
+
+/// A notification that an [GlowingOverscrollIndicator] will start showing an
+/// overscroll indication.
+///
+/// To prevent the indicator from showing the indication, call [disallowGlow] on
+/// the notification.
+///
+/// See also:
+///
+///  * [GlowingOverscrollIndicator], which generates this type of notification.
+class OverscrollIndicatorNotification extends Notification with ViewportNotificationMixin {
+  /// Creates a notification that an [GlowingOverscrollIndicator] will start
+  /// showing an overscroll indication.
+  ///
+  /// The [leading] argument must not be null.
+  OverscrollIndicatorNotification({
+    required this.leading,
+  });
+
+  /// Whether the indication will be shown on the leading edge of the scroll
+  /// view.
+  final bool leading;
+
+  /// Controls at which offset the glow should be drawn.
+  ///
+  /// A positive offset will move the glow away from its edge,
+  /// i.e. for a vertical, [leading] indicator, a [paintOffset] of 100.0 will
+  /// draw the indicator 100.0 pixels from the top of the edge.
+  /// For a vertical indicator with [leading] set to `false`, a [paintOffset]
+  /// of 100.0 will draw the indicator 100.0 pixels from the bottom instead.
+  ///
+  /// A negative [paintOffset] is generally not useful, since the glow will be
+  /// clipped.
+  double paintOffset = 0.0;
+
+  bool _accepted = true;
+
+  /// Call this method if the glow should be prevented.
+  void disallowGlow() {
+    _accepted = false;
+  }
+
+  @override
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('side: ${leading ? "leading edge" : "trailing edge"}');
+  }
+}
diff --git a/lib/src/widgets/page_storage.dart b/lib/src/widgets/page_storage.dart
new file mode 100644
index 0000000..e73daf5
--- /dev/null
+++ b/lib/src/widgets/page_storage.dart
@@ -0,0 +1,275 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'framework.dart';
+
+/// A key can be used to persist the widget state in storage after
+/// the destruction and will be restored when recreated.
+///
+/// Each key with its value plus the ancestor chain of other PageStorageKeys need to
+/// be unique within the widget's closest ancestor [PageStorage]. To make it possible for a
+/// saved value to be found when a widget is recreated, the key's value must
+/// not be objects whose identity will change each time the widget is created.
+///
+/// See also:
+///
+///  * [PageStorage], which is the closet ancestor for [PageStorageKey].
+class PageStorageKey<T> extends ValueKey<T> {
+  /// Creates a [ValueKey] that defines where [PageStorage] values will be saved.
+  const PageStorageKey(T value) : super(value);
+}
+
+@immutable
+class _StorageEntryIdentifier {
+  const _StorageEntryIdentifier(this.keys)
+    : assert(keys != null);
+
+  final List<PageStorageKey<dynamic>> keys;
+
+  bool get isNotEmpty => keys.isNotEmpty;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _StorageEntryIdentifier
+        && listEquals<PageStorageKey<dynamic>>(other.keys, keys);
+  }
+
+  @override
+  int get hashCode => hashList(keys);
+
+  @override
+  String toString() {
+    return 'StorageEntryIdentifier(${keys.join(":")})';
+  }
+}
+
+/// A storage bucket associated with a page in an app.
+///
+/// Useful for storing per-page state that persists across navigations from one
+/// page to another.
+class PageStorageBucket {
+  static bool _maybeAddKey(BuildContext context, List<PageStorageKey<dynamic>> keys) {
+    final Widget widget = context.widget;
+    final Key? key = widget.key;
+    if (key is PageStorageKey)
+      keys.add(key);
+    return widget is! PageStorage;
+  }
+
+  List<PageStorageKey<dynamic>> _allKeys(BuildContext context) {
+    final List<PageStorageKey<dynamic>> keys = <PageStorageKey<dynamic>>[];
+    if (_maybeAddKey(context, keys)) {
+      context.visitAncestorElements((Element element) {
+        return _maybeAddKey(element, keys);
+      });
+    }
+    return keys;
+  }
+
+  _StorageEntryIdentifier _computeIdentifier(BuildContext context) {
+    return _StorageEntryIdentifier(_allKeys(context));
+  }
+
+  Map<Object, dynamic>? _storage;
+
+  /// Write the given data into this page storage bucket using the
+  /// specified identifier or an identifier computed from the given context.
+  /// The computed identifier is based on the [PageStorageKey]s
+  /// found in the path from context to the [PageStorage] widget that
+  /// owns this page storage bucket.
+  ///
+  /// If an explicit identifier is not provided and no [PageStorageKey]s
+  /// are found, then the `data` is not saved.
+  void writeState(BuildContext context, dynamic data, { Object? identifier }) {
+    _storage ??= <Object, dynamic>{};
+    if (identifier != null) {
+      _storage![identifier] = data;
+    } else {
+      final _StorageEntryIdentifier contextIdentifier = _computeIdentifier(context);
+      if (contextIdentifier.isNotEmpty)
+        _storage![contextIdentifier] = data;
+    }
+  }
+
+  /// Read given data from into this page storage bucket using the specified
+  /// identifier or an identifier computed from the given context.
+  /// The computed identifier is based on the [PageStorageKey]s
+  /// found in the path from context to the [PageStorage] widget that
+  /// owns this page storage bucket.
+  ///
+  /// If an explicit identifier is not provided and no [PageStorageKey]s
+  /// are found, then null is returned.
+  dynamic readState(BuildContext context, { Object? identifier }) {
+    if (_storage == null)
+      return null;
+    if (identifier != null)
+      return _storage![identifier];
+    final _StorageEntryIdentifier contextIdentifier = _computeIdentifier(context);
+    return contextIdentifier.isNotEmpty ? _storage![contextIdentifier] : null;
+  }
+}
+
+/// Establish a subtree in which widgets can opt into persisting states after
+/// being destroyed.
+///
+/// [PageStorage] is used to save and restore values that can outlive the widget.
+/// For example, when multiple pages are grouped in tabs, when a page is
+/// switched out, its widget is destroyed and its state is lost. By adding a
+/// [PageStorage] at the root and adding a [PageStorageKey] to each page, some of the
+/// page's state (e.g. the scroll position of a [Scrollable] widget) will be stored
+/// automatically in its closest ancestor [PageStorage], and restored when it's
+/// switched back.
+///
+/// Usually you don't need to explicitly use a [PageStorage], since it's already
+/// included in routes.
+///
+/// [PageStorageKey] is used by [Scrollable] if [ScrollController.keepScrollOffset]
+/// is enabled to save their [ScrollPosition]s. When more than one
+/// scrollable ([ListView], [SingleChildScrollView], [TextField], etc.) appears
+/// within the widget's closest ancestor [PageStorage] (such as within the same route),
+/// if you want to save all of their positions independently,
+/// you should give each of them unique [PageStorageKey]s, or set some of their
+/// `keepScrollOffset` false to prevent saving.
+///
+/// {@tool dartpad --template=freeform_no_null_safety}
+///
+/// This sample shows how to explicitly use a [PageStorage] to
+/// store the states of its children pages. Each page includes a scrollable
+/// list, whose position is preserved when switching between the tabs thanks to
+/// the help of [PageStorageKey].
+///
+/// ```dart imports
+/// import 'package:flute/material.dart';
+/// ```
+///
+/// ```dart main
+/// void main() => runApp(MyApp());
+/// ```
+///
+/// ```dart
+/// class MyApp extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return MaterialApp(
+///       home: MyHomePage(),
+///     );
+///   }
+/// }
+///
+/// class MyHomePage extends StatefulWidget {
+///   @override
+///   _MyHomePageState createState() => _MyHomePageState();
+/// }
+///
+/// class _MyHomePageState extends State<MyHomePage> {
+///   final List<Widget> pages = <Widget>[
+///     ColorBoxPage(
+///       key: PageStorageKey('pageOne'),
+///     ),
+///     ColorBoxPage(
+///       key: PageStorageKey('pageTwo'),
+///     )
+///   ];
+///   int currentTab = 0;
+///   final PageStorageBucket _bucket = PageStorageBucket();
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Scaffold(
+///       appBar: AppBar(
+///         title: Text("Persistence Example"),
+///       ),
+///       body: PageStorage(
+///         child: pages[currentTab],
+///         bucket: _bucket,
+///       ),
+///       bottomNavigationBar: BottomNavigationBar(
+///         currentIndex: currentTab,
+///         onTap: (int index) {
+///           setState(() {
+///             currentTab = index;
+///           });
+///         },
+///         items: <BottomNavigationBarItem>[
+///           BottomNavigationBarItem(
+///             icon: Icon(Icons.home),
+///             label: 'page 1',
+///           ),
+///           BottomNavigationBarItem(
+///             icon: Icon(Icons.settings),
+///             label: 'page2',
+///           ),
+///         ],
+///       ),
+///     );
+///   }
+/// }
+///
+/// class ColorBoxPage extends StatelessWidget {
+///   ColorBoxPage({
+///     Key key,
+///   }) : super(key: key);
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return ListView.builder(
+///       itemExtent: 250.0,
+///       itemBuilder: (context, index) => Container(
+///         padding: EdgeInsets.all(10.0),
+///         child: Material(
+///           color: index % 2 == 0 ? Colors.cyan : Colors.deepOrange,
+///           child: Center(
+///             child: Text(index.toString()),
+///           ),
+///         ),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ModalRoute], which includes this class.
+class PageStorage extends StatelessWidget {
+  /// Creates a widget that provides a storage bucket for its descendants.
+  ///
+  /// The [bucket] argument must not be null.
+  const PageStorage({
+    Key? key,
+    required this.bucket,
+    required this.child,
+  }) : assert(bucket != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The page storage bucket to use for this subtree.
+  final PageStorageBucket bucket;
+
+  /// The bucket from the closest instance of this class that encloses the given context.
+  ///
+  /// Returns null if none exists.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// PageStorageBucket bucket = PageStorage.of(context);
+  /// ```
+  static PageStorageBucket? of(BuildContext context) {
+    final PageStorage? widget = context.findAncestorWidgetOfExactType<PageStorage>();
+    return widget?.bucket;
+  }
+
+  @override
+  Widget build(BuildContext context) => child;
+}
diff --git a/lib/src/widgets/page_view.dart b/lib/src/widgets/page_view.dart
new file mode 100644
index 0000000..83926c6
--- /dev/null
+++ b/lib/src/widgets/page_view.dart
@@ -0,0 +1,948 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/physics.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/gestures.dart' show DragStartBehavior;
+import 'package:flute/foundation.dart' show precisionErrorTolerance;
+
+import 'basic.dart';
+import 'debug.dart';
+import 'framework.dart';
+import 'notification_listener.dart';
+import 'page_storage.dart';
+import 'scroll_context.dart';
+import 'scroll_controller.dart';
+import 'scroll_metrics.dart';
+import 'scroll_notification.dart';
+import 'scroll_physics.dart';
+import 'scroll_position.dart';
+import 'scroll_position_with_single_context.dart';
+import 'scroll_view.dart';
+import 'scrollable.dart';
+import 'sliver.dart';
+import 'sliver_fill.dart';
+import 'viewport.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+
+/// A controller for [PageView].
+///
+/// A page controller lets you manipulate which page is visible in a [PageView].
+/// In addition to being able to control the pixel offset of the content inside
+/// the [PageView], a [PageController] also lets you control the offset in terms
+/// of pages, which are increments of the viewport size.
+///
+/// See also:
+///
+///  * [PageView], which is the widget this object controls.
+///
+/// {@tool snippet}
+///
+/// This widget introduces a [MaterialApp], [Scaffold] and [PageView] with two pages
+/// using the default constructor. Both pages contain an [ElevatedButton] allowing you
+/// to animate the [PageView] using a [PageController].
+///
+/// ```dart
+/// class MyPageView extends StatefulWidget {
+///   MyPageView({Key key}) : super(key: key);
+///
+///   _MyPageViewState createState() => _MyPageViewState();
+/// }
+///
+/// class _MyPageViewState extends State<MyPageView> {
+///   PageController _pageController;
+///
+///   @override
+///   void initState() {
+///     super.initState();
+///     _pageController = PageController();
+///   }
+///
+///   @override
+///   void dispose() {
+///     _pageController.dispose();
+///     super.dispose();
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return MaterialApp(
+///       home: Scaffold(
+///         body: PageView(
+///           controller: _pageController,
+///           children: [
+///             Container(
+///               color: Colors.red,
+///               child: Center(
+///                 child: ElevatedButton(
+///                   onPressed: () {
+///                     if (_pageController.hasClients) {
+///                       _pageController.animateToPage(
+///                         1,
+///                         duration: const Duration(milliseconds: 400),
+///                         curve: Curves.easeInOut,
+///                       );
+///                     }
+///                   },
+///                   child: Text('Next'),
+///                 ),
+///               ),
+///             ),
+///             Container(
+///               color: Colors.blue,
+///               child: Center(
+///                 child: ElevatedButton(
+///                   onPressed: () {
+///                     if (_pageController.hasClients) {
+///                       _pageController.animateToPage(
+///                         0,
+///                         duration: const Duration(milliseconds: 400),
+///                         curve: Curves.easeInOut,
+///                       );
+///                     }
+///                   },
+///                   child: Text('Previous'),
+///                 ),
+///               ),
+///             ),
+///           ],
+///         ),
+///       ),
+///     );
+///   }
+/// }
+///
+/// ```
+/// {@end-tool}
+class PageController extends ScrollController {
+  /// Creates a page controller.
+  ///
+  /// The [initialPage], [keepPage], and [viewportFraction] arguments must not be null.
+  PageController({
+    this.initialPage = 0,
+    this.keepPage = true,
+    this.viewportFraction = 1.0,
+  }) : assert(initialPage != null),
+       assert(keepPage != null),
+       assert(viewportFraction != null),
+       assert(viewportFraction > 0.0);
+
+  /// The page to show when first creating the [PageView].
+  final int initialPage;
+
+  /// Save the current [page] with [PageStorage] and restore it if
+  /// this controller's scrollable is recreated.
+  ///
+  /// If this property is set to false, the current [page] is never saved
+  /// and [initialPage] is always used to initialize the scroll offset.
+  /// If true (the default), the initial page is used the first time the
+  /// controller's scrollable is created, since there's isn't a page to
+  /// restore yet. Subsequently the saved page is restored and
+  /// [initialPage] is ignored.
+  ///
+  /// See also:
+  ///
+  ///  * [PageStorageKey], which should be used when more than one
+  ///    scrollable appears in the same route, to distinguish the [PageStorage]
+  ///    locations used to save scroll offsets.
+  final bool keepPage;
+
+  /// The fraction of the viewport that each page should occupy.
+  ///
+  /// Defaults to 1.0, which means each page fills the viewport in the scrolling
+  /// direction.
+  final double viewportFraction;
+
+  /// The current page displayed in the controlled [PageView].
+  ///
+  /// There are circumstances that this [PageController] can't know the current
+  /// page. Reading [page] will throw an [AssertionError] in the following cases:
+  ///
+  /// 1. No [PageView] is currently using this [PageController]. Once a
+  /// [PageView] starts using this [PageController], the new [page]
+  /// position will be derived:
+  ///
+  ///   * First, based on the attached [PageView]'s [BuildContext] and the
+  ///     position saved at that context's [PageStorage] if [keepPage] is true.
+  ///   * Second, from the [PageController]'s [initialPage].
+  ///
+  /// 2. More than one [PageView] using the same [PageController].
+  ///
+  /// The [hasClients] property can be used to check if a [PageView] is attached
+  /// prior to accessing [page].
+  double? get page {
+    assert(
+      positions.isNotEmpty,
+      'PageController.page cannot be accessed before a PageView is built with it.',
+    );
+    assert(
+      positions.length == 1,
+      'The page property cannot be read when multiple PageViews are attached to '
+      'the same PageController.',
+    );
+    final _PagePosition position = this.position as _PagePosition;
+    return position.page;
+  }
+
+  /// Animates the controlled [PageView] from the current page to the given page.
+  ///
+  /// The animation lasts for the given duration and follows the given curve.
+  /// The returned [Future] resolves when the animation completes.
+  ///
+  /// The `duration` and `curve` arguments must not be null.
+  Future<void> animateToPage(
+    int page, {
+    required Duration duration,
+    required Curve curve,
+  }) {
+    final _PagePosition position = this.position as _PagePosition;
+    return position.animateTo(
+      position.getPixelsFromPage(page.toDouble()),
+      duration: duration,
+      curve: curve,
+    );
+  }
+
+  /// Changes which page is displayed in the controlled [PageView].
+  ///
+  /// Jumps the page position from its current value to the given value,
+  /// without animation, and without checking if the new value is in range.
+  void jumpToPage(int page) {
+    final _PagePosition position = this.position as _PagePosition;
+    position.jumpTo(position.getPixelsFromPage(page.toDouble()));
+  }
+
+  /// Animates the controlled [PageView] to the next page.
+  ///
+  /// The animation lasts for the given duration and follows the given curve.
+  /// The returned [Future] resolves when the animation completes.
+  ///
+  /// The `duration` and `curve` arguments must not be null.
+  Future<void> nextPage({ required Duration duration, required Curve curve }) {
+    return animateToPage(page!.round() + 1, duration: duration, curve: curve);
+  }
+
+  /// Animates the controlled [PageView] to the previous page.
+  ///
+  /// The animation lasts for the given duration and follows the given curve.
+  /// The returned [Future] resolves when the animation completes.
+  ///
+  /// The `duration` and `curve` arguments must not be null.
+  Future<void> previousPage({ required Duration duration, required Curve curve }) {
+    return animateToPage(page!.round() - 1, duration: duration, curve: curve);
+  }
+
+  @override
+  ScrollPosition createScrollPosition(ScrollPhysics physics, ScrollContext context, ScrollPosition? oldPosition) {
+    return _PagePosition(
+      physics: physics,
+      context: context,
+      initialPage: initialPage,
+      keepPage: keepPage,
+      viewportFraction: viewportFraction,
+      oldPosition: oldPosition,
+    );
+  }
+
+  @override
+  void attach(ScrollPosition position) {
+    super.attach(position);
+    final _PagePosition pagePosition = position as _PagePosition;
+    pagePosition.viewportFraction = viewportFraction;
+  }
+}
+
+/// Metrics for a [PageView].
+///
+/// The metrics are available on [ScrollNotification]s generated from
+/// [PageView]s.
+class PageMetrics extends FixedScrollMetrics {
+  /// Creates an immutable snapshot of values associated with a [PageView].
+  PageMetrics({
+    required double? minScrollExtent,
+    required double? maxScrollExtent,
+    required double? pixels,
+    required double? viewportDimension,
+    required AxisDirection axisDirection,
+    required this.viewportFraction,
+  }) : super(
+         minScrollExtent: minScrollExtent,
+         maxScrollExtent: maxScrollExtent,
+         pixels: pixels,
+         viewportDimension: viewportDimension,
+         axisDirection: axisDirection,
+       );
+
+  @override
+  PageMetrics copyWith({
+    double? minScrollExtent,
+    double? maxScrollExtent,
+    double? pixels,
+    double? viewportDimension,
+    AxisDirection? axisDirection,
+    double? viewportFraction,
+  }) {
+    return PageMetrics(
+      minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
+      maxScrollExtent: maxScrollExtent ?? (hasContentDimensions ? this.maxScrollExtent : null),
+      pixels: pixels ?? (hasPixels ? this.pixels : null),
+      viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
+      axisDirection: axisDirection ?? this.axisDirection,
+      viewportFraction: viewportFraction ?? this.viewportFraction,
+    );
+  }
+
+  /// The current page displayed in the [PageView].
+  double? get page {
+    return math.max(0.0, pixels.clamp(minScrollExtent, maxScrollExtent)) /
+           math.max(1.0, viewportDimension * viewportFraction);
+  }
+
+  /// The fraction of the viewport that each page occupies.
+  ///
+  /// Used to compute [page] from the current [pixels].
+  final double viewportFraction;
+}
+
+class _PagePosition extends ScrollPositionWithSingleContext implements PageMetrics {
+  _PagePosition({
+    required ScrollPhysics physics,
+    required ScrollContext context,
+    this.initialPage = 0,
+    bool keepPage = true,
+    double viewportFraction = 1.0,
+    ScrollPosition? oldPosition,
+  }) : assert(initialPage != null),
+       assert(keepPage != null),
+       assert(viewportFraction != null),
+       assert(viewportFraction > 0.0),
+       _viewportFraction = viewportFraction,
+       _pageToUseOnStartup = initialPage.toDouble(),
+       super(
+         physics: physics,
+         context: context,
+         initialPixels: null,
+         keepScrollOffset: keepPage,
+         oldPosition: oldPosition,
+       );
+
+  final int initialPage;
+  double _pageToUseOnStartup;
+
+  @override
+  Future<void> ensureVisible(
+    RenderObject object, {
+    double alignment = 0.0,
+    Duration duration = Duration.zero,
+    Curve curve = Curves.ease,
+    ScrollPositionAlignmentPolicy alignmentPolicy = ScrollPositionAlignmentPolicy.explicit,
+    RenderObject? targetRenderObject,
+  }) {
+    // Since the _PagePosition is intended to cover the available space within
+    // its viewport, stop trying to move the target render object to the center
+    // - otherwise, could end up changing which page is visible and moving the
+    // targetRenderObject out of the viewport.
+    return super.ensureVisible(
+      object,
+      alignment: alignment,
+      duration: duration,
+      curve: curve,
+      alignmentPolicy: alignmentPolicy,
+      targetRenderObject: null,
+    );
+  }
+
+  @override
+  double get viewportFraction => _viewportFraction;
+  double _viewportFraction;
+  set viewportFraction(double value) {
+    if (_viewportFraction == value)
+      return;
+    final double? oldPage = page;
+    _viewportFraction = value;
+    if (oldPage != null)
+      forcePixels(getPixelsFromPage(oldPage));
+  }
+
+  // The amount of offset that will be added to [minScrollExtent] and subtracted
+  // from [maxScrollExtent], such that every page will properly snap to the center
+  // of the viewport when viewportFraction is greater than 1.
+  //
+  // The value is 0 if viewportFraction is less than or equal to 1, larger than 0
+  // otherwise.
+  double get _initialPageOffset => math.max(0, viewportDimension * (viewportFraction - 1) / 2);
+
+  double getPageFromPixels(double pixels, double viewportDimension) {
+    final double actual = math.max(0.0, pixels - _initialPageOffset) / math.max(1.0, viewportDimension * viewportFraction);
+    final double round = actual.roundToDouble();
+    if ((actual - round).abs() < precisionErrorTolerance) {
+      return round;
+    }
+    return actual;
+  }
+
+  double getPixelsFromPage(double page) {
+    return page * viewportDimension * viewportFraction + _initialPageOffset;
+  }
+
+  @override
+  double? get page {
+    assert(
+      !hasPixels || (minScrollExtent != null && maxScrollExtent != null),
+      'Page value is only available after content dimensions are established.',
+    );
+    return !hasPixels ? null : getPageFromPixels(pixels.clamp(minScrollExtent, maxScrollExtent), viewportDimension);
+  }
+
+  @override
+  void saveScrollOffset() {
+    PageStorage.of(context.storageContext)?.writeState(context.storageContext, getPageFromPixels(pixels, viewportDimension));
+  }
+
+  @override
+  void restoreScrollOffset() {
+    if (!hasPixels) {
+      final double? value = PageStorage.of(context.storageContext)?.readState(context.storageContext) as double?;
+      if (value != null)
+        _pageToUseOnStartup = value;
+    }
+  }
+
+  @override
+  void saveOffset() {
+    context.saveOffset(getPageFromPixels(pixels, viewportDimension));
+  }
+
+  @override
+  void restoreOffset(double offset, {bool initialRestore = false}) {
+    assert(initialRestore != null);
+    assert(offset != null);
+    if (initialRestore) {
+      _pageToUseOnStartup = offset;
+    } else {
+      jumpTo(getPixelsFromPage(offset));
+    }
+  }
+
+  @override
+  bool applyViewportDimension(double viewportDimension) {
+    final double? oldViewportDimensions = hasViewportDimension ? this.viewportDimension : null;
+    if (viewportDimension == oldViewportDimensions) {
+      return true;
+    }
+    final bool result = super.applyViewportDimension(viewportDimension);
+    final double? oldPixels = hasPixels ? pixels : null;
+    final double page = (oldPixels == null || oldViewportDimensions == 0.0) ? _pageToUseOnStartup : getPageFromPixels(oldPixels, oldViewportDimensions!);
+    final double newPixels = getPixelsFromPage(page);
+
+    if (newPixels != oldPixels) {
+      correctPixels(newPixels);
+      return false;
+    }
+    return result;
+  }
+
+  @override
+  bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
+    final double newMinScrollExtent = minScrollExtent + _initialPageOffset;
+    return super.applyContentDimensions(
+      newMinScrollExtent,
+      math.max(newMinScrollExtent, maxScrollExtent - _initialPageOffset),
+    );
+  }
+
+  @override
+  PageMetrics copyWith({
+    double? minScrollExtent,
+    double? maxScrollExtent,
+    double? pixels,
+    double? viewportDimension,
+    AxisDirection? axisDirection,
+    double? viewportFraction,
+  }) {
+    return PageMetrics(
+      minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
+      maxScrollExtent: maxScrollExtent ?? (hasContentDimensions ? this.maxScrollExtent : null),
+      pixels: pixels ?? (hasPixels ? this.pixels : null),
+      viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
+      axisDirection: axisDirection ?? this.axisDirection,
+      viewportFraction: viewportFraction ?? this.viewportFraction,
+    );
+  }
+}
+
+class _ForceImplicitScrollPhysics extends ScrollPhysics {
+  const _ForceImplicitScrollPhysics({
+    required this.allowImplicitScrolling,
+    ScrollPhysics? parent,
+  }) : assert(allowImplicitScrolling != null),
+       super(parent: parent);
+
+  @override
+  _ForceImplicitScrollPhysics applyTo(ScrollPhysics? ancestor) {
+    return _ForceImplicitScrollPhysics(
+      allowImplicitScrolling: allowImplicitScrolling,
+      parent: buildParent(ancestor),
+    );
+  }
+
+  @override
+  final bool allowImplicitScrolling;
+}
+
+/// Scroll physics used by a [PageView].
+///
+/// These physics cause the page view to snap to page boundaries.
+///
+/// See also:
+///
+///  * [ScrollPhysics], the base class which defines the API for scrolling
+///    physics.
+///  * [PageView.physics], which can override the physics used by a page view.
+class PageScrollPhysics extends ScrollPhysics {
+  /// Creates physics for a [PageView].
+  const PageScrollPhysics({ ScrollPhysics? parent }) : super(parent: parent);
+
+  @override
+  PageScrollPhysics applyTo(ScrollPhysics? ancestor) {
+    return PageScrollPhysics(parent: buildParent(ancestor));
+  }
+
+  double _getPage(ScrollMetrics position) {
+    if (position is _PagePosition)
+      return position.page!;
+    return position.pixels / position.viewportDimension;
+  }
+
+  double _getPixels(ScrollMetrics position, double page) {
+    if (position is _PagePosition)
+      return position.getPixelsFromPage(page);
+    return page * position.viewportDimension;
+  }
+
+  double _getTargetPixels(ScrollMetrics position, Tolerance tolerance, double velocity) {
+    double page = _getPage(position);
+    if (velocity < -tolerance.velocity)
+      page -= 0.5;
+    else if (velocity > tolerance.velocity)
+      page += 0.5;
+    return _getPixels(position, page.roundToDouble());
+  }
+
+  @override
+  Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {
+    // If we're out of range and not headed back in range, defer to the parent
+    // ballistics, which should put us back in range at a page boundary.
+    if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
+        (velocity >= 0.0 && position.pixels >= position.maxScrollExtent))
+      return super.createBallisticSimulation(position, velocity);
+    final Tolerance tolerance = this.tolerance;
+    final double target = _getTargetPixels(position, tolerance, velocity);
+    if (target != position.pixels)
+      return ScrollSpringSimulation(spring, position.pixels, target, velocity, tolerance: tolerance);
+    return null;
+  }
+
+  @override
+  bool get allowImplicitScrolling => false;
+}
+
+// Having this global (mutable) page controller is a bit of a hack. We need it
+// to plumb in the factory for _PagePosition, but it will end up accumulating
+// a large list of scroll positions. As long as you don't try to actually
+// control the scroll positions, everything should be fine.
+final PageController _defaultPageController = PageController();
+const PageScrollPhysics _kPagePhysics = PageScrollPhysics();
+
+/// A scrollable list that works page by page.
+///
+/// Each child of a page view is forced to be the same size as the viewport.
+///
+/// You can use a [PageController] to control which page is visible in the view.
+/// In addition to being able to control the pixel offset of the content inside
+/// the [PageView], a [PageController] also lets you control the offset in terms
+/// of pages, which are increments of the viewport size.
+///
+/// The [PageController] can also be used to control the
+/// [PageController.initialPage], which determines which page is shown when the
+/// [PageView] is first constructed, and the [PageController.viewportFraction],
+/// which determines the size of the pages as a fraction of the viewport size.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=J1gE9xvph-A}
+///
+/// {@tool dartpad --template=stateless_widget_scaffold}
+///
+/// Here is an example of [PageView]. It creates a centered [Text] in each of the three pages
+/// which scroll horizontally.
+///
+/// ```dart
+///  Widget build(BuildContext context) {
+///    final controller = PageController(initialPage: 0);
+///    return PageView(
+///      /// [PageView.scrollDirection] defaults to [Axis.horizontal].
+///      /// Use [Axis.vertical] to scroll vertically.
+///      scrollDirection: Axis.horizontal,
+///      controller: controller,
+///      children: [
+///        Center(
+///          child: Text("First Page"),
+///        ),
+///        Center(
+///          child: Text("Second Page"),
+///        ),
+///        Center(
+///          child: Text("Third Page"),
+///        )
+///      ],
+///    );
+///  }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [PageController], which controls which page is visible in the view.
+///  * [SingleChildScrollView], when you need to make a single child scrollable.
+///  * [ListView], for a scrollable list of boxes.
+///  * [GridView], for a scrollable grid of boxes.
+///  * [ScrollNotification] and [NotificationListener], which can be used to watch
+///    the scroll position without using a [ScrollController].
+class PageView extends StatefulWidget {
+  /// Creates a scrollable list that works page by page from an explicit [List]
+  /// of widgets.
+  ///
+  /// This constructor is appropriate for page views with a small number of
+  /// children because constructing the [List] requires doing work for every
+  /// child that could possibly be displayed in the page view, instead of just
+  /// those children that are actually visible.
+  ///
+  /// Like other widgets in the framework, this widget expects that
+  /// the [children] list will not be mutated after it has been passed in here.
+  /// See the documentation at [SliverChildListDelegate.children] for more details.
+  ///
+  /// {@template flutter.widgets.PageView.allowImplicitScrolling}
+  /// The [allowImplicitScrolling] parameter must not be null. If true, the
+  /// [PageView] will participate in accessibility scrolling more like a
+  /// [ListView], where implicit scroll actions will move to the next page
+  /// rather than into the contents of the [PageView].
+  /// {@endtemplate}
+  PageView({
+    Key? key,
+    this.scrollDirection = Axis.horizontal,
+    this.reverse = false,
+    PageController? controller,
+    this.physics,
+    this.pageSnapping = true,
+    this.onPageChanged,
+    List<Widget> children = const <Widget>[],
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.allowImplicitScrolling = false,
+    this.restorationId,
+    this.clipBehavior = Clip.hardEdge,
+  }) : assert(allowImplicitScrolling != null),
+       assert(clipBehavior != null),
+       controller = controller ?? _defaultPageController,
+       childrenDelegate = SliverChildListDelegate(children),
+       super(key: key);
+
+  /// Creates a scrollable list that works page by page using widgets that are
+  /// created on demand.
+  ///
+  /// This constructor is appropriate for page views with a large (or infinite)
+  /// number of children because the builder is called only for those children
+  /// that are actually visible.
+  ///
+  /// Providing a non-null [itemCount] lets the [PageView] compute the maximum
+  /// scroll extent.
+  ///
+  /// [itemBuilder] will be called only with indices greater than or equal to
+  /// zero and less than [itemCount].
+  ///
+  /// [PageView.builder] by default does not support child reordering. If
+  /// you are planning to change child order at a later time, consider using
+  /// [PageView] or [PageView.custom].
+  ///
+  /// {@macro flutter.widgets.PageView.allowImplicitScrolling}
+  PageView.builder({
+    Key? key,
+    this.scrollDirection = Axis.horizontal,
+    this.reverse = false,
+    PageController? controller,
+    this.physics,
+    this.pageSnapping = true,
+    this.onPageChanged,
+    required IndexedWidgetBuilder itemBuilder,
+    int? itemCount,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.allowImplicitScrolling = false,
+    this.restorationId,
+    this.clipBehavior = Clip.hardEdge,
+  }) : assert(allowImplicitScrolling != null),
+       assert(clipBehavior != null),
+       controller = controller ?? _defaultPageController,
+       childrenDelegate = SliverChildBuilderDelegate(itemBuilder, childCount: itemCount),
+       super(key: key);
+
+  /// Creates a scrollable list that works page by page with a custom child
+  /// model.
+  ///
+  /// {@tool snippet}
+  ///
+  /// This [PageView] uses a custom [SliverChildBuilderDelegate] to support child
+  /// reordering.
+  ///
+  /// ```dart
+  /// class MyPageView extends StatefulWidget {
+  ///   @override
+  ///   _MyPageViewState createState() => _MyPageViewState();
+  /// }
+  ///
+  /// class _MyPageViewState extends State<MyPageView> {
+  ///   List<String> items = <String>['1', '2', '3', '4', '5'];
+  ///
+  ///   void _reverse() {
+  ///     setState(() {
+  ///       items = items.reversed.toList();
+  ///     });
+  ///   }
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Scaffold(
+  ///       body: SafeArea(
+  ///         child: PageView.custom(
+  ///           childrenDelegate: SliverChildBuilderDelegate(
+  ///             (BuildContext context, int index) {
+  ///               return KeepAlive(
+  ///                 data: items[index],
+  ///                 key: ValueKey<String>(items[index]),
+  ///               );
+  ///             },
+  ///             childCount: items.length,
+  ///             findChildIndexCallback: (Key key) {
+  ///               final ValueKey valueKey = key;
+  ///               final String data = valueKey.value;
+  ///               return items.indexOf(data);
+  ///             }
+  ///           ),
+  ///         ),
+  ///       ),
+  ///       bottomNavigationBar: BottomAppBar(
+  ///         child: Row(
+  ///           mainAxisAlignment: MainAxisAlignment.center,
+  ///           children: <Widget>[
+  ///             TextButton(
+  ///               onPressed: () => _reverse(),
+  ///               child: Text('Reverse items'),
+  ///             ),
+  ///           ],
+  ///         ),
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  ///
+  /// class KeepAlive extends StatefulWidget {
+  ///   const KeepAlive({Key key, this.data}) : super(key: key);
+  ///
+  ///   final String data;
+  ///
+  ///   @override
+  ///   _KeepAliveState createState() => _KeepAliveState();
+  /// }
+  ///
+  /// class _KeepAliveState extends State<KeepAlive> with AutomaticKeepAliveClientMixin{
+  ///   @override
+  ///   bool get wantKeepAlive => true;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     super.build(context);
+  ///     return Text(widget.data);
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  ///
+  /// {@macro flutter.widgets.PageView.allowImplicitScrolling}
+  PageView.custom({
+    Key? key,
+    this.scrollDirection = Axis.horizontal,
+    this.reverse = false,
+    PageController? controller,
+    this.physics,
+    this.pageSnapping = true,
+    this.onPageChanged,
+    required this.childrenDelegate,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.allowImplicitScrolling = false,
+    this.restorationId,
+    this.clipBehavior = Clip.hardEdge,
+  }) : assert(childrenDelegate != null),
+       assert(allowImplicitScrolling != null),
+       assert(clipBehavior != null),
+       controller = controller ?? _defaultPageController,
+       super(key: key);
+
+  /// Controls whether the widget's pages will respond to
+  /// [RenderObject.showOnScreen], which will allow for implicit accessibility
+  /// scrolling.
+  ///
+  /// With this flag set to false, when accessibility focus reaches the end of
+  /// the current page and the user attempts to move it to the next element, the
+  /// focus will traverse to the next widget outside of the page view.
+  ///
+  /// With this flag set to true, when accessibility focus reaches the end of
+  /// the current page and user attempts to move it to the next element, focus
+  /// will traverse to the next page in the page view.
+  final bool allowImplicitScrolling;
+
+  /// {@macro flutter.widgets.scrollable.restorationId}
+  final String? restorationId;
+
+  /// The axis along which the page view scrolls.
+  ///
+  /// Defaults to [Axis.horizontal].
+  final Axis scrollDirection;
+
+  /// Whether the page view scrolls in the reading direction.
+  ///
+  /// For example, if the reading direction is left-to-right and
+  /// [scrollDirection] is [Axis.horizontal], then the page view scrolls from
+  /// left to right when [reverse] is false and from right to left when
+  /// [reverse] is true.
+  ///
+  /// Similarly, if [scrollDirection] is [Axis.vertical], then the page view
+  /// scrolls from top to bottom when [reverse] is false and from bottom to top
+  /// when [reverse] is true.
+  ///
+  /// Defaults to false.
+  final bool reverse;
+
+  /// An object that can be used to control the position to which this page
+  /// view is scrolled.
+  final PageController controller;
+
+  /// How the page view should respond to user input.
+  ///
+  /// For example, determines how the page view continues to animate after the
+  /// user stops dragging the page view.
+  ///
+  /// The physics are modified to snap to page boundaries using
+  /// [PageScrollPhysics] prior to being used.
+  ///
+  /// Defaults to matching platform conventions.
+  final ScrollPhysics? physics;
+
+  /// Set to false to disable page snapping, useful for custom scroll behavior.
+  final bool pageSnapping;
+
+  /// Called whenever the page in the center of the viewport changes.
+  final ValueChanged<int>? onPageChanged;
+
+  /// A delegate that provides the children for the [PageView].
+  ///
+  /// The [PageView.custom] constructor lets you specify this delegate
+  /// explicitly. The [PageView] and [PageView.builder] constructors create a
+  /// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder],
+  /// respectively.
+  final SliverChildDelegate childrenDelegate;
+
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge].
+  final Clip clipBehavior;
+
+  @override
+  _PageViewState createState() => _PageViewState();
+}
+
+class _PageViewState extends State<PageView> {
+  int _lastReportedPage = 0;
+
+  @override
+  void initState() {
+    super.initState();
+    _lastReportedPage = widget.controller.initialPage;
+  }
+
+  AxisDirection _getDirection(BuildContext context) {
+    switch (widget.scrollDirection) {
+      case Axis.horizontal:
+        assert(debugCheckHasDirectionality(context));
+        final TextDirection textDirection = Directionality.of(context);
+        final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
+        return widget.reverse ? flipAxisDirection(axisDirection) : axisDirection;
+      case Axis.vertical:
+        return widget.reverse ? AxisDirection.up : AxisDirection.down;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final AxisDirection axisDirection = _getDirection(context);
+    final ScrollPhysics physics = _ForceImplicitScrollPhysics(
+      allowImplicitScrolling: widget.allowImplicitScrolling,
+    ).applyTo(widget.pageSnapping
+        ? _kPagePhysics.applyTo(widget.physics)
+        : widget.physics);
+
+    return NotificationListener<ScrollNotification>(
+      onNotification: (ScrollNotification notification) {
+        if (notification.depth == 0 && widget.onPageChanged != null && notification is ScrollUpdateNotification) {
+          final PageMetrics metrics = notification.metrics as PageMetrics;
+          final int currentPage = metrics.page!.round();
+          if (currentPage != _lastReportedPage) {
+            _lastReportedPage = currentPage;
+            widget.onPageChanged!(currentPage);
+          }
+        }
+        return false;
+      },
+      child: Scrollable(
+        dragStartBehavior: widget.dragStartBehavior,
+        axisDirection: axisDirection,
+        controller: widget.controller,
+        physics: physics,
+        restorationId: widget.restorationId,
+        viewportBuilder: (BuildContext context, ViewportOffset position) {
+          return Viewport(
+            // TODO(dnfield): we should provide a way to set cacheExtent
+            // independent of implicit scrolling:
+            // https://github.com/flutter/flutter/issues/45632
+            cacheExtent: widget.allowImplicitScrolling ? 1.0 : 0.0,
+            cacheExtentStyle: CacheExtentStyle.viewport,
+            axisDirection: axisDirection,
+            offset: position,
+            clipBehavior: widget.clipBehavior,
+            slivers: <Widget>[
+              SliverFillViewport(
+                viewportFraction: widget.controller.viewportFraction,
+                delegate: widget.childrenDelegate,
+              ),
+            ],
+          );
+        },
+      ),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(EnumProperty<Axis>('scrollDirection', widget.scrollDirection));
+    description.add(FlagProperty('reverse', value: widget.reverse, ifTrue: 'reversed'));
+    description.add(DiagnosticsProperty<PageController>('controller', widget.controller, showName: false));
+    description.add(DiagnosticsProperty<ScrollPhysics>('physics', widget.physics, showName: false));
+    description.add(FlagProperty('pageSnapping', value: widget.pageSnapping, ifFalse: 'snapping disabled'));
+    description.add(FlagProperty('allowImplicitScrolling', value: widget.allowImplicitScrolling, ifTrue: 'allow implicit scrolling'));
+  }
+}
diff --git a/lib/src/widgets/pages.dart b/lib/src/widgets/pages.dart
new file mode 100644
index 0000000..7b0ba2e
--- /dev/null
+++ b/lib/src/widgets/pages.dart
@@ -0,0 +1,118 @@
+// Copyright 2014 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.
+
+import 'basic.dart';
+import 'framework.dart';
+import 'navigator.dart';
+import 'routes.dart';
+
+/// A modal route that replaces the entire screen.
+abstract class PageRoute<T> extends ModalRoute<T> {
+  /// Creates a modal route that replaces the entire screen.
+  PageRoute({
+    RouteSettings? settings,
+    this.fullscreenDialog = false,
+  }) : super(settings: settings);
+
+  /// {@template flutter.widgets.PageRoute.fullscreenDialog}
+  /// Whether this page route is a full-screen dialog.
+  ///
+  /// In Material and Cupertino, being fullscreen has the effects of making
+  /// the app bars have a close button instead of a back button. On
+  /// iOS, dialogs transitions animate differently and are also not closeable
+  /// with the back swipe gesture.
+  /// {@endtemplate}
+  final bool fullscreenDialog;
+
+  @override
+  bool get opaque => true;
+
+  @override
+  bool get barrierDismissible => false;
+
+  @override
+  bool canTransitionTo(TransitionRoute<dynamic> nextRoute) => nextRoute is PageRoute;
+
+  @override
+  bool canTransitionFrom(TransitionRoute<dynamic> previousRoute) => previousRoute is PageRoute;
+}
+
+Widget _defaultTransitionsBuilder(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
+  return child;
+}
+
+/// A utility class for defining one-off page routes in terms of callbacks.
+///
+/// Callers must define the [pageBuilder] function which creates the route's
+/// primary contents. To add transitions define the [transitionsBuilder] function.
+class PageRouteBuilder<T> extends PageRoute<T> {
+  /// Creates a route that delegates to builder callbacks.
+  ///
+  /// The [pageBuilder], [transitionsBuilder], [opaque], [barrierDismissible],
+  /// [maintainState], and [fullscreenDialog] arguments must not be null.
+  PageRouteBuilder({
+    RouteSettings? settings,
+    required this.pageBuilder,
+    this.transitionsBuilder = _defaultTransitionsBuilder,
+    this.transitionDuration = const Duration(milliseconds: 300),
+    this.reverseTransitionDuration = const Duration(milliseconds: 300),
+    this.opaque = true,
+    this.barrierDismissible = false,
+    this.barrierColor,
+    this.barrierLabel,
+    this.maintainState = true,
+    bool fullscreenDialog = false,
+  }) : assert(pageBuilder != null),
+       assert(transitionsBuilder != null),
+       assert(opaque != null),
+       assert(barrierDismissible != null),
+       assert(maintainState != null),
+       assert(fullscreenDialog != null),
+       super(settings: settings, fullscreenDialog: fullscreenDialog);
+
+  /// {@template flutter.widgets.pageRouteBuilder.pageBuilder}
+  /// Used build the route's primary contents.
+  ///
+  /// See [ModalRoute.buildPage] for complete definition of the parameters.
+  /// {@endtemplate}
+  final RoutePageBuilder pageBuilder;
+
+  /// {@template flutter.widgets.pageRouteBuilder.transitionsBuilder}
+  /// Used to build the route's transitions.
+  ///
+  /// See [ModalRoute.buildTransitions] for complete definition of the parameters.
+  /// {@endtemplate}
+  final RouteTransitionsBuilder transitionsBuilder;
+
+  @override
+  final Duration transitionDuration;
+
+  @override
+  final Duration reverseTransitionDuration;
+
+  @override
+  final bool opaque;
+
+  @override
+  final bool barrierDismissible;
+
+  @override
+  final Color? barrierColor;
+
+  @override
+  final String? barrierLabel;
+
+  @override
+  final bool maintainState;
+
+  @override
+  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
+    return pageBuilder(context, animation, secondaryAnimation);
+  }
+
+  @override
+  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
+    return transitionsBuilder(context, animation, secondaryAnimation, child);
+  }
+}
diff --git a/lib/src/widgets/performance_overlay.dart b/lib/src/widgets/performance_overlay.dart
new file mode 100644
index 0000000..8caa1ea
--- /dev/null
+++ b/lib/src/widgets/performance_overlay.dart
@@ -0,0 +1,121 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+
+import 'framework.dart';
+
+/// Displays performance statistics.
+///
+/// The overlay shows two time series. The first shows how much time was
+/// required on this thread to produce each frame. The second shows how much
+/// time was required on the raster thread (formerly known as the GPU thread)
+/// to produce each frame. Ideally, both these values would be less than
+/// the total frame budget for the hardware on which the app is running.
+/// For example, if the hardware has a screen that updates at 60 Hz, each
+/// thread should ideally spend less than 16ms producing each frame.
+/// This ideal condition is indicated by a green vertical line for each thread.
+/// Otherwise, the performance overlay shows a red vertical line.
+///
+/// The simplest way to show the performance overlay is to set
+/// [MaterialApp.showPerformanceOverlay] or [WidgetsApp.showPerformanceOverlay]
+/// to true.
+class PerformanceOverlay extends LeafRenderObjectWidget {
+  // TODO(abarth): We should have a page on the web site with a screenshot and
+  // an explanation of all the various readouts.
+
+  /// Create a performance overlay that only displays specific statistics. The
+  /// mask is created by shifting 1 by the index of the specific
+  /// [PerformanceOverlayOption] to enable.
+  const PerformanceOverlay({
+    Key? key,
+    this.optionsMask = 0,
+    this.rasterizerThreshold = 0,
+    this.checkerboardRasterCacheImages = false,
+    this.checkerboardOffscreenLayers = false,
+  }) : super(key: key);
+
+  /// Create a performance overlay that displays all available statistics.
+  PerformanceOverlay.allEnabled({
+    Key? key,
+    this.rasterizerThreshold = 0,
+    this.checkerboardRasterCacheImages = false,
+    this.checkerboardOffscreenLayers = false,
+  }) : optionsMask =
+        1 << PerformanceOverlayOption.displayRasterizerStatistics.index |
+        1 << PerformanceOverlayOption.visualizeRasterizerStatistics.index |
+        1 << PerformanceOverlayOption.displayEngineStatistics.index |
+        1 << PerformanceOverlayOption.visualizeEngineStatistics.index,
+      super(key: key);
+
+  /// The mask is created by shifting 1 by the index of the specific
+  /// [PerformanceOverlayOption] to enable.
+  final int optionsMask;
+
+  /// The rasterizer threshold is an integer specifying the number of frame
+  /// intervals that the rasterizer must miss before it decides that the frame
+  /// is suitable for capturing an SkPicture trace for further analysis.
+  ///
+  /// For example, if you want a trace of all pictures that could not be
+  /// rendered by the rasterizer within the frame boundary (and hence caused
+  /// jank), specify 1. Specifying 2 will trace all pictures that took more
+  /// more than 2 frame intervals to render. Adjust this value to only capture
+  /// the particularly expensive pictures while skipping the others. Specifying
+  /// 0 disables all capture.
+  ///
+  /// Captured traces are placed on your device in the application documents
+  /// directory in this form "trace_<collection_time>.skp". These can
+  /// be viewed in the Skia debugger.
+  ///
+  /// Notes:
+  /// The rasterizer only takes into account the time it took to render
+  /// the already constructed picture. This include the Skia calls (which is
+  /// also why an SkPicture trace is generated) but not any of the time spent in
+  /// dart to construct that picture. To profile that part of your code, use
+  /// the instrumentation available in observatory.
+  ///
+  /// To decide what threshold interval to use, count the number of horizontal
+  /// lines displayed in the performance overlay for the rasterizer (not the
+  /// engine). That should give an idea of how often frames are skipped (and by
+  /// how many frame intervals).
+  final int rasterizerThreshold;
+
+  /// Whether the raster cache should checkerboard cached entries.
+  ///
+  /// The compositor can sometimes decide to cache certain portions of the
+  /// widget hierarchy. Such portions typically don't change often from frame to
+  /// frame and are expensive to render. This can speed up overall rendering. However,
+  /// there is certain upfront cost to constructing these cache entries. And, if
+  /// the cache entries are not used very often, this cost may not be worth the
+  /// speedup in rendering of subsequent frames. If the developer wants to be certain
+  /// that populating the raster cache is not causing stutters, this option can be
+  /// set. Depending on the observations made, hints can be provided to the compositor
+  /// that aid it in making better decisions about caching.
+  final bool checkerboardRasterCacheImages;
+
+  /// Whether the compositor should checkerboard layers that are rendered to offscreen
+  /// bitmaps. This can be useful for debugging rendering performance.
+  ///
+  /// Render target switches are caused by using opacity layers (via a [FadeTransition] or
+  /// [Opacity] widget), clips, shader mask layers, etc. Selecting a new render target
+  /// and merging it with the rest of the scene has a performance cost. This can sometimes
+  /// be avoided by using equivalent widgets that do not require these layers (for example,
+  /// replacing an [Opacity] widget with an [widgets.Image] using a [BlendMode]).
+  final bool checkerboardOffscreenLayers;
+
+  @override
+  RenderPerformanceOverlay createRenderObject(BuildContext context) => RenderPerformanceOverlay(
+    optionsMask: optionsMask,
+    rasterizerThreshold: rasterizerThreshold,
+    checkerboardRasterCacheImages: checkerboardRasterCacheImages,
+    checkerboardOffscreenLayers: checkerboardOffscreenLayers,
+  );
+
+  @override
+  void updateRenderObject(BuildContext context, RenderPerformanceOverlay renderObject) {
+    renderObject
+      ..optionsMask = optionsMask
+      ..rasterizerThreshold = rasterizerThreshold;
+  }
+}
diff --git a/lib/src/widgets/placeholder.dart b/lib/src/widgets/placeholder.dart
new file mode 100644
index 0000000..062f962
--- /dev/null
+++ b/lib/src/widgets/placeholder.dart
@@ -0,0 +1,100 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+
+class _PlaceholderPainter extends CustomPainter {
+  const _PlaceholderPainter({
+    required this.color,
+    required this.strokeWidth,
+  });
+
+  final Color color;
+  final double strokeWidth;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final Paint paint = Paint()
+      ..color = color
+      ..style = PaintingStyle.stroke
+      ..strokeWidth = strokeWidth;
+    final Rect rect = Offset.zero & size;
+    final Path path = Path()
+      ..addRect(rect)
+      ..addPolygon(<Offset>[rect.topRight, rect.bottomLeft], false)
+      ..addPolygon(<Offset>[rect.topLeft, rect.bottomRight], false);
+    canvas.drawPath(path, paint);
+  }
+
+  @override
+  bool shouldRepaint(_PlaceholderPainter oldPainter) {
+    return oldPainter.color != color
+        || oldPainter.strokeWidth != strokeWidth;
+  }
+
+  @override
+  bool hitTest(Offset position) => false;
+}
+
+/// A widget that draws a box that represents where other widgets will one day
+/// be added.
+///
+/// This widget is useful during development to indicate that the interface is
+/// not yet complete.
+///
+/// By default, the placeholder is sized to fit its container. If the
+/// placeholder is in an unbounded space, it will size itself according to the
+/// given [fallbackWidth] and [fallbackHeight].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=LPe56fezmoo}
+class Placeholder extends StatelessWidget {
+  /// Creates a widget which draws a box.
+  const Placeholder({
+    Key? key,
+    this.color = const Color(0xFF455A64), // Blue Grey 700
+    this.strokeWidth = 2.0,
+    this.fallbackWidth = 400.0,
+    this.fallbackHeight = 400.0,
+  }) : super(key: key);
+
+  /// The color to draw the placeholder box.
+  final Color color;
+
+  /// The width of the lines in the placeholder box.
+  final double strokeWidth;
+
+  /// The width to use when the placeholder is in a situation with an unbounded
+  /// width.
+  ///
+  /// See also:
+  ///
+  ///  * [fallbackHeight], the same but vertically.
+  final double fallbackWidth;
+
+  /// The height to use when the placeholder is in a situation with an unbounded
+  /// height.
+  ///
+  /// See also:
+  ///
+  ///  * [fallbackWidth], the same but horizontally.
+  final double fallbackHeight;
+
+  @override
+  Widget build(BuildContext context) {
+    return LimitedBox(
+      maxWidth: fallbackWidth,
+      maxHeight: fallbackHeight,
+      child: CustomPaint(
+        size: Size.infinite,
+        foregroundPainter: _PlaceholderPainter(
+          color: color,
+          strokeWidth: strokeWidth,
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/src/widgets/platform_view.dart b/lib/src/widgets/platform_view.dart
new file mode 100644
index 0000000..b9ab916
--- /dev/null
+++ b/lib/src/widgets/platform_view.dart
@@ -0,0 +1,1051 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+
+import 'basic.dart';
+import 'debug.dart';
+import 'focus_manager.dart';
+import 'focus_scope.dart';
+import 'framework.dart';
+
+/// Embeds an Android view in the Widget hierarchy.
+///
+/// Requires Android API level 20 or greater.
+///
+/// Embedding Android views is an expensive operation and should be avoided when a Flutter
+/// equivalent is possible.
+///
+/// The embedded Android view is painted just like any other Flutter widget and transformations
+/// apply to it as well.
+///
+/// {@template flutter.widgets.AndroidView.layout}
+/// The widget fills all available space, the parent of this object must provide bounded layout
+/// constraints.
+/// {@endtemplate}
+///
+/// {@template flutter.widgets.AndroidView.gestures}
+/// The widget participates in Flutter's gesture arenas, and dispatches touch events to the
+/// platform view iff it won the arena. Specific gestures that should be dispatched to the platform
+/// view can be specified in the `gestureRecognizers` constructor parameter. If
+/// the set of gesture recognizers is empty, a gesture will be dispatched to the platform
+/// view iff it was not claimed by any other gesture recognizer.
+/// {@endtemplate}
+///
+/// The Android view object is created using a [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html).
+/// Plugins can register platform view factories with [PlatformViewRegistry#registerViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewRegistry.html#registerViewFactory-java.lang.String-io.flutter.plugin.platform.PlatformViewFactory-).
+///
+/// Registration is typically done in the plugin's registerWith method, e.g:
+///
+/// ```java
+///   public static void registerWith(Registrar registrar) {
+///     registrar.platformViewRegistry().registerViewFactory("webview", WebViewFactory(registrar.messenger()));
+///   }
+/// ```
+///
+/// {@template flutter.widgets.AndroidView.lifetime}
+/// The platform view's lifetime is the same as the lifetime of the [State] object for this widget.
+/// When the [State] is disposed the platform view (and auxiliary resources) are lazily
+/// released (some resources are immediately released and some by platform garbage collector).
+/// A stateful widget's state is disposed when the widget is removed from the tree or when it is
+/// moved within the tree. If the stateful widget has a key and it's only moved relative to its siblings,
+/// or it has a [GlobalKey] and it's moved within the tree, it will not be disposed.
+/// {@endtemplate}
+class AndroidView extends StatefulWidget {
+  /// Creates a widget that embeds an Android view.
+  ///
+  /// {@template flutter.widgets.AndroidView.constructorArgs}
+  /// The `viewType` and `hitTestBehavior` parameters must not be null.
+  /// If `creationParams` is not null then `creationParamsCodec` must not be null.
+  /// {@endtemplate}
+  const AndroidView({
+    Key? key,
+    required this.viewType,
+    this.onPlatformViewCreated,
+    this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
+    this.layoutDirection,
+    this.gestureRecognizers,
+    this.creationParams,
+    this.creationParamsCodec,
+    this.clipBehavior = Clip.hardEdge,
+  }) : assert(viewType != null),
+       assert(hitTestBehavior != null),
+       assert(creationParams == null || creationParamsCodec != null),
+       assert(clipBehavior != null),
+       super(key: key);
+
+  /// The unique identifier for Android view type to be embedded by this widget.
+  ///
+  /// A [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html)
+  /// for this type must have been registered.
+  ///
+  /// See also:
+  ///
+  ///  * [AndroidView] for an example of registering a platform view factory.
+  final String viewType;
+
+  /// {@template flutter.widgets.AndroidView.onPlatformViewCreated}
+  /// Callback to invoke after the platform view has been created.
+  ///
+  /// May be null.
+  /// {@endtemplate}
+  final PlatformViewCreatedCallback? onPlatformViewCreated;
+
+  /// {@template flutter.widgets.AndroidView.hitTestBehavior}
+  /// How this widget should behave during hit testing.
+  ///
+  /// This defaults to [PlatformViewHitTestBehavior.opaque].
+  /// {@endtemplate}
+  final PlatformViewHitTestBehavior hitTestBehavior;
+
+  /// {@template flutter.widgets.AndroidView.layoutDirection}
+  /// The text direction to use for the embedded view.
+  ///
+  /// If this is null, the ambient [Directionality] is used instead.
+  /// {@endtemplate}
+  final TextDirection? layoutDirection;
+
+  /// Which gestures should be forwarded to the Android view.
+  ///
+  /// {@template flutter.widgets.AndroidView.gestureRecognizers.descHead}
+  /// The gesture recognizers built by factories in this set participate in the gesture arena for
+  /// each pointer that was put down on the widget. If any of these recognizers win the
+  /// gesture arena, the entire pointer event sequence starting from the pointer down event
+  /// will be dispatched to the platform view.
+  ///
+  /// When null, an empty set of gesture recognizer factories is used, in which case a pointer event sequence
+  /// will only be dispatched to the platform view if no other member of the arena claimed it.
+  /// {@endtemplate}
+  ///
+  /// For example, with the following setup vertical drags will not be dispatched to the Android
+  /// view as the vertical drag gesture is claimed by the parent [GestureDetector].
+  ///
+  /// ```dart
+  /// GestureDetector(
+  ///   onVerticalDragStart: (DragStartDetails d) {},
+  ///   child: AndroidView(
+  ///     viewType: 'webview',
+  ///   ),
+  /// )
+  /// ```
+  ///
+  /// To get the [AndroidView] to claim the vertical drag gestures we can pass a vertical drag
+  /// gesture recognizer factory in [gestureRecognizers] e.g:
+  ///
+  /// ```dart
+  /// GestureDetector(
+  ///   onVerticalDragStart: (DragStartDetails details) {},
+  ///   child: SizedBox(
+  ///     width: 200.0,
+  ///     height: 100.0,
+  ///     child: AndroidView(
+  ///       viewType: 'webview',
+  ///       gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>[
+  ///         new Factory<OneSequenceGestureRecognizer>(
+  ///           () => new EagerGestureRecognizer(),
+  ///         ),
+  ///       ].toSet(),
+  ///     ),
+  ///   ),
+  /// )
+  /// ```
+  ///
+  /// {@template flutter.widgets.AndroidView.gestureRecognizers.descFoot}
+  /// A platform view can be configured to consume all pointers that were put down in its bounds
+  /// by passing a factory for an [EagerGestureRecognizer] in [gestureRecognizers].
+  /// [EagerGestureRecognizer] is a special gesture recognizer that immediately claims the gesture
+  /// after a pointer down event.
+  ///
+  /// The `gestureRecognizers` property must not contain more than one factory with the same [Factory.type].
+  ///
+  /// Changing `gestureRecognizers` results in rejection of any active gesture arenas (if the
+  /// platform view is actively participating in an arena).
+  /// {@endtemplate}
+  // We use OneSequenceGestureRecognizers as they support gesture arena teams.
+  // TODO(amirh): get a list of GestureRecognizers here.
+  // https://github.com/flutter/flutter/issues/20953
+  final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
+
+  /// Passed as the args argument of [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-android.content.Context-int-java.lang.Object-)
+  ///
+  /// This can be used by plugins to pass constructor parameters to the embedded Android view.
+  final dynamic creationParams;
+
+  /// The codec used to encode `creationParams` before sending it to the
+  /// platform side. It should match the codec passed to the constructor of [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#PlatformViewFactory-io.flutter.plugin.common.MessageCodec-).
+  ///
+  /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec].
+  ///
+  /// This must not be null if [creationParams] is not null.
+  final MessageCodec<dynamic>? creationParamsCodec;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge], and must not be null.
+  final Clip clipBehavior;
+
+  @override
+  State<AndroidView> createState() => _AndroidViewState();
+}
+
+// TODO(amirh): describe the embedding mechanism.
+// TODO(ychris): remove the documentation for conic path not supported once https://github.com/flutter/flutter/issues/35062 is resolved.
+/// Embeds an iOS view in the Widget hierarchy.
+///
+/// {@macro flutter.rendering.RenderUiKitView}
+///
+/// Embedding iOS views is an expensive operation and should be avoided when a Flutter
+/// equivalent is possible.
+///
+/// {@macro flutter.widgets.AndroidView.layout}
+///
+/// {@macro flutter.widgets.AndroidView.gestures}
+///
+/// {@macro flutter.widgets.AndroidView.lifetime}
+///
+/// Construction of UIViews is done asynchronously, before the UIView is ready this widget paints
+/// nothing while maintaining the same layout constraints.
+///
+/// If a conic path clipping is applied to a UIKitView,
+/// a quad path is used to approximate the clip due to limitation of Quartz.
+class UiKitView extends StatefulWidget {
+  /// Creates a widget that embeds an iOS view.
+  ///
+  /// {@macro flutter.widgets.AndroidView.constructorArgs}
+  const UiKitView({
+    Key? key,
+    required this.viewType,
+    this.onPlatformViewCreated,
+    this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
+    this.layoutDirection,
+    this.creationParams,
+    this.creationParamsCodec,
+    this.gestureRecognizers,
+  }) : assert(viewType != null),
+       assert(hitTestBehavior != null),
+       assert(creationParams == null || creationParamsCodec != null),
+       super(key: key);
+
+  // TODO(amirh): reference the iOS API doc once available.
+  /// The unique identifier for iOS view type to be embedded by this widget.
+  ///
+  /// A PlatformViewFactory for this type must have been registered.
+  final String viewType;
+
+  /// {@macro flutter.widgets.AndroidView.onPlatformViewCreated}
+  final PlatformViewCreatedCallback? onPlatformViewCreated;
+
+  /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
+  final PlatformViewHitTestBehavior hitTestBehavior;
+
+  /// {@macro flutter.widgets.AndroidView.layoutDirection}
+  final TextDirection? layoutDirection;
+
+  /// Passed as the `arguments` argument of [-\[FlutterPlatformViewFactory createWithFrame:viewIdentifier:arguments:\]](/objcdoc/Protocols/FlutterPlatformViewFactory.html#/c:objc(pl)FlutterPlatformViewFactory(im)createWithFrame:viewIdentifier:arguments:)
+  ///
+  /// This can be used by plugins to pass constructor parameters to the embedded iOS view.
+  final dynamic creationParams;
+
+  /// The codec used to encode `creationParams` before sending it to the
+  /// platform side. It should match the codec returned by [-\[FlutterPlatformViewFactory createArgsCodec:\]](/objcdoc/Protocols/FlutterPlatformViewFactory.html#/c:objc(pl)FlutterPlatformViewFactory(im)createArgsCodec)
+  ///
+  /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec].
+  ///
+  /// This must not be null if [creationParams] is not null.
+  final MessageCodec<dynamic>? creationParamsCodec;
+
+  /// Which gestures should be forwarded to the UIKit view.
+  ///
+  /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descHead}
+  ///
+  /// For example, with the following setup vertical drags will not be dispatched to the UIKit
+  /// view as the vertical drag gesture is claimed by the parent [GestureDetector].
+  ///
+  /// ```dart
+  /// GestureDetector(
+  ///   onVerticalDragStart: (DragStartDetails details) {},
+  ///   child: UiKitView(
+  ///     viewType: 'webview',
+  ///   ),
+  /// )
+  /// ```
+  ///
+  /// To get the [UiKitView] to claim the vertical drag gestures we can pass a vertical drag
+  /// gesture recognizer factory in [gestureRecognizers] e.g:
+  ///
+  /// ```dart
+  /// GestureDetector(
+  ///   onVerticalDragStart: (DragStartDetails details) {},
+  ///   child: SizedBox(
+  ///     width: 200.0,
+  ///     height: 100.0,
+  ///     child: UiKitView(
+  ///       viewType: 'webview',
+  ///       gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>[
+  ///         new Factory<OneSequenceGestureRecognizer>(
+  ///           () => new EagerGestureRecognizer(),
+  ///         ),
+  ///       ].toSet(),
+  ///     ),
+  ///   ),
+  /// )
+  /// ```
+  ///
+  /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descFoot}
+  // We use OneSequenceGestureRecognizers as they support gesture arena teams.
+  // TODO(amirh): get a list of GestureRecognizers here.
+  // https://github.com/flutter/flutter/issues/20953
+  final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
+
+  @override
+  State<UiKitView> createState() => _UiKitViewState();
+}
+
+/// Embeds an HTML element in the Widget hierarchy in Flutter Web.
+///
+/// *NOTE*: This only works in Flutter Web. To embed web content on other
+/// platforms, consider using the `flutter_webview` plugin.
+///
+/// Embedding HTML is an expensive operation and should be avoided when a
+/// Flutter equivalent is possible.
+///
+/// The embedded HTML is painted just like any other Flutter widget and
+/// transformations apply to it as well. This widget should only be used in
+/// Flutter Web.
+///
+/// {@macro flutter.widgets.AndroidView.layout}
+///
+/// Due to security restrictions with cross-origin `<iframe>` elements, Flutter
+/// cannot dispatch pointer events to an HTML view. If an `<iframe>` is the
+/// target of an event, the window containing the `<iframe>` is not notified
+/// of the event. In particular, this means that any pointer events which land
+/// on an `<iframe>` will not be seen by Flutter, and so the HTML view cannot
+/// participate in gesture detection with other widgets.
+///
+/// The way we enable accessibility on Flutter for web is to have a full-page
+/// button which waits for a double tap. Placing this full-page button in front
+/// of the scene would cause platform views not to receive pointer events. The
+/// tradeoff is that by placing the scene in front of the semantics placeholder
+/// will cause platform views to block pointer events from reaching the
+/// placeholder. This means that in order to enable accessibility, you must
+/// double tap the app *outside of a platform view*. As a consequence, a
+/// full-screen platform view will make it impossible to enable accessibility.
+/// Make sure that your HTML views are sized no larger than necessary, or you
+/// may cause difficulty for users trying to enable accessibility.
+///
+/// {@macro flutter.widgets.AndroidView.lifetime}
+class HtmlElementView extends StatelessWidget {
+  /// Creates a platform view for Flutter Web.
+  ///
+  /// `viewType` identifies the type of platform view to create.
+  const HtmlElementView({
+    Key? key,
+    required this.viewType,
+  }) : assert(viewType != null),
+       assert(kIsWeb, 'HtmlElementView is only available on Flutter Web.'),
+       super(key: key);
+
+  /// The unique identifier for the HTML view type to be embedded by this widget.
+  ///
+  /// A PlatformViewFactory for this type must have been registered.
+  final String viewType;
+
+  @override
+  Widget build(BuildContext context) {
+    return PlatformViewLink(
+      viewType: viewType,
+      onCreatePlatformView: _createHtmlElementView,
+      surfaceFactory: (BuildContext context, PlatformViewController controller) {
+        return PlatformViewSurface(
+          controller: controller,
+          gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
+          hitTestBehavior: PlatformViewHitTestBehavior.opaque,
+        );
+      },
+    );
+  }
+
+  /// Creates the controller and kicks off its initialization.
+  _HtmlElementViewController _createHtmlElementView(PlatformViewCreationParams params) {
+    final _HtmlElementViewController controller = _HtmlElementViewController(params.id, viewType);
+    controller._initialize().then((_) { params.onPlatformViewCreated(params.id); });
+    return controller;
+  }
+}
+
+class _HtmlElementViewController extends PlatformViewController {
+  _HtmlElementViewController(
+    this.viewId,
+    this.viewType,
+  );
+
+  @override
+  final int viewId;
+
+  /// The unique identifier for the HTML view type to be embedded by this widget.
+  ///
+  /// A PlatformViewFactory for this type must have been registered.
+  final String viewType;
+
+  bool _initialized = false;
+
+  Future<void> _initialize() async {
+    final Map<String, dynamic> args = <String, dynamic>{
+      'id': viewId,
+      'viewType': viewType,
+    };
+    await SystemChannels.platform_views.invokeMethod<void>('create', args);
+    _initialized = true;
+  }
+
+  @override
+  Future<void> clearFocus() async {
+    // Currently this does nothing on Flutter Web.
+    // TODO(het): Implement this. See https://github.com/flutter/flutter/issues/39496
+  }
+
+  @override
+  Future<void> dispatchPointerEvent(PointerEvent event) async {
+    // We do not dispatch pointer events to HTML views because they may contain
+    // cross-origin iframes, which only accept user-generated events.
+  }
+
+  @override
+  Future<void> dispose() async {
+    if (_initialized) {
+      await SystemChannels.platform_views.invokeMethod<void>('dispose', viewId);
+    }
+  }
+}
+
+class _AndroidViewState extends State<AndroidView> {
+  int? _id;
+  late AndroidViewController _controller;
+  TextDirection? _layoutDirection;
+  bool _initialized = false;
+  FocusNode? _focusNode;
+
+  static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
+    <Factory<OneSequenceGestureRecognizer>>{};
+
+  @override
+  Widget build(BuildContext context) {
+    return Focus(
+      focusNode: _focusNode,
+      onFocusChange: _onFocusChange,
+      child: _AndroidPlatformView(
+        controller: _controller,
+        hitTestBehavior: widget.hitTestBehavior,
+        gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
+        clipBehavior: widget.clipBehavior,
+      ),
+    );
+  }
+
+  void _initializeOnce() {
+    if (_initialized) {
+      return;
+    }
+    _initialized = true;
+    _createNewAndroidView();
+    _focusNode = FocusNode(debugLabel: 'AndroidView(id: $_id)');
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    final TextDirection newLayoutDirection = _findLayoutDirection();
+    final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
+    _layoutDirection = newLayoutDirection;
+
+    _initializeOnce();
+    if (didChangeLayoutDirection) {
+      // The native view will update asynchronously, in the meantime we don't want
+      // to block the framework. (so this is intentionally not awaiting).
+      _controller.setLayoutDirection(_layoutDirection!);
+    }
+  }
+
+  @override
+  void didUpdateWidget(AndroidView oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    final TextDirection newLayoutDirection = _findLayoutDirection();
+    final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
+    _layoutDirection = newLayoutDirection;
+
+    if (widget.viewType != oldWidget.viewType) {
+      _controller.dispose();
+      _createNewAndroidView();
+      return;
+    }
+
+    if (didChangeLayoutDirection) {
+      _controller.setLayoutDirection(_layoutDirection!);
+    }
+  }
+
+  TextDirection _findLayoutDirection() {
+    assert(widget.layoutDirection != null || debugCheckHasDirectionality(context));
+    return widget.layoutDirection ?? Directionality.of(context);
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  void _createNewAndroidView() {
+    _id = platformViewsRegistry.getNextPlatformViewId();
+    _controller = PlatformViewsService.initAndroidView(
+      id: _id!,
+      viewType: widget.viewType,
+      layoutDirection: _layoutDirection!,
+      creationParams: widget.creationParams,
+      creationParamsCodec: widget.creationParamsCodec,
+      onFocus: () {
+        _focusNode!.requestFocus();
+      },
+    );
+    if (widget.onPlatformViewCreated != null) {
+      _controller.addOnPlatformViewCreatedListener(widget.onPlatformViewCreated!);
+    }
+  }
+
+  void _onFocusChange(bool isFocused) {
+    if (!_controller.isCreated) {
+      return;
+    }
+    if (!isFocused) {
+      _controller.clearFocus().catchError((dynamic e) {
+        if (e is MissingPluginException) {
+          // We land the framework part of Android platform views keyboard
+          // support before the engine part. There will be a commit range where
+          // clearFocus isn't implemented in the engine. When that happens we
+          // just swallow the error here. Once the engine part is rolled to the
+          // framework I'll remove this.
+          // TODO(amirh): remove this once the engine's clearFocus is rolled.
+          return;
+        }
+      });
+      return;
+    }
+    SystemChannels.textInput.invokeMethod<void>(
+      'TextInput.setPlatformViewClient',
+      _id,
+    ).catchError((dynamic e) {
+      if (e is MissingPluginException) {
+        // We land the framework part of Android platform views keyboard
+        // support before the engine part. There will be a commit range where
+        // setPlatformViewClient isn't implemented in the engine. When that
+        // happens we just swallow the error here. Once the engine part is
+        // rolled to the framework I'll remove this.
+        // TODO(amirh): remove this once the engine's clearFocus is rolled.
+        return;
+      }
+    });
+  }
+}
+
+class _UiKitViewState extends State<UiKitView> {
+  UiKitViewController? _controller;
+  TextDirection? _layoutDirection;
+  bool _initialized = false;
+
+  static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
+    <Factory<OneSequenceGestureRecognizer>>{};
+
+  @override
+  Widget build(BuildContext context) {
+    if (_controller == null) {
+      return const SizedBox.expand();
+    }
+    return _UiKitPlatformView(
+      controller: _controller!,
+      hitTestBehavior: widget.hitTestBehavior,
+      gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
+    );
+  }
+
+  void _initializeOnce() {
+    if (_initialized) {
+      return;
+    }
+    _initialized = true;
+    _createNewUiKitView();
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    final TextDirection newLayoutDirection = _findLayoutDirection();
+    final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
+    _layoutDirection = newLayoutDirection;
+
+    _initializeOnce();
+    if (didChangeLayoutDirection) {
+      // The native view will update asynchronously, in the meantime we don't want
+      // to block the framework. (so this is intentionally not awaiting).
+      _controller?.setLayoutDirection(_layoutDirection!);
+    }
+  }
+
+  @override
+  void didUpdateWidget(UiKitView oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    final TextDirection newLayoutDirection = _findLayoutDirection();
+    final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
+    _layoutDirection = newLayoutDirection;
+
+    if (widget.viewType != oldWidget.viewType) {
+      _controller?.dispose();
+      _createNewUiKitView();
+      return;
+    }
+
+    if (didChangeLayoutDirection) {
+      _controller?.setLayoutDirection(_layoutDirection!);
+    }
+  }
+
+  TextDirection _findLayoutDirection() {
+    assert(widget.layoutDirection != null || debugCheckHasDirectionality(context));
+    return widget.layoutDirection ?? Directionality.of(context);
+  }
+
+  @override
+  void dispose() {
+    _controller?.dispose();
+    super.dispose();
+  }
+
+  Future<void> _createNewUiKitView() async {
+    final int id = platformViewsRegistry.getNextPlatformViewId();
+    final UiKitViewController controller = await PlatformViewsService.initUiKitView(
+      id: id,
+      viewType: widget.viewType,
+      layoutDirection: _layoutDirection!,
+      creationParams: widget.creationParams,
+      creationParamsCodec: widget.creationParamsCodec,
+    );
+    if (!mounted) {
+      controller.dispose();
+      return;
+    }
+    if (widget.onPlatformViewCreated != null) {
+      widget.onPlatformViewCreated!(id);
+    }
+    setState(() { _controller = controller; });
+  }
+}
+
+class _AndroidPlatformView extends LeafRenderObjectWidget {
+  const _AndroidPlatformView({
+    Key? key,
+    required this.controller,
+    required this.hitTestBehavior,
+    required this.gestureRecognizers,
+    this.clipBehavior = Clip.hardEdge,
+  }) : assert(controller != null),
+       assert(hitTestBehavior != null),
+       assert(gestureRecognizers != null),
+       assert(clipBehavior != null),
+       super(key: key);
+
+  final AndroidViewController controller;
+  final PlatformViewHitTestBehavior hitTestBehavior;
+  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
+  final Clip clipBehavior;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) =>
+      RenderAndroidView(
+        viewController: controller,
+        hitTestBehavior: hitTestBehavior,
+        gestureRecognizers: gestureRecognizers,
+        clipBehavior: clipBehavior,
+      );
+
+  @override
+  void updateRenderObject(BuildContext context, RenderAndroidView renderObject) {
+    renderObject.viewController = controller;
+    renderObject.hitTestBehavior = hitTestBehavior;
+    renderObject.updateGestureRecognizers(gestureRecognizers);
+    renderObject.clipBehavior = clipBehavior;
+  }
+}
+
+class _UiKitPlatformView extends LeafRenderObjectWidget {
+  const _UiKitPlatformView({
+    Key? key,
+    required this.controller,
+    required this.hitTestBehavior,
+    required this.gestureRecognizers,
+  }) : assert(controller != null),
+       assert(hitTestBehavior != null),
+       assert(gestureRecognizers != null),
+       super(key: key);
+
+  final UiKitViewController controller;
+  final PlatformViewHitTestBehavior hitTestBehavior;
+  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return RenderUiKitView(
+      viewController: controller,
+      hitTestBehavior: hitTestBehavior,
+      gestureRecognizers: gestureRecognizers,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderUiKitView renderObject) {
+    renderObject.viewController = controller;
+    renderObject.hitTestBehavior = hitTestBehavior;
+    renderObject.updateGestureRecognizers(gestureRecognizers);
+  }
+}
+
+/// The parameters used to create a [PlatformViewController].
+///
+/// See also:
+///
+///  * [CreatePlatformViewCallback] which uses this object to create a [PlatformViewController].
+class PlatformViewCreationParams {
+
+  const PlatformViewCreationParams._({
+    required this.id,
+    required this.viewType,
+    required this.onPlatformViewCreated,
+    required this.onFocusChanged,
+  }) : assert(id != null),
+       assert(onPlatformViewCreated != null);
+
+  /// The unique identifier for the new platform view.
+  ///
+  /// [PlatformViewController.viewId] should match this id.
+  final int id;
+
+  /// The unique identifier for the type of platform view to be embedded.
+  ///
+  /// This viewType is used to tell the platform which type of view to
+  /// associate with the [id].
+  final String viewType;
+
+  /// Callback invoked after the platform view has been created.
+  final PlatformViewCreatedCallback onPlatformViewCreated;
+
+  /// Callback invoked when the platform view's focus is changed on the platform side.
+  ///
+  /// The value is true when the platform view gains focus and false when it loses focus.
+  final ValueChanged<bool> onFocusChanged;
+}
+
+/// A factory for a surface presenting a platform view as part of the widget hierarchy.
+///
+/// The returned widget should present the platform view associated with `controller`.
+///
+/// See also:
+///
+///  * [PlatformViewSurface], a common widget for presenting platform views.
+typedef PlatformViewSurfaceFactory = Widget Function(BuildContext context, PlatformViewController controller);
+
+/// Constructs a [PlatformViewController].
+///
+/// The [PlatformViewController.viewId] field of the created controller must match the value of the
+/// params [PlatformViewCreationParams.id] field.
+///
+/// See also:
+///
+///  * [PlatformViewLink], which links a platform view with the Flutter framework.
+typedef CreatePlatformViewCallback = PlatformViewController Function(PlatformViewCreationParams params);
+
+/// Links a platform view with the Flutter framework.
+///
+/// Provides common functionality for embedding a platform view (e.g an android.view.View on Android)
+/// with the Flutter framework.
+///
+/// {@macro flutter.widgets.AndroidView.lifetime}
+///
+/// To implement a new platform view widget, return this widget in the `build` method.
+/// For example:
+/// ```dart
+/// class FooPlatformView extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return PlatformViewLink(
+///       viewType: 'webview',
+///       onCreatePlatformView: createFooWebView,
+///       surfaceFactory: (BuildContext context, PlatformViewController controller) {
+///        return PlatformViewSurface(
+///            gestureRecognizers: gestureRecognizers,
+///            controller: controller,
+///            hitTestBehavior: PlatformViewHitTestBehavior.opaque,
+///        );
+///       },
+///    );
+///   }
+/// }
+/// ```
+///
+/// The `surfaceFactory` and the `onCreatePlatformView` are only called when the
+/// state of this widget is initialized, or when the `viewType` changes.
+class PlatformViewLink extends StatefulWidget {
+
+  /// Construct a [PlatformViewLink] widget.
+  ///
+  /// The `surfaceFactory` and the `onCreatePlatformView` must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [PlatformViewSurface] for details on the widget returned by `surfaceFactory`.
+  ///  * [PlatformViewCreationParams] for how each parameter can be used when implementing `createPlatformView`.
+  const PlatformViewLink({
+    Key? key,
+    required PlatformViewSurfaceFactory surfaceFactory,
+    required CreatePlatformViewCallback onCreatePlatformView,
+    required this.viewType,
+    }) : assert(surfaceFactory != null),
+         assert(onCreatePlatformView != null),
+         assert(viewType != null),
+         _surfaceFactory = surfaceFactory,
+         _onCreatePlatformView = onCreatePlatformView,
+         super(key: key);
+
+
+  final PlatformViewSurfaceFactory _surfaceFactory;
+  final CreatePlatformViewCallback _onCreatePlatformView;
+
+  /// The unique identifier for the view type to be embedded.
+  ///
+  /// Typically, this viewType has already been registered on the platform side.
+  final String viewType;
+
+  @override
+  State<StatefulWidget> createState() => _PlatformViewLinkState();
+}
+
+class _PlatformViewLinkState extends State<PlatformViewLink> {
+  int? _id;
+  PlatformViewController? _controller;
+  bool _platformViewCreated = false;
+  Widget? _surface;
+  FocusNode? _focusNode;
+
+  @override
+  Widget build(BuildContext context) {
+    if (!_platformViewCreated) {
+      return const SizedBox.expand();
+    }
+    _surface ??= widget._surfaceFactory(context, _controller!);
+    return Focus(
+      focusNode: _focusNode,
+      onFocusChange: _handleFrameworkFocusChanged,
+      child: _surface!,
+    );
+  }
+
+  @override
+  void initState() {
+    _focusNode = FocusNode(debugLabel: 'PlatformView(id: $_id)',);
+    _initialize();
+    super.initState();
+  }
+
+  @override
+  void didUpdateWidget(PlatformViewLink oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    if (widget.viewType != oldWidget.viewType) {
+      _controller?.dispose();
+      // The _surface has to be recreated as its controller is disposed.
+      // Setting _surface to null will trigger its creation in build().
+      _surface = null;
+
+      // We are about to create a new platform view.
+      _platformViewCreated = false;
+      _initialize();
+    }
+  }
+
+  void _initialize() {
+    _id = platformViewsRegistry.getNextPlatformViewId();
+    _controller = widget._onCreatePlatformView(
+      PlatformViewCreationParams._(
+        id: _id!,
+        viewType: widget.viewType,
+        onPlatformViewCreated: _onPlatformViewCreated,
+        onFocusChanged: _handlePlatformFocusChanged,
+      ),
+    );
+  }
+
+  void _onPlatformViewCreated(int id) {
+    setState(() { _platformViewCreated = true; });
+  }
+
+  void _handleFrameworkFocusChanged(bool isFocused) {
+    if (!isFocused) {
+      _controller?.clearFocus();
+    }
+  }
+
+  void _handlePlatformFocusChanged(bool isFocused){
+    if (isFocused) {
+      _focusNode!.requestFocus();
+    }
+  }
+
+  @override
+  void dispose() {
+    _controller?.dispose();
+    _controller = null;
+    super.dispose();
+  }
+}
+
+/// Integrates a platform view with Flutter's compositor, touch, and semantics subsystems.
+///
+/// The compositor integration is done by adding a [PlatformViewLayer] to the layer tree. [PlatformViewSurface]
+/// isn't supported on all platforms (e.g on Android platform views can be composited by using a [TextureLayer] or
+/// [AndroidViewSurface]).
+/// Custom Flutter embedders can support [PlatformViewLayer]s by implementing a SystemCompositor.
+///
+/// The widget fills all available space, the parent of this object must provide bounded layout
+/// constraints.
+///
+/// If the associated platform view is not created the [PlatformViewSurface] does not paint any contents.
+///
+/// See also:
+///
+///  * [AndroidView] which embeds an Android platform view in the widget hierarchy using a [TextureLayer].
+///  * [UiKitView] which embeds an iOS platform view in the widget hierarchy.
+// TODO(amirh): Link to the embedder's system compositor documentation once available.
+class PlatformViewSurface extends LeafRenderObjectWidget {
+
+  /// Construct a `PlatformViewSurface`.
+  ///
+  /// The [controller] must not be null.
+  const PlatformViewSurface({
+    Key? key,
+    required this.controller,
+    required this.hitTestBehavior,
+    required this.gestureRecognizers,
+  }) : assert(controller != null),
+       assert(hitTestBehavior != null),
+       assert(gestureRecognizers != null),
+       super(key: key);
+
+  /// The controller for the platform view integrated by this [PlatformViewSurface].
+  ///
+  /// [PlatformViewController] is used for dispatching touch events to the platform view.
+  /// [PlatformViewController.viewId] identifies the platform view whose contents are painted by this widget.
+  final PlatformViewController controller;
+
+  /// Which gestures should be forwarded to the PlatformView.
+  ///
+  /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descHead}
+  ///
+  /// For example, with the following setup vertical drags will not be dispatched to the platform view
+  /// as the vertical drag gesture is claimed by the parent [GestureDetector].
+  ///
+  /// ```dart
+  /// GestureDetector(
+  ///   onVerticalDragStart: (DragStartDetails details) {},
+  ///   child: PlatformViewSurface(
+  ///   ),
+  /// )
+  /// ```
+  ///
+  /// To get the [PlatformViewSurface] to claim the vertical drag gestures we can pass a vertical drag
+  /// gesture recognizer factory in [gestureRecognizers] e.g:
+  ///
+  /// ```dart
+  /// GestureDetector(
+  ///   onVerticalDragStart: (DragStartDetails details) {},
+  ///   child: SizedBox(
+  ///     width: 200.0,
+  ///     height: 100.0,
+  ///     child: PlatformViewSurface(
+  ///       gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>[
+  ///         new Factory<OneSequenceGestureRecognizer>(
+  ///           () => new EagerGestureRecognizer(),
+  ///         ),
+  ///       ].toSet(),
+  ///     ),
+  ///   ),
+  /// )
+  /// ```
+  ///
+  /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descFoot}
+  // We use OneSequenceGestureRecognizers as they support gesture arena teams.
+  // TODO(amirh): get a list of GestureRecognizers here.
+  // https://github.com/flutter/flutter/issues/20953
+  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
+
+  /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
+  final PlatformViewHitTestBehavior hitTestBehavior;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return PlatformViewRenderBox(controller: controller, gestureRecognizers: gestureRecognizers, hitTestBehavior: hitTestBehavior);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, PlatformViewRenderBox renderObject) {
+    renderObject
+      ..controller = controller
+      ..hitTestBehavior = hitTestBehavior
+      ..updateGestureRecognizers(gestureRecognizers);
+  }
+}
+
+/// Integrates an Android view with Flutter's compositor, touch, and semantics subsystems.
+///
+/// The compositor integration is done by adding a [PlatformViewLayer] to the layer tree. [PlatformViewLayer]
+/// isn't supported on all platforms. Custom Flutter embedders can support
+/// [PlatformViewLayer]s by implementing a SystemCompositor.
+///
+/// The widget fills all available space, the parent of this object must provide bounded layout
+/// constraints.
+///
+/// If the associated platform view is not created, the [AndroidViewSurface] does not paint any contents.
+///
+/// See also:
+///
+///  * [AndroidView] which embeds an Android platform view in the widget hierarchy using a [TextureLayer].
+///  * [UiKitView] which embeds an iOS platform view in the widget hierarchy.
+class AndroidViewSurface extends PlatformViewSurface {
+  /// Construct an `AndroidPlatformViewSurface`.
+  const AndroidViewSurface({
+    Key? key,
+    required AndroidViewController controller,
+    required PlatformViewHitTestBehavior hitTestBehavior,
+    required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
+  }) : assert(controller != null),
+       assert(hitTestBehavior != null),
+       assert(gestureRecognizers != null),
+       super(
+          key: key,
+          controller: controller,
+          hitTestBehavior: hitTestBehavior,
+          gestureRecognizers: gestureRecognizers);
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    final PlatformViewRenderBox renderBox =
+        super.createRenderObject(context) as PlatformViewRenderBox;
+
+    (controller as AndroidViewController).pointTransformer =
+        (Offset position) => renderBox.globalToLocal(position);
+
+    return renderBox;
+  }
+}
diff --git a/lib/src/widgets/preferred_size.dart b/lib/src/widgets/preferred_size.dart
new file mode 100644
index 0000000..e809b49
--- /dev/null
+++ b/lib/src/widgets/preferred_size.dart
@@ -0,0 +1,131 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+
+/// An interface for widgets that can return the size this widget would prefer
+/// if it were otherwise unconstrained.
+///
+/// There are a few cases, notably [AppBar] and [TabBar], where it would be
+/// undesirable for the widget to constrain its own size but where the widget
+/// needs to expose a preferred or "default" size. For example a primary
+/// [Scaffold] sets its app bar height to the app bar's preferred height
+/// plus the height of the system status bar.
+///
+/// Use [PreferredSize] to give a preferred size to an arbitrary widget.
+abstract class PreferredSizeWidget implements Widget {
+  /// The size this widget would prefer if it were otherwise unconstrained.
+  ///
+  /// In many cases it's only necessary to define one preferred dimension.
+  /// For example the [Scaffold] only depends on its app bar's preferred
+  /// height. In that case implementations of this method can just return
+  /// `new Size.fromHeight(myAppBarHeight)`.
+  Size get preferredSize;
+}
+
+/// A widget with a preferred size.
+///
+/// This widget does not impose any constraints on its child, and it doesn't
+/// affect the child's layout in any way. It just advertises a preferred size
+/// which can be used by the parent.
+///
+/// Widgets like [AppBar] implement a [PreferredSizeWidget].
+///
+/// {@tool dartpad --template=stateless_widget_material}
+///
+/// This sample shows a custom widget, similar to an [AppBar], which uses a
+/// [PreferredSize] widget, with its height set to 80 logical pixels.
+/// Changing the [PreferredSize] can be used to change the height
+/// of the custom app bar.
+///
+/// ```dart preamble
+/// class AppBarContent extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return Column(
+///       mainAxisAlignment: MainAxisAlignment.end,
+///       children: [
+///         Padding(
+///           padding: const EdgeInsets.symmetric(horizontal: 10),
+///           child: Row(
+///             children: [
+///               Text(
+///                 "PreferredSize Sample",
+///                 style: TextStyle(color: Colors.white),
+///               ),
+///               Spacer(),
+///               IconButton(
+///                 icon: Icon(
+///                   Icons.search,
+///                   size: 20,
+///                 ),
+///                 color: Colors.white,
+///                 onPressed: () {},
+///               ),
+///               IconButton(
+///                 icon: Icon(
+///                   Icons.more_vert,
+///                   size: 20,
+///                 ),
+///                 color: Colors.white,
+///                 onPressed: () {},
+///               ),
+///             ],
+///           ),
+///         ),
+///       ],
+///     );
+///   }
+/// }
+/// ```
+///```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: PreferredSize(
+///       preferredSize: const Size.fromHeight(80.0),
+///       child: Container(
+///         decoration: BoxDecoration(
+///           gradient: LinearGradient(
+///             colors: [Colors.blue, Colors.pink],
+///           ),
+///         ),
+///         child: AppBarContent(),
+///       ),
+///     ),
+///     body: Center(
+///       child: Text("Content"),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AppBar.bottom] and [Scaffold.appBar], which require preferred size widgets.
+///  * [PreferredSizeWidget], the interface which this widget implements to expose
+///    its preferred size.
+///  * [AppBar] and [TabBar], which implement PreferredSizeWidget.
+class PreferredSize extends StatelessWidget implements PreferredSizeWidget {
+  /// Creates a widget that has a preferred size.
+  const PreferredSize({
+    Key? key,
+    required this.child,
+    required this.preferredSize,
+  }) : super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  final Size preferredSize;
+
+  @override
+  Widget build(BuildContext context) => child;
+}
diff --git a/lib/src/widgets/primary_scroll_controller.dart b/lib/src/widgets/primary_scroll_controller.dart
new file mode 100644
index 0000000..4efd6a6
--- /dev/null
+++ b/lib/src/widgets/primary_scroll_controller.dart
@@ -0,0 +1,74 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'framework.dart';
+import 'scroll_controller.dart';
+
+/// Associates a [ScrollController] with a subtree.
+///
+/// When a [ScrollView] has [ScrollView.primary] set to true and is not given
+/// an explicit [ScrollController], the [ScrollView] uses [of] to find the
+/// [ScrollController] associated with its subtree.
+///
+/// This mechanism can be used to provide default behavior for scroll views in a
+/// subtree. For example, the [Scaffold] uses this mechanism to implement the
+/// scroll-to-top gesture on iOS.
+///
+/// Another default behavior handled by the PrimaryScrollController is default
+/// [ScrollAction]s. If a ScrollAction is not handled by an otherwise focused
+/// part of the application, the ScrollAction will be evaluated using the scroll
+/// view associated with a PrimaryScrollController, for example, when executing
+/// [Shortcuts] key events like page up and down.
+///
+/// See also:
+///   * [ScrollAction], an [Action] that scrolls the [Scrollable] that encloses
+///     the current [primaryFocus] or is attached to the PrimaryScrollController.
+///   * [Shortcuts], a widget that establishes a [ShortcutManager] to be used
+///     by its descendants when invoking an [Action] via a keyboard key
+///     combination that maps to an [Intent].
+class PrimaryScrollController extends InheritedWidget {
+  /// Creates a widget that associates a [ScrollController] with a subtree.
+  const PrimaryScrollController({
+    Key? key,
+    required ScrollController this.controller,
+    required Widget child,
+  }) : assert(controller != null),
+       super(key: key, child: child);
+
+  /// Creates a subtree without an associated [ScrollController].
+  const PrimaryScrollController.none({
+    Key? key,
+    required Widget child,
+  }) : controller = null,
+       super(key: key, child: child);
+
+  /// The [ScrollController] associated with the subtree.
+  ///
+  /// See also:
+  ///
+  ///  * [ScrollView.controller], which discusses the purpose of specifying a
+  ///    scroll controller.
+  final ScrollController? controller;
+
+  /// Returns the [ScrollController] most closely associated with the given
+  /// context.
+  ///
+  /// Returns null if there is no [ScrollController] associated with the given
+  /// context.
+  static ScrollController? of(BuildContext context) {
+    final PrimaryScrollController? result = context.dependOnInheritedWidgetOfExactType<PrimaryScrollController>();
+    return result?.controller;
+  }
+
+  @override
+  bool updateShouldNotify(PrimaryScrollController oldWidget) => controller != oldWidget.controller;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ScrollController>('controller', controller, ifNull: 'no controller', showName: false));
+  }
+}
diff --git a/lib/src/widgets/raw_keyboard_listener.dart b/lib/src/widgets/raw_keyboard_listener.dart
new file mode 100644
index 0000000..28ad225
--- /dev/null
+++ b/lib/src/widgets/raw_keyboard_listener.dart
@@ -0,0 +1,138 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/services.dart';
+
+import 'basic.dart';
+import 'focus_manager.dart';
+import 'focus_scope.dart';
+import 'framework.dart';
+
+export 'package:flute/services.dart' show RawKeyEvent;
+
+/// A widget that calls a callback whenever the user presses or releases a key
+/// on a keyboard.
+///
+/// A [RawKeyboardListener] is useful for listening to raw key events and
+/// hardware buttons that are represented as keys. Typically used by games and
+/// other apps that use keyboards for purposes other than text entry.
+///
+/// For text entry, consider using a [EditableText], which integrates with
+/// on-screen keyboards and input method editors (IMEs).
+///
+/// See also:
+///
+///  * [EditableText], which should be used instead of this widget for text
+///    entry.
+class RawKeyboardListener extends StatefulWidget {
+  /// Creates a widget that receives raw keyboard events.
+  ///
+  /// For text entry, consider using a [EditableText], which integrates with
+  /// on-screen keyboards and input method editors (IMEs).
+  ///
+  /// The [focusNode] and [child] arguments are required and must not be null.
+  ///
+  /// The [autofocus] argument must not be null.
+  const RawKeyboardListener({
+    Key? key,
+    required this.focusNode,
+    this.autofocus = false,
+    this.includeSemantics = true,
+    this.onKey,
+    required this.child,
+  }) : assert(focusNode != null),
+       assert(autofocus != null),
+       assert(includeSemantics != null),
+       assert(child != null),
+       super(key: key);
+
+  /// Controls whether this widget has keyboard focus.
+  final FocusNode focusNode;
+
+  /// {@macro flutter.widgets.Focus.autofocus}
+  final bool autofocus;
+
+  /// {@macro flutter.widgets.Focus.includeSemantics}
+  final bool includeSemantics;
+
+  /// Called whenever this widget receives a raw keyboard event.
+  final ValueChanged<RawKeyEvent>? onKey;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  _RawKeyboardListenerState createState() => _RawKeyboardListenerState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode));
+  }
+}
+
+class _RawKeyboardListenerState extends State<RawKeyboardListener> {
+  @override
+  void initState() {
+    super.initState();
+    widget.focusNode.addListener(_handleFocusChanged);
+  }
+
+  @override
+  void didUpdateWidget(RawKeyboardListener oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.focusNode != oldWidget.focusNode) {
+      oldWidget.focusNode.removeListener(_handleFocusChanged);
+      widget.focusNode.addListener(_handleFocusChanged);
+    }
+  }
+
+  @override
+  void dispose() {
+    widget.focusNode.removeListener(_handleFocusChanged);
+    _detachKeyboardIfAttached();
+    super.dispose();
+  }
+
+  void _handleFocusChanged() {
+    if (widget.focusNode.hasFocus)
+      _attachKeyboardIfDetached();
+    else
+      _detachKeyboardIfAttached();
+  }
+
+  bool _listening = false;
+
+  void _attachKeyboardIfDetached() {
+    if (_listening)
+      return;
+    RawKeyboard.instance.addListener(_handleRawKeyEvent);
+    _listening = true;
+  }
+
+  void _detachKeyboardIfAttached() {
+    if (!_listening)
+      return;
+    RawKeyboard.instance.removeListener(_handleRawKeyEvent);
+    _listening = false;
+  }
+
+  void _handleRawKeyEvent(RawKeyEvent event) {
+    if (widget.onKey != null)
+      widget.onKey!(event);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Focus(
+      focusNode: widget.focusNode,
+      autofocus: widget.autofocus,
+      includeSemantics: widget.includeSemantics,
+      child: widget.child,
+    );
+  }
+}
diff --git a/lib/src/widgets/restoration.dart b/lib/src/widgets/restoration.dart
new file mode 100644
index 0000000..e212b10
--- /dev/null
+++ b/lib/src/widgets/restoration.dart
@@ -0,0 +1,1080 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/services.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+
+export 'package:flute/services.dart' show RestorationBucket;
+
+/// Creates a new scope for restoration IDs used by descendant widgets to claim
+/// [RestorationBucket]s.
+///
+/// {@template flutter.widgets.RestorationScope}
+/// A restoration scope inserts a [RestorationBucket] into the widget tree,
+/// which descendant widgets can access via [RestorationScope.of]. It is
+/// uncommon for descendants to directly store data in this bucket. Instead,
+/// descendant widgets should consider storing their own restoration data in a
+/// child bucket claimed with [RestorationBucket.claimChild] from the bucket
+/// provided by this scope.
+/// {@endtemplate}
+///
+/// The bucket inserted into the widget tree by this scope has been claimed from
+/// the surrounding [RestorationScope] using the provided [restorationId]. If
+/// the [RestorationScope] is moved to a different part of the widget tree under
+/// a different [RestorationScope], the bucket owned by this scope with all its
+/// children and the data contained in them is moved to the new scope as well.
+///
+/// This widget will not make a [RestorationBucket] available to descendants if
+/// [restorationId] is null or when there is no surrounding restoration scope to
+/// claim a bucket from. In this case, descendant widgets invoking
+/// [RestorationScope.of] will receive null as a return value indicating that no
+/// bucket is available for storing restoration data. This will turn off state
+/// restoration for the widget subtree.
+///
+/// See also:
+///
+///  * [RootRestorationScope], which inserts the root bucket provided by
+///    the [RestorationManager] into the widget tree and makes it accessible
+///    for descendants via [RestorationScope.of].
+///  * [UnmanagedRestorationScope], which inserts a provided [RestorationBucket]
+///    into the widget tree and makes it accessible for descendants via
+///    [RestorationScope.of].
+///  * [RestorationMixin], which may be used in [State] objects to manage the
+///    restoration data of a [StatefulWidget] instead of manually interacting
+///    with [RestorationScope]s and [RestorationBucket]s.
+///  * [RestorationManager], which describes the basic concepts of state
+///    restoration in Flutter.
+class RestorationScope extends StatefulWidget {
+  /// Creates a [RestorationScope].
+  ///
+  /// Providing null as the [restorationId] turns off state restoration for
+  /// the [child] and its descendants.
+  ///
+  /// The [child] must not be null.
+  const RestorationScope({
+    Key? key,
+    required this.restorationId,
+    required this.child,
+  }) : assert(child != null),
+       super(key: key);
+
+  /// Returns the [RestorationBucket] inserted into the widget tree by the
+  /// closest ancestor [RestorationScope] of `context`.
+  ///
+  /// To avoid accidentally overwriting data already stored in the bucket by its
+  /// owner, data should not be stored directly in the bucket returned by this
+  /// method. Instead, consider claiming a child bucket from the returned bucket
+  /// (via [RestorationBucket.claimChild]) and store the restoration data in
+  /// that child.
+  ///
+  /// This method returns null if state restoration is turned off for this
+  /// subtree.
+  static RestorationBucket? of(BuildContext context) {
+    return context.dependOnInheritedWidgetOfExactType<UnmanagedRestorationScope>()?.bucket;
+  }
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The restoration ID used by this widget to obtain a child bucket from the
+  /// surrounding [RestorationScope].
+  ///
+  /// The child bucket obtained from the surrounding scope is made available to
+  /// descendant widgets via [RestorationScope.of].
+  ///
+  /// If this is null, [RestorationScope.of] invoked by descendants will return
+  /// null which effectively turns off state restoration for this subtree.
+  final String? restorationId;
+
+  @override
+  State<RestorationScope> createState() => _RestorationScopeState();
+}
+
+class _RestorationScopeState extends State<RestorationScope> with RestorationMixin {
+  @override
+  String? get restorationId => widget.restorationId;
+
+  @override
+  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
+    // Nothing to do.
+    // The bucket gets injected into the widget tree in the build method.
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return UnmanagedRestorationScope(
+      bucket: bucket, // `bucket` is provided by the RestorationMixin.
+      child: widget.child,
+    );
+  }
+}
+
+/// Inserts a provided [RestorationBucket] into the widget tree and makes it
+/// available to descendants via [RestorationScope.of].
+///
+/// {@macro flutter.widgets.RestorationScope}
+///
+/// If [bucket] is null, no restoration bucket is made available to descendant
+/// widgets ([RestorationScope.of] invoked from a descendant will return null).
+/// This effectively turns off state restoration for the subtree because no
+/// bucket for storing restoration data is made available.
+///
+/// See also:
+///
+///  * [RestorationScope], which inserts a bucket obtained from a surrounding
+///    restoration scope into the widget tree and makes it accessible
+///    for descendants via [RestorationScope.of].
+///  * [RootRestorationScope], which inserts the root bucket provided by
+///    the [RestorationManager] into the widget tree and makes it accessible
+///    for descendants via [RestorationScope.of].
+///  * [RestorationMixin], which may be used in [State] objects to manage the
+///    restoration data of a [StatefulWidget] instead of manually interacting
+///    with [RestorationScope]s and [RestorationBucket]s.
+///  * [RestorationManager], which describes the basic concepts of state
+///    restoration in Flutter.
+class UnmanagedRestorationScope extends InheritedWidget {
+  /// Creates an [UnmanagedRestorationScope].
+  ///
+  /// When [bucket] is null state restoration is turned off for the [child] and
+  /// its descendants.
+  ///
+  /// The [child] must not be null.
+  const UnmanagedRestorationScope({
+    Key? key,
+    this.bucket,
+    required Widget child,
+  }) : assert(child != null),
+       super(key: key, child: child);
+
+  /// The [RestorationBucket] that this widget will insert into the widget tree.
+  ///
+  /// Descendant widgets may obtain this bucket via [RestorationScope.of].
+  final RestorationBucket? bucket;
+
+  @override
+  bool updateShouldNotify(UnmanagedRestorationScope oldWidget) {
+    return oldWidget.bucket != bucket;
+  }
+}
+
+/// Inserts a child bucket of [RestorationManager.rootBucket] into the widget
+/// tree and makes it available to descendants via [RestorationScope.of].
+///
+/// This widget is usually used near the root of the widget tree to enable the
+/// state restoration functionality for the application. For all other use
+/// cases, consider using a regular [RestorationScope] instead.
+///
+/// The root restoration bucket can only be retrieved asynchronously from the
+/// [RestorationManager]. To ensure that the provided [child] has its
+/// restoration data available the first time it builds, the
+/// [RootRestorationScope] will build an empty [Container] instead of the actual
+/// [child] until the root bucket is available. To hide the empty container from
+/// the eyes of users, the [RootRestorationScope] also delays rendering the
+/// first frame while the container is shown. On platforms that show a splash
+/// screen on app launch the splash screen is kept up (hiding the empty
+/// container) until the bucket is available and the [child] is ready to be
+/// build.
+///
+/// The exact behavior of this widget depends on its ancestors: When the
+/// [RootRestorationScope] does not find an ancestor restoration bucket via
+/// [RestorationScope.of] it will claim a child bucket from the root restoration
+/// bucket ([RestorationManager.rootBucket]) using the provided [restorationId]
+/// and inserts that bucket into the widget tree where descendants may access it
+/// via [RestorationScope.of]. If the [RootRestorationScope] finds a non-null
+/// ancestor restoration bucket via [RestorationScope.of] it will behave like a
+/// regular [RestorationScope] instead: It will claim a child bucket from that
+/// ancestor and insert that child into the widget tree.
+///
+/// Unlike the [RestorationScope] widget, the [RootRestorationScope] will
+/// guarantee that descendants have a bucket available for storing restoration
+/// data as long as [restorationId] is not null and [RestorationManager] is
+/// able to provide a root bucket. In other words, it will force-enable
+/// state restoration for the subtree if [restorationId] is not null.
+///
+/// If [restorationId] is null, no bucket is made available to descendants,
+/// which effectively turns off state restoration for this subtree.
+///
+/// See also:
+///
+///  * [RestorationScope], which inserts a bucket obtained from a surrounding
+///    restoration scope into the widget tree and makes it accessible
+///    for descendants via [RestorationScope.of].
+///  * [UnmanagedRestorationScope], which inserts a provided [RestorationBucket]
+///    into the widget tree and makes it accessible for descendants via
+///    [RestorationScope.of].
+///  * [RestorationMixin], which may be used in [State] objects to manage the
+///    restoration data of a [StatefulWidget] instead of manually interacting
+///    with [RestorationScope]s and [RestorationBucket]s.
+///  * [RestorationManager], which describes the basic concepts of state
+///    restoration in Flutter.
+class RootRestorationScope extends StatefulWidget {
+  /// Creates a [RootRestorationScope].
+  ///
+  /// Providing null as the [restorationId] turns off state restoration for
+  /// the [child] and its descendants.
+  ///
+  /// The [child] must not be null.
+  const RootRestorationScope({
+    Key? key,
+    required this.restorationId,
+    required this.child,
+  }) : assert(child != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The restoration ID used to identify the child bucket that this widget
+  /// will insert into the tree.
+  ///
+  /// If this is null, no bucket is made available to descendants and state
+  /// restoration for the subtree is essentially turned off.
+  final String? restorationId;
+
+  @override
+  State<RootRestorationScope> createState() => _RootRestorationScopeState();
+}
+
+class _RootRestorationScopeState extends State<RootRestorationScope> {
+  bool? _okToRenderBlankContainer;
+  bool _rootBucketValid = false;
+  RestorationBucket? _rootBucket;
+  RestorationBucket? _ancestorBucket;
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _ancestorBucket = RestorationScope.of(context);
+    _loadRootBucketIfNecessary();
+    _okToRenderBlankContainer ??= widget.restorationId != null && _needsRootBucketInserted;
+  }
+
+  @override
+  void didUpdateWidget(RootRestorationScope oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    _loadRootBucketIfNecessary();
+  }
+
+  bool get _needsRootBucketInserted => _ancestorBucket == null;
+
+  bool get _isWaitingForRootBucket {
+    return widget.restorationId != null && _needsRootBucketInserted && !_rootBucketValid;
+  }
+
+  bool _isLoadingRootBucket = false;
+
+  void _loadRootBucketIfNecessary() {
+    if (_isWaitingForRootBucket && !_isLoadingRootBucket) {
+      _isLoadingRootBucket = true;
+      RendererBinding.instance!.deferFirstFrame();
+      ServicesBinding.instance!.restorationManager.rootBucket.then((RestorationBucket? bucket) {
+        _isLoadingRootBucket = false;
+        if (mounted) {
+          ServicesBinding.instance!.restorationManager.addListener(_replaceRootBucket);
+          setState(() {
+            _rootBucket = bucket;
+            _rootBucketValid = true;
+            _okToRenderBlankContainer = false;
+          });
+        }
+        RendererBinding.instance!.allowFirstFrame();
+      });
+    }
+  }
+
+  void _replaceRootBucket() {
+    _rootBucketValid = false;
+    _rootBucket = null;
+    ServicesBinding.instance!.restorationManager.removeListener(_replaceRootBucket);
+    _loadRootBucketIfNecessary();
+    assert(!_isWaitingForRootBucket); // Ensure that load finished synchronously.
+  }
+
+  @override
+  void dispose() {
+    if (_rootBucketValid) {
+      ServicesBinding.instance!.restorationManager.removeListener(_replaceRootBucket);
+    }
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (_okToRenderBlankContainer! && _isWaitingForRootBucket) {
+      return const SizedBox.shrink();
+    }
+
+    return UnmanagedRestorationScope(
+      bucket: _ancestorBucket ?? _rootBucket,
+      child: RestorationScope(
+        restorationId: widget.restorationId,
+        child: widget.child,
+      ),
+    );
+  }
+}
+
+/// Manages an object of type `T`, whose value a [State] object wants to have
+/// restored during state restoration.
+///
+/// The property wraps an object of type `T`. It knows how to store its value in
+/// the restoration data and it knows how to re-instantiate that object from the
+/// information it previously stored in the restoration data.
+///
+/// The knowledge of how to store the wrapped object in the restoration data is
+/// encoded in the [toPrimitives] method and the knowledge of how to
+/// re-instantiate the object from that data is encoded in the [fromPrimitives]
+/// method. A call to [toPrimitives] must return a representation of the wrapped
+/// object that can be serialized with the [StandardMessageCodec]. If any
+/// collections (e.g. [List]s, [Map]s, etc.) are returned, they must not be
+/// modified after they have been returned from [toPrimitives]. At a later point
+/// in time (which may be after the application restarted), the data obtained
+/// from [toPrimitives] may be handed back to the property's [fromPrimitives]
+/// method to restore it to the previous state described by that data.
+///
+/// A [RestorableProperty] needs to be registered to a [RestorationMixin] using
+/// a restoration ID that is unique within the mixin. The [RestorationMixin]
+/// provides and manages the [RestorationBucket], in which the data returned by
+/// [toPrimitives] is stored.
+///
+/// Whenever the value returned by [toPrimitives] (or the [enabled] getter)
+/// changes, the [RestorableProperty] must call [notifyListeners]. This will
+/// trigger the [RestorationMixin] to update the data it has stored for the
+/// property in its [RestorationBucket] to the latest information returned by
+/// [toPrimitives].
+///
+/// When the property is registered with the [RestorationMixin], the mixin
+/// checks whether there is any restoration data available for the property. If
+/// data is available, the mixin calls [fromPrimitives] on the property, which
+/// must return an object that matches the object the property wrapped when the
+/// provided restoration data was obtained from [toPrimitives]. If no
+/// restoration data is available to restore the property's wrapped object from,
+/// the mixin calls [createDefaultValue]. The value returned by either of those
+/// methods is then handed to the property's [initWithValue] method.
+///
+/// Usually, subclasses of [RestorableProperty] hold on to the value provided to
+/// them in [initWithValue] and make it accessible to the [State] object that
+/// owns the property. This [RestorableProperty] base class, however, has no
+/// opinion about what to do with the value provided to [initWithValue].
+///
+/// The [RestorationMixin] may call [fromPrimitives]/[createDefaultValue]
+/// followed by [initWithValue] multiple times throughout the life of a
+/// [RestorableProperty]: Whenever new restoration data is made available to the
+/// [RestorationMixin] the property is registered with, the cycle of calling
+/// [fromPrimitives] (if the new restoration data contains information to
+/// restore the property from) or [createDefaultValue] (if no information for
+/// the property is available in the new restoration data) followed by a call to
+/// [initWithValue] repeats. Whenever [initWithValue] is called, the property
+/// should forget the old value it was wrapping and re-initialize itself with
+/// the newly provided value.
+///
+/// In a typical use case, a subclass of [RestorableProperty] is instantiated
+/// either to initialize a member variable of a [State] object or within
+/// [State.initState]. It is then registered to a [RestorationMixin] in
+/// [RestorationMixin.restoreState] and later [dispose]ed in [State.dispose].
+/// For less common use cases (e.g. if the value stored in a
+/// [RestorableProperty] is only needed while the [State] object is in a certain
+/// state), a [RestorableProperty] may be registered with a [RestorationMixin]
+/// any time after [RestorationMixin.restoreState] has been called for the first
+/// time. A [RestorableProperty] may also be unregistered from a
+/// [RestorationMixin] before the owning [State] object is disposed by calling
+/// [RestorationMixin.unregisterFromRestoration]. This is uncommon, though, and
+/// will delete the information that the property contributed from the
+/// restoration data (meaning the value of the property will no longer be
+/// restored during a future state restoration).
+///
+/// See also:
+///
+///  * [RestorableValue], which is a [RestorableProperty] that makes the wrapped
+///    value accessible to the owning [State] object via a `value`
+///    getter and setter.
+///  * [RestorationMixin], to which a [RestorableProperty] must be registered.
+///  * [RestorationManager], which describes how state restoration works in
+///    Flutter.
+abstract class RestorableProperty<T> extends ChangeNotifier {
+  /// Called by the [RestorationMixin] if no restoration data is available to
+  /// restore the value of the property from to obtain the default value for the
+  /// property.
+  ///
+  /// The method returns the default value that the property should wrap if no
+  /// restoration data is available. After this is called, [initWithValue] will
+  /// be called with this method's return value.
+  ///
+  /// The method may be called multiple times throughout the life of the
+  /// [RestorableProperty]. Whenever new restoration data has been provided to
+  /// the [RestorationMixin] the property is registered to, either this method
+  /// or [fromPrimitives] is called before [initWithValue] is invoked.
+  T createDefaultValue();
+
+  /// Called by the [RestorationMixin] to convert the `data` previously
+  /// retrieved from [toPrimitives] back into an object of type `T` that this
+  /// property should wrap.
+  ///
+  /// The object returned by this method is passed to [initWithValue] to restore
+  /// the value that this property is wrapping to the value described by the
+  /// provided `data`.
+  ///
+  /// The method may be called multiple times throughout the life of the
+  /// [RestorableProperty]. Whenever new restoration data has been provided to
+  /// the [RestorationMixin] the property is registered to, either this method
+  /// or [createDefaultValue] is called before [initWithValue] is invoked.
+  T fromPrimitives(Object? data);
+
+  /// Called by the [RestorationMixin] with the `value` returned by either
+  /// [createDefaultValue] or [fromPrimitives] to set the value that this
+  /// property currently wraps.
+  ///
+  /// The [initWithValue] method may be called multiple times throughout the
+  /// life of the [RestorableProperty] whenever new restoration data has been
+  /// provided to the [RestorationMixin] the property is registered to. When
+  /// [initWithValue] is called, the property should forget its previous value
+  /// and re-initialize itself to the newly provided `value`.
+  void initWithValue(T value);
+
+  /// Called by the [RestorationMixin] to retrieve the information that this
+  /// property wants to store in the restoration data.
+  ///
+  /// The returned object must be serializable with the [StandardMessageCodec]
+  /// and if it includes any collections, those should not be modified after
+  /// they have been returned by this method.
+  ///
+  /// The information returned by this method may be handed back to the property
+  /// in a call to [fromPrimitives] at a later point in time (possibly after the
+  /// application restarted) to restore the value that the property is currently
+  /// wrapping.
+  ///
+  /// When the value returned by this method changes, the property must call
+  /// [notifyListeners]. The [RestorationMixin] will invoke this method whenever
+  /// the property's listeners are notified.
+  Object? toPrimitives();
+
+  /// Whether the object currently returned by [toPrimitives] should be included
+  /// in the restoration state.
+  ///
+  /// When this returns false, no information is included in the restoration
+  /// data for this property and the property will be initialized to its default
+  /// value (obtained from [createDefaultValue]) the next time that restoration
+  /// data is used for state restoration.
+  ///
+  /// Whenever the value returned by this getter changes, [notifyListeners] must
+  /// be called. When the value changes from true to false, the information last
+  /// retrieved from [toPrimitives] is removed from the restoration data. When
+  /// it changes from false to true, [toPrimitives] is invoked to add the latest
+  /// restoration information provided by this property to the restoration data.
+  bool get enabled => true;
+
+  bool _disposed = false;
+
+  @override
+  void dispose() {
+    assert(_debugAssertNotDisposed());
+    _owner?._unregister(this);
+    super.dispose();
+    _disposed = true;
+  }
+
+  // ID under which the property has been registered with the RestorationMixin.
+  String? _restorationId;
+  RestorationMixin? _owner;
+  void _register(String restorationId, RestorationMixin owner) {
+    assert(_debugAssertNotDisposed());
+    assert(restorationId != null);
+    assert(owner != null);
+    _restorationId = restorationId;
+    _owner = owner;
+  }
+  void _unregister() {
+    assert(_debugAssertNotDisposed());
+    assert(_restorationId != null);
+    assert(_owner != null);
+    _restorationId = null;
+    _owner = null;
+  }
+
+  /// The [State] object that this property is registered with.
+  ///
+  /// Must only be called when [isRegistered] is true.
+  @protected
+  State get state {
+    assert(isRegistered);
+    assert(_debugAssertNotDisposed());
+    return _owner!;
+  }
+
+  /// Whether this property is currently registered with a [RestorationMixin].
+  @protected
+  bool get isRegistered {
+    assert(_debugAssertNotDisposed());
+    return _restorationId != null;
+  }
+
+  bool _debugAssertNotDisposed() {
+    assert(() {
+      if (_disposed) {
+        throw FlutterError(
+          'A $runtimeType was used after being disposed.\n'
+          'Once you have called dispose() on a $runtimeType, it can no longer be used.'
+        );
+      }
+      return true;
+    }());
+    return true;
+  }
+}
+
+/// Manages the restoration data for a [State] object of a [StatefulWidget].
+///
+/// Restoration data can be serialized out and, at a later point in time, be
+/// used to restore the stateful members in the [State] object to the same
+/// values they had when the data was generated.
+///
+/// This mixin organizes the restoration data of a [State] object in
+/// [RestorableProperty]. All the information that the [State] object wants to
+/// get restored during state restoration need to be saved in a subclass of
+/// [RestorableProperty]. For example, to restore the count value in a counter
+/// app, that value should be stored in a member variable of type
+/// [RestorableInt] instead of a plain member variable of type [int].
+///
+/// The mixin ensures that the current values of the [RestorableProperty]s are
+/// serialized as part of the restoration state. It is up to the [State] to
+/// ensure that the data stored in the properties is always up to date. When the
+/// widget is restored from previously generated restoration data, the values of
+/// the [RestorableProperty]s are automatically restored to the values that had
+/// when the restoration data was serialized out.
+///
+/// Within a [State] that uses this mixin, [RestorableProperty]s are usually
+/// instantiated to initialize member variables. Users of the mixin must
+/// override [restoreState] and register their previously instantiated
+/// [RestorableProperty]s in this method by calling [registerForRestoration].
+/// The mixin calls this method for the first time right after
+/// [State.initState]. After registration, the values stored in the property
+/// have either been restored to their previous value or - if no restoration
+/// data for restoring is available - they are initialized with a
+/// property-specific default value. At the end of a [State] object's life
+/// cycle, all restorable properties must be disposed in [State.dispose].
+///
+/// In addition to being invoked right after [State.initState], [restoreState]
+/// is invoked again when new restoration data has been provided to the mixin.
+/// When this happens, the [State] object must re-register all properties with
+/// [registerForRestoration] again to restore them to their previous values as
+/// described by the new restoration data. All initialization logic that depends
+/// on the current value of a restorable property should be included in the
+/// [restoreState] method to ensure it re-executes when the properties are
+/// restored to a different value during the life time of the [State] object.
+///
+/// Internally, the mixin stores the restoration data from all registered
+/// properties in a [RestorationBucket] claimed from the surrounding
+/// [RestorationScope] using the [State]-provided [restorationId]. The
+/// [restorationId] must be unique in the surrounding [RestorationScope]. State
+/// restoration is disabled for the [State] object using this mixin if
+/// [restorationId] is null or when there is no surrounding [RestorationScope].
+/// In that case, the values of the registered properties will not be restored
+/// during state restoration.
+///
+/// The [RestorationBucket] used to store the registered properties is available
+/// via the [bucket] getter. Interacting directly with the bucket is uncommon,
+/// but the [State] object may make this bucket available for its descendants to
+/// claim child buckets from. For that, the [bucket] is injected into the widget
+/// tree in [State.build] with the help of an [UnmanagedRestorationScope].
+///
+/// The [bucket] getter returns null if state restoration is turned off. If
+/// state restoration is turned on or off during the lifetime of the widget
+/// (e.g. because [restorationId] changes from null to non-null) the value
+/// returned by the getter will also change from null to non-null or vice versa.
+/// The mixin calls [didToggleBucket] on itself to notify the [State] object
+/// about this change. Overriding this method is not necessary as long as the
+/// [State] object does not directly interact with the [bucket].
+///
+/// Whenever the value returned by [restorationId] changes,
+/// [didUpdateRestorationId] must be called (unless the change already triggers
+/// a call to [didUpdateWidget]).
+///
+/// {@tool dartpad --template=freeform}
+/// This example demonstrates how to make a simple counter app restorable by
+/// using the [RestorationMixin] and a [RestorableInt].
+///
+/// ```dart imports
+/// import 'package:flute/material.dart';
+/// ```
+///
+/// ```dart main
+/// void main() => runApp(RestorationExampleApp());
+/// ```
+///
+/// ```dart preamble
+/// class RestorationExampleApp extends StatelessWidget {
+///   @override
+///   Widget build(BuildContext context) {
+///     return MaterialApp(
+///       restorationScopeId: 'app',
+///       title: 'Restorable Counter',
+///       home: RestorableCounter(restorationId: 'counter'),
+///     );
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// class RestorableCounter extends StatefulWidget {
+///   RestorableCounter({Key? key, this.restorationId}) : super(key: key);
+///
+///   final String? restorationId;
+///
+///   @override
+///   _RestorableCounterState createState() => _RestorableCounterState();
+/// }
+///
+/// // The [State] object uses the [RestorationMixin] to make the current value
+/// // of the counter restorable.
+/// class _RestorableCounterState extends State<RestorableCounter> with RestorationMixin {
+///   // The current value of the counter is stored in a [RestorableProperty].
+///   // During state restoration it is automatically restored to its old value.
+///   // If no restoration data is available to restore the counter from, it is
+///   // initialized to the specified default value of zero.
+///   RestorableInt _counter = RestorableInt(0);
+///
+///   // In this example, the restoration ID for the mixin is passed in through
+///   // the [StatefulWidget]'s constructor.
+///   @override
+///   String? get restorationId => widget.restorationId;
+///
+///   @override
+///   void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
+///     // All restorable properties must be registered with the mixin. After
+///     // registration, the counter either has its old value restored or is
+///     // initialized to its default value.
+///     registerForRestoration(_counter, 'count');
+///   }
+///
+///   void _incrementCounter() {
+///     setState(() {
+///       // The current value of the property can be accessed and modified via
+///       // the value getter and setter.
+///       _counter.value++;
+///     });
+///   }
+///
+///   @override
+///   void dispose() {
+///     _counter.dispose();
+///     super.dispose();
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Scaffold(
+///       appBar: AppBar(
+///         title: Text('Restorable Counter'),
+///       ),
+///       body: Center(
+///         child: Column(
+///           mainAxisAlignment: MainAxisAlignment.center,
+///           children: <Widget>[
+///             Text(
+///               'You have pushed the button this many times:',
+///             ),
+///             Text(
+///               '${_counter.value}',
+///               style: Theme.of(context).textTheme.headline4,
+///             ),
+///           ],
+///         ),
+///       ),
+///       floatingActionButton: FloatingActionButton(
+///         onPressed: _incrementCounter,
+///         tooltip: 'Increment',
+///         child: Icon(Icons.add),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [RestorableProperty], which is the base class for all restoration
+///    properties managed by this mixin.
+///  * [RestorationManager], which describes how state restoration in Flutter
+///    works.
+///  * [RestorationScope], which creates a new namespace for restoration IDs
+///    in the widget tree.
+@optionalTypeArgs
+mixin RestorationMixin<S extends StatefulWidget> on State<S> {
+  /// The restoration ID used for the [RestorationBucket] in which the mixin
+  /// will store the restoration data of all registered properties.
+  ///
+  /// The restoration ID is used to claim a child [RestorationScope] from the
+  /// surrounding [RestorationScope] (accessed via [RestorationScope.of]) and
+  /// the ID must be unique in that scope (otherwise an exception is triggered
+  /// in debug move).
+  ///
+  /// State restoration for this mixin is turned off when this getter returns
+  /// null or when there is no surrounding [RestorationScope] available. When
+  /// state restoration is turned off, the values of the registered properties
+  /// cannot be restored.
+  ///
+  /// Whenever the value returned by this getter changes,
+  /// [didUpdateRestorationId] must be called unless the (unless the change
+  /// already triggered a call to [didUpdateWidget]).
+  ///
+  /// The restoration ID returned by this getter is often provided in the
+  /// constructor of the [StatefulWidget] that this [State] object is associated
+  /// with.
+  @protected
+  String? get restorationId;
+
+  /// The [RestorationBucket] used for the restoration data of the
+  /// [RestorableProperty]s registered to this mixin.
+  ///
+  /// The bucket has been claimed from the surrounding [RestorationScope] using
+  /// [restorationId].
+  ///
+  /// The getter returns null if state restoration is turned off. When state
+  /// restoration is turned on or off during the lifetime of this mixin (and
+  /// hence the return value of this getter switches between null and non-null)
+  /// [didToggleBucket] is called.
+  ///
+  /// Interacting directly with this bucket is uncommon. However, the bucket may
+  /// be injected into the widget tree in the [State]'s `build` method using an
+  /// [UnmanagedRestorationScope]. That allows descendants to claim child
+  /// buckets from this bucket for their own restoration needs.
+  RestorationBucket? get bucket => _bucket;
+  RestorationBucket? _bucket;
+
+  /// Called to initialize or restore the [RestorableProperty]s used by the
+  /// [State] object.
+  ///
+  /// This method is always invoked at least once right after [State.initState]
+  /// to register the [RestorableProperty]s with the mixin even when state
+  /// restoration is turned off or no restoration data is available for this
+  /// [State] object.
+  ///
+  /// Typically, [registerForRestoration] is called from this method to register
+  /// all [RestorableProperty]s used by the [State] object with the mixin. The
+  /// registration will either restore the property's value to the value
+  /// described by the restoration data, if available, or, if no restoration
+  /// data is available - initialize it to a property-specific default value.
+  ///
+  /// The method is called again whenever new restoration data (in the form of a
+  /// new [bucket]) has been provided to the mixin. When that happens, the
+  /// [State] object must re-register all previously registered properties,
+  /// which will restore their values to the value described by the new
+  /// restoration data.
+  ///
+  /// Since the method may change the value of the registered properties when
+  /// new restoration state is provided, all initialization logic that depends
+  /// on a specific value of a [RestorableProperty] should be included in this
+  /// method. That way, that logic re-executes when the [RestorableProperty]s
+  /// have their values restored from newly provided restoration data.
+  ///
+  /// The first time the method is invoked, the provided `oldBucket` argument is
+  /// always null. In subsequent calls triggered by new restoration data in the
+  /// form of a new bucket, the argument given is the previous value of
+  /// [bucket].
+  @mustCallSuper
+  @protected
+  void restoreState(RestorationBucket? oldBucket, bool initialRestore);
+
+  /// Called when [bucket] switches between null and non-null values.
+  ///
+  /// [State] objects that wish to directly interact with the bucket may
+  /// override this method to store additional values in the bucket when one
+  /// becomes available or to save values stored in a bucket elsewhere when the
+  /// bucket goes away. This is uncommon and storing those values in
+  /// [RestorableProperty]s should be considered instead.
+  ///
+  /// The `oldBucket` is provided to the method when the [bucket] getter changes
+  /// from non-null to null. The `oldBucket` argument is null when the [bucket]
+  /// changes from null to non-null.
+  ///
+  /// See also:
+  ///
+  ///  * [restoreState], which is called when the [bucket] changes from one
+  ///    non-null value to another non-null value.
+  @mustCallSuper
+  @protected
+  void didToggleBucket(RestorationBucket? oldBucket) {
+    // When a bucket is replaced, must `restoreState` is called instead.
+    assert(_bucket?.isReplacing != true);
+  }
+
+  // Maps properties to their listeners.
+  final Map<RestorableProperty<Object?>, VoidCallback> _properties = <RestorableProperty<Object?>, VoidCallback>{};
+
+  /// Registers a [RestorableProperty] for state restoration.
+  ///
+  /// The registration associates the provided `property` with the provided
+  /// `restorationId`. If restoration data is available for the provided
+  /// `restorationId`, the property's value is restored to the value described
+  /// by the restoration data. If no restoration data is available, the property
+  /// will be initialized to a property-specific default value.
+  ///
+  /// Each property within a [State] object must be registered under a unique
+  /// ID. Only registered properties will have their values restored during
+  /// state restoration.
+  ///
+  /// Typically, this method is called from within [restoreState] to register
+  /// all restorable properties of the owning [State] object. However, if a
+  /// given [RestorableProperty] is only needed when certain conditions are met
+  /// within the [State], [registerForRestoration] may also be called at any
+  /// time after [restoreState] has been invoked for the first time.
+  ///
+  /// A property that has been registered outside of [restoreState] must be
+  /// re-registered within [restoreState] the next time that method is called
+  /// unless it has been unregistered with [unregisterFromRestoration].
+  @protected
+  void registerForRestoration(RestorableProperty<Object?> property, String restorationId) {
+    assert(property != null);
+    assert(restorationId != null);
+    assert(property._restorationId == null || (_debugDoingRestore && property._restorationId == restorationId),
+           'Property is already registered under ${property._restorationId}.',
+    );
+    assert(_debugDoingRestore || !_properties.keys.map((RestorableProperty<Object?> r) => r._restorationId).contains(restorationId),
+           '"$restorationId" is already registered to another property.'
+    );
+    final bool hasSerializedValue = bucket?.contains(restorationId) == true;
+    final Object? initialValue = hasSerializedValue
+        ? property.fromPrimitives(bucket!.read<Object>(restorationId))
+        : property.createDefaultValue();
+
+    if (!property.isRegistered) {
+      property._register(restorationId, this);
+      final VoidCallback listener = () {
+        if (bucket == null)
+          return;
+        _updateProperty(property);
+      };
+      property.addListener(listener);
+      _properties[property] = listener;
+    }
+
+    assert(
+      property._restorationId == restorationId &&
+      property._owner == this &&
+      _properties.containsKey(property)
+    );
+
+    property.initWithValue(initialValue);
+    if (!hasSerializedValue && property.enabled && bucket != null) {
+      _updateProperty(property);
+    }
+
+    assert(() {
+      _debugPropertiesWaitingForReregistration?.remove(property);
+      return true;
+    }());
+  }
+
+  /// Unregisters a [RestorableProperty] from state restoration.
+  ///
+  /// The value of the `property` is removed from the restoration data and it
+  /// will not be restored if that data is used in a future state restoration.
+  ///
+  /// Calling this method is uncommon, but may be necessary if the data of a
+  /// [RestorableProperty] is only relevant when the [State] object is in a
+  /// certain state. When the data of a property is no longer necessary to
+  /// restore the internal state of a [State] object, it may be removed from the
+  /// restoration data by calling this method.
+  @protected
+  void unregisterFromRestoration(RestorableProperty<Object?> property) {
+    assert(property != null);
+    assert(property._owner == this);
+    _bucket?.remove<Object?>(property._restorationId!);
+    _unregister(property);
+  }
+
+  /// Must be called when the value returned by [restorationId] changes.
+  ///
+  /// This method is automatically called from [didUpdateWidget]. Therefore,
+  /// manually invoking this method may be omitted when the change in
+  /// [restorationId] was caused by an updated widget.
+  @protected
+  void didUpdateRestorationId() {
+    // There's nothing to do if:
+    //  - We don't have a parent to claim a bucket from.
+    //  - Our current bucket already uses the provided restoration ID.
+    //  - There's a restore pending, which means that didUpdateDependencies
+    //    will be called and we handle the rename there.
+    if (_currentParent == null || _bucket?.restorationId == restorationId || restorePending) {
+      return;
+    }
+
+    final RestorationBucket? oldBucket = _bucket;
+    assert(!restorePending);
+    final bool didReplaceBucket = _updateBucketIfNecessary(parent: _currentParent, restorePending: false);
+    if (didReplaceBucket) {
+      assert(oldBucket != _bucket);
+      assert(_bucket == null || oldBucket == null);
+      oldBucket?.dispose();
+    }
+  }
+
+  @override
+  void didUpdateWidget(S oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    didUpdateRestorationId();
+  }
+
+  /// Whether [restoreState] will be called at the beginning of the next build
+  /// phase.
+  ///
+  /// Returns true when new restoration data has been provided to the mixin, but
+  /// the registered [RestorableProperty]s have not been restored to their new
+  /// values (as described by the new restoration data) yet. The properties will
+  /// get the values restored when [restoreState] is invoked at the beginning of
+  /// the next build cycle.
+  ///
+  /// While this is true, [bucket] will also still return the old bucket with
+  /// the old restoration data. It will update to the new bucket with the new
+  /// data just before [restoreState] is invoked.
+  bool get restorePending {
+    if (_firstRestorePending) {
+      return true;
+    }
+    if (restorationId == null) {
+      return false;
+    }
+    final RestorationBucket? potentialNewParent = RestorationScope.of(context);
+    return potentialNewParent != _currentParent && potentialNewParent?.isReplacing == true;
+  }
+
+  List<RestorableProperty<Object?>>? _debugPropertiesWaitingForReregistration;
+  bool get _debugDoingRestore => _debugPropertiesWaitingForReregistration != null;
+
+  bool _firstRestorePending = true;
+  RestorationBucket? _currentParent;
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+
+    final RestorationBucket? oldBucket = _bucket;
+    final bool needsRestore = restorePending;
+    _currentParent = RestorationScope.of(context);
+
+    final bool didReplaceBucket = _updateBucketIfNecessary(parent: _currentParent, restorePending: needsRestore);
+
+    if (needsRestore) {
+      _doRestore(oldBucket);
+    }
+    if (didReplaceBucket) {
+      assert(oldBucket != _bucket);
+      oldBucket?.dispose();
+    }
+  }
+
+  void _doRestore(RestorationBucket? oldBucket) {
+    assert(() {
+      _debugPropertiesWaitingForReregistration = _properties.keys.toList();
+      return true;
+    }());
+
+    restoreState(oldBucket, _firstRestorePending);
+    _firstRestorePending = false;
+
+    assert(() {
+      if (_debugPropertiesWaitingForReregistration!.isNotEmpty) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary(
+            'Previously registered RestorableProperties must be re-registered in "restoreState".',
+          ),
+          ErrorDescription(
+            'The RestorableProperties with the following IDs were not re-registered to $this when '
+                '"restoreState" was called:',
+          ),
+          ..._debugPropertiesWaitingForReregistration!.map((RestorableProperty<Object?> property) => ErrorDescription(
+            ' * ${property._restorationId}',
+          )),
+        ]);
+      }
+      _debugPropertiesWaitingForReregistration = null;
+      return true;
+    }());
+  }
+
+  // Returns true if `bucket` has been replaced with a new bucket. It's the
+  // responsibility of the caller to dispose the old bucket when this returns true.
+  bool _updateBucketIfNecessary({
+    required RestorationBucket? parent,
+    required bool restorePending,
+  }) {
+    if (restorationId == null || parent == null) {
+      final bool didReplace = _setNewBucketIfNecessary(newBucket: null, restorePending: restorePending);
+      assert(_bucket == null);
+      return didReplace;
+    }
+    assert(restorationId != null);
+    assert(parent != null);
+    if (restorePending || _bucket == null) {
+      final RestorationBucket newBucket = parent.claimChild(restorationId!, debugOwner: this);
+      assert(newBucket != null);
+      final bool didReplace = _setNewBucketIfNecessary(newBucket: newBucket, restorePending: restorePending);
+      assert(_bucket == newBucket);
+      return didReplace;
+    }
+    // We have an existing bucket, make sure it has the right parent and id.
+    assert(_bucket != null);
+    assert(!restorePending);
+    _bucket!.rename(restorationId!);
+    parent.adoptChild(_bucket!);
+    return false;
+  }
+
+  // Returns true if `bucket` has been replaced with a new bucket. It's the
+  // responsibility of the caller to dispose the old bucket when this returns true.
+  bool _setNewBucketIfNecessary({required RestorationBucket? newBucket, required bool restorePending}) {
+    if (newBucket == _bucket) {
+      return false;
+    }
+    final RestorationBucket? oldBucket = _bucket;
+    _bucket = newBucket;
+    if (!restorePending) {
+      // Write the current property values into the new bucket to persist them.
+      if (_bucket != null) {
+        _properties.keys.forEach(_updateProperty);
+      }
+      didToggleBucket(oldBucket);
+    }
+    return true;
+  }
+
+  void _updateProperty(RestorableProperty<Object?> property) {
+    if (property.enabled) {
+      _bucket?.write(property._restorationId!, property.toPrimitives());
+    } else {
+      _bucket?.remove<Object>(property._restorationId!);
+    }
+  }
+
+  void _unregister(RestorableProperty<Object?> property) {
+    final VoidCallback listener = _properties.remove(property)!;
+    assert(() {
+      _debugPropertiesWaitingForReregistration?.remove(property);
+      return true;
+    }());
+    property.removeListener(listener);
+    property._unregister();
+  }
+
+  @override
+  void dispose() {
+    _properties.forEach((RestorableProperty<Object?> property, VoidCallback listener) {
+      if (!property._disposed) {
+        property.removeListener(listener);
+      }
+    });
+    _bucket?.dispose();
+    _bucket = null;
+    super.dispose();
+  }
+}
diff --git a/lib/src/widgets/restoration_properties.dart b/lib/src/widgets/restoration_properties.dart
new file mode 100644
index 0000000..d6e1ce3
--- /dev/null
+++ b/lib/src/widgets/restoration_properties.dart
@@ -0,0 +1,400 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/services.dart';
+
+import 'editable_text.dart';
+import 'restoration.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+
+/// A [RestorableProperty] that makes the wrapped value accessible to the owning
+/// [State] object via the [value] getter and setter.
+///
+/// Whenever a new [value] is set, [didUpdateValue] is called. Subclasses should
+/// call [notifyListeners] from this method if the new value changes what
+/// [toPrimitives] returns.
+///
+/// ## Using a RestorableValue
+///
+/// {@tool dartpad --template=stateful_widget_restoration_no_null_safety}
+/// A [StatefulWidget] that has a restorable [int] property.
+///
+/// ```dart
+///   // The current value of the answer is stored in a [RestorableProperty].
+///   // During state restoration it is automatically restored to its old value.
+///   // If no restoration data is available to restore the answer from, it is
+///   // initialized to the specified default value, in this case 42.
+///   RestorableInt _answer = RestorableInt(42);
+///
+///   @override
+///   void restoreState(RestorationBucket oldBucket, bool initialRestore) {
+///     // All restorable properties must be registered with the mixin. After
+///     // registration, the answer either has its old value restored or is
+///     // initialized to its default value.
+///     registerForRestoration(_answer, 'answer');
+///   }
+///
+///   void _incrementAnswer() {
+///     setState(() {
+///       // The current value of the property can be accessed and modified via
+///       // the value getter and setter.
+///       _answer.value += 1;
+///     });
+///   }
+///
+///   @override
+///   void dispose() {
+///     // Properties must be disposed when no longer used.
+///     _answer.dispose();
+///     super.dispose();
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return OutlinedButton(
+///       child: Text('${_answer.value}'),
+///       onPressed: _incrementAnswer,
+///     );
+///   }
+/// ```
+/// {@end-tool}
+///
+/// ## Creating a subclass
+///
+/// {@tool snippet}
+/// This example shows how to create a new `RestorableValue` subclass,
+/// in this case for the [Duration] class.
+///
+/// ```dart
+/// class RestorableDuration extends RestorableValue<Duration> {
+///   @override
+///   Duration createDefaultValue() => const Duration();
+///
+///   @override
+///   void didUpdateValue(Duration oldValue) {
+///     if (oldValue.inMicroseconds != value.inMicroseconds)
+///       notifyListeners();
+///   }
+///
+///   @override
+///   Duration fromPrimitives(Object data) {
+///     return Duration(microseconds: data as int);
+///   }
+///
+///   @override
+///   Object toPrimitives() {
+///     return value.inMicroseconds;
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [RestorableProperty], which is the super class of this class.
+///  * [RestorationMixin], to which a [RestorableValue] needs to be registered
+///    in order to work.
+///  * [RestorationManager], which provides an overview of how state restoration
+///    works in Flutter.
+abstract class RestorableValue<T> extends RestorableProperty<T> {
+  /// The current value stored in this property.
+  ///
+  /// A representation of the current value is stored in the restoration data.
+  /// During state restoration, the property will restore the value to what it
+  /// was when the restoration data it is getting restored from was collected.
+  ///
+  /// The [value] can only be accessed after the property has been registered
+  /// with a [RestorationMixin] by calling
+  /// [RestorationMixin.registerForRestoration].
+  T get value {
+    assert(isRegistered);
+    return _value as T;
+  }
+  T? _value;
+  set value(T newValue) {
+    assert(isRegistered);
+    if (newValue != _value) {
+      final T? oldValue = _value;
+      _value = newValue;
+      didUpdateValue(oldValue);
+    }
+  }
+
+  @mustCallSuper
+  @override
+  void initWithValue(T value) {
+    _value = value;
+  }
+
+  /// Called whenever a new value is assigned to [value].
+  ///
+  /// The new value can be accessed via the regular [value] getter and the
+  /// previous value is provided as `oldValue`.
+  ///
+  /// Subclasses should call [notifyListeners] from this method, if the new
+  /// value changes what [toPrimitives] returns.
+  @protected
+  void didUpdateValue(T? oldValue);
+}
+
+// _RestorablePrimitiveValueN and its subclasses allows for null values.
+// See [_RestorablePrimitiveValue] for the non-nullable version of this class.
+class _RestorablePrimitiveValueN<T extends Object?> extends RestorableValue<T> {
+  _RestorablePrimitiveValueN(this._defaultValue)
+    : assert(debugIsSerializableForRestoration(_defaultValue)),
+      super();
+
+  final T _defaultValue;
+
+  @override
+  T createDefaultValue() => _defaultValue;
+
+  @override
+  void didUpdateValue(T? oldValue) {
+    assert(debugIsSerializableForRestoration(value));
+    notifyListeners();
+  }
+
+  @override
+  T fromPrimitives(Object? serialized) => serialized as T;
+
+  @override
+  Object? toPrimitives() => value;
+}
+
+// _RestorablePrimitiveValue and its subclasses are non-nullable.
+// See [_RestorablePrimitiveValueN] for the nullable version of this class.
+class _RestorablePrimitiveValue<T extends Object> extends _RestorablePrimitiveValueN<T> {
+  _RestorablePrimitiveValue(T _defaultValue)
+    : assert(_defaultValue != null),
+      assert(debugIsSerializableForRestoration(_defaultValue)),
+      super(_defaultValue);
+
+  @override
+  set value(T value) {
+    assert(value != null);
+    super.value = value;
+  }
+
+  @override
+  T fromPrimitives(Object? serialized) {
+    assert(serialized != null);
+    return super.fromPrimitives(serialized);
+  }
+
+  @override
+  Object toPrimitives() {
+    assert(value != null);
+    return super.toPrimitives()!;
+  }
+}
+
+/// A [RestorableProperty] that knows how to store and restore a [num].
+///
+/// {@template flutter.widgets.RestorableNum}
+/// The current [value] of this property is stored in the restoration data.
+/// During state restoration the property is restored to the value it had when
+/// the restoration data it is getting restored from was collected.
+///
+/// If no restoration data is available, [value] is initialized to the
+/// `defaultValue` given in the constructor.
+/// {@endtemplate}
+///
+/// Instead of using the more generic [RestorableNum] directly, consider using
+/// one of the more specific subclasses (e.g. [RestorableDouble] to store a
+/// [double] and [RestorableInt] to store an [int]).
+class RestorableNum<T extends num> extends _RestorablePrimitiveValue<T> {
+  /// Creates a [RestorableNum].
+  ///
+  /// {@template flutter.widgets.RestorableNum.constructor}
+  /// If no restoration data is available to restore the value in this property
+  /// from, the property will be initialized with the provided `defaultValue`.
+  /// {@endtemplate}
+  RestorableNum(T defaultValue) : assert(defaultValue != null), super(defaultValue);
+}
+
+/// A [RestorableProperty] that knows how to store and restore a [double].
+///
+/// {@macro flutter.widgets.RestorableNum}
+class RestorableDouble extends RestorableNum<double> {
+  /// Creates a [RestorableDouble].
+  ///
+  /// {@macro flutter.widgets.RestorableNum.constructor}
+  RestorableDouble(double defaultValue) : assert(defaultValue != null), super(defaultValue);
+}
+
+/// A [RestorableProperty] that knows how to store and restore an [int].
+///
+/// {@macro flutter.widgets.RestorableNum}
+class RestorableInt extends RestorableNum<int> {
+  /// Creates a [RestorableInt].
+  ///
+  /// {@macro flutter.widgets.RestorableNum.constructor}
+  RestorableInt(int defaultValue) : assert(defaultValue != null), super(defaultValue);
+}
+
+/// A [RestorableProperty] that knows how to store and restore a [String].
+///
+/// {@macro flutter.widgets.RestorableNum}
+class RestorableString extends _RestorablePrimitiveValue<String> {
+  /// Creates a [RestorableString].
+  ///
+  /// {@macro flutter.widgets.RestorableNum.constructor}
+  RestorableString(String defaultValue) : assert(defaultValue != null), super(defaultValue);
+}
+
+/// A [RestorableProperty] that knows how to store and restore a [bool].
+///
+/// {@macro flutter.widgets.RestorableNum}
+class RestorableBool extends _RestorablePrimitiveValue<bool> {
+  /// Creates a [RestorableBool].
+  ///
+  /// {@macro flutter.widgets.RestorableNum.constructor}
+  ///
+  /// See also:
+  ///
+  ///  * [RestorableBoolN] for the nullable version of this class.
+  RestorableBool(bool defaultValue) : assert(defaultValue != null), super(defaultValue);
+}
+
+/// A [RestorableProperty] that knows how to store and restore a [bool] that is
+/// nullable.
+///
+/// {@macro flutter.widgets.RestorableNum}
+class RestorableBoolN extends _RestorablePrimitiveValueN<bool?> {
+  /// Creates a [RestorableBoolN].
+  ///
+  /// {@macro flutter.widgets.RestorableNum.constructor}
+  ///
+  /// See also:
+  ///
+  ///  * [RestorableBool] for the non-nullable version of this class.
+  RestorableBoolN(bool? defaultValue) : super(defaultValue);
+}
+
+/// A base class for creating a [RestorableProperty] that stores and restores a
+/// [Listenable].
+///
+/// This class may be used to implement a [RestorableProperty] for a
+/// [Listenable], whose information it needs to store in the restoration data
+/// change whenever the [Listenable] notifies its listeners.
+///
+/// The [RestorationMixin] this property is registered with will call
+/// [toPrimitives] whenever the wrapped [Listenable] notifies its listeners to
+/// update the information that this property has stored in the restoration
+/// data.
+abstract class RestorableListenable<T extends Listenable> extends RestorableProperty<T> {
+  /// The [Listenable] stored in this property.
+  ///
+  /// A representation of the current value of the [Listenable] is stored in the
+  /// restoration data. During state restoration, the [Listenable] returned by
+  /// this getter will be restored to the state it had when the restoration data
+  /// the property is getting restored from was collected.
+  ///
+  /// The [value] can only be accessed after the property has been registered
+  /// with a [RestorationMixin] by calling
+  /// [RestorationMixin.registerForRestoration].
+  T get value {
+    assert(isRegistered);
+    return _value!;
+  }
+  T? _value;
+
+  @override
+  void initWithValue(T value) {
+    assert(value != null);
+    _value?.removeListener(notifyListeners);
+    _value = value;
+    _value!.addListener(notifyListeners);
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    _value?.removeListener(notifyListeners);
+  }
+}
+
+/// A base class for creating a [RestorableProperty] that stores and restores a
+/// [ChangeNotifier].
+///
+/// This class may be used to implement a [RestorableProperty] for a
+/// [ChangeNotifier], whose information it needs to store in the restoration
+/// data change whenever the [ChangeNotifier] notifies its listeners.
+///
+/// The [RestorationMixin] this property is registered with will call
+/// [toPrimitives] whenever the wrapped [ChangeNotifier] notifies its listeners
+/// to update the information that this property has stored in the restoration
+/// data.
+///
+/// Furthermore, the property will dispose the wrapped [ChangeNotifier] when
+/// either the property itself is disposed or its value is replaced with another
+/// [ChangeNotifier] instance.
+abstract class RestorableChangeNotifier<T extends ChangeNotifier> extends RestorableListenable<T> {
+  @override
+  void initWithValue(T value) {
+    _disposeOldValue();
+    super.initWithValue(value);
+  }
+
+  @override
+  void dispose() {
+    _disposeOldValue();
+    super.dispose();
+  }
+
+  void _disposeOldValue() {
+    if (_value != null) {
+      // Scheduling a microtask for dispose to give other entities a chance
+      // to remove their listeners first.
+      scheduleMicrotask(_value!.dispose);
+    }
+  }
+}
+
+/// A [RestorableProperty] that knows how to store and restore a
+/// [TextEditingController].
+///
+/// The [TextEditingController] is accessible via the [value] getter. During
+/// state restoration, the property will restore [TextEditingController.text] to
+/// the value it had when the restoration data it is getting restored from was
+/// collected.
+class RestorableTextEditingController extends RestorableChangeNotifier<TextEditingController> {
+  /// Creates a [RestorableTextEditingController].
+  ///
+  /// This constructor treats a null `text` argument as if it were the empty
+  /// string.
+  factory RestorableTextEditingController({String? text}) => RestorableTextEditingController.fromValue(
+    text == null ? TextEditingValue.empty : TextEditingValue(text: text),
+  );
+
+  /// Creates a [RestorableTextEditingController] from an initial
+  /// [TextEditingValue].
+  ///
+  /// This constructor treats a null `value` argument as if it were
+  /// [TextEditingValue.empty].
+  RestorableTextEditingController.fromValue(TextEditingValue value) : _initialValue = value;
+
+  final TextEditingValue _initialValue;
+
+  @override
+  TextEditingController createDefaultValue() {
+    return TextEditingController.fromValue(_initialValue);
+  }
+
+  @override
+  TextEditingController fromPrimitives(Object? data) {
+    return TextEditingController(text: data! as String);
+  }
+
+  @override
+  Object toPrimitives() {
+    return value.text;
+  }
+}
diff --git a/lib/src/widgets/router.dart b/lib/src/widgets/router.dart
new file mode 100644
index 0000000..b54a28f
--- /dev/null
+++ b/lib/src/widgets/router.dart
@@ -0,0 +1,1272 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/services.dart';
+
+import 'basic.dart';
+import 'binding.dart';
+import 'framework.dart';
+import 'navigator.dart';
+
+/// A piece of routing information.
+///
+/// The route information consists of a location string of the application and
+/// a state object that configures the application in that location.
+///
+/// This information flows two ways, from the [RouteInformationProvider] to the
+/// [Router] or from the [Router] to [RouteInformationProvider].
+///
+/// In the former case, the [RouteInformationProvider] notifies the [Router]
+/// widget when a new [RouteInformation] is available. The [Router] widget takes
+/// these information and navigates accordingly.
+///
+/// The latter case should only happen in a web application where the [Router]
+/// reports route changes back to web engine.
+class RouteInformation {
+  /// Creates a route information object.
+  ///
+  /// The arguments may be null.
+  const RouteInformation({this.location, this.state});
+
+  /// The location of the application.
+  ///
+  /// The string is usually in the format of multiple string identifiers with
+  /// slashes in between. ex: `/`, `/path`, `/path/to/the/app`.
+  ///
+  /// It is equivalent to the URL in a web application.
+  final String? location;
+
+  /// The state of the application in the [location].
+  ///
+  /// The app can have different states even in the same location. For example,
+  /// the text inside a [TextField] or the scroll position in a [ScrollView].
+  /// These widget states can be stored in the [state].
+  ///
+  /// Currently, this information is only used by Flutter on the web:
+  /// the data is stored in the browser history entry when the
+  /// [Router] reports this route information back to the web engine
+  /// through the [PlatformRouteInformationProvider]. The information
+  /// is then passed back, along with the [location], when the user
+  /// clicks the back or forward buttons.
+  ///
+  /// The state must be serializable.
+  final Object? state;
+}
+
+/// The dispatcher for opening and closing pages of an application.
+///
+/// This widget listens for routing information from the operating system (e.g.
+/// an initial route provided on app startup, a new route obtained when an
+/// intent is received, or a notification that the user hit the system back
+/// button), parses route information into data of type `T`, and then converts
+/// that data into [Page] objects that it passes to a [Navigator].
+///
+/// Each part of this process can be overridden and configured as desired.
+///
+/// The [routeInformationProvider] can be overridden to change how the name of
+/// the route is obtained. The [RouteInformationProvider.value] is used as the
+/// initial route when the [Router] is first created. Subsequent notifications
+/// from the [RouteInformationProvider] to its listeners are treated as
+/// notifications that the route information has changed.
+///
+/// The [backButtonDispatcher] can be overridden to change how back button
+/// notifications are received. This must be a [BackButtonDispatcher], which is
+/// an object where callbacks can be registered, and which can be chained so
+/// that back button presses are delegated to subsidiary routers. The callbacks
+/// are invoked to indicate that the user is trying to close the current route
+/// (by pressing the system back button); the [Router] ensures that when this
+/// callback is invoked, the message is passed to the [routerDelegate] and its
+/// result is provided back to the [backButtonDispatcher]. Some platforms don't
+/// have back buttons (e.g. iOS and desktop platforms); on those platforms this
+/// notification is never sent. Typically, the [backButtonDispatcher] for the
+/// root router is an instance of [RootBackButtonDispatcher], which uses a
+/// [WidgetsBindingObserver] to listen to the `popRoute` notifications from
+/// [SystemChannels.navigation]. Nested [Router]s typically use a
+/// [ChildBackButtonDispatcher], which must be provided the
+/// [BackButtonDispatcher] of its ancestor [Router] (available via [Router.of]).
+///
+/// The [routeInformationParser] can be overridden to change how names obtained
+/// from the [routeInformationProvider] are interpreted. It must implement the
+/// [RouteInformationParser] interface, specialized with the same type as the
+/// [Router] itself. This type, `T`, represents the data type that the
+/// [routeInformationParser] will generate.
+///
+/// The [routerDelegate] can be overridden to change how the output of the
+/// [routeInformationParser] is interpreted. It must implement the
+/// [RouterDelegate] interface, also specialized with `T`; it takes as input
+/// the data (of type `T`) from the [routeInformationParser], and is responsible
+/// for providing a navigating widget to insert into the widget tree. The
+/// [RouterDelegate] interface is also [Listenable]; notifications are taken
+/// to mean that the [Router] needs to rebuild.
+///
+/// ## Concerns regarding asynchrony
+///
+/// Some of the APIs (notably those involving [RouteInformationParser] and
+/// [RouterDelegate]) are asynchronous.
+///
+/// When developing objects implementing these APIs, if the work can be done
+/// entirely synchronously, then consider using [SynchronousFuture] for the
+/// future returned from the relevant methods. This will allow the [Router] to
+/// proceed in a completely synchronous way, which removes a number of
+/// complications.
+///
+/// Using asynchronous computation is entirely reasonable, however, and the API
+/// is designed to support it. For example, maybe a set of images need to be
+/// loaded before a route can be shown; waiting for those images to be loaded
+/// before [RouterDelegate.setNewRoutePath] returns is a reasonable approach to
+/// handle this case.
+///
+/// If an asynchronous operation is ongoing when a new one is to be started, the
+/// precise behavior will depend on the exact circumstances, as follows:
+///
+/// If the active operation is a [routeInformationParser] parsing a new route information:
+/// that operation's result, if it ever completes, will be discarded.
+///
+/// If the active operation is a [routerDelegate] handling a pop request:
+/// the previous pop is immediately completed with "false", claiming that the
+/// previous pop was not handled (this may cause the application to close).
+///
+/// If the active operation is a [routerDelegate] handling an initial route
+/// or a pushed route, the result depends on the new operation. If the new
+/// operation is a pop request, then the original operation's result, if it ever
+/// completes, will be discarded. If the new operation is a push request,
+/// however, the [routeInformationParser] will be requested to start the parsing, and
+/// only if that finishes before the original [routerDelegate] request
+/// completes will that original request's result be discarded.
+///
+/// If the identity of the [Router] widget's delegates change while an
+/// asynchronous operation is in progress, to keep matters simple, all active
+/// asynchronous operations will have their results discarded. It is generally
+/// considered unusual for these delegates to change during the lifetime of the
+/// [Router].
+///
+/// If the [Router] itself is disposed while an an asynchronous operation is in
+/// progress, all active asynchronous operations will have their results
+/// discarded also.
+///
+/// No explicit signals are provided to the [routeInformationParser] or
+/// [routerDelegate] to indicate when any of the above happens, so it is
+/// strongly recommended that [RouteInformationParser] and [RouterDelegate]
+/// implementations not perform extensive computation.
+///
+/// ## Application architectural design
+///
+/// An application can have zero, one, or many [Router] widgets, depending on
+/// its needs.
+///
+/// An application might have no [Router] widgets if it has only one "screen",
+/// or if the facilities provided by [Navigator] are sufficient. This is common
+/// for desktop applications, where subsidiary "screens" are represented using
+/// different windows rather than changing the active interface.
+///
+/// A particularly elaborate application might have multiple [Router] widgets,
+/// in a tree configuration, with the first handling the entire route parsing
+/// and making the result available for routers in the subtree. The routers in
+/// the subtree do not participate in route information parsing but merely take the
+/// result from the first router to build their sub routes.
+///
+/// Most applications only need a single [Router].
+///
+/// ## URL updates for web applications
+///
+/// In the web platform, keeping the URL in the browser's location bar up to
+/// date with the application state ensures that the browser constructs its
+/// history entry correctly, allowing its back and forward buttons to function
+/// as the user expects.
+///
+/// If an app state change leads to the [Router] rebuilding, the [Router] will
+/// retrieve the new route information from the [routerDelegate]'s
+/// [RouterDelegate.currentConfiguration] method and the
+/// [routeInformationParser]'s [RouteInformationParser.restoreRouteInformation]
+/// method. If the location in the new route information is different from the
+/// current location, the router sends the new route information to the
+/// [routeInformationProvider]'s
+/// [RouteInformationProvider.routerReportsNewRouteInformation] method. That
+/// method as implemented in [PlatformRouteInformationProvider] uses
+/// [SystemNavigator.routeInformationUpdated] to notify the engine, and through
+/// that the browser, of the new URL.
+///
+/// One can force the [Router] to report new route information to the
+/// [routeInformationProvider] (and thus the browser) even if the
+/// [RouteInformation.location] has not changed by calling the [Router.navigate]
+/// method with a callback that performs the state change. This allows one to
+/// support the browser's back and forward buttons without changing the URL. For
+/// example, the scroll position of a scroll view may be saved in the
+/// [RouteInformation.state]. Using [Router.navigate] to update the scroll
+/// position causes the browser to create a new history entry with the
+/// [RouteInformation.state] that stores this new scroll position. When the user
+/// clicks the back button, the app will go back to the previous scroll position
+/// without changing the URL in the location bar.
+///
+/// One can also force the [Router] to ignore application state changes by
+/// making those changes during a callback passed to [Router.neglect]. The
+/// [Router] will not report any route information even if it detects location
+/// change as a result of running the callback.
+///
+/// To opt out of URL updates entirely, pass null for [routeInformationProvider]
+/// and [routeInformationParser]. This is not recommended in general, but may be
+/// appropriate in the following cases:
+///
+/// * The application does not target the web platform.
+///
+/// * There are multiple router widgets in the application. Only one [Router]
+///   widget should update the URL (typically the top-most one created by the
+///   [WidgetsApp.router], [MaterialApp.router], or [CupertinoApp.router]).
+///
+/// * The application does not need to implement in-app navigation using the
+///   browser's back and forward buttons.
+///
+/// In other cases, it is strongly recommended to implement the
+/// [RouterDelegate.currentConfiguration] and
+/// [RouteInformationParser.restoreRouteInformation] APIs to provide an optimal
+/// user experience when running on the web platform.
+class Router<T> extends StatefulWidget {
+  /// Creates a router.
+  ///
+  /// The [routeInformationProvider] and [routeInformationParser] can be null if this
+  /// router does not depend on route information. A common example is a sub router
+  /// that builds its content completely based on the app state.
+  ///
+  /// If the [routeInformationProvider] is not null, the [routeInformationParser] must
+  /// also not be null.
+  ///
+  /// The [routerDelegate] must not be null.
+  const Router({
+    Key? key,
+    this.routeInformationProvider,
+    this.routeInformationParser,
+    required this.routerDelegate,
+    this.backButtonDispatcher,
+  })  : assert(
+          (routeInformationProvider == null) == (routeInformationParser == null),
+          'Both routeInformationProvider and routeInformationParser must be provided '
+          'if this router parses route information. Otherwise, they should both '
+          'be null.'
+        ),
+        assert(routerDelegate != null),
+        super(key: key);
+
+  /// The route information provider for the router.
+  ///
+  /// The value at the time of first build will be used as the initial route.
+  /// The [Router] listens to this provider and rebuilds with new names when
+  /// it notifies.
+  ///
+  /// This can be null if this router does not rely on the route information
+  /// to build its content. In such case, the [routeInformationParser] can also be
+  /// null.
+  final RouteInformationProvider? routeInformationProvider;
+
+  /// The route information parser for the router.
+  ///
+  /// When the [Router] gets a new route information from the [routeInformationProvider],
+  /// the [Router] uses this delegate to parse the route information and produce a
+  /// configuration. The configuration will be used by [routerDelegate] and
+  /// eventually rebuilds the [Router] widget.
+  ///
+  /// Since this delegate is the primary consumer of the [routeInformationProvider],
+  /// it must not be null if [routeInformationProvider] is not null.
+  final RouteInformationParser<T>? routeInformationParser;
+
+  /// The router delegate for the router.
+  ///
+  /// This delegate consumes the configuration from [routeInformationParser] and
+  /// builds a navigating widget for the [Router].
+  ///
+  /// It is also the primary respondent for the [backButtonDispatcher]. The
+  /// [Router] relies on [RouterDelegate.popRoute] to handle the back
+  /// button.
+  ///
+  /// If the [RouterDelegate.currentConfiguration] returns a non-null object,
+  /// this [Router] will opt for URL updates.
+  final RouterDelegate<T> routerDelegate;
+
+  /// The back button dispatcher for the router.
+  ///
+  /// The two common alternatives are the [RootBackButtonDispatcher] for root
+  /// router, or the [ChildBackButtonDispatcher] for other routers.
+  final BackButtonDispatcher? backButtonDispatcher;
+
+  /// Retrieves the immediate [Router] ancestor from the given context.
+  ///
+  /// This method provides access to the delegates in the [Router]. For example,
+  /// this can be used to access the [backButtonDispatcher] of the parent router
+  /// when creating a [ChildBackButtonDispatcher] for a nested [Router].
+  ///
+  /// If no [Router] ancestor exists for the given context, this will assert in
+  /// debug mode, and throw an exception in release mode.
+  ///
+  /// See also:
+  ///
+  ///  * [maybeOf], which is a similar function, but it will return null instead
+  ///    of throwing an exception if no [Router] ancestor exists.
+  static Router<dynamic> of(BuildContext context) {
+    final _RouterScope? scope = context.dependOnInheritedWidgetOfExactType<_RouterScope>();
+    assert(() {
+      if (scope == null) {
+        throw FlutterError(
+          'Router operation requested with a context that does not include a Router.\n'
+          'The context used to retrieve the Router must be that of a widget that '
+          'is a descendant of a Router widget.'
+        );
+      }
+      return true;
+    }());
+    return scope!.routerState.widget;
+  }
+
+  /// Retrieves the immediate [Router] ancestor from the given context.
+  ///
+  /// This method provides access to the delegates in the [Router]. For example,
+  /// this can be used to access the [backButtonDispatcher] of the parent router
+  /// when creating a [ChildBackButtonDispatcher] for a nested [Router].
+  ///
+  /// If no `Router` ancestor exists for the given context, this will return
+  /// null.
+  ///
+  /// See also:
+  ///
+  ///  * [of], a similar method that returns a non-nullable value, and will
+  ///    throw if no [Router] ancestor exists.
+  static Router<dynamic>? maybeOf(BuildContext context) {
+    final _RouterScope? scope = context.dependOnInheritedWidgetOfExactType<_RouterScope>();
+    return scope?.routerState.widget;
+  }
+
+  /// Forces the [Router] to run the [callback] and reports the route
+  /// information back to the engine.
+  ///
+  /// The web application relies on the [Router] to report new route information
+  /// in order to create browser history entry. The [Router] will only report
+  /// them if it detects the [RouteInformation.location] changes. Use this
+  /// method if you want the [Router] to report the route information even if
+  /// the location does not change. This can be useful when you want to
+  /// support the browser backward and forward button without changing the URL.
+  ///
+  /// For example, you can store certain state such as the scroll position into
+  /// the [RouteInformation.state]. If you use this method to update the
+  /// scroll position multiple times with the same URL, the browser will create
+  /// a stack of new history entries with the same URL but different
+  /// [RouteInformation.state]s that store the new scroll positions. If the user
+  /// click the backward button in the browser, the browser will restore the
+  /// scroll positions saved in history entries without changing the URL.
+  ///
+  /// See also:
+  ///
+  ///  * [Router]: see the "URL updates for web applications" section for more
+  ///    information about route information reporting.
+  ///  * [neglect]: which forces the [Router] to not report the route
+  ///    information even if location does change.
+  static void navigate(BuildContext context, VoidCallback callback) {
+    final _RouterScope scope = context
+      .getElementForInheritedWidgetOfExactType<_RouterScope>()!
+      .widget as _RouterScope;
+    scope.routerState._setStateWithExplicitReportStatus(_IntentionToReportRouteInformation.must, callback);
+  }
+
+  /// Forces the [Router] to to run the [callback] without reporting the route
+  /// information back to the engine.
+  ///
+  /// Use this method if you don't want the [Router] to report the new route
+  /// information even if it detects changes as a result of running the
+  /// [callback].
+  ///
+  /// The web application relies on the [Router] to report new route information
+  /// in order to create browser history entry. The [Router] will report them
+  /// automatically if it detects the [RouteInformation.location] changes. You
+  /// can use this method if you want to navigate to a new route without
+  /// creating the browser history entry.
+  ///
+  /// See also:
+  ///
+  ///  * [Router]: see the "URL updates for web applications" section for more
+  ///    information about route information reporting.
+  ///  * [navigate]: which forces the [Router] to report the route information
+  ///    even if location does not change.
+  static void neglect(BuildContext context, VoidCallback callback) {
+    final _RouterScope scope = context
+      .getElementForInheritedWidgetOfExactType<_RouterScope>()!
+      .widget as _RouterScope;
+    scope.routerState._setStateWithExplicitReportStatus(_IntentionToReportRouteInformation.ignore, callback);
+  }
+
+  @override
+  State<Router<T>> createState() => _RouterState<T>();
+}
+
+typedef _AsyncPassthrough<Q> = Future<Q> Function(Q);
+
+// Whether to report the route information in this build cycle.
+enum _IntentionToReportRouteInformation {
+  // We haven't receive any signal on whether to report.
+  none,
+  // Report if route information changes.
+  maybe,
+  // Report regardless of route information changes.
+  must,
+  // Don't report regardless of route information changes.
+  ignore,
+}
+
+class _RouterState<T> extends State<Router<T>> {
+  Object? _currentRouteInformationParserTransaction;
+  Object? _currentRouterDelegateTransaction;
+  late _IntentionToReportRouteInformation _currentIntentionToReport;
+
+  @override
+  void initState() {
+    super.initState();
+    widget.routeInformationProvider?.addListener(_handleRouteInformationProviderNotification);
+    widget.backButtonDispatcher?.addCallback(_handleBackButtonDispatcherNotification);
+    widget.routerDelegate.addListener(_handleRouterDelegateNotification);
+    _currentIntentionToReport = _IntentionToReportRouteInformation.none;
+    if (widget.routeInformationProvider != null) {
+      _processInitialRoute();
+    }
+  }
+
+  bool _routeInformationReportingTaskScheduled = false;
+
+  String? _lastSeenLocation;
+
+  void _scheduleRouteInformationReportingTask() {
+    if (_routeInformationReportingTaskScheduled)
+      return;
+    assert(_currentIntentionToReport != _IntentionToReportRouteInformation.none);
+    _routeInformationReportingTaskScheduled = true;
+    SchedulerBinding.instance!.addPostFrameCallback(_reportRouteInformation);
+  }
+
+  void _reportRouteInformation(Duration timestamp) {
+    assert(_routeInformationReportingTaskScheduled);
+    _routeInformationReportingTaskScheduled = false;
+
+    switch (_currentIntentionToReport) {
+      case _IntentionToReportRouteInformation.none:
+        assert(false);
+        return;
+
+      case _IntentionToReportRouteInformation.ignore:
+        // In the ignore case, we still want to update the _lastSeenLocation.
+        final RouteInformation? routeInformation = _retrieveNewRouteInformation();
+        if (routeInformation != null) {
+          _lastSeenLocation = routeInformation.location;
+        }
+        _currentIntentionToReport = _IntentionToReportRouteInformation.none;
+        return;
+
+      case _IntentionToReportRouteInformation.maybe:
+        final RouteInformation? routeInformation = _retrieveNewRouteInformation();
+        if (routeInformation != null) {
+          if (_lastSeenLocation != routeInformation.location) {
+            widget.routeInformationProvider!.routerReportsNewRouteInformation(routeInformation);
+            _lastSeenLocation = routeInformation.location;
+          }
+        }
+        _currentIntentionToReport = _IntentionToReportRouteInformation.none;
+        return;
+
+      case _IntentionToReportRouteInformation.must:
+        final RouteInformation? routeInformation = _retrieveNewRouteInformation();
+        if (routeInformation != null) {
+          widget.routeInformationProvider!.routerReportsNewRouteInformation(routeInformation);
+          _lastSeenLocation = routeInformation.location;
+        }
+        _currentIntentionToReport = _IntentionToReportRouteInformation.none;
+        return;
+    }
+  }
+
+  RouteInformation? _retrieveNewRouteInformation() {
+    final T? configuration = widget.routerDelegate.currentConfiguration;
+    if (configuration == null)
+      return null;
+    final RouteInformation? routeInformation = widget.routeInformationParser!.restoreRouteInformation(configuration);
+    assert((){
+      if (routeInformation == null) {
+        FlutterError.reportError(
+          const FlutterErrorDetails(
+            exception:
+              'Router.routeInformationParser returns a null RouteInformation. '
+              'If you opt for route information reporting, the '
+              'routeInformationParser must not report null for a given '
+              'configuration.'
+          ),
+        );
+      }
+      return true;
+    }());
+    return routeInformation;
+  }
+
+  void _setStateWithExplicitReportStatus(
+    _IntentionToReportRouteInformation status,
+    VoidCallback fn,
+  ) {
+    assert(status != null);
+    assert(status.index >= _IntentionToReportRouteInformation.must.index);
+    assert(() {
+      if (_currentIntentionToReport.index >= _IntentionToReportRouteInformation.must.index &&
+          _currentIntentionToReport != status) {
+        FlutterError.reportError(
+          const FlutterErrorDetails(
+            exception:
+              'Both Router.navigate and Router.neglect have been called in this '
+              'build cycle, and the Router cannot decide whether to report the '
+              'route information. Please make sure only one of them is called '
+              'within the same build cycle.'
+          ),
+        );
+      }
+      return true;
+    }());
+    _currentIntentionToReport = status;
+    _scheduleRouteInformationReportingTask();
+    fn();
+  }
+
+  void _maybeNeedToReportRouteInformation() {
+    _currentIntentionToReport = _currentIntentionToReport != _IntentionToReportRouteInformation.none
+      ? _currentIntentionToReport
+      : _IntentionToReportRouteInformation.maybe;
+    _scheduleRouteInformationReportingTask();
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _maybeNeedToReportRouteInformation();
+  }
+
+  @override
+  void didUpdateWidget(Router<T> oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.routeInformationProvider != oldWidget.routeInformationProvider ||
+        widget.backButtonDispatcher != oldWidget.backButtonDispatcher ||
+        widget.routeInformationParser != oldWidget.routeInformationParser ||
+        widget.routerDelegate != oldWidget.routerDelegate) {
+      _currentRouteInformationParserTransaction = Object();
+      _currentRouterDelegateTransaction = Object();
+    }
+    if (widget.routeInformationProvider != oldWidget.routeInformationProvider) {
+      oldWidget.routeInformationProvider?.removeListener(_handleRouteInformationProviderNotification);
+      widget.routeInformationProvider?.addListener(_handleRouteInformationProviderNotification);
+      if (oldWidget.routeInformationProvider?.value != widget.routeInformationProvider?.value) {
+        _handleRouteInformationProviderNotification();
+      }
+    }
+    if (widget.backButtonDispatcher != oldWidget.backButtonDispatcher) {
+      oldWidget.backButtonDispatcher?.removeCallback(_handleBackButtonDispatcherNotification);
+      widget.backButtonDispatcher?.addCallback(_handleBackButtonDispatcherNotification);
+    }
+    if (widget.routerDelegate != oldWidget.routerDelegate) {
+      oldWidget.routerDelegate.removeListener(_handleRouterDelegateNotification);
+      widget.routerDelegate.addListener(_handleRouterDelegateNotification);
+      _maybeNeedToReportRouteInformation();
+    }
+  }
+
+  @override
+  void dispose() {
+    widget.routeInformationProvider?.removeListener(_handleRouteInformationProviderNotification);
+    widget.backButtonDispatcher?.removeCallback(_handleBackButtonDispatcherNotification);
+    widget.routerDelegate.removeListener(_handleRouterDelegateNotification);
+    _currentRouteInformationParserTransaction = null;
+    _currentRouterDelegateTransaction = null;
+    super.dispose();
+  }
+
+  void _processInitialRoute() {
+    _currentRouteInformationParserTransaction = Object();
+    _currentRouterDelegateTransaction = Object();
+    _lastSeenLocation = widget.routeInformationProvider!.value!.location;
+    widget.routeInformationParser!
+      .parseRouteInformation(widget.routeInformationProvider!.value!)
+      .then<T>(_verifyRouteInformationParserStillCurrent(_currentRouteInformationParserTransaction, widget))
+      .then<void>(widget.routerDelegate.setInitialRoutePath)
+      .then<void>(_verifyRouterDelegatePushStillCurrent(_currentRouterDelegateTransaction, widget))
+      .then<void>(_rebuild);
+  }
+
+  void _handleRouteInformationProviderNotification() {
+    _currentRouteInformationParserTransaction = Object();
+    _currentRouterDelegateTransaction = Object();
+    _lastSeenLocation = widget.routeInformationProvider!.value!.location;
+    widget.routeInformationParser!
+      .parseRouteInformation(widget.routeInformationProvider!.value!)
+      .then<T>(_verifyRouteInformationParserStillCurrent(_currentRouteInformationParserTransaction, widget))
+      .then<void>(widget.routerDelegate.setNewRoutePath)
+      .then<void>(_verifyRouterDelegatePushStillCurrent(_currentRouterDelegateTransaction, widget))
+      .then<void>(_rebuild);
+  }
+
+  Future<bool> _handleBackButtonDispatcherNotification() {
+    _currentRouteInformationParserTransaction = Object();
+    _currentRouterDelegateTransaction = Object();
+    return widget.routerDelegate
+      .popRoute()
+      .then<bool>(_verifyRouterDelegatePopStillCurrent(_currentRouterDelegateTransaction, widget))
+      .then<bool>((bool data) {
+        _rebuild();
+        _maybeNeedToReportRouteInformation();
+        return SynchronousFuture<bool>(data);
+      });
+  }
+
+  static final Future<dynamic> _never = Completer<dynamic>().future; // won't ever complete
+
+  _AsyncPassthrough<T> _verifyRouteInformationParserStillCurrent(Object? transaction, Router<T> originalWidget) {
+    return (T data) {
+      if (transaction == _currentRouteInformationParserTransaction &&
+          widget.routeInformationProvider == originalWidget.routeInformationProvider &&
+          widget.backButtonDispatcher == originalWidget.backButtonDispatcher &&
+          widget.routeInformationParser == originalWidget.routeInformationParser &&
+          widget.routerDelegate == originalWidget.routerDelegate) {
+        return SynchronousFuture<T>(data);
+      }
+      return _never as Future<T>;
+    };
+  }
+
+  _AsyncPassthrough<void> _verifyRouterDelegatePushStillCurrent(Object? transaction, Router<T> originalWidget) {
+    return (void data) {
+      if (transaction == _currentRouterDelegateTransaction &&
+          widget.routeInformationProvider == originalWidget.routeInformationProvider &&
+          widget.backButtonDispatcher == originalWidget.backButtonDispatcher &&
+          widget.routeInformationParser == originalWidget.routeInformationParser &&
+          widget.routerDelegate == originalWidget.routerDelegate)
+        return SynchronousFuture<void>(data);
+      return _never;
+    };
+  }
+
+  _AsyncPassthrough<bool> _verifyRouterDelegatePopStillCurrent(Object? transaction, Router<T> originalWidget) {
+    return (bool data) {
+      if (transaction == _currentRouterDelegateTransaction &&
+          widget.routeInformationProvider == originalWidget.routeInformationProvider &&
+          widget.backButtonDispatcher == originalWidget.backButtonDispatcher &&
+          widget.routeInformationParser == originalWidget.routeInformationParser &&
+          widget.routerDelegate == originalWidget.routerDelegate) {
+        return SynchronousFuture<bool>(data);
+      }
+      // A rebuilt was trigger from a different source. Returns true to
+      // prevent bubbling.
+      return SynchronousFuture<bool>(true);
+    };
+  }
+
+  Future<void> _rebuild([void value]) {
+    setState(() {/* routerDelegate is ready to rebuild */});
+    return SynchronousFuture<void>(value);
+  }
+
+  void _handleRouterDelegateNotification() {
+    setState(() {/* routerDelegate wants to rebuild */});
+    _maybeNeedToReportRouteInformation();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _RouterScope(
+      routeInformationProvider: widget.routeInformationProvider,
+      backButtonDispatcher: widget.backButtonDispatcher,
+      routeInformationParser: widget.routeInformationParser,
+      routerDelegate: widget.routerDelegate,
+      routerState: this,
+      child: Builder(
+        // We use a Builder so that the build method below
+        // will have a BuildContext that contains the _RouterScope.
+        builder: widget.routerDelegate.build,
+      ),
+    );
+  }
+}
+
+class _RouterScope extends InheritedWidget {
+  const _RouterScope({
+    Key? key,
+    required this.routeInformationProvider,
+    required this.backButtonDispatcher,
+    required this.routeInformationParser,
+    required this.routerDelegate,
+    required this.routerState,
+    required Widget child,
+  })  : assert(routeInformationProvider == null || routeInformationParser != null),
+        assert(routerDelegate != null),
+        assert(routerState != null),
+        super(key: key, child: child);
+
+  final ValueListenable<RouteInformation?>? routeInformationProvider;
+  final BackButtonDispatcher? backButtonDispatcher;
+  final RouteInformationParser<dynamic>? routeInformationParser;
+  final RouterDelegate<dynamic> routerDelegate;
+  final _RouterState<dynamic> routerState;
+
+  @override
+  bool updateShouldNotify(_RouterScope oldWidget) {
+    return routeInformationProvider != oldWidget.routeInformationProvider ||
+           backButtonDispatcher != oldWidget.backButtonDispatcher ||
+           routeInformationParser != oldWidget.routeInformationParser ||
+           routerDelegate != oldWidget.routerDelegate ||
+           routerState != oldWidget.routerState;
+  }
+}
+
+/// A class that can be extended or mixed in that invokes a single callback,
+/// which then returns a value.
+///
+/// While multiple callbacks can be registered, when a notification is
+/// dispatched there must be only a single callback. The return values of
+/// multiple callbacks are not aggregated.
+///
+/// `T` is the return value expected from the callback.
+///
+/// See also:
+///
+///  * [Listenable] and its subclasses, which provide a similar mechanism for
+///    one-way signalling.
+class _CallbackHookProvider<T> {
+  final ObserverList<ValueGetter<T>> _callbacks = ObserverList<ValueGetter<T>>();
+
+  /// Whether a callback is currently registered.
+  @protected
+  bool get hasCallbacks => _callbacks.isNotEmpty;
+
+  /// Register the callback to be called when the object changes.
+  ///
+  /// If other callbacks have already been registered, they must be removed
+  /// (with [removeCallback]) before the callback is next called.
+  void addCallback(ValueGetter<T> callback) => _callbacks.add(callback);
+
+  /// Remove a previously registered callback.
+  ///
+  /// If the given callback is not registered, the call is ignored.
+  void removeCallback(ValueGetter<T> callback) => _callbacks.remove(callback);
+
+  /// Calls the (single) registered callback and returns its result.
+  ///
+  /// If no callback is registered, or if the callback throws, returns
+  /// `defaultValue`.
+  ///
+  /// Call this method whenever the callback is to be invoked. If there is more
+  /// than one callback registered, this method will throw a [StateError].
+  ///
+  /// Exceptions thrown by callbacks will be caught and reported using
+  /// [FlutterError.reportError].
+  @protected
+  T invokeCallback(T defaultValue) {
+    if (_callbacks.isEmpty)
+      return defaultValue;
+    try {
+      return _callbacks.single();
+    } catch (exception, stack) {
+      FlutterError.reportError(FlutterErrorDetails(
+        exception: exception,
+        stack: stack,
+        library: 'widget library',
+        context: ErrorDescription('while invoking the callback for $runtimeType'),
+        informationCollector: () sync* {
+          yield DiagnosticsProperty<_CallbackHookProvider<T>>(
+            'The $runtimeType that invoked the callback was:',
+            this,
+            style: DiagnosticsTreeStyle.errorProperty,
+          );
+        },
+      ));
+      return defaultValue;
+    }
+  }
+}
+
+/// Report to a [Router] when the user taps the back button on platforms that
+/// support back buttons (such as Android).
+///
+/// When [Router] widgets are nested, consider using a
+/// [ChildBackButtonDispatcher], passing it the parent [BackButtonDispatcher],
+/// so that the back button requests get dispatched to the appropriate [Router].
+/// To make this work properly, it's important that whenever a [Router] thinks
+/// it should get the back button messages (e.g. after the user taps inside it),
+/// it calls [takePriority] on its [BackButtonDispatcher] (or
+/// [ChildBackButtonDispatcher]) instance.
+///
+/// The class takes a single callback, which must return a [Future<bool>]. The
+/// callback's semantics match [WidgetsBindingObserver.didPopRoute]'s, namely,
+/// the callback should return a future that completes to true if it can handle
+/// the pop request, and a future that completes to false otherwise.
+abstract class BackButtonDispatcher extends _CallbackHookProvider<Future<bool>> {
+  late final LinkedHashSet<ChildBackButtonDispatcher> _children =
+    <ChildBackButtonDispatcher>{} as LinkedHashSet<ChildBackButtonDispatcher>;
+
+  @override
+  bool get hasCallbacks => super.hasCallbacks || (_children.isNotEmpty);
+
+  /// Handles a pop route request.
+  ///
+  /// This method prioritizes the children list in reverse order and calls
+  /// [ChildBackButtonDispatcher.notifiedByParent] on them. If any of them
+  /// handles the request (by returning a future with true), it exits this
+  /// method by returning this future. Otherwise, it keeps moving on to the next
+  /// child until a child handles the request. If none of the children handles
+  /// the request, this back button dispatcher will then try to handle the request
+  /// by itself. This back button dispatcher handles the request by notifying the
+  /// router which in turn calls the [RouterDelegate.popRoute] and returns its
+  /// result.
+  ///
+  /// To decide whether this back button dispatcher will handle the pop route
+  /// request, you can override the [RouterDelegate.popRoute] of the router
+  /// delegate you pass into the router with this back button dispatcher to
+  /// return a future of true or false.
+  @override
+  Future<bool> invokeCallback(Future<bool> defaultValue) {
+    if (_children.isNotEmpty) {
+      final List<ChildBackButtonDispatcher> children = _children.toList();
+      int childIndex = children.length - 1;
+
+      Future<bool> notifyNextChild(bool result) {
+        // If the previous child handles the callback, we return the result.
+        if (result)
+          return SynchronousFuture<bool>(result);
+        // If the previous child did not handle the callback, we ask the next
+        // child to handle the it.
+        if (childIndex > 0) {
+          childIndex -= 1;
+          return children[childIndex]
+            .notifiedByParent(defaultValue)
+            .then<bool>(notifyNextChild);
+        }
+        // If none of the child handles the callback, the parent will then handle it.
+        return super.invokeCallback(defaultValue);
+      }
+
+      return children[childIndex]
+        .notifiedByParent(defaultValue)
+        .then<bool>(notifyNextChild);
+    }
+    return super.invokeCallback(defaultValue);
+  }
+
+  /// Creates a [ChildBackButtonDispatcher] that is a direct descendant of this
+  /// back button dispatcher.
+  ///
+  /// To participate in handling the pop route request, call the [takePriority]
+  /// on the [ChildBackButtonDispatcher] created from this method.
+  ///
+  /// When the pop route request is handled by this back button dispatcher, it
+  /// propagate the request to its direct descendants that have called the
+  /// [takePriority] method. If there are multiple candidates, the latest one
+  /// that called the [takePriority] wins the right to handle the request. If
+  /// the latest one does not handle the request (by returning a future of
+  /// false in [ChildBackButtonDispatcher.notifiedByParent]), the second latest
+  /// one will then have the right to handle the request. This dispatcher
+  /// continues finding the next candidate until there are no more candidates
+  /// and finally handles the request itself.
+  ChildBackButtonDispatcher createChildBackButtonDispatcher() {
+    return ChildBackButtonDispatcher(this);
+  }
+
+  /// Make this [BackButtonDispatcher] take priority among its peers.
+  ///
+  /// This has no effect when a [BackButtonDispatcher] has no parents and no
+  /// children. If a [BackButtonDispatcher] does have parents or children,
+  /// however, it causes this object to be the one to dispatch the notification
+  /// when the parent would normally notify its callback.
+  ///
+  /// The [BackButtonDispatcher] must have a listener registered before it can
+  /// be told to take priority.
+  void takePriority() => _children.clear();
+
+  /// Mark the given child as taking priority over this object and the other
+  /// children.
+  ///
+  /// This causes [invokeCallback] to defer to the given child instead of
+  /// calling this object's callback.
+  ///
+  /// Children are stored in a list, so that if the current child is removed
+  /// using [forget], a previous child will return to take its place. When
+  /// [takePriority] is called, the list is cleared.
+  ///
+  /// Calling this again without first calling [forget] moves the child back to
+  /// the head of the list.
+  ///
+  /// The [BackButtonDispatcher] must have a listener registered before it can
+  /// be told to defer to a child.
+  void deferTo(ChildBackButtonDispatcher child) {
+    assert(hasCallbacks);
+    _children.remove(child); // child may or may not be in the set already
+    _children.add(child);
+  }
+
+  /// Causes the given child to be removed from the list of children to which
+  /// this object might defer, as if [deferTo] had never been called for that
+  /// child.
+  ///
+  /// This should only be called once per child, even if [deferTo] was called
+  /// multiple times for that child.
+  ///
+  /// If no children are left in the list, this object will stop deferring to
+  /// its children. (This is not the same as calling [takePriority], since, if
+  /// this object itself is a [ChildBackButtonDispatcher], [takePriority] would
+  /// additionally attempt to claim priority from its parent, whereas removing
+  /// the last child does not.)
+  void forget(ChildBackButtonDispatcher child) => _children.remove(child);
+}
+
+/// The default implementation of back button dispatcher for the root router.
+///
+/// This dispatcher listens to platform pop route notifications. When the
+/// platform wants to pop the current route, this dispatcher calls the
+/// [BackButtonDispatcher.invokeCallback] method to handle the request.
+class RootBackButtonDispatcher extends BackButtonDispatcher with WidgetsBindingObserver {
+  /// Create a root back button dispatcher.
+  RootBackButtonDispatcher();
+
+  @override
+  void addCallback(ValueGetter<Future<bool>> callback) {
+    if (!hasCallbacks)
+      WidgetsBinding.instance!.addObserver(this);
+    super.addCallback(callback);
+  }
+
+  @override
+  void removeCallback(ValueGetter<Future<bool>> callback) {
+    super.removeCallback(callback);
+    if (!hasCallbacks)
+      WidgetsBinding.instance!.removeObserver(this);
+  }
+
+  @override
+  Future<bool> didPopRoute() => invokeCallback(Future<bool>.value(false));
+}
+
+/// A variant of [BackButtonDispatcher] which listens to notifications from a
+/// parent back button dispatcher, and can take priority from its parent for the
+/// handling of such notifications.
+///
+/// Useful when [Router]s are being nested within each other.
+///
+/// Use [Router.of] to obtain a reference to the nearest ancestor [Router], from
+/// which the [Router.backButtonDispatcher] can be found, and then used as the
+/// [parent] of the [ChildBackButtonDispatcher].
+class ChildBackButtonDispatcher extends BackButtonDispatcher {
+  /// Creates a back button dispatcher that acts as the child of another.
+  ///
+  /// The [parent] must not be null.
+  ChildBackButtonDispatcher(this.parent) : assert(parent != null);
+
+  /// The back button dispatcher that this object will attempt to take priority
+  /// over when [takePriority] is called.
+  ///
+  /// The parent must have a listener registered before this child object can
+  /// have its [takePriority] or [deferTo] methods used.
+  final BackButtonDispatcher parent;
+
+  /// The parent of this child back button dispatcher decide to let this
+  /// child to handle the invoke the  callback request in
+  /// [BackButtonDispatcher.invokeCallback].
+  ///
+  /// Return a boolean future with true if this child will handle the request;
+  /// otherwise, return a boolean future with false.
+  @protected
+  Future<bool> notifiedByParent(Future<bool> defaultValue) {
+    return invokeCallback(defaultValue);
+  }
+
+  @override
+  void takePriority() {
+    parent.deferTo(this);
+    super.takePriority();
+  }
+
+  @override
+  void deferTo(ChildBackButtonDispatcher child) {
+    assert(hasCallbacks);
+    parent.deferTo(this);
+    super.deferTo(child);
+  }
+
+  @override
+  void removeCallback(ValueGetter<Future<bool>> callback) {
+    super.removeCallback(callback);
+    if (!hasCallbacks)
+      parent.forget(this);
+  }
+}
+
+/// A delegate that is used by the [Router] widget to parse a route information
+/// into a configuration of type T.
+///
+/// This delegate is used when the [Router] widget is first built with initial
+/// route information from [Router.routeInformationProvider] and any subsequent
+/// new route notifications from it. The [Router] widget calls the [parseRouteInformation]
+/// with the route information from [Router.routeInformationProvider].
+abstract class RouteInformationParser<T> {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const RouteInformationParser();
+
+  /// Converts the given route information into parsed data to pass to a
+  /// [RouterDelegate].
+  ///
+  /// The method should return a future which completes when the parsing is
+  /// complete. The parsing may be asynchronous if, e.g., the parser needs to
+  /// communicate with the OEM thread to obtain additional data about the route.
+  ///
+  /// Consider using a [SynchronousFuture] if the result can be computed
+  /// synchronously, so that the [Router] does not need to wait for the next
+  /// microtask to pass the data to the [RouterDelegate].
+  Future<T> parseRouteInformation(RouteInformation routeInformation);
+
+  /// Restore the route information from the given configuration.
+  ///
+  /// This may return null, in which case the browser history will not be updated.
+  /// See [Router]'s documentation for details.
+  ///
+  /// The [parseRouteInformation] method must produce an equivalent
+  /// configuration when passed this method's return value.
+  RouteInformation? restoreRouteInformation(T configuration) => null;
+}
+
+/// A delegate that is used by the [Router] widget to build and configure a
+/// navigating widget.
+///
+/// This delegate is the core piece of the [Router] widget. It responds to
+/// push route and pop route intents from the engine and notifies the [Router]
+/// to rebuild. It also acts as a builder for the [Router] widget and builds a
+/// navigating widget, typically a [Navigator], when the [Router] widget
+/// builds.
+///
+/// When the engine pushes a new route, the route information is parsed by the
+/// [RouteInformationParser] to produce a configuration of type T. The router
+/// delegate receives the configuration through [setInitialRoutePath] or
+/// [setNewRoutePath] to configure itself and builds the latest navigating
+/// widget when asked ([build]).
+///
+/// When implementing subclasses, consider defining a [Listenable] app state object to be
+/// used for building the navigating widget. The router delegate would update
+/// the app state accordingly and notify its own listeners when the app state has
+/// changed and when it receive route related engine intents (e.g.
+/// [setNewRoutePath], [setInitialRoutePath], or [popRoute]).
+///
+/// All subclass must implement [setNewRoutePath], [popRoute], and [build].
+///
+/// See also:
+///
+///  * [RouteInformationParser], which is responsible for parsing the route
+///    information to a configuration before passing in to router delegate.
+///  * [Router], which is the widget that wires all the delegates together to
+///    provide a fully functional routing solution.
+abstract class RouterDelegate<T> extends Listenable {
+  /// Called by the [Router] at startup with the structure that the
+  /// [RouteInformationParser] obtained from parsing the initial route.
+  ///
+  /// This should configure the [RouterDelegate] so that when [build] is
+  /// invoked, it will create a widget tree that matches the initial route.
+  ///
+  /// By default, this method forwards the [configuration] to [setNewRoutePath].
+  ///
+  /// Consider using a [SynchronousFuture] if the result can be computed
+  /// synchronously, so that the [Router] does not need to wait for the next
+  /// microtask to schedule a build.
+  Future<void> setInitialRoutePath(T configuration) {
+    return setNewRoutePath(configuration);
+  }
+
+  /// Called by the [Router] when the [Router.routeInformationProvider] reports that a
+  /// new route has been pushed to the application by the operating system.
+  ///
+  /// Consider using a [SynchronousFuture] if the result can be computed
+  /// synchronously, so that the [Router] does not need to wait for the next
+  /// microtask to schedule a build.
+  Future<void> setNewRoutePath(T configuration);
+
+  /// Called by the [Router] when the [Router.backButtonDispatcher] reports that
+  /// the operating system is requesting that the current route be popped.
+  ///
+  /// The method should return a boolean [Future] to indicate whether this
+  /// delegate handles the request. Returning false will cause the entire app
+  /// to be popped.
+  ///
+  /// Consider using a [SynchronousFuture] if the result can be computed
+  /// synchronously, so that the [Router] does not need to wait for the next
+  /// microtask to schedule a build.
+  Future<bool> popRoute();
+
+  /// Called by the [Router] when it detects a route information may have
+  /// changed as a result of rebuild.
+  ///
+  /// If this getter returns non-null, the [Router] will start to report new
+  /// route information back to the engine. In web applications, the new
+  /// route information is used for populating browser history in order to
+  /// support the forward and the backward buttons.
+  ///
+  /// When overriding this method, the configuration returned by this getter
+  /// must be able to construct the current app state and build the widget
+  /// with the same configuration in the [build] method if it is passed back
+  /// to the the [setNewRoutePath]. Otherwise, the browser backward and forward
+  /// buttons will not work properly.
+  ///
+  /// By default, this getter returns null, which prevents the [Router] from
+  /// reporting the route information. To opt in, a subclass can override this
+  /// getter to return the current configuration.
+  ///
+  /// At most one [Router] can opt in to route information reporting. Typically,
+  /// only the top-most [Router] created by [WidgetsApp.router] should opt for
+  /// route information reporting.
+  T? get currentConfiguration => null;
+
+  /// Called by the [Router] to obtain the widget tree that represents the
+  /// current state.
+  ///
+  /// This is called whenever the [setInitialRoutePath] method's future
+  /// completes, the [setNewRoutePath] method's future completes with the value
+  /// true, the [popRoute] method's future completes with the value true, or
+  /// this object notifies its clients (see the [Listenable] interface, which
+  /// this interface includes). In addition, it may be called at other times. It
+  /// is important, therefore, that the methods above do not update the state
+  /// that the [build] method uses before they complete their respective
+  /// futures.
+  ///
+  /// Typically this method returns a suitably-configured [Navigator]. If you do
+  /// plan to create a navigator, consider using the
+  /// [PopNavigatorRouterDelegateMixin].
+  ///
+  /// This method must not return null.
+  ///
+  /// The `context` is the [Router]'s build context.
+  Widget build(BuildContext context);
+}
+
+/// A route information provider that provides route information for the
+/// [Router] widget
+///
+/// This provider is responsible for handing the route information through [value]
+/// getter and notifies listeners, typically the [Router] widget, when a new
+/// route information is available.
+///
+/// When the router opts for route information reporting (by overriding the
+/// [RouterDelegate.currentConfiguration] to return non-null), override the
+/// [routerReportsNewRouteInformation] method to process the route information.
+///
+/// See also:
+///
+///  * [PlatformRouteInformationProvider], which wires up the itself with the
+///    [WidgetsBindingObserver.didPushRoute] to propagate platform push route
+///    intent to the [Router] widget, as well as reports new route information
+///    from the [Router] back to the engine by overriding the
+///    [routerReportsNewRouteInformation].
+abstract class RouteInformationProvider extends ValueListenable<RouteInformation?> {
+  /// A callback called when the [Router] widget detects any navigation event
+  /// due to state changes.
+  ///
+  /// The subclasses can override this method to update theirs values or trigger
+  /// other side effects. For example, the [PlatformRouteInformationProvider]
+  /// overrides this method to report the route information back to the engine.
+  ///
+  /// The [routeInformation] is the new route information after the navigation
+  /// event.
+  void routerReportsNewRouteInformation(RouteInformation routeInformation) {}
+}
+
+/// The route information provider that propagates the platform route information changes.
+///
+/// This provider also reports the new route information from the [Router] widget
+/// back to engine using message channel method, the
+/// [SystemNavigator.routeInformationUpdated].
+class PlatformRouteInformationProvider extends RouteInformationProvider with WidgetsBindingObserver, ChangeNotifier {
+  /// Create a platform route information provider.
+  ///
+  /// Use the [initialRouteInformation] to set the default route information for this
+  /// provider.
+  PlatformRouteInformationProvider({
+    RouteInformation? initialRouteInformation
+  }) : _value = initialRouteInformation;
+
+  @override
+  void routerReportsNewRouteInformation(RouteInformation routeInformation) {
+    SystemNavigator.routeInformationUpdated(
+      location: routeInformation.location!,
+      state: routeInformation.state,
+    );
+    _value = routeInformation;
+  }
+
+  @override
+  RouteInformation? get value => _value;
+  RouteInformation? _value;
+
+  void _platformReportsNewRouteInformation(RouteInformation routeInformation) {
+    if (_value == routeInformation)
+      return;
+    _value = routeInformation;
+    notifyListeners();
+  }
+
+  @override
+  void addListener(VoidCallback listener) {
+    if (!hasListeners)
+      WidgetsBinding.instance!.addObserver(this);
+    super.addListener(listener);
+  }
+
+  @override
+  void removeListener(VoidCallback listener) {
+    super.removeListener(listener);
+    if (!hasListeners)
+      WidgetsBinding.instance!.removeObserver(this);
+  }
+
+  @override
+  void dispose() {
+    // In practice, this will rarely be called. We assume that the listeners
+    // will be added and removed in a coherent fashion such that when the object
+    // is no longer being used, there's no listener, and so it will get garbage
+    // collected.
+    if (hasListeners)
+      WidgetsBinding.instance!.removeObserver(this);
+    super.dispose();
+  }
+
+  @override
+  Future<bool> didPushRouteInformation(RouteInformation routeInformation) async {
+    assert(hasListeners);
+    _platformReportsNewRouteInformation(routeInformation);
+    return true;
+  }
+
+  @override
+  Future<bool> didPushRoute(String route) async {
+    assert(hasListeners);
+    _platformReportsNewRouteInformation(RouteInformation(location: route));
+    return true;
+  }
+}
+
+/// A mixin that wires [RouterDelegate.popRoute] to the [Navigator] it builds.
+///
+/// This mixin calls [Navigator.maybePop] when it receives an Android back
+/// button intent through the [RouterDelegate.popRoute]. Using this mixin
+/// guarantees that the back button still respects pageless routes in the
+/// navigator.
+///
+/// Only use this mixin if you plan to build a navigator in the
+/// [RouterDelegate.build].
+mixin PopNavigatorRouterDelegateMixin<T> on RouterDelegate<T> {
+  /// The key used for retrieving the current navigator.
+  ///
+  /// When using this mixin, be sure to use this key to create the navigator.
+  GlobalKey<NavigatorState>? get navigatorKey;
+
+  @override
+  Future<bool> popRoute() {
+    final NavigatorState? navigator = navigatorKey?.currentState;
+    if (navigator == null)
+      return SynchronousFuture<bool>(false);
+    return navigator.maybePop();
+  }
+}
diff --git a/lib/src/widgets/routes.dart b/lib/src/widgets/routes.dart
new file mode 100644
index 0000000..694c6f7
--- /dev/null
+++ b/lib/src/widgets/routes.dart
@@ -0,0 +1,1872 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'package:flute/ui.dart' as ui;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/semantics.dart';
+
+import 'actions.dart';
+import 'basic.dart';
+import 'focus_manager.dart';
+import 'focus_scope.dart';
+import 'framework.dart';
+import 'modal_barrier.dart';
+import 'navigator.dart';
+import 'overlay.dart';
+import 'page_storage.dart';
+import 'primary_scroll_controller.dart';
+import 'restoration.dart';
+import 'scroll_controller.dart';
+import 'transitions.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// dynamic routeObserver;
+// NavigatorState navigator;
+
+/// A route that displays widgets in the [Navigator]'s [Overlay].
+abstract class OverlayRoute<T> extends Route<T> {
+  /// Creates a route that knows how to interact with an [Overlay].
+  OverlayRoute({
+    RouteSettings? settings,
+  }) : super(settings: settings);
+
+  /// Subclasses should override this getter to return the builders for the overlay.
+  @factory
+  Iterable<OverlayEntry> createOverlayEntries();
+
+  @override
+  List<OverlayEntry> get overlayEntries => _overlayEntries;
+  final List<OverlayEntry> _overlayEntries = <OverlayEntry>[];
+
+  @override
+  void install() {
+    assert(_overlayEntries.isEmpty);
+    _overlayEntries.addAll(createOverlayEntries());
+    super.install();
+  }
+
+  /// Controls whether [didPop] calls [NavigatorState.finalizeRoute].
+  ///
+  /// If true, this route removes its overlay entries during [didPop].
+  /// Subclasses can override this getter if they want to delay finalization
+  /// (for example to animate the route's exit before removing it from the
+  /// overlay).
+  ///
+  /// Subclasses that return false from [finishedWhenPopped] are responsible for
+  /// calling [NavigatorState.finalizeRoute] themselves.
+  @protected
+  bool get finishedWhenPopped => true;
+
+  @override
+  bool didPop(T? result) {
+    final bool returnValue = super.didPop(result);
+    assert(returnValue);
+    if (finishedWhenPopped)
+      navigator!.finalizeRoute(this);
+    return returnValue;
+  }
+
+  @override
+  void dispose() {
+    _overlayEntries.clear();
+    super.dispose();
+  }
+}
+
+/// A route with entrance and exit transitions.
+abstract class TransitionRoute<T> extends OverlayRoute<T> {
+  /// Creates a route that animates itself when it is pushed or popped.
+  TransitionRoute({
+    RouteSettings? settings,
+  }) : super(settings: settings);
+
+  /// This future completes only once the transition itself has finished, after
+  /// the overlay entries have been removed from the navigator's overlay.
+  ///
+  /// This future completes once the animation has been dismissed. That will be
+  /// after [popped], because [popped] typically completes before the animation
+  /// even starts, as soon as the route is popped.
+  Future<T?> get completed => _transitionCompleter.future;
+  final Completer<T?> _transitionCompleter = Completer<T?>();
+
+  /// {@template flutter.widgets.TransitionRoute.transitionDuration}
+  /// The duration the transition going forwards.
+  ///
+  /// See also:
+  ///
+  /// * [reverseTransitionDuration], which controls the duration of the
+  /// transition when it is in reverse.
+  /// {@endtemplate}
+  Duration get transitionDuration;
+
+  /// {@template flutter.widgets.TransitionRoute.reverseTransitionDuration}
+  /// The duration the transition going in reverse.
+  ///
+  /// By default, the reverse transition duration is set to the value of
+  /// the forwards [transitionDuration].
+  /// {@endtemplate}
+  Duration get reverseTransitionDuration => transitionDuration;
+
+  /// {@template flutter.widgets.TransitionRoute.opaque}
+  /// Whether the route obscures previous routes when the transition is complete.
+  ///
+  /// When an opaque route's entrance transition is complete, the routes behind
+  /// the opaque route will not be built to save resources.
+  /// {@endtemplate}
+  bool get opaque;
+
+  // This ensures that if we got to the dismissed state while still current,
+  // we will still be disposed when we are eventually popped.
+  //
+  // This situation arises when dealing with the Cupertino dismiss gesture.
+  @override
+  bool get finishedWhenPopped => _controller!.status == AnimationStatus.dismissed;
+
+  /// The animation that drives the route's transition and the previous route's
+  /// forward transition.
+  Animation<double>? get animation => _animation;
+  Animation<double>? _animation;
+
+  /// The animation controller that the route uses to drive the transitions.
+  ///
+  /// The animation itself is exposed by the [animation] property.
+  @protected
+  AnimationController? get controller => _controller;
+  AnimationController? _controller;
+
+  /// The animation for the route being pushed on top of this route. This
+  /// animation lets this route coordinate with the entrance and exit transition
+  /// of route pushed on top of this route.
+  Animation<double>? get secondaryAnimation => _secondaryAnimation;
+  final ProxyAnimation _secondaryAnimation = ProxyAnimation(kAlwaysDismissedAnimation);
+
+  /// Called to create the animation controller that will drive the transitions to
+  /// this route from the previous one, and back to the previous route from this
+  /// one.
+  AnimationController createAnimationController() {
+    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
+    final Duration duration = transitionDuration;
+    final Duration reverseDuration = reverseTransitionDuration;
+    assert(duration != null && duration >= Duration.zero);
+    return AnimationController(
+      duration: duration,
+      reverseDuration: reverseDuration,
+      debugLabel: debugLabel,
+      vsync: navigator!,
+    );
+  }
+
+  /// Called to create the animation that exposes the current progress of
+  /// the transition controlled by the animation controller created by
+  /// [createAnimationController()].
+  Animation<double> createAnimation() {
+    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
+    assert(_controller != null);
+    return _controller!.view;
+  }
+
+  T? _result;
+
+  void _handleStatusChanged(AnimationStatus status) {
+    switch (status) {
+      case AnimationStatus.completed:
+        if (overlayEntries.isNotEmpty)
+          overlayEntries.first.opaque = opaque;
+        break;
+      case AnimationStatus.forward:
+      case AnimationStatus.reverse:
+        if (overlayEntries.isNotEmpty)
+          overlayEntries.first.opaque = false;
+        break;
+      case AnimationStatus.dismissed:
+        // We might still be an active route if a subclass is controlling the
+        // transition and hits the dismissed status. For example, the iOS
+        // back gesture drives this animation to the dismissed status before
+        // removing the route and disposing it.
+        if (!isActive) {
+          navigator!.finalizeRoute(this);
+        }
+        break;
+    }
+  }
+
+  @override
+  void install() {
+    assert(!_transitionCompleter.isCompleted, 'Cannot install a $runtimeType after disposing it.');
+    _controller = createAnimationController();
+    assert(_controller != null, '$runtimeType.createAnimationController() returned null.');
+    _animation = createAnimation()
+      ..addStatusListener(_handleStatusChanged);
+    assert(_animation != null, '$runtimeType.createAnimation() returned null.');
+    super.install();
+    if (_animation!.isCompleted && overlayEntries.isNotEmpty) {
+      overlayEntries.first.opaque = opaque;
+    }
+  }
+
+  @override
+  TickerFuture didPush() {
+    assert(_controller != null, '$runtimeType.didPush called before calling install() or after calling dispose().');
+    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
+    super.didPush();
+    return _controller!.forward();
+  }
+
+  @override
+  void didAdd() {
+    assert(_controller != null, '$runtimeType.didPush called before calling install() or after calling dispose().');
+    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
+    super.didAdd();
+    _controller!.value = _controller!.upperBound;
+  }
+
+  @override
+  void didReplace(Route<dynamic>? oldRoute) {
+    assert(_controller != null, '$runtimeType.didReplace called before calling install() or after calling dispose().');
+    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
+    if (oldRoute is TransitionRoute)
+      _controller!.value = oldRoute._controller!.value;
+    super.didReplace(oldRoute);
+  }
+
+  @override
+  bool didPop(T? result) {
+    assert(_controller != null, '$runtimeType.didPop called before calling install() or after calling dispose().');
+    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
+    _result = result;
+    _controller!.reverse();
+    return super.didPop(result);
+  }
+
+  @override
+  void didPopNext(Route<dynamic> nextRoute) {
+    assert(_controller != null, '$runtimeType.didPopNext called before calling install() or after calling dispose().');
+    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
+    _updateSecondaryAnimation(nextRoute);
+    super.didPopNext(nextRoute);
+  }
+
+  @override
+  void didChangeNext(Route<dynamic>? nextRoute) {
+    assert(_controller != null, '$runtimeType.didChangeNext called before calling install() or after calling dispose().');
+    assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
+    _updateSecondaryAnimation(nextRoute);
+    super.didChangeNext(nextRoute);
+  }
+
+  // A callback method that disposes existing train hopping animation and
+  // removes its listener.
+  //
+  // This property is non-null if there is a train hopping in progress, and the
+  // caller must reset this property to null after it is called.
+  VoidCallback? _trainHoppingListenerRemover;
+
+  void _updateSecondaryAnimation(Route<dynamic>? nextRoute) {
+    // There is an existing train hopping in progress. Unfortunately, we cannot
+    // dispose current train hopping animation until we replace it with a new
+    // animation.
+    final VoidCallback? previousTrainHoppingListenerRemover = _trainHoppingListenerRemover;
+    _trainHoppingListenerRemover = null;
+
+    if (nextRoute is TransitionRoute<dynamic> && canTransitionTo(nextRoute) && nextRoute.canTransitionFrom(this)) {
+      final Animation<double>? current = _secondaryAnimation.parent;
+      if (current != null) {
+        final Animation<double> currentTrain = (current is TrainHoppingAnimation ? current.currentTrain : current)!;
+        final Animation<double> nextTrain = nextRoute._animation!;
+        if (
+          currentTrain.value == nextTrain.value ||
+          nextTrain.status == AnimationStatus.completed ||
+          nextTrain.status == AnimationStatus.dismissed
+        ) {
+          _setSecondaryAnimation(nextTrain, nextRoute.completed);
+        } else {
+          // Two trains animate at different values. We have to do train hopping.
+          // There are three possibilities of train hopping:
+          //  1. We hop on the nextTrain when two trains meet in the middle using
+          //     TrainHoppingAnimation.
+          //  2. There is no chance to hop on nextTrain because two trains never
+          //     cross each other. We have to directly set the animation to
+          //     nextTrain once the nextTrain stops animating.
+          //  3. A new _updateSecondaryAnimation is called before train hopping
+          //     finishes. We leave a listener remover for the next call to
+          //     properly clean up the existing train hopping.
+          TrainHoppingAnimation? newAnimation;
+          void _jumpOnAnimationEnd(AnimationStatus status) {
+            switch (status) {
+              case AnimationStatus.completed:
+              case AnimationStatus.dismissed:
+                // The nextTrain has stopped animating without train hopping.
+                // Directly sets the secondary animation and disposes the
+                // TrainHoppingAnimation.
+                _setSecondaryAnimation(nextTrain, nextRoute.completed);
+                if (_trainHoppingListenerRemover != null) {
+                  _trainHoppingListenerRemover!();
+                  _trainHoppingListenerRemover = null;
+                }
+                break;
+              case AnimationStatus.forward:
+              case AnimationStatus.reverse:
+                break;
+            }
+          }
+          _trainHoppingListenerRemover = () {
+            nextTrain.removeStatusListener(_jumpOnAnimationEnd);
+            newAnimation?.dispose();
+          };
+          nextTrain.addStatusListener(_jumpOnAnimationEnd);
+          newAnimation = TrainHoppingAnimation(
+            currentTrain,
+            nextTrain,
+            onSwitchedTrain: () {
+              assert(_secondaryAnimation.parent == newAnimation);
+              assert(newAnimation!.currentTrain == nextRoute._animation);
+              // We can hop on the nextTrain, so we don't need to listen to
+              // whether the nextTrain has stopped.
+              _setSecondaryAnimation(newAnimation!.currentTrain, nextRoute.completed);
+              if (_trainHoppingListenerRemover != null) {
+                _trainHoppingListenerRemover!();
+                _trainHoppingListenerRemover = null;
+              }
+            },
+          );
+          _setSecondaryAnimation(newAnimation, nextRoute.completed);
+        }
+      } else {
+        _setSecondaryAnimation(nextRoute._animation, nextRoute.completed);
+      }
+    } else {
+      _setSecondaryAnimation(kAlwaysDismissedAnimation);
+    }
+    // Finally, we dispose any previous train hopping animation because it
+    // has been successfully updated at this point.
+    if (previousTrainHoppingListenerRemover != null) {
+      previousTrainHoppingListenerRemover();
+    }
+  }
+
+  void _setSecondaryAnimation(Animation<double>? animation, [Future<dynamic>? disposed]) {
+    _secondaryAnimation.parent = animation;
+    // Releases the reference to the next route's animation when that route
+    // is disposed.
+    disposed?.then((dynamic _) {
+      if (_secondaryAnimation.parent == animation) {
+        _secondaryAnimation.parent = kAlwaysDismissedAnimation;
+        if (animation is TrainHoppingAnimation) {
+          animation.dispose();
+        }
+      }
+    });
+  }
+
+  /// Returns true if this route supports a transition animation that runs
+  /// when [nextRoute] is pushed on top of it or when [nextRoute] is popped
+  /// off of it.
+  ///
+  /// Subclasses can override this method to restrict the set of routes they
+  /// need to coordinate transitions with.
+  ///
+  /// If true, and `nextRoute.canTransitionFrom()` is true, then the
+  /// [ModalRoute.buildTransitions] `secondaryAnimation` will run from 0.0 - 1.0
+  /// when [nextRoute] is pushed on top of this one.  Similarly, if
+  /// the [nextRoute] is popped off of this route, the
+  /// `secondaryAnimation` will run from 1.0 - 0.0.
+  ///
+  /// If false, this route's [ModalRoute.buildTransitions] `secondaryAnimation` parameter
+  /// value will be [kAlwaysDismissedAnimation]. In other words, this route
+  /// will not animate when when [nextRoute] is pushed on top of it or when
+  /// [nextRoute] is popped off of it.
+  ///
+  /// Returns true by default.
+  ///
+  /// See also:
+  ///
+  ///  * [canTransitionFrom], which must be true for [nextRoute] for the
+  ///    [ModalRoute.buildTransitions] `secondaryAnimation` to run.
+  bool canTransitionTo(TransitionRoute<dynamic> nextRoute) => true;
+
+  /// Returns true if [previousRoute] should animate when this route
+  /// is pushed on top of it or when then this route is popped off of it.
+  ///
+  /// Subclasses can override this method to restrict the set of routes they
+  /// need to coordinate transitions with.
+  ///
+  /// If true, and `previousRoute.canTransitionTo()` is true, then the
+  /// previous route's [ModalRoute.buildTransitions] `secondaryAnimation` will
+  /// run from 0.0 - 1.0 when this route is pushed on top of
+  /// it. Similarly, if this route is popped off of [previousRoute]
+  /// the previous route's `secondaryAnimation` will run from 1.0 - 0.0.
+  ///
+  /// If false, then the previous route's [ModalRoute.buildTransitions]
+  /// `secondaryAnimation` value will be kAlwaysDismissedAnimation. In
+  /// other words [previousRoute] will not animate when this route is
+  /// pushed on top of it or when then this route is popped off of it.
+  ///
+  /// Returns true by default.
+  ///
+  /// See also:
+  ///
+  ///  * [canTransitionTo], which must be true for [previousRoute] for its
+  ///    [ModalRoute.buildTransitions] `secondaryAnimation` to run.
+  bool canTransitionFrom(TransitionRoute<dynamic> previousRoute) => true;
+
+  @override
+  void dispose() {
+    assert(!_transitionCompleter.isCompleted, 'Cannot dispose a $runtimeType twice.');
+    _controller?.dispose();
+    _transitionCompleter.complete(_result);
+    super.dispose();
+  }
+
+  /// A short description of this route useful for debugging.
+  String get debugLabel => objectRuntimeType(this, 'TransitionRoute');
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'TransitionRoute')}(animation: $_controller)';
+}
+
+/// An entry in the history of a [LocalHistoryRoute].
+class LocalHistoryEntry {
+  /// Creates an entry in the history of a [LocalHistoryRoute].
+  LocalHistoryEntry({ this.onRemove });
+
+  /// Called when this entry is removed from the history of its associated [LocalHistoryRoute].
+  final VoidCallback? onRemove;
+
+  LocalHistoryRoute<dynamic>? _owner;
+
+  /// Remove this entry from the history of its associated [LocalHistoryRoute].
+  void remove() {
+    _owner!.removeLocalHistoryEntry(this);
+    assert(_owner == null);
+  }
+
+  void _notifyRemoved() {
+    if (onRemove != null)
+      onRemove!();
+  }
+}
+
+/// A mixin used by routes to handle back navigations internally by popping a list.
+///
+/// When a [Navigator] is instructed to pop, the current route is given an
+/// opportunity to handle the pop internally. A `LocalHistoryRoute` handles the
+/// pop internally if its list of local history entries is non-empty. Rather
+/// than being removed as the current route, the most recent [LocalHistoryEntry]
+/// is removed from the list and its [LocalHistoryEntry.onRemove] is called.
+mixin LocalHistoryRoute<T> on Route<T> {
+  List<LocalHistoryEntry>? _localHistory;
+
+  /// Adds a local history entry to this route.
+  ///
+  /// When asked to pop, if this route has any local history entries, this route
+  /// will handle the pop internally by removing the most recently added local
+  /// history entry.
+  ///
+  /// The given local history entry must not already be part of another local
+  /// history route.
+  ///
+  /// {@tool snippet}
+  ///
+  /// The following example is an app with 2 pages: `HomePage` and `SecondPage`.
+  /// The `HomePage` can navigate to the `SecondPage`.
+  ///
+  /// The `SecondPage` uses a [LocalHistoryEntry] to implement local navigation
+  /// within that page. Pressing 'show rectangle' displays a red rectangle and
+  /// adds a local history entry. At that point, pressing the '< back' button
+  /// pops the latest route, which is the local history entry, and the red
+  /// rectangle disappears. Pressing the '< back' button a second time
+  /// once again pops the latest route, which is the `SecondPage`, itself.
+  /// Therefore, the second press navigates back to the `HomePage`.
+  ///
+  /// ```dart
+  /// class App extends StatelessWidget {
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return MaterialApp(
+  ///       initialRoute: '/',
+  ///       routes: {
+  ///         '/': (BuildContext context) => HomePage(),
+  ///         '/second_page': (BuildContext context) => SecondPage(),
+  ///       },
+  ///     );
+  ///   }
+  /// }
+  ///
+  /// class HomePage extends StatefulWidget {
+  ///   HomePage();
+  ///
+  ///   @override
+  ///   _HomePageState createState() => _HomePageState();
+  /// }
+  ///
+  /// class _HomePageState extends State<HomePage> {
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Scaffold(
+  ///       body: Center(
+  ///         child: Column(
+  ///           mainAxisSize: MainAxisSize.min,
+  ///           children: <Widget>[
+  ///             Text('HomePage'),
+  ///             // Press this button to open the SecondPage.
+  ///             ElevatedButton(
+  ///               child: Text('Second Page >'),
+  ///               onPressed: () {
+  ///                 Navigator.pushNamed(context, '/second_page');
+  ///               },
+  ///             ),
+  ///           ],
+  ///         ),
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  ///
+  /// class SecondPage extends StatefulWidget {
+  ///   @override
+  ///   _SecondPageState createState() => _SecondPageState();
+  /// }
+  ///
+  /// class _SecondPageState extends State<SecondPage> {
+  ///
+  ///   bool _showRectangle = false;
+  ///
+  ///   void _navigateLocallyToShowRectangle() async {
+  ///     // This local history entry essentially represents the display of the red
+  ///     // rectangle. When this local history entry is removed, we hide the red
+  ///     // rectangle.
+  ///     setState(() => _showRectangle = true);
+  ///     ModalRoute.of(context).addLocalHistoryEntry(
+  ///         LocalHistoryEntry(
+  ///             onRemove: () {
+  ///               // Hide the red rectangle.
+  ///               setState(() => _showRectangle = false);
+  ///             }
+  ///         )
+  ///     );
+  ///   }
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     final localNavContent = _showRectangle
+  ///       ? Container(
+  ///           width: 100.0,
+  ///           height: 100.0,
+  ///           color: Colors.red,
+  ///         )
+  ///       : ElevatedButton(
+  ///           child: Text('Show Rectangle'),
+  ///           onPressed: _navigateLocallyToShowRectangle,
+  ///         );
+  ///
+  ///     return Scaffold(
+  ///       body: Center(
+  ///         child: Column(
+  ///           mainAxisAlignment: MainAxisAlignment.center,
+  ///           children: <Widget>[
+  ///             localNavContent,
+  ///             ElevatedButton(
+  ///               child: Text('< Back'),
+  ///               onPressed: () {
+  ///                 // Pop a route. If this is pressed while the red rectangle is
+  ///                 // visible then it will will pop our local history entry, which
+  ///                 // will hide the red rectangle. Otherwise, the SecondPage will
+  ///                 // navigate back to the HomePage.
+  ///                 Navigator.of(context).pop();
+  ///               },
+  ///             ),
+  ///           ],
+  ///         ),
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  void addLocalHistoryEntry(LocalHistoryEntry entry) {
+    assert(entry._owner == null);
+    entry._owner = this;
+    _localHistory ??= <LocalHistoryEntry>[];
+    final bool wasEmpty = _localHistory!.isEmpty;
+    _localHistory!.add(entry);
+    if (wasEmpty)
+      changedInternalState();
+  }
+
+  /// Remove a local history entry from this route.
+  ///
+  /// The entry's [LocalHistoryEntry.onRemove] callback, if any, will be called
+  /// synchronously.
+  void removeLocalHistoryEntry(LocalHistoryEntry entry) {
+    assert(entry != null);
+    assert(entry._owner == this);
+    assert(_localHistory!.contains(entry));
+    _localHistory!.remove(entry);
+    entry._owner = null;
+    entry._notifyRemoved();
+    if (_localHistory!.isEmpty) {
+      if (SchedulerBinding.instance!.schedulerPhase == SchedulerPhase.persistentCallbacks) {
+        // The local history might be removed as a result of disposing inactive
+        // elements during finalizeTree. The state is locked at this moment, and
+        // we can only notify state has changed in the next frame.
+        SchedulerBinding.instance!.addPostFrameCallback((Duration duration) {
+          changedInternalState();
+        });
+      } else {
+        changedInternalState();
+      }
+    }
+  }
+
+  @override
+  Future<RoutePopDisposition> willPop() async {
+    if (willHandlePopInternally)
+      return RoutePopDisposition.pop;
+    return super.willPop();
+  }
+
+  @override
+  bool didPop(T? result) {
+    if (_localHistory != null && _localHistory!.isNotEmpty) {
+      final LocalHistoryEntry entry = _localHistory!.removeLast();
+      assert(entry._owner == this);
+      entry._owner = null;
+      entry._notifyRemoved();
+      if (_localHistory!.isEmpty)
+        changedInternalState();
+      return false;
+    }
+    return super.didPop(result);
+  }
+
+  @override
+  bool get willHandlePopInternally {
+    return _localHistory != null && _localHistory!.isNotEmpty;
+  }
+}
+
+class _DismissModalAction extends DismissAction {
+  _DismissModalAction(this.context);
+
+  final BuildContext context;
+
+  @override
+  bool isEnabled(DismissIntent intent) {
+    final ModalRoute<dynamic> route = ModalRoute.of<dynamic>(context)!;
+    return route.barrierDismissible;
+  }
+
+  @override
+  Object invoke(DismissIntent intent) {
+    return Navigator.of(context).maybePop();
+  }
+}
+
+class _ModalScopeStatus extends InheritedWidget {
+  const _ModalScopeStatus({
+    Key? key,
+    required this.isCurrent,
+    required this.canPop,
+    required this.route,
+    required Widget child,
+  }) : assert(isCurrent != null),
+       assert(canPop != null),
+       assert(route != null),
+       assert(child != null),
+       super(key: key, child: child);
+
+  final bool isCurrent;
+  final bool canPop;
+  final Route<dynamic> route;
+
+  @override
+  bool updateShouldNotify(_ModalScopeStatus old) {
+    return isCurrent != old.isCurrent ||
+           canPop != old.canPop ||
+           route != old.route;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(FlagProperty('isCurrent', value: isCurrent, ifTrue: 'active', ifFalse: 'inactive'));
+    description.add(FlagProperty('canPop', value: canPop, ifTrue: 'can pop'));
+  }
+}
+
+class _ModalScope<T> extends StatefulWidget {
+  const _ModalScope({
+    Key? key,
+    required this.route,
+  }) : super(key: key);
+
+  final ModalRoute<T> route;
+
+  @override
+  _ModalScopeState<T> createState() => _ModalScopeState<T>();
+}
+
+class _ModalScopeState<T> extends State<_ModalScope<T>> {
+  // We cache the result of calling the route's buildPage, and clear the cache
+  // whenever the dependencies change. This implements the contract described in
+  // the documentation for buildPage, namely that it gets called once, unless
+  // something like a ModalRoute.of() dependency triggers an update.
+  Widget? _page;
+
+  // This is the combination of the two animations for the route.
+  late Listenable _listenable;
+
+  /// The node this scope will use for its root [FocusScope] widget.
+  final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: '$_ModalScopeState Focus Scope');
+  final ScrollController primaryScrollController = ScrollController();
+
+  @override
+  void initState() {
+    super.initState();
+    final List<Listenable> animations = <Listenable>[
+      if (widget.route.animation != null) widget.route.animation!,
+      if (widget.route.secondaryAnimation != null) widget.route.secondaryAnimation!,
+    ];
+    _listenable = Listenable.merge(animations);
+    if (widget.route.isCurrent) {
+      widget.route.navigator!.focusScopeNode.setFirstFocus(focusScopeNode);
+    }
+  }
+
+  @override
+  void didUpdateWidget(_ModalScope<T> oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    assert(widget.route == oldWidget.route);
+    if (widget.route.isCurrent) {
+      widget.route.navigator!.focusScopeNode.setFirstFocus(focusScopeNode);
+    }
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _page = null;
+  }
+
+  void _forceRebuildPage() {
+    setState(() {
+      _page = null;
+    });
+  }
+
+  @override
+  void dispose() {
+    focusScopeNode.dispose();
+    super.dispose();
+  }
+
+  bool get _shouldIgnoreFocusRequest {
+    return widget.route.animation?.status == AnimationStatus.reverse ||
+      (widget.route.navigator?.userGestureInProgress ?? false);
+  }
+
+  // This should be called to wrap any changes to route.isCurrent, route.canPop,
+  // and route.offstage.
+  void _routeSetState(VoidCallback fn) {
+    if (widget.route.isCurrent && !_shouldIgnoreFocusRequest) {
+      widget.route.navigator!.focusScopeNode.setFirstFocus(focusScopeNode);
+    }
+    setState(fn);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return AnimatedBuilder(
+      animation: widget.route.restorationScopeId,
+      builder: (BuildContext context, Widget? child) {
+        assert(child != null);
+        return RestorationScope(
+          restorationId: widget.route.restorationScopeId.value,
+          child: child!,
+        );
+      },
+      child: _ModalScopeStatus(
+        route: widget.route,
+        isCurrent: widget.route.isCurrent, // _routeSetState is called if this updates
+        canPop: widget.route.canPop, // _routeSetState is called if this updates
+        child: Offstage(
+          offstage: widget.route.offstage, // _routeSetState is called if this updates
+          child: PageStorage(
+            bucket: widget.route._storageBucket, // immutable
+            child: Builder(
+              builder: (BuildContext context) {
+                return Actions(
+                  actions: <Type, Action<Intent>>{
+                    DismissIntent: _DismissModalAction(context),
+                  },
+                  child: PrimaryScrollController(
+                    controller: primaryScrollController,
+                    child: FocusScope(
+                      node: focusScopeNode, // immutable
+                      child: RepaintBoundary(
+                        child: AnimatedBuilder(
+                          animation: _listenable, // immutable
+                          builder: (BuildContext context, Widget? child) {
+                            return widget.route.buildTransitions(
+                              context,
+                              widget.route.animation!,
+                              widget.route.secondaryAnimation!,
+                              // This additional AnimatedBuilder is include because if the
+                              // value of the userGestureInProgressNotifier changes, it's
+                              // only necessary to rebuild the IgnorePointer widget and set
+                              // the focus node's ability to focus.
+                              AnimatedBuilder(
+                                animation: widget.route.navigator?.userGestureInProgressNotifier ?? ValueNotifier<bool>(false),
+                                builder: (BuildContext context, Widget? child) {
+                                  final bool ignoreEvents = _shouldIgnoreFocusRequest;
+                                  focusScopeNode.canRequestFocus = !ignoreEvents;
+                                  return IgnorePointer(
+                                    ignoring: ignoreEvents,
+                                    child: child,
+                                  );
+                                },
+                                child: child,
+                              ),
+                            );
+                          },
+                          child: _page ??= RepaintBoundary(
+                            key: widget.route._subtreeKey, // immutable
+                            child: Builder(
+                              builder: (BuildContext context) {
+                                return widget.route.buildPage(
+                                  context,
+                                  widget.route.animation!,
+                                  widget.route.secondaryAnimation!,
+                                );
+                              },
+                            ),
+                          ),
+                        ),
+                      ),
+                    ),
+                  ),
+                );
+              },
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+/// A route that blocks interaction with previous routes.
+///
+/// [ModalRoute]s cover the entire [Navigator]. They are not necessarily
+/// [opaque], however; for example, a pop-up menu uses a [ModalRoute] but only
+/// shows the menu in a small box overlapping the previous route.
+///
+/// The `T` type argument is the return value of the route. If there is no
+/// return value, consider using `void` as the return value.
+abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T> {
+  /// Creates a route that blocks interaction with previous routes.
+  ModalRoute({
+    RouteSettings? settings,
+    ui.ImageFilter? filter,
+  }) : _filter = filter,
+       super(settings: settings);
+
+  /// The filter to add to the barrier.
+  ///
+  /// If given, this filter will be applied to the modal barrier using
+  /// [BackdropFilter]. This allows blur effects, for example.
+  final ui.ImageFilter? _filter;
+
+  // The API for general users of this class
+
+  /// Returns the modal route most closely associated with the given context.
+  ///
+  /// Returns null if the given context is not associated with a modal route.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// ModalRoute route = ModalRoute.of(context);
+  /// ```
+  ///
+  /// The given [BuildContext] will be rebuilt if the state of the route changes
+  /// while it is visible (specifically, if [isCurrent] or [canPop] change value).
+  @optionalTypeArgs
+  static ModalRoute<T>? of<T extends Object?>(BuildContext context) {
+    final _ModalScopeStatus? widget = context.dependOnInheritedWidgetOfExactType<_ModalScopeStatus>();
+    return widget?.route as ModalRoute<T>?;
+  }
+
+  /// Schedule a call to [buildTransitions].
+  ///
+  /// Whenever you need to change internal state for a [ModalRoute] object, make
+  /// the change in a function that you pass to [setState], as in:
+  ///
+  /// ```dart
+  /// setState(() { myState = newValue });
+  /// ```
+  ///
+  /// If you just change the state directly without calling [setState], then the
+  /// route will not be scheduled for rebuilding, meaning that its rendering
+  /// will not be updated.
+  @protected
+  void setState(VoidCallback fn) {
+    if (_scopeKey.currentState != null) {
+      _scopeKey.currentState!._routeSetState(fn);
+    } else {
+      // The route isn't currently visible, so we don't have to call its setState
+      // method, but we do still need to call the fn callback, otherwise the state
+      // in the route won't be updated!
+      fn();
+    }
+  }
+
+  /// Returns a predicate that's true if the route has the specified name and if
+  /// popping the route will not yield the same route, i.e. if the route's
+  /// [willHandlePopInternally] property is false.
+  ///
+  /// This function is typically used with [Navigator.popUntil()].
+  static RoutePredicate withName(String name) {
+    return (Route<dynamic> route) {
+      return !route.willHandlePopInternally
+          && route is ModalRoute
+          && route.settings.name == name;
+    };
+  }
+
+  // The API for subclasses to override - used by _ModalScope
+
+  /// Override this method to build the primary content of this route.
+  ///
+  /// The arguments have the following meanings:
+  ///
+  ///  * `context`: The context in which the route is being built.
+  ///  * [animation]: The animation for this route's transition. When entering,
+  ///    the animation runs forward from 0.0 to 1.0. When exiting, this animation
+  ///    runs backwards from 1.0 to 0.0.
+  ///  * [secondaryAnimation]: The animation for the route being pushed on top of
+  ///    this route. This animation lets this route coordinate with the entrance
+  ///    and exit transition of routes pushed on top of this route.
+  ///
+  /// This method is only called when the route is first built, and rarely
+  /// thereafter. In particular, it is not automatically called again when the
+  /// route's state changes unless it uses [ModalRoute.of]. For a builder that
+  /// is called every time the route's state changes, consider
+  /// [buildTransitions]. For widgets that change their behavior when the
+  /// route's state changes, consider [ModalRoute.of] to obtain a reference to
+  /// the route; this will cause the widget to be rebuilt each time the route
+  /// changes state.
+  ///
+  /// In general, [buildPage] should be used to build the page contents, and
+  /// [buildTransitions] for the widgets that change as the page is brought in
+  /// and out of view. Avoid using [buildTransitions] for content that never
+  /// changes; building such content once from [buildPage] is more efficient.
+  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation);
+
+  /// Override this method to wrap the [child] with one or more transition
+  /// widgets that define how the route arrives on and leaves the screen.
+  ///
+  /// By default, the child (which contains the widget returned by [buildPage])
+  /// is not wrapped in any transition widgets.
+  ///
+  /// The [buildTransitions] method, in contrast to [buildPage], is called each
+  /// time the [Route]'s state changes while it is visible (e.g. if the value of
+  /// [canPop] changes on the active route).
+  ///
+  /// The [buildTransitions] method is typically used to define transitions
+  /// that animate the new topmost route's comings and goings. When the
+  /// [Navigator] pushes a route on the top of its stack, the new route's
+  /// primary [animation] runs from 0.0 to 1.0. When the Navigator pops the
+  /// topmost route, e.g. because the use pressed the back button, the
+  /// primary animation runs from 1.0 to 0.0.
+  ///
+  /// The following example uses the primary animation to drive a
+  /// [SlideTransition] that translates the top of the new route vertically
+  /// from the bottom of the screen when it is pushed on the Navigator's
+  /// stack. When the route is popped the SlideTransition translates the
+  /// route from the top of the screen back to the bottom.
+  ///
+  /// ```dart
+  /// PageRouteBuilder(
+  ///   pageBuilder: (BuildContext context,
+  ///       Animation<double> animation,
+  ///       Animation<double> secondaryAnimation,
+  ///       Widget child,
+  ///   ) {
+  ///     return Scaffold(
+  ///       appBar: AppBar(title: Text('Hello')),
+  ///       body: Center(
+  ///         child: Text('Hello World'),
+  ///       ),
+  ///     );
+  ///   },
+  ///   transitionsBuilder: (
+  ///       BuildContext context,
+  ///       Animation<double> animation,
+  ///       Animation<double> secondaryAnimation,
+  ///       Widget child,
+  ///    ) {
+  ///     return SlideTransition(
+  ///       position: Tween<Offset>(
+  ///         begin: const Offset(0.0, 1.0),
+  ///         end: Offset.zero,
+  ///       ).animate(animation),
+  ///       child: child, // child is the value returned by pageBuilder
+  ///     );
+  ///   },
+  /// );
+  /// ```
+  ///
+  /// We've used [PageRouteBuilder] to demonstrate the [buildTransitions] method
+  /// here. The body of an override of the [buildTransitions] method would be
+  /// defined in the same way.
+  ///
+  /// When the [Navigator] pushes a route on the top of its stack, the
+  /// [secondaryAnimation] can be used to define how the route that was on
+  /// the top of the stack leaves the screen. Similarly when the topmost route
+  /// is popped, the secondaryAnimation can be used to define how the route
+  /// below it reappears on the screen. When the Navigator pushes a new route
+  /// on the top of its stack, the old topmost route's secondaryAnimation
+  /// runs from 0.0 to 1.0. When the Navigator pops the topmost route, the
+  /// secondaryAnimation for the route below it runs from 1.0 to 0.0.
+  ///
+  /// The example below adds a transition that's driven by the
+  /// [secondaryAnimation]. When this route disappears because a new route has
+  /// been pushed on top of it, it translates in the opposite direction of
+  /// the new route. Likewise when the route is exposed because the topmost
+  /// route has been popped off.
+  ///
+  /// ```dart
+  ///   transitionsBuilder: (
+  ///       BuildContext context,
+  ///       Animation<double> animation,
+  ///       Animation<double> secondaryAnimation,
+  ///       Widget child,
+  ///   ) {
+  ///     return SlideTransition(
+  ///       position: AlignmentTween(
+  ///         begin: const Offset(0.0, 1.0),
+  ///         end: Offset.zero,
+  ///       ).animate(animation),
+  ///       child: SlideTransition(
+  ///         position: TweenOffset(
+  ///           begin: Offset.zero,
+  ///           end: const Offset(0.0, 1.0),
+  ///         ).animate(secondaryAnimation),
+  ///         child: child,
+  ///       ),
+  ///     );
+  ///   }
+  /// ```
+  ///
+  /// In practice the `secondaryAnimation` is used pretty rarely.
+  ///
+  /// The arguments to this method are as follows:
+  ///
+  ///  * `context`: The context in which the route is being built.
+  ///  * [animation]: When the [Navigator] pushes a route on the top of its stack,
+  ///    the new route's primary [animation] runs from 0.0 to 1.0. When the [Navigator]
+  ///    pops the topmost route this animation runs from 1.0 to 0.0.
+  ///  * [secondaryAnimation]: When the Navigator pushes a new route
+  ///    on the top of its stack, the old topmost route's [secondaryAnimation]
+  ///    runs from 0.0 to 1.0. When the [Navigator] pops the topmost route, the
+  ///    [secondaryAnimation] for the route below it runs from 1.0 to 0.0.
+  ///  * `child`, the page contents, as returned by [buildPage].
+  ///
+  /// See also:
+  ///
+  ///  * [buildPage], which is used to describe the actual contents of the page,
+  ///    and whose result is passed to the `child` argument of this method.
+  Widget buildTransitions(
+    BuildContext context,
+    Animation<double> animation,
+    Animation<double> secondaryAnimation,
+    Widget child,
+  ) {
+    return child;
+  }
+
+  @override
+  void install() {
+    super.install();
+    _animationProxy = ProxyAnimation(super.animation);
+    _secondaryAnimationProxy = ProxyAnimation(super.secondaryAnimation);
+  }
+
+  @override
+  TickerFuture didPush() {
+    if (_scopeKey.currentState != null) {
+      navigator!.focusScopeNode.setFirstFocus(_scopeKey.currentState!.focusScopeNode);
+    }
+    return super.didPush();
+  }
+
+  @override
+  void didAdd() {
+    if (_scopeKey.currentState != null) {
+      navigator!.focusScopeNode.setFirstFocus(_scopeKey.currentState!.focusScopeNode);
+    }
+    super.didAdd();
+  }
+
+  // The API for subclasses to override - used by this class
+
+  /// {@template flutter.widgets.ModalRoute.barrierDismissible}
+  /// Whether you can dismiss this route by tapping the modal barrier.
+  ///
+  /// The modal barrier is the scrim that is rendered behind each route, which
+  /// generally prevents the user from interacting with the route below the
+  /// current route, and normally partially obscures such routes.
+  ///
+  /// For example, when a dialog is on the screen, the page below the dialog is
+  /// usually darkened by the modal barrier.
+  ///
+  /// If [barrierDismissible] is true, then tapping this barrier will cause the
+  /// current route to be popped (see [Navigator.pop]) with null as the value.
+  ///
+  /// If [barrierDismissible] is false, then tapping the barrier has no effect.
+  ///
+  /// If this getter would ever start returning a different value, the
+  /// [Route.changedInternalState] should be invoked so that the change can take
+  /// effect.
+  ///
+  /// See also:
+  ///
+  ///  * [barrierColor], which controls the color of the scrim for this route.
+  ///  * [ModalBarrier], the widget that implements this feature.
+  /// {@endtemplate}
+  bool get barrierDismissible;
+
+  /// Whether the semantics of the modal barrier are included in the
+  /// semantics tree.
+  ///
+  /// The modal barrier is the scrim that is rendered behind each route, which
+  /// generally prevents the user from interacting with the route below the
+  /// current route, and normally partially obscures such routes.
+  ///
+  /// If [semanticsDismissible] is true, then modal barrier semantics are
+  /// included in the semantics tree.
+  ///
+  /// If [semanticsDismissible] is false, then modal barrier semantics are
+  /// excluded from the semantics tree and tapping on the modal barrier
+  /// has no effect.
+  bool get semanticsDismissible => true;
+
+  /// {@template flutter.widgets.ModalRoute.barrierColor}
+  /// The color to use for the modal barrier. If this is null, the barrier will
+  /// be transparent.
+  ///
+  /// The modal barrier is the scrim that is rendered behind each route, which
+  /// generally prevents the user from interacting with the route below the
+  /// current route, and normally partially obscures such routes.
+  ///
+  /// For example, when a dialog is on the screen, the page below the dialog is
+  /// usually darkened by the modal barrier.
+  ///
+  /// The color is ignored, and the barrier made invisible, when
+  /// [ModalRoute.offstage] is true.
+  ///
+  /// While the route is animating into position, the color is animated from
+  /// transparent to the specified color.
+  /// {@endtemplate}
+  ///
+  /// If this getter would ever start returning a different color, the
+  /// [Route.changedInternalState] should be invoked so that the change can take
+  /// effect.
+  ///
+  /// {@tool snippet}
+  ///
+  /// It is safe to use `navigator.context` here. For example, to make
+  /// the barrier color use the theme's background color, one could say:
+  ///
+  /// ```dart
+  /// Color get barrierColor => Theme.of(navigator.context).backgroundColor;
+  /// ```
+  ///
+  /// The [Navigator] causes the [ModalRoute]'s modal barrier overlay entry
+  /// to rebuild any time its dependencies change.
+  ///
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * [barrierDismissible], which controls the behavior of the barrier when
+  ///    tapped.
+  ///  * [ModalBarrier], the widget that implements this feature.
+  Color? get barrierColor;
+
+  /// {@template flutter.widgets.ModalRoute.barrierLabel}
+  /// The semantic label used for a dismissible barrier.
+  ///
+  /// If the barrier is dismissible, this label will be read out if
+  /// accessibility tools (like VoiceOver on iOS) focus on the barrier.
+  ///
+  /// The modal barrier is the scrim that is rendered behind each route, which
+  /// generally prevents the user from interacting with the route below the
+  /// current route, and normally partially obscures such routes.
+  ///
+  /// For example, when a dialog is on the screen, the page below the dialog is
+  /// usually darkened by the modal barrier.
+  /// {@endtemplate}
+  ///
+  /// If this getter would ever start returning a different label, the
+  /// [Route.changedInternalState] should be invoked so that the change can take
+  /// effect.
+  ///
+  /// See also:
+  ///
+  ///  * [barrierDismissible], which controls the behavior of the barrier when
+  ///    tapped.
+  ///  * [ModalBarrier], the widget that implements this feature.
+  String? get barrierLabel;
+
+  /// The curve that is used for animating the modal barrier in and out.
+  ///
+  /// The modal barrier is the scrim that is rendered behind each route, which
+  /// generally prevents the user from interacting with the route below the
+  /// current route, and normally partially obscures such routes.
+  ///
+  /// For example, when a dialog is on the screen, the page below the dialog is
+  /// usually darkened by the modal barrier.
+  ///
+  /// While the route is animating into position, the color is animated from
+  /// transparent to the specified [barrierColor].
+  ///
+  /// If this getter would ever start returning a different curve, the
+  /// [changedInternalState] should be invoked so that the change can take
+  /// effect.
+  ///
+  /// It defaults to [Curves.ease].
+  ///
+  /// See also:
+  ///
+  ///  * [barrierColor], which determines the color that the modal transitions
+  ///    to.
+  ///  * [Curves] for a collection of common curves.
+  ///  * [AnimatedModalBarrier], the widget that implements this feature.
+  Curve get barrierCurve => Curves.ease;
+
+  /// {@template flutter.widgets.ModalRoute.maintainState}
+  /// Whether the route should remain in memory when it is inactive.
+  ///
+  /// If this is true, then the route is maintained, so that any futures it is
+  /// holding from the next route will properly resolve when the next route
+  /// pops. If this is not necessary, this can be set to false to allow the
+  /// framework to entirely discard the route's widget hierarchy when it is not
+  /// visible.
+  /// {@endtemplate}
+  ///
+  /// If this getter would ever start returning a different value, the
+  /// [changedInternalState] should be invoked so that the change can take
+  /// effect.
+  bool get maintainState;
+
+
+  // The API for _ModalScope and HeroController
+
+  /// Whether this route is currently offstage.
+  ///
+  /// On the first frame of a route's entrance transition, the route is built
+  /// [Offstage] using an animation progress of 1.0. The route is invisible and
+  /// non-interactive, but each widget has its final size and position. This
+  /// mechanism lets the [HeroController] determine the final local of any hero
+  /// widgets being animated as part of the transition.
+  ///
+  /// The modal barrier, if any, is not rendered if [offstage] is true (see
+  /// [barrierColor]).
+  ///
+  /// Whenever this changes value, [changedInternalState] is called.
+  bool get offstage => _offstage;
+  bool _offstage = false;
+  set offstage(bool value) {
+    if (_offstage == value)
+      return;
+    setState(() {
+      _offstage = value;
+    });
+    _animationProxy!.parent = _offstage ? kAlwaysCompleteAnimation : super.animation;
+    _secondaryAnimationProxy!.parent = _offstage ? kAlwaysDismissedAnimation : super.secondaryAnimation;
+    changedInternalState();
+  }
+
+  /// The build context for the subtree containing the primary content of this route.
+  BuildContext? get subtreeContext => _subtreeKey.currentContext;
+
+  @override
+  Animation<double>? get animation => _animationProxy;
+  ProxyAnimation? _animationProxy;
+
+  @override
+  Animation<double>? get secondaryAnimation => _secondaryAnimationProxy;
+  ProxyAnimation? _secondaryAnimationProxy;
+
+  final List<WillPopCallback> _willPopCallbacks = <WillPopCallback>[];
+
+  /// Returns [RoutePopDisposition.doNotPop] if any of callbacks added with
+  /// [addScopedWillPopCallback] returns either false or null. If they all
+  /// return true, the base [Route.willPop]'s result will be returned. The
+  /// callbacks will be called in the order they were added, and will only be
+  /// called if all previous callbacks returned true.
+  ///
+  /// Typically this method is not overridden because applications usually
+  /// don't create modal routes directly, they use higher level primitives
+  /// like [showDialog]. The scoped [WillPopCallback] list makes it possible
+  /// for ModalRoute descendants to collectively define the value of `willPop`.
+  ///
+  /// See also:
+  ///
+  ///  * [Form], which provides an `onWillPop` callback that uses this mechanism.
+  ///  * [addScopedWillPopCallback], which adds a callback to the list this
+  ///    method checks.
+  ///  * [removeScopedWillPopCallback], which removes a callback from the list
+  ///    this method checks.
+  @override
+  Future<RoutePopDisposition> willPop() async {
+    final _ModalScopeState<T>? scope = _scopeKey.currentState;
+    assert(scope != null);
+    for (final WillPopCallback callback in List<WillPopCallback>.from(_willPopCallbacks)) {
+      if (await callback() != true)
+        return RoutePopDisposition.doNotPop;
+    }
+    return await super.willPop();
+  }
+
+  /// Enables this route to veto attempts by the user to dismiss it.
+  ///
+  /// This callback is typically added using a [WillPopScope] widget. That
+  /// widget finds the enclosing [ModalRoute] and uses this function to register
+  /// this callback:
+  ///
+  /// ```dart
+  /// Widget build(BuildContext context) {
+  ///   return WillPopScope(
+  ///     onWillPop: askTheUserIfTheyAreSure,
+  ///     child: ...,
+  ///   );
+  /// }
+  /// ```
+  ///
+  /// This callback runs asynchronously and it's possible that it will be called
+  /// after its route has been disposed. The callback should check [State.mounted]
+  /// before doing anything.
+  ///
+  /// A typical application of this callback would be to warn the user about
+  /// unsaved [Form] data if the user attempts to back out of the form. In that
+  /// case, use the [Form.onWillPop] property to register the callback.
+  ///
+  /// To register a callback manually, look up the enclosing [ModalRoute] in a
+  /// [State.didChangeDependencies] callback:
+  ///
+  /// ```dart
+  /// ModalRoute<dynamic> _route;
+  ///
+  /// @override
+  /// void didChangeDependencies() {
+  ///  super.didChangeDependencies();
+  ///  _route?.removeScopedWillPopCallback(askTheUserIfTheyAreSure);
+  ///  _route = ModalRoute.of(context);
+  ///  _route?.addScopedWillPopCallback(askTheUserIfTheyAreSure);
+  /// }
+  /// ```
+  ///
+  /// If you register a callback manually, be sure to remove the callback with
+  /// [removeScopedWillPopCallback] by the time the widget has been disposed. A
+  /// stateful widget can do this in its dispose method (continuing the previous
+  /// example):
+  ///
+  /// ```dart
+  /// @override
+  /// void dispose() {
+  ///   _route?.removeScopedWillPopCallback(askTheUserIfTheyAreSure);
+  ///   _route = null;
+  ///   super.dispose();
+  /// }
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * [WillPopScope], which manages the registration and unregistration
+  ///    process automatically.
+  ///  * [Form], which provides an `onWillPop` callback that uses this mechanism.
+  ///  * [willPop], which runs the callbacks added with this method.
+  ///  * [removeScopedWillPopCallback], which removes a callback from the list
+  ///    that [willPop] checks.
+  void addScopedWillPopCallback(WillPopCallback callback) {
+    assert(_scopeKey.currentState != null, 'Tried to add a willPop callback to a route that is not currently in the tree.');
+    _willPopCallbacks.add(callback);
+  }
+
+  /// Remove one of the callbacks run by [willPop].
+  ///
+  /// See also:
+  ///
+  ///  * [Form], which provides an `onWillPop` callback that uses this mechanism.
+  ///  * [addScopedWillPopCallback], which adds callback to the list
+  ///    checked by [willPop].
+  void removeScopedWillPopCallback(WillPopCallback callback) {
+    assert(_scopeKey.currentState != null, 'Tried to remove a willPop callback from a route that is not currently in the tree.');
+    _willPopCallbacks.remove(callback);
+  }
+
+  /// True if one or more [WillPopCallback] callbacks exist.
+  ///
+  /// This method is used to disable the horizontal swipe pop gesture supported
+  /// by [MaterialPageRoute] for [TargetPlatform.iOS] and
+  /// [TargetPlatform.macOS]. If a pop might be vetoed, then the back gesture is
+  /// disabled.
+  ///
+  /// The [buildTransitions] method will not be called again if this changes,
+  /// since it can change during the build as descendants of the route add or
+  /// remove callbacks.
+  ///
+  /// See also:
+  ///
+  ///  * [addScopedWillPopCallback], which adds a callback.
+  ///  * [removeScopedWillPopCallback], which removes a callback.
+  ///  * [willHandlePopInternally], which reports on another reason why
+  ///    a pop might be vetoed.
+  @protected
+  bool get hasScopedWillPopCallback {
+    return _willPopCallbacks.isNotEmpty;
+  }
+
+  @override
+  void didChangePrevious(Route<dynamic>? previousRoute) {
+    super.didChangePrevious(previousRoute);
+    changedInternalState();
+  }
+
+  @override
+  void changedInternalState() {
+    super.changedInternalState();
+    setState(() { /* internal state already changed */ });
+    _modalBarrier.markNeedsBuild();
+    _modalScope.maintainState = maintainState;
+  }
+
+  @override
+  void changedExternalState() {
+    super.changedExternalState();
+    if (_scopeKey.currentState != null)
+      _scopeKey.currentState!._forceRebuildPage();
+  }
+
+  /// Whether this route can be popped.
+  ///
+  /// When this changes, if the route is visible, the route will
+  /// rebuild, and any widgets that used [ModalRoute.of] will be
+  /// notified.
+  bool get canPop => !isFirst || willHandlePopInternally;
+
+  // Internals
+
+  final GlobalKey<_ModalScopeState<T>> _scopeKey = GlobalKey<_ModalScopeState<T>>();
+  final GlobalKey _subtreeKey = GlobalKey();
+  final PageStorageBucket _storageBucket = PageStorageBucket();
+
+  // one of the builders
+  late OverlayEntry _modalBarrier;
+  Widget _buildModalBarrier(BuildContext context) {
+    Widget barrier;
+    if (barrierColor != null && barrierColor!.alpha != 0 && !offstage) { // changedInternalState is called if barrierColor or offstage updates
+      assert(barrierColor != barrierColor!.withOpacity(0.0));
+      final Animation<Color?> color = animation!.drive(
+        ColorTween(
+          begin: barrierColor!.withOpacity(0.0),
+          end: barrierColor, // changedInternalState is called if barrierColor updates
+        ).chain(CurveTween(curve: barrierCurve)), // changedInternalState is called if barrierCurve updates
+      );
+      barrier = AnimatedModalBarrier(
+        color: color,
+        dismissible: barrierDismissible, // changedInternalState is called if barrierDismissible updates
+        semanticsLabel: barrierLabel, // changedInternalState is called if barrierLabel updates
+        barrierSemanticsDismissible: semanticsDismissible,
+      );
+    } else {
+      barrier = ModalBarrier(
+        dismissible: barrierDismissible, // changedInternalState is called if barrierDismissible updates
+        semanticsLabel: barrierLabel, // changedInternalState is called if barrierLabel updates
+        barrierSemanticsDismissible: semanticsDismissible,
+      );
+    }
+    if (_filter != null) {
+      barrier = BackdropFilter(
+        filter: _filter!,
+        child: barrier,
+      );
+    }
+    barrier = IgnorePointer(
+      ignoring: animation!.status == AnimationStatus.reverse || // changedInternalState is called when animation.status updates
+                animation!.status == AnimationStatus.dismissed, // dismissed is possible when doing a manual pop gesture
+      child: barrier,
+    );
+    if (semanticsDismissible && barrierDismissible) {
+      // To be sorted after the _modalScope.
+      barrier = Semantics(
+        sortKey: const OrdinalSortKey(1.0),
+        child: barrier,
+      );
+    }
+    return barrier;
+  }
+
+  // We cache the part of the modal scope that doesn't change from frame to
+  // frame so that we minimize the amount of building that happens.
+  Widget? _modalScopeCache;
+
+  // one of the builders
+  Widget _buildModalScope(BuildContext context) {
+    // To be sorted before the _modalBarrier.
+    return _modalScopeCache ??= Semantics(
+      sortKey: const OrdinalSortKey(0.0),
+      child: _ModalScope<T>(
+        key: _scopeKey,
+        route: this,
+        // _ModalScope calls buildTransitions() and buildChild(), defined above
+      )
+    );
+  }
+
+  late OverlayEntry _modalScope;
+
+  @override
+  Iterable<OverlayEntry> createOverlayEntries() sync* {
+    yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
+    yield _modalScope = OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
+  }
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'ModalRoute')}($settings, animation: $_animation)';
+}
+
+/// A modal route that overlays a widget over the current route.
+abstract class PopupRoute<T> extends ModalRoute<T> {
+  /// Initializes the [PopupRoute].
+  PopupRoute({
+    RouteSettings? settings,
+    ui.ImageFilter? filter,
+  }) : super(
+         filter: filter,
+         settings: settings,
+       );
+
+  @override
+  bool get opaque => false;
+
+  @override
+  bool get maintainState => true;
+}
+
+/// A [Navigator] observer that notifies [RouteAware]s of changes to the
+/// state of their [Route].
+///
+/// [RouteObserver] informs subscribers whenever a route of type `R` is pushed
+/// on top of their own route of type `R` or popped from it. This is for example
+/// useful to keep track of page transitions, e.g. a `RouteObserver<PageRoute>`
+/// will inform subscribed [RouteAware]s whenever the user navigates away from
+/// the current page route to another page route.
+///
+/// To be informed about route changes of any type, consider instantiating a
+/// `RouteObserver<Route>`.
+///
+/// ## Type arguments
+///
+/// When using more aggressive
+/// [lints](http://dart-lang.github.io/linter/lints/), in particular lints such
+/// as `always_specify_types`, the Dart analyzer will require that certain types
+/// be given with their type arguments. Since the [Route] class and its
+/// subclasses have a type argument, this includes the arguments passed to this
+/// class. Consider using `dynamic` to specify the entire class of routes rather
+/// than only specific subtypes. For example, to watch for all [PageRoute]
+/// variants, the `RouteObserver<PageRoute<dynamic>>` type may be used.
+///
+/// {@tool snippet}
+///
+/// To make a [StatefulWidget] aware of its current [Route] state, implement
+/// [RouteAware] in its [State] and subscribe it to a [RouteObserver]:
+///
+/// ```dart
+/// // Register the RouteObserver as a navigation observer.
+/// final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
+/// void main() {
+///   runApp(MaterialApp(
+///     home: Container(),
+///     navigatorObservers: [routeObserver],
+///   ));
+/// }
+///
+/// class RouteAwareWidget extends StatefulWidget {
+///   State<RouteAwareWidget> createState() => RouteAwareWidgetState();
+/// }
+///
+/// // Implement RouteAware in a widget's state and subscribe it to the RouteObserver.
+/// class RouteAwareWidgetState extends State<RouteAwareWidget> with RouteAware {
+///
+///   @override
+///   void didChangeDependencies() {
+///     super.didChangeDependencies();
+///     routeObserver.subscribe(this, ModalRoute.of(context));
+///   }
+///
+///   @override
+///   void dispose() {
+///     routeObserver.unsubscribe(this);
+///     super.dispose();
+///   }
+///
+///   @override
+///   void didPush() {
+///     // Route was pushed onto navigator and is now topmost route.
+///   }
+///
+///   @override
+///   void didPopNext() {
+///     // Covering route was popped off the navigator.
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) => Container();
+///
+/// }
+/// ```
+/// {@end-tool}
+class RouteObserver<R extends Route<dynamic>> extends NavigatorObserver {
+  final Map<R, Set<RouteAware>> _listeners = <R, Set<RouteAware>>{};
+
+  /// Subscribe [routeAware] to be informed about changes to [route].
+  ///
+  /// Going forward, [routeAware] will be informed about qualifying changes
+  /// to [route], e.g. when [route] is covered by another route or when [route]
+  /// is popped off the [Navigator] stack.
+  void subscribe(RouteAware routeAware, R route) {
+    assert(routeAware != null);
+    assert(route != null);
+    final Set<RouteAware> subscribers = _listeners.putIfAbsent(route, () => <RouteAware>{});
+    if (subscribers.add(routeAware)) {
+      routeAware.didPush();
+    }
+  }
+
+  /// Unsubscribe [routeAware].
+  ///
+  /// [routeAware] is no longer informed about changes to its route. If the given argument was
+  /// subscribed to multiple types, this will unregister it (once) from each type.
+  void unsubscribe(RouteAware routeAware) {
+    assert(routeAware != null);
+    for (final R route in _listeners.keys) {
+      final Set<RouteAware>? subscribers = _listeners[route];
+      subscribers?.remove(routeAware);
+    }
+  }
+
+  @override
+  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
+    if (route is R && previousRoute is R) {
+      final List<RouteAware>? previousSubscribers = _listeners[previousRoute]?.toList();
+
+      if (previousSubscribers != null) {
+        for (final RouteAware routeAware in previousSubscribers) {
+          routeAware.didPopNext();
+        }
+      }
+
+      final List<RouteAware>? subscribers = _listeners[route]?.toList();
+
+      if (subscribers != null) {
+        for (final RouteAware routeAware in subscribers) {
+          routeAware.didPop();
+        }
+      }
+    }
+  }
+
+  @override
+  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
+    if (route is R && previousRoute is R) {
+      final Set<RouteAware>? previousSubscribers = _listeners[previousRoute];
+
+      if (previousSubscribers != null) {
+        for (final RouteAware routeAware in previousSubscribers) {
+          routeAware.didPushNext();
+        }
+      }
+    }
+  }
+}
+
+/// An interface for objects that are aware of their current [Route].
+///
+/// This is used with [RouteObserver] to make a widget aware of changes to the
+/// [Navigator]'s session history.
+abstract class RouteAware {
+  /// Called when the top route has been popped off, and the current route
+  /// shows up.
+  void didPopNext() { }
+
+  /// Called when the current route has been pushed.
+  void didPush() { }
+
+  /// Called when the current route has been popped off.
+  void didPop() { }
+
+  /// Called when a new route has been pushed, and the current route is no
+  /// longer visible.
+  void didPushNext() { }
+}
+
+class _DialogRoute<T> extends PopupRoute<T> {
+  _DialogRoute({
+    required RoutePageBuilder pageBuilder,
+    bool barrierDismissible = true,
+    String? barrierLabel,
+    Color? barrierColor = const Color(0x80000000),
+    Duration transitionDuration = const Duration(milliseconds: 200),
+    RouteTransitionsBuilder? transitionBuilder,
+    RouteSettings? settings,
+  }) : assert(barrierDismissible != null),
+       _pageBuilder = pageBuilder,
+       _barrierDismissible = barrierDismissible,
+       _barrierLabel = barrierLabel,
+       _barrierColor = barrierColor,
+       _transitionDuration = transitionDuration,
+       _transitionBuilder = transitionBuilder,
+       super(settings: settings);
+
+  final RoutePageBuilder _pageBuilder;
+
+  @override
+  bool get barrierDismissible => _barrierDismissible;
+  final bool _barrierDismissible;
+
+  @override
+  String? get barrierLabel => _barrierLabel;
+  final String? _barrierLabel;
+
+  @override
+  Color? get barrierColor => _barrierColor;
+  final Color? _barrierColor;
+
+  @override
+  Duration get transitionDuration => _transitionDuration;
+  final Duration _transitionDuration;
+
+  final RouteTransitionsBuilder? _transitionBuilder;
+
+  @override
+  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
+    return Semantics(
+      child: _pageBuilder(context, animation, secondaryAnimation),
+      scopesRoute: true,
+      explicitChildNodes: true,
+    );
+  }
+
+  @override
+  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
+    if (_transitionBuilder == null) {
+      return FadeTransition(
+          opacity: CurvedAnimation(
+            parent: animation,
+            curve: Curves.linear,
+          ),
+          child: child);
+    } // Some default transition
+    return _transitionBuilder!(context, animation, secondaryAnimation, child);
+  }
+}
+
+/// Displays a dialog above the current contents of the app.
+///
+/// This function allows for customization of aspects of the dialog popup.
+///
+/// This function takes a `pageBuilder` which is used to build the primary
+/// content of the route (typically a dialog widget). Content below the dialog
+/// is dimmed with a [ModalBarrier]. The widget returned by the `pageBuilder`
+/// does not share a context with the location that `showGeneralDialog` is
+/// originally called from. Use a [StatefulBuilder] or a custom
+/// [StatefulWidget] if the dialog needs to update dynamically. The
+/// `pageBuilder` argument can not be null.
+///
+/// The `context` argument is used to look up the [Navigator] for the
+/// dialog. It is only used when the method is called. Its corresponding widget
+/// can be safely removed from the tree before the dialog is closed.
+///
+/// The `useRootNavigator` argument is used to determine whether to push the
+/// dialog to the [Navigator] furthest from or nearest to the given `context`.
+/// By default, `useRootNavigator` is `true` and the dialog route created by
+/// this method is pushed to the root navigator.
+///
+/// If the application has multiple [Navigator] objects, it may be necessary to
+/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
+/// dialog rather than just `Navigator.pop(context, result)`.
+///
+/// The `barrierDismissible` argument is used to determine whether this route
+/// can be dismissed by tapping the modal barrier. This argument defaults
+/// to true. If `barrierDismissible` is true, a non-null `barrierLabel` must be
+/// provided.
+///
+/// The `barrierLabel` argument is the semantic label used for a dismissible
+/// barrier. This argument defaults to "Dismiss".
+///
+/// The `barrierColor` argument is the color used for the modal barrier. This
+/// argument defaults to `Color(0x80000000)`.
+///
+/// The `transitionDuration` argument is used to determine how long it takes
+/// for the route to arrive on or leave off the screen. This argument defaults
+/// to 200 milliseconds.
+///
+/// The `transitionBuilder` argument is used to define how the route arrives on
+/// and leaves off the screen. By default, the transition is a linear fade of
+/// the page's contents.
+///
+/// The `routeSettings` will be used in the construction of the dialog's route.
+/// See [RouteSettings] for more details.
+///
+/// Returns a [Future] that resolves to the value (if any) that was passed to
+/// [Navigator.pop] when the dialog was closed.
+///
+/// See also:
+///
+///  * [showDialog], which displays a Material-style dialog.
+///  * [showCupertinoDialog], which displays an iOS-style dialog.
+Future<T?> showGeneralDialog<T extends Object?>({
+  required BuildContext context,
+  required RoutePageBuilder pageBuilder,
+  bool barrierDismissible = false,
+  String? barrierLabel,
+  Color barrierColor = const Color(0x80000000),
+  Duration transitionDuration = const Duration(milliseconds: 200),
+  RouteTransitionsBuilder? transitionBuilder,
+  bool useRootNavigator = true,
+  RouteSettings? routeSettings,
+}) {
+  assert(pageBuilder != null);
+  assert(useRootNavigator != null);
+  assert(!barrierDismissible || barrierLabel != null);
+  return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(_DialogRoute<T>(
+    pageBuilder: pageBuilder,
+    barrierDismissible: barrierDismissible,
+    barrierLabel: barrierLabel,
+    barrierColor: barrierColor,
+    transitionDuration: transitionDuration,
+    transitionBuilder: transitionBuilder,
+    settings: routeSettings,
+  ));
+}
+
+/// Signature for the function that builds a route's primary contents.
+/// Used in [PageRouteBuilder] and [showGeneralDialog].
+///
+/// See [ModalRoute.buildPage] for complete definition of the parameters.
+typedef RoutePageBuilder = Widget Function(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation);
+
+/// Signature for the function that builds a route's transitions.
+/// Used in [PageRouteBuilder] and [showGeneralDialog].
+///
+/// See [ModalRoute.buildTransitions] for complete definition of the parameters.
+typedef RouteTransitionsBuilder = Widget Function(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child);
diff --git a/lib/src/widgets/safe_area.dart b/lib/src/widgets/safe_area.dart
new file mode 100644
index 0000000..bce7403
--- /dev/null
+++ b/lib/src/widgets/safe_area.dart
@@ -0,0 +1,224 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+
+import 'basic.dart';
+import 'debug.dart';
+import 'framework.dart';
+import 'media_query.dart';
+
+/// A widget that insets its child by sufficient padding to avoid intrusions by
+/// the operating system.
+///
+/// For example, this will indent the child by enough to avoid the status bar at
+/// the top of the screen.
+///
+/// It will also indent the child by the amount necessary to avoid The Notch on
+/// the iPhone X, or other similar creative physical features of the display.
+///
+/// When a [minimum] padding is specified, the greater of the minimum padding
+/// or the safe area padding will be applied.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=lkF0TQJO0bA}
+///
+/// See also:
+///
+///  * [SliverSafeArea], for insetting slivers to avoid operating system
+///    intrusions.
+///  * [Padding], for insetting widgets in general.
+///  * [MediaQuery], from which the window padding is obtained.
+///  * [dart:ui.FlutterView.padding], which reports the padding from the operating
+///    system.
+class SafeArea extends StatelessWidget {
+  /// Creates a widget that avoids operating system interfaces.
+  ///
+  /// The [left], [top], [right], [bottom], and [minimum] arguments must not be
+  /// null.
+  const SafeArea({
+    Key? key,
+    this.left = true,
+    this.top = true,
+    this.right = true,
+    this.bottom = true,
+    this.minimum = EdgeInsets.zero,
+    this.maintainBottomViewPadding = false,
+    required this.child,
+  }) : assert(left != null),
+       assert(top != null),
+       assert(right != null),
+       assert(bottom != null),
+       super(key: key);
+
+  /// Whether to avoid system intrusions on the left.
+  final bool left;
+
+  /// Whether to avoid system intrusions at the top of the screen, typically the
+  /// system status bar.
+  final bool top;
+
+  /// Whether to avoid system intrusions on the right.
+  final bool right;
+
+  /// Whether to avoid system intrusions on the bottom side of the screen.
+  final bool bottom;
+
+  /// This minimum padding to apply.
+  ///
+  /// The greater of the minimum insets and the media padding will be applied.
+  final EdgeInsets minimum;
+
+  /// Specifies whether the [SafeArea] should maintain the
+  /// [MediaQueryData.viewPadding] instead of the [MediaQueryData.padding] when
+  /// consumed by the [MediaQueryData.viewInsets] of the current context's
+  /// [MediaQuery], defaults to false.
+  ///
+  /// For example, if there is an onscreen keyboard displayed above the
+  /// SafeArea, the padding can be maintained below the obstruction rather than
+  /// being consumed. This can be helpful in cases where your layout contains
+  /// flexible widgets, which could visibly move when opening a software
+  /// keyboard due to the change in the padding value. Setting this to true will
+  /// avoid the UI shift.
+  final bool maintainBottomViewPadding;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// The padding on the [MediaQuery] for the [child] will be suitably adjusted
+  /// to zero out any sides that were avoided by this widget.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    final MediaQueryData data = MediaQuery.of(context);
+    EdgeInsets padding = data.padding;
+    // Bottom padding has been consumed - i.e. by the keyboard
+    if (data.padding.bottom == 0.0 && data.viewInsets.bottom != 0.0 && maintainBottomViewPadding)
+      padding = padding.copyWith(bottom: data.viewPadding.bottom);
+
+    return Padding(
+      padding: EdgeInsets.only(
+        left: math.max(left ? padding.left : 0.0, minimum.left),
+        top: math.max(top ? padding.top : 0.0, minimum.top),
+        right: math.max(right ? padding.right : 0.0, minimum.right),
+        bottom: math.max(bottom ? padding.bottom : 0.0, minimum.bottom),
+      ),
+      child: MediaQuery.removePadding(
+        context: context,
+        removeLeft: left,
+        removeTop: top,
+        removeRight: right,
+        removeBottom: bottom,
+        child: child,
+      ),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('left', value: left, ifTrue: 'avoid left padding'));
+    properties.add(FlagProperty('top', value: top, ifTrue: 'avoid top padding'));
+    properties.add(FlagProperty('right', value: right, ifTrue: 'avoid right padding'));
+    properties.add(FlagProperty('bottom', value: bottom, ifTrue: 'avoid bottom padding'));
+  }
+}
+
+/// A sliver that insets another sliver by sufficient padding to avoid
+/// intrusions by the operating system.
+///
+/// For example, this will indent the sliver by enough to avoid the status bar
+/// at the top of the screen.
+///
+/// It will also indent the sliver by the amount necessary to avoid The Notch
+/// on the iPhone X, or other similar creative physical features of the
+/// display.
+///
+/// When a [minimum] padding is specified, the greater of the minimum padding
+/// or the safe area padding will be applied.
+///
+/// See also:
+///
+///  * [SafeArea], for insetting widgets to avoid operating system intrusions.
+///  * [SliverPadding], for insetting slivers in general.
+///  * [MediaQuery], from which the window padding is obtained.
+///  * [dart:ui.FlutterView.padding], which reports the padding from the operating
+///    system.
+class SliverSafeArea extends StatelessWidget {
+  /// Creates a sliver that avoids operating system interfaces.
+  ///
+  /// The [left], [top], [right], [bottom], and [minimum] arguments must not be null.
+  const SliverSafeArea({
+    Key? key,
+    this.left = true,
+    this.top = true,
+    this.right = true,
+    this.bottom = true,
+    this.minimum = EdgeInsets.zero,
+    required this.sliver,
+  }) : assert(left != null),
+       assert(top != null),
+       assert(right != null),
+       assert(bottom != null),
+       super(key: key);
+
+  /// Whether to avoid system intrusions on the left.
+  final bool left;
+
+  /// Whether to avoid system intrusions at the top of the screen, typically the
+  /// system status bar.
+  final bool top;
+
+  /// Whether to avoid system intrusions on the right.
+  final bool right;
+
+  /// Whether to avoid system intrusions on the bottom side of the screen.
+  final bool bottom;
+
+  /// This minimum padding to apply.
+  ///
+  /// The greater of the minimum padding and the media padding is be applied.
+  final EdgeInsets minimum;
+
+  /// The sliver below this sliver in the tree.
+  ///
+  /// The padding on the [MediaQuery] for the [sliver] will be suitably adjusted
+  /// to zero out any sides that were avoided by this sliver.
+  final Widget sliver;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    final EdgeInsets padding = MediaQuery.of(context).padding;
+    return SliverPadding(
+      padding: EdgeInsets.only(
+        left: math.max(left ? padding.left : 0.0, minimum.left),
+        top: math.max(top ? padding.top : 0.0, minimum.top),
+        right: math.max(right ? padding.right : 0.0, minimum.right),
+        bottom: math.max(bottom ? padding.bottom : 0.0, minimum.bottom),
+      ),
+      sliver: MediaQuery.removePadding(
+        context: context,
+        removeLeft: left,
+        removeTop: top,
+        removeRight: right,
+        removeBottom: bottom,
+        child: sliver,
+      ),
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('left', value: left, ifTrue: 'avoid left padding'));
+    properties.add(FlagProperty('top', value: top, ifTrue: 'avoid top padding'));
+    properties.add(FlagProperty('right', value: right, ifTrue: 'avoid right padding'));
+    properties.add(FlagProperty('bottom', value: bottom, ifTrue: 'avoid bottom padding'));
+  }
+}
diff --git a/lib/src/widgets/scroll_activity.dart b/lib/src/widgets/scroll_activity.dart
new file mode 100644
index 0000000..3b52305
--- /dev/null
+++ b/lib/src/widgets/scroll_activity.dart
@@ -0,0 +1,669 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/physics.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'gesture_detector.dart';
+import 'scroll_metrics.dart';
+import 'scroll_notification.dart';
+import 'ticker_provider.dart';
+
+/// A backend for a [ScrollActivity].
+///
+/// Used by subclasses of [ScrollActivity] to manipulate the scroll view that
+/// they are acting upon.
+///
+/// See also:
+///
+///  * [ScrollActivity], which uses this class as its delegate.
+///  * [ScrollPositionWithSingleContext], the main implementation of this interface.
+abstract class ScrollActivityDelegate {
+  /// The direction in which the scroll view scrolls.
+  AxisDirection get axisDirection;
+
+  /// Update the scroll position to the given pixel value.
+  ///
+  /// Returns the overscroll, if any. See [ScrollPosition.setPixels] for more
+  /// information.
+  double setPixels(double pixels);
+
+  /// Updates the scroll position by the given amount.
+  ///
+  /// Appropriate for when the user is directly manipulating the scroll
+  /// position, for example by dragging the scroll view. Typically applies
+  /// [ScrollPhysics.applyPhysicsToUserOffset] and other transformations that
+  /// are appropriate for user-driving scrolling.
+  void applyUserOffset(double delta);
+
+  /// Terminate the current activity and start an idle activity.
+  void goIdle();
+
+  /// Terminate the current activity and start a ballistic activity with the
+  /// given velocity.
+  void goBallistic(double velocity);
+}
+
+/// Base class for scrolling activities like dragging and flinging.
+///
+/// See also:
+///
+///  * [ScrollPosition], which uses [ScrollActivity] objects to manage the
+///    [ScrollPosition] of a [Scrollable].
+abstract class ScrollActivity {
+  /// Initializes [delegate] for subclasses.
+  ScrollActivity(this._delegate);
+
+  /// The delegate that this activity will use to actuate the scroll view.
+  ScrollActivityDelegate get delegate => _delegate;
+  ScrollActivityDelegate _delegate;
+
+  /// Updates the activity's link to the [ScrollActivityDelegate].
+  ///
+  /// This should only be called when an activity is being moved from a defunct
+  /// (or about-to-be defunct) [ScrollActivityDelegate] object to a new one.
+  void updateDelegate(ScrollActivityDelegate value) {
+    assert(_delegate != value);
+    _delegate = value;
+  }
+
+  /// Called by the [ScrollActivityDelegate] when it has changed type (for
+  /// example, when changing from an Android-style scroll position to an
+  /// iOS-style scroll position). If this activity can differ between the two
+  /// modes, then it should tell the position to restart that activity
+  /// appropriately.
+  ///
+  /// For example, [BallisticScrollActivity]'s implementation calls
+  /// [ScrollActivityDelegate.goBallistic].
+  void resetActivity() { }
+
+  /// Dispatch a [ScrollStartNotification] with the given metrics.
+  void dispatchScrollStartNotification(ScrollMetrics metrics, BuildContext? context) {
+    ScrollStartNotification(metrics: metrics, context: context).dispatch(context);
+  }
+
+  /// Dispatch a [ScrollUpdateNotification] with the given metrics and scroll delta.
+  void dispatchScrollUpdateNotification(ScrollMetrics metrics, BuildContext context, double scrollDelta) {
+    ScrollUpdateNotification(metrics: metrics, context: context, scrollDelta: scrollDelta).dispatch(context);
+  }
+
+  /// Dispatch an [OverscrollNotification] with the given metrics and overscroll.
+  void dispatchOverscrollNotification(ScrollMetrics metrics, BuildContext context, double overscroll) {
+    OverscrollNotification(metrics: metrics, context: context, overscroll: overscroll).dispatch(context);
+  }
+
+  /// Dispatch a [ScrollEndNotification] with the given metrics and overscroll.
+  void dispatchScrollEndNotification(ScrollMetrics metrics, BuildContext context) {
+    ScrollEndNotification(metrics: metrics, context: context).dispatch(context);
+  }
+
+  /// Called when the scroll view that is performing this activity changes its metrics.
+  void applyNewDimensions() { }
+
+  /// Whether the scroll view should ignore pointer events while performing this
+  /// activity.
+  ///
+  /// See also:
+  ///
+  ///  * [isScrolling], which describes whether the activity is considered
+  ///    to represent user interaction or not.
+  bool get shouldIgnorePointer;
+
+  /// Whether performing this activity constitutes scrolling.
+  ///
+  /// Used, for example, to determine whether the user scroll
+  /// direction (see [ScrollPosition.userScrollDirection]) is
+  /// [ScrollDirection.idle].
+  ///
+  /// See also:
+  ///
+  ///  * [shouldIgnorePointer], which controls whether pointer events
+  ///    are allowed while the activity is live.
+  ///  * [UserScrollNotification], which exposes this status.
+  bool get isScrolling;
+
+  /// If applicable, the velocity at which the scroll offset is currently
+  /// independently changing (i.e. without external stimuli such as a dragging
+  /// gestures) in logical pixels per second for this activity.
+  double get velocity;
+
+  /// Called when the scroll view stops performing this activity.
+  @mustCallSuper
+  void dispose() { }
+
+  @override
+  String toString() => describeIdentity(this);
+}
+
+/// A scroll activity that does nothing.
+///
+/// When a scroll view is not scrolling, it is performing the idle activity.
+///
+/// If the [Scrollable] changes dimensions, this activity triggers a ballistic
+/// activity to restore the view.
+class IdleScrollActivity extends ScrollActivity {
+  /// Creates a scroll activity that does nothing.
+  IdleScrollActivity(ScrollActivityDelegate delegate) : super(delegate);
+
+  @override
+  void applyNewDimensions() {
+    delegate.goBallistic(0.0);
+  }
+
+  @override
+  bool get shouldIgnorePointer => false;
+
+  @override
+  bool get isScrolling => false;
+
+  @override
+  double get velocity => 0.0;
+}
+
+/// Interface for holding a [Scrollable] stationary.
+///
+/// An object that implements this interface is returned by
+/// [ScrollPosition.hold]. It holds the scrollable stationary until an activity
+/// is started or the [cancel] method is called.
+abstract class ScrollHoldController {
+  /// Release the [Scrollable], potentially letting it go ballistic if
+  /// necessary.
+  void cancel();
+}
+
+/// A scroll activity that does nothing but can be released to resume
+/// normal idle behavior.
+///
+/// This is used while the user is touching the [Scrollable] but before the
+/// touch has become a [Drag].
+///
+/// For the purposes of [ScrollNotification]s, this activity does not constitute
+/// scrolling, and does not prevent the user from interacting with the contents
+/// of the [Scrollable] (unlike when a drag has begun or there is a scroll
+/// animation underway).
+class HoldScrollActivity extends ScrollActivity implements ScrollHoldController {
+  /// Creates a scroll activity that does nothing.
+  HoldScrollActivity({
+    required ScrollActivityDelegate delegate,
+    this.onHoldCanceled,
+  }) : super(delegate);
+
+  /// Called when [dispose] is called.
+  final VoidCallback? onHoldCanceled;
+
+  @override
+  bool get shouldIgnorePointer => false;
+
+  @override
+  bool get isScrolling => false;
+
+  @override
+  double get velocity => 0.0;
+
+  @override
+  void cancel() {
+    delegate.goBallistic(0.0);
+  }
+
+  @override
+  void dispose() {
+    if (onHoldCanceled != null)
+      onHoldCanceled!();
+    super.dispose();
+  }
+}
+
+/// Scrolls a scroll view as the user drags their finger across the screen.
+///
+/// See also:
+///
+///  * [DragScrollActivity], which is the activity the scroll view performs
+///    while a drag is underway.
+class ScrollDragController implements Drag {
+  /// Creates an object that scrolls a scroll view as the user drags their
+  /// finger across the screen.
+  ///
+  /// The [delegate] and `details` arguments must not be null.
+  ScrollDragController({
+    required ScrollActivityDelegate delegate,
+    required DragStartDetails details,
+    this.onDragCanceled,
+    this.carriedVelocity,
+    this.motionStartDistanceThreshold,
+  }) : assert(delegate != null),
+       assert(details != null),
+       assert(
+         motionStartDistanceThreshold == null || motionStartDistanceThreshold > 0.0,
+         'motionStartDistanceThreshold must be a positive number or null'
+       ),
+       _delegate = delegate,
+       _lastDetails = details,
+       _retainMomentum = carriedVelocity != null && carriedVelocity != 0.0,
+       _lastNonStationaryTimestamp = details.sourceTimeStamp,
+       _offsetSinceLastStop = motionStartDistanceThreshold == null ? null : 0.0;
+
+  /// The object that will actuate the scroll view as the user drags.
+  ScrollActivityDelegate get delegate => _delegate;
+  ScrollActivityDelegate _delegate;
+
+  /// Called when [dispose] is called.
+  final VoidCallback? onDragCanceled;
+
+  /// Velocity that was present from a previous [ScrollActivity] when this drag
+  /// began.
+  final double? carriedVelocity;
+
+  /// Amount of pixels in either direction the drag has to move by to start
+  /// scroll movement again after each time scrolling came to a stop.
+  final double? motionStartDistanceThreshold;
+
+  Duration? _lastNonStationaryTimestamp;
+  bool _retainMomentum;
+  /// Null if already in motion or has no [motionStartDistanceThreshold].
+  double? _offsetSinceLastStop;
+
+  /// Maximum amount of time interval the drag can have consecutive stationary
+  /// pointer update events before losing the momentum carried from a previous
+  /// scroll activity.
+  static const Duration momentumRetainStationaryDurationThreshold =
+      Duration(milliseconds: 20);
+
+  /// Maximum amount of time interval the drag can have consecutive stationary
+  /// pointer update events before needing to break the
+  /// [motionStartDistanceThreshold] to start motion again.
+  static const Duration motionStoppedDurationThreshold =
+      Duration(milliseconds: 50);
+
+  /// The drag distance past which, a [motionStartDistanceThreshold] breaking
+  /// drag is considered a deliberate fling.
+  static const double _bigThresholdBreakDistance = 24.0;
+
+  bool get _reversed => axisDirectionIsReversed(delegate.axisDirection);
+
+  /// Updates the controller's link to the [ScrollActivityDelegate].
+  ///
+  /// This should only be called when a controller is being moved from a defunct
+  /// (or about-to-be defunct) [ScrollActivityDelegate] object to a new one.
+  void updateDelegate(ScrollActivityDelegate value) {
+    assert(_delegate != value);
+    _delegate = value;
+  }
+
+  /// Determines whether to lose the existing incoming velocity when starting
+  /// the drag.
+  void _maybeLoseMomentum(double offset, Duration? timestamp) {
+    if (_retainMomentum &&
+        offset == 0.0 &&
+        (timestamp == null || // If drag event has no timestamp, we lose momentum.
+         timestamp - _lastNonStationaryTimestamp! > momentumRetainStationaryDurationThreshold)) {
+      // If pointer is stationary for too long, we lose momentum.
+      _retainMomentum = false;
+    }
+  }
+
+  /// If a motion start threshold exists, determine whether the threshold needs
+  /// to be broken to scroll. Also possibly apply an offset adjustment when
+  /// threshold is first broken.
+  ///
+  /// Returns `0.0` when stationary or within threshold. Returns `offset`
+  /// transparently when already in motion.
+  double _adjustForScrollStartThreshold(double offset, Duration? timestamp) {
+    if (timestamp == null) {
+      // If we can't track time, we can't apply thresholds.
+      // May be null for proxied drags like via accessibility.
+      return offset;
+    }
+    if (offset == 0.0) {
+      if (motionStartDistanceThreshold != null &&
+          _offsetSinceLastStop == null &&
+          timestamp - _lastNonStationaryTimestamp! > motionStoppedDurationThreshold) {
+        // Enforce a new threshold.
+        _offsetSinceLastStop = 0.0;
+      }
+      // Not moving can't break threshold.
+      return 0.0;
+    } else {
+      if (_offsetSinceLastStop == null) {
+        // Already in motion or no threshold behavior configured such as for
+        // Android. Allow transparent offset transmission.
+        return offset;
+      } else {
+        _offsetSinceLastStop = _offsetSinceLastStop! + offset;
+        if (_offsetSinceLastStop!.abs() > motionStartDistanceThreshold!) {
+          // Threshold broken.
+          _offsetSinceLastStop = null;
+          if (offset.abs() > _bigThresholdBreakDistance) {
+            // This is heuristically a very deliberate fling. Leave the motion
+            // unaffected.
+            return offset;
+          } else {
+            // This is a normal speed threshold break.
+            return math.min(
+              // Ease into the motion when the threshold is initially broken
+              // to avoid a visible jump.
+              motionStartDistanceThreshold! / 3.0,
+              offset.abs(),
+            ) * offset.sign;
+          }
+        } else {
+          return 0.0;
+        }
+      }
+    }
+  }
+
+  @override
+  void update(DragUpdateDetails details) {
+    assert(details.primaryDelta != null);
+    _lastDetails = details;
+    double offset = details.primaryDelta!;
+    if (offset != 0.0) {
+      _lastNonStationaryTimestamp = details.sourceTimeStamp;
+    }
+    // By default, iOS platforms carries momentum and has a start threshold
+    // (configured in [BouncingScrollPhysics]). The 2 operations below are
+    // no-ops on Android.
+    _maybeLoseMomentum(offset, details.sourceTimeStamp);
+    offset = _adjustForScrollStartThreshold(offset, details.sourceTimeStamp);
+    if (offset == 0.0) {
+      return;
+    }
+    if (_reversed) // e.g. an AxisDirection.up scrollable
+      offset = -offset;
+    delegate.applyUserOffset(offset);
+  }
+
+  @override
+  void end(DragEndDetails details) {
+    assert(details.primaryVelocity != null);
+    // We negate the velocity here because if the touch is moving downwards,
+    // the scroll has to move upwards. It's the same reason that update()
+    // above negates the delta before applying it to the scroll offset.
+    double velocity = -details.primaryVelocity!;
+    if (_reversed) // e.g. an AxisDirection.up scrollable
+      velocity = -velocity;
+    _lastDetails = details;
+
+    // Build momentum only if dragging in the same direction.
+    if (_retainMomentum && velocity.sign == carriedVelocity!.sign)
+      velocity += carriedVelocity!;
+    delegate.goBallistic(velocity);
+  }
+
+  @override
+  void cancel() {
+    delegate.goBallistic(0.0);
+  }
+
+  /// Called by the delegate when it is no longer sending events to this object.
+  @mustCallSuper
+  void dispose() {
+    _lastDetails = null;
+    if (onDragCanceled != null)
+      onDragCanceled!();
+  }
+
+  /// The most recently observed [DragStartDetails], [DragUpdateDetails], or
+  /// [DragEndDetails] object.
+  dynamic get lastDetails => _lastDetails;
+  dynamic _lastDetails;
+
+  @override
+  String toString() => describeIdentity(this);
+}
+
+/// The activity a scroll view performs when a the user drags their finger
+/// across the screen.
+///
+/// See also:
+///
+///  * [ScrollDragController], which listens to the [Drag] and actually scrolls
+///    the scroll view.
+class DragScrollActivity extends ScrollActivity {
+  /// Creates an activity for when the user drags their finger across the
+  /// screen.
+  DragScrollActivity(
+    ScrollActivityDelegate delegate,
+    ScrollDragController controller,
+  ) : _controller = controller,
+      super(delegate);
+
+  ScrollDragController? _controller;
+
+  @override
+  void dispatchScrollStartNotification(ScrollMetrics metrics, BuildContext? context) {
+    final dynamic lastDetails = _controller!.lastDetails;
+    assert(lastDetails is DragStartDetails);
+    ScrollStartNotification(metrics: metrics, context: context, dragDetails: lastDetails as DragStartDetails).dispatch(context);
+  }
+
+  @override
+  void dispatchScrollUpdateNotification(ScrollMetrics metrics, BuildContext context, double scrollDelta) {
+    final dynamic lastDetails = _controller!.lastDetails;
+    assert(lastDetails is DragUpdateDetails);
+    ScrollUpdateNotification(metrics: metrics, context: context, scrollDelta: scrollDelta, dragDetails: lastDetails as DragUpdateDetails).dispatch(context);
+  }
+
+  @override
+  void dispatchOverscrollNotification(ScrollMetrics metrics, BuildContext context, double overscroll) {
+    final dynamic lastDetails = _controller!.lastDetails;
+    assert(lastDetails is DragUpdateDetails);
+    OverscrollNotification(metrics: metrics, context: context, overscroll: overscroll, dragDetails: lastDetails as DragUpdateDetails).dispatch(context);
+  }
+
+  @override
+  void dispatchScrollEndNotification(ScrollMetrics metrics, BuildContext context) {
+    // We might not have DragEndDetails yet if we're being called from beginActivity.
+    final dynamic lastDetails = _controller!.lastDetails;
+    ScrollEndNotification(
+      metrics: metrics,
+      context: context,
+      dragDetails: lastDetails is DragEndDetails ? lastDetails : null,
+    ).dispatch(context);
+  }
+
+  @override
+  bool get shouldIgnorePointer => true;
+
+  @override
+  bool get isScrolling => true;
+
+  // DragScrollActivity is not independently changing velocity yet
+  // until the drag is ended.
+  @override
+  double get velocity => 0.0;
+
+  @override
+  void dispose() {
+    _controller = null;
+    super.dispose();
+  }
+
+  @override
+  String toString() {
+    return '${describeIdentity(this)}($_controller)';
+  }
+}
+
+/// An activity that animates a scroll view based on a physics [Simulation].
+///
+/// A [BallisticScrollActivity] is typically used when the user lifts their
+/// finger off the screen to continue the scrolling gesture with the current velocity.
+///
+/// [BallisticScrollActivity] is also used to restore a scroll view to a valid
+/// scroll offset when the geometry of the scroll view changes. In these
+/// situations, the [Simulation] typically starts with a zero velocity.
+///
+/// See also:
+///
+///  * [DrivenScrollActivity], which animates a scroll view based on a set of
+///    animation parameters.
+class BallisticScrollActivity extends ScrollActivity {
+  /// Creates an activity that animates a scroll view based on a [simulation].
+  ///
+  /// The [delegate], [simulation], and [vsync] arguments must not be null.
+  BallisticScrollActivity(
+    ScrollActivityDelegate delegate,
+    Simulation simulation,
+    TickerProvider vsync,
+  ) : super(delegate) {
+    _controller = AnimationController.unbounded(
+      debugLabel: kDebugMode ? objectRuntimeType(this, 'BallisticScrollActivity') : null,
+      vsync: vsync,
+    )
+      ..addListener(_tick)
+      ..animateWith(simulation)
+       .whenComplete(_end); // won't trigger if we dispose _controller first
+  }
+
+  late AnimationController _controller;
+
+  @override
+  void resetActivity() {
+    delegate.goBallistic(velocity);
+  }
+
+  @override
+  void applyNewDimensions() {
+    delegate.goBallistic(velocity);
+  }
+
+  void _tick() {
+    if (!applyMoveTo(_controller.value))
+      delegate.goIdle();
+  }
+
+  /// Move the position to the given location.
+  ///
+  /// If the new position was fully applied, returns true. If there was any
+  /// overflow, returns false.
+  ///
+  /// The default implementation calls [ScrollActivityDelegate.setPixels]
+  /// and returns true if the overflow was zero.
+  @protected
+  bool applyMoveTo(double value) {
+    return delegate.setPixels(value) == 0.0;
+  }
+
+  void _end() {
+    delegate.goBallistic(0.0);
+  }
+
+  @override
+  void dispatchOverscrollNotification(ScrollMetrics metrics, BuildContext context, double overscroll) {
+    OverscrollNotification(metrics: metrics, context: context, overscroll: overscroll, velocity: velocity).dispatch(context);
+  }
+
+  @override
+  bool get shouldIgnorePointer => true;
+
+  @override
+  bool get isScrolling => true;
+
+  @override
+  double get velocity => _controller.velocity;
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  String toString() {
+    return '${describeIdentity(this)}($_controller)';
+  }
+}
+
+/// An activity that animates a scroll view based on animation parameters.
+///
+/// For example, a [DrivenScrollActivity] is used to implement
+/// [ScrollController.animateTo].
+///
+/// See also:
+///
+///  * [BallisticScrollActivity], which animates a scroll view based on a
+///    physics [Simulation].
+class DrivenScrollActivity extends ScrollActivity {
+  /// Creates an activity that animates a scroll view based on animation
+  /// parameters.
+  ///
+  /// All of the parameters must be non-null.
+  DrivenScrollActivity(
+    ScrollActivityDelegate delegate, {
+    required double from,
+    required double to,
+    required Duration duration,
+    required Curve curve,
+    required TickerProvider vsync,
+  }) : assert(from != null),
+       assert(to != null),
+       assert(duration != null),
+       assert(duration > Duration.zero),
+       assert(curve != null),
+       super(delegate) {
+    _completer = Completer<void>();
+    _controller = AnimationController.unbounded(
+      value: from,
+      debugLabel: objectRuntimeType(this, 'DrivenScrollActivity'),
+      vsync: vsync,
+    )
+      ..addListener(_tick)
+      ..animateTo(to, duration: duration, curve: curve)
+       .whenComplete(_end); // won't trigger if we dispose _controller first
+  }
+
+  late final Completer<void> _completer;
+  late final AnimationController _controller;
+
+  /// A [Future] that completes when the activity stops.
+  ///
+  /// For example, this [Future] will complete if the animation reaches the end
+  /// or if the user interacts with the scroll view in way that causes the
+  /// animation to stop before it reaches the end.
+  Future<void> get done => _completer.future;
+
+  void _tick() {
+    if (delegate.setPixels(_controller.value) != 0.0)
+      delegate.goIdle();
+  }
+
+  void _end() {
+    delegate.goBallistic(velocity);
+  }
+
+  @override
+  void dispatchOverscrollNotification(ScrollMetrics metrics, BuildContext context, double overscroll) {
+    OverscrollNotification(metrics: metrics, context: context, overscroll: overscroll, velocity: velocity).dispatch(context);
+  }
+
+  @override
+  bool get shouldIgnorePointer => true;
+
+  @override
+  bool get isScrolling => true;
+
+  @override
+  double get velocity => _controller.velocity;
+
+  @override
+  void dispose() {
+    _completer.complete();
+    _controller.dispose();
+    super.dispose();
+  }
+
+  @override
+  String toString() {
+    return '${describeIdentity(this)}($_controller)';
+  }
+}
diff --git a/lib/src/widgets/scroll_aware_image_provider.dart b/lib/src/widgets/scroll_aware_image_provider.dart
new file mode 100644
index 0000000..3c97f46
--- /dev/null
+++ b/lib/src/widgets/scroll_aware_image_provider.dart
@@ -0,0 +1,114 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+
+import 'package:flute/painting.dart';
+import 'package:flute/scheduler.dart';
+
+import 'disposable_build_context.dart';
+import 'framework.dart';
+import 'scrollable.dart';
+
+/// An [ImageProvider] that makes use of
+/// [Scrollable.recommendDeferredLoadingForContext] to avoid loading images when
+/// rapidly scrolling.
+///
+/// This provider assumes that its wrapped [imageProvider] correctly uses the
+/// [ImageCache], and does not attempt to re-acquire or decode images in the
+/// cache.
+///
+/// Calling [resolve] on this provider will cause it to obtain the image key
+/// and then check the following:
+///
+///   1. If the returned [ImageStream] has been completed, end. This can happen
+///      if the caller sets the completer on the stream.
+///   2. If the [ImageCache] has a completer for the key for this image, ask the
+///      wrapped provider to resolve.
+///      This can happen if the image was precached, or another [ImageProvider]
+///      already resolved the same image.
+///   3. If the [context] has been disposed, end. This can happen if the caller
+///      has been disposed and is no longer interested in resolving the image.
+///   4. If the widget is scrolling with high velocity at this point in time,
+///      wait until the beginning of the next frame and go back to step 1.
+///   5. Delegate loading the image to the wrapped provider and finish.
+///
+/// If the cycle ends at steps 1 or 3, the [ImageStream] will never be marked as
+/// complete and listeners will not be notified.
+///
+/// The [Image] widget wraps its incoming providers with this provider to avoid
+/// overutilization of resources for images that would never appear on screen or
+/// only be visible for a very brief period.
+@optionalTypeArgs
+class ScrollAwareImageProvider<T extends Object> extends ImageProvider<T> {
+  /// Creates a [ScrollAwareImageProvider].
+  ///
+  /// The [context] object is the [BuildContext] of the [State] using this
+  /// provider. It is used to determine scrolling velocity during [resolve]. It
+  /// must not be null.
+  ///
+  /// The [imageProvider] is used to create a key and load the image. It must
+  /// not be null, and is assumed to interact with the cache in the normal way
+  /// that [ImageProvider.resolveStreamForKey] does.
+  const ScrollAwareImageProvider({
+    required this.context,
+    required this.imageProvider,
+  }) : assert(context != null),
+       assert(imageProvider != null);
+
+  /// The context that may or may not be enclosed by a [Scrollable].
+  ///
+  /// Once [DisposableBuildContext.dispose] is called on this context,
+  /// the provider will stop trying to resolve the image if it has not already
+  /// been resolved.
+  final DisposableBuildContext context;
+
+  /// The wrapped image provider to delegate [obtainKey] and [load] to.
+  final ImageProvider<T> imageProvider;
+
+  @override
+  void resolveStreamForKey(
+    ImageConfiguration configuration,
+    ImageStream stream,
+    T key,
+    ImageErrorListener handleError,
+  ) {
+    // Something managed to complete the stream, or it's already in the image
+    // cache. Notify the wrapped provider and expect it to behave by not
+    // reloading the image since it's already resolved.
+    // Do this even if the context has gone out of the tree, since it will
+    // update LRU information about the cache. Even though we never showed the
+    // image, it was still touched more recently.
+    // Do this before checking scrolling, so that if the bytes are available we
+    // render them even though we're scrolling fast - there's no additional
+    // allocations to do for texture memory, it's already there.
+    if (stream.completer != null || PaintingBinding.instance!.imageCache!.containsKey(key)) {
+      imageProvider.resolveStreamForKey(configuration, stream, key, handleError);
+      return;
+    }
+    // The context has gone out of the tree - ignore it.
+    if (context.context == null) {
+      return;
+    }
+    // Something still wants this image, but check if the context is scrolling
+    // too fast before scheduling work that might never show on screen.
+    // Try to get to end of the frame callbacks of the next frame, and then
+    // check again.
+    if (Scrollable.recommendDeferredLoadingForContext(context.context!)) {
+        SchedulerBinding.instance!.scheduleFrameCallback((_) {
+          scheduleMicrotask(() => resolveStreamForKey(configuration, stream, key, handleError));
+        });
+        return;
+    }
+    // We are in the tree, we're not scrolling too fast, the cache doesn't
+    // have our image, and no one has otherwise completed the stream.  Go.
+    imageProvider.resolveStreamForKey(configuration, stream, key, handleError);
+  }
+
+  @override
+  ImageStreamCompleter load(T key, DecoderCallback decode) => imageProvider.load(key, decode);
+
+  @override
+  Future<T> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration);
+}
diff --git a/lib/src/widgets/scroll_configuration.dart b/lib/src/widgets/scroll_configuration.dart
new file mode 100644
index 0000000..0937c86
--- /dev/null
+++ b/lib/src/widgets/scroll_configuration.dart
@@ -0,0 +1,157 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+
+import 'framework.dart';
+import 'overscroll_indicator.dart';
+import 'scroll_physics.dart';
+
+const Color _kDefaultGlowColor = Color(0xFFFFFFFF);
+
+/// Describes how [Scrollable] widgets should behave.
+///
+/// Used by [ScrollConfiguration] to configure the [Scrollable] widgets in a
+/// subtree.
+@immutable
+class ScrollBehavior {
+  /// Creates a description of how [Scrollable] widgets should behave.
+  const ScrollBehavior();
+
+  /// The platform whose scroll physics should be implemented.
+  ///
+  /// Defaults to the current platform.
+  TargetPlatform getPlatform(BuildContext context) => defaultTargetPlatform;
+
+  /// Wraps the given widget, which scrolls in the given [AxisDirection].
+  ///
+  /// For example, on Android, this method wraps the given widget with a
+  /// [GlowingOverscrollIndicator] to provide visual feedback when the user
+  /// overscrolls.
+  Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
+    // When modifying this function, consider modifying the implementation in
+    // _MaterialScrollBehavior as well.
+    switch (getPlatform(context)) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.linux:
+      case TargetPlatform.macOS:
+      case TargetPlatform.windows:
+        return child;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+        return GlowingOverscrollIndicator(
+          child: child,
+          axisDirection: axisDirection,
+          color: _kDefaultGlowColor,
+        );
+    }
+  }
+
+  /// Specifies the type of velocity tracker to use in the descendant
+  /// [Scrollable]s' drag gesture recognizers, for estimating the velocity of a
+  /// drag gesture.
+  ///
+  /// This can be used to, for example, apply different fling velocity
+  /// estimation methods on different platforms, in order to match the
+  /// platform's native behavior.
+  ///
+  /// Typically, the provided [GestureVelocityTrackerBuilder] should return a
+  /// fresh velocity tracker. If null is returned, [Scrollable] creates a new
+  /// [VelocityTracker] to track the newly added pointer that may develop into
+  /// a drag gesture.
+  ///
+  /// The default implementation provides a new
+  /// [IOSScrollViewFlingVelocityTracker] on iOS and macOS for each new pointer,
+  /// and a new [VelocityTracker] on other platforms for each new pointer.
+  GestureVelocityTrackerBuilder velocityTrackerBuilder(BuildContext context) {
+    switch (getPlatform(context)) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        return (PointerEvent event) => IOSScrollViewFlingVelocityTracker(event.kind);
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        return (PointerEvent event) => VelocityTracker.withKind(event.kind);
+    }
+  }
+
+  static const ScrollPhysics _bouncingPhysics = BouncingScrollPhysics(parent: RangeMaintainingScrollPhysics());
+  static const ScrollPhysics _clampingPhysics = ClampingScrollPhysics(parent: RangeMaintainingScrollPhysics());
+
+  /// The scroll physics to use for the platform given by [getPlatform].
+  ///
+  /// Defaults to [RangeMaintainingScrollPhysics] mixed with
+  /// [BouncingScrollPhysics] on iOS and [ClampingScrollPhysics] on
+  /// Android.
+  ScrollPhysics getScrollPhysics(BuildContext context) {
+    switch (getPlatform(context)) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        return _bouncingPhysics;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        return _clampingPhysics;
+    }
+  }
+
+  /// Called whenever a [ScrollConfiguration] is rebuilt with a new
+  /// [ScrollBehavior] of the same [runtimeType].
+  ///
+  /// If the new instance represents different information than the old
+  /// instance, then the method should return true, otherwise it should return
+  /// false.
+  ///
+  /// If this method returns true, all the widgets that inherit from the
+  /// [ScrollConfiguration] will rebuild using the new [ScrollBehavior]. If this
+  /// method returns false, the rebuilds might be optimized away.
+  bool shouldNotify(covariant ScrollBehavior oldDelegate) => false;
+
+  @override
+  String toString() => objectRuntimeType(this, 'ScrollBehavior');
+}
+
+/// Controls how [Scrollable] widgets behave in a subtree.
+///
+/// The scroll configuration determines the [ScrollPhysics] and viewport
+/// decorations used by descendants of [child].
+class ScrollConfiguration extends InheritedWidget {
+  /// Creates a widget that controls how [Scrollable] widgets behave in a subtree.
+  ///
+  /// The [behavior] and [child] arguments must not be null.
+  const ScrollConfiguration({
+    Key? key,
+    required this.behavior,
+    required Widget child,
+  }) : super(key: key, child: child);
+
+  /// How [Scrollable] widgets that are descendants of [child] should behave.
+  final ScrollBehavior behavior;
+
+  /// The [ScrollBehavior] for [Scrollable] widgets in the given [BuildContext].
+  ///
+  /// If no [ScrollConfiguration] widget is in scope of the given `context`,
+  /// a default [ScrollBehavior] instance is returned.
+  static ScrollBehavior of(BuildContext context) {
+    final ScrollConfiguration? configuration = context.dependOnInheritedWidgetOfExactType<ScrollConfiguration>();
+    return configuration?.behavior ?? const ScrollBehavior();
+  }
+
+  @override
+  bool updateShouldNotify(ScrollConfiguration oldWidget) {
+    assert(behavior != null);
+    return behavior.runtimeType != oldWidget.behavior.runtimeType
+        || (behavior != oldWidget.behavior && behavior.shouldNotify(oldWidget.behavior));
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ScrollBehavior>('behavior', behavior));
+  }
+}
diff --git a/lib/src/widgets/scroll_context.dart b/lib/src/widgets/scroll_context.dart
new file mode 100644
index 0000000..48b1127
--- /dev/null
+++ b/lib/src/widgets/scroll_context.dart
@@ -0,0 +1,71 @@
+// Copyright 2014 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.
+
+import 'package:flute/scheduler.dart';
+import 'package:flute/rendering.dart';
+
+import 'framework.dart';
+import 'ticker_provider.dart';
+
+/// An interface that [Scrollable] widgets implement in order to use
+/// [ScrollPosition].
+///
+/// See also:
+///
+///  * [ScrollableState], which is the most common implementation of this
+///    interface.
+///  * [ScrollPosition], which uses this interface to communicate with the
+///    scrollable widget.
+abstract class ScrollContext {
+  /// The [BuildContext] that should be used when dispatching
+  /// [ScrollNotification]s.
+  ///
+  /// This context is typically different that the context of the scrollable
+  /// widget itself. For example, [Scrollable] uses a context outside the
+  /// [Viewport] but inside the widgets created by
+  /// [ScrollBehavior.buildViewportChrome].
+  BuildContext? get notificationContext;
+
+  /// The [BuildContext] that should be used when searching for a [PageStorage].
+  ///
+  /// This context is typically the context of the scrollable widget itself. In
+  /// particular, it should involve any [GlobalKey]s that are dynamically
+  /// created as part of creating the scrolling widget, since those would be
+  /// different each time the widget is created.
+  // TODO(goderbauer): Deprecate this when state restoration supports all features of PageStorage.
+  BuildContext get storageContext;
+
+  /// A [TickerProvider] to use when animating the scroll position.
+  TickerProvider get vsync;
+
+  /// The direction in which the widget scrolls.
+  AxisDirection get axisDirection;
+
+  /// Whether the contents of the widget should ignore [PointerEvent] inputs.
+  ///
+  /// Setting this value to true prevents the use from interacting with the
+  /// contents of the widget with pointer events. The widget itself is still
+  /// interactive.
+  ///
+  /// For example, if the scroll position is being driven by an animation, it
+  /// might be appropriate to set this value to ignore pointer events to
+  /// prevent the user from accidentally interacting with the contents of the
+  /// widget as it animates. The user will still be able to touch the widget,
+  /// potentially stopping the animation.
+  void setIgnorePointer(bool value);
+
+  /// Whether the user can drag the widget, for example to initiate a scroll.
+  void setCanDrag(bool value);
+
+  /// Set the [SemanticsAction]s that should be expose to the semantics tree.
+  void setSemanticsActions(Set<SemanticsAction> actions);
+
+  /// Called by the [ScrollPosition] whenever scrolling ends to persist the
+  /// provided scroll `offset` for state restoration purposes.
+  ///
+  /// The [ScrollContext] may pass the value back to a [ScrollPosition] by
+  /// calling [ScrollPosition.restoreOffset] at a later point in time or after
+  /// the application has restarted to restore the scroll offset.
+  void saveOffset(double offset);
+}
diff --git a/lib/src/widgets/scroll_controller.dart b/lib/src/widgets/scroll_controller.dart
new file mode 100644
index 0000000..43c7421
--- /dev/null
+++ b/lib/src/widgets/scroll_controller.dart
@@ -0,0 +1,378 @@
+// Copyright 2014 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.
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+
+import 'scroll_context.dart';
+import 'scroll_physics.dart';
+import 'scroll_position.dart';
+import 'scroll_position_with_single_context.dart';
+
+/// Controls a scrollable widget.
+///
+/// Scroll controllers are typically stored as member variables in [State]
+/// objects and are reused in each [State.build]. A single scroll controller can
+/// be used to control multiple scrollable widgets, but some operations, such
+/// as reading the scroll [offset], require the controller to be used with a
+/// single scrollable widget.
+///
+/// A scroll controller creates a [ScrollPosition] to manage the state specific
+/// to an individual [Scrollable] widget. To use a custom [ScrollPosition],
+/// subclass [ScrollController] and override [createScrollPosition].
+///
+/// A [ScrollController] is a [Listenable]. It notifies its listeners whenever
+/// any of the attached [ScrollPosition]s notify _their_ listeners (i.e.
+/// whenever any of them scroll). It does not notify its listeners when the list
+/// of attached [ScrollPosition]s changes.
+///
+/// Typically used with [ListView], [GridView], [CustomScrollView].
+///
+/// See also:
+///
+///  * [ListView], [GridView], [CustomScrollView], which can be controlled by a
+///    [ScrollController].
+///  * [Scrollable], which is the lower-level widget that creates and associates
+///    [ScrollPosition] objects with [ScrollController] objects.
+///  * [PageController], which is an analogous object for controlling a
+///    [PageView].
+///  * [ScrollPosition], which manages the scroll offset for an individual
+///    scrolling widget.
+///  * [ScrollNotification] and [NotificationListener], which can be used to watch
+///    the scroll position without using a [ScrollController].
+class ScrollController extends ChangeNotifier {
+  /// Creates a controller for a scrollable widget.
+  ///
+  /// The values of `initialScrollOffset` and `keepScrollOffset` must not be null.
+  ScrollController({
+    double initialScrollOffset = 0.0,
+    this.keepScrollOffset = true,
+    this.debugLabel,
+  }) : assert(initialScrollOffset != null),
+       assert(keepScrollOffset != null),
+       _initialScrollOffset = initialScrollOffset;
+
+  /// The initial value to use for [offset].
+  ///
+  /// New [ScrollPosition] objects that are created and attached to this
+  /// controller will have their offset initialized to this value
+  /// if [keepScrollOffset] is false or a scroll offset hasn't been saved yet.
+  ///
+  /// Defaults to 0.0.
+  double get initialScrollOffset => _initialScrollOffset;
+  final double _initialScrollOffset;
+
+  /// Each time a scroll completes, save the current scroll [offset] with
+  /// [PageStorage] and restore it if this controller's scrollable is recreated.
+  ///
+  /// If this property is set to false, the scroll offset is never saved
+  /// and [initialScrollOffset] is always used to initialize the scroll
+  /// offset. If true (the default), the initial scroll offset is used the
+  /// first time the controller's scrollable is created, since there's no
+  /// scroll offset to restore yet. Subsequently the saved offset is
+  /// restored and [initialScrollOffset] is ignored.
+  ///
+  /// See also:
+  ///
+  ///  * [PageStorageKey], which should be used when more than one
+  ///    scrollable appears in the same route, to distinguish the [PageStorage]
+  ///    locations used to save scroll offsets.
+  final bool keepScrollOffset;
+
+  /// A label that is used in the [toString] output. Intended to aid with
+  /// identifying scroll controller instances in debug output.
+  final String? debugLabel;
+
+  /// The currently attached positions.
+  ///
+  /// This should not be mutated directly. [ScrollPosition] objects can be added
+  /// and removed using [attach] and [detach].
+  @protected
+  Iterable<ScrollPosition> get positions => _positions;
+  final List<ScrollPosition> _positions = <ScrollPosition>[];
+
+  /// Whether any [ScrollPosition] objects have attached themselves to the
+  /// [ScrollController] using the [attach] method.
+  ///
+  /// If this is false, then members that interact with the [ScrollPosition],
+  /// such as [position], [offset], [animateTo], and [jumpTo], must not be
+  /// called.
+  bool get hasClients => _positions.isNotEmpty;
+
+  /// Returns the attached [ScrollPosition], from which the actual scroll offset
+  /// of the [ScrollView] can be obtained.
+  ///
+  /// Calling this is only valid when only a single position is attached.
+  ScrollPosition get position {
+    assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
+    assert(_positions.length == 1, 'ScrollController attached to multiple scroll views.');
+    return _positions.single;
+  }
+
+  /// The current scroll offset of the scrollable widget.
+  ///
+  /// Requires the controller to be controlling exactly one scrollable widget.
+  double get offset => position.pixels;
+
+  /// Animates the position from its current value to the given value.
+  ///
+  /// Any active animation is canceled. If the user is currently scrolling, that
+  /// action is canceled.
+  ///
+  /// The returned [Future] will complete when the animation ends, whether it
+  /// completed successfully or whether it was interrupted prematurely.
+  ///
+  /// An animation will be interrupted whenever the user attempts to scroll
+  /// manually, or whenever another activity is started, or whenever the
+  /// animation reaches the edge of the viewport and attempts to overscroll. (If
+  /// the [ScrollPosition] does not overscroll but instead allows scrolling
+  /// beyond the extents, then going beyond the extents will not interrupt the
+  /// animation.)
+  ///
+  /// The animation is indifferent to changes to the viewport or content
+  /// dimensions.
+  ///
+  /// Once the animation has completed, the scroll position will attempt to
+  /// begin a ballistic activity in case its value is not stable (for example,
+  /// if it is scrolled beyond the extents and in that situation the scroll
+  /// position would normally bounce back).
+  ///
+  /// The duration must not be zero. To jump to a particular value without an
+  /// animation, use [jumpTo].
+  ///
+  /// When calling [animateTo] in widget tests, `await`ing the returned
+  /// [Future] may cause the test to hang and timeout. Instead, use
+  /// [WidgetTester.pumpAndSettle].
+  Future<void> animateTo(
+    double offset, {
+    required Duration duration,
+    required Curve curve,
+  }) async {
+    assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
+    await Future.wait<void>(<Future<void>>[
+      for (int i = 0; i < _positions.length; i += 1) _positions[i].animateTo(offset, duration: duration, curve: curve),
+    ]);
+  }
+
+  /// Jumps the scroll position from its current value to the given value,
+  /// without animation, and without checking if the new value is in range.
+  ///
+  /// Any active animation is canceled. If the user is currently scrolling, that
+  /// action is canceled.
+  ///
+  /// If this method changes the scroll position, a sequence of start/update/end
+  /// scroll notifications will be dispatched. No overscroll notifications can
+  /// be generated by this method.
+  ///
+  /// Immediately after the jump, a ballistic activity is started, in case the
+  /// value was out of range.
+  void jumpTo(double value) {
+    assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
+    for (final ScrollPosition position in List<ScrollPosition>.from(_positions))
+      position.jumpTo(value);
+  }
+
+  /// Register the given position with this controller.
+  ///
+  /// After this function returns, the [animateTo] and [jumpTo] methods on this
+  /// controller will manipulate the given position.
+  void attach(ScrollPosition position) {
+    assert(!_positions.contains(position));
+    _positions.add(position);
+    position.addListener(notifyListeners);
+  }
+
+  /// Unregister the given position with this controller.
+  ///
+  /// After this function returns, the [animateTo] and [jumpTo] methods on this
+  /// controller will not manipulate the given position.
+  void detach(ScrollPosition position) {
+    assert(_positions.contains(position));
+    position.removeListener(notifyListeners);
+    _positions.remove(position);
+  }
+
+  @override
+  void dispose() {
+    for (final ScrollPosition position in _positions)
+      position.removeListener(notifyListeners);
+    super.dispose();
+  }
+
+  /// Creates a [ScrollPosition] for use by a [Scrollable] widget.
+  ///
+  /// Subclasses can override this function to customize the [ScrollPosition]
+  /// used by the scrollable widgets they control. For example, [PageController]
+  /// overrides this function to return a page-oriented scroll position
+  /// subclass that keeps the same page visible when the scrollable widget
+  /// resizes.
+  ///
+  /// By default, returns a [ScrollPositionWithSingleContext].
+  ///
+  /// The arguments are generally passed to the [ScrollPosition] being created:
+  ///
+  ///  * `physics`: An instance of [ScrollPhysics] that determines how the
+  ///    [ScrollPosition] should react to user interactions, how it should
+  ///    simulate scrolling when released or flung, etc. The value will not be
+  ///    null. It typically comes from the [ScrollView] or other widget that
+  ///    creates the [Scrollable], or, if none was provided, from the ambient
+  ///    [ScrollConfiguration].
+  ///  * `context`: A [ScrollContext] used for communicating with the object
+  ///    that is to own the [ScrollPosition] (typically, this is the
+  ///    [Scrollable] itself).
+  ///  * `oldPosition`: If this is not the first time a [ScrollPosition] has
+  ///    been created for this [Scrollable], this will be the previous instance.
+  ///    This is used when the environment has changed and the [Scrollable]
+  ///    needs to recreate the [ScrollPosition] object. It is null the first
+  ///    time the [ScrollPosition] is created.
+  ScrollPosition createScrollPosition(
+    ScrollPhysics physics,
+    ScrollContext context,
+    ScrollPosition? oldPosition,
+  ) {
+    return ScrollPositionWithSingleContext(
+      physics: physics,
+      context: context,
+      initialPixels: initialScrollOffset,
+      keepScrollOffset: keepScrollOffset,
+      oldPosition: oldPosition,
+      debugLabel: debugLabel,
+    );
+  }
+
+  @override
+  String toString() {
+    final List<String> description = <String>[];
+    debugFillDescription(description);
+    return '${describeIdentity(this)}(${description.join(", ")})';
+  }
+
+  /// Add additional information to the given description for use by [toString].
+  ///
+  /// This method makes it easier for subclasses to coordinate to provide a
+  /// high-quality [toString] implementation. The [toString] implementation on
+  /// the [ScrollController] base class calls [debugFillDescription] to collect
+  /// useful information from subclasses to incorporate into its return value.
+  ///
+  /// If you override this, make sure to start your method with a call to
+  /// `super.debugFillDescription(description)`.
+  @mustCallSuper
+  void debugFillDescription(List<String> description) {
+    if (debugLabel != null)
+      description.add(debugLabel!);
+    if (initialScrollOffset != 0.0)
+      description.add('initialScrollOffset: ${initialScrollOffset.toStringAsFixed(1)}, ');
+    if (_positions.isEmpty) {
+      description.add('no clients');
+    } else if (_positions.length == 1) {
+      // Don't actually list the client itself, since its toString may refer to us.
+      description.add('one client, offset ${offset.toStringAsFixed(1)}');
+    } else {
+      description.add('${_positions.length} clients');
+    }
+  }
+}
+
+// Examples can assume:
+// // @dart = 2.9
+// TrackingScrollController _trackingScrollController;
+
+/// A [ScrollController] whose [initialScrollOffset] tracks its most recently
+/// updated [ScrollPosition].
+///
+/// This class can be used to synchronize the scroll offset of two or more
+/// lazily created scroll views that share a single [TrackingScrollController].
+/// It tracks the most recently updated scroll position and reports it as its
+/// `initialScrollOffset`.
+///
+/// {@tool snippet}
+///
+/// In this example each [PageView] page contains a [ListView] and all three
+/// [ListView]'s share a [TrackingScrollController]. The scroll offsets of all
+/// three list views will track each other, to the extent that's possible given
+/// the different list lengths.
+///
+/// ```dart
+/// PageView(
+///   children: <Widget>[
+///     ListView(
+///       controller: _trackingScrollController,
+///       children: List<Widget>.generate(100, (int i) => Text('page 0 item $i')).toList(),
+///     ),
+///     ListView(
+///       controller: _trackingScrollController,
+///       children: List<Widget>.generate(200, (int i) => Text('page 1 item $i')).toList(),
+///     ),
+///     ListView(
+///      controller: _trackingScrollController,
+///      children: List<Widget>.generate(300, (int i) => Text('page 2 item $i')).toList(),
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// In this example the `_trackingController` would have been created by the
+/// stateful widget that built the widget tree.
+class TrackingScrollController extends ScrollController {
+  /// Creates a scroll controller that continually updates its
+  /// [initialScrollOffset] to match the last scroll notification it received.
+  TrackingScrollController({
+    double initialScrollOffset = 0.0,
+    bool keepScrollOffset = true,
+    String? debugLabel,
+  }) : super(initialScrollOffset: initialScrollOffset,
+             keepScrollOffset: keepScrollOffset,
+             debugLabel: debugLabel);
+
+  final Map<ScrollPosition, VoidCallback> _positionToListener = <ScrollPosition, VoidCallback>{};
+  ScrollPosition? _lastUpdated;
+  double? _lastUpdatedOffset;
+
+  /// The last [ScrollPosition] to change. Returns null if there aren't any
+  /// attached scroll positions, or there hasn't been any scrolling yet, or the
+  /// last [ScrollPosition] to change has since been removed.
+  ScrollPosition? get mostRecentlyUpdatedPosition => _lastUpdated;
+
+  /// Returns the scroll offset of the [mostRecentlyUpdatedPosition] or, if that
+  /// is null, the initial scroll offset provided to the constructor.
+  ///
+  /// See also:
+  ///
+  ///  * [ScrollController.initialScrollOffset], which this overrides.
+  @override
+  double get initialScrollOffset => _lastUpdatedOffset ?? super.initialScrollOffset;
+
+  @override
+  void attach(ScrollPosition position) {
+    super.attach(position);
+    assert(!_positionToListener.containsKey(position));
+    _positionToListener[position] = () {
+      _lastUpdated = position;
+      _lastUpdatedOffset = position.pixels;
+    };
+    position.addListener(_positionToListener[position]!);
+  }
+
+  @override
+  void detach(ScrollPosition position) {
+    super.detach(position);
+    assert(_positionToListener.containsKey(position));
+    position.removeListener(_positionToListener[position]!);
+    _positionToListener.remove(position);
+    if (_lastUpdated == position)
+      _lastUpdated = null;
+    if (_positionToListener.isEmpty)
+      _lastUpdatedOffset = null;
+  }
+
+  @override
+  void dispose() {
+    for (final ScrollPosition position in positions) {
+      assert(_positionToListener.containsKey(position));
+      position.removeListener(_positionToListener[position]!);
+    }
+    super.dispose();
+  }
+}
diff --git a/lib/src/widgets/scroll_metrics.dart b/lib/src/widgets/scroll_metrics.dart
new file mode 100644
index 0000000..1b03bf9
--- /dev/null
+++ b/lib/src/widgets/scroll_metrics.dart
@@ -0,0 +1,177 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+/// A description of a [Scrollable]'s contents, useful for modeling the state
+/// of its viewport.
+///
+/// This class defines a current position, [pixels], and a range of values
+/// considered "in bounds" for that position. The range has a minimum value at
+/// [minScrollExtent] and a maximum value at [maxScrollExtent] (inclusive). The
+/// viewport scrolls in the direction and axis described by [axisDirection]
+/// and [axis].
+///
+/// The [outOfRange] getter will return true if [pixels] is outside this defined
+/// range. The [atEdge] getter will return true if the [pixels] position equals
+/// either the [minScrollExtent] or the [maxScrollExtent].
+///
+/// The dimensions of the viewport in the given [axis] are described by
+/// [viewportDimension].
+///
+/// The above values are also exposed in terms of [extentBefore],
+/// [extentInside], and [extentAfter], which may be more useful for use cases
+/// such as scroll bars; for example, see [Scrollbar].
+///
+/// See also:
+///
+///  * [FixedScrollMetrics], which is an immutable object that implements this
+///    interface.
+abstract class ScrollMetrics {
+  /// Creates a [ScrollMetrics] that has the same properties as this object.
+  ///
+  /// This is useful if this object is mutable, but you want to get a snapshot
+  /// of the current state.
+  ///
+  /// The named arguments allow the values to be adjusted in the process. This
+  /// is useful to examine hypothetical situations, for example "would applying
+  /// this delta unmodified take the position [outOfRange]?".
+  ScrollMetrics copyWith({
+    double? minScrollExtent,
+    double? maxScrollExtent,
+    double? pixels,
+    double? viewportDimension,
+    AxisDirection? axisDirection,
+  }) {
+    return FixedScrollMetrics(
+      minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
+      maxScrollExtent: maxScrollExtent ?? (hasContentDimensions ? this.maxScrollExtent : null),
+      pixels: pixels ?? (hasPixels ? this.pixels : null),
+      viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
+      axisDirection: axisDirection ?? this.axisDirection,
+    );
+  }
+
+  /// The minimum in-range value for [pixels].
+  ///
+  /// The actual [pixels] value might be [outOfRange].
+  ///
+  /// This value should typically be non-null and less than or equal to
+  /// [maxScrollExtent]. It can be negative infinity, if the scroll is unbounded.
+  double get minScrollExtent;
+
+  /// The maximum in-range value for [pixels].
+  ///
+  /// The actual [pixels] value might be [outOfRange].
+  ///
+  /// This value should typically be non-null and greater than or equal to
+  /// [minScrollExtent]. It can be infinity, if the scroll is unbounded.
+  double get maxScrollExtent;
+
+  /// Whether the [minScrollExtent] and the [maxScrollExtent] properties are available.
+  bool get hasContentDimensions;
+
+  /// The current scroll position, in logical pixels along the [axisDirection].
+  double get pixels;
+
+  /// Whether the [pixels] property is available.
+  bool get hasPixels;
+
+  /// The extent of the viewport along the [axisDirection].
+  double get viewportDimension;
+
+  /// Whether the [viewportDimension] property is available.
+  bool get hasViewportDimension;
+
+  /// The direction in which the scroll view scrolls.
+  AxisDirection get axisDirection;
+
+  /// The axis in which the scroll view scrolls.
+  Axis get axis => axisDirectionToAxis(axisDirection);
+
+  /// Whether the [pixels] value is outside the [minScrollExtent] and
+  /// [maxScrollExtent].
+  bool get outOfRange => pixels < minScrollExtent || pixels > maxScrollExtent;
+
+  /// Whether the [pixels] value is exactly at the [minScrollExtent] or the
+  /// [maxScrollExtent].
+  bool get atEdge => pixels == minScrollExtent || pixels == maxScrollExtent;
+
+  /// The quantity of content conceptually "above" the viewport in the scrollable.
+  /// This is the content above the content described by [extentInside].
+  double get extentBefore => math.max(pixels - minScrollExtent, 0.0);
+
+  /// The quantity of content conceptually "inside" the viewport in the scrollable.
+  ///
+  /// The value is typically the height of the viewport when [outOfRange] is false.
+  /// It could be less if there is less content visible than the size of the
+  /// viewport, such as when overscrolling.
+  ///
+  /// The value is always non-negative, and less than or equal to [viewportDimension].
+  double get extentInside {
+    assert(minScrollExtent <= maxScrollExtent);
+    return viewportDimension
+      // "above" overscroll value
+      - (minScrollExtent - pixels).clamp(0, viewportDimension)
+      // "below" overscroll value
+      - (pixels - maxScrollExtent).clamp(0, viewportDimension);
+  }
+
+  /// The quantity of content conceptually "below" the viewport in the scrollable.
+  /// This is the content below the content described by [extentInside].
+  double get extentAfter => math.max(maxScrollExtent - pixels, 0.0);
+}
+
+/// An immutable snapshot of values associated with a [Scrollable] viewport.
+///
+/// For details, see [ScrollMetrics], which defines this object's interfaces.
+class FixedScrollMetrics extends ScrollMetrics {
+  /// Creates an immutable snapshot of values associated with a [Scrollable] viewport.
+  FixedScrollMetrics({
+    required double? minScrollExtent,
+    required double? maxScrollExtent,
+    required double? pixels,
+    required double? viewportDimension,
+    required this.axisDirection,
+  }) : _minScrollExtent = minScrollExtent,
+       _maxScrollExtent = maxScrollExtent,
+       _pixels = pixels,
+       _viewportDimension = viewportDimension;
+
+  @override
+  double get minScrollExtent => _minScrollExtent!;
+  final double? _minScrollExtent;
+
+  @override
+  double get maxScrollExtent => _maxScrollExtent!;
+  final double? _maxScrollExtent;
+
+  @override
+  bool get hasContentDimensions => _minScrollExtent != null && _maxScrollExtent != null;
+
+  @override
+  double get pixels => _pixels!;
+  final double? _pixels;
+
+  @override
+  bool get hasPixels => _pixels != null;
+
+  @override
+  double get viewportDimension => _viewportDimension!;
+  final double? _viewportDimension;
+
+  @override
+  bool get hasViewportDimension => _viewportDimension != null;
+
+  @override
+  final AxisDirection axisDirection;
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'FixedScrollMetrics')}(${extentBefore.toStringAsFixed(1)}..[${extentInside.toStringAsFixed(1)}]..${extentAfter.toStringAsFixed(1)})';
+  }
+}
diff --git a/lib/src/widgets/scroll_notification.dart b/lib/src/widgets/scroll_notification.dart
new file mode 100644
index 0000000..3e11dc4
--- /dev/null
+++ b/lib/src/widgets/scroll_notification.dart
@@ -0,0 +1,290 @@
+// Copyright 2014 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.
+
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+
+import 'framework.dart';
+import 'notification_listener.dart';
+import 'scroll_metrics.dart';
+
+/// Mixin for [Notification]s that track how many [RenderAbstractViewport] they
+/// have bubbled through.
+///
+/// This is used by [ScrollNotification] and [OverscrollIndicatorNotification].
+mixin ViewportNotificationMixin on Notification {
+  /// The number of viewports that this notification has bubbled through.
+  ///
+  /// Typically listeners only respond to notifications with a [depth] of zero.
+  ///
+  /// Specifically, this is the number of [Widget]s representing
+  /// [RenderAbstractViewport] render objects through which this notification
+  /// has bubbled.
+  int get depth => _depth;
+  int _depth = 0;
+
+  @override
+  bool visitAncestor(Element element) {
+    if (element is RenderObjectElement && element.renderObject is RenderAbstractViewport)
+      _depth += 1;
+    return super.visitAncestor(element);
+  }
+
+  @override
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('depth: $depth (${ depth == 0 ? "local" : "remote"})');
+  }
+}
+
+/// A [Notification] related to scrolling.
+///
+/// [Scrollable] widgets notify their ancestors about scrolling-related changes.
+/// The notifications have the following lifecycle:
+///
+///  * A [ScrollStartNotification], which indicates that the widget has started
+///    scrolling.
+///  * Zero or more [ScrollUpdateNotification]s, which indicate that the widget
+///    has changed its scroll position, mixed with zero or more
+///    [OverscrollNotification]s, which indicate that the widget has not changed
+///    its scroll position because the change would have caused its scroll
+///    position to go outside its scroll bounds.
+///  * Interspersed with the [ScrollUpdateNotification]s and
+///    [OverscrollNotification]s are zero or more [UserScrollNotification]s,
+///    which indicate that the user has changed the direction in which they are
+///    scrolling.
+///  * A [ScrollEndNotification], which indicates that the widget has stopped
+///    scrolling.
+///  * A [UserScrollNotification], with a [UserScrollNotification.direction] of
+///    [ScrollDirection.idle].
+///
+/// Notifications bubble up through the tree, which means a given
+/// [NotificationListener] will receive notifications for all descendant
+/// [Scrollable] widgets. To focus on notifications from the nearest
+/// [Scrollable] descendant, check that the [depth] property of the notification
+/// is zero.
+///
+/// When a scroll notification is received by a [NotificationListener], the
+/// listener will have already completed build and layout, and it is therefore
+/// too late for that widget to call [State.setState]. Any attempt to adjust the
+/// build or layout based on a scroll notification would result in a layout that
+/// lagged one frame behind, which is a poor user experience. Scroll
+/// notifications are therefore primarily useful for paint effects (since paint
+/// happens after layout). The [GlowingOverscrollIndicator] and [Scrollbar]
+/// widgets are examples of paint effects that use scroll notifications.
+///
+/// To drive layout based on the scroll position, consider listening to the
+/// [ScrollPosition] directly (or indirectly via a [ScrollController]).
+abstract class ScrollNotification extends LayoutChangedNotification with ViewportNotificationMixin {
+  /// Initializes fields for subclasses.
+  ScrollNotification({
+    required this.metrics,
+    required this.context,
+  });
+
+  /// A description of a [Scrollable]'s contents, useful for modeling the state
+  /// of its viewport.
+  final ScrollMetrics metrics;
+
+  /// The build context of the widget that fired this notification.
+  ///
+  /// This can be used to find the scrollable's render objects to determine the
+  /// size of the viewport, for instance.
+  final BuildContext? context;
+
+  @override
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('$metrics');
+  }
+}
+
+/// A notification that a [Scrollable] widget has started scrolling.
+///
+/// See also:
+///
+///  * [ScrollEndNotification], which indicates that scrolling has stopped.
+///  * [ScrollNotification], which describes the notification lifecycle.
+class ScrollStartNotification extends ScrollNotification {
+  /// Creates a notification that a [Scrollable] widget has started scrolling.
+  ScrollStartNotification({
+    required ScrollMetrics metrics,
+    required BuildContext? context,
+    this.dragDetails,
+  }) : super(metrics: metrics, context: context);
+
+  /// If the [Scrollable] started scrolling because of a drag, the details about
+  /// that drag start.
+  ///
+  /// Otherwise, null.
+  final DragStartDetails? dragDetails;
+
+  @override
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    if (dragDetails != null)
+      description.add('$dragDetails');
+  }
+}
+
+/// A notification that a [Scrollable] widget has changed its scroll position.
+///
+/// See also:
+///
+///  * [OverscrollNotification], which indicates that a [Scrollable] widget
+///    has not changed its scroll position because the change would have caused
+///    its scroll position to go outside its scroll bounds.
+///  * [ScrollNotification], which describes the notification lifecycle.
+class ScrollUpdateNotification extends ScrollNotification {
+  /// Creates a notification that a [Scrollable] widget has changed its scroll
+  /// position.
+  ScrollUpdateNotification({
+    required ScrollMetrics metrics,
+    required BuildContext context,
+    this.dragDetails,
+    this.scrollDelta,
+  }) : super(metrics: metrics, context: context);
+
+  /// If the [Scrollable] changed its scroll position because of a drag, the
+  /// details about that drag update.
+  ///
+  /// Otherwise, null.
+  final DragUpdateDetails? dragDetails;
+
+  /// The distance by which the [Scrollable] was scrolled, in logical pixels.
+  final double? scrollDelta;
+
+  @override
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('scrollDelta: $scrollDelta');
+    if (dragDetails != null)
+      description.add('$dragDetails');
+  }
+}
+
+/// A notification that a [Scrollable] widget has not changed its scroll position
+/// because the change would have caused its scroll position to go outside of
+/// its scroll bounds.
+///
+/// See also:
+///
+///  * [ScrollUpdateNotification], which indicates that a [Scrollable] widget
+///    has changed its scroll position.
+///  * [ScrollNotification], which describes the notification lifecycle.
+class OverscrollNotification extends ScrollNotification {
+  /// Creates a notification that a [Scrollable] widget has changed its scroll
+  /// position outside of its scroll bounds.
+  OverscrollNotification({
+    required ScrollMetrics metrics,
+    required BuildContext context,
+    this.dragDetails,
+    required this.overscroll,
+    this.velocity = 0.0,
+  }) : assert(overscroll != null),
+       assert(overscroll.isFinite),
+       assert(overscroll != 0.0),
+       assert(velocity != null),
+       super(metrics: metrics, context: context);
+
+  /// If the [Scrollable] overscrolled because of a drag, the details about that
+  /// drag update.
+  ///
+  /// Otherwise, null.
+  final DragUpdateDetails? dragDetails;
+
+  /// The number of logical pixels that the [Scrollable] avoided scrolling.
+  ///
+  /// This will be negative for overscroll on the "start" side and positive for
+  /// overscroll on the "end" side.
+  final double overscroll;
+
+  /// The velocity at which the [ScrollPosition] was changing when this
+  /// overscroll happened.
+  ///
+  /// This will typically be 0.0 for touch-driven overscrolls, and positive
+  /// for overscrolls that happened from a [BallisticScrollActivity] or
+  /// [DrivenScrollActivity].
+  final double velocity;
+
+  @override
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('overscroll: ${overscroll.toStringAsFixed(1)}');
+    description.add('velocity: ${velocity.toStringAsFixed(1)}');
+    if (dragDetails != null)
+      description.add('$dragDetails');
+  }
+}
+
+/// A notification that a [Scrollable] widget has stopped scrolling.
+///
+/// See also:
+///
+///  * [ScrollStartNotification], which indicates that scrolling has started.
+///  * [ScrollNotification], which describes the notification lifecycle.
+class ScrollEndNotification extends ScrollNotification {
+  /// Creates a notification that a [Scrollable] widget has stopped scrolling.
+  ScrollEndNotification({
+    required ScrollMetrics metrics,
+    required BuildContext context,
+    this.dragDetails,
+  }) : super(metrics: metrics, context: context);
+
+  /// If the [Scrollable] stopped scrolling because of a drag, the details about
+  /// that drag end.
+  ///
+  /// Otherwise, null.
+  ///
+  /// If a drag ends with some residual velocity, a typical [ScrollPhysics] will
+  /// start a ballistic scroll, which delays the [ScrollEndNotification] until
+  /// the ballistic simulation completes, at which time [dragDetails] will
+  /// be null. If the residual velocity is too small to trigger ballistic
+  /// scrolling, then the [ScrollEndNotification] will be dispatched immediately
+  /// and [dragDetails] will be non-null.
+  final DragEndDetails? dragDetails;
+
+  @override
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    if (dragDetails != null)
+      description.add('$dragDetails');
+  }
+}
+
+/// A notification that the user has changed the direction in which they are
+/// scrolling.
+///
+/// See also:
+///
+///  * [ScrollNotification], which describes the notification lifecycle.
+class UserScrollNotification extends ScrollNotification {
+  /// Creates a notification that the user has changed the direction in which
+  /// they are scrolling.
+  UserScrollNotification({
+    required ScrollMetrics metrics,
+    required BuildContext context,
+    required this.direction,
+  }) : super(metrics: metrics, context: context);
+
+  /// The direction in which the user is scrolling.
+  final ScrollDirection direction;
+
+  @override
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('direction: $direction');
+  }
+}
+
+/// A predicate for [ScrollNotification], used to customize widgets that
+/// listen to notifications from their children.
+typedef ScrollNotificationPredicate = bool Function(ScrollNotification notification);
+
+/// A [ScrollNotificationPredicate] that checks whether
+/// `notification.depth == 0`, which means that the notification did not bubble
+/// through any intervening scrolling widgets.
+bool defaultScrollNotificationPredicate(ScrollNotification notification) {
+  return notification.depth == 0;
+}
diff --git a/lib/src/widgets/scroll_physics.dart b/lib/src/widgets/scroll_physics.dart
new file mode 100644
index 0000000..bbf498a
--- /dev/null
+++ b/lib/src/widgets/scroll_physics.dart
@@ -0,0 +1,821 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/physics.dart';
+
+import 'binding.dart' show WidgetsBinding;
+import 'framework.dart';
+import 'overscroll_indicator.dart';
+import 'scroll_metrics.dart';
+import 'scroll_simulation.dart';
+
+export 'package:flute/physics.dart' show Simulation, ScrollSpringSimulation, Tolerance;
+
+// Examples can assume:
+// // @dart = 2.9
+// class FooScrollPhysics extends ScrollPhysics {
+//   const FooScrollPhysics({ ScrollPhysics parent }): super(parent: parent);
+// }
+// class BarScrollPhysics extends ScrollPhysics {
+//   const BarScrollPhysics({ ScrollPhysics parent }): super(parent: parent);
+// }
+
+/// Determines the physics of a [Scrollable] widget.
+///
+/// For example, determines how the [Scrollable] will behave when the user
+/// reaches the maximum scroll extent or when the user stops scrolling.
+///
+/// When starting a physics [Simulation], the current scroll position and
+/// velocity are used as the initial conditions for the particle in the
+/// simulation. The movement of the particle in the simulation is then used to
+/// determine the scroll position for the widget.
+///
+/// Instead of creating your own subclasses, [parent] can be used to combine
+/// [ScrollPhysics] objects of different types to get the desired scroll physics.
+/// For example:
+///
+/// ```dart
+/// const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics())
+/// ```
+///
+/// You can also use `applyTo`, which is useful when you already have
+/// an instance of `ScrollPhysics`:
+///
+/// ```dart
+/// ScrollPhysics physics = const BouncingScrollPhysics();
+/// // ...
+/// physics.applyTo(const AlwaysScrollableScrollPhysics())
+/// ```
+@immutable
+class ScrollPhysics {
+  /// Creates an object with the default scroll physics.
+  const ScrollPhysics({ this.parent });
+
+  /// If non-null, determines the default behavior for each method.
+  ///
+  /// If a subclass of [ScrollPhysics] does not override a method, that subclass
+  /// will inherit an implementation from this base class that defers to
+  /// [parent]. This mechanism lets you assemble novel combinations of
+  /// [ScrollPhysics] subclasses at runtime. For example:
+  ///
+  /// ```dart
+  /// const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics())
+  /// ```
+  ///
+  /// will result in a [ScrollPhysics] that has the combined behavior
+  /// of [BouncingScrollPhysics] and [AlwaysScrollableScrollPhysics]:
+  /// behaviors that are not specified in [BouncingScrollPhysics]
+  /// (e.g. [shouldAcceptUserOffset]) will defer to [AlwaysScrollableScrollPhysics].
+  final ScrollPhysics? parent;
+
+  /// If [parent] is null then return ancestor, otherwise recursively build a
+  /// ScrollPhysics that has [ancestor] as its parent.
+  ///
+  /// This method is typically used to define [applyTo] methods like:
+  ///
+  /// ```dart
+  /// FooScrollPhysics applyTo(ScrollPhysics ancestor) {
+  ///   return FooScrollPhysics(parent: buildParent(ancestor));
+  /// }
+  /// ```
+  @protected
+  ScrollPhysics? buildParent(ScrollPhysics? ancestor) => parent?.applyTo(ancestor) ?? ancestor;
+
+  /// Combines this [ScrollPhysics] instance with the given physics.
+  ///
+  /// The returned object uses this instance's physics when it has an
+  /// opinion, and defers to the given `ancestor` object's physics
+  /// when it does not.
+  ///
+  /// If [parent] is null then this returns a [ScrollPhysics] with the
+  /// same [runtimeType], but where the [parent] has been replaced
+  /// with the [ancestor].
+  ///
+  /// If this scroll physics object already has a parent, then this
+  /// method is applied recursively and ancestor will appear at the
+  /// end of the existing chain of parents.
+  ///
+  /// Calling this method with a null argument will copy the current
+  /// object. This is inefficient.
+  ///
+  /// {@tool snippet}
+  ///
+  /// In the following example, the [applyTo] method is used to combine the
+  /// scroll physics of two [ScrollPhysics] objects. The resulting [ScrollPhysics]
+  /// `x` has the same behavior as `y`.
+  ///
+  /// ```dart
+  /// final FooScrollPhysics x = FooScrollPhysics().applyTo(BarScrollPhysics());
+  /// const FooScrollPhysics y = FooScrollPhysics(parent: BarScrollPhysics());
+  /// ```
+  /// {@end-tool}
+  ///
+  /// ## Implementing `applyTo`
+  ///
+  /// When creating a custom [ScrollPhysics] subclass, this method
+  /// must be implemented. If the physics class has no constructor
+  /// arguments, then implementing this method is merely a matter of
+  /// calling the constructor with a [parent] constructed using
+  /// [buildParent], as follows:
+  ///
+  /// ```dart
+  /// FooScrollPhysics applyTo(ScrollPhysics ancestor) {
+  ///   return FooScrollPhysics(parent: buildParent(ancestor));
+  /// }
+  /// ```
+  ///
+  /// If the physics class has constructor arguments, they must be passed to
+  /// the constructor here as well, so as to create a clone.
+  ///
+  /// See also:
+  ///
+  ///  * [buildParent], a utility method that's often used to define [applyTo]
+  ///    methods for [ScrollPhysics] subclasses.
+  ScrollPhysics applyTo(ScrollPhysics? ancestor) {
+    return ScrollPhysics(parent: buildParent(ancestor));
+  }
+
+  /// Used by [DragScrollActivity] and other user-driven activities to convert
+  /// an offset in logical pixels as provided by the [DragUpdateDetails] into a
+  /// delta to apply (subtract from the current position) using
+  /// [ScrollActivityDelegate.setPixels].
+  ///
+  /// This is used by some [ScrollPosition] subclasses to apply friction during
+  /// overscroll situations.
+  ///
+  /// This method must not adjust parts of the offset that are entirely within
+  /// the bounds described by the given `position`.
+  ///
+  /// The given `position` is only valid during this method call. Do not keep a
+  /// reference to it to use later, as the values may update, may not update, or
+  /// may update to reflect an entirely unrelated scrollable.
+  double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
+    if (parent == null)
+      return offset;
+    return parent!.applyPhysicsToUserOffset(position, offset);
+  }
+
+  /// Whether the scrollable should let the user adjust the scroll offset, for
+  /// example by dragging.
+  ///
+  /// By default, the user can manipulate the scroll offset if, and only if,
+  /// there is actually content outside the viewport to reveal.
+  ///
+  /// The given `position` is only valid during this method call. Do not keep a
+  /// reference to it to use later, as the values may update, may not update, or
+  /// may update to reflect an entirely unrelated scrollable.
+  bool shouldAcceptUserOffset(ScrollMetrics position) {
+    if (parent == null)
+      return position.pixels != 0.0 || position.minScrollExtent != position.maxScrollExtent;
+    return parent!.shouldAcceptUserOffset(position);
+  }
+
+  /// Provides a heuristic to determine if expensive frame-bound tasks should be
+  /// deferred.
+  ///
+  /// The velocity parameter must not be null, but may be positive, negative, or
+  /// zero.
+  ///
+  /// The metrics parameter must not be null.
+  ///
+  /// The context parameter must not be null. It normally refers to the
+  /// [BuildContext] of the widget making the call, such as an [Image] widget
+  /// in a [ListView].
+  ///
+  /// This can be used to determine whether decoding or fetching complex data
+  /// for the currently visible part of the viewport should be delayed
+  /// to avoid doing work that will not have a chance to appear before a new
+  /// frame is rendered.
+  ///
+  /// For example, a list of images could use this logic to delay decoding
+  /// images until scrolling is slow enough to actually render the decoded
+  /// image to the screen.
+  ///
+  /// The default implementation is a heuristic that compares the current
+  /// scroll velocity in local logical pixels to the longest side of the window
+  /// in physical pixels. Implementers can change this heuristic by overriding
+  /// this method and providing their custom physics to the scrollable widget.
+  /// For example, an application that changes the local coordinate system with
+  /// a large perspective transform could provide a more or less aggressive
+  /// heuristic depending on whether the transform was increasing or decreasing
+  /// the overall scale between the global screen and local scrollable
+  /// coordinate systems.
+  ///
+  /// The default implementation is stateless, and simply provides a point-in-
+  /// time decision about how fast the scrollable is scrolling. It would always
+  /// return true for a scrollable that is animating back and forth at high
+  /// velocity in a loop. It is assumed that callers will handle such
+  /// a case, or that a custom stateful implementation would be written that
+  /// tracks the sign of the velocity on successive calls.
+  ///
+  /// Returning true from this method indicates that the current scroll velocity
+  /// is great enough that expensive operations impacting the UI should be
+  /// deferred.
+  bool recommendDeferredLoading(double velocity, ScrollMetrics metrics, BuildContext context) {
+    assert(velocity != null);
+    assert(metrics != null);
+    assert(context != null);
+    if (parent == null) {
+      final double maxPhysicalPixels = WidgetsBinding.instance!.window.physicalSize.longestSide;
+      return velocity.abs() > maxPhysicalPixels;
+    }
+    return parent!.recommendDeferredLoading(velocity, metrics, context);
+  }
+
+  /// Determines the overscroll by applying the boundary conditions.
+  ///
+  /// Called by [ScrollPosition.applyBoundaryConditions], which is called by
+  /// [ScrollPosition.setPixels] just before the [ScrollPosition.pixels] value
+  /// is updated, to determine how much of the offset is to be clamped off and
+  /// sent to [ScrollPosition.didOverscrollBy].
+  ///
+  /// The `value` argument is guaranteed to not equal the [ScrollMetrics.pixels]
+  /// of the `position` argument when this is called.
+  ///
+  /// It is possible for this method to be called when the `position` describes
+  /// an already-out-of-bounds position. In that case, the boundary conditions
+  /// should usually only prevent a further increase in the extent to which the
+  /// position is out of bounds, allowing a decrease to be applied successfully,
+  /// so that (for instance) an animation can smoothly snap an out of bounds
+  /// position to the bounds. See [BallisticScrollActivity].
+  ///
+  /// This method must not clamp parts of the offset that are entirely within
+  /// the bounds described by the given `position`.
+  ///
+  /// The given `position` is only valid during this method call. Do not keep a
+  /// reference to it to use later, as the values may update, may not update, or
+  /// may update to reflect an entirely unrelated scrollable.
+  ///
+  /// ## Examples
+  ///
+  /// [BouncingScrollPhysics] returns zero. In other words, it allows scrolling
+  /// past the boundary unhindered.
+  ///
+  /// [ClampingScrollPhysics] returns the amount by which the value is beyond
+  /// the position or the boundary, whichever is furthest from the content. In
+  /// other words, it disallows scrolling past the boundary, but allows
+  /// scrolling back from being overscrolled, if for some reason the position
+  /// ends up overscrolled.
+  double applyBoundaryConditions(ScrollMetrics position, double value) {
+    if (parent == null)
+      return 0.0;
+    return parent!.applyBoundaryConditions(position, value);
+  }
+
+  /// Describes what the scroll position should be given new viewport dimensions.
+  ///
+  /// This is called by [ScrollPosition.correctForNewDimensions].
+  ///
+  /// The arguments consist of the scroll metrics as they stood in the previous
+  /// frame and the scroll metrics as they now stand after the last layout,
+  /// including the position and minimum and maximum scroll extents; a flag
+  /// indicating if the current [ScrollActivity] considers that the user is
+  /// actively scrolling (see [ScrollActivity.isScrolling]); and the current
+  /// velocity of the scroll position, if it is being driven by the scroll
+  /// activity (this is 0.0 during a user gesture) (see
+  /// [ScrollActivity.velocity]).
+  ///
+  /// The scroll metrics will be identical except for the
+  /// [ScrollMetrics.minScrollExtent] and [ScrollMetrics.maxScrollExtent]. They
+  /// are referred to as the `oldPosition` and `newPosition` (even though they
+  /// both technically have the same "position", in the form of
+  /// [ScrollMetrics.pixels]) because they are generated from the
+  /// [ScrollPosition] before and after updating the scroll extents.
+  ///
+  /// If the returned value does not exactly match the scroll offset given by
+  /// the `newPosition` argument (see [ScrollMetrics.pixels]), then the
+  /// [ScrollPosition] will call [ScrollPosition.correctPixels] to update the
+  /// new scroll position to the returned value, and layout will be re-run. This
+  /// is expensive. The new value is subject to further manipulation by
+  /// [applyBoundaryConditions].
+  ///
+  /// If the returned value _does_ match the `newPosition.pixels` scroll offset
+  /// exactly, then [ScrollPosition.applyNewDimensions] will be called next. In
+  /// that case, [applyBoundaryConditions] is not applied to the return value.
+  ///
+  /// The given [ScrollMetrics] are only valid during this method call. Do not
+  /// keep references to them to use later, as the values may update, may not
+  /// update, or may update to reflect an entirely unrelated scrollable.
+  ///
+  /// The default implementation returns the [ScrollMetrics.pixels] of the
+  /// `newPosition`, which indicates that the current scroll offset is
+  /// acceptable.
+  ///
+  /// See also:
+  ///
+  ///  * [RangeMaintainingScrollPhysics], which is enabled by default, and
+  ///    which prevents unexpected changes to the content dimensions from
+  ///    causing the scroll position to get any further out of bounds.
+  double adjustPositionForNewDimensions({
+    required ScrollMetrics oldPosition,
+    required ScrollMetrics newPosition,
+    required bool isScrolling,
+    required double velocity,
+  }) {
+    if (parent == null)
+      return newPosition.pixels;
+    return parent!.adjustPositionForNewDimensions(oldPosition: oldPosition, newPosition: newPosition, isScrolling: isScrolling, velocity: velocity);
+  }
+
+  /// Returns a simulation for ballistic scrolling starting from the given
+  /// position with the given velocity.
+  ///
+  /// This is used by [ScrollPositionWithSingleContext] in the
+  /// [ScrollPositionWithSingleContext.goBallistic] method. If the result
+  /// is non-null, [ScrollPositionWithSingleContext] will begin a
+  /// [BallisticScrollActivity] with the returned value. Otherwise, it will
+  /// begin an idle activity instead.
+  ///
+  /// The given `position` is only valid during this method call. Do not keep a
+  /// reference to it to use later, as the values may update, may not update, or
+  /// may update to reflect an entirely unrelated scrollable.
+  Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {
+    if (parent == null)
+      return null;
+    return parent!.createBallisticSimulation(position, velocity);
+  }
+
+  static final SpringDescription _kDefaultSpring = SpringDescription.withDampingRatio(
+    mass: 0.5,
+    stiffness: 100.0,
+    ratio: 1.1,
+  );
+
+  /// The spring to use for ballistic simulations.
+  SpringDescription get spring => parent?.spring ?? _kDefaultSpring;
+
+  /// The default accuracy to which scrolling is computed.
+  static final Tolerance _kDefaultTolerance = Tolerance(
+    // TODO(ianh): Handle the case of the device pixel ratio changing.
+    // TODO(ianh): Get this from the local MediaQuery not dart:ui's window object.
+    velocity: 1.0 / (0.050 * WidgetsBinding.instance!.window.devicePixelRatio), // logical pixels per second
+    distance: 1.0 / WidgetsBinding.instance!.window.devicePixelRatio, // logical pixels
+  );
+
+  /// The tolerance to use for ballistic simulations.
+  Tolerance get tolerance => parent?.tolerance ?? _kDefaultTolerance;
+
+  /// The minimum distance an input pointer drag must have moved to
+  /// to be considered a scroll fling gesture.
+  ///
+  /// This value is typically compared with the distance traveled along the
+  /// scrolling axis.
+  ///
+  /// See also:
+  ///
+  ///  * [VelocityTracker.getVelocityEstimate], which computes the velocity
+  ///    of a press-drag-release gesture.
+  double get minFlingDistance => parent?.minFlingDistance ?? kTouchSlop;
+
+  /// The minimum velocity for an input pointer drag to be considered a
+  /// scroll fling.
+  ///
+  /// This value is typically compared with the magnitude of fling gesture's
+  /// velocity along the scrolling axis.
+  ///
+  /// See also:
+  ///
+  ///  * [VelocityTracker.getVelocityEstimate], which computes the velocity
+  ///    of a press-drag-release gesture.
+  double get minFlingVelocity => parent?.minFlingVelocity ?? kMinFlingVelocity;
+
+  /// Scroll fling velocity magnitudes will be clamped to this value.
+  double get maxFlingVelocity => parent?.maxFlingVelocity ?? kMaxFlingVelocity;
+
+  /// Returns the velocity carried on repeated flings.
+  ///
+  /// The function is applied to the existing scroll velocity when another
+  /// scroll drag is applied in the same direction.
+  ///
+  /// By default, physics for platforms other than iOS doesn't carry momentum.
+  double carriedMomentum(double existingVelocity) {
+    if (parent == null)
+      return 0.0;
+    return parent!.carriedMomentum(existingVelocity);
+  }
+
+  /// The minimum amount of pixel distance drags must move by to start motion
+  /// the first time or after each time the drag motion stopped.
+  ///
+  /// If null, no minimum threshold is enforced.
+  double? get dragStartDistanceMotionThreshold => parent?.dragStartDistanceMotionThreshold;
+
+  /// Whether a viewport is allowed to change its scroll position implicitly in
+  /// response to a call to [RenderObject.showOnScreen].
+  ///
+  /// [RenderObject.showOnScreen] is for example used to bring a text field
+  /// fully on screen after it has received focus. This property controls
+  /// whether the viewport associated with this object is allowed to change the
+  /// scroll position to fulfill such a request.
+  bool get allowImplicitScrolling => true;
+
+  @override
+  String toString() {
+    if (parent == null)
+      return objectRuntimeType(this, 'ScrollPhsyics');
+    return '${objectRuntimeType(this, 'ScrollPhysics')} -> $parent';
+  }
+}
+
+/// Scroll physics that attempt to keep the scroll position in range when the
+/// contents change dimensions suddenly.
+///
+/// If the scroll position is already out of range, this attempts to maintain
+/// the amount of overscroll or underscroll already present.
+///
+/// If the scroll activity is animating the scroll position, sudden changes to
+/// the scroll dimensions are allowed to happen (so as to prevent animations
+/// from jumping back and forth between in-range and out-of-range values).
+///
+/// These physics should be combined with other scroll physics, e.g.
+/// [BouncingScrollPhysics] or [ClampingScrollPhysics], to obtain a complete
+/// description of typical scroll physics. See [applyTo].
+///
+/// ## Implementation details
+///
+/// Specifically, these physics perform two adjustments.
+///
+/// The first is to maintain overscroll when the position is out of range.
+///
+/// The second is to enforce the boundary when the position is in range.
+///
+/// If the current velocity is non-zero, neither adjustment is made. The
+/// assumption is that there is an ongoing animation and therefore
+/// further changing the scroll position would disrupt the experience.
+///
+/// If the extents haven't changed, then the overscroll adjustment is
+/// not made. The assumption is that if the position is overscrolled,
+/// it is intentional, otherwise the position could not have reached
+/// that position. (Consider [ClampingScrollPhysics] vs
+/// [BouncingScrollPhysics] for example.)
+///
+/// If the position itself changed since the last animation frame,
+/// then the overscroll is not maintained. The assumption is similar
+/// to the previous case: the position would not have been placed out
+/// of range unless it was intentional.
+///
+/// In addition, if the position changed and the boundaries were and
+/// still are finite, then the boundary isn't enforced either, for
+/// the same reason. However, if any of the boundaries were or are
+/// now infinite, the boundary _is_ enforced, on the assumption that
+/// infinite boundaries indicate a lazy-loading scroll view, which
+/// cannot enforce boundaries while the full list has not loaded.
+///
+/// If the range was out of range, then the boundary is not enforced
+/// even if the range is not maintained. If the range is maintained,
+/// then the distance between the old position and the old boundary is
+/// applied to the new boundary to obtain the new position.
+///
+/// If the range was in range, and the boundary is to be enforced,
+/// then the new position is obtained by deferring to the other physics,
+/// if any, and then clamped to the new range.
+class RangeMaintainingScrollPhysics extends ScrollPhysics {
+  /// Creates scroll physics that maintain the scroll position in range.
+  const RangeMaintainingScrollPhysics({ ScrollPhysics? parent }) : super(parent: parent);
+
+  @override
+  RangeMaintainingScrollPhysics applyTo(ScrollPhysics? ancestor) {
+    return RangeMaintainingScrollPhysics(parent: buildParent(ancestor));
+  }
+
+  @override
+  double adjustPositionForNewDimensions({
+    required ScrollMetrics oldPosition,
+    required ScrollMetrics newPosition,
+    required bool isScrolling,
+    required double velocity,
+  }) {
+    bool maintainOverscroll = true;
+    bool enforceBoundary = true;
+    if (velocity != 0.0) {
+      // Don't try to adjust an animating position, the jumping around
+      // would be distracting.
+      maintainOverscroll = false;
+      enforceBoundary = false;
+    }
+    if ((oldPosition.minScrollExtent == newPosition.minScrollExtent) &&
+        (oldPosition.maxScrollExtent == newPosition.maxScrollExtent)) {
+      // If the extents haven't changed then ignore overscroll.
+      maintainOverscroll = false;
+    }
+    if (oldPosition.pixels != newPosition.pixels) {
+      // If the position has been changed already, then it might have
+      // been adjusted to expect new overscroll, so don't try to
+      // maintain the relative overscroll.
+      maintainOverscroll = false;
+      if (oldPosition.minScrollExtent.isFinite && oldPosition.maxScrollExtent.isFinite &&
+          newPosition.minScrollExtent.isFinite && newPosition.maxScrollExtent.isFinite) {
+        // In addition, if the position changed then we only enforce
+        // the new boundary if the previous boundary was not entirely
+        // finite. A common case where the position changes while one
+        // of the extents is infinite is a lazily-loaded list. (If the
+        // boundaries were finite, and the position changed, then we
+        // assume it was intentional.)
+        enforceBoundary = false;
+      }
+    }
+    if ((oldPosition.pixels < oldPosition.minScrollExtent) ||
+        (oldPosition.pixels > oldPosition.maxScrollExtent)) {
+      // If the old position was out of range, then we should
+      // not try to keep the new position in range.
+      enforceBoundary = false;
+    }
+    if (maintainOverscroll) {
+      // Force the new position to be no more out of range
+      // than it was before, if it was overscrolled.
+      if (oldPosition.pixels < oldPosition.minScrollExtent) {
+        final double oldDelta = oldPosition.minScrollExtent - oldPosition.pixels;
+        return newPosition.minScrollExtent - oldDelta;
+      }
+      if (oldPosition.pixels > oldPosition.maxScrollExtent) {
+        final double oldDelta = oldPosition.pixels - oldPosition.maxScrollExtent;
+        return newPosition.maxScrollExtent + oldDelta;
+      }
+    }
+    // If we're not forcing the overscroll, defer to other physics.
+    double result = super.adjustPositionForNewDimensions(oldPosition: oldPosition, newPosition: newPosition, isScrolling: isScrolling, velocity: velocity);
+    if (enforceBoundary) {
+      // ...but if they put us out of range then reinforce the boundary.
+      result = result.clamp(newPosition.minScrollExtent, newPosition.maxScrollExtent);
+    }
+    return result;
+  }
+}
+
+/// Scroll physics for environments that allow the scroll offset to go beyond
+/// the bounds of the content, but then bounce the content back to the edge of
+/// those bounds.
+///
+/// This is the behavior typically seen on iOS.
+///
+/// [BouncingScrollPhysics] by itself will not create an overscroll effect if
+/// the contents of the scroll view do not extend beyond the size of the
+/// viewport. To create the overscroll and bounce effect regardless of the
+/// length of your scroll view, combine with [AlwaysScrollableScrollPhysics].
+///
+/// {@tool snippet}
+/// ```dart
+/// BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics())
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ScrollConfiguration], which uses this to provide the default
+///    scroll behavior on iOS.
+///  * [ClampingScrollPhysics], which is the analogous physics for Android's
+///    clamping behavior.
+///  * [ScrollPhysics], for more examples of combining [ScrollPhysics] objects
+///    of different types to get the desired scroll physics.
+class BouncingScrollPhysics extends ScrollPhysics {
+  /// Creates scroll physics that bounce back from the edge.
+  const BouncingScrollPhysics({ ScrollPhysics? parent }) : super(parent: parent);
+
+  @override
+  BouncingScrollPhysics applyTo(ScrollPhysics? ancestor) {
+    return BouncingScrollPhysics(parent: buildParent(ancestor));
+  }
+
+  /// The multiple applied to overscroll to make it appear that scrolling past
+  /// the edge of the scrollable contents is harder than scrolling the list.
+  /// This is done by reducing the ratio of the scroll effect output vs the
+  /// scroll gesture input.
+  ///
+  /// This factor starts at 0.52 and progressively becomes harder to overscroll
+  /// as more of the area past the edge is dragged in (represented by an increasing
+  /// `overscrollFraction` which starts at 0 when there is no overscroll).
+  double frictionFactor(double overscrollFraction) => 0.52 * math.pow(1 - overscrollFraction, 2);
+
+  @override
+  double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
+    assert(offset != 0.0);
+    assert(position.minScrollExtent <= position.maxScrollExtent);
+
+    if (!position.outOfRange)
+      return offset;
+
+    final double overscrollPastStart = math.max(position.minScrollExtent - position.pixels, 0.0);
+    final double overscrollPastEnd = math.max(position.pixels - position.maxScrollExtent, 0.0);
+    final double overscrollPast = math.max(overscrollPastStart, overscrollPastEnd);
+    final bool easing = (overscrollPastStart > 0.0 && offset < 0.0)
+        || (overscrollPastEnd > 0.0 && offset > 0.0);
+
+    final double friction = easing
+        // Apply less resistance when easing the overscroll vs tensioning.
+        ? frictionFactor((overscrollPast - offset.abs()) / position.viewportDimension)
+        : frictionFactor(overscrollPast / position.viewportDimension);
+    final double direction = offset.sign;
+
+    return direction * _applyFriction(overscrollPast, offset.abs(), friction);
+  }
+
+  static double _applyFriction(double extentOutside, double absDelta, double gamma) {
+    assert(absDelta > 0);
+    double total = 0.0;
+    if (extentOutside > 0) {
+      final double deltaToLimit = extentOutside / gamma;
+      if (absDelta < deltaToLimit)
+        return absDelta * gamma;
+      total += extentOutside;
+      absDelta -= deltaToLimit;
+    }
+    return total + absDelta;
+  }
+
+  @override
+  double applyBoundaryConditions(ScrollMetrics position, double value) => 0.0;
+
+  @override
+  Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {
+    final Tolerance tolerance = this.tolerance;
+    if (velocity.abs() >= tolerance.velocity || position.outOfRange) {
+      return BouncingScrollSimulation(
+        spring: spring,
+        position: position.pixels,
+        velocity: velocity,
+        leadingExtent: position.minScrollExtent,
+        trailingExtent: position.maxScrollExtent,
+        tolerance: tolerance,
+      );
+    }
+    return null;
+  }
+
+  // The ballistic simulation here decelerates more slowly than the one for
+  // ClampingScrollPhysics so we require a more deliberate input gesture
+  // to trigger a fling.
+  @override
+  double get minFlingVelocity => kMinFlingVelocity * 2.0;
+
+  // Methodology:
+  // 1- Use https://github.com/flutter/platform_tests/tree/master/scroll_overlay to test with
+  //    Flutter and platform scroll views superimposed.
+  // 3- If the scrollables stopped overlapping at any moment, adjust the desired
+  //    output value of this function at that input speed.
+  // 4- Feed new input/output set into a power curve fitter. Change function
+  //    and repeat from 2.
+  // 5- Repeat from 2 with medium and slow flings.
+  /// Momentum build-up function that mimics iOS's scroll speed increase with repeated flings.
+  ///
+  /// The velocity of the last fling is not an important factor. Existing speed
+  /// and (related) time since last fling are factors for the velocity transfer
+  /// calculations.
+  @override
+  double carriedMomentum(double existingVelocity) {
+    return existingVelocity.sign *
+        math.min(0.000816 * math.pow(existingVelocity.abs(), 1.967).toDouble(), 40000.0);
+  }
+
+  // Eyeballed from observation to counter the effect of an unintended scroll
+  // from the natural motion of lifting the finger after a scroll.
+  @override
+  double get dragStartDistanceMotionThreshold => 3.5;
+}
+
+/// Scroll physics for environments that prevent the scroll offset from reaching
+/// beyond the bounds of the content.
+///
+/// This is the behavior typically seen on Android.
+///
+/// See also:
+///
+///  * [ScrollConfiguration], which uses this to provide the default
+///    scroll behavior on Android.
+///  * [BouncingScrollPhysics], which is the analogous physics for iOS' bouncing
+///    behavior.
+///  * [GlowingOverscrollIndicator], which is used by [ScrollConfiguration] to
+///    provide the glowing effect that is usually found with this clamping effect
+///    on Android. When using a [MaterialApp], the [GlowingOverscrollIndicator]'s
+///    glow color is specified to use [ThemeData.accentColor].
+class ClampingScrollPhysics extends ScrollPhysics {
+  /// Creates scroll physics that prevent the scroll offset from exceeding the
+  /// bounds of the content.
+  const ClampingScrollPhysics({ ScrollPhysics? parent }) : super(parent: parent);
+
+  @override
+  ClampingScrollPhysics applyTo(ScrollPhysics? ancestor) {
+    return ClampingScrollPhysics(parent: buildParent(ancestor));
+  }
+
+  @override
+  double applyBoundaryConditions(ScrollMetrics position, double value) {
+    assert(() {
+      if (value == position.pixels) {
+        throw FlutterError.fromParts(<DiagnosticsNode>[
+          ErrorSummary('$runtimeType.applyBoundaryConditions() was called redundantly.'),
+          ErrorDescription(
+            'The proposed new position, $value, is exactly equal to the current position of the '
+            'given ${position.runtimeType}, ${position.pixels}.\n'
+            'The applyBoundaryConditions method should only be called when the value is '
+            'going to actually change the pixels, otherwise it is redundant.'
+          ),
+          DiagnosticsProperty<ScrollPhysics>('The physics object in question was', this, style: DiagnosticsTreeStyle.errorProperty),
+          DiagnosticsProperty<ScrollMetrics>('The position object in question was', position, style: DiagnosticsTreeStyle.errorProperty)
+        ]);
+      }
+      return true;
+    }());
+    if (value < position.pixels && position.pixels <= position.minScrollExtent) // underscroll
+      return value - position.pixels;
+    if (position.maxScrollExtent <= position.pixels && position.pixels < value) // overscroll
+      return value - position.pixels;
+    if (value < position.minScrollExtent && position.minScrollExtent < position.pixels) // hit top edge
+      return value - position.minScrollExtent;
+    if (position.pixels < position.maxScrollExtent && position.maxScrollExtent < value) // hit bottom edge
+      return value - position.maxScrollExtent;
+    return 0.0;
+  }
+
+  @override
+  Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {
+    final Tolerance tolerance = this.tolerance;
+    if (position.outOfRange) {
+      double? end;
+      if (position.pixels > position.maxScrollExtent)
+        end = position.maxScrollExtent;
+      if (position.pixels < position.minScrollExtent)
+        end = position.minScrollExtent;
+      assert(end != null);
+      return ScrollSpringSimulation(
+        spring,
+        position.pixels,
+        end!,
+        math.min(0.0, velocity),
+        tolerance: tolerance,
+      );
+    }
+    if (velocity.abs() < tolerance.velocity)
+      return null;
+    if (velocity > 0.0 && position.pixels >= position.maxScrollExtent)
+      return null;
+    if (velocity < 0.0 && position.pixels <= position.minScrollExtent)
+      return null;
+    return ClampingScrollSimulation(
+      position: position.pixels,
+      velocity: velocity,
+      tolerance: tolerance,
+    );
+  }
+}
+
+/// Scroll physics that always lets the user scroll.
+///
+/// This overrides the default behavior which is to disable scrolling
+/// when there is no content to scroll. It does not override the
+/// handling of overscrolling.
+///
+/// On Android, overscrolls will be clamped by default and result in an
+/// overscroll glow. On iOS, overscrolls will load a spring that will return the
+/// scroll view to its normal range when released.
+///
+/// See also:
+///
+///  * [ScrollPhysics], which can be used instead of this class when the default
+///    behavior is desired instead.
+///  * [BouncingScrollPhysics], which provides the bouncing overscroll behavior
+///    found on iOS.
+///  * [ClampingScrollPhysics], which provides the clamping overscroll behavior
+///    found on Android.
+class AlwaysScrollableScrollPhysics extends ScrollPhysics {
+  /// Creates scroll physics that always lets the user scroll.
+  const AlwaysScrollableScrollPhysics({ ScrollPhysics? parent }) : super(parent: parent);
+
+  @override
+  AlwaysScrollableScrollPhysics applyTo(ScrollPhysics? ancestor) {
+    return AlwaysScrollableScrollPhysics(parent: buildParent(ancestor));
+  }
+
+  @override
+  bool shouldAcceptUserOffset(ScrollMetrics position) => true;
+}
+
+/// Scroll physics that does not allow the user to scroll.
+///
+/// See also:
+///
+///  * [ScrollPhysics], which can be used instead of this class when the default
+///    behavior is desired instead.
+///  * [BouncingScrollPhysics], which provides the bouncing overscroll behavior
+///    found on iOS.
+///  * [ClampingScrollPhysics], which provides the clamping overscroll behavior
+///    found on Android.
+class NeverScrollableScrollPhysics extends ScrollPhysics {
+  /// Creates scroll physics that does not let the user scroll.
+  const NeverScrollableScrollPhysics({ ScrollPhysics? parent }) : super(parent: parent);
+
+  @override
+  NeverScrollableScrollPhysics applyTo(ScrollPhysics? ancestor) {
+    return NeverScrollableScrollPhysics(parent: buildParent(ancestor));
+  }
+
+  @override
+  bool shouldAcceptUserOffset(ScrollMetrics position) => false;
+
+  @override
+  bool get allowImplicitScrolling => false;
+}
diff --git a/lib/src/widgets/scroll_position.dart b/lib/src/widgets/scroll_position.dart
new file mode 100644
index 0000000..8edbbad
--- /dev/null
+++ b/lib/src/widgets/scroll_position.dart
@@ -0,0 +1,942 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/physics.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'gesture_detector.dart';
+import 'page_storage.dart';
+import 'scroll_activity.dart';
+import 'scroll_context.dart';
+import 'scroll_metrics.dart';
+import 'scroll_notification.dart';
+import 'scroll_physics.dart';
+
+export 'scroll_activity.dart' show ScrollHoldController;
+
+/// The policy to use when applying the `alignment` parameter of
+/// [ScrollPosition.ensureVisible].
+enum ScrollPositionAlignmentPolicy {
+  /// Use the `alignment` property of [ScrollPosition.ensureVisible] to decide
+  /// where to align the visible object.
+  explicit,
+
+  /// Find the bottom edge of the scroll container, and scroll the container, if
+  /// necessary, to show the bottom of the object.
+  ///
+  /// For example, find the bottom edge of the scroll container. If the bottom
+  /// edge of the item is below the bottom edge of the scroll container, scroll
+  /// the item so that the bottom of the item is just visible. If the entire
+  /// item is already visible, then do nothing.
+  keepVisibleAtEnd,
+
+  /// Find the top edge of the scroll container, and scroll the container if
+  /// necessary to show the top of the object.
+  ///
+  /// For example, find the top edge of the scroll container. If the top edge of
+  /// the item is above the top edge of the scroll container, scroll the item so
+  /// that the top of the item is just visible. If the entire item is already
+  /// visible, then do nothing.
+  keepVisibleAtStart,
+}
+
+/// Determines which portion of the content is visible in a scroll view.
+///
+/// The [pixels] value determines the scroll offset that the scroll view uses to
+/// select which part of its content to display. As the user scrolls the
+/// viewport, this value changes, which changes the content that is displayed.
+///
+/// The [ScrollPosition] applies [physics] to scrolling, and stores the
+/// [minScrollExtent] and [maxScrollExtent].
+///
+/// Scrolling is controlled by the current [activity], which is set by
+/// [beginActivity]. [ScrollPosition] itself does not start any activities.
+/// Instead, concrete subclasses, such as [ScrollPositionWithSingleContext],
+/// typically start activities in response to user input or instructions from a
+/// [ScrollController].
+///
+/// This object is a [Listenable] that notifies its listeners when [pixels]
+/// changes.
+///
+/// ## Subclassing ScrollPosition
+///
+/// Over time, a [Scrollable] might have many different [ScrollPosition]
+/// objects. For example, if [Scrollable.physics] changes type, [Scrollable]
+/// creates a new [ScrollPosition] with the new physics. To transfer state from
+/// the old instance to the new instance, subclasses implement [absorb]. See
+/// [absorb] for more details.
+///
+/// Subclasses also need to call [didUpdateScrollDirection] whenever
+/// [userScrollDirection] changes values.
+///
+/// See also:
+///
+///  * [Scrollable], which uses a [ScrollPosition] to determine which portion of
+///    its content to display.
+///  * [ScrollController], which can be used with [ListView], [GridView] and
+///    other scrollable widgets to control a [ScrollPosition].
+///  * [ScrollPositionWithSingleContext], which is the most commonly used
+///    concrete subclass of [ScrollPosition].
+///  * [ScrollNotification] and [NotificationListener], which can be used to watch
+///    the scroll position without using a [ScrollController].
+abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
+  /// Creates an object that determines which portion of the content is visible
+  /// in a scroll view.
+  ///
+  /// The [physics], [context], and [keepScrollOffset] parameters must not be null.
+  ScrollPosition({
+    required this.physics,
+    required this.context,
+    this.keepScrollOffset = true,
+    ScrollPosition? oldPosition,
+    this.debugLabel,
+  }) : assert(physics != null),
+       assert(context != null),
+       assert(context.vsync != null),
+       assert(keepScrollOffset != null) {
+    if (oldPosition != null)
+      absorb(oldPosition);
+    if (keepScrollOffset)
+      restoreScrollOffset();
+  }
+
+  /// How the scroll position should respond to user input.
+  ///
+  /// For example, determines how the widget continues to animate after the
+  /// user stops dragging the scroll view.
+  final ScrollPhysics physics;
+
+  /// Where the scrolling is taking place.
+  ///
+  /// Typically implemented by [ScrollableState].
+  final ScrollContext context;
+
+  /// Save the current scroll offset with [PageStorage] and restore it if
+  /// this scroll position's scrollable is recreated.
+  ///
+  /// See also:
+  ///
+  ///  * [ScrollController.keepScrollOffset] and [PageController.keepPage], which
+  ///    create scroll positions and initialize this property.
+  // TODO(goderbauer): Deprecate this when state restoration supports all features of PageStorage.
+  final bool keepScrollOffset;
+
+  /// A label that is used in the [toString] output.
+  ///
+  /// Intended to aid with identifying animation controller instances in debug
+  /// output.
+  final String? debugLabel;
+
+  @override
+  double get minScrollExtent => _minScrollExtent!;
+  double? _minScrollExtent;
+
+  @override
+  double get maxScrollExtent => _maxScrollExtent!;
+  double? _maxScrollExtent;
+
+  @override
+  bool get hasContentDimensions => _minScrollExtent != null && _maxScrollExtent != null;
+
+  /// The additional velocity added for a [forcePixels] change in a single
+  /// frame.
+  ///
+  /// This value is used by [recommendDeferredLoading] in addition to the
+  /// [activity]'s [ScrollActivity.velocity] to ask the [physics] whether or
+  /// not to defer loading. It accounts for the fact that a [forcePixels] call
+  /// may involve a [ScrollActivity] with 0 velocity, but the scrollable is
+  /// still instantaneously moving from its current position to a potentially
+  /// very far position, and which is of interest to callers of
+  /// [recommendDeferredLoading].
+  ///
+  /// For example, if a scrollable is currently at 5000 pixels, and we [jumpTo]
+  /// 0 to get back to the top of the list, we would have an implied velocity of
+  /// -5000 and an `activity.velocity` of 0. The jump may be going past a
+  /// number of resource intensive widgets which should avoid doing work if the
+  /// position jumps past them.
+  double _impliedVelocity = 0;
+
+  @override
+  double get pixels => _pixels!;
+  double? _pixels;
+
+  @override
+  bool get hasPixels => _pixels != null;
+
+  @override
+  double get viewportDimension => _viewportDimension!;
+  double? _viewportDimension;
+
+  @override
+  bool get hasViewportDimension => _viewportDimension != null;
+
+  /// Whether [viewportDimension], [minScrollExtent], [maxScrollExtent],
+  /// [outOfRange], and [atEdge] are available.
+  ///
+  /// Set to true just before the first time [applyNewDimensions] is called.
+  bool get haveDimensions => _haveDimensions;
+  bool _haveDimensions = false;
+
+  /// Take any current applicable state from the given [ScrollPosition].
+  ///
+  /// This method is called by the constructor if it is given an `oldPosition`.
+  /// The `other` argument might not have the same [runtimeType] as this object.
+  ///
+  /// This method can be destructive to the other [ScrollPosition]. The other
+  /// object must be disposed immediately after this call (in the same call
+  /// stack, before microtask resolution, by whomever called this object's
+  /// constructor).
+  ///
+  /// If the old [ScrollPosition] object is a different [runtimeType] than this
+  /// one, the [ScrollActivity.resetActivity] method is invoked on the newly
+  /// adopted [ScrollActivity].
+  ///
+  /// ## Overriding
+  ///
+  /// Overrides of this method must call `super.absorb` after setting any
+  /// metrics-related or activity-related state, since this method may restart
+  /// the activity and scroll activities tend to use those metrics when being
+  /// restarted.
+  ///
+  /// Overrides of this method might need to start an [IdleScrollActivity] if
+  /// they are unable to absorb the activity from the other [ScrollPosition].
+  ///
+  /// Overrides of this method might also need to update the delegates of
+  /// absorbed scroll activities if they use themselves as a
+  /// [ScrollActivityDelegate].
+  @protected
+  @mustCallSuper
+  void absorb(ScrollPosition other) {
+    assert(other != null);
+    assert(other.context == context);
+    assert(_pixels == null);
+    if (other.hasContentDimensions) {
+      _minScrollExtent = other.minScrollExtent;
+      _maxScrollExtent = other.maxScrollExtent;
+    }
+    if (other.hasPixels) {
+      _pixels = other.pixels;
+    }
+    if (other.hasViewportDimension) {
+      _viewportDimension = other.viewportDimension;
+    }
+
+    assert(activity == null);
+    assert(other.activity != null);
+    _activity = other.activity;
+    other._activity = null;
+    if (other.runtimeType != runtimeType)
+      activity!.resetActivity();
+    context.setIgnorePointer(activity!.shouldIgnorePointer);
+    isScrollingNotifier.value = activity!.isScrolling;
+  }
+
+  /// Update the scroll position ([pixels]) to a given pixel value.
+  ///
+  /// This should only be called by the current [ScrollActivity], either during
+  /// the transient callback phase or in response to user input.
+  ///
+  /// Returns the overscroll, if any. If the return value is 0.0, that means
+  /// that [pixels] now returns the given `value`. If the return value is
+  /// positive, then [pixels] is less than the requested `value` by the given
+  /// amount (overscroll past the max extent), and if it is negative, it is
+  /// greater than the requested `value` by the given amount (underscroll past
+  /// the min extent).
+  ///
+  /// The amount of overscroll is computed by [applyBoundaryConditions].
+  ///
+  /// The amount of the change that is applied is reported using [didUpdateScrollPositionBy].
+  /// If there is any overscroll, it is reported using [didOverscrollBy].
+  double setPixels(double newPixels) {
+    assert(hasPixels);
+    assert(SchedulerBinding.instance!.schedulerPhase != SchedulerPhase.persistentCallbacks, 'A scrollable\'s position should not change during the build, layout, and paint phases, otherwise the rendering will be confused.');
+    if (newPixels != pixels) {
+      final double overscroll = applyBoundaryConditions(newPixels);
+      assert(() {
+        final double delta = newPixels - pixels;
+        if (overscroll.abs() > delta.abs()) {
+          throw FlutterError(
+            '$runtimeType.applyBoundaryConditions returned invalid overscroll value.\n'
+            'setPixels() was called to change the scroll offset from $pixels to $newPixels.\n'
+            'That is a delta of $delta units.\n'
+            '$runtimeType.applyBoundaryConditions reported an overscroll of $overscroll units.'
+          );
+        }
+        return true;
+      }());
+      final double oldPixels = pixels;
+      _pixels = newPixels - overscroll;
+      if (_pixels != oldPixels) {
+        notifyListeners();
+        didUpdateScrollPositionBy(pixels - oldPixels);
+      }
+      if (overscroll != 0.0) {
+        didOverscrollBy(overscroll);
+        return overscroll;
+      }
+    }
+    return 0.0;
+  }
+
+  /// Change the value of [pixels] to the new value, without notifying any
+  /// customers.
+  ///
+  /// This is used to adjust the position while doing layout. In particular,
+  /// this is typically called as a response to [applyViewportDimension] or
+  /// [applyContentDimensions] (in both cases, if this method is called, those
+  /// methods should then return false to indicate that the position has been
+  /// adjusted).
+  ///
+  /// Calling this is rarely correct in other contexts. It will not immediately
+  /// cause the rendering to change, since it does not notify the widgets or
+  /// render objects that might be listening to this object: they will only
+  /// change when they next read the value, which could be arbitrarily later. It
+  /// is generally only appropriate in the very specific case of the value being
+  /// corrected during layout (since then the value is immediately read), in the
+  /// specific case of a [ScrollPosition] with a single viewport customer.
+  ///
+  /// To cause the position to jump or animate to a new value, consider [jumpTo]
+  /// or [animateTo], which will honor the normal conventions for changing the
+  /// scroll offset.
+  ///
+  /// To force the [pixels] to a particular value without honoring the normal
+  /// conventions for changing the scroll offset, consider [forcePixels]. (But
+  /// see the discussion there for why that might still be a bad idea.)
+  ///
+  /// See also:
+  ///
+  ///  * [correctBy], which is a method of [ViewportOffset] used
+  ///    by viewport render objects to correct the offset during layout
+  ///    without notifying its listeners.
+  ///  * [jumpTo], for making changes to position while not in the
+  ///    middle of layout and applying the new position immediately.
+  ///  * [animateTo], which is like [jumpTo] but animating to the
+  ///    destination offset.
+  void correctPixels(double value) {
+    _pixels = value;
+  }
+
+  /// Apply a layout-time correction to the scroll offset.
+  ///
+  /// This method should change the [pixels] value by `correction`, but without
+  /// calling [notifyListeners]. It is called during layout by the
+  /// [RenderViewport], before [applyContentDimensions]. After this method is
+  /// called, the layout will be recomputed and that may result in this method
+  /// being called again, though this should be very rare.
+  ///
+  /// See also:
+  ///
+  ///  * [jumpTo], for also changing the scroll position when not in layout.
+  ///    [jumpTo] applies the change immediately and notifies its listeners.
+  ///  * [correctPixels], which is used by the [ScrollPosition] itself to
+  ///    set the offset initially during construction or after
+  ///    [applyViewportDimension] or [applyContentDimensions] is called.
+  @override
+  void correctBy(double correction) {
+    assert(
+      hasPixels,
+      'An initial pixels value must exist by calling correctPixels on the ScrollPosition',
+    );
+    _pixels = _pixels! + correction;
+    _didChangeViewportDimensionOrReceiveCorrection = true;
+  }
+
+  /// Change the value of [pixels] to the new value, and notify any customers,
+  /// but without honoring normal conventions for changing the scroll offset.
+  ///
+  /// This is used to implement [jumpTo]. It can also be used adjust the
+  /// position when the dimensions of the viewport change. It should only be
+  /// used when manually implementing the logic for honoring the relevant
+  /// conventions of the class. For example, [ScrollPositionWithSingleContext]
+  /// introduces [ScrollActivity] objects and uses [forcePixels] in conjunction
+  /// with adjusting the activity, e.g. by calling
+  /// [ScrollPositionWithSingleContext.goIdle], so that the activity does
+  /// not immediately set the value back. (Consider, for instance, a case where
+  /// one is using a [DrivenScrollActivity]. That object will ignore any calls
+  /// to [forcePixels], which would result in the rendering stuttering: changing
+  /// in response to [forcePixels], and then changing back to the next value
+  /// derived from the animation.)
+  ///
+  /// To cause the position to jump or animate to a new value, consider [jumpTo]
+  /// or [animateTo].
+  ///
+  /// This should not be called during layout (e.g. when setting the initial
+  /// scroll offset). Consider [correctPixels] if you find you need to adjust
+  /// the position during layout.
+  @protected
+  void forcePixels(double value) {
+    assert(hasPixels);
+    assert(value != null);
+    _impliedVelocity = value - pixels;
+    _pixels = value;
+    notifyListeners();
+    SchedulerBinding.instance!.addPostFrameCallback((Duration timeStamp) {
+      _impliedVelocity = 0;
+    });
+  }
+
+  /// Called whenever scrolling ends, to store the current scroll offset in a
+  /// storage mechanism with a lifetime that matches the app's lifetime.
+  ///
+  /// The stored value will be used by [restoreScrollOffset] when the
+  /// [ScrollPosition] is recreated, in the case of the [Scrollable] being
+  /// disposed then recreated in the same session. This might happen, for
+  /// instance, if a [ListView] is on one of the pages inside a [TabBarView],
+  /// and that page is displayed, then hidden, then displayed again.
+  ///
+  /// The default implementation writes the [pixels] using the nearest
+  /// [PageStorage] found from the [context]'s [ScrollContext.storageContext]
+  /// property.
+  // TODO(goderbauer): Deprecate this when state restoration supports all features of PageStorage.
+  @protected
+  void saveScrollOffset() {
+    PageStorage.of(context.storageContext)?.writeState(context.storageContext, pixels);
+  }
+
+  /// Called whenever the [ScrollPosition] is created, to restore the scroll
+  /// offset if possible.
+  ///
+  /// The value is stored by [saveScrollOffset] when the scroll position
+  /// changes, so that it can be restored in the case of the [Scrollable] being
+  /// disposed then recreated in the same session. This might happen, for
+  /// instance, if a [ListView] is on one of the pages inside a [TabBarView],
+  /// and that page is displayed, then hidden, then displayed again.
+  ///
+  /// The default implementation reads the value from the nearest [PageStorage]
+  /// found from the [context]'s [ScrollContext.storageContext] property, and
+  /// sets it using [correctPixels], if [pixels] is still null.
+  ///
+  /// This method is called from the constructor, so layout has not yet
+  /// occurred, and the viewport dimensions aren't yet known when it is called.
+  // TODO(goderbauer): Deprecate this when state restoration supports all features of PageStorage.
+  @protected
+  void restoreScrollOffset() {
+    if (!hasPixels) {
+      final double? value = PageStorage.of(context.storageContext)?.readState(context.storageContext) as double?;
+      if (value != null)
+        correctPixels(value);
+    }
+  }
+
+  /// Called by [context] to restore the scroll offset to the provided value.
+  ///
+  /// The provided value has previously been provided to the [context] by
+  /// calling [ScrollContext.saveOffset], e.g. from [saveOffset].
+  ///
+  /// This method may be called right after the scroll position is created
+  /// before layout has occurred. In that case, `initialRestore` is set to true
+  /// and the viewport dimensions will not be known yet. If the [context]
+  /// doesn't have any information to restore the scroll offset this method is
+  /// not called.
+  ///
+  /// The method may be called multiple times in the lifecycle of a
+  /// [ScrollPosition] to restore it to different scroll offsets.
+  void restoreOffset(double offset, {bool initialRestore = false}) {
+    assert(initialRestore != null);
+    assert(offset != null);
+    if (initialRestore) {
+      correctPixels(offset);
+    } else {
+      jumpTo(offset);
+    }
+  }
+
+  /// Called whenever scrolling ends, to persist the current scroll offset for
+  /// state restoration purposes.
+  ///
+  /// The default implementation stores the current value of [pixels] on the
+  /// [context] by calling [ScrollContext.saveOffset]. At a later point in time
+  /// or after the application restarts, the [context] may restore the scroll
+  /// position to the persisted offset by calling [restoreOffset].
+  @protected
+  void saveOffset() {
+    assert(hasPixels);
+    context.saveOffset(pixels);
+  }
+
+  /// Returns the overscroll by applying the boundary conditions.
+  ///
+  /// If the given value is in bounds, returns 0.0. Otherwise, returns the
+  /// amount of value that cannot be applied to [pixels] as a result of the
+  /// boundary conditions. If the [physics] allow out-of-bounds scrolling, this
+  /// method always returns 0.0.
+  ///
+  /// The default implementation defers to the [physics] object's
+  /// [ScrollPhysics.applyBoundaryConditions].
+  @protected
+  double applyBoundaryConditions(double value) {
+    final double result = physics.applyBoundaryConditions(this, value);
+    assert(() {
+      final double delta = value - pixels;
+      if (result.abs() > delta.abs()) {
+        throw FlutterError(
+          '${physics.runtimeType}.applyBoundaryConditions returned invalid overscroll value.\n'
+          'The method was called to consider a change from $pixels to $value, which is a '
+          'delta of ${delta.toStringAsFixed(1)} units. However, it returned an overscroll of '
+          '${result.toStringAsFixed(1)} units, which has a greater magnitude than the delta. '
+          'The applyBoundaryConditions method is only supposed to reduce the possible range '
+          'of movement, not increase it.\n'
+          'The scroll extents are $minScrollExtent .. $maxScrollExtent, and the '
+          'viewport dimension is $viewportDimension.'
+        );
+      }
+      return true;
+    }());
+    return result;
+  }
+
+  bool _didChangeViewportDimensionOrReceiveCorrection = true;
+
+  @override
+  bool applyViewportDimension(double viewportDimension) {
+    if (_viewportDimension != viewportDimension) {
+      _viewportDimension = viewportDimension;
+      _didChangeViewportDimensionOrReceiveCorrection = true;
+      // If this is called, you can rely on applyContentDimensions being called
+      // soon afterwards in the same layout phase. So we put all the logic that
+      // relies on both values being computed into applyContentDimensions.
+    }
+    return true;
+  }
+
+  bool _pendingDimensions = false;
+  ScrollMetrics? _lastMetrics;
+
+  @override
+  bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
+    assert(minScrollExtent != null);
+    assert(maxScrollExtent != null);
+    assert(haveDimensions == (_lastMetrics != null));
+    if (!nearEqual(_minScrollExtent, minScrollExtent, Tolerance.defaultTolerance.distance) ||
+        !nearEqual(_maxScrollExtent, maxScrollExtent, Tolerance.defaultTolerance.distance) ||
+        _didChangeViewportDimensionOrReceiveCorrection) {
+      assert(minScrollExtent != null);
+      assert(maxScrollExtent != null);
+      assert(minScrollExtent <= maxScrollExtent);
+      _minScrollExtent = minScrollExtent;
+      _maxScrollExtent = maxScrollExtent;
+      final ScrollMetrics? currentMetrics = haveDimensions ? copyWith() : null;
+      _didChangeViewportDimensionOrReceiveCorrection = false;
+      _pendingDimensions = true;
+      if (haveDimensions && !correctForNewDimensions(_lastMetrics!, currentMetrics!)) {
+        return false;
+      }
+      _haveDimensions = true;
+    }
+    assert(haveDimensions);
+    if (_pendingDimensions) {
+      applyNewDimensions();
+      _pendingDimensions = false;
+    }
+    assert(!_didChangeViewportDimensionOrReceiveCorrection, 'Use correctForNewDimensions() (and return true) to change the scroll offset during applyContentDimensions().');
+    _lastMetrics = copyWith();
+    return true;
+  }
+
+  /// Verifies that the new content and viewport dimensions are acceptable.
+  ///
+  /// Called by [applyContentDimensions] to determine its return value.
+  ///
+  /// Should return true if the current scroll offset is correct given
+  /// the new content and viewport dimensions.
+  ///
+  /// Otherwise, should call [correctPixels] to correct the scroll
+  /// offset given the new dimensions, and then return false.
+  ///
+  /// This is only called when [haveDimensions] is true.
+  ///
+  /// The default implementation defers to [ScrollPhysics.adjustPositionForNewDimensions].
+  @protected
+  bool correctForNewDimensions(ScrollMetrics oldPosition, ScrollMetrics newPosition) {
+    final double newPixels = physics.adjustPositionForNewDimensions(
+      oldPosition: oldPosition,
+      newPosition: newPosition,
+      isScrolling: activity!.isScrolling,
+      velocity: activity!.velocity,
+    );
+    if (newPixels != pixels) {
+      correctPixels(newPixels);
+      return false;
+    }
+    return true;
+  }
+
+  /// Notifies the activity that the dimensions of the underlying viewport or
+  /// contents have changed.
+  ///
+  /// Called after [applyViewportDimension] or [applyContentDimensions] have
+  /// changed the [minScrollExtent], the [maxScrollExtent], or the
+  /// [viewportDimension]. When this method is called, it should be called
+  /// _after_ any corrections are applied to [pixels] using [correctPixels], not
+  /// before.
+  ///
+  /// The default implementation informs the [activity] of the new dimensions by
+  /// calling its [ScrollActivity.applyNewDimensions] method.
+  ///
+  /// See also:
+  ///
+  ///  * [applyViewportDimension], which is called when new
+  ///    viewport dimensions are established.
+  ///  * [applyContentDimensions], which is called after new
+  ///    viewport dimensions are established, and also if new content dimensions
+  ///    are established, and which calls [ScrollPosition.applyNewDimensions].
+  @protected
+  @mustCallSuper
+  void applyNewDimensions() {
+    assert(hasPixels);
+    assert(_pendingDimensions);
+    activity!.applyNewDimensions();
+    _updateSemanticActions(); // will potentially request a semantics update.
+  }
+
+  Set<SemanticsAction>? _semanticActions;
+
+  /// Called whenever the scroll position or the dimensions of the scroll view
+  /// change to schedule an update of the available semantics actions. The
+  /// actual update will be performed in the next frame. If non is pending
+  /// a frame will be scheduled.
+  ///
+  /// For example: If the scroll view has been scrolled all the way to the top,
+  /// the action to scroll further up needs to be removed as the scroll view
+  /// cannot be scrolled in that direction anymore.
+  ///
+  /// This method is potentially called twice per frame (if scroll position and
+  /// scroll view dimensions both change) and therefore shouldn't do anything
+  /// expensive.
+  void _updateSemanticActions() {
+    final SemanticsAction forward;
+    final SemanticsAction backward;
+    switch (axisDirection) {
+      case AxisDirection.up:
+        forward = SemanticsAction.scrollDown;
+        backward = SemanticsAction.scrollUp;
+        break;
+      case AxisDirection.right:
+        forward = SemanticsAction.scrollLeft;
+        backward = SemanticsAction.scrollRight;
+        break;
+      case AxisDirection.down:
+        forward = SemanticsAction.scrollUp;
+        backward = SemanticsAction.scrollDown;
+        break;
+      case AxisDirection.left:
+        forward = SemanticsAction.scrollRight;
+        backward = SemanticsAction.scrollLeft;
+        break;
+    }
+
+    final Set<SemanticsAction> actions = <SemanticsAction>{};
+    if (pixels > minScrollExtent)
+      actions.add(backward);
+    if (pixels < maxScrollExtent)
+      actions.add(forward);
+
+    if (setEquals<SemanticsAction>(actions, _semanticActions))
+      return;
+
+    _semanticActions = actions;
+    context.setSemanticsActions(_semanticActions!);
+  }
+
+  /// Animates the position such that the given object is as visible as possible
+  /// by just scrolling this position.
+  ///
+  /// The optional `targetRenderObject` parameter is used to determine which area
+  /// of that object should be as visible as possible. If `targetRenderObject`
+  /// is null, the entire [RenderObject] (as defined by its
+  /// [RenderObject.paintBounds]) will be as visible as possible. If
+  /// `targetRenderObject` is provided, it must be a descendant of the object.
+  ///
+  /// See also:
+  ///
+  ///  * [ScrollPositionAlignmentPolicy] for the way in which `alignment` is
+  ///    applied, and the way the given `object` is aligned.
+  Future<void> ensureVisible(
+    RenderObject object, {
+    double alignment = 0.0,
+    Duration duration = Duration.zero,
+    Curve curve = Curves.ease,
+    ScrollPositionAlignmentPolicy alignmentPolicy = ScrollPositionAlignmentPolicy.explicit,
+    RenderObject? targetRenderObject,
+  }) {
+    assert(alignmentPolicy != null);
+    assert(object.attached);
+    final RenderAbstractViewport viewport = RenderAbstractViewport.of(object)!;
+    assert(viewport != null);
+
+    Rect? targetRect;
+    if (targetRenderObject != null && targetRenderObject != object) {
+      targetRect = MatrixUtils.transformRect(
+        targetRenderObject.getTransformTo(object),
+        object.paintBounds.intersect(targetRenderObject.paintBounds)
+      );
+    }
+
+    double target;
+    switch (alignmentPolicy) {
+      case ScrollPositionAlignmentPolicy.explicit:
+        target = viewport.getOffsetToReveal(object, alignment, rect: targetRect).offset.clamp(minScrollExtent, maxScrollExtent);
+        break;
+      case ScrollPositionAlignmentPolicy.keepVisibleAtEnd:
+        target = viewport.getOffsetToReveal(object, 1.0, rect: targetRect).offset.clamp(minScrollExtent, maxScrollExtent);
+        if (target < pixels) {
+          target = pixels;
+        }
+        break;
+      case ScrollPositionAlignmentPolicy.keepVisibleAtStart:
+        target = viewport.getOffsetToReveal(object, 0.0, rect: targetRect).offset.clamp(minScrollExtent, maxScrollExtent);
+        if (target > pixels) {
+          target = pixels;
+        }
+        break;
+    }
+
+    if (target == pixels)
+      return Future<void>.value();
+
+    if (duration == Duration.zero) {
+      jumpTo(target);
+      return Future<void>.value();
+    }
+
+    return animateTo(target, duration: duration, curve: curve);
+  }
+
+  /// This notifier's value is true if a scroll is underway and false if the scroll
+  /// position is idle.
+  ///
+  /// Listeners added by stateful widgets should be removed in the widget's
+  /// [State.dispose] method.
+  final ValueNotifier<bool> isScrollingNotifier = ValueNotifier<bool>(false);
+
+  /// Animates the position from its current value to the given value.
+  ///
+  /// Any active animation is canceled. If the user is currently scrolling, that
+  /// action is canceled.
+  ///
+  /// The returned [Future] will complete when the animation ends, whether it
+  /// completed successfully or whether it was interrupted prematurely.
+  ///
+  /// An animation will be interrupted whenever the user attempts to scroll
+  /// manually, or whenever another activity is started, or whenever the
+  /// animation reaches the edge of the viewport and attempts to overscroll. (If
+  /// the [ScrollPosition] does not overscroll but instead allows scrolling
+  /// beyond the extents, then going beyond the extents will not interrupt the
+  /// animation.)
+  ///
+  /// The animation is indifferent to changes to the viewport or content
+  /// dimensions.
+  ///
+  /// Once the animation has completed, the scroll position will attempt to
+  /// begin a ballistic activity in case its value is not stable (for example,
+  /// if it is scrolled beyond the extents and in that situation the scroll
+  /// position would normally bounce back).
+  ///
+  /// The duration must not be zero. To jump to a particular value without an
+  /// animation, use [jumpTo].
+  ///
+  /// The animation is typically handled by an [DrivenScrollActivity].
+  @override
+  Future<void> animateTo(
+    double to, {
+    required Duration duration,
+    required Curve curve,
+  });
+
+  /// Jumps the scroll position from its current value to the given value,
+  /// without animation, and without checking if the new value is in range.
+  ///
+  /// Any active animation is canceled. If the user is currently scrolling, that
+  /// action is canceled.
+  ///
+  /// If this method changes the scroll position, a sequence of start/update/end
+  /// scroll notifications will be dispatched. No overscroll notifications can
+  /// be generated by this method.
+  @override
+  void jumpTo(double value);
+
+  /// Changes the scrolling position based on a pointer signal from current
+  /// value to delta without animation and without checking if new value is in
+  /// range, taking min/max scroll extent into account.
+  ///
+  /// Any active animation is canceled. If the user is currently scrolling, that
+  /// action is canceled.
+  ///
+  /// This method dispatches the start/update/end sequence of scrolling
+  /// notifications.
+  ///
+  /// This method is very similar to [jumpTo], but [pointerScroll] will
+  /// update the [ScrollDirection].
+  ///
+  // TODO(YeungKC): Support trackpad scroll, https://github.com/flutter/flutter/issues/23604.
+  void pointerScroll(double delta);
+
+  /// Calls [jumpTo] if duration is null or [Duration.zero], otherwise
+  /// [animateTo] is called.
+  ///
+  /// If [clamp] is true (the default) then [to] is adjusted to prevent over or
+  /// underscroll.
+  ///
+  /// If [animateTo] is called then [curve] defaults to [Curves.ease].
+  @override
+  Future<void> moveTo(
+    double to, {
+    Duration? duration,
+    Curve? curve,
+    bool? clamp = true,
+  }) {
+    assert(to != null);
+    assert(clamp != null);
+
+    if (clamp!)
+      to = to.clamp(minScrollExtent, maxScrollExtent);
+
+    return super.moveTo(to, duration: duration, curve: curve);
+  }
+
+  @override
+  bool get allowImplicitScrolling => physics.allowImplicitScrolling;
+
+  /// Deprecated. Use [jumpTo] or a custom [ScrollPosition] instead.
+  @Deprecated('This will lead to bugs.') // ignore: flutter_deprecation_syntax, https://github.com/flutter/flutter/issues/44609
+  void jumpToWithoutSettling(double value);
+
+  /// Stop the current activity and start a [HoldScrollActivity].
+  ScrollHoldController hold(VoidCallback holdCancelCallback);
+
+  /// Start a drag activity corresponding to the given [DragStartDetails].
+  ///
+  /// The `onDragCanceled` argument will be invoked if the drag is ended
+  /// prematurely (e.g. from another activity taking over). See
+  /// [ScrollDragController.onDragCanceled] for details.
+  Drag drag(DragStartDetails details, VoidCallback dragCancelCallback);
+
+  /// The currently operative [ScrollActivity].
+  ///
+  /// If the scroll position is not performing any more specific activity, the
+  /// activity will be an [IdleScrollActivity]. To determine whether the scroll
+  /// position is idle, check the [isScrollingNotifier].
+  ///
+  /// Call [beginActivity] to change the current activity.
+  @protected
+  @visibleForTesting
+  ScrollActivity? get activity => _activity;
+  ScrollActivity? _activity;
+
+  /// Change the current [activity], disposing of the old one and
+  /// sending scroll notifications as necessary.
+  ///
+  /// If the argument is null, this method has no effect. This is convenient for
+  /// cases where the new activity is obtained from another method, and that
+  /// method might return null, since it means the caller does not have to
+  /// explicitly null-check the argument.
+  void beginActivity(ScrollActivity? newActivity) {
+    if (newActivity == null)
+      return;
+    bool wasScrolling, oldIgnorePointer;
+    if (_activity != null) {
+      oldIgnorePointer = _activity!.shouldIgnorePointer;
+      wasScrolling = _activity!.isScrolling;
+      if (wasScrolling && !newActivity.isScrolling)
+        didEndScroll(); // notifies and then saves the scroll offset
+      _activity!.dispose();
+    } else {
+      oldIgnorePointer = false;
+      wasScrolling = false;
+    }
+    _activity = newActivity;
+    if (oldIgnorePointer != activity!.shouldIgnorePointer)
+      context.setIgnorePointer(activity!.shouldIgnorePointer);
+    isScrollingNotifier.value = activity!.isScrolling;
+    if (!wasScrolling && _activity!.isScrolling)
+      didStartScroll();
+  }
+
+
+  // NOTIFICATION DISPATCH
+
+  /// Called by [beginActivity] to report when an activity has started.
+  void didStartScroll() {
+    activity!.dispatchScrollStartNotification(copyWith(), context.notificationContext);
+  }
+
+  /// Called by [setPixels] to report a change to the [pixels] position.
+  void didUpdateScrollPositionBy(double delta) {
+    activity!.dispatchScrollUpdateNotification(copyWith(), context.notificationContext!, delta);
+  }
+
+  /// Called by [beginActivity] to report when an activity has ended.
+  ///
+  /// This also saves the scroll offset using [saveScrollOffset].
+  void didEndScroll() {
+    activity!.dispatchScrollEndNotification(copyWith(), context.notificationContext!);
+    saveOffset();
+    if (keepScrollOffset)
+      saveScrollOffset();
+  }
+
+  /// Called by [setPixels] to report overscroll when an attempt is made to
+  /// change the [pixels] position. Overscroll is the amount of change that was
+  /// not applied to the [pixels] value.
+  void didOverscrollBy(double value) {
+    assert(activity!.isScrolling);
+    activity!.dispatchOverscrollNotification(copyWith(), context.notificationContext!, value);
+  }
+
+  /// Dispatches a notification that the [userScrollDirection] has changed.
+  ///
+  /// Subclasses should call this function when they change [userScrollDirection].
+  void didUpdateScrollDirection(ScrollDirection direction) {
+    UserScrollNotification(metrics: copyWith(), context: context.notificationContext!, direction: direction).dispatch(context.notificationContext);
+  }
+
+  /// Provides a heuristic to determine if expensive frame-bound tasks should be
+  /// deferred.
+  ///
+  /// The actual work of this is delegated to the [physics] via
+  /// [ScrollPhysics.recommendDeferredLoading] called with the current
+  /// [activity]'s [ScrollActivity.velocity].
+  ///
+  /// Returning true from this method indicates that the [ScrollPhysics]
+  /// evaluate the current scroll velocity to be great enough that expensive
+  /// operations impacting the UI should be deferred.
+  bool recommendDeferredLoading(BuildContext context) {
+    assert(context != null);
+    assert(activity != null);
+    assert(activity!.velocity != null);
+    assert(_impliedVelocity != null);
+    return physics.recommendDeferredLoading(
+      activity!.velocity + _impliedVelocity,
+      copyWith(),
+      context,
+    );
+  }
+
+  @override
+  void dispose() {
+    activity?.dispose(); // it will be null if it got absorbed by another ScrollPosition
+    _activity = null;
+    super.dispose();
+  }
+
+  @override
+  void notifyListeners() {
+    _updateSemanticActions(); // will potentially request a semantics update.
+    super.notifyListeners();
+  }
+
+  @override
+  void debugFillDescription(List<String> description) {
+    if (debugLabel != null)
+      description.add(debugLabel!);
+    super.debugFillDescription(description);
+    description.add('range: ${_minScrollExtent?.toStringAsFixed(1)}..${_maxScrollExtent?.toStringAsFixed(1)}');
+    description.add('viewport: ${_viewportDimension?.toStringAsFixed(1)}');
+  }
+}
diff --git a/lib/src/widgets/scroll_position_with_single_context.dart b/lib/src/widgets/scroll_position_with_single_context.dart
new file mode 100644
index 0000000..688f77d
--- /dev/null
+++ b/lib/src/widgets/scroll_position_with_single_context.dart
@@ -0,0 +1,285 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/gestures.dart';
+import 'package:flute/physics.dart';
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'gesture_detector.dart';
+import 'scroll_activity.dart';
+import 'scroll_context.dart';
+import 'scroll_notification.dart';
+import 'scroll_physics.dart';
+import 'scroll_position.dart';
+
+/// A scroll position that manages scroll activities for a single
+/// [ScrollContext].
+///
+/// This class is a concrete subclass of [ScrollPosition] logic that handles a
+/// single [ScrollContext], such as a [Scrollable]. An instance of this class
+/// manages [ScrollActivity] instances, which change what content is visible in
+/// the [Scrollable]'s [Viewport].
+///
+/// See also:
+///
+///  * [ScrollPosition], which defines the underlying model for a position
+///    within a [Scrollable] but is agnostic as to how that position is
+///    changed.
+///  * [ScrollView] and its subclasses such as [ListView], which use
+///    [ScrollPositionWithSingleContext] to manage their scroll position.
+///  * [ScrollController], which can manipulate one or more [ScrollPosition]s,
+///    and which uses [ScrollPositionWithSingleContext] as its default class for
+///    scroll positions.
+class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollActivityDelegate {
+  /// Create a [ScrollPosition] object that manages its behavior using
+  /// [ScrollActivity] objects.
+  ///
+  /// The `initialPixels` argument can be null, but in that case it is
+  /// imperative that the value be set, using [correctPixels], as soon as
+  /// [applyNewDimensions] is invoked, before calling the inherited
+  /// implementation of that method.
+  ///
+  /// If [keepScrollOffset] is true (the default), the current scroll offset is
+  /// saved with [PageStorage] and restored it if this scroll position's scrollable
+  /// is recreated.
+  ScrollPositionWithSingleContext({
+    required ScrollPhysics physics,
+    required ScrollContext context,
+    double? initialPixels = 0.0,
+    bool keepScrollOffset = true,
+    ScrollPosition? oldPosition,
+    String? debugLabel,
+  }) : super(
+         physics: physics,
+         context: context,
+         keepScrollOffset: keepScrollOffset,
+         oldPosition: oldPosition,
+         debugLabel: debugLabel,
+       ) {
+    // If oldPosition is not null, the superclass will first call absorb(),
+    // which may set _pixels and _activity.
+    if (!hasPixels && initialPixels != null)
+      correctPixels(initialPixels);
+    if (activity == null)
+      goIdle();
+    assert(activity != null);
+  }
+
+  /// Velocity from a previous activity temporarily held by [hold] to potentially
+  /// transfer to a next activity.
+  double _heldPreviousVelocity = 0.0;
+
+  @override
+  AxisDirection get axisDirection => context.axisDirection;
+
+  @override
+  double setPixels(double newPixels) {
+    assert(activity!.isScrolling);
+    return super.setPixels(newPixels);
+  }
+
+  @override
+  void absorb(ScrollPosition other) {
+    super.absorb(other);
+    if (other is! ScrollPositionWithSingleContext) {
+      goIdle();
+      return;
+    }
+    activity!.updateDelegate(this);
+    _userScrollDirection = other._userScrollDirection;
+    assert(_currentDrag == null);
+    if (other._currentDrag != null) {
+      _currentDrag = other._currentDrag;
+      _currentDrag!.updateDelegate(this);
+      other._currentDrag = null;
+    }
+  }
+
+  @override
+  void applyNewDimensions() {
+    super.applyNewDimensions();
+    context.setCanDrag(physics.shouldAcceptUserOffset(this));
+  }
+
+  @override
+  void beginActivity(ScrollActivity? newActivity) {
+    _heldPreviousVelocity = 0.0;
+    if (newActivity == null)
+      return;
+    assert(newActivity.delegate == this);
+    super.beginActivity(newActivity);
+    _currentDrag?.dispose();
+    _currentDrag = null;
+    if (!activity!.isScrolling)
+      updateUserScrollDirection(ScrollDirection.idle);
+  }
+
+  @override
+  void applyUserOffset(double delta) {
+    updateUserScrollDirection(delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse);
+    setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta));
+  }
+
+  @override
+  void goIdle() {
+    beginActivity(IdleScrollActivity(this));
+  }
+
+  /// Start a physics-driven simulation that settles the [pixels] position,
+  /// starting at a particular velocity.
+  ///
+  /// This method defers to [ScrollPhysics.createBallisticSimulation], which
+  /// typically provides a bounce simulation when the current position is out of
+  /// bounds and a friction simulation when the position is in bounds but has a
+  /// non-zero velocity.
+  ///
+  /// The velocity should be in logical pixels per second.
+  @override
+  void goBallistic(double velocity) {
+    assert(hasPixels);
+    final Simulation? simulation = physics.createBallisticSimulation(this, velocity);
+    if (simulation != null) {
+      beginActivity(BallisticScrollActivity(this, simulation, context.vsync));
+    } else {
+      goIdle();
+    }
+  }
+
+  @override
+  ScrollDirection get userScrollDirection => _userScrollDirection;
+  ScrollDirection _userScrollDirection = ScrollDirection.idle;
+
+  /// Set [userScrollDirection] to the given value.
+  ///
+  /// If this changes the value, then a [UserScrollNotification] is dispatched.
+  @protected
+  @visibleForTesting
+  void updateUserScrollDirection(ScrollDirection value) {
+    assert(value != null);
+    if (userScrollDirection == value)
+      return;
+    _userScrollDirection = value;
+    didUpdateScrollDirection(value);
+  }
+
+  @override
+  Future<void> animateTo(
+    double to, {
+    required Duration duration,
+    required Curve curve,
+  }) {
+    if (nearEqual(to, pixels, physics.tolerance.distance)) {
+      // Skip the animation, go straight to the position as we are already close.
+      jumpTo(to);
+      return Future<void>.value();
+    }
+
+    final DrivenScrollActivity activity = DrivenScrollActivity(
+      this,
+      from: pixels,
+      to: to,
+      duration: duration,
+      curve: curve,
+      vsync: context.vsync,
+    );
+    beginActivity(activity);
+    return activity.done;
+  }
+
+  @override
+  void jumpTo(double value) {
+    goIdle();
+    if (pixels != value) {
+      final double oldPixels = pixels;
+      forcePixels(value);
+      didStartScroll();
+      didUpdateScrollPositionBy(pixels - oldPixels);
+      didEndScroll();
+    }
+    goBallistic(0.0);
+  }
+
+  @override
+  void pointerScroll(double delta) {
+    assert(delta != 0.0);
+
+    final double targetPixels =
+        math.min(math.max(pixels + delta, minScrollExtent), maxScrollExtent);
+    if (targetPixels != pixels) {
+      goIdle();
+      updateUserScrollDirection(
+          -delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse
+      );
+      final double oldPixels = pixels;
+      forcePixels(targetPixels);
+      didStartScroll();
+      didUpdateScrollPositionBy(pixels - oldPixels);
+      didEndScroll();
+      goBallistic(0.0);
+    }
+  }
+
+
+  @Deprecated('This will lead to bugs.') // ignore: flutter_deprecation_syntax, https://github.com/flutter/flutter/issues/44609
+  @override
+  void jumpToWithoutSettling(double value) {
+    goIdle();
+    if (pixels != value) {
+      final double oldPixels = pixels;
+      forcePixels(value);
+      didStartScroll();
+      didUpdateScrollPositionBy(pixels - oldPixels);
+      didEndScroll();
+    }
+  }
+
+  @override
+  ScrollHoldController hold(VoidCallback holdCancelCallback) {
+    final double previousVelocity = activity!.velocity;
+    final HoldScrollActivity holdActivity = HoldScrollActivity(
+      delegate: this,
+      onHoldCanceled: holdCancelCallback,
+    );
+    beginActivity(holdActivity);
+    _heldPreviousVelocity = previousVelocity;
+    return holdActivity;
+  }
+
+  ScrollDragController? _currentDrag;
+
+  @override
+  Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) {
+    final ScrollDragController drag = ScrollDragController(
+      delegate: this,
+      details: details,
+      onDragCanceled: dragCancelCallback,
+      carriedVelocity: physics.carriedMomentum(_heldPreviousVelocity),
+      motionStartDistanceThreshold: physics.dragStartDistanceMotionThreshold,
+    );
+    beginActivity(DragScrollActivity(this, drag));
+    assert(_currentDrag == null);
+    _currentDrag = drag;
+    return drag;
+  }
+
+  @override
+  void dispose() {
+    _currentDrag?.dispose();
+    _currentDrag = null;
+    super.dispose();
+  }
+
+  @override
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('${context.runtimeType}');
+    description.add('$physics');
+    description.add('$activity');
+    description.add('$userScrollDirection');
+  }
+}
diff --git a/lib/src/widgets/scroll_simulation.dart b/lib/src/widgets/scroll_simulation.dart
new file mode 100644
index 0000000..4a875d9
--- /dev/null
+++ b/lib/src/widgets/scroll_simulation.dart
@@ -0,0 +1,231 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/physics.dart';
+
+/// An implementation of scroll physics that matches iOS.
+///
+/// See also:
+///
+///  * [ClampingScrollSimulation], which implements Android scroll physics.
+class BouncingScrollSimulation extends Simulation {
+  /// Creates a simulation group for scrolling on iOS, with the given
+  /// parameters.
+  ///
+  /// The position and velocity arguments must use the same units as will be
+  /// expected from the [x] and [dx] methods respectively (typically logical
+  /// pixels and logical pixels per second respectively).
+  ///
+  /// The leading and trailing extents must use the unit of length, the same
+  /// unit as used for the position argument and as expected from the [x]
+  /// method (typically logical pixels).
+  ///
+  /// The units used with the provided [SpringDescription] must similarly be
+  /// consistent with the other arguments. A default set of constants is used
+  /// for the `spring` description if it is omitted; these defaults assume
+  /// that the unit of length is the logical pixel.
+  BouncingScrollSimulation({
+    required double position,
+    required double velocity,
+    required this.leadingExtent,
+    required this.trailingExtent,
+    required this.spring,
+    Tolerance tolerance = Tolerance.defaultTolerance,
+  }) : assert(position != null),
+       assert(velocity != null),
+       assert(leadingExtent != null),
+       assert(trailingExtent != null),
+       assert(leadingExtent <= trailingExtent),
+       assert(spring != null),
+       super(tolerance: tolerance) {
+    if (position < leadingExtent) {
+      _springSimulation = _underscrollSimulation(position, velocity);
+      _springTime = double.negativeInfinity;
+    } else if (position > trailingExtent) {
+      _springSimulation = _overscrollSimulation(position, velocity);
+      _springTime = double.negativeInfinity;
+    } else {
+      // Taken from UIScrollView.decelerationRate (.normal = 0.998)
+      // 0.998^1000 = ~0.135
+      _frictionSimulation = FrictionSimulation(0.135, position, velocity);
+      final double finalX = _frictionSimulation.finalX;
+      if (velocity > 0.0 && finalX > trailingExtent) {
+        _springTime = _frictionSimulation.timeAtX(trailingExtent);
+        _springSimulation = _overscrollSimulation(
+          trailingExtent,
+          math.min(_frictionSimulation.dx(_springTime), maxSpringTransferVelocity),
+        );
+        assert(_springTime.isFinite);
+      } else if (velocity < 0.0 && finalX < leadingExtent) {
+        _springTime = _frictionSimulation.timeAtX(leadingExtent);
+        _springSimulation = _underscrollSimulation(
+          leadingExtent,
+          math.min(_frictionSimulation.dx(_springTime), maxSpringTransferVelocity),
+        );
+        assert(_springTime.isFinite);
+      } else {
+        _springTime = double.infinity;
+      }
+    }
+    assert(_springTime != null);
+  }
+
+  /// The maximum velocity that can be transferred from the inertia of a ballistic
+  /// scroll into overscroll.
+  static const double maxSpringTransferVelocity = 5000.0;
+
+  /// When [x] falls below this value the simulation switches from an internal friction
+  /// model to a spring model which causes [x] to "spring" back to [leadingExtent].
+  final double leadingExtent;
+
+  /// When [x] exceeds this value the simulation switches from an internal friction
+  /// model to a spring model which causes [x] to "spring" back to [trailingExtent].
+  final double trailingExtent;
+
+  /// The spring used used to return [x] to either [leadingExtent] or [trailingExtent].
+  final SpringDescription spring;
+
+  late FrictionSimulation _frictionSimulation;
+  late Simulation _springSimulation;
+  late double _springTime;
+  double _timeOffset = 0.0;
+
+  Simulation _underscrollSimulation(double x, double dx) {
+    return ScrollSpringSimulation(spring, x, leadingExtent, dx);
+  }
+
+  Simulation _overscrollSimulation(double x, double dx) {
+    return ScrollSpringSimulation(spring, x, trailingExtent, dx);
+  }
+
+  Simulation _simulation(double time) {
+    final Simulation simulation;
+    if (time > _springTime) {
+      _timeOffset = _springTime.isFinite ? _springTime : 0.0;
+      simulation = _springSimulation;
+    } else {
+      _timeOffset = 0.0;
+      simulation = _frictionSimulation;
+    }
+    return simulation..tolerance = tolerance;
+  }
+
+  @override
+  double x(double time) => _simulation(time).x(time - _timeOffset);
+
+  @override
+  double dx(double time) => _simulation(time).dx(time - _timeOffset);
+
+  @override
+  bool isDone(double time) => _simulation(time).isDone(time - _timeOffset);
+
+  @override
+  String toString() {
+    return '${objectRuntimeType(this, 'BouncingScrollSimulation')}(leadingExtent: $leadingExtent, trailingExtent: $trailingExtent)';
+  }
+}
+
+/// An implementation of scroll physics that matches Android.
+///
+/// See also:
+///
+///  * [BouncingScrollSimulation], which implements iOS scroll physics.
+//
+// This class is based on Scroller.java from Android:
+//   https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget
+//
+// The "See..." comments below refer to Scroller methods and values. Some
+// simplifications have been made.
+class ClampingScrollSimulation extends Simulation {
+  /// Creates a scroll physics simulation that matches Android scrolling.
+  ClampingScrollSimulation({
+    required this.position,
+    required this.velocity,
+    this.friction = 0.015,
+    Tolerance tolerance = Tolerance.defaultTolerance,
+  }) : assert(_flingVelocityPenetration(0.0) == _initialVelocityPenetration),
+       super(tolerance: tolerance) {
+    _duration = _flingDuration(velocity);
+    _distance = (velocity * _duration / _initialVelocityPenetration).abs();
+  }
+
+  /// The position of the particle at the beginning of the simulation.
+  final double position;
+
+  /// The velocity at which the particle is traveling at the beginning of the
+  /// simulation.
+  final double velocity;
+
+  /// The amount of friction the particle experiences as it travels.
+  ///
+  /// The more friction the particle experiences, the sooner it stops.
+  final double friction;
+
+  late double _duration;
+  late double _distance;
+
+  // See DECELERATION_RATE.
+  static final double _kDecelerationRate = math.log(0.78) / math.log(0.9);
+
+  // See computeDeceleration().
+  static double _decelerationForFriction(double friction) {
+    return friction * 61774.04968;
+  }
+
+  // See getSplineFlingDuration(). Returns a value in seconds.
+  double _flingDuration(double velocity) {
+    // See mPhysicalCoeff
+    final double scaledFriction = friction * _decelerationForFriction(0.84);
+
+    // See getSplineDeceleration().
+    final double deceleration = math.log(0.35 * velocity.abs() / scaledFriction);
+
+    return math.exp(deceleration / (_kDecelerationRate - 1.0));
+  }
+
+  // Based on a cubic curve fit to the Scroller.computeScrollOffset() values
+  // produced for an initial velocity of 4000. The value of Scroller.getDuration()
+  // and Scroller.getFinalY() were 686ms and 961 pixels respectively.
+  //
+  // Algebra courtesy of Wolfram Alpha.
+  //
+  // f(x) = scrollOffset, x is time in milliseconds
+  // f(x) = 3.60882×10^-6 x^3 - 0.00668009 x^2 + 4.29427 x - 3.15307
+  // f(x) = 3.60882×10^-6 x^3 - 0.00668009 x^2 + 4.29427 x, so f(0) is 0
+  // f(686ms) = 961 pixels
+  // Scale to f(0 <= t <= 1.0), x = t * 686
+  // f(t) = 1165.03 t^3 - 3143.62 t^2 + 2945.87 t
+  // Scale f(t) so that 0.0 <= f(t) <= 1.0
+  // f(t) = (1165.03 t^3 - 3143.62 t^2 + 2945.87 t) / 961.0
+  //      = 1.2 t^3 - 3.27 t^2 + 3.065 t
+  static const double _initialVelocityPenetration = 3.065;
+  static double _flingDistancePenetration(double t) {
+    return (1.2 * t * t * t) - (3.27 * t * t) + (_initialVelocityPenetration * t);
+  }
+
+  // The derivative of the _flingDistancePenetration() function.
+  static double _flingVelocityPenetration(double t) {
+    return (3.6 * t * t) - (6.54 * t) + _initialVelocityPenetration;
+  }
+
+  @override
+  double x(double time) {
+    final double t = (time / _duration).clamp(0.0, 1.0);
+    return position + _distance * _flingDistancePenetration(t) * velocity.sign;
+  }
+
+  @override
+  double dx(double time) {
+    final double t = (time / _duration).clamp(0.0, 1.0);
+    return _distance * _flingVelocityPenetration(t) * velocity.sign / _duration;
+  }
+
+  @override
+  bool isDone(double time) {
+    return time >= _duration;
+  }
+}
diff --git a/lib/src/widgets/scroll_view.dart b/lib/src/widgets/scroll_view.dart
new file mode 100644
index 0000000..8ae9c08
--- /dev/null
+++ b/lib/src/widgets/scroll_view.dart
@@ -0,0 +1,1989 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/rendering.dart';
+import 'package:flute/gestures.dart';
+
+import 'basic.dart';
+import 'debug.dart';
+import 'focus_manager.dart';
+import 'focus_scope.dart';
+import 'framework.dart';
+import 'media_query.dart';
+import 'notification_listener.dart';
+import 'primary_scroll_controller.dart';
+import 'scroll_controller.dart';
+import 'scroll_notification.dart';
+import 'scroll_physics.dart';
+import 'scrollable.dart';
+import 'sliver.dart';
+import 'viewport.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// int itemCount;
+
+/// A representation of how a [ScrollView] should dismiss the on-screen
+/// keyboard.
+enum ScrollViewKeyboardDismissBehavior {
+  /// `manual` means there is no automatic dismissal of the on-screen keyboard.
+  /// It is up to the client to dismiss the keyboard.
+  manual,
+  /// `onDrag` means that the [ScrollView] will dismiss an on-screen keyboard
+  /// when a drag begins.
+  onDrag,
+}
+
+/// A widget that scrolls.
+///
+/// Scrollable widgets consist of three pieces:
+///
+///  1. A [Scrollable] widget, which listens for various user gestures and
+///     implements the interaction design for scrolling.
+///  2. A viewport widget, such as [Viewport] or [ShrinkWrappingViewport], which
+///     implements the visual design for scrolling by displaying only a portion
+///     of the widgets inside the scroll view.
+///  3. One or more slivers, which are widgets that can be composed to created
+///     various scrolling effects, such as lists, grids, and expanding headers.
+///
+/// [ScrollView] helps orchestrate these pieces by creating the [Scrollable] and
+/// the viewport and deferring to its subclass to create the slivers.
+///
+/// To control the initial scroll offset of the scroll view, provide a
+/// [controller] with its [ScrollController.initialScrollOffset] property set.
+///
+/// See also:
+///
+///  * [ListView], which is a commonly used [ScrollView] that displays a
+///    scrolling, linear list of child widgets.
+///  * [PageView], which is a scrolling list of child widgets that are each the
+///    size of the viewport.
+///  * [GridView], which is a [ScrollView] that displays a scrolling, 2D array
+///    of child widgets.
+///  * [CustomScrollView], which is a [ScrollView] that creates custom scroll
+///    effects using slivers.
+///  * [ScrollNotification] and [NotificationListener], which can be used to watch
+///    the scroll position without using a [ScrollController].
+abstract class ScrollView extends StatelessWidget {
+  /// Creates a widget that scrolls.
+  ///
+  /// If the [primary] argument is true, the [controller] must be null.
+  ///
+  /// If the [shrinkWrap] argument is true, the [center] argument must be null.
+  ///
+  /// The [scrollDirection], [reverse], and [shrinkWrap] arguments must not be null.
+  ///
+  /// The [anchor] argument must be non-null and in the range 0.0 to 1.0.
+  const ScrollView({
+    Key? key,
+    this.scrollDirection = Axis.vertical,
+    this.reverse = false,
+    this.controller,
+    bool? primary,
+    ScrollPhysics? physics,
+    this.shrinkWrap = false,
+    this.center,
+    this.anchor = 0.0,
+    this.cacheExtent,
+    this.semanticChildCount,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+    this.restorationId,
+    this.clipBehavior = Clip.hardEdge,
+  }) : assert(scrollDirection != null),
+       assert(reverse != null),
+       assert(shrinkWrap != null),
+       assert(dragStartBehavior != null),
+       assert(clipBehavior != null),
+       assert(!(controller != null && primary == true),
+           'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. '
+           'You cannot both set primary to true and pass an explicit controller.'
+       ),
+       assert(!shrinkWrap || center == null),
+       assert(anchor != null),
+       assert(anchor >= 0.0 && anchor <= 1.0),
+       assert(semanticChildCount == null || semanticChildCount >= 0),
+       primary = primary ?? controller == null && identical(scrollDirection, Axis.vertical),
+       physics = physics ?? (primary == true || (primary == null && controller == null && identical(scrollDirection, Axis.vertical)) ? const AlwaysScrollableScrollPhysics() : null),
+       super(key: key);
+
+  /// The axis along which the scroll view scrolls.
+  ///
+  /// Defaults to [Axis.vertical].
+  final Axis scrollDirection;
+
+  /// Whether the scroll view scrolls in the reading direction.
+  ///
+  /// For example, if the reading direction is left-to-right and
+  /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from
+  /// left to right when [reverse] is false and from right to left when
+  /// [reverse] is true.
+  ///
+  /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
+  /// scrolls from top to bottom when [reverse] is false and from bottom to top
+  /// when [reverse] is true.
+  ///
+  /// Defaults to false.
+  final bool reverse;
+
+  /// An object that can be used to control the position to which this scroll
+  /// view is scrolled.
+  ///
+  /// Must be null if [primary] is true.
+  ///
+  /// A [ScrollController] serves several purposes. It can be used to control
+  /// the initial scroll position (see [ScrollController.initialScrollOffset]).
+  /// It can be used to control whether the scroll view should automatically
+  /// save and restore its scroll position in the [PageStorage] (see
+  /// [ScrollController.keepScrollOffset]). It can be used to read the current
+  /// scroll position (see [ScrollController.offset]), or change it (see
+  /// [ScrollController.animateTo]).
+  final ScrollController? controller;
+
+  /// Whether this is the primary scroll view associated with the parent
+  /// [PrimaryScrollController].
+  ///
+  /// When this is true, the scroll view is scrollable even if it does not have
+  /// sufficient content to actually scroll. Otherwise, by default the user can
+  /// only scroll the view if it has sufficient content. See [physics].
+  ///
+  /// Also when true, the scroll view is used for default [ScrollAction]s. If a
+  /// ScrollAction is not handled by an otherwise focused part of the application,
+  /// the ScrollAction will be evaluated using this scroll view, for example,
+  /// when executing [Shortcuts] key events like page up and down.
+  ///
+  /// On iOS, this also identifies the scroll view that will scroll to top in
+  /// response to a tap in the status bar.
+  ///
+  /// Defaults to true when [scrollDirection] is [Axis.vertical] and
+  /// [controller] is null.
+  final bool primary;
+
+  /// How the scroll view should respond to user input.
+  ///
+  /// For example, determines how the scroll view continues to animate after the
+  /// user stops dragging the scroll view.
+  ///
+  /// Defaults to matching platform conventions. Furthermore, if [primary] is
+  /// false, then the user cannot scroll if there is insufficient content to
+  /// scroll, while if [primary] is true, they can always attempt to scroll.
+  ///
+  /// To force the scroll view to always be scrollable even if there is
+  /// insufficient content, as if [primary] was true but without necessarily
+  /// setting it to true, provide an [AlwaysScrollableScrollPhysics] physics
+  /// object, as in:
+  ///
+  /// ```dart
+  ///   physics: const AlwaysScrollableScrollPhysics(),
+  /// ```
+  ///
+  /// To force the scroll view to use the default platform conventions and not
+  /// be scrollable if there is insufficient content, regardless of the value of
+  /// [primary], provide an explicit [ScrollPhysics] object, as in:
+  ///
+  /// ```dart
+  ///   physics: const ScrollPhysics(),
+  /// ```
+  ///
+  /// The physics can be changed dynamically (by providing a new object in a
+  /// subsequent build), but new physics will only take effect if the _class_ of
+  /// the provided object changes. Merely constructing a new instance with a
+  /// different configuration is insufficient to cause the physics to be
+  /// reapplied. (This is because the final object used is generated
+  /// dynamically, which can be relatively expensive, and it would be
+  /// inefficient to speculatively create this object each frame to see if the
+  /// physics should be updated.)
+  final ScrollPhysics? physics;
+
+  /// Whether the extent of the scroll view in the [scrollDirection] should be
+  /// determined by the contents being viewed.
+  ///
+  /// If the scroll view does not shrink wrap, then the scroll view will expand
+  /// to the maximum allowed size in the [scrollDirection]. If the scroll view
+  /// has unbounded constraints in the [scrollDirection], then [shrinkWrap] must
+  /// be true.
+  ///
+  /// Shrink wrapping the content of the scroll view is significantly more
+  /// expensive than expanding to the maximum allowed size because the content
+  /// can expand and contract during scrolling, which means the size of the
+  /// scroll view needs to be recomputed whenever the scroll position changes.
+  ///
+  /// Defaults to false.
+  final bool shrinkWrap;
+
+  /// The first child in the [GrowthDirection.forward] growth direction.
+  ///
+  /// Children after [center] will be placed in the [AxisDirection] determined
+  /// by [scrollDirection] and [reverse] relative to the [center]. Children
+  /// before [center] will be placed in the opposite of the axis direction
+  /// relative to the [center]. This makes the [center] the inflection point of
+  /// the growth direction.
+  ///
+  /// The [center] must be the key of one of the slivers built by [buildSlivers].
+  ///
+  /// Of the built-in subclasses of [ScrollView], only [CustomScrollView]
+  /// supports [center]; for that class, the given key must be the key of one of
+  /// the slivers in the [CustomScrollView.slivers] list.
+  ///
+  /// See also:
+  ///
+  ///  * [anchor], which controls where the [center] as aligned in the viewport.
+  final Key? center;
+
+  /// The relative position of the zero scroll offset.
+  ///
+  /// For example, if [anchor] is 0.5 and the [AxisDirection] determined by
+  /// [scrollDirection] and [reverse] is [AxisDirection.down] or
+  /// [AxisDirection.up], then the zero scroll offset is vertically centered
+  /// within the viewport. If the [anchor] is 1.0, and the axis direction is
+  /// [AxisDirection.right], then the zero scroll offset is on the left edge of
+  /// the viewport.
+  final double anchor;
+
+  /// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
+  final double? cacheExtent;
+
+  /// The number of children that will contribute semantic information.
+  ///
+  /// Some subtypes of [ScrollView] can infer this value automatically. For
+  /// example [ListView] will use the number of widgets in the child list,
+  /// while the [ListView.separated] constructor will use half that amount.
+  ///
+  /// For [CustomScrollView] and other types which do not receive a builder
+  /// or list of widgets, the child count must be explicitly provided. If the
+  /// number is unknown or unbounded this should be left unset or set to null.
+  ///
+  /// See also:
+  ///
+  ///  * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property.
+  final int? semanticChildCount;
+
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  /// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will
+  /// dismiss the keyboard automatically.
+  final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
+
+  /// {@macro flutter.widgets.scrollable.restorationId}
+  final String? restorationId;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge].
+  final Clip clipBehavior;
+
+  /// Returns the [AxisDirection] in which the scroll view scrolls.
+  ///
+  /// Combines the [scrollDirection] with the [reverse] boolean to obtain the
+  /// concrete [AxisDirection].
+  ///
+  /// If the [scrollDirection] is [Axis.horizontal], the ambient
+  /// [Directionality] is also considered when selecting the concrete
+  /// [AxisDirection]. For example, if the ambient [Directionality] is
+  /// [TextDirection.rtl], then the non-reversed [AxisDirection] is
+  /// [AxisDirection.left] and the reversed [AxisDirection] is
+  /// [AxisDirection.right].
+  @protected
+  AxisDirection getDirection(BuildContext context) {
+    return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
+  }
+
+  /// Build the list of widgets to place inside the viewport.
+  ///
+  /// Subclasses should override this method to build the slivers for the inside
+  /// of the viewport.
+  @protected
+  List<Widget> buildSlivers(BuildContext context);
+
+  /// Build the viewport.
+  ///
+  /// Subclasses may override this method to change how the viewport is built.
+  /// The default implementation uses a [ShrinkWrappingViewport] if [shrinkWrap]
+  /// is true, and a regular [Viewport] otherwise.
+  ///
+  /// The `offset` argument is the value obtained from
+  /// [Scrollable.viewportBuilder].
+  ///
+  /// The `axisDirection` argument is the value obtained from [getDirection],
+  /// which by default uses [scrollDirection] and [reverse].
+  ///
+  /// The `slivers` argument is the value obtained from [buildSlivers].
+  @protected
+  Widget buildViewport(
+    BuildContext context,
+    ViewportOffset offset,
+    AxisDirection axisDirection,
+    List<Widget> slivers,
+  ) {
+    assert(() {
+      switch (axisDirection) {
+        case AxisDirection.up:
+        case AxisDirection.down:
+          return debugCheckHasDirectionality(
+            context,
+            why: 'to determine the cross-axis direction of the scroll view',
+            hint: 'Vertical scroll views create Viewport widgets that try to determine their cross axis direction '
+                  'from the ambient Directionality.',
+          );
+        case AxisDirection.left:
+        case AxisDirection.right:
+          return true;
+      }
+    }());
+    if (shrinkWrap) {
+      return ShrinkWrappingViewport(
+        axisDirection: axisDirection,
+        offset: offset,
+        slivers: slivers,
+        clipBehavior: clipBehavior,
+      );
+    }
+    return Viewport(
+      axisDirection: axisDirection,
+      offset: offset,
+      slivers: slivers,
+      cacheExtent: cacheExtent,
+      center: center,
+      anchor: anchor,
+      clipBehavior: clipBehavior,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final List<Widget> slivers = buildSlivers(context);
+    final AxisDirection axisDirection = getDirection(context);
+
+    final ScrollController? scrollController =
+        primary ? PrimaryScrollController.of(context) : controller;
+    final Scrollable scrollable = Scrollable(
+      dragStartBehavior: dragStartBehavior,
+      axisDirection: axisDirection,
+      controller: scrollController,
+      physics: physics,
+      semanticChildCount: semanticChildCount,
+      restorationId: restorationId,
+      viewportBuilder: (BuildContext context, ViewportOffset offset) {
+        return buildViewport(context, offset, axisDirection, slivers);
+      },
+    );
+    final Widget scrollableResult = primary && scrollController != null
+        ? PrimaryScrollController.none(child: scrollable)
+        : scrollable;
+
+    if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) {
+      return NotificationListener<ScrollUpdateNotification>(
+        child: scrollableResult,
+        onNotification: (ScrollUpdateNotification notification) {
+          final FocusScopeNode focusScope = FocusScope.of(context);
+          if (notification.dragDetails != null && focusScope.hasFocus) {
+            focusScope.unfocus();
+          }
+          return false;
+        },
+      );
+    } else {
+      return scrollableResult;
+    }
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<Axis>('scrollDirection', scrollDirection));
+    properties.add(FlagProperty('reverse', value: reverse, ifTrue: 'reversed', showName: true));
+    properties.add(DiagnosticsProperty<ScrollController>('controller', controller, showName: false, defaultValue: null));
+    properties.add(FlagProperty('primary', value: primary, ifTrue: 'using primary controller', showName: true));
+    properties.add(DiagnosticsProperty<ScrollPhysics>('physics', physics, showName: false, defaultValue: null));
+    properties.add(FlagProperty('shrinkWrap', value: shrinkWrap, ifTrue: 'shrink-wrapping', showName: true));
+  }
+}
+
+/// A [ScrollView] that creates custom scroll effects using slivers.
+///
+/// A [CustomScrollView] lets you supply [slivers] directly to create various
+/// scrolling effects, such as lists, grids, and expanding headers. For example,
+/// to create a scroll view that contains an expanding app bar followed by a
+/// list and a grid, use a list of three slivers: [SliverAppBar], [SliverList],
+/// and [SliverGrid].
+///
+/// [Widget]s in these [slivers] must produce [RenderSliver] objects.
+///
+/// To control the initial scroll offset of the scroll view, provide a
+/// [controller] with its [ScrollController.initialScrollOffset] property set.
+///
+/// {@animation 400 376 https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_scroll_view.mp4}
+///
+/// {@tool snippet}
+///
+/// This sample code shows a scroll view that contains a flexible pinned app
+/// bar, a grid, and an infinite list.
+///
+/// ```dart
+/// CustomScrollView(
+///   slivers: <Widget>[
+///     const SliverAppBar(
+///       pinned: true,
+///       expandedHeight: 250.0,
+///       flexibleSpace: FlexibleSpaceBar(
+///         title: Text('Demo'),
+///       ),
+///     ),
+///     SliverGrid(
+///       gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
+///         maxCrossAxisExtent: 200.0,
+///         mainAxisSpacing: 10.0,
+///         crossAxisSpacing: 10.0,
+///         childAspectRatio: 4.0,
+///       ),
+///       delegate: SliverChildBuilderDelegate(
+///         (BuildContext context, int index) {
+///           return Container(
+///             alignment: Alignment.center,
+///             color: Colors.teal[100 * (index % 9)],
+///             child: Text('Grid Item $index'),
+///           );
+///         },
+///         childCount: 20,
+///       ),
+///     ),
+///     SliverFixedExtentList(
+///       itemExtent: 50.0,
+///       delegate: SliverChildBuilderDelegate(
+///         (BuildContext context, int index) {
+///           return Container(
+///             alignment: Alignment.center,
+///             color: Colors.lightBlue[100 * (index % 9)],
+///             child: Text('List Item $index'),
+///           );
+///         },
+///       ),
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+///
+/// By default, if items are inserted at the "top" of a scrolling container like
+/// [ListView] or [CustomScrollView], the top item and all of the items below it
+/// are scrolled downwards. In some applications, it's preferable to have the
+/// top of the list just grow upwards, without changing the scroll position.
+/// This example demonstrates how to do that with a [CustomScrollView] with
+/// two [SliverList] children, and the [CustomScrollView.center] set to the key
+/// of the bottom SliverList. The top one SliverList will grow upwards, and the
+/// bottom SliverList will grow downwards.
+///
+/// ```dart
+/// List<int> top = [];
+/// List<int> bottom = [0];
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   const Key centerKey = ValueKey('bottom-sliver-list');
+///   return Scaffold(
+///     appBar: AppBar(
+///       title: const Text('Press on the plus to add items above and below'),
+///       leading: IconButton(
+///         icon: const Icon(Icons.add),
+///         onPressed: () {
+///           setState(() {
+///             top.add(-top.length - 1);
+///             bottom.add(bottom.length);
+///           });
+///         },
+///       ),
+///     ),
+///     body: CustomScrollView(
+///       center: centerKey,
+///       slivers: <Widget>[
+///         SliverList(
+///           delegate: SliverChildBuilderDelegate(
+///             (BuildContext context, int index) {
+///               return Container(
+///                 alignment: Alignment.center,
+///                 color: Colors.blue[200 + top[index] % 4 * 100],
+///                 height: 100 + top[index] % 4 * 20.0,
+///                 child: Text('Item: ${top[index]}'),
+///               );
+///             },
+///             childCount: top.length,
+///           ),
+///         ),
+///         SliverList(
+///           key: centerKey,
+///           delegate: SliverChildBuilderDelegate(
+///             (BuildContext context, int index) {
+///               return Container(
+///                 alignment: Alignment.center,
+///                 color: Colors.blue[200 + bottom[index] % 4 * 100],
+///                 height: 100 + bottom[index] % 4 * 20.0,
+///                 child: Text('Item: ${bottom[index]}'),
+///               );
+///             },
+///             childCount: bottom.length,
+///           ),
+///         ),
+///       ],
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ## Accessibility
+///
+/// A [CustomScrollView] can allow Talkback/VoiceOver to make announcements
+/// to the user when the scroll state changes. For example, on Android an
+/// announcement might be read as "showing items 1 to 10 of 23". To produce
+/// this announcement, the scroll view needs three pieces of information:
+///
+///   * The first visible child index.
+///   * The total number of children.
+///   * The total number of visible children.
+///
+/// The last value can be computed exactly by the framework, however the first
+/// two must be provided. Most of the higher-level scrollable widgets provide
+/// this information automatically. For example, [ListView] provides each child
+/// widget with a semantic index automatically and sets the semantic child
+/// count to the length of the list.
+///
+/// To determine visible indexes, the scroll view needs a way to associate the
+/// generated semantics of each scrollable item with a semantic index. This can
+/// be done by wrapping the child widgets in an [IndexedSemantics].
+///
+/// This semantic index is not necessarily the same as the index of the widget in
+/// the scrollable, because some widgets may not contribute semantic
+/// information. Consider a [ListView.separated]: every other widget is a
+/// divider with no semantic information. In this case, only odd numbered
+/// widgets have a semantic index (equal to the index ~/ 2). Furthermore, the
+/// total number of children in this example would be half the number of
+/// widgets. (The [ListView.separated] constructor handles this
+/// automatically; this is only used here as an example.)
+///
+/// The total number of visible children can be provided by the constructor
+/// parameter `semanticChildCount`. This should always be the same as the
+/// number of widgets wrapped in [IndexedSemantics].
+///
+/// See also:
+///
+///  * [SliverList], which is a sliver that displays linear list of children.
+///  * [SliverFixedExtentList], which is a more efficient sliver that displays
+///    linear list of children that have the same extent along the scroll axis.
+///  * [SliverGrid], which is a sliver that displays a 2D array of children.
+///  * [SliverPadding], which is a sliver that adds blank space around another
+///    sliver.
+///  * [SliverAppBar], which is a sliver that displays a header that can expand
+///    and float as the scroll view scrolls.
+///  * [ScrollNotification] and [NotificationListener], which can be used to watch
+///    the scroll position without using a [ScrollController].
+///  * [IndexedSemantics], which allows annotating child lists with an index
+///    for scroll announcements.
+class CustomScrollView extends ScrollView {
+  /// Creates a [ScrollView] that creates custom scroll effects using slivers.
+  ///
+  /// See the [ScrollView] constructor for more details on these arguments.
+  const CustomScrollView({
+    Key? key,
+    Axis scrollDirection = Axis.vertical,
+    bool reverse = false,
+    ScrollController? controller,
+    bool? primary,
+    ScrollPhysics? physics,
+    bool shrinkWrap = false,
+    Key? center,
+    double anchor = 0.0,
+    double? cacheExtent,
+    this.slivers = const <Widget>[],
+    int? semanticChildCount,
+    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
+    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+    String? restorationId,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : super(
+    key: key,
+    scrollDirection: scrollDirection,
+    reverse: reverse,
+    controller: controller,
+    primary: primary,
+    physics: physics,
+    shrinkWrap: shrinkWrap,
+    center: center,
+    anchor: anchor,
+    cacheExtent: cacheExtent,
+    semanticChildCount: semanticChildCount,
+    dragStartBehavior: dragStartBehavior,
+    keyboardDismissBehavior: keyboardDismissBehavior,
+    restorationId: restorationId,
+    clipBehavior: clipBehavior,
+  );
+
+  /// The slivers to place inside the viewport.
+  final List<Widget> slivers;
+
+  @override
+  List<Widget> buildSlivers(BuildContext context) => slivers;
+}
+
+/// A [ScrollView] that uses a single child layout model.
+///
+/// See also:
+///
+///  * [ListView], which is a [BoxScrollView] that uses a linear layout model.
+///  * [GridView], which is a [BoxScrollView] that uses a 2D layout model.
+///  * [CustomScrollView], which can combine multiple child layout models into a
+///    single scroll view.
+abstract class BoxScrollView extends ScrollView {
+  /// Creates a [ScrollView] uses a single child layout model.
+  ///
+  /// If the [primary] argument is true, the [controller] must be null.
+  const BoxScrollView({
+    Key? key,
+    Axis scrollDirection = Axis.vertical,
+    bool reverse = false,
+    ScrollController? controller,
+    bool? primary,
+    ScrollPhysics? physics,
+    bool shrinkWrap = false,
+    this.padding,
+    double? cacheExtent,
+    int? semanticChildCount,
+    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
+    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+    String? restorationId,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : super(
+    key: key,
+    scrollDirection: scrollDirection,
+    reverse: reverse,
+    controller: controller,
+    primary: primary,
+    physics: physics,
+    shrinkWrap: shrinkWrap,
+    cacheExtent: cacheExtent,
+    semanticChildCount: semanticChildCount,
+    dragStartBehavior: dragStartBehavior,
+    keyboardDismissBehavior: keyboardDismissBehavior,
+    restorationId: restorationId,
+    clipBehavior: clipBehavior,
+  );
+
+  /// The amount of space by which to inset the children.
+  final EdgeInsetsGeometry? padding;
+
+  @override
+  List<Widget> buildSlivers(BuildContext context) {
+    Widget sliver = buildChildLayout(context);
+    EdgeInsetsGeometry? effectivePadding = padding;
+    if (padding == null) {
+      final MediaQueryData? mediaQuery = MediaQuery.maybeOf(context);
+      if (mediaQuery != null) {
+        // Automatically pad sliver with padding from MediaQuery.
+        final EdgeInsets mediaQueryHorizontalPadding =
+            mediaQuery.padding.copyWith(top: 0.0, bottom: 0.0);
+        final EdgeInsets mediaQueryVerticalPadding =
+            mediaQuery.padding.copyWith(left: 0.0, right: 0.0);
+        // Consume the main axis padding with SliverPadding.
+        effectivePadding = scrollDirection == Axis.vertical
+            ? mediaQueryVerticalPadding
+            : mediaQueryHorizontalPadding;
+        // Leave behind the cross axis padding.
+        sliver = MediaQuery(
+          data: mediaQuery.copyWith(
+            padding: scrollDirection == Axis.vertical
+                ? mediaQueryHorizontalPadding
+                : mediaQueryVerticalPadding,
+          ),
+          child: sliver,
+        );
+      }
+    }
+
+    if (effectivePadding != null)
+      sliver = SliverPadding(padding: effectivePadding, sliver: sliver);
+    return <Widget>[ sliver ];
+  }
+
+  /// Subclasses should override this method to build the layout model.
+  @protected
+  Widget buildChildLayout(BuildContext context);
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
+  }
+}
+
+/// A scrollable list of widgets arranged linearly.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=KJpkjHGiI5A}
+///
+/// [ListView] is the most commonly used scrolling widget. It displays its
+/// children one after another in the scroll direction. In the cross axis, the
+/// children are required to fill the [ListView].
+///
+/// If non-null, the [itemExtent] forces the children to have the given extent
+/// in the scroll direction. Specifying an [itemExtent] is more efficient than
+/// letting the children determine their own extent because the scrolling
+/// machinery can make use of the foreknowledge of the children's extent to save
+/// work, for example when the scroll position changes drastically.
+///
+/// There are four options for constructing a [ListView]:
+///
+///  1. The default constructor takes an explicit [List<Widget>] of children. This
+///     constructor is appropriate for list views with a small number of
+///     children because constructing the [List] requires doing work for every
+///     child that could possibly be displayed in the list view instead of just
+///     those children that are actually visible.
+///
+///  2. The [ListView.builder] constructor takes an [IndexedWidgetBuilder], which
+///     builds the children on demand. This constructor is appropriate for list views
+///     with a large (or infinite) number of children because the builder is called
+///     only for those children that are actually visible.
+///
+///  3. The [ListView.separated] constructor takes two [IndexedWidgetBuilder]s:
+///     `itemBuilder` builds child items on demand, and `separatorBuilder`
+///     similarly builds separator children which appear in between the child items.
+///     This constructor is appropriate for list views with a fixed number of children.
+///
+///  4. The [ListView.custom] constructor takes a [SliverChildDelegate], which provides
+///     the ability to customize additional aspects of the child model. For example,
+///     a [SliverChildDelegate] can control the algorithm used to estimate the
+///     size of children that are not actually visible.
+///
+/// To control the initial scroll offset of the scroll view, provide a
+/// [controller] with its [ScrollController.initialScrollOffset] property set.
+///
+/// By default, [ListView] will automatically pad the list's scrollable
+/// extremities to avoid partial obstructions indicated by [MediaQuery]'s
+/// padding. To avoid this behavior, override with a zero [padding] property.
+///
+/// {@tool snippet}
+/// This example uses the default constructor for [ListView] which takes an
+/// explicit [List<Widget>] of children. This [ListView]'s children are made up
+/// of [Container]s with [Text].
+///
+/// ![A ListView of 3 amber colored containers with sample text.](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view.png)
+///
+/// ```dart
+/// ListView(
+///   padding: const EdgeInsets.all(8),
+///   children: <Widget>[
+///     Container(
+///       height: 50,
+///       color: Colors.amber[600],
+///       child: const Center(child: Text('Entry A')),
+///     ),
+///     Container(
+///       height: 50,
+///       color: Colors.amber[500],
+///       child: const Center(child: Text('Entry B')),
+///     ),
+///     Container(
+///       height: 50,
+///       color: Colors.amber[100],
+///       child: const Center(child: Text('Entry C')),
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+/// This example mirrors the previous one, creating the same list using the
+/// [ListView.builder] constructor. Using the [IndexedWidgetBuilder], children
+/// are built lazily and can be infinite in number.
+///
+/// ![A ListView of 3 amber colored containers with sample text.](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view_builder.png)
+///
+/// ```dart
+/// final List<String> entries = <String>['A', 'B', 'C'];
+/// final List<int> colorCodes = <int>[600, 500, 100];
+///
+/// ListView.builder(
+///   padding: const EdgeInsets.all(8),
+///   itemCount: entries.length,
+///   itemBuilder: (BuildContext context, int index) {
+///     return Container(
+///       height: 50,
+///       color: Colors.amber[colorCodes[index]],
+///       child: Center(child: Text('Entry ${entries[index]}')),
+///     );
+///   }
+/// );
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+/// This example continues to build from our the previous ones, creating a
+/// similar list using [ListView.separated]. Here, a [Divider] is used as a
+/// separator.
+///
+/// ![A ListView of 3 amber colored containers with sample text and a Divider
+/// between each of them.](https://flutter.github.io/assets-for-api-docs/assets/widgets/list_view_separated.png)
+///
+/// ```dart
+/// final List<String> entries = <String>['A', 'B', 'C'];
+/// final List<int> colorCodes = <int>[600, 500, 100];
+///
+/// ListView.separated(
+///   padding: const EdgeInsets.all(8),
+///   itemCount: entries.length,
+///   itemBuilder: (BuildContext context, int index) {
+///     return Container(
+///       height: 50,
+///       color: Colors.amber[colorCodes[index]],
+///       child: Center(child: Text('Entry ${entries[index]}')),
+///     );
+///   },
+///   separatorBuilder: (BuildContext context, int index) => const Divider(),
+/// );
+/// ```
+/// {@end-tool}
+///
+/// ## Child elements' lifecycle
+///
+/// ### Creation
+///
+/// While laying out the list, visible children's elements, states and render
+/// objects will be created lazily based on existing widgets (such as when using
+/// the default constructor) or lazily provided ones (such as when using the
+/// [ListView.builder] constructor).
+///
+/// ### Destruction
+///
+/// When a child is scrolled out of view, the associated element subtree,
+/// states and render objects are destroyed. A new child at the same position
+/// in the list will be lazily recreated along with new elements, states and
+/// render objects when it is scrolled back.
+///
+/// ### Destruction mitigation
+///
+/// In order to preserve state as child elements are scrolled in and out of
+/// view, the following options are possible:
+///
+///  * Moving the ownership of non-trivial UI-state-driving business logic
+///    out of the list child subtree. For instance, if a list contains posts
+///    with their number of upvotes coming from a cached network response, store
+///    the list of posts and upvote number in a data model outside the list. Let
+///    the list child UI subtree be easily recreate-able from the
+///    source-of-truth model object. Use [StatefulWidget]s in the child
+///    widget subtree to store instantaneous UI state only.
+///
+///  * Letting [KeepAlive] be the root widget of the list child widget subtree
+///    that needs to be preserved. The [KeepAlive] widget marks the child
+///    subtree's top render object child for keepalive. When the associated top
+///    render object is scrolled out of view, the list keeps the child's render
+///    object (and by extension, its associated elements and states) in a cache
+///    list instead of destroying them. When scrolled back into view, the render
+///    object is repainted as-is (if it wasn't marked dirty in the interim).
+///
+///    This only works if `addAutomaticKeepAlives` and `addRepaintBoundaries`
+///    are false since those parameters cause the [ListView] to wrap each child
+///    widget subtree with other widgets.
+///
+///  * Using [AutomaticKeepAlive] widgets (inserted by default when
+///    `addAutomaticKeepAlives` is true). [AutomaticKeepAlive] allows descendant
+///    widgets to control whether the subtree is actually kept alive or not.
+///    This behavior is in contrast with [KeepAlive], which will unconditionally keep
+///    the subtree alive.
+///
+///    As an example, the [EditableText] widget signals its list child element
+///    subtree to stay alive while its text field has input focus. If it doesn't
+///    have focus and no other descendants signaled for keepalive via a
+///    [KeepAliveNotification], the list child element subtree will be destroyed
+///    when scrolled away.
+///
+///    [AutomaticKeepAlive] descendants typically signal it to be kept alive
+///    by using the [AutomaticKeepAliveClientMixin], then implementing the
+///    [AutomaticKeepAliveClientMixin.wantKeepAlive] getter and calling
+///    [AutomaticKeepAliveClientMixin.updateKeepAlive].
+///
+/// ## Transitioning to [CustomScrollView]
+///
+/// A [ListView] is basically a [CustomScrollView] with a single [SliverList] in
+/// its [CustomScrollView.slivers] property.
+///
+/// If [ListView] is no longer sufficient, for example because the scroll view
+/// is to have both a list and a grid, or because the list is to be combined
+/// with a [SliverAppBar], etc, it is straight-forward to port code from using
+/// [ListView] to using [CustomScrollView] directly.
+///
+/// The [key], [scrollDirection], [reverse], [controller], [primary], [physics],
+/// and [shrinkWrap] properties on [ListView] map directly to the identically
+/// named properties on [CustomScrollView].
+///
+/// The [CustomScrollView.slivers] property should be a list containing either a
+/// [SliverList] or a [SliverFixedExtentList]; the former if [itemExtent] on the
+/// [ListView] was null, and the latter if [itemExtent] was not null.
+///
+/// The [childrenDelegate] property on [ListView] corresponds to the
+/// [SliverList.delegate] (or [SliverFixedExtentList.delegate]) property. The
+/// [ListView] constructor's `children` argument corresponds to the
+/// [childrenDelegate] being a [SliverChildListDelegate] with that same
+/// argument. The [ListView.builder] constructor's `itemBuilder` and
+/// `itemCount` arguments correspond to the [childrenDelegate] being a
+/// [SliverChildBuilderDelegate] with the equivalent arguments.
+///
+/// The [padding] property corresponds to having a [SliverPadding] in the
+/// [CustomScrollView.slivers] property instead of the list itself, and having
+/// the [SliverList] instead be a child of the [SliverPadding].
+///
+/// [CustomScrollView]s don't automatically avoid obstructions from [MediaQuery]
+/// like [ListView]s do. To reproduce the behavior, wrap the slivers in
+/// [SliverSafeArea]s.
+///
+/// Once code has been ported to use [CustomScrollView], other slivers, such as
+/// [SliverGrid] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
+/// list.
+///
+/// {@tool snippet}
+///
+/// Here are two brief snippets showing a [ListView] and its equivalent using
+/// [CustomScrollView]:
+///
+/// ```dart
+/// ListView(
+///   shrinkWrap: true,
+///   padding: const EdgeInsets.all(20.0),
+///   children: <Widget>[
+///     const Text("I'm dedicating every day to you"),
+///     const Text('Domestic life was never quite my style'),
+///     const Text('When you smile, you knock me out, I fall apart'),
+///     const Text('And I thought I was so smart'),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+/// {@tool snippet}
+///
+/// ```dart
+/// CustomScrollView(
+///   shrinkWrap: true,
+///   slivers: <Widget>[
+///     SliverPadding(
+///       padding: const EdgeInsets.all(20.0),
+///       sliver: SliverList(
+///         delegate: SliverChildListDelegate(
+///           <Widget>[
+///             const Text("I'm dedicating every day to you"),
+///             const Text('Domestic life was never quite my style'),
+///             const Text('When you smile, you knock me out, I fall apart'),
+///             const Text('And I thought I was so smart'),
+///           ],
+///         ),
+///       ),
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ## Special handling for an empty list
+///
+/// A common design pattern is to have a custom UI for an empty list. The best
+/// way to achieve this in Flutter is just conditionally replacing the
+/// [ListView] at build time with whatever widgets you need to show for the
+/// empty list state:
+///
+/// {@tool snippet}
+///
+/// Example of simple empty list interface:
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     appBar: AppBar(title: const Text('Empty List Test')),
+///     body: itemCount > 0
+///       ? ListView.builder(
+///           itemCount: itemCount,
+///           itemBuilder: (BuildContext context, int index) {
+///             return ListTile(
+///               title: Text('Item ${index + 1}'),
+///             );
+///           },
+///         )
+///       : Center(child: const Text('No items')),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ## Selection of list items
+///
+/// `ListView` has no built-in notion of a selected item or items. For a small
+/// example of how a caller might wire up basic item selection, see
+/// [ListTile.selected].
+///
+/// See also:
+///
+///  * [SingleChildScrollView], which is a scrollable widget that has a single
+///    child.
+///  * [PageView], which is a scrolling list of child widgets that are each the
+///    size of the viewport.
+///  * [GridView], which is a scrollable, 2D array of widgets.
+///  * [CustomScrollView], which is a scrollable widget that creates custom
+///    scroll effects using slivers.
+///  * [ListBody], which arranges its children in a similar manner, but without
+///    scrolling.
+///  * [ScrollNotification] and [NotificationListener], which can be used to watch
+///    the scroll position without using a [ScrollController].
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+///  * Cookbook: [Use lists](https://flutter.dev/docs/cookbook/lists/basic-list)
+///  * Cookbook: [Work with long lists](https://flutter.dev/docs/cookbook/lists/long-lists)
+///  * Cookbook: [Create a horizontal list](https://flutter.dev/docs/cookbook/lists/horizontal-list)
+///  * Cookbook: [Create lists with different types of items](https://flutter.dev/docs/cookbook/lists/mixed-list)
+///  * Cookbook: [Implement swipe to dismiss](https://flutter.dev/docs/cookbook/gestures/dismissible)
+class ListView extends BoxScrollView {
+  /// Creates a scrollable, linear array of widgets from an explicit [List].
+  ///
+  /// This constructor is appropriate for list views with a small number of
+  /// children because constructing the [List] requires doing work for every
+  /// child that could possibly be displayed in the list view instead of just
+  /// those children that are actually visible.
+  ///
+  /// Like other widgets in the framework, this widget expects that
+  /// the [children] list will not be mutated after it has been passed in here.
+  /// See the documentation at [SliverChildListDelegate.children] for more details.
+  ///
+  /// It is usually more efficient to create children on demand using
+  /// [ListView.builder] because it will create the widget children lazily as necessary.
+  ///
+  /// The `addAutomaticKeepAlives` argument corresponds to the
+  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
+  /// `addRepaintBoundaries` argument corresponds to the
+  /// [SliverChildListDelegate.addRepaintBoundaries] property. The
+  /// `addSemanticIndexes` argument corresponds to the
+  /// [SliverChildListDelegate.addSemanticIndexes] property. None
+  /// may be null.
+  ListView({
+    Key? key,
+    Axis scrollDirection = Axis.vertical,
+    bool reverse = false,
+    ScrollController? controller,
+    bool? primary,
+    ScrollPhysics? physics,
+    bool shrinkWrap = false,
+    EdgeInsetsGeometry? padding,
+    this.itemExtent,
+    bool addAutomaticKeepAlives = true,
+    bool addRepaintBoundaries = true,
+    bool addSemanticIndexes = true,
+    double? cacheExtent,
+    List<Widget> children = const <Widget>[],
+    int? semanticChildCount,
+    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
+    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+    String? restorationId,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : childrenDelegate = SliverChildListDelegate(
+         children,
+         addAutomaticKeepAlives: addAutomaticKeepAlives,
+         addRepaintBoundaries: addRepaintBoundaries,
+         addSemanticIndexes: addSemanticIndexes,
+       ),
+       super(
+         key: key,
+         scrollDirection: scrollDirection,
+         reverse: reverse,
+         controller: controller,
+         primary: primary,
+         physics: physics,
+         shrinkWrap: shrinkWrap,
+         padding: padding,
+         cacheExtent: cacheExtent,
+         semanticChildCount: semanticChildCount ?? children.length,
+         dragStartBehavior: dragStartBehavior,
+         keyboardDismissBehavior: keyboardDismissBehavior,
+         restorationId: restorationId,
+         clipBehavior: clipBehavior,
+       );
+
+  /// Creates a scrollable, linear array of widgets that are created on demand.
+  ///
+  /// This constructor is appropriate for list views with a large (or infinite)
+  /// number of children because the builder is called only for those children
+  /// that are actually visible.
+  ///
+  /// Providing a non-null `itemCount` improves the ability of the [ListView] to
+  /// estimate the maximum scroll extent.
+  ///
+  /// The `itemBuilder` callback will be called only with indices greater than
+  /// or equal to zero and less than `itemCount`.
+  ///
+  /// The `itemBuilder` should always return a non-null widget, and actually
+  /// create the widget instances when called. Avoid using a builder that
+  /// returns a previously-constructed widget; if the list view's children are
+  /// created in advance, or all at once when the [ListView] itself is created,
+  /// it is more efficient to use the [ListView] constructor. Even more
+  /// efficient, however, is to create the instances on demand using this
+  /// constructor's `itemBuilder` callback.
+  ///
+  /// The `addAutomaticKeepAlives` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
+  /// `addRepaintBoundaries` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
+  /// `addSemanticIndexes` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None may be
+  /// null.
+  ///
+  /// [ListView.builder] by default does not support child reordering. If
+  /// you are planning to change child order at a later time, consider using
+  /// [ListView] or [ListView.custom].
+  ListView.builder({
+    Key? key,
+    Axis scrollDirection = Axis.vertical,
+    bool reverse = false,
+    ScrollController? controller,
+    bool? primary,
+    ScrollPhysics? physics,
+    bool shrinkWrap = false,
+    EdgeInsetsGeometry? padding,
+    this.itemExtent,
+    required IndexedWidgetBuilder itemBuilder,
+    int? itemCount,
+    bool addAutomaticKeepAlives = true,
+    bool addRepaintBoundaries = true,
+    bool addSemanticIndexes = true,
+    double? cacheExtent,
+    int? semanticChildCount,
+    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
+    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+    String? restorationId,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : assert(itemCount == null || itemCount >= 0),
+       assert(semanticChildCount == null || semanticChildCount <= itemCount!),
+       childrenDelegate = SliverChildBuilderDelegate(
+         itemBuilder,
+         childCount: itemCount,
+         addAutomaticKeepAlives: addAutomaticKeepAlives,
+         addRepaintBoundaries: addRepaintBoundaries,
+         addSemanticIndexes: addSemanticIndexes,
+       ),
+       super(
+         key: key,
+         scrollDirection: scrollDirection,
+         reverse: reverse,
+         controller: controller,
+         primary: primary,
+         physics: physics,
+         shrinkWrap: shrinkWrap,
+         padding: padding,
+         cacheExtent: cacheExtent,
+         semanticChildCount: semanticChildCount ?? itemCount,
+         dragStartBehavior: dragStartBehavior,
+         keyboardDismissBehavior: keyboardDismissBehavior,
+         restorationId: restorationId,
+         clipBehavior: clipBehavior,
+       );
+
+  /// Creates a fixed-length scrollable linear array of list "items" separated
+  /// by list item "separators".
+  ///
+  /// This constructor is appropriate for list views with a large number of
+  /// item and separator children because the builders are only called for
+  /// the children that are actually visible.
+  ///
+  /// The `itemBuilder` callback will be called with indices greater than
+  /// or equal to zero and less than `itemCount`.
+  ///
+  /// Separators only appear between list items: separator 0 appears after item
+  /// 0 and the last separator appears before the last item.
+  ///
+  /// The `separatorBuilder` callback will be called with indices greater than
+  /// or equal to zero and less than `itemCount - 1`.
+  ///
+  /// The `itemBuilder` and `separatorBuilder` callbacks should always return a
+  /// non-null widget, and actually create widget instances when called. Avoid
+  /// using a builder that returns a previously-constructed widget; if the list
+  /// view's children are created in advance, or all at once when the [ListView]
+  /// itself is created, it is more efficient to use the [ListView] constructor.
+  ///
+  /// {@tool snippet}
+  ///
+  /// This example shows how to create [ListView] whose [ListTile] list items
+  /// are separated by [Divider]s.
+  ///
+  /// ```dart
+  /// ListView.separated(
+  ///   itemCount: 25,
+  ///   separatorBuilder: (BuildContext context, int index) => Divider(),
+  ///   itemBuilder: (BuildContext context, int index) {
+  ///     return ListTile(
+  ///       title: Text('item $index'),
+  ///     );
+  ///   },
+  /// )
+  /// ```
+  /// {@end-tool}
+  ///
+  /// The `addAutomaticKeepAlives` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
+  /// `addRepaintBoundaries` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
+  /// `addSemanticIndexes` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None may be
+  /// null.
+  ListView.separated({
+    Key? key,
+    Axis scrollDirection = Axis.vertical,
+    bool reverse = false,
+    ScrollController? controller,
+    bool? primary,
+    ScrollPhysics? physics,
+    bool shrinkWrap = false,
+    EdgeInsetsGeometry? padding,
+    required IndexedWidgetBuilder itemBuilder,
+    required IndexedWidgetBuilder separatorBuilder,
+    required int itemCount,
+    bool addAutomaticKeepAlives = true,
+    bool addRepaintBoundaries = true,
+    bool addSemanticIndexes = true,
+    double? cacheExtent,
+    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
+    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+    String? restorationId,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : assert(itemBuilder != null),
+       assert(separatorBuilder != null),
+       assert(itemCount != null && itemCount >= 0),
+       itemExtent = null,
+       childrenDelegate = SliverChildBuilderDelegate(
+         (BuildContext context, int index) {
+           final int itemIndex = index ~/ 2;
+           final Widget widget;
+           if (index.isEven) {
+             widget = itemBuilder(context, itemIndex);
+           } else {
+             widget = separatorBuilder(context, itemIndex);
+             assert(() {
+               if (widget == null) { // ignore: dead_code
+                 throw FlutterError('separatorBuilder cannot return null.');
+               }
+               return true;
+             }());
+           }
+           return widget;
+         },
+         childCount: _computeActualChildCount(itemCount),
+         addAutomaticKeepAlives: addAutomaticKeepAlives,
+         addRepaintBoundaries: addRepaintBoundaries,
+         addSemanticIndexes: addSemanticIndexes,
+         semanticIndexCallback: (Widget _, int index) {
+           return index.isEven ? index ~/ 2 : null;
+         },
+       ),
+       super(
+         key: key,
+         scrollDirection: scrollDirection,
+         reverse: reverse,
+         controller: controller,
+         primary: primary,
+         physics: physics,
+         shrinkWrap: shrinkWrap,
+         padding: padding,
+         cacheExtent: cacheExtent,
+         semanticChildCount: itemCount,
+         dragStartBehavior: dragStartBehavior,
+         keyboardDismissBehavior: keyboardDismissBehavior,
+         restorationId: restorationId,
+         clipBehavior: clipBehavior,
+       );
+
+  /// Creates a scrollable, linear array of widgets with a custom child model.
+  ///
+  /// For example, a custom child model can control the algorithm used to
+  /// estimate the size of children that are not actually visible.
+  ///
+  /// {@tool snippet}
+  ///
+  /// This [ListView] uses a custom [SliverChildBuilderDelegate] to support child
+  /// reordering.
+  ///
+  /// ```dart
+  /// class MyListView extends StatefulWidget {
+  ///   @override
+  ///   _MyListViewState createState() => _MyListViewState();
+  /// }
+  ///
+  /// class _MyListViewState extends State<MyListView> {
+  ///   List<String> items = <String>['1', '2', '3', '4', '5'];
+  ///
+  ///   void _reverse() {
+  ///     setState(() {
+  ///       items = items.reversed.toList();
+  ///     });
+  ///   }
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     return Scaffold(
+  ///       body: SafeArea(
+  ///         child: ListView.custom(
+  ///           childrenDelegate: SliverChildBuilderDelegate(
+  ///             (BuildContext context, int index) {
+  ///               return KeepAlive(
+  ///                 data: items[index],
+  ///                 key: ValueKey<String>(items[index]),
+  ///               );
+  ///             },
+  ///             childCount: items.length,
+  ///             findChildIndexCallback: (Key key) {
+  ///               final ValueKey valueKey = key;
+  ///               final String data = valueKey.value;
+  ///               return items.indexOf(data);
+  ///             }
+  ///           ),
+  ///         ),
+  ///       ),
+  ///       bottomNavigationBar: BottomAppBar(
+  ///         child: Row(
+  ///           mainAxisAlignment: MainAxisAlignment.center,
+  ///           children: <Widget>[
+  ///             TextButton(
+  ///               onPressed: () => _reverse(),
+  ///               child: Text('Reverse items'),
+  ///             ),
+  ///           ],
+  ///         ),
+  ///       ),
+  ///     );
+  ///   }
+  /// }
+  ///
+  /// class KeepAlive extends StatefulWidget {
+  ///   const KeepAlive({Key key, this.data}) : super(key: key);
+  ///
+  ///   final String data;
+  ///
+  ///   @override
+  ///   _KeepAliveState createState() => _KeepAliveState();
+  /// }
+  ///
+  /// class _KeepAliveState extends State<KeepAlive> with AutomaticKeepAliveClientMixin{
+  ///   @override
+  ///   bool get wantKeepAlive => true;
+  ///
+  ///   @override
+  ///   Widget build(BuildContext context) {
+  ///     super.build(context);
+  ///     return Text(widget.data);
+  ///   }
+  /// }
+  /// ```
+  /// {@end-tool}
+  const ListView.custom({
+    Key? key,
+    Axis scrollDirection = Axis.vertical,
+    bool reverse = false,
+    ScrollController? controller,
+    bool? primary,
+    ScrollPhysics? physics,
+    bool shrinkWrap = false,
+    EdgeInsetsGeometry? padding,
+    this.itemExtent,
+    required this.childrenDelegate,
+    double? cacheExtent,
+    int? semanticChildCount,
+    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
+    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+    String? restorationId,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : assert(childrenDelegate != null),
+       super(
+         key: key,
+         scrollDirection: scrollDirection,
+         reverse: reverse,
+         controller: controller,
+         primary: primary,
+         physics: physics,
+         shrinkWrap: shrinkWrap,
+         padding: padding,
+         cacheExtent: cacheExtent,
+         semanticChildCount: semanticChildCount,
+         dragStartBehavior: dragStartBehavior,
+         keyboardDismissBehavior: keyboardDismissBehavior,
+         restorationId: restorationId,
+         clipBehavior: clipBehavior,
+       );
+
+  /// If non-null, forces the children to have the given extent in the scroll
+  /// direction.
+  ///
+  /// Specifying an [itemExtent] is more efficient than letting the children
+  /// determine their own extent because the scrolling machinery can make use of
+  /// the foreknowledge of the children's extent to save work, for example when
+  /// the scroll position changes drastically.
+  final double? itemExtent;
+
+  /// A delegate that provides the children for the [ListView].
+  ///
+  /// The [ListView.custom] constructor lets you specify this delegate
+  /// explicitly. The [ListView] and [ListView.builder] constructors create a
+  /// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder],
+  /// respectively.
+  final SliverChildDelegate childrenDelegate;
+
+  @override
+  Widget buildChildLayout(BuildContext context) {
+    if (itemExtent != null) {
+      return SliverFixedExtentList(
+        delegate: childrenDelegate,
+        itemExtent: itemExtent!,
+      );
+    }
+    return SliverList(delegate: childrenDelegate);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DoubleProperty('itemExtent', itemExtent, defaultValue: null));
+  }
+
+  // Helper method to compute the actual child count for the separated constructor.
+  static int _computeActualChildCount(int itemCount) {
+    return math.max(0, itemCount * 2 - 1);
+  }
+}
+
+/// A scrollable, 2D array of widgets.
+///
+/// The main axis direction of a grid is the direction in which it scrolls (the
+/// [scrollDirection]).
+///
+/// The most commonly used grid layouts are [GridView.count], which creates a
+/// layout with a fixed number of tiles in the cross axis, and
+/// [GridView.extent], which creates a layout with tiles that have a maximum
+/// cross-axis extent. A custom [SliverGridDelegate] can produce an arbitrary 2D
+/// arrangement of children, including arrangements that are unaligned or
+/// overlapping.
+///
+/// To create a grid with a large (or infinite) number of children, use the
+/// [GridView.builder] constructor with either a
+/// [SliverGridDelegateWithFixedCrossAxisCount] or a
+/// [SliverGridDelegateWithMaxCrossAxisExtent] for the [gridDelegate].
+///
+/// To use a custom [SliverChildDelegate], use [GridView.custom].
+///
+/// To create a linear array of children, use a [ListView].
+///
+/// To control the initial scroll offset of the scroll view, provide a
+/// [controller] with its [ScrollController.initialScrollOffset] property set.
+///
+/// ## Transitioning to [CustomScrollView]
+///
+/// A [GridView] is basically a [CustomScrollView] with a single [SliverGrid] in
+/// its [CustomScrollView.slivers] property.
+///
+/// If [GridView] is no longer sufficient, for example because the scroll view
+/// is to have both a grid and a list, or because the grid is to be combined
+/// with a [SliverAppBar], etc, it is straight-forward to port code from using
+/// [GridView] to using [CustomScrollView] directly.
+///
+/// The [key], [scrollDirection], [reverse], [controller], [primary], [physics],
+/// and [shrinkWrap] properties on [GridView] map directly to the identically
+/// named properties on [CustomScrollView].
+///
+/// The [CustomScrollView.slivers] property should be a list containing just a
+/// [SliverGrid].
+///
+/// The [childrenDelegate] property on [GridView] corresponds to the
+/// [SliverGrid.delegate] property, and the [gridDelegate] property on the
+/// [GridView] corresponds to the [SliverGrid.gridDelegate] property.
+///
+/// The [GridView], [GridView.count], and [GridView.extent]
+/// constructors' `children` arguments correspond to the [childrenDelegate]
+/// being a [SliverChildListDelegate] with that same argument. The
+/// [GridView.builder] constructor's `itemBuilder` and `childCount` arguments
+/// correspond to the [childrenDelegate] being a [SliverChildBuilderDelegate]
+/// with the matching arguments.
+///
+/// The [GridView.count] and [GridView.extent] constructors create
+/// custom grid delegates, and have equivalently named constructors on
+/// [SliverGrid] to ease the transition: [SliverGrid.count] and
+/// [SliverGrid.extent] respectively.
+///
+/// The [padding] property corresponds to having a [SliverPadding] in the
+/// [CustomScrollView.slivers] property instead of the grid itself, and having
+/// the [SliverGrid] instead be a child of the [SliverPadding].
+///
+/// Once code has been ported to use [CustomScrollView], other slivers, such as
+/// [SliverList] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
+/// list.
+///
+/// {@tool snippet}
+/// This example demonstrates how to create a [GridView] with two columns. The
+/// children are spaced apart using the `crossAxisSpacing` and `mainAxisSpacing`
+/// properties.
+///
+/// ![The GridView displays six children with different background colors arranged in two columns](https://flutter.github.io/assets-for-api-docs/assets/widgets/grid_view.png)
+///
+/// ```dart
+/// GridView.count(
+///   primary: false,
+///   padding: const EdgeInsets.all(20),
+///   crossAxisSpacing: 10,
+///   mainAxisSpacing: 10,
+///   crossAxisCount: 2,
+///   children: <Widget>[
+///     Container(
+///       padding: const EdgeInsets.all(8),
+///       child: const Text("He'd have you all unravel at the"),
+///       color: Colors.teal[100],
+///     ),
+///     Container(
+///       padding: const EdgeInsets.all(8),
+///       child: const Text('Heed not the rabble'),
+///       color: Colors.teal[200],
+///     ),
+///     Container(
+///       padding: const EdgeInsets.all(8),
+///       child: const Text('Sound of screams but the'),
+///       color: Colors.teal[300],
+///     ),
+///     Container(
+///       padding: const EdgeInsets.all(8),
+///       child: const Text('Who scream'),
+///       color: Colors.teal[400],
+///     ),
+///     Container(
+///       padding: const EdgeInsets.all(8),
+///       child: const Text('Revolution is coming...'),
+///       color: Colors.teal[500],
+///     ),
+///     Container(
+///       padding: const EdgeInsets.all(8),
+///       child: const Text('Revolution, they...'),
+///       color: Colors.teal[600],
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@tool snippet}
+/// This example shows how to create the same grid as the previous example
+/// using a [CustomScrollView] and a [SliverGrid].
+///
+/// ![The CustomScrollView contains a SliverGrid that displays six children with different background colors arranged in two columns](https://flutter.github.io/assets-for-api-docs/assets/widgets/grid_view_custom_scroll.png)
+///
+/// ```dart
+/// CustomScrollView(
+///   primary: false,
+///   slivers: <Widget>[
+///     SliverPadding(
+///       padding: const EdgeInsets.all(20),
+///       sliver: SliverGrid.count(
+///         crossAxisSpacing: 10,
+///         mainAxisSpacing: 10,
+///         crossAxisCount: 2,
+///         children: <Widget>[
+///           Container(
+///             padding: const EdgeInsets.all(8),
+///             child: const Text("He'd have you all unravel at the"),
+///             color: Colors.green[100],
+///           ),
+///           Container(
+///             padding: const EdgeInsets.all(8),
+///             child: const Text('Heed not the rabble'),
+///             color: Colors.green[200],
+///           ),
+///           Container(
+///             padding: const EdgeInsets.all(8),
+///             child: const Text('Sound of screams but the'),
+///             color: Colors.green[300],
+///           ),
+///           Container(
+///             padding: const EdgeInsets.all(8),
+///             child: const Text('Who scream'),
+///             color: Colors.green[400],
+///           ),
+///           Container(
+///             padding: const EdgeInsets.all(8),
+///             child: const Text('Revolution is coming...'),
+///             color: Colors.green[500],
+///           ),
+///           Container(
+///             padding: const EdgeInsets.all(8),
+///             child: const Text('Revolution, they...'),
+///             color: Colors.green[600],
+///           ),
+///         ],
+///       ),
+///     ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// By default, [GridView] will automatically pad the limits of the
+/// grids's scrollable to avoid partial obstructions indicated by
+/// [MediaQuery]'s padding. To avoid this behavior, override with a
+/// zero [padding] property.
+///
+/// {@tool snippet}
+/// The following example demonstrates how to override the default top padding
+/// using [MediaQuery.removePadding].
+///
+/// ```dart
+/// Widget myWidget(BuildContext context) {
+///   return MediaQuery.removePadding(
+///     context: context,
+///     removeTop: true,
+///     child: GridView.builder(
+///       gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+///         crossAxisCount: 3,
+///       ),
+///       itemCount: 300,
+///       itemBuilder: (BuildContext context, int index) {
+///         return Card(
+///           color: Colors.amber,
+///           child: Center(child: Text('$index')),
+///         );
+///       }
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [SingleChildScrollView], which is a scrollable widget that has a single
+///    child.
+///  * [ListView], which is scrollable, linear list of widgets.
+///  * [PageView], which is a scrolling list of child widgets that are each the
+///    size of the viewport.
+///  * [CustomScrollView], which is a scrollable widget that creates custom
+///    scroll effects using slivers.
+///  * [SliverGridDelegateWithFixedCrossAxisCount], which creates a layout with
+///    a fixed number of tiles in the cross axis.
+///  * [SliverGridDelegateWithMaxCrossAxisExtent], which creates a layout with
+///    tiles that have a maximum cross-axis extent.
+///  * [ScrollNotification] and [NotificationListener], which can be used to watch
+///    the scroll position without using a [ScrollController].
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class GridView extends BoxScrollView {
+  /// Creates a scrollable, 2D array of widgets with a custom
+  /// [SliverGridDelegate].
+  ///
+  /// The [gridDelegate] argument must not be null.
+  ///
+  /// The `addAutomaticKeepAlives` argument corresponds to the
+  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
+  /// `addRepaintBoundaries` argument corresponds to the
+  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
+  /// null.
+  GridView({
+    Key? key,
+    Axis scrollDirection = Axis.vertical,
+    bool reverse = false,
+    ScrollController? controller,
+    bool? primary,
+    ScrollPhysics? physics,
+    bool shrinkWrap = false,
+    EdgeInsetsGeometry? padding,
+    required this.gridDelegate,
+    bool addAutomaticKeepAlives = true,
+    bool addRepaintBoundaries = true,
+    bool addSemanticIndexes = true,
+    double? cacheExtent,
+    List<Widget> children = const <Widget>[],
+    int? semanticChildCount,
+    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
+    Clip clipBehavior = Clip.hardEdge,
+    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+    String? restorationId,
+  }) : assert(gridDelegate != null),
+       childrenDelegate = SliverChildListDelegate(
+         children,
+         addAutomaticKeepAlives: addAutomaticKeepAlives,
+         addRepaintBoundaries: addRepaintBoundaries,
+         addSemanticIndexes: addSemanticIndexes,
+       ),
+       super(
+         key: key,
+         scrollDirection: scrollDirection,
+         reverse: reverse,
+         controller: controller,
+         primary: primary,
+         physics: physics,
+         shrinkWrap: shrinkWrap,
+         padding: padding,
+         cacheExtent: cacheExtent,
+         semanticChildCount: semanticChildCount ?? children.length,
+         dragStartBehavior: dragStartBehavior,
+         keyboardDismissBehavior: keyboardDismissBehavior,
+         restorationId: restorationId,
+         clipBehavior: clipBehavior,
+       );
+
+  /// Creates a scrollable, 2D array of widgets that are created on demand.
+  ///
+  /// This constructor is appropriate for grid views with a large (or infinite)
+  /// number of children because the builder is called only for those children
+  /// that are actually visible.
+  ///
+  /// Providing a non-null `itemCount` improves the ability of the [GridView] to
+  /// estimate the maximum scroll extent.
+  ///
+  /// `itemBuilder` will be called only with indices greater than or equal to
+  /// zero and less than `itemCount`.
+  ///
+  /// The [gridDelegate] argument must not be null.
+  ///
+  /// The `addAutomaticKeepAlives` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
+  /// `addRepaintBoundaries` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. Both must not
+  /// be null.
+  GridView.builder({
+    Key? key,
+    Axis scrollDirection = Axis.vertical,
+    bool reverse = false,
+    ScrollController? controller,
+    bool? primary,
+    ScrollPhysics? physics,
+    bool shrinkWrap = false,
+    EdgeInsetsGeometry? padding,
+    required this.gridDelegate,
+    required IndexedWidgetBuilder itemBuilder,
+    int? itemCount,
+    bool addAutomaticKeepAlives = true,
+    bool addRepaintBoundaries = true,
+    bool addSemanticIndexes = true,
+    double? cacheExtent,
+    int? semanticChildCount,
+    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
+    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+    String? restorationId,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : assert(gridDelegate != null),
+       childrenDelegate = SliverChildBuilderDelegate(
+         itemBuilder,
+         childCount: itemCount,
+         addAutomaticKeepAlives: addAutomaticKeepAlives,
+         addRepaintBoundaries: addRepaintBoundaries,
+         addSemanticIndexes: addSemanticIndexes,
+       ),
+       super(
+         key: key,
+         scrollDirection: scrollDirection,
+         reverse: reverse,
+         controller: controller,
+         primary: primary,
+         physics: physics,
+         shrinkWrap: shrinkWrap,
+         padding: padding,
+         cacheExtent: cacheExtent,
+         semanticChildCount: semanticChildCount ?? itemCount,
+         dragStartBehavior: dragStartBehavior,
+         keyboardDismissBehavior: keyboardDismissBehavior,
+         restorationId: restorationId,
+         clipBehavior: clipBehavior,
+       );
+
+  /// Creates a scrollable, 2D array of widgets with both a custom
+  /// [SliverGridDelegate] and a custom [SliverChildDelegate].
+  ///
+  /// To use an [IndexedWidgetBuilder] callback to build children, either use
+  /// a [SliverChildBuilderDelegate] or use the [GridView.builder] constructor.
+  ///
+  /// The [gridDelegate] and [childrenDelegate] arguments must not be null.
+  const GridView.custom({
+    Key? key,
+    Axis scrollDirection = Axis.vertical,
+    bool reverse = false,
+    ScrollController? controller,
+    bool? primary,
+    ScrollPhysics? physics,
+    bool shrinkWrap = false,
+    EdgeInsetsGeometry? padding,
+    required this.gridDelegate,
+    required this.childrenDelegate,
+    double? cacheExtent,
+    int? semanticChildCount,
+    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
+    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+    String? restorationId,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : assert(gridDelegate != null),
+       assert(childrenDelegate != null),
+       super(
+         key: key,
+         scrollDirection: scrollDirection,
+         reverse: reverse,
+         controller: controller,
+         primary: primary,
+         physics: physics,
+         shrinkWrap: shrinkWrap,
+         padding: padding,
+         cacheExtent: cacheExtent,
+         semanticChildCount: semanticChildCount,
+         dragStartBehavior: dragStartBehavior,
+         keyboardDismissBehavior: keyboardDismissBehavior,
+         restorationId: restorationId,
+         clipBehavior: clipBehavior,
+       );
+
+  /// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
+  /// the cross axis.
+  ///
+  /// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate].
+  ///
+  /// The `addAutomaticKeepAlives` argument corresponds to the
+  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
+  /// `addRepaintBoundaries` argument corresponds to the
+  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
+  /// null.
+  ///
+  /// See also:
+  ///
+  ///  * [SliverGrid.count], the equivalent constructor for [SliverGrid].
+  GridView.count({
+    Key? key,
+    Axis scrollDirection = Axis.vertical,
+    bool reverse = false,
+    ScrollController? controller,
+    bool? primary,
+    ScrollPhysics? physics,
+    bool shrinkWrap = false,
+    EdgeInsetsGeometry? padding,
+    required int crossAxisCount,
+    double mainAxisSpacing = 0.0,
+    double crossAxisSpacing = 0.0,
+    double childAspectRatio = 1.0,
+    bool addAutomaticKeepAlives = true,
+    bool addRepaintBoundaries = true,
+    bool addSemanticIndexes = true,
+    double? cacheExtent,
+    List<Widget> children = const <Widget>[],
+    int? semanticChildCount,
+    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
+    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+    String? restorationId,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
+         crossAxisCount: crossAxisCount,
+         mainAxisSpacing: mainAxisSpacing,
+         crossAxisSpacing: crossAxisSpacing,
+         childAspectRatio: childAspectRatio,
+       ),
+       childrenDelegate = SliverChildListDelegate(
+         children,
+         addAutomaticKeepAlives: addAutomaticKeepAlives,
+         addRepaintBoundaries: addRepaintBoundaries,
+         addSemanticIndexes: addSemanticIndexes,
+       ),
+       super(
+         key: key,
+         scrollDirection: scrollDirection,
+         reverse: reverse,
+         controller: controller,
+         primary: primary,
+         physics: physics,
+         shrinkWrap: shrinkWrap,
+         padding: padding,
+         cacheExtent: cacheExtent,
+         semanticChildCount: semanticChildCount ?? children.length,
+         dragStartBehavior: dragStartBehavior,
+         keyboardDismissBehavior: keyboardDismissBehavior,
+         restorationId: restorationId,
+         clipBehavior: clipBehavior,
+       );
+
+  /// Creates a scrollable, 2D array of widgets with tiles that each have a
+  /// maximum cross-axis extent.
+  ///
+  /// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate].
+  ///
+  /// The `addAutomaticKeepAlives` argument corresponds to the
+  /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The
+  /// `addRepaintBoundaries` argument corresponds to the
+  /// [SliverChildListDelegate.addRepaintBoundaries] property. Both must not be
+  /// null.
+  ///
+  /// See also:
+  ///
+  ///  * [SliverGrid.extent], the equivalent constructor for [SliverGrid].
+  GridView.extent({
+    Key? key,
+    Axis scrollDirection = Axis.vertical,
+    bool reverse = false,
+    ScrollController? controller,
+    bool? primary,
+    ScrollPhysics? physics,
+    bool shrinkWrap = false,
+    EdgeInsetsGeometry? padding,
+    required double maxCrossAxisExtent,
+    double mainAxisSpacing = 0.0,
+    double crossAxisSpacing = 0.0,
+    double childAspectRatio = 1.0,
+    bool addAutomaticKeepAlives = true,
+    bool addRepaintBoundaries = true,
+    bool addSemanticIndexes = true,
+    double? cacheExtent,
+    List<Widget> children = const <Widget>[],
+    int? semanticChildCount,
+    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
+    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
+    String? restorationId,
+    Clip clipBehavior = Clip.hardEdge,
+  }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
+         maxCrossAxisExtent: maxCrossAxisExtent,
+         mainAxisSpacing: mainAxisSpacing,
+         crossAxisSpacing: crossAxisSpacing,
+         childAspectRatio: childAspectRatio,
+       ),
+       childrenDelegate = SliverChildListDelegate(
+         children,
+         addAutomaticKeepAlives: addAutomaticKeepAlives,
+         addRepaintBoundaries: addRepaintBoundaries,
+         addSemanticIndexes: addSemanticIndexes,
+       ),
+       super(
+         key: key,
+         scrollDirection: scrollDirection,
+         reverse: reverse,
+         controller: controller,
+         primary: primary,
+         physics: physics,
+         shrinkWrap: shrinkWrap,
+         padding: padding,
+         cacheExtent: cacheExtent,
+         semanticChildCount: semanticChildCount ?? children.length,
+         dragStartBehavior: dragStartBehavior,
+         keyboardDismissBehavior: keyboardDismissBehavior,
+         restorationId: restorationId,
+         clipBehavior: clipBehavior,
+       );
+
+  /// A delegate that controls the layout of the children within the [GridView].
+  ///
+  /// The [GridView], [GridView.builder], and [GridView.custom] constructors let you specify this
+  /// delegate explicitly. The other constructors create a [gridDelegate]
+  /// implicitly.
+  final SliverGridDelegate gridDelegate;
+
+  /// A delegate that provides the children for the [GridView].
+  ///
+  /// The [GridView.custom] constructor lets you specify this delegate
+  /// explicitly. The other constructors create a [childrenDelegate] that wraps
+  /// the given child list.
+  final SliverChildDelegate childrenDelegate;
+
+  @override
+  Widget buildChildLayout(BuildContext context) {
+    return SliverGrid(
+      delegate: childrenDelegate,
+      gridDelegate: gridDelegate,
+    );
+  }
+}
diff --git a/lib/src/widgets/scrollable.dart b/lib/src/widgets/scrollable.dart
new file mode 100644
index 0000000..08495ba
--- /dev/null
+++ b/lib/src/widgets/scrollable.dart
@@ -0,0 +1,1131 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:math' as math;
+import 'package:flute/ui.dart';
+
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/painting.dart';
+import 'package:flute/services.dart';
+
+import 'actions.dart';
+import 'basic.dart';
+import 'focus_manager.dart';
+import 'framework.dart';
+import 'gesture_detector.dart';
+import 'notification_listener.dart';
+import 'primary_scroll_controller.dart';
+import 'restoration.dart';
+import 'restoration_properties.dart';
+import 'scroll_configuration.dart';
+import 'scroll_context.dart';
+import 'scroll_controller.dart';
+import 'scroll_metrics.dart';
+import 'scroll_physics.dart';
+import 'scroll_position.dart';
+import 'scroll_position_with_single_context.dart';
+import 'ticker_provider.dart';
+import 'viewport.dart';
+
+export 'package:flute/physics.dart' show Tolerance;
+
+/// Signature used by [Scrollable] to build the viewport through which the
+/// scrollable content is displayed.
+typedef ViewportBuilder = Widget Function(BuildContext context, ViewportOffset position);
+
+/// A widget that scrolls.
+///
+/// [Scrollable] implements the interaction model for a scrollable widget,
+/// including gesture recognition, but does not have an opinion about how the
+/// viewport, which actually displays the children, is constructed.
+///
+/// It's rare to construct a [Scrollable] directly. Instead, consider [ListView]
+/// or [GridView], which combine scrolling, viewporting, and a layout model. To
+/// combine layout models (or to use a custom layout mode), consider using
+/// [CustomScrollView].
+///
+/// The static [Scrollable.of] and [Scrollable.ensureVisible] functions are
+/// often used to interact with the [Scrollable] widget inside a [ListView] or
+/// a [GridView].
+///
+/// To further customize scrolling behavior with a [Scrollable]:
+///
+/// 1. You can provide a [viewportBuilder] to customize the child model. For
+///    example, [SingleChildScrollView] uses a viewport that displays a single
+///    box child whereas [CustomScrollView] uses a [Viewport] or a
+///    [ShrinkWrappingViewport], both of which display a list of slivers.
+///
+/// 2. You can provide a custom [ScrollController] that creates a custom
+///    [ScrollPosition] subclass. For example, [PageView] uses a
+///    [PageController], which creates a page-oriented scroll position subclass
+///    that keeps the same page visible when the [Scrollable] resizes.
+///
+/// See also:
+///
+///  * [ListView], which is a commonly used [ScrollView] that displays a
+///    scrolling, linear list of child widgets.
+///  * [PageView], which is a scrolling list of child widgets that are each the
+///    size of the viewport.
+///  * [GridView], which is a [ScrollView] that displays a scrolling, 2D array
+///    of child widgets.
+///  * [CustomScrollView], which is a [ScrollView] that creates custom scroll
+///    effects using slivers.
+///  * [SingleChildScrollView], which is a scrollable widget that has a single
+///    child.
+///  * [ScrollNotification] and [NotificationListener], which can be used to watch
+///    the scroll position without using a [ScrollController].
+class Scrollable extends StatefulWidget {
+  /// Creates a widget that scrolls.
+  ///
+  /// The [axisDirection] and [viewportBuilder] arguments must not be null.
+  const Scrollable({
+    Key? key,
+    this.axisDirection = AxisDirection.down,
+    this.controller,
+    this.physics,
+    required this.viewportBuilder,
+    this.incrementCalculator,
+    this.excludeFromSemantics = false,
+    this.semanticChildCount,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.restorationId,
+  }) : assert(axisDirection != null),
+       assert(dragStartBehavior != null),
+       assert(viewportBuilder != null),
+       assert(excludeFromSemantics != null),
+       assert(semanticChildCount == null || semanticChildCount >= 0),
+       super (key: key);
+
+  /// The direction in which this widget scrolls.
+  ///
+  /// For example, if the [axisDirection] is [AxisDirection.down], increasing
+  /// the scroll position will cause content below the bottom of the viewport to
+  /// become visible through the viewport. Similarly, if [axisDirection] is
+  /// [AxisDirection.right], increasing the scroll position will cause content
+  /// beyond the right edge of the viewport to become visible through the
+  /// viewport.
+  ///
+  /// Defaults to [AxisDirection.down].
+  final AxisDirection axisDirection;
+
+  /// An object that can be used to control the position to which this widget is
+  /// scrolled.
+  ///
+  /// A [ScrollController] serves several purposes. It can be used to control
+  /// the initial scroll position (see [ScrollController.initialScrollOffset]).
+  /// It can be used to control whether the scroll view should automatically
+  /// save and restore its scroll position in the [PageStorage] (see
+  /// [ScrollController.keepScrollOffset]). It can be used to read the current
+  /// scroll position (see [ScrollController.offset]), or change it (see
+  /// [ScrollController.animateTo]).
+  ///
+  /// See also:
+  ///
+  ///  * [ensureVisible], which animates the scroll position to reveal a given
+  ///    [BuildContext].
+  final ScrollController? controller;
+
+  /// How the widgets should respond to user input.
+  ///
+  /// For example, determines how the widget continues to animate after the
+  /// user stops dragging the scroll view.
+  ///
+  /// Defaults to matching platform conventions via the physics provided from
+  /// the ambient [ScrollConfiguration].
+  ///
+  /// The physics can be changed dynamically, but new physics will only take
+  /// effect if the _class_ of the provided object changes. Merely constructing
+  /// a new instance with a different configuration is insufficient to cause the
+  /// physics to be reapplied. (This is because the final object used is
+  /// generated dynamically, which can be relatively expensive, and it would be
+  /// inefficient to speculatively create this object each frame to see if the
+  /// physics should be updated.)
+  ///
+  /// See also:
+  ///
+  ///  * [AlwaysScrollableScrollPhysics], which can be used to indicate that the
+  ///    scrollable should react to scroll requests (and possible overscroll)
+  ///    even if the scrollable's contents fit without scrolling being necessary.
+  final ScrollPhysics? physics;
+
+  /// Builds the viewport through which the scrollable content is displayed.
+  ///
+  /// A typical viewport uses the given [ViewportOffset] to determine which part
+  /// of its content is actually visible through the viewport.
+  ///
+  /// See also:
+  ///
+  ///  * [Viewport], which is a viewport that displays a list of slivers.
+  ///  * [ShrinkWrappingViewport], which is a viewport that displays a list of
+  ///    slivers and sizes itself based on the size of the slivers.
+  final ViewportBuilder viewportBuilder;
+
+  /// An optional function that will be called to calculate the distance to
+  /// scroll when the scrollable is asked to scroll via the keyboard using a
+  /// [ScrollAction].
+  ///
+  /// If not supplied, the [Scrollable] will scroll a default amount when a
+  /// keyboard navigation key is pressed (e.g. pageUp/pageDown, control-upArrow,
+  /// etc.), or otherwise invoked by a [ScrollAction].
+  ///
+  /// If [incrementCalculator] is null, the default for
+  /// [ScrollIncrementType.page] is 80% of the size of the scroll window, and
+  /// for [ScrollIncrementType.line], 50 logical pixels.
+  final ScrollIncrementCalculator? incrementCalculator;
+
+  /// Whether the scroll actions introduced by this [Scrollable] are exposed
+  /// in the semantics tree.
+  ///
+  /// Text fields with an overflow are usually scrollable to make sure that the
+  /// user can get to the beginning/end of the entered text. However, these
+  /// scrolling actions are generally not exposed to the semantics layer.
+  ///
+  /// See also:
+  ///
+  ///  * [GestureDetector.excludeFromSemantics], which is used to accomplish the
+  ///    exclusion.
+  final bool excludeFromSemantics;
+
+  /// The number of children that will contribute semantic information.
+  ///
+  /// The value will be null if the number of children is unknown or unbounded.
+  ///
+  /// Some subtypes of [ScrollView] can infer this value automatically. For
+  /// example [ListView] will use the number of widgets in the child list,
+  /// while the [new ListView.separated] constructor will use half that amount.
+  ///
+  /// For [CustomScrollView] and other types which do not receive a builder
+  /// or list of widgets, the child count must be explicitly provided.
+  ///
+  /// See also:
+  ///
+  ///  * [CustomScrollView], for an explanation of scroll semantics.
+  ///  * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property.
+  final int? semanticChildCount;
+
+  // TODO(jslavitz): Set the DragStartBehavior default to be start across all widgets.
+  /// {@template flutter.widgets.scrollable.dragStartBehavior}
+  /// Determines the way that drag start behavior is handled.
+  ///
+  /// If set to [DragStartBehavior.start], scrolling drag behavior will
+  /// begin upon the detection of a drag gesture. If set to
+  /// [DragStartBehavior.down] it will begin when a down event is first detected.
+  ///
+  /// In general, setting this to [DragStartBehavior.start] will make drag
+  /// animation smoother and setting it to [DragStartBehavior.down] will make
+  /// drag behavior feel slightly more reactive.
+  ///
+  /// By default, the drag start behavior is [DragStartBehavior.start].
+  ///
+  /// See also:
+  ///
+  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for
+  ///    the different behaviors.
+  ///
+  /// {@endtemplate}
+  final DragStartBehavior dragStartBehavior;
+
+  /// {@template flutter.widgets.scrollable.restorationId}
+  /// Restoration ID to save and restore the scroll offset of the scrollable.
+  ///
+  /// If a restoration id is provided, the scrollable will persist its current
+  /// scroll offset and restore it during state restoration.
+  ///
+  /// The scroll offset is persisted in a [RestorationBucket] claimed from
+  /// the surrounding [RestorationScope] using the provided restoration ID.
+  ///
+  /// See also:
+  ///
+  ///  * [RestorationManager], which explains how state restoration works in
+  ///    Flutter.
+  /// {@endtemplate}
+  final String? restorationId;
+
+  /// The axis along which the scroll view scrolls.
+  ///
+  /// Determined by the [axisDirection].
+  Axis get axis => axisDirectionToAxis(axisDirection);
+
+  @override
+  ScrollableState createState() => ScrollableState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<AxisDirection>('axisDirection', axisDirection));
+    properties.add(DiagnosticsProperty<ScrollPhysics>('physics', physics));
+    properties.add(StringProperty('restorationId', restorationId));
+  }
+
+  /// The state from the closest instance of this class that encloses the given context.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// ScrollableState scrollable = Scrollable.of(context);
+  /// ```
+  ///
+  /// Calling this method will create a dependency on the closest [Scrollable]
+  /// in the [context], if there is one.
+  static ScrollableState? of(BuildContext context) {
+    final _ScrollableScope? widget = context.dependOnInheritedWidgetOfExactType<_ScrollableScope>();
+    return widget?.scrollable;
+  }
+
+  /// Provides a heuristic to determine if expensive frame-bound tasks should be
+  /// deferred for the [context] at a specific point in time.
+  ///
+  /// Calling this method does _not_ create a dependency on any other widget.
+  /// This also means that the value returned is only good for the point in time
+  /// when it is called, and callers will not get updated if the value changes.
+  ///
+  /// The heuristic used is determined by the [physics] of this [Scrollable]
+  /// via [ScrollPhysics.recommendDeferredLoading]. That method is called with
+  /// the current [ScrollPosition.activity]'s [ScrollActivity.velocity].
+  ///
+  /// If there is no [Scrollable] in the widget tree above the [context], this
+  /// method returns false.
+  static bool recommendDeferredLoadingForContext(BuildContext context) {
+    final _ScrollableScope? widget = context.getElementForInheritedWidgetOfExactType<_ScrollableScope>()?.widget as _ScrollableScope?;
+    if (widget == null) {
+      return false;
+    }
+    return widget.position.recommendDeferredLoading(context);
+  }
+
+  /// Scrolls the scrollables that enclose the given context so as to make the
+  /// given context visible.
+  static Future<void> ensureVisible(
+    BuildContext context, {
+    double alignment = 0.0,
+    Duration duration = Duration.zero,
+    Curve curve = Curves.ease,
+    ScrollPositionAlignmentPolicy alignmentPolicy = ScrollPositionAlignmentPolicy.explicit,
+  }) {
+    final List<Future<void>> futures = <Future<void>>[];
+
+    // The `targetRenderObject` is used to record the first target renderObject.
+    // If there are multiple scrollable widgets nested, we should let
+    // the `targetRenderObject` as visible as possible to improve the user experience.
+    // Otherwise, let the outer renderObject as visible as possible maybe cause
+    // the `targetRenderObject` invisible.
+    // Also see https://github.com/flutter/flutter/issues/65100
+    RenderObject? targetRenderObject;
+    ScrollableState? scrollable = Scrollable.of(context);
+    while (scrollable != null) {
+      futures.add(scrollable.position.ensureVisible(
+        context.findRenderObject()!,
+        alignment: alignment,
+        duration: duration,
+        curve: curve,
+        alignmentPolicy: alignmentPolicy,
+        targetRenderObject: targetRenderObject,
+      ));
+
+      targetRenderObject = targetRenderObject ?? context.findRenderObject();
+      context = scrollable.context;
+      scrollable = Scrollable.of(context);
+    }
+
+    if (futures.isEmpty || duration == Duration.zero)
+      return Future<void>.value();
+    if (futures.length == 1)
+      return futures.single;
+    return Future.wait<void>(futures).then<void>((List<void> _) => null);
+  }
+}
+
+// Enable Scrollable.of() to work as if ScrollableState was an inherited widget.
+// ScrollableState.build() always rebuilds its _ScrollableScope.
+class _ScrollableScope extends InheritedWidget {
+  const _ScrollableScope({
+    Key? key,
+    required this.scrollable,
+    required this.position,
+    required Widget child,
+  }) : assert(scrollable != null),
+       assert(child != null),
+       super(key: key, child: child);
+
+  final ScrollableState scrollable;
+  final ScrollPosition position;
+
+  @override
+  bool updateShouldNotify(_ScrollableScope old) {
+    return position != old.position;
+  }
+}
+
+/// State object for a [Scrollable] widget.
+///
+/// To manipulate a [Scrollable] widget's scroll position, use the object
+/// obtained from the [position] property.
+///
+/// To be informed of when a [Scrollable] widget is scrolling, use a
+/// [NotificationListener] to listen for [ScrollNotification] notifications.
+///
+/// This class is not intended to be subclassed. To specialize the behavior of a
+/// [Scrollable], provide it with a [ScrollPhysics].
+class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, RestorationMixin
+    implements ScrollContext {
+  /// The manager for this [Scrollable] widget's viewport position.
+  ///
+  /// To control what kind of [ScrollPosition] is created for a [Scrollable],
+  /// provide it with custom [ScrollController] that creates the appropriate
+  /// [ScrollPosition] in its [ScrollController.createScrollPosition] method.
+  ScrollPosition get position => _position!;
+  ScrollPosition? _position;
+
+  final _RestorableScrollOffset _persistedScrollOffset = _RestorableScrollOffset();
+
+  @override
+  AxisDirection get axisDirection => widget.axisDirection;
+
+  late ScrollBehavior _configuration;
+  ScrollPhysics? _physics;
+
+  // Only call this from places that will definitely trigger a rebuild.
+  void _updatePosition() {
+    _configuration = ScrollConfiguration.of(context);
+    _physics = _configuration.getScrollPhysics(context);
+    if (widget.physics != null)
+      _physics = widget.physics!.applyTo(_physics);
+    final ScrollController? controller = widget.controller;
+    final ScrollPosition? oldPosition = _position;
+    if (oldPosition != null) {
+      controller?.detach(oldPosition);
+      // It's important that we not dispose the old position until after the
+      // viewport has had a chance to unregister its listeners from the old
+      // position. So, schedule a microtask to do it.
+      scheduleMicrotask(oldPosition.dispose);
+    }
+
+    _position = controller?.createScrollPosition(_physics!, this, oldPosition)
+      ?? ScrollPositionWithSingleContext(physics: _physics!, context: this, oldPosition: oldPosition);
+    assert(_position != null);
+    controller?.attach(position);
+  }
+
+  @override
+  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
+    registerForRestoration(_persistedScrollOffset, 'offset');
+    assert(_position != null);
+    if (_persistedScrollOffset.value != null) {
+      position.restoreOffset(_persistedScrollOffset.value!, initialRestore: initialRestore);
+    }
+  }
+
+  @override
+  void saveOffset(double offset) {
+    assert(debugIsSerializableForRestoration(offset));
+    _persistedScrollOffset.value = offset;
+    // [saveOffset] is called after a scrolling ends and it is usually not
+    // followed by a frame. Therefore, manually flush restoration data.
+    ServicesBinding.instance!.restorationManager.flushData();
+  }
+
+  @override
+  void didChangeDependencies() {
+    _updatePosition();
+    super.didChangeDependencies();
+  }
+
+  bool _shouldUpdatePosition(Scrollable oldWidget) {
+    ScrollPhysics? newPhysics = widget.physics;
+    ScrollPhysics? oldPhysics = oldWidget.physics;
+    do {
+      if (newPhysics?.runtimeType != oldPhysics?.runtimeType)
+        return true;
+      newPhysics = newPhysics?.parent;
+      oldPhysics = oldPhysics?.parent;
+    } while (newPhysics != null || oldPhysics != null);
+
+    return widget.controller?.runtimeType != oldWidget.controller?.runtimeType;
+  }
+
+  @override
+  void didUpdateWidget(Scrollable oldWidget) {
+    super.didUpdateWidget(oldWidget);
+
+    if (widget.controller != oldWidget.controller) {
+      oldWidget.controller?.detach(position);
+      widget.controller?.attach(position);
+    }
+
+    if (_shouldUpdatePosition(oldWidget))
+      _updatePosition();
+  }
+
+  @override
+  void dispose() {
+    widget.controller?.detach(position);
+    position.dispose();
+    _persistedScrollOffset.dispose();
+    super.dispose();
+  }
+
+
+  // SEMANTICS
+
+  final GlobalKey _scrollSemanticsKey = GlobalKey();
+
+  @override
+  @protected
+  void setSemanticsActions(Set<SemanticsAction> actions) {
+    if (_gestureDetectorKey.currentState != null)
+      _gestureDetectorKey.currentState!.replaceSemanticsActions(actions);
+  }
+
+
+  // GESTURE RECOGNITION AND POINTER IGNORING
+
+  final GlobalKey<RawGestureDetectorState> _gestureDetectorKey = GlobalKey<RawGestureDetectorState>();
+  final GlobalKey _ignorePointerKey = GlobalKey();
+
+  // This field is set during layout, and then reused until the next time it is set.
+  Map<Type, GestureRecognizerFactory> _gestureRecognizers = const <Type, GestureRecognizerFactory>{};
+  bool _shouldIgnorePointer = false;
+
+  bool? _lastCanDrag;
+  Axis? _lastAxisDirection;
+
+  @override
+  @protected
+  void setCanDrag(bool canDrag) {
+    if (canDrag == _lastCanDrag && (!canDrag || widget.axis == _lastAxisDirection))
+      return;
+    if (!canDrag) {
+      _gestureRecognizers = const <Type, GestureRecognizerFactory>{};
+      // Cancel the active hold/drag (if any) because the gesture recognizers
+      // will soon be disposed by our RawGestureDetector, and we won't be
+      // receiving pointer up events to cancel the hold/drag.
+      _handleDragCancel();
+    } else {
+      switch (widget.axis) {
+        case Axis.vertical:
+          _gestureRecognizers = <Type, GestureRecognizerFactory>{
+            VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
+              () => VerticalDragGestureRecognizer(),
+              (VerticalDragGestureRecognizer instance) {
+                instance
+                  ..onDown = _handleDragDown
+                  ..onStart = _handleDragStart
+                  ..onUpdate = _handleDragUpdate
+                  ..onEnd = _handleDragEnd
+                  ..onCancel = _handleDragCancel
+                  ..minFlingDistance = _physics?.minFlingDistance
+                  ..minFlingVelocity = _physics?.minFlingVelocity
+                  ..maxFlingVelocity = _physics?.maxFlingVelocity
+                  ..velocityTrackerBuilder = _configuration.velocityTrackerBuilder(context)
+                  ..dragStartBehavior = widget.dragStartBehavior;
+              },
+            ),
+          };
+          break;
+        case Axis.horizontal:
+          _gestureRecognizers = <Type, GestureRecognizerFactory>{
+            HorizontalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
+              () => HorizontalDragGestureRecognizer(),
+              (HorizontalDragGestureRecognizer instance) {
+                instance
+                  ..onDown = _handleDragDown
+                  ..onStart = _handleDragStart
+                  ..onUpdate = _handleDragUpdate
+                  ..onEnd = _handleDragEnd
+                  ..onCancel = _handleDragCancel
+                  ..minFlingDistance = _physics?.minFlingDistance
+                  ..minFlingVelocity = _physics?.minFlingVelocity
+                  ..maxFlingVelocity = _physics?.maxFlingVelocity
+                  ..velocityTrackerBuilder = _configuration.velocityTrackerBuilder(context)
+                  ..dragStartBehavior = widget.dragStartBehavior;
+              },
+            ),
+          };
+          break;
+      }
+    }
+    _lastCanDrag = canDrag;
+    _lastAxisDirection = widget.axis;
+    if (_gestureDetectorKey.currentState != null)
+      _gestureDetectorKey.currentState!.replaceGestureRecognizers(_gestureRecognizers);
+  }
+
+  @override
+  TickerProvider get vsync => this;
+
+  @override
+  @protected
+  void setIgnorePointer(bool value) {
+    if (_shouldIgnorePointer == value)
+      return;
+    _shouldIgnorePointer = value;
+    if (_ignorePointerKey.currentContext != null) {
+      final RenderIgnorePointer renderBox = _ignorePointerKey.currentContext!.findRenderObject()! as RenderIgnorePointer;
+      renderBox.ignoring = _shouldIgnorePointer;
+    }
+  }
+
+  @override
+  BuildContext? get notificationContext => _gestureDetectorKey.currentContext;
+
+  @override
+  BuildContext get storageContext => context;
+
+  // TOUCH HANDLERS
+
+  Drag? _drag;
+  ScrollHoldController? _hold;
+
+  void _handleDragDown(DragDownDetails details) {
+    assert(_drag == null);
+    assert(_hold == null);
+    _hold = position.hold(_disposeHold);
+  }
+
+  void _handleDragStart(DragStartDetails details) {
+    // It's possible for _hold to become null between _handleDragDown and
+    // _handleDragStart, for example if some user code calls jumpTo or otherwise
+    // triggers a new activity to begin.
+    assert(_drag == null);
+    _drag = position.drag(details, _disposeDrag);
+    assert(_drag != null);
+    assert(_hold == null);
+  }
+
+  void _handleDragUpdate(DragUpdateDetails details) {
+    // _drag might be null if the drag activity ended and called _disposeDrag.
+    assert(_hold == null || _drag == null);
+    _drag?.update(details);
+  }
+
+  void _handleDragEnd(DragEndDetails details) {
+    // _drag might be null if the drag activity ended and called _disposeDrag.
+    assert(_hold == null || _drag == null);
+    _drag?.end(details);
+    assert(_drag == null);
+  }
+
+  void _handleDragCancel() {
+    // _hold might be null if the drag started.
+    // _drag might be null if the drag activity ended and called _disposeDrag.
+    assert(_hold == null || _drag == null);
+    _hold?.cancel();
+    _drag?.cancel();
+    assert(_hold == null);
+    assert(_drag == null);
+  }
+
+  void _disposeHold() {
+    _hold = null;
+  }
+
+  void _disposeDrag() {
+    _drag = null;
+  }
+
+  // SCROLL WHEEL
+
+  // Returns the offset that should result from applying [event] to the current
+  // position, taking min/max scroll extent into account.
+  double _targetScrollOffsetForPointerScroll(PointerScrollEvent event) {
+    final double delta = _pointerSignalEventDelta(event);
+    return math.min(math.max(position.pixels + delta, position.minScrollExtent),
+      position.maxScrollExtent);
+  }
+
+  // Returns the delta that should result from applying [event] with axis and
+  // direction taken into account.
+  double _pointerSignalEventDelta(PointerScrollEvent event) {
+    double delta = widget.axis == Axis.horizontal
+      ? event.scrollDelta.dx
+      : event.scrollDelta.dy;
+
+    if (axisDirectionIsReversed(widget.axisDirection)) {
+      delta *= -1;
+    }
+    return delta;
+  }
+
+  void _receivedPointerSignal(PointerSignalEvent event) {
+    if (event is PointerScrollEvent && _position != null) {
+      if (_physics != null && !_physics!.shouldAcceptUserOffset(position)) {
+        return;
+      }
+      final double targetScrollOffset = _targetScrollOffsetForPointerScroll(event);
+      // Only express interest in the event if it would actually result in a scroll.
+      if (targetScrollOffset != position.pixels) {
+        GestureBinding.instance!.pointerSignalResolver.register(event, _handlePointerScroll);
+      }
+    }
+  }
+
+  void _handlePointerScroll(PointerEvent event) {
+    assert(event is PointerScrollEvent);
+    final double targetScrollOffset = _targetScrollOffsetForPointerScroll(event as PointerScrollEvent);
+    if (targetScrollOffset != position.pixels) {
+      position.pointerScroll(_pointerSignalEventDelta(event));
+    }
+  }
+
+  // DESCRIPTION
+
+  @override
+  Widget build(BuildContext context) {
+    assert(_position != null);
+    // _ScrollableScope must be placed above the BuildContext returned by notificationContext
+    // so that we can get this ScrollableState by doing the following:
+    //
+    // ScrollNotification notification;
+    // Scrollable.of(notification.context)
+    //
+    // Since notificationContext is pointing to _gestureDetectorKey.context, _ScrollableScope
+    // must be placed above the widget using it: RawGestureDetector
+    Widget result = _ScrollableScope(
+      scrollable: this,
+      position: position,
+      // TODO(ianh): Having all these global keys is sad.
+      child: Listener(
+        onPointerSignal: _receivedPointerSignal,
+        child: RawGestureDetector(
+          key: _gestureDetectorKey,
+          gestures: _gestureRecognizers,
+          behavior: HitTestBehavior.opaque,
+          excludeFromSemantics: widget.excludeFromSemantics,
+          child: Semantics(
+            explicitChildNodes: !widget.excludeFromSemantics,
+            child: IgnorePointer(
+              key: _ignorePointerKey,
+              ignoring: _shouldIgnorePointer,
+              ignoringSemantics: false,
+              child: widget.viewportBuilder(context, position),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    if (!widget.excludeFromSemantics) {
+      result = _ScrollSemantics(
+        key: _scrollSemanticsKey,
+        child: result,
+        position: position,
+        allowImplicitScrolling: _physics!.allowImplicitScrolling,
+        semanticChildCount: widget.semanticChildCount,
+      );
+    }
+
+    return _configuration.buildViewportChrome(context, result, widget.axisDirection);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ScrollPosition>('position', position));
+    properties.add(DiagnosticsProperty<ScrollPhysics>('effective physics', _physics));
+  }
+
+  @override
+  String? get restorationId => widget.restorationId;
+}
+
+/// With [_ScrollSemantics] certain child [SemanticsNode]s can be
+/// excluded from the scrollable area for semantics purposes.
+///
+/// Nodes, that are to be excluded, have to be tagged with
+/// [RenderViewport.excludeFromScrolling] and the [RenderAbstractViewport] in
+/// use has to add the [RenderViewport.useTwoPaneSemantics] tag to its
+/// [SemanticsConfiguration] by overriding
+/// [RenderObject.describeSemanticsConfiguration].
+///
+/// If the tag [RenderViewport.useTwoPaneSemantics] is present on the viewport,
+/// two semantics nodes will be used to represent the [Scrollable]: The outer
+/// node will contain all children, that are excluded from scrolling. The inner
+/// node, which is annotated with the scrolling actions, will house the
+/// scrollable children.
+class _ScrollSemantics extends SingleChildRenderObjectWidget {
+  const _ScrollSemantics({
+    Key? key,
+    required this.position,
+    required this.allowImplicitScrolling,
+    required this.semanticChildCount,
+    Widget? child,
+  }) : assert(position != null),
+       assert(semanticChildCount == null || semanticChildCount >= 0),
+       super(key: key, child: child);
+
+  final ScrollPosition position;
+  final bool allowImplicitScrolling;
+  final int? semanticChildCount;
+
+  @override
+  _RenderScrollSemantics createRenderObject(BuildContext context) {
+    return _RenderScrollSemantics(
+      position: position,
+      allowImplicitScrolling: allowImplicitScrolling,
+      semanticChildCount: semanticChildCount,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderScrollSemantics renderObject) {
+    renderObject
+      ..allowImplicitScrolling = allowImplicitScrolling
+      ..position = position
+      ..semanticChildCount = semanticChildCount;
+  }
+}
+
+class _RenderScrollSemantics extends RenderProxyBox {
+  _RenderScrollSemantics({
+    required ScrollPosition position,
+    required bool allowImplicitScrolling,
+    required int? semanticChildCount,
+    RenderBox? child,
+  }) : _position = position,
+       _allowImplicitScrolling = allowImplicitScrolling,
+       _semanticChildCount = semanticChildCount,
+       assert(position != null),
+       super(child) {
+    position.addListener(markNeedsSemanticsUpdate);
+  }
+
+  /// Whether this render object is excluded from the semantic tree.
+  ScrollPosition get position => _position;
+  ScrollPosition _position;
+  set position(ScrollPosition value) {
+    assert(value != null);
+    if (value == _position)
+      return;
+    _position.removeListener(markNeedsSemanticsUpdate);
+    _position = value;
+    _position.addListener(markNeedsSemanticsUpdate);
+    markNeedsSemanticsUpdate();
+  }
+
+  /// Whether this node can be scrolled implicitly.
+  bool get allowImplicitScrolling => _allowImplicitScrolling;
+  bool _allowImplicitScrolling;
+  set allowImplicitScrolling(bool value) {
+    if (value == _allowImplicitScrolling)
+      return;
+    _allowImplicitScrolling = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  int? get semanticChildCount => _semanticChildCount;
+  int? _semanticChildCount;
+  set semanticChildCount(int? value) {
+    if (value == semanticChildCount)
+      return;
+    _semanticChildCount = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    config.isSemanticBoundary = true;
+    if (position.haveDimensions) {
+      config
+          ..hasImplicitScrolling = allowImplicitScrolling
+          ..scrollPosition = _position.pixels
+          ..scrollExtentMax = _position.maxScrollExtent
+          ..scrollExtentMin = _position.minScrollExtent
+          ..scrollChildCount = semanticChildCount;
+    }
+  }
+
+  SemanticsNode? _innerNode;
+
+  @override
+  void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, Iterable<SemanticsNode> children) {
+    if (children.isEmpty || !children.first.isTagged(RenderViewport.useTwoPaneSemantics)) {
+      super.assembleSemanticsNode(node, config, children);
+      return;
+    }
+
+    _innerNode ??= SemanticsNode(showOnScreen: showOnScreen);
+    _innerNode!
+      ..isMergedIntoParent = node.isPartOfNodeMerging
+      ..rect = node.rect;
+
+    int? firstVisibleIndex;
+    final List<SemanticsNode> excluded = <SemanticsNode>[_innerNode!];
+    final List<SemanticsNode> included = <SemanticsNode>[];
+    for (final SemanticsNode child in children) {
+      assert(child.isTagged(RenderViewport.useTwoPaneSemantics));
+      if (child.isTagged(RenderViewport.excludeFromScrolling)) {
+        excluded.add(child);
+      } else {
+        if (!child.hasFlag(SemanticsFlag.isHidden))
+          firstVisibleIndex ??= child.indexInParent;
+        included.add(child);
+      }
+    }
+    config.scrollIndex = firstVisibleIndex;
+    node.updateWith(config: null, childrenInInversePaintOrder: excluded);
+    _innerNode!.updateWith(config: config, childrenInInversePaintOrder: included);
+  }
+
+  @override
+  void clearSemantics() {
+    super.clearSemantics();
+    _innerNode = null;
+  }
+}
+
+/// A typedef for a function that can calculate the offset for a type of scroll
+/// increment given a [ScrollIncrementDetails].
+///
+/// This function is used as the type for [Scrollable.incrementCalculator],
+/// which is called from a [ScrollAction].
+typedef ScrollIncrementCalculator = double Function(ScrollIncrementDetails details);
+
+/// Describes the type of scroll increment that will be performed by a
+/// [ScrollAction] on a [Scrollable].
+///
+/// This is used to configure a [ScrollIncrementDetails] object to pass to a
+/// [ScrollIncrementCalculator] function on a [Scrollable].
+///
+/// {@template flutter.widgets.ScrollIncrementType.intent}
+/// This indicates the *intent* of the scroll, not necessarily the size. Not all
+/// scrollable areas will have the concept of a "line" or "page", but they can
+/// respond to the different standard key bindings that cause scrolling, which
+/// are bound to keys that people use to indicate a "line" scroll (e.g.
+/// control-arrowDown keys) or a "page" scroll (e.g. pageDown key). It is
+/// recommended that at least the relative magnitudes of the scrolls match
+/// expectations.
+/// {@endtemplate}
+enum ScrollIncrementType {
+  /// Indicates that the [ScrollIncrementCalculator] should return the scroll
+  /// distance it should move when the user requests to scroll by a "line".
+  ///
+  /// The distance a "line" scrolls refers to what should happen when the key
+  /// binding for "scroll down/up by a line" is triggered. It's up to the
+  /// [ScrollIncrementCalculator] function to decide what that means for a
+  /// particular scrollable.
+  line,
+
+  /// Indicates that the [ScrollIncrementCalculator] should return the scroll
+  /// distance it should move when the user requests to scroll by a "page".
+  ///
+  /// The distance a "page" scrolls refers to what should happen when the key
+  /// binding for "scroll down/up by a page" is triggered. It's up to the
+  /// [ScrollIncrementCalculator] function to decide what that means for a
+  /// particular scrollable.
+  page,
+}
+
+/// A details object that describes the type of scroll increment being requested
+/// of a [ScrollIncrementCalculator] function, as well as the current metrics
+/// for the scrollable.
+class ScrollIncrementDetails {
+  /// A const constructor for a [ScrollIncrementDetails].
+  ///
+  /// All of the arguments must not be null, and are required.
+  const ScrollIncrementDetails({
+    required this.type,
+    required this.metrics,
+  })  : assert(type != null),
+        assert(metrics != null);
+
+  /// The type of scroll this is (e.g. line, page, etc.).
+  ///
+  /// {@macro flutter.widgets.ScrollIncrementType.intent}
+  final ScrollIncrementType type;
+
+  /// The current metrics of the scrollable that is being scrolled.
+  final ScrollMetrics metrics;
+}
+
+/// An [Intent] that represents scrolling the nearest scrollable by an amount
+/// appropriate for the [type] specified.
+///
+/// The actual amount of the scroll is determined by the
+/// [Scrollable.incrementCalculator], or by its defaults if that is not
+/// specified.
+class ScrollIntent extends Intent {
+  /// Creates a const [ScrollIntent] that requests scrolling in the given
+  /// [direction], with the given [type].
+  const ScrollIntent({
+    required this.direction,
+    this.type = ScrollIncrementType.line,
+  })  : assert(direction != null),
+        assert(type != null);
+
+  /// The direction in which to scroll the scrollable containing the focused
+  /// widget.
+  final AxisDirection direction;
+
+  /// The type of scrolling that is intended.
+  final ScrollIncrementType type;
+}
+
+/// An [Action] that scrolls the [Scrollable] that encloses the current
+/// [primaryFocus] by the amount configured in the [ScrollIntent] given to it.
+///
+/// If a Scrollable cannot be found above the current [primaryFocus], the
+/// [PrimaryScrollController] will be considered for default handling of
+/// [ScrollAction]s.
+///
+/// If [Scrollable.incrementCalculator] is null for the scrollable, the default
+/// for a [ScrollIntent.type] set to [ScrollIncrementType.page] is 80% of the
+/// size of the scroll window, and for [ScrollIncrementType.line], 50 logical
+/// pixels.
+class ScrollAction extends Action<ScrollIntent> {
+  @override
+  bool isEnabled(ScrollIntent intent) {
+    final FocusNode? focus = primaryFocus;
+    final bool contextIsValid = focus != null && focus.context != null;
+    if (contextIsValid) {
+      // Check for primary scrollable within the current context
+      if (Scrollable.of(focus!.context!) != null)
+        return true;
+      // Check for fallback scrollable with context from PrimaryScrollController
+      if (PrimaryScrollController.of(focus.context!) != null) {
+        final ScrollController? primaryScrollController = PrimaryScrollController.of(focus.context!);
+        return primaryScrollController != null
+          && primaryScrollController.hasClients
+          && primaryScrollController.position.context.notificationContext != null
+          && Scrollable.of(primaryScrollController.position.context.notificationContext!) != null;
+      }
+    }
+    return false;
+  }
+
+  // Returns the scroll increment for a single scroll request, for use when
+  // scrolling using a hardware keyboard.
+  //
+  // Must not be called when the position is null, or when any of the position
+  // metrics (pixels, viewportDimension, maxScrollExtent, minScrollExtent) are
+  // null. The type and state arguments must not be null, and the widget must
+  // have already been laid out so that the position fields are valid.
+  double _calculateScrollIncrement(ScrollableState state, { ScrollIncrementType type = ScrollIncrementType.line }) {
+    assert(type != null);
+    assert(state.position != null);
+    assert(state.position.hasPixels);
+    assert(state.position.viewportDimension != null);
+    assert(state.position.maxScrollExtent != null);
+    assert(state.position.minScrollExtent != null);
+    assert(state._physics == null || state._physics!.shouldAcceptUserOffset(state.position));
+    if (state.widget.incrementCalculator != null) {
+      return state.widget.incrementCalculator!(
+        ScrollIncrementDetails(
+          type: type,
+          metrics: state.position,
+        ),
+      );
+    }
+    switch (type) {
+      case ScrollIncrementType.line:
+        return 50.0;
+      case ScrollIncrementType.page:
+        return 0.8 * state.position.viewportDimension;
+    }
+  }
+
+  // Find out how much of an increment to move by, taking the different
+  // directions into account.
+  double _getIncrement(ScrollableState state, ScrollIntent intent) {
+    final double increment = _calculateScrollIncrement(state, type: intent.type);
+    switch (intent.direction) {
+      case AxisDirection.down:
+        switch (state.axisDirection) {
+          case AxisDirection.up:
+            return -increment;
+          case AxisDirection.down:
+            return increment;
+          case AxisDirection.right:
+          case AxisDirection.left:
+            return 0.0;
+        }
+      case AxisDirection.up:
+        switch (state.axisDirection) {
+          case AxisDirection.up:
+            return increment;
+          case AxisDirection.down:
+            return -increment;
+          case AxisDirection.right:
+          case AxisDirection.left:
+            return 0.0;
+        }
+      case AxisDirection.left:
+        switch (state.axisDirection) {
+          case AxisDirection.right:
+            return -increment;
+          case AxisDirection.left:
+            return increment;
+          case AxisDirection.up:
+          case AxisDirection.down:
+            return 0.0;
+        }
+      case AxisDirection.right:
+        switch (state.axisDirection) {
+          case AxisDirection.right:
+            return increment;
+          case AxisDirection.left:
+            return -increment;
+          case AxisDirection.up:
+          case AxisDirection.down:
+            return 0.0;
+        }
+    }
+  }
+
+  @override
+  void invoke(ScrollIntent intent) {
+    ScrollableState? state = Scrollable.of(primaryFocus!.context!);
+    if (state == null) {
+      final ScrollController? primaryScrollController = PrimaryScrollController.of(primaryFocus!.context!);
+      state = Scrollable.of(primaryScrollController!.position.context.notificationContext!);
+    }
+    assert(state != null, '$ScrollAction was invoked on a context that has no scrollable parent');
+    assert(state!.position.hasPixels, 'Scrollable must be laid out before it can be scrolled via a ScrollAction');
+    assert(state!.position.viewportDimension != null);
+    assert(state!.position.maxScrollExtent != null);
+    assert(state!.position.minScrollExtent != null);
+
+    // Don't do anything if the user isn't allowed to scroll.
+    if (state!._physics != null && !state._physics!.shouldAcceptUserOffset(state.position)) {
+      return;
+    }
+    final double increment = _getIncrement(state, intent);
+    if (increment == 0.0) {
+      return;
+    }
+    state.position.moveTo(
+      state.position.pixels + increment,
+      duration: const Duration(milliseconds: 100),
+      curve: Curves.easeInOut,
+    );
+  }
+}
+
+// Not using a RestorableDouble because we want to allow null values and override
+// [enabled].
+class _RestorableScrollOffset extends RestorableValue<double?> {
+  @override
+  double? createDefaultValue() => null;
+
+  @override
+  void didUpdateValue(double? oldValue) {
+    notifyListeners();
+  }
+
+  @override
+  double fromPrimitives(Object? data) {
+    return data! as double;
+  }
+
+  @override
+  Object? toPrimitives() {
+    return value;
+  }
+
+  @override
+  bool get enabled => value != null;
+}
diff --git a/lib/src/widgets/scrollbar.dart b/lib/src/widgets/scrollbar.dart
new file mode 100644
index 0000000..39d1c57
--- /dev/null
+++ b/lib/src/widgets/scrollbar.dart
@@ -0,0 +1,1240 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:math' as math;
+
+import 'package:flute/animation.dart';
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'binding.dart';
+import 'framework.dart';
+import 'gesture_detector.dart';
+import 'media_query.dart';
+import 'notification_listener.dart';
+import 'primary_scroll_controller.dart';
+import 'scroll_controller.dart';
+import 'scroll_metrics.dart';
+import 'scroll_notification.dart';
+import 'scrollable.dart';
+import 'ticker_provider.dart';
+
+const double _kMinThumbExtent = 18.0;
+const double _kMinInteractiveSize = 48.0;
+const double _kScrollbarThickness = 6.0;
+const Duration _kScrollbarFadeDuration = Duration(milliseconds: 300);
+const Duration _kScrollbarTimeToFade = Duration(milliseconds: 600);
+
+/// Paints a scrollbar's track and thumb.
+///
+/// The size of the scrollbar along its scroll direction is typically
+/// proportional to the percentage of content completely visible on screen,
+/// as long as its size isn't less than [minLength] and it isn't overscrolling.
+///
+/// Unlike [CustomPainter]s that subclasses [CustomPainter] and only repaint
+/// when [shouldRepaint] returns true (which requires this [CustomPainter] to
+/// be rebuilt), this painter has the added optimization of repainting and not
+/// rebuilding when:
+///
+///  * the scroll position changes; and
+///  * when the scrollbar fades away.
+///
+/// Calling [update] with the new [ScrollMetrics] will repaint the new scrollbar
+/// position.
+///
+/// Updating the value on the provided [fadeoutOpacityAnimation] will repaint
+/// with the new opacity.
+///
+/// You must call [dispose] on this [ScrollbarPainter] when it's no longer used.
+///
+/// See also:
+///
+///  * [Scrollbar] for a widget showing a scrollbar around a [Scrollable] in the
+///    Material Design style.
+///  * [CupertinoScrollbar] for a widget showing a scrollbar around a
+///    [Scrollable] in the iOS style.
+class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
+  /// Creates a scrollbar with customizations given by construction arguments.
+  ScrollbarPainter({
+    required Color color,
+    required this.fadeoutOpacityAnimation,
+    Color trackColor = const Color(0x00000000),
+    Color trackBorderColor = const Color(0x00000000),
+    TextDirection? textDirection,
+    double thickness = _kScrollbarThickness,
+    EdgeInsets padding = EdgeInsets.zero,
+    double mainAxisMargin = 0.0,
+    double crossAxisMargin = 0.0,
+    Radius? radius,
+    double minLength = _kMinThumbExtent,
+    double? minOverscrollLength,
+  }) : assert(color != null),
+       assert(thickness != null),
+       assert(fadeoutOpacityAnimation != null),
+       assert(mainAxisMargin != null),
+       assert(crossAxisMargin != null),
+       assert(minLength != null),
+       assert(minLength >= 0),
+       assert(minOverscrollLength == null || minOverscrollLength <= minLength),
+       assert(minOverscrollLength == null || minOverscrollLength >= 0),
+       assert(padding != null),
+       assert(padding.isNonNegative),
+       _color = color,
+       _textDirection = textDirection,
+       _thickness = thickness,
+       _radius = radius,
+       _padding = padding,
+       _mainAxisMargin = mainAxisMargin,
+       _crossAxisMargin = crossAxisMargin,
+       _minLength = minLength,
+       _trackColor = trackColor,
+       _trackBorderColor = trackBorderColor,
+       _minOverscrollLength = minOverscrollLength ?? minLength {
+    fadeoutOpacityAnimation.addListener(notifyListeners);
+  }
+
+  /// [Color] of the thumb. Mustn't be null.
+  Color get color => _color;
+  Color _color;
+  set color(Color value) {
+    assert(value != null);
+    if (color == value)
+      return;
+
+    _color = value;
+    notifyListeners();
+  }
+
+  /// [Color] of the track. Mustn't be null.
+  Color get trackColor => _trackColor;
+  Color _trackColor;
+  set trackColor(Color value) {
+    assert(value != null);
+    if (trackColor == value)
+      return;
+
+    _trackColor = value;
+    notifyListeners();
+  }
+
+  /// [Color] of the track border. Mustn't be null.
+  Color get trackBorderColor => _trackBorderColor;
+  Color _trackBorderColor;
+  set trackBorderColor(Color value) {
+    assert(value != null);
+    if (trackBorderColor == value)
+      return;
+
+    _trackBorderColor = value;
+    notifyListeners();
+  }
+
+  /// [TextDirection] of the [BuildContext] which dictates the side of the
+  /// screen the scrollbar appears in (the trailing side). Must be set prior to
+  /// calling paint.
+  TextDirection? get textDirection => _textDirection;
+  TextDirection? _textDirection;
+  set textDirection(TextDirection? value) {
+    assert(value != null);
+    if (textDirection == value)
+      return;
+
+    _textDirection = value;
+    notifyListeners();
+  }
+
+  /// Thickness of the scrollbar in its cross-axis in logical pixels. Mustn't be null.
+  double get thickness => _thickness;
+  double _thickness;
+  set thickness(double value) {
+    assert(value != null);
+    if (thickness == value)
+      return;
+
+    _thickness = value;
+    notifyListeners();
+  }
+
+  /// An opacity [Animation] that dictates the opacity of the thumb.
+  /// Changes in value of this [Listenable] will automatically trigger repaints.
+  /// Mustn't be null.
+  final Animation<double> fadeoutOpacityAnimation;
+
+  /// Distance from the scrollbar's start and end to the edge of the viewport
+  /// in logical pixels. It affects the amount of available paint area.
+  ///
+  /// Mustn't be null and defaults to 0.
+  double get mainAxisMargin => _mainAxisMargin;
+  double _mainAxisMargin;
+  set mainAxisMargin(double value) {
+    assert(value != null);
+    if (mainAxisMargin == value)
+      return;
+
+    _mainAxisMargin = value;
+    notifyListeners();
+  }
+
+  /// Distance from the scrollbar's side to the nearest edge in logical pixels.
+  ///
+  /// Must not be null and defaults to 0.
+  double get crossAxisMargin => _crossAxisMargin;
+  double _crossAxisMargin;
+  set crossAxisMargin(double value) {
+    assert(value != null);
+    if (crossAxisMargin == value)
+      return;
+
+    _crossAxisMargin = value;
+    notifyListeners();
+  }
+
+  /// [Radius] of corners if the scrollbar should have rounded corners.
+  ///
+  /// Scrollbar will be rectangular if [radius] is null.
+  Radius? get radius => _radius;
+  Radius? _radius;
+  set radius(Radius? value) {
+    if (radius == value)
+      return;
+
+    _radius = value;
+    notifyListeners();
+  }
+
+  /// The amount of space by which to inset the scrollbar's start and end, as
+  /// well as its side to the nearest edge, in logical pixels.
+  ///
+  /// This is typically set to the current [MediaQueryData.padding] to avoid
+  /// partial obstructions such as display notches. If you only want additional
+  /// margins around the scrollbar, see [mainAxisMargin].
+  ///
+  /// Defaults to [EdgeInsets.zero]. Must not be null and offsets from all four
+  /// directions must be greater than or equal to zero.
+  EdgeInsets get padding => _padding;
+  EdgeInsets _padding;
+  set padding(EdgeInsets value) {
+    assert(value != null);
+    if (padding == value)
+      return;
+
+    _padding = value;
+    notifyListeners();
+  }
+
+
+  /// The preferred smallest size the scrollbar can shrink to when the total
+  /// scrollable extent is large, the current visible viewport is small, and the
+  /// viewport is not overscrolled.
+  ///
+  /// The size of the scrollbar may shrink to a smaller size than [minLength] to
+  /// fit in the available paint area. E.g., when [minLength] is
+  /// `double.infinity`, it will not be respected if
+  /// [ScrollMetrics.viewportDimension] and [mainAxisMargin] are finite.
+  ///
+  /// Mustn't be null and the value has to be within the range of 0 to
+  /// [minOverscrollLength], inclusive. Defaults to 18.0.
+  double get minLength => _minLength;
+  double _minLength;
+  set minLength(double value) {
+    assert(value != null);
+    if (minLength == value)
+      return;
+
+    _minLength = value;
+    notifyListeners();
+  }
+
+  /// The preferred smallest size the scrollbar can shrink to when viewport is
+  /// overscrolled.
+  ///
+  /// When overscrolling, the size of the scrollbar may shrink to a smaller size
+  /// than [minOverscrollLength] to fit in the available paint area. E.g., when
+  /// [minOverscrollLength] is `double.infinity`, it will not be respected if
+  /// the [ScrollMetrics.viewportDimension] and [mainAxisMargin] are finite.
+  ///
+  /// The value is less than or equal to [minLength] and greater than or equal to 0.
+  /// If unspecified or set to null, it will defaults to the value of [minLength].
+  double get minOverscrollLength => _minOverscrollLength;
+  double _minOverscrollLength;
+  set minOverscrollLength(double value) {
+    assert(value != null);
+    if (minOverscrollLength == value)
+      return;
+
+    _minOverscrollLength = value;
+    notifyListeners();
+  }
+
+  ScrollMetrics? _lastMetrics;
+  AxisDirection? _lastAxisDirection;
+  Rect? _thumbRect;
+  Rect? _trackRect;
+  late double _thumbOffset;
+
+  /// Update with new [ScrollMetrics]. The scrollbar will show and redraw itself
+  /// based on these new metrics.
+  ///
+  /// The scrollbar will remain on screen.
+  void update(
+    ScrollMetrics metrics,
+    AxisDirection axisDirection,
+  ) {
+    _lastMetrics = metrics;
+    _lastAxisDirection = axisDirection;
+    notifyListeners();
+  }
+
+  /// Update and redraw with new scrollbar thickness and radius.
+  void updateThickness(double nextThickness, Radius nextRadius) {
+    thickness = nextThickness;
+    radius = nextRadius;
+  }
+
+  Paint get _paintThumb {
+    return Paint()
+      ..color = color.withOpacity(color.opacity * fadeoutOpacityAnimation.value);
+  }
+
+  Paint _paintTrack({ bool isBorder = false }) {
+    if (isBorder) {
+      return Paint()
+        ..color = trackBorderColor.withOpacity(trackBorderColor.opacity * fadeoutOpacityAnimation.value)
+        ..style = PaintingStyle.stroke
+        ..strokeWidth = 1.0;
+    }
+    return Paint()
+      ..color = trackColor.withOpacity(trackColor.opacity * fadeoutOpacityAnimation.value);
+  }
+
+  void _paintScrollbar(Canvas canvas, Size size, double thumbExtent, AxisDirection direction) {
+    assert(
+      textDirection != null,
+      'A TextDirection must be provided before a Scrollbar can be painted.',
+    );
+
+    final double x, y;
+    final Size thumbSize, trackSize;
+    final Offset trackOffset;
+
+    switch (direction) {
+      case AxisDirection.down:
+        thumbSize = Size(thickness, thumbExtent);
+        trackSize = Size(thickness + 2 * crossAxisMargin, _trackExtent);
+        x = textDirection == TextDirection.rtl
+          ? crossAxisMargin + padding.left
+          : size.width - thickness - crossAxisMargin - padding.right;
+        y = _thumbOffset;
+        trackOffset = Offset(x - crossAxisMargin, 0.0);
+        break;
+      case AxisDirection.up:
+        thumbSize = Size(thickness, thumbExtent);
+        trackSize = Size(thickness + 2 * crossAxisMargin, _trackExtent);
+        x = textDirection == TextDirection.rtl
+          ? crossAxisMargin + padding.left
+          : size.width - thickness - crossAxisMargin - padding.right;
+        y = _thumbOffset;
+        trackOffset = Offset(x - crossAxisMargin, 0.0);
+        break;
+      case AxisDirection.left:
+        thumbSize = Size(thumbExtent, thickness);
+        x = _thumbOffset;
+        y = size.height - thickness - crossAxisMargin - padding.bottom;
+        trackSize = Size(_trackExtent, thickness + 2 * crossAxisMargin);
+        trackOffset = Offset(0.0, y - crossAxisMargin);
+        break;
+      case AxisDirection.right:
+        thumbSize = Size(thumbExtent, thickness);
+        trackSize = Size(_trackExtent, thickness + 2 * crossAxisMargin);
+        x = _thumbOffset;
+        y = size.height - thickness - crossAxisMargin - padding.bottom;
+        trackOffset = Offset(0.0, y - crossAxisMargin);
+        break;
+    }
+
+    _trackRect = trackOffset & trackSize;
+    canvas.drawRect(_trackRect!, _paintTrack());
+    canvas.drawLine(
+      trackOffset,
+      Offset(trackOffset.dx, trackOffset.dy + _trackExtent),
+      _paintTrack(isBorder: true),
+    );
+
+    _thumbRect = Offset(x, y) & thumbSize;
+    if (radius == null)
+      canvas.drawRect(_thumbRect!, _paintThumb);
+    else
+      canvas.drawRRect(RRect.fromRectAndRadius(_thumbRect!, radius!), _paintThumb);
+  }
+
+  double _thumbExtent() {
+    // Thumb extent reflects fraction of content visible, as long as this
+    // isn't less than the absolute minimum size.
+    // _totalContentExtent >= viewportDimension, so (_totalContentExtent - _mainAxisPadding) > 0
+    final double fractionVisible = ((_lastMetrics!.extentInside - _mainAxisPadding) / (_totalContentExtent - _mainAxisPadding))
+      .clamp(0.0, 1.0);
+
+    final double thumbExtent = math.max(
+      math.min(_trackExtent, minOverscrollLength),
+      _trackExtent * fractionVisible,
+    );
+
+    final double fractionOverscrolled = 1.0 - _lastMetrics!.extentInside / _lastMetrics!.viewportDimension;
+    final double safeMinLength = math.min(minLength, _trackExtent);
+    final double newMinLength = (_beforeExtent > 0 && _afterExtent > 0)
+      // Thumb extent is no smaller than minLength if scrolling normally.
+      ? safeMinLength
+      // User is overscrolling. Thumb extent can be less than minLength
+      // but no smaller than minOverscrollLength. We can't use the
+      // fractionVisible to produce intermediate values between minLength and
+      // minOverscrollLength when the user is transitioning from regular
+      // scrolling to overscrolling, so we instead use the percentage of the
+      // content that is still in the viewport to determine the size of the
+      // thumb. iOS behavior appears to have the thumb reach its minimum size
+      // with ~20% of overscroll. We map the percentage of minLength from
+      // [0.8, 1.0] to [0.0, 1.0], so 0% to 20% of overscroll will produce
+      // values for the thumb that range between minLength and the smallest
+      // possible value, minOverscrollLength.
+      : safeMinLength * (1.0 - fractionOverscrolled.clamp(0.0, 0.2) / 0.2);
+
+    // The `thumbExtent` should be no greater than `trackSize`, otherwise
+    // the scrollbar may scroll towards the wrong direction.
+    return thumbExtent.clamp(newMinLength, _trackExtent);
+  }
+
+  @override
+  void dispose() {
+    fadeoutOpacityAnimation.removeListener(notifyListeners);
+    super.dispose();
+  }
+
+  bool get _isVertical => _lastAxisDirection == AxisDirection.down || _lastAxisDirection == AxisDirection.up;
+  bool get _isReversed => _lastAxisDirection == AxisDirection.up || _lastAxisDirection == AxisDirection.left;
+  // The amount of scroll distance before and after the current position.
+  double get _beforeExtent => _isReversed ? _lastMetrics!.extentAfter : _lastMetrics!.extentBefore;
+  double get _afterExtent => _isReversed ? _lastMetrics!.extentBefore : _lastMetrics!.extentAfter;
+  // Padding of the thumb track.
+  double get _mainAxisPadding => _isVertical ? padding.vertical : padding.horizontal;
+  // The size of the thumb track.
+  double get _trackExtent => _lastMetrics!.viewportDimension - 2 * mainAxisMargin - _mainAxisPadding;
+
+  // The total size of the scrollable content.
+  double get _totalContentExtent {
+    return _lastMetrics!.maxScrollExtent
+      - _lastMetrics!.minScrollExtent
+      + _lastMetrics!.viewportDimension;
+  }
+
+  /// Convert between a thumb track position and the corresponding scroll
+  /// position.
+  ///
+  /// thumbOffsetLocal is a position in the thumb track. Cannot be null.
+  double getTrackToScroll(double thumbOffsetLocal) {
+    assert(thumbOffsetLocal != null);
+    final double scrollableExtent = _lastMetrics!.maxScrollExtent - _lastMetrics!.minScrollExtent;
+    final double thumbMovableExtent = _trackExtent - _thumbExtent();
+
+    return scrollableExtent * thumbOffsetLocal / thumbMovableExtent;
+  }
+
+  // Converts between a scroll position and the corresponding position in the
+  // thumb track.
+  double _getScrollToTrack(ScrollMetrics metrics, double thumbExtent) {
+    final double scrollableExtent = metrics.maxScrollExtent - metrics.minScrollExtent;
+
+    final double fractionPast = (scrollableExtent > 0)
+      ? ((metrics.pixels - metrics.minScrollExtent) / scrollableExtent).clamp(0.0, 1.0)
+      : 0;
+
+    return (_isReversed ? 1 - fractionPast : fractionPast) * (_trackExtent - thumbExtent);
+  }
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    if (_lastAxisDirection == null
+        || _lastMetrics == null
+        || fadeoutOpacityAnimation.value == 0.0)
+      return;
+
+    // Skip painting if there's not enough space.
+    if (_lastMetrics!.viewportDimension <= _mainAxisPadding || _trackExtent <= 0) {
+      return;
+    }
+
+    final double beforePadding = _isVertical ? padding.top : padding.left;
+    final double thumbExtent = _thumbExtent();
+    final double thumbOffsetLocal = _getScrollToTrack(_lastMetrics!, thumbExtent);
+    _thumbOffset = thumbOffsetLocal + mainAxisMargin + beforePadding;
+
+    // Do not paint a scrollbar if the scroll view is infinitely long.
+    // TODO(Piinks): Special handling for infinite scroll views, https://github.com/flutter/flutter/issues/41434
+    if (_lastMetrics!.maxScrollExtent.isInfinite)
+      return;
+
+    return _paintScrollbar(canvas, size, thumbExtent, _lastAxisDirection!);
+  }
+
+  /// Same as hitTest, but includes some padding to make sure that the region
+  /// isn't too small to be interacted with by the user.
+  bool hitTestInteractive(Offset position) {
+    if (_thumbRect == null) {
+      return false;
+    }
+    // The scrollbar is not able to be hit when transparent.
+    if (fadeoutOpacityAnimation.value == 0.0) {
+      return false;
+    }
+    final Rect interactiveScrollbarRect = _trackRect == null
+      ? _thumbRect!.expandToInclude(
+          Rect.fromCircle(center: _thumbRect!.center, radius: _kMinInteractiveSize / 2),
+        )
+      : _trackRect!.expandToInclude(
+          Rect.fromCircle(center: _thumbRect!.center, radius: _kMinInteractiveSize / 2),
+        );
+    return interactiveScrollbarRect.contains(position);
+  }
+
+  /// Same as hitTestInteractive, but excludes the track portion of the scrollbar.
+  /// Used to evaluate interactions with only the scrollbar thumb.
+  bool hitTestOnlyThumbInteractive(Offset position) {
+    if (_thumbRect == null) {
+      return false;
+    }
+    // The thumb is not able to be hit when transparent.
+    if (fadeoutOpacityAnimation.value == 0.0) {
+      return false;
+    }
+    final Rect interactiveThumbRect = _thumbRect!.expandToInclude(
+      Rect.fromCircle(center: _thumbRect!.center, radius: _kMinInteractiveSize / 2),
+    );
+    return interactiveThumbRect.contains(position);
+  }
+
+  // Scrollbars are interactive.
+  @override
+  bool? hitTest(Offset? position) {
+    if (_thumbRect == null) {
+      return null;
+    }
+    // The thumb is not able to be hit when transparent.
+    if (fadeoutOpacityAnimation.value == 0.0) {
+      return false;
+    }
+    return _thumbRect!.contains(position!);
+  }
+
+  @override
+  bool shouldRepaint(ScrollbarPainter old) {
+    // Should repaint if any properties changed.
+    return color != old.color
+        || trackColor != old.trackColor
+        || trackBorderColor != old.trackBorderColor
+        || textDirection != old.textDirection
+        || thickness != old.thickness
+        || fadeoutOpacityAnimation != old.fadeoutOpacityAnimation
+        || mainAxisMargin != old.mainAxisMargin
+        || crossAxisMargin != old.crossAxisMargin
+        || radius != old.radius
+        || minLength != old.minLength
+        || padding != old.padding
+        || minOverscrollLength != old.minOverscrollLength;
+  }
+
+  @override
+  bool shouldRebuildSemantics(CustomPainter oldDelegate) => false;
+
+  @override
+  SemanticsBuilderCallback? get semanticsBuilder => null;
+}
+
+/// An extendable base class for building scrollbars that fade in and out.
+///
+/// To add a scrollbar to a [ScrollView], like a [ListView] or a
+/// [CustomScrollView], wrap the scroll view widget in a [RawScrollbar] widget.
+///
+/// {@template flutter.widgets.Scrollbar}
+/// A scrollbar thumb indicates which portion of a [ScrollView] is actually
+/// visible.
+///
+/// By default, the thumb will fade in and out as the child scroll view
+/// scrolls. When [isAlwaysShown] is true, the scrollbar thumb will remain
+/// visible without the fade animation. This requires that a [ScrollController]
+/// is provided to [controller], or that the [PrimaryScrollController] is available.
+///
+/// Scrollbars are interactive and will also use the [PrimaryScrollController] if
+/// a [controller] is not set. Scrollbar thumbs can be dragged along the main axis
+/// of the [ScrollView] to change the [ScrollPosition]. Tapping along the track
+/// exclusive of the thumb will trigger a [ScrollIncrementType.page] based on
+/// the relative position to the thumb.
+///
+/// If the child [ScrollView] is infinitely long, the [RawScrollbar] will not be
+/// painted. In this case, the scrollbar cannot accurately represent the
+/// relative location of the visible area, or calculate the accurate delta to
+/// apply when  dragging on the thumb or tapping on the track.
+/// {@endtemplate}
+///
+// TODO(Piinks): Add code sample
+///
+/// See also:
+///
+///  * [ListView], which displays a linear, scrollable list of children.
+///  * [GridView], which displays a 2 dimensional, scrollable array of children.
+// TODO(Piinks): Add support for passing a shape instead of thickness/radius.
+// Will need to update painter to support as well.
+// Also, expose helpful properties like main/crossAxis margins, minThumbLength,
+// etc. on the RawScrollbar in follow-up changes
+// part of https://github.com/flutter/flutter/issues/13253
+class RawScrollbar extends StatefulWidget {
+  /// Creates a basic raw scrollbar that wraps the given [child].
+  ///
+  /// The [child], or a descendant of the [child], should be a source of
+  /// [ScrollNotification] notifications, typically a [Scrollable] widget.
+  ///
+  /// The [child], [thickness], [thumbColor], [isAlwaysShown], [fadeDuration],
+  /// and [timeToFade] arguments must not be null.
+  const RawScrollbar({
+    Key? key,
+    required this.child,
+    this.controller,
+    this.isAlwaysShown = false,
+    this.radius,
+    this.thickness,
+    this.thumbColor,
+    this.fadeDuration = _kScrollbarFadeDuration,
+    this.timeToFade = _kScrollbarTimeToFade,
+    this.pressDuration = Duration.zero,
+  }) : assert(isAlwaysShown != null),
+       assert(child != null),
+       assert(fadeDuration != null),
+       assert(timeToFade != null),
+       assert(pressDuration != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// The scrollbar will be stacked on top of this child. This child (and its
+  /// subtree) should include a source of [ScrollNotification] notifications.
+  ///
+  /// Typically a [ListView] or [CustomScrollView].
+  final Widget child;
+
+  /// The [ScrollController] used to implement Scrollbar dragging.
+  ///
+  /// If nothing is passed to controller, the default behavior is to automatically
+  /// enable scrollbar dragging on the nearest ScrollController using
+  /// [PrimaryScrollController.of].
+  ///
+  /// If a ScrollController is passed, then dragging on the scrollbar thumb will
+  /// update the [ScrollPosition] attached to the controller. A stateful ancestor
+  /// of this widget needs to manage the ScrollController and either pass it to
+  /// a scrollable descendant or use a PrimaryScrollController to share it.
+  ///
+  /// {@tool snippet}
+  /// Here is an example of using the `controller` parameter to enable
+  /// scrollbar dragging for multiple independent ListViews:
+  ///
+  /// ```dart
+  /// final ScrollController _controllerOne = ScrollController();
+  /// final ScrollController _controllerTwo = ScrollController();
+  ///
+  /// build(BuildContext context) {
+  ///   return Column(
+  ///     children: <Widget>[
+  ///       Container(
+  ///        height: 200,
+  ///        child: CupertinoScrollbar(
+  ///          controller: _controllerOne,
+  ///          child: ListView.builder(
+  ///            controller: _controllerOne,
+  ///            itemCount: 120,
+  ///            itemBuilder: (BuildContext context, int index) => Text('item $index'),
+  ///          ),
+  ///        ),
+  ///      ),
+  ///      Container(
+  ///        height: 200,
+  ///        child: CupertinoScrollbar(
+  ///          controller: _controllerTwo,
+  ///          child: ListView.builder(
+  ///            controller: _controllerTwo,
+  ///            itemCount: 120,
+  ///            itemBuilder: (BuildContext context, int index) => Text('list 2 item $index'),
+  ///          ),
+  ///        ),
+  ///      ),
+  ///    ],
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  final ScrollController? controller;
+
+  /// Indicates that the scrollbar should be visible, even when a scroll is not
+  /// underway.
+  ///
+  /// When false, the scrollbar will be shown during scrolling
+  /// and will fade out otherwise.
+  ///
+  /// When true, the scrollbar will always be visible and never fade out. If the
+  /// [controller] property has not been set, the [PrimaryScrollController] will
+  /// be used.
+  ///
+  /// Defaults to false.
+  ///
+  /// {@tool snippet}
+  ///
+  /// ```dart
+  /// final ScrollController _controllerOne = ScrollController();
+  /// final ScrollController _controllerTwo = ScrollController();
+  ///
+  /// build(BuildContext context) {
+  /// return Column(
+  ///   children: <Widget>[
+  ///     SizedBox(
+  ///        height: 200,
+  ///        child: Scrollbar(
+  ///          isAlwaysShown: true,
+  ///          controller: _controllerOne,
+  ///          child: ListView.builder(
+  ///            controller: _controllerOne,
+  ///            itemCount: 120,
+  ///            itemBuilder: (BuildContext context, int index) {
+  ///              return  Text('item $index');
+  ///            },
+  ///          ),
+  ///        ),
+  ///      ),
+  ///      SizedBox(
+  ///        height: 200,
+  ///        child: CupertinoScrollbar(
+  ///          isAlwaysShown: true,
+  ///          controller: _controllerTwo,
+  ///          child: SingleChildScrollView(
+  ///            controller: _controllerTwo,
+  ///            child: SizedBox(
+  ///              height: 2000,
+  ///              width: 500,
+  ///              child: Placeholder(),
+  ///            ),
+  ///          ),
+  ///        ),
+  ///      ),
+  ///    ],
+  ///   );
+  /// }
+  /// ```
+  /// {@end-tool}
+  final bool isAlwaysShown;
+
+  /// The [Radius] of the scrollbar thumb's rounded rectangle corners.
+  ///
+  /// Scrollbar will be rectangular if [radius] is null, which is the default
+  /// behavior.
+  final Radius? radius;
+
+  /// The thickness of the scrollbar in the cross axis of the scrollable.
+  ///
+  /// If null, will default to 6.0 pixels.
+  final double? thickness;
+
+  /// The color of the scrollbar thumb.
+  ///
+  /// If null, defaults to Color(0x66BCBCBC).
+  final Color? thumbColor;
+
+  /// The [Duration] of the fade animation.
+  ///
+  /// Cannot be null, defaults to a [Duration] of 300 milliseconds.
+  final Duration fadeDuration;
+
+  /// The [Duration] of time until the fade animation begins.
+  ///
+  /// Cannot be null, defaults to a [Duration] of 600 milliseconds.
+  final Duration timeToFade;
+
+  /// The [Duration] of time that a LongPress will trigger the drag gesture of
+  /// the scrollbar thumb.
+  ///
+  /// Cannot be null, defaults to [Duration.zero].
+  final Duration pressDuration;
+
+  @override
+  RawScrollbarState<RawScrollbar> createState() => RawScrollbarState<RawScrollbar>();
+}
+
+/// The state for a [RawScrollbar] widget, also shared by the [Scrollbar] and
+/// [CupertinoScrollbar] widgets.
+///
+/// Controls the animation that fades a scrollbar's thumb in and out of view.
+///
+/// Provides defaults gestures for dragging the scrollbar thumb and tapping on the
+/// scrollbar track.
+class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProviderStateMixin<T> {
+  double? _dragScrollbarAxisPosition;
+  ScrollController? _currentController;
+  Timer? _fadeoutTimer;
+  late AnimationController _fadeoutAnimationController;
+  late Animation<double> _fadeoutOpacityAnimation;
+  final GlobalKey  _scrollbarPainterKey = GlobalKey();
+  bool _hoverIsActive = false;
+
+
+  /// Used to paint the scrollbar.
+  ///
+  /// Can be customized by subclasses to change scrollbar behavior by overriding
+  /// [updateScrollbarPainter].
+  @protected
+  late final ScrollbarPainter scrollbarPainter;
+
+  @override
+  void initState() {
+    super.initState();
+    _fadeoutAnimationController = AnimationController(
+      vsync: this,
+      duration: widget.fadeDuration,
+    );
+    _fadeoutOpacityAnimation = CurvedAnimation(
+      parent: _fadeoutAnimationController,
+      curve: Curves.fastOutSlowIn,
+    );
+    scrollbarPainter = ScrollbarPainter(
+      color: widget.thumbColor ?? const Color(0x66BCBCBC),
+      thickness: widget.thickness ?? _kScrollbarThickness,
+      fadeoutOpacityAnimation: _fadeoutOpacityAnimation,
+    );
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _maybeTriggerScrollbar();
+  }
+
+  // Waits one frame and cause an empty scroll event (zero delta pixels).
+  //
+  // This allows the thumb to show immediately when isAlwaysShown is true.
+  // A scroll event is required in order to paint the thumb.
+  void _maybeTriggerScrollbar() {
+    WidgetsBinding.instance!.addPostFrameCallback((Duration duration) {
+      if (widget.isAlwaysShown) {
+        _fadeoutTimer?.cancel();
+        // Wait one frame and cause an empty scroll event.  This allows the
+        // thumb to show immediately when isAlwaysShown is true. A scroll
+        // event is required in order to paint the thumb.
+        final ScrollController? scrollController = widget.controller ?? PrimaryScrollController.of(context);
+        assert(
+          scrollController != null,
+          'A ScrollController is required when Scrollbar.isAlwaysShown is true. '
+          'Either Scrollbar.controller was not provided, or a PrimaryScrollController could not be found.',
+        );
+        scrollController!.position.didUpdateScrollPositionBy(0);
+      }
+    });
+  }
+
+  /// This method is responsible for configuring the [scrollbarPainter]
+  /// according to the [widget]'s properties and any inherited widgets the
+  /// painter depends on, like [Directionality] and [MediaQuery].
+  ///
+  /// Subclasses can override to configure the [scrollbarPainter].
+  @protected
+  void  updateScrollbarPainter() {
+    scrollbarPainter
+      ..color =  widget.thumbColor ?? const Color(0x66BCBCBC)
+      ..textDirection = Directionality.of(context)
+      ..thickness = widget.thickness ?? _kScrollbarThickness
+      ..radius = widget.radius
+      ..padding = MediaQuery.of(context).padding;
+  }
+
+  @override
+  void didUpdateWidget(T oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.isAlwaysShown != oldWidget.isAlwaysShown) {
+      if (widget.isAlwaysShown == true) {
+        _maybeTriggerScrollbar();
+        _fadeoutAnimationController.animateTo(1.0);
+      } else {
+        _fadeoutAnimationController.reverse();
+      }
+    }
+  }
+
+  void _updateScrollPosition(double primaryDelta) {
+    assert(_currentController != null);
+
+    // Convert primaryDelta, the amount that the scrollbar moved since the last
+    // time _dragScrollbar was called, into the coordinate space of the scroll
+    // position, and jump to that position.
+    final double scrollOffsetLocal = scrollbarPainter.getTrackToScroll(primaryDelta);
+    final double scrollOffsetGlobal = scrollOffsetLocal + _currentController!.position.pixels;
+    _currentController!.position.jumpTo(scrollOffsetGlobal);
+  }
+
+  void _maybeStartFadeoutTimer() {
+    if (!widget.isAlwaysShown) {
+      _fadeoutTimer?.cancel();
+      _fadeoutTimer = Timer(widget.timeToFade, () {
+        _fadeoutAnimationController.reverse();
+        _fadeoutTimer = null;
+      });
+    }
+  }
+
+  /// Returns the [Axis] of the child scroll view, or null if the current scroll
+  /// controller does not have any attached positions.
+  @protected
+  Axis? getScrollbarDirection() {
+    assert(_currentController != null);
+    if (_currentController!.hasClients)
+      return _currentController!.position.axis;
+    return null;
+  }
+
+  /// Handler called when a press on the scrollbar thumb has been recognized.
+  ///
+  /// Cancels the [Timer] associated with the fade animation of the scrollbar.
+  @protected
+  @mustCallSuper
+  void handleThumbPress() {
+    if (getScrollbarDirection() == null) {
+      return;
+    }
+    _fadeoutTimer?.cancel();
+  }
+
+  /// Handler called when a long press gesture has started.
+  ///
+  /// Begins the fade out animation and initializes dragging the scrollbar thumb.
+  @protected
+  @mustCallSuper
+  void handleThumbPressStart(Offset localPosition) {
+    _currentController = widget.controller ?? PrimaryScrollController.of(context);
+    final Axis? direction = getScrollbarDirection();
+    if (direction == null) {
+      return;
+    }
+    _fadeoutTimer?.cancel();
+    _fadeoutAnimationController.forward();
+    switch (direction) {
+      case Axis.vertical:
+        _dragScrollbarAxisPosition = localPosition.dy;
+        break;
+      case Axis.horizontal:
+        _dragScrollbarAxisPosition = localPosition.dx;
+        break;
+    }
+  }
+
+  /// Handler called when a currently active long press gesture moves.
+  ///
+  /// Updates the position of the child scrollable.
+  @protected
+  @mustCallSuper
+  void handleThumbPressUpdate(Offset localPosition) {
+    final Axis? direction = getScrollbarDirection();
+    if (direction == null) {
+      return;
+    }
+    switch(direction) {
+      case Axis.vertical:
+        _updateScrollPosition(localPosition.dy - _dragScrollbarAxisPosition!);
+        _dragScrollbarAxisPosition = localPosition.dy;
+        break;
+      case Axis.horizontal:
+        _updateScrollPosition(localPosition.dx - _dragScrollbarAxisPosition!);
+        _dragScrollbarAxisPosition = localPosition.dx;
+        break;
+    }
+  }
+
+  /// Handler called when a long press has ended.
+  @protected
+  @mustCallSuper
+  void handleThumbPressEnd(Offset localPosition, Velocity velocity) {
+    final Axis? direction = getScrollbarDirection();
+    if (direction == null)
+      return;
+    _maybeStartFadeoutTimer();
+    _dragScrollbarAxisPosition = null;
+    _currentController = null;
+  }
+
+  void _handleTrackTapDown(TapDownDetails details) {
+    // The Scrollbar should page towards the position of the tap on the track.
+    _currentController = widget.controller ?? PrimaryScrollController.of(context);
+
+    double scrollIncrement;
+    // Is an increment calculator available?
+    final ScrollIncrementCalculator? calculator = Scrollable.of(
+      _currentController!.position.context.notificationContext!
+    )?.widget.incrementCalculator;
+    if (calculator != null) {
+      scrollIncrement = calculator(
+        ScrollIncrementDetails(
+          type: ScrollIncrementType.page,
+          metrics: _currentController!.position,
+        )
+      );
+    } else {
+      // Default page increment
+      scrollIncrement = 0.8 * _currentController!.position.viewportDimension;
+    }
+
+    // Adjust scrollIncrement for direction
+    switch (_currentController!.position.axisDirection) {
+      case AxisDirection.up:
+        if (details.localPosition.dy > scrollbarPainter._thumbOffset)
+          scrollIncrement = -scrollIncrement;
+        break;
+      case AxisDirection.down:
+        if (details.localPosition.dy < scrollbarPainter._thumbOffset)
+          scrollIncrement = -scrollIncrement;
+        break;
+      case AxisDirection.right:
+        if (details.localPosition.dx < scrollbarPainter._thumbOffset)
+          scrollIncrement = -scrollIncrement;
+        break;
+      case AxisDirection.left:
+        if (details.localPosition.dx > scrollbarPainter._thumbOffset)
+          scrollIncrement = -scrollIncrement;
+        break;
+    }
+
+    _currentController!.position.moveTo(
+      _currentController!.position.pixels + scrollIncrement,
+      duration: const Duration(milliseconds: 100),
+      curve: Curves.easeInOut,
+    );
+  }
+
+  bool _handleScrollNotification(ScrollNotification notification) {
+
+    final ScrollMetrics metrics = notification.metrics;
+    if (metrics.maxScrollExtent <= metrics.minScrollExtent)
+      return false;
+
+    if (notification is ScrollUpdateNotification ||
+      notification is OverscrollNotification) {
+      // Any movements always makes the scrollbar start showing up.
+      if (_fadeoutAnimationController.status != AnimationStatus.forward)
+        _fadeoutAnimationController.forward();
+
+      _fadeoutTimer?.cancel();
+      scrollbarPainter.update(notification.metrics, notification.metrics.axisDirection);
+    } else if (notification is ScrollEndNotification) {
+      if (_dragScrollbarAxisPosition == null)
+        _maybeStartFadeoutTimer();
+    }
+    return false;
+  }
+
+  Map<Type, GestureRecognizerFactory> get _gestures {
+    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
+    final ScrollController? controller = widget.controller ?? PrimaryScrollController.of(context);
+    if (controller == null)
+      return gestures;
+
+    gestures[_ThumbPressGestureRecognizer] =
+      GestureRecognizerFactoryWithHandlers<_ThumbPressGestureRecognizer>(
+          () => _ThumbPressGestureRecognizer(
+          debugOwner: this,
+          customPaintKey: _scrollbarPainterKey,
+          pressDuration: widget.pressDuration,
+        ),
+          (_ThumbPressGestureRecognizer instance) {
+          instance.onLongPress = handleThumbPress;
+          instance.onLongPressStart = (LongPressStartDetails details) => handleThumbPressStart(details.localPosition);
+          instance.onLongPressMoveUpdate = (LongPressMoveUpdateDetails details) => handleThumbPressUpdate(details.localPosition);
+          instance.onLongPressEnd = (LongPressEndDetails details) => handleThumbPressEnd(details.localPosition, details.velocity);
+        },
+      );
+
+    gestures[_TrackTapGestureRecognizer] =
+      GestureRecognizerFactoryWithHandlers<_TrackTapGestureRecognizer>(
+          () => _TrackTapGestureRecognizer(
+          debugOwner: this,
+          customPaintKey: _scrollbarPainterKey,
+        ),
+          (_TrackTapGestureRecognizer instance) {
+          instance.onTapDown = _handleTrackTapDown;
+        },
+      );
+
+    return gestures;
+  }
+  /// Returns true if the provided [Offset] is located over the track of the
+  /// [RawScrollbar].
+  ///
+  /// Excludes the [RawScrollbar] thumb.
+  @protected
+  bool isPointerOverTrack(Offset position) {
+    if (_scrollbarPainterKey.currentContext == null) {
+      return false;
+    }
+    final Offset localOffset = _getLocalOffset(_scrollbarPainterKey, position);
+    return scrollbarPainter.hitTestInteractive(localOffset)
+      && !scrollbarPainter.hitTestOnlyThumbInteractive(localOffset);
+  }
+  /// Returns true if the provided [Offset] is located over the thumb of the
+  /// [RawScrollbar].
+  @protected
+  bool isPointerOverThumb(Offset position) {
+    if (_scrollbarPainterKey.currentContext == null) {
+      return false;
+    }
+    final Offset localOffset = _getLocalOffset(_scrollbarPainterKey, position);
+    return scrollbarPainter.hitTestOnlyThumbInteractive(localOffset);
+  }
+  /// Returns true if the provided [Offset] is located over the track or thumb
+  /// of the [RawScrollbar].
+  @protected
+  bool isPointerOverScrollbar(Offset position) {
+    if (_scrollbarPainterKey.currentContext == null) {
+      return false;
+    }
+    final Offset localOffset = _getLocalOffset(_scrollbarPainterKey, position);
+    return scrollbarPainter.hitTestInteractive(localOffset);
+  }
+
+  /// Cancels the fade out animation so the scrollbar will remain visible for
+  /// interaction.
+  ///
+  /// Can be overridden by subclasses to respond to a [PointerHoverEvent].
+  ///
+  /// Helper methods [isPointerOverScrollbar], [isPointerOverThumb], and
+  /// [isPointerOverTrack] can be used to determine the location of the pointer
+  /// relative to the painter scrollbar elements.
+  @protected
+  @mustCallSuper
+  void handleHover(PointerHoverEvent event) {
+    // Check if the position of the pointer falls over the painted scrollbar
+    if (isPointerOverScrollbar(event.position)) {
+      _hoverIsActive = true;
+      _fadeoutTimer?.cancel();
+    } else if (_hoverIsActive) {
+      // Pointer is not over painted scrollbar.
+      _hoverIsActive = false;
+      _maybeStartFadeoutTimer();
+    }
+  }
+
+  /// Initiates the fade out animation.
+  ///
+  /// Can be overridden by subclasses to respond to a [PointerExitEvent].
+  @protected
+  @mustCallSuper
+  void handleHoverExit(PointerExitEvent event) {
+    _hoverIsActive = false;
+    _maybeStartFadeoutTimer();
+  }
+
+  @override
+  void dispose() {
+    _fadeoutAnimationController.dispose();
+    _fadeoutTimer?.cancel();
+    scrollbarPainter.dispose();
+    super.dispose();
+  }
+
+
+  @override
+  Widget build(BuildContext context) {
+    updateScrollbarPainter();
+    return NotificationListener<ScrollNotification>(
+      onNotification: _handleScrollNotification,
+      child: RepaintBoundary(
+        child: RawGestureDetector(
+          gestures: _gestures,
+          child: MouseRegion(
+            onExit: handleHoverExit,
+            onHover: handleHover,
+            child: CustomPaint(
+              key: _scrollbarPainterKey,
+              foregroundPainter: scrollbarPainter,
+              child: RepaintBoundary(child: widget.child),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+// A long press gesture detector that only responds to events on the scrollbar's
+// thumb and ignores everything else.
+class _ThumbPressGestureRecognizer extends LongPressGestureRecognizer {
+  _ThumbPressGestureRecognizer({
+    double? postAcceptSlopTolerance,
+    PointerDeviceKind? kind,
+    required Object debugOwner,
+    required GlobalKey customPaintKey,
+    required Duration pressDuration,
+  }) : _customPaintKey = customPaintKey,
+       super(
+         postAcceptSlopTolerance: postAcceptSlopTolerance,
+         kind: kind,
+         debugOwner: debugOwner,
+         duration: pressDuration,
+       );
+
+  final GlobalKey _customPaintKey;
+
+  @override
+  bool isPointerAllowed(PointerDownEvent event) {
+    if (!_hitTestInteractive(_customPaintKey, event.position)) {
+      return false;
+    }
+    return super.isPointerAllowed(event);
+  }
+
+  bool _hitTestInteractive(GlobalKey customPaintKey, Offset offset) {
+    if (customPaintKey.currentContext == null) {
+      return false;
+    }
+    final CustomPaint customPaint = customPaintKey.currentContext!.widget as CustomPaint;
+    final ScrollbarPainter painter = customPaint.foregroundPainter! as ScrollbarPainter;
+    final Offset localOffset = _getLocalOffset(customPaintKey, offset);
+    return painter.hitTestOnlyThumbInteractive(localOffset);
+  }
+}
+
+// A tap gesture detector that only responds to events on the scrollbar's
+// track and ignores everything else, including the thumb.
+class _TrackTapGestureRecognizer extends TapGestureRecognizer {
+  _TrackTapGestureRecognizer({
+    required Object debugOwner,
+    required GlobalKey customPaintKey,
+  }) : _customPaintKey = customPaintKey,
+       super(debugOwner: debugOwner);
+
+  final GlobalKey _customPaintKey;
+
+  @override
+  bool isPointerAllowed(PointerDownEvent event) {
+    if (!_hitTestInteractive(_customPaintKey, event.position)) {
+      return false;
+    }
+    return super.isPointerAllowed(event);
+  }
+
+  bool _hitTestInteractive(GlobalKey customPaintKey, Offset offset) {
+    if (customPaintKey.currentContext == null) {
+      return false;
+    }
+    final CustomPaint customPaint = customPaintKey.currentContext!.widget as CustomPaint;
+    final ScrollbarPainter painter = customPaint.foregroundPainter! as ScrollbarPainter;
+    final Offset localOffset = _getLocalOffset(customPaintKey, offset);
+    // We only receive track taps that are not on the thumb.
+    return painter.hitTestInteractive(localOffset) && !painter.hitTestOnlyThumbInteractive(localOffset);
+  }
+}
+
+Offset _getLocalOffset(GlobalKey scrollbarPainterKey, Offset position) {
+  final RenderBox renderBox = scrollbarPainterKey.currentContext!.findRenderObject()! as RenderBox;
+  return renderBox.globalToLocal(position);
+}
diff --git a/lib/src/widgets/semantics_debugger.dart b/lib/src/widgets/semantics_debugger.dart
new file mode 100644
index 0000000..2d7d1a0
--- /dev/null
+++ b/lib/src/widgets/semantics_debugger.dart
@@ -0,0 +1,378 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+import 'package:flute/ui.dart' show SemanticsFlag;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'binding.dart';
+import 'framework.dart';
+import 'gesture_detector.dart';
+
+/// A widget that visualizes the semantics for the child.
+///
+/// This widget is useful for understand how an app presents itself to
+/// accessibility technology.
+class SemanticsDebugger extends StatefulWidget {
+  /// Creates a widget that visualizes the semantics for the child.
+  ///
+  /// The [child] argument must not be null.
+  ///
+  /// [labelStyle] dictates the [TextStyle] used for the semantics labels.
+  const SemanticsDebugger({
+    Key? key,
+    required this.child,
+    this.labelStyle = const TextStyle(
+      color: Color(0xFF000000),
+      fontSize: 10.0,
+      height: 0.8,
+    ),
+  }) : assert(child != null),
+       assert(labelStyle != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The [TextStyle] to use when rendering semantics labels.
+  final TextStyle labelStyle;
+
+  @override
+  _SemanticsDebuggerState createState() => _SemanticsDebuggerState();
+}
+
+class _SemanticsDebuggerState extends State<SemanticsDebugger> with WidgetsBindingObserver {
+  late _SemanticsClient _client;
+
+  @override
+  void initState() {
+    super.initState();
+    // TODO(abarth): We shouldn't reach out to the WidgetsBinding.instance
+    // static here because we might not be in a tree that's attached to that
+    // binding. Instead, we should find a way to get to the PipelineOwner from
+    // the BuildContext.
+    _client = _SemanticsClient(WidgetsBinding.instance!.pipelineOwner)
+      ..addListener(_update);
+    WidgetsBinding.instance!.addObserver(this);
+  }
+
+  @override
+  void dispose() {
+    _client
+      ..removeListener(_update)
+      ..dispose();
+    WidgetsBinding.instance!.removeObserver(this);
+    super.dispose();
+  }
+
+  @override
+  void didChangeMetrics() {
+    setState(() {
+      // The root transform may have changed, we have to repaint.
+    });
+  }
+
+  void _update() {
+    SchedulerBinding.instance!.addPostFrameCallback((Duration timeStamp) {
+      // Semantic information are only available at the end of a frame and our
+      // only chance to paint them on the screen is the next frame. To achieve
+      // this, we call setState() in a post-frame callback.
+      if (mounted) {
+        // If we got disposed this frame, we will still get an update,
+        // because the inactive list is flushed after the semantics updates
+        // are transmitted to the semantics clients.
+        setState(() {
+          // The generation of the _SemanticsDebuggerListener has changed.
+        });
+      }
+    });
+  }
+
+  Offset? _lastPointerDownLocation;
+  void _handlePointerDown(PointerDownEvent event) {
+    setState(() {
+      _lastPointerDownLocation = event.position * WidgetsBinding.instance!.window.devicePixelRatio;
+    });
+    // TODO(ianh): Use a gesture recognizer so that we can reset the
+    // _lastPointerDownLocation when none of the other gesture recognizers win.
+  }
+
+  void _handleTap() {
+    assert(_lastPointerDownLocation != null);
+    _performAction(_lastPointerDownLocation!, SemanticsAction.tap);
+    setState(() {
+      _lastPointerDownLocation = null;
+    });
+  }
+
+  void _handleLongPress() {
+    assert(_lastPointerDownLocation != null);
+    _performAction(_lastPointerDownLocation!, SemanticsAction.longPress);
+    setState(() {
+      _lastPointerDownLocation = null;
+    });
+  }
+
+  void _handlePanEnd(DragEndDetails details) {
+    final double vx = details.velocity.pixelsPerSecond.dx;
+    final double vy = details.velocity.pixelsPerSecond.dy;
+    if (vx.abs() == vy.abs())
+      return;
+    if (vx.abs() > vy.abs()) {
+      if (vx.sign < 0) {
+        _performAction(_lastPointerDownLocation!, SemanticsAction.decrease);
+        _performAction(_lastPointerDownLocation!, SemanticsAction.scrollLeft);
+      } else {
+        _performAction(_lastPointerDownLocation!, SemanticsAction.increase);
+        _performAction(_lastPointerDownLocation!, SemanticsAction.scrollRight);
+      }
+    } else {
+      if (vy.sign < 0)
+        _performAction(_lastPointerDownLocation!, SemanticsAction.scrollUp);
+      else
+        _performAction(_lastPointerDownLocation!, SemanticsAction.scrollDown);
+    }
+    setState(() {
+      _lastPointerDownLocation = null;
+    });
+  }
+
+  void _performAction(Offset position, SemanticsAction action) {
+    _pipelineOwner.semanticsOwner?.performActionAt(position, action);
+  }
+
+  // TODO(abarth): This shouldn't be a static. We should get the pipeline owner
+  // from [context] somehow.
+  PipelineOwner get _pipelineOwner => WidgetsBinding.instance!.pipelineOwner;
+
+  @override
+  Widget build(BuildContext context) {
+    return CustomPaint(
+      foregroundPainter: _SemanticsDebuggerPainter(
+        _pipelineOwner,
+        _client.generation,
+        _lastPointerDownLocation, // in physical pixels
+        WidgetsBinding.instance!.window.devicePixelRatio,
+        widget.labelStyle,
+      ),
+      child: GestureDetector(
+        behavior: HitTestBehavior.opaque,
+        onTap: _handleTap,
+        onLongPress: _handleLongPress,
+        onPanEnd: _handlePanEnd,
+        excludeFromSemantics: true, // otherwise if you don't hit anything, we end up receiving it, which causes an infinite loop...
+        child: Listener(
+          onPointerDown: _handlePointerDown,
+          behavior: HitTestBehavior.opaque,
+          child: IgnorePointer(
+            ignoringSemantics: false,
+            child: widget.child,
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+class _SemanticsClient extends ChangeNotifier {
+  _SemanticsClient(PipelineOwner pipelineOwner) {
+    _semanticsHandle = pipelineOwner.ensureSemantics(
+      listener: _didUpdateSemantics
+    );
+  }
+
+  SemanticsHandle? _semanticsHandle;
+
+  @override
+  void dispose() {
+    _semanticsHandle!.dispose();
+    _semanticsHandle = null;
+    super.dispose();
+  }
+
+  int generation = 0;
+
+  void _didUpdateSemantics() {
+    generation += 1;
+    notifyListeners();
+  }
+}
+
+class _SemanticsDebuggerPainter extends CustomPainter {
+  const _SemanticsDebuggerPainter(this.owner, this.generation, this.pointerPosition, this.devicePixelRatio, this.labelStyle);
+
+  final PipelineOwner owner;
+  final int generation;
+  final Offset? pointerPosition; // in physical pixels
+  final double devicePixelRatio;
+  final TextStyle labelStyle;
+
+  SemanticsNode? get _rootSemanticsNode {
+    return owner.semanticsOwner?.rootSemanticsNode;
+  }
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final SemanticsNode? rootNode = _rootSemanticsNode;
+    canvas.save();
+    canvas.scale(1.0 / devicePixelRatio, 1.0 / devicePixelRatio);
+    if (rootNode != null)
+      _paint(canvas, rootNode, _findDepth(rootNode));
+    if (pointerPosition != null) {
+      final Paint paint = Paint();
+      paint.color = const Color(0x7F0090FF);
+      canvas.drawCircle(pointerPosition!, 10.0 * devicePixelRatio, paint);
+    }
+    canvas.restore();
+  }
+
+  @override
+  bool shouldRepaint(_SemanticsDebuggerPainter oldDelegate) {
+    return owner != oldDelegate.owner
+        || generation != oldDelegate.generation
+        || pointerPosition != oldDelegate.pointerPosition;
+  }
+
+  @visibleForTesting
+  String getMessage(SemanticsNode node) {
+    final SemanticsData data = node.getSemanticsData();
+    final List<String> annotations = <String>[];
+
+    bool wantsTap = false;
+    if (data.hasFlag(SemanticsFlag.hasCheckedState)) {
+      annotations.add(data.hasFlag(SemanticsFlag.isChecked) ? 'checked' : 'unchecked');
+      wantsTap = true;
+    }
+    if (data.hasFlag(SemanticsFlag.isTextField)) {
+      annotations.add('textfield');
+      wantsTap = true;
+    }
+
+    if (data.hasAction(SemanticsAction.tap)) {
+      if (!wantsTap)
+        annotations.add('button');
+    } else {
+      if (wantsTap)
+        annotations.add('disabled');
+    }
+
+    if (data.hasAction(SemanticsAction.longPress))
+      annotations.add('long-pressable');
+
+    final bool isScrollable = data.hasAction(SemanticsAction.scrollLeft)
+        || data.hasAction(SemanticsAction.scrollRight)
+        || data.hasAction(SemanticsAction.scrollUp)
+        || data.hasAction(SemanticsAction.scrollDown);
+
+    final bool isAdjustable = data.hasAction(SemanticsAction.increase)
+        || data.hasAction(SemanticsAction.decrease);
+
+    if (isScrollable)
+      annotations.add('scrollable');
+
+    if (isAdjustable)
+      annotations.add('adjustable');
+
+    assert(data.label != null);
+    final String message;
+    if (data.label.isEmpty) {
+      message = annotations.join('; ');
+    } else {
+      final String label;
+      if (data.textDirection == null) {
+        label = '${Unicode.FSI}${data.label}${Unicode.PDI}';
+        annotations.insert(0, 'MISSING TEXT DIRECTION');
+      } else {
+        switch (data.textDirection!) {
+          case TextDirection.rtl:
+            label = '${Unicode.RLI}${data.label}${Unicode.PDF}';
+            break;
+          case TextDirection.ltr:
+            label = data.label;
+            break;
+        }
+      }
+      if (annotations.isEmpty) {
+        message = label;
+      } else {
+        message = '$label (${annotations.join('; ')})';
+      }
+    }
+
+    return message.trim();
+  }
+
+  void _paintMessage(Canvas canvas, SemanticsNode node) {
+    final String message = getMessage(node);
+    if (message.isEmpty)
+      return;
+    final Rect rect = node.rect;
+    canvas.save();
+    canvas.clipRect(rect);
+    final TextPainter textPainter = TextPainter()
+      ..text = TextSpan(
+        style: labelStyle,
+        text: message,
+      )
+      ..textDirection = TextDirection.ltr // _getMessage always returns LTR text, even if node.label is RTL
+      ..textAlign = TextAlign.center
+      ..layout(maxWidth: rect.width);
+
+    textPainter.paint(canvas, Alignment.center.inscribe(textPainter.size, rect).topLeft);
+    canvas.restore();
+  }
+
+  int _findDepth(SemanticsNode node) {
+    if (!node.hasChildren || node.mergeAllDescendantsIntoThisNode)
+      return 1;
+    int childrenDepth = 0;
+    node.visitChildren((SemanticsNode child) {
+      childrenDepth = math.max(childrenDepth, _findDepth(child));
+      return true;
+    });
+    return childrenDepth + 1;
+  }
+
+  void _paint(Canvas canvas, SemanticsNode node, int rank) {
+    canvas.save();
+    if (node.transform != null)
+      canvas.transform(node.transform!.storage);
+    final Rect rect = node.rect;
+    if (!rect.isEmpty) {
+      final Color lineColor = Color(0xFF000000 + math.Random(node.id).nextInt(0xFFFFFF));
+      final Rect innerRect = rect.deflate(rank * 1.0);
+      if (innerRect.isEmpty) {
+        final Paint fill = Paint()
+          ..color = lineColor
+          ..style = PaintingStyle.fill;
+        canvas.drawRect(rect, fill);
+      } else {
+        final Paint fill = Paint()
+          ..color = const Color(0xFFFFFFFF)
+          ..style = PaintingStyle.fill;
+        canvas.drawRect(rect, fill);
+        final Paint line = Paint()
+          ..strokeWidth = rank * 2.0
+          ..color = lineColor
+          ..style = PaintingStyle.stroke;
+        canvas.drawRect(innerRect, line);
+      }
+      _paintMessage(canvas, node);
+    }
+    if (!node.mergeAllDescendantsIntoThisNode) {
+      final int childRank = rank - 1;
+      node.visitChildren((SemanticsNode child) {
+        _paint(canvas, child, childRank);
+        return true;
+      });
+    }
+    canvas.restore();
+  }
+}
diff --git a/lib/src/widgets/shortcuts.dart b/lib/src/widgets/shortcuts.dart
new file mode 100644
index 0000000..e8413b2
--- /dev/null
+++ b/lib/src/widgets/shortcuts.dart
@@ -0,0 +1,621 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/services.dart';
+
+import 'actions.dart';
+import 'focus_manager.dart';
+import 'focus_scope.dart';
+import 'framework.dart';
+import 'inherited_notifier.dart';
+
+/// A set of [KeyboardKey]s that can be used as the keys in a [Map].
+///
+/// A key set contains the keys that are down simultaneously to represent a
+/// shortcut.
+///
+/// This is a thin wrapper around a [Set], but changes the equality comparison
+/// from an identity comparison to a contents comparison so that non-identical
+/// sets with the same keys in them will compare as equal.
+///
+/// See also:
+///
+///  * [ShortcutManager], which uses [LogicalKeySet] (a [KeySet] subclass) to
+///    define its key map.
+class KeySet<T extends KeyboardKey> {
+  /// A constructor for making a [KeySet] of up to four keys.
+  ///
+  /// If you need a set of more than four keys, use [KeySet.fromSet].
+  ///
+  /// The `key1` parameter must not be null. The same [KeyboardKey] may
+  /// not be appear more than once in the set.
+  KeySet(
+    T key1, [
+    T? key2,
+    T? key3,
+    T? key4,
+  ])  : assert(key1 != null),
+        _keys = HashSet<T>()..add(key1) {
+    int count = 1;
+    if (key2 != null) {
+      _keys.add(key2);
+      assert(() {
+        count++;
+        return true;
+      }());
+    }
+    if (key3 != null) {
+      _keys.add(key3);
+      assert(() {
+        count++;
+        return true;
+      }());
+    }
+    if (key4 != null) {
+      _keys.add(key4);
+      assert(() {
+        count++;
+        return true;
+      }());
+    }
+    assert(_keys.length == count, 'Two or more provided keys are identical. Each key must appear only once.');
+  }
+
+  /// Create  a [KeySet] from a set of [KeyboardKey]s.
+  ///
+  /// Do not mutate the `keys` set after passing it to this object.
+  ///
+  /// The `keys` set must not be null, contain nulls, or be empty.
+  KeySet.fromSet(Set<T> keys)
+      : assert(keys != null),
+        assert(keys.isNotEmpty),
+        assert(!keys.contains(null)),
+        _keys = HashSet<T>.from(keys);
+
+  /// Returns a copy of the [KeyboardKey]s in this [KeySet].
+  Set<T> get keys => _keys.toSet();
+  final HashSet<T> _keys;
+
+  @override
+  // ignore: avoid_equals_and_hash_code_on_mutable_classes, to remove in NNBD with a late final hashcode
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is KeySet<T>
+        && setEquals<T>(other._keys, _keys);
+  }
+
+  // Arrays used to temporarily store hash codes for sorting.
+  static final List<int> _tempHashStore3 = <int>[0, 0, 0]; // used to sort exactly 3 keys
+  static final List<int> _tempHashStore4 = <int>[0, 0, 0, 0]; // used to sort exactly 4 keys
+
+  // Cached hash code value. Improves [hashCode] performance by 27%-900%,
+  // depending on key set size and read/write ratio.
+  int? _hashCode;
+
+  @override
+  // ignore: avoid_equals_and_hash_code_on_mutable_classes, to remove in NNBD with a late final hashcode
+  int get hashCode {
+    // Return cached hash code if available.
+    if (_hashCode != null) {
+      return _hashCode!;
+    }
+
+    // Compute order-independent hash and cache it.
+    final int length = _keys.length;
+    final Iterator<T> iterator = _keys.iterator;
+
+    // There's always at least one key. Just extract it.
+    iterator.moveNext();
+    final int h1 = iterator.current.hashCode;
+
+    if (length == 1) {
+      // Don't do anything fancy if there's exactly one key.
+      return _hashCode = h1;
+    }
+
+    iterator.moveNext();
+    final int h2 = iterator.current.hashCode;
+    if (length == 2) {
+      // No need to sort if there's two keys, just compare them.
+      return _hashCode = h1 < h2
+        ? hashValues(h1, h2)
+        : hashValues(h2, h1);
+    }
+
+    // Sort key hash codes and feed to hashList to ensure the aggregate
+    // hash code does not depend on the key order.
+    final List<int> sortedHashes = length == 3
+      ? _tempHashStore3
+      : _tempHashStore4;
+    sortedHashes[0] = h1;
+    sortedHashes[1] = h2;
+    iterator.moveNext();
+    sortedHashes[2] = iterator.current.hashCode;
+    if (length == 4) {
+      iterator.moveNext();
+      sortedHashes[3] = iterator.current.hashCode;
+    }
+    sortedHashes.sort();
+    return _hashCode = hashList(sortedHashes);
+  }
+}
+
+/// A set of [LogicalKeyboardKey]s that can be used as the keys in a map.
+///
+/// A key set contains the keys that are down simultaneously to represent a
+/// shortcut.
+///
+/// This is mainly used by [ShortcutManager] to allow the definition of shortcut
+/// mappings.
+///
+/// This is a thin wrapper around a [Set], but changes the equality comparison
+/// from an identity comparison to a contents comparison so that non-identical
+/// sets with the same keys in them will compare as equal.
+class LogicalKeySet extends KeySet<LogicalKeyboardKey> with Diagnosticable {
+  /// A constructor for making a [LogicalKeySet] of up to four keys.
+  ///
+  /// If you need a set of more than four keys, use [LogicalKeySet.fromSet].
+  ///
+  /// The `key1` parameter must not be null. The same [LogicalKeyboardKey] may
+  /// not be appear more than once in the set.
+  LogicalKeySet(
+    LogicalKeyboardKey key1, [
+    LogicalKeyboardKey? key2,
+    LogicalKeyboardKey? key3,
+    LogicalKeyboardKey? key4,
+  ]) : super(key1, key2, key3, key4);
+
+  /// Create  a [LogicalKeySet] from a set of [LogicalKeyboardKey]s.
+  ///
+  /// Do not mutate the `keys` set after passing it to this object.
+  ///
+  /// The `keys` must not be null.
+  LogicalKeySet.fromSet(Set<LogicalKeyboardKey> keys) : super.fromSet(keys);
+
+  static final Set<LogicalKeyboardKey> _modifiers = <LogicalKeyboardKey>{
+    LogicalKeyboardKey.alt,
+    LogicalKeyboardKey.control,
+    LogicalKeyboardKey.meta,
+    LogicalKeyboardKey.shift,
+  };
+
+  /// Returns a description of the key set that is short and readable.
+  ///
+  /// Intended to be used in debug mode for logging purposes.
+  String debugDescribeKeys() {
+    final List<LogicalKeyboardKey> sortedKeys = keys.toList()..sort(
+            (LogicalKeyboardKey a, LogicalKeyboardKey b) {
+          // Put the modifiers first. If it has a synonym, then it's something
+          // like shiftLeft, altRight, etc.
+          final bool aIsModifier = a.synonyms.isNotEmpty || _modifiers.contains(a);
+          final bool bIsModifier = b.synonyms.isNotEmpty || _modifiers.contains(b);
+          if (aIsModifier && !bIsModifier) {
+            return -1;
+          } else if (bIsModifier && !aIsModifier) {
+            return 1;
+          }
+          return a.debugName!.compareTo(b.debugName!);
+        }
+    );
+    return sortedKeys.map<String>((LogicalKeyboardKey key) => key.debugName.toString()).join(' + ');
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Set<LogicalKeyboardKey>>('keys', _keys, description: debugDescribeKeys()));
+  }
+}
+
+/// Diagnostics property which handles formatting a `Map<LogicalKeySet, Intent>`
+/// (the same type as the [Shortcuts.shortcuts] property) so that it is human-readable.
+class ShortcutMapProperty extends DiagnosticsProperty<Map<LogicalKeySet, Intent>> {
+  /// Create a diagnostics property for `Map<LogicalKeySet, Intent>` objects,
+  /// which are the same type as the [Shortcuts.shortcuts] property.
+  ///
+  /// The [showName] and [level] arguments must not be null.
+  ShortcutMapProperty(
+    String name,
+    Map<LogicalKeySet, Intent> value, {
+    bool showName = true,
+    Object defaultValue = kNoDefaultValue,
+    DiagnosticLevel level = DiagnosticLevel.info,
+    String? description,
+  }) : assert(showName != null),
+       assert(level != null),
+       super(
+         name,
+         value,
+         showName: showName,
+         defaultValue: defaultValue,
+         level: level,
+         description: description,
+       );
+
+  @override
+  Map<LogicalKeySet, Intent> get value => super.value!;
+
+  @override
+  String valueToString({ TextTreeConfiguration? parentConfiguration }) {
+    return '{${value.keys.map<String>((LogicalKeySet keySet) => '{${keySet.debugDescribeKeys()}}: ${value[keySet]}').join(', ')}}';
+  }
+}
+
+/// A manager of keyboard shortcut bindings.
+///
+/// A [ShortcutManager] is obtained by calling [Shortcuts.of] on the context of
+/// the widget that you want to find a manager for.
+class ShortcutManager extends ChangeNotifier with Diagnosticable {
+  /// Constructs a [ShortcutManager].
+  ///
+  /// The [shortcuts] argument must not  be null.
+  ShortcutManager({
+    Map<LogicalKeySet, Intent> shortcuts = const <LogicalKeySet, Intent>{},
+    this.modal = false,
+  })  : assert(shortcuts != null),
+        _shortcuts = shortcuts;
+
+  /// True if the [ShortcutManager] should not pass on keys that it doesn't
+  /// handle to any key-handling widgets that are ancestors to this one.
+  ///
+  /// Setting [modal] to true will prevent any key event given to this manager
+  /// from being given to any ancestor managers, even if that key doesn't appear
+  /// in the [shortcuts] map.
+  ///
+  /// The net effect of setting `modal` to true is to return
+  /// [KeyEventResult.skipRemainingHandlers] from [handleKeypress] if it does not
+  /// exist in the shortcut map, instead of returning [KeyEventResult.ignored].
+  final bool modal;
+
+  /// Returns the shortcut map.
+  ///
+  /// When the map is changed, listeners to this manager will be notified.
+  ///
+  /// The returned map should not be modified.
+  Map<LogicalKeySet, Intent> get shortcuts => _shortcuts;
+  Map<LogicalKeySet, Intent> _shortcuts;
+  set shortcuts(Map<LogicalKeySet, Intent> value) {
+    assert(value != null);
+    if (!mapEquals<LogicalKeySet, Intent>(_shortcuts, value)) {
+      _shortcuts = value;
+      notifyListeners();
+    }
+  }
+
+  /// Returns the [Intent], if any, that matches the current set of pressed
+  /// keys.
+  ///
+  /// Returns null if no intent matches the current set of pressed keys.
+  ///
+  /// Defaults to a set derived from [RawKeyboard.keysPressed] if `keysPressed` is
+  /// not supplied.
+  Intent? _find({ LogicalKeySet? keysPressed }) {
+    if (keysPressed == null && RawKeyboard.instance.keysPressed.isEmpty) {
+      return null;
+    }
+    keysPressed ??= LogicalKeySet.fromSet(RawKeyboard.instance.keysPressed);
+    Intent? matchedIntent = _shortcuts[keysPressed];
+    if (matchedIntent == null) {
+      // If there's not a more specific match, We also look for any keys that
+      // have synonyms in the map.  This is for things like left and right shift
+      // keys mapping to just the "shift" pseudo-key.
+      final Set<LogicalKeyboardKey> pseudoKeys = <LogicalKeyboardKey>{};
+      for (final KeyboardKey setKey in keysPressed.keys) {
+        if (setKey is LogicalKeyboardKey) {
+          final Set<LogicalKeyboardKey> synonyms = setKey.synonyms;
+          if (synonyms.isNotEmpty) {
+            // There currently aren't any synonyms that match more than one key.
+            assert(synonyms.length == 1, 'Unexpectedly encountered a key synonym with more than one key.');
+            pseudoKeys.add(synonyms.first);
+          } else {
+            pseudoKeys.add(setKey);
+          }
+        }
+      }
+      matchedIntent = _shortcuts[LogicalKeySet.fromSet(pseudoKeys)];
+    }
+    return matchedIntent;
+  }
+
+  /// Handles a key press `event` in the given `context`.
+  ///
+  /// The optional `keysPressed` argument is used as the set of currently
+  /// pressed keys. Defaults to a set derived from [RawKeyboard.keysPressed] if
+  /// `keysPressed` is not supplied.
+  ///
+  /// If a key mapping is found, then the associated action will be invoked
+  /// using the [Intent] that the `keysPressed` maps to, and the currently
+  /// focused widget's context (from [FocusManager.primaryFocus]).
+  ///
+  /// Returns a [KeyEventResult.handled] if an action was invoked, otherwise a
+  /// [KeyEventResult.skipRemainingHandlers] if [modal] is true, or if it maps to a
+  /// [DoNothingAction] with [DoNothingAction.consumesKey] set to false, and
+  /// in all other cases returns [KeyEventResult.ignored].
+  ///
+  /// In order for an action to be invoked (and [KeyEventResult.handled]
+  /// returned), a pressed [KeySet] must be mapped to an [Intent], the [Intent]
+  /// must be mapped to an [Action], and the [Action] must be enabled.
+  @protected
+  KeyEventResult handleKeypress(
+    BuildContext context,
+    RawKeyEvent event, {
+    LogicalKeySet? keysPressed,
+  }) {
+    if (event is! RawKeyDownEvent) {
+      return KeyEventResult.ignored;
+    }
+    assert(context != null);
+    assert(keysPressed != null || RawKeyboard.instance.keysPressed.isNotEmpty,
+      'Received a key down event when no keys are in keysPressed. '
+      "This state can occur if the key event being sent doesn't properly "
+      'set its modifier flags. This was the event: $event and its data: '
+      '${event.data}');
+    final Intent? matchedIntent = _find(keysPressed: keysPressed);
+    if (matchedIntent != null) {
+      final BuildContext primaryContext = primaryFocus!.context!;
+      assert (primaryContext != null);
+      final Action<Intent>? action = Actions.maybeFind<Intent>(
+        primaryContext,
+        intent: matchedIntent,
+      );
+      if (action != null && action.isEnabled(matchedIntent)) {
+        Actions.of(primaryContext).invokeAction(action, matchedIntent, primaryContext);
+        return action.consumesKey(matchedIntent)
+            ? KeyEventResult.handled
+            : KeyEventResult.skipRemainingHandlers;
+      }
+    }
+    return modal ? KeyEventResult.skipRemainingHandlers : KeyEventResult.ignored;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Map<LogicalKeySet, Intent>>('shortcuts', _shortcuts));
+    properties.add(FlagProperty('modal', value: modal, ifTrue: 'modal', defaultValue: false));
+  }
+}
+
+/// A widget that establishes a [ShortcutManager] to be used by its descendants
+/// when invoking an [Action] via a keyboard key combination that maps to an
+/// [Intent].
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+///
+/// Here, we will use a [Shortcuts] and [Actions] widget to add and remove from a counter.
+/// This can be done by creating a child widget that is focused and pressing the logical key
+/// sets that have been defined in [Shortcuts] and defining the actions that each key set
+/// performs.
+///
+/// ```dart imports
+/// import 'package:flute/services.dart';
+/// ```
+///
+/// ```dart preamble
+/// class Increment extends Intent {}
+///
+/// class Decrement extends Intent {}
+/// ```
+///
+/// ```dart
+/// int count = 0;
+///
+/// Widget build(BuildContext context) {
+///   return Shortcuts(
+///     shortcuts: <LogicalKeySet, Intent> {
+///       LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.keyK): Increment(),
+///       LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.keyL): Decrement(),
+///     },
+///     child: Actions(
+///       actions: <Type, Action<Intent>> {
+///         Increment: CallbackAction<Increment>(
+///           onInvoke: (Increment intent) => setState(() { count = count + 1; }),
+///         ),
+///         Decrement: CallbackAction<Decrement>(
+///           onInvoke: (Decrement intent) => setState(() { count = count - 1; }),
+///         ),
+///       },
+///       child: Focus(
+///         autofocus:true,
+///         child: Column(
+///           mainAxisAlignment: MainAxisAlignment.center,
+///           children: <Widget>[
+///             Text('Add: keyboard Shift + "k"'),
+///             Text('Subtract: keyboard Shift + "l"'),
+///             SizedBox(height: 10.0),
+///             ColoredBox(
+///               color: Colors.yellow,
+///               child: Padding(
+///                 padding: EdgeInsets.all(4.0),
+///                 child: Text('count: $count'),
+///               ),
+///             ),
+///           ],
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Intent], a class for containing a description of a user action to be
+///    invoked.
+///  * [Action], a class for defining an invocation of a user action.
+class Shortcuts extends StatefulWidget {
+  /// Creates a const [Shortcuts] widget.
+  ///
+  /// The [child] and [shortcuts] arguments are required and must not be null.
+  const Shortcuts({
+    Key? key,
+    this.manager,
+    required this.shortcuts,
+    required this.child,
+    this.debugLabel,
+  }) : assert(shortcuts != null),
+       assert(child != null),
+       super(key: key);
+
+  /// The [ShortcutManager] that will manage the mapping between key
+  /// combinations and [Action]s.
+  ///
+  /// If not specified, uses a default-constructed [ShortcutManager].
+  ///
+  /// This manager will be given new [shortcuts] to manage whenever the
+  /// [shortcuts] change materially.
+  final ShortcutManager? manager;
+
+  /// {@template flutter.widgets.shortcuts.shortcuts}
+  /// The map of shortcuts that the [ShortcutManager] will be given to manage.
+  ///
+  /// For performance reasons, it is recommended that a pre-built map is passed
+  /// in here (e.g. a final variable from your widget class) instead of defining
+  /// it inline in the build function.
+  /// {@endtemplate}
+  final Map<LogicalKeySet, Intent> shortcuts;
+
+  /// The child widget for this [Shortcuts] widget.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The debug label that is printed for this node when logged.
+  ///
+  /// If this label is set, then it will be displayed instead of the shortcut
+  /// map when logged.
+  ///
+  /// This allows simplifying the diagnostic output to avoid cluttering it
+  /// unnecessarily with the default shortcut map.
+  final String? debugLabel;
+
+  /// Returns the [ActionDispatcher] that most tightly encloses the given
+  /// [BuildContext].
+  ///
+  /// The [context] argument must not be null.
+  ///
+  /// If no [Shortcuts] widget encloses the context given, will assert in debug
+  /// mode and throw an exception in release mode.
+  ///
+  /// See also:
+  ///
+  ///  * [maybeOf], which is similar to this function, but will return null if
+  ///    it doesn't find a [Shortcuts] ancestor.
+  static ShortcutManager of(BuildContext context) {
+    assert(context != null);
+    final _ShortcutsMarker? inherited = context.dependOnInheritedWidgetOfExactType<_ShortcutsMarker>();
+    assert(() {
+      if (inherited == null) {
+        throw FlutterError('Unable to find a $Shortcuts widget in the context.\n'
+            '$Shortcuts.of() was called with a context that does not contain a '
+            '$Shortcuts widget.\n'
+            'No $Shortcuts ancestor could be found starting from the context that was '
+            'passed to $Shortcuts.of().\n'
+            'The context used was:\n'
+            '  $context');
+      }
+      return true;
+    }());
+    return inherited!.manager;
+  }
+
+  /// Returns the [ActionDispatcher] that most tightly encloses the given
+  /// [BuildContext].
+  ///
+  /// The [context] argument must not be null.
+  ///
+  /// If no [Shortcuts] widget encloses the context given, will return null.
+  ///
+  /// See also:
+  ///
+  ///  * [of], which is similar to this function, but returns a non-nullable
+  ///    result, and will throw an exception if it doesn't find a [Shortcuts]
+  ///    ancestor.
+  static ShortcutManager? maybeOf(BuildContext context) {
+    assert(context != null);
+    final _ShortcutsMarker? inherited = context.dependOnInheritedWidgetOfExactType<_ShortcutsMarker>();
+    return inherited?.manager;
+  }
+
+  @override
+  _ShortcutsState createState() => _ShortcutsState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ShortcutManager>('manager', manager, defaultValue: null));
+    properties.add(ShortcutMapProperty('shortcuts', shortcuts, description: debugLabel?.isNotEmpty ?? false ? debugLabel : null));
+  }
+}
+
+class _ShortcutsState extends State<Shortcuts> {
+  ShortcutManager? _internalManager;
+  ShortcutManager get manager => widget.manager ?? _internalManager!;
+
+  @override
+  void dispose() {
+    _internalManager?.dispose();
+    super.dispose();
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    if (widget.manager == null) {
+      _internalManager = ShortcutManager();
+    }
+    manager.shortcuts = widget.shortcuts;
+  }
+
+  @override
+  void didUpdateWidget(Shortcuts oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.manager != oldWidget.manager) {
+      if (widget.manager != null) {
+        _internalManager?.dispose();
+        _internalManager = null;
+      } else {
+        _internalManager ??= ShortcutManager();
+      }
+    }
+    manager.shortcuts = widget.shortcuts;
+  }
+
+  KeyEventResult _handleOnKey(FocusNode node, RawKeyEvent event) {
+    if (node.context == null) {
+      return KeyEventResult.ignored;
+    }
+    return manager.handleKeypress(node.context!, event);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Focus(
+      debugLabel: '$Shortcuts',
+      canRequestFocus: false,
+      onKey: _handleOnKey,
+      child: _ShortcutsMarker(
+        manager: manager,
+        child: widget.child,
+      ),
+    );
+  }
+}
+
+class _ShortcutsMarker extends InheritedNotifier<ShortcutManager> {
+  const _ShortcutsMarker({
+    required ShortcutManager manager,
+    required Widget child,
+  })  : assert(manager != null),
+        assert(child != null),
+        super(notifier: manager, child: child);
+
+  ShortcutManager get manager => super.notifier!;
+}
diff --git a/lib/src/widgets/single_child_scroll_view.dart b/lib/src/widgets/single_child_scroll_view.dart
new file mode 100644
index 0000000..2efe3e2
--- /dev/null
+++ b/lib/src/widgets/single_child_scroll_view.dart
@@ -0,0 +1,734 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/rendering.dart';
+import 'package:flute/gestures.dart' show DragStartBehavior;
+
+import 'basic.dart';
+import 'framework.dart';
+import 'primary_scroll_controller.dart';
+import 'scroll_controller.dart';
+import 'scroll_physics.dart';
+import 'scrollable.dart';
+
+/// A box in which a single widget can be scrolled.
+///
+/// This widget is useful when you have a single box that will normally be
+/// entirely visible, for example a clock face in a time picker, but you need to
+/// make sure it can be scrolled if the container gets too small in one axis
+/// (the scroll direction).
+///
+/// It is also useful if you need to shrink-wrap in both axes (the main
+/// scrolling direction as well as the cross axis), as one might see in a dialog
+/// or pop-up menu. In that case, you might pair the [SingleChildScrollView]
+/// with a [ListBody] child.
+///
+/// When you have a list of children and do not require cross-axis
+/// shrink-wrapping behavior, for example a scrolling list that is always the
+/// width of the screen, consider [ListView], which is vastly more efficient
+/// that a [SingleChildScrollView] containing a [ListBody] or [Column] with
+/// many children.
+///
+/// ## Sample code: Using [SingleChildScrollView] with a [Column]
+///
+/// Sometimes a layout is designed around the flexible properties of a
+/// [Column], but there is the concern that in some cases, there might not
+/// be enough room to see the entire contents. This could be because some
+/// devices have unusually small screens, or because the application can
+/// be used in landscape mode where the aspect ratio isn't what was
+/// originally envisioned, or because the application is being shown in a
+/// small window in split-screen mode. In any case, as a result, it might
+/// make sense to wrap the layout in a [SingleChildScrollView].
+///
+/// Simply doing so, however, usually results in a conflict between the [Column],
+/// which typically tries to grow as big as it can, and the [SingleChildScrollView],
+/// which provides its children with an infinite amount of space.
+///
+/// To resolve this apparent conflict, there are a couple of techniques, as
+/// discussed below. These techniques should only be used when the content is
+/// normally expected to fit on the screen, so that the lazy instantiation of
+/// a sliver-based [ListView] or [CustomScrollView] is not expected to provide
+/// any performance benefit. If the viewport is expected to usually contain
+/// content beyond the dimensions of the screen, then [SingleChildScrollView]
+/// would be very expensive.
+///
+/// ### Centering, spacing, or aligning fixed-height content
+///
+/// If the content has fixed (or intrinsic) dimensions but needs to be spaced out,
+/// centered, or otherwise positioned using the [Flex] layout model of a [Column],
+/// the following technique can be used to provide the [Column] with a minimum
+/// dimension while allowing it to shrink-wrap the contents when there isn't enough
+/// room to apply these spacing or alignment needs.
+///
+/// A [LayoutBuilder] is used to obtain the size of the viewport (implicitly via
+/// the constraints that the [SingleChildScrollView] sees, since viewports
+/// typically grow to fit their maximum height constraint). Then, inside the
+/// scroll view, a [ConstrainedBox] is used to set the minimum height of the
+/// [Column].
+///
+/// The [Column] has no [Expanded] children, so rather than take on the infinite
+/// height from its [BoxConstraints.maxHeight], (the viewport provides no maximum height
+/// constraint), it automatically tries to shrink to fit its children. It cannot
+/// be smaller than its [BoxConstraints.minHeight], though, and It therefore
+/// becomes the bigger of the minimum height provided by the
+/// [ConstrainedBox] and the sum of the heights of the children.
+///
+/// If the children aren't enough to fit that minimum size, the [Column] ends up
+/// with some remaining space to allocate as specified by its
+/// [Column.mainAxisAlignment] argument.
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+/// In this example, the children are spaced out equally, unless there's no more
+/// room, in which case they stack vertically and scroll.
+///
+/// When using this technique, [Expanded] and [Flexible] are not useful, because
+/// in both cases the "available space" is infinite (since this is in a viewport).
+/// The next section describes a technique for providing a maximum height constraint.
+///
+/// ```dart
+///  Widget build(BuildContext context) {
+///    return DefaultTextStyle(
+///      style: Theme.of(context).textTheme.bodyText2,
+///      child: LayoutBuilder(
+///        builder: (BuildContext context, BoxConstraints viewportConstraints) {
+///          return SingleChildScrollView(
+///            child: ConstrainedBox(
+///              constraints: BoxConstraints(
+///                minHeight: viewportConstraints.maxHeight,
+///              ),
+///              child: Column(
+///                mainAxisSize: MainAxisSize.min,
+///                mainAxisAlignment: MainAxisAlignment.spaceAround,
+///                children: <Widget>[
+///                  Container(
+///                    // A fixed-height child.
+///                    color: const Color(0xffeeee00), // Yellow
+///                    height: 120.0,
+///                    alignment: Alignment.center,
+///                    child: const Text('Fixed Height Content'),
+///                  ),
+///                  Container(
+///                    // Another fixed-height child.
+///                    color: const Color(0xff008000), // Green
+///                    height: 120.0,
+///                    alignment: Alignment.center,
+///                    child: const Text('Fixed Height Content'),
+///                  ),
+///                ],
+///              ),
+///            ),
+///          );
+///        },
+///      ),
+///    );
+///  }
+/// ```
+/// {@end-tool}
+///
+/// ### Expanding content to fit the viewport
+///
+/// The following example builds on the previous one. In addition to providing a
+/// minimum dimension for the child [Column], an [IntrinsicHeight] widget is used
+/// to force the column to be exactly as big as its contents. This constraint
+/// combines with the [ConstrainedBox] constraints discussed previously to ensure
+/// that the column becomes either as big as viewport, or as big as the contents,
+/// whichever is biggest.
+///
+/// Both constraints must be used to get the desired effect. If only the
+/// [IntrinsicHeight] was specified, then the column would not grow to fit the
+/// entire viewport when its children were smaller than the whole screen. If only
+/// the size of the viewport was used, then the [Column] would overflow if the
+/// children were bigger than the viewport.
+///
+/// The widget that is to grow to fit the remaining space so provided is wrapped
+/// in an [Expanded] widget.
+///
+/// This technique is quite expensive, as it more or less requires that the contents
+/// of the viewport be laid out twice (once to find their intrinsic dimensions, and
+/// once to actually lay them out). The number of widgets within the column should
+/// therefore be kept small. Alternatively, subsets of the children that have known
+/// dimensions can be wrapped in a [SizedBox] that has tight vertical constraints,
+/// so that the intrinsic sizing algorithm can short-circuit the computation when it
+/// reaches those parts of the subtree.
+///
+/// {@tool dartpad --template=stateless_widget_material_no_null_safety}
+/// In this example, the column becomes either as big as viewport, or as big as
+/// the contents, whichever is biggest.
+///
+/// ```dart
+///  Widget build(BuildContext context) {
+///    return DefaultTextStyle(
+///      style: Theme.of(context).textTheme.bodyText2,
+///      child: LayoutBuilder(
+///        builder: (BuildContext context, BoxConstraints viewportConstraints) {
+///          return SingleChildScrollView(
+///            child: ConstrainedBox(
+///              constraints: BoxConstraints(
+///                minHeight: viewportConstraints.maxHeight,
+///              ),
+///              child: IntrinsicHeight(
+///                child: Column(
+///                  children: <Widget>[
+///                    Container(
+///                      // A fixed-height child.
+///                      color: const Color(0xffeeee00), // Yellow
+///                      height: 120.0,
+///                      alignment: Alignment.center,
+///                      child: const Text('Fixed Height Content'),
+///                    ),
+///                    Expanded(
+///                      // A flexible child that will grow to fit the viewport but
+///                      // still be at least as big as necessary to fit its contents.
+///                      child: Container(
+///                        color: const Color(0xffee0000), // Red
+///                        height: 120.0,
+///                        alignment: Alignment.center,
+///                        child: const Text('Flexible Content'),
+///                      ),
+///                    ),
+///                  ],
+///                ),
+///              ),
+///            ),
+///          );
+///        },
+///      ),
+///    );
+///  }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ListView], which handles multiple children in a scrolling list.
+///  * [GridView], which handles multiple children in a scrolling grid.
+///  * [PageView], for a scrollable that works page by page.
+///  * [Scrollable], which handles arbitrary scrolling effects.
+class SingleChildScrollView extends StatelessWidget {
+  /// Creates a box in which a single widget can be scrolled.
+  const SingleChildScrollView({
+    Key? key,
+    this.scrollDirection = Axis.vertical,
+    this.reverse = false,
+    this.padding,
+    bool? primary,
+    this.physics,
+    this.controller,
+    this.child,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.clipBehavior = Clip.hardEdge,
+    this.restorationId,
+  }) : assert(scrollDirection != null),
+       assert(dragStartBehavior != null),
+       assert(clipBehavior != null),
+       assert(!(controller != null && primary == true),
+          'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. '
+          'You cannot both set primary to true and pass an explicit controller.'
+       ),
+       primary = primary ?? controller == null && identical(scrollDirection, Axis.vertical),
+       super(key: key);
+
+  /// The axis along which the scroll view scrolls.
+  ///
+  /// Defaults to [Axis.vertical].
+  final Axis scrollDirection;
+
+  /// Whether the scroll view scrolls in the reading direction.
+  ///
+  /// For example, if the reading direction is left-to-right and
+  /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from
+  /// left to right when [reverse] is false and from right to left when
+  /// [reverse] is true.
+  ///
+  /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
+  /// scrolls from top to bottom when [reverse] is false and from bottom to top
+  /// when [reverse] is true.
+  ///
+  /// Defaults to false.
+  final bool reverse;
+
+  /// The amount of space by which to inset the child.
+  final EdgeInsetsGeometry? padding;
+
+  /// An object that can be used to control the position to which this scroll
+  /// view is scrolled.
+  ///
+  /// Must be null if [primary] is true.
+  ///
+  /// A [ScrollController] serves several purposes. It can be used to control
+  /// the initial scroll position (see [ScrollController.initialScrollOffset]).
+  /// It can be used to control whether the scroll view should automatically
+  /// save and restore its scroll position in the [PageStorage] (see
+  /// [ScrollController.keepScrollOffset]). It can be used to read the current
+  /// scroll position (see [ScrollController.offset]), or change it (see
+  /// [ScrollController.animateTo]).
+  final ScrollController? controller;
+
+  /// Whether this is the primary scroll view associated with the parent
+  /// [PrimaryScrollController].
+  ///
+  /// When true, the scroll view is used for default [ScrollAction]s. If a
+  /// ScrollAction is not handled by an otherwise focused part of the application,
+  /// the ScrollAction will be evaluated using this scroll view, for example,
+  /// when executing [Shortcuts] key events like page up and down.
+  ///
+  /// On iOS, this identifies the scroll view that will scroll to top in
+  /// response to a tap in the status bar.
+  ///
+  /// Defaults to true when [scrollDirection] is vertical and [controller] is
+  /// not specified.
+  final bool primary;
+
+  /// How the scroll view should respond to user input.
+  ///
+  /// For example, determines how the scroll view continues to animate after the
+  /// user stops dragging the scroll view.
+  ///
+  /// Defaults to matching platform conventions.
+  final ScrollPhysics? physics;
+
+  /// The widget that scrolls.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
+  final DragStartBehavior dragStartBehavior;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge].
+  final Clip clipBehavior;
+
+  /// {@macro flutter.widgets.scrollable.restorationId}
+  final String? restorationId;
+
+  AxisDirection _getDirection(BuildContext context) {
+    return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final AxisDirection axisDirection = _getDirection(context);
+    Widget? contents = child;
+    if (padding != null)
+      contents = Padding(padding: padding!, child: contents);
+    final ScrollController? scrollController = primary
+        ? PrimaryScrollController.of(context)
+        : controller;
+    final Scrollable scrollable = Scrollable(
+      dragStartBehavior: dragStartBehavior,
+      axisDirection: axisDirection,
+      controller: scrollController,
+      physics: physics,
+      restorationId: restorationId,
+      viewportBuilder: (BuildContext context, ViewportOffset offset) {
+        return _SingleChildViewport(
+          axisDirection: axisDirection,
+          offset: offset,
+          child: contents,
+          clipBehavior: clipBehavior,
+        );
+      },
+    );
+    return primary && scrollController != null
+      ? PrimaryScrollController.none(child: scrollable)
+      : scrollable;
+  }
+}
+
+class _SingleChildViewport extends SingleChildRenderObjectWidget {
+  const _SingleChildViewport({
+    Key? key,
+    this.axisDirection = AxisDirection.down,
+    required this.offset,
+    Widget? child,
+    required this.clipBehavior,
+  }) : assert(axisDirection != null),
+       assert(clipBehavior != null),
+       super(key: key, child: child);
+
+  final AxisDirection axisDirection;
+  final ViewportOffset offset;
+  final Clip clipBehavior;
+
+  @override
+  _RenderSingleChildViewport createRenderObject(BuildContext context) {
+    return _RenderSingleChildViewport(
+      axisDirection: axisDirection,
+      offset: offset,
+      clipBehavior: clipBehavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderSingleChildViewport renderObject) {
+    // Order dependency: The offset setter reads the axis direction.
+    renderObject
+      ..axisDirection = axisDirection
+      ..offset = offset
+      ..clipBehavior = clipBehavior;
+  }
+}
+
+class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMixin<RenderBox> implements RenderAbstractViewport {
+  _RenderSingleChildViewport({
+    AxisDirection axisDirection = AxisDirection.down,
+    required ViewportOffset offset,
+    double cacheExtent = RenderAbstractViewport.defaultCacheExtent,
+    RenderBox? child,
+    required Clip clipBehavior,
+  }) : assert(axisDirection != null),
+       assert(offset != null),
+       assert(cacheExtent != null),
+       assert(clipBehavior != null),
+       _axisDirection = axisDirection,
+       _offset = offset,
+       _cacheExtent = cacheExtent,
+       _clipBehavior = clipBehavior {
+    this.child = child;
+  }
+
+  AxisDirection get axisDirection => _axisDirection;
+  AxisDirection _axisDirection;
+  set axisDirection(AxisDirection value) {
+    assert(value != null);
+    if (value == _axisDirection)
+      return;
+    _axisDirection = value;
+    markNeedsLayout();
+  }
+
+  Axis get axis => axisDirectionToAxis(axisDirection);
+
+  ViewportOffset get offset => _offset;
+  ViewportOffset _offset;
+  set offset(ViewportOffset value) {
+    assert(value != null);
+    if (value == _offset)
+      return;
+    if (attached)
+      _offset.removeListener(_hasScrolled);
+    _offset = value;
+    if (attached)
+      _offset.addListener(_hasScrolled);
+    markNeedsLayout();
+  }
+
+  /// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
+  double get cacheExtent => _cacheExtent;
+  double _cacheExtent;
+  set cacheExtent(double value) {
+    assert(value != null);
+    if (value == _cacheExtent)
+      return;
+    _cacheExtent = value;
+    markNeedsLayout();
+  }
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.none], and must not be null.
+  Clip get clipBehavior => _clipBehavior;
+  Clip _clipBehavior = Clip.none;
+  set clipBehavior(Clip value) {
+    assert(value != null);
+    if (value != _clipBehavior) {
+      _clipBehavior = value;
+      markNeedsPaint();
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  void _hasScrolled() {
+    markNeedsPaint();
+    markNeedsSemanticsUpdate();
+  }
+
+  @override
+  void setupParentData(RenderObject child) {
+    // We don't actually use the offset argument in BoxParentData, so let's
+    // avoid allocating it at all.
+    if (child.parentData is! ParentData)
+      child.parentData = ParentData();
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    _offset.addListener(_hasScrolled);
+  }
+
+  @override
+  void detach() {
+    _offset.removeListener(_hasScrolled);
+    super.detach();
+  }
+
+  @override
+  bool get isRepaintBoundary => true;
+
+  double get _viewportExtent {
+    assert(hasSize);
+    switch (axis) {
+      case Axis.horizontal:
+        return size.width;
+      case Axis.vertical:
+        return size.height;
+    }
+  }
+
+  double get _minScrollExtent {
+    assert(hasSize);
+    return 0.0;
+  }
+
+  double get _maxScrollExtent {
+    assert(hasSize);
+    if (child == null)
+      return 0.0;
+    switch (axis) {
+      case Axis.horizontal:
+        return math.max(0.0, child!.size.width - size.width);
+      case Axis.vertical:
+        return math.max(0.0, child!.size.height - size.height);
+    }
+  }
+
+  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
+    switch (axis) {
+      case Axis.horizontal:
+        return constraints.heightConstraints();
+      case Axis.vertical:
+        return constraints.widthConstraints();
+    }
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    if (child != null)
+      return child!.getMinIntrinsicWidth(height);
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    if (child != null)
+      return child!.getMaxIntrinsicWidth(height);
+    return 0.0;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    if (child != null)
+      return child!.getMinIntrinsicHeight(width);
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    if (child != null)
+      return child!.getMaxIntrinsicHeight(width);
+    return 0.0;
+  }
+
+  // We don't override computeDistanceToActualBaseline(), because we
+  // want the default behavior (returning null). Otherwise, as you
+  // scroll, it would shift in its parent if the parent was baseline-aligned,
+  // which makes no sense.
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    if (child == null) {
+      return constraints.smallest;
+    }
+    final Size childSize = child!.getDryLayout(_getInnerConstraints(constraints));
+    return constraints.constrain(childSize);
+  }
+
+  @override
+  void performLayout() {
+    final BoxConstraints constraints = this.constraints;
+    if (child == null) {
+      size = constraints.smallest;
+    } else {
+      child!.layout(_getInnerConstraints(constraints), parentUsesSize: true);
+      size = constraints.constrain(child!.size);
+    }
+
+    offset.applyViewportDimension(_viewportExtent);
+    offset.applyContentDimensions(_minScrollExtent, _maxScrollExtent);
+  }
+
+  Offset get _paintOffset => _paintOffsetForPosition(offset.pixels);
+
+  Offset _paintOffsetForPosition(double position) {
+    assert(axisDirection != null);
+    switch (axisDirection) {
+      case AxisDirection.up:
+        return Offset(0.0, position - child!.size.height + size.height);
+      case AxisDirection.down:
+        return Offset(0.0, -position);
+      case AxisDirection.left:
+        return Offset(position - child!.size.width + size.width, 0.0);
+      case AxisDirection.right:
+        return Offset(-position, 0.0);
+    }
+  }
+
+  bool _shouldClipAtPaintOffset(Offset paintOffset) {
+    assert(child != null);
+    return paintOffset.dx < 0 ||
+      paintOffset.dy < 0 ||
+      paintOffset.dx + child!.size.width > size.width ||
+      paintOffset.dy + child!.size.height > size.height;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child != null) {
+      final Offset paintOffset = _paintOffset;
+
+      void paintContents(PaintingContext context, Offset offset) {
+        context.paintChild(child!, offset + paintOffset);
+      }
+
+      if (_shouldClipAtPaintOffset(paintOffset) && clipBehavior != Clip.none) {
+        _clipRectLayer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintContents,
+            clipBehavior: clipBehavior, oldLayer: _clipRectLayer);
+      } else {
+        _clipRectLayer = null;
+        paintContents(context, offset);
+      }
+    }
+  }
+
+  ClipRectLayer? _clipRectLayer;
+
+  @override
+  void applyPaintTransform(RenderBox child, Matrix4 transform) {
+    final Offset paintOffset = _paintOffset;
+    transform.translate(paintOffset.dx, paintOffset.dy);
+  }
+
+  @override
+  Rect? describeApproximatePaintClip(RenderObject? child) {
+    if (child != null && _shouldClipAtPaintOffset(_paintOffset))
+      return Offset.zero & size;
+    return null;
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
+    if (child != null) {
+      return result.addWithPaintOffset(
+        offset: _paintOffset,
+        position: position,
+        hitTest: (BoxHitTestResult result, Offset? transformed) {
+          assert(transformed == position + -_paintOffset);
+          return child!.hitTest(result, position: transformed!);
+        },
+      );
+    }
+    return false;
+  }
+
+  @override
+  RevealedOffset getOffsetToReveal(RenderObject target, double alignment, { Rect? rect }) {
+    rect ??= target.paintBounds;
+    if (target is! RenderBox)
+      return RevealedOffset(offset: offset.pixels, rect: rect);
+
+    final RenderBox targetBox = target;
+    final Matrix4 transform = targetBox.getTransformTo(child);
+    final Rect bounds = MatrixUtils.transformRect(transform, rect);
+    final Size contentSize = child!.size;
+
+    final double leadingScrollOffset;
+    final double targetMainAxisExtent;
+    final double mainAxisExtent;
+
+    assert(axisDirection != null);
+    switch (axisDirection) {
+      case AxisDirection.up:
+        mainAxisExtent = size.height;
+        leadingScrollOffset = contentSize.height - bounds.bottom;
+        targetMainAxisExtent = bounds.height;
+        break;
+      case AxisDirection.right:
+        mainAxisExtent = size.width;
+        leadingScrollOffset = bounds.left;
+        targetMainAxisExtent = bounds.width;
+        break;
+      case AxisDirection.down:
+        mainAxisExtent = size.height;
+        leadingScrollOffset = bounds.top;
+        targetMainAxisExtent = bounds.height;
+        break;
+      case AxisDirection.left:
+        mainAxisExtent = size.width;
+        leadingScrollOffset = contentSize.width - bounds.right;
+        targetMainAxisExtent = bounds.width;
+        break;
+    }
+
+    final double targetOffset = leadingScrollOffset - (mainAxisExtent - targetMainAxisExtent) * alignment;
+    final Rect targetRect = bounds.shift(_paintOffsetForPosition(targetOffset));
+    return RevealedOffset(offset: targetOffset, rect: targetRect);
+  }
+
+  @override
+  void showOnScreen({
+    RenderObject? descendant,
+    Rect? rect,
+    Duration duration = Duration.zero,
+    Curve curve = Curves.ease,
+  }) {
+    if (!offset.allowImplicitScrolling) {
+      return super.showOnScreen(
+        descendant: descendant,
+        rect: rect,
+        duration: duration,
+        curve: curve,
+      );
+    }
+
+    final Rect? newRect = RenderViewportBase.showInViewport(
+      descendant: descendant,
+      viewport: this,
+      offset: offset,
+      rect: rect,
+      duration: duration,
+      curve: curve,
+    );
+    super.showOnScreen(
+      rect: newRect,
+      duration: duration,
+      curve: curve,
+    );
+  }
+
+  @override
+  Rect describeSemanticsClip(RenderObject child) {
+    assert(axis != null);
+    switch (axis) {
+      case Axis.vertical:
+        return Rect.fromLTRB(
+          semanticBounds.left,
+          semanticBounds.top - cacheExtent,
+          semanticBounds.right,
+          semanticBounds.bottom + cacheExtent,
+        );
+      case Axis.horizontal:
+        return Rect.fromLTRB(
+          semanticBounds.left - cacheExtent,
+          semanticBounds.top,
+          semanticBounds.right + cacheExtent,
+          semanticBounds.bottom,
+        );
+    }
+  }
+}
diff --git a/lib/src/widgets/size_changed_layout_notifier.dart b/lib/src/widgets/size_changed_layout_notifier.dart
new file mode 100644
index 0000000..7a367bb
--- /dev/null
+++ b/lib/src/widgets/size_changed_layout_notifier.dart
@@ -0,0 +1,95 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+
+import 'framework.dart';
+import 'notification_listener.dart';
+
+/// Indicates that the size of one of the descendants of the object receiving
+/// this notification has changed, and that therefore any assumptions about that
+/// layout are no longer valid.
+///
+/// For example, sent by the [SizeChangedLayoutNotifier] widget whenever that
+/// widget changes size.
+///
+/// This notification can be used for triggering repaints, but if you use this
+/// notification to trigger rebuilds or relayouts, you'll create a backwards
+/// dependency in the frame pipeline because [SizeChangedLayoutNotification]s
+/// are generated during layout, which is after the build phase and in the
+/// middle of the layout phase. This backwards dependency can lead to visual
+/// corruption or lags.
+///
+/// See [LayoutChangedNotification] for additional discussion of layout
+/// notifications such as this one.
+///
+/// See also:
+///
+///  * [SizeChangedLayoutNotifier], which sends this notification.
+///  * [LayoutChangedNotification], of which this is a subclass.
+class SizeChangedLayoutNotification extends LayoutChangedNotification { }
+
+/// A widget that automatically dispatches a [SizeChangedLayoutNotification]
+/// when the layout dimensions of its child change.
+///
+/// The notification is not sent for the initial layout (since the size doesn't
+/// change in that case, it's just established).
+///
+/// To listen for the notification dispatched by this widget, use a
+/// [NotificationListener<SizeChangedLayoutNotification>].
+///
+/// The [Material] class listens for [LayoutChangedNotification]s, including
+/// [SizeChangedLayoutNotification]s, to repaint [InkResponse] and [InkWell] ink
+/// effects. When a widget is likely to change size, wrapping it in a
+/// [SizeChangedLayoutNotifier] will cause the ink effects to correctly repaint
+/// when the child changes size.
+///
+/// See also:
+///
+///  * [Notification], the base class for notifications that bubble through the
+///    widget tree.
+class SizeChangedLayoutNotifier extends SingleChildRenderObjectWidget {
+  /// Creates a [SizeChangedLayoutNotifier] that dispatches layout changed
+  /// notifications when [child] changes layout size.
+  const SizeChangedLayoutNotifier({
+    Key? key,
+    Widget? child,
+  }) : super(key: key, child: child);
+
+  @override
+  _RenderSizeChangedWithCallback createRenderObject(BuildContext context) {
+    return _RenderSizeChangedWithCallback(
+      onLayoutChangedCallback: () {
+        SizeChangedLayoutNotification().dispatch(context);
+      }
+    );
+  }
+}
+
+class _RenderSizeChangedWithCallback extends RenderProxyBox {
+  _RenderSizeChangedWithCallback({
+    RenderBox? child,
+    required this.onLayoutChangedCallback,
+  }) : assert(onLayoutChangedCallback != null),
+       super(child);
+
+  // There's a 1:1 relationship between the _RenderSizeChangedWithCallback and
+  // the `context` that is captured by the closure created by createRenderObject
+  // above to assign to onLayoutChangedCallback, and thus we know that the
+  // onLayoutChangedCallback will never change nor need to change.
+
+  final VoidCallback onLayoutChangedCallback;
+
+  Size? _oldSize;
+
+  @override
+  void performLayout() {
+    super.performLayout();
+    // Don't send the initial notification, or this will be SizeObserver all
+    // over again!
+    if (_oldSize != null && size != _oldSize)
+      onLayoutChangedCallback();
+    _oldSize = size;
+  }
+}
diff --git a/lib/src/widgets/sliver.dart b/lib/src/widgets/sliver.dart
new file mode 100644
index 0000000..f75304e
--- /dev/null
+++ b/lib/src/widgets/sliver.dart
@@ -0,0 +1,1749 @@
+// Copyright 2014 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.
+
+import 'dart:collection' show SplayTreeMap, HashMap;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+import 'automatic_keep_alive.dart';
+import 'basic.dart';
+import 'framework.dart';
+
+export 'package:flute/rendering.dart' show
+  SliverGridDelegate,
+  SliverGridDelegateWithFixedCrossAxisCount,
+  SliverGridDelegateWithMaxCrossAxisExtent;
+
+// Examples can assume:
+// // @dart = 2.9
+// SliverGridDelegateWithMaxCrossAxisExtent _gridDelegate;
+
+/// A callback which produces a semantic index given a widget and the local index.
+///
+/// Return a null value to prevent a widget from receiving an index.
+///
+/// A semantic index is used to tag child semantic nodes for accessibility
+/// announcements in scroll view.
+///
+/// See also:
+///
+///  * [CustomScrollView], for an explanation of scroll semantics.
+///  * [SliverChildBuilderDelegate], for an explanation of how this is used to
+///    generate indexes.
+typedef SemanticIndexCallback = int? Function(Widget widget, int localIndex);
+
+int _kDefaultSemanticIndexCallback(Widget _, int localIndex) => localIndex;
+
+/// A delegate that supplies children for slivers.
+///
+/// Many slivers lazily construct their box children to avoid creating more
+/// children than are visible through the [Viewport]. Rather than receiving
+/// their children as an explicit [List], they receive their children using a
+/// [SliverChildDelegate].
+///
+/// It's uncommon to subclass [SliverChildDelegate]. Instead, consider using one
+/// of the existing subclasses that provide adaptors to builder callbacks or
+/// explicit child lists.
+///
+/// {@template flutter.widgets.SliverChildDelegate.lifecycle}
+/// ## Child elements' lifecycle
+///
+/// ### Creation
+///
+/// While laying out the list, visible children's elements, states and render
+/// objects will be created lazily based on existing widgets (such as in the
+/// case of [SliverChildListDelegate]) or lazily provided ones (such as in the
+/// case of [SliverChildBuilderDelegate]).
+///
+/// ### Destruction
+///
+/// When a child is scrolled out of view, the associated element subtree, states
+/// and render objects are destroyed. A new child at the same position in the
+/// sliver will be lazily recreated along with new elements, states and render
+/// objects when it is scrolled back.
+///
+/// ### Destruction mitigation
+///
+/// In order to preserve state as child elements are scrolled in and out of
+/// view, the following options are possible:
+///
+///  * Moving the ownership of non-trivial UI-state-driving business logic
+///    out of the sliver child subtree. For instance, if a list contains posts
+///    with their number of upvotes coming from a cached network response, store
+///    the list of posts and upvote number in a data model outside the list. Let
+///    the sliver child UI subtree be easily recreate-able from the
+///    source-of-truth model object. Use [StatefulWidget]s in the child widget
+///    subtree to store instantaneous UI state only.
+///
+///  * Letting [KeepAlive] be the root widget of the sliver child widget subtree
+///    that needs to be preserved. The [KeepAlive] widget marks the child
+///    subtree's top render object child for keepalive. When the associated top
+///    render object is scrolled out of view, the sliver keeps the child's
+///    render object (and by extension, its associated elements and states) in a
+///    cache list instead of destroying them. When scrolled back into view, the
+///    render object is repainted as-is (if it wasn't marked dirty in the
+///    interim).
+///
+///    This only works if the [SliverChildDelegate] subclasses don't wrap the
+///    child widget subtree with other widgets such as [AutomaticKeepAlive] and
+///    [RepaintBoundary] via `addAutomaticKeepAlives` and
+///    `addRepaintBoundaries`.
+///
+///  * Using [AutomaticKeepAlive] widgets (inserted by default in
+///    [SliverChildListDelegate] or [SliverChildListDelegate]).
+///    [AutomaticKeepAlive] allows descendant widgets to control whether the
+///    subtree is actually kept alive or not. This behavior is in contrast with
+///    [KeepAlive], which will unconditionally keep the subtree alive.
+///
+///    As an example, the [EditableText] widget signals its sliver child element
+///    subtree to stay alive while its text field has input focus. If it doesn't
+///    have focus and no other descendants signaled for keepalive via a
+///    [KeepAliveNotification], the sliver child element subtree will be
+///    destroyed when scrolled away.
+///
+///    [AutomaticKeepAlive] descendants typically signal it to be kept alive by
+///    using the [AutomaticKeepAliveClientMixin], then implementing the
+///    [AutomaticKeepAliveClientMixin.wantKeepAlive] getter and calling
+///    [AutomaticKeepAliveClientMixin.updateKeepAlive].
+/// {@endtemplate}
+///
+/// See also:
+///
+///  * [SliverChildBuilderDelegate], which is a delegate that uses a builder
+///    callback to construct the children.
+///  * [SliverChildListDelegate], which is a delegate that has an explicit list
+///    of children.
+abstract class SliverChildDelegate {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const SliverChildDelegate();
+
+  /// Returns the child with the given index.
+  ///
+  /// Should return null if asked to build a widget with a greater
+  /// index than exists. If this returns null, [estimatedChildCount]
+  /// must subsequently return a precise non-null value (which is then
+  /// used to implement [RenderSliverBoxChildManager.childCount]).
+  ///
+  /// Subclasses typically override this function and wrap their children in
+  /// [AutomaticKeepAlive], [IndexedSemantics], and [RepaintBoundary] widgets.
+  ///
+  /// The values returned by this method are cached. To indicate that the
+  /// widgets have changed, a new delegate must be provided, and the new
+  /// delegate's [shouldRebuild] method must return true.
+  Widget? build(BuildContext context, int index);
+
+  /// Returns an estimate of the number of children this delegate will build.
+  ///
+  /// Used to estimate the maximum scroll offset if [estimateMaxScrollOffset]
+  /// returns null.
+  ///
+  /// Return null if there are an unbounded number of children or if it would
+  /// be too difficult to estimate the number of children.
+  ///
+  /// This must return a precise number once [build] has returned null, as it
+  /// used to implement [RenderSliverBoxChildManager.childCount].
+  int? get estimatedChildCount => null;
+
+  /// Returns an estimate of the max scroll extent for all the children.
+  ///
+  /// Subclasses should override this function if they have additional
+  /// information about their max scroll extent.
+  ///
+  /// The default implementation returns null, which causes the caller to
+  /// extrapolate the max scroll offset from the given parameters.
+  double? estimateMaxScrollOffset(
+    int firstIndex,
+    int lastIndex,
+    double leadingScrollOffset,
+    double trailingScrollOffset,
+  ) => null;
+
+  /// Called at the end of layout to indicate that layout is now complete.
+  ///
+  /// The `firstIndex` argument is the index of the first child that was
+  /// included in the current layout. The `lastIndex` argument is the index of
+  /// the last child that was included in the current layout.
+  ///
+  /// Useful for subclasses that which to track which children are included in
+  /// the underlying render tree.
+  void didFinishLayout(int firstIndex, int lastIndex) { }
+
+  /// Called whenever a new instance of the child delegate class is
+  /// provided to the sliver.
+  ///
+  /// If the new instance represents different information than the old
+  /// instance, then the method should return true, otherwise it should return
+  /// false.
+  ///
+  /// If the method returns false, then the [build] call might be optimized
+  /// away.
+  bool shouldRebuild(covariant SliverChildDelegate oldDelegate);
+
+  /// Find index of child element with associated key.
+  ///
+  /// This will be called during `performRebuild` in [SliverMultiBoxAdaptorElement]
+  /// to check if a child has moved to a different position. It should return the
+  /// index of the child element with associated key, null if not found.
+  int? findIndexByKey(Key key) => null;
+
+  @override
+  String toString() {
+    final List<String> description = <String>[];
+    debugFillDescription(description);
+    return '${describeIdentity(this)}(${description.join(", ")})';
+  }
+
+  /// Add additional information to the given description for use by [toString].
+  @protected
+  @mustCallSuper
+  void debugFillDescription(List<String> description) {
+    try {
+      final int? children = estimatedChildCount;
+      if (children != null)
+        description.add('estimated child count: $children');
+    } catch (e) {
+      description.add('estimated child count: EXCEPTION (${e.runtimeType})');
+    }
+  }
+}
+
+class _SaltedValueKey extends ValueKey<Key>{
+  const _SaltedValueKey(Key key): assert(key != null), super(key);
+}
+
+/// Called to find the new index of a child based on its `key` in case of
+/// reordering.
+///
+/// If the child with the `key` is no longer present, null is returned.
+///
+/// Used by [SliverChildBuilderDelegate.findChildIndexCallback].
+typedef ChildIndexGetter = int? Function(Key key);
+
+/// A delegate that supplies children for slivers using a builder callback.
+///
+/// Many slivers lazily construct their box children to avoid creating more
+/// children than are visible through the [Viewport]. This delegate provides
+/// children using a [NullableIndexedWidgetBuilder] callback, so that the children do
+/// not even have to be built until they are displayed.
+///
+/// The widgets returned from the builder callback are automatically wrapped in
+/// [AutomaticKeepAlive] widgets if [addAutomaticKeepAlives] is true (the
+/// default) and in [RepaintBoundary] widgets if [addRepaintBoundaries] is true
+/// (also the default).
+///
+/// ## Accessibility
+///
+/// The [CustomScrollView] requires that its semantic children are annotated
+/// using [IndexedSemantics]. This is done by default in the delegate with
+/// the `addSemanticIndexes` parameter set to true.
+///
+/// If multiple delegates are used in a single scroll view, then the indexes
+/// will not be correct by default. The `semanticIndexOffset` can be used to
+/// offset the semantic indexes of each delegate so that the indexes are
+/// monotonically increasing. For example, if a scroll view contains two
+/// delegates where the first has 10 children contributing semantics, then the
+/// second delegate should offset its children by 10.
+///
+/// {@tool snippet}
+///
+/// This sample code shows how to use `semanticIndexOffset` to handle multiple
+/// delegates in a single scroll view.
+///
+/// ```dart
+/// CustomScrollView(
+///   semanticChildCount: 4,
+///   slivers: <Widget>[
+///     SliverGrid(
+///       gridDelegate: _gridDelegate,
+///       delegate: SliverChildBuilderDelegate(
+///         (BuildContext context, int index) {
+///            return Text('...');
+///          },
+///          childCount: 2,
+///        ),
+///      ),
+///     SliverGrid(
+///       gridDelegate: _gridDelegate,
+///       delegate: SliverChildBuilderDelegate(
+///         (BuildContext context, int index) {
+///            return Text('...');
+///          },
+///          childCount: 2,
+///          semanticIndexOffset: 2,
+///        ),
+///      ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// In certain cases, only a subset of child widgets should be annotated
+/// with a semantic index. For example, in [new ListView.separated()] the
+/// separators do not have an index associated with them. This is done by
+/// providing a `semanticIndexCallback` which returns null for separators
+/// indexes and rounds the non-separator indexes down by half.
+///
+/// {@tool snippet}
+///
+/// This sample code shows how to use `semanticIndexCallback` to handle
+/// annotating a subset of child nodes with a semantic index. There is
+/// a [Spacer] widget at odd indexes which should not have a semantic
+/// index.
+///
+/// ```dart
+/// CustomScrollView(
+///   semanticChildCount: 5,
+///   slivers: <Widget>[
+///     SliverGrid(
+///       gridDelegate: _gridDelegate,
+///       delegate: SliverChildBuilderDelegate(
+///         (BuildContext context, int index) {
+///            if (index.isEven) {
+///              return Text('...');
+///            }
+///            return Spacer();
+///          },
+///          semanticIndexCallback: (Widget widget, int localIndex) {
+///            if (localIndex.isEven) {
+///              return localIndex ~/ 2;
+///            }
+///            return null;
+///          },
+///          childCount: 10,
+///        ),
+///      ),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [SliverChildListDelegate], which is a delegate that has an explicit list
+///    of children.
+///  * [IndexedSemantics], for an example of manually annotating child nodes
+///    with semantic indexes.
+class SliverChildBuilderDelegate extends SliverChildDelegate {
+  /// Creates a delegate that supplies children for slivers using the given
+  /// builder callback.
+  ///
+  /// The [builder], [addAutomaticKeepAlives], [addRepaintBoundaries],
+  /// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be
+  /// null.
+  ///
+  /// If the order in which [builder] returns children ever changes, consider
+  /// providing a [findChildIndexCallback]. This allows the delegate to find the
+  /// new index for a child that was previously located at a different index to
+  /// attach the existing state to the [Widget] at its new location.
+  const SliverChildBuilderDelegate(
+    this.builder, {
+    this.findChildIndexCallback,
+    this.childCount,
+    this.addAutomaticKeepAlives = true,
+    this.addRepaintBoundaries = true,
+    this.addSemanticIndexes = true,
+    this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
+    this.semanticIndexOffset = 0,
+  }) : assert(builder != null),
+       assert(addAutomaticKeepAlives != null),
+       assert(addRepaintBoundaries != null),
+       assert(addSemanticIndexes != null),
+       assert(semanticIndexCallback != null);
+
+  /// Called to build children for the sliver.
+  ///
+  /// Will be called only for indices greater than or equal to zero and less
+  /// than [childCount] (if [childCount] is non-null).
+  ///
+  /// Should return null if asked to build a widget with a greater index than
+  /// exists.
+  ///
+  /// The delegate wraps the children returned by this builder in
+  /// [RepaintBoundary] widgets.
+  final NullableIndexedWidgetBuilder builder;
+
+  /// The total number of children this delegate can provide.
+  ///
+  /// If null, the number of children is determined by the least index for which
+  /// [builder] returns null.
+  final int? childCount;
+
+  /// Whether to wrap each child in an [AutomaticKeepAlive].
+  ///
+  /// Typically, children in lazy list are wrapped in [AutomaticKeepAlive]
+  /// widgets so that children can use [KeepAliveNotification]s to preserve
+  /// their state when they would otherwise be garbage collected off-screen.
+  ///
+  /// This feature (and [addRepaintBoundaries]) must be disabled if the children
+  /// are going to manually maintain their [KeepAlive] state. It may also be
+  /// more efficient to disable this feature if it is known ahead of time that
+  /// none of the children will ever try to keep themselves alive.
+  ///
+  /// Defaults to true.
+  final bool addAutomaticKeepAlives;
+
+  /// Whether to wrap each child in a [RepaintBoundary].
+  ///
+  /// Typically, children in a scrolling container are wrapped in repaint
+  /// boundaries so that they do not need to be repainted as the list scrolls.
+  /// If the children are easy to repaint (e.g., solid color blocks or a short
+  /// snippet of text), it might be more efficient to not add a repaint boundary
+  /// and simply repaint the children during scrolling.
+  ///
+  /// Defaults to true.
+  final bool addRepaintBoundaries;
+
+  /// Whether to wrap each child in an [IndexedSemantics].
+  ///
+  /// Typically, children in a scrolling container must be annotated with a
+  /// semantic index in order to generate the correct accessibility
+  /// announcements. This should only be set to false if the indexes have
+  /// already been provided by an [IndexedSemantics] widget.
+  ///
+  /// Defaults to true.
+  ///
+  /// See also:
+  ///
+  ///  * [IndexedSemantics], for an explanation of how to manually
+  ///    provide semantic indexes.
+  final bool addSemanticIndexes;
+
+  /// An initial offset to add to the semantic indexes generated by this widget.
+  ///
+  /// Defaults to zero.
+  final int semanticIndexOffset;
+
+  /// A [SemanticIndexCallback] which is used when [addSemanticIndexes] is true.
+  ///
+  /// Defaults to providing an index for each widget.
+  final SemanticIndexCallback semanticIndexCallback;
+
+  /// Called to find the new index of a child based on its key in case of reordering.
+  ///
+  /// If not provided, a child widget may not map to its existing [RenderObject]
+  /// when the order in which children are returned from [builder] changes.
+  /// This may result in state-loss.
+  ///
+  /// This callback should take an input [Key], and it should return the
+  /// index of the child element with that associated key, or null if not found.
+  final ChildIndexGetter? findChildIndexCallback;
+
+  @override
+  int? findIndexByKey(Key key) {
+    if (findChildIndexCallback == null)
+      return null;
+    assert(key != null);
+    final Key childKey;
+    if (key is _SaltedValueKey) {
+      final _SaltedValueKey saltedValueKey = key;
+      childKey = saltedValueKey.value;
+    } else {
+      childKey = key;
+    }
+    return findChildIndexCallback!(childKey);
+  }
+
+  @override
+  Widget? build(BuildContext context, int index) {
+    assert(builder != null);
+    if (index < 0 || (childCount != null && index >= childCount!))
+      return null;
+    Widget? child;
+    try {
+      child = builder(context, index);
+    } catch (exception, stackTrace) {
+      child = _createErrorWidget(exception, stackTrace);
+    }
+    if (child == null) {
+      return null;
+    }
+    final Key? key = child.key != null ? _SaltedValueKey(child.key!) : null;
+    if (addRepaintBoundaries)
+      child = RepaintBoundary(child: child);
+    if (addSemanticIndexes) {
+      final int? semanticIndex = semanticIndexCallback(child, index);
+      if (semanticIndex != null)
+        child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child);
+    }
+    if (addAutomaticKeepAlives)
+      child = AutomaticKeepAlive(child: child);
+    return KeyedSubtree(child: child, key: key);
+  }
+
+  @override
+  int? get estimatedChildCount => childCount;
+
+  @override
+  bool shouldRebuild(covariant SliverChildBuilderDelegate oldDelegate) => true;
+}
+
+/// A delegate that supplies children for slivers using an explicit list.
+///
+/// Many slivers lazily construct their box children to avoid creating more
+/// children than are visible through the [Viewport]. This delegate provides
+/// children using an explicit list, which is convenient but reduces the benefit
+/// of building children lazily.
+///
+/// In general building all the widgets in advance is not efficient. It is
+/// better to create a delegate that builds them on demand using
+/// [SliverChildBuilderDelegate] or by subclassing [SliverChildDelegate]
+/// directly.
+///
+/// This class is provided for the cases where either the list of children is
+/// known well in advance (ideally the children are themselves compile-time
+/// constants, for example), and therefore will not be built each time the
+/// delegate itself is created, or the list is small, such that it's likely
+/// always visible (and thus there is nothing to be gained by building it on
+/// demand). For example, the body of a dialog box might fit both of these
+/// conditions.
+///
+/// The widgets in the given [children] list are automatically wrapped in
+/// [AutomaticKeepAlive] widgets if [addAutomaticKeepAlives] is true (the
+/// default) and in [RepaintBoundary] widgets if [addRepaintBoundaries] is true
+/// (also the default).
+///
+/// ## Accessibility
+///
+/// The [CustomScrollView] requires that its semantic children are annotated
+/// using [IndexedSemantics]. This is done by default in the delegate with
+/// the `addSemanticIndexes` parameter set to true.
+///
+/// If multiple delegates are used in a single scroll view, then the indexes
+/// will not be correct by default. The `semanticIndexOffset` can be used to
+/// offset the semantic indexes of each delegate so that the indexes are
+/// monotonically increasing. For example, if a scroll view contains two
+/// delegates where the first has 10 children contributing semantics, then the
+/// second delegate should offset its children by 10.
+///
+/// In certain cases, only a subset of child widgets should be annotated
+/// with a semantic index. For example, in [new ListView.separated()] the
+/// separators do not have an index associated with them. This is done by
+/// providing a `semanticIndexCallback` which returns null for separators
+/// indexes and rounds the non-separator indexes down by half.
+///
+/// See [SliverChildBuilderDelegate] for sample code using
+/// `semanticIndexOffset` and `semanticIndexCallback`.
+///
+/// See also:
+///
+///  * [SliverChildBuilderDelegate], which is a delegate that uses a builder
+///    callback to construct the children.
+class SliverChildListDelegate extends SliverChildDelegate {
+  /// Creates a delegate that supplies children for slivers using the given
+  /// list.
+  ///
+  /// The [children], [addAutomaticKeepAlives], [addRepaintBoundaries],
+  /// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be
+  /// null.
+  ///
+  /// If the order of children` never changes, consider using the constant
+  /// [SliverChildListDelegate.fixed] constructor.
+  SliverChildListDelegate(
+    this.children, {
+    this.addAutomaticKeepAlives = true,
+    this.addRepaintBoundaries = true,
+    this.addSemanticIndexes = true,
+    this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
+    this.semanticIndexOffset = 0,
+  }) : assert(children != null),
+       assert(addAutomaticKeepAlives != null),
+       assert(addRepaintBoundaries != null),
+       assert(addSemanticIndexes != null),
+       assert(semanticIndexCallback != null),
+       _keyToIndex = <Key?, int>{null: 0};
+
+  /// Creates a constant version of the delegate that supplies children for
+  /// slivers using the given list.
+  ///
+  /// If the order of the children will change, consider using the regular
+  /// [SliverChildListDelegate] constructor.
+  ///
+  /// The [children], [addAutomaticKeepAlives], [addRepaintBoundaries],
+  /// [addSemanticIndexes], and [semanticIndexCallback] arguments must not be
+  /// null.
+  const SliverChildListDelegate.fixed(
+    this.children, {
+    this.addAutomaticKeepAlives = true,
+    this.addRepaintBoundaries = true,
+    this.addSemanticIndexes = true,
+    this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
+    this.semanticIndexOffset = 0,
+  }) : assert(children != null),
+       assert(addAutomaticKeepAlives != null),
+       assert(addRepaintBoundaries != null),
+       assert(addSemanticIndexes != null),
+       assert(semanticIndexCallback != null),
+       _keyToIndex = null;
+
+  /// Whether to wrap each child in an [AutomaticKeepAlive].
+  ///
+  /// Typically, children in lazy list are wrapped in [AutomaticKeepAlive]
+  /// widgets so that children can use [KeepAliveNotification]s to preserve
+  /// their state when they would otherwise be garbage collected off-screen.
+  ///
+  /// This feature (and [addRepaintBoundaries]) must be disabled if the children
+  /// are going to manually maintain their [KeepAlive] state. It may also be
+  /// more efficient to disable this feature if it is known ahead of time that
+  /// none of the children will ever try to keep themselves alive.
+  ///
+  /// Defaults to true.
+  final bool addAutomaticKeepAlives;
+
+  /// Whether to wrap each child in a [RepaintBoundary].
+  ///
+  /// Typically, children in a scrolling container are wrapped in repaint
+  /// boundaries so that they do not need to be repainted as the list scrolls.
+  /// If the children are easy to repaint (e.g., solid color blocks or a short
+  /// snippet of text), it might be more efficient to not add a repaint boundary
+  /// and simply repaint the children during scrolling.
+  ///
+  /// Defaults to true.
+  final bool addRepaintBoundaries;
+
+  /// Whether to wrap each child in an [IndexedSemantics].
+  ///
+  /// Typically, children in a scrolling container must be annotated with a
+  /// semantic index in order to generate the correct accessibility
+  /// announcements. This should only be set to false if the indexes have
+  /// already been provided by an [IndexedSemantics] widget.
+  ///
+  /// Defaults to true.
+  ///
+  /// See also:
+  ///
+  ///  * [IndexedSemantics], for an explanation of how to manually
+  ///    provide semantic indexes.
+  final bool addSemanticIndexes;
+
+  /// An initial offset to add to the semantic indexes generated by this widget.
+  ///
+  /// Defaults to zero.
+  final int semanticIndexOffset;
+
+  /// A [SemanticIndexCallback] which is used when [addSemanticIndexes] is true.
+  ///
+  /// Defaults to providing an index for each widget.
+  final SemanticIndexCallback semanticIndexCallback;
+
+  /// The widgets to display.
+  ///
+  /// If this list is going to be mutated, it is usually wise to put a [Key] on
+  /// each of the child widgets, so that the framework can match old
+  /// configurations to new configurations and maintain the underlying render
+  /// objects.
+  ///
+  /// Also, a [Widget] in Flutter is immutable, so directly modifying the
+  /// [children] such as `someWidget.children.add(...)` or
+  /// passing a reference of the original list value to the [children] parameter
+  /// will result in incorrect behaviors. Whenever the
+  /// children list is modified, a new list object should be provided.
+  ///
+  /// The following code corrects the problem mentioned above.
+  ///
+  /// ```dart
+  /// class SomeWidgetState extends State<SomeWidget> {
+  ///   List<Widget> _children;
+  ///
+  ///   void initState() {
+  ///     _children = [];
+  ///   }
+  ///
+  ///   void someHandler() {
+  ///     setState(() {
+  ///       // The key here allows Flutter to reuse the underlying render
+  ///       // objects even if the children list is recreated.
+  ///       _children.add(ChildWidget(key: UniqueKey()));
+  ///     });
+  ///   }
+  ///
+  ///   Widget build(BuildContext context) {
+  ///     // Always create a new list of children as a Widget is immutable.
+  ///     return PageView(children: List<Widget>.from(_children));
+  ///   }
+  /// }
+  /// ```
+  final List<Widget> children;
+
+  /// A map to cache key to index lookup for children.
+  ///
+  /// _keyToIndex[null] is used as current index during the lazy loading process
+  /// in [_findChildIndex]. _keyToIndex should never be used for looking up null key.
+  final Map<Key?, int>? _keyToIndex;
+
+  bool get _isConstantInstance => _keyToIndex == null;
+
+  int? _findChildIndex(Key key) {
+    if (_isConstantInstance) {
+      return null;
+    }
+    // Lazily fill the [_keyToIndex].
+    if (!_keyToIndex!.containsKey(key)) {
+      int index = _keyToIndex![null]!;
+      while (index < children.length) {
+        final Widget child = children[index];
+        if (child.key != null) {
+          _keyToIndex![child.key] = index;
+        }
+        if (child.key == key) {
+          // Record current index for next function call.
+          _keyToIndex![null] = index + 1;
+          return index;
+        }
+        index += 1;
+      }
+      _keyToIndex![null] = index;
+    } else {
+      return _keyToIndex![key];
+    }
+    return null;
+  }
+
+  @override
+  int? findIndexByKey(Key key) {
+    assert(key != null);
+    final Key childKey;
+    if (key is _SaltedValueKey) {
+      final _SaltedValueKey saltedValueKey = key;
+      childKey = saltedValueKey.value;
+    } else {
+      childKey = key;
+    }
+    return _findChildIndex(childKey);
+  }
+
+  @override
+  Widget? build(BuildContext context, int index) {
+    assert(children != null);
+    if (index < 0 || index >= children.length)
+      return null;
+    Widget child = children[index];
+    final Key? key = child.key != null? _SaltedValueKey(child.key!) : null;
+    assert(
+      child != null,
+      "The sliver's children must not contain null values, but a null value was found at index $index"
+    );
+    if (addRepaintBoundaries)
+      child = RepaintBoundary(child: child);
+    if (addSemanticIndexes) {
+      final int? semanticIndex = semanticIndexCallback(child, index);
+      if (semanticIndex != null)
+        child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child);
+    }
+    if (addAutomaticKeepAlives)
+      child = AutomaticKeepAlive(child: child);
+    return KeyedSubtree(child: child, key: key);
+  }
+
+  @override
+  int? get estimatedChildCount => children.length;
+
+  @override
+  bool shouldRebuild(covariant SliverChildListDelegate oldDelegate) {
+    return children != oldDelegate.children;
+  }
+}
+
+/// A base class for sliver that have [KeepAlive] children.
+///
+/// See also:
+///
+/// * [KeepAlive], which marks whether its chlild widget should be kept alive.
+/// * [SliverChildBuilderDelegate] and [SliverChildListDelegate], slivers
+///    which make usr of the keep alive functionality through the
+///    `addAutomaticKeepAlives` property.
+/// * [SliverGrid] and [SliverList], two sliver widgets that are commonly
+///    wrapped with [KeepAlive] widgets to preserve their sliver child subtrees.
+abstract class SliverWithKeepAliveWidget extends RenderObjectWidget {
+  /// Initializes fields for subclasses.
+  const SliverWithKeepAliveWidget({
+    Key? key,
+  }) : super(key : key);
+
+  @override
+  RenderSliverWithKeepAliveMixin createRenderObject(BuildContext context);
+}
+
+/// A base class for sliver that have multiple box children.
+///
+/// Helps subclasses build their children lazily using a [SliverChildDelegate].
+///
+/// The widgets returned by the [delegate] are cached and the delegate is only
+/// consulted again if it changes and the new delegate's
+/// [SliverChildDelegate.shouldRebuild] method returns true.
+abstract class SliverMultiBoxAdaptorWidget extends SliverWithKeepAliveWidget {
+  /// Initializes fields for subclasses.
+  const SliverMultiBoxAdaptorWidget({
+    Key? key,
+    required this.delegate,
+  }) : assert(delegate != null),
+       super(key: key);
+
+  /// {@template flutter.widgets.SliverMultiBoxAdaptorWidget.delegate}
+  /// The delegate that provides the children for this widget.
+  ///
+  /// The children are constructed lazily using this delegate to avoid creating
+  /// more children than are visible through the [Viewport].
+  ///
+  /// See also:
+  ///
+  ///  * [SliverChildBuilderDelegate] and [SliverChildListDelegate], which are
+  ///    commonly used subclasses of [SliverChildDelegate] that use a builder
+  ///    callback and an explicit child list, respectively.
+  /// {@endtemplate}
+  final SliverChildDelegate delegate;
+
+  @override
+  SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this);
+
+  @override
+  RenderSliverMultiBoxAdaptor createRenderObject(BuildContext context);
+
+  /// Returns an estimate of the max scroll extent for all the children.
+  ///
+  /// Subclasses should override this function if they have additional
+  /// information about their max scroll extent.
+  ///
+  /// This is used by [SliverMultiBoxAdaptorElement] to implement part of the
+  /// [RenderSliverBoxChildManager] API.
+  ///
+  /// The default implementation defers to [delegate] via its
+  /// [SliverChildDelegate.estimateMaxScrollOffset] method.
+  double? estimateMaxScrollOffset(
+    SliverConstraints? constraints,
+    int firstIndex,
+    int lastIndex,
+    double leadingScrollOffset,
+    double trailingScrollOffset,
+  ) {
+    assert(lastIndex >= firstIndex);
+    return delegate.estimateMaxScrollOffset(
+      firstIndex,
+      lastIndex,
+      leadingScrollOffset,
+      trailingScrollOffset,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<SliverChildDelegate>('delegate', delegate));
+  }
+}
+
+/// A sliver that places multiple box children in a linear array along the main
+/// axis.
+///
+/// Each child is forced to have the [SliverConstraints.crossAxisExtent] in the
+/// cross axis but determines its own main axis extent.
+///
+/// [SliverList] determines its scroll offset by "dead reckoning" because
+/// children outside the visible part of the sliver are not materialized, which
+/// means [SliverList] cannot learn their main axis extent. Instead, newly
+/// materialized children are placed adjacent to existing children.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=ORiTTaVY6mM}
+///
+/// If the children have a fixed extent in the main axis, consider using
+/// [SliverFixedExtentList] rather than [SliverList] because
+/// [SliverFixedExtentList] does not need to perform layout on its children to
+/// obtain their extent in the main axis and is therefore more efficient.
+///
+/// {@macro flutter.widgets.SliverChildDelegate.lifecycle}
+///
+/// See also:
+///
+///  * <https://flutter.dev/docs/development/ui/advanced/slivers>, a description
+///    of what slivers are and how to use them.
+///  * [SliverFixedExtentList], which is more efficient for children with
+///    the same extent in the main axis.
+///  * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList]
+///    except that it uses a prototype list item instead of a pixel value to define
+///    the main axis extent of each item.
+///  * [SliverGrid], which places its children in arbitrary positions.
+class SliverList extends SliverMultiBoxAdaptorWidget {
+  /// Creates a sliver that places box children in a linear array.
+  const SliverList({
+    Key? key,
+    required SliverChildDelegate delegate,
+  }) : super(key: key, delegate: delegate);
+
+  @override
+  SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this, replaceMovedChildren: true);
+
+  @override
+  RenderSliverList createRenderObject(BuildContext context) {
+    final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement;
+    return RenderSliverList(childManager: element);
+  }
+}
+
+/// A sliver that places multiple box children with the same main axis extent in
+/// a linear array.
+///
+/// [SliverFixedExtentList] places its children in a linear array along the main
+/// axis starting at offset zero and without gaps. Each child is forced to have
+/// the [itemExtent] in the main axis and the
+/// [SliverConstraints.crossAxisExtent] in the cross axis.
+///
+/// [SliverFixedExtentList] is more efficient than [SliverList] because
+/// [SliverFixedExtentList] does not need to perform layout on its children to
+/// obtain their extent in the main axis.
+///
+/// {@tool snippet}
+///
+/// This example, which would be inserted into a [CustomScrollView.slivers]
+/// list, shows an infinite number of items in varying shades of blue:
+///
+/// ```dart
+/// SliverFixedExtentList(
+///   itemExtent: 50.0,
+///   delegate: SliverChildBuilderDelegate(
+///     (BuildContext context, int index) {
+///       return Container(
+///         alignment: Alignment.center,
+///         color: Colors.lightBlue[100 * (index % 9)],
+///         child: Text('list item $index'),
+///       );
+///     },
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@macro flutter.widgets.SliverChildDelegate.lifecycle}
+///
+/// See also:
+///
+///  * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList]
+///    except that it uses a prototype list item instead of a pixel value to define
+///    the main axis extent of each item.
+///  * [SliverFillViewport], which determines the [itemExtent] based on
+///    [SliverConstraints.viewportMainAxisExtent].
+///  * [SliverList], which does not require its children to have the same
+///    extent in the main axis.
+class SliverFixedExtentList extends SliverMultiBoxAdaptorWidget {
+  /// Creates a sliver that places box children with the same main axis extent
+  /// in a linear array.
+  const SliverFixedExtentList({
+    Key? key,
+    required SliverChildDelegate delegate,
+    required this.itemExtent,
+  }) : super(key: key, delegate: delegate);
+
+  /// The extent the children are forced to have in the main axis.
+  final double itemExtent;
+
+  @override
+  RenderSliverFixedExtentList createRenderObject(BuildContext context) {
+    final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement;
+    return RenderSliverFixedExtentList(childManager: element, itemExtent: itemExtent);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderSliverFixedExtentList renderObject) {
+    renderObject.itemExtent = itemExtent;
+  }
+}
+
+/// A sliver that places multiple box children in a two dimensional arrangement.
+///
+/// [SliverGrid] places its children in arbitrary positions determined by
+/// [gridDelegate]. Each child is forced to have the size specified by the
+/// [gridDelegate].
+///
+/// The main axis direction of a grid is the direction in which it scrolls; the
+/// cross axis direction is the orthogonal direction.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=ORiTTaVY6mM}
+///
+/// {@tool snippet}
+///
+/// This example, which would be inserted into a [CustomScrollView.slivers]
+/// list, shows twenty boxes in a pretty teal grid:
+///
+/// ```dart
+/// SliverGrid(
+///   gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
+///     maxCrossAxisExtent: 200.0,
+///     mainAxisSpacing: 10.0,
+///     crossAxisSpacing: 10.0,
+///     childAspectRatio: 4.0,
+///   ),
+///   delegate: SliverChildBuilderDelegate(
+///     (BuildContext context, int index) {
+///       return Container(
+///         alignment: Alignment.center,
+///         color: Colors.teal[100 * (index % 9)],
+///         child: Text('grid item $index'),
+///       );
+///     },
+///     childCount: 20,
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@macro flutter.widgets.SliverChildDelegate.lifecycle}
+///
+/// See also:
+///
+///  * [SliverList], which places its children in a linear array.
+///  * [SliverFixedExtentList], which places its children in a linear
+///    array with a fixed extent in the main axis.
+///  * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList]
+///    except that it uses a prototype list item instead of a pixel value to define
+///    the main axis extent of each item.
+class SliverGrid extends SliverMultiBoxAdaptorWidget {
+  /// Creates a sliver that places multiple box children in a two dimensional
+  /// arrangement.
+  const SliverGrid({
+    Key? key,
+    required SliverChildDelegate delegate,
+    required this.gridDelegate,
+  }) : super(key: key, delegate: delegate);
+
+  /// Creates a sliver that places multiple box children in a two dimensional
+  /// arrangement with a fixed number of tiles in the cross axis.
+  ///
+  /// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate],
+  /// and a [SliverChildListDelegate] as the [delegate].
+  ///
+  /// See also:
+  ///
+  ///  * [new GridView.count], the equivalent constructor for [GridView] widgets.
+  SliverGrid.count({
+    Key? key,
+    required int crossAxisCount,
+    double mainAxisSpacing = 0.0,
+    double crossAxisSpacing = 0.0,
+    double childAspectRatio = 1.0,
+    List<Widget> children = const <Widget>[],
+  }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
+         crossAxisCount: crossAxisCount,
+         mainAxisSpacing: mainAxisSpacing,
+         crossAxisSpacing: crossAxisSpacing,
+         childAspectRatio: childAspectRatio,
+       ),
+       super(key: key, delegate: SliverChildListDelegate(children));
+
+  /// Creates a sliver that places multiple box children in a two dimensional
+  /// arrangement with tiles that each have a maximum cross-axis extent.
+  ///
+  /// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate],
+  /// and a [SliverChildListDelegate] as the [delegate].
+  ///
+  /// See also:
+  ///
+  ///  * [new GridView.extent], the equivalent constructor for [GridView] widgets.
+  SliverGrid.extent({
+    Key? key,
+    required double maxCrossAxisExtent,
+    double mainAxisSpacing = 0.0,
+    double crossAxisSpacing = 0.0,
+    double childAspectRatio = 1.0,
+    List<Widget> children = const <Widget>[],
+  }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
+         maxCrossAxisExtent: maxCrossAxisExtent,
+         mainAxisSpacing: mainAxisSpacing,
+         crossAxisSpacing: crossAxisSpacing,
+         childAspectRatio: childAspectRatio,
+       ),
+       super(key: key, delegate: SliverChildListDelegate(children));
+
+  /// The delegate that controls the size and position of the children.
+  final SliverGridDelegate gridDelegate;
+
+  @override
+  RenderSliverGrid createRenderObject(BuildContext context) {
+    final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement;
+    return RenderSliverGrid(childManager: element, gridDelegate: gridDelegate);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderSliverGrid renderObject) {
+    renderObject.gridDelegate = gridDelegate;
+  }
+
+  @override
+  double estimateMaxScrollOffset(
+    SliverConstraints? constraints,
+    int firstIndex,
+    int lastIndex,
+    double leadingScrollOffset,
+    double trailingScrollOffset,
+  ) {
+    return super.estimateMaxScrollOffset(
+      constraints,
+      firstIndex,
+      lastIndex,
+      leadingScrollOffset,
+      trailingScrollOffset,
+    ) ?? gridDelegate.getLayout(constraints!).computeMaxScrollOffset(delegate.estimatedChildCount!);
+  }
+}
+
+/// An element that lazily builds children for a [SliverMultiBoxAdaptorWidget].
+///
+/// Implements [RenderSliverBoxChildManager], which lets this element manage
+/// the children of subclasses of [RenderSliverMultiBoxAdaptor].
+class SliverMultiBoxAdaptorElement extends RenderObjectElement implements RenderSliverBoxChildManager {
+  /// Creates an element that lazily builds children for the given widget.
+  ///
+  /// If `replaceMovedChildren` is set to true, a new child is proactively
+  /// inflate for the index that was previously occupied by a child that moved
+  /// to a new index. The layout offset of the moved child is copied over to the
+  /// new child. RenderObjects, that depend on the layout offset of existing
+  /// children during [RenderObject.performLayout] should set this to true
+  /// (example: [RenderSliverList]). For RenderObjects that figure out the
+  /// layout offset of their children without looking at the layout offset of
+  /// existing children this should be set to false (example:
+  /// [RenderSliverFixedExtentList]) to avoid inflating unnecessary children.
+  SliverMultiBoxAdaptorElement(SliverMultiBoxAdaptorWidget widget, {bool replaceMovedChildren = false})
+     : _replaceMovedChildren = replaceMovedChildren,
+       super(widget);
+
+  final bool _replaceMovedChildren;
+
+  @override
+  SliverMultiBoxAdaptorWidget get widget => super.widget as SliverMultiBoxAdaptorWidget;
+
+  @override
+  RenderSliverMultiBoxAdaptor get renderObject => super.renderObject as RenderSliverMultiBoxAdaptor;
+
+  @override
+  void update(covariant SliverMultiBoxAdaptorWidget newWidget) {
+    final SliverMultiBoxAdaptorWidget oldWidget = widget;
+    super.update(newWidget);
+    final SliverChildDelegate newDelegate = newWidget.delegate;
+    final SliverChildDelegate oldDelegate = oldWidget.delegate;
+    if (newDelegate != oldDelegate &&
+        (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRebuild(oldDelegate)))
+      performRebuild();
+  }
+
+  final SplayTreeMap<int, Element?> _childElements = SplayTreeMap<int, Element?>();
+  RenderBox? _currentBeforeChild;
+
+  @override
+  void performRebuild() {
+    super.performRebuild();
+    _currentBeforeChild = null;
+    assert(_currentlyUpdatingChildIndex == null);
+    try {
+      final SplayTreeMap<int, Element?> newChildren = SplayTreeMap<int, Element?>();
+      final Map<int, double> indexToLayoutOffset = HashMap<int, double>();
+
+      void processElement(int index) {
+        _currentlyUpdatingChildIndex = index;
+        if (_childElements[index] != null && _childElements[index] != newChildren[index]) {
+          // This index has an old child that isn't used anywhere and should be deactivated.
+          _childElements[index] = updateChild(_childElements[index], null, index);
+        }
+        final Element? newChild = updateChild(newChildren[index], _build(index), index);
+        if (newChild != null) {
+          _childElements[index] = newChild;
+          final SliverMultiBoxAdaptorParentData parentData = newChild.renderObject!.parentData! as SliverMultiBoxAdaptorParentData;
+          if (index == 0) {
+            parentData.layoutOffset = 0.0;
+          } else if (indexToLayoutOffset.containsKey(index)) {
+            parentData.layoutOffset = indexToLayoutOffset[index];
+          }
+          if (!parentData.keptAlive)
+            _currentBeforeChild = newChild.renderObject as RenderBox?;
+        } else {
+          _childElements.remove(index);
+        }
+      }
+      for (final int index in _childElements.keys.toList()) {
+        final Key? key = _childElements[index]!.widget.key;
+        final int? newIndex = key == null ? null : widget.delegate.findIndexByKey(key);
+        final SliverMultiBoxAdaptorParentData? childParentData =
+          _childElements[index]!.renderObject?.parentData as SliverMultiBoxAdaptorParentData?;
+
+        if (childParentData != null && childParentData.layoutOffset != null)
+          indexToLayoutOffset[index] = childParentData.layoutOffset!;
+
+        if (newIndex != null && newIndex != index) {
+          // The layout offset of the child being moved is no longer accurate.
+          if (childParentData != null)
+            childParentData.layoutOffset = null;
+
+          newChildren[newIndex] = _childElements[index];
+          if (_replaceMovedChildren) {
+            // We need to make sure the original index gets processed.
+            newChildren.putIfAbsent(index, () => null);
+          }
+          // We do not want the remapped child to get deactivated during processElement.
+          _childElements.remove(index);
+        } else {
+          newChildren.putIfAbsent(index, () => _childElements[index]);
+        }
+      }
+
+      renderObject.debugChildIntegrityEnabled = false; // Moving children will temporary violate the integrity.
+      newChildren.keys.forEach(processElement);
+      if (_didUnderflow) {
+        final int lastKey = _childElements.lastKey() ?? -1;
+        final int rightBoundary = lastKey + 1;
+        newChildren[rightBoundary] = _childElements[rightBoundary];
+        processElement(rightBoundary);
+      }
+    } finally {
+      _currentlyUpdatingChildIndex = null;
+      renderObject.debugChildIntegrityEnabled = true;
+    }
+  }
+
+  Widget? _build(int index) {
+    return widget.delegate.build(this, index);
+  }
+
+  @override
+  void createChild(int index, { required RenderBox? after }) {
+    assert(_currentlyUpdatingChildIndex == null);
+    owner!.buildScope(this, () {
+      final bool insertFirst = after == null;
+      assert(insertFirst || _childElements[index-1] != null);
+      _currentBeforeChild = insertFirst ? null : (_childElements[index-1]!.renderObject as RenderBox?);
+      Element? newChild;
+      try {
+        _currentlyUpdatingChildIndex = index;
+        newChild = updateChild(_childElements[index], _build(index), index);
+      } finally {
+        _currentlyUpdatingChildIndex = null;
+      }
+      if (newChild != null) {
+        _childElements[index] = newChild;
+      } else {
+        _childElements.remove(index);
+      }
+    });
+  }
+
+  @override
+  Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
+    final SliverMultiBoxAdaptorParentData? oldParentData = child?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?;
+    final Element? newChild = super.updateChild(child, newWidget, newSlot);
+    final SliverMultiBoxAdaptorParentData? newParentData = newChild?.renderObject?.parentData as SliverMultiBoxAdaptorParentData?;
+
+    // Preserve the old layoutOffset if the renderObject was swapped out.
+    if (oldParentData != newParentData && oldParentData != null && newParentData != null) {
+      newParentData.layoutOffset = oldParentData.layoutOffset;
+    }
+    return newChild;
+  }
+
+  @override
+  void forgetChild(Element child) {
+    assert(child != null);
+    assert(child.slot != null);
+    assert(_childElements.containsKey(child.slot));
+    _childElements.remove(child.slot);
+    super.forgetChild(child);
+  }
+
+  @override
+  void removeChild(RenderBox child) {
+    final int index = renderObject.indexOf(child);
+    assert(_currentlyUpdatingChildIndex == null);
+    assert(index >= 0);
+    owner!.buildScope(this, () {
+      assert(_childElements.containsKey(index));
+      try {
+        _currentlyUpdatingChildIndex = index;
+        final Element? result = updateChild(_childElements[index], null, index);
+        assert(result == null);
+      } finally {
+        _currentlyUpdatingChildIndex = null;
+      }
+      _childElements.remove(index);
+      assert(!_childElements.containsKey(index));
+    });
+  }
+
+  static double _extrapolateMaxScrollOffset(
+    int firstIndex,
+    int lastIndex,
+    double leadingScrollOffset,
+    double trailingScrollOffset,
+    int childCount,
+  ) {
+    if (lastIndex == childCount - 1)
+      return trailingScrollOffset;
+    final int reifiedCount = lastIndex - firstIndex + 1;
+    final double averageExtent = (trailingScrollOffset - leadingScrollOffset) / reifiedCount;
+    final int remainingCount = childCount - lastIndex - 1;
+    return trailingScrollOffset + averageExtent * remainingCount;
+  }
+
+  @override
+  double estimateMaxScrollOffset(
+    SliverConstraints? constraints, {
+    int? firstIndex,
+    int? lastIndex,
+    double? leadingScrollOffset,
+    double? trailingScrollOffset,
+  }) {
+    final int? childCount = estimatedChildCount;
+    if (childCount == null)
+      return double.infinity;
+    return widget.estimateMaxScrollOffset(
+      constraints,
+      firstIndex!,
+      lastIndex!,
+      leadingScrollOffset!,
+      trailingScrollOffset!,
+    ) ?? _extrapolateMaxScrollOffset(
+      firstIndex,
+      lastIndex,
+      leadingScrollOffset,
+      trailingScrollOffset,
+      childCount,
+    );
+  }
+
+  /// The best available estimate of [childCount], or null if no estimate is available.
+  ///
+  /// This differs from [childCount] in that [childCount] never returns null (and must
+  /// not be accessed if the child count is not yet available, meaning the [createChild]
+  /// method has not been provided an index that does not create a child).
+  ///
+  /// See also:
+  ///
+  ///  * [SliverChildDelegate.estimatedChildCount], to which this getter defers.
+  int? get estimatedChildCount => widget.delegate.estimatedChildCount;
+
+  @override
+  int get childCount {
+    int? result = estimatedChildCount;
+    if (result == null) {
+      // Since childCount was called, we know that we reached the end of
+      // the list (as in, _build return null once), so we know that the
+      // list is finite.
+      // Let's do an open-ended binary search to find the end of the list
+      // manually.
+      int lo = 0;
+      int hi = 1;
+      const int max = kIsWeb
+        ? 9007199254740992 // max safe integer on JS (from 0 to this number x != x+1)
+        : ((1 << 63) - 1);
+      while (_build(hi - 1) != null) {
+        lo = hi - 1;
+        if (hi < max ~/ 2) {
+          hi *= 2;
+        } else if (hi < max) {
+          hi = max;
+        } else {
+          throw FlutterError(
+            'Could not find the number of children in ${widget.delegate}.\n'
+            'The childCount getter was called (implying that the delegate\'s builder returned null '
+            'for a positive index), but even building the child with index $hi (the maximum '
+            'possible integer) did not return null. Consider implementing childCount to avoid '
+            'the cost of searching for the final child.'
+          );
+        }
+      }
+      while (hi - lo > 1) {
+        final int mid = (hi - lo) ~/ 2 + lo;
+        if (_build(mid - 1) == null) {
+          hi = mid;
+        } else {
+          lo = mid;
+        }
+      }
+      result = lo;
+    }
+    return result;
+  }
+
+  @override
+  void didStartLayout() {
+    assert(debugAssertChildListLocked());
+  }
+
+  @override
+  void didFinishLayout() {
+    assert(debugAssertChildListLocked());
+    final int firstIndex = _childElements.firstKey() ?? 0;
+    final int lastIndex = _childElements.lastKey() ?? 0;
+    widget.delegate.didFinishLayout(firstIndex, lastIndex);
+  }
+
+  int? _currentlyUpdatingChildIndex;
+
+  @override
+  bool debugAssertChildListLocked() {
+    assert(_currentlyUpdatingChildIndex == null);
+    return true;
+  }
+
+  @override
+  void didAdoptChild(RenderBox child) {
+    assert(_currentlyUpdatingChildIndex != null);
+    final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
+    childParentData.index = _currentlyUpdatingChildIndex;
+  }
+
+  bool _didUnderflow = false;
+
+  @override
+  void setDidUnderflow(bool value) {
+    _didUnderflow = value;
+  }
+
+  @override
+  void insertRenderObjectChild(covariant RenderObject child, int slot) {
+    assert(slot != null);
+    assert(_currentlyUpdatingChildIndex == slot);
+    assert(renderObject.debugValidateChild(child));
+    renderObject.insert(child as RenderBox, after: _currentBeforeChild);
+    assert(() {
+      final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
+      assert(slot == childParentData.index);
+      return true;
+    }());
+  }
+
+  @override
+  void moveRenderObjectChild(covariant RenderObject child, int oldSlot, int newSlot) {
+    assert(newSlot != null);
+    assert(_currentlyUpdatingChildIndex == newSlot);
+    renderObject.move(child as RenderBox, after: _currentBeforeChild);
+  }
+
+  @override
+  void removeRenderObjectChild(covariant RenderObject child, int slot) {
+    assert(_currentlyUpdatingChildIndex != null);
+    renderObject.remove(child as RenderBox);
+  }
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    // The toList() is to make a copy so that the underlying list can be modified by
+    // the visitor:
+    assert(!_childElements.values.any((Element? child) => child == null));
+    _childElements.values.cast<Element>().toList().forEach(visitor);
+  }
+
+  @override
+  void debugVisitOnstageChildren(ElementVisitor visitor) {
+    _childElements.values.cast<Element>().where((Element child) {
+      final SliverMultiBoxAdaptorParentData parentData = child.renderObject!.parentData! as SliverMultiBoxAdaptorParentData;
+      final double itemExtent;
+      switch (renderObject.constraints.axis) {
+        case Axis.horizontal:
+          itemExtent = child.renderObject!.paintBounds.width;
+          break;
+        case Axis.vertical:
+          itemExtent = child.renderObject!.paintBounds.height;
+          break;
+      }
+
+      return parentData.layoutOffset != null &&
+          parentData.layoutOffset! < renderObject.constraints.scrollOffset + renderObject.constraints.remainingPaintExtent &&
+          parentData.layoutOffset! + itemExtent > renderObject.constraints.scrollOffset;
+    }).forEach(visitor);
+  }
+}
+
+/// A sliver widget that makes its sliver child partially transparent.
+///
+/// This class paints its sliver child into an intermediate buffer and then
+/// blends the sliver back into the scene partially transparent.
+///
+/// For values of opacity other than 0.0 and 1.0, this class is relatively
+/// expensive because it requires painting the sliver child into an intermediate
+/// buffer. For the value 0.0, the sliver child is simply not painted at all.
+/// For the value 1.0, the sliver child is painted immediately without an
+/// intermediate buffer.
+///
+/// {@tool snippet}
+///
+/// This example shows a [SliverList] when the `_visible` member field is true,
+/// and hides it when it is false:
+///
+/// ```dart
+/// bool _visible = true;
+/// List<Widget> listItems = <Widget>[
+///   Text('Now you see me,'),
+///   Text("Now you don't!"),
+/// ];
+///
+/// SliverOpacity(
+///   opacity: _visible ? 1.0 : 0.0,
+///   sliver: SliverList(
+///     delegate: SliverChildListDelegate(listItems),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// This is more efficient than adding and removing the sliver child widget
+/// from the tree on demand.
+///
+/// See also:
+///
+///  * [Opacity], which can apply a uniform alpha effect to its child using the
+///    RenderBox layout protocol.
+///  * [AnimatedOpacity], which uses an animation internally to efficiently
+///    animate [Opacity].
+class SliverOpacity extends SingleChildRenderObjectWidget {
+  /// Creates a sliver that makes its sliver child partially transparent.
+  ///
+  /// The [opacity] argument must not be null and must be between 0.0 and 1.0
+  /// (inclusive).
+  const SliverOpacity({
+    Key? key,
+    required this.opacity,
+    this.alwaysIncludeSemantics = false,
+    Widget? sliver,
+  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
+       assert(alwaysIncludeSemantics != null),
+       super(key: key, child: sliver);
+
+  /// The fraction to scale the sliver child's alpha value.
+  ///
+  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
+  /// (i.e. invisible).
+  ///
+  /// The opacity must not be null.
+  ///
+  /// Values 1.0 and 0.0 are painted with a fast path. Other values
+  /// require painting the sliver child into an intermediate buffer, which is
+  /// expensive.
+  final double opacity;
+
+  /// Whether the semantic information of the sliver child is always included.
+  ///
+  /// Defaults to false.
+  ///
+  /// When true, regardless of the opacity settings, the sliver child semantic
+  /// information is exposed as if the widget were fully visible. This is
+  /// useful in cases where labels may be hidden during animations that
+  /// would otherwise contribute relevant semantics.
+  final bool alwaysIncludeSemantics;
+
+  @override
+  RenderSliverOpacity createRenderObject(BuildContext context) {
+    return RenderSliverOpacity(
+      opacity: opacity,
+      alwaysIncludeSemantics: alwaysIncludeSemantics,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderSliverOpacity renderObject) {
+    renderObject
+      ..opacity = opacity
+      ..alwaysIncludeSemantics = alwaysIncludeSemantics;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<double>('opacity', opacity));
+    properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics',));
+  }
+}
+
+/// A sliver widget that is invisible during hit testing.
+///
+/// When [ignoring] is true, this widget (and its subtree) is invisible
+/// to hit testing. It still consumes space during layout and paints its sliver
+/// child as usual. It just cannot be the target of located events, because it
+/// returns false from [RenderSliver.hitTest].
+///
+/// When [ignoringSemantics] is true, the subtree will be invisible to
+/// the semantics layer (and thus e.g. accessibility tools). If
+/// [ignoringSemantics] is null, it uses the value of [ignoring].
+class SliverIgnorePointer extends SingleChildRenderObjectWidget {
+  /// Creates a sliver widget that is invisible to hit testing.
+  ///
+  /// The [ignoring] argument must not be null. If [ignoringSemantics] is null,
+  /// this render object will be ignored for semantics if [ignoring] is true.
+  const SliverIgnorePointer({
+    Key? key,
+    this.ignoring = true,
+    this.ignoringSemantics,
+    Widget? sliver,
+  }) : assert(ignoring != null),
+       super(key: key, child: sliver);
+
+  /// Whether this sliver is ignored during hit testing.
+  ///
+  /// Regardless of whether this sliver is ignored during hit testing, it will
+  /// still consume space during layout and be visible during painting.
+  final bool ignoring;
+
+  /// Whether the semantics of this sliver is ignored when compiling the
+  /// semantics tree.
+  ///
+  /// If null, defaults to value of [ignoring].
+  ///
+  /// See [SemanticsNode] for additional information about the semantics tree.
+  final bool? ignoringSemantics;
+
+  @override
+  RenderSliverIgnorePointer createRenderObject(BuildContext context) {
+    return RenderSliverIgnorePointer(
+      ignoring: ignoring,
+      ignoringSemantics: ignoringSemantics,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderSliverIgnorePointer renderObject) {
+    renderObject
+      ..ignoring = ignoring
+      ..ignoringSemantics = ignoringSemantics;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('ignoring', ignoring));
+    properties.add(DiagnosticsProperty<bool>('ignoringSemantics', ignoringSemantics, defaultValue: null));
+  }
+}
+
+/// A sliver that lays its sliver child out as if it was in the tree, but
+/// without painting anything, without making the sliver child available for hit
+/// testing, and without taking any room in the parent.
+///
+/// Animations continue to run in offstage sliver children, and therefore use
+/// battery and CPU time, regardless of whether the animations end up being
+/// visible.
+///
+/// To hide a sliver widget from view while it is
+/// not needed, prefer removing the widget from the tree entirely rather than
+/// keeping it alive in an [Offstage] subtree.
+class SliverOffstage extends SingleChildRenderObjectWidget {
+  /// Creates a sliver that visually hides its sliver child.
+  const SliverOffstage({
+    Key? key,
+    this.offstage = true,
+    Widget? sliver,
+  }) : assert(offstage != null),
+       super(key: key, child: sliver);
+
+  /// Whether the sliver child is hidden from the rest of the tree.
+  ///
+  /// If true, the sliver child is laid out as if it was in the tree, but
+  /// without painting anything, without making the child available for hit
+  /// testing, and without taking any room in the parent.
+  ///
+  /// If false, the sliver child is included in the tree as normal.
+  final bool offstage;
+
+  @override
+  RenderSliverOffstage createRenderObject(BuildContext context) => RenderSliverOffstage(offstage: offstage);
+
+  @override
+  void updateRenderObject(BuildContext context, RenderSliverOffstage renderObject) {
+    renderObject.offstage = offstage;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('offstage', offstage));
+  }
+
+  @override
+  _SliverOffstageElement createElement() => _SliverOffstageElement(this);
+}
+
+class _SliverOffstageElement extends SingleChildRenderObjectElement {
+  _SliverOffstageElement(SliverOffstage widget) : super(widget);
+
+  @override
+  SliverOffstage get widget => super.widget as SliverOffstage;
+
+  @override
+  void debugVisitOnstageChildren(ElementVisitor visitor) {
+    if (!widget.offstage)
+      super.debugVisitOnstageChildren(visitor);
+  }
+}
+
+/// Mark a child as needing to stay alive even when it's in a lazy list that
+/// would otherwise remove it.
+///
+/// This widget is for use in [SliverWithKeepAliveWidget]s, such as
+/// [SliverGrid] or [SliverList].
+///
+/// This widget is rarely used directly. The [SliverChildBuilderDelegate] and
+/// [SliverChildListDelegate] delegates, used with [SliverList] and
+/// [SliverGrid], as well as the scroll view counterparts [ListView] and
+/// [GridView], have an `addAutomaticKeepAlives` feature, which is enabled by
+/// default, and which causes [AutomaticKeepAlive] widgets to be inserted around
+/// each child, causing [KeepAlive] widgets to be automatically added and
+/// configured in response to [KeepAliveNotification]s.
+///
+/// Therefore, to keep a widget alive, it is more common to use those
+/// notifications than to directly deal with [KeepAlive] widgets.
+///
+/// In practice, the simplest way to deal with these notifications is to mix
+/// [AutomaticKeepAliveClientMixin] into one's [State]. See the documentation
+/// for that mixin class for details.
+class KeepAlive extends ParentDataWidget<KeepAliveParentDataMixin> {
+  /// Marks a child as needing to remain alive.
+  ///
+  /// The [child] and [keepAlive] arguments must not be null.
+  const KeepAlive({
+    Key? key,
+    required this.keepAlive,
+    required Widget child,
+  }) : assert(child != null),
+       assert(keepAlive != null),
+       super(key: key, child: child);
+
+  /// Whether to keep the child alive.
+  ///
+  /// If this is false, it is as if this widget was omitted.
+  final bool keepAlive;
+
+  @override
+  void applyParentData(RenderObject renderObject) {
+    assert(renderObject.parentData is KeepAliveParentDataMixin);
+    final KeepAliveParentDataMixin parentData = renderObject.parentData! as KeepAliveParentDataMixin;
+    if (parentData.keepAlive != keepAlive) {
+      parentData.keepAlive = keepAlive;
+      final AbstractNode? targetParent = renderObject.parent;
+      if (targetParent is RenderObject && !keepAlive)
+        targetParent.markNeedsLayout(); // No need to redo layout if it became true.
+    }
+  }
+
+  // We only return true if [keepAlive] is true, because turning _off_ keep
+  // alive requires a layout to do the garbage collection (but turning it on
+  // requires nothing, since by definition the widget is already alive and won't
+  // go away _unless_ we do a layout).
+  @override
+  bool debugCanApplyOutOfTurn() => keepAlive;
+
+  @override
+  Type get debugTypicalAncestorWidgetClass => SliverWithKeepAliveWidget;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<bool>('keepAlive', keepAlive));
+  }
+}
+
+// Return a Widget for the given Exception
+Widget _createErrorWidget(Object exception, StackTrace stackTrace) {
+  final FlutterErrorDetails details = FlutterErrorDetails(
+    exception: exception,
+    stack: stackTrace,
+    library: 'widgets library',
+    context: ErrorDescription('building'),
+  );
+  FlutterError.reportError(details);
+  return ErrorWidget.builder(details);
+}
diff --git a/lib/src/widgets/sliver_fill.dart b/lib/src/widgets/sliver_fill.dart
new file mode 100644
index 0000000..cb9c76a
--- /dev/null
+++ b/lib/src/widgets/sliver_fill.dart
@@ -0,0 +1,491 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'sliver.dart';
+
+/// A sliver that contains multiple box children that each fills the viewport.
+///
+/// [SliverFillViewport] places its children in a linear array along the main
+/// axis. Each child is sized to fill the viewport, both in the main and cross
+/// axis.
+///
+/// See also:
+///
+///  * [SliverFixedExtentList], which has a configurable
+///    [SliverFixedExtentList.itemExtent].
+///  * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList]
+///    except that it uses a prototype list item instead of a pixel value to define
+///    the main axis extent of each item.
+///  * [SliverList], which does not require its children to have the same
+///    extent in the main axis.
+class SliverFillViewport extends StatelessWidget {
+  /// Creates a sliver whose box children that each fill the viewport.
+  const SliverFillViewport({
+    Key? key,
+    required this.delegate,
+    this.viewportFraction = 1.0,
+    this.padEnds = true,
+  }) : assert(viewportFraction != null),
+       assert(viewportFraction > 0.0),
+       assert(padEnds != null),
+       super(key: key);
+
+  /// The fraction of the viewport that each child should fill in the main axis.
+  ///
+  /// If this fraction is less than 1.0, more than one child will be visible at
+  /// once. If this fraction is greater than 1.0, each child will be larger than
+  /// the viewport in the main axis.
+  final double viewportFraction;
+
+  /// Whether to add padding to both ends of the list.
+  ///
+  /// If this is set to true and [viewportFraction] < 1.0, padding will be added
+  /// such that the first and last child slivers will be in the center of
+  /// the viewport when scrolled all the way to the start or end, respectively.
+  /// You may want to set this to false if this [SliverFillViewport] is not the only
+  /// widget along this main axis, such as in a [CustomScrollView] with multiple
+  /// children.
+  ///
+  /// This option cannot be null. If [viewportFraction] >= 1.0, this option has no
+  /// effect. Defaults to true.
+  final bool padEnds;
+
+  /// {@macro flutter.widgets.SliverMultiBoxAdaptorWidget.delegate}
+  final SliverChildDelegate delegate;
+
+  @override
+  Widget build(BuildContext context) {
+    return _SliverFractionalPadding(
+      viewportFraction: padEnds ? (1 - viewportFraction).clamp(0, 1) / 2 : 0,
+      sliver: _SliverFillViewportRenderObjectWidget(
+        viewportFraction: viewportFraction,
+        delegate: delegate,
+      ),
+    );
+  }
+}
+
+class _SliverFillViewportRenderObjectWidget extends SliverMultiBoxAdaptorWidget {
+  const _SliverFillViewportRenderObjectWidget({
+    Key? key,
+    required SliverChildDelegate delegate,
+    this.viewportFraction = 1.0,
+  }) : assert(viewportFraction != null),
+      assert(viewportFraction > 0.0),
+      super(key: key, delegate: delegate);
+
+  final double viewportFraction;
+
+  @override
+  RenderSliverFillViewport createRenderObject(BuildContext context) {
+    final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement;
+    return RenderSliverFillViewport(childManager: element, viewportFraction: viewportFraction);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderSliverFillViewport renderObject) {
+    renderObject.viewportFraction = viewportFraction;
+  }
+}
+
+class _SliverFractionalPadding extends SingleChildRenderObjectWidget {
+  const _SliverFractionalPadding({
+    this.viewportFraction = 0,
+    Widget? sliver,
+  }) : assert(viewportFraction != null),
+      assert(viewportFraction >= 0),
+      assert(viewportFraction <= 0.5),
+      super(child: sliver);
+
+  final double viewportFraction;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) => _RenderSliverFractionalPadding(viewportFraction: viewportFraction);
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderSliverFractionalPadding renderObject) {
+    renderObject.viewportFraction = viewportFraction;
+  }
+}
+
+class _RenderSliverFractionalPadding extends RenderSliverEdgeInsetsPadding {
+  _RenderSliverFractionalPadding({
+    double viewportFraction = 0,
+  }) : assert(viewportFraction != null),
+      assert(viewportFraction <= 0.5),
+      assert(viewportFraction >= 0),
+      _viewportFraction = viewportFraction;
+
+  SliverConstraints? _lastResolvedConstraints;
+
+  double get viewportFraction => _viewportFraction;
+  double _viewportFraction;
+  set viewportFraction(double newValue) {
+    assert(newValue != null);
+    if (_viewportFraction == newValue)
+      return;
+    _viewportFraction = newValue;
+    _markNeedsResolution();
+  }
+
+  @override
+  EdgeInsets? get resolvedPadding => _resolvedPadding;
+  EdgeInsets? _resolvedPadding;
+
+  void _markNeedsResolution() {
+    _resolvedPadding = null;
+    markNeedsLayout();
+  }
+
+  void _resolve() {
+    if (_resolvedPadding != null && _lastResolvedConstraints == constraints)
+      return;
+
+    assert(constraints.axis != null);
+    final double paddingValue = constraints.viewportMainAxisExtent * viewportFraction;
+    _lastResolvedConstraints = constraints;
+    switch (constraints.axis) {
+      case Axis.horizontal:
+        _resolvedPadding = EdgeInsets.symmetric(horizontal: paddingValue);
+        break;
+      case Axis.vertical:
+        _resolvedPadding = EdgeInsets.symmetric(vertical: paddingValue);
+        break;
+    }
+
+    return;
+  }
+
+  @override
+  void performLayout() {
+    _resolve();
+    super.performLayout();
+  }
+}
+
+/// A sliver that contains a single box child that fills the remaining space in
+/// the viewport.
+///
+/// [SliverFillRemaining] will size its [child] to fill the viewport in the
+/// cross axis. The extent of the sliver and its child's size in the main axis
+/// is computed conditionally, described in further detail below.
+///
+/// Typically this will be the last sliver in a viewport, since (by definition)
+/// there is never any room for anything beyond this sliver.
+///
+/// ## Main Axis Extent
+///
+/// ### When [SliverFillRemaining] has a scrollable child
+///
+/// The [hasScrollBody] flag indicates whether the sliver's child has a
+/// scrollable body. This value is never null, and defaults to true. A common
+/// example of this use is a [NestedScrollView]. In this case, the sliver will
+/// size its child to fill the maximum available extent. [SliverFillRemaining]
+/// will not constrain the scrollable area, as it could potentially have an
+/// infinite depth. This is also true for use cases such as a [ScrollView] when
+/// [ScrollView.shrinkWrap] is true.
+///
+/// ### When [SliverFillRemaining] does not have a scrollable child
+///
+/// When [hasScrollBody] is set to false, the child's size is taken into account
+/// when considering the extent to which it should fill the space. The extent to
+/// which the preceding slivers have been scrolled is also taken into
+/// account in deciding how to layout this sliver.
+///
+/// [SliverFillRemaining] will size its [child] to fill the viewport in the
+/// main axis if that space is larger than the child's extent, and the amount
+/// of space that has been scrolled beforehand has not exceeded the main axis
+/// extent of the viewport.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// In this sample the [SliverFillRemaining] sizes its [child] to fill the
+/// remaining extent of the viewport in both axes. The icon is centered in the
+/// sliver, and would be in any computed extent for the sliver.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return CustomScrollView(
+///     slivers: <Widget>[
+///       SliverToBoxAdapter(
+///         child: Container(
+///           color: Colors.amber[300],
+///           height: 150.0,
+///         ),
+///       ),
+///       SliverFillRemaining(
+///         hasScrollBody: false,
+///         child: Container(
+///           color: Colors.blue[100],
+///           child: Icon(
+///             Icons.sentiment_very_satisfied,
+///             size: 75,
+///             color: Colors.blue[900],
+///           ),
+///         ),
+///       ),
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// [SliverFillRemaining] will defer to the size of its [child] if the
+/// child's size exceeds the remaining space in the viewport.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// In this sample the [SliverFillRemaining] defers to the size of its [child]
+/// because the child's extent exceeds that of the remaining extent of the
+/// viewport's main axis.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return CustomScrollView(
+///     slivers: <Widget>[
+///       SliverFixedExtentList(
+///         itemExtent: 100.0,
+///         delegate: SliverChildBuilderDelegate(
+///           (BuildContext context, int index) {
+///             return Container(
+///               color: index % 2 == 0
+///                 ? Colors.amber[200]
+///                 : Colors.blue[200],
+///             );
+///           },
+///           childCount: 3,
+///         ),
+///       ),
+///       SliverFillRemaining(
+///         hasScrollBody: false,
+///         child: Container(
+///           color: Colors.orange[300],
+///           child: Padding(
+///             padding: const EdgeInsets.all(50.0),
+///             child: FlutterLogo(size: 100),
+///           ),
+///         ),
+///       ),
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// [SliverFillRemaining] will defer to the size of its [child] if the
+/// [SliverConstraints.precedingScrollExtent] exceeded the length of the viewport's main axis.
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// In this sample the [SliverFillRemaining] defers to the size of its [child]
+/// because the [SliverConstraints.precedingScrollExtent] has gone
+/// beyond that of the viewport's main axis.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return CustomScrollView(
+///     slivers: <Widget>[
+///       SliverFixedExtentList(
+///         itemExtent: 130.0,
+///         delegate: SliverChildBuilderDelegate(
+///           (BuildContext context, int index) {
+///             return Container(
+///               color: index % 2 == 0
+///                 ? Colors.indigo[200]
+///                 : Colors.orange[200],
+///             );
+///           },
+///           childCount: 5,
+///         ),
+///       ),
+///       SliverFillRemaining(
+///         hasScrollBody: false,
+///         child: Container(
+///           child: Padding(
+///             padding: const EdgeInsets.all(50.0),
+///             child: Icon(
+///               Icons.pan_tool,
+///               size: 60,
+///               color: Colors.blueGrey,
+///             ),
+///           ),
+///         ),
+///       ),
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// For [ScrollPhysics] that allow overscroll, such as
+/// [BouncingScrollPhysics], setting the [fillOverscroll] flag to true allows
+/// the size of the [child] to _stretch_, filling the overscroll area. It does
+/// this regardless of the path chosen to provide the child's size.
+///
+/// {@animation 250 500 https://flutter.github.io/assets-for-api-docs/assets/widgets/sliver_fill_remaining_fill_overscroll.mp4}
+///
+/// {@tool sample --template=stateless_widget_scaffold_no_null_safety}
+///
+/// In this sample the [SliverFillRemaining]'s child stretches to fill the
+/// overscroll area when [fillOverscroll] is true. This sample also features a
+/// button that is pinned to the bottom of the sliver, regardless of size or
+/// overscroll behavior. Try switching [fillOverscroll] to see the difference.
+///
+/// This sample only shows the overscroll behavior on devices that support
+/// overscroll.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return CustomScrollView(
+///     // The ScrollPhysics are overridden here to illustrate the functionality
+///     // of fillOverscroll on all devices this sample may be run on.
+///     // fillOverscroll only changes the behavior of your layout when applied
+///     // to Scrollables that allow for overscroll. BouncingScrollPhysics are
+///     // one example, which are provided by default on the iOS platform.
+///     // BouncingScrollPhysics is combined with AlwaysScrollableScrollPhysics
+///     // to allow for the overscroll, regardless of the depth of the
+///     // scrollable.
+///     physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
+///     slivers: <Widget>[
+///       SliverToBoxAdapter(
+///         child: Container(
+///           color: Colors.tealAccent[700],
+///           height: 150.0,
+///         ),
+///       ),
+///       SliverFillRemaining(
+///         hasScrollBody: false,
+///         // Switch for different overscroll behavior in your layout.
+///         // If your ScrollPhysics do not allow for overscroll, setting
+///         // fillOverscroll to true will have no effect.
+///         fillOverscroll: true,
+///         child: Container(
+///           color: Colors.teal[100],
+///           child: Align(
+///             alignment: Alignment.bottomCenter,
+///             child: Padding(
+///               padding: const EdgeInsets.all(16.0),
+///               child: ElevatedButton(
+///                 onPressed: () {
+///                   /* Place your onPressed code here! */
+///                 },
+///                 child: Text('Bottom Pinned Button!'),
+///               ),
+///             ),
+///           ),
+///         ),
+///       ),
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+///
+/// See also:
+///
+///  * [SliverFillViewport], which sizes its children based on the
+///    size of the viewport, regardless of what else is in the scroll view.
+///  * [SliverList], which shows a list of variable-sized children in a
+///    viewport.
+class SliverFillRemaining extends StatelessWidget {
+  /// Creates a sliver that fills the remaining space in the viewport.
+  const SliverFillRemaining({
+    Key? key,
+    this.child,
+    this.hasScrollBody = true,
+    this.fillOverscroll = false,
+  }) : assert(hasScrollBody != null),
+       assert(fillOverscroll != null),
+       super(key: key);
+
+  /// Box child widget that fills the remaining space in the viewport.
+  ///
+  /// The main [SliverFillRemaining] documentation contains more details.
+  final Widget? child;
+
+  /// Indicates whether the child has a scrollable body, this value cannot be
+  /// null.
+  ///
+  /// Defaults to true such that the child will extend beyond the viewport and
+  /// scroll, as seen in [NestedScrollView].
+  ///
+  /// Setting this value to false will allow the child to fill the remainder of
+  /// the viewport and not extend further. However, if the
+  /// [SliverConstraints.precedingScrollExtent] and/or the [child]'s
+  /// extent exceeds the size of the viewport, the sliver will defer to the
+  /// child's size rather than overriding it.
+  final bool hasScrollBody;
+
+  /// Indicates whether the child should stretch to fill the overscroll area
+  /// created by certain scroll physics, such as iOS' default scroll physics.
+  /// This value cannot be null. This flag is only relevant when the
+  /// [hasScrollBody] value is false.
+  ///
+  /// Defaults to false, meaning the default behavior is for the child to
+  /// maintain its size and not extend into the overscroll area.
+  final bool fillOverscroll;
+
+  @override
+  Widget build(BuildContext context) {
+    if (hasScrollBody)
+      return _SliverFillRemainingWithScrollable(child: child);
+    if (!fillOverscroll)
+      return _SliverFillRemainingWithoutScrollable(child: child);
+    return _SliverFillRemainingAndOverscroll(child: child);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(
+      DiagnosticsProperty<Widget>(
+        'child',
+        child,
+      )
+    );
+    final List<String> flags = <String>[
+      if (hasScrollBody) 'scrollable',
+      if (fillOverscroll) 'fillOverscroll',
+    ];
+    if (flags.isEmpty)
+      flags.add('nonscrollable');
+    properties.add(IterableProperty<String>('mode', flags));
+  }
+}
+
+class _SliverFillRemainingWithScrollable extends SingleChildRenderObjectWidget {
+  const _SliverFillRemainingWithScrollable({
+    Key? key,
+    Widget? child,
+  }) : super(key: key, child: child);
+
+  @override
+  RenderSliverFillRemainingWithScrollable createRenderObject(BuildContext context) => RenderSliverFillRemainingWithScrollable();
+}
+
+class _SliverFillRemainingWithoutScrollable extends SingleChildRenderObjectWidget {
+  const _SliverFillRemainingWithoutScrollable({
+    Key? key,
+    Widget? child,
+  }) : super(key: key, child: child);
+
+  @override
+  RenderSliverFillRemaining createRenderObject(BuildContext context) => RenderSliverFillRemaining();
+}
+
+class _SliverFillRemainingAndOverscroll extends SingleChildRenderObjectWidget {
+  const _SliverFillRemainingAndOverscroll({
+    Key? key,
+    Widget? child,
+  }) : super(key: key, child: child);
+
+  @override
+  RenderSliverFillRemainingAndOverscroll createRenderObject(BuildContext context) => RenderSliverFillRemainingAndOverscroll();
+}
diff --git a/lib/src/widgets/sliver_layout_builder.dart b/lib/src/widgets/sliver_layout_builder.dart
new file mode 100644
index 0000000..0784da0
--- /dev/null
+++ b/lib/src/widgets/sliver_layout_builder.dart
@@ -0,0 +1,81 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+import 'framework.dart';
+import 'layout_builder.dart';
+
+/// The signature of the [SliverLayoutBuilder] builder function.
+typedef SliverLayoutWidgetBuilder = Widget Function(BuildContext context, SliverConstraints constraints);
+
+/// Builds a sliver widget tree that can depend on its own [SliverConstraints].
+///
+/// Similar to the [LayoutBuilder] widget except its builder should return a sliver
+/// widget, and [SliverLayoutBuilder] is itself a sliver. The framework calls the
+/// [builder] function at layout time and provides the current [SliverConstraints].
+/// The [SliverLayoutBuilder]'s final [SliverGeometry] will match the [SliverGeometry]
+/// of its child.
+///
+/// {@macro flutter.widgets.ConstrainedLayoutBuilder}
+///
+/// See also:
+///
+///  * [LayoutBuilder], the non-sliver version of this widget.
+class SliverLayoutBuilder extends ConstrainedLayoutBuilder<SliverConstraints> {
+  /// Creates a sliver widget that defers its building until layout.
+  ///
+  /// The [builder] argument must not be null.
+  const SliverLayoutBuilder({
+    Key? key,
+    required SliverLayoutWidgetBuilder builder,
+  }) : super(key: key, builder: builder);
+
+  /// Called at layout time to construct the widget tree.
+  ///
+  /// The builder must return a non-null sliver widget.
+  @override
+  SliverLayoutWidgetBuilder get builder => super.builder;
+
+  @override
+  _RenderSliverLayoutBuilder createRenderObject(BuildContext context) => _RenderSliverLayoutBuilder();
+}
+
+class _RenderSliverLayoutBuilder extends RenderSliver with RenderObjectWithChildMixin<RenderSliver>, RenderConstrainedLayoutBuilder<SliverConstraints, RenderSliver> {
+  @override
+  double childMainAxisPosition(RenderObject child) {
+    assert(child != null);
+    assert(child == this.child);
+    return 0;
+  }
+
+  @override
+  void performLayout() {
+    rebuildIfNecessary();
+    child?.layout(constraints, parentUsesSize: true);
+    geometry = child?.geometry ?? SliverGeometry.zero;
+  }
+
+  @override
+  void applyPaintTransform(RenderObject child, Matrix4 transform) {
+    assert(child != null);
+    assert(child == this.child);
+    // child's offset is always (0, 0), transform.translate(0, 0) does not mutate the transform.
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    // This renderObject does not introduce additional offset to child's position.
+    if (child?.geometry?.visible == true)
+      context.paintChild(child!, offset);
+  }
+
+  @override
+  bool hitTestChildren(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) {
+    return child != null
+        && child!.geometry!.hitTestExtent > 0
+        && child!.hitTest(result, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition);
+  }
+}
diff --git a/lib/src/widgets/sliver_persistent_header.dart b/lib/src/widgets/sliver_persistent_header.dart
new file mode 100644
index 0000000..aaa4ade
--- /dev/null
+++ b/lib/src/widgets/sliver_persistent_header.dart
@@ -0,0 +1,467 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart' show TickerProvider;
+
+import 'framework.dart';
+
+/// Delegate for configuring a [SliverPersistentHeader].
+abstract class SliverPersistentHeaderDelegate {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const SliverPersistentHeaderDelegate();
+
+  /// The widget to place inside the [SliverPersistentHeader].
+  ///
+  /// The `context` is the [BuildContext] of the sliver.
+  ///
+  /// The `shrinkOffset` is a distance from [maxExtent] towards [minExtent]
+  /// representing the current amount by which the sliver has been shrunk. When
+  /// the `shrinkOffset` is zero, the contents will be rendered with a dimension
+  /// of [maxExtent] in the main axis. When `shrinkOffset` equals the difference
+  /// between [maxExtent] and [minExtent] (a positive number), the contents will
+  /// be rendered with a dimension of [minExtent] in the main axis. The
+  /// `shrinkOffset` will always be a positive number in that range.
+  ///
+  /// The `overlapsContent` argument is true if subsequent slivers (if any) will
+  /// be rendered beneath this one, and false if the sliver will not have any
+  /// contents below it. Typically this is used to decide whether to draw a
+  /// shadow to simulate the sliver being above the contents below it. Typically
+  /// this is true when `shrinkOffset` is at its greatest value and false
+  /// otherwise, but that is not guaranteed. See [NestedScrollView] for an
+  /// example of a case where `overlapsContent`'s value can be unrelated to
+  /// `shrinkOffset`.
+  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent);
+
+  /// The smallest size to allow the header to reach, when it shrinks at the
+  /// start of the viewport.
+  ///
+  /// This must return a value equal to or less than [maxExtent].
+  ///
+  /// This value should not change over the lifetime of the delegate. It should
+  /// be based entirely on the constructor arguments passed to the delegate. See
+  /// [shouldRebuild], which must return true if a new delegate would return a
+  /// different value.
+  double get minExtent;
+
+  /// The size of the header when it is not shrinking at the top of the
+  /// viewport.
+  ///
+  /// This must return a value equal to or greater than [minExtent].
+  ///
+  /// This value should not change over the lifetime of the delegate. It should
+  /// be based entirely on the constructor arguments passed to the delegate. See
+  /// [shouldRebuild], which must return true if a new delegate would return a
+  /// different value.
+  double get maxExtent;
+
+  /// A [TickerProvider] to use when animating the header's size changes.
+  ///
+  /// Must not be null if the persistent header is a floating header, and
+  /// [snapConfiguration] or [showOnScreenConfiguration] is not null.
+  TickerProvider? get vsync => null;
+
+  /// Specifies how floating headers should animate in and out of view.
+  ///
+  /// If the value of this property is null, then floating headers will
+  /// not animate into place.
+  ///
+  /// This is only used for floating headers (those with
+  /// [SliverPersistentHeader.floating] set to true).
+  ///
+  /// Defaults to null.
+  FloatingHeaderSnapConfiguration? get snapConfiguration => null;
+
+  /// Specifies an [AsyncCallback] and offset for execution.
+  ///
+  /// If the value of this property is null, then callback will not be
+  /// triggered.
+  ///
+  /// This is only used for stretching headers (those with
+  /// [SliverAppBar.stretch] set to true).
+  ///
+  /// Defaults to null.
+  OverScrollHeaderStretchConfiguration? get stretchConfiguration => null;
+
+  /// Specifies how floating headers and pinned pinned headers should behave in
+  /// response to [RenderObject.showOnScreen] calls.
+  ///
+  /// Defaults to null.
+  PersistentHeaderShowOnScreenConfiguration? get showOnScreenConfiguration => null;
+
+  /// Whether this delegate is meaningfully different from the old delegate.
+  ///
+  /// If this returns false, then the header might not be rebuilt, even though
+  /// the instance of the delegate changed.
+  ///
+  /// This must return true if `oldDelegate` and this object would return
+  /// different values for [minExtent], [maxExtent], [snapConfiguration], or
+  /// would return a meaningfully different widget tree from [build] for the
+  /// same arguments.
+  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate);
+}
+
+/// A sliver whose size varies when the sliver is scrolled to the edge
+/// of the viewport opposite the sliver's [GrowthDirection].
+///
+/// In the normal case of a [CustomScrollView] with no centered sliver, this
+/// sliver will vary its size when scrolled to the leading edge of the viewport.
+///
+/// This is the layout primitive that [SliverAppBar] uses for its
+/// shrinking/growing effect.
+class SliverPersistentHeader extends StatelessWidget {
+  /// Creates a sliver that varies its size when it is scrolled to the start of
+  /// a viewport.
+  ///
+  /// The [delegate], [pinned], and [floating] arguments must not be null.
+  const SliverPersistentHeader({
+    Key? key,
+    required this.delegate,
+    this.pinned = false,
+    this.floating = false,
+  }) : assert(delegate != null),
+       assert(pinned != null),
+       assert(floating != null),
+       super(key: key);
+
+  /// Configuration for the sliver's layout.
+  ///
+  /// The delegate provides the following information:
+  ///
+  ///  * The minimum and maximum dimensions of the sliver.
+  ///
+  ///  * The builder for generating the widgets of the sliver.
+  ///
+  ///  * The instructions for snapping the scroll offset, if [floating] is true.
+  final SliverPersistentHeaderDelegate delegate;
+
+  /// Whether to stick the header to the start of the viewport once it has
+  /// reached its minimum size.
+  ///
+  /// If this is false, the header will continue scrolling off the screen after
+  /// it has shrunk to its minimum extent.
+  final bool pinned;
+
+  /// Whether the header should immediately grow again if the user reverses
+  /// scroll direction.
+  ///
+  /// If this is false, the header only grows again once the user reaches the
+  /// part of the viewport that contains the sliver.
+  ///
+  /// The [delegate]'s [SliverPersistentHeaderDelegate.snapConfiguration] is
+  /// ignored unless [floating] is true.
+  final bool floating;
+
+  @override
+  Widget build(BuildContext context) {
+    if (floating && pinned)
+      return _SliverFloatingPinnedPersistentHeader(delegate: delegate);
+    if (pinned)
+      return _SliverPinnedPersistentHeader(delegate: delegate);
+    if (floating)
+      return _SliverFloatingPersistentHeader(delegate: delegate);
+    return _SliverScrollingPersistentHeader(delegate: delegate);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(
+      DiagnosticsProperty<SliverPersistentHeaderDelegate>(
+        'delegate',
+        delegate,
+      )
+    );
+    final List<String> flags = <String>[
+      if (pinned) 'pinned',
+      if (floating) 'floating',
+    ];
+    if (flags.isEmpty)
+      flags.add('normal');
+    properties.add(IterableProperty<String>('mode', flags));
+  }
+}
+
+class _SliverPersistentHeaderElement extends RenderObjectElement {
+  _SliverPersistentHeaderElement(_SliverPersistentHeaderRenderObjectWidget widget) : super(widget);
+
+  @override
+  _SliverPersistentHeaderRenderObjectWidget get widget => super.widget as _SliverPersistentHeaderRenderObjectWidget;
+
+  @override
+  _RenderSliverPersistentHeaderForWidgetsMixin get renderObject => super.renderObject as _RenderSliverPersistentHeaderForWidgetsMixin;
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    renderObject._element = this;
+  }
+
+  @override
+  void unmount() {
+    super.unmount();
+    renderObject._element = null;
+  }
+
+  @override
+  void update(_SliverPersistentHeaderRenderObjectWidget newWidget) {
+    final _SliverPersistentHeaderRenderObjectWidget oldWidget = widget;
+    super.update(newWidget);
+    final SliverPersistentHeaderDelegate newDelegate = newWidget.delegate;
+    final SliverPersistentHeaderDelegate oldDelegate = oldWidget.delegate;
+    if (newDelegate != oldDelegate &&
+        (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRebuild(oldDelegate)))
+      renderObject.triggerRebuild();
+  }
+
+  @override
+  void performRebuild() {
+    super.performRebuild();
+    renderObject.triggerRebuild();
+  }
+
+  Element? child;
+
+  void _build(double shrinkOffset, bool overlapsContent) {
+    owner!.buildScope(this, () {
+      child = updateChild(
+        child,
+        widget.delegate.build(
+          this,
+          shrinkOffset,
+          overlapsContent
+        ),
+        null,
+      );
+    });
+  }
+
+  @override
+  void forgetChild(Element child) {
+    assert(child == this.child);
+    this.child = null;
+    super.forgetChild(child);
+  }
+
+  @override
+  void insertRenderObjectChild(covariant RenderBox child, dynamic slot) {
+    assert(renderObject.debugValidateChild(child));
+    renderObject.child = child;
+  }
+
+  @override
+  void moveRenderObjectChild(covariant RenderObject child, dynamic oldSlot, dynamic newSlot) {
+    assert(false);
+  }
+
+  @override
+  void removeRenderObjectChild(covariant RenderObject child, dynamic slot) {
+    renderObject.child = null;
+  }
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    if (child != null)
+      visitor(child!);
+  }
+}
+
+abstract class _SliverPersistentHeaderRenderObjectWidget extends RenderObjectWidget {
+  const _SliverPersistentHeaderRenderObjectWidget({
+    Key? key,
+    required this.delegate,
+  }) : assert(delegate != null),
+       super(key: key);
+
+  final SliverPersistentHeaderDelegate delegate;
+
+  @override
+  _SliverPersistentHeaderElement createElement() => _SliverPersistentHeaderElement(this);
+
+  @override
+  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context);
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(
+      DiagnosticsProperty<SliverPersistentHeaderDelegate>(
+        'delegate',
+        delegate,
+      )
+    );
+  }
+}
+
+mixin _RenderSliverPersistentHeaderForWidgetsMixin on RenderSliverPersistentHeader {
+  _SliverPersistentHeaderElement? _element;
+
+  @override
+  double get minExtent => _element!.widget.delegate.minExtent;
+
+  @override
+  double get maxExtent => _element!.widget.delegate.maxExtent;
+
+  @override
+  void updateChild(double shrinkOffset, bool overlapsContent) {
+    assert(_element != null);
+    _element!._build(shrinkOffset, overlapsContent);
+  }
+
+  @protected
+  void triggerRebuild() {
+    markNeedsLayout();
+  }
+}
+
+class _SliverScrollingPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
+  const _SliverScrollingPersistentHeader({
+    Key? key,
+    required SliverPersistentHeaderDelegate delegate,
+  }) : super(
+    key: key,
+    delegate: delegate,
+  );
+
+  @override
+  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
+    return _RenderSliverScrollingPersistentHeaderForWidgets(
+      stretchConfiguration: delegate.stretchConfiguration
+    );
+  }
+}
+
+class _RenderSliverScrollingPersistentHeaderForWidgets extends RenderSliverScrollingPersistentHeader
+  with _RenderSliverPersistentHeaderForWidgetsMixin {
+  _RenderSliverScrollingPersistentHeaderForWidgets({
+    RenderBox? child,
+    OverScrollHeaderStretchConfiguration? stretchConfiguration,
+  }) : super(
+    child: child,
+    stretchConfiguration: stretchConfiguration,
+  );
+}
+
+class _SliverPinnedPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
+  const _SliverPinnedPersistentHeader({
+    Key? key,
+    required SliverPersistentHeaderDelegate delegate,
+  }) : super(
+    key: key,
+    delegate: delegate,
+  );
+
+  @override
+  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
+    return _RenderSliverPinnedPersistentHeaderForWidgets(
+      stretchConfiguration: delegate.stretchConfiguration,
+      showOnScreenConfiguration: delegate.showOnScreenConfiguration,
+    );
+  }
+}
+
+class _RenderSliverPinnedPersistentHeaderForWidgets extends RenderSliverPinnedPersistentHeader
+  with _RenderSliverPersistentHeaderForWidgetsMixin {
+  _RenderSliverPinnedPersistentHeaderForWidgets({
+    RenderBox? child,
+    OverScrollHeaderStretchConfiguration? stretchConfiguration,
+    PersistentHeaderShowOnScreenConfiguration? showOnScreenConfiguration,
+  }) : super(
+    child: child,
+    stretchConfiguration: stretchConfiguration,
+    showOnScreenConfiguration: showOnScreenConfiguration,
+  );
+}
+
+class _SliverFloatingPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
+  const _SliverFloatingPersistentHeader({
+    Key? key,
+    required SliverPersistentHeaderDelegate delegate,
+  }) : super(
+    key: key,
+    delegate: delegate,
+  );
+
+  @override
+  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
+    return _RenderSliverFloatingPersistentHeaderForWidgets(
+      vsync: delegate.vsync,
+      snapConfiguration: delegate.snapConfiguration,
+      stretchConfiguration: delegate.stretchConfiguration,
+      showOnScreenConfiguration: delegate.showOnScreenConfiguration,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderSliverFloatingPersistentHeaderForWidgets renderObject) {
+    renderObject.vsync = delegate.vsync;
+    renderObject.snapConfiguration = delegate.snapConfiguration;
+    renderObject.stretchConfiguration = delegate.stretchConfiguration;
+    renderObject.showOnScreenConfiguration = delegate.showOnScreenConfiguration;
+  }
+}
+
+class _RenderSliverFloatingPinnedPersistentHeaderForWidgets extends RenderSliverFloatingPinnedPersistentHeader
+  with _RenderSliverPersistentHeaderForWidgetsMixin {
+  _RenderSliverFloatingPinnedPersistentHeaderForWidgets({
+    RenderBox? child,
+    required TickerProvider? vsync,
+    FloatingHeaderSnapConfiguration? snapConfiguration,
+    OverScrollHeaderStretchConfiguration? stretchConfiguration,
+    PersistentHeaderShowOnScreenConfiguration? showOnScreenConfiguration,
+  }) : super(
+    child: child,
+    vsync: vsync,
+    snapConfiguration: snapConfiguration,
+    stretchConfiguration: stretchConfiguration,
+    showOnScreenConfiguration: showOnScreenConfiguration,
+  );
+}
+
+class _SliverFloatingPinnedPersistentHeader extends _SliverPersistentHeaderRenderObjectWidget {
+  const _SliverFloatingPinnedPersistentHeader({
+    Key? key,
+    required SliverPersistentHeaderDelegate delegate,
+  }) : super(
+    key: key,
+    delegate: delegate,
+  );
+
+  @override
+  _RenderSliverPersistentHeaderForWidgetsMixin createRenderObject(BuildContext context) {
+    return _RenderSliverFloatingPinnedPersistentHeaderForWidgets(
+      vsync: delegate.vsync,
+      snapConfiguration: delegate.snapConfiguration,
+      stretchConfiguration: delegate.stretchConfiguration,
+      showOnScreenConfiguration: delegate.showOnScreenConfiguration,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderSliverFloatingPinnedPersistentHeaderForWidgets renderObject) {
+    renderObject.vsync = delegate.vsync;
+    renderObject.snapConfiguration = delegate.snapConfiguration;
+    renderObject.stretchConfiguration = delegate.stretchConfiguration;
+    renderObject.showOnScreenConfiguration = delegate.showOnScreenConfiguration;
+  }
+}
+
+class _RenderSliverFloatingPersistentHeaderForWidgets extends RenderSliverFloatingPersistentHeader
+  with _RenderSliverPersistentHeaderForWidgetsMixin {
+  _RenderSliverFloatingPersistentHeaderForWidgets({
+    RenderBox? child,
+    required TickerProvider? vsync,
+    FloatingHeaderSnapConfiguration? snapConfiguration,
+    OverScrollHeaderStretchConfiguration? stretchConfiguration,
+    PersistentHeaderShowOnScreenConfiguration? showOnScreenConfiguration,
+  }) : super(
+    child: child,
+    vsync: vsync,
+    snapConfiguration: snapConfiguration,
+    stretchConfiguration: stretchConfiguration,
+    showOnScreenConfiguration: showOnScreenConfiguration,
+  );
+}
diff --git a/lib/src/widgets/sliver_prototype_extent_list.dart b/lib/src/widgets/sliver_prototype_extent_list.dart
new file mode 100644
index 0000000..1de2a8d
--- /dev/null
+++ b/lib/src/widgets/sliver_prototype_extent_list.dart
@@ -0,0 +1,183 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'sliver.dart';
+
+/// A sliver that places its box children in a linear array and constrains them
+/// to have the same extent as a prototype item along the main axis.
+///
+/// [SliverPrototypeExtentList] arranges its children in a line along
+/// the main axis starting at offset zero and without gaps. Each child is
+/// constrained to the same extent as the [prototypeItem] along the main axis
+/// and the [SliverConstraints.crossAxisExtent] along the cross axis.
+///
+/// [SliverPrototypeExtentList] is more efficient than [SliverList] because
+/// [SliverPrototypeExtentList] does not need to lay out its children to obtain
+/// their extent along the main axis. It's a little more flexible than
+/// [SliverFixedExtentList] because there's no need to determine the appropriate
+/// item extent in pixels.
+///
+/// See also:
+///
+///  * [SliverFixedExtentList], whose itemExtent is a pixel value.
+///  * [SliverList], which does not require its children to have the same
+///    extent in the main axis.
+///  * [SliverFillViewport], which sizes its children based on the
+///    size of the viewport, regardless of what else is in the scroll view.
+///  * [SliverList], which shows a list of variable-sized children in a
+///    viewport.
+class SliverPrototypeExtentList extends SliverMultiBoxAdaptorWidget {
+  /// Creates a sliver that places its box children in a linear array and
+  /// constrains them to have the same extent as a prototype item along
+  /// the main axis.
+  const SliverPrototypeExtentList({
+    Key? key,
+    required SliverChildDelegate delegate,
+    required this.prototypeItem,
+  }) : assert(prototypeItem != null),
+       super(key: key, delegate: delegate);
+
+  /// Defines the main axis extent of all of this sliver's children.
+  ///
+  /// The [prototypeItem] is laid out before the rest of the sliver's children
+  /// and its size along the main axis fixes the size of each child. The
+  /// [prototypeItem] is essentially [Offstage]: it is not painted and it
+  /// cannot respond to input.
+  final Widget prototypeItem;
+
+  @override
+  _RenderSliverPrototypeExtentList createRenderObject(BuildContext context) {
+    final _SliverPrototypeExtentListElement element = context as _SliverPrototypeExtentListElement;
+    return _RenderSliverPrototypeExtentList(childManager: element);
+  }
+
+  @override
+  _SliverPrototypeExtentListElement createElement() => _SliverPrototypeExtentListElement(this);
+}
+
+class _SliverPrototypeExtentListElement extends SliverMultiBoxAdaptorElement {
+  _SliverPrototypeExtentListElement(SliverPrototypeExtentList widget) : super(widget);
+
+  @override
+  SliverPrototypeExtentList get widget => super.widget as SliverPrototypeExtentList;
+
+  @override
+  _RenderSliverPrototypeExtentList get renderObject => super.renderObject as _RenderSliverPrototypeExtentList;
+
+  Element? _prototype;
+  static final Object _prototypeSlot = Object();
+
+  @override
+  void insertRenderObjectChild(covariant RenderObject child, covariant dynamic slot) {
+    if (slot == _prototypeSlot) {
+      assert(child is RenderBox);
+      renderObject.child = child as RenderBox;
+    } else {
+      super.insertRenderObjectChild(child, slot as int);
+    }
+  }
+
+  @override
+  void didAdoptChild(RenderBox child) {
+    if (child != renderObject.child)
+      super.didAdoptChild(child);
+  }
+
+  @override
+  void moveRenderObjectChild(RenderBox child, dynamic oldSlot, dynamic newSlot) {
+    if (newSlot == _prototypeSlot)
+      assert(false); // There's only one prototype child so it cannot be moved.
+    else
+      super.moveRenderObjectChild(child, oldSlot as int, newSlot as int);
+  }
+
+  @override
+  void removeRenderObjectChild(RenderBox child, dynamic slot) {
+    if (renderObject.child == child)
+      renderObject.child = null;
+    else
+      super.removeRenderObjectChild(child, slot as int);
+  }
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    if (_prototype != null)
+      visitor(_prototype!);
+    super.visitChildren(visitor);
+  }
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    _prototype = updateChild(_prototype, widget.prototypeItem, _prototypeSlot);
+  }
+
+  @override
+  void update(SliverPrototypeExtentList newWidget) {
+    super.update(newWidget);
+    assert(widget == newWidget);
+    _prototype = updateChild(_prototype, widget.prototypeItem, _prototypeSlot);
+  }
+}
+
+class _RenderSliverPrototypeExtentList extends RenderSliverFixedExtentBoxAdaptor {
+  _RenderSliverPrototypeExtentList({
+    required _SliverPrototypeExtentListElement childManager,
+  }) : super(childManager: childManager);
+
+  RenderBox? _child;
+  RenderBox? get child => _child;
+  set child(RenderBox? value) {
+    if (_child != null)
+      dropChild(_child!);
+    _child = value;
+    if (_child != null)
+      adoptChild(_child!);
+    markNeedsLayout();
+  }
+
+  @override
+  void performLayout() {
+    child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);
+    super.performLayout();
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    if (_child != null)
+      _child!.attach(owner);
+  }
+
+  @override
+  void detach() {
+    super.detach();
+    if (_child != null)
+      _child!.detach();
+  }
+
+  @override
+  void redepthChildren() {
+    if (_child != null)
+      redepthChild(_child!);
+    super.redepthChildren();
+  }
+
+  @override
+  void visitChildren(RenderObjectVisitor visitor) {
+    if (_child != null)
+      visitor(_child!);
+    super.visitChildren(visitor);
+  }
+
+  @override
+  double get itemExtent {
+    assert(child != null && child!.hasSize);
+    return constraints.axis == Axis.vertical ? child!.size.height : child!.size.width;
+  }
+}
diff --git a/lib/src/widgets/spacer.dart b/lib/src/widgets/spacer.dart
new file mode 100644
index 0000000..f4d4607
--- /dev/null
+++ b/lib/src/widgets/spacer.dart
@@ -0,0 +1,68 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+
+/// Spacer creates an adjustable, empty spacer that can be used to tune the
+/// spacing between widgets in a [Flex] container, like [Row] or [Column].
+///
+/// The [Spacer] widget will take up any available space, so setting the
+/// [Flex.mainAxisAlignment] on a flex container that contains a [Spacer] to
+/// [MainAxisAlignment.spaceAround], [MainAxisAlignment.spaceBetween], or
+/// [MainAxisAlignment.spaceEvenly] will not have any visible effect: the
+/// [Spacer] has taken up all of the additional space, therefore there is none
+/// left to redistribute.
+///
+/// {@tool snippet}
+///
+/// ```dart
+/// Row(
+///   children: <Widget>[
+///     Text('Begin'),
+///     Spacer(), // Defaults to a flex of one.
+///     Text('Middle'),
+///     // Gives twice the space between Middle and End than Begin and Middle.
+///     Spacer(flex: 2),
+///     Text('End'),
+///   ],
+/// )
+/// ```
+/// {@end-tool}
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=7FJgd7QN1zI}
+///
+/// See also:
+///
+///  * [Row] and [Column], which are the most common containers to use a Spacer
+///    in.
+///  * [SizedBox], to create a box with a specific size and an optional child.
+class Spacer extends StatelessWidget {
+  /// Creates a flexible space to insert into a [Flexible] widget.
+  ///
+  /// The [flex] parameter may not be null or less than one.
+  const Spacer({Key? key, this.flex = 1})
+    : assert(flex != null),
+      assert(flex > 0),
+      super(key: key);
+
+  /// The flex factor to use in determining how much space to take up.
+  ///
+  /// The amount of space the [Spacer] can occupy in the main axis is determined
+  /// by dividing the free space proportionately, after placing the inflexible
+  /// children, according to the flex factors of the flexible children.
+  ///
+  /// Defaults to one.
+  final int flex;
+
+  @override
+  Widget build(BuildContext context) {
+    return Expanded(
+      flex: flex,
+      child: const SizedBox.shrink(),
+    );
+  }
+}
diff --git a/lib/src/widgets/status_transitions.dart b/lib/src/widgets/status_transitions.dart
new file mode 100644
index 0000000..43205b2
--- /dev/null
+++ b/lib/src/widgets/status_transitions.dart
@@ -0,0 +1,62 @@
+// Copyright 2014 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.
+
+import 'basic.dart';
+import 'framework.dart';
+
+/// A widget that rebuilds when the given animation changes status.
+abstract class StatusTransitionWidget extends StatefulWidget {
+  /// Initializes fields for subclasses.
+  ///
+  /// The [animation] argument must not be null.
+  const StatusTransitionWidget({
+    Key? key,
+    required this.animation,
+  }) : assert(animation != null),
+       super(key: key);
+
+  /// The animation to which this widget is listening.
+  final Animation<double> animation;
+
+  /// Override this method to build widgets that depend on the current status
+  /// of the animation.
+  Widget build(BuildContext context);
+
+  @override
+  _StatusTransitionState createState() => _StatusTransitionState();
+}
+
+class _StatusTransitionState extends State<StatusTransitionWidget> {
+  @override
+  void initState() {
+    super.initState();
+    widget.animation.addStatusListener(_animationStatusChanged);
+  }
+
+  @override
+  void didUpdateWidget(StatusTransitionWidget oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.animation != oldWidget.animation) {
+      oldWidget.animation.removeStatusListener(_animationStatusChanged);
+      widget.animation.addStatusListener(_animationStatusChanged);
+    }
+  }
+
+  @override
+  void dispose() {
+    widget.animation.removeStatusListener(_animationStatusChanged);
+    super.dispose();
+  }
+
+  void _animationStatusChanged(AnimationStatus status) {
+    setState(() {
+      // The animation's state is our build state, and it changed already.
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return widget.build(context);
+  }
+}
diff --git a/lib/src/widgets/table.dart b/lib/src/widgets/table.dart
new file mode 100644
index 0000000..aa96135
--- /dev/null
+++ b/lib/src/widgets/table.dart
@@ -0,0 +1,491 @@
+// Copyright 2014 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.
+
+import 'dart:collection';
+
+import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'debug.dart';
+import 'framework.dart';
+import 'image.dart';
+
+export 'package:flute/rendering.dart' show
+  FixedColumnWidth,
+  FlexColumnWidth,
+  FractionColumnWidth,
+  IntrinsicColumnWidth,
+  MaxColumnWidth,
+  MinColumnWidth,
+  TableBorder,
+  TableCellVerticalAlignment,
+  TableColumnWidth;
+
+/// A horizontal group of cells in a [Table].
+///
+/// Every row in a table must have the same number of children.
+///
+/// The alignment of individual cells in a row can be controlled using a
+/// [TableCell].
+@immutable
+class TableRow {
+  /// Creates a row in a [Table].
+  const TableRow({ this.key, this.decoration, this.children });
+
+  /// An identifier for the row.
+  final LocalKey? key;
+
+  /// A decoration to paint behind this row.
+  ///
+  /// Row decorations fill the horizontal and vertical extent of each row in
+  /// the table, unlike decorations for individual cells, which might not fill
+  /// either.
+  final Decoration? decoration;
+
+  /// The widgets that comprise the cells in this row.
+  ///
+  /// Children may be wrapped in [TableCell] widgets to provide per-cell
+  /// configuration to the [Table], but children are not required to be wrapped
+  /// in [TableCell] widgets.
+  final List<Widget>? children;
+
+  @override
+  String toString() {
+    final StringBuffer result = StringBuffer();
+    result.write('TableRow(');
+    if (key != null)
+      result.write('$key, ');
+    if (decoration != null)
+      result.write('$decoration, ');
+    if (children == null) {
+      result.write('child list is null');
+    } else if (children!.isEmpty) {
+      result.write('no children');
+    } else {
+      result.write('$children');
+    }
+    result.write(')');
+    return result.toString();
+  }
+}
+
+class _TableElementRow {
+  const _TableElementRow({ this.key, required this.children });
+  final LocalKey? key;
+  final List<Element> children;
+}
+
+/// A widget that uses the table layout algorithm for its children.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=_lbE0wsVZSw}
+///
+/// {@tool dartpad --template=stateless_widget_scaffold_no_null_safety}
+///
+/// This sample shows a `Table` with borders, multiple types of column widths and different vertical cell alignments.
+///
+/// ```dart
+/// Widget build(BuildContext context) {
+///   return Table(
+///     border: TableBorder.all(),
+///     columnWidths: {
+///       0: IntrinsicColumnWidth(),
+///       1: FlexColumnWidth(),
+///       2: FixedColumnWidth(64),
+///     },
+///     defaultVerticalAlignment: TableCellVerticalAlignment.middle,
+///     children: [
+///       TableRow(
+///         children: [
+///           Container(
+///             height: 32,
+///             color: Colors.green,
+///           ),
+///           TableCell(
+///             verticalAlignment: TableCellVerticalAlignment.top,
+///             child: Container(
+///               height: 32,
+///               width: 32,
+///               color: Colors.red,
+///             ),
+///           ),
+///           Container(
+///             height: 64,
+///             color: Colors.blue,
+///           ),
+///         ],
+///       ),
+///       TableRow(
+///         decoration: BoxDecoration(
+///           color: Colors.grey,
+///         ),
+///         children: [
+///           Container(
+///             height: 64,
+///             width: 128,
+///             color: Colors.purple,
+///           ),
+///           Container(
+///             height: 32,
+///             color: Colors.yellow,
+///           ),
+///           Center(
+///             child: Container(
+///               height: 32,
+///               width: 32,
+///               color: Colors.orange,
+///             ),
+///           ),
+///         ],
+///       ),
+///     ],
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// If you only have one row, the [Row] widget is more appropriate. If you only
+/// have one column, the [SliverList] or [Column] widgets will be more
+/// appropriate.
+///
+/// Rows size vertically based on their contents. To control the individual
+/// column widths, use the [columnWidths] property to specify a
+/// [TableColumnWidth] for each column. If [columnWidths] is null, or there is a
+/// null entry for a given column in [columnWidths], the table uses the
+/// [defaultColumnWidth] instead.
+///
+/// By default, [defaultColumnWidth] is a [FlexColumnWidth]. This
+/// [TableColumnWidth] divides up the remaining space in the horizontal axis to
+/// determine the column width. If wrapping a [Table] in a horizontal
+/// [ScrollView], choose a different [TableColumnWidth], such as
+/// [FixedColumnWidth].
+///
+/// For more details about the table layout algorithm, see [RenderTable].
+/// To control the alignment of children, see [TableCell].
+///
+/// See also:
+///
+///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
+class Table extends RenderObjectWidget {
+  /// Creates a table.
+  ///
+  /// The [children], [defaultColumnWidth], and [defaultVerticalAlignment]
+  /// arguments must not be null.
+  Table({
+    Key? key,
+    this.children = const <TableRow>[],
+    this.columnWidths,
+    this.defaultColumnWidth = const FlexColumnWidth(1.0),
+    this.textDirection,
+    this.border,
+    this.defaultVerticalAlignment = TableCellVerticalAlignment.top,
+    this.textBaseline, // NO DEFAULT: we don't know what the text's baseline should be
+  }) : assert(children != null),
+       assert(defaultColumnWidth != null),
+       assert(defaultVerticalAlignment != null),
+       assert(defaultVerticalAlignment != TableCellVerticalAlignment.baseline || textBaseline != null, 'textBaseline is required if you specify the defaultVerticalAlignment with TableCellVerticalAlignment.baseline'),
+       assert(() {
+         if (children.any((TableRow row) => row.children == null)) {
+           throw FlutterError(
+             'One of the rows of the table had null children.\n'
+             'The children property of TableRow must not be null.'
+           );
+         }
+         return true;
+       }()),
+       assert(() {
+         if (children.any((TableRow row) => row.children!.any((Widget cell) => cell == null))) {
+           throw FlutterError(
+             'One of the children of one of the rows of the table was null.\n'
+             'The children of a TableRow must not be null.'
+           );
+         }
+         return true;
+       }()),
+       assert(() {
+         if (children.any((TableRow row1) => row1.key != null && children.any((TableRow row2) => row1 != row2 && row1.key == row2.key))) {
+           throw FlutterError(
+             'Two or more TableRow children of this Table had the same key.\n'
+             'All the keyed TableRow children of a Table must have different Keys.'
+           );
+         }
+         return true;
+       }()),
+       assert(() {
+         if (children.isNotEmpty) {
+           final int cellCount = children.first.children!.length;
+           if (children.any((TableRow row) => row.children!.length != cellCount)) {
+             throw FlutterError(
+               'Table contains irregular row lengths.\n'
+               'Every TableRow in a Table must have the same number of children, so that every cell is filled. '
+               'Otherwise, the table will contain holes.'
+             );
+           }
+         }
+         return true;
+       }()),
+       _rowDecorations = children.any((TableRow row) => row.decoration != null)
+                              ? children.map<Decoration?>((TableRow row) => row.decoration).toList(growable: false)
+                              : null,
+       super(key: key) {
+    assert(() {
+      final List<Widget> flatChildren = children.expand<Widget>((TableRow row) => row.children!).toList(growable: false);
+      if (debugChildrenHaveDuplicateKeys(this, flatChildren)) {
+        throw FlutterError(
+          'Two or more cells in this Table contain widgets with the same key.\n'
+          'Every widget child of every TableRow in a Table must have different keys. The cells of a Table are '
+          'flattened out for processing, so separate cells cannot have duplicate keys even if they are in '
+          'different rows.'
+        );
+      }
+      return true;
+    }());
+  }
+
+  /// The rows of the table.
+  ///
+  /// Every row in a table must have the same number of children, and all the
+  /// children must be non-null.
+  final List<TableRow> children;
+
+  /// How the horizontal extents of the columns of this table should be determined.
+  ///
+  /// If the [Map] has a null entry for a given column, the table uses the
+  /// [defaultColumnWidth] instead. By default, that uses flex sizing to
+  /// distribute free space equally among the columns.
+  ///
+  /// The [FixedColumnWidth] class can be used to specify a specific width in
+  /// pixels. That is the cheapest way to size a table's columns.
+  ///
+  /// The layout performance of the table depends critically on which column
+  /// sizing algorithms are used here. In particular, [IntrinsicColumnWidth] is
+  /// quite expensive because it needs to measure each cell in the column to
+  /// determine the intrinsic size of the column.
+  ///
+  /// The keys of this map (column indexes) are zero-based.
+  ///
+  /// If this is set to null, then an empty map is assumed.
+  final Map<int, TableColumnWidth>? columnWidths;
+
+  /// How to determine with widths of columns that don't have an explicit sizing
+  /// algorithm.
+  ///
+  /// Specifically, the [defaultColumnWidth] is used for column `i` if
+  /// `columnWidths[i]` is null. Defaults to [FlexColumnWidth], which will
+  /// divide the remaining horizontal space up evenly between columns of the
+  /// same type [TableColumnWidth].
+  ///
+  /// A [Table] in a horizontal [ScrollView] must use a [FixedColumnWidth], or
+  /// an [IntrinsicColumnWidth] as the horizontal space is infinite.
+  final TableColumnWidth defaultColumnWidth;
+
+  /// The direction in which the columns are ordered.
+  ///
+  /// Defaults to the ambient [Directionality].
+  final TextDirection? textDirection;
+
+  /// The style to use when painting the boundary and interior divisions of the table.
+  final TableBorder? border;
+
+  /// How cells that do not explicitly specify a vertical alignment are aligned vertically.
+  ///
+  /// Cells may specify a vertical alignment by wrapping their contents in a
+  /// [TableCell] widget.
+  final TableCellVerticalAlignment defaultVerticalAlignment;
+
+  /// The text baseline to use when aligning rows using [TableCellVerticalAlignment.baseline].
+  ///
+  /// This must be set if using baseline alignment. There is no default because there is no
+  /// way for the framework to know the correct baseline _a priori_.
+  final TextBaseline? textBaseline;
+
+  final List<Decoration?>? _rowDecorations;
+
+  @override
+  _TableElement createElement() => _TableElement(this);
+
+  @override
+  RenderTable createRenderObject(BuildContext context) {
+    assert(debugCheckHasDirectionality(context));
+    return RenderTable(
+      columns: children.isNotEmpty ? children[0].children!.length : 0,
+      rows: children.length,
+      columnWidths: columnWidths,
+      defaultColumnWidth: defaultColumnWidth,
+      textDirection: textDirection ?? Directionality.of(context),
+      border: border,
+      rowDecorations: _rowDecorations,
+      configuration: createLocalImageConfiguration(context),
+      defaultVerticalAlignment: defaultVerticalAlignment,
+      textBaseline: textBaseline,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderTable renderObject) {
+    assert(debugCheckHasDirectionality(context));
+    assert(renderObject.columns == (children.isNotEmpty ? children[0].children!.length : 0));
+    assert(renderObject.rows == children.length);
+    renderObject
+      ..columnWidths = columnWidths
+      ..defaultColumnWidth = defaultColumnWidth
+      ..textDirection = textDirection ?? Directionality.of(context)
+      ..border = border
+      ..rowDecorations = _rowDecorations
+      ..configuration = createLocalImageConfiguration(context)
+      ..defaultVerticalAlignment = defaultVerticalAlignment
+      ..textBaseline = textBaseline;
+  }
+}
+
+class _TableElement extends RenderObjectElement {
+  _TableElement(Table widget) : super(widget);
+
+  @override
+  Table get widget => super.widget as Table;
+
+  @override
+  RenderTable get renderObject => super.renderObject as RenderTable;
+
+  // This class ignores the child's slot entirely.
+  // Instead of doing incremental updates to the child list, it replaces the entire list each frame.
+
+  List<_TableElementRow> _children = const<_TableElementRow>[];
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    _children = widget.children.map<_TableElementRow>((TableRow row) {
+      return _TableElementRow(
+        key: row.key,
+        children: row.children!.map<Element>((Widget child) {
+          assert(child != null);
+          return inflateWidget(child, null);
+        }).toList(growable: false),
+      );
+    }).toList(growable: false);
+    _updateRenderObjectChildren();
+  }
+
+  @override
+  void insertRenderObjectChild(RenderObject child, dynamic slot) {
+    renderObject.setupParentData(child);
+  }
+
+  @override
+  void moveRenderObjectChild(RenderObject child, dynamic oldSlot, dynamic newSlot) {
+  }
+
+  @override
+  void removeRenderObjectChild(RenderObject child, dynamic slot) {
+    final TableCellParentData childParentData = child.parentData! as TableCellParentData;
+    renderObject.setChild(childParentData.x!, childParentData.y!, null);
+  }
+
+  final Set<Element> _forgottenChildren = HashSet<Element>();
+
+  @override
+  void update(Table newWidget) {
+    final Map<LocalKey, List<Element>> oldKeyedRows = <LocalKey, List<Element>>{};
+    for (final _TableElementRow row in _children) {
+      if (row.key != null) {
+        oldKeyedRows[row.key!] = row.children;
+      }
+    }
+    final Iterator<_TableElementRow> oldUnkeyedRows = _children.where((_TableElementRow row) => row.key == null).iterator;
+    final List<_TableElementRow> newChildren = <_TableElementRow>[];
+    final Set<List<Element>> taken = <List<Element>>{};
+    for (final TableRow row in newWidget.children) {
+      List<Element> oldChildren;
+      if (row.key != null && oldKeyedRows.containsKey(row.key)) {
+        oldChildren = oldKeyedRows[row.key]!;
+        taken.add(oldChildren);
+      } else if (row.key == null && oldUnkeyedRows.moveNext()) {
+        oldChildren = oldUnkeyedRows.current.children;
+      } else {
+        oldChildren = const <Element>[];
+      }
+      newChildren.add(_TableElementRow(
+        key: row.key,
+        children: updateChildren(oldChildren, row.children!, forgottenChildren: _forgottenChildren),
+      ));
+    }
+    while (oldUnkeyedRows.moveNext())
+      updateChildren(oldUnkeyedRows.current.children, const <Widget>[], forgottenChildren: _forgottenChildren);
+    for (final List<Element> oldChildren in oldKeyedRows.values.where((List<Element> list) => !taken.contains(list)))
+      updateChildren(oldChildren, const <Widget>[], forgottenChildren: _forgottenChildren);
+
+    _children = newChildren;
+    _updateRenderObjectChildren();
+    _forgottenChildren.clear();
+    super.update(newWidget);
+    assert(widget == newWidget);
+  }
+
+  void _updateRenderObjectChildren() {
+    assert(renderObject != null);
+    renderObject.setFlatChildren(
+      _children.isNotEmpty ? _children[0].children.length : 0,
+      _children.expand<RenderBox>((_TableElementRow row) {
+        return row.children.map<RenderBox>((Element child) {
+          final RenderBox box = child.renderObject! as RenderBox;
+          return box;
+        });
+      }).toList(),
+    );
+  }
+
+  @override
+  void visitChildren(ElementVisitor visitor) {
+    for (final Element child in _children.expand<Element>((_TableElementRow row) => row.children)) {
+      if (!_forgottenChildren.contains(child))
+        visitor(child);
+    }
+  }
+
+  @override
+  bool forgetChild(Element child) {
+    _forgottenChildren.add(child);
+    super.forgetChild(child);
+    return true;
+  }
+}
+
+/// A widget that controls how a child of a [Table] is aligned.
+///
+/// A [TableCell] widget must be a descendant of a [Table], and the path from
+/// the [TableCell] widget to its enclosing [Table] must contain only
+/// [TableRow]s, [StatelessWidget]s, or [StatefulWidget]s (not
+/// other kinds of widgets, like [RenderObjectWidget]s).
+class TableCell extends ParentDataWidget<TableCellParentData> {
+  /// Creates a widget that controls how a child of a [Table] is aligned.
+  const TableCell({
+    Key? key,
+    this.verticalAlignment,
+    required Widget child,
+  }) : super(key: key, child: child);
+
+  /// How this cell is aligned vertically.
+  final TableCellVerticalAlignment? verticalAlignment;
+
+  @override
+  void applyParentData(RenderObject renderObject) {
+    final TableCellParentData parentData = renderObject.parentData! as TableCellParentData;
+    if (parentData.verticalAlignment != verticalAlignment) {
+      parentData.verticalAlignment = verticalAlignment;
+      final AbstractNode? targetParent = renderObject.parent;
+      if (targetParent is RenderObject)
+        targetParent.markNeedsLayout();
+    }
+  }
+
+  @override
+  Type get debugTypicalAncestorWidgetClass => Table;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<TableCellVerticalAlignment>('verticalAlignment', verticalAlignment));
+  }
+}
diff --git a/lib/src/widgets/text.dart b/lib/src/widgets/text.dart
new file mode 100644
index 0000000..90dc632
--- /dev/null
+++ b/lib/src/widgets/text.dart
@@ -0,0 +1,576 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui show TextHeightBehavior;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'inherited_theme.dart';
+import 'media_query.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+// String _name;
+
+/// The text style to apply to descendant [Text] widgets which don't have an
+/// explicit style.
+///
+/// See also:
+///
+///  * [AnimatedDefaultTextStyle], which animates changes in the text style
+///    smoothly over a given duration.
+///  * [DefaultTextStyleTransition], which takes a provided [Animation] to
+///    animate changes in text style smoothly over time.
+class DefaultTextStyle extends InheritedTheme {
+  /// Creates a default text style for the given subtree.
+  ///
+  /// Consider using [DefaultTextStyle.merge] to inherit styling information
+  /// from the current default text style for a given [BuildContext].
+  ///
+  /// The [style] and [child] arguments are required and must not be null.
+  ///
+  /// The [softWrap] and [overflow] arguments must not be null (though they do
+  /// have default values).
+  ///
+  /// The [maxLines] property may be null (and indeed defaults to null), but if
+  /// it is not null, it must be greater than zero.
+  const DefaultTextStyle({
+    Key? key,
+    required this.style,
+    this.textAlign,
+    this.softWrap = true,
+    this.overflow = TextOverflow.clip,
+    this.maxLines,
+    this.textWidthBasis = TextWidthBasis.parent,
+    this.textHeightBehavior,
+    required Widget child,
+  }) : assert(style != null),
+       assert(softWrap != null),
+       assert(overflow != null),
+       assert(maxLines == null || maxLines > 0),
+       assert(child != null),
+       assert(textWidthBasis != null),
+       super(key: key, child: child);
+
+  /// A const-constructable default text style that provides fallback values.
+  ///
+  /// Returned from [of] when the given [BuildContext] doesn't have an enclosing default text style.
+  ///
+  /// This constructor creates a [DefaultTextStyle] with an invalid [child], which
+  /// means the constructed value cannot be incorporated into the tree.
+  const DefaultTextStyle.fallback({ Key? key })
+    : style = const TextStyle(),
+      textAlign = null,
+      softWrap = true,
+      maxLines = null,
+      overflow = TextOverflow.clip,
+      textWidthBasis = TextWidthBasis.parent,
+      textHeightBehavior = null,
+      super(key: key, child: const _NullWidget());
+
+  /// Creates a default text style that overrides the text styles in scope at
+  /// this point in the widget tree.
+  ///
+  /// The given [style] is merged with the [style] from the default text style
+  /// for the [BuildContext] where the widget is inserted, and any of the other
+  /// arguments that are not null replace the corresponding properties on that
+  /// same default text style.
+  ///
+  /// This constructor cannot be used to override the [maxLines] property of the
+  /// ancestor with the value null, since null here is used to mean "defer to
+  /// ancestor". To replace a non-null [maxLines] from an ancestor with the null
+  /// value (to remove the restriction on number of lines), manually obtain the
+  /// ambient [DefaultTextStyle] using [DefaultTextStyle.of], then create a new
+  /// [DefaultTextStyle] using the [new DefaultTextStyle] constructor directly.
+  /// See the source below for an example of how to do this (since that's
+  /// essentially what this constructor does).
+  static Widget merge({
+    Key? key,
+    TextStyle? style,
+    TextAlign? textAlign,
+    bool? softWrap,
+    TextOverflow? overflow,
+    int? maxLines,
+    TextWidthBasis? textWidthBasis,
+    required Widget child,
+  }) {
+    assert(child != null);
+    return Builder(
+      builder: (BuildContext context) {
+        final DefaultTextStyle parent = DefaultTextStyle.of(context);
+        return DefaultTextStyle(
+          key: key,
+          style: parent.style.merge(style),
+          textAlign: textAlign ?? parent.textAlign,
+          softWrap: softWrap ?? parent.softWrap,
+          overflow: overflow ?? parent.overflow,
+          maxLines: maxLines ?? parent.maxLines,
+          textWidthBasis: textWidthBasis ?? parent.textWidthBasis,
+          child: child,
+        );
+      },
+    );
+  }
+
+  /// The text style to apply.
+  final TextStyle style;
+
+  /// How each line of text in the Text widget should be aligned horizontally.
+  final TextAlign? textAlign;
+
+  /// Whether the text should break at soft line breaks.
+  ///
+  /// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space.
+  ///
+  /// This also decides the [overflow] property's behavior. If this is true or null,
+  /// the glyph causing overflow, and those that follow, will not be rendered.
+  final bool softWrap;
+
+  /// How visual overflow should be handled.
+  ///
+  /// If [softWrap] is true or null, the glyph causing overflow, and those that follow,
+  /// will not be rendered. Otherwise, it will be shown with the given overflow option.
+  final TextOverflow overflow;
+
+  /// An optional maximum number of lines for the text to span, wrapping if necessary.
+  /// If the text exceeds the given number of lines, it will be truncated according
+  /// to [overflow].
+  ///
+  /// If this is 1, text will not wrap. Otherwise, text will be wrapped at the
+  /// edge of the box.
+  ///
+  /// If this is non-null, it will override even explicit null values of
+  /// [Text.maxLines].
+  final int? maxLines;
+
+  /// The strategy to use when calculating the width of the Text.
+  ///
+  /// See [TextWidthBasis] for possible values and their implications.
+  final TextWidthBasis textWidthBasis;
+
+  /// {@macro flutter.dart:ui.textHeightBehavior}
+  final ui.TextHeightBehavior? textHeightBehavior;
+
+  /// The closest instance of this class that encloses the given context.
+  ///
+  /// If no such instance exists, returns an instance created by
+  /// [DefaultTextStyle.fallback], which contains fallback values.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// DefaultTextStyle style = DefaultTextStyle.of(context);
+  /// ```
+  static DefaultTextStyle of(BuildContext context) {
+    return context.dependOnInheritedWidgetOfExactType<DefaultTextStyle>() ?? const DefaultTextStyle.fallback();
+  }
+
+  @override
+  bool updateShouldNotify(DefaultTextStyle oldWidget) {
+    return style != oldWidget.style ||
+        textAlign != oldWidget.textAlign ||
+        softWrap != oldWidget.softWrap ||
+        overflow != oldWidget.overflow ||
+        maxLines != oldWidget.maxLines ||
+        textWidthBasis != oldWidget.textWidthBasis ||
+        textHeightBehavior != oldWidget.textHeightBehavior;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return DefaultTextStyle(
+      style: style,
+      textAlign: textAlign,
+      softWrap: softWrap,
+      overflow: overflow,
+      maxLines: maxLines,
+      textWidthBasis: textWidthBasis,
+      textHeightBehavior: textHeightBehavior,
+      child: child,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    style.debugFillProperties(properties);
+    properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
+    properties.add(FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true));
+    properties.add(EnumProperty<TextOverflow>('overflow', overflow, defaultValue: null));
+    properties.add(IntProperty('maxLines', maxLines, defaultValue: null));
+    properties.add(EnumProperty<TextWidthBasis>('textWidthBasis', textWidthBasis, defaultValue: TextWidthBasis.parent));
+    properties.add(DiagnosticsProperty<ui.TextHeightBehavior>('textHeightBehavior', textHeightBehavior, defaultValue: null));
+  }
+}
+
+class _NullWidget extends StatelessWidget {
+  const _NullWidget();
+
+  @override
+  Widget build(BuildContext context) {
+    throw FlutterError(
+      'A DefaultTextStyle constructed with DefaultTextStyle.fallback cannot be incorporated into the widget tree, '
+      'it is meant only to provide a fallback value returned by DefaultTextStyle.of() '
+      'when no enclosing default text style is present in a BuildContext.'
+    );
+  }
+}
+
+/// The [TextHeightBehavior] that will apply to descendant [Text] and [EditableText]
+/// widgets which have not explicitly set [Text.textHeightBehavior].
+///
+/// If there is a [DefaultTextStyle] with a non-null [DefaultTextStyle.textHeightBehavior]
+/// below this widget, the [DefaultTextStyle.textHeightBehavior] will be used
+/// over this widget's [TextHeightBehavior].
+///
+/// See also:
+///
+///  * [DefaultTextStyle], which defines a [TextStyle] to apply to descendant
+///    [Text] widgets.
+class DefaultTextHeightBehavior extends InheritedTheme {
+  /// Creates a default text height behavior for the given subtree.
+  ///
+  /// The [textHeightBehavior] and [child] arguments are required and must not be null.
+  const DefaultTextHeightBehavior({
+    Key? key,
+    required this.textHeightBehavior,
+    required Widget child,
+  }) :  assert(textHeightBehavior != null),
+        assert(child != null),
+        super(key: key, child: child);
+
+  /// {@macro flutter.dart:ui.textHeightBehavior}
+  final TextHeightBehavior textHeightBehavior;
+
+  /// The closest instance of this class that encloses the given context.
+  ///
+  /// If no such instance exists, this method will return `null`.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// DefaultTextHeightBehavior defaultTextHeightBehavior = DefaultTextHeightBehavior.of(context);
+  /// ```
+  static TextHeightBehavior? of(BuildContext context) {
+    return context.dependOnInheritedWidgetOfExactType<DefaultTextHeightBehavior>()?.textHeightBehavior;
+  }
+
+  @override
+  bool updateShouldNotify(DefaultTextHeightBehavior oldWidget) {
+    return textHeightBehavior != oldWidget.textHeightBehavior;
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return DefaultTextHeightBehavior(
+      textHeightBehavior: textHeightBehavior,
+      child: child,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<ui.TextHeightBehavior>('textHeightBehavior', textHeightBehavior, defaultValue: null));
+  }
+}
+
+/// A run of text with a single style.
+///
+/// The [Text] widget displays a string of text with single style. The string
+/// might break across multiple lines or might all be displayed on the same line
+/// depending on the layout constraints.
+///
+/// The [style] argument is optional. When omitted, the text will use the style
+/// from the closest enclosing [DefaultTextStyle]. If the given style's
+/// [TextStyle.inherit] property is true (the default), the given style will
+/// be merged with the closest enclosing [DefaultTextStyle]. This merging
+/// behavior is useful, for example, to make the text bold while using the
+/// default font family and size.
+///
+/// {@tool snippet}
+///
+/// This example shows how to display text using the [Text] widget with the
+/// [overflow] set to [TextOverflow.ellipsis].
+///
+/// ![If the text is shorter than the available space, it is displayed in full without an ellipsis.](https://flutter.github.io/assets-for-api-docs/assets/widgets/text.png)
+///
+/// ![If the text overflows, the Text widget displays an ellipsis to trim the overflowing text](https://flutter.github.io/assets-for-api-docs/assets/widgets/text_ellipsis.png)
+///
+/// ```dart
+/// Text(
+///   'Hello, $_name! How are you?',
+///   textAlign: TextAlign.center,
+///   overflow: TextOverflow.ellipsis,
+///   style: TextStyle(fontWeight: FontWeight.bold),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// Using the [Text.rich] constructor, the [Text] widget can
+/// display a paragraph with differently styled [TextSpan]s. The sample
+/// that follows displays "Hello beautiful world" with different styles
+/// for each word.
+///
+/// {@tool snippet}
+///
+/// ![The word "Hello" is shown with the default text styles. The word "beautiful" is italicized. The word "world" is bold.](https://flutter.github.io/assets-for-api-docs/assets/widgets/text_rich.png)
+///
+/// ```dart
+/// const Text.rich(
+///   TextSpan(
+///     text: 'Hello', // default text style
+///     children: <TextSpan>[
+///       TextSpan(text: ' beautiful ', style: TextStyle(fontStyle: FontStyle.italic)),
+///       TextSpan(text: 'world', style: TextStyle(fontWeight: FontWeight.bold)),
+///     ],
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// ## Interactivity
+///
+/// To make [Text] react to touch events, wrap it in a [GestureDetector] widget
+/// with a [GestureDetector.onTap] handler.
+///
+/// In a material design application, consider using a [TextButton] instead, or
+/// if that isn't appropriate, at least using an [InkWell] instead of
+/// [GestureDetector].
+///
+/// To make sections of the text interactive, use [RichText] and specify a
+/// [TapGestureRecognizer] as the [TextSpan.recognizer] of the relevant part of
+/// the text.
+///
+/// See also:
+///
+///  * [RichText], which gives you more control over the text styles.
+///  * [DefaultTextStyle], which sets default styles for [Text] widgets.
+class Text extends StatelessWidget {
+  /// Creates a text widget.
+  ///
+  /// If the [style] argument is null, the text will use the style from the
+  /// closest enclosing [DefaultTextStyle].
+  ///
+  /// The [data] parameter must not be null.
+  ///
+  /// The [overflow] property's behavior is affected by the [softWrap] argument.
+  /// If the [softWrap] is true or null, the glyph causing overflow, and those that follow,
+  /// will not be rendered. Otherwise, it will be shown with the given overflow option.
+  const Text(
+    String this.data, {
+    Key? key,
+    this.style,
+    this.strutStyle,
+    this.textAlign,
+    this.textDirection,
+    this.locale,
+    this.softWrap,
+    this.overflow,
+    this.textScaleFactor,
+    this.maxLines,
+    this.semanticsLabel,
+    this.textWidthBasis,
+    this.textHeightBehavior,
+  }) : assert(
+         data != null,
+         'A non-null String must be provided to a Text widget.',
+       ),
+       textSpan = null,
+       super(key: key);
+
+  /// Creates a text widget with a [InlineSpan].
+  ///
+  /// The following subclasses of [InlineSpan] may be used to build rich text:
+  ///
+  /// * [TextSpan]s define text and children [InlineSpan]s.
+  /// * [WidgetSpan]s define embedded inline widgets.
+  ///
+  /// The [textSpan] parameter must not be null.
+  ///
+  /// See [RichText] which provides a lower-level way to draw text.
+  const Text.rich(
+    InlineSpan this.textSpan, {
+    Key? key,
+    this.style,
+    this.strutStyle,
+    this.textAlign,
+    this.textDirection,
+    this.locale,
+    this.softWrap,
+    this.overflow,
+    this.textScaleFactor,
+    this.maxLines,
+    this.semanticsLabel,
+    this.textWidthBasis,
+    this.textHeightBehavior,
+  }) : assert(
+         textSpan != null,
+         'A non-null TextSpan must be provided to a Text.rich widget.',
+       ),
+       data = null,
+       super(key: key);
+
+  /// The text to display.
+  ///
+  /// This will be null if a [textSpan] is provided instead.
+  final String? data;
+
+  /// The text to display as a [InlineSpan].
+  ///
+  /// This will be null if [data] is provided instead.
+  final InlineSpan? textSpan;
+
+  /// If non-null, the style to use for this text.
+  ///
+  /// If the style's "inherit" property is true, the style will be merged with
+  /// the closest enclosing [DefaultTextStyle]. Otherwise, the style will
+  /// replace the closest enclosing [DefaultTextStyle].
+  final TextStyle? style;
+
+  /// {@macro flutter.painting.textPainter.strutStyle}
+  final StrutStyle? strutStyle;
+
+  /// How the text should be aligned horizontally.
+  final TextAlign? textAlign;
+
+  /// The directionality of the text.
+  ///
+  /// This decides how [textAlign] values like [TextAlign.start] and
+  /// [TextAlign.end] are interpreted.
+  ///
+  /// This is also used to disambiguate how to render bidirectional text. For
+  /// example, if the [data] is an English phrase followed by a Hebrew phrase,
+  /// in a [TextDirection.ltr] context the English phrase will be on the left
+  /// and the Hebrew phrase to its right, while in a [TextDirection.rtl]
+  /// context, the English phrase will be on the right and the Hebrew phrase on
+  /// its left.
+  ///
+  /// Defaults to the ambient [Directionality], if any.
+  final TextDirection? textDirection;
+
+  /// Used to select a font when the same Unicode character can
+  /// be rendered differently, depending on the locale.
+  ///
+  /// It's rarely necessary to set this property. By default its value
+  /// is inherited from the enclosing app with `Localizations.localeOf(context)`.
+  ///
+  /// See [RenderParagraph.locale] for more information.
+  final Locale? locale;
+
+  /// Whether the text should break at soft line breaks.
+  ///
+  /// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space.
+  final bool? softWrap;
+
+  /// How visual overflow should be handled.
+  ///
+  /// Defaults to retrieving the value from the nearest [DefaultTextStyle] ancestor.
+  final TextOverflow? overflow;
+
+  /// The number of font pixels for each logical pixel.
+  ///
+  /// For example, if the text scale factor is 1.5, text will be 50% larger than
+  /// the specified font size.
+  ///
+  /// The value given to the constructor as textScaleFactor. If null, will
+  /// use the [MediaQueryData.textScaleFactor] obtained from the ambient
+  /// [MediaQuery], or 1.0 if there is no [MediaQuery] in scope.
+  final double? textScaleFactor;
+
+  /// An optional maximum number of lines for the text to span, wrapping if necessary.
+  /// If the text exceeds the given number of lines, it will be truncated according
+  /// to [overflow].
+  ///
+  /// If this is 1, text will not wrap. Otherwise, text will be wrapped at the
+  /// edge of the box.
+  ///
+  /// If this is null, but there is an ambient [DefaultTextStyle] that specifies
+  /// an explicit number for its [DefaultTextStyle.maxLines], then the
+  /// [DefaultTextStyle] value will take precedence. You can use a [RichText]
+  /// widget directly to entirely override the [DefaultTextStyle].
+  final int? maxLines;
+
+  /// An alternative semantics label for this text.
+  ///
+  /// If present, the semantics of this widget will contain this value instead
+  /// of the actual text. This will overwrite any of the semantics labels applied
+  /// directly to the [TextSpan]s.
+  ///
+  /// This is useful for replacing abbreviations or shorthands with the full
+  /// text value:
+  ///
+  /// ```dart
+  /// Text(r'$$', semanticsLabel: 'Double dollars')
+  /// ```
+  final String? semanticsLabel;
+
+  /// {@macro flutter.painting.textPainter.textWidthBasis}
+  final TextWidthBasis? textWidthBasis;
+
+  /// {@macro flutter.dart:ui.textHeightBehavior}
+  final ui.TextHeightBehavior? textHeightBehavior;
+
+  @override
+  Widget build(BuildContext context) {
+    final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
+    TextStyle? effectiveTextStyle = style;
+    if (style == null || style!.inherit)
+      effectiveTextStyle = defaultTextStyle.style.merge(style);
+    if (MediaQuery.boldTextOverride(context))
+      effectiveTextStyle = effectiveTextStyle!.merge(const TextStyle(fontWeight: FontWeight.bold));
+    Widget result = RichText(
+      textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
+      textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
+      locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is null
+      softWrap: softWrap ?? defaultTextStyle.softWrap,
+      overflow: overflow ?? defaultTextStyle.overflow,
+      textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
+      maxLines: maxLines ?? defaultTextStyle.maxLines,
+      strutStyle: strutStyle,
+      textWidthBasis: textWidthBasis ?? defaultTextStyle.textWidthBasis,
+      textHeightBehavior: textHeightBehavior ?? defaultTextStyle.textHeightBehavior ?? DefaultTextHeightBehavior.of(context),
+      text: TextSpan(
+        style: effectiveTextStyle,
+        text: data,
+        children: textSpan != null ? <InlineSpan>[textSpan!] : null,
+      ),
+    );
+    if (semanticsLabel != null) {
+      result = Semantics(
+        textDirection: textDirection,
+        label: semanticsLabel,
+        child: ExcludeSemantics(
+          child: result,
+        ),
+      );
+    }
+    return result;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(StringProperty('data', data, showName: false));
+    if (textSpan != null) {
+      properties.add(textSpan!.toDiagnosticsNode(name: 'textSpan', style: DiagnosticsTreeStyle.transition));
+    }
+    style?.debugFillProperties(properties);
+    properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
+    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
+    properties.add(DiagnosticsProperty<Locale>('locale', locale, defaultValue: null));
+    properties.add(FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true));
+    properties.add(EnumProperty<TextOverflow>('overflow', overflow, defaultValue: null));
+    properties.add(DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null));
+    properties.add(IntProperty('maxLines', maxLines, defaultValue: null));
+    properties.add(EnumProperty<TextWidthBasis>('textWidthBasis', textWidthBasis, defaultValue: null));
+    properties.add(DiagnosticsProperty<ui.TextHeightBehavior>('textHeightBehavior', textHeightBehavior, defaultValue: null));
+    if (semanticsLabel != null) {
+      properties.add(StringProperty('semanticsLabel', semanticsLabel));
+    }
+  }
+}
diff --git a/lib/src/widgets/text_selection.dart b/lib/src/widgets/text_selection.dart
new file mode 100644
index 0000000..ef10f8b
--- /dev/null
+++ b/lib/src/widgets/text_selection.dart
@@ -0,0 +1,1617 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:math' as math;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+import 'package:flute/services.dart';
+
+import 'basic.dart';
+import 'binding.dart';
+import 'constants.dart';
+import 'container.dart';
+import 'editable_text.dart';
+import 'framework.dart';
+import 'gesture_detector.dart';
+import 'overlay.dart';
+import 'ticker_provider.dart';
+import 'transitions.dart';
+import 'visibility.dart';
+
+export 'package:flute/services.dart' show TextSelectionDelegate;
+
+/// A duration that controls how often the drag selection update callback is
+/// called.
+const Duration _kDragSelectionUpdateThrottle = Duration(milliseconds: 50);
+
+/// Which type of selection handle to be displayed.
+///
+/// With mixed-direction text, both handles may be the same type. Examples:
+///
+/// * LTR text: 'the &lt;quick brown&gt; fox':
+///
+///   The '&lt;' is drawn with the [left] type, the '&gt;' with the [right]
+///
+/// * RTL text: 'XOF &lt;NWORB KCIUQ&gt; EHT':
+///
+///   Same as above.
+///
+/// * mixed text: '&lt;the NWOR&lt;B KCIUQ fox'
+///
+///   Here 'the QUICK B' is selected, but 'QUICK BROWN' is RTL. Both are drawn
+///   with the [left] type.
+///
+/// See also:
+///
+///  * [TextDirection], which discusses left-to-right and right-to-left text in
+///    more detail.
+enum TextSelectionHandleType {
+  /// The selection handle is to the left of the selection end point.
+  left,
+
+  /// The selection handle is to the right of the selection end point.
+  right,
+
+  /// The start and end of the selection are co-incident at this point.
+  collapsed,
+}
+
+/// The text position that a give selection handle manipulates. Dragging the
+/// [start] handle always moves the [start]/[baseOffset] of the selection.
+enum _TextSelectionHandlePosition { start, end }
+
+/// Signature for when a pointer that's dragging to select text has moved again.
+///
+/// The first argument [startDetails] contains the details of the event that
+/// initiated the dragging.
+///
+/// The second argument [updateDetails] contains the details of the current
+/// pointer movement. It's the same as the one passed to [DragGestureRecognizer.onUpdate].
+///
+/// This signature is different from [GestureDragUpdateCallback] to make it
+/// easier for various text fields to use [TextSelectionGestureDetector] without
+/// having to store the start position.
+typedef DragSelectionUpdateCallback = void Function(DragStartDetails startDetails, DragUpdateDetails updateDetails);
+
+/// ParentData that determines whether or not to paint the corresponding child.
+///
+/// Used in the layout of the Cupertino and Material text selection menus, which
+/// decide whether or not to paint their buttons after laying them out and
+/// determining where they overflow.
+class ToolbarItemsParentData extends ContainerBoxParentData<RenderBox> {
+  /// Whether or not this child is painted.
+  ///
+  /// Children in the selection toolbar may be laid out for measurement purposes
+  /// but not painted. This allows these children to be identified.
+  bool shouldPaint = false;
+
+  @override
+  String toString() => '${super.toString()}; shouldPaint=$shouldPaint';
+}
+
+/// An interface for building the selection UI, to be provided by the
+/// implementor of the toolbar widget.
+///
+/// Override text operations such as [handleCut] if needed.
+abstract class TextSelectionControls {
+  /// Builds a selection handle of the given type.
+  ///
+  /// The top left corner of this widget is positioned at the bottom of the
+  /// selection position.
+  Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textLineHeight);
+
+  /// Get the anchor point of the handle relative to itself. The anchor point is
+  /// the point that is aligned with a specific point in the text. A handle
+  /// often visually "points to" that location.
+  Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight);
+
+  /// Builds a toolbar near a text selection.
+  ///
+  /// Typically displays buttons for copying and pasting text.
+  ///
+  /// [globalEditableRegion] is the TextField size of the global coordinate system
+  /// in logical pixels.
+  ///
+  /// [textLineHeight] is the `preferredLineHeight` of the [RenderEditable] we
+  /// are building a toolbar for.
+  ///
+  /// The [position] is a general calculation midpoint parameter of the toolbar.
+  /// If you want more detailed position information, can use [endpoints]
+  /// to calculate it.
+  Widget buildToolbar(
+    BuildContext context,
+    Rect globalEditableRegion,
+    double textLineHeight,
+    Offset position,
+    List<TextSelectionPoint> endpoints,
+    TextSelectionDelegate delegate,
+    ClipboardStatusNotifier clipboardStatus,
+  );
+
+  /// Returns the size of the selection handle.
+  Size getHandleSize(double textLineHeight);
+
+  /// Whether the current selection of the text field managed by the given
+  /// `delegate` can be removed from the text field and placed into the
+  /// [Clipboard].
+  ///
+  /// By default, false is returned when nothing is selected in the text field.
+  ///
+  /// Subclasses can use this to decide if they should expose the cut
+  /// functionality to the user.
+  bool canCut(TextSelectionDelegate delegate) {
+    return delegate.cutEnabled && !delegate.textEditingValue.selection.isCollapsed;
+  }
+
+  /// Whether the current selection of the text field managed by the given
+  /// `delegate` can be copied to the [Clipboard].
+  ///
+  /// By default, false is returned when nothing is selected in the text field.
+  ///
+  /// Subclasses can use this to decide if they should expose the copy
+  /// functionality to the user.
+  bool canCopy(TextSelectionDelegate delegate) {
+    return delegate.copyEnabled && !delegate.textEditingValue.selection.isCollapsed;
+  }
+
+  /// Whether the text field managed by the given `delegate` supports pasting
+  /// from the clipboard.
+  ///
+  /// Subclasses can use this to decide if they should expose the paste
+  /// functionality to the user.
+  ///
+  /// This does not consider the contents of the clipboard. Subclasses may want
+  /// to, for example, disallow pasting when the clipboard contains an empty
+  /// string.
+  bool canPaste(TextSelectionDelegate delegate) {
+    return delegate.pasteEnabled;
+  }
+
+  /// Whether the current selection of the text field managed by the given
+  /// `delegate` can be extended to include the entire content of the text
+  /// field.
+  ///
+  /// Subclasses can use this to decide if they should expose the select all
+  /// functionality to the user.
+  bool canSelectAll(TextSelectionDelegate delegate) {
+    return delegate.selectAllEnabled && delegate.textEditingValue.text.isNotEmpty && delegate.textEditingValue.selection.isCollapsed;
+  }
+
+  /// Copy the current selection of the text field managed by the given
+  /// `delegate` to the [Clipboard]. Then, remove the selected text from the
+  /// text field and hide the toolbar.
+  ///
+  /// This is called by subclasses when their cut affordance is activated by
+  /// the user.
+  void handleCut(TextSelectionDelegate delegate) {
+    final TextEditingValue value = delegate.textEditingValue;
+    Clipboard.setData(ClipboardData(
+      text: value.selection.textInside(value.text),
+    ));
+    delegate.textEditingValue = TextEditingValue(
+      text: value.selection.textBefore(value.text)
+          + value.selection.textAfter(value.text),
+      selection: TextSelection.collapsed(
+        offset: value.selection.start
+      ),
+    );
+    delegate.bringIntoView(delegate.textEditingValue.selection.extent);
+    delegate.hideToolbar();
+  }
+
+  /// Copy the current selection of the text field managed by the given
+  /// `delegate` to the [Clipboard]. Then, move the cursor to the end of the
+  /// text (collapsing the selection in the process), and hide the toolbar.
+  ///
+  /// This is called by subclasses when their copy affordance is activated by
+  /// the user.
+  void handleCopy(TextSelectionDelegate delegate, ClipboardStatusNotifier? clipboardStatus) {
+    final TextEditingValue value = delegate.textEditingValue;
+    Clipboard.setData(ClipboardData(
+      text: value.selection.textInside(value.text),
+    ));
+    clipboardStatus?.update();
+    delegate.textEditingValue = TextEditingValue(
+      text: value.text,
+      selection: TextSelection.collapsed(offset: value.selection.end),
+    );
+    delegate.bringIntoView(delegate.textEditingValue.selection.extent);
+    delegate.hideToolbar();
+  }
+
+  /// Paste the current clipboard selection (obtained from [Clipboard]) into
+  /// the text field managed by the given `delegate`, replacing its current
+  /// selection, if any. Then, hide the toolbar.
+  ///
+  /// This is called by subclasses when their paste affordance is activated by
+  /// the user.
+  ///
+  /// This function is asynchronous since interacting with the clipboard is
+  /// asynchronous. Race conditions may exist with this API as currently
+  /// implemented.
+  // TODO(ianh): https://github.com/flutter/flutter/issues/11427
+  Future<void> handlePaste(TextSelectionDelegate delegate) async {
+    final TextEditingValue value = delegate.textEditingValue; // Snapshot the input before using `await`.
+    final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
+    if (data != null) {
+      delegate.textEditingValue = TextEditingValue(
+        text: value.selection.textBefore(value.text)
+            + data.text!
+            + value.selection.textAfter(value.text),
+        selection: TextSelection.collapsed(
+          offset: value.selection.start + data.text!.length
+        ),
+      );
+    }
+    delegate.bringIntoView(delegate.textEditingValue.selection.extent);
+    delegate.hideToolbar();
+  }
+
+  /// Adjust the selection of the text field managed by the given `delegate` so
+  /// that everything is selected.
+  ///
+  /// Does not hide the toolbar.
+  ///
+  /// This is called by subclasses when their select-all affordance is activated
+  /// by the user.
+  void handleSelectAll(TextSelectionDelegate delegate) {
+    delegate.textEditingValue = TextEditingValue(
+      text: delegate.textEditingValue.text,
+      selection: TextSelection(
+        baseOffset: 0,
+        extentOffset: delegate.textEditingValue.text.length,
+      ),
+    );
+    delegate.bringIntoView(delegate.textEditingValue.selection.extent);
+  }
+}
+
+/// An object that manages a pair of text selection handles.
+///
+/// The selection handles are displayed in the [Overlay] that most closely
+/// encloses the given [BuildContext].
+class TextSelectionOverlay {
+  /// Creates an object that manages overlay entries for selection handles.
+  ///
+  /// The [context] must not be null and must have an [Overlay] as an ancestor.
+  TextSelectionOverlay({
+    required TextEditingValue value,
+    required this.context,
+    this.debugRequiredFor,
+    required this.toolbarLayerLink,
+    required this.startHandleLayerLink,
+    required this.endHandleLayerLink,
+    required this.renderObject,
+    this.selectionControls,
+    bool handlesVisible = false,
+    this.selectionDelegate,
+    this.dragStartBehavior = DragStartBehavior.start,
+    this.onSelectionHandleTapped,
+    this.clipboardStatus,
+  }) : assert(value != null),
+       assert(context != null),
+       assert(handlesVisible != null),
+       _handlesVisible = handlesVisible,
+       _value = value {
+    final OverlayState? overlay = Overlay.of(context, rootOverlay: true);
+    assert(overlay != null,
+      'No Overlay widget exists above $context.\n'
+      'Usually the Navigator created by WidgetsApp provides the overlay. Perhaps your '
+      'app content was created above the Navigator with the WidgetsApp builder parameter.');
+    _toolbarController = AnimationController(duration: fadeDuration, vsync: overlay!);
+  }
+
+  /// The context in which the selection handles should appear.
+  ///
+  /// This context must have an [Overlay] as an ancestor because this object
+  /// will display the text selection handles in that [Overlay].
+  final BuildContext context;
+
+  /// Debugging information for explaining why the [Overlay] is required.
+  final Widget? debugRequiredFor;
+
+  /// The object supplied to the [CompositedTransformTarget] that wraps the text
+  /// field.
+  final LayerLink toolbarLayerLink;
+
+  /// The objects supplied to the [CompositedTransformTarget] that wraps the
+  /// location of start selection handle.
+  final LayerLink startHandleLayerLink;
+
+  /// The objects supplied to the [CompositedTransformTarget] that wraps the
+  /// location of end selection handle.
+  final LayerLink endHandleLayerLink;
+
+  // TODO(mpcomplete): what if the renderObject is removed or replaced, or
+  // moves? Not sure what cases I need to handle, or how to handle them.
+  /// The editable line in which the selected text is being displayed.
+  final RenderEditable renderObject;
+
+  /// Builds text selection handles and toolbar.
+  final TextSelectionControls? selectionControls;
+
+  /// The delegate for manipulating the current selection in the owning
+  /// text field.
+  final TextSelectionDelegate? selectionDelegate;
+
+  /// Determines the way that drag start behavior is handled.
+  ///
+  /// If set to [DragStartBehavior.start], handle drag behavior will
+  /// begin upon the detection of a drag gesture. If set to
+  /// [DragStartBehavior.down] it will begin when a down event is first detected.
+  ///
+  /// In general, setting this to [DragStartBehavior.start] will make drag
+  /// animation smoother and setting it to [DragStartBehavior.down] will make
+  /// drag behavior feel slightly more reactive.
+  ///
+  /// By default, the drag start behavior is [DragStartBehavior.start].
+  ///
+  /// See also:
+  ///
+  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
+  final DragStartBehavior dragStartBehavior;
+
+  /// {@template flutter.widgets.TextSelectionOverlay.onSelectionHandleTapped}
+  /// A callback that's invoked when a selection handle is tapped.
+  ///
+  /// Both regular taps and long presses invoke this callback, but a drag
+  /// gesture won't.
+  /// {@endtemplate}
+  final VoidCallback? onSelectionHandleTapped;
+
+  /// Maintains the status of the clipboard for determining if its contents can
+  /// be pasted or not.
+  ///
+  /// Useful because the actual value of the clipboard can only be checked
+  /// asynchronously (see [Clipboard.getData]).
+  final ClipboardStatusNotifier? clipboardStatus;
+
+  /// Controls the fade-in and fade-out animations for the toolbar and handles.
+  static const Duration fadeDuration = Duration(milliseconds: 150);
+
+  late AnimationController _toolbarController;
+  Animation<double> get _toolbarOpacity => _toolbarController.view;
+
+  /// Retrieve current value.
+  @visibleForTesting
+  TextEditingValue get value => _value;
+
+  TextEditingValue _value;
+
+  /// A pair of handles. If this is non-null, there are always 2, though the
+  /// second is hidden when the selection is collapsed.
+  List<OverlayEntry>? _handles;
+
+  /// A copy/paste toolbar.
+  OverlayEntry? _toolbar;
+
+  TextSelection get _selection => _value.selection;
+
+  /// Whether selection handles are visible.
+  ///
+  /// Set to false if you want to hide the handles. Use this property to show or
+  /// hide the handle without rebuilding them.
+  ///
+  /// If this method is called while the [SchedulerBinding.schedulerPhase] is
+  /// [SchedulerPhase.persistentCallbacks], i.e. during the build, layout, or
+  /// paint phases (see [WidgetsBinding.drawFrame]), then the update is delayed
+  /// until the post-frame callbacks phase. Otherwise the update is done
+  /// synchronously. This means that it is safe to call during builds, but also
+  /// that if you do call this during a build, the UI will not update until the
+  /// next frame (i.e. many milliseconds later).
+  ///
+  /// Defaults to false.
+  bool get handlesVisible => _handlesVisible;
+  bool _handlesVisible = false;
+  set handlesVisible(bool visible) {
+    assert(visible != null);
+    if (_handlesVisible == visible)
+      return;
+    _handlesVisible = visible;
+    // If we are in build state, it will be too late to update visibility.
+    // We will need to schedule the build in next frame.
+    if (SchedulerBinding.instance!.schedulerPhase == SchedulerPhase.persistentCallbacks) {
+      SchedulerBinding.instance!.addPostFrameCallback(_markNeedsBuild);
+    } else {
+      _markNeedsBuild();
+    }
+  }
+
+  /// Builds the handles by inserting them into the [context]'s overlay.
+  void showHandles() {
+    assert(_handles == null);
+    _handles = <OverlayEntry>[
+      OverlayEntry(builder: (BuildContext context) => _buildHandle(context, _TextSelectionHandlePosition.start)),
+      OverlayEntry(builder: (BuildContext context) => _buildHandle(context, _TextSelectionHandlePosition.end)),
+    ];
+
+    Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor)!.insertAll(_handles!);
+  }
+
+  /// Destroys the handles by removing them from overlay.
+  void hideHandles() {
+    if (_handles != null) {
+      _handles![0].remove();
+      _handles![1].remove();
+      _handles = null;
+    }
+  }
+
+  /// Shows the toolbar by inserting it into the [context]'s overlay.
+  void showToolbar() {
+    assert(_toolbar == null);
+    _toolbar = OverlayEntry(builder: _buildToolbar);
+    Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor)!.insert(_toolbar!);
+    _toolbarController.forward(from: 0.0);
+  }
+
+  /// Updates the overlay after the selection has changed.
+  ///
+  /// If this method is called while the [SchedulerBinding.schedulerPhase] is
+  /// [SchedulerPhase.persistentCallbacks], i.e. during the build, layout, or
+  /// paint phases (see [WidgetsBinding.drawFrame]), then the update is delayed
+  /// until the post-frame callbacks phase. Otherwise the update is done
+  /// synchronously. This means that it is safe to call during builds, but also
+  /// that if you do call this during a build, the UI will not update until the
+  /// next frame (i.e. many milliseconds later).
+  void update(TextEditingValue newValue) {
+    if (_value == newValue)
+      return;
+    _value = newValue;
+    if (SchedulerBinding.instance!.schedulerPhase == SchedulerPhase.persistentCallbacks) {
+      SchedulerBinding.instance!.addPostFrameCallback(_markNeedsBuild);
+    } else {
+      _markNeedsBuild();
+    }
+  }
+
+  /// Causes the overlay to update its rendering.
+  ///
+  /// This is intended to be called when the [renderObject] may have changed its
+  /// text metrics (e.g. because the text was scrolled).
+  void updateForScroll() {
+    _markNeedsBuild();
+  }
+
+  void _markNeedsBuild([ Duration? duration ]) {
+    if (_handles != null) {
+      _handles![0].markNeedsBuild();
+      _handles![1].markNeedsBuild();
+    }
+    _toolbar?.markNeedsBuild();
+  }
+
+  /// Whether the handles are currently visible.
+  bool get handlesAreVisible => _handles != null && handlesVisible;
+
+  /// Whether the toolbar is currently visible.
+  bool get toolbarIsVisible => _toolbar != null;
+
+  /// Hides the entire overlay including the toolbar and the handles.
+  void hide() {
+    if (_handles != null) {
+      _handles![0].remove();
+      _handles![1].remove();
+      _handles = null;
+    }
+    if (_toolbar != null) {
+      hideToolbar();
+    }
+  }
+
+  /// Hides the toolbar part of the overlay.
+  ///
+  /// To hide the whole overlay, see [hide].
+  void hideToolbar() {
+    assert(_toolbar != null);
+    _toolbarController.stop();
+    _toolbar!.remove();
+    _toolbar = null;
+  }
+
+  /// Final cleanup.
+  void dispose() {
+    hide();
+    _toolbarController.dispose();
+  }
+
+  Widget _buildHandle(BuildContext context, _TextSelectionHandlePosition position) {
+    if ((_selection.isCollapsed && position == _TextSelectionHandlePosition.end) ||
+         selectionControls == null)
+      return Container(); // hide the second handle when collapsed
+    return Visibility(
+      visible: handlesVisible,
+      child: _TextSelectionHandleOverlay(
+        onSelectionHandleChanged: (TextSelection newSelection) { _handleSelectionHandleChanged(newSelection, position); },
+        onSelectionHandleTapped: onSelectionHandleTapped,
+        startHandleLayerLink: startHandleLayerLink,
+        endHandleLayerLink: endHandleLayerLink,
+        renderObject: renderObject,
+        selection: _selection,
+        selectionControls: selectionControls,
+        position: position,
+        dragStartBehavior: dragStartBehavior,
+    ));
+  }
+
+  Widget _buildToolbar(BuildContext context) {
+    if (selectionControls == null)
+      return Container();
+
+    // Find the horizontal midpoint, just above the selected text.
+    final List<TextSelectionPoint> endpoints =
+        renderObject.getEndpointsForSelection(_selection);
+
+    final Rect editingRegion = Rect.fromPoints(
+      renderObject.localToGlobal(Offset.zero),
+      renderObject.localToGlobal(renderObject.size.bottomRight(Offset.zero)),
+    );
+
+    final bool isMultiline = endpoints.last.point.dy - endpoints.first.point.dy >
+          renderObject.preferredLineHeight / 2;
+
+    // If the selected text spans more than 1 line, horizontally center the toolbar.
+    // Derived from both iOS and Android.
+    final double midX = isMultiline
+      ? editingRegion.width / 2
+      : (endpoints.first.point.dx + endpoints.last.point.dx) / 2;
+
+    final Offset midpoint = Offset(
+      midX,
+      // The y-coordinate won't be made use of most likely.
+      endpoints[0].point.dy - renderObject.preferredLineHeight,
+    );
+
+    return Directionality(
+      textDirection: Directionality.of(this.context),
+      child: FadeTransition(
+        opacity: _toolbarOpacity,
+        child: CompositedTransformFollower(
+          link: toolbarLayerLink,
+          showWhenUnlinked: false,
+          offset: -editingRegion.topLeft,
+          child: Builder(
+            builder: (BuildContext context) {
+              return selectionControls!.buildToolbar(
+                context,
+                editingRegion,
+                renderObject.preferredLineHeight,
+                midpoint,
+                endpoints,
+                selectionDelegate!,
+                clipboardStatus!,
+              );
+            },
+          ),
+        ),
+      ),
+    );
+  }
+
+  void _handleSelectionHandleChanged(TextSelection newSelection, _TextSelectionHandlePosition position) {
+    final TextPosition textPosition;
+    switch (position) {
+      case _TextSelectionHandlePosition.start:
+        textPosition = newSelection.base;
+        break;
+      case _TextSelectionHandlePosition.end:
+        textPosition =newSelection.extent;
+        break;
+    }
+    selectionDelegate!.textEditingValue = _value.copyWith(selection: newSelection, composing: TextRange.empty);
+    selectionDelegate!.bringIntoView(textPosition);
+  }
+}
+
+/// This widget represents a single draggable text selection handle.
+class _TextSelectionHandleOverlay extends StatefulWidget {
+  const _TextSelectionHandleOverlay({
+    Key? key,
+    required this.selection,
+    required this.position,
+    required this.startHandleLayerLink,
+    required this.endHandleLayerLink,
+    required this.renderObject,
+    required this.onSelectionHandleChanged,
+    required this.onSelectionHandleTapped,
+    required this.selectionControls,
+    this.dragStartBehavior = DragStartBehavior.start,
+  }) : super(key: key);
+
+  final TextSelection selection;
+  final _TextSelectionHandlePosition position;
+  final LayerLink startHandleLayerLink;
+  final LayerLink endHandleLayerLink;
+  final RenderEditable renderObject;
+  final ValueChanged<TextSelection> onSelectionHandleChanged;
+  final VoidCallback? onSelectionHandleTapped;
+  final TextSelectionControls? selectionControls;
+  final DragStartBehavior dragStartBehavior;
+
+  @override
+  _TextSelectionHandleOverlayState createState() => _TextSelectionHandleOverlayState();
+
+  ValueListenable<bool> get _visibility {
+    switch (position) {
+      case _TextSelectionHandlePosition.start:
+        return renderObject.selectionStartInViewport;
+      case _TextSelectionHandlePosition.end:
+        return renderObject.selectionEndInViewport;
+    }
+  }
+}
+
+class _TextSelectionHandleOverlayState
+    extends State<_TextSelectionHandleOverlay> with SingleTickerProviderStateMixin {
+  late Offset _dragPosition;
+
+  late AnimationController _controller;
+  Animation<double> get _opacity => _controller.view;
+
+  @override
+  void initState() {
+    super.initState();
+
+    _controller = AnimationController(duration: TextSelectionOverlay.fadeDuration, vsync: this);
+
+    _handleVisibilityChanged();
+    widget._visibility.addListener(_handleVisibilityChanged);
+  }
+
+  void _handleVisibilityChanged() {
+    if (widget._visibility.value) {
+      _controller.forward();
+    } else {
+      _controller.reverse();
+    }
+  }
+
+  @override
+  void didUpdateWidget(_TextSelectionHandleOverlay oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    oldWidget._visibility.removeListener(_handleVisibilityChanged);
+    _handleVisibilityChanged();
+    widget._visibility.addListener(_handleVisibilityChanged);
+  }
+
+  @override
+  void dispose() {
+    widget._visibility.removeListener(_handleVisibilityChanged);
+    _controller.dispose();
+    super.dispose();
+  }
+
+  void _handleDragStart(DragStartDetails details) {
+    final Size handleSize = widget.selectionControls!.getHandleSize(
+      widget.renderObject.preferredLineHeight,
+    );
+    _dragPosition = details.globalPosition + Offset(0.0, -handleSize.height);
+  }
+
+  void _handleDragUpdate(DragUpdateDetails details) {
+    _dragPosition += details.delta;
+    final TextPosition position = widget.renderObject.getPositionForPoint(_dragPosition);
+
+    if (widget.selection.isCollapsed) {
+      widget.onSelectionHandleChanged(TextSelection.fromPosition(position));
+      return;
+    }
+
+    final TextSelection newSelection;
+    switch (widget.position) {
+      case _TextSelectionHandlePosition.start:
+        newSelection = TextSelection(
+          baseOffset: position.offset,
+          extentOffset: widget.selection.extentOffset,
+        );
+        break;
+      case _TextSelectionHandlePosition.end:
+        newSelection = TextSelection(
+          baseOffset: widget.selection.baseOffset,
+          extentOffset: position.offset,
+        );
+        break;
+    }
+
+    if (newSelection.baseOffset >= newSelection.extentOffset)
+      return; // don't allow order swapping.
+
+    widget.onSelectionHandleChanged(newSelection);
+  }
+
+  void _handleTap() {
+    if (widget.onSelectionHandleTapped != null)
+      widget.onSelectionHandleTapped!();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final LayerLink layerLink;
+    final TextSelectionHandleType type;
+
+    switch (widget.position) {
+      case _TextSelectionHandlePosition.start:
+        layerLink = widget.startHandleLayerLink;
+        type = _chooseType(
+          widget.renderObject.textDirection,
+          TextSelectionHandleType.left,
+          TextSelectionHandleType.right,
+        );
+        break;
+      case _TextSelectionHandlePosition.end:
+        // For collapsed selections, we shouldn't be building the [end] handle.
+        assert(!widget.selection.isCollapsed);
+        layerLink = widget.endHandleLayerLink;
+        type = _chooseType(
+          widget.renderObject.textDirection,
+          TextSelectionHandleType.right,
+          TextSelectionHandleType.left,
+        );
+        break;
+    }
+
+    final Offset handleAnchor = widget.selectionControls!.getHandleAnchor(
+      type,
+      widget.renderObject.preferredLineHeight,
+    );
+    final Size handleSize = widget.selectionControls!.getHandleSize(
+      widget.renderObject.preferredLineHeight,
+    );
+
+    final Rect handleRect = Rect.fromLTWH(
+      -handleAnchor.dx,
+      -handleAnchor.dy,
+      handleSize.width,
+      handleSize.height,
+    );
+
+    // Make sure the GestureDetector is big enough to be easily interactive.
+    final Rect interactiveRect = handleRect.expandToInclude(
+      Rect.fromCircle(center: handleRect.center, radius: kMinInteractiveDimension/ 2),
+    );
+    final RelativeRect padding = RelativeRect.fromLTRB(
+      math.max((interactiveRect.width - handleRect.width) / 2, 0),
+      math.max((interactiveRect.height - handleRect.height) / 2, 0),
+      math.max((interactiveRect.width - handleRect.width) / 2, 0),
+      math.max((interactiveRect.height - handleRect.height) / 2, 0),
+    );
+
+    return CompositedTransformFollower(
+      link: layerLink,
+      offset: interactiveRect.topLeft,
+      showWhenUnlinked: false,
+      child: FadeTransition(
+        opacity: _opacity,
+        child: Container(
+          alignment: Alignment.topLeft,
+          width: interactiveRect.width,
+          height: interactiveRect.height,
+          child: GestureDetector(
+            behavior: HitTestBehavior.translucent,
+            dragStartBehavior: widget.dragStartBehavior,
+            onPanStart: _handleDragStart,
+            onPanUpdate: _handleDragUpdate,
+            onTap: _handleTap,
+            child: Padding(
+              padding: EdgeInsets.only(
+                left: padding.left,
+                top: padding.top,
+                right: padding.right,
+                bottom: padding.bottom,
+              ),
+              child: widget.selectionControls!.buildHandle(
+                context,
+                type,
+                widget.renderObject.preferredLineHeight,
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+
+  TextSelectionHandleType _chooseType(
+    TextDirection textDirection,
+    TextSelectionHandleType ltrType,
+    TextSelectionHandleType rtlType,
+  ) {
+    if (widget.selection.isCollapsed)
+      return TextSelectionHandleType.collapsed;
+
+    assert(textDirection != null);
+    switch (textDirection) {
+      case TextDirection.ltr:
+        return ltrType;
+      case TextDirection.rtl:
+        return rtlType;
+    }
+  }
+}
+
+/// Delegate interface for the [TextSelectionGestureDetectorBuilder].
+///
+/// The interface is usually implemented by textfield implementations wrapping
+/// [EditableText], that use a [TextSelectionGestureDetectorBuilder] to build a
+/// [TextSelectionGestureDetector] for their [EditableText]. The delegate provides
+/// the builder with information about the current state of the textfield.
+/// Based on these information, the builder adds the correct gesture handlers
+/// to the gesture detector.
+///
+/// See also:
+///
+///  * [TextField], which implements this delegate for the Material textfield.
+///  * [CupertinoTextField], which implements this delegate for the Cupertino
+///    textfield.
+abstract class TextSelectionGestureDetectorBuilderDelegate {
+  /// [GlobalKey] to the [EditableText] for which the
+  /// [TextSelectionGestureDetectorBuilder] will build a [TextSelectionGestureDetector].
+  GlobalKey<EditableTextState> get editableTextKey;
+
+  /// Whether the textfield should respond to force presses.
+  bool get forcePressEnabled;
+
+  /// Whether the user may select text in the textfield.
+  bool get selectionEnabled;
+}
+
+/// Builds a [TextSelectionGestureDetector] to wrap an [EditableText].
+///
+/// The class implements sensible defaults for many user interactions
+/// with an [EditableText] (see the documentation of the various gesture handler
+/// methods, e.g. [onTapDown], [onForcePressStart], etc.). Subclasses of
+/// [TextSelectionGestureDetectorBuilder] can change the behavior performed in
+/// responds to these gesture events by overriding the corresponding handler
+/// methods of this class.
+///
+/// The resulting [TextSelectionGestureDetector] to wrap an [EditableText] is
+/// obtained by calling [buildGestureDetector].
+///
+/// See also:
+///
+///  * [TextField], which uses a subclass to implement the Material-specific
+///    gesture logic of an [EditableText].
+///  * [CupertinoTextField], which uses a subclass to implement the
+///    Cupertino-specific gesture logic of an [EditableText].
+class TextSelectionGestureDetectorBuilder {
+  /// Creates a [TextSelectionGestureDetectorBuilder].
+  ///
+  /// The [delegate] must not be null.
+  TextSelectionGestureDetectorBuilder({
+    required this.delegate,
+  }) : assert(delegate != null);
+
+  /// The delegate for this [TextSelectionGestureDetectorBuilder].
+  ///
+  /// The delegate provides the builder with information about what actions can
+  /// currently be performed on the textfield. Based on this, the builder adds
+  /// the correct gesture handlers to the gesture detector.
+  @protected
+  final TextSelectionGestureDetectorBuilderDelegate delegate;
+
+  /// Whether to show the selection toolbar.
+  ///
+  /// It is based on the signal source when a [onTapDown] is called. This getter
+  /// will return true if current [onTapDown] event is triggered by a touch or
+  /// a stylus.
+  bool get shouldShowSelectionToolbar => _shouldShowSelectionToolbar;
+  bool _shouldShowSelectionToolbar = true;
+
+  /// The [State] of the [EditableText] for which the builder will provide a
+  /// [TextSelectionGestureDetector].
+  @protected
+  EditableTextState get editableText => delegate.editableTextKey.currentState!;
+
+  /// The [RenderObject] of the [EditableText] for which the builder will
+  /// provide a [TextSelectionGestureDetector].
+  @protected
+  RenderEditable get renderEditable => editableText.renderEditable;
+
+  /// Handler for [TextSelectionGestureDetector.onTapDown].
+  ///
+  /// By default, it forwards the tap to [RenderEditable.handleTapDown] and sets
+  /// [shouldShowSelectionToolbar] to true if the tap was initiated by a finger or stylus.
+  ///
+  /// See also:
+  ///
+  ///  * [TextSelectionGestureDetector.onTapDown], which triggers this callback.
+  @protected
+  void onTapDown(TapDownDetails details) {
+    renderEditable.handleTapDown(details);
+    // The selection overlay should only be shown when the user is interacting
+    // through a touch screen (via either a finger or a stylus). A mouse shouldn't
+    // trigger the selection overlay.
+    // For backwards-compatibility, we treat a null kind the same as touch.
+    final PointerDeviceKind? kind = details.kind;
+    _shouldShowSelectionToolbar = kind == null
+      || kind == PointerDeviceKind.touch
+      || kind == PointerDeviceKind.stylus;
+  }
+
+  /// Handler for [TextSelectionGestureDetector.onForcePressStart].
+  ///
+  /// By default, it selects the word at the position of the force press,
+  /// if selection is enabled.
+  ///
+  /// This callback is only applicable when force press is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [TextSelectionGestureDetector.onForcePressStart], which triggers this
+  ///    callback.
+  @protected
+  void onForcePressStart(ForcePressDetails details) {
+    assert(delegate.forcePressEnabled);
+    _shouldShowSelectionToolbar = true;
+    if (delegate.selectionEnabled) {
+      renderEditable.selectWordsInRange(
+        from: details.globalPosition,
+        cause: SelectionChangedCause.forcePress,
+      );
+    }
+  }
+
+  /// Handler for [TextSelectionGestureDetector.onForcePressEnd].
+  ///
+  /// By default, it selects words in the range specified in [details] and shows
+  /// toolbar if it is necessary.
+  ///
+  /// This callback is only applicable when force press is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [TextSelectionGestureDetector.onForcePressEnd], which triggers this
+  ///    callback.
+  @protected
+  void onForcePressEnd(ForcePressDetails details) {
+    assert(delegate.forcePressEnabled);
+    renderEditable.selectWordsInRange(
+      from: details.globalPosition,
+      cause: SelectionChangedCause.forcePress,
+    );
+    if (shouldShowSelectionToolbar)
+      editableText.showToolbar();
+  }
+
+  /// Handler for [TextSelectionGestureDetector.onSingleTapUp].
+  ///
+  /// By default, it selects word edge if selection is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [TextSelectionGestureDetector.onSingleTapUp], which triggers
+  ///    this callback.
+  @protected
+  void onSingleTapUp(TapUpDetails details) {
+    if (delegate.selectionEnabled) {
+      renderEditable.selectWordEdge(cause: SelectionChangedCause.tap);
+    }
+  }
+
+  /// Handler for [TextSelectionGestureDetector.onSingleTapCancel].
+  ///
+  /// By default, it services as place holder to enable subclass override.
+  ///
+  /// See also:
+  ///
+  ///  * [TextSelectionGestureDetector.onSingleTapCancel], which triggers
+  ///    this callback.
+  @protected
+  void onSingleTapCancel() {/* Subclass should override this method if needed. */}
+
+  /// Handler for [TextSelectionGestureDetector.onSingleLongTapStart].
+  ///
+  /// By default, it selects text position specified in [details] if selection
+  /// is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [TextSelectionGestureDetector.onSingleLongTapStart], which triggers
+  ///    this callback.
+  @protected
+  void onSingleLongTapStart(LongPressStartDetails details) {
+    if (delegate.selectionEnabled) {
+      renderEditable.selectPositionAt(
+        from: details.globalPosition,
+        cause: SelectionChangedCause.longPress,
+      );
+    }
+  }
+
+  /// Handler for [TextSelectionGestureDetector.onSingleLongTapMoveUpdate].
+  ///
+  /// By default, it updates the selection location specified in [details] if
+  /// selection is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [TextSelectionGestureDetector.onSingleLongTapMoveUpdate], which
+  ///    triggers this callback.
+  @protected
+  void onSingleLongTapMoveUpdate(LongPressMoveUpdateDetails details) {
+    if (delegate.selectionEnabled) {
+      renderEditable.selectPositionAt(
+        from: details.globalPosition,
+        cause: SelectionChangedCause.longPress,
+      );
+    }
+  }
+
+  /// Handler for [TextSelectionGestureDetector.onSingleLongTapEnd].
+  ///
+  /// By default, it shows toolbar if necessary.
+  ///
+  /// See also:
+  ///
+  ///  * [TextSelectionGestureDetector.onSingleLongTapEnd], which triggers this
+  ///    callback.
+  @protected
+  void onSingleLongTapEnd(LongPressEndDetails details) {
+    if (shouldShowSelectionToolbar)
+      editableText.showToolbar();
+  }
+
+  /// Handler for [TextSelectionGestureDetector.onDoubleTapDown].
+  ///
+  /// By default, it selects a word through [RenderEditable.selectWord] if
+  /// selectionEnabled and shows toolbar if necessary.
+  ///
+  /// See also:
+  ///
+  ///  * [TextSelectionGestureDetector.onDoubleTapDown], which triggers this
+  ///    callback.
+  @protected
+  void onDoubleTapDown(TapDownDetails details) {
+    if (delegate.selectionEnabled) {
+      renderEditable.selectWord(cause: SelectionChangedCause.tap);
+      if (shouldShowSelectionToolbar)
+        editableText.showToolbar();
+    }
+  }
+
+  /// Handler for [TextSelectionGestureDetector.onDragSelectionStart].
+  ///
+  /// By default, it selects a text position specified in [details].
+  ///
+  /// See also:
+  ///
+  ///  * [TextSelectionGestureDetector.onDragSelectionStart], which triggers
+  ///    this callback.
+  @protected
+  void onDragSelectionStart(DragStartDetails details) {
+    final PointerDeviceKind? kind = details.kind;
+    _shouldShowSelectionToolbar = kind == null
+      || kind == PointerDeviceKind.touch
+      || kind == PointerDeviceKind.stylus;
+
+    renderEditable.selectPositionAt(
+      from: details.globalPosition,
+      cause: SelectionChangedCause.drag,
+    );
+  }
+
+  /// Handler for [TextSelectionGestureDetector.onDragSelectionUpdate].
+  ///
+  /// By default, it updates the selection location specified in the provided
+  /// details objects.
+  ///
+  /// See also:
+  ///
+  ///  * [TextSelectionGestureDetector.onDragSelectionUpdate], which triggers
+  ///    this callback./lib/src/material/text_field.dart
+  @protected
+  void onDragSelectionUpdate(DragStartDetails startDetails, DragUpdateDetails updateDetails) {
+    renderEditable.selectPositionAt(
+      from: startDetails.globalPosition,
+      to: updateDetails.globalPosition,
+      cause: SelectionChangedCause.drag,
+    );
+  }
+
+  /// Handler for [TextSelectionGestureDetector.onDragSelectionEnd].
+  ///
+  /// By default, it services as place holder to enable subclass override.
+  ///
+  /// See also:
+  ///
+  ///  * [TextSelectionGestureDetector.onDragSelectionEnd], which triggers this
+  ///    callback.
+  @protected
+  void onDragSelectionEnd(DragEndDetails details) {/* Subclass should override this method if needed. */}
+
+  /// Returns a [TextSelectionGestureDetector] configured with the handlers
+  /// provided by this builder.
+  ///
+  /// The [child] or its subtree should contain [EditableText].
+  Widget buildGestureDetector({
+    Key? key,
+    HitTestBehavior? behavior,
+    required Widget child,
+  }) {
+    return TextSelectionGestureDetector(
+      key: key,
+      onTapDown: onTapDown,
+      onForcePressStart: delegate.forcePressEnabled ? onForcePressStart : null,
+      onForcePressEnd: delegate.forcePressEnabled ? onForcePressEnd : null,
+      onSingleTapUp: onSingleTapUp,
+      onSingleTapCancel: onSingleTapCancel,
+      onSingleLongTapStart: onSingleLongTapStart,
+      onSingleLongTapMoveUpdate: onSingleLongTapMoveUpdate,
+      onSingleLongTapEnd: onSingleLongTapEnd,
+      onDoubleTapDown: onDoubleTapDown,
+      onDragSelectionStart: onDragSelectionStart,
+      onDragSelectionUpdate: onDragSelectionUpdate,
+      onDragSelectionEnd: onDragSelectionEnd,
+      behavior: behavior,
+      child: child,
+    );
+  }
+}
+
+/// A gesture detector to respond to non-exclusive event chains for a text field.
+///
+/// An ordinary [GestureDetector] configured to handle events like tap and
+/// double tap will only recognize one or the other. This widget detects both:
+/// first the tap and then, if another tap down occurs within a time limit, the
+/// double tap.
+///
+/// See also:
+///
+///  * [TextField], a Material text field which uses this gesture detector.
+///  * [CupertinoTextField], a Cupertino text field which uses this gesture
+///    detector.
+class TextSelectionGestureDetector extends StatefulWidget {
+  /// Create a [TextSelectionGestureDetector].
+  ///
+  /// Multiple callbacks can be called for one sequence of input gesture.
+  /// The [child] parameter must not be null.
+  const TextSelectionGestureDetector({
+    Key? key,
+    this.onTapDown,
+    this.onForcePressStart,
+    this.onForcePressEnd,
+    this.onSingleTapUp,
+    this.onSingleTapCancel,
+    this.onSingleLongTapStart,
+    this.onSingleLongTapMoveUpdate,
+    this.onSingleLongTapEnd,
+    this.onDoubleTapDown,
+    this.onDragSelectionStart,
+    this.onDragSelectionUpdate,
+    this.onDragSelectionEnd,
+    this.behavior,
+    required this.child,
+  }) : assert(child != null),
+       super(key: key);
+
+  /// Called for every tap down including every tap down that's part of a
+  /// double click or a long press, except touches that include enough movement
+  /// to not qualify as taps (e.g. pans and flings).
+  final GestureTapDownCallback? onTapDown;
+
+  /// Called when a pointer has tapped down and the force of the pointer has
+  /// just become greater than [ForcePressGestureRecognizer.startPressure].
+  final GestureForcePressStartCallback? onForcePressStart;
+
+  /// Called when a pointer that had previously triggered [onForcePressStart] is
+  /// lifted off the screen.
+  final GestureForcePressEndCallback? onForcePressEnd;
+
+  /// Called for each distinct tap except for every second tap of a double tap.
+  /// For example, if the detector was configured with [onTapDown] and
+  /// [onDoubleTapDown], three quick taps would be recognized as a single tap
+  /// down, followed by a double tap down, followed by a single tap down.
+  final GestureTapUpCallback? onSingleTapUp;
+
+  /// Called for each touch that becomes recognized as a gesture that is not a
+  /// short tap, such as a long tap or drag. It is called at the moment when
+  /// another gesture from the touch is recognized.
+  final GestureTapCancelCallback? onSingleTapCancel;
+
+  /// Called for a single long tap that's sustained for longer than
+  /// [kLongPressTimeout] but not necessarily lifted. Not called for a
+  /// double-tap-hold, which calls [onDoubleTapDown] instead.
+  final GestureLongPressStartCallback? onSingleLongTapStart;
+
+  /// Called after [onSingleLongTapStart] when the pointer is dragged.
+  final GestureLongPressMoveUpdateCallback? onSingleLongTapMoveUpdate;
+
+  /// Called after [onSingleLongTapStart] when the pointer is lifted.
+  final GestureLongPressEndCallback? onSingleLongTapEnd;
+
+  /// Called after a momentary hold or a short tap that is close in space and
+  /// time (within [kDoubleTapTimeout]) to a previous short tap.
+  final GestureTapDownCallback? onDoubleTapDown;
+
+  /// Called when a mouse starts dragging to select text.
+  final GestureDragStartCallback? onDragSelectionStart;
+
+  /// Called repeatedly as a mouse moves while dragging.
+  ///
+  /// The frequency of calls is throttled to avoid excessive text layout
+  /// operations in text fields. The throttling is controlled by the constant
+  /// [_kDragSelectionUpdateThrottle].
+  final DragSelectionUpdateCallback? onDragSelectionUpdate;
+
+  /// Called when a mouse that was previously dragging is released.
+  final GestureDragEndCallback? onDragSelectionEnd;
+
+  /// How this gesture detector should behave during hit testing.
+  ///
+  /// This defaults to [HitTestBehavior.deferToChild].
+  final HitTestBehavior? behavior;
+
+  /// Child below this widget.
+  final Widget child;
+
+  @override
+  State<StatefulWidget> createState() => _TextSelectionGestureDetectorState();
+}
+
+class _TextSelectionGestureDetectorState extends State<TextSelectionGestureDetector> {
+  // Counts down for a short duration after a previous tap. Null otherwise.
+  Timer? _doubleTapTimer;
+  Offset? _lastTapOffset;
+  // True if a second tap down of a double tap is detected. Used to discard
+  // subsequent tap up / tap hold of the same tap.
+  bool _isDoubleTap = false;
+
+  @override
+  void dispose() {
+    _doubleTapTimer?.cancel();
+    _dragUpdateThrottleTimer?.cancel();
+    super.dispose();
+  }
+
+  // The down handler is force-run on success of a single tap and optimistically
+  // run before a long press success.
+  void _handleTapDown(TapDownDetails details) {
+    if (widget.onTapDown != null) {
+      widget.onTapDown!(details);
+    }
+    // This isn't detected as a double tap gesture in the gesture recognizer
+    // because it's 2 single taps, each of which may do different things depending
+    // on whether it's a single tap, the first tap of a double tap, the second
+    // tap held down, a clean double tap etc.
+    if (_doubleTapTimer != null && _isWithinDoubleTapTolerance(details.globalPosition)) {
+      // If there was already a previous tap, the second down hold/tap is a
+      // double tap down.
+      if (widget.onDoubleTapDown != null) {
+        widget.onDoubleTapDown!(details);
+      }
+
+      _doubleTapTimer!.cancel();
+      _doubleTapTimeout();
+      _isDoubleTap = true;
+    }
+  }
+
+  void _handleTapUp(TapUpDetails details) {
+    if (!_isDoubleTap) {
+      if (widget.onSingleTapUp != null) {
+        widget.onSingleTapUp!(details);
+      }
+      _lastTapOffset = details.globalPosition;
+      _doubleTapTimer = Timer(kDoubleTapTimeout, _doubleTapTimeout);
+    }
+    _isDoubleTap = false;
+  }
+
+  void _handleTapCancel() {
+    if (widget.onSingleTapCancel != null) {
+      widget.onSingleTapCancel!();
+    }
+  }
+
+  DragStartDetails? _lastDragStartDetails;
+  DragUpdateDetails? _lastDragUpdateDetails;
+  Timer? _dragUpdateThrottleTimer;
+
+  void _handleDragStart(DragStartDetails details) {
+    assert(_lastDragStartDetails == null);
+    _lastDragStartDetails = details;
+    if (widget.onDragSelectionStart != null) {
+      widget.onDragSelectionStart!(details);
+    }
+  }
+
+  void _handleDragUpdate(DragUpdateDetails details) {
+    _lastDragUpdateDetails = details;
+    // Only schedule a new timer if there's no one pending.
+    _dragUpdateThrottleTimer ??= Timer(_kDragSelectionUpdateThrottle, _handleDragUpdateThrottled);
+  }
+
+  /// Drag updates are being throttled to avoid excessive text layouts in text
+  /// fields. The frequency of invocations is controlled by the constant
+  /// [_kDragSelectionUpdateThrottle].
+  ///
+  /// Once the drag gesture ends, any pending drag update will be fired
+  /// immediately. See [_handleDragEnd].
+  void _handleDragUpdateThrottled() {
+    assert(_lastDragStartDetails != null);
+    assert(_lastDragUpdateDetails != null);
+    if (widget.onDragSelectionUpdate != null) {
+      widget.onDragSelectionUpdate!(_lastDragStartDetails!, _lastDragUpdateDetails!);
+    }
+    _dragUpdateThrottleTimer = null;
+    _lastDragUpdateDetails = null;
+  }
+
+  void _handleDragEnd(DragEndDetails details) {
+    assert(_lastDragStartDetails != null);
+    if (_dragUpdateThrottleTimer != null) {
+      // If there's already an update scheduled, trigger it immediately and
+      // cancel the timer.
+      _dragUpdateThrottleTimer!.cancel();
+      _handleDragUpdateThrottled();
+    }
+    if (widget.onDragSelectionEnd != null) {
+      widget.onDragSelectionEnd!(details);
+    }
+    _dragUpdateThrottleTimer = null;
+    _lastDragStartDetails = null;
+    _lastDragUpdateDetails = null;
+  }
+
+  void _forcePressStarted(ForcePressDetails details) {
+    _doubleTapTimer?.cancel();
+    _doubleTapTimer = null;
+    if (widget.onForcePressStart != null)
+      widget.onForcePressStart!(details);
+  }
+
+  void _forcePressEnded(ForcePressDetails details) {
+    if (widget.onForcePressEnd != null)
+      widget.onForcePressEnd!(details);
+  }
+
+  void _handleLongPressStart(LongPressStartDetails details) {
+    if (!_isDoubleTap && widget.onSingleLongTapStart != null) {
+      widget.onSingleLongTapStart!(details);
+    }
+  }
+
+  void _handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) {
+    if (!_isDoubleTap && widget.onSingleLongTapMoveUpdate != null) {
+      widget.onSingleLongTapMoveUpdate!(details);
+    }
+  }
+
+  void _handleLongPressEnd(LongPressEndDetails details) {
+    if (!_isDoubleTap && widget.onSingleLongTapEnd != null) {
+      widget.onSingleLongTapEnd!(details);
+    }
+    _isDoubleTap = false;
+  }
+
+  void _doubleTapTimeout() {
+    _doubleTapTimer = null;
+    _lastTapOffset = null;
+  }
+
+  bool _isWithinDoubleTapTolerance(Offset secondTapOffset) {
+    assert(secondTapOffset != null);
+    if (_lastTapOffset == null) {
+      return false;
+    }
+
+    final Offset difference = secondTapOffset - _lastTapOffset!;
+    return difference.distance <= kDoubleTapSlop;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
+
+    // Use _TransparentTapGestureRecognizer so that TextSelectionGestureDetector
+    // can receive the same tap events that a selection handle placed visually
+    // on top of it also receives.
+    gestures[_TransparentTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<_TransparentTapGestureRecognizer>(
+      () => _TransparentTapGestureRecognizer(debugOwner: this),
+      (_TransparentTapGestureRecognizer instance) {
+        instance
+          ..onTapDown = _handleTapDown
+          ..onTapUp = _handleTapUp
+          ..onTapCancel = _handleTapCancel;
+      },
+    );
+
+    if (widget.onSingleLongTapStart != null ||
+        widget.onSingleLongTapMoveUpdate != null ||
+        widget.onSingleLongTapEnd != null) {
+      gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
+        () => LongPressGestureRecognizer(debugOwner: this, kind: PointerDeviceKind.touch),
+        (LongPressGestureRecognizer instance) {
+          instance
+            ..onLongPressStart = _handleLongPressStart
+            ..onLongPressMoveUpdate = _handleLongPressMoveUpdate
+            ..onLongPressEnd = _handleLongPressEnd;
+        },
+      );
+    }
+
+    if (widget.onDragSelectionStart != null ||
+        widget.onDragSelectionUpdate != null ||
+        widget.onDragSelectionEnd != null) {
+      // TODO(mdebbar): Support dragging in any direction (for multiline text).
+      // https://github.com/flutter/flutter/issues/28676
+      gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
+        () => HorizontalDragGestureRecognizer(debugOwner: this, kind: PointerDeviceKind.mouse),
+        (HorizontalDragGestureRecognizer instance) {
+          instance
+            // Text selection should start from the position of the first pointer
+            // down event.
+            ..dragStartBehavior = DragStartBehavior.down
+            ..onStart = _handleDragStart
+            ..onUpdate = _handleDragUpdate
+            ..onEnd = _handleDragEnd;
+        },
+      );
+    }
+
+    if (widget.onForcePressStart != null || widget.onForcePressEnd != null) {
+      gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
+        () => ForcePressGestureRecognizer(debugOwner: this),
+        (ForcePressGestureRecognizer instance) {
+          instance
+            ..onStart = widget.onForcePressStart != null ? _forcePressStarted : null
+            ..onEnd = widget.onForcePressEnd != null ? _forcePressEnded : null;
+        },
+      );
+    }
+
+    return RawGestureDetector(
+      gestures: gestures,
+      excludeFromSemantics: true,
+      behavior: widget.behavior,
+      child: widget.child,
+    );
+  }
+}
+
+// A TapGestureRecognizer which allows other GestureRecognizers to win in the
+// GestureArena. This means both _TransparentTapGestureRecognizer and other
+// GestureRecognizers can handle the same event.
+//
+// This enables proper handling of events on both the selection handle and the
+// underlying input, since there is significant overlap between the two given
+// the handle's padded hit area.  For example, the selection handle needs to
+// handle single taps on itself, but double taps need to be handled by the
+// underlying input.
+class _TransparentTapGestureRecognizer extends TapGestureRecognizer {
+  _TransparentTapGestureRecognizer({
+    Object? debugOwner,
+  }) : super(debugOwner: debugOwner);
+
+  @override
+  void rejectGesture(int pointer) {
+    // Accept new gestures that another recognizer has already won.
+    // Specifically, this needs to accept taps on the text selection handle on
+    // behalf of the text field in order to handle double tap to select. It must
+    // not accept other gestures like longpresses and drags that end outside of
+    // the text field.
+    if (state == GestureRecognizerState.ready) {
+      acceptGesture(pointer);
+    } else {
+      super.rejectGesture(pointer);
+    }
+  }
+}
+
+/// A [ValueNotifier] whose [value] indicates whether the current contents of
+/// the clipboard can be pasted.
+///
+/// The contents of the clipboard can only be read asynchronously, via
+/// [Clipboard.getData], so this maintains a value that can be used
+/// synchronously. Call [update] to asynchronously update value if needed.
+class ClipboardStatusNotifier extends ValueNotifier<ClipboardStatus> with WidgetsBindingObserver {
+  /// Create a new ClipboardStatusNotifier.
+  ClipboardStatusNotifier({
+    ClipboardStatus value = ClipboardStatus.unknown,
+  }) : super(value);
+
+  bool _disposed = false;
+  /// True iff this instance has been disposed.
+  bool get disposed => _disposed;
+
+  /// Check the [Clipboard] and update [value] if needed.
+  Future<void> update() async {
+    // iOS 14 added a notification that appears when an app accesses the
+    // clipboard. To avoid the notification, don't access the clipboard on iOS,
+    // and instead always show the paste button, even when the clipboard is
+    // empty.
+    // TODO(justinmc): Use the new iOS 14 clipboard API method hasStrings that
+    // won't trigger the notification.
+    // https://github.com/flutter/flutter/issues/60145
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.iOS:
+        value = ClipboardStatus.pasteable;
+        return;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.macOS:
+      case TargetPlatform.windows:
+        break;
+    }
+
+    ClipboardData? data;
+    try {
+      data = await Clipboard.getData(Clipboard.kTextPlain);
+    } catch (stacktrace) {
+      // In the case of an error from the Clipboard API, set the value to
+      // unknown so that it will try to update again later.
+      if (_disposed || value == ClipboardStatus.unknown) {
+        return;
+      }
+      value = ClipboardStatus.unknown;
+      return;
+    }
+
+    final ClipboardStatus clipboardStatus = data != null && data.text != null && data.text!.isNotEmpty
+        ? ClipboardStatus.pasteable
+        : ClipboardStatus.notPasteable;
+    if (_disposed || clipboardStatus == value) {
+      return;
+    }
+    value = clipboardStatus;
+  }
+
+  @override
+  void addListener(VoidCallback listener) {
+    if (!hasListeners) {
+      WidgetsBinding.instance!.addObserver(this);
+    }
+    if (value == ClipboardStatus.unknown) {
+      update();
+    }
+    super.addListener(listener);
+  }
+
+  @override
+  void removeListener(VoidCallback listener) {
+    super.removeListener(listener);
+    if (!hasListeners) {
+      WidgetsBinding.instance!.removeObserver(this);
+    }
+  }
+
+  @override
+  void didChangeAppLifecycleState(AppLifecycleState state) {
+    switch (state) {
+      case AppLifecycleState.resumed:
+        update();
+        break;
+      case AppLifecycleState.detached:
+      case AppLifecycleState.inactive:
+      case AppLifecycleState.paused:
+        // Nothing to do.
+    }
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    WidgetsBinding.instance!.removeObserver(this);
+    _disposed = true;
+  }
+}
+
+/// An enumeration of the status of the content on the user's clipboard.
+enum ClipboardStatus {
+  /// The clipboard content can be pasted, such as a String of nonzero length.
+  pasteable,
+
+  /// The status of the clipboard is unknown. Since getting clipboard data is
+  /// asynchronous (see [Clipboard.getData]), this status often exists while
+  /// waiting to receive the clipboard contents for the first time.
+  unknown,
+
+  /// The content on the clipboard is not pasteable, such as when it is empty.
+  notPasteable,
+}
diff --git a/lib/src/widgets/texture.dart b/lib/src/widgets/texture.dart
new file mode 100644
index 0000000..0511666
--- /dev/null
+++ b/lib/src/widgets/texture.dart
@@ -0,0 +1,66 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+import 'framework.dart';
+
+/// A rectangle upon which a backend texture is mapped.
+///
+/// Backend textures are images that can be applied (mapped) to an area of the
+/// Flutter view. They are created, managed, and updated using a
+/// platform-specific texture registry. This is typically done by a plugin
+/// that integrates with host platform video player, camera, or OpenGL APIs,
+/// or similar image sources.
+///
+/// A texture widget refers to its backend texture using an integer ID. Texture
+/// IDs are obtained from the texture registry and are scoped to the Flutter
+/// view. Texture IDs may be reused after deregistration, at the discretion
+/// of the registry. The use of texture IDs currently unknown to the registry
+/// will silently result in a blank rectangle.
+///
+/// Texture widgets are repainted autonomously as dictated by the backend (e.g.
+/// on arrival of a video frame). Such repainting generally does not involve
+/// executing Dart code.
+///
+/// The size of the rectangle is determined by its parent widget, and the
+/// texture is automatically scaled to fit.
+///
+/// See also:
+///
+///  * <https://api.flutter.dev/javadoc/io/flutter/view/TextureRegistry.html>
+///    for how to create and manage backend textures on Android.
+///  * <https://api.flutter.dev/objcdoc/Protocols/FlutterTextureRegistry.html>
+///    for how to create and manage backend textures on iOS.
+class Texture extends LeafRenderObjectWidget {
+  /// Creates a widget backed by the texture identified by [textureId], and use
+  /// [filterQuality] to set texture's [FilterQuality].
+  const Texture({
+    Key? key,
+    required this.textureId,
+    this.filterQuality = FilterQuality.low,
+  }) : assert(textureId != null),
+       super(key: key);
+
+  /// The identity of the backend texture.
+  final int textureId;
+
+  /// {@template flutter.widgets.Texture.filterQuality}
+  /// The quality of sampling the texture and rendering it on screen.
+  ///
+  /// When the texture is scaled, a default [FilterQuality.low] is used for a higher quality but slower
+  /// interpolation (typically bilinear). It can be changed to [FilterQuality.none] for a lower quality but
+  /// faster interpolation (typically nearest-neighbor). See also [FilterQuality.medium] and
+  /// [FilterQuality.high] for more options.
+  /// {@endtemplate}
+  final FilterQuality filterQuality;
+
+  @override
+  TextureBox createRenderObject(BuildContext context) => TextureBox(textureId: textureId, filterQuality: filterQuality);
+
+  @override
+  void updateRenderObject(BuildContext context, TextureBox renderObject) {
+    renderObject.textureId = textureId;
+    renderObject.filterQuality = filterQuality;
+  }
+}
diff --git a/lib/src/widgets/ticker_provider.dart b/lib/src/widgets/ticker_provider.dart
new file mode 100644
index 0000000..fc67528
--- /dev/null
+++ b/lib/src/widgets/ticker_provider.dart
@@ -0,0 +1,280 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/scheduler.dart';
+
+import 'framework.dart';
+
+export 'package:flute/scheduler.dart' show TickerProvider;
+
+/// Enables or disables tickers (and thus animation controllers) in the widget
+/// subtree.
+///
+/// This only works if [AnimationController] objects are created using
+/// widget-aware ticker providers. For example, using a
+/// [TickerProviderStateMixin] or a [SingleTickerProviderStateMixin].
+class TickerMode extends StatelessWidget {
+  /// Creates a widget that enables or disables tickers.
+  ///
+  /// The [enabled] argument must not be null.
+  const TickerMode({
+    Key? key,
+    required this.enabled,
+    required this.child,
+  }) : assert(enabled != null),
+       super(key: key);
+
+  /// The requested ticker mode for this subtree.
+  ///
+  /// The effective ticker mode of this subtree may differ from this value
+  /// if there is an ancestor [TickerMode] with this field set to false.
+  ///
+  /// If true and all ancestor [TickerMode]s are also enabled, then tickers in
+  /// this subtree will tick.
+  ///
+  /// If false, then tickers in this subtree will not tick regardless of any
+  /// ancestor [TickerMode]s. Animations driven by such tickers are not paused,
+  /// they just don't call their callbacks. Time still elapses.
+  final bool enabled;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// Whether tickers in the given subtree should be enabled or disabled.
+  ///
+  /// This is used automatically by [TickerProviderStateMixin] and
+  /// [SingleTickerProviderStateMixin] to decide if their tickers should be
+  /// enabled or disabled.
+  ///
+  /// In the absence of a [TickerMode] widget, this function defaults to true.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// bool tickingEnabled = TickerMode.of(context);
+  /// ```
+  static bool of(BuildContext context) {
+    final _EffectiveTickerMode? widget = context.dependOnInheritedWidgetOfExactType<_EffectiveTickerMode>();
+    return widget?.enabled ?? true;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _EffectiveTickerMode(
+      enabled: enabled && TickerMode.of(context),
+      child: child,
+    );
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('requested mode', value: enabled, ifTrue: 'enabled', ifFalse: 'disabled', showName: true));
+  }
+}
+
+class _EffectiveTickerMode extends InheritedWidget {
+  const _EffectiveTickerMode({
+    Key? key,
+    required this.enabled,
+    required Widget child,
+  }) : assert(enabled != null),
+        super(key: key, child: child);
+
+  final bool enabled;
+
+  @override
+  bool updateShouldNotify(_EffectiveTickerMode oldWidget) => enabled != oldWidget.enabled;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('effective mode', value: enabled, ifTrue: 'enabled', ifFalse: 'disabled', showName: true));
+  }
+}
+
+/// Provides a single [Ticker] that is configured to only tick while the current
+/// tree is enabled, as defined by [TickerMode].
+///
+/// To create the [AnimationController] in a [State] that only uses a single
+/// [AnimationController], mix in this class, then pass `vsync: this`
+/// to the animation controller constructor.
+///
+/// This mixin only supports vending a single ticker. If you might have multiple
+/// [AnimationController] objects over the lifetime of the [State], use a full
+/// [TickerProviderStateMixin] instead.
+@optionalTypeArgs
+mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
+  Ticker? _ticker;
+
+  @override
+  Ticker createTicker(TickerCallback onTick) {
+    assert(() {
+      if (_ticker == null)
+        return true;
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('$runtimeType is a SingleTickerProviderStateMixin but multiple tickers were created.'),
+        ErrorDescription('A SingleTickerProviderStateMixin can only be used as a TickerProvider once.'),
+        ErrorHint(
+          'If a State is used for multiple AnimationController objects, or if it is passed to other '
+          'objects and those objects might use it more than one time in total, then instead of '
+          'mixing in a SingleTickerProviderStateMixin, use a regular TickerProviderStateMixin.'
+        )
+      ]);
+    }());
+    _ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by $this' : null);
+    // We assume that this is called from initState, build, or some sort of
+    // event handler, and that thus TickerMode.of(context) would return true. We
+    // can't actually check that here because if we're in initState then we're
+    // not allowed to do inheritance checks yet.
+    return _ticker!;
+  }
+
+  @override
+  void dispose() {
+    assert(() {
+      if (_ticker == null || !_ticker!.isActive)
+        return true;
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary('$this was disposed with an active Ticker.'),
+        ErrorDescription(
+          '$runtimeType created a Ticker via its SingleTickerProviderStateMixin, but at the time '
+          'dispose() was called on the mixin, that Ticker was still active. The Ticker must '
+          'be disposed before calling super.dispose().'
+        ),
+        ErrorHint(
+          'Tickers used by AnimationControllers '
+          'should be disposed by calling dispose() on the AnimationController itself. '
+          'Otherwise, the ticker will leak.'
+        ),
+        _ticker!.describeForError('The offending ticker was')
+      ]);
+    }());
+    super.dispose();
+  }
+
+  @override
+  void didChangeDependencies() {
+    if (_ticker != null)
+      _ticker!.muted = !TickerMode.of(context);
+    super.didChangeDependencies();
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    String? tickerDescription;
+    if (_ticker != null) {
+      if (_ticker!.isActive && _ticker!.muted)
+        tickerDescription = 'active but muted';
+      else if (_ticker!.isActive)
+        tickerDescription = 'active';
+      else if (_ticker!.muted)
+        tickerDescription = 'inactive and muted';
+      else
+        tickerDescription = 'inactive';
+    }
+    properties.add(DiagnosticsProperty<Ticker>('ticker', _ticker, description: tickerDescription, showSeparator: false, defaultValue: null));
+  }
+}
+
+/// Provides [Ticker] objects that are configured to only tick while the current
+/// tree is enabled, as defined by [TickerMode].
+///
+/// To create an [AnimationController] in a class that uses this mixin, pass
+/// `vsync: this` to the animation controller constructor whenever you
+/// create a new animation controller.
+///
+/// If you only have a single [Ticker] (for example only a single
+/// [AnimationController]) for the lifetime of your [State], then using a
+/// [SingleTickerProviderStateMixin] is more efficient. This is the common case.
+@optionalTypeArgs
+mixin TickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
+  Set<Ticker>? _tickers;
+
+  @override
+  Ticker createTicker(TickerCallback onTick) {
+    _tickers ??= <_WidgetTicker>{};
+    final _WidgetTicker result = _WidgetTicker(onTick, this, debugLabel: 'created by $this');
+    _tickers!.add(result);
+    return result;
+  }
+
+  void _removeTicker(_WidgetTicker ticker) {
+    assert(_tickers != null);
+    assert(_tickers!.contains(ticker));
+    _tickers!.remove(ticker);
+  }
+
+  @override
+  void dispose() {
+    assert(() {
+      if (_tickers != null) {
+        for (final Ticker ticker in _tickers!) {
+          if (ticker.isActive) {
+            throw FlutterError.fromParts(<DiagnosticsNode>[
+              ErrorSummary('$this was disposed with an active Ticker.'),
+              ErrorDescription(
+                '$runtimeType created a Ticker via its TickerProviderStateMixin, but at the time '
+                'dispose() was called on the mixin, that Ticker was still active. All Tickers must '
+                'be disposed before calling super.dispose().'
+              ),
+              ErrorHint(
+                'Tickers used by AnimationControllers '
+                'should be disposed by calling dispose() on the AnimationController itself. '
+                'Otherwise, the ticker will leak.'
+              ),
+              ticker.describeForError('The offending ticker was'),
+            ]);
+          }
+        }
+      }
+      return true;
+    }());
+    super.dispose();
+  }
+
+  @override
+  void didChangeDependencies() {
+    final bool muted = !TickerMode.of(context);
+    if (_tickers != null) {
+      for (final Ticker ticker in _tickers!) {
+        ticker.muted = muted;
+      }
+    }
+    super.didChangeDependencies();
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Set<Ticker>>(
+      'tickers',
+      _tickers,
+      description: _tickers != null ?
+        'tracking ${_tickers!.length} ticker${_tickers!.length == 1 ? "" : "s"}' :
+        null,
+      defaultValue: null,
+    ));
+  }
+}
+
+// This class should really be called _DisposingTicker or some such, but this
+// class name leaks into stack traces and error messages and that name would be
+// confusing. Instead we use the less precise but more anodyne "_WidgetTicker",
+// which attracts less attention.
+class _WidgetTicker extends Ticker {
+  _WidgetTicker(TickerCallback onTick, this._creator, { String? debugLabel }) : super(onTick, debugLabel: debugLabel);
+
+  final TickerProviderStateMixin _creator;
+
+  @override
+  void dispose() {
+    _creator._removeTicker(this);
+    super.dispose();
+  }
+}
diff --git a/lib/src/widgets/title.dart b/lib/src/widgets/title.dart
new file mode 100644
index 0000000..07c09fe
--- /dev/null
+++ b/lib/src/widgets/title.dart
@@ -0,0 +1,58 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+import 'package:flute/services.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+
+/// A widget that describes this app in the operating system.
+class Title extends StatelessWidget {
+  /// Creates a widget that describes this app to the Android operating system.
+  ///
+  /// [title] will default to the empty string if not supplied.
+  /// [color] must be an opaque color (i.e. color.alpha must be 255 (0xFF)).
+  /// [color] and [child] are required arguments.
+  Title({
+    Key? key,
+    this.title = '',
+    required this.color,
+    required this.child,
+  }) : assert(title != null),
+       assert(color != null && color.alpha == 0xFF),
+       super(key: key);
+
+  /// A one-line description of this app for use in the window manager.
+  /// Must not be null.
+  final String title;
+
+  /// A color that the window manager should use to identify this app. Must be
+  /// an opaque color (i.e. color.alpha must be 255 (0xFF)), and must not be
+  /// null.
+  final Color color;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    SystemChrome.setApplicationSwitcherDescription(
+      ApplicationSwitcherDescription(
+        label: title,
+        primaryColor: color.value,
+      ),
+    );
+    return child;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(StringProperty('title', title, defaultValue: ''));
+    properties.add(ColorProperty('color', color, defaultValue: null));
+  }
+}
diff --git a/lib/src/widgets/transitions.dart b/lib/src/widgets/transitions.dart
new file mode 100644
index 0000000..d8f7813
--- /dev/null
+++ b/lib/src/widgets/transitions.dart
@@ -0,0 +1,1530 @@
+// Copyright 2014 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.
+
+import 'dart:math' as math;
+
+import 'package:flute/rendering.dart';
+import 'package:vector_math/vector_math_64.dart' show Matrix4;
+
+import 'basic.dart';
+import 'container.dart';
+import 'framework.dart';
+import 'text.dart';
+
+export 'package:flute/rendering.dart' show RelativeRect;
+
+/// A widget that rebuilds when the given [Listenable] changes value.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=LKKgYpC-EPQ}
+///
+/// [AnimatedWidget] is most commonly used with [Animation] objects, which are
+/// [Listenable], but it can be used with any [Listenable], including
+/// [ChangeNotifier] and [ValueNotifier].
+///
+/// [AnimatedWidget] is most useful for widgets that are otherwise stateless. To
+/// use [AnimatedWidget], simply subclass it and implement the build function.
+///
+///{@tool dartpad --template=stateful_widget_material_ticker_no_null_safety}
+///
+/// This code defines a widget called `Spinner` that spins a green square
+/// continually. It is built with an [AnimatedWidget].
+///
+/// ```dart imports
+/// import 'dart:math' as math;
+/// ```
+///
+/// ```dart preamble
+/// class SpinningContainer extends AnimatedWidget {
+///   const SpinningContainer({Key key, AnimationController controller})
+///       : super(key: key, listenable: controller);
+///
+///   Animation<double> get _progress => listenable;
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return Transform.rotate(
+///       angle: _progress.value * 2.0 * math.pi,
+///       child: Container(width: 200.0, height: 200.0, color: Colors.green),
+///     );
+///   }
+/// }
+/// ```
+///
+/// ```dart
+/// AnimationController _controller;
+///
+/// @override
+/// void initState() {
+///   super.initState();
+///   _controller = AnimationController(
+///     duration: const Duration(seconds: 10),
+///     vsync: this,
+///   )..repeat();
+/// }
+///
+/// @override
+/// void dispose() {
+///   _controller.dispose();
+///   super.dispose();
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return SpinningContainer(controller: _controller);
+/// }
+/// ```
+/// {@end-tool}
+///
+/// For more complex case involving additional state, consider using
+/// [AnimatedBuilder].
+///
+/// ## Relationship to [ImplicitlyAnimatedWidget]s
+///
+/// [AnimatedWidget]s (and their subclasses) take an explicit [Listenable] as
+/// argument, which is usually an [Animation] derived from an
+/// [AnimationController]. In most cases, the lifecycle of that
+/// [AnimationController] has to be managed manually by the developer.
+/// In contrast to that, [ImplicitlyAnimatedWidget]s (and their subclasses)
+/// automatically manage their own internal [AnimationController] making those
+/// classes easier to use as no external [Animation] has to be provided by the
+/// developer. If you only need to set a target value for the animation and
+/// configure its duration/curve, consider using (a subclass of)
+/// [ImplicitlyAnimatedWidget]s instead of (a subclass of) this class.
+///
+/// ## Common animated widgets
+///
+/// A number of animated widgets ship with the framework. They are usually named
+/// `FooTransition`, where `Foo` is the name of the non-animated
+/// version of that widget. The subclasses of this class should not be confused
+/// with subclasses of [ImplicitlyAnimatedWidget] (see above), which are usually
+/// named `AnimatedFoo`. Commonly used animated widgets include:
+///
+///  * [AnimatedBuilder], which is useful for complex animation use cases and a
+///    notable exception to the naming scheme of [AnimatedWidget] subclasses.
+///  * [AlignTransition], which is an animated version of [Align].
+///  * [DecoratedBoxTransition], which is an animated version of [DecoratedBox].
+///  * [DefaultTextStyleTransition], which is an animated version of
+///    [DefaultTextStyle].
+///  * [PositionedTransition], which is an animated version of [Positioned].
+///  * [RelativePositionedTransition], which is an animated version of
+///    [Positioned].
+///  * [RotationTransition], which animates the rotation of a widget.
+///  * [ScaleTransition], which animates the scale of a widget.
+///  * [SizeTransition], which animates its own size.
+///  * [SlideTransition], which animates the position of a widget relative to
+///    its normal position.
+///  * [FadeTransition], which is an animated version of [Opacity].
+///  * [AnimatedModalBarrier], which is an animated version of [ModalBarrier].
+abstract class AnimatedWidget extends StatefulWidget {
+  /// Creates a widget that rebuilds when the given listenable changes.
+  ///
+  /// The [listenable] argument is required.
+  const AnimatedWidget({
+    Key? key,
+    required this.listenable,
+  }) : assert(listenable != null),
+       super(key: key);
+
+  /// The [Listenable] to which this widget is listening.
+  ///
+  /// Commonly an [Animation] or a [ChangeNotifier].
+  final Listenable listenable;
+
+  /// Override this method to build widgets that depend on the state of the
+  /// listenable (e.g., the current value of the animation).
+  @protected
+  Widget build(BuildContext context);
+
+  /// Subclasses typically do not override this method.
+  @override
+  _AnimatedState createState() => _AnimatedState();
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Listenable>('animation', listenable));
+  }
+}
+
+class _AnimatedState extends State<AnimatedWidget> {
+  @override
+  void initState() {
+    super.initState();
+    widget.listenable.addListener(_handleChange);
+  }
+
+  @override
+  void didUpdateWidget(AnimatedWidget oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.listenable != oldWidget.listenable) {
+      oldWidget.listenable.removeListener(_handleChange);
+      widget.listenable.addListener(_handleChange);
+    }
+  }
+
+  @override
+  void dispose() {
+    widget.listenable.removeListener(_handleChange);
+    super.dispose();
+  }
+
+  void _handleChange() {
+    setState(() {
+      // The listenable's state is our build state, and it changed already.
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) => widget.build(context);
+}
+
+/// Animates the position of a widget relative to its normal position.
+///
+/// The translation is expressed as an [Offset] scaled to the child's size. For
+/// example, an [Offset] with a `dx` of 0.25 will result in a horizontal
+/// translation of one quarter the width of the child.
+///
+/// By default, the offsets are applied in the coordinate system of the canvas
+/// (so positive x offsets move the child towards the right). If a
+/// [textDirection] is provided, then the offsets are applied in the reading
+/// direction, so in right-to-left text, positive x offsets move towards the
+/// left, and in left-to-right text, positive x offsets move towards the right.
+///
+/// Here's an illustration of the [SlideTransition] widget, with its [position]
+/// animated by a [CurvedAnimation] set to [Curves.elasticIn]:
+/// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/slide_transition.mp4}
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_freeform_state_no_null_safety}
+/// The following code implements the [SlideTransition] as seen in the video
+/// above:
+///
+/// ```dart
+/// class _MyStatefulWidgetState extends State<MyStatefulWidget> with SingleTickerProviderStateMixin {
+///   AnimationController _controller;
+///   Animation<Offset> _offsetAnimation;
+///
+///   @override
+///   void initState() {
+///     super.initState();
+///     _controller = AnimationController(
+///       duration: const Duration(seconds: 2),
+///       vsync: this,
+///     )..repeat(reverse: true);
+///     _offsetAnimation = Tween<Offset>(
+///       begin: Offset.zero,
+///       end: const Offset(1.5, 0.0),
+///     ).animate(CurvedAnimation(
+///       parent: _controller,
+///       curve: Curves.elasticIn,
+///     ));
+///   }
+///
+///   @override
+///   void dispose() {
+///     super.dispose();
+///     _controller.dispose();
+///   }
+///
+///   @override
+///   Widget build(BuildContext context) {
+///     return SlideTransition(
+///       position: _offsetAnimation,
+///       child: const Padding(
+///         padding: EdgeInsets.all(8.0),
+///         child: FlutterLogo(size: 150.0),
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AlignTransition], an animated version of an [Align] that animates its
+///    [Align.alignment] property.
+///  * [PositionedTransition], a widget that animates its child from a start
+///    position to an end position over the lifetime of the animation.
+///  * [RelativePositionedTransition], a widget that transitions its child's
+///    position based on the value of a rectangle relative to a bounding box.
+class SlideTransition extends AnimatedWidget {
+  /// Creates a fractional translation transition.
+  ///
+  /// The [position] argument must not be null.
+  const SlideTransition({
+    Key? key,
+    required Animation<Offset> position,
+    this.transformHitTests = true,
+    this.textDirection,
+    this.child,
+  }) : assert(position != null),
+       super(key: key, listenable: position);
+
+  /// The animation that controls the position of the child.
+  ///
+  /// If the current value of the position animation is `(dx, dy)`, the child
+  /// will be translated horizontally by `width * dx` and vertically by
+  /// `height * dy`, after applying the [textDirection] if available.
+  Animation<Offset> get position => listenable as Animation<Offset>;
+
+  /// The direction to use for the x offset described by the [position].
+  ///
+  /// If [textDirection] is null, the x offset is applied in the coordinate
+  /// system of the canvas (so positive x offsets move the child towards the
+  /// right).
+  ///
+  /// If [textDirection] is [TextDirection.rtl], the x offset is applied in the
+  /// reading direction such that x offsets move the child towards the left.
+  ///
+  /// If [textDirection] is [TextDirection.ltr], the x offset is applied in the
+  /// reading direction such that x offsets move the child towards the right.
+  final TextDirection? textDirection;
+
+  /// Whether hit testing should be affected by the slide animation.
+  ///
+  /// If false, hit testing will proceed as if the child was not translated at
+  /// all. Setting this value to false is useful for fast animations where you
+  /// expect the user to commonly interact with the child widget in its final
+  /// location and you want the user to benefit from "muscle memory".
+  final bool transformHitTests;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  @override
+  Widget build(BuildContext context) {
+    Offset offset = position.value;
+    if (textDirection == TextDirection.rtl)
+      offset = Offset(-offset.dx, offset.dy);
+    return FractionalTranslation(
+      translation: offset,
+      transformHitTests: transformHitTests,
+      child: child,
+    );
+  }
+}
+
+/// Animates the scale of a transformed widget.
+///
+/// Here's an illustration of the [ScaleTransition] widget, with it's [alignment]
+/// animated by a [CurvedAnimation] set to [Curves.fastOutSlowIn]:
+/// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/scale_transition.mp4}
+///
+/// {@tool dartpad --template=stateful_widget_material_ticker_no_null_safety}
+///
+/// The following code implements the [ScaleTransition] as seen in the video
+/// above:
+///
+/// ```dart
+/// AnimationController _controller;
+/// Animation<double> _animation;
+///
+/// @override
+/// void initState() {
+///   super.initState();
+///   _controller = AnimationController(
+///     duration: const Duration(seconds: 2),
+///     vsync: this,
+///   )..repeat(reverse: true);
+///   _animation = CurvedAnimation(
+///     parent: _controller,
+///     curve: Curves.fastOutSlowIn,
+///   );
+/// }
+///
+/// @override
+/// void dispose() {
+///   super.dispose();
+///   _controller.dispose();
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: Center(
+///       child: ScaleTransition(
+///         scale: _animation,
+///         child: const Padding(
+///           padding: EdgeInsets.all(8.0),
+///           child: FlutterLogo(size: 150.0),
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [PositionedTransition], a widget that animates its child from a start
+///    position to an end position over the lifetime of the animation.
+///  * [RelativePositionedTransition], a widget that transitions its child's
+///    position based on the value of a rectangle relative to a bounding box.
+///  * [SizeTransition], a widget that animates its own size and clips and
+///    aligns its child.
+class ScaleTransition extends AnimatedWidget {
+  /// Creates a scale transition.
+  ///
+  /// The [scale] argument must not be null. The [alignment] argument defaults
+  /// to [Alignment.center].
+  const ScaleTransition({
+    Key? key,
+    required Animation<double> scale,
+    this.alignment = Alignment.center,
+    this.child,
+  }) : assert(scale != null),
+       super(key: key, listenable: scale);
+
+  /// The animation that controls the scale of the child.
+  ///
+  /// If the current value of the scale animation is v, the child will be
+  /// painted v times its normal size.
+  Animation<double> get scale => listenable as Animation<double>;
+
+  /// The alignment of the origin of the coordinate system in which the scale
+  /// takes place, relative to the size of the box.
+  ///
+  /// For example, to set the origin of the scale to bottom middle, you can use
+  /// an alignment of (0.0, 1.0).
+  final Alignment alignment;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  @override
+  Widget build(BuildContext context) {
+    final double scaleValue = scale.value;
+    final Matrix4 transform = Matrix4.identity()
+      ..scale(scaleValue, scaleValue, 1.0);
+    return Transform(
+      transform: transform,
+      alignment: alignment,
+      child: child,
+    );
+  }
+}
+
+/// Animates the rotation of a widget.
+///
+/// Here's an illustration of the [RotationTransition] widget, with it's [turns]
+/// animated by a [CurvedAnimation] set to [Curves.elasticOut]:
+/// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/rotation_transition.mp4}
+///
+/// {@tool dartpad --template=stateful_widget_material_ticker_no_null_safety}
+///
+/// The following code implements the [RotationTransition] as seen in the video
+/// above:
+///
+/// ```dart
+/// AnimationController _controller;
+/// Animation<double> _animation;
+///
+/// @override
+/// void initState() {
+///   super.initState();
+///   _controller = AnimationController(
+///     duration: const Duration(seconds: 2),
+///     vsync: this,
+///   )..repeat(reverse: true);
+///   _animation = CurvedAnimation(
+///     parent: _controller,
+///     curve: Curves.elasticOut,
+///   );
+/// }
+///
+/// @override
+/// void dispose() {
+///   _controller.dispose();
+///   super.dispose();
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: Center(
+///       child: RotationTransition(
+///         turns: _animation,
+///         child: const Padding(
+///           padding: EdgeInsets.all(8.0),
+///           child: FlutterLogo(size: 150.0),
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ScaleTransition], a widget that animates the scale of a transformed
+///    widget.
+///  * [SizeTransition], a widget that animates its own size and clips and
+///    aligns its child.
+class RotationTransition extends AnimatedWidget {
+  /// Creates a rotation transition.
+  ///
+  /// The [turns] argument must not be null.
+  const RotationTransition({
+    Key? key,
+    required Animation<double> turns,
+    this.alignment = Alignment.center,
+    this.child,
+  }) : assert(turns != null),
+       super(key: key, listenable: turns);
+
+  /// The animation that controls the rotation of the child.
+  ///
+  /// If the current value of the turns animation is v, the child will be
+  /// rotated v * 2 * pi radians before being painted.
+  Animation<double> get turns => listenable as Animation<double>;
+
+  /// The alignment of the origin of the coordinate system around which the
+  /// rotation occurs, relative to the size of the box.
+  ///
+  /// For example, to set the origin of the rotation to top right corner, use
+  /// an alignment of (1.0, -1.0) or use [Alignment.topRight]
+  final Alignment alignment;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  @override
+  Widget build(BuildContext context) {
+    final double turnsValue = turns.value;
+    final Matrix4 transform = Matrix4.rotationZ(turnsValue * math.pi * 2.0);
+    return Transform(
+      transform: transform,
+      alignment: alignment,
+      child: child,
+    );
+  }
+}
+
+/// Animates its own size and clips and aligns its child.
+///
+/// [SizeTransition] acts as a [ClipRect] that animates either its width or its
+/// height, depending upon the value of [axis]. The alignment of the child along
+/// the [axis] is specified by the [axisAlignment].
+///
+/// Like most widgets, [SizeTransition] will conform to the constraints it is
+/// given, so be sure to put it in a context where it can change size. For
+/// instance, if you place it into a [Container] with a fixed size, then the
+/// [SizeTransition] will not be able to change size, and will appear to do
+/// nothing.
+///
+/// Here's an illustration of the [SizeTransition] widget, with it's [sizeFactor]
+/// animated by a [CurvedAnimation] set to [Curves.fastOutSlowIn]:
+/// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/size_transition.mp4}
+///
+/// {@tool dartpad --template=stateful_widget_material_ticker_no_null_safety}
+///
+/// This code defines a widget that uses [SizeTransition] to change the size
+/// of [FlutterLogo] continually. It is built with a [Scaffold]
+/// where the internal widget has space to change its size.
+///
+/// ```dart
+/// AnimationController _controller;
+/// Animation<double> _animation;
+///
+/// @override
+/// void initState() {
+///   super.initState();
+///   _controller = AnimationController(
+///     duration: const Duration(seconds: 3),
+///     vsync: this,
+///   )..repeat();
+///   _animation = CurvedAnimation(
+///     parent: _controller,
+///     curve: Curves.fastOutSlowIn,
+///   );
+/// }
+///
+/// @override
+/// void dispose() {
+///   super.dispose();
+///   _controller.dispose();
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Scaffold(
+///     body: SizeTransition(
+///       sizeFactor: _animation,
+///       axis: Axis.horizontal,
+///       axisAlignment: -1,
+///       child: Center(
+///           child: FlutterLogo(size: 200.0),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AnimatedCrossFade], for a widget that automatically animates between
+///    the sizes of two children, fading between them.
+///  * [ScaleTransition], a widget that scales the size of the child instead of
+///    clipping it.
+///  * [PositionedTransition], a widget that animates its child from a start
+///    position to an end position over the lifetime of the animation.
+///  * [RelativePositionedTransition], a widget that transitions its child's
+///    position based on the value of a rectangle relative to a bounding box.
+class SizeTransition extends AnimatedWidget {
+  /// Creates a size transition.
+  ///
+  /// The [axis], [sizeFactor], and [axisAlignment] arguments must not be null.
+  /// The [axis] argument defaults to [Axis.vertical]. The [axisAlignment]
+  /// defaults to 0.0, which centers the child along the main axis during the
+  /// transition.
+  const SizeTransition({
+    Key? key,
+    this.axis = Axis.vertical,
+    required Animation<double> sizeFactor,
+    this.axisAlignment = 0.0,
+    this.child,
+  }) : assert(axis != null),
+       assert(sizeFactor != null),
+       assert(axisAlignment != null),
+       super(key: key, listenable: sizeFactor);
+
+  /// [Axis.horizontal] if [sizeFactor] modifies the width, otherwise
+  /// [Axis.vertical].
+  final Axis axis;
+
+  /// The animation that controls the (clipped) size of the child.
+  ///
+  /// The width or height (depending on the [axis] value) of this widget will be
+  /// its intrinsic width or height multiplied by [sizeFactor]'s value at the
+  /// current point in the animation.
+  ///
+  /// If the value of [sizeFactor] is less than one, the child will be clipped
+  /// in the appropriate axis.
+  Animation<double> get sizeFactor => listenable as Animation<double>;
+
+  /// Describes how to align the child along the axis that [sizeFactor] is
+  /// modifying.
+  ///
+  /// A value of -1.0 indicates the top when [axis] is [Axis.vertical], and the
+  /// start when [axis] is [Axis.horizontal]. The start is on the left when the
+  /// text direction in effect is [TextDirection.ltr] and on the right when it
+  /// is [TextDirection.rtl].
+  ///
+  /// A value of 1.0 indicates the bottom or end, depending upon the [axis].
+  ///
+  /// A value of 0.0 (the default) indicates the center for either [axis] value.
+  final double axisAlignment;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget? child;
+
+  @override
+  Widget build(BuildContext context) {
+    final AlignmentDirectional alignment;
+    if (axis == Axis.vertical)
+      alignment = AlignmentDirectional(-1.0, axisAlignment);
+    else
+      alignment = AlignmentDirectional(axisAlignment, -1.0);
+    return ClipRect(
+      child: Align(
+        alignment: alignment,
+        heightFactor: axis == Axis.vertical ? math.max(sizeFactor.value, 0.0) : null,
+        widthFactor: axis == Axis.horizontal ? math.max(sizeFactor.value, 0.0) : null,
+        child: child,
+      ),
+    );
+  }
+}
+
+/// Animates the opacity of a widget.
+///
+/// For a widget that automatically animates between the sizes of two children,
+/// fading between them, see [AnimatedCrossFade].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=rLwWVbv3xDQ}
+///
+/// Here's an illustration of the [FadeTransition] widget, with it's [opacity]
+/// animated by a [CurvedAnimation] set to [Curves.fastOutSlowIn]:
+///
+/// {@tool dartpad --template=stateful_widget_material_ticker_no_null_safety}
+///
+/// The following code implements the [FadeTransition] using
+/// the Flutter logo:
+///
+/// ```dart
+/// AnimationController _controller;
+/// Animation<double> _animation;
+///
+/// @override
+/// void initState() {
+///   super.initState();
+///   _controller = AnimationController(
+///     duration: const Duration(seconds: 2),
+///     vsync: this,
+///   )..repeat(reverse: true);
+///   _animation = CurvedAnimation(
+///     parent: _controller,
+///     curve: Curves.easeIn,
+///   );
+/// }
+///
+/// @override
+/// void dispose() {
+///   _controller.dispose();
+///   super.dispose();
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Container(
+///     color: Colors.white,
+///     child: FadeTransition(
+///       opacity: _animation,
+///       child: const Padding(
+///         padding: EdgeInsets.all(8),
+///         child: FlutterLogo()
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [Opacity], which does not animate changes in opacity.
+///  * [AnimatedOpacity], which animates changes in opacity without taking an
+///    explicit [Animation] argument.
+class FadeTransition extends SingleChildRenderObjectWidget {
+  /// Creates an opacity transition.
+  ///
+  /// The [opacity] argument must not be null.
+  const FadeTransition({
+    Key? key,
+    required this.opacity,
+    this.alwaysIncludeSemantics = false,
+    Widget? child,
+  }) : assert(opacity != null),
+       super(key: key, child: child);
+
+  /// The animation that controls the opacity of the child.
+  ///
+  /// If the current value of the opacity animation is v, the child will be
+  /// painted with an opacity of v. For example, if v is 0.5, the child will be
+  /// blended 50% with its background. Similarly, if v is 0.0, the child will be
+  /// completely transparent.
+  final Animation<double> opacity;
+
+  /// Whether the semantic information of the children is always included.
+  ///
+  /// Defaults to false.
+  ///
+  /// When true, regardless of the opacity settings the child semantic
+  /// information is exposed as if the widget were fully visible. This is
+  /// useful in cases where labels may be hidden during animations that
+  /// would otherwise contribute relevant semantics.
+  final bool alwaysIncludeSemantics;
+
+  @override
+  RenderAnimatedOpacity createRenderObject(BuildContext context) {
+    return RenderAnimatedOpacity(
+      opacity: opacity,
+      alwaysIncludeSemantics: alwaysIncludeSemantics,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderAnimatedOpacity renderObject) {
+    renderObject
+      ..opacity = opacity
+      ..alwaysIncludeSemantics = alwaysIncludeSemantics;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Animation<double>>('opacity', opacity));
+    properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
+  }
+}
+
+/// Animates the opacity of a sliver widget.
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_freeform_state_no_null_safety}
+/// Creates a [CustomScrollView] with a [SliverFixedExtentList] that uses a
+/// [SliverFadeTransition] to fade the list in and out.
+///
+/// ```dart
+/// class _MyStatefulWidgetState extends State<MyStatefulWidget> with SingleTickerProviderStateMixin {
+///   AnimationController controller;
+///   Animation<double> animation;
+///
+///   initState() {
+///     super.initState();
+///     controller = AnimationController(
+///         duration: const Duration(milliseconds: 1000), vsync: this);
+///     animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);
+///
+///     animation.addStatusListener((status) {
+///       if (status == AnimationStatus.completed) {
+///         controller.reverse();
+///       } else if (status == AnimationStatus.dismissed) {
+///         controller.forward();
+///       }
+///     });
+///     controller.forward();
+///   }
+///
+///   Widget build(BuildContext context) {
+///     return CustomScrollView(
+///       slivers: <Widget>[
+///         SliverFadeTransition(
+///           opacity: animation,
+///           sliver: SliverFixedExtentList(
+///             itemExtent: 100.0,
+///             delegate: SliverChildBuilderDelegate(
+///               (BuildContext context, int index) {
+///                 return Container(
+///                   color: index % 2 == 0
+///                     ? Colors.indigo[200]
+///                     : Colors.orange[200],
+///                 );
+///               },
+///               childCount: 5,
+///             ),
+///           ),
+///         )
+///       ]
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// Here's an illustration of the [FadeTransition] widget, the [RenderBox]
+/// equivalent widget, with it's [opacity] animated by a [CurvedAnimation] set
+/// to [Curves.fastOutSlowIn]:
+///
+/// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/fade_transition.mp4}
+///
+/// See also:
+///
+///  * [SliverOpacity], which does not animate changes in opacity.
+class SliverFadeTransition extends SingleChildRenderObjectWidget {
+  /// Creates an opacity transition.
+  ///
+  /// The [opacity] argument must not be null.
+  const SliverFadeTransition({
+    Key? key,
+    required this.opacity,
+    this.alwaysIncludeSemantics = false,
+    Widget? sliver,
+  }) : assert(opacity != null),
+      super(key: key, child: sliver);
+
+  /// The animation that controls the opacity of the sliver child.
+  ///
+  /// If the current value of the opacity animation is v, the child will be
+  /// painted with an opacity of v. For example, if v is 0.5, the child will be
+  /// blended 50% with its background. Similarly, if v is 0.0, the child will be
+  /// completely transparent.
+  final Animation<double> opacity;
+
+  /// Whether the semantic information of the sliver child is always included.
+  ///
+  /// Defaults to false.
+  ///
+  /// When true, regardless of the opacity settings the sliver child's semantic
+  /// information is exposed as if the widget were fully visible. This is
+  /// useful in cases where labels may be hidden during animations that
+  /// would otherwise contribute relevant semantics.
+  final bool alwaysIncludeSemantics;
+
+  @override
+  RenderSliverAnimatedOpacity createRenderObject(BuildContext context) {
+    return RenderSliverAnimatedOpacity(
+      opacity: opacity,
+      alwaysIncludeSemantics: alwaysIncludeSemantics,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderSliverAnimatedOpacity renderObject) {
+    renderObject
+      ..opacity = opacity
+      ..alwaysIncludeSemantics = alwaysIncludeSemantics;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Animation<double>>('opacity', opacity));
+    properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
+  }
+}
+
+/// An interpolation between two relative rects.
+///
+/// This class specializes the interpolation of [Tween<RelativeRect>] to
+/// use [RelativeRect.lerp].
+///
+/// See [Tween] for a discussion on how to use interpolation objects.
+class RelativeRectTween extends Tween<RelativeRect> {
+  /// Creates a [RelativeRect] tween.
+  ///
+  /// The [begin] and [end] properties may be null; the null value
+  /// is treated as [RelativeRect.fill].
+  RelativeRectTween({ RelativeRect? begin, RelativeRect? end })
+    : super(begin: begin, end: end);
+
+  /// Returns the value this variable has at the given animation clock value.
+  @override
+  RelativeRect lerp(double t) => RelativeRect.lerp(begin, end, t)!;
+}
+
+/// Animated version of [Positioned] which takes a specific
+/// [Animation<RelativeRect>] to transition the child's position from a start
+/// position to an end position over the lifetime of the animation.
+///
+/// Only works if it's the child of a [Stack].
+///
+/// Here's an illustration of the [PositionedTransition] widget, with it's [rect]
+/// animated by a [CurvedAnimation] set to [Curves.elasticInOut]:
+/// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/positioned_transition.mp4}
+///
+/// {@tool dartpad --template=stateful_widget_material_ticker_no_null_safety}
+///
+/// The following code implements the [PositionedTransition] as seen in the video
+/// above:
+///
+/// ```dart
+/// AnimationController _controller;
+///
+/// @override
+/// void initState() {
+///   super.initState();
+///   _controller = AnimationController(
+///     duration: const Duration(seconds: 2),
+///     vsync: this,
+///   )..repeat(reverse: true);
+/// }
+///
+/// @override
+/// void dispose() {
+///   _controller.dispose();
+///   super.dispose();
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   final double smallLogo = 100;
+///   final double bigLogo = 200;
+///
+///   return LayoutBuilder(
+///     builder: (context, constraints) {
+///       final Size biggest = constraints.biggest;
+///       return Stack(
+///         children: [
+///           PositionedTransition(
+///             rect: RelativeRectTween(
+///               begin: RelativeRect.fromSize(Rect.fromLTWH(0, 0, smallLogo, smallLogo), biggest),
+///               end: RelativeRect.fromSize(Rect.fromLTWH(biggest.width - bigLogo, biggest.height - bigLogo, bigLogo, bigLogo), biggest),
+///             ).animate(CurvedAnimation(
+///               parent: _controller,
+///               curve: Curves.elasticInOut,
+///             )),
+///             child: Padding(
+///               padding: const EdgeInsets.all(8),
+///               child: FlutterLogo()
+///             ),
+///           ),
+///         ],
+///       );
+///     },
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AnimatedPositioned], which transitions a child's position without
+///    taking an explicit [Animation] argument.
+///  * [RelativePositionedTransition], a widget that transitions its child's
+///    position based on the value of a rectangle relative to a bounding box.
+///  * [SlideTransition], a widget that animates the position of a widget
+///    relative to its normal position.
+///  * [AlignTransition], an animated version of an [Align] that animates its
+///    [Align.alignment] property.
+///  * [ScaleTransition], a widget that animates the scale of a transformed
+///    widget.
+///  * [SizeTransition], a widget that animates its own size and clips and
+///    aligns its child.
+class PositionedTransition extends AnimatedWidget {
+  /// Creates a transition for [Positioned].
+  ///
+  /// The [rect] argument must not be null.
+  const PositionedTransition({
+    Key? key,
+    required Animation<RelativeRect> rect,
+    required this.child,
+  }) : assert(rect != null),
+       super(key: key, listenable: rect);
+
+  /// The animation that controls the child's size and position.
+  Animation<RelativeRect> get rect => listenable as Animation<RelativeRect>;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    return Positioned.fromRelativeRect(
+      rect: rect.value,
+      child: child,
+    );
+  }
+}
+
+/// Animated version of [Positioned] which transitions the child's position
+/// based on the value of [rect] relative to a bounding box with the
+/// specified [size].
+///
+/// Only works if it's the child of a [Stack].
+///
+/// Here's an illustration of the [RelativePositionedTransition] widget, with it's [rect]
+/// animated by a [CurvedAnimation] set to [Curves.elasticInOut]:
+/// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/relative_positioned_transition.mp4}
+///
+/// {@tool dartpad --template=stateful_widget_material_ticker_no_null_safety}
+///
+/// The following code implements the [RelativePositionedTransition] as seen in the video
+/// above:
+///
+/// ```dart
+/// AnimationController _controller;
+///
+/// @override
+/// void initState() {
+///   super.initState();
+///   _controller = AnimationController(
+///     duration: const Duration(seconds: 2),
+///     vsync: this,
+///   )..repeat(reverse: true);
+/// }
+///
+/// @override
+/// void dispose() {
+///   _controller.dispose();
+///   super.dispose();
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   final double smallLogo = 100;
+///   final double bigLogo = 200;
+///
+///   return LayoutBuilder(
+///     builder: (context, constraints) {
+///       final Size biggest = constraints.biggest;
+///       return Stack(
+///         children: [
+///           RelativePositionedTransition(
+///             size: biggest,
+///             rect: RectTween(
+///               begin: Rect.fromLTWH(0, 0, bigLogo, bigLogo),
+///               end: Rect.fromLTWH(biggest.width - smallLogo, biggest.height - smallLogo, smallLogo, smallLogo),
+///             ).animate(CurvedAnimation(
+///               parent: _controller,
+///               curve: Curves.elasticInOut,
+///             )),
+///             child: Padding(
+///               padding: const EdgeInsets.all(8),
+///               child: FlutterLogo()
+///             ),
+///           ),
+///         ],
+///       );
+///     },
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [PositionedTransition], a widget that animates its child from a start
+///    position to an end position over the lifetime of the animation.
+///  * [AlignTransition], an animated version of an [Align] that animates its
+///    [Align.alignment] property.
+///  * [ScaleTransition], a widget that animates the scale of a transformed
+///    widget.
+///  * [SizeTransition], a widget that animates its own size and clips and
+///    aligns its child.
+///  * [SlideTransition], a widget that animates the position of a widget
+///    relative to its normal position.
+class RelativePositionedTransition extends AnimatedWidget {
+  /// Create an animated version of [Positioned].
+  ///
+  /// Each frame, the [Positioned] widget will be configured to represent the
+  /// current value of the [rect] argument assuming that the stack has the given
+  /// [size]. Both [rect] and [size] must not be null.
+  const RelativePositionedTransition({
+    Key? key,
+    required Animation<Rect> rect,
+    required this.size,
+    required this.child,
+  }) : assert(rect != null),
+       assert(size != null),
+       assert(child != null),
+       super(key: key, listenable: rect);
+
+  /// The animation that controls the child's size and position.
+  ///
+  /// See also:
+  ///
+  ///  * [size], which gets the size of the box that the [Positioned] widget's
+  ///    offsets are relative to.
+  Animation<Rect> get rect => listenable as Animation<Rect>;
+
+  /// The [Positioned] widget's offsets are relative to a box of this
+  /// size whose origin is 0,0.
+  final Size size;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    final RelativeRect offsets = RelativeRect.fromSize(rect.value, size);
+    return Positioned(
+      top: offsets.top,
+      right: offsets.right,
+      bottom: offsets.bottom,
+      left: offsets.left,
+      child: child,
+    );
+  }
+}
+
+/// Animated version of a [DecoratedBox] that animates the different properties
+/// of its [Decoration].
+///
+/// Here's an illustration of the [DecoratedBoxTransition] widget, with it's
+/// [decoration] animated by a [CurvedAnimation] set to [Curves.decelerate]:
+/// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/decorated_box_transition.mp4}
+///
+/// {@tool dartpad --template=stateful_widget_material_ticker_no_null_safety}
+/// The following code implements the [DecoratedBoxTransition] as seen in the video
+/// above:
+///
+/// ```dart
+/// final DecorationTween decorationTween = DecorationTween(
+///   begin: BoxDecoration(
+///     color: const Color(0xFFFFFFFF),
+///     border: Border.all(style: BorderStyle.none),
+///     borderRadius: BorderRadius.circular(60.0),
+///     shape: BoxShape.rectangle,
+///     boxShadow: const <BoxShadow>[
+///       BoxShadow(
+///         color: Color(0x66666666),
+///         blurRadius: 10.0,
+///         spreadRadius: 3.0,
+///         offset: Offset(0, 6.0),
+///       )
+///     ],
+///   ),
+///   end: BoxDecoration(
+///     color: const Color(0xFFFFFFFF),
+///     border: Border.all(
+///       style: BorderStyle.none,
+///     ),
+///     borderRadius: BorderRadius.zero,
+///     // No shadow.
+///   ),
+/// );
+///
+/// AnimationController _controller;
+///
+/// @override
+/// void initState() {
+///   _controller = AnimationController(
+///     vsync: this,
+///     duration: const Duration(seconds: 3),
+///   )..repeat(reverse: true);
+///   super.initState();
+/// }
+///
+/// @override
+/// void dispose() {
+///   _controller.dispose();
+///   super.dispose();
+/// }
+///
+///  @override
+///  Widget build(BuildContext context) {
+///    return Container(
+///      color: Colors.white,
+///      child: Center(
+///        child: DecoratedBoxTransition(
+///          position: DecorationPosition.background,
+///          decoration: decorationTween.animate(_controller),
+///          child: Container(
+///            width: 200,
+///            height: 200,
+///            padding: const EdgeInsets.all(10),
+///            child: const FlutterLogo(),
+///          ),
+///        ),
+///      ),
+///    );
+///  }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [DecoratedBox], which also draws a [Decoration] but is not animated.
+///  * [AnimatedContainer], a more full-featured container that also animates on
+///    decoration using an internal animation.
+class DecoratedBoxTransition extends AnimatedWidget {
+  /// Creates an animated [DecoratedBox] whose [Decoration] animation updates
+  /// the widget.
+  ///
+  /// The [decoration] and [position] must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [new DecoratedBox]
+  const DecoratedBoxTransition({
+    Key? key,
+    required this.decoration,
+    this.position = DecorationPosition.background,
+    required this.child,
+  }) : assert(decoration != null),
+       assert(child != null),
+       super(key: key, listenable: decoration);
+
+  /// Animation of the decoration to paint.
+  ///
+  /// Can be created using a [DecorationTween] interpolating typically between
+  /// two [BoxDecoration].
+  final Animation<Decoration> decoration;
+
+  /// Whether to paint the box decoration behind or in front of the child.
+  final DecorationPosition position;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    return DecoratedBox(
+      decoration: decoration.value,
+      position: position,
+      child: child,
+    );
+  }
+}
+
+/// Animated version of an [Align] that animates its [Align.alignment] property.
+///
+/// Here's an illustration of the [DecoratedBoxTransition] widget, with it's
+/// [DecoratedBoxTransition.decoration] animated by a [CurvedAnimation] set to
+/// [Curves.decelerate]:
+///
+/// {@animation 300 378 https://flutter.github.io/assets-for-api-docs/assets/widgets/align_transition.mp4}
+///
+/// See also:
+///
+///  * [AnimatedAlign], which animates changes to the [alignment] without
+///    taking an explicit [Animation] argument.
+///  * [PositionedTransition], a widget that animates its child from a start
+///    position to an end position over the lifetime of the animation.
+///  * [RelativePositionedTransition], a widget that transitions its child's
+///    position based on the value of a rectangle relative to a bounding box.
+///  * [SizeTransition], a widget that animates its own size and clips and
+///    aligns its child.
+///  * [SlideTransition], a widget that animates the position of a widget
+///    relative to its normal position.
+class AlignTransition extends AnimatedWidget {
+  /// Creates an animated [Align] whose [AlignmentGeometry] animation updates
+  /// the widget.
+  ///
+  /// See also:
+  ///
+  ///  * [new Align].
+  const AlignTransition({
+    Key? key,
+    required Animation<AlignmentGeometry> alignment,
+    required this.child,
+    this.widthFactor,
+    this.heightFactor,
+  }) : assert(alignment != null),
+       assert(child != null),
+       super(key: key, listenable: alignment);
+
+  /// The animation that controls the child's alignment.
+  Animation<AlignmentGeometry> get alignment => listenable as Animation<AlignmentGeometry>;
+
+  /// If non-null, the child's width factor, see [Align.widthFactor].
+  final double? widthFactor;
+
+  /// If non-null, the child's height factor, see [Align.heightFactor].
+  final double? heightFactor;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    return Align(
+      alignment: alignment.value,
+      widthFactor: widthFactor,
+      heightFactor: heightFactor,
+      child: child,
+    );
+  }
+}
+
+/// Animated version of a [DefaultTextStyle] that animates the different properties
+/// of its [TextStyle].
+///
+/// {@tool dartpad --template=stateful_widget_material_ticker_no_null_safety}
+///
+/// The following code implements the [DefaultTextStyleTransition] that shows
+/// a transition between thick blue font and thin red font.
+///
+/// ```dart
+/// AnimationController _controller;
+/// TextStyleTween _styleTween;
+/// CurvedAnimation _curvedAnimation;
+///
+/// @override
+/// void initState() {
+///   super.initState();
+///   _controller = AnimationController(
+///     duration: const Duration(seconds: 2),
+///     vsync: this,
+///   )..repeat(reverse: true);
+///   _styleTween = TextStyleTween(
+///     begin: TextStyle(fontSize: 50, color: Colors.blue, fontWeight: FontWeight.w900),
+///     end: TextStyle(fontSize: 50, color: Colors.red, fontWeight: FontWeight.w100),
+///   );
+///   _curvedAnimation = CurvedAnimation(
+///     parent: _controller,
+///     curve: Curves.elasticInOut,
+///   );
+/// }
+///
+/// @override
+/// void dispose() {
+///   _controller.dispose();
+///   super.dispose();
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return Center(
+///     child: DefaultTextStyleTransition(
+///       style: _styleTween.animate(_curvedAnimation),
+///       child: Text('Flutter'),
+///     ),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AnimatedDefaultTextStyle], which animates changes in text style without
+///    taking an explicit [Animation] argument.
+///  * [DefaultTextStyle], which also defines a [TextStyle] for its descendants
+///    but is not animated.
+class DefaultTextStyleTransition extends AnimatedWidget {
+  /// Creates an animated [DefaultTextStyle] whose [TextStyle] animation updates
+  /// the widget.
+  const DefaultTextStyleTransition({
+    Key? key,
+    required Animation<TextStyle> style,
+    required this.child,
+    this.textAlign,
+    this.softWrap = true,
+    this.overflow = TextOverflow.clip,
+    this.maxLines,
+  }) : assert(style != null),
+       assert(child != null),
+       super(key: key, listenable: style);
+
+  /// The animation that controls the descendants' text style.
+  Animation<TextStyle> get style => listenable as Animation<TextStyle>;
+
+  /// How the text should be aligned horizontally.
+  final TextAlign? textAlign;
+
+  /// Whether the text should break at soft line breaks.
+  ///
+  /// See [DefaultTextStyle.softWrap] for more details.
+  final bool softWrap;
+
+  /// How visual overflow should be handled.
+  ///
+  final TextOverflow overflow;
+
+  /// An optional maximum number of lines for the text to span, wrapping if necessary.
+  ///
+  /// See [DefaultTextStyle.maxLines] for more details.
+  final int? maxLines;
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    return DefaultTextStyle(
+      style: style.value,
+      textAlign: textAlign,
+      softWrap: softWrap,
+      overflow: overflow,
+      maxLines: maxLines,
+      child: child,
+    );
+  }
+}
+
+/// A general-purpose widget for building animations.
+///
+/// AnimatedBuilder is useful for more complex widgets that wish to include
+/// an animation as part of a larger build function. To use AnimatedBuilder,
+/// simply construct the widget and pass it a builder function.
+///
+/// For simple cases without additional state, consider using
+/// [AnimatedWidget].
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=N-RiyZlv8v8}
+///
+/// ## Performance optimizations
+///
+/// If your [builder] function contains a subtree that does not depend on the
+/// animation, it's more efficient to build that subtree once instead of
+/// rebuilding it on every animation tick.
+///
+/// If you pass the pre-built subtree as the [child] parameter, the
+/// AnimatedBuilder will pass it back to your builder function so that you
+/// can incorporate it into your build.
+///
+/// Using this pre-built child is entirely optional, but can improve
+/// performance significantly in some cases and is therefore a good practice.
+///
+/// {@tool dartpad --template=stateful_widget_material_ticker_no_null_safety}
+///
+/// This code defines a widget that spins a green square continually. It is
+/// built with an [AnimatedBuilder] and makes use of the [child] feature to
+/// avoid having to rebuild the [Container] each time.
+///
+/// ```dart imports
+/// import 'dart:math' as math;
+/// ```
+///
+/// ```dart
+/// AnimationController _controller;
+///
+/// @override
+/// void initState() {
+///   super.initState();
+///   _controller = AnimationController(
+///     duration: const Duration(seconds: 10),
+///     vsync: this,
+///   )..repeat();
+/// }
+///
+/// @override
+/// void dispose() {
+///   _controller.dispose();
+///   super.dispose();
+/// }
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return AnimatedBuilder(
+///     animation: _controller,
+///     child: Container(
+///       width: 200.0,
+///       height: 200.0,
+///       color: Colors.green,
+///       child: const Center(
+///         child: Text('Whee!'),
+///       ),
+///     ),
+///     builder: (BuildContext context, Widget child) {
+///       return Transform.rotate(
+///         angle: _controller.value * 2.0 * math.pi,
+///         child: child,
+///       );
+///     },
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [TweenAnimationBuilder], which animates a property to a target value
+///    without requiring manual management of an [AnimationController].
+class AnimatedBuilder extends AnimatedWidget {
+  /// Creates an animated builder.
+  ///
+  /// The [animation] and [builder] arguments must not be null.
+  const AnimatedBuilder({
+    Key? key,
+    required Listenable animation,
+    required this.builder,
+    this.child,
+  }) : assert(animation != null),
+       assert(builder != null),
+       super(key: key, listenable: animation);
+
+  /// Called every time the animation changes value.
+  final TransitionBuilder builder;
+
+  /// The child widget to pass to the [builder].
+  ///
+  /// If a [builder] callback's return value contains a subtree that does not
+  /// depend on the animation, it's more efficient to build that subtree once
+  /// instead of rebuilding it on every animation tick.
+  ///
+  /// If the pre-built subtree is passed as the [child] parameter, the
+  /// [AnimatedBuilder] will pass it back to the [builder] function so that it
+  /// can be incorporated into the build.
+  ///
+  /// Using this pre-built child is entirely optional, but can improve
+  /// performance significantly in some cases and is therefore a good practice.
+  final Widget? child;
+
+  @override
+  Widget build(BuildContext context) {
+    return builder(context, child);
+  }
+}
diff --git a/lib/src/widgets/tween_animation_builder.dart b/lib/src/widgets/tween_animation_builder.dart
new file mode 100644
index 0000000..aba95a9
--- /dev/null
+++ b/lib/src/widgets/tween_animation_builder.dart
@@ -0,0 +1,226 @@
+// Copyright 2014 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.
+
+import 'package:flute/animation.dart';
+
+import 'framework.dart';
+import 'implicit_animations.dart';
+import 'value_listenable_builder.dart';
+
+/// [Widget] builder that animates a property of a [Widget] to a target value
+/// whenever the target value changes.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=l9uHB8VXZOg}
+///
+/// The type of the animated property ([Color], [Rect], [double], etc.) is
+/// defined via the type of the provided [tween] (e.g. [ColorTween],
+/// [RectTween], [Tween<double>], etc.).
+///
+/// The [tween] also defines the target value for the animation: When the widget
+/// first builds, it animates from [Tween.begin] to [Tween.end]. A new animation
+/// can be triggered anytime by providing a new [tween] with a new [Tween.end]
+/// value. The new animation runs from the current animation value (which may be
+/// [Tween.end] of the old [tween], if that animation completed) to [Tween.end]
+/// of the new [tween].
+///
+/// The animation is further customized by providing a [curve] and [duration].
+///
+/// The current value of the animation along with the [child] is passed to
+/// the [builder] callback, which is expected to build a [Widget] based on the
+/// current animation value. The [builder] is called throughout the animation
+/// for every animation value until [Tween.end] is reached.
+///
+/// A provided [onEnd] callback is called whenever an animation completes.
+/// Registering an [onEnd] callback my be useful to trigger an action (like
+/// another animation) at the end of the current animation.
+///
+/// ## Performance optimizations
+///
+/// If your [builder] function contains a subtree that does not depend on the
+/// animation, it's more efficient to build that subtree once instead of
+/// rebuilding it on every animation tick.
+///
+/// If you pass the pre-built subtree as the [child] parameter, the
+/// AnimatedBuilder will pass it back to your builder function so that you
+/// can incorporate it into your build.
+///
+/// Using this pre-built child is entirely optional, but can improve
+/// performance significantly in some cases and is therefore a good practice.
+///
+/// ## Ownership of the [Tween]
+///
+/// The [TweenAnimationBuilder] takes full ownership of the provided [tween]
+/// instance and it will mutate it. Once a [Tween] has been passed to a
+/// [TweenAnimationBuilder], its properties should not be accessed or changed
+/// anymore to avoid interference with the [TweenAnimationBuilder].
+///
+/// It is good practice to never store a [Tween] provided to a
+/// [TweenAnimationBuilder] in an instance variable to avoid accidental
+/// modifications of the [Tween].
+///
+/// ## Example Code
+///
+/// {@tool dartpad --template=stateful_widget_scaffold_center_no_null_safety}
+/// This example shows an [IconButton] that "zooms" in when the widget first
+/// builds (its size smoothly increases from 0 to 24) and whenever the button
+/// is pressed, it smoothly changes its size to the new target value of either
+/// 48 or 24.
+///
+/// ```dart
+/// double targetValue = 24.0;
+///
+/// @override
+/// Widget build(BuildContext context) {
+///   return TweenAnimationBuilder(
+///     tween: Tween<double>(begin: 0, end: targetValue),
+///     duration: Duration(seconds: 1),
+///     builder: (BuildContext context, double size, Widget child) {
+///       return IconButton(
+///         iconSize: size,
+///         color: Colors.blue,
+///         icon: child,
+///         onPressed: () {
+///           setState(() {
+///             targetValue = targetValue == 24.0 ? 48.0 : 24.0;
+///           });
+///         },
+///       );
+///     },
+///     child: Icon(Icons.aspect_ratio),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// ## Relationship to [ImplicitlyAnimatedWidget]s and [AnimatedWidget]s
+///
+/// The [ImplicitlyAnimatedWidget] has many subclasses that provide animated
+/// versions of regular widgets. These subclasses (like [AnimatedOpacity],
+/// [AnimatedContainer], [AnimatedSize], etc.) animate changes in their
+/// properties smoothly and they are easier to use than this general-purpose
+/// builder. However, [TweenAnimationBuilder] (which itself is a subclass of
+/// [ImplicitlyAnimatedWidget]) is handy for animating any widget property to a
+/// given target value even when the framework (or third-party widget library)
+/// doesn't ship with an animated version of that widget.
+///
+/// Those [ImplicitlyAnimatedWidget]s (including this [TweenAnimationBuilder])
+/// all manage an internal [AnimationController] to drive the animation. If you
+/// want more control over the animation than just setting a target value,
+/// [duration], and [curve], have a look at (subclasses of) [AnimatedWidget]s.
+/// For those, you have to manually manage an [AnimationController] giving you
+/// full control over the animation. An example of an [AnimatedWidget] is the
+/// [AnimatedBuilder], which can be used similarly to this
+/// [TweenAnimationBuilder], but unlike the latter it is powered by a
+/// developer-managed [AnimationController].
+class TweenAnimationBuilder<T extends Object> extends ImplicitlyAnimatedWidget {
+  /// Creates a [TweenAnimationBuilder].
+  ///
+  /// The properties [tween], [duration], and [builder] are required. The values
+  /// for [tween], [curve], and [builder] must not be null.
+  ///
+  /// The [TweenAnimationBuilder] takes full ownership of the provided [tween]
+  /// instance and mutates it. Once a [Tween] has been passed to a
+  /// [TweenAnimationBuilder], its properties should not be accessed or changed
+  /// anymore to avoid interference with the [TweenAnimationBuilder].
+  const TweenAnimationBuilder({
+    Key? key,
+    required this.tween,
+    required Duration duration,
+    Curve curve = Curves.linear,
+    required this.builder,
+    VoidCallback? onEnd,
+    this.child,
+  }) : assert(tween != null),
+       assert(curve != null),
+       assert(builder != null),
+       super(key: key, duration: duration, curve: curve, onEnd: onEnd);
+
+  /// Defines the target value for the animation.
+  ///
+  /// When the widget first builds, the animation runs from [Tween.begin] to
+  /// [Tween.end], if [Tween.begin] is non-null. A new animation can be
+  /// triggered at anytime by providing a new [Tween] with a new [Tween.end]
+  /// value. The new animation runs from the current animation value (which may
+  /// be [Tween.end] of the old [tween], if that animation completed) to
+  /// [Tween.end] of the new [tween]. The [Tween.begin] value is ignored except
+  /// for the initial animation that is triggered when the widget builds for the
+  /// first time.
+  ///
+  /// Any (subclass of) [Tween] is accepted as an argument. For example, to
+  /// animate the height or width of a [Widget], use a [Tween<double>], or
+  /// check out the [ColorTween] to animate the color property of a [Widget].
+  ///
+  /// Any [Tween] provided must have a non-null [Tween.end] value.
+  ///
+  /// ## Ownership
+  ///
+  /// The [TweenAnimationBuilder] takes full ownership of the provided [Tween]
+  /// and it will mutate the [Tween]. Once a [Tween] instance has been passed
+  /// to [TweenAnimationBuilder] its properties should not be accessed or
+  /// changed anymore to avoid any interference with the
+  /// [TweenAnimationBuilder]. If you need to change the [Tween], create a
+  /// **new instance** with the new values.
+  ///
+  /// It is good practice to never store a [Tween] provided to a
+  /// [TweenAnimationBuilder] in an instance variable to avoid accidental
+  /// modifications of the [Tween].
+  final Tween<T> tween;
+
+  /// Called every time the animation value changes.
+  ///
+  /// The current animation value is passed to the builder along with the
+  /// [child]. The builder should build a [Widget] based on the current
+  /// animation value and incorporate the [child] into it, if it is non-null.
+  final ValueWidgetBuilder<T> builder;
+
+  /// The child widget to pass to the builder.
+  ///
+  /// If a builder callback's return value contains a subtree that does not
+  /// depend on the animation, it's more efficient to build that subtree once
+  /// instead of rebuilding it on every animation tick.
+  ///
+  /// If the pre-built subtree is passed as the child parameter, the
+  /// [TweenAnimationBuilder] will pass it back to the [builder] function so
+  /// that it can be incorporated into the build.
+  ///
+  /// Using this pre-built child is entirely optional, but can improve
+  /// performance significantly in some cases and is therefore a good practice.
+  final Widget? child;
+
+  @override
+  ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() {
+    return _TweenAnimationBuilderState<T>();
+  }
+}
+
+class _TweenAnimationBuilderState<T extends Object> extends AnimatedWidgetBaseState<TweenAnimationBuilder<T>> {
+  Tween<T>? _currentTween;
+
+  @override
+  void initState() {
+    _currentTween = widget.tween;
+    _currentTween!.begin ??= _currentTween!.end;
+    super.initState();
+    if (_currentTween!.begin != _currentTween!.end) {
+      controller.forward();
+    }
+  }
+
+  @override
+  void forEachTween(TweenVisitor<dynamic> visitor) {
+    assert(
+      widget.tween.end != null,
+      'Tween provided to TweenAnimationBuilder must have non-null Tween.end value.',
+    );
+    _currentTween = visitor(_currentTween, widget.tween.end, (dynamic value) {
+      assert(false);
+      throw StateError('Constructor will never be called because null is never provided as current tween.');
+    }) as Tween<T>?;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return widget.builder(context, _currentTween!.evaluate(animation), widget.child);
+  }
+}
diff --git a/lib/src/widgets/unique_widget.dart b/lib/src/widgets/unique_widget.dart
new file mode 100644
index 0000000..30a5e64
--- /dev/null
+++ b/lib/src/widgets/unique_widget.dart
@@ -0,0 +1,40 @@
+// Copyright 2014 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.
+
+import 'framework.dart';
+
+/// Base class for stateful widgets that have exactly one inflated instance in
+/// the tree.
+///
+/// Such widgets must be given a [GlobalKey]. This key can be generated by the
+/// subclass from its [Type] object, e.g. by calling `super(key: new
+/// GlobalObjectKey(MyWidget))` where `MyWidget` is the name of the subclass.
+///
+/// Since only one instance can be inflated at a time, there is only ever one
+/// corresponding [State] object. That object is exposed, for convenience, via
+/// the [currentState] property.
+///
+/// When subclassing [UniqueWidget], provide the corresponding [State] subclass
+/// as the type argument.
+abstract class UniqueWidget<T extends State<StatefulWidget>> extends StatefulWidget {
+  /// Creates a widget that has exactly one inflated instance in the tree.
+  ///
+  /// The [key] argument must not be null because it identifies the unique
+  /// inflated instance of this widget.
+  const UniqueWidget({
+    required GlobalKey<T> key,
+  }) : assert(key != null),
+       super(key: key);
+
+  @override
+  T createState(); // ignore: no_logic_in_create_state, https://github.com/dart-lang/linter/issues/2345
+
+  /// The state for the unique inflated instance of this widget.
+  ///
+  /// Might be null if the widget is not currently in the tree.
+  T? get currentState {
+    final GlobalKey<T> globalKey = key! as GlobalKey<T>;
+    return globalKey.currentState;
+  }
+}
diff --git a/lib/src/widgets/value_listenable_builder.dart b/lib/src/widgets/value_listenable_builder.dart
new file mode 100644
index 0000000..e12ae6a
--- /dev/null
+++ b/lib/src/widgets/value_listenable_builder.dart
@@ -0,0 +1,192 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'framework.dart';
+
+// Examples can assume:
+// // @dart = 2.9
+
+/// Builds a [Widget] when given a concrete value of a [ValueListenable<T>].
+///
+/// If the `child` parameter provided to the [ValueListenableBuilder] is not
+/// null, the same `child` widget is passed back to this [ValueWidgetBuilder]
+/// and should typically be incorporated in the returned widget tree.
+///
+/// See also:
+///
+///  * [ValueListenableBuilder], a widget which invokes this builder each time
+///    a [ValueListenable] changes value.
+typedef ValueWidgetBuilder<T> = Widget Function(BuildContext context, T value, Widget? child);
+
+/// A widget whose content stays synced with a [ValueListenable].
+///
+/// Given a [ValueListenable<T>] and a [builder] which builds widgets from
+/// concrete values of `T`, this class will automatically register itself as a
+/// listener of the [ValueListenable] and call the [builder] with updated values
+/// when the value changes.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=s-ZG-jS5QHQ}
+///
+/// ## Performance optimizations
+///
+/// If your [builder] function contains a subtree that does not depend on the
+/// value of the [ValueListenable], it's more efficient to build that subtree
+/// once instead of rebuilding it on every animation tick.
+///
+/// If you pass the pre-built subtree as the [child] parameter, the
+/// [ValueListenableBuilder] will pass it back to your [builder] function so
+/// that you can incorporate it into your build.
+///
+/// Using this pre-built child is entirely optional, but can improve
+/// performance significantly in some cases and is therefore a good practice.
+///
+/// {@tool snippet}
+///
+/// This sample shows how you could use a [ValueListenableBuilder] instead of
+/// setting state on the whole [Scaffold] in the default `flutter create` app.
+///
+/// ```dart
+/// class MyHomePage extends StatefulWidget {
+///   MyHomePage({Key key, this.title}) : super(key: key);
+///   final String title;
+///
+///   @override
+///   _MyHomePageState createState() => _MyHomePageState();
+/// }
+///
+/// class _MyHomePageState extends State<MyHomePage> {
+///   final ValueNotifier<int> _counter = ValueNotifier<int>(0);
+///   final Widget goodJob = const Text('Good job!');
+///   @override
+///   Widget build(BuildContext context) {
+///     return Scaffold(
+///       appBar: AppBar(
+///         title: Text(widget.title)
+///       ),
+///       body: Center(
+///         child: Column(
+///           mainAxisAlignment: MainAxisAlignment.center,
+///           children: <Widget>[
+///             Text('You have pushed the button this many times:'),
+///             ValueListenableBuilder(
+///               builder: (BuildContext context, int value, Widget child) {
+///                 // This builder will only get called when the _counter
+///                 // is updated.
+///                 return Row(
+///                   mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+///                   children: <Widget>[
+///                     Text('$value'),
+///                     child,
+///                   ],
+///                 );
+///               },
+///               valueListenable: _counter,
+///               // The child parameter is most helpful if the child is
+///               // expensive to build and does not depend on the value from
+///               // the notifier.
+///               child: goodJob,
+///             )
+///           ],
+///         ),
+///       ),
+///       floatingActionButton: FloatingActionButton(
+///         child: Icon(Icons.plus_one),
+///         onPressed: () => _counter.value += 1,
+///       ),
+///     );
+///   }
+/// }
+/// ```
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [AnimatedBuilder], which also triggers rebuilds from a [Listenable]
+///    without passing back a specific value from a [ValueListenable].
+///  * [NotificationListener], which lets you rebuild based on [Notification]
+///    coming from its descendant widgets rather than a [ValueListenable] that
+///    you have a direct reference to.
+///  * [StreamBuilder], where a builder can depend on a [Stream] rather than
+///    a [ValueListenable] for more advanced use cases.
+class ValueListenableBuilder<T> extends StatefulWidget {
+  /// Creates a [ValueListenableBuilder].
+  ///
+  /// The [valueListenable] and [builder] arguments must not be null.
+  /// The [child] is optional but is good practice to use if part of the widget
+  /// subtree does not depend on the value of the [valueListenable].
+  const ValueListenableBuilder({
+    Key? key,
+    required this.valueListenable,
+    required this.builder,
+    this.child,
+  }) : assert(valueListenable != null),
+       assert(builder != null),
+       super(key: key);
+
+  /// The [ValueListenable] whose value you depend on in order to build.
+  ///
+  /// This widget does not ensure that the [ValueListenable]'s value is not
+  /// null, therefore your [builder] may need to handle null values.
+  ///
+  /// This [ValueListenable] itself must not be null.
+  final ValueListenable<T> valueListenable;
+
+  /// A [ValueWidgetBuilder] which builds a widget depending on the
+  /// [valueListenable]'s value.
+  ///
+  /// Can incorporate a [valueListenable] value-independent widget subtree
+  /// from the [child] parameter into the returned widget tree.
+  ///
+  /// Must not be null.
+  final ValueWidgetBuilder<T> builder;
+
+  /// A [valueListenable]-independent widget which is passed back to the [builder].
+  ///
+  /// This argument is optional and can be null if the entire widget subtree
+  /// the [builder] builds depends on the value of the [valueListenable]. For
+  /// example, if the [valueListenable] is a [String] and the [builder] simply
+  /// returns a [Text] widget with the [String] value.
+  final Widget? child;
+
+  @override
+  State<StatefulWidget> createState() => _ValueListenableBuilderState<T>();
+}
+
+class _ValueListenableBuilderState<T> extends State<ValueListenableBuilder<T>> {
+  late T value;
+
+  @override
+  void initState() {
+    super.initState();
+    value = widget.valueListenable.value;
+    widget.valueListenable.addListener(_valueChanged);
+  }
+
+  @override
+  void didUpdateWidget(ValueListenableBuilder<T> oldWidget) {
+    if (oldWidget.valueListenable != widget.valueListenable) {
+      oldWidget.valueListenable.removeListener(_valueChanged);
+      value = widget.valueListenable.value;
+      widget.valueListenable.addListener(_valueChanged);
+    }
+    super.didUpdateWidget(oldWidget);
+  }
+
+  @override
+  void dispose() {
+    widget.valueListenable.removeListener(_valueChanged);
+    super.dispose();
+  }
+
+  void _valueChanged() {
+    setState(() { value = widget.valueListenable.value; });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return widget.builder(context, value, widget.child);
+  }
+}
diff --git a/lib/src/widgets/viewport.dart b/lib/src/widgets/viewport.dart
new file mode 100644
index 0000000..f6293c3
--- /dev/null
+++ b/lib/src/widgets/viewport.dart
@@ -0,0 +1,356 @@
+// Copyright 2014 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.
+
+import 'package:flute/rendering.dart';
+
+import 'basic.dart';
+import 'debug.dart';
+import 'framework.dart';
+
+export 'package:flute/rendering.dart' show
+  AxisDirection,
+  GrowthDirection;
+
+/// A widget that is bigger on the inside.
+///
+/// [Viewport] is the visual workhorse of the scrolling machinery. It displays a
+/// subset of its children according to its own dimensions and the given
+/// [offset]. As the offset varies, different children are visible through
+/// the viewport.
+///
+/// [Viewport] hosts a bidirectional list of slivers, anchored on a [center]
+/// sliver, which is placed at the zero scroll offset. The center widget is
+/// displayed in the viewport according to the [anchor] property.
+///
+/// Slivers that are earlier in the child list than [center] are displayed in
+/// reverse order in the reverse [axisDirection] starting from the [center]. For
+/// example, if the [axisDirection] is [AxisDirection.down], the first sliver
+/// before [center] is placed above the [center]. The slivers that are later in
+/// the child list than [center] are placed in order in the [axisDirection]. For
+/// example, in the preceding scenario, the first sliver after [center] is
+/// placed below the [center].
+///
+/// [Viewport] cannot contain box children directly. Instead, use a
+/// [SliverList], [SliverFixedExtentList], [SliverGrid], or a
+/// [SliverToBoxAdapter], for example.
+///
+/// See also:
+///
+///  * [ListView], [PageView], [GridView], and [CustomScrollView], which combine
+///    [Scrollable] and [Viewport] into widgets that are easier to use.
+///  * [SliverToBoxAdapter], which allows a box widget to be placed inside a
+///    sliver context (the opposite of this widget).
+///  * [ShrinkWrappingViewport], a variant of [Viewport] that shrink-wraps its
+///    contents along the main axis.
+class Viewport extends MultiChildRenderObjectWidget {
+  /// Creates a widget that is bigger on the inside.
+  ///
+  /// The viewport listens to the [offset], which means you do not need to
+  /// rebuild this widget when the [offset] changes.
+  ///
+  /// The [offset] argument must not be null.
+  ///
+  /// The [cacheExtent] must be specified if the [cacheExtentStyle] is
+  /// not [CacheExtentStyle.pixel].
+  Viewport({
+    Key? key,
+    this.axisDirection = AxisDirection.down,
+    this.crossAxisDirection,
+    this.anchor = 0.0,
+    required this.offset,
+    this.center,
+    this.cacheExtent,
+    this.cacheExtentStyle = CacheExtentStyle.pixel,
+    this.clipBehavior = Clip.hardEdge,
+    List<Widget> slivers = const <Widget>[],
+  }) : assert(offset != null),
+       assert(slivers != null),
+       assert(center == null || slivers.where((Widget child) => child.key == center).length == 1),
+       assert(cacheExtentStyle != null),
+       assert(cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null),
+       assert(clipBehavior != null),
+       super(key: key, children: slivers);
+
+  /// The direction in which the [offset]'s [ViewportOffset.pixels] increases.
+  ///
+  /// For example, if the [axisDirection] is [AxisDirection.down], a scroll
+  /// offset of zero is at the top of the viewport and increases towards the
+  /// bottom of the viewport.
+  final AxisDirection axisDirection;
+
+  /// The direction in which child should be laid out in the cross axis.
+  ///
+  /// If the [axisDirection] is [AxisDirection.down] or [AxisDirection.up], this
+  /// property defaults to [AxisDirection.left] if the ambient [Directionality]
+  /// is [TextDirection.rtl] and [AxisDirection.right] if the ambient
+  /// [Directionality] is [TextDirection.ltr].
+  ///
+  /// If the [axisDirection] is [AxisDirection.left] or [AxisDirection.right],
+  /// this property defaults to [AxisDirection.down].
+  final AxisDirection? crossAxisDirection;
+
+  /// The relative position of the zero scroll offset.
+  ///
+  /// For example, if [anchor] is 0.5 and the [axisDirection] is
+  /// [AxisDirection.down] or [AxisDirection.up], then the zero scroll offset is
+  /// vertically centered within the viewport. If the [anchor] is 1.0, and the
+  /// [axisDirection] is [AxisDirection.right], then the zero scroll offset is
+  /// on the left edge of the viewport.
+  final double anchor;
+
+  /// Which part of the content inside the viewport should be visible.
+  ///
+  /// The [ViewportOffset.pixels] value determines the scroll offset that the
+  /// viewport uses to select which part of its content to display. As the user
+  /// scrolls the viewport, this value changes, which changes the content that
+  /// is displayed.
+  ///
+  /// Typically a [ScrollPosition].
+  final ViewportOffset offset;
+
+  /// The first child in the [GrowthDirection.forward] growth direction.
+  ///
+  /// Children after [center] will be placed in the [axisDirection] relative to
+  /// the [center]. Children before [center] will be placed in the opposite of
+  /// the [axisDirection] relative to the [center].
+  ///
+  /// The [center] must be the key of a child of the viewport.
+  final Key? center;
+
+  /// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
+  ///
+  /// See also:
+  ///
+  ///  * [cacheExtentStyle], which controls the units of the [cacheExtent].
+  final double? cacheExtent;
+
+  /// {@macro flutter.rendering.RenderViewportBase.cacheExtentStyle}
+  final CacheExtentStyle cacheExtentStyle;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge].
+  final Clip clipBehavior;
+
+  /// Given a [BuildContext] and an [AxisDirection], determine the correct cross
+  /// axis direction.
+  ///
+  /// This depends on the [Directionality] if the `axisDirection` is vertical;
+  /// otherwise, the default cross axis direction is downwards.
+  static AxisDirection getDefaultCrossAxisDirection(BuildContext context, AxisDirection axisDirection) {
+    assert(axisDirection != null);
+    switch (axisDirection) {
+      case AxisDirection.up:
+        assert(debugCheckHasDirectionality(
+          context,
+          why: 'to determine the cross-axis direction when the viewport has an \'up\' axisDirection',
+          alternative: 'Alternatively, consider specifying the \'crossAxisDirection\' argument on the Viewport.',
+        ));
+        return textDirectionToAxisDirection(Directionality.of(context));
+      case AxisDirection.right:
+        return AxisDirection.down;
+      case AxisDirection.down:
+        assert(debugCheckHasDirectionality(
+          context,
+          why: 'to determine the cross-axis direction when the viewport has a \'down\' axisDirection',
+          alternative: 'Alternatively, consider specifying the \'crossAxisDirection\' argument on the Viewport.',
+        ));
+        return textDirectionToAxisDirection(Directionality.of(context));
+      case AxisDirection.left:
+        return AxisDirection.down;
+    }
+  }
+
+  @override
+  RenderViewport createRenderObject(BuildContext context) {
+    return RenderViewport(
+      axisDirection: axisDirection,
+      crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection),
+      anchor: anchor,
+      offset: offset,
+      cacheExtent: cacheExtent,
+      cacheExtentStyle: cacheExtentStyle,
+      clipBehavior: clipBehavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderViewport renderObject) {
+    renderObject
+      ..axisDirection = axisDirection
+      ..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection)
+      ..anchor = anchor
+      ..offset = offset
+      ..cacheExtent = cacheExtent
+      ..cacheExtentStyle = cacheExtentStyle
+      ..clipBehavior = clipBehavior;
+  }
+
+  @override
+  _ViewportElement createElement() => _ViewportElement(this);
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<AxisDirection>('axisDirection', axisDirection));
+    properties.add(EnumProperty<AxisDirection>('crossAxisDirection', crossAxisDirection, defaultValue: null));
+    properties.add(DoubleProperty('anchor', anchor));
+    properties.add(DiagnosticsProperty<ViewportOffset>('offset', offset));
+    if (center != null) {
+      properties.add(DiagnosticsProperty<Key>('center', center));
+    } else if (children.isNotEmpty && children.first.key != null) {
+      properties.add(DiagnosticsProperty<Key>('center', children.first.key, tooltip: 'implicit'));
+    }
+    properties.add(DiagnosticsProperty<double>('cacheExtent', cacheExtent));
+    properties.add(DiagnosticsProperty<CacheExtentStyle>('cacheExtentStyle', cacheExtentStyle));
+  }
+}
+
+class _ViewportElement extends MultiChildRenderObjectElement {
+  /// Creates an element that uses the given widget as its configuration.
+  _ViewportElement(Viewport widget) : super(widget);
+
+  @override
+  Viewport get widget => super.widget as Viewport;
+
+  @override
+  RenderViewport get renderObject => super.renderObject as RenderViewport;
+
+  @override
+  void mount(Element? parent, dynamic newSlot) {
+    super.mount(parent, newSlot);
+    _updateCenter();
+  }
+
+  @override
+  void update(MultiChildRenderObjectWidget newWidget) {
+    super.update(newWidget);
+    _updateCenter();
+  }
+
+  void _updateCenter() {
+    // TODO(ianh): cache the keys to make this faster
+    if (widget.center != null) {
+      renderObject.center = children.singleWhere(
+        (Element element) => element.widget.key == widget.center
+      ).renderObject as RenderSliver?;
+    } else if (children.isNotEmpty) {
+      renderObject.center = children.first.renderObject as RenderSliver?;
+    } else {
+      renderObject.center = null;
+    }
+  }
+
+  @override
+  void debugVisitOnstageChildren(ElementVisitor visitor) {
+    children.where((Element e) {
+      final RenderSliver renderSliver = e.renderObject! as RenderSliver;
+      return renderSliver.geometry!.visible;
+    }).forEach(visitor);
+  }
+}
+
+/// A widget that is bigger on the inside and shrink wraps its children in the
+/// main axis.
+///
+/// [ShrinkWrappingViewport] displays a subset of its children according to its
+/// own dimensions and the given [offset]. As the offset varies, different
+/// children are visible through the viewport.
+///
+/// [ShrinkWrappingViewport] differs from [Viewport] in that [Viewport] expands
+/// to fill the main axis whereas [ShrinkWrappingViewport] sizes itself to match
+/// its children in the main axis. This shrink wrapping behavior is expensive
+/// because the children, and hence the viewport, could potentially change size
+/// whenever the [offset] changes (e.g., because of a collapsing header).
+///
+/// [ShrinkWrappingViewport] cannot contain box children directly. Instead, use
+/// a [SliverList], [SliverFixedExtentList], [SliverGrid], or a
+/// [SliverToBoxAdapter], for example.
+///
+/// See also:
+///
+///  * [ListView], [PageView], [GridView], and [CustomScrollView], which combine
+///    [Scrollable] and [ShrinkWrappingViewport] into widgets that are easier to
+///    use.
+///  * [SliverToBoxAdapter], which allows a box widget to be placed inside a
+///    sliver context (the opposite of this widget).
+///  * [Viewport], a viewport that does not shrink-wrap its contents.
+class ShrinkWrappingViewport extends MultiChildRenderObjectWidget {
+  /// Creates a widget that is bigger on the inside and shrink wraps its
+  /// children in the main axis.
+  ///
+  /// The viewport listens to the [offset], which means you do not need to
+  /// rebuild this widget when the [offset] changes.
+  ///
+  /// The [offset] argument must not be null.
+  ShrinkWrappingViewport({
+    Key? key,
+    this.axisDirection = AxisDirection.down,
+    this.crossAxisDirection,
+    required this.offset,
+    this.clipBehavior = Clip.hardEdge,
+    List<Widget> slivers = const <Widget>[],
+  }) : assert(offset != null),
+       super(key: key, children: slivers);
+
+  /// The direction in which the [offset]'s [ViewportOffset.pixels] increases.
+  ///
+  /// For example, if the [axisDirection] is [AxisDirection.down], a scroll
+  /// offset of zero is at the top of the viewport and increases towards the
+  /// bottom of the viewport.
+  final AxisDirection axisDirection;
+
+  /// The direction in which child should be laid out in the cross axis.
+  ///
+  /// If the [axisDirection] is [AxisDirection.down] or [AxisDirection.up], this
+  /// property defaults to [AxisDirection.left] if the ambient [Directionality]
+  /// is [TextDirection.rtl] and [AxisDirection.right] if the ambient
+  /// [Directionality] is [TextDirection.ltr].
+  ///
+  /// If the [axisDirection] is [AxisDirection.left] or [AxisDirection.right],
+  /// this property defaults to [AxisDirection.down].
+  final AxisDirection? crossAxisDirection;
+
+  /// Which part of the content inside the viewport should be visible.
+  ///
+  /// The [ViewportOffset.pixels] value determines the scroll offset that the
+  /// viewport uses to select which part of its content to display. As the user
+  /// scrolls the viewport, this value changes, which changes the content that
+  /// is displayed.
+  ///
+  /// Typically a [ScrollPosition].
+  final ViewportOffset offset;
+
+  /// {@macro flutter.material.Material.clipBehavior}
+  ///
+  /// Defaults to [Clip.hardEdge].
+  final Clip clipBehavior;
+
+  @override
+  RenderShrinkWrappingViewport createRenderObject(BuildContext context) {
+    return RenderShrinkWrappingViewport(
+      axisDirection: axisDirection,
+      crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection),
+      offset: offset,
+      clipBehavior: clipBehavior,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderShrinkWrappingViewport renderObject) {
+    renderObject
+      ..axisDirection = axisDirection
+      ..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection)
+      ..offset = offset
+      ..clipBehavior = clipBehavior;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(EnumProperty<AxisDirection>('axisDirection', axisDirection));
+    properties.add(EnumProperty<AxisDirection>('crossAxisDirection', crossAxisDirection, defaultValue: null));
+    properties.add(DiagnosticsProperty<ViewportOffset>('offset', offset));
+  }
+}
diff --git a/lib/src/widgets/visibility.dart b/lib/src/widgets/visibility.dart
new file mode 100644
index 0000000..652945e
--- /dev/null
+++ b/lib/src/widgets/visibility.dart
@@ -0,0 +1,497 @@
+// Copyright 2014 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.
+
+import 'package:flute/foundation.dart';
+
+import 'basic.dart';
+import 'framework.dart';
+import 'sliver.dart';
+import 'ticker_provider.dart';
+
+/// Whether to show or hide a child.
+///
+/// By default, the [visible] property controls whether the [child] is included
+/// in the subtree or not; when it is not [visible], the [replacement] child
+/// (typically a zero-sized box) is included instead.
+///
+/// A variety of flags can be used to tweak exactly how the child is hidden.
+/// (Changing the flags dynamically is discouraged, as it can cause the [child]
+/// subtree to be rebuilt, with any state in the subtree being discarded.
+/// Typically, only the [visible] flag is changed dynamically.)
+///
+/// These widgets provide some of the facets of this one:
+///
+///  * [Opacity], which can stop its child from being painted.
+///  * [Offstage], which can stop its child from being laid out or painted.
+///  * [TickerMode], which can stop its child from being animated.
+///  * [ExcludeSemantics], which can hide the child from accessibility tools.
+///  * [IgnorePointer], which can disable touch interactions with the child.
+///
+/// Using this widget is not necessary to hide children. The simplest way to
+/// hide a child is just to not include it, or, if a child _must_ be given (e.g.
+/// because the parent is a [StatelessWidget]) then to use [SizedBox.shrink]
+/// instead of the child that would otherwise be included.
+///
+/// See also:
+///
+///  * [AnimatedSwitcher], which can fade from one child to the next as the
+///    subtree changes.
+///  * [AnimatedCrossFade], which can fade between two specific children.
+class Visibility extends StatelessWidget {
+  /// Control whether the given [child] is [visible].
+  ///
+  /// The [child] and [replacement] arguments must not be null.
+  ///
+  /// The boolean arguments must not be null.
+  ///
+  /// The [maintainSemantics] and [maintainInteractivity] arguments can only be
+  /// set if [maintainSize] is set.
+  ///
+  /// The [maintainSize] argument can only be set if [maintainAnimation] is set.
+  ///
+  /// The [maintainAnimation] argument can only be set if [maintainState] is
+  /// set.
+  const Visibility({
+    Key? key,
+    required this.child,
+    this.replacement = const SizedBox.shrink(),
+    this.visible = true,
+    this.maintainState = false,
+    this.maintainAnimation = false,
+    this.maintainSize = false,
+    this.maintainSemantics = false,
+    this.maintainInteractivity = false,
+  }) : assert(child != null),
+       assert(replacement != null),
+       assert(visible != null),
+       assert(maintainState != null),
+       assert(maintainAnimation != null),
+       assert(maintainSize != null),
+       assert(
+         maintainState == true || maintainAnimation == false,
+         'Cannot maintain animations if the state is not also maintained.'
+       ),
+       assert(
+         maintainAnimation == true || maintainSize == false,
+         'Cannot maintain size if animations are not maintained.',
+       ),
+       assert(
+         maintainSize == true || maintainSemantics == false,
+         'Cannot maintain semantics if size is not maintained.',
+       ),
+       assert(
+         maintainSize == true || maintainInteractivity == false,
+         'Cannot maintain interactivity if size is not maintained.',
+       ),
+       super(key: key);
+
+  /// The widget to show or hide, as controlled by [visible].
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// The widget to use when the child is not [visible], assuming that none of
+  /// the `maintain` flags (in particular, [maintainState]) are set.
+  ///
+  /// The normal behavior is to replace the widget with a zero by zero box
+  /// ([SizedBox.shrink]).
+  ///
+  /// See also:
+  ///
+  ///  * [AnimatedCrossFade], which can animate between two children.
+  final Widget replacement;
+
+  /// Switches between showing the [child] or hiding it.
+  ///
+  /// The `maintain` flags should be set to the same values regardless of the
+  /// state of the [visible] property, otherwise they will not operate correctly
+  /// (specifically, the state will be lost regardless of the state of
+  /// [maintainState] whenever any of the `maintain` flags are changed, since
+  /// doing so will result in a subtree shape change).
+  ///
+  /// Unless [maintainState] is set, the [child] subtree will be disposed
+  /// (removed from the tree) while hidden.
+  final bool visible;
+
+  /// Whether to maintain the [State] objects of the [child] subtree when it is
+  /// not [visible].
+  ///
+  /// Keeping the state of the subtree is potentially expensive (because it
+  /// means all the objects are still in memory; their resources are not
+  /// released). It should only be maintained if it cannot be recreated on
+  /// demand. One example of when the state would be maintained is if the child
+  /// subtree contains a [Navigator], since that widget maintains elaborate
+  /// state that cannot be recreated on the fly.
+  ///
+  /// If this property is true, an [Offstage] widget is used to hide the child
+  /// instead of replacing it with [replacement].
+  ///
+  /// If this property is false, then [maintainAnimation] must also be false.
+  ///
+  /// Dynamically changing this value may cause the current state of the
+  /// subtree to be lost (and a new instance of the subtree, with new [State]
+  /// objects, to be immediately created if [visible] is true).
+  final bool maintainState;
+
+  /// Whether to maintain animations within the [child] subtree when it is
+  /// not [visible].
+  ///
+  /// To set this, [maintainState] must also be set.
+  ///
+  /// Keeping animations active when the widget is not visible is even more
+  /// expensive than only maintaining the state.
+  ///
+  /// One example when this might be useful is if the subtree is animating its
+  /// layout in time with an [AnimationController], and the result of that
+  /// layout is being used to influence some other logic. If this flag is false,
+  /// then any [AnimationController]s hosted inside the [child] subtree will be
+  /// muted while the [visible] flag is false.
+  ///
+  /// If this property is true, no [TickerMode] widget is used.
+  ///
+  /// If this property is false, then [maintainSize] must also be false.
+  ///
+  /// Dynamically changing this value may cause the current state of the
+  /// subtree to be lost (and a new instance of the subtree, with new [State]
+  /// objects, to be immediately created if [visible] is true).
+  final bool maintainAnimation;
+
+  /// Whether to maintain space for where the widget would have been.
+  ///
+  /// To set this, [maintainAnimation] and [maintainState] must also be set.
+  ///
+  /// Maintaining the size when the widget is not [visible] is not notably more
+  /// expensive than just keeping animations running without maintaining the
+  /// size, and may in some circumstances be slightly cheaper if the subtree is
+  /// simple and the [visible] property is frequently toggled, since it avoids
+  /// triggering a layout change when the [visible] property is toggled. If the
+  /// [child] subtree is not trivial then it is significantly cheaper to not
+  /// even keep the state (see [maintainState]).
+  ///
+  /// If this property is true, [Opacity] is used instead of [Offstage].
+  ///
+  /// If this property is false, then [maintainSemantics] and
+  /// [maintainInteractivity] must also be false.
+  ///
+  /// Dynamically changing this value may cause the current state of the
+  /// subtree to be lost (and a new instance of the subtree, with new [State]
+  /// objects, to be immediately created if [visible] is true).
+  ///
+  /// See also:
+  ///
+  ///  * [AnimatedOpacity] and [FadeTransition], which apply animations to the
+  ///    opacity for a more subtle effect.
+  final bool maintainSize;
+
+  /// Whether to maintain the semantics for the widget when it is hidden (e.g.
+  /// for accessibility).
+  ///
+  /// To set this, [maintainSize] must also be set.
+  ///
+  /// By default, with [maintainSemantics] set to false, the [child] is not
+  /// visible to accessibility tools when it is hidden from the user. If this
+  /// flag is set to true, then accessibility tools will report the widget as if
+  /// it was present.
+  ///
+  /// Dynamically changing this value may cause the current state of the
+  /// subtree to be lost (and a new instance of the subtree, with new [State]
+  /// objects, to be immediately created if [visible] is true).
+  final bool maintainSemantics;
+
+  /// Whether to allow the widget to be interactive when hidden.
+  ///
+  /// To set this, [maintainSize] must also be set.
+  ///
+  /// By default, with [maintainInteractivity] set to false, touch events cannot
+  /// reach the [child] when it is hidden from the user. If this flag is set to
+  /// true, then touch events will nonetheless be passed through.
+  ///
+  /// Dynamically changing this value may cause the current state of the
+  /// subtree to be lost (and a new instance of the subtree, with new [State]
+  /// objects, to be immediately created if [visible] is true).
+  final bool maintainInteractivity;
+
+  @override
+  Widget build(BuildContext context) {
+    if (maintainSize) {
+      Widget result = child;
+      if (!maintainInteractivity) {
+        result = IgnorePointer(
+          child: child,
+          ignoring: !visible,
+          ignoringSemantics: !visible && !maintainSemantics,
+        );
+      }
+      return Opacity(
+        opacity: visible ? 1.0 : 0.0,
+        alwaysIncludeSemantics: maintainSemantics,
+        child: result,
+      );
+    }
+    assert(!maintainInteractivity);
+    assert(!maintainSemantics);
+    assert(!maintainSize);
+    if (maintainState) {
+      Widget result = child;
+      if (!maintainAnimation)
+        result = TickerMode(child: child, enabled: visible);
+      return Offstage(
+        child: result,
+        offstage: !visible,
+      );
+    }
+    assert(!maintainAnimation);
+    assert(!maintainState);
+    return visible ? child : replacement;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('visible', value: visible, ifFalse: 'hidden', ifTrue: 'visible'));
+    properties.add(FlagProperty('maintainState', value: maintainState, ifFalse: 'maintainState'));
+    properties.add(FlagProperty('maintainAnimation', value: maintainAnimation, ifFalse: 'maintainAnimation'));
+    properties.add(FlagProperty('maintainSize', value: maintainSize, ifFalse: 'maintainSize'));
+    properties.add(FlagProperty('maintainSemantics', value: maintainSemantics, ifFalse: 'maintainSemantics'));
+    properties.add(FlagProperty('maintainInteractivity', value: maintainInteractivity, ifFalse: 'maintainInteractivity'));
+  }
+}
+
+/// Whether to show or hide a sliver child.
+///
+/// By default, the [visible] property controls whether the [sliver] is included
+/// in the subtree or not; when it is not [visible], the [replacementSliver] is
+/// included instead.
+///
+/// A variety of flags can be used to tweak exactly how the sliver is hidden.
+/// (Changing the flags dynamically is discouraged, as it can cause the [sliver]
+/// subtree to be rebuilt, with any state in the subtree being discarded.
+/// Typically, only the [visible] flag is changed dynamically.)
+///
+/// These widgets provide some of the facets of this one:
+///
+///  * [SliverOpacity], which can stop its sliver child from being painted.
+///  * [SliverOffstage], which can stop its sliver child from being laid out or
+///    painted.
+///  * [TickerMode], which can stop its child from being animated.
+///  * [ExcludeSemantics], which can hide the child from accessibility tools.
+///  * [SliverIgnorePointer], which can disable touch interactions with the
+///    sliver child.
+///
+/// Using this widget is not necessary to hide children. The simplest way to
+/// hide a child is just to not include it, or, if a child _must_ be given (e.g.
+/// because the parent is a [StatelessWidget]) then to use a childless
+/// [SliverToBoxAdapter] instead of the child that would otherwise be included.
+class SliverVisibility extends StatelessWidget {
+  /// Control whether the given [sliver] is [visible].
+  ///
+  /// The [sliver] and [replacementSliver] arguments must not be null.
+  ///
+  /// The boolean arguments must not be null.
+  ///
+  /// The [maintainSemantics] and [maintainInteractivity] arguments can only be
+  /// set if [maintainSize] is set.
+  ///
+  /// The [maintainSize] argument can only be set if [maintainAnimation] is set.
+  ///
+  /// The [maintainAnimation] argument can only be set if [maintainState] is
+  /// set.
+  const SliverVisibility({
+    Key? key,
+    required this.sliver,
+    this.replacementSliver = const SliverToBoxAdapter(),
+    this.visible = true,
+    this.maintainState = false,
+    this.maintainAnimation = false,
+    this.maintainSize = false,
+    this.maintainSemantics = false,
+    this.maintainInteractivity = false,
+  }) : assert(sliver != null),
+       assert(replacementSliver != null),
+       assert(visible != null),
+       assert(maintainState != null),
+       assert(maintainAnimation != null),
+       assert(maintainSize != null),
+       assert(maintainSemantics != null),
+       assert(maintainInteractivity != null),
+       assert(
+         maintainState == true || maintainAnimation == false,
+         'Cannot maintain animations if the state is not also maintained.',
+       ),
+       assert(
+         maintainAnimation == true || maintainSize == false,
+         'Cannot maintain size if animations are not maintained.',
+       ),
+       assert(
+         maintainSize == true || maintainSemantics == false,
+         'Cannot maintain semantics if size is not maintained.',
+       ),
+       assert(
+         maintainSize == true || maintainInteractivity == false,
+         'Cannot maintain interactivity if size is not maintained.',
+       ),
+       super(key: key);
+
+  /// The sliver to show or hide, as controlled by [visible].
+  final Widget sliver;
+
+  /// The widget to use when the sliver child is not [visible], assuming that
+  /// none of the `maintain` flags (in particular, [maintainState]) are set.
+  ///
+  /// The normal behavior is to replace the widget with a childless
+  /// [SliverToBoxAdapter], which by default has a geometry of
+  /// [SliverGeometry.zero].
+  final Widget replacementSliver;
+
+  /// Switches between showing the [sliver] or hiding it.
+  ///
+  /// The `maintain` flags should be set to the same values regardless of the
+  /// state of the [visible] property, otherwise they will not operate correctly
+  /// (specifically, the state will be lost regardless of the state of
+  /// [maintainState] whenever any of the `maintain` flags are changed, since
+  /// doing so will result in a subtree shape change).
+  ///
+  /// Unless [maintainState] is set, the [sliver] subtree will be disposed
+  /// (removed from the tree) while hidden.
+  final bool visible;
+
+  /// Whether to maintain the [State] objects of the [sliver] subtree when it is
+  /// not [visible].
+  ///
+  /// Keeping the state of the subtree is potentially expensive (because it
+  /// means all the objects are still in memory; their resources are not
+  /// released). It should only be maintained if it cannot be recreated on
+  /// demand. One example of when the state would be maintained is if the sliver
+  /// subtree contains a [Navigator], since that widget maintains elaborate
+  /// state that cannot be recreated on the fly.
+  ///
+  /// If this property is true, a [SliverOffstage] widget is used to hide the
+  /// sliver instead of replacing it with [replacementSliver].
+  ///
+  /// If this property is false, then [maintainAnimation] must also be false.
+  ///
+  /// Dynamically changing this value may cause the current state of the
+  /// subtree to be lost (and a new instance of the subtree, with new [State]
+  /// objects, to be immediately created if [visible] is true).
+  final bool maintainState;
+
+  /// Whether to maintain animations within the [sliver] subtree when it is
+  /// not [visible].
+  ///
+  /// To set this, [maintainState] must also be set.
+  ///
+  /// Keeping animations active when the widget is not visible is even more
+  /// expensive than only maintaining the state.
+  ///
+  /// One example when this might be useful is if the subtree is animating its
+  /// layout in time with an [AnimationController], and the result of that
+  /// layout is being used to influence some other logic. If this flag is false,
+  /// then any [AnimationController]s hosted inside the [sliver] subtree will be
+  /// muted while the [visible] flag is false.
+  ///
+  /// If this property is true, no [TickerMode] widget is used.
+  ///
+  /// If this property is false, then [maintainSize] must also be false.
+  ///
+  /// Dynamically changing this value may cause the current state of the
+  /// subtree to be lost (and a new instance of the subtree, with new [State]
+  /// objects, to be immediately created if [visible] is true).
+  final bool maintainAnimation;
+
+  /// Whether to maintain space for where the sliver would have been.
+  ///
+  /// To set this, [maintainAnimation] must also be set.
+  ///
+  /// Maintaining the size when the sliver is not [visible] is not notably more
+  /// expensive than just keeping animations running without maintaining the
+  /// size, and may in some circumstances be slightly cheaper if the subtree is
+  /// simple and the [visible] property is frequently toggled, since it avoids
+  /// triggering a layout change when the [visible] property is toggled. If the
+  /// [sliver] subtree is not trivial then it is significantly cheaper to not
+  /// even keep the state (see [maintainState]).
+  ///
+  /// If this property is true, [SliverOpacity] is used instead of
+  /// [SliverOffstage].
+  ///
+  /// If this property is false, then [maintainSemantics] and
+  /// [maintainInteractivity] must also be false.
+  ///
+  /// Dynamically changing this value may cause the current state of the
+  /// subtree to be lost (and a new instance of the subtree, with new [State]
+  /// objects, to be immediately created if [visible] is true).
+  final bool maintainSize;
+
+  /// Whether to maintain the semantics for the sliver when it is hidden (e.g.
+  /// for accessibility).
+  ///
+  /// To set this, [maintainSize] must also be set.
+  ///
+  /// By default, with [maintainSemantics] set to false, the [sliver] is not
+  /// visible to accessibility tools when it is hidden from the user. If this
+  /// flag is set to true, then accessibility tools will report the widget as if
+  /// it was present.
+  ///
+  /// Dynamically changing this value may cause the current state of the
+  /// subtree to be lost (and a new instance of the subtree, with new [State]
+  /// objects, to be immediately created if [visible] is true).
+  final bool maintainSemantics;
+
+  /// Whether to allow the sliver to be interactive when hidden.
+  ///
+  /// To set this, [maintainSize] must also be set.
+  ///
+  /// By default, with [maintainInteractivity] set to false, touch events cannot
+  /// reach the [sliver] when it is hidden from the user. If this flag is set to
+  /// true, then touch events will nonetheless be passed through.
+  ///
+  /// Dynamically changing this value may cause the current state of the
+  /// subtree to be lost (and a new instance of the subtree, with new [State]
+  /// objects, to be immediately created if [visible] is true).
+  final bool maintainInteractivity;
+
+  @override
+  Widget build(BuildContext context) {
+    if (maintainSize) {
+      Widget result = sliver;
+      if (!maintainInteractivity) {
+        result = SliverIgnorePointer(
+          sliver: sliver,
+          ignoring: !visible,
+          ignoringSemantics: !visible && !maintainSemantics,
+        );
+      }
+      return SliverOpacity(
+        opacity: visible ? 1.0 : 0.0,
+        alwaysIncludeSemantics: maintainSemantics,
+        sliver: result,
+      );
+    }
+    assert(!maintainInteractivity);
+    assert(!maintainSemantics);
+    assert(!maintainSize);
+    if (maintainState) {
+      Widget result = sliver;
+      if (!maintainAnimation)
+        result = TickerMode(child: sliver, enabled: visible);
+      return SliverOffstage(
+        sliver: result,
+        offstage: !visible,
+      );
+    }
+    assert(!maintainAnimation);
+    assert(!maintainState);
+    return visible ? sliver : replacementSliver;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(FlagProperty('visible', value: visible, ifFalse: 'hidden', ifTrue: 'visible'));
+    properties.add(FlagProperty('maintainState', value: maintainState, ifFalse: 'maintainState'));
+    properties.add(FlagProperty('maintainAnimation', value: maintainAnimation, ifFalse: 'maintainAnimation'));
+    properties.add(FlagProperty('maintainSize', value: maintainSize, ifFalse: 'maintainSize'));
+    properties.add(FlagProperty('maintainSemantics', value: maintainSemantics, ifFalse: 'maintainSemantics'));
+    properties.add(FlagProperty('maintainInteractivity', value: maintainInteractivity, ifFalse: 'maintainInteractivity'));
+  }
+}
diff --git a/lib/src/widgets/widget_inspector.dart b/lib/src/widgets/widget_inspector.dart
new file mode 100644
index 0000000..346eec7
--- /dev/null
+++ b/lib/src/widgets/widget_inspector.dart
@@ -0,0 +1,3118 @@
+// Copyright 2014 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.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:developer' as developer;
+import 'dart:math' as math;
+import 'dart:typed_data';
+import 'package:flute/ui.dart' as ui
+    show
+        ClipOp,
+        Image,
+        ImageByteFormat,
+        Paragraph,
+        Picture,
+        PictureRecorder,
+        PointMode,
+        SceneBuilder,
+        Vertices;
+import 'package:flute/ui.dart' show Canvas, Offset;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/painting.dart';
+import 'package:flute/rendering.dart';
+import 'package:flute/scheduler.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+import 'app.dart';
+import 'basic.dart';
+import 'binding.dart';
+import 'debug.dart';
+import 'framework.dart';
+import 'gesture_detector.dart';
+
+/// Signature for the builder callback used by
+/// [WidgetInspector.selectButtonBuilder].
+typedef InspectorSelectButtonBuilder = Widget Function(BuildContext context, VoidCallback onPressed);
+
+typedef _RegisterServiceExtensionCallback = void Function({
+  required String name,
+  required ServiceExtensionCallback callback,
+});
+
+/// A layer that mimics the behavior of another layer.
+///
+/// A proxy layer is used for cases where a layer needs to be placed into
+/// multiple trees of layers.
+class _ProxyLayer extends Layer {
+  _ProxyLayer(this._layer);
+
+  final Layer _layer;
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    _layer.addToScene(builder, layerOffset);
+  }
+
+  @override
+  @protected
+  bool findAnnotations<S extends Object>(
+    AnnotationResult<S> result,
+    Offset localPosition, {
+    required bool onlyFirst,
+  }) {
+    return _layer.findAnnotations(result, localPosition, onlyFirst: onlyFirst);
+  }
+}
+
+/// A [Canvas] that multicasts all method calls to a main canvas and a
+/// secondary screenshot canvas so that a screenshot can be recorded at the same
+/// time as performing a normal paint.
+class _MulticastCanvas implements Canvas {
+  _MulticastCanvas({
+    required Canvas main,
+    required Canvas screenshot,
+  }) : assert(main != null),
+       assert(screenshot != null),
+       _main = main,
+       _screenshot = screenshot;
+
+  final Canvas _main;
+  final Canvas _screenshot;
+
+  @override
+  void clipPath(Path path, { bool doAntiAlias = true }) {
+    _main.clipPath(path, doAntiAlias: doAntiAlias);
+    _screenshot.clipPath(path, doAntiAlias: doAntiAlias);
+  }
+
+  @override
+  void clipRRect(RRect rrect, { bool doAntiAlias = true }) {
+    _main.clipRRect(rrect, doAntiAlias: doAntiAlias);
+    _screenshot.clipRRect(rrect, doAntiAlias: doAntiAlias);
+  }
+
+  @override
+  void clipRect(Rect rect, { ui.ClipOp clipOp = ui.ClipOp.intersect, bool doAntiAlias = true }) {
+    _main.clipRect(rect, clipOp: clipOp, doAntiAlias: doAntiAlias);
+    _screenshot.clipRect(rect, clipOp: clipOp, doAntiAlias: doAntiAlias);
+  }
+
+  @override
+  void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint) {
+    _main.drawArc(rect, startAngle, sweepAngle, useCenter, paint);
+    _screenshot.drawArc(rect, startAngle, sweepAngle, useCenter, paint);
+  }
+
+  @override
+  void drawAtlas(ui.Image atlas, List<RSTransform> transforms, List<Rect> rects, List<Color>? colors, BlendMode? blendMode, Rect? cullRect, Paint paint) {
+    _main.drawAtlas(atlas, transforms, rects, colors, blendMode, cullRect, paint);
+    _screenshot.drawAtlas(atlas, transforms, rects, colors, blendMode, cullRect, paint);
+  }
+
+  @override
+  void drawCircle(Offset c, double radius, Paint paint) {
+    _main.drawCircle(c, radius, paint);
+    _screenshot.drawCircle(c, radius, paint);
+  }
+
+  @override
+  void drawColor(Color color, BlendMode blendMode) {
+    _main.drawColor(color, blendMode);
+    _screenshot.drawColor(color, blendMode);
+  }
+
+  @override
+  void drawDRRect(RRect outer, RRect inner, Paint paint) {
+    _main.drawDRRect(outer, inner, paint);
+    _screenshot.drawDRRect(outer, inner, paint);
+  }
+
+  @override
+  void drawImage(ui.Image image, Offset p, Paint paint) {
+    _main.drawImage(image, p, paint);
+    _screenshot.drawImage(image, p, paint);
+  }
+
+  @override
+  void drawImageNine(ui.Image image, Rect center, Rect dst, Paint paint) {
+    _main.drawImageNine(image, center, dst, paint);
+    _screenshot.drawImageNine(image, center, dst, paint);
+  }
+
+  @override
+  void drawImageRect(ui.Image image, Rect src, Rect dst, Paint paint) {
+    _main.drawImageRect(image, src, dst, paint);
+    _screenshot.drawImageRect(image, src, dst, paint);
+  }
+
+  @override
+  void drawLine(Offset p1, Offset p2, Paint paint) {
+    _main.drawLine(p1, p2, paint);
+    _screenshot.drawLine(p1, p2, paint);
+  }
+
+  @override
+  void drawOval(Rect rect, Paint paint) {
+    _main.drawOval(rect, paint);
+    _screenshot.drawOval(rect, paint);
+  }
+
+  @override
+  void drawPaint(Paint paint) {
+    _main.drawPaint(paint);
+    _screenshot.drawPaint(paint);
+  }
+
+  @override
+  void drawParagraph(ui.Paragraph paragraph, Offset offset) {
+    _main.drawParagraph(paragraph, offset);
+    _screenshot.drawParagraph(paragraph, offset);
+  }
+
+  @override
+  void drawPath(Path path, Paint paint) {
+    _main.drawPath(path, paint);
+    _screenshot.drawPath(path, paint);
+  }
+
+  @override
+  void drawPicture(ui.Picture picture) {
+    _main.drawPicture(picture);
+    _screenshot.drawPicture(picture);
+  }
+
+  @override
+  void drawPoints(ui.PointMode pointMode, List<Offset> points, Paint paint) {
+    _main.drawPoints(pointMode, points, paint);
+    _screenshot.drawPoints(pointMode, points, paint);
+  }
+
+  @override
+  void drawRRect(RRect rrect, Paint paint) {
+    _main.drawRRect(rrect, paint);
+    _screenshot.drawRRect(rrect, paint);
+  }
+
+  @override
+  void drawRawAtlas(ui.Image atlas, Float32List rstTransforms, Float32List rects, Int32List? colors, BlendMode? blendMode, Rect? cullRect, Paint paint) {
+    _main.drawRawAtlas(atlas, rstTransforms, rects, colors, blendMode, cullRect, paint);
+    _screenshot.drawRawAtlas(atlas, rstTransforms, rects, colors, blendMode, cullRect, paint);
+  }
+
+  @override
+  void drawRawPoints(ui.PointMode pointMode, Float32List points, Paint paint) {
+    _main.drawRawPoints(pointMode, points, paint);
+    _screenshot.drawRawPoints(pointMode, points, paint);
+  }
+
+  @override
+  void drawRect(Rect rect, Paint paint) {
+    _main.drawRect(rect, paint);
+    _screenshot.drawRect(rect, paint);
+  }
+
+  @override
+  void drawShadow(Path path, Color color, double elevation, bool transparentOccluder) {
+    _main.drawShadow(path, color, elevation, transparentOccluder);
+    _screenshot.drawShadow(path, color, elevation, transparentOccluder);
+  }
+
+  @override
+  void drawVertices(ui.Vertices vertices, BlendMode blendMode, Paint paint) {
+    _main.drawVertices(vertices, blendMode, paint);
+    _screenshot.drawVertices(vertices, blendMode, paint);
+  }
+
+  @override
+  int getSaveCount() {
+    // The main canvas is used instead of the screenshot canvas as the main
+    // canvas is guaranteed to be consistent with the canvas expected by the
+    // normal paint pipeline so any logic depending on getSaveCount() will
+    // behave the same as for the regular paint pipeline.
+    return _main.getSaveCount();
+  }
+
+  @override
+  void restore() {
+    _main.restore();
+    _screenshot.restore();
+  }
+
+  @override
+  void rotate(double radians) {
+    _main.rotate(radians);
+    _screenshot.rotate(radians);
+  }
+
+  @override
+  void save() {
+    _main.save();
+    _screenshot.save();
+  }
+
+  @override
+  void saveLayer(Rect? bounds, Paint paint) {
+    _main.saveLayer(bounds, paint);
+    _screenshot.saveLayer(bounds, paint);
+  }
+
+  @override
+  void scale(double sx, [ double? sy ]) {
+    _main.scale(sx, sy);
+    _screenshot.scale(sx, sy);
+  }
+
+  @override
+  void skew(double sx, double sy) {
+    _main.skew(sx, sy);
+    _screenshot.skew(sx, sy);
+  }
+
+  @override
+  void transform(Float64List matrix4) {
+    _main.transform(matrix4);
+    _screenshot.transform(matrix4);
+  }
+
+  @override
+  void translate(double dx, double dy) {
+    _main.translate(dx, dy);
+    _screenshot.translate(dx, dy);
+  }
+}
+
+Rect _calculateSubtreeBoundsHelper(RenderObject object, Matrix4 transform) {
+  Rect bounds = MatrixUtils.transformRect(transform, object.semanticBounds);
+
+  object.visitChildren((RenderObject child) {
+    final Matrix4 childTransform = transform.clone();
+    object.applyPaintTransform(child, childTransform);
+    Rect childBounds = _calculateSubtreeBoundsHelper(child, childTransform);
+    final Rect? paintClip = object.describeApproximatePaintClip(child);
+    if (paintClip != null) {
+      final Rect transformedPaintClip = MatrixUtils.transformRect(
+        transform,
+        paintClip,
+      );
+      childBounds = childBounds.intersect(transformedPaintClip);
+    }
+
+    if (childBounds.isFinite && !childBounds.isEmpty) {
+      bounds = bounds.isEmpty ? childBounds : bounds.expandToInclude(childBounds);
+    }
+  });
+
+  return bounds;
+}
+
+/// Calculate bounds for a render object and all of its descendants.
+Rect _calculateSubtreeBounds(RenderObject object) {
+  return _calculateSubtreeBoundsHelper(object, Matrix4.identity());
+}
+
+/// A layer that omits its own offset when adding children to the scene so that
+/// screenshots render to the scene in the local coordinate system of the layer.
+class _ScreenshotContainerLayer extends OffsetLayer {
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    addChildrenToScene(builder, layerOffset);
+  }
+}
+
+/// Data shared between nested [_ScreenshotPaintingContext] objects recording
+/// a screenshot.
+class _ScreenshotData {
+  _ScreenshotData({
+    required this.target,
+  }) : assert(target != null),
+       containerLayer = _ScreenshotContainerLayer();
+
+  /// Target to take a screenshot of.
+  final RenderObject target;
+
+  /// Root of the layer tree containing the screenshot.
+  final OffsetLayer containerLayer;
+
+  /// Whether the screenshot target has already been found in the render tree.
+  bool foundTarget = false;
+
+  /// Whether paint operations should record to the screenshot.
+  ///
+  /// At least one of [includeInScreenshot] and [includeInRegularContext] must
+  /// be true.
+  bool includeInScreenshot = false;
+
+  /// Whether paint operations should record to the regular context.
+  ///
+  /// This should only be set to false before paint operations that should only
+  /// apply to the screenshot such rendering debug information about the
+  /// [target].
+  ///
+  /// At least one of [includeInScreenshot] and [includeInRegularContext] must
+  /// be true.
+  bool includeInRegularContext = true;
+
+  /// Offset of the screenshot corresponding to the offset [target] was given as
+  /// part of the regular paint.
+  Offset get screenshotOffset {
+    assert(foundTarget);
+    return containerLayer.offset;
+  }
+  set screenshotOffset(Offset offset) {
+    containerLayer.offset = offset;
+  }
+}
+
+/// A place to paint to build screenshots of [RenderObject]s.
+///
+/// Requires that the render objects have already painted successfully as part
+/// of the regular rendering pipeline.
+/// This painting context behaves the same as standard [PaintingContext] with
+/// instrumentation added to compute a screenshot of a specified [RenderObject]
+/// added. To correctly mimic the behavior of the regular rendering pipeline, the
+/// full subtree of the first [RepaintBoundary] ancestor of the specified
+/// [RenderObject] will also be rendered rather than just the subtree of the
+/// render object.
+class _ScreenshotPaintingContext extends PaintingContext {
+  _ScreenshotPaintingContext({
+    required ContainerLayer containerLayer,
+    required Rect estimatedBounds,
+    required _ScreenshotData screenshotData,
+  }) : _data = screenshotData,
+       super(containerLayer, estimatedBounds);
+
+  final _ScreenshotData _data;
+
+  // Recording state
+  PictureLayer? _screenshotCurrentLayer;
+  ui.PictureRecorder? _screenshotRecorder;
+  Canvas? _screenshotCanvas;
+  _MulticastCanvas? _multicastCanvas;
+
+  @override
+  Canvas get canvas {
+    if (_data.includeInScreenshot) {
+      if (_screenshotCanvas == null) {
+        _startRecordingScreenshot();
+      }
+      assert(_screenshotCanvas != null);
+      return _data.includeInRegularContext ? _multicastCanvas! : _screenshotCanvas!;
+    } else {
+      assert(_data.includeInRegularContext);
+      return super.canvas;
+    }
+  }
+
+  bool get _isScreenshotRecording {
+    final bool hasScreenshotCanvas = _screenshotCanvas != null;
+    assert(() {
+      if (hasScreenshotCanvas) {
+        assert(_screenshotCurrentLayer != null);
+        assert(_screenshotRecorder != null);
+        assert(_screenshotCanvas != null);
+      } else {
+        assert(_screenshotCurrentLayer == null);
+        assert(_screenshotRecorder == null);
+        assert(_screenshotCanvas == null);
+      }
+      return true;
+    }());
+    return hasScreenshotCanvas;
+  }
+
+  void _startRecordingScreenshot() {
+    assert(_data.includeInScreenshot);
+    assert(!_isScreenshotRecording);
+    _screenshotCurrentLayer = PictureLayer(estimatedBounds);
+    _screenshotRecorder = ui.PictureRecorder();
+    _screenshotCanvas = Canvas(_screenshotRecorder!);
+    _data.containerLayer.append(_screenshotCurrentLayer!);
+    if (_data.includeInRegularContext) {
+      _multicastCanvas = _MulticastCanvas(
+        main: super.canvas,
+        screenshot: _screenshotCanvas!,
+      );
+    } else {
+      _multicastCanvas = null;
+    }
+  }
+
+  @override
+  void stopRecordingIfNeeded() {
+    super.stopRecordingIfNeeded();
+    _stopRecordingScreenshotIfNeeded();
+  }
+
+  void _stopRecordingScreenshotIfNeeded() {
+    if (!_isScreenshotRecording)
+      return;
+    // There is no need to ever draw repaint rainbows as part of the screenshot.
+    _screenshotCurrentLayer!.picture = _screenshotRecorder!.endRecording();
+    _screenshotCurrentLayer = null;
+    _screenshotRecorder = null;
+    _multicastCanvas = null;
+    _screenshotCanvas = null;
+  }
+
+  @override
+  void appendLayer(Layer layer) {
+    if (_data.includeInRegularContext) {
+      super.appendLayer(layer);
+      if (_data.includeInScreenshot) {
+        assert(!_isScreenshotRecording);
+        // We must use a proxy layer here as the layer is already attached to
+        // the regular layer tree.
+        _data.containerLayer.append(_ProxyLayer(layer));
+      }
+    } else {
+      // Only record to the screenshot.
+      assert(!_isScreenshotRecording);
+      assert(_data.includeInScreenshot);
+      layer.remove();
+      _data.containerLayer.append(layer);
+      return;
+    }
+  }
+
+  @override
+  PaintingContext createChildContext(ContainerLayer childLayer, Rect bounds) {
+    if (_data.foundTarget) {
+      // We have already found the screenshotTarget in the layer tree
+      // so we can optimize and use a standard PaintingContext.
+      return super.createChildContext(childLayer, bounds);
+    } else {
+      return _ScreenshotPaintingContext(
+        containerLayer: childLayer,
+        estimatedBounds: bounds,
+        screenshotData: _data,
+      );
+    }
+  }
+
+  @override
+  void paintChild(RenderObject child, Offset offset) {
+    final bool isScreenshotTarget = identical(child, _data.target);
+    if (isScreenshotTarget) {
+      assert(!_data.includeInScreenshot);
+      assert(!_data.foundTarget);
+      _data.foundTarget = true;
+      _data.screenshotOffset = offset;
+      _data.includeInScreenshot = true;
+    }
+    super.paintChild(child, offset);
+    if (isScreenshotTarget) {
+      _stopRecordingScreenshotIfNeeded();
+      _data.includeInScreenshot = false;
+    }
+  }
+
+  /// Captures an image of the current state of [renderObject] and its children.
+  ///
+  /// The returned [ui.Image] has uncompressed raw RGBA bytes, will be offset
+  /// by the top-left corner of [renderBounds], and have dimensions equal to the
+  /// size of [renderBounds] multiplied by [pixelRatio].
+  ///
+  /// To use [toImage], the render object must have gone through the paint phase
+  /// (i.e. [debugNeedsPaint] must be false).
+  ///
+  /// The [pixelRatio] describes the scale between the logical pixels and the
+  /// size of the output image. It is independent of the
+  /// [window.devicePixelRatio] for the device, so specifying 1.0 (the default)
+  /// will give you a 1:1 mapping between logical pixels and the output pixels
+  /// in the image.
+  ///
+  /// The [debugPaint] argument specifies whether the image should include the
+  /// output of [RenderObject.debugPaint] for [renderObject] with
+  /// [debugPaintSizeEnabled] set to true. Debug paint information is not
+  /// included for the children of [renderObject] so that it is clear precisely
+  /// which object the debug paint information references.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderRepaintBoundary.toImage] for a similar API for [RenderObject]s
+  ///    that are repaint boundaries that can be used outside of the inspector.
+  ///  * [OffsetLayer.toImage] for a similar API at the layer level.
+  ///  * [dart:ui.Scene.toImage] for more information about the image returned.
+  static Future<ui.Image> toImage(
+    RenderObject renderObject,
+    Rect renderBounds, {
+    double pixelRatio = 1.0,
+    bool debugPaint = false,
+  }) {
+    RenderObject repaintBoundary = renderObject;
+    while (repaintBoundary != null && !repaintBoundary.isRepaintBoundary) {
+      repaintBoundary = repaintBoundary.parent! as RenderObject;
+    }
+    assert(repaintBoundary != null);
+    final _ScreenshotData data = _ScreenshotData(target: renderObject);
+    final _ScreenshotPaintingContext context = _ScreenshotPaintingContext(
+      containerLayer: repaintBoundary.debugLayer!,
+      estimatedBounds: repaintBoundary.paintBounds,
+      screenshotData: data,
+    );
+
+    if (identical(renderObject, repaintBoundary)) {
+      // Painting the existing repaint boundary to the screenshot is sufficient.
+      // We don't just take a direct screenshot of the repaint boundary as we
+      // want to capture debugPaint information as well.
+      data.containerLayer.append(_ProxyLayer(repaintBoundary.debugLayer!));
+      data.foundTarget = true;
+      final OffsetLayer offsetLayer = repaintBoundary.debugLayer! as OffsetLayer;
+      data.screenshotOffset = offsetLayer.offset;
+    } else {
+      // Repaint everything under the repaint boundary.
+      // We call debugInstrumentRepaintCompositedChild instead of paintChild as
+      // we need to force everything under the repaint boundary to repaint.
+      PaintingContext.debugInstrumentRepaintCompositedChild(
+        repaintBoundary,
+        customContext: context,
+      );
+    }
+
+    // The check that debugPaintSizeEnabled is false exists to ensure we only
+    // call debugPaint when it wasn't already called.
+    if (debugPaint && !debugPaintSizeEnabled) {
+      data.includeInRegularContext = false;
+      // Existing recording may be to a canvas that draws to both the normal and
+      // screenshot canvases.
+      context.stopRecordingIfNeeded();
+      assert(data.foundTarget);
+      data.includeInScreenshot = true;
+
+      debugPaintSizeEnabled = true;
+      try {
+        renderObject.debugPaint(context, data.screenshotOffset);
+      } finally {
+        debugPaintSizeEnabled = false;
+        context.stopRecordingIfNeeded();
+      }
+    }
+
+    // We must build the regular scene before we can build the screenshot
+    // scene as building the screenshot scene assumes addToScene has already
+    // been called successfully for all layers in the regular scene.
+    repaintBoundary.debugLayer!.buildScene(ui.SceneBuilder());
+
+    return data.containerLayer.toImage(renderBounds, pixelRatio: pixelRatio);
+  }
+}
+
+/// A class describing a step along a path through a tree of [DiagnosticsNode]
+/// objects.
+///
+/// This class is used to bundle all data required to display the tree with just
+/// the nodes along a path expanded into a single JSON payload.
+class _DiagnosticsPathNode {
+  /// Creates a full description of a step in a path through a tree of
+  /// [DiagnosticsNode] objects.
+  ///
+  /// The [node] and [child] arguments must not be null.
+  _DiagnosticsPathNode({
+    required this.node,
+    required this.children,
+    this.childIndex,
+  }) : assert(node != null),
+       assert(children != null);
+
+  /// Node at the point in the path this [_DiagnosticsPathNode] is describing.
+  final DiagnosticsNode node;
+
+  /// Children of the [node] being described.
+  ///
+  /// This value is cached instead of relying on `node.getChildren()` as that
+  /// method call might create new [DiagnosticsNode] objects for each child
+  /// and we would prefer to use the identical [DiagnosticsNode] for each time
+  /// a node exists in the path.
+  final List<DiagnosticsNode> children;
+
+  /// Index of the child that the path continues on.
+  ///
+  /// Equal to null if the path does not continue.
+  final int? childIndex;
+}
+
+List<_DiagnosticsPathNode>? _followDiagnosticableChain(
+  List<Diagnosticable> chain, {
+  String? name,
+  DiagnosticsTreeStyle? style,
+}) {
+  final List<_DiagnosticsPathNode> path = <_DiagnosticsPathNode>[];
+  if (chain.isEmpty)
+    return path;
+  DiagnosticsNode diagnostic = chain.first.toDiagnosticsNode(name: name, style: style);
+  for (int i = 1; i < chain.length; i += 1) {
+    final Diagnosticable target = chain[i];
+    bool foundMatch = false;
+    final List<DiagnosticsNode> children = diagnostic.getChildren();
+    for (int j = 0; j < children.length; j += 1) {
+      final DiagnosticsNode child = children[j];
+      if (child.value == target) {
+        foundMatch = true;
+        path.add(_DiagnosticsPathNode(
+          node: diagnostic,
+          children: children,
+          childIndex: j,
+        ));
+        diagnostic = child;
+        break;
+      }
+    }
+    assert(foundMatch);
+  }
+  path.add(_DiagnosticsPathNode(node: diagnostic, children: diagnostic.getChildren()));
+  return path;
+}
+
+/// Signature for the selection change callback used by
+/// [WidgetInspectorService.selectionChangedCallback].
+typedef InspectorSelectionChangedCallback = void Function();
+
+/// Structure to help reference count Dart objects referenced by a GUI tool
+/// using [WidgetInspectorService].
+class _InspectorReferenceData {
+  _InspectorReferenceData(this.object);
+
+  final Object object;
+  int count = 1;
+}
+
+// Production implementation of [WidgetInspectorService].
+class _WidgetInspectorService = Object with WidgetInspectorService;
+
+/// Service used by GUI tools to interact with the [WidgetInspector].
+///
+/// Calls to this object are typically made from GUI tools such as the [Flutter
+/// IntelliJ Plugin](https://github.com/flutter/flutter-intellij/blob/master/README.md)
+/// using the [Dart VM Service protocol](https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md).
+/// This class uses its own object id and manages object lifecycles itself
+/// instead of depending on the [object ids](https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#getobject)
+/// specified by the VM Service Protocol because the VM Service Protocol ids
+/// expire unpredictably. Object references are tracked in groups so that tools
+/// that clients can use dereference all objects in a group with a single
+/// operation making it easier to avoid memory leaks.
+///
+/// All methods in this class are appropriate to invoke from debugging tools
+/// using the Observatory service protocol to evaluate Dart expressions of the
+/// form `WidgetInspectorService.instance.methodName(arg1, arg2, ...)`. If you
+/// make changes to any instance method of this class you need to verify that
+/// the [Flutter IntelliJ Plugin](https://github.com/flutter/flutter-intellij/blob/master/README.md)
+/// widget inspector support still works with the changes.
+///
+/// All methods returning String values return JSON.
+mixin WidgetInspectorService {
+  /// Ring of cached JSON values to prevent JSON from being garbage
+  /// collected before it can be requested over the Observatory protocol.
+  final List<String?> _serializeRing = List<String?>.filled(20, null, growable: false);
+  int _serializeRingIndex = 0;
+
+  /// The current [WidgetInspectorService].
+  static WidgetInspectorService get instance => _instance;
+  static WidgetInspectorService _instance = _WidgetInspectorService();
+  @protected
+  static set instance(WidgetInspectorService instance) {
+    _instance = instance;
+  }
+
+  static bool _debugServiceExtensionsRegistered = false;
+
+  /// Ground truth tracking what object(s) are currently selected used by both
+  /// GUI tools such as the Flutter IntelliJ Plugin and the [WidgetInspector]
+  /// displayed on the device.
+  final InspectorSelection selection = InspectorSelection();
+
+  /// Callback typically registered by the [WidgetInspector] to receive
+  /// notifications when [selection] changes.
+  ///
+  /// The Flutter IntelliJ Plugin does not need to listen for this event as it
+  /// instead listens for `dart:developer` `inspect` events which also trigger
+  /// when the inspection target changes on device.
+  InspectorSelectionChangedCallback? selectionChangedCallback;
+
+  /// The Observatory protocol does not keep alive object references so this
+  /// class needs to manually manage groups of objects that should be kept
+  /// alive.
+  final Map<String, Set<_InspectorReferenceData>> _groups = <String, Set<_InspectorReferenceData>>{};
+  final Map<String, _InspectorReferenceData> _idToReferenceData = <String, _InspectorReferenceData>{};
+  final Map<Object, String> _objectToId = Map<Object, String>.identity();
+  int _nextId = 0;
+
+  List<String>? _pubRootDirectories;
+
+  bool _trackRebuildDirtyWidgets = false;
+  bool _trackRepaintWidgets = false;
+
+  FlutterExceptionHandler? _structuredExceptionHandler;
+
+  late _RegisterServiceExtensionCallback _registerServiceExtensionCallback;
+  /// Registers a service extension method with the given name (full
+  /// name "ext.flutter.inspector.name").
+  ///
+  /// The given callback is called when the extension method is called. The
+  /// callback must return a value that can be converted to JSON using
+  /// `json.encode()` (see [JsonEncoder]). The return value is stored as a
+  /// property named `result` in the JSON. In case of failure, the failure is
+  /// reported to the remote caller and is dumped to the logs.
+  @protected
+  void registerServiceExtension({
+    required String name,
+    required ServiceExtensionCallback callback,
+  }) {
+    _registerServiceExtensionCallback(
+      name: 'inspector.$name',
+      callback: callback,
+    );
+  }
+
+  /// Registers a service extension method with the given name (full
+  /// name "ext.flutter.inspector.name"), which takes no arguments.
+  void _registerSignalServiceExtension({
+    required String name,
+    required FutureOr<Object?> callback(),
+  }) {
+    registerServiceExtension(
+      name: name,
+      callback: (Map<String, String> parameters) async {
+        return <String, Object?>{'result': await callback()};
+      },
+    );
+  }
+
+  /// Registers a service extension method with the given name (full
+  /// name "ext.flutter.inspector.name"), which takes a single optional argument
+  /// "objectGroup" specifying what group is used to manage lifetimes of
+  /// object references in the returned JSON (see [disposeGroup]).
+  /// If "objectGroup" is omitted, the returned JSON will not include any object
+  /// references to avoid leaking memory.
+  void _registerObjectGroupServiceExtension({
+    required String name,
+    required FutureOr<Object?> callback(String objectGroup),
+  }) {
+    registerServiceExtension(
+      name: name,
+      callback: (Map<String, String> parameters) async {
+        return <String, Object?>{'result': await callback(parameters['objectGroup']!)};
+      },
+    );
+  }
+
+  /// Registers a service extension method with the given name (full
+  /// name "ext.flutter.inspector.name"), which takes a single argument
+  /// "enabled" which can have the value "true" or the value "false"
+  /// or can be omitted to read the current value. (Any value other
+  /// than "true" is considered equivalent to "false". Other arguments
+  /// are ignored.)
+  ///
+  /// Calls the `getter` callback to obtain the value when
+  /// responding to the service extension method being called.
+  ///
+  /// Calls the `setter` callback with the new value when the
+  /// service extension method is called with a new value.
+  void _registerBoolServiceExtension({
+    required String name,
+    required AsyncValueGetter<bool> getter,
+    required AsyncValueSetter<bool> setter,
+  }) {
+    assert(name != null);
+    assert(getter != null);
+    assert(setter != null);
+    registerServiceExtension(
+      name: name,
+      callback: (Map<String, String> parameters) async {
+        if (parameters.containsKey('enabled')) {
+          final bool value = parameters['enabled'] == 'true';
+          await setter(value);
+          _postExtensionStateChangedEvent(name, value);
+        }
+        return <String, dynamic>{'enabled': await getter() ? 'true' : 'false'};
+      },
+    );
+  }
+
+  /// Sends an event when a service extension's state is changed.
+  ///
+  /// Clients should listen for this event to stay aware of the current service
+  /// extension state. Any service extension that manages a state should call
+  /// this method on state change.
+  ///
+  /// `value` reflects the newly updated service extension value.
+  ///
+  /// This will be called automatically for service extensions registered via
+  /// [registerBoolServiceExtension].
+  void _postExtensionStateChangedEvent(String name, Object? value) {
+    postEvent(
+      'Flutter.ServiceExtensionStateChanged',
+      <String, Object?>{
+        'extension': 'ext.flutter.inspector.$name',
+        'value': value,
+      },
+    );
+  }
+
+  /// Registers a service extension method with the given name (full
+  /// name "ext.flutter.inspector.name") which takes an optional parameter named
+  /// "arg" and a required parameter named "objectGroup" used to control the
+  /// lifetimes of object references in the returned JSON (see [disposeGroup]).
+  void _registerServiceExtensionWithArg({
+    required String name,
+    required FutureOr<Object?> callback(String? objectId, String objectGroup),
+  }) {
+    registerServiceExtension(
+      name: name,
+      callback: (Map<String, String> parameters) async {
+        assert(parameters.containsKey('objectGroup'));
+        return <String, Object?>{
+          'result': await callback(parameters['arg'], parameters['objectGroup']!),
+        };
+      },
+    );
+  }
+
+  /// Registers a service extension method with the given name (full
+  /// name "ext.flutter.inspector.name"), that takes arguments
+  /// "arg0", "arg1", "arg2", ..., "argn".
+  void _registerServiceExtensionVarArgs({
+    required String name,
+    required FutureOr<Object?> callback(List<String> args),
+  }) {
+    registerServiceExtension(
+      name: name,
+      callback: (Map<String, String> parameters) async {
+        final List<String> args = <String>[];
+        int index = 0;
+        while (true) {
+          final String name = 'arg$index';
+          if (parameters.containsKey(name)) {
+            args.add(parameters[name]!);
+          } else {
+            break;
+          }
+          index++;
+        }
+        // Verify that the only arguments other than perhaps 'isolateId' are
+        // arguments we have already handled.
+        assert(index == parameters.length || (index == parameters.length - 1 && parameters.containsKey('isolateId')));
+        return <String, Object?>{'result': await callback(args)};
+      },
+    );
+  }
+
+  /// Cause the entire tree to be rebuilt. This is used by development tools
+  /// when the application code has changed and is being hot-reloaded, to cause
+  /// the widget tree to pick up any changed implementations.
+  ///
+  /// This is expensive and should not be called except during development.
+  @protected
+  Future<void> forceRebuild() {
+    final WidgetsBinding binding = WidgetsBinding.instance!;
+    if (binding.renderViewElement != null) {
+      binding.buildOwner!.reassemble(binding.renderViewElement!);
+      return binding.endOfFrame;
+    }
+    return Future<void>.value();
+  }
+
+  static const String _consoleObjectGroup = 'console-group';
+
+  int _errorsSinceReload = 0;
+
+  void _reportError(FlutterErrorDetails details) {
+    final Map<String, Object?> errorJson = _nodeToJson(
+      details.toDiagnosticsNode(),
+      InspectorSerializationDelegate(
+        groupName: _consoleObjectGroup,
+        subtreeDepth: 5,
+        includeProperties: true,
+        expandPropertyValues: true,
+        maxDescendentsTruncatableNode: 5,
+        service: this,
+      ),
+    )!;
+
+    errorJson['errorsSinceReload'] = _errorsSinceReload;
+    if (_errorsSinceReload == 0) {
+      errorJson['renderedErrorText'] = TextTreeRenderer(
+        wrapWidth: FlutterError.wrapWidth,
+        wrapWidthProperties: FlutterError.wrapWidth,
+        maxDescendentsTruncatableNode: 5,
+      ).render(details.toDiagnosticsNode(style: DiagnosticsTreeStyle.error)).trimRight();
+    } else {
+      errorJson['renderedErrorText'] = 'Another exception was thrown: ${details.summary}';
+    }
+
+    _errorsSinceReload += 1;
+    postEvent('Flutter.Error', errorJson);
+  }
+
+  /// Resets the count of errors since the last hot reload.
+  ///
+  /// This data is sent to clients as part of the 'Flutter.Error' service
+  /// protocol event. Clients may choose to display errors received after the
+  /// first error differently.
+  void _resetErrorCount() {
+    _errorsSinceReload = 0;
+  }
+
+  /// Whether structured errors are enabled.
+  ///
+  /// Structured errors provide semantic information that can be used by IDEs
+  /// to enhance the display of errors with rich formatting.
+  bool isStructuredErrorsEnabled() {
+    return const bool.fromEnvironment('flutter.inspector.structuredErrors');
+  }
+
+  /// Called to register service extensions.
+  ///
+  /// See also:
+  ///
+  ///  * <https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#rpcs-requests-and-responses>
+  ///  * [BindingBase.initServiceExtensions], which explains when service
+  ///    extensions can be used.
+  void initServiceExtensions(_RegisterServiceExtensionCallback registerServiceExtensionCallback) {
+    _structuredExceptionHandler = _reportError;
+    if (isStructuredErrorsEnabled()) {
+      FlutterError.onError = _structuredExceptionHandler;
+    }
+    _registerServiceExtensionCallback = registerServiceExtensionCallback;
+    assert(!_debugServiceExtensionsRegistered);
+    assert(() {
+      _debugServiceExtensionsRegistered = true;
+      return true;
+    }());
+
+    SchedulerBinding.instance!.addPersistentFrameCallback(_onFrameStart);
+
+    final FlutterExceptionHandler defaultExceptionHandler = FlutterError.presentError;
+
+    _registerBoolServiceExtension(
+      name: 'structuredErrors',
+      getter: () async => FlutterError.presentError == _structuredExceptionHandler,
+      setter: (bool value) {
+        FlutterError.presentError = value ? _structuredExceptionHandler! : defaultExceptionHandler;
+        return Future<void>.value();
+      },
+    );
+
+    _registerBoolServiceExtension(
+      name: 'show',
+      getter: () async => WidgetsApp.debugShowWidgetInspectorOverride,
+      setter: (bool value) {
+        if (WidgetsApp.debugShowWidgetInspectorOverride == value) {
+          return Future<void>.value();
+        }
+        WidgetsApp.debugShowWidgetInspectorOverride = value;
+        return forceRebuild();
+      },
+    );
+
+    if (isWidgetCreationTracked()) {
+      // Service extensions that are only supported if widget creation locations
+      // are tracked.
+      _registerBoolServiceExtension(
+        name: 'trackRebuildDirtyWidgets',
+        getter: () async => _trackRebuildDirtyWidgets,
+        setter: (bool value) async {
+          if (value == _trackRebuildDirtyWidgets) {
+            return;
+          }
+          _rebuildStats.resetCounts();
+          _trackRebuildDirtyWidgets = value;
+          if (value) {
+            assert(debugOnRebuildDirtyWidget == null);
+            debugOnRebuildDirtyWidget = _onRebuildWidget;
+            // Trigger a rebuild so there are baseline stats for rebuilds
+            // performed by the app.
+            await forceRebuild();
+            return;
+          } else {
+            debugOnRebuildDirtyWidget = null;
+            return;
+          }
+        },
+      );
+
+      _registerBoolServiceExtension(
+        name: 'trackRepaintWidgets',
+        getter: () async => _trackRepaintWidgets,
+        setter: (bool value) async {
+          if (value == _trackRepaintWidgets) {
+            return;
+          }
+          _repaintStats.resetCounts();
+          _trackRepaintWidgets = value;
+          if (value) {
+            assert(debugOnProfilePaint == null);
+            debugOnProfilePaint = _onPaint;
+            // Trigger an immediate paint so the user has some baseline painting
+            // stats to view.
+            void markTreeNeedsPaint(RenderObject renderObject) {
+              renderObject.markNeedsPaint();
+              renderObject.visitChildren(markTreeNeedsPaint);
+            }
+            final RenderObject root = RendererBinding.instance!.renderView;
+            markTreeNeedsPaint(root);
+          } else {
+            debugOnProfilePaint = null;
+          }
+        },
+      );
+    }
+
+    _registerSignalServiceExtension(
+      name: 'disposeAllGroups',
+      callback: () async {
+        disposeAllGroups();
+        return null;
+      },
+    );
+    _registerObjectGroupServiceExtension(
+      name: 'disposeGroup',
+      callback: (String name) async {
+        disposeGroup(name);
+        return null;
+      },
+    );
+    _registerSignalServiceExtension(
+      name: 'isWidgetTreeReady',
+      callback: isWidgetTreeReady,
+    );
+    _registerServiceExtensionWithArg(
+      name: 'disposeId',
+      callback: (String? objectId, String objectGroup) async {
+        disposeId(objectId, objectGroup);
+        return null;
+      },
+    );
+    _registerServiceExtensionVarArgs(
+      name: 'setPubRootDirectories',
+      callback: (List<String> args) async {
+        setPubRootDirectories(args);
+        return null;
+      },
+    );
+    _registerServiceExtensionWithArg(
+      name: 'setSelectionById',
+      callback: setSelectionById,
+    );
+    _registerServiceExtensionWithArg(
+      name: 'getParentChain',
+      callback: _getParentChain,
+    );
+    _registerServiceExtensionWithArg(
+      name: 'getProperties',
+      callback: _getProperties,
+    );
+    _registerServiceExtensionWithArg(
+      name: 'getChildren',
+      callback: _getChildren,
+    );
+
+    _registerServiceExtensionWithArg(
+      name: 'getChildrenSummaryTree',
+      callback: _getChildrenSummaryTree,
+    );
+
+    _registerServiceExtensionWithArg(
+      name: 'getChildrenDetailsSubtree',
+      callback: _getChildrenDetailsSubtree,
+    );
+
+    _registerObjectGroupServiceExtension(
+      name: 'getRootWidget',
+      callback: _getRootWidget,
+    );
+    _registerObjectGroupServiceExtension(
+      name: 'getRootRenderObject',
+      callback: _getRootRenderObject,
+    );
+    _registerObjectGroupServiceExtension(
+      name: 'getRootWidgetSummaryTree',
+      callback: _getRootWidgetSummaryTree,
+    );
+    registerServiceExtension(
+      name: 'getDetailsSubtree',
+      callback: (Map<String, String> parameters) async {
+        assert(parameters.containsKey('objectGroup'));
+        final String? subtreeDepth = parameters['subtreeDepth'];
+        return <String, Object?>{
+          'result': _getDetailsSubtree(
+            parameters['arg'],
+            parameters['objectGroup'],
+            subtreeDepth != null ? int.parse(subtreeDepth) : 2,
+          ),
+        };
+      },
+    );
+    _registerServiceExtensionWithArg(
+      name: 'getSelectedRenderObject',
+      callback: _getSelectedRenderObject,
+    );
+    _registerServiceExtensionWithArg(
+      name: 'getSelectedWidget',
+      callback: _getSelectedWidget,
+    );
+    _registerServiceExtensionWithArg(
+      name: 'getSelectedSummaryWidget',
+      callback: _getSelectedSummaryWidget,
+    );
+
+    _registerSignalServiceExtension(
+      name: 'isWidgetCreationTracked',
+      callback: isWidgetCreationTracked,
+    );
+    registerServiceExtension(
+      name: 'screenshot',
+      callback: (Map<String, String> parameters) async {
+        assert(parameters.containsKey('id'));
+        assert(parameters.containsKey('width'));
+        assert(parameters.containsKey('height'));
+
+        final ui.Image? image = await screenshot(
+          toObject(parameters['id']),
+          width: double.parse(parameters['width']!),
+          height: double.parse(parameters['height']!),
+          margin: parameters.containsKey('margin') ?
+              double.parse(parameters['margin']!) : 0.0,
+          maxPixelRatio: parameters.containsKey('maxPixelRatio') ?
+              double.parse(parameters['maxPixelRatio']!) : 1.0,
+          debugPaint: parameters['debugPaint'] == 'true',
+        );
+        if (image == null) {
+          return <String, Object?>{'result': null};
+        }
+        final ByteData? byteData = await image.toByteData(format:ui.ImageByteFormat.png);
+
+        return <String, Object>{
+          'result': base64.encoder.convert(Uint8List.view(byteData!.buffer)),
+        };
+      },
+    );
+  }
+
+  void _clearStats() {
+    _rebuildStats.resetCounts();
+    _repaintStats.resetCounts();
+  }
+
+  /// Clear all InspectorService object references.
+  ///
+  /// Use this method only for testing to ensure that object references from one
+  /// test case do not impact other test cases.
+  @protected
+  void disposeAllGroups() {
+    _groups.clear();
+    _idToReferenceData.clear();
+    _objectToId.clear();
+    _nextId = 0;
+  }
+
+  /// Free all references to objects in a group.
+  ///
+  /// Objects and their associated ids in the group may be kept alive by
+  /// references from a different group.
+  @protected
+  void disposeGroup(String name) {
+    final Set<_InspectorReferenceData>? references = _groups.remove(name);
+    if (references == null)
+      return;
+    references.forEach(_decrementReferenceCount);
+  }
+
+  void _decrementReferenceCount(_InspectorReferenceData reference) {
+    reference.count -= 1;
+    assert(reference.count >= 0);
+    if (reference.count == 0) {
+      final String? id = _objectToId.remove(reference.object);
+      assert(id != null);
+      _idToReferenceData.remove(id);
+    }
+  }
+
+  /// Returns a unique id for [object] that will remain live at least until
+  /// [disposeGroup] is called on [groupName].
+  @protected
+  String? toId(Object? object, String groupName) {
+    if (object == null)
+      return null;
+
+    final Set<_InspectorReferenceData> group = _groups.putIfAbsent(groupName, () => Set<_InspectorReferenceData>.identity());
+    String? id = _objectToId[object];
+    _InspectorReferenceData referenceData;
+    if (id == null) {
+      id = 'inspector-$_nextId';
+      _nextId += 1;
+      _objectToId[object] = id;
+      referenceData = _InspectorReferenceData(object);
+      _idToReferenceData[id] = referenceData;
+      group.add(referenceData);
+    } else {
+      referenceData = _idToReferenceData[id]!;
+      if (group.add(referenceData))
+        referenceData.count += 1;
+    }
+    return id;
+  }
+
+  /// Returns whether the application has rendered its first frame and it is
+  /// appropriate to display the Widget tree in the inspector.
+  @protected
+  bool isWidgetTreeReady([ String? groupName ]) {
+    return WidgetsBinding.instance != null &&
+           WidgetsBinding.instance!.debugDidSendFirstFrameEvent;
+  }
+
+  /// Returns the Dart object associated with a reference id.
+  ///
+  /// The `groupName` parameter is not required by is added to regularize the
+  /// API surface of the methods in this class called from the Flutter IntelliJ
+  /// Plugin.
+  @protected
+  Object? toObject(String? id, [ String? groupName ]) {
+    if (id == null)
+      return null;
+
+    final _InspectorReferenceData? data = _idToReferenceData[id];
+    if (data == null) {
+      throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Id does not exist.')]);
+    }
+    return data.object;
+  }
+
+  /// Returns the object to introspect to determine the source location of an
+  /// object's class.
+  ///
+  /// The Dart object for the id is returned for all cases but [Element] objects
+  /// where the [Widget] configuring the [Element] is returned instead as the
+  /// class of the [Widget] is more relevant than the class of the [Element].
+  ///
+  /// The `groupName` parameter is not required by is added to regularize the
+  /// API surface of methods called from the Flutter IntelliJ Plugin.
+  @protected
+  Object? toObjectForSourceLocation(String id, [ String? groupName ]) {
+    final Object? object = toObject(id);
+    if (object is Element) {
+      return object.widget;
+    }
+    return object;
+  }
+
+  /// Remove the object with the specified `id` from the specified object
+  /// group.
+  ///
+  /// If the object exists in other groups it will remain alive and the object
+  /// id will remain valid.
+  @protected
+  void disposeId(String? id, String groupName) {
+    if (id == null)
+      return;
+
+    final _InspectorReferenceData? referenceData = _idToReferenceData[id];
+    if (referenceData == null)
+      throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Id does not exist')]);
+    if (_groups[groupName]?.remove(referenceData) != true)
+      throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Id is not in group')]);
+    _decrementReferenceCount(referenceData);
+  }
+
+  /// Set the list of directories that should be considered part of the local
+  /// project.
+  ///
+  /// The local project directories are used to distinguish widgets created by
+  /// the local project over widgets created from inside the framework.
+  @protected
+  void setPubRootDirectories(List<String> pubRootDirectories) {
+    _pubRootDirectories = pubRootDirectories
+      .map<String>((String directory) => Uri.parse(directory).path)
+      .toList();
+  }
+
+  /// Set the [WidgetInspector] selection to the object matching the specified
+  /// id if the object is valid object to set as the inspector selection.
+  ///
+  /// Returns true if the selection was changed.
+  ///
+  /// The `groupName` parameter is not required by is added to regularize the
+  /// API surface of methods called from the Flutter IntelliJ Plugin.
+  @protected
+  bool setSelectionById(String? id, [ String? groupName ]) {
+    return setSelection(toObject(id), groupName);
+  }
+
+  /// Set the [WidgetInspector] selection to the specified `object` if it is
+  /// a valid object to set as the inspector selection.
+  ///
+  /// Returns true if the selection was changed.
+  ///
+  /// The `groupName` parameter is not needed but is specified to regularize the
+  /// API surface of methods called from the Flutter IntelliJ Plugin.
+  @protected
+  bool setSelection(Object? object, [ String? groupName ]) {
+    if (object is Element || object is RenderObject) {
+      if (object is Element) {
+        if (object == selection.currentElement) {
+          return false;
+        }
+        selection.currentElement = object;
+        developer.inspect(selection.currentElement);
+      } else {
+        if (object == selection.current) {
+          return false;
+        }
+        selection.current = object! as RenderObject;
+        developer.inspect(selection.current);
+      }
+      if (selectionChangedCallback != null) {
+        if (SchedulerBinding.instance!.schedulerPhase == SchedulerPhase.idle) {
+          selectionChangedCallback!();
+        } else {
+          // It isn't safe to trigger the selection change callback if we are in
+          // the middle of rendering the frame.
+          SchedulerBinding.instance!.scheduleTask(
+            selectionChangedCallback!,
+            Priority.touch,
+          );
+        }
+      }
+      return true;
+    }
+    return false;
+  }
+
+  /// Returns JSON representing the chain of [DiagnosticsNode] instances from
+  /// root of thee tree to the [Element] or [RenderObject] matching `id`.
+  ///
+  /// The JSON contains all information required to display a tree view with
+  /// all nodes other than nodes along the path collapsed.
+  @protected
+  String getParentChain(String id, String groupName) {
+    return _safeJsonEncode(_getParentChain(id, groupName));
+  }
+
+  List<Object?> _getParentChain(String? id, String groupName) {
+    final Object? value = toObject(id);
+    List<_DiagnosticsPathNode> path;
+    if (value is RenderObject)
+      path = _getRenderObjectParentChain(value, groupName)!;
+    else if (value is Element)
+      path = _getElementParentChain(value, groupName);
+    else
+      throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Cannot get parent chain for node of type ${value.runtimeType}')]);
+
+    return path.map<Object?>((_DiagnosticsPathNode node) => _pathNodeToJson(
+      node,
+      InspectorSerializationDelegate(groupName: groupName, service: this),
+    )).toList();
+  }
+
+  Map<String, Object?>? _pathNodeToJson(_DiagnosticsPathNode? pathNode, InspectorSerializationDelegate delegate) {
+    if (pathNode == null)
+      return null;
+    return <String, Object?>{
+      'node': _nodeToJson(pathNode.node, delegate),
+      'children': _nodesToJson(pathNode.children, delegate, parent: pathNode.node),
+      'childIndex': pathNode.childIndex,
+    };
+  }
+
+  List<Element> _getRawElementParentChain(Element element, { required int? numLocalParents }) {
+    List<Element> elements = element.debugGetDiagnosticChain();
+    if (numLocalParents != null) {
+      for (int i = 0; i < elements.length; i += 1) {
+        if (_isValueCreatedByLocalProject(elements[i])) {
+          numLocalParents = numLocalParents! - 1;
+          if (numLocalParents <= 0) {
+            elements = elements.take(i + 1).toList();
+            break;
+          }
+        }
+      }
+    }
+    return elements.reversed.toList();
+  }
+
+  List<_DiagnosticsPathNode> _getElementParentChain(Element element, String groupName, { int? numLocalParents }) {
+    return _followDiagnosticableChain(
+      _getRawElementParentChain(element, numLocalParents: numLocalParents),
+    ) ?? const <_DiagnosticsPathNode>[];
+  }
+
+  List<_DiagnosticsPathNode>? _getRenderObjectParentChain(RenderObject? renderObject, String groupName) {
+    final List<RenderObject> chain = <RenderObject>[];
+    while (renderObject != null) {
+      chain.add(renderObject);
+      renderObject = renderObject.parent as RenderObject?;
+    }
+    return _followDiagnosticableChain(chain.reversed.toList());
+  }
+
+  Map<String, Object?>? _nodeToJson(
+    DiagnosticsNode? node,
+    InspectorSerializationDelegate delegate,
+  ) {
+    return node?.toJsonMap(delegate);
+  }
+
+  bool _isValueCreatedByLocalProject(Object? value) {
+    final _Location? creationLocation = _getCreationLocation(value);
+    if (creationLocation == null) {
+      return false;
+    }
+    return _isLocalCreationLocation(creationLocation);
+  }
+
+  bool _isLocalCreationLocation(_Location? location) {
+    if (location == null || location.file == null) {
+      return false;
+    }
+    final String file = Uri.parse(location.file).path;
+
+    // By default check whether the creation location was within package:flutter.
+    if (_pubRootDirectories == null) {
+      // TODO(chunhtai): Make it more robust once
+      // https://github.com/flutter/flutter/issues/32660 is fixed.
+      return !file.contains('packages/flutter/');
+    }
+    for (final String directory in _pubRootDirectories!) {
+      if (file.startsWith(directory)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /// Wrapper around `json.encode` that uses a ring of cached values to prevent
+  /// the Dart garbage collector from collecting objects between when
+  /// the value is returned over the Observatory protocol and when the
+  /// separate observatory protocol command has to be used to retrieve its full
+  /// contents.
+  //
+  // TODO(jacobr): Replace this with a better solution once
+  // https://github.com/dart-lang/sdk/issues/32919 is fixed.
+  String _safeJsonEncode(Object? object) {
+    final String jsonString = json.encode(object);
+    _serializeRing[_serializeRingIndex] = jsonString;
+    _serializeRingIndex = (_serializeRingIndex + 1)  % _serializeRing.length;
+    return jsonString;
+  }
+
+  List<DiagnosticsNode> _truncateNodes(Iterable<DiagnosticsNode> nodes, int maxDescendentsTruncatableNode) {
+    if (nodes.every((DiagnosticsNode node) => node.value is Element) && isWidgetCreationTracked()) {
+      final List<DiagnosticsNode> localNodes = nodes.where((DiagnosticsNode node) =>
+          _isValueCreatedByLocalProject(node.value)).toList();
+      if (localNodes.isNotEmpty) {
+        return localNodes;
+      }
+    }
+    return nodes.take(maxDescendentsTruncatableNode).toList();
+  }
+
+  List<Map<String, Object?>> _nodesToJson(
+    List<DiagnosticsNode> nodes,
+    InspectorSerializationDelegate delegate, {
+    required DiagnosticsNode? parent,
+  }) {
+    return DiagnosticsNode.toJsonList(nodes, parent, delegate);
+  }
+
+  /// Returns a JSON representation of the properties of the [DiagnosticsNode]
+  /// object that `diagnosticsNodeId` references.
+  @protected
+  String getProperties(String diagnosticsNodeId, String groupName) {
+    return _safeJsonEncode(_getProperties(diagnosticsNodeId, groupName));
+  }
+
+  List<Object> _getProperties(String? diagnosticsNodeId, String groupName) {
+    final DiagnosticsNode? node = toObject(diagnosticsNodeId) as DiagnosticsNode?;
+    return _nodesToJson(node == null ? const <DiagnosticsNode>[] : node.getProperties(), InspectorSerializationDelegate(groupName: groupName, service: this), parent: node);
+  }
+
+  /// Returns a JSON representation of the children of the [DiagnosticsNode]
+  /// object that `diagnosticsNodeId` references.
+  String getChildren(String diagnosticsNodeId, String groupName) {
+    return _safeJsonEncode(_getChildren(diagnosticsNodeId, groupName));
+  }
+
+  List<Object> _getChildren(String? diagnosticsNodeId, String groupName) {
+    final DiagnosticsNode? node = toObject(diagnosticsNodeId) as DiagnosticsNode?;
+    final InspectorSerializationDelegate delegate = InspectorSerializationDelegate(groupName: groupName, service: this);
+    return _nodesToJson(node == null ? const <DiagnosticsNode>[] : _getChildrenFiltered(node, delegate), delegate, parent: node);
+  }
+
+  /// Returns a JSON representation of the children of the [DiagnosticsNode]
+  /// object that `diagnosticsNodeId` references only including children that
+  /// were created directly by user code.
+  ///
+  /// {@template flutter.widgets.WidgetInspectorService.getChildrenSummaryTree}
+  /// Requires [Widget] creation locations which are only available for debug
+  /// mode builds when the `--track-widget-creation` flag is enabled on the call
+  /// to the `flutter` tool. This flag is enabled by default in debug builds.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [isWidgetCreationTracked] which indicates whether this method can be
+  ///    used.
+  String getChildrenSummaryTree(String diagnosticsNodeId, String groupName) {
+    return _safeJsonEncode(_getChildrenSummaryTree(diagnosticsNodeId, groupName));
+  }
+
+  List<Object> _getChildrenSummaryTree(String? diagnosticsNodeId, String groupName) {
+    final DiagnosticsNode? node = toObject(diagnosticsNodeId) as DiagnosticsNode?;
+    final InspectorSerializationDelegate delegate = InspectorSerializationDelegate(groupName: groupName, summaryTree: true, service: this);
+    return _nodesToJson(node == null ? const <DiagnosticsNode>[] : _getChildrenFiltered(node, delegate), delegate, parent: node);
+  }
+
+  /// Returns a JSON representation of the children of the [DiagnosticsNode]
+  /// object that `diagnosticsNodeId` references providing information needed
+  /// for the details subtree view.
+  ///
+  /// The details subtree shows properties inline and includes all children
+  /// rather than a filtered set of important children.
+  String getChildrenDetailsSubtree(String diagnosticsNodeId, String groupName) {
+    return _safeJsonEncode(_getChildrenDetailsSubtree(diagnosticsNodeId, groupName));
+  }
+
+  List<Object> _getChildrenDetailsSubtree(String? diagnosticsNodeId, String groupName) {
+    final DiagnosticsNode? node = toObject(diagnosticsNodeId) as DiagnosticsNode?;
+    // With this value of minDepth we only expand one extra level of important nodes.
+    final InspectorSerializationDelegate delegate = InspectorSerializationDelegate(groupName: groupName, subtreeDepth: 1, includeProperties: true, service: this);
+    return _nodesToJson(node == null ? const <DiagnosticsNode>[] : _getChildrenFiltered(node, delegate), delegate, parent: node);
+  }
+
+  bool _shouldShowInSummaryTree(DiagnosticsNode node) {
+    if (node.level == DiagnosticLevel.error) {
+      return true;
+    }
+    final Object? value = node.value;
+    if (value is! Diagnosticable) {
+      return true;
+    }
+    if (value is! Element || !isWidgetCreationTracked()) {
+      // Creation locations are not available so include all nodes in the
+      // summary tree.
+      return true;
+    }
+    return _isValueCreatedByLocalProject(value);
+  }
+
+  List<DiagnosticsNode> _getChildrenFiltered(
+    DiagnosticsNode node,
+    InspectorSerializationDelegate delegate,
+  ) {
+    return _filterChildren(node.getChildren(), delegate);
+  }
+
+  List<DiagnosticsNode> _filterChildren(
+    List<DiagnosticsNode> nodes,
+    InspectorSerializationDelegate delegate,
+  ) {
+    final List<DiagnosticsNode> children = <DiagnosticsNode>[
+      for (final DiagnosticsNode child in nodes)
+        if (!delegate.summaryTree || _shouldShowInSummaryTree(child))
+          child
+        else
+          ..._getChildrenFiltered(child, delegate),
+    ];
+    return children;
+  }
+
+  /// Returns a JSON representation of the [DiagnosticsNode] for the root
+  /// [Element].
+  String getRootWidget(String groupName) {
+    return _safeJsonEncode(_getRootWidget(groupName));
+  }
+
+  Map<String, Object?>? _getRootWidget(String groupName) {
+    return _nodeToJson(WidgetsBinding.instance?.renderViewElement?.toDiagnosticsNode(), InspectorSerializationDelegate(groupName: groupName, service: this));
+  }
+
+  /// Returns a JSON representation of the [DiagnosticsNode] for the root
+  /// [Element] showing only nodes that should be included in a summary tree.
+  String getRootWidgetSummaryTree(String groupName) {
+    return _safeJsonEncode(_getRootWidgetSummaryTree(groupName));
+  }
+
+  Map<String, Object?>? _getRootWidgetSummaryTree(String groupName) {
+    return _nodeToJson(
+      WidgetsBinding.instance?.renderViewElement?.toDiagnosticsNode(),
+      InspectorSerializationDelegate(groupName: groupName, subtreeDepth: 1000000, summaryTree: true, service: this),
+    );
+  }
+
+  /// Returns a JSON representation of the [DiagnosticsNode] for the root
+  /// [RenderObject].
+  @protected
+  String getRootRenderObject(String groupName) {
+    return _safeJsonEncode(_getRootRenderObject(groupName));
+  }
+
+  Map<String, Object?>? _getRootRenderObject(String groupName) {
+    return _nodeToJson(RendererBinding.instance?.renderView.toDiagnosticsNode(), InspectorSerializationDelegate(groupName: groupName, service: this));
+  }
+
+  /// Returns a JSON representation of the subtree rooted at the
+  /// [DiagnosticsNode] object that `diagnosticsNodeId` references providing
+  /// information needed for the details subtree view.
+  ///
+  /// The number of levels of the subtree that should be returned is specified
+  /// by the [subtreeDepth] parameter. This value defaults to 2 for backwards
+  /// compatibility.
+  ///
+  /// See also:
+  ///
+  ///  * [getChildrenDetailsSubtree], a method to get children of a node
+  ///    in the details subtree.
+  String getDetailsSubtree(
+    String id,
+    String groupName, {
+    int subtreeDepth = 2,
+  }) {
+    return _safeJsonEncode(_getDetailsSubtree( id, groupName, subtreeDepth));
+  }
+
+  Map<String, Object?>? _getDetailsSubtree(
+    String? id,
+    String? groupName,
+    int subtreeDepth,
+  ) {
+    final DiagnosticsNode? root = toObject(id) as DiagnosticsNode?;
+    if (root == null) {
+      return null;
+    }
+    return _nodeToJson(
+      root,
+      InspectorSerializationDelegate(
+        groupName: groupName,
+        summaryTree: false,
+        subtreeDepth: subtreeDepth,
+        includeProperties: true,
+        service: this,
+      ),
+    );
+  }
+
+  /// Returns a [DiagnosticsNode] representing the currently selected
+  /// [RenderObject].
+  ///
+  /// If the currently selected [RenderObject] is identical to the
+  /// [RenderObject] referenced by `previousSelectionId` then the previous
+  /// [DiagnosticsNode] is reused.
+  @protected
+  String getSelectedRenderObject(String previousSelectionId, String groupName) {
+    return _safeJsonEncode(_getSelectedRenderObject(previousSelectionId, groupName));
+  }
+
+  Map<String, Object?>? _getSelectedRenderObject(String? previousSelectionId, String groupName) {
+    final DiagnosticsNode? previousSelection = toObject(previousSelectionId) as DiagnosticsNode?;
+    final RenderObject? current = selection.current;
+    return _nodeToJson(current == previousSelection?.value ? previousSelection : current?.toDiagnosticsNode(), InspectorSerializationDelegate(groupName: groupName, service: this));
+  }
+
+  /// Returns a [DiagnosticsNode] representing the currently selected [Element].
+  ///
+  /// If the currently selected [Element] is identical to the [Element]
+  /// referenced by `previousSelectionId` then the previous [DiagnosticsNode] is
+  /// reused.
+  @protected
+  String getSelectedWidget(String? previousSelectionId, String groupName) {
+    return _safeJsonEncode(_getSelectedWidget(previousSelectionId, groupName));
+  }
+
+  /// Captures an image of the current state of an [object] that is a
+  /// [RenderObject] or [Element].
+  ///
+  /// The returned [ui.Image] has uncompressed raw RGBA bytes and will be scaled
+  /// to be at most [width] pixels wide and [height] pixels tall. The returned
+  /// image will never have a scale between logical pixels and the
+  /// size of the output image larger than maxPixelRatio.
+  /// [margin] indicates the number of pixels relative to the un-scaled size of
+  /// the [object] to include as a margin to include around the bounds of the
+  /// [object] in the screenshot. Including a margin can be useful to capture
+  /// areas that are slightly outside of the normal bounds of an object such as
+  /// some debug paint information.
+  @protected
+  Future<ui.Image?> screenshot(
+    Object? object, {
+    required double width,
+    required double height,
+    double margin = 0.0,
+    double maxPixelRatio = 1.0,
+    bool debugPaint = false,
+  }) async {
+    if (object is! Element && object is! RenderObject) {
+      return null;
+    }
+    final RenderObject? renderObject = object is Element ? object.renderObject : (object as RenderObject?);
+    if (renderObject == null || !renderObject.attached) {
+      return null;
+    }
+
+    if (renderObject.debugNeedsLayout) {
+      final PipelineOwner owner = renderObject.owner!;
+      assert(owner != null);
+      assert(!owner.debugDoingLayout);
+      owner
+        ..flushLayout()
+        ..flushCompositingBits()
+        ..flushPaint();
+
+      // If we still need layout, then that means that renderObject was skipped
+      // in the layout phase and therefore can't be painted. It is clearer to
+      // return null indicating that a screenshot is unavailable than to return
+      // an empty image.
+      if (renderObject.debugNeedsLayout) {
+        return null;
+      }
+    }
+
+    Rect renderBounds = _calculateSubtreeBounds(renderObject);
+    if (margin != 0.0) {
+      renderBounds = renderBounds.inflate(margin);
+    }
+    if (renderBounds.isEmpty) {
+      return null;
+    }
+
+    final double pixelRatio = math.min(
+      maxPixelRatio,
+      math.min(
+        width / renderBounds.width,
+        height / renderBounds.height,
+      ),
+    );
+
+    return _ScreenshotPaintingContext.toImage(
+      renderObject,
+      renderBounds,
+      pixelRatio: pixelRatio,
+      debugPaint: debugPaint,
+    );
+  }
+
+  Map<String, Object?>? _getSelectedWidget(String? previousSelectionId, String groupName) {
+    final DiagnosticsNode? previousSelection = toObject(previousSelectionId) as DiagnosticsNode?;
+    final Element? current = selection.currentElement;
+    return _nodeToJson(current == previousSelection?.value ? previousSelection : current?.toDiagnosticsNode(), InspectorSerializationDelegate(groupName: groupName, service: this));
+  }
+
+  /// Returns a [DiagnosticsNode] representing the currently selected [Element]
+  /// if the selected [Element] should be shown in the summary tree otherwise
+  /// returns the first ancestor of the selected [Element] shown in the summary
+  /// tree.
+  ///
+  /// If the currently selected [Element] is identical to the [Element]
+  /// referenced by `previousSelectionId` then the previous [DiagnosticsNode] is
+  /// reused.
+  String getSelectedSummaryWidget(String previousSelectionId, String groupName) {
+    return _safeJsonEncode(_getSelectedSummaryWidget(previousSelectionId, groupName));
+  }
+
+  Map<String, Object?>? _getSelectedSummaryWidget(String? previousSelectionId, String groupName) {
+    if (!isWidgetCreationTracked()) {
+      return _getSelectedWidget(previousSelectionId, groupName);
+    }
+    final DiagnosticsNode? previousSelection = toObject(previousSelectionId) as DiagnosticsNode?;
+    Element? current = selection.currentElement;
+    if (current != null && !_isValueCreatedByLocalProject(current)) {
+      Element? firstLocal;
+      for (final Element candidate in current.debugGetDiagnosticChain()) {
+        if (_isValueCreatedByLocalProject(candidate)) {
+          firstLocal = candidate;
+          break;
+        }
+      }
+      current = firstLocal;
+    }
+    return _nodeToJson(current == previousSelection?.value ? previousSelection : current?.toDiagnosticsNode(), InspectorSerializationDelegate(groupName: groupName, service: this));
+  }
+
+  /// Returns whether [Widget] creation locations are available.
+  ///
+  /// {@macro flutter.widgets.WidgetInspectorService.getChildrenSummaryTree}
+  bool isWidgetCreationTracked() {
+    _widgetCreationTracked ??= _WidgetForTypeTests() is _HasCreationLocation;
+    return _widgetCreationTracked!;
+  }
+
+  bool? _widgetCreationTracked;
+
+  late Duration _frameStart;
+
+  void _onFrameStart(Duration timeStamp) {
+    _frameStart = timeStamp;
+    SchedulerBinding.instance!.addPostFrameCallback(_onFrameEnd);
+  }
+
+  void _onFrameEnd(Duration timeStamp) {
+    if (_trackRebuildDirtyWidgets) {
+      _postStatsEvent('Flutter.RebuiltWidgets', _rebuildStats);
+    }
+    if (_trackRepaintWidgets) {
+      _postStatsEvent('Flutter.RepaintWidgets', _repaintStats);
+    }
+  }
+
+  void _postStatsEvent(String eventName, _ElementLocationStatsTracker stats) {
+    postEvent(eventName, stats.exportToJson(_frameStart));
+  }
+
+  /// All events dispatched by a [WidgetInspectorService] use this method
+  /// instead of calling [developer.postEvent] directly so that tests for
+  /// [WidgetInspectorService] can track which events were dispatched by
+  /// overriding this method.
+  @protected
+  void postEvent(String eventKind, Map<Object, Object?> eventData) {
+    developer.postEvent(eventKind, eventData);
+  }
+
+  final _ElementLocationStatsTracker _rebuildStats = _ElementLocationStatsTracker();
+  final _ElementLocationStatsTracker _repaintStats = _ElementLocationStatsTracker();
+
+  void _onRebuildWidget(Element element, bool builtOnce) {
+    _rebuildStats.add(element);
+  }
+
+  void _onPaint(RenderObject renderObject) {
+    try {
+      final Element? element = (renderObject.debugCreator as DebugCreator?)?.element;
+      if (element is! RenderObjectElement) {
+        // This branch should not hit as long as all RenderObjects were created
+        // by Widgets. It is possible there might be some render objects
+        // created directly without using the Widget layer so we add this check
+        // to improve robustness.
+        return;
+      }
+      _repaintStats.add(element);
+
+      // Give all ancestor elements credit for repainting as long as they do
+      // not have their own associated RenderObject.
+      element.visitAncestorElements((Element ancestor) {
+        if (ancestor is RenderObjectElement) {
+          // This ancestor has its own RenderObject so we can precisely track
+          // when it repaints.
+          return false;
+        }
+        _repaintStats.add(ancestor);
+        return true;
+      });
+    }
+    catch (exception, stack) {
+      FlutterError.reportError(
+        FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+        ),
+      );
+    }
+  }
+
+  /// This method is called by [WidgetsBinding.performReassemble] to flush caches
+  /// of obsolete values after a hot reload.
+  ///
+  /// Do not call this method directly. Instead, use
+  /// [BindingBase.reassembleApplication].
+  void performReassemble() {
+    _clearStats();
+    _resetErrorCount();
+  }
+}
+
+/// Accumulator for a count associated with a specific source location.
+///
+/// The accumulator stores whether the source location is [local] and what its
+/// [id] for efficiency encoding terse JSON payloads describing counts.
+class _LocationCount {
+  _LocationCount({
+    required this.location,
+    required this.id,
+    required this.local,
+  });
+
+  /// Location id.
+  final int id;
+
+  /// Whether the location is local to the current project.
+  final bool local;
+
+  final _Location location;
+
+  int get count => _count;
+  int _count = 0;
+
+  /// Reset the count.
+  void reset() {
+    _count = 0;
+  }
+
+  /// Increment the count.
+  void increment() {
+    _count++;
+  }
+}
+
+/// A stat tracker that aggregates a performance metric for [Element] objects at
+/// the granularity of creation locations in source code.
+///
+/// This class is optimized to minimize the size of the JSON payloads describing
+/// the aggregate statistics, for stable memory usage, and low CPU usage at the
+/// expense of somewhat higher overall memory usage. Stable memory usage is more
+/// important than peak memory usage to avoid the false impression that the
+/// user's app is leaking memory each frame.
+///
+/// The number of unique widget creation locations tends to be at most in the
+/// low thousands for regular flutter apps so the peak memory usage for this
+/// class is not an issue.
+class _ElementLocationStatsTracker {
+  // All known creation location tracked.
+  //
+  // This could also be stored as a `Map<int, _LocationCount>` but this
+  // representation is more efficient as all location ids from 0 to n are
+  // typically present.
+  //
+  // All logic in this class assumes that if `_stats[i]` is not null
+  // `_stats[i].id` equals `i`.
+  final List<_LocationCount?> _stats = <_LocationCount?>[];
+
+  /// Locations with a non-zero count.
+  final List<_LocationCount> active = <_LocationCount>[];
+
+  /// Locations that were added since stats were last exported.
+  ///
+  /// Only locations local to the current project are included as a performance
+  /// optimization.
+  final List<_LocationCount> newLocations = <_LocationCount>[];
+
+  /// Increments the count associated with the creation location of [element] if
+  /// the creation location is local to the current project.
+  void add(Element element) {
+    final Object widget = element.widget;
+    if (widget is! _HasCreationLocation) {
+      return;
+    }
+    final _HasCreationLocation creationLocationSource = widget;
+    final _Location location = creationLocationSource._location;
+    final int id = _toLocationId(location);
+
+    _LocationCount entry;
+    if (id >= _stats.length || _stats[id] == null) {
+      // After the first frame, almost all creation ids will already be in
+      // _stats so this slow path will rarely be hit.
+      while (id >= _stats.length) {
+        _stats.add(null);
+      }
+      entry = _LocationCount(
+        location: location,
+        id: id,
+        local: WidgetInspectorService.instance._isLocalCreationLocation(location),
+      );
+      if (entry.local) {
+        newLocations.add(entry);
+      }
+      _stats[id] = entry;
+    } else {
+      entry = _stats[id]!;
+    }
+
+    // We could in the future add an option to track stats for all widgets but
+    // that would significantly increase the size of the events posted using
+    // [developer.postEvent] and current use cases for this feature focus on
+    // helping users find problems with their widgets not the platform
+    // widgets.
+    if (entry.local) {
+      if (entry.count == 0) {
+        active.add(entry);
+      }
+      entry.increment();
+    }
+  }
+
+  /// Clear all aggregated statistics.
+  void resetCounts() {
+    // We chose to only reset the active counts instead of clearing all data
+    // to reduce the number memory allocations performed after the first frame.
+    // Once an app has warmed up, location stats tracking should not
+    // trigger significant additional memory allocations. Avoiding memory
+    // allocations is important to minimize the impact this class has on cpu
+    // and memory performance of the running app.
+    for (final _LocationCount entry in active) {
+      entry.reset();
+    }
+    active.clear();
+  }
+
+  /// Exports the current counts and then resets the stats to prepare to track
+  /// the next frame of data.
+  Map<String, dynamic> exportToJson(Duration startTime) {
+    final List<int> events = List<int>.filled(active.length * 2, 0);
+    int j = 0;
+    for (final _LocationCount stat in active) {
+      events[j++] = stat.id;
+      events[j++] = stat.count;
+    }
+
+    final Map<String, dynamic> json = <String, dynamic>{
+      'startTime': startTime.inMicroseconds,
+      'events': events,
+    };
+
+    if (newLocations.isNotEmpty) {
+      // Add all newly used location ids to the JSON.
+      final Map<String, List<int>> locationsJson = <String, List<int>>{};
+      for (final _LocationCount entry in newLocations) {
+        final _Location location = entry.location;
+        final List<int> jsonForFile = locationsJson.putIfAbsent(
+          location.file,
+          () => <int>[],
+        );
+        jsonForFile..add(entry.id)..add(location.line)..add(location.column);
+      }
+      json['newLocations'] = locationsJson;
+    }
+    resetCounts();
+    newLocations.clear();
+    return json;
+  }
+}
+
+class _WidgetForTypeTests extends Widget {
+  @override
+  Element createElement() => throw UnimplementedError();
+}
+
+/// A widget that enables inspecting the child widget's structure.
+///
+/// Select a location on your device or emulator and view what widgets and
+/// render object that best matches the location. An outline of the selected
+/// widget and terse summary information is shown on device with detailed
+/// information is shown in the observatory or in IntelliJ when using the
+/// Flutter Plugin.
+///
+/// The inspector has a select mode and a view mode.
+///
+/// In the select mode, tapping the device selects the widget that best matches
+/// the location of the touch and switches to view mode. Dragging a finger on
+/// the device selects the widget under the drag location but does not switch
+/// modes. Touching the very edge of the bounding box of a widget triggers
+/// selecting the widget even if another widget that also overlaps that
+/// location would otherwise have priority.
+///
+/// In the view mode, the previously selected widget is outlined, however,
+/// touching the device has the same effect it would have if the inspector
+/// wasn't present. This allows interacting with the application and viewing how
+/// the selected widget changes position. Clicking on the select icon in the
+/// bottom left corner of the application switches back to select mode.
+class WidgetInspector extends StatefulWidget {
+  /// Creates a widget that enables inspection for the child.
+  ///
+  /// The [child] argument must not be null.
+  const WidgetInspector({
+    Key? key,
+    required this.child,
+    required this.selectButtonBuilder,
+  }) : assert(child != null),
+       super(key: key);
+
+  /// The widget that is being inspected.
+  final Widget child;
+
+  /// A builder that is called to create the select button.
+  ///
+  /// The `onPressed` callback passed as an argument to the builder should be
+  /// hooked up to the returned widget.
+  final InspectorSelectButtonBuilder? selectButtonBuilder;
+
+  @override
+  _WidgetInspectorState createState() => _WidgetInspectorState();
+}
+
+class _WidgetInspectorState extends State<WidgetInspector>
+    with WidgetsBindingObserver {
+
+  _WidgetInspectorState() : selection = WidgetInspectorService.instance.selection;
+
+  Offset? _lastPointerLocation;
+
+  final InspectorSelection selection;
+
+  /// Whether the inspector is in select mode.
+  ///
+  /// In select mode, pointer interactions trigger widget selection instead of
+  /// normal interactions. Otherwise the previously selected widget is
+  /// highlighted but the application can be interacted with normally.
+  bool isSelectMode = true;
+
+  final GlobalKey _ignorePointerKey = GlobalKey();
+
+  /// Distance from the edge of the bounding box for an element to consider
+  /// as selecting the edge of the bounding box.
+  static const double _edgeHitMargin = 2.0;
+
+  InspectorSelectionChangedCallback? _selectionChangedCallback;
+  @override
+  void initState() {
+    super.initState();
+
+    _selectionChangedCallback = () {
+      setState(() {
+        // The [selection] property which the build method depends on has
+        // changed.
+      });
+    };
+    WidgetInspectorService.instance.selectionChangedCallback = _selectionChangedCallback;
+  }
+
+  @override
+  void dispose() {
+    if (WidgetInspectorService.instance.selectionChangedCallback == _selectionChangedCallback) {
+      WidgetInspectorService.instance.selectionChangedCallback = null;
+    }
+    super.dispose();
+  }
+
+  bool _hitTestHelper(
+    List<RenderObject> hits,
+    List<RenderObject> edgeHits,
+    Offset position,
+    RenderObject object,
+    Matrix4 transform,
+  ) {
+    bool hit = false;
+    final Matrix4? inverse = Matrix4.tryInvert(transform);
+    if (inverse == null) {
+      // We cannot invert the transform. That means the object doesn't appear on
+      // screen and cannot be hit.
+      return false;
+    }
+    final Offset localPosition = MatrixUtils.transformPoint(inverse, position);
+
+    final List<DiagnosticsNode> children = object.debugDescribeChildren();
+    for (int i = children.length - 1; i >= 0; i -= 1) {
+      final DiagnosticsNode diagnostics = children[i];
+      assert(diagnostics != null);
+      if (diagnostics.style == DiagnosticsTreeStyle.offstage ||
+          diagnostics.value is! RenderObject)
+        continue;
+      final RenderObject child = diagnostics.value! as RenderObject;
+      final Rect? paintClip = object.describeApproximatePaintClip(child);
+      if (paintClip != null && !paintClip.contains(localPosition))
+        continue;
+
+      final Matrix4 childTransform = transform.clone();
+      object.applyPaintTransform(child, childTransform);
+      if (_hitTestHelper(hits, edgeHits, position, child, childTransform))
+        hit = true;
+    }
+
+    final Rect bounds = object.semanticBounds;
+    if (bounds.contains(localPosition)) {
+      hit = true;
+      // Hits that occur on the edge of the bounding box of an object are
+      // given priority to provide a way to select objects that would
+      // otherwise be hard to select.
+      if (!bounds.deflate(_edgeHitMargin).contains(localPosition))
+        edgeHits.add(object);
+    }
+    if (hit)
+      hits.add(object);
+    return hit;
+  }
+
+  /// Returns the list of render objects located at the given position ordered
+  /// by priority.
+  ///
+  /// All render objects that are not offstage that match the location are
+  /// included in the list of matches. Priority is given to matches that occur
+  /// on the edge of a render object's bounding box and to matches found by
+  /// [RenderBox.hitTest].
+  List<RenderObject> hitTest(Offset position, RenderObject root) {
+    final List<RenderObject> regularHits = <RenderObject>[];
+    final List<RenderObject> edgeHits = <RenderObject>[];
+
+    _hitTestHelper(regularHits, edgeHits, position, root, root.getTransformTo(null));
+    // Order matches by the size of the hit area.
+    double _area(RenderObject object) {
+      final Size size = object.semanticBounds.size;
+      return size.width * size.height;
+    }
+    regularHits.sort((RenderObject a, RenderObject b) => _area(a).compareTo(_area(b)));
+    final Set<RenderObject> hits = <RenderObject>{
+      ...edgeHits,
+      ...regularHits,
+    };
+    return hits.toList();
+  }
+
+  void _inspectAt(Offset position) {
+    if (!isSelectMode)
+      return;
+
+    final RenderIgnorePointer ignorePointer = _ignorePointerKey.currentContext!.findRenderObject()! as RenderIgnorePointer;
+    final RenderObject userRender = ignorePointer.child!;
+    final List<RenderObject> selected = hitTest(position, userRender);
+
+    setState(() {
+      selection.candidates = selected;
+    });
+  }
+
+  void _handlePanDown(DragDownDetails event) {
+    _lastPointerLocation = event.globalPosition;
+    _inspectAt(event.globalPosition);
+  }
+
+  void _handlePanUpdate(DragUpdateDetails event) {
+    _lastPointerLocation = event.globalPosition;
+    _inspectAt(event.globalPosition);
+  }
+
+  void _handlePanEnd(DragEndDetails details) {
+    // If the pan ends on the edge of the window assume that it indicates the
+    // pointer is being dragged off the edge of the display not a regular touch
+    // on the edge of the display. If the pointer is being dragged off the edge
+    // of the display we do not want to select anything. A user can still select
+    // a widget that is only at the exact screen margin by tapping.
+    final Rect bounds = (Offset.zero & (WidgetsBinding.instance!.window.physicalSize / WidgetsBinding.instance!.window.devicePixelRatio)).deflate(_kOffScreenMargin);
+    if (!bounds.contains(_lastPointerLocation!)) {
+      setState(() {
+        selection.clear();
+      });
+    }
+  }
+
+  void _handleTap() {
+    if (!isSelectMode)
+      return;
+    if (_lastPointerLocation != null) {
+      _inspectAt(_lastPointerLocation!);
+
+      // Notify debuggers to open an inspector on the object.
+      developer.inspect(selection.current);
+    }
+    setState(() {
+      // Only exit select mode if there is a button to return to select mode.
+      if (widget.selectButtonBuilder != null)
+        isSelectMode = false;
+    });
+  }
+
+  void _handleEnableSelect() {
+    setState(() {
+      isSelectMode = true;
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // Be careful changing this build method. The _InspectorOverlayLayer
+    // assumes the root RenderObject for the WidgetInspector will be
+    // a RenderStack with a _RenderInspectorOverlay as the last child.
+    return Stack(children: <Widget>[
+      GestureDetector(
+        onTap: _handleTap,
+        onPanDown: _handlePanDown,
+        onPanEnd: _handlePanEnd,
+        onPanUpdate: _handlePanUpdate,
+        behavior: HitTestBehavior.opaque,
+        excludeFromSemantics: true,
+        child: IgnorePointer(
+          ignoring: isSelectMode,
+          key: _ignorePointerKey,
+          ignoringSemantics: false,
+          child: widget.child,
+        ),
+      ),
+      if (!isSelectMode && widget.selectButtonBuilder != null)
+        Positioned(
+          left: _kInspectButtonMargin,
+          bottom: _kInspectButtonMargin,
+          child: widget.selectButtonBuilder!(context, _handleEnableSelect),
+        ),
+      _InspectorOverlay(selection: selection),
+    ]);
+  }
+}
+
+/// Mutable selection state of the inspector.
+class InspectorSelection {
+  /// Render objects that are candidates to be selected.
+  ///
+  /// Tools may wish to iterate through the list of candidates.
+  List<RenderObject> get candidates => _candidates;
+  List<RenderObject> _candidates = <RenderObject>[];
+  set candidates(List<RenderObject> value) {
+    _candidates = value;
+    _index = 0;
+    _computeCurrent();
+  }
+
+  /// Index within the list of candidates that is currently selected.
+  int get index => _index;
+  int _index = 0;
+  set index(int value) {
+    _index = value;
+    _computeCurrent();
+  }
+
+  /// Set the selection to empty.
+  void clear() {
+    _candidates = <RenderObject>[];
+    _index = 0;
+    _computeCurrent();
+  }
+
+  /// Selected render object typically from the [candidates] list.
+  ///
+  /// Setting [candidates] or calling [clear] resets the selection.
+  ///
+  /// Returns null if the selection is invalid.
+  RenderObject? get current => _current;
+  RenderObject? _current;
+  set current(RenderObject? value) {
+    if (_current != value) {
+      _current = value;
+      _currentElement = (value?.debugCreator as DebugCreator?)?.element;
+    }
+  }
+
+  /// Selected [Element] consistent with the [current] selected [RenderObject].
+  ///
+  /// Setting [candidates] or calling [clear] resets the selection.
+  ///
+  /// Returns null if the selection is invalid.
+  Element? get currentElement => _currentElement;
+  Element? _currentElement;
+  set currentElement(Element? element) {
+    if (currentElement != element) {
+      _currentElement = element;
+      _current = element!.findRenderObject();
+    }
+  }
+
+  void _computeCurrent() {
+    if (_index < candidates.length) {
+      _current = candidates[index];
+      _currentElement = (_current?.debugCreator as DebugCreator?)?.element;
+    } else {
+      _current = null;
+      _currentElement = null;
+    }
+  }
+
+  /// Whether the selected render object is attached to the tree or has gone
+  /// out of scope.
+  bool get active => _current != null && _current!.attached;
+}
+
+class _InspectorOverlay extends LeafRenderObjectWidget {
+  const _InspectorOverlay({
+    Key? key,
+    required this.selection,
+  }) : super(key: key);
+
+  final InspectorSelection selection;
+
+  @override
+  _RenderInspectorOverlay createRenderObject(BuildContext context) {
+    return _RenderInspectorOverlay(selection: selection);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderInspectorOverlay renderObject) {
+    renderObject.selection = selection;
+  }
+}
+
+class _RenderInspectorOverlay extends RenderBox {
+  /// The arguments must not be null.
+  _RenderInspectorOverlay({ required InspectorSelection selection })
+    : _selection = selection,
+      assert(selection != null);
+
+  InspectorSelection get selection => _selection;
+  InspectorSelection _selection;
+  set selection(InspectorSelection value) {
+    if (value != _selection) {
+      _selection = value;
+    }
+    markNeedsPaint();
+  }
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  bool get alwaysNeedsCompositing => true;
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return constraints.constrain(const Size(double.infinity, double.infinity));
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    assert(needsCompositing);
+    context.addLayer(_InspectorOverlayLayer(
+      overlayRect: Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height),
+      selection: selection,
+      rootRenderObject: parent is RenderObject ? parent! as RenderObject : null,
+    ));
+  }
+}
+
+@immutable
+class _TransformedRect {
+  _TransformedRect(RenderObject object, RenderObject? ancestor)
+    : rect = object.semanticBounds,
+      transform = object.getTransformTo(ancestor);
+
+  final Rect rect;
+  final Matrix4 transform;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _TransformedRect
+        && other.rect == rect
+        && other.transform == transform;
+  }
+
+  @override
+  int get hashCode => hashValues(rect, transform);
+}
+
+/// State describing how the inspector overlay should be rendered.
+///
+/// The equality operator can be used to determine whether the overlay needs to
+/// be rendered again.
+@immutable
+class _InspectorOverlayRenderState {
+  const _InspectorOverlayRenderState({
+    required this.overlayRect,
+    required this.selected,
+    required this.candidates,
+    required this.tooltip,
+    required this.textDirection,
+  });
+
+  final Rect overlayRect;
+  final _TransformedRect selected;
+  final List<_TransformedRect> candidates;
+  final String tooltip;
+  final TextDirection textDirection;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _InspectorOverlayRenderState
+        && other.overlayRect == overlayRect
+        && other.selected == selected
+        && listEquals<_TransformedRect>(other.candidates, candidates)
+        && other.tooltip == tooltip;
+  }
+
+  @override
+  int get hashCode => hashValues(overlayRect, selected, hashList(candidates), tooltip);
+}
+
+const int _kMaxTooltipLines = 5;
+const Color _kTooltipBackgroundColor = Color.fromARGB(230, 60, 60, 60);
+const Color _kHighlightedRenderObjectFillColor = Color.fromARGB(128, 128, 128, 255);
+const Color _kHighlightedRenderObjectBorderColor = Color.fromARGB(128, 64, 64, 128);
+
+/// A layer that outlines the selected [RenderObject] and candidate render
+/// objects that also match the last pointer location.
+///
+/// This approach is horrific for performance and is only used here because this
+/// is limited to debug mode. Do not duplicate the logic in production code.
+class _InspectorOverlayLayer extends Layer {
+  /// Creates a layer that displays the inspector overlay.
+  _InspectorOverlayLayer({
+    required this.overlayRect,
+    required this.selection,
+    required this.rootRenderObject,
+  }) : assert(overlayRect != null),
+       assert(selection != null) {
+    bool inDebugMode = false;
+    assert(() {
+      inDebugMode = true;
+      return true;
+    }());
+    if (inDebugMode == false) {
+      throw FlutterError.fromParts(<DiagnosticsNode>[
+        ErrorSummary(
+          'The inspector should never be used in production mode due to the '
+          'negative performance impact.'
+        ),
+      ]);
+    }
+  }
+
+  InspectorSelection selection;
+
+  /// The rectangle in this layer's coordinate system that the overlay should
+  /// occupy.
+  ///
+  /// The scene must be explicitly recomposited after this property is changed
+  /// (as described at [Layer]).
+  final Rect overlayRect;
+
+  /// Widget inspector root render object. The selection overlay will be painted
+  /// with transforms relative to this render object.
+  final RenderObject? rootRenderObject;
+
+  _InspectorOverlayRenderState? _lastState;
+
+  /// Picture generated from _lastState.
+  late ui.Picture _picture;
+
+  TextPainter? _textPainter;
+  double? _textPainterMaxWidth;
+
+  @override
+  void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
+    if (!selection.active)
+      return;
+
+    final RenderObject selected = selection.current!;
+
+    if (!_isInInspectorRenderObjectTree(selected))
+      return;
+
+    final List<_TransformedRect> candidates = <_TransformedRect>[];
+    for (final RenderObject candidate in selection.candidates) {
+      if (candidate == selected || !candidate.attached
+          || !_isInInspectorRenderObjectTree(candidate))
+        continue;
+      candidates.add(_TransformedRect(candidate, rootRenderObject));
+    }
+
+    final _InspectorOverlayRenderState state = _InspectorOverlayRenderState(
+      overlayRect: overlayRect,
+      selected: _TransformedRect(selected, rootRenderObject),
+      tooltip: selection.currentElement!.toStringShort(),
+      textDirection: TextDirection.ltr,
+      candidates: candidates,
+    );
+
+    if (state != _lastState) {
+      _lastState = state;
+      _picture = _buildPicture(state);
+    }
+    builder.addPicture(layerOffset, _picture);
+  }
+
+  ui.Picture _buildPicture(_InspectorOverlayRenderState state) {
+    final ui.PictureRecorder recorder = ui.PictureRecorder();
+    final Canvas canvas = Canvas(recorder, state.overlayRect);
+    final Size size = state.overlayRect.size;
+    // The overlay rect could have an offset if the widget inspector does
+    // not take all the screen.
+    canvas.translate(state.overlayRect.left, state.overlayRect.top);
+
+    final Paint fillPaint = Paint()
+      ..style = PaintingStyle.fill
+      ..color = _kHighlightedRenderObjectFillColor;
+
+    final Paint borderPaint = Paint()
+      ..style = PaintingStyle.stroke
+      ..strokeWidth = 1.0
+      ..color = _kHighlightedRenderObjectBorderColor;
+
+    // Highlight the selected renderObject.
+    final Rect selectedPaintRect = state.selected.rect.deflate(0.5);
+    canvas
+      ..save()
+      ..transform(state.selected.transform.storage)
+      ..drawRect(selectedPaintRect, fillPaint)
+      ..drawRect(selectedPaintRect, borderPaint)
+      ..restore();
+
+    // Show all other candidate possibly selected elements. This helps selecting
+    // render objects by selecting the edge of the bounding box shows all
+    // elements the user could toggle the selection between.
+    for (final _TransformedRect transformedRect in state.candidates) {
+      canvas
+        ..save()
+        ..transform(transformedRect.transform.storage)
+        ..drawRect(transformedRect.rect.deflate(0.5), borderPaint)
+        ..restore();
+    }
+
+    final Rect targetRect = MatrixUtils.transformRect(
+        state.selected.transform, state.selected.rect);
+    final Offset target = Offset(targetRect.left, targetRect.center.dy);
+    const double offsetFromWidget = 9.0;
+    final double verticalOffset = (targetRect.height) / 2 + offsetFromWidget;
+
+    _paintDescription(canvas, state.tooltip, state.textDirection, target, verticalOffset, size, targetRect);
+
+    // TODO(jacobr): provide an option to perform a debug paint of just the
+    // selected widget.
+    return recorder.endRecording();
+  }
+
+  void _paintDescription(
+    Canvas canvas,
+    String message,
+    TextDirection textDirection,
+    Offset target,
+    double verticalOffset,
+    Size size,
+    Rect targetRect,
+  ) {
+    canvas.save();
+    final double maxWidth = size.width - 2 * (_kScreenEdgeMargin + _kTooltipPadding);
+    final TextSpan? textSpan = _textPainter?.text as TextSpan?;
+    if (_textPainter == null || textSpan!.text != message || _textPainterMaxWidth != maxWidth) {
+      _textPainterMaxWidth = maxWidth;
+      _textPainter = TextPainter()
+        ..maxLines = _kMaxTooltipLines
+        ..ellipsis = '...'
+        ..text = TextSpan(style: _messageStyle, text: message)
+        ..textDirection = textDirection
+        ..layout(maxWidth: maxWidth);
+    }
+
+    final Size tooltipSize = _textPainter!.size + const Offset(_kTooltipPadding * 2, _kTooltipPadding * 2);
+    final Offset tipOffset = positionDependentBox(
+      size: size,
+      childSize: tooltipSize,
+      target: target,
+      verticalOffset: verticalOffset,
+      preferBelow: false,
+    );
+
+    final Paint tooltipBackground = Paint()
+      ..style = PaintingStyle.fill
+      ..color = _kTooltipBackgroundColor;
+    canvas.drawRect(
+      Rect.fromPoints(
+        tipOffset,
+        tipOffset.translate(tooltipSize.width, tooltipSize.height),
+      ),
+      tooltipBackground,
+    );
+
+    double wedgeY = tipOffset.dy;
+    final bool tooltipBelow = tipOffset.dy > target.dy;
+    if (!tooltipBelow)
+      wedgeY += tooltipSize.height;
+
+    const double wedgeSize = _kTooltipPadding * 2;
+    double wedgeX = math.max(tipOffset.dx, target.dx) + wedgeSize * 2;
+    wedgeX = math.min(wedgeX, tipOffset.dx + tooltipSize.width - wedgeSize * 2);
+    final List<Offset> wedge = <Offset>[
+      Offset(wedgeX - wedgeSize, wedgeY),
+      Offset(wedgeX + wedgeSize, wedgeY),
+      Offset(wedgeX, wedgeY + (tooltipBelow ? -wedgeSize : wedgeSize)),
+    ];
+    canvas.drawPath(Path()..addPolygon(wedge, true,), tooltipBackground);
+    _textPainter!.paint(canvas, tipOffset + const Offset(_kTooltipPadding, _kTooltipPadding));
+    canvas.restore();
+  }
+
+  @override
+  @protected
+  bool findAnnotations<S extends Object>(
+    AnnotationResult<S> result,
+    Offset localPosition, {
+    bool? onlyFirst,
+  }) {
+    return false;
+  }
+
+  /// Return whether or not a render object belongs to this inspector widget
+  /// tree.
+  /// The inspector selection is static, so if there are multiple inspector
+  /// overlays in the same app (i.e. an storyboard), a selected or candidate
+  /// render object may not belong to this tree.
+  bool _isInInspectorRenderObjectTree(RenderObject child) {
+    RenderObject? current = child.parent as RenderObject?;
+    while (current != null) {
+      // We found the widget inspector render object.
+      if (current is RenderStack
+          && current.lastChild is _RenderInspectorOverlay) {
+        return rootRenderObject == current;
+      }
+      current = current.parent as RenderObject?;
+    }
+    return false;
+  }
+}
+
+const double _kScreenEdgeMargin = 10.0;
+const double _kTooltipPadding = 5.0;
+const double _kInspectButtonMargin = 10.0;
+
+/// Interpret pointer up events within with this margin as indicating the
+/// pointer is moving off the device.
+const double _kOffScreenMargin = 1.0;
+
+const TextStyle _messageStyle = TextStyle(
+  color: Color(0xFFFFFFFF),
+  fontSize: 10.0,
+  height: 1.2,
+);
+
+/// Interface for classes that track the source code location the their
+/// constructor was called from.
+///
+/// {@macro flutter.widgets.WidgetInspectorService.getChildrenSummaryTree}
+// ignore: unused_element
+abstract class _HasCreationLocation {
+  _Location get _location;
+}
+
+/// A tuple with file, line, and column number, for displaying human-readable
+/// file locations.
+class _Location {
+  const _Location({
+    required this.file,
+    required this.line,
+    required this.column,
+    required this.name,
+    required this.parameterLocations,
+  });
+
+  /// File path of the location.
+  final String file;
+
+  /// 1-based line number.
+  final int line;
+  /// 1-based column number.
+  final int column;
+
+  /// Optional name of the parameter or function at this location.
+  final String? name;
+
+  /// Optional locations of the parameters of the member at this location.
+  final List<_Location>? parameterLocations;
+
+  Map<String, Object?> toJsonMap() {
+    final Map<String, Object?> json = <String, Object?>{
+      'file': file,
+      'line': line,
+      'column': column,
+    };
+    if (name != null) {
+      json['name'] = name;
+    }
+    if (parameterLocations != null) {
+      json['parameterLocations'] = parameterLocations!.map<Map<String, Object?>>(
+          (_Location location) => location.toJsonMap()).toList();
+    }
+    return json;
+  }
+
+  @override
+  String toString() {
+    final List<String> parts = <String>[];
+    if (name != null) {
+      parts.add(name!);
+    }
+    parts.add(file);
+    parts..add('$line')..add('$column');
+    return parts.join(':');
+  }
+}
+
+bool _isDebugCreator(DiagnosticsNode node) => node is DiagnosticsDebugCreator;
+
+/// Transformer to parse and gather information about [DiagnosticsDebugCreator].
+///
+/// This function will be registered to [FlutterErrorDetails.propertiesTransformers]
+/// in [WidgetsBinding.initInstances].
+Iterable<DiagnosticsNode> transformDebugCreator(Iterable<DiagnosticsNode> properties) sync* {
+  final List<DiagnosticsNode> pending = <DiagnosticsNode>[];
+  bool foundStackTrace = false;
+  for (final DiagnosticsNode node in properties) {
+    if (!foundStackTrace && node is DiagnosticsStackTrace)
+      foundStackTrace = true;
+    if (_isDebugCreator(node)) {
+      yield* _parseDiagnosticsNode(node)!;
+    } else {
+      if (foundStackTrace) {
+        pending.add(node);
+      } else {
+        yield node;
+      }
+    }
+  }
+  yield* pending;
+}
+
+/// Transform the input [DiagnosticsNode].
+///
+/// Return null if input [DiagnosticsNode] is not applicable.
+Iterable<DiagnosticsNode>? _parseDiagnosticsNode(DiagnosticsNode node) {
+  if (!_isDebugCreator(node))
+    return null;
+  final DebugCreator debugCreator = node.value! as DebugCreator;
+  final Element element = debugCreator.element;
+  return _describeRelevantUserCode(element);
+}
+
+Iterable<DiagnosticsNode> _describeRelevantUserCode(Element element) {
+  if (!WidgetInspectorService.instance.isWidgetCreationTracked()) {
+    return <DiagnosticsNode>[
+      ErrorDescription(
+        'Widget creation tracking is currently disabled. Enabling '
+        'it enables improved error messages. It can be enabled by passing '
+        '`--track-widget-creation` to `flutter run` or `flutter test`.',
+      ),
+      ErrorSpacer(),
+    ];
+  }
+  final List<DiagnosticsNode> nodes = <DiagnosticsNode>[];
+  bool processElement(Element target) {
+    // TODO(chunhtai): should print out all the widgets that are about to cross
+    // package boundaries.
+    if (debugIsLocalCreationLocation(target)) {
+      nodes.add(
+        DiagnosticsBlock(
+          name: 'The relevant error-causing widget was',
+          children: <DiagnosticsNode>[
+            ErrorDescription('${target.widget.toStringShort()} ${_describeCreationLocation(target)}'),
+          ],
+        ),
+      );
+      nodes.add(ErrorSpacer());
+      return false;
+    }
+    return true;
+  }
+  if (processElement(element))
+    element.visitAncestorElements(processElement);
+  return nodes;
+}
+
+/// Returns if an object is user created.
+///
+/// This always returns false if it is not called in debug mode.
+///
+/// {@macro flutter.widgets.WidgetInspectorService.getChildrenSummaryTree}
+///
+/// Currently is local creation locations are only available for
+/// [Widget] and [Element].
+bool debugIsLocalCreationLocation(Object object) {
+  bool isLocal = false;
+  assert(() {
+    final _Location? location = _getCreationLocation(object);
+    if (location == null)
+      isLocal =  false;
+    isLocal = WidgetInspectorService.instance._isLocalCreationLocation(location);
+    return true;
+  }());
+  return isLocal;
+}
+
+/// Returns the creation location of an object in String format if one is available.
+///
+/// ex: "file:///path/to/main.dart:4:3"
+///
+/// {@macro flutter.widgets.WidgetInspectorService.getChildrenSummaryTree}
+///
+/// Currently creation locations are only available for [Widget] and [Element].
+String? _describeCreationLocation(Object object) {
+  final _Location? location = _getCreationLocation(object);
+  return location?.toString();
+}
+
+/// Returns the creation location of an object if one is available.
+///
+/// {@macro flutter.widgets.WidgetInspectorService.getChildrenSummaryTree}
+///
+/// Currently creation locations are only available for [Widget] and [Element].
+_Location? _getCreationLocation(Object? object) {
+  final Object? candidate =  object is Element ? object.widget : object;
+  return candidate is _HasCreationLocation ? candidate._location : null;
+}
+
+// _Location objects are always const so we don't need to worry about the GC
+// issues that are a concern for other object ids tracked by
+// [WidgetInspectorService].
+final Map<_Location, int> _locationToId = <_Location, int>{};
+final List<_Location> _locations = <_Location>[];
+
+int _toLocationId(_Location location) {
+  int? id = _locationToId[location];
+  if (id != null) {
+    return id;
+  }
+  id = _locations.length;
+  _locations.add(location);
+  _locationToId[location] = id;
+  return id;
+}
+
+/// A delegate that configures how a hierarchy of [DiagnosticsNode]s are
+/// serialized by the Flutter Inspector.
+@visibleForTesting
+class InspectorSerializationDelegate implements DiagnosticsSerializationDelegate {
+  /// Creates an [InspectorSerializationDelegate] that serialize [DiagnosticsNode]
+  /// for Flutter Inspector service.
+  InspectorSerializationDelegate({
+    this.groupName,
+    this.summaryTree = false,
+    this.maxDescendentsTruncatableNode = -1,
+    this.expandPropertyValues = true,
+    this.subtreeDepth = 1,
+    this.includeProperties = false,
+    required this.service,
+    this.addAdditionalPropertiesCallback,
+  });
+
+  /// Service used by GUI tools to interact with the [WidgetInspector].
+  final WidgetInspectorService service;
+
+  /// Optional `groupName` parameter which indicates that the json should
+  /// contain live object ids.
+  ///
+  /// Object ids returned as part of the json will remain live at least until
+  /// [WidgetInspectorService.disposeGroup()] is called on [groupName].
+  final String? groupName;
+
+  /// Whether the tree should only include nodes created by the local project.
+  final bool summaryTree;
+
+  /// Maximum descendents of [DiagnosticsNode] before truncating.
+  final int maxDescendentsTruncatableNode;
+
+  @override
+  final bool includeProperties;
+
+  @override
+  final int subtreeDepth;
+
+  @override
+  final bool expandPropertyValues;
+
+  /// Callback to add additional experimental serialization properties.
+  ///
+  /// This callback can be used to customize the serialization of DiagnosticsNode
+  /// objects for experimental features in widget inspector clients such as
+  /// [Dart DevTools](https://github.com/flutter/devtools).
+  /// For example, [Dart DevTools](https://github.com/flutter/devtools)
+  /// can evaluate the following expression to register a VM Service API
+  /// with a custom serialization to experiment with visualizing layouts.
+  ///
+  /// The following code samples demonstrates adding the [RenderObject] associated
+  /// with an [Element] to the serialized data for all elements in the tree:
+  ///
+  /// ```dart
+  /// Map<String, Object> getDetailsSubtreeWithRenderObject(
+  ///   String id,
+  ///   String groupName,
+  ///   int subtreeDepth,
+  /// ) {
+  ///   return _nodeToJson(
+  ///     root,
+  ///     InspectorSerializationDelegate(
+  ///       groupName: groupName,
+  ///       summaryTree: false,
+  ///       subtreeDepth: subtreeDepth,
+  ///       includeProperties: true,
+  ///       service: this,
+  ///       addAdditionalPropertiesCallback: (DiagnosticsNode node, _SerializationDelegate delegate) {
+  ///         final Map<String, Object> additionalJson = <String, Object>{};
+  ///         final Object value = node.value;
+  ///         if (value is Element) {
+  ///           final renderObject = value.renderObject;
+  ///           additionalJson['renderObject'] = renderObject?.toDiagnosticsNode()?.toJsonMap(
+  ///             delegate.copyWith(
+  ///               subtreeDepth: 0,
+  ///               includeProperties: true,
+  ///             ),
+  ///           );
+  ///         }
+  ///         return additionalJson;
+  ///       },
+  ///     ),
+  ///  );
+  /// }
+  /// ```
+  final Map<String, Object>? Function(DiagnosticsNode, InspectorSerializationDelegate)? addAdditionalPropertiesCallback;
+
+  final List<DiagnosticsNode> _nodesCreatedByLocalProject = <DiagnosticsNode>[];
+
+  bool get _interactive => groupName != null;
+
+  @override
+  Map<String, Object?> additionalNodeProperties(DiagnosticsNode node) {
+    final Map<String, Object?> result = <String, Object?>{};
+    final Object? value = node.value;
+    if (_interactive) {
+      result['objectId'] = service.toId(node, groupName!);
+      result['valueId'] = service.toId(value, groupName!);
+    }
+    if (summaryTree) {
+      result['summaryTree'] = true;
+    }
+    final _Location? creationLocation = _getCreationLocation(value);
+    if (creationLocation != null) {
+      result['locationId'] = _toLocationId(creationLocation);
+      result['creationLocation'] = creationLocation.toJsonMap();
+      if (service._isLocalCreationLocation(creationLocation)) {
+        _nodesCreatedByLocalProject.add(node);
+        result['createdByLocalProject'] = true;
+      }
+    }
+    if (addAdditionalPropertiesCallback != null) {
+      result.addAll(addAdditionalPropertiesCallback!(node, this) ?? <String, Object>{});
+    }
+    return result;
+  }
+
+  @override
+  DiagnosticsSerializationDelegate delegateForNode(DiagnosticsNode node) {
+    // The tricky special case here is that when in the detailsTree,
+    // we keep subtreeDepth from going down to zero until we reach nodes
+    // that also exist in the summary tree. This ensures that every time
+    // you expand a node in the details tree, you expand the entire subtree
+    // up until you reach the next nodes shared with the summary tree.
+    return summaryTree || subtreeDepth > 1 || service._shouldShowInSummaryTree(node)
+        ? copyWith(subtreeDepth: subtreeDepth - 1)
+        : this;
+  }
+
+  @override
+  List<DiagnosticsNode> filterChildren(List<DiagnosticsNode> children, DiagnosticsNode owner) {
+    return service._filterChildren(children, this);
+  }
+
+  @override
+  List<DiagnosticsNode> filterProperties(List<DiagnosticsNode> properties, DiagnosticsNode owner) {
+    final bool createdByLocalProject = _nodesCreatedByLocalProject.contains(owner);
+    return properties.where((DiagnosticsNode node) {
+      return !node.isFiltered(createdByLocalProject ? DiagnosticLevel.fine : DiagnosticLevel.info);
+    }).toList();
+  }
+
+  @override
+  List<DiagnosticsNode> truncateNodesList(List<DiagnosticsNode> nodes, DiagnosticsNode? owner) {
+    if (maxDescendentsTruncatableNode >= 0 &&
+        owner!.allowTruncate == true &&
+        nodes.length > maxDescendentsTruncatableNode) {
+      nodes = service._truncateNodes(nodes, maxDescendentsTruncatableNode);
+    }
+    return nodes;
+  }
+
+  @override
+  DiagnosticsSerializationDelegate copyWith({int? subtreeDepth, bool? includeProperties}) {
+    return InspectorSerializationDelegate(
+      groupName: groupName,
+      summaryTree: summaryTree,
+      maxDescendentsTruncatableNode: maxDescendentsTruncatableNode,
+      expandPropertyValues: expandPropertyValues,
+      subtreeDepth: subtreeDepth ?? this.subtreeDepth,
+      includeProperties: includeProperties ?? this.includeProperties,
+      service: service,
+      addAdditionalPropertiesCallback: addAdditionalPropertiesCallback,
+    );
+  }
+}
diff --git a/lib/src/widgets/widget_span.dart b/lib/src/widgets/widget_span.dart
new file mode 100644
index 0000000..6cc7e44
--- /dev/null
+++ b/lib/src/widgets/widget_span.dart
@@ -0,0 +1,204 @@
+// Copyright 2014 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.
+
+import 'package:flute/ui.dart' as ui show ParagraphBuilder, PlaceholderAlignment;
+
+import 'package:flute/painting.dart';
+
+import 'framework.dart';
+
+/// An immutable widget that is embedded inline within text.
+///
+/// The [child] property is the widget that will be embedded. Children are
+/// constrained by the width of the paragraph.
+///
+/// The [child] property may contain its own [Widget] children (if applicable),
+/// including [Text] and [RichText] widgets which may include additional
+/// [WidgetSpan]s. Child [Text] and [RichText] widgets will be laid out
+/// independently and occupy a rectangular space in the parent text layout.
+///
+/// [WidgetSpan]s will be ignored when passed into a [TextPainter] directly.
+/// To properly layout and paint the [child] widget, [WidgetSpan] should be
+/// passed into a [Text.rich] widget.
+///
+/// {@tool snippet}
+///
+/// A card with `Hello World!` embedded inline within a TextSpan tree.
+///
+/// ```dart
+/// Text.rich(
+///   TextSpan(
+///     children: <InlineSpan>[
+///       TextSpan(text: 'Flutter is'),
+///       WidgetSpan(
+///         child: SizedBox(
+///           width: 120,
+///           height: 50,
+///           child: Card(
+///             child: Center(
+///               child: Text('Hello World!')
+///             )
+///           ),
+///         )
+///       ),
+///       TextSpan(text: 'the best!'),
+///     ],
+///   )
+/// )
+/// ```
+/// {@end-tool}
+///
+/// [WidgetSpan] contributes the semantics of the [WidgetSpan.child] to the
+/// semantics tree.
+///
+/// See also:
+///
+///  * [TextSpan], a node that represents text in an [InlineSpan] tree.
+///  * [Text], a widget for showing uniformly-styled text.
+///  * [RichText], a widget for finer control of text rendering.
+///  * [TextPainter], a class for painting [InlineSpan] objects on a [Canvas].
+@immutable
+class WidgetSpan extends PlaceholderSpan {
+  /// Creates a [WidgetSpan] with the given values.
+  ///
+  /// The [child] property must be non-null. [WidgetSpan] is a leaf node in
+  /// the [InlineSpan] tree. Child widgets are constrained by the width of the
+  /// paragraph they occupy. Child widget heights are unconstrained, and may
+  /// cause the text to overflow and be ellipsized/truncated.
+  ///
+  /// A [TextStyle] may be provided with the [style] property, but only the
+  /// decoration, foreground, background, and spacing options will be used.
+  const WidgetSpan({
+    required this.child,
+    ui.PlaceholderAlignment alignment = ui.PlaceholderAlignment.bottom,
+    TextBaseline? baseline,
+    TextStyle? style,
+  }) : assert(child != null),
+       assert(baseline != null || !(
+         identical(alignment, ui.PlaceholderAlignment.aboveBaseline) ||
+         identical(alignment, ui.PlaceholderAlignment.belowBaseline) ||
+         identical(alignment, ui.PlaceholderAlignment.baseline)
+       )),
+       super(
+         alignment: alignment,
+         baseline: baseline,
+         style: style,
+       );
+
+  /// The widget to embed inline within text.
+  final Widget child;
+
+  /// Adds a placeholder box to the paragraph builder if a size has been
+  /// calculated for the widget.
+  ///
+  /// Sizes are provided through `dimensions`, which should contain a 1:1
+  /// in-order mapping of widget to laid-out dimensions. If no such dimension
+  /// is provided, the widget will be skipped.
+  ///
+  /// The `textScaleFactor` will be applied to the laid-out size of the widget.
+  @override
+  void build(ui.ParagraphBuilder builder, { double textScaleFactor = 1.0, List<PlaceholderDimensions>? dimensions }) {
+    assert(debugAssertIsValid());
+    assert(dimensions != null);
+    final bool hasStyle = style != null;
+    if (hasStyle) {
+      builder.pushStyle(style!.getTextStyle(textScaleFactor: textScaleFactor));
+    }
+    assert(builder.placeholderCount < dimensions!.length);
+    final PlaceholderDimensions currentDimensions = dimensions![builder.placeholderCount];
+    builder.addPlaceholder(
+      currentDimensions.size.width,
+      currentDimensions.size.height,
+      alignment,
+      scale: textScaleFactor,
+      baseline: currentDimensions.baseline,
+      baselineOffset: currentDimensions.baselineOffset,
+    );
+    if (hasStyle) {
+      builder.pop();
+    }
+  }
+
+  /// Calls `visitor` on this [WidgetSpan]. There are no children spans to walk.
+  @override
+  bool visitChildren(InlineSpanVisitor visitor) {
+    return visitor(this);
+  }
+
+  @override
+  InlineSpan? getSpanForPositionVisitor(TextPosition position, Accumulator offset) {
+    if (position.offset == offset.value) {
+      return this;
+    }
+    offset.increment(1);
+    return null;
+  }
+
+  @override
+  int? codeUnitAtVisitor(int index, Accumulator offset) {
+    return null;
+  }
+
+  @override
+  RenderComparison compareTo(InlineSpan other) {
+    if (identical(this, other))
+      return RenderComparison.identical;
+    if (other.runtimeType != runtimeType)
+      return RenderComparison.layout;
+    if ((style == null) != (other.style == null))
+      return RenderComparison.layout;
+    final WidgetSpan typedOther = other as WidgetSpan;
+    if (child != typedOther.child || alignment != typedOther.alignment) {
+      return RenderComparison.layout;
+    }
+    RenderComparison result = RenderComparison.identical;
+    if (style != null) {
+      final RenderComparison candidate = style!.compareTo(other.style!);
+      if (candidate.index > result.index)
+        result = candidate;
+      if (result == RenderComparison.layout)
+        return result;
+    }
+    return result;
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    if (super != other)
+      return false;
+    return other is WidgetSpan
+        && other.child == child
+        && other.alignment == alignment
+        && other.baseline == baseline;
+  }
+
+  @override
+  int get hashCode => hashValues(super.hashCode, child, alignment, baseline);
+
+  /// Returns the text span that contains the given position in the text.
+  @override
+  InlineSpan? getSpanForPosition(TextPosition position) {
+    assert(debugAssertIsValid());
+    return null;
+  }
+
+  /// In debug mode, throws an exception if the object is not in a
+  /// valid configuration. Otherwise, returns true.
+  ///
+  /// This is intended to be used as follows:
+  ///
+  /// ```dart
+  /// assert(myWidgetSpan.debugAssertIsValid());
+  /// ```
+  @override
+  bool debugAssertIsValid() {
+    // WidgetSpans are always valid as asserts prevent invalid WidgetSpans
+    // from being constructed.
+    return true;
+  }
+}
diff --git a/lib/src/widgets/will_pop_scope.dart b/lib/src/widgets/will_pop_scope.dart
new file mode 100644
index 0000000..7c96284
--- /dev/null
+++ b/lib/src/widgets/will_pop_scope.dart
@@ -0,0 +1,153 @@
+// Copyright 2014 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.
+
+import 'framework.dart';
+import 'navigator.dart';
+import 'routes.dart';
+
+/// Registers a callback to veto attempts by the user to dismiss the enclosing
+/// [ModalRoute].
+///
+/// {@tool snippet --template=stateful_widget}
+///
+/// Whenever the back button is pressed, you will get a callback at [onWillPop],
+/// which returns a [Future]. If the [Future] returns true, the screen is
+/// popped.
+///
+/// ```dart
+/// bool shouldPop = true;
+/// @override
+/// Widget build(BuildContext context) {
+///   return WillPopScope (
+///     onWillPop: () async {
+///       return shouldPop;
+///     },
+///     child: Text('WillPopScope sample'),
+///   );
+/// }
+/// ```
+/// {@end-tool}
+///
+/// {@tool dartpad --template=stateful_widget_material_no_null_safety}
+/// ```dart
+/// bool shouldPop = true;
+/// @override
+/// Widget build(BuildContext context) {
+///   return WillPopScope(
+///     onWillPop: () async {
+///       return shouldPop;
+///     },
+///     child: Scaffold(
+///       appBar: AppBar(
+///         title: Text("Flutter WillPopScope demo"),
+///       ),
+///       body: Center(
+///         child: Column(
+///           mainAxisAlignment: MainAxisAlignment.center,
+///           children: [
+///             OutlinedButton(
+///               child: Text('Push'),
+///               onPressed: () {
+///                 Navigator.of(context).push(
+///                   MaterialPageRoute(
+///                     builder: (context) {
+///                       return MyStatefulWidget();
+///                     },
+///                   ),
+///                 );
+///               },
+///             ),
+///             OutlinedButton(
+///               child: Text('shouldPop: $shouldPop'),
+///               onPressed: () {
+///                 setState(
+///                   () {
+///                     shouldPop = !shouldPop;
+///                   },
+///                 );
+///               },
+///             ),
+///             Text("Push to a new screen, then tap on shouldPop "
+///                 "button to toggle its value. Press the back "
+///                 "button in the appBar to check its behaviour "
+///                 "for different values of shouldPop"),
+///           ],
+///         ),
+///       ),
+///     ),
+///   );
+/// }
+/// ```
+///
+/// {@end-tool}
+///
+/// See also:
+///
+///  * [ModalRoute.addScopedWillPopCallback] and [ModalRoute.removeScopedWillPopCallback],
+///    which this widget uses to register and unregister [onWillPop].
+///  * [Form], which provides an `onWillPop` callback that enables the form
+///    to veto a `pop` initiated by the app's back button.
+///
+class WillPopScope extends StatefulWidget {
+  /// Creates a widget that registers a callback to veto attempts by the user to
+  /// dismiss the enclosing [ModalRoute].
+  ///
+  /// The [child] argument must not be null.
+  const WillPopScope({
+    Key? key,
+    required this.child,
+    required this.onWillPop,
+  }) : assert(child != null),
+       super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  /// Called to veto attempts by the user to dismiss the enclosing [ModalRoute].
+  ///
+  /// If the callback returns a Future that resolves to false, the enclosing
+  /// route will not be popped.
+  final WillPopCallback? onWillPop;
+
+  @override
+  _WillPopScopeState createState() => _WillPopScopeState();
+}
+
+class _WillPopScopeState extends State<WillPopScope> {
+  ModalRoute<dynamic>? _route;
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    if (widget.onWillPop != null)
+      _route?.removeScopedWillPopCallback(widget.onWillPop!);
+    _route = ModalRoute.of(context);
+    if (widget.onWillPop != null)
+      _route?.addScopedWillPopCallback(widget.onWillPop!);
+  }
+
+  @override
+  void didUpdateWidget(WillPopScope oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    assert(_route == ModalRoute.of(context));
+    if (widget.onWillPop != oldWidget.onWillPop && _route != null) {
+      if (oldWidget.onWillPop != null)
+        _route!.removeScopedWillPopCallback(oldWidget.onWillPop!);
+      if (widget.onWillPop != null)
+        _route!.addScopedWillPopCallback(widget.onWillPop!);
+    }
+  }
+
+  @override
+  void dispose() {
+    if (widget.onWillPop != null)
+      _route?.removeScopedWillPopCallback(widget.onWillPop!);
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) => widget.child;
+}
diff --git a/lib/ui.dart b/lib/ui.dart
new file mode 100644
index 0000000..da28f4b
--- /dev/null
+++ b/lib/ui.dart
@@ -0,0 +1,28 @@
+library dart.ui;
+
+import 'dart:async';
+import 'dart:collection' as collection;
+import 'dart:convert';
+import 'dart:developer' as developer;
+import 'dart:io'; // ignore: unused_import
+import 'dart:isolate' show SendPort;
+import 'dart:math' as math;
+import 'dart:nativewrappers';
+import 'dart:typed_data';
+
+part 'src/ui/annotations.dart';
+part 'src/ui/channel_buffers.dart';
+part 'src/ui/compositing.dart';
+part 'src/ui/geometry.dart';
+part 'src/ui/hash_codes.dart';
+part 'src/ui/hooks.dart';
+part 'src/ui/isolate_name_server.dart';
+part 'src/ui/lerp.dart';
+part 'src/ui/natives.dart';
+part 'src/ui/painting.dart';
+part 'src/ui/platform_dispatcher.dart';
+part 'src/ui/plugins.dart';
+part 'src/ui/pointer.dart';
+part 'src/ui/semantics.dart';
+part 'src/ui/text.dart';
+part 'src/ui/window.dart';
diff --git a/lib/widgets.dart b/lib/widgets.dart
new file mode 100644
index 0000000..81d9cde
--- /dev/null
+++ b/lib/widgets.dart
@@ -0,0 +1,129 @@
+// Copyright 2014 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.
+
+/// The Flutter widgets framework.
+///
+/// To use, import `package:flutter/widgets.dart`.
+///
+/// See also:
+///
+///  * [flutter.dev/widgets](https://flutter.dev/widgets/)
+///    for a catalog of commonly-used Flutter widgets.
+library widgets;
+
+export 'package:vector_math/vector_math_64.dart' show Matrix4;
+export 'package:characters/characters.dart';
+
+export 'src/widgets/actions.dart';
+export 'src/widgets/animated_cross_fade.dart';
+export 'src/widgets/animated_list.dart';
+export 'src/widgets/animated_size.dart';
+export 'src/widgets/animated_switcher.dart';
+export 'src/widgets/annotated_region.dart';
+export 'src/widgets/app.dart';
+export 'src/widgets/async.dart';
+export 'src/widgets/autocomplete.dart';
+export 'src/widgets/autofill.dart';
+export 'src/widgets/automatic_keep_alive.dart';
+export 'src/widgets/banner.dart';
+export 'src/widgets/basic.dart';
+export 'src/widgets/binding.dart';
+export 'src/widgets/bottom_navigation_bar_item.dart';
+export 'src/widgets/color_filter.dart';
+export 'src/widgets/container.dart';
+export 'src/widgets/debug.dart';
+export 'src/widgets/dismissible.dart';
+export 'src/widgets/disposable_build_context.dart';
+export 'src/widgets/drag_target.dart';
+export 'src/widgets/draggable_scrollable_sheet.dart';
+export 'src/widgets/dual_transition_builder.dart';
+export 'src/widgets/editable_text.dart';
+export 'src/widgets/fade_in_image.dart';
+export 'src/widgets/focus_manager.dart';
+export 'src/widgets/focus_scope.dart';
+export 'src/widgets/focus_traversal.dart';
+export 'src/widgets/form.dart';
+export 'src/widgets/framework.dart';
+export 'src/widgets/gesture_detector.dart';
+export 'src/widgets/grid_paper.dart';
+export 'src/widgets/heroes.dart';
+export 'src/widgets/icon.dart';
+export 'src/widgets/icon_data.dart';
+export 'src/widgets/icon_theme.dart';
+export 'src/widgets/icon_theme_data.dart';
+export 'src/widgets/image.dart';
+export 'src/widgets/image_filter.dart';
+export 'src/widgets/image_icon.dart';
+export 'src/widgets/implicit_animations.dart';
+export 'src/widgets/inherited_model.dart';
+export 'src/widgets/inherited_notifier.dart';
+export 'src/widgets/inherited_theme.dart';
+export 'src/widgets/interactive_viewer.dart';
+export 'src/widgets/layout_builder.dart';
+export 'src/widgets/list_wheel_scroll_view.dart';
+export 'src/widgets/localizations.dart';
+export 'src/widgets/media_query.dart';
+export 'src/widgets/modal_barrier.dart';
+export 'src/widgets/navigation_toolbar.dart';
+export 'src/widgets/navigator.dart';
+export 'src/widgets/nested_scroll_view.dart';
+export 'src/widgets/notification_listener.dart';
+export 'src/widgets/orientation_builder.dart';
+export 'src/widgets/overflow_bar.dart';
+export 'src/widgets/overlay.dart';
+export 'src/widgets/overscroll_indicator.dart';
+export 'src/widgets/page_storage.dart';
+export 'src/widgets/page_view.dart';
+export 'src/widgets/pages.dart';
+export 'src/widgets/performance_overlay.dart';
+export 'src/widgets/placeholder.dart';
+export 'src/widgets/platform_view.dart';
+export 'src/widgets/preferred_size.dart';
+export 'src/widgets/primary_scroll_controller.dart';
+export 'src/widgets/raw_keyboard_listener.dart';
+export 'src/widgets/restoration.dart';
+export 'src/widgets/restoration_properties.dart';
+export 'src/widgets/router.dart';
+export 'src/widgets/routes.dart';
+export 'src/widgets/safe_area.dart';
+export 'src/widgets/scroll_activity.dart';
+export 'src/widgets/scroll_aware_image_provider.dart';
+export 'src/widgets/scroll_configuration.dart';
+export 'src/widgets/scroll_context.dart';
+export 'src/widgets/scroll_controller.dart';
+export 'src/widgets/scroll_metrics.dart';
+export 'src/widgets/scroll_notification.dart';
+export 'src/widgets/scroll_physics.dart';
+export 'src/widgets/scroll_position.dart';
+export 'src/widgets/scroll_position_with_single_context.dart';
+export 'src/widgets/scroll_simulation.dart';
+export 'src/widgets/scroll_view.dart';
+export 'src/widgets/scrollable.dart';
+export 'src/widgets/scrollbar.dart';
+export 'src/widgets/semantics_debugger.dart';
+export 'src/widgets/shortcuts.dart';
+export 'src/widgets/single_child_scroll_view.dart';
+export 'src/widgets/size_changed_layout_notifier.dart';
+export 'src/widgets/sliver.dart';
+export 'src/widgets/sliver_fill.dart';
+export 'src/widgets/sliver_layout_builder.dart';
+export 'src/widgets/sliver_persistent_header.dart';
+export 'src/widgets/sliver_prototype_extent_list.dart';
+export 'src/widgets/spacer.dart';
+export 'src/widgets/status_transitions.dart';
+export 'src/widgets/table.dart';
+export 'src/widgets/text.dart';
+export 'src/widgets/text_selection.dart';
+export 'src/widgets/texture.dart';
+export 'src/widgets/ticker_provider.dart';
+export 'src/widgets/title.dart';
+export 'src/widgets/transitions.dart';
+export 'src/widgets/tween_animation_builder.dart';
+export 'src/widgets/unique_widget.dart';
+export 'src/widgets/value_listenable_builder.dart';
+export 'src/widgets/viewport.dart';
+export 'src/widgets/visibility.dart';
+export 'src/widgets/widget_inspector.dart';
+export 'src/widgets/widget_span.dart';
+export 'src/widgets/will_pop_scope.dart';
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..ec6228a
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,12 @@
+name: flute
+environment:
+  sdk: '>=2.12.0-157.0.dev <3.0.0'
+dependencies:
+  characters: 1.1.0-nullsafety.5
+  collection: 1.15.0-nullsafety.5
+  meta: 1.3.0-nullsafety.6
+  typed_data: 1.3.0-nullsafety.5
+  vector_math: 2.1.0-nullsafety.5
+dev_dependencies:
+  args: any
+  process: any